diff --git a/.azure-pipelines/steps/build-package.yml b/.azure-pipelines/steps/build-package.yml deleted file mode 100644 index 81a6160b..00000000 --- a/.azure-pipelines/steps/build-package.yml +++ /dev/null @@ -1,31 +0,0 @@ -steps: -- task: UsePythonVersion@0 - inputs: - versionSpec: $(python.version) - architecture: '$(python.architecture)' - addToPath: true - displayName: Use Python $(python.version) - -- template: install-dependencies.yml - -- script: | - echo '##vso[task.setvariable variable=PIPENV_DEFAULT_PYTHON_VERSION]'$(python.version) - env: - PYTHON_VERSION: $(python.version) - -- template: create-virtualenv.yml - parameters: - python_version: $(python.version) - -- script: | - python -m pip install --upgrade wheel pip setuptools twine readme_renderer[md] - python setup.py sdist bdist_wheel - twine check dist/* - displayName: Build and check package - env: - PY_EXE: $(PY_EXE) - GIT_SSL_CAINFO: $(GIT_SSL_CAINFO) - LANG: $(LANG) - PIPENV_DEFAULT_PYTHON_VERSION: $(PIPENV_DEFAULT_PYTHON_VERSION) - PYTHONWARNINGS: ignore:DEPRECATION - PIPENV_NOSPIN: '1' diff --git a/.azure-pipelines/steps/create-virtualenv.yml b/.azure-pipelines/steps/create-virtualenv.yml deleted file mode 100644 index 5f6160c4..00000000 --- a/.azure-pipelines/steps/create-virtualenv.yml +++ /dev/null @@ -1,44 +0,0 @@ -parameters: - python_version: '' - -steps: - -- script: | - echo "##vso[task.setvariable variable=LANG]C.UTF-8" - echo "##vso[task.setvariable variable=PIP_PROCESS_DEPENDENCY_LINKS]1" - displayName: Set Environment Variables - -- ${{ if eq(parameters.vmImage, 'windows-2019') }}: - - powershell: | - pip install certifi - $env:PYTHON_PATH=$(python -c "import sys; print(sys.executable)") - $env:CERTIFI_CONTENT=$(python -m certifi) - echo "##vso[task.setvariable variable=GIT_SSL_CAINFO]$env:CERTIFI_CONTENT" - echo "##vso[task.setvariable variable=PY_EXE]$env:PYTHON_PATH" - displayName: Set Python Path - env: - PYTHONWARNINGS: 'ignore:DEPRECATION' -- ${{ if ne(parameters.vmImage, 'windows-2019') }}: - - bash: | - pip install certifi - PYTHON_PATH=$(python -c 'import sys; print(sys.executable)') - CERTIFI_CONTENT=$(python -m certifi) - echo "##vso[task.setvariable variable=GIT_SSL_CAINFO]$CERTIFI_CONTENT" - echo "##vso[task.setvariable variable=PY_EXE]$PYTHON_PATH" - displayName: Set Python Path - env: - PYTHONWARNINGS: 'ignore:DEPRECATION' - -- script: | - echo "Python path: $(PY_EXE)" - echo "GIT_SSL_CAINFO: $(GIT_SSL_CAINFO)" - echo "PIPENV PYTHON VERSION: $(python.version)" - echo "python_version: ${{ parameters.python_version }}" - git submodule sync - git submodule update --init --recursive - $(PY_EXE) -m pipenv install --deploy --dev --python="$(PY_EXE)" - env: - PIPENV_DEFAULT_PYTHON_VERSION: ${{ parameters.python_version }} - PYTHONWARNINGS: 'ignore:DEPRECATION' - PIPENV_NOSPIN: '1' - displayName: Make Virtualenv diff --git a/.azure-pipelines/steps/install-dependencies.yml b/.azure-pipelines/steps/install-dependencies.yml deleted file mode 100644 index 79684d4a..00000000 --- a/.azure-pipelines/steps/install-dependencies.yml +++ /dev/null @@ -1,5 +0,0 @@ -steps: -- script: 'python -m pip install --upgrade pip setuptools wheel -e .[dev,tests] --upgrade' - displayName: Upgrade Pip & Install Pipenv - env: - PYTHONWARNINGS: 'ignore:DEPRECATION' diff --git a/.azure-pipelines/steps/reinstall-pythons.yml b/.azure-pipelines/steps/reinstall-pythons.yml deleted file mode 100644 index 79647925..00000000 --- a/.azure-pipelines/steps/reinstall-pythons.yml +++ /dev/null @@ -1,34 +0,0 @@ -steps: - - script: | - # When you paste this, please make sure the indentation is preserved - # Fail out if any setups fail - set -e - - # Delete old Pythons - rm -rf $AGENT_TOOLSDIRECTORY/Python/2.7.16 - rm -rf $AGENT_TOOLSDIRECTORY/Python/3.5.7 - rm -rf $AGENT_TOOLSDIRECTORY/Python/3.7.3 - [ -e $AGENT_TOOLSDIRECTORY/Python/3.7.2 ] && [ -e $AGENT_TOOLSDIRECTORY/Python/3.5.5 ] && [ -e $AGENT_TOOLSDIRECTORY/Python/2.7.15 ] && exit 0 - # Download new Pythons - azcopy --recursive \ - --source https://vstsagenttools.blob.core.windows.net/tools/hostedtoolcache/linux/Python/2.7.15 \ - --destination $AGENT_TOOLSDIRECTORY/Python/2.7.15 - - azcopy --recursive \ - --source https://vstsagenttools.blob.core.windows.net/tools/hostedtoolcache/linux/Python/3.5.5 \ - --destination $AGENT_TOOLSDIRECTORY/Python/3.5.5 - - azcopy --recursive \ - --source https://vstsagenttools.blob.core.windows.net/tools/hostedtoolcache/linux/Python/3.7.2 \ - --destination $AGENT_TOOLSDIRECTORY/Python/3.7.2 - - # Install new Pythons - original_directory=$PWD - setups=$(find $AGENT_TOOLSDIRECTORY/Python -name setup.sh) - for setup in $setups; do - chmod +x $setup; - cd $(dirname $setup); - ./$(basename $setup); - cd $original_directory; - done; - displayName: 'Workaround: roll back Python versions' diff --git a/.azure-pipelines/steps/run-tests-linux.yml b/.azure-pipelines/steps/run-tests-linux.yml deleted file mode 100644 index 185a83b2..00000000 --- a/.azure-pipelines/steps/run-tests-linux.yml +++ /dev/null @@ -1,14 +0,0 @@ -parameters: - python_version: '' - -steps: -- script: | - # Fix Git SSL errors - echo "Using pipenv python version: $(PIPENV_DEFAULT_PYTHON_VERSION)" - git submodule sync && git submodule update --init --recursive - pipenv run pytest --junitxml=test-results.xml - displayName: Run integration tests - env: - PYTHONWARNINGS: ignore:DEPRECATION - PIPENV_NOSPIN: '1' - PIPENV_DEFAULT_PYTHON_VERSION: ${{ parameters.python_version }} diff --git a/.azure-pipelines/steps/run-tests-windows.yml b/.azure-pipelines/steps/run-tests-windows.yml deleted file mode 100644 index 1730fa14..00000000 --- a/.azure-pipelines/steps/run-tests-windows.yml +++ /dev/null @@ -1,21 +0,0 @@ -parameters: - python_version: '' - -steps: -- powershell: | - subst T: "$env:TEMP" - Write-Host "##vso[task.setvariable variable=TEMP]T:\" - Write-Host "##vso[task.setvariable variable=TMP]T:\" - Write-Host "##vso[task.setvariable variable=PIPENV_DEFAULT_PYTHON_VERSION]$env:PYTHON_VERSION" - Write-Host "##vso[task.setvariable variable=PIPENV_NOSPIN]1" - displayName: Fix Temp Variable - env: - PYTHON_VERSION: ${{ parameters.python_version }} - -- script: | - git submodule sync && git submodule update --init --recursive - pipenv run pytest -ra --ignore=pipenv\patched --ignore=pipenv\vendor --junitxml=test-results.xml tests - displayName: Run integration tests - env: - PYTHONWARNINGS: 'ignore:DEPRECATION' - PIPENV_NOSPIN: '1' diff --git a/.azure-pipelines/steps/run-tests.yml b/.azure-pipelines/steps/run-tests.yml deleted file mode 100644 index 011cecf2..00000000 --- a/.azure-pipelines/steps/run-tests.yml +++ /dev/null @@ -1,34 +0,0 @@ -steps: -- task: UsePythonVersion@0 - inputs: - versionSpec: $(python.version) - architecture: '$(python.architecture)' - addToPath: true - displayName: Use Python $(python.version) - -- template: install-dependencies.yml - -- script: | - echo '##vso[task.setvariable variable=PIPENV_DEFAULT_PYTHON_VERSION]'$(python.version) - env: - PYTHON_VERSION: $(python.version) - -- template: create-virtualenv.yml - parameters: - python_version: $(python.version) - -- ${{ if eq(parameters.vmImage, 'windows-2019') }}: - - template: run-tests-windows.yml - parameters: - python_version: $(python.version) -- ${{ if ne(parameters.vmImage, 'windows-2019') }}: - - template: run-tests-linux.yml - parameters: - python_version: $(python.version) - -- task: PublishTestResults@2 - displayName: Publish Test Results - inputs: - testResultsFiles: '**/test-results.xml' - testRunTitle: 'Python $(python.version)' - condition: succeededOrFailed() diff --git a/.azure-pipelines/steps/run-vendor-scripts.yml b/.azure-pipelines/steps/run-vendor-scripts.yml deleted file mode 100644 index 2aca1fe0..00000000 --- a/.azure-pipelines/steps/run-vendor-scripts.yml +++ /dev/null @@ -1,33 +0,0 @@ -parameters: - python_version: '' - -steps: -- task: UsePythonVersion@0 - inputs: - versionSpec: $(python.version) - architecture: '$(python.architecture)' - addToPath: true - displayName: Use Python $(python.version) - -- template: install-dependencies.yml - -- script: | - echo '##vso[task.setvariable variable=PIPENV_DEFAULT_PYTHON_VERSION]'$(python.version) - env: - PYTHON_VERSION: $(python.version) - -- template: create-virtualenv.yml - parameters: - python_version: $(python.version) - -- script: | - python -m pip install --upgrade invoke requests parver bs4 vistir towncrier pip setuptools wheel --upgrade-strategy=eager - python -m invoke vendoring.update - displayName: Run Vendor Scripts - env: - PY_EXE: $(PY_EXE) - GIT_SSL_CAINFO: $(GIT_SSL_CAINFO) - LANG: $(LANG) - PIPENV_DEFAULT_PYTHON_VERSION: '${{ parameters.python_version }}' - PYTHONWARNINGS: ignore:DEPRECATION - PIPENV_NOSPIN: '1' diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml deleted file mode 100644 index 46b2e799..00000000 --- a/.buildkite/pipeline.yml +++ /dev/null @@ -1,5 +0,0 @@ -steps: - - label: ":python:" - commands: - # - make - - ./run-tests.sh \ No newline at end of file diff --git a/.buildkite/project.json b/.buildkite/project.json deleted file mode 100644 index c6b1b270..00000000 --- a/.buildkite/project.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "Pipenv Test Suite", - "steps": [ - { - "type": "script", - "name": ":pipeline:", - "command": "buildkite-agent pipeline upload" - } - ] -} \ No newline at end of file diff --git a/.deepsource.toml b/.deepsource.toml new file mode 100644 index 00000000..902f3dc2 --- /dev/null +++ b/.deepsource.toml @@ -0,0 +1,16 @@ +version = 1 + +test_patterns = ["tests/**"] + +exclude_patterns = [ + "examples/**", + "pipenv/vendor/**", + "pipenv/patched/**" +] + +[[analyzers]] +name = "python" +enabled = true + + [analyzers.meta] + runtime_version = "3.x.x" diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index c9efdaea..00000000 --- a/.dockerignore +++ /dev/null @@ -1,11 +0,0 @@ -./examples -./tests -./docs -./news -./pipenv -./.git -./.buildkite -./peeps -./.github -./tasks -./.azure-pipelines diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 18e12434..00000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,42 +0,0 @@ -Be sure to check the existing issues (both open and closed!), and make sure you are running the latest version of Pipenv. - -If you're requesting a new feature, please use the PEEP process: - - https://github.com/pypa/pipenv/blob/master/peeps/PEEP-000.md - -Check the [diagnose documentation](https://docs.pipenv.org/diagnose/) for common issues before posting! We may close your issue if it is very similar to one of them. Please be considerate, or be on your way. - -Make sure to mention your debugging experience if the documented solution failed. - - -### Issue description - -Describe the issue briefly here. - -### Expected result - -Describe what you expected. - -### Actual result - -When possible, provide the verbose output (`--verbose`), especially for locking and dependencies resolving issues. - -### Steps to replicate - -Provide the steps to replicate (which usually at least includes the commands and the Pipfile). - -------------------------------------------------------------------------------- - -Please run `$ pipenv --support`, and paste the results here. Don't put backticks (`` ` ``) around it! The output already contains Markdown formatting. - -If you're on macOS, run the following: - - $ pipenv --support | pbcopy - -If you're on Windows, run the following: - - > pipenv --support | clip - -If you're on Linux, run the following: - - $ pipenv --support | xclip diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md index 8a30d5aa..e02f479c 100644 --- a/.github/ISSUE_TEMPLATE/Feature_request.md +++ b/.github/ISSUE_TEMPLATE/Feature_request.md @@ -5,7 +5,7 @@ about: Suggest an idea for this project Be sure to check the existing issues (both open and closed!), and make sure you are running the latest version of Pipenv. -Check the [diagnose documentation](https://docs.pipenv.org/diagnose/) for common issues and the [PEEP list](https://github.com/pypa/pipenv/blob/master/peeps/) before posting! We may close your issue if it is very similar to one of them. Please be considerate and follow the PEEP process, or be on your way. +Check the [diagnose documentation](https://pipenv.pypa.io/en/latest/diagnose/) for common issues and the [PEEP list](https://github.com/pypa/pipenv/blob/master/peeps/) before posting! We may close your issue if it is very similar to one of them. Please be considerate and follow the PEEP process, or be on your way. Make sure to mention your debugging experience if the documented solution failed. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 0e1c095b..325d14db 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -7,7 +7,7 @@ What is the thing you want to fix? Is it associated with an issue on GitHub? Ple Always consider opening an issue first to describe your problem, so we can discuss what is the best way to amend it. Note that if you do not describe the goal of this change or link to a related issue, the maintainers may close the PR without further review. -If your pull request makes a non-insignificant change to Pipenv, such as the user interface or intended functionality, please file a PEEP. +If your pull request makes a non-insignificant change to Pipenv, such as the user interface or intended functionality, please file a PEEP. https://github.com/pypa/pipenv/blob/master/peeps/PEEP-000.md @@ -22,7 +22,7 @@ How does this pull request fix your problem? Did you consider any alternatives? * [ ] A news fragment in the `news/` directory to describe this fix with the extension `.bugfix`, `.feature`, `.behavior`, `.doc`. `.vendor`. or `.trivial` (this will appear in the release changelog). Use semantic line breaks and name the file after the issue number or the PR #. diff --git a/docs/_templates/sidebarintro.html b/docs/_templates/sidebarintro.html index cbf7e5f8..335315a6 100644 --- a/docs/_templates/sidebarintro.html +++ b/docs/_templates/sidebarintro.html @@ -46,23 +46,23 @@
  • Pipenv-Pipes
  • -

    More projects founded by Kenneth Reitz:

    +

    More projects founded by Kenneth Reitz:

    Useful Links

    -

    More projects founded by Kenneth Reitz:

    +

    More projects founded by Kenneth Reitz:

    Useful Links

    diff --git a/docs/advanced.rst b/docs/advanced.rst index f410006b..e48d94c2 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -41,7 +41,7 @@ Very fancy. ☤ Using a PyPI Mirror ---------------------------- -If you'd like to override the default PyPI index urls with the url for a PyPI mirror, you can use the following:: +If you would like to override the default PyPI index URLs with the URL for a PyPI mirror, you can use the following:: $ pipenv install --pypi-mirror @@ -58,7 +58,6 @@ Alternatively, you can set the ``PIPENV_PYPI_MIRROR`` environment variable. ☤ Injecting credentials into Pipfiles via environment variables ----------------------------------------------------------------- - Pipenv will expand environment variables (if defined) in your Pipfile. Quite useful if you need to authenticate to a private PyPI:: @@ -71,10 +70,18 @@ Luckily - pipenv will hash your Pipfile *before* expanding environment variables (and, helpfully, will substitute the environment variables again when you install from the lock file - so no need to commit any secrets! Woo!) -If your credentials contain a special character, surround the references to the environment variables with quotation marks. For example, if your password contain a double quotation mark, surround the password variable with single quotation marks. Otherwise, you may get a ``ValueError, "No closing quotation"`` error while installing dependencies. :: +If your credentials contain special characters, make sure they are URL-encoded as specified in `rfc3986 `_. - [[source]] - url = "https://$USERNAME:'${PASSWORD}'@mypypi.example.com/simple" +Environment variables may be specified as ``${MY_ENVAR}`` or ``$MY_ENVAR``. + +On Windows, ``%MY_ENVAR%`` is supported in addition to ``${MY_ENVAR}`` or ``$MY_ENVAR``. + +Environment variables in the URL part of requirement specifiers can also be expanded, where the variable must be in the form of ``${VAR_NAME}``. Neither ``$VAR_NAME`` nor ``%VAR_NAME%`` is acceptable:: + + [[package]] + requests = {git = "git://${USERNAME}:${PASSWORD}@private.git.com/psf/requests.git", ref = "2.22.0"} + +Keep in mind that environment variables are expanded in runtime, leaving the entries in ``Pipfile`` or ``Pipfile.lock`` untouched. This is to avoid the accidental leakage of credentials in the source code. ☤ Specifying Basically Anything @@ -126,6 +133,12 @@ Or you can install packages exactly as specified in ``Pipfile.lock`` using the ` ``pipenv install --ignore-pipfile`` is nearly equivalent to ``pipenv sync``, but ``pipenv sync`` will *never* attempt to re-lock your dependencies as it is considered an atomic operation. ``pipenv install`` by default does attempt to re-lock unless using the ``--deploy`` flag. +You may only wish to verify your ``Pipfile.lock`` is up-to-date with dependencies specified in the ``Pipfile``, without installing:: + + $ pipenv verify + +The command will perform a verification, and return an exit code ``1`` when dependency locking is needed. This may be useful for cases when the ``Pipfile.lock`` file is subject to version control, so this command can be used within your CI/CD pipelines. + Deploying System Dependencies ///////////////////////////// @@ -149,7 +162,9 @@ Anaconda uses Conda to manage packages. To reuse Conda–installed Python packag ☤ Generating a ``requirements.txt`` ----------------------------------- -You can convert a ``Pipfile`` and ``Pipfile.lock`` into a ``requirements.txt`` file very easily, and get all the benefits of extras and other goodies we have included. +You can convert a ``Pipfile`` and ``Pipfile.lock`` into a ``requirements.txt`` +file very easily, and get all the benefits of extras and other goodies we have +included. Let's take this ``Pipfile``:: @@ -160,7 +175,10 @@ Let's take this ``Pipfile``:: [packages] requests = {version="*"} -And generate a ``requirements.txt`` out of it:: + [dev-packages] + pytest = {version="*"} + +And generate a set of requirements out of it with only the default dependencies:: $ pipenv lock -r chardet==3.0.4 @@ -169,22 +187,41 @@ And generate a ``requirements.txt`` out of it:: idna==2.6 urllib3==1.22 -If you wish to generate a ``requirements.txt`` with only the development requirements you can do that too! Let's take the following ``Pipfile``:: - - [[source]] - url = "https://pypi.python.org/simple" - verify_ssl = true - - [dev-packages] - pytest = {version="*"} - -And generate a ``requirements.txt`` out of it:: +As with other commands, passing ``--dev`` will include both the default and +development dependencies:: $ pipenv lock -r --dev + chardet==3.0.4 + requests==2.18.4 + certifi==2017.7.27.1 + idna==2.6 + urllib3==1.22 + py==1.4.34 + pytest==3.2.3 + +Finally, if you wish to generate a requirements file with only the +development requirements you can do that too, using the ``--dev-only`` +flag:: + + $ pipenv lock -r --dev-only + py==1.4.34 + pytest==3.2.3 + +The locked requirements are written to stdout, with shell output redirection +used to write them to a file:: + + $ pipenv lock -r > requirements.txt + $ pipenv lock -r --dev-only > dev-requirements.txt + $ cat requirements.txt + chardet==3.0.4 + requests==2.18.4 + certifi==2017.7.27.1 + idna==2.6 + urllib3==1.22 + $ cat dev-requirements.txt py==1.4.34 pytest==3.2.3 -Very fancy. ☤ Detection of Security Vulnerabilities --------------------------------------- @@ -199,9 +236,9 @@ Example:: django = "==1.10.1" $ pipenv check - Checking PEP 508 requirements… + Checking PEP 508 requirements... Passed! - Checking installed package safety… + Checking installed package safety... 33075: django >=1.10,<1.10.3 resolved (1.10.1 installed)! Django before 1.8.x before 1.8.16, 1.9.x before 1.9.11, and 1.10.x before 1.10.3, when settings.DEBUG is True, allow remote attackers to conduct DNS rebinding attacks by leveraging failure to validate the HTTP Host header against settings.ALLOWED_HOSTS. @@ -237,16 +274,15 @@ Example:: .. note:: - In order to enable this functionality while maintaining its permissive - copyright license, `pipenv` embeds an API client key for the backend - Safety API operated by pyup.io rather than including a full copy of the - CC-BY-NC-SA licensed Safety-DB database. This embedded client key is - shared across all `pipenv check` users, and hence will be subject to - API access throttling based on overall usage rather than individual - client usage. + Each month, `PyUp.io` updates the ``safety`` database of + insecure Python packages and `makes it available to the + community for free `__. Pipenv + makes an API call to retrieve those results and use them + each time you run ``pipenv check`` to show you vulnerable + dependencies. - You can also use your own safety API key by setting the - environment variable ``PIPENV_PYUP_API_KEY``. + For more up-to-date vulnerability data, you may also use your own safety + API key by setting the environment variable ``PIPENV_PYUP_API_KEY``. ☤ Community Integrations @@ -276,7 +312,7 @@ Works in progress: Pipenv allows you to open any Python module that is installed (including ones in your codebase), with the ``$ pipenv open`` command:: $ pipenv install -e git+https://github.com/kennethreitz/background.git#egg=background - Installing -e git+https://github.com/kennethreitz/background.git#egg=background… + Installing -e git+https://github.com/kennethreitz/background.git#egg=background... ... Updated Pipfile.lock! @@ -308,17 +344,17 @@ This is a very fancy feature, and we're very proud of it:: python_version = "3.6" $ pipenv install - Warning: Python 3.6 was not found on your system… + Warning: Python 3.6 was not found on your system... Would you like us to install latest CPython 3.6 with pyenv? [Y/n]: y - Installing CPython 3.6.2 with pyenv (this may take a few minutes)… + Installing CPython 3.6.2 with pyenv (this may take a few minutes)... ... - Making Python installation global… - Creating a virtualenv for this project… - Using /Users/kennethreitz/.pyenv/shims/python3 to create virtualenv… + Making Python installation global... + Creating a virtualenv for this project... + Using /Users/kennethreitz/.pyenv/shims/python3 to create virtualenv... ... No package provided, installing all dependencies. ... - Installing dependencies from Pipfile.lock… + Installing dependencies from Pipfile.lock... 🐍 ❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒ 5/5 — 00:00:03 To activate this project's virtualenv, run the following: $ pipenv shell @@ -336,7 +372,7 @@ If a ``.env`` file is present in your project, ``$ pipenv shell`` and ``$ pipenv HELLO=WORLD⏎ $ pipenv run python - Loading .env environment variables… + Loading .env environment variables... Python 2.7.13 (default, Jul 18 2017, 09:17:00) [GCC 4.2.1 Compatible Apple LLVM 8.1.0 (clang-802.0.42)] on darwin Type "help", "copyright", "credits" or "license" for more information. @@ -344,6 +380,21 @@ If a ``.env`` file is present in your project, ``$ pipenv shell`` and ``$ pipenv >>> os.environ['HELLO'] 'WORLD' +Shell like variable expansion is available in ``.env`` files using `${VARNAME}` syntax.:: + + $ cat .env + CONFIG_PATH=${HOME}/.config/foo + + $ pipenv run python + Loading .env environment variables... + Python 3.7.6 (default, Dec 19 2019, 22:52:49) + [GCC 9.2.1 20190827 (Red Hat 9.2.1-1)] on linux + Type "help", "copyright", "credits" or "license" for more information. + >>> import os + >>> os.environ['CONFIG_PATH'] + '/home/kennethreitz/.config/foo' + + This is very useful for keeping production credentials out of your codebase. We do not recommend committing ``.env`` files into source control! @@ -355,6 +406,8 @@ To prevent pipenv from loading the ``.env`` file, set the ``PIPENV_DONT_LOAD_ENV $ PIPENV_DONT_LOAD_ENV=1 pipenv shell +See `theskumar/python-dotenv `_ for more information on ``.env`` files. + ☤ Custom Script Shortcuts ------------------------- @@ -388,30 +441,13 @@ For example: $ pipenv run echospam "indeed" I am really a very silly example indeed -☤ Support for Environment Variables ------------------------------------ +You can then display the names and commands of your shortcuts by running ``pipenv scripts`` in your terminal. -Pipenv supports the usage of environment variables in place of authentication fragments -in your Pipfile. These will only be parsed if they are present in the ``[[source]]`` -section. For example: +:: -.. code-block:: toml - - [[source]] - url = "https://${PYPI_USERNAME}:${PYPI_PASSWORD}@my_private_repo.example.com/simple" - verify_ssl = true - name = "pypi" - - [dev-packages] - - [packages] - requests = {version="*", index="home"} - maya = {version="*", index="pypi"} - records = "*" - -Environment variables may be specified as ``${MY_ENVAR}`` or ``$MY_ENVAR``. - -On Windows, ``%MY_ENVAR%`` is supported in addition to ``${MY_ENVAR}`` or ``$MY_ENVAR``. + $ pipenv scripts + command script + echospam echo I am really a very silly example .. _configuration-with-environment-variables: @@ -451,7 +487,7 @@ In addition, you can also have Pipenv stick the virtualenv in ``project/.venv`` Pipenv is being used in projects like `Requests`_ for declaring development dependencies and running the test suite. -We've currently tested deployments with both `Travis-CI`_ and `tox`_ with success. +We have currently tested deployments with both `Travis-CI`_ and `tox`_ with success. Travis CI ///////// @@ -533,13 +569,17 @@ A 3rd party plugin, `tox-pipenv`_ is also available to use Pipenv natively with ☤ Shell Completion ------------------ -To enable completion in fish, add this to your config:: +To enable completion in fish, add this to your configuration:: - eval (pipenv --completion) + eval (env _PIPENV_COMPLETE=fish_source pipenv) -Alternatively, with bash or zsh, add this to your config:: +Alternatively, with zsh, add this to your configuration:: - eval "$(pipenv --completion)" + eval "$(_PIPENV_COMPLETE=zsh_source pipenv)" + +Alternatively, with bash, add this to your configuration:: + + eval "$(_PIPENV_COMPLETE=bash_source pipenv)" Magic shell completions are now enabled! @@ -571,9 +611,9 @@ at all, use the `PIP_IGNORE_INSTALLED` setting:: There is a subtle but very important distinction to be made between **applications** and **libraries**. This is a very common source of confusion in the Python community. -Libraries provide reusable functionality to other libraries and applications (let's use the umbrella term **projects** here). They are required to work alongside other libraries, all with their own set of subdependencies. They define **abstract dependencies**. To avoid version conflicts in subdependencies of different libraries within a project, libraries should never ever pin dependency versions. Although they may specify lower or (less frequently) upper bounds, if they rely on some specific feature/fix/bug. Library dependencies are specified via ``install_requires`` in ``setup.py``. +Libraries provide reusable functionality to other libraries and applications (let's use the umbrella term **projects** here). They are required to work alongside other libraries, all with their own set of sub-dependencies. They define **abstract dependencies**. To avoid version conflicts in sub-dependencies of different libraries within a project, libraries should never ever pin dependency versions. Although they may specify lower or (less frequently) upper bounds, if they rely on some specific feature/fix/bug. Library dependencies are specified via ``install_requires`` in ``setup.py``. -Libraries are ultimately meant to be used in some **application**. Applications are different in that they usually are not depended on by other projects. They are meant to be deployed into some specific environment and only then should the exact versions of all their dependencies and subdependencies be made concrete. To make this process easier is currently the main goal of Pipenv. +Libraries are ultimately meant to be used in some **application**. Applications are different in that they usually are not depended on by other projects. They are meant to be deployed into some specific environment and only then should the exact versions of all their dependencies and sub-dependencies be made concrete. To make this process easier is currently the main goal of Pipenv. To summarize: diff --git a/docs/basics.rst b/docs/basics.rst index 8cac8971..4a5e5f26 100644 --- a/docs/basics.rst +++ b/docs/basics.rst @@ -10,10 +10,11 @@ This document covers some of Pipenv's more basic features. ☤ Example Pipfile & Pipfile.lock -------------------------------- -Pipfiles contain information for the dependencies of the project, and supercede -the requirements.txt present in Python projects. You should add Pipfile to your -Git repository, and let users who clone the repository know that they need only -install Pipenv, and type ``pipenv install``. Pipenv is a reference +Pipfiles contain information for the dependencies of the project, and supersedes +the requirements.txt file used in most Python projects. You should add a Pipfile in the +Git repository letting users who clone the repository know the only thing required would be +installing Pipenv in the machine and typing ``pipenv install``. Pipenv is a reference + implementation for using Pipfile. .. _example_files: @@ -131,7 +132,7 @@ Example Pipfile.lock - Generally, keep both ``Pipfile`` and ``Pipfile.lock`` in version control. - Do not keep ``Pipfile.lock`` in version control if multiple versions of Python are being targeted. -- Specify your target Python version in your `Pipfile`'s ``[requires]`` section. Ideally, you should only have one target Python version, as this is a deployment tool. ``python_version`` should be in the format ``X.Y`` and ``python_full_version`` should be in ``X.Y.Z`` format. +- Specify your target Python version in your `Pipfile`'s ``[requires]`` section. Ideally, you should only have one target Python version, as this is a deployment tool. ``python_version`` should be in the format ``X.Y`` (or ``X``) and ``python_full_version`` should be in ``X.Y.Z`` format. - ``pipenv install`` is fully compatible with ``pip install`` syntax, for which the full documentation can be found `here `_. - Note that the ``Pipfile`` uses the `TOML Spec `_. @@ -216,7 +217,7 @@ To make inclusive or exclusive version comparisons you can use: :: The use of ``~=`` is preferred over the ``==`` identifier as the latter prevents pipenv from updating the packages: :: - $ pipenv install "requests~=2.2" # locks the major version of the package (this is equivalent to using ==2.*) + $ pipenv install "requests~=2.2" # locks the major version of the package (this is equivalent to using >=2.2, ==2.*) To avoid installing a specific version you can use the ``!=`` identifier. @@ -371,7 +372,7 @@ You can install packages with pipenv from git and other version control systems +:////@#egg= -The only optional section is the ``@`` section. When using git over SSH, you may use the shorthand vcs and scheme alias ``git+git@:/@#``. Note that this is translated to ``git+ssh://git@`` when parsed. +The only optional section is the ``@`` section. When using git over SSH, you may use the shorthand vcs and scheme alias ``git+git@:/@#egg=``. Note that this is translated to ``git+ssh://git@`` when parsed. Note that it is **strongly recommended** that you install any version-controlled dependencies in editable mode, using ``pipenv install -e``, in order to ensure that dependency resolution can be performed with an up to date copy of the repository each time it is performed, and that it includes all known dependencies. @@ -409,5 +410,118 @@ production environments for reproducible builds. .. note:: If you'd like a ``requirements.txt`` output of the lockfile, run ``$ pipenv lock -r``. - This will include all hashes, however (which is great!). To get a ``requirements.txt`` - without hashes, use ``$ pipenv run pip freeze``. + This will not include hashes, however. To get a ``requirements.txt`` + you can also use ``$ pipenv run pip freeze``. + + +☤ Pipenv and Docker Containers +------------------------------ + +In general, you should not have Pipenv inside a linux container image, since +it is a build tool. If you want to use it to build, and install the run time +dependencies for your application, you can use a multi stage build for creating +a virtual environment with your dependencies. In this approach, +Pipenv in installed in the base layer, it is then used to create the virtual +environment. In a later stage, in a ``runtime`` layer the virtual environment +is copied from the base layer, the layer containing pipenv and other build +dependencies is discarded. +This results in a smaller image, which can still run your application. +Here is an example ``Dockerfile``, which you can use as a starting point for +doing a multi stage build for your application:: + + FROM docker.io/python:3.9 AS builder + + RUN pip install --user pipenv + + # Tell pipenv to create venv in the current directory + ENV PIPENV_VENV_IN_PROJECT=1 + + # Pipefile contains requests + ADD Pipfile.lock Pipfile /usr/src/ + + WORKDIR /usr/src + + # NOTE: If you install binary packages required for a python module, you need + # to install them again in the runtime. For example, if you need to install pycurl + # you need to have pycurl build dependencies libcurl4-gnutls-dev and libcurl3-gnutls + # In the runtime container you need only libcurl3-gnutls + + # RUN apt install -y libcurl3-gnutls libcurl4-gnutls-dev + + RUN /root/.local/bin/pipenv sync + + RUN /usr/src/.venv/bin/python -c "import requests; print(requests.__version__)" + + FROM docker.io/python:3.9 AS runtime + + RUN mkdir -v /usr/src/venv + + COPY --from=builder /usr/src/.venv/ /usr/src/venv/ + + RUN /usr/src/venv/bin/python -c "import requests; print(requests.__version__)" + + # HERE GOES ANY CODE YOU NEED TO ADD TO CREATE YOUR APPLICATION'S IMAGE + # For example + # RUN apt install -y libcurl3-gnutls + # RUN adduser --uid 123123 coolio + + WORKDIR /usr/src/ + + USER coolio + + CMD ["./venv/bin/python", "-m", "run.py"] + +.. Note:: + + Pipenv is not meant to run as root. However, in the multi stage build above + it is done never the less. A calculated risk, since the intermediatiary image + is discarded. + The runtime image later shows that you should create a user and user it to + run your applicaion. + **Once again, you should not run pipenv as root (or Admin on Windows) normally. + This could lead to breakage of your Python installation, or even your complete + OS.** + +When you build an image with this example (assuming requests is found in Pipefile), you +will see that ``requests`` is installed in the ``runtime`` image:: + + $ sudo docker build --no-cache -t oz/123:0.1 . + Sending build context to Docker daemon 1.122MB + Step 1/12 : FROM docker.io/python:3.9 AS builder + ---> 81f391f1a7d7 + Step 2/12 : RUN pip install --user pipenv + ---> Running in b83ed3c28448 + ... trimmed ... + ---> 848743eb8c65 + Step 4/12 : ENV PIPENV_VENV_IN_PROJECT=1 + ---> Running in 814e6f5fec5b + Removing intermediate container 814e6f5fec5b + ---> 20167b4a13e1 + Step 5/12 : ADD Pipfile.lock Pipfile /usr/src/ + ---> c7632cb3d5bd + Step 6/12 : WORKDIR /usr/src + ---> Running in 1d75c6cfce10 + Removing intermediate container 1d75c6cfce10 + ---> 2dcae54cc2e5 + Step 7/12 : RUN /root/.local/bin/pipenv sync + ---> Running in 1a00b326b1ee + Creating a virtualenv for this project... + ... trimmed ... + ✔ Successfully created virtual environment! + Virtualenv location: /usr/src/.venv + Installing dependencies from Pipfile.lock (fe5a22)... + ... trimmed ... + Step 8/12 : RUN /usr/src/.venv/bin/python -c "import requests; print(requests.__version__)" + ---> Running in 3a66e3ce4a11 + 2.27.1 + Removing intermediate container 3a66e3ce4a11 + ---> 1db657d0ac17 + Step 9/12 : FROM docker.io/python:3.9 AS runtime + ... trimmed ... + Step 12/12 : RUN /usr/src/venv/bin/python -c "import requests; print(requests.__version__)" + ---> Running in fa39ba4080c5 + 2.27.1 + Removing intermediate container fa39ba4080c5 + ---> 2b1c90fd414e + Successfully built 2b1c90fd414e + Successfully tagged oz/123:0.1 diff --git a/docs/conf.py b/docs/conf.py index 90e16afe..e5aa5953 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # pipenv documentation build configuration file, created by # sphinx-quickstart on Mon Jan 30 13:28:36 2017. @@ -18,7 +17,6 @@ # import os - # Path hackery to get current version number. here = os.path.abspath(os.path.dirname(__file__)) @@ -56,9 +54,9 @@ source_suffix = '.rst' master_doc = 'index' # General information about the project. -project = u'pipenv' -copyright = u'2020. A project founded by Kenneth Reitz' -author = u'Python Packaging Authority' +project = 'pipenv' +copyright = '2020. A project founded by Kenneth Reitz' +author = 'Python Packaging Authority' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -87,7 +85,6 @@ pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True - # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for @@ -155,8 +152,8 @@ latex_elements = { # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'pipenv.tex', u'pipenv Documentation', - u'Kenneth Reitz', 'manual'), + (master_doc, 'pipenv.tex', 'pipenv Documentation', + 'Kenneth Reitz', 'manual'), ] @@ -165,7 +162,7 @@ latex_documents = [ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, 'pipenv', u'pipenv Documentation', + (master_doc, 'pipenv', 'pipenv Documentation', [author], 1) ] @@ -176,7 +173,7 @@ man_pages = [ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'pipenv', u'pipenv Documentation', + (master_doc, 'pipenv', 'pipenv Documentation', author, 'pipenv', 'One line description of project.', 'Miscellaneous'), ] diff --git a/docs/dev/contributing.rst b/docs/dev/contributing.rst index 0fb0c36f..d43e3ec5 100644 --- a/docs/dev/contributing.rst +++ b/docs/dev/contributing.rst @@ -8,13 +8,7 @@ contributing to the Pipenv project is *very* generous of you. This document lays out guidelines and advice for contributing to this project. If you're thinking of contributing, please start by reading this document and -getting a feel for how contributing to this project works. If you have any -questions, feel free to reach out to either `Dan Ryan`_, `Tzu-ping Chung`_, -or `Nate Prewitt`_, the primary maintainers. - -.. _Dan Ryan: https://github.com/techalchemy -.. _Tzu-ping Chung: https://github.com/uranusjr -.. _Nate Prewitt: https://github.com/nateprewitt +getting a feel for how contributing to this project works. The guide is split into sections based on the type of contribution you're thinking of making, with a section that covers general guidelines for all @@ -29,16 +23,15 @@ Be Cordial **Be cordial or be on your way**. *—Kenneth Reitz* +.. _be cordial or be on your way: https://kennethreitz.org/essays/2013/01/27/be-cordial-or-be-on-your-way + Pipenv has one very important rule governing all forms of contribution, including reporting bugs or requesting features. This golden rule is -"`be cordial or be on your way`_". +"`be cordial or be on your way`_" **All contributions are welcome**, as long as everyone involved is treated with respect. -.. _be cordial or be on your way: https://www.kennethreitz.org/essays/be-cordial-or-be-on-your-way - - .. _early-feedback: Get Early Feedback @@ -75,11 +68,9 @@ answered promptly and accurately. .. _Stack Overflow: https://stackoverflow.com/ - Code Contributions ------------------ - Steps for Submitting Code ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -102,26 +93,14 @@ When contributing code, you'll want to follow this checklist: The following sub-sections go into more detail on some of the points above. -.. _development philosophy: https://docs.pipenv.org/dev/philosophy/ - +.. _development philosophy: https://pipenv.pypa.io/en/latest/dev/philosophy/ .. _dev-setup: Development Setup ~~~~~~~~~~~~~~~~~ -To get your development environment setup, run: - -.. code-block:: sh - - pip install -e . - pipenv install --dev - - -This will install the repo version of Pipenv and then install the development -dependencies. Once that has completed, you can start developing. - -The repo version of Pipenv must be installed over other global versions to +The repository version of Pipenv must be installed over other global versions to resolve conflicts with the ``pipenv`` folder being implicitly added to ``sys.path``. See `pypa/pipenv#2557`_ for more details. @@ -152,7 +131,7 @@ tests, the standard pytest filters are available, such as: Code Review ~~~~~~~~~~~ -Contributions will not be merged until they've been code reviewed. You should +Contributions will not be merged until they have been code reviewed. You should implement any code review feedback unless you strongly object to it. In the event that you object to the code review feedback, you should make your case clearly and calmly. If, after doing so, the feedback is judged to still apply, @@ -171,7 +150,6 @@ a ``.tar.gz`` or universal wheel is not available, add wheels for all available architectures and platforms. - Documentation Contributions --------------------------- @@ -190,7 +168,6 @@ When presenting Python code, use single-quoted strings (``'hello'`` instead of .. _reStructuredText: http://docutils.sourceforge.net/rst.html .. _Sphinx: http://sphinx-doc.org/index.html - .. _bug-reports: Bug Reports @@ -205,7 +182,7 @@ be aware of the following things when filing bug reports: to check whether your bug report or feature request has been mentioned in the past. Duplicate bug reports and feature requests are a huge maintenance burden on the limited resources of the project. If it is clear from your - report that you would have struggled to find the original, that's ok, but + report that you would have struggled to find the original, that's okay, but if searching for a selection of words in your issue title would have found the duplicate then the issue will likely be closed extremely abruptly. 2. When filing bug reports about exceptions or tracebacks, please include the @@ -241,11 +218,30 @@ be aware of the following things when filing bug reports: Run the tests ------------- -Three ways of running the tests are as follows: +Two ways of running the tests are as follows: -1. ``make test`` (which uses ``docker``) -2. ``./run-tests.sh`` or ``run-tests.bat`` -3. Using pipenv: +1. ``./run-tests.sh`` or ``run-tests.bat`` + +Note that, you override the default Python Pipenv will use with +PIPENV_PYTHON and the Python binary name with PYTHON in case it +is not called ``python`` on your system or in case you have many. +Here is an example how you can override both variables (you can +override just one too):: + + $ PYTHON=python3.8 PIPENV_PYTHON=python3.9 run-tests.sh + +You can also do:: + + $ PYTHON=/opt/python/python3.10/python3 run-tests.sh + +If you need to change how pytest is invoked, see how to run the +test suite manually. The ``run-tests.sh`` script does the same +steps the Github CI workflow does, and as such it is recommended +you run it before you open a PR. Taking this second approach, +will allow you, for example, to run a single test case, or +``fail fast`` if you need it. + +2. Manually, which repeat the steps of the scripts above: .. code-block:: console @@ -253,9 +249,26 @@ Three ways of running the tests are as follows: $ cd pipenv $ git submodule sync && git submodule update --init --recursive $ pipenv install --dev - $ pipenv run pytest + $ pipenv run pytest [--any optional arguments to pytest] -For the last two, it is important that your environment is setup correctly, and +The second options assumes you already have ``pipenv`` on your system. +And simply repeats all the steps in the script above. + +Preferably, you should be running your tests in a Linux container +(or FreeBSD Jail or even VM). This will guarantee that you don't break +stuff, and that the tests run in a pristine environment. + +Consider doing, something like: + +``` +$ docker run --rm -v $(pwd):/usr/src -it python:3.7 bash +# inside the container +# adduser --disabled-password debian +# su debian && cd /usr/src/ +# bash run-tests.sh +``` + +It is important that your environment is setup correctly, and this may take some work, for example, on a specific Mac installation, the following steps may be needed:: diff --git a/docs/dev/philosophy.rst b/docs/dev/philosophy.rst deleted file mode 100644 index 3f4c0bd0..00000000 --- a/docs/dev/philosophy.rst +++ /dev/null @@ -1,25 +0,0 @@ -Development Philosophy -====================== - -Pipenv is an open but opinionated tool, created by an open but opinionated developer. - - -Management Style -~~~~~~~~~~~~~~~~ - - **To be updated (as of March 2020)**. - -`Kenneth Reitz `__ is the BDFL. He has final say in any decision related to the Pipenv project. Kenneth is responsible for the direction and form of the library, as well as its presentation. In addition to making decisions based on technical merit, he is responsible for making decisions based on the development philosophy of Pipenv. - -`Dan Ryan `__, `Tzu-ping Chung `__, and `Nate Prewitt `__ are the core contributors. -They are responsible for triaging bug reports, reviewing pull requests and ensuring that Kenneth is kept up to speed with developments around the library. -The day-to-day managing of the project is done by the core contributors. They are responsible for making judgements about whether or not a feature request is -likely to be accepted by Kenneth. - -Values -~~~~~~ - -- Simplicity is always better than functionality. -- Listen to everyone, then disregard it. -- The API is all that matters. Everything else is secondary. -- Fit the 90% use-case. Ignore the nay-sayers. diff --git a/docs/diagnose.rst b/docs/diagnose.rst index 5cc47f4c..92a1e264 100644 --- a/docs/diagnose.rst +++ b/docs/diagnose.rst @@ -29,7 +29,7 @@ usually one of the following locations: * ``%LOCALAPPDATA%\pipenv\pipenv\Cache`` (Windows) * ``~/.cache/pipenv`` (other operating systems) -Pipenv does not install prereleases (i.e. a version with an alpha/beta/etc. +Pipenv does not install pre-releases (i.e. a version with an alpha/beta/etc. suffix, such as *1.0b1*) by default. You will need to pass the ``--pre`` flag in your command, or set @@ -58,14 +58,9 @@ distributions, with version name like ``3.6.4`` or similar. ------------------------------------------------------------------ Pipenv by default uses the Python it is installed against to create the -virtualenv. You can set the ``--python`` option, or -``$PYENV_ROOT/shims/python`` to let it consult pyenv when choosing the -interpreter. See :ref:`specifying_versions` for more information. - -If you want Pipenv to automatically “do the right thing”, you can set the -environment variable ``PIPENV_PYTHON`` to ``$PYENV_ROOT/shims/python``. This -will make Pipenv use pyenv’s active Python version to create virtual -environments by default. +virtualenv. You can set the ``--python`` option to ``$(pyenv which python)`` +to use your current pyenv interpreter. See :ref:`specifying_versions` for more +information. .. _unknown-local-diagnose: @@ -101,18 +96,6 @@ This may be related to your locale setting. See :ref:`unknown-local-diagnose` for a possible solution. -☤ ``shell`` does not show the virtualenv’s name in prompt ---------------------------------------------------------- - -This is intentional. You can do it yourself with either shell plugins, or -clever ``PS1`` configuration. If you really want it back, use - -:: - - pipenv shell -c - -instead (not available on Windows). - ☤ Pipenv does not respect dependencies in setup.py -------------------------------------------------- @@ -124,15 +107,15 @@ for more information. --------------------------------------------- When you configure a supervisor program's ``command`` with ``pipenv run ...``, you -need to set locale enviroment variables properly to make it work. +need to set locale environment variables properly to make it work. Add this line under ``[supervisord]`` section in ``/etc/supervisor/supervisord.conf``:: [supervisord] environment=LC_ALL='en_US.UTF-8',LANG='en_US.UTF-8' -☤ An exception is raised during ``Locking dependencies…`` ---------------------------------------------------------- +☤ An exception is raised during ``Locking dependencies...`` +----------------------------------------------------------- Run ``pipenv lock --clear`` and try again. The lock sequence caches results to speed up subsequent runs. The cache may contain faulty results if a bug diff --git a/docs/index.rst b/docs/index.rst index 26bfde19..8447c468 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -32,7 +32,7 @@ Pipenv is primarily meant to provide users and developers of applications with a The problems that Pipenv seeks to solve are multi-faceted: - You no longer need to use ``pip`` and ``virtualenv`` separately. They work together. -- Managing a ``requirements.txt`` file `can be problematic `_, so Pipenv uses ``Pipfile`` and ``Pipfile.lock`` to separate abstract dependency declarations from the last tested combination. +- Managing a ``requirements.txt`` file `can be problematic `__, so Pipenv uses ``Pipfile`` and ``Pipfile.lock`` to separate abstract dependency declarations from the last tested combination. - Hashes are used everywhere, always. Security. Automatically expose security vulnerabilities. - Strongly encourage the use of the latest versions of dependencies to minimize security risks `arising from outdated components `_. - Give you insight into your dependency graph (e.g. ``$ pipenv graph``). @@ -48,15 +48,19 @@ You can quickly play with Pipenv right in your browser: Install Pipenv Today! --------------------- -If you're on MacOS, you can install Pipenv easily with Homebrew. You can also use Linuxbrew on Linux using the same command:: +If you already have Python and pip, you can easily install Pipenv into your home directory:: - $ brew install pipenv + $ pip install --user pipenv Or, if you're using Fedora 28:: $ sudo dnf install pipenv -Otherwise, refer to the :ref:`installing-pipenv` chapter for instructions. +It's possible to install Pipenv with Homebrew on MacOS, or with Linuxbrew on Linux systems. However, **this is now discouraged**, because updates to the brewed Python distribution will break Pipenv, and perhaps all virtual environments managed by it. You'll then need to re-install Pipenv at least. If you want to give it a try despite this warning, use:: + + $ brew install pipenv + +More detailed installation instructions can be found in the :ref:`installing-pipenv` chapter. ✨🍰✨ @@ -84,7 +88,7 @@ User Testimonials - Automatically finds your project home, recursively, by looking for a ``Pipfile``. - Automatically generates a ``Pipfile``, if one doesn't exist. - Automatically creates a virtualenv in a standard location. -- Automatically adds/removes packages to a ``Pipfile`` when they are un/installed. +- Automatically adds/removes packages to a ``Pipfile`` when they are installed or uninstalled. - Automatically loads ``.env`` files, if they exist. The main commands are ``install``, ``uninstall``, and ``lock``, which generates a ``Pipfile.lock``. These are intended to replace ``$ pip install`` usage, as well as manual virtualenv management (to activate a virtualenv, run ``$ pipenv shell``). @@ -106,7 +110,7 @@ Other Commands - ``graph`` will show you a dependency graph of your installed dependencies. - ``shell`` will spawn a shell with the virtualenv activated. This shell can be deactivated by using ``exit``. - ``run`` will run a given command from the virtualenv, with any arguments forwarded (e.g. ``$ pipenv run python`` or ``$ pipenv run pip freeze``). -- ``check`` checks for security vulnerabilities and asserts that PEP 508 requirements are being met by the current environment. +- ``check`` checks for security vulnerabilities and asserts that `PEP 508 `_ requirements are being met by the current environment. Further Documentation Guides @@ -126,7 +130,6 @@ Contribution Guides .. toctree:: :maxdepth: 2 - dev/philosophy dev/contributing Indices and tables @@ -134,4 +137,3 @@ Indices and tables * :ref:`genindex` * :ref:`modindex` -* :ref:`search` diff --git a/docs/install.rst b/docs/install.rst index 6aceba0c..ec62edd1 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -71,7 +71,7 @@ Homebrew/Linuxbrew installer takes care of pip for you. =================== Pipenv is a dependency manager for Python projects. If you're familiar -with Node.js' `npm`_ or Ruby's `bundler`_, it is similar in spirit to those +with Node\.js's `npm`_ or Ruby's `bundler`_, it is similar in spirit to those tools. While pip can install Python packages, Pipenv is recommended as it's a higher-level tool that simplifies dependency management for common use cases. @@ -80,28 +80,25 @@ cases. .. _bundler: http://bundler.io/ -☤ Homebrew Installation of Pipenv ---------------------------------- +☤ Isolated Installation of Pipenv with Pipx +------------------------------------------- -`Homebrew`_ is a popular open-source package management system for macOS. For Linux users, `Linuxbrew`_ is a Linux port of that. +`Pipx`_ is a tool to help you install and run end-user applications written in Python. It installs applications +into an isolated and clean environment on their own. To install pipx, just run:: -Installing pipenv via Homebrew or Linuxbrew will keep pipenv and all of its dependencies in -an isolated virtual environment so it doesn't interfere with the rest of your -Python installation. + $ pip install --user pipx -Once you have installed Homebrew or Linuxbrew simply run:: +Once you have ``pipx`` ready on your system, continue to install Pipenv:: - $ brew install pipenv + $ pipx install pipenv -To upgrade pipenv at any time:: - - $ brew upgrade pipenv +.. _Pipx: https://pypa.github.io/pipx/ ☤ Pragmatic Installation of Pipenv ---------------------------------- -If you have a working installation of pip, and maintain certain "toolchain" type Python modules as global utilities in your user environment, pip `user installs `_ allow for installation into your home directory. Note that due to interaction between dependencies, you should limit tools installed in this way to basic building blocks for a Python workflow like virtualenv, pipenv, tox, and similar software. +If you have a working installation of pip, and maintain certain "tool-chain" type Python modules as global utilities in your user environment, pip `user installs `_ allow for installation into your home directory. Note that due to interaction between dependencies, you should limit tools installed in this way to basic building blocks for a Python workflow like virtualenv, pipenv, tox, and similar software. To install:: @@ -149,6 +146,27 @@ If you don't even have pip installed, you can use this crude installation method $ curl https://raw.githubusercontent.com/pypa/pipenv/master/get-pipenv.py | python +☤ Homebrew Installation of Pipenv(Discouraged) +---------------------------------------------- +`Homebrew`_ is a popular open-source package management system for macOS. For Linux users, `Linuxbrew`_ is a Linux port of that. + +Installing pipenv via Homebrew or Linuxbrew will keep pipenv and all of its dependencies in +an isolated virtual environment so it doesn't interfere with the rest of your +Python installation. + +Once you have installed Homebrew or Linuxbrew simply run:: + + $ brew install pipenv + +To upgrade pipenv at any time:: + + $ brew upgrade pipenv + +.. Note:: + Homebrew installation is discouraged because each time the Homebrew Python is upgraded, which Pipenv depends on, + users have to re-install Pipenv, and perhaps all virtual environments managed by it. + + ☤ Installing packages for your project ====================================== @@ -159,36 +177,44 @@ tutorial) and run:: $ cd myproject $ pipenv install requests +.. Note:: + + Pipenv is designed to be used by non-privileged OS users. It is not meant + to install or handle packages for the whole OS. Running Pipenv as ``root`` + or with ``sudo`` (or ``Admin`` on Windows) is highly discouraged and might + lead to unintend breakage of your OS. + Pipenv will install the excellent `Requests`_ library and create a ``Pipfile`` for you in your project's directory. The ``Pipfile`` is used to track which dependencies your project needs in case you need to re-install them, such as when you share your project with others. You should get output similar to this (although the exact paths shown will vary):: - Creating a Pipfile for this project... - Creating a virtualenv for this project... - Using base prefix '/usr/local/Cellar/python3/3.6.2/Frameworks/Python.framework/Versions/3.6' - New python executable in ~/.local/share/virtualenvs/tmp-agwWamBd/bin/python3.6 - Also creating executable in ~/.local/share/virtualenvs/tmp-agwWamBd/bin/python - Installing setuptools, pip, wheel...done. + pipenv install requests + Creating a virtualenv for this project... + Pipfile: /home/user/myproject/Pipfile + sing /home/user/.local/share/virtualenvs/pipenv-Cv0J3wbi/bin/python3.9 (3.9.9) to create virtualenv... + Creating virtual environment...created virtual environment CPython3.9.9.final.0-64 in 1142ms + creator CPython3Posix(dest=/home/user/.local/share/virtualenvs/myproject-R3jRVewK, clear=False, no_vcs_ignore=False, global=False) + seeder FromAppData(download=False, pip=bundle, setuptools=bundle, wheel=bundle, via=copy, app_data_dir=/home/user/.local/share/virtualenv) + added seed packages: pip==21.3.1, setuptools==60.2.0, wheel==0.37.1 + activators BashActivator,CShellActivator,FishActivator,NushellActivator,PowerShellActivator,PythonActivator - Virtualenv location: ~/.local/share/virtualenvs/tmp-agwWamBd - Installing requests... - Collecting requests - Using cached requests-2.18.4-py2.py3-none-any.whl - Collecting idna<2.7,>=2.5 (from requests) - Using cached idna-2.6-py2.py3-none-any.whl - Collecting urllib3<1.23,>=1.21.1 (from requests) - Using cached urllib3-1.22-py2.py3-none-any.whl - Collecting chardet<3.1.0,>=3.0.2 (from requests) - Using cached chardet-3.0.4-py2.py3-none-any.whl - Collecting certifi>=2017.4.17 (from requests) - Using cached certifi-2017.7.27.1-py2.py3-none-any.whl - Installing collected packages: idna, urllib3, chardet, certifi, requests - Successfully installed certifi-2017.7.27.1 chardet-3.0.4 idna-2.6 requests-2.18.4 urllib3-1.22 - - Adding requests to Pipfile's [packages]... - P.S. You have excellent taste! ✨ 🍰 ✨ + ✔ Successfully created virtual environment! + Virtualenv location: /home/user/.local/share/virtualenvs/pms-R3jRVewK + Creating a Pipfile for this project... + Installing requests... + Adding requests to Pipfile's [packages]... + Installation Succeeded + Pipfile.lock not found, creating... + Locking [dev-packages] dependencies... + Locking [packages] dependencies... + Building requirements... + Resolving dependencies... + ✔ Success! + Updated Pipfile.lock (fe5a22)! + Installing dependencies from Pipfile.lock (fe5a22)... + 🐍 ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 0/0 — 00:00:00 .. _Requests: https://python-requests.org diff --git a/docs/requirements.txt b/docs/requirements.txt index ee44a2ca..0e349f9c 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -20,6 +20,7 @@ six==1.11.0 snowballstemmer==1.2.1 Sphinx==1.6.3 sphinx-click==1.3.0 +sphinxcontrib-spelling==4.2.1 sphinxcontrib-websupport==1.1.0 urllib3==1.24.1 virtualenv==16.1.0 diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt new file mode 100644 index 00000000..219984ec --- /dev/null +++ b/docs/spelling_wordlist.txt @@ -0,0 +1,87 @@ +appdir +ascii +asdf +backport +bashrc +bundler +canonicalized +cmder +Cmder +codebase +Conda +CPython +cygwin +Deduplicate +Devops +eval +filesystem +Homebrew +ini +installable +Integrations +io +js +json +Linuxbrew +lockfile +macOS +Makefile +manpage +metadata +mingw +misconfiguration +misconfigured +msys +natively +npm +parallelization +parsers +pathlib +pexpect +pipenv +Pipenv +Pipfile +Pipfiles +piptools +powershell +Powershell +pre +py +pyenv +pypi +PyPI +pythonfinder +resolvers +runtime +runtimes +sayers +scandir +sha +stateful +subdirectory +subprocess +subprocesses +subshell +supervisord +tox +Tox +tracebacks +triaging +txt +unicode +uninstallation +unnesting +untrusted +url +urls +UTF +vcs +vendored +Vendored +venv +virtualenv +virtualenvs +Virtualenv +Virtualenvs +zsh +zshrc diff --git a/get-pipenv.py b/get-pipenv.py index a5d30e38..88d82b79 100755 --- a/get-pipenv.py +++ b/get-pipenv.py @@ -1,11 +1,11 @@ #!/usr/bin/env python -# Note, this script is based off of https://bootstrap.pypa.io/get-pip.py # # 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. +# an entire copy of pip (version 21.3.1). # # 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 @@ -17,138 +17,107 @@ # 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``. -# Note, this get-pip.py installer is modified to meet pipenv's needs. +# If you're wondering how this is created, it is generated using +# `scripts/generate.py` in https://github.com/pypa/get-pip. + +# Don't manually edit this script! Check ths instructions in the link above to +# create get-pip.py and patch the following: + +#+++ ./get-pip/public/get-pip.py 2022-01-12 16:52:11.920161471 +0100 +#--- ./pipenv/get-pipenv.py 2022-01-12 20:11:35.816906142 +0100 +#@@ -55,7 +28,7 @@ +# message_parts = [ +# "This script does not work on Python {}.{}".format(*this_python), +# "The minimum supported Python version is {}.{}.".format(*min_version), +#- "Please use an alternative installation https://pipenv.pypa.io/en/latest/install/", +#+ "Please use https://bootstrap.pypa.io/pip/{}.{}/get-pip.py instead.".format(*this_python), +# ] +# print("ERROR: " + " ".join(message_parts)) +# sys.exit(1) +#@@ -70,7 +43,7 @@ +# +# def determine_pip_install_arguments(): +# implicit_pip = True +#+ implicit_setuptools = False +#- implicit_setuptools = True +# implicit_wheel = True +# +# # Check if the user has requested us not to install setuptools +#@@ -87,8 +60,6 @@ +# +# # We only want to implicitly install setuptools and wheel if they don't +# # already exist on the target platform. +#+ # No need for doing this, since pipenv already has setuptools as +#+ # a dependency in setup.py +# if implicit_setuptools: +# try: +# import setuptools # noqa +#@@ -109,8 +80,6 @@ +# args += ["setuptools"] +# if implicit_wheel: +# args += ["wheel"] +#+ +#+ args += ["pipenv"] +# +# return ["install", "--upgrade", "--force-reinstall"] + args + +# YMMV, so dig a bit to find how to add pipenv to the args passed to pip. + +import sys + +this_python = sys.version_info[:2] +min_version = (3, 6) +if this_python < min_version: + message_parts = [ + "This script does not work on Python {}.{}".format(*this_python), + "The minimum supported Python version is {}.{}.".format(*min_version), + "Please use an alternative installation https://pipenv.pypa.io/en/latest/install/", + ] + print("ERROR: " + " ".join(message_parts)) + sys.exit(1) + + 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) +from base64 import b85decode -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 - from pip.req import InstallRequirement - - - # 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 +def determine_pip_install_arguments(): implicit_pip = True - implicit_setuptools = True + implicit_setuptools = False implicit_wheel = True + # 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"] implicit_setuptools = False else: args = sys.argv[1:] + # 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"] implicit_wheel = False + # We only want to implicitly install setuptools and wheel if they don't # already exist on the target platform. + # No need for doing this, since pipenv already has setuptools as + # a dependency in setup.py if implicit_setuptools: try: import setuptools # noqa - implicit_setuptools = False except ImportError: pass if implicit_wheel: try: import wheel # noqa - implicit_wheel = False except ImportError: pass - # We want to support people passing things like 'pip<8' to get-pip.py which - # will let them install a specific version. However because of the dreaded - # DoubleRequirement error if any of the args look like they might be a - # specific for one of our packages, then we'll turn off the implicit - # install of them. - for arg in args: - try: - req = InstallRequirement.from_line(arg) - except: - continue - if implicit_pip and req.name == "pip": - implicit_pip = False - elif implicit_setuptools and req.name == "setuptools": - implicit_setuptools = False - elif implicit_wheel and req.name == "wheel": - implicit_wheel = False # Add any implicit installations to the end of our args if implicit_pip: args += ["pip"] @@ -156,27 +125,47 @@ def bootstrap(tmpdir=None): args += ["setuptools"] if implicit_wheel: args += ["wheel"] - # Pipenv modifications here. + args += ["pipenv"] - 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"] + args)) - finally: - # Remove our temporary directory - if delete_tmpdir and tmpdir: - shutil.rmtree(tmpdir, ignore_errors=True) + + return ["install", "--upgrade", "--force-reinstall"] + args + + +def monkeypatch_for_cert(tmpdir): + """Patches `pip install` to provide default certificate with the lowest priority. + + This ensures that the bundled certificates are used unless the user specifies a + custom cert via any of pip's option passing mechanisms (config, env-var, CLI). + + A monkeypatch is the easiest way to achieve this, without messing too much with + the rest of pip's internals. + """ + from pip._internal.commands.install import InstallCommand + + # We want to be using the internal certificates. + cert_path = os.path.join(tmpdir, "cacert.pem") + with open(cert_path, "wb") as cert: + cert.write(pkgutil.get_data("pip._vendor.certifi", "cacert.pem")) + + install_parse_args = InstallCommand.parse_args + + def cert_parse_args(self, args): + if not self.parser.get_default_values().cert: + # There are no user provided cert -- force use of bundled cert + self.parser.defaults["cert"] = cert_path # calculated above + return install_parse_args(self, args) + + InstallCommand.parse_args = cert_parse_args + + +def bootstrap(tmpdir): + monkeypatch_for_cert(tmpdir) + + # Execute the included pip and use it to install the latest pip and + # setuptools from PyPI + from pip._internal.cli.main import main as pip_entry_point + args = determine_pip_install_arguments() + sys.exit(pip_entry_point(args)) def main(): @@ -184,12 +173,15 @@ def main(): 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: @@ -199,19861 +191,26938 @@ def main(): DATA = b""" -P)h>@6aWAK2mm&gW=RK-r8gW8002}h000jF003}la4%n9X>MtBUtcb8d8JxybK5o&{;pqv$n}tHC^l~ -I+Mavrq?0()%%qLSNv=2JXgHJzNr)+u1xVRS+y8#M3xEJc+D&`xG$ILLcd^)g_Juxq^hK-W7fVro!OK -0X56!kJCu>>lSemZerjK`FeKt4O?bX9-iiWDY7!DYHK31t|U{{> -PE#tZP_+VteGhH)eTGzMZy!aHJ(Q?6Cjc(3MQ0lIm@hktf`o4axNvVCTc)Ts4@VJ>@!hh%K`|2$iKE+ -HHx+6sw#7#MJW!3g|Y$%N)ur)tC3;}#CBEQ7CdI~xY=+?Ot(T=34r+9E$`%6N}j>;=NFf=Z&^bu!zEv -3t>Ua&1Gxq!8;Ps7soN%ES($^#>_e*>Ru`El;Z0c`kR05XmE3{WT9s{ZCofrE;qGp;vO#hcs@DjeDR$ -tn@v;IglI6VSWzNghfplGqI!0gHwP<*5k}E|s-0tgq2{VgAu^rc%*@ -7HRg@?*fSPs9ypVK;PZfg`L*{@Wh4H}=)J&0S$#2!{sXR907wMxwCB>Zm0$&8dG^t{{SFIu9BwcKPai -iS)37*53oHqWOqTV)O3RPrz%ERGmE0Tun4O(ssPA=8(oYCvxpzPymKk}-Q$?RIdE=IK(@bmxe)jVQYH -8{VWs)8KiU3x%fE5{sAyYgujXSqq0M!Jcq(%y4NcRLa4k(bC--&}_#|G%=iwT(weU1)OKQ+;gdjz%u) -oWwP6Kw|to?PIw?Km1kAC7Ms_kh)WuY*}FOiLCVc@zRudBQ9tsceu3uNfZ`pomDWvf`>KU^QgE|jC3f -JeGPP6hUu>U2ZL8-0vz>6l;DW>Cpc;OqR~k!*C(#5^E>jBZX2(m7SL-6X;oqX)fNgKHx<25fw`lciam -T?0Sq;<*0efo>>~_mb!wtQ8FET)G{S3%GLx;TuGy_0I*8^0_3ZXQ@aNJf0K2y8VP7S+U1FD)Lc1YZU5 -_=?sZ~~HXI3ze#a`M|s-Y_t#noGnybn*-wS~M*gQeu&v6y8yuxLY!d!A -F!&;`Rs^bRcsWT^vka6lXVZTrPm;4Ks2igbSlrx(sRT^p6}=17w9Ix8?jmITqsTRyjGrANWtnsTD|j$ -Y4h<5(QWgT)wb!BhII2*G}|zk1yNsG$GkHLdlf5Gzaat{Tc>$@p` -a+TwY7Wli;y;&R%LORzm+XNlE7>Vmn1j*;EP+Vbf#*@tWz5o0+$?;>TN2)pj76eCD51u1o>jxO7Rd+T -^~TVJZ5V=f+fUt3~2+X?Q3%F77oSob@jkBylRQqf|H}cxNlq|euM|+Co9)Srn2x(&;r3@IQS4AF!H7F -o8r-xn-D4>d|PI6qlSXmJ;9W|=O@}p6HPuvOHX05>OET{SlFW?YMVB^bOj7$3}o2vCc*b=Na9ht-RL`cQj!G22J9&fIo^m$3 -29+HJ>nF|s8yA0n&;d{IKJHp=j(V~BT0>~4G)GPEMc(VQAuvRl`;M6?1>952}1Ot5I~#MYk0Kxxi3Ah -q0z)s{+ML0+{{$39}{osGDzV+%G3gnJXTS9DXfMe;)R!F^lZ>b%Fq4=Wdfk4}wCzJh`hBBYO~_dq4)E -TcmM7`3(}e7h%A3)V?v$2PKRYqb~CG)@>9bn8D8{C!mRaF*M5$oJ*5h{7A4fUSurLlk|Ge9Dj35F+Yz8R+C*ftDqF;u_LZHS<>zfUP3#rrKI%~GDOrn(Geb3oa;V;xkn21A-6!o~;5)IrK3&>Lg$n -YELmLl9n0XsGIFkW7P7W+cQbn<5G`uwYfk^6+2P*{9yc*!NCRzAqXJB#nGfJ}B!NvFOOhTfndqX%NMl -ise-9(t=Sm&iY#gz#t1Fx4SUsz^%mm(E_;OVNHrzq3|yB|Hrue#yvyr>(@~yJ^SpJ4 -OF^(;kKyNZ_T@JUlux=BG5cWr9_}dO9aCTQY^g^RyvVq;_ugniS6F79aaVdkZH1IkocB$7G|e~a`MGK -!XEswYXIAV1vyQFCC0F2wxgx`n?t@Ug!osc^vn#KqIIKVLe-1_p#*HV3aVasg00W ->1$}ndUjF>@ZB-R2u;rQ+EVZ$_M+hh_d^Rb?NSN{|#E -&S)jskXLv=z{g*0oLz4Y%3MIH@hdj)++w_Ub=yY}Mo-b#g03!^1v$ME6ew7%D``6|eh~C_r=)A@uzIJ -N=ON&A!*ch(O)=3CM}bncF8OaorQ9gI$?Nhg|T|4M#Y5=A{BwMaNvm<)f~Zvmpdn?c=+yAoeAhSb@87 -TMqdtzY}KDV&{B5+UyK14KGjFsSQCzTOv4hWZCpoO%X5b5|_B(AtRH1E(COJCKK$k!;-T@)v_JO?!To -)%RJsP6QFy)qYW9u%;pS0F>J1x>2t;K8GzcH^9$#>PBA1lHjmwg*|^KH_x<*S -=isHyhr3Ego`XEQrC#p5F9cRF;-Fkfs^?iV@3$}~Bb~8GHljaVSWu|(Mo1&i*{s&u@{2 -vB>ipRBhNi?@MIwmShkyR`VyPj6zz!LsnP`&@S*HQQ=7(&M}FoqXi;>q5?XVgA32$|3zK777d8C`@`` -Q>eL*?-`xmZezko^TratE%IrW;s|5rr@c=|$CA9;DDD_s0Y6IRO)eAR$E8qZkc2N%#@nudxO>zHZlhN -2jBVZNHhBtEQG^Dy!P2rftr_IL518vqjU9{%mWwnSm9`zqI)V0jtcq>J>%|@n$Up{%CyZ>kbt$0eh+HnBqyweJn0Mr=_SB26a5@YX!F%-Jxjf(olZ*omrcHoC;?4Se^vyEI1TC~oScxJA -ydY*9ir{^bUp|3fq&=IMai&S)*AlSmHC7Z!cjNpdQ`)7^W##r$<8wz4V*m_)V>32#VR*ohWuXOIH= -sMUKJ;SFsX7PGyqBJzH9agmUcR4tm!(8=?0d&g_`7e6Mm)jWmUWC$m06TLbvadj&v -W2y^Z;Zu-6cO2gdsaIxnc_KJlF8^$0_h`_YKC!7uSl|V7|pGHx0EY)4x)chSpS2a^%2D$kE08mQ<1QY --O00;m!mS#yyNu*^n0RR9<0ssIH0001RX>c!JUu|J&ZeL$6aCu#kPfx=z48`yL6qa^qhepR4X$Ov65% -(yx$r_O+A$C>v?Xk0z4RXq#_nz%vY>qQ1WfxkqQ3~9gVkXcZ82v&-nOOFrzI{72-zy%~$-fkObx$UHf -Pxf%%rxUd8a|66~GLQ3R8vL7cRBF~PDAlJ+)moR4V01a?*}x!0kg`h%(L#G~Xb*s9h+(`5M8UCb&3ZG -qcoGOQp;VW#N-&4rFgQZvZ8g0VLYnU307k(&=&*eVS1J1Pdg6a5y1w?^{XcI6_WR=6a(m`zGIdXf614 -yQS7FS(g!rYKD_V)ETsH=luY{RzM;)7bdFi;y4^T@31QY-O00;m!mS#zU7>o5o4FCX!E&u=$0001RX> -c!MVRL0;Z*6U1Ze%WSdCeMYkJ~o#`~C_-MPNDSC{2p%hXssYvX9niy1Up%+rwf(&=PH{ktLOsyz49S- -*1KwiJ~NLdg$W}Br8!f!{NM#WDo@JndIc8*lt;#kT_#f&ImpVp0SF<-=eP4oXa2xj#i@B5=vKfRSQlj -Nw;MoD#Dhs$m)ty{eE<0#WEu?*t`{uDItC9)H?fWAWIpD}6Jz1HSc9wXX0B~C5viTIHdBUG -8z!i%>vNb=)LD9lwMa&eMg%fp-Q_vdW=q?pi%`%?vT9l-C%(H?e4dt}F;Zg#T7KT5?yzI~o-?PLBaz+ --ptXP(*na_kM#Ejg*tp4B;Iq);Y4EmMeyR@j~`#Q~%(^RP8X)C8FF197BNLTnYN#p9I$XDsQg87Jbcsty96bJg;U%%|k^yG|c|#i^F%)%Dqri_5zk`u=Y5 -;gp^(t_{x7w4E0$I%_6OcqzCxkr`R@ik6~S&q$6d&C>sH3PRm@xRH@=yYK{71_J}~(Fov0iSj3d0bl5 -j3$!U3Z+QIi=;(-25FWVIoZL^0?k5j0j+23^=2oW>aQQ)vg_P!O3$6%uaHO2q8ckR%f8lX8JyuddAi% -#Ua<1NM36A0pY|;c)03+utlX?gyqp}j5Z6%C{0e`BFU%v*|1+^uxoM1+}V_b*;_(0r*uOLpOd0J5#N} -jD|B!w7(0+_2A3}5)uhDbj?!Ysda{9&TloE#IR5UH20!%R?B@O|<^k{5D9UXai#Fr3ab8ZLe6p{=Zz0 -QaDkhdw4t61o8hszVXrtL1o5IHzSBpS{mu?XgHL0R=^AQpA*cfL3MzWglCJPe;w8B4HeQKH$sY%a@Im -r!CqS)>tHwo1)GV0?Q*N$dalc)h3nZova}dlnp8jssU;&3pJo;RBC8e9>uIoE9FPww97BVbCe<)mrVk -ZCh;v&4xL5Ky7P6G@D5n6HXJ-R=YnOH{RRTY?KEu$iMH$`H#($>aM+Q&18L}LsIGoo4x10tA+1DcH=X -G$Tdu<_F|t#sGmUW@!^RBqaV1hN=jgICQl(oCKB(RtUoyC`);48%D`OCC=3y`Ibi-X($O!*NzZ7X6T2 -UxmNGPC>U{h6PFrD`3q$|<`CmdX)jWvy=y3(`@G=Gs&^C*G8N>R|X>=Xu|O9-+okFh}66ta?Y3tNd=f -&=MMS6_}XeFx5vaS{V0MDLirTGluqiHhcEW;JNDL2wt#MRn|1hZ27TQ9fPmwUsxZ1C!p|e1Q5Zg*-wK -B3-4Bl=$FW3W|3=2jIeBy3(__YWJcGG{pWaEqtGbI2NKJi8wWB^_URR<3Y -k(YH2p-{dA+jYpulE)CMz&?UkuYgp5g@feKK_+}zuaUZ{B^X(y8IM|vfvKtGPW>HHD`0om(?PS#Zy@? -jPuTVEz|`E}wr{$w8YHP?%ZyY0luC3ds^x+nK2YNYu$oGL9f%;%9AONxv50hT?>TaIT~w;=1pnsG2ms?IwmnX%0kp6#2wo?A_d2h$Yz#Ny -8QTNmt*H4QDJQy$#J|$z_V$T*hsPPNrA~ZrF|!Wldfl)Wj?SumPZ%M3}gxDi_JJs5WYkiS8ibV(FOcW_Za2SDQ -Z4Bc|%N4c4B3{?DD!NUIm}C3Zet!gi{Y?=28}>3im9d;*k`35k+2 -;R1ySivda|oxZ7Mj^&?cpG%G6cWQ@_*Ce#SKK5e#eX|QM)LLHAkDsArvJQr~)5x3l%DFiO;pjVDu}Gb -%%>j-6|P(=Fg!iT=>gR6bDfwtsr^tJBez(8|VKh`DgY(gQp+$$S1 -fH5==&@-?t@ZG0YW*kkiF4ux3ob1u|G-5>F5q;7?4C|y=sRMz>G|Pr)C88)T8%nG#s{{V;?E6L<@a@; -9?buIR4CAfn?d9p^F{#8JtJ^hKO&qMGgusvPif0Jzwn3~n+P-n{wXjo|82T!~B`}S6K&+4v&v&T+*5P -eaWQnF74R$`Z*XwGrrEx#GT3peKOS-tYy1Sh`;BMWU$sj3J`br87AG{u>clPt*=JtlZJGot4UTC6Z(% -mlVP#f;r%&`C({O;HbRf`q$4EO=f%m5}t?Uf^mv{DT;R03JHhv*6lhw$b1ZrBu$o%e*(fv!x2wq+Jy5YzVOwQZXP{$~?U54d`oj!W%3HE&P -^ABgo1mtGTvf2MNZ9FUp88L)Cbj&f3IdaqhXeLoPI+f~dE04L|+{rmlILcdXk<~b -p0&lacu7YiI*jeB7ESzJyOnPWV>QM3M~+au!<*UOR%y(Jf;;bxgmbyz}9{z}H56R -Dl9dT#o@hb?KJ9Js -nxpgPK-f8rw`apPk{oN~e_?b@<1L3;L}yU7GhCE4YSlfv2WeHI_pXzSb5gZZ7cdTAZBRe6gqdy+Fsbm -2s#7CJad_{o<(gFL*l^gMTae*Tt$Nvuqw3uG#1>=5efWP2?nHOR{@BiZaCxvHyM -VF#?l{_Ks%H2Nh_|ok(%Xbe$ecUVJtCH8im~DKH)U;-Rv51SEMZvs;{qA{km&mhbQiZrp3c}X(%&RgsKik=5 -UnXXOXu2&h8Xrz*Z2NpH+{w{dmi|EL^a@*Lo&he@V~jQ1y)Vet5@dxs|}OTMnZ!xAzZ5XiEizZlu9Zy -XxFpMq2k!+4afU_>JZl{M0~DnUuR~V_ZmL^q07v`Fx -0iVsk2kZ>AISVhm30$Ds&jM8VH{4SBodsn-__A5s2JF^sPO1k{Q_a;}$-_o$lj0GCGejTjf#l(%M6Dg ->7LH)cwG@snz2^)JpG^w9QKLfpgZ++46J)sB#@xy-ejXGpMRYOvF7nJJ;DTHn8=;}#?*f+<^0D`z{mdb^RlwdZ-?#AkkffWC`D@l}Z;Yr#9i{xu@Y*t~u0f^@DFJ$KPaSxA-@kLs3 -ZDY)Qj@3TdOu`W26Kn&JP6JByWn2Gn^a>oGtduj)ARb%(|q5HXU0M8-3b%Eu>KTm*NC+NPq7qI>dP)h ->@6aWAK2mm&gW=X57{yU}&007}A000pH003}la4%wEb7gR0a&u*JE^v9BT5WIKxDo#DUqP%9NJ`i0C5 -L`7>O<4K+!-h?!J+9F#}&8|cbBy!3M94by`ulUGklRqNozZ21kSEB9L@~q<(Z*ZtJUABVnlSBiuC_A{)0gN)M*(x{6D+COf7J&1Az{S{I -7{&Mq!43Sh{kXp2s=Eq^Q|BR62rycA6bTvNIF_m|r*#cGWYZ!=g?)>J9-MKY~Vzp%RdBxFN1@J;;z<+ -mVlt63Gj&aREz-~;bShpRc0e+Ib~IWV~q;4yn3CtFXCpN2Ef(RIxFifzGtc*}KBq>9zsHF-_t4%B=7` -r(M5+(!6wX?b=6tcA|l^h%QrBedqbmR01)^?u-%o1I`sl~+uak{bsecvhg9qNi!-6;M-2!7QYP&?jQ+vyj -`6(6%BC(-d}6`NhEI8kaSW_?i&NRW-xqsoJ~LvnI7@claq=6PE9;Nt#@3QM9Wos~qS%;pY^(_FFo$J8 -9rx*@4!*k(Vk@O3(VH4NU1t~MK5+AwEs5N^7zLhS}6Mz;VYOmUEc!6Y5wE)>N;HgAq8zg19`(dZ!fEY~8|sLoY -ZDzY2-UxdjN=#DM8h4rN;SU&G@p}S1|Zq6@xr64T5 -Kd0t=VwYPA^CdtsKkzXpOr4wom=iwZ*e@?~ZA&`$YWsX~cl+x5q>Suqg+wc_-HSj}@C{3b70$keOlR^ -D;zjd;w`O&&x|(b2efQH$u=>`nY>pl{j^OrdR{?5ocOTf6_O(_q%w2%KBes1H2opf~0+j8Qk?g&J>^7 -%=F(L18$Upax8{uD%n}dFsOe-e<HS7=*(Q(v{8Un*0idAwK1RC@-u|p0x@SUhW^xlJzrK_bG9QleEVXT6^qL!l$5M; -EaejJXGCD(RYqJuO6T3Ho%&tLrgMLqg?>EuIPQ75A7YwAD339HBITZy4=$Vy8{5$L(hL -oV>FZ4ubX_{Okx(E3dvdygcSHPgC2G@0+>lQcGVUMfm5mMU{=g+1XXL-pqqT*z!o}|g)&|ZGutN@K9%(kqOXm4PDzeLR3BWWR3CHzt;261sLi3h8%GodOv}Ynu0_M`8X -4KO9AXo@oLt{CBMq@8j!f(qCJsg=a^e`;vl2?#Pkvm#kIx>tO4NgX7 -6)*}9oNTGuhtMNX2-_+MF6*CoKxu*lqxYYG{dD_t@#*#-ACuX^!dXQe42y~#TEHKRSRw3XFEO2>&2Um -ij?5xQ+Mdiv?CQuX7KhQ8E}Sc&UDDb7EDN`QoOjiu=5au_kVHZ)u=GW~J%jk6o*2lWXh-!PvJnWO(%| -(1;x}^n_A?}X0r0;Z00r904ji2{#10&f#!iZ(CvlC)0ViX`G_?!tI?09P8atItYPm77n&6_MYJFOa4k1f+<$vQnPJpV%Kk5U5Wp$ci@4ZzW%7hS!B2F%civg`r ->SETCAurYE09H^|I{RA$tW$}Q(q&odE9NsR$_w@i|V)XYlXkRSk9RQChS4L??%vA-_1kr7v&S*k->B| -cFAn+|@Zmq%1RL4pjO+ZLjH7bWdu!QnWvD3i|8$h060XH`=Ddv5ZMHmya4T!iCCOOwaJQ!ZMw+RcPL@ -!IjZ(_koEda66EISihm*j1J4r+c!*^4E9Qb2$Eg!A|`%R*7!fde-^vd7_mSF=a&NeEYTkh~2y=T|pl*qDHEd(E3n -WL)6<-C#?dW>cSlht?0A`#7+LZHwJ2I#VCTc&JW)02qy$rp!yicdcpVn+~edgi~N&cr(voIGf>Z&*HK -vqFEK1)jq-0G97>2+TFcUxDzB~g<}*mC4jo?KtK7I?{Xk$udIv0i(XH40cd95-Xp5SYKM^z0PRRyJxn -AZ=|Vli4}fFw3op^9Cd^7V*375oaQcC0^D)DDtBiL8Gzd3n(IhLN_A$J=vER0cPVs9g`c^NEUY(oxi{ -ms(*Z9Ng*>*U(x78*&#}IzIA=SL3TZ%i|yF|q&E<2fVzXNIqOYUDHRSCziq2P{jL1A;15g|L1doez -zK+5wR@x($gSQC>iV<-_^?wU$kadY-mo@_E6_*5tpw$D50Va=Zu1m*v@XQq;4E;nRHyoWLnV#{qe9hA -JW;GqKy$vo~MLj(~B5kY`yQ84<&*2c5A!QZ)LT}?}tCWX0BPG)cy^E47d<&#>W_Gxl;wUnwXQ+WAG;R -OSJh4?X-cZj^f5dI!JE<2+h{_xRvCTBSkEe<$FoPj4g!7{^S(8C`b4#uD=w5y-zxMI4eYGA(rlEObxh -{~^_ovPu-310jNh0F(<)(;VX?pVvr#k+MtEN3&go@6YWh2;MUWeJl+rx#d&CN>|`D}+tW^yS=19{nwINd07g#2`ib0*&6fJt-e&OO5T@w~J -XO7RVL`m?ShBbD&?BG8gT5)kqspZLrGO*<(7x2uURQ!w_quGV-|SJ-1c0BZjW*|0r5aDe^!l7~HmE{7 -$91za{?z5?;zz-PM?;9nD}y^Nuyhd=%=aPWk{^Bl;Vd5j0iH-ijjDtES)gVDIMCj=SDtyxEZ{fNnJCY@Y^+6;dL$c%gE^B|4>T<1 -QY-O00;m!mS#yynKB`M5&!^NKmY&{0001RX>c!NZDen7bZKvHb1ras%{yyv<2aJv{VNCs4cU9PdS(}W -hYL1`%O;ub;AXmmBr`X--iAQSw9SnyX+sEE+8vC7QL`j8= -I$mwCQT#0QvGD{0C?%#|)y&@Y<~(35V~LT31J7R#zq#Ud7&Ea1Po-U@))sL@U -KPn`6ag8Rb-58~x@?=aR%t5qrYh@3$hj%=woxg6k9gd&EwZL8bK`~q{y?srdtpJ^kL&zE2)sq6OvT;L -H#fIecX#Q#s~>Nswr^xdKFPWOq8hslP$tpELVb3S#v=iLKa}-GHWy{l)MY*u%T1GJO`fiSHn~bSumhQ -=>T{O23)OcQWjfb|thZAF;x)HMrB7?6@=3q!rd+6gdpFyg>%K29Gsz^i-9O)5-KH1k7w@jp%j?^zFm; -wzHOScKep1`$+$3vh)~cI#cYpig{oC~2`Q5v#yU}O_VktKAL8Z*Hl;n84V!{zg>&Yo$j~v5)Zxyhs0I -BeaEXw&`RMyY{nk>X@CO}l$4IGq)gk+(!hQ&25+d&hHShiN#LrMxK&(nFU^F_+q#^E)!W9;YI`?65I6kKW}=b+pOxIye -IRnH6%qDrbQ=p9c1fSwf40|y=_p8{Lt#&w1DV8>TTS($AzG~;|1>xDZ5e)O4_)X^pmWBK$mQqdK|!*iegG@uq@$Rg!?gKrr1%@R7 -A`lzs2#-HGOiMki~Yqk#L3?nJI&vOukK;tl)N{h!k=gH-5M+){!C&(NrPqp;a;Su@((QrcK?dZVH` -%vmaz`l!sK$t?0Hb$S2UG*Bx|$(BVX_in2y7s^U@CWw8M>FCJyBQ46s5)8gTcdUzlvOTB7qvRMgtOr| -5)daeH2YQPU5q0I!4hxUIW5VNw#w;Y?|eyns~;6G4)ku>HdWgsc12WYV8wI;{p{1BlQ^g22 -Mnz6H2y&}8gxYxj~IW0^A6(wONT#>9pdk`JxfmSe7F@6IrUjLbXI^dsyU3rUl|D+8KB^|yp(`rrXWbR -`5FrPS}SI93ecK0cmq{gEXaK?#ebjQzQ2C|b}FuJZ$I2Ju#4O4`|FGIA4OiSRxmDvMEcB3kR(797;;2 -yzzDPw^kcTvxpH4%p1IRgC;j&Z%oH5$v#65II`YU8-9Q7PE`<|p4mNN{Fdq&%;01<47eKPZXZjMP0^E -G_K)x7Fa3{AYXY?Jg(Lw#KPG(h?pSOoaYDQxMEc}*cTPZ}K9;_VuLLJ>zD$~m?kc?LZ?TYpejji~ID) -SVBsi(z%exm*aS{|_x{PZLuUD?!{Jc2`*+ED|2=C?7ndPnTv_{jbwKkH4q5kp6e9+nN*jvd8E+Cu3Y10ju#%7Sf&!+6`vyp+R@fBz=XJ)mEZ -FQ&{M6l08N?(PMagCop`aAX_P$Lt`3PRLDl5S)f{9+=re(7d5zpg^&ZL7fVftP&BM$0Bw_add#if(?5 -}e2HWZ4}^KpRcdV@T6Y5O}e9P7-;sIk~02nKdvGs -(lW6qoEeW4Sl?ZHvSgb8L>@>4NXCGsO!msgvPx!w%{! -hlAeE7Wck6zm#1=M$Rr)38bKKPebHo2R(E%$63A}-Iv8=DD)^4WSS#(IJdBACSS(?nPJ?|cFtI3^Ira -x!L-u$3LR162+nK7Il30xr7w2N$VycEP$sjN+AhLM@J~VOb#=e#GrQs4ck^*zZ9?19Wrsli+BNNI|Ktw5{{QgwU* -xY4i+6^JlfKG=F)d=^zf-^z-eH1KMDUD=~Ug9sB2LooH9lF^zYY@yEkSV!R;+nE^JKA}Y1f; -mfY@@YQRSC9_eV1BQrP1IxY=Mrju$G0*N!?uCh&SK;Av9-X76?Iq=K0O_M1abcg4`*w0Ckm7PcHQWy~ -Y5FHTwp_aG&&6Gc~nLJDMHQO{8*Qg3pK@r4s-&`xHUXiGzw`pO#_nT;U?f9)tX;EMsU<#mO5(!p7b*@ -D7?hiV#&iX-dQ$GpfrJTWZxU1(@dGWE)+MtoOM%Y`2_`xfqxpH}`9O%=ns=U`PxxrqDGn%LmGWG-3w6 -c(It}x_B^5KulnOl4YlYWCBN|G~%c@EcqbzFn8pk2lllr@5Ck)HjIaMvEfZX-x;(IpE%T1-k~E>CdA?0Zi -c#(e1|(`hytK_?a6Y4X7W5;G!K4M9hKcWgLiZ&M*G!{OwgV;6ix6(H#d~9CL&Yfg>>^Zw9kz1C0I6`0 -&0q^E5!%5g%q6Olqx5(;O$g9X-R*JB0T^nU~PLqw%{BclYnlf4Vxt6Bjhq4}7tO3!$d63xgB?s8HIcZHK;-Fi?3aYioZ${n={^BbVx>C$B9aqyqN_^P_#MTi}`V=ui6pJdua^$lHD?7Y7?0a&gf8ja(e -c(_0!mL1zULz_SY($=&GPAtA=jp2{syJa9;Wq*fim}>n_Slu+2 -62+RbrGoUtYDr|ei_585}IVzh@tTO41w1zeiJfFI_&0v$mlk -S4E57dv+rw%gkIA@1IKD>Vng|7LsNYD*=PZfXRZzqdOMW&^VOZKXITH4z6uy6d**T5wu9*V8j#hS`=wvpf6QI~n{$W7mU0vaILi9(xaU$C -M$7@(O+B*?_Sibrc_PW#XWYF<7W(!~sc6!Xp`qy87Jw??9C`!oP#n=}H5R5FN(NXk0+ZZuU -!KrF&>P=pr0N*e_=|9^HKn+GG_E<2{ZqOLF0UfS8K%D<~yk4H>O2%AgFIQS=Sk*M-GY?cyDs*oy?a@} -5LoUIB3~&hM`-aq~<02r|vr|G0|94xo$RjOUhEs*r(SA81>%J=%9>l3^rvvnF{)6vXhqPvxb= -@hU`)+a7HfJ40km?p(*C;)M0Q%w^-PZSt;XcPdXM7#S?L!WvT)tt~B)4;uQ9Ix-VO?ur*LrK{h>%Fsl-FR4gWHo}w}wFAo`Ld922t+NNVS> ->j}{4o+|Be$ShbdK%Tq_EoBO{DbBk2dW1lopRJY2qK8qk@Nne^b#5&c<&qeC$*F(+b}Jy??y$-8kvOA -%j#mL9@pJkObMAw|*h;K8YJh*bv&T#AQHBg`V7zK$FIyFBn9Y-RwccIH;!$e8i|9ZZG!HW#HQt1Sb}< -k!Y>|h>loS3Bi3wBl?%&`Fsp-CDzJBhm;Lu0S|Dij@1wV8aRr*X#U`+cvx6KwO}gU)BnX8^N7npoG>` -~^Fb5Cxfb#WzvJ1Qk*Cq#&ptTlKKH~1-5SqF2YaG`wm_u2vqzpk9e)A`pX`oXn%OARq!r(19p(s6a`C -d6@uYpc7{Gm5y_LPKKxw&kOW1ohU9dVv$AyX&!$Q+zcn9^EgPXws&;ZdOqV4D(j`eS!WW8!RV&?*cP9 -Iz4+SMaF0rRiqNV5T>#;DVU=MnyIA|b0-Ft^|WJD(z%ky&LS#l9QKWxj+bgHs~#MGoSuVV@@P%kup0E`C|nm5VqnM$N(C@6>A?1P(L -}lvap`41Y{DHYvFnk{pXAo0ZSlXlIxHps!`6-`%xFSzJbg$nvK7x<)}K{bx>jf(R -?e8?Aski-5Tw&@EJF}j<0iHJF_!LP0{lumQ(3KazVE0vBm-WtK3R}nVxo^f^p;Z95b1ZU4G-xk730M0 -UbGZbNV!m}mojPlVv(L<0{{GFsp2~T)P)U-+;?;ggmTwbBF(zY4Da+u&*t5H^h!D;=y9V*z7?S;09Ei -R$&#yo31P6sT`3rD(9IQLBudVhmDgpIxPxqL)^YPOpVJR9jqfllEwZr-qhgiu!5$|w3FGP2_n5Y5WfT -G#bLkXKYzwIv%oaFG^77Hg$Q9#oa3{SP?e4x96!aFTBo+RGsWC+8ObsO^9<~{=Mk0AGSMQB()?kl-p@ -V;})IRm-_W0w`nC^%~=z~RdfcM8Wml|~pjJ8Eqx=AOjWXNh%Dr~71WiNc0Ncke2?BXzitnO|oFGzi~k ->?P03YjiBH+~MjH`4paew?IO!B<^)e0_+_>K%`QVt)5H8C*nQ~VYO57R7qKMnITqJA+*886XFG9n4~U -y7>AFTz)bLCN8+eLNNMTl>!UL*Q$5uGgvf800|GJ|z-7HtL&qU-I;Q_K85yU^- -soA3e?JKl5_cf4F-8zJ==J{iUfgy>GBJ+dDZQER60OpD8cy2Lu(n9(1pm#7jh}+W+b+Lj7Eo4dR2nQL -BgQb1n@J`84`FI;ut5-;UVwzGoO6)=9pA-CePuPl%w3aKM^{PqV(jEMV8(>w9lUU%mw~*5t#Y4((~-L -W9Zf4xcBQ_ug0hi${sDq-Hv3_v>QxR#j;}7u9l^mrL_?ov&rH-ALQEvY$3Z+AOMiZNAsid{QeM&3@b3E{$Ao7I -`5L4w`y~)@vd&=fxMl|O(RaJ@$f@l<_i3R3?gGiQ$v1IwE -z7K`W0l{=>*`vnxUGvKpP2zSX|1G4mEgw-eZPPATK=t-s&N45Cw2t@ih~YMAg@YgAe@$}NeiGMJfs;- -#fwy}e#q52ZRV@4>$=KhVY|KAzB%pQ(W;tPk=hh_BX66jsMk#`zXmK%7T;1ccx@bvAo7pFfyPoDqz-Sbyxr>|d~O~oAM-L3(JIm|dw?QB -z5bE(uMm`NgwvTPD~8a7n2X*W$(6) -Ksvd6AhOXV2fgfAjA3>z8NA*^AR3!fs3026NLEKf@;KA<^ch#dm;YQKi|Nx?0vijBisQdHC>blb7C9i -fW#E`{<^IH3u3M5`L}I`byTcKwX#Xxor#|dwV?C(y+|1>HC~uUdL&cZW>uznBS&KKILbyh2On$iI$it+RO+8Q2{X?s -!Zs#b0yWs(KU}agVyveWSclo^8(;wL*)4{<(k|2{OAd}C>NLYlMAV%JFo1!LyKmjgp{}c@*;qXJ5q`UXxuQssHB42@mKY}dl -Ac9LQl@Lr)Dviy$%SYYFea>h*+dgoUZc^7e!m5AQxB>W+kbspsumkF-w~i#{OWD -9_6f5WIZ|-A4Z4(d>FyAG>LfHrr;!ZM1ri`H5*AO`OKC0}8nYyhtSBn4VT -J2!x>gZZ1TP$;Sh>4Hh)T^KhJtbB25&PEN%5*&Bj7KDJPHbut8ie!HhfP-Jnxu|XSeoq?efs3ZWn*;? -x6<)Q)sP)HCIg}jypbzJ0SS~xxNr`HY890YfM{M!rL6GWl>OgNuJuu(jUf3ET}^2R!cJsaIl(9jUKpi -=vTcbI+}Y5y%;)d%8Pg&i6x{s_`k{U0p%XUcul5ZkS8Aq_d$2=QdO!W{kVVKaDeAY6rJIp?r -*pFm6{av^K1Q!)R!dPIt#su186Q6fn;t?I>Y1D -$U4&xJ8LE@`(Bbu2AoGWSb%M6ThHUC+;*WziF=(;0Et+V=O#u9dkCTo+`XKtiwH*8e=(>kLfCbYZ0cN -+#%iaj*qxq)wf!LM|#8W95-T>UQ6^%looWLnqbd?t7z_-=xYcYSj6P6btK=`vk@goKAN8JVs23l_2XyW~j$-x%BM*xc{IBB;YUcDP#~@jfE4fuC=%8y3LtAHFq)Le&U@7zOs&$%|)})V&sSm_p`vuIR -1W1)PNfLo}TucQxhU*O)J`A>_8orM0TUcK19$ -Mzvzv^o8^zPA=?bx1~8rla&Ogw!&+{IT+d{d0laxF(Z$TuqEiaG{fQnk=I?P$zI!zKS0M1?$@$^q3wN -+HJs2P%ss(*C0f?ruZ(JOPhSt*fn5f$N(_mBV+~Fv0MUrMl -HH&r09zc#m+t!z1_~w4_>s53y}g4i6{~FWJT+oT-b#GwNgQU}E?Pz~VSFlu3fnN3P+`X(D)TA9x0 -^y~sr5PH8l9h(zBfNKMyAnd%G2lm3G{ -cy(K-UUcS5ggXvLn|OvM*pT-~JgQcYYV_9vK6S%Bn7VJ*@b3K9>&DRMf{34|}LIBeFNv?KxabL&v;fe -=IyplMyT%N5ZZc(Y9l3(Vah_zX>;rd0{X`NVHdaNy%$8T91T=D^1pu`MeMgabtZ7gh_M+pH>~&;;Ka7 -%V*dcpwm8$=m~lI~51aEz}%u0t`_&rod1)ckF)q_C$11>Ac*upf=tcKo1pIf8wDCginXB7L_9e<@?W!s>Ok%Q -+oYlSK~QMuSBjuU2F-3noj81bM$v3c*TIueu=kMq>gcO}6X0Zc1>N_A-qux?FLKfdPTEyFU*J#4h){; -%k~OZ*6!*q*-LSXgfzr|>!R#x5vx(Svi9sfM+|3uZF>yI&{~m#do0p^h1XeQ5FmH1*l(dLG^!EAa8KZ -Fc4#pZCdl>c8f422YSuUFuw6{~G^kcun%TzUoKWK_UdqX_G>Bqo|bJhZVXiEcdOzYVwBl80}Dul`P)p)ww;wUZ|fR0y1bS1mO=m+=@h36kv9~sA=*%)+5v?|Dmqk -iMh}32UO_M&S}mxT0vj%J)PkkPoj?+}*n!8x^k7FNHWO6>DHopYz^%3p0MG#<-7ikflBHgmWGBq0vP2@^Gq3ds#fY6$ss#1X5@cWU+X|7T -EwSXQ5=h&XuB6Hv*)2bMzy15Tq?I0LWhoa`IH0fePMMEwU3kFZgJz#Ni9EDAHv->Psn~(Ej@Y|FWUI- -H{JCg1mJ2t*1|u<23B-i;TnxEb(Er@pfH+&tPDI -CFdUftVQb{;ezw($raOGEAPl=qo+oZPsQnL`Y;J&3E&duq=#VRfLXrtP*<>@<`g?#vZ>CsEM8O&wtT(_AelZlDTS70$;{xjVhv*!UHyYDy87p$?_PAS*Qy%RbsUMf47-J*5WS>Y*JYU)40YDk>bbqg#{sVh~jG13l(t~)BcfXyK5v+USg&OzQrvea-2!MeFLL7(M+Zw5RXvvlct|JbfY?Q!N*NBT)+z3Zbp?(Vt`8O%! -*BQ?n;Ucs7AZ*V%m$1_k3LnM@jl+v=q97KTd!qps^fq&uJd`YemDvYk}OhGX(vbVlixW-AVChnG>#aMePq&Czl3;Qp-B+eC9_$DwWm$D-&(=z18t+I9f4w^v?+{4-kG?Pz>EMGw!+a$}5KoO -a;}2NI|iu36Vc0_a7`x?Rgj$C$W<@`e*c@D=M&j~_h}@87-zmsmN_ZX~jpqFg+-QrHaDnMuQsuW`-mD --;C4Vau{2_QA>(5(X|X7|vPtvPZBXGi5*FNY{`6h(-oI%4NEcCp(*S>ktz^|4+sh#oBP{pg7bZINboi -1tIpyE}4L9fF^T!XciUCKuru$~B2{#=oO2_NGP3%fLbzNQwpI`*8H}kuB=MK9ij -kLi0-_sI9NHY*tmKt;KyV##7Sd;I8qkg|5-ER$fI_%YkK~Z7a8=OSd=qYyH(D`FFY6;JFt+T^uI891o -*!l9@^Fou0174hz#GYCH#-3VIEMp-z+Nxw+|((P;EX*=Pqsrx{U@)8_jc`4yu}Os*SY*!S7$G>5H=p?h+id?`HY&3_^&+6K$~*l3ANT -*)mZ5P@kU<+WWeq`%Q09|V3gq^7^rZ@0;ysWEs6Fr{9udSn5H7MQ{cR~4R2yGTE --YccKtdMKg}LE5i=d1^T0pv*fZ;~jeG4CMHDOexLB7enpv7qG~tNar#d|MjnUn&Hvd&zIOW#3Y{rpKq -09H6FEq#iVzXZSni`7d05zUs~~{06eLr4Div2|GE!xzqF$KTU#}97!L;NtTW7SCtC2*U{PR!@+Rt$gH -w^%WRF4dKzx7t=O3OQgDOYjk+~k|RQ%CNQO!OS_w3bO=?oQYL$s|EHnXF_l(I>ca!F5&c|Z2Q-KY#jy -(vjXEywk2JVE`x@MmD8*ok}GE?U55NwY}rNOJ)5^>7~bUc57YMlmQ#Cje4L%yevYEHD%C5R=#ufS))b -#+ka7)Ld(EaL~yc%JId(Q5%32)#Lw&kk5Z5mH8{epCE%klhet;zzz?)Kl&m%PY-{&nEdru+l`e6A6tq -j^JMO9sa2*|HcC4gvfgg0_RNiH8X^`am+RX)^;53HcXb^*U1U3l`+Xi4q?pwC -&}!3H2w;0JSbY5~u&*7JIDUv-+t`&`Z}6&uDhqjcRF_r@W@AQj$-*dF)C3#$dU0`( -*T&s-@T))uPcv7&@AC=o(j6tAf5n+C8i%Sn$-9AS9+pWJ;J!@j)_BqQyk)`aN)~Lt&*}J=6Wc?Zna*d -ZJdk;EXo8S;w&Q#1{F2oCQe>1lEPG)WVl*PNl;WlgrixuUbBN%SzISm&Z9I$MSI3`p|&1G)_N$f$I#9M|p=-(I4YPQ7>udQ(D!A-Y+s3uG-ya(&pEp@Z)7m=FMCGvL1ddFURS(-HR`+iM ->54>v~m-&7bwQO^qBV#EV?DQIsr?!a%!66ZN}oP@1o25${dBjNUd;1SVxF(bkxQGV#Jj`Kn%Y|5*+6G -diNEoXc^i}UiBY@Lw!kZQNDwr|1u -Io!^hEW{UiNvy9rkkb5@-o9nHP{HiMKyZwyIfHX*)%hdGJR5>ro(m3%p0amiEb& -t>VuUupgik{LcB>q)k-i|pm^k#Nu7^1phIplzVIxe=*P5ZrA|)$NpAHg>u*hV4s^uyrhr#wN-JScrVW -@qJdqJP1c}LIkT~d$I_c^T=<$59z_{_Xe2M2u!G-(Aoc*eN7VGIxKD^@Wqf%JDbBBC}4S-A663%e9+)Z8@-vzrgZ*+$NDE -FDgDrL##-cE_2C=5ffVQxJ36Pm67j+bt{nIgnH;809UV~xUPjdauJ -AEdyVy4kh?y9&U-?%DywhKe!n4*SsLp(R?*v=TWfQBf^+K(H8esaD(zoVMh+3RVHBcD)0DnXcQ^S~2> -3l)*;!g93UUsM5g*!f-t-4qnT^Fv#+(=JU4JX8aBSJb#g98JZ5J`tiBB!Gy(#4g%}|k&$6Z83s&>byU -TqF$%IpzHGr7={vF9|4^KvyUBeNqYtFa=S~40U+X|A=yV4dVX2jyC@Lo$5MCkmF(m!Ga7&m&INS?16x -dF3y=k^Ry8Qh!**$oH{JeFRe%CDafG|g@IFm^ktZ)j)V);wH*$IOe{+{!se2$)|Jzs-~OB{V5pk)Rj^ -qTa_gc*rSV`XVT1$TGyfG{yM&A2e`G6lIsr&&3_y0hmnRrbz)2dr87H?enJWQICRt5K(Mv}Eu&{)I2Oc^<=Y+ScP)`)t+F{+i?1y9DCpVP8a -VhD7*&HMy?BZw8ktL?kYgSy5j|9skxq9w7k))9w24pOL2?dUNQH^L6Aw;|cKDuf=pIBF$In1SUxF1mg -YULmJp|rUMnjX_$G}JZ9%vsp?J_xQm(kd5NOloN{>SW!nEab=1eh{IzzhR6I@nDP-)8Kr*8a6uL8iRl -APA@0Kn1%<9WD5K%r)nv5L9C@?M-HgnEJZ6p~l*In^&7H$7Z@5S(p5BTxD$NdH~kzGp(Jl69tcaD6s) -=Cum3C1No>z*d>oiWnoA83T_B&&oJk&=9R4k%=f?0N$S9CMUH~4!tp}Y0qQ#wg!Ro#rmLl2O9LEi -vo&~cn&)b-;KWoP&lu4g9{0^1WvHm?5;(+qk}RuXE4X{_jLDzN_UQ2$p` -EID%P6`s~3}yxhp4dq=9>eG}FcZZ}@b!R5O9#|8AcFm%|@T4+14_b<;f6WY<()o*tm&9w0B5o2FRJ^+ -^#9X7kJ6zpx*8(iQ%R?~3Mj@&-~d>*~@j{VHuW?O$|cW*YBDVTc6{?x!fh3WhuLI_Ij4bE5w#%K%BO_-mdUZN{Cf2q39_-`Uul3@SM)oF(ygI~(lrBU_^_W{v88jarbZouxG{A}byla -ZyB%fEEzES>MN-=}K3Bo<^qo3_=;9g$l+B{ls&h2SmY=#noYEWjq!Cf;@K9^pL*62ELU^&5ic9`Z*;| -=Y_KTv9QyaN@K(t^6TuLHF-Kq^-s1rNO%#`Q -K{@dO^*L2lfyS0++KFwbV~7)?UMgLDmPT1j)nZcrf;3t2j1FAWuJ+cC+}5ufVO#tMuK~ZDsi16mbV=* -k)oHT^QBRa<#mPBrp2P8`Lcl8+4qaHxE$IpoGdaB;eD|3Syt=B4R=oRD5AMCN -!5^cDI+Q@j++-&XS5kmqH5uNfmg~{^vID -OJy9Ao@WrS$z;VJS`U=PH$nT%&e*brPdJvB+Mc#e?v3=eNtT$|b1HWE!+ajNJ?Jk9XBeuBCQ*7Fu=U} -?IiLrfm+ThyWCY1azUM!y?OB;y?O?p|p9r(uqTo;CWk{Cy$63Csm{#lHIBTb1d;&q_tQn;|C4p`ZO@m -)uG@aWIfob|Q$fF-jk+tNYsS*7|EFz81vT0L0Zy=38W>N_uyZJa~>y-J$y8mbRt_lBwQu^7|+Rj@$gk -G>bvMdCgB#Gq>Q(H*+a*qi<*m~k4V6aCv>qt?Hb%V2U>nLeSZ=?_@zPXQ>d)9!AMHNYchl$+l52=?eb -9>Cjut(le`^E#*8`M@ne*k7LoeyDWPd8rfenD*-RfH7i=nsZ|s=N|XOvx!)KVBqrtzH~s>G~<0ey)yv -pM-i<3a=Oq_1$0k;e0E0P92%l#c88n7{PiCW!s>+3SR}7j+Sxdm({-s@Pk12=(*~6n*KU%zjwVik%i- -Ss_&%^-r(JAGhcg14gTBE`@vNvFhVY}iJFFl&#yi2+#={g(1$)t2Xiw=HEttj43b()KdP5^BTqQ5KTV -EK#{p(lOuce)nk^7Bvd0`!I?dOAIOda5%d+O4-@qBQ)EpcQ%B5erl9X^Su6a)7FAg;oitsAlis~S9xK?h|p&f`(ZaU#j(-e2cjEbhkr480z+BY2S27W0yOe^Hn?k^j5!|en+s -rvsi2h-7i>%9b{?@SXj{|=E%!7mDEx~%Eml3M6>nyCPB9p);TZuVC-Ij>M81h<7ytkI=DU|ZL#XpIVv -lsIxMG)T$`<5vD&*Pn1Se%!yK|DNL0ts753Tk8MlOU;O>Z2Tm#Qa7#77*x>Y*hK(`R~sxEW4=rRsKeQ -L3#S5z6m689*w>=#q^fb1=<{3MJ`oTE3y-NlrHMU!`;y1QKPlZzy~V#wa8_bQGYE`sdC>WU&iX*OYbB -V8rUH_`ARH-KlE!~OxppX9g>=d-Fd1jVg#q$_Y%d9BMD#fxeq7j03PaBT1H2?j-FDs*_C%Z>T=9t;yL -a}4ik!ns?90^}ik4VT=~?%G!E*j&!8$Zw_V5&{TLPoE{II^3-)$D2+j -!;l{bb+)GhVPzd@2K)|2i8fdJJ#}pB**iBVm`O?5R3?D{;oWno9V-|DtG0;4nQ!x#64F2pp{d<4Yf3M -kpr^&y^#P>C?gErgt_%Vn-yQgVLgV+-_*Z}Js#^x9j7n>LrWxu0s15A!JKHBhjVFH2Gl}LSF#2X|kyw -DisI*qEY)}O%y;mgwgaV~>r=JeQyuG4Pp)XwyPILAjQHcdt{I=b^^u3bEa9}RTwC~9@+1VW!z0LqRsizJ>tJPu?OWtR7^O|PQ(7K2KO3*`yxQbbZ`om0)3rb=T{i=F0DceZy2aOMrtGp;M%hkREdCH{=x*gbF^5g621FZpgfh5R3&M4G9L$R<=`Z8?Apo4I!^X5Go^w^NWz!|H~=YG`|J0 -01Efewa=f*tRaG2O0ve5n?$@uO%gf98W1@;uTjNFLRVvDjv(#i}YRrxp -vL^TN7P$fdVgRjNgJz6j%Vsa$Do=Idd}Mrtz_YgjIp`>}AUP_iCf=wiCqDwA3__4=`%Lq_l~_ZJrzNh -ZA)pL8*I&bnda@@RG;@PdP{rB`H0sQNqdx}JG!d?~X`(&xcMZR=p+oifVF>|j8omD6S|bZc_k=oh2^7 -qgW)XZWr5rPIrbKL23LYg?Igc(jGX5_eM5!R`M~Y$=vXtjLNet?)46S9^?f>aR*Wl`B)W -m%lrF?5}mEK1t_Q*xs|9Sl7FzLBb|UMN;^qqsl+x|5_W~vV^|CAg$${gCM!o9EJeYuT-KWhFCZwgOw0A_=o-6*>aOzil -{LJr_5<9l7AHiHK@b+n*3}Lb`RuXtr1QxKdH6njQ2SSz%$KPYY9`dwx>Uh*J)U-%WIlT37k0Hsh}TU`=EIi7v1nUbEExO6qUBN -JRlO4!n{JSTqZ32aW67JHE^WFWj@TDPOLu%Z%+#wevAdj8C|O1fem8^ZpH`b8kLiGCtogTc_87o8cZp -io-e+ereR*~!PiikPIu6OFB9i~V5-X`Q)Lo))Fqvldu31d3B?YN?Fm`7qYim=nfobJ~le1(i!N7E?Gj -U}oeuWP~%i~&wiTy&rEceLp)k-Hwhdn5>wW-8n{k<4wcBdT5O0dEqGlp5_UZ@BsQmecfpz>H^6tMyxv -}9vSE=gcirqIS9tKw#SAR!omYrq#W0V(Kxr6{)agA!lgd~R%{ib6=4MAO&6CRHdoJ6&$3;wpnT4=~^( -V}?}a$dxS%5E(hL)t5R6AWd7SQD6czoMOTN(KGFs0ug1l$mE_jvW4=n2-Q;K6!!wGyl+-e2X%$oQ*J= -w589+*5lxGr7S6;ywj9HtRemvE;*ZnoHCQU>4YU52e*6x7xE*4gXh8EkQ|)1y~Ln;6LLceU!jCK3NewZ4y -ve?aSsvmxHi3b=P)l3o+K7C#)`8?czWKWc4<-;5JSo*P6rGae|6YsfR)a(B}ZE8%%E3f{_XQAoZZ@o& -hd;n3Oh7Z*rGy;s|D!i0c5t3Th>BBfZ#k7sWmDw7j3NigfgJ_FZ@8fh}_cheZeL7+hw6P^}_#df~i1m -1ob$W4UF41b+pbC=X#8K8{&Rcqv${HaD(4oWEQ}kUZV#*Ya>(TOUS<`VFgYB1K1c*A*mmP%8(_4$}qj -wJT+)o{A=Qg$Xn`T*bw`H#;*d^?m%i3Ejq=vSF94FFr7eTl~5QHzegJ9lcgj@)&dO~TZJUuDUd5sDl~_+g$zUsh(yOjSui2tm`?3?;m(i6SG8Z(=d#x3B+! -*t(*AR^$>)GX~)$Bwq`Nd`bH%R{Q-yliScJ}@ErxC@FeIL!jQjw;57P09;MzFj`RfL1zfXFCy0H~dUf -Pnjnr6-H$$UV09|}hB1#dOUke$h_BoL%aHhPP1dbVUjZCgeMmqItBX_U<@H^K?xDE78;$8D>puX^^VZ(^2 -{dBNpnDu%Z!GJx|;xfevILuK^{zdl&fuxDeMqylYf7-kZrLSL0O2Q`Lr9XfBbZ-imb8YwEt=uAh0(V1 ->#EY`M!lv&WYF1z>ZVryWij~sQ8iib#cua~Tb&^uBeIjTm;qB~ -w*u>NWz_@s{cO>g&h5+xz+A=IZl}5Z{TnHU)Nxc2K`iXJJ^YK-K8Bln=dHC8OJZd<{CZWNYqhNICLAx>yDNf<`>V=<0p;hAqMm`c1<8vMyy*g3*{Av9x>Xwu*u^BsEQ`*%7Oc| -*}_(COmMUg_Z&ou`G7rQ#3F6$8qn~FvHxZ?cStvI)5s|${>8;d#72UqYw`90c;EBe|et`o0bQ3FNPo? -3EvHaM!TAl77a3JU(eeMK8GFNhwQp;e8lf-OYm2(4Z`Xx7hxGwEH;nRRF!HGRQ&v#mTjHx<`>bLJFRs -2$yw3(i(5pr@NaM^}9qbl|O=WEAy*b)%ds@8L$T*{jd#r|vo3-^@SH#iy$`|G0WL|192q6dyj`i}zRe -Z*By>6BM8L{QB$UXmbRgFXG)*;miDD?mz5B`+RQk>NiX -Od2DE0lXsmg~+@1HL5UDlm-+u9Q?^0z57%%rU^5}N}w?XBr{#QiUi8{+1D(-|LzGwO_?pkE2pBWRLhy -~QIf)iFohBHm}Si@xXcdZORRmp}gkNuQ+ZxqqI70bKCI%O8LG`MG~i8heN6h%Z`j_1r(FyLG~yVMnZ) -g>^%IpferyRo6A#M|BMagF-V^NvK^M&78%*Op*;~nM0nhFL#P{5Q=@znxxj2bDeHG(PQ#Gpa+G}Pgf= -V*5$hbB8DXY@;Usi@$--R$Isy)U_7W}ie4u!?CWlnXOEjW;fu`XC(>a%n|e^7A6e-~7(D~r`HT19BE6 -a*gQSV6WiV(MY>(j9Xmmc74)^x8^QRV#KL`vuQ(NIfw^dmA{4xz?w9uY{0|NyPdmc8h)aP(SCPzOQ9R -DiyX^-~Q#Gfgj`|DJ}lbQ<>nItH#k!zzm^^;(^u2F0Ts#l`aNfuB40Z>Z=1QY-O00;m!mS#yd_$B{fD -gXcuoB#j}0001RX>c!TZe(S6E^v9pJ^OnbH*&x0uR!SfNXF#KQZDUPjgq>K)A*~;@n<9U -heSJ&%Szi9X7u{KYQDZeQW$NdMZJMe>Kf%XFN#LCtt|lavNm`c9SQg?^2JIPvu#sv0ZIFbX -t!&?*IhReRo!gzb@_{&;m{{k{Mp;*-@g3e#q7m@zkBie;^lX*FDByR`!{dCd;9Li*Y4Y+anNj5$ZaDR -InI^t!dr<$EnZe{%I(S19~r4(JvH@aSU^KnH-oof!EL^{$=#wbvc(PIsRTUCw)<{XSGJbCTga_PI*gL -9%eHIE`3^t5uDfsQT~$oPe*mt3kWC9OpRF627yFl0+vPxh@bP8!9xzq>xRP@HO}UmxgN(56t9G|V;F7 -<3h$-sZYF+1rZvWe77qfS-0HtL+gIS&kMEtC)`49L9pNS3)h+71z>vIc&-pybRZLzJ~{`U0qF26Qoy_ -Vg~bSP6GkO0(b3Ut27~}y>|9(9zrkhGJ+G^-sn^4a7%n&6Y|*V}tGp`K -a<&1Mm*ey-*1JMtfj2Y&`3;hGGrR(L7cZWF|Mtb~ySFd@^X2Oc1WUy9NWePnYG8U3v8vmS$V?EU=O4|#@c0B?$UWI*k&=-p^WR5Y87q1a-bx2;g?m^jmOn4_|QU29a&{{S1y`0R+Tt!fh8tFk@Q3$Mi2;866%LVGgT70qn`ZFdcmCgOJt<}JN8TKMrd&S=ogsX$US@JmL@)=u{*>D -xU?FgGmJ*UHeHFq7inDm@#!bcpC{svC;XpN_|N#uLAIooFfvd=gdFVE#8mV8x60d9Z~yS)$RNS1x3&{ -kcPrV%mAM`*@dlHS&gwPF0E3m&~v~nU@iBjI -&9yG=OGQ?K-*1#CN!wZcCVOaa*p}L~kA_GcKCItt|C(Fk^g&(hIkNWNG@jTx!;Vtzp~2Mvw#fiFf<0B(-7`v5kSx-l4S;z`;;Xgi=}y0o%c -C-4sCd*E<~bnHa=yhN>S!|J&Gdkw@Y`fUXLM?b5mfs@H%4jd{}{8Fwr37vNkSY#K<*4vd`XQlOyD)}8 -XbL|o>ej%=htb=~YUaZv+v-WwZ&%QGG!Y-AOHKzNdOC8?D=Ko(AcF*_8R+B2uBp4>u$LM9vFov`4LG0 -EmVcV|jnFRxd$oM6CG`~h|e=%xc^4mc>*wtv8Zt9`dZY2d0CT -vcmeb1?XDvNGGSQWs`|R#^k_B{h;TCP*3su}V(0Joc5}^`y)OBS^lFTT=p?utdVMAG -FdZ*Rfii{1i}_xKQl+Rt{mWCWeEe@r^Xrt#D3SrJ1Xz5FlCo1ew)P)?r*D+^36kr9pe^d8>DesmG53%2wCNG -GJt5h##2|bI*T=Q3B)6W(wK<6mnMU#+)C72* -tTM9t8YmrB@J3aKXS+1A0Fx{}{ftIZdWj$<`%ubo&2C)#Je}iqg#f=Sk(6YSV;oeUhkhX^?9-{<?d-@N?q-@ia98M@c5>fO3PdV{_EdR@<9S&`g}Ur9*e5+rzgP+-e~{T)`N!Whzai{dhg_AGcYO0~HdF -)Te85vze!x|6BSf@)b$aB-;abPcosbGec2jLfm4GLsn#Ij=mzA}R6!d-JJEZz@Dj35oJ+%R^sLj}=(l -bXIxG+o$%#0EwX(MvBWkMN)%?9W+QXxnN(NP>ehq#CmYjpW_$v2_`8Vfc(;FX`@yA=iggoObZsrtxcf -?4mmR`WMrNuAXRQ*Y%`EZS?~9?Zu5M>&PBb%$_faE?9lB%j8Gc|iLxCNng+;MU=PM;HsTm4IS^FYAaN -mWwqisOCRkydY?i+J!lRO)e6eV|>xe4xE3rS)QJ_y1%it*Pai{TvZ-#Mp7{nE!isVQ9Ct!yN{@nF8ai -LIhHIC8jdWW=%&m;0pp$@j_6sj+uULji?q8srTJxIftHR5o!SP9R5Qz)avRS0Qb9Z&McF_e{^%(ElE` -z;a5ns5Mv4TR;W4^SQ(oH1(PUr%JmPiMpNQHUYG74)N7F!6}O3h5n0br>K-pVNr)D`}r$JVez6jft{_ -)smtPr>tF8=^{?k8Z7JuYfE>%wQveyQ{UhmLRF;Sd%B~4ma=t|f2ctsAg48f9H(0a0c3f2e)R7ku^Lb -oj|lNl&x5u20n_?mY2tez+u`GOh5PU07I+Be+z7IM#%RU>mxF?vuQLHHRDUWq&lDTjExd)ELE*YQbD@Bb>~?YQU7Bu!rcl(c6 -zwU<5ufWx#N!DF0-hHHfW`W6QAi}ConG7Z+OjQ1Myh(eVIZE{`~QR$OlN8j&2`F*tRw?Q;M)CTzSz2> -d?VBWN6Nm7;1xhuEUj@CFbVZx3u<0Tss*!@ko3vfiG>)e2X?LL?-SY#Orb$%mQjw)pE?9T&(h{Y&QnN -J?cG3tVUmOFv9)A_z6$VpJ=V$3A^DJ*!noM3+XT&TyV+;SN9}JGTV^ -EZ3UMI%XAOlAj)qT$6LB)WoSyg=Mt53)r)rFst({i&3OK*^GIy({-d(S-f&*)nH^rhxCtEtmX@aA_m7 -w0{=(Hvoq{Io&(NpIz@kg%Nh1`E-;CvT46u?dvE^?2m$^06GZaqm^cgK-Wz##IIa~~$^QmAo8FQMSz( -}c|lZP;bClfG)wHT#rc_M-%8kTZZ58sid8KA7Bq7o4Wy!F^WfnUAO$ -*Ag&R=sbqIsH#Z@w)a&$^eS+!7n1h$FN{F8Rv4u51Y=|6+DSkk?lU$`er0N)+=Qa7}*#53iB1l`|R|+ -*Sf;s_ke?JjMvJkt^g&GNi2YZttwsao?!*zH_o-7m$F>Ij -#!=(U#*V92wa*PZz{vPaLiB=|KuJd&G&!1JSDIhcy)qmn9%B1a+lGs@)JY+K -%yz^x*>Ih1nop9xTf6qkYhMZ2UFv+fq0F=Z?R&q6gUI#ER7zgwYSo7l98l*^606LEUL6)L$kJh&U&u{ge^YHXd-i0L+Th362B78=cG|LT{B2O#Dqs#X96o -y&j#B|aJk9v;69FYsoEknxOX)H}Kd!$dSOHgd)sgcYqc?2q!oBMal_er<%g2&2(;VXIfU*6i2(JY%Ki -8q}L|x(ZMqWb$;2AWpqa)N(&l_#sL>`U+db))R*$KYA0GWco&y>e_<^XP>4)K2@4a@SQa}~?p%jP_U2Vls30Y#lS>KW4t0}XtnLE9GEa_xYEKkk9!(O(d3;kjQ?d>A_%ZR<3H -f8Eng(najL{uD5SO221lX^m(;3?7LE(*Uy`ZrmZ*fq6EdA0>+hs0G8mj?I!dLfc6Tyas*o?8n_va5Xa -^HX_q|2biFpYC5G(O%WE1WI9E3Pkl2mH4LaqI -LG$4N597qw(zNU0H?oN3pjnq)IcesCLkCk{pTL=!Yh&#Sut#_@KSu4QTc!Brf(9UqEs(7_#jW>8}Q^+ -G}%dzq3^3pw9ikA{z}L_BK8Rw$;f8O~HMK{PuXCXOi(L|Kiz*%$}>-1n**2^>c4RSRVhx1XP)Z0MMEE -l49>urzcYpYQD9bsV9Iq@#~hsyCxVMCT-E$2~$v_&Nv9jt^dAVJ&?Y#O0BO#o3KO^Q~})@QF6!2|6sD -<{oz?b}ad^!yX7jiW3mUm&14$ccR@U0eeR1DOR=h_OvLP89y8Fv^~)QAfJGG<$;(mo>lpJ|BH?9<&(D -92oVfEWs%3171<56fVfPGV**@`o`55T-VbdlpcQgk!9P__-IGpOm2cij!|)&9YLF<&4G7cxmxoiUfx8w23d_6Oa^Rkz+t28B0O!UgT(oiIHQ$(di5hRL9kf=7N -GkFQ9|FEeWD*bC_s%7Fi%$nF1DN!pPKoC2hq}{^lBpmBUNg{(Kr1N^65b65u#Klb7smzIV<#FjC06il -##V1=F3=if+((ew`W(i$IwQ{cpQ^tB$ac4ae;|N&)XnG)KnMZ{d+I69Xv~81ew$Runsp#+ssm7RZd9) -0r*o^JpBq4leQiLL-Ma(*+riJX*er`K^1d_<)R%KD?4Du7&*cF&OfXCH;lHnuC49Aa;;PaoKoF*WW1~ -iD&4znxGc}5JJ&Vj5%`b%3KBADcVM&^6D@8ut!7l@myA^wVYC2(7BW9zQ5znfuGrrFtkm6H%iv)(P!M ->johEBtN>9^T@dtgxDzyA?nsC+!Ju0p>tZ(FyY8I8nXlQgMoyI1BZOs;d!UCYs8J{5LdsQ5t1cL1^2n -+ZX%?`nqZ*WuYMztFNQeEQqeeBwM=8{>S{1uux5dY;7{<^>qyX4M&5zr0LPEmb_JG)wUc-sAQ{H#jdL -G6?5=H_2LwfV(HeK{S|922Z&QWM70U$9SuWKu+xTnmO;SItccP)j#XcaM`qzvx+3UU`jzV-e6P4x(4;(E1(xi4DVoVyBfXN;r4=0|YvVqA -aG?Hjh9q2(Y`pbuCl`kJmQKM~cn*iRLV83b9J^-zj`6@n}kO?L+dmJYttOga}cyP#!${g{4>eFK|^?( -e&Z=Kn|4GB-kOO6{nv;{qce*S8NB!oUic|o%#*_Tms9ebP(r@hZ2=jwz1Q_6MG3Nn4M>21blDcq9nIW -yOma#L=%u9_d5O(n{g7{Jx0GiJ1S`cNOq*DpgWbB~1?6DK&O5Y&M%8uuj@&GMocCG64oBm3idVB>TO2 -fbjMXi9qWdfToBIjFjWCxDX -x=7a+2f4OqVAvB_8Iq|q+M-qD{+hiXAREw?5@PW+ -C6W7$-PZ!D`)SpO0$Bw1x!A^bOVQxys0lk_noBWy*tR(jk?6;ot5hMgTr9zD`za=U4RPXD(V2xu5zXLi!H3lPB`OR_fYCPO7(6s9599S19MM)SVRsa5q|z2YxChCFuDZ3e -=ZB0}kIiUjN*9A(Jha0D&crsb6irc(+8v4_C87A-X8rvrg8;pfT1EkWiw8EkPP*!(>IaS#qn{o;oK3! -{+q;ve1I^ay%&a?(VfIV8O`I4%ObZ$=p6mD^YB-F!r7+&9N!>L4EBtny5kCHZ|sxRH<<(8eoJpj`H_I=*t;fYH)wu0l;4|9(A_5?i -~B5K9PxsiO6qoc#fiPmZS4fw7zHxD%YHfm^uTI;h*D3NXH<&QUyR27K_c4{neMUNbyM%Qqmx)~vPF@%?Ay#V@Y&Tz*|U^y&Y7dKTGS2 -3+1$S+M4Vn9e8uIf?he3srPQ4Q%>RRzjya#%zfDV@sh!|Lzd0b5>Lih9uvq2F2haRpxt-go8aRk+FbF -7^NX_Hn%4}6k;r%`D$3xQSnkaf`Wbd3zOnj;W=PP8MP=+A&=cy0_C(2~k%1k_?=vxXu-g-nq!MSJmW9 -T7>2?)qnCfH%GGzpU<{>)V=%z_McpmdV;h&kE3^MS@;Y#3I_h_h#Z`|kIz-eA;|wRk9HgtH;L3Ggr>H -A0S9r>o$5JalHM%VK(F>NoP-vv`ay^XV^7r~f+3uAYpi_ODN#OyR{4)vxU3*k-SCj}aW3Pvka1g|~gTd+GQE)yGiwF -}DJW=*IzH?j2{ -bd)9e1~?rx8qB^wK`^A&Jp&Ivk(zCa6MKeOUTLCzSyCOy^!lWDnYWiJ}!91j5<(11J`hi$&Qe6mX)KM -p7=t(Qt-#=IKxa_hbhY%{1mV7-xzZB_vL~O2m?jB5ygE5RcVk0IfK8ab^yqJ`scIz)T;0adKbjx-m=f -5pju;#@6(I^AzJL4bP||rc&PqI^NI|qKnHPYsFe!V -GqQIPaulBIPF6x4PUMvu+JqLqW6g0~bb=lY)ql)q|!ROqkDdveLrC5FSH%`@aI)_cEC-M#2Q5V-`#ts -FpHByx?eXv(?qO!qah4ngWlHtWNm@@`0ayeAhhEAXd?G&nm*e8DSKbvG6`a&UolQ>19=g9kF@_fv6m( -KNrUyx3116!6zMlY1bJIwqa+^AwQy!o&7ES;OFb;t_XX74Q|8mP)#NMs!(E;M`Y0#7!njrRt#AE@*~6 -mDuaJkAR8h_iw`^^$_{A#P+yKRmxsX-1S&Nu6O)q9t)IP;x1+ -$DFUDa58e}i<{mr%&ks8EZPgMYm3!7^%xSrEueC}#Eu5+zT%B8C*V&GxC-RaCTX@5h~KOHGCNGUE|rL!uky~a}w;(~y+d0wE#kAsVEg6mBhs6pBjjtb6!#C|TrL#D -EB**E9D@3+a|kMmL*X}0YT5Wru5VnVc(IgO?W!O3)aPp(4FjNA9M--o1s_XlRUKkL#^XDn{7TpL{ -DgX)Ve3m_x-%R>R{=9q~kAT74sL*f6A_NMmG%e?%@wFMO)nP(Xb4s2&4x-(k9Q2k8!mrMnmZfC}2|MJ -1m2E&yik`mF(ysAZM85opB<5-pOVU;UFz1zB2F%j-c%EQ{!agPNNnR2zw*&4 -%r-errf9_MjQ*)>ExIeI3$uU#FpP095#OLYY!a}RC7upwW$ZvwVKqb~ixllNNq{9|&|12$j`&cwj5mL8ME;5pvlIaN -0mV2w?FH-!ap{?AXJChGk8V4w{QCnMU1nPQFOK+=Q -;p|=kNJ20WCt@2U}Kh>24B&oQZEuCiC2w+4BIB8)jD1)h0+{;`?fi32kn*#XA?@zIgVvxla|%W_TVi0 -7i*QrB=*us`^&sIAx^ymm5C6PPq(lhc#m;S$_$D)s*4JjzWdHGY0czN3SrUigRm_o2UeT!G1^+rD%w# -Zs@Y}A$cLO*P+KjEx!9UG>7VvnF#KU4*UokPyc3fc!TM+^!kXvJWnK7j0l%THKf0Yf4De~jJf6>QB1l -*QZ~w@@G;QaP;OBdf1)Kt8QGv*kV;p9)pw94fpH819&tz0X(Q>hc#c+pIcPeZ!VdM;Ym@PRfu{kM6qu -8`IEa6P$L)Ze(u?s!`p&&jIy4ggxuOSa^hx0fX1(GY$37TagN3Qf;bmmw<}RnvXj**-Yqj2W4{HV&c7 -ayJFwb!;mnrF+5+$;lmrhXs5cI{$9llO$cJ$@kTY-)fNaE6UBJ?F1o>-qil!>Vc;Twc;Er$jX$=$AK? -T4*^L`KBtu5oQGRrwC+Dr3?(eOBxR3MM73K*CCF{;XqqbBFwyB`M|691Zch4rGyZZAU10m@6G9GmOrP -gxp?p`LBZVc+FU2iSw%zp$vzvmEV&Yki#%W`2Z0p;3R&u!I9H@_huVGy{M*dpbPISlJTQzby7`tI(rr?!3jodmNWx*#*HR{w)uFq -7#`&V^fz=_afBQJg8R}r<8*GUIhvYX)A3)CW^samWs<9$EQ@^DOas}9!cA2$B*Jzl;P73~qA+3JReR1 -bJqlTtR-)U-08h98p6a=X`;*m%7h;%UUG_P9;AeE}10b$tc*$h;i0t63N6KYhm`xi+POIV0>k{LU?06 -}ZcYieq*!|CfDM|MZ(DljF00JOAq6vqz6-KmGKl>6Lb0S^J@bZEn_2>IJm&`}~Fy_+lm@6};J!TS6J3 -F@YS2;Uo=Dh%&?w{@1SR+ZKaBoNU0Ia`#L)0~X))(#YGU)Y0S8x0EyN_*2g`8eg}fHbw3IVeT{tqRPoDLN)adk~h$U_lyR -=j!fMtt_$&(%3)zG%{x*50a^A9!%`j*q)Z&1iAt%C(taQ}X8m_yd&TD~zzw&!2wgpG|PA&^RsXBG_Df -|N1$sma4!7)dIzP9-Qzxn4jPm3KF~MP95KTaLD)ITpV@o%(-7Em@(o={u@5H>bg+A!)uAJuE&3eWNovY=Ux8yjitPC^L43bqfC0lLP(@a`o?w$SVP8GiAKBR; -;=qkrJF?!7mTzbc~(JkuA9hy6Fyb*^p}Hv-MQ_pATyI`c_SXV(2!$p7s+^F>cz{t<}zq6ab8{|Ll<+0 -&PQ1Y*AILCh*|SG~&+KZdI}7UQ*BDg-hM^-g#(>s2;wGrGR=rrCt7C?XsCd)DC2yCswNm`sDzR#@9_t -m53PesnqOoq{8+Mnlr`8=YMiEdVw&T(e@YpZe&L$-Wt*h&AUPEZ>6<ZuqVA>DMivlfGTv} -*V5^g!}-nWl^&hj?Omkm~@f96ciWB -3ciw1;7LVw&w0BQqSp@Z}dwpx?R2&8k87Gj2_NG>ul!vdQIDUxwNy<`PB#cyu}NGyMaoj%l=NG?-k4` -3d&^oOLj38>I9EoNIq{DCeR5!V~(m>GRi4Vyzji_VE?0UDCSXlnpJFSL6-_*yu0&lNQX1^!6S4Ct7qU -@m~M}5u3#$*9%<-qYc?408F5t&4ZTj)?C9v_QYgy;S;L^K`w8Dk$Tv)Inle6!^~d2EjP-2zXZMn5UPb -W!NJVb9+F=AcWy{&W*;GLHSCF-f+RONcEQ~wl_LZ3c+B||eHH~2iS56M%Oz;g7t+}d;+{W?_=|#tlld -Pv=>5njLICo&e0j(Hu%03fao?Pp=2$5*W%I9>+Hhn~P4T{*;$#Rt+YO_Pag%z?`?+Q86H$CRfwd{PUz -LK|i1PBZ0r8-f@Shf_ZA@jX0x0aY#TBrLxorAvIMBi&XHm%^fnZnly`SJ|@(XrR6jCdkWJlDYR40DJ; -Z_G_Hw?2745(Hxi@Yf~5P(-CP$jh`7HAEH71kwI3%x>PJ_no!&8pC83gA)Q^ZV3 -;6Z}PaEPAsg`ziq=UyUdD=HOZ-cl0Pkr&CscM=6xK?g+Ok7 -~PK4f@`9O#bOet?E&^n)|($AEL__g8i-0qH|~q|Y5R^)IqQ)N<)?jtp2mOKMgQuX57`)zL}Z%}ELeiP -m?mRGV?S191nLW2A4bA5N!LJq4IkKFLhC&n2&>ckZD_R?0WgXHH6^p?E|27AessfmOQ*uVQs4P6lJY= -Z^Ze175oZPOv^dMFd>vgyHUH$1ic0)2Fd4b?T(yywyF=x7by>hc|CIc`P4M;?rD5M`zZGt%30o9l9sL -z*k#9=9aeyvpFKmbiv(Uyc()+T;81NMYpp$b<47jYD-+0bHzDrbu^cB6_BD{lvebLz-PLajq>;NCiMS -MO9KQH0000805+CpNw*JonUM$p0QwaG01yBG0B~t=FKlmPVRUJ4ZgVbhdA(TQZ{xTTe%D{YIuDY!j@- -ooeQ*&J!LGLrF1tCbQ|uLKAkY$R^CF8nl8WOl_Wt&MLsGw-&2C#1^@}BPW;p!jo8eHC$>dt)+)7m$cG -w9m*g>#TsRy=En&qn6$gP<%SsBaAk{R1Wt+w}T4WiM`&wzs_}Zwlu_E*DQ543N> -7UY?+g2#n>6E)UQ0OQOlaN -;Y_Ra>FUGVn@=n3@u`N*#t7Yuj@;pOT}TaYqce6{*QNA0vbR!;+$t@FiT<`)e$3unV*Tvx)kk*GDy3v -M%Xz*N8Qzl#tWTye^>%r^yj^DRFF*bW&G*aI>hdp3=*4mTkLY$M4co|4F#Ojg&qcvp%+^P8k6DF17vz -Uvb|;uojm{BK4(Y~51JN{!ZmOK5$P&*V_|_RUO`Z#5HceR`Tl}zy6ARXIRluYK%xe)9q7)VaNmG^3V@ -K66E%xd$m?meZX$0@1pRRt&#=CoWb-ny>`QB|0KAn4G=cIUE1Q3s@eX=otDa3{yG>DRUv24%v12Ci&l -1_TMh!}oEPWt~u3JwZ_$15_g7;vnBne((?aAbj`h^p{0kT(5A$*Ny*#$Mjo)R{O)sH<93No#Dz;zK-T -2ur((+WSqvZYXuzO}@&AX+T6bRVAx!)>H*_9;LPoFGcm3oB;7rAVn*7s~hYYK#CYt2+9;^iI{TM>(AI -bUK+s^u+s-=1PC*<2OB$XxV1V#R$@O|@G`5l*vO}N+HXep>$)*fLK7OP7iGV@U|V5(JCijxBG{SC29h -t>j0OYFTHK1gD^6{Sm@zrdJazU`Yo(3jS*f-%CtJM-X?=9Xv~ --0Qctb9zSL)pRc229DcdLN&j(JXurn0>%s89@Oh_{OGMbf^b>e7Eg;%VmcBFm{}T1rW+_I -2BLZ=)X)n2VZMn8ke8r^==LS5Km4w(KK72k><3;zY1mGtogn#lXowx$ck^ACFu@_C>PF3>KcUA)$P(x`4sP4k0kyL^g&*Pw#3s0_`vKp -6JV8VSz8mhM+#)_RHbw0nZ_AgH?k~LV*x*Q6~jbKP=7*)p$;f^O8vPl1{CbpF6d{hNxX}LO#e50rX{8 -$h41<7HJpk^Jfri2(j)W~XbENP1xm5y`7tv{CdBEanD&-^4TNfNC8!1SUn`%T$In-ob#o`~`H5kL`j7 -DW-(W+-{|z|l{|lYAbSs^^b5ju)*}O4&PKRkpg~FvN`qZl6x(Ib?qhW+9XxqY?VCk|nRN$Pc-{#kHO+ -|iwt+r@-dydZCMVoLhE{1H38cRdEmU{C$_#EPdup)mS22O6&|%x)<2HUpYA7tC+;RitLou#8ei|t`0x_gUO}BV<0gH49i*rBlJ*wQTCN+qnRz~WeL(p -6OVkFlb*vAEc(R&jZp&P63GvSp<0_BFQLP2Gx`NHUTicJ6RPoobqJrxq)l -DGz2wANNRR?1hTBXA0&X50(Pqq`Bs6kRE!-&;uF#oU|B}Q3>KJUoscXdr^=Iw5@ZshbeUuT_bq}rzfn -CG`*P^(4C$1CcVzWW#!qmna35~B81mA)+pLA$9r~x9HhPA^diK5Y@xRDhvhvXeZP{F# -pn#3s(0+q#R12NrI>vkLs4>PsM7YtBo#>g~fu8EuhfuUpQL0|Fmz>nq?`NjCgik6bkk@^#T6jK#C*# -k#o2-j$7O_yfpB0@{l?m~U$=!Pz?l)1F70sgfShCY;FA3Nv#RuQq+=o*>dYN*^TSK@)YF~R5a)cb@Qk -lQ{_r>F613x*R5;9QIln6(Ivho{r7CVuZGHeg4*6C6S&zJKxbchY?`GaAP}^0FyJd@@WY)ajV-ripW8 -4N)+F<8JZ8{aCI)tA@9iF7q}%N5VaY&Y#6doi;!p4hk(FhaHVUM6DYf?~H!W?ie1MNx)`uKb_vaz8?c -}D)LSE=tpipKU_!d-o0L&fH56?CJE=~zW`860|XQR000O8HkM{d`Y438OAY`4fnf#gVu>SUi#iNlDjCzI%5U01 -1*O*-m?z$VU)Z>)VUsTa_I-Sf_v -0`yNZ|X*gIA(HH7OG}^Rux%Oi&#HTCT3g-`>k46_Uk=Yxy%>sbD8maUZ|CQ&WgnX-cKgAS|3jsd^#FQ -kFsHMwz%LK%>EA5*O7&%vG|Ck^BiLk+z -bV@RjFB&x6?iE$#lofCsB4shBgM7i;e0JFa~AKhPR%Rf!)ZzQW1E0+l`cmc_{=xTt)-&;yxRcO!)$h*O&g89Nbpskp$F2omAdM4%2!f+?mgdC -zanI;FvP7wvH4A@wrlT2gAUO=u$`N&%*0E52sakqfn-=?o7^QYZ!5n})1q(6N+Nncq9KB;Q -G^_a45>f!CN5i^{Zo&3FYm1xB^PeGt6WVSqXnt0uz?hTan)|IEm?k5XQ(gI3p70egKCb$YP5)6k9_?MNYc1%Q69j!XBgWH5c>Cqz!;kURuRoo>eS3NK1qR(s{wy=3w$yW==5;NU9DXhH=o^opu@TBby6PW<>;S?b`)#xi{p`^deI=1IHJy_ -5DF8sQ|9R{gBTk2K8nQHGtB^G|VqiYg`agZ^r9??dM{nD8bh8`E|J*Jqp`!3Tn1+$0PkFdduTTO~s6_ -Z)ga-aNz)ua4rQL(fSpM#*Bz6)fsy7X4x*>N8DlG`Pk<^{Z!drS>HOV?_`~VXZ_Yp9lKZ7hmNs16Lsm0uWCj65k3-yWNI{gSa4-J7H6;_bEB -?qds6i9V1hD@am|GlH{{4*|JRj3{_Y*oj5um=Ygn^U=0ZAPYn4_{QLeNp3Q+;sdaMJKJDGKYBRELu}_ -~q0m&8A!kynhCR2($6?w8Y-d;5sR+&$F)>ofF(2Au9(z%}g6}s7C2m1`htjCqASSY??zFC1T$=T5%&3 -TKogJUeRS+y*h44pi4JyPZXPgzF$0tHLl)Y2({cr9|o&k6%MI4ZnDCvwbTbYldON9Y1BNaz^`5kRyR& -(}0}FHTGY1*&!<;ugCE1~ua$afJ*sP|r5Vw8N0RTVyx~n+h&SHu@S3dyEC6eEtUxh-jVRY$+h6#ay-o -uEG8SPk+ZTTNP=OiE+2UG&8!H!)<0ok@?;0XrmH(4F%t6+=T2DG8H*%YC+FEfA|6IS1(o3ES9V;*itk -K{1nQEgv6PNthn#WWnNW%ajEap7IEwhkmvF}dx2n7nV0*~i>Oyx_nS7+lHUnOj+jP$$S+^G^@fIO>e` -6P*HxJ=BfDcc>SVACWI-!bO^NydpVR~(Am>Ter0DYt5WY0y0oqSn2oQB4??lery~wF7y5JI7Lz>bkGB -wA!35ef=UK@YR#wYChMyrrwq+zTg!3JXCP|sLzi{chJhI54M@e;r((Om~ahulvyVQgZKo&bA7CgBi+v -wtVCrkbtWI$Gq>y}XsBNF|R7wb;R@oy+&}r>%dq1MkjzUj%$eGW08CGy-Z!ZWnG+BtWhzv7 -|uDVct^YLN>t>Jh=f*6MY@A>5OR|{cyXt@;Q%8IWNhF{E4f(GoF{0}l9@o9z{U@-^r -#+FnG)|s0%?##4SS;*StHu0fOkbLj@bu1R5Og`nD1jYfV+cJ)ch9oLw#v&&biSl()9_lnYaJm}sCa*Q|@oIRi6_*V=@`P@t -Ry>80VQSw``G3)12(6&2M}g_D`@d58;sG+!Hc=*cN;GbVO!C7k -CYRUe_8JS6=`{nOOEx29X@u6L(U4p#=Z)14AF^-_v`7w0eS|w%uOc;?2jBr1mtt6fRJV7UP7LWVTK}4 -iUHTg49s0lzrToFhODBFjSOqrT0HQiyG>>5qJf$nN^L0mW!HPMx(GXdF6K-t74JikQdT59!+u09y3Kk -Vpd~i#KDcfApLxJzte{|lzeKyo1a^!z@PM74|Lexsgz6>Boo99V@)m1zP@7oj1f8zcq2$7jAsUu)J|* -S=Y!Fc`T6FpFu;1Ulp}hv9y7hPR*o9cvV#ZSs^LjTq$J=`y!7cXQcpTPTCEl?XO=ao|Q;=I$y0}XTJx -u}ZQ9NG=zqiFU_9eFW@c#kIw6rXm4DSUs%g-US5mW_uBRH`r0HV-8bKl* -nO<7caL{(kOVw*R1T0Tq%nSbV?c@ds%&w(YUXqKfa-wJNTVN)Kv@{{KwA%A@80yfs1a=94FMVVjd+^+ -&Kle43fVq(QXRP?YHYw{4Y`O|K9OiONVBZobMQ8=3tO0XX;|*ZCZ`!^mDn{XhFL~WB;Cy#3boZDoPNK -J^}{xWZS3=OgMZ!DA$QP1tBIxFZzUoc^>x56t++@^DH(()51uHRX-RJsP+B4ZPh -)K@w2LJ);ZM2E8U8F_>PVh9Vsjmp|YRPi^$}Dje{3O*X?&uaVZ_n~Vv-CF$~FL)LEdLe0%s|820c2K2qby^n=r -z?Fb9E7RNwT!|1tOpmZ)or-mM)4hJ1%|I%o#pnrP-^wnIT<;y<3{iph!Mg@&`ze^8*$8wH?GI13L)Z5 -X_Rx$6yF;|W&rAt^J62z-+XG1OkOQ$Q>otPBg+sgd#$);=^=~%86iqN4nc#8t|A@CQGUrMPNH)^~jDW -1!q{Uw8UtJ75=LzmMI7YcPj^Vh3_6i*)`kY~7g7mjT!y%hB_>;eXHxkkv^5%XzcszMz+b8|e?(D(Y^b -F!`;g3od;2FI$genr4l_#pGs%`5SowJ&?KKblt%+l!BK9p#Ts)FF#x!uh0W?e1t_m?tpPYN1LhOR@49 -LfRwm1b)Y$FIM_e&HkfGe<|#P4_o@#S>!M%Tr5aGHJiYlzg8I3c_G8A=bIaXr8GQ>GwMH^HRsJaG{$6 ->%{KAP)h>@6aWAK2mm&gW=Y$uclLb%005i-000vJ003}la4&OoVRUtKUt@1%WpgfYc|D6k3c@fDMfY= -x96?*RE@~4oSZ5^3#O0vSf?$cnRbfY6l8lhdnsMCKCf3!ROeS&c -CuHpJ_IC&Bq9)C@Q}n!^1old<^_PXH+LVZY!SdrPft%zPj^pGV=|c>U3J@SRky*is)M|2+O#P0auuXy -7R#ogByU^>_{N9 -<`t=eptfT6y>PwNtzX^yz3^R!Xlf2Vud)kTvoRWkqnJ5?^KOmQK#N%I1bs2f%5qVMTWpltJPoZP80tL -nJPA9R_kwuYuI-x+E_Ia>iqm$tgBdf38nbqLdx$g2CYsM1WAK{6z5l>nNvpeu3s@VBj?9u{ib=2h9~D -&bK8*Cq2rimxu}x~gaJ=q@et?9a#$uYf*fx>2*>Q`s;pDSi`K*hRHSJe^XQZ`xV`9<@rdWU=nb+h%qY^%{t~T(?neRhcx~yewU+b17j*T{JHgLK2mBv5i#p%7rsG$1`zF10agsWQdbg{jR!!B_K$Rh6p4WiAPtrwNR% -H$(&3{n|j`t|@#+&L+**aCZ%vW1s_9K!XiF#;*^I#g^uZ!u?(Vs3pTwT6(RH{qAn%DhaH#E30w0eyIixEq3T_ -~Dh)vyQ3vK#&V!1$yf>S`F03YJV5l?HwYu`|t!c}eVuFoairRErd-ivt6tQ~3ABW!$vf1wh%LK`Af`? -o|LH?k>-i+Zq9a7Mb$_(gqru(XtLVB~H$!D}YtjY3ty4kMD}AxI -)Q&!C9U77!_Q*m$+ui_v+)e6ccU#(llBCCFZH2~+Dp7hw9V%4X$Di486HL6(tN&F;^V=MsO0xYo@SCF -xkDUYKu&0wX;fOr9P8I%$l2*ADpUJ}8eKx7Jm0+z(6(ZIxn$12NV1rk0naR?npac)6`{juID=H -3ZK{5VX}6x_PGx&38`SdlJ|8TIAGSiG;#hpoMxR0Twm%>WaH$xRd*KefI2TCjGpAcJ`gU`R?p{d-MI- -&5iCCXz92B$P`D*B+yDXO?7X{naK)H6y~<^4jE3s(Ru*_68Z}|7xatqltosoD&|mU8DtZ&naqN3G|wI -l*7Cs$d~oeO)3Xw1niYfnWgj8AB}wufDnxTHZy2Yq0*9rcQ~5Gov|p^?_F;cvqg1n`IUj1-p|6NwB_< -bgi&X-b8=zlXh#J#`eUkbOMH=QF;KfJcE^tCf|*wuY)c&=OAx^d -)+f#1TbWWgtks(N=XmLtY;q-j$2-K%I!W55dn>jl^yo_dK~;nY6&>|@VvtE@PWkI6H`h;ZB3C5Yx$qh}((!iC??{W -HTA}cyXP^uhtLl8ptKAfqJCK;S4NSfBr@`-kA3S?vReI6pL#zx;uF4g_BtjFur0ALTs8shMf{wH;uyz -(J^OD{$BzXOVZfUVa&w?O0t>@DSr^3>b5gR2;ku@D}MFFd7Z^% -E_2Ce!&ecsz7!z*qZ8$xjdIK18Jwsd>FLJY>pjk9`A*)RrfM?f*X -4k}`r~_**E8vab_rbTe>m^RgzyH(RgUrM3%%?1Mz)tO#ox&Iam#XPmFr5x6PQ8^7$I7Aww3(xo_02bB -p?k2*9};%cFef@Z1y&I#JjcJ`3Cg2?{F_rOcG^~_{MfZ7Kh_-?R_K%jJ*Ej3>$F^fwV*leIQaSJpMwt -sNkz&jWznv~(>1TdZ?6QgsH5sEE -1%1aMZyg+|ipgeae9n-@Q0a&s@7q~2H-t;S8)l~**SXfxLB6<1-^C(}; -Mhr|BAQXV;TebcRLnlKDV$wE)Ae4vi!qX9d==6?S8S<9w1gCo%|UpgeVIGTsngF3&pmJm;BpU?wM^+t -j-)bR0EaQKWo>K~qOPz;Fx-IiUak&l#=}B11o-;hc+eNE^_*szVh77JM*r!62sn85F66M#6Xm_cdxwb -;G_Dfwxg&y`J`A2t~Tb){+ZWjuhBHZ%#R5@A8pbL)Q}FB*LEfCfZkM^0{=xDZOZ -vx(%O*8Nb+~t>o-3#2?c8mvqltYl7Ee8&|ZY^?;TQvTwSXXKBC>&*ZNzib8t14Y4`hPE|Hlp{)L}>T- -L7{_ZXMZ;}QKdjJ$prGl*Yjk|tXSXsVkL#Rn!t&uv>KIqW;7-UAO2h}U{F~a385t_@DF%|C2tk)nPEyKm2YA!u-d+iVTKdwZ`EEFJ*g$vAv#3mFdlhraDp@ -u;bF)G_E~dLJV7EY<%L_y5f62H~46ot_3HkIT0&8`c})x51`lAS&*qV#ce`yJQ4d4KV?!b(7twBWBL7 -}+UCmHeB#wxb`LU8U(oTdXC{IWHP-@=dysa8a-$YPy&-!18pzACF)O^51$jq}jzCbD73%t!veg%%Ef>vGl7ZwrQZfx(cRcdL+lf -QmdUWF2~W?Uo@lk5Lipu&EayN`|YvW@DIEZaJg$IcyHf)9KWKT@$+}X27v7j?|3!m4AT+m@jiJ%>13< -H|aJ&2TXpaw0VY2k-I&2A}qX@vAYcmYM}T=+9gB2Nsr?E;8d+sV -RLenfWjX0!R6xDpL?09En=7UH?FZB%VV-Uf-9573{;vX*?w?W_|oIJot8*y#p#F^&hqUbU;CBoSqcYI -HaSJTZ=^vz^?u^xY)7Po>{kI@k|7M}3`HTp|*v75LUib!9PQDhzTomkv$0X(pVg&e#G$z|eZTjZ_Jlu -x7UCpTbtW^5`?V-)IRX7a$Vr>Q}G7itWvA(H&^Ao@Vh;WQub1sVX;DLt{kzorL-j$X2~FLI>Wy}iW?* -3V@SI%w^+IT{ZqZMmXZexTCOU*ANoE90P%Jgf`~SIoZDJ@f$612^2+UC-x}wduu#{ND-R-8c -U>ZFBqP=b}hY21B)7h`AjTM+>G>Xw43sGL{v9--c1Fl_?S%Fh`NSNc`>K4qQuEdQ!R4cI97rCm?y$k;Kb+slpi{I?M}T#uSu*0`r1Rx94d@7rKP%qdt+qSrLWpdQs^g5F&Co=~AI~CDe#qSuAn -dX3N&HC45g`Z&sp0Lq;%;mBwl%DcvtZI8u*sg84AAJkqy2ym2`waq4J>4ILkLBq)VN&IHaW2 -jbksVQXBgNyew_=nptG~qAapU55sh!zx?+D^Bou`7s=-A+Pf -3~sZr(ym29PJ8W;b~HF>xUW=_lU=rm@Xz(x$#*x_$SC$hv$%f+%%@Mj3g`r&rG2UOB@`QVxV%HaOR~l -ts-0L;3-SBracIHi#!twOYUepHim(G2SFkdmq6jaHvhM6v--L~82?DTX69N*2+`*XV8ZPU|jzC8tj->K&G> -F=IAvDn1YBfyGxEWK8Z;_3vT?t;6t&QsibAr=y(<|c%iw{p^AQ<>1yQfVEF!tmx*wj)KX8N+>^xASgs -tJ=7#SB$=#zThsLe*4=~rvnBXb^wM`u!o|kzq175y#v9Y89XV_^*(P@Ai!DcSB>crDV(Vj?>tl|el6n -9T*LueUiW>{U*(%ENU!VBzQpwm~e1|$q?3{+|+1VqEemDL0N`M>vmHd%K~ts84%GUOJxKI> -KYh32%V2nePK${7LqU127w8}e0mTzxQ(Fz!r#4W6X49QP?IwmkN$oOBSFWG$dzIWGM&VgHi;+bKkHS) -!-c)_w!}XjQ5pC71w$cwkK75>>(e>kvUlPD0I3SLAe| -cEb)OTTHcamljdcT$oht%FKNBH!`LW_`hc}0tf!$2?Pp+;`{)1oj7n4V{NK=l1OJ>#k%6KffQ*kqBw5 -s;^W8ZhaJ5y7JS{j}8;|Run6UbXcW^)=wr~qaHdGjh5tC?ZH63qzYdMWk>Ug}V0TT|}TJp}$Y^1RWT? -+z?!sXHFiEmN=#8WYRP-|lFn0RV6Oh176EdFF9w13V85PZ -qI=$GKxkKSa$2n*x1lhJ_yLhDRrvS;Vu&7(m2~rs7ea7H2B7Ze17AeT@f59V8+FX;=<+twyt2xBh3xL -YP3m6#Iw=QixxRQzyMk(-!Z`68In}+6Lb*Zs<{T%bc?k!+%5)uzba0voY>`e@PGR&D)$RjiXkj21y)NBzX?5Wa|<1Vk!VWi5tnE(TIN{0m^OoOaG0Tf(SGj -w`kuuHXFrRZaYvGImxW{=>Y4p`$M1RRh(beA(c+*`!TNQ8F4eo&hNVe&?%aKlktJAEAG1XS4#4m~M{L -)`SBHUqZNVx|hxjLyUHnGT#S>y#sFiRmTu0-qxaGcTq_0;08UV~E`vjA?-JFr6IR3;yz#i?>&wK3w2o -=XdWvUcP&K^_RauRT!J9#6w5|jFd>^Dn6mbhqJ^#$dB~=wsv$I^jxyKmxCtH)1sBKQX~L@_YP0=0scS -7|4kzUWZh|J$O%5w^1|AYbRJGOy0^7qQ``#4(Vu6(x}g|!Ff+yQ673VckX!91ZxvXqygs1#9AFjD=&~ -wnqzhg+rw8=;>EhLkPp>~ZUHIrIyK{x7gfK+V-Ok0{=`QLHyIpnK93!_J49d5nU2wz`DrE7t+J;kj1B -*26lu}6y1u%s@4R$iVW0Ded77e -mH{J43wlO_zmbvb`U0ek$Z$@)}v;M8mAKoetq`zX6B$A{mVFiO5{u+8>mL4ei}V?Fr279 -A$GV_}bw^tJU-N@ETMu%^NbCRiEaLTglvg}7m}xvBe364(0O85!j9G~p$v_(6s9wVdS<5)QtxjODK^g -gnPnW(rA!Y!n)mE|4Y=&VQQauWO2%ennr+d$A7tkN?c(Iu){Sx5< -N7vQ*@xsDr!dcG6T=ZP-g5~$o}-ZbTncpAImPYh0ethb7u}}=8Jzb%37ud`oTxe!$yK0u6Y4NEI7G}P -e^FGL;+zko1qes)J}l46>%thN>~&gr*sPou*;YMfoPQV23dxb# -K~54f~}fHQ*AKL@3oHv}6A2JJ()k%Y?|_9IPo{P|!?X{O=7Cd~@x@d%ftIAZ_PWdDlMS=8Vg-?R5~~57NmMR(=UW~rm&QPBS;Q(izB4mgn(5Qw)rO{PdrwIrFUY95<@7RWPBD -Ryv7&~diP?}Qq9u;qA{?wI8xLZaV}ZyaHe6>XGIiYi+)pd_cf3oH3dANi3p#%R-4JZOGD1j9Uq@cdV! -G;R8W@TiG&8mT_tkWQdS-rsVaROIZVjaEKS2D6bRGR{_*(Pp-~b5!!I6t;7tJ#!A3@yQ!G;^TK`dTE) -KR@D>_jDWsA?BUcmKo8*R9iVgbI*z1tWd1~@!r6mlb!wCD=88gzy)#_05lH9E&_$2nxuQM$I5-$@$N1 -n0dG~<)H_siShkSOPcJ=hj=k#d1!}6wbI?wG5?ikaZf%6$NX8yxDR3Fi)dPlnI)AdI}Km0S2%5vlow0 -q1*&*RZ7H9VKKs;ASV=?TSoXRHgR51Oe=QIF@GchRG}xS9-pu8L!_-%$1O_=_RFBOw?p7s$yUIAXx`h -oTqs%^uGPp>7CpJ}gcJU(=pk?Np_Q&(xBDT^h1$$AJ1d4rH+6`&~3`yKA&JL`yu|gY$ORNQOQx3^fox<%X$9)mq^mP -c~xP)-!{aT5s<2dsUsN>d@4mf9RZV{p?Tg{73jIipUbMs2M(IUEq8P#97_FM+oB2Jg$pPUyqtD>5xg;&aRP(g=!4UqcbJ?TrJGLr~p{NcZp%zTokuLmcV91oo9*``A7fe>Snj)3+d$UDg`Kdk -K01oJ0(_rM?5H+}`5K6nDbf527j*a*N<+0mIvI#NDf!SRmBAR~;YVE8fH-NH;w&7YctSKy9ug=i=M^T`Z}Nl;lh72M`Mq0-#TE&qfly=`TivE^toG!7q9#8{O0w)1RCJhJ -BuvL3JxL|4N5#=N|{xQwMX><1-E6CipyH>c=CZ{irXYEZN7?Vviv87}TN3$qCmyftnL{^ltnnMuk`J3 -m>5cJ_{E2*}2PQB#uTWta~-<>RvLY=#)eWY`Xv2x#CIQ!9=094&lnkjIt`dA|^e-NFuuy4LG9D4Hl@w4aOfo8;

    @#JN)PffAZ%Hk278BA1HHSi3UjBGWK45Zb7xvW9oFbaAvpI|}Xrhji#|%%z?M?1!6K<> -3iZh*({}yGy8|X@kG$f=ne;Pfb+W6`UaC!h^mH?J<}RwDP!y{m% --pcT?$TR)3ejL;9#l3`_Gx@R -&BbPBlg99_lV+U{c?5lAg)!Nt+g_k!Py>7o0%pjTsS?AYT4)`xlG>Tt`iP|f-l90S< -95-_@-=zANoaarD)Bh`nI6}7$hU7Ld_x*xra~A~bks$VE0`li)__wf?P{RjA~EVg8rQ?8S6pS;6~^;2P#~$`mv -ur?pYBexa$(8iOa{{c`-0 -|XQR000O8HkM{dPrb}Y4+a1LJ`w-`7XSbNaA|NaUv_0~WN&gWUtei%X>?y-E^v9RS6y%0HWYo=uOQTi -*`2Gj8@d4n`miD?R-o9!KG27vP-y8|Vnc}lDny#+pX-o%5DtlO-vx7nbsT)BE01p2 -KHntQT_ajP5<<@q3aC>yuy7$I9v8`26i(1|ia9j6nsga)P? -G(k~8d5t0ZwLSzK;^`()IITa0D`wYW#Ug>-YTu}T~!rX90V3+sXVSLb+iV`a0I(T_Krk=-3hI3NTg1E -N(YznRw^CAbOHM0^6J%Jzg@n)x)d+PYve?Vrk*h)Wd(eRcQuiYU2sx4oR_L(K!);ia>=`2udd$amv4RtiUVUZu#5jL#_ -zzcl?^SxL*SI>%ViR%L>q0=q5x*^=`ATv+ZJ2|fbru6gEFWKj3U(yUXH_`(a-pTWoII>NGt+%`Agn#m(UGsb~@wp0p3kol -m%-Ul71eMY+pDu$veJZH);!SXHWv#IXzmp($0v$5?mMH%fiGLo6caS;I@?-BHD_}(Hp=qFi*^cDjpjj -bUq0F6h4&<;*BxgSsvF3FVs=JBmX#L`N$8=kr5D%NgU=(qMbCow(9P@XJKi`js1pR&py|tl{l -O*^f2g)Vo6~pb-nbo_mm78JukS!B&WXElZ1e-Qqpe#-jxtv4hc6p7Wa=yc##dvFsR$YY{#U)0cEl~XK -&I4kreQEWmWA&D?(Xp&Z_tVe@Tm7K#jYU8=uw-osw&7~RJYOF-72VCobDYfBjJ{INTng?WW^E_v)%^v -4@1Hrl7$kTDX6D5o2)Vi4g`Z%PbtNMI%NEQ(itDnZpWJkHh-8nSW5?f*Cb0AOgv=Pbz1pFnx!l8Y&i` -}9u>8V;|T2OLv*#eFLac@KxQV8f$YgZAZE$Y@Pz-qTX97qcCroBslc9)DzkJLjXVN*I5Mk-DKn8xt=3 -kCf`fHig52US4>&%&iJW2OXJ(dV9H{JI9975DI1XBt>`_DuQ;wrsg33<}-b1@i6@e&_RI?3qEbo0gup -9sz9?vQ^OMD19&+n|Qggg}1I97R%+iB@pJ6)DQ^!I~2lH!s0W#H&I!N(WZVOAkm%%1Xicr%z`VAs?P( -`9fMJ9t2J4@fc+kH5Wt!AxU)%=p)S@_mFLE%sqoB7Mve)8$fpzqd{9W*wzrz;{x`<9VD-X3HWiWKk19 -^VT51-A$u0{Tq3*!Bqe^&aPW?vRwN{KR%ovgR|T%1l8Hxf^&>i(To?0K9te;t5 -s{GV0734t~{;rC`2AhL=P0uwYrYgYLG@&9RM7o0(XGXS$qcps6#?&oG(Kp~Hf);or@?RK-I=X6HKmTg -;r!8~z6_uW?tNi~o<8ES=0~k@8zsQ-eVwXA+v3)yNiWc81lkGt0tt;M5!ddavdXmybWJse|SGD-C83x ->pv&bgmIIVXlo=4VixSz3>Gl{{m1;0|XQR000O8HkM{dg^8Amh!Fq)U{?SD761SMaA|NaUv_0~WN&gW -VQ_F{X>xNeaCz-q>vG#Tvi`59z|v<*l1rw1NhT9-oL!GE6W7F-jhsoQqLe~Wki?iGbO_S6=G5+c>>KT -4ZFd7835t@H#L4c=v412I33NC5`k@JN?N9Ag#_fI(wRv>Srn6)$qS_kUSl?j27zEL1m!-*Y;|II~*AK -*W76+qo!Ww>)ZEtQq-q_yU+GP8F5?u36;w7AQ#4x$>V$Mz?->sOlwZo1EgBiQvNgVv^pCYc+9LIT`1Y -wW_T(Y5v*?=cJo&*sHz`W@+416yMM6@BN+z*C<&jvv(-CC`~IeRuvlId={JqnU>+IM|1X&1z4!*m?{+ -|c!cP%qg+ox?LGB6c(8u#iMBnF#m*wAjE)JT?qMHIy;UU8d8ih!b}b -__2_n0up4vhh7>ct|zCrf02GL*t_cgnMWI`{PWJ%=JwM^Yi~O*FJJ6zJ$ew2tS?hC?O5CPjUR)_JN3eK21_DZ2x$14AORHGU1bU5DoaPJ5DAcQGa{*6GN -Sna$ZO4w;3myfR4O)Al+fi`GCu-L`+=~kJ^1ta?mK*!uqlig1%*PRS};KkGdYvyqmZ@Q#jiU+0xr76DepGlv%NjG1G9zz}x=<~Wfz;75)6NAKFJH`O%F&5WaKLiQqWf~2zPI0fZyG~eyK2LaUFp&4qC -e*=aPHxf9oHRkAV?Cpiq%D+8#4N+k?LA0}7ryD$6zydr$lP4cqObpBn^b9r0Y0k?aN^en -`P8!i<=NSCp$IfAy&Km3RB*8#U#(~6GaYCHC?J)QniG&3M?u9MZPr+bOIAdmg;Bp{ifeAOMi5R3IXZ#jCFOdZ*>qVQcgQaIM4D1ae%BtlnY3nswdd<35qy!FFgK&nl!Q -&n#fgbpM1Hup$zjr1g-opIgrI12n{at&c{uT=lQrc+m*aJ@5wPHv=uXvKi5gVpq$fjO0Mjhe`ky9b#V -cr3jq!3bdAu&QKGz)XINThxwLv9L6vxc+6-^q|E0Y9doUYw#80Ng;gBAy|sn55(b*W#p>>#zn{79<{E -+SC?qmPLTi7Uzrr`obVUBY~HoFZBgfh{~0+%h?n@gNsH3_97;xD4rJ@qbpPLkYjGv{5bltm{S9e)M8Mbyz$hG6uG -0|5aBbpOV=k*PXfsOhL5yJ2^twwr2-0l2q+XBKE`RpF#DqrAW=AT(eP-o7BR8N+=w&;QH`dNj`M8deH -8I?_Zc`b{O{OKbdL5XJAn&8*^G>SQ9V-#T;N$yo{j}@oW2kt_o5clwNh!l$$Zc$u$4SvmvPE-eQ@Jm7 -Jp3;xtd5(zKgc^KyAPS{R2LtTVZTTbXBjCTEvqZ5nmzqbV?Hp{b?+y_$#Fm9Tl240fcYgHmsaQew`vB -6r%uq7x0QTz+jhz`(xl)LJtHK+F{U-y?EC4{U_gl|LBLOjW->4YqLq1;<-hQQYAX8pGtB;@P^h4yrjlHF&xcy!F?-Qo%kK^t9^Vx -JPy^X~jo46h;|wkofexY+#8XRv<4Dwp{hJ-M_hqDUN9q+7A1g>+x3u?r{@>0?5Z!EKA(aPZ176gL#O8 -QerIX~bNzvdR&8$O3MdWvRhg$JCdLy3nK;9lGZ2(A;m;v~;MMrsTfy+x -mKcPHo5368JxEnte&`ZlOXb<0 -ufttwed9_p-MyA_4Yr<2R+>c3Eb1u}Z5BBNj&jn;rx1_}Q>K&zk#P_iKHoro3{GkCzW4|w(uglDr*m{ -Z@>i(ys*fJ%|y3jqi2CV1>zz)2Zg@M`uo9K~Dj%jTr*Gsrh5n7D(_953P%=C%a -?iVnrr^Rd>dqMao01vy}i&%`nbC7`YDK-uU;OW%g0PC$6(it!!DsIyf`iZ7$Q>zw-A?$uZ98|03CI%l -NKUE2kJ#ow6PQ@~Af>is57qA?f9h-2b1FGh}*?)C*a`w|MI~3#)Sw_f{ENzmA7%gc7HG*R?^2If$Bzi -NReLKLnDpu2o@8JCO^!zM?eE;vk+p?JbTF`Bcb{b~afF5HzW?>*DWy*=Pzs&MD8iI_l3LZq7r4(rWY3 -U`Oss+58OQnD&>vG=HWFm -Q12Am%!$NCFFXz&CK||BsUI!Un`25nP!=TWTXv^T#)$)&{NG&)3V1u3ntsTx@Y)J{bbnZ4TI4)?kFp! -4;cIbga0uw_(M;b{(gTwWl338+K<*&=^(2NW;c)R?(xC-+40Fw%lpUcD~t4*&CP-k3)-9B(6l$pOIq0 -31i{~Fq}yVSLS)YSxq?*rxYB})Rw-G%5ms2#;#Qbi&F`iQ_L*h)p%3;@gCA-zDeqhN!Ey|HKbs -qZHRa|x6kg|9CDsg|S9@UDayYj(Ip%zsY)#|EPAbMA2T&a;#(^Kh_Fa)UJSB! -9N)$iD?kwH{5!Cdb0vqKH#JWob<=QNjettUPacNm{U61ROt@QDuxzY=K$lvF)@S3vGY^vCR&PB%bH!- -*7rH=W2?6GS>W9bQu1>eB$d45ksR(1aa;k2Gz#U$ewKCj1Gq0eY}F;9E1}9~9<$OEYsx>ebI%l{=q@r -HjkqMSV1;;@es?eszEr^sn3tXrAbg~yu#qt642OTHIArv}Zc`~p6rjtKO|_#$BHGYfexhdiOan)lqHp -pZHYz??-GmPc?KE8m3zm7b5W#7h7}r6vQcSq|F~xcc@fWX?6ryZjWRscJWUZzB-!4_1r6%C4%>eWr-Bw+10RY1Am{p%TDUpKub9O77!dwBYAt(TqyL-AitoyZBduk-EXSyxiKkYbk0$b<~@!a{X9%Tx -<@iMtYX*)v22L`M~Jfgx#fX~U}F~W$cGq;( -Swzbj`S~m3n)PS65lu;VzOQjR+{vB4avh{1_VFpC{x8zy6R`F_=jxVycb*E_L$qneUbWX56Nw(KiRj< -S&i(6Klbgs*}a#Gsu+v;8@bNN`^5-pcTeWu*^r|CPTt{U~pHO=yP>lCH8XR%qDS(XvHC)C)fh9YHJ(tr%( -&-OU&UQnWV#^+Q^ogFg<)A-HZ7McVe@KFM~lyZ+OUav}x^mw3$*{DLnBN{4anI06=|w6$haudj6co94 -kroY*g0-#US6`jZ!a$y$$Dxmk4k8B1Q-XwMX9QG64LTLP6_91_hHP1fpJebQNW$$(8=v6L2?3nlAbWhDeyspNF-GdZ#8zsLePw5`rPD2s%xZ6QZv18JdR0>r^9Ne>on?YW -X>TfBJXTKo!Ji2Xwz^!>jTL^#-vQ;`AV$9xjMsRK3=je5Gqk*B!OVdrWP2s*e1Ou2I;J^OK?!Y%@glRZ^FIoMJ`lv``q^s+?Tk(-h)d`f_^uxLtRhRZv -;_0VFrERO)nP?IOu2knep5+NdEmKcxz_(`J09s>*;wBo!-ax%WXiRUBLDAH9e8|S$HD}{sH -0}op^T%FXdPuXDo#yPPBk6kdUL%R`HKN0uK=GyrcU*rI{3RId{qT76>C@-==6>wS9#Zci={Z!2q3q)# -LF&0yu80Ttx~AuB`|;!XE@_!Cw$bJW^<(Z`6)~_i%O+~+G@GKli+s+T26Ok7?#*o%tlvQuqw#Ues;p6 -*z;E%TNbH2@9>uYUs~W&k)(V4PE7O)nXQNgNDj#E3&c~Eqp}MUcM~yMRGGCG(x4*wTG44FRGZk+CurT -}e(;ELz4A#4k`Hn$%s9qVDZ<%Wo-u-@4&NB*0JLi>e9ngght__MR2j-D@zoG*x)#S -xQ53rJ}_l35%ncScDbA&a8}*ln+F1x?E~#udO0j@Bqg$eRWbaH(%W+TU(5B4sZ_xdGm;629`l7b6$_K*+V)BU%-i~Y+NY@KZv9Ge7if8$V; -8+E_l%H}t7xRR0TXoo&I>s;=i9USFD=Bk_WXH($J^zgj@u~g*Lz~TU;b$rpg{Pp6<;0YZT$WG-uRKl5 -uF6}0wLsg8-m!3S@q|eMAY5Z$3GP}nG5`E!pQOg&FhMwePsK#p1 -KLMB^{Zq -n91FxyGu{*vLl7V#yWoNV8tq=K(g1nc*D6Ax(gv;*YLG3Rs!dmhJJ&3a;R{XjjXbChS{CR*Yv8y%|=h -38eRPLEUC5Id!gN;aW*sd9?x3*e06cHH>-6v7~BIIwzptb_-?8`Kof9{1Jtj=SD;vV4haFkQ|iE!;h4 -%iYwoFfHNo>G&n8p@9074ByFJ_MLRtcdUT}@?4J$TQsTJw8id5bAk(eAL8fB{+|>!6XFhWzpaeI3^ZF<_eJvVk+zb1BG>~wc8$;q?`S*$5hOH%%5Hvjw02LOT~D9MSF-t9ZR*T -y7)!C<~H7z}_1;%TqKkCas{ -3`Pz$>w4wd?W%a&{dzh{l5T&+iWRj(XE(grC1gcKqQ5jB&ihgW-J$_NakXk%@%1A&Bsz)C*_oyQ9wcf -@plCxJ1-+B9YOg8JWcF!A}R^tX<06Y4&_^Wv;i$dh`AIl_ -%Zl2$YXaTs4qjSJ8jjlzOi|8Vk@U6@c*XukflljF!6xpP_j&cc5;-o0^sHcq$^m7&r(51U&1^fa%FxZs#(JiHVGfI0UKz4p&NAt>&OFhb3|eq0Hqq@F+@4pbLDxl0;Z -JlXIp8b>ziuInCxhq8qratvY+<+t<#)r?W>d>MWvVhKVrj!^bk`3w@k8C6hx89qT0fI!J{$-Z&%`w{C`EqUb}U?q1od4tzY~5<*dQ0uBCFwXE)^;<>R*L#*f}YaG -##LzZCQv~D_rb-Mt(Doek9CU~@=uEW)TCtSk@s~S0;#;&X1(=ufdbdZyE8AEmH0Qa)I6j`DF0)DH%id*yYw*s+IucxWJ(GO{M0pfOHzAeu|ez12P06if -S3t&P+f6%?WEYPF|Jrc)Wf7lW5%cBHjI-^%dhkp -YD_2d5GOR+0@4`n&-bwd(8ninZcaY>4bnl;C!(& -TqENi>!m?svhoE8_{GJOSM%IjG(H9=p!LxLy`cQL>-K7=)sz+NNfQ;z)HFN?zfcuyb_$^7-Dg!~Z(i -M`9lC1Nq*)zYEcfiG`1K0_L(@=5re85FVCJi}R80CJe-&Lh(lv(rTu?4G%S$jzrQ=LCt3;KPYmN7WBZ -3%6yT7E>wT@h9IkXG)`r2AjAW4On-|5Q-I9~0{}ZX1+2`s>ny)4*fb-Yd@56h^77|{XX5MdN#`znG$R -)XW}cw^o8;MyK9K`82h)$xz!OqMZE?L1b!cl9n~Z#oDotorluyCc{lk;fm&Y~6GGDwbxEY8i;SKRYya -2_3k(KB)&gRI-7GjDZm*_gkxZlwYYAXjO@buv56rO)QczFT{z1TZFJ$QKp=;Sg)-xdG_Y60uXKqMDi@ICyZ-|M@WpBp4KYdjrjV6AQQ -YHZ(Jf6;%vJ$f4`SpQW1Q~%!>j~{An7izdpEQVR!$(h_$^o$Gq$K#nVdPVeB+WT3pE%FaLQ8SVTF(C1mSbKqMj~iRn^^ITh|m+m9cDsMtTyBB~KhJK -N!1?at};GOKfJK#_No --*Qv?y-%S(X^y6p^8}qh5`OlqC@5rId7QmV6RQ&E13@{Nb%1=Z?EUu9RpXg!jygrkH;tcX(D5Apv|Wr>5)Gb;Fek~oV+?YPy%GNaw2mu{#+q -jfJlIOQ47#GbncW!*lThAAkAvITwKUP{u%j_he15;P2vuToYE9E}=RDdF8^4>T`JJ&ohwcq@ -w$CdeoohG5GTO<8$?BflddczYUMNwkS7NPGb!JzEV9dm2-6 -xDO^ajw!$Z<;V#Ew}8D+4B;}kNuo`if6`|B7VRcP)u2tFDwBoHXLG5IVTyyaHppP67ivZhmcEo2mF8F -%TVr-V6)gS$6SncC%+E&JDcgFMWtYo^m?knuWxHiFjq^I=_7=mfEj4r`c~SD<0JuTtIiO#gVHyLhe1> -Xl;NZ246}4gvMxN576rhUeDoRqKGGZ{#=G$_%C~wV>gFaGh++qaCUle7WIFowo27ubDZxnHHLm|qy)& -UV4I(G_mveZq~CV1B1?m-Z#Qh+Ci?&XZ=YsTIZXJm`0D5N{!IZR`rJeLy+WYS<&qoOtWPg(_qftE&7A=+w17IPkv!*jKIK)j2FkVEJYrcum$-1@KweGnkL6$xlki4$acLR#bFAN -ZKB^|nJ==Unk>}ICN$H~ -?rI>5>o#Ut>$LkX*4xss}2$jg8+INiULnOS>;{^GQ -0>ym;EV5soVE9Q|3$6iUx+3PeUW+i5B25;etjRXz?U`96tW)(U&543QE=2O*IX>G2*9Iw?4>zE8A^=e -Te<`QCxi#V%uY%G1{}Sf%k*>7!&Urd4CJvB3-}h0xA+rV!N3STgZSCv62ceULKsD{(W>G#E``}PZp>o -e9C|P8E)AC -6+lTIW_#hpxtIEYcO$W(DJT%k@l6C#3G -|4JV-?_tQt*TdaZ7rDkY-t-F=WCRktR~0E5K8F@lCixL%Oo}VCnl-sBsHrY^w@Gf{%vT1Y~B>x54F*J -WJUjSODcBaMpApi53GJfnmjP23;2-w(^!;uknDSpiY9U3Oq`pB}NA|;VMZCJd>;62%uCp)@rO -)Vb4WWW}g>I43}qdQjII-NB7U%Wwq^5du*r#~g|RxLPRFX47rO6bDzQ4v&vUn@x4vpuFT#A2`Lapw{U -Ff>_w3aSU{AAYfTaftCvVsu|JdEv_6LbxW?#3?>TZJ!;uSF%^x#3Y-*|H$!UuKx;Yp-evZ?VW@rb3eT -3otUhw_nB|OWBb>h$!M%=Vp7pU=^jnq>1ZXh+bG}T{c*Nw~WPOZkuU%21QX|ir6t%j(I+pTPx3OIdrB -=8Wj*lxT(Ij*Y{6G{cEzrNtW-W_s!gq_en(H+hZNzn|ac574-4@nt5NI|2KHipt$CRt+gAmf+|=m -Y@eAEwdNHg!28H!;z7qAZ~qtap+W5`nd4HnER_QF7SUK*eZfGvl}Lkud&k>X!7jbbFix{VrnfkX>R9E -PaV){`-9?ng6iVdfNX+d-XgD1O4qgClX2c_%yP$Hq&|h7o?)1iLuv!MNR_Y^8=;AzL^f1=DwX{@fr?B -8aO1|tII;(|D`+bgc6dDPLC3wDRQSsO#5Z%N5(X+!}YOa^P5McoazXk}V)_uRVGX5u+r!R}kGFwnuu1 -?W^$QeHIL#X+%0u)ZmS^6EWW~>k>mK!BRdKS83&D~5?tH}~$ytzuF~y -M8mgqpXxwxY~G<^%=IAdgc-)Hn&z2A+nSJDKQ+FWm&T#)xG)V>(SSL?uINuX2r@vdmdwE!E!uh{n0tw -n7~b;!YCEcKB>ZQ%c9DOp>aWVt)#WGfj&D3s=Z@CDJ;h1l0YG@u;!PX=hF&bHp_rMOzM>$6(EAU6Z#$-|g7Cln9)*q*qanYe*JC{)ET16DiDJyqAo`nh=QX@5D#WCY$g+ZhF~(w|c&*#A5xd6Jevw -t;LKA-A4*%yCbQ?%P6X@S6R6noo2^0y*6^p7m~qj*sIOCkGI3UefMbWvdGhMk~qHpo(_FtC8+AlFJho`*4!^fB~5n%W^a7v4LDm!6u -~?3HkdUKt}&r6}Ex}rZ87^kdtW(X8hX$Wz1CbWr0+B4?0#Cc&Xmo=gpB+t%7z`k#Ph< -kN3SQjq^j5|?5s_On~`;u;Lz3#?clsAB-A~>6DdwNkXYutOP^qCEf!%vUJxmS+2+X41vKR`47Rhj<>m -`uMgIF{jF<-xbH>Hh_xE(o8|>Nf>ooJKHDA)4Fi_Ca?3n;e%8_h0BOD$V^{Q9IviI}13g7Na=}in`%y -bi7RE_C=bVM=3zYqY58;8+>%$H~$prcfmBf46q -(7eI4V!80Y>9UzQX*TCluLCa#xNE*+c7VVWHFVa(xU5ZV37ifa6W0c9P3v?F#+ -CDJC{E2McvRsMHyMehN#>vrUy7$r-AU$G*`-QQA$T4$U1^*aNiiBB_eZ0kkm;luxWFvbbQ#MLK%;Guo -+8$ujacpWJOL2bR;z+aO>I@x2FVdhwZp4U)r$yMLh4$rEw}+z#VKs&@pieQ#N1=0d2^%LMwgAnF}4y+ -A8!8p$C7u)>9Z)j4mWS)W9#RFuFx}0;2AVkAh)*D4h0OOa2%-5pnh0Iz#78~4-98#7VmJ7InS~bhKZ* -v<0SG(z8bN_n&F++4+v_^p=}FI3ndff_5srYX*YhPmPkO;ghr0WYDDE$%bkycN`XZ=qCX7A)OhP^@*!`#%2IX}r2Ed~ -oMKH6LK$(8R|Q7s4ubcx=O+SmO6XJ{emg!xY{NFQW7UQ?{lv3!2p`^JcBHw1z+4`MSdv-gGq2G=esdJ -k`~pG|859en*RPaZL-tU0=g>tMtCzIU<>xy>wgA;v*qDMIdTHNmg&2K$asH!v_7cw+ -sxuEa(aW3h@a*uIP8~WMa+>bp!(+9>jQyO(-V|X@j%X39(A!LPKvMV=cQeK>_(?Z@+A`_X+hPcZDQvD>R@*EGUo*vz?D<0%Z{V;sYUT8-r&c@Th|RaDLaDlI4LLbm -IKZd~Z-l-PBBG-SY=nv<3lA$BGFElcKseH7Hdd(=k1-k?Y1p;6ykXt>wI^IMh+p3h){MP7$Ca^lSZir --(z()|`Vn2g*=YprrK4C{~qvnwmFNyrq$=-qp#2~R|_)=Epn5&^KwjVZs%<*s|cDoTOhbuu&7_HtTHn -Ctj=n-k)$Tg3I(-#z+M{(c}D<$NytyT|^rE?air?X(nhS+M)=%a)QJGVZ?nGZgN3^%-W3d4^B?o3oKN -nQbZQcCM|M+qJfGo{m}K;W;yc=4N93VKqcIOGH*4TH~3V>oKONmXO$YuMXK_jtZl?Kfi5J;RqQAKfo9e7fB#YE9Kd+>&jcp# -@%}pbd_k}Yns|?6DCTkaXkWPYD3nyRP=I*o3i=DbgMIICAM5)qHR#F+oMisT<^nq#Jx|WhutoWHuu){8jWdb+`xM4G78Vj?b* -7HB375{2fWy1B1tB64D+L+I$xlBRQ=Bu&O8ulP8yOq6Lxr& -Rt$(5#vFAMiqw!4nUs!H^Gys@=c_!s5MtSZqcI)t^f~Z4SDE;RkRN`PiVaRu4l_SBR&^y=fHCV1Vbf1 -g3VwZeM(-%i1rkEh)Rm6TfY#xOQc}OFEI@18@?5x?4l`?>v{$Wpj+6l?(fj2*>NJH&!*W?7e&RUIgzR -zwetvpv@VS4vfBQMYVBtO{m&9#)kA$m`>Uoh^1WfuC{yM*{Cwct4-?&D!Ahnb&hidZ3b5rymSXcav^W -(r?KKetnnB9H{tj6@Au{pXYd3Dji(aLd<|Gg?*psl#vrb0HpQ-eb0xH*YK0ehwpGdI^I%Z3Wy^r$sqqkO1Ya7Zwpr^GBb@iUMCNON$Pb -b&-L;CsH>St}k*4?}5+bs7n-E*>?Kcsg%Z|&I3rSweSeLY#Z$&3Jfbmvqy_PS1aCe0(-VbFKoVq^a|> -!R6sa}SKN62$M^AG0B1^SRsFDqcT#RV|y26!HC-xy431aGX$HJx*|E1|a0s#K-jGy2qTvyDdR7rAWJ; -8J|kH_m*eH+ikXlKQhS-(X&;$8Ehrfrphyavs+1KZbQd| -+>*Ggchga?lC9T|LTf)a^ywk@G6yJ@*TXxx9*ZD&Tw&BKv`_NXm!<$ggty7WDw_x>$P;kRd3HL!kPoF -m-qNkcyZE3*ZA;3*ZZYAeOY)&x1K8b!}4?aLYDNbT~&p{He%6>v(d34gi;;gp&wrg(3K14NFQqJd^d> -Z1lCBYLW_e(WTr#?^}SJKX#La@Tr{Upnl+)hIzD}~NIFu@_bGauh;v7dU?-;*rwqL)le;zm+nn}Kfu+ -=&R$QZDZC_ZL=Y-I^E8AVQ4;9ACA7=-6rvsLQ=~YNzsl()CJX&hD+a)Cv?&2`gH7^#Uug(qk$AvAFB= -W~uT}9Kr{3xyxyuEia}PVE=t$VS`TM08iP&I%0-r9-L$D;F7a~bWRo}c)vRRar+xi?4i{EQZ`{VmJmL -bOfB+kQScpQ`c*1#C_qHF*M;O999?;haUf6gt2JK+;fk7 -Qh}dUVI4Vp}q|fb^0g+y3U8ul{5EaZlgOBK=)Ts*CC<;8|n;1I>3{AD{$OETnSF_NB=%(^o5Enx49<$ -qv6i&}Y%%rj=Tje2*Xg{dZ`Zm##B-|{StRpkfem6rK(`7@XA56 -Yy#5w`q5hq#P6uPB6YH8pae)0Yd|z995dz=0`YR*gfUSkuz6uEW0%>bq%|}=9f+H(M3{V5EI`UmCEW# -^y^^;gEV5)?4oJg_sZ7&titQk^k%pM(cP=HKl>%mfcE31q&?}+SA60H6ZX@}}GNZjlK$i1pd|9MXF%_ -~HM+;0#!OaI_=`Q(L)#eg@LU_w*$xqBfV;q{I#!6u89#OUiN__SLQgsp-Y5M%U`fT9 -+tWm0UxU$;F(yHpI8P)%7}ddF7N9>bUYGul65_fDf+0hdKY}4-Ah`PQYY86tQkFEq2mL03iooKVR&NC69&V~ndjnu)%eEx -Do=P&zQ67^$|tOA;Et){(|nhT)oz&U5!_yNFhOP34MK~c)Wm>@Z7zt5aB6%0PQ9wwpWIR1AD6K&x6x9 -^nt3~t(ZhIwE;8;9+vBDoq3&}pl(=3kK4Uk^*pVkc_?$1u-UzoLwxZ@V_M!xsmQGBVHIGFFB*!5FA=f -SWFc)I(n+D($SvMsa@O+FMR-m -t=1p!JQ<=^JXPX^DnR>ld`(#%dQ-c6?$&VUt@v(N1YZusSDq1#uaRE4%OXHT2P8EPd-{&HuWEm1AfDV -;`$0IUyzT8u-t5SUxiZ@RwROHO%Xlr9a%FX7Qojl| -gYZCIBc!{$u77)ry)aK!@2be6HLvowaT}{_6G{I*Hq -ym@q(#bMoxqgzcXPkZlp!GYrwF#@W1As*!0bXJOd%5Is}XmJJe*Kl)*s@kv|PEQ9g62IQ=10iTBbUmZ)Vdz7uk3|W6D+OaJ -c>WZPh~Ef?Kn(wiNXTf->9}>MmBk8}2A`=2tb7XyT2v?DgPFg^brXGNpDr(SRCsYm-m2jh&6!c)YHST -GZ67>$E`e4U+2(C}Ok5Rb6{f=ya5lLY?OM0o8p#Fn+7PBs1LEjVm>z<1F`Sc5y+=#P|oQ1Iz=T(Ys(o -`?f}-zEv9-i_QE?y@m?4A^3yoa3r`D6mtmJ{Umut6 -crL1J6gW%+4X}52ILL^3dO{$Xp-uCuy1(@1__2xJ0X)4pjZugkuIaMmJFsZz(@7=0KG-jOWP_hx;dZ+ -M!#!*WZI&4+(ojz0ouS$xLXnVW-uNYqm$O9?@M#oyQXMXl@BVo}Uib(PVdrIhZnN&<+pu~^xZ -EQCz32^USM*wyjY#TE_0~?ORl7fviMN_#+H72YRV5f5U*#5zJWDP?J0kv7rWz*tW(XfDx;m;-f(L-GH -qdh4XoQM28maX@*0Rq308mQ<1QY-O00;m!mS#!a#HIylI{*MMIRO9|0001RX>c!Jc4cm4Z*nhbaA9O* -a%FRKE^vA6eS3Esxv}T}`V<)HdPv$7MN%>?E0(g3$4+$4#GX6jB)jW%c$#EW>X_{2bT{?LX76X;s>1u -x&88%KJh{Vr^z(iKAFa85zkneu^)bV!`A -66&RGPt(lSpb>oUvP%ZogYmvLJ5c6N4mcVDu3RFqw|K%&<%kv{+7>gWLe7_nuPCad)#Dw8aQB9lDIZ& -{MEA8yObECmYhFOx$5P_VUVeVWHn86T{&Pw{~w5))+>r87p=Z%=j@d@9SZMV4N4cH*09yeiq-Xc@o -G^DO7JR#8#-G=Nj6ExJ}w(=H2t(ziekdd()cB{1*}|2~bjoXu}uCG`fs}gUcjsJP^f*p6*6KKK}F#HM=!|bdWlmb;0H~}q<(~I)b@2Ss}tWm*O -y;wjQc}w{9k?ucy&*`!ktj|DB;$LT{Y{(uzW`{#tR(X^Zv6#}m7zeby3svF^uyEPBWZ4{wq!%&Cymj8 -f|1P7F;f&htxbhKnr1qVI_VM<4$#`M0Js36TdoVn{599y&pWAT`<-3Z7Gd4O>|N55oZ|2TN_D;@S&EM -OsJYTLBv@*mqnlSAST9V7Ch+rOZ%Lc<*j=5Y~LS6Q-DosHB8|yOAo@va=OX$aSmbUQE{1Z-uVec8czK -rvj%p{9%lA;WlB1Nm6_~s)q+5#>sY*Cm5N*U)4u%Z@>-ZWdMWxHb!9F8=~lqMXSQR~;*$wmX(hUt@+G -{Ha~WxP5a*i#I@V`su#BxKZ~0{!dyGJ$nH0Ud+dy5ZX-Ud&jcm61I;B{Ig3>Hvc4#KEAEqI`Ncs}6zh1s?$!^Fo7hI?8BJKo8VagFTlRq4S`}A*7-MmXW(7pM11-&VTsp(f6<4{_*{vp!vfg{(GeU4U43*STFf5;IGkQ%^kJ2{;TK+ -?NjJmMqfj7xSgP#23jexk?7IBL6ChLm&>U5bRV)QCn3n9sFT%LwAB{>)ui4>pMaATwl=s%gKay2MUhO -R5DxstEio?nNM9H;hQ3sfkLSz$!k8tZjT81#kQDv~`VL(p0gcJdGxj!PtTs_Jn&L1o2mKNg*NO#3O|C83og<^qTq2>|~)GPoCYcI1H3f7SlN&|-s8HO$;`;`@rWr?OpN- -HbU%d%n8XMxUoObYQd>FKBo9UeQ||{Ap-S|JI|N7HFE{(P|Y(xln*8l^SR1S&y}B0^flfaGR}(KlHvL -v1x7NhE~NC*s#Npx*+gpVmdzVWhLLRvFVV7A^GT2HKu!KrJHrbB1VXgSgHstfR`pe -i7rNO^CTOv5$!j|A%%jc1n?k{gZepRl;xs;zA1LKP?W<_`t;3S(ZyeDa1{a!OfE-Hv?78lbz;p(G&Ay -jABZkNe)y@<4|#r%eOP2TBkE05VEDw1imfbDREoAgPN@=!f!!35lV>WrYPM;_EhnpeR`L%VkFY3S#*X -rHUSdrWIJNt;gWt0NvR&Bhq9i)a4i{xM#`7cos}S)0MibHsQJVcNi|$#tncNiOzzxV6J5lCoinr*ayC -bVXxIPh<~`>0U9

    m%W$){kE*lo^d-4s0MQ&2VpyV5ki0B)~nUYA+MU{YMfYJ6;46zE5tR(rv5wXhv -^c?{cDIWvmeW$&&)7Z%yIt7)Eo?aH|w?#i5N*U_$0n$caAW-$7aS~I%0)&RfF8NJ&`4n|*oC`W22#|1 -2e2~Wt$buho(9a!q#)g~yxZ -QUtEH+shGa$l%;bl@{!~}-#QNC+BP{!EccTyblB%R3^iU^Y{??DUW4U~c4_$UQPD3CBQI{$ChfHysOBLR;jRlu?qROMs&?Px69$U$AArlF#1eCaFVB-;3*8~P1#O_>y9dwtoV6_eGIkgjp2uHcs2U_6E?7;cKpDA$akrj^{Z?)S?PF -TWPamUSLYmSFi@0Z6PKc$u9p*ZA@Lu2Kwn{buG9284g}WWTdRyqjVCrBJ%)~8a7cOe6Er -}=s`S)vNb3Y#(3@nrUK$Ni8<>=UOw`$_=DbNio7ZrMc`hm0$42t_JP`+8;@`_8ZR2;{(9zX@2}<8GAz -8N12Az&HFU5MfjPm54u`{QPA=88KlPnCgK^j|r^mqY=EZwBsqL;64A`AfQZk?T-(d24ZF=NKlT7&1qU -cU$bYjwJcM6cIlwVxn~!66XobedCPY;TrLVRa}^*ps*Ke*E*x@6Z4D>+#?IbpHPJPd~hT^;(hoVD95J -&j-V&z%~p1I2axu91hH0Rg^pI9~=%_osS{)Ch?ybBT*AH6}E~hd6w-s^w`owv(jG0IU(liX`Gep;*M$RbEx}fByobP -5;+d-~GYf^TCNjU(V9PKVfz71*9sgDtL5tDlShp33fAa=0F_D000u!y|!9nZN@!_sa!GF+}P_7z*Q?- -I*0VyOKHE&|zIxh9E!S2wx)sG#;9>5-CZW(*cO7Ip~9{Ft`R<_CNlnhw?OT+6X?s?XLK6R}ojC)`;lu -Vn?xr!n!l)42%M8u_(Gb}c_o!{mLPeoC`z3JVFUipLc##z2!dI)+7Y3_kokxq)>>yBxU9(1paJk0KF2 -NXLS{=Q#RM|HZ+bKDj}M<$DG`pH|h4V4?%nrf|cqn2?(yc$-O938nu537pe`skEBsx13_$jhq2wI -cffeZjFE;va|j$I(a7hBIRTDi`!4wG!ip@>z?^Y`vIa&<~@M(PDx7j)do({0^OXC?meUN+P2XpU3uxk -DX0hlb7b!MDo}woTHFdQME)jtPWeJ74E7r)SJR&#>}i3{y&ec5LrBPR-!%jhHU9Fh7B&?e`jO*2O0?U -`<%^8v7UhTp%M7M|IaWSvet<+L*@6qRkmtt?I_l;yW}O{&z}6JQG!y(usf%+$=*ebUMMeVr((#ml;x1 -d_S_WJKgg;dd84|eg-PNY`q8Ga$No$b!5DTtT~lB{P_@d<;)UOuRYHjaCPVRQ>0G@yO1l2&J^ -nfaVD4LE-OAIs}+6)je)}knrd<*HhCxrwZ%kHgGb1>*=;G%D_m}hw&^us&;aWT@5Ptq9{Wio|HZeos& -krMrx2|zf@3*ha81F{cE5(UBECtny)3~?Z<1%xY>MlGtZ~K(c|Z6`e7L($(zTui{z{;><#PWUnCJns{ -YsRwf)9?5?CqvPHl%JdQt1}qr`ixhRo6s%p9VIw+NjwZhmA+PmGQ&C4n&GbZ$K(&IbmkK0YUd%obKqc -Std7V(WS?3kurm8XxTn|6n>WR4`K8^e3VI*JdkI_hs=u(PIN~L&kFJwY1k{rm-o|ESG)!I?}z=t$KKf -R$I&zK+;RE&9B#SdYt@wu8Sa*lp0skw%CPGT5!_M^tMsfaFZS2odn<|^6iLsN3Q4B=UPCnz$CjFME@D -rJq{Ko?zcMjOjvP7CI%TMloP2l#2%D#fu`yfa3J0zNe9=)AFfwhe-*PFT0ErIm@;I~#O;bcr1=gU%Qt -lFgx}A1zY*dk2C>D+!Id??R;`HL1k~;&N!+kP$L7j`#Z#4?oI-v+!0+VIH#eT^bQWPIDjk8(lKDKQ%@02z -hv|aHR;Xm!!LN!%qUTYw+=*;kk)gii`54fDyl&T{;4$#beH~EkGFir$I&>XhVJMtmcffoVOpmxuic8@ -}v&#tdELllSBb^1#3ocNs0j*p|w>@02@Sk|pgB54k!vq{X_>zcCqZBXS(YNX%8=kPCAYiCYfxC+}%8H -Jd_#6mrO<)jCbh3l7X1lqXSlC&uVHW1uDo@Zgg*N?=ma&=v8L^y+HvDTIP^33TeUzF++GyO+36k?yN0 -*$OJDOTJxmn$*IR8NqMf&Z)6^qV^>MEWiEXB16tj)8B@yF23i~oTFi}Ui9>sO51jMXBWk%3ywjZWyPM -^VWvy`aMgC%|Y#vLf`Ec_vFw4uR2Eo)Iou8^e}2=P{aOHRhG7S;UYGP38?uDx!Di(mchawyJo_ajJop -TV-V30*uiC4=y}{2~mT6_x2|yYSFB-8SJvSchR#=G9GxSN!v4W!gmM5XJ9n?y}@p$=Wt|EpgX8g42mb -|K34BV`6_BPY-ZT+4^C#27FLy1u=-Sk+D!`OQBPG9<#r*w*3Vx3cAO2^5UhqytwSRy!hXPi?%H)0${*yI>PGJNsCkWMI} -?2Ky5(-z$d^IH9?o(cWx}c4YGHFKKKdd7p~SNPv=~M@3DYsg{M>SpKmd}sR)ix36o<1rN^S^XvgMCIY|4{?XAGom_!JRaJm(l`Pz*12D}E9(b9C)1nQAPgZpf!O(tHa -(-~Yj;-RexCF)uBb!a57;xz-idK%F{0N6aWCepAEpg+^z(nH?*BS9vzzll!PjKX2QPpRwH8V1g5tan_ -zKRyfOr-+StQ7x1M>pUR<#G{;7}b@<(HSyox~`CS1yVagn{9^pyVwlarq*Gz)`CQs3$C!0#kbFdVj|=0rr^XdnF7qu%(t*RRh1{PO$pcjtdRfB#bt@5QgsMK9Y`Lx -~@nnia}w*E1~RZN*4(be5`mBBEK9gL4N?gb3PwjOb`CUW6>p{rkPm^mi6yb7Jv-1{|13>em5YL?bIEC -WLTgEY04qFsnJ`(frzHm*TWyn*9*v6#EKvZIP(EQz}!vQftMMD0Id2IK^m*fpYcNbC;VdmZWI(=6!z4 -z#*Dm;y5b}fL$-f_O3BE0Y~d#@VNi@fCo^dCzZpX+^jl1gBadL;x+@tQ(lA8EMcWwXX}D&2W%(6K<(4 -IO@UhY2VWOE7|=8*m6Q@i+ZVx7ohVIjHsFd1FSMy4(3f{OFXyDG(mTcN2(Vt-ay#fh89X^)N)yB;N%J -g5`|qc=&;6zP)L1Jx$U`2_)>8@e;72zF!UI{J@wm&{Ug)gb(Gg7}P};V`n?@+}CA)}ITDjG+wV2(MCI -eq`Y9c?rureN#l;@jZ#F<;d0#~=`rP3K{G^-CA!QT?#(T3rdfR~?IoGkp9*4RuN6EJG`ndn_qN2p -aV35)c4S`VT{z-*GnB>z1+7ZH&;?yReiJRg#iMWc*iR~^PFIce#j1oK_|R$O1U7VZ0Mz3%l`pD-!ZME -dArMAu>8387lnM$5Ms2$2Wlu@2cJSpL^v*+%+eY<*Rt`bQ_~d6#1S96`fUbOPZ0-lx#M^b1Mhx_`{V4 -B{0@_sJZ+M)6w%#-KwiwE4@@~zZaCEG7O)9BP_A)9iE9v5o?aTP4ZPzymxF>O8LTzhy^MK*KmPN^oyB -f-a-)5_!ec|DAtcnI4s$jrc_7ZLR{bLK>$7wXZjCmI3SE6k|{@~F}Q+HBRL^#+DZz5fE;)=kb-XC#D2 -w7I;h@n+m7nC -Ylx<5l=gBNb$MJ1ZYZb30w9!4K4F8drs)_op6k%7awB{hc^YWKiaH@t%PiW0F&eH*5=-eigv+%#7Abf$g4pNh+G@ -D-)jG$1l8Y9`131U#=j(Hnn{($Tek&%OAF7>cFQYt6Ak;EN3CtGpi`tf`#tXYbjr)GzJ!s{wvVJ$}is -ZCHsFhSq^M_sRP(m(zX?48wuzTCh{$-_AW`FltwoHTc%_;|Ck41~?=(b>U96eML#5@3RLEzVy3L6){8 -};r!%}gcHtHp>!wlDo -k;F(k9<+#o!SOiG~tI?f*Q%-ez~Q-gf~qkXQt>i -O^yusQH%8~u3p-HjF;I}Y=VG^oC|`Z${F1OHv$rG5I@|z3XUJFKj2wGY2XgTv9lw?;=_vK1Hw14imQ9 -VgPz+rqSOJG(zE(i_YaYJ1qjQ~gKBi1#AS0L -*HO$TCUSg>dWY(ZEU2H8EDVh|AdWu -<@7|ja@i2Dnch4Ho5ijiHEvvXvZfWG`fQ7HA&v*rn^3ks27A@`cM2vvQQ=OYjZI(4fBXVff5t-qC1$RSR<4qSi;epalWaeWT<;}QPoBz)=nl__GPfcHMZLj#Tpp`WO-5QGh%+Xb-=Y&qhhCmO>vi9#jLDJuFs0N@vo2MyHnh;n`To{lKs`EHNN -ySvv>s`oJjN69;_NYZUyy2%?yMFJg-Qv!UN!Hz{$Z2bv}zZ5wm`1e2H-&;-n9{a=^KS6iqTwT-~Af1o07FG4c~rpCod<<0A-crm*k#l)dzzJy@9z2G8(r+I|*0xDfmYp7~jb^5AHa`t -c0C1l~W{;v79Jvt4APd8Sx>gZ8A+pb4)7@%IwAl^mftO}cEbI**5A?Mj}pC#hIspzQI3QM5EW8LF;@i -fonoqTSZBf}5-y|uP{#?!a^91QMl^1In2US!v_NS$~cYIxihJcrmHY$SKK1-XISrN-x88#)*|<=dh|R -Q;?$bzNJ0(Rb3NEF6SDaFPGKPm3hNb4VNr-`X25hVc6&n306{tA$R>D7juAjiW(%50EdE3T%Qvx)dVF -E-Y -P|3`+4e8z!50s&kDZzNT>bhxC+$g141Ov0(3|DdY0ovfo6x%EuEMKWsmb_@d1&7Jysd`Qg}MaJJ_3Y -X!xTxM~Rpj!stJ6BYMhbN$3Bd0A9Iu&r+?P7^gkGUYx(Wt))5Rm`k?tv=1=E&6U&{k0}O%$u&kz -E=Zg@_`Q-P=Bpg4gB5RyB}+mM8HR>0mB80UZcV86Vjmk$)U>)h!D3Bzmm3`VS=a}7FSPrE$+OlD7df^ -oyzD_U_SA$Y7obChFNW>gfhBJ=!A5lD40Yo#5@mc!}(SV!=yl4ILwsX -SLHXwdnm;_dmzPo@$aj6!SzHg4$YSnqDkN%=da(tQLD@S*Anb2rmd$EG1VDX^PIa=ItokjxtMBx5{RfO@qPh5mQvFd>*Th|ldGtVYlWsy3wR_GiIz -faqP}~X`GJII`+`v#?I)|NqkV#kjTZZPe32B034_Oc;@6S)>m3Czd$yg_MXfrKUo5i81~sjf_Ig~@tl#=wuDzc^S*^p!gLXGiqK9((=s8s^w_8iPSt71`qzW -Of=<%~ukk@>@P=}?0F%GUpFqwje+3E-AQx`gUgnta-kE3J!qYr-!;k&_e_--(S@A@AFQx0(HsnJov^n -3Lp1hEy-)4|9Y2#j}zM=^=_u0VL?h#k=1C0cbs=^7vW!Yng-upG1j)Hg;BKE>+xR7XI*$uGj#A9Y~=; -R`BN@cTi0LyIGVHH`8L+j>yeaKzf8lnnn6RJV9+TcHk%@UuuDu%qNWrvNkMCWceLZ@dbPXn9U_wqq8Q -Fp4TGP)6tNFnSx&|Jm;FXm_jHGO{hJ8}p}cP;x@6&{v*hE+FQt!Sv#!Mk+c~+zecVA5&$alJ$Ey1vvO -h5m(Gt2*yq`O=A=$9t3QbFN5_x6ys{_SyUsBhPYz>*29zBf?HunBTr@z9|;7tPS8iFfGNZ3@=j2>LOXpaXmw?lAOB&KbMoFSEb#h$@b=JIlNcZK$cdhUB0?OsS7MvelK#^3mI4=JL~jDo3$M9$> -!rNW$OH6~n8nLgd26Ocsx?qZgC44Dw|e%4WxTh@OLj17b=0i=-PZ!(AK$g+?cm*$9B;UISf^EZ7p5&E -9YUCIT*V9jh+-M7_-t4q2rOUUyQ1li2Mg|Da^mR1mkm4j$mmTQsws`Ght<}c@o7MR^=HB>4!{c~86$R@Oab2dHtO%_eW{3W)kl8t6G4N8G`)-v_7FVbF8b9|xl~$CH)D1;=C6jua%rZ=RA5^JzU55znO~gX_DM;E#Yo#AB~a6VSq>acm%Edcz -vFUm{7v --k@)if0^D&!$%39usake)FnQM`*?|OMLb5aUFX=Jl)yozWwbCM-*Ichb4_O%&K#l=-$XQfRNlAT2vT) -x*Se?l2>X)9|KK(m=dy%{o06shoRjbNGK)}olzzQcmiP+ODE9lYCBhR7O;z`}4|>e(g+SHWt&`tTI(Zv-m>%b${?NSQCZnP7oI -kZu7&1#*2!fHw)=aL;Y|e9!Pt$pI@Jp&?P)@%}p6*$xriS!k5(_-@F?151+Eb-qXry&e>I42roorsKA -Gl6A(?MiLhy5mT1d)4v|}4FGOuc-)gZ{tddpaDxM>>2#GQ(!fWouMYPw0LEljWD56BPur8xCS*;g%c% -}cn?eUTVu|v?T<1U7w>)XzOZegV5^vYsw -DTaHcx@d6oleEzgS-QMNmBlHI2;a!!>7-WpLX2%N(Kubxu68lj2zUoflG8%o1NfY%Bs>^2LjIL##_IE -Sb#ro?JI|`=ksKmP~=mFgYi{q+(4<_nJn!D2<7#tO+i9|BIZ4i>1AE$!uT*k4HBRU8u3c9jd~=2iuhx -W9)Sw}`O06-v-32hs&`~_KDjbY7ZH^UKaVnO>TgYe0%!*xBlE@a5;lfQ`S6YTJo6+rCLz8vEw1X>SJ( -|oe5WAZ?we9#T-sHsM~!VIW!uEdtRNH5!IW{*ub49wh-DHt||8#47*l1rKH0)l -geB@D?j^}8!+pB2IcL1fyv@u+?W8hT?Tr;(|rDl!5_2Ez#DOY -0a-h99W7PE?ogs5vXXQBH~ekX7bB}on(lHoLhW`bxcY>oCn=Sim&Qwa~1I660pc|iTDyb8)W6};CcLn -B3(SHYR4@W(h?@!haWi18F^!*G32$wv1RMup^M*Fw_YMt05QW=!dA-Uq6EjH(V1(BdNWo>1*83__w%1 -c8N5osse?nsb$~9j)qF?tDQg;3kf6Fm!0uQ3a~b%Wd^=U~W`}2Cp*g$3_`$@w3`i7?Nc8rJn( -nVmf6pINc&`C84x-BOdMKh>y4kD{;kqUTUD@TMg})V2AYHG7Y}6tKMx51y{+iVHZ3xKqrNRt)_Ev!afKfhC(GXhrsooA?CI0jt7$#j$NBDD602P* -?tfI55d%RQ7`$ug1!ZLt`N`cvQ|pD1xKjY>k~G7rnA@azkBQzNg6EHx`;(;G(Jmhpj^u=c|P%_x#lBe -S^7;I4dqecUdcI0Ip=$vgsuP)!c;vh#r+Rl*oSW=`H$`ahz%fF^P`!ETu}~69`M13Qf?V~2LcSd#C*f -E7AqTta_-qr$ue0)`Qp}{d&coegeu(} -bF$wYd?*9r8VG%hwE^thYEcuw)00bjixhnfIX^c5Q7&=85iY3ibdhwmj2?>YitkWov9-u#YaNOZwFIMUTk#>k#4Ka^w-?~S_DR+5kF -GMYmN4cvb-y=@d3-w%>3Qgbaz1u^%mx1;W+Xek}CZT6Y$X&`voJZ?LDMpY2aD^R&r_a4kI(__99}d2xSGI_T746?xRDziD>Z87}|%jyp{{em=jPIvOnqDJe)S*8rq7#9bgDGVeIh6b<8OMk*q1U4e8pbG?F -HzNH*yT+DP8Dg#TZbdz&YV{zCR{qn+I^4K(VZt7vATFZ286pY^*sE+Iyw}C3+L00s~Ze$P4#?3N5kpe -i7<3d;S1GwmSkDE0Lg5lj9b$^T17&)u_;2>eAv8j-yKHIyN)9CvI%^g5U -!~4ZQvQnZ%)OpzVs3~Kf@P#f=^!H^G+I|y@*TPBJg`_ZvF6(HI%RHQO(Yx_7ixV((2nUn<*eJaD@^Ew -ux^xkYnv}KWeZ28j75yv|aV;QR^A6r3uJwKZ_@@2=CO!64T3 -W1IO=^u5FCu8W9uE+dew%{_=f1`T0NhH$;gP7M-X(BTFWh -Eq@yI$(iU?*;E1_BS`F|tS%*D4}E%cZ$qKMv%d4pU(IV8HH8MF;BZ5+qrO&}sxo$vgujVMMa8{bFCWWOO|unICy=<|SbM2oSY^jLH -c{vPJj8CyPzM%cR1J$Ux&ti6=nKGNjQAHJe2bBXZ}F$s|MUIp_v2UZ{_^%clss17Za}ST= -I#?mxw)u@^?Msw>RAsYf%xvC4+m00NqZQlmy<(r-@l-k#h0g-f1&fot%7QQqQvmg_mpgRb278$uThpnj|s4Ok@JQ06)pZ#=IT})woJ0C_+S#xGx4CM)kjHeku@SlZ^<3#lU<8e! -(otb!_Kocok<}LTFX-b+QAQX>7aX>;Wouq=ALi4P!^{~OMMPhl2 -8VZCpP-xTl!f3(z&i6N3n_zY!2^oY>WIV>ocsE^EN<6b4mie>y --A?Ba7*D5{`VT}JF#ctuk7_rxNB4!P3yb1=Qvm$cHVWSJ4JH3x?qg -+en=sehU?-}9Zla4(j=DLEJ5S1)7RK6~w}m@tdC{2E3l$OcOO!OUHdu^LOrV^{t3@=GJ)tK{3;yg%l3 -F-*1*~Q#ND-3p5et3J~^cbW)g15e4^0}(>AbglsWFn(p+O{TgqGAoPgj^s>1q$kr~gj -^<)uK2xlzc5;a~v_sZ}y0=Yh(N_|8fh{1&7o5*q7*HQc=QYC{hhM^{(RP5gZoFnU1F_fQ -5;m$>t>SB1Pa8vlJMdNSR1KjXuXhWi}V(z^G4xzMPzG+rfcGmmOju;0|qUsc;POz -YJrqvI2$IAuvO7S;x+Z8=}66}5xNxJE<_%tgY6W>||lbu6dB4$sG1uk?JDD4N#f)B0M$B^y$5bo|ts6 -jlC^XLTnV_4)Zd!?pgzq&J=2lhmMp@&{9lGgWk0s9QS0xS?eoox>HW{uOAFNODMd&4FJA!wvHM7F`R@ -jNo){PHsRAk3NzgrU5YdPO;x*heL!b`?wPzN4@dfGdYu8jc%Q^Y_G( -)@V3QMATThmZ&v(r1b;|3mCwF4@m6Y%vhSp|G4WE(fvZP48G_%xz-syyr_X5s(z51(^hH$@>JClgMoj -Dwf1#_aXDYs&7nOO{?%7!bWmaIVuKw1jZk_zw=H(byR$%~E0~SK*G>=$$`PtQzK!mvT6nJpp)sQxB0l -a$n^!bQhuc`-L#jU2wWERtS#(`FQd?yv7l)9|d@`d3duM#l7&u~-tAulW{akqy3{@`RbIX*dQRb3oZg^z|$kNVF>qoV;V`opKshsRGx&-;hZkB^=|9gLnH9uG%P -M}7A^x5@2B)r7d)80N3?YGb(gU2Zm3-fP^nCjAEEdJh;^j9i&@e#3G74ac<+j_c?SIIdqAsAXgj3~r4 -jyrp;s^Vt`MxXeET?Cn?fbhQcUYP?QQbjLwOAvPV&{fqw{NL0wUxYYJ8JEfLKz3uzeL!Go17UQXf}#$S0+XlwH`W3n!aw@pR_w))dWLO54I0JldZ9$Vc -kBC{;!TWt_-l#AU}B)O2|)t1HhnGiaH78Tm|pB#W#o5}zANXQ>6_Tkfb3@oe%i{|L4V-JRpP-xcuukZ -*~4q=hpl-WF6Z-{7;s*cL+NRMd4dGg+@z3{-M|e7Lp^fCJIx%6-f -_R<&LATwX+fFGAk&wIGMDY%5z1Viiaa!w`Gy`=AenFI%-4=&-cZy^3cqq7^SaTphU)IJ*hUT&V-&zV- -%e`AbpCb1m>c8t8;tqCC5*W-!oR_o+hl%&F|UR(R|jJo1LMEJm|G}iwG91IFyq0(XRk$hrT;tN)H0lF4^0(iE?4uC5zR$7xKvGGvr=qMPfWL$eFd6s|i}jrFS4k!-I3z;|G -d$>;a&wpD7?w^+$bG?-G9#X!j9aXSMLt9V!}4eOvr=mmQ6!4*hsg5N>5f8s)Ue!k;mxMK&RGT7vvl=d -=Wi+X54Kq&mDyAm|wD@3J^8*10$)yN)^J1D%F(8M=b!Cj<^kHc+|fGT$b0$Ti$dxc2~b(lX0Z4ZH0TL -Mj{bkMEwKV^Vn-;PDFLVc_xWs1|t~fBx*y`!_`hw{l#A6X@505ZCUgCd -Yk|7=wYO!?ECL^H1be@Bf&s@iGETZ96kC=pE?D`)1>;Hko -E=Z|AAYt8_RUx=c3t1W|T0%_rU{+|n~aT>RE{uzgb(()cZN?CZ)L3u;x*8mKMM=6$Sk+!&HwSLNFn7fIdxeZ$Wbfq`B9!E+a>T$dUUdm|Sy+}RzT91IWZOAMjRQ8hD0X -yVX?b9YJ&kfEA>&8Nijv+4qRl?@Z23xFRRp_uv0x@cdT@6aWAK2mm&gW=Ym -k86y)00090E000^Q003}la4%nWWo~3|axZUkWMy(?WMpY$bS`jtwO4IV<3ji;#8 -BEi*><{9CH*Q6d8L%wm7zLcLStV|9fY49ls>uDpjh5l6YR8nR#Y+b~%P&QEmi(Tq>}VK79P};a3Rb&8 -o;#7&E1K{>Vh{xOWUwCRSWZUgW?fESX>r8+a6Pu2^crLNEr!0+MAc9+?fQfH>bk$%HI&^e!G$%yUG87 -!nLgwrYt$vRJ5>STHnCA(pa8c#MdU7Rh?Wauq9zxZoL+2xF-wgQHpHbJW*?QWj^xb0FPT1utAJi?ss5 -q!K(K6dM6}o@DEk^4J<=e8sCM1faQ)1iO}42jyzRsz~{Qz8MvVFy;Co<8o<3$_f5qtXM -&y1L=v-B7P{InJ&!qJuehyZE~TGlue^6m0ZSH#sjNB42PHoavrzp6O@ZoxFR?qeD(a1U7@(YPeXS$gCGR&YC7@Ukqw?do -LrAQ{|j88w;x0>@vgiGp`$>DtpGiDR-qv9l^YJnD0D8oi5K14aOp)pAzvc215+oAyy5l431NC2PJ@|? -sf;RjKkz;OGQ^1PmFq_X%m8KJe!~}-jh)Fvhjp&8o>1pH45qiC_hlSa2;*Qfa#3~RVy@1`#5G~D*x|( -Su51`NSI!q#`v!;=YBQ5*1#vTWwE|;1_&1Eaz^B57fggqVYGd_bR0G_2GuH+u^k$fL1#uY$7>X!Izd% -Ewx9=J(YDc$gXhQGTGy1uuQYSHToeAR32%_yW==Bx?7x%SfVubq=_=;8v(C;h$%JW_?$#4!pAX1z$Da -oxAKkUY&|EbrbNGV&ue2!y9&F2VbslkA;Gh^b}4S%zllh#HI^Qi4Ep+mZ!Cty(X+w;HYYL=%@$0l8nW|YOu9% -U8Xo8t!||aA;yO+Tje)7z&V)DyZa^`6#R@5zsPZP;hX-ruXDj6gN5^)WjGO1nlS?B4^F!cPS5-aX~JQ -sc7v_r62tuUH|##g@WeLo{{3gT!yh)3f<4=ivscxmL>(WrN@+J-GQ|~J)jiiB&vCY9-G);XoM1Gj$gs -<=A>lx8=t7^3dZ?)NlzAMa~rgMsBzIV3SXxEvtY}c$+V1;$asJ7Xmx0CFkEJ~|mO@ -^j6GOoq+Vk#wr&dyUzRDoNGy~%8g)sGD)FH1bzjSdeV>K7Vxd$r*#OAYlO_S=DwRae@%VXAnXvh=^@c -lJNzch<@exu>pGJ;QmY&LgqAeks=e!Zow%E8A3~Dm9mzSZpXiwkp-;wBLpy^ezN#jC0sBWcV3Sn_HuX -Ws)88qMjf2&Hc1ipQr7vZunNKva`VLLp{dr@^2< -QmugO|BHD8-WK}I)n080;9tgT%8g2G#w4E**I;_f7A9?e+4VEt1#*1z3xX0mJJ`JPDvfSmea;8b$9;r -Bs&?8=FRBnC34jQqxGG|W5!icmMo83|2d#_#*vZFGhUc0%CQcJcvK}GDUwXn80fZvE~tAlZjw7Uk&1H -ye4ziHWu;r}~njYO9)y-bn}Z -xO^u{E)F~Z=TalpncZR49jF|`RhH10|9s8*0;+~x;+fdX5UFRZ8Oil*QZy3|0#$r{an&ci1UP5T5Gp& -hW&2P>XxT#ho8>%%P!HPyxO9KQH0000805+CpNx>j=j_S|=0IO>Q02lxO0B~t=FJE?LZe(wAFK~HqVRC -b6Zf7oVdF*|Af7`~f=>Pf@2z`AB+8`~;N!louTUnM9-N=@Yq%`S~RsJ9nl3|B=bkXFMFP9CyR);iv$OMf5KQvXY@7ts`7|!dG`r|c=W7qv9t1o2bY7$vmsJoBJHf{K`qS>ldjIPnc -pJ~g!ErBmJ^XH#76}!8n-r6@EYmy-(lWSAisWn_ToiFuC8H=fFOnq4&x7G*TwEkkP~}0K&4X!Dlu#i* -tKu|+mH|#2453*pTU`R6GC!|w;v#|KqaZHJe3-@nF&O2;*(AxTxWX3C({WM;VRZ?830g;@POC!=jgoj -Aq#3|V0{JSqNvq3zRs}^;Rz*5QC=nDIj%Oq8hb%BoC#h%!0T4b)1UoBX9N52yP@HRfnuM-*+&wG|v&|~fi&c;+vvUpWq#xR64#5-dTng?9R# -<&XXMOgtQq%aZFyr4$i@%FeA-|q%T2d|EQ+CJP3_Kt$LhX;S#d%62EXl)rQlsMde -bNu(<;8n2w=I_A|dv9JM#&>^yd$@aa6dW7|d#~T_@9n;fg1t98`#-+id-GlJ0&2cFI1cvrUhf?P*y96 -gQUKfAg?g_5$m`w1o$ukn_KUs!z2m<}!K=OFHwg6=fZYz>ZXX`+?fkgEeHgs`@$l`z(Jpl1B>;Z2_vY -0hw6ptq_subk2wH}x!S3JSXK?iW_WnLKxBVlG;gI^cbMW@>hkM_Be;j;&u>W!w9=_Ox-fh3w-{r>M{2 -f=~{`TJMD0sR3di%Rws&@cz4ymB%;ZNW1(j#bb8~(R*ym#;hakF#q=J*hPB5sZk4v$skpZ1P+qhR}R? -+6j|>hJ&>K*U0w0|Emz-|R9RL^}2yV5r%FLO7Nmk9Ku`UhZ!11GFQoZcZgm?%H4gi*_&wwt{zStyXJI -@f*p2y1~vEmN?K165J$#1vn~s1)rxGlVS298NzA`WL4lWlL>rZTSNM-flLt=P-B%887yKT?sz&a@_2Z -OaEC<#523V%jK9isGKIPlbhLI|_J~%iBIYFnbUaQzJekMCp#m5orDG{z3`pEXaxojn1;CvGlfg9uP=c -R;t>|t>!91U>jqf^rTV?gK2^f^lGu7@M2K)$kG@8jS!Nu@^QX)}W8q=?GB^9P -O-#3u3WDf$!=vuCOcAcSH=psfrmgj!T6IU}gpIlJOiD_}e+mPS#sn`#xdr69>=)mc;tlcoMX}2lfU`= -%>6GkG^OHA@mrz{sn9XglNG6V0e>YZRfW2tc=GqX133x;8{Ex;h>V_`4_D_;N@qtbX*0qDfAL+@81I8 -(Om>z_EVVPcznl9bN_Zb8{GxTIKdsF4A#Q^+xFx3v$xo*VwWHH1_OXU7(BlVa$sw51)aM90x4B>jZ)y -Fz{Rr*7$xpRvnuZbhOU#MqA6R`9WZNkHaFMS0{EO4z^yfD0(AioM6AHZ7>}oyamnQ#NJ5tg2mHheTU3 -O8zzV|3A=m8$kAqg!f`8qIU$hwRB~AlWa^{2HO<`QHzhCm3R`)ruSZbt4b9hAK=GUE$SY%?JVRxL)a4 -$5s6**?Bsi6BjIBCP~I5k6I$rBtH3q`3lkS0Z8 -fcpUcc90Z|TR!=_KJ->wgLk0F@-uuJ<>e_Jbdfcd#U^@XNg5LL}=7K{t3dttP{Zzs?W|y)vH_!vvVuM -bgWX>N&J6mNs{Cji}=$iLZ*}T&{Zj3KM;U&t!3ww^Y@6DZheLl+jpKRaK}+2E?Lc9JPWn9w$UC>d}-| -xBLauHB8Rp;gxzYpC%>uas)zk1&EQ|l)u;3s$#yWx!9n(#Dyn|9q#74z80$3w$B$VUsN+qZ0g4{{6Q`|LR`9Cm;etuRpap -Az7LL0bqd*X1@DMoC!}49$3LXZbLAH@gtDT^ObPa2hX!2yO4LcOFuALb89dM5ylKBmc)O;j%BzgpguJ -TP@?Pc^UpPa!agI{qJ_;(k?7y3JyR`YhW)_z6oJQ=;1GkE*z+_=o7vTKhFhEw9p|q!({;`psXf?Tnf{$dZHNbzUU=V>~~|hWvfX@)vzC5E`-A7clBwnm2fh -@93%EySo{%9Inof>jl-WtscFKt9Wdb6b*^3KovV839SxM@_c;opTxSD@8j=75Hw+Wuaob1Q?pt*OfHf -SSnB91ogUME$u$L1`G-R5(QHax$M47aRg$SMJ3>MMBn4~jpO86=eRm}P3Xu3!0sOC|$R+C6+9XY6vkA -dx(L#Uw^I1w&K&IOn=VfvnUx=Rv(4Kd-h5VS9o4~&4d_GL908 -}Q!8*Rk7^u~=GTy$r_YGQb~Mj=(1IRe}QrJW+%A`)BhfEDEIj?P@0SeMfekiol-bV{lV_=r0!+exzP_wB`0w`l`OW_KErWq -guT7NNyEsPLVq!%f0qhk<;fJ-ve3&|DJ>^%JdJT2xH+fXYuCI!LMffqs30#b~y!alr?r;J>&e++nWKx -q(|VZ*7CiQFe(Q>bQeW({#4lKTiWpLb3+pPsJ0{rli46tU}^te*y3Tfx({^gO@nf{v4t200jqZMCP1wBM0UKa2G{$&6&cqIymQw^TmSfoYa6NdugUtC=9fiA`K6$zn2Af5?fm=BS$ -5NQw;SKTfyUWkgg8J4q21c&Af4rzHo%0^fXFhu;uVl<%!UMDD0XLCGsjUy0Sp$d!-keQ6jq-pplK;au -AGkE-gzVOiCxW?lRK`QTPDFNirmqkA9rgWM?a>g}5$9P$oM-13ZNW%fc{*lQM#FjJx0eRjF_Mi&xl3Y -wdM??rHQ0AN=oyY08fp7%_=sP8~^Da3cyTF0=tXboNwBj$4 -N+4h2K7{eKKTO7`Z-?0;=BwY?3>|_J2HhH+uYSW5FIQB7=>F<9Kp5ii2u1Xuo?mYX^@5*e*j -@Ghqe%C+du7e;O}Y2hb#jyaOkW=O^{GIK#9(M7)U-&@Uj652t|V6bDzXqPU)36I1C1&y}^J_F0h -`@@eGa%u@>+lWJ7`rDJI0-CKiMoh^Pp)J7NjZ$6Ae`1h8&dh$yq+eX>0$-^JlGh?KYnY -bK?yVc>8hrxczOLxiQTgG!a}>0kAF0fax%`H{Jq}viFLqw;zKYL=FukocbsE4Tq8pE??AqY0L}{CAgDen!v(Z`85+gR&>b=j?EF2Oxhl -oYdM^y|rg(2Q7885X~sUy)-Kk`XTq*Y`y3s^_g!>b$UsT0x1 -~-YJ8;^rWQm}oVS|^Hjm_d9+#wl0BS2!7`&D!Kypi~!5}1MW)w`JK8P~; -aBCd|mhxf?wGI)fx9MwO5FFO%K~car{A+HuP!W}Vt0Ij+T4HKS!y&HEX<`d#8aEkT4vk?#aFXWY;t8cPx(G_o=lY@u(T# -kNwi!#85ZHRB7^wI%DS~bgU+3wl3{d0%0CI8x_C*%M$|r(4AHzws1YVcp_0#N3+{<|TEBFNrDx4-g#t -M8P#K2#`4gIwc+cD(Tf!H-PfyK0k+2|>Om+(fW)yu)zoK()JEw-n>8}PXnzL;VoK1B9#mK0L$tQRCIl -pP?$eC$9NlCJ4KFsMyWH*k|W{NgFU4b}RK46--&;<1Ja94>6Z@KE#3qil5= -fWzziK$g8-)Ys+k+!bELDezNUj0mYgi?~WnSZ4F0>DI$C2vHaVp)Yti0wFcT-fTV`p((`}{&r9q`p#+ -@KcV6`>DKEU@1mpi_HbcAG+2?A+v+0 -&o?msu!WoUMN+gn)-^SXtF7A6$JwV*c05l7bKVSY?^->Q2eBlsl#X3oU#FLPG`k;Ql~!a -EMIF}MZ(T9JKJ&i51}B^BTLi_@zd?-l6~#h|#2d>l(ILs%iI+Ch424xVuA(T$go-nnWNOaP$}FE{qi= -zK<6N;Mw+r5(K}aPBAf>qRoW~Mpvd{asv{xNLho5a{qu -xOMe0~S#!LGjb?*NohiaJ_xE3Sq{IyxM(lJbnv!kKg!qeVfS1+xqL{^X~ -(Ymw4J;{s3HA>Xet1Zvy&QZc9NZ0Xrrfno$97*d=69~T90a8R0?kXD;;jf^Y#$DK9cwaZ7l(pGG -Br#hSvr&~a3Zt4MQn9Jtb=lsEw6o#P?KsIo0lIV7(S?ADsR&;EeZYdE5ROCk<)nXlr!19>ib`-Z>h+$ -9FDS>r2Gj*CCAEbD=seWL?!*bJE-Oi?V#^ScOy#2#&O=eq0UWmGNen_12)*qmZLPs@KaYc^`V){3E0SR!<03Pe-cE^kt)Zm%{)es<+BpX|lDXzVYHGz*=ugixIO9<_M^U -XI;`WsK4_RWTHt)NLz2&8+SZQ+9I_@q!gB6pt)Zo-Ab1P~IIosNMh0@Xs&6lB|Jxi9%S5Ngw#HX*6q^ -uEWavR{chr2eN;o)T$>hm}QTWCODa9NAiAUfc1?&w)8YHX}v!;=TA2{HMR6zCP`@Pn{{kD{iR6Ror)S -^ju=YeUGE3R{&SNs*N5?92dsbiD&d!=2bk_Vpd=c{~n|20>#ZYgQtDt3H1n$3LC~VTomvKKw7+%`85(dR_xFV_kex0oYt;6J+10m%qL97N -IfXEfIb36`_&ieF6Tp3ZQVv$n)P=;71lvceF-jA6??VojMrN7Y$8(JK40MqJGs2*S;lPr3K<>;%CQc# -8tX_0i1}hr#`I0yAj)_;-ApifM5WucrNLNjm{-V(D-oEMIn};iD~SQCV~@$a0)H1FPI1{69I4cU`UW7 -D`H8Z96XTb;y3ZUl*^oMiTTivCo(oPqPs+?wHVG9kUReI4KwsjD=PpIN9cE=44(uYb%bx8pwTR#l2N- -Iot$<&BlOdb2J5MUA7jeZ_fsEmUSxhC{7Wb{DjBM<*?vH=?tcwhPNak#ih3taA?cvdazOFhGaxe9s`o#8=HgDEJ8qtX$D$yUc>DI~SJM -ygPM;F3Z_x3J-s1K^v0`e+o;iTI?brI;F8v((hh?C$N1wP~m*G5M)_~6vOs&Y|w6$RI;?ZO5!ucHQX? -Ra-**_*3VdFP^@p&3_r7193C!IR6$wZr0PXHe{qaaMU;_#|fJzM=;(U1W -7iVpfH$(Mi&+^b)yt7rN*UVHwH(%PliLvN~XXB$@s* -E-AdvC=upN+Ine_02m7j{NkY_3@MWD|{BO$b=(Dw&sc^)W2V%eW+4 -O&f(#(2})U%eG-jz1+q^TMeVvc72l<;%mOAXw-L8Z6r;idQAbk7oIy%1)Nt}BW&0u0s+6XsDU+sLG7e -~AAF1RFH4#|z)!2yI+WTxMg_Teqiz^Z7VJ4I^fOdF6jsAedmn;48_zr12o*o#LTAfF+sCve;4Ep4Fee -+MA0IicF15$z#|vAviAW*UK=3DHcB65u#qWgO1ug#07;b18mxiB1xDER;sjneb?S#dYX}WoXa$6K>!U!OAFQ0hM!sh-cgFj5 -}q`DZ=t&o@nyd-W~Go!yOOP`7Bm -=WAKGC_x71dHlKR9|Cciej5^Jgl`Kar*AB2-!nA42s%Hg8pc8dnC0BHoMZjqG*$gJfQ!$)Noywau3vn -CFM7ECG$wgD(co>Jaof>s!e>MhW#?uE33ivltUifQq2UwdLsG}o$Ml1f5jv3@{aBOxQuBby7%B3wtU# -P@T`GdqfGr2Cz9zSnt5cRCy9rOqx6k46+q^o2!-C*lKH_AJx6_O~uoC6 -c>Y^}26u61i3rz$>m{^H`B+{DHS6sA)^oA)XV4s3$t5)f(kdro4T1cebfO@bQK*6GF6(@SoiQ<`o*fw*^aS*XKm~klTjraFg9dqR7O@rzQ>Tz(-XjBs58Jc!O9AkfBQ2|sQqg*`Ej<_XDxdMsM_Qy`% -?vL`(SyDhj#*L*LKo7!2vHr$LVv?Tbg9}XDX?^4F$)zljSSGz>m*~KD7Ddza3MH`u>lP^){^imHtn4m -0BlA^m+k@*7qDY@Oz{eoTM#2$N|D9b#E#NQim~H>--&$=F@XD=EU{$2TWAw-GWeUl41jwiMWr -;?xES$Id4#?@{w1(SRZ6m56#?;rSex1krXcKR{X%A)<}C80yFbyh6(q6GEJZoHI+vGEqPvXPvwkv(<%4M+`6;UXChgY=O*aQi!e9l%2JR%e2p1T_^u3` -uNYGW&ddH`eRGGPM9!H<-+-1u0lL{HY~pF(MU!skZTX95h^S9QE6c)SDuVn`LA9^Cg_!x)G(h+XBFGU -W8?#S?T8&hL|g%f1)W@g-c!g*OG~%^5!j1@FTeb9rL`scjwEB -yQ}Us5=&4M8Sx8dJFEgo-Q9({W8)5OO&q13wA9}R?%Ls+X%(1iif<@8_31T{=BUyA~5G(>RhGhpQv!q -5USYn{xFQpfOU3s|^p2&+E(r+mSR-StFh+;Etx7oE(G}Uwhb7;!xs6$xe@@n)xC`aR~_vgX*sx*WXdc -|+>#i+a*pTEBvkIMI>t8(1-l7(|U%rq1Qi>}L2rTBPD(%S-#pVP*KmI;~_>a_UbCN_=`nI*Q)52w -RonE$XnBMi*A_cIa><%WU+@c-^f84tGQ80!Vp|?*%Hyr(D15C0uyFsjJ6$)y!(yLmQ$rp9UCnCP -Ch2sA>qvPK!V^Nlm*r$|tOWaxQ2QjKS()_)$d+oXIW6EJ)Mz4I+y>y7o;A%uPSSy}@vv#n<^j-*F3SO -t;!Rl{&GVkE6IO!wF+a4M!9K|AD5pbGII345c}r9}!JE`Z;Rdw~s)z6s12P45Ugv&rz5(tv4HxI#=%= -EE>$)APp|r6(>bE5InIVbEF^#Nn5G&X$V!0+GAhTS<2CWE7KXOazCD@xy@n9RO8irYP%}pI^o1|GVX& -RK-XoW;?$#j-@C6n>imTTif`!C+_-~?bI3W_+3DtSvD;LzzPJ#}RCtR}AKuDtrfejx6c;VksC@vPL?Ym!3_4Go^4+e7?J=OiR>mi0e} -=?Kc{f5v}bkgemNdpy${AE{!8B-1=@okL8T<-uerDsk~aZk#lH@&)g|wGy!W7cK+NJWtY+jy9~&;v<} -7=JUow>!E?UwjQpmE;*v(VNFzs1El<_o01E9#brTD^67M@A*j!4XNMWE#=@I$;c8TPKQ`6^hWK86EwG}zc|~X~&r(wV7iyrJDP^$= -x=q^WC!|kaLIZLy5x-U?@9@O&z+_qr^?M)EjqzB-a9eCVEM2N>&f1Mt(^C3MYP#c}oyQPC -42zMYw@-NvhXfer?t%fybe&h@spTRKYD3lkY=)M@d>^JuM&ehnOtjck_Ev1D9Hp9G%{kkK{WI=)ZFlh -~01<3R{moX;!p(-{#atzP+WuAG)j*sb{v~;G}aPU{6%_haQ6@FCz_vqjatgj-4lm4%AmT}^{*=2 -npSX3%=t$BPRjZ1{l)#h*Rk2Cn(ie$2=PeTwCT|ByX?8t2sNA -Oq3S~JKj7seoYD=t*s+|vjYPZwwwxGMN92$Zl1m@e^RHA=Nwh{E_ERbA1=eMZ=nVtE`NT#FU9ClKCI# -k!y9*m(^6uD{Vnp8eDWN0107LszrX3bnMy!HlLobxc5%( -hU5OFcTp`EuU8CvqR-evkp?U10yES*D184z7{>Sulb70pte!J?0?GtjWIw9t3;_1`G%M*5vd88@+c|OBGLB@!t6xNYTYn`T6UCn^{%-E#{A4u62L6GzSGb=lb`8)T -UWAjO)j~JO`n@9z^_(eQ078$aUn{1)sA{;G;%-(6V0y6+>_Sc@$PE_QQ}~D0w|R04tBX`tw`2^U(Z{C -alcDv+(-z#`U=oFQ7_tsYO-{L`xL;}NbqkYs{1UuBb(7xPSO>k9mM*t=WxSFi#1{fEi;MLwE~cx7f3c~<*Ceb4Qj#Y0>QU}nM%6E9hvCOo~mf92M*CwBWLFKgg_Pw -MPX)=*rRwU7Y7(qW>-{%p$XxH8Bl{*5?m?nFEA>w&D) -{0H)2~!Xf{s|PC5e-muNYx&yhfotvfgnq~_=V-F&n@Zsc|sDZ8HII1`HndXE9OH)5w1#yl)+U8`};3#Y -fH8lAz_X`2u9q|lbci>X#N+iba^LgT5juAMauI@k<0He9-ZherPz=b!5KUo`0e8U;i9de}NTQ49g=tm -Z1M3->S=*6s%2d84cd?|K%GTFLm@;1^a;LwOx545DMlo+t>M3tLiLHI~JVBlLL6_LO^B2Q%69$*MYBk -Gg#~6oBYyBOu{ex55p%%^b;WUz>Wu$+X=@+vjO@iJ2gP!*SrK2FCd^jW4pi1STe(z>vPjB7adnt7OR3 -iq26!J%T=KZVyonMh<4V$iN7Nxm$PI?!C-KUMu9wBBzT;yd$%dAn$E3{Qi$Thm^1ndEY(;` -w>Y=enmuwXkf#OsIIQ?9Ni!(yV@pBO}Zo^ATHC@7+eay1+E^C`b)Lk8u#bX^=U)^q)cXxPj2NL$5Z5O -BkrPTxHSqejJWkT7!h<>*6(2qUag*>2ZINwa8+*-vQ4AaqXFp<)92ObSdFNp{*g#=BAI$wSy&=Y -!!KUuy!prIxoSb5!cnqhwBE!J9D-2WEq2Oy~~hXP9Ba+9p1-(_$>ge=3T0oa0EHqZgt>HCQ!l^fUuV<9l@HK3jXdY4|7Z^;VutHfv6~6r)2k1}+)Kkcaphkca_GPMMdLGQ6VP -ww46f&5~5cVp-5~h`*jfS%El$>NeFZk~882#NSt*`)v^T@h|J|DT>$1@F90n2@RMCT66S*;D}=2?>SNM4K{uEtntD>e3Vo48B+pb^!a~nX -O|*0S?0Ead^E=_CRL(9g_%zC!Zpr>ADU}amglDA3xlWTC$LUGVMBU9W64$$eH!WZBS6mr`8WBqO05UHyN_0UOw -qDcyMGIztPgSItCrb+U`8HmOnQDa(N0CUo7@F%}HrJu-WcH(g_BpG&GcwF;=rAoh?sYa&y(hoT(isB} -0jSP5i0*TGj1RG@Rx3HZQAT^q5QzT{-5%()QsmSy+BCZ|lx=0R@z@0a$T)EV-L+rlT}%NB?X`h>K{?? -QNp6m%xV_;w7eXP63~MwarKz+hb&`YybW=__?vbY$;rzN7^$C~xm|5b7bchYf5M?y@cswpK(O7$w40D -cChCaDPI-z3`r%A>_%y5vDaDB!_TFoVkftdtJti2{vRAKD_l?V|Arpf$(nAm!%vy_phFXN2QZYp!VDx -jbYi^e`wWi*beO{y9SgCQtGCp{Hx9v)CKwQcI7ne#w&@TNe0MPW^kc&!jdAR`UOEOc|3!f~hX_`Uqo9 -~hj6AWoUw!y2P_z|jfiV9I%kcBTT?Cz~6mxO0V1qp!>)_3V -R=nQI5YB!*WZ^?z&K(NsQADQBTOqcO56ly?*p`l`1M9V18pGnvxd1OySYVcEo@945O@>m5Z}#ZE@+!r -!55UzeU2aHK(X+mX7Oyh%jH-86&43`3X#V#XCFY!h|SwLtfEfA7ui;N{+{S1_utMN@mPg6%hd4|Wb --?gl&GZ@>9&_b3W>|F-)k_`g3M9mDh2uXo=Z9|Z@8!HfOvH$M;%DDq{T^#T0c+kN>3d0kZR$EtZl1J<<3dN)t+>Jap>^NRso5zyrZ?=I4cq1ldnVNBI-%c6Vd6IrM -(K#OUa&ohsF(Sy5L*uBHgkfi(uYixjV?D>OnJB4k4#NMD-f6}ESn)2!-Sn0pK!W}qw;bhrWV>fwzfP3 -v-tkcKP>#f2@72fo2z5Y2jCe#d`j{No++uu4@VXBiYxeOv}@#^i)Bf^79<;x0vLFXAbQ#Z%Os;@U_#P -oS)R9ks3QFqTF*?*^qeJoYZ}ecB4UM*RNAb^91MV!EyQ=N8o6pgy-rVp;1wlSf_8B7_M#M^xQ-aJf+W -_gVPWn{dq$p5sWYi1`rJM%iR>uRQZPm -P7+K>dWro9yHb@zY+Q!1*9$F++C~$v0yYf7u%O!@!1fYdY4^XrosEGF^2II;f>%G-83cV_y^wOA1Jce -BUk4&CU3k7JzQk>D1lyjk)`M9FwSg#T+4gffPR?V%DIsHj05#wnAv^v6P+=weu_#nM8Q6V6>?XqnoCW0~l)Rp;L!uH^D?l4^ -<0fXu^$46UuE> -79|l+4~-czAf`#N<274uF|RIaIOeTMQ8&c-Y2~^7jYuHE($9qAC+M0txV!MtKxSjQ>{Sbqtu8E=!cid -@Jb%`$H{rs6`9!$M{02cnoN)J2F6W2%C>kH8L;Zz4^!%iZ^l`6@>VW!KbeflXvSLr#-Y>biIK`tz=B4 -b)d`+7b(VTdhhY*19uk>|;?Xtbeq*VP;y~K1!j?ZNFKoX+?halnOE>mfZ{XN{Wjl4^4$1jY(Yg(7Eii -cq-CL09!f-a3jbU@SPJ)n32!LRH*y9C5p|087!T2}m1P_L=H^Y&|)JK;taeHYRSIFBUVNBEp6kq2lT2 -GDOkQ1Y{CY5r)4vBQ@z+RG``?#3fQrT*5(62d4p!9zxed3ONXLr{Nq#FoMF!2MNZSDPG%HkRc(j=?I) -vfLa(M7Dtq%|0{nxY&X{_Bs2?`h=qBI9Z|gr^$eZ@M$;t0Vb+^+V8KUk|?ia{Y&3#yp5G^r)_w#qeL|$@W`&rGw{=9xZdA*+UxZ?L5RsN0Aay;C&Eo3OE&v-RlDirZ -nW77p{6Csu657$C9hhk?OGgMUj|-h8&xf}L}<94RoY3T7q!$}xXJSvu6)~L@J3@U1=;cW08fWn$TKO4 -6F_f|$9zp)*2UXJ^i?FZ_Y65SpA}Th3bTYGOwVSO9=X_Slxo}xFrn%0&B5{RCX=yB2^BoTHTbBuUAY^ -k2paOO0!Coak4HK6BtYpYsbk_yir6t~ZPKCK_ae@gK*^=@e~B$1z3-N0S -d@n-)PY6s*7JGio02p5sJ0f|Rz`j?d1Dpv@KWKwV!I@iFr-d9l3IwscY+sDa|06@piT85s)-T@oO^|^emtUxEp4eet7;IP7-y_aO86 -6q*xJ#smN!G%sXU6r`AxUKV&XxTabS^P;Bam0VrtlN-X_9H4<9Owlcd9Ikxo#EG4V{)Odd;Gm-?>@?N -a1G3as9)U5PDf{A`$9v#OA57xdz?_Krk08$J|nL{QarKkSC14@JGZI>cles5e~LGbwylvkGgi3?Ym}D -w(jTn`rb$7xXJ5GNPnb*{Ul>JP^T17y`+!82SH1JWRSLg0 -uAH2Yn%G*Vr%d*irOiShIXl0(0>P+_)E-|7bPWG0}f65jAJb}m|~8nt}wx{IgYp7G1u^Q9ZDDfEhs_R -)D}xFRgsTy>=RX;P3t&Tju~kcJ+HbG_GEM>KILWY2yH^{sh#e4cSc51cimX6Z;vThw~@sd9W3GmBd{o -Lmf*;5qn17nj4Hy^<9^7D15n~3r0j9a#viEVJ3_eTL07vLfK(WIUcs$N; -IwlA-17r{?3yK+#pTh&6X~S7rkBs%U%tV+*41XCJbs7(^;tR$Dx)bl)4#iT)P;mDcj5OyV>)rE=lp*Q -)oGo|+Xu5vIonIzTyTp`_r6Ct7s76l0b7AvJokgIj5!h9TUX=f^NLT_eajVJV0KJlLQj8J?L@~+}_I; -2+2St}K+A_6m?Ecb8sg@+Hv?kWoi5Q71iM`%+IOht6IZe-P6NmCrnNG|yX+enY_VHyBk2tniJVp%$)| -R)_T4?-?R@U^npQ*KFT)mn(HGMv}O*MT!yJ0nbRmdIbOc;){7oo-i5pqw1UB3jBJLm`w|a -zX#en)%J5QGcY8>{IdcAkfJsWd&d=;}KucX88>bCgLD#Oqd^uV0P>s~S)h|v -xf;Qg3U7~mZjj$ss|WeJfPnX=TG@VT9uC+kT8(>#>fOCIjeDfRtgBy__V)hzSk{M{JVoFJ -V@)IV0|}xrZC6GrP^-Ye44M|AH>}jyJG6%hF^vX@conl^v+f3BBNmW}qkyAb!jKPfNH+KW_?@`zQsQ~ -b+CS@vI!$qf3Et-LdKn308vnO&bXZ-ll#PVkUIA@3BX2wu6U?oM-xvL#@13v9;`Hr_vDL&Au!z@8EMj<>yZtz4}Rmo&Z8g^JaOy ->?T5nfMA_y!;voxfHgE9u*z>axghtXT8M#z|M{=d)ZK8R0NoQ7Rp(sL#&!vbwu7A!48zihUpg9xr0}y -_(!&nb@3Xi^*+(HYLj#=s>`H3#9R3a&7NJu6SfN1@9kN)>e-ea_(9&Fv!@xCkM(ukXdvx1 -OE*7Ao1@uef{Qm1+O5zXYGdu@k?Rscq&v0!RO?*p$nrRdYgiT6qr(8{s@0d@BHY}CC3O!>QO!=aSC -dO*hOL!5B@q2g&~tu -MCRqbccx69`yw`Aq7E7Ad7WpQUBV-*Qy-gThZZSqGd;7u8q&JF7CM|evkF=Io85|%Q|RY1K#7r;H$X?92BFZNJf~9(!U)DYI&8O=M97+kz)}HG?f18L-R+|he9!$6B^z2m -0N|D@N;i^lE7D_Db;72u6)HXECifuxY6J_p|5#U<{nS;DJB32#f6Kvrwd6|gYuFsem;V9Rkiz(uc~$r -61Lghza_0Y{1HDu41@EO(gc~|c1OuCvm`t@75(|&OxB@95&l58U5xULjL9$3s?y?86H)V7mczov{)eu -a2Rh>o(8$c30~O|mJE{5L>Q7Qwume!;kEFjzBaEJ`Dz3WX^XfT=rs^L&>lH3LcaMwn+q -%a(Oa$%cD_}`=ps~l?*R0q;x*kN16K5KMqOaNxdBL381CzEAO4Cdv#@8o -a{8crMMOg>kBd46>$HKDX-5IOqznHwl^jGA1x&m|2L_i2a^5yExU1nRFwQU1p=E<~DEC#N#bk4yQbvQM{X@n^lIB1mH -+4yJjb(O_pH~Sf4kzfhf$?U?$%o*gh%qi^*`ZCxu%A&*9=uvcOe2;G3~4fnM`G7WKEUo?8PS}dK#g=1 -R~$oPgeE_))!@;HC#OK@Cd9KG7(G?f$%(R&LXl!ErG+Q+JCFmQ1!M)s+M^q|69|tCuM?QfyAC{}v|7r -_Q8D{zkzc2y#7(D2mZS`gW&~rdC?5(U0&ezOgiBFLQfa44ZWWmWkAr*;B`5-q7PC_GLU7g{Afg5qcED -jC!t--HEnMS_3Ma*pg**#;$CSu$nj|CGuF*J){ObVkB8?)T0SQKgjd7hE_^}D^5=E5~5ZC2?pd-vH8F ->Iem$*wvOerI-QuUN%NfY2apNSL-(ur{{ssZHVoH}w%Za>BpoF`;Y9bs)t{V#?SOwbLDgR7F$Hy#RwV -CT4v_iU!n!MH1X=S~j`1$c%bc6M&3<$RcvFKG+c9UAt)wu)o9$XnDc-31fY&aKtlopd$Aa_sd(kbCI% -C;LLsMmZ^FMmb^HTC~DbmPl(@#O0-!lCz{#&gCpExXn|n56vJ>b}> -Y7fy`Hm`zPPqQ$Q0Yz%gX78do{yOBMB!)Qc)_j^eNHPO{ozpip+eJ}W03E~c*vlt~TBlsR0@`5rq6Gq -^*;|AYkjmL{aOzhGollTo*)ye7t_+3_WH9HpPM0Vs%nadaFUc;{APPCKiIM#utkTpyVW$ -?OiH3~g_DT6VJ$tn#~#F%Wi_+_C%?agmPGVOoikZpl$3V5>%FN#qlG-Ct63AwYeO>R3hwFZUwH;Ibk` -6Q{)?za-ni#ze0RUmkT!_SiIYADbg4mR#SpXfxxyhagKCIey7oZv>JOMV+jSW247Zn|V1(QXqU`D0}= -#n_%4JYe_?>!8A{PU6bEqGe5zkVdObNGwnBWaP|Gcm^E`1ho$L`ssKK-em=jmJgJ3O4{va$x0((@f;0 --PTJGT5p(%0`QtQ|#waL^l&8FH&X{DYWvwJ{*K$A$4(s7saA+YlycYR7Fm#! -x%P&l&u`ZA4ZX?24`Qdoxtaf@KR%LGGSCTg>4^UUV%l&oOX(Fi@al=`i+CCRvzR(wcnzg*F{J -@L0;c2UM+biK61TyLqbJ(Epo{@`j_Od~8y&tl#Juh)%Z#|VZx6sj_$5NMi5dx|bK++S>D -^I=%C-LmPV9%0&Z-MXR`3u=zgON9d@b(K4k=nsW5Ky_}!>i3dTax2M?H*O9FLD@jCGyc9HP>q15tM1C -NP$+?eH_0iJ_=Aj>GkCrI^I-4IF~|yloN+u|EV~@xzEERyv<$B1{BGg1MPXI~Z&@X%_BBwBjnF}ow>p2*7ZMZ^;ol3p!@gWkhGK30PfqrutGMVPMy#G274;g}LjL -uQq*XHm4=RyiKMKksim9hal`@2}3Ee)$zVxq5&8=RbWjzItDtkG}cypITO3k@S`zI9XLFtq_q5VJ=F@ -r^L*K-02w$K)~=B9%B>jp#6wv;BnCYNBdC-;JWm{qd2Ws;hvyZY<-`tMXj{4vjR@63je@Q9fVhV`j3C -qOUsXF(EHAXu%r<8cuArAfm9zx!lGmF$8PQ)w{3sizRh{)l%tp3c{aPDD3t-J#CP*?Nl!!(Zjrlq^5wls0KE(68ZR|9$v=RehB`0}`Z57 -K;}BNA2aqu&BTX|@-TXl+c7f}c6YH_06j+YNtez?75Bfw8Hl3jQUQ -Meq;@-8ig(LgN!YlU1Tyh6dOM47}d!8U8wD$MG`Uh}9C(lWr^9f^J-NSPKoF@Y_~H7e4W;2I>GO=7Jg -Hq)HZxMS;B&$@fm!0g;Q-G`=CG!d!gFtrGARiT`0!y~0lCAz_VGXtl|j#3mCL#Kq{Bw20Z;*d33u}H5 -dMRyWWSrLb#J&AoaIzNqMLptZe{Vi)|xtsR^P2hiF}-P&tx?K|1p5x4eY&I;xuXbr0T9b5A*G -+gCd=rk=KVA_LSOHO!zw1k-$P_ihlRn$U?@mBI;OfydLFw0QKj1(mir;5uRg_L2r0UgCIMEhs&{I_2^~G6GSb4MdC>MTC)|8rklq -CUmu(_*zhXlYSFM`x!}ohWI5Q6*#8WaRQ29SnzDBcqmF|`C+(o1(VQ*)NMR38P{Q%%-fXpc@AEX{C}1 -eDM}zY*^0q95dZr1@AKP;Yu!fiS&@MFwjms#a6(%uUv~iboS93m>>hzc{ljJ@lHf++^=!I*h7v{S(_p -a@8DUt{o%QQ)=ZdDJ+SY?`4lqq-=P^>c2Gzk3*Y|=B#(>LVAd${>V(8hS2-*EobUy~vaq-F=7nnmp(9 -?;N$8nAZ99>GW3WAU4Stg!P*a6OAYT9^g|<(MqkD=0&Q`n{jTBlGunIhr+q0skUo{EHCjc|+2yCig7M=kFw_R^+*TZVEG!U>Y%=08hl(pA?QsG -&ynL^dGNrsXj%%)H&?rF{f=C*hf7kCd>_V7#C+KDrftosZ#CNl6p+5%wU%7>-LlLBK-Q4mOKuffOth! -V>D#H~fVdr{)ztqq%L2@$}`s3)FnQ09& -qIFfMpzt_vom|TSDx*zn(e60AH`iOMKi+akblhZzjkfIkKTad4;%sZ@hs$D?X~G&eYJQ0SndC|w7^5| -)>ivuHkQ+Uzs3H@SnM&L)hTDU&Rqrto?1E04D|!Oe;gQp&`N$+d!Le*10`3RBr-Q!%F2K^*yzge))Hb -hn_>#65>6{)w&vZry`7|41Y#+09tD7nSBvCghQ$nTSG?*#7+RU2Iv|8ofQV?l!}C>Y-jsI5cpimWM4r -uesH)J|WI141*mKy(eX83U3NlL|=xt -%h)5w7Fw#zxn%=)D^hi1DSM}f$P!&yNVxn)lnS>u_{$crN)FAKx1n<6#r;++lvmHm^ZYXzpu -Ob}L^!}wrn3H}9#UO4y5A9#kQD*7ntD&P4%E!YPB9ZJC@)M=wTos-Rt+PRSmqR(NYD~E!)E;h51zN1t -3^bUP5am=LIkhPlIFgvmZr|5;T95k_q{wM$rw1)W50-DZA><89-S7F3#dZgNWytsSbskD$)d)YtrP!E -2VQBZv*jWXRWv;GSDL0;b&M}Y(&40TUiwb&TuLEGAt+Z6U?x;F(K;-16VhX|kbdTgrh=HcKwpFq2#@Tb86`&cq6Bl?kS8;4NBsZjx8s89IL?6 -h8tj*M6DBW8~)SMrf3_8#f0E%fq6a+S>bwfv;m{fLpA0S~iA5qbW}y8qCow6SB}%q|XB!#mwhRR>rB -`mBWrF`$&;Mpq&qMx?+ImjO8~|oq^JG!GHELF -RI~;?a9c{F{uu9NO&}M6yYq;Xl8pnmL?-?{o}E5*h7snHcQH0HgIA?K{U-uMGS@ZqxK!;n~aBKFi4tD -w~;&Pu&RKIbgVJr3GJfq{IPKCKID_Ms!+Yk!68c{l*2yf=e=9%H~K(DjZB(u128wjD6V41lA~v1G;Cq -NS{-THfoV=3x4Nx*xiTM?$uP^EM4}DNG{Q9jnQ($8j)8ERlM)s4CL2Dze4gq4C~Ysyiso#El}x&Mo= -F+?8y$8sivsVX% -VKCi3B$SzRdyH-i`jVbZ(kVRFHV&~vg!@+jwc2(C}okL3Pf?@%!M(Bh|Ah`jo -1d>@oLCc=8>D5m2bc=kIyfm0%i^vmEbk6kj$S1@ty@C+A&Gv-V)dQ4siLu+5e%_S2c4j%ysq)ymqwx+ -xs1q9gr7w+9xz? -hT$^tb#Kz|@dqLzVax3uJJ)PkR$Ixw0ERDjlg3tW39{J4h)IzMIxn((}JGjh7#Q#0Y@GtfL48@A(_wc -@j3(BF6(e3<_l{P~-8qo?}ZR7RwZQ>o1=FN}YU$HhmpPs#ed9V&y&6!uvyL7vA}lrS_;`Z49ptIa3X${bM^u4EQPZdEi>UYNd?tS -%qqFDM$!U|IvV-j^#fM=sOa#e6@nIaZA%{|>ZVJ*q$J|tjkzR+Oq)yD-noQ}A^u)Rx;Lt=M7b#H-zN6 -U&{>>=C@MnnGECL0y?iC>;3z98Z@qT0V*a{&Sghm>ISb*RxUYd(kI?6H?Kus&0B2dvEo1Gnvf%aq56Og5VwRmu5~>GXQIn`E>TezUxw+3-`ba&&68r{ST<=@{j@GiWHQ670Q*O7czX) -o>}QmmgpGLmFS8WsFyXWt#MuY}h-Vk5dc>Pmd@J>u!FRc+N%1}seVj_OF=^w{VUc4E@}nqcXVwnG=U< -FrKN1%UV~lhq;(QnWo2$3JR4ZL$)c7OkZpvQM<%hxkvBxo^&4eMbW#?$2TQp44g7OoB#*J4xW|)dwp} -8$)9Ho|q?LYGR59Z`9WFE1tw|K8tR8&ZqU|Jj^cKD{73EWgd;UjzKPUu4~YuCFnGAWA8Gy#XAcxl -*nn89V6BZN-iBDR$Ak&_Jejrq2FK5CR6c_PVwT#O+3Qvdmy?H+=XE&mI(|Pb)rbwBgj|;82=c~BRJzp -Y|s-Hi8L^sT8Ir73bP^XBZdI}Pg;L;%!tc*a1+nX2wH$06>-iCh@qAy-VM__BkGd84gY?KZ;S(FNsEUzV`0~C$OAzB{bx7=Myv7|L{ssk -X(@SUmIJ4vw(co^X;m4&M0X)uEY$#*D{Q5Q4l9$&@cOnmEf6t-w0`iO5X3B0I*psg7)7LT`gtRrK52s -H@afmNe?W&tvnoDE1sM3HF60h|lF0iIE^9*_zebe8~7D!R35Vmi`Wk{6kHZL~-V8 -_K#wEebV-kz^~xvjD9~?A^|-IUG81(`Fi}z%X(~#08zPc5p(7Um#})EY{F21q$n`NdNA@zHU*YnqF-K)3M?fIvp%5%=w=d1Lz*Z;ED|LPkb(wyy<7Rtxt(W>O?=9kc@JFpXYaJ -uF|SV*olQa$PVVBCXrePnw=4XHqDL1=__Iz|_hlYQ(y|?er38R(lPhB1Rm -mPQP_oC!F6))`5ZIQr{Teu=OAvgFfFpK>b*rax+N3&kLF{q8EaXB?ErTZh*cLC&`dcDoyo@7XC%59Nl -U3h{);sIjMPAewQR6WuY`beDM3f;BaC4Uw*e=?=#`Jz>@KrvuG%7(@=-46=fg+beDO41vQ*qwu74$ -3YaOgD}mX#0KZcoUX3tE)Y=vE+NaLGCvBuGP2?z3V9D@oXTDn%+!UL_71fWxS1;bb=qyGho6@^f{jt; -Cgf2ag!416UHPdE)EJG7I9;qGXn4^_+=~5Ix03aN72}tj%F(mGu%#+^G-a4%+bJ&76R6F`WYi5(x`zA -;N#^<503h?J!?0J-mxCNGvg^9-aq6Jd$k5bBV|MR!_M6Ca1&P?VY83&&=NEaRuylD`J1vLK -r-@KwzQTe!548{)S~Y-B9g#=`>36*~utAqR)1=24xEhjg8|5*X3=qIr)~Pz(eus0>z`rz>`#1qRkQwM -Qa7LvT?+_Rb4|O`zWaUY0oIw02a+@Z;$z7~?_jR$m`}5B3+vB~1H@dS>@iX^U>B{8?Q=}PZTdxbaB>& -|T*CRwg?hjXs}swofIEC -R?*aoqZOq;*LbZWNl{Enyz6iMn9*QJXGViPE2}~3wwo{86IDl*_nSrr?$}%{-G&Q3Q4{ ->Ip!HbTkx22ezKuWY}^Z*7T@$wU7I6YXrGPaOE>iF4R{RnPPM(;&1LrTA=NrIsEWX7I*P*32;@pQj00(TZjFcuq=H**=Zt({ONrB3A`| -|0swG%PuqniI8FC;OHB@2lOW6K2PDX_1yhZ*G-+?i8NiXcPF6TKkpHq7|JVwcv-aiL#fhB5538Y#HQCg~Qf7Osj#9 -3XJrF#+VMa+uQH**LfrT;~BHP+{IK3uI)h$4}iu2K)Myy|&av*eEOZ9>JnMZSUa-`GT!RDq -tU`rV<(+f|tS>b(F?c0-((Mr5Fx&SyyspMP-=zDW6R7enw?)mmK|0U>c(t%KfF4p0_%}Jyn~dN^Q!{Z -96@0Q}|5!x}A*t(tJMc7HjZY5#C_dlk% -bD;r)Hr7l_3hKKUSmheoNxB=N;Dnm!gUo@$V+8c;yh!*6}YDK&&E?$k2sWd^PDuKJ(0zeB~hpnOdls) -bx*9S@xnc3ztb1>Ibgf@5hQ3)*xz{=g<&6C~;{`1_(}N*Ogdep`=q2OXw3)E>tJ|Z`X!wNGm6`c6GCs -N2A8if_5<+qzk>WG(Zy($GCM!iq`s -0B+N-vvED~)GGcH-C;QhDFIb11`APMOd{9W_5HLk_0$*GLe>j3441v`ji(p}IRoDR`i91RQdFpSH$(v -;Hh`r5mr=pZ@XcNR)-B|j-tI17Eogs~hO!X$^gW_lw0*G>6ASmWBF$&%s9M{TnBmulJ7Ve>S`As`ex? -^wcynfm0I3n3_G78{%urnF;*IUYo93G(yx$#Hu^7JL#$+N1!sP3FEgZn|+XOD>1`7~XPGm@Gb+Oy9Yu -1avQ=S--Hq%>BJi<6wTtEAP&WG;HLK$?;&Rt$aIk|Kmz`JR%*=h;E;zd_QgdmXX8gZx--W-n8Dd=P(~ -WhzSrz&T10T1smP@3A7(Ddpo<#K$ZJr8T|RQm7q4n%73d(W42Pah72+`b-4)y9jCj0s-y6LCYj*A05X -Q-5*`YMJht6Wkv=z<=o0SFI(h*)>2wTtuP`m(%yyxlwo3XpjS$mnwCmsZv>kOc5ZdY?t+S3Xs52Rd6! -Z$LWw~sg5GqMIT1yQ5!<0z`on9{B8ENd!s@w|cE$a>jwi*$L=|U+2OkrM`7EngMW!c%H*>?3S7eJ9x3 -3r>i_Pw7SC)3Z=C)17SX9dm{-f2lq-4V<$hXx_Z(_t6pf)2WM}fR#&q|sJvvq+&Vef2=`X-UNY;_YLi -(I4`QbCskgo)oas97nI#Tq~DM`MpoBahCo93c!^c!9BXDZ38IXr1w&)cU_aNE{nXm185C*PdH)3(GCK5ARdwomVtHT<*Nu%E@ve@c1^2@vT1JoSsuvG*vY#&Vv}{&kG6Q%P3 -fx2E3cIu{RGSXSaLaukX!j)T`aso6tSFJGrbE7c{y=C5_)pMbd|bMX?V@Cds0d8`gRgdD^XAqAt~WHK -@)$gG@rI2UL0HLoKIZ<+0L)Dv?4JfYFdjK-7Lr#cD%$a+`3SXIl!qEmAmBScBGBn0sPdMo-K+c8n8U5 -~$CQZPJ8-s?%On4kjojC3G2q;Rt?Brkk37<@s5?YG?01D1AI -PM7_*e(5>ux(&wr+(wYUIsA~(g7wyK`N#9)&e-Fi@6iuQ<6Jw-xk4nc+GRsL9-W<82&k{k%S2|(%^fI(tgA4Cj>!$3u$XYfMUZqq$w=UYqvMukcVD*kX6aIj2joNroDl3a()FNp~$S -+0b3r;|uJfJ0);Jv982CVB!u75rF4yG={&)O**jx0vWRI{%u*r*#!sl5p7z=m?CJDMFZ+wW|b89aRUgIkq9M -4?PwntIA+i8G*-`wfoNnA?t951QP%6xnhLTcDnwd>rh75 -QnV;ak2mAIDI^TP)bjEw}7n2WFo?2v1mPmi5g0DSw-ea3EpVA$?bz~=%kfX3N_FgGM}X)`WpJs}qH6lbLZ<+ZZj-+21vS6~0>&)>AjNrs*>4}q6mcH&qTh))DYBFDv$1C!K;}vm!$1%RbR7v1Gd1V@ -}q$n~n?<4Jhp$I`BEnZb~xg7)eOFuffXWSR)Zp>jCe-qB%{CR7dTDTdkni`e`=g{0bITEH>O`NZ;bWAvUdj{XrpC>{M}G -Y+C#|S7LIi59)k__QnaLgFDNQnQ@@wSwU*`g -Fzg$xNG*cM2ohNSh=>0lek=(?gHakCV?(h3&ETeVK78Jj=B7x(Cn-S^-Q-iRvkhh=zHWe2afe;(|;Ib -O5Z3d#pdTA@Oo_p;;s-m5z@Lmyi2?`^}hOjn~=37XZ(@77q`@SkOD -BkaYyMxBKH35%r19riW6B<8mIEE=D%QPIyYA>d+ -ew%}{prE{c3M4Q>4{^wPfC4o>R~&b>zd#NPXq*Q8wBGPH|Jwb@AAL$#}$)ZVP{Huj}@A>h=Z_!a5qb% -PubkhI~l5@mI>j0=TiHmu8ZYJ}=(by?&$+E3ZtmXP^Eov!HpGjrE;!Zx^oEZ0X#)RuIEJ?EjQk=|!8t -F_p`TF{_^#`JvMZj>IPb)wlYog&dz;@OHl^(gB7-8K7bm-xK&ZZ1WSQhbHr(T(wG>n9q9Pzy-gFfiJ@sA -Zm_vsx{-@3GVHrIXcZAlEJ^OX-y}kRoyIZrA;r4) -y}0!l^VDUUvvV$z|9BF5^@FS7Jkov9$rP(xS+Z|Bt|hTTs@&N6Oq&oDJb$jJwthKj9NXvZrl#vk>J70 -o-h;a}c9qcj2-l>V+0i70;}U12a4mX(yLkeXs}Y-S^_jv6iVFf>y(!}KDI@S=6{xO@7SccaJedhbS$3 -{?zymD%OA4f}&_|VBoXu|B6*Je>Ggs3%$>F}fU5~! -J3wK`sw)670mw(xTpHJ^P;mU?rAw0HDw!8 -mzD%FWTeUgX^!3l9n>#zl_9CLtEVfsaB&ZxM&8=^{gqz%-5Je&i;=tbu)ILEYv9B8DQmc8I8VUt`zv{ -?L3&@IRIMK#TI9A_$%D3)dopOyKGql#cRdRClzV)}&ax*p49P4v`9DV9HwNby_suG -COMVpV|@cjXbVq)A$_wD=2vbd0?TP+c&!t(|Me-vQpMA~aH@Y0GceriJAM-9H*PyUM@;AyZA+HQj9kc -2W)nWNpcSffGoL$Mw<9eLlqtU*tZ(q)R~Cr5UOkr^&GGC8&f|>@bSl@mO@FOhEXf*dMA5GJO`|#6=+< -BZjNKM49*KKlj@u2uTfMnQ#7)#@qyPM9dXV8zUDD72FJy)}E$;%?mC)jLxDGWK<%g?$i!y^51#9-hyZ3$t -(IJBumpLTIGCvgojy5f*MPN8Pn+J0aV{ufcIW@K_oeM^+(^RT{VO`uI&4}^T9oY^ilnS8+Zn&B*j`)N -IVgosi)=|9^BOj3S+O*~{jCE>0gWapTh1}ha3(U@4HOCmpin5(VX#ri=xU2D^Uc&vG{;j{`G#2PW?wT -4eTDBXZIvg(8D$lcg}F#Aab^2z(VnF)F3sYuduVjHT~Hu!N{C)A5V5@~@@I)Qf~!1fqd#8MU;fHqgQU}HH#WMW-Yq~yv=#XT -heaf%H3?Ia)rBA4w`?2BD|>MAe>Qt{OREE`N8uSPbqeK>&eT*y@P{A$Ixm0ZV`XK)%p2uakn_G2QPW! -$h)ex!x}n(FS@Fg!vRAbtRL%v@uO5%09M860;OJWEYEYP>9w&UVU9*e(|?%E4viDky8-XYjLaLMW=N!XxzJ7xU -HATDK?sL{Hl*rSgxe3Tvbu|kxEJj)6zUySxf0@DF0p6Yzv|nSpfh!-2#0aayci>@9(3RW5wG7bCiJm; -9Wjb4wxJr8&1_}#MhKj3$sp1^xA5M#G&KTC9+U*1DvGt#)Pp^nGpgz?9CKiZ1$pX%L%ayd|`(c)64TG)mVr(Ej*hU_yCd=vg=ItEZ^#>wtQ7WXr2~$bXHdk~LK?O60j72`^}Hxi{kMZ685I4u -znmsTdoz0QF!}E<){@QkgIcBHv5u9{k&x@*H?E4`pf>&k0P4$ChR{Fd{u6Zf2}Fr9U0xE2qIjE6?DiT -KnqiA6a)aekf;S)!oDmZeg3ITU=D -cs%9rYY>_=*!Q}{M-Q6V8N`#Dx@bOCLO)6ynA8&^6r`hBON(2c@sW_XP@D;R?{dsKmWfyj -{LYP_UyF{8W=BWUqoYyn8d};L6k`<{&xG(`0V`3`QR0Y4HV1)?$aqf-GA*qJ*B75oU+$txrBN8MX>`UngM}EwF= -Rs>|~5xS|tk)6@-3Yc-JBzbz6aj^|wH;k{2+QCHk@KB7K3qMx5ZW|KhiUEPa>Rb}9mR#kyfC*)qe`0U -qsQB3dhNq|35%trkqyLi~4T&gWE95F;VgC)-3AZEL}z -B;O1R0<(Is~)h9t-xd_-!8Xo+Z|cBuW1=X5Pm3)-H{vI`y_j=^WB)YQmGqR1;F3RbPFmn@R`xzh8R43 -i_++<_G>UYtLwK#VwBDS*+;1OcY#x*U%8Bsy -Q(404&#SV=4dEtH-F8p}Z?|t964Z|g;1b>!N+_v%w1npcgg4$sF{uQ=xRvCof;C2#H5&g7 -?=17UBqyB9y3uP3%!h*QzPyvF=RH1=`g_{gery%+3U!EU6XO} -A}FiLmjbS^CIB_a?$oaM003Mz_El$JS~3a8{*&bAuK= -BTIYjiP2X0x+=Y8)vz&@_d-2MYayi7kb0hJ+&$#cY7ia!b@ZD>YhNH-br;K5cS(9MhR1x6AZyX=*HIb -k!{Bwx`AR!u9-na}8C0ceU!DmF1uc@Ma8$VwbKgUs50plW0ZVn8Fh3Wqp-Cb|fMk`*-ML%o1VJd$BrD -_`!%sn~fF*NzI0Eyqsl9!FhCY4d|Sw@A5Gv_$Ws@C~OvR4jap)FaaVlx~zC{t+Q+uCLU|K(*#k(oju8 -vQ(J*?O3^p9QV?Ho4pw?kO$zkeHJuN}Q(T2nOK=#4&-|`jw=eL%d#1XlSUSfE4X)nX;^}_IKoyY2>%@ -3k;=#e&E4SKj#kp}%Zo;>htTQ9923MGqp!pBA!jJ@k^#zYBR~L76)pg}a^0&F7mhW$?uBTOOAA)Pn^z -GJG+%jF27U1En3JoR0J}q9QAF)RLJea@XDpku=!PE0Mb`D~G%B&M;3~wEZ`3cvC!Eq{Z5cl4Ac%n?Of -Pk==XU)lUjG3bB6b9)O*g>&e6^d&M15&5&GKyWVW=~lv^LpaXT@An;Yinq$8|t2ABTUUPo*rV<7jGb_ -5Y8U7VWJCqsP -RfELle23<~7y4vam2^Ze`3`~KB5G1(2z)0OW+5usi>kp-{;vSlh-i^^>AW(JegnLPCjyf_kH -G8)k<7nlb%MPN-2h4NYFm-=y18==`x!_hjNLfD0R}QAfA?SS)y_rv_=RQ}!oGS#ui8Q?E-iG{iAsIC>vxp;fit{pObKcMD@Er3g^IzfFV%B~!5xCW|$s(&T{ -$Svo}Nwn`rpri7^1xO0=m7p}NQTWUH1&NIr$V0~}YuNPXOMBxMXkWbn_X>x)=8R5S^1-Z}Q4A&xqklx8^>ttShV=oVxb;on-?Dyy@R|1|vd;Cn)%?A~CY^f5c7BNkukFJ!kabOP%_E%AHDxr>})Fe9SEOaDHE6KA&5Id(S2A$lJ=C16O -`$>H)wa+YZnAo?aW=UMOV>#R4Yl<0BPk^8v8ngl)~z)fhyVxp{IrN~7>WXXJjD9A@~++4iYyyN?8OqM -j|bk9m9w*SGaa;=PYZ6-Jy-*!Ch!&D_uwn9Nc3QsV~mvSFM9h@*=R(ift3_}it%X+o?dL4xH2_L&{4H -D}YRcnfBiOSneO0`1Ewivn$brx^Q>`LM8yF!I>OPQA>zt917M`XMg36`Xryefxd(NR$2THdcU5(6f#; -a7NK-lu2q^H|CQ6wfP5iyTCPiz&L>U})DZzZs_mUPQZY%Syd@{O$=BSjzR-^0V=v7^2NsLO4%zqqMzbhC*IC7P -=t6rv4bt}5B7pfI*D_X!Boh6iZE-fOMTo@&hGS+vwF(e5^4S*w>(ig&V2J|m5-x)~V{YwdMnl-huP~< -f%5J%GFkLOcdYNnu!awxJ+sKIv$#2A%~;!O8IZf7zPgVY~o;LMfI+?ve?qz( -j+NwX6Q1Mk612FVHMNS1iK}YPbyX5Or44qM`Uwf(1_Y>f}ijQ~|82Q>oyG!)Yu6^EegSZ<-4g9_SOmx -Qm=W!z}=$)(Vl%G#f;-)y2qb-OT9QCs}1TtpK;6bHP@m7GK^2;FcfkD0FD(zCH`6ATu<_aoN51p+v+`(j9`EI9EC2{|CY`C{N}`05uxd;1|DULn`hZ8ITBN -*&_^gx#Woi_XYS*>ZA}PEqN%U1RHpd7?k9^z)@myP$wXrKCXWL{F7h|cA=6m6U??P?93Y!o^rTiU?M` -vi8tQLVzA~A=pb2a((9|X-47}unjfx5^%fc`+ed!*00!k+F -W1_e526RH_QUp95v6;r4owOr>&HpdsdI7u4s})uB)aky<#NSm*v94#of0CJnO)xq16!D88GL<$v-KmogLQ2JU;oM3^g#pZ5?AYp;PBMj7 -AP>ji{V266LNc>QK)z)DBrnpsE9k}93rbU&qq!Q{(wYN}Mu34jHq-|w}V-?~>&7lO(8Nj=zmxdj}gW` -NVMIVO}FjY%$?P$@z2`jzfUgxV)E-t!i0ia_Lg?0w%@T8wc@3*4ndkhZgl*ZZIO+*Ns!2MCBAg=bJPn -N!y&v2@->MU2(S$U>2)mEDAwx~Pf-BA&sM6gYNn1me98dM=pyTS*F=CZ;9Cc26pPHi+c_tohD<8P(2{ -9Q)MHJSxAW0xv-o%iGEA7Azb8EBE{`$rT5f4)6IPD$|u=5y{Z?=y;q-8?gbn{D9s07M9p1S+~t2tGOyIL&1bA}hWUj -iWAB{928~v^W2^NAIvr4{-?U?oGYnx^>e*)$Y{A5(BHu90yYxJQu+kcY9aAy+B?5>V}gXt+|VCqbon`L3BFuK8tjb+6cz?1$IH{W_WJ3Q=;iCZ7e73W{ -{Hmf;N@SVA6`Fw`XYLK@YB=i)laWq9Xy5ad;9+rkxP2?`1Q-bz7T12i}Q4nbyb>J1@q1S{MEdn=#s{`jP{o0}cRi^ovp9+AJaWOJg@CViSfg+z -9MD^O4B(qiBe#IKmD2yjF=1CM?YK>8N*14)?tSAj76pu(mJd{T3E5lY+&(=Up7Q1wVgFs>?77;T -&gGlvO<&SPRqNufO^QfMEP5!qBudD`|9H3KLDIv_zWb*wp`;27`+w+c)BF9lh@v~g4|aT@ZmA%YBtX& -|?z^U)!&=_1CE9uA|T0glu}Vp^+dNCO$QH#U-}y}1#prRE*cJbJWYtHX@u+K)DpZ``U}hNnJN+}u#$% -=D$In;XePx2BJJg(CjuJ?hlC4#pGVYWR(7;y0>{U#Nba&N>?#_#nYQ07_;UYdUR;$J~0LuIN=0HGELO -d33qupxbo2c;}Aa^_vch>_69~@WxmjzdYLT$??-uml}_3hrX$z#ZONIr0^>Ggm_ebRh -cLPJcu$jrOQop_Nu^E#sERNbM?CUwA)PlIylvuV5eFianU93YTa(^K`Xa6* -3iJ6SmSR!ZeSZHNzw0JI|&w|IIrA;+8s0-_+W{Wj;bpd`I<-iNMYZ{Tbc_T}--cQmK>GWQ^0tkrZhabw~-G|5dLwH5cG~L-x2q9YBr#8z>H_I^o_dsk8C<-GfKac7P#K0Z%jUIp&112L -ahQ=qFJnoCQuhGEpBRkC(x~O`O-3Qt;%M@$$<{PCLe+r-j8X^jd+U1ygvV^DS8BU%mlV*+Py2fTbIE&Vo(djg$1n16OF+JDAsN1~31)HI3_}*B(Uh#4*+ytY -okIEtYv7jCSKXznvb;+<=XW5LH74oUD;s)7*q6`i}_3fN0(Bj;qAuJhF0T4!VAv}0r0Zu8lm@o|;S4N -Lsz^=@jh{6$8feZF;n6twN&=KZA{@_M?I>Q!txFugqJENIS-9T%5B`+V9iJWj@l$Q8 -c3|aG#5M=;jKi+RNx1FTTB0AxeQ1TE^?>M{vXK26qd^XwCQ@OFf4$r41Qt*yi0)7*Ni6orlxtcpYMzP -23v=)25#O3Xt^Y%K>|v=d+j(V_PlZAylv9j!ZTvO%WxKKem$U%l{~;+{hK&=F`zRP3Rj(uTVNGUFG^S -1+cJ!cG$6cYC8Q%26 -!R|fhhd?+xNY`DY7?p#9l6>uM#{ZEN*><04g@q?g|9?)X*wRBdaI@`k4xv`1mqJ4o`YM -7?0nk=ULjfS*ldz6TbXq(ZJe!v?7qLZ3`;3(Ks{dLrqEz6^)l9YaRDd1SIFw8hV$`IZz5gdzVe~(_C9 -9l8Y#3=E(Xbbd0BgDybq(5**#Ck8+d_F4D_F(GaReqxoRKazB|aF0eJkYfKBu*mYo-#`Gi`m}oEgj7= -v+_{ylhW$)5pO{86n3aBrHmCv~;z~FX7!F4&X0Lzxe6RUP*=L=x2y>2#QDi6P{qq~8NDQm}*m!lo3L- -8J7*)Sh*J*v~eR0!qfi-l$|@Ung`y%z-hU<({%r?VPVWdp)$XCDG5ZVbsrkf7QtLwbh5@sS0;su^K<| -FcvN-Lur#=en -(fA$Br<8L#i*$Z&mzQ#wCFU;mf7O@Ux_sn(W;_?q^h~D -XNqR;hdD*jfN*UE{#pjot3Aa61G3K&s7ueQ@ -I$DIsPR9j1z4@+00E!K9}!#?GB)F(kG -EwhJBDvtqzFL-Z)}kTi*#K_sxXZ+7_sC8!Dpkh?+naGz6PyU}0;6|igYN_|TuiXb;5Av}=7@W2#R`l# -P$n{jH!dHo5G$RwfQvl08U|=7#xbD0Bl40J=zqVSt$CO6U>M -%#ke-P&M`WN(y>5OQulvRoZT`;;B&j)=pdIb?s>WX3@0xcmWqdquJQ9U@9vK^Fj^L`% -BrE85XblwEUO{es8%(2+fKytleYLEIiK8XZ^AhSoDhETYl9C~hZ>Mfe& -n^v9H|tHtV8tF@YBZSCC_vtVsTB3Td2zh9yH=Vr!E8~;|^I4bgCj_GA!y&rrqYw3EW^Zeo?jj&@3oMu -d2{%0lCm9yABF%RxS&AmWfoy2TQRZWz1`74Voe~JTir)r@eue{K^FXn>*=lC9<{F3!%&WZVCJRT_D!* -YTNdl%gW;d_MZks^oaY9*S5!5|y$YHQFJXR=&TgoO%`Z7urg4cRKCnBJnF!GUc=XVK_Uv!9>kv*_1+0;h$+ys(WtRImOyPY0+Q -wZ<$^IOOJ!+$HVJb{>(CpogK}bR7S6p@$723;@7tSEn^t^&rbAtp4%*6?MTW(%yXV@R9Sxg8;SGA5tK -GuU>nj-Q3vNXfLXqkLU^#fjDoz{pRbh9yOgufP2N&!;)UOJ)lqq4;5>%ovM48m0M~wv22>>5zljGdhR}E*4Rc}Ii=MAmwa`-fDX7BAu6()2GbZ)|IplsDdFB -M|{M=Nnh9E7H6bOS8 ->nXtazT9VEC*r%l)|=4oyVU|q*zU!c=I-&9FAAmo@rkv5gb)(6WX8g?hM_^VYgF35{MnmLDnA79;wac --7QjXkJY>QES;WZGuJd=`c_}4i4?(fn&&;5P}8RNO<0Y+!5lVN=VU=^4~`ZZsfpX0*iPr!pd -zCk#4Ja7)}_G#V*GU^iwZG^BWGxY<Y{*mq5|@)4dCDb-1Y=HR%{k -u?8G&tYJR}c?hLVNyv-6qGsui6QA2Jb2$6;D%G*Q7_aZXd{**ZEL6{W|SMX9oz;9(NzsUb2M3@lxX*> -^ZboZsLiI~^-Ud~m2#-~s^{i}K{wj4@TQpDM&MIR1^NxF`7{&&&A??|Vpr^Smg~rQK|aBdSAZtYC;0k& -L`Q7YxXhyBvY2JVs5ecE^AM)`^Oe{AZy!<|tpe6|-a|@EvcUi;NMX(~Bhtb648}aJ55{L#qk1C%wEfT -n`t`T)_Q!mLDOs@K8%C}`Yle$+U{4^nk~hF?jCMSuOEsv40Q&H2Uq-YHcu`1)H&0)|3#)y*5_(fs2CC -VH!7Ou_TUxb%Q{Zw&2b%dr@J3QP0#pITZ~}KpgG4n}%ZUP@$)veO-p7-JQ-Q4%s_9ybK(>pzIK#Gox)ft`*t48`eNdEU#h^>%+nDD#ZCbV)N04?mCGGpepQ!#_6GSavEG|B8Mdm~hA3%<1>-Zc#S5VOmf=A -H-)|ohx{8m$_u5iMQp1Q453BGNJVhhE6k@mo-##qiuh10ufBh}uPb`b{^K7w2=0S?VYoBuZ+aALWyz;t+|FrNvP)n8noIM#F07A2-o?>LQMxP{2h)b%#z**b -x78%cS}U>(+M-M7Zw@EJO&DlB-s+6XBvuBMlme{KdswX7E;f6bo6%eLjy(6_8^(D_^27&GPO0$~Z4pv -`}FZbfqPiuEVb!BQJ9bQzQ%<+Bgl^N!isVC%OUq%2=b=a2IwiFK)zcc0l?P4cQJuz1rR}Gz}^Yg2Y(p -l(NRzBS;cwij-}&tF#ND)?&wJzidaRKb*57^d{J^&=@S~m$qD;p!tuIyTtjWRHM@11wWN>3+SaDw<=W -2LBsJ`0sShFNcpZVj6skrn|cDrb<9aW3)?i=V -%FhidXY~1D`>@3N!T8t<}}s!>8ZmeS?QrwWP_9pC#X7UcTOo?u<}$h8XmxFj^^#ChC0hmU_RjVKh57G -Q=^7hee_op_Po3^!1>;e#uuYRxvfB-fa|dOg@Z3Dysu$aOmno5leX&H+a+W57jS6Xie~9sysZL8Ng^h -LWg%0-M6L@@P(3&pkKdx*TQBQpxQNm2OF>p1`ES_RpHx{19dFM`SBCncsjLbI;1S_wj>F<)b9&LIopg -H}s$NAW@E^}=I^x1{cTTD@7={ej0fnqg<2$O7whkNiM+H*%MN+^&(u&Y{LWRGbEvm5Zj8P=R -|wx0BPX*lpPOd_T_j^U}3xlc`u(~FkZCqA=;~uAu!>yDMef#m4+)3Rg*TDXG1mvHo!I^V>5+vV_9 -b01p#Y7@YrP(>)JeiK6KSMS4Z?Y^p_#oG~GOW2~iwvCWq)elkFQzh4W(tSEEH&N`jGXDr@~8}S4yZUi -C@7_CD4h=3zlRcy42tvo)Nkgu3Dip^!yysqmHVb5CPMHiV+G-G+=K8D$*C0ze86}t%7OV&`7amdzP#Z -rVIYcutrWCjmLAFanVf`Xf{6K1Hin>MD=a@da0bS$L=3pGi?<4krl*c7*AHJJ>9_tlSpl*$b49=P)m^ -`D;>fI>zEz9?r>JLFzmm)I$M$Y@*XPdh=L?}7dQtW@#v8z`$h9h3gNZXgIf3EEoeNnv$OsrcllU|2xf -iGNK{U*U<0;1^1QyGIXfn@tt2;%d_Ket>@%YdiA--n7=XP9vDH;?B -&P&6r@o-GlDi(z{GUKVs0O{&k2{hr505^yr_0)*TCWe%el+<0XGETUyh*2*3L7SPVu_nU9E;5)=A@#U -d+UUz|!6Sxwj)EX`wg-!SgnpSOCmRciWCVd0>zL~KY@qWvGZh|RYRj9%UhFf;&<-3U!lRL$NCZF2&*t -NK5e58zzf)p0Ok!pFhCazB1NN(D@83%VzWzWu2T76w2be3;PED_aW<>vq#UMJZ5H5?`>5shcG|u*&$nDn?UKJVonV6_X)&(?Q&F-~}fFNWh3Gkgk61@45YKpo_`CzPj -dm$-=ItqDWeZ-L&ALzzrex#tK6mC(&@+PX{}E*u$@{OTjob@r4;4Nqh>CDqllItByhVc)>7O!2pZuEs -LF*R^PA`6lqMRwkp;V;TSYgzk#wVYtpcS*wSe0KlotX5w5SPulUgVYFx|ZTDha-eO^{UPdi|+Vbc-37 -e|dMRldSqujur|8w_?#dqssReQVjbc8nDcd2Drw5hV12hjMy|gdJt> -`Ptc1Xbnh3BmWMsQU0KQ&irG1(G4h*YkD&N=v6iQMzqxXHH>5uUE)8C+da1!36^gxp6<;wyq$)Gy3<1 -UvvS=)+RT27RI>F4hPZ<}}CT*JxohpH2Ag0AB^0(LS}ujZ!-fHfcc}Yg8J1`L4MtrHbd6t5eh97dub! -mzt`?vuU!*z2l=@e?=SgBG5`8zl*IU-ucT-ymB-A*yrBzGjF_t9~8b@K24~5TRs7GAGS;%bW$69q{zc -Qp?IS*WP1Z*ffVh|y<)kh%aooDWvyn@$)#3K&@5X`Ls8y4anAfMTPJQJc>=F+-^oA_!i%WPO1EV<7fL -uVeoM&*Hp=QdQZ$Tw)ivxfEQNPAN@`zmse|Q`eB`$(K(IoQ0X4LFY!!+-I|1Vm)3pwQL|>P#9Ol2~v- -6jyr@*f<2666dEFI{Zp+qr=Tl+YXFazCL9Sa4ogh@zuirnj+^2>sH>aY{7IcFFT64irk&D8aWND$SOX -?(F0wUKkiqUYwV+j%0Ad1p^Di^8U?L>b#&pi4{KlWx`kkObRUbXu!8mr04V8Z~{U(uGYxK_h9bD8bTl -#&flnEH!hYi(sWqEjjL%<-TG(pOPM;mV%_4M7^YMWul8pY4D=oMf5Rs(*;U`A_xL!NWAF#o4Bsx?DpJ -z!%dQVIzx+heg2sWe-?Gv?9ZoKXH3yN;LR_n17(R?Y6a8FC&?2}y~SecufC-IR0Xht#A_J2BsS=38H=1?9=ECtt?6-O+*M;Dd9Xybh@JKIudz{QkAJuJ -VR~6+R|_(ctnWG8M -4WZXmep2iMRLW-ll~}0Lcd#_x{$%n5bm|T1ldLyp8yFnaes(fHJ2N*xdU#tbbn_h0M_k8&hHfyh`Gl` -C`UWhWOZa59|Dm?KiwA&=7{>DtJbq1-;I7$Q`}qeIzqHHiv5QZy6DUi~bz$n0$rn9Rday3*x2_wt*7X -7udt+p_Gdy~*<4zdE?d{sN6W*BFx$<7*eJ>(wpG%Pv+=Og(nxp}(j(<>Q=;qab`vWI5r#J^NNmaK$N5 -d(Q8@oL+Lt#3ERh4x<_yU_lbE+8Ukbb19Nw$B{$7ya)i5F!(Kp2NIt1Y~^$%D83Uy7(0^zlFV(+8#me -||ZwRq@{4T{C4WI!;pu&KQ-jfeUXWQQT2Q=s+^)tAF@kD69U3;&2xGa4pWZ+uPdaEsQ|~F7tUcJI_Q*6V)CCNCk2CfoCRS;i}3 -T$Ke}#zCP3CZPS+p90vN?blSquRIpdo#Aw|$BXbu?eixX--D%@!M8V-Ttc0v&21(H%Ho7Q6BVWs(8Uk -6#&Wm@C8U=Xf-G-ELwZnQBhA^|2Zg-0kn((&ag(;-vy@(bre3~Kw0>JY?0DQC~C{f{8J0_*6pop-i)m -LQkW~)xLMTUa^>zzAmch;h_!T2N{MCaK6HueG@;T&|8-6k*wJlv@zwM1o`%G`T?J-7ctpoFV-YT@xU# -rV|mk8Ub{tOQJbzfK|{NLS%|cLHRR*3nVNN~oxCr37?z5Bn|~xiPI1EKg9iPdKZU`wE3A9fPh -1CV%p53jq9dwTc|9^pPewMDOSa|Gtew!u`-GabiABE$f&XS&og@+TPyQ^j>@}H=`|}=BaAZ2odY`I=>I`x9I<0R;=}FY^ -Ux-)PrM!s{R%p7hfQ_G1)MXQ*e2Hif)bNgTTj+}1pzwKtYfO)ehlg>vO}Z4S*+e1=+Y*#ZCTik_o0sX -??r(I;X=J0#!r?2YF{m$G+KUYI~csX7s>z9 -}D?MA%nan@Y`Tbt_0#{=QMWHexA(S4PwQtiaH)2`OUtKR8{c-H2;F8S6wbSdQdEkfPsCaof?`5n1!~T -@4?MPX$MtT#{b>aQvEvFq@Y68HSb&UxCpDSm1sMTLV@%^RH2={9e`4iDrH%cm0`R2^9YHBAcmD2XP>E -xoRDGtDY>g8OM*Au_hd$EMBApfgFw$v9cgHD-mxIFPjL4h100Su{?e-3Gf4)3jbjSE;)X4PH=K*ri3x -2C4f#2sHJ@1J41FU{-Q`_-9f1X3CD6nO)4r*Q{ofFn5rYR8i}g<6>|lAZg|aYa*b>=(kXncIKT@Ao^R -fWvk;*Ub)>_EiIg$-YT;7ZkWpfvEU`c2=*YvGNRv`>2 -$NAmrk_jT=PR7hK9VTL4a1R|T{4z`Z=REJdH7pZ#K^YyR -g==-BcAA3M>M(tq;SrP5E%bS*4#q&oV%Aiaj__5QDNgAmhG+HB=>Sb(pg(7@>=0i!b^|a+7;^&22K+k -x8yXMO?0SYRT(c+i3A95_b8vc3>E@4W20~mSjo_bD2Zw-0*`YwH40SJw)A!)0p7nHm0aZr6gFBUUjt2!z=zskO(r>YReIc -XI2TvA#@CzCtsny_1R2M>j*>1$>j1@Fy@Z#1fP0>cSL)*=sVn#8qj__~YXbar=uM+sK*E;R?ffc^JLR -rEk*0*R6(ctbeb3Akv9FL((3}Rb$Vo#+H}d})pMi7)gA+!_GT3A?d@znh#qh6?Crt#-S%eXg;S?f -e~c&f9kR{dWXrt0J@aVu0Y9Qb+w=(Dic-6}qI=NC5mF;4B;6vzQ08do8mU8anw)G!X^X7tVv=`H%nvD -Z6y#&!Rx>4A6t1MejW;&1Wc`Wo9@Gx3`wBh9SwbiDDs&0W70{DtEkj46!HoEFcrqS{@Wk#}nYe8*yqK -+sWm?4q*1Q%h1_2;~#T2dQhyh3`L3ji5JelchPnS|4zu>(?(ZVlr?YW9`o=(r^WSx$IL2!+cZ6dpPX( -l+0a@A}t5i6WOZ!Sl(^!;BMI@&vfR5i%nWrLl~hNcVIF)*4X74eHPCmmE=AcuZzDD^4Eb;_e{I%uitj -ixwj_2v{@f!}m;d(c9r|4YIiMUFwEDZAd@IxZVE#(F-*Dy#&w(C`CF;0G<`EUaJF+htq1a)c)u-I!-$ -43M?U)JHa;Ubi?VNt`F11C~oAH-beN$?_>M=fF*J9L-*ao4nqVY%LIr*mi*@Z>_moOQPD9a+iuaj##B -6s>>lCP2meglH!gXM>3vhz!6R$6Jocu -j}G@+(hi!u4+rWYq)_qc++vNn4+iyWsEfRoq*#jnCQRxSvz1^g^z4LqsVfE^boz@Z^X!iIWyK(nx=Tx -*}kCH`DVsKsRB3uX{DQA9?&V|67^DG6t3f$51T)DWC)r&1`k?mwQxw6eiOzRCf3*g4xA6!1M-HX~HNC -yAm%5}+xn)A*Fz(4bJtMO*Rf>!jpEml!8H(xZb2EEG!Wq5^XOLvKimZt)cpwyrO`dUR{UA=;JTi!EcA -cf58*YrFXfLpPC-Nq|B4MA;tyH3oO~dMPQzYNC+h7^Dg#OaZsR=Fw1JVe`)98VKRUjvUHd%?4p+$`D< -Qaa_5Yy}f4J{mL7uKeHFn?RW%HE$!Mx|GKZVjrlEh!HJ4uvv-kW1ReD#gf|AS!qXBR=&Tj4aVk>l+2W -rFgg@G&DC`Fmu1jy}s!g4kY$`(KjRaJAi#8?WFo0zaME}+vKC3$X_+C6W@Bwm@z^VO(NbcAw5D$%LXM -l;Vwg&v4Qmst$GmJ)a2=Jecrz-iI_5^1wRMkczJg7C81jEoHf69N=DJ)o@TuQZN+&^by)d5&v%GyAgs -4Lebj(Yf)!ygYg+HW*V&usX*X?j5qs4b3lK#C0m7Cj200z(OK%6QTalKM1k%oLr#Ca>ce>V6rUAGHag -3WvG9w4*Xw9ChwcaU1k@VSuUrsqI3~68iVKmjZ16gC2`(d(t73fI@k{YBfy4%4KU^YfS^vNU51UPq1r -=AxHAIE?~+p;R2Of8gm_U1UE$7q!nOkR#n+)V*)~xVmcm{qPGAo-i%Q$4(QZ%hsoya;Da}9ZBGhE4^P -9aE=lw(vmHz@Vj_o^L1myMHHV!fx*|eY?jq$gCPK$)0|<_lwW5TotSYKKQl@AfTGQ8ts6mXjPj(}a=K -9fgzKi79zMt<#+xOw=2=|8p(@eyVB9Cbb4HDN*YDq2kgL`2#cR2p_2IGPhN2trh6?B-M6){wG1$z`x@ -Py^@0tSLRJSSUVXt+PBZ!QpG{s_v|7eR*`Xr5594b&?;?|6WSm!7r9VBGtWuk#= -1J6c%S>1e>*CMivAD6^>L&ys&Q#`J9HBGz3x@yK!Y~6t?3&Qi7(L3lFX$b~IzyMT-EZfhl*LJzYJHZd7b;=0#Py|Gn#0nODLYBM(U@yA9q-0(`~PWE^|;G*32Oda0T=A$ -#~(Y+N_Y+v(He{3zl>V-+vNR^fHd~eiu?+9T(8JW=m2@XX%G~nZWM588I{rERxQ(7?Dmt$he2p3NV-7cr0!S|JWr)+7y=4Ex_Tk>+-D{f|M{gSGrP&aW -C%F5o2`@w~qa)^FdIH<+>V7Nw4RU(EiOxsPL|_LJsg@u7QuI&Tn-2zWi~cXa@VB?W{LZ_=X#ScWw7&o8Lmi@NV-&`hL%A@DtPrv7Bxjw@u -x8HBU2oH`Gud-Cc#Vejap)F>@Qhd6f#v9zOLfIH$|ZM9i>j}zhbWznxv7s?@jP#xO3dR*ZQyZpTCbA| -B7ll?dxW=cxaKmb=tt;H2lA?E)|+!MIt~x+W5Zn{%^;fjpny$^K`HI?0CJl80vhK_s*xWwuo#E*sp$C -KiYU-ZzS!n&?if`__8-Ew+M}WebZ(-?XBbF8v@OOumbCl?ke(oYM*FKu9&>E81b%xlIW7=hjh3*(2wJ -~rf?fWi5tu>YIyo+{q*Ab{>vv%0TX0cr2aKAY9w+(X;W*`auIK#_B4A8?Cx!*jYqW!R;^QWlK$3yW>U -Sj{^N0Nl$`;aqJrGAP^Ux!u`AiAHl24MVg9e0$vH{Rw^OWYOc+D)a=R0lDt#cLULv%pJ{($>v{52 -TOB)9Lu4P?uw<`Q>xU6sbIk>Yd}d15l|nY43C-e8RDY6Zg1=VOE;EQEh`#{eJcJH{aIECDZ1wwq$zJ1 -HfnJ`7dt=!_j#1&uKB6zq@#U`D?i5e|53$;+p@}Rk=3(;ylMKn(a60U(|)b&=7f1u)@aswY6)L0~9sPCfS$>ia^4X;Z;b1uKQI_vTIBqB$Hy$p-(NdZxHmO`LwZ%$$GJZ$V4q1mnPP}QcAFy1y|K&o`%e$**n?3SOwxWr?(GwTW6E9{5B|XxPxr%Z38 -p5`T))BE?l(-ZA20PPwTWKzxi#wC4USGB;yzn+hnWy46gzPd=H#9<{g{)26hgDr~nZ|?-DaI4no9&MW -@hE%_U>9)Ze6DU>9N%+R>rAtM&hx48WuqLB%2S5@QB|-t=TJZ@E2mkCxt>k)(@R7ORypNT8DS`%!nHp -Jcrm@gLJc&~^RDv3?)nB|&2b{)#H%JG+}%E2f{q-4(V)fjQzmC9j$^7r%$%^4gy_#m%BK^X(u@0h(TlyZFeC{HV{+aarSj@VStN~8FCwM+V#NUp`{+6rKG=K+=Z1$7HV*$9&q8>P2MYKDt!vAE-vh7y#dB;}<=L3<%}7WN<*3W1?FZokh -fl&%FQmy-6IZ8e`v0$B`Hnnd?f8)aaDBd0UFPEe!Hfu2)}m^xE=olv)A+_A|BUv6w*F -c4iD=M7Vx4(nFSJkD3f$&v;%gbxE?Qt(VEVqH;2=XrE3b0)rUhHVgRIo`5*%x9;>wJ>P(F$9JZ1zF&K -S(vF%#xU%^jVGUHD>h97i-y12FDdiXd;xfvax1E(}CCU6}NkDhUK_9}T!hHL}CY=q=Ez4~v{H`Oe2O -^@hQ9o^a#G;(+BY5E5Ub{5W7?V@$Yy~{6EiM{P5)EkNEZ3%hzN{i+}#|^7Wgi(z* -Dv5Tiqk_<2DGJjGgHo<=Vn)*AhYV1LkH-!Rz6m#k!d17M-b-vO*2Jb?1VmM$l>S1s}|oIfMYhxwKa9* -_j9y6y<`ScL+9z^pvWW|S|7IeA?N0s-1NL+yV`)l{gLdc(vLStjL6ED8lmO{dg}PNK`*kz3Lvy>n+iL -6b(~Dq|Lrb;?%JCL6|V`3_Juev)AlJUr1gf|3ts^EBJ!c9Fc+Q<1%E@J?0Vr^3#WMDK?KyZb-a87t0>1Yp!nbUmwYVNm};m*wf -1d(*^wb4o8Lfs?g*mti`&@t~nLz)};7OIZ<}e!}zr$u5z}n=TlK2s~Y3e*)KqKGc?-l<)w<<)|nhg3~ -AT4{n(2wQh6EUU0^iXad0hsY{&x$>4vb)TfmBzDJfLjoJ+ZsSE=o?YnB*q)%kT;~M-+zmH0boD6A%9g -Wgt$_`2;>W&6$vByx-A;?yFdcT%vFPIM9Bp;Vmpz0D~sWN?Mr+%MI*DOxEwb1BU?eYPXdcAjoItsK%P -%4d{)s5JqbbQxo(hT6hfc^8vqgB>=3%q`-vGj3t*Zq=lTrv&*I1_<1txQB6ET?+_WV|ovh{|>>f5#=d -`c}e<@88yI#^{?^gZ1v)swK4uuqIg!=K}kRG{kk&_T6F^bvLSR303{IOp#Il6I;F?YFc-7My;z?4pM< -vYByGO6H`=c?p*7eO3l+vHwjv+2)VplwAj{MMXZOXbXv=Daw*|W9O~L_F_CflNjXKg`IK&nP@rc@$AR -}KBlUfWy^}IRu5Zu!ySVx=)$lk{#?IRy#!j=-o!a?qHrcv=-)z9Gi~McQjRPAQ)^^RC?fdC&gl}QfKg -+i716V8OuQ{YP_IRN%KZP+{ub{IELbTbO&7nu8i8M)-U4k>JwLQTU2l?o&Hj_@R{S;Mf&;{<>F4Be89 -X&0d=MXzANEiPao_pm_mcOnFbxn?<(%f$E?b57>2l?A9sv*H^7zOEK6rn*ygERx20feK%Q^w)KHBxt4 -Vfz8Zs%82D(r**p^yn$JQE?=)W6|7=mJEAH2V=#qNDD0{39nvjfBRfZ?QbitG>X)#$j`-T#oZTDsJmX -IsrbPMIkSRhz5yaAk8`2{E>#n@Fs9>Ba_=6_4BQH<=|Sl_p=T-|seg5#(v4g7wK2ynbSh?oXjI!`EUw -jko7cJP7McOWa|h*XcGw0iPA|!oSs5)sOSXYj-(mEHX}St0i@G98I(%1@G&;@E`CKkpzb>W{*LP}fSys1dI -je#LDLX$K?bN1dTwZGkPiE~^ovEDQoHIm?(7k)tCS`Y`GOv%ko|vo3GFV~Bs4bn -?tQ4yIT$wKeK^96;ed}mbc7^4V=S+)4TdJB9X8lx3ohciri*bD*G5Hqi@^dc1GkaTtIaKjo@g4Va{$I -D@rOo@o!Q^er1vy`FU)~;R&y1w1a*V0$VLJs$ZDvBMH>1sjtb7Tq)*Dul9xRG`6>H~=@0^^H1nu`*HK -I;j`>!XvcX$y8#=tu0nJHr9lO?(^>D0iy{to#?S1wh$gDzhYQVz9E7hyVL^kzieQWQ<-@E^}ckt8GYv -m@jBqZMXY>19Gz9MeBpTFC6p1gsfBuSxEE}C6VvYp!5H1F4T+tI^lBWgz*+xJT#oWkkk97-n-qD}a(o -wO^;?~*3#9QV30)eV&^KQd#8AK5u5nZSQR`2e2R+&lmylfjcvhu!3yUCUC!MGPFx23{6S3E5&7pODMY -yYgYn-3bIOg35^DOPNv$BEbN-H=Sf_8|ay0{j1h`c8D>QrY4rByt --Tt>Z63=Zp^r-=xFx>Rp8c=12_=xUFnwF0%W -$e=0tQhs0iCp#%_#Ju#b8w$b}3Ym#p{cmsA3|q1r}k`(SVMzys3N>BrReXJe-&wa}6H`*D$9L&ChA77r$zSFxT0s8F -Qq$s&`d*i85+SUF{+uN!e5O?)I`wsp~6A_sU>aOn=>STa6U`DLZPsI;11XRZo$%wqO^1Tlh@K$6P&b7 -^!cxOD>D~d5%!*_MGV%)scF2MX|L~W?5_8K(aPoMe%w}RTXn0C4xZ}wIrDq+$34F);r^ -j`W|EKP=M!iuH?ZL5R)3(mQ>VOXnhnQx=%N#&AzVit@)~ZlC8?vuAN1f(qkiD!=eiG+7lz<6w*+PA`F -3tA4(VXH)sFSFQN=**UO+4DrISq4bj(a(UUu&u4+{VsMrkjNUN%4@yI+n3P%?X1}_s%-%b$A5gqQ6V{U9NQWUv)*|+%8Ma6u#WK>o=g)X*ea)Jl6#3)|B7BK#hK -F+6}}2btZ7Exv7?#x{vD1{tVoNzXOakOwd)8DzG^%H2v2u({L*O?)cGHM>#$0OiHHNk5m7uAH#eHVNX -_)`xIO{JJzGS>S9Rj?gcsguK0M5(Lui_2=mE@ETEHrUEVJWA!ro|xnPCrfFmG~U4@H(28fWUpcY<}|L -@<-x=B2QEwTPSGB8ublv=N7nG5VIjMNpy8`v_NXB_&*(}+(iKWcEfqyv7^`R}g5e}0bF~?-rgRt -Qk97vy|KM-T}&7=FUtBevE2WU}jBg(>Mj90`~$Iq@zA)4eKG6%tUKUrTJJDM5*NEt+wYQP=>%n;5ZQ!DM+b1-721vOU?&`4Zq;BI`Xj`J(7H_;Ij*SeUT4 -Yx|nM&0#$(R~ylHf?E~wH1ZeqvrC$#{r4fcCFeT@6q~$dtbTD!lQ=wthw22w%g7l00bOsj?r?56AlAg -Az`{rbUKfcsJ;%BJHQz0_u$J{$H&4b&TqeCYdko%HEabBIVev|C#HEB;rS9=@fMoYYHZGsm6v4*oA#h8mCX*4=p53OS_a&cwV1Hv -}_L|I#JE~hl$m(e!t3@m8hElS1g3e&}RcA^I*eT?0(C@~K4?UCA@$!lb@O6@^v2FlVE*LWkO&1c8%f? -CUAw!*2EH{30;=BjMCep>_DB$EiIQ^=P6oFm56K$EW_Y2fW_J3G!IE9D%IlS%h*h*^ -+?mt_+*NA!aY;b$xqBifgcS5fdO#F4Hao_LX(fd6@9qXvsKZyHA+3;UR0%0UV_v5s)*Qfi|F^@;|H)U -`?zjqqf4r?sgeAY@m~>isZYUZkfoJqL>0aG_*fXXVPoM#VH9sRIA#-COychc4YB@+c-4!AHoF_GmNhg -3ws*t_uXS#=yx=$}bSW2_(} -Cco9p)`s8nKbt83V_A{Uy(@#EL=6Eq -C(i1IVNO_`ke_vT$hIb4onY)8zfzA?i+E8~IP6Z5}s+9%(>3A~H`Cm@w7(Ec$9 -b{;Uz!vzfaka1tRbeISwfOt3&d;@@(b4qi==chL)oKm==je#uyg$BrFT-;*8jXAP#(KS0FNG6v9R2&_ -Ii9=7fDk3E;%o^fwK}y|7ns!R%P^@m)@$|JVoX$E0fWguKNmr+s@C(Z_Rv9}l^d#usL}VRj^6O&epc# -i_aLs%$%Hsy%`F;J^}Uo4HKo{Q^&Z74;y7|UwYa7XW8r&)vfh$I5GBA&A{*LkC)DoB&hFeW$@Ok{iGY -zG!4mmcW5AK#1AdYAAXGYuMzu0j1u%VI*Q{S{5UE-^w1 -q}8|O((=X(Gbwa2^AStG{lo^c9CTxfi%H$(gB-Dq>kP6Ps-qI|AVE}wUgRF-6TkrAenffGEc=T(|0yR -M7;9xCy5SSz!^}HI4}`rY`KY8s=5p(SRZLPVvDsz+fO=dLg$pBG|(P3F}|m=gdD;+=B&1}{b7ccj%Z( -dIkhIEG9QkOAfvO8QDKfedut#E -L5Sx_B*&K_cufQU8FBs@HXACy1T7brtpA%q|DnK2NWP1 -llzU-<`AOUxd8<x2Z9P~?B1r!C1ir}`iD5N-SFUo+y2vHK+8gUWygh^$09Qd0No?dCICLm*h(U -BC;@Y7KT2IvO#^jC!Ti#tVHEa|OHnNNE#^l>fh7Rl~99DeuSVuV6x@xCWhhMnH-pdH-cG&#VNDwSeln -JC>Qc};3a?zR!57E%suC%qQKq)Ad=&cA%uI(D6m_fX=mQy63x{4SIcPyM>sU=Z@Q4G7uDtN4BMnIerl -|bM_743LuC!kA0JgjW9RC%KN13XG|d8>Q)mw>ds@cgeo6c-Qx}EVYZvh3>9f6`4i3A2{mb*i -r``R(?7e=I=qQ3!?9P4`2dQmwjKxL(P_@2Bkb&dnvXH<2@%{Ncy$JWGb*YOqc)-|Lq8}qPncaQfJm(HqFs>^Jm(a%Waq>~rI)v*Np?BrfUQp3r0*-9zSU-Pl!Es7?r3gjRg96qsEy-u4?Zv-3V%w -gy3!${68yn)u5AbAfQDJb^Pe?$5P8`j@Xu0 -OraK)vMwF9l0lHTcbhy04iY{ocP^sN^1{X{)&A`0_)1)cWQJQ7U6XPQ1pJP(+Idbp@v01!5Wy;QLl$? -2N`NZ^{wHzw!Iz22KtpG;FSpKX{*72!%jM6xhRCo4&tI3b%z|AGE}*ekeqe}MTX=4d|>o8R`^12q8M2 -n9!@i)ov7l(YsA&vDA#CB`1o6$Fbrl^UD}>uY%}q*Q;+ey`YZlv)Q_E#TW-lteXmXF!<@{-8yj%UrH4 -P5hkKj+jK0vn58#0Wju7q$BuZF5ejGwGrHp0xr?(Qqz=0IT)Bu9rBNMY&SEou?0QnFjiwwwoBg8%2_h -mEnCrf&QoGH|o_xR^>m2Md9k&#*Se=YR^*_p!k*v;UAY_NBp_1?b9vMF!PrKoUtoJZ{vWms)&Dap*L* -XGmxF|J^WNt_5yhB^m+`NfXpC7ovbxTG;qC3hDW#E6m!gN+%!>_hVgi`BA32JH`AY+?k(7`R$K3v>D6&k&aVbVcYM%^U|9hn|?O -tCEXlFbJZPb#ULn($&?fYUK}_q*U1TDbDD6X-`dd(!yf6>r8@uO^A}H^zCib*Smh~!ouMKJi#u_|jc= -WSsEdSGPoKVyyu>FZFm{OwFbcp=8l^=5^q7n4KfQ49$n`l(WMCL7gAAR2LT|?}4t!0(+P|c&PWc?;ms -fOLkA(h#Nuhbed(YQz2M!4o^#Ggmoc?N;~vp4^eYRE-<;$qqo}ME( -^}_f<9%nVOc<*%p5uqrS|Z>VP4#xf_TSo3Qv{WX@pla4N5s`y**b>Bu1b39C|f)Z{C -JBm7n6_}-GbHzkP9^Y-?~N?%kKBO#6V;< --ni_RIX{D!l4Y#x%clytyU|#cmKL}=MIv=wyoRT{-W9J92G}zj_-ZnZ0^eOB$6*}0|8JgP#c@U1gbAj -ah-}*TF{Sj?kn3AR-&UnRKrPgGI)avzKrhu`(%oAG`i~id^9>n`dBFU@2^0T`-E(ap}1ieRc$s9^WJ3 -gDFbvBd1%WKTVG!|W-1<@!tvmPGgGVqg4e(`V*LxuFAr_6BX??V&`^&O&H!llCBc}Gj?IIjJH6I&h^+ -N-tIfc*N@~+pe_CV{H02Sr_TrmYW}&Jl_0`nr_LxrqYtaz}yjw{TuRlhQUrTCAudntYPGXF@^DG@==V -Ff|rq7{&HRikN8=bp`yi9jI#e+aKV0wm$swB-4ZF#a&tEsG2qE)A*S~+6zt7)b@45fk;2B?jbZHN&P4 -e2SK1f{EYb4z%+co;b{j5%Z*cB$R(uN>`|;B*b>E-F*6*;aH7*eu$da`Z^fWg_*UO=Y}8(XN^p?Mg+_ -AX8O%1slo*C&`5ILeFFEdYP*@i^fy`4p;QC^A+M#hDK9zvo4n~8efGu490X!x?} -S6HVPPM#C+d1@zO@w4E^yjRRi`iBeZ_p+ut6hy{>x5SUi9A?%{SmdH1mN_$z$;m3?e5dj96+H(zbEp; -R9x0U%I}@i$u2WV`I}%CBbmFmvAG1B>Lrho63W{$zVT&-?Nr&9SDPzBtFWLlaL`av}nlK_Pz1&d`VB= -R6KJo}Bac85&(xynB*j7?{de*dg~d-PGCOh4PgX;oN!S1re{z0$O2=Rqa5zM&Yl}_J&f_p#Fer=tSgf -)JHMk;4nA_wn7=uFDLFDfvYqJto;Y;{(}dt_QUq0Z=GjfwKg`h%?B>ztl4(;-)=sLQjbq>r^{q$_ebr -cNSCsxpJ~ZV&=yQm3IEZaNl8I#m1A#Y>98l#|DA!zPqa}+wi>=e#X -npMht6JE?f4(jI`LQjztG?6r@o<*gjh0#mS9)AurB1zJ?Ir1pFRsXpC%Lxh)#9y`dUW(AtZih~z7A7C -;fmDJFjp4$xm -0gVK@=WsDgvn@a4Qq+TYc*8=RY&9tBO@?knyik}--#Hs-5o%QDN_ecG8$DhvW^Q{n4H_TCo)mhl2lEn -rqQP;~EZx-Bc1QhztbR_l7ebjf}u%4iWZoOtYsh>Eh+Bu)C>1ID?hagO2I|>XA&uYCcjK~CMrJxNTr3 -Jh{!wVuMTR5@~QpQl}${%Z?lQ&=L&APn6K^p~CKh$?p8xU(%Fq>tnIPV=zQC&Z5nnvnL#Y8Ak%Au1h; -CaL<=EH{0Ta^w1GyJG^)W27EG?Qk#UNFa>Iee^gF%qK0$i^rQDE}8c%LWFWm!vwY2T+YW+0$eF -9yPviRCGi|%`dA4K35OjaSSxPkOpg!(XJU?jX(f3?JPR>$GsvNxJ*_kDTn|&U<(zgHAvKY^+x>0Ymf%^r^b|L`Jw_w2#`%3)@Uo*@la-P$&tq#+#2i8_h?@SDWz9!(((-KE7&yPy -c>sC!5!e_^7qKP~*dcYuQoMVQE#D?y7u->BK?M#{U5m0|~Yz{=06dS2kRsiEzMPP0O0P -_1?`raSKF6aI^8|$!{*h1O|6NKVC-h+@a(8>9` -(~pegLO2N2b^Jn+VzL#Nru>lb7&m4u{Qt-^h>tG^<9+$H+yea~K`tgY)rx%1^ead_J1bGSvlo!l|d|N -la%<>`3E#d^P)Z_a9eyV!@YN-&3W^J|eQAe)7_keLXxs-x?0LK(H&~e_X9MH#W9lu3b0K@Ct@=K)N8p -3dV7m4+gpUMVU$f;I9<1Q%RrSQ9i-R%!wYo|Dk?ZS9Ue^sD7=;i#pi~f##d*xG8@FiQ$CcJZf(`f(&Y -Db(irwcJsaZGy{M1#~v2YWUEV;y%Rc>S<^zPm${0BouPQKfcZNY$Z9>5GT6vP?Pz7mIuDcQvQ-`>u{) -iPhtWlLLg_I1IlJsFPob!r3THh7|SSOwfsQN(U)tvbJEUjtbnHH42=OX -G7~Imz#eru(?|Ib2W!-!%BNW)kk`Qk$IxF5nI)?3xR}eM?R~8SL7P6SnbQ_v+OduENNtdR?&jR*eeQq -67SA&uZ0;%sR)gSIpXTE6(#v7USzTta^6DIylcsF69mXE>}Cjv#EFE!o%}TFcF|0%+r{`CwQhHq=A&- -6wxv|I*&yr92siu*Q(GmoH}jcGs1ZYEX9@?4V+rkq!XcK-r^+P~buiuk(v^DS!F)KPxY=AO;P043wHC -#~qh-j>-#)jJ!`|>9D}zQIqe;TIg&NKz#-~M10>ZFd3Pp@9MXvI)0o5Oxx~4m~*&hR$x>L~~Q%H7)`H -o~SyG3@Q3XsZfYvFSb_V$v;kCXj -<)sf4h)t{#W$E-lR?DNo+03^}lB=YJf(IFPzlyLG9d$GItfbgsQ_fJ6tHpTz80JsYj#vPEGv5_ivJq( -bs4dEk*x3F&X^UpscBJTr;z--cyqLvvM%84hfMjX=@$I!*MYw?X(T!R&h%1y0vmRk${cDS!*)mMd&Fu -AcHePgswGelcrgyvv&6B8K1_x}S>O9KQH0000805+CpNnjoajb;G=00jd802lxO0B~t=FJE?LZe(wAF -LGrqc4cm4Z*nehd3933Zo)7Oz4t3Tc1SwWO6-WlXY7Pf6u>PWA+3^tK-$O8Nz+nkEQi!_-gBOxOP9WL -0XkpM5c$Ux_SBDIYr6e}I9&soqBx;L*f6#3bvV^}@8egF=Oa0*FDR92$e>bXSrkWX;OcdYvRoIyt9N9 -BTztjc8XQ(Pw8zfX{R6fKgWCQKiQS@1cve^Aoq}BDY6&z}YdQyw1+r0;As<+Sj570mTv^sk_;*J(p_W -W8Cm=Nwf_yBLnX_6+QF5bpm*5a5OoF_Fr#p}b&4FzVLUdvYwhNr<_2JB2CnP^~wTA3jz7Qq23L7Fu0| -!SY2dS?wn!#H^)(35jU`iEu&-BX0Og1*$gV>dmTcaCnSsZa4Hz_`9Y}<-#(}`KP|8!cgQA)*FDcEd4C ->8Z6B?c;A8?-V`PP_J2!rKNupH=hkFhWPzcC|~z(tO1ep68v;XkHM8K4*;4u1xXx_w8R$O9KQH00008 -05+CpNg;c@3eO1u0Q4pR02crN0B~t=FJE?LZe(wAFLGsca(QWPXD)Dgy;}coyL%VrFzp3PI?DU@)f&6nHDR;`zgz -$Rbr+Dj32fTq?m=m2&LWhzDBR1Yj|mlj+4Bxw@N@zg^s2-OUl?{nh;sH$UH#_ZPRf7uWY!mv`jmmb|^ -Wes^_$b#o0*-;;~$U&-IEuHVfG18V^v_EZYg0L0{INtS4{-7)5fkQPQHS+ba?JO&qz`*ng2Ygmg>EfF=-+XoY= -d(9}o3Ir8ti&!jPN;a0b8>u~eBRE^Cj>sz=~VxH$Kpaz1;VFQ9;4=kclDSIjUr&is?^3^6_8ioY1PrM_19o -Vs3eax;|UlrSM*6Q<0;8l9%v0_WC=FZYhs#Zm=b2zusS0TjXAnXi9} -_u^PAG8{@tmtD3M9+Y`9M%2RZ%XliySt9E#=C5^^--Al@V2nZAum8)n>)SLGpxUbT>wY45O4kv1BiWs -YlU<=a!)DD1F+y7>bQxm~CL52$EhX8^oDyANB%5iH2FM9a*%gGR3h>3mep-7#C0HZB*ou**>AN9HfN3 -0XU9|htc1GeNQ3e$9#k3aKBp)5q#nh38Gcd&La%9Fd7LNDCA#bT^gH)?G^EK!j2J*anjMIcZ!9>!i@W -o^;{3I3rqv1(X2rSsT=p9bioTy9ga0T1nVaEA=0P5aMsK?XuUJ^K!<7Ki0hdiau^aFg(W(^NvyU8wfkI4PayBk6iXcE@YqU(0VWD;31=+ -Jl}q($vabb>V{MuIdSKDOig(;g2ND5ATac32_qg?XZPvQQ7x6cwnEtR2*w`+pQ(19-r3(H`6e1uW2#c -OWdQVH&@Tug>UZl~7V^3GIRw=o}ikU62O7;h8=gqaCX(K-0Z?ZC%ep%4o;|T3x~HW%Gy_W>S- -GHs2F*umu8A)Te_1#(4I8+$nVD)4i0LCK8o>lXK1lttajwG#ZbGFNuLEwpSw@}n&LUS*QB-LIeUbf{- -t`i7`>5x&+E3}7@W>PcPtxjUv*fNfjwPP2h9a)u;(w+o-a!$4ZG}&IsaI*uz^Cgz&K`x$(V^8dFHALiIsM)hcrT_wK@mc; -3^r6KL99(EpB9TwBy0P7L8}{i3^<{iqJljM?{LxlE2g%LK@p?z+DPdWv470!B(^usmlxTfkICHc3Gk| -{*9drYF_U`7=ebGa!P#%_Kfuj8y@z}B%W23!{tQ~=F{r)98) -@#25!YjYZ}%KwM=UeG;AIVL@87j;4T)u}da1qu>sC4~&w-`)qOw-Xbx~!ByRtcS+Dma7bTE7^t!vtFO -=Z^$^bMdF|7SVcaW%bY_(-#geK`HtMeeYD8HFFUdvSQKfm%7j;hh38=m0-t-%r4>_n62Kr9 -I0VakDlNQ138B(a#kSVIvyYc6#KI@p{xD#;0Q0uv?@g^KF0$(561=z~;^+OXg%%Df69ts-$_rujAzJTG4zD+j;<7t*ewiD5UhrNq`Xx8>ct+|PqGF>%$BW_xX;sjFU+H7`VqRkpCDajG@ -3VTvi4N0n(F^%rjd_Jdrg?eRP%r$VJ}F6PCxcgzBl^8B<*oF-RzZbO0~+-Z0~@92iC_&}yW- -5F6;W>Xg-m*E*{-Yw`C9xphQN7+#y`t2XFQ*LD9@#Bn9?2M$Rka1J5Z6N@Wc6SfMp_1k?3XqXR+l9Nn -!QAh6?ftO~jJ2fca`(D;KOn}yT?j&wX+1&e9YoxusHMT`-IA+$rFX64H>$m^0zR;?()v|fm;$5XZ?s( -sz1lrEUlg*1-gyxCnNNus|WNuDxD~4V(wI%eb(xl#=0>}H(dG?2XF+t2{RTBOb3Z|C_o%=s5KF+OZfF -8toJUh^mbSFO(L{@kTR*E`2E#E%F2sP@oc7;4Azb$^B6Rqv}zw+Nsf1idaW?sEB_G5lJ4L=n;589us) -YOq$1+HqdXDHVf;}={E-w#RAahkdB^o`dpdr8?ACp)y9@`Nb>C~6q;=9gl196sU$mHx^06h#kLv^ZI-E?+5mQM}NAT0I+z*QxQjlR{(v@(@h{m0AvEe);#6QExSwIDC7Q^t -y0ceykg$kP4D=aWieE>WeMT4c$v9Dgsi#1T(Sjn0Gq9Wkcd~=qnmQ#%oQT`0v9MUKlbtsk1{vI8dpIG -ZM3pAXTycU7>=kKpSvN@19q`g7qZ6yTAYIfxX9ALLn>nc^1|FlK6G&yYywpWqLVlTkORWGfOa6aW44L -?V1>V&8kgkDFc9l8^8+Nl%rnRonCbBdv7jyg3H>2>h^zq(>>+A#$ss{uJqZdnQ=$@>^SF*T)z$>a(<) -CRsFo9!AA{(q)<3zIVGbg!;xLRKr4B@XfbA0RsrzoA)Lq68oSG!NJ&H5nSn`2=k(6Dmip1ItL$>5fa! -@tM*{!rHMIJ;3dJwoFOX8F&t+&>eOnjQM#ns1~FO&I{%@*u>e)ZezYF8^Tvm|dQY+4T48`E;>hS92gXySP4|O(A!7d3yf&YCbbk5?l9La!^V!WGV<7c$c5{hzKVHq*gk4YOH?z~v=aV_R{ye|FT14H9heX>xv!l>xQMXK2G5xn!qT*MH1szka%5pRUf&rjYqz3fxUToKIzC(6rO@$?Rgx&L$U=U#E -n11$3~52q<}EUp`GS6YHG7ztfx9)g?CO^y>0v4#_dJb$(MIf0-?&V>X%37ATgF^Q#MNCJGjCu80g^UQ -Q()6neF900h&Y7t?|yJDX0(+H32kE#32k1N2Uh@mA}d(FN@<)= -!^g?O(i1+WpuMR)oHOUin49O#bDq#ZVrPuh1yQouwk^=PfL~cOG4Jqj?=rJhk*6~<)EVy?~VQDwf!3G -A++bS;Hx|&QwlI!P;oaEoGEx1?W+}-JydkmaeHw6$1#uq&TLIGh*t5}*SGBCgdIci+wS7GSp2TF{QdD -Q`}u@fZ^!K22t>AG0Q!Ew;1A7Bbmx!+5I0N5lplZH#5&Vk}cqn))S4&rLnbppmh1{_)j_yV+JX*x0>}2~Ci*Dd?)ZjXg0-WbsIL -G$LidV#N61o}m>Ba^tm1Y7>zcu{ReQ<{($v^(RV*f4YNH=_Rw*sxdU~i9sbSCTqs+0t@!Lr`4pMGL*- -;CJz?2Y8G6pAD|%Z|XV#$Z&JAxA5{cEJRr{Nj~GB4GbIZ0Y -SzT1n|k8f+Vd>*pjVE~gXT;F%Vcd*dHjM0lw5&@rQ;@8~@^irfP!tso55rSfd?I0C05%8;tO9SM9JCK -Lmirft^sn4Wwl4WBhb)1QD8kD)rU4d<)4CvGhC}%~bjwbF%x&x!@Xw(q}@f8bifghE-|)5bh#BL0|ocf3Rcx-c3O0~1`TJj`&QAqfYParnTkkwF^dn-NRnvYxR*9MQOl9 -^!i#VU8oPF1uST(RBge1pc`=7+HQ7f+q$(7t9&VeTV~Jpqr~ijbL}+Yl8WzQAznGekg!^lv$9#1Y_66 -Q>a-76_A$6F&fbr9g|FXfGxu#0c=BR)R$5QMF(DhN9JEheJVS1M=wIBgy3^W{PE;6jw+p+!?bA`L?!D -ZyP#5SggLhuc|aKjj>Mbx1PhhQnAs#E&{8bpk39q30EA{34b_TNvtvqdwZW&UjB0v|y;lee69y%P+oQ -P;ZkS`aB4jywI1{CYf|s?7CF-coHRV=^tLkkz!N~;8aQly3>STk~j!u@1E$o>}D&5uvX61g6Hm4J8CC -<-ZhYAC{b$G<8;Q6)&_jdrqCuw2Oqrw!f9%S_!R8hy(gGb!f1xI8gQ{ZUkOJWxchJa;T=rcf1(}0$_f -gEEElE~|Ab)Xf4^_gQ0)quvEe83#6hen^wFOuzpv8edq@rX6|j!8$2z5a2DP;Jy@?{tv|Al>F}Ak7Cs -wJ?HzO>5ACsjF%>Lz7mpdmEXyR_|_50nsJZI=?$jSz&)VjhTj2qd?oCjWNz`X^RS*vIw3o0PNb$*3t* -zb!R~P7!$SuK$w=YwOXK|Hxlc`nh2h397KY41imu4skZ^B{26YWuXoV81)8p~fpsFzUQT -!OGVl*TF{y(EObTEx>qs^f)`x`7AL!=~(3eoK8d^CLpOpC5o>ik-=`gb<8hw#6~g~cP*XfTA%T7@AE+ -MGk5S5sLB_G^0`W74L&<%8j6w!2wvRR9Keb&ip6-80?Q*oF()-TJbv8dkY=|KQsehc0yk!I>OoRBR1t -L`Y=^e;(8&iZh0l=)rD^)Noc1<@qh4eNGS7*Ek|vXc&@dZL6_lcxEFjUaK^r%4INwU$)5T^FRamyUj=C3?R@gyKH1&bmnCJOGG%=U1Hdd?pJBZt{wD@UC$hh3|cz%)`5a -h`h0iKo^eIf16g6-C});1o09N&6hCeOqIyC54`4d?QTv+n8oClZH0VUNA(-C)FKWF7>74$w5WtHg0;4 -%qgKkeXY(cA=muap|_x&w%HZB`LIaF(!cVyP7fQS=*H+4Vv+Q=5?23?q99r7pVuRP8OJqciuP5 -p}b_#lfqu+8C98%iA*p9N3P0*^G#eD>lnW7#X40gsc33KWm@RFC_2Ot1+$DJ6LzeE8($_NGj*x6qu0)<}9ei$s^OqavE|Q}gp79rc -Y|Lr{cl>;_**e)e8Q^_DEa5O>!i|w*_2L&O8fsZw0(Sf=N9Xun;6fR~ -^dJ7&#kz*GiokYsM_;hn|eqE#pJ?ZWk$$S_<4VvRh%VkL$Cf~S0=xhR*^*1@_3RFTQDBr%AT}&^U*$8 -fz;5+C>anwN?e3&lk6{H;%;1OAs@J%I$2olf^LU@W-CG??^Lxc#ZjS#|qy1BVt@bp1;TQi5cL&#(sjn -iLeRfI;yE(#0sJ*zlvdAsO%*MWC0=Q-mskxy+1f8{(UnE#=NHjtDjsnSb)Y=sfAch56X@=obRH=~A~F -f2xot;p`l6+*BZ_1FXE_<$yMNb36frv -+(|uY@>hZDBbWu`aNNirjgGm!SlK;+s~?R8$v!NYz32*#kR4e`Y8qu`Zx2Vlq@f;)-i>+{2sjAi)LX} -~veMc8DdwE&oW|~|!Iy#AU&UsFx0=cxIi(!kM}1Won7z`!uc~Nv{~{$_#gS6RkHwQB5O>T$8g;{kfVJ -Q+j7Nb6-Y?I+T9vR#8v7;+BpBgt{C-%C!R>QZJPW2;(UI6T26F|P&w7z;(ayWXOBVT$ -B%+GqF{CIv0~r9c8Lb>E)4Nn7?@{-4Vvnnkv^3z_hIDetGZuM -9@17fBjo{Y`)*{h4P=`;`x0X)RRDKzi&{nlylx_odkj9BCz`meRL>g>Hx;GmqDe1{dcR#i=WdK2sJo%8wGQH4xW5=IJ#u^FYyxG@DOBxfyG -bi-W`biXBK!F3mVKZRA_AI(X^Ts$8)j}ELG -nX}tkC7>V&;{JaqqSYFlso0Hfnx+wo)O1h-YE!9gGeT?&cutLFlHupO2m5&9vZQ=ZLzk&ZNC|x;m})P -9iN6Z8;J4Vzy6>y?hNiroSE2+z!uk)bw(duiNgXnz>rOJ7-+-K)>0HEB)EZj7wSj&$yrG=hLc_eBA3V -8>qNPawzU8tZ!hX-o)`@txxS*M}0x1U2of4yPw_??m>D>n7w-2&ewDGRw+xD-d07tOK*#{P#cMzdYf` -EPa^wfnyctlbysEXuf2d5Cp>CbKwU-W86UX+Vdx+M-l4-gNZ^ry{}KXt(2JdW4B)NmzXKnjjx~)#@b7 -q*r16v5ihnNlTooVE@+%VBS%B9Yx-7^J5@>(U_{s%upX|pcH}b;+I|6CVxlio8l#V%73b-guzA&t)wT~vIj7|CwF%u)eV$|1!c4w``g$_u$0<;fw~;Jwyo --)D5A8+qFoQNX!#>nyi;C2|*jyZ_qd@$BcpD(r)NScTi`#ddM`TtBvO9y>kRnxJ?2vKqYZ%@$<#@MlF -5N8fPsQxmLvv@&;pmlnjSkJek%N!8JzkAUlivEXa)Kn^8}`;Fu#4PT%EqpS{l0Iux#lg*|nyq;QzpmH -7Rd*TdNbXd@YEN{N+ogz|?>>SCou=H)ICC4CMpx}c;(9u?4HtZ~#20k%9=^UNUqbDD+ -Zut0^YXncl(ca%y={L`z3z|x)t-2ICkFAjnF>3 -XAgz_-oban8h7zgimV?-8*o*ZXrl%bNzGJL`^SxAuR66GnE9wSwak}07DATmR2AkfjF -)bxKvV)D$~Tp&O$avh*wz9l!pE)#nCZ3_6p5}TR5Ci+V5V`(F7(0mAMnD2gSW0R_zt~m`;-k-_g_Nq8 -T~JZO%=lmvZgTzG8@TQqXGT^(K?pNgXe~!yQH^d^t7B -AU>G$~&R!@h6PE9W2)3o(~F=K(%|CMJlrkg5d7_W|A`&99Swz^{q5Wqs6rKy|*P>@DNP!YB0{c!-Hci -alIH8Qc3vFgT{n`O#Q>I`Z)APuo}Xh6$Gbi>PX6N9+! -MxbJVuNt)7okHSB%RYHtmq={0N1oLo65Z&JXLX0fHhN><&d?Cg>W~8F8J$vqAWmW9w7_ApIM)+0D|`# -^3uP=UY6BTIUk2)Q)2)qHAWb@wr5%)1+jh|+PEBLmN7DlKbaWqBCkE+m`qWd;qyJU1e%n(;(-JMMmib -TE@=};J#QQ?g_ixNaTi%L685mD*jSd6+PZ>HYNS5ZFjLH^7*)fI%lri=o*Iv+%HT?O*aAf0xTGbSqXo -zwQxTgAG7bt`KT3y+VeEvj12TeGDK~M<+&>8@rO)i6&Qu?J$gotgOqM{FosY_Hsy2lt;D9uh`Ce5aQ< -!*>Bay?Ia(&{dBC0yg1TlUildt=-vmbi0zYkkh|_)|&H$V`R}j2bLKUAURI=E{?=Mn>4PqP^-F*6eA! -YEi~3_9ag5-BfxW82>eMBi{|d!D35L0Rf)XdWXuX;Ek5BQ=Wvb$F20}pZ`1>GyH)E3{aq894s!+l7b<8i^VOII9ccOLYj8Vi=w$J7GF}$LXt-AK_Nt99hmR3^WNK -|iK@Ht4bya6pb-71nX)nM~+2?8FUYc0gy5$)CRAiQgWY|J-K#TI6%JGe?O1au_sWm~xocNoY-U&Go;* -;uuS~w&L+K|G?s$*B`GMamEWd@C1wY{vK&Kk@~fIo51F~Zf!JskboGe{o+O#7d5+N`m%L``X9mMvSN_ -sBSSt&y>Gh`cPZMhz5L2jT4oj&=Z}0LySV*|KfJNFrdclM=_umPQP$5^PX2PtFj%LAoHT7&W%~xMzVl@Rvym)0v6{)8dr_S;ISsu$O+xKCKiFlu>A#Trm{#i428&JbK -?!AChx$$JA2v@Y#{x+Iu}`mw8QDm`YYl%bnN~Su#qJZDsW57xB4A#pVdugJNS04HB3`vR;Eh5JS0LGQ -uuVC>6P3{W8B{peh3nW6nqX@q3YNLoPAO-3;^`ir8=ODE8a2{X=CSt`cBX61I~#p#U>@0E*HVnIGp_# -R36%tEx*oTiw~gPbF~6cQd!PwS(~x!DJb4k0H?52x{FDVi`fCQ!GhV0A8_~j1`64V{j&12au1B5whhI -*B+6Ki5Fycohi%UXa&OuiH%Td4Q2?T&b}hwjj>8>&676UH4Cc$=sZUlN`&kTWp1H44Te@4uQz^VL3=2 -_+A!=AHU8*@j=}m^QK&Ki07icoMUQTTdvJl;fBJ~whl)bM>&gc?jpm^#;E5@)hBg^p0ib11GzC!O=O) -doF(=9L)RdD*ib|AQ{U?V$Zh@*38RT@6iwq+%BCGiA23G}d9~$A;BEQyhPOpMDT8@Bwwj4meb<2t5o- -IdC_h>m(BAeajt{CKk2n=fA%Sf2`IS({*)~HGrc{dwbb10CV^p1RzM(s^u0!*JU_YtW6T -c+H-HyQ0_5)645L`LL&YxvJ-SRH8cc;0|u!@jV7B}pXJN(m^m47R4a$>B;^Eh^fCmrAY}C?_1~h+wSy -f=AG+shg3+MRT4AS`?Yg3@}rbi_8WphVh{+U}Mzj`20lv#9LLdBj?W(HS?A}{mz-#oJPTB_fDg$Fc<6 -ktc!U7PX@9Cl?*kPD29Y2HJsX4Jw_7DGXYxW-l6g&$MSSs4ndqZ=_F7^>mRi{jjv1Ly&IJt%VX*$!ov -R(j8h2ftl%!<{zq;>Sbo~TrRoO4-U~ -R0c8v%Y59Tr*&oNl(L=N7kzHA1Q?mJENAs&6u+mb0`%!@$9GWM<(LbT|1!ecTvnhKz -MTr`pURk#I?cK2f*gO;=9&^sc{hR;faVq -f9yHpTbiu%w9c3IhlJMInlyfv@)@aQB{eP8PEft38$>=v~lh3_aZcC#G>Bv=mtgF8xO%1Oe#>Q!I2a6 -H%<)EZEaknv6n)#`SFqVgS8lEiFk>gN-;~Z6eErQg;JLZrTB^~J?kg=7Yh(}Ecm1?Q$n{l2S7fjW>CJ#rhr`HTaZw8o$(tq{(E*8xEkZfX*+RYGjo9M@mFF_(kaFikDx3uY4Np6Hob(C~62{X2%Z{a3S8aNhH -NG)DzE8fBTRGC3R+l-&-^Y!dPBGo8&A@Cut}ns0A1V^YuuB9>yyT-*QSDp{{ys?%iWc40=U%jeaq@#p -pjx5iDpJ(l^NrVo=Ca{01kUS8GX;sV%;MXIe#N0qIh=rQt_~$)JEyZ2`82t$R_$?O*w!hH`k+CUH7rH -aqNVP@Togr7eOQosQpo3?hsyK1TnDBx8OJgDgr-jtt+pu?Lk3ZBk_4DlROK2LBIGO9KQH0000805+Cp -NdQ)?I<5c!04@Ol03-ka0B~t=FJE?LZe(wAFJob2Xk}w>Zgg^QY%gD5X>MtBUtcb8d2NqD4uUWcMfW| -$q#JgPu3VXDhzk=v1DVtT8lclmi^SVo1QHi)|9gM(M{8Z8ZK-6RgnMv9U`cTIiKIqLMyV%u1TFz#{6O -MOsf7oybJwoj$)%RZ1U>r(JPs5?z7UPlTC1wUF=MGJI74w0XW@Yo*%uknfUaK%WpZ)sg>7e8ni;=IVv -ZK!V2(#~DwO&SFq2>iB-^V0XI2AW7}CCRUC=77fAsrqj@1`XO9KQH0000805+CpN!nCKr2_&004N0j0 -3ZMW0B~t=FJE?LZe(wAFJob2Xk}w>Zgg^QY%gD9ZDcNRd4*F=Z=5g?z4I$v<$#e^&V3~gX;L+ZbX7H* -vt_|7SOwd)O``VKcl^b&P10}y&%Bv=^PZ8j(bkhm4}(-gRh_J!iIIl!XHdPiL?(cRUkfzX`%0M8vbC$%cWi_JfHPV-$Yh+s22nyx$FU5 -TS-TtqSFP3`Jx#s1@dX-|#gxY&@u)#u`hACTpwOY=gJrfml1}1f?S9ZNaYEih`WTm||26M@6aWAK2mm&gW=T-ysV -r0l0000I001KZ003}la4%nWWo~3|axY_HV`yb#Z*FvQZ)`7NWMOc0WpXZXd6idDZ{s!$e$THEt{CDJf -zjgjR3HyquXh`;4F@cTdpHb+Vl#;l#quP%x$M`El5AO)<#aPp)Uim46Mbz!EtvbUZ|hPE -!PR21m*Pws*5t)2)RmCAguh!*TIIB&jRvVmr}WEq`}rsLty9ZOQEMqU2iXff)A9(<{&O#9zV;#?SgEa -8x4358o2>EEXlFN{x2t+{pshR!<9p5364(XBE|~J``urM^P!VAqsT%ZTb1jcDW -|Y4==yHY+sgttrk|Lf;}k;Rx@1`nF79F6YKZp4^hL~ii{q+Sb(Mnwb{xxDdH1RSm)uniHUHP`+!6JK^t -FaoT$ZQakXzb3wxlO`kHcHJ9z&K?gcES5PQue3}uY*WH8-=CQQKl=O2@>z#z4PyXqBxsUK`!n8L4-!ZjVn6*`Nz)-a5-kd4bh+0|23y>P1GNznRohRKw(cUFjF=VqL%TF) -c<gj -Ao?VPQfwc!+*I+5>(h(o$XR~P`t=Fx?XqP0eO6Q_yHTGgw+hL5;)NAJVsb*C@uZXG-?TfpBtQ1dctF^cMqH(x&hGuoAX -{L|u;HrX!^hkTaINa{U4WQ0*D28weJAbDSnFA#-Io=%vRGf8WX*D$RM6HuQFq=ck)K`!@t`nV52O`X! -`Fd2H(wROwb>2a%%hWJa>!P#+M`O^~d_`jq(Qu@|$1v>iuQX%}v~kxgR@Vsr>};(Kk%;bDFgyOGgA@N ->Mv2n93dZ$^%pu{ZSmcRO5c@jXk)h1K=H=f7y0OUn>Ug -6^g))#T!oxN;P&bjv0RR9N0{{Ra0001RX>c!Jc4cm4Z*nh -VVPj}zV{dMBa&K%eV_{=xWiD`eol(7RgD?=@{S+rABBeY)qzwI8B1KV$?#Q)|;Kta@0zaAc5+DQi~`n1mhkWYrIK{2uQ`!#y0M}@d@L$Tx`Zypbpq6vq^S*FJL*9GxY2>mr&QMw2)ccRr>h|E>G(#E}mWzaK-zS@x -jkF}mqdOk>W-r$T)gpDljT}W<>|5*&EbjQrP(7L)|IZ8-UCS@bFHEr+D6Y%y;Wv6yrDcvj3e6Yh9$Md -~fGhD0P)h>@6aWAK2mm&gW=VMxOYqJB005u?001HY003}la4%nWWo~3|axY_HV`yb#Z*FvQZ)`7PZ*6 -d4bS`jtl~2J+13?VE&sPMzRFJ)RD|i(2BBk`cOlMP#o!!JFQ?`Haxb9L5dTw|*9c -2UwMX5_foayL5huJ^pBXMK;$;`~*--0|XQR000O8HkM{d$6&eM2oC@N;4%OJCIA2caA|NaUv_0~WN&g -WV_{=xWn*t{baHQOFJo_RbaHQOY-MsTaCy~Q>u=mP692w`1y4q>+RLu&*nzKS!0n|@0t85#B1x|weC~ -plxVx(Lsz~|K!~O3!GbANakB!|H2iyXQ^&p4CdH!aoAP8okcY^JdsKTkOfvJb;=*FJ`kJKfZ$@wTR*}uhL9}b?|c-UjBS`_|3u9^TpZXoAB@u?(z1o3%I- -dnXbZr;iY+Y5Jvbn0D7}TY_urWO`V4DZ*ewb@B?}OfR82P3>R%A@Wd4Ct`=Ky&$h7hf~5s}RaGFd^qz -go$_@MY_JW=K{j1YxMpW<@N)SOVmW(%Qhsu6f!is9QWFX-hZZ$0gNi~)9{oU -3?8Vm$d#viFk+wuC&AJ(l0qnsNc-I*=Wrdj_WspSInn{?Z2>Z~a1Jd88BWSwOhF?HQgIg+EXkXohJ@Mo%5Rj$N`Wo3VKxyMcWtl|@x8XN}Q-~)C+Hk -p92J`{@Ivug-r2`h{mIBp6(SZm`QyA~jN;mpT~0}<3jfh666rAJ^40{+D``!qU^j*-SEm9W#(sGV&5V -y#6&`eD3CSny2{{Zghyc5iGL -Ap~!t*19{@cA-=giFN!i?!6N{5w-N4puMJ8+Zj4DbL}j%G76uA=t&4;uoS0e}rsl2%&MC;OOst$^Mld}4HtCV`=Xe&~jhmtbB32l>%*) -Gm##JN%RH$&ioG*qm)+nnruPN+ZdhhY4nhLV%s%> -;>iTD@R@IoIqA>x5Pr^wMLKRCG0&-pn$eiT2gd7$BiePKTL`Cas#MbDo34wkHqq -Z;QhcN5O>QB&eJp#m1GN&sNEmsBxvpGw?V(fQAkm2h3(LCEg&^Ok2)jQc_B`;_w9T#3QJFc`sgw?hrt -qC{Lb+i?EAEN{?3n~DV(^<94E%Z~WMaWT8lSlF;j+Q8w)@^1mova(Sq0gcHt@JIoZqAVa7Z$L^r1^}0 -P?pl;9%@Oh!$G&U%8~SL78h_kz$pSk)FdJgm4;2Jak@Q^76}IQ!^PJzW_|~pZX&{a-+s9XEnnzD%3ud -ojcEI4~=y$Ppz${%#;1V&wTa4ld$?11c35xXza5{vIFK5Otxltt(Q!B1)qIvBCP00oIALS8(6IuIoUjCV4m_*?igz_1DcK=I$iw -(qBWa)&0&J_14P=Zk70d>VMdTU;jsTP0=I@aSP@QfuNC20O+Wp}?Sf~(y~oyk -h+mw@sTUgyp2HR2S#?h>`Ka(wly{&@>>@?@pHra`*XM1+iC%B>un*(f22*Iks#dN8933>!)QHwGj{#} -OXdz@WWMM_iO~H-}J&oZsUD83w#0h8YS&F!$BkMtk&mUfYIAfn7dI=0cpnFJ`O-{uvmx?;;SUs%rPn8&73o$<1eAel&L7A`Vd^zCM$#e{5RnIDVlt_!F0UapVmd3+1ksG*g-UfC0wV!vq} -y>OE)B@ETr@B(!_a?&5`@1L#Z%?hq_SG?M_^;fD*+p<^Ry5eU=>0-m6v_8&eDxQn*i!Z!?cTnHFyWM% -(prBz+$iXn?z4`2k4XL+QCs#DxpS)VrAh+g;6FUM}*m{OBj9@P}=QPU$-^}2Nkjfb%ZCWTTOJ9n$+sWMjXOtv)+h)@a4N+3~4?sc642n2WvQRt -4hrJeQeXwoJ61q~pap<*UIV2|++Bs=K`h&l^&|Im)T_MGVnyQ~FDjU_*5 -J^QP)p|O$!xmxj=Z-3Es|dvgRo;ar#;Xkn5j4Eca@x+`TV>izbA0LqGcc%xQpn}WjFREolT`w)2A?X% -Eo|uyFQ7TB_81o`YkxASKyEKO)($W#wOxI{)lFq8&jUJDA0L@hE! -7}Zs#6f4*pf}wFHIF+A(P(54yGP?D4FwhlAXwWVKK>?z`(u>L$z=9eLR~n%DwS&Lcy+G89Xe1Lwr!bI -z3#fy3kfAdtKMm0)HO9&!NvW}$nTDpE?2bLPQ}wtCezHx`5h}#px+`lO;RBtCOP_SAE+g>|nYM -CbW#xds3wNqzU3g_N*oROpaub9uva>o0pAF)~)-P`IAoV#y{7bI{Mp?v{T43tO+ -8;V}-Rht+#gZ1E5eYe%=_@m^yLzJ=(MV1z8tI(|kC@9FFJ<08l-S1vCqm3ZY&?SX) -v-q|z0T>BP2E)oR7o@Y71hs$&5A`*y_yF#Ms3Cpvyibr;HmVU< -74d1UIysMXw<2n@}9%;mngxCc3Hnr}=z3M)$`~UVf|+-6GH`G+z$cCN0J(Jdp9vV7v+j^EsM}- -|II8&5q+2E@&ZyU0|40*n!p+s?#i5({tE?PV9~q#W5vRJba-s^PUQvx%jom9;DU==iZ?VO%LY&fdVP= -&s%x)G-zz$mMC=#r$EI%d}}*~bhOuLRwHZFL|H&`8(klNj|FR^k*?@kqy@VXqVj4_cb|jrX>h+CZ0Xz -kz71cPJe@T3J<&)v)Y%i1*TX#2?r@k@`mir)^2&jIJ#0ZS!YASh?Ny?WLs~cC#RH~WX--dc6|X-ZfnD -lD(Oy!($*-}8YDCk;T=$#jlc4|PK4k&Its^Pk#1?(yV7$3#!|=J?@@_IqHXEiSFvHgen8{JK}*Q$RAuy*eh@R?A|DwrTKaG -gBw`L9+&z8*hl`~Ck@7}k7EMNQ}SlbHRrpI`8OKu~^i^b&PR$1~t -l@C0g0Y`>y}Y%rn292|vV(W$@m>bl{kJ0<$`7xB|lSB5BWdwve!w_iOP8!GWjNPBRI$J{{bPDdn6u9C -WJ1;Z-b1F_puDPgz4AGV5hSgNFjovw-{J -nq(=4jI@o`5h4)jnwPEn11$rTdKmmh%%I7$h~{s1-h2;i&6WuBw+Sr)RY~K5f4~cp1Ius55xLHW0o0D+bBIHjpaC9s~H0rVUV_Xo_xuJ#-6!qDB_i6 -e*BY9RGdaP_`_)!2;dG`jFVx@XebyZ-#Dy@5p#EUGGDppAL!RuP=+mLR%FhJt=D|rFYN9@~5Esp2Nyl -Us$L0!-DX-xw*;zKX!>l(=m3 -LI1_yf(o|(Z|&GGnOZ6+i_4H4%%?kWy(WT2?_LuHQ;QG0-|%5qH>^$K1exG`@OQFqL6&L>H$v^F(3qn --oBR@HtFEzUfPN%;Jx=7!4BIFrh971=g-Q2lO_{1{5ZtNMm6$iX=f -d6*XUcK*v%_ZIO5t$+Faw-)DYA>`ckj-!@tfv8UG?x1rpH__{zsOP-M44BrB6&>hA0-2Hfp8OaySZ|c -g1{^UVeUgpvSt#?&ui)-EPG+VvC|Eu62D@Hu6l%O!Hbp(_GK_CDW^itMe);(6>A&tM&S-7E}kvi|@0S -MgL6l4i41{8bBgi$BnGIw2_CiuGcE5^XS6DIUIrixfG<)`+zmIk~8afd~-3O>- -XF<8gFKjqd^66=Fg}aE-!h1jq)u1f;gDrGi^5XAuBx|+)%ar96r0{CBa)7g0-eP{hWwn6Z0<<`61xr* -rTUEe|yFSk>9hcuQ2jY3^cGE+^%?USDY*Tp3?pcP)h>@6aWAK2mm&gW=RfI9{j-t001oz001Tc003}l -a4%nWWo~3|axY_HV`yb#Z*FvQZ)`7UWp#3Cb98BAb1rasl~-GD+cp$__pjhw7?wR-wqvKojOC$Q+oeF -$Zmol2C<1|&D4PvMY9tlME&AVgNRg5y=hFB=*5%yzoy$kkEEiIv7&F2XEIYJ()-)rXPAWx)NQNm33r( -3q?rSP!hIN898KE3YMOwF!{^pd;ld_)6r<0Hrk=vX~65&)68CU}&YxV5C;AF7uAo!VQFlPURtel2i?1F)nkVF@fmlD4s7l*a)g(EGZE -y0QLqMco*RWCpAxP?Y#lYywc(?!lmFc=KLXw0xu=nt&OIVmJnn#O@xe{M@8Am_a!s!%S -)4NXXbv=9h0CU)MHLX2J$G@H#U<*nBFaxwu?rb0?|8Vb3YY<0#aGL2uH%oooTteXlxJ`GR91=?+iBxp -wvCnTxNXkoIkh{O%7?_`0tqLA>!lJHK9T(0`F*%XaY_v3+uPa3k;?bqNJxvkm~JM~}=F0 -H7AvkJ=Tp&iv*g!vv;v-2+xpnd;g^?C^an+VQ7T)uk+c<%5Xx{AlFNC;8_8s&2{ky=#xW>2)9OT|qHBCN -n@(S<&G|3Jclf{AW(`F@_RRWnW(`||jb2m>Fn!)7TLn#_*tD6?!n}iK}PidAq>Aqqdlzr#2Z^5Jd2&p -EPZkSi(612s?^v&!3RWoOPU1v?ksAwmE<*KvUV04tYJARg6JTnACbN6NW{A$p2S;gZQc;!f_#V79bJ< -7I)$FbmA3T938^I{Ftu-`5Vr&3kL>(cH^yU@qWVmO3(J{*Fz{|chCGQ0+y0y(DK417b|IgwN(brDom5 -Ji+z9Yrqx!{PPrAosSXEX)vo4Y`$i`q;fEm9>+>(Tr+9TjgWKh0)6}J -$l5-1T6xFJxzHPP6ui8fRV6C`Oe0=$7l89JMK9lw2e6{*}V!Ff(=Ql6^Ia -~_6ZK}Hau_O%QBohVul9)_5(K{lqCopWahy|M%^$Az!@aI!}4)}&~owT9|i>=_y=hQ*(jG -)9K7=U;Rwu-Z}owhp?Je{(FN6Xf@2*Jm1)Ic)ezC9%uuhCHGI#U+Pi%Gj43$C6b=0Y_mOx_)o=e`O4e -$p+o!knQ5-fHCJ2WQPYko+h>x)V_DGV%zAVh-;wv-Alyo)8QuZm((>G7=7`x0nYA>{xet#n0ZwR+F>L -ff?IJFugw1S(H)f3)z73`$D?YZaYe7#tHlXnl8jZMWtrW7o3WR$em7%abOWAk!Cc_!y%3^X -YWZK;RA&zn>k({pjjzqH_n+W0OZrcR+3yHl<~1q4THe@~9ndGW*uE-QYp?I_P8P$7HYbKTt~p1QY-O0 -0;m!mS#y3VSuc!Jc4cm4Z*nhVVPj}zV{dMBa&K%eb7gXAVQgu7WiD`eomg9s -+cp+{_pdms!9voks?80>b^t%LX}X&NO^RlaJ{X3FmZ&i^GNp=?r*4t|-g8JwrY>V=YXlix$dBjxopU7 -ib=7FtGR41swGj4BRNd7lRmtx9k5X(Gi=wIaP>VX<9JtJ@CT;j{EmvBlN;hq$+lDJ}J6Rpq?_u;3Rc2tF;CKWhGMzKJZ4cQvAW2sM>zwnMNpsMd#%0zQ)bGED~^0Ll>Rfw^e>z -zN_TWO7`t$$63xB1R1at-gqepGz?!;x#y71T -|2;Iq4hPT@PIM8?YUh&JR!g>Wr~-~H_RyNqQ!PP#Q!nHHktj}5D9-lVS)Vau|IAMn|)_Ss!Bi(!=Aqo -ILG;6*_hj6$LnN!lF;f<-lCUZNdtck`0p%^e78M`)R7vQ}tik0N`*_Be}zM`|)I9c#SFl(}}^1G8PKc -F*&KUd)1^cUKJN;NMlj~xoPrvSfG>a&Y2KK3r<6%4LGIGQ -vOY97MU9avs`NWb!ME=t64DFHoN0y&{v7NMy6wTu=#6R|z|rZgA;^dvZ#<$pZRCJ|#ZH$$%m -caXh;TV+bA+IFPNz$(#z!_bQrhoRbhqSGOc!k7A$9EIlB$sHDHvJ(^jJ%%IHLF=6}Mfh*=^YS3Ckndr -&P(joWwTZV6jTR)#}Kc6Dv3SQ$*Qb}1b;gu9to$*o+v3uDcCCO&-KR(9>$2xm%9A71xdNA@}~B_j4s_{MnJ*zcofK -jJ&AK$iOX5z`(EE=M+UxttRZ{rKViyQd62&7TW-L;g4r70F5)F@a-Ug2U91T^{)-PIAaovg;RuBuGD1 -LPo1$jSRAE`63ub9QA^xheJ=RDGyNFnA&nbo9-l9g4U{8!*tcCWrT+(5ZnZ)x55grdEiI2ylq-OO_F% -jiVatIx9j-Sdz2N+C%p%W;WZmq@WHP>WbsEHVlm!wF5w=V4QVRgzSc)7JWv$|!cQ6U4m}%2tu?V9SKo -8Zo4r6cKJKs&cEm%7RK5B*YRs+S_D77UQ+#<)k3#SITiqN-xk+K|iUa*|DY!KTe|`!G{(Hu2jX_?v{7 -utT%>)JkOe{PLd_{g@54`Jvm?auw)__8l;W$8j3D(q3K~oPEwK)t5>a0Ady0R@Dm=S?N;wm!L&&~>%> -zwUV-{c6rx_P;Ve>nMqq2x|?Qs7@hcb(Pizpj_*1efi8i)*V^cTU1Br}VZs2Vx^<-=(qd>Z|AL=?vn< -flxRHWji7i0<|VH;R<`McU5k3nqJuebn-myaF@==YD`lzZ$duUm;~4y{(Sa1ND*<*bc9fG91XK!91q_ -jhS;`xE+($9x3g{U$-j?$*u|P-p=f2M={25A2~K%cmLsrFEjGO5Ud%jA{K;T1aHgP;aY_;IJ(*aP=Ez -6LDW?W(objeT%e|Zrg5V=2RdJk@NbGRQFLZj=*HMO0=$^JIkpN%zDE|BkcJN94BYcXfd4@TRk|05No-?V)m{?edQo2m?fbyO}go{U`TVH$0FA8alZLt}*F@#UL;x{Ps*QUnX{iYn+s*zwj5 -MLs)H}aJHB*magcA+~K&6*Z1?g2T;`X{HJAaSO7QpT>j4P`~mE|z*C4Ff*yGRCS>PrPZ^Bc{2PVs+}S -*J)kVk?p6w^gchdQob!6RMs9Esh3nY&lCMLk8;p;FH(!}jh$I;%5QZ@&Q><@Q*gp%!YZb~G7EvVcFj`mGjH=ixb6QiI5TJ&H$gW+H~4kI1MJUZn -7Ce@!JNqOpfAa8Zl}wJfQdYo*1aW^M$>y6rD%7euWTrcZPb308H15pIw&*1A+@TO8w2+!0v8L}{w{$O -bd>;>lbP;#nb*Ew?S9YHRZ;%?!eAQJ7X58KPhr|3c?ZqWi&BkqNyLdx%1cC?{m`yU%wWH%~PBpE|u;j(J)cOHt6HlV0#(Oln`0FI>& -eNW+p{46J`^_g*uxW8>xu*fUveZxPIQ?2WqwL&XFX+ -!L?moes8zQ}Lx&0vNJsW?7@rTB*6qd(GLIrP(3{R3&!d%OryU5P) -h>@6aWAK2mm&gW=V^C#X`IQ008m<001KZ003}la4%nWWo~3|axY_HV`yb#Z*FvQZ)`7ja$#_AWpXZXd -3BIK4}%~O#dkgho0u5gO&A(Gck12{JS&NcIgX{Dz7hYRMp-!We)sNi4ceaPs#YTba?_6%iC0RuP}zad -c8^(NO#ti1$B6Z*5`b4k3bBe2UG%P7aNTQ!SwsA-9LZ~Z_bX^F -Z=1QY-O00;m!mS#zw9aj9y0001S0RR9i0001RX>c!Jc4cm4Z*nhVVPj}zV{dMBa&K%eV_{=xWpgiIUu -kY>bYEXCaCv=@F>eAf42Adninm=^_yI_rDk`xQ39)plqQobx-X)Raqww#W#J(r^FTk&x)UwJ&q6*bFm!R06uZW^B2nha5S1O_{pR`BbzS3M`{Q6W2IloU-C%&(x-d=zePoaL8(hf&3cZMdfT*5Lj_$i^OS8fLh+Wi$iObFsoK1;gW66x-t) -o-}V&poF6XH{?Dg4RP_25$>wbS0Z>Z=1QY-O00;m!mS#!8rw0F91pold4FCWw0001RX>c!Jc4cm4Z*n -hVVPj}zV{dMBa&K%eV_{=xWpgiPX>4U*V_{=xWiD`eomXvd+cpsX?q9*VA1Y^t5_DS!7znT=O@IPv45 -Znzq9_DfIz@~nQXuI#9{S^VM^X}HC)+T6u_)gA^W2dZO{=tJ6*pBaRukcu%1kDuRt;-Kn`TcS3#HRq< -?B+^z=F^qqY9WUCJdi9>>iks^^R@1w9G2zr0K<0!Z6aBGou(;V!|>ZgM?EeOU_PN^9RFDv0~{ -myT4uDTz{Da2kz6w?d91W`|cN)e_mX(t*C2Oa#1r;qAzf+7_T+(V#nZF7;Bv8t@jnIc|8}+k%aA$Q#0 -w1jA`dx-*n!|pnEHXCCdtS*9 -%tQ|{pF4-eVn6+pbuW*k7r<5?*-X0OHFY`EEVj9sEN%v1(6H^#V&x0||a|}YsLRg_BBGRID>H)h2bVJ -7bGB_$ZIFW76Guq-ZSOO}6_q77FD-0AVqSQCj#)?|}g;rQU)toU?sZCwP{pzEjFgw1Z7%~N#)@XmaIRsj6cYh*^?^nm_qeOoB -jh$FQ(h~BTq;%Mt^1IcIndpn0W{Nz?~Vpii%OZ769pLkXXr -sFU|6C-dH0|DbLs@GA-seKP!(5iX}Uim;{E%!TDXU>v;}ge6uWY>3_@<`^=l##@U~7%p-42d_8aS|^! -aa7m)Xm67#5i&i}VwJSpOT0mn@k?!gK@fjHy2Yndu$jqSy!X)h>L6QD_EE1qXf$+As9iXHA@Ot1P?1# -7U_9)6>G4X-^W-PZNZ?J%>=y?S)M?1;$?4U2g#U?csfA{X^9JV9P1iU_^ksbeh23WAl+SV+Nj~|4sHY>d4G{-o>G$wuQ4 -!3aaITprjz#PB-sW(G;0SydSvyjv<-sx$%wyuUavJ)@=b@9u$8T*la^~-NBOp%t`Dt%PKmm!T>^vD^E -YK1HgwOYe2Diwv%MMm|c-I%9Tug;Ud?<9N0jzS_D4;Sgt!c3Gu#RK*CJIO;i -jqW=B2;3M=t@ipGH{4oSWi-e0?FF2af^kBkx*8x;8NtZk>$equAKI)F4VE^Hy_GovfYhS0mcJgkhSce -k^Z;<|Sizfij?S|K`c}FtN<_i~WvgY!cj|aeuxX^>3cuimX0UCeR`QMz1r_(|4!!5|Si2t*&(RF53M) -778e}W(U(UV8#)^s|(^;B?NDr|+Poa=Gg{QdSi_1U;7Q7G_dM}Kb#`kpI4bmrYJCWQIz{j2f+i -ZYCgV)F@i% -Zgg^QY%gPBV`yb_FLGsMX>(s=VPj}zE^v8uQ%!H%Fbuu>S8%!1PE#-Ih8zm?w98J#FkojvvFKW@ECmw -NHvfL4qxfT6bg*dhy~hvJZ45mq)gI{>Q7N$fAw&XW-)yqM7ey-oqY26OIEM3kQ4|JSpg`KGgiY`!m1L -@i0{E;}tDh(8f`|Kh2TkY?n)Kc}OD7Hu5ZgkhlO~Ex+rZ62=_c#QiO2UQ;mdZt-c|7VY;ebo(<#9(G&X(LO^0RJzhFaL?4Th7Q -ObHtN|gy+yW!TopY -MRj#LC&^;fh(ece$;m?8&&9eMcck{=y{xXMz>jm1<#)|G)tG#2)SzS1f5$ZGX3%m(jYAy=;*7(a_ygS -mBeWr&A=W>Gb`GdRNsz#L(9)0I>x4D@P6+Rox>2LiN8+F5(`BpWh#HfHJC!ABWp!=dSBmC+{=4WI$0o -UX|9HlK08mQ<1QY-O00;m!mS#y<)M+`h0002+0000W0001RX>c!Jc4cm4Z*nhVZ)|UJVQpbAUtei%X> -?y-E^v8Gjj@UXK@ddq`HBVulXXxxFfuq-50%4Y1;e3PTWxN3T6)^M+rM{3g^P-M)T0Ja$+c5HUktXA& -@Px@jcSoPn2sl9JlV48d^sRjKXfKBcaxR1)(#(=VfH@4uzVx%9b9Ycn0|XQ -R000O8HkM{dJ{6~5ivs`v+z9{x8~^|SaA|NaUv_0~WN&gWV{dG4a$#*@FJW$TX)bViwN_Dc+Bgt?_pi -`Q9t?9FvB7Pox#0nfo6r*I0GV8;?RXS>!MYmRCyAr{`&-El#&EuA9_j}``|103t^L;KiuhT65Nwku@* -(3?C6tqUDpU#w(sSH?myDKU(*NC0=}rPkSp-}{he!Z2o%q3Q8IUzgp=;T;ZLJa}$u^63DUc0t5LAIA! -`X5|qJ#<>DG(&eVhpN`+v{uK-97uxa)=r2W@2-#SJzrM1R2pB -LM>`auzXB5Aof?M-~VD;^QHf?`es|BU|Ou48{BEdur}3%DhqXevZYt;TZ`Z&KJCG7|Ml$$aRKg;~6Z#GJ)#9na8eBF -Rn_WTz_s^n_jFl3ymFlNR<-od8Zq_Pgs-?4e{Of83cB*5_!xsP8 -gSpV$-Q&xY~nGj5kuoBREm(oXD91=oU`os5H@w8t{|s1kz9iYuNpmbeEuVc9Fi@owg5l!ysyv -2`%FOVI3T6kXo@ACbZkKSjsX_IxYkMpTm#%Kf?!q$dItC$OsDgw8qhCuJO*b{o&g?)cfo8mJO$nu!2d -j)oiW}T;OTy{3hKa3S?)4e)q!`GL9WOu0erUWL0eNM(gO}=n&QOgc_tLZ9WzvV0CKcG$=1a@;=smK#K -nwHCwKQNb5>#Sr;&f>Q~;h;5jKEZ0p_fZj9vv4XJrrs`lmS_gHj6tuL8=m(ij47DnOrA#duo*=B$p%Z -)#o`(dhe6c)@G(g4g5)ugMEulNY=uFL+H}@S42fHF?4NwO(+YCNsEBlN(&8$qug5GFZMG+Sb2NO9KQH0000805+CpNylTr*>DN~0K_E#03!eZ0B~t=FJE?LZe(wAFJo_PZ*pO6VJ~ -5Bb7^#McWG`jGA?j=tyqKnX>G8?yF}t{Z%U&PN5?(05k~k7li -GV2~*wy81etkP<%Qz9^gLsprxn{X2uhdqRm#I29Smx=5jk#1YvrngR{bQN~8eU!S=$pB^)!PIf(vLNJ -j-?j)rnJ0|N%m=kuLO7MDV+p$=eR6#)sEfL2?*#^|8uEQfoD3p2MC -3Mio0O!5s}rb`E>iV~hnrq`nExJ;o*^SClW@5W(Rwwjdf+YPMSkkCmT!hsX*N@(o73K*SpLbOb=t^8) -Uwsj3>2EqVo@m)JWZGXn5Ew!qsg=Kq}-eb}A6YFx5C1L{^MdDQBUJ`*uKfnwE#3FrGSgTNpki$4*)9c -$y7Oi;>YfT%R&OZ!HWn;teO-(iX7p<0Kk%bpr$_&K9Krk3b0|$i7@)Gw(}dmH2 --qdwFN(-UM*V!coelN#i2aPQoAZ(X_51PP8hx04K0%FhO2F+_X^3@}ZZ?pegMABTg8CPjA+XoHfbkbf -=*f>fQKC^$cU|iHGR-%T$t_L=VqcKnz+JkmS9K4COr&y8Uz?iZ$%^Vnb<91`pJ6#A>tEAzef?5o1Q=)~FH7s;zK -KDocTyYenr5W{jo>!KTL}LKK)YE@8&h%>1?;P;j$BDkPBCrfFQ?qShX7=*UsKX@chJJTMWv)ssYlVlZ -W)+QutPx*n(;H#=az~6#1OqX4;%kQ2?xRtDi#P&_STb5B5+_&{OR3Ig>|rqhB?IhIEe*l8A;pb3#78g -(hR)+ARj^{7MP24bn9iG^m<4)XLY7JeS*+EpmR2)9G~1H8I!am2C-`@l7s8Y_77tCNZ~?PiL8>W*Scn -Cb*ODz!Zv!kBv&b;St(`RDLlYbh5R^GzE@2j?sUE0FZyZWsi&&|Glqs!n+$^A2+m&$TQ4So7bM)yBT_ -)6(kTBEW+b2;0(foi)BvW~0)re&Y*RVp|fOHiy!6jju;edh$m1E13SZ`sM@vVlcB^y?~&I{4Shc -U7a!CVvNa~kmDX^6b#s2++4nUf9Dt~vz010N4CKr#@ow}0|3{bZaz-01_4ZyKZ}7I!tuq`boRkPSkf- -B-0YdxL-sfGx4W(3<&f>WoB8~Dw>7;Uvi~x_y83j#8^F9Dj{fs}DEQCwA?uIRck}DJ>23hqUl4M;MG1@HGnBEk$}QHwp%40{IQOp`tFkW -DWn?elz3EK96GINN4VL}c0^95&b!i&**6ID8od!y~GWHa*d~)n3{}_DyYSS?|^+vTbWq%X&zg$iAaZE -r3C7ve7rRsT2H8Z6fQgHnjkTwTUoV+SCFV&?d5OYg0FayR^v;UQ?Tl`H;mfZR(@V%jE0VrY?#0MC#F| -K1%J0)~8Lq1ltp@t4%$0+Y_r-oBGMOCt_EddZ@T3R*yFI5VIHTkM;r=w^06mM|0n0`L7X7_uv{`B#9PVtiObWGjmhh#I)uOzogKBw9huvl1_0!?idJiBj^38hR_cmU@{Nr -Hch6xC`FLIK%b8GAdH+nHStu25XPB;0zW9n{s?$Lzyi>nyVh_IgVH`*FFY69s1rJaE`rZs -B16XZ@l>*Yb4LY&z?6o%&$4?QB%!icY(at4HMTq%l}ws-hN!@T0?qYy5m*w|CB2?RW{BPsl>jieaI=rji@C|7s@O()g+R;;ltc>;dncY@G7^(|JThrV& -D3%+IHI^M!IuJ`uPaNV{*dV24d-VX0|ktM>1n(?y{TH(0D)nA|>NLbkkF+D>Vrz>D=5^LXk(4O;6%o?XUl$|usXuE?~I^(}W`+&WAWsq7^dV?pe)RxOKk#i+N73HF0C4_~aE?;t{)5V9u -E|OS^|FHwq8hUeB&MsN1V*0>&#Q2j+lR}GL7jWTZOv2K{`tqk7F}1S8kz6EP!}6rzh^$1XO`1bC1*0M -h*aiG9(q|d48C(JPVN9WD-ogzoo*P!*1GWXo-@GaK0R_&E4 -<}fw_txUzj|rbai@zP-u%$5?n9U$kMREo$Q3M1V-2$&bqWcF?UXQIDN>0GRHscJ&Pk0^SufJ&XVF;oS -)R5$*r~e6XUfO%>5kdMtwF*nRp7)~W!bp|2kYQJP)h>@6aWAK2mm&gW=Vb4*e`_w004al001HY003}l -a4%nWWo~3|axY_VY;SU5ZDB8IZfSIBVQgu0WiD`ejZ{sK(=ZUd=U0r>!=|ec!4?UrQY4@hu?X#DS8xD -Sxy__CICeF5DEar;&X<$z(v4Kro*9qdn|b3Itkkh4zCRfFtRyoBCgBq!Bd~^>m#-Enyubedix(@wDvc -oc4yDEfG$MStU9Io#*HCbQX~fH;A_L6eUv9eHPD#5rf^R#Eu5}a@q)JGc)ZC~Omx~){z3;5)L49+kD2 -kK?w$Ub6m4OWIl*HbIM4c%*5j!3+pF{@MFu1|D$?eA4J~E-LG0L$3LXBp_gox+M2<*p;5D#z-QW>9S> -D)88)w)8My?MO^wf%)VqoMSr+~Q7^=tFHmz*hv?FGv{&1m|gRe~XAqZems$Et@Lf|>7JL6^PnX>#05FB$A^ -COaa~U9BV=Vf;x(>%0zpIEnfU~;BlJJOfx4dr+{8RObQ!RXdj{aJg~5P@8Vt+>y2fagzvtQ@#r+7(Cn -4RuI&rnbk)t1vVOB$RgtL0Sc|r6vL0j-A6Ew9OsWW;zymV>1ESY3`BoB6{gd$e5jdR4;c20Bx*Vn&KZ -q-q6bfzxvF;ovAYK|iJyMjwws=(Eiq<_#R6`Bb4a*@Ud&ZK!>sV4K@pbOR3{9P8>h{<#q+)d%*+9n-I -yn8#x7iH7yvVJuPu_sQcKB!9M8@G*vH`DX>m-d`(tT*@=LQ|1!Lu?iP0Z>Z=1QY-O00;m!mS#x}ZK6F -)1pol`6aWAn0001RX>c!Jc4cm4Z*nhVZ)|UJVQpbAcWG`jGA?j=%~x%2qc{-$o?l_L+6(Ma)R%A7^+T -Gb*POCR6|!nqs}&+J6z&L)Y|}J%y8nJ-Ou!^SH(H&vsEW$?nejZs%y@iChIGAU@k=DgA2jtuiI;I8N$f+)!6F=hOvb~})ESX5PQdQ2a{5p0l`{eO{0oa&~bcU -qoCAp_2ENA%P1G>?H&Zog-;?76IMS~BYytjCjt&~=azu(IL0tkugVn|%q;E;q`sN}-{9jGE%!LaPZaz-^FST6n4{H@vDPYmse*sG&xw^FVmH_ZDQQnIl=(x=4 -**oBi)4S1-Cwc6DgCp(FX_Ev$vtp5bm{_i>UGdb3QdGcm+-$MXfSK?AoA^tyAosdi(ht6W&Qs>G*r=wBc;-&F_O5(hza)>4zGAvZHC -d&StZq%<)K+h)b6V+$Olz+1U-a8pFBgK&xcsW?k%MYZMZsCZh$Lm@wsXR%k?(PZo#RkoYlskeOHpLHQ -a?cmRhi<_?AV0_$3s#u(oaY#f26*%UEW`fX=FfV;U*8)mXDxx@ju}if$EEVi8nLEH9dSTY_bG&h@79v%PPiV6X7kE45rRl61Y%eyRLO<6L?#LFQlgNW^ -K)y9$t_+F}Bv5S?Q_3Lf{_6Qlo!HE-alpzMFcal#zNgYV==Y*vQLbpA+sgiZ|7@K3J4>z;O4D3MdM(o -Y`y22TaiMUvJQ+ycWEJd@hjkwxES{4m?`{CI?%2=rW_SN-?%SSRi`|UslMzTUxcWpZa>esB3ou>5GW) -aw0q?j_q1#0TF6`G31X8c(D$^(AtaIKtGG}Q`b=P3YTUpcf(>KAUy=1>m@p+IE0AE6ZnJKjeodva3(h -n@&wBB*LdY)q<9-cvVQ~&H3kD423L(FzvRrCL_xoV5{tCwWZ@v&N)ZTrXS_xoLTNK`CXd6(C3$=J -^}xG1loxJW4rR2^C57heX0KNX+E#66Bm4zE5hR%4qXSAOFB*p*;MKpS(TB>Y_o(sa_w>CF)GSNpaNM` -k|d6~NwqP)h>@6aWAK2mm&gW=VH@XCdMR005F00018V003}la4%nWWo~3|axY_VY;SU5ZDB8WX>N37a -&0bfdF@!;ZyGref6rer5>f*#;UMYts*|Xc5Sr#Lg%hM|^Mv-pgzXs{dA%i^s{7k-{IQF@ECkx-bPvZ% -cI^4~Z^q-)pvM<0k=)ND{Ki=zX90tEfsmA};I!R2Yr)0%63$L~G{{l{G>HgHNfS~+;O4s5AB_7jO(5i -taM*aRk)2Ev5-JK82{+Xsn{YxTe&yoIF(|7%wo8d>b|0>saXZU~F;pkYdDDfQ~3`g(JZ@ -~MZ(+}-7)F4jgAjyMl>aQ?}{=64FW3dTobba;iwnFkXN`~ef9ub~gV3w{%!w-W?EBx9wYt=Zy>tJq)n -6G2eT4OSW$s}QkoJ{H|p;H^A`YEzCL5?7!5^d>IZCw%B<1FQrj3Yq^yU3n1LG1R%EJ(?lOUP_N6n(?XhWM)s4# -J3jsYwJf&S4IRwN?VbuJ$T_95j=;^D_p5NhXG$*GmW(kA2&_-63~on;MKxKZ^APyZhzaNq;B@#yn&aE3a%U@xt4m~3R*T_U -!k<(6sF^{Alhk4t;?LDYDAal4mjKJeZRs`T#>23y5{bdxC`ql71pr&)Ha)-)7pI-P}RxT4(SEQqr}K7 -WGK1yhWdFm=@x4BVR$<==$GqinaEj=#IaY9=!N#zH}AsmYqxw91uMabwhKP%!r$8@Hxj+hQ}N{_T7k+ -YT74dzXbsBBM0-um?R3?!4*de(m1y@S`Tm?c+P*#gRZ0##30=7MDD<~Ii=F8=)n1u@|FZC|kS#5vV($ -R=9ZnO3sqj65(2UhVm@ON`x>K+F3eJ78cHX_gUFC&kU-70~e>5sM>|!x5+~R;z%mj5KYXfZfH_3vhiA -*@NIWVkFxcUA*Lx~k`#minMQZD|mvQC*?kVwzBAI;s4mmZD)3C_a=FKTRH?UOJ_N$lRl>gp`V=j#XZu -)skpg^rPKZFt}M1TXUG)?2^x*XajZ8r ->kxBgFb=j@S|xSP*%f#!yl;5SCh&h5rT!@vFwI?bkZMyO#GkWN+QdIJVYMsYPZPt=oZ12?n2o=QwkA+q<$@gEpiyp3xWwW)cYVdQ)gLXQGC8MG>r0KGm -Pbf;*ab_EHg&`-CwC_N|IeeMQEwOhnGx)ZJo^^R=V9-f4E8x&cRNm>kd|osbYfIu4f2)oTPpk7oGRh$ -Ipbp)^%bA)74K6d=^v)|M3yJ}d3Pq7%v&?7`#YFM^nQ?$LK8%iFDnL{F5tRh!O5xuqNPhS_$=?JoEb) -&)?#^T|CB1ybnZ8>loGBZ?;a{#YB_a__7im3*ShK#7zRcTyEgl^>lQei#7shQrZi9qX#y*vE%zi-bBh -Pk*${B5nr|(}dE^nlh7YNCWm~T53*jQNSX7n8`&ZdpU@T_H+!#&5{pI$9=aF7(Vb*TMY)}$EluK%zri -@h$1~K+|$)#er(P@<$-KJr?#H}-maN+o7hs{$BdAe#2)9KeH^))-qVMv?Wa|IsRN<8OD3OUQ907U`-03QGV0B~t=FJE?LZe(wAFJx(RbZlv2FJE72ZfSI1UoLQYg;Bvy!!QiJ= -PNAjuokguObDS$yD*BFG;L@dT&t{g+d!LCt_R!S<8~d|jT{pD<@xzJ8NuXc0;$W4EkZbOK6&gAHBuv3 -xaLHrC>gwk53}jrY$DqOd>E@w5bICV1VUSx6m5$cumdg;-cDRP3$e@jM-#$}JJ?LK5ljLk(9+K`nf-y -CWCbDCXt~ph5n;7lMC&*T_{gAb28+#lw_QHH?>5`G1BItd-8f>XTv3Q|rIO?usg)K<0&^HogIRDtRw} -J@+rqPHxib6cm}1k}@ctq{R3NJnmQqZobDEQ@FVZk{S|6(FRoqla3j1M<`#tgbOh{DBncEV~SGJ&^CKUrc!Jc4cm4 -Z*nhWX>)XJX<{#9Z*6d4bS`jt?R{-~+cuWycmE2kJXuk$%*4)2UwlWslRD1SchWc;J8kc}9+eg;n>9t -Me6gd+=D+WG0YCr*B_-WHcjrFq4~ZlK2M6aIoHrbRyJCNDU(AX)&2NrGQ_uIm#9wxHcXq{bv0Rnu&22 -4$Rga`1E?{`p*dpXSj@oJYT;%MhBqOlL`6C9$XrQQs!w^{T!ta&cbF>xZaJ#EYWICb5@J^@%jjki9f`bvsX`;_PdJ~$yz`sWGc}<4&Lhy5X}ww|6%2J9RSEw -zrpI;iuBM0ZtjSYgTG9gMP7>;iG)pe9VN{7Ze5PLJJg3v^Ce7+J2iid!`K2ir`OT!jyfn3-ZpibwJn-5qfY9%`aZ@_P2PU~rATx7P%X2xmXGPP1uRRi6{rBdmI+WOxd#xxHb -qS{p3)}(kgE@6cDRh{+dNt%OiJIMYHC!CI_yzDr<@7n=y` -YE`ghrx~*&A*|qu8yG2$mXU>oR)+7x{&#(BQCG58s-@TNJK#!z;vBz0tATMdX0-4~2R7MY=8J0~AeAb -^^$Z4IFbx~w`)uymiYhG;pX>;3olvPRFfxfb!OdCLOSr#`+XDM+d5ypqXZBf;RNDc;r_Sq2sy*yeXv* -WA3U{m-8BVP?iVt@Z@VK*3##n5aM0x%8uCM&L^ti{M&CoP(3MR?jvq}AHyujbs!B%BqCB`CRI`1c^(8 -~p|U`NL4I7yMg9_3Rd!>TV(Z!t*zgp`VY`%A;@LO<6R{;9x8cJ4+MM9SelAvy%YfH>m!!*hcBY*RHmQ -AAN8xpvmdk4`sAmYO(3+dY^LdDD!^h4eKPd6nL;fgKHs@DtXOPr$19ocT~Tna$U4aR&pj{tm`bhV5-6 -`%z*l{%)!Rizg1rvU_Mc8%D<1&o-PGDO4Mwdpvf>>N^{a07N5;@b#Vxa7uuk-dQ`K67Bn&~ou$7GKwg -FJ=yBA~EXP|Y@{lxn3Ja3ZT&vkPX?0UGQ!7^T6Q$!@AC&r|mnnOa0-B08q3K5vH){Jx3d5qH{dN+LliJL`bTulyabMhT_0Ocm4*$h?yeu5a`& -5L94`N83zU}=M3V_5Kc_{Z>RtFKWmjvhZIDu!uN&%>g;d5mm$T+V0MbyxP>=bpxp#w2P~r)6T%hm)g!}W*afST0Itmvm -e2xbBxVqciHFBU3)GdX9xh4o0y)ZH`eiT@lZjqG>Nj)bp5*~wn`=-m3vmz{tuG+pmWuHF+q1VXpNm&# -7b2>m;gSf=&QAndA*rAhG9X56HcwFz=HlTto!uG$F@o2EFuRS)Xol(veXJ4~02)zb(RGrE0$P?VJlF? -idLMl -Vo2gA$1hgY8&i!R9wEJF}+SiTQweH#ql<@@{K@q*#gJnKGoC$y1;PeZ52Z(^eZ>jC(Z^zji$oGh-BI0 -gtf1&{z>;InegMYEiA_&dP;ZJ)Qx}ZFO61LW16vsqF*6^r1m0&?YpWfu4NQI?kS_i9O -^@+<%+lQkq5X4R<641(yZ~)(DLsVJApTc2??skBXM*P#i578clr+{PN&S$_J{|)lRO-P&$Ag^CCly8jryZ2EXVvG7%O`B_E@j23G`pCdSbmq@}uRVsOon@r -l7oig}b~4NzH}<*=LqSOwyM2+y0G*1ALU3P(jJC^YHk8V{s^V>ig$wfBt|t(mKEeSMIjRLt8=7@01Z^G96(44i*nX;q#Ha|ZjeQ3CUJ -Mp9$Gioe$woUAAkI@>*M2s<)uNkA3UlViY!ElA#ix7s_-?LaT;N4}Lfh>N7khtfGxD;8|^qpI8ZkqFwb4UHb#k43Z$&;uUpNsLCLj -%Q>d4@Cbxuzyr;z*%7c5!PD&p${8aLcpG~06!LkfL3l$YkAPK7Zil+Nd|iLAt5`{JCOK8_YJYT>KhbV0+TkVNT7Yp?r!g2yDOt -^wmRH;qU8-k0#(5awpQvfzbQA8wAWth4jJdG9>W_b;Nk-M39-!^-H8j{3r=q~572u0dtgz)2q&*pyO2Q&3jLauZ1Ikd1MOG -Ad7+g0?`3Oi@MkshzMj*mCW{yl}>rQfS5Gw>nCdDkvENkLK5(?XvNd^n^K9SO90G&0!O^9|4u23i!KC -WhEx@;vfo$CohTMkSX@u=hV*__Zz-DB4!JD`b=OT52t04Dl*H>c>y#J^z1_%lcdw&}Qo_@h@u^hTWlo -~F(@NJ)q)S&l3eu@nIU+8Q+sn&-honm;`p_`J;y-9;VUp}51>1XlDcF_uQ?5?P8L0D=DK6Lx2!Fb~tb -!m#eixb0zJ%@`r6PJXEc>XsSsa+iLz-nG!Z;NCSDZ|BOFM39Icw6I?Jy5FSdygzeUJ=XP_T@lF?vXh(3pdR8h1rVz0u -xdp%Nvq=EZF!7g{Gepk`fe!VGbts3atiGUl`v%uxL}BIg0AEx8RJebGXiabySZuB@kB|IBSKJ|p5A3kLZpl# ->;LH$GioSoo!=+^ZNS8ccc10B+a1_1)YRj)w_H`RoQC*jT@jJv)(UUQa)QJVOmF*r(PTt -iHGV+%lBRXh*4eUk+Nj+XXmY8kh!DBo9qoKwbq?|j!^!q;eq|@u28BT!TPi1sIY-U*jBJZkis|o{_CT -TRm@dE|}#f*$yOxp*fl`;5>ZeTtR(!8yYg_&4l?QI=c(JT6Ct~mjM~S%WI$c5<83gjgl5_E*TuXY?`Kft7gVDLkqM;?dglFyh&g2n;19nx`X^k -Srb_L&z;jumv%9<{z!NaOyLyNxnJzZu<7(#h08QX_lsPES2*B -Pts&sCiV{PfDyPnnq2)!YS$hO^L -4tDVsn5(X~ObY6^WdKIN0^_0rlPFvs9UNZifKY9d52hGV?+>g*26dc>0oAP!kk9aoX`l -EB^zzD`?B_y+$pr&U8s+;3IA~fG`(J+fr?1*?>LC?n{A7Z5#@BH6Z9w}DB*3r-MPqQ+cqoFkY;{C1D3 -d78Qa7KhKVve+?K0Av$QZ&j@=Oi4;zWc}{FDNyZi^;slUQ=YjJuFwij-GV6-_xyIB_h*DN6CLOja2Wc^hePEYoLJ6B6^oDng!)m9@3diq#K!Y -Jwcp_1{Xw?cHBlX_nxI7(^QtS5(vE#RNq0TSxDQVQ+!Jm2vH2vu(4yBR`!^zSulT3MK+iU1Nib#)HOW -A(@ME+ui>#Oz>(!1CsyY`JfAH0pB#Sv6e&K3{*-}5FgyX(t5oCFE!8nbjnj&bMgfh)`3fA;cU+A?gvA -XTDoJ4A)-)tw41#lmkM~iQawSleAgf7hTc82fD?CC&!j;-n2WYhE)#AtpR`#p(h6=aT{*g&%Oi8qOr3 -QT{MZheVI}%81c?PDPqK4^PLE0jyKV>&aBeTqb1;N+_0$JNqdVRD$U{+QZh%qHfz=IdMsF#4YZk*LaO ->m;-XT@fS>9i=PRb4CtM-O#oh<|dms2Ms@qnz0}Ks&8*U3LN#1IG1mXa|b(G>@k&KY^pWdhyv6&!x7h -1Q^ZzFGF#X%G$t$mL`j0OCQiqS;VMy>)Cg8Q6kq|rb^GDKr97#JS`$e;{At#5)&OVto73UM8v;so0uX8+>S9VF^8In%d)u6l7&1u -J@oeLXU!Uj274H^%OQVmy@&oCl+mrbF&S2yRdtZXIP(({a9 -jSjO0hfXt*n0V4X;j-(&d|p!Bce&--7Ce~lK)|0R-o7CLb}9_mq%H7B2XkKc2gev>-QeJAmc4)G{{@N -_KxD8;VHxjB%iJ@o|&A*G5pOAm(!1|7N9Rb=>uR+VgQAaG@Gfu>p$*ciF+ -@o<`(vi@q{S&7Gdq^x-m)i$*PDT}DlVKQ!ZnAkew6zENLlhA5xrIB7G6)+T+_tGF2208=YLU0VVGP+B -&s7K%@uClv-%Qmo0iHqC-@Jt&%W_&ac`kFWv@zRhMfGER#MZa}qyDHIRirVu8B^AV_85ZRT~O>oM+90 -lHtv6Fn)%hg0g5(xVF>^5USFvNQ?Y`zD9AJOb^^77KJgS0`psKm+WESD$0)3652G2BFf_1N&r0Rf^%!N>KQN&G#*NIG!eYDMMQGhmjRij}S_ZCuI%r=0b7H -eT}>T@j+Zn73il!IO26L=edSuKZSa0@;D9dky4w^^f&LuVzL+pS7P2DW}PRu!yo@l3Z9bO66INof)~;bC5!hS26+~dngZMnrVpw~-;T@Qk`=r7q(>bfuoCd3;Tj-lb0tZ*T -1ZZ#tHgV4g}x|*pj^TjSJv}gTntiC52dw}jfY&4Dm_UgdN)X6FGWq$j{==l_uwR}SXx>l0c_l=X31OB -%{8eeI(QagZg`GKE2FbbT&y(IL>;RsxV7UBE=-)(=;Ln`8LIv+10l*=TX2$5Sw>AhEE?xM&A`z}POwZ -GoRrjEw^4Pg>i)5;wSdX#-ST$abW^1ZHs~GMg(lA+RO^IU7G~tDK#2&;rX`gf$Y9p9wRj>xU-!Sb-tE -YNOuouG$iw$fK(t6^rc-UGKLqa&K8$pn-jPGrRu~zPe -IS{SlK_VL1ihpRC>7bq>+r<4G;x1^yUR%+yMK98WR@@hg-8^x$@C-_$qm55#vK@>?KGfb9j!O(T_s{; -T>zdV>GWgx>(XR3tlv(3XptiWdVSMgb{e@t?VKg18o%D<%&Ee{9w-q(3;L}(@Z-1M=qf7Uqp3Y38uR$B7mPs&g|cRFeht@8P<$Ju)eZpxQwt$yxK6oH8Zr -58{G+#q$#p*HGy7E@uID|_D&vM^wrx*F<_-U;dU-WaC$aaP_JEtZrw3_$>x5$=9u>(eBzuI<;zmT>r& -SA$WHbnst$#KAvhcFDtwbHiu=Ueij7FkFF*{^(%=;=9P!3N4t*rGFzIRmaG@M_uG_@2)}%two0{LATC -WOZRkem%;jl{_Q*6A7R1#?qTI^XQG3k~?M4Og7mf-Fa3*;F-kF(R<&L;Jdr -x{rmUoiYMIZ34#0Ii*cMgD;AHzJxiBK_4rR;eE#`YPalh*{A#qbE51>crQiz~0|z6SSRmiMY$!-odq0 -QaAII>|pYWg0@t-efwJul772Y=cn!SE*3=hG8(E*#PNo&@JX+A5;CFV2mI0#r?+S87xl?;)?p3H20%a -tcgDxD6boUHz~F&#^w;%2c}O}7KsgUvL$Mh2?hu;i=n)8t{yH%!RSr=Wi6<3_ZME5exiCP&a^G4nak+ -mM=>lq~QLni5Y8;sT;tcI@8@xfY9BK@P1>n!#C;8{+UoI#bGpdooTO}xYx7zg4b|fzJ7D^{aCy@`SD^b{& -w`bTS!;=kZp!PlE)m1^DJ*%#1Gy62yD125r^a`vk3>c_U+;0yB0d&3SnL}Q#iJJP7Nr_euFImZlR;Zl -YdPIKho><3zmw&~;>{Q+bTa7PReSm@&zVYA~V -T+i35KY!RDJgLEubBk||S5DVvV$Ioxpw9=$U+5O+ZZHaPsB -+}_?jG@wXQtz#8(^^fxq&eEteC{unG?6>#893)FK?pfd8`-y4w_9)+!_EZQl+Xx=ZcSq5`Uci>_GBh3 - -8?R|Szu2ft-!zi_84x~0IdxyGgcU0vyg-T&Ms+$yW-_ZQf8o8qo)?&e}H7=vg$dv0SuAdW1oPy^%I?S -9APdBhYZq0UBVAzi}V@tC4jor@`ns&b@&X$+S9&v+Sv@Di!(@#YaO@&0$A)q&W^O+vm@B|yZFu -j7e^Z@XZ89mSgkJYZJI3ct2!Z8%5b9g{knNTuI9qS#_tw83UoSxjt5qkM|sJl*()*WcKlr&Lmo7IO4u -Z1SttpR#QR_;U|@X_-rm) -NM4quavq0RMIkSr6aM0V3z0Up6FiE!Vrhg)l0f4#cJd^2q?_)qgQuT_D3=atF|L}$lD7r;$nk&**X*NAT+22QLFaT(%Zkaw#|zby@N(?BecYW|a=hGspDi -f|Z0Pr+Tf;KpW}fZa%(w+ej)}!hm0K6>8&xf-PaoWSn^xzp-Yuuttt9S(LS3iVm^T>c*+y4cFRUZx51 -0{3tb!}O;U!yO3J7KBx&-M$7e{4kO|Xloyd#<5i%^60ySb`9OZDpu8G^gZaePHO`+lrqg!Vu$a?v@gO -gBRefJskc`FAM>;nLf1M7ck7i2p_tMfRi -kuO3*Z;WR8ykBocT$R-F_brR>kgZ0imb>sfmhjz;V+ac&Kr0Y78;=PwHFrc`{?A$(vxPA=EZ_|WPj}@wed*C)IIPp?K|<1&f5+^y6O7!4ywiu51LpHt$vpZB2x -2Ub7Ey}kZlh82(%oTWV`%GSNV;5YvQr%qjG&`ULaDL+K_sWP~gh!nR{1A;%1ig(vDa0 -Q&DtHc$pm+RkBh}JD$@$HF9i^R{noSR^j-(57O3#n*xEhbCiB;JZ@o>d3-lEb+~t*EHai7f=h#?i{Wl -|L^2Wc>WP%@=6ud=as4w)_*{791cuCCrRUN{*l+Tm3lvkI3N)@h$-YPpr^%{6?g>J`5jpCp$IvFuM64 -aNY8DP3fJy$C^srRV@mR(UJc8&fxd{yp6)gOrc?Y!UjyZxfhqV7N`%K- -eiqJ#z?At1a+vF7bRViq%g9J(z4>_1}i%GjzN8YS5zhZ`s#+VMWp-ESXM}VL}}pZ^OIK>r!P)V-neg? -y^Qw%F}=cnpX`4%z1sVOeD{+1J|$yN>-EqJ0MMx -9eGvn$Bq<%YXDqo`=U5-t#`n0W&!T_bxM+G>e?E*iaL^S++}%ekNk<-B8%1Tcr>fQUbfS3+r!l8u&X{>P -j`V@$0{`seIt2 -q`S*1q%&=x;zkhG-nr&b_u`@|L}h(K!xQgHP;^frKOWJYbvFIS20vb64a~;eApW&|E>NO-UqpqYhI~R -pl5$-`j?>^@pL%tVSbyC4HjDlKZvVsoe;WR;$(=d8@88Yz@crX?z74B>4<2l$|5mi`rMrjbuJT4i1GH ->wls9cwnrd4${8+6`wZQ+Qwg0(SE&kanH;vy-_7B$a+-mz@!Ru|=^xvEZTP#q5AJon$?h>Wv|0QdbAp -L%e^#2paISsd7Jj`7;TwMQxy$jZ9t#eL6i9EKfl3a8lZB;DjO5S;s>BN_Pm8t%6eD>;ld~fROXBQVIZ -(a%Kt-pT{{`c!YFH3;??rV%WuRa?cJ${To@>#8~!@bcT%u|g&oV|J83k)x&{^{Yf5&UoL0Y5+MAO5G% --{(X>6;;;f0fdyq4ll3{o$Qrz9(G2iEngZPycVS)E4hWLhVZ0~Kh_TnvtmAa%!ZW+^i!M&ZfE -VfmD+syWpa8b9DVH@k=x-s6S} -o|?j49Q;z3(JaBYme5@fgi3dIlzMZ@{0&7TmyPeb0g&bAa(`TFFZG5k1OaLs)1q}TE3(iPd~+X2tx(^ -|5ksMUDk=bC11eSR-Ah@hMP!;BnbX8h9e-odf6Hq3o#$_JN*5Yw#7E@-*z0j+{G~AxZ~X6afrqq%kw> -gz+i!xFeCrrU;ghRw^W7!ry;{U$g4=n_TRQm2zY;ac43pbSF_Uur3mYd8Z$caYHe5!lFtZH%M%*^zM= -fGJc5dOgzJ0rAA6njm#0O)S^&{dRQwG|uA8b8c)Zbd=ZT{hl{y;68=%h6QdgnU{vXiVJ{M(D>>12gZ -7su|7{}x);`Yt^;wF5$X#nyYtG0qBmH#r~#a?qkzw9rvI-u*Db&?uuL0Cc@VD9)v~Mtd6g;zfE_h~qP -=H=?eLeG7L_1f4l^Uqp9_i}jA2T+zn1wB(5G&hlP!w^dJNrwWxFpV(s}bZf6eY_`&Mx9f1lFt-m^Cx_ -RMD`otCe_T1dMr42TubysaH_6dI2TqFo_DOVc;0S|Wg5?G!_^0ia6F*A}(SuRHCW~7G6;myew-pECq% -DX1dFWSlt(zs$bi-KMG+_|FnkEBb-Diz+uWoh$wD@xmS}tnJ!`IO#Kj}A0N&S(%CteGQv*Y;qzc3vCDk)ez@Ysal6%}7zJF2Fnh}N6fw{@DO^{TyMT)sg@6%Mc+#R>s3qa6wHDxVEHyzb -L_mf-&@6aWAK2mm&gW=T2Rqf+V8x!l5<`B_-tpBuM`&rkwXa)2rvLBiMx0I`}Jeq50G-4y -}kMlwHu4TOwUYDPfvGGch6yVd~}@6n?+e)on)JSdHhFwb8vWYm_2LO+qS&A?z7RearVV$pMP--|M@z5 -e=V{%+y1(#vvo@$Br?yR&J3-)BX=$ez7^_5SUPKY#!J_1ky&63QJ -sdi3bc*_-UkfBHHrSL;fwMBOtG_;PTtY@1a!n=Lo}rWLapmTB5P+tej=Sj?)j7j02>2M6-QWzmVR|Db -+hnR#b^)xEgytMXF4xh}eE>u1$mT>%o*&qmi=H(hzZF8XWrqTA|ke=XPim+b6R_3?SxVOwYSbFsz&O| -n*W&8D4;PL`ZED*&p>zw29Q{Kaee@m91Q)KXusL_h21*MOf%_I=%L*7#N|{=%is+O}zB#g*s_n1@2ud -?|lVvX^J?pFV&3{^{(S7vG(|diwHglKt>+XJ_Bdf)5hZrY|c6X0$HaPRv^I%cg8;nkL!2fF3Po>$X{o -w%<-NE>jmPF@p)8$uUe0vhZm|v6zW_gxyWDyB22B{CxKAFK^+yXz9-%@MAkZI5?QiimC!KIL*#;#BW< -(ZVt{ -c{dHweNJB?u|pRczI0C+4%J^i_Z1`nRSe);n0tLM>@Krk!7t15c%{KdQXRPG%V8s#ruy?g)kyYJ54=F -z#%-<~~t{q}iIfBoO@&)&U1d!GAq1E1c#{{HQ=vv*L=?=;kW_is<%LbWkB{^Hd)uV-gJzQ;aJsf#6G5 -PEhnuZphAX3vm9M$P5VV&0EWs4ZOA`0vxKqZQ9ol&y;O8nBlk#yVmb24IG3ugm%-TUayDIp7vz35=qw -%YHT+b)s6z=Ik=~ynqsAQI(y5MzRu#p`ME=vNT{~WtY`Wy8`;yW=$=!Wz%K?`j`1rKaB#%cBeQ3K*-< -z(|wI&h`hcMHLx&P%JA(QU}fS!4`*J9q8%E`Gc1tx*Aj;lfY|ql#Ii?Zm3^0$JlqV1*^3>Wo^pfZu;1 -}716?l`3+niUW-QVDr;9~aI8%zf*26KqJpsN_tf=G(?LB@SXf8gsa!F+w`E>)#wlCBp?OY}^#$iILDXW?YDQ*bvXSwlM=^@BkoF;?QLm~S&e -2kt1{6N`-c&AM+Ic&7&`#_oUvGN7p=GN7@;^lv&CLxC8BbuCfW0&bhO6x}p?v81W*)?!{ROR(Nw;O1;zb-q2j=Qp|U?*s9h;<+vYhjFSEwK=9Sw8MO!+7ol0E2%)&;oXavJFkT6qu -3}uLgm0>(aR7L29H?1{Hposexk$p#+XQQ36l{1*~5OUO{H|r)p;y$39Ca!$s=ivLenC=?x1?@Xbg~qg -*ak+`0(axFGPm1JCESa`GZATTr?vlu345R2zFG#rsdC7@Ge{309-9qmxLpV{hx=uRAx8j#zF9!f^{r* -XO;#PQg&)JA#=%{oTUTwiMN3=1}=i=l&lSx1|7BO;2D^O#%Ff-XuAN4dyWWL1eW6gaW64-py}tjogX+ -RzRl(Ol||TqGj`}s8BQoPJSuo1@Hk_aTY8p{9RdZ?r|0MV%23MF}<4dhv(w5Eb1rUUvBDt12fyguf7{ -2f=m~RYIzDGM0Z!hjM$R)INk_os9gy>V -1}f_A~fho`zw4OqEHcON}be@akOD`@owRx0jT{&Dbv%Ro3dssPr_zG#Otwc6e? -E`bHyL`w(|j?#7YTt`dbWLj1TV3B}|`f9{Iniky*n3*V6qwyGbeg3f)QBHQ8>~RPBACq^y5)%9r#r&Z{#PNao-VtrjND!74%#@$1KN50T>;a~DN?S)zMO4 -AMLUAIptVTKRB&@MMpy}WtF!?yKGJk92HgZe#kju!iB^}n)cFP3s<^^v&v|f<~cycKWVBM@Y6>0)nSt -H`sFj!#iuZtcxJpcluj_=OUFC|uzjBZI7TD^1%vuqfjU=ISxAt4l?58Wz^P69m%whJntw$iBZ5}9%UU -Uo_wAeCp5MJ-MVaJ)NE2r&@L*4M_BNP=;26~KFUae6CmMsM3BIQ;S+WS#9lHvl&uk?VYZ -%~WCE9XsqlYj74Fa?(l!Npm5o_t){dO&6|2r~|$HpgdFgQeoCx$Z3-mMF?!za4}4~YLL(k~h`NaMu75 -|-15gH4Qe5adYEVF1A#>jsMMTA&FWk)+VM0!_7C4*Qlxdjqh*(+7zR`(c+?C1k@J0OhxI+HRP1v@8O6h6 -_W$dZaD(-d4#F^_9EMr -5-{>phw#mz#Pn%T!xM?NoYMwlFGCn1R`_j7C<}y2(~eD|oZAcOn`ma#?Y|1}-5M%Cve-XAYf$adCl00 -@%*r6}N}R)PUfJiF`6h(E+9bF{;@p{^Tf6$$k;#UR84^Y=hQszADt&#EsZyr`(A+1?j#AXA$ag_0^AC -TR2{+-rGwn-YwlFzbJ9`59gn`2898oEYc>eto_^7#lyDczQeW5@>|P|BDDtf&P2x}se?q~*TCN!C``h -EySKn*jFt&$-gCdn4oF$mY}2ru3QKA&bxBxNWzUBTmx4|w7=G0YJ^p=yw-NwqlRBB~Zl3L~k>(;B&h118Hkq1zIB2c;wFsQ`Td0SQHTlA!r~5q}a;=+~RGH^ -*Q8;Va-9d&!-Hf|N}k(7^nfY^@H5JeY*`w-@vmEIi~XUD1Y2#qsQGi&mLzZS66cNM=68;1 -t*jIMk1@Mz~gb>fn}7JHIP6l*KA`=+v9cu|A(gcG@}F^bZpoTHw`5~kCmzwBKhM~E$&cqSd-~e77s_g -o^h|%=(j=#VKHZx(K>1pRv7Vfy^XGToHJ9-}dEvQx&ZS9`I@Xg^ -%9VFPxE16z%+4AF<$>yI+}kDeM*I5T3Ur)eq8siPJM>l0ij@jb_AJ7EH9Oo%~g0< -bFWC_tfW$R#g+t{)uuylOgxcAlZZYLfS*JZc}GMn|HQ*wX=lS8!1fMZ*v#4WRCSjAxZIQ=iT5S2@P$C -198u4=+FQvU*E>NjQ)rna8!onUQ#W_yUUVs#SPKSti~utW;0SGl1O{iGyO$Nq-r#{;TE|}RP(l%%?LQ -?y1CpDj5@aiMWFLiHT*?<0+KUfVEqxe-JP~M+KuK33=wB@@S9eXjux5$*>inj-5-=CGZ_(uzrOejuC( -a}ap-wsSx-qC2Pxa^`9!IK^4|C1)d5MC;j7WRlSY{J-9CiVn=;lD3Y&UI&T<-)Y9@X^Wb%debPS^qbywm}kda$(TXv0Ql%o1dX0+P#1z{>zHFGi~d6Fl6Ta;I#3-n<6 -B<}mR=;SAUe~6xSCHr(`OHrT~c4z3;tuIeDZ2*V1_X3C-?F|qe>Psz2Xl~D7u~kj6m~Gms3w#@_&Ufr -ZNuT~zxyWdelbb6588Q_jFP;3_=JujvgaxqwgBX0%unUM5nSb}$Gre= -?Dpx3H|Atgo_NTdVfe43%h9RSn5L`EN7SH-q7X9_3`YOSP3nE>YO8W>2zuSVH)ZM{R2`NW~vacU?^Rv -#Wh#0}DN6P9flRT+8Lz}!h=_lW`gh{iusoqZ&=y2QBGl_gv13D;VOCRp@|-U~OX2AW8ek193d={XfS3 -9xHv7s!|N*>i2Pob{FP2(M;E-`g_&YT=JYe{v76zm?v_Hdx- -S}m;3go~+%0@Gj&1Z0vir#iK`*52p$%5gw(t8tPx+fA(+59rfz3k-Uh+g0;q1BkSbZNaIVZi3~Xk8H! -OoNfdJlm27daO$R;Scuz?ZNaW;`+l8CNF*eFKi8Xg(()AjaIdTD9|mNCmJ=Gw#16Hqs607&asB819?eVG0DJk{r!htj_0Een%Vl|w5=?gfxch+ei&x$(sPM2!SIz@r@n38ULng -(e$K9WXB&3OiXuGq`Kypx8C26)PjxB`d*pB>IvgF&5REc1u4>)|VraNPF=BXlfj$GW*z|&8i`lw*S16@Ri3((iMN&b<3`L+p4GSR|M4_*Y -d83pF=cPR&W!Xzc$BrUSiK-KHgeVEdGy53}kW0rSWm-~E-O^u%9guws81~s5k?k@YO=OfA%J4%6vkx^ -RJ*{_Gk)+DZwj`dv^e*jaos2>{x7Bf_w8}3rfTPqsO;Ig8GQiF=@YT`=h!}BP=Ij_bo>N!$TVMH&HES0UMtiX7;GiLs+-nvkcXWpcpP7KL+N2PErTl%B8r3RvMXqAt~}&PCe7??7l!)kG{w6#c5!=?)h_wHQ+HR1B-~&oEtEpvT3ktXXrr6dJ{o?B(;XcEICkR0c;Rs0c0)Iyxo?MZCQvu_^ -nw$nM~Z75AXH%*!5Qmxm&B*~^8G1R1PyhXDZ?#6x&14-tg{BpZr=TQ(J%;)xFP)7;m)U7g#Li3~EsQ$ -D?=5l`@%-?y$@7*ilaS8<||x}tR`UTHAO)~42({4!3!eB7OCN#EwUBeVxd5jC2#1Y?7?r6LUDK-6EeOmi(xImhjzQfKf2xx7EH3 -{U|AT26W_?>AA{-ddF&PGS>qlWzOp}b{G@m2!v(|y2SMHn=MuaAGa(y&8d7@4xUMyn}o#`&v!|c7lAs -0Y`$27@5TnkZUpH08`Qthw4;bg!u$!|RoA-by%GRwoSZ$DQ}hta}AP{>ioIJJ?V&SNPL;Q_cA6_eF&ExmdsX^an6!VsVAiVDE~5})>1e^F$eK6ghuPEX?d -Syr)SWeN3715lwzgS=~TukBMdoKV>rF#SMK{3Qn;ep&qgZKmZ8I6#~7%#&WSeSB9a|YC7$eAOW#ALa! -BT%jQm2R@sp4DQJBH`vXX2p%1~N9L~(AWtDj~K=Z~4H08NXa_pV!9cC^I_|0kRSWTAnf}hG)*0ptup{{#Z!$rCd7uDniYf#q+m-x^14wVgmIp_UU<2 -I7O%u0++4xEN9GSOG7~bW+Xi_kab{fB((u0#*EYwu6RY_VfmEm#<0*H*jaH7I~?W|T&uk_z=R{vY3@Y -jo&x`XbF@@}$=3Y967)dXJLRGaW|e0yN*S6kq&=A{d@Ow=rF@e!?m=XdglEWSKWbSfhi1Hb{r>EPjr&jEykK9FYushEutiCGttD=;eb^hD -3ay=qs19R`xq@!gxN;> -xv0J)SDO&%I0;V+8B&9Es(JXs(s1O+|SpoWgZ0z(97Z)R=b4mG4>P?h-pekz-Zn -mA9bsz*0?JUnU?IES>cgGs1C+~8MNqirPp@`*HU{u=J+iqv=rXEa8N}EjQV-A6;!!mPR+F@im4*Rka# -*RA8p4LW%6!*03u6$^8wuAX$@VEje3&+um+QO~0r>g@KOj@i?T-dq`Z|-`eDRi -8ffL%GSQ{r1AKCCI#VWlHBl?v!n5HHgyKfXdKs76RnXKsK7+Y`Wm<|R0ZIVgC4zoo=+T(iDv&xB!ofo -1uQXGHzPhUrClT!*OXE;>~YaTMOfuvEg&SPR~YG5#P_#u)I(!ni>AomW1d#gx<&c~N)(n -&_+usNC5)+SLTKSKE^BV`XKF^6QM3f0#$Vydddme0sc!IngQwB2b#W2b~Voki8+ -;dTmE_QsOd22~uB^FZ6TXk&b_g%rM9C%GCqh)1TSv6j -Mj4R~BKq#y&$;oW`7`D=$?<qI+D3p2TsfaWlYZX;2~t~Wdz$nSV7OPYBTqoNfuk@RfAi?mFmG -vtxfN%GSiiDp+xd>jW3E-d}>0mvl#Jn+speHltC8*!8LpY&Fo1}o7+r!m1+EO+9#&xnU18_@4g_rerD -=~*Q1=B{0Bk<5QG;v*hih|MD7$I{bw(Nr@nLP#-<}5x~!5sN|16yl(iCla -QN~8P}O-gI#?boCk)1PQH&1psC6z7P^3M~&M%D>RGpi-@ynz+Bt%oW;JXe%i!TiDXdU>8`~*-+gVH?( -u2T?TfTP2E$jN0Nn+PH0n*`=*A4;o=!ZtDA`Tj3VVZLHccpt}k?f%`vK@fU~O&1!;|^*^AnNg;6JJ6S -Po}(G45a={pLkpU>UB@!*x6`*TeBqb*mk7%SNkF#YaM&6-hwoa*o*jfttl}bt0?lG9Nh`{O0mH`&`>;&U{IU}aRFN1lc>qb}#ZdA`tQb8`m2XyzO35bSWH6U+mAa^a6I^ln$mzkL -Y{iKFsxo9qd|9YC3`8*R7Y9VF%_ARleZ(aqVWUE7JCjfY$uFqQB(|Hy?nU=6WXlo2w --|fdf&=&xj|XGlC@XS2Ic4BGDq1R(X=nLh6WQZ7m+5Zn`&L5U#j%pq9x}$Uf}@fULeOMM6>84G-vF4R2-FpJ(Ap3*F29szShTr96uI>Q^m;B2F2 -GeW9ANn6M5Cx6_sTNJzP7AhtE)StWEzMv7MaEC)^YH;6^_Zo6GKqwtM5)2$B&wNz>q-n7o);wE(sE$S -_CiH*k)a_lw>40C61#8gd*s@6&HbcoOOdO~(c1KUhDBB_*jujZf@Fc&uIF=d4g`9T#tZXpSDd`wZH($ -B(*gvPi027xE*JT&fBYAYuhz_k9WYkIn(r1Qgk8LExG=RbhOR|ZFbJ-S7q!x$8Ff!c${ow$DL$+X@z=#oswDkjr<>{ke_mY;&>*p5G9*9KmOt*;O%~WQs32T^ -6P*8Vegca(YeZ9mV`%kNFl`YE=ARtJfG~J8RuBJE$sVjD&k*hZ3>4{BY?IkZue4z2h{&V7_rajQesN% -fYaf6pZ0u{RZZvk(vE1;`5;jdgP!)%1S(xg6hqZfl5uum%&o1;&5l=QcAHE4`oLYMrmnVWk9vPAr!iN -0o`?G!urjki{va87)5<^*R5dHbLMT@2?`hq%&mhd0L(qIc>`FX?0sfLyN7h9vR3vrNO7t|yise+1;1- -SZ1=iPnzdm{L1T+>HHeGi;ZQ83RAR~(wt?5rt4#t3*>X4HqPE~|B0Z~5?Z{BAWxD?54zE9ZF?uaE5(d -+KZv_4ee5`j}!u0AkuNvlD87Bx|U-sRGA+adg3lc25qUJBEni3^^x#apHk0!K|;T)g@BZ;$bI2pEo%l -IXBQ$wkqeij?($CX?>ct_+<~{=m7LZ_?|$&(nXTph;7}@kS;8LSRJv>`q>6hOL#vZ6KyKoGu0$) -yB(Tg_{V{A;y|e{?^&Doegbt)d%kQOFV(xSti0>)>Yr5?7*L`DiXqYZ$O^lB27)1?5SkVJwhIHJC##U -4dQkl4dvkb}xXlIM+W(37c7mAPOfA)R9Dg!AB64KvT0h;#ry-XGDz&S$LM<Q>`@_U%9}mx<$a=?NWRKI#wmFoV%VPZmPu|;Bq{0$?m6#UG)B7@sW6paY%8G2rP)eGi4eS -#U2Qo3r5off(#z$S*zv|E<1h-owR -*fCFf3Q6WXUv-nSWeYD66J;_!Pqa(o7wOf2!_^4_tsBdP8Z_C4wo84pD*^>~fmOkQ$7*>l^!YLtP`(@@AKlyMZ-b5I! -wGAvX1(8We+VU`xWBW6CLQT&5O%>Si>T}sB!=2;CNmW#W78e$D1t9C;c$(0E2i~!6ci({~qLrDw| -T~%0Zt|e1`Os{e4G0qI#JBXu>n^Q$6mpdTi%^USM!DGI@=(z;^N#zed?XuH;Z`OK#$Wn~xLw5MWL;TllV -@<->b{}I6K5Gjhm@JdTniPh}IFZT^@o4(w5#Juk7XXogLtejfDf&B3KI&w9NkA$ndy?hR>Et1e*Ge#% -fqSSz`%HZ-_lsVhc9Q>7A|Zxsbp7DCEA=G#rA7pu5Z$3Hx8$3s+Gcab-U&<4f%d_J6paVEB7F=sqn$| -Z0b8M5b>qO=;nZ-*nA5S*k;Klh{?vKyL^}-(x)c674y_M+jT~(q`abNi*JAU@GqAnRdX2k`!T`A&9;s -jCt#WaJn;)jmq#o<^l(M@6G}pI9-J!Q7^_=rpz(6Y3Nn@J5)^s=MiUWGaos^8oHkCrZb0f1JD><^5Va -m(LB3s~o=iZ-$#dkO5TAtzQcb|xD?_k*vu&tBl@bpQ-Wmpcu&4zA2yTmjEkO)BhL|X-M%{|ZQtvZ@k2 -Dz#{*iV)j4tg;Vu>Zf>H6Wsl{zEzj)Z&g9@r)e=PUAie2aUX8To;Rs&s^)M@8|orE!NlZn;qXO2SWEo -zw8RDXsN-;_AdD)rH?ih*cA#sUzcl+j4?^r3e<#)#m~hYi_UP{0j@-9^r~1hI|;7QHi9?E?s7V-;!;# -7x67n+Y@UPy^j+pzE<5QqrOgjYZ4`?u(nAT1a-_Jppq~JgE_4j8chOf;Pbg|_0~{X{7%Ojz@ico{bbBn0UtGwFI7E|zPxeQL3 -}{*yq66o=jB4EB^aIZ%aQ01b*kiu?(j#3(`xaxdygK~swJyzi5jUJn%S=Y(vq?;6JCdOJEpi9F{mPZN -p2}eGBfH3v^5LPSY6~%(xZNsxqCH&kE#3{-eKAF5Mfo4%xyA=MG?4lb#0Xcqu*Wlm1M -NLwg2d1)cf8^&{uuev)k^tBEA)$GIqy>+R^Crp0}?J@OJ8X?RD}%26OFR;5F~zoRe7IMxz9DUHJ%{I53D -qO}nj<7fNwyKrFtOQSDuxwlRA;x-WGaETXp;HVxD4{Ize2PJThNl<=vc@w(0}{v^=&U;Z+$E4VZAsove5*BR!L)VQ -h-x-HCZhrV$ewVfztmGVe#{oi^b&}0b8j6iN^_H{)c_0iPD#|a82Hfb>JlCzON#S)faj$^DhM*a0O(s-0D!5vF%`RK1|UCRgE>Eqb1bQAtLh9-|q?OzR#Ms3-K{#>=J&Lps@sJLyOY#6-I!`ik@n<73V8`3NtRA2Z*Y;5{ETf`I -j*^fEno&nMr}2wsA8!^cZZ(R}cj-gwR(zvP7y(9Ol1)E~F`=n&x8jp9oWWh>(8@ -VS~GQPOj?@IU|DUY~9wkI%q;&ocEJ>U!5Z^VrHOBFlgh9_09+!(Z9QUarh8Cz2#E4?vmwsrDH?bwbVp -|EPSV<_ln5`Tc9B$}}Dp-QpIR?dBsf#_CPyL%AeT_dRIAbKskbrCzG@-#SB7`w(-Wa4B^L1Qi@*`QnH -5mn?wqn-PR4x}Mpow7*Bu**$ZEi5I3&f81)5736bhTe{5^u!iBzJWLSz~ssju0!l=RXcHiIWKY(Qn%m -zA^#>KEkSDFUIAhk3za#xU+n3Spx7o2wTg}(0*41`*h*dB&d}Kr^e3J%;XbQq4x>$-2h#v~d5)_+?Sn*>~-Ac;OHFg?aa!NKz; -Gh((+=_EPVMZLF_c7?`3BH?D?qin5;eIRr8Z^W;mMf -Y9ceR?6p$=45|ub|w*0-edsFhnJ6p!E-PNTa-x$?C-@u7F+?|j)BKi6Vxm&3nS7uOAlB&;q6EOs#41= -6Qh32U4?2IAEr7Vb}j;QZyxA)~QEq0XOu>_}gR9&GS1w;zk@2-hQ0%t)#aB -h>8ID}@!+5=#GGhH{f!wz&uo3h+ZrQ6IY4l&l74}n#UN~`G+5w9F-Tgkal*r11c^w}6kCpAWzYd$g*> -j^m#^n6ta3Ixy-P9BNJ(dggSNXH0$)5Y3aL2^@E{2dmMY6_3l|9Aa-qV^7?v`l_5}+#sPXmv1R@6fg3-ZYq^tPJLnwt`>G&neMgxrjvlcKkA -j0y~C0$;e+Xk`U?YdQq9}=v=ztE3XuI@P)h>@6aWAK2mm&gW=VrM;w>>1003Q5000~S003}la4%nWWo -~3|axY|Qb98KJVlQcKWMz0RaCz-KYmeNvwcqzw@Y*$`NoM6o+Msvay@2a%ngtxkSg-qlYs_dQ&J42}N -tdE_J-z6E?|G0C^_rd7#zh)LFuWRxJUl$_b4co;IQ`;OBt@$7&6%jp`the!-uYb6DgAbwIiQ1KgHewrB+f1$8&#c&NU<&SMQMt -QnTN{-%zv@+~i$qq6G=fNQOb9n40{7>OcmyK8!IB0nUuG~$vTUT9rtuTxs@cKgLt*A -r?Jxu@T#%D@CJ2p~|%zD|l=85`Fq{O0l~SAiT>Mj&i&R0+H&Wk9{aP0v_9rJu -d*JQ4V?wl%<|RD~7mGaJ$;D!Ja`Mf^x6j|cyk1AD8=5MI}j2ROk);4w~jy7tdd -QkInhG-0{JVsr%>2cFtybxlj2U9EeP -=I%eg1Y1j-I3Xtbyqg>F_P;d&1ku7cC%y53$^3;1WglRz+H^nylQK!;apeD(LmZvuvp%C%U)OJx>|u# -(w&j{G`*Rpio6$)6Tpm(W(lnW`ix2~4Ciah}NNq-k&l+>Un$<_zc{#oMcwqFB?k2#H8su0>veI+QX|Y -b8^jGqziUULqBQsz!yW26c}{(L&rK7!yc={{x&3%e(DRBNku_)@oC0vJl~{Z2@Ap18L2r$%>5j9S}1hzs??8|nvtc@QFl%0)=x7|g9$1|I40(lJuIud-pF@>^g;iXv8 -23T)c`OgyRr@kk8Op(mwP*2R`}8D@Z-?l!8bYys+poEdMhDK}Hk7uy16HTF`M399mkeg7>||194dGQGDY%Ge9AbAPr&!A_=da&9N`upnwA1fC+gI3LvusI)b4@p> -ve!iXFbGm_Yyx`GYX~?7^$C-~CG`FyYqlN5J>wW|72M7QO><0<8sFSHRBTJ(|R{sLeS}dcv4aU(i=-O -?_vheL)}*nRD@E98&_)@^@ST@mhiU+zj4Y-}J*9&oA{aq_Sssz)~;Ju<8Vqakm4RBt4oiJb#vk>;zgg -^b?h^orp~;H&Vw5F|;wJqNC$gYdOxeG_}sp2%rTsZdcf~x*@?qLSW(fPzolLCtQQ}h=A*XI9d$!jR02{6;N(lq^ESRv3w>t0-paV -?iF$YwM;39a^W+h*uTg%HzfB4YWQiUXl2FQHxz&VF(S4D`1i@8^YHMUrc;CSjjbNA=*&Xc7vj}lMpba -UKH|t6RUjgNdPpeZL%;Tu+uSxHP~9F5W_*>Q+bU2+)}ovfECgSP)BU$UCHoxG(_+Ki(X)3^piuncj-; -+SxK2AkLL8A!;rYOb<%|*=vyF^8K)w=>nZ?n0wVwlgF4b~UmO?wG~f`m1_X)LiUi9O{Spj>FMU^bR}h2^ee`!_%pc*=1*s6Y(qZaiuGxiw@Vspo7UFtT|^U9 -mPv42^D83cBJz4x@Yb=1e>nXO!riEx)(VmQ7ZGXhLoQU_st883S)(fz+4|ZjhTcA`*GMTLHr8-ksxb& -<0go9RTBKH2VUercsa`>^Q1`S!afbtNEx{UOy=U^t2NM%|a+~30WJ*lY|-*&lKajaJN;N6xX`$+4l^9IDP-vHBn{m5H&k@~?x98o;(-?$qPrxaX9}H^zI -~W=}0fzw(*Zh5_g4dBp#06P5#HR;!6`N$sb8Ds7&iP5;jbM;KZVe#X-K2Q$Cyr})UsMr_WAtO8^3Z_1 -xCbkMiuxHkwVjDCMG(bR;)T9F)KUm2lIjt&~cI;V9XmU=}$z!Rmy`Sm#oI4zjqvd^C33WV -D#>CjXH3anTTG&<-yAJ~-^%QIB{j@o11YV#EcEcQm)H4$fI{9?<CW^XIA-l} -0HFu?D))>i}{)AP0KUxw8|+dWa0W{3b9NKv^!iq={7@vO1p#N@~5Fur@b$vEHEj|Gy6GfDs7J{OIQO< -?D<2pcw!^)a+lrVQASpZ50@!cL-W>h}?tig%F4%8q4?oGeD^Otn9&{vN5Z(aG@vk2xXyojV;S)c>!wFKn44F)L&t@7`N*jAsNnJqbon)Q{C|xd-H;P&rfqaM4eFD^#&}f~88&Yi>ahT(g? -7Y|6(Tzd*oLSObtV>A*xjoQNoy0(^>e3>&zeRr$1zX4lD;xIT>S6@k^PB7b?~%BkR~(j7#^L7{~D~$O -_e_pY@6G~K%SIsvc%;#5gdbMfVkMiRwu(6jAcq|RUmFC;?%b*u0S=kFl0!2Y~p95s9|^93UVG)Sm#5- -y|;BhcvRFHg#0)w^_GwX_0tkdj;-x!cOd*M%9)-?WV((R#R;S8>)<^Q{)5e`6A2y^?>ZA-N#sN`ajuD -}>R>uOTsvmV3=vgYCm8?W$_Rp=3mJJR^)HA<5f)ffn80$m!XFDThcffyQ#+AHl6fXh9$+$d%NirWg*2 -8~_=^jt2gqrPNPG)}lMeTH5@8<0ux*}kSJYX002+u8Oo`MCWAy^JQ{18Dfh>UfVhf}QH2W%gHZsXyuo -L`wtQFWYsLTE#L90yF=KjaI4TP}9U1P4G+4OI^fBc$+=fyi)d~6{__I8E1x~{F@49mN=hyaNVvLBIe% -2=SV8L#coKA;9=PGnnhdrft&bT+Q!d*_w}$VKfkYc8r3BUMK8^ED?w)>AP8)idxfL`*k+2Ppbch~ZT__ -1$k}~(>2CXA_HF;>zSpKD4>W3j^wWNy7?}P0zrEbQB>IA|B!q6yW<@OGol4hXp$bJiV-_c}*Nwh -nvJkiSVD~%oRkFIvffj4RpYjd*Ky0bd3((0cj0Z-d{wpO~A6WR1EG7H1dxKF$g2*c~o%0zR=0H7ijQ{ -wkx-2`q8-nwt@DDKto9^B)~|uKMVwpS!yk~^IAX(>|&kPsPTh|;Z8l?XaGhj44e;YaP|!RsF&b*ICQ< -%hbdL_<}_nj3939&qJ}!r!Pn|QiqnLhZr6xFt+O^hZj}bs{ubWg+3WR*O(lh3f@ -!I8}OID0^RcZ*EQ&)*PwfC?Jln)6!){O#kN+&T99Vw&tOB2u^@k;VbOv#HD)v&zZ#5Rw87A|L=Eb$E! -=^{ut$>s2~6w7w-8s!aos;yUzmzs{F#e7Z|e(gwG>as99LcUdUrQu_^TX!xBN*C*}Fbrg`>YH;SQ-in -Tw}K#(+Glk6)j-XJx{kV`@!PTHNLRrHNW+KsyM8IE&_#-mXgExd&~wMR9Y;^0iIlgPcun8YpqTr**rTXFJh=*m(i8A8}JkrfxVtJ6qRza)!rofcjd=TL}plJmQW2dB+x@;-|63y`U -={<3UM#ER5>vxWOjNio41(gUZ(RXv&)60fDrp-7szQO{*mlPDJXkwV)RZ>Wz+G4}rCPZBQ1a$iN( -PQQtm>_V)0&LzC8nk`Kc0N3`ma<%8tu9zDXZ92djSBw4!s_#`H=|aS?M6pB6H<|G -jEQbfV#g@g6XHqlVx@RAJmh!76SR1B1F~0z1JrQDa~H$fHEvOoP*5! -=XV~WkqsRsefWJ^!um({cNO6P47;hj+D`2jEA0#!_4d5=u6On2ez(q3hdvQ?E_S7i|qK}V~c~zO=#P_ -d+@@-c*!u{52uD9P-|gE0k`i%hxEK9$(#lr0-ib#(V4#`I~DdDh?B>=QI4q9^a1bjv)eoiV8oZ_0?5f -jjSmCDY#!WisJj&?1R27(Jj2TvS{M=2hEL8JC4rJcvsFd%;-&Y2@(97+Sn;+Z+1WMX^L&uXDrd@@Y!w> -Qp&}YY3m7YS!Ebt!`K|2xl%VkLKKf$q-ob?!Vx5~?Uijh8;{lOsoJW$o7x>r?jOGG3YY8K926VtG8+K -(zoK#kK)-nlj6aoxU-$98oD_cHAPn+iZV*u*g4}M!p|_;qHU;|&3*(}3K^@(q2_^Hiv_T(DDfsvRR-J -5?Q_-iH#&!o|e`coe`0o8lrwOh5P?3?|4*S!TT-AH%+h~fx>1iOoY^FNSyD)I^ -9ry+M&;{+m0|a)bSSt7ft;n4}cvL+Ab3V@gn^}L)?)X@%VB*l!?O@OEtjh?w_czZHQJ5!LpE&mr=6<6 -51cLmb!D*U4VF&lw5tJIG66^V_C)3S5`_sZ(d^AUBez#ST@|V#5q5qNFRSg2=6<-ZC*wvX)`0BAYbqD -|4AFOpDO&-+S_~a;RcPu6S9Y!xYX$o#Jf-ISWY(UqOh4y%nHRe~It&XUei9O~Hla8;Cb+WZa9U>|GP8 -ohQFE)kl*EhS>1KYUsDZZ}EQ45?kFMpr$gU(r~-uJym%OQhrfO+5W^JZw&PU07^zKWiVFLXB`B0K5cb -a6Xw0A4DXo%|b6O9KQH0000805+CpNzui4ueLG(07b_D03QGV0B~t=FJE?LZe(wAFJx(RbZlv2FKlmP -VRUbDb1ras?R{%^+eWhBcl`=P8ZSVHf^FroGn&Y`9>C;H&1@J7H0& -sKs2+?5b#@4r&Fbm^gm0V|&6kbdHRn{a3L)HIIzRS;0bEzkNmdviQbh55s2C8O@6AQCFnN?W|*qkRzm -?GF1n<|^s%cQ>Y4y#@-^JX&7ml-e4B5M%hKVf1f*sy|H!Qf%KaG!YLa4Cc_f%%*O_SH1x*I5OhUsmNi -=wMM+t7Mt~!UffwNm(^6>_H79!`}P=-CyI|Ed2+UqS=;;w^v!Fr{lly%Q(pE3FBryU1kRdxZz;Q&(zL -~0s6P}E}C#}C!>RdZ=SsV=Gm(W{(+)ZM(7Pw5`{lSZ*M;yoki!#@%-WOUoJi#g`-1Xch -kTZGC@sh4awBr!)9Jitmr$&!56EmV7=s_~y-b-%gO8JbUqG^5%cPeC9NVDEQ#2SuNiuYgm{WQLP8>qN -`@~SSI>Tg%HdVhtn1SU57ygt9 -x*2(NTS->m-hRk3C)qz7YA`cmHX7WkEHGwtVmeqBTRLG2g15Gyzn3kX}SDImc5}(BHpd-r+z&ay8!YQ -q?ux_j3#1g4d`)3Z!jNlO1Z;MWwgWc6Yllig^rr9jnAen1|I@#9L8Q!!(7)pIurfIp@02P9b3fMGjHn7xNs&iFtmT7plW@Uwoy4;QwW~?^LCSNZzcd}{&N>rD?gv)6%g<;l!zrmt}4TD4Z;nj1P?cfI(@jR~p@ws- -aYZuuqqcNhkev4JFvn{T76fUJ@ASdCb%0puWvOA=x^z5`lsC6S!;v6bmbV^M@F1Xfj0jyN(?7Md3m|G -F(KvP2-N*0K?`pAT+ip7F7XF$qA8?f%H2p18oC6e#Sd;`P`UPhyF;DQ$LS4o{TO%=&7VZN$ty-a4=gx -|PRhmVwv?UE79kiVI1S!caaXkwx7Tv3-2T=LM3cwhAO%O-=5Lihu!Yc=!;alw4?iTmPzx-U)?HKdJDR -#7=cX`w^-h&FLa#Ns8Fu7hLP*1(C8W+V;2&MeCBkiNRyRInVt89|uApP@rh7&l23FMh$TKp&^SoT$(A -7vysOuF6b*33>gdpgmOE4!mr!pn}=EjQ-wUEu{iT+sjP~l9pR#ozBCt%<7TB(3%8B2Rx7XH^|zzSNZH -JAZeuzw&f?$U12^1^=3L~&?k|j_$0EPRFivb-;7D-;z4Ut7^qm^`m+q}7ATDmC;l<6 -4|Q%oZBorZ%F%2(VLY!MJ!xQ`Qp4FoPxi>3%VG@cZ*3?U@#Xg5F=foQI`Hz-9X(;WV(W>+H_cXz-9&S -*+#9#e9V1C}Moyf7$x!n=6zF)jkGlUf911CvQ!fJ8fy9mMqIER^{*H@-qJe4axckeG8oS(0jape=P4; -fA4|27eRwr;&PDme+*ltFlJrZH97(FlLxrnCNZa#w{{dT~P$06;%9ONF)3NO(B*5{jJy8ET89D-3JW; -%r?Nhp#7K6ULHeN$`wfMfWHdhG4b8Y%W#=3lG!%AyzB#(GuEgciYyKr0+t>T+uF5=F)z0-pU2(_#?^= -?q_V(T{_9u!2gL=Xiv~+oGYyCaa!;*gCXp^x&^7>SR0R -Lr!0K&k92<~rED+lrl{#mZ?ismq;b>&E`h0FGRnZ99=nMoQm{U??m;xd*!;bS_uQUo8fN=R?k5VNiHH -k_F>Q^lm5Cx7UCM7EU8IjYRimNS>VeAQr@-#2NRuvjLgI0ZVWE1+0xB_lZQrYAGh5tszcEq7`8QPFN+ -fOMR#WoOtOgpZU8(6OyA$00TtSYNvUqc7ZTR{dY14%5(#_VI9t=7#p8ugn>)T0eA=0n(NK!brrUxSXF -jXFiSOEIW$u_v -vVw!;@PfB&qavTN!iF*iUhj3J&pc$>Xs+hp7g5);Y+Gu2;$;jJ~20e+Xm|1Vq+7R4&GCy2hs7+ -h1w(E_VId+=W$JE|xHw@8)||Kfa&*2-X!-@qD3B{JB+OOQ2?2!mh)YqegJh3 -^J5HDsfj58?JUO;D{sF2n{TFa3_$4eM!BvS%%c)Bu%18oZg3XlGRcN@o(bMAIUr;Eip1a+`zZKh21$p --7ZS4<)k(PQA(VQ|S!a_`2j~Oi_NEWao%VY~!RN}<0{gVk2jwzY+Xt=^ -*xTHC8Y{patvnA@vsP^ZD33d=1l{m**7X~0#wWP8Z5g&b6A2l$uVX{KjO8sDXUN%zC$|@t>n^6XpVn4 -?G9dHFjX;k48t|z>tl{iRVnO&77;Ox4}<{5H%H2$>G?YxV4%0lK}(8e@8SUNlEqqI0@t+ypsFQX>L?` ->&AwK@ylR0bE$>j@j4&osyw_3uGPk~>sy&(0G -3)CwC7wXnJhzqI|CbT8n#5KZl*}DXZLpx-#9RxRZ9Q=~6M;7gA)hA6-*xWZyZa{OW17&gNL7?zRui(v -C_pilYWQ3>{aY0xMGyvjDR=$-@BFj~puVNKu -e>qhl$De4Kbooph{l0PZoA?n<_1mmaybM8D6wD#xBwuJ%+*;mlDKOoVAjvDqbX=qpI&<27xBNqdyDfO&M)>;@X0(8EC -qP)mPw#*7Mo5TLIL>cH0&k)q7<-g9hb#REoIUEOvGPQ>-O*vFkcSuWc2(xhb*H%Csvn9^}>t7ba9CS; -X-1`yBW+x+`<=t_ra)l8Z)IC3(j1IbV!B*-;UlBGz&U*3zC#xDA@>vCP_Z+n6UOo$JioeIp@)k`YF$j -27Biu1tEY4ipyS?%5)u$SlL53p{7QrDMKBZ0AXRHb=q~lJuy;?zk4L2|AAdy`LdcLyzmMj{@v;Jl(Ff*~J9`}7(S2tS-3Ke`~#$OFeUVpKjqg+eZbjyz&l_u4m&`GTNMg2yt@LJ`S@vh7l5^=sHd@y&h>O#;Pf}7_tL-mu@jyUTM17C -qXWa`Whdw=Dy(9-mLf&EnkMVR^fZ2UZor6@W -nqMP%R(~HrFSjY_$6rL4wSX>PB<5%k!G2H1$D2yY@b{iA%5fXQsROp(C!o0Wv!pxhTdc_Jta1Z5nd+U -)-Y42&;Z9RUHk(W?tvB9hQboV+-&5c%Ldm8KRhfZIia1S-GX&Cg6%66U6_-Mt}5m$sc?X!i^7~J%4U{ -|OV4;fB`fPWyE=v^iA874`Vv<6@h2VrzgKMmu)Ta*(6JxqdTGd@S=@;!UfD|{5q+D$*Gbh}^oqE%GKp -jZ1*DysEwq*nOcnPTB{6iUz#h7lsos!<&sl34eCVvgtI)LiOa~b6veJ|X`78HeXF}!qXMVmKPiM%$FgA2!R7veD# -Ln6w~VDGg>Rl0$x{7D=wMnpN35x}_7bByYmeeRpSARtO_HtRcvUXbnz{(OlVy5ZEVHC&T^pLEQf!x~r --e4hDEpLYjq>Ax@MUZ=UYF~L>1{jxN*OYXsI6_OE;@`>t%|+c*b{B%J(t2ZyIrNUi|a;*ArGiI0#64p -M-FF`zzf5@Kiao3daD&E{h>8&3;7^S(zQ5Pj1ZW3!-cce&O@$NhZpDYyN6<-{+GrQXM*59vf!OM{WnW -W?+n|h^*dY2y`&U=fF8wl)<}p-)J|d6M59rhq^Yf8)BK2!NLqHss@7NLxZR-QlGEX3I;b1%x+?bcj2+ -uOzM!1#k5;IQWp!0PV~$$FqGZ4~`ly>VsIi3D7(Zf=u<80UXgdfEWf??|;)kOUx8ORfu99_cR17^_qRUIvK`$@I=zuD3@%LjXpuJp!tv4llJ$U-lIQYJ`4BZ(NTFVQvd -%C71Bf9#OJbtH_rrYgO``b`Bf-xcQxx8cF1v=!)34&=qNo=ybh^pAzj;&irVN@f6=w3Yv#|q#W`A`&#M9Grl*3hF9S9Fp&Z9>~^wgXsNv{aq!WZAMb~?j9&a2CK3+SRG=cj|b(d|0*8@70Yr}-8O< -NG`JJ73q{ULfI8P7CH!rhDDabBTgab8c_UxzgXS|Jsi)YFyS-RTaxzRubEZ<4cc5PhJGUQd~*2wEq8(yDXD~G{%xK9RVR(2y5!L9hHJGE--)O6g5i;i(`o(Y+Ok6jlV -mfGdaVu%#5E7l9{N2gjVYf^!8)3mG}P2ANE?aGo`i|m|FsN(EhQzf2VEirTaGs>y*(IYcybJT<=S=$q -e$x~F}U%!s%yCY56GOQhnN~@4|04nlAFgw(W4n`E-u6b#fl&@3&=1P@pTJtCG_=RqRY}d!5Y|d-zY7) -MWUg=japQLvT6d3Pe(#Xcz@d& -}P;f(M4q=dp+2xf^))=}GX9(r -EPOWt|)`NJ-+Y&&II3VbFCvrqB977wdZk3B#=$}XTS0&W4sU>Z(d^b8iC-6P-*-E9Cy2YvRCeoKfCz% -DC~!wp+z_@A%8e{olCi1orcz^X7SO;*hl;3>cZ;U#Q}EeKt!Tw;~pY2 -T(qk=e69#7|;i>6Ek%H&-x#RQS?VZlN~^ZAInjh7GC)8;~w@|RckWGXr+IEc|gz>K}jt*Dz}~djw%Xm -uhjPxuac(``piJ@Ve1t4Em`6o-Ug|uzp1CZ+s2eCY6 -HqAZ;^Hb%BOBAAHtU|#_N*%iX$y*mpi2rT8G~vmXvova-+frVY)HO5ApH%!0e0eM}0HMa@~;&-*sh~^ -Vli8mxSuC<={l8&>mgIXOB(}PyP_883c7#dgBinv+YgBdCv8wiF6XGD07TeV$;i7rw2)$^Na$OK;+3% -vF;gkpvUwB`m8@~1E0O3LRBX2fSL~Qa4fZsq4uPwf%QYz7@AM(C8ljde|nNv+hY}^QDuwl9iP|Ym&@CXUF9>#;r$T+=TN)1WLkysEPKVUXoS%k$MLOY@}3pyf4sCethxc%}U-cNGSNX^e -tHAnS)MRL;oIHo_SaPP~<3z(~fD8Tn_NCfziwiF+)y`O3OeF1to5q0TZt -3M&G+e!Ih?1SQaQEMyo^`>QErO+k!;GM~`IgCU`cnm{jg^gy%mc$G<#0{>z{K$H(Ibr++>B@;~CEdy} -_s-ydJ-tY`$`Kb&-SdP#8q@1tAFjMgfp7;P!6%c)Fg37QgUH6J~O74n7oB-3(32JF0Y$-;j@19dKFGf%s_l-FWqlG( -SeUJlA&-*R%|ISc4wrdr}>fZYznbw%gV|gReit~H+ekAWIM1d1};0nO3L}nXp4biQ5d=-M8u9*$bS@G -Al>FKC+3$==!Z^yX$HY484az4W35JWHn6 -_paVg8@7}$WcWAl*#W_+t|KVuV8`VVO6nh$A+*US}|H{b0DaZObBJipt$1z68RP@0KF6sou94Rjg(8{ -9mU=6!a6@2ue%5a*~pgDhdf&D_L*_my|_lrxf$yxPU_UEvP@QJn$@_uK*scr?Q+Vq@h0wD+9I?;k8k5 -!wZZu8G^+SXc*!{aPGfAIZZF#?LH8S)=IP1m4^Q6oJH7G=p|5PV+e>ktJ%@@Zfw7y7j&K~-5@lb%bmR -|4{8xq+S~^_A`pj*@V=Ro^eh2GG;;vl1GYJs3-)DGK=zxNm`CN?j_fRf1)=L%ITP~2xM -AmMQ5c-N1a|7GsHcE7gJ2iFc0{(#%D867$7zv^)MIdbnVf@JNssb{IT9$|GU`Ht&(aE&(v(%KTnWm^y -bZ))OgUZ4R4vZlQ`Hl3 -8(kyU?IM{;b(af=;bCYpu6&7`&yaqL;xDK2RNe@fP@|HkT*as-y`3mSiK!;VsZ3y6qK{s)}->hL=9}$z^egez{VU}}O;W}zq*rqbNtc1|kWnU(*7a-4bm -d-W{;4KO-<(iEgAZp+*ym{(m%YuX{rcrk}H4~cLvjjyV>7ccQ?O{VTCmsb<(=;-bQQ~TD=Yv2td_=TE -H%%;Sm+SZS7IF}jvDrPN)c~E`Ji35&Ch#oiSoe3jBzxVF?Q9@}(FxYNKeyazsE|Xcoc-gA!nFe!iF0v -SX6R8mjI}fIW1n&k2H-=7jFz50aaC*EI{rS`bjHf{X%+ZN<)@sa8Bzug)3OXFZ$Smii_N>^D1V#&`Sb -Vh{`~pm^H1L=)e1AxzuzyiwhC`ye4m`WKTcNZ=bw(=uaa3=zdQc&ZTj)(?bNow5Sn8oai?!Pvps!&*X@NeSlO`%xG86#9_U;oJ3Ffqj}&d@k$RIi6fvMvj#%kNy!k?^Y -ys+8R+(lbm2k~-e$C9kX`fotCa2E%G-xU$nZxKnOO&}Gnla87oYzQ)b)I$7%gQ#nX+p{4%M!JG4UTH= -$c^U5iLM<=qM@wV({32Kl;W_qp>#4traFYK?_7n826VvU_^~D#a`4a`UH3?&L6(UqrW}dr2k4;|ea9hV>olah0q~`nm6EbBPDqLu>}IEksB%+j+p#a@Z&*Vy)%P;EZ7$cGsj4h8R{m6W -WN_EAbq)}GHc%cNr!HF88!2h$!c)e`5tCdmo^XPP0gqt=pnG9mc#LNl-HB>MiipJ=iluAAw*lXFLSva -JSDmIj}`gj0!uT{sXk6OP4<@6ZMx`{GsqJ>a3y6JgSMnyYe^Ap+2u8reqfp_;)7-~&}Hyr!Wf>^dXe9 -KPRmLVKtWVaA@ITndEhF3L-UeVkJw3QwVfk4A13C*CDclIxn18BsPK)H#>J$>-^!>VAVxN**J -82ewJ8{@re&^SdB`tDRRi3PT~GlLn6NJFX#AI}#9l{lhRc51rrMDB4?*?_eXbWBP4kSOI%Yb9WB4LMm -98#a;x;#P8U1a@5~hRO@1nRq*<2;X8T?p4V~uT5bZ2mBO@Gf3l_bOO1xxm*j8i_F#jyfQ6E5%*<7S?C -1QBpjfJzBeF*2;rj5poH&^*vOI0Q6vFwPca3(^se_z96odHbw~1mspin(PpdO{K#kan?EY4J*l`@K)i -Tg&U=#eFb|RhGyDR(=088iH(Xz=~dC;xjVt3AsUOh!^PrWgF7qiFu>kPx!k@wIsg- -(7G|^P^xnb*;bha^Wu1wtx6E{Sru-{(2qa<7`*xZtM5r{KUFFBCDm|&KB!h+Cj}bMhEw!-QrDXmT3kT -X*Wf628Up}sOBD5yu7VH%luELTqQ`qqhjlp_sy2_R$OPOv8d{Oz-Vs|}wK1|*uvL8`)q9L`M=I~>8s; -}{vL#(Lu_^Lqi+b#Xud&L&Y|=!RnB^7G#$@%Rt#% -TNi99;_r@8)Zqo9`Ld_W_;k-X^cGc%-#0qtj_w(qj8H)t76QbHtyW$6w9i%TivS -5FhG#17Mj54sVM>S>!}1)^=oxHC*XQQqV7($Kk{r&H723^OKpx+s^zLM4xVyqs->6pS1K -ROFOefa6lbQVUa@z+k2vB`e=^D?O0aql3Z{w~eq63u?gc%tJ+X|&WVa!WqM&(6am(aX -Ld$v+#zG%~W+f&nQl#pGh$|I#S$}bh9Jb8pj;bK|uoL`gjSOec8@NKRCKo4reG!$KK@-cZC|mnamt#B -Gm6gHzNWEQMuT*NBoo|23Go8GOh8ilciX8rmf^rk=^uoIER>?GL<{bZn)jeP%w}L?i$_2nk4zcyO%yB -7&}X9jA}U+|zw;=Lka;FgpxgC})xOVJ~og|F?1y;I~~9uNibe$gN|G9uRK0=Q}fXo^R`L*x|G2bAF3mJ=CuB|MPc9^BJseCuXsx^_KPSR;MHxiypF8Zb_g&Tpvja>)x7pzb_v --?F)zl{XpeR@FAU`8dylDy^TIFZXv2k$%V#49YH#}{K30~I6I8+6jRy_yk@=XPkej7!$$lC1PR`#QN% -?&VZPnB7e)W4^1*oB+GzEJ -4RIKWJToBY4di-O~QQDRn87V+!KJ9Ii2s)haH+gxrAq1>|wdws5qRI8N8%g)mJqi@Q^4^r%u=09>kFN -Rt+`R2S4@6uPprtIqSCE5Udp*bp`I1Eb@X_YtpD1^-|rXYvytJ?(3qE!phKOzm7&|q6ql_)mC -Rycad5q-6zGRoW(d-@G0&mxqcRkf4e*)DW%4TDf^`vbut6Pa1jCI&){HU9kz~k@0xzG?(vlW7_HuhLS -NP57$m-c1qD&iBmD90P&XO))w$zoAu3pzt0ow(eY@e7;&O%@c_2BHA=)jl)YQ2TtfnYWe^TrdfsYsg%kXtNB{*Cw>U`e3DL&tmq@lE -ML+r=HC63|X%Z*YG&Gdk0{h&VKY~BJ#)xP*h-wO`$D(d?W*PLS4?*1C6veb>fMc9zo=-2Be>OuN3Q9#C?wnbVZ%TZSYe+!|Lf?**c+8ajchIGzxg+ZrHeocal1 -+=8-SlsqhcQ>y~ypjZHaY%pp=55G+BL*I|+}(p&mQ$sq( -3{g1ao*`rqHrOK}T&C@TK0Wy+^FEX|cy5(}c9xQT$QEqr{y$28xSH8f?pVmTCVYw_}=QYHP)gOEbv8v -uZ5n`8;i*E*Yuc!BW+)cChFwDoc06QHZNV_c2fq6T{F!=xHTqygA3*IOOZk^{Oa&5nG0IbAt_r~Poe4}k*w6{IobG6YX@2?aW$@|A=fMO1^ -y$O+zzHsg3HB(7Ud%Cl4n1Z+f2t-vfrgRl+jSoE{itzSEgtYSBoFRnku>kl@YfS_W5~0FR&&Ysbmm6Z -GvGQng>NIwzIcY|4@X}j09dQX=Rb| -G`sh#CE3t`bgE6Or@+SuURx(zZ#zQ)(WnKQFtMuvNGHyzrbSDLT%vtfxW72q~k;BRRE~V+$3tKEypD4 -$lwMur4-fQD*y~fZ>k~Fm~O07AsXAYjEsT16xGoH~J#cVI3yc#UfwMl6Dvl`xBl`5PAH*C^$Y!fM65% -tEUohSmNJtyVtV0E!|TD2*rF|{t7QaRz#$aHNaz|@wwdNSP-Aw0)_I>zF*+__wXV%n=lo$_>#w{i-`Y -7T7DY29BMsJjX&9uFj16+;lMy;*r>__%J3o@9#Y9LxXV*uDGPo)SGQRoOIsb4|nK9obL}WUI0j=y7+% -w^vljMLB(zb0^ZZ)=?uD!f!`99?L#>Mo_NCM~n5rh^MUJ3V<_kDiiZ^baBr -tr6LTvVu_wSrwvQqQ&(v+##Y(SeHhC}kJZd6S)PO9tovmhWJ8b=HQJVH3(=t%E(3VfzHjeUb(@UL#J{ -CSKUIzu6^|eQ;_n#1WslXeBRv||j<#&5y?Yd=N|)D1A?nR~P029-!L9CjD&mL^E~5OAG{-AEG63r;_q -b6;C!`9#s{&+uVQ=J^S?7S+@HlK+nn}yb&JPc$U<74ztEM%O<}uFAVgD&dbJiiA78O=iC&>HZUc! -*50~TL#>}#oM}Rrc=VnaVzbYgb62aP#$D^y@WvQyf)#k5RhLTmD;r^1YP^0c-d-v(Oan%aRoNP&AyiP -kz#X%tgEGt*RRRE#S+!m9g!qE~9^YG%_~j*Axb#JXdNke4F{@r5exWCG?@jC>vxM-Mv*CW5I2Bc}XLOYO -)#YE*(&?nHID&#|d#^mBit%<`<{5!-}Yx4usGLpnLd1miSc1|C|1|F`G9?Qj@T5621CWMg*f@Xv3t^9*{iUrgXBzG{T5U5-upoi3MbN$A~n&{;DLPxy|*?Gu}XrTmoJkVGni|n~W -oepZi?!Kk;pn5IazR*UoJDPcIe2z8JvNEY#e>o)cXAmn1e9JR81=F_FYl@-}l&X6R@BnY9y%L*EK-HM -ET-&8?x?e=xYAZrQ|7KS^l2nnJ?6kS~RfAS}g_W%G*r?MXdjs|mAVk059brwRJ2rCU*1eMl9a+e*YXq -{aU6qU~_NEW?paG3zV8M;npth+zwi9J2khMqi?AR&%9Lt}wsE1>rq}Ix3YO9VZ=5fMTJWDQZ<9c&8-w -(-hj;4x40ZF?PoTZ|J(Rk?<=2U+=GM(6%w#ww>Xr_a!d+W9u>`J8x1%6KWVW49UKACdY%Hj<`zxm4i)Cx$*w{^~3Y*d`6B{#$l&dl5A+}lXXlYbJM!fOphB6_ -ybB?L(y(n~XSUe<6u>?l4#mAEVg#SCec4k-09qoK7`GJnU)tqFTL9qy->t9}mo1%u6YqSN2&{HKFm>XFgZW -DHo3#O`QNO%d9II{I_H9SR`-%(^+BMs%q&C<^Plb-j=)a3b{C^^4xb-tEXh&*7v0it8xYKig1r}s}Ox -Y#5zwxrPhpe#I8$tLO2pOY~X?Amp0la4K -jhIl2dzc&Bz3PSKDRp0=&&q?3lRT8S(mhPOWLANWmA194D-bvhTCM`OltmAjq``(pln=&osCB>|o*vzD6qP7#2DV;*BIU*GAD&Y?l{AU -l#u?o|>0sDwdIn^@r-B}yDM?C8YSzT+aKc3(S_8Q)tvreaf#eY!5o504JxmnckVY&3o(=Cr>SBArNKH -_N@NHXCVAt)BPV|uXdB)KAGj1?QaWk@vp_wAvJNyj8WZCZ7idwC0Hs$kv8$Ef#5VPjEuloW@NZ8?D{( -kY}()F@>|&2HQTrn+@Zz;k-9KE?fGdKR31WP`-mGoJV4X+-ucQyt!`^#n*`_GocYeSFJL=* -1)1S=O;1#TvbHL$a%`|3vzAJqp~~!&Y3oI4w8SAR*gGIgpg{O7XWDquLwv{k(NOgqp+sD?-Ow>*64(Z -qluDFC-O=}2AVxc5U6$`%DAcJy&xDz+Ql1u-LD-bpdr{_YQ_;Q4VxS{CvjxclK4mWf3t8L2g_9neD!w -VuVLgAv;ee|G13}O90Fa=!)`HmQwQz}`Kx+pU^EJBB$TnaqMD`%C!SEe*Ripv6lolHL7{4FNuC$f+B!e_nKBlay -Mi($3HQt5u)ObIR+wD;dEvCg~mgfO -VaA|NaUv_0~WN&gWWNCABY-wUIZDDR{W@U49E^v9x8)r$IXX|NvjO` -vfw#!f?B(bIll^`u^rayk)djLrAkfX$HrpioA5x~Q{FF-qNv^!#vh?8tKW@RxQ{Tts5b_P4_KrGjJGM -g97J@DAxw=eca@ZU>zwl3x(V;5pttiqhL_oB?=ut-Ga!>S9;*@vTp!;_0czql`0n8obi^yKpV=s!PRo -}OReOPDu!`t<4GAPuEtIbY^n@~i+HSTW};O=Q8ulueVAOO|9TWHG$TlS!#|865szCU;>9Ygi#@5Jpnz -;|g3oWH1%h-@|$-7Mv|}F-iGC`m}GdSc)9Rvqe}$bMrM7vl*aZz6xoca&DgFS`G%&Tr8N+^s9HUU7Ab -|?<2lM?DTLX7R#_OlcqA}z+|om5m_@7iR_N&MF6wsgTWvO!ZZy6w$HAd<1kC6To%sFV1UT+9A0ZI{TV -Ml&^tE>vT(tJz#9PjLw3!kB2C4L$854z)XQZU0Vlp;ho>I~!NKW=4{y&d4ui9|mzRg4E;Iyr33-tl+exNr8aGw+Y<3?BaB>4oeM{Bwaw0b1`$j*>#@#>sTba -}*s&y-W*KfTGMZg|C*0U*usX)3D$}w&E<~JO(HzYr$lSN|~-LNFZJyP@)1&MQ${7&;J*x-%Edw`MwXk -Cz3!JU=z+JxhPS^LCdU_Fa3Wqk%4x{5_L5=``_jFrzgQrhvyeZr^ql+1RumELEu4>O~uuC4}=tvJ{z& -QV)7f03U54M@BzJh+tTbwnWP004LkDtWh!!G0W`jr1z!*W@U;L!kwFA2TrSf!&b+4d7X -)X_E`b7k0s-Z~0Fkc|FGV3|;4FCV_ils8__T5#%+E)Y@WYA3zpnY7iF`pXXdZ|R2EZ1gRqT46*8~NA1 -$+RCs{;HD&X;`IoCr3jM6%D23(Y|@B6;C@isT*jKRYWYX%exwXGi*V#bD$Ev~~wTuGbh)tRYoG!vk$}ajr -LL%tEurndky&MH4zrDbtju)N1*gNHT&pq(v>5N;}@(i6jQSN7tkn9|8X)8;#g+B|3tPvsD6`5Q`xQIL -vO@T9i*ZkZ37wjZdz@#_LR6K!{QND`0AXUpsS>sgb*j<6f6q{1`cFDYzzp)th&BhsZlMs-$3(HfN$Rd -iBtbD0_>v%8XKEc)VtOV9-#f;lG2Lzh}Q55Z)kmBdugK7Q0MhnT?Sd?D_ -5-)cRp>x3#Klq0Hf0#~C+)ZSdqMVZFvj;M|VkwAUX8-SQ9*%GJ|89MN~tOQJs-<}-3KfJi~lMF~9yvE -&-PoH^FEXU8E$07oipmM?&`RsWG?YR_X9&!Bc&x=L61N(xzjb$(6A;%1J@TsaU&VXRjFmRKsTn?4F$u -d_HRJER!iwQ^sT>1*!nY2IwKd2xWQef8FPYZd<@4!OU3ZF42jIfw>a7mc1!nF+s6bUei$AE+*b-j{Hw -HUWGxB{G?Nk{ZMfty=!ih0h#Spf1HBK*Z{Lt;Th2`?e>9qt3>05Vj{W`Qt;&ycy~Q-Lh=$|L2%*ZPfD -2OkA^Xe|f0Fp~^HzcdLgROTQ6%VKI<8CaN{{s5VvSYhRLi+>9&!)hXsk#|r*#e^eFnum8JGz&zp_zW+ -Qq2y5rum%d5q=bsk274F+SKTS2K -F#n-Eu&)hG2EK*x5ieB#?Jl;-~A8W~a$poPHRskMm^=o26nBrlYT^4eh+m{%o%fJ#6XY3M9xY^n@z=t -9y%|!3uwD|p?reUwKVxg6&bQ#sdBST8k9~D2PY}R(0DO%=b*4Cf~MQy!!6h(bf?^5|mGjf{ -2&L*^`6Z_PZK<43W5srZ*$p7!G+>+6-T|;CX#vyORju`l5i8**(UPPfjM`%IN>0`kcOFYg9Fpq22rXj -Y~+s8<5he}AzbONd51H`ds`b)blohNs&S6^Sclg(~d|NVqJBS@s18RBgjebD|84f*{N>_f6wn!FdUtH-_9cud;yDm{F>JJhne`WT{W5YWdF}pppfBtJNWHama) -9v$fTx4ZQxWhKyMQ!Az#LJOZGriq}8g|wN-qvf@M2lQ^B&Y;IEP%%78Ze$3zA>pYi*?V~Puo?OujOQ8 -tsz#cMI;BAldaN`C36#Z$der|$nc)Df!SQ1S!npl}4u2-%NWazB#Ani`vF9EG4?E;=61kYaUM&y0d%U -k*d!?`2XbCoJ)-4D%jHW%z<;g}SnVLyo?;QTyeEh|3f*0cY2tFzdW=404&sMiP`L2~!r$A$5pgN05c8 -AD`+W>wrx%%$=}zJ5n7&*n7-m8p=7g$B@47uv?CzrMIfVISHd%GpPdh?)2jM&q)@GmHduo(K-Zk97gQ -)!eYhI6dMI7V(NnyAZF%T(a0n9QSCXS+KNJ$y%4#54vr!vte@`s%k@)r?yDw0oeDwk@Xiu)9A7-2ggL -(S5seCtjm{JmXvij_Fb!78EwC**ly=fWSuPE%`nVN@YT?NZc3RFGNDw9+b6G0}{no -9TRdzh^YO2-pC7A#hr*U|tC7DF5BCXLA~6L{|>$<*bYh>j2&gc*aXL^P}+klq=}QJ*zDQjeOc248A+O -E5XuQQZzQhZ|ae=(dV5!GZ+a*N%h=uF1(_<0Qy2zjTPb!*$<@q -a+FAmXyqkf!1xK`9w4g@6ajJT$hcmnk3P{?VOUVrH1gzu8zVdHmtTHic#@&WSC@{$i2y}inf|tI$prC -Dwchj)$k(;=Ef8DPUZ`$+ZR&p+_d2W`XruWa^E6xo@u>(&53!Um2Vi)8c%tP#8`-K;!L0&`-NKy)-vU8!?kSbev2RX~5re~=r|Bq7Zms;vw0R`b$cPd_uCPV!kryUvt}?;y%4a -)3J#&8B99B=q61tL-D(klxG5-A7jr2FF;@jgX_nd1&f9o5%AniPl|-70q_r*rCO!J1rEc9j?sW8so1L -P;THHwPH3sc_y|0=3a>({N&51a{JdH9VlkHfEYcq72*tZqi#acWxiJzoSRqj1UHP50p($7?k4^sQdJ% -MHnLH+ma+p#Vh1&{|-hV$-YrjeO#ZWwEx(*sgSY?o%mzH_1+25WE_XuuyNn_inb(?rnf?6t`KsN!rzT -zp(posGhHo9tCVnO^9m5VV>h(z17xc<$BX4_n+AGDIZr7?C_fWi6`eT`VBk$QNWx>VGbV -X+=4`wHQC2>{NLZb4Hew(9i^3{2M=hr=uu&d%8Y69#O#eyrhu3IDBHc_2hxatBT)&*62h~`g|XhS;Ek -W3eu?t$nI^{uFnu&Xg`acsgPytnB1dYWI)J3kz%OOh72qvP*SF?6kR0UQHGx8dVeTc@A~jgTr^Q+!Ze -tVFfCh88$#bFD#A1<$C;fPL5cR1!hy<>dxms#Ea>yfyZ~7`qTSFPm|?iHaRN6{TYkQ6XyzYaC&z1*qD -De*duSwh9ym%#F$%FBqrmvHnp6`FTrzy+Pp<0bz%&xP!knl6I?xP|cEMsf~c%4%H1h^%Juu4E)g&5v{ -MUn{HyUN}{GesDqCkehkNG%Z2@4v@D*zmtB4jvZyEV -?&|A@v8?n|;R5J3D=$eXZ4tua*l2G8+XvrTsE#Pe<8QZf)Bdr_8$S>FLuRh#d75Z5d+EnEI#}Bu^UjO -*p?Z;nla1-EWCnqPLs8m2~4J42&YaY224H6=IDtAmOvga!)&THCiI=W$`Vr$-joV+2Qu0D}(zWuHMI< -pzyw?YChrTtPUzfy;O+16A8xBYCkmSRtemDsmb6aUp{UBX*!v@r@Vhyp#EO{otpQx31wy!KeUDIY=d* -{m#SU6&=fBzGBaG?T4lI=i3EW~-ViMXnxc-BGYtE*}11D?MM%2s|i1{=OqM-*XMhF%M`g<9xxMft*qx -kt%vTvk)zl2(5tgUuFlNjQuxTlA7)xDoUOg*;Zz&n6O-&0EeYeFR@F%lc3{$foF4i!(VD$yZqWM2rY(#I2!gV(~p2HadsJwhyl>i{l0Q^l -Fe$eF=#`z3GI;%<3yKVY7c7P12ZFQQDiGLlMGbNW!T1%V92LmC05Ppl`dx1nScP1YR*`Jhe!TBx?>I?qV9AaCb)Zvtg9p~{Bt(Zxa)xC%|=bSqsZAyqAN_ -m<}l!5I@3EFdAmbqf}dSR+7d)Aj9 -M8jO2z{l4Le69hW)0!&=zJ$v|%zA(&1Q6YOQcH5ZCaPsCzUHi25OjhfSlnwNcHsH0#rW4-2!=g -vTZp_O$rlAI98t&!zk*8j*OwSbiB7HKFQYsK^tMSo@{!Z9C!&DyLT>8CAUZ83 -aTv@^LvNh<~9#Xc>&XIs{+0lD7383cyc>`Y=_=7I2cR+UFW8I{htoIM-u>j=&8?w9kVe{8P7L5pwf20oN|d&G^%5+Md*_3+u -6?mU)CE~{umgnLOA+lFB*e4#uWh{;Tb7D-^5@T<+UL&@0&FN!6^9x`4+zAhgqE0A4ZOvb&zRhj4!iu= -M^P{%c=UM(Bhtn+hinhuu*RbavM&)7@bps0)CUrGnTN>kft-JdptU?q(TyiBar!B+X?T@hy|Q%Y5dwQ -&u~w6t0~Dj@E3^_Il7vvGl2JWMSXQ`Xoxn?qwJ4yQLyyE4>kHz#-hkMLkh5x0mS$2aOIV@iu)zK+^8v -?)bRe3~j3FmNM|%V)5*T{Ru~S0lCSklOvfM#o|D}(V*VusPS!~=|eYGWx$}3TqDT7Xpc~U_`(PY<%C$ -fb_6 -A_lbAsChz8gxm43Iab6FTKMmv_J^%8P_RBkD|bwf1T{kUK!Tf59*@Qmj#o>)dV;BF8= -mQHwV1)tG+YX$IFSm^%YKRVk#eE}|U@45m89z(*Ov;!OMk@Vlm(#y>I;(qV@N&K*JgvtrK?Ft;eUYX^ -DrCAK2=YaHoFsMp)&{j{w!hupj;=iqL~Yq@tpW#Oh=%i1?FLmIX>|?^8<#vqA)Py&cmM^-|kb)ER4B`9B*xcSKFMu~&b(jNX;-bZ$6t -6uRoJTF9Yn_Gz^){+l%bLm@Q=9M#+ug*tTNq1VNp9g7?ufwv-%DoCsY`Ph@}Qt&NMRzgCFY084zKUc- -~hpC?sU2~q?$l?*F2HZPP^f*dV)6}(!)wx;L*9w;c)RcP)h>@6aWAK2mm&gW=RdEZ1J}v006O<0018V -003}la4%nWWo~3|axY|Qb98KJVlQoFbYWy+bYU)VdF?%aSKP>v|MMw25IgwvM>Z;Nf8$BJdX_@84)gD_mm!to}CmU -NETkLtcxUcf7>xRY82W)qHXP2E`bN2SWxh@NKQeHN9X~o&ga#>_)lb6L9I-PLNUL8K49Gy(Y&BunNMa -G`LIXXQ){PNxDo8uFF2<Lykrf4f6bc|z@_Z6tkhVGbb6Cs@tVDzysgI@8z;-fq -O6*lrL!5ETOH;pJdAh7+e5Z9-ogLu4he8#$^kT3A*v=-g?Jk4jS$DG$SV3^>Wt>c;Og>K -e0*NvN>sq+fK{*B5hQUlcoZYhi`_grr(n0e!=S@Yxu{8ewV%GX>|jDRJXi9k2M?vopSKV(&*EQ)RHX!QclL@BoPT<1(*!X4oLF*bB5UKW?LeA=vp+%$Z?X -joIi5NXDM8Sg4!2aDZuKFez0Sy&9*2U7`ona;+ck>3>+pO#le{&${zD=4@MiFd_KQQj41Ohx%(Iltgl -aG1m5Hl5|!YRVa)>1@f7Y`N%4P`B7(c>&X52X7C>)ZrNvNks3A{vgqwMUnPe}FW)4 -Sqvvm493Fk;JbZgRc{w>gp1e@p5uM{OlR`)zwya?lanqv|fL0_>!NAI5%8_B&Z=d}Rr(X-H0#G=Bxxc&SutU4iM-)KF00POY!= -ni`IDDV%B)jmY;82;(&z_9)9vz_GS;pEke6KED|f{!E*A&rk<7vaCwqqnoPL -4g26F --Ffy)&qJ=)$Ta$=%nn*U$32*XNT7Z%VAIaZG#kA;euaO(xv0w-Z+IO5^0q2(^NbT3$3mFZnqIFDj=uq -JBZ65#I(oJ6Xb;uGi^h2NyMMDad7Z-12LBfeVg6DbtX1>+uDOUKl&ayC{2QM(s=Znw+1#vCSAYFpXgy -As$wxS^i*%+}&E||%BLzVFywRRsXz-y+Umr+_-Zohf`)9lV@&7#ce~RY_aDJOM+$@p@NuipJz9gcb@M -gIfd7Owqiw+PP`w42Bnps>{Z{gwf8n#jKxpXCJIP$@(SB6b%-oyj@*?74Crsc79I)~qzQ?KK_mcZfnX -14%RllP~`2cT5D`a%^&5$WeY)KB;K;XmGP^DhRQKS%Ie1iwWCXoPBZ#xI#NDRxPPox${D`9$i6 -`K8u0_esmyj`lPe!Dv`k1+!*OulUUCwG-%N=+V_{H`Gg0Dcox}h@qh!y{gi@=Bw3lMG8f%3tl&hVzE& -AjT-#|*lgO6%8KnYQW;p7Zv;S4K*^^fYL@N ->`$T!g?_YVyLu1jikx3#1y-VIZ(slpq{0W?a#qP$PaE3I-brju{FTI2XOP0Luy%17tu123mEG8h_0>A -}Q@Q)c1?BVT%+DSTJt@L*;;wL)r89i3*VLIfBTYy+Gau!Vq70_`6i_UXLq2FK@vFKmdcT(JW0`j<)l; -qwVyJW^_b3+V1C$w%a|Lr5(^{yPr$RU81Dqi*_^OLpZBz_9K|>cnoV3vR0CC#*5ekKR~gwt*L4wj&H*^2%uEIEOQuKeUKaU}usxNOs0B)5k~J8iJIbC7QkxdTmsPfrGBB -`q*?te7biy)ft%kLmY_t6yF6e?~=?w$5kf4^uVyXl7h52jO`U{2|u>W=zwr>f(FPFk?kr%=%hq$5V@i -#E}-=NP?h)c69cbr|Px10pc^tuGK5S^6UL-a3^vl9M=ezG-VBYX>8V501zDsOnfq+%=O*u}D8puXPJ> -@IJv$syX5<*arf?BIHX3`}p!JagXc0+^4y)<6*)jyB7i2Bwl`C8^C;ADmU1W#|m5;8^1+x=zJ9z#!;2 -VFkZaG^cGX*b7_eKX>gvf$E0fM5AfBoMlDS0CvzUE$&&p;8WP2Q^~-#_~>!btQH({<95T)+(WbQcD!% -_*==@1ZFVEU0qO}dG%A^=r^mY_?tsgGq85?b&kEsaj|Ge*(H7mO_+A)H;+fU0)q0`Kqdm`_inQ}9v0H -^qE7e|@S9_MFCEjF~wY3*AzM>mtaqZdGmw0y#7Fa?S*q&pKiMOdR$@YwKW{yG$!|aqt~n)*=^3!Nh@~a6Nm>8MlTMZADm8Xhlyvm`kot?55TaLvg{-XM=h3VXE)PxC)f4KsrAZ -sx43?cK8L66uC(2l>-aj>b#t|Nss+XNm~=RgI@vymkoG*-E(D_1>ASbDCN|0J(b3H?c{O=GIXXR2hd^ -veM1245=nw%WGY&jF6v%emU{*sRVzCHT=`1}jwGr -(Iux~+qQTbsl9V5l_aA(~5c`H7GB$*44z^07GiQ1L_0$;7_;QqyzlCRmF+=5hnhMnvEVw2unjD0+P^}3!S_fmh&=`M{rc&Uy5p~;FWa}hGp<_V?=aD)>?#LUUc4L4@*6IJ+vE2 -n4IUY9;Q}Wh2cN^pI9s(O4?yr*B@oVOcq)3DPv=Fm#2BWRxBm^gE@?S%Y@>hH*qCd673s64!ioIV2M7 -qvK}CnWLeEsQd -FTk()OZ?*JJ7!gJj!dPwF2ETewjVdk{SJ(ABB81xZ-Xz#|-?X)FY=)xQq(_ffOK)DnzpWkbvOtPdL^M -k0>~K{pUQKuB?N$L{gaRQE0u0CabG3_P76*Re?%J~Qq)pcx+kI^$ -oYp}s&}|_Rux@06ZUZfb@kbIK|1rlZp0!aD4aT$bj#u%ZJy**GpgBk#7 -S=VOUIr2!~6zJ-l&ttKE`y!Aw$9AJhCQ@+$spn=c){OJoIVf6d3IkhrV`hm-?ZSXKq -soECWTUyRsI{a;OR6|H&S8t^eV9IWJR=Xxc%*Td)hFGfweD#q)JP{#ux1nlDprwH8)xM%7l5D$lN|$l -7O?^jI~~nbF2nuyW%yptdDK>I7N+fGPKK1*{&F#%&dQqGDv9ZC>j;mSs8G$Fsm|*wn5!50D|N82?%AR -80H`zV_#G(^)VbMETkN}QUeI)acodT>eEsHX8Qtd693pCf-2OP%bpBalHU#D~^U>5)(1?^| -ok!?t#+k8(}36m)gbKKcobSq^|&p5~hh{5(4cHZEoMqTn!1>ulCJ39wtE$_ZADUQvZcw?TQqT&o}uZS -&F;L(DCveMO8c7PeoRj8qC@v=I#+9Mp+UhV_bwEA#p5dj#&cI;~~T_0nn7yz=$y9lIRjgE7yoHcBFzI -2P95YUq-bQG`(ZAUo*~fn?^CFg8*Oxi -{c&H$TK5?iUW89MMs10w-$Ewi5)e^rL>mT}emU)2BCgX?0b*65xb4VLb$e0{RtOp;H0Fm7Brc%aIdnsPxvul8g5w(kE -nkf_s0nb=P`#CXtO=EX(}&muoDHyvd}+_^8w^sT#G9i!+bQj -3=Aol*w|hN^AgQMJQ*#tJRM5Lh}rk~TH1B+5rPaT@)-N(GLaktWebtbg|{UhA|>h2hdGOBN?!|iy(SA --BG)$vi;o6xOkjp|IbXMh35CJo1gX&&o5TTmBgiDV{Osnu&o(EJFi{P)8{Dl+2*K&y(Bw|=A{4v8O3! -7lgAN$tInc8zN#jP_zVX;?{1D5~o=_ZzJv7>PgC~W)og5%%Ig=7mG~x#7d}XLIR#w@vY*qKSYDG?U16Vek8d;78F3?xU(JpY(`HgXasLM<-CR8M`*S5Vz3#w?6Q9B#RB= -!zKazUd3pH$^+cpnRveqx>P~J*ZZaelWnBNM#b@@SwPQKfT(b_!f^32vfPn;wNCXHRa2+%akWD$vl#= -=Y0*2`*_NY(GAW421iYTk;Uzie(UZ9BJM2dKVPah=xCWjJU;@}E6`rDd`66_ -?^3v|oMh+1j49w90HuJ@Tk4aacWtl}{YiNR92@%J@tw-LSIRLl@0m3Y)^AK?KRwLw9?0nWJltU^|z>J -ITL25>Hz<>G&fJt)9c9wQQ^HJ~@`k9s#hvq1kQ1Zn0AkyHPFbO_=bkRm0#oa0JoLJli=R1eYG#Z*(k5 -kH2F5b$PHlV{?n8uWhv62S(PrnT~b2pK-Kl6Y^v|r^7$LbUk}HhQiEY*tBTIXo3m=_?+%uNVyibWcj7 -p@FhdF2le4^RiGAV5i*D}d$1F1i@ksUp4HbFWUjRoZeovOi8guK?l2m@Oko^DC0So$$m9Va_JEL{5hiK^H6&Lra5J7u|34|rV{3VshD89rF?g=4jOkpyjEjonH&RtOAHX~9kyt*0~ErGET6e -dQ_71^}K_)flrQ^o^E7`8*3c@9%(W>JBh*QddmspwwYZB}78$I^w5<4uqIjk$yr` -S|nHA*+tU_giF4^_{0#32fJpTw#sA-&!`Iza<7Q3;QdxNm|ajo-AM(ia!lq~1ky0M>6Gz{buwU|2=nM -JA;gZI_~)d`_C@#rcv-D_jAeVpP8Uc0nailM7TqAM;q89-j^nW@~OOVqU6*m+FtnEH0x8>@zdj^FoME -vyU!j<*#)NNMJpseEG+j}B}9F(sFD?`~sDAwhQo%B^Kb -8m@KT`lSN!$Kbr{Z3ICvH^IrvAqmL%*h_Q@)Kls2$E`vdfr>7nzHVxlpVAZR(@GojQ);?K(uq;+YAvD -m^NwOCt6H0_=_S>*db)?yO7`6C)^0w7MI{)#*g@USNgwMx(tG(j)S>@o)dpwoG?&8fzEpFt2RUz9n=A -IuPUrsF>DWIDD`86~Z}oD&Ti2?WZ{5tX&*O@ne;8M+C%lyJ2O1j%Ce1RHVo6yjQkAhE`O&ivSU(kN70 -EkK%%9PQ&Qs9po-)d8qYQC1Z0!+NtC+2#W5LY5fKF&%+lS3GL6Dh6KR4gvtRH7F7D4P|!94uXu?l4=q33AQI5B`wY7$<-j2b9sntr9Nsl9)IwbcK3PG;@-2#GOe8o@e^@ -y#u`Ev=O(V9dN|ewuUxX4K4vTUIeD8g2w;&q-ONL|EBZ`v8fW$Y?5TG%6^J5$K(`VWhPNeEBoyADZ?bM!-E!!nDy(~8R_tbV|IXP_bDyPb-b~#%x9%?F<5dY -7~n>7 -vZl*9WK1zn&bovO)j)L-s}d!x;WMc=~Hq#sbOM)&6XI^!tO+U(RFsOEMmvKUEJ0fBJQV;%A^C#`3 -dmx^lXtIQ9~^(*G3xlIo#F2Gv;X|0Fl^6G)esSHznUCPjt@@XAeblyQw4~8czW*dMUXNZ(-m5``1*Wf$q`|@@6lNJGOqf0_3Tz?AEMm$ -^VagD(Y*NeW2t#sg4N2w~leZH<`sn%h25nI(7_(?~H38OOLYMyQVwuk}^Ji!@IV8U|&6MUrd~F`vx%f -SxWO)nF#f#g#DhoP9)HF`MKRP{lpL~1p>X25twa9^j9C6q78N#C5tF9Qt-ZDd*13Eaz2OI8UaaC)Jgt -|yZOOkq40xc3+utc?DQKBmO3V&4O9&G>i>7g=FIh5U*NV|Fcyw5FJ9ea^o`~m+%UK}i}&11)kssb6kb!!W^uyPh%aWLW2hU)Q9KYgzH9X_?l7BYKvxhQ?7iO5-{>^t1ga+0LbWgHU&VlVPv{RB -6YM=w+h$9UM)ghDtfx?>9(K;64gZnAGtPwUuD__GB}(eS;Mn~`NDC+X1vB`-kT1dWp7h|QoZ&hg$m^} -jmM0Y_+?^4?Tj!vz6bzHAo&s?gT>SlPmgZq`%>)}|ZSv};23ayq5kI+X2T@=`VwlS_dr5l8q#e07%=T -~I`1T1k17=@qPD&oXz(HFfx@&oBsnS?|)PHBP@k)TmfOks1LJ=b)_0x}6xvYMl3$o8Izx+s~9UKyY^TH><_U6AMkib}wA4n?Y~dW$5t-s1YhHt2k9-6kNiPMxMUqIJ2frd-y@irdwwN&R -Y(sjU}>%6VJ;J}HHAX3f;#9Z1BcL}{&dVsyNbm4fwkRikl7r^wnO){!9ib#-rxul=&6(&xxCSAx%OP< -g$f&z^|)F;9>os{iPBJQ{OBrqcC$`8qq>291X6A_* -w}FQ(CmYu*-=Ah`>YhZ&Ti-oYH%h`TE&%{{_y}(Zi&K`>5OiIWsuqAEqnI6-*V!s`cCA;xhB=1L}S{cNHQLm*XQ77BL~%kc<c?rhY4__bn7}=#*qE=sZ-HgLyySLJe46lA&2bi=?db&wGnTr3Ss00TAh^brf%<;hAAF6<~j$T-jzK -vqvx!$sA{TrhZgn08438K3fO0pA5zrzple8V3)dd%;TqCeE2Qnbhx2=B22a&;m2x&&Nu -IA=%b83u*LL8e0*z#h29U<}o^!LK^Wi*Cfq!ozThh9hS!Z -Raa>3FG3L-}thY~zYU}LBmT;6L*|$C!9Lr~b;+d@0Hx`flfWAy@J6y5Q(IR}Vk=mb`_&|_F2=l7T8M7 ->aQB)!B@BveRNs^)9cRrNWO^Y4`yAKz-*%9O#8KxQmTg~q6r8O_R9Wwn0_ap5B$h3zDzrH67G-orm<| -})yo+dTRu$Fm>ITd=B4`r^EHRDcLlEDp@*1C7QEcAEL`h6CL2KvL;vFJzgEvUZs&R!lAqy}d(sIdAv; -oRqYY5BzB;sY&#jNjrvcv%rr?-9{x?@7Ifry2$f2GHYX{lX}Ucj`M?9iEvjhM(A;E-MU}0Fz)g8`3v| -@zx2|a-E&??-djFF7zuiq@V=jlCVdQUM2oa -XmD4V3HNruJ^@;DN$VPC#xrBM~e7;Cn?wDO}ftm8~)YgtApnl_Wky)_{Nu?pJ0CdBa+^Be -%+AXc7FMh-md)mBeim)lKjskezIg)Q+7x)=}VjP8pHaV3iUksulGF(WkIxk!N?uLQW6~lwib$J6w!`x ->^CcJtwz1JX(i+9xNQ}Btq?P+jzP^keGVwLacm{R3BSIpbD+dH&9b<9j8j}2;ZbL*1=*hcNVt$Bf%n< -wlX?>sE_%=lzPMds&aY?nyLh0Nn8+wUcm|`WkFocMM68bMa0UXnYb;On2dQ+%mBym0#?+6ERZ+${-^K -UGoYP(GCI5VH1-6=@in)8w=SbE9%e~%c1HhFR#23-#8Q)Yj@c-T|{o|o-7zF3!~Iat -eh;qPzM@lZ4D4qE$t%zok>7~$3#_@R?&M>+=woZh^7g0G -IL~<2&i4tVmtdSgK*e8q=_j5ip1SHZ49f|>t2yvI^DCUuqCv=Sn*~0+5dNsa8dy1BM^JfLYk&@3A@>u -}P$2BU>c+N1K*>=%^K&TSAY0BCab_i*fxfe>c#_uBJSTP?g0Mmt{QhpAk%}9RqFVNS!#RvKW%JnIv9P -{jp%Pen@{(NaAB}eicfH#mRj^?7&E?pG>p<+Ym~A=Vc^~YU5x!G6o!zDP-d7y)htxqh638EKL+QAX3f -w)3?&507xelTi;ic3FQJY|si;AZ=ZO)ecI^&u&+@GA!YTLwaWoET$JkASLC*tj)1hx)iS9p~xv~AJJx -&B~3)0^;$yO`VktTPR|J7I9)S_F2P*g}?%jN+mcHFS;sYCe-|#{^JoXsj%y_PJcf1QJs$|3$} -oyB&m=z{brQsI9AP)h>@6aWAK2mm&gW=Y7mMVP1y000gu001BW003}la4%nWWo~3|axY|Qb98KJVlQ% -Kb8mHWV`XzLaCxm-dym_=5&z$xf{k&oeD$iEOM57cZrfy=6amuYkWC7-hM~|FZ8IxNf++8KfIjK-reyDrLOCAYhpMb}gI!{zfA7x3SY?B6oyM| -Q)%$}$GBcXBOqB~n%wtlkOs)3M$aIlCz~^*yfyds7_pl-ClT!G)+8hw<{dlB+{qz}>bIVlVO)5xi5>V2Kf@EB9ctjqT-nvjm(Rc -RAOD*J6YJPtk`a0=JvKw_Nu(y9%`A{TeS=Bj*hNAmcre9lcl~IX$51e*FU7HGr4;Guofj6!n{VAmSoM ->JCP=3Rg|Ktk2AIvbwUr56<1=$N?z}hx-idDST7;1Qhat#Uo>}X9ORibL131b2)TRY0L!?S14-1S>U8KONw+-w6WfX}vI -iQGx~+sN_EOJ4E4doyRBfO<|&nt!nxe_<BirY5)paqyb#(a1ATB0W$@4Rgqg+L20y(hb8j)DwR|x97J42$Z8bB~Ls+rB!z1t~{M{S -31!A7hN;?qA6cSz~rzL0h6ASsiOEaIFaPqZVrUni8iY=U!q4mm)W_oUzIMYBFV9u}ybW -{B2?y+36kA@SWt-;yMlB6;{w<^%eT(a0AdGJYy|4lHK$>u%f;QUz*UtKb?^yUj-x8=F>~2X)6JtsQ^7 --_2W=x@{5wMZ~0bu6eX}0hJ4q8L~(!l7=Ca7uB9;+0hh$T2gCV9oa#F_6RGW&mC%3fzR -Oqyx_#!t{`t{mS3Q$N*Hy|bI?Tvj*f$wKv*Fh;xZ>Ay#d%FnwWV7lK~W!D$rmL;U-rSz|FBKk{qKF`u -LtaaiTy5p+?5gd~s7BjY-HHoLMM>afwODB;?uZP8J884zKZEhYkwM#k~>OlpY9VR?GcBIW%GC#R!!^e ->PgRzqhcFn>#u(!5nPQeVY|4p6N{T%ctvJ-Rf!4QHXmwNutZU?M#m;NQu@~ZeZRX?ITi^{3gy6+K_gE -oejFaG;0MVA%HUia!_R(c;_0UMc+|gCs|mQY;0K?Q*Q`iVWc2zuSL`TRg;G!6CXekml>~tWgkNosA`4 -fHky3pcYNX(wp&@2Fj4VP!`cVRv;r$dlej#tCs2^#Z$W=a>xqt{u*IAs!9$y^db=;uL)NLZ`YFiK7va -mS;JOTheR&OVSQEimNuu8+35-lAKICj6Y}?*@&v9oc*X#=VG+T`f4ZF0$_9%Vb&3iW9Jpx1?yJFYZdVtu+VBKq5=x#`D4}E$rC&^>vQRM!)R^hCp -U$-$wJQ!;3d2Ba*>UiB&RSz>LziIU)#MM86Ua>y`7|YlsD~vLBq4ezjx?%9v)l?7-1N=&3h8u$nsPf3K@gS^~9Pu3(0-|W0%y_B)v7oz@n( -z3BO|HAwf`fA-kZY71gm%U?8+x$onRE2FmS7d|Z%biH*&I1xE`_`rh~KQ)Edk+(vymtcm@Wd4+d@t3_Dz}RDyR|R@{p;+H^vTMvOM>cUM?>p>0sABoo>*gV --Ec3Wz+_J&hu++l8h*sa{8mjc%j3%7s~GkgvFK3nz$i+~~Uu_e$0SQ96JH!c^!pWI%z53*AVr_X0|J8 -U^c1UlH6F1cpayfy2pH4Kg`V37S0sjh(ybvM$mBge@%PGI$G1g-j4lZ*p2ykRUFLGP<00$c|D#ld;5| -*PfEG?~MKhqg~KD7FwS5#uEeu5zA)pDt4jWFWu;ahN6LnsqhEuio{6`?p$hW=o*`+A#gQ&SQ$XBEB2iIp21aQC>w!ez<)m-Sy~8{PinwuAe=M8vaqlPgo -}91ZSA9mSYeOF0TzSE0QHgi{T&>?W+L7j#WI9fO-{qQ6n^jV#XaRQf3ZPXxYVdlLIO -@uaoz;SVE8X_5qzXrdDpz8zx^nk;n<*ND8wnJ&I-ZonxC#1?Ok~a5?eHkLSAgzi1+sNp9D{Ruwy -hrF5I9zi5t(v(6WeMp#pC|=&)=way(B$xV_dH&fEaXQPwVyW4U;-d@>Av;?Pe?be-K-?es2n#~{ZC>F -1?u?d=Udr4(qaC)r;}d_y2oF;Agfo81F#k{NqGU0g1w4BI4aV -aWYnq_{V(p+zOa6AFpueEwMChUotP&>?<VQ~y&#BzNu%AbI%@Ev -)pL}MZaQ=GBRnuh!2Beme9rlbHed9Kdl27%PT6;?<5L$*{-~XGYJa3@AcJLGOA1fklk~9P9}^ce^mSS -MFS`ANbEnUk5<`~IA&Zy82`HU3=O$0gWCY>*(ApeXxU+Th#UAZonB(H!4~u?FW?OEnZg=l|48faauq4 -lqI{0@*ajWP(2~r! -i&+d(tj2ag$i|9U%8Vv}na^5UF7V{tr+~0|XQR000O8HkM{dy8r9j3K9SScsl?99smFUaA|NaUv_0~W -N&gWWNCABY-wUIb7OL8aCCDnaCxm8{d3#4^>_Uhh%_ImMxyhv^*Udb%uVe(?cBr{J6*OU<3S`OBc@0W -0Bx(cKYs54APJC^?Q~^wiAVwu@B0hjgv`I2lcY#_zBwY5TF-yLC!@(|Lf#bRPVmi@BH^1UdGYM|%Q^h ->7xF95>5g2`ue^+4*cne)E?G)cLDUr^=R0*(>+p-Xf{IpX{F5ivpZB}eHKu>*J$o -iLH^)IPJm8j-!Cr1WmloZ>ND$>3HT-SUvBPmlXQ8CL6vu5O9ygcO@v-1R#MI{m@>#1vw#M-x&;#vJ7# -98l3CMb|UBO3-l@9+)4R$@0p?%XiA1w5{1BhtUj6qZzqt}-?wJdbZ!els17kaFPgqGmw^L(lZHFphJ& -WpNCn;-61Wk3XDU#=jiDU7ns?T*8DP2u44?zT0L5)|Mcw#rS#jY)oo;7vm3?r}H1iz&Q{K&5E2Y#ycj -*uV0ORq*Ai&D%%m5pUcI#68Vu#uGp5!`IaZ5kf026VC@l=+vw)`m~3gz*G#Hk-NjywNCRq_0yntYdFb -Gu@rab707lC~9E;?NqgW~d*nIrqdw7XdF2)BEeyUvwKW};d^2PY@)d-+Jna_!VyxwVLrDd7%#Av4kjt -X2-u>VwCXr+`9na>d~&=;we`MP){jW0DdHAwu8P=R8Wo(p~hDzIVFdwilL3W%Js8gz3S=IEA@DK;rI-*PCb=ejU0X1hB%xUbmJK(^tTh)B -tc&N2+;VjV&tM4XF6cgrXmq-Ke{mU~E#IER?@vId1k>gONQJB5-;*cNccq4W`})(f`Cp?i--VyZXZ0o -eZugGQK?d<^1gU^28|RWRHUaG;56}3m6&4TMBj%2S?gsTKk=$)$3;|gE?rY*$oS~MOv -ZltgAek9yL}L$7W_6+Y!^|72E?#05LLea@PW*$n8)=KRA~b4$?uz=H}D0FGfP@ki6cl-Lkp -Cj-yD4iL|V&+boj#^ZKT!7KAHaMtxCvo0EsCg`1MkJO?;gn3sTks`7sbT^&2^5)7NuKGk>@H!YA}9Jk -G*NiNjtI;ta_}~?Dp6LT8MVs3T96mf_a2c_fg2XlE!YC0b|620&I59AKv)N#=c9TGy93FIWI95pI2vi -aVflZmLNUkhhV4(QK%kQW`~ji0uUd{z!0BBxKpJA=Eh8G7p#yEJoq_WcD&gH4yzh-OA9aG=(QOHX4l5 -}RS3Ez2A!K}*a$#&zmK1!1e)CoHWb6RoIXDWMBIL0L;F0qSz*O9F!0q;`fY4XJ0m57Sf#ayM2rUe}Rf -4In$M9@M9FBqzTrkFf2&f>v=Bp5kBJxJI^u0?@?^F8V!ge(94h -3i$@r+R`lV&~~-lw{z)$%VGkUqGQmSggK62()XA$3|es#0-R_EP*_Zfa)ZIQBXN!;F9Rf#3U`vGaLmj -zd=BjEa7V#AZ6sOVCKY%fj%PK1?f2O(WpnLHg&7X@`|l!zLAb|j(N_}^-7klXBM{@RC7jvotX9L-!x6 -*jDq(7Wt59cQL%w6DHXd|RQci*LnXL*2tq}Fy8KT -kiUnNovbW&8#D5eb7K>qhxXrZ6}$vf?PhoSeVASbmH;R06_!(1L`8j4*QQxLFpG-vv{Yy! -ZSoy`cfx)KCI?{{>*;E_@%E8!9->b*u!u!VqIi@G<&lFzpPCjodI=m>+=<%k5BW3)&CU&(SS?teIpnnP(nHrd)GA=H2}DDajv@1HVix!lJln0;_*(EhG%NSg4cAjyaxtKyg8+@ -+dTvp3dwM^Sn&H3<@D*9sV{pf<){d#ZeuiX1Y%yB}G%&_mZw48L8*L>Nq{f$dvqLOWA*ICElP5aso!+ -~gmtNJEj;Te-a#tvr#;nW!V26?G5~M`a@WoWC;N(fr5sXfZ!gm)ZLSPlSe5Zd51R7HkMo^09kSK-0Dd -m|d))6gofH~Seg>euvzQ`BMn%~7HNx07proC9%=(rK<-l2F+$Uh2kO$9a)blCWNQ=DMP%^6EgfpW(4> -JGIH0+Nh+aBM++w}&jqkZs9hLXFA*4N=%s>t<<|-1jodOnRmzH62;kF*dlj2xez8KoZrxo -s^xAwH{0ate*FS3Q7)qe^G}j|_Ms{+`8ot(C%RR`5D)8#MaJ}VOMpkeG(G)Xoff=`xO{|T6Tuo?E$L -vA0-aj~Tt7J0e~MF-P$D}yr5VII!d19oX*a3#TUr9908W#jP<@*Xd1;*ieL9hzp&u-`=ml*okx3d#`x -VyfW=5yU~dH7p6yd?EWTwp-6Tdh)b>;vRWEQ2M(hf_GtL@|WjFBrTA@3i&6sa|F61&l6qxXBvOiBTM( -+)D=}GTQMFMBVEb>f<~v3*zM45uxMBuPINH~hk+g*xR^REhNUQS(>(#_odMq^U7KQ>&}0N4pr5b;(F#0HCA6rQ@Z>$X~ltpW#vCaqP!UgB#(MH;|>{@LvHL(cL2&w7+j -UmupSpy-%>rCDWW^n*&^GBG?5(>hi3$uTyh3u;=XWz< -WXZ>a&RTPo$yX=c6V0wlgUl4;ONk=+0kZ$GoyG8UkF+EaL!SF;a!66nQ6q6Vjux)BO?G>wbNH?*q+gf -+3Np69UPO?`|*KL(f6YKzDTcT|Ld!c7WrC<<$A525s3wz^swyf4L+olNhwi$+{?j~MK2a-{bY|Ak$U> -qt|=>Oa3jkh==tVa%#+pK?}=R2&^{dLPcyC#B>Gtx-H@@j>fwWcFyM*yoG#K4-Gu34SH_?@**pQM?IP -EoSbP-SoS20@$NyQ_7)n1e3jVfFNBv&>U($Tz0Rx(-yPMYbELCA%EZx>-LA1J&)+^m@{d0Mc#uNCklm -0(rfk9{R6!Ze@49Lo;?p5D4}&ep~mK|7~ryU0`h3Y|uqOuCELJD!=hEPN|}Zvt9$U@*DId9dtVKX=r^ -qD5@H40|L`W-Qt5hL+hcBe^DvZ0PGZ9elyr8d2e^ZQ>sTfOh~?1w~vP&$fD72%b@;v)dBK#pkv_ElK0 --X?}7uV+YVsc_F-@EU_8;XaGJG7=JAT>khtq-H5K=X9k1?o-|$)!Vp~|a4*Gnrd?0gOKj>pCD=l0QM! -y||*Z$6!roaaZq?-`DCnR;Q0CNHeu^EHVz;G#^1bvF}O4p76>KXS*0WYjXt*)>;E|m-om^~VR7mOEd3 -))u?ZTR^X{2K~&ecv;baaxX7i5qwX*(wz_tBf+EhHrq+KqsbFj|c;9%QaWcMychu(|n9fJ)|B{Y&A4{{=;@ -<8c8CoA#^&rTYI?%HFqOym_J?Py}0IOiL%xp@dxUZX>ad!qIhAOpXXIyWB(X!t$APXzdgc#O0RdQXN8 -~aa$h<-I&~5iVMjPm+j!AtX1H7ECua7XnUjI-Y0|pYFc!?M9qsFcL1e^W?Co*2M>|A}??ED;zVu=yzU -ec^!V7FWIsiM>aTuTXneF3?WA>(hZ6Bb`jOw1Y9qcu;TOKt{y9Si0K|DxK5n;-Qy@*)rG0DE??+p+wA --hrPWv_ug0pabZ4kfTwJ<>GvZ2%#B{8kV_(|EPIzmt(YpoeFII?$tk{to!_y$c+@{4&^!H9NNZc2&Eb -JroqA+_$YR_nl@l%IOD4!mLE?D~-ynN)9IR>jwbBJ&=cqufbp7J;H?+8r2+P2gucGnTHXIX`);8*ip7=r -`{SzW21w*4lZpyx~zGR{hSI-R*r;mD$L^9+l|(Slz7YVS%CG9uv`R$XK@_7i6tBtMMl6`E^{s9sIb&q -8r=~Kf^%VM}Fm -!eSMC7N7Beh3+Izd(6=j2clkW^WNTQefd#NgNQbwsLJrUKF5P`x@(vQVs9sYplH{5m+TeWt`%zsrjww -qC3oRJkox@bX-_?(udr69c)l*ep!O)}qzr4Uf0@5BDWfH9uTphfD?p;1FAc)hy81 -eb+-29{v;Ld=5}d*9`oY|F)tn4M-}tR|tmzwwxXru4a>bj~pX8o*Y68;U6n`LYf6Bi&@!D_~9YHr@cU -W<+t>{;OEA4@iCe~Z2>xjCigWORN3(4N0Q7FsUF1#7hU9s+Q#tuASmLAre#~L~j_sBzV_X~YOPTIP=> -BiY@!^jzS);4m5-Hf?Xs+32EhbyjDRdUT#REW*t4Sr}8N&L|0P;xx>+;r6rJ4cbc#-`!UHP6$5vq$>F -pD3YOBKdESkDr@EJwF_2+`j+w9B}9fTA!>0E`0yz&N-|>1+GWGeBqC73+1-e79yH6J;m3!dOP`LvzvT -HMT#{920^-#MOG=?_D-(ph7pr4Lr&8HlD3E7@cXBXDt^Q2PqiR-5m0#4K)k6bl+hVq#q|P<=6j1sG~Y -Jh(QHWkJNcLWI>CGa6?OnkBYbX(k;w0yo7uhZ7AEApT4g*T$LCA3F0fJM92&;*woaqn)5?2`TKC)@9- -wYEZlhMGj!G)%)(3cGwrBk{Zm&4x1Gq!T@oYT60&C-Vz|h+)AHH$B=X3~80r8>^bxK$5Y`t2}SBkB3cw7uvwICmx*{I>!r=F6!|YqML4ShanI5 -((d_4Cgosb=1(rI@@(ftfMp8k$=E2;khP)h>@6aWAK2mm&gW=Zv!kPN)4000170RS5S003}la4%nWWo -~3|axY|Qb98KJVlQ+vGA?C!W$e9sU=&5RINb9{(n%)i0TK-Fh(VE{q60`A0u#uDAOr_uX2@a!t{BIWR -SeyLy95$@M$>6#)xCOmy;pZ-(OrD(E~_H=0B0CXz$XtEqp%v?s5NxdATb#rq`z~jdnO5r>%HIa_x<&u ->8`Fi^*VLx)Hzk>)Rg_~LB_-|j2Zq^m0@-<+CK;L@Bb0_%N+MgCi7zII~VUVEPUtU8@;!$$z5H0*KM^ -o|04I6oA12yE19~tPjgC7Jg^}CckIcF7R-lt`MSIXZu% -awU>STF+hlTz2x>=yjWhmtu80SEHorB-Y=JU;&E+^(U5LPW0+UqS*CW$f7^!S^3!a$Ms%smFcpQC{SB5dNFc+r2`zh#)27?@&Uo^<^HG9NTB@3Rb3e -~4j5{}|H0K5uNGoesl=!LQ_RUC+b2X_ww0?5lY&OfJK$xUzQT&HT-fG)eFKWcb@N7!K-nT&aOE`>w_FL#eG>?g3FtG!oiR)Qz0(oaFN5Am}v1C)NJOUgOS -U6ZSRo!6oxqvftutp)?-tHCA}_gPf{qeh;c0@ccjxCfe=6HWsH(j?W|ywW5H5`>}2!9!Qv^s%6oFl@+ -vGr?3nEFI-ts+v)872SB!!(q`F?Iu9JKTwb%~WTeyeAF!LSEVv?(>s?X?AgLSaK`_qgc6<*i_7#Yj5Gl$eq5V%3$65r=F7Q -*!&igGxn(531vosAK>hnJ-j&c -NN$7Bq`55i&$ai-6lAy*)0De?!16EDS01wa_ao^;8p*>${%RhCG(bnx_+*s9ctt#k~honM9wD{&xp`K -Q6Bb4tV9p@-sg2l7c>Sao1jck4SNl2)%58+cv?JaA*R!ow!6N`Egijtk3mCNbSy5&wa+X_Q2Uds54J_ -FPTN!#ahG-dDhiJ5)`T!Z6WV-w5tZ_YQyHdMw|vy -7^bq4=H#`UEaddQP2oQ51u`nGc0=M+kBf|)LFr%O^CrsErUIC+TRrN6#FeBAMyAD_*Fm%W5wby<-)V_ -n@`0AfDwr7161%j-Rpb>dF^?vCwM2urZm5H4DbBkI`K+2f6Y2us9LzD<&yV!6E^;s((HaN|769v2KcHlA#1;y4BSAk*>-ZJa0pwa)DK@h;;_UH0< -1%5KEmR}U8t-id2=3vKi?{*E7OJd~l1-;7O=tDw+zv>71d^|XWNv42WhGWW0c&>aHS-q%tdy)|Vq{S~ -GR<@fzyVkwveP0>0q#=0jFNFfN1kaQFGQA*ZHBJbh%?6XW?!z7WVb2ayOuy3AbcAPyOI} -NZB&L}ZAa4d+S0{lqh8&Y(hh{w_{tvSUTzof{RX_$1(W(x_>m?9d8N45BvUOL=@7ZH(vYUp~pUL1Y^=H!flp_27+HVx2d@?>9ew -wZ0G!A)rFwk`lt4L<@d)yu$>y4g65}ZuuvMppgWaaeVP3pznx}N -UDwx7VuG1Fjn}I4&x=>;~n}aucmeTjLWs*`b}zLxOxzO_J}}emW#cXukZoR^B;f@209Q`Zs~D+ -lbO&UDbS9X3>7?%Z?EGkoru1TX|V|CT@{Z#4zV)LA@0iwmKuQ}np$e?M}+SD@S&L8DbotRmmmd>;}oP -L`2Ae_WlAtX7bN6BLf{xA>^&4S?KcH6pC`y)&AQ2M7`w#4dbfRrxLyeDQ7I9rluXne9rQ{2avVV1ZV% -aKMiw>2LtX_UcsrU0y|!l{O$)|Tk*EpQXrIM<@M~DO$7BO}s8U_KEhJa5_D`T9dy9}o{*uKoEzEG>jK -l>&TS)m8KnNDP6v4{(F$_e(EG>MPtR8|(91A%!$Qqh62XZP0U}df;unW1QI#HajFX-%ad9g9%!Qpi2S -GK@PVRe_2)l_(}O3wBcC$7&bNu{j5oxBS9Be)9h2T{B-BCB-85m;a+!-xSAeZ#AzDdeeP0N$+RdFvTm -=ByyUkfC(7wn)i97-r~43OR&O3@ksi7+6Va;;(=vz+W)U#6ql@5pqZh=m@^CWbsH4%Tyetn=hioe~dTF5yb=xovXj03vt%Q0;+JS&r -)5Gv>~U6;WNrfI+TIz>uJErZjg@vfiKY)vO^`SoSYa10OXHDaqKJ%Qo*vS2RmBjtDV_lHj>iDBzv^3KH!)3C_G0DV;|X<~D(Tg` -R>b_WoX7|W^~E{3cw^2EhRyq1Pi4IV(87BQ6T(z5jDpgp&TYsVGR3UeA(Skz6bnjeOa(t{Pn#6OSDS~wf181tjb%;eV{#DbET%3bo1eUfOR -{#ztv^+lvpgln?dW4KjB%qBnRYsC8fpLh1x4{YyU@3kq*Qc%OyaRd?DBPEpoH7_SRZZZ|rrw-2JaYgh -!o@vXKxYv#(&7%Qu=0vkY~y{7--pUS8KL*L6k%0A;W0s=Z09>tgtEF9So{WIJsCaBZp_?g2$1tfa^h3 -`FwxD(Sm0+1|R&C`ath-gV1>>%?Fw0d>C8MMC*;W$RZsVOAQPvHaG{oxMt)o4l0=AQ-zutm|d?@p4&A -b;cSE(7gpJuq0U?Na!Wo;@gBh%^=mO -!r303rPF#ZiD-$IYNoX$+to&64LKZ)%K*$lUprvzAx*H3Bag;)NioHV^S5amUXQCEUteMC}LAaqEX`^2; ->rdgYwIQDhq|-k4tr|hx``hX&}z-c@~Fomxo-IucLgF80+JQ%Ll_}lH~p3r{lt(CYvmeAd6FyUS`ZiO -m|V}LU~llr^xPP&HFTcH*ZZ#pl>F2GR73YMEelU{E28Oq({S5z -^xfYfe}aGTF!^F6@aSjZ%>`SZ-t2`Epc$TzTZYyO8)Raf_36F*Zvib&6*$QHjC7ycr-yZ9P&J&eX7`@ -uCKwL{SU%A71|rxi>xD&glK{fUz}j{ugQO=D=Zry||()`V@Xv#bhCtA -2#g`pG|_Af}?$eiAWzWgJ@cb!VZ2+!d3MLB)OAmIyLph1}(dCiDjE8SZRq`;aKi%V%s --joz)ix3PTY)7VWRrKKbXQJ_s~H`#d^<$ -_D5<4zGE3g#2{NH|!eR?|7|!tSP@-`zi^Cc5iP)tWV17Ob!?q=|h7K1u8|3Lnw__vrn1>x^fdE_!|+3 -rebvg;>uZEO^BCRFDd@$QAvS9%LCNy+tYRF0qLG3^$nxv2Hm=LNM5F(tauK$#qVrk~1Cn_f%!5TrwIJ -PZx1~2TiCM4P2(i!k}D7JGvS>IzCn73n}Iv@WrK7$qkjlU8b?;Qe$2-4Slx(mgU-*9p9^^X>m388UW-UqR90Tvg9f@x2HgeIiB-97_IS}TBg>?- ->ia+)OE73Py*)ljBynF-;BT^NJA{KAQs(1?XkVm&pfrm?-mWRUnv|M5int5fGo^G;$n*!rTjzce_kKh)fO7}Y^f|$28#sMSB=)z}qCqBn?;By+ -mXJI=$Yc^+Ft@z3?)7O|b8kuyQz9zKN*A?%>>#+xtSOAfo%+_8G-{!3he8UQ`)q?R<^=(Nig){^Fw*? -co_5uc5k~-m;YGy($NnM#m^q__A#7Kcw$g-u822np&vsdE)IG_kX>Ql23xuEB)!kw$f4mlv`#f`>71O -VO-FbxRf#j>?sN|20K!Yi@zShvJR7BRNN7kS(W#hlw?dqi%j2Gc7`vIDBF1X@OT^eZ{ -vvW;Qev`Po`ifOdNZ(_R}FwuCBt@1EbBEWsjsFQaHjPdwS6Glfg(?heJ|%f3emxPHob!98-W9ye>8k^ -J40fuoO|FuP$J(Xl3dQ;jl7Vg^GizkxgyEouMkNZe=&&KTwsd<*?Wu&yv^W9#}H%UTh?d<-szu2U&G} -n;@|>*q4AFBvlOfo!J?1SL?ah?kn*Hn@n3zwM8&s^=`GOzC~DARU5pXn{k2TRNU-jIR6v4iIBTo8<&Dve9UKZd!K|sCq?u0)sG9{p03!O+z$jJSSSdIEJ@zyAcvc*F440H> -0)ylGy|3mrO3y6chTg<1CypK*=AYZFVZFm*}Vt1)UF0)AM&C(2MJu9srEtOiS?&7n0wACBizr*3zXF(+}Ojeg -7K?cEMoi!3fT;c0Y<@{k;Z1}L}qW{``KvB?=!k2n7vWZy;JCpf(*$f~KOJe31!oG=V3Aiw<@3euGS(c -6_#2|!epwX8F@Lk-e$i~TG=yx}&gQ-kCuEoY^1((=*b4OTV2{G=mAw~+f_Dn?|AmYG8m+eRSgY|R25t -1FKh101m7D_6lP3`$^1Iaz2&@%$;|SB!6AknRHdV71KwsVhj?n8ulcfhp%SeoeWP6llY3#^7;$KGKTM -$KS{2j)VBz{WflnZhj<2U%?LgdVD{91@_U`Qz82L%WLp@?14b%Ib`7GV3}D8;+AR)tq+-}6d;&3zgG& -jV*n^6i0zWgtPn%n(vjeoK1PmdOfpED?b6sJkgqiW5{uqx&viI_WxdO7?<^zDimD=yg!OmNMZi4WJ|l -U9o}7VJHjt54>k8DgEV|>gK|6ObRjF;~Yh!H=Idoo!1m3 -Vr{x=dfjQW!AvI}aOia9;z}=flGorZO{pHZRYL!H>#uMyE7fUuNF*2MS*2BZ)*$@lx0Sm+lx(hc_S)WXNw*S^z=4nIL?}69 -L1*X6t(Y$&mvyd`GB8-V944>)dDY^0J^K2SL$@#FHLPtKmg$j`o1H47Gs@tvtf=&5$cxOT_iUCK4n-)EJaoieB&vA_Y)CqE -!q6;x^ngw{JY&6AxmxfN;^-mR}w89q-=9^8cIqFNK&vaLbxsw1M(k_` -9*V`IhU&H@RgF@;uxb;p7TfqL--I7%%PvUB?13c-w2hUd(05HFFu}WgKJtD25M -Z`^pDUe8&APKxPDrHTM*mlObjo!tpk!kA)7|_LGT31?utUsH-jNYOA_Bi_FqAjw%WR-H?pCa2GF90mZ -QL@i?wyEl&WvC{|C*+fI7)B}*Ns$bxiwAucr -)3RC@=;3Ex1FmZ+~3MzdYlB^$WUSniG6MBhPi$0zzuGg=+}saxVO#lquu1nByDs1uXpLxb=2Wv2nA;x3ay*4^h_EsC_2^<5CY+#W#sqvLCqyMry~RAo9O}XNwUC97@xc8aL^?3b!f -T} -DAdoZe!JP87YcLnspTZ)p=7HlqJ<3_i~d+p5-vkCxqIUsFMF+3{en7w%oC#OI$M)jcBVz&3yo)ilo#( -I`l^P{{NBHL$aHCcQN-KNOKSR5T+uJ<6yBJZ66w#<@0BMMM6w1pf6Nef|!I6rQeY%PUglhgV~+=UnGE -DRSwX-=Kii0k__Tr||9a;sEzFmR0bs?+=^d>lo|;(e*=xK!6Feq)BP{cP<5vgJw;eN*_a;A570EoLLX -hA1gTJ))I|QSl-Qs*Vdu1l`mSHZ9D~ -NTdxDO^#im~08Q@5r82XX5iW0`y~ga?Hb{Hp-aXCfm?=w_yY^QUMLY>74nGsHqX#esycp(PD{z3}Ax(*el4? ---D17O<$-U4SnSsdXkqc(hmZ;_3CCdm+hPUbD3+t4Kd9fAy@9uI%Hy4?_XvZgO2bA8(0^7V~;=z9mC~ -16nn6?Ibrj4MZO_J1xk0_UFIrGaWkZ-3$ffe^~A!TwDehc6@BLL647;YUJ%<0#|9IiExga{7-$I~KZw&KH42K8BK|!ORtir(cRdPBj`HY(2NDxkJi3Z?x`s#`-1alQVD?A -!<^rQ!5lGU1`0Jr_Au1cDrWz9gHistU<_7QcgO2~cwr04+Xmf0V6m8lX}Q5hnj^&?UURkHCUJ#{sb4X -!UR+^gwD!R>Hw_wh6(e)dB-6XlDEpz>!@woADmMI4NtS8IrUs5}tVPj@!L9n6P&o0xa5bfNhYfsLcbF -B@0cF3_kXtDCh+mA8Tf(uV@E6IZsoIeyW|opckZfWaOUzVt3YJ(BDih~NtzIo*MQ6-BPj&X%&u;j&?U -;NDi=b#jnMmc+vF_yhMdo_t?&cD6L*ST@m`N~0ko{hp_{q>7B@Mw^l93b670;Tu&0Ywpbj)F;pe9|*msKB#;ErOqw%B=W-N>f0KSiEW -2};Oi1xfWle{079sXvK|%6$Z8pgyi@)|@NjNosa#@k{%F;_}Dni?fN^I2 -R0;h%liKT`0Jyxr?6Booir&N_57=ax1kV#X`eWvA7U7NiSb@RF#`;8~c^*wiafBmN>+p+r7M#1qko|NzfpjCL}S^ -2Smx^c1xRQ!9bbkqEjQtd#ZMc^QxKXeZ#ww=R0_Y^$f6W>yoLq@U248&V4N0wFf%QGOfrMd}9&y~tmZ -n6P|5#%UH2;+K6EV%#0XSvA;D{f^!0QFS%v{sG^p{ZUq-4eET0xoO61zh4bhg5eQIx`15Bh?Mw=@19* -bj;wZWriZu4c)k@4Vj+p@Eob4H;zlW)Y2Um`Kt;lSYe2ZT(9a#9}7rVMikVsLRNR!h~sgUo+Ox3ogj( -y65tz5iQ>1Zve4GQOL8l%BoZHhp;RDX4KN72Np>D5r%q}k@f<^?MFTw|pwc2x-s_D(OXAG$ip|Ki#{| -vaIUpp>pz}?gBDUltCY%M9lDrK^fG01nj)-{_5QW>5vRW>WLE>=l!8jO5nAgL^jb;O%;Zcux$W{LYLE -%yct?e56RwM!UI!+;%%V;2zijacIr5@=||Dt+XVyRH4zJb7TvjU&RI|GO2gH9Zpd1yv-tY8ev1xxLuvOuVqW{+$bua>p1PXW2KF-I8!d0gdWGF|JCn3H5@mc -rp#bbI4XvsCW!kVuU5gikkY%3`2BuYH|p&vLCjRgrSJ%1XsPSUjvSi>(EMMHp%eMV|q%KsTQv*CC^|l -1+)s^azyP1>ewmZR^K=cI_m2#@uqZcex*EWjB>nb8u^69x1`9W{ejq$xH}IGZdGch)&QG}XQ>QA -j{wnG6)qw}^*j>@i+OUC(SguCqneW`kd>VJ3fqF#2joA$q8*{isjj-^oM%aLTB{^(s)a-3CJ&~g7-7M -)#clUOjZe@bk$@jWE2!%QHlpE4!Bb@_Nzj!)G1=sk&A|4dW490qa3?`WN>@QQO1;nr -In$moF|&x_hsqgk=|xPSbhVH|9$m(uSX5%RCy_Kr9FDzDq_ZSej^&skw%f7VC}fGHtBp{JK}nNYDA{4 -g#O7+;@c1_q_<1EU6DkyK0>3tB0*{6XoI~V(uq$=2hw1SOovScWVV*Xi1T=eXDyWz^wUy+cc{a@N#KZ -myBogUdx6-*@BNcD#yTD|B??j^QwI}}FWdG}Vll?b|%u -yLjxc9^lPWJRIFoz%NpX~EVz!I{n$YTqI5N6Q0kqJju>coB*) -NkC&#vbr`$UCX-3$`mRc-IlD;Kbm@Re#8sf7WnVQL$(sjy~1M9QB!ndpDAu~r55hv^FXD(7(PG;a0h2 -n4S)=RZ%3Ov#NfNvdD7-F-Van0?djo5yl6~zs@(96UA-_rhlC -eaa4~vq}92dIgL30#oKTO=gPW8QdO7z2;VJ!LMmvPa8JVS%5j*L5@LE3H%t|TQ3ptGD*oH-Gv^NpF4WzO_&cagb -Nj_|*1nx9mEI*qTTyFaVcGHZQZAJdpFbHTX=eUrbrI5R1YcgbAS!psQ>?4-c68(EoiXgLtnsDVyM6DC -m5OA&M=1kyyEM(m1k1=`=$1=PQKu%W+1qqAa=i(U2}s}o*Z2$6WXi1E{VOpp#ZcB2EKW9eGx -eOm9hwF;8##X#Zn+%b)9USMLQ*|zR!sG|XXUC+v>1v=Rlvcz?5&)5h@qzsO@^5xIsf0_V`c_#X*A6(0 -vtylqB2-lseS*NoM4dwt(nrruo04)O=*e%_*Z_RI<3%VC!t1T>flxjX_}2SgaA7Pup_5*LsLuntok}N -jeoh>lp&`ym8p#jlarJQ_Nq=$UNPYb7$;RT>ihkCYAQpc%>X|a8dTu4a`%=1*(up_iq>}rcG9RMH5s7 -sawOl`i30$g43tCG4rNjP9NW8pgf!Cbv -hz&%gm8gUtIJ=?Oh9m0uRw_X}oItKyKk?&7PVOB3cc-gtEqDBv4Xn}*3;0-nvhKw-s(H)d> -y2NmwJ-lWtI!%ZLX+j3w+_C!7qQAyv6^O`>xt&&{P9?^mGj>5LSw^X(B2Fa}Ys9)(8vnd6V`+_KLQ07 -trC{JR1SZHS&Be)=rYVz-yIu3th*J+_{;)7`1sktF(=2KH_OjNA=D<**z9HXUctr!+fAIfdI(JjE=w8Te++s@Q2A6>^|So -LWV~>1 -)VM^whKUEe0`a5W404;WpM3#+MadZ;GFp!cilieCk;!I8B2)fpAd4@(4W5{Q{4hyo -bBRWL1g2-MN9xdso*nC{{Rf?YnV3V+AlR&Dcb@R~HtQdw>vHM}RUsn|T$52_d(fEa*TJzZ5|KuwV%&1 -`-NEOQn(=5P6ggdA78Xm1arRIiTBsfMOg%ZR{P(^gWz@7n;jZh{jDHE1qDv$(^7;HPl#CRO)JS(0f#@ -VVO$E(_Lh7@X6vmkSR}{Z(g4+4{0>22JWShVu5vkv5S;`tINHDDK$BwCdW@M>W$}FbXc@wHIH0}BpD1 -oIlvmDow|g*NS~ebxt~4>eZEbfuhA#kW{?>m9FPY-!S9FR8|W;b)PIs7$4ekszk55RFnpREcn(i|R;) -gUlkwVgDhoPVt)n2x&hYTX!tF6hWnDJ_4f{{jTz-PspbKoyYJIU15Ce-@?Nz2<^p%CC` -Q{q{$Hmt%v@Wy^f(%u6zo7iZu3158JyELbLl#c+q0M3Q{Q>a0 -m3MU5A|=1M8B8Qsdl-2J(bN6icDSPGCqL>u6?kz$zQZ3o5Y4vNVg6vI0ZlhG)!0H{MG -x%~AaG4azy!txVJOVOsx#0B0&^VA^1v(Y_?usQmCB5a8M3BnCDjMFRn+eDHvDnq#b%p_s5*kaZOS>Dt -u?Z&p)`?%n${t|nr?M?J&5F^vEzax~!?Rhg;%pTNeAvzn9q#f9^=v2MfO9vNA^kTDVu@nc~xgma@0L%7K_bG -k6L{#S;nLJq7udfxg(-qipz(?%h#(IB2e2=pJ)Ar>LTO3q(`@7p>5EPUU^`KRMs;QUwOdp+mB6yHlZ| -5!Xl*@5(MF&*1r0B4wsdugwWTzUiD!)OX1-Bt4A0fF@4Xd1c0I##)7mB2aw*u+p!5>>%$_1VUt)YHzZcCt%T1qjDM)y%SrayDsEVgmA&ENR!1!x`R_uE}C7|gS=kGJ4ACQo~Wo0tjtYV~0pDmP&Py@`j(Mgj4_Qr -$M4ej2PALjhnJ8ZuKi83wq2P)#Ha69Mqy1c0W>5jU=08ViiDO;%2dExG}#OG(f)WVg$uW)SVBYIP$9xYrpeh|ql2qv)(Upexrxc5URNlkqBv6^UNE$O3dLie_VHW2?Cb3 -XM2?6X@{W%565$_}jQ&@1!d8?feEYb|-(O7`qD;a==SBt-fAEh1PgfzxY-9$e`ol3Ne7GW|_X^)xBiZ -0!>2yIjE9a3AwzbMh+lFp*J|I1*PPE97Sg(B`{8lc4)Pwp*%t(dcrt|IDebwMdC4wEo{_cJTF&}Iz3a -z3+Zuc2N-yQ@>e9EI!g;>0BFsl_=F+#XKoX2(W2-$c?xKFW3t948Yo{u6S9qkGA*E?xe8ri$Zn9^nUN@L(3G#9Q@Axe1ka5>{0PHBsbN*Pojv?&>r`731F?MIimJmsVZb>8 -q4>2ZxT0iqXc*Mau4=^HhFwiQXJ5D0C&Ti&6e;&)|tC7y@1B@SE@T6Jinzh4{hccbm-fSWq}?b{7Jb)q~f5ejMOr~1aG5#qtM^v(-R -}wRA$39dZ+ly^-g^cYd3YBhc$6Gaw3Kwrta89u@nM;r)XTIOjr0OQzI1v{2_jr9NbJ>j`OAIV6^tT9~ -7K5MxC&Dnf^LjygYb=%fT#KcUqh9{zkpX)7`XidATs5$6dQdP(FJG*K -O;3Q44-fwoy}X>Pz41a?cX!_at^4@lM9J-kFQ|2wABk(-du|+b(SpZKo-Eh*uU%Ux2Ko#_YIi;E#u$W -jbb=e*?Icxk+e=zE?a5!IjO>C6J_ei-$N4>jP -m#?`?|j-yyz#c6b23S*>s6t3w0v?LQyipB~b{niF8;_Cr4n-^u0Y;Ttgh58c@jG;6-U;vc~8sY7x6R+ -kULFFlLk$8KZ!k?LBONV}<4+@oE4BPFA@U1A<^%Ye)@%%En~lf?B_laMDnvI=vP{$m86U3f9RG7HD!E -2WTeJZcblJ{q5DeNLUDumreucR(F7|bGT=ow$rY>|_7qt83^8O0zKkc>_Lkaj8p1Rb -USrlS$Dk{NiwztWQf#5YS2jky(yOwfi;Y0Cn9ABln@IT_;=rOM>Dh$TI@fb##Rro -;s8#il}D+_-U|8BzXv4uN~`gah&GYenD?-cmPtw(k3N*}8pu9P;eR8p{mi}9m+#WH%}R22~mH>0&I;M -zJ99W8aGIs2jYt9RQ)VQ*ssz8Na3qM>k}}@h?g-fN%dnj%Tyq& -Wy`B0>NKp7TtuNTNV#Ks+>DZWH*Pn&ST3`c+d^(BMgCTvKz06O{590!`sw -_N^gl=;FC^Vxfd1fweo|!5nmnf$eDgUAgxKB^_oKo>x4s4)RHQ0{LMdz}TqW -W1k@u#u1m*Pp(IC|%SC0JsppD|uA%biLF+Pp(IpcBxCLs3wMhR&yz8*iEe9XcSK#J<1?@)fj=EHq)k% -v63};22-X?FqR6_!1&}v`%I;B}4bL&Z#N=JykM@R|hdc0wh&fN}_IH8jwAu*~lQqFoQLkN&Pt4>SYKE -+)FhKaGbXtb9)NLAPE8m{<eVdUZm?WWBuIRu0^!heixBX+fLAkD`z{XoarUcnii0SW6k6gG5Et}l#{D}Kd%;73`yCr9r`T?4JHu -E!FQT55*7rz&8lsc_rWZdXh?o0se$Is^K$d=W~+CJfM(80PAkWf(u -rl?zZJv|dv*SAs4%b*2Bzxfc&@x8lnm7MyPs(N!ik2^WQlG9Rh)B1J?-EKwOt&LgI#?{UD;i1;t=KB_ -)BGyvk4M9P^4DyeU;;#K`y=wMBC?LMF%|ae-Aul3;RM(5wsgk7>x)yoyDAH#j=l(&7uQJfhU+2~!)oGmr@B_n<;{ -$CuM(v%|>{i6hA~WdyN~)VocnM-{-B8o}5WgHTjb0m@aF$^`=m-9ngLoJQDS*Z{MB`TnAeFky`(xCEA -P<*77iryrfF+Z)uQ#=?soK|T+Sg3&>#y3EL;HF{`zqGHexrSrYF~cs>!v~+CX85v5rD3FAL^qaZ}j3l -H_(OSnoz|xs5oMLiiec!7-0P7(MBorLoJSKNgG!|-b*|r6J5ydh1BFGt3h>Tz}q6uW&|iio<3mU#xl6 -|@b!ymZ#N0>faFYv@8b{Xs6p654r^i6GmzNu?x|EV^&CCJion{4el^}30l=dk^_Jp$E5U&~>YYhHI<$ -|e^n(uRQLzvq7){f_~Bw90x}?^6n4@8-u{%g1!op5VhhfOmL$wrbF%|@5JGL$rVviZt@_O2{9 -#<-=`?hyM{( -?K!v=J?fQP-N?7q><-EndTl~X%_hhXJGENy-A>H#>V3C_MhRkD -_aGN!~Q=*QHJx8T!SPL~l5+|(2)310Fr^2Gu?X=dr_=SGJp}tx|hG4^zDoH@Y8+q9tQSd59u=3;?!>GNPz%l2Tp5 -?vlF-T0xkS-Ib$@e);)(VDa-L6Sf8Tb8L-X44{a#xgpj$RtP8!}#j+0b)AT6Mlv>xhEtgN#(pe>ao0U -(|KCI+-=qUdd(5gKmTufH(qso^wmdx9S$`alcNM>TwnCJ>k#*qsMPi$YrfsWU&sv+>^C|DD^F-17G-L}!=g-O5qdhAJQs!~Sw4uzeTgfI&?>C535ffE -p>d(n{JnHY4i+2p>T~NG%g)u`dakswOcgeDhwC~2HlQ9rkda^QGnPa7N59scCG2N~5#-&ypmNSYO^bI -yqOns|qbfB&Xl2k8rxjxld`PzxpzkAV#Jbjz!Z5kz(IuGj>O##uAj0pZ7E#m5y00lL#BA?&s!;z#sQ- -1GqA$}MFY2rUCp(W{(jp|KmRS{3s?!QYcu9*r%oi6Taq*WI;??H-_(HsQfqogaFEc|mTQeh!ZFcq@`p^FN2zpsLUghg8?j1s>PzStw(;BwUE|T4VWzluO)g4|9QgbT^|G?!G(m0 -*{z+ZKfDAugw=@NnBtV9z*SM_Sw`@&Oa7UZMeWZ1KNMKt9`)EjnSd#1!wel02PzyeRbCKz5LrA8Zlym -Zc=NHaQb#dC6X=DS-NIG=+-%?Gx~3ufjff2qOXR%vU%Eejn0Vh$O_K%b{b1*hy -iO$7ckXU`L|OdvRYF(LamUvPMf${tM88E%%w0$&vgPSFpH{_V(Ru&APL80Ot`EGoA7| -6byUc~zX=liKy!GcLAjaS6w@!vYa1xD%fL5VK=FhXW%BD(dWZT8>e~5<1$Av2W)pYP>HX>N@m-F@b{F -+$9e67QxmX=&gb%WdEmZD7lPW*nYa>^&fn&l*GICGC%3}9!Rize5^hO(Mn9P6T#^a^3PV%3JsL#K+K8 -q2G@;~8rn(bn&s5V24{cIg8hH|-`or+QJS)98@cVpPkld~pp(V6$uNoqR-@fie%b)8r!Su -gBYYI<;>Q#=Y0m3551*G#ljS(=cZe=G@Qig#VlK!}WE&Of?VsdrZ7}^|^jS>_XQlbpICa!MzgbDQ;y9wDZ3LrJ@T5~Q{@)9%n~@jf0lm|dX#xyj}AhQ4stsU-|JDB_6XW_7~6G_kI;7Q%?~TXd`!O^gh;o -Ft^JldRt6Pu_{4n}7R$(n;_-Uzj2gR9V4+}fuTl8|BDtMu`QfTT_gO#|{hlrZuN6}?(-dx)L9gff#&% -f0psO^8dw8$BcaQ~DVuQ1ErU_bFlvVELcHW(AJM0M-vv^zQC>UU!scU7+3(~?o3tn@v;RRToFVgP0D1Lz@^BFy`9HpL}*lfMG`b8JE&`OzpH{4 -^!HV9UEu|-~DDOk)3NwjN`^2M*#gHoiV>j4?-TEI$k&C(r~ip8#_l~t9?Kr2-bqP|{=+_LZ$;9e6zmX -RInVVs83J&c1&3MfqjkWcLM@VYyIjd(w(b?Cb`TYEwG>i3dnV8U)S}Twq3u)P?Fn9N>q9Rm92{HefkyhK1$?#~@flIm7!Q9e -{6SclOFpEs8vx>=I&LrDh`W8*#4Y$xD%5p1$w#z{?SiXeu~Zhz5s`5GH6lsoFD~VWk~eoHxXdpgsJy% -f+q4BA%J4nd(JlB;()M6ix8MU-aU2Mj)^J!KY$36(&A|DO(yib&A;o7RPFNfYob9+_@%#O_#e(~%;#r -nYgC6lo;#spWO1@!*RQA3!Zw15)v*a6AN@WLI=B?a}qCvg3eI*> -jUrJcL!9$CZDQ|LmZq90QgrEMuIyZe_Vz(N?L90A$K_t#~ya&-xg0RSRxW(6qtL8X`O6ZAzA%2c>x{Z -6V^=6~CZ?-W)Y()VJpJe=aU}Yep8H`27>w?Gt#PMX!1kF96`~rCh!*|7>uc8KksYp`2s^OO#vTyVBme -rgRteb#Gl`lq05yJyOfLaMN-{M8CdccyKOAjJ309?Dcvqe^ppDx`I=8=~PQA7eIxgI~E^E$zrqFt$wh -THpa3$50EoM=jSo_8D>hteoqWmsQFRadMGKgV=Kg2K6B7|?~#W -}$z^9HVsmZYTGM@L0!eGsEJ(E3puaxxAS>VgI34z!=6SplQ$~w1dCLxB|jVq7WE}U=Mte_J -)}!3dHtrQ_^Tu^D`cm|?E!p{-l%52Ef$W()n0NEq0YOH?u&KN9q+&~&TmB)Sx9%pc72XNsPO^rKT!uI -y6NOnk9xOZ!d5Iq{Q$oorr)`V@AdRMFaEuC@+kUqDpv0btwfBQh_e_Uav9Mm6*R4R1sMp-&vqfbVo;9 -Z%_$&IO%5F6Ct_px5y7(Ki+vUlt`_?YF7*iBr_=3>83fSwt@!g%ifs$F#qK2h<#hjyAFm#ioE?~VOP- -#>X(=qK;+UvTqy51+E0sjrQQF2tB+h2-*P)G$u$y-XCHW5 -(G9$5fuP_e4(R#D22{{bsnmc4SwS@PxoV{1>-&~QSE^{^W+ck -BKJ$FMKZ~AEAQvxF5|R+LpU6k^58`hn;AQ<CCWYMw;E#cZRq&#A2{43{Z -;KEf-z!W+uFC>b~&ze)n_iGWp}*aa%U7+%5S#^15W@Gtx9D{yHsQZc*N(josBg5?$VB541!_z -JOSUMLsa~b2PJ?L7!9LSx{%CXXt-T#*=no9GUfZ^pPZae@|eeWxM89Cnv{ECHl&Pqcu|nllDUhO$%q_ -84nyhEj^wGWi|$9!?XUhkx!~WZ4fR4BJ4SYr=4R++jh+W;*ITz;=K@zj)Ahfi)WZ~c@D35!X4*dWKl) -OO-2vbZ-#snjUG+i#VO``&c7KxNaPEYuvXa)6qIHK1G!Ku_i)%Ho7AZ(@<}zDIfVCq;e{h5}FxAhQMS*3TQ_6U6O?MXpYa(at|~4LtNlC;F(S7@{T+TTz@(Zw_F3GKS#ye)F -Q5*!SLBluBnX`U5#bmi7RF>=qA+VQWkJ~!Y=aGc(aqF(X&}L^>#-M`#PBS8;f!n+ -tKGXn~uhP<^xEnYVRJfT&;bLy5&2~WS15Afe=cSD?N;|NBQV?s_0hg#Wj;xyrNUpI{ByoZHSPw1KsJD -~9+?TP`CDC+p{UG4cv>h{OJFqptbqZ*G+`XsIL9!%@;`b(A#ps -lyni_8)SmmLy@AvF -x!+AZMhdo6KntkG@-mwAQI<0rhE?TZ4Kx1`0(gsQ2LjqZ-t?BD|IVF8+7NS-Og -oydcX~z~9qDL_IUCx7InhN|-GLD5)ES9lvo)NGPZd)lrg;aKFVZ!H5mt%2DGYywFR8kKPC+4yeDO@ez -U}L0(6vyRtxwNy;j>b?I^Gv#<_=I@X&dWjjHG55dE`$tKmHym`Rg;9)!=?;fJ_^bPbbAP?XK8r@?Ve8 -+sLfmZKx#dduD)DUagjwc%J!DE64>p*p&WOA9?zxS>>(HzmOH=ov{H{kVl_Rl>G727qo&bdnRtBw=19 -C(_{7S$3AsFo#@jrB0oK_FIxupWhhzIUlWIY_~?RtNTz*wx12mTX+R&6p$}J=ldC3aMw8}4^u#G$AA0 -DFS-iEKp*rmdG87wD)-JK+yKO&ae#k!^@7O1k68o`>yXRUQZn;xNTOd9|{1r%9DIlsCl9gHFW%QoLjZ -;8yY+}?hi~JT@iI-nGS#g#sWQwyV37q`a_St07e)@Yb%%*>!=|Ga~?3GgDSAP5ikG$v3o$ryi?c9%f< -WUbk%_HwmAjXk%f5z{}&gmX`U;jBF);#oQG%Vf~H!ObfT)#)&%>z909(&{eqDNjgG8Ek-kJ?X5J2d-x -%|QAb5CgsPq`_W!FV%-WPk80YK`i8sP%8J5pKXJ99~&m-YRzd8*s%W_JYGtv9N?+9=0UV -}iu>wOKfX9d_Yc5vX9CC3FZ+GB>?ZU2lSZ> -w;S3UN^u6kcxz*X-bgI)ESFYK!K>;PB2KMZiy`;VXCs(07Fb=51=UG<#0t6rh*sy9t{)w@D>)tjKZ>W -#q`JVm`N#HXkuLXaBUYtDN2T=XN(dUxzQ&sp!0^PTnP14Q)I0nU0msk2_izj4;v7I)U$d~T4lUitqw& -U$Bmg0tR%xU=5q0nU0a=+1iNf9b6E#N+|adQYDFsm^-$e9u{Lrsk|S?I$_wO~tNgLm!=ZzPH}v=;j?A -68F}dPQCTU$G!FNSD>l4-V2Q8t+yV%^(Ikoy?>qWt*7z0f8(jQcbMj>cm2Qj)QcnNr+e!C6TnS<^1PV -r>5ZnI@2RIdt^I(*9(v6DA9K|k`j-K&dQY4i?5c+~{d8A7I!{&VXZw9uz2BYtpStSN;(p9kFAnhUUG? -_S-q7|0apBh8^myG(Z&}<;Z_fZXJ%qb}o8C0)riTC*cGDX&z)kO>|Gk^usKDV(7g0C8Aw8|)VdlsE^m -Od~4;}RaKiN@l(NNt{Pa}+kqaLE--#hBv((kCZ^m~qaj`=m>3?}ZW_wE1QQxE6i|3y!|-<|srPd&2x0 --ky(`TdZm-pKFbeC2#kJ*;Q2r=EOX;Xmf7hhBHoQ;$05#Xa@ViI#fmHD5ZwQx9X&Q?K#-miBwyXds0 -P1iOAv#6Hh=@0a(Zei)q3oW0+{!~zax(tQr*KKMHfe;>nN9>lvFb??2!i}l@7$r+QKR*xI>26FKjKkHUH>cOs)Q@Lm#>YvgVNX7&tODA50B@ZosPkbCG+ -o4dmA$(H}tG}Zh5JJE)Sd_>a%L!bpF=^E`lI$U)Eju;@Q9HO=|!I@N9kUMp45QO`MSDVF8L -8En%-;X{|qa8i?M=dpr4as?2s%TS2klz1F)L_;s)tWq51w1ZSw2?a@o&NaTN23!;pmqfe{hwL7q>!IIG)7rA{GZW4 -1l_sd~vqZu!%*=PxWYE2IZH(H;4_Su)wVqV;50pR?1U&oA1S%yZd;|8H8c;6U$tWM)th+8D|EU%6$wW -E^;DYc$pfSkdpy4I{_v!Jh2gI*MFdMOeb{Yf|!4tq;&S5x3?0BsInL@vy+DZ$6Tfe7xU{oG3?`~G#q^ -TQ5T@Zk(h_vJ1jf7(emF`DKYKnL)Lgrh!2U26#jdhxEjwPqL8ji4_2TL6ud@E4)VAs%9326__79>`Qq -?3W{RD%Jsd2Lezjl>!Z?KzWF_%}%Ta`FUpj!sId5rp-xMtTF_ddNxMoViXkAxH%aSrld4JMg!Q_*Oet -28e0PGP|P3b4K1%GVPw!strNH7# -!MN7jG%bHArm -?)P_KJY=Qzdk)=F9c%9sGUW()r5C456n|R|I%W$M?CMOzU+_Q&M!bwXG^G%&x@*5|P{}!z)_8zyP@^> -LMTKu?3vl49L)y-;8)ERHHjg$bFzQ-}qBAr&ceL9@4!j5z9-f1nbiXo7Yb*s7gu4&tOnu$gP~;KRe}r -$EGW`cQe>2L5FO|SdIS|DI`vJw=`y$;0gJSMDFhvsDf`4by>DVawO(ke1a5?wVEWAAmP}bH|-vOOsi| -u2eG{Zc5Z0!lTj)kQgJtq&sz^TqQv6&U?+RRWwZB%Z7wzbT~+jkjq``Q18y*B}fx_JM`hf7vP!PLYnS -H+|7Sngvv76BCkLs9UIP!>hGTy{}Q0bMb1#q`suX=!R%S!sE6si5Xj=8dYXI_PjjpHC(<^dQ&wE~Wl5Vvb#9#s2_{yhjdpgQn~JJXLxneaCEA79ku -Z=P-@_Gp)Fdp|FRe9qXSk=%ih+I9sIu78`1qn$Y*;)cJ_wUrNh{01N#RmENwV%K!HnnONy(P*!(t=Km -4W$=?xjCL;&(yobZ{^I8f)w=tkc#gO)%PdwGMj_qE)M7gz2_=a7TPLSVoLj<1))5(~=PFky+*z>rc+Q -YOC-G2NTR{u^_>-)BO3TRq&{iJkTO$QHSM;9%lZ0Yg73Bz(uRIq1{@vW*g!y1md0Hmdw9YTD17`LPN; -@`VyLRbq|~=pq_nQij{2rvKY$a!Y6G9;yIi3r?F?&%E`_4b?`ltZ&*{XU1qP#Lfdz^wj=oiP*S)8Ih7 -x?Jib9l6Go>byu54wvdR9XqVAWUsdWqlmi0FScF+f*+=+qwWZdLjVdB0d7UG0W!u+bpqFYF>pDF@_@QEc15LTVFKgBmi6p~hJyvH)gxz1S!XY*cBrh8MYK6tHH$Jjcp73V4%{ZlL&?Nwv%)UsgfWHfc`wxt0N;(P -=fg+Wk!97uy;0a)0w_hGS_Oxy!782!$n#ne)^?!mqYX#gIWPm)^^4ZxkOE8(1I(K(kG$BFIwmnXYm<8qR9gr~BU=qj#euO8>eLuOqp((ob9)9lg -CfQPW&xRlB{TdVk6`%$U^h+p^W*OLs5^AM30$jzt2L(sobC5IgH9{Xv*ZKJl|P53AeO@LSyTchgL)}s -DmGNFm|+*;Ds`CpPMOmaSIWwYBc!oxpL*WUVCJqNz#hFSR$tioW)B?Pj;xEn|o;}CiL&iw|dphQ5xk#bfP>2{WkE=g89Wk -`jQn+au{QzW(o -bKE}SJ_6^Rbr#vEVuIVO%T&Ie~#Z3=kQy)Ui{XG-{a!IK```k6nu#st$)3vf~E3b? -5=dMGOI9<{tW}0iDOWfK}mnhPbbcq^!T_T*JOMFOtUgqllcne9B`1*QDlL&ILc4tTLak~k -_=W0*+O~h1&1 -dZCSMo)9iH22a2DupDW*V?8+ev#RxR!-MsXAg3IS*76bQMJrwS63|(3A>tW0!{5R&Bc!XrhCkLo#lvB -MpZcoT;nJC`bxfQ;R`il)kDHpKJ3$6BdZXh(wXZoEGGB4-oi57eQq~l8RHE$oSTLmDm4OO=n-AOdN^7p;Ubp$B`>tc<7}+LSgt4b|KTzZw)(h1l -{FBN{QKCuKL1McM2@*5h%QmAWSG#7G2|p=>M5NU9R0Tp4S=T1SmP(Po)vr( -{u01T87>@e1D?<{-aqvdfVJBG+`MuC1f9fFXA-5}f$R8n_)?^uTp@;MmddQcA9#ZrVWt+h`#_@7nfCe -(fhAh>hHRh^l!gHq!?V~6{p?w4=XO=lZ`zShEr@f+66KG#`Rv1XPw-o6&vvHB6bzF+YtZyvQHJ0Sssx -KTc>7(9bqA6A4O0h=spmtx8O6nLa`(|Ggdc&adjgrRj^kOWHA?ZIDR{B(3rSr{bNkU_I*~T8^dR_!Uj8uD#;R=ufjp28S#!z&FPzZ|TQtdT{ndWUk6e`1$_95(4 -hLJ*LD9VS(8zi;Y)T;J3LC$5-XJGtvMY(thwc%s6g2m@9SJZ|F#quFSNeV+zyHe>(3d5OLC)JE3PCbA -E5JAlqL+SnCb#WCtxHT0T!&0R`buYzxUE4Nxk}Nu@e4ssy7TUw;+NIU)WIrleMT00p$x5fzH%EbD -0zMTtU-Xyb?uX3-@pT12=*izqlm)*^z4^3B(Uj!q0W<E=5) -!&ZrEy4oXqo7^j1LQ4;+~RVjWL;v_G=LwDD_i&n5H^RbbJ}MMl1Q(HSp@D4+)K96Q{~jj-x^0eWXUp# -k>s-iYj59*bWL5*6EDiuL>oi^W?tOE!R1a>0RAqoujeMWc8@bvho8W`BQT8mTefQy+w!Vy-mm&f)&@f -nY^m+lLn>9YH3t@$htr%u3+b*gbwT*z8P^W+aQNoR(vkn!U3r#L@?{)zbn-X&0&AZ>TK$15xG=%n5>b -~w(NSy+NWXaXU*G0$7Y&hSs@0dlSSzk!T9w$Vx#Jun6B=n4!<`l?n<4mZHhTyimW#rutvuQyrg4BfNfYvADWoMT5?l>d_yi)!%g>@t<30w9Z9Cv$&)Wv -tCA=GUhR}T`3i6Ad4v*oE$h_8uVPH~z1AhgHucrgVr5gT?G8~(qDmabru}5*7;OuIL$ak3`o>=y+=3* -j^#aXd8gA4mm-Tk`{>11JXH>=A1`D%W6PjsOx3xB}!=iPuIO@gz;wxDV=V`rKBxxW|71?c9t*4LcU_B -~_dDj-kI)<#(U)*}a!j%{m;ryw^StV9|CSld*D++6wr4kp}iYsmFZnKp?81*|D^03MjFu9p^;UbJ`<7 -)BV{+5emO-^Ow&hddNWx+?oaOKlEV`q`J^ofhCwk?kdM!Y0(7|}WFvLc*FaFWoVOoo)(U+viFK@5m1- -(l%FX+EfEjY3E~C%(WvHMzM}T`pe%DRzqDvykT2gXPjGXSj-->)gw==)C;5Az6IPf#XEVOs6f*!o@{= -%X!h={0zpdfzA%5G3Fwgv<4G~rAWx{WHgn8aO-$$QMiol7&Eg=wzPQKagxD}Qt;n{`g&ghS>tT`JEg^|c3#lWN^}Ln ->8g`|Zif_o83S#A0A`>mm!Vm)0+^N{RI&OQHvOtkQDCX^-}1p-NlhK4&EqPuC!&7&<98tF%r{G5_dp{ -=r>)^Wij;OI3?DGy?AxpNF=ota3D;buwR58ScNdmU}(z@isBf-uK~Yu5vcGnNK@8?W0+(*G3&pZp+o= -y_n23^t#xeejXv1OVItuf+q7_7YuZmKgeOhKu1b;{vb#59w+l&k@x{y;?!x`!irE1<~=wr0-;{Eg<33 -yG4DYbTEe3}6c8Pk~g>bA_B2*ko*m9#!ZTZZQTDcCq>gu4yjZ -155Dc|+)Am6KBQAv+|N6@R($u+4?yiq3`-WGL&z>6!$evZJ6UKR{=>V9PYASd%LqVz6^(tF!3Y2YS@s -jI7BD5R)``F0@}+l8Y*b#?ZOllcNrcAtZDIqRqW2pN+I@(HFBln~5EVg#$-5p~mv%DNMoo@9EG*@nzE -WO|V4L8cp-Ze*&+RFUaSrgQzek;>>p`rS?NIf3;ZySf?ZL|EI^g?O)D5B6^L>Mqm$LdDQzTm|Y*?XuX -i(|oqL(&}mql0Tr%bT4)wi1NUX8b2ER=%aHvKVXU -FIefOSUt7Zr>U)jk!v^PdcKfzge#TTl&kP@@BAk60=RSpnpl$#^$GG`mE(Q?~4mm^WB>0=86x7FHw|MdmNcnTUhtG@E -+;Y+qwmD73yMJnOG2~q2Tg@5pCuUcj!z)8?NTMr`{5I_gD}4^mZq|3SFaX<<4TYd@{2x>^xb9R!ZvHpS5TK*#c^F_BhEutG71;tq9lfXg$aE3k8u#tHExW;KsC$5`(npG+~;GfLa~aib;PHct-HaB&;VOm$nX2aFoT?l=7L)ik-EW49=Ea)sB`uI0r& -pR=-PB7*Gpbirl9+DLz_y#dOGGy2wp62y#RyNhmzdq?J(^j8h#?imMB)hm5#p)y34@`nFLyoX&iZmzg -eCZ_{2co;s7VEtryA#{?M0q(@q`GgJ;q1P>L -fQmj~458SjJX%uL}Wan2={X5|S!UJF{4oSj|rvBUgU)xGMw&MEoWvD#Xa -?p$2mqRywJYe^}@ag(b!pmN+U6h;qL!ALCBF}ou~#C>*=1vVAHSpyo+ND9zmY)2BlRO0v~@kKclBj)p -MGDBij()H5GDMD3r-nx1oPNh9>8Pdk@r1le2TS~Wl@QzILwf57TGo_QDsvmq8y~tz-L@&4x#p;_WWHB -iAMj8897C~+8zQx(0T3tS(b{KUjBUHnZhFT9c7iaWH0gcY|TN^yC25ssx+7vj|+UGs7N~M~7ueBdpM_ -qC8ZyCiLjjbj-Olr%RZnBegQNG|&vU$x=jue;l&i9zDs@cI`e~l&BB%@5 -uo1J{k@L}N#4Cc9i -+SL~=yOdQ|-KvbXjPR7^f=7$G(7<8&I4;=JsM1jtT`}+ -HmYv>}gS9(nIgsq^SvIaFWSp0&xj5S&*Hy=QSpxcqW2r;N^+s>w>Hc&Dl%&odNoVkwy!bKf?YXGEE=~ ->@M$SdilTZ77HuCCKbB&`Rs5~Z86^^q-r?s|bP%T<^)1c64FFSAmTRD@gE5=c2kyF#X45e?bP?g1A#@ -VEoL&jY;u1^&wX5ySS9NFe2or`-KvU-4qN-H^83LrX!W7urFOFBna9A{dZ=wZCy603$I7CAy5P9h;2?`iVeI8|T(JmJp -Ns^o#GEQxBPEM|F!cA&%WpzzzGAI)RCQ*=T)!z_@bzZiPy&(qLQRVt`IB6@f*6&m6yQ6JKk<^V!z0Bz -OdY8vuL`rhHb%)y}S-%$_V0bk)5Dgt_r)UJD2rlSk&jp?8xIhgqXd}45qmBzYfeVNQ@WF0zQP^yL5w| -D%+t>Z1rnlDpsxh||-|^Cu#`nveBaPVACS0AUZSuX6jQYjDmpb*Oeyk-+Y@juK%ujB3bgTsD(eJcx>?5v)5Dr9%r@2sh3RRHh8buL6R&Ekz -c9V5eT3QD>LtuR)=t9ISlbBG-|8mJPF81O23u>f5v4H9dU+Hu)bT={wMsa}SkDM^u=OipCRmRMGs(JN -n8U4=!W?ORPnct^8-@9Z^;KbJSXT*il69#tv#m>nnQtus#-6$6TO`$!pOY=N?F5FYP}{ZGP*Lrks9g!L4xPY5e|8S7kO#T_En8N!MkIM!TYJum1NR< -TEFq_AET^b6}{LBFup2>OM!R?si37!9pmg%vj?Snm~9Z1A);S5U@%sMZ@eN_7iPX|nz%tZiWZQCK};J -teHrW?GL3s~Xll!s-R<2g2GL)(yhi2iCR1s)2Qxu=>OLgs=v~I#*c3V4WeXI#_dsH3rrR!a5k%k*PAu -33!Va9!ao93M)>Bv-%6`NLcR|*0HeOC#;Xa+FB&y`S3XVW@j8_X`*!zJM3t5zL+A;q<7Z3B*-q`NG>X -^OOotzKyuM&U8c(}?+cege{I0bmeK~Yb#SSlk^5Zi4Ydq()4B|oL2~J&b;*!jLM0cq)@73H(pz%z(z;~JF84|fwt*pzSQaVHFBd&^x!4UFZ(3ct- -xx{?#vQC{p3Z@OSU<8Jh_RN}hl7M6R<+LaeNmlr$7nJ(ct0w-frth5ar6lc0R^5L{` -yWWQa)cdga$3rbUl>qn7Zzlp&TI(e-*mP-BRdZ!dPx5GKMN#u4+>G|BgETqBkephS9 -d}tGI}m>BwQ$&9|-}9bn5C(>>M?&$+Y0mTJktZu6vOFN0k0HbERYT{J%z@g$u&m6vBQjAIK%jFkeu4Q -$@HtRd(FpymD04`lhE&`IXeCjx|eLb$hIf+w5d7nBtPP}KlHS*DYh7LgX7h&ER|!he%SDQMjS1h;N{VLA5LdAHO1BBSI_ -M9V98g{$lDGxyxKTPwlhvg$B-BAQ9^|M_xVpeeMnFWB}gF1B$!UHkl-bP?F1(XE)q2Pow05N!2}5eV+ -pbe<`O(Z@G-$Lg0lpF5ZrTxF)xBZf&_v`2xby2BY2bG7{Lz&R|p)hGUh?hjUbR<0KssA@dQSKg#=F#t -R#4wU?0I*f*OK*NV>a^pbtSPK@7oIf^3301j`6E5$q;7L2#MC@fyV=P!nhf1`wnWj3>w@m`<>OU>U&{ -f;|Ms3C<^QUO43it&!-N^y4sG(1Se~>|uhzzYvf -#e39D!VD~6Cf&L1}eFPiM2C}{^*v{_50$Bo!73qv%Ls%S(W)HC#;S!x^h@GBgOfuw6&=*hwIh4u-md6 -ZCPk;Sb2Fs>DM(-w;%Zx0GLg<;Az(9Wprxxk9V9kU%oN{`ICDWgM7|A!8avs74HR3~cLa`EAlnBw7Kb -~PkhlcslA6)xVI1-iUPfs}Ilc^@iqhAxz02~uVjtOJ~MH#{^Q>2?C!cHP;3dojDzXc+d@kHwka><}H4 -CI^3Cem9jg#hJ+B89rN;GZRY(kWgRdsGgWNAAt2B$V_KE`xq22!FktrpIoy -kwfjZsfeR+saI!DY^hQf6SKtWlnILM%AaF6%iy0$uN~E}1f=;E*mHPIz)pst%Fbe+26{R(iXjM}V`4o -48T)$H4*=umJ{%B?X9G8 -)A9rM^RnZxS^+UM|Hul>*8D_}$uGeOb`otiNf3!aKLK#_|jN8~K4tGyZ0N0k-_|MNJ{yLJDu7SSFE1V -=YH%i*+d~>G}T+{L@6LdNy5P-e2Y@=?yif)Z(r9`>>EZ)^{G|rk636+H;=lk6y4)aAV`?*5@N&gOnx+ -%rfkF=Fa`Sj~)Lso=%S3%VmicXAr4o*!qAef13W^;g|Y3Wr*@lms3~Lt!sxU(>%csy+uDdNsKvY>3Un -)O5AX3d(_y~&)wsP5E2if&T_=Nu^xUJyZX8Nbkpf~B85;=EVzY#HFo`VrlscvrBH;$+k-3q|(neL{!*MeOibIy -GJIM>2!S6lYj|p<}qja2=Ul$UObIPDnV@$?Qtz{Lgg6e~e|2IRkPSnU9j$hfJ&-dXWj;zcZPMqHP$&I -F0caw55o?U+ER1nK%8ZspRTQR|#YrPcc#2GX(9;vHNqp%ReQq`afO#{|7v`*6F1GZ4Pj#`@7qx9tSwo{W;#{UmXYhyMKR1KmOX#pZ%M?-(8a|(_R1lZt -1O~yP=D?H=UKto@1U{I`8rMWeY3|%NISdc*(z>eCp|Eo_%iVvgentc;UsBt6o~YX6?(btb6sf*Vn)C= -35&!zWvUo&0Ds<``-H>e7J4NlXzA|J>YmnZ+TPpFvweq-ojTv -wMcuVqcds5jd-cBm0q;J2`}NoO`1<(=1O^3%glfaWBO-O_<0oY3Gbc{Unmi>tCpRyDs-eJWDx5Zb#-n -4#j(a%mk=y$}Hgi@{@qe2B|8)NUcKJsQh>nRJIB0NO{DTRJLmo;RIxKnkh?LZkqee^Re|!J`Bg!9lpf -#6!#Li@QmhI2j*~`hU$PJ6^>`&O)7u(sF*xCPOXMfVpzR9u9zS+(W?L$*3J+Tq*L6V||u_5%EAXX{*u -^6%?h_C~*3kDkWdYH+2V@i6qN!SP3*`uBg+QT)2-Y&>J2aKUqJ)7?+!w -XUGvAsZyMT^a5kl1Y=gAJ}EEXlrP+*cPSZ`NGYaI)DOs;p2|kBWPxo%Jn=LsDVbL?yMzQfr+Fpw=9IK -?aUqGWw6v5eK%*&1Dw$8xS?^xKNvS0yuaQufl!RRb>fR;uODGm2v2N0&Nh+1=^!)s3`T6zaxxw@oN)j -bx$^x-MNwTy2*N0*4N%~XUTxQ6BOt|+K?)_yKM)GB%Fr#EMM$81IwL8r6@C+eIwy=TQL-P8Q;*v~ezP -!exxJ8MXk4dZr;E5N~?+lwAb5AMne6c#1EpUX4J^}XtvVr73UaWQ1$?frC1*pzN!dJmj!ArqM!9mGe$ -ydr}hAnR;UnNf^AEc8mRviptPAcIrP+(j4juj|`D6n%&6|=2eu?nTQ4yRD|?}=hASr;c+;Aa1xE@+YR -Q}R*LR?<__QR3r#E^v< -E{7pkKiL|AFbo^lY`>V8}D5vvSqh>89KXlk|pAb&mouX*CEAMYybNeTLeYr_RgQ=c@J7^%G3S^zqrKj -1jr8z%P?JFLipZIz68PWl(t34s~k%?7|rI=^5%wLtYMr7SUPvu2NqXpEsUxuyvK{sV2Q)Mpo`bb*9Mt -7XJdh(P-z*3XB;Ir!ndUal=5*l-VtbIv+Q0(-48Wf}$Qi)OcoSbyBQK=zWBBNJ7D&t<87HBr3XJInJLkJduC5#eRkD90^eQ+@JZGOyy|5X2QH -=KfaV~*0VmvSR`^y=D+=%S%~nyAa}w-k3Hac*A*jnu1QqA5q8Yb;Rr))!K%$ -;wpgbMlQd?!R+BCz!*fbA3p5rvSW&&i|ytsCn>J$yGBcYBBx$@=foGnoDT2YXjH{Gt=G)3D^2 -9h6e1^Thk3318 -7D?#?@m!C1-F3*y_jIHcX0^QWcal!|MFh7?~<^6R}-5vdJ-gjsJUg|sho4VcE|FQ0O_8;R_-~WJ}|Jy -xuY)_B+{)ZdHKm9;`|B43w`F?kfUlnru{9zVob+$5&B>U~?xDs~fd{)HY*}wJZJNp-msqcThf&a|$b+ -}pV>i@%t^kbNsiL-`W4boAv$oHSk}uLGs7*>03H>{x{nzTZ26qhZ*y_T<5kOdsL3FMxX!gb@})9y0mw& --%j$l1Ibfip$sU3*eCWC>7KPAO$&Z+5H#({Z;WEyU>^>W; -a6f#$DpxDuQxdnOI`e>seJC=q^lKSsuQ3VBA6LaMhhUe8~$-YGnKscI&1lt~%Hjd<)n3rM7)(<3UWJr -1rA460tN}BO`r&!0SsD81Ni-*TTBgVrE{f(iNITh>F_=mOlB=VmJd=-w_{>;el# -d+|QaA&%vKU#k)XPzavFPl)0_E+&*kMCr6a0MpWn|NoCe|U@XrTW%!aI3zLTXe}obWj0I9Q*aFBT@)C -&Df*Lj1yFb4j3zHe?woi|k~=6$=n7#3@0aUZ}stJxij(Ky@&DMm|bsUW1yBVn;>#^=lgoH;S}aSKddR -N90ZtWMsrnpFk5g3P}ZT#N=%)%H#IlPH_b>`thcT6ZM89gT4S2{+(l7E}@&Ao%Lu##G`UElJm22qw`D -@?kA!dvSCtQ7HDT5W3FfS?&&tiEd~ -9xEmLV?}GNn|5)YMwJC`>y%&&J8@glHX&1ZN*P54Wvf?SSmK*k3!raVDwOIBJ9nGyQv(rGDPo`K2gseM`_vh9?ld(rY6rYP>;0-z0wYn5jH -kA2zE#HWpzQyp?CKsrEdOgAGkmKP{Kn9dnx}p+cuA -}gkt52*}-zMGJHziXio15=4e3Wc%q40-ZI`>is_WeuNm&V*a{i0REHk1UQp(h?bp=N0(CZF)VHT==z- -<-b2m`{-u6YHpuS>{*i*j$1Vf|&%<35*2!1d|9JAs9)JK%gTCCg?-Zo4|`eP0)$Jlb{WO2Z0-bioltG -5!70B>@tCs;55N;s}%k)nfnMT2sRO{Cs;}F6v1MGg#;x8*#wUeGJ3@1n+2qWlC(22l94)07*^8?XC@HxS5f(-=A37# -TYNHCosi6D#s&;OL{fg6AR$#(~KE3P=?L3uD<*PJ2xz9af6E^hw8W27b5=3BjKI?|Gfxk?Gu49m9GQCg;wX}&5jrS9T-lCQQkcR9oRZm-FroZ1DoVh_ue~({Cm{BAI_q8kGl8ik5YK&y7$vX4q_j -h@;q%@4d$=Dk| -8QUw+AM-n_}=rduKc(+iI4p^4}d2TI(S``&5O4vE)8{6cMsTkW)IGiM^e1%9CR5I=C0y!X<(ie4^2w> -pg;yju?)IB-C`i)n?5elGCZOyNIm=0(+cip=-O@6!&cY7jp&vsQX9M)(7VGBXeDmEUV6|7>!v#XF^6O -a6E$OtF-{^qzfHggqR*s!0D^BkH1K|N*w{D< -P@7HZ)4R)u``g^V{J}J+!9V`U4^ -?fZWeVh4C7SF4h1D|AVWCAE>E4R8k?rAL9JRh#fVx!W-eKe5;n%aN#Y=A4ymxZ-f`;J{*_4Mfw6a$(y -O>%a7#EYP(bvpLQyVBo^pm2TlYIB~0pQZUYW-IQw{!w}D~;Ucwv*oCsVA+zGmH@VSS~>wm`Q{{H?fF) -@)PCnvK>_al(}1Uw%*9LYCqez8}N+^~_oOzaKGT#E#4a|JTV(n-V|uAcYIn>=|k%gM=M27^KLAG2o7VyjG%?Da>(**isAW}X -MoAo7HhVnV`dmKS^TdPf@aie7_jbU`RDA!i4*MWufJv&zCX^6{>IsvGiL-} -R8>{6%NKrP-(BPE;>C;X>eZ`)SMWIYr|}_)u_nYcs>O^oBfe=)jlCr`U^Tyog>xS^h7V;k_*C{BU&1! -?jqG!NMCngi8;akJ;%g{=IK@w(_@gNPM2bJ1w_*z@{&I@{CdIFy_@7byb9V82QhXi7A5HO%6n_!Lf0g -3zr1(cD{&9-`4aNVC;#X1p3l#r~UHtw%8A~GZFrTI`i;awJSVVK0S7}V!MUC*YR*WC_Vf^c%jGvpz_) -kk1zqpa{D|YeQQ2cHbUqkUDD1IWvr?f6+QT!Pce*wi`LGj~m@Gm8JcU3?NglBg-?ldysNz&13a_ --;5qgW{|2VLXE3kEQsJQv7Er{uYY=xm|qLk|nWr)MmS~#mt+UZ6G^DoPUwUG2Uu4<39O}51q&O)a8sX -d7JT#`xrm+tzCRI#Sf(T2^4=k#h*^`7g78*6#pHHzn$XmrTCQRl2a7_dy0S27QYXr@F1m-MJYT^DXgU -wc2f#xD20oyIQz|qv&%y{yE2utt4lb$wvn^zN4PD1M~dH<;zv^aVHAG?#V@4z^C|vPivKdj-$e0uQv5 ->{{}jcqwu`TlOQ0>q??~~xQv9A2zYoQap!g#x{#1&;kmA2g@pn-C6L#_IKWWlq?-U(9AZkFASl?#}5A -^Qg)va64K{ns00r7EhF){Jck&#g`X?=V5>ea(*(4g*03NdlyP5*IZiHS+`z8?Vw4N}Yg@rlu~@sX5#Y -;;uQfT*-S2+*TPx302(T%s;2J}NpUn&Jbmeto>XBhu=Sj8BYLo`D@b+okpEC#B%kt*g5GAjGHi5Osu4dr2EI}7Z_~Q --gEDFf40tkOTY^#Y5?Ls23m&#Q^{4*u=Q__{7-6F7*PCf3F -8b`U4VU;$stI5(nPbp{*PM{`dFpo4};>qeT`fLwkeeIIQi6>(y0e4-#i=79n73J&X?xKlIO -H50QiRa}g1rW=3^Pj8x|z6%?THYwD!BCyqR$ -2M{m&`$tFD`KP4~hvS18=+Zsc7P&LK}Fq`1(&`18>eNifi -0UB2SYbQSr~nbH$B7qq1BHi)apMMCa+R}<^nT>JT|e8uD-Co(G|*AITs(mW -x*0UkJwXHA%d8dO#C-U6Hk9vUQ~6Q0gr8y?`S&z_U$l!)17Z@*=<{i?UEGJ_2U2_;#gC`>V<`SqivKv -pUqSKTruh3Qe%(0tUpeK!a?1a$a!Loe59rXLgIM}+FSL#wJbU!--yfHCbWnHb;Mt+;16@2?w!Dw{tbM -oMJ$m%&(Z7|4`~5BGy+haTy?RmDRvumYd-wNdow_~HyMO0q{aaChmMvP`*Qsld-u;_5>wTZ>pjP*Opu -bAx)|=woe^1L69X#7T(7(A#)vT$Di`)I~?j4+)bavww-KkBl?%w_Td*f0>%KrgxZ!d4}uHJ&aPV_(r)wQdn=_qz*EDVcqPBPqX -q1wSN48mihL>Fm&2-k5vB7&&z*w5a@qipGDVh1jAuKyd)-&v*yzSu(1wo8{TC{zGGk?YyCX;bQ1#Bsb --QsT%N>M6uF45h^XsmDo9*X=IDqg*jOg8kJmzW8D)jXNJ)xNzZzUw--JM;d$1{`~XL-;??6<;#~Z(7g -2AsZ*ym?%liB;^5%m9TpbWU#7`ZP6u03P!4~k{7}~znD6P?vuAfQQC2PQGOp*CF=HYF=^=Z^FTecq37 -SXq9XodLAAkIj(|F2{9Xo~vD*o-a-*TFNbNCYtoW|L&tyXIl(R${y&pvyhva+&xz<>dMOsXer!D+{&E -i%9b_1a7Z*p)3o*m~0$VRLeF{Ay}y{$S_^?;ij5RNjZE4f-REprD|hz>n|}_<#KI$Natb-s6NHfA`&Y -1>WC(|2_ZVhadRi!-x5iBS%Cy#3300?&m1ZiyJp?oV9i9)>$O7#hT6Lhw}6DV-_u1ltgkhaKrW>?{@9 -l`IVKGjrR5RMcH}A#KiPQx_=e_OP4Njl4X{3@7_It%5c$7KmEijDk^N{eCW_2{?%7s2|THO1n#h(K7E ->>Jb6;QgC+$?<`E|G+~r??{k4Yp?mDGu{o{{6_;0`ccIk^RzIgAu@4ow%^16m_MCX&#-U0}RMTq-X@h -5o-dD#H?D*zf4K%FQ6{1rMBy8;bqQGjr^0oAz;?A-x>2mf8Wb}giO>_$8>7c}hLxl`Z|{y1^sgs6ivX -U>Rs;D|OsZCIE`j~*3(wt@P9iF!lZL7Y!M`9$E4I;8f`Q4cq7-sJWVypa{TylVFwN91Hf-+C#WOfw&fSj^(Q! -wJjl8Ke$M-UgnNWJ?^nV3u#Y)^?R(CDzjEa$wUthP1^?~aw=ePX@(Lgsei8MTs1HE{@I{%UTtN@&0=U -DDy8q^zZv?=OGDmp>U+@8F0p2Ko)bUH_IgdHUx$ZO0wTCzlI>5Q#KB8ea=MNAKJ-2b*?S0M{o#MQLBnF&V7l7+%Gx5Qp}1O7+9OLI1X&_jg(gC`{#27JzW1mPM=Gz1Xe>qOymlq>22dpMmeuE))~(0#EW8&W8{U@x=cFzm#Y -QKg@Z^rxFbsY72dKa^8E#U&o*N;w3$L^a!AOScG(}{__g3(=GvoQPsgeAdJe@#d^beem_>S|Tr# -Vjq4PVLanRq5lY0vwm_KZHM=XR+*_x(_6)70Z0xE=mf&kOzi{ku_rJ{S0dPa4tzz6W17q(h+vybeD4_ -P{3o%Cs=P+7!-TD2Na=;2BrN`6!u&q;EM-Ae;t&W!IiTgWR6`p--YdwFmV{U5SP+?@E0uJ+m9bzgxF% -HvXqE9^?Ei^?~+_@j$5yv$F%%4qG2 -u3u*xXmUvPo*N2-YiE9a@-OYIqbQvAuf_6!=7_6!=7_KZHM_Xi@Gs}1o-JHz-wa+VE1g8~idXsnGWwB -XrV9Kzq88Oq;$RLftTPBavT^WQK2#J~JQq9KiF81p01VAr1G<@OwPRO*w0Kb6L3rBCWbG<5%<(SAwbP -xAf3fPjE*B%{m){^%EBf@eSjWFCbUxM8eSMhlYmsL|rI=_6od#8*o=>LEn%59`hRXn;5Gww&B@6Cz!uSG;A&k`lKZ}QGDT)0epT|G&fI-=EWH?++rHdzxnd8put|o=qJks!4Sx8AbY5u=;vdc?8mKM2Lo~c4w`cT8t4tAmg -&~4JmmkTW%GL2FvZJ_#XqZPd%pn?P5e;}6$`}tHZM^>h{=^G{|B0{Xf|hyn=JECG*9&^?h7N@m#I+uI -hwq#l#4Ct~4~PcQo{0wZNlJTONqy3CLnMDTU&o&$8Ws@^3y6kN;-lGman+~jlcMZojCZOV@Be|n!v9* -WwkL1^e)H$gx6#m$jyutE<3=q%Q@MF14Ttef(Cp3`m;KLi8j@6j@8)nw9#-UI_^{_c+e+pD!PmIOf)po&)n{5k8)bPcyT55oxE4CU -OX)=jjvt1mSbElC@A1YqmeIMxKOm~ufP6U^gED+8lwexV{AZQ1z8C4DCPrr(4G&yY~rB#zN$=+Y-Y+dj4`x9Pv>7@VPR#7iHW -npKg8=xXx<;NdGqG=)P_0v@#)j2b7~VD?+-rsppX$h`sgFU55OI`qK*{4QtAWrf%h=afn0~WKzl%&7V -Vkp!M;7e+Mqoj*!m*mz~}FVc|t-$o?5zesaOX=-f~;!B_$<1B_)M-@7|r?fB*fQWNU#7@Ic$J2hgBEL -s|gfDJ5<6t)H$K&p#~H(fBOM7#N>lpB2rwJ~@$}{A3Hi^84>cslM-e{o%Rip5t@p&b_o`$r3(i&Ya&z -j~>mlv$I9H)4Wpf5@$Ql+j;Z|3PGGyDRh -a^S^!UvBz!#&(zdZ0cg_-4G%o<0G~E(ny3ei8Oqv@LI>~#{>qv!<_0j)9?(Y69vZfHjM?^c56qw0UtE -7f+XDX+FRhwAd-msOr}6ReeCEuVq8>m4@E03SJWq;N+afCdFXgEH^MTnzFx_#Sw|1P_5Wd)|Ufh4 -9pu{L9PBD{#}MO;2DB@pteCZvwzeSZ{_K@WPt2$z&4kH#$0+>vTHauU|h=5Aa8vhIBN>TT0tdOz;u<1 -Mn64W7M6pzCk!a-FCP73xPk;y)ZB^up5=(T(swH+qMZBP(D~Ur1l|bNKa4a2?+^;x7Ms#Bk)IAqs(Ck --e?Etf8Y+;6#N5O8|?sX0Br*4q7NqAZ&EsQJv}{jRPQhC-n|=FkV5CFDVAS8=!DugL#Y-g7~N(l0hf2g)0L5RKhpo&cW0Slh^&zQ7-KqsS1@>wxdUODJ=+q35uf|F4&O -J^bzYAALl_F}5KsFtNrycI;UG?6c1b{z~w5*w%kQ1 -IA9sFX;ck2N;hqcA|_i=S10~9+dT6)C1np=cBJhKSpy3q1Paq|7qL>{@~p^@%ioe-M%k^zp`$Dat4jy -b>gF&BqLahX(1}YbO^Mmmr$kO{F$ngZ_+U&flrOt)hPEJoQT_NIrjwWZTIk1LGS8AA)WqZ(NvoPSTTME -fjh_t-O}|`6bQ|{mOadZjt{lcgVjp_2H{2&l-Duy0X_qSwEL${+k4ssLwr0{pR~f7&=qv8=>cdeg$jd -Sd0DQGUtbXyDMF97xdF}=FCZd>ZzwvfCFSF%pWj5qF={YguWWGEaVjU!H#E)wN76M9S`)KSks1n2W#@ -svHe;8m;=xpG?(TJla={B=DElhax}_Bne#wyvVWj28~vlC?}M&v)5XS?F6uoO8t{Y2-+u2y!#xiu6O3 -O!e}96%Vonk3n2Y2!!0|tGp7xWZH%&e(>3x;`1NQs~{SfqwVPt+$pC`LuOriN}I?1^y_WLkU|B!)!3& -x)hmga~xFsuh-Or!T^n>f;791FpzDO5aMULv>OLiZjN>%l&z&`E)?|Bqw<6ENg -lzq0QM6b~#Ck5)5$*Gb?iKpu)CT;hlXW!E^+Cr3e)QfZ^$$w^kbmrTr49A(zy;3+SwFG5P+E_@eBmcP -``7w7fFDyD@MEN`Gxd}Cv4NgW{O56*Fec$Sp1|o@}ps1rAs@L0RCz`Z{z?Z^-&S=$Wt{3Vwu+3AzoltY4ZrL4s%IWChlMOjcG_mcD%X@) -Wd1%=aOmgKpr0GJyHn>MWr*guV*vs;|py+b_!c1+)q1N1!V#(Z|@z0FQki4c)Ch|D$a^`sky%)c;Rb^ -aGd|U~Y#pLEnS2xNQB3fBa;U&=X*NdV{>azQ(RiK+j~6^<>2|4tNAVQodsePF=0v{+0O|+970qdp(Q2 -?hfVjXF8*!JKqvI7U=5c5)MVOuF>wfrT;_z7K;DNG0e*`}~(BHoKXkD9 -Vj34cIt|AQA$9^ggfZLf3s?oVA>`w -V(Ou;$_)^t(Em|sy_w5_E#)P*T^gSz-=NfN(!?sI2$gop -rfd$C~y4u@q0BI4WBY)idY9;v}lo7yHMsnD07%7e`RkY%A?_!j0b5_xX*~U?kNBD?b~+*wR7wZaF{xEswj8#2a1kF;Q?h -0S>Xp7Cu*~^vnLWS7m$pE54$nv|Fbz&7nBv1{o+ZJChdWa2W#{rMvUO73+UHqT(;>8Q0@vJDtkl}lg6 -q)s5~E|v|gn7`Oi%HWaH1CF3K7|(tvD-^2S;&co1vp&}*Q5*y|Bc2j~|N54fN#&~Ky91fDx~?0ACRos -L4Z|Ah+|rmtGHDn*%dqn?4o#EBERx3@PRGGvIrO-zES!M>`Q$6?YD6xX4|{STjY;C(f^pu= -E+D8Yw{TL(dR(7FmBv9p<_n7Kv+B|17(dD@-lQPklTrd<$ob>TmB8#u{C64c5OZVj>zgzONj@pCXs)!B@Pe)tb8_Gg{ztyA7R3n}=Mv4qp1&-mfw?& -5UYPq~-iLW-FwHw5!$772-srdRU=9O$@tvoqiZtw>>g&>)4%QJcXG8g+o-hU}dr&`^7c1l~$a|3WAZt -R_#e8G^@-mSI<{X0w57ghNQKN*O8+{nc;q>lRd`n5Vm`_4>hun^N1LjV%^n*km$=`Cmj>_fv=bsnyAo -hqruZFq=zciG^4_9swa{sIeHklj5ZbtdhxFGs1=;W|}Kp9K%Y%Gf4Kb<}*$`f;_(kzj`?M55$9{PIlD -e7|AuwkN1(NCi;zuEZ`e|yd#vHx?$^auIJ@4tfho0a(6w{I7+nzA1U{WfGS)Cc-gdt6}0SZ>e%2(Q$? -vL^{?VVp$%7>_VdK-%b+Y3whykB@#Dcq1K@1$0HA31c?alMom4Y>eHzckiwj7f+Gv`X~O;6zvPhV#}O8O~|N$pJWR&57YuSG?h@HD{*LNWBwIT62*@s>L~LP2`Yjamt -!23rjC3(C3WLo}r$elbu@--pypl4K0{3NuQHm&^IS*f+4RUFVom}LS9a2dO=RVLZ5Exob=qROnredMQ -VDq&1j -ujWz5h4ltLVdP68^7V*(hK5p3-hMv4c*kHtf&dNU?RL*W_oskzFS0C|Jy{a%e?>Xa}VoZ57)5%Hlpc0 -tiMv85lK-=Q86)bLk5i;DO>*g{?EN*5XOfm%`{P((V9m!<(f^J?V4kn*L<4!cK1#69q*g#`?&8@zOVW -2_5H^8cVADx&VG@8!~LfEE%IC8_qyL+zr%h%`2Fm6)z8WQ9{+a!_xlI=hxtF|zrz1R|9bw%cMg*Dzw*)!_wF`PJ=+mH|g8m4a790?w4M_}nE99Gyb0L>Pd_#jnhlQqx=7v5Rx* -&8(=*rObq3?x$9eN@3M(91-PTB!llXkAQT)RNnt5rPlmk}_F35XVO_%`!v7VX9bt@E81YKP&4?b6K9SIPV1s -6uCPy+ -9m@=GW7&uir?&Km1zwck$2kFY=%7zuNzG|Be1T{4e>lfVKex111E#74UU{b6~5$c7aiW6N%bq15X8h7 -g!S*859>}3|dIFbTH^;3dJ!f?p2)I{1g+--5kE{6lmhqe31HF^9YsvX!X+IAm?;zR;tg -S3+a83$;&bS7_hT?$Lg(J)ym!bq?zs<`WharVC3Bn-;b;ta-RM(fxD;6Ur*wr{Jb(t$9ZCg619Kz)`+ -)d>8qx_0#%i`~Mp7O5nyoJz-NFR1@@R@Rz~ULNS -?Ke$&+|TOd^Y%O@!9UPo9gYD&v!mQ`&{z5;nT&}-*>d{GT)WHANwBmJ?4Af*V*p@zctjNj`%h8Z{hhZZP5%}F-2?6q@CgVEhzN+Lc9#^88ju+ -cLB{9Le8$h{!}A%j9jhh&GW4>=IBDr{F+ZCLm4=H`uq9&h4{t!P4_GEo9 -Fkb-)DYqM3JX|XMZozLpw~rgeHgSS=s?i1pl_+?xIo-DKF?CEBvXrd!?%~;Q0mcM@!RM(#lK}hpMYTjTLKOSTn=yxaSDA(`=!2DBOu=_MUH5rHJ{^6!`}FqdBzMJ2I<_iid9-Ur*mo)VBY ->_Rg5KsVEG?DFn@spLxn;+bO_j?NQDj+I&_E-uwaD_9sJTx`Wx>11I{_z``qW{aNyk6x+Bc=g_<4xL*Eu!vSz{LOx_fvT} -q~GKANuSnZDUJyJGI|bHC@Fc`v*_-j(+-vy%Cic|TvWyYeJX>`xo>i{cs=x2QoM2xABA-Ms7jdR{-!k -M&bw^ppOgzshXC^n|%%?io)AePo`PSLrwDyY8Cz)_do*ywBdtOyv490WB2WWJ(4w?*^#}lS2pU${e*PXj6dtAeHq~NW{(Tb@{nUeD#!)3U^Qq5onR0QgR$tx>x`+ -CN~?leR87@Whw4OKs;HB6Af%R^iqnvp_r&=Tozz)f(GBrN_xk$xiAZ96La$<~;#S?7Fx7N7+)cM7ZPj -)=ZrAO(eRpVw{>VS~FT`zEegsj(5XU4E;?HR$h3^z*p^!!f5JxWvTX|`!B1$Nuf+}jLV-e4>gk?0af> -o@ei7m9zK^Hyrv5g(B58xBvhEegOa*0001RX>c!Jc4cm4Z*nhWX>)XJX<{#QHZ(3}cxB|h33wD$7BE_!PA8p ->>Ih8&h(u@LQqL3kcBni7C< -E^OEm==T#^n;>Obe+>Q2~Z=6mmZ@B7~O_()aV{oZrWJ@=e*&$%7Abp>bUI4&9fbe-esIQGxY{r7({_! -~5;eh~M5+NN{sO#V&hPMdRYW#0UX2k)u4^ZvZDJ0E!9K`HO9yYnjK2lDQHAkTCCjd}M!IQ#C4v$E11M -jhw={k0eG*4K8$|GM9q(KQ?1kKXlQ*CX`(K$n%iZ|#}~?|HwwxoZ~9yRNI4#@}b;zukD>Lf^l>w`>m9 -cc#9VcsXwNZ&SFU{lC6B9@oW<<}#AfIc_rq-e7U1qcM`{0@WJ)JxlR)W;!FEY08e*yyioj -gYWRdtZepg1n}{FtP25W*@SbJjtmy+Xw41ou*n#gj?(F|MGXws0a$IfFnUgP;?p`Fpd)h{$Lpb*26Z7 -OjZ!WHweW!FM$9;1dtCRZ`{B1iipBw&P%(8Jcg;)?eJOtiW)5-Z7E}qXa(lNqVxlDMUeM&y}#TAtmW$ -;a50VqOcPb8LC2 -Aeq1xmFC-I0_)fPuABXTz%omxt{POT@3pj1!BnK$W!fr4r)~fvv}?jvogkcG{*iophHB(bE@e47QA$;W% -P`}Bw}A2NH2j%n>8ksO60X%fi{XkS^IcSjR#$A<0XilN1hyy?O;VY+aWaI;7bLc4B1iTcBqeCQP8b8-opyW`ivJQxUbjPuE2Pd0>o5&TXoD2tp}Ppym%|AO5l~A^gcP$QINEUO -eV?HjppXVMA~XSZuMwB#YUeTkW~}sKXbnaOW1FTyB#e@D@J~n7?~g+-pmmXa -D%9c1RnKP4W4Zi3=z)5NmE-c4bYa8!OAc25fMe-zb-dAPe2ib>FrOVnVF|4bI-#{HXzjZ2jC?)d`nqB -`ztWNc$>lp8GfSpJUKS7N26X;2+pKK7xmr!DyGTV^{XnHT?B^|*AIa9X>;9H5M@SblF!Aco^l>mOZ5x -ruzy`Y1bo*Z*6QD_U^Eyb_#-I1>KDkY+lR4Ct(Zw ->nOV -bx8uh(#w@Y-tYY3%Pkk1xQ}s3I@^5Y3lCYR+pk@ZjTLL-O&n>LGa%Eq@F^+>lEm<1j*=u?ilNJHYPPH -xY2r%Mw{Vo3Vbr%q2d?3ddU4j2WQx;$h@`HL%ABgAEQzlwN|(|_JtDeVE56svfaFjtY+6{$rjab&gkAogefdcCfDcjJW3^&e`Bo+H}~-edJiUB&qzzw?uMW?AhuM3m6Mw9}SCoo8K -IX$Ml^P13_PShBXz?OC6O&*PcT)urTK4p9UzYPH4>!NK@zsQeNN>XI-fx+<{AfdBCcqTX -1%^sCNNEZ=^I_oq7yE2mC89=SS60{z!CXJ)dskC1+ssoM)_ -7N}4IOS-(&O-uOCkEU2ryBrrIqHsFpl#exg!w)2>1>FCu)xtg+h`u?D+rW(iY{cK&1%;Wwim!^D8a>_ -rT&rDk&9G+4dlF79<3^gY5_W#93xnHYUVJGTd~5~pp61H$_s>v@d6)bV}=+4w1QHk7TnlMag&<``&I# -Nr?A6q+~~d$Su`JM0On65+hH8CUCn{ER%274T=m5qSe#9oL#V5wjZ(I4A5!*^8F4-t+D=vXVlLbf^KN -U}sHK&=8l`MeiNLZkAwK;85puFfB2CT)*jHgIy@IyuClQfky?u^}!=1Hr9bG3m>Ys8bKy>@R*HH?5HH -4uxR(rh9uphW7JL*=Oav1=j@Q5OaknHNT9Ev|uhI%b_&F^rBtjIpb>o|!=#i1*`oeLMQp*wr9P2|13& -^0@B4Pla}P8kKe<~ca4UxR$MO2@A;!?4|~E+C+=_uvr5uEUrv?8!w%mIoasP8x)hFntswCEDw1{ZO1p -oI&-i#HUD_wX@U-kW2k*A`F>YcoAeLt*nz+@-$w7;$bttTA7FQGRZxYK|=^77 -N9)c|D3?Nft$P%~+$I~D?!X*HX4WW@UF!5@ZSYRZk)(uC@TR{1Z)nN!^N=p(TlJ3(Ruy -&u`CVvd|(J7+qcJNq^t19k9u~D+9{~S%By*%GUS+D2^z+M`-fYTSwvSC+U+bgaDAU&0W-yI7tMfyu8_mp7UYPo)yZZK6JQW)skWcpE#E-XTi{D-oOehm%WO -n<$ar#CU|Ty*dfIu)gAQ=#GVUr*+{4eEn~rRQ~z_ed>vx)S;+zEOM8T0;q0JBQU7YY!+W%k;_Bd(V>` -#4eDcC8fjYrz^u%|YzgKj;?rB@pM5Dgj``T$@Bl1gdmz{?pRcNe6BJJ#97o$l2#`4Ee#h-Iflzw*hJrB9b^e$wpRhVn%2g-novj4uwz7+d(X4oQ!|d<||@N=%pL!m7$wqYHGDCAX;JMJ& -3Mb_=QM`9duPh^TM!>CLdkQi$M=IR~c_@*IlpLfVZ0&q@INm2oh2qtC)wL~bT}Ib>0;&&?C_zXte&fZ -6kNbR3JbX$O3yB|02J{e19z*dZ6?`a`qw*beCCbX1N5t^sz- -07`?6BJljG8)3-MpN*pg#voe#AvcUe%)1d+1JWQA-MXRr`2L=&9-zh;6Sx^=vknXPLhMP&Whev($VMtrQ9)^o>I -4jg&5L{qur*%}j0Hws3c~;_1&WL77&p&yfTfkv#5z^s{lNOVQOGLrkce*QKS6;7AZFoLLIS@s!mr#gCKBJI&n#ydBp){BT=hOaF~*)jY(8#toWX`g+sKW%J&mZol+*Jot)w|R3ciU8K>gilkehzUpKI%GG)of!@qEy -m#Fo~8fbRBKtyM4qWU@_5uPRKJlk{~W>}4)<#bJQ+2(m8P4&AERw&<|Bc2K;R(OUuax*JV=&08Gnp`7 -YG6ny~#`H$EFi{qn6QroWPI9Ibg2HPiFM00Dr6D_n8k& -44TlXFqIt4&7b%@h2p -36C7sm;TsCIQC^>#dX$drIn!{ltd!#v13X1}q@Ex+*I%Vb^5Q3DbtFm&I_g1Ky=V&)Di-1yPJ|HMk#x -XPXyi$SDUTH!xJd~ee^2Cnujb$Q^(<6ldfL&Gt$tqahy_?% -dwd%IBDDiZ~zJq81NAF-Hb#(u7*z_ErF}5~BaC*iiGsSot*G#@ -q;J=CkV3&`llZi)X#t$T}Re;%STeb|PyxRH&|l_E)Hn45si*Uo`>~ifpJL%2- -8t@Rxu$QD67aZ!6TMzB1X!OQrZM$g4i(Hlk4Bryk)A1-!Z_iwYU3Fk^dpNe<+*A$SsQRABw4l)ieN@)Q=@zRG) ->0ug%an;qx4CsxA$Bkm#R!k1lu8WKfDu4_o2tFdaZK)oHcL235L-NsLMoVpWG7-$0aYL2*Ek?bZ)C7~ -fG6 -zc@Dm)I7lo@j<9|LGi-Ut_@1xZ7pz19%Le7x4BRB*hNjzZT1g@Bk5xFc0tA%e1L<^^Vfii=F`?>1RN6 -vQB5Bp_XJMJ@wb)s}YkFpLo7ESZo9hcz=HdCxK>pkvAP-psL=t_qoDUv?N_ -=I%5q+Vlt%1;FU^ALsSMW+TGRc$;*4GencA$pY1z)4H~(%dGebQ%i14 -_Ll`HNXKEsIG_1TKUs$dCrm$B0B}+j$PO&6(qzMtti+A9NJj-!6sDaE5FyWb$m={5$ZvTyPjWf873C} -d!RvY}5nyZ$6{*AouLYSpuzwMoE2lD7+Y79qxqMS4OPAn>Ywj^=oZ{l-VTg;1!q=GIF$vIj(F2eTm|FzBJ>K<-59$~rL{B&a -KQm3D(tG35hot+2{4IRVR%|MoAx+WSq^p!@u5`W~nX -w0K1GBrfhLXm(yGw+G2=Kk{2lZ`;>jhlr2eo{r9Qbe5gpi3n~#ZbgfK@PLr-zqJyM~(By^Kc8=1U!Uu --~j`Xx}4Y2lp1+5Iyia^Sz{%ukRkDq -SBH6V#L>_d^=iz^V^G=&UnS-2YU4@OsK|1)rc|S5wNs%;k!!m=7*wDTxZ~RkQ*NJIzu5cpcqca5mGS8 -)s?PL6-Yz7ORn^vOkLrvSe%dkmW9#CDq7MavkeNI%{kYWV4;n6;F|q4<4lFgMJieHv*djD-{n~@IXs# -^0NxILl)g~w*?k7ki(Fi8}gp*3CSQi$od&-R{ab-^XLzbfb=hH`aq4) -O>nd)%0HlZTLF|8750KqI3`4!o#4b)M7lAA7cIPVc^d5IER|PZX4^KI+#B2*~;p=T`<`5<}zfH$(zXc -1DAwQxl^S!S7$;c_gP(zjc6V@SRiliNq{sc*yMMP}VTcQuaAXk;*vUqV2R?VsWWJ#Km9CypIZ1v`^bI}I!} -uxPzHFfUIC93HmK=MWPlkT>d{!WNh1GO{&a40~;2IACMPb+~!!_8=X}KT(px-5p383YKc-znB*G3gaL -TsG}QeorSDI0e+R@=|2RV#r&i+xNbO8!( -CK_D|p9=m13B-tZQk9a~VBGnJz{Fi0EJH{4i57zZypH>$tvhsg>*>pqZ#^* -OLx3t`cuK8uP=`zQZ4;t)fC5fb&WQ{Q6xzf)xukPf1NAoZyxfcJJEva~+}fAkhg6dU}=UIA;n3Bh%)A -6XiIXd3K(?I(*{be=_6e6yHo#_hUs&QU1R18%Rg4M30Q&FaE_C5Jzpwe>I(cpus73A?SXbSYnN@R{3{ -*!YSc!@?mYmc1~gE(^Jj>WBvl)=?p<7lfc|8k)pE3&ayiHu%2VCemq@(|{rS?ng!d8*J=69MfF`(p%! -`Luk68x8APi49N3hB2Oq+?QnqMw09e{7cQ{^Ug`GVzrkQVUMb$0fvnmv6dNNcbXjablJjnh8SB(u!$1lPoFLD|=MDC^ -3*N%P?qqdBRbhMTTa-q^dN3lB$p%2&6VGd4&R3!&w?Vhx^EzE4RHTVsgCd6UedQqhX8F~$B1U;hhdL; -Q?PBL34MVwfDhuCdy!%9EK!Ghrw~Baf=UQJmz8|3UtZutV=#f|3H=l)Cr0r5JKx;L8tIZQBM{LExzlISP_~|*`Id4)q;RgVl`X~Sgnz|LjUt -R&>?{RG5kxdBFaPeArX@gET0Hyd2Y!1zevTnk#y5mikAUy681_ -ILXAgZtTT4KT$CZH0%c}wdN9CeHHUn3qV3FN|lqeeHd|vNO;{|NXvJq>aTFUrRtwT%^q6wIV=Uj7t!z -_2>V?>$ris$l|J&5pK#c~o6vVbqD`YO+F#M`<#jqGkHJe_N!cO$t1Rj-81)iFt?&l-NIZj1Z3eKY!9D -VMEtcXW1{9X?U*ZZ;)0wBoQ6$va3wAbT}fD>&-Kj{VTig;2vePy@4 -38yBF9!rs3-(y^&|F#r!{&~QZ}RALhsOu>j9e%gso0cyf3%FT!U-j75Q@#U`*x5mVL*uTG2_e?!~y{$ -b^FBGqR9~p|zU>NEpG^;H5(PEFrjAAjv0mp`5Gj+B+WDhq{dPW7KldJs=R(o>fjDe9X%7;;gN!Fb&v* -iZe1fDgfOV^Vx=(+jw458LqUgVUr^+tLI+muv&Wyz0BZbJq7^%6yNL3ahGnL@|VgIBMRr{t=9FcNZ@COEukxFrL6fi^{vI>zyod;`<>Co;v -P8phUl+R7GB+kJpZO@7&P|&oMf{cDVIMJfd`T*5kBb=QQd6FOaxKA^*?r*^iD*#CsJC@ogkr -;BT~6f*L6X~^RC1$REW*r!Z%=+Yc~^+|UM-CoWl-HI=(Jjh2f9k3t1L|2ekEc*e7>^vw+g({FGmA#|z -#6_HYs8lpx6ak6N0bD*OEcc=*udUW=)$M1$#ALmDkstSF>McHf9okR1jxMh8l6^k%jYx`fX#XaYWZi5;>DlAT)Us%Hu_f8nN161dQbAuURv$$^9&I=fb*rgbFB$GndnymC>!C_z;jvConpP1!W@|`l{U5aa&}0Tu_%BsG*w(t&rkI$m$9IO -$BuKxONFd6Zk0y1=m5jb->96S&X7cza{p6Zr`b+*#L=RzLf|UpjH-K6F!dF3JG#C3ax#l!VF91T-{&UaaG2FE;D;3A7h~H+u0VhrRd-dhxU -1`dR+uzk%Wu-G&~%-lX0ET>pyj9cDbwc8%N%BL9-R#j>A(2CTBi6&ocY$?T$sF*bGeK3zwwOVL|^NI8 -_FLl$2GnIE%K1wt27AUyg!Wvdpl$iwOjJE+d#9xK&3{KV-^+V7dZpcA+1mQ&OPr`YNXxd^Q74)eq$`R -X1a7x7X06)DPp2*l?{$E$Pf*y4>g54E*@7q)a)i_DYBxit+B@k;0M(Bxb#RhgQbr)&mLPqm>AgETV}r -clO-Jaa$o4z{843Ym9wKT+?YtgvLkL$EwNmjtc`&sjI9(INCeYWVdW*V8egp`WPtkE5TaQ|Nn0)Y%PF -aGgp0Dhbs~I|XHn&Sv3`98@u(QHsCpI{@wk5Y4YuCU$btoNLLD&hAMt6SqUFM|aYrC<~A2B(sxFkyB8 -Po8+_9U!h>?YLUk1&Ct9<8{rKel7~W;B$&lvEK%S+sMP|_)4m}E>lpGaRO|Q(Qk$alQP)H#$%W=BP%H -20V{=tPtd*g93EFu&2=Fa7<)~wE8rcPL(&N45 -vvI&5(rMy-`V!`!j0SHdqg> -qCWPezu{hYB8;X}v@6^G36s5xCv?MG&QWIr(-gjLq*@>rWxKU3Uued0PD@eSzo4j>Rr4 -(t`r=t&WR%lQ%FGk=2QWBb1K!?^kk|>dWfv!BtH1P70n-{5v(&bkFfoAc*2v%q4`j+7@FCx&Q7LE2-&XgIE3S1_OI}XYj{Bb1SX3Z?3Q->NkH@yuX`05W%*sZ1 -!}U&Pl-U4?JZt)50Gl_hM$tW0c}aX*qX>=lqF>O&hC^eYJj+xQyPKnL)M9;wtYH1Y{T5uTX(~g0qS-S_=di}%Xy_&9s@Jf3Wv4n3zC58k?Sf##L*trLX|9p#c?WUM(D -qBqP=_5%OyJ8Joxn3#d=wxlK)qvJ899z=Ak}~LCG?IA&H&>%0OR?2Au+CxSq~sqhr?1JQ**_#uI|V(S -b=}nuxUKjrdtTG*~t5LN8D1Dx=c&-@mr_f9WhD6LXTLxBR0M!L@Cu|o3gu444Feyb74P%G^O(gr8Nn5 -q<^6upgcic^BMN5p+IzXNQ2aOSZEf6iFUE-SonsGhjL>zVLT$u$6YUfE}-2c6Es35JM`A -jWNYxyqr*I89Tb0g3L4L7H!E#<(1YmJfZ%9lH%95}lg^=jSwMO1kM+UWb{l+c8}PN6jan+q?Mq+7I0b -8-l;hk+HdFhakEbDZis^jK57c>Vs%^Q)QBZ5rhJH*57n)H)R^xQg(TH`HB@{2<_CZXnXuKnkuH(W*b( -t-A()`H;0+vzpuI%-Eu1nFn{O95mUl;tv3AC0vIh -^bOIvgk4NTq?)B*^j%Aj>`^%YzwN#wTh~Lf&?5h(V#z{|qVgxtNG~lLwIRuZ%<_-#n1-@=2i?<$eDH{ -XU83h&yL^org}L;D=A8;NLI`ejrZ4H^(XXI)j3jsAug@Q1D%4aSFa@AO)YFAiBRcDENH!t9=Fqe++@X -c^^{nC~}x;<338npDY_l#1{=D;{ElZS$w%CH1CeSfmFPT)h%D9F4)JY_=P~lFMx~_7#Y7~-vBcH>wSs -2-?cYE#!HYUoW>926n}_b<_eP8U&~*hmgr#AYzw)sDtrwR$5-8a5S5;*#_KvES2LzQ><}4F(r9_g^%tjxy$4`u)vR2w#iS#z+M{YlO|xG!- -_Ovix|HA(q23%?hqqKEo!3`8|V;}??wgY?kx42&;CYB^@m4Z)d39l^2t`mvWd%N3t#_WZdUbDYTPG}f -Yz*%9eUL>g&IOqi*nu*31L5Z{dz0^p$Wa+LcBHvWe=s(rZm{nrsOL3SUCf_C;4iGra5eQzBK*D0mk)o -0hfwp5rK5Hcso)256#?z6F5x$WRI>p^-!Vu^q%-e7+seFsHy{!*1iN^F3W{6=$7(ldLq*0Coz9GE9YH -Z_eFGzvTxK>@>p-)H6RYo?JiyJxy1UaiLaRld8?1&dR~yD-Uc;81DEZI&ujFK6a?0Ov}U*}1*eh^{wj -qo2WhKk`MgUCg*rU3@+KV;+K&tTC!0LA_s`PXq#-~vGe}cU`>Q7DRpq-rKlxFcWrY8{RTEqt0zat(Hq -%)@pj1koVn(Q|zl*cEYCHX;2S1$_K&yc0+(zwl^8={b%5uMh -ijL5@ -ze%$B`a%;)bc)FM6CRT)fa}?CyG~%+*9@c7mY&{F}Odt)`=Lf+kvkkr#wi2+VtjHf$-YprA}NAJI;B*eTZ^ZfC`_SMjbh&db(GpW4DCZ|Dia`a8q<^XPfeJXBt -Fx5q^B5R@_gfJ1d}oQ$L{I=b?r=^c_Aw%;+!y&dtk5LK6Zw+{#w#mm- -CE4WhgBNp2dskCVWc;hT1TJhz0@IFOt;d#nZMb;dXGok-22pJQqIuM%9?U{**o6XEQEb|d|~n-7hPfd4%L%rEuIxrtRGK*_jQ@1~ -vTGN4UFk>@PY^=U;Gb*277&Ab;zyn(r9t@sh;h;C_}=sH@NshY4nWiCIVwn3*bJVrkjJ8Vo12~wt7h$ -k7X>IGBSF83!KLgBiE7*6{)SUqoDzbL+t5-i=4^feX1DGyoZ50AYI&z;L+K4s!DPR^&?nVJGTx-XP`3 -|Wg@F+4es%CR9cUE`t24v;*xVLp<13|%Lhjg|`)~ih*T2+_j>fgh3lsV!idbYR^ba30F{?qA2K}LhgAv@g5qs-*Xy?m3tK|Qw!*4xz}1_gI -6-G2sGk~7IXc|x~4*G&yBp>_`$<0C5h5rji2)2A%5ak51)WouVK2cc7!3CjZs`7F -hCp`6W9-nm*`0V%=mrCZUe*^HFt6f3m66J&?nf&Qc6Hyj0h)Y`C}*$iB@A#QEhYGMwe+nGNiaL98&rm -XUj_5e8yd(mtUQPE9+IwzoM3Yu@tfWK*~0BoeXc)PB9@s!*99J&%-q|V)*SQ@boOxx)xocU)d1Gb12% -5$h_IF4d(eJYZGEqeFlTsN?mdtcUdKKM_LnGZdAjM{nR0n3A${BbBK*ozys(Ahb$bm3V({*$ -#p0UxtgeUG{*UNZ}1J@O2wJ|EHwNl&Fe2T745$a+)vaBmU#nnJ3&oy^yKPNhCCJKI)bQ}~*Z^y5ZJQevfi%@;IutCXX}?&NE-XsC>@! -7JRN`FLR!0j9JWFHS+Bu+)l1Z0GT7pyB$x8I@=Kr3KIj7kPf;)>yO~PxWA6)1n9I%?KmgHZd&NeI8Wz -Im8J)>k43aux|MJLm57Dj^NTs3)NVIJcunp4x1Inf|l{nTD$>~_l#V|s*8Thol;YON$Gqqzw){my>kb -RR0E_*4b7o*jW%^q-weiD3d^sFK}f_025V!Vfc>o9mz(X4;0JLJO#T3njD+LD}0QxQ+_^^+$nz&c^!@!hgdwv8Q81+nDtL8$a2iZwa<7wctV@h3dQpA1u -k9!+z2bo>ZX3iXKfeH=}^ELHmqu4B;Tw02{6+zX^DM^f>VbHNGatL{Ib5s$2zp4f-9SSOSU7=7Q)^Jn -OOvRmbj;CnJ{7?iCqb!>X!v6zd*6uexO>mMq(%KDP^R`|!Sv>nl|x9i&IIVo}ToOHU+O`_>~%4Z!0+I -VP5ML*_c&{=_m=QU|I$WD7O-CVo_RHKVaNe0oIkDcX8Njykb<0Dc+E>=&p_h-= -Grfx1^U#hD59&01DNVm%V0x8nylka&fzCUysx!vGtOKwBQK&~w(KN>lK8PN|bp9^E*|JuqBFc@2Y3g2 -Ql57~rrxOPN@oU)-n{hMKo0{QHnJT=yXYJ2Z)(mg@_(K3-dh9V1(7h4Ugr-gBsf)RP>R)*&7Y?p%l^Ok==hUIyc?&rA)vk-->v%LL$uPb!4x9O(jh&O?>Rv4!$KC;`^~EL0WFlha{q#Bioo3zWg8ErVwsb@>pMGqN= -CSF&BGT4cIR9^_LRl4xAC6f#V7vV=<$3BU04sAuAAzUB&i196>zZy>4X;~Qw`D10x1Wr=Ut&Uh5_aBD -@Z75EP~((Oo%OvR&L09!u#NzA7vdcyLJs$~L4JtyXPmX8+oM#?kM{XWOU68f|5xHy6A(I@c^z^Y>x%D -^yCdWnl0_lFhqazGuY+!IwG5YAkKTXcA_*iPdfq#2H2uRPJa;?;~07@0)w3`lOshY8B2ouHW -)-c)1GPXfdI2k#CkgNz0}HL{SiUqGp5vv#@GRhisjLQTwC^~pD*6liFh$W3389`E{gy=~MbYsHyR--& -Gw@N055Jcjh>wPC>u*Cwb=sB$f3Lo<6Z{i7M!ZtR_pP&tMrMw;u%a=9A~2q3D` -rvH6-9%^*I=blEx;Cug+#wqjXVlXMQ$bL=L=_28LD;yT8M -9>J?EU|M!UdcjSo3)qE61i-%IfdKj|pkV~bT#si1lbSultQ;_mnScYgv;OxBgNb_wa^rJjV2yGVXJpV -O27-MX6EjlHX5?dLVgmdB4X_05T)#g8PQ99B>88UkwQ8nS3uh@Lr+&=Y`3QnW+VfmTIg -K}+936}iTXi=frQaFQ-}ExYIq~-z}<-q>W>=+9QIH-7U9p%ebI0UWG`tnd{|MA_1V-1-lu|W58e{lfJ -eF@LgR_8VJl1TKrvMpQfJ?MWl?UjJeIigiqTC*vKOC=uR+D5@s(Q4x&LC4Q`Gz%j@w8rv&O~keZ_FrJ -JcH4^)M9x<9BDCEFX)o7JJnrRP1Sso0LCB3Ehh4==`rQDw1qsco{vbRw6dEK|L4`yIL!gA>HgFZS-94 -*JvzD1Zx1R@#3{VsMX5yhkRBc>^?@#3w(Gt1hwfskGAtV^)3s&hcD%!WGf#;`=I&)+PXqJp)}h3A%9X -#tUiLxF5X{V^f<>gkk!ae%Y2JM*=o)(;;t@Q0g3TwM3Cs?H2MjQ&ScSeysbT4UG(IDIJ8gI%BzdQ1LE -+8Snb!}n2LtWVp+3j?tqHql~C!5^-Lg~_6};L##XJTQ+__zVp;zUM_oJ;_YIxom3OiOUSkWrCMoO -tEwC^xth(J>5R4qX!|6ZVZ{vr$X9dV%ABhRd-H65C!pap|Ow8SDo0RGgC84FW^H7y*w44o_&>{DSqxq -*Afbk_Nu=Ki@-nEsw+*}JXSPTE56H_*;b~`03RvZxF*n4vVg>6(VXZw{K-D2!>zi4<~o^*DDU-_3t^(uFrKtSI*yr#9)K7!4n#p1K*mt-- -*R9lYFA1Twbad_G3*PJA82dprxp{0@8=!<8pK)8Z^T@gD<6Sf8#f>HDz5>k4(32-& -~Qf+84v3sUFW{)#@#YtJR$(YeYR}Y1i$q(YzZVcBpfk+C}}f|BZ3VRI45`YpJUAI-V%ubU#k>4!m66@ -In2Jp0MtOm9kLmH5!`3LI+rAJ_~)tLW@{v -GYyd{3Q&yrm_<-VBy7q59c6p)eoTm8gVOhLhn`zd^z1G@P0oQbH{)GH1Y@CKiaHMpu7E- -xZHv$1yR3M87EvPIqW%TlEnUSAH*%ab9BRpqU^pfXQm*FYG|Vxge^%#jW1hOU*VydF1swyf!`6a~=H5 -!p{AVF=NT(;~H+Y@SEp^P;N2JrsEaU&SDKW>FkgO}}j5%(ePq(sjC{`NpZ2Zm*e7EsT85ow~hr!1rv-0 -=Wz05mV>m-VFfoJ9mn8j7~$$m=3YdX*B0|?eg;;pm`&QLTkRBPvL9GyR51B>5npYJbKsyibhtki>|Da -89g`4#EGK0)!9&8MDMoBtY>#y-H;1A=ub@>{bj;LU`YIAo8KG}_?7MV3>vV{{-t;$yc1*V4_jLE?$q5m8 -6=v@4w^!&75i!oL*HCfJ>5(@!*xsy>LQ?$m`3sswUaBs*HXD&Bbuj}DgFhi_-i*%SFlUA^)z$tRM?7(yd9F+n|_>ZWSb&22wTa})$}#}$EJV$gx$3EtOE(ASiKgzI#B)OAzj)qcXzW@vO<#_GfQg=*f@3P~_Bw=|Tmj}903`tNPZ`ef(|#N(*=`hA&|DME&k57}8Lc}&C=ktb79;I8SCilB~or7(!@QKzjC2c+BIn%7^ce5F{1m*{V3r$)#FY`8&Y -Z$`aNpKkBFk6O#*z!=nRAHhHhqt7H83W~eCPX9}x?rdK3-VNro7n{u%2-d1%RJ>e -pXTf0UeZWyxp5Wt&(wy%qA4>lpQ43$-J=t6qDMSqQ(!iqbBOhA!0Oie81TVZk5eEaL4L(etAB#kb{A9lj!;D*<^#rP9KRArIO$s;|r-cl`R72bPgU`)EF_Wn{W8OaS>$$ZTl -^w6LMaB@jQXh|y{{0Bym7K&+OBE^Vd?0B-6Qj__v2p@dJWY~{Y;D>AP0-K;5OI|P@o)$icM=bm&fl?3 -IkEf<~6FW7%25(vLw(5N#DGox5TZA;g;w%RfeK*E|V{*iO1fGb4xRu>Kr9S#Og&SXk_e%k$NFxwO?yO -$Y`oroqoSfweyZdn1^!8eQrMI=X4>l%wILyKI0CM%L`UKr0N28zNsIWisDB3nqCH3tCMho^nG_t$Dl& -Ur2UHbz|)r#@9gheoN{_3-^An4trN@miw6Rx3@fHRE2KHL`*i2jSm92%cV -tAUR)tkRo%^zx54L^p$i@DS5I=FO)mW=ZhH-F3UEWf+dSO!&lmY#y=9xFV>vcV@E+LO#?0vWOkmqt`u -LuabhlU?X8T+%M*Q6fM5^b5ytwdNV*rv;IT4?d0iL`@g;kTSuzq7Rf)dKRZe2{Hsksb>_@BD{JFhbxd -Tb-QDZ0guad`=84xm3ara34hrLA8Dd$P3QpXGqD4cs%Ga}`(Pq36##+g53k_J*3*&o#z(eU3?-+Nmgx -9iJfP{nQeZJifoUsIaS`Xk-IPB0~tf#*P@FnH6)E#)1)By>XLE!?}%J5Pov5Yq^Iu^rr&j#j4gFZAQxsei1!mDYMn -B{K;o?r8N^gdlrXhPjoR#grb3M4}B@b;2+Kd=`7bCba)LqA~7w)udW)KB(dI*oh_lC6*4bB)f`WGz)P -u4jC@JY3!SVd4nlcKAfzZ)x;(LwmEEUNqn%Th;qEqLuRuN1nH+Y>0fdnBT*%Jo~D0<)~1R4$oT`CYLK -sdHl-MawSHiVl=8DPgGixM17OwE(bo&RqpDLnzhU50pT>5!JSiaRr55!uk27a;B_)eb5guiI+n6vsg3 --~R(@que#BD8N_?s3Lj5qn=f+9HDVXXk25SPiai@>8;r3Hth3yX)V99kzd)C-%291O2A)rdY^GQ1p0d90_Vzpd -_p>_+?6C}p|OI<>XG+B3coTvKN6Qmyy)Gr!%x~^CB=kpF4<4E0>%=zHBeTP+|%JVZxzd0{bRQJ%9^ev -Lu}O3NwkXQsn%}6%uAX4hk{w@>=OU%H@Eto>rwqo-I@RP46Y-9Q-BDCumW+yLwLE?fNP)Fomzytn%1@ -(VqUVoRpZp}uZ`z2bm&{D4t+uyqN`>8x&d{BvhKxTDC<7t3%^t&dBt0l -M7>-T0kyUAANK$2nqaUe;$3m*Y|lqv_z(v8Z2Wa(n%K#DX}UoTy*?tL#Y*A?)ox&7g>yHV^XSv%krMY -0D`->|4J;iWBxa5D?9!SFmaxht;5Ve0sb6V(3xGE2ro99MKpvNRa={`4m%I+I0nBc1@LCj=`km@Nt9p -75R40J+Gap-;5PS`*gd4VN4}+@A$>_)cmt>_czvRA)o<7}a1NlGBB7z^u*(wA~02uoV#OSrA%RC`YYQ -wsJH>%2bXfOR0WX5#zLr{NxkVj{B*TxAHQ6bC6RW>>(ebCcWji_(UjZ77wl@Oe`W<;}-!b--?unSjNJ -)xz)9G$%SIsdg{UaTRL=QcB_@s8}T+cl67n$RPm{L=nyr0bfEecdY04#qCaZNUAur4`XklnZxQs*H4E -7QFV`**(xJ;~&}BZDtK%=}(Ehr1@xe5D8cll{J>JL2mMytr72s&lXJBcG?hI0E*9y;yI83H`>?w6J?8v;PYbgDuaBkw -fwSYD2~K@`N#a>2dyxdR(=`!(BcmUMCnxzT|U>k%IWmZHqq56@{`&{bGr!gZZ_tLe2l6f#Ju5na43dv -$`f5*RQNFMpC2=EcA^;3&0_fM7-i>Qzk?2KV>33gS+_q!!;n7Js@orDiLcPaZ?Hyn#CuG0bmH$6oOo| -K(g6*p0R4-4I`r1~T{i=ChErbw>`kY`&Cu;*jaE@(sBe+A6BTKv_n#Ddwu}ElyDNz=U$NXK>J2s9q&r -?sm4EWp>3#IyqrTM~^Q^2Ei7kMa8#Y+|=JjIFHlK5=Z_K9>?0O|JD0f3mw=_kJ9I(N65qwWDzR!j49Q -K)B(=D$O!yc2~gwH*F%$hF_o-LH`VoM!G!?w@C)+{g7 -P)RL#19wzfsOOt*hBgEOu+Y)|}{CCMr@G7Q%3e={;zsWBG2iY;vo0zolSY#y>HK#k{V7IxH*2Nuqa>V -r?w9lRu=Pr1#YG1LSWPjsb0$ULOs4nMNo6UtWSz1h!##@?XX8A@2Fg<=}r^!4@fVf1uPDffo5wN1YKR -^TzX@=2j&6WZ_~i(tV_QOsn+OzO}#EgRifHBfBshBl#+*+3n8-)PfE~D8QCE -VdM)pvR{oGS0@=^t1NeJNSY<|eXvPhFLfBoRt-xMPe#012RMzRCWQ1R76Y2Q3$v=zv5xmn(Z|(OHKQq -i@ulEnA=Joyo{V$OH==C^c8v$@F#&2vc%2Rri9?epE&GJxU$$Jx$&n1>zc!#pi!q8w)rU}o78HxgPs)m@q)&-TjMlCez3^$ItVQ6c -=?I7pRE~Bo_!wiBLJZa2pXv9y)J;_V4IAQ~QYy2_5DuBO&(-(i2^(0#44QxkP^(Y@%GVNrZ21@S3CEj -2{p6^hd?$u22R?>PD{{;tDEmj@WjA=1RuY)2mkP@zz7RQ_S?;86cYB|^sxOJ25u4;RfizRU&%X-Y9^R -TKWjVKz4fGa+I|kEx3CG!)1IT*%^L~~fG;%FT?u6{A=*jpV9^};SqqH0(mKneaH?Z@K+df8~ZwU4TWL -BPks!6wBaSN+#33@&5l+xTa-F(e_^rW*7fh2$c$hTjkEY2z6q{C&ypwcihQ5#cDl{S%NYflIYJCXg>avHuZJ~J$JawN1Da_CZBnoh<R{-%j3 -|J|%8UjMykjG>x%mzv==X=fW63Gyk;sW!zkJYix75%J$U1)?|uk7#Ex>ppr8;@pPPD!mz8Z!!)vU%T0 -G*W+uhGqH1>5@v`1gB$q~#$>kQF)#gf(^&{aFIlVG(S`^MjYj>_Q6c}h87=6FW;;sx$1G@Kzu4jD@OP -9-m=utxvu!oUHABC$D0ZAsF&mq1Z@!7u-OSN?N=-bTqj0*%;n7>PhY@Ee<|N7I@GH$AMuSwFUXH)`D) -3LOrG9FFb$;ZP;WZ%pmGp;#)Z4GZUm1ez@;xBVqJrsM!+VfyEXSg5)s@HsO=81zD5OcqUnjVlE6_<+fMN$8Rm -AbTmsp*aM_!I5TQl5foNpb4xP{vZ#Xy4wV@*67ap!t!K?PhUGRUCyW-Dxfh&Gdi2=k=hjetnSre9aU@ -=&z*rL)4@FIv)$~;Z@`NKN`T*S(sR$u2sMO6sf11lPLC*tpT_(kH%@2Mf(EK)yt<54B<6RCW%z3wu4e -wROw{g@we=L5v~&EjYaoC0Cx8de0Uzv$4@yKeEM`2@ur8&)D2hKViW4mJ<)NVh>K~{cB0~SlLn$;^-P -?yFY&!D@Z&>x;M;Ks-KQ)2AGmr2>)2SoG+ohw~CSzRqJM;p*3!DCGGXCUJd_)sY98o4v=w|iBQ+uzvMSMw{4AifYUa5%og{i8xaaI91@B+csH9TvAoMRi{YoT -Ft?Yq0DubW7=Y+~JPa-*)9T4@*6o+yNC{Sk83YfHt8ZzY22x7NuMwj$Xcx#aHejFwf~${TW=0_qVI0) -?AT|wjc7}4)B2QI&Urg8<4tj~+Z&BBaS_u=aPHV$i5rGEh(Jpu?uczDJ`!$=H_l_Bs?K`!XU|%>0l|> -ifj|>~LJQsfkg(OGl&fu0ZgsLUnaK7eJ$f7Q#Ez_UidFn-K_!upQ_m23uSI09)J3Sp90|i>I-*edWVbY;E8F@XWTh8!tM=*49UDZ3o8;TicgkNPu+p3j=IzuN-gK+ -WwQps!RGaroZr?Y;8}nSEZw}saGY6NB>4g!`GsXp{w}w4`4(~(AbFd7`Ce4evlZEVXMkUgzm6M@)NeI -IHG)rs?0Zpmltk`8{&3dA2-AWW(u=;!=LB06-|6s#9Dc9{I9a5&KJWQP -%bxs?rUu#{IC6c6C5QCHW44_hZ{qO#YkN<)DAN&=2|Fgg16CVP~ZhY<}f5p -|$CDaRHeN%U82KEYox^MU{BuNb|5fWKn%|LCun+~4gd{&6OM#p -bAk`72U)1(X_EJj+L);IHU~F2wy6_nhFb7O(Ws|7#A6)~6X-z?HPpLSrp8bPUEF*M=8_{{%32gd) -y-j~2dU2TuwVUb~T1_cF|92FG91pyO9laWDZa7f&(EI=8AWSNE;#c~0G0!Ivcy`Gho?R(G4T~pjQR9s -3+D@v;`VM!aJsrP@*z4to|2x{;5-f#K*`wo|L?tac)&)r$^!2^u@S{`5=%MUP)bvnQ}mK|Um`~S}Y#; -;z#*#Sl{@eO$r&jIXfet@ysK>mvd7{6PALTM=rr?mr&+n9ZZv;7SR7!!VQoU8lqKEOD1`%Mlo=4`*&0 -miG_{`3LHvammKfU$mCvjdC`x-#oc4lwTAb{hv64cl5jz?fLr>;NM_iiaY$;{@YB(kX?Gj-6l}f~)B` -LkC@u2edkkw>r@H$l_ZcXdKB7G!Aq+&{)9_G?o<%&$9VK*U8Q^*3M%aEDoz{b2y@@H372JMLyK%U+@T -?X~cmFKhrpdpJ_z%2#5Las@j3#NMj#yr11`Mq_LAY(%4=cX>2EsG~#}aR*p3O)S>kwjS+Yf@Ai%~dNe -q4rMA%NNF!@OU%pVSEg#oTtJ<(jSN4F9vFri2^k3i6myc|3$GMejOWk^~d(-g#?q8vqM&WleZrEJvH -msCgnn(|VMXP7V2tH85`I=G% -t%OKpc3FO3stcY9-JTivTM#_8uAgfCG=)yJzET&nb6zKH7``4V$pRr;O}l+;sLs{3Y5FoNpEo|^m6vUj?Lr_24Br~u6)bEyye)L8z!naxDr)7==hSKHvK0I|tay;SFQuC6_3U99Q?Pn^_No+ -~ZH#H#byTgacar8{sr2}CQ~{<=~4&e%QOAst@Smd9#}8iVF3A(-;fnmQjX>e@pn<)dp_gaK_gB=iXj& -c2w%TY7Kh^Cw^m_*;5`278{yV1G2!U_U43URa5fo;kwKBgr=g|56UZQ%8N#Mx6L3R!_ZsCGHig%i>2& -dc4B+0=&piG++wFbl<)V9FE4pQrCLw4H8wk`+`!^bhN=;S}`BvaDRFkdU~AswY!%t<5EO;J>}c^c?=) -FMA^ASW%)Rb^y<@&<$A{%ZC2M~@f;1lQFvTh17c-S$OL@39ghQt>~kOeGCxK`1J=e8Qm_CDk9CsseyY -0oqCc+LI_z@~bhumsF6&Mf{c+Xik8P-CpZnF9xSc)Lt!@hs#0Ray#Xy^_x5e@|59E0aqzqX5q|mWQ4z -=tT$w1i=mqvVd2~RvJV3<+ciAgO3R@iZ6MsKe(*!O6+VuZt@`3P3)T4iv(fLC-CU3RzB7Ej!!HrsUUrlQoZv6Y7k|fQOL3117``qH*}6gbJ>BpTNo-l9k&t9%i`>c{^ud(|jp{MB9}?NM_MN? -?z*#6jI-RGqxbg*e{jnxWm!4>XpgX!zyJ_RApFP^t1D+s7rT@mekW3s~!*huwA+4;I$M#JTY&!8U1d%}@6 -X>zeUIFqD^>toM1E*VFyljm*jXE|5PlFxt}Et0W~mo+z=WY?D(bj5vPfPU6N)dt--b-K2!SYH{ZfF`l-8~Z7=FT8?9V_`>dvQ!t;ZFFg|q9Tkr_!>9a7E-L`3p>FGb -!l-B_L=Bnb$@WNukK5z%r%cA)kktXW{Tj;}^>7A*&Po~aCP=`*uIC;J@dBTj-cTlEJrzlNf1$D=bdld>pnayVy>3 -n^;?|2laVs==w_4Xo}_|&3kVw<72+d1_IteuL6;cFfnEbJ5@h9GTVbMN-R<}Uv3S1FOZ#~HlstThNbDh=aiNeZsLlWQ>;=?;!hXG47V{*MuMJh4mBCQJsT>%BgV -7IYx}{4maBKRSIIPiXg_QQ8|ew%*!7Z&x8NUdW5PbO7BIEp9{!!9HH;Wb;pEvrmJv=QcJOiwCI0?(e7uAIc^<{+qQhYzV=uZ}U!D~ -lOvfwpJ3%4DJE5VO^H8lk>e{QeB@%r5QS%($ZHi~GKgO}oo*3}GV4rHk-nv-*tnoOze7$VE62@ -(PAe{8F!FAACj8)>j#q75Gd=&(nUIyc34;S?0jSt~$I3oQXDvPkt4bRBtJ95o9aN>`K@-;rYZ|ywZ5+ -6|LhxuOk2Rl`uYxg4jZ{OLE`h}}Fgkk~-tMX`x~y0fu8sM5 -K^R^&0t}R#QT@HrXy3QllTWUPvK>)0;PFWA5v17;`dNkSCg_-YOCMeIY=u>0X9h@ws`xHekrL~L*jeDn4Vl%h=M!d8oR^PM)F25tESd_kE%&=6n4$gu0(Xu!1ye#jIzPuIO(g>SYVs4aljq1hNgTK+ -6w*oJbq{*2yU8jGDg2w(Sx;1s??w0Amh!36uN8^;>De=(%C*GxM}8~?y}t|;!4*i-)1my(=`ZbPqVZB%v&cZcfY5qzmi))w2>%trPy6`JY -9J71jOXLOCH(4QF?Nmr-cp-Q31z2|v#j7@iYVF74?N{RM2XTHHms!)}OMX-p_w-_GtCdAZX!$+&>dGJ -Ni90flW$^|0z(AuXyJ&q+x;AVVWd6JW5I#ohxtVIcQCV}tGV2b#>wer%29BTv`Y5zJYTfYoRtD5wrK< -QM253JJk5X8ISXoW<;Ur{e -Z8ws)~28)xPPf;zwQhitBi`;<`#+amCGUIw%0T?E|nG7&bXLOI7j!F7Y<(j#U*8a%anW=as6W1~)vWx -*&LlV;QA3z}<1xtg#Hs59GlxdAT;6g@~TL^E#=v6J2@k$!By>*mbyQtFt%Ot}tNkU~jBV#zyGUZno&^8t;8>81!`p`yCtk+HIpF?)c(!s^Up}@fPwQmn{`6G+aSlj?WJ2qC2U -I)fC@@Y7fnCg_QaSUorvdTTl1$J!ZNXy5Q5=AFel#6mo%asbvpM;l8{S#)%xWHL6q>oQ6yPy3)zPCgZ -FEYo(0McorX*K8mVvDY)8RkM -sJt_^aKHvLSBz($_vOF9zdGJ@XW^W6{1%nY_TTl|r^Cyc-NQSb4=BbY#4bKuKVn&^Pxx6b;H!Sa$Mbb%FDtR__8ox62``08?qK( -o{5)S&R>vTR5<}(_X(bXOT?JG57y0**ND&0pTJz}yuYh6ikog(x=WsoB1Kij1&}1r8N|ky->1R+a8C! -h#HGw*AG9%S(YV5uCnaArW&vtH#v9C>){kqfj+<|XVY&6O)T0K&3t};1osciV?Zz*`4Pa(Ym*6%zbY4 -ilpoHMrLh|{R3(20^qVtzxuB*mh$8%6-zK(pf?R8{LtLwyuZ4Oke)7CtNZdU9f^&VCe^lfek&oxOVd2_1WsqFQUe -DwdPG#*4M$XY+8ja0ClrN(EahW;K9uATKCqGe@1B9kF&PHado_DqoOSm47?L3KOsE76{P?g-y0A;&Ltap}?`P}6pGvRu8*w)}7L; -c=Ehv9+iAAHdc7^|LwgBd6Q#k9+nePm|hPLEK8}x8=QL5hlhZbKJjj!Mhk1;q!G(;drgJ|%}Z@f{(Yo -Zo~;G$waZXV{_sipG`AUL>Q!}`>)crzorZU6&C2XS&Iwyw8>P!@gDV)77i8!k91Ii8=4#9Hs=#A=#|- -O_+kGPakI$F*BDyz0e@g=J{E*+#SdQvFQ6PA(Rhp}R(B+(YdUoT$YxxV{cAaM@GTXs{*~HD*}jHf6Dv -%Nn?8I&0$QsY?7@cmw2J>w(w6Xoo48o#VFJ3qG|!F_F7UUHdI=clAD3ds-nHt#oZs^j)@20Pkl^OI<(k2ZN%2MTz`(6BrCAh)o{&31t(fnjI# -|ow%ZOfj;{uTs3~vwkb-|_nNWKKUrN&!UAxm3Dz3R@46~Yt^%r{)Jke>t4kmi`I2l_T-mpik4eM)hJF -{3D*4G|lq+)pe#eKI<;fZD9ciA*kmD{D$Y*iR{QaV>6cd?L -Skj2824lT;AhmheM(U!cFbOkpwY)K5r`}$U;w=t6zkZO<0EfG?jR`0QJ8w*oexB!$SQpJ}U1GKGt$Y7 -QxnIo0z`%HRjPLz+Lh_RJvo7n)2jt@Xm;Dmnkh=za6}23yCAZ8sUGFj9fehhuy{S_*V#vWImC>| -2WA|G{oupzgR94Y6G#eAzi3$PJ{B<0D5ga{D@w+qD&PBahR7r`ePk1fehTaixsnzs+?}{N~(ND3UP-cuU?I3A{E`YzCTN2U!l54AO3` -L@5i7Qr&iTCyU%yvHb_kacK4RwGk1Ck87C|Be0`qfBjdPBS!*0aFlx1)h7|WEH@pum?^TH9Rq4C<$RE -oe#Or}SDq%O)c-Z%{CJ{`^fU=563L~SBN}2c(Tts74<#rT@XI)CLcdN&V1oi?YTeM8DyVdL6i8oGMoK -_X#!@&)4N`TbU8Q7a&UZ^Y2P@n)ATIWjRL?T@;=DlMYx~BoJ^x{Fon -4jsN13iegz!5qcwHK`PWs$~m_bZPRGA;}V-S~=q;AZJ%`#}ArI-~s?$VN8<Y&-SNFUibx=giOWF5Eg%NFTjgGzQmL>XmF~ZW`J~ -c;i1|G_-sFu5^hFVy-)S+y#irEc6WBMNmo~yEKkS%l$qNd0QLxgI+OWgJdKW%F?7h?tk72S64h=h8tg -3W77!*nJ8IXn@ZdPAzbe@6&>akT)6wG}9yqbZfL5z0_zF2wH<0%>vO(gJz-l0xp`@pt*$RoG`i -{P3w*972LNCg%ZYKC~Rbpa26qv{AJut*BQAj_y<*CaNXZBgY@)(oJTB!^hLEUPMO3q2+Z6ZnIvaWQd0 -&M)haPfS;8rrGRT8x5OIp)HuFd4)rcI|L&%sx(0oo%qbVeuKaWNW0|!6bCoFsNXbXN>{?t=CkLM5MMS -*iR@%-80ls`rhAEs|3rf)r(NoakWjup0oBeKMTWY57Zcncd<#ikZ>%~L>M+K$bO@53z<1fx(D2QX)j8RuGeoH8|qg6KKUOknh@V*PW2kGt{ar -znAMoeRfkutqiX~EFuNowQnG3$ -;E+v18sRu?Q_4Y6HVW&|ZSdP<_G+<3PoZZ42R6Y$Fnf>wheKg=4o?(MfAZ-Ur%d;GhO>W=M3vw--a;1{QdCN8p#(-Sf4VJ-sqRO@Fb -lrELp!gE-ob*gRE+GAIs1%c|RI$9bk+0Gl_%nM;V93`*&nBdzET3B`6NOYmM7vT_Hwcbrx*WW(QA6hUCZ?f2>ZqS0M`E1DsU$#;P9 -<6Th|Her9eM4-8QN9an{5pQ)j@KrZ3`wAEa1VM2GRNxAkC<-`AS6~>u6L0-&Zd-Is*gbQ;Bh1*iq5%v71_iNuwb>ISwQplERV-;)BWU-{cnB*546=Nrbt=p<->KY$Lat(}*M;z -=Qk+G`2^?_{{)<4?WPhgGpko8f=w{dQw01vF(FL`!%s<$O0!}an-3fhfGU3^KSb*MejjH_WdAsO -?t>IpYCF&UqMwZq|Gi?;-^gWg*^;Vb9_<6JbGGp@hXz!;wn{oh?`L(KkpLu97C^p1pLyzjjxc*ouW2b -}nX@jh3X|M8B?v)jx3?^3gSu0Vdsai*rs>R4>QvlH$*#(S^q@n&49WS3eW!3Q*O@*0PsZ0Z`tygFa8M -4rLefuy7X9UuYi^)UFXFC2z(+T11AdvO2B_$Gs25}~))3ypzNA9+|Z8(DJ&{TmccI~2}xlUkl}dfV8>$*lIBqetvGliD|M@aD_JuL);jPPi_#TX;&DMX6a}2!*v4mdLqWTm`P*mzl(k#Cu7Y^ -9AUHz^4Jo3vGC~rY4fgxh276wh!G12eWgUyfIu_odj@>;~u4BY*%fOpjY7_Borz<55wsufm>}I!1511 -kOp@wul&PpIu6s#jpb-+4OBkBlVF1cv7Bl8y#y~-d;jO51}y8t8Ja}Bi;cWfS|0sT7Q<^9bH!j*B5Q~My_3 -Fdo#!k@|a6i2?tYVoDxMk#bYZYEmA@)PoLrO5YK%=d6!FpGmLcw1z|4CXyk{B@SY(?t#^IrT?owi3YP -e~cgN@p_}c)|MUEs%-*IHR^aV# -!q>~)UkPdMqTH47Gv$TmL`O=yM$Lj#ns~j&yd?m+`YiSwBFt9;lE~IF3h?rO6yGKwQspT(Oo$aJ&NXa -E`A;JecDdhzD}~O~idTz6No^aTE`v-}F3f5tlfQhXSRu9Ir;an&Ybx-_3DcuaP!#yaw@iIW8f-n&b6| -KgIDz#7jAj3&GL?jw=y2b6kyh8pi_>pU!c%0o%y&P{d<79*%ev$DV^@8@^{;#)ahiuf9iS0Mf>$5$f0lH;opU&iq_5ij8Q8pN#}$Ge)OjIn -Hx%kPJP5pH@@UH8<-_^wNaae})vn)w)~1e15TNjNuAgUL(W_EZl(~SJ-Y_P;cRw2tu}$`u_fdK&E)bh7e!s -UpOmCm04x6I}Db58VvBw1$Nf5TO76WK@E^lI|Ao|u2XC8qU+bKci`QTAQhDdB% -Apq*YfE;SD&4A9{O_MdE&IhJ0!vJs9@?ux9$N!yCzHv$5&B==T_)vv(UYe1^@JlrHbKhUbg;x1|Q69KZ&VjTMwKARdIVCF~k3}9)j*#i2+xDBN -yvX;71<=F7DUcwb1P7yp%R0plU?bwvm7aA{uBLh!g&}hHx0?jph04=Vbp~dsVqHR2rnlb@-?ZLAP7=av{hSyWx19_Gp=h@dSGU}L9M(ts=6-mmb44dnR`32b~vTH}3 -kKQTOZ$Ga}(Hoyv(DTVv_!mm}8wpUA&h}<6P2z6zSREgV;YKWdnY-maHUq4e-@x=uy^jz2aTo37>(+K -O0Cz5m3%J>n2$k##&~)7}1sf}~i@;)bT7LR3j};t!1o2*D&Tnme-q -HH1BYD%3>EM$0rdjT5HLf)M+7Vt@L2&r6mYYEdj&i#;7l_xRb -J+6Jea6i6Q?0WQFtb(XoZ4A|H|!ZA{8v0f+!AW!7mTs6f&8NAp=OX2t(EnCU+4diFbrO -h1^37B#w+HI%XP|Yl+X#v?f_{XPNRKplk?j7ReVGHR6z7vU-xoXYLMBpc!M8XeE27ntaP-O*|*~AP`11cB(+kg+GGm* -t;1Pr-KV`k|r%xva1131b9EEWFeu~23Lzw^N)4Z^U1Z4Q|Yx*TwU+|OkpG=+usnam~?{AH3ygu7fY_k -z@r!$)^%@P8JwH;Fhk5SxvKkM_|0T|w#@GcM}Fsk4=XDhIV`ni1Fss0!3=(9iTvfEX948g$~nbtvYk@Yx$HT|H~1Fi>&ARHIi-6 -Xgfm&>kHulbQ#zNW9@ee@MEPi$Zs86-WSZpkYmI}nHqs*k+7&q*8_VIDB2{me8kiPHr*oKcctiii{G7 -))y?MQBExx<{h4`@4ru~cY4ROS0W;ur8&IR`t@MU9Rv{sYky4e(>9A4DFKz}j|)kN|c<%2}Y;yTPZIT -vrFe=r$#)BMhb*i3?AUcYmNJ+_K-S*bMLP?ns-Z(g2|DhzQJqcF{>?6lQSvLc6+ -->pg`&R_JA)^*?aETIef;-cRVmg}#H(*VJ--N1-ne`aq#q3w@B#S4v#pMd*`+zN;MG_gvpij$i2S5PH -4P_mJcJj_WmYd_vz-=r!lLK3`4`p`S0ucaG~Hk?9lqt3tm~=&8`J6#7I~_bp5=L3so7Vk+z7<;Ie#Nc)n!6};`YCSn=j?xp)6eBLf3jcA|8(;IFZgc -E(~SS_5};`M-R{?{1Spz*F1Pt>Dgpo1->vw^pFjMZbm*R9{Mant54TR -+~my>iFSUAy<}-M9b1!9!Jtj~uN&cKpQ2Pd+_$`m@i^oc-dRdu9rSKa`lnmy%R%ofODu -1pf1-uHqJ@4%3;mNV^nYuiU+dCbzpjNImn~7It^|e4J!4JsT;sINoV46|ldP%M%-kF_pOR;?jGy_CX_ -nQHlb&nIW){;#IACu~&9iD}Su^LFl5)*9v%{R5@!&`zgq%9tG&(n*`Ar#QAGiQ -1DnmVF3tqu8$Qwwxqz{-5n&ll9Ccg`0((8q=HhA&O-Y}Crv8=c@2cRq$J!Wq6sZ1EdXBx#JY@(45iW| --)x>|Hs2u6>ESmVBukVn^Vm!+|I);-Kf$>-{?j;2=8GR;=7X5|AYlSy12Dr+D+IY%Z`#&gb -EzGKpI@<^0I>%k;{0%5=%}$aL`d<~!(+-UW^OF$FMO>3=hKs|MM{{3b#Fly914v!>3>l56Ia9MqtFIy97;)ErH!862j8AIvpqKnJ#PXECLwY0@pZ+2 -Df*(_5ZA!2Hae3G_IcY0bknlVyHp&TLIOBjg7AJd@SxWKHs{X~0gJ&774vi@T6*_y_j6Hf!4sz#bB%Z -TluZW&tBslg7+@9gQsy$>VmEgNHYe%_%mubMg6R=%~0)o}%C{J3AFH -NL7pi*y}qG@}Vzcc^RgesbH9unIjVVhHy-l?97~0mPo)l5I^8yTP_d|Y3P)lHw_HSy;CsU|GZ3V1}0^ -mHPzx|e4D{Gr4Qp$xM_udnSNeQox{2zz0yo9iJjeuY?(57%z)_D)3<#+EJvZ0N9Y&%Wb$al+ZTed03nx_dh&4rqmnXWNqo2~Qv-8#ORCXHN3Q-RP~SWM+Y#1bXjl$|S@&| -V}2dX-)v8-%dg;O2TlCQi~#I{Omsmz(4_c2lMOjeCqGeQ~ZcZukt8tMX<{h0K{h17t50u<~Z)WoDbRO -v!S4G9&-4s2LMYS*FxH(~LNyVG^@83fqC^w3+1gJ{-I$+PRQ+5iUu79hIbGnh)t122dwu|Nctip9X5O2hiUR={NiNx8r8u7xl8=VS59yYsBHbppC*7g|pBPx*sm8y$LnY9wYDZL%rYhiFHL;17522MfJ_zLc&p9nY10j9Vc0(HNBHb0+ -Nc&13vv(39#S4KicR4K}A0Zw6AuRuiYO}&kLEJEZJzR)~;yGS^WX1Sz$N2Z_=Edm)y1QALXp?Cfdl$6 -%Lka0C(=x!DbWNK;x=v0aU8Ba6u8IW0eQ35@Ik1og{KSgjBpQE1S%n3P>}wCU>6wSZ)Rx#;R5e9yhZU!k;Cl{o&Wf>rf~iI+7Um -X*Ds>EyMIDkd;-)z&xrV0o_a%`da*oJusYR_ctx}``#Irq-c1EjF$L6N5f6%vt3drI$J@~j+B^@^F+% -0=b2XQce&!}WF@RglO@4TsN*0eN)``(gZPGLlzL$bhpW#pu>_vj3f=KW$UFrjCI#v5ub};*;sFOIYUc -}4N)D}Gd2(*2mB4l1!x$uQ@0cCTtD4Ujm%9e3%f;#f?LXIQ!>+A*PrahDyU($JE$9g&SW%m;n6Y>?PN -6$Eyf2X!Mf|ml>{mueHN(%{jMy3nv>>W_2p7gHqtoEpMH@l^{Cb@)TUDc!AUEU-l>S5OqMY?PA9)Q

    MH^uI}e81)*o$*(nq=_m -SI+5W}12w4Z}oSY6lm88RD73l!&LI(?L*;6pCaJk*s4Q+-M+Ke<`sP{hDc8GQZ6tq8q5I@vk)^6}LeI -3$tRjX-Q?I!9Z)O$z!&-ej8p#2LbVNKBkS84@+gn=t3wQWafxq@ -d^&!yQ-i5S}@CsLWxnLO!S9HL9^#U3JM(})9wCLmH^k@kg4@=}Yn(`{BzYFQ2DC0ChUIv9gKVyvoIwM -JEML3aV0zAN|=;1?pSp7(kG&Ph773tB`w -^WUoHc~hw{J3g63p_yA+3LZ?K@`huYJcH{!i^Y?xAqrj$5u6YqG}iA)ygQgwx0(w@D`F`SFC@nS9Qiy -h-BNOou$Z4r$AL+bk9sp2{IA-KV8yTF2yCCc&^c%Ow0}gUcL~1$@Wl=H%sOnc}RLtaupCS|LS{l)-*k&2+m3=UBt>jJWi2;T!rc5!~}O|As2{FOcj}g*NG%9D>qNpjff%tu^26+xc8b`TL1FBe=BnvRLxD6+YT)`YbJIoRC5YM4hNE|bgF^wDtDzi-18#1 -%fZBmxWWG1^@Cz?{zSm4Bpp(84@M1pRt6HuCGE3hoZ=giHtIpowrOwPa=UjvkZ>FAypZ1NVbwMixm%Zi*?Jh_0SXOW}Hn -X~4I^xJo1YXgB5uymZCXEkL{&IF$GSPBD1o)EPg4W<#Tt1&kfO8!hss%1X#^Eu1ki6&0QYF;~dtp@4g -Lnl2XDx~-WK(j3`0|OgvOHYSl0x|$|m4w+xauC;|-@B2b@$rd>`#`OLG|Eh4?N{n-6L|_#$fTHj9+{yLLRY2Usf_tO!sWUt=jBlvmz7af(kNZZH0( -uJQCG5PN>^cd*2vs6nwJ^hd+8u;K^Pa+dh|p_=K2GS9grAARe3F3EMELiLxU+_U*G@D?^gR`U0Qsu;*4;@bLjGmIA1}V+D`oXAK>QyF8H7Q)NOv;Ueo6 -HA1Cd<^yTv}cOl=tEWLbx+u<*IiD45!;GFRJgP&_i^5+kI9& -w}l6Q{qdxlG95Mcn6gXpiHZ?DMbkcGN1MS->m-GXzW%Fh#)W0wxKl7cg4DPyxFMs20#iK&60g0ulkQ{ -=s3RfWHY?FW?0MB>~S1SR>$R0rv~IRlv1>G!Oq>0bdnxm4FojmI|0JV1|Gx0?rUHNx*0ULk0A7Yc4Zs -gn6KVY5|o35}EHSf?ok^1gsXYQouI_tPrq3zzhLX1e`8ll7M;vV+D*BFkHY;0W|^!3aA!PDPZGeL7#x -t0#;sb9{w7kUoBvTfcXMu2$&>bw1E8u3>Hu=pwuYh6Y#%FE`4=i^Ld^BZT7cJi^w+o3wf>LH<8W_BHu -)O=E2SVxe2*Wo@*K?lpKD21c&G2{ylK9_dNec25z+<2?Ae@r`1G;kaK!8WOa(Yg8Wq0~8bA(@Y) -+6KU90Ca`UGa7I=fIUIgyMh~l6MF#-U_Ke(=3bnhtpF{-P~O1Y3a|>)yMQBH6aw`c@B)BWK%EEtD!{p -Y2q|u-Ao&2l?1Qu3KqtUyq0nvtJ{{mgp`89Kfb07aG8xQquf)1AXsZzi_;)A(`GBtkcySORPXXQtFm5 -pP6M*Xg+6MD*@&SGi>MWQ`0NX`yzLEf%Be)xc6#_^2X#`KJdVv0s&_;rL4Z!F~tT>Qg0JWgp08auq0h -AK($pGJuhTv -EM8{J!$f#e3w<=w12Angn7b<&tQ*bCfEx%YKY;26<`)2l>o_k6JH->S0L%jcri=ml0Y|uK49}wifWCU -L19LUN5A;0VYJi&!PzJz!E5J_^csQp4h8Uqd03HhPkwi}O0)Q`0hVlvKs{wAA0`(gG0{mtQPpk6)=S= -19vjC2q1~Ln{i3KLV!21?(I%fdNeuL9f$Dj(eM2l%Zp4_rh@LIEKwF&uy|7I43-0lr- -bJcIeW0CyE~_qzdJDdaq=mq7aosuA2EtSkoE5^#j^B|IEGz|N0}x(4t?fv*Nw^O&e>06%)1=lfcK{!c -(Xh4O&#S5PAW{|(@w3TQt8uL9U%g-Any=bq!`=RCmqt2k{704C#}JupuJ_|fyC-2m9}1s<a1adkEj#&FNVKFl-O+$72DW-V1F -7;0Rye2jvIwHvyj7$KyQ>@V)&&84EP#=DW@Qh05=01dK -&x#o&?bSGZ=ed{sN2x^)AK*F#R)5Kb||lGYY#wCP9e%{KFxw5dI|05#l~`#1ZxtIKpuPN0=vY+*AIvz -!9z$IKsmMM<@v#_arONa2)p>M+h7t?(ao<5I!T!5&l!)2=@yd;W>dLye@ErxbG5igi!*=y_b^({@3^a -_s7q)yR!8`5J?N#6SlTifNO-Md^yd@od+A2tOj!&S8W7{{;C0@y#x@~6~R#f4pIUE;@V-jqGdP=cEV5 -JiaDDTXvG}Ae5c~-Y+l-i`N}rT-)zHtV;koC+b}=fhPl*=InJ}-gBwZ<$d!d7`Sohp6_=n -|G9HkJ5`*3w*B5o3~&AI=D!yt{$aTm%w^oJNw=suyp%I&kj{3_}p{HsU#o2}qxF%O^Xvo10BRaHm#a)V1O{I4*6u7~( -3wI8iQ(!dPqyL$BUSDL|EahRLP8`qs09qqzg7Z#;bT)n4ybF}J9|{DO7_Z3ORZ%^K)G^2+3@#;ZpQDw+GElw -O;%z40ouM)#1uwW}H_vu5d!K}g&h-Lvb!PjhP)KBJ9W6J@FRxHY-jqjGVLYr!Ze0zI6RD=0+);SEh2N -+H}i`y17US`3O75Cte#P#&OEpn6a&=W0>bZ&l8tqM}G*Vj`I|X%czlkw==9@rsHH^7h+rleKHtvNHbZ -r=OBj7E(!ngN -R`bYZ01K&=WGG%+ZG3p(F$#W7O9#84IWV)!V@AmtrB$LJTmF?@MV0fFBWB7|H{qfSJHH4L77;h}<(Sx -czdQjpj6uF6#-FOJ4;^~BG6wA`-=C`GVgoKc=urLxH9!^G%97(iVEio7jEbS&tm_VjZol5S#_g?bA0} -qfzX=BLj*|W(*4?RS(v$KiCVqxva!i5XTOSVzuokvEJj~2y{qS^6e*_=4?RIZjhXNe_S3Wt;T3Jm1g! -kJ{lN;BE}#5hv@${Z59jgtO5DH*(%l2N-U8FG-45l1MQahQ_0lN97KN+x_oNgk*v-%xV@_ms@4p=9O- -N(u`LNm*GLS+QaT`Nu#0L7sW$8M12CDzbX@YVz7^ud)1Hvt|uhw{9J4%eHRaN>1!rLzetZ$+~(<_Uze -14jec@4j(>DjvYHjPM$nTKL7l4^2HZlkc;1*CMSNOhCFZ -iDg{`09HSqlDFf&cfxeMe2LE4z|8wBK7W`iX{|!$52 -lW9NUJGUYei)Zo30bq8kfShes^0~b@VFnLrw0@I#RNjnKTPNkD+sOsfY1gf|Nh{=7x)hc|96A`MDPz` -)x)@-&IkWX!TxqF-zxxOsY$kNVV}w4uiqI7w68gb@LXUmr^0|JT9)TJXOU{2vAXXTg7+lYga1fdKH|75w)E|9!xJf -AD`d_@55`9|r%+!2j#ue>?d9+{u6QFPVSNMsabYwWGBZ$2H7X3r`2?>T#3CZ0%1_gpWI0zLE`Xqz@QCjpImyq1GW5* -zF&u9$pr2!%hj_{KM{5y6WH%^ZMz-#c0OqZ4%r@rk;`F?Vzc5Dw73U+4fM;o--zD -CAU29)S)-`myi_$0R}$C&nivGA39Ia{2@P$-2R@NJ0XH(Phk-jvYIK1BZR`UG0)%2gO4K_kjH!W5zHN -x;gCQRPL(i_Tljfx1J08{w*Q4|g-PxY1CSqoU&Cn(UL4M{CE&wD)ko3(&Y^4Ben>ia$ -9Hos5t1b@7Nr`?yh1ToXu>J*E#7uvl-u_T!V|5=X^AStG?h-qFR;Z3dpDy^4!hk6j`2|I -B_DAM;4jJI%JPG^6w_4nek+$C626J5>M7VyNC?k4>AR`bNW-1O#Oz-4EX?`e)?(h+;h*7=bwL`y!6sb -uTBTP0p|IJtAn{U1$=g*%fb#-;*!i5Xum!B^ -(+2Hrze<#e)8ihR#^mo -8m;A`D^|fp-ZE88m3norr0=bP4Rz^UfaX_U-S0au(DpG$f>N$RIzps-G|DyY%ecw=cN$Q}-AYHYkjA> -vd=7pzdCS{J=qbU*9{r^$ZCe)rV34{;fQQ1P*C715d%9!zodR7tg3i0Qm#1eK)UTc)U0u6@EqLh;dWb))n}6TlVS@&R;mukQ|D9o -B!C_%N!x(?v;KMzAJ$o`OS**f!DZ&o`xRSOvUnrd1pnKfd--FK&;raFy=0tPTcJ?+t`3OQgC*>|;{qO -kxl?iX87q|Qe^ytw86BQL+8P~_t(-T@2_M9kQ2ai|CLrcMq4ah0}@A_OtxIVY(9@7=OM=buD7himF8u -U9`E?&I&-A_OL^gZ-F-~9OFkKY3P>bKv1y9ndb^JmYV{b1j|eRhRH5jJAPh(Us0%qKiD(wXoh9Y_o2tCvuS%STx7s7QBoo1L8<+1S{4gXB9XhlR(g$=g`VSsFNH=ZTM1elKapOit_qX4EOTYW>J6c^`O^+Qr#@wMFkP*=QJotI>gAY -DfxM9PFg&?xU7ZnwaH=E76<;#~Rfn1HW;c=F*&Ye3)mX(#=7ZDMGX&0!|=|VBwKTH3wzy3-=mRZreck -dyPhRc8W;Rjk-S?NgUqeqX@&p!K%(Fys(Xh*!JriPw5bB5`WCs~2a!yuHo-+uo2=SC=Ze?XYhl`B{1F -Tecq>nERlvgzEpb6-JRm(d;Y`8TL6iJCaapzGMOHwwLqVQ9 -l(RXie}(>CyLK&ueC!2fq8NGDxpOC@AIrz*pMTEs;OnoyW_qL%>jczc22Y$g!4%dF%nyW^Z&-KG&!Iz -y82y-sQ2#0B!}aU(E;Igt{tqy}iuO>4BmNBi4Gj$xx*=BnqobqyU^zyG^dCNam~Pp!g+iWFkeOK>#`M -ShIB?(qQ%L*%{rg#6Ieq#x!;uGU1F*bdJ;6Le+SdO>sp)e{M;)Ow>HwvK_D~wOgVKSOlukTI>D%8@`g -=pe38*XGZb`Wl{oA%}TM--_JOpI;<GHFbUcGvC)t{js+IojV`eh~Og{=OSdV=); -%geMcDII&7(zs6`{gDUAgS!q<8UZ}yoT9YhDy8YiC=K7{Sem#k`Y&C&gk^bIWMpJ7C=12N1LVUEydW) -Dj*!OI_`r0>G{-!`a)ad`+g%v5F@nYe{o~GX9!8&}^lqSaIPfq8%8mInr$6%qrNLXp{>$5>ANmX`xBp -oFp+BOiTIPvd&M{pvFR)x;*^v3bI*#QG%RSZ$8DhP_Z_?M4-UB=&K=~hYit{kCn$ls1IS=7b7y9p{G< -5r)ryttl6(J!ZLm(fPV?D#Rr8PdVthZIZF#XXU`vvre?Tqs`^*q#3zsyQ|M=!Dda&`G9RtwNo#!k@y8$Z*rw;{YYP(Svk#A=Pv^!mBJTT+(kbUColrw*BJ%K=sLxPlM#%N~ -0I$#3CiU6I>+^uEyiP-l+uFOijDE=HWl>R4y`ViWM*6XwwB!TJJ(la1e8{|DS;unpRn=Ph#=H^q728P -qLf+ks2mBtWrSx9GL(*528i6MLXHNAQc@Xt^Aht=+riMV9)Dw89OWPScof8o*Jo^#2JeG(-$ -iLZfCO2RulWPWzVEXKa%a&NS6$F@PF&{J1956Qs8{oex*PW3rK)MxDp-X=vK=KZtWCiMj#dT(j9U1Icue7|hSkRiQ -5Mkz-6v0X%nWd?abnMdXY&9JYP$8wC`fh8Terzw~@;`j|@IIIqvcmd`bR%s@yUYu={n*~)xCYxz?5nVE!*AP?Xu1h_Shr|6U9%vDz6 -(6Gtj~XkHfcq+mM)t!nwDn9(W2RLba9%F+HLpIFHco79-L*2fkMV;i+*VDS@{QgmLuIr7eb^7>Bjnib -jx{yZM?I5jQub2h~D}~W;9(>1uy!Hv0CfTDGxG4V;Ncm;Lk05i&}jO22Jn!sql?nw84vhjn>5@> -#u&1v_41Ex2KHenN1%H1%{S>g@4Q1%E|QgVp4_aST)TFSR=@oW8yYDi7Zif$<7xX -I~`-tu=9!e{Lhb_PZtIxm#wn=h*UJY&1D$6MP4|6R28}P6kcvuQNlt4KuH0cpMT5tc6e! -2X|#KiPL8j!xy(ozQxE%~?^FW0VJrC;xOmhM>=N4FP6(=DPtf9O=7Ullw&CwO=g%F*&UT3VLLdBBg!7 -|=f(K|TLH>wgcC8^0jTbLf9pOqehsAtxv2AkvEU5$nLBMT-~@&gJV?d4dq@RLe0|Yu~Be?>l4+tk315 -KDYR8D*qt;nD=_U9`k{|_10TV_Ci^vtgMXAojcdT!_D}(S)SmBZPMCBw^5&ghgRB|w!R=tr;3V-9nf~ -tzJ2@B-?3eTM@~G8nrOTErWA*xrFTP;y4$4BU@q%M2GKj_;9P?dSCa14VyV0((?5c@)mW4B#LkH3NRH_(Hx&TW!0=5iSW>oc}V&*mp0`L`T@j -TkYaEHN>$5X%pg^%XGgAF^)Ux_6-tQ?R4?`S}#;1V#P0apRbbuxHO6RvwUcq!sf>E?07XAb(i)aGZm3 -9p(kr2dvYqK0`h@*XOrd)aR-VFQOdy-0c*eVZ(+!^~^KRuz3)SThwJ*P*6apPMu17_wG&m_3KALwq~> --9auM<74jgfmb{?CG9`zNZR_FZXVR@DvCuzr83X(0cNWIc4S$!DI_`H~wjq7Uc@`jDva_-uFXLH_K -y#rhk%h5U;w1=g)wcMd(BIdi6}sj1QXz#5?c{`>D&d3kyIefEGgn9dqZ-b*ewVyJu1Q=+PqJln!;Hug_5 -iuU9$&!5!wzai&dSaMQ@<8clUU&Rs_(~^VgdW*jC9wskinTt- -ek+kTEd%%{cxtwFwx9HIy4C3}LLum{*=cRa-Zxi5Rfe&Ek#y*%LpyE$Tjw8! -ap%c@(3-LegBtP2@J7aAHGl-Mlor`-+OOy?Tx47P}d{E!pTp`CR#Ha1dYw1y8Keyg;{={NDe@OvX`Fm -|!0b--_Ikoaz$Cm>V!+8%29ntt}i#1P;*$UU+I&(Wb>9Ol1uzIUVFE&tevL?4^D7Dj693l=O;J9qBX^ -6lok>3k1aLe_bW4}f-qk4Rj*?@Q3G>38S9;(N9sm-ryoVe0+Z4{RO&$h{7Dd-(8S^~^KRXn8g|OlRGu -Hc#BdYh=dY2VygyPfAMqGPK9(cl$+m4C9V%`q~q^Wm)=q*2VW(8}uSu*iP1BXqWi(z#!%Q3-m*a=$Dp -%@!uOw{?*r7OwH5ackB)Cu@455W6!WfURYp;5&(5uz$z_{s`X*kI6aVJ^NtlyX*t6vH93q?3m;f8rP6D4> -j$Yeq^^VIqyy0-L{C&P2B=MvqofHG3_)SpZU%Iac2(BJq*d?cvZbPmlLa2eeOwCH -!@eP>hO3nuf|6C-w=;U&N$2&Jwr4OOSr#l-g;*|ITZBQIire=!<;e68Izsyc4r*zjG4|@=Zq>g4rA>8 -yB)7*5RLOa$xa{P=_6L8-Y5Nc(mzSa7kLe$l2{Mytua@M#J*|b^Jn_~{vS%bKTT{t$FMneM$CPO$0eu -S`>$O}t-PpI_W2mbMlBdj;hx_pwcvH7&VQt7>5B%|`t1=TMto0lyXB(OUDz{;IX@PA`<~dPe~VpuOXB -mO#I{Q%2Ie;mcS}!|c;m{FcVjpiwNP-r?>n{JbJvvmZiYza -jma*xa*XH(%n$V5Z=W;9TG<)WoU9zIk1#_KyaH^^V3)uV263_taBQ*Lmfiq^v_-1#R9F0$r{M9F -MZm#c+cGJj+OI9j%?JYl+lzsUy1Q2YUrSo|_<#Sq?@6)(6IfJdQmUvkzu|;vY9wnuxzc3$4Y$CqgS@_1 -NoIE~#}NcBcV(%uSHTOb0W)*OA8roKDX#d6IAU?%la=I+zNN{ -V9gEp%dVp?)ix;s;jF@>gwv2y0KSipl$*$Xw=uinj#L~2hK!26nO+=0^8W&;7c|6vHtV*g_;H}kL@?H -@5K2NvEW1unEgN1kg4%5up(-j)W(fGQWpnb0h`bmmoxv`M<3L4O?S!Tq(5}ue`2!Dn>YLR?%kVz03XYxU~YC1U@V8hn7ffZPtAV0+ --j_0B)5*M63xaRTboElz!Xze^{;nYK7MS)D@zP0ORqyFmKe|LLy(CO<I?am7t}+*3P`>3j75a{u9G{@mQ$zh`D<>bfh~bYWqkdi?Rnb$$m;#ugY`$^O8 -bYn^YY*8|JcuQp84X90{n2CE-btxuP!_uqY6sb1or$&)AFo0XMS%{pppYfBd|Ui@lWTAEt6Y?-bDZ`- -y_*Dg%%1J4=Zzd73oKN5X1jeE-X-VoX9GyWSlZrmKvdz}a3>%nEfmL$$n^XJdk>+Y@AoVYi}kDIfVa( -m7__x%HZ;XiXr{Ba2^k-G7W88dYK6&jW=U#|I%Jun!FkpWXfHu4~T;`5@Sq9r2B6%r%y!)|i^{&K3(@ -Je{UqoAPRG#C#x`Z;svDE0z;P5g2kFM#hx4$T=6Gm5YJRQS0@?$s#y`DIV+Cma2>(Wb`FJ&5h#H?>@3 -kXkyp2KwQ~5!eIl0@pzcyufZ_Gokaui4)sB1H&k^{%_s7)fWf^a!t<7K10KjB}>%Uv18Tj*|RlmrtW9 -(A-;#)7<<9F#MahUo(6Wu0Or>7Gbi?E!-fq@xgRxod_Fb@Y+>QTg&Jc$QD8x<0dGShwHa();_(uKTyU@#{RXp7{KgTln3sw^usFACB(rGm*!sjg5_mg2CV?VxvBI -?z!ilJaFJZH@g!Khp)&zN?&;41^vE!=eAHNG{ds2>2@2Bx$?6c_fdH40;X{Sz|qR#r6+@nL}?>c% -&Zj8J}B?o!`xu|b-#9YsJk!fN$6VDi(!j?VqUMy!KK7B*Nn(w~aEk5?(gP99mGdYnv7W7 -??<-kWi`hpw&So&dX6M6)#=Jy@#zuvE&D2>I2`A+Uc{z(2xu0XDmCb`mXp$n{)oE+Mbf97qjouOl#Ym -$TQxgNU*xj4BOxgU8Sd1t!hoy0K2RM3sxqLIT8FaGlB<@z4(_WozFnhtdYayIzKKH-DR8Pu15`hbqNi -1&!~h&73I$v47#H|u+lb7Tu0?BBe3^EA$l4TBFIZ2@(-ev-~7iQS3Y$s5R>*7~RGIUasTsV3peo;`bX -JjfXla5eT4c}a}L+fTLVxPNVaJmzMxFTy|Z3)*gh$#H(b_)^-z+R5rt$63u!a;Tpa>iMsm{YUn&^~fo -E`OrfTX`W)I*~`DZ5>&10r|a{d`>KARUVG_zu7A;7fBg7y9jlr1IM{7sF7^X^>ZXPN^6*zOc*V2UZu6w2#jF!gS{vbf)2pAe_BH35Uj3A{W -KV{#qQYOixTxA&Ra{h3F)6jOyyU)$`~rWmuVP$rVSagOMd@QJ#^sk5-{-3+9>4PLRBy4br0_9+h4~ws -lTz;Gwobtkzg+=}){)+gA?vEd -+l1upuo3s2Y{YBm){+*QStC&%;vUHihJk?uSI3=Gy!#FARF<()IKXr1(gkCPbIr)U%XU~|>jn<3_@kP -t)j0uLHlXIrzOqn)q#_Z{NdCq^ndOdWn`J3GowXI-NuqAjj*dDwP^oGWS3c^+4P2pYPrts15nec^hXS -gdoGLjM*6G@L`NAe;Ck*dh1$gW6Jg>bsp4cjk2ybbv1dKM>cz#$26xkr#ELcXE*0G=QS^CE@-wQE -zzUVlhHHL_UMIZN3=6~HQE*R*dy&xc8cw_$Jl9hx}9Zb+c|cgy~r-G%j_z<-ri*Iuy@&(-DJ1eN9~jL -8N1!SV0YM^_Eo#f_UH#oT6+Ez%WI9X(yVkV%gVNLtUPOxRbZ7_RaU*V$=YG<61U%EwOB{3lhzrl-MV0 -PSe@2YtIP5PMg~R&QUcz1lod2}*(M)=eb>c!Jc4cm4Z*nhWX>)XJX<{#RbZKlZaCyyreSh0FlJNij6l{DsQjSbJ>2~iqUzKd?x*J~;+h;r7y -_3BvEm9V1id0F;FMZk1e)9r=c%$sJyT5z%YaNLM27|$1Fc{1XHa9kd{c8D8=hyQ#i1r7;tKFBcw&Bko -gY$V7oIJGistnGmS$mh%S@2D@D$}IRt1^a~XIU1!Kioe!Iy;El`!-0*G}u2rIzK)9>f`zG=@~wRavS+ -#S=DVYt*Vw!X@aIrC-g%W&nx-uho& -m!_oBMK&dY20vufnG<-=rJEtYwqFsRkHX1;3kLj9_yH(9&U)(?9d0es}+rr5~tr`fU%4(Z83U03y95N -rm^I=NmXdqG)2w{EkVszLc+6dYA$*Uai_Sy$7nQAjlp>T8?SGYz>mTP)4r_w8MsEH@my+IgKNDfanOu -#-cwS}YzW3Ir0mgC`rv7ug1nOf2htoi{ji2h<{rv3QLXPFh(gYT6p$OxlNK)(oi&^nEA@J`C#QZjzVF -RXYsMsO;hKkkE5J(_}i&CfLfPsaEwAfUN4GT4p7E`Ju|;%e-xufYmU#UljFnieLTE8YtE>D(}wEPu>A -v7g;?FzDk;Gzba?>^{P(V3ZC31MGlOYW@Xz03(B`iUSjnrRb3`^gTFqWzNbML2LF>Uzk#tF2D1hx2uz -Y5P^OoocZ2a&0v)+Ze!2{)9LkP5yUy-mWXhn)vPF;tS -9u%Ele~U_5w3!}EVx?bMH;}<)iS77Fdb(>vq(TN)WI^(U?u>hyLmpH2Pk_MNts+{$yJe|Oxf5!{_x=~ -kf07Pnyn%HAM&Hg=>a~?Vlkg1dJ5z?{QBVNoGL^YcfX9|?aRT7aO2>w=cjKc`n#=P_VCZe-HXBAFCh? -uU%m*!LAdeV!Rgt_!9Em~Poe#-H{r&{>B0Ns6L=m*<1b&2KKuLSXz&Ys1A-`u%<{TH!2*mK>~*{~7>FJpV!1(FhRrNg2OXO0Z! -#rzn_&2N$1F<7S4%(Z(e2pkNWC2`h)HF<;{&E*D2EP!XSG|Cs&;WbQO!EHl40%N77(q`(UF#jtjO(5; -qmBjc**!I>fmNe-0St3`@LwMR`wz^MFv>{Y5~^s-sEv|n;RR2H!5gHIXOOMF4c`0^-0T -W(NXTjnr&5}eOVQVS2CLbgE{CN5@yu{gqUteh@pnepmx_bn%lFO=cF_46yDLCDbf~N -jS}n*8&LxNC&%XImUH;sdY-=hV}QJ1d&GzStm4}*NulZZa<(sgfQN{q;^53gkQe?Y!vP}VoxA|5*9cQ -f0=;VjMjM?T%9B1Szf02Ho`gsU#G?n+tHG{Ky)PBOSgC7^CpO%H-j*E9z@6u!yttJV$@)wF}{7?;5&d -!@bF)Nhz(s19FhaAvU{5scsYNZ9aK5Ftj44lMFOZkSHyHsx(;DJV)Ql9Fv9Oc0eHm!^yoSgvYOMQxs668LL5AM_Us!N)BsE7Wx_iXq_C -ugyh(=4i*7|X;^1sG!|?8X5fLuq>ZcX8HY)$|1)R?K?UxL3HHzP*EaG3~vvZi5vn4t!YA3!fA#3zlb3XOkT!fv{kekkkuuA=2Vs -q5MfmV64t<#ClcQYvI?|i@Q2+Q33=dvf!WJsYG{tqHVtM0?9OPhh1pO)U?I?)?_h?4mX&#Dkv=BzvRX -z4enSH8w(d%Tu6x`SyJsC0`}-v8Q^!VxT0fJnM^=xeP>XNht(asBmhU$jERF@Vy2q81_a&nuMw~ --iGpzu6tlD4fQ*ogyXu=52t#9yH?$8YnxT_rF2)ZGkD4o-qU?0(T#4;qfe-1v;Bu -Aze@m*@<&lIw+HcArgGEpDT*pS&@dOiup$yMv?eChy;VJlcPEa5|X;X#xrWvS6}gmArUB8$nT(xb-Zv -EX`6FaNMI!=LtM)P%}jSY}(aTP$a8ziu(T|BV92eb4J{?9wfzG^3Vj!inT_gHmqukWO{rCk9i=dcSX{ -|lmuN1*2y%f@A5JvnHq(j4uo9y(GSdP;6I>#URNbi#2y3f{n*>mC0F91b?ck`<^tyE7{%pmz=dN9LmZ -j)l|o6n4Q%K^c*96VTwk-JXdJ3wOACW2-OnjxiON!trl5ACuquk`E=wmc)VL65Xt|j1%MpqZ6zsDE>e -DaL^b%JiDnmbED-XktGoix_*9v9kiI&=8X4cjE%>HoUy5~KP?M28W8G2H -s{R(x(Y8$KjI9^syi(K2cu{ZHcy=LsB5}jI1pTH-;U_qKPbxh8SQN++sOV#b1#6DmE`8u|bKjrboFT- -DMPK0?ld&{oxP$23doIbknO0rt-xw>R+zz+ZF<&6bpSYFZN7Lmn -Fc?-at<)9peMl8h9| -74IdiV^qLkGEK*%31TV`Yi5T<*w8?a=-_`X`jdkcgCaHj4jW}0|oCQ!yCdvN_fCR!j*4w05Wda!UJJc -JvD~-YKT2+&ipUUx1;`U8?9j7@d(M?v&oVIWxATs7$@U8K8HS|u&VDYcT+y6{qOzrmw<0mn=A)pw2S3 -rSSKmPg{bkq#wH3<<_6f!6@~Vej}hZ~0cd1Vn+_huV>(AHQh~?if27YN{ky_y -yv%lJq(Ma6ara_u3iu6}@dB{0X{O+cFD?PL<67y~^E_KVtt!iOjGga#cKv|39JPz+kNm4!s<+@r(7YFZ$i|g -6F$};8vvbd-c>cJ(zxA63(RfCw$+CQCVI>iN3#Um-5NfR#|mi8!Ob7+6umO(SEb5M;EVPO+Ci)OV1l%O`0{aAviZ`u -bms3C6i1kH4`5(;ARnHjCV3+kuJ3SgNH -aqUl~uWo7;WdxJmBID@}xE*8K29t%{Qpn;Uj~|AD{skMyrAOJCEg%N;Lpv7m$x^FFWC$%_0)KZk&+M9 -xW_?qh{g2$DV4PjGhr!gt!m_y|5kBIj)+jShd+RSi=-oI^efo{wDZ98zv1@G#g+m2mEY=-5no`O|`OJ -ve^Sg|EdO7R;$!ys -yUgAcdznQOLce<*xlw^y3!bvZ=8JLw-jmv7+zWy9b+Kuun07@uXiv>=Q32Gj&HTJg)lxikZP)b@2Gi{ -THz$b|UBFJDJ>7Yv7;bLtI97Ir~jMyZu8Y(liJ27zSt^~6YM48qMOuh3Age5eG)1Tkd#sF)hG!=4+00 -^mxpHp$d)YiC*fvL|WVB+7ywt=l@0A_(g2l~L4U068t8A>#St3ZX^Dy*g;U3x&tGC^8pO&PWxOlCM(S -pTYmSwwZss(Hg*!DRxpA8hTRTA`s?!J_`k_G1=+!3nGZ2|7R_TeA$*u|$k$_E3`=VnHzHadYDzBTbA=P-mBvfbS^gBY}iQGI -q<0ag!W{hyu3xoc6`AoH*z^wASUSN#R9v?Q@hsV>MF1-PrMhP595V=91Rcd_GWHhXTlz2nSlNMv~(rzBpqscyVwoo|A(E83AZd#D1K`1K| -@8GhV1rLjt1IA(%cMLGqDU0P0S92AVrr|li!D^Nk#hOAClXI_t5ZkVwJY!G6s0G>~Yea+9L`8@{T;(# -d#8fgFStb4qyvvfdKCBwyy!M#H44&<}3j;(fxu;c~`Uk!I2+bggqmd1e7#LJ#;L8-aG@EeTugW)f^G| -NKKishJsIjAupwo!#n-FTNN -CyVbwJhlrr~OPeF!y^iqqv6V&B#Yb$hrL(26{j(JTp(t3*aG$zr+NMb_k^=G?HoVVvi`lb3HD8-c9cu -QhriEf_?^w{+%LXw_iyLgpTfcX=sF2n7rM?a0)dR>t=>V-uKnx*RuUgZXa -+j38h^C`&7i*@R+O@6@6{I2Us0LJLQ>e4#E%MkRV$okZypac-J@wh@l=hlB9-p|zRzpn!&lZ5TAw8oh -_$$q>R@YH@ib&ChO_^@-3@3mMyJ9o1+vMX@mB?ls4D|wAJ8wl9=^E-xV}A`bXvT~emOmE}KZCk^yB?x -aYq+JNz1%0TKUJbV*HZ_I9n*)n`25bnshWOz2tc1aFos3$k&!X2cLpkI?&Ms6oG85$M|miic1kt{BOS -Ov??mdKZ3c8Go=h=fR0t6G{g=t{w=U6KcQy9u)K*hNH@5MTrCGK;#T`zGLUjzom>X^glAa(27kO)2e0 -5{YK9vwATF@(m%jqy+BhyS;Z-Ezwq7_wFl=WMAgrwJ4${j?PFNW?cHifcf;eMq5byXC2o@GSZc53^AUwBrllr%556dwVW-SIh(2gJ`u<7>q?MxegMsx9%+<@2f}Mwyc%n@m^kLr#T6zK4lJKcbUf54nud;Reo|8%YI+)2(oyM1ha -pe-;&9+Xk?sTlZOD}={9;$z|09XRcR(Ic2jpA!*vTG`kn|stu{rGNW0_GzN -MdQ8dB6)5vST?h(M#b1H4?DRm(&jWb>P1&1^;BZizvj&_rR8m9RY~#G<1e~^M>i~VIxLi^F=DkBgJ`^W5ntlBtu=Kc6*5k}c@5>G5mtH=VakKivu$;&;qhQyh -geklQkl|xNy&c5QM#0OzL$iMxy3QT(Ls)AM@l%;nJtw~92~MIOlK -29#|HwZoF9f56PqiCjTkdlutNqj6{r`tIqn~4Dg#NQLFBbt3ok>%W*7@ZLJ@PDqnnEYCEQAm%iu40nO -1kOCxv3~kN4lce|vKB_1p8eh!DWb%g%#DgcB|(;|n|}87?>9G^+*s43al}haDfZ$ow$mAW7Tujv^J4N -JRBW#7>US4*$xvD}|;HOUfC*m=ap(gjk17Ft3F2*R`}QoGmdn@_q@Rv1kA(?eUI{e -R)6qzh!(*dXy(tj4p-X$eY7*SbnBT*O2!bW=Ih*5woz3u^O8jwYBYS*n*LuZe=!Aow59Fvz&L%5159) -<1lE~!gk-6;H8@s03jyILtfFlpc0W4opvFKd$AaTUPBpyVb)>wytw>vTrNgcDGTfGfa?6GtSx#AtFTDXuGEfcXN8pu42VC$t$Gf<7r9KyrX`^8NQxB&9XFvjY= -}-;SBD2NVI6XDG>nz0JXn_$LGy!sVoDfB-Xq=4SYj -%@sY=xVeH{kW6)sC*+Ik`f&f%cm%d@B81r&V|NStLzGoL|`!+E5IGLz9WMxS&#c0*TcXY=21b_czcW< -yU11#<%f4|8DksLWd6N&y7LoKV=8u-M+QN3l|zQA_+2323gRnSq2h#Lv+vLSLj(^(P?BOG6L=wdAR|@ -0?Oc#I&Kfs+}x5)N$roosO*97X)!DN#r$!O(dP1tk9Q1hG6L{1i#p=UcF-A>d~4c0^*cJgrGN=+7m4k -@WpOGNx@tn1e~2Y2my{gh7}Fnakc6iqiBYY{-AfGNz>|qpYHrZm}YY)1k>u^$?5TbVt%=!w;v8BZ;!s -7UR+a-pp&Dn36~6;Y_?f7vnjk8xK*g*J%a -+&#IDp_QDlUrhvSrttz-4u|k?oCSwoKKtK_|l~#8FBpv~HLLlr|1kQ;G%6v>{s-=730>_ZklJ>v(U$Z -Um9ocI7jJ&ZHos!+>fscpb0uv^MQ_xHFH5n#@rq-1Cl9+d(jX`MHUufk>Vj -Gov_Gnvf$bI9sGdBc)zN%XB&jKz}aIWR0hKF#a$5NRHgK!YbUps*%I)6J^tH8Gr)=i%+jc>Y81N%>@p -D(CR$qa^KF364SD+zEHapqDQhsG0b%%vVm51@veXm`&^P#uGQB3j3GZ^7Z8UnYFK=4Cjf2E%T*rUd1$b5vH=s8ajGRZeeRgseG)Zu!~qyrNz_kHwebDIPOz$ -M4|4tRtU9_t&mfF2pGC5B_7Fx-f+u3&iZB4wWQYOx(6aoeA@PIhranr&TX}x@B%C7E>27Fsi3-gFElNMY_FG*+Oe!aW1bcXIx5mI?FlzP3DWU{P;IHyDSh4XCSkPLSR~ajHpZJWA9t{zRP)k0fIb~7%mH75gs -L;%qha=rp;lEB%_1Rv-ZUU^HeY`J$AAB09DI}C^N`Tav(lUiHpR{pc6h6%1+gZZ2N&_YdfJ|#kJ4TeuJmY8?UJD32-=6}=U>SLMRV>nZm -5-va~-pXRKA~_+2&0^ZFV>(WS6s*gRgbi4pA0oL6mlU~zq(~lvMlGOL2^BB1d(8zhe77u!?| -La`Hs-R8rYrcyDZYnXwqmw^8(=WW(WQgg<@g@nN%hd;!V_8smkmz#3NaS2kH@%u@x~c)GZJ5 -oyn?ii%S+C$S7lNS#pLEYN)LSb|!^oSo!!%i5RLMn$bkgh!A6(A)i8CT9l -lbh9<RPo5KQm$0x`^OMxAU5bX#uK=8lg8TaO7Vp3S)qWue)9;@3Bp@ZYu2&%R0XW0)}=nT=IYr`GR -fRo}N*X9#B=yzl7~bXkXPLLF_!braLBCT9LexePs$?pRH9)6=7xd0n{=>SY}?~a<6cm7O*>C>ne%`9% -~OsDP7;qLsdF2EyeMSbnsKewKL?Ewojfa3HxK~1lJo===yR)H9EF2BWGtmS5EwBPMTJG_%VWXnY()-n -O^ZWwEnbt9+JjvjW&RY5!Om|o@?~JO`dXH6Tj1G#Z4*_$ -7Mb8wJyex>5@fe@LfaJ`3PZam=3IS$o2Iy>R&_X!bjiS6Axe+6tue^DuCP_M1IiV&M#OP(iduYx+iP? -LQpsC4PVoS>D{^X5l6GrESJ67iwx71^vc>M -EWULZF9jdpPx!lZyXeF{_jo|EwW)G5~}}m4*dw0czAv3CzG~Sq!X{tzMF&-l;*;Y2Qn4l7UHg1%zUDT -^gA~&_4p)ecCwl`IiY}?Mh3j7aii#_Em6CG%!>ImJB9?Z6)qEX6DKHid$8CeBYEOWEbOX{K13;d+9}MT0_wmqw61E*=Qo4X0W%U=dBl6eO|8IH6SRiQ<;Af};BliHX}4kEH@ftJ{!?n-z?G -cnMPP<$Wvgz@%XiF!Ph&a)e^0DwFr?O744Hzb)>SK+xk#o4RbW)MAwOjWi>Zq7SZ2k506|5DnG7T^_- -6{5^61L8r;94V))R`L5!h_#iUr@7d3%u`{ad5t#mOR)ISY-Rt*I!5K3kMFl;%OGKU|0bsdxwaZJvoI3_~l_f#jU5Vk|UY8MmcO2a88L+8URJD!#+qpz2%LP`>SHe%?0>1rNX5<9O -eC-<;Uxt`r?CemQ~0Y3&5KOR{}D<{(`8n`uxXUOA-rME8CHC-o0W`TW(tkvklKDjp;-ykDkTEJ0<3UBN^-op!UhOtbq51qRVk-F -R1fv>rEa&D*(Y@>lAyAJj6_4nEGhPIBEGhj0w#@ua=Ib<5w>;53z -p(W~A7MJYg`(!<#wTN}gZ3GN5;__5em||W{W5ql3SL@b2S_U!Qqxtte(nPoSQ8N0eyw~CQbL>BgHTwPgVM3JQQ%L&+{s0#Ch42}1bmouHG+9Tr>X$Oka4zy -PG9M1rPX)^p15eg(lN>m*eSKpyj4It_0;icqxxL9%Tb^cUDaKu3a;gVvst0PS2hrL7{%`L)&kVie7Iz --bg72{?JB9~%-JsY}!6IsP={hUPw-yaffR(+z+W3t0dFc|;BiAe`BR{^&!1PZy5F;@kQ33E1&C_aUXI -;x`27f^3uv>ecY$n`F*CX;)i#+YR%^)jOwaiPeT|}gR%r#JhX4rG&wwGjw<3dDlADzY?A@1bt)nlaH^ -)RiROfB7d5~ew?BYzHl4sC1HbZV)?zhy%+{db4o9ZU}XdVX+(u9AE -^d<>=I>!0XC7yNql(<}L%{soAAHY_Y9YhLlc(3W&XT?s_9Nc=nL{KxTLO+2|BiYwB@^?*jEX2nbNw7X -1Vt#d!eH~X$PB7g+8**IrZQMmgvgf4Z*Em@e3YnNjX2<0W}*&GD(*fxvs#U!wxM(B5u%6 -->TZT7eo59$g5Dv0evA@mwIUx*>HLR@%oT=#6;Fj-^T1>!`4XCybc7sesXQ4nIOZi`+#22|%$__rS}z -BOKn)R$qsr@whxi)uPtt!?tEw@C!>)HZq6^|t~q{jKONG}&*lZ-^beY>nLCh{tSfnO_Xuwd0hL7*?|f -9drFpzQip29*F>baQ5vzY0LQrmXQQYQM6=3bk|jRUz>E`?@?Jb31fy7xO>&EFEAPu^GKecZin1_txxX -}>{{v3KcPq6SbZkOiZndm{cT_B*SFyZbNyzfckRo6d-=uZUDQ2p)<|eg`Ih$vZ@xh}JG1}^;>r6;Das -(piw5l>qJdZOJzHiL*#b|eIDM?{n8lz}Q<#hD?b-g}AtrM}sCdM=jfR;bOZetFJXBqvCvDzZP%wKcDF -7=*Slr7)0RU`KVbB1CR}MEhG4u&ZM-JNEKr;5;>oDErOMnmwgZh>+`O!-=L#m&>%bUbZ5FR$z+DbsBo -NVNs$ns7Fm7AQGePAh0s4Km@IzJtFWw?D&x=wQHP6A99+Z-N?96L@lS6;=UgqRblwvwrq=nWk~NkJn6 -g`eF$qg>oSY00?Bcav^eNB2CDEL%8r-6h41(*?bvoP#~dsAsF9u-(46Eps}`Pn}_LEHTEO?YZgQ#GU9 -?7q%j>c+NT^u7Ka-pAY!w&*3mUhyPE)&VTkq)HTl@S7K -2CN%QgO+oR*Z9EH7}rm%;-O^VSjrY^t7Z!dRu-BS;KCp8jOs>GAmBLShaeW8L5&*WwSw&Fogp#)7|49 -I*#np|c?{;GFbkt`c}w`;yg5i=P;s=B+vgbeav2XMJH%U -{Ge{AS>`sWGoRz=e=oiEb;)fHH)T2FKl|-fS?_kl+&8Ltku;Q)7qsIKE)xoD6i3&I{kvHYpL=nzS4IJ -u2+QtaCT$(LWff=J$X3w!I5#n9!eN(C0eRzoqf8Ca@^fDRLU2tMNh^%ZbM8x3oBw79H5R>dfI?rc%}%#a3L1!agRP(&>}@rB2Ek6CsH;EkY4G{yY4H3xD!a -GqtQ<%c)}1CP-u#4qD9ohtr%9wpKE|23%gL^N)3=+WBZX=oZQi`uqyIYcY!Yp|ru6XNO|$a$_8$GWj< -#J+50nf|ThB;3gwNmYJ^!%x{0tTVdUH*;FsUvMJSQjX>_Z3VZ=cRqDv4zHHM}#oKq~dF5HF@$$H_i}W -qS1=_=m1LK)pv7cP= -wwiV&y>O!zO1vDvB=ruEG;@h4D`9lg@>^O{tsV5AzAdxNxeL%o?;c(UQSy{2(I;!X-J{eoWlok>(xlv -QPKNs>%CcQ|99;vmsIWuQPsdr2669F()eaL=s=kPXupR!nsIJ_g&C|pEcauNAKmN)NQNQ>(8vpIr%dN -o$nkOgw?+*6Aotz%*e>^=yL!z(B_-{KG7sH<~FSahWe!)Nd;MdCzz=y-L4{y(*$+LrVyZYb8fBX4z>* -Cj6E`Fw#|3#&u$gcr+GVe|BTeqoDQK|U&O1Fy2q|i&c^gGzvA_@F+wQOGw;m<4l^ZBo_mc`54MVaZrZ -W}**8yP^zMRGrUA3U%3pj*Hv4O$B5(X*|sXTbu~mB|pTqyW)jIB5E=BIDaexLFHJosPc^$3@;ys66@C -XQ*7)H?YwTf2J#e!e7HrM^A>^*~2A9(RnVSg7fKoq8ao>O-Ln!R~O70;XmDI}T|HgNj(;Xf7)09;d#lO`h6Oy -R9cqn|^v}Z)zMspdv=r*j=tWMH=niRsyP9P;o05>D|k-)jj{u_pFuI}5RD%U`uaCA1Q2^emhhEG -WbU-9owsqWBgUoZLa($sA;g@K_?8I6Fn2#JGD&1@HN<#pvSJ#IPII1V%vM>f=8AFf+``j -SXC18{sQNF{&iT@wkBXkLca``N`Qk4xvZe%U4O0>CbSd57K8cuNpFUu1Si$5A&FFp0e;kd4dZXf!kM3 -H{#aD+pNZ{jZHA^sx>>*7(7g?1)2+STKO4id8p&q@ZGiy~&+|16-6{f0$pQa*DA^Z -oV80QSs{Fx$FesHX0CUY3%f%uMlfojz{b6e)7=WS9rX;%QRpb-aIU^{IqRLC;W;o~nLfJK&PgD|SjlZ -IPv3TXK3>^-GhxNmK-%oO|vCdRAy-gzT+NI3#M#&*2yzRrH6bLpmt_YY3bL5cXUkB6rRUpqDWZ`AR^I -XXT%SWh7+c~>XP2@hR_|A=9^$@tDk@sr@w@BW##4y}>;_GthBLz#(agxH>(9-klYAHSc#u-v}-{1dSe -LoqT2#^Yl6=ph(EW3CWn?ARpo;OWX!Q9G{43#-8_I9Tua1V)W3^>dUQiyl@GkTJ;x^>8wsCo*)n8%J} -PT?AEBissknV6vXK1tKI4%r;w=-=s!(YTUwnbm|j9Iu>GNy_|bQB_@Qhgm~ez+z`IPrNzkqPU1ASVJ8E>u -eFz?^RcIR8C{WR;#>0T8rPTya$cLOaxutyUm>NBqwkv{09d!KF6NbPE$tlk_r!=Bw#4QPN@U{WI#&j$ -V>h4$BJb?{2l}1N?10diA+V=|2^Mt=V79Nt$uM&?Oe@kcCfF}BV%S*xJ1ycq*sJDe1`YqLY#`i7@i$Y -Pd3_g}kGRtg5l?t2iNOdLLL6K}sy%6X~l1{mm5_KsHJB6nXooi2~^Qyx1Cj=GsQ!SiS3`x9}JCxPSR* -lz&az4JO2*)>VDi{@d%bT;pnJE_TjtLq+IB6&;6LnwAnc7GfiyyBf*3VW1{Y1=KHwzhet0f};~AGXE -D884Lv+5S<{tDCsgMhN>{lC_)su=9CzriHpLLsZNd9px_|}0o6ARg~XS07--6xLHly7CAZ<*D9 -f%^d65zeXDACv8e=*V0ZU?n!%QvaEpfWmpti^!1FhsjYwqr~cC8f`DYJeKGBe9*V`Q`=ltdD{fcE@}P -W1!^Zn~-;;pwnjs`ffw7H0Clet5rqda^I_LGjj>>;Sp~)6@2cRl?aX-CojT&Ko%}F>Bn@OQ2!# -f5Fh{zQMA_x6qU`$;v>`KVs-8J5#}FEoOJ~Hy+mSyoY#ODzK!k|MZKINS=6)s3;P7C;!O&ahA+bhBi) -NeH`a(VUihY7ds>UFHgwRMV+F2%y0xeqBdzuMFv#Hl`!9C?T`u7mqKc(x!bIj$vTiQPkD;YRb47!Z-z -?=#tEysPwGQB-fY?xkwe@bi>j`S%LgZC9=`w+4t_|5~X%sLw==`r+B73|R9IRgImo*vsk3Q=}t_V;`e -N1LNUoV&*XSOFWoXK=X>rY_3Phvie0hLwcQ5MuVL?ml@vWZ1n9z>Tri-V5_PhoXq^o7VI#<67I6HYq* -QB1E!_$iFv6@G;I4LiK(-Y1y|dp~{};pa)yiVOO#RXrn>`UKTYeKvJZ`-S$Z&bCfKpr&2$rEOUsOGPtrn7uY6PiW_szrWjXv!>uP&B#{7DDkX!W#cL($ -L@$X!LL%NCtLl-2tXBs$opo~8-UT;q -hVUdXM#r6&NtQ{ciI^S)F+2bU%i!vMpn{u$29e>r`u``TycaubU9blpKy~d7P@;)(JfHjyee4&L1C?c -P79&thLe);GTjc6`mz+@Zot<3yd$(FG!?_^>0G2H8s9r?mzfTPx^D;Me9h{3u_o@~B9Z#oz8NCj2bg5 -Bd*t%`ROO!gTMpujS=`r*ixZF}E`XBmE -ZrSY&gFm_WGm!`eQq8LwPAqcoDxT19@)P58t)C#*i*Ve8oy|B;4d|0SbTI2puFl+5j^9M#lt&$%qbj1 -jyeB$>X>us>Q0(#syoZi@+pH=`!i9wAU-Sp!OsNS@9j@Q)MMP -L(W_}$BJ~kY5`UzL}=NzYhIBzvb#OYlcX-qa1Pyu(khQmO|+KY-#M3rj-)33N622nv5{!%IipG#Bc?v -9FTHHgL%bk5fz=j4WUY456>hH!-Z@+OQ1e3R=s$)b)lY$8;jWLmdwSFt~ob#DzW9gOkKC7*pWnc?SC_ -LWXw#2~HPLCMEcZV!6TTbpR$xfDel{6$lX)Kl3eCYmp9v4DDy-r|h~JofCE_T@Qbz;3cRT?`3ZAT4c= -*y`(`#+RX)T#CXaZGA*kzoB(kH#_`gFTKbc6*INDG9mWYkUe;L|9yDKv&6#+C2dbd^ -}poaK9$#T@lr*JJ}FpY^qK_K`HMvRj%o7Tw~+vt9Ggo;{QI**yiQ+NG`W{+Bvh70QU-?Sude*U8Riw9ICr2OOXj)+(w$Qb -Imkawkw%DC$BMK#&Xqmi|(1K;JH4)vj75}uVA~vIc0|`EY?{IkxHU0bVz(NU#pPXkpiQq`c!)NFpx@^ -a5w7B4W8hIF(vd|UdpFB7mAL{%?FYu%P9@NJoryKEF?rJQiCX;bw(dbS3+7&7Zr?(9Y*9(WJN_fU(-S -ddQymMT{lRf3Us8?DcP}FPwuBi?Tl#KfW(Nr23wD<)(xp)$tF73XVucXgV)E(F?zy^C==Fl=Xxxt9VI -zr896A&bvK|FjY@VX1kET=%i|2%IjFWN3VVZe88RI!xsboD -+m5EzDshW!IvzNjat?d>2mWp9~;G@5Cbhy5em_NM&r0HRME?}F1r$-i_+Z%FGbi-NuFc*Pkb|}ezO@! -j3fCH#S^Fg5SN3ATR?*%2~G}9f-iPImjY%p&@WfxT9iyP&2D#=*|Nbu+q+O>XHzwvZd1Y(-5OYDF|ZX -Vaodyl@&(`cD>5p`>pD077f?$B1QY-O00;m!mS#zy{RLL982|vETmS$b0001RX>c!Jc4cm4Z*nhWX>) -XJX<{#SWpZ9;fiZ{{R&%-$KpydVU?3=f5PR}k*z5F_7el%xqzB_yW?(N^cfB)UP3;YQ6 -I(vJ2oww^`#n(K_sWpoi%x8-gzYb?B&bBNVw^&LWN1%oBi^{GR10)SBiu3C}v6>C|7Xrc- -vzM%G!JuKiW`GoSw;y0BcPef-wXVF;<^c3AmieZ!Z2aHBO4ADSy)$T4Wu)u-kL-$#;41x}}F8mHKB$@ -8h8sWs_zaPT~ZVMr=?Q2Y&jv!~O@U-RkI>2x~5%Fi;^0@eP(Uu`%&IzvJO$ou{>%ozOdM{H9zQjEMiraR2SD9imQ;5OBF88Fz2*Xpf!Bq})Ug!MWF$`Wo3-_#E?&d%bz;D0_BRJ7pThq6!=urPu(k!@x -~9m^?!PhtLokJFOVWk+!ite|xkE*SDFl{)Ovv3RT2D$#lm#S)pokD@qd)CH{bCn+taC#l7rFrzmettt -Phn51eiVh(DEi)tl#U>&ECIbp{FmVDQ{i+F2y5)h6-028>$?lc&y{EtWZRV&8J_TDs%ZHY4BCh9SecJ -+anBe|0r_XTTUd|#-``#}|2;xf;Z;j?G+ILN$&bcz?J%V#NH@RY-%dj?9yU&nL4+85FQKwbLT<#ei{C -O!!6u3?3Rwl*oopi-Wstu6PSWVZDL7TliVb?BVd;B@LKspuM##{dqWT4C#rM4K-_vn3!&ZVCutHce6> -1BrtgoObzf-XK4e^x&Cim4aL<4O7rc(JV#^;_)nwxExlP^QIY#OlDR^>)?=`_=L7$?@&0+mqWz$G6A74BW$CZ$BM7ugBR_d-P$PxswCuu_b^+n{^Y6@%+ -FZd+^(N`q(O1Ym1r))eaAlSch+s&!r6lcKC;}pmJOF?Vvl{I85e?6P~Z0 -oYXWyz3jVRZ+kwzWzo*MUL7}90?8I2UNw++RX3p@w^ui6-KW|4aK4B-vUJ35g! -*G1mT>Vke`LFs4YSs3j&}apjybAM;%-W>UO4FoRA6deKaB%}1AfYg&Uscn4*%WJKLCDqNt#MK5mINS+ -!hC%fUa**val`pg>Q@cMGL1J0BCz(vqit0YC5}FpHAlTkv-R_l-zbE!fwzjUKma&m&e4<(fHqyQd3cn -CdAQ|L^@mv;Wd*2U1%pMSccPC{c0HJwBMqQs4#SPe+8PB4G0`1Lr=p>kC95#EE!T1;28^Pr2F&cmm=L>UH8opDJ7_xRPqsva(ix(lm(7p{evWQ -+3lQZp(Hx<+EoI0($Xg#o=D6%DAF!^qCPC!+Ndn8jw&TRrU^#7$A9-*#HPs9@DYr@2$nLe-QjRMAyrK -C)r|MD#BubZ7z~Px3cfsvmr)WO}MEtdCFr+p2ce(?G -(XQ%Iqi!YKwQU*21S#hL13dmBdJ>2;O+9zLQ*^-sb`lIX>ltW)JnU>57;y#Ue0Ho@C^i!zKOZgH!U8Q74@kI*p0h>7l(q0QKrp}OKLC`Z(fn%YA9wHmZgT~r!sDpQU^)cj)uD20Uv~d@Jf9#s+MDwlfR -Jn6Avz>Hnd0DJHf$x8>z)-_@--@$u2tQH7YnQ{I!*pn#gZDz)h0owc}mlMglTg?UO4f_M}eH8~_ke4Ng6NM~+2i=I`-X)>0vAgJ7Qq{y*6v1ym)Fr&}xj!i;TVU7 -WFcT9o0R5;(qS&}pD;zy%c=VT&U8T|mZX4X~h8Ei|DB*o?3=R5kY+*$qD<$5C62+e|gHk2WND?;9 -W}`4GTRJPiY-XEHSFPZ(~o1@SWV6L6kf=5OP0UV$U6cCaf!Zm-!qOhEvzZd^9oi74xXsp;7I6EB -JgF|*}e)f&NxgFx&yo`L$)qj28%EPVDbyCet(hkv>F^D7R6HFp6YbgA)6x+Ys&J$I_mbFk_V97sy+Ip2!t=X{5l4i)ZtNl1@wwPGCwDW2aNBW;;Q<9JcD{rP+zvzRP?=XU=3r^n;Nu{YtkY -DLt|-GBq{QU1-4Z8q%fsiO`uEGk&2h(q)RfF3}crtauK>K7y-F?Ezi*Ts`UCTQRw)lE@7_`I{{FlF{B -8&=40BIzXuOfO^WS_OTaA_W**c8E^=8fTs~E{TgJBxjl{M9(Sn(}iu{8!iF8wWv6jhRe+=3Y}(O0xAO -sm!{R^=8{=1-5!?pF(8(ld14!IoH|!KDt4XCUOQm9dC(j0jZZ@ISo&K@|gk -yk7C3&D}oxdwd170Iv{C9|r8Dd}OkF5E}#x$mB!P2jF+1E2u~9?N8uDc}L!Jujdqzh%~$c#_k5!VscID9kJ -J^5KvW#w20mf9I8fsy7`8cGIKx>`7;ZbN{X0^WTK2nJ?nrFM-|ia2HFRjR&S=wRn4juBz0E5taE2f)4 -tGE^ED0=NvZB812Sf(c&qb`VE|XH?%BCGH{+Zq1aCD)a}yMN{GPD`|HClB(~3j4>MQjJRes@z}r|8aJ -sG@gPoWs8xj94EEdwPA85U(#f$_vl`WFj7C<}h(0@JVQFJ7P{e{*^L~uO$R43|4Ta7Lk?qZ9lNvpd2~ -6o-j1oXQHQFx!O=$b-{~>LQ2<=}%TXTJx^sRHjzY(Sy0E^Vtln># -rTx3Q`Ypm;Zqk{h6vnP%Sb=SR5W#F!`S)s!o!SB%*7I+^d~^Ek>Dhbq2W@I?YkMcw$UEpx9$OB$g!rr -J2SXF<7_7@83Xp7~AkS*RQtQLWo7m&IgP>BY=Z*VSp!>`_!&p=g{`h!&0BprNRsg3v*@;w%70_qC8!4 -56`B -3NSZlVm`OZ0B*n2l$)zgqaWesbwM;~By9SMXB6#lTjUC47b1Pf*Yd1S6zWd<#$XAfR=sm|0NnnLRXMj -}Fn?Pv@arl$HMM8P9z=%N9Q0YnpMHTzvo4S8xAukJa*FH2maEVZ|KmV9xt;rZsY(?m6~oJlVzS1+-0c -{BWhJ6xb&al%j&80^^))`iMmwwc*j-<6+@jT-T(p(O}v_|HfN#8>MzM;MmXm9i))m3XHG7*m{P!q&EbUsnW7Cq|q94 -Ty1B#EBsWnX*VoHhoi&cWP)iRt-uXR$VS7%NyAj!(UoSq6zGWuBh>*zMf6qMRJ@Cg1n;f=Ec2HlxFrD ->rS;X+AoE*jXf7=A*)g&astA{Bl1ARZg|LK>WL*nEP%vH`OQ9~YDh49k;K6iZ`vX~D$0W>9_JKH33us -s7S;d(!MQB~R=|c3P5S(Q_?chKR0|4=gGqDV}ymY-~d{0pC?SkFJ8x^@UtB5_K5F?Z2CZQdA+-r!cBQ -P1pWUKHa3ML0Tj|l#n4((#wEl&Q*-ZK_#Qn_CROoBcIl%L<6zxCMH1k7ji%{qZ51Sr2m@)zOV -4UzNw<_)|IdcBt}I~sic0^wsCgP74S8yxlr%zl38y*zv(x5^rV(x}c*;)Br;EtO|PIu1wXZ}J#gg0_% -Mq~@l}4v$_OG5hnr*E=e}U*QC8GBMde#NRd@yAkNKH%W95 -jhQIvN6T#@heLK{U$4DU@!UhRag!*|sH2a@+(Y3mHC;u}qtU^lp^bY<2l#Ta{K~Uons%WgY?k}Qg6rUAbn?piHf%Hm|DnJ3L`yQF?GbEx5B=OH#$9V(p_Q;WzM3F|iNNd?dVCBM0gC5VqIq&kt0+ZBmmhpU%%l1Hd9D2RyUhn0egpN9ha-6aud+~zVp9Ub@fdy1X -mpSh7tam`fv|m3O&z<&hGymD_Ss+$L6c?L=>=e&A?Ex|TJ)B5->g{>#qPXP(B-}z}*z8lXir0YZ;OIH -CUwF?y+l4J+D&|Ns-ttxdq_U8ZlPzMcv$Hdc<^EC}Pr{`PFHJP?Ufa~*7JiSt6d6EgF#KBKuz*N{U3H -bpggK(KNSLWM=c{lXHjkkz5}=)(>&It8>w%U;Ss|AH9Fvgt=yVepPD0(!G2bv|grv;4s;(Yy+@;lXOY -OBm_3r5NVI2VjiN2gd%H7ck32QMlp(u(jg;1I|^+JtqMSU8}p4kB&RI*Zwd}3mSF7ZHy`}-o5Pi1c5S -J{YxXgKowUfS=;Wecn#R<1K-dfUD2)>si+HA*`gGo~ki -RH2{`xi1^^y@nNZ4w}$`>L%*2j`7>zP$P(x;_y+g<}Jg#sJElq?C12ZYM=&u?ADVN1%mL+tf20>r1(GAKB#d-yx6hP -R9{7TS8QZHzx$frS{**)*G-Eto&05ESQ+}o}pkOB~_gOkd_3RchFe?&o-vAyJaPaT{LQ*&9Z{q&;}_Z -1td#>s}F*=f!zd+NTHdYUyk)nL5u(>|2A!Km;+z*Gu&MqZDNXXMjxw#m76J(C0NIz`Ii`IqnB*MgsTo -@8=x3q!mGIS69VLKD+_>vXhKMT%<_d14r0$IPlgW>i^nHjHmwd?TsXRiD7B*K6QXhxaekJG_t|CEmQ^ -^;Z>6SkdT><1P3duUn!tA-gd}wVLTq5gVN(GG14BMU^~x46am3K}RJMmsO5fV^&x_SstS@JqvRhlF_g -~!3XQ5)qF}AJ4?KuF)!Hu{2|ASDFpP?Vh1d#l?zf78Tn49xxjiV+>o6+#8t8uByFArq=;c;)(+BR4fc -wu&g-KS0a4j6sz(`Kl&5@T!)^;ffFdDRg`rgjsBu48C82$H^0nBlHI -~C~%)XVAX=W?oobEn743lPS2ZFCF2ZD*7*VTE)uQgg;YjfXtZdPL0cq70p<^IPuZZ)WUzZ_d*Mg&ETG -}(C=R+TK1v8VrOrxPgqE^7N;_hoUC6rQ^iWVIcKQRx(Fd&9?t!64Lhxox -0?mKuLCj`Fmr+9*(atX18PA;_r^pP37WYsRD8Wb3xOiHdwG(a7DEK~-E=h~bW=?K}M0Xa7xCbXeuFa= -Leib}>o%Wg?bN*LNP$r5=mTe2F^<6+p8aiYIhY&C}TvxiLzq{24aP!u4iF7lBmH7}hTOC_{#8bGEu>d%b50t&|AJ0 -5&$irESC4Xs2vYF_9hQXu7qZ!!02ofuI7kO$z!sU!-V;px4+xuzH%Gj!Rj$Y7>sCV8x3d3^c@hZH-L+s=5^tf_vSi5lx4+xMZOhRav&w*3%+v0M$Mb6@w+fY5lENHv?Y7>&g4p`-b?!N1huQ@mZMVI2D1JK -tyW4lT_^#{CK04fOHi3$_UPYMwPV)|W?(e%5@mUfZ -P-CnG@V|&`TOa&rx&FoUq&my+cwu}C6eLR53IHBXB9s?5&-0Gm?fN;dhexk(F$e=~l(sUE^rY1al}hp{P2te`j=ld8c(FXQ(h2lBOgf~LA{DxC6A0L2vs$!UmUlIK1T6Xq;Bx -0i*e)?dIZ=sza&OjGU@yuP)h>@6aWAK2mm&gW=Yt60Et?r0000^0RS5S003}la4%nWWo~3|axY|Qb98 -KJVlQ_yGA?C!W$e9ecvRK30DR^vIY}lt10)!}BLoWuH9Eu+hu{F25S8FS$P6il*ek~ANLvi&0Ja1YPl -o1XGiqDxt@qY@GhgETg*i7PQK&x0#qo%sYLp4fF1_(LtTKk-t1giJm=Xrm;e_kHuoU`{{` -+Kdm*IIk+ovQCW$XPg!v%*i)IPNf~|8sNy`#%c5oEd-0;hxEO?efE><*!}7I`D%{1sfZGd{^W5e^_wG -_kZ-GABzS5bZ0@M^rM0w{HUP(*6M;E{&@YJS7&Eu78+$7%G&VzGePr(``_7n)er6nV0p>5dOaMs++^cE{B)H+8Q061OEFMePV)ScMAhV*wj1qV*j4{h~pOGM}diZ%=-<4*=*t-<)PRp$4 -&ho#(d8Em&^^cv(ohK@K=j>ZWZBu!;?mXu&>sOVG20z_NyD$e_#AQ$6axy(f4`q+kY_})a$-l2jlv#h -DX0q=Ba;xi(K8fsqqf@W_^V|bF1Ke`Wwn?ymP~k0Wd9xV`IR|TV%QvZppXz@c;k!|K?u}IqeG+|JBWD -dF2r|NBZ}O*^9YQM4A*D6t52~gcP5a=Z2)cdV)APa5#(Of?U7>q!938R*4UCT929mkamD{M%LAJK*24 -it$=BJto;!6XT>utH}RY_KkRXMHOU?h4WHMvfCkJbfC%ZOmtV(mdaRe;scD-0Qhv~+<#E`os$u7Av?Z -P6y0KtV#rvIFxMJ8V9I^F`xH<1iy4TKe@=JDT;FM=Eie$KW$xIUAN)z$$MbU71@v!;^iPf{F$*1S}0| -97z*fZ?Z0)ec>oL6ZYR+{=Lg<725u9&bp8rTRBE9te+n4sCONyD^iJg`Z4`^ItH!-|L6hDWfWg-Xq7# -glNwnk-6FqS^*FN#?_iHAu)lo;Uiv+Z_M9& -A3@V#cdS9aDH1a7O9bZyf3r}!ZF%Prl)ck&>yl|?PH -yN$DH85m%2 -pA7>Vm9O(b;(WiC{=$U_o2!V0b4bYo01FOkZ+jteJsp6ViCGJCPhz&GAsdwWwVoKD@92reEb&)*;((s -Gf&NKPT+R~j)e}vDKaY2FvGT&(aP89aDxod_&c__{yneqQCeg2n&?qg>0cr4=8wjuxz6&J~fC9pT*!; -aXywIrv9SX+Fi|y9*?|i*npl%i{#GdbB#q^)YhCsAKC+AmTLRP}KlQMVI%{{jtfU04V)!E?+9{7ZCh- --k+o*^e@ij#x9mugu!tiY7)05+_>!_eSOXCyR;2>m>(V;9r`=vrGVck&JLjWdJhwbBe{q(NRZGr*m4b -HeV3m)-?H&PZ*8l4dzmYdLQuS7P!*kX#AL!lAUB~8u2$c@R!Ov@Pn2VjX3k6kGS -?y_!ex6b+sEn`_9vV=SuJqC)mSpc@p5m0Fuc*Mj>Fo_$=b5yPP0+ -nr^*3@)cML0RdLyFsxp35WXO+uNzLQ2MuXFoFpfRSg -CMkZAWQjGYg$wc(#(P0qnu@`fw1DX)E8^SNx%-h3`oOkeEkYWzYqlP{Cg@f6E;;Ve#hbvQI|0jo&kii -UjtARmZdKol%ywCs#HPrA-vo)auj_gS2s2BlTPOtrP)1Nd$zeFR_PBYL(-qA~P1g{PpQ}TnEX(DA`N=FRSQZ`r=idMy4xPrK@+pttn=XU~DWN_b$w=uV_znc# -Gmyw~R*yx%!0u%1-xDynEC(!W;Yu?wL`$r}^`6p|pD!GFxvMHs?_y^xR(389m -aaP(Nh(q#!_K7UxemiN&oV0leVY@p{o9P2u0kH(NlV`!osIL)4{FV^>A)%W@7unk#Mv7bH%Nbo8xgkH -bu%0{G?%x5~{Wx)7@_CP;CQ|o1bADYtC=Zug#-t__0a}4> -M7^NdG=g>nGqE$3xC+`cEupKIBxpVQFqCbx8%ZDpg!?B<$(;2Cy-7OFmos)jhCOIDFN#-hp9<;^~Wda -Dmon2IKX8^gkeL2QI_MU>UCMAR1e8Bp&$51lpem#ZFwO6w}@CMLN~OKr@HyoCD+)I-V`pb|nok!wUUK -rxAqWVEtjmz*5pcYV0cC;CfjcFGqk@`G!!p)Zb9*5Mi}2zyi-lQP^6ouPAwery%InB`gTrplw=@0gCP -Hf?8`aQfHh&p86(Z%i%+uq<6tbCr`r_*hf!-=HR1M6Yv>f-%qmVqm%KeOvX{kUpLxo=!F2>#6faG-J) -4LCU;t0lD+YCvP*K=sp*S3PMj3LH@$rpRETuJ+JjNG^x>HxzXhNV0WUo09&9f1veiC+;Lt*d$pe7Hm5 -_F1IXr4f!qvG+An8zyS?iHwR@X@5mvUJz)>FSuoD#qO;9ORXn{Jh -d{n_7?=4{n?nO)YsF=KuN^+yJ&yn2;Vn1WIU)2Y&Sc43P!1_sgX`DZD8trFEwyTdC)Sn1kii^(dYbtW;9hqdpn -)^lgAKXld%(3a?G$03iuy41}l8s(upl^loDSz0C$d?0wP3Yp)a -8H1?eIWvCA>GgZ?L=g+8q?s3?NcKQP(VUYCkx0_wxbSQNg3998r%1|fp}#0auH^!-^(nx)^t2@7aQ=6 -`?&zN(`S$YkPGx^_^)Wy1L6-Nw*d%J)%uJmRb_%6X|1LbXa-67g2zoccgEM@<3Ce{B{*qkKOo<(GPf# -R+r?WI<%4rC3tcu)>2Z*sbTU1|B4z@7?lapI7k$`Kal-SHwn{n#Ak$Sm`sFS{ujv$@%EVyyXLYIV>NjQ|du>5Ob0_+>k -^1YHbI?{*7chUwKg*8QE?*D}Sju-;j@5TR!AWrWUc6+&m1>v#wLcNzu3iKJD;nR=euMOSH8e;Uy#W_` -JZL!|-Hd8TRDkxJbD=AH><%`W?nel3e1n0~9hCayv+LO%A0hq0|kBG;y-rl*@^^zydF$4>?lHzyb%33 -KNuwln+{_UGCDH$A^YWJ-M4Nm(OdWy*db5S7WiR&<%>wQGhK6gg-Rp4prFgfbyWNkq<}&7mXiuA40xH)_7yGy=UpcnK`nGO4GTYe4kG;8^(a$H3Hq~ZIBxECt7(y`DN*zRtRHi -mhDlR@JOog#x`D>qhrvw_;gE+X*{cU(1vq3~tWHDT5j2H8yccZ<>vgtcxg3=X{8ia -!MZ1&eYp({p1!8oahqkEMsovcWpMZMtJE0bkh8ACiJP5j%D6O}G;$3eInyP(tM8{JQ@?9OmuJhP0KhQ -J}nIyhwnKeEezDPQ`l_w{P-c{-b29H|Ah2-t%IUC7+=uyz9)bzs}k$P9(=%J)0b)zxjqt$+ -UEd>+Qy`ArGR2G=+4;adm4wzV!>bG}X@d&)_k*%x)4|pJBCIL&qil@|uD8J`C0+h?FRbT8>7~+n}cK) -JL#Rkg|w{)mjX--pf{5pn+@k&H(001!NfybZzNUGnyA6 -QWqRXgDK3)76-Z2J{aXiekh729??RU<+F;hR;!3bg46`~Pdoz_&+xi>;0b_}Uc||#?TA`X3dn{qb$N7 -^JvA|3XR(P<=OBvgwiz-22Cf9;s{IDJ@4xJZ-1n~SBDs%L*dLcA2P;u89Lv2+?)?ImPm@w%mvVgcFMz -!!k6pgg!PWZcN6=fyR@)l6mzR5o0Y;tpxJexUI4|Zrf!&nSV-@*01Ue%7C||^g|1VdgVF?Bbh+KFm8e -XiyatGbrh~0fD>yqx?k!lf5GrD^tksQDdGvyJJXp=|G(qCEstw#TS2C>aB1l4~Wg~zgtQFzmO(J1)kH -#AsSbLr=qsY-tipII@re6YeUiwk^Ih1UB>g1&t%%915lqQx%lS|(?+iGM>=$CHYwsHcMVHv~;+mH(S4R=cj-dy;>kvyqP_*i`si1 -dR0rjh*gT!gtGIjKOUN>9c3E7+-43hZyOUd{~5c&gM6r-KF~}hYDd-sp_LAC!+uZ($Q2}jX -j~ubBzcGUBV*zVLX7F(huii!o7TO3JndSK9CncgX~KvD(@))aW@xwNKef-pmmG^bupOZFolDjmrPGcJ$FEr~b&~iP!rBa`i25R)zFvJud>ea+^n(!n;sXFCd -Nh_GmtApH0iRTo6%T0Y8!v812$KYX14+yh#7GPor4MCIRC|0uhX*oRyCTs5;4wzDQAwKg8@HuTNK1=f -PS(XdWhFy7X_R5~mUeg@xWwW!_Y@WTYvcl^(57-vKi+H%b^Vr+Ew;0~A8th$&VWu_5Y+IS+Y~uD{;@) -`>irEU_nPKH3FO~2*; -N&&yV7Y%5%T;ZAFj}#jxlP{RU3px2e4euZ^W?F?YnD0gA~bM-gQY#G@Ib#IwWUN7p3p0&w -Q$4c4;EU9IModZR?R?U<<3xLd8VOYj!)p8=YX`&o)c -Gv%uB(Wl5Oby`vCFBP5$~E9-x|I>Z%$M+KuO$f^_HE+) -vcIB5b@K!5oK6r{7bsr%I_DL`Zhde(*ep$4Vi?m90{Y`KeZ4iMdydS02K)K8UfbtD%knuQo0qub4BC} -G1gE-fWS09)Bx31e%JzP@UWG4PW*5TihOO|PbIRQBY3xX5t9Gwa5iIlF(PSFl)9~U)#`d7y^;g==3dDQ*MpH`bkeW^N&ycxQ=~t>I~%9*r{?Wca5JIWZz;lNDCW -8&3NIT1lbZE9|6#*TV}i?BNI9)&`vk!@P%Us8T&l*1{@Am8#S7`4O=`d=rrVTC#j+Zn%7>L-F(z&>It ->ZZVg*+MW46DKmK=f9fIPKI%fop2B~ViB5JPvHm~c?K#x9Nz=!5bm>7u{Vqe)c)S>h$Fy5 -^Er&0wwSCzU*O@Jh&u`>9%KwOwevRl|)DR_rTuF(kz&Ax7dZ=ITNROm!|Pq7&-lp+n9ty7&_fuqTkOZM16}9ooiRx=2?Q>lh4lLopv -lt7AtE6vLCb+C5XXd-An==FmI?@Tirm@%gLCXSjFgJnkHHYTB{2sNa##$d@DvJo69Nqd)_x-H@(npP) -I`=MQqL0-LWxZ;B&rXz5>zyYd?f`Bf`%yO|y^jLt`S4!DNjIryR%7=pa;YwG~&ZqvfnuQ~Wq7O*uzE0 -$yV$;CjlSI8rU+un3W-1X}^TAn;3o?0{0|YAJ~wI_3-OJ(Cx~BI~8IFp=q{<7gU|qFtYo(Lv;K^g^d?P!L^;`;6 -|bFmw}{J*G!Zhe<)rFj?;bP*Lf0tTb@rd>pz5QHgrgvX~oPWFLdj-pBFr6h8J_7LSqaN%p{7FrTGfE3 -knt?;f;UBpw*FtF!U+-Q9Le6~E3E-DJaLw&SLiU>W+lf_q2B*w)%}I6){BG@W4k1X(+75%Wk#&|F&-6 -=%YahaY@POX-jqw@v{NR=QlY1+E67I)Y8ygAcVv+j1Lulh)x7g*WmfO{u~PKy;6G6LYDysm3sP6m0yZ -hFJ}Z@tj_adCiF*)o6mskBO4VD+vfCkET10MLuT=HKmaX4~`~qU;k!%XX33@!qJ`Eoh7A&ng)ij -Azoq_=+{@axDalGa^rM^eexTvHhr!vndV}`b6eYIB8TdPij!6Z#5Q2!VCAV=Rq8Lxw-UEHj3#FK1O8u -9cf%ho$1l!vhyMS{Z@&HCQwFAhdVgIY<%GDdO`*#0SQ9om@Z58E>|@T>z?40r(x0~Vu|6n>2e_*l}bX -o^ -_(=a-tRBS+!-NQpx_|`UWpkD#;)sCG$x*M9)0o6h~p!h=c(g8L`r<~Wh0s5@q_MBFNJtPVG5@zjT!?h -xi3#0+;{?S5a6|W5qiZ}RlNUh}6G2;Vsksk!DCo#gDfbtIG*sQQ>N}8JCqi=qUs5=NXJrhC3+qsVIXz -xHt3+q8+*v}CLQ(zdk;xPX6Afz(uVQT_8Sw$gK0(7vG0anb@LtFLGd_5G@LksoLZFYCFZth#R+)ZU -5phL{aAp1vNikzvPPorI7e_ZpNVv_{z;J?8OhZ=)`W4#;%!2hi^l%uY%kWIOjdjL}#f+lh2U#9@k1^nZc!g9v(P&eusdwylUu=^-EdhvdKQZT7pXz3CTEzYg>35V`Ua~}7H%X!@0o+n51TzvW%m`Nqs8a%7I{? -MBLm{i5RZ@`59Q2n?&POYTAG-YcKxM-|8fD3B>_QDNd0e(6^GE19T{iqdLm*Hvs+LQnCs6dqD;Anw8_ -oiWrS_nMOC`7t`Du9Pt=zw>wt?sY_1&&P;z&d4`pz_w!o@3Q~7_hsN@q}D(8z3d(}pCJ1 -i>mP~RP(F&A>m=bkS*yHYg=$>qh1LxK2;$Do3)%KK2tnz3MCn4^6$97~2gN|b=g^jx5gZDk^?CpbOnn -swF&8wN7f%2I`{`a+%F>7DTmtCScqG%hnDbRP>>Z!B*l?kI?!1{90A>VEzvAYqee~{RKGB}EKooqT!7 -}w-NN_`|7H+=MN3VoH*y`DhjH;{X^`Pb0*+3r@Q1os!EGkJ;o%BNB6ht1uX8HW|>C&VvaG!Pny9{yvo -{IZ47{C#yvq#XIpQ$! -%p$D-mfM!{gzFxx3v~C#ohoXB?AtKQf-G#b=qxL$FuNnH%<*{!P=-r=>YCLrv!XK*aKJq2-9a9Ae!6U -s}CisHUA*nZsr04%+HnBVgWTnlct6}XwQmyPUATi& -?FRzdKIZxtKpfOVlK>HiK}DVOs%SKYdZ1tw&kniAdk12&NCq^#uds&Jh`gijFi^(wcWNz6CR1k)XcWb -4QH!d4%#zsv^{T}X6>o#s3vs|MqiwU -Co3A7bmR9=$uly94$qP(l&qtHbGI|iJS};?prBA_^vOx8ce7^y>TLbio~S|ePVD%uY -u8h+%0C)uhnF2>c`d2laHIkiOM?b;|`$}2ZF5N<=zn`N2$QV&^e&X4Br3&3;7f1WhBj`C4JC@NP(+gw -9~ol+wJO?ve9*P#tNNg(tY%mD)=0#u%mm3UubP+;ww)Ug)2=;4q1_3l(0P2$ -$z26dE27xFq{=Rz)x;_C*%5IfiORh*$g$`-TeLULVr4!hs_pQaK{q$(wibxFL34HWO8QVa$IUtl2#44 --8)|K@;@fz2A}9cUbd1)(s}v>ELci~6=Db78_l3|S8g;zB_=hK@KCbbii -w@;>X;VyKN9fF&r+1|=eSP5&tF8qQ-Of<>G^mH?B5Kq-E6yy(JBxs&}j>hfp+8O4A9GwwAHk*^+urY6 -k!7@ddJeT4`_J_(sCxFWzEx@BV;zl7M$smyVL -C45D{k=ToV1vLEgQDVP|c2k{j>*$M=5&LRJ>St=!6P7=dzn|v)KkXrle -_`^R>;qkFHN=K00@$>G+Q>gF-8g3sX$)!%fc$`+d?%AH5;n@Y5^y(W~|E5wQ?tm&+j#P1t_P;qC&|%PScQY8#Y&Evi$b#8>yRUk3}6EdR -`ik?UC5@A8aDC4ku{3vv{KUt+-C~2aLWZ8&HXgR`vAk6K%}e2!x!6lS4lH8ws8iveTvgU)lZUu^!`tD -dRSpYZ+PgMAN|Ml-jVd^Cu!}=i>-rElax!lK4F=EpUSKTTFxP@Y+0ey4Y=METGODA!KhgXU4Z_T*4f3 -9_VVIFbEGHH<{1%=L@I;UT5(dPltYdV^&tP1KN?YL9Hh>!)b*=B(PO1abe&#P`6nqv`1Eg@re05u9-~ -T?!xe3`C>{qQcyt2lQMHgANMSl1|4CA?pp)4rZw$tdtf=@UouWTm1i1vIJZBD)^Xi;=M6kR*xY$}rI> -eu}-(+h(n<+Lj-|7uom7}?7glo$pLU^gk*nH_M&#`5SULv$F&zX(D9tM148&3qco&!P6I_T_7X*L7B0 -zoG;(A*>_fLU~~D>8#zxQecXZh1+%Ha|_v>teEx(7M%v+cz}SEwud*K3!D~;b-eHF5Of$5H3%%4sy*x -E7}vGX0yWs1 -*sTCz!cdzIFwR})C(Wv@-2{y%9Y -vmgoB-&g5b8mO2qTEbwp-e>TD!NVEsgJF(xO{9*sThnkMEcOl{eXNLM6RMjCgXPykmu4?*TYarfPMEi4v`-*H+v=RzOXY+`N`|gjT697kKXl^Z -plKBzb)*62kKh1xxu~xH7{ztDT9f)go*q3sh2FpsX@1guo|&`cs?)f61!6RsseaOfb}P?m;7&q_uuZU -mxU9d)WzKF_2T0?V2o$6&pIEGR0#0N#$p14^uw5t^<3qfB7SQ5dB%rg|;YhnD2hOwiIIR!9wI#_z{v6_BVEb9)?S153l}+X%D1p%fK#1bWeS@-o1(ZHN(I*Uca5 -?E20R$Fl2#h^Z&H@~&x&S5Kz32H2v}DnIOnRv#xFz^0Qd`i$;aOwxrT<;7M|^~FxzJKf`$5^=+7I^HN -|s5i+>)kD>^yvFdb#NjTsnnXj+uBN{@oAfMIFQZ3$Q+2=G%gRBw&ZNP|ZWw&7{SZw(b$m75RWsI?f;o -NeNYzVc_8RgFaXUsA{aje|(Y59K?0D}4x&ol|6Ix$=0HfBaRx*w`5oDakaX!{e-Zt*>POEhj4)6P5z1!wATpCKGnP~QPhYjPQ1?hAXK -F&t2X?qI<>A1%v99S}%1D)ER80?iU|}7q* -Z-VmHi=D`gv|6@=SZj&xuE*BWh-Z?|yGx6f#nZ+CKf``}rS35~l3mRo3&<#lM3>saG)IcC+X*!CZ48e -v~fEp&2wBf1zcTkUNqarsHraC-(wA7a&Dl$Ww#$a?fWpfWN9h)-fb34r~U`8qLRW>7pl!|hyKg_Uich -AZqTmm;ARl>@DUmpuvVGR-sWI=}6H=Sgw~i(tcsz5^PFl~8wD^9pOTdQWGCwFN{ATX1lVni*cojF@ud -Yp-FGNywTeE(vMkB7drFPNqdEi(omBuoSnNfop*kKKXxWlQ<9Bqh=ylEDfn@seIld>AdA%PmSedyD#Nr4;Wx#3XEQM>i@#Ys_AdM-^Ql~N5=4}$)hFDJoT-7N; -#F}Q+?hPpQ<*F2#~Jk4=v7<&1cP$`w7(C#Zpin{A5v4BBX7f=lA;b%`-2}FVoLY#3TXowMOn0gB}&b3(({f~+6SYucui>yFOBoko3Uj -`3ez6(fBU10EPvFm4B^}8r7>~Ho#G7CmL<{kpq8I*(UI` -}p%lFxaq=}tq7<*j7N58wmP{O=>7*dXyq>RC46e&y~ijJ72se|rvP$Gh6YF~lkJxNdt=BahX&`dalW* -D!2f5E7vK@u>UnvYgj84AxuIA7ZdSjGW8+x=91*Psoyb;3xX5{Li7yJJY;>34P9{`I?KNuc#o64+&c- -ID^Nx$pkpNZ{eBOGp5a|1ZYYiz?6=qZNMx%Kz%!B+7qT^^b>Q#Oa`oP6pkW7I$h|OM`t#zdJ!mQ$TO7 -u#R!iCS1z`=CgIBywzcm3W+~HTL?PO|r|k^C;2B`&f -iYKOju1?A2u5?aGSVNu^aj%L^ftv4VxQWczLM`cj^gqxP1wSpK%Z6OL?OUj|}pM9PR&tVilmx0gMb4C -4l#`lGU+C&M_u>bkODCc}7qP+d_LPKMEzpe`zVAQ?uDL%q7}7s+rthNqVuFdN}8hI7iE!Eic+QGV7ou -MhIt@mgj3df+V3!IcaPCV-|_w5Ne2^flX|ms!DB@*~snR8R??KYW2%(9)YxOF_~3~)F$0O)!&{XJAQ?3;nkk`?&NKYc=dhN9Um&G|;0r(4m=}Czql_e8uACMYoA(v9(vAwPpr8Uuq9mfZ{$=vXXu^8~yd58_)^p!?8H -%g5z&um78x{0v+m>zQn%342m_ZjXX5rmW3JOvd~GfOgIwp0`M$)dzIeF?DjP1p58eveYB@)HZ3ziERX -<6HTH_Q4|oz}PwzByIAWN=5zk=d~jp1pYVNxjpD{nqjZnDf%GQ%Y!g{a9YV?hA -qM)9j6xb7*;#)jQzy^gH`qLsI&YTcKpt5jrE;NoV%BWgG|kQC7dgoS28+R!XoIX6BY>Z$nMXSQGrtLD -b_4BDgzT8`7ljl-e9H&6{VyfF{s0eJ4fZmmZ13PWn*}d07`wQ-v_87fWjT7)wGn@_#O?XoKmR~ZUzO{>We6QD|`?iW4DlN2Cw?dvq8 -F$+jzvX;AT&cz2New{oAfrlj;j*K}hu}s}_sdq-}F$R^wBw6N!tmwvijKM)dnKMk*y1O+MiiWDp*p+*#K~U83!W*S&xcy=>u;hd)W9!vWF8{ -4;h8VJo>5DbogNYVjf4#(~ri8P)wXivU`Ei3>}+QmEYREHQhL(^|)Ic-%^ElGg@1!2Jmo^Ty@&|1a2p -YmW-zU#|y*^J)Kvx9(Rc8dcZ-)mE&Nj5Z6ckDKvCuEQ*D44SK<{JH%G -t8$%}CeeCmQ_BLBK}9bl(yufvt}*N42@WmoCouN2zcKw6`aZcqZaOhpnm}R?ufmiMD|G#|Lp;hhJg%o -Nf5BiOX8)i^gZk${{VyVkK8q@6sL27Utmo9Mm}JYSa;Tu$IRFV~#avObybO^`oC|V2yk?f+F*g=6>me -)0WPU3rP4D!KnACh&*$?9xM}`HN5NB4c@Z=2Q6&_ZZ47&HX*bY&qf~cj5xJQp>;Y0DuvK86K?PJX(J%;o8#lro5<|~&nfW{@@T -HMXB<7IyvsJ+Ts2;b(sRYDui@o@DJpE2`Lf3vSaRXB~x_LMZvi)0wo=HydxlC>w!?G#TJ}`;i8&dBtE -Mij;`gL&wUnlhPD_(2OvDPqq0dNGxpl7qMD8^%V7AU|6u_LCadwQ55rr%gb1Am(0`cr??gRGstR5xIW -nN9|sV(fZ>SOrowjqx~h@p4|f#v-v)tuOO9yV5&1B^`J)|~FE=+`;M#cUa>mW4U@nABWz -=(7oB4u^ILUU#Nw%iQHpxqZ7Gx!WIc(LI1roT=77)_uQ{h_%pr?rcZ2#6%nh{Rf#of$;T?@P)bciP2xDTzgX{rbW -mpgmMi0&5Y7GGAHhR1rlAP$mFdRxM{RHb*UQ#S#=83UOaR+IzX%)J}xFKy}*=ae?}>LZJ1uh~Qx{Tb~ -sElU{!*kw>-58=_vwXr@nAT~N9)qdPDX~kp2pcsWtN|Wi6T`8OX3ixCj4(tFi^E(hf08uqJT-8qxy~m -DP$y;+dX>rj9!Xc|mPAqQBmJ{FG!h`H09O_riq+d9cj?q~doyMXga?Gq%ImGgY(4cr@gR5WcP#2)MDI -BtR>A#iZVd#E6obILfmooyw?J1TG@{5_6(}p>_ghTMZvnbjOTFp8URF8!G{f!q!{Vd9sgV@ITqJyq`jbqCa^kBYv89_3upqaNr{k8sHJ4?T*q9znZ~W4n694%V)tMNxG^kQ?=K -l9^r8k=pae8^CmbRa7B)EF%w!$J;Bj8(e0Ihl1s!X7v+@6b@w;McGxT)jod>GXDS{V0ZV@dDR?Om-OM -h^!(U$>}Xr%u(OMr_OtV24&t4&ig*iG(OkB#!vDkZF+3DkUg)Ut21f#pz@=y5@ZM3Y?z;+5bMP!2o`T -cF8{}<;^E5HdB^J)xd{ub%S2Gd}9st`#=h@J2Nb!Z{=B2@AK}Ys -EH54zkitR@qBe@{)~pt)qJ7bB_*!R+D9X5UlGKURi2Y?zY#g^scF`t6h84%4t0v^pCGFFLFLOkv;>Tw -SzVg!8x-yE@%n>*|~!j+SkFjD11HjN8kkdKNxR6`WpeiBTYPsl4F)m*T`nxl@RWG6k5@8mhCJ}P3|mB -@cZOX1^E-fHL|W=vc-S2gyV){>?*L_XxuD+WRcJEjdRcJ(+VHz2|}A=f5JpLZf&%K6m99!3PXKAfO#z -p_d@c6@LrGa0f;Mrk8=3UI@X@nmfl`rYFlP%uP{TgZOhDZr$vsj!e31l9=>T5mO+{1PNXA*`fUp8K^; -c!L>S!Ig4DLLWyIk14E&yg-(&Fm4g7Y)Zx{Spu@B|<_bKHM^ue3Lo5K4vyidcsAKv}&9)R}%yc6(Fz< -U_p!(l7h#yCsAC6OEr4LeQYHvm8WZqz>cb@kmH4`K_)e(SwAVxWx|jR*`!5;iPeI~?9tI&92A;piM>U -pTXG80y`y0qY%l7e;#O2G-x?oSfC?qzLC^utJ_krP3vFW{9~=V*K%;Z^xkdg0n=IUiww8;_E`Zk@_!Yx%0sP$XtAw8yes^qOb2AmjdMdp0;hhi -fTzKcg+W~I}yzTI|!<&aU4{s~HtryRYK1SQ%*$Thi@Oym2n7OI2K&!{h1q)w14`bsln!C{rWpTVr`IZ -Imy@&o&UV85zfK2OB8#yI-UJCE|@@sFCpP>+mrX?>14v!5yGd2(z4TS5xF%OS?vE`?U2++QmK`cg^{L -^7H=`FP;!yOK-ikF~(w*cOTMMCVTI~z6wuc<>Z9-H<&J0tS{QL}*WbdYi^wuTJf**4Q9VYejTMIZY18l6LP>UFN6Wf!Sf-Tz8ry`eS-l7p#Dkb -bSF>8f+f#$MLm$L(Bpn)&1&shqx(0zt>xaehWSMm|Iv=Cu7#q`=k7i)-qB%x+$EBm@d><)x4T*F7$^s -fhH%~kn;-jDA{SS+{T1jXbVMo5weRLD<-;mfkwzb5L6+eyw<=z)T!27TZw?_6bciD=K;!Jt_22PwxU& -iQ-8`yEV0{Tx+vEy>|;EQO@vD4WvC65i3pg*qMxxq(k**&de4(KqG#Sws>-hsKLzK*LN111JlP^);EO -tVFMrD$GYMt8ZAhj*j@lA&Q%#+GeYk$3@*7GtuC -d{xCEBx3#ic`rWQr<3M}pa{%oOVMIY|L@W+J@&!Z)2fKuW7FXAniA7PjH@rI=(}M-8=-=OCYkK=q6G) -J)5$SCZ$2Q~NEWU8inmY{S}ZU79A1qiVF;t*&X11iLE+v -UM((8VFAd3oA2NBb@t7ORfK?bbYuD!?cxsBKpo4w#zW_8azreSJ|~r4M5))Ca}Jh}_O^l6-^p={U2imYCNKCH+{ey_*aKaNo9QnA26;Ns -PNC!GvW?<2c?aO;Wn@dhK+9L2(9v?n3y7B4^!{k_{N$E^BK5gSm_PXSKp2yKV~$de=tn7i^a;$r2Z5A -j0m7gmoA2U-u*_I?KcFvEusSlvis`6^nL)kd#$%eMOv}gH&__n3=`kCAGTT)i54f|SZdmjfYJj>>9=R -Zm9}8jy5cQ-yGAxb9dm`M{(M0tn%$trI0k?0wKsrb5>jf8G6Juvv*mS*q(rHj>;$0(B4(XtOV%Les@p -2~U7$_Wv-52meW86(c<8Gw54DED{pSEc90-kXaQGV3o>^s0Nfr@#xJbP64XYt`RQ637o$A|)>zKe&Vk -kGF8z6oDp>5KN9)^4E{5P%a7Rsf}R$DiOXDyI9dczUc4 -in+VHSdn_e|LxW1MlFJs8i;JJaBz`uFrvbc^|m)JghamFH4RELEpsI{e};c#qSK7qcs($2nx|LpUKpp4`T -7#HBJZ>!=Q(kPJb3xlxCiH-z1%d<&lnqEKsUa&8rq=6L0V=omKr5S(W;qr6oAeLTL2}^HWP5lKT69Fh -a-;P$YzrXCbw!SECC$aCViZQxtE^A@u>6b9xV6|WvJKjhI!!_d -L?k>=o^>Qif=jJ?=Rp6+Qhx6|_O|aW#*57&utdlSTa%-KEMMwqQ0$(m5baheODRJnXv%J1oV6@72_M#Pp9)!5mR`vMc1?t06_KAf+UK%KZNM)L6R -$mJ**07eMXh(SQNb2iC>TB5eGPq6QD7V4CZUR_CJa(TSW`%xrM0Z1#%jTn5L-A0lDX{+pPRVcBt|TI% -16AxYb~gO66KpDwb+CgL?D#c^jDg*luonx_-obD)3ol58*R$}#WO#3R7W=a-j=*xgM2vGo+#Y<8Dyko -oc16WyrP&B8-sr`{()dXdc)vPGQu9J5#W~m*aZD;y$|GmV4ZBWW@9$6E|bq*?_zkDXulXy@ -w?*3$dGr=fS`nt%&A4^o$eaqmkdM4SQ!KYJ$i-$ZV8xHLUKLpgr50qXOk$4| -FTm(QBF+vT$sDR&Rv6kFH9UP`tepnrWj#TS~MHu%EeV!InA@m?ONUc1St-9FM!&=*0>mVXMI2P6i&DNS7@e~100g6+jHDD9k9WhbxD-RSD+;pIh|lq27Gr -6iD7_TNaiYwYfCZpUBnf(RS&3@ho$V|gv7o6VQpOJ~>r)B`9aXvqzSpBi(gTp#;GCXcgcKYMnuCuPr9 -+4C{>#2enX8N$I<-VO0VK1!|Yln_R{I)&CRP;E%Bbw!1r6+k&!Vd#UOp|^z`#sP&<+cjoHyT*)he`<{5p -Jw2e)lr!ih?O$6i1Uj&#lp%;F&6?ts2fr<4j_2^2z<(6{ItgZnEE!we+%C&>>Ksu_&=wT%y9zW&F8L^ -=E*UuKKW$Fu<`=71V~ufFk0deyIC;)J%}N!rFTa%g(EMA%lRI?*t!4QBvCvwAZ%Jsq3g -26W*4g-8BeYJ#7VJaZfEUg!7Y-rud4+>Vz4WR)<{!}>LS(EJXOb|IWSF5L{a27k+bsraw!-@R5Q>pwA -?`&n_qlW(R}$-m2M(bI&{|{0Eyil$0d%Agm&JQWTh^?T{3CB{|d;*1!hKc=!xXkR -wm7Siv=JYhuY8TGcs@5SoUQ&Lyd -7N#{r1_H3PvM)n2W{{WZIjsE53OeU)FD+VUwfUU8byYbn<#u39TuZot;oHot)Fyi3Z*o8?9UDn|iaS4 -rcH2{o~03j0wVe~I;mwI_@Vw|b>aHwmpGFbE)!*0rdh;!1JEl$?YZTj22!lz}w*B;sFM1?C0t?F_)Wv -NMGD%@((X{=#zM(uC?)eB20|Xa_Qe%SIUK4Mq#Ze0@L$qaX_jFD@Hatw??ks}o=xG1BrdESkqhfu@9x -@3?H6H5A!2Zs>%3oE!S^4ZeuX%;r7Z8jsg -oNHV+l+Jo)<#&RG?2H^BiNVb`dMM$dEdI%}TUOvq?goHgA -x*S~v>hL42QU|T0VO~32m@x70FPxW(&Vp`e1)&A6|dGmtx)(2SdThMFB&A+;#=u+0~Z(mvHSw#lKKRR -QYfNtY-lF?qz*r&>r>|BJ`$m+paS@CC)WE^$(-=(V0s|O@09x5$N+8lRfE2Ak)kHSX?b(%X_<}jmZ -G5c(pAf7Uy7lPkcQae2-sawS>_KuJ(M3DBtgKF}NJU+@&=8(OU^CXJ(S|2BD$gv7`@>dnlDV|XR=I%Y@6*dL>nCL|B+i -Wy$og?1?Qjc-3Y29?!7dztl&|H#eJ2;@NjL;TBWz3k%u5os#@;cxZhF{?(~&_Uje?_qmjTX~nwI7Z_! -;I2%;(bx9D6dhv@FX4quSs?w!F&Yr=mtSj@KQxJ*Ub<@XCC6x{UUa$4JG$G2;yI<%^s1AXp~|SCXP@p -Q$(nre0h?2#rk7p^jbVRr<^IVj&zi%1@J&0e8mp!m6X16OzR&*~E;S3-vlyPGO%8UV<@afL?nRnG*Y3 -k>H|XMhDaEVrG5w|nl9oKUG*TU&svjJ(=zr0neffeyb9c{K<&i=tvn_lhJe$v&MO*V(lk^=@}UDp>-FUH?RIZ;H7(mv -%9nsmbn6`RoET%5{ouJoqVa~6zvOG(}Y$p?ug{&LOOYb4GGKaj3}XbI4B;6uyd9T -xw89jxPoCO5R^lA;hs?zIZ()eo6lyVn{R09-_Y615|g(Ar_F2k&Tp$3Xsh~KzI_3QC(j4G>Mg>NsF(I -+=;sq|AYrVt=oL$q!x?eAx`dtkn4@4gRQ@+y_UY=3QJ=V9fDE@(V+~QK1WnaCke3#Zfx%lQ@SsW17Oo -mt88n5%2(uEn4R*`Ot>yJb^!rFbyTEHHKpMr~dVSbN62eXq4u(#@stZRv6D3rZnS>ErEKwzdxKnxnGY -Y!~QI328`wxpfIv}RXM0e5ALOVSJ*%uZ3-z+0SnuJ~ZA)tk@e2tYJ$F1vyCegakEL|=fayq-^5g6$x` -dP8Yn3V(AKP8Mc8-S}c7Du;U34`3=VuK`vZo+z4p=P6<;@n{N8n43A=`9xlPi5^`z}kVW0d`f&>J#oe -djYjey!j%heS!|twpy4^a+GpmLHr9d{_!P5%>{^>dmz~S0^$tF?t0OWBh@lYE3E?Dx`3GcigU1tAwoD -*2~Ss*W78Gw6P7Mk9>Ga#K9?zF>Fwy`vfUMJ4tGV%h&F-a8vjE2^(;mGINo#gIb<>4JdI1SItFKAO?P -z)yIdI8QN$R63vVXafC}{NRMX8u=>KW&O8}y*zP>M;;*y|Qp;?ZKOW`uCGt3My2&j-Kih|~XD1)MG!i --|6U_gqarvElAO-n6X{$-0=sR?TCrKXjsxurRXB_$~(dB5|!&+`lef?5CG_x;}Q>wz=(K6gFm+;i7+p -L;*6i}V1@9@c^%gu8?}L>AeO1@aLdo9=cwy53VE$s)!+c$EorAnR9|EWPzvA?%ylI0>nD`5dIRl89=QoORT -o<&^qgOGubB{4SdJEv&4d3b}A0lJzMQQ@J>6SzLmA?1reB$FREeyj+U}?L@||xP?W5h};kZ%q?9uAfW -zK{{9O2>T*f6$z`-v5b7fntyS330^^Y|!UZvL>I>h15IjI@Pxc;(r-f@Hg%FFlb<-~teF(Fp7j6|{%) -KVNjSUu6(9+iCveMp(wOEPMDk{R{>gll(9k;JS3mS7HjZ6fN1={xFt0iySj+YR>1LE(0N&2jM4&~F-p=FkG1WYE0koOlX{?=|K=r!Z)Bf(N&f!(rQ3ccK1mK1YY%3tTybaL$XePo_L+EQwZmuPS6A3m -zOkaw&&0>SqCO^~N!EKk%|2wAm61v56kVL1NN88G5zN%4% -%y1l!0h;*+AA=FSPWSh72wX%#~6wP#vYI9qqjH+p;L8=O6gYz(P&*dl+ALJy|GE04ST4>@f@?TWnmt^P$>M>X?9m?UT3QOzX%@V?(yvQp~SBt36q=gY** -=oq2f{s4ONb(UPQ@JX~#O5@3$7!d+cWZ2=wFGB4;o{G(SdGGFZu?&Dy*unFx{Kpwao=CTyRVbg*jY)_{8B$hmW<`YpJ?^)mllR -+ER!}Z_n%ByByAGrKfPy-E%zj6n0v(K|bVh|1j^i{wK)hPPUfUk6%Alc$``HS&vx1K|W)*drGsiYeu1 -C%5!Jt_TpTqKH|{nkHi(I`Z9}KVJm2;n~eEG)l!6_S_&kI@z@025_Ma)=b%Q4a(6Bar(bF->zAPTo7HmR!Vj>UV#87Vt~ih1;)rnh$zxy02t|P2g}@{O%OL>+t*R8Tw7tf8|lMr -a4CQBVB$i*}CcmM$3rdu^>;<{-hRGL;tg>zWzs~CFy_Ej`|-sL;o{aeAm;|WA0s&_GkaglJ*DWV!g$I --eV3NsJ3&U{0y`|o9bzQe9C0)&qksBLGbcTLZ7Qdm97Y%Hu!H+vOUPxmN3;I5D)bP-$KkzZK4rM1hivNLtB<*&Lc_neAmJks-N<~!Z*dEsGqRNY9B1a7X=85#iPhaSn@oIIt$APkD_)$^ -#idWdQlaIE)){BTa>cc%yb{Bb9rS`nd}sgr+kG5V4I@B!M%RC%P-vQNdoYwbT5fV@mZSm7D9uwR8C2f -YO7$eM6_|?)|wm2Li^TAQ5>iX#d|$U7CB=_Ai`p~z5JBW`yi5}_u0#8Ik%NL`dOOF?$)g$CmKb%Z4(- -w9j@0zD3w(7)=@PjE0C#2(XUYVoUpHX!m)7eWxe~P -p=1LD$2?xH3(y)R*pa1Dv_*=m6FiFYgvLm)P-u)G$*CnS&=`q^)@8S7)CAhsT@?W`?k-09&FxYoX^NI -XQ*_E2QEiQ{DgP2jvbLetW1=Ndl1j18|A>CiY-0Tnz7snv&vYY73ojKur=_o+`U|kn3d;1ThjFwLB|83bZ`mDOw)U3_^(`l1p^d@} -w4S1)@;%oNx@`pymk^Y97HKBCo$xUlYsQ-2^#RqRl}6*%W;77OI}dYlMgwEmu@MpNn0-2qh_bMCnT0m -y|p+u`fYAf+Y0-7$AZcmJXr!&u@q;2O+JA(DD>3?WspG-fQc&xr1aeO8kMwCqig^B5IaZ+$Z}HYZa}J -2qil=sQFPSmb0v*`4Jq2=Ep7(?F&VdtZ07V4$Y5{5LxpBA{K7AA@nMuyICl8LAA;sFho-RxLw25uIiE -%3vSd={(v!(@~0RH3H^^!IixnUL^NFHu|of|t3azZ$8%ACtwI8&c4`+>R~SUY7htcnl7DRluV^+{Xo; -G*lofN!RUhGfxLV4ka4RYl6~a2PCIS@J8O=Vg3&oG?sQ_*Cdv$^eL)yUM6g$G3PC*|a=Th -USbhz8SVs~BepQC!#3;$88e)G19?Nxjlw+kByHfSr#NdW%zh1L9b>VjN~&aa7cW15!(fV79rxD8&nHP -anzZYUySbxm2{Dtd`QY?ig(ASwHo2g?qy73hiVY^7D4yKF^(k;NzS*H`o|TqrWG=qlA{i6SmaQK3sj+$d{$@WN*L%LJJpmI=CrWrb%1r(YTz?n;fWeTq3{nyl -*?zs$(SKW}8u5L_Y9FEz6M1Pcj@mdUyv9NV5)T$(phT2ROxB7L4+DNc5lR3si{-!~~VwBnXP`HBbZA+ -tPKXk%ti>>{z;9Y49M!aaWSFBLBFldti6I$({yX-S7B{-vWT?zO$Xz`jE9?gC|H$bNK+HP&4m;G^F^b -CmurfkV6%7c*Obs&~4yv|1BXXi#&rTDfiF0iXS5&uFuIbR$?4wroN(&5C<$U23uLSs>2Xa6C|4UTN{N -c}N)As;my{Pj!}D+S@9uQkX;bFt%K>R(yHur|s7w-3jN=(SZyX8Pgi%ZFv^SqzCvMQx!I7<7x`2mr(a&vdF^i8+6rF;!8)~(*K;#2Swga& -1zf6DQ;zXrd6Z)v~ida~D~NuZ(&m=JqjeAIq&atqsCPsz6eirszCjkK_Beo8uRuw>S;LT>a}s7K=!uo -cNdqk(fAN-SrruENDloIhIWQTQDCtbVRemQjVXY0|1C40Di>|A`3VgmAn6R#v!`j5%51f&)8EHO$rn1 -vl#?v)s3EY|D6wZlr0xKVBlFaF?;7&^qgw)pCZ?4_*WghzQ^BqF^+Y_L1-`yV-=8dB)A9vcz(}5SMq% -=wIFwr!?TC)Au*oH{zR<7q%2JN}S|VPpz%zY!@_b&%JM#(Uldt^Ib-_<~W|Mu+&JAD!whd=vFf8yd(( -+3(s~eJFAq}E(lJd4LW*o)zxwFQ8}LHLJ7W{X<>VOA+S&NxKcVK9(O-@P=tKz_QG@dWwxefjl9!5)g# -}DAlwr_D*UMNW5iD!e#YS^dtnKNF#{}zioY;xtlMcmYxbycq2>hf7e;#yuv{xV=V9((-R^&)?2@zDy` -SHobCY@P!UtG>8#Q8w_1N_jg*!Zbj`)`jwH_%v2S2Cq7|U;E6^KJAxjtg4ezbfzSlOK4q -oUfn8lOD-mttXfvC#j{vo8$aZJRL9aY6Xyx(mW>Y>{?v+r_z57mqi(5Zpv81b-fHOINPXla7&gGW*8c -uArYFq4E}A$XHs)>*rDD@fpE+Chjt9XS+04eBn~!Zu@NR?M_q#Qqt=1${NRSkFXXQTWQ<5T&!5Nw8mD -=AKtcTn9~E)s^U!IHg(ppxwW*v^k-C+;-gL9QV)7zgEd%*pr9n- -G!oGZ>*fA*=I?>?_(6JwFsD}9xswBF^+Y_=zBcS`S)&bO@nP@)Bnf_UC6zOBW=W$`uKapG=kZWeb?R> -US&sKs?X33Wc6=usx$KsPqn4VU$pjR`jWBI%6N2MTYVnYKT{=7V?Jbr)`MLx5wag;oNn1uaAFc}?<<5JwMMEtT9-jUb1`iY|r6x%AmU7sk}=UibcNOsMyxU1Hupi@CH#txRI;uy0rJHU*ttdvn7W@HW*lZr#l-zc=NSH>2 -i`r!uhyT-Mkp2A(6)gktPU=_FUa@*A4D0k_Oq4)%`u6^0|(o$S*dD%Lst^W!Ae#RHJ>VLi++qO|8Je}jg=h22E;TiqDw-^OQR-K+nh*@4ekCh``jxD)=vSH&3`SRzh^{7yo -U&4>tI5iwt|n_5bv0SDv&my3x}Pj`Khx3uX~dslg8}$7&Bm>Lq7Y4Shp(y?wxm -^2vBCLlf(ea)sf{;y*k{-H)P>JZ+9Px=iyRz|K1j5Bg>mNXUy)}RB^4s%4Y3Z&K^Y*uv1~0tL^7Zd@1 -a1yClq(wsXStv>g{_TiX}H^s?=P8D!fbURAcu!t}PiFU%gcH-*{Lwpy5K+l#^sustoz4mPVWwYEZG8f -=eeq7^anX+~SNaEi1|66OHgIAIR7jSyy>El!xjYy*Tj!e$iaXq#4;<7{eSrr3H2bCOLZ%ygTVFtcr*! -Zh35g_&n#!klTV&X7@;c>x6YVtSf|d -6|Daf)-|xs6;|xyu+7Y{A1p96!+tD4s<1*IW*a4}=yGjw!ioxGi&9X=nF2PA@W4?Aww}U@qr7aLg!Lq ->_X_J7p+gr|oH1-;!U}b$?HBCW*mzmcFRbFwfz!fTDd-p0DnY-nRtx%tRa~q0zObUU+tvvyjykoi5LS -E?Yx`Haj52OFvK0vre8Oj&A*^j-%@kHISSJW8mX>WJgjEG=tgw2++E-Y6z^V~e+(2jRDXeN(I|*w5to -I757Sm^|w0PE=~GRgz-c361C!MaOWamBW6v#^eUb-l2ThIOs5j)V0Dk%-sWI -!CCRM_QZc-Nca4v%d`M};DVddm*;)pQa2-yB79G4 -?blN8mLkV%CZ*)5cbg=;T%Rml+UniXWtR(*iZZl<<6_Sf -q@3vTWu}Urmz1uUg%M8iIsCQc`yG)c^BK2;|WtZWS%K*LGD%mAMxJ(pIn`Z{|Fti!#esr- -@I9j#Gqt^1<2US@YAbz0X7_j2oYpRxh^?X#R{>f=>1raE|xx21(REgZMFMR{8T)#4=D;4wWg{_^s8wi -G&U*H1AcrooUm{|aAFSL$zw-H$`bd3JIW^nOa3)M)se4IO70&dm3LaBsek)O&G>P-gaMYQ%bm1`SvNbS(20ZccQUEbT`U?A7cSsi$i(^2G -V?E6eS)){N#2Qs_RJF%bt0lxcI7SrL-B@Q;>u=2N>wtU^kk^+@zt{HSuY`Gx{)Tzd?xw)JaJPF!`#4+ -REyzoU(^J4O8U<1ol@7V!3)lGg;4vzTYkYh~6K+w7o7}1s<3X9&x -)XE4Ie}Gh62r`k@s>h}Zv5TQYLu{(u0+LAc4gFP1YyFLBcS)N&oJ+ij@W-VW -mqK?gt|H8zcDF5JhBCFgm?x%oG=B5~5@Q!BJ48H+(VM1H)%*yzj-=|#rUmJ*nms7_7K!l*HqT{(3AXA -yW#5MF2c#u$-;mG3!gHIetENXNm7Z6XFPosNw#;{1i!mP6 -tb6KF^p+jdBN$(WX66UZ5HI-|L5@R*h!#p3(mSauuAm}X%`xm;Wh{3QAVkP -@|2kWxO1%M*!u_k%ty*xy#!D^VCyO0C;Xi<_+{G;4UlghtvMw08$CTdTny-B|Y_xXnc@4w%nj@6X;kw ->3-SLsy?YG&wrdWsh}dS4Q%w-;>kaS~l_-D=TsJ-q8z_#bQ=b?szrnFVA5fs$5Ol&SshJcr~d&qdde( -zMOQ#bo=F`XV*EI@5B8exSA9vY}LD(6qf?x4DZR#g=emeYO%w9?O{*bvstzGla^%Fxt}!u8)?;vT6-T -@Ywss5WcK?>o#p#UefF0OaxLb{1*P4N$(!hEDhA0{E{2HW`x6(G-V=}gM -?x5^&N?ScZ;I4Sm+WNe+Wfrvfijz?F5KDDjvQO-l3gsGg=MaV+g+QVw|rrx$?=ZG_m>_%Q&{S>6+7O{x{AJ(9=g^zxjSLjg)l4J?WDVy(?s{(bZ6)9b-T&7gKWEWXDlsn2iIZ$y}C2zX4 -vw@bDXb#V=Wtn^Wy4O-vi?4%7NaVE%xB_M@utY#dZDcc25?6{j9tVEXBL6i)1_Na%2>F@vhGhQU7l|Y -MQcy62uWqCdeb0Pw*1K2Ly)*E)zIkV9bjkfFOooEI}s0Y=XrEs|Y?M*hg@apw0J;btdRdpd%PaFqXhf -@Fc+t1RDvC64(gXMM|H*NDxObn!rp@M6j6PC4#pIHWGYJaEhRcpe4<-ya>7x_z^@B3?)b*c#Pl~f;R| -u5}YFVk-)W_u?Gpd5d;tz31SEm2qqDj32@cMa|EvvY$y1N;5Mkco1I{^`wjls$aFo7w?=Y!&R!gv2otq>EJ5}ug3lR?$jXVzOEbsiWKA&T<}#MSa@hoy#d4U5{( -7?%mQH_+sko8l5lo;z3dV-BVXPnP#k3A~Ulzm$vM7}${Q+NT|kk=`;X1Sp>-Qm9P}{%OJ|nc}6f$K-HXFVm(+Na!sey9unMx6il$=LnKE8h-SGSh8$${Syl+G}@TypF_5{I({*HEwj -E99a+9cnur3Qh{&8loVjUVJ}lTa@?~QKI8zF5Zp{-~sTvwZ8f>&0j3P>4L)B*RzK53;GNBK}u8pVt#@ -4{IW$!A>C;dK8IpiL>dj%9HlPSrl_PB@E7n;5~-S4p1{11EK$-MN=_-oTk-d0!M82%EXvI!V=UF@EZH -Be;4~qP4X0aIj(`nPnjkPsagdqYxA(pd^3!lS8FDL^CTd)cNHxXY22A3JCx6z31T9P*TjjMSj%|?RBas1 -F7~%Jx(^gr-_&<(K}@emmKk~U?Igxsly9p3{|ktC@y>NK_;fyhxQsphRS6`7BbSwto -l`Dc=e1)9-h#)`U;Bl>Cdzo0EawEaq}5W#%tPeq)oJGvT3w(%4b+@2w5Z!YtpxAo;rQz?78pGU-mNXBStuCQaR&HE+@Ku2vqNt=rt)_MUt1^J>@r{tgd3*iq -G~a~JQf-MaU9=wY9py?Xai`}+9@1O{oe!8*MmBs9#JJbproDRttcw8>M_GcvQXr{?6EEz_pw&3J6o=r -LoH#x?H$_{>?e^Z%#m|DVqPKjwdU--yVle*FhT$2>AHcF^FsAw%Pb4NpiMG4fG~|Bd_qU%`Jj8aB0%T -f_&*?kd}#bFkw|WOv!V$ie=UgMG1seTjqpUk>)C9qhO!S+Q?$uw(VTnUtQ`n}=rr{po1rg0V}kSSe#P -dX)ZpGOTe(e=57n4Ec`<_ddeCkL* -NBIcZUV#NGqhTV=ij+A${ScOU#IASa~fnlk>ANh|LYbv$l!x*vBQ0pS$tKg{MrQoCBpyaLOE9Eo8p0| ->(lBbdn(n%Jx+Z-|Xk#OiIu&sT^N(VyZI=Cf@QGKRZ?ND5YQ7FgvSTSd*jT0|$b9_%0v`G0W`6y{C=_ -%KhyXeOLC4BCOK1BDIcVdbde^~LOMu;;>|!Bh>y64!{U?8 -mYkGikw(9qG+`Sy$&!;RyCs@ZGEMT!FpFf1&B_!G@fO)kHX|j?@}@>mQqZfXzH7lm&k@w$zXN5{QOMSfMPMqX40fd?(R+Vva_dWXWx?ke|0zV2i>^P3JfBE-TX}LsM0c31CuS86DFB3ZyYbx_vEy6Q;N!*rOL`SWvWbhrU@2v^7wQJ{qRg!;Fn4TuS(8 -TC1+Eh6bg@8KmyU*Axw@bIYpJ4la)cCMReAslf<)_tnrk$UCdNdEvB3qX_*sMsUq`R{Buoavx7IwHK! -2jDVFT?v8cS%HLv$(L^fdD%uqxM_oa5kp2g%j)K~N?8H$bm~vm|Sd>C_3EViIh0^q1K_d|1C;+FR4@ --le-LS;idPuaj~Ecy#a7o#;wW&zdgEvdb;(ZXizMJZPX?b0%6cOqu3fRS(lNsx@h;DpN+bdB#Jx&u6? -zr3hUzbaD_oML+}-GE5m+)Q@y#U8yJSOyirbVui8pyYviTfBnHGutTSeVhlDkJ~IBbUYlZ!QO5^99kB -Yb`_`U4hc&tg-Dva?xuX-PKxd4megvIG?&#dKjO=t%Qo7uZj2>!AHzntqM)w~c9WT6;7N&Q03R;-;eH -c6BOU?15hH=67IxvsQrt)6pWvt_0TF?LZ{l?qIpQF0H|K-lN_y4)e?ft*-uIoR~!9TE@kwtf_>wl9B;`=hJ5F?ejTnx_!B|%D%n-mpQjj|Fvni_fILgJ^lepZlC|<)ph;%)boF -LZ7m;vSkpKkhrVWHwsZEN>=^c4l+y6+e68Eh3mNf2Xu@Z8+AiJ5W=!?J1-V5gA-(2t3=xEj+w -xX_w-W|31ts!Jw?POd4RZgU$m?$=PDl58E&7(gvS2iMAXnnPD@s5%bA<(h$y$i12kKg0TrwOd4ESvf+ -`)N?n!lV{N${aUGsHNg$OHl{bN=%oLXj2q>|&;7a4(F44J>rty}E6HPgB#8wE#k1Pt*MC7EIiD~KaR6 -YWiV3&cWBTzU5D9sP3?x=zMElsjkMEa)gj&PLL3zte-~C!_p>9kufd1wT&T$%n_MU5ey2+Hyo|5Y}JQ7)sNF@%U%O=4d9udcw2B{2=iiN#7Ng$P3T8e0CXpYQ|N;QF`iTVVoo=A0?Lc -itkPHdtObHPK0cEgp@lkv^U>1V<^j!BX1M5lh~mfT4|t*<3DmB!FCS`@hrHAzAhkJO?vQwFC>B^cvey -HbO2)XKOFjEE)l1WSpd-jXE9I3+gRrT+8$Kk#7gHRyZe=6Bm|i^O|rt8k{6k|s-jJR6SdbgOe6f9Jmo -(SM%*FCNGfzMWTO$3-@wGs0dRT&aok25@*G^f@>d4f-5+*=#18&1Jt94u1E_abbGM>3Yg;*#FZ;2Do> -U-2>(KaQQt#evgym43*vEWiwGuf25puy6m38nAjZwJqq>=<;b`;k;50rIM0#ICuILZ*}q6m2bTe3h5I -G>{bd=SHFAEh$?ltE{63fczn1;Ino7J-c&6~&(PV$py)pkk8@@6B|H+5TkMDFNyA$}|{`}E?3Fmr`i~ -K0@<32O8p$CnEZw1Y7m(!g+Dz~TGIlkBLPaBrp;f2($zN1ZsjF1S$eAf|dk~p!#bgyG&psI -8Shf;3UB@f};e72o4bJA=pV!O0b3C1A=t~s|c3L;TIFkBPbv+6QmQ2BN#>yM=+4UNYIl&MbON(R>r;P --IKtbfDu%GWn`5EHiBaWdk8)xSVORwpnxEqAcbHw!7ze>1d#*=f&hY^1l|N42-*^~B&hk4(j+)ZaOg` -Z{7y1IBv?oA3_%e=Ho<6uVFUvSv;>|6HAjt-eEjD{+Tc@q&G@&zJF#1F#kl~=135i**vLj4As$m)b+w -WG*FT<(K9awx(dWMbx&MZzac!gI-?+B&e-`h*^ZEZ>2IS{|xhUZJc_(LA_che>jeBCo6SC@=y?Rpm;{IVH?t2<>Kh}u5tpRt8{i?~`y|WXu5%b -Q=n;|Q`ofr;XQr^9~Ik9!_weO|9o!BI|+V>s-SNMUNgZ#jC^4?AF?(}kn;?n!{>3FvtJaFKE -c*m53xzo=TUXv>Pr_Zc(zf6(&F8O`>LHBCJPfe|n-t!Uuz`@kigS+MT8p$7LSk>U2(yt+ZJQOBhN?&? -Uzb?WbIB;;6Y9TEr6T-6&VL`it-3~dBRui9e0en&-h%%~!X|kmyf{bUnB*1RXi#+*6kIKa%r+fB+U78_VM3#QfKYGFnsv%ZK(qT-Xt?=O3c*3oNq|tvrD>f8#O$M<@43sHVj94A1y --q`JDf7{rY+)r1){hW_Rq!J)3sq%tZ!v8&JYDmBB;e6L@44d~$D`N%VB>+LihE_%O9v%|b##Sa^6ii; -j*Kyc;rP2unyvU?WG4WMjsRVY5^Ev56BWvdNPtvy6-kmXnhs+K*YYX0ewoVeHMvLfHGW^{jAW6k9MQf -<2QJ&R)zhvdwdJ?B4~^?1eew*`}r0Y}ZqdutTe-upXaq)^j^&zPmUJ+sRqrKF$oEb2j=j&LWO-_Q-M0 -hMeXsmtgog&PHA2Y{D7N#$V=a&YU@{q@;u`S+ayZ|NQf8*|KG9`SRs#)v8r&?b@}XeAlmE&o*q>AlkA -mTeh$xJJz!&e&TFHC1;<0`YGGHcQ5L215YeGWfzL>G*BsVRnvbUlNtm1802=`^9_z*UOPi0H_61IWA%Z~EHN_ -)!MQvA*oUrq5tDE>f-Ka%24r1*KfHJeZIms9+=DSj!%|AOM5cZlDO;u|UcqZHpv@fT72*D3yXihr2mA -EWrEDE@a8zntPK@3; -5VLX)LkEZyKQT*pA{zi&_)FD1=#p2j~RA)P}#mtAQZ4f(1l3&T98E^e4;lt -N`|&VKggtZE2n*QRoIeFMf-;!mac3n>1p6n`7VKkg8}?vo@vj!qE~eZ%{P3;hxs_;8P|-km#l ->u>iB?;8^x9T^!D5f&C6nbfOC_wHT2`}gmnq!1ZR-t-?$mdMB?pN9~je}9$i9}^o96%$6uM@58(^$kz -zi2z-@cJ3tmN5>k&W5OdMBPc%b>fO`FCp4)R$(Yy}<>`07*L_L7drK*JckZO>(jW0D{g{~Ou$ZI=?r! -Hr{uH2x9H3hg#SaTd3Cyk_-qS^G~GjtB)lX$414(3L-=f3jYcJq)1;QP>7*4+V|^s -_uY3>0K0!uP?IEMpD4;;5cxmYub)7ngWW&E!_7n6N*xsw8B1L5*HIAhKtEYL!gS%`el8wv>ae8Pq}b> -v8OvBjx}qZj`bP9|b!t9H_5iPBHAYCV831d6dSVI)Yk{^6QHwSO}g_cqbw8PS(yIUp -b+qSiktsc-mTeXFK!L1c|cLh8|xwfQGSAjn|-UCvF7@Q(;1agAZM{@@P@n6X9cR)dowV#D+#Ym7OjFF -gZ1TL;I66Ca};ypevS@FsVSe}q1|uD2vVlzt=$VsuoXuAa9;`ox;(a9urb7E||$t>?{oX@0dwLD(4@N -R<9Ppuw?R0Sl`)p1b`xMH$Zx9XeEuM`oM)+s7WS<%P0&X;ExxP6Yeli72-Ih1pEAhsG4t&JFmQ -vxIZfm?4kM=bn3xz4+pbY{iNd?B$nVW^2~05o3k--+y0>2S5J!V=-3vvh+3fB8@lJ(^z5W&YkS@&p#J -qgY)N3vvcRpvGeE8vxHY8xhjzA4C+g@l)X`DBTs(m~x*6 -2bJw+YetE@Hufcf%I*bu&lP31?}5`L1s%P&y>UFi^?I>b1d(PvX1XTAAh_^eb2cce)!=6nWw9&s;&r%!X5e{=W~4V?PfZ*4YPInjFdi!Z)-sjRFlzi;2Z{!A(-Y{9vYNn2! -q3(B>*46rL(g0S`G0ff!S$ndYOuKtao8N74++Y!GHQXLFH8XAqJ8}K8%1pfQ>?c*PP^bsfg_=g{UDDb -{;;R65u`|tUoLx=d`!-qvU#Gx?)+|N^-mG8d$?yOClHqD|TTU23T;o$7-?8rrn7RAxH8n|J5kk5Vh-R -ECYQu3&upC5SV6&V@X1L^)*{I6cU%4saKq)V4BfyBc_mo8o6rKP2IJ|8@Ikbm>dHv&&8AAvjUXU?4AC -r+FY@1RKm8uJK~WUlI`pMI()xw}DW+J5`(H~#a_KVSXw%P&9r?z`_!Q(nI!9MSnS)we*xVG-i~S^Q}{ -g}m$l{1pHV3ZP6B0R9Rcid}*Fv?xG0+kwj54vy}Czk>gc9Xl3KId&$QC;|=Jw{I8tLq3ilKQ7AP?Af# -89XO&+P#qTLkt0V0pl+Z%V4~bmcMxa){`~@flp)oBj&itp^CowE==}z{*TA3Zu-~7-|Jt={oVp<)|5~ -lK8{`-O{6G8bGroEAW=>_!Y0NC@F!+!1*t>VH0N}o7&mK`%jvYHD?4SW{0OSqz1Z4!=HvY)D={V -`$FKB>;Bt9Q+2pkOR;Hyup8z@hg`(kNk>r;}@Lk4|1+Kz`6e(qG2cJ4-*aDwsPM2W6l?yEUAqQSIV?gwL)+329gy|L(imo0$GO~oj&jZe{H& -fueOBnmvTb|4S2?sb3Rh0A?`Hi0|}=A-#FA~&>+|6-e{AkP3=l;QYWIJInkLxWuj -H;vpmM={z=35qrFh%U#HV`Ls=+*`U<>(8*o=>LEDe^9`hQso9L_1x8eEZ2`&GKXxK1Y$Jfu)^S6kG`t -|u)YLk{^g!2Vc`to^c5xj6>1kX>2pt^wi3>sdMX;>!HuoyH@IKM-j$EJ7CcSJ45e@T+hGLSVIVN#CsA!YI9mW{%S2W!I1Aj&S^?H3b-~ -jyQ&6{VZp*|hAqviMCYxvo+7xhVJw94DCPrrP@fOJYT-4%(R^d|G<$tk#u& -@w`n;Ix!XM$kWy=;18tV*G@bA^DS5RtdYGz7GN(Hr}q8wg%nd#7z`z`v9WU?K -P2l*Xx<;VVZ(;Es19@T<9T^`oazL}`y-D$BE|@xe)_492jC7|QAUbfDdhqBAbXhSU|ff?Kz%@+7WJ9R -!LdHSUavkM*t8Pkz!&dCc!Gn2pINqSnOFxw-f~^$1qB5>At8Zx>C%Ni^w2|`#?}HC;DNf~2%tfM`m_K -bQ%c%sTR&Sdo^L5OQvWQCG0;E1IV*y1dU_&1v411K_RB9vsJ!oZ{bA|SrM#%9=<1RsOZXE{Jn_q;k3P -!N)6)gtXkbCses4G~Hz&sLe7$c$VKuZG;k^k!{@aWI3{~$8e-BsDy*{ -2_W{PCN>Gchqy0P3_t!^00h%%@MEF3JIYhO)M!&;fjbzq00wxdBYn2h2_0pg{r9pv-$Q7sL1(at}OVLWV$_qikVJh49pt -{L9b5)8UkLn(?gc?XL7j<*MX1kPw{8_QfInC_r1~LfNKQ`X0|yQivbAQ-8i7A}4W7deyip -I({=gk$Q^*g-+NcMp1E>>77i}=%ev{HE^78UBQn|mfb0<#M5jvlycZxsqbsYaFd<9^>2z;n)IgKr_t6 -0J^uFFZ;L+gop;_5@xW{1la1Q8G1N!SB;2=?8*Unh-s4X7H -)8yT@ekU6s*j8Cj`{`@xS+gIXGu=QSRx}ML$s|_*G2z^wg&R8$RX+h>Ltnqa)L5Io9s9qf*t9ij8Gov -&xu}qQG<5V?GACb6r1_~Jm2f!?ges{{VM^zvKLu`kqCQOY}jYLs{=f`+ ->F&{gERb;O)kZ8~F<_yddORslx!!t*p&s+=O??jG{lFHuE(X7nlD8cRT)${i5R-#&K+OyD|Z+TD3~V2 -VDpc+JG-)3vDN8QE;d6>8pQH-fzJlxKRBP@=yJDkuv|f9WBb5r=q_@*}x6upy=dKW@w9`OMngvv;cR? -UUGM<)74i=JQ4dHS*ZhsR3;Z3|KdGItum6As^qm; -Lp#6g!pg%(22_9q43Era|l=WSd1K!c*qpd|dMso_G*C3kzZrla_klowKd1LbK*cQQGS+@Y6K_g_H*;FHr=8<`z*mxv4Qs*BDfsQ+eDvp> -fAzD#<#srL)(&oNZW@}~O{F?rjrNSjoWD?eTTboLWonm>)A;-q8rx2$F)+Si@FnO>&e&(5H5`Y87PMANSe?+^Ez6fnK#uK|w#f%BwGlHN4_oTT?v_76D9BlJViGaAVJvaU>aM4v+Q*JK*!COGcHK>1?~3|!Fv -Y+jZj*1)hHj5T>Bf56yFlHL>gHX)D4BwZVHJdj7|sG#eFo^a%T5p|D}Kl*W+?`O`MHEXh?zFQg3!o=A -6?b#7xEfMRvSVwfsAG%lQj}z<3qfyq;K-UKy6XelntJFRy`D6U!s4K0ne+Mpj*30^d)zhT)*s3d+_?( -~W;sAL}s3(tMvd+|BmdAQ}I*~uklaiM$TbAI62l9rT!^8u98`e~zU%;C1#^~V@76ve*R=f-79dwx(RrJ2kYz5HT_%G_d(Bu^-#zobWG4~6w3OgnG+;#@fk%cS;yvZ5c -rya01M@C0oScu{5hhVOeiPUs1+KD}OEUti-;C!lAt%6hVV83#N<9x30^1ShZ8t^dmW4D}FWen&lvqwW -rT`Ximuk?rpY9Sd}IMTEm_S=Z?B+|vFbf2-A+{L)J=B|tZX@i_2M=GDr+LEr&ipgn>g9_Vl1eyp}mG$ -fA>vesbxk1_VlnKLuz&Yhb9K0Ni*Q=;8NyNkXL?LTAz{D3SXZ%3WekLQl^AI^P6b)ud;ria^gWw*;8e -G1k&u%-(hV}3<-(Y}^mUl*p74a(xvC2_p+{84`8yRT~H(OfH!FU}r`>>KR=DSc-p|Ad5uvteOjV%-(G ->9n*oK7amvF~0*&N?V|`l_($Z=Bn*D|NP}C{G-Bfu@^v*$5&?b$c;+UcGt^r+P2uf#~a@mw| -4H##wySs8NFM#@3qamyOboD|;&`dF*?>{RjTUf82VB4lwA*Mif#mo3iew=!Xyva-*~t*q8X;d+*^$%zN%2Z;?OpMEg@%SU4H -!VND)=KH41U7RHPjBXrED7YK_7JW$qnF<(2VfmlP+n#^@H7v(93G_!fIXR+jz+P2lT@mF4UW0 -D5$w!VHdG$}`E%HY`)Ygdhzac!7G4Q+ZzROpvSRvMm(KceuNzp0(#k@uS$VcgaAxofF8H1zlDSAoZ02 -zah1M5$lHf>V&3I5f*?eyQt8YtQsw0$V&_3PI|{(c}{lrU-i%AfddSJfz<)E{y0P$AZZ}NORy#Ki|>~|3a -`4Z4>Gda8z_4JXKRC(Iu)f5lt@a}_nsm6j1+(6wSt4%{LC$oKWx(PE5qmF8eCR!M1KE{ -?eu=6;y>Vcw~wc_+p&7*hdnv|D&EhrxL9{b#3&G#sCb8`7E%))6pg1OHG?=!29!sGFaR65}n5_b}GOS -QBGi%s1XzULw-KoMQmtf$|$Ua-`66qYVQe&g^`dZ!8EA^GS@|F>c4a0duEWrv4(2_|u%PBfh-&;)`ND -h&>|EtD!6*FZIXbhsxHAasR9d_Axhz-2nbkzaZKz=;W|}KW;xv(el~VqYJ+$? -ZQL-Xnb9$FCv&1||L{pL`<5YRY~bwA&bSp*+x@I^qI5`f^A4M|h -?Dl|4yF3;iVWM}LHQ0@6miOnra8V|=vJz#Hj+7tj@fCiK}@PeNSGv(b0&+_|$(Ts*Uzz7_6Cz+7Ztw* -e(gQyDzq?!=lhmC93`_JkAHoNxlCc*A*X&J5>j;^Y9Fw&ucc3Mbxh)|x9*tEJWnC#|{3Aywj>wI*^%l -{jUsDKjMJ=9)6br_WI3Wu#~3hIF>%Wa@G!OfqF8=l04-n~;;0o0V$rH6bfQmzFnEE-8uBG<|>uJoMXuqcc$E7YW`9@ikWMgV97}{&ye5ACC4eeul<{$Aw2mMi1&gVuWn@=l$<{M<;xS>Y1Cmg}RNpqq>{gM{QKcsVAskQEyj&sjg8s@qN(uXW -yZIQ~aLwd&6&|-$B2Ven0zp_;>RU@gL`J@}KL!-2Ws0ulz6gU-kD0@CxW15Eu{^@JPU;0Z9Q<1M&lk0 -(Jyk2yhJ?8<-yWbl}dw&jODImIt~8-5)eGXj;&dLHmPl21RRVg1-CW%g-D|q{bO&@_>rU&g=zh^P(TC}y^;!C9`o;R^_1pDnhAhJz!*_-@AzmTfLzoy -~h%3U>pR2!D7x-5BKI2#DpB`{Npjn_-penFeU~phm;3I)!0!@M0ftv!i1nvsFFQ_Ewsi0?rUJQCU=(V -7?gEj$sfr0<-~r17?Ua`d7g70m}nk4tO`H^JyGXe_ -&ivt$~E(u&2$by;$c?3Nl^eXXVd(fVsi$OmI{T9SDEj4X4_iH+79@YeD44Ozyj3z-dT9c;9)y&WoYSw -B#)|}C(wB59E+63)rZH{)ncD?rD;Mm~A;HQG04?Yw8eQ-^%ldhT0LwB#PgRYD2A)T*Is|%r0AEbL!m! -wP4P1a@V%(@x6d|i?5Y2E9(cXXR{TXZ{hdv%9(U+KQpUDo}iyRKt;mA;!kRG+M$q|er$(O=O2sQ+EBG -ITXOYzQ|zYk0x1*-&b@V5l(sWLO%qBIKQr&qB_ITnrf*njShk^wZF9iKotC&BHvy9t;~AmK0_VTMOPA -85=?3(@Jeno77X)o77v?yVU`HWBiW#pYR_WkQ(rKz>@);f?|RaG#MHfZGiTp;4H$mm)@X%Utg+!*09n -L7BY)+`#MA&wk&L0*r#Dv!kD-~;sY`Q)OxCI&#PZnA5foHJNtI?P4J!M`<(AbzT146-(7ya{QCRN@O# -;BzyFW^zxsCwm=dr(fRpTY3XBPy8Mr1eKd3n9l%_&crO{~nX=iKaY4>W6YENmeXn)gwtlO_UqWea7N_ -Sotr%%x5=wHx(um4%!#PE#L8YlFZVZ^?CZ4`W5=!`d{_ -T;A(I;v^01c+8VqJ9So_4bVIhmY{)ZQG6aPbQ;Bv8Js;X8Y<}39unl290_SYT){!w&{gL{R+UPgL&+5 -0-?-jrGejoYm@VoBk?BCMAtA8K=zW%BHEBwEpR&01cPQd1XhXbPnKMC9y_-){oz~(`1g4zf9QW>WPWd -&J+W(O4p{VV9BpmRZ2gSu*bG)FbpHO^Xht(W!zZEtOm)<7jOP)K{KHcR`MHlIr6U)mMgW7?m!ErVO>@ -6q3{|62c;VX5I0L+21($T+l91=5`&dFm`*<{#mo<-gcJH}EvI9v7)}uV}7P9yc@*REyHIbG19P724Us --v$@xR_PY&Khn1`6ofn)`f%8&u&H71g&hXniy1SJk*Qv#-lOj4`-bmlz88Jn{T}wqr4}L8f2#jI0fhm -Ypw85Kjnz!ly{Z4+@KDI`km(`g!f>!}9>v}7d(iiY@7KO3d{6s+N3n1Cw({%f7wMPp_b+N!_W3>FALT -#R|1tm9{Ezyd_P;CO{s8ZQlz?dg?*)8K?Q6@x(7=9yMS*!ivx4RZy-TfUQ%!5lcIdqF=|}5R^ywsjGxbIK1xV+RG+#{#ofPUG)+1~{*rc#|Ve7(nhV2Qnf#+u=y}P^GTiru -FKs`xqRxegBSAVEJsy;>(dHHI6BPss^-&I7*8LHnZKcn9`zXHDne(SKSx{|TBxT{?4rS1{flj>1Gpf= -DDXbg-D91u7#FfM2qwYvv`4h0>hp6Dd?MdztEx=j616;WS9ZLzDyo!VnhOvD0%`=*%n&p~R)PJqhyro&M`9SlbW()OXJ2iW#H -#eWs^?7WO7h=Q>#1!^wV;DmrS;bKAgN!fU8ns}yGMIjTdl3pvS8O>_h4;^ -Iy4~E7&;i{?3_6(t^1A{r&#^@u9hQX3o6MoH=vOnKN^X?^q=m1wk;upRNl+oxuNjg -#Z1o7ygD#ST{`gcl!1V>I{MH7u>SwCl!t*S~huHJ -xpOh}b@_wl8b9{nu-_xnW)`K_SmWb;TCJLD;8G_INf!BFl$wlyEGi_M -R_T_Gn@c!gqz_x_FQoHFMb#RFF|5%<6|K#{vO>~bXF%=1QO=q_Cjdz}s%yc)7ZvOhiy+{8Au4fA(E5r4Z9iU`# -QFOft0l8N*JH(c>@qfEo^*>ePr6mQO(ut-UQd9;py?x6(`_&Q2 -#Ssr=lNV;2EzB}(JY{MiEIniLt3vU2;y&|blHHe%LHH|UT2D{oo*V5$SKaFZ;#+FfzmE1vhfsvg>*@j -Px)N+WDk95B=!NbEq59uvPY^j0XqC-JH2{1d&;DZQdV{cc60)?qZ8{Gi3uu97P(aS=bo00{jsM2kLzI -qn#fjqK{l@yssx_cAAwfJ0=K7Sfo0kzBIL#Y -lq0yXjKF<dpCm^{@DljS;#kmI{vf{XAEGAzt9N&ojiOhF;{A81#+?nqu74Z7F>?dDHcT0Ck3#9oA8n>NATOOHwB -9lJd_SH71Se+yQUFIQ|-DqHQ30l$;AX{a!2XahR?{hkUN-x?j2&4muJWIEohJ0#os_5O11XjZ7YYxzt -2kA>aJT=rBJdb3T0riAh6^pA~x2=RMbyz$=O>KRSR$bhW!$b=wo -$u#>({Y3(Y3&q*MIIqT9{U@M!ty3G$jKqkYhmOJMlSTK!EFD=E@3MgJ5gHhYjuj^ICT|(Z_;u6K^why -*4ntumCNelf80K3|eMJxdpv4}=t=~&yX>pCE7$s#s!zhN2XA-d@Aky0bJU-QiOLFTpJ0kCdsIV3dZ7`U>4dBwOoSG -f733@C$c+P9npnG2R_je-?Jh*tPGIG(Zf~T_g8Vl<5V^GVAm_e|QnV2g*)7(#(9~>38pHhR<`hY+^$B -GF`$Zsf<%UZQ!Umj@u2w(!*h@C+wv(+`S>Y>?LfV3v0Q7r!mDNKja^zX(oD`gj;VW)!GUw+QggNwW?B -x#V^^DliIecFqj+$y>G(CVs>h7o*U0IN=h?NByN`|aQWp_*YH<}TM9P)>7#79FBnE67;?5VjLht!kXA -0Um;jkU4Ug5V`P!;76$;{?ADT1s{>R-BEMm-b@jL%q6Q7FL#fa9(-J3KXYD0VYxmtY$Yooyic9Ap;`H -3W7rL>oRqPFo|`bk8*bY8Wf<3am;Pw8L}}$0&!nro(ou>@8WraL-V|Ft~>xd^&lc+yKdVFgSvhq<20S -f*?&}4u*9NVQdUr6QKpEsvSpnGdF&ikPH-e-J@Pd!*a4?SxBUt-YfWTFaR|%&@@tN=Z+mpzuWquG6)X -^flYm){cd`a%9pnw%4e2;;P5??_y-$J^9OOm3%RmaQa4OS)MzWl|5nD4@XU4P4+He+$o%~luf}}PfNt -vA3%g=Bmk3g9=%FA5PgVe#BdU-s~4{@$~zuKz9O7178B6Cv8j=wFF({j(QKt#e=2>V%B)>{L{Xd-6dt -QZLTBl9h3Kwk8qna#Z{8mGcukN6y5iS7fA6#$~i4*)CA3q-OfQp7tj>BD=QNxR5STYc4t?m8ie3*IU2Q=^+a_&m~x^~j<#B3+eMi5gR;hLIM<=rJQ?bA_+aF -jWaD!kk;rBl^^Ux0Z>S(?uz^s^qOdy+H&&s -eP_x}JOFAGkjS0{@_ED4-(Iq3Tky=s-KUYh{oln)pbmkEBxM3`Pg>`5lXG~)tSf{WckSikacS%sBu+c -wW!6xIBs9>hwS?1g>=ZFK8cpl_@yY^S%uJ89IMQe7phl!>FOv%1WhuDHoEE?<8?x1k?g`2WRic{%m!T<16>=}g0-~hwtM4|AQI_q@shVC^U_*ab#TpF -zlrhYwMf^o^K&r3-_IM5?;M^MI#{WIW!gP~t(NrZ(fqkT9^Mgu-kZZ0WvnrFB^D^Vw^Urp1P~_IvT@% -l6W09{Y+Iv6b{{A|a<+`h!Exnc8K9KWfQu0_$pkb*&RvO|v7tG)4aqC3g*Q(J7EJakAnRw(f_HkyWHx -av*!in4BIIN=@l+EN{pZ4iJ^{#qs_uaBG)SeNOv1FB3mHh^RCs8^&eZ|6P;*0dD5_?E4sY~`lT{|d`s --?yV5aM~Ef9}=I;*ctHuC}r^NGUbaS+sCo{&$xq>qgWeFCXfEgUu4AQ;4I160*)PQd^qdTS>4i~@0Mp -BUQBC@oudLt5{6(2&gf -+*w|BmaUz=m -?$phkssHC9P@4+I>#KS@RDP4IDoS6mSW -OZjEmE8Ied$A3D!5Z7zWH%9O@2UNV`TRSEm&t6HP<{awG}pt_85TzVBr0KBOzdK!;FUbbrsc0e8zI7s -={?u2=)4$<>ZJ;Npw8@(F;i3<2z2`kbG9>KETW&@;=P#e_&rN%fK&om#5277blqXzoH8W_xuBB*&qob --=iuRPG9tF17eV-O3!X2E~29`x~z~<2xVO5pcJP&m3Zs7f21ea1C5UPl00Ny=bXLB259n^&&Z!%7!#d -TjE8rKVa;Vb6aA6Vc8(vN1}c=EnnmVv^by}?n94cN~FO@j!Qkqv~-zl_LA@XJzd`1<5~v3x)1rpS;wJ -x*&SW_FbWy>K$X6Dbs%^njbDKjB37eVq4w&^1E7TF(%2(9lGslAVlIy^Va>uBbfHwk5F+c_{dyCKMGP -Y-YnY!+)FL9C66wOc1 -e6Ab2ub-g&Z0j6T7FAq=RVj8>x^~dgjy66lS1Rl2tfzr+uCbiT8>`@}ykoVG}tAU+$h3c2JlezYu1`B -`(Gjb~|q$xH5S9*6KVon3TLyqP?VC`-&D$@axerQd!*Q|av!B0%}@&m0U-Cm2OQnAUT$f{*j=4S*`^v -$wfFT0K9$6%t5Bb#Gw(5&kAWyk0#Yoe90I{=m44Y2mUE$|iw(_BYlFGC>raceO-Tui<-K5p?x2h5WC) -r8r^)K&bzkrJufYlhmECwh@WS6n3s?xx@*te@!Eg5vk8Qh=G>l$_evp7I*XBX6p)sIeo7egVyr99AFVi -l60+J}ptpo@ZllM<&@bXGQd_!$u5yzV6`#HYuXnZ+o6C}=(5Yt2wQX#yb<>A(i}oq?5o!;)B8G>o_Y($GF7L -+v*xX0<;h_#Zkx4RGWojpSqGX5S#9Iv8I|L-l|o>g>9Cjp|N4K3_)C^1+JqY(dCu)@F7~c2&8CW?jiT -NqXsweBRxGX-sx__-dq@N6k=-2BP!qsmMDs=&~N<38hm7wbULTsl2oLJ%k1z^NP`gNFJ)Gp#J(%+qbAwXlk~b5DG(G5n(Q9c<>Z@l3Xws1_c}1ko^PrV -}A%EpA28x-kiXX8&9Vf5o+Y%u4 -xN_~Jau*^SrDs@pFq;dr&4eFqEa_F>XP6Lz7D$;4k=v*neplh(zv#q$Q@Lsrjs(}CQ2%c%23v_Z+_i<6Q -1c!dm!T@AkN;>5!!R}$5X|dJ{N&O6;@qBM@>zf!l1XfJnHV|( -h4@QC`4WOjx6uzr3vPH`zh}FNsH@86<14=ATJU{u -!Z*11vaD53w+7n9E3Zb;(!Oq}RP-^#If=ccA(BqHbjoL?X)7LtVyzRPeBSe$gCW+biSy?;juQKozhL+ -4V1xkwL%N-+gt(JXvp)qhFY0; -{j3xLU&hr0moX!<-X7n#q4$GDwpGn`+-x+RMjm6#~>{(i_A2c)o65Elv -6A=3VJS&E7$ME?TG2e3nC?T#hnw4CGupW1ofb*oW)m<~Y8^u7QT-w5Hj$!jn074hL87WU5$M+kZkvX8 -&Z4KvKfr&TrL`ys|t!^w@z#d`yA197u+DL<}HUOiKg8sBs0a4w&Z>&MkzGk!5rU8sEA{+f6J4S3#y8# -BUeo$~~K*1?MMjHGm(`Lf&fMPBVwFgt1Q-DF}eh^)tbidvj+yLb -|(s?$ZWQx-6`vnzxyXB{E0d~QTs-&7y)ELFFQNO^~oHK|KxiX5!>2h*n}7}X3#e1EZ8CZ*e+&(ZEfmX -65G9YDkX_gOwHwx;G6f937ZBSSlYG;iQ8)_G;zvWhNwQuf|k19Wva(9Z=yRfQQrZP)!RjyTIcI84fmZ -6MM`%J-SeUK;gcL8<2Hg%^p_E*fl?r2&JIg+Ql*&~LK>!>?Dv6T#}Lt75fDZxk7g9GZPs--)EDtN=Ee8PJULv>O2Amkh)b2l-+~62uB1!?j!hd76Qw#X;(4@MdK2%7y -`$^^B%?3tVF8Ff*(LP_5qv;xTY<+$6-!Sn85^0eb;NH(w6A7O?L{;h;V28}AJVErDue}XWc8xlJP&8{x<8A!y8rVZYyi#&R`y#2Az6KN;$c?TGgz1E>=!gA6!0id?Zv# -h?SX%w6(#3B)|~PED1M_;NlsNM>lG@yAeUR&t~?jeXOE8ygq6&nrE!S*4;W_lA#yEAxPN$dN -alRy5F69quwSA*TafI&gC})M-M4D5zc_+V|d@U=%s*tJH1B-bA5{^y;<)Z*o0C)@|F~WTRsZ>A@_uUk53AD_!lv5+c}n*|m*I -U%_BSUUVXMV5tV#q#_TO>G^kaJIYkfKG%%~2~LX02Kpi{a<+fI5syO(f%Oac_ib;U$-f76T+k|GKp+D -q1K@kFA622_x~=C8)I_%iQ1k|sXgi|of&hxSf$%Np7j(9o&c<1W$+wWZDcz;V_vrFv=A7YkwKAhMAaU -uSu_qA8-tY}Fct6?Yjd(2X3?)~u_Z!>P-b>4mN2DWa@9?Fmb=fG9RzO(*Ko6AIZ{_cJVJ#2Vs7ZgbCZ}mQF0g&c{+}8-wD{zrbFo4>=bJq6^&NlYArzif5vQQ;b%CA)#ax_P&APy?a{SU5u|5Ya(_dl55Z!T*dNeJm=;XS1 -a5EOS%a6xS+w{X-%!ED@R_ec7yi*;-fk`1AD(|&&{FAQNP^tie?Eel+JVt%>bVAG!5MZL=$srW+=e{W -xDOVo143{({ceD599kmsaC+n`>{!-;LE8%8YVm4B2h5i{88feGY)^|dNVSBLn+F=_*0 -BOCF&ugGFLrhQZ7>ur7ENJEy|TN){lr}GUOxhs(D~K+=KOz>^tBQdzR@ylkehDx4}bO4&j+Rd>w`t)7 ->e_%_0k$e#_ZD$0}wGB3lBb=09u@go4{m$_Uiym@3i<%31?D&d@W06=noWakT~__kh}biBrS**wFqo^w;@-95V!3n+n@vx -%uB-R8SOmQRR|1m#EC6XA(haCrhfPjsSsJwL5fEHHKuOSjAec>_$GXT)N&iSQldbS@A5ey2lhdKeV#u -ze2MBAg`+yiqhoCpiS{XadyepZ{7c8k~3v576ZwXAUIP*EEtC8#%1vCOp`73AKR&=9lL!(t)-1RQ525 -tl9tWSRCuY8>Z+9uiU8ZI@ulC9f*3-M}pJw)hCBNY4jKIz#m0}=CvbvP5M5KrnoR-suM&s^fFTHqJon -yM6d6g5@#-~%!&KgKekd24Mp+jBX^CcBC;U3xrn3OeGkEy?jLIlR7}%}YHv4CJP%tPGV!5rPj|026f; -&vEFauK$q#fo|LV3Jgv5*N4zJuiO6kYLb!3AW^Jqff+!5mz#)GTqJw_A?76lbjo12LhLA4JMCh!#}gQ -*&bH`bqn1Yh{h6-2_Thf^U9To3!+0~|ej7j26YjIfldSai&oCU{21bK!^+g8Gya)Gzi9a7Xl9}GZfM? -4*G?Rx)cqpHT7V*%|;y=h{q)eYX$wrAVvm}!nA? -x@qJsp^N#Qb-Z69~pa##H)dCz>&L(uIAAQM{FBPmH3Hi{{bcXm@ZI-T|4Fu~5n}T*TbHFjcM-KqLW(U -%Qi|IuiZ&VA+_{$wxO~w2tHTt50Dy`Lc%@Xcq#XewPi;!I#lagMM_lRVlOK<)Zt07bVB|a+1A=#>e>i -mzhX#KH6`$(kxW7Y`=dwImUbvrO~aY&=7Xk{y(z{MXtBg+bvX9y^T<{mx7r%Rx_|zZRoapuEo(e=Bwg6AHh)+GkY=G -P7UN2+se)r~zdFyb?T6QkipQKsoRSG%zKHsKseLRWH>|q$t1cKcTdU+EQ-+uLVT$_T{gD3 ->Z=Oaeo59kzF%^aUHnb`iNzttd9La!*|aYpHco;J195ML^oY!i -r>pL?C_Ep4E)3}a?RtJ*uN{212v0?Nh2TW?A5TI*RhGtdd$9BO?zB4X{M -j9~f(sc;P9RJJ0SH9QJEabZFS1+Mg&~oQNsOMrR;a(_#p9>g2lW_&xpjOBFMi(7}Wf1`mz)0dgvD!QR -ky#kA!^avi8j6tCGAmRh;CmlN#-F=c-qkDT!ra+IJG|#E*NWvr_HD0Y({`iAFqwe{r2w9iU@;?j3@}b -x$?Mo3tHZ#Qi{laq5@^snyV$j8*F8J()uE9*>X#T5cY}ZFMaEB%IzWQ}CVC_JnE5!Z%#>;@Wd$x}xZc -3*%#A5kC%*-hmVbpBzsFPaZ>I}a)Ab(is*uNQ66oRzf+E(V+vdE4g+I$k0Sy42kGKH>g9?0{Pih -MKzByCne&kUpVxR=q0GMWd!;P}W<3zzK!3JH!}i^M?}0}tnV&Aq3YA|99Jp}&j=G8cSy)Izs)yvVrKF -Hdtmcad(}2r<)BE5-t^ef+e*yu0$GwRjW^ovU7Wap(z(p>l>FHe>NMjOzm|Hr1HS -`_K=6HFYJsFKVLM*wXc|T2GA~YT#giXcq2tq(KV+}4lih<^G#gtveBoHCRL0P@QR!2j}(W#RLpXyTlp -+Nisb9nnMEePwi>%;#Zwm`Qv -bogS8Wq%)`k@<=$KJ6e~K=?)EFrmKnvI$AP_)v1rW*Od2s?+@I!ts+%G8R2i=C9vnfVF>oC(Z>Vr1i$ -{&Ny%%Qz(SYlss(MYUrWiRbwHOei1ciSVywDKFI(U}I2F}^AS24P1l(4qj}JDbW)LSzrO3e}+7Qtv=; -F})sgsreJd5U#~^I)uV@?Fs7oh8-n&u?QFR?Ox)D7uI-=!rV|;lXrqHV2NYVi7IHP$B(dbgDy&spcdi -KE(+B@I<+Z{LFLf)BRFR0SEXgr%}0_0c=Hi9fIr}Ul85FFv}Gj>TvNGKi~eaKY0Khx1{^N}94~Q%<%D -9e1yD@S19hLww#%hm-O;Clqv`dmYTQp(XWJd9TV4Uf+a2{N+4Qo(Hh%8XyQ2nWO!#q2cho9YhZz+YS= -G-5V6hF)wv!?|q^X@p)s~cSk^MFFu?tS4*L{iYst572Lm5Ud=b?oV3TBcWH0L(P8E{{YZn#wtdW!65$ -rP(2DBFT4I@g-}0Fd1b15`qaoO(;R$Pzm8#27Ex4B20whq{7xo7(Dt7R0Ut0!I(FN$S9Way~n!)&xA* -3DZ^mYyxi1;jWx)%xcimU~CT`fPP9AhcepLPIfR~BU>?Yj9y)=KFZX++18c7R%#5|s11x2pi38klFf7 -)tJ-~93~dHTaSd+HbI+8;gBEAU5Wja&7n&qAV7CXKq~R0j)LsNS;3_6}heybyh70hsLDsQ-_62Z~QKS --ewUBvI3|$fEx0iFlhL}@OC@v_nDC3w@R1E{mJ_WJCbQ-+^!HO)H)Xhpr364}5OiELc-Jqq3)|3(jqz+WfihX;kfF8J#W -c4)te+bhM!;qV7J;D={{mk#`4nczwBrwf02gKb@vSxzB#3%3vdf8@a(NG2hi?ZGYL%6OnC;z_!a~gy)wH{2%!CS@zV#;S`_i$oDw+vF~&4{hV_jN^tHS3C_Ja&bjB%CYt2j>q -`=xd)ZLVy(GzOZ;o^BC3F>yb8fU9(WfYK?pP(Wvr)#pA1)cnyq67S-h<`gc6^yPy!h^cq1^ilUUu*bd -NJkP`(ohU7Xjcj&b~*`A?!PqCddBQ2b1i34)TI?oP+tEKY1TN3R8bDSxPay#zvrzret5Oiq*&zS9`<| -8g5pnD4A-DBP`ith;6mS0I!_grqN|Lp%yF?3C7BRlE|cV+&`J+l*w&Y(n5RFK<%DoNt|t9tnT;l(ml) -mRPb)haNNQd_vsk;iUkIgX&C6hU3junj$GN^gS(C$aF<+wOJG!w5n$obH-#|+M+ia#&(MOjTUi@0Z;vfMQtSNnQEt1t+%Gnva -3I~2yyJ5>FbW$6jt1?m^XKb!#bP5C30z0A|1`XPBc7(7kr5WIEI#g4cd(!&Zqvb6ANH$b1IOk4n$h>D -!jSPUw1>Z)EC%kudV>;1y!x~9Z=Pxx>@~t!ff(vpJOIS_78NItn^-PSq-|+Vt}pc$NBuoXevULow{bVNV>YQ~T?& -Nf=XnwVKlxayL-o4bn+LqL8VwmM19|DvH7#QG&vEI2?mP`osb*LrkECwVCF3Xl$gKunWMPlD#+#S!t3 -IOKn*e|U3ZMYxCJ>5?Yci7gjGxGQ17zvNECo{KX+!X`A$hLBJUNhO%wQg#p%1cRo@)}r5{MLk>P2ft* -5&MEj&A$ID!%bugMxGEyib7%)hstLr&4#D_{`5tOf&rGA28vKpF`=rSPXW$Qmnobw}C59r-;?hrl4Pk -k}XwUn-NT_4CGl%nz{16JWId7bd#U#FDBpkjZMIoDRU$nhi47mM_Zk=(kDzGD^?>Xl&;yNWY-vFz0t3 -4j%{PBt&hrjOTbMlM*AZ^1Nu0p;vrn^i2$K7oUPEa-bBxUr?5(U|#iz -H5%4$;#Nu6(t$U1aHlxM9C|CB{km5(>1r>y+4?V$w70IF+r6CG}-ss`v68Z0TdLmL73>G>p%q0)AGC9L$s_ho=QI7iC -ezl(aw)?(6xi;t_FoleGMrZ8Hq&muqQ#068g?S7VdA@_X-D+n}q3l!#VBnvv_h*^45*RPK^)+0XG>@$ -8DUY;k%{>FTa*5Tw-vdg4`QU8@77czppe~=*AiDR`FX!0_&xJd3zinWZH&Qg%_V*E6X50cd@u=-0(8Ald4O;#L^m{sBq!eo7j(+jZN2R^l3K27dj -)fy7Egce7XYaf(T&QQ&znXd(x+_kce3LQ?FK(`e?_#?m(qur;Q7FzfIGTmn%yl?$F=d+1Grlfw$n?K#m3N+%e4>J#+TsDUAW~au8qHBBycc5dhj4eAW~Qh(USTl$O2!M%q -FHDn2-U7Mg7d6{Dd@0RlT@0iqCV`_@nz#WTP$n6YHaP8_FDa3c@OuwcG2k7?j~&(&Q%}LCWY}JPM%M= -=1xNy?uj~0rLgY=gw#6hsUyXDEQ$)uv7m@ie*WO%rRsGkL@8dDeRhLYiKjiq#21ceyAO3PV+3ZO9$MkRx^mr>j?zHMS>Ji<_X4&0ekwxFb{7ksq$ox# -cC-4lF$p`l&st&K>22Gr7U)2C%qmNePr!8bVy~~Pb$8MRlB{z{DNMS}b6S_B;g-k%+_Hg!bw$;;C;gi -*dbXmQ=M=xuhp$i$_aJdoQrU@BMGcL!_EN@MD5h$aZF@xGrB1f+8;l<#l^JbF08WPI91&Z@tvHA+eNz -iE=7vL#q>@9VyM}Z^|S!60UepO%>19taTG4c%SLE+LP@z$f=^ktEo{T311T&yip!>S3V89xf!B-Y?)g -(OGj@CH}jJFW7JM(1S=X>i*28{0~3{pg&aHe1{uF0av2Nse|H-M$7w`<__tSEoC5Wf49GlzXIZpI}h# -z=tI`%ulkMy6xr~W-a!~rAI;S_%~!_UKKLgtbP@@I~*yIjh96I#N-4<%tQh7=&JEqblY#9;bO^8kSF= -F^k!Ji@qPw(|MF@d`5HL5ObYL2?VIk_@v!@J_kfa78E{)FV&m!RHToT{q}~ -?Y7JM38LYaxU7_lg`&7nWCS{1jr22{vP+-h$MGBLNsqh+YQ#-JJBEkH_cYurQR$kV);4Y>8@b!TbW3| -wwDq5TWJCQBrJlU&u$EI7}~c$_8sRotkyUHlBHfM`En#WuW9&3quKW)3}1HB>Ju-OK&%w$i(9<7Szlm -Hy$Aq`3-a=FMKRlO6hEUHb58w8p) -#x^I9<+8LAd0UP`BR?Yyw>Gs;={7)jgAz>do3Z;#r&+i@Ne|uTQoB`tfcED=J(dT$9eQ*%)WXBK=nB3 -C4`>yZbr6t*sP3(0Vr>6w3>yem6PPvK{`bR~z^K`h#uB -9dUblIpe(wcjO^^5VON#ApjNGo -?em)VU^>a_WM|1-5_-m*N3TNXJ9o1(NpFJco!U5G=tyuBfTSo^I)*30g0C|d7-IW1D`Vv6Q0z$bT560 -f(Gw?`nqkDSRM9)YX>ks_H45#&3*$~FI9nKSEMw4i6`wMFKm~cX@Y$l=4(+{=f_jZ&n5r9s>7MF3v@! --W+^OfXjXhGb*3WsH>swUqMS(VQjZIA7)f0L^ZSXm)bCTeX@L@2}l@r3p=DYj@}e+ -z(q*_y5^%H#vSH+i1UZjFcJjNu`&@}EXsJ9_^tHX4|Kh%)ZWw2d=fw^3>R6^G8^|RK$(LWs`A<4p%`}Qu|5zMR-gm}`qd?01urG0eer?AMlS`* -v4&z|8(R#2c=<;YXaCap%TxHcY6gxxI_CP+8>Nh`DC-HeMkku1enj1P~_qZFGR2>UHk#B+*mBlz(=Dw -8#)uzKELp2^{V;X{4YWD!H%sM_VR+EvyFwZR$)2U2?Fs*_ml@`F_cpvcyf_2`8a6q{9iszQ7twW&KZ^nj3<{J&HhJ%q=2Ii3?m$uMmGv -CDyo3Gr(g*-q&&QIVS+dl_$kDQ2s!pIVXa+zHEP2wmWeU>@9r(vE7CQ^Tdmz;e)mocl^D9p*FO`zvPL -BU!mbmT7PB}P}TM`>lUX%{263x#I$Ro`)!xa55SXL*yK?KqyXi(PY&{Ztz;h1+>?L3B@z3CxM?zntd} -K-mk6$uU3amDKxj27L}(Luxkgt>$(mEf7vY>Cbfp$89)_+Y!&2i7<39K!j=qlI~-ET4^hdHEFWmEUs~ -3$_~2sW;UoJ&IW)~Pj$C44wj_1Je30uB^7?tgMUGo^rl(5paC6hb#()p#Of~u#I-{)SGv=KW+aYdEPk -uNw=yjL@N~Qv%p&Qj@jG2K1k%WHNq1+hi4qB!=lv&f$l_EgM)#An$dg>+0>ye -TLN|#KB`DA&%|J_*JJ9AXER>L4USJahDlR`V&uZ((gZ?sVXD?lkizVwz{XuoQhi>x-GDr&5V!OX~M!s -cChJ*m=%IX5C++)UUpLickqZ`&PX?)GejxQeB7|de-mW)v5NU}X#h7B`}B&NEqg06U}KR(T`Ec8HA)^A5fwAEGb4d=W8@0xLN$3o%T%y<8n{d$jX3qOv^5&@^RNDbMMC5KG*F4_(Mi5rqFPFlCYp&Wa#1HQQU -1oJL<-$t;w1IEfrs~b0IXKn+k;^X!;9K@H5xa{Yk}F#(uyX=D@)+InZ+5P3+J2P2oF@ce;lJRj#p3NC -@-~0(NF3L1@hA|#yJOy&8)#LBQ8{wy(tDK>IVO`&ntc>+3-m^QYzVLD@YJj*k;5yxeAU|(OL#@ERBP4 -Jyy5$My({q=;11e-Be(Qkl!iCXLs_A&ptYw-@T36?E%0?HMqkEhvV;5Lp>k3$O=^|!rBb<5Z&f7t%cq -;RpqW$6)amcH44T%aY(di+-pPR84RX~cZ0gX))o?x}l-9`3@ziH5hOJtXpC6SM`G|5~)QHk9Nq|B7u^-4h@? -sF;^7vg>U%K3%3$EjRWi1u7%YN3z{k74epk`r^H#fN;Qjga0)Td$TWy^s5U*t*ZwVrRm|+oVvJGHRuH -pji;Q+a~%Jt*(qnRid{AlAvcEWt6xsHdYeXePdO(n4l>TkG&GteVtX2DrLsuVA%(_aasnvTa`3;9}fv -so`u(?p~>U|y0A5Y=8-zJ$9&uM1z67g+ARXYziHUg1iDv^)9oRhy0#6{ -27$((6I1jS!Aw4`ZL)Ex~Lv7Hi6b(c~AWh4rPPION=XMxxu-BvWD(BL(4DPA}`YcF$D8RFjqdR`OSat -3tUQx$ASuk{|nnDX!H1QHjfmtQg_H72{^bfOdRW3%k9$Q@72jz-`aqYc#V3QH8rH+KZBVb -1+q^-09KM(S&A#SBp&Mb&^c#DZ-TvYkIyTQB!=o2 -Xx=dZMdu-4um(FWJct3Xs*aW4cm_lzo%$d@^cvjYgiO_^~IHT91nI?2Rm`!{glNy4i%wt+gW)}>41CV -=BAxhcGMTPiz1!ZC(ep~^kg9jcL6yi1hOwBb3!5nXmuS6TY7SGwoDJH{P^I(Z)LUzMvvRM5lZe(>g15 -K`|53gtuLw~{u(#$ArQnU~$Osvj^$U??Vmbjy0=y5}Qj}cE$`P^SUVwKcWrbjM?U$YomfTqih#U);HP -zM#)20?g+W*YD==@xweB>Z9Mm4TQBT--2)t1R0R$SItX@E`kS0>v%-tUJBq0K=PDjpx~cQj~E>BllFT -XgRw610k60jd%vI+pzjG49gNA3J@>>3SS*e>`)NjE5<}17I#JyPEo71_&gdr|X8bI{i;`>zm{QLK< -VASYAG<4&J-V%KNgN+n>bCI@GcF@-f`zwqoS!r@Qy4S=ZD81d8I||hEdwx+hpqzxD#wH7*t0FHXMKsD -?U2K!4ALCCw2v8RlCI#(L3;mP11g*e8-Rz&q$o%&$GiJ57Fvbz&~q%8pA1>l^~2%h}&5*V{~KPc_`+5WBP&`Urg@w -58{QP3rum|$BpW9dB&$&I~lqR?^NiHyAw*SbGrn`VBpi{9`t%1~ -iCh_&%Z$0UiM1_aZDVfKn-3}v-oF4tp#B9VoYVhBbK$l?S3Mq2A$tqV`*#9!Sc;pDkHz~R9qfO7y#L% -*i2V=beo`dXW{oIQ`&h5~Sg)cENvu7uOg+isPO`WLhhJ?-@#`(v=2mR;7G;xI+aT7q=C+V1D-!6J&Kr -n3^`k$a-BsVnFKAZID|4p=v+>4enU@p%8dAjCjNE9#mg++fz)u3C6=qVe)Ge3-WEW5@@mPvW=a_mr0> -)i(X-iKnn&QCh@t^VFE;Di3OFf4NJF7c^9a>J;l -{GRS;nsODoRcEd=WdJ5>(Ce)`nxcqh1R_+LQWiEKrn;jhC7VZ?Sd0nXYojr9@FxCZ1ic&5WtPZXY`f6 -=7Ex~jj>P@&-sC!-`RM2>PuUXU -8}P#!W>JUySd>c#847VR{S3b_JN1@9X|5iZ%GFdYO}m$e0J=&;EFf+quST*2!o!0f -vEixE&+0E5pDtZr%Et%BqH^dk@$rcc8J|GBKE!V?;Cz1{slty%;bb&+>9AT?InE5)&kW`b4MwCAsV)^e7qQKM2fgO!birT}D>6NR03NigS3o -8^$1)gI@4z_%3r!VX7~G{-;#aHq8Q|Zd1faI+U0y@_kd~PsY#GwDMA&{-IPZ+`(AI5yJKnmick`@ ->O>F7)zl2c#e(``XA+S*D8xmi@-ql3UXJKVPyCmPhNNxc6rj5hP5cL7lpq#st8hG3s0}md5 -g$1z~`Za$qVTTLNzxfB-ol?fCYY`3Du45k776hPc5jgUA$wrQ7^E?DEnAN46Clis~@QHmf(_bQ-`Xrz -mWV0JBLsFKqlx7Fs2eOg55S-y65WDRa#`TkTrr}6Qn16bJ3=g>5m1%gCfo!3dokr7hyE4HKfwxYh^nD -F}4O_ndYFPsou-jsew1_*rQxU@McXZ^8YibM1d6&NOj(Z=M`ky^LSj -RLL)Fx)5*AWEVCqVH*B1;y1-VwKMX5{NR;=d#~Up4)6lMfOAQWL9d5O-Hqknt@Ju?ZSp#oK>wCIZ`Cd -m*W70CifiheneLEQdM#hnR3Wmow5BS4#b*oqjKp#3*V*+Ly?Blrw+HYXQEXHSJCgm^ckv4iGSU2Gv-CbzS -dhtzwt1h?o<2B<*_QAp2o9#Mt1%?#@>6FF=MB0`};k7{(a4uFy7Y!k&neAH?zna#^Ysb$XRF= -nj)Vth40Gp7TgC4)6+~(hxs)nf4*~ya%p~vL76J9$rsni26x|5TkcLM^XeREP-TY6Z`|$A-4^@~8kUl -84}K^|W}d$mmzhe7#c$li(#Q>_s@SV&oxKo*Hf5X|%T`3aRWYIeXq^d2R2xxu9zL?_OcuPFrfxWd!ZW -kH;WCS64BrK-_Y+p&bZc>00XE0%HU&AO8=%9Xx&eLy?4z?gwz>t0{+0&;u{mDg`TpqVrVVKFy)ojw*y -<-OQodceJpXosa*4QRIH)lOciYl2Fu{M6Bds(_#@&*|fU(8KZSJ;54x?_6KPI@-uhv_Nji35+4+qx+- -l~pb*RZ9^B=x9C8Lb}8Rx;}^k6jDoehlb_##(1bmS6nsx>PoDCNZvo_;Ys$yO1=> -PjV-P08kFh;tPL^698s0SD-uz%lpu8n4SF{0MaL^+jUyK4zqSq>nVKx5oaCQNjILtjml1Uk%?b1u-1V -`)gA1Ds1Ecn0D8HXtv=85dDx<#z@&bF3G;cB_2$N(%+`<(2-^s19c8Y@Ns$V%^+@iS_w4fWF)z-MUusnT(luNWnl? -p#?t2G>l4Xv5FVHEa7}GJpFPw%USS%BF86y43J{>{8AimS}`FKlm?Q*K*NjH3jhSi`NkFQ-vM9eI8(` -^ls7(+w2159mVfR{g=-tFg;l&+JPrKi>%RlKu1$5duy^!yw>TYG096e-(_X2pI7Jt70-beG-jOy;-THr$l5MtnWfc>6kzo -*!5Kl?q&e*4&OZ=+}Kydj>Wb6~_C`+buA$`S$n>3D#|sA&x{VYwjZ#0IvbFNQe#$ku|m0EF&L?1qcgz -h;uJtsxlS%=pV4ahvU9hIq`ZI~l|(2C+j9oBy~03t4tYJpDqRUXo0Inx&T{($jdlJ(>OxOV3ZFAG`w< -!VXBde*+inCj$5`g@NlfYXa^Ad{Y1p#S*s}95(OecDqfu<*czB~ZLz?*7 -n}baZki7xY;8*+f;P~R)PoPmLWkq4r2(%Ppqo$$08q=l*a(5NMqB0h^pb>>iOhb!@Vn&nIm+TY;q>V$ -Wr96?X_8Ehti1{5@ZnO)Cc{M!4*)L;g1l{vSk`3PXM&hte;_lDx8Fr7Kbb -u(QK4t~&Q_=-(vVOf~83<-6H5OS<5!XaDi@33sj@Zbybb_XIqn4s4KYA5ve{G^L~;#nEv* -Qt6|Zr8Wc5ZdOi(-l)_-;)P_;PW0obOziZb$jd{c9}7h?j@7_gfeEPWck%)toerF+O_H=0UC9Mw6a)W -HGibdw()z7hCP> -13|qMB2ZA~K<`FEti{wagr5~mN=>t}L9|#QiSqAo92;2ripNRSJcMB~5OjF0}sOGlIVc(xo{=bBZuP4 -Xx#n+e$?9E^B0-Be6tKu>^3W595&}6hj?=G@?0+H;XqU&PluWSL^O4Y6GTWt&Rh9h^AG8Io-PYRUQ17 -JWwH>MUz?PS|T#She%6dS8s@z9xuUr7RxivR?MApgnWMVR}1%soafZ98k*%=nuqJE}G#zf!q`mb{j1_ -E)RV-E4_8#hhvAMzCWFWDLC1$EMT_6Zr#_Yl3IZn0*4 -DnFpoIlW}*d46%@rWxXzXDyCXxL+h{Mu}{ZOQUimtKMH)FB@BCifgLVJ&-CzsR;*RA*huWv3e%%RgxA -TE^?)5Y+JVaSqpz&5r3YD$M26nUyMJiQ~5To6Pp4pPW^9I4v?vP@8P@4YHz7n{U)-bT>Mr#N%N!YDRMoTfcOh)=M?b+sCeC1nHA^#t{ -LbFg`?btUA?hxCZ^xwyd`#+ebhH+>R&loIC?+<`v~Hsp3Qj{^frY2M>73ng5(|`&Fm^4(Hq5@0K#g)z -N9k?+rC5vvgpnEj9-ix`&{^5NrO3+3w>hkjlJyS%y=VoF=?cZYuMo9hccnO`TXpyv~pH2gwRSS)c62+ -L47PA+6ZyWFixt3nW=8~V1h(Wgmf`{ehN$WqBaqm&yan;_^i>0FF$^wUG4n|KM$?;KEy91S9>4kMpm_ -ViBhWeR*2PU$e?!j@NjdH)%IY%#9n?!BzM0g-q;Te -^*+2FHu4gt;iZ19lePeE0-&Dn47EOK#tp;?c;5-k&sMqTSb)?=X5^{81y7_Ho6wrKRol&0ibx1c0ENY -yi%#Vz+WJD8SUs8<-jwu8cb-_ybTNIa4v9_1yjVY~4pyo1)Q^KjNF}U!1@Wd~QB^vgdw*g|RWo{KfCf -ZI??JW3_m0fGeMtxD(JYu1;r#fazU;4_$L%#hp1ARpT-pu5b-L}H8&RAsuJn?f?2b$j)0PED6K;6Js~ -5CJ6dnPhPH&UQ>8gXM&noA;>I~%=n1p>` -V$~wDCqFp&OV7!aEtT*Ved=eqO8{c&#=e{=-@))KJFwgsAwpfjLg`8khrCl2FM~50>O-8xu8H795F2` -+pVl@xm_!BDX-$bW;A#kYxlEL47 -jP+IytABbFkDyEt}I$HoNA=;U|u~n_e)x#!&7^|E#D2p|VK^YFEHHV~7q9NZ<5Py4Xu?09*d^`5K(fQ -3;v4Lxix|&X07_?VA1Eou?kXXGfi2i%kj~eQH6|4XAds+oECfjBe^M%P)$1i5Fw*64K7>dNLlM5OAC> -FJyUkp{X!9LJ2WgT@M!6I)QyrK{&6Y)guYtD*(mH7B-Rj|oXVVkZz)gdmK!>5Rz -GH!*Msl$hDWza03^mn*VPKIEgh;{L9Xdv#p5lLR6ZKLRax@`dc&%_xL7w6hnhz7Z;v|vQ0y#;lhu=-c -d6`OMo2UKQBgRwG|3`;#kQ#Ep!y=}ogx-in)Ny8RSmFOWy4iWByUGBdbDhI$lh59oDikbmZ2Zy~t=#vs5$0ozZL!yIn-f6%|;KYp(a4koiG-z6SNZgqv`M9*Z*6$0Ipq&_sx(Ds}o2 -yhR7h@Mx!NtCIqe-@)sP5=7uqnmlxC|jMSLTwX%zryN?XvmSV+d1uhX`;USeYv)XvuUm$%YdO95uqF; -g<#1yw0AiGT>&cGXDaRikVU&Yh{p(XtbH=e-Em91s6kX-RXaSTO0ZxYACJ|>obS-mETK)SLJDG&{cj6 -12NsF^0;*JsN8=^axEYKrsLl<{EM|XzSXFa9sSc3(D}jBE){Gh86(5(t1IuVao^*kK7R!}!>Q$%-bw8 -@YUOFJ4)9yO82E=Lwz_7LCj=MGwcV&+K+e -p*YM@3R|N(4!KpTHV&e4TS~2fbMsjO)l$6U=(jWhh^W@tClW2#(!LVu<54;d96I*~2a6Yy*k1VP&6k% -0~2c9G^;=r_M$EO%HSxECQiu1r0-x@=45tjJ;hLjha^EIq0$H0m*SpIkM5p0F2aDKaZX2a4`yV#Q0o= -Ni5kP@N!r;wMpBCjeM1Kt6CLX^XzsnreH745mlij!!U|6O!JX#7P~Tr0G}S8YLjLuec%3~J}0<2?~l+ -@U5nY9fHVeno7nBXwXpRvHpzvn9694>^HdY2wBqzDs>@ve2rIZZQ5(xh%bv4xP1dI?L~c;I6y?Y&H --Gfih$_69sihvECG?t#n*$?>pvF>La*3^|=vSnHcs3OWs|c8fDhNa|v74nG06;3LGvwazXBaO*7vx6d -lv=Yfr+-PB1kjzejP8tJ^yUkgH6B4a^{W0xsp4~R~P`ImU3eUXs`xJJwNDAXFH%( -z*-IWyL)|$#zIz<GMko{tL9`$$%u3 -_CFj$wK4y -S%;g?iF2Sua#aUVDpqQPjRFOWn3v{(f0OsC6}@>;De)gyt)+fp6{S#_FAnaPrOaOh5nek)&k$+zGZid -g9SlW-DTr9(+sDNHuOlB3J!<7Bat|SSz?@wfxxj-#?% -+DI7h?!V$?m4V -|<0tCLe5?_gc!>KJ@c%!u}vu95}UY~!R)nUMScRH)X$FNZ^4sG?RYH -8RO$8gqs>D|Xpe -t7~RxXx|1?L@mwX-R-@^?M&1(caz`PCSq11U3{egEdbfZQ!`K&SAl)kXI?;4H={!Av+p4eM>lLFLOjj -kskLG@x?0Uf%ey`PZrEs`A!&uPg2^WB5mB^p6=p{%4yrR1p3^N1HAVb1KLp1r5CO#>8y+f;b?ke)xt! -Pw78*VeoHR;CBvrk&HF>c(ka<%f(fz!G0yi(9WO-v{MDIz!X}&hnT6}Ej -34pgQ*OAnfJ-VXDt;14Tsf>2ObzHKaRvjLsc)(ILk8RKxF}SqV@I=Ip2O>Hq<0{8$Tu;JrbJ$s6)K97 -p4zgTYucX6A&AJ4qdqyDd$h7)b!fabx#rXhxFP0Fg)q%yHS<$V4*lEr2BYim-Q<(kyJ_q&Ff`unxTopnM;*SHDkbYDC7v`(@%M(JZoXn&r@Zh8FT43xbJ-o;uo@a$YAL|sskP0;A(DI2Od-3d@a -G1SBJtrA>1Nm%tFO3XiD!q^b3evMQ*a;qMt_}YN2M1&J4&b8t2Y$=hy)!6EZAusqgHj|noC%#?9VH>L -P3jyedKCSalArmO9~MivBPkGYY(iB4E0niJ%_ZFo(Vd&((|^q((}4p=`j@T5GxJS3`O^P$km?lVzuW9 -ZM6r3H!}%4L>-@`7xoA}NiJaYZ)K(^-5jwkDEck -d@{Jt7aeOkLH!9B16Oq~>>pGwFhyt`+CU?3 -H6Ca|ECNVdb}%>t{-(AgDBn!}BrDOca!8IorZ$l%IlSM{~*Ko~g0P1!#F=sS=>4O~C=^&b)Ifw|Rbun -_1Tl|n2??IwpU*~;}J0jOK@rKR{cO1#YUk18v%R8kJEPAx^|nV56lRprl -*rkRHaVAZPJqnKmWYL``%ZsjX9Jl2+^zLQH*aybf(!8VX)Da$ZgicZg)pH^5t0VkLDoQ~xwu=kFtyR` -MG)AJ^X&}P4{t74-aH@ZsP*}PV9)KFAFEWx$&ma^N7C=c&6(n~x_eQ1=FDHg0&cuH2v1uK!D>7SwX*R -CpT&EuprR}F{U`sY@|g7n&p98o94`qkLls!r6_5C{}*&Ek^BiuJ3pwRHjA7qPz6Q}SXF>vY3KtmdY|( -`RYKH2yN~49t;hSVx;)!!kCyhUIC}7O~!o6-{|r@lm;oWfiMfR3hf$uSxzdiqq(to5Mp4MV@HS`MV_f -=8}-g->5p+Tcw#JD($090=LRXs0<@&P1~_1Wf)(J)Jze4VV}BHtO;9Pt5}Aj^h8SVomBp=L>Q%YqM)HYr5NF)~9DhHhNn}`tM~A6 -oIycitb#*PR9yT@%MJ>;rPNNtK)(RH^N$0$c87D#_+NsB0ZW^gP(tujVg|%S=b8;8u^$ElhcFF(>0_x -cwRz%s#)~5K)b5H0EGv!`7^SrhYL}WeceLVP_>Xnfr?M$CjeR7trA&d9kTD#Kubn@Ipl=t9iyyzk!n$ -knr^YlWxwsKm?C!BZY-?rVH><9L>5?&E&ZjTtq|{s9kP(7Qlk2*2}rt -XEdAG2MS}l6)I1j+@~9eOE}c>WEHm9#slJqyNu?qHqC_%;&$82@CW?fiCJL>vxE5Tav0gLM+I;Mz%;8 -gnh8ed;TNK@Cb(BH9#n96`L(U3?dW%^hai6-|tdR3SkxOH}rKoX@)v>qgwI;qta-5=p@vMjz5f3qzmt -ajkXD#bL1Y5owXK@Hzu|x#A8wB*9QAV+ZXxT1&)E0@n;$=5Z!hjttMcFS8-Vwg8gZUt8 -(g>}VwQID8B$I|oz6wBS(L#=a>B@U8^u+|DFRi(^X`Zi53V9SGsK0+{T-{2{snPb0Qbfm7Ugz{$>}N! -p&P`h#79L5bo;oW+p!UJaqHR4P=W9zBGuoM3ykk7bYWaj*a*hj8MkVlDbRVF4CNf`idYp#s1J&0aq>Y -z{d#NiankaSJ&2s#H<@Abu*Mufm{Zr~6;Rd58K6>fE^(n?U*fjwyRq*BZc$60v5v2>7#3SLH0?pR8NR -TuMGspze~3S`hn0-dt2(JPutdWUm#tY?K|-;N4zvuA}k)u#$)#Z5z6SpTH>m0iWr(q9{jajr -bySBaZOv=-bhTQhVG4Q?pv7XPt -~G)nn-9RvQRnTKP)-IjshHm&@D@KXsMiuPztdoXOH8TB0|Og5( -^kxG2RJ`lF|TBDoHIG9ooye -Kb4$UPTQU{R_v~!%T3mxen@QbqpKI!|R~^jlEoSx_n{{$Oj1UKfIF8G^9dRnkfy!KQ?y>sbmfFP@n1| -GhUFzl%opbKOx=r1l3*C3AABYRtZ(7QtO1yN@yu3*5>HZ!I^({+UId5F3?daY^jpxC}?rJ)vu50qeME -IApsQJAUv3;BqVV-U+JYO+4iZtkNmTDJJ$|G6DiG{JGNCzJ7t9{2|&mQi*kHX1gPLGRL!4_gIyihUcp -K#euF3y)1)ZI$i@N9Bt*#a?i;DM`y#Thh1$zJMN^?}o-c`TA1orDJtLVTz3^HbDW*+k*DLpaW6yh-Qy -$vOK|XrJ=z_;_;jz)gA1sh^@1C#O+X$EVhilW3ckW6FdFVp`O+9Sa;~Q}rZ|7Tl2;6%2}n;)O^> -aL`Uc2Y_TSf-;^g^zl~8QW%`*I8&sGbyP_kgntWg}#O)*Jn?95C`u^6{5|f1;t3GxXhrD74IP=(RDu3hf00Qycyzr!VmDB+b5DpLlJI>!}+SUs)n__9$kyIBe$OY+= ->lvGyv@7^l)Y5Y&Tyq-=hysS3>}~&B}s%dXadbEAg}9%->1?A#1UiDeHXqGRETM*6(Pw^*hGl|L&NjW -?lGL!^Y`4y+^re@mEj{&4z8qOvleJJ}*v4pGJ^eZ8+HhhiwG|dN7+2(2LovV+vw*`U2`d-W^kr!onAm -orUYFLL0a*5$+yUL1feE7gYsl{{HaSg%?FlYF<-J%rW8Odl1*%s%1*%Fr=hyeRRJ=S -giYqOud>!7ml@fn+?rTv2Y2pY%wbZa#&MNOW6ur#EfVaZ=kLBR)jEcg0+cJ9}!=@<#Wn;Fd;mc!jG)= -*RS|(IcZOY!TX=hcAVN+ay_|QBp;$U7c@in%yOSV_aKnnZ+n)@x -{SNv1OJ3Idbzfh*^7zX!D}~S7r(hq|4QgLk%Cd@7D^y3y3Xk+cOsB9yjFU*s{qzgJMID#TmxDmm2Hn) -C~WmdVcP2nS9^7=gW`hMlv4osjak#vq;MRomKWDLYh;+`E2CFkq8seRIO2af~k~Abbr%6M)bG$T!JI6{xj&qbW=buaNHad#biOamC%jA5uDSdSsZ#1FU>e2!)cag9H`+8PpUf%qhC_pDHcX)#+L`qw$ -h9)(y32!9fsk&nCLnL!&xoO*d^gSEzQ_=?>s5Z1u!3yX3U5>%cXe*%p0V6wfF{En$ux^LYmjXyh57S! -(1fI=)s)%(u@te&P-{>hF52zG#`U`vNW%O*(%ML4{(l@<_efYrP&E{Pid}(IY^poVfL41Of))~H0xph -H9_(jW~Vd{u5b2!YeN;9stcCL|T?51(9l4e|Q?JSYzX)w>1=0uotq&Xe -t6luohH|I2I&WAZxnhRi#l;#qc!=!ly%)!#U8s<*Yyawg~Xmw;(p9>Ilgn!9(n7jKlFM@Ga$$ySfXJmtx>QP+baHt>x_l&Ea>%7by -6llI`Q)-ty1Y5V)c}#>-O}-?8LpOy98;uY=?qtsM2=IXqiseV-y^5EU7Zz=RsKk8=?r@9R%_`3eBb-n -9hmyqV=cAl9WxS#@Hod$7DsLA6h`VvU$f)AXvYo{KNuN~)7^Nvh4ja2T$qSmay$9{h+SpVbn`tNC(Vx -3e8*W!@oBq{)uA&x61~noDY|@dn_R~eCdVP%C{p;PUf!>^lLzK^!HChRHlfx~fkkhwmM2Raipv+?A+J -1-KKmPKA0D~m$CbtQ53nvihXZQo_O+Io3^Z(->xtR)!is9=ZP*|bQh`HJB5^|UtNVNo9TJ}~I;Vq4;? -Qy5eeBau(VOiHK$sGVo#h^hR^xrO2JkZ}#qkmTb##&g!%W}u2)aKfF@NF0D)1x>e9hMxd+KR)#kBEz -DY4C#~xtPYswDM -Hj4ie6?)BtVv&FyYqHNMHgG2LQJ7a#4w_H$=sF{M`2bF+}TCwi7Mxi>G~Oz|SI$(%gV8H-^#nb(P#Yc -azeOx86b-af~NLT}ndV^kXKrK`bSqSBUd_i#?MPUsEBRC*(;Vf6-FN%$*m^+Q?MNe0~7}S)AMAW!M -BoxK|x=TC`-0Bh!gPVAWWB(~&Nt|Q1IPpKuv)uU*COyT6U9A&i78E$jlge?mD)&f~d#Du5B5|1))%I8vu+)q#mqi -IrQzablrVrIY?snT!;xV@jkLD-NGXNlBJk^-=Vo(Wme8FL`rB-L+OeS4P?XwW9}(3`UeZkLdD`bG=0GS5Ls6gz=GskE ->5c6$sln8aj`O0?NU%JwM{H`P(MY(qIbB>Uh5IRZptHTa1f+05Fi|{2Tz~Pg?U_=~swa71UJ#ad?lBa -16z*5h?nnG`j+m|HK!||7nUZ)z(b~)PFXAGs6Gv3fLMNrI -FaCaEd$VH?#XtVOxSeWh;XxyzP&pEEaGBeB6kfh;>w@z*sD&>J95-Jcx~uK@^z`3Zt(I$ZiHR+A -Ec9^aapM#7%q)j+{CrUCZAPtwAcM?b6gY}dsWi2-GTb&a{_7Rs1#oMr@!?0<$Ym%nP`M}WVtDlaB -MG>l7bY=!c6Yac(d6AYd5@3vh|l)0t%thZCSaGCTI;u#orKF?Qv(1XqriLypHf4&q48j!Si8mbPs^Vy -FEUE8kI6ZkuX7y8~M#>|d{_JMJZ>tZnnO#b0tm+T!!IhPm%jrx4?4OKS2?`}XL!wc}x8Jx^E*tGPHfb -;!=>;$60VRR(KEQh-kKT|>%o2b_k*h5tk+L|v2E_2XQc(I8ByjGZXP ->hKA5k4-+I&asW93(;^Kcj%oPRyOL}V$D=E(=5}8duKg?>RYzh~IGTRVRSaJ#%gfX0{?yHugV#~ZU=eW*P$EuV(Q{C4&`>RHZQnGDn!#wIZp53UISn --?OwAb)iO_q~b3v2J@&;IlL?|zI1oqz)rj8yP81=AJ0N5SO^u2ryH!A}(YSwVe(ggq4;q2SF5-l1TYf -&~h$P;i}sZ!7qNf*!3TY^`7y1^X&EQo)-QOjOXO;0gs_Q*ft(A1YX_pjT^^kAi&_9HZc!3f`mO0}4K? -;CcnODY#$3lM4Qz;CTg`wUMxsf_)VXSJ15BEefV6I7h){3cjG=Rs}y)uv$UZR>JEP3{@~(!LbTXRB*b -2=?dPX;C%`{tl&BYH!FBZ!BYx;tKdZi{R0&~>iICBzP+iZydN%#VeHo%C5$hU@cje{8$Oc{OZPSX8~1 -PQdG{OKnT#be#%9u+uhX&oZ=_#TcCIBq!xo>Lot&DN$JlI^$C6n#%Vnwb8^BUnCd1ab&@i3I@0fzg^r -PTx3Y*Nvvi>Yw<)zsNv0*HhSzI_wVK=jA7RAOhvvAF2c9z9#EQ4}PWkz~)>4$ucB76(xE9s?F#d5%MldSKTiw+GZjem_%kkSM{22*>XrDGQ*JX3M?YTN)XfTw$!T6#D9$?}{lD7|{UYb?KEeTH398qFOcMgTmrC4^6Mq@QetDAKxYUzdj3H%d8s;MkrU_MYumb9Vdv{Jkh|3Pf{Rm(e@a!XY -h%le(I{Lw1T5mK3Yg)F(JUtgYp6;ezV7^b+%PE-9v!}Mm6Pl&EqwFh`JxJM&%HCetk2$5igR&{E+GMP>Hm!)Uc+20u3Fwrq#s`|wGl3U{pXR7@rqxt>vyH!l|L -)hn5+GY8u`@^vDodWyT+e;{3UKbk8AvCagG1e?EhczTv?{+|J@RxtNUH+Q%?ePbw7`5{MAXofA#l!^y -Al-e(pARziTF0(Outut@PH>-OvU1K-4`2i{f)Eo+p6!r|KZ2#pML)3*WYT+)}A|m;o_ysoasC~y}X-&cE>px&%=%B$v!iEmJA$<6Vh>@di937c7GdU$SZB} -~5op)u<&dScYJ2%f}pEEaq-aWTZpK(V*V$=Tj&R?)_(f>64|I_*Zm-%lR6J@rHy~!FKGcGpn=JD|pCQ -h0><(8?_ZoN(NziI#f75v9~j-Ok7=uCDmH~Yg4>}6!vyV;jFu;1UnzM_GBWdr*I4eSp#uy63FZ-1+S9 -m|N&am(rTcXPMWZv<&T(1G&AOrd;I)UO}I9FhDPT`u#~j|umI!hN7}Asw3B@R$_DV-}+_InRa>m-gID -TG*UM+{ddqBh7UZ!5lRo;~vr^Mjm!CX2%mF#(VQz>BOjUA#;V#5wm-l${r{5&SdJ_*MpjDp+)1`1TL$@)`;@%Y-0?H-Oii{$XQgH5&K4e1Ret0jo0Ml -WCEGIQq{e6G*mGR&(oH7E;wa^$S*c^P^JUB_RH7CdO<^W>07-o;{qO{f+JM=J>!m%*o|$>QOZ-?f{OK -@m_H4$Qc?5V_EEaD(Lqn;3Km=oW{4^guyCF1mYP=7rn?})G=@Ziv>7fB?PCh+)3c-AsUa!ZHUC(^9Hm -zAf!F>gb3n&B6`wH$`Qqb1hn{p^FE~bV$G_)YTpoBsP_X&@mT0rqBllXY-tTqN0loU`bM%kpNr|b1T` -8hdrb8@ao|KHu+{f@dJR$y+X7wa@V2_HKZ?vTL)QZiM;RaZ`vGiIk|+ifN#*GaadOrs??H#^suk!6fc -vS%fyr(*0oQ~76PrluHe*~aXg)GT9aermGamNYX{Yayp(!2|zIhh&BGKA(MAMz*Sn+` -z!skymL``6KtOoZ4{jXAP*8SQyMqff8AJ`MObE7eA9%di=fvr|%y*=bq~nce^~2U*wOSn_kILduwuqZ -DuU>?AS~SCIvg*Jl8gLm!y((o<(9kz-~?mSXf3>7?e)&d5p&|*n@7|qsv|eKNxW1jSiIdEeUVh}j*V)@PjeTHNVO7?|ZapwJ8Z}}%`j5Qnw@~5Cn@)WUI+D -EUc^R{FGE);WRXZ|0e^}V`iK&^XNqMQ$Z<-Q4NqB3m%z&Jfne5sgF^Ael&)TH;vo>LVtj$Eju@>ciIh -*^+x!Ud>hzInpKdjmjGT-55*BQK+A*>lQ4ED=u)ybE2N-?lbVJ%sw+@R{~D%u`vT^=CPLO#EABG`qog -IZi)59T|#S*XrW$NUIkU0Yw)Hq5}<4i2bpS>b=ow_Km&lh`a?*UW=8o9GeWs=Y63zuS+sw-HQf&f16R -S^J5B$F%ehb!Y4hy+5e*TM68J1a4ZG(q5#q)2lzC!nAG1+NS6!UmxV_3itQkjBV{*7f$Ev#e9iQ!Z)X -}bRVbY>G?i&;hVScW-Y?nu@<_vtQqz3TDZbs>T;=n?tVlkZViKpVX&J}i)U5;(Y3d8;ekT;<|NO++-kk -s5Ejbj)i^9ZwFB-{Ej+25i;Nm$gf2$=ZdrVC^QhscuzKCzA-1G=i}o>AgcML$|Wg3u?r)5eSB-G{XqY;eW-WEA0cV_lq%WS)4VTO7!ryX*}PtY~wrRk?=OZKFxCiDYb36Lda8y1_wuj~$V -)eSbgJF=YblI60nm3>(in>SCBIRaq|hAWhZcDUlv65J!rBY3koCiM479uQ*+ko*}&>H6|IgL%3I|4C;El@l8m%MIuyPmwG -SN!x2u*^8(ih_>Me|YM(@vBUFqbeMP%t#BWj0{HUJ!ih3s71HF(rSw>Vg)Q+^D>_a#C1YCwP!t -l?ob_6aow@D<*jpCB?iPxx%Oqgxy@X0yU_s4Oz%=HuE@Iu -C|4Fj9AF1EK=my`{xOW2#Ni*7v&P;Enww^qM=T=ffSe7lgwbBn00)zavyX8~aWEFib8-A4Ryt=?Rv-H -tD0>|$YETAKW}??QZgz^|hDF`shp9Ir&rc#lw|X@!4~o@AI_;&%}7J7{p*oc5u9tZP^b>#Dnr)$c(hE -&DyLE`P5-O#R2hb@JDKm>=sEcDH9QU7BZO=@~m-J-y$aVyw^8Zs~Q?v(}RhtTole*14huPDDDPT3y{u -_0&f7G^GXAHGkAs+4tpAIf^<gbym{+0@3_Lqck7$(%N=Pb8y}{T8dYd0}Zwp&eSdu-$m1M5My-uEcllZfNv7RdY8nT<-BH7jAH&G{#QkXUy-Rh*MLv? -sIQAevq9rgBL-eEeXYwblmZoyhV;1@62$<~xkOApp^uy1Jl{w*wBZLNvNt*E^WU~QsWKHyueuV{A6tK -38J%<9Uw73CYi3@7|@DBjSnsx56t<=19N%iM+^A7fUc`Q ->uPJox~2q>3>jG0I-M=P{ooeNXtR?}Vk4b|G}y3QN;`|m)=;LA=&5j_KCZ;SZkb{~_HtG*7GS7z`;y# -BzGO{4|JjEu;eYlaOZdNKA97cn`*zsC^_Vdr4ip$w$N)*s{1@&O!iDU_s7z8V{=neV>4#vVm@ -;nixTr1+N2?A=prq9ZY~W*we -h6}%2Fvz&aOj%eT^J|eiRMgT|1ULc#vycc8WbSb!di^Bi469l=LF0MHA+c_XPiCLmN90YEp>AC) -QptWsPv><_K>2;6^=C8yO8!ITZ)zVgE=U@hnnvc6Jd9*Q%Xv%z~)BJ*zBYf#U)B10{nC%Q>K*5 -zGG%Jb(E?SpU9#zv-33DILpMFEwS3noJBXumP>!pQ*k&&>{L^HwD9OsN4tzi{_kS084SwK$g)vRA9+| -)lX6gYq#+5j34?u>BWcT}l9@a&2Qim5kOmXGQ-(>(wb^sP%Bk##mo~#wR{~#pP030Z*{4|YlT&lRZ7K -?6&fWz5O?rEHM(3GRXWD1YO3jTY20{dSdrh=u-jYFCCS^{^nwyc8LQ)I2*zD}P>^WoYS;Cg&9D)>Gbg2r=u|SEv8hROQm=5Y@`%nNr=-k`dm4J0vQj4HW -MoBU+X>GfC|!3xpCG9!Jt+ym)+Edx>uualzx56fhssMa*leBl;YS-j+%#B79 -U#20vEKU~YJ(Y46b%1>=%DH|`X6$8@<}InYdBXOA2qR^lv0a`MDNPZX%}3r+o+PR4JaUI8B!$`F51U3 -hYky@OB-x)OS`16poQ&M;EX+|!X`{x%=&e<|$=R;D$i5KuyOH4RC0U!}Q*#A5S;-PH%K9*@;aQVikDk*0Ri?lCV$Jjv6*r+%{L9mUuHbhE<%6BUJ|KZX0V-9}y^Zt*9Fm`x`@kHADT-#R2BCYcKm4^0uyHWz! -?>@*!_q(C$hxhp1Q2%-U^Ze(z+JicWqfoT^`nJ@%#O6>P4cuk!PA^Se& -P1vIF11C$%~Ww%x@+`B3Fp~`Mj_9$hKS8*mP_ely)Rq5ZV^3GK5vl(_qGO@=4d$MyCuFX{V0)_Kp1(z -uQLgini(!rU+ST27`*`HDPtX28FsN6Rx{EjI9kCcCqkK~2MGk3l>EQhN-e|7jj8voy&{vVD1|LcQ;q} -=Tr)c37_75u;c{NDasj|PvYdbId!ew2K8_9ylIgnA0D{Iu!6^k}+ER0H?W3o_q+3I;3KQ$eGGofHgGF -hD`Qf=t2c^Ac7l_`ZVs72K_0xq@32+^paR1=lP1s)FkjT&v(31)orGwSp@YEKtyoHGIxWcv`_v6x^@i8U;%f{O@ -W*60fg6?)cy4|GT^x(s+EW{R#b5mFsU3=BWHa`&~WXzxD}e@{#_=CZGQ{6#uPH)4Ttqf781M|7Y?3E1 -&=0WuU?He_SNi^jGsU9$sQNvX(TT#3l2VbW{E>d@kWL&r7)Y=?d{lY2?BJ0oPIZ9nYP5&e%lyi!VzUs -o-1J7~Z7dMhbuQ+0)N%zB2rKCqE+{fDKwGV;)m_QMrPb -l|H>z>3ajztF_P7gC%@F>aT%|)bp!K_hi(cgBq?4`-00#UuEOGn>E4mKlV^9f0y1vemZuD;1C+*uJP5 -eFoM0?Fm{pLg9$!J?^ZIeBDk5}NPnGxF9(tiL+-B=0t+eDf3!_A9ci@C>=YAeHS$75BAitwgh)6^KOEZaMm@2Sw`@UE;8;0f< -MtapWJ^XIJB#D4=4CTS4pqGk@!t-5rqjQIIBBj%gCHg(9nbQVloF1>`(7zZwf>3;T|&0)dX+u#h5>(8 -BcH>y`PXf;BCE0w`#6q(+Pe^FC#op5}epa;yjt)+w_K!c`HF&Kr)TY(+J*qz2qVG^Ka}+IySjupTt}J -NFO6}c|UQkk)Q=|Uw;`M_fCZjkna5m&QRt<1Ya72Hi62Epyv>(CuH^~I5muPQshgpG)$(mjNk=&^6C%s{02F$)e@~w -nmRyf5c_Z)&7!X@7TKcd%0?k5TU7A|oJ9!@-uz*$BQEp=5yIzanIqPYA9YNn;}lvx;EQD48bU$W -bypVD^oqS0i77Ki#NgmEaAdmHZGK6G^lXPs{{wkCf?8C%7t7rU`gRnE^da(!7G;aeAjyTsDTv$V{?C= -0t+enH4_?o>%6J1Ru7@xT^^+97{4r;R^`vzlr34%z$6tB+JN2&>l_YOXhrn?PFB^BG_RZnaRBq!C|p7 -op6H1ag6;+<`ROZZkF}p6M}pS$#yfs^A^TBPNi}sb0>mhrb^t*SVf&G!yh8}6m^A@Da;yzIFTFf;RM@ -FCq0+kg9xsiF5OoVte7rw13WZ?lSa0}k^@e(sh0OeDDJU -Kw{Y!T$4!51@+0nat?~Yw2A>VE|_@p?XK|IRtMmqA@&~;|b;$NgM!QSLS1I -FQ)M+g#j%18_`QqOxJ6P~8@$(%^=&8JnHK(NgknQvQyZJuQ;lfv{P_{?+E#*+C}fL9Wq8tgPze -Tb~X28)KiN9oyB>2uo*%u!pIB1i^A&j8UW+?-Dz%4YkBr{;v+r%p}=Ma4QZN+DT_IF5cA@>yoKclyT% -oPNWY^S!E%*P0hESG&8;Mg6~-Ab_aZsIeU+Y)@4-XQ1z1bz2XS&-SE;8J?y$-E43Kk1=l229zn`VoTv -q&J7$%L$G@NHii0!N1eH4DJMT-X(g-TtM)qL&RG$ClU-iOf-@?h~Ol8H;{QU!8wN|jrc|Z-x@qY?FQf -v$_$8o`fG_UKM;CYk{-E3eu@4hwz@EwsI9i#p&oV)oujP^7OnZv;pyEtyzbDZm<0iCj)`3+E&BQmQhz(}rz^YC -@2*D}cX~Al8*A5H1sEK?JdO6sl{Oj!t}`Wo^V=%(*qUe)vImPzU%7jL32od)YsX|7g9UmU1tw2lKQ1)KlT#Y1M2MgZ&P@$I(x-7o%ps| -vs<_8*llk4QJFI%y^#V-_b=$LdecMU7hhyswrpYL<>l< -7k3M3TFJESAC{`c>lLZ%QF@JXdp#pzq*f)3XQDHsGf4o@We{t^I`STIrM}FwyQGVzwdG8~;o-997T(Z -xd3%m2^p+kp+T?~u#^zTQ0F-`c-onNj0jw18D%0Bm~z83M*(k@E-B7{G5G%fAuK4rfs{WHn^BJ7m@Me -@f(VHU~sr9Jbk2!H6%(Y?~)tVsWJJ3)$R49S69GfL({~oQ{mj>jc(#yL@zI3I(j|n^`X~5Z&waE4^XoHdvYEY7RK -V@;@G4~lh{4?+*41+SFBjUUViyywqe5tA>$u@_#r!U=8Q|ubFe&2eSXgRO+F}zdvosP8`HM4McaSeo_ -1F@TSP^aww?1u+p7{t67E{`!*2;2$*}0DGq-boA#K5dGgGEa*`5{~_6os-yJGGh&-q&kd|_#y?YB=!V -2k*3+uxdk^tLQV`inUK{_NTHOh_^5eG9vGOe{J&TJUbdgbD1HTW(>u-g+yWF=GZ>m@<~lnl+2vdFP#M_Uzd#H#b+b9}5;NV9(e`vsdm -J#ok;P!3t+t*s{B#*s5$3dptLiy|Z`(`+Gq&du;JcwrO<^+k5{wcI>&kSnxK^`t9Uw&|c0)@8)diLC! -`V;cWV0&Z6GuY}_f%CVb9W9=%h({EJ^JXQ?1?9yU~AT_Vb -4GRyeQxG>({fl-g-;4Wm~syW$*1;&zAhe*<00|?b)-39XN1+9X@=R9Y21Yz5o9E?9{1K?9)#_Wj~%d$ -=>^!voF5*LdZpBWhMLd$8XtJ=Qyjbu4ZS?o)xl!$73M%5AjTVC#YXRI@EfpGqj`%Y~-!kC_aeY&L^;W -{BE|IuVioW*V+60xYnMswiLfR#Sf+UqbPnX#lMx}&!YJGycJtY@t>ghuTlJRihqLQpLUDio8m`O{M#t -Ljp8q-_%BiXofQ8##Xm{$Kco0xQT$4Z|0Bh(af?5&H|cjYmd&9t-U=GKtY6O9(U+)C+eMY|L@UNm4r2 -V%35=h?lcoHH&7v)oqLq% -3*xMeT?7z1mi2;VEpy{j358pExwWB52N_86n`eg&!_myDgIiD|0c!XM)CJie9Cj>Clvn-#jke7??)+& -qZBeIg}+e>&r=G!DTOa6h3ZzE{XB@XUng)@b2n#aS8{glbpHTcNxA=O+fk2Aik>Yow_`NB9KZ<`N#h*s;@22?6DE=rLL -kgrZFb7Fvs={?$xt<_ueUq;mJ+u_e(d12k(PS{26GE;>fSYbID* -u?cC`-&}O5PG>8a>98&<_E6_3GYD`A5e^nqo{*<|v8}yax0O3Ar($4#}9f80{I`F{oX_fB`avp540{d -)$QhlzvQ1^yrv`&TZNUkv|0pRsnh^Q2fy*M2?C{=-8%Bd+9H5?AhH&NJhKTPY7(=rp>rfB0z`;(9MYW -QD*p>;NP}Q=WzT9iHWn -sb*&eG{QLA3>5qvs$5`Ubabvr52vh;!e|>QOSSHht5?N?mOBjg&ihm;gK@oAp;y6o8oFGEvpz)vZPcR -RP1PU>fMu)Lu+q7vz0bKqG!T4zy4PH3nZi9^h59+2rz17(Otdc{81^m6b3Aig7WcNs#LNO*ae;9n#!W- -#VH+qsEXdhlNE&)%hnRj4_RmXzAlUjEqqUNIlwImw!SOf{c%7;o%br|ESRg^KWD&85XuWH;QdoVqxnaTgY -zMPdWv)bJmYIyX8x%GvpI|_~D1yRWoQ~bYC{3j{?8x;Ql#joq<{-dY-M^E{`s;6k@I(O*MK`e#07Y9Fg2m?sKeNRO_+rao7X{5Osar2x6@M~)n*XbMv4U<*C?@JINEvPQ?;x_9s1JqUtVEw3?d(CxS1K6)5EWbX -LUM<3l!<7mEp`*!}#H{WpTPx*%*euxPw{`u#ha~gkh_!AAB`q@vNPG=?2`o)P8C!X4|W5=Q~W5x_&vY -hbAKszS4^aB@^tFL-t);?jtrvjZ3cJ}PqLuzYl&oeZG*N%UC;`dRigJDSHh8u3^4g3f%f&amS2lf9uA3b`M|Lb4>5_nS -i2;5<=sHor{fBdnqgC^}Ioku{DxnF8Dzfy9<=2^Zfbq{O6y4{^f%YKG^crS6_Wjd7VQzqVqwjZ$k --(<%s)7@h5!>dAVNTuf3o_dr>CZ3;Z=YG_&?Lq(yrPXV*(*?t0zb@%|b7ckSA>jLNY)$wU!o*tv73z# -sB)>eMMw248&fg|Guh)CsD?0>1a&d*VgiKzRV7+)#HA=iPVT75JkJss3}6!{y7`E+hGn{Q|jPgg@2c! -G8q*nwlC;-H?#~@bK{7kYl{S|M20%{GE5+;Z)|FbY@Y9!GDy;fddD`3*7hb-!JOQ$&)9A88n~`fV`oe -pp1aq#veFOJ;nLxBb%^KA@cQi3d4<`3&d3)zrL4b*0nqV(y0jwr$&1_UzenDCzLasl7y -b2pWJdcn-dT9+U-ehZ$x6*=L`L7iRDr{06>|1JDAz!GDzTv)^%U{*d#?6P!mJ<@|<2oDbPgH0#}n(#k+_AF$1*^nVax|1vvfd(pvD`){OkR#yO7#-j{c#blH+(7=(?$Ve|AT*xvANR4OV -a)rS-$=NQAR2~}+~k~;_-DMwdC#qC|J^m>Pkjd0+JDGD^+z0U!!pt29DGGtK&~Jg8Xc(PkTb|V>V*bT -FYrwIg7ce+h8U9nu^&kqMjhjP_+d#yDAk32J2?;D{>SmBws>W)UcH7=IV?v#L)+3A9gy{=MLNu)Zo?pxJ6TOc$h5xy8=Q#B<^oG9tRr!rRp=u%t)<1ACUDKZBp-TvOf3UD(f`0xQ$(wD)6UrUKSP>)}7k(BH -#}>X-Eg;9&+7~4viMbI^^i{LmT*ub4T*$?4$Toc{d6g@XV;>{8mLn{O6p<5>D2Cxz%UTpz8Agv`N&a_ -M$ea8`02pvus=GS=<=@-Me>p$v^e+=;yB}57b}u2U=O6KESTg0Q{-lT}@K@%Id64bKw|&)6jXc|UTVSVc5AIiGq))@QUyF(22}XV9S4XV9S4XS7Md?}%j1HpC -zG4E+n~S*{l}Xm3M08ml83EqFF98qVLCKZ3t@PXvD{pJ%hPoiNn(Xe3w(Li+p^%*ogt7v#s(Xa -wE+&PB-EuCmcGxLQh7C{3Zv`Hh}bc~^U8kc{x8R)~HN8nwzZXJK+l~*|QBJFiAldIK}OP4P3V^1aV9f -ddWt&52Usta!t4gXN}8Ew)t_8a+=xi|9FIivZitVn);rinX_wiR>eO1tNEp%wKAg=THn|x={4ZNIac!y{Z^_gfuo21p}HPj|Okvp0{k`u`vBpQ|z4NHlJ -Vv?i9sb+yF+9Z>kj`3zyJfh+1G>cGN<3k41C^7V -UV0*E@*aE#U1^U?0tTsj8obD65o4W6s&KfoVlZ?#%c4*aE;UJ|+&beYo9Qa)$S92X5&qvL92f(LEVhK -1KqpNWP>+L@-F_TbZs6)Sd7+sXU%>BAEe68Q7aKhM!G=jG*bo6W|TEn6n)^{1bHD%u_BLXFV^ywNwHt -%5EDeH7yXJgCn{U$FCw=V`q0+#FYZ)^va8$-@0|Hfpne88vgzJ_aBy)mX?*0l2S$Os3?bL -pM92(A3vT?nly=*m6h>_AAXpxTD3~xgYiA^1r6v6&`)S|UX@37ZeomTW --09_vq1sUw{4eoOEk}3-CbQaDPFA_BNyiFJwwf8*S_1Cuj1l#gWuMOC1CK^D7IY_@)PE@sHo#$ZLN4? -L8{*Yo32ty?Qk-Dk}PA<;s@SVn$LY6=S>ILWoZlI$fEtrEqKMc7?AC0<#`3Q_7 -;RYQEWd~Xsd5HXTpMghzbp8jCsqU`H$;tWr-h1!83_Pb!ohn|`X^n=yef#pcbLWb3K%b$_?PzoWU*NC -J`C@DUi28s!g8I;~zN61}AA4Z@%>Ll~8|oJ1pJeHo#fulek9ryt6T|1vpD)S*GywlGW5)1l)20b`lmT -eaUeKV8dodP+ehs+?o`8@c(B>{%(5Vof+L8x)_Ut)q!-fs_V+`?U@P};Tg)Cvd8E(J}bIx|VUDV&Gs3 -;y88Oa9>7$C|4{)p3%j>cq5s~Z}G9HBjcT%kQi*=h3|gcFwAu66xF;7@cf8#ZiMcj93Y>hnMU`ACCo_)yt$(k-y7Skwn9Z%*c&x?I<`dD5f~RQz-+1E<{@QD=i9YZ -j|M-W92VN7OoYc0>pgwXw;l7jH{>J3G$F;8C2>lKE588jKkIP|4eFFq8C~wqRl2f5e%$_}4w5?RvMgN -Aj2J)@RA?g9@CCUVHf-*px?5+>NjC4^(C=c}KL@&NrM!V^Kjkvq;cb8Rn8FrU#;0C%NL#PXX``h0*wO -OK{c7K4I_97128MH-sAU_xr;RWuX>*=ST#vJ1s4-bz&in|McP5&o;7l8(?U2KdF@EdIq_1$8e0GUEx+ -sK^0z#nC!=@3}gf!sruz;o20)!5Ac$N63le|Pyu8_}?jZAc3s=GdoCpUxk7^*Q{A1;)5=P2W`L?vW2!2v}m}Ke)__nl=my}2QE~;e~7g?%lgrw7 -cK|>MP_O_@f?>Jg)rC#9iuUYS -l1w$|7zR?{*c|P$$3-q?%o!`Uz@i8pFtyJo#f~;=?G3LEA*=!{$kwm;P}Mr{>?A)z`lpS -xpOZvAAWiC+_@EujjooEOn))-R8XfN`-`BiLHcg_7ARQ$SKz-$K`xe(7r~|D -3-Hws{i3j_8eAMgBuA(bqzK`Am^wu;OFT#!$m4@?yO+Cmfh;-jj>hoh#Q&T7WzxK{GHj3+t<3kb(s#M -iqD)~byulxxK5tdS^DD_hz!B#6|fpAR;4U4nbYy4iYY>JJG+hQI9rzI`OJnWsodS`a2p$RyT@AT1nK~`@1vO9&a!PRHWM5SDZ`_6cm!%CiCCza!p03xL==!-E^Z!R -4x24VPmv-|tItDWZZv^K8U!f*WE%x0pA^L9J6V{uCo!-8Edtm?m{e|#A>_q;6f5fiii?G$ivcxHT$9v -kEp-H?Dj0e1vnl|_jHF+?$ndp-P$Q-m%<_l#yzbDUyE^#z6(K!!sledAFefu8{-Un9p^5~sP7gO5JBz -;iyy=xzm*E}E-{MX3euZn0(p{irD8Wf)lR(jk>f~)5nj?e7~}>v9ZjH -ck6hTS7PfEO;4#>BK2JAh+g_&ui(euO47$17o!2|17kuT=l{a751Kylj~6RV#^2$E*6rdGW}Q=y9lJR -!zW3o&9?-|aBz??wG1JAaJ|^LGioVQ~0tXKsEcEgLZS)JtJl~{IL+Vl9$ZiJ6I -cY0=&~ppSYr-p`miywryoz>=hoUn;;7s^>whOgp2oqGf@vkAHkTwHd0 -AH>@~E>JiD_33W6zW$PDPJlD*c5$+8E)O)NkCN`Q@_6sxQ|G_V&zOhA{9c^Ji`^lonJ}aNH-Dxu7O=Y -SOCFkBtkKgxWB;JPd-v|Zkt0V6!G?&(;X~)udfgy=APejf-_gKtPi&c-6L-=_kFMR({u5(w-MY1M$Br -Ep$YI~Ueah}(ckzALe{=zPpo`Gtsf6Ij{Z=;KqUb5PSo#^hIWE>6_ald&*8Hj -KruUMLWw*Ds5kf4efNkDDg-vAtD(w@ZOK(@0kl3w8lDlSUaS@$Tz^l9Y2u%(tgOX3IXMm7M^jT%)%x}8-}3wYV&le*st&w&?_O2A -(76vX=N0+uwT;Ll*(cNJQ=WH8>egN3zXb~xtdjFy<$?Hma2c>A8E1(#Yu2dyp03uMJT}^o>$R1J+JS6 -_2mGb{p)KQ&VX#E%#`*dAs{RTOH8nL#?$`s3k!T&zHDs+1(ocL^US3`zbvZ0!Bp&Q0=btI3N<&ss_HC -u5rN0K_p+>)I)hfYQfUilvJb@P=cddtdjfj4gzUpHs&+p2`Oe#Bd(jn5m`d8b5sy+aYgix#%FZbZ -`ykhZjd+46qAa2QSD1yN%6+&vWO_?KAETqfqmI&z?Pj&d$z4opUqJ@K91xBIeJZFP1M~u6WaRKaCG@9 -(tqg1?v(|ojTQNIJv^TG-dy=KP@dSW%Nf)9-oiR0b5wRcCEsgnHQW(Lk7CWOS}xGLfkIz;n021o}iyx -!}8W7@JF?^waPZIR#n#(882kbePfgR`}>dICv8O^I?~oC`+p}sbPRm*Pw+7tJGFEvo?47QJP?(XhJ|3;)Nb{bCo52`oHgayTaKBY+4VlVJ8_-i9Lf8y+{bgfzG?ZC{xEI2@1#CHb@b@bmm`tLzod=2aqQT!A0 -0n_e2U$P$Ky9;A61rRssGD!9y3idzoVn$Y$B0R|;>s-x%|d+!%d5A -#;$Ge{|MCWy|=w{hI3PjJ}?8(P?5h9na`Fg)MvTeJ5ukKD{Kvny72+L3kF7<9Qe$w -&$!`__RAG)!od==x|mN{7an9~Q -jIJpgzvzTE$z$d&GLgn#8 -)~8}UOsRUhOWd6EytZ}sZc3g^a#A&0?>o#J$JrphOY-HF@D8_1m+gUb|+m46YUOUk9ay -@;Kf=Qktb)b?d+{paEOZ-}>Fi*o%d`uekH&#G8WugAe|6LT>h*i$bryvLV&^`Gp7p;Ywa<^?{};dJFU%j&-AH%);{N+=~KI`%`q|p;c&2GeR+ -egzM{M`oSDA4w(`kvacQt35MEFbDz2>xS8dp|pt!2y$w0W`iMmDUzKTF)XhSfpe_=2)ebE#C^sFy@-s -j8MRJ%E>ev9xPdt2;WC$9_#i#OMXHZ{1P<$r2}HJc@s;PVBwp}J6cup}6s_|p6J)AjOFe)#a4!Mb3%u -bjV`>49*5WnI%+&1EK< -0?py@9XS~3`jYRM+|eWnPR4zK69SwHW}b=kO3iAs --fT8o%{Fto=;dL1*#7_Rzfem91QY-O00;m!mS#y`)S#&iB>(`+m;eAA0001RX>c!Jc4cm4Z*nhWX>)X -JX<{#TXk}$=E^vA6J^gpvHj=;VuVCxVBNa=`M{~V>e(UZziPQQvvHfhP-Cf6JC<(F|OC%pjSy6WPf4} -(vK!5}#*=_gs-Ys7nOB65|41mFWVX)&39}m58Hc8U!J#SG>hrh=+y`A2U_bSU*MRI*pdBLl`_x$OzZ- -(&Cx89FQ8n3+b_*0UHQ0#3o7HKIaUX^*(jqpxa)lHUq=h?Jc#)a_SWQ%kXR|&j>52Em5XfLgbWVEO<_ -;y_gF&Altu+D|>-X6U=JU%}RtIw4erxWkh$??V6(aZN2CuisQ63X?aMK<@MXu7Btg@__Andezi0sI8I -BBEJRi6Wkry`KDG6qn-L7wQ|98JFrC;3leMF7)@LtQJ)=D?|L3n-cRlnW -1zQC -e2w}Af=*`jF!{dW@hq4CJK$Q*4iMT%Y0$jH{{$VDtu_pRe+j9{41kW`2KSD-xD*a4Yk627FSc~K?n -l}on0o?P2hiu?_zh|Pb)`VZy$ac&l2BNBcawx;reY1zx94RJd4iXzu{MU8iq6^^f9`Ni@-O!mS&Tz^7 -}}DG^@NCz#b7RFXHQYya&I*G=VzQu+@tHsC-oV-XrRTNwCYk=U2Vczd&Ik)XMjEjha2co_{jBVHM~do -W1%1ez$oF^+gdY=!b=XFJlq-Lmx&h@{Kp4d;^eP9<8Ov`3aO@B4APEobcBg#&J%pd8mexL1HQ0o-Q(= -(dZjPH4#%Uf;E-gi6|Z=fdqn2j%@&_ORO_62ymr~@D%C5{d5(*K79H9d*6Vm;f8UZi*yqB6F_Gxa6cV -g{1Bb~<=w&Cwfrp)`#XMzIq>r2{17HCAq`*8k4}z%J -><7(`TU2&mj}n+w;CN4{)h7M&R3T|e<(hrSC3(!RMFS4^&Gu*)%ZD}I>FbMKYx9-`}N;HobUGgAIjYz -eBA$?0pav77eAaFL-mor^A)cqzwzTw)~`#SSa(*2rO1hOK9P*G$|8~paDRyBqe<*#dtPQNNp}Igni=E -(0RlC8<5^sm-a8P-U^@)5(I+vk`o`J@j+azX6qI5%H5Lti4PkFXNelKHXdRmS+du5zL7tGWHN1E-Ay5 -MkH)Y(+1|EnFAS(iQE!Q@1scKvS%(_my$#9XQ_!by&YXk~02X^1U!DzRxgp9R?M3`rJpsVRxz_Y#Efp -_O6sR6-e1TdVp_`Ak)uO)Th`qn>R-d@#cPm^>K&9lj3rf{DwW;3QAqO*@;fYA+TOchwuW+BSOtdbqAN -uZ}vs~on{|<2^WmbQNI5AYx?_&3IJ(Gi)1z-u_R%oz$59Is-hAhN-s -D)JoR2Y|IdD?LC^WCRH_My&`G0)t*Yne2X{4$MBp$6G7@6cc-G2jWB76qm$# -livjJd0NfzJ_uNiZhDFh?e4%O2r8uEsZks@z&O^Y%(XEdT(`!|Ni0%_bScn)CauVrDl -_W|$HC%a=I?&sa)^2*^BPU -C})Ogz9wiPnNa?>6k94|x2T{~7ZGOX=6#ta{3^G#Dn;2)*5PCY^n*t_qn-SPMen?3JQLEFMQ>?ASV__ -fy-5h)&(_&1Qhbd07sM9$G641U#_+c^V&@qANUi_3wIeHH4xtpi==WfE|*O$BCfTq7>*8dT-Kl<~)q! -__Dg%{fE$AL^;NRs%iL2MYK -=6US*5bU#F+0n%pRaaTJgkoXMTr)*9iT;9flugRu)r^R$z{Zkc_5{r<6(uqM8U$@#vnU)7%$;tH1o-< -eo2VWI|&uunFg>VAsQt-vi?N*6^b<(7qM7v>q)z~Wov5SHYwz)kO$*3x)K24A2)G%k8A?E<+hyXTM3( -=l8B#SE4Rv>lZWq~#zEX$q-6q^dNM}w|`E*4|f(vpxC_Mldj{lf3#I{7lS3Cg0dHl54GfyOHPm*C6PW -`)Z;C~TLx^hx+0N|Ut76k9q464H>st3?jzUM6W3Bz>9)G*^~)S1p;!s`&>gyE`TmNq@@nZ|}-(S%Ye{ -T?J;5s#vw_sqUMbsCK>ZOj4r_a!lIZQE14%OhcHiY0!d3!#0|_*THg%qrlT67){%-@zGkP@`#WS1lra -9vQa~jT;u&otVomXQg(lgXA2>9T_yj}Q~>@b#_y4V+UMgK -Dv<-La8|LKRr!?(?4dcQHJvF>C5tc@{>8u?cS&fH=VZzL21bhg`c;|eJ(-qed)maTIwlfyY7 -D)N${R6?(`z}4u(r`DR3U1kuhbx@j45O_GzLM&gL#NXdwWP^Tii?(Pb*QV_R*(U5OF1g64L0r1xwuk -qNQH9tmcyB63#aIHPECabGU~xtyEUZpno}hcC`h@y1MTgIh=BKmv*U!RaR}JvdXSwwAV=2<3dh_5N(I -S$p26@l#to9St9jYlmRe*@SdVV3ZK4%I_<6kK6C0S)Zc~bmw-_6~v-S_P@fquVP#gUmrtsPz=rl)}_Cn%D`h_mm5*n}w?M;9TdDmr}hk-~vo^ -ne*s5sm~I5y@k(}WQvK#;w7cAXW#YUj$_;Al_tR>qRK9Wd}~QC5MY7)Qc{i(*#BQxT26eIZiZqyjF{4 -+{*`$YGl9`v8aHJpw;2$4S!GwKP717QbF%&_z^0{X)5c_)Doi$XE?T$QVLd2t8Urpg?}@11p`!w}N7a -@)b#(B~U6c)@F`!sdR`1YELV~jO;1xCi~4MmE$%*j~ObLzM~8_8ZLixJBY4(2Br$uvYUsHF9p)UtT%| -1(zs>$k~9lkm_du=QqLeE+udTr!t6b&Pbb7nUsR@I1YoY8c*I+6E{=|m=w=nJg*rnk0h8g+?(OIS5B0 -4@CM(J6=pZ{8kU9KLo6h1*UMd^k)BxY1~F9r!;LSw$L4m66_(vNGcn@#4I#RDF@e4&?84*NtvtY*&0; -O8=1Mc4}@l4l5v6V7nhXv`}oz#W%I8;F9h~Z24vET2TE<^M^N{IV)p@zSDa2dlFl}pDX2DZq>wFPoEy9Wid -$GTSP8dU*MaXw4a;I{n!}9>@WQN7n>FPe;a!0y!X*!|f%yZ~capOW^&P%CHTSL5d^gwQolgI@Iz06L;5Ivm2@jH9*RPn3=C>eEsIwRZ9*k@-{W>#L#h+bF?7HNXt-s7R?FKe72T-n$J2}sG}_NmWB -zXEf%duak)K;YQyCwjP-Y%EnBOvx$*{HR^52ZP~v`YR=%A&90fp28A7~zpE{(3G_jvFO#-gSUn?{;J0m)Pgx3iOhd}oHLeI!jH|3zfg%pV -6{yk!TAWingyQ!sU>mvI4!Ug7VCQ9v%1g!P$}1O1MUi4Nfx`;>^;l0ImkCDG9XMf4T_7)1#LWZ#0)aa -y?A#sjX(=0;+qQ}pYt>@KZQJ$S{IM{V2PP4$XgwC8cTkovCFv~CNO)72>7Z7IB1Cji!o+PHmb^%_#q~ -|Sewl>(Mk%d7*-B;ScpCVcmnR?j%+wzid;=B~L}QN -yR@`@6It+1|W8@6jZDG$BdMj-Nx*3F)8a+rI4p_rDh@AFbJ=!3D-D10l_mM0S-kn1jQka(K#0!k(-kM -eDlol{KJEpRjf&qj0*;_o?ITFXC%#YgkL3Ef0N7@^xyu)8|v~e$LHLZFFdqWq>x8{rjr%5{Y|h@%YHC -HLhTHK?#=^Kxq7aN&+#E7xq?AKk0u0&61`!#MN_y>L%((WNs^ADALjhWo#KP${=XlkNRDfs3x{3z^v- -vP~1Jg!1hNc*z}0~5=T`XBSQv`F4IQSuBpn^mI4{GYTF8sM9{~_k8hXgRp^Wx<#1PJACSi+8-{_hoxI~|TrF#J9y#xECQOoXTq*kkBd -3`J8XAC6m+BNJ$|ky)2BnX_k)TEcHy{%P&9MaId|-Nv3z5g^c%_QTT1FVu1-3N?ac_hHwY+~Mu$8cx1 -jzwtlEH_K=lE7A%eeu^pdrk_2n8^rqIUsW{eT2A!ay1_o=5|Q+<%~-hSDidE)I0prOpSFjYWTUN6^o; -0HWEm2@JO-Ts4(Q!=BM;p(flNdgz&6dlXW=_tAND@}m)VoC3=A^!WHOlf-a!oNQz`H^z84vvM^2@gs` -JA3xewrCO9OR#CA)Ns=agD8JXdl4dOY%u9i-3jEUGs0y`4IrSR$We&jZ{W`1vU&=0_g9bLm9<)MQj2+ -3^TQ)*!?P$_Jv9X>uSl2CY5z@5LKHL@4WX4uFy2#NLONZKNhto8pW0>mOMO6^AySmsvCpIdus(!Wa63 -mFd@JezJfZhk~Y6AF4%xtc_XCsBK!P&N{{tZq(r| -Yhz2wsKo#*2-*RgieS -majWBo$_yJqd7{W>vr#<0rPBiPoV*ERG=_?}J>#LX3FzAqOShv{MH(Q(dz~%tfLJQ7L0-BOAgsO3?!; -{Ml?^NdPAC=G%PXpgQ;C@=?f>YmZqS50*+g}s9+Y^ZB2lEh&h&BPv&@QHUP&tXLpZ;k;OB-d5a=K(uY -!G6pt;0lciKTZNvl5|*o#U&;jI5NVVMOT+{>eCn?pm4`fv%5yl!L*&?c2lAoMqyW?v* -py=?7W>B&D?2w#^XU$?vmO`rr=$`r&4i->~=chXWo^6saN4yEu7%z%Kq!mbT{C$$4D9&0V@!Q6#HmIl -ZH;jx=q?Y<=*>azlPp2IWALP{gn#<}|+zgqlxXa7uS8Qvoil83)h+O)(3v-cfe7XX7D5_wdloSL<)A3 -%Xi}3MTNY&lwG}a=H&(`3`Q8_H$@5|CkhS_h+Fy>?JvB)xv@8uP?lr~@Rgl{ -DCqP(u4nQfWQjOs?~mmZ3)iiNdTT)2w?l&(n3~@Px8&NXu!b1Q;%AYeo_d{(j7;`whIOO%V5TIcL=>( -f)&YXibf9{%am=;Jbz=oBCW1T!0w)?-B!^K^J8S{6&6^z*QT7%(TDXDLYw{3nB%X##c@u^3*_w5$2UZ -j)0>6NZn;q7GzS@^G+l(ngW`>1<5a@n|1pidENZgIe6=aEz$qO!AM{-AtJ -Q((PNb6EY(!5z%_RaL*H@Ck(R!pdV~L9j1z&XF9!X@#FQ}7$4NgTn1OE -skxoZy7I5gq!lt90s%e~G -J?nB9FCT5jZ9YeBoQ@Kk_WFG*^iKt+A=#OaDcB$a_bkD~fPkNAr@|L)V5VX^RJb-AU$XD1{h{-Yy6HX -~a?NTzmDmEN+31PSftn88ja{J_1BVQKr2`>=gb*RogcEExSJN9PcIv%gs=8hiC470q*WlyYRVsa+8fT -j`ZARs;s^D!r)qC^D^6LgmZWdqpv{Ac0QjuSrIz4x$b%HCOV{>=x|k*zT?iWD}QD2KOIiHTV|CJ;Uwc -O;8*h`ih6-^NjjE@p~Ow=ckajDcy`nVXLl{t3*q%w_`Zqt}aM(>{#PEqjL|9WM9?p=~1-Q3f&Y-geTE ->eHo_K$8-0aIQN2D$cn5QMUg5HLgo@Ott~Q<&lEOE* -I>6`!tJaNKT^&I$yz8_8k=~4F6-+sqz%1r$k%lrHgC8x!DeG5LeRqJTkS*IUgbG4+%A0?FVZ}AVU_ea`MC -@lEpI@9n?#uViv~52k(5sVzItFamW|>W-_M|5;towWA~?^uvU;0Rpayz)DZVxH;=3%Fa7-y|=&AQS{Jck|1sTY&^Zen|m8Qw@LzLB`3GE7Pp~E&KXV*+FVucI?Te5c34Pi=ut%kcO*Ve$?!>%n -*r>48=OP|gGRm!6Ra~o{6GhNih}Ru)uP_xcw+|nqIo*S_ZFO~VQm8**Y%P8OsPjC~WjE+Me17l?` -41^onWE!*Qv$iUyHE)pQ(O;z-2#Q0x(3wm>dvkkPg!?8)uD5;oa)@b+cbCak$KwHsUM!oG+g&g1c+i{ -a}#N%>`BUq?|@21v$a`oU(3`KWS3XRS`toh%eCd96Fg_INXcLhJ07excg_llkE4AHxjE*Yj~|E|pBe> -885pw$=D;dY`s3+fg7Z`42 -2oHh9at#KdUqUBCn?SgSpBKrWzL=SU1ckw~^sRc3Ty)KKiUR%HAYSI9W{%$c>jEo;!&J#VL^2QqXXf|dp -lgqlXracB+XcJh(cq?#v20Uc+B^Gk@^=R@P7yk+>%fHWk{rlY4KjGZhj(2hL`a~a{rTL$*J?#JWQKhs4F^UZcv$2?;eK018bADi$bAln{H6y(oQFb4eB~<(Su>nx6ZEDQtkvT-ER -#=eNGkE3fRKib6PuEXXNJOyr!mP#pNzd_yb5W}s>bADJq^stA|Vq6aQY-p;4ntA#6_LJe2=u|Td}fpC -di~3L*_uzwL>?)L1~U5;=CiHGMgjZ7qZQaYT;2Puxka0a&A`OB~PRlvz5MpPdoo4q -S9uDbql9|`hIwH{N{wRHpymOAlHJE9Z?0^`77;y>nl#M!aiAjm15SAg*jb`%5H!qZNl@2YuZ~S8)R3L -W7~tpcT}=*t9#JWA2pf0eT68e{C8f+<*G&7i|)R~Sg;m7ggS+HK0Wbfc6kV6>u@y5{Dfs=0E|o@XeaqQb) -}%LFqu3YlY7-aJr?=Pdxz$`V-u(LL!Pv21`M4I^m2ghkUNe8+0v@eU>v46c^juYDsZ^bT!Ico8=DyW< -ReL+*2Sx80i^?=`3_6JcnYnugwW=4uQr3sUQGN1mx?I-#@ClFmkwo|2OE@cEa0pYA2SclaPa99Of?D| -*NT7(NUX8XVRxZCKOeSyqZ*>M*!;^I&Iqk0Re1?%&1#jsaL)XLnX?Oha@6tOvnP`6?@Q$_wggUL`==q -gpE}cBoygD=D_-h-(%ZS8JzKZIyInVzn4`XNpq`K%E*@(rRP1tyh5z{lp`^HA*F$}- -j!1~;8e2JOlo9NV>$G$Is`RGoA{s16DjO*(NirTsIa#C<|ItvfzcE^H9}zWM#PbmLAwcKJ3WAWg?<+&qZe2@U>y{{6Snw=cGY`^`GQ -01yD(7bF(kjO_Lx(PTTj2T1tDRcexON498dktVo~<7Ruy8}x|r_tee -dhWkPL!fj7h(2#y;mpiP7f}QUcNnyE)Kpw=M$y2P=d0`togdf5{w;$DZLQ-emO}LodHW*OJqTcLoqMi -Bh_S|-*cfp4^~WfZg@%Mqr1+s%jlhy{2r=7_XTnCa=1pP^&q#eX|Qi-NGOy`-^M3zGNXmgYnxx=7q$A -0xA4t1psslL`c@b_j`@E8P)h>@6aWAK2mm&gW=SLOXZ*te000sJ001cf003}la4%nWWo~3|axY|Qb98 -KJVlQ7}VPk7>Z*p`mUtei%X>?y-E^v7zkHJm@F$_fS`HE4_5CpyP2R$J12Pm6(yN0Y&#SYc}Jubb?j6 -9>~7~@B;vyr0oD^tZf`i{@Twk|k9ElbU#qVY2FVA3b7eSpPiNN$))=+cf9!dWW+a-E)*mQ{Vz%UGur< -1X$7d3k!qg8G)emRx?WeOTIAO9IZmDwS%OnlA)aSVwNRd?W -Om6nf@}gG#1N7yaiYac~8Wy_KJIwl^N+HG={s2%*0|XQR000O8HkM{duZk~a&H(@b%L4!aB>(^baA|N -aUv_0~WN&gWWNCABY-wUIUt(cnYjAIJbT4gbb7L-Wd3{sCZrd;nz3VH81cjX~F}9wD0y(Tf1_T(=p-E -4}AjmSEXj3FZqPO_(C)rW!rr2~5P4YcGKFL#9yjy^@l`@ArXsKR&iWxR#Q&`!ibLwy;$X7GC{qXS?_D -6(o9UZNKovrDQa0p*)Ybrs?nj(O9i12l@T5osjg3biORIqy3?jJYzPy2_*T}*~PHknNBh5Xf6M;>bHK -$%MYR#huBNG-fa-(&06$jI%BfM86WA_A;Wk?%ZXoC~cvhb4T^q>xACwX-MgZR;dva|lVTMXCF^_GrSu -nEhakk?ZalgcSi0IN`$khKAu;VneX$>ucw%8;}*&Z@crRD5i?eu<_bJ1OggvXgd>&xe9~%-gPm#4Rf3IX88W#oe~#2%q$sb9Ct+k?pFiVgugt -ty9AsHvq_3pxGm%qeOL%2s>dWJ-CdeP1uIlm0sbLOfN9ad=Bc-Y7#c%yD7EkW~9K}Uhcx}Z2DW*e6c< -S@v-^pjQ(K#Nk*o5*3`>q*A&L?|O+c8j?+FqTviP56kr4{`aeAKW2uK)(LQkKG-CuySl2 -n-d#PX=J7X=&8k}G<@TBB+Rfwtz+X;IPfpE?YJaHn?R9IC7c+DAS!e37l!+X>FUTt5+5mxiPR%1 -;5s2XWI<0&kT_C=WGSw0JD>me7CRa)>MstY7YKmo3$sKy4u0xbf2}?Mjm9#2EXdKO}We3mHWD?io!C2 -`jf5evSLuF0%@}vi9I=K>%+4X1Ap>h`Q?nhE`I;lv&H+5Z{NOo_u=LD=G=U!J9}b3uk5}xuW9Vdx~}S -H_3@3Nx(T5QTT%;@a+3Lox5de-J^`-_(^oHRx`XQhu-6XCnu{SY -Z^oJCojuSd0mw|Tk?+1cuV&%LI?JFd29}7+`BAq?AqL3+YG{##DP-lbJDzO}D8U -iWyAGSD=Fp!73|I&~A@{2Jm2TW*SC=Y>3Rw3`iY3-{*TfBZU{zbbB9&Y6e*Bc4(GXRadNO^`$iyz;v* -F0QsHG*1rmJDb~-LRaOez8h}JFaX0IOz$GOE{v1QKmf-;>Z*#C-XdRuUrKv~|3j}V2%*(+5Z?e2-2m` -P6Kf?Z0w+$hon*IZDH_-ENh|~48-IzeflTA~v=4P{Q+PNugxoxk{pMLwz(O$Wqr?11eW1;#}rtsxYQAzrJI?{?)ea>QY@&I$YO{mOXm& -KG>li6mb%a=<}iwXSpx37EMO;%&e~M>V_OPu4HgcR4JxHpeP9`7?Ry8YpnZ0JS8GGo0;?T2#0%TAvLp -EEJ1z#!e+mSg+)Xo5QO}W{UI_?eZ<^Nt{yd4q!|1G0u0m}BA(omd1O3{8S@U=vnfqpnhSk&dB+wuEr= -lyi|#Dhln>jnQ-k)^>?)Xz5?=t#6{spoto;m%8;vIO?7+e%_km$B_2YGh!A#Y)vhSe{&m8g!b_{t4y9heS>ZUS@qh)TuQS2$r?A;|Z6)8TRW!j#d)5&U -mowE$2`W6f{QIo(M8lw+#sMdnVciX8DmpG;PJ5($)tS;`8EY1!H~ZC-M`vC9q^$7DW>lYxPbsI|`<7? -i{od94ygBmP$9qSN}z65Iva3U(|fp-%T4;ZU1B)o1sT-BTAN{qesV^|3i%>U%MN@{NA-jCdtkHn=~aA -7WD^slzZ6MO<0oWE6R?p-trzq=rka;Nh6DAJ+32+uP)`@b`KkTf+H##pvY!G{FeRa9a|JN-x-OwnW7{N -xGYbQ!J|SK-iMhIIU``IZA}?>6a|+nzg4#I-*mD}c(4xpW3HCfzcv)thm0JL_UBuvmJOa|0x`A8-f(r -`4vtAkIh`{?nyAEU*4OmuSh<)JDgrmjR8?k^eB?0GBtzfk|WoM{ALb~?mc19(hmanu7Ig)x_xSjTN}TW==q{^pi%V^IJ8q>r^SD{8c9|xvC{ZQpi_MIo2cA68{`qfpcK7(Dtm?FjKox()wRbz~Jl!sGooCTzSvzzEzQG=Ybz3$ey#KuKUraEtQBOMWW^0Ov(HSY+9D* -A_MxOC%^gj@vzO%{7*0TybdW^lD_sBa?Y#;~AM?Gu+Mv*`@WaNT -(WK`02Xu59n5MZzDzpWN#M~@9EVr{PZ6Yr%;@q{$SAG+bytV@>8t1$tJhc8;$Vm -=N8KBb>CwAmTs1+hO<@!SrFE27tZTufOQn)}5Lk0|E!d7*eOM^p$lAM%u&PHlv6>gafBwZ6IVOhRY&Y -R^Y=raVCyxu$W?asURK#kP?c0mxBQWDo2x>r9iE02SOWEH@!%3$#f01s9K#IG -qY#(WO*n3y5C#-!u5SbiocE1%&>swbv_2WV0RJizai6^-XKYYU2Rk7YHlH!)mfE_kE4x*QDE#gBaFZO -NQWdFc&>&f*L~0s=7K*RVeYSCeV5WI5{eO@gY#i5n9MqOica1D<^0K{q}pgX%ooelQsEZ<$Qf*u!nibW -STu>?2K5r_b|Co%HM%G1PFmRx$E!aa4A;yCiahk>_<5jgE)Bv`s)!#RwV_mj(Ou?{u|bN_fC*f7TXXv -K+i(AKxV##MWb%Sf3sJsAH{=vF%ws2GMkT0@3bir*bCJJzXJdQLEf->tEhI;Q7MSa*y3u&PR}hIpOo` -wkZjOYX#LkcocxI0h;#A`}5qE@)o31FvLONezx+A8O0yrYUM>_&+rZw=sml+ZgfO!&1m^)?p{`I?L<_+uIpDymFkx66{6>@2n0rD@6|z{F#{-t0c5E3CcT;)dcxsZrrtdDC>Z; -i6r>gUA6x5MFv)R80fVkY7DV4_q%6dz9birI;!w*&dc6L?sURkE;Tr0jF} -a1v@5loqpd_oS%La|Zo-}#t|gNEgiSN5Gt?wkuTro=4Q3mWe+c>!0Pb(S1j54*0Jb~b0{}4?0U#c0O^ -4xG-7<)PQtWZu=IbP=gpg~JO0a=vbFRJPm%8fq6oREeO5j>cC7d@dw(E!Yf(5j9dG+oFSF#YuyTex}mpT`!0_y5TjYQqINT0+W`plw`m -6iFW`;KP^-pfu-`?U#H%nHz8}%c`;GPe6_8!=mm=_&6{tpog~*Jj!t@$(ejEs_oYHD+(__m#K=7?7@f -SOFAs&h3$HVjolndQI!kRGk+Y|m21mYf~jIX74J7y7*h^bz)3L`CSkWt#{sjsnJnSgvk4{qh?veN8m8 -par$oavI{bbY52fu+2ut`r(w8*`?~rB!Pi>OfFwroa0x0w3r|qOvz -V^kcdh4^4&xTEsoUSF`7Llgg>gW#WB9)4v}*te+5YX!!TO63_PP0airX`N2HgHKowG=r>GT&Gu0t1qLo^offFY_Cao2v(Ulgx)F{M49UIHlics6wd -!*!xjDGif6g!+)=)`celn6|Amh$iVM%W`yv*$5rJxszmnKBibJkU{m)J-}Zfxel@Gm-zmY6`eK!F0(Z -xTkxH5^qN2$7NX33>;!%?qQ>$!@#X7h=I1BBOj4egC@AgQ)CPRX_?^sw$W2k=Q1(I^QZR!K$Q(9%7J& -h64gxv&MzK=3$jt5h$k|5#CXQ{{3XR)N{Nv{-CAgCnS|`-kM|fc%gc*V)$IcdU(8%V_+@i@MN)9|_z- -)|99)M?vRbN=g9frNGKpKTc?vxQz*IVo_=o_GGEruvybAm~{!~Q8n ->T^oJp?w>)*XS}Y2gV;?mI^r6v@wE1CgEQdb22{R@hMbuqXC^F*<%UFfjW0@{aNr%t^ELI`RINo1o4^ -&inb`Yd|(eV5}hZvwFrWp~0xVNb9@lJ$;^4nO=I=a+B7!w2d)MZL6tEh-MfJV@?k=Z{&4D2Qw$v%~Wf- -;5&&kDvV<3-9pKr>~OV2a;KZ@{%i20?MubA9`Q|1l~LKc*}%bRO(AnmP%Fm*ze1gsi^SgeETj`34;kX -kpIkUK#F=rqEvCqB=XB+%X9nSkCA~$$!PIWE4jR)2YF`(-gSb^vN^Wd-^@T&&|8j*}T^R>1r~Tr#fOstoZEYL3 -@jef3A#4|C;Zy_alixPHyj5-Twsv#JL0b`t9QTm#_Zx9P*g}Bm9$74L;`(t!U;h-=dydki6ThxN(~?Q -UnZ7@7&z+o*=4odQHTOW>h1KYpseB>E^HnN*L;km}JIB&UkxVmOFwB+n-+@0e_$P^#3Ap3WR4ewiaSD -7InqGYVrKti$A>n%ge>9H}C%Z{KI?9Te-Bb{esERGm}I$n7O$~!d#7W+}Y)v3l~!?I%M@@@)5JSR6QO -Bi4$1@#UICDF(h*jEb^Y0bo1F)XWS2!9H_#?@CpNAJhG3MkCrTJdX8{-Lw8Y4 -H;ld;08w$}!IUObw#%fF6+F9^Hu5=Ol2)eHDgL|Q`JRga)wB$2)-+pOw*?_1T>z3SrUz!M&-cbG5EE@ -w{T{mRk_Pmrh?HLrYV-P>CI8Z$3yd(( -7mtvV@hadq-!MRN(Ea-Py^{Sv>Apk4m)VNi??lK|%?3>?ES>&w6i~Z_(xoKvd#t`9BXKV&`pOI>jBK6 -D>W2t|(~*osL{2{ZTa=b?fBY~d`S`=z=P&;F4`n0^dI67ePzw1;NW)n06T}8M9WRk&^q#iHr8pA7m%> -{R2BrN}T3s>Ff&Hkl5H9y^bSyRLdqCVgK(}@UFEIst60q>-q7nMVscpo)W9*8o5UxP?`FJ;a*G`7(UB -E}wOBTogh%^S|L6<-r`S!*PAoqHIxl(?&hC-s7~PwGDmu3kd~XVH!77(dOue<5$DcsQizWpX9SCl1uj?9xWPBSNpC*i)y7^*w@4QX=$az@&#wdU3T%HQ{!Ye(=_2(tT>{S~aSm -0}KSz|E)ztW*sD3@2k@T*9o{*eaj65DiD1%Bw+S6R>Z(D!P{M-!ycKvILo@%b95^ -Irgp}V^f1*(9}Hr9le*!Ow)9#*UHAOcFXkMV^7hvA<* -&MEIMHejfFI0Jl0>af8(WYaj$WsYNe&+&{LUtUmGXG;CL@Dcupe6F`sa^!dP1k;^5GO*(i9(djeVDNJQ=GfUIO}yjiv -FCTglgA702R?%9y&1;w1IGn6MD>D*o}c*oM9=46qv;-2ZnRh1*(Nt_?D`Pr*W?p|@BWHG{BrDZos~j7 -)7mSIPrF#w{^X{@@VNu9$(6FpJh=o5MD -R0M2)MbWjJhG)BOI>B3xOZ&ASY6GS_*fF-B`h7JcsZ#L`YYKKv&tEdWOafLVowf%4_Z -Ib88oHZ=fI%j=EU9^%yO@(@_kE|#QhTK#|}#`*q=Gn+DIiOr}11Ca0ctqW(`D>d^r7L9Ga*A{-`Z^?M -YTepeu%?ze!PRwEF0Psd6!d1;y<}`E*bXsZ8lFIshN5#CX#mD|P|rLaqvoMm0Gp_QpeB6MkX*#d4Kbh -Pwl`YPbp+`x6}6ufa7zk&jW;+~8o%8E#7UC9+n=u90E5H5@@%`<(2xtiu;@EW;t^EfizIb9Kk<3qUUzi -t*sS-!jj7_rneGAMp&y~0$+Uhh@4JL|NmpB+3X0XM&!(!b}wL=YLbxERXU6bGz -RLTS0><+YsxHy$e5bY`{NyqQ`a)sB!Y|)i(WMb6j@x$Be>KLT|Xa4p?$>ZF-A~8?lQHu3{GgIiRMY9J -|=PD%XwtAI*0}llT&*PX76SlY8D|?glF#8Mn`-9j5Hj{ufY70|XQR000O8HkM{dHV;zP=pFz7OKAWAD -gXcgaA|NaUv_0~WN&gWWNCABY-wUIUt(cnYjAIJbT4yxb7OCAW@%?GaCzN5{de0olE3?}z)Hy@l}L2r -xW1fQx6iSi*4HHVv)nX$wpXDj$YM;9T7vY)_IiK&%?tn%AVJ&NcJGJPdu=2V7z}{r-IXyZV<3nh-*6a0lqlgQ^icEp@1R#>me67guw$~VZD$s}kG!r~u1wp|Ja|txYCjl1wIShn-O`?~)f2*=Dks%;SWo;gWm9*30WKcRv;8<7Ifo5r`8K&aL@-f{jG; -sr?`c3tV#R8STyFn&$%c!F(g41z&O-EX}@}B|N==m6-84n}~4ECwkp|{NF&XX0-+Q$$3%cDYM(rTeTT -SV`C$X9<-Z4A#>dOfZy_{EW&BRWy8tw$zd42Zcf-Qg|rktlNT7>B(pAjc0-id{RRi*?3SOBJx$NU<;1B)RdY?Wu1dAMYVpmP!pq+ -w35E78r&2ZV7U-~sk!J3FkG7Cl0sw~D4^oXmQ_TsFY7(d+GtAjxic?ho9n)*g@+@kkK_LcUUs5%e!hy%&#zr5y?pVf*zz8d#Xi@ZrO#Mo-C`@{Tnp@;NY4lj2swCmy$fy@3$fF3P``>zn=b -d^kMSmm&14aumr6MK$B>GiHkCW*Qysu3Z9BG=ha(1*UKcE0$Wti*VKH{tf(K>D3bUOf}D%2%t6s;sXd -R=8OUedY#K&a_#A{`bQNB5+$|DmlSz<^RT3Az7kESVasW(S^_oa1&{%%)n_vcYQ$P(D8Uf430siBAAV -;K}44{QqcL)XOkD}@~WOhpsgs{cJtJ>ehpx<=sAQTf(z)CFr0qP`S@7ZL$_tU}qgK>asHDCE+U{Z#)X -Ld&mgyznI$=o6<=QqDMb|4rl(ko)iJhw9njkuXqWnQ%a8 -e&JCu4F&&wJpeG)K4@wqngs$>AmNFryMvSP#?32Eh~j(_W_{*c&lYm!x* -si6NMG+x_pG&uHRbXM85~IS>a$)0KkSlQc^;L%~sfJ$gabrB$`rv?XLE+$UeRraj0N8=&d!PdMKw@G$ -AM~qlvBT2620FrdcVo6S|%W7efIY7)B6K91G^W!i{y^MW}*G3tYNm`IY^i5ECm#<^#)n5AV7rVCfz(c0N*;VV*n6NYTF#TI19*mS4}ZjMLL -vMiOZnR5m+`@gHk{~*b|A{BD98;WuV5&7jvgqI03Y#C@*Ai?IW>ybYPl7bKC_!hijAb>P;fdG7YsO)>f1B`#=8Uz~qwMTL9 -gPdz{ZH=kZc&lGGc$Oyy5ZFkbUdMTsI)T)$8$ddl-gw!`qSEuLG-|Wie!kM_&h8V9eUA3YCErLZ)yztBy&_(ZN(!AP(vgu)0%IhZ -kzfh-fBz(Kip(Ay)|MW7AE*xZm<#(7^U!g($zp&kD?wNOeAeZo(K-zQKFe=z)8Z`se14xby=RQ>{eN8 -Hw#eC;WR4?=4E1oFNt{b7#u6%u^$}Eh%tbA)Q(x~9v|=Tp6>PrsJMIo+cU{UPu@u?`lTUoJ6cu?fz81 -sm*zuH%}m9+bl{E}Y%;qA%p`-5MPPJZrL6km00S>^7@-8Vt^BwZ7Qb%c -4oi>G;*-(l!MV*yW}&Vr}29(&pX()=_ntF!Hmi+au4=)2Ii(RHJ#LoAvLWB^nLXMG)Xi*@@bt!09VJvln#1E_qvPQy`07y$1ToVHhGCT$u$U6yzG -?+L*pJDK_M7tGjS{JL2rO@q5dU6&j#ns{u|Ezzjbu~-Qe5D=ydQrc>OEQiuwUPA-h$kLxOC@Q-j`kDziE}9%X -ev+;I$?i=Cb*;mc)Ma4w>7#d{Pen%5{K;JG3WbBYqtFW)d6@Bx{Y_`ofYxp3gnLD|`Ejh-BWyFU7qZJz)CwI`F&Hgr1kMf*E(6w+%6aF>I -{0MkE9J$1>j3nn6a|~**$Txl_)P%fhU!c_c+A73)#VYpeBbW&DO|o{VpZmG26GTAu%)9c6yO6+y}_^v2Bx5==vDELIE|qrh7A2?W+pDc9*+MZ_j^f|CFpsEs} -;~XkY^#IlE+8m!%sLRq_m)pDr`+6i1Yer -Co|k)S+<-d>7XRUJ^xeUbc-YFZ9vbQ_+Kc)FYXUr0E0Wd|1VSX)Oo@xJJ`Z#F3s`g?OyAmcsAw&H -lmol+bI7Go#^~!w>GZ7*=<*Km4%w?w9?8O<*N=j}Ha>mV4IZ(bc`tYb`nc9irEd4a-t>PdobDmoFGaf348(^8v%)^LeU_ -0r0K(ybWOe?AZnD(@QrHc)xyLWq)L#2E!G0^cI7+t15cy+gZ%4pfjOLM)<4wuxbdloWuXQE%JCPuFYgIBNt9;XPCpi#sSX9 -Mf5X+QnHI6ACK}LM^J2?Vmr(pPxUYur#bmU0^`50hHw^a6p+~$1^@7$$S$hiM1c@>IABrEb|=0o;ts6 -OY81hS~<;@yVhNHzt!M=jni3nBk0`6`!HfhV<1)!6N1Y!S}?K1eps_xC=_VYt{Edc<6Oza5i-#u;~TP$Fv+qj+|F_($u -5j)I90{4brPts}Ok~6V@hj&P(i -}0F%Yk-1T%|k5MlWt6yb5*Aa9m3pzPxjg}6SU!UY$L4-GbJcI;ZhcHA~ux25I84u<|%0@4QVP$?PzGs -Lbf*QSi4}~RVU&ahWdjYCSn1q$8Fy~+&Rr=S*x?NXx9x2i(4MCB*<-b(?_gFeeOa-Zx{^tX_D!_}$>QlsJSu6RqLC?2vKuK{aQ0G_SR4wfd0wXSL;@xHNaB=KxzjwK3eAnaQtQOUNc -RuSiJ1lS-^1iKg01G%tTnH2CqlBy3a$0`GU_SPF|+c8ich@M`qj*^{N4i^c(8@Ib}bJW=u(KoqakGHHqoY5RErH8dZSl-Z1<)-OK1 -^CVe1%GS=l~wvH6rz3B{s`Is_aSz#Qcgw5;PdS^0>Ci9ma9Kex-?pL&7Y%8wOjZ~b9+)01pl2uFWfTR -K>b=)UlyF!4aVLZZd6g;9D2bgLI!UIFs8b?jHL93T)&~QOz6>ArtG+}h|UHfbay|V>Gvoh9`m9Y)sbH -($NCtATu*roK>L5WI|S^F|GhuvQmCgTm(f-dwg|`|fGZvqSV$+aB@ -%K`duo;}ReS6r=nqGy2iuaYWno>2g0aZK%ZOlur*~|3wW8WY&>5eQq`z^B_daq3IWo{ipwbN!m2;u9q -G5VjhL?CsG#1K`ol*MC0g0SPV0iS4L3Fm!Mnh=#WbY?QOOL7WA|YW_Xc+)H27ORU^=;xBc1iSv`mTk41_sTQ#|x;R8KuG3@;ZyN8cVYz-EOPf2k1|yGrvXOR10Sw -OI7K-M0BSGtVpXXMlPYcGJZ1iO}S_kFbS{2yHes%LZptG>(n?fK0#~V3g!_C6s16It!DL -PyV3z&&5b;V@~2$cFm>u)io6+M5zk|FP@yA=e_G31bSvXDq3AK%MF6Rc#iHsl5_q6u9!HJPMoel*8e# -6XMQvxHKN7&OcdIy(!4sLFlt_Y^Rb94)s!%&)S}j^ur;oSmlwc3WQY3q|+vp9X*a#Cmm~pvKE_fIf=9 -PAOY?um1x)fR)9*w_c{4yYlK0Hw&*Vg#4z#V{&L@D{H|p$aXGzRC -K?DtbNCyajtrmBX^LplhQls9vki#SAy6g>_t9r19Ji+ai^i)K@DdmHgRZJ%-ER8%LlU*Okz%SNi6Us? -i+V?Xb~H=iz5_3f22BF2KD(Z^+^rFy`1Am!QPY@ko=xVEapTXPb$P>;_9hWa2rI@+va&oLytN|Mzr8! -m0bZ=Gxq)zf@sSwARtm(It!-mJ*ZTF=GdP_UY3n^VEA&e*C~boFb_ajU&umWW=EXK<7XOd%yMi3Y#1s -?-3IO52FqScHR=@sOGS~92EO?R^}uxFo{b&G9k0)i%b+l?d}e>C>mD2ff=9EW9gW>A;fWP+TV!4sIZo -|?%{omIf!C_t9ttwfseqaS!P&S&fbjo*N?^^A4%@#VJgr&QdRNHM_%Y1BWPSAjNgleDx;BeAQUOmLL~PHbiroUQp%HYjjKuyr5deTuz&%|>u*x&cI> -2N{BLhJE>h)HG2wJozNr{b*cTXh2{LmyA)_W>OvOL$<8?O-1r5NqmfHug4F`Ia<$prJC-$RRUdfNua_ -1U;gfgHZU-eaj&*oMWF)rR$y+UYfTmcZkCym&z3i=o|m+yMylR~5aUZERnd`8YZ`r;msNnN|62Iq*nz -8|giUot3_FT^eDn#HL&cRDIS|ne@s_u#2W#zTevH{iV6=j#XC}I9C0Y94oX^F&3bp0juKGZlGHdXmod -JUr|wSgnDI+m`rlsIAZUeozH@c=Y}^by-;kbs`@M(6uB&$B3W;*b}qXyZe6O;H@2A+beXeNnS+q#0+V -MR?0hIwEo2Z=x`yK4m3f89G6PCg*x<1hv{+$5+BIcoh_~MF?j4OkP4*9t-yQw+{lSOR$?5LNj|U{h1u -qOhMHWcQ8WT0j{?0H>5>MkKF7AK~DV0MSdx?1j5mJUY3NgxJ=Y^}7j9{0UDne5GKmro$*U?v%Da#vuP -+NBH!nqb6(fm@?*0%hQdT+z%nhqSQ#xf|;1>3WeKz4PkL~A#>3GW0Z!qKcP^a&(M4eBtEPI;>N4;vur -b2x@1X9G*<_;l#4QlU8C?2`Y2Lu2evZ~zzrg#X*S0Bk-JDIU?)73C1JSFE2=o6%L`^%M0G@b)&K- -ER!*Ud6n!jBB)?PjmoCaYYvO}#e&iTh0*VaN@RH|`DD$18PQ;@j-ujpZURT$N; -4F{2%dk8MLbQoT8Hn}@%6cQ=Dv`@{s1uPg@5k{&wdzux2}F+6ef^0l8$rlK%?M?bYz}_D9HqO97BCv^ -v)u2FYsgYcLq9g<_EJ3N?*7qp|DwE;~nTJIwr%)Ec49cQ6&k5m*ColqdqI_slj-%& -^OQ}?y1(eV~jdNCL{hC?S4WT{jwmQxd^rsl=f`a>p;LILjuL9?Fjab87TDd>lmu?wV(raS^FUi -p$d9xkH#HQ)j1R}Vu6r~vIN;hNt3Bhmi)acZJ>jMa4!fIOR6pQG`+bVzTXF1fwJYmN1w>grP!`w=@&b -29ho$FNDDG~Wtc+)kWuM-dC=$K(w>vOO}o8RurD4CRAGBTY5aCaod8+8Ch;OW|`o_Jw!m)VJkS^-Ybq -sRIyp*xEFKxtI^b9Lw-l;BiHe-y6@Kz1nzi+tmKvvsjdVqJYBj9wl<{>!gY;xJaYVj$=@Sp)}lSt#rN -o@$p_qsIKmef22k34~v?2^!<^!N}~P>^0fP^MY4R%4u91M7Z|w->ra*F424eEv}&#OjeTGs73yl)xWL -NCSy+U|A6URk`@wef>l;%CxMw^CLpjH*2$b-~Zu))%Qx&6UgdmVBUfAnEWBcAyvCZggKOtaPXzKlW@l -;R{LiDJafk}1n9p}MC^-b$U;N0ii9$10AuT@7caeFj!zdFWWJc%w^WH>{3(D -j#H?-&O}!YADVVG$t5z6`!$#-m(0fHpbzWhk8_3g?a7^bMbG3=syWTf^9-;J((!srdEFK{{T=+0|XQR -000O8HkM{dL_!}!Vp#wH)M)_#C;$KeaA|NaUv_0~WN&gWWNCABY-wUIUt(cnYjAIJbT4#aa%O34WiD` -e-MxK#+cvT|`oBK~S3WtG5?O0GFHL>x-s2=r>ZgvK$96X9rnxF5(K0tOsY6n6+}-`|cisTvjg*|U=ic -+vZexiA1_NL)m>CRaHn+e1v#}XfadDZ>lfmM4WAl%J_&i_S7U}G&in`Bx(eBR9Zgd;=F -tyx`2Alq^?NW)euR1l^La$|%BV=nr1+dn2OFF4;!RRqr)8PuSrnyZ6kR1nGQN#wMVwX1v>!#6MUq5Oe -i==!;$oJ-lPZs*IJ=D&Nm1q*z{tl{oMve@i{dDn0ESpRs;&TDnO{~nagkt+X%v@bK1pK$98L4d@;b?? -xJn5IjuSNBt*)SK=Ul3FdeUArP2xFVm?2y!ajxM;H)(a1FDrlnJ*$dzf)M*yb~0a15i3S5>pnio--%;yXv#m?|37})lC7_h -koj(Zi!AeP+Ao2&d9YXFS8gE%Xf%OV2=VBDzO6#gr7fCf1HBbikEl$yHC=kxpqdQEs_(-eDK?$KOdK! -JFie@>{EOh!?bSI`T_3ulZmV~UV>YF7p -AMt*)0Y?T4$cmvqx0y^+3DYoUL3xNItS+wJm~bJcSjdLoxZ(@pvc+5$;B_x>C5QgY&eP%h -H)n_E=h5j|boBbo@zLRnespy5{P^vQqmx%r^bG2roL)r7N3V}A0P@9YM9m7kqr-E60WhQ2hiA`!f+q* -hj*gEmet|K4d314tkl`Buj}D^f&B58l(et;*2kG>hNIC%juPEJpbPF|itqld2#Pc8<4|49 -T-qv-H&@H;yH>EQU7+l~(2LZ{9EGQfR)`sSCjqgOv&0F0kbk6#?(^Jj;E@WHd=LvEWo{QUUf=ygAOaq -#-!)gjkC1!#Z@7GXz|q?Bk;q*(<| -Gg`YF8=X$)EQzNP6-UavDbgxIA_tBNBwk#{RTLMKtMqeH4zT#f#%S~zmJ(D4u=`QxKhBcRDgOKqcqeZ -n{J^`N!S{om4%CR3z!wUrfKN3a{dKvslRQFR+Vyy8535aqgJC7A_8%|c*B-Fq;c$CrcYF77H2iMw(eB ->PFnX{93_9w*EYf}i&tE2ED%Oks1EEYlm+6$iAe5u&p0Qm-PoF-1`oseIn1TL1x`vK#!=S)wnkU!eWi -b+pMM`tN=^=WjG-vA%WrUMwPO>@6Fq6sR^hcaj7ZK=NI`WrM_s -qaZqWV!lLrPoPas8~*PF=R#Rdy#BxRK@My29B*fn-vRSz&F#4nze}@eep4cu0iOaYdNSCFia0HSah>E9K!kxzaK9t48&Y+|Eh6FUN8N);I6sx?hbs1 -J^dGIZn83V4=($C%{LO0+>3W0~|NH%I8zs1sKqI^1MiZ#?EiA$2sgc$AH?KBK- -`MGE1;e{EbTcm_Gp2O;q|lHIOIqbz%(T0ybwHI$Co=iE0k+<64=|iL^d+F5==PyaXsu#Vi~CAnL>pRJ0GJTqKDQ*k4EEVIKR0Cj3OWb8)mBy1QDK?%RpFaQj{OIq8Q1$U} -H&AJwWV7lDciu&jPZHS4K?EAl^U0@jDddZUW2+1oW`qNB$K?DmK(25-i~1JIeJdWB3~(@$iR6 -MIMOVgM6+y3V}lYBh##;?9=&%f{`zGHJ{_~#A}`Y~X2p%;!;@DRKaEZfUL%&nogJ@>*YTJ1dUaA%8Q{HfV3 -rTigH!zY*snlPhskxWqZo5H?3Nq|mk6 -dwhv5=pw%UL4geJ`Pr!ra+igUfgcHJbHPm3qSD-qf&91yDgwJq-%W_ss)R?N@vS_S@LM(3cwnp|GOGwBjypd$RiVLFRn9@&Jm)sfI~HQ9O1S{BBoXA^|MWt4=Sy(JXeADY+aQb -#qvT5k`#fwuS0G>kKY9P^SXZB|u0EURV;D=Re13Y2(&Na);eE%fe4Yc*0{?ZJ-FSO`ad0*QDfjik1=M -pFsnPE6`=?K%F0qfE$bQ6=blqVHYc*C<;myJOx`NL&-EMXl@fY3Li^G=(Z;vnP`gQByzLn56`-f~NAi -pE5>UIHgM0EucvI1W&7bL~)ZJfV-^9FQ)!xtl*uXB`Zg!A;H1N}|dOMk;3r$|@`C?vd6`;l6P!QyJQ^ -&{aW{YW@Nu(-R@`jNYkTC4HW>1_nYLWy1#$x>*F0H$v;)dBKs)bZMe#&5jt;2KDvJ`ZcRQhSC?qyr*r --TIx!B8UL&My2Rc(DWBse~eBJB3XeOL>!4A7sX1-9q^JlIy`E%NqFMaK)FG!0utf#<$Vcw7~tQOe)3>N2Nv)9(TiNx-FYcqZuREaJIZ2gT&e=D@P -8qRq);pYn0v_gv5eNDM-D2{p|3pM(dw6DxH{yXfL{)=W)d&)tLmIT?d}wIPL+a>i5j~P18URHvavM9O -)TokZ1}c`wo)mbv{kFWcj3u=Ywx)>U?zc@|gC;oqV{9Qen-CZ6O3p!usQqJ9bG+D#C)mT -&UpA6T2V_8zKT=L1158@R41x7B8?AFfA_SU|5Si21ww8TzF^~Mk!=tXgngjR7?vkzsX>azIglY?C9c9Az+ncgN#x -1@crR)-LzGd5lsGCCTOuS>b$BEfmsM5GAr`s0!r41?CdrmqKejtz&xx#MCzOxi0nKHj|egVC}|+Fvl~ -W46*Umq8LmKt>wMeNO|ukjuj%h%QvZ5bps?rV0C^>@^=>Dtka=VNJ(WVrWroLUF^*oDt$EwgWP8Rxss>)^>9Gn5cPr6<+RHR6%WsybYhdoB;WAq^EipIH(D0a(U -4;1&4x5tb;NRNp8I!L!6%3a}Z*+?%4>B}^mc1cHNJb_jg(O>qX?ctu@{;7K(_7)$lo(!f*Lw^PTwA+9 -!Zp6F?F?NgF>IzRM=!K0W9de-9fIIX}j@E@T9fK66S}jr7a-jBspDJrx3N+9IL2;hn3{(e8G*>|%Cs# -$6SYfa3tG4*5589((C+^C8bmDT7rX(!U*;J>8XC<8-c=EKTD~d_(Z@9r4HaYbndtr2x!3La8eSX3ZWhS3wxj9vM%CmmUNkrfTD&yw;MnFupd2jo;}zH?5ExGLki?L{AiJC#`A~+nBhs;3 -`B|jr+pD__iZgIG||?yswGv=k~`6)u%UhDp;8iyJ#Y%r67S*uP>C}^m$;U@RQ|E!gVxGXWO8dry7eQS)i;S2eSiMPT`(M-QC -Ae++M2%vl9XB00l))qx<(GDdP}XNm|9do%?wwx@VT@M_I3@n4>J89*`SyI-k0sH8~IZi>a97BFmy5LD -zr$q#1{LZPhEU+d;Jm#P1tiB_;u)%3?H&{oAo&ZAS0kzmML5)SO;}_$oB8RRR@W#h=rBS9;0_EC=rGOkKgc#%3zr-;>o9O=NOoaJmLrQ4h<@>u?=hdkrLvfh;O^SCI-A4=%CE>VW5@X)b)zRc*D^CWqH(W4RIehNkmKb>XJ7NV~~Fp|o&F-<{NE@0`(!IEr)q% -tdY4h9s;5JUw8A77<2kVBzYs9S?{72PBT8n}_F;f5`3nF%g{vhEB$v01b#C$kyxVtjz}(sd|y_~_{~p -i#i?!ybJ9i0j=c^6>E!Rb=@1$=>7P?#CX}&s|y>uWn@=Zbhin=~UltT}U`z+@jD65>>Y>Ca@Wnl@crW -@rcsg{T8?CAc6TZ*+Qgt1htWj)K7q;7yr?vs+X2@?%8M88=Ov{fwUmAVg+CASxI%h{e6>vasQkm0=g~ -*6bHi8MX^k5ZU6&@e5jZVP-E&2pWGkr>^`#D0rJcokMAz+x>O&keX^cnUrDDTCg7q;zn{H*DMwjDVDb -^RNYDAOs}E(bW7sW$-uGc~e7?@7T_xK~RfdZUU|h32u5Pe9r*l@_db|iv*c0S+P=Y&^Ee5t+v;mTJ7G -T2Sns#byr}y1*ZYqB%2I1Fc4*Dn`K@c6oZ;?TX2#&k$f!>Ioz3iitpm5a=DmYdNR}zCj2rRmTHNiZzU@bG`f;2;VPJ5n3Gt1C^MvKfU-SLKP8G(gRPmi -0lzhS_NYmbmYWCu@On|*nqZCZP5nr)j(`pV1^FRE6?S?@z?G`k2K?~!$zi?g>U&+B^s{ -(udAG^MtyxWp6LZjP43)6uh|lY_HgI*ukz=>2P-VRDgL<%V7jF3~kIXYaUf-9uEupFDZes|RB;&&z~H -!e|02*3SW!QJ&;Y9ymOM?77+m*41U8suZgMum~v->^AFHTv6{&BW9@{eLbhQWJRO1u96IXpjc1i1)AE -%Np+JXnc|>;>c(gGQYjca_du1(O06c)Ll};9lB!CkCk>nRN-SDvZyw#B`^ZEsI=&P(qGMGr -rvn!E`I82Hh(i%g>4w~+j|*6nBtO{!#4?}}H6Q{7|U4gKNK -dsF0JZoBf>?n{lPs)h%8>S@<7h*GcV$PhpKG_!Ql;YZ`-a1os*r`@IlML_Wdv)q5X -F>th!)SX%1cj)NAmzbUcdj14h~2W7!M7OnFI3B(d_rG56IVU0@aD}a{bn+8UMcf6>|f%{_ -v>*n0J_&wMTQxh9Q&Cy)u()Jk|vS2G9P!`yx?H2C%yG=v$zkhXC<7c?$+R>@rQi9drGQKoa1)vQ!-wj -~^O*b0eB2bJ>TC{l$>Ec!%mR#7>WPaE&TI-D^OgYlt`wQ9A9xMs=k8Hh8-h(U*<>Bnudvzh#Hx;n&M{ -pr>;DkM8$<29- -El-Od2my?aci_r?AC`-i>lhqau)8J)d+KK%aiZdAs%B_6$8FUtxgs_5wabo=Sk$KP)c^(oa$@<2CE#J -xV5nDHTMTIK=<9TAFTF^|!khYwZP5XXVo-jREg*w#vQw#9-*m@3s6weN?vU1{}%3G~`T%k^qrUuVxcpUj1r0?4B6_r<_#+V39K|V)|SPGl -uC5X2-aWO4d5MY2;o5x6IS-rE~bco@vMNsC1D8jm&FUzZLZ_O6GGqINGX`BytqhD_U;)kz>XY7+eLLa -Wkl+3CTM0O72@VMm^u0?p0^N>1~1l2)J_PU5msmlMcV&!C?N0O7ru0bZT&<8 -;TW`EmZG<{8)VZPc0P`6umB;W?p`YbLD?T8_wwu_l3h@<@ZMA>n;Af;0#X6CnLLPeGM0KGFF-kYC1LW -d&c|kACC5=r@t*$p6XFz!+9aV_>yOXyIXg{v|2>_CrMBZG3ug{xjP7aw(r)gMhylFyyuh*h~2I96y+q -;#*L33#wQmw>iR-QV3ScPTGWM3r+x7wZ6XmG)+tiDkT0mJBA@{8(Vu7e@Zf@4#a8j3jQJU6FYSv4aM_ -H+4?m};XCxicA$SnzMy|g}3gDpz43bX>sIRS-H`2&7MZbOV4vzv1X<=!3Sa0Wdg1v -zS7Tp!93UO$XG>V*f~fb({>E@r#1LbIibsYMNn;Bf*J -wUUr4QG(eaT*!A`B^kuWeDGm5`XaBiGGNDeajrARr7f_N4cQI(r6B~ky -@DXXIfb=Pa7XQWZOTKs!XN!&a>Avd9K}{aq2cB>#Wx~`sD*F_x$(#8X5Qk --$`xKGZQ*WE1S;JRO{Z6T6FFlqDAC_7Ht*(y1&(1eYC00)AsBks88RT4zPmLs$;dRb^5M%^boAzR&1> -o^N7#4kE`+njL1;2<tiGkZlMFHW~ -$1JD&H6AE;Di#u=LR$o_hd;FtMy#HvMbjVn&f0oh85`DDqI#9ec&<@LX-yStH_(;~yW3nH|ViAK(mhL -mdeMKysyB4>2>3Z0NU3`2O*q7HrG|W6#VNwif=r%N<;O@@A1PX*fG;Spa=GE!eVkP`3ta`LbfS`V!o& -iuk5ww^Ec7+^?YOTGs_Y0?~U5_0bf107%22!A^!%Z5xBy-|&qg`hD{bcV|)9)@W@$jBtL?AOlk^WNkg -8QMc9175aLk!e*X@&_L}q_LIjkKnyZddYYLwaC}c6WkMq6Y|@6tC9I{_uR&YKi;BdUKAI+7( -*f-|Ua%#X#}MY>_oT>WGjz^pq3j5)D_?vMBLrhv2Y?7FcWZ&QJLhM{xahYi5tyS+5`pTB=W`*0`lD*7 -!Eyw6DGNcDT%Y=?LOsParGd3$)g?xSY%uObUocgi!Gz -P7hpXUz|-&`F$54*%N1_<*06m#fRrRVv?<2P$Ph=iJX}mJZ3+v7<((|DlVw3Vl2}LYV2DjQc&|A~_1= -{l7fA%pITVn94r;N=Eme-k6>8F5+kUg_*b!s4Sk(G?bAzoWJ;i-AC)#!fLlq1NV_fw*&ALoujX*pB#F -X+_=%Q*rx`yU9>mqg^FNwo0fPeCZUX}ZLWqh!?)<+KrjtxS<+y3^%{6H}8Vu@VgTvqc=;0MECIlb@KY -}OD};c37;-q420b@8Jv%$m~${qtwa!3Rfqg(n-W63xZB_AqYq_y$gIO?LxJqGnVb3tw<-1q4Q+y)QlV -DRg^3_{zfafmg<~yry+I8xo7eIMG=TiaA -tiKa+ja8GR(V@%;q#q?I&KqCnnA89~#_z|tuyVN5Jy;B7WDC!8_qD`wZ6xP%#AbL1xxb|~>me0=8);k -$!6H~_%z!Kc7H)4D>j@T8y5ADU~E@|m3Erkv5?zU}y-OYB*tthUd>%6RjM;b&s&cT&Avl6TYPjWd109 -&EwbKc;VS(VQ5yh=RG@D}%1I7@jUQjY$5YB*}a|7Fg_*`Q^^GVFer?hW+Ty|xBt)DQDki%`tB&5Y23> -T333;AREW^KE5M8hQ7sL$+Wg5gC*@!F8;0xOBV8fhExk;Fd?H??k;^mK14-f&fg1`a%;^5j}&kAiJ_f -_wzJMOxoWb1}GnW`6N~amTw_@p>uOc*dIIFR<2+5Ij-`;B*jdvE>jPt(V=aeEu!fCHBMOnMO(x?sTia -G$+cprc1%?|)>=lcxQ<*G^z*&*tD<@BMuMImy+G*2S_4* -^>v=SbbyNr;JhH31)Oad41Y4Av<^_IUA7q13d%aOGKj^OyL2nzT%MxO(r4(}Tp*I)YPx-FYEL_BjSD_ -L2XQ;IxBaIsOvCOxrS&K>uMCQ_t{*{_Y2#7BaLVSKX}&?dMwrPgC9uh(4UPtuD29PV%zT^re2l1-BRy -d0b#9{xN!KfL&M)%(xs7eHlXap4;H28=a^WQgnMI{wn7&hGRK?b+NzgRq~!J@cdvHWUTehSx5%>8!0t -a9Bgo2=yWeZT;RHp1fEwQh>2;hnSMrhJ}5Tzn3+);sByf0z0nI5fs3DwKrtuSnu9|89^XOthOTJI2Di -5Dk=QCUlg9CA}N3za?zmrIVp65TV)%Q`-2#ozb|_X?v?`7VQHmZo0@d+ID!r$#Alc8$tS^Rxpf2>3t5I8CZJotKzrZi!>5Ft_#fXa?Ui4zr0-T2#7T9E4zGy&v+q9sJsB^s`9d=gZF46q5m3hw2wyRJeQPPU3I|RLA{v5&RgN{+hgS;R1P7BWzh3N1 -+iC9ognrh0Mq32b^MRKz*6`<&G37mAR892m|eK7&!E$^@x)DFnu;!J9hFXj~?mLEDq>|iKSf -y2q1D&n{1*!RYd93E;$R8r0C>cQL&)dq0KLq71+TUY0WOY$+;~tf-G(jl$Xt_qr)if7;63O=PP^2dDX6s!l(FFOmB(L4K6@0us9wp6)3${w_Vj{UjsAw -8N5_@wjrgni(Uw0 -N!8gQWH0tXS?5AugFi%MV7fSZmu2~6-2U6C#XE<_CH04yj7zsP9knkzJ6PAhSYtNovCpHsp&_)r7!?% --v2Op;_=U%DH!f&U<%qcPRN=SYdUBb7|>$ESC$+?{}A|LEU7N>R!k?3HwMG*#zeHydKG#8M -}RV%FF^~v{bTZ^WH!)5?JoNI79Et#w0nc=nae%(CTIHd1GLTiHmx!8wiGO&wPEAs7c!+zpKx6HY^?nwX? -=y+u;s44grY$)kY}9ZIr^OC&N9a$1Cdra>;N8N)}69bn|qTr~oEi$c*n&*|>AW8|q@@mj!u7NJGS88t -f}`}QJLw`eGFDCiz@P$=+~4L)o+8_A4Vvu@IVh|t{WG-&giCliCin^PHaAjnCR4bGWUott&Vk=vc;*g -S3;8U4nYHAl|`7)VgpG@4&S6ziiDLWeWxwV+cQ#L=6qkyvUTwhpNn94166U%TGvPErM;b4KSXmiDU?c -wlHgyh?Br-}dqR49($^>&}T0s)^2o!}rsd3rTTpO8a-`H>!-SQn*^i00Y>Y+tg-sk(8C%{4OO~Am~|= -iG;6Se3>NFSIJYS^YRulKn6I>y={z7nnefa&ySAugw#F5Rye}n75`BY@iU2MZhmUpkE9$lh`85}x|Si -w$V_M~zn*Vo>DD$M_2uC%ftxwDAbbwAAw>G4lmEp?pJY{%yNhqL6iXjcorY_S#u$8{fk7Ir8(g!WCexyN$g-F;?% -t$J=Vr2*b?6*#|3tc!0gcL&^AyJu!t6tw!i3UFhNxWfydZgZ#d7(SY0da9Hq0SYp;$8Urz=!nMt=b5M -F$jWMWy5MNwFSE&ttE724S$8CtfV^l)nfI1~M4ofTv|j3413=7fSVcR0HmXCIKisfsV0K;0Cwd8)G`3 -(`M4ZyRD2-=H}Ws^5#`!W1pH~X!dlX_A8@$KavE`>q*FgI4EmwuC^pruYDb{j3NFP*&)~uCA8yml4=E -dGKUTry;*D1#gVphU9TmcNjIUz^+%TTZ}%ya;#133?g_X-BNm?KVi8POhHDPRAxSKT$mNUTvIqTm25o4NTEjb@#8jA)kjxNw<50Ho0e#7*H``fxQ9xD7F=t_H2{ -4`=Vr_Mu&t}_eWZkcc7t?pb^GCyN0d*tb8qKK{_765$}mEC7YXXa$KSOC*=Xl=i})7gqiI+Ew#9-%eL -;#@$@TbB9lA!FQ^C=AbUdKiGMX3Drjy_H++Z5Veq=;k(Bxv~;PS59D7+u+M|HRc5E<*BN|LVE8AYkrB -HA=rJ4mu&@tp(buepJ^b`jheUDHj_CZN=X@X#8&bz^R0$>Zr7s{i?K$BdwDeVxHy2Y>yqW9aoUa4*-| -pzQzlIWxZ_9{o`~IBC{REOUHofTN(DWpp(yNPbSLeNL=GsiHwNFY*Zvaks~LctE`l8_32f$z$$a7_8(Bf~ -_kL(kcOt?&y?3qX|{le4Dh0Kfqj~5%&VA(_7o$xkxbQY3bXY8H($q{$|OM`0CMB{s8=ZoXpZJGm^ORp -x6cg)h7C!cXo&VRQ;M63g}l3xKx3T$v1nPJvY8kf4R?M9xD>E#jw6vq|ncpgU&gLm$B!+5uRJ2IJ758 -GMy(Sg_$nVzRMwXuj)MKyl#LTCs)Si>cir(*te;uW>5ZkrA<&lKi42kOTSg0xv*y$U1Wp!lPcnmht%6 -G_}_zQYoGr8HQTa4&@Euk{?m5T0c*^qCA7vY0{1H^zw_+T -F&@F+gu`x55H^p3LapduI-Q%-HpyX2er3nvZhh$#RqfOL8=%`N+&kwc3JF-w?$IHfuweSM8*=7v3Ky? -wgI;A=N(8L{TQI(Uq!ZDfWm@qvu~H*VDdE7aPXweC#wevMb2(4T-dXZW&8h->zarnq2`3F;$4~NSG9#@}WmQUg -)bP>Mz2W00A1xH56#JJnWh%GP2qX>cjN_@+?M)>3_ApR_;UA99@=CC-SjF4f1I7Jgo1Tf4#9vkSX9uf -2{b(0r57oL1^m+KiDKoRn44>=`cON-r#3@x5+I_S)eCPsF-l)3N!zY}E+bUJ7wpW&`DZUd=)3Idd9zO -o=qxIf!l~aQSW%hQV+^XSPI`wv+Jl@;=es#T>Tkret_8#tjzp|d;fvUy>s_i{|_}xdppg5iCf)AmMM~ -{tywSiX9fY*DE8>qzKnx`Oi{p~yQHWGuN7DL#g2pkOLm0LE7OSVN*OxBUiV+^pE&MEl?u(>PiD59Rb{ -pdjX38UbxsJqQW%b0j^^0E%jaQ}i{pd*F&wMtL&$z-`mfdf*Ib=m5c+0w|q$G)(WL|uN8dn=TSm23-- -6inv1%fh_=<*5JdZPaI1WIowB -HTwy0V^rOJhRAZhi?X9e+4R01wAf9Nr`_Y$_@Ph}JwAom-sD@OvIEMO&I#OHO?;>nT=XLlV6*8#2KAweTFflVEssz3Rt-uOWf8xil-om0hI$0FrfQ7zCk&N4!n -oaYtRh@4raAsmsl2VzckjuQ;UK~c1m2|%v0&>fn>T2S;;VG{nw1weiSz{&FF`U|;TSL&98|_a*u -rMC%S|d`5$b7fkX8tf?uiGU&6#R#S=&W|GM3bNjf6WfD&h4$*Mrlr(68R -b}q0G9f1I*p^2jBjiv>XHnX$1J$>f(EY{t8gy;r;o!vLm5N=PWlQ9m^%_$}$yeF9G}XTA`+!A=SOpim -p;>9411)(D(O$*zGM!hLHNavq^~|gHEmQx7XBsvkDJ)E(;FMIKviwFK(ULb -2LW0HRU&-xFQ*>PwSkOF+ZgXo5PrV!M5 -kAFk;HNhsxA9zm3$1Hb>B*H4RDNjkX**TPFL3g5W!q0|29ve=*EWQ3%r7{FV+U=t_b@#6Fc+xhJ}t8^ -U&pOs&B+h>S#k=Ci~`@iw#gW-H;x9Os;6BD%BKcFD9wKnTx|C5pXF)C3S)R+mA@rH0WqIhrTVQzaG8# -SoSJ1ir>fY%g~a*qV90+D8J-`4)_?Sq})xT@SK#ti^K;Wm+a%yI%O3#J!11*zb -kDvj(_YxQ -yr-PI?XfDw*azW`5W8BnJ&ByE*260d(80n@vE;_$XADB)F-4FlIui3{3y!Q*e@ro$iM?L=PVA1q&U#t3%Jmk5|E(AFm=u%_T0E -YGp2~!uyzn5)KOsZbYb=MB3Lu~uw?d)>2rmpCP$BYNdou*{bFgF0xp^*<&ppuX$rq(J$6RjUW26*+e? -QIPqQi*oLf1|pcKMzP`=rvIG20;fWo_As@?|*+;(JMbKv>V92Hmfgi@#f1ATzdSA>ZXS)RqSDb9*`ia -F3!QIzQ-U;)#)ZW3jEx -zz!tm~m3GVqxQoe|ML-s4CcYV8h~z5X96RK%TdltjXTMDfkZ2DlmR*p%@qrSIx(2tqez#c2=~+QV_J8 -#DR86IYwjHg2mL0!Hl*_dq%7=xACb-j%6a|_GC|?mD<&uKsy5R-=Iw0q`BfJ)n1y&f-$H9D#)XkM~BB -R&i!SgsJHdi5wPw9*=kJO$S3WOKyGIpH1WO*`9*bx973zLINfyN;%?^mwYoN3t82qx*X)A7i;mTJYeYBWRtraVmal`Aj#jc7Xkh`?FZa>U6Oj`;g>v;Uo4FpyoF|;) -WOUDSdN(nVqJ=fnS=~^qIj-LiMp$u&(w%RJDVQDjrhF^z|0?>+)DzqL-H1*1KhZjz|zo!c^7nYX2uz7 -ZeO|$#$6FY1;eN-5Kz}|h~Yb9SCzVN}_JwFcUc6hhb@AN*d8UlXuVQ+8uV_(|)FvMSNLosY2^fe9<)) -|vw6S0dSh+|g9Zb?zz_=lVClr9l)`kd$oRpEZL>jlK%v^;aOt -^{4}1L;YX>C9xSchbps=>abNE+%xuVyTH}l{&371%ea*`B=yXfkQ{Y$&-Go8bsY26f0Xu1;NFrmr!=D!X2sQ3rm$Pmf>~j&94Y_JW667H!rxk7bWdWl8j48e-40Mqoc;~)dyPrh% -UA`yp7WOE*Iznh1Yqg2%6FB%jKx#sTie|CG#hP*+8}M^x-4W^M0i4c6YuMPpz)J4euO*voDR6sZoUt; -8*tzM^!x_tPHe7QC7;~+2O0VpcJxRU{ohVNK1XuHjEnRExoG2I=HM+vAn%rr{ei9ubqnX?9)0Gk57Jf -Do(D}LiPDiE~?}CS}i_1c3Zqm{n*W+nlF!Dp1Q+RJDW6AR(7KL$jh9Tb5lLrRUL -+$7!%@1J7>Jag(nT5Z{XleJk-)gJ=*{D!W<*rfw$kwS6)cE_&XdmxUK<{|6#^wsI+zZK!&KNwQPptOhmk4{wSn28dW(YW)9o=Hxu~cJ^(kFF?n)8!x_D?FSMc2_-a2tAMadIDl5qv -23W%-F1av{^V=iON6{g;6SAFQO8YR|4(8RZIWxnx%ST6&Rnlh4bCQZCRx!=KPK+>W-mzdx5!=eD`ofI -?Ht~@apkv!6Y!hL8>0mCi*NVf$vszB;R6391t3Ys4hN_Rc`TXe~S)Xhj;b})*XusEX=v~ok0CMx7%mfXlYASF!SDx|KP%klV`k -u~874bY4O=9aM%R`G8#r_@18M%2ec#}qwWlBtz!7{01@u@4e_*MukcO{PhOnP4(>SFA_RxP=0;n{Kxx -`m)fnaKc@Y^H*^;H@!Kba|Jzbz0!@qG05F8qVObBZB^|Q@r|QT`0?(jf(pC>qsMc}v(s^0(&U~o{;^+ --n(_aj|kY?r7LM33iHHp&+gm#A;la||~0Jo!@9RqZ%3{sE{Cy~>$qX>6|IlGHzygI -bbg1ja^OkCmy%^?WDi84nnR=?eg}Mni$L=%k^KR>&Eh#3S0*6C96AN}R%>995S|aItNL$=uA#E#J(e% -cBe)-&wvAp{3|;Nn{Kc%VcDr@}^`0o-Oc163*bc@P6^&meI7^Ichv!Iq=1&u%mND9)8KQ{i -Ma2b_h#``d*@kJ6Fq$FKw&tKY`9r&9qh&b-D*n?>M8fjns>xxsz!pGp_-cuE#Q>V3Z~em~W!#}cchx! -VMN49K$&yEQbq!L*>ryY{(m7;x5}u7P>Pg)K1G${XGwdtrHUj7hFV2sXB;(A{77+W~w884!^x4#zTHV -B^O1`n8qB5yx`83fft^|w`Omz}Zu9RDxl+FDl$37-@%EMA$ivV>knbtGs8LYf5t_WIHX>f{C@*BFxBr -WBbqXNh6_Pq0GcW(`CpRe(9x@a>JRmB+K(9!caxl}=~*qMq|L??NP`n1JvGP>j@5@osxlSr%4x8}emL -$5U?o7=n ->eQ?uP_0f8)WC};{}VfZwpX_HMDTKL^KkoeT3&h5Pvb&oL^v~UKuEMx-I}j -y>i(=t{?C$menUnMGHujh8dPG~SOTigDT69mu_e9)Zuf?DYEvsX1cfjAlZdP@->bZz~ummiB^^X4o^m}4=;X_YxJu7c*z1;t9z;1JQ2B}R^yPD$&T(J_-k3Hn67_Tp0qGqscB5BRAsDlR=2J(lU14W|C4^^%`)7yG -PtRU(K;--PKi$N|tPEK-EGveg!1uNKj#IXfcmXPOfw@*0ug$3$HEDjbZHI-@wG~sa*8V0j+`!L*uhQX@t4}(39|GKYdGH3EEF(AP;NlUT1RC -))>_?JT&6bGRuf-rbv_8e%=@Wl3bG{AFUIAat$gR%~gn>D*-jK5QV-HtRvSi2h`5FInkiEQfn%3GrqE -0AI$=Sx?>SrH2-d!7=hy2K|tz-3&6%VzX4MOhp#Xwc4kUp7zN-VCOOeZV*78ni`lEP;Jjr)(IrNON~z -KDmVH0=Z)*E(XNittuZBu)y`<^io^J8uefGp_mgRzW=jAD{K_^6P?W6`k{S}J=3+Ze)M1Rf_8|5t#g2%}v#Xp{)mk2K^ry5!OO6KQB$Yvyvu`sFuhR2kID#rHK%h{;ohH3}s~i7O -wBJRZs4s8R3P1;5o5%%NsrKL#;_hA02u4Afpzqdn -lMJs{=WOB$iLjyr~S5*f-k`uP7c5C4xqlnD_ml*ezlF&w=s^caE?A6x`g=a%GKkqX{JV4#SZEtUP>+n-yq@wJan4bw`{*Yiwy?dNl?XLEsbxmwyzDl7c4pVL>TU -vooc;@-q3`N~rh-5KdCIU=%g$3*jzE|S=85dG-q{{23Oc4wC`w9gW&)#dPmBf;V -xhqR&bLX(OOR`G7}((>oB3@{oa{=9MnvE3y&Jh5`LalC8j6x76>%3Vopo)U4URF^7Rk7~l6nwEISBT7 -Tl?S;vn59#;t`6j-(NunD}M}Ri!F@}q)G541R)rM_NKAWch4}~aVPZ071a_n=V&t_C2bz`?~uJU=HbL -I^ZX~&u*iY)Z)Qv@$k&VmWkb40UYuaD5gP-RAtL!lW&(+kg!bmxO|YtW*vrfun~3F@QOV=3vownN8^U -#6_?Y?I0hJHUj*D_o!B2@A^WN9M3d#oEQW%V*?yIk?2+u^0~t83=*mSd)tigtS5QOTI+DW^;{cdU4KL8`;Y{rR=P^NogA&}$owJ4UjzQLWjfcP;i#;PyKELsJ%*a`1V!?x&S64{ALwF8#k& -HGqFKNU2zf9r^ZByksFGY&mJkIone$xGORKvwe_{5oUUH^d{c2i#?i=rvt1%h0rvdG=`?=XsYKhf|*P9eHF?$OeC-#k2Y=K|r_ -`PrE@PIL>5G$ji#6~M`uklt{8I@NtN9hOcA)kLXHS<+=7sCfVhn=KD7<792z}0n!4%y1%svwU~)d5F> -z)$#a#;O{q8So%BYY?wf$}~xiS#Elu8R5UpbLFzSrkHOG3fdi6ZuB^?@M>^AIyyf)eAUI`ZrFj0k6yY -440zTzVNR3J!N`c7e>pi`*Yz@rgpYO;eBozCtlL*xmUz)(akVJ^vTwfnKz}|410hj#Ku&&14fCBMajp -$i5T?XdwEx>HAcsxng;wiBKYqt*9%WFrNoFsO&O&77?pf6kY1C*^B_5_Rt3d;keL2n}%*t?PUT25=RFWx&&9BD8C -c;FS9xrJs%A=J|30=W>bGhPBj1^us~SmA8_@pCHf`2CVRlqNgHNB%y@Q)Z>CRqqU%!%G>+ypj2U(MaH@U}mqZ%}w)V#bzG?LpMgr(%h%L#Z^c-sIDclvoyWH#>Dc&yUYbg6v(3A=4G@!-`Ylto~6kxtHqLvE -{c4*oHQM(*!BheIH19L6E3|9P*K5wxY$*qYci@HUZ$SzZ$(>yL=aT1bFcjCy^`W)+v~2z2-jM3Bv_9q -W^Q#{8ZCI#hN%I^^XQqCIZ{V}fXyxpcen?gd(-~CX{SNjaz93w)hg(Fq}^*nJuKYVn32uQZqY(Ckc|HM+9KLle#@_p|_vE9|@`m56wf{YcQ2(;$hLULVebP>+*95GMQY~;8ET( -;4w}0&Py{%CC+Yjc1YL6)YaLFu}Mti4&y$~@w^Co3s_Z?LD8(EmyRr{n9)rlP$pnq|mQZi+!y%?vCapLkvG -;9LPS!_F@yLVAyPu6X$@NxIkxZ`=m`h9N?F3XIZMtl{i!Nh^JXufx6QW-TVcn`ks)Q@Q&g2-2=cH&!% -V!5@dZm>+bgH5E|)dPxXB&&Z)k61wZbIr(`=sWh%c;#`zQ$JFOs8@x>NzPUXN)wxj0Bx+ESB8?e7$R| -y>J`F%p<7A;+JyHkT1$_by9iKof7t_Zf(cxPDmErx6pZtF3ags-C7H5&5ognh3o|#PnbOzeIM%LBq39eRlt5?@jGGcTzgOoL0uOdP -$J<&TN845kN5%ardSebbv(nOEbd#Skr -l@E>bPg7F@_Auz=teIHy4Q*9?`O2ly%`EOFymv@}lL?&_~wOIBI4!_MVLn;_k%3Cdq98I(cA30f5v6h -^(XSu{`Fd92I%4rt8OS36nQ&skUP{V87S?o|&%lf(r*{j?rFk8i#V@09x6QxCwa{ew!Nu?z&?DL&>@S -2O@g>Sy~ZBH$JbPQ=W{p6UG(ugk7w*#4|pHA@qRky~QZi*Yc`Q=6vN_R_Icy@m+Y-{PfHdnVaVIxd7zywSjDH^m;+MVmr6=;codP$yM9pX*T7)zyAy0E(5=4X}Vi -u4KhOaivln98iLn+~mb4=cHO|Hxl=zit?KbV}H|Wcsh{`b*WrETwo|Nau6+@)aX-FHMs&P35UecLQRkv=*{ka+u_=-W;xOW`(jC{c2IbSFjzu1^F$tXVx6V_LFNSGa=aPa1;(5^5ciJ%U4V;^ -uj#wjW0*{51JQT(9n&*Ng^$d>x3j4zXh_+m!sm2pH`xmrVEWzyftzuU1RPj5yqayjg)Mrc$mO=SA$$A -aEJsAx@dNpZC-$O8nnfXrWZLKwAx!s#G2_RLvq -Ih77b7t{^f!nz;&l9|UU0^A_w)A?c@2q8EY#iD#%GmS)FK{Rf7Xy-Scz8_9)EZaF*v0S)kgdmA;D>^M -L*b=qFk4l4a%HgjhQcb84NrGlEE%Pu8KJCxjD`*ObiI2O)K;SrvO$+&b8q5vaADuPz!e+snB?gj<~1K -zPA_XR@tSv9f-8$x74HML;axzm=PhFpk^8mbc-7_QV}hfVB-aN>Ud3%-nawd*$ZvYs>w}Cnq`bm0)1)QZJK}7Tbk)R`mF -ws4>(Syv%75BCIqIrh)=q(zEL0dAwzkH_ED?9z_)|hB&b -9}6stN76xCnNRx(ciqQ7>C*Cw8%3wP&KiOAgc(7jy{_Rvj{X}4fG0VG!oQ9)Wk^!c+Hk>kNgqLNvsat -8f@Rdv*(XoJCj|P!LB=McV()EW-N>8j{KASY>E9TzU1&Ri{GtaX{bo6+GM#dk{JbR!fJgxAt*lD=Bo> -=pHoaEmPG@Q_&6c1tC_HB2s+4;X@LKN5$T#Qa|34~k8}C-F`kj{gNLdzCHD^d=YOG|_T{Z#}S#Yinn*+ca-nAXSi4L> -Wn_2NjaKA_EQY{Z4(8s0V*o2V_gKPp1Q?nDz|XCIyL^5Y&ziqVk&jKbIf_%rH$ah)Qum_f*sg+axX}# -2X$4(8&)9(*bCP8X8AkK(j|Xu;wP)re!_GHMWhUt7Dtl&_W@Ld`(TVr&3wDM#2e;H7XV&22*paxlLY! -2Q`2#t3DD0N{!4x>~{e?4JvTi^k9UU`Ai8zE0Aq#$}&-9Ia{gXvdkwbQK`OI5Ez38)&&il -00bCb<*Z2_BBo<--LU8A2*Iy@EHTAsNF@>~ptn%(~oES7`_uuM2qbwFD>AqC@Nn#eM{Yu^`=>Lzc5Xg -Hg#T39w -TajB>IME03Xgo>sR;<{h%P%wKo-F -))~(&y)&gd=H*gDmi0X&RxN;~;qbg)#wT`QY&I{n7bFlcH}8c*Ph71gsP*N8Mt>%5obVKfCV1cEUEHxu?Us6Nx;Jy -AMQ3U0tJdp}9i}*2Z)tfKAwFSk_re1)8zr%vYZ?|Y9>S%Z1!_mI@hx6GsFLfB(`l8das58mN#= -4mJ;^MExh{02los{5~feRb^Rv*Vvz=E -^Jn{HL=T5t{joWfsjTDVB{-=t{+?CC}D-0rr%WKzX-0?*=Ze^_wKoCPUJBlGHMtZy*AE91S>h^wi+NM}B?=mS&Z9nR(S|Nx*i~Vy;@_WJ-O4Y>}le8S|0O%h}@ -i(J*sZKXkeuuuk0RT&+glIqNguhR4)E$*tbPYnt5+{+y|4r0P`{3qx#tnW=;x5?z3~4lMa}s%2M%zM6#2rSJDuOX7jhaqmakw^xsf*|z5DAyM3NQ2hFZWX{$WVY1~O -ABN_hhVR;umP{e;Paj4zqBV{p8<6fWioW0HHw>dIHE$6*?obwX${cTGVN|spOpmI4J<}Lvx~=&&Hjs7 -4*B+kEpJa){$)jv~t)a!RgbuA22rqWw+rLRWc(vGn3;vwu5*P{B&H{bzH1s+t4 -CrojRLfqp9V(HUjC=Rsj!GAc3Kr>s{M^q4BgHQ746<=CUQA -iuiR4rkbOf)g8UUx;n+4wLmqcKsah0sQw8nEdMY*8$79E(4aA6?t`6W$kP_^=!yr8H)Vz$DjNhr&R@v ->MEJz(YSKhGCUtb;bry4E4T79^e>)cf<5xLwKp!xC589jtrWIs0pnZnXzm^7RfSG%=>M>=6pkZn^yS( -byD}|HHmp*Z%nGw7f_$<76}f8iia5;5Wg(tG;S|C=7j=F6c83)o)7(go(TZd`s+0+Kmv5qA$m4+2v<_ -X>&C9nBdy2~N0@D{wWNyZLnGYM96OIr|)pwZfn=5rJM!DWF6%RSrm!Y|}8vjvK!~8mMLm{l-*wBp1UM -vkRvTxQ1@B)zt3~xT7cnUTf62M4UDDvg(N@7@7X<;XWCZ%tl=byAh<$8vx=?hY5qtIZ>2a<2u>9avhV -V7h(hi@bAp)p{YO&oA@3BTb4bj2drupv&C72fJWp|Y>SG*+B@wIJbntMsmG(k+>Ck!Mpn@fBcF@GByp -6N8rs{TQc*j-II?ZuH#i5gjm(M(_Ge^Q*})1CxjeWNrKSu~V)a+p=ZOr%jn)n7 -cB0B3dM_HLlem%{At3NORwE+{95wX$4$(6kw`v*oPbk5zC-^py!ntTKX!Ucu2)VKwVQJYdy4-_%)z1t -7_(wL}h_jp(G-@E@yTR-r+4{C_G-yW)!oQW17_g){>%VNfm=PD}LBlaZv$?jUES2f<`$oztN&@g=PUL_gw8be}OPO&@d0PLgpv`DB3BVfPxtfw(ygLJRXCi}VSJiuFGopf#GV`H~3 -+1vP=C7O4Xz#rtt+vXR3(y7-O3sJ8RVZN1)$pclSaMEVYH7J;)oqG=aisd(!h+T`$5F#d1*-`R@z=x7A;(?-EDa(eUxQ^0tr0kDI?45cQQPel3Zjt8A@wMo>OC4 -nksPjfo6h*L;Bh@S5#(05_q9aN_jT^gatyq&pS65X`BQsZ)%ZiQ2bQ{L>0F677V>Z08q1^pcj-*46boyj9`WAyLK(E -P9Jj#FxerC>C3-SF;7aPb?_xK=U>mJX^pq2-bzMPgk2Z3I6=Y`{rd4DU22F9BvDod-*{)McZhw|awWhwkegxeY#I3Usn -#_svZqKbfrahOxt>t&ZS<~xh-C?P2GWN5Y?7u6>r6PneP8yEt+*JZkCHd^nAHFIT`!(LYLn2e -zjP=kxiXaf8MNDn}M%}jaFaqfq%Zi(a1Z8IB(ir!*WI-3t<}#|u($Vk?u7p4>N_ya13Ec6M)HLN|FFz^Klp4SM)CVUo_2_1sXY9K9-E>j_ --o6S%n^D{wJ-5Ez_;kYqwD6Ov5g89fa41bJX&@|ZY@ISm;&6sstex+x}wabUU -=hG~*%;iS;Q1ZK*tUj$}q!AuFvG_y8mG(=`?&S;2C&uN+!V?nJ-=*bAN%&x(V5X(HHIU~d}i&#d8Wxh -c*Bg8U`SmsB>(W7U+b}$Puxmjo?Hw$S{8)F&alO?I;V0mUX#xh2OGP5yej1fogY#Gaq@Yvn#Eg9`>S! -yOFOB3SQK~w6LnLQ!PV&a&2&k4gE7@iOZy>g3T?!naDVwe+#xkW6e7|Vee>4^~GxrHSsSaQ4ca)Kqdu -;c_wZlfiqXvuACrP2WKshf*GfH2d -7bQhh>6`PS4A8v{%v_WSbx-EHdlHzYn3@vPBoC<*6m%Sx3kw(>%d)%4&$u%sVTM(xj4=GP(D`S)As?p_-MpY -I=;>1Rn8R3kY^t{Vr363|{!hQ}fWT6~6fvXF-F6gGvm=Olh6NXxkK1OA!LQ~}FrpVWYP#0odFs4QY^( -u{DF?kyMT2HLIGi^_r=t8FTq~u^)89i9<@+Z-HvQ!IaIht~VMwyL;N`p{oze;3`UdeQjcqL6-X|fC13 -!KU`Q(1Wlar9Q{RdbRFtV}>ML18!{$%J}ND+MnVGMcX{EQ~M!uVULhq3&5=I5?o?Pp%barM4#r&Q)%O -a}qYWmRC_gR)udlBvWL@uaaa++p8kYmDOBPuWB@7?#;)XR+iKnls}ni8aK2#<=FB^CtW^07x*sIJHPL -y#HsYo@6)>Qy}7n0%`rPgEVlynfN`>D#rjRp?{60kJ#$E*fTo#M=@{9*eAdvT&S{J}qyEkMu3o5T7xm -P_8~G@-_2}Z$1BP!mJD+w>-|n74f&(nr$~YP2psCrrLptO_dt8O|U=U`7PGPf3H|5k_2ed2&CM!j+DA -Y|+tar2sb18q!@emaSar7#ZsU{_(rP75aE!^yI@zJyz<@$(__62TjM_ -Qx^1~DQ*2}U>+6HASRB@0%=lGv+`wzNxfp5JCdiV@ibMQQuhoPrsm|3MH(WvNiF%6=4ObDCM7eIl9@@ -#Xi~DkW`SgL7TDa8Y|etMV7ELWx~s^^mDqZqUeb>XjQbvYfI~GX~(9Sa=Jvxv(}DWO -G6As!|y1)XFod73Dp?Npnk@rZTYkil?)#jD+n5Qql%eW@f#Yc|wVcafi+=`NKvtDxXeYS%`Ik=|ZUsI -`}Jk;;j-hJ5>@9$K+TQJ@!{@+-YSgHcgZyr6$Izj1*?E7S6`hVNThS{LzPsTY6^4PHD%k$P0O5uyI#s -DfaMo@n$h>zj5;EZIXQ|jA+}ABFmxY*2un9U=cn4MrOZcC2>lVh^8cyMwUY|sg=n{CbN1n+GR5yk54(3xHuPLq8g%F^qb*{aim&hSFc}k_TpR)a=%!LX+L@KIK8}Te -@xG+>mP#C;HTMSHcEcQS8v3=_|@e>hqD9D)wNV2LtJ!DBNcr;^wOPg)}Jpn!&EUgvkSP?=utLun9Ic= -?#?$-_I^5Zx^?d;hO<~fY;(T3UoM6z{F_~)d;d;#qw>Bysl71Bb?b*!`vo7GTiK`L%Z=D->ZOlw#N)l -{JnhF>-2!2Du`>gAqZ;aTj_OnmKHCvnbfe^ADLeUo9XO&cA!QMwiK&r|L*e4yPi3(UpwC6STvrJP}Wo7GiQDvw*%HU{ew7=sC&WxX1H>2<)H1hJ2 -c;HL_fej@=RHGvLmvE!n$us47SbeOt04c9bYbJ)ghMpcVn{{9{QD(8iVdovKb3ofv2ic>t@{#qO}4?q -e-<`pGJR_z@Qi+y!~vlp-Rz{&qa8gn@CeW3@{`T=#Q8+#8 -+3T8t%U$HyRt0Y$m2-^MI9HmVLM89hTY)&8~bhUMYG_`+%ALH&XiJs?-lv2BA+SpQjy;)@`EBjO^)UP -^f$RwBG-!isK`{28RZo=l+`4v$vm*Z!cMlTeG!H8UfeHvLjf}D39US7#8@9gx|#n~UOt}f56uD<{8tE(T*t{&@1{!3J$yLxQntH& -2|euf_(v_mCuL7Ad)r)U$Pp=lKDJXAV#=_0Bq;kkU~P(Oj{Gv^GInk(91n0bSimMWd9_yH97Au17C{3 -HaeL0d)nTv2rQ+ZaME1BFlvp%%@;Q?!G{f$M^*<{(5lL^+v*LQ{~aenAtW(nO$)Djc+>3dBT%ihvsOJ -^++K$5kct)i7ZQ)nS-`QcHvu8Z>06z^@P(g|$$$0l)Dfmi}HNlPr -{GGhoR#uOHeD>GQ_fx;J7LrYB~*BH50@H4`PVagay8N-yZhAeB*(k4_v&lW=pvQI#Bhp6MO2kKKL0Ij -K_`fv|sa!;Ab=gLg(ql*t&KL&-B9QbfwS;+&^7(}3qDjXD6as&xSVDb=4kOMd#!#@t-IDq3x0}6ux7z -bb+fbkkKgcJu-9N=)E!U+|Qb;1R?0|x;OYstAbj6f5rsIgjdu7z_gIVXUln5%X{yPLr7CUCR}+D`@6g-CqRkXR8Y1Er2wF(SeEf< -YKzA~?G902KW=>=J-baP;FC5F50pNe8x00XRhygHi)_Q_;e>&Wl0EifZZRR6!VYegv9;jtvUDE&z*m? -k-j~b~q2a1Se02)2d_W4o5nU;kz2LHCUK=Pi}3BHW4ZfDqW0GWEhcAyIRbWGOECO-cSW%wTnu -PVbs|tDeP{@JAp2Ox(ImDkry3o!Wrnbz)LWQ3BW3-MWChgU>as!Qb8581k@6sC7>3d)d86<%FIup>ANbFB#l55uvCuXyFSqftN;}jqM~A0(wZs_Xom`iSXYCBg;4e7__7jUPFTm4PF|}@8*_y~C -;zTLojJ}6XSM!2KiNu~@qX|5+<8B5{@10;IuDMo{@=4j1-s&h^4S(PMS_xB47QiU -HlSk;OKB^B_|-Y;PNqpJ;hD)ae_e8UBwvgntMkR5ohd@JKFkh(eJa?1jtSpkj`vSV=sqqX+ex)PDz7@ -sm_C#qZD%~YNsb2^Y^OTWKRd~R4K>+-kl4?7C`&>9v6ZF#JI(lan(?7D-=)D9&rwP9zL3r -VOy|}wyiryJ+@t+=K#_;EddeP$U>PW6Ix%~d>QJVDV7V=-p&;B<(`>T7#uFkGLoz7mKp1-a33s%tyd?|d`xe%QQU47bzCqmsFp?A<@>|KW753e$r7mJ?@n*4dlnMtLCa3%V>1p=@d3 -=eljNQ78k;YDs29Gp#FNlr9>MCe*I}G<1uU@@>|Ni{Ff4=VDyqdeN`wFJK`UTFYD+_g%AQjk=ewj}PH -%#O8g>rLPkI!0DHOOp#s>>yIxUGrazW4ihJ!8O+dRpnjkpOvejqQPld*I30*+gNabRAy$?*VKU@(6FU -UJYD!=IvXF>S~Z$Hn-w~Ap@VJfZ9pqkGUsOwy0YtN8dYbc?LnTYRBL{+Yg10uvYd~o^n|E(KfzR-^EP -!CrdRQOpe(}o}Zs5H=tdQ)TP^vGPqaQ)l&e_tRK|Hwmg0F$?U@jd?Ox*{ki(~rCd+>hul9pJs9>>!C{ -48IDeAMtCuOo>P)RMdqw{-kYV3Ej+UzaiS7f*j-79A^lju}%5vTE)zx(2I=U -%D$@aAAL7IoFBX7r7a$&2Bd37yhi=RU4GUQuQ5s&s3J*;C8=;9OFNz?Mb@!*ufLWTw6`ai7J3`tQZb2 -Kt@9s{uYHTAuStnsNF(Qp(WTv4|l}Hg4<&d2}z`{+vyf^@laO?JHrMybIfQhi`a83>Y+L2L=t%;$WMtxKVyN2nJ+j+ADb^ooY-Cn%g+Wzq{{;GBQBorU -NUdJHbNB-rWDjV8QSx12m8+Q&9^F_vfwz;3C9N@4mjBkOip9!5K{pkx8W#Jxl`hy|c#k!}y>UehTZht -bQB5T`J`+T?)T>EF>_`xi4VLe4k<+%GithOx2cY;T!%i6ppesmUgv6+1Pi5q^Wz64U=A?_mc1n%~E1Y -ms(jyq0RtJG&(he;*AT3XRJj7mandid68cJhBvO9KQH0000805+CpNgfPsHCrD502F@!03-ka0B~t=F -JE?LZe(wAFKBdaY&C3YVlQ85Zg6#Ub98cLVQnsOdF?%GbK6LA-}NhIsoD~`Cb^RQR#{G2ubeAhTV>0+ -OUVig1cww>Ab`a~l=gD}J>5MI%nKClT~|`cvF^O$V5VoLr{CQJ&dYqsCX;znHzl7;Sh6hgvS!h=%G0L -i6Y+h>X31?*C3!YvO_s#@j8D>}=4F&tTU&F4Q6$B9a?7(>UXH8eP64X`Vt&6Ugo -#FQUkB+x|qai!ZVn?DbqBWZf(hDIDcHJZzcDaprEYylxNUA$rfs%<2cW%I?8H2)#-<`A*+fg=Ev7j8O -5-#YRIB0PLg-LHoko>3SP!h#k(MVNv@hd)g`R&?T4+cy1YNwV(@W#9 -NpKvf)1Pjv$xcGT9$b!y36P6(15kg-sjm!o))`>UBH4Wmd{zc>^WensW?kAR$p^glzc=RD7PGR&;$S; -*7ZBjDqi<5;rN$hLN4$32tB<40mKIg(E)Ka*fLT2EfCG|3Y4!)pW?M8EAUdi%F)xdErqM8G|-sP8OcXxMhME`j~{~po5r_{7}hW|c4qc1Pv%h} -1v?g;;LieJvoPU+vXFP{+#eOG^pwtd~UuiGB$w#T~diEewM+n(ySr@HN#ZhNNNp6j;fy6uH-d!gH2>b -94NPZ&tHGGbND3uLM!nOjI}QQ{+h|Bn}Fd-8?jYI&|&UTnU)7i!E)1?8n`d81n1Y`(f9HReb`IaV#ls -^w(!)t#s@rwYocYB^IaXPd81h@zqT>kQQfX`@NeWO3a@C*59MwS+2xh}pvbF))(nqEdq4hI!65ek}M?Gi2ztZ6TIudWU(N<~1A4q3{xK8+u2IZy2Id+mNo4$eb{-P)yAoZm>S -LA$YDscaDDD+zNlm4roxRll8B>>sv=pjBgHZFhAId_jvg_nKS{*ef~Q-lYUNIt-1mdM3E%z3xLx)cIV -syN2_h$z4xzFUG#Xz96;S&S)=8B2gd&;YcnMaCBFm0-(Nol&EK&{R~h~JEF)v!N4$7^n<}SE=_61&Vg -s()(A172Dhwc6M;kJ3R1J|U*d|K$UnlVka9L5dYwI~VJvTGqxX>s -`O1RibC$o5hc@z+OY^{8`STmcZP7Y@%3H-HCX`DHwQIINaO8+}Y@s -F!IsOg(_U|k7DvClMeiTE_AeK|ynCA!O|4{4-jp#PvY&psSw-bSvF4s05d3#?J5SYdDK(vtZ -l%=vW^Jjj}snahg}WB6}y!vrM>&V+jLeWd}e14CAVCK`_R7x%fE&!-x}{Es21B0%i}!eqydyWCYsAf4 -R6g|H!K%&niA(d8s$L&EBL{4u%DfZ%FJ$vsseO0Yu7HB_IxsEpxOU8Jbdr^FYjszGDD3jc`UwWqAX~^ -J2|%O|6xH4J-WPLv|LWayg_sucIoe>r$_!hZyd4jP;iw&tupIHU|>6$)amvSYy@6=n*Xt9k@j1yd$jN -p{5fAgoEzJK(7_X75cr2>fSS075Zo>4!K83#n}a{fx?LW-f@%(`=rKTgBnFa3$VOZxFJE39#XF#%O3l -&1@ChOvVh#wT22Vy&wy}5QRDRhl!MYSqwA=gm-%fn5X~UCmp~8Hu&WYa;>P3FTv*dTs{mM}Rw?R -x=M>hZ}j=)zlhqow{dJpDApQnVsi!_Sii;N<~d5K{kl4+xg|JJ}rTpSFGkm(Aw+&E90rHC%2Nyb@?5l -Vm~*Q7m{2?(I5N$X=Y<3GWwfq?dQ_n+)`J2r$;g9!lETj-EP;BrZ^<4Ck|d){5L?oy!utntxn_C7C{0 -PlD3<|Mi)$}M_Z{tJerr}x0>%k(Js9Nv=S=`9;@HDL-hRQRzNqKhL_(IaNm@G)a=!SgA2oXitG8;UUoD+%`#<4r?@)E7}XOL;} -1d30BgKKz1x>NBn;d0>2{koHIe^T)QGio@w%I3~u8k_v`pJclQWS -&tLLIlKK}G%jeqkk@b=qN*=Fx#Kw|}D%mFVeVr-yF<94N%=8?;1znC$I*x%YIoLuLKM$NaL3{JMKQNtNY;YVmxjc+V90E-S+NFTn -4LmZ;IO`!-IKI3xu_?kPh|fL-%u-pn+at0RRXzZ5C>YDLc&;(OmXI#aXZ+5#|d1tkvUt(1UOmsRQXe%=v4?p1o>J`s$3|EQ4td|7N -Us1C2RdCQ}jm7GHoxP(n?+1zFatc) -h3TU1)lFO6PKq}~Q;euMM#;xxuw|j!pWcLnp=oBTTN3ZbF -$U9S31g%y7J7 -qqi`x$`4*bh}$x)>h33-vIP*!CQ9*j(PO9$N;@gI4^Fc8|}@Fpp&7@ykqGOEFOO|n3h25lJ2X8O*G6z -u^_pTgrjtCOtZTXv(wfaEQjb-XC^rsxlB1`~N&(V?oNvhEKYNpKE_UEiZvAT+FD#Zi`Fl -z^hT6~hkh%B6pwRG*Y+JdYBw%}bd7fD*ZE|0 -o#m2HKL|HCvX>4AQ(ZCOBGS5;K#+>1P$4$fGkcM89lu>Y+fLcofg;*YAG=P#&) -BO-DzMEF>eHRP$@!kDgZ|7*h#V-45nS@Gf~=x1$AWllh2*ht$JuDu}x(|LSb5{m^+Bk$+h!{BQi;C==G$#Q^Y(Pn*SL;u8{8kRYbpDZ;ELa8GIJIM63)0*X18e4g0LU8<#ysh44y4?WA;!__kEK8R(!C0t&M(x@7pzM&*~wu-8 -32l*zBF#f*cXf?3BX)CV$%C>FX62?UGA|%&6FW`suo}c$_!T|2t4C=wG#o|e4}C3e>fCCK?+F-xDW)1Dq5&u^ -$qpMtG%+H|XZsKr9(q+b@Kv6<7qh8Sx8Ej5zBs*_1 -~|t9ld)?)u%y&GRZ{I;=VO&_?b!{K#slH5^4Xej+AiB+31xdN_6d|0ieOnG_$A8CU`^U1347yCH(gDz -qb=2fpyFq9vc?WwJ=J31|_ZHWyZ!+yIk32uMc!+J3zi2<&`SjrR7R?)Qc$lrau6;P67tu{{_ho#KeWjqV6 -6~og@dYfTULgd7U-s@Z~6$sLn(UwIc6?5Y1a)Q9M!f|^uOF+@*n*=5i$@peTwSd2HiUMMB&4#NG_q~8 -Wv4Ri&(sPLiv0&ctY?8BSLNh~saZOcX#Cuo}Wsmm67sjgGHhYLK*YHYV-06zj(u0z? -d#fPmr8ti8~t8vX;E%G|Hk=D0^q{5X%~?`Db~-=H&(QqP8y?y}*x5IE#BU;%~bCV}zAOjOQS5uWsWze8Sp8b3EoVG}A;}E#P!-3loua7FluRQx;fegGw2-C>%|Jq?g&L -+z`vgH4NyRHBYYB)WM>`J1k~@lZU}PSn(aa*2a0J-EaKn|4$yYIq_d@E&Uze9OPf4$es>f-@aAWRZB2kQL*2&0kRYEi>p -mg%!(YAtUX7wa_?C$d~K|2%RQmjd*8HI$0qpx2lTHE+VezFQrHcxIvaP+nR|7}6Y%&Ur6;?O!GOoNbV -RA3!1g63pVfmC`M{1Jbi5P9{1m2nL4(3KibyL|_EjF0XAQnn!dazJtK|vZxRBoq5u7*z!_NV(+E{$u45THmEHtm>XwUZ=x9P ->k^sY;#``7Y1a5)06~-9^uqJb|*}^rwGQLtR{w-_Y8Q8nR%!IFv4KO~o%{ff8nuCH#r_!!Hw1H9{i6S*7;GR5wwcWkL^oQGvo3x9&bPELe2kb(df*GX|$L79O0bg)JRQ>w -M@InETeMeos#zNNy$+)sGa)5INS4t(ufFu>A&MkNc&qZ|a&ai<)4IewZvlV?U~8p#Ub{WJ*f9LST2dv -~FBila8YHQ)X?w0Ya4AM`?m@ecrU!Ei}hGPD1A(R~LY7BEtK3|-@IAA+O -m^;HYz4o3>-kr*xNccNLzr#`6VanG+d%WL5d%Pcj1|5#t4ggI9`-f%lkFN%-odysydSQfBI3{-`4|V@m7l(kEWaY8Ld-Rk -gh9tfL#Titt5qO*R|0d}RQ_K$Ss;0wPkOJ`02KJB&^eKm6Mah;FM{TMw?8 -)`pN}@>C5GFDWr!!U3lt?hcL6H&szhaeW$JG+5;^CQl4r!L8gzCEXYk?~)T$XhNRK%8d3NN<|?vwxIX -QQ6XDqNIfhxT)#|!>dZxB!DJ4N#IQ~(7I)z;;!~P7v3!izu}RQm&foVR*b55pSTebGPgd}dG -S^i8QNB8!Ctwg8O}xh&^|7Ug94Xfyod<5GB(%v)6QPUFLv`JX_7cQyt6e`FlVd -OU&hIk)fcDPZ>99A~0$+nv8!g*v^Sg|nXTu!> -K>@plbw!)1Xf)v&xP|9cNb-1e`Dd -Wye;qog`m%i|n*Zi6ATgc)h?uH{YO4=iFIp!@m5E{da9EQ?*1CE3NEBAhZqW{~%@S$0BMcVJg<;ZK_u -{$>`{26MbZaapTIKJX3ma6=BGDKjE#qsbwTB95V*r4s{!_`dguLXux)lth{Xx? -AK;lT;v-ZmM)g{&rBDGM{JQ`L{W9I10U*^lk~6Fo)EpQfR0HzS2Wf{Gnj7Yww2>Z*({ga)TN82~IU{Vo -X+X?A9>*7#ABL+0E>qJNw%6&c2FJK)je+%iPjeZ0QB`Nq}r9Ib`G&&dFpsVyAMj{xG>;%}S(p*3U&Tj -amwRr1T&%FXHxzP5F0xw52957qPnLeN+wuMV{l7J#cQiqeW(A=N)Ui~l{^<>c)_(ZdmALmOdof~>x5m -9zua)4G^zw#k<)B;5iJ7!f3W+LzzcNym(99`C;x=P3#V_xRQ`I -B=u9{se$*eEXcGSdhf2p_>3qpe!Fh!Ng~HfnxGN!b^R+lW;N%YBAiV(}u6ibMYP -T^iEAMYe$u5!smH4KSjQWmlT8=~$!&n)XJc*XEtRmg2j73mR@3&C)^)nZd4*Pp}e3Tp=Ysqp|Z`UWb@ -su@!;Aiv(1lkN9JYJaMkwgygGvJ5^lXw(B=B$e2+KvEoq)a)OMZ8e|*j8N;}_j;ixAS}Y^))G>7nD>- -#UlzQBKv5OL!#_c}R=z8kbCi31aV!^=y%E|$0_(qL)cmXR8uxIBDuMqO^6VCTbaPkGh(;mKCafG)MZ+ -l_hG4V$u_vA>tUkiD7IbdbpcaXZV -1LTL_)$j>OlN(90Psx+qM?5pTEr=LphZ4CSCQgl@f~SdS~|9cAW=35Z##r!YlbnINFB-|nFj!Dgup*` -SaR;KI50Uv^Tzs~`1!gcR-d&;ZGOU{!u|O2QY^z!Hm>cmCZyw7JJ?xVFYkgWj4>CYj(m=T@7C^^i3(S -{CQ`Eu@oFU+VC`(~n`}&t+NDgrUqNzJiuB7#laR+I2`Q3e!9A(T2@vJb$s)_?l@Uqh0GcC3q0Yf~X3< -t;7@&?YRW9D~%P>c_nkn%+IN)kJK(i&UVyV`~(6=3QxY17c9 -SJ2VP$lKsk08#9*KD}T=L?gol+27=M!hY_V8c$}~Vr9|MH8K1J4&$ejj8*L0l#QuR@ylb*+r*3`u*_H -qrmbs0+`Y#vs#Wrdt^ZEgwwmSWCkDpxU>-D*TP{2lzaFJ#?Q)yU7P3O(&72Tpko?&H -DX54^p0v!Ujwi9H^33S4v>LguM4v#LQ=)0ahpyJL4Ck2uxLSy3+Z}qQuj?ogyE-FBO;enudQ`u-{ -&^n{vc?TwziI^dqpohSlH{hw;STQ9VCXGo_ix`8@i9}Yudjxc}-M{CQ?8=`|P3t?FUIXu -qSKOW3O59pI%DXyjuepKkLt+Z6m?av9>2(^H=f%e)J>{FomA=X<*ha8`lT!{PvENWUqTcY5Nd`e#Le- -+2%UF5AnRe?{)iC9MBSU7s;-nOG|p&cG}kp(zP`94T5zOBKq?stBY-w74Y33kJG~ch-QSXxUf5^y -qeW6k7K@PQJhI!%JZFWqz#1i49knt!Au_`9xCQg61n$~(8FcDSsB~ZjHl!TGE+3V;N(DCNWWIW&OFr< ->i@`XEAN;eFRMDVyo^wPRh4v6r70H}*T{_t*$TW@9o^pw{tymP2Ch$QskYk5<-*a%~S6db5FWVxM?wL -=hp0f45xY=Q{8dJk+CKY2QHGLZJDkY@AfM=!4>?cFQm7uVNGbR(9iLBkHrU(I$=DBlQhLcIc)4!u& -A2QQI%tCAqhUvM9b!wA)Ei)HCx!UId9A)#L!qNo22P)h>@6aWAK2mm&gW=X_GY8Jm7001N_0RSQZ003 -}la4%nWWo~3|axZ9fZEQ7cX<{#5bZ={AZfSaDaxQRr?Ol6w+cviUKc50+W@@>$RHwV!M_OkmiS21-`^ -Yx#-RyNezBCC*tSM3@NIPnq?|#n#2ofa0mqfi{aypGH0SCZ&{LaAv2#f=NMy{{NQ5XgE`kFYixgUhYy -kWi@h4fndeMD^M&S8%49g)a$EZ?Trt`pM0blIavW27;6=A-L7>e+rUV$M5B2un``Vlo2%#6>ONb*U8s -H71MM^!JFyKaVWF*}99Oru)B8?;Q*Dv!q4L<+gzo$W2E!&w -mA(Wrecc<~kv!Bk(N{9X}^)4233dKTma*_O5Rw1Bs7e+UO&MPB~OJ6(QJPKJDP;(|Le0TA~_pk8lH~j -0QXc#zDmbrk>j~+>W;9ul~e>srf9X@*W$Z}1_I6dA?=MNeT{F~oEG>1wgN;q~_~4*%UxL7{{ynG?q%QxgEfW23bH|DnY82{4C5EH -YhC+gM?72;nsFqsf#L;7kH7^=zc-k$Vc%?kK2js8|I-y$e8m0WYy|sB0@maz6$51c65kNGT0PV8$*l= -h1gzjv|aBKs;q38j&*0ENgkCe@8SzDRBTETwWcjS{0grC26Z8@*f-spr2^Mw5HTnHR9fJT{)PnT0I8E -gzlI&BF9AlO(I{g|E}-PMMV(K%<+Iu+GiNPOO+M|ir5Ojqw$Pr5cmPhmcmCc_D>@OEH<7hFnwvfMsqs -DddAfeF;JhOM3Lxd?#~CuhwsaJkn*#v`CKt1=+behDk=JhXhwgGW;ZlAL$F@NAmgy_=)0`ia;+Ul9`~ -g&;~Ak2@HHU6e0qL7NZKDoz_niC?m^Q>AC*;X-Y8hj}# -;kx+3W<-;{GJf{B5f7<3HD*#3V2i${iTo~Qq6Ln373cN(*bFj-h{^LGc#||s2PM{00Igv+xHGaGIgL9 -x4V=DmW1C}MX{B^Va{+YY)Fm{4bDP#qF{hlmIujrq-?Ci?C<*avg5v^7y%*n(GU$?8G5YO+&9Bm993D -c*mL#@^yCD87#POrH}J`Wq)&%OLI&CPpfMB1r!+F5NjwFh3uZukGzF&0KWz+!pbNf=jDdb>OI{|#t}= -C&Rrdsa3izRW3;<0!G2J1C<8usRhGRb%dcfwuv4%X-TNp>pXkoi|&=u(2pGGn)_$&GR{(Y(u)CEos2F -LGCzdAl9-<~L3g+JhHfSyADlU&<_wAeE!_2l>*%N=SNZu}Y{ei;vqu`K63zzt9R2%X2Af>!e|52NNO1 -?*w|j86S~GN2>y&NKgxCgz8BM&xWGYs<0hj*pL@Xk-Upf2ENfNRR(^rmgZsC+@h)m*?6lUzb)nKRwkc -)%od}zDmZVVK(#!L~Jx1T=M?DIKryu=V$T1=lJgv{zW4J;v*{Xnx9KjpUZFl)|S@aWX-=zT7Q?{{G%< -cf5@6&NLpXWZ@z3x>q}YlD@p4s`OVjDX?-ngK9{tf%Wqz^rS(GAd?{(Yl;51TrFAN6o=I9~@|*Lvw9X -T4I+nx)qFv=74WFfWvI3nRb0d^7x7_#RBe?)~7FX~2ftv4D)jOz?9m0&qU2Z`25!{DcwDy~F4cnMZ(#Aq4#3DMY+ae&^FY2n91U -GYqdqR)G%=3yT~>$}5bV$VVp_@IT##OU`QL5Jl=o;gHdpWBIP{89B{TW7P;E?GOXnbZQ{xs3?oV)GIU -Tii2ki`Qo$=X4{^JVc^_EAwA)GF+F+UF3Wrz)Cwjd?HwDyhEmBxEnL*Lo#RBdZ6?CU8xb0goq)wT?Lth5-QcsteBYC4@gezDYp%f10V+Z%1u-?(4E-^A9uk+D -&?!_RUJgto^6VpsZlz7~uHR36nA3VZGop8|CF%ryI4o313{0dMHE9_M+WQf-0*ogkp14P)g1LN(=v6dDLGgR)O@LP?fS}$WZA2T -q{);Y3Lf1_1_zFwjH7F$%m#@$-sOZ{Ev;fn -=;e6XL52xR+v_S7=F>U`)Vw(O5;;08PXlDo$aF3`aGa -px;9VP|m?s#*Vw_dLW46fB+sL0E)7O2j}aO`gNx>-9#(+HiI*tj>a!&-uwPPGFQ#0kVcK73pi%-PHj& -xT9l2Jq%kRIPh#20wWv)(g{V3KygnEOV=S8RhH_KMR)Yzg4r`cO6Mj(vPP-!A7rrc_2YbUc7Xdk8EnaqV&Mv4<~RA2&Gc$RMz}p( -VOB9J}UZ=xfJP(k=c?u2jj69bV`$c|pOZ`HTv+6em!A#PVB+7|*Zb)`2KqyG0{UXyvUNf2dxgcE)ib? -rF5yA*Is-$W9BJjd*$}q4RAm9pK?lz@{o9zebq3?JCdNEWasb?|65n>)2^dUYpo#*bl{lwe3gDgZ!E= -=Mf-vVgCTkwS>Cxogxsp*}2)Qc3Y4_v&Ok&$~%CN$6ds*op=R_-wQt?48nSt!SRfDjJbS7IZ{lxKQSk -TiKLj5=JTU`qXyqkY2vS(+=3J*g|VLi$;_c{NZrL~X^G`SmX@(0XXU2_qhr5d_F<44;1SO_9_I9mr?Y -tc01S-gHkwX$KOkTJp|ep5MI7;%d|az%fj0DX-| -Fz-Y9F6_W1G{A4sRzm>q!L|l$yL57`ZEdg2j9Gy*4@+aMuJt@^u2=Qu{peM_>}jLwbJqkBc7o`A1ie8 -`&uisIHLFDR6Q!?HIyEJ?8n}K`3$8OWxQ*tg)N(OQ2R*Dy27XpQRdszvFRIguxy9a^buyr=pBUi&2%e -5_pfX}5;3;oEU3|K1R8`H1KNSYqvf6X&*7NUOYEP&wNxZLNBhTI1F%y) -bIvu^_F&rJt?ky`>bRWZDpb;E1!QpVEOPjZUIq-h%xp1k+IP_cHj7uh`sb@_}W>X?Zy3d_?G)FaAY02 -*$qD%&T0#YzPwlJ2KePF>`@lg8$faj`&v|A10-)+>*PWyYF~4xY>aBU#jaf#BK61Xu(QvWabuSfjKrJMZdWp48O6G!DX*)`RfosXvSUEQ;QY9+DsVbxx^#6GO*!>Uy|)Q43Y39FWc<6K0rT3|KoKEN3+8Q -+|mY}&}1Zh_ND-#!Fx+c=sX<+lA{ak`0v)s+(eS#@O1?Q&0Sb7SVU$`0m|fi!rPqAcX;O+Vj{-t;>${oPNmi+t)#>XY~zPU0^E&iVjiB|n-!6pZ| -(T%$JtcsN1}IMiO@({=C*-Kz2j+!_TlG-<4+fme>>dSzQmcpD2+n}(+ijcSWZ{2`?8Gviq*Q@EZ8zrM -454zhVamRcUz*z2l7!0D%M-sJ8asoEgJo@6TZ+^rZ>j}Q+AAzHDzb;7a-mv-sX3U0T>r9Hu>4**#D^_ -k4mgI*7M+W@_dtRJmp)%xW5N^4Ro2hO~Lu68|_?nYFyqkU4Sj^7=ogBoGlgGmatRphc2i?ttZ)$D$Rr -9`Q_`cMk>68{9j8CP{YHP?UCR_8}l@%zpX03i;HgCo3bl;5nd0#dBt%abTc$`t*1!qoBuzBVkq-47%y -86tzg?wGO*2QOyTr1tE^6FM?U?v%=hR0h1yu&S(Nao>VW%_1Fpw-^f9uNA8W^N+)1yxPb5l&GD1+On%*cHk4AdAgdmYFMiUfJK -q1)VuM46&TW6uTG8^tF^u@M(AAuW@6Nfm;l<_yfE-RN&Al#tTRZKEQyUG&-<>yGifgK`$QR0EFgtwr#?#)Di@-gga?m8CmB{dk`ZPNJMKxA;M@!jXnJI*!p#=oP5Gy0uz>K{w;4HZ -*3ePpM1jk`OO?mh@y@#N`Uo7wl_%i1TK>k4O94OO@??^wEG7iQ)_d9b{IGKUufavpS-QV?I-2=m>Jr-7b`M&%`v!ow$#}MXSxO2dJdAB|VM^mf@_zaj9(+D2pBgWxc -sU1ilH4%-e_L`e^!ErRS2zKTyx)0;^D40n#|o3WmASP89;VDx*6(Jz=WKNbSdYl7t?oDR2$^|GZ9K1a -1@pkSC}ZME>sMYvZ2)oH!vUz>Fr0j1Ntlhz^=in$X0vwFB8fjheLW)L3i#rgD-F!25CE)ZJ`LiJby^R -!-&@a$L+iwP7+0&Ex1NL5j3?u?cK9;0mPYif)QDEfO$|hjg7bX)5oP41_v@((ENhD|dpO!29C^NgBDQ -HPDubTFXw)r=AB!!DGO1TE7!|I_UwQ0km1W;xqGoyc+NBL*+f!`?u%HWt1Zz1+b#UA8YMa5kM(H&Uy; -d{zyO~QAXth{ctM*ycQ5rU`^4nCBI-9??w2ePnLHn5xZY*N)ou>~;`PVV><#fMr{cm3BACn)&oh{>%&= -3n(iWpg!i!^Sx{FGWQ^gMKYuk@5HY!Q -C2ZlQ(CVppzI98Jn%P^NulF)1M>QKVRSoui&=!ipxx>S!cu81MD;GOV={i_4$Hren4Ij>CO+2z+8@L! -5bVdXSrmfzf~|**d?1wk5%dlNcQMNaLr2!y}+);pdm)bBLcW;Bz(T4GtDq@V}zc96!GnpDBJmJIKGVv -y=q2Qp9anFa|juJvyh%$Zn2PpW;>;XDlnOpd_ch#lLOnSCa#njD{D)6Js9TxQ_M8hDtyB9>s%~7A&N* -^qYo+EI&ZZfm*0CO%&M{gKMe7gQ6W?@}sUEY1-p^(*ndflr#>fy5tD-hmL$us56(Mm9e(vu~Q>{Z8uJ -g$VVr|;sAVR=I!D}L{FW}q*(R -G(HuQsGtm%g8w!s6tH05mFyKJfG=Kfq2lO~$W$7R(%xZqFLa5_M#1FJh$WIvN-HCeJPZmV#-7^jD^6d -JyOUl{62=j?4gOSpx>H_jwBTtv5)GVpaC0ix@k<^}W*#Jq>SefRdgtbKz4#piXe!OAyj+z--!Z~tAWU -Dxk;39Q9>e97HGRjRJ4U+>Pkc4w7**m>mVyX|oTwzDR(J{Pe1SvoBOm`($SeYc@*jA+#xxw~0+x-q>; -yyO&bh(vR$b3P@Y`NH%n-|kxFNWpG~%LmtbxQ*s-y9kfU7`KU#j|*>?ZfAjYSviBruMx!)tJzN^*BW& -NCqHV4n;-VH1NiU(hGN{C@b2$$Kn5DvwK1z}T$|r#7cU>~`kS#albW4al@*^{b-dCpHf>zD*3UGL0>i -j6B+*K;wxk)vQX7Fv)u!SF;C!9+Df`@HOryxV@UE&>k?UVn-4&JIXAdgwXd{i^g|&NV+@{-2#MQvtmW -ry%<8JB@nUZ0Z{#JEvQ4JqB-ZyaR4R;WC-PazJ%2?d|4gDQQB@x%FnA8mp?WJNiN4b1LsQm4meTt$ww -eC`_ZmmmAV%|-g$~Nb`nd3a>xXSiA|K24&6}t}cDen3;BBb?;fg5#=8~pr2e0K5k_eR~WUwj6JgN0v- -iWYu;Y3O$Q;#05tx?wlk;hSYZ`KCpDiW?W>=1Mm&;#2L-U-M14|Dua~KhPWmBp=KwAG5@{x4$b0~J3HWM<%!e^r53_t(y5QY4P@2hSlC;JoRoowZs&2k-9+?!8Y -HL}|*YTSRzzApT(4aK5P!3Ami311f0)y4Ec8%GxEAxo`OJ%H6K=mnBZT+oG6!a%%Wkr{~U&M2B4d=i4 -AvQgGQF{i_8v3*2XD2Fn6CXOd52n@wQyXsW#*68lbE_s$k!7ngzqR18nPUQ5y5oIJZz@>2voAdLSolD -(uu4_yv$0Jb|3=PC?4hKGk{(Jn!mTwxNv#QQHiHu0Q|e{9^W3xF#AiOpez6W_HC?a%9vBDPHv9G=Z*r -JMo9Sj=8Y@kI9|D7#%*T8D>!+1VO)FbL=7U+VF9qaX!@n)T4fLmnn;vf3!%g*)@zz$`3O3NMwzXnQAA -M|RwXIq}^{Z{#`a8SY_K@A}4y1+F;!OIy&<=2`4^&EDZLC9&jV`w}^~d{|BUiw*&yn|7xLa6QrK*jW+ -iE@kCYRfKDCwc3P^*WMU5Aod6Mir$+Fk`eoXCEq1&n<0{X0%br^@{7D0240tBd7wMjO(n3@GRieBxx3 -qwGuMbD3UpA`Xv7GTFwkWZBvN@*bNjBX`JulSmGH;<7q#NM1GK9l_!Qr>Dl!ls;2{mdgcfwCP$X6 -reh=U9^QYowh^wm-dz3}v69{;%r^C;$w1xF=gXw!JiA#oJmz7ihV^JE$=r;(}gleKH&lfI4ZclB8nJ{ -}k2`DIr;{iC_nSp~}4bgfNGLOkAHC=M=gj3>0u1lsgpd>K)T|VK|^yF(?;@*j*9+rLSM_w3@dt~7s$< -;*}gHOvoBgff+;){aU*hFB0g$vbo5lKvJ>YqW^t&q-@J}(q=+YS-iW4$9s8>sg0g -PQPiF53>F2*d8>^BF|oSo92Vg_gG$~>E49j?Q?k?*7L9ahL}R0X6Kt>pLVDjZ%4NlSj){)z2ScBL;Uc&(PovE|%HXDmvm& -8mMLn78b{EgG$`*nn9!zx?OyWqd;}^AGZuQjp)F;Y1Zu)%yD62F}mn41}r78SwKbQ#>={1*B)4BXvJpaSvey`GJU(Eyr>+m7@z)4kleAj1|uYFYA+ -BLE6~b2$r^;qFodx^XXYbD8V1=$X_#X<6G~btZw+;4SO#cI(c;=XeA#?kz{Qf1f_I`xp2Z(SxUOx -;3zn2xQO`L}koGupViQT?fM7LhGajiA;2<0G7NA&s>e@T0bYcKTt;RONd*EL%Pcv#FNbzmtJcQc)zTriqwz` -L?m6^{CKLgOS~hdh9hoMsP)wI~wq&nz7JJV|$I{DTiq!C8B)2#Ti*)ftM4V^v8*F=$p?gmS7_m0EP&? -qZ@lH^6cWS$fv51vArSgTx0=X*z?!x}(gaVK(9-_Uy%Y6Qm_dCNThIVLE`LoH-wDriy&qOG962j@)b{ -1O!UVG3x_J`SoWW4N0FFV9-lJKjE}zp|~=jS^5bTMckL&FkZu*7@D_WghP#ePjk_>G?_*bNG`QX;@@abbP4ImehTNkcIvUzLDluC)>eIBc7 -G-^_&KWlXL-E(IikMGlJ!stB{z(6UhRlsTB1!k$~=Klzg~&e+GA!-!CHOKqg|Cbh4@F2E{(^T_Tc7IM -KVkazy03`f8c8H2g3RxD4vs>;CTw$VsJxrrj@HlMWw}Jv`Wd -f2M6Mc23^AAN>bVO9KQH0000805+CpNywyq{s~9`0J}v203iSX0B~t=FJE?LZe(wAFKBdaY&C3YVlQK -FZgX^DZgg`laCyyrX?q(vlIC~+3XW%IZSPnv9wH?cr+Zgr$+o)XV{EyuuG-pUW|GWOGLy5IqD>uRIq#*3riK=ZUoqom-P&ue7QAa&(+wN!zh -W?ASL@%=&F&VlNC6qGsF^r;5*o__PJMiV*DMse=zy8ZpO>>f;C&jb=@!hjdj}7s`BF!Gol9FT==I -Te@^AaNlQ`xu7P=^b{glz6Wi!?2ZgFIZT`4Fec4hDj#l1P{|Bk^SR#8eyJZIl8irL*dvtGbtIaS*199 -W}S5U-Vw5bpz?5w}|3UX=Gl6Ua+s72h`nDtn?B!oqLP4Oi*LutyOgo5{IVzI$==VTWgY?*BqLPyh0fx -#dnw=M2V^=I8f75&W=FOSgoRkr(~tfn&~eJxAi^A!+3Gv$7!xC+`%IDmItY+_Q?aQi -a^s{KWDh}pq;#`HovYoZH>eh6TS_n`6{AByccWS>b!@N*N{b227R}ZQEzoYiv(>IrIFM2^5=6X133gz -XdS`Ri^>W6vNTU{tCece|6;@MWl9>7ALuES!b>kQ-EXnCi`cBpjC50_;-INFqV#MwJvCaGTGdQblCn? -E~r*43spd)+O4hK|q-epmzAR((|qFvCy&H5wf;NN&=l} -{e|h=ShqEUy)Q+>T7Bb?0?x}^dX1&oP-HvA8PuJ^EZX2l!|J1O4aG-f_X}k638?{&aBMnpqc -QrzL$!Z>a2Jw15lkXI#~KOn9rYS?RGRT@x@_XAj`HolhogU)PbdDP>c -7v4jy#RFtq>FVE6r8RB$3so#KZO^UEUf5Cc&g6WT3TlJ*L6K`>qCtH8LX2{X&Tz%iaHz -nTF=GZL_L(;z{3Xc0kwTqMcq!C-X_Ba|v~KPoOH-k;8raiQDA$p&d06e17EzY#Ag8DH%1 -$@CRiyn&dbUeRNyb-6dg~tYEK8FxE%WUvWOJhjV4MG<1yL)H8K!&(V_Vy(gtNoJFv9CS{g42`+BI5M>3m1=+*RHww^|+ZJ;Ne;RsDF^NRPsVO;y|l_2;{XaG? -)ysx9<3`y&`g)tVP2UffpQyR3`%yu7En9p2Um(`2BJ3w*B$Ozv%=mE4QoX{sZkr4CCL|Fm0-wT^y8-L -_VnO4I6ODG+nsQ*(1A&)D3PMiCpSJl?5YZE1Ua+NO&%JqW!lwvM1WrY#RcwFOMmUPVlHr{cJ6aeMW(r -Te;`xoq2m`{Np`2lUGxFE2@?M32)b4r<=MA~b&oRdas&sFoT6TuEwzYRKPlNlvvr%2e_Ll3NNVDYCZQi75kfv -YZ_UiUXVd`e}@nCqT{fO##zOJ;lP6HiL6 -vn~h6l*c>2Ur1@-vF_a8BHM^LO&$hjIKM?QJSo&X@yPW;4W+O{)|4yr+YS=F3uW0Y#?d;suC@bz)hN1pz+vKW{q)<&$4ZLbcXgF4t -hGf^8(AuV*EVOA|J2=>5OZN5AR9&Q3%Iq1zI-BC&?%#s$ZG8J^%2szYRVz%hD!cl<&py|B(=O}V>?!e -NZ24B{OP)Hy_&SdE%4k17)uLQSS9TbYZA)?Gp;h6P-Nq8K-`mtyrF%yD^)+qF5YhuIb<0k${B$VX%N -1FWF*zs31*Jj9VU-#V6b=o#`&w%%O7v1`rRdY*)4^Z50l}1Omap`B#rodlc$8S|+E5B+os0HI7_LATM -sWHrSU9zp159DsrC@}7e-WVSgnHR-6H15drzie>52B-AD=y`FR-s%`-VjP)PXGw_1us|Ma#KNO&KX&% -ym!_qyxp&x(z@t{nCa1kYXNU|)o3~GbwRjA(6LWke-(!bJo64bm{>1z}EOlc9_g@Lx#|M}>V7HY*&j% -zga@alhy&9~otb9;OH;&${R&6eK`rqk&+cNM-p0`iL04(j7?-YSCXKW{JEGWpHYU0~Sn_rKBDhVV{Jl -dD~vRQQ&78yi2qy{K6+eWfBvzJrwt|IK49r`%6BYR)#=sD-u2(l246tk~1PRkb~ZDF0TA@Ym_`li7iZ3)-A@e5 -_>c2l^O#h{aPp6#Y^>jBVfwWRDJDYEElPe&C^n(Pr*v8Su$<7d@?=W11my!h)!I|#Bc-_zfRm?bX@_h -^oCrz3x#sqpe>pMK2MPGvvEel3|VbDdGRhbT_{t2+03H=PcO?55bGb83@N>@({Rvi-hNfN)P^e!5406 -~Qt~%gsKj_1fDf!eAdhER>78M+JjuPmhA=W)H!h9tV5WC0x(LJv~`OVI1W9X38R3mf0S4TBPaTG1NAG -4~5=2J%D5{%d5d2{BRF`vF%{>jbW7?JdKea>1KyXT7; -A3loc0)?J@_Jg&U-sJh_jB~2ym4$vfm>5WObscvQIvW4egJy+YuUR0Rdk=i*3v*(X@@v1r6)1CF6)vG -#R*+Y^?YAbTLn<(D1qF0{%9*Qcz?;qe3D_NKOD2hExVdCvER!RB*bCaa??A=GzY%>3DPk-N?e`$|Zljdo_Pb$)UR(aqevI&Y?Wx&~>>$}=P -Rl*&cDv7`x?8(7a?9qc@}qU(bESgItlGeR(C;xk)T!=l0sr6Z0IPn~yj&`c_y_dn|Fhk9)j==#z08YG --@iUFk0T%^W$q{Higd`v0N#1H@oa$gc3lD9BnqLpJfsiRftIgJ}AKF3eZy35x&~EwLCY#PZf~)ZmG3(vWYc}X@h97D3w7>6Xh-Fh_C -^@Y(WiW7%aoK7y57Rg&LeNOT1WZDAO`igV$!|RKyMPV~hOQBAe1mR*E7EtNzzN6s^P>`P4s!be8e`W| -f5tjjsOwsQzwonHNN=<3;8rdF3~M3*e;zTGXbs79iTYQr!t-rJY%Hr-|y{Z|mQJM%NRE$x@kBL#L~{s -9}cozIy#7uZiwI)IWgDH~KlA7X7LD1$+^$4feA7yTR3M*AqtL>)$8!@0KQ7N7_MBDiC`5!f{2|0F@Gi -Cl#=v*3WZ<*(%g`w=`k>`%V2@&=?ukKZOKq&}|gdf~kN0Q2jI%`BkH{R8fBQvW8i;OGy2C-~4LudiG(A^e;I&o62EMY-Sp;i=PH1RIY(_G_HZ$)yn!ReejA(+3FuB#g8`jp0PjK*e{Iz*Ef2_c!M5RABA^O!qM9CbWgwHnfF@cN;aaB<)!XG -qlQ~3-l8+X;9PY-*4;REsZ{@P8W;nV72-AzW!+_f*`z!Dkq>O2~N##LlYJLs!%IdUX$pbm(|afqRbY0 -O;)Q{WgeQ}pQ_(2&3#^k>sXnPQg0u>Z6Oyeq@k-k;_C5$sGTsndP9&_3U>FhhJ9*b4eh;y_I?ZPmo(K8w2fDdi~e -efC_G&-^Tn?eY!q0p*(|n+jKu2bAnK`_MM?mCk_Wk`?3uC2JR36sXT(ok*c9vYn-&XPNs -+>MZZQ|cnDeWM089f2HO!a$DnfkDk3e|w!r79cxRKKnGy#3uNpkT7dx9w5wQ&YqU#Z^|%g)-T$&_U#@ -EzHxWN&dVSr^*~Xd)~X&6+P2=81oUCY?@p3vlEpADk)kQvUHv5W{ElE@4qPm}(?`WeYc=jlWmQmp(tTE3Xt-`z0twajh(h;-6s+Z81Gz -*^kN?EFZUsU~j`s~@W?K+{=$#355bo5e7^PhWfl#J^K;hOQ%7wwyS_Nt{&xet$4H4P2v(|S-=gLZgUL --W_j{B>yl8k@f+j~}Uabz0Fb4XMpWyEm-QZQ707O_kkU?aQzsK(a&?&}>^XOV`}|rQ^VDRaMy1xV1Xs -*Y37;WXiN}QbOtwaqB$@77G0+M6IH}e&oU3yC;X<)(Iv7SMXKtbR6FA*&zm3B{<-~ix8Ag -W)*<`$R}+`T&pHB&-%ULg)ALlVN1k)-Rkn}#c^s*IuXcx&-)Agk9afZdIUevS7tSZ!)s=advis}HME1JQ2nA#qa=_?Sv-(eUF1Kt -0k58Ojl%qg`a$CAJz{+gwKn0uH8%XWDpAfnqO$UC!s`! -W9!(CP_w)f@|L(ZoQ~w?IPhR$VXP-a5?|nLd{jQ>#jK`zV^M-1Gh3fOW)3aCS@6Jx^x`u;eQ`fMrYjD -z2)x3Lsan`$d|HHww_wS$IU!Gi^zkkmAFn%UcWy3*piG5$x%ZxFsu;e(V&n19#{PxzEqXIeSdoX>Rk1*>Fmq%k1s#JeRXm6WAEhU%d>Zv -O*@BReOk9OQmlMXCElK2sHV)Yjm9L{lUlHoek<6QCm+srN1N6@)uMfQ^6~w!{;aETr`r^6bUZ-aXLa32r(62>v)w<@#;a{@Fx9 -lbL-i-EtfqrT57p3WA+-W?s^Q?s6l-UU67k3L*Kc;08yX#$j+$~Wo8}sYa~B(!V(n}>ZJXO!4!vtb?&r01|tsJ20W`Yb9XJ_w}=6`zA(wNCQZDzbYQ6}UXVIIR`r`0yBY)w5urS --Sjqa(Y&A&eLFoI@@V5aM~BgBp5@|?IeIrt0ecJSuh0)?JO8sEbN#A(;ahw1lG=hGiT>IGhlQy>6x;x -nzF}R=Fd-c>C^L*vyW%mE-K$sZJp1W#l>=$<6@?r!#pzWty<|CN5vB*62l7lx~10|}jV!1GM)QEG76AN0-6=){jQzoC}ESr>ZWR&Y4z8|Wgdp`X -=Gd=QNLW`lLI%+!kB)Y6yGSqH2m*P=N5=-W*~Is+%B -0F=Xshf`z<}PSHr$Q{aMpq5zL3S?)CVe$mZtjQ7@QSR9JE3jN$9KtF6*Rw1v_Ne%m#j^_I-KmO=O`lew_*2i3^KK -Qy(bx(pukJt%UH{OAi^MO734@lXI-$FQ8cO)ZL{=~Uw0MoF0iJCWEAF(bzkSsn5Mi$-=KyUOnHqgS6fSUr|(IF7YGAlzGD_E_UauA0LLO6Rg#B?w-P7Mzh3I)#6p!88G) -`02=G0gUh%~-ORS(+Qkd&FMHgo3YQpIG=B)g-l>y -7&xaGXjl&>t5WY4J!T)k#FR>Ep3Vb42r*Hy>J~8REti~{$c9xVmP_4D{jtNHQr%xQD#hL!p09N%rP`d -A1=h{Njxm&FB3u*}MZZh&QRkIdWQSd{%P@2)V~tnVrqkHb;@C~qZSZK}bxB=hQ% -UAxedr(|-iNjrKX0rG1~%Ry=&_{vKFlh8RDKc$l4$Jm(@_UUMW=%<+U9a0Y8T$li0qca8Hs;i9gUktS -RrdFB_*3Cv}q*4U>>*$xHsbo)3oDZx3uUGSxxzaEfk`yBPJ5VerRE&JUvm++cvE_D-hV$_sy=1RmS>v -_U^TT9vU@2yL?m6YRpIlnss2jkDE&9tOJ${!vKxZK28E>T`0M`W^m8gyTfkb%w8tsodunB!1XZ -_Rw-b_25&F~d{f+i()R>EDI=SiQ64Ymf<(^z)3@bfeK{*cr|lHtPWOJI(NU`>40co6>h6v1pnGUa``cdG^ZI4x -^~A^hfyu=lJ=OX6pbNg?-f-r~s(!Bqryl4@8+o%4BEoWk<%kyb=;O;Mc+hG?ICJsbPP{&zbz~t=BXpB -LNQ#0)E458H*Vh1N@qHY6>wN0>OK=0cv7z5DoK89vX?fw#UlI_w^EvwHd(I9ifBI>K(aEIzN-K3w;s7 -%^wDHi{w=q$5t&z7D@AunF!v{A`mSc*~LG_UQ$?d*6xR#wU}wI?UjJqrahPG)SHu!?4A#eeBXF}XqJs -3w%~8Px|;uK0Aidm&EC+48Qc9gQ9M@~{z+%f01bA;J}v+_i@xY@8O9e8wv}VdoHslwsyU3pSoYzQSLT4TD+Es&C9-&hTuAdCf?$2SY!DIa_s>5ME=b(U3P-}-WXH?eQ^uk_?>eC+)uJ$^xg2Vn~) -FBYDmi`1ah}9Wkh;!Ti975J9uYty5owMS?IXf?S2RRr*O63*n(*$s{mM$1Jvw?pR({*$RI4O={?=QCf -H9Gm1PFKzF|D|Jt#z2J`^Kbg~7uhev)?Y=O)70}+1gA0X4?6@Fk672Nc*)QkSr}{cd1#^S&9UI^GK;D^M5gxn(1qrX!z?j{T -zTh$i8|_G^D&Slj$!Ic3Oih|aG}Kz34O7P`(30cECk_giF|2X^<6A)OUz4qjg@RH|8f|3OyY1*CycjC -9|l46O -P$VC7%&{<|MQFXeq!(>hcfy+*SN8xi|lyLGom_sY23yoF&g0>T;XK!(=F~z~dTV5K>TzRV&l5#C}0)2 -$r-iEO<*kUVeWboF~C-_a(xtcgW!xr;xN2q3Iy%l?Flqj*t8n)kw#oYA#om{OkVDIEgis-~Uxsu|*=N -*g7Ot5$1($kmdTw=qHr=_}FMgG+Th?0FYI?}P5E-%8sz44`eW=&=r7}A_~;Vt%d=BmUyd!&u}y-UASj -a?azVBz1TNrh6&Ju!Xf5dm#7v4eYu0~0Uw3F=prQpM}sbfD<;xmW2=sAqm)sSX_vqmDA`tI0{aqRh9Eu%-n44r_weVvQ|Q9kBjX&U5T^hR5BmGM!c3&_TA*{iS@DE`yTNs& -d4H7H>ms-h*L>LRN7s+A4itXXpbvaA4(d&mAl`gs$OsRcNWUL#Vc#jz^Bpxg>PV=FhVD0~=-8s6!jyH -P_^oG&EKl+`Mx+LnECy2+C64Ada)&ao!+-$@wx0{Qiuitk_q*r*JBcMc;EcmB0w6VsrJL!l?`;SIpSe -_rW~&5Jba?^?Tc00fQ7&4J_!a0~YbqLmzPElo_c&uJ6~8pYh3`S;XIWd|u;ryx)Y$_8c(3LuL^ewSEs -1jSSKE9VPI9imT6`S6x1q&{+q}8Z(yAnFCDWS`XQ1?@e$tm@sVC0V=aiTHVL-QH9CK1s(+*9L0`Ch+1 -gu2T!N=til}mrF6FTLBEkoX+J|`a(!U^Go$*!Rx_j8A2=V;bn62Fjjrll5~U&D&CSfyZ_ubsOSn;Hj} -B?TukfFZ7Jh&t%;x}F3-6I*0Tg0k`g`_yc@Ni%VD08IjtxWOLnLr3i)h -@1w&=(oO_P(dFvWM+Zv;WoPU?_wm3WdFUxhLP9D?(Gf8qL!**k5=D-6~(1Mye;{VAB7QvlIUl;twZ4{Hm@IDp> -m9n2yqgaLY$a>WgMP{v3HNFB-4fI`lmPYuEi7x1{+KD>HVuqMwnsvWZ6z*u0OS|q*^#ZEiEC!KA$X07 -y`j3yJM<}xG|mnWG^a4{XebL3b!jav?ADbYF82LG*}gdojZyPRG*?Hr6=K(s!F-4uR&n-h2%TVE<}8t -(H#H+%j~UzL^$&ZNDl6;vjvariy6;h0U^hY9p)-D^+G -~#*gMU%HE0Fc$W(p*hKd3oA9g4mFHUUKVj(}BDDO8FKsZ?-n-iQZj1kIZ`$_`b>_ftFw#+f$ol}kRLj -M=%FpjedW3i=%Aam9gG#Lq`BVsZZ1Xj2N9CvIIMkv!yR)}jFz;&kV0w{4HhqK+!90Tzrbk+rP+Q)*ys -ai+iXc*yro{P+#kl>sdIx$lHOktL}0M5El(j6oD&oaz{wg0oNR7TvNIm|MZ&{+p8!Ynw7(qG9kj?8Zo -DnkH^4pPJb#scjO0t6rt$!a437blv>5WPr4!+HDXj?!UJwywDiM%?!pTF9zftDkl(mHFd_Tu1d)Tdi&(S8+r|$z%$yC6xY9m|fdm2gHj`=>!sMY>}$l;q=;7*izWMj>l@&C3lpF;f*8E -sgO5KU-lEUVooyz9{0Scva!0|cRTrk8KiG_+rVe991%K!;X -2$+UyFQ~UKSKSHHTYwv5n~~L>@;G=@+W?z0rw1l;#_9j_xwpL7N+T+c=Zb;bk+e&u}%P|s!dmavV~yg -*H3nTaTM@Vr@vjXLjT;4}O;0Mx2BHEL}7>#C**c{?^Z&HU$^_=P*mF*SEJNc>F9}F|ft -=;n4@)%i~Pw=Z-LpUG!+&aH!_-xDnWT5yYsFaTAn@z)S!B89}SVI4Xc!xX2;%;IiP9(<7IKp@waYtmn -Ld?9q52`Q8i7vnnv16>!#p8ky1?AD*q1=r=h*IF|O<2_kSDF7kT>I`Jlk!faMfIKO%VBj(9hPZ%DXc% -nzbUh5a@@HULsBwqkPH>L5!g@Stgj>?!21ojD>$LR)_gs}`L6fUwO9z&t*AwtRuYU-F`qaqpkS8D4L5Ys#MiZ0+grV6OTjF6O;%z+IEP06jdmC~qFjAWc8i9>q^$L>)KLKQurFTv -?G=g7*LFIV!2Z4W&-uxkG#~j1dG}8TV0Nk&g0?}}01Y<{Mwn*&o_{^5T#+5}@zXw;AfVcJ=FR0^|;2z ->QbCJ`nH#?jr^;V7^$Br2b9*$p|%b4G}Vjm8oIOT?&oX3`;P@2 -SCsK%qn1#J~#{#2H+dsp&eFn|bxB5_&$C2-kB<^HAS{Hnw+)JrLsD#Y!UZOi=1qiyySx6E_^=qlFF*wNyP4QqwYw_U- -mu!VI@H-=v(@KO+=lSU`@8yeCBrm2plP|-0FC1ws%kZT@a4Tgq(I%N1E-~)n>2u}5+zNvqVY`A%|KmL -=Rv{(WZ__~a4FcAhe9$Gp=n!voXY=QxL?h0WK1ufFG6LDKaqzhm;y*s@y9l^sH8vYURLx#tIPZ*v7PJ -G_I0l3d#lYujya5Zl}13qPV064SrrUabX|4iKq7*5^t7*5^t8BX0|e!d5Iz+iND!qt2ab|x&H@4?Q5# -q<3N@HxZ5CQbu1oY-6td=AfM>NEcV$!E-Pk`Ff!pkda~y8sRMxx?VU&*Uo_c5Ze>!_L}?m$=45Q9Xuz -#Okn?Z@sZ7HsYbTkTAmD4?X6$Br)+glq4qRwru%gAkZ!nC=`@;^2k$GhXQ0NQ6kS7BM$^7Q^S-JZy%1>rp&H;N8pcNJ3{_uz2?p(okKdR(%1_@e&V~I -bXbo3F(0uxGc;ILoMHZ2u%G|Gc}b2Y~})nS>SkO)3>~E#8dngZAxf#_16T<)IcI^1=rsebmFkXpb^AI -evE8C1vOJ+3O}C5gYqUcm}bV9EBE8DI*(!}axnKC6vBlZ%;%D%T@5KKvq5|^?j=k_gKD?+)jDHW$eWn -q)Z!=*CliR!C~WBN=pqr@@jju}h<|w4Eh$9KXU_1f{kWVP{hq)}so-T)xKQRr(r*Qbm_3w;YbU0C8RW -SD)h+%Hg2Aw1!=}LtY%;OxYJ{DAI*`aadIAOOuiU7Q8O+8FcMq*#3C>CUjt5Vj)~}_4?LHyy8=adnY~ -}*ylz0cV$T;5s!3m$AR6Qd4&I$|}(pu=@0Rat0j29=t3XW9)DN7nmn>A;EJ@xPaP-g_I@|ft%IvFF4A8cXkw1t)IM161Q^woG^mdPp*tba?EYxaMdHFEO}k^taY`N -ns6i$NDY8*=LH7@W|6;FeVIhB860F$%2Lg8M+kBIb4s1g9FIep*-hgFb^1mO$!fV8@6Sz)+ -SDz1AC8t``b$c+-p`4v*;B-N5edfH2f8JX9H|$&zDD@$kA^5H{g?L^~B|)`js4$cR=2w%-L-7EunZnj --Ttu)Qf1S$QyK^hS+_yYlga$iwyqHwJ>O?-@53xkyhKn>*p=wm#G+T8hUCX@H|V!YaEUW*aHp;b9L2i -`qj88@h!!P2?urXMCF6xe&jTY9Uq;#D1C?PDcoxD@2wWqdUUxAV}l5nSTETFni_3q;$S(t)T4O^aY%4 -I>JQ?D#H~tjnhJeD-bi#8ZTn7Lys!d_eHv=78vqQo)(!1g)?}#MKswIQfcfaO%q{y{gH!0+FsJ_%rY% -xj50ZJ3qpv2GmBNwaa^W^BIUr?nNWbfb^y6p@Ag8X!b4dQJTrlbF&;G1;054Ny^?1-p2-Qy72AE(b1T -B=Q$$OGFWfH__!2F7tv^~ARCr2RuVf*`CljEQA*YB;C3MyW=lp5_6753k72-J%*n12SKMpAu#1p_-7b --r3fUymE&x#LkidT!#d)!&W{x)0)nG6B9Ogk{hwZL93;wwIcy6K-9vG7hX-B6+3S4FU_KF-m>UC!5ectFOo2GP-a1vTIf9C36%|} -x{#X{7ODAh&%&&it^5}&98u2HBg%k^cxw4PL1RUSx@Q|+;Uf);(M$4I)wGccT#3>10eEkPJMRVDH(I?QX14AOloME#rFhdpq@`=Jbzoz{sKq8Vf&4#;8=ocPes-ib(Lp7tM2PGDBFu7O=I2DKm0!`Qk8CA(9%%CrRZ{dsI4J`J^Uk(5kRiL+Vkr>S%V -@I$^(ss@7)2+Kj$AmzxY34cKBv%L%LmlE_3=Kx^9DL5jMn7gi@jv*qi_G%}!&;r@7IfwW8{0d;6si!1 -4Y@C&GbebATE+q~0b(i;$3V;(F(if#1%ld`Fk;kl8cOKQ38q?(1cDe6eLJKQ$BYP<(j?-{IKwY$=bCu&l_9AhJ=G)~oda5Qu2uLgLbT%ih@Ar8s86Rpc -cH4A820vfj)+iYFmzvZRk4;o8%ZOc>K%U%O=0EOuClLZEUt6frRmLE5EQRy@3fRU=Yhh8Bhf>yS{~H5 -u*GNa77wQkS;11O{<#WEm`%uGjvTYI_b>5|4B&#V#DG@CY#WEVuJTYsoCNAtqR>z(A~xt|x-Jmd8sbIvRh -nLPpHjv`pz)4ZG>|~K>R1gbD{u3m105Ft!z8}$$^&J6ro$-w>RzO{;8OPLezQUvFC8C594iVWbk+eIL -qi{bh$!w83gD~@CBMLEX~a<q@MvVVO#-^WB`QqE$kr#L84*W!dR-Vr`E?41HA+(UDv41Zg*iceemTe^M7X5z8{NgL;&NL{#a^ -~D`#lfANrmF%01wBrQOe#H7?z(%SpaU^6g1DDNP!)Vwl!R>4bHCeW$mq!LJjv+My -x%)RZWQ?U&ai+h`KcHy|djlH?@ -BtzL)^4yZEm@f!pfV(|juF@x!b;eg=?35o)=aT^1AU8c=CJr}@P7s_sJpTQ<^iN0Y|!54Vq4B-fkVMM -?PSZVU;G$P&rA$ZzQ_L3+;dx|Oc6tQ@tap2Jiz)|29q+U@AcsADa0tVBQ-GSVZ#B-sHKHzkd?FcWP#B -=j7%YO)1P5d{4U18lN%~kL`hMwRRuNqSVgC1C)NWzSaI;6(=@jOVL!d=km&aQ*zL5pL3Vf9k@=ER=wWYH&mi2@mG^bsD7H?2_Gz!4KT -Ha^osHvv^K=}NQ`p~fVj0|htz!ThH@06RwCKl37H*uDniWV<(w>)ZrQXi&d20E@Zi9=*R3<+Vq9}lfh -Z}|enfO>yBx8I42(=SF*k*Q|gVU`#?!hvJ^|) -cGMbh{?A_wrW0!>7&CWgsw#)hXv3QGpm1hnC!)a$i3|-GHPcRow)3Jb)(}Z!dK7T3pMX$ZnK(3|B4mD%U8%wJe8nNdk#vYu%ec$}>0Ww-l9`8SyL --5CY`F^JaKl2bIoWWC&8Ln#R?s`VVS`wt0CMWckid8V9cl0y9OBUnfuWt*grHncV2vgsPgFzVM3fbD{Zy|f|KGwVwF?M$?v-i<-=;dV=2G*-cW|FUMF9*Z1f)Ef=sy=K|4+x^L6>!#t -(rZg2f_9#?Q81o+2nZ>JntV;gM-n>gf;mqb0g4Ka=ZbW5*IvLii9$?9=6?Il#E$PA5_8WaIw -w;W`Z{y>duu)LAB>5F1xT9rKND=SBOBQPn$Cgy)6fc&$bwlZfw^+Ew!hGA*7im?!ItYKh~ueBAs=tT#OgA>WV$linQZ3`1PXPOL9h9s -(gAK(koq=dq>)<@d3y4q5MBb+ATn6c=2jd<2Uy1>P>0OhN(2!A2K@O*+milX<>rEPkJ1vo3Jg*8vmzv -b=>Bm+nRc7T*RXdiib82@v8Y=#hZStDrUqe7g_B$>trJFU>pnrC<*P6N*SPSPJr0x6y{;KsoruIYt0Br2Lo#r}X<-t4y{oEDdRT`d-+>f -8b`acn+iIrgFcC_rF}O8>Fp0wOobUylTd^?IN+>bZ3iO?Ck --)3eU@wj-fmPy={^>1(U&JF~31?%d`C!Rtlg8qu2#hOTn@4)8g8C(C2P5c&$zFNIy>U@nNN;s?%VokU -K(R!y1#f9oa6HdC8rju}+_kAauUy9wRJWKf5>67qP}2JXh{P+X5FjHUK>ItWNyq1hJCN(o_-vV*6>gX -Ah$VQyVDh651^n8H*v7yXea^4>sR;UCSj~@{^kr5sv@(gxIwc)I1)snq)N@&E|9{2mMiKeju ->?c`Kny2mjG;!KkV8y4cafp#xpSC8^)3QIsTRW-s+$IqAW&sflaf -pZrZ@LNT(Iq6Lrt;ZN3;?+s&_Ma#5yXaLHV=oB3X1Uj04IZo1;JSv*1#H#@MoZ3$h -6FRp;usUy`o01Ln9=7I&~v_BL>-~;f-jCGbk+sqGTIPN5v1$@j3spD0Q12aqD?izqmy9t)#TJJ1Z>s; --q0i+A+jcX(lJ53W*8<6o4J6}pXNGf`SD=7?L8X7d2?pKY>>@tQ&Yij49UL`ENU{dUl44Kw5~qxb@N= -JXP_0h2=Op>9~ti(+q8mle!p=-P$c={5roakJlj|48^p4rM*$}|)80_!;65t+)t0N!8*Ufu#9o4zDe1 -CHji75VA!Bf*m=s+(xgVGZ&aOb~ -s^7af@U!I5L}cw;H{|i8un-?3jEiRVG1WB1Zx^>p&^>%L5RbqB)ULm>&y?3|t@?OvWt;>N{HueJkOJT -L}H;B|LXKGStBcJBtbHT!rOr7<`QiCDN0Q86|>&&_MZS^H9L)Y=#&}&YX>NLKgG8l_h%ClBShfQN5VV -^e|);`!%D%kWqP=Er6ooPwCV&U~rK-5!(b+W@O`$4YTRQVjvWHx&=P|rc8X^2^z$;ZCy>{*Y1m8*PXd -!ByueW7VH0Yhg~zpuS>58GyQE#dhRq}teDbNuFYKe?*hb+fh>S&qQH+CGLxW8-k+EQ&fp=yGSA%zD3k -Xm3=^5S6k%*De@JvVs>6W;LW)m?kAVnqX^}o;opo+fjDX^7!6}33Uc`{$^vc(m;e20$Dl#`C;N50)Gl -HSKp&c?96MA|yW&~ZCkZ`&gfgBq<2+MH3N)a-GuTpr7;HwlqBj_r{q2Nkfr8r~^U!@RiaZng=sbs`e5 -y6tcFRkE-IN?Sad6xo>$h#C^3U?{~Q^0ECUkS!{DSU>DyA+|s;4TF&1!VFd9GS(L^|kvWLhxM*D$s;# -!8_l(MqX84i@dz1lCVvq$7xtiHvkO+L@J+j;@;gFtxr>H7VWf1K4qzGVCJd4U~}}a9u(12QCsyw4; -7#0@o#!!wyCF28l0doEK51g2Zvbgp7%%aNRJWX7E5A-HOQc{nXA=TM(1X??=aCkz0&bVoD)~f>??A1B -sA{`amY)DS4=_%#9BS&bjeKFLL8y24r6Ogy5DJG$NG9E2lQBymG7p%s==f=lt9>6F17f@e6rb9&5hPB)k$FGU#|nR1LmW;-;1 -tB_kxnX!KPTnNMTfDU1iVx9Z6r8xwYcQnTY=4BDvS9y{P3oQ?F2`U!R1rU7mdrZ~v#%n@gW|KN9H(pa -9Y^@yuCH6kV60O$gFURq*>Cs@sIy^xRK_Hbpm#J!yvz4kgW(2vcqJXfrgz?+VlOu-By1?c`LNh@0+b` -e(VhpWUnFkX_B~JJqveV%w=GH=u_*}m2O*fsdZw7sWY_EJJKglvB1c4!#POiX6pmu~d^Hux#iGV<}4l -G*I^KeL0sNf4Ah5~WpMDRHtK}Ck8M+iz;7%}+T4W~DHLk81lnEC{#XS4za(;dJC!6{7>nWG9W7pOzR$ -j7Z9hb?#6^R*hPU0S*J@)axv_F?(jev}qmY|)K%gl*@D;dBe#BPfN`dka(fHE5|08bMfM%4)M2o>X8#|QZ}e_#t2!zwtPaEz>e^M^x+@HHwp6+5(xiENmKfJ5_ -eE44z1Y>_5tBlnUmBXj|Xc4i#D=HE2HAwreODA|s+#~P!VKA0k?fTaFpuh -EsQ#(Z3Z63d9wseo|dqYf7``>>mNP3OMUP` -6;_09*?YIL5@Rryts9(p!5m!5%$~)A+8$_2x09G -#*CtD6AwYzBF%{>jFxq{4z@Bcz^MYZ^=%Ha@mCQ_Yf&8grY{P)5~S%(fGzpaG{wPacgl^W=@_t$<6CqX=Z)(_b0pptZ++qu -au!*`#{}ohoiT&WW2z(MpOn_VoJ%x)qhmry%KDOBAQ3#8j1r>>mifSNfRJ!0&TL2}I~ES}TYk+B+9zM -T#W8#-j)leyrsE&8*p$M6kUWsc>oaWD0ovcaGe-4BSUtnZ&3EK`AO}r@kqwOt5jLBv;zNA+K{q;cS%mI~jB-RhWI&RMIQ-$D>TcAw6Y --9%LnVFpfcxUSKsFybDC7k(?D20n1setBWh=nxToq2Lf|3(FL=u)lu?on726nKpgO(8s2RhuYiC?1S7thSq -DK1ytibGpu%iV?nZ+mdcD?f6LWa<$}g-|=G?M{rp0*v+Dc67T5Cie_rv#iEj9z(pl!l%7VScNX -u&;}}ML@A&qc2i3akjMbD-yyL;g>h4mpvB7nbyG(QeHOX(knuX)8Gn5dvIFRyiR&iHKt`30^!-nA$Us -FdXtk1n0iQMy+B#_XV156tPqgnTIS{?yT)Mjk%&DZ6}1e$eVw*DEJ-1EmrkQRC08C#>7_Z`U_J-iot9 -s(ODX~D3W4g4ZgV{mEVi;UD^v%}vEM)u_we#{m37rumHfy1f+7P&7XfM#U##g-Zx;EgkcX??1AfDdwf ->B!Rz;g^m)A&*=0rLaB{Zok|n1lGO#&qt4xGpF{C;W|u;m&$Z_zW!=%?@zt|sLJ&3`W5}FSFcW+Y7{7 -_JJ9a~cHD33J$VV(prJF504Uh7p&Oh4HfkhpjwTiCu%$Z#Y}~+16j{M0EvygNQ46DXkLxyuFRARbp&L -=zlh)%W*mo`66tI^K-I#bjZTm}dIcv3vV6R%bR|YoiH?S$%oeo+V90O+L2VSP576#c*jr`E=)W{D!Ph -0t!%qRtGc$Pcog8u?+&r$&C%ZY#ej@oeM=J(#{~VaK%&7wJ`)T;6X&DDLCa) -Gri7se8Xsf2g+t4jSO6jZ%Jr8Mc^0Im(xwK3EtvptB^X0f#L>UwWuP==0Od7}s<l(^D3|Z>EFhM|CaxW0xg@r{`&0Eqeq)8Tts(O%R%I;MxWk1?_E9 -XEz+!a^}Kh}i;`aAt;1Z)CVvqX;W~f%OciYl{_XLqC^p}I^Udw;?Tg#di!@t)Gnh_~zj>?Nr|Lg%FCP -D@s(-A%$nsb<>BnB4_fEaSyGZl=2elPc`@pB4^Dt}ve)etEnT;Yd$UH!Bg`WQ~w`OO9 -KQH0000805+CpNn_Y|+{{7%0J6ye03!eZ0B~t=FJE?LZe(wAFKBdaY&C3YVlQZPZEQ7gVRCb2axQRr? -S1`s+cvW3@A)fO`Rt2)*HV(E?e6B*eK$#yuFq{gPVD`-$JfV?5+Rvuid0F;irUlv{ml#j5&!`blw`;0 -RwpNxNMHaA27~#+V3uc#;PP^|DpxtXybR*SGRw;#oD^BID%mCfeH29TOX1mY -bEJG@GpA1fU9ZjMYcuNmwvh_Dz^yHz8cE%2=XuJcS{bVOmz>9@5B)WjJLoufjZ>0_FjQVKI&4zb%(6| -MJId&GI8rj7{S(d6lpQ0~kla6$_&{o!1YhQCNoc&s_gaKxn9EI4ws(8P3n@pY%En7mT_ieqWV~6xL$|(yg1 -|(IT#GyBeWn4fUaOBf-Dbaal%GPe9eMv@-K#@nU)!pAn=0$pM*(ptkKqZ&dM|R{eu4fkVQk}g5ni^5ZL8q%GQ^cLxgd}D`4M`0^}%#c!F$9sYgr@C@ho||CVZ0KY`Nf -dShVXi(+V0$4@*9fZ-D(nzCV4`-n5^@oO=bGriO5r*82$kVCHl0lv%%RjVZ~COcwTN?2js -So-6Fb?D6?f2#BxDdKKd -zlT=5U;NsrUGgrO8HnelaW{5DHrD9Aphd0E6_r~(rLB=2A(6fBp4d{NZ>%MMnT!QNOIW3k>izDjHU^(K~>7D5T%182`2TN@=0OQIKVZIJ -W*o+!4!Hqfxb>*<0)7KK=N!gzpCovHuQB8PsUwrgQ!+ROZDR*_|QZM{KFB<;=CvYLR4&2IE4ut3&b7^ -4A+ojF?2vBPN%+T=uJDn6HN3_Z%(N=Y~gEFdFp)uHO?;{=x>%+xKiK^ib})tAzwbzq!5HyHFtPO45oS -y^JHZJ@&n^|Pq2qXmWPt*TpTJy&vzTq60MvgfD3C3$0A9-vB?zEJccPs=dy&diRh!NV7&q16$V-T){9 -jwK#}UZF~PD;oMZXjm=q}~LXlc*9Ak55^4Cy~l5><}KJoU3VLufT-94y8C06Er)e=At -#%Hj-|3}qYY{`U%W8BR`^RrAq`mchL+E18Rq3#h{&TUb5p1GBFX0T6c=cgCYxY|3;@*&&g+5&Oslep2 -S2Ui{JIzfkAguGFM#5rKzWpA@|QXNi)B~}5>ZR3tGG!|7Hn4UkxDemg0XwRmr@$%j&cS -oF}Bs)_Bb2`%`?WL12ls&3r$NhU*dpe!o_akAsKH1dxMO9&X!Y1;N&6!J1$X0uCumU?D~uWXw2p{?u0 -5s~dJ?<7WxGVv|sa?Mg752cHin3^*+NE9_^33r7{eAFz!Q%ZSOctzBA(CsYzvJ<_Vm8yaY7kS?uJ9Q< -+v8+rLGii>cPuw&Tv*bJyS8r_6>jE_dRzk3c31su>(2j&(8as%HgpaSlZ#%qq14==w56t@(c<+u;B^4 -)*Kdz6Rx`~OIDFMQTKL}G80977+1ab$#D{8D9wGs(9ShF@ -loDaB(VQ^s|E{INI2m|DB5?ZL37%Yvd4cA4_YgiYs*M(`3z*-+V1t8N%bLf(mh6ChXk5GmM!DbLHmq| -P&ygd4d5C4eRIc!_Ja9cc)}9<*9))cNHm6`SqGIm>;z(QO*4J5z}v#wx8%VkBFkFkG3d&Ik2dg_ -EA@rZihUqlj8rNHEq~;+<0bRT?=8^EXP-m)QcxY#j!Fr4;@?n4W*A1mw%8c;jq4b=kB4XXi+G;Re!=L10&Hfs+rd~A}6-L}Jb^Qz=fX#(Y -+!NpPCD(bOAdJLjM0le*(Uk?whP*n=CV#hAWi2)1~mJaRHc!&dg4EwEd4+ooF6w!@2=i;$K@ZyoCTI#E4G -s2rWM!&{?cjzkwA=Rf_5n8*j;Cm$53C=ObrfTZ#m#)sK4L)`R=LTaCaPJ`dV6HE+dXHK__tTQn)Et4*4Q95=&OB$x96u8x;UR*){?T<^%a9krfMuhVRuuxQSrhVFw -ObDJKdzzT1&I2s8_e1sOX5vVaSn(0SHmc-Et_UKl7E`EODT<@9IHO>fsJyfmf|3F^?s=x_|?!gQ+|Kb -W1uDEEv8rN7p;TcE3KUDDVaPZFp6&;{! -ZAR0bz^CkvaLqj%Wl^7*NyMMTPdkpHcjuP%Oll$G!ZpU4Mz(4^QhsxmUVJHf@w1sf@wP9f-*LZCq7`{5MfL#QC -sKHq_Jia-Jmh*CP609j8SDiq^__wT#dt4ZS#j_H6 -XzLRp$JVus9R)g0NQ=Nxmz3t!e2aX!W)2b4pz87&xZyEZfsjRqP~g4;D>3pUgo;KVeDs^1c=I+|&?&Q ->Y1;Sq2u+FA9*EW(ROY;5@3bw8dVIUWSMuL{@*k3~a{tin-P)EDk@2`xfv%ers~IRJ_oIg_(_go%Ta-LFi^{Q`@%K_TQC -dZA&pR-VHArmX&K89&*4p&p!#&pHawyez8nSgusx)+maAnbr!v_%igk`mE)oRpwh0L0kORgbZs_3UB~ -Dge7HFIt4#dC)dVkyrlSVn8huj?E;`dO8!+5 -yB5c^P?@R|40f6}wi%mqtS2B*Tr8z}9|7L+raDprlDm4muKIe3t>}?gT2X7@P|ML318@u1RnBDaK6~j -u{KNq9#G!s>aHx%!*yRy&NzI7~eC`GEKn@03*%eEcz%7NDj$6#N`k?tv3#0o!Ez{r6Ff -hhacJ3g_B4Ji%?L%}2Lk^$yzR-VYgu!t-%Rv^+lW>-x4+M)Eb&^d=JNwPHx7c -m~=$YHxF$LPt)8F$=}bEA2FgG0=2Sf0mG#L}^S+(KHAy%X|}J3QymIymWva}Q2qO^Oq2m9meZ+XCuFk -%*MCH=%fLJ6chOh0z{{*~bdp^bOxa3(^DZ2CW)M3sL;9u+1hcKi8ak7Y(5f8>*^+w$ajx&?waT(9Z(k -#@-C3;F@UbzGnJ1vC1e%o&C_DN;J#9WoTFnrlhA4HJ2HHV?6R!p6{VS;dU&93ATt!w8+JU0g`MiJj7` -{M*F*58+dZ%2A;dRdZ;f9d0MZQy$pk1+aW!yD4mT?tyqa)ZHedVs7a^tQ0|~!I#-(gl!uka@o)h@>SR ->dgEsjdaKa4R8Jl6ZO&)rI5}^PIt^0HRY1$|puau| -3q~!~-+B2g4$$dtb7{RR6JOnDeH_>Z7S-TQ`{a7{`lxo5hsf< -kCR7_qB5c&bR_QJ=5hYyN$}(^fBDnnCy$>zdejzxG8jF26h=2;kUn+2oI)mX$2 -UoE3!!z+x|8Vph%+Kwko&gx#KG``7 -7RgU?t0acxT~+tLTG*MEE(S@s|1jtgVQMWI#ScIf9HyGOOBhdDq>=|CplJKK1T%5h1RumO7E7xJp1jMXFyBL?5z8OD$_6$U( -zdZit@snrIYB2?sT59{NK22F#_)4_!Cy>b=@6f_>Ge@1w5Dg?|JOKP;RIPg1PdY1BX}}i;DGY^1%~^@ -TXrAQg1F08%ub)oUJs(BkuSbucj=ub=cU_Q9qCA{=@SJ8vPqC&^af>R&cD)3O3uGL_Hg!omihXx9y*^ -qE^BDDWAU&9&Zyx{YN$;U+KtSL5>Prt9qyn*i>P=9YR>M!yU9(BardLu9$bJUq7WUw;?)p&IeH5%#sn -O+Z>%CY^GzgZqcSPwB;>|t%%nZ}9gW;hRd(wezMEPVFihg2}^r8Fcm})`svb3ny6`8g~m}bl6Gw98FJ -&q>}Oq=ouZI#%wM>kKNJc5UGX#L4Z?cEq&cNt^!3@m1n7W_~U$Iu(^I?kzAxCwjU+)=TKZz~#m5XLTK -u_IB~jB34R$VjeZF?XrX;VMc(d1j_N(M+FVx+$+Hdu7pxcx>;OjdXVJPjAnV#l~oQpGK2aIhtqX2$O9 -T14|Yc4GZQuCNrYf=&FYosCP$+q(@~q_hF#QVKMWZ*j@+P=}zc9Ops6S-5~9L;r>I*X4?^3oll25T3% -F3#qMi4kgj+hT~KI)YNjx9jZLwcf5@_Oh|c_&RfkoF1DQ9ait|6T49|b*V;H5f^bdlpyi)x5o-@*wqN -#yX1NNP{miFNW^Ja;jSlNlC)yF~lIh0NBWg{SXKO^YmRgL}&7w@o&@TFL98R?6^^L~D=iZuu>H{z5(; -_!Sp9)7&Io;@DQ3zK$iYODwF;db--$L+iq!E5gF7TLy#nZR_9JlgXOal7y10wj_taE{@5V^|f>(jmdK -!zTKxdDjcWGaX+S7OrP>QU+~H1}I;@BM#TBu6f@3jj)|sk5u>=(6sRzkJce`H2kG87EgAqqSdm)h@rT -4m@a01r#yBqL$UBL&xjv2a>LkQWjuW&Pep5h5e~zD7c>IV?L`p@ly`-w+8utjl^7AHrU;=9hsg6&O`) -;gK5aL=FkusH#ikX`VERU)TRie>H)&njUry40m%XKoOFU|(mp;RXw&ECS$4wJB^F_IVx`#&Pw;4ZQc8 -YJUZOSv@X0va_b897gl~ZSh;Q-UW(}n|oXIV-IrY7?uTcr^{JGH>$*!VSx)9Zl+h~5^)8UF|+U-F99U -ZQit>?6i7|7P}etgqY$(VP-j5lD3N)WS7AEJ(nb)rW5w-T3{bcG@lwm3j-RfqOInIyv+KC%?|Xmoqh!Ku%Y`6 -QLs$+EHIoHD;W~H8BS>zTMezoB1G0Itk`nWJMJcy5p3Yg7%300WE8Dne>XzJ@Fc8NRjb60m;3E&ni2b -yRZ~QB%eNyUQXs#8}l@Sj=wIkvnkmJN-7-13o?dI)P^ef8*DDi5xUX=h-^p -;R_35`h*l!sn+iPfkX~YRPbFgLRf)7h@|3%ISWr-Rw1%=zFojybl_!$mE1$-sEtwDrciVn{uuER0n-D -@C_^su@iO(*vPaxDI{)PvKQ?`{{+^-QAx7v!@~7r%fhy3IS{NiqDg?QN08`P2G5rs1I4Yc81CTsq&l) -iv{~g98i}K1QS+F*Z`Z}~V`txaTkQrqwXLde9wxZ1E~%AUOlN)@N23RwN~;{aOL;x*LQUUo&3+cu=%e -zM+W+2q&4OCPetugS@m7kxJlAc*jF&RIm8e(C*lnjjlswlGYaSAVIQn0udElmUq1V3>R${tEFqmg$@P -}d${K1jF!xxF9wc89Nrb2Lw$lTWQ(6pRmO6yj0f^68!nXOE<-P&?!F;d7i`)^>Z7`jw>(%&(gHP^m9j -1(Tad~M;X>=h;y75@f^d#1IUCKnE?)!;(PyHXf*^zI?vnt|SC2+U@qI3>+;R8P@<)%Z>?Y^#dR`J9=t -*rs9cBe(=b={+H&#R2Q>%IJX^6X*^Hj`wZy@Hav98LzLg#X}nP*sPS89nbBmmsr!@c${kV*wu~w5UbA -K?nrP_cx){^Rryc%B|zETOOi!@wsUn4WwL|dWtb!~Eicc%p9ghJeN*6B5Roo5S|hvcq2K#khoqU3OBkKb^ZIlVr1|I -eLDlfQd*jOMV4C->FvSkzO&Rz>@pfZDe_k%!URb;$+xZ(lnquaEcds@N8VvIVmJuaiPJ2RIACMnj7oew^{l=XZOJN*(02(5^b -$QY^A976=&ab1aaH*rxNYMD9SM}EMAtIm+_R3AS~jDV<^d{*FUYYl73AwRU!o7tn`X7 -qzIJF&Ov61dJXw_`(UiVY>8WGcEtd&j3_yZEtr(4%=UHv^^f{G%!^^#+GS*&vTrABl{ByCihpO^h5%j -`zRf40tur&#Vm1btbh>GF`q4=6@=8Trq+M&^E?fgB9qju~Az2QYpG|Aa2pK`7WJIHl&gPZ-c4!x9G(&s9-U5iRRz(Aqv -B#Vl5t?C-K$h1oXf;mhslCOVIDXz-9Uqn-996Pvi-B)^(=aQ)a}5x$%Wag#TY2oo>Bd@fG&c^~R`$Lr -->cNSC*%YUoT{m~m*t4=d^wWWs~piaFGuo57JJ3KIVO;+5|GgDccio`2nqYVOAJya!CL*=gYZ)^`AQ7 -3%05ysLis05vN`{DHv*9qBRXXx7x)(GNJBm%rc)x}WnJ>fV5yh0N>;;KT%q9^{b&_S-`7vkJ{_(t -a^kO(P36HFZ92#G3ff=P|8wvEw8aNv-9#fCc!PN2Kor)V;$hpC=fKOz&<6aME*yPcNIOHV2@6_w<_+; -{)V3iZwCRW1Igy{a16E$fEn)oJ*V$Mkh8Nffz8yQ6_s-q_iC?oO+*5a^dB^b3LUV>cjz?jGN;#xS~!i -5Nx=1bA&;yDLAO!QnothAN=Kh|r#3-A0iYljgBKWU|tB1nOS#-&2!kOjS&n!i6L4!+k_1h3se2nbk;}B^`fRU%4dcot -_r*6emE>a4Z23xP^JmP!h=LC6+3s1vFf4+JM^ot5Qig%hZfc+P=(}lhs>&p0jg>VVgKb-oJ0-`o||Gne%4TdbxIvyxN~&5J7)>Z)eG3mavl@$QQhfr}wh_e@CB(twU}yKmAj(Q2 -xN56ZZVpa}mQAmeZ2OqbEvB^RwCcqboOzm))Y*}C*zLm}^|GMC(q-6h!X@1%o5U~!=lJKMUF}B`1eWB -Mh$!kV9~tF;wf6*(qV&qRI?ZWU$k`2^taovF`(<>sZ2?6L*{X*@fg*nN+=$N6oAgvQiYG3puIsSA-Dt -<}!4fdeR?CF<9+p%b%B+NKSsj9=Nk#L|2;T`(eVZ-ucKB*v1X~uXhSuG;6&ZE$o7h!+&#YHLD5v^G<7 -OP`z1wZwlB8-YGCWrM+q>E#mi1}t5DB*-)#oi!5zg52R~9EmH7icFeh>~CC|02DhC7i7ujrS$8b7}b2 -*_I@GmtA0fYiTT*f*VjO`-~I{8%5gTP58lj9UTRuVI&8qN@{Kz7c1KcD4vvU36_ktxVJ{8mfAC3j@?{ -sY9(?lic9jqnCL+pJUn`^#bs{+S<3H3?7TP?XKu -49^KUp*O55N9E*or&axa~N8W7pMORXx{kTzjSX{d7?Je=7cVuq&O3l~h*5&~Py=)X#H2ib1ykAn{&Y1 -3Fq}esO7X=IZXZBKVmHDE+R_2y%Cmn14PK{zdMp444%Oa{rP2Z;lyQf^5J_KXN}J<1DU{3%8Bye#vos+ufhuv^OE(##gaqx=#Ui^7*%{J>Sj|!k+Cz -h1xwMLV_@O@mEUC-b<&{$moR(8Ng@=Wy3SRw8-;vhvYH%+^YO7?AWClO1u~JnOWm1>_}SH -FMa8<%$D+`&wD`*W2N^t`WMl628oCd6l~wC$8n%Cgj1xPnWbfpGGYSi+7AWaCOdG1frOhJ!bwf4pMel -e)pYg+tIpOuEheTr&ej}xrFcT_+TK8wc@!TI(Hh#L#IboQotb&PYx+yDP=Z4gsQh-`*`Pt=N4xlYH{L -pYJZP&W6=sow`5J~>FztFGraf2JmI(4jD^8%G#G!-YNheqE$lE}iqoDuLdlZVBK?6*f9?i}#>8{y>;& -gSw%&D2EJ7Ga4tslggZ?Lg|hn_JQrP(cxM2ZEXtqYGMt8t!vC#%5vEHPB;VL?7-$j@Zdmbgr -ydHom1L98UT;b-zIYqeGVhMWd$31^W*bM!0X}@ycRUX;(6L$O$n)>QxqiDR=ak -2&KPcc}SN^LTz$QluHa!TUS#!jGKpl(D#~4uUJX?xjBPiXm3)rxjl3#gjdKTqJ2x%f%+$+JxISTJ?x6 --pO`%dULBLaE%~N|Ng_Lp&`sF@jC(C4gpnpFWLpqpbA|i_r5q%HUX4j@P5uxJO)X(n#-feqU2S0!-7x -)7say~%XzpAP97RaFu~h@1`_j|AjG?;<|&Ym?lJI98loftvn3F*3Ss;5hro0>@gR?b!`#= -Got7sBvMQ)k@~J+76nXdv6D7FX|h@Oq#VHS$k!M~?lHG@ewy(YgDW=SCl8MtvNBwk8`i=) -<(@`}-FA`02}FzsG^YBmnGebMWKbT3`GlN#%Mhvuwn|JlXi3Y-k(co;K!=A^ -EmsA=!@ejt!`aC*2{r>L>hwoEkJNGdAJ7Aj{i@07{{>RXp$((ZBuAVD8&$hO>=B?wQudq}^J(>9d`-B -S0UnJ0ZV?$~?|(T6r6u$87XLXe_}oPj)l#n5DXkogQ20PwP~zE{VT8Ql=k+*^=_u$7!;P*l`PQ76{m# -skRKn!Yj#_Zm6-0p*eC=OwE5U;a(Z4wXyX^UI#$G7m)6dUi)o_yyxB=|i^4L_XRu(qZNc6M{ -ub}hG}G>!wd8YwaD)nYZh8qHub@Ot-%E0{qDL^Rz5VKUG1xV&1JhPmoii{d6#Z?#yn3BS%F!>hwfHby -<}K!SbFq2}x+F0hq9{rQ_e8DKFG>;)@P)~M+wSb$gyT`B?qp5wuAV7lYREfghN22Zqy*$M#tm}SeC*- -FSSkFC|d)E@HrY*hX>`17*|jfoHto;F@I>i_W=y639@l@;`l!U8|5CbY`kIpHSvBm{j+K8u@)$#~{Q+ -*i@y%#X-ez;WZVEiin9lHG)rYsR>KgZdnRs=#9AzN#k$t}zRg1)aT-DiYQ^qNAYc<|q)#VwD_Fmau>! -jd~py?8q7#kG(~I1A)Vph-WHpgTw$>&Ywk9>}r})UNV#If#(Ub9)eG(E>puNt8H%4+BB -*58YD+z>2k#*4kr=)61El_9)@`=F3Y?6&mBwU?z>=q)L4SViO6x*dbnvgWQ+0Z{o+uv%j5j$Mj~Q#4W`eY(h%##0L^w&67St|r0Mnv7TFf-jL79_C9n -K3f{{Ep6h3aobKOvX5I^4p_|@8_UN{nmaV>FCww+1S6vMKQ&(9{fJrmJe>k!vzB~;6|)%y=0y|={6E{ -&)ia{@>>3qLE{iRfS;6Dk@Qw}i*2NLD63;d=at#ld^7tI9e2y!L66&FS;p;IvJSj*E>akRKpn;dKSp` -N^BG0OW6VpPPFqm(?mpC=^F*>@LSjBu?7Ld~rL&9!Y)zQCkiZOw%TiWDUW@~ewVPY)S1nEYrA{H+99&x*h*qN0{4Q|1{#`NDI92=)zKJ{ysL#)^Yl4YzOcg;9 -=e8_)-T@{etg_TuDa6<7G1h(@@$SiPD0~vT8D7li$mHIPHN3H7l~-DCS8MQTbkQqc@fHjKRSs@bo0;F -*V6h$MgCF0y@tYSLczwiGdQUZ?P~N|)#O -~EG*LCon%VNvBbR^=)U+^7DWmj`n(&?u+agjnVScJWOpA|8x*bdGj#;L-r2C6sJtnaoR7|5`btCQ54{ -8Tc_rZGz8oKApWHr{Bv0?aZ;!%Gp~KP0Fv0GkdY9iTekE?8eR-t5%~>c!A;mWOf+JPNwH{B=t!Hrb+A -Hx1rTUF)&ENYPiF589-g!>WC*{gUIgd8l>A(QhcoZf_XaQuWoe?LLtqTeepho+f~tmNO$3dV0}$CdDf -zX%C5ycve&;>(Qh#%~_Kl?fI-sGn%(MTSbrXov!0Qd%ErK*gxcI4>4e(`TfL!RUO-F5S;w9-A%)1tv! -SiKaG2NCLKcxQ^x<%@~yo}Shyu1{o&ASC84mdU_oNABT$~ -PC7aiw?Lt=v1`2;Qi@%Z1?jrS%-rDZj9Of7ccFxvJ1)d!0ASwcoeu+^8i5BAvOf3-#P01nGA07w6{Sb -6Y^m$rZUl5#eez#eoj@>D?}FFYXSbTpzH>oaHgs90k%vkP?O!{eg_aA6|a{{OtK7k@tm8cnpF+h&BBX -k?_!Zs^+vS=}zCUSJ828!x`Al*lv$pZd}hVK!bw_kq?;!RAZIn3abtLvCY#`jbQr+X%zzVR(-p~ -J=xS!cI?Jz%ZT99{q9}#Oiew;mHd{qOuGecd3KBPr0%IXZJQ$Mu&PF)QM+b2_A|?KQGHVORq@#F81|) -c9y%WAZZLXCUX>06N>f`8o9r4^%$x_}{bm%md92x*VWa}SX4G!fQ$t!7LF)E{+wmHL=to@aC8?6qJmc -6y2+;BUOSi)v=r}$p%TekLOvOB$x_|RHhB%Rbsqrk2c)6P5jez$TBryNiVMkNun=R*+0-gyCe+ePDtT -9-~J(yAMg{Uwb9a%i$#aN$DZD5P-P0 -RkllhxYy>r^^l;2I#}tqlAl_p=|B-xdde!*wD{~U2f;sNjD{8`0o&rYIM(lixO_+fI@LX-6q=<@r4TU -an5bns+l4;Q6Y!49J)I(BHmkAgbArkH;?@3_&9?{v+TLwDaxQ585JO0+3*s&u5B+n&ni4S?VCyhWTbF2Z>{{i0Q)d! -1(s!dq9)^cv0lIKCSXzRk5{9Tg&3mvrsiNA`XTOaJtY?W9_zN_320?D~60z&?kd=4o^eqiY0>5jQNVs -s!Y+tGaU~nDX;|#;B67(sdt!l;eHTS`E8_)=G%n9;aQm<#-58J6@w@T%lD7amo!^{F(J0gdW*wg`ClF -UYQrPS0aTg^M4yY6}_nis{)Rlgz&s?&6}&F+HJ`zyKb1d4_Cl=P!g{^BHhv@8CIbDRxW<{sZS6dGdkJ -HLz7S=-CiQ!OM>Z+R2m)#!$G(Z<+{(p9c$bZt#;_GcXao{_g41Rt@st8Z*-GoQI`37PCkK{X0ig`#w< -dbV$Ik#EwE(NRX#vLgm;-Jm^Zm=>J#!<^i6!CWYuz+2iiVn3a|hj;TH>(N4{2Vr#OQcr8pf97Yw)Z=`}#L1HVm=qa9v@3_x> -L-^;{_7!VhbI@>Sn;Zp(gJ8Sm90GwwEmPS&Nz#UrEl61ESh?nudg_^-9%+k8;^D`?t==^K<9%$4b!IO -?$EaBR@9HspEW4N`WvgxGqZR2n0{+jii9&1iiSx+ELT6Mb1VtMHt##yZRPaaEw^fe+Zb%S^sOFq+G?t0jiu0?E}2tmWPsZ#>X;d*&OjvnAG -={?0P5d(b|eC1^_r+2L=8Q{H!rSPu@Zo0xPBp>~DuSxX?m`oMlOT7oN1_FfC|Mm(_CnLVR{tI~HOIjb -=wEo9%ahaAheygstB=S{JWc)wn}*m~oRZ42n+p%PcEw?BErs95gt@ZzA#r0i(_+HRyfjdD-Yv`4g1kB -A!|Sw(ChSib*VdtF=0XE+nA$vE1byY}&XG7(HI)OC+RPcNjOFlxu<7J^Tj*#sWiOSThqJ|k+>TyZFa- -NFH<#~KQMGbfzrYBSwDns=NSvf5MezUti!yXI-8=8o5Fm1y`ri+|n6wG_Rt?ZnpY9uJCIjk@>s3=ppL -mUBRT&+`}))yqa>qDaV~dfbG!82ciP+ieX*_TztP^lPr>wQ+t=dX8T`| -_>h2)K9hjmkxV_qVcC!;r0YbI5M*g>adPO!FTdO5byfyucXGndQEXSQO&(DJA=?3$~hDDI2!Bw_KYkh -GQ%4io`bWQkkx6zx?A*uvt{h -cCAHmVh+&YyZfz^>5Pnao0ADF75jBGHocPBf*!fhNGT_i#?f3wzhyc?M`=X`Ur+7vaeFS*rArqDFQ{D -lF-W^YD=`Sy8yEGXe!CuY#TdRHjve9A#Ci~;-O(uwM7eUd7G$0LYX_D@OPufT^c=n?FMbD=q~alKo@O -^NqQxw*xqQ+fppXl|jrw$6YfIuk -MPt5!HkRwt+ZYosDAg$m)m=?nLabn7>W9#ml -@^m)KhOM=^kpQxT)J`{EDbxw=MuJ9VSzTQI_w7Rd_LHIPQa4g -$pY?=6faLqQb&yYyXK0-wnHt#HO-Up!Hl2f(I?;E(s>W(ojg7p0-rK;dNCA@-|klRsGwo>tVFMG)Ax0 -*s;;B4m{@>{D4`$<{U=awd&BeX^u{-eI27FDrL@a-c`#@}JTzpcI(jT;8WeWwtIow*LIdy6m~z8e=Fd -`~>xdjI_!;>_WWQN_8*j!9GrhI8#TcvsU1AnR?J(%7SqRUTn$7hBC86%(%HGYO;B=FvS?vvN=2&7XVe -d+HtLqTX+Y-fHspZ(aAu#Bw+6VjrQQeXyiqO7`E0q%N*{z_$}B_8pINoW>>WGNiK<)&A0A{w`ERa6*wL%bXCGuO@{4g8o~CN#YL;b~4(iiZai5@8DZ}&2QOVP57UsKfHRwqfL&+xuPD!I&0ViZ%2 -|r$YJ!aRZ*V){RdbOnDDZF%ULGjw4(W624Paplg;u9RRZ|_1AT8tK~)DDSF3%`x~`9}YT^wAYSk0`-B -mY@7h%b!S(4>cm;ZiAzy0biGHwc}9=ypsvWv>XbhQ>;lw1QitDbehm?rTOBq_o(et#c2V$PI0GX?~fM -p$LWUeMRh8iXF`Sss7n^$0(2_ydpHi)EGqaqzLST*45K&*Wo2NCnKZ0)c*d)J$)(f0PeBpcK_=S%gaorGEG(>|z%jN(q}T^`!!VPpBi^^=CRMJ(R=f9PiI7Dvt@acmRj_^Z3x?7-4`UHjwjE3&&!TqD9Z-@P$gJ^g4=0^;x=+ -lN}CiNkE*!k^t3;Z||9naLop-ce~Z1tl!AAkURwMcw|aJdG@G4LE@?|8B*nf0!B-Yef86gTtM=9`+Mq -e***p<;J$o7IGUrn{=LJZMD3v#kSf@zHr*m0NEkSW)CDZbe<6NaQAHj}*whH+7-xd6Gu#BS1-O4<67=FWDPr~KFh`s?L0I)) -WueeYf-Rqf{0*(%-19lB*Jwxa_@i#T012jo77pyHxAgPm8oy0Imbc1ZZD(f#=h=*yi(`o4Q|-I3o~*- -z`c~7P;-Wh ->Y(7A&eUk>83OoI|-Rk0i&kr-cP)Mi%Sp5Cct$(ZlX?+8D#IN-$yPM@Ypo;_fcOx_CnMfM -)F_tZ65Z}qq_8_UkasJgi3-m4oSb(5_^#Kp7(L|}vU5kP*R+d~QRMFcfaZ4dVA8}=B -h^f^p=<);?Lo&Dx^kS3$wz~*Z^eItiwR*=YaSF+Zy7Ed$Yido~Wcz%DR0uKanSQUCYyM7z8!>yhzss{ -Y&P8a!Z<9ctcTcDh%?QdyvgS~^BpRA3`b@RDO66&<={g{Ktgzt8TL}v`L2QK9BRa9&vFnAu$8aB+s)d ->0?G*J0Vz4mNaI++D)_AsxKcGhh!&<21Cg?apr^cE-`TENw2jk1o+ll -FzM6R(oCqirwHAJf)jQ*<`^SvJ -R)4q2_G53|{9*??r?+h}%BCxPbE^rc4S{;k`KO)wT1Eb#xJ_2&Xr7hC2YxgXKGS(?e(e10ikCiv=li}aHTzIR?v@=QVq)qQ*)ZdNOdJlhbz^-XQnUE -gr$^h3KeGI=Vt59E?5-aXD-pDuEqo4sIK^5^Em$c;K76XP~v511bH55#{dK?if6(5|9Ki@xs*-b|Ie3 -ys<{+Kh7^|^AlXrmgPWMGDcYN}mWLQE^6&y9zpFP|I|NI&OIx7UCx9DZ>sL*<80lpItDDgvK^yP#h~* -6Au4y^cV2$Sh{Q4-kK0FF;+<|)P2agp%jhp&dfoNxp=PNN3UQC90h#NUE>;{`9J09>20%W#&s*W*+&d -%a-w-7O`$8;W{p2E65n#B=BjRoc1aq!Ep8gn0DKjbcP2H%Twd~$)Oh57!$yUU~epDD2PE^48>&(xkGO ->)8feZ9fG`Z#4gFC_2gU@)K;h)miCqd@6aWAK2mm&gW=Y(~yEJnT003b(001HY -003}la4%nWWo~3|axZ9fZEQ7cX<{#PWpZg@Y-xIBaxQRr&0BwS+qN10-=6|yE)liLa_ly3YFpW5iM#c -dHoe$cvx{>qMM4&8isT5=wz}@S?|A``1W8C%(%x;#k(NN<{f!sT3owf!Td^QmRAp80AYfvZXGO`vxy+ -KPAgs(W-CTyN%2@lifWm$+h^gxr$LFEN#h -7tJ4O>~MV4$H8mgu5sO5r9Z%=7$;FXjyQ68z$EvJ7GapirOj|GL_*SU8=Pnz-1JYO@e<_S;-qI&vPD; -gp$pe=S_dfMC$j%9<$Gxcj=90r_X-&T;nmc;81Ioi-h~$dk+IPvH`6R-ndWZ6~79j^~Vv~WA}f&%e%* -qaUJ41!u18NFL6D=^%btKaXq~(FH>(HZY5(tUAR9X)>hct>i~_fXxB)cp%}|5DwbsQXvy{` -E)Dd#di=sC!8FBh_-GT8@-_Pj=O|`U1L8EFq|QsuF~nIqP5*<_M*K|7R}{GN>Z?C4MD5^}WltQ0eiv_ -;_PBo3X>aMOLuu3AbadR|as$nEc9_Zv+3YGT`H!I}F0vP}SB}| -UVqi0bCz1~dWJPRuQbLgQFqy;fWYDRQNp{N%f83?^faOT9u-l`gJYm@leS39rqSTjSC6cg6){K1eC|u -nWYZ>17vn$3UVQTd*}Ldc>#rf*p7KJ;t~M -Tqk8VA44GIm4|}!?(}Ogp-ixc0k!8D>BC`tR(M^%o9WUb9VG$+*hDuvmN4fz{#S$$p{xk -Pt{?h_r;*7kGcd~&rZWUhhBcuGVXXZ%E_N?_bw++N)94;dc{jW%Ex_sJH$lD)dCgS&_F#A6SczvOqM-20>B596^lcYpy?=GVvrgU1tQf<|}iaxnuoK{Y -kM_x8HT&i4lCz?*DL5o41=Brimmh+nxc(!2s3CB!xJIQ{VmqrrV5y_)@yrF_Ev^rxnO5oCEOn`PtOyL -ZRze3?~A%x*c0!f44^xfJrxs)NRG2nY+%Q6ya8=TGXVPfxThD53oA;`>+U4KIJlc+awvoE9)7x@pQxEB2w1D~qH(^q#gycaVin?(!zAF>GpTNzQqdJIN3MTnq3K$_suTn~k@QLyG8%;yLKV)e%t%F%QhG(AZ7@1Ta<-sGi&~sT6^&|fhCa|QItEJBz`(5;#$+)y$e?^!{i%@AIL%k1uU90 -M;Qe4`~+NDu`wzp8s-6pRK|F*&~EVcaS@oPskIH^21&pXDb#gq;${zPAR1^(@|%W@AU=_LM_rJjVK`d -jJ9eZ-H@|6{|3@h?yXkbqACFn`T+TG$H<$>x8#r_H)bjsRL2W0G5U;NHZ*CwcrHcuQD17(*D=O*qyo*~Zs=lM*O&4^4l$}kHrcRf|fhGLb_1x`&A -E^=`9kh4F3bRC@21WE}I(lt^f`sa{dXz^$7nT7nPJ+>$sj0uqjS1+n%UCBEaYy(pVQP(fm)?9 -TyRE6qSyjT(_Fl?4=j>;yg`Om0PI45@(iqap{&UcD9zrj-4PT?5_V9smR-IK -~zIuEs!v^f*R`v}C=-@?fVj#bg6D&eZb+howbjpG)Y96B=Ec4@o11id=S6PM`P8-n&`9VGE;8t9V11z -1zOM9dZoZ4xV^1EE6w1D6hATZ4TD`qxIe1o{yzSTx(Y-*rXXQPca_IkujxME7M=S%D&z|Fj3hpUK1H2&r$+12?xgg#X)N^~uzZbVTOOM=Z&qg6KA^&SKSjL -qcz>Q`(Y5Rak0FLAslQrfOfW3iondDit)Zwkz^SLB9O@cD9|JB#LNL?Sy*}{!R(0u&Z7oA$s^`ATT4T -#j&+V*grCqGQfLes!C?6|8B4xEwlh>eMm>lc7MT>gT9y;m_Ek6t6nAR(Zo?daCv>MFbwt7^sgVj&^{Qvk&%00!TY$c-Nc=IF!iZFaEW@$c8Y5x_Wf8{^jLe;nT-#@Qx8-BCfDPZy%I -7M9sH>T;^QnjT#dDD~2QG4*@z(J${qrvr4+eP+x9R8c*K`}DDXLOZpmpS^tj;^Ln_p0S6LaDX9AC8B5 -LE1SOVk)xCs(RZ=;!F_sA^Z0zmM(oG8e|h!Y3+92xsAbZU7*OCz%So=0l~+4)^4R%5&o9otAD$xbPR` -X6UQc6xN8He}i@3CB56{prB7?p -9IzaUb_MNz^;}{ZGeB4<-FmhePe)$k-r;N8|*ZO11C6(0=N9_ -q7<;k(oX9`rT=rt5L$nsB2&ZIntOy?IYI_bb4@a2sy*&ZHxa;4@kQgFP~pL-w+c6qxl_ChJ!Z8#*ma= -o_iFx`{A65Nvv|@g)`a|pzjXzQ^smjqsd?QN@OC!JJr!h!`z3aM7`OXt6$ero}%+qP0`*q+~DOFDGpA -h8{M}D%6%S=mLh5JkHS;h-F1`0?X>jxfv%bYWdieEZ{X5;)QLv(c8puT2kheY%h$*3InW)^(&#`|xmq -C!Hadr0!z2xVXOEk@hmvZyfHUqLv5_NtY#VXyo*d@*T=m~}rU-*xZk(h3b^Qj-;I%Sju#Cv$HF37l -H`EQWMwKNKUpGkob>53>9-_5TB>d9HhuK=(LYE~lS_v3uV~vD^@pzMj@eG{=(Qhjq=R8JVIy+PNF7^^ -v_(c+B8+DI&D3mm(AzFp0@RVh4pBKg9NKP&9h<=M1Ut}vq+_6id_3dC^mmTSesnGWKw^nFQ3m5^~nx2S!g -r=YE??06L`_HW1o4Qon8a;H7@Tt*r&FTMZsIH$g@5H&?LVZl)uU`%L2&-uM*h39+3La%wDW1k#t29Mv -@TMGP5=$8C|J7{RPtty`$G^u|*9O(5sc(jRkId_@&Vpv+)&vF*{O021GMdZ#bVy>}1Ks_E#5$dDp2E! -QcXrkPn&^t+K2#X*aJI7>(eZffIR4QF&>E_@cD#4*-m$YcZ(hG)-=CeIKmY3)`{C^D<=IQGa}q?o=v@ -{O^H9Ix#8%-o*G^o~TUKROtgYkt_C%+C(^d+!{;1i_Wlb{O9KQH0000805+CpN$=CI52gSB03iVY044wc0B~t=FJE?LZe(wAFKBdaY&C3YVlQ8Ga -%p8RUtei%X>?y-E^v8$jFr7bFNFL217sAzL8pra=V#ZbT)sEr+3Pm1ik7}GC~W17D3oe9AS+@@tSX3g~;50EBLL933$H6H6 -1$M7gKT&5_b^CP1?RR-qC`p)U1`FU|ZtxkNmm=vz-@xmNAqsfUF^ny8VMK);ff#FY$4^T@31QY-O00; -m!mS#!Jl|Gi70RRBg0{{Rc0001RX>c!Jc4cm4Z*nhabZu-kY-wUIUvzS5WiMZ1VRL0JaCwDOJ#X7E5Z -(DJ4$dN>ih*?KV8BC%b}3qP$YultZ5{1HDN?wjTKnIZ6y;cU9E6+1ckfFl%b^3U?GOh>t%15ea0IiJ; -D(60JU7tNUlIzwfx*)*w5T0LWaC6Bn>lvDIo!nrku%9p)A!`}N~xVQBJj+F)%3DnDM)9rSbW_|WV4H~ -HG)u(HZa75mUOR1V2c+1;7KY$XB+4lEgjdtg2#y|p9knqj(l9TBxdVyZFoO)=%YNgWG(V5GE>1CWayE -r?5lxHTrWqBTD=0vPFg?$S?;2i4`b@hr`cHL$=Too{657)@bG#0Wd%NDOe(2MYzOqYN>O)MHpel1lJm -u6GH_f4r1; -0m$ANZT~T^!!+yFk=cD#{aVl1TN`fJ1+F4WHoYZNhT7Sv}IreW^EcBX*Y3P}2a1_$>BbUY-7y? -!C}%io7o0?pXAQRj07tvy<@!B;SP+k_Uivy9O=DvC}tTEo -%eh5stKA^5hHEF%Ux)cT0N4Y4MBxV!dUp6` -|`YY08|d63xLxf@dIPRbFqq5aAELgY^@>PC2OsOTu>G149~tCpNx8l~C2(`yE^&8k=mqrsDS)LsXlsb -Mg*BKN7}3r{X;)3!>ovbX}obFIZg@|Y*DHk-qWA< -EAzAN&qVo^#3xd*n0gaDn>3s2hP+1>$B(AOHXWaA|NaUv_0~WN&gWXmo9CHEd~OFJE+WX= -N{Pc`k5yy;aL@+b|Hk>nj#MQK%ra*8n=CMSJYYw;&*BX%#O@lLAR4&fj;bhegRw`an^hEY9w5W;ujgX -HOty+lStvlt8D>x&Z3nt?mQL@w@)N*PI^8n` -#e&|iUP)7Xgg@J(T@>Lc<2T)f-e!a7SPL@x2M0F7oK%O@%0kZNm!;sIO+#YZJ39dZ;*+>C=*l&2I-Ja -u0;#zU8`)yXk2Z}vs-t;Qz|44>Xty!1XW{G&PPv)tAu6 -tjXLmXhTDF9(zyI~V*5g`-&C=q6^`7I37PiS6C4$_6^Fij~Q0N>t3Ai-~ec6l#xmf6113o3qQ_^VIM} -RwaQLrgC#~5m_i-j-)Xe_5(epgPSC*iDF%;nntYJ@>LDNhYCJzpJnoc!Jc4cm4Z*nhabZu-kY-wUIW@&76WpZ;bUtei%X>?y-E^v7R08mQ<1QY-O00;m!mS -#zA(0qU&0RRAP0ssIu0001RX>c!Jc4cm4Z*nhabZu-kY-wUIW@&76WpZ;bVQg?{VPa);X=7n*VRUqIX -<~JBWpgfYd3BFXYr`-MhVT9rBBumG*tyW#I@oE4?pA_PVr7^*agdeL{QKESJKD9?$sg|{J?hx^pmjIN -#H2OYe)KGWP4I3K((AnhV^20%Ka?;Hw)KXzvqFq6DAlDN)fH_pkW#|)MnSCQv-QqVE0GV^*cTgOGVit -(Mdy}AfzRb<$L#rr^=r_?;4@aK)?oTJ|9?%7 -+H8>qzs~Vc!^p^9Ig`xB7|YpXni4)X{hGc*xz^#MHvvrDjXctU2`yP)h>@6aWAK2mm&gW=SG|`^1$10 -015V001Na003}la4%nWWo~3|axZ9fZEQ7cX<{#CX>4?5a&s?YVRL0JaCv2pK?=h#3SBUSQ)7D@$IWW)89uCN?TQsdFwQuL@6 -2&6#9$plPGn=$zRQC#4h@P^l$Q&rhB9?Q-GKV>h-NwYP^>q4sfxGn@v(JT`YSZ&rS%vp?t`(YH -oQUv*zlO9KQH0000805+CpNg=>IjJE;+0JaJM05bpp0B~t=FJE?LZe(wAFKBdaY&C3YVlQTCY; -!02l=^mRWEfaqB8k11sa5Dl#zxF^+%Tw08f6_*mRC@qZ8vtz -ZR^qGsBFB`@68gcY`9;wbH$Yt0O;t!*hhb^MtYN?MSXhKk;}#)Vlw)G0xhre)ZTxJ@(i%tQw$UC5(kj -`S{f(lsVY=h=tM2QX|EsjP38^B%a6EtlOqLtMV{2t2OjCKPG23_i)=?j+ZpE6=3E7r`bc<5n+F#&xfO -J${^&PX@)i^x|y@bMRh)X}Z0pm#FCnIWKp^S>E8fWKr8CTqK>K))|Szhd4cHp-*Y`9u2kHSomju*0vG -952xhn!feRJw^Tea@6Y=35b-Hvhad6Ioog-)=-$xnts;QagCNYT$DV^I$!k9Yx1(X_%Hip31BYxW(Y} -z|X<6S^1stZ^D4?HQs{+0b()9|(>2@m5+e#L)oJ)G%dEMC@X3f%Q@56rOV(~o@uEf>NfSx45X#fQn!} -LD&IF;y3JzTI)9h}LjFI%w7Th%7N(pf_cv7%p(nBQ#(T;FYqmzoP$uIZzVj5WrAQzl9w5@5(yi1>FG+ieR>*-NaRWBBO`2_ -$ckkG+i}~Av6!lb}z%%pBc;=h23neR(rbS~K1!+onRZC?Ed(g6M45aOUOmhChHJ2hLjo_KgAuYK9#Y! -Cng~h0Ooun@ya;Xx{Ur#^=UQKF_HFRppN$|ilbX!R#g*HqWeG&>*fv#BwIwmiY=lc>WKzXoe;k0j_m| -_`*rGwEw*%3;U?~;pfB#D9`$V#R)`NT`y2yI|V`yE{e1Xm6Pricq}(lpdi7BSJXQ5nQU!Hg~c<>?gxI5=%NeK3qaN&Qs{DsF!zVEsE~>n`3!<^ -LA;|UOIUW7CbH^TOOz6I<&C!035bZh_y@x(;jqp$EL(Lf_tSk6@B3zR -%QUJkYBcC0KG6>J|xF>A%cd!qZRZD6k__fHZLA!(J%rJVe4VsxLFcu;d(vKMr?j?HXwc0&>GF3?r5n& ->4W5N3Q?KiEN7g|e-ombpQv&mE4Ler6lP2es)1N4C*lzw6NBDZDzKO;6J{vS5`C%pJET>1?SNY_JnJK3uka -#SL11>4MDldBNhFw&GAwV$~u>CGZO+2v9*!49RpbAVMEul4lh!;Guus@`4Tm6WM%PZxM>@{CtKV8 -D++HF*Yt(EOZ#_=y-jb2Pno$(^}T{xFmdpUZ0ra1DA0@CvY3fBir163nggSk%JWfX?maR;I=0M$3u9n -SW;#E0^HB2ab50NMgQt%gFO)5K7sssO<~Wn9#n(ogwzc@Tpg1e}FHn1>q}t->^sHHSH20JkO{cHoS;( -K=jqJj5qsXG2W|8BH6TJ-`966MEYZ?3jP)h>@6aWAK2mm&gW=SFKog~Qy001T_001li003}la4%nWWo -~3|axZ9fZEQ7cX<{#CX>4?5a&s?laCB*JZeeV6VP|tLaCz-nOOM+&5WerPV4Z{3fZ$>`k3*0YD1zkJO -L7R11_mwCYaz1elJsu;?>jS;EQ+FR$&YOg6(CuU;cy<`42R>TR%A2AenMAR~rjBNjC=W?7RtVFJKu5iIZr2cr4BvzF -axF`n7)Ao&K(^*PwvbG%b8Dp_b}Px2xi;{9m6s0zue>w>JtFdX*5SaJ+3-xVY{fOt0Np4sXPH_f)vU! -#OGsR$Qy0gA{I`=2j&hO$LXp#lnvt1I_oE#aDzyO)$x3q3Rh!}Ki)-!iM6G#QKh1(cU%!5R%bp~IM5| -~j8s7*}%^EV4>y6z-!n8(;%A@`&hxj4Rr1sB%WwG=tGve;qVXYc^C)a*mC{K;DAcv>j~GazTM- -HIEq9jv33^=AE`ITTgBzW)TkJ%pVDD@X%=p~x|XQa>1X5zrVwKB89`ga=TT@T5tX2)ob|Ef9fFIlft{ -qEJr&qs4CTrr6T;0>Apsnhk<~oud-RIZ0VUT$1U4H6j*{o#@F_@u1aq6N#Yu)1{**BDVSvNP#Bcb`Yd)f)qJ$kzzi#O9cbskUu38AI+J2Fp&&y1@ -UDtNgGo@27+rDVdbmYPKzx&4n&i;@gb?|hi?fgmJqZOS4t#A@OSTap&5!Wf}xaA9g-lSlIC=SiYGngd -G7+{eGH+WSe=}T{MO58g^$~dpuWFcRe$Ts`n0NZ)2hsx9q!OGMF`YXy~Duq*qHFI0 -f6E*WP_feG^(((TB7l=zcXkb?F$wn2bhbN?P$3_bjl|ZJQI=Vq4s8kgq7AV3Fc0@xu-VxqdN)$r$V?& -7HB8f9Z5`jqedXPT@D}e_i+cB>o6JZnN6aXEC;*qK_wtE?#!8WIOlyM=nGzQ8*ZS$;v)t6)87y)kT;Jk|U150y`~PwAfgIEEv7oQkkJy57_EC0$?9^)+4JjN$2ULgVO)SixVw2UR-77-m!u0h*{Y;qXK -y5+Vw3w#ArM#c3-JW&l?e$J)^D%b0Qo$j>Tfyy@b3y|Xb7bIUvY%j}Q3E_(MnJ;r4UtBk{e8Rv1?m7{ -WnUo>AG0yMFvAk&-ZE=3QoI`m0Z2Mz;S_2zo@pHpP`!lqAK*{-k1xe#kS{T#VvT#NLuOX>mr31;jbIN -;Q?BmPBN^Yp(I^+53tXTVxlQ|yOEt<(O8qNvz-zoFb-XlxP`Ng9(2Dg!rZ>@7(hdrk^YZdHucu0_ -)bEjWaio{;v46JRqZ2j~uK$DSKLS&Oj%O(xtxMahXwDvyuuo|X;V6`Jb=9)b<3C{C`8Cxzw`gpHPd1@OXP;vZE_XIVlmH{(mY@qpY8Xu -?X03(FkSXOn}ssfhNY%vlCp&BjD=iJf=uzH*m010K4)3Bc*Tc0*;^FNi7_#Abatys#C=W%bWV%m0L{X%;)*d3oebOi+!kBxAw1|8pc_id^HHrHW -aK~xi-w0rO0OdwYeGEWbQ%zY;RkVU`(JXQ$+-36qL;V!DUc8a@uY>WNWCMCA~Iw=pgt$a -EPkUfTDEA`WAbz3+=iHIYz#yDt?<#Fw;VH3XWDRTTk`K@@$ -w7c9M|%RZ-asHTX0AgNZz#iR3r8XNDk<2qd5admv&Vv%yDepDL4!rp -OgVUijX4+-xc{16lJTH=Np5J`Mi<@u#;zLsPK+8Di$-VE?#+g(1%j4V{K-+$blpT0@!cB07Pvg2rM+0 -A04Zk)ceon786{3%8VEt{ucqIIsl^@36vbRXQ~$W@+efU#!ATtgpdPAZA<{!QDQjYAz{=Afv+kOSHwt&H^XY771;?)tOM=D_!ev#JIX+C##UU5FHWb3Yp;_qGy=7g4G>+VrP_fmD(n+*W3F&wmntkRFbJtrtNks*7=eCniRWQZe@vqRj)xN!nSz&TxTy -HKmgq_J-bIL*2P>zxj3J?)@tZWTD9GK#c=O7GXmLu`HTgQ}85xU`5IC{04v`PD5s6y!WhGhwaI8V)2c -Z0P8@}=pCHq;j5lFB0)#QsIMbOxtZ=*>GQ72P -1WSZ7ZFU17bDb(B3_F#S>7-JxnLEj;+$wjLQC}_$qDb!e7UYbkoi|rtV$w4y@K}imU76H`$7P>g7K+D -`IU0kNIdStA%r0ejbGgERFUvG`;+%4gSn-TAGbN_F!nm^qn3(WK#Xsjlxgk^WjRV4<(&IY{Y>|QytCz -}SH(Al4bRD?ZQFdhmqalciDWtk5T!wTwn0#l7y4xugFi)Uva;8n^S=QOPFX6B@i!|cS%z+FmH~%mkvQ -6eD8{O2s0?Mo>$D+bxo)-G0)T4t8+zl8Ta`o7##BqkLh}_852_WsdW -mliRHCj3ieu5(NNL6Bs21`o@<^l#f+UeD#?;us;T0*@lS?VTW>pHXN;j6QyqZQC -~_Nda;o~mcJ7RaDquzI_#wik5`En`TrCbWI4Vp}mn;!(0&7uJ94ioo5BK1D7KCX}9JRQ<)k3;8xTE!@ -BmL@I2lAZ*!F}#2RrjpQ-Q-_VVcfbKauC9xe@M!m_m+D4bnCsIA{ -)iR1_mh^vfTDFE|PBrcq)uZt6hDPfrWDt|9`5X$lDznk}z6n_H-xgzo+P8y<;hFH0kTd=!@V@#F8PzX9IM -xaxrV(G>?$M2;5*GQ;yZ(}n+8rV5Un&J-$wB5_ii?cukS2GOiW;ZN#JzpaNYH!m^(++JNj~UQyQ?)N!dI0|3%8=RT{l7idK7e4JFMSEO -0LLJXeBxAF4{Wh<$8gPmwwyqz|9_juYtp=l(5;zN0qsh>Jcu=xmUGtH@dbVlzMRQ<-vL8k?fSrXtk4GH%n!$FI3Y!mXL1F*T0S*xu73U|A=X2B26}EWHCZNgp3`$-r?tdVmSiXgc2{V7v88jbCDeM}T(={0c#*& -%(0pEaXeXq6)+9114&;i&jfHtHm{oAmz2(1Ybk$0<%1|*+fgAbD@}hz-f8!J+ -mMKdQgt|4FdkGjnyxatg@{B2+n_T*sOs|END=ab=`D|W%BSrb*2ihFqE{fBp-mi@yW{&U-cTVOE{ANn -Y|lRDtTyx*T(5bM?T5VORdLjOZ~3it%=$^mZ9TH`mDk2qI&O-!3$Iy5z@~Xx=HXh4TEdlT+-9XXuY34 -Y@jiFU!Ueu-8aC##DB}v%8#)6X7!5d7 -4%R+lE!esSCA5X&2n|+7=%~$6JOmDLIe6t4(=n>VDRy`@Qyep~A*MVCAj7CFR?iXeG0CGdF44@G?A3c -|8|fz6hIn+a*~|2jCX(C2uSO&x|Vv6}3Ssy-#bw46CEE){R12!&64r-`TR3+ul(-2pGkl{2a>>Qh@?< -k|$ZS5EOtvGJXJAiDef6qtCjKMbc8#&OCvo(9SUmbb`Wl!~tm(pW)p-$Y4imI2_uh$3CCD6!z#kTn8* -Dy9#Vq&smm1%&-yt#gpdx5tK3Wxd3MK`AGA4Nr+m!)JY%?f2-u;5OAbTBg=&gI?}G2>R-@cPx~rw -xuAeY+IE+(3mSZ+eZ78MIFMr!w*%(|38h+hw@oe%o~dc3w*)SF;C&npisTClSQ@fzlY`zyg?&!~w7~J -KF-U89jf*3CM*Kp~A&73!dHcZy(v6(bl1B75ZY9YSm~QAKN;HCQ+)h&*SdX9wi!%v7XzJ|C9YDI|{To -g_7^1nm|3ZOJ+JOzMstjx`n}f12Fp%b@5FUe8e4X_*z)#WOq7`M++53jGT=I=ke -dw==ikIUbGd-vf790K%HT3%2dWzAe^Pf_BCnah)t{VpzFM>bE=2> -qeL{{y3yJUflr-MJ(h848u5Iw^?2%qC;gBPzMtGo-KTHw~czxnp*)8|jvp%~9uA7Sk^2exgKg2G(EBW -i1gDGBjsnJ!^^j6%^%l2ne{H?1-M#I4NQWqauK^#G@S -aO>-CJa(gu;sy%5E`4vX;<4t(S>XFS$GGsgJ6#tS!QeHR2Z%T5a?T*PZ8baWxa_nQ9?>SdeRoZ=HAMq -V>j$VOBKBek(alU-(p!gOZq;w&ycyXhGgzwi2rI`n?P>6V;Rk(T-dFO#4 -z4VoD<(cZI*M+1@(DO&_y@WKTpzlCQ}4rNMH*bf9|p5VfF10od -$0pWTrzCtQm?Kgdhln8jN6O4bG(aitjS0v$1dcD~ur5cD)aT2@))H0c_S%bAokU{%Y{Dve}jgxNTDZQ -sfK-_#=nH@h+8HU^;{)G2XJh4i+5d9R-;YtgqHGhn0RWbD#?#+f&8zSO^^!-eHGb$6WO?_#I{}c$eB# -z+NYYQKz`9#WB%`WwjL`Y$OA#3OkjL6RG%Pz_JvTVEJr?MkzofS6G~+YP3);~AP3tX;&Y%2AXr##l8q^Hj`mBW;864y*G#E7^|*H?R -sXBV0-bt~5sNUAwiTvPp0Ci=eaW%c#?d%a8By_2}SN^rlPvRhVPkahV{|7zGX9n^v2j@m6Y2B2;2#*v=FV18^>2eFy|Zn_+|uuu0NfE&tHEU-=4j^89&_ZgJ2kZ4K*DmoHVSH -&vmtOSRhgf*3sJ&wXUq^pfWg9U~R)swH{`Vzh+FwKE*JP%5zJlc8$3lZ|i8TzS&^!ZXdCWl6#l_)+v5 -Ntw%Izch)*dOSw_d1Zz6ua}rKE>HF$B0u$4+bRg>cmXL$zPaf$PgWyq50nuc`eN`%58zAT4mMWmx-P-2JOF}oph1%AN2{+>eO -q^5`s!vpK0eul=Z|!$$gb;R3Ngd8x59?T?PB7vDxjDinVV{6W-CbI2WUC3V~M|eb66rC^6jSA=hJU*g -R@}pZYo7cQ6=y8eN}Ho}E6L-RSx(=1;fxH{0pW_I`MMqi?piXT$qJ`E782GrGQ -sCYU$gjK`0^0T%A)TB>Oszd5~Q93EXykuQ8Zzy5Uo?Dp~K8GOJ0#`5-YuEBV-Chd9|dKhl5PKsIw{R? -_tiZlYXj|OGGsKj$ChV6XZ(`6~T<>WdfW(Ok8`C$|6zDkVUl%licOfbbERbGySV7$X~ScT8Z0m0+5;b -G}t^{TU2n9*=L9A7`ZZOd@ozS=mcf4-);QMank&cEg}tloycbw`1-Q0|Un?^g0sI3lu@daD|cnOJ)l9 -@q7oqw*dY-01Wg?3+!Ow`cciwE&-r&K|v-+(eJz?@Rm7&G_a0P4wSSC*S?g&Fp3QZ4@T=9RAzgi}XH8 -?z8YdGxyo<{?Yy9{svDj(O65l#^>mKsr=l3D7jFa-MQ5SxpRY`|C=%NnPB();h#6b!*B@Us8|augYn7 -#0#Hi>1QY-O00;m!mS#zxj%uyY0RRAV1ONak0001RX>c!Jc4cm4Z*nhabZu-kY-wUIW@&76WpZ;bcW7 -yJWpi+0V`VOId6iU6PuwsNz57>;j1Y-7joND^4hW^aqArI@+m+=_CSY*v$abLg*LR!`SZ8S^e6ZsAc< -;@4oYvV6glLB_I1~a@*IO4r9=z2kRE4KSzF_SG -{G-US2r@qN@Is)Ex`A(#_N0OWKF=HtoR<$)nQZ0B8Qb1mT~}N2qxxR`q(Fy6*Y!$c#&_p_zu|Dc -#-}V+Yw10FQ<@A}6kAT;`B%n()|hhR(4%|8MJeG^Nq^9I4B3wN4tW7Q;O`(EO1_5;Xyrqe)|clKWP>f -WJGM@U%%g6J3+C0&OvLc{)!14G!9Jn^W#C9-`7e~X!b=}f#=ISmC%#VtR6EB|SGOcQ+{!x+zmk|`Nz* -{~s%??8<3@G_8)ZcW<-5&V=}M~HT*4QYxKSVLs!TJgiu_B`f8aj74`*z6i-}Ngb*BR{5zV#5tK<7qwl -lazAHbS4klu?W_+UpmWrDNKP~$W}9-i;fk$2pm<3>>@&?zA41OB0Mjw(6Ty#}wX*0T2~CWTqz&r{wPg -?erby_q<`C)qUo^G!RXABlnE2U+^zkX^t<0b?Bd3s6e~1QY-O00;m!mS#zUGGio<0002c0000j0001R -X>c!Jc4cm4Z*nhabZu-kY-wUIbaG{7VPs)&bY*gLFJE72ZfSI1UoLQYRg66j!Y~Ylcb~!v3rMI3Kw@O -z3f=RPqe#bdW9M-N;+y-7yO9 -KQH0000805+CpNiiqKz7YZd022lP04o3h0B~t=FJE?LZe(wAFKBdaY&C3YVlQ-ZWo2PxVQ_S1a&s?dW -o~n5X)bVi%~aoO+b|G*_g`_S1Z42S`dSF1BrfzJ>9!cw5{yx8xzTD%#*({*vHyKK%SrtsEq$CIMDl%i -y6^7O<+UmyN%F=tHBS-{Wu`|+tF+6eS05b6+v`{jDMv7ErJSl|XH7m5|5oN}N0W#foQM`&S;=W%zFc3)sc&DOa -@xK642|g2Opj3q4faLRK4CS?9o2}u#<2S7JU+S+}(#GgO8&oMrq{MNAAMC=Ju56j9Na6if!9bBr?_DCZ^5)UD`& -}gqBe`fmcOcR&^ow^16P<_^px#n-i9rz7E3^ijBt#;^+wo;W$PtdPhgjOI4O!8glMYorcJukspn`QfWIL&PDE~QZOh|uRc}tScvpCv!CA6+!3x1+KLO|@A4 -kkw_;iYT)@rk{W`pYAA4qOM99<^=QWu(AD=b<08mQ<1QY-O00;m!mS#!hTwf3x0ssJg1^@sk0001RX> -c!Jc4cm4Z*nhabZu-kY-wUIbaG{7VPs)&bY*gLFLPmdE^v9ZR84Q&FbuuxR}ebcfaf1jpob!BfgKhU$ -Oa530zp|?CQH{r}+b2-SSLvkD -2{c;froc=|k5(whSQ#*ml}epBru4Qb3bX^%o%dGm2+OTE->O>m{%M*U?Yz+5jY*4_#`y2T;08P4umGdMTf`0nx)+7E5W -QzgdRqV0US;l*ss<;?+g!0#xY@ces?q)lso;$g?Pqmh*E? -6<(FfYy2n4J)W3b%Wy}sv(GyMSK%z{t$bf0Kczl{~o^MIfkH%cEn-XlTlBq5MJW|vpoPiCcpe{bYkVPr+g?n!4>EfK+e3Co`n@ -T4MZ2(Zxgajj^fK~^U-78f7eQ1nJSdFsJ#}>O#&P)THZON+4?|}nr0yXsSnz`>HaTme>3(uV}i85G$d -lSaGH##zfem91QY-O00;m!mS#yu**9+E1ONa|4FCWw0001RX>c!Jc4cm4Z*nhabZu-kY-wUIbaG{7Vs -&Y3WMy)5FJE72ZfSI1UoLQYomNe6<2Dez^H&VKSkzuP0{U?>;6s`X+69^v*md_(6b3DgZ8ntXawt1#( -f{5VQj%pkaf9R%+ZsMTzV~LR(P(rHxw97YU|a=m4W%n;iv(-uVO1Mjm}&!sS+CJ!72v^C1xa1-h_Lpq -R6j57eu2W}b;$*kDYbBrd(;6z$Fc~6K0<1!@Brayhb_nyo?OkpVgbQ1jxy!WqF@2x9=)~@woHO%N_{( -lYyLIJ+T3cQhUi{<<7$eyksT%kn!(40*i=5r^QpRVW%}hdRb{Z)A|aGD1vu^=wg{#Q=+`E;??r-6JVj!&q#56MUQ$u;bsn6b^3f1Z@KL^H4dIDe;A5y+5M -C5ypHe@8w&Y-iN8&7`CeW4R4Sb{$b(w;bZ+vV`iY52S!fu(43Gnz=Z9IyN--+0L|J>?N&Iycj&4DRk! -H<4y9<82))1F}%WWq<&L79s8H-TbGPLGw8>;Z@cMie9Xq2<8G2*I0G9gw|m19l^}!&P)AMnF -f8sq*Tn>a=EUh&6Z1vxAT0i6*Lw)LVtmeNIRz|&rv-?Qmc`0(N3p#6p>GX?y|r}&8noIYu#iaqof$1WoLAvZ7&mgW)u?Os<6_6FKup3XDm5vE9xZjDu3L*%~I}A!VscD{&^TU!arTUXB|@=})4L+2%}=i!!nv&l{A*fg=mM9 --DU`GwTlA`;&C;^|6F4iKA;UKV}`Le(`*s4nI3#oaR$EnD`Z}|MVU3>jKZkU789!cs1WGO%!3g^SI8M -E189%!0xUlCkV`7;1plYGOT82!bO~>BU?gEW1iIxFz3$}zJvSQueYBek3*GTAUJ~ZS&U>Bh1`?Pfn1zEg8%ez=JK4s7wz5Q2_4hikhir6`qyoe!LO4&)#(Cpt^)IV5-Da84XV4wc*#UuX0n}dhh(0oA$TLW9~MUk-67@iy6JAkzBy=zEeDPM#FIEqm -UgREB^yfO9KQH0000805+CpNkqz4s&Wnh0CqM204V?f0B~t=FJE?LZe(wAFKBdaY&C3YVlQ-ZWo2S@X ->4R=a&s?YVRL0JaCzMuYi}FJ@wRE7}=Z2(0Tmd7Qz@$v3_yJw0?^WQr& -`?%e^B}Li!P?SJq@@{utJMWz=n`*=E@0YD^8*zWnnwJcY!3$YRKc*-=N7vi#1qS04?sC! -ZJjZihug*rKT#%|Vv_6oM)&3n-(#v8#T?Gp>OU5k>zKG6VhR?UABxn{z_g8i`TLo0+s$Z}B>@Pn5N_k -hC$)Z_n8n5dAEGO+D_3ZZc(W;6TJ=O3O#{R_m%99ooYF!|>_txRdSeXstKJ=x*u(OztLq&`9EE -0IrIWg>?KU4XG_sV(E;(7B)N9&R0$9JP1|tHqG285qYDw$zwxOT6o{g)9lpPI>-Y%-V-MH5i5AP!{T1SQXo`lBhx{ -yMl<)S~LrwUEb1InunX{~<%R^3QYe^__QICwv>-gQ+hP%g;p)Z(kT1jmD920JVf5$pL{7K^up7y+XF; -5vG22};nO0c%pRO{=trB+I;Lfh}GX-k~r+3>)A-0g9q{Ba0oY09s>2fF8JG?tWSD71HsIr(+`(`afQZ -ss(=Nyy1aj{lCDm60OMNJjAsd)(H?MX!t~Q*Q#Hf)o5Tei?{##%}N8mzGC*5g38 -ejkSS5`#UVx8ols{jk2FAS&nZ<&(-Wl`19jv#GV^lv?o6~!h4}wgvuhzBR2K;&d>za7%aV0fMrbac?7 -i)5Xnn6`tx(eo~yqQ-Tq{z@y-sW03Ws&P7lZ=6exO(p+t|`3}y-W!Udk9^GaylkykQlfKg%&Hfs$gVJ -bPkuU)!-B71 -Ps@7s;YTI_D2kcI2n4riU5L;LYMNTL6?0V!v;$r_bD9O^6h6Fz|=8l;aGWiUf?T*2pX}eK8nMd_dTsKj8keyIB^SL{Vf#`EQnm=g@x@X65Dt5JSOW3i9*-^7&)jt9!94 -u6i-mFLM%WhRb|{f26yZ67ghNsWaXN474@1c8=y!a;gLpaUjc52l3Tt@O5z#&;fEg}=BRbm!eMHPYeK -IpGz`dvf#MCKT!|9Piog1e=tYeJ0H&Vfk6l*BZs=NQb?51lp~D|WUVy1m2G#Ruq!HxQIIsW*P;G(bdY -2Tv%ph`e^WA4cDh!DxU&6AYi*OjjX?0oobwkY3iPX?v!1dgCc7rH* -aF_CQe;!qZ*na^iZ{M`az(=!eMFG7nqy3xsQA$G;-YFPr+66#M6|5|9#0?}%Dq#jfSvC -Bnfd3dfG0W#V-IH(`EOd#1|*nPc8`*22NhK(X<%Ve=%97ajsa(%V&&v@@&-KMw=L+qXyWP#RQp@5O#L -%A3$S%zM9ML?8`;XPNyP;jShB>$^s& -fD*)&&PQ*fq*Yg8qHR>RlmAeZG(i>);uj&1nuk5r3T6CEvM{jbJ83cb`drb&QoASy -hp-%3Y177kpyEXZyLVpvA?vBhRgR9=v4OOLHhc!csSFc_?P>10_Rj?1>uyn&df*w!8JcrsuaR8#F6?hxpHyW05d{4B1rF#R(q%(Gxct9M(pbKiQC^=d -SzIg2%GIsLpj~F?wdIzXy{>pLSOQfDo*_&4cFC`GIOm*q9KKCQAq6|o -W;%gjddQoDL35W(e=ZLwRSl*>yX`fV}q()pV5z6@2WTLXnMwlzBlW+g9oQCb7}VpQsj2RJQC-e;IWpj -#e0>PFt>kD_80J-go*s~fzPN>al(7aovrDknmySp(RUjWhE$j)TB2RyZjF{h%gr1`6{Y$v2p+w=qE>21d6-14u7y1~wUTE4}?{y754+P*rm8pmUc)VCu4g5CcUe#c*hXAd#s1;y -PIb<1P(JyVEF;$CPE@e*nfGb=-v{DP7`%|A_2y_4{r@r5%c04OBq1;?fV*Fv9oh5+iF -_aF+vw1n6P8#0LWTn7z6*esItBl9(1><#A-moSEO3V8RVttPE3cx7eV08{543%t}7-{Sf^(JDQHo)3f -@c01pIlI8pD%j=RD`WEw}P6WY_L-i9J?0=8-EhL(4ph^*{IrTPT}^1?TG!5{!9Mw&grm}7}hP4o%U-E%iZtK;Z(F`0B~Y=Uht-nm<^i_0F@50gE;nJdZI&5 -bDpB&k3DLsLP{kq -?DHU^B@1T7=5gJYx3QJR?5RXv{cY~pY#+&Py$jgO}w9wwUy*-PCfUaUp_o^C2iFKzJv4nl$R&|6_bQr -OxyG=(XX#noMM(bc>QBIeRR!^~D<(-N=QZ8{+#cf-=9U++`%3X9k%F>`mEhczM5BzndSmk7lA_@!)sw -7JMR7LMq_^_Q4U+t-#*?60ml_HldgOjpoZN7h$cqVzy7YUs}?L&I(MUd~+1?ajk*WnXAPZr0r%d|y@W -xY=hE-G)MeSX`v`6B1rZ(S0rM;%4$G65ucHX~k=Y`BK7u^E)Bv_< -&kmYQ`MefNch-bD#5R`SkVPJ0)9q6ftcnsGT(17a;)7J>Op4w(|{}u~0!*-daGNUDtTKN0g!6BhnJc* -ZO{kyy@Y_N>2fG7yuO#-50?MNRLdJ|d}`V#Dwn~Ms=H<}jIrdv#N!(Q~kb1s7h2Cc{-n2&>)H%<3DCi -|2<>`mF@E%$DUFSjER^=&YUZqzISLNDXS!k#Nwa~a-iLIi+R8j!o?j*j6P()iru@yoW%snl^JQT7;uB -XXB!ow(7f7L+;Ko6U_>rBFC8VkWNe6*~Dj2e-;skYf$z3;L{p=!nu#t?5CcZxtn0$!yC=r#q-*`|EKq -Z;4YQ6J!o-h>R`GO)6CP+S;lFZD39G$#c6?(T_Y7q`Mv;!79m2!9mX -=YlJDS+=cZlKB$Q4<|$a0{jhfE5RWSsl(b72f`g#%EH$YCFja)M5Jl+Ieab>@Og+tD64z@orLCM;a>U?JA -cE(wWlvJAQJk`tP}RlX^Ksn2DLd2#fV1qIr6yei0hD@o_PBkhV9x|46!Pi=z-?0G;nf{ -Kuok0`lC^LgU{;j=CBBaMB;X)1Sut)uYKI8K4Nk7cRB|}F+Na6fE{Fmfls%4ke9g^9bZu9=xfnH)RsSsX7BK-Ut$iroull6Ib$ -0kvC9d#KD3}2kWK8c#?yqOn>C~zr`=p2mGvfkLHsn=d&;82|MBUlLBRh4P)h>@6aWAK2mm&gW=RO@F8 -JFA004s`001Ze003}la4%nWWo~3|axZ9fZEQ7cX<{#Qa%E*=b!lv5WpZ;bWN&RQaCyxdYj4{&@VkEnt -00KHDMHte!CW9s(yiE%6sFWw#RC9j*hM61=hBu#F<*;5P&;T_FJwo3v64GSVBcIZMBhCl=4=E>T7}pq0IVL -bD0F$3;sVCUlL!TcjZ}5_fRC1?@O+QXotj0rT@-eI!3?n!2$dNx{e%SI7)Xu+C9GRHQkrvsQk~8iF}F -)@PHl%)N9{c@Ur`KSv+|U0n%+*MKFKT!j^+nlu9Jhiu>|U+A4pQ4-D-GG~YE+EX6$#WWT6*hy|ZPVyg -lhdRfk<+_8ohsa6se>nt*GJOIw&WIqY!4iQvxakfjz=DTQO@ -b0n>)E;26$2eC$s<>6bIfs~mfw+e-H?S8*Or6>!Y*-75EfR91=;z?e+@2U$Fqo=pt0a?%Q&3Qq~Lexp -qd0(uX+^Q!7d40?e)djKw*(h6ZF`AsXbPP+mqKDnk1>zM9-N=ay(qI+4rzT-iX46`-=pX#g1_?n57%RB>c<}Uv6E=S}?K$z^1(^X!ao=fb@z3$etKPP7_eCL`HIu^3^2;B}{6?L6FA)1cmD8Y$?vz-C`|V(#zvXUD| -L~o-^;FVmKewb#frF(4!SkOVJ_P6{AkwfMFX+UCzOObmQNBPE-qHI@8$QrkWbpI_^wUQ-MFP -hTnStpaHoA=+;Nh&HasN36-+!hCA$>)U9x^(pLArgN2e><9Ukij-%eiU?c}w+-z!q17JjEzs -3*E(p97hPenY3LQ6+o?nGnPmEpb%Blbn?fDiW~ -slxtM=fGp{9?3Y#e3$#Ohkiaj`Q-@VqNpFbZnKg_G-h3)4}?$+X1ybQf{_p-N_1_v^m5jFu#!Yp0%Ij -Y5jzM}rlV8`xrCF9L$<>iByJX}e`SBSJo|mQy0|<&ll3eeJRuzku%%w};()n^8JQO%T=<4@)Rl_u6O1 -W&ub^|;?f&1mJk-i$-tn~tRmpc~Dn0jke3OScQBz7FBU?82o@R_`YC5*}-8!vuac5qSGmJTmTFTtJ=n -Q{)dGh7_>|(iE{_*il>Thgc6CaKk?4AvA&Z8H)B%jHO|-v5?-L)O1N5CTLt-h-TgtqoDKbQM1bz!bPKn@Q7iJY=<6$y)e4X$oUEZ)0$Ub#(7$JC3)z{|Cmh7-L~nS8?-)R%trE6Cw}WSB1NsTy7|Io_KnatukQU9 -V!--C#2<>I@ft2hz{>>E5%nV`yD@S8PtjoUTnjo@pwp8>k@T;C$A623I}+SKem}kvk6tZ;m^6M&F}1b -NiLJ)0Zo-1$twx3-P+yISAHI*7_-!@AZIFC`n*lCJju(1=VXqQ>x90zE)BCXHBcPxmUaTEwC`N9LCM| -&=Tr>obeVHs3aI%kk_J*LRkHZ;?=DC$;7$*28Q+hxFWWGRxzLb3s6e~1QY-O00;m!mS#zzeV6bv3IG7 -yF#rH60001RX>c!Jc4cm4Z*nhabZu-kY-wUIbaG{7Vs&Y3WMy)5FJ*LcWo0gKdF>n9ZrjN9U0*TFzzC -J4*=!zzm4S^fg@LYPWE~)d;tFGNB(tSRfirUKZu9Rwb0cSlhZN;a`qCv5TO!Z>HfPSztGe2d#bUJ+yP -7Q)B;RbSS`d22t70eELVr(4mOtb?ugZk%%6wU6Y*FNb)wJM~7i9ZXpF -_?Cif7G$v#dC@%IRN1az7j!AA`pNv7OaxsoYPM#NTQUO~f$V0R7c7o`kLUmRdi!enHBMhmqv>Qa$=Hg -lnK-|x8GEDvi`%Yg@ez1Sh_X|+)w!Js}`YM#Uqo*Rc}Mqw~D0k!d>hsO78n5y?4`>c8#qQ*ueTn;p2!Rgur5d$B2^+r%-1htD7qMoi(iGDxgUypc1j!5j7 -NPhI)Su-eqWs4gq$C@5fU^S--rEBkiR6=NTBOv>NoB=Vx2z$ -)A$jh|@N>+KzMF9Tk(WF|^hX=I*aOQ8I6wBW~-A=nT1 -+6p=F+jvib)z!4Rxe6gHz$Z3uLTj<#zGRr1`>m(P3_(rgKe4b#$Q0jitAJBcy#_2TP%i_6X|NZsDqys -m!9PgTni>qg2e{K(cfd@nXgqP2~-QrcTf3gRSMfl1jD;MJq1b&%M;c-lYj7`T)<@+F}9>Z& -VfUS{kO2z4vTG*!=HgV!UDFTQ3TD@4urnOf9rQ$4UljjXZu8rcCGC7$Zf5F_6}$QbD|Drn-_9_ztnn^ -lPz2{E*e0&KWK14q2{A;An)HQG}L72J!%i6 -rLadbel>#Bb07nM=0LH;>u$SOjpl4Z5jEUZ`m?m|Bz%1wj-sbh~BpGN{A43Z9xsw8fZxxmNd)0%sqkZDH;Th(W2ObT%V8D!P6ck*g -*)S}r33qz4#}5MY1=2-HZ)7O#^-Jof83CUl!&QM%V{?ySj9qRjkec+w;jptJS)$W0a8LBFSoswrQ+lf -yc>m4Swj(4w#{VGjLaRZ5(I(wACxKsCBZ49%Xb4obZL^{C|xn2z3RD$jNr{e}eI;-lWzncR{B5@FMQr -rHwA!1MZ9wcOd!Z^llCS;Jb -l&l(=pZ)^>RcW4dA|DH9hIEg15niOLaVr1Au(0}~({OV@$;r;uY2<3KB)Aje_dhy|xzse7mvm^-ZfcT -BoIW6*kv%1;Ex0LG#q|$MVVdwk?FHzlQDQ2(n-41NaWgF6hM}n>+Y|GP@b|=j03TSl3{ocrO<`oyT8^ -E)8O4_+c5Yj#4DkswhNcOdqZD95=9YC%h>~J88T6M`0Cy|_+$sZ^1gdFvDzxs;ZLW`F3LndyHRIW@XY -LMq~3dpJrX6QvfSGpV>ZZ9&EJQ2D9I;<^o=>F%3e~#pTrNK+`lDs0Z4sh}#c{5Es(ul1*wV;LF&z$Da -KFMYHysoP{j%-=bQ>`T}CH_5q&^u~e-)@rW)*nKOmYEuA`Fnz%_&z|Gc5e~wcP)M-I_P2W#Lqt*AIk_ -oK0a~Wp}jHa2s~h+=F0uMGoE&&OxmMwaPnu=I??&#Rvo3=WA}ut!5l|CA!c^1ws{U!H(uadcOS$zR4n -i12C+HMPSq`s$^`Sd#qFKUCc1_EveM@dID>pA|lmo&rzw -uJI%>;QM$t3OC_2A%yw2x>+sCYE9c+jf*l{KyNN%TzTKg}C={(7oFDeF^b<#AkBle%ZI!xQA4G7rO7e -xLLRvdDEFw>B(*z_E-xabdL(f_KpMn9X5#^snW5p5|DCK>ThRXJB~M-pXhTLrT?t*GOi!oM!M1w)3(r -Zh7r7fwJUd=WycQ`_`&yvG&E~X6Ap`X)BVczIv3_BOD(%xPEaV(yxbEl+ow0K$P0OVJZs;^N-xV*1-q -RK^(3B-Yw5dsD%YP~x$4S0#KzTXdExasAo-0tec -9$8aogk%2NixYcXJO8g@4fDgIzLNFYi=Qt?DcdJ)GquE=XRU6XX{g4|X~LVvt>@zeWz2X?62Uog$W2JwKl#p_9&$z|Vq -+-*q0c0^AU-8Y=oX^?Z{pytF*S56h#Su?710pwmcd@ggnficosnDZb7m%2RI$EI4e&5W6fhAix~v;oY -$H?Rla&i1;WU<|4!r+5cstll#?^~uU(7du8qCbkeI_62h!LY!H(H|!4-;@ -}Ssrj!2yP)h>@6aWAK2mm&gW=Y!qO3-W#002=p001ul003}la4%nWWo~3|axZ9fZEQ7cX<{#Qa%E*=b -!lv5WpZ;bWpr|7WnXM~ZEP-ZdA%EJZ`(%lyMD!%h9RnzSvOw}$W;Ln+qniv(;#uKSGYc6O|C4~6shBq -c67b`_nX-lcb7~0f!qmbBa`#qnc11yRTM=(*LhbmvaTD#yWOsC+M>E9?S_%^^R`S_+c5ZATs5@WC*&N -S7dfM3!y5R;Nn4YSGeSApcG+fDuLWvF+v3)s;oDu!d2v;;CAllwP2IJChK$w?y)J!b-MfRXeNbmI0p)ZOK)`=nc7I?HywkX#j$9#-&tweOGZ1jR+AyvO*!<0YzF>bqff)hH<1x%hE_hP( -%J#!gnky)06NgSK3qnF^i&THd{Afvou|IZP&0g -CB+uklF%z&mtD(J`S+6K#cjchx>}O1DzZ9fX<4+ap(URkklh}nZlC7`r=Z;mlx$hnviyi<8RL95Q&jI -HXRiNlnEuB1T*;DX5w2j7)T1P;E8bExl6vXuqTuvv1Mkt4ZV84KU(&0tD09|uA3ZI=0~q+Rk=$}>jXe -W_yRtmTXDf2vbZpkr>$G9l?DGzuGz`Sv?!Zgq=+F4#pIfAD+neh@?jiNo= -1xWr~!|xq?{92`wrio1AJUx{Q>4(Jf9KxChQuZcaqH$ZceUmE;{2*Y0a -KK#nk@G^M!b|zn>DekMsq9 -m#_vV7t~qO87bOHCJT@Miys_WJkhyMw!f6f5$WAHWC?Df1bXZG4uDPB`o|^X(Smp5V>e$k^SQAW)8_H -?hHa%zrE*n{J3U&ymZ12P3=YYJ1s1n%~awca3faW%l3m!)Yx$-p#DIW~@>h%ODv{wQ-iZvB2PtQ+>d@ -a3VA5jKDTI7(ggNXX$Bg)`NyQU0isy}&~r@X~gjDGy)^p}(K-`<~*L;fNnhXaCWwbTh%mpx|>;uATXN -OZ^}@xK@F2DrwLQqst@d=#27>f_KXlLqlD_xR5tN5w#&Ihm7hNi1ENd`F%wmf9SPAsI{tItO5KRuY(m -F+jN6U@c;!Auz^XQFlCEU{QgNg-I>EJ|4?)QV-yLKes`!+vqytg_)irtjMJn{hfD3> -{Q0U#&9@1ejLx|F;fU-x_fucC_fnX!zv^jC};aVsZ?ENq4SrJL|7S;D1fx^%G7D!!#{w$X*2sXLRtU`zx=Y?*gp2Tf%%yeq6S6l8MU) -ZH%rVdySm^*pdZutVBe7oW+hucs6Fi1CYxU*2QxA7Q=KHs;w|5}D5X?#HQj5-ivMu`S=ho)e>`9u#r| -;R2F9RHAWl>;+t?`pRI1d>Q{Nu0oZNd;?7w@9RGMBVt~Z`nR;~m=*GgL`|SW+rpkPvgH`39C#s84X6H -xx?#^mwj;i`g?2K7BjLwwJT4x79_zIFgEqs006X^;RzpM;56WA~INRyE%O(62wR%s`E*x7#)%yG1hM$ -0oaA;>)9`__i+@lYdxu~O7SRc;EiYbdp!0YY&{^%s_GMd^d6r8_>qY*?CHn}MdHbjAfa` -o-ErtwzoSeah+qtWkGl>AW_RovW~4zrd{GRM2dNHbu*o{VLJLwlx%9k_wKHq)J{(J|^SSo -5)8sa{+kxcUYw8Pv07ipbfCbL;mZubG?)3KE(kUvv1#|SW)=o!VRmoL#}H!JM@+=ecu*PqN`adR7_C3 -=IAt6!n5ioFDjhw>anJ`ZdE_=RNL5O1etx@(r@{odH{*zA`ulM7Z8XmD;n*Tc7I*);LZ4QNX%7-gftS -;4ol;QCRQhF&c0f9^hnuyd_S@-cKAOrhX&NbHiNWN*JJ;Dm38LSd0jG+@!E@d11(_Awog_zFXkT?>Va+Rt6Jfri8T;w{=eNo-P?_oZ_3AZ+5V3NuYE -Xs^3#6Ab#E{27v3P6I(O-H;^#_$=M*6j;m%VqYY4$)zHQpXP@BL6N{!3F1#d(pn-u4r)sPS15?kiliK -p_QAuF?UYD3m40iv0N{5O>zty5F7#a42-*i6~PSCS&h>rmCx>9c^%=fCxu;cE$1mP0S28Y;V_&V)3S$ -3U|ySu-gt?bz=Of%KhliL;gio+<~iVV%2~~tjG2`ZP(NG-=6Cp+F3E!}Mu2{pwzDYx7n$cTkoF} -I7gn1E{0KOr4b3|BHV#-xE$jqN$mt%l24CO)%VT@AtceL%?O}pgD^7G?t!F0V6>E0Yn_sD~363= -~6v@#b9q4-82z$#Nkdhw4oXN4cjz8-5*yE5F;S`@IYCXyF*!heq)$^PSQvH8ejc=e@+r=Kh9=~)wIDB -l%I$sQqU*y3da9PC@*v1Y0pYf3kRE#ImymJ1`E6fCpSK2G34a|Xr@(X> -vB)Z^#?VoY^-AQQo7*xF0SwG%pYJGzZh+0stRx)^$GJQ#JJ$wVwKo{ntiHE7V@8k@%1&C^)bQfd<_O3 -;Y-SJRsJ#vYnCx>gRFR6G|fZ9p-FkaJX`H^k|o%9xN&zB -K%7RmaB%nniekFo1%m2vrumZ@3phPJ?cKl#}S`JrkPaUSlTdcc;>ElxMW{4XZ4Bsa;$=zKHOzs5hH+* -GU2BCl-)(-$?=KC()%d(!l8zGH`X&_udlx5=VHMY;d`dunTFC^sl)2^S@!Zj60yrx%oS3hhYnT -Z#f5=*)+ac1(bZSqS$`oiwRZT+-%gewHDbjwxkN)c$+es9_@O*-W}U$>}3Vfj*b`gp;hf8vC -`k2szeG_mPKY6ivqE;Z2RXIfMbPzBaGR#LB4u3$Kn3vbl-q_m5pA1*^v=fx9Zgyw(1wuYA`^d@q}_H~ -sHF3a+%0OB2N^0Bqo$tGjrZep=*sDWKSpl;!G6GhQU!)VKdRK|Yd!kaoT!OsDC#pI=6b=mih?(ym~DE -^Gx)y)l**+mQZj8}8?61G)S^!^HRZBe6Qn1*rPY~@r?T#HsR2%o2G1i>fbdfkSi{aA%Ma+W6+v2(nrj -ny&UAFh}j;CEb-!vY0IM-s0Qx0|KHb%zqToBsikLqf^?Zc}6%b-5ar)699*CGHA^?{Pu!kLt#n?W1-e -2QO&Y0LkEdMN$3EJE6hR$21~_hXY|Y59=^|?2e#=Xf_lJF;qbtN#i+r|LN7+Hz)G4-kcnc4!7VFkLLL -I-Bpo~C;=%eiu}tI0h1f)N3Is_d69eB+MtY04FyuNF7WVR9xcd=6*(5?0M?6nG?y>N2YX%?(Tgj&>wJ ->c6;u~ckO5@L1eaWln90Ao8mhgbG9KUl_PwR0V4&~wY| -=4*)S$;PegIo6afTIv?0JW&94m6Rr?y3x{uE)+wuric!Jc4cm4Z*nhabZu-kY-wUIbaG{7cVTR6WpZ;bUtei%X>?y-E^v9xSZ#0HHW2=< -UvX6t)aq-D5+&Jnwl5QqSQhV|d*O4(ljAsk4w>Y -fW`;=tat(#dD^4}6r2=NCB8G_#n5~l4W(zr6uc@NKz>bMrqe5g9rBm2Y(a6dj6&hHMVk{#ZAE3U}6iS -8Q8O`B#k58zVWPei?d8=s@EhS`%5<`K2MTUFAcT_od2nc!)W?xe4sJFY96+T5sG?L4R6(y&I^)FPHw_ -h+sn4mixg_pSWhhITgGAZt?uTmuF7BMY~Mwlm>Dn>ZE`2-4zfuJq2vw_wohz2b=eyUC7O^ZBI)Kp41D -N*MUBN${Ujp8_tqP3C*tXAs^mFQ{(=zvrRbc?{1p)3D=is`>&8vUF?C0HhNy5bI->l!0X54sqXHUvXB -EUYTSxIQfBbWJL5R@8C@dkrd22N2=KyOy`*+)JNuV$qCH#hh+?wRjv)HL5V& -V%3%3>HX2t~QsT3dtiGVT!D!;Dw#Wi3Q~BkS+ZI*M?OXWs+d9=A)PcuSdD35hTYL5ZQ$(n!C -OPgJHjh;iYPjd0+E?d>KvA60<)Lmx3pk_;U~O!`?{BxzOLViIXw5&Vp+FdoX4TQ2Ku#>+Ve61>s-_;S -oIlaJH!x3vNg#tH#;Hkg)O#wsdg^8$55_3%F>l-*7;`Z11wWg=Y20{7?fkt9Nw0O6OsvuT^t# -j`VoxZUOg^lA_J`s~{CNX};~WIr6QpyVo?`g#I=5({4!USrFpnvQoB9@;#8UsCZagS!R2`fEwvm0c`q?gc*iZqc*}Sp=&+;Ohg}4LWC;+ZJY{MtR@pVW& ->m^&)4R_TuMR_v|`hyKQA>+iz9Q^Pw)=A2|`MBSu)uz%5kQjKV9o%qog9oP#PI+kGUJSA+i&qrP)T+j^(Ju(ZO_6FCAP}W -qa@s+fjcR-2z)lF4duj;_$KMlNB8y^Z4W?(EZ8&3bNJ>RpF__{X9b_e=@-ewWPq1^9{`@nV`$R_a_CJ -bJpIoedLH=sWa3YWc2szWR5E*Ti`^5^%W2zRt)#3>S(!EzoNMh6w}79A3^~EL!1}}3^T7~hmSkgtzA8 -$yxA#5LJjS7Or9igASgr&$(_< -D$XGteK>K0qBp3}oH(hTZl;!-bCF0wzLFyNcZ^J8rUu3G@OX&!9sUqq$!%^fPBKh8UjKjG8*2B(zwQ? -WYC&(%Wu=^B23t;a}rG>W4^6MVm~fja8B=$B9pDLPV?A$&YU);X#8ilO}Id;1b`8oNrS_W_2YVLLHa+BlKwihkI_X&ohuv+PwqCN<)B>^y(*_Hy~>yDRH{Y&m29J&Wxg9wlOXg^`}25C-!QjW)Q4Xz4w+oWg2#^3 --`~`Hx3#GFBO53jZ>(|KGH>p^fI?Sa%pHfi~WeA~?sRAI6oObLgG$Y{akQ<=7eNeo*~Ko8Fm)-=Dfeb -3|*3uT{Shj26eN>N}_)NP)h>@6aWAK2mm&gW=TyWn82b0002u9001Ze003}la4%nWWo~3 -|axZ9fZEQ7cX<{#Qa%E+AVQgzv$p5}Oen=-vmXox65y`vz-3RZE)LNH0OOo~8?6pV|Ci7jX4deG!srE)BZara{e3X?eiw -WBoGA%QaC~1V|s*0jDVtmcjG=r}PSOSY9Os8pCRE8I(3dDlvqT2CPR1@}C%50%TE`UB^cK2e-HBSK@u -#p=A8&Mzw~m}t6I#i9L0M -Hr1-Zte(~n_IEs!!iwyDfb193_5oH`jQL1=Vu_gke^8T|(&3I;Gmx(pxj4Y%{l2Ikpdcq8_dMXawoZ= -p+!B0fxK#7+l5}M0OfIH?Y-??-U*+T15H=+WUUSD2qsIY+aE!cmtIqXC{WAO)}C0FuaVIlBjGj2FgK< -rVqyX=p(5~8@8vSfl<>P#RT(XJ-I5I4O013Ny`3phjI0LsGsC%q8sE`0L4m&fAm`TBMT!M -wpN@@pN=}%s&zP^}W?S1&OrEuDcALXi_ZsX3TV-N3zBT|>n4D}{=2%1hhug`o3&&0H@I&CTGDTjJxf8 -bA-z%A3WplWSU|Ah1BXYkSQg-REUY^ZCA#j9g4_B}QxWcqmfkv7G@ng8`kol-T60m*oP?TSafCd-b3M -9LTw8Pyv8OzO35$It*HNy3k)_=i|nkl*BHp1ZbdMON-sv`Eh?AijDw-O4&cI$6JSwdg=AmNCL&9}h3= -g|4BXRk&%b%{e@12zM=b_uBZ$l9bLJm3>uY+Y(*$_KDV`aHLHN)6bRXiTtj0~-AsvkTF -CXy;uW-ID8`9$00 -w1Qm582kATgg1GPTZud@Lkhw{w-Jq+e;xZRqX8A5bF9fN@WT6i8__-E=Owu89RdvOfY7eHc1xKU0TGW -49rv99|!=|`lgpKM3{rnTU(^>wTv949*@j&{Rsq@H2K3wm)Fuk6WTyrwskn4E23VSI;1M$|SHS2rrR` -k}v@`Hp8p`K*Q(MyqhLo?7pZDwx>(ZNm|gV>qp^oi{cGPqE%>!5@05Me`n^DM4N-XHx=@6aWAK2mm&gW=Um=Z>jeI004yr001Wd003}la4%nWWo~3|axZ9fZEQ7 -cX<{#Qa%E+AVQgzP{0R6RtlpH6*zL!q9_Df8YM)i1tev}DEj -XuX=<}#r{zJ*%$vtI3t5V$LRnUJsuPf9h*fPP6ru;&@J>N?{>P|bMv-m}Rupa4={kFiqjZ}P>j?PbPPDIw7c{r)pEmyNJ4Bb+m --e9LPr?4ajChOcmvesH0+C)9ivuY0xbLyQjJIDM^(4@T!YTz>k2&?no;7=8lw@6aWAK2mm&gW=SH!5HS)3002x9001cf003}la4%nWWo~3|axZ9f -ZEQ7cX<{#Qa%E+AVQgzSZ#0HHW2=bRPB|a*ANidje -*hAvt?68D4y5AXz)B2q7-GEY@o=Lj*Yl)!x*bYu}bW=1k+f??c6Ol&%Wrggr%vDxNIOC~RdfR-UI -DD08SiyaXEHuQa%1a?3@c#-He}27r6MhZiH=!STo|nOrt-#La8sMi&NaP>PB;-(78p15=FPy{;>r8v| -h(Bt*Z+oJ0_lgxg1&d_mW*@QsczQ>kyhy=d9kuS}!SH)BTuQ!TzJ+`1NBGHw^s#Liu1X!Cz_RBSsnjG|#*{uo1 -$l=?vLT9_`GEnd0ou8^QG_JyHkZA`SuhFvtJ%Fb^?Zq#r$7KEFd&zaixM99gA+sCUkc0|Qx3==s@JBj -rE_dAv07vMfuo?6vTcRz;J95u;)a#DKPS-C1w1grkZxKOEZEg>k6ZiH<>PjMcs~1m9#MAOS{=BE1Qrg -EF)$40R$)qPy?rSPWrH<04!hO@iLLx_fLMb$u4UAaYdp*;o7t*-@q@cTPf0W+^nMBsM#6|IgLI=aC -lc7rRtcTTwS>2ADHe38CrDQKmpH*y*eiz(aw>BiO^>^dstMjaf6{9&=6*oLq^?>t-%XAHATVow%eBWe -RFQpHCD{S!4$ipN5Y|E&Sa#Hc@C!JDRr#tHfI~Jw5m!Iyr?5uBm|*7Y|zMd9qkUM1Cg<33>x%~{{aq5 -+|npo#5P=3v{y4JjIA{w2yq+Z{U%5LBb&jvj0k%ekeLNum=@jYOrPq|rDRCyWu_tT1^?cJIF;Hc${3CV;+o(Ba&G>;ZtlH -Bc5v!$K+|+LeJMZ(4m`3h{!XKuF&<#5$=%XjrNE$Vxfus?emaTCcX@CYM=4FW&=+5{VJQi9+j;sa(9swvnH+urF6~B*|x%V{MOWwEa`6a^SY`f<&7C -=boQOb18JUkA8*5sj7;0C?uIDPsfc0aNNJ4EzZ6~UJpX5luNp>dF_u77w#7gy3q~%v=Qw6R1tcA15ir -?1QY-O00;m!mS#zo4V*`41^@u17ytk+0001RX>c!Jc4cm4Z*nhabZu-kY-wUIbaG{7cVTR6WpZ;bWpr -|7WnXM~ZEP-ZdF5DJZ`(E$e)q2+9CVd1N2vFhEdz$c0Sc^X(RpZ!zz}GOwzjhWR0p#dAI$e&dn+bsr-w;W-B#%YT%p3d&1{CD_rGCdUkb^VlQqeqNft+=x2?uJj){#R*8Kn# -v3#L>ioKI(D3fAyMO+&CT$*7d%eNm{OSpUHiRb!*=am;hB;y92j+suG4YhQd7v5f6mt~6b@u1rjc8ed$AkMKK~jvB1C_HM%R80{0@#Rix@X}xfh -((Z#*>(*-v&;eq+ZpSt?$v-+3D{_#T@eIBqT!#r(Bi{ -t`|OIe`+`XO{I8sJo?-V)otNgTmd*!3w^;Uc(ZoOFj5g|h((KaMzT$(WvG_B7zL6;-sM}_M9?tyJ37)MO4SO`sZ(||a5iHo^%H^06 -f}|{xh8Y>yiK;%63?jgJ(;i0P&85kzr5lCDZOGF$LpfVMk^>TwtTuwmK2Ej(kd-m=Xor%To&3&&7Awy -isyQQ_DZKzC;bxUw+}S?1o@6LZ3OneXGtX_Kd^3Xya?d@XYF-G$`g21-K1R7b;fQ510|lb^!h*r$5+> -SCbxs88*~wEMlvpyFUOh^a@SQ*$&^-dyL&y9I>p+R8p*9o?+dPO>w|6=VpXj)7w>5W*~!(T4K?28a9567^F%)9XX2-FbwWFxC$*Pm}1?;I|Ujs4d -J21lmwDQUs?!sFW{Wv+3*bwPEt}opXzSW+kL=|M -sfKv#~l;>2ONPU@g^!8LGJII3WM}SDb3I;aOZRl1AII$2;ZYof{=m -|F46e^eH*VD1>CHr(NSC(`qzRxGP5Vlq3Z(AgTm -95EH|^xPp3`P{e99_k6okgkb`}D1#OEfr4K&oyzgKxAxYjX(Pvwvv%s|YKLn%2XRl!!KWu20t!eTJqi32IVo$RvDMgVm2_$TXBL*J9JH}tQOn -RJK-|@o9-%v{f1QY-O00;m!mS#y4Uu_V`0ssI72><{p0001RX>c!Jc4cm4Z*nhabZu-kY-wUIbaG{7c -VTR6WpZ;bXJu}4XlX8Rd6iYcZrd;nz2_^~?l5N%g6%pWhb+lqz>;>WfpsVfL$&Etuq*|ZoUR!5?-Ok& -mTP-Wb+ILpk9_2llomRNC`wCP78pe!^1UuB@CT!1X)&tj3=(k=Mrg&LR3g?1Mp9TTxHOYVN*Fs-=26U -IUEmSq`;6_kI`|S#F85WVS)IQ%M6QvkY??C`gsCwB$ -&GLQmLXSFeuYzoyI%(Ql=>uU>_0iKtaH0ctVJ|f<562Rc)qPO8q3%4z$9P|GvP3(4~PnZ2k3A7d0HYQ -_PulX|-`_h_rPV29&lHM%U;FW_5j_$0EHSN37cory=8lbc9`&oCcEj6F+sbaVDUKwo?WE_Q_{LCGNOz%Nax`c3#qC0P}e*)^1$OcMQ0-g?OOC5J+dA1nAb -C*-Yw~OPco#Dup&yr+LT9$^3wY#UyHD{rTIgTgKo(WowN3C30lkolqT_7z1Zs9rzI;^n%_6S(_h&tfe -%)cL^POC@9TA-(5c1TwdM-cP*-l?dbb=z&XbQ^Sylz`7ZFt(J$+#$p+bs3= -%kL4O~R}-hI4>i#c6$ZskotQXo=_p9Cac%w=NSdUR`UZXCehZgIcLI%WT41*rWS8qGl>l);Y8?G0jd51@{Omu9Y_ZWN_P1EHO -j6LqX_j^Rdc!Jc4cm4Z*nheZ)0m_X>4ULUtei%X>?y-E^v9pT5XTpI1c{qUm>KJi!+zF-NhcT7;K8|URz**-J; -hQ?1ye)D7G?D$F@>Qo=pDzk(6Z1d7Jb)hc$|su|!cM^^hXzK4G)V87ovNo0}`v+U4wL97&SVXp~f{xL -wLhWa}-Pv9}esOQq|XY)Y{f@T0Memv_=Ay=6$paDmB^I?^#@Fj=3 -xhvprCctP-(eMO&!l0cY6OD?oUMhP*_9T&WD9O!N;bygn_BwWwN?dkq-ognqv1E=DoLI{e@=q1IYZ8` -L9ldeR4q`%@r35B-d-hHmN9&gIO7G0oK9$1+!tbP*^3GV*diNI_6ycpg6cibDI0@;6;Uzn)@0+D5edS -9_B5A*H-G&#wrn9hx4@+`VsGJ%vP3mb!e6ACnVs}YXPQilN^pbPo@K5hJYRE=iwHK-u4u -lK@*y!!vOG(uC+ylHX4Ea;GOHZD-N-_)rB*ewE5Qt}1#>V0xsHf>004f24=dH)tXQMKL)yj)jr-lgfl -irG1Z6leI4{69ZeF*Qm22<=D{El_o70-UB7jKM7=SJc^Ga)_!QBAXf)y19goD>H1?p%IC!{7?e9%5e* -6^yLebKoNti)%4aNzKwaT|Hp117=EJg(VFRiz>0669iLJ<5R-zObcxLt*grycWR7sHR)`k*s$@2WQ@g -wQ!7RcSH=^a06#&gD(x1yixWKQadL0%NMPsb&DJ35>G9jep`LeaG7J1HOy;d5+;=-t -^wIX-#N#e(sZBtmKDia1&_m{!FZFqBgfy!ZE!*v6%CP|*KKb5|9IDY%;?az!iC5YS1=;cmmgZ9BV*)d -_YDqD!-iCmImD{i>8I_(IiY~;1f-PmYyMds=#vf(^9=HAo?8aTQ~=H_9m8)5SXJac~&N0J4C!7lyW8< -;3Tn81S(wDX4W@&Q~?wH99oSRYSgIE;JfB4va~dF{+uL<^8*qj?IMM@8sZOs|8_c1WG<|9w;sN@z^A!{mBhRo_4ie`S4_W#Ee1Q& -KoGLCd`dr03n|Fixt0deNlpY{5%t;{4COu+cTwgc9lE{u^uWPy)4>BK?RfT@yu-~(wpoeB-8KaP3avz -O&X7w=OkP?8`AG?vRRiRZt4Kw83tkK7sNzM~Ri=~Ze1!&(Kcv$8J}M8x8}k_ro(~=a<$({#KG^LMr0< ->bkm&A$!O%T8hGW;wIfi$eJ%Z#>06E(o%yED4>Dt-;5N-dR9?^xqG~#*KBX|>JM(otQ6ezk+jbO43xA -Qqr>h|50KN91cv~V%|d$n+QLu_H8f@yh|Ts)|cli*SG$=|QI7A2x6Rf{j8uwEI6IgpLWM%p}2ji{DWS -ZG&%79SkUa18EE{FQX;LIUiTSr>!mZ$Qy#c@PJm7l)%XK9}rz8CXz`UP~})$x;A2ahyUzBQ1ho++pm| -TM9HGi(iT8c8u_{$NM>ZHpSJ4ZVhrRyW$p(vDt#=g=$Iz*1cXv+&gL?r^Wr6N>|LPLf}IcjFmo}7Ldc -xsYYC)uzgI_U*;(5fk+8SV=YR_t$?anF5_LJ9YX%So6N>1(qZkohBBmvoD3XyB*P2T(9E<8!q8~apM0 -nW@#K%3^}E9n2%qbL#8JXM-Sy>16*|w6QhS^q5&Pc?XzmFB{04Al+BIg=hXogxBKHWQeNsem^bN2bA@~jefZ>SL{MRAF&HYG+CaL$?_cj!dyDfr@iw}26rsQktnA0k#XmqT89>pS -&Kq0W#Q8xB!Gf9C~_nUO?`?X$~m?H&WbTRjR2A~ZUmf17wt@C_vEncTL=MZSnjMf$vQ=x-3AARJLUxZ -IaG3F*$hfQ{O!ouKiP<=c20PujP(VMtNXC534`g*E^Apj{y+nNN7HiIDb|}0AA6`i^QBv~9v|#-xLTh -s5^S{fZM&Z+3<|C*3CJy#uk7x?)3N=x!wBm=jIU6iLjIyvOCgloVdYILK|avN6^Kv~(%5K9lc9 -uUgkY}G?Bbq4?%4c;J-c7}r(fmzWyg8+rvWvZ9}MT`N7CT|Px^TG1hN77wH1sOVv+#o29cXDFv`ab1D -{Gysi9D}#zs5iy3;X8UB%uu6@rItMLotd#LeusPKIZVs~*1xg9El%nwNU5^N$0>Qemab -^+}yEUTdYspdaAN5Z>UQtO4Yg`{@z?bcl%(vAUe4TLx9if>kkGVquccCKB_14Dtk2xE^jDMu0RjAi3y9;jNF7V4GaQKjR`Uodq% -RU14l+fZk0ZQe6M(*=Mwaj&Sk>8KLB;3E7F?DH&=p4PhM?cGSoO!VU5I;p%hPsuB(HpRoCy{op&~mf> -0VJK@`>QAnUhpMej3o{G>4cu9vy@tBIcz3Cw=fhWBNv(cx~U~$L??jUV(4XOnkKH#jxE4&7{%7v_?-T -K1nrAxJ1gMed=*x)x>Jn(A=ziIf=fP#ciWZic!ViC}pRY1}~HGBz(y`7e2fn!V_+ahwKm?VoOfK=|_w -xeU7d(gOp*7@IRU6SMh^VUf74fY+-A2INNCL2f*n|MI>Z8jc$jrd!Q>_mBFE1Rz%ig>`xL!xXbNNngK -;kQ}Zr#-#`-Y3ano{)eaIe^2(79B2>W)3to#$HNUJ+!JO%?CX|&4z -y$64BBDr6^)@yHUrqf4H!PZDuXzNP#iwC9uM}I2;^1enxmaN&S`8A<&(a?J`vi8U44KZj15Ctl7w@J{ -=Y}^Urc!Jc4cm4Z*nheZ)0m_X>4ULY-w(5Y;R+0 -W@&6?E^v93R?BYNFc7@!D;6q(NT{nOIR>zc7HEI~c^n#`M}f9hHW5m6NUDzizDrV)t(TqXVw&O(XJ=< -g#5g=q@B*h*!9EwvPt!PNf`f_mQY0-LeaL6F<Mb=x?@~mHiIW=?B9C$xVL -}&iWh>Dm6-+5eSQjsEZh62I0%B3U|PciFJB!n;kJ9lw=OyiLpuX(wL)5IBrwQn9aNf^YhXqed<9!2x2 -Vh>WDbJRqMJAgT>N6O0AT^<-EA(lEYJd7#vfca1GBO8F`M5pK?ECqexnR?BKcNvg^@;)&zz{F!u&~=O -*Zy1YJb}ihNzb=B&kaNR$*GQf}{?2%P>w>LTZfSHUQsOxHWM9{97rhxk0 -2^o8d34!2I8!G@{K8N^D$P10FED2ywPPMo2J^>H)SGO4vk+~w=5^!#U}N}ifEndil23BFDVz$4o)5q5 -a_4qJ8pKi!n(#T?4EfdaZ4jj5YhE(pRojY+deBZ&#qMieGY -3NC|Kg>>39Pl0&WCH6jIK~fCsjFkWnDH!@0p0GowdAWmxM^>jCT-H}(XyC!#-g!jn+LC-j=-Qu$-yli@DoEi;?xRHv)gJU+qBy%2>L+7^cSN$~#v -JioIHU9q)5+(zdPr0;NjMXdl=$#t*MM>FiVx31{+!pwEJYm$@gP`5&Rt0)vm7oxM+(LG{&n7FzmxlX- -IQ9O1fv^RC5tqp8-9n%R#4NnfYZxVdx+UTLpC${rn1I>1#{!CbslooWkhXN;d{zyyp)*Kzet!M{Dda` -+SNAnuRNm=2LsMjW-qJ9aib;d4|lR{1fojKaw*?YuyNuGOkOh){2I_ch5$)v{r0Z>Z=1QY-O00;m!mS -#yICcJ)g0{{RR3;+Nn0001RX>c!Jc4cm4Z*nheZ)0m_X>4ULZEIv{a%^v7Yi4O|WiD`erB>f-+b|G*_ -g`_igxV~4?Q0>0ZL~00w?X?b7{e&BylAndRFc=`zu%o?D{*XR3)4#^=kB{--+^gFg(pyR_R@aLQc -5gCFVs)Ve>pOyej9gWp+IWyC%!LSUl+l!?51L#b+fuJFTs-zrDSMiVQD~{JS)&=H$&~dXDgc5BIFX@J -6k6!DlC<2m$OtE?}P}VMz&u>E#IEYsFR^K{zy0JU84+OID+bwCVs$*esj~EAtAulU*QnGIA*UrG#`2W -)yr*aD$ppu6Sm$nVFdwX5c~A%2=}S?-;%1F#a20D~c4UNoQgcTRk_J!nO<(ylFpUX1bjy_ge!}Ss)eQ -SoqdY|8IccvdHOMi{buH_T`N@5?CDoMXz{}{+&4p206GY= -LEKm4-0yKf&KpN6QkF1iusx}6u!O6%mmFmrR| -il7o(()=0$F}=4DZc_J(etuwo4b4^DRGe=Us=`ocI_?Yjpqi?Xm)76DJcR4&Kvx_n{G^eL%&2iP?pfjA4@q3Q -U*bZG>hFS+oj>ZM+HyH)u{p#q%Q($el`@BuH2CBDCJcJbD|Fru`h)gF;#1=|bYXH|p5&;1eQe=9)YX& -}Lp{gp!>bnlym1lC`8QW*u4*4+OKda(b%SHVG8$}TCFEvk>rh75W3xEqY@d*$toh`QhBb_0Lm8t03!eZ0B~t=FJE?LZe(wAFKlmPYi4O|WiN1PWNdF^Yi4O|WiD`erC953FqmMFA(sK?;U2Hj-=?DQL -Q#>Ef{lYzV@aqGXs8p7ZC#7ge*%1!be-I*m)*Rw`Za+%1&6XpGBkaI4vp+r`YO^PK4SO^pmrS%R -5L#4->mu#(GCkiUs%I|<+669FLfQmX{KfdRw_aK^zo!@6B?Fi!GwB{lG4J=0!>v$HcN!)Zr1`$iQ48Q -LFz&Ngxnd`U$H9s+#YI35|1XA_2EPvCeot4L3;c#dYa&P1oTstks}hmrydS;4SWmHU>NYUy0ZgD|xen -j&NZ@y&R?;%kG>o`@C_orG4T@C~F~KSuSY;0jSM&s|o>H5KlOs#5-3i5QIy%AqBsYSe(|#UKqqwnRjK -`;)LP^wInn3AXK0QOBoIx8Yq22)(j~j*%8$kZM%(Fm9NTCeA5j`nZ@1W0}mF!@-r4N^5Zce9aajPrb8 -*XWH6`p9692#h7m>ebO%R3`x~&VDyNi46z-#{rz&pydX&+06tX|;?Reo7c0a@i>G_y!LZqEsw**@1(X -d6jIx5;WFg3(YVxuw=8)HoRTyNl?C2vJydX7++1O`{l?YYwC@gx-n4NmJsvW7N4%?la5=~kYCk&H7u_ds2cQ-E|Jyi?gDMr5^5nvr&WEi**Wz&5lhSN9xh3> -kgH&28xjP9^@s{8x&^&NmzO_XKTNlGl|wQpS$_1Ip$^&CM;QM{(bwBpyQOT*=i0W>(D@x2ja*SU&F|v0AKB&1c5%%G)a|}faPL0#WjuWDMW -LQHvmWrKH^{oVugjv(gS~YkRl+HBq4KS+tHozKTjBQ2a?sU*(`wxS2Y*4r+E@BC+5vCZ&|Ubhs}A_NZ -?~a=b=iH*3bErTO^S`Fh{Fjlwk30Lg!jZtp9SCIS1LFj>yzyW;!0abk?SYGkg!LuP(UFrp*73X=gKV+{5OYbuAL5)24lJ1H{JA;baCXj8Wf0(B`TTS|(i}Y%ot!=KToSL7z|?Gpsbn^5@g4Muz5OX=8aKqW%coZqe23<~?C5R6_O%~0e8I -fFqk`F@h9S>w=%kN5=zmZpzG$E%SATwB$I&<7!3kAG_^W4K)Yst?*N3_)TOchs*8>xyMFlM<=*56zY> -r+s1!c)hLT4z;4*B`R#r5@pC=-QtN<|`JZk#|t%5$}HVaD?tt=W&a{H -gxUn}20&I3^aG?a4>N3__tKKP@idia7MP`R&1`qnH7RN6H24j5$+$EcM|hL7Z1sa%ZJP5#S=td&G#kB -l{gvmH*l@6plePRcN^)gHXw7{Hz&D>1dYi!=IpIg#TTA;|E(jxJi2wF&z{d~&0*a3x{$1Y*Y*eWl1(O -N!hu(-90Xck#vj<@uETgVB%6pDu2~`;Yf`w>N(qH*^Jr0gxT3-+ -ue;przJ0Lkj^-Pcmo@oHQWgHnk(o(hhM332lG1?=AJknPG46g&E-svH`nNy2cRC951aVn5q-4(4ctB= -+E`@>G5wSHUEdSD?%r&Lzp7m-!ai$N$WUs!t6=`SMo{FeQDsxJA;d+*5ukYMMU0({lwo(Y#d;@`@N1I -Uqt(>?anfa+j|1VcLG~C5JZ4B=dI=slOJ{lc-7SD9oVw6hE*X8)hMYjbf;!4w6k;twGtse`fBOYSSX) -#PUs9<=?2c|iKtG#0=+t=bal|KhP%ec9b@C|-R14{h0M0yfm3%l_#aS90|XQR000O8HkM{dyM^$*2?Y -QEf)xM&CIA2caA|NaUv_0~WN&gWY;R+0W@&6?FLQBhX>?_5Z)0m_X>4UKaCz-mOK;pZ5WeeI5DE-St* -YA1E$G_lE2qRon*c#k6arV8^%ADUHA#8B|Gq$tb}$hR1j#ndlwKKb;WW~KmgA-B@k -AAY6^5^^WB`2mHUe%zTKiCzDUenpKD{lq2EV%LZTL6CE*6#4YW2d2S%e9t&Ut;M+U9{viYsL(UjU_`% -L;#xsCO0nub4FfBsLFrN4!nok9+5G7VIon?^%zDmf3>UBc&+C%}x;Es)Af2=?q&MiS}bO>%c=ZH#$QrX{W@wJ|ow3TC{%O08$-ur`+p7?bu+$=rxfz!{rHlqAZy -nl~3C*H9RvtSZO|`67Ct+os7D2O6IF0?mG?5A8RJT5p+*aai2|5xfwF3n<`QXZFyreYm#xRea=;pnQz -X9UH9_aV~@hec^_DV|&sBQuZHuS$<6`Wt)i|(?PNcQ*J-NQ}XksaGh%24wc5 -RB-wE0mdT3KkU0j4r;&!Z@QH!0UQ<|rf0dP>mJj%d*|cdV0%coRWSNLDGpN@JB%+RMjoAeo|$eu>Yky -=3xrpD3^4G%vRaxsAi*nCR%lNG2ym98XN_W#J6IzpMelM0^y0*_SOAtHd6*1|llHYp`AmUVuD~H#$a_ -8k@ILOh`Dp9A|oFoN3W;F*Z_7XHh`u032zczr?|% -=)g}pC_!F;7PY7NuEdW!{=77VK5%A?WP!M0X<- -wc)scYr>uT63XC3@s#N8Ma@nW&Rikpov0NLg$Pu`ATXy+r{Ey3We)}rP`b8=9!WTtCZ_hY<|9FOiaMf -$a8;xvBG#N;IH%EYo&`nxBQ&a9{aS|hmDFs_?D?B1ER7fsPRI_DZ-WBK3Y -S4z(RCQZwF?Bq^3m4w#pwj6f90rfWD7;W+i)i!F9l@B6ALytSz5wS55f60NFr9O9&#)C9tO&d9iUVvS -gSmI3lgLASds%{bSA&!84oybgi@43E>Mj$bOlFN|w9MQycdz~Cncdpw?)|;Q3Nm(6!hbGQe5@9 -xouG*$+pb0CGPR+24dVD+Y$*AX6X*QicCe=%zMRg>^i%j$2s}?@A;$I_ct~#_ojyq`Z$ -s|ivPa)r%%5x>eo*Eq$BM$(>-=aPax}UxMO*II(T_Oro52xgTLQ1+@cii7orVBKLZ*y` -Wuuef{ih75@TIO9KQH0000805+CpNs%K7**yaQ05}Q&04D$d0B~t=FJE?LZe(wAFKlmPYi4O|WiNAiZ -ER_7Yiw_0Yi4O|WiD`el~!AC(=Zf%&#yRS8YH5GjmJp^fy4w7FeE1NfKcS5C#zLAF1AC-zsJW;+~(3w -v#Qo{&gDDbxww^*TgdaOF^$4J2fp1&Wq=i0ipF5>pQETdm%85>zQri2NImPdk}tB{d|(EpwYa30PV -GtH)&=iBd4;$%0|pvrMm&xN^Ug?b0%6 -5H58LmlJLdF1kQ1S^bVzhpmQM8Xyt?g>~iZbZ>(RtXDambvE4lqaaS$WA!SWzlE!tLDdJ7F9WV92<+E -X##D+!r3-u3I=fx$jB-*)!Aqdb5G&kd1D!#J`enp6*laiW57<#9hyGh(_V9&Ur8A&tKPlfobsp*P+{4^F^1 -3ppRC732xdQ$OpCi>+A99$kre5r|K<`2Qw*wgVniqIua3&dU0C*(C;U!9#TSK0Nbn`T2C_mFN%Dr471 -KkMImzZz0#`M@?jL5;)&zI`=${yUY_D?(?2pQVhh-5rtx4{!AYS5B_gL??H6 -`9|}t&*(qAs;t6MVmNoTZlpPJOz`-HF5euHIaEo&Nh@Im|=1nD!*czvchJaO}YMqmZ<#Og~3xEo%w{e -JO@_Qc1jlX=kHX5Ekch|=N+-X|>15ir?1QY-O00;m!mS#yjDOkpd0RRBd0ssIa0001RX>c!Jc4cm4Z* -nhiVPk7yXK8L{FJE6_VsCYHUtcb8d391vYveEtz2{eml-|-zTb5E57Peo9g|aM6>7^{jlPD9hj%94Qk -bl3nleBYb4so8WCq2FB3|>YGFpxn=XCJizLmk?HJw)512N@9r{S<(Q0`8pd(($~cHSmf)5ZmeuuG`B7 -wCzjU{r+*wX6WVIRuaK>l7&`EX=4)e2fS5~o?0m(F{xPHWDw#sX+Li;dfGnUmsvNcqm1k`+Un -qxL~hrslN;>V&7O{<6vE%EV{Mz1)XCL<`bhRzS0@Vc%>#RcC}c}fv1@T*g>Zd&_3a|PZs0uBa1NictN&0-0|XQR000O8HkM{dfp(V47y$qP0RjL3 -ApigXaA|NaUv_0~WN&gWaA9L>VP|P>XD?r0X>MtBUtcb8c}yVrp03-QFcwhI8pXOe5t&F7&?kmKtbqHoDaD*15*TRha -!d1TJUiq7I5F0LJ_p88|eMn&%qj)*e9t2sgpUV5?X8BHx>CK+!&~<+C@fUYnxhkG+o&LjG_`_&7kG_O -)7GU(eXuvcnPz=;tG59&-zi;npcE%z -1yD-^1QY-O00;m!mS#!jX-ebE0RRA80{{RZ0001RX>c!Jc4cm4Z*nhiVPk7yXK8L{FJEJCZE#_9E^v8 -uQcY_cF%Z4$R}A(cb`eW(F2&$Oo8(XkrQlL1rHIMyY%QXdHqxxqrS!jdBsnH2tuB&g-h1=8gwF??ptp -`d?ruT{&bH(ebujskBJhELDGmujUq0_fY~{MEFb!WQCL6q3!lt>@fW9lR_xqb?OaptOV!Zosefx2Ldk -W}_Er!5VTQZ0=hGaR!S@il4p$)_6hzUAkv@SKOk6{2~`iZAV#(*8h5E<}31vfF7`q$9eqs@tF7^C$p1 -mAK_RGk=UPD-gyUpKJJ@8(zwG1m9t+vRuI?O-GBtN**NRL;`j1HIjX505C?JqTs1Ku%%_{KN1(QpzG6 -wIY}3pWb^+R83m4>Zpe&%Lg+^*{#ErR{5pjO~I|J#A2~1_jM$c7GV*A0xxW_ocYZw#K#22b -9Q=p%tLyx&p+cF-Jv@O_r+HxXSt3A -o(u-I~5#=p|9YOi~8>P0@eYsPqbGG^7fq`~7L8=5!El)lMUynyEuN!G|0$yG(4f$D`nPpNs9KF8N^!I -YN?<7|rMLc?mU;n(lA`U6l)0|XQR000O8HkM{d`u(I5a{&MVhy?%uBme*aaA|NaUv_0~WN&gWaA9L>V -P|P>XD?rKbaHiLbairNb1ras-IG0UgD?<=cmIl0vw%cV)v;2Cexy>QC`+QQmIDX4GB&l3Ncs0Q5Sj#( -qIBp`FUG!m&M)WJGuRZ;K_(S~pVL|>P%=U7FojyDXn_g@LR&g8S-W_uMB=AqmlnZ0S|^QWGq{iMA`m) -l)1KF_eNGH(#CCJJc$u%(^TUC<-Lu9}SKBMFSQ#hD*uAfp84HOiD}{uRA_=Kn%ra|A;5@6Tw#YfivND -!HY@JaxA@9}^q;e-4VG&eTYVv?is1eFoqJ(p>(3#Yd_JP@cU_#*oW8g=MnJ@2!Sk427D)Rw242%9y4L -3b{bBYwg6&H~v7&Jw@cz6mbI$#kP-w`{e--L2_hTzo*eoi62UD}b*2?lLO1WkI(dD}0}&qV7<1=`>1R -VDMuK5T4#_D}hwh#{ddzj#M0%mv9E_P5Je{{@dnw}jsY8s`7PNVk9Oq+vg%jWi_wgO5g!W7Q` -9W?Zachblwi3uww*7_6Z@16JL;uun~uNnpOS@8VueG~cFd -lJu6=1>;F`-(;YAePcxkCj|b^VUaGs<+PECyL!K%qOGKeiI5=7&85sxME$a?i+B>ffd0QJXJ`DBaj^P -7IBT1j{L1)U#*3Kzvt^PNmx|yENS%~$rf}GqASZ;pCN=vbGb$!2+B}mMR12BP`-TbwAsU*On-UJ*0$t -jbm>k!)SydL@%49N0)2yoCsODs)OcWTH(~N(mJcvo~JNVkBtZDHPdGk`R+FM@MwG -T8uE&~gvv;CqEzkl5}(|%MqPk#8~?!_>SV8NlAANoGpQcjNBptxGQiz1*4*#!U467I)hR^A;Z9cPY6P4*dMS4 -HLoe^U-YcOXC$HDN&%c}Q5Mt$2t~y?hJB$vv4V!|`U}k^Fjw0kCEV$xVYYifkUKU__;`g6V -MqJoziOb3Nih!Hhu7Fj7MSVA6Sd_z0I8R2nsR=NV0$*wCJqPbi<9j5848#$yv?>|jv8i4x2)YZEudDi?Txvmx$$BPdqOE -^j$HBZ8zw3blL51~utl^`PDErgBBX>%{P0ODh~kONwHL>?iHziA;XJ;jI9ie`e2?mdOKgA@t3ZgdZl3 -0$iYX+{VFY-$nRbtd*yVj{6i7|^IeEWa?T6w_USan4x(PZs-epu*OCntX&( -zjwG6Gdg6%#q@Ba$&~azkY(S4V5uh5r!-zN_WMe5xJ04eyli -x9bxusVaOci-=Z5m7?59xVBW4yNPKAfGMD1AU?sIIN}j0dhP7pSm}s^5vru7f)MygUUkRXc_b<{Q$R? -M;t*5xI!Z2@Myr>V-ZzUtBCdX`#a)O0Y=bRERK|TsT{izh(I?|8i0cfT{#a4PL&rqqdcc6*4`9y3AO;C{t+fUmK*51pnQ^9om0E -#_YC`qbTvx2j8wx^Vqawlt#vI{3A#taKv+q2j!$*X8|t4J+B7cY{4?+BFv$EAE6rYH3mR5X2*%n8Vy| -bg<mJ}d>5_XG_Gzl^>#O}6~>!#8L0E -|*%kpVwbXj$)Sg_A)2uPfIa(mArf%W9~czm|<5r#ew8`cBlgi$pMu2}lLQdK;o1QE8^8tQ91TurjxW? -`y(ZiPdmo&o>;g7RnTPmswY*}HFP)-#D~PJ -WBi(dXKAn7q1PEbdIB2_IP(;;#We|%FRZLX#imE{z12ks1Gr-YG+c=S%VHw_xz`xXVS}3s;uBK{wkH@6GXzuXX5;<{5OyXrMCJ}Mw_qOBexe -vMtw!;N)W=2Ry=X5v6B7qD(awzeHF_$!8E5LhX-4W11iCf&Q!5lFWiWo^R#~1}Yg{Z!6j+ct>75FkdCKMkCYVu&=k7@Lvdj0E! -k2)rG@Iq+s_9NrAq9B{od4&CcAobLGWBhx|$#UI~|a~hv)?Gy0<#-w0A?9xz=-Bj -0n9(i^)Zz}!XH@$)n|MZmHWGRfYV=b$0pA)raW+jHQ6Ebxn=zmqi?^c6AN;$`m0Km$AT{2S6bo~_*#R -lOZ@IgPPim|S?*bAjLRhrg`CKbzP>OVxDU)Fxc(&dl;jmZcUz4R-^pv&?M_-DRZ81_nO_p`;w*9luqm -{WD4;~Z;Vhk6hMO9Kooxt1#FGvkG3j!pD6V{kufqP`T`imtvawux`!)=N!#MZfQ7>0t;ln|2RN9X`2F%(k%ohdp@>TKYDH!j}eY0{{v7<0|XQR000O8HkM{dt`V -Q}cLo3e=MVq@B>(^baA|NaUv_0~WN&gWaA9L>VP|P>XD@PPadl~OWo>0{baO6nd6idLbK5o$e&?@1>4 -%UbhU0e9nT%4`VPq!NL{~{UM;(^~i;#_!A{YSj(Io%93xK3RiOHmXut@Cgwccm+Qb!xo@7gbT__=Lr209tQ}Qxn_Hjo;5rnN(s@2lkq5?rE#sW -)MuPAX|TZpPAVp-Wc&f}){LVS+f)US1m#3xT8PkDbGAWQw%*DuCs~H*rea(od?i@1l_WFo0m|tuRfYw -q;jT6T}SF{SVTT{)}WQvC?B+;xZ2qkk{5PYq@7#YE&EXynE~{&S#_Ld9)mBIqkXoI16 -3h8K1?o<@n`TC3);ydhv${BD^<9g -^dmCu2C#x5eX+F8)TBgGavSLK%a=c`^Z_2N4(`U<7atk!F)|s-j{5JNB3;4-BbRnJSPr-Gla)dKQ`rK -$CRP4_$#{0VX(a;v|ItWl(reo*cKfwih7GxH`z7yaFiG5cprrp>KXsFXLCUc@mGJH1=DdN&G4ry>Ef` -ZrKmRwRrQH3iqCwf1|FtmU|k_-djnvL3-n8G5CSLbUl9`87)(Ny-uLEVX)*MF^m&=@)8$WiN*L7UZmdz4vJ&j)3ub%Q30u&+pGWM7-<>z=p(j~gXWP%r|A_6bZBFsg-5<3 -*fA%egYG?MO8djt;m6_AuE+GMb%btxzYt*2Oan`hs@_)Q#`q(@o_pOX5Gz$4NYe_5r*fwDY>%9NcMjo -xMHuUG?k^0lEun?F#^LsCKYhBHFoU4?K4Dk*wry*e%RpwufBkXktUTrZC$(L(ph|`3Un4W|bQ55|S&n -s~a`qRX2sVb{K0k@YO7IrgMcT!BmjGnT%KoTCZ?kJUoWAZ9_T}*jk#b8LJ+S&~kn@AEFU1(Vbc}1&r$ -(0(&7xf^FbI8!ccDEMfmE0r!ro!>;$IHJJGuuR#{wusf79g$n=W4VI_qOuv={P#_9g8NLQo_*I)|rh< -^cF4{rU3$Qi=!gNOpy-lgZTeJ9{qPGAJ`zPBPn0h-p?Q^h%*g)w1HS4g_w~McSJvDL3$r^TCT2h&1o* ->0Ku-(l}EbD^MM)HSYr;!4;ng+Wf3-tzP8miZW)k+h+uI{a_%4f2tLDfRvdUnvz-SOd47S!7%QEME6_ -ryP`;U&HW8{L>K5U4z0lz@e%D^^$^wS -&@LS9?R+4VaO~+8Zk4PP{x`kjdp+b6o6a1P_p`(jSzYEf~BQ-s&j{^I3%B)^CC>=4Tc*>Ie|M|=t6zyw4i_;yw}se01Vi2Y_4w{9Q)?>m)sL_ptvP -)h>@6aWAK2mm&gW=VWSgN%MmPb8uy2X=Z6}k+T}ka0f$CjOG0H>ux-zfx%Fe;#_v -KVU;a$252-I{X#d;I!wM>Xs+&YiRaLATd7;0LlUaV9BVyxZRp(^`9m=dhL=Rk#%>GL6s;W@-K -@L;)9@O$ckDOj0N}{pjss^CXFPiO2s&TT(o5jTvP|XUVl<9G;DP(&Xf{l}}(9UVA9yfMs_EZl){Foh0`eKYgk@-Iku}g|QO)T2OkToz!Q+dIp_0 -X=L7z_nKi^7N&{7dkKx1TuS}IvK0$0s(h(iq+^-3BXQsQF=%FJ~tJs{W8vTzF{ZxEx@Luil5EQDI@Hy -e>)+vFM-j~IH#B<_oa{D;8-mI9_#--*f;I9iIbr3!FvvRO#%oB(bmNFvGmrV3DWz%;4=td@!V2k47?V -_nMM7He$27v`(FT7eeWI^1FSS^_Fcs(qF|KoNRBwcQhe6Wc6cVuX_@ifRKYce*A1Xc^f~Cx$CSCsncm -jn#|kSTkS`!tZ4B>tnlvC$b9UGcPTwpnI2NK84BKs_eu4ONquQF9FM33-=rZy(A!L(hO|C7Bv}}9C-R -rOu=r!a4nv-m=Mq>P3LvBUJVC!`h!u5c7HV-j -r$uLvma~AhCWQ*0YKSRXr7rc7ENTCTs54k!4%jVm1d{;PAsUK;XmkkE0WZg$GBEctLD^aRI-TVhQHPUnE!!J$L^x;MrN -A9j=ST3()%TtR>1(%Ss-MxW{0C;LBl8#~nrfNHeRqRdb5Y`w~pHoVixAFajOH-NPZkJ`Ob`C70xho}B*5X}ZG&`GclAY5J&-rgPai<6ud<>T -)_X6HQ}V4D?$LD=8_+)p2c-9MIhxh7%=@^Y)=4i(361Cw1X+1zEmgw>HAn3eBYSTiwuNQgP -`i{se97FV*V2eC$y(evzQ_ft0T|?+T`}lJXttIyW-Xh}{`A<^2tuVB(Jc%O*+)AziA*Nt&kF6KtS~73 -~-G#cz)(!1A|Ms`R^YnOt9w76PPA1O>%*VoUt-=ZfjL|tlw~b`mZM9xuhyyoh9(h8~lmXL}9UD-yiiF -m{@qmCkTw;?9e$f1qEU&Y5-Y{TAHP17>5rE-b+y%EUoQXYc53@^i#W<9lakB@qU7~99FQQPA6Xtc+fD -a80`>dj^h}Leo0vuqKW}Iw2f!E{hq+#l#T7KBtgVA -nj-8Esd@=xN#V;27?zqt8@g*I*i&${H|Whj~>lIA1%Rz0)Ywh26k1~xc4avS_9HG$_5+V&I{KV#uDWg -Hen1LZNA8jAL~idlax)cZuzR+$pw;+=3;iy+K;GQ`Rl+~(pPgnuyJy9bhR0E9fZT=6im&!?&EqLnFaa -TND~ABRZgk~HAw^B!EF+Ng-Y!&~6hTJLoys|EvCDavUP -#>dgjy*@vFUH(doUWEx79{RAYj$6rIoFj7A^_@ZE3N`zFO0b&A8koft+bc99z)?st`GdWSTYUJ2vGMA -by)A>yFvmC -9!wg(`sSro?c1yS -W5FFcWCMWsmOz~uuoN>ewJQWkAlVk-)v^+G};p~)i9>MwzI0tR_N@$o@Rx -WOZO45D+M%(L%H>O*l>^TIb6U44;%F5JqPXb)x*X;dFW&xIL(L8@`3Z)Ip6_ -}Rs;R=+P$=+4==4qeC876>82Jdg>M|P_+%w#d!W}9M6Y#R482WP@bI#qC{U&|W*Jh=@FGJK -yfCDCh$hp%Fu{dc%?nPKL$*(R;xCeNJ=9vCXAGO%&+`u`C0E4|LHop;>J-lEHut|Ts>hETr71e_+2PTxnCq62_CUG{SZ78@KaLhT3R -427Q0IPT)~?sUk+yQne|V?^g -?=}?<>FK3whw3`76n_S}N0tl}t#~*>z;$R#sTxjSjOWNzBH1sVpNa@H27A!bTRJoXt{(7+2ieJBq4Eo -~i`00P0KY28I#RrwEisg3gM>mB&?^FHj=!jaIy+;gs_$V=3`QAP>&eMzc>3J7hf+=pNx7lr--HKwhfG -@HUw*V0TUFUMs-4x#ik*3n6)+kD?_At@Jy$0t_r?gu`OOA%N_##p3DMgckKQ`!j#wDlEZ?yDr#%(hbsa8E|{H!lX(~TLm#MmPwbC`V5 -IwLOkViGdA>&dT00sap9EYrMnXYx-b;Bf5 -8B$JaU{p%>&FuB}>V$Mno2rBu!!9*$0))qh}r34FtJI~4_=YdJr8J}Bh;FM?2elY60-P% -()0{a!{Y3j^>$pbE9YSHR_j{UsEoGWPzq&AaI<7TPKauuAs#E-i)lQv+oHVaSkz3yT;or&b;1^NU{a+9h?)Q!UZ&v1aV9E|Qv@|us|7YH*Ljp7L(C -opOS4u}qXc3b*dbaeLO>9cdD^B5Ih&t}Z2KwPy@H2Um^EXiNSjJ%Grg|vNw#622xPTLJ5hdEH+jD}tL -MJjcs5bHnb^w(I}gLU^%EBnUSNeD=ktYf54=g@hja#lFXS?A03#3K;UB?S_ibp<*F(cr`6R7($?Jg6> -1U|b}O_-L0gx^MH6EH?hk>6Nb5(<0AC0S@nwmGq4zJROOd%_+LBjY)F@QjMfssg}u_uGtB?3I^cvCV) -4?s5mAan~jtuc-Fyg(}WqgWJl*?PO?W>9vJ%hIR!SqmCNc{U&{kKlNU%d*naoUkkiP*7l5wTedpo4zd@t9Cfi7n5S7oQ(m|=8B!F-ep$lVWQq3RT6&*oWO -sI*oKDb?R>7_g8SCISTX7?%4M|OvVH9(E5OyPFQlg6G`yh7n@I&rjBsXJ+SzMdFx+>$bAyo5rOj#E8B -eT*kz^g#Gtjqo2tGded76|`gWl`>E~YU7E?fEA+sxls-)Z{TA)*LhWhw$M?PP1YqkLU>OPNYFWr7Mu%!U-vt1{GB^UX`)UzS#mOrrXIt@!tG701+!r}cM+{)msw)Z#<}fU&<3n3^=(8Sr=d*eY4Y+v(`V+aB!lN}|0VD`1t0%d@0HA< -O8e@VUX42BCB4`^LCTe%_x44oTx05T3JtAnjZiUiM)lwJUgqg6=&3zGIjKq^{JJd;h?aI>TCVRN* -|3|N=zUE?E||53~B{*s%0wP(-|`4LD9u5${V4w?QEJ@~uTKxUHvDoW_&pFjy!s@YQuF&MF4_fbO(lvY -S5twQqD&~b`G#r4p0{ceXMg4eq=QTzJ0Q$=q|lX36~ZW2CT_9Z+x(*F< -c$d@c=rZm;|i{L7g$hzGFY(fDQCcYc%s8UDRkEr_NapFxkRbQ`l_m~@VEh9PO`zlHgc^)yMA%vb<>wM1?nXu1JnT9{E5HcFRi?wVowx-(W@n$Pz5k8ij)G1WqjJ? -&JFM(9j6+O#spzxNWidqC^$V~8@%O{QG3q-o}6Jgw0(7Ud_H<{-istXqAu*E?PCx*EtTD!z%tEjUV{y -vQ;Rk;_N>i9oeho$=gAQ;*3Ix3Y1SDjd75-lHhHWr-uD~I2WmzN#&<(;?raALmN8q1yc{02fO&E*-j< -EW1`7}DzmrY8LQzjKB5h&sDoJG19iZaL`<#Lwc9lqhj%qo}XZbaTyV)M5q+7=(o2su^^NgIn0Jky_%+ --ElrvL+Fj{k6Wu08JUZ$XW70Me^$At8OT8+92=KO1#69*oFwv2D+}|M87Jo9||%Z#;N6`}B2cIzFD)s -y%>&dryy0&OG5AO0ZFs;}&{HEW+!KEVHapK!*YZ**&HiF~YK$?NM+=15H8RhV7jW2VwNUnkx)yczct> -=9^)Z1}*wDOYc|$(dDwPe!`oEQg7vzxHhMJ-EC^N`p)IT<<~utY8rT?h7^s=46d#^s85rjcZDu4tfRi -P9aJP>!0;|Xr_fX<&-A-5IOQoD!sNpNbf<4VR`{Ud2eU#qVPxDnNk(Yx?cABV_3KV$ucTlWr#;??B@oD0 -H`}cVIAyq5TO4>o~$x)2}Ud+dV-v_(EU7&?nKP#|$Q4jXN8nlMauv$p4@V4l5eenCnjcmtCFE#tE5le -Zm55d&^cAYR?76JE5cY(IfoI;@c@zrGf%&p2hft725vIi2A6XsNl}`+7CJw(qlf*NAK;J+sZ+H&-(}T -?3F((k7A0r#zT9_vYHuhwTw9#nyy_uL+TXAJa-!+NKtb`>7DcQs5}-_Moa;VsbMXW$}|k2Yq -&f=Fe{0-);LT~7_^s;oAym=r59zB0OVP+aoO4_FOiLUpu&3IKL6+smEk{`7#L! -jC5vi9ECNVuYF@lE`O#DOdaXRm>_n2cCI#t1;TdMA2ZJsXj7+m5$w8YU>{dIW<421%Ih -V3*{X68r`U04uB^7`$UiC9+U+)Er&*LKg_^)DkHl?`(SJl2gpx;f|!X=*qw&TIG=GI-I$AmGV7b?h5) -T-mYX9wOU2QO0@Ieziu5M-J&w2NBVb+7x^9%o0BlR7>+mIiN!+xEtTLCOZ*@96B<|T$V6NRw|(zV9?z -u1bucMEY9xYrFH>rA~9b4Q)3^TpFzu$fE}hoSr%WN({MseMxx6Izswi2pH4|q?wy5=`zuOzz#xEFsf@ -q)%TFY8M}ACnoMgoq^byw9k`$031mpswkBB`V1UHp9arLA*n*p?3ZM8JunmSVQme1tnZR{D%heV;0*{Z)z?ATl9_@8gZ|(_8=azFcC5^80&D*@-DluCg+YC#!(uD-CAM=AA1Rt^nxTo -W0@6aWAK2mm&gW=X~BCl3(;005-{0015U003}l -a4%nWWo~3|axZXUV{2h&X>MmPb#!TLb1rasZBR=~#4r%P=U04mFJ0`0Me!nd@UaJBk%bj6A|cyM-C&x -ON$Ts@o2GgY3`2&Qd@r)V!_h``))8_yqjqq%B_GLP^oAJeNJzweMCi(JHRv|C?F?)1NE~hOVu4e+ZlJ -v>uPjo6Y@x^JD1CjzdVZc22-FglJRX&#c!4MjQIUQ6d;h+PWx3$DxN(-IzuurC|FZaD -qO>;KoF1o*NkZW~X2c2DUzBf^X9lRQxcQgb=FLK6u+|XTL}>z0e0P^OKy6bIO{lr{8YilB7iow#0#Z%|7E1QY-O00;m!mS#x~ -@5Z5F4FCWnEdT%@0001RX>c!Jc4cm4Z*nhiVPk7yXK8L{FLq^eb7^mGE^v9}T5WUNHWL1>UxCs$6S0< -AIqluGo;YdJ`Z}J;xSlwZez1KiMM5%bieN#?FRA=*@a -^#rM<-`TcEN(5X{Bnz8xm+)sw%6Mz6ZaV(jrldGMBX!sjTBHucE_eiAKO-6!^M5(i)#)KlNNzj3vk*GYE{HSt|GH6;Wl{XKx9GC^8Nmc7*LTgrT$>U;{6p*SB1e1bC8l?+BAu;!t!Z{<8r?pqIzi<>yl($ytMJx`IJg%yPU#*0{3N`%=dN4cHCh%~Zqdmbgn(Pp7#7Ph8F_p%xr(c+$z)i`d=~aJqCp;t -CfD*lY|qPUJ8L8q`MW>a%%i;=YdK`1k;vo4G>t`tSqGvLFNE^B%Fn{}u)|jV{yEqlcG=E9i?lC$Y(I- ->Uv{__pGDX&I>I{Y7UPjv$ofj9tp%`q3nYA`=Ax?OB5Am{!?Q)-66!?N$8;JjWKqj>(3RONwTe+IgNs -+Y3;dngS+r#%%IuAmZhB&wRc@NZjNIqvi4_r+2f@T-pGmSP@nGo9_nZSx8#kdht33(l=H^+!3t#C!#( -#z{g9yj#BCdxtbx7j#7D-HwaaH|Y&4+sY&q9x)%%ZQ$2?)28d@FLw658^!D`*=xTE}{pq7vgjVDR9A?pU;l?eC{%a -1M@iHT`^h2C3yc~F$m810}&+lyuZohZS#ANz5%|(`5ps=q!}TAfbZV}Mf_>Oz=Y92vmk!e)v&Uv6H22 -egy$a9IjYJ$tD9-`;c17xSsjIbX=K$o(P#d0BamI#yPvI_CpCcMuiPGMpieFVJChu^O9fJ8(btf3CR48$(aF_P^Fz-1sz;F -}nTHZQN@ucpAgUnRg)SsDWdBf*NPggjEP_Xcfl{3LKr^AnDr!gjln{2mi;Fv-lZ?B-fRJSrDrMqBcCswDtN#*wKpZebH4XEQ>tHxf+BWo>GLc5WJ3cDGj-&LwIILWAg2<@FGSm+9s&Voy$I=%+l#RNIH9K;vI_l -ByB~UZlABS{wcD1@Cl_ -X^Cw$=oFfCAi1LB<`w@LzFHXo--oYGU$Dq8&&PY;UG9cogs;~5uJP0=I~We9FKjqj2H<%SSzcd;>o~a -ZtK^*ax5o?7`SOwFd%QruXZd+Q#LM3=9;XO{mYe>sdFrVq)3P98WJKTtA$z|ey|PQ!3(W`(ocYPj -{4sQ@ju*9j_tUaZpzmfdp$GXh3j5HX2bkhPhfkkgJnx%?KH(+<0hv6>=b&F}pG -|H8J#@nV+j*79H+wa7o9JSfh#9lKBy8t`->_|-U=K7KYR5oacSa4`Rs3U>3y!6^yjue<>7#@HUQ`S?x -_f^YV;K5}?He(1133Xi@#FZXlb_!m{qUL~sL^iwQfWtgpbiG17s^=fsI-~4qMU`34VkITBu><39*Pq_ -lTA&)szsHIb#m3Ov@8@PPM8Dn3`NnirY3UyTWNkroYhK8du(fJkSZV;BeToK&S7p -+w@P#!x}hf`XUapEV94@EbFG%;(01>_kd#|Dz_3z`N4~ly-Ve>OLp-yP4}y%)gU7}-1w)I>0h`0n=Z3 -^}Xc0#)Yv8-4eCus%k2Pn{pu=n}RnevDN`Or9*F@k9^=TPQj)XN~qctPXKOrBIz+V!NbIm8E;}r7=2{ -=_WGSH7k1do?#@wPqeMfXp#VDJItM-2-Dz!|hpB>{0$1XW6-lA3n{+;%u@rQu2E?SRN&o<|65Og=_}s -Cjf4ZhLGTCVgy}t<*EoR!x?tv{xQ!^|Zi$%RNUPnnFfcV_53Qjs5KBfztqzebETEhJl~&-fe(iKF=tK -ep6Y&D&Cn?b4RIWtzZ2GPS;?vo@Q`2)nAm|skif(dRs{XejO9O+U}_pY-@o*w_)(% -LRwWs_hgo{U5U|-+7SL`M1%o|F-nOAf&-p!B3{u%nnqIOCcH6RT8B4rk2hE1PZm^4ncIEZdrl#Aq?tt -74Mq)oaA7B2J4(I>NzJ8MZ*S2z9bHm;z9h2zo!UG^s~<2>M -E~Uh$e-8hqePj%+C?ELZ;0v4@_sCw-Nd;lA5GdW^ep8cM7K58KnMNmH?xV$=q?flD1c|oS|FHxOrjvF -Gvk2T2zw7)SjdM6VQI-!FHC75=cLJ*fB)^z?Ze4P|vfEB;dVOr?P#LObhOViG(DiP0`FXgy3hsxPIAS -gCQ!lkm6S9v~t$SF{>buRFyKP{u3jAzGkw7sA^i=_!S2QF-Vq5+=5+<1P^X24Y-Tbu)~mAYBKUnYAxH -$9Fb=}r_!MaxJA+FS~VI6anKCN+@DBYjg9lB>*qnU^X6uueZg?KiIGko;iS!533~H%ep8{BWn6?)p<| -=m1vy2bold2u8STV8Vsp9RFupadb@Ng|Y^g_LiW1Kb5kEI<__lb#iG_KC1^<>6r7iMnvv=CP8d2RBb$ -lm=GMYzXKN|1thy4(Jg2Hqq)W-djN|uD>m9G3=fDX}C%MV~Tb)V9aJK9oxcnMGNiP5xaUsb>T>~Es1!e7`c`90*MMO7Z7&+Q9D66DwvbgCf!|_E`w# -)X@Q%@cp8Z_1vbw>jiQa$Anqp3G1Blttqe9(Ueh?*Hv0D~(mRo80FrZ|J00%(K5m7rQz}g5zs9eQ -UsxjvAdhW@7TWIL(It6sWRDTdXH_n%0E;*S(w-4vI$+`O`15fGcC8)fo_j+V!>}uaSF4{B5}+qlP?0X -YfWS-6J|x@ICoGts_A7e?N#6vyqrrTHJ{WrE^_Nwx??hOP+BnELR*714>s08C10`cH81EHfoy?Z@rI) -2oeyS)_SLSKIl@2j6gw((FIZLlmf_6h{T-_?Z%!<3?-6rf?yg9yR|YqW)Uva;w)OtHWogltaaya=)3z -lxubZ|i&~?kR>YG-#p70l^hh3H1Ip3#VE0m#qqBVH?^2BZo_!se=6?@B>O-kvnjc3UA#A`xo%>|`pUU_@ -6aWAK2mm&gW=Y8{KqgIX002Id0RSZc003}la4%nWWo~3|axZXeXJ2w?y-E^v9 -xz3X}#$FV5-pHDF%mp1?j99uCA`@K{l)B)$;sFwppJY{(f(Ne}C^yF}p0z%d9Eedeh9x?CG1Adxwe7-riH+sA -{t!Th!;(tXO1c)uIH*%XLvL%XxNFtuLx2JYO;BaaK3J^pHtAu1kbaHSp&80{+uLyS9XXi?twR*dR0P$s$RAk^n6t;tJP)!z#Lk8gjRN1!u$dzZ3_rgOK6K-!8r20z1|_hY|hK -JI>*_AH*V_Yl96B5&6P}WyKUFymA>DO_ww@moS%%dVmZ$?OMC_p`~w)u%XKy@U@;c0z@?R0R96+vP_d -E2bjzNqIp5! -c>X0}v*Yv(0+bl#@wTUD0dBXvF>1_x9v>7^MDLyT7Vh|5{g9rT*Qxzt-DT=^Dd0a2V#RbRj?76 -wMM=$hB|Rg?o0kSprqo3-@q!dA?a!3x^WXK;5{biZ<=D|#=kBbMvG=skkYBdx#vFt;@-O1KH1CQBTwe$lh#Wt(Mu -R`C~x-3JASAtrpdKYeB$2SVlDzR -!-b_syo5aW`dQ*s*6jrU)W^(VbhcZNqc -bAj@fjP{tiWxB35?E-_`kyE6XBj~;o0Tw=#d_UTr?BC0c7bG(gUd|PIOgS|*doJ{2`pz9z~S1fHaEQo -@bk$8XLmB`oxa3NFf*^}W!Xa_!-1ta*CGct-@;ZLjPB63g(IdqtKf}0HE+r -vHgJSpmCN -M=qQJuk2yEWet5rGAvoDD0W&*#>_C1`NY5}JTu%Zml-<+R}=)cM!bNpin7>sa>&db@N0AOu>Rc7Z4*a -QnX2?*3_2`$d(^v6RH&es}VYJs1$XInmO;VcA1@;#Y5B?UZ_pu0(1t{rvkL0>?$aPa*0w-3K#e1>l~O -WgGGRVE0}82tAeJZ*0-VDuzVka)6$|F)Zw&$2?N0c8=l+H}ez<6CU;)TWaV!QqhyUFWV3MQQS`TyIvy -jK5`gUsu4NhV|(`fbk1bFcWa(!FpT(LA2%KOn%2t1MfFW5ixqnv7t8efh7!{^-|6zf7WE{_rnB|LE67pYZ -IWfBvtT{m*}G^v~0?fBvr_{m*}G?9-&b{MStWZ+`8`U-nFXxdut-%8lW&+;;hzLF^bBoLB%T#&#JZa$ -jKj%k_lL1UQWF7m$XB&0zTb{C@Ud*>Q3B;|c!peg0qB;b?%8UH9?#0Mp0OI%zVf-@~{9x6Tg -D2TwHW+t)zpl!g+h~#TaK|TjRkPER -lTg@cjvs1%q;oLDuVeag)M*N|hNm9Ap&bGL8RUa5SY9AFcb6b0CjjBg$4?%eAf|(dj~;*e+t2>}cfbF -S9!Lk!eomuXZm!Cvn(>{ZsoHv(d|_Lai#c_Y{|L8*#qjst;dH4ze0y+jkliO3dqJy%tX#Hy=>Q?7Sgb -CJ2dCw_cmTKMc{RtIJ<2<$CE#0TL<)N;==5X|u5!JYhYQ_VGuBibWydGIE1xEnd{N)PCLSedK-MD%E& -@$amKnm}6AgLzAil!Y3`t!FS-|ZEgr`MW;4Q2;PrfL0XVtIP9=s;b&NAjv# -?k0`JvusknjTOcHWUjdFVk1+F98-uHsz)sq#T?0pB!5n*EmN!m3XE_@#3r#)W%t(g@E2t+{4Da{(!#s -Pr%&zJd^;|9L!k`BQ2AAD}ClqnnESDwTh6}j8W?ul75irkcq@EMF}c -DX38%f-(8>WC6>zam<^4f;-|lO_5Nz=jwg&krZ$ -S{gY04Sr;UcW2<58G$HLx?R6qUKfjMF3ooU42OP=+J*@#H~}$04@IM&1l@^T==t#w9-|g@oZY*3c_X; -XEq^%5H!D03hwy|R7?ApCUd`4yvRn}QT4*@V1mb9<9ijlqFT#bEd!Vn36?F)Ix=#0$>Mj8mork7Z6hz -yhAvSt;e0hQpjt7$#m7;j=U!M%m;b(@XVlcLKShXH2`d0_P6BlaP#e%wSrE7yPU~n>ocM%nU(2ot_jd -KYLvJk-14t@a1c(on638kK>F-$t#DNe_k9YKAX&w!;gfcN#8S(7C+U*L`Agi6F7ZB -?z@J#SBNjEQ+hsd6C^Kn)CLFs)DdLd_gp@C9odRy6epE@9)2rqZ7{~eLQHhtKvg-wYhSt?S)f@<#I8B -lL&R`K*#W36xR)OF}^IeC|1`f5g_xus+I&(0P$GQvagF-_WE7+H_?gJ8{m7V%IVpva}W6cJ=o-M_OU* -0rAOT9Ho_*${=8X}u`0VM;Y`QF5|vxCi*knAvQSx%XB)u3T-G;;BEfRnt`2G}*WPR3Gz3WZ(DDYS@J>q`uvl*Sw(FKTR`0>?tVFnTP2hM!nP?o!Oj&!O%S4MZhm|O1v${bGDIDp6f)QaMO8^Qv-Bb%pg -{-NChgZd{R-XQjy3DVN_3UDZZMhjp&sVrycmqQMCm|D+zz$cCGxz63b5kw%J!1)u5rfWvy8LkXNellQ -WS?Y1xW?uGs4F=4_z|5*!_o1R$0wt4c9owu^=38vbmT=(Id#4*7Ms#&@AY#)xaz`2mzcJ-+yarp4L$* -CnoRI^v&RtrT(lr{JBz2#L-J08J1 -9>81a?CW9)tjZz&JXm~e=1evC^#!iu8>Rp^le7cKYD*vx@`3=+e!$mcS3j$lXTSytlI`eKz5b%!Ea!^ -EH%nxB%NICR)T^5_fv8=7r`BkCEiibPsR9uKnZ7bE-a1TlXbFWgpwcO0F*LAGjrCVGh~YvOXG|XMi+k -u-&Yh&V#O?@nKNDn<@PT@kB@+m0sDQ*~LNW}DIw*BA*0tFRu)SEgov?`ve<|UI4N49AlTL(jgNTOBx} -1sgD?u=Pd{;k~LZ=GFE*xBN;f-=$!fkFJHxXX!)=GX~emQym^4)s`_UD(cp1%FN`}*$nH*cT4a9^MO< -;AnVzW(OD`{DVEZ(n@%`i*?op7-Afr`pfR8g>R-6HcXY3y3#IQ=TK9^wWbyU0-fi=qe(pz5Kdd7wB>E -#_+zEx+Uz7U%;GcV_lCoFdzOm80h(V@bzl7-dY$X{$SWFLwU7XENm-)V!uA;eQ$sI&uaB10>Z-t28#@ -*6dou)IrcTVfEigtPuU%1f|m@>v_1eA@~^2Fqr6?+Bn&N-xc;aMd2kAiZ;ECf$yW7pp=S^rJXb|U`{( -I(Q7sDC4bMXsxp(3&q!AYxIxgeI)~BZ}@rCS^ZvHje;ZZpR)VQVNlHbot1kjQl65r&D3kanNb-Lav;8 -&2p36sgh+3Mk1g0Im_If~gMhi=5-h7tOaz4a-26u}8M-yV`OWU&C@$*FJzM(b4;$dJl*?Hh@(_s)r6A -Xcv{+OBi|nui^1THGH2RDxwVy)oS49~1PjS@2zR|DQB?JlcLjNz8Wvz%zabaOs&!?*iseume_QBi=E% -3J7J_3g8qUKRoGs9pK;k_9J6M;d{u=CD)PW$f1k%@ZLT0asv_27;|ubCnzxhPZn#A4s~*Btvb5g_<4C -|8?g|(JS5gIRvZc|)gxfObD)aQ!Fk60Zi+=ja(S{_F;|$b)3z<>%>9lN2TScH5y*aM0E|cR-1Xu#ych -10{)W-_c)!H^OUw>43$yFq8}r@ -;v?MUh?Py@hRj#0GkiZe*5N)nUk?2K9_)Y+X;1$DQus_8!a4Ca<2`L+KLq_1V}s?#JJ!I$+yrD+UVD3WfL6A?qg88@|)O7=l#SMoH+Bk -Y?p(z>hrUGl4i5BvOpKBeN|$dSJ&l|{FB*f$jM9-5cNPVMLQHLVc-0H^7YgI_4+NcDenVz{N&S;r=j_qd>bvYTdQB=ce82SX3E~sm!*4~6*W@>dCJ -Qb{6sn{G=9JrG(+>sz%&-l-*`H`s^^5$gz527uh|Ab}wVC<=AL0$X -RSU?a0s5DUF3#t>6-H^~CZXn?a&BI!4lHH1*MtLBDQ5fa$0-0kIBRf)qQ#&jmA_L~}qWilJwT>1k}`r -S8r#+JYQK^8iWH7ny$Svk~$z1Qh731V<)z{?ZtCA#_|h@qQW{J?D(4tu< -`5IKBZ%-+=2s$HFVP)FHd5bF -XlsZrWF;i711*3~7RE_22F4T0@(Q=qmisxH?wC;V>i-jp7bZ$qbpH>6}=%OD1*9}a-_oAT{Sm=}&?kS -E?s%8RKce!c3j+s+^#j6$-a#&qNd)KWCFi=ntohvt}n4&I>m$D3#9X|!6+@SLSzCb0Y%Ytmlm6Y@P$D -Cls{;0*hT)!4TgQ$*iHhG6;iP#kGzfh^VXeEjWif6Jb+u79y*Xu|VbU|Z<5KArBgzhKl3-pMMqK%hyx -W&h&~pn5)LzN(g+4|E);hC1ZX0TXiJ4v+KSAq)Hb=+peMdrA%?z(4TzTAkbI3K!;q&HB9E9f)vDNjQ5 -<+Rma7*%E+k`%D5fRC3>fhk>V$SOV-u>R1gtMTgdbpplRYG=iq1^)1`a_Q%=&WIyS=vab -e-dH%`#@RN2QZ4yA4x)*@jN8`-?@CbhBY{BGK_z05)&L9v98dy+dXHyp%tl-9qgu=6#u`W@}LgEs?SV -XYa7fro6zsMw#OVrts(k^7KX}d_*JQVpm>g1u@PL1mu7=Wu)lZvlN&#a -S78%sep%a7|y*0!84L)1?qz4rIS}wWSdXDZ4MSEBVi35D+vGik9(rpVKHspY)V|*vI=d06*3b%w~=Q^ -XAVcQcLL;Mk$cYcinvLr>LcrF%vZ#9XIXvN0)HvnkiVceoQw&PAd{XKj)LD@yQ3rb-14TfXt(4Z6pLa -Mqa^xDXUiqdi*}zvtt9{xpYAY2x)oB?`4x`@qIe4o9Yx@pMtq%wZ+u&{u78YgmI6KAt9mjhS;nivo=0 -MjM#5Z+zhVT(ctt(T|2@y1!gh|{E6yl1g$_2dGUA?(z@U?s`lRuGl+et=Yg6oeJ!e*OlqGg(XU^=)LO -T3@R|=VXW{vF9Hx(1^qfuApA$frCgQ?un;#yUW(NDByo8jO2J`=TQ8Vg75}KcQ-k13CnDbI0;(2@cGHT! -_|Z(K=O%pd)9#C(j*^;}4*DWdIer7Yf$?WwADms{h01$?7X>3w(h6uH`<#A{N#rgVC0G`6+nAkt6Jp5 -7)FtD*rnt1dZbz)ja4rBtl(r@aW1R6XWa;o{&{s8DcS2p?)V(xiQ`?4oMzX1aFcomh=$EbBF3}t647I -edxcS*ce8jj7lFtz|JszmRWpE;I2fkQg3w#Csv?^XdEWGpOX1oT)F~C8A{vbZlHHKFWRwyVq2<8T0Cx -v?KY9E}pPB4M2L1Bj+t45&MI^uk3^#cMKW2kV>tw>8^UE}9`E47v!GF>xvy%5uy3;`CXS^_wDD0N_2w -Vzds6cro{?*U)7=Iy_rtOTXn;R7T{2C;xZV)i`?LE{gy7_oDTph7!uoP`7}D>G#xwJtODSvI9tSZhU8 -J|$^{a5py=T^+$BO$&JkD~pdIBj-Y6(LN$AO7~7#1}MWzj4R{qWVf>o9Nydzn%oWD5ETO0#S;B`?l8N -EgZC7%^e&j*=lLguRr35;GzphzPFW4wvF`i0(u<;iorc*?Nh_q?|7mvv7qCjIl9B(L8)K}GdjZXM>4&3<3mn$*;mu+HcU -_PB=%OA6BxTr2M>4s|F7}jVy9i{sc_p(H9U&&;;jyQ#h(1%abVtQ0yo$h+iDD}n&yMFPZoKZRgH(Gv6 -J4bilMODuI`x&}A0}U6=)<$G^wBy6h?lA^LcXp9ZUyg-VJrd!HAE#$&J2c$!=Nn$J2in-ZH0e&YJ5cE -g8{~5Vx+2aJygl_Y)l}e5*`xNfjI0&n0LKg{jVgO1ejK8xVMPxYLiUg-!1BR=Ew&i&|54bAc2fA8hSr -G6mJCb3GRDY=72VWWk}|=dLaSrs0|8&tV#|PwK92m3xGFnLdtt$R_rlona;gvibY0I% -FS2pVd&HZ&86n8dKa$Ly}6{>Z)244V@R^5GNCKHF9ChcotV>YvmQ``5Ej0;80=-BWDacCG-fQ-sJUcD -0;yo#bachSjY~oO@pXU_hdW4&>0^L;~t*Ulf;9W*nb3~z1C@0qJ9c*ZXOTCH4O7&xeJz28isGPpTS%t -!y)}wT^?vLIsKELZq_AU6=**m&K7Mr$?oqr4W{7Y1A!acnNRx$-QXZ*3#-BI=)5HX72bn<`l*R}$hMP -7tW{2G_VIH-Daz>& -;qq`K|ggT0UalI^AYuY0(rhcs`KdTNX5Q)OdVi9n5~fpSy0^LpK4zg%umq;%*lpbOq4p6bUDd2EnyEJzj4z5JMB~-1jv!- -l^74+`BY4J1Ei0{*KR-mSQI@RTXLCuQrf=TLB^yLR;R&%BWuavEqv(gP9z($>^0@z+cP#qzU$um4O@C -5JVqa+uIPTU+gi|_$mB)fm@$*p#MAzQmcbQ|8X^68^g6cW;q6rF*Go6KqJWEP{VlhM5w!D?~0>F=#%@XKha5Y;GCq0jI)1EEhb~ -2=ja4y-a#6@d7hlJW&N>prV1=CLAEk=whv1wqFRk91z;1QSW%RBMilW#KgwwVFU4{enhfxiBCdgIvQT -Uxb!O~;&3mY^USWZvRY|rJ%_sD06mf~W0PCd!Z7bbV9b%PUxfh%b5X&qJ$%xR6fEa`-NDH%6BMF=gRj-dz@Ug_VUDkV)1mIdN;_FXuLl -_V9lwmqum@#sk2UbMS%Wv(bWoiafzzWs0-igFcVwQ&9PTy@N={P-A~pQLqOHj1K68KY#*QU)nesPMbq -Cbi2Rg`d0Fq^Mo5U5cRuwkiIp&pw48>(i}11?}y2DxI{xL||^0g+xy2qUz=0e8c|cj-#gN-)}g)s`1( -p-Rg-bUzU=fhQcN=i^Cwi&EJ&9y@2E@mA-DhleUu>WOu`p560R6YDP6)mAXJKm;uV -UFdWv8jj;1ynsF`q@bO>}mQh@2#SIPnFCVVQ7S&?%toZ{(q#YQZaGj*lo=912BV)Dh9>i1{W6LS -?f)LWyUr3D1sLXzB>`A`VGPZO0_xu3FD3lOmiY=8#=GDLUIMokTE3>A_tag(g7M#K;7sSC(G6TTdUaf -x&#J3HZPZKlS7m(r&+8c*Pgp5ru0yycL+Ptm~L5K!KJYaHZ~AM{mm(5Jt|S3m>KN@el#uZj6$M -%W6PMDLOP~XiXTjBiD8ksc2c^ILjE0f)Nk8<<=a+ILZuqlLR5escSXS_k9WTn^cKGD;_EnjqDp7EWev -PXJ5HA8OO($pDp)a*73!4EYtK-w-|*VWiA+W;Gv9MZif -YPHLlYeQq}f3lx%SM8V)7dP9?cF-`9#BIVeh5*7^&G&N(|3Mbcg1>3uiFqM*)d{0e3XIO-wC5*3=RF? -(H2gShy9yjhwjG)VMMrLt{yoH4E3%v7Kf$(~V6s6|$Zfl@(rD#K!{b5%@4o3O92ABF5SsaiKBSuE507 -U(a}7i5MF5AA4xSDBPk<$H5pnyrWuP*O$t3$T)1TiDW{Y6-yk(Dd}rbQ)pBi-3V!m+#hhnGHWh;b~?3 -gBRKg!ud80u3_JfO>F6c4DAK5}Z*(;t{x6@!5r$@v -J)6&&?63{fG9q(eaZfhmTGoP&K&k4#VR7Y6 -WG`k_1F#rt?Spo1aW`VXCZ;tihZ~d!_ebu{RabT)0StO1<4H@cZqN*vwd-jDtDI~AdlyiqRNkK4UJs* -0p&OYObpJPr_)Yld^*XD`iu%F*Pr5sTwffL>qf+kf5?skfRx1ZVdrLw4cP+33zm}1U-HQ~+s<>P -NEre;jEIM}-)O`fJtfW>Gm|&p6FjTzl1|@nMB%rjyM*35wP5SzMkdbZ=cL$MTz;bD;&1w6JTnV@Q4v4 -!dka}>!IUPnyB*~i6VT3(8fg2UHl21v9yY8QYJI|HU7zEAFD+JzqfmdyYpu-{p78jAwv*_jr~82_yNFLQFOCmT-pY@n25OO4SAn-cNB!y#jM<=bm7GW -|ngh<2cXCpUDVqLKer#Z7d#fQeJ$!f7VhaD}~H2yY$tv@Co7VW=9X0*bcW -iEBFFq6)M9673pFHUv)egp3Qb3`>I||HqBkZh><)za9ylT49(xZqH@$?p;_ZQV^o3z5?2i5l$9e8mZ; -vIvAhj2b`Wi%00po{t78Qq7-rKT?C10DD%)a=^%yxYxFZfjymb-+jqmXK}>GCWxW-k{C)xmWMnwr{Uz -`YW0v7)hnD=$g3`VFyl8CE}WC9QC2U1P55>th01Iip9+Fbu -{$a4JN!`;x=O>DHxF;?7l%<5h^ddh+TtJ6jZZhnbW4p~XzRBp2P3vXcJxyvCe0BxDI#%$4p_-Qxe^F9pn?H^>>N|0X5I<9)JPMI=B=j+2cb>CI2KFPJ=m{qM8}E5rrH~N4Q$nz!9~UjQ+xX*_Si4$ -e=nP=4uK|k_e&@Rz%$_6{pN?ndakoJ~x05`JYo3qGJ-<&^o -(TbF8|oR(|4s?JRzp$K-~N*bkL_*}Lw&_;xl%K47;84$fPsXhhy-7iVl_WqGM)jevRD-*+Yec!ilc{X -`b%VWvumn%KCOkE}`{5j0AScl)_+ -c@eHSpxc?!>m?=%m}c<9zDj(8gp}Vm~U@E$xOPamMJygeL*%kg&!n`m5Hqn59<0*){di=wrS_W9^Yw!{Nl7&v+KaGCI+ -mH4{miEV_T(3$B1+K$NLQ6(Z`LibCrR`FzT)XMPtO4~NK*MR&NW+qOCdfpSUCU?XP%vdVj`%sm!&xUI -(2L`IqA+^>mVDY=?=JX`Du%95HfH+5mU3O2m{#sJ`kOOCUf#Tm()k@x%`h%sXwtLtJJPiE0rbHZ$~A# -1a`zmhL42`_5Z_B~dz{ciOD5zLR&QHHr;+5_AZLv#=*TW4T9{6@P}DH4<;L2o8!B2<}poKem$wG~A%M -PzEprZX=EBjLnGxyWV0!kfC77?a>_EWY_XPfPRwDKmejrN$;r6mMZh&@#(}C{e(M5q*a9f`G|bi+q6ai -MxtyF%Y#4}hwZQ(z8TUEPY`BxWw3iwMsdjZd-4-s+%8V{-A{9$Vy9|Yx9K@R&>oYI;ie61O%#|zslnc)eLLKhtrydem)il_v>4el|yf`2LXdaC!Cuil>GkJi1(t`YmgP -7PQ2075S-Z*f;RW3L&@hZCq?D<|Ss^k9s2Tm`*YCAI=D0wt{pi4FfNXX#bILsai&k2eJLM*!ByC&sch -dj6ecXA|qGJJ@toT1qS=8%OalhA>-6D}TD?8PUPbJ26ee=S+k%-;KBsDTf|dT=J`ni5buS5OV6%J5NR --SPo9ROKx0nv%1N!kRL!a4slEXtzbHsNiE~)i@H69%QxE3OM>GV2g=nM@M#2yM7i6JNM=?#tLrtqsZG*l5e;ROsE9U -oYg)A>BuDss544^i3Hu0v~Z5FIkL;EZ|o8}xg`v~uWxi8U7~C@ySmDv(oHz=jsGV6B3K3a0s*00SjDsO!?x$tCNHH+#*?XU3b``Cq8<845cTVzdn6b0F8>M|U@_8XhiOie$bHC$^Z$er -@=;#Q`Y!vId$uYk>2LCELWYaL9w1cjJBJZf|32;E%l$60QfqIZWPofx3``$%F^vS#O -|E?TIed+*Yi=6xfTGNoN&xQP~y(yNJ>SurOS->bgPLuyPJh%{81Hl?BtKahz?a^m!ac`hK&J8>ktH~(w)fA5R0w?=@eT+*(q!eMN*#v!Cyh!FvmZJ?h@KtYN -O$(kzJ#ZEY+2pI>FZ+!cbJ!3Y$8?)7pQz-zA9}DSOowvjRCCH!sO7p)93DSfDXUIbb3bIIC)b*t}Az& -$F+$g8E7Ab!4EEf+Cc445T8nrGVrDgYMG}(P7^AMx?GCXe4=f$}t_+KssI@;adjWc(Ak#GfcM{w_E9j&H`R|hkiPg>* -rWEG^jgsQ0U9MYJi}LO)gX1uc;u5!<^m?JIY{FN=C_Y6G%8Jd<>6()pT`Q@MQ?rg8+$VSOjuN=Mq4dj -%q6(UVIbA0-ba3&S;;>5a@a<-~uCB_s2%gjfbuLGlR%9mrquKa)2~bsYRwGBFr)$Qc6yX{jx&}4(BsEZ%EB0rP_4Wj^ -{$itGe(splX>L2m-uNRXKr+z&^5O4JOZzXH1Ht~6&H!e${iQR|9buet`_~vPV)~0ltC?PZ_HZfeJ#NlQ-o>b@n+D|dd5K|FLp!nfVZl&gG90m2l -B#wS~46az~Z8d!3}x{Kuhh!Nb>!sOxI6cJUkr9nByf5)PT8ibsQ?$h3qFe0{*6KE{au)R>%fTY4rwO& -on(Oh00ToQj=<&gd>}y8I7Q`A`Fw9T@=f63A2w7J|#5Qs;GJM_xFE!{c3{MW1l^J_Lmodsl|f4E}Dus -D5g87TxKj_GiDXv?WB{i-Vsp3C|^*{e4}=T6-9wI5?+y*5|w*{*K(B^qextQ6p(X5X2dq39EcU`c;P) -Cpi2!r4Ks~vFcJIV~*gks)hUP(v%hD6waQZ~tSpLZH7f4k -hehNf`iufA8l2azCShNJ$1EQ!}=8)PndZBNls89?c9PvBLU?+5Tn;41S;SIX@q?RyNmV>WQ9_z99{3D^a~F(a5=CXD4(V+bp#rAD$O$44lRre{IIEX` -~b@4C1TQuZAo*#Rev!seZ&1q{SBX$a(f%Ays$amhLa~X!FUaR=dO7u-%S_*1OUK%$M20r@#A*aHYV3d -AUYU#9qrf&@|^sJjw>k^}rWeMM5`)R%=W+O$Y1o;0A*No54w>$veP=Z{NYQ8uJOP%7!v+i}$(gW{eO6 -76Z7laSAw&EN29YQzVyHD;r?Sk#c|?$`o{#pK4egP9Wx=tYbL5!8j)m%r=%MOlXkZPghvDleRjNE>mN -U_DcBO&KtG+9~gVN&fiU5e)szQUox8W@aHezz6mGks>K4P!~IVvpR^qZWdwadqmlT6%>t)PL>4ByER; -!ULCJY2@jGdLi2jln+dO*)YeR+A3#JtzwFD?feVC7iVWR0d-n?4JDgj}anC3*YQI0Cskmun?7Ou_lk0 -DIq7`+^t@AzkPD}m!-EphfD;vfmJc%$PEnF|Q8;+HK`v(}v-y- --fF$3eF}`Li*9)>RnHs5Ls*4V{wkHxb;-Nq>)rUdM7{4wqwcLeMH)0{Ez?zL8VL!v6G9aB5$Am`Ne`O -Er&p!KX1bpUU{X6&s5a7kx1vDIb%BRh61~^lauIt+@8sbU}kSWjxHy%6fM!`NoPp -W7y?VH-{+)rB%gf^3-pc1T2J%2bACUx?lZ0k4xH*O!$GLBF}h%X&oBa**Tkg -IoRjLg4alG90%-QUldmco^FOj(d6|1!6GG)#vN#f9~VF$Yob*4ABlDfx<~cEJQ6=F}B3GcH0TNStk!J -b|GajWPoRfzY8i!z{-@elxkk)IO=E1Lg(p=$3z@9#Dx3RN*(T-+yjYaQ|FvFykui_Y7sCMX8C6y6z>` -$2d&5!kGLa;sw>(211Y}RFXq+C5yslPl0>?hYG!H+Dv| -*4v&{G=FOB=z4MtuFLDZMJw{wyFJ5%KPSjmWSDb8+rJH@$_a44DKX3Q*0G&=Wq`%ma&+H&Zao|}8cC} -hTuWgQ4h&~Z*2k4Bgig6Fwl#>*>Gq)X3 -VF&9!oe&;g0zr_vK0Xn3S9PTobqn4G#_n$$4tZ9M00=;Q0SMJ^at&@W+RT|B;{EAB^#Zup}^zPkndg- --s0MK2&w`c?_SwVV2i%eY{6RalDU1kA<@$2LQ@&12-oL3dnl`}D?u~a_5Ys=nge}IStj -wK(;V6uzDswEWysYEj1^LoWAIEv|Y^9J9(uit})4wj(Z}xN>IPu)I)2hz@yj_>=%hy8SDKF-Jc@RAo(9u@;MbOLhUx1M-uv18={}JDc68&&os -B)|j9=?%GVm{aEFrkT*gijw6jGh{6GM*+*PVuGU-{Vs4g2j9;_XDZ3z^%f?s!*y -x3H=isy0?w{Kp&?)BuQnz<;$52HAc4T5?B)o1GeuQtGEDN#1b(RjobpefN_F?MAltT!h(#B*~+!OY73 -DmsY=TzF*=g1pn=Y;=kGV`ra}kVc(hv7Vb?q$H)F;Jn=m%L_4c`#HxRE*fNgZV13sS*}6Mzb5zz1Llf -ML4HIY+A{R9RFpd!#<4voa4Z?Tvry&9`#AE5m3(1}E45>W7ETzyJ*D+^ry8u1>1iBP!Su1bIES$snG_WuqT#csL(9dJ6+;72sb^dB_US@g%$ajs2=p^^VrTLQTxxl;EIx8Z0IEW@5ImEL1;p(Ih&IJmxfFO@M|rw&c=N(&;>~KZYRa?f!_ZP(@F4QnKENaglnJ8D$dJ -bAoi~lYmZFvG6ob55&I^gYBQU;w80%%Zw(9X3#2R$x#4P`C$hChUXals@VV+4!AYm_v5}=Cj<>E=_pj -q!d^NxF7FXpjMg`KPE#ov%~Y$x^-Skz>*#QT0Zp8$rj*I6*3=Ay0p440QKqFrB=;5E662iue7!NH3!z -dU^T>WkNd9j`Zay;kE_+=!FB+Fy7kI*qwMGT?hmm>r12pOE)+He)yvH{{--@YXnVON4>ZW_0{KLW=J{ -uR?AnqD++MY3K100^pYM`@0VV2SJ-fum5t*0Lxr=y($2yG^c~nT^Ka!WC^;^7`}*FD@HTToavEDeAMD -kL=Lgfrt-_-Y++OQ3iq5*fH#KW^%>hHTBG6NGjm}0e#FxmUwb*BnM#T0_})4Y1jrcKS%@)?(W0wDs>#r?xP33v97$YZ194U>E&Z$8~ -$@Dmzq9>6VDhBQt#ryfy`qfjhnQJsd}Gfm?F@N8fZ_>EGL?<&jV@{r8dlz&Sr;vf&eRJBMXj$XemuWI -zK4(Qf?iPOS$ljdakU{X*^_=I(#oR^+56tRR)lua7aAV~9*`WG;!C(Aj+EbmLIrY$1hN?Y5jjLF;#wAwxI25Hgh+Mu$g!2xw39$)B8gQ-`cD-A%j)Gx2->x&CKl5?^?;)FHrj56r -M2?c4{1WK>;w}PsE{<6zJf;wulCpfq;q{BUErS~YX5|JT#Eb70%{l&a4--0^w>?COKI7|VGebMGjvYq -UnL#q!uHcYaHI=Pg7P+74g)*9NvIy}AdoGpH3&R-u&h%~Io;d8F$r40vCL%cgcFV%f@mkj$*BB6Ea~@ -uNys-{xptTp32{fEIh67KM+-r3Ai=sjCPWMFYAMS#>o8*En*RSlpoC2nQJa2*LPmBfbtd02Bfp>BVF2 -2P2hv@bum6U7xd4vIB=&7L`xZRuv7nlQ0;^t~_CQf}5hlm$ejzY@fizHS&Y2&aKI}>R_<9Ci1Dzm%BJ -_MPy7f;(3#_g2Uq2hwWJt$^T$B4qnCoSca;XcN}y4Wn-ehnz{PbQxl_D_E_r83hL1dQHf1;yXc69h_* -jky7|YRk>MK0Ir~8zrNvM58ox-yw5{>2^0;rLntByuOBs${#&Bkt?h~_!q5(>oZ=AKOtv>F1BuIWOwY -~-g&s?b0-Bvd+*n>e#gekg!b@mYB!p-E*c|tEOp7%X#-+UoM#$|BX;xk)ErePT_+nLH} -#~IFRX~of`O92O0yYfSv`_0lu(CkX?98t&)+CRDPIX_cF!m<}AgsGb_MqgcryLr&U^Jll<5<(mz@8a~<(3t%+I+0am(` -!fQj3u7Zm^?i?fI2p-GflOhyzAFkU^ukB#?7}go{i>z(FdkGo_u65BXAhD -yLVIy#7kMCarkhYfQ6YzCO;s-E3#l`mB?QxLuW_UWlibTD`H(NZ(YZ --jXutg2P)KrYM0?AjmcEmO6*y3GG5Rt8uI+@xn|4h|p$d%tMfI5c?sQ{rXm{xOfcGAoDS~Wal)*TVlH -H@DTezxw%iEF6GCN~ZRQ8RUm_E*8?;)?SG0l#`_d@GdMkOGfe%~4(<;O#rz}A8uFs|TVn*^k5t$k3k$_2F0OXO`v?NtVbJsbmqUs3^y%qDH -rIHP7dcXI?9Y$I=<8ENA3Des(gXKLRNAldIhz&L;K?=hmTIuY?;*8^j<| -V5d8-OJUr&6Ct4?gf%n43PuEbdpWPL3O>aoX)Dws$D=b!Q+l4C!P2u0v3gge7IJ^zMqr>Z5tpmlMG*4 -~s)PiTM5>4NWdW`xVjAA892b*1xLvw#0Y_dcJAad`zo$8Wm=GlINi~2`64|mk6p@S%@pLVzpck~{=PS -3l2m($KuPBWXkzwq{H)aYKfj=J}+m2C$0XRlt@=h+1u>I*TUa7f<*Q{2D#D|^tep9xRNH<)AciMe<=@ -1>)#&;18Dwg>C_fr8l|nHVK!9dAl5Fi$055l#@Pqn-fDC&|bo@K_+`OmdhZK~mXa5Rs(m<>V!rd?1zd -lVw}hok+D0N)~6*yUAqr+6f|Zr`46+9SV7O4!v+vw2qXsMn#g8>*%8cE((^1JowaY{;%cMJA=FRkY#X -`QJE@@`$p6y38$)a?~22eUM^=fChj|uxC<$6-5%|WfqXwD0vQ_XNI$%-DB&5%+yaG)V(MGn7&UodmNf -Og=%f^iH3E1$BdyG*`D1ZFKmvV|87LEs^hCe?|L_0(e+CATPJ`3I_aDl$??0U4|L21^KE*Cz=k3amDV -#XTyt@BSw*)_h=@MAWj>X5QJE}(g&HFD7e;+EAIjL4p>dl~KCzDhUb^dBGG{W!Rz}Ot`iM*8(9M7>IQ -j4FT+k6x~jP4+NUS5}rdPQbwG(MV##uw2rKK9#th5jFC6v766UeRH2!d^(R4o9JlQd~9|sR9{i-FGmY -KlZC6`442|VaL(Zy+1mBcoM*X*KL&n>XW*(MZ=CUJI-X62$oB=kTFZtSuDtNv*{solt}d~nN7K$v{m??P -Kek5D1;jLu*5r|E*mAd@09QcIBGE3%ero35!mc)^9vYJ1Bf9SnBHj)HT#vm4MvjKDYsw=19~0X&8mon -5gz`Q8MW@d7R;WVwMO0G+eoqztMhPOzL6Tt|=o`IE-|JDHImxnWG$c_s0-^|X*gFw;b{DSHz{RpH1BS -qnk3QyeepH0ao6o5L@7Sz!XDY%v;nz5i0lA{zz}37$x&BS}S3RG?Cm -Nhp@CpvRa`94tm=H#`+e(+Vb9Tv`N1^%z6Il67o4U0t3V^#;|slsrq*siu=DKNM&Yb_JP|YkM3_BwkV -ztz_{N4NVKA_=Ag8vMYtA~kDm=Sa -sKjPhQ?*JcAS=GL3)l$v?IV$+3Dg@Gi+G{E-9BB7TsYtr&^2FVc!Wq#!#)`Uet+M5ioIRr+Bm^qE2LJ -`d&w27u3GBB%8e152dHkSo=(mp2ag2CcOR1m}!ypVO_+(X+Wa#pEpX`{$p3E=lCza&}r!aPe7P<3EyP>v8 -_O{Lw=ciJ~7L({B#S&aqId<2!=%C?G;7T|D2RsIacEueTfs9#Y7NZ$0-qY&&%j2G`Xd^NU3|dG_oId+dt -?>0V+EvUXN1{gn{s&8aZB#C04H6yS%h_LLa+sBc?G@qaw4-4hexY?*o^&`{&(qcv@Fw=Z -F3MGY(`|2=q9BM0;7S`khelgOX{r+?2hI`gAyEJ{_h>AiE8JeI|?DF#9&SS_;xz7C$i=jE&F^*Zvun! -jd5coq|0dNQ=+ed6-Cn~u{nWJO#8-M<7zY`uL<$^D8h -M1A|w`&ZmD3^EqspObEwUw{*}Ayu?873yW*tOYQc -NCf2-4DpEA!Snwf}xi&c5IH`ju&Da~I8XsiV6G)SocJB>&ID2RCwt~Blc6}4XisZi-hB|A}hVvRO9JN -=U)a;q|BQrf;Mq@qI@{DmLqt6iA)aB47wf`X7QFr+%1mRD}Nbe&1@L(EO`g^SmoQ>{teQ4Dn#Pke$@o -HrEEfm+83b2Kdlq~ykx{MBw2$Exbw+G=E0{`cWIf&vdan-K9%CO$*DMg*u*Niv0Fz02!jL5`~oDbKPm -0u`sB7nuNbK4x~+)s-&F$+C4TJPq}1i9OR*k#^ds#(}?1h+#QPlDWC~o$!Jan{!z<%73OAIJod!*@l5 -l{(draWjTDE%{yvLjS{iza3|~4O*!iHj>L6%`Rw={YitdrRcX^A3OCZ3b`3oMai=8SL-9vF;#eaH9isrv5DVQ7^`Py?W_1HRXjQZ7kdt3jjaNu`Wuo>8kJPw#oX|*! -;>8-sO!i00x2_Px87`L6tv2bqy16VGGCU#JgU=p6B+h>D*`rU>=?8j!P^Ht{cIwpt?AzmOk))$Wfh^X -;0bD(8!Ue3#L^hpj>4vte(x55Y0JDp=R#mY^)9SK&VLEk%ZKpE{l;-me?yk-{t1Uf^#8j3lGm_S#-Qc{)-k#ML -ck6LG}N{7u05iq1(HcPL8m4afrYKSi?rnUxOJ>Fk0MM=CP|>ls43xE;la!v*PTNGKX>p?r>Vcg;mH`f -R}ZrHSc*2gF?1wGQ5`$go8)pj%ksucw%n|kV^v=Rk3?M*i%-jJv%+W<@28!ZP%N-0m1Si9#vomcyyto -9$h!@UIr)xl5T*L-PxLN~U*hY92%cQHO=~$1+IZkid4~CM*jtahTJhd7j)1X9zr`dWi%?B_yC}?fpCM -niwpiM@c+o(wGugOm7WlAPB__ve8hAKSQ3We&ubT22YrHMChq7(viQA?M;T~OgKe!8D;e}9fKDnSQ?l -M6H)w=QH1-I@r22*HX^3qy&>-0{Yyy1kP(Z?@JaGkolgy~l2S$(y_lDDv@mhGWVn@$0jT=Pi?B@}^ts -ymKex0}s-c&cNm?lhYx?LdJTYqd_%;TW2DI$+pPbaZ683)jqtn}k|stj -5-%0+Q0MgMxPo;a?E=v25)gs-MVhicjms{jS=>KS@N%A*0^3$Tq{?0q%DO9bkR^Uu+x%je|wkWTdJ|qtcq9tGK@Buu;wxCX6c{!9pNWAQiWQ?}@U0)P5-)5hj`tkOlPD<9xrK -(yrO&PET?+e+mxN1FlssNdi8mY8w0o>E>*{GxE`*>m0P?ITIsFbk?Gzs%DT#yg@T#cDg8* -myF^ALAVXt;lb-luD3Q*(%>LvJ!11in_%yRvqQR@aD*NOQ{3)=zqj}G>*wEm^@8?MuDR@+{qJw?55Ld -hzoUEqwm-@_*ceL%zIys~)R^g8S77+ZH~$mK;Pd1Eb9i!pWGMa+A6=JC_X#~+ZSgrd)2CZF7?^|O^Zb -6~MexVH=ui0v5F5)D%)fo{_Rp{1y~zFxzr6hN)$6w}o;`i{V!Wp#bZ+aYk$3~-zZRD4Y4kNK#hVgKW| -vDYp}mAl0A}N=P+h=>1K9%jl;U4yZ3C2l?%jpW4; -n1oBPH=>_-*f8C!i`XY-3OmxgU~a(U&Je?Re>bq0S=N_^VI=!+iUt;x3LjEtGR(jVQ0KUg9KgTD -J|H3bV@v26r7j4xdJ0chq4b+(Vw+o+FbR)3|I1wf+>5O}L;u88j -L{v%yKPKB(Iw~ztK;BLSRH4a@f1CbQZU)sP#rN=3?^qrxWaE{t{mOiY~F6t%%rM`!+B>Z#>y8U=B&7b -pjlXNiWh}9QMtS(Ha_NHjs-~f3AS!oqLUhwv!!tOW_h`+Z6Q??`Zz%o4UC~=?X+cM^p%k25ZJ?3_BQue^so8_SIrOA2UpyJ&=)MgDw-|24Oq+zP)EcSqDz^$nX -Dg6g@jiwB>dG{P_Eu9Hfo7!4voaTOpBXo6#Q=V0cb$l;OvzE$M0^3EGJobdZ$k``R$}%~?+Zyxf(Y!pNzj5^B^4k=TL>_s3;$MC5x -Bvm5ka;gsh?03^c4 -2pmXk4nnR@eoDHy0R@uEG_dHRs0}iXJKj(1K_g%Rcv@Jyw{WXm<>yVkSy7gA%Wy>Y!J$luG&u8Li^(m -Ixu$VDFdBW8r|2<$8Jw6apOg~PCtOFco2UnRWB52+##UX%*hmLwAzt~TR$#Q!FhF@6wBnuw31yImV -cr}mhBKe78jSU`98chh|U0PkyZRbKu -c_FHs{IH8zny!v;F-NL0Foqmk?6APn~LZiOJR>3lBG0 -t2x;mSl7n)x_J7$@1i?6(ep@2Dq)QX2$|iaL(heJ)cijx$)y6^`Co!vbN59QVDe{w7R9zZHT4$@ISsPm6Q>hm3tJ`SnRcOX^%&(m;bnYX8lt8|5?BOMeMBtuo^fd4PNJl${w7hk8R88u@@aqStr)o9H+!|f4DR -&9m?H9$5TOLBO#yTV>xrwt3EVly4on{-5dKM2h%LUd(CC3^TiWXZ5yvVa`Smk9tCZ$>!bFRROEuoAOg -7|u?+@KpxHMfWcYWh@g>a9)#d;Q}w0mBPM5p}DRrF#jE2L5kHAWDWjOP|fIlEX^Ga_ZU@-Q~~+$wAje03#OJz+fEZ-4vHixj*s7O=T6)&6CJr>)_n_sE?~C@D2j4!wbESw6?(e?7EXD -EtimX}9K2+3=qN{ihN6$DfvnlqeC<+9WF;9_~vRgsg7zrj=)ZuQ$ZcC_P&}p95fEp%sPT%_+eLhp&VY -`7b#gNHyDFxmT<Id7nLW%O=5PpwQf>>Z^Yf&3&3FpF)OW~u_N-JST!yj+nE_@icC-} -;!TpI^C5>-swCYH8lGbDX8&AwU(^*qvRf%Uv(TBG-;HuY!+0AU%aU;2o=$wWj_)9D~Ji8ETI4g@a2CRJ2IHxsQie1^3bS=wOT!-b>TIYqAUN7Qe7ydXhwTuasp*dXBAeL=e|-#u)u>voKb(}8-?RNy3_nD5nXs`Gp=i4yM$+% -MBv_Vfg|M!vUG5L;5rb^+~+#F{U -e&p=5QFSX!~o}Y*4*AO(0H?hn6XA~r9r%&y+Ye+QUkjV@`X-9^c^pIHBF0FD=*Lk$Z<+j6Iy-a$p;RS -?(TGR#{DtqS;aStyz9sSQ{#rBUk%!2jDll^$EadND4a8my~3Ke-Y45zQcFCp1KFY%tW<{-<>5-&05ON -uF5ZB7@}j9wu@k{nd?PBQa|RblhM7}>3-&IOYL`IKC-I|qba1aHfcQQT&=aT|xMHa-?;x2?w80(K=c> -yI@W`Ameal7M;U3<&|aP~~q9p4N&*{XDD4rc)!{OsA|NAd3kuTg=Tc70qnD*!Fvhq?G*$-H}|OVr|!| -oH6G+I_f9aL>D{L$Qz&^3pHNdt^*hQY`KXivkIptBi|6&J>pvti4J}p$f*#YP6M|b5I(2$>J;~R$5+> -`0=YIhk%cj!BHy-ayJlsNr%cx)*!sGFv_^LY%HB>>8FVo2R(vj3Ts=IhUcwmarB!Aq;k0BX$j%I2JW) -)L=^=**@(kTbc9JbN*_p}1U0<0=eEelN2>{KSQEUQ@vLke)BpX9GBGWKk1}_Jz=vKEHJkI8ICb_)x2*is-(oF))>S)SD&-!Id>cRn!zRtBrzZ;;z -Q-3Nf$_>>0zsr-tk;TM@Ut-TM0_!|I -AO@Vj4#({y2`WzcA;DWHZc&b^w-w}ZDrlXb`Bg2E5|K6a@J%H`&&L^b+wnMeRksRS?!_7RODd23x>z5$d -<(D{@a8(Ez;Pru1ER+I7`R2!X{;I0H?O{W@$Q}4(s=Kmmgpxr7n6r`z2oB8hR>!}W@u)FGm#T1!m86+ -AFrOAq*oy^Qv!0g5<@>UfmF`eSsrHf@@x?XL+D5HgCRE(X3r9UG-lJk6hlrzW;4RRRbq3hN`Lb>}e6 -m`W*A?p|jma7T9co8ymOUBnT<&GNqDbf2nl8?OLDIU@tM;?m7H8cg8a+xNJT8D4t9>ff;ksT;C{Qz{U -PLye?@OjJqhriM=rF`2KM7kUw4@20W->;BGl(jn;UGu`i{>`7p7;%phLpZE3HvP2YMIO!(&Gtb0j0Dv -la888kUJy43uN43f|F(7evX_0<|H{)gote%uIrsAs%;v~h#jLmkq%N3Aw>a -dvh7VyOWVxV4sxwNDwc2*oO;oO_U03T3Ilxf1ebDmsBHyDChLw%hr7y3 -2`|Yz6_RpdiwU>j_Jml{mx7<&8B~a*TGU3+CTwfA~oZUmGa*8K^Jg%GINeW;b+o9MY8bIS5EZW7=Q!c -wVwD1^;!dP_6qFX0u$K*m9UJmuGblWMRJu{5yCVhxLJ6Mmi3)o@2EsBn8aT)q<#;pXXS03aA9{EwgI1 -HT#7tS8v*qgw-1J!kBP_u{iMfO+$`~VJ)kc(jCHrWmg48D$gldqI(6H$u)}{RKQW{W-(sl>{0&cQT{N -Wm)8&PKmPRbf0RXO-Nek}i98nbI)AvuA8NK-5FaCF&&AY2CIj=RGbjX%gNa+6PzY(e9`=rnS7nqPW}RUe2eG|Q7#or-wdBNxPr|?v|AecWJxR$&+ -R7I{dy+g~xZO_@a2yKdCLfZlINeMEJI<_*xj@br^(n~VsZ_lO?A6SNa?`FkQ4>%CO2%9UrOoaE1>S?{ -nd2qWyh^%jd!fv<`hzLhf{kKFa#p?I8HmR?bv7ilNBP-Au*HtgLo(oz-PfV*xV&IpYWfm-GMg+^0xNq -Ir287VbKV$q>6l=Pr|-*(vuRfljb+b*L}`?>iWf4$L{d2;V?#Wf&N$r|70yIUb9Gj!8D*JG5sW0w;lE -uX0?jZw>}HQ#}BfZmY_%Za78WYm37^4Ch^i%&H4UNF586<4$_!$3IgvW%+ -$Qt#@{Z=%W#lAhch?RT$I`2Az*_sRVEh{r?W<8P5@?;-|OlV8_`U(X7^d%U~TR?FI~i=eTx@5dGk@rGEc?tcMBkI(&SR%o9P4?n{`xiN41VvwlNN -)F4UnL+_Yj=`%M?f(;PFx0^R6Ol^^W(Ch5g>-X8Sr(b=Qz5mP0ciA_up1*$f&DSqpy?^mM1JU*`ub=; -c_(#{PV)UDxf@Zq`VQgq#6W{sVp_o7}mbrSyE?H;{a7vt(a*{|h4@#hBPAXyuo284eSE(q-G1(iA@0! -NJ@?GZ@4rU7&HMenPxnE1RqeYgl?lz%GBvk>9SLBUjn$cFzui^6|*1hpn(r@JApc5epZU -)`y*k*M&4LET{Vvtv^Ebtz-kIv`O|-_SdZ(~KwX(Y86XAzJ>;kz4JA@3~bxh9J1SF@XsJXYg~7J*CR7 -aEXva|ET4?DYh*aa=~aG48fud1tj=LB?TW1R-YMYbR%LX!W?74*avCNg&5Err%t|E3#7E>SRu(}r_o& -o97PBgv)M+{a3+<0j?QzPe3@4>D6KT@7iwqBx*_3ScUDVp>BvDV5 -kv6Y`L9psCSI)5}s{mTT0-!0%F1At;(y&G=uFY&(i0CznrrXJpa~^0|pw9_Vq9Q${}|ipJPdHatXfGC -6+qgn484kq#&*e8wsEu{=P1YAZ{!+a{XMsa~|}GCEnAbly}sU7wNxM-<5e9 -zOg!8im_<&9_&Eo78dW!{fCULZZrH<-|D$M7im0$rA4&WBhE;0g@vf$`4ra)RmrRFOq~`i7#BCwQqxh -(9+C|44z*VmnA%C-BVqW(=9IRmjUsIqX**@+n#Ma1{r+n4WOOx}I-=FRK3#tlQFm -Y`FDH`34J -V~!kkXriTu7Xl1Izhg7|k9_5+8Yfw?9<(9*x_{G!mOwI0cg=hQ-8e>gqsJ8=3S -&AEyQ_&UD-cAMXD{;OIZ+lJi;85i4-Ur6C)XRzHrj*}a7u0#7LaT95#Xi*6eLT8uSD<+Fh -Q$I?&W<7esx#YU{tmOFhllJE5&ueK(49H@i^QP@`U6-z%=M3ND@X0}jA@kX0l>ysETA&O#dpqVpSlZP -k))!hO6iPKLBv$%nriy>1^ho$}-4sm)WXo&-E-S?Utma1m5U7a*MbjgG!T^TKey8wZDvPT#9cU3=to= -fDdr#p8m+Ddm0z*E?-XT+)@xBSd*vY`iX4kPxo`QFGxRExkxa*-c>;ik*LVp0Q0Tk*h82Ue0(K0LW{z -|E1@Q<(tZ}uIk#FpfqY4ifkW>EqtefdTjl|{1vnU1ZP^_k?_Q^gj!c%tDh_PwM0pqR0t%=erYEPQvk& -n`PaaOfI!4xIWZn2NEWr$QsZBPrq^MN`b`+~pw89_61h6=80TNzqpGa(LF4NdGLI#9gDmpqoD#I8w?6 -ZtoR2hz!qB5ejIlsk<6c6?7=Z17l=G=Ea& -K|#d)ra%Tm%=yF}~LDZTyl&C9H~E-I=Ao-@mNk75SV0x>blCXe2BuszDNGtyPod;yXLqSr0YpU{xhO3cH!fz*+T4Cw8S;Xu%GctU7VPW~FPYsdLw6o28eTaPV_I-dOak;9hqF5 -^U98h6kdVVpy&3_!|&nK9N$pG`B)MamjhUDS$|Apd6XMg_&#pg*XvC{mPOWuC|R1iQ0Ca^U&sSpWqPW -iZXBls!Jk%`!2fJ%-}lCJvE!<1i!A@>^)sTv4_biSCXfU^mbE>*fRr<1C8mX)GjyWVlmbO88MBc6ox -h|Vw8($PPJVqf2$t^{fg$*5>7tJFo2$+^!sVKsBat-;-*WxJf6K!*#j4N9)`ZiC7gloB9Wn_i=|=>Js -@dBW4@RH7PbpZH(Dy%iBAWjUGcS``-&+rMUNdZh>p;m3ptl2r3}GzPVD{vP)h>@6aWAK2mm&gW=Z;5a -g34#007Sm001BW003}la4%nWWo~3|axZXlZ)b94b8|0WUukY>bYEXCaCxm(TXUjF6n^(toSHm{S>l*H -Zf&N9C@NJ5S-?!q6Gfmky#bo-ZjJu?J>4LpiP@d1^&!H!eCIn?a|L5o?Kxe)NHEd?+>LJUU`82VGXYN -o70D|V@W-}({Jt&{nh#S}eCu8HuHcdIf(k)d36jA^5ED|wGofG^UEz09W{^=KIep$p0`djH1Y60sgcD -dXjs|i -0iF-`lkG(i&J|3D*MkkNV?DSwO#J|nEbk|TkM`KHToXwL_Bb*T;qZhWW8VwyKbMGxHZZXk)=bd|v}^O -KvYJ~_=ws9Xa|@Slq;i-B{sLS-RJ36k+8Dx$Eai;#L&Bknem2qz6l=>OkH?Y3uK}>*Z~${VQJjyX(NWc-iosV;&`=@8&2XFXi -+gPadx*<%}isEvOFiLM1g26$ln`MYqLZWNegmNsppL3GO5)?g2UaYQbO}uD)udukQw2;7N6tvdc8QtE -mUNX-aYhhhj)FHrg%Z$CD{C3pS!i=mib>$z&h9LRM -gzJQ_1%6$>zNx1MPa~jyhzMkd2%}T6kO`Nu6sS?0jlrq9)|^d~0x-NXeVr4qk(9_G|1My0!&CqYemNDM8IzsN-bxdr&NVx%w(mlQ`Y?4=cJfopvr}6`QIAW>wfNdN(rw97OA-fxOPS#C4Cc5-RSb@>Y2Q>2kKJ -SD~4MM*L4_9@c*Eamg~W1sK34pGP#I{Zz(C?4gKLRDEF$V8M4HDF9IE!^f6LFOXJq;&e_+tv=_;>^|% -C@?2NWQHyv;jMuUG@>kFg3W|AF7TOoC~w@f)sLCwA!?G#gww6gsV3nTgd{3oTIHg$t(XP3z?C6$C|Ul -E&g#!nrO7f5~n=SBPnP)h>@6aWAK2mm&gW=RjjMguei004ao000{R003}la4%nWWo~3|axZXlZ)b94b -8|0ZVR9~Td9_vBZrer>edkvUVi=YLN_E_qB4~or)Jh^ikuFKu2m-^P#ig_@MKVj$wt#$q(<|Bn3HsW8 -K>w*<(%I#by2MFS6cJ#Lc6QF0v$Hd+1L*hqFwY{EE}lSD#{DmZN7D|#%vo43B{2!Yr -al<94T$m#C$NZ*R2Nn-0gsFg4-qbckZwcmnD%UFIb(^lrf0T=Nj7+JM@TUxo;g8>acbFC@LVPzS%E`* -e}EL=xIoT{6M#PvG+Vk(3D}l#Y7-c_9x@rubMViIX&JVTqCH{;Ix;lh5MjEGPrjZZ8^*xUm>Odt!;?_ -86sMXPf$w5g56|Y$>_D7h*X~;X<8+uF9eWC$=ZdruQ{S-bwk;@hCgbRv}qa*1ksyJqMV -tv?QX+-b3(PxL9Rfx6@xDNSM&Z!E5R~zpJ`U+|T1dkO)ss3!dp -jiJh;u%w-WCb5#=5YXr%g{7qo8PsR%fn4Oxxm#SVriE=m`n#N33)z%MMmNhtxoo5G@J)m~TyKw -VM?e#~Rh!<9v^Ko0r;Jytz0?Uy>&qK_ld-h(LMha1%;v6iAyTjNY;D%0dP}$esrN58HLQzPH&{m`@3y -UHE~2fh%3w!|r6`; -2e?c!%-NcNk5^=gG%1zWr;T@$Kskqm|A%U12%GWVd4ZGAw9;fKG*{*QJu5mHkQCAC>(<+3%J8PTAMW- -XC@UZJN?HAGTjTN3~~baPD&a0oxzB2H1m-8NN?Hv<*Va!q9!yedz3p=rw2W{*QsL8D8`sU%q$j@p0pL -&wB`IE`h~L@H03Dn&21p5P$f=zz4Z=tnXH*FKvlioJED>_56b)BkG+THKRqb^GK|(@Hg-ql17q@ -wvcD?(+twKT15ir?1QY-O00;m!mS#yRfAKI60{{Tt1poja0001RX>c!Jc4cm4Z*nhia&KpHWpi^cV{d -hCbY*fbaCxm%&5qkP5Wedv2HAt{Kx;HP6+sh}E7LXsS#n8v7eP@JT8pxY*rGsEyV+yfq%F`xQ6N9>mn -Z2^rq@{@=%J_%9&tF|&x|Ra!TIBJSnEnPn`h9u`utm;iJ~(|bbDviX6sLRZo;-z_(q^MATuR(1U -nvXU-5g%O-|S1Z8?W{LMRbPXr8IkGt`|)5ST+?@$~vR2J143OFQ|A=uE)kg1O3~JwMs@Ireoq}9QZqbl#rJfhXA$7DN4|0+;W-QjePWA;`R8?KEFS&mP?%0eeZG=4bZu2SIka -+D2e^Oya(wG0hWK&yyJOW<^7Ux+&`G6ln+INw4F$6QN7_id7aF`=zK|ELT)+A^>2_OMX;F394PX#MH~ -9SVO^CmQ_-lx7LwpzFZz293;va+2e{83@x3QDWp_@ZybYG%ybFZ4=vGg3C#CUDEP#wC0$49Qw=&QpzQ -U9oZI?ZXR<>=+0{{Rf3jhEg0001R -X>c!Jc4cm4Z*nhia&KpHWpi^cXk~10WpZ;aaCyB}QE%c#5PtWs7?B4cwNNfnUrwd%1{Sbt?9^)#oq7^ -7W(|91Y~)>s@b5djHel|!l&Dqx0Je9&Z@&3<#`X;i^6kD5tF?ko+=ctQ5BD$@d9lhRJaJrzz6uGyyi} -*ZuQriLdvU(`)w;26;E@*_A*IMOP&t&6+Ylq44V!!^68w*{B`k$hg;!+C5N3*YrwnuKQ9#|oC6}(yx@yHtcoa8d}+hB$X~>gFOe@&_`@Mu3 -6@HH-p(3a-zzY?v{MDM&~(82v-R%tjnVd4X0x3Wt-_;Y*BMi9KsD4i@u44J;EdG(8% -W?VoztI%l}WxKMHY`%oW^+V1u8KbXn-b6iQyfcZ$!_sk|N*0e4do5Eckp5VzbQ)g`Flcg?F=9Vdq$uH -NifZ4`u;F%09F|zkXQ!_1pc=Y1?8G|M|kox{atfjiiLk_p}K+ZD+j$O|!44bYYrd(T3Zm&u1YqhR9y}rIfYoD&|sE3-}4*ca(G7)lKx;7ZO~-&KQgG$Ky%WtV1G?ck+s?azgIr2WypB -H6C;uQoju8Gm0&vwl!~X$~~yznc!Jc4cm4Z*nhia&KpHWpi^cb8u;HZe?;VaCwbW&5qkP5Wedv23Ztd2U_Rn6a-08u1wnmWX -U1vT?9!`Xf4VnVv7PP?Ph^KL(n9LyhxG)P5Ux^k`8IDciU5R;D|Hx{mfWCf|L6vurj5t*H58qtCJ@_6 -GcamnC9B*^`?d6)es&(eDoNm+Ss*maHgzH9j(mn=jaH*3uSlOIc;ia4RlV$DAHWV -utTW|V^Nn-YS&tQ(X|TN4U#!iwO55zP#KF2*T~a!wlPk@m2NkHSszW;LZuWSVxz3OxQ4YY>QrXd+8F%Q=jhNK(S)0!W$yqBzMUqXMbUnVbQqQ^FCEBM9kksk0UU+zxm_AU>I69_?7><>a4^6#B-3gQ&Gs5s)DX5i&h;wwXJ#u|7h&^|K}e2{(B$)>f_)4yMIUSe*sWS0|XQR000O8HkM{dX({S?U<3dF76||VAOHXWaA|NaUv_0~WN&gW -a%FLKWpi|MFJE72ZfSI1UoLQYbyZt$+cp$_=dU<$9xQJ%9ecnw7)yYzOV?m&62u<*sM6B0&6N^4l8U1 -Z-EZHaM9EIl8iGZh>vyg?gXwou$aNvB-4bfQo&FpsjGX~MmW9_W%O-SgAL#xw$Y$Y1>=`6?kZ9i0YY23I{8_#|mTx_l@7{Duu1Ku&+xJ-5 -`bIIQ%M{%;EQ|HLIQ2;gT_ea(Q_PvNYOyu+c+Lws0}f_zrzC-g|G{ayC004&1bUxVF3TWYFNw3tzj8Q -iy@LI6v=s;|^F0AB@#IE6S39ffCiOCOqSS-tt^m6p0UNYg;2@oO7U9?(bIXBV;2QrA0vq3z_@mGdA;! -@uN^R&c`XxbrG+N6LhEX80}&}6i;|vU)I)>Aa;ilWxX6NdpZ# -U6e5f|Fr)O8F&`N7QAhuhJ~Q}*vGpE7`#svO;w4fN3re9yaJ30_!vdz@5aH7hoG|hehj+D7Q0TlaDWA -eiU9pgS55L5)yAn9|7g9Oic0J27FTKJnqk4>c=VKy2qIHC_V=VZ5&gX%=sii7f;`!70Vm?2g6H(NJ- -FBkSfCY_+tm7Wh1=bK-$9&sgyC`2+qM3a|b5$A-UISKQqY!GVXzqR(J6PRb_eDrSZh>0INfIHoTkR<^ -_rjCwA|%g96KoiZk5mn)U1)u(E6wYj!D_X-p;Q&MLJoHoe3Vt84~_(+8x}{@bx<1`p0J}Jw~g^(l+3> -2B6?#g@6aWAK2mm&gW=VDXxhYT+0010K001BW003}la4%nWWo~3|axZdaadl;LbaO9ZWMOc0WpZ;aa -Cz-qZFAd5a{kU=F_AtH+)y~}ZdG!{TDnS>wOLhc$+6^1ReUaZNDRd}fdGRS(cG=gZ%=p63ub@;O*y+s --Q`$inFM-zdV0E_>FH_Ul%0KZ#+F4Y^M@I0>ebmF@ycj)a&j^%#eX)Us;ii%d|QjM8vR%IGaB8mWyLl -{+GGOwP2lx?6jazIYGSk|)0tC(aN$qn%;s(8 -!mwfR{U%STa -$-$V)`ZB$Dk^$)_2b3+*Z0ZgMe_3f&8yc}$=i$jpQh|V)QQNIbtpxw1y5lnry~~lY)i2fd74zqwb+P6 -u994cRHTX#Z|b$x<`jj4i7)vBUJvuDLuS$z`xfV=6}t -WTEEaXKe736Jffat8p6~8nw-TEl&*@k>w%cr36pyksKJgiV53E*SPQYyc!VPVoW7JXqv|~q8cd%0{f7 -oH@)6T<`wI(rT*ZC9AWO|3r$&K@%^BGYRKnb~2ehN-C8bR9`^)sL!g!@u1Vg%th109H5^E0|#7%&KOKy~Zb#(a50O -+uPgMFJHrLHfKNb3}D*5b9en8S8(Iob9?pO)&0Bct2?-O?olT9*Ke+F-`~THn*tWF(P)|Rs$wqzelFC -K7Zr=Yz+CDH67&|sqEz%xCZG9e3v=e=urIL`8Lv#d;_;#Do7iVr`*JL};a#Pd*QW?Z9F#V~*cCX8$Q#UFRrN5vKtf2uK{{i2F{nAUa_GukJpG45oR5x=Htcm`7hG -rj~n24x`M+Hw3lB@+8Ve*v>rJQ@Id@-t$zEWjiyJ!)es5W*$SK0+Z%-fR}ouvi&YIa>{CqDIeQd5L@6 -HvDrXe-?@p{E(Yw<8X93Be&;IsA?JyHuv~J>jcAivgR;5eVYjo#^K#A2J529QpRgAvn@a@m=$6=>KQE -`n-;lpfEAY+DEHOP9bi@Q*lb~i)mtZv@s9Ag6?4>LFlEYep&`v|4cQJLK`205^U8ADz%~Kw-#DMb!kE -3>qc8C$qqs)1NDGiOFKVW?HA>cyTrs;|v3=1n+*Ys$u(l@SC7EhjWUP2n7J(O1sYXI(VtMV>5>&Ypx+ -mVQi|vjHZDL%pPoF4u`ef`4RkDRImKW`1VXs>Q{`M_pk%4w+EoLgITst$+4$Ka63qnixc)|C0plu&j~< -I{7em7y;=RMz)d<_$}#rWqVOO*EkT`Y6?=-_~SsBH0^o(yc^dASi4>K>0LLScjCbzW+dgj7i>DKhFrO -P*?i7AtY;o;99--*P;q^9KBd)PMROC|M!>J*IK;Fa2atAWzGXzs)Z9UfUw-v(sPu?QpwRtJoa&g?P@H -PH^$Y_zb}xlm=&3W*`e%qzIojNrq>czAnUfTXy8TbRKBmfb=Pkd9YnczCu4-7)`ayh2p)kcNQG{MY`4 -Rl0?O0u-2zx@vfENI3hLR*OLgRg&2$e4aM*r~3k>r$J@_bwaa_4C#aP8$O=6cxJo7}MqV%>gV$zF1Z< -Q!JYD4d)c<@Y$t_-2tpT-)TN#i>SNSz*sxxqO5GYT*W|L)JBDmiCU(73^hr*3jZhLyjpDizZ#K=qsJt -b#1d}7-5T@2JG^{KAk?wgt}Jh%wn+bDf7DR7-SE6a5qiNds}Ki?`!Bl!jAf9nk*9(If+nbi>7kDu%s2 -KS-x=H#_$c|I9QV@QGfwHrj$V-Ts5T%Lt1$Hyu}Qg3RfW*g$M$ssaeoEt3$~)?$>dk110Q-gNxXUPyI -dR|2ztGnEe((puYN6vga%zAU_(zkAUwGa=WNAcWpOV3)&^y{M&3>waHR_wqdCNV6 -DWnJxLy^h9|x*c~GMl_6^Z!p^jlS+D<_m6NQ;yQV*WN@#a^zHeda*y6+BiY=F*l(#to|`Xyk`@#O7V= -&TgBQmv@ZlxBPv^V7cl%H*uNvudIA78F;@3}%f8u%cta!c8yRB_bcg|D-adyqo8YpMlX%Dx`ze|5ve;g6Kf#q>}m!x+#{D@_7g|D*XY2??E-NP`tDl(BS>M*F7?mj -W+3&ov_KTWvx<(Iwfs-GHMQIq_M|9;#u(s2SJiT#UO@aP6RNNCPqs-@(hk_2kg)u6uCD~*+m9i$uG}z ->KN33Z5XuSsMpwQ0ix*9N|iUm2xQhXai>>BKI(G8wub6p`*3~0Gx@39_mw#TjZQAD(XS+^j)URK8sl4 -T -G-ZJIzgq1OGD1H-wwy}23(2wTa>uD;N}Z+lW<+AO3XSz%(6=SxA_aqCA0wS@hwWG3`LKb(II6G9~B(S -7^zXl-`7Xu%oJL(;@l;;);9R=+G^MCf44|U-dA^ff|_on8K;Wz7|ynXe= -L~Q0ExY{8>(p?^KN&h@b8eXpR!vtAtkguXcdg2cLHKxU@Vfw9ET~G_4(sRfrcW -CAJ7r9C@6b3A2}?3*GZA^%jG7oWQBfFp+)h0uPQ9S{PaOb%k{D;OOe;vUY7(A!^)<7O#%&F4*39{AuP -FBnReH#PHZ#>q{ab?)IYHf8Dd`>nE<40HAi{m;k;L@(#_qI8htcwj9dJu7q&G^V`eM6y>gE|iVBv!W_oURhZB25r;lQt+&NJ?YSJpWdFS32qp8QkX+Ce}SOT962H5{(+O-PM~^BS#UH7FPv0rW{u5H?>h&GLjk%Ec --UzxHjM)R17kEUg8=Fng;~4QI;*A_M)O#ybgl{iH~c{^krixH)d3pEl~3&PxZ25DLm;xOF&?9pQB?%$ -6|v;HK91)Y0dQ6~;0RW18EWo2$$FcCDoq^RL(z?HZ-iBxbf0T()D>+p9wx2AuMsDYZ!jr-tT -vM|@ruT(`6pk=WQrR^8)=F-4PS7r?S8<cWgua$S8+6f7%y}Q-xZSwrC+(=E6I?^oHP5wxme_SxE?296$<26bjgH -?<#l@FY~7WqTH9^+zVXl;gcAg^icRGC(1D{G&zb3a;{s=OCO(;Rwq`Z`d!j}99LnR{&J_{R^>^@DkT! -acTqDl%~Ta;lb|4d3fSJkHc%5IXmU*Yp60Kn8KIEnLPe%87n#&x-uZ`L8j1OO^F3@&ui*lc8nc#Iz<& -YE;0bITPA}30~?092NzhM{88Dk0;=IMyZCASPjWc+GNt>q*Tx%-J0pcr!I&dD|&<3NNJ#A^J64@$u}*1V!)A_c3uk{fey-7 -+up(NInX(^8Lmw6ModT-wCozHyd$=;_!=pHUF=+#cd>Q|m!T$Czj6Gia`%PxUCb311={+Ve09_5#x52 -ZI`AzfP%n$FRdi=R?C)jUtcfK4ObUArxNSZHxAv0O;Rc64B^PlW6X`hIv@y{ -1}V5;;2=e}q~J;;Pvz)UW}s~xo*Mg)XB3YdmT+7Nec#67_8_yq?)p+zT1+@Cr%ZQ;o}#Z!u%U~5^cbD -OeQWPnNxt05q~#%GuD(dAAGa)1|XEhtb|5l~gAgwGC>EV+4x2sx>uJZ4{Z5$0#7Tpe|VZIZfb%|M%I< -h?h!8|dD=SE~dEzIWy|<$TdQ`o&y8QZ{oR`fE_S2Glxt{^ -^IKntOuL}y8$Jg_AMmfmW9c71p8`LYpD+qdHkD%cPN$KsOHGYCHS?i+j#Sc@e*qMuMm+<_ -3C5u}*CQ#WW6394l0zX4E70|XQR000O8HkM{d$g9t&!UX^Tq80!E8vp(SCbpNJ@#ET -esK(-NpJr79`Gv=bRaiUc>g*Hl!+(YR4vz9&KEX@ZrX#LH(%|*bPYdZS)yIga%q1bL~$-tEDgqRuoUS6cluxu;q3H -ibUq%zOlbt-eN-1OoI=to|T$in -I>rHn9R6LRj6#X4O&1BM>3dA=;J!H}6~oorvkx|OS3 -{VprKL@aya&fJb+>vA@LuU#%sWT>BpYX&Ei$t9flWlflsB!#}{JXPvO;zQ>Q%8`5bgV39x{4>{# ->2p~pNv9MAcH%qqw|yX!BxI&(#UHIDwxVkNGf8^BrmEaZ3S|LH-vxl)E0Osu_?IMicH}b^B`Zq6j(C4K0ms&jzH1dp(vC=O1!Tles|whW@P0*>NzY>~$R;+)g5Eau$T3<%5S3lDbZ -}6yejGkzOV~{5?ob$+4v*!Wgfu*I$$4S(>Xtt%3sjYDxja!T$QAEj!AeNmWMfd@qHd(imDnUc#ToZyg -2s!d4}zhck%(;H^B8NK#YvZdC|w2k(0yvqBD({?KkdP8xHJ2S-(Sev5z(aW{Ih$vq=i(TtJSr}OlmHJ -)(#Jq%2AX97^ADiiLU7Un!)A8crv`anhcLeXQRmoYHLD(z46XiFno{M{d^t-n^J^Uo7U>L$9h93;X*8 -z?46cbm8zVRV#N7G2%=*T>lSy!g`BNhE=lLK*0VJppFxUL(kq$gT&r%qq{QI>jt;I5ug;IpMs$FXfVi -4PZrmY3fo^5S%Z_m-s4=)cm?R8R(r!ysI4Fu%29lf6cjODX?l=#PW=Uu3T`AT1$f6*?gvERfnOc?HZy -H!bOQrlb-H%*}&QSYpu|!08Motc*T%JN%Z~L+1w9ogSG>9b##;m8PTSt_6pTc -k8?T`s@t`OnMB>U{o*j6Ty`cbByCCXnKEtzn)BLgL`l25$xT0s)v=%_oE5ty0CT`4~P6WmE}O``EZW5 -(+eJ&1mWkMH*bE~uZI~Q`!yV{p)z$V>+o6AA<_u)!Vy8jrJ6MrU3`7J!HG4V=E`8k1XhOD1$W|B$qcA -;T#*}<-XN(%EvXx>7BclZB6{X?t)CCtL*U-MS#YmC4~C(UK<>I1e!ki}S0z3+HIBoWNOaXCw&%i6VX -%9*^)75IhKmr5lrgXBX`Whal7#Ki>1M?ZnvY&PE$RAn1_o{Bn^3p2(PODPxFh*W2y8di!!;5n{Xn4qd -q}iL*L0uwDyaFrxSX6`oR2>;$DbeY0nX0mXI4O)$DNm;J{cVxf5F+|?9S20fo3G1LV2l7=w3Xch2F&< -PcODH9)LbF_MgF5YW5%+caYr(p91`k9}Dn=pEG9-eG}15ir?1QY-O00;m!mS#y@P(3GF2><}^9{>Ox0001RX>c -!Jc4cm4Z*nhkWpQ<7b98erVRdw9E^v9}8EbFb$nm>=#lnU`GAA*ykuw6L!ohWX7b6!Zuzf&^G$1H(C2 -dTR%}v1OUnIS2egr=n0yFE~SBUT85*wI~a=NQ*4zQn6Vn*^Q$}^ao;Dl~S`(xi`t(n-u(T(U{3_~~nAPba6Zc>w}(dm2y6WtD1oT+6&lrQ$=Tv}_>Cd%=|h;g4FTQ=Qn)i7u0 -Gse`Sx$Qwwalw<;c7r+viLk7SF2>!qmppQHnG^JBrs|5LKu<4J%;Ns);-R1RNa`(4Sm&vamZ$2b{-28 -rdeev<_xSO -SU@(**5>%!ak%vPhSpnkrRlRmvjci(InECNbDfM)b6zl;1B -`8z53Vj@6BZ;u-MU@wPK!!Uz(2_E*`Tv2LplM5``fP+*vRL#TPspuA{eXt)sg$hu&a+jeOP%zK`{y)N -~tOLLkrcnAoHc^@Tj7l;TgaV(=Ao*bmi?E{0v%qCP*e<5Qnx`S?UW)$y=xjz!=b94md6TRlfwx?%Jmm -HDlh1CP%NBm??C9!U=1kiF-;Vpv=_69ucd9GU9K@)C3pC#6j1J&K6|Nesxit*0@42|afwVSuVbp_@oH -PmV)&S*Tia$A=;`k>=u0p0RJ3ws7zlGGYrYQ@{6Z%M@dTw -R*auFKP`c{^FL<>Ne7Yd5{^Otf0IN~~9Ojz69MsF?^{ZnFrv|4n(xeu723-g`uI)pt`z<*HRW`(?$5S -9Wtb0F``l;e))C~FOUOc7w60Y1L=$C*1g0BsN)9Y3Q0`L&Nm(_Og7_xL207%e=c$&{4Q|QGGwhUSW=p -mO?3emd{(?EG>*n%d|9>3{m-@0(i5cocxcAYix84f@EH_8?93IjLn+KZ0*xaIK!ehE^XmmWIXymQtA&`tXtF5lJR?@1zvjzIKee|ZrEzmQk>AJUwXb#V0R%_HhyH+I|SU@&ZKD17i}Yb+|1f*^N;;Fcnu;=kb@ -Vx@{}nHE{O4BvPrn7VQqcH&)i;{D6n@#)vdH{0{lbKfz#t_Y_?BjeDr5%qmTV{z`sy{b$Ezkh2;cxl8 -^1E-53&jo-M6?t4{KwnJ-#=b%1!cnZMTtL|!_WL{{IHAVwz%hT;-nW)JEd$nZoLobD8*#RV6YY`D@eu -8BYLocPs*83ABT?vhf5P0?oYMdO{AzO>mULX~K%;G|zoJ}cmO@bN%U{}63$8lG?T9=EZ64V4W8dj2`w -qF)z6I>>eT4<6qOwp1ct=*Vy2orabb9T0vFWn9nj}s!vkvWXbBpE`d6pj*pb{%2s_nca8k+o17-FQ>l$yr0xxA#_bZxGa+Fhb<_5cy?AlIO66R2d;c4* -xMtmvg3=+EFsCs`M#cV|07A-GsOFOWIlVWs9Ix>Ur~6oK-tW?TuDlW4xgY~lOiNte8Y7KR(?iN$oI@D -ItuEcD(`>bGERoHF50Kb0vq2Mrsd2q0I%GF|B?=RoGXbdpzy&Q3;4AJDAn}zmI-Zo-6Rs6j3odz&3go -*LboOuxa&k-0vfWE6vHOhlV#WFWrkV$Ac;$#zDqxak2V-U)+$?*$dt9RhLAN3VM#+@64VgGES2&oEA@ -;h#U)agn;Kw|t(@o%myL{lC(_SBR97@X-2HPx8=V}axSHE34`@o`iE4{uBZlBz;Jpb4&ziK1e!qQNT?AP(T2IQ}ig{QY{B{va*)oy(?#N;|H~+qi|OQ -p4aySdvE`1UP3F~RY4_rMb>w&GcBv99?a&SKte=~-N#`|f?m_r`aLoJ1GA{Z=~Cz^k7cbnjFCqCdD*Ck5-$X9)SluL#&Lj_YXHt>W4g90pH1e8+*$wE6K4c!Jc4cm4Z*nhkWpQ< -7b98erV_{=ua&#_mWo_)eS+k=^m#%rwuLwKmrjDtr#5~BXE|`S`NFe5$HfD)Q5`#ee`gXL3jELNk@nz -OI)@9b6mKnLj9UO;^{8{f>%a@@4`AzA%A#FF`4*x0Mhf5Yt-wyWYkEb*q+VmTrw*4i$#9^O)V~75HZ7 -=;3`k&uULsX=3|IbcV!zisb-~PwXhqM1<898k)A?RgbjD~RpK|q@(5Z|Orlc(*iy)@veB(;g74U=qQc -!OXY6yMO{hDJ9`w29CSLT!+AlOP);+NAL&!IxpTEb`+R3A(}14a{u9WP@Ux1lu6^@&k%&D14LR8#vie -*>VvQT`s~<8=BmZ*ai#V#zP76#}Q$AgEAW`+~DvAr#4Bn3BfhU4GnK%YJ(w53z;rIP~?WdHgUM2=nYP -9Qe+dQnFun+&wj(7!MLaH6c}-+0lq6iMM(OMJCLnxY7b!1L_2&e>7n*jy -3ygo5Xp)w#U9pUaEPj4;O`ZB*+8rzN-VfoVX$NFmwfJv?C7AAE60jIka>N9GhRkbNxt~}nOdQ_`4DmQqQplt)5r|sQXXe -B?w}v$%jIT^!G4}XM9;YBs0KK{HuC*p-NPK8&S#c0kL%-O6m&!ws;P*YN6wyqX7OOuyFx|WVuK2aqPv7^Yc;elt3a?Ep4(zfjKFq7?Q-ZVKLgODq(vR -ryIm;C$=EUy6cUwH?XR{;9*9WdaLA757z#L*P&a-1~o`kI-@8kWY%oG>e+X5}Mv5R|88`LJf$lUuOr@ -;lxdw8*gC|81`roeZewzEW?-G`^BS)pZpNbQ|x*7dp+6QVe~qz_T|V(oT?z -1aT)%70kw`s}{6ujy1R#(NgT7W_YD0iknA$MeCPFqewJhj# -_$5C;e-v*Ru)IeAF`Ep3M+F*gLVCjh9wD3T?Fz_psSw{F$qQ2PFZhwZ214M4AAmnmv;p%3x{SLF7cTIK0F>wr@tR5sxCnXyTELOnhFsvc!N4h?m-#I}5)1I7LO=MSa_s9LPO|%OB#cooK0X -eyzATj-SSFqMZNY{#EH;%!m^cV5p$X!o73>IU0vCeDCujr>`Kxhh)1-LfXLk2CsBf|5PSVVBl-?Gky*P2(N -Mfd -@eB7Yv37?*17h79)d$r)brom2OuW>9nX*Ora6kyH7Q`q`Yo#>my;42nChyla{iM@@L@tHIxoX+2iYiT%uRXVL>NVGJsRTjs&fT{qiU*X)o-N>^OA -|Kei8}PHl>5z5CTXsZqu64>R;Ekh7KOG9PKHkrWCau_L-|AFb7#4XVViEB|r1_!LEQhVy7-{sH52Y^b -p$+ZB$9a3M@%tqCQZ`XOdw%@=ZRfqVjhp7h|l<`dB>gEW -ga+#m0OJ?iNbCf%U&BT8D~uYNO=$UPKH@Td8CQZRWE%`#L(?9)j`1r;!$86ZlwNs>(hu0X$NL@hE41(!;HfiECu0I8DK1b~8C&Ij -|M7hXlo27XDQMawXo1W+s6P{=~e5LxC7l-hzdfGK=KW*Z{f;0v-As)AEMFO&WjUqb=#b>II9UjpveL= -Jz6FG~=?>VUuD3z)W^iB1o{mO2})n&`rPuK5ZN=XRk?;>?;KXyd=569n+|U8nX#C+Hiu=UCSSA5aO%A -0Y>1Vn=srE5uhD^x0T&ns#Ex?FV_LZ+zFfv>}T=)7S7uKhg*2>T>1bNf9eCz>!f@>7wG-L -027uK0Gf{Mq9=_j4}Rv@eRSNhPZlddoM3SpZus@g -&ttZVns{nL>4L6)ipcMUSHtnN^6Rp~{ry2m!x_}xW}_Jw|;PI+cbgq88Q8h7Vs?{qGPov(Z?UbDuk#7 -5o8ds9*81mYPln2*hUH(uMA*vgGLbWiPZ#rFcN61-|FGhsO;S(0MDmy9u#l4&dc#;R`@N1=KHEev -h>b}xBQXJfyXez?C6AUZ#(YJ^+eF`u06(#!TExZdj1+9<9K>pv@^K>e24p0xHaKexIR==e?~m -fK<{tcyu58{O;icbJeSXb>M2jP1_ud1<1SY{X`(E!ZVo;sc%FE{G^V3`l_9LE;rfDQ7+1?`@r488osG -3GS?6iGR(NPq2W~%zhI+^44%)$McZ~Abz%H|}Yk|fc>8KVX%kFMWKugYE04e% --0~89yH<Ct)YdJ^1$;Xvo?eBf>@G5esRb7!+@8*Fp{f?jOmtFY_7Vc(8JR9)h4$M#YK%kn!n-sRf-;m89BlQ96pkJJCJZ%P -yY#{$I90j+lE`fqyLgAY@={rDbN -t>MKw*-kagrfXioq!qLtvD^QHr7|7@=W?BtaXFd}$oS$)X1V)kA6#k1@VT!Z+Fj{fPs(0!0ZZ`_jBH^ -QCdH_*9d{0lKJUY=cJ|l35&}VC)DWp4i0L1_P25-4Mj$%p~E(^$Ij-O#WKi0CEKvWYOl6aXE@zM@V2P)c7D7c;983&@gz;0A{(cq&j4|dm -?xD1Iml;U~Xr;PWN8%I;N`^85F{p2L`G|v9;ksX}cd4|r5@tq=8ga5>*?fbI4Xj`cYDk!e2cXpf4#M6 -OA0an#*@`&qhIutDaajQ$=vEbeJiz7oLI0Hp_{S~$!w0xLKj^>m0AIg_|H=bgo*(q~0Dm;Q{?h8w2C|V< -I_2naThkGwV+UrXRc*y7qqSyHbL|5&!#$y8#pTIt2YKT>V%-j%?`tf4sH72w7j3e3gm)`og1G36D+K|5AS$g)xIW#QJIkfxj -==TZVR8rPQeOy%h~LaE>(}OqXrA0L98UZ(UaNHdTn}d0pjHZ@Pw9BRm2gr79+t3+=3{R) -9*uh}(;=(NBWi1Q=S@Ubpd`i5*xfyADJ00;qmOw+c`5@pKGeHssV3DWW0F=OJwD)4Wljm&Tw=x=`YY> -RF6+Esb?%q=h(Lowv4SyCrsTe_mDNsXN!0~gz=d$Sg -xZ-197fG*rTvl5OA8i)_L7I(r`!G!MKrD$*AW8q0-=qiN-%SPw@Xv*#8UjU1l -0RyD*hK?5pJuqT`Z2~L?V5Ef6%PL_ubh5ZcfT2TDi>ZO&i|IfCnDbtM#rhe!Ga+hJl&w^Vzz*b0Sg7pHiQ>z1zZMnfolU31|}8@i(+2>fAVjbCqj4Kr)tzVcQ#&&nKn -KuiTeMTd1Czk=E;A}Jo)<%@Sm9{drZBV(rv2hmiAW&BpZx7cTZ&d6w2n}SEm&>2YwY`JueVP``AZAPk -ZxK$>x$|=OGjAi|mD`o;zTQ)a{^EEf4qOeu>xf`#ws8B0ANh3g36TJljpTUZKlv&&&41e>S4zZ!ddhw -w+65oCjEr638Z1jpl^d*&bAGFUi`h#}dpFO^I1FML7BDGF^Nk%yxHIxv|uR_nvTdP}qD_)1z8L`jn`e -=x+&y>u>$VRfoJ=aq@g%k@|Lf+@B{sLUHOsPltnCmHj7@2tshj)=>FGJ!;f`Yn~MAYZbywMC{GO4sl!M>;y;cjzD -~G?oslQTph-mLe4wH>cM;tt3!8oAG8|mCYJ0Bn;y6EG|a8Vd3|4>$FWcZwIsAVl#`ug`(TOUq$XS+6y -HuVmN+`@h~;#0I%|b1bwwL>%D#o;ZvPNhnoBz>D|FfZy04+E2qkQb)4|9igwUVxeF1LXBc>V~TN6HlIFb?n&}+20Toj*gz7eJoMCS?cZq2g!r^$N&nHqa%gYmaa#>k~M*=I4wL}KwQ7~@m -1FO?^P`etxZ&>kA-Oy!wU_OqHcdXw-M3cDczJ^k85Wesxw&d9a5sYP^5*<^yeDG -QFL?fsChdQT&woQ=+uGDu21HzBv_#f#XO>6#m`oEV$zg?R6+TQ--TlDTj>%-}AwMvGGXTpVPu{5+)fGhrhBd(sVH+uvcInLl -+Z(2{$RZN$E{QY%*#I^hfvx!(Xw7uj3-b5-|bDUh)EvWN~^d!F^(}2LSG~SK?XvGCQbb`IjUXR|yyrL -w;dfkjdhN0av8ZmlGylrWe6W2!uoz+XC1e!1y4t#h((5JTU~GF&P58dZ(GKV+oc#i7IN|sQ2z -G7Hmn+w1R~XJa+?B(;soe4vM@ZlcdzsR;(Yw?0jVr&bt~KHH;iNpHCX7K=fz#wBab+5 -wwEhBea|#r!>aVCT@_It~x`g|K!0hQo}#?3}|zK(it?HME;wxO>F2UWf -yv^ZZ6TV%kt-MZRwMhbPld@DQ@2jWv@*CH?I&-P}sCA($dYA+`|U(Xlzft;~4abm0-G0F?;Q+qZ~a^( -yjq*dJG7+=UBpNqAM;-^?bFLoKMWB&T^>mqor<%XIj+2wIHExTvHp+Pg)_#FtG+OZ(dn%zcKk9*@tsH -AChy{XK({dD}6>1tCGhLg`>UX(E_f|s29Y_YeM*DE)Qc?Tf?3Y{`e#gC%z1P -F}CX%xVIM9}PmsP`aLJ%~#xAZD4|n#OxNr4MkuLmb=2rXb68lP6T}fik$Ss!KTbU`pa%GE`^rW=N((R -gU5aqCly}So-jmEfrOEENUYkM$TUp!iird8no-<&z1Ofw_VJY)TrASyy7P{1>$o{Stu;Q?83ta~=?d9 -&$mZvRziPuCtzd?ZiGexWW~C?^RjizLl3Rg^AJ~}|3MB5fT}{>Jn^>itZ;^fm%^u>3{3_zuDm<&sfO0 -U2Ct(V^6X7*wENNZ=i%;u-+BB8W%;O$amDvit*$5AZh@fFj7ZjQY&uMS%kWo-w8obOTD&ObSePrhSo; -SA#LFBPX)RyiI=lyt3IX>DQ_fAi(pvX5=Ug=jw+Ils=mFZPXrza+DbV-wZ>`d_rHt%i%Ph@evH%k -e{c{0{g0Cf^%bQ7-y(7e^oPWTiI;3`K*aayg~s0y8yu1E(JT+$dQ{1=o)z~x@Ckq}UxT;dYZD?I{1U(pR<0EDmDMYvozd!_kB*aks12(w -AZC8Dx$n^;05c(h@#<-!@Z97%!;N54Iy)f=%iAoLXWP5pvTXT|&O1OvJFcxCir)g|zD_8TwWQX$@wxfe-oFIfw?F8HG+T -|G#!6y&W@nRTmN8b|l1WbTt~Ph!CupZOE^?i_6am|f-x1=bIK2WAFeKl7qGZ}WHAKa-yXm`;RB5h -Id6b{%Mld*j)>L7;kj?2T0lIK51w`F5D4E~@bu#3syyPzaM(ZVFwj?LFGazVcY5@h4|Bc0_zx1k=+_f -D7cHa&7;Bt5&Ta-JVYIlQQ*!VxNSHTGyvj{upP93EyA&s^D#oF<_5Ib>v@7{=+nt~8q59^>cQ?h4Lfnbyv#UbeB_TKr|!9K2=Sx=3+)X<9z{1~($y4qipZ$!cs~u0R|~8D?n`UcN;stC5vlZR`H0-yV_ -vco<3);`J`NH&JZzRE(z8Hh>&ffGXa;sf@Pv|>!R!*7p8?wq7FGeZYaJ)Lta&Nsi1PM`eUdsP!@0P%6 -=oJ1gls&P_P{Wx#i&U<;-q0 -nt4Fv$)o5xlD8!sgOQCXue^mZWH_x#Oi@tk3UCx=qx0v)0I2svBZRex1A+Ec5udfc8kBRoW$J~5^igG -@6qXbhefp~eM$PF{DryxFS9C40vY3;36fhiXP&GP;6eA4GqM8a}P&zJR;Z(&HhHP{sfX2?9HOn|0+d` -UB&Qf@l}*wm&PWyl!0UFcItNt{>M{1BealcZ!Sd)mr*hRRO8;8UpAg~H!)+{e<=5L05MhlA11_*iXQL -z~eiFxJRgAydC&wkAJvUeqPMrJf*@QE;^vR(Uc$!!uaOi`iKpQf><%x)asAKi9W^nOJO+yiwxK>x?Y< -Z|}JI>|(hEIG=yDtmB8n`zLt)mk0ac*k`wT9sk`4tKm&?UgEU-6VAT$WCH<)qsx-Nq_$x%M|89(G$7< -+c-cCXgbNuWbOAyZf9=VR7skNp;z7pa#RCo_%Sj}>n89Tyy{xz@ao+Qt(o2VdpPB*>t?itG40F+viZRg5BxrsTLSo{xkI+$nO~>rmYe1$@x3@_EE~zPwfE$cIN>VWuM)D9+U2yJS*>>RC -aFvhv%mAV$`|5acvtq&=(Q$6m6M; -G^eqQ0BE;yS@CN|yEg7}Q{m^LE2U)+R*3Pb+e@(=|*!$Pa;sR$81~@32rkXMBEoGSlXz7DdsTn;rb2J8>S=Kb{90A=Qy*|$HXB$n7+bQcHCQXp+d3pYh{}+sbPr||X^0(IfM^^TJQO`zpm+6p>BdRR*h -x%7w&@LYxJ4;9OZAtp6M;|fhHX7Wknj|nh&Ky;;E~A=FlwLc1gRxIcR%jyz5$k?e=uD$Kr^KYb&h|>N -@ITr-+{Gc8?NXNFk?P1>NTL3U3^{qsI}$W?4}8DIFDe)Re0%ufwEra;)pBJX^b`NxafgMTSj$b -q_4$UZhls8Ou5HWP~@-eSddm{87a5n95I1fS@{Dh%6^=aFL)xUt% -&qMzOSpV0D{S&r7kNekP4Nyufq=WG#*BuB$An&kjA^7rdvUp>GoTLDNL(u*utO4heWAqYL1_Nc;CW9A0FVMjN#B{P;DR~3&D?gu17o# -GIm&kd9FWLK9w4uQ<%;Jp(h$oXJI-Q}vg|+^24U@lv_2X~DdRMUJr#ILBj(VblC$jU5fP#bmyso^d(R -VTW{kkHhTeFI5U_LLqzfNyr6#7n$E^f}nQ#l``WoxnWbK4lrKZ;QswkJpu*wd#Pg}P`=Ji# -(5%9CS7|x-Qy^+D2;Km+Vvh%}RDk-bNq&XaikkQ+ipby#yQ;Q?}z#to_xRD_gW -{?#eKdV&w7!~xAi%6R}=!P9u&e>Ig!{9*-{o)w}}_hId$FA4S8E%$E-SHeA%cWES#U{bh?_cnd}=B>- -9*a?z0Yw@CkdK9*Hrs)vA_>(7pM}BxASvaC90cU}E@&z#+z8`l|1BKAPO;uzgU}s!D9ehQS&eeFLy5NnM$KKv-juiijMGL7%k6d;p$`2(b(N2 -B#jeKQFYtWja<_eA1eX}~O(IXq)SZm2G6z*b34THio#9Sf$X9f;I|B1C&1mN0TNBQ@w}=VXN_I@Q^-c --ei^azKbK%l-PneUwcY9`9SpzYy&5?k}apY~&=!+J1ac!HKjo0k!V4u!+APAct~$?OGrb;#|R=N5eFK9YZ5`OhojMhD&iA(RG^8kCWx!1JQ$GJ -YPHKZaQ!mI5t+{9OsATiVyNi)@tRbZs+Y}inGjZ^<7f4$7y|GB%Ev2p#ds`2%)+uj|zJokbo$5XTm9O -mY<)KFlBgp!kNQ4H~sLj -e{P{J;7tbxZIE8U~QN;^!U#{9P*)ZuVWeZ-UFA(!dw(LOzavFWxT}}L|@W;z}9fwQ74scxnXYd1>H_6UtH_p+qaQ_GcrApwMYoG!o*qhi9VZ8i5skuKltO -0Z<-^0>K++7#C+9&f8;T3OPQnQdHfD37YG3nOZ=IxKhk2Xy<=KcMmT*MH2(gyq%cZ5jh7U -`z||G(a=2gfe{+@8dVRvp$N~9Yd^r_)j{oU*DbEZb~$sRc4WFbZrUvy#9L|Ic#zDGV`dQKwpP-qe6VW -|xg?A(ZZ8>ngnO~0VjQ)~%-0>WY&4-~xf{OLY-^xQjBSokGMlok3b>f5tHZXzxVc%`!QSJMPVwfG%!a -5^t@?U)D09tB41Kl2p-Fp8VU>wYw-8dfSp~$=latC}h=aXFn;mvB&OCPl%ljkMd4cI2rPRT@qOI7KE3 -DG=q|iOgl7uNiBNnjz#8|Et@3-6 -ZiXmz7H9y%j{$IMJ17+L#5DLtQ4R*e=jkj$THC1LjG3+-=*tK_QyaFRp@66^Dhld6CUO9!@x9u!1&Kb -gMquf2y^**E3h{4wE6_UkboZ8uV%qO<$cKgmn4->&j*oA3Y(H5^*8#{rzGttPGuk)4)rb9ijr- -5?rmSUYD{G{mTkt^5NiSdi7x(y89U)f5mMefcUR?M>Iu3D42BS_rUp|5BDcNe>c=0b-eyX -OrzA2pp7w0&UBnCj&LGe!mEpL4i}g6k~M`ap;kQJMA4U+#$RQ*s8^~tpesWx<3+>cIBz_5Qc!T^t$$R@s@(zE8>E{0=rnOP|tXy*Y9@A#xN&wTrZAmx&3f0g%mfK==U*j73RmKv*k>R=@ckVSU8#rBw&gyX%tjI4WB2}}Srh1*gjdy6`q -BVY)CK7KE(WQE8L{0dt_^MqxK$(=v`M4EbYptC3;ouXoIu^&+z0biY1+;_65KVFTE%OgX(-ztE(BLDF -?T_%CypeOKmg4<^p%ZnQ2L7I}kYbWU?h*6O>R4C%Y@{y`dRX;5Cx`o>Ip0fDs?WRhctd*oF*jEMxvFb -Ujklw9*rT*`@+PDs+iS;8$Yqt`N7{zm>?xK^z0JhQ7FRaBqD>Tb2hmxaX1cGBl(B>dYPC;)x#8U|Cj}s=_tHMYqQy-pJZe8nb)oT}H5bAka -P2wg=&eecQ~s5j{sGxs-6m;^Ur^^wM~_LPW|lqrlU3^hk;|&Bv5f1J-t;QMWi#O$?E=e;QkuJT$}?t`M71oBR|d&$3t@QtY%otUou330QC$Dkw0PaG57EM` -tkPaL&|j?}y%9?1Sob$j-h2eDc?>}WoaSae5B!bqmsg$c7O=9uxJi^1{!%n*l!6FX%$7-nT3(An -!IOHr)}EA=_`L4g<z#CU@;&hPLeQ1_I -`-U$hTT-*J>{r=g5%!)P9Z|1ZYS<#Bf!2o6S4=*BWl-bqzIyNj`I7Afy2+Sdm=4sdq#8vo${f#@f;2I -?_~gT6F#?EJ2%mb@Ut6}d(HkmW{PyX~X^)W0xl0;4xoikJ6r|+Z) -a=a=1I;$At}AifBp&Yk#b4g8i9}{JEuRNp$|)rL_GquN(R!v0KYS5B%p61u!Q@@ZUL+!9wLmaDut<^At+mB;)94pxglic7LfammmA&+0oB^j#UxKjm@1M*&dH5}*1~M6%Fet$IZPK -HDJOdNq!D#QtQYIF@Gmqr`sr6Ocdhx!-rSX;uja_)uyyhb34%1V3g{KMaJk6uFu|^SQz7mZGD;Cb^_1 -?g<6%vQ^6go%W9#$S$vB_r;t-zsq(1{LOrnIej@Pc7Gi3Sqv}mcJN}j`KSc`&kR46IsFVTK);VKgic@ -T{HhE3ea$EsVQL2_mZ(lAL|eK-`ha!o+m$Ow5#JwYOcaA!E#Xvd#n9>^6BtlRr!{NM?`D=6upH}Fiej -czv~&0S42N*FVq+_lo^X7qp114B?;Zc_MJCfAF#lNRN$fq4ftW~`sm~JZhk{G)_eHcV_P(&%d9nhv5lI*1bgIhXZu?j%&SF4{g -x)Yi*P*TR5Gp&4Z)Do649tU*T(7wSDeLjPT@knA1!8ca46+W^NBov=IlY}PvU(%d4!KGtl4`eFcU7Df -y48#3nj4o@atu+e`wTBY9r%d0f3L<*`<58MiaUY5KsiglUX~x<_xoD`)xw`NOTn~avCWOATl`$A$Gk% -2!EA2(z{<5$4(`^(z`d}-PfYYv)Fa%1TP07mjSJgz9hP*KsK$B_p@$1Ei4H&3M&;O1ICqLu#NY#Sh1G -%;*!*PtqCV;?EyY3vendts-}eIHuP&y*+WFm~gJ;t}t^PEj4DYTGGn7xG#vjw?LvgQ-$Mnzv+0Xm_(I -Co6^Vk0I`SiKc;W|d5ZTzOgBX>wO;b!2JnNQ|CD&sbO_NMb)!>LC{o2Vh5NdtP{C;Q(}lcKw`X&N7}tpT5;GtBd)GW$V~sIuw88FO3Xi*37SX~wx!6T; -hG(pIT8*E#XaJWam8>fs{`7#K@0)=p}^WJkIhx=sZDJmi1ms3IQo>dO5nboln9=lU8Ah(@$VAJcWcXcXzMv -R(2S1uLFEpe`7e>pnHxJ#BX?2c?lOLZ#T -6No;fw7T}{QE(WIpNftAy$)20iI4P)(r0r@`S0&cHj1e$ZGeFl#YUPOU*WBgoguDyEF35G$iX{+#KXxr5OV~siDSt9J58Z^JAF>eiwKi^T(%bs-%{qX4RAqfgZmG4!FUW^j6?P -;ZoI-#6_mm7T9OMyb -OdM2PR7m16U`OYzA_dLdw;C&(l8|wxv$UoJdr$q;o&WbRp)4T54X+%YEL#+DemeKZf&xSwbT4eNKRpC -Y1Ij-+{Lm!GZ;fOvr-w2A?5sK{fNb~s&c0@<1!TyEa?o -)e3_nAjxO2TzVXJ(?ier$22ngK`3e3v>b0iVH!t*C>D2c!;UA4($l-tKgwJ}ce>(UF6yZ2deW^{eSJH -GCExU{XBZyZikR_A)m0q1?i&q(Y)hz|8E+f9)xs)y>0HQI(mooquB4)|f3SR+ju+S&S@-zPW0*H)R;? -C&nyDgAkp$3dX6R#2~uY7HwjN@e4|CC@0RS5E>P~q1`CTj6r1Hge>2wxkSfI6m_1(i#XJ6ZD95_EC0C -(%+21Z|_LR&f0H4tD6h=!dIu#)pGlH&N&D;j(+zsFB8%9~$-aeLwV3Q}tCE{BPg}?MC091qNccxR9%0`SE6*#4WEBq02M%;Z!r6cTpRwxN=c)hq6X4iO$S#t?-L~6Wt9V(k?IuKh^QH_I-A-rt$VoTHQyt*}T}~*QR%uTUL>4j%TSTS3;tWSm8rHn)RNP(62VFUP8Z -)YyjCZxAVKgbo-ga*dkqsf`8%Og65x&5%zR%W%UotqO8cJnb6@nMY7v%HVf^y)&|(ac8vagE(a -x%%)rBid;H3O7F#_qJFly`8Ux-%$6_DFseq05PjUYc=an2(s9(j)mS2{$;%mJ=JdEU_U(Pbl5w*-4JC -c9ZtuTzg?=m(m(-08S?~~ylS}aZXGdO3;~Iw(~8(fh;!SdnSR*#hGnv9cTd$h6>)El -njw{?wOOGa-xJb^D>yZ7_rN!Cwe=DS-Zv08Qz9r2$`m%)N-wC(c#p(cyln4{{E<|y~j=Xp-0zt1D!`)jC5~}g{rPu4*!antBk`@lEPe))g0oyWUukoYS -#=lbu{Li33|2|v3I_1}N`G)>BVhX?3VzA&Y-`SA94E|S1Z$JY$d#%gJB0m5SzyaacRXf91?JJ62;+qJ -*I5`P=0YQc?U|JN?TWWB$2qy67D{8r@9_m+{iQ*SjmP{si`SK&`^_4^bR`e@{YH^3Ya+NXSrF7zD`|V -pjatvMmgkOpbpe4x`Z|$qz=rRL+4{U7T&^oq{_q` -aX8;A3WCO|bt!%d*e)`?li8nf$A0`Q=Q0K+7_dzww0m0AHLi?p&YWY0SAr}N^LYW?H69$gy{VLH-G%#hRZp7EI*ag0Zt -c3gWw+|+~>#XzvL3R%ydKSW@iviZE@1nVH4<;z~whWR@0>%G4k`9wsjbu{h5DRg9zj~9t`9`}60U{YKiJBLnZ%wRCWcC*KK4io)S>!xx4q*>Wgu2JC6uqH6hgPB^epEWS>CdaiY3*m$tj^k -X1VFS_ID>3FJzmU2anzd+pPS#}vDXwko%#b8GLz_Cqdj+N@jR16u2OV(M6)d#i->ALn2vUHG|Fiv0vP -P7|b6b*d(rkedEsCI=U_B!(^gVL!`#?rnRjLLYYS(iWc)rCqi2^*BTLMY**NDj53<8U -3wqpZFkx-CQ1N+x;y;^P6-2M4j)C|1EZwO{>HU6R$6;L&Dd$)4j54-@a5RdX;IQ-cnoXFR_!7FTWeMU -;xJ!JppLLgs-a6^vhM2EWiNRpzy^7h7d~_F9n+8SJAd*Gc2(ng2|S>rxCj78iHA(by>K85szOO!4@87 -FCt~g;*rDAS4z-pe^2(>%@e&sg-En)Y>i*rbCKU}i;==b(ICIw!z%p~?4%kuek}IR_x+Ab+>)h`L5#j -@iMO~Gz>7BOORmjQ%J6M>3-MFiXj*^7{Gg97&igUumsAn*kSSgwuv+`%R?uQw`DkquWGHQtgmJ=7`Z-t6=eMR~B=#*c -Uf)5w%S&h~cWgUCgvEmwH%wby9ksp0g4H)*0)_jrUbHwy6r<58|JOA)}*!1z2gTbSD>ya)BE>>p?r3& -5|ZI^Zgv+1EDj-3KPC%Cq8%Yb2eJ9ffY?c8gz~TQLZ3pZKh^l`U}_KpAu90&IWT>r9@Lb^6>sXdBa?Z -suC)IYEVv!U$&$c*KUU_gmDvH=CG_duR3ZqRdPo?!rhHq_CAKJP}+l>}5UBbjDzVskoT;44qOtZ&+-6 -p3=I^T(`R&?sC10&nq+1`xVrjhklxv89Pl!+4Y!=&{>hEPV@n$n+twD)@7~H4)rUhL%)K$q~9s^ejf$ -(qUzCHL*AgXo*&Tfa! -4S_N<{uH!s@tK0n5By36RDjk$d)lmiY(FbLj5cTx$xlZWqtsxaaEv0-)FowKFuY&n&y)ohGU~CR%(Po -S_$sM%|X*H+V|C=rMM1f=OuWN5I7LL0Pn^3Yuq_YG4?(N6IxP_8|x#RxE;mVxlG61VQNvanGFu>Z&-+ -5ox3%e_<^wU_GJL)N0`5(=0R6rN}tYDb*jhI*?mAw;<+GJJWd1k?DCen -|kJOctj8@VUp*A4emydx%urvqlwuAf`n@!n_ts|&ZOEII+LRFq(S7G3?Vu#V%&r3J47Vz=7jAq$w4^} -m(%&wX3!;l4>b^K(>e2=uv7Nra%ws8ER|&EgLclmPAAO;H6UYj#!|4{J$kje>+D -1%f0@TF#U~49m6-RsgT~iwa6_~$|PI3y|R%A-D2(a6?zZZ8T>yH^(ke-c5M!17x}=EEq%N4ot3t1)k< -=sFeI}Rs%VqQ4$`fS8vmG2{u9cC?yX7KuCGm?TMN?4+{mrwxvOO(TeH&2oz_Fsd--vDcNd?GcBM;X|4 -{3hsC1Jv*~BO5UCt!@5B!(%RZ_T9rpsSZCgtByrXPkM{**HPX|!JaAzD{HQ-A)eX#LZx0lvSS@2_U1M -8AsI`HzTQ-6HmFf{0&S>B!ACHKXx7IqL&`NRnxUorZ{0i;Aw&g_K{h(qCJ+sUf34uCMql$2}~FGy-^w -dSsJxNMcXSNM0}Ry>-7x*Xqoh4Q6?jXpZbeM0Wm`w8w={vI%WuW)mOk8_tmdNdi~4f=~r=zZ=H*;?L! -K9OYUq&B$gGo~VWsEIw(@&~MS4j<4q(I=+|_iAQV99KV2T7&ad4k(hqEa~|Ur4Q*;KR~u*QYWh)*i=a -H$JVL&tVr1p3sMHKAjF<4zxm_?FfiA)Qw?lfEF`>}%1{Z`D#`8##M?#h%C@^*H!a!7U8sb;HTurpW+U -x!P(xWJO7h?zNa&)U`}yUH65CF-4FP)^Uizz5HADR6s59YD=Ov -jW#`qnnGXVtY_^5*#c_bwzflRD9`>ItdCmbHf}~b(OtIQc$e2Jns3!}9J~|9;2}wI%#?D97U{MvP!iqa0#c -Uzv4)cw2aY{*Yy0|cP>f^5)1sH4&T;EU@2VAN@6jx|=IrIn>)4>Y`NzU8@aL&^3#F0k9ekN62=VDv)+ -WkcU~9?D!}5`72fpi8`m>Gy*h1y|f9Q1kQrTTsBst`!{JfwE`wlCpSx9p1c-J^U8Rcm`s|j&1lbv@-! -3Bt~<O@KwS;GN%pE-WriX|5*2YEJE_1sBf1rkF`DFv7O$S87eg_Dl^wk&&I+)1cEfE- -UJmAym`U-K`D3EooM_H{&n_~SMRFL{81)9e&-P14(kCWZSqn%Rks5D>KD5rL>Cn2{M0s2b*5Q*ETiIf -islmIVY3JoNU&;4-ZWzJ3Muz60vT|vFAcK_tY}(Vw3MsR_JhfOqyx}3_4%#^tAJO6YViEQJfcfTz2s4 -c|DaOzR(@(hl4t&5y9MS>L13g^Q30r*cKtI)Ig5yWjI<_3TF}($(pVs;OykDQ04_dK~msix5UuYjLfG -oK4f=BHU)r-4?iiN;j8?~V8o7iY|eM406Y8}ZF;^fnl&z`j9!Fb{&Sel=oHC+S9SkkBaXbG=dXFy4Bs -Zn>(Q$JP@3oJ7Sm+FwiCCPFyyFc-`G%8F-xuBaU^O)|~fp&>keZ -q=IwNrSU>z;KQlcsA%j-%|iGI7Tgo`(kru+JHtXcRooM^=11O^cm+(I)efi2F(~gg3ND80UZ-g&o=raK&t9yr7&&<55-4OiEBpkund-q+XdaQ+w!r-I_ia --0F8D3so4QWgDET`3<*FBU_Y&FORUf-z%GQ~^lASMu*T1n{*F^dL>d%fQ|1*dDWyo1AUcyLsXA*R4#7f}Z<>Oan`HjK_8@F0FC5WwG01mcUa5~TL+kk`-pA95H}n-&~qf@#zy|99bjD -hvv$XxV*o-AsiNAbepG2wN25^D_4{-Zbo5(&zff0N$4*8e&MU2JKXXO-N0+~AyZql={&w}i_m -}_oD>wY$GK8npQ44qQP9^>E8x~hUA4O@99!ZdP -(3B86V792Strp05%WNYR^T2ej6~b>AybeH63bh5R?$1IU}ty17iH)bI%wQ#j9uXN@{r?&@-wNPpCgJ{ -@A2+KH1Z-Hewka?KCchu&5G|L+R!zBMn}9lYs?Zwpo78f3qToWKB>!6wb&$4%jI44lGg`?1BEf&ld7N3^9V(Po-9rXa`>3xMk@5nA0H^-Yc -=KyNnCoJBK}ymxZzza==r!F9D5w~dp-Ah?u2L0!8GXQvVDCvu&dZqUSt6af9YV@>Y@PWRJjwHhW7ipB --6z4e96GiIzLGM87mNV(T*#XBhq&crJ;Pd_fdGpfFqZhG4DPZh-lB+1+<->D?}=f7@;V6Z82$Vp}^;| -l-{8`+%yQDn8#ICC&iCTsfz(>z;p(r82{i+$^ACLR%oW2N{4cNMM)w(-JaudbZFV}#rndcu*|)EGoYm -MphxFu1VB&-Z!eu?L##d;FY8KjatB`U}fEu=;309~zDuwPmmb`QIQQf(ToR2-QQj_JOLq! -{`gh8ch8<7X;dCab7UlIOE1Hd++^sYaODQoDzBz^PS<@w*gEx$GY-Ta(Z_{)vIg4>r3zu0?!k89+LT= -b_y($SVD*h(Da9)HtYDLzED5F91$t -B!17f^4|}A-j0Vh3^3+;Fo`x?01!gpbFZBUPDxhco`23bk( -Y&h8U|gcnW#O#4f-aY+YlhzEmI0%FBXaOVg2bzl1<(7)Pf96GKd`eNXG -$2knS2TR%)irp&fm2A81Qk@K7 -56^}t#o%tr(InoAtjSTeRo>9+DSKDyY+M$%@1HRPXNx3VP?D*Jr9)NZ)rIKOrvSzMDqXE_s^q^K%=u; -E9eAtQi2@_%-9hh8yj;SgNt|B?F?^x8dQC5glJIxX&rDx=<3nck=-HB%1eC9Ssjy}M*r$$p)$wqQZ3C -*EDbk(rnW=|&%~7FS@OAkB{Y58nKN5X7YICha#v*{k%v --!<+^*2cuT8Ry37i(Zm4`wR$_W-`VggQ3s#?%Wl&0$}x}=~yC)#;Y4BI!CmsCAu<4s2f8g|~J&va5V#rsPru7`u?yXwvC -08(aOHSQkFv(e_c-Enuni~h-4&$8_I>J`6>O`ZPF8izD-v#so(8z=q4UBBVL$9vn}-}Y$#j%3EBSH*1 -Vlp(wYD>SvGhl6NCgUu^w4{_GjdyP=2bwT~yyJ!szHjQX(i(S_BcMWJ_a&IDIwgNhd?+I#T`$O*G4!J -MQ$<}TBD>R_Cp0oho+nsk)MU>iF1lHFnWa~D=>AeSfZ(7>`DBi|n;E&Op-P94^^T_C)mEOV^c=vl`c5 -M1<+h%0%krH&23ZS2eRe>FuNUD#F%}ScvAAqM`_J8R1b&<_CD=!5z47FU-lxg1oXcq_Lp2 -+Nc)YZr%z-C@J(4up$Bx=~y7>+Ot{0@0GaHu+;`sE8hV@P9c> -mJeRL{NhIRP*o8Ztb{3(Yz|&|)KIw75&kT=Nn -?MAcshP;EPGOuG{D)rM2M%)E_qlTpB#6Db#q$wnDU3#{gA9M^-8B(z7SNvRgi$iGi0Cuwtg8k)w?~3L -u`x9~h)Sa`SlTLL0w3XHq#D&(N79LR4K!b&!hPZONAe!Bj9Uzlzbpz$|>d;6)fHeL1w7_94HnX3C(asjXC -6r;^TQ?3jebG9Ug$~;rP*R%KiN&nu#K+$I*DPa3dUKUB1Jy!ji|C*p|E^4a6IyPf6q(RK}S)gk>QwpNxE -2;(QShBbQ!f3HA6dP&b++(E_yTspd3s -?$SD$mnQ@gCUUaTmYv|S4i;`%h6fgk=8(unWQ#rgs -t{VZ(U|7t$y|g{MSGYq(Au_{ZpXc-TD3vP;YVoU!@^*88Ip!bLZQ?0Bhj$=#ra -s{BVF3p^fl_Hw`By^g$EKg9*ks{0?MNOwH<^o^>Fqu{j~)wj0wao-fvQ_Rh@1WC&H@L18rLmG+ZEsye -+ihajZBU>Of{{Car0PeI2nfbz7c!UUtVq7z?lp#g%5gMNBXDW4XY| -mY2G7Jgu5{kXg8Y|D;<53BxYwhv5IwdfS>&d%O%g{$(dC!Aj%QwrTLT4Xu{q8{Bt74kQ62L|SN8jOVb -fSPCdzp`69x|xo9f7X8Y&1Zh(!{{WWQjJfG?-lj2&NR -oVz&13U8Pa82JS%;BR0(Ej%{~Ke_Xrjoi-lBb#LpoxPQEISbFngVlWakLk(p!5a93RbP4usiv7M<-LW}YcoFHLrSGOD -A6QjUu7Wyub4QuVj{KkqFCX%SApy>L=PdsRm(aGSCOGqeF=N%t>440)*~-R>e@JTk~^a|LpJuzs5ah4 -?-Px*4i{-aAdrYwrSJaDqgYrzGo@k*@;pBIyp*Dg#PF#TP*8O}Srz-ZIsHk;Zn*z5A&O;T9HlHBbomE95qrnfkCKr81hxOg?mxly*CYHh++Oi;g*z;m~O%BHu#Ed;tc5LJzFcTuaLiDB%EyB@-)8J29pgY*PtBPRVXO*KLNeX@rJ>6Dg@sKp2 -A;ESFP6rZ>rMUjP45HF}@A^rt8kTRnvOt6}qv#D!BeZ_pY*(+-jKH$>Ij1@`nsr6STf#MrW=LsXlST(JI^rh8p6=^e^_pRpd|l9&=SL>!Km)GH>jv9CG4b_s(OO`bZ%mz@B$_Gy2r`$fb^kV8qV -cWqeda`?R{3d9r>8c|CBxq?%36e8Z+*Cn)#5^l -#y5-0wa~v{%V%H1&F}2Kd*|$J@9Fu~j8{w6mR3H5;&<4Jbw7)$l;$LC+dcXsV-V3V%Ht_xxcK@9N0{a -m^sndK&Jqc=wi&P$7hPP^7Y!$oEMnAr*M3!vRd|wy{P*z%&d;C4`G)$1VLp}0GR2^V5Ihq;%JSdzgcL -$^4uIroQ;As%A4p2AYTz;dN4X)^ -v4$=dxATu7ogxGs1ic(8VW3@ME3h^x`<8Vi)NB;%&(EJN3s%>-S0J*@N_dOl)Z@Rfe8q+F+>oUUeZJf -V5X{5eQIjB=xx7q;K~B=A=h&RH(hBw;!YG3B;*rR>cDFCaxM|X_Hz+EfS6Msw*E#d@NsLid+!w$iACuSzDJ#ineT_Gk%F8|xuZN3{fi1Y5RBqP6V_XUJ^b*Des{jzr(--e~*J$?_v?HZV9o0wY}2eB)2Bp;5F=!W+(xt(&G)!T(e$aG63Y}ZVEF`!^}V -zP-??MDgIi1GrW5%YlyBDc-zygazm_f( --#Y(oR<8Q~z<=`q--M^uc)i>@h8BJK5T5#YE!tlLwio}%1vKL)87wRI7|-{tXEgr^<$(H42W$E!S^1} -ml^1+Gel@z9T?_c`$batuKXC!z&vZrxR7NWIM7q#I(sgt^-P=_3xv~mvYtRvLf&3>><#_3eY%A{gI1)x1WYt+iZKy49sdin3++Q$V#qcQ0OMS -qlN#VshLItecq77*7csWfq;>2Rlf&vP}v<9R=Q^=bNqo=Ayx8FqP2=;Bj(b+}&|Svpq^(Vv6c3JCrZT -@Xg-#&t8(xfE3N&HxFNrdx!^w?e_>S6RZ>{HgCzPR3 -Ldz%@*Nt|vu@P*~>xwJ>i(Xzq#8iHf&J6dmVAl~n9W4P6~W&2&!^XTg}Iy8GBj(E{?z&DI5 -Fmev6{Ft6*i&V_9ycD%ASra9P%J*%nntJX`SGG`+Y<(yCCdHhNnDs=Pi#iq)*7+WL#Tvf5=|W$B$si-JG -BY1#);FsC@m^mWZaIfUsMD$PAD@v((e5h~u|Dbm{KoxUbN|N=dR5M=%zWw?# -(UatRsrg23V1_a5(eb?h#Xo5$t>J& -;4$MQ6#4ObafDM$Ie)Th}*&lxD%a`c^ -lcf4&uAI75sqd;%yAbZ_WhIc -BuKh3|5p>E53~QF}`swz0&OmIlO@5sL6Vwe**sB2vGh00DmGZaim}pJ^NZAP}(CD9jR`fK6)iccsnmS -IFOM48IpLbX%;x+vdY;=))^SFTSK*3USg4bEQ=(_9gAI>@9O2ukG|p7oBhG8Ssy&){jzf`!>K&YtuiP -xnoT}0A&4ntXP;m2DEVS+fl$3wJpcn-GbEz**z`wx*T!2*YBihwP112{XiKAP~kH2s!c2Hm>V)yS8Ay -scb8L+R4`;jf5_9pZNn+Y_^j)pqy(w4mAgrJV!9?v&^?9%JAepcB|guk)YPrr_PeVXi;1XNWx+v?XM4qdg)0<6tMZ%M5QgJ6jN`j@K2&yFnU -#|#Ib|(w*Un;B#9VTN*9%)r{3yy+$W*$c-lwR`%1zRw=&OJ#&nA8p7a&cP1f~fil<$x27q%%$`k&(#hXjtPKf(MU)DXnM{Y -3L%hI#taEHU^<4QOR(9DrV-F-8mCh1bnGiMyuU)z-tB<1ADjjE@$g=X{lCarXCc^y@Oc^6*9oca(<-M -0$NE=4V}66K45!LT_SW|ecA>a8C>(cqLi%}N2Dy4d?hJLPd@75y)(Kl}%4{N0%J$0qjRE+6QNI4+uLJ -5(>A085)UtCyW%;l6Q|%}PA`WaDq!l9KimqkVw^eqLez=>h}%yuy52V47bpZ6CvZ0)CK6GzGZo-LwxW -K90_C1AW`_&!Vy3Y$NaMLg*?t3B!Bks97&ZkonlnuXiSXzdS=_ud1;;fDFnFh)r;`VvJ**%=a^Pv(6L -ds7jq4ZA~!y*@5mTH^!Vr_&Nyky!JHQ;*96Ydfx)61U= -srQ37xk>4ND^_$(C2U3+3nbLz$=P(JaAyvtC(UmGYrox+T-1A%(Dg9j(Wq+cf@mp0u1h2lIDZZmR6(8 -Sn!5n_~_Sukp=g0!O1iHh6xVh2=S%FH}I-@0#Ju6&I{1hMk@NlDH|H|Lu2=2BP)__)hh(w840ddrBJ|$q8f{_*EAb+B${6fN -Ov3HOZFrB`{z*`~$=g2HKm=RnZF7-}w{eEe-96m1g2=|qw(i>Tv+v7ZK-SHY}*M1Qc;qcOLe$~=SFHW7#)#Yd9}}Fn~CND<}Zuf4k~ -H|MwSWph6C{Xld|t|j5jarOq9Wr^!FF7RnI$!^%RN~(85G2eQe|eWCQKCU`j2_6$(+3mpD!fCO(C; -9(!{wB{GN3s!op_rTUre;-d}Jw~?gkJ_x9j(XYhrp&wRc=mx2klR)U?kR+G~R3wx>KA%KhXpTNmkd$g -~S+&!90)%~flVtp+qxIv2U$m08Y}Y#7o$H+ikA>E(k?gzD_>90E%5*PYRe4V$r5`qZIK)K+*o1fzsU@ -ibJI#q2Y_)F5Pau*7^vaDjdp?H>^^BZOcj-hrs~e7KOfn)T -0Eq2k|8*cE|f1T`$VP|su&i)^P}1ytaTNh(l1!o{Krze8KTqE$I9$De_}l(akK9k@K%afB?I2BBJ|uh5cBwW{5~_9iJt2HDz#=jHfe -9)`)Eu$qQ(eBpZEd(kbB!k@mDHlE)+%O%tL?r$klk`^Lkoc{^+rb)zNzBvd8|}#*I^#*765!zrS7R_( -3Q9`)z=)MGN!7F6&w+O~1;9Wg=^nXm%7Aay)ATgWlZNtiu5Hj-F^|gni2kXe^j3;iPDkf1D5`bLJ$kA -zxMV_A!v$Ox(sYDCDVBng@!TR(?rpAVwM-p}_7wEbps-qPZ@#VzfJl7jw8^&+a)U-uym^4-!@L32mhY -32*mx2^cQN%s9pXc+}7l8gY?qbga~#2b5aQr#GuH1M)0Q**Xexeivk&bO-DR6NwP?+}nrjdnKu_R&`_S3hXbKiA2=Z -RRe#SfVtY*XW^6)~B4_fk;TwhPRY)WT{VJLi_h*uV~H8}x-&xvC|+3FIfZ``F1g!(2GvdY&VGak~9a7 -N=)mrhrV{RD_lY;O1j -a*xG-YBW1Yh(v3%JrQ=-sl&Pb10>`Z5%9qB#;C54h9GgOh`GzP)8N0j0cy@GczlR_$E^0{EUZi0eCY4 -o7(Z_Po)tt$a9c2WjJy0fjYKnNN_TBWv$M;t6iVko!FE;?!SBIbvSlbJ|!zIXjN88W3eF3qeAfG8s6E -)gj0bXwyzs;SGF;WW{plBCYX$6iDnXJ(I&On7rjoe2LQjgcb(F1GN`?gCBF6<`V{--Z>AYf|K}p|C;g -+BYcnC-O26OCfi{7N_v35%cjKZwi~Q(6^lx_m%5wi?!=Eb2pIbm6TXA=#xMZ;L!<9yc#MW_@61!b6wK -ZLD168|+Fdlra{zdLyMd3zzQ)cVnVv;R+h(~+VSF|;K!Rh8ONbKWVn^sq}B`ANHg-mt>img%{Z@pscZ -tyPpwO&JrZ|O{U7efv3t#ln>8xuzM?B>dNF=qEA+H2Jmy)o~V!{fWx@yfi(t+4%hRKLmA6cc=XJbWHE2^^2>>&uMx-8Rdz^sl3RA96v4vg-0QK)5uZ-ghSvC@LLzoPq!8ZpX -3ks3nt=8Fkj2MUh*sTS_2xn<}Tiy(e6e&^%@c+E=aMX6f%I`fhFowyviiT>wNaLbw@&Bapvfgy#`a&d -=nZ{rY~+a+q^#BN6BYO5>({7cY8qwV7i#v!SKsvRg1TR?zr4ik4l14b}Mj@>LDuys*qqeO#9;g6ms -g%#w#*i+aummjD@^8kLLiw~dK^UF-@({`oo%rHvp&H+qOf8JP$UGEi1n2FJz=8s1=yqR=$Dy|<#Ha^^ -9#v3#~ZlNT-=Y|=S0@XMzQFp)__3DEP27tE~IaKiTd -ZtbDi4Z|G-mf__@n3b0)Xo7fh7G}&!?(oHuAr?>SOPd48=BHI2*ZoYKCQj<)nt=%Ufx12DoLobqXBGE4llXv-H{JOkUp`eoIYovnG`s@=a!W -C-HK*&#|)Q7vXBI^82opTE=Fo;L5e(I{9IH1kuIldwPWd-|lYxoQo*YIpX(3mevu -!2Ehe>lr)$YAkyGMPK{7u^iw}MISWwdMUhk9$2I5ViCOi7$oM*a0S_;?NGP}Ho%RnA$FRKLZ)Ynak=1 -N+MF&Tg7y+kXo{2+)H*P`$~tOYp|^&EFUYPCTAo8YzCL<@XzaoTz5Wo{@F_1I&+A(>Rv3U>0v!Uquj7 -+x=X#S`_kwm!N_!%xN5p(LfNxr)Ismh4*KW3OJJw9Skl0XjyxNb?=|Yh9cz8529-Ub(2hGA&7PTF6Q^ -|NQ>MdtM#R3>kE%h)DE*u6#WIvz0K%e`Q#7}sEoQb@7QcZ*_(nKNmYvyv~RqkOcBiFm45Q^9R4bTsN4 -)a79ALj+*il*1juh_9#PG+;nO_WD^0vnAj&VEzkadSs${j27~vh|JwzYu+}z7E#%nWI#prE#G`T-gn(@FGzlV#Fb#ZnCtKFxiI9A~A)6Z|Y3L^9 -%&=i^Li4~`oIKr&jKfXlLs**F-U_fx{O#4I6Pmil6A-c^O;BVP&5T7wR+TPS$XuD*j-0zC){K9GQ2lF -71$Yn8FwtlN}O;>wsv3cf~{N$i)yiOk*)%L=%whfzjc;BABS>kqa}ir{fuP~g@&e#^dUm-k_vQNuB|A -h~#*c(R$pexU8yOHuo%RMm5A4MzX_%YPpP{cmy7{$?cW|Eu5;C*J~rZH@oz{hy)XUmoKdZ2Z~&KcwH` -?LXwxn1qD4X_R%@U)g+=Z2H4+v`v#l;WoyZ>}>^G5}n+pQlrnMBi6+oi8tWE_J-1Rp^x@vR3_X4ob?R -rA-8ye*n)@^Pf~o>qg$7I{8!Qu`*`abS8R<8(bil+Fxx-TU<-P-I3(G-Hqs3}HdnmeO(@#+B)p9O;m8TFh2M=DRvd91%)0wmGrR4hAVjr&5B -H8W_pM+2QTy7>IU#l|OLrjVxpJTPtljmy*7+V$Zf$Shl0e^thQwL7qZLoo#SnI(p~}unH -IF>|af$!9c)DPYA0K{qVw2>e?wDFbW?e6vNoe!a;O{q)LDBZUA -=@1hQjo{Qt?$3-GNlHzcQ(u+-GHXo><+T>BngtI7nYkWcbh?N$ger50{np22=0`Au&7eH~eK%6BOgR$ -f&;1Q+CNj`D3dt*pydaPlNt~rS=l=fvy%^~bb^G3vs(er4f#Of^cpCW?-r8^~a$${2&=ypo7 -fpx-e4G21Xt8(TVt%XkTe>QrbawnA%{_*rJ)OH{=97sb7 -rs2|A`9NV%KWOlECB>=UF5_n@zUVrEBG{@B8Fg -gQMo>6z8$Fw4=L}JosNJpW-+j@~&wyiB0Ha}!jg@r*lMBWTgH&z@&@mWUB7}D}HD$Y7o!JagaR*=LX) -OiALfHcOdQZ^WoS6Acpr^F6fLu$_^oShaJnP*?&%ld;iwe-G>{Pjib%JWAyM#lKAp7okv1t7< -3X1){d!jR!|0F7MY~#28`b}VMmT%hUZx@+gam>Cazd4t6N6Wd`D^~Sm)0SJ_?cVp#|3J_bc2mK11+W~(#+-IX82u9&3gA+JP(>O^pG(jLFMNkyEDa#_8)GYjIR2E`eAKoTvM{V9l6VH^_5|DJdK7FkBk3Lot^dS{Ev#E%Z_~+(Hu#On6}f-iYq -*{oO?P>RcsnS%*Q~cdk=P9RFmjWYT@Sa?ffexa{cxKN`7SV#?BOLz@1}jbR^W$2;1;A|o4M0p&5K?)M -1_NIin7)b*K>qr)zGxy`Pp!KryL<7KAcTzO_H!e;;v+*JbbcaF?xk^Z2r;J=&UiJwBz=-*S7Bq7p%**{h1%&x1R&gZ9fNdk`~teGD~U;m2nRhGh3Ixc9U -}0yRyGvJ@5tWoj`Un*EJ-kvM%d)XQL#s5)V<`eW%{k*|PlOS`Wn6y+pX%JndtC_KyWC*R@V64`-GrIi -_O_(Syw#I2OKg0pRbD0SqL3@u0S0brp-2ep}h&fwS59nD-{pk7Ym%bH -aL^Vhk#=>MY*NwK5`_+8;Kbnp61Pm4z*~utpn%LFsRSd6gKjU)1Tb~mZhp06=8Np -fxT8d|8#GIF;oWt7cX)nA}6Gb-xsGn-k7AGSdS>~6bzjqgIL1s!Ma^n=(!a0Fs_f+@pxpJGd`c^@ -a!KYvGNkA;RF;TV~OJPiz2y@Bc+|KVoOb)?|nC3e;9ol3C^r3*L#7|t_dTDQ??*&|PadFQyLpbz?+?s&<9rcgdVRDLI-vWGO5BFV~ddKQ#)u$O7~HoJ7*^inlJc`l(@O-_hRo>&j{ -~KNmp3&^&SCrqdw9Dt5?TnUZ2e21*j2>6d^xoT@2MWev6h%o2#uyYu5E>^i1YxL8g&d=;{8P+)&K9ggR*=``0Q4SJ9j`_2X-%3UCj|@_P|A!=9?1VquSZ)k|(RWR>&)U>Qx}f$?cDN -e|#?MBZUruV|d*E+H)(s(i43ku7Z -YelAe)Ni!bqI4+5{VVQ4keej*o#*F{#UMNY^lPNonM8K>u{eyPa*`?G>8`Pu=!FR)aI?*yQP63V-Uz| -PV3n8P)?7m8cq-(v5d)1%&aYRC7Im$1-IdHQ%ED{N^O%)OJ(q@+f%+nra3@#Rke2e|<^8Rk>GNy459U -h+gsw=>2|;t>9P<^?mT~x=K)5M_-niq{0?T9xbGVmJ_B}35Aw^Fep(ENu_b$w#n}No;hhHn7Sy+x)=> -L?tR{o&=_`Lxl@U7+QAkEj=4-eX1s9abv7*DF6$Pw-gNf*s8!c!)r3*C4MSga8|Sx4_M@V)ZX9|-A%a -a|GbcXnIep3!4{-MO#NjfMGFt=jkmuo8;tE1@_PZ1dez>&7R3Pj`&Y?{l}KE`Qw)*f!+;q&{m+*sPQqZhtPS<4KHHkQaJGg}7z#ZN{Hv`YR&mhi?U`|CwN@8}IhWFz%y$r$ESPslYfYk&!_0icS3I*;Y~-(1x^BCB#dx(c#uGVKp^z#hfSK!s?PdV_c{MF)6tcoBr$s<*pFM!T9kKV -t?bFpH;$xkSy6ZYMmCu*!a7Rt{U1&C$ImJ>EdK`|E?ctlUWb2J>>s?m`-7!X3dc~I`pF%L-%nKLxBqJ -H`Oh5x|9hADB(M7G9KXEUZ=pvK3?lt>rIkt;09i+wRW$$y>=Jak@;WoXWkAC}>F>!a7MqTRA6b0nBdH -&QA~B5v6n_kZ`T>QmG>`}o(jgJRND=*td~!JjN`SgAg06~!D|YCOWHLinS!wD$wUNeP@F7Sw0N)6BNG -sh{9D|hq@)?Uj&G?Oki-ZBfD*kPnUmQX0lyrXwJ^GorVljT=4bu4YhfPe4v$hz^-LROqI`j^MZ*evtK -DN$&ZrJ&(zl@i5$uZ5+CixwX1c}eK`N21+^~QIRayiK>bd!e)*c@Mr$>Z6N7&|b~qKMqXvg~O&1R%I8 -y`$^Q0Ft}b2ZZ<90PqD~tE|mS7v5m|`VZU8??|i`58*Gwlp;O}&X_sDU-fjB1+_*Bc?WnT^@CY*90Yn -bbwQhtLi~J-&faqeKjn%2&tdCrfZ~tESGZ}GJVEkj?V}RH{ciAq1matx{tALx?!_gr-U$4b-rdV`H85wiUUaqr9!*2CU4`5&`z`C5YL-z?9M?mY}4SVT+9&bTUJM7&Ea=W!ZgbA<$2J(5#mY+6h@$q`tmr -RH&$<7njNJ-9&tP2CBqU&{HmO8*|IP%?+Llw@G}r5+AYRiegi+f+k07KePHjGZ(&-d^3FpAbTgMHui- -bj>ihSfd8nYz?;fpgQAvz(9gklgni+im@9BUaxT$f%M*1_>LMSP=E|RLY=kJq;v@D9vFNzJ~o_G1Z#q -ReT5%`L0_Jf5MlYV>4UWnrIl%=`(ynK!dTj6)?w~sL&zjsjP#^M=~oV5SzHD1dfB6s;_dXvk(7hZPaW -WP9&9_!yQ{Qh22^9E$7H<~bg@j_&7cj3O|Qn=UGH3RxwG=P63Y2Vzy0*>727mbkO@-i*1QANvP(X(ZC -*izS%7Q)8yNj*=5?9k#(zaLs3zD0*nx<&+dWq0#+6Q`N8MawuypcZBir|@|&&t9xH@D!@@;W3VaO3x( -E(AxwX_L<#S2bpv)uW85ETdU&pBr;yC+T6MZJ -K*YY!hG1{}S`l!32pI5fFTXB647HX%XbQM1^y(9(Fes127$EZm$`-$3+ybQAF~GMW)SAvn!SKioAfK1 -hkt+^+@k!#9aw16qzt-a0WGP_fW{@?9f`AIk;@(gRsHA`fMHKjB^y1_szja!ktJ8x1&S_!(KXqDu5;} -g4e!c1%H_+Fef7IuD2UgquQx`;hO=`U2AGcjN=4W}qbzc8wTF`g%`Zv>pzMI!~(^}TlzsXO5d7EQ -~c_A-^Xq%2-j28>~FsXC|Q%cG}4&+NSGbcpk@cNYhhpDA#7low0)45#cCB -;k!%*1_Ynb(0!9e(n%5JbRZaXB_4c4N2Noq;PPraCqo=R|$; -h5mF2Rag6XB$nBttsP&-v#v_wkX4v!2D5m>Cb(hbJ$2Dj}-m9DE{qU4wq4S8CuK2syxdrrs3!_c9*33 -2b=FV+nv8Y1`R%&^k(I -=^vviH32}_&q0~KR${7$^TIDw|tDn@ymPQi`kFf4Y9fm#{RqafQ#NTD69xM0%vJEcF)hiNin$Gk01Op -L`@W@)AS>H1SD`i^EZ;VMg`E%{uJogi+^!57q@ZvS)Bv=ntM43!*tEAqQj>qo?^qcF1EgdRfg-Fv{Cf -A!0aR)&tkpbq%WD*)w2dg%3jG7558=-XHU&i3Z2!gX_D2l5ct&dWU{2j6 -E3uY~Ti5jeTb_xI+sJKpzY9dX4HN|h98jqQBpwB&F)P>tes2V_f*Tl9joW@?P#axWEk4>?nHGl$XL^@ -ly3TJ8Ddp5Q=sV1(X0bMA1g3BMmC^Dx2HEU3%FEa{mozpTa4vUMzm(%6`8o5%67a}fkD;-}kOPmyYKh -HgIdYkkYQ%w5-O|}s0H#@gIZkkxyZjSLWAWg~gf+Pvpkg>uO{cmzFdOE00 -eEmadUY9hVUrqVP|L+al&i-G2*x~Kbm%|v|=4%-y&Ej!<>@S5?^9qNkd=1U55A*U5B^&+=cpN_;bp6S -j`G4n%AFj>E(Rwq4_xa0}zL6#V&aa`#71o}tH5g`X2#Wy`0uh7!$+A6(L1HGNfXkD8&nO-OUjaZI7ax -eg04f9}fX}fwHOof8O1}01U=c`#08)euWJW0Z&zSrbZhxh_txr0!6&X+#DHO3_|a*A?An3t{j%)Lob0>LF|Bv7Usq(ZXC5)SyM-2~1~=mY=*QpL -#}}tzclKQZS(Ydm&@VnxcW8ar9K)BmSP$dF0UExXT#c}{3*3ZAYo?X?LWuUeS3GkZq*&bN*}k6at+n7 -vXo5WJfCtMqU>yOS1tPJE8QX8$pk?E?H|L+;8|YUz=bzpi=vO!ApWYkjS2yRE_vT-5nM#%>P=m$@aoQ -O!j~=5Ul7uc*od;|eq7aq>^r|om8C>p}m@0uh&M_ZFM*`{Wy^6(|eno1LI;F#_d1b+C(`nbss54U*2R -F-O#B&K@nHl3MGtd-KX&b{i-nUVG;GCmo4{haKc#6ZhlYNPLaeEn`X3A}>TXW*KLp^nIO+#)jY`d`Zl -hcT*0`43Q6yg&gh-~VbNzN2t;dXWJu9ovE$x_P&y*viJt3-Q#X*62=L -ma)N}*|c-YRs(fbn;?IpOB{UDsof?uPpF}xVqZc!|A@5lAWOui;2V>~du6Cz=n;t}!s&E_j6--Mzs8;&Yz#VD`9 -2o7HSByG0)jhQ7%^bmrma7)B6jZ_f`T!RR_SG%)z*H(g($M>EwTw*5!3pXJO^RTd&vHMCWvKOEQ#$9q -*W}CZ%A>s?|r#x&8n -2U-;rOZvbg9v3l5Cd5v-K-d9b|T%S{3#uq_~%Sf+Ha9qx3lOwO*1Lf)+uwk=?z1rHKPK>tMs`!jFG{% -78dWt*fUe%)3+X5>(^r{50z@?9GUXD>rV!6@E2Y{B#J7q9O!_hcQd*+UVk-`}O{uFL1;ip>1Ro_ii5c -h^Szdhp^=|81%d5Pd>FAfVXKE+Y9LM%beY#!a6$EX*B=#h@%6>HWOKc^I>O#Wq||oN@zLKWLVyBloib8R>!J-B-D9Ti3#3p6U3gE>nrzo!dB8mcZG;_Yctt1k4 -bIGEdsgi<9UTLq>FaD#g)cz`>Z8*8GG^6Xvg{$P5Yx!%i2++`CPi&lNU54vW>!j4uJB^ux>xoW4~m(f7|VoeBe{QM!v350+dl0g|7Mo=8ZFbXGN1ZRkEkx?N6`ik&3 -xx5hclP*pv3I&OpVI!2irJz@|W~6#qb -A=Rf$&@hSn+g^k<5KepotQvDh(~MGUWWuPdu8ocxKQU`$yIyyy -`9y~HiZX*@rBU5)d>>?!WjA;D8ef^1CuO@99sGAh6P9T_EE*!ZJ}xAWFj2w#@@VOd;OO)r2be#ib^p$ -}!e#YbEgqC*jRZ2qp*)h5z4|Dy4cTgy7CS@5KMG}6wd51ql8c}{v~?8$!=_^QUmCUA*oHp@~DPWYZDG -@hEhc#d}bgmi(0-x7uAY27HCU;0ZV0Q%{lOb_}`^ZO^$gZ|U}{>k*9uk-sC04M*zk<2c#NC=}-t+i70 -$PO({1;>t`9}v5Mbur^Z65`1n7Rx)Ugm0LsGDfdo-(FVw75 -{GhEZ2S;O83|FyVdy;%%`+B81uE4HJ_hXHjd0}`IglSm|)jqXDFXcO0Pyh-+hCbcab&M=%E53+C0M;h -IUul**sy87IUSV)jpIK9a@)QOUIIv8e_j&j?~SkaG)_8NneovItG*%amL7BjKpkk4!Vyxkd=7J0er*= -HOCCo_Z&mZIzYJTrBkh&5)H1%5_*>(oBvOOZIF5Fs6Yye;oWN*Tp)>q>7heXZDP(jn8G5;~3d`DtPHH -ZiMDfKxzEc`!}l^vQ?M>raDU;;NKmIe`_$Pw*4;=B@7kBl_tR{GHkdYs0 -g8zYj0^I+TLb7ecr~AWCvU{lno=EQXK^p?4?-D+)mtwCH|0uGJaqZwr -tFM_YRs7YGALRI3h({2cUxY{lInBZ%q5chbi6nOCRo>In|&=@5${uEFlp|@~fqg~}*Ew$lMh6vQcS>h -%H7w4SSCZ^=upzEApiM>2V)65;sO+!=B{m)uLpG87FIoO -0s?u$4$5&SSAc^kB%=dxIMpURW@%LLmS5^Az+>wdLpVhLv;z2sQrVbM6ug$$Rszp7{WJYlTU5 -MamjF{Ix&oG0%8SLnAaT$iwYUKw0Y-xUXe0$-BVz4_#@D{%FkVZA6rh4f7|>q~)csbvPr&{ef~)?{fU -o2XkMeiLyvt8TFVG28Kp*n)i-Gc2RIZqevS=;=pGhpA=LmBqmFEsotvxhboYW6w?p#RG%!z<$^SaUvt -!Wl^{;k5pr?Ra-A8iqXz=t=M3+;2``?ksd26+8fe^F!amoN)jBA<0A?<0}onn7ZITFt2nm`8c~L;0sM -Q93vGyKAu6-goYe90C*=e`6B-LC6QtW3a!HnAhJW=JPjt44q7nP3!H&x^Oq6=fUNkyi>_`V~@e}wFJQ -B%fa@!cS|WM7!IVzv(U-$eSa!ObW}B1V76>!lny8OeIuPG>C9NzR!V4XlPZ14Qs+FJnNFdkT`5uG<4I -1?loY!Iy6JS}f)#V*p*)6mW{OFejM|;=olw`m4t*C@Xt`}JInNMV!e1hubX1RnS3+VZ+iFrC?+9(3LR -RLC67>|PI>=iujgU&XZ?`TaSI5IQvOIDdBC<;~W7C>)fHAPl6=R@sh`j6BGTU -e4?u&)a#0gh?5yddNIGyVmqKsIM^3$iLXDEPyF+g`w9yV_<4WmNl7HH= -M(~deGE-l_&1Kw2*UqidL7z6G@+TJP1WRO+W%!S|N8sCGQ$5SPy2-faWoZA?H(=72iMS?9M31Gw_1GcqQjM@8z|9dAk2=@bw(7ZOQ -8q|vxy9JHxs|_P!@n1%9#Gn6gS_jEEV*rP*-R%;QIh**}#2X<(*miXz_^9=NO<>70;ixZPo<#!?v~14 -+p)|JIR9lPebAf(l0Wx#Kf<~1fy>uF(94#vhDKy&_6~F{db4{aSZfthyF7>=0_sKAK@{W)?YTHx34+b -9RzN7#b`pO=v>&#@wN#^>ZF7jL~F+a9c(`;2m5;JudP6e^nq%;&8s@VHlis0Hf5+>83p-{XydJKc5I- -f7u1iBfFX9XEN$^*RKa7wvP}{^uU&93%`-Vi5{=OTVsq1l+)QS9-`>-{rIIx3a!YC$n<&FG40+5TTzw -f`Og7y|C6SM4e7xYsEQ}*pJ9XOj!Ct_&u_MwNPh{f-!D8x=aP>=&kuZc5{9Ip)`NrN)*VBZKyj1fK!D -qI -!rz_3gUwz(oI+`*!;2+7EDw#-2RwIP!rLaQnLa`jz$ns#@_|3&Pwm1^ADJ8lu+sc>i~#*pfA&}A`PV} -7tec$hGJ)f3=!E+|%uX*i=_1yTLmNrk#=9PrVZcNzZP<=gU|A(4 -u2j5#Tg`CM>WH3E3Ug#a@cai;apTv#lW_87nyJv^;dbaZ~ABxNUVrHIJ5 -4%^sr-GL#Vp(87UDlmIv+!4&ch1iMy;fI`;3c;2odN*$kbgb-*JGex+xV5z3)Ac?tY5Tu6aUk;Z{V@( -y#CF!pzr4OZ>9x(H?QyQ|M!|6&^MwUT&eemasS+BQkhlW1JMX!j%(GOY=1e8nZ6zsMqf3h$%8A@YV%P -abDStoQ&gkRla9;{evX?&TkNxThv&_>eRPxIRJWlw_LTN0DXl9u9<7S9V3B3UQ -_*{>36yx!p?5_CeXUA;ziua%ARlIl0&wOZnHJSj3ix4&{~{Oh1k<4mMw&3DMt+L(_-)Er{7?)%^*#wr?KJ?-)^Ki_%@_;CDC>d!7{Omerywh-5>XCck&UXSvcVDJotGh0 -wR0;RI+|k`VZb^pp5DQj+UJl#+X-hMwyB@VtqhIR{w7(W@Nh_{xHdbF>y~*edWNZofT5cb=q1{M=w7E -4Hs^>a<3r@~soJ_K=2za(!Awum_h*!#?8sM0`3Q@`=;ATDX!tYX&%KRN4=v$z%bH{X`YQ1Y6 -n=%brXTwYCFqZg&>{Xqas=9mxJo^{tFe1jJG7QGt${=FWEdeeYx|EzX*1Dw{iG^v*YM^3@K^WU1%Q-c -^|m@l@T!RM23CFrH~r#U$XZKSLCX9iS3>qyl+Mp=E&t$PS?0V?SrVCZKvK;CZ}3k0+Si2W%bZz|lCyh -|UHMVd;|o0a>!_hGSNa5ORTjF$JI2Or0boPDao6C&b1? -gLH1d?k+H`pOA4{6?rPH!1BA#u&ETpY0KH-4Jr`70~9aq7hYD+-hqk#On>et#L&&`3myS~dLgX+y&%9 -3xjz)hADe~2UP-bIU>RcQr!lUjMZAz&oGb8Y_;CgzHy-nGCdF5gf;M*gM!ZaInZh~K6H%13V)Z0zG;V -hZ+e_;ctx0JzZ*=bc4}^OWWkZDWbaRuKo+iV_bsdrzPB7*{oWrJ+kP7MLAM3jCl#T_b`EnjiXXJ_cfP4j+7qQO>K -nU-4Y`;(hJ$rz&+7U%~KcUtiBIw7cYrS7oT*CnTGD^~jM3-BNI8)xt)V&p3)V1#Amf?w%f%D}5JrJrK -PkW|ggXI=rBCvhm%fy!V;*@b=~-eu<=bFX{d%T{C~P#5)aj@o%sl=ym#<1S&a(QgD9-j({{ehd{T*bk -WC-H;4-!2J<0}P2qA4W*jw8vD;z?6UWPd!?Iq#hxeN>!=`$dN~7FllFAV(B%W@W6w4uWMR -(k#Ygl3wDk(Yu8`YhSD%-d7RK^q#)=@Q=5SdZ&FNMP)6PAd`fQvCqI2@cBQ_4KM>^1>hFR -`H&g8W*j>*MPw(gda+-)_fJKpSNYhFaw;@P|gAir$^`i__2*WiG -$>6KrP%4>u&pjww^AkHAx(%v$*1SB+9mI89EMTQ_83DcEccx9k -pu9~a`Mf{BhdF{*V2F4!gK2bT+^8C0%JWhEawmvI|sh%?5vI(=>pZ)i<^AabGe`} -`W^XX=y47#kM4}e@u_`eIQe_MxmFWk+VEl;{FzO;+>(pC()_Vcz0I{jR@+r>{!U?1-}8tnilV^RYICs -IX=Vf>&a{#e_gU)JGD`4@FK`HiJM2Q2k~p6$?7kK;>gZf=uqv!jdD$`VTK#k@FEhu2KN5n7&fnhd?n7 -Gt#KdQOt))WJJ_qCFFm(^Gg57+KjqGPT>EiFt~z%znF{_R^WS9oy|$k-Y%hk~Q|c9nUr#VWN1&HeC@| -4$?unk+^hoC%8etV*ao!K?(P;6}->*=R{1~_=MP)zwgk!jx$4HN8P>;8{-;R*8VvtGc*^|G4&;PSXNk -`6(9DMJ|gCR?80qT-?#BgPKLw7J~*UGRpU*=ZN|RrpIX)qHqQ<{wA&81SyP5SM%m$z#mV9!?b514NV` -bI>{tvQrDZ2li*UFrFH|f}i4~`#dO(Jt3sLCenw!Dmx|{vrhK6C;eWI3rh`;vUX6Kw*PLo{kiHOM5j0 -d9KXQ8MkA#yGXFShLQ2~|CQs!5@5ur?WobC;aeDrLxO0HK63_g7`Y2gYf1D%ex@z~>R -c~76<(`~x*%KbQ&r9Sj&$ec*bJO#d5_?RBfDK$)PM5zz6Ga3mJ7jtcwyg{F6U3wh2Bcrp&N2+^CTNA7 -@afsr3EP2$37vHuX<(SpOmdS@TZJ5a}bjWKqqU1*U)8*Y^cL+JZK2~1mPk~5R)^Qd_lNhv$p3Y72fg1 -`zrv1I;4|>a_+S=>5)fAV%9;v-GTwbJBE2leuc8)JrduLtzi*Zr7?iatQ<0e*wG@AuDpd|=q!MdOi;^mwRyTMr&y*7u@^xg)2FC9OdYH3;qc!YOY@FAHzNCkfm(Lm0y+&(`x!z3L -mteYViI{bVbIxVyjo?Q*BHXqevApOs73KWRT%UhJvm9;XXQZSnyHzvG8^>n* -sB_B0yC3;@NotMhjo}(_-A@m(MJv&}=GNPRb9Zn~1TeSBPzc5o6g;$hI^K%!-ho>AAHNr~OS)lS>g&% -b1AdxR^w1V`OrA_O`A*Z0?%sShGL<4)fvzT@>cnzY2+9Pd?T<%VAW+g{8?chXswE}6pgePl`oW>!K^A -V;t2qboDVOl5l#TE}gN)H`=x*RyF_KfT%&FtodW->ueQ)DG-xF=Mlns{EYqm2|@gXL*~Zo;P2`h)WHM -PD;Hw`=TCHSXI7$6)Y%Q@ILP5_+OuJy$P%-p^`GJ;OwL--dIAyB_pNT8Fz|uhfw3j~uZqHZoPEJO6%C -vqE>W+M;#G8SA6oKTeq1TC%4;=Bs^q;z`+er?T(krv6k_KtS5|W8?Ase}9{*eMde2oDlvWp6fI3`?vG_km*HH45ulEps>Xsqc -Mi2DU2i+7Y#;n3Z)o^CMg{KR$h(ZAZb9z6r&)eN~|!ai2gJCo-TpNK$bR(0g4)sK -jWZzj|TmFAfZg*;H*>%(z_uFfKC|$>a*11H~os^2}OXr1**X)$m5dmiuFo?bA{0F`pO{!;8iKLrkYn; -hh?bIDnUmOplBSfR|N`l45;re*CxqT4k-PtGll}kgZ|718y@@EdF*&|&r;^l(>5c}M*m*g;rg>Pb_oU -1d`EbKWU73|63`~HQW^eL#_Db9J$DLypFI79MgPPbtz)65zd*X4S*0^>&=%=KSi7hJ3*^gjIJo>&-yR_)ME3amFQ=0F0eRz*?9QK$5;dys#TDS -d*ztzXkV=WA~szxL)W@ja~8Q^&r7$BQmD+~{b;af$iQc@oJCRHNKr_NAt|v^QzyXF7r+oio%LS_Qv>R>@a#16WJrYT}zMY -&f{IFw`$yFbyA<>Eo7HfHYlO)dA(Yj;^L=MHPDOQgc-2&e8 -I8@LtSxkE<2kLro%%rsoE5uSDmq`l5uZaocNW>~^CbQY3}k1CsnHOkPgBEAadINy8$j)Fjl=H9J; -mI*dldb7EM7vAZN05-ha@f$Phn%VzH`=h(0GXSn5F1B)$7NV)Q`BN?p%|41a{-ux>b0Kmy44nSM1C=J -*kb0Kl#0(O@mA`cGN%!!yZ#!%dheEs?_J!omEVZNx|#v7j_-(P-gatGYheIb(Yb0PS4c`8Qu4 -A=IWr#7)MbhJ6F{F^Yc5t)vML#jW;Kv5OCL(HM-{yuc7+ZbPANTI0$kp`9pzF-&naH`CcfYqRlfS(CY -wWe#CD%5s4qFt4rwQxRrp7Sllx@D;3!G?MIf{eP0#%6ma?9q -MZ2(;Tf}a;I4q0~-yNj97HVneIH=G1GmqS#H%4(D7s}W(z9WUl%@Ptt0?i!)wSSzsN`*@s$4p#u1{QA -)&Lm5rhA?mnVJ-7~@~1I>>Txq0j%`)!X@3{Hq{P~*RSB7$-@G_AB(~n__dtJ`1fia{<6)@Adv7kG(Kd -RPIdRM{M-v%rw|9eWg|=AAXowb8cSEf&Y-~w2Ex_(3Z?;-HnyPeGlqti@7OK58>|?$6dVos -M=_+_@KwBUJfjQ$6NRCh%9Zb&<4s)=gFiNQg<@NJ$bDkpg__>|WpPq6_Z}v9iMuL$sWUMbP<_3Qg108 -Tu6WjJkd0gD>L!?Ks?q5IIoSK7;PcmJ$Lk3~Fxj!n>a8#+B*|t~o4=d;9Ee<4hbh_qFp6o3SGuW%GDW -`u)PXtm@Tu4;8zh7;mDt1qshBwJ%_=-CmS@pTl;0Fr7F&yCrp3%7~wz+xdj@&+diB -jC&}UgqK|0@rPI+M*S!fMdUK5`om@+CH}&02nM%0BZocl`$CG;t8f9X!Aop{6qll3s-qa0$iA8z&;rQ -E=e+gLVei{2&|AGD@pubgLnYgzjpKKBnrnYC(O(@NAg6t0=?g|)A4&CLG`nf2vBv7#(m-ewbiRSua%a -!HnG3-BvtXrVGv-*T)Vj4w{i?{05!{)Wld -n$e{Vnm@yBf!uKG?~!_js&agj54Z7a`CU^LYwzokjydxnVSx?0c>k&rS1Sy3H)j=UU2cv -*1Y^<%wpvALz2>B3r`ca%2=Uy|E)A#J1;N$T!erj>1Ja1t?GZ+cH1(WGW#2Vmmah&;#GyR&`ur!GLV{ -r8U9#?KI42OXRRj8hRYykmJXB6K|`?xXYF|J!?Vl9f|yk)BzCd)l<-)%^YFC`+@3<`YJ+y|mdBo9zOBs2*phpfoQiBOB`1)*fT -|pp>`L8YaUJ>E!vrKI%A?qfGm_j==YGsy8h%#Pt6rg8EMA{Jp=(SIS3CC*Bca(f=B9O@xB4y)#Vz;k4 -9cb}T^&RGP)_-1qc26iVcwQ5@irBuO;u5NmSgnpW<`2JQu0B0^fl#`o6M%~G!@s7?jl)f>`}{kWUS#v --3zX!ct$f?eMWe+-_&M9;^I&p2Y%RgnlvCK9T7%ACHl8`rhyTKR2HLofG}daQ=9bAG^;Pd@-5~!O#qW!Z?gkI71*bhB73!m`EGP1qkP3g|;cQwMEkHv4WI*R0BGF_u4 -U)Ar(zv`Up@Lz;O8y}|EUmFL&1fexbQ-e$dzgpeq$z4TYG&pR~b0)ea%pc?)Zz<}4R(VBXZ#iWnW14z -Yus%ri#wj=y97NV!7onqS!l6FhzapJMmm_wza3iVi3JqPiYps~U6W?y*hyiJ7X{>nN8R$eSMDW-sBW% -2Op@hGRZ*~=cD=0kOZKHl}1f7>+-1>FkL$x;%t6ir5la7^=w;A&@URd6Rq@em -SyLljxfc7Kor6;ew#dbkjF=h1guh=uXPo&*FCej6B1q~9dl@28}Tap@_6m)!Y$;xIx#GA-i{mIVmxs9aWRbFCWe=GKPXfgQ1Bk`o|Xmb*BQ6GCWPH*i`RLU>)@zeJ)!I-Iv(3&yG2h9!6rEblgI7N=?`?`9&zLZU+NQiBC}}NN10zoF6S@x3eZp^Xr1|Agou!Byw>U=Fje{uQbPRK@D4+x)&()%$#zFe|5IZzwCh4}frQSMjtOw;6R^x#IS}`nEGSG-P_G$+?jTZ$~W#|7d_tY`f_Bh4==8qm%#34>w+h*7K0Odh>R-)$U!L{Wq69 -&jR;S4+38-hqIHi;`Pt^Mhr`NAi&`XbohbRI*p9FEO)B%^EwBr3ar%eo{2#EH|HE_s&3yjyd|xdoMi3 -Yd!!SkS2)qQM48l+-PGK;G;TS~&y=nSe7MJ9$hL8e)IuQecWMKZVm5PM`vQ;qwEb3w(m#FlkQ|#|03P -8-V_L5`RTGU{GB?}YKNWNG>X6+AO&bq{qNC;>T<5fliC4Pk$!B=bt0febz3^26Bt2h?G&r`r=F5&WWm -ULx@S!^`D@+yKF2n+&EAc+-$9yQR`PW?%*q%_Or7YmLo208n!Azc}ryH7@EgYBO2p>9;;@K~1Y+kC(J -Rq~xbEO7n*mLcsY#rR+{Ieay3^76Tn#oa5$oEflVfgbLi^F{pm9c+TGM)W=6d~Xte{4ZV1} -hto_jtB#2*aV)gJ}VxV#HN6Er?rwLnkdP9xAvA29;+S#X8{%{Y#=g(+#_W;5fkm>ke)U`p!_^*&E;u} -)MXxuhd@XN9}?uesbmTa-359kths0mK@jnWC_ov&?vM1!R -AZLYD-X@S7uneB5(?4R5N#nrQ2;uhV%XV>-1;(dKXC-`@+73f>C6;<3!=vCOLjKn6Ee<&K6eK41RABF -dZ@I;;rU*kd+`j0#RLQHb;1>$`kdYGQZ(CszLLGH`WEJgA;!<9Pi#%De!SJrUoy+UL6Ky=-so^|O~bd -lRoY1;XLU(-TRW%3238WrJ)PQ*!6$$`P4l`GYyd1acme(7Ya(`cisySAQB^6i$F3dRXI;z*h+1{8AHL -50N}wNsdDrhl1_Ss9rAYo_RMPqNHLYt!hW_a|9AU-Y8b -q4`s42h{Fseh{)17NhH-*f3?mK%qH&U@NEoM=@RA}J0$y{Ue^1BKph!=@(T%NzZ7c@R$&3QoR2Tz^&o -EsTq|wF7d}|P?H=raAP?8i1Z0Qm~GAPhYS_0R_vMqrhiUG2*B_NDZ0CZ$lFhCUjN|z}Ex!7eS^eV>$T -Cqb=gC|HJae&|;ZJZFm&?X4bc0?i&UoJZ=;{=V`A@~zPgKTcHN-ZpQdN~!gYBj{sZw=xV04Q&t07{dU -UB=p;eDtBWOly0~LRgvn-{^sX-#bAk`$zbqS;0}i^9KwC8tPoUYglY`wu(!JgmeH(Aqh=$W@kJ2ypVEl3?-5(MVLBlCpc -iOnvX&l*y2N68j(f1_e^*h3QKsz)jjT=y{@lwi2ZqZmKhL?cRUW{5Q1Y|gaVZ|445OtI^;T~{*IfYn= -PQ&ndelJ-eEopwA9r~Q`CGrl$;d~$(nmrN2jd>WDp27YQs__oH5r!#uZb3tHw&Q8^xqW&qEjfGnG -002wnj~0>nD(IA!TxqtA19k2`a>N?c@>Pu{g7c*> -kRHrb+YNQ*>zltA{pc1;&DllEYgCjP3(e8vkkuqxa`?DY4dI~Vb2J@8bDpLV@YupKA9G`8=q1S+`oxi -zyWJwPHr)$MaXILE7<9~A=0vHwrxjh5l@-%qqUYuEc*6Zfg#VhOtkpUKF2;{Ms1&jsHnRXR8g -DqayQTaJYux$&oIuz-Xl2xEdyP|;Q)G!ZJ<#62jBeH&w=5;JM=UD`{A%3#f(vgp_Yh`A~1|$mLC|g#C -s@7G9*F67`4QE1o5qyF#-(OV%V10Yq2PcNh4rjDv*`U1JHA&z^;%P=o^TMZ*5x=0s{Fr$}M6|VPj57ozPM7eJ`NGHvQ2)s)9fSe0 -&iD;_27`<(Xj_Ou_I3#rVG2kx|IW5qz_wkKzr>7{&6Mlmz~`VWl7qMWRMuVtb2p@EQ2gG^2 -bYB{LQj%+14(h%vGfO$9g5Df1 -7u-_|Dv@<2f}UlkbTi4s3bx~)O?8#b`RNT^_p)70zHJFNw^PJWXFYsSyDq*KI1{Kx?Ky0=^LaNx6c@~ -h74^s6nJ^fE-kL>hoc12cxM7XM$zy1$M;!SWEjsL_yQ4MrJe3sUv1NSHLjej?!>FIvZYV1c-<+A6*iL -Nq0cg^BZ82QG$YHJ^C+KI1njiTsty>?OlqyLiy4k5$$=g0TColdV=H6t*MlU)EAqf!Aa;?rQ)S1`p{`b9#4|k7@i1f&aJpV1LYnB-qi39q;5o_(e*IqlQtq>o6o;x|f_WeefW=0>!wrCtK#hE6wO?4w2Nd_wGXOVK-y{PVzl)I*RS43EHKRT3A*n7t&lbOz= -Qye`{8)CqU;{(N5;f5q)JJ8Ql53z${v)6n0A}sP$SE9@jf$Y&4+w`MwkSCA<^Mgo_pRLUV@ov*MtwDC -7Pg7+#g)?N+_kPye(S)l5j)Xds&<8QLT1y4K%99+nrK6$i+_fDfQ|e&6>K1o|(W;#(y8|eU#6_Nn^Nqh~u-UhKO__l;w -zzaHF=cFL(P?i{^5RvrfZE6cpF{^@wT6^5kwSc(9Q6vo(vm6|)$;;*J1Odb_c?z1~NEnpumQTu7Bkbi -sa&_a1NsvNCau84A@GlLoco0<#e<-5`j|}-ols>TPj<8)`x-B6Td-&kX& -fpmB!#_Ns`mxK9YF34i%R^P3Zfs#5&5D3W_v-c*rzO6pu+!#n=EXGGa&jnD^QOGkK}lH1j)DqZ^2*>o -Z5}+9_5^o0@=?dDjf5Sp%2Pk~{EhMF;*2<*jvyX`&z(1<=_4AoQ?v;cC}%6R==-e06XxopxFN;-ecW- -U7lz>7b(HxM4<84e4a0%Fc9NII#U*DhyD8zj8jqmpEb4imQUp=?l@bo)afn^b+z=_hSLO^K;$FU>OUN -o5UPd*x_V#+wSY6A{NL-5Q5sE60&X1=~ZzDVD4xUlQVtVk7SISJ=A<$l5r;{kEp(@f(vxa|8F)H#$hE -TND3?U~eTpu~e0ER83>!WPfP=dW@&E+=%ugco7A=RTd2D>$u^IZ_aaqN-P3^Dm(pzGrbeK&dVeV~1)d ->v|EecLr3o{S-pnS11$gbuGy;sc6&q7F~R$AGl&qD}s!%E$9r5H=@&Hq2W&OH`(At_4+mC0x*-Du=s6 -P(7j-O5+kS>&wM1j9}xTgcS4_t+hzD7-N%R4jI)9lv_iVYHN5ZUi;cbmexK^Jj~xZ9~ycZd`gOh>2amduP-x$TQF2+gD#pQvWyeA(lusa_6ttDTUJDyzW|%+7}Td! -jS1=V*?n`yeG1UR6$uR^zBuDjeMPS@FNd$KL>zh5~#{`dc8?~b;+K%&@W^`pOD${OGy=r5|HFG@ML+2e~7{qsMX==IP4O#b13~+SqYynejvDL*h2SN5iFEa9%)ZqXEvX}DLI>c?$FZlb>#oh-Fr$(pl0p|6%s3Km_>6|6b6wuBDCCeW_w#mDd -cmT1*n)Mw=+46}#IB93YgGc{eq$5bSI}(!vIf(6e9zb%fi{-Ra4QE_d0!N9TM?PGn3sJFvW6S&h+w`7 -JTkjdmmt`e&0XcF*#y*&}wu+Q!n^mlI9XZH*GJ2&jJ`vv{%hW+jo3}^`%JM%&bwQCyV)CRm!DZcVJNY__0*E -OeL#=ckFV>jF~ofk4xTZq>~>fkb`ZkH^FG(>Ii`y)loQgFGHDSMwkjL!aHo<=81>&JvYPcJ@k74Oh*W -W=(MaI5XqX>)2qF+D>zc409c1RdkShhr(1hGVwTYL%lAx-J-?UWqij5)T -N*c0q#72VF%c(v%L=Z;L*vWJguc5C -Li?+4f(Lz6Q1T#kh^K|7FiBhk{?_S*5LD#Ck8{3APH83H#3?9ZlR0R91es$5dEqUm!DGmGw2?xhp8oA -pynm1c6pigXDCD7Sf)k19k&e51_qdOGoLoVF-qe?xd5z2scj;4UhKdnwk5`jK@p(yD{tVEWa=FJYWWhH)>OUEEwJC#+Cex;_->DT!LO@0d9_zn0)@zUS=D-qQ! -hsIHKMsPX>X>JIe6LM9KYKuHQo@kzSbHEpG&)3m+Wo073F&zf-i_`- -?m-!R$)t;!&5{~}+3#Wefn-@eG`N89uI16Ka5P5L*E`K}%MPy2s|%NWj}AjwQ3G)yuKg3_Ow(JVpy0& -gj^!rnS%9g9Jm2&l2Hh0i<(^GTN=9SnuXKF>GPK!SwPpj-*2HoYaR<97l;t;!;t05kwwGXcyhF+wSzm -;D^7L3d0F1VR}D5PAV!I07kfECcePWj)y{g@OsNS}mtvA(x41CK~ -3E;%JeS6^sO)jzsa1QjuaVm1T=dAGCtep|&!Kjs+(NBCae^xl2E<`|a1Truo-`Nvw(4Uf>bOSM -sEH3+sG_MwW&>!U^auR4^Wj~3;AbA0C4do7%`=gnguQf*OnrzNlcoSrp}7hg9z^fKNOh?8a@R{3&o4| -GdI6scPyNAsF^JvLXm5;wusXZgye6S>2!YDYbprhII@c7)U-2x0x -~6$P+(Z;n@AQaY((%y`lQ<-oGS?r)hBz0PUv{re+Lg7o^Iqpo0+F#n@91G>?furjZ@Gun6+?<5t#2Qn -JKn#e+fut^cD3h9kzJw{c2gsvI=d;ksp6XmWi3lbk7vHy%o$dQ+qlLWSB>?@Zu_uBL3%Ye1AVA{zN?< -YEpGVRb>F45=p)vlW}#~kJR5E9?rRFUYvhfyDIYgC -^QbdSp(+hG}NJOS5#-p<2+tT)1-;l^Oi^}kMW$b*g -Dkg;Zg=~^3;!p!~X2&!}Q98LO`8IDUUp{**C?U%zMZU7EUQhPu%S`GS75=95A6O_Bg&5iE^h@7p$O)T -^Phg#^^;~a?5jMs8crC18|i7)^s%C+|4pbojC?W1g~uy>2I?U0hK{3M@x{!r5?-#j<30g?~y&COZ -jtYuy--lbGOUGPZ{l}s?MaS+GZskTfS#}PO4ZJ_py -Zdg!!#NPU4$@Py$;?DX+gs+$1hy~Pqgp5nAuqE4<$BYF_2tETJ|z4RSLo`F_>W}yF51Jzk)E3o3JsCF -JTJSed<%s8HV6*t;KU34e2Tbf$M8!g4h$~&q<*W0azFF~GO!z#-_IPnIX@ndmC)QhYw?#KFE6Ng-rsD -PuIt*T+Yy#>Zc`B?9_HGAfv!1aGi3eWC2YSs>d%zz`y;+3Z3G3w7>&UMh9DHdFa-Ij{w;%nj2l!r7|> -Rgyv>VGK;b=sL3)KE)Yb2cUhinELWWNbMi%RqZ@5`$hQ(3G43w#HyUH4srid4R)!8zL!CqJ -7TUmSe~SG%QD}oBBfA;s~hBWDKK#Di0@D(Krk!98k^WYok~WGBWTwL?uhXA{P3%aLR?hr -N8EE1E|c9{s(7MkJI4lh(qb{{Zq}G{f%QE1T=3JTzs#hmj2*mz=qrYW*QPZ8Ns(K3IcVISP7e?{Dvug -)pWCOmNT;oo6V}}{&Cv~**_Qv{@v9;-;uh{R`Uzq27PNSGaY0lXH4A46a1A|LhJ77X`9jJCfz4-r&pv -mE8g^Mcevn@IBvqm07)~PzYO9k8WP^0YhgDDq^`6A^DK!-Z#=rN#csY3Th`H)K`xI@P6VeX`PlfSNX0 -!A94RX{m3Fi3GRj8b%Z){RQg&WjZh7mG5?{C*;!*2(y`eD8#kNZzZ&Ypx+cbGBGYPuHtk(#76j+FI^f -|uoG=)^n$d#(DygrFjq$F6_?36jYJmIP?r~o73g6v1;QcZ~hk?e6tq1Wlm9NEXvy2S5FB@RxTwSTU!NM?P<3;(TKx -@#&n&(_DozS>t!r+(HSh2z$7=6$O26smizsC*jOJh5JlyI?e0Y<4t3gLPV1b+}dsLU5L2E140VrTI5F -!GOk>4Cl2hc4+5S*HkEsMiDI|iq@Ju#t#e(doOXLrl`ywLKPenD&2BD@Ay2ED*j6Cr$ldMzwKH+wiR6 -eb=4zA8^%F*fQQ1DSK##g^xL&MWj%7uEF@X&`_vSK5&eM^PIaHa_^Uh&s4yRS6Ym+2v40LzG>`Bd2Q? -f7Dcph(RWwDPLg`X{f8FFRF1(`@;FUBN9lv{kpLG=Cw@;VduO+V_VB?M4_Fd4s -y<*h?-oy6o2AvpY02M?EDwJP53L`2CY`p{{nA=SmhuW -8GC_C_<(Efh<6>ABkcWzFl7+6a@5v=px7GHpdwjIjw~}l?{U5=_wwMJJIL3Rq_#!mcD$wocWVNT3KWc -yYX1`E{R2<#qAa}neT91q`*;f9~%LH1U0iOw5ZK2>a@2-2Ei-!F1#A@~%HMiK8%E9Xt -8UT||gl9b;S)TC7qwt@0R8q;Em!4#U|#9AH#Pt%bZzNA$73+;aKcDfOwIpI1FG+FEE@Ja@L-w0)YFQjs_iWCE9_on02=61dy@ST(fEsknKP|90ignl(jksy~e6xq%41 -Tvdu^tV%1*RDsoi2FEFX^=qD8%~R{5!$~{Wa->kj>>pFhhEn!%+ZK0WyE8oxzigcQ^XX5C^g8TJb(W5MQ+-6 -$hJOaI8VzY?o$Tj0NgIwFyFe4S(B9}yB8xHxzg4;{SI<4~v&4MwN|Yy_zs=j^?E0pN`PyFa(~&b@cLhV^ejsxH!fCz^cKyv$e -Af(!QVflvBtakxkzz;;LvakjXqdz?lA)31hs%FIBYz7+LRa$iX88isAm0W^6$U6&3coYLWlKi>BhEh` -V6%2iAPCUQAnPQXm|h3Ip`bRFt$hv%447C-0!qfLpRlk%=4Wm7%a2oHJ%$9iKm=WTFk(PJ!BkiTXhgH -MX9EnIM*!hlcFSpCr;{;Y9Sao&jfpX!h6n=|lxAxKKD^M%UkXHXP+M!i*48X}_d=op=^kg74A9qr4!w -B&T$U4jCd>J5`iqTv!-q8U#~G^&^}0ge?(l63!Y_>bzjo3?MwNWmN^e%6>=nF>*(JI5Rl^xec@%>t(z;zr4P(HqtCqi! -gA(nsj95_3pu-8M9c0s47v@y&D@IP6I&S0xMLvJk9XABH)B#9FFJZYhork7OuuB=X=bWt)CD}6!9sO- -AaR!8o|5Zxb`&vtF1rU63x%qeNG_^QR7T>;sp~D5xg?q9?0ITu6as$akD)|3qtuLQ<(*2kpBn2XAk;V -|qXh#=_3W)clJUKyJvz;_Yxm-G>nhU9Ud%;yKs|fy~ -}Z6b+m+^Sjw>Y`y9G&{AYgbqR`FdX$TXq-0}yrm11Xj^k{g|3_|^jx{}n5`D;!evwdKK(7rj*#-Yj^e -G*MfR+Q`h3M-cm6GjVxNIpBn8o#aWptdVwcf8}h^l)|ejL{03m2B-!N1B>9#r~3o$T50cqG0av!px6t -CN~fsQ?7TC1M_&>)SKJ0cXzK^-WFS4GELu@OZVcpFtwYOP=vPx487+=>+iypiafO;chGe=z)z(NUR4p -cN%Oi~D$^lfgB)?QSYq^8%VZHMk@t$Wzik#p -6voukn`9W%$J4;8Z8=C{}7EF*nsZ==B6O;;CUk0eWryP~nuEKV@PA#JIO?hdIRGXLop`Mx-)XCxxM`u -FQfk8?CWvacZXN5}fx5B?a5`aS%76>$jVP~MjV!Wj6MAW0NB*kBY*Q3%T5Fav)IuM0vY(b{~mPJl}?P<#S2UeUFm5CN4d -WEF{|^a69A=T?xl-C@nDU>V3@ybX+{ae#6-4a#Z^v(8#bK(2;NK#BuQo%>lmj84G<&>ZZypnQUWA#e! -{RI^A5uoWXg+J#vSabgslky&eI3lG#fi}=>=Ja@UWvFNd_8d3$w -mkC5n$qavEc4G#q8!|RP|a|YnhiSf5l`HOdd*4W~0i?b0P6xD|FCWqPu)7^Beg5#*ytCA35xMAGu(J% -vt=V-4O(*fA5|^Ki-tTdrzPrZ_3}jC(w^K<#+DMx2hEA7cO=8>p`Fj>Ed3Dtey$q;fDsRaxK<&$ajWlp|>6*>0a?j_mo@1vy+7Mw!dW5`ZQ7wXGp?rv!=bnpZ@ksD5_TmNlQ=GsUQ$cD= -ja6XlC^sE*s8%QJ)bFEibb?o=xe>~Ka(5_0P8E@Bg)g56CI7g#LQ$w^J7gu9Tl&b&e`Y0R56YR|2U!9 -AZl#w8T<2JM^|gKLm^voTb8Dz13+4lUkA`JHf_AmbyS&0zLHZi>q}xomv!l -0OM~MfrOPi%t3P=>YXKrDp>?)#vW^7)+T&Qq}!z?DM%Ziu2u7;%lg=Mz_06Pj&E->k`DzdSfjoN||>xDD|>Mq3vyAv&_GXGR;)egGjG~Xi~;mR*#KY2z~J4!)JEc7^6d3$#UC=MqIXKMMCf}6Ko+}iH -`v>g;=m@af>YUTEXcIoX3}hf=2d$VHP}&gG+I5Km29Pg&CHcHLXKj7~UIR4H(=st -o*aCTM4r~aJ444BEBp^Rn3VynL4vqt1Z}u}!ASMFGFJV8Gt)_S@NuuF3hF>lAmS~?@t&A3Cl&$`HtfarDDKKb>q@I6d0nFaMlp#avkDcmO<}BZ-&&~A~n)0eXf#k2vDh -nhxVY6V|T3`79%x{DIB2cpv9Is}hFMH}H35xeNapwB?cAovsLUX<6g;O!YlXCAmJ -*w*8`Z8)37vbiy0UDpXxh+n%OADW=kcOOn6cx_UQP+mi6&Y=R!}bKYK=4Tt1+3O>YPt9n7g6hd?iu%R -kOR8y=S=w4mnQG>jPvS`OuJ=KJ -idY?si2lna-H&n+^ieO0t#4+WTB8^ox%}#(g+FtlAtj?{2O4la2~l?l(WW){B^H=j1>y>~7Yc)y3(Ku -Gr=xt0?o~kaBfVjxhpo!|M9B_5YIaclF1`Qr9+i -XtDlQQ$?hA!kA&vgnmT!>uLB3`ZwA+0yDkJExr1rSk0w#Aji##ksY^Wbhz-dBx_8FDUOB&|!`cH+*uA -7h+~1ru)A$FnY{MucmL`N38fiF8e8Fevy7!2pIsrGS5=tLtu_{{#71vSc9;39_2vuL757m6R+*1jc{`6 -OhV~z<&a?FQb6kq!^$!G4Z~BAX&kIz!+H#4D$pOC~^u^K#24o)GsGl3Qv$Mx$%Fr2p{E6XJrRQ -rChg)c9@z!Y{iZc{G+YKJQoaPJTFh2r -i72-7pWqPZEzwB(v*=g?L%nxfN#W;6656QawZu22oC}AH#W%?48QBr%@vG#u%CoP)(hJ_H7pK^ERr70w-N6-$HU -`pX1nVWD??iFEijs|m?!4<2MB(9fCw-^Yj6=;up9ymR$ne+d5{2Fvoa5vlgquqIQtWw#Bv3K@HoQ#*% -vw|X`V=TGaGpqQSR}9L1c*CV1h|-(QC31+xZ)9boHIf+0N5RGSjjWCn4;Oz#HdnJX$%*TK+{xT9&+1d -b5SFQ#YJaBVi63b-^?KW_i;~&+gi@cs|tWw#Ag>sVjLKPqgY;Jt39l6G^(hBfINVw~#>8PhJ;>t$#VR -_EoF0{pIo+G_$fPO*iqQ=AJq>Xq7&!-73rjIS(&}E2Zpk310^X;twyCj7UA-h$ADUn{5)j&=Sq;h04c -!T)`eHyfyW$bqIAv4rQ)!TDnqq!7CUeVz41heJIbTOd!5)U2g&$jvl`AjVKBK6D{-4l$}tJ&tSc -dYAN%ul*uy+kiGQPMW<{qDANs$Igui*<_gMJHLEl+7QNZ1v1cPBP1SSxIz)=iEmXMrYf^m|eXoC6Frg -y=t90y%30Bvv}U|isF32zY+MAr*E0;q{V$0+EF`P}awivYw?s|Is{E?_zcuhXINb)F!y7RNJykW2!6@ -Rv|MU%P6MpQ$o|10uXO(BNRdB_v<)ZGpi!2G#xrQc)O8c?RAaFi;YJ7FS?5xQ-f3fXEa8DG7@A%cU*Q -x~wc+u{c_;?$^#u!wQCf6y}#$s_gcaTV5NJ;SMT&sBYcoh56mT4u*fY$uFoTyq@cSryO-UeJaJfcb2a328QgS36Q15{{JDt5uF-V-tS|9q37-y^3hRWgL1PGyP!V21FV|OSF(-> -(J~`u9PGCiGD&3<)`nnxI2S$){ysvxh`n5SUslnt)`bA@p2_ -SCFxDXmmbWS)V|ktmM3&7;@6#cs)^I)a@s=*TQOT9-Nm1!D47&5Qb&f -K5-6v2q2;Fjc#c>S>+sO^OP0G03t#p^!B(Hzj-X@P+U43oowiZD8*_WFq$+l{eLC@f-;K#5;gf#pQ>T|0@k^~v>1ydIc%U9cZX3c6ZQC>;_dwDon?Go3Ph6Ut+)GY<}y -wp!^d=ua(m68Ci^EYGq{2vhpveXNtXKQ4OARwO&V#OF}*r+vuov#-B2$&KKj-nF)*)RdZPGl{$gOv8K -T_O|^5shEl7_8$}T<*Ts;Ou>&R5-%cadY@tslE8o#urwy!Z?*+9<`K3JYip`Fb+O;Tp>Ivv%r;*uhhf7*qGKp3(|tV2NT%rNAeOS$llAa*|!3xWhvsA9s_@BV@#_J`#Q?(hc -%=m9AV@D*&+>KxH!tqV|lYQ%RRUly!#>d%f7h>f1O4DUF{sGlENadmzZAF_JBcdBE}x;1Tfid$1K{Nj -!$JnJ=M9uDt)HqX5h=6$A|snPF=lhOeJZWGlZ{QgxlU0z(nPnoWc&}`_ajjNEqR#(Kfb`NqBs89LGtK -XLn%F$4CjDRYNNpS!o*fDyAs7okJS3h`Z-g+0)dN4I-ro)xH$V(J*Rv4@LMm6+2 -N&1un58w~4D9kfByhn2PTK89bYukv&(Uk^TxUH0mLKlY_|9ptCv3FTg_4!4t|3C+zW?<}&D=bLldK*D -izQR4o%+zY)kZ}3rzNjPPGK5OCU?D32CFncWb`0y^wi@?4#-A5K*Oj#t^f&(tw$p -5L9S)~C)EnLmA3pl^kk%g(R(FUstPuFTqP5R+*_Jh1YqoYVkv(!D~6`-SUmwcFrd9~tB2yyS%W#S=2= -&IqJYV|!aKbn#fEQ%v)Ry)3FXj^y||J3uPWa3!H6j_A{L5@*j#*c)`g>I?cG_t@oSMbr|IFf2EBmr5&|bMs6;QQ -iKJ_W6#Q5%XQ8+-@1%DF^z;;HhrcbNR9FKu>BhaQNKw(betH&b@0_QaUN0R*n;1L2 -q^+F1kxOh4562<~6s`W4Q>URlK!0!@)SDoML{n#Pq~j0DwHbqA0czaPT!S2Q?>W=N2wKuhDqn*vMq`+ZX3yx%Sgjg?|iZ^iCEAN -{-y~hJGV?tEy&B^0PXov){2HqhMaT-9Ur^HF^be`uH|XoA3bG`?Xv3eNP~6Lw|9#{P#UPK2GWP^C@9$Xr_50v1+tZcQ5%c$2OYghy!mlO+P<=gI9!TEjtBUndUTE1!xKIt6wSW;OPfh}r6p~)>Aedb1TkL3LRk>6R5H{!^c(tF2jXk=BEDGe*z30;+ -0UBh{+{xE7izsp#(#pN>;7M)s4uQxzCCvDe)l6Ty3}7x_6NBB|NO%~p!{!r&@UYu7f@XAFCtcHIts9v -TE~Mg@h+8vbTCP+j?L)WTSk5g)c~7uP+Vowb@E#Ru$f%P%u(5gZzBxUTwq)ezSeVrbM!y5pF#C<0J0_ -qz%-v|WdZ8}%a&zT%Rhh%Nhcfa~kV>ho*-)g?l|y2f8!B -J`_k{M98wzc0WNo-ySxS++ayyqxV>o71vQ5Bbw{tZ&of^1%3sC!~foka7vGz0D!7^zxEk7ye6DBKm47 -p_8uW^>(VR-4wC4LhLIS66!6Eln$)~3g(H-3%ds?TdBOPnrS!wbUr;4uv(Fo#dR?tl4dVt?;yhk -!}8<|w-y>_~D7hAn^szY1MyC-+@y&-Ts^wlLTjz0yx8Za`;}iP+fM!=>n%Sz{ -qpw2g{|}gnyELLR5~Y$Rt4|n;15+h^u0gzcNu`Tv1<#>%5y^~Ta3Ge56jxX7h@MKkNy4a1Qo=o^371? -OYv6+++?mK9j^T3odEsL7v`_NFVOFNVgBm-0{!|6)2?1pKP#0(tX_L}*RdB`r|w_`!?$IWy%KXi?Woo -gcDWpct`U3f9_@$Gj0hKNl>7ORgqe(FMhb01K|3fO=3yPPF)0`^Pgs+MaD0a8{<5&iQ5866nw*!r;Wl -R04&L(1ZB@A{^O@|H72wV(A`CpcUq_p}*S2u52vbRRLIp367GgS0byHsDBZBd6k3P0Z6e-)yDcMrgef -BR*$I@tDX0JJm3{?6|dfO>|W%bcG! -E?Mj+!p2?wI@bD1g5<^V>=xWjOzurbZ2m-&eb{c`jj+Hd%T(fLS=Z$^AmlwS>;UQgP)EY-~1Z}*YC-B -k>~&Q^1$Ce$qyxhzc|6Sqyt9?ibiRSCMlRENicna29571vWng*2BTr-Q>8nwa9mv|DI5?BY#kCoWI$t -vNAK$Og&QClr~;rrF*{n1#xp<`me3wgLHM7o1D3FLfCK@r?e8S0tkc<=npiUqKh>BatHOR^N>l_=5eN -=C>KJOBM3I87LC~w0uS&JGaXwx<^T6jVAB+Je6VqTF=*p9jbuh)UriBlI!>L~}6TSq@q}Y5-A}DY++* -*}QB+fmua@L~za96QAt|%6^Qtc0*=r_N;m7ss6Lh$C6^k1kD09DV|iAw4p6#~hEjpCDTwojN!F&bcM; -9Hg8Lx{d?Jc{_YLi8Wsyq{en^s8(9)g?l|y2f8!BJ|@mrWI>(Me_dZQaK@omi01Mf9)=iPOKxRm3JE*O5jQp6%K{_APco9A_g*lq?w1(w7jsS#F*$xTU7V<(U13{SN -ev`1<=tbu_Q6z%^_oveanC+3kg8Z1;NLRlcP>Sz_zm<|edIZdLLUxia$ZJP%I13)ojWcWpi+J`T4$dIzyEetWqu1(N -C#u<$bz{J?BN|p{o(!l7rTADVb@*0eTkwtfxswD;S}|$92ZVNOq8ae+zF&e5ins(XwXg*uR@XGR+L28D1I@q --kX-HLq8Pk3fm28dHUi_31ms?q*L%FW+YxV~+;w6&7zKcW$P_d{uVa5O^p`;#3-YbcuX9nQy)8+iW_{ -`jR|M{NJ{u5QhmK*bE=z!4sp1&Q?*p(Wp<2ET>yF8h^SFh?HS;%x#+^8(7Ibu&qYc}8!@@`5qjzQWL< -=#G&QaVO?D$bwl$_;jrNiQ+&g}Uk%!ELk$y=f?$<1XUY;1z2bzBl->=2h2^H9AFKaA#HU7<9+hjPE|22QD`ZhkSEX5&v57or?WWZw&~X-UEGL%U}UR~%7K(6d1$x9fzi%+RwZiiQY1 -7F4q4Ynr|ry$@aBZXkVMA`QsCE!)>Arn=SV~LTQ#N4v+iGU;cAVD7lg-KD5MH`$#<}>1zcjLc`sk>j- -4RyPK>)QdKvl|BQ6(qaB6BB+SpNti`f+$oTn6-8lK$}`N9qQ5Zl|Xg>&kaBC@rYQ|Pd8?Z<}^36_lM@ -n=RP_xEJ&TcXyk-W2JO;;3;r+Ek;8Pgx11Qj^XGO`^14o6*Vc%)BPJ| -rB6NHX391-2l%@W~kuh$_hu7}8^)e90o7j(2_?-kq&i0`{-t6lF09TOgOlV-dt9gx6n$2r=}ox1JP?s -UIe=ONuUg{2;|*Elp-vd4+Tn4{5h6P@TN*&Asz)y677Q}uAR&YP2qiI;Ss`aAi8<2_!WV|wpRf=-`y*@bU-ELDbsd%dA~3hKzeZYXl?mjn{a>w|JzaU+Bmb;YxCt2sMS}0^_$IOz$kuc(*?qo_M3$2=XK8Fhoa%m6~qQI_^%H3i3 -@we5r)4`Om(pa2z?%)4tRXw_vHZ>^-8=MeDPtKafaSAD}ZeSsqiB{dCzYm4#_LuX1rPpKD|RKW(dJ(u -EX&_Ys{N@>vtOgqy|5<$v6!=X-BsDXp>PSQ(A)tReJA}kxT+a9Pw!h3}E`Hi<4{f__Ty<*N^#rs`r+{ -S=&iPpc+}C(fAv!k?jRhd!^CbMlqTpo%v>!pCyhjJdFB#U#Tf;pyNqcQCvvOyTgFx&ED4?g59Ef~C{c!Mfcf$Q&v!G2fX6ucedqQim7cxbG_v3Iiy1b;a%VVqf(8G7Ivftw -}y}$-JwWX-+$-`Wu6RCDj_~LSRehHoBEXTz&#-A+ZV2_4$lEbd+oGgTH1X7+4p*=dYrcS3|r)Rxp?-B -bc%j`h6mEoxM8S%I0=0Z28y4achW%s~vPnrZrh#^tyMqTAs+rGB%k{I5hu2BIJ#ka)B_=WUNw!G&a|CMaa>=btGymMc8L}VlHxOPlJ)WY^$bM3k2Iw^L -dCeufW$vQJ9v2XP$AD;n_tm|!h6j|Q?@oq;!Y~QxuU7v0I4mxdoeRO|H;QjAB&Ib(s<`_SMAWc#TO;Z -$2Aqa*c1OqcLOv400FbG1U7=hCm@}){>K@@<9$l6<$0>ze~Ad>`!Sp>dzibDa#L=li~`$Y#Df&pe-0R -k8zD4lt07DKGYlM6=D0NoZ+yztfK{}2X6Hp?zy;5z=Rx|!8o9bH8|Ks}7FwqPVMq5!rSt1y^Gz-;0Ih -rqR*T6rwFmd>nA;Xulg23QS5N+g(}1bmeOt=7}ux|s_!7|{FXTY}GYkZIgn4;kMHX)4?5&PkhJSy>e2 -pS{FI;VmBn@Q6d={Q`yu|6Ki$8S5Oe(qEBWPf&1*uQAVlcUW-5LgU#FDD@qHAkSlNe46<~PJ=A*AN18t3u^c4y*0ap?Bssv@uQ76osOSgj>^VM69xSu+OMYr* -E-3RyjlA2|gpqI0z$4#JN+!MZ#YMK(7qaCGu&%FHYGd~Ht{D$-lgmaj6xJu!`uyuH6w1_x3^Pfvwp} -*o6-YwMcQ3iA;w-&ZHn?9E|O1Qt}H*o0vHAcujYS)2-pOaa0o14kXs$JdT` -dH5J5>g?rS2$J7OKCK%GN&9>Hkj$l3(memu5;50h0^|oID$f&E)!bx_R3EvI&w`MYo0uo{aqjj7oBG~ -BBmP`(<~-i_~w-%mtDn+V@^Y_W3P8S*PB$IY4WYrka^d{GJbGsJK8?A`z@-^^1V`-F)+JvT;sB(i*RmT76zxP2NE$P5RWvDJmRJPP9r$Uk)iujr -!NeEyclcLa6vt&Ka+#?H^4E{m}e>8 -Fy2Si^e=>&z?!u}>sizV}^S>_;SABokzK6p9_Oq4><-Ten-W&tvv>N0AW8S+nB=G%yqG<6#`^f$H42g -2$O3%_Z275p7wM}7Xj3<3)^S^1PJ&OOZ%-zIU^TAG}XPgWjDYuERmT(hB%Qvqk<$kxIHMOWjL4+K8^c -y@qtFIbp;%$`Xua)U0|{sqb#70K;g><0SGzkcN|`IWW@`a#^&Ppsf&=a+7lq`=eC{19EF;s}?h8yld7M)gh=m_13WhFaA2PwQL;O9Ax8^_%a)`(Fqw -%|Yuq~KdK1m9^PbslYn(MNzyx@f9?T`1rZ{ -?KVQP~*X$zK*BZ(x*$+linF1bPS6-e@kzGOpo@HZ}%UKC$HyJmx1`OV#tRb>t#t{%$fi$^^l}*++xsH -`!@S9|fij4;0#!A`S`?vLG@cV7O3mXnx3$c>O`mB(%%PquB?#fCun`CSTmMiRXSL7>LaoHdOpLgIuOJ -oJJM{WHa`sE7CDMkZ#14nd++k0?n>m@|%3N9OE%LdD}#w<94?)_8;aHJai4!{m%1 -zh`$6|P|U6#_?G_B4P6hk<>ua>T|1hp{}q?11gTr6uc*Be3BMEDu-?VR;&ySpcy+jXh=TOH*X!qgpGA -*dYty<>#JdVY2+#zkI-;ub!|_3AnNutIK)apaaADpPmC)H;z9JX?sT*ei;91t$gDKVEHn%bq?Y(pFS5 -%dpts5FF)>N9axJ|JG$QiSf68i{M1$QCofO|Efr*xBDb6_=6AG<;g -*!R6qTOVSoOsJ5}+_E#yfXly|!yfxE3x_KsxVPzxF;?FTga0W^j||8~utF)L#3oXVd;1z-yF0V+1?!w -aZb@5S~&v!R%+w|o;^9bx95kxP-f_lYP?%mQ=B0%>_9lFUB4r_03&HTNmPFA+2M!prGr?0OEWSm35lB -vXMr=hsKwm8|>C^6-amOA$xd=kakTur~C;xOwykXak(^oqfKM$@7+$uNhoZOJ~RU&F6j{6hNZ+7Bxzh!e~N3)S@i0a#Vue%|(*{KqFPe){nC%J -qV35}huw@a5$y-=YrLT2c`oin48Jr6&OaTqd%w+$eg%s>>*z7~ePL&O_r$hXhC>ZY*j -q6+@irzxnQPcF9a)+W|BTD3Y0txEeB|hl!Z)M_=LHe?spE`fmuEL-WQN~>p6{sQiNq^3{celf*d3m;48pYE(E)dS7w3JfD< -f%|H2!hm9>B@9tCoCpz06@GCh2?yvN~HLXv_hIAm=!yv_bb)t;gqOgZ^n=% -Bq!7ELju!&>-`!bq!7k5n|RYn!k@}?63^uZ|Jd%?|+W8p?@HwSAwIwNBUsaWw*(4s_PHb{cMO0W$olk -A{8m;)+ta#Zl4nV79PVKolF@)19~`*65` -9;B@2Qsf?Qbt~23BbYd78V8(!7F;O>7ZaVK7I?fwkik7G7-RI_U%D2)O_;A`#b7zK -$&qR5=)=xr6QUpUB9eaisB&(DfJ=TLw4p0x9&A^B3196A8e0h3t^|_-J$*=A4a7efC5tU6&_C(vDY ->mzDfq6Zou(vsIhPpKe=rqZ+=r6HPCBG5^J;&+hTu1!r#Bc9UI6OZ$j(_;e(vAg?gyh+GQJ(zaBz00< -n{P=k&>{CvAF>iiemQ>w@9iXd7jf`O+b54tNcX>V#~=9k>z)4O)=qz^Phac7@!;D%c>_i65K --b!I6HB$>#nTZiedJx~b%h+tC~7`q{>-6;`+NwH;5prPB^iERN;3$_Oaef2!9<`^hu56mI=x1%zN!=qf$@#LZ{PSf%;5z;@m$7P9{xg>Wt=e=!`DYx+ -A3UicxYFHr2hoM{fTl&mB*NK??K=fpqU*KW!XavxQo}VjwPuy8<0I<1PWGA+uu}hYk;v#M=c(pAWa`` -FmAWsZ$laF6A!_=oqE2qRSzzAU${9J0WV3Z0w&ymwJo9Ziu&3aKpxKOQ2W0Db$dS@qJ%W% -cs`C;&S1-O@AShr52N~Cw@g)3{czds76*U*F{(`!&5Hl*tvNg*TR#*dJmWrAv@i`H50&Na1--+NSmr4 -n0YS3FSdgmAbE?K}E8Imt~3H`fR<@2?#aq{o2WoCsr=H?p5k`yGvd*@)ZD%r{cZKZCwzlz1#}%tXxrJ -3C%kcN}xN%1}QBXjt90Kek}`RDL!NSQS2Bo`Z~OR-CW>4jt5PddAxPJ#{7W0xJP*TySys3$@6X)-Y>d -zFM;Uhf`O?)IP*55e#_ecyuQ2j~pD+T!y15MP8aFqc*3k9iND%F%&_v;iJ1j{XQo+Rw*XS++qvF)q$X~jEqc@J>1kZqV|je?H$>l4@WZ -&`twKM1YMbras)0{1Q$B~;sr*Jb)DT(h^2#tr<$}Xq8ohM)z7zFFTa;1$s_POzs=GqiImpg*^D{>Wq>EYn3<#>J8EqU|sUH%{P$(#bZJMj11G&HH`*1wopCb^hN= -B1pv_i=m~*%G6Zu-7{0vyRkM+`oFGntkITX<6J}M6tYSpq;l=MA6GIS3Ur0`S5I4U=!JC!<`VbR*xARs;{i93Ao1ORgY1y}}sF#u$-3FwNdFAD9I+eaY##{N)5xafEuT({4`rz^XJvJeb%jMN2c$JwD3U(_Nujga=L$j|pj+>Jpr7%mq-I;S|>zC&wxqzrj}U$%c{R$&@LYRDWWX8w4a*$ -Z^G-S%dFb2zItMJoOoH6ip#e2?;SrzhRiDdHq~v^zUMc|rU%2ci|k@ -LqD<|71$KI(Rc>41VNw7H>6Af!@u4Vr4Lw$1`Aehl`R72sx`m<|zJ2tEuB1C3lB2cm6830=>JIo~Yqk -UU!yJ`{{n8w827v-0Q&B3`aY6Dk*a>zYQhdqufb7B**=H*##R}E1}bULBaIMC}n5BW+4YN+&FH=xY)&@T1Gl9KsgWeYpWR}%^k{%#ciB3ChnC-{5 -(FFNQH&TSSq-DgNqooqiDW5J&;Xw_F_IG8$P_f!m`|Tw;}Vh-5&01c)C^NDZWG|cZSOobUGj>&DtAcl -;!wAv#QfM?A1UqG{XCrh(>woz!ZaAvSXOEJf3Wk5y^Z(~}_5p{#UH2y)1i0C@-GpBoa=(Q3rSpXmRPONfD_8vzG8vn1u|3k7cjX{%76r$0XK3Cq)b5ybnOuidVQy$Ops -hLa(S_U|WHD#u9HtnaeUa&-&z#=jLZ?mCbuCBC-~-9-iCyK(fOiY3CLDy~=Q_G -<(1&@R85T6Z!iRMVy#Mc-V-x&~aapHtNVx$nKoQ~=^gTwJt?$GB?NH-D#$l$v --nMm_-UE8!#{QCw$5NS40@jqQw8oO1hV(^(Hy(j+{*I6un466hhq3pNPMqY7>3;UxC3N=C#JvbIrZ)~ -%72B$Z$(ep3dZ~j#+k_CwtJ+mSXc-;;?rm^V=(D7XbexZOQSkG4PqXY={?`EGGtGF$h&%{_{4qEU4c1 -qx00R*sE*qlJxjgy;BN5PD=nKtMJZ1;X771RY2-bnqLy0J&K!`ZWXr2ug)= -_i!{=AMbxj -_#XtjL(SEpWq(J@Bz6&E2_p5~#x9$Thka`Je&qxFU -9_P3EP%t}l`qEU~1N*rO1OhUe9{eum)4y^0~*JN#U*4p4&o9LX}9uzlnxOm#;C7qDMB -UoBVjZGQX03S0`2JEyr^n@0a!jAtcI!5!kPGL+zaphjcsgP=6VX=KZBk=T5EnB0R$Ot{r0nP73JzZwt -KZU3$=+x6ZrHiR0T|a+_u{?!G!}ofVK>Z+=ucOfWzA9J>EU3m#Yp?_J-b|1VMN5pcMD?7t>$HOm -@wHD=b&m`CGa>Dc+%2Ck;;(F3mp?QrR&6@llr8C -cAMYn4;SN`esV~x~-Op_kw!`)Alb0Gg4*tz+k3!KbG0D_$9%f3@ZN95UhWpdyLB}WLh@PB{;K$_^)lH -t4rR4G2(=5{q)e+jC`>QKkUdxVt>RlPrH)}U5Giw?=&1${Z5PRdJvwJ^<&$zLBwlasFCQdwGRQ|zlq3 -o7oWQmcR=g{Bn!ZnaEhHDXYkzsi5q{WB=*L@H^bS*9$@u;7l;iIf?Ji5lnoos8Z+&Z=v59Y)h=3MsH-GP2lG;k1V&s=WPz8~@peOJ!P=lRB;A -nWaYYw?U?-SYnFEQ%c!iZ(e3mJroVp`v%lSo>Ub9bcv$pS=jT~B?zvs4$;fT_Jrk7H9-wdQGyj<%DhbyYA_=o!M|4#*CDG+8=!5?xlH%b489sI|CuY>77{x$lKf9?L`U;p^qANIfh+vDH=t@#66 -B0Xn$T@_RE2UpZZ(*)ky3_BjlYJI8AV45QYJ)av|0uAYRzXpsTaJMrpgyqtdkgNPn9GFXMw -*eYTG7a06D+TMYAm+d!SAWr<+v5;r&?sK?mLzv?qNVv{CI}OMqO5QZ-sLxBE~J9BW5 --$@@oeu4_fy-=I*c`v~h!VG8;!xk_4ojFm#1hkm=lSMU9n -l889?A6%rt7}jPrPvgsp|9a>}D?NP0*WOuFoTEt!UC0g7EOqx0~i(2m8ZhP29NyG4kVqb4|DV*lA|d1 -_}Wi;}2F5EVRUyYBD@|pyW3n_e^&=w_1b4`Hb`qJKNaP2nAl$Vp&1

    ^ME8S4Z9H&)n*NrY+{|q14hl}l>@RmbZq|9 -FN7%gG;pNh}sF8Oq<}9bb?-v?#h|hZj+`Pj=X+G`=wA_@@7C7nS4j&P(i_bd98}7nohim$$sUPceBK8 -Y?;ouM*UqMr?&k6M7gJPYCdHGi0fg0z58o-e}%k4SK`GE@#Ltvx3KDOU(KJ<3?cXP6GI;38%uTGk3v` -bF;&?A_hBedc{>~F}fp~-kd90a8;z@Ih2Jsnap!YTZo+^ElE#%tqyqeCFX`0yr`Z!>B&{Qmb@>a -BI%h*oVWBT2``fDp_2+3l+}>k$Ziee9v%({Htwbub9m&@CKtT3aGLXTbjXphZLOUk^b(SX!G=cYyO=x -O*?`i0orZ@7S8lp_qY*8V?wcUYtMusY^Gsvz`;flERATPtG?MIcT|h-nllhquA(nL)I^yt6gfXlyS*6PUg -hSHC^nxKrq-q?r*E|SZosH>b317AClh^6H?y4jK2!dBc*-ps1zq9u{c~G>1WjLU^;`4hkOt|uB@{?u&@E_zd{CSmuj-5lOj=PSkR?*9x5qCm4wsl=Sp~Rht%{8_0#ZmS -1crtPy4o?*s{|wlJp*G5OtCEsV{o)zH9|^P$HWLsqM>WbafyR~8y5_$mZ^0H5a>B!5Xt}}X9kp2gBd# -p$VSitryQZ-%wh~_T=#)*uGxA-v^I0+ICp}V$5R -{iQk%6h8fAwKKk#5D?vdooEzqxlzJ25RLMwpqo*lo>v$4QpYtTw}Fjm>6_<7l2&!0>dor?b9NY|f^WO -`a}IN7W@msy(?OtauY8s6%U1%b2p_*@T{vpy@L-mSj{Wo=LNJIfE~r`EYjM|1G`!t~o_r0-1Em39!8dFj<98+l5L&MXXFOUKbC>Vi2p>_7_-HOV9Rak^EGEh}}qne|uO1e?5-@WMDbKh(rLCsCdKFE*d -S&d(>p2`W?KZe*ILn4)BcB3~I=X(%TzJ$4B^u$yoYkj{mLZl52>J#FkUMCPb_3+SB_QQ39(vERFqoRL -6Q@>8+VZPpBvr^A{_UQQb10OGyeB>xe?2Y_;0tX6F|8M4LWwX9i*zgfn -IY``GU}>f~>()~E*i$*7>i1#dG#$*ytarCaeS85C^G^ef#;xpX+?JYi>-f}L6pLlI-Mb(H-2mqAA4q# -wEXZ_@$Bx&>hW|KqrD-yiC)68#@RkRVWkA`ylG<{&VN5 -Y*QeS`qN9MAv=-Q3%A0_}a9GUP*OG0jG~30Sq5Ter0D#M{8+w08#+}$3kG#vHXvNt^x(7)O=zCUUTzF-*(&UewzF#kQ=uXL@{ -1Z7JvP>zUU&t}}H#w#PA>YZ#aJNOX-QbNSNOr)v*T>AxPufAltZ6FeSOnoFD$tx -K&%}Y2_(;90H*r(o9n>FtsmU@S!_-G%Sof%FYu{p?YPN~HO-2#y_)PlSc^jvX=XKt9G1vA~v2GJXC?&tJ^E@J9k~$1uqN$sZk8zb+wW1yDkX6}*nckr>(rt}}4{kS2HmV^VEX0v^8^{r)=|a?b^o;KQzO|o5jo|IH-0o{|@ -AMNo^1)Hj?Y@1UdUBFuvLNUtI@F>uKW1WvHkE>Q_IbybVxuE|`@Cz1G!C&MLfUH>k#gf7nLFq0cuzYB -ntrP!?PZ!{kaH7%gAM4=2F&@TTgByhJtE%xVmfjx%w2ukEBETDtJ$&k2+?Gu>veY`}e$XfV6e1BcB{|#So6-f_9|;4G^Ee{{ul)EZo-c2t<^JE(L&>evb(GLWB!GY=+bH6 ->m_)NV!A>UjeB`B!f%eaX(1NWEYLf_m5o1Irt8&2wzmTbI<7|sqkHL1(Pa0`xD6q(EE3iW;SMC*r;y*3tZLPF+`QqF1H*4u1H;%vAF7 -1@a1)$_fR^|F>Q*+NB1xY@5)b=b1hf_x_&J4MChI9KKlsqtoa)HNMfm5bPo-T_Xq8`1IGN=6HD?9qG0 -S-4*9QN_&qi=802AkX$~^5x9~L3$`J5=r<#SI}=7lNt_QSq -omR~X_a&u8-9HNr)REcyjcBf2@jaCUtttAZJ3tpqw-ef1Bbk07EanRgp>xd)e(;mjAkfwEI+gSMQHMc(JJBke49Pap78aO3~N2=%cK -}T%7=R`rd-^%&nNC$8!So%?YLi)uiUA&&m>6y;-ek&d6D~_dahNaQ`JX6Zvy&L -^92&>%l?h(Qjg6)IjA0AG%Dcy(S=caVaJT!N65v7CL*^)OJw7Z2dPK#EGG$eVV=UQsk%Vic>Ynl*Jb<(5wbENgQYelJcbP%-p%wh@UkPoN;dcHe2(>Pl5KR5#h*JlM3-*@9B5VG7`(Q`{Iwk($5aVVU8TxA!#%EgTm6IV#Rkr&J#;9~^hQ -3%M9b90(V85s>X3PF&Eh-CD8@aT9dXV{Mf=Tml-Li!u`tI$7 -NIto)>LNWv}tq+e=Ug-PqM0dX+gMjNn|R>DM6~dI -zbwZW*jKSVIy}B_>`p0oj8yRw2zT|KWS0#h%zSxuyo^@t89%+JlelFrxftuNeA6#B-#C}>I$3)p>HI1 -A8lSyr4LZCZG~JIoO?^!4H7%m_BISR(a_Q+^bzb!dgP* -^xwmmNEBRF9|-^b|y?1wyTZe%vv9&T6_fx8f!t>?T-#(e)RtX`c@PIFAPU~ZvgnV@Y}NT&qw@xKOdheh;s)!;*gGX3cjpxCi`kO{5{eHJmc}Vd9({jwG36C3tVe&OC(*36ytQ#9(E*FKdof@5!#pN|`A6w7=admT7rMIu -^OU#ERJY!9$YVqDkW-Ga)=)j^VV3@rdSE)68ae7_rygLQ3h*v*@6`HufdRdsTmu&BiV7vJYoE^rGwx< -%5laFtd))4RKS%E4DoR~)hbTQV%7OUWK6BUWaya*?ejo$9=*esKUB@wO0d -ip!`73sYm5fVb)rp!V&gm!cy#u6+l3H0uy++EO3q#dH0{~#UAbu6V{n1%w;q#o5#m+1Ikx9>+3uE$o>S-(hKb`u>lgKK}a_@vnbY&x*tj`x=W -;2nVr1oIBc?qF=$D|b3FksyBQ=2c<3-c)OqGns=Y+UKggHLK^oOiXmNAcT2&4{KM#lPIVVISOrHtCW- -r0Xkrv`y0XfIzUfTm4>M@tJ4*#VtN?<)`I;{Oyk~Hvg%hk@>Q6Fq!PAl3@yTIYJP -?0G1&-{#qnPrE7N*zM1g2YIFloHNt&L1=ybR+7PKU( -c)xUvy#kxj5f$cEjaF)Dh}t7RsOt0eFTd@A;DzgCMkVdKp{@Ri2=I%`lk1u57qBYn17V&+)ylwMN)=J(v`5L -C{R4`ZPe7GlvO$ZY22T(cIfwagWi$-KF-U_eKX$f5%;hqH!(4xweYmws8CAS6#_@f*5d;2XqJ}d?szWEq7L -0uJt2wx()H*;a9+X7Tcb8DCK{lfK}6;5M4m3FyycHc#js*|#9m>DnMv2wMsu4q+E)eBJ*&ks-UjIfdG -Q-YSjM^{?;-OfmT_AOPw7(7s7wqA0+KcyWr>hzgw4gftS>;f=rx{mu2}?_>wlA^<+TQmiQ -7uunU|g)>TLojHDN^`zQKIFZx~@55vQ~?ih3Fjb0bH6;wI%rY7hUE~Z`{P)Z -71(W4ljo*Y=enV=rao-z=WX~|DEKt^>(dbGjUn8Z*HD)j!pkuDepzr^emZ!0p#BM=&|5bSp}t$Pj~T+ -FyP~lqY?}jjFR9?hj&ns9QnWoCp4a6og#=xEoX2Q`&*XOCyAAKZHihbM`n_`A@`js*$n!>@wBgxVR{X-vs5!;Uojqff9 -c=6p;*jf(n&SODNoi3H`$!6I`%Zj_Er4=P5g)SDfIxy}=$$H9kO(Yj^GOgDSCU -(<41h+N_^aN?3U2Ul$5V$@R?~=J+U@;4%HOE9&3=kbsE$+xkiytp$nijTPuWe*WZdf9$T0KZ3W-=Oq5 -)xj@*pt*1{M>;H@6e?aX&b?l#OC21INJc?u(lqN_VLokNMVVXc;hM@?WCJ5$BU=9h8p#wT;66l4HwIu -^VgLzpB201r$^`4G#&;bel+HxAkfJQor*Hyy5C)DZ=9Rb-9@z!i11QWhmKV#lEI5sjpd>Sj!E1bNa7eETt{4HNF~)xj%-gk9V -eq|HG8msV*G!4qb4-ye!%=pW=g(l(y)}Jkgg-ZE|JjQ&u0!ObkiULjCVVs;7W=TGgtYHn5O78O)5<`2 -D}|VnUA`3^-fExc5P<#*sCQ5Tv`~Wi;ZM_BsQ3ETph@1}!qaX_>|YmMLTlCKDmahkcfl3D>~L5MEx#E -XXxI`S450Ut?okC?lbcVEHBMs@LuBQFbIDb+0}y+nH|0-06WaUb!L*P_l6Z)-N&xDAwdL0zN*iQsk?smxKjM -O&_Nyea7?QN}9GE-6`(8$8d*Hskt|k`+)I^oe#xvdxG}O*j-q&c4}{vCYx<7mPtQiaiqYz^TJ`8?SZGB4{L$Z{!(VKmxVh -PjxB|2>11RNkkF{lxIS&qSh6`*73a?AWpC=}es_&~{kbRGw(cJqb);^ZD(<%2eY}^QY%-Y}r%S{IzBFFCH{bZs!r4_V!D#KlWDQ6j}LU-g|&chHJR=|XG8emJsYxwL;YwSkKUD|lD1%467QNMeW -CrvJ!kdpwuzsCGBHbJC*Mu-EIx9TJ~@sN)3vTU95lNmN3*mz^7gFnL+N(E4j7u}5t9<-^|Yr3BB||MR -g2^(D)=*1!X?C|0zr;zp)z%-dSOgAUeHZ0>hfhUIJ~~AUWi36C*JuZrl0)teF@W1h2l)@d=^JPh!z?i -n6j8WBmrViIa9h)UUMb4_hyT -Y4D0$Wb|_EDUb?^f=SY?;=;b=y`LnQ#L2bW)2Vtz&yA5Np-T8hGcvOPATUFqZ_!x;9r;*Jy%MTC968p -a#7W8>A^vO41{P2ZafDri2y?y#$K4UQT$4?*p)ezu+_pv_U{vRCYM{uW*1;8;BCMlFg2^@jp4XnM05U{;{~+A?;5R?reXTD^cU(Bm*#jrM>&EA{s<{ -5wVi1y+iKV1fVN~#6Z2^eIK5gO!nm#Bk6R-Xn6;5%@pO~L>4D%@JgHpup3;25AZ -D+Nsxfxe3dc?5z1+a_<;UE19sWEUd}P ->+iwm&hf-Wn^YX#IU#g_tR!2Kch_VH|@))z~!#@FRdx0&!0h@$<9F1E-`MSTH_rwoP8L}0>%5U_3)C6 -~UZ`r=```Hkd*ucpqZFULWSw&ppr%Z$A&Z4Jr-`YKD;H;q^Px}Ye*Fl`Rp -7G3EtSSAA8||gagTn)ZD1Gyt&Zhy@yVnuL!!uDWPs0e34>98r{bc>@``ZDSn=AQqO5&-ZMw4)CEXNxu -!XoJOs~WKD440l?xbZn+}j6Rt<16&x7Y1`ilhx9V%rwGC-`GWH+QtnvwF@Mxow_03EhI*b3@(dNaNZ< -RS-lqf-Xh-;Yp91dqL3KP8n~qwV4|4z3$~F4G#2`2@5^`9!7--x&;Z;njL}bqTL%Pw_EG)$ZVOl0LcM-I1#!W!$p0!@suM%hjR$d02 -L$*?N7PBbPZu?KwNiqdO~zZsW*xgw8&uJ|3{6x~9HIo>u}bBJmYpjuSDlbyAk3=>Ah?rN15!u`dSRpU -KfOTE2=X>HZLkSJ5p?w%>@@|6&mMKXWd>2Z^6+b5UxE6M=%4AW0fQ8D{PIw}1nQQVaK6a092PFDc+Mv -LYdHIbFtAIsk6p3vZ<1pI022T*3alJ -2mlOTATfGV>;HZ8>i3=;8dpKyxhYdHqe>iLtbMq?-`1q_i{XJ5A>(aL%MGAevPJnn8UEbHx#EHS#zeV -V>{0oQy2(t|G+5a@ed?=Lv8pOMbbv(WfUhyiB}eS?_qef~=p$Yn*!KNReX@>rpq;~h9+r#k{qPtg|$X(dBaRX&hw|U9alO`{1Q -`%)9>_$V-3*xAce%p-q)2X~&US5f^{SiKB_okZ${&+Zua!lx38oKy%5m8D}TRTD8ZN)xk&LMlzV95(Kl&!2lOC;BznP&9)VCp4}VdgP#;jEn$`4L{a111;U1kwW@O%~XD`emYGh4c9s$tYuJyY{(GbUvBPjvl=*M&st<*_Wg)4+sv -fJjt?VTGs8xV88<4=Egx8WR-6nu@=?oTAm-Be6ff=0y8%u6>(;g1&2`1liqLN}v8TxA#E!cyzfpzLIW -t2{C*b1gZz8s{=LWT)rKse%_w}<-Jf_k126;Jp+XxGHOT05(^|DtU&+Ovgj>3Z2;@vl;HXy>oF}P`C< -y6q|-InhY9R3{`hTd_w@#g1M=3^?gx0@2*Cwy13+Z6SppVG?O*Z$K73!yvr+OV~CiG70MgzM8FY6n4Z -Y`9ldG~7%{$)>`G`JQ>MKR@?lx@WOQAQyVCNva3k`A+C9&p2WHL!aHwTyfwyXQyYYKa66?j_|_jiy0a -IWgE`jwVBJI9nQkDj*cl3^-xEbT0kqi8+Z3t(k58OY4TaZSe!_rv4mhK-^GP8n%3{hogKHy9KfjMSZJ -w)azVw=-jPkUoE{IV<>@AfcjIx#u+g*Lm~-AA3qDpJEO)a{ojHcoV|qLvelu7U>CGsGXF60kGET#T(u5-DiQ0S1f`gni&^^H~aS-*TF&8{J1xhH20UDz0)tigF+}hWNmZs`8D^1B>y(@Yyh -Vyy$?>YUZ-J;y|eg0H9KMD;Q}Ru%g>Q#{b}U+4^qk?qYQmXDP#X8r3^C4(0?VR3^K}4`W~{qrIaz<7R -R^h@<&Q}Wi#T6a6hM%zYc8qmnmg{{LmVf{_B+T2QB)yDP=&HAid>VEY|9ysmcD8jwzsrWab!K-Uqm(A-BFYsHn0^* -o*<5U{=A%BwCuI;3UxcJi}*Ps8AP~*Ds90RQ(0Hsbz9HpNk;vfG%NI3p7nbZI5#`EiRotTwSB@un2p> -TQ*T7~?CR`YhIsz%wXaVWSTtsJ+2D2A^1Lv*^9iZ-!y~+?<){#fZ9dD{sz%=5cI|m7BdwBc=xI>(Eg2 -_8+vkrdkJD694!JrWui;SN%$6B?0T)g-FKvpOzKd>f+$0mUxUVvHQuhyhqN2-P4zWa{NWkD3pM|XZS5U}{^7R1!y!<{B -q*H1aG1sk8evuxA~A|UNgRhUghEOBb1PT?ImyfV2>}5ho`6~LYqUtmuXN>WPCbGH4HC#QelBpuUdq!o -V#NrcHCn-fLP5vsy4d<}NPyJjdMEZa$DRUa4-xgsZ~jtugaN!-@dJ4!QP(BbNESF#rC?*}%btOJrAU( -u2#Qve0_9KwXq(mumwFjO(60et3=CQ=qL-$21u)>E_4l!D4+O5H{B@>1*N6cxufduq2+dH5MI$LcX_3 -CfwuAjiY`d`T2Nqp{>gQ^M33(qO+*dDi4HBoo1D&T1dcCce$oFgUF*uvpdZ}!5`#v7ur9zANZP@mD9` -yFaFOl;FKuR#g7p{1})~2;vsme=(G(`18eA}5S&Ws0rb9OhXuRt#Ka<=+MX0$&ByuXNdnNQ-~Go*9Qd -%$~iYj+Oa!y`cP_i%Jlz8Dn%@Rq)LL*FNnK&nVw6D3)<)K15e#XpeV`lfa){rsDF2RrDm(C)oW`2}{! -w`X_z@Vx&2cKB+1{~LC=pT@w@XF>3UirBdkxx=BmAd?quks>U4y+msMSS6`Zs|S*}=D|^2B#7zVs)*L -0DjT{Xx`m4avz?xIF?m+Pv+OEMMYolq+>ZHiQz83_SF*h)pY+SF$Xic=6qM96iS|@U65%s98Y -?h5dUI2+5hCk(l(N+~%&TTVAq-|o&=8$Il3mCbZxXZUBUYbNFVs6(R2gQp!(jN0deGE(F1Y3h|5_ -Now-CmU*ZjLYg7%F9oucenA1dQ%|_gg3`#cN#sq>AdjHUdyl6^z-_u9NyJY&`$PSE$*VEZm;n4lp7U2hVG|Pqii|6tOcgpPqXAbY+Abi)XDu`t@_+Z|~Kno`q6OlOuXs>fws&56^7RDc$Uz8>mfI -R|EC_JLcwHGdvPxrXZAweasS5?lxE6B4KPd(=dxgP1ubc#=izd0@tJ}bMOaC>Y`w>AeO5)QiW_-{a-DA6(T8<@oQ0<0&<(^*%7`RGLio#w*!wZx1ZVud*$z+nIV^C}B?eA*>39|% -r?A|dMmbmm=;ncjhR3Li30+O&n8m1jINYof -p?~WKm=_v1KB2HABbIm`(()b6N{||ET=GTP%aq6S%CH|d=75^6mhWamatN#V}`4iFl7km7cb&(i?V_+ -B!P9p?Hz$8f$7=kb~LBPn$!blv3iBEmBun@S^uhc4iCEP*uAHD|GWUs7$2G|es3Z_vo8uU}vwLX%(Jn -mNtL}$SA3PS*4qZ80s#3X=Y#VIpG5z2 -`C0V;4;U99T+qK^Tx+!a!PKCC#JC)1G{!HCYni^KZJZFjEWp@h4V@9co*w@EO?BZf$OHt~#EZWyA1za -07##A(z`hKHTk=u2Mf`0joI`l!+ZzbpKj^>m27dbz{?!`@-aqKS@&U=a@Zz|#ZRM-syy29avn}7lw)BZ+q&IBXnX1fB -lx*SS&h$$tR7^M)?t}J)jwFD>pI;RIZNv^1ERoFIqM5jb-VfOkP4}QG!}>=}Rh>obN<6_=($x}?jDYm -W_t}x0)n$j*Wxo)(y!NF#o+1Gc;rom}+)cBx*fyyU;S?K5!Jmxg5YPuBIxb$#R4f)nV!QW?JcJ~37laRLK-_v(T(zyw%3+#snLIy=6jMxWII)BQlh*UzJ(!EU@8Di!{{f`0P^R7Emy7mo{^ -Ngp10(r-(*N_ncj4_n|L^uc|Icpt&;R2FauDx_@3Za&jP`d2RF^RM&;P4hm-)2N{@;Fp>Hpd1e -uv(Fa^CL~?F5daVDcM6BA-gRVglsOSIo;YkTOqRb4ZgnhiepQs8*O{QqOI%} -H)v$k(;<)680j2Yh+?AMrn4Y$Q<@XZQ*WvL&j$1;)#oV3Rxx?P*lld6$AG%8uh;91l7KI_kk_-`ZDwV7Z*-Q6_X*7jfW~mQg#0MtbbR7hrY` -{@MaoQgcg~$aL?@WN^m`x%B_@3A28Pn=br0@_dd?|Q>p4o$q0=NT_rlqq^=tl?o{v@dYhQtpoanf5N2 -WPZVlYPd_>mLr#o(yhrjq_M8yX=QqA$4Gz-KAY`GQj@p-0-9eTP^oI@JKrV}1lcaKuz~CB$YMDDWZAqlsUic6}mS+3(GhVjJ3l_MIiBo%-?Th(*e?IAKs -!bVEwPeheCAAK{=TI?`Y>W_ZW3N!cvo~)j+4Q81TuV$)-=3KYKgb$=LR& -T#;MqsVw<8Y}k`!+#*&6jb;0bc86DDv#{2;HFz*m75Xw$Rs0=2+;V!{sx6Q92mU&8)Z%PW9dekBfDrz -#3-SAIOBJK{3`W{9_*3@FxO0#2bbWJ~sm3|0IsyQ+HA!&~~hU<;=KL$=ExCpm@f%o@)h!?kdtd!b?d=3%=qu@Y -n~MtLLu6>0oJDl^R!)}z>nHSsv&H+v}MT9(GIm1E^Lq;?mY^fOBvq}UCP{)%;dv#I{JqyGWsO&SjT&4 -`aT@}r)<-~;4iINCN>k8)bDmHugGk5Vc7kVq|F-YuK!-cTaw;>lvsay&<_y*Us~uLum66L?=*HW`18^ -=|17nRK^h-^4OAtf*EosfWmyEjTq<$UQ@lpNI0|r*{=DG5LLg8)qoCZtAV9npQ$XJg2B}iO$0B~2Dy> -l~0LTbHck+sZkzY;00OgAKWu}3@CX17xNV7UawtCOH$L1usJkvW5cH}xT+6WQIxD}Nf&1PTjW_@jL^WD8=dbK|=Qr0Za2;h&8>klh)izrpPq3 -ia!4uUqZ7H{Q)BclHX51yBiB!CB2$I9+ddt?v<4;Nd^YwC1lJPWpU!p=LfEkoy@DKzkFo(+2WseFC#v -y&t4~b~R2)$;Ka+((VEFJqcE2a|QUz8Y6n<8eC3ARGZhR6dRqd-huw|S1$<&?V#_)+`y#jSItQ3TQgD -)nvvEwTXzEjU3uHHB($66;mr=$h|?{YpIuT?4k4=Axj~ftxDi=PZcb<~%ElxI&%(HhPxwNzTxZbM1L} -`Xc5%Af;7KaA#VlOrRU)chOAES8bv -Ty%=wj9>z7l7?)j1>IrH5_eHdpIZ9Me%(xYLvO{iLUbL4+Q9W-w=C^H`%;{0d6_l -v2lLn`2`qlSo-I%#qpYLK^HYk_wEdAi|7p*p73taeF`NXmFUN(>Zf45!y&%5#$nbo&zte9}5o}lySxP -#X`G>yK|A=!tDwzvfzn$Vo{!+MV`xyh2tlBQ)IRAC>70@iUY&L*F9~mF{@%WlZ_?q{-N?V+^?_t0Lk? -^*yES5jz&LwZ2Qa$=u?9_y{a6SARkj*8UlvjoHyK^N~lX4SFLxxSkI1beEBp(gi8`Q-H;i=%crLb%PS -WM`P@{FcG0`z#-#4^X)1LOw-Flc;7QrRczV?C;*%*oe}Ks6df~bV_C?(lG^bv=W-*Y^ZX3i>ca}jvP; -`nf)%J8v>kRQ$Iu^<6K6OKyOj8y2P}YSVlq_nb3OB|2>Tx%LlFC -*Rz6xnCYD>JX9;M<)Fg)6Qdk^;K#6d3L517k!>N3cbTH)t_SG-4G=Y;xP}5??Qj*s|JC4qYoAW`*x;#uG9M$)2xF6oml0-V4+k~5(Gt%;$LgG&HZj{?m1-PvsXtd?DcH;HI#kdB=^Cq}TNi(@UK -x_u*he1@1VPY55UN4<R2CYg8MXcIVyQRZp(#KcX%Q4#se_p6MP7pm}^;3o@w3*y8S3OnMxyQTL;EGbzh?+PdOx##%-lJv!sD -(Zqc=F?uc3B)QqM|D>|B@+jfbUhUFqV@Qy%Pf?JDpeax;SjkJ -40<|IhN|B&1Bk-Yl^GiFLW91Vq45?|DqQB5OuaG-R2k;@e4g|;S`sfJ9jlOw -U4kF9soiRmJx13 -a|pm&^mF_vwo-`(M^C7okwT8PSmTwEgG_2}p5_)8G<5 -2JBI%`6u>vgiy^UE3>%lE?IoLu08D5v)iNGi_kPTeK@DO?Xep$W^67<>{%nezhlAbnteMvr+m{@wtOG -TQCJ9WwL0M~r-iIJ<*qiNUn-e^xTQ4e)qtmPtgz7<3-xMD$QZ7`HNX368KG8@zoc`WWqHss0cotjrA<%4@i2A&@a8*Bp6SX@x?6O}mYk7uT%oTRb@6%PJ|E?Ayq`{6i -04+UL%4mqkmc#ZKQNy0a<=IVmmlZMUZF>?O_n`I1TGK!-gDMp?&?k|_1By3qDbKi --T=J4$jJE?3=D!b?yS{J!yR~0&khR{2d~|KPLSB7otF*29_6O+vy=S}w>6fFw#ZiV~5E{W?`ZGKY!X3JJ1fC1@@#}@EyRLtcco7ZgOO6)+v0~j^B)% -Xkvj4u-3dE|?bSUwKW1>=5VLQ9Uv)K+OIojpA6P}B~^3A8s2hRBf!yJtx_sf&xaGjpjHaT*TH3Sz4>o -VK8_prXv)D3ZBE0Q8dYCo7oNvdpIrea*$SiVKNjI8bJ8UbwUGp+M^wLmQQuphHh9L2%Qr@m`Yo8OodS -;~QGdwwRf&E?dz8Hajy5mjNU-NelZo6KZMHb<#OdyujgAQ~*I_$& -HWu26=pOX!d71(>CB!%v^%Hc8a_Q8`%QeRQIp6XInnfLP%GJBt4}*K(t4CtAyGJ?;q5YUTJZcUl_Gnw -Sx*&L~LFlx&-X(~HSa=eiVBEGif-UqHtCb_qhT}?&FfQp>q$ovJ!1r2T~=oj -$hnLp+U`;ClH<<~EhA}Cp0M5;Y+`T~IAA@w&GIlm=8EcvKah_loQZ2-G0)dE0bQvAPk)!VfFyQ}39Gau)OwIxdT;Dw|AcM_@{De -D?(KK4j>dXz2-(k$g+31cq;y04RC#kgw3jI@`d8_Af%r92-qmU79BqNK=;m;~n6@(CMRN_k#cR;$C02 -NDY&I>mGI744ky96s=)RwlZNJH(Dz4EAW$0Y8+i5ID!KbLaAD_NbJPu`KPQkoFpv%rX^O|gTXVvqm)m8(6ny-aRV%c}cjb4W>DBPm){qt;r#QmLR~kGo<)w~z_NB -HGvPKu0KioI*A&+S-Jr+0U;`DX5R|$z0q=yJ(j#+y-KQobS)MJywPOAfM24-P2*k7S-5;zKeSP29X0!JHkjfg+5t^mD6_?kfu1M;$Rj06 -R+k0-zzCwftam5QvCVU4pR^hI+(spi)c$g)?@A(enId}TpvfDgaS&tn)Ad}0zPGhpy00cLRO9oqw|%=-cglx0~PWx`)6; -s5#Mi*I#S4x|R3FE~?X&EwLMcHwdxK_Y8HhEXC4L^u7|Aj5?pjn}Y>1!af!QD&3}V^IqAMip)Q)yTbt)l -aNe#w&UNm5!XbZS4fB&rnI@T5M<0t$RQI$MYONL{Y_RZ)xQvv>x?HhI|mqn=a>ZQluuGp|mhYkygHR=&11UATeTR>03G -0!MW-;wE%Bg5QI^*&cyt$4SRo&9kq{9N(>Qt%g`&tPNv3)m2<$UW_;OTk>z+;4fP7DcrbXBzE>emC_z -fj%UP3pE?}a+=zWgZuRQ_R&WvAzx7Hw!e^GJ5>uwS=m!lF^GCx?pMn1yE?uh`(U><=ap}Vsd3rHYCVV -cb{9%^J;h1fIBC%-yOKq%2EO|{3W={Abie3aA{6dr+z?uwUR2h=(8s93Q(WNc=A7E*P>4CderF@1G{q -Ysjf~2rg?Qrk$*zUO!mfNDZN(j*77|a#PMY-I~Fm*-^d*||e9H -C5?rwy5TofQvPvg>gUyTInyDzJUoYNUA&77Dps?HCU;hbaW%ig`Z1n-rge=OMa4Y&`_tKYS&y{j-8ulOS`2T*Hk1GDpOZ@$)X9`HvLCA*!-PwBCrD2d=%}@|erZ~V7U>Ab|wK)2D -rg)7dWBgAh14{8V#>_CF%nRRUWu!na{-yyCx+w=^j_Fr!@$ -d%Avyk8OY}D?uMhK^7?_zuUqV8AjUUV8yt{r;Tfe%@3>ehqzgW%Kpz|&t4}yapgamZn&EP)h+dFkB|H -)|j8NRU>gn!E -46Dh<#=W%9EqT*6713F&zAG0Y3hk1-Ij9iL?P=c6m${JaqYLTD8mw%!-hb4xzihTxpg(i6KzmcqI+{2 -dckiV3aW4;hmycAbbIfBxiun-UW;=f{kh8}S&Ojs^4yLV@Z=7u_qC9pEOk^)_o70hEr@D0eH58ZOkvH -jwp$5)89ax`D@3#v?>#^X)>#OT8H@!L4waCj;4By22prDcIDvAl<(Ey`fg;;7DP1% -!fjHka>BU%ty4d)wk*Q~PVt86-SNW&Pwgd={KL27jedpr0Dx{(+bHTY_TG7e_9rwP)ffvmA57J)xrNG -TC{Jio}W9AGT>i$Z9An2~I2g?m0hnZxEKf&tp>!?V~WtEZ4G}V`KAp6WtzTGxJy{iF7Sa+QlWxLPC{O{=e$7!$rfJ1+G?8i8aZ;$)VrV~aPoWU3hjNhXWguy=rp7mNxzsapo;3Kj|0 -|fE%ElI%->y;e@G_M*aivFrZnZf|D*60F#MJgEyv}Ys@QmQ~{h5^%*6_XGeOwuD>Ds1`%7WA(=(t(Bd -OTMWeXpHB?_2q|JAvM}^3zUKmH#D{a(yIN{T&gPtcx6tgA$-NIdn?pZqOykn$c&u~gq!9s+x4 -AYOfzHN7FykY!f3zBNjw*%YIYF8pDs7k1eA5p<@QTdx`cVxEVT?u|n!UFK-ypKy -@XTRaSR27C(mW0OUdVqCY@J;zTJaogIK$CxM$>58o#kCYqj2WNXS*ly&a!dAVcJU!6;F}N8H`z+Rt;1 -UyI=(DKNPCDE6wBJq7!Em4~jpv@*&`2g72ai6pjX`?5VG6e^Q=2Bki}-C26~a(v~K;4#Qn}? -0XS8s}O2sC?@7n%iCO$9Rtmttx6$4W{&h9&ZcjhBH_Y`_iFDF0NJ0jAr8vU%E^J!L~Jrjq{DZi0C -BjC%EL8WBc#$@eW2Lf}jj>v-HlA=^aOsIN3k5x53n0XVk7PWE2FognkblNeArg*nRCs8?zP9+ImpKqn -7?O(}FbMiGW8V~mj*(3UUx|w^HJoi}G6ZEM)+mCt3eYoIO+gMR_j!F4>JvkhK9j^9%GCmC`iytK9kLF -bD=Wf?^$#M^$4r3^{7hWqw-Tb=`6*z&SqP(IwHgMBIvVR0Uh8B;p+wpw~l!uDojqf<)34jH-nNaF5UeeXLOsgym3YBg1?swD))+GuB`9^T^<2tRp$pK-K3%J1)^8(jpmHXfJ+ -=M5V)lu9F<-<1l^uDIn_O}Qyw`*LEqK=MZc{93EM0i)bYr9PCHH6@8vyDbO-A)b%jmu;&Qs--7V&*rd -i2f$a*KQlDd>tSou9SKe}Mk6-{L%SJ!1Mg`~+1u?8NY8{K61s -A&-iEzkLy3A=p&3-JmZC6>gZibmsB^4Q0=TM4#`1n6!&y2LPTo`xYBs?uC$q#JrKj1Q2kR4YL>{PXp+ -KS)Yv8ckicDSue0`%ykVbbdUG>&NMac#3$fj4@|!p{eM0L2RtYvx6F#zbE13Rf>h*ck?y%)-SW!sG)I -w^ut|fv8BS--|_Om?p@^{q8o8O)~PeLZFW&Agn7;Fo(Io -&JKvVKESpt?Y7L5!e!ipo)&huWEPp%07ocd=_S)#-7rE$59m6Ax(m$ct` -{Czhb59;E>huVL)#+e&qG>_&&2fygZ?-!QWYl{3V3|lE$sEZX-ui -UIY4$g2YWmMlo-A>uzlGI*|`oe -zP`R(ibotFpw)$9A6mk0gT>-(LT2PO7D7)_gQWX!wISqvfd4s;Ncy{Cg}UQJg}LEde -Kunnn$V-3DhyJ`*1jWQo_%7p+N1fxM1b_^Rn$?D2{F+b)xRseOqZ0IzJUpLAA6X?BP^94;fbVAdC4zE -M_-y8W`2nt8MFjVPDzgjIR_*WvyBg6vIpL`wIN-K4|S?o8JsvA>*3VH`!*CoV&*E*~fH4Zr$WMd~bs_vsjlkucFid@6)|KT&zn6YVa8liXhnxX!m@k3gHo`JhF)z-@k+e)!C*2Yi&jj%> -3vfsQhxI_amlM=btW$hT*`Z(2AMew-`%^;qbWgQGWICW?~4TDi$RqBc7*TOKo@6i8!qP>Hwa;TXw*RWbOF`c;vf<%Gd;mm0@vR6&&NE-!)0YUJa%}V`g?CEc --CCaB2C+#7qYXuJ?b|KPuf(jpd{&Y{AxIDbdUF;;WNdaycdRpFCsFF!nWJ6QgY2WMY_!!ZCg>6dpWE2 -nBDNKr6>aGX3<>MciO%2H*u$?@?vqy6OH9o7f(l+_Y*(mhC_C|XY+n5NyWxtnuDUaxW$Ad6+`H$M&%X -V`2|L2N1OF7A^kM6&;>iq``fN&mdF$BbF^HB3@hzB35UtL+tCi_aw0lN(GQ6E#1Srx(6ay+)HNDsU%h -DOhx5b)A+vVZ{Kk8~?}B^1N8$CADL;PqALrZueeHiUu^(-0|IGp)KD3`7@!j-L8Y3teLs#e}7>ZzUlm -rvZUQT+S^~5JH?>qcu&j%AP9~T1T&sR7GnTc0pI+lW=q3Gv5@ySbt_GZNg()ch)XRN5Wu8k9*)*K_CR -E(0~R7CVAO}yZp{4@O3?6Np|6_ekxBNQm~qA&Y;G6eX&?r=rhb)j`RkT`jjd(jjiJxu-i)&U5noP~c< -FHS(AFHIZ+?p*hTMSq*aKLa6E^`Z9GIQI32Q{BSm_di|QL4WLM3oBM|_*)8|pY*edPJ>@d?YAj-RXlL -OJ;W)$oh^O0`6_86bXP>u0U!|m!{F(!53Y}WQGZvXzKFPPf8*OyzTF3X+*xwEZn2~C;ryN`k<46tZ~7 -+X{?Q$0>0gxL(1-l{+m}#&m4>hQ{KI44Lmu>(n5U1|f%bn^)A7 -+&43_9f%Gcr(Xs?HR~|HG)c7{xP440~KbV{_s6Bw;_wyBFh20vQILA6p)NkjkU2HilO2a?;JT-mt7tF0V&E$dhQFCEJZD9XGuy-sr| -*UF#t7x+ezExw)KI5{-4J6{gJ^PMN2nMm0+CmfV#q?`fREsIP?zlhCQIrM&ol#uxM{w*3MJzw5(Nx1) -t^?vPeFRZ}6xrap!jhPq&8PycGj7i(~uaTr)<-)v@f85kmPAk(R-YtHVBa9%%{p}c3~Wv+)h(Q1=JQCXV9PJxgd_=87`yIiXZ6BEBhFL$#a^jutM|@eFV -x+J4ZA*pY_4r(N{2n4RE-~-3i=7|-p1Ti9LLFEk;mQpnMoD7tcgcoEV^Jy2=gT8pgBe2Kk)|$Vhb0+| -GgQZFmgZEc8JCVP#&)~Rjceei>bzc9>BHHIs6x9Sw66|=;fl6OU`J-zHCI-?*J<2B`b3k*`D_)Ha6Hb -loa)y?n=F#*IyYiAB9%KkJA&`1q8(v$X9OnIRF729lX~gmhXQ#5ll^|azVGhMB?*&1lJslwHwMLD>C) -8Uqq%1(n5Pn6|KL^W50*qp42Mbj$6Wq@VV#dW|G!=1I}0@!r%(z<843ksSTLIY)TNffUX!?R&_%QcXi -@ZMh=B?~Nd6h4pcoCNS<&==X1JV9otz1oL$yq*Jv?%jp|zCIsDZ&4&WsO$^A9*gW(r6y+qjAu -l&+r9CVn2f!8!x3(3F_Yb=?(MAhp;>kkPH>Hy4Zd?+wdN5M$06-`$ZUiX1Tpg{_Q!OuWrjRU7NFlYw; -GyU7PC0PR3-R=BQ6L2;oMGgIINuL(pM&}!2S_lY>OjnnpwN&Gpr@2I1}erZ`3uD~ -fAn6XyA2l~OC{JR-2KKiPi%i(#~t>NMvp~3Yv|78WS6_obM*rKqNj-mJx%KYpr -r|99rxF|KaxX9(V>dq3ZtXFAUIvZKwTlI?3G-Mxe;yjInTwE>~{}bG#?MoO^43nctUMYM7KsX`O(t0j -!DScS-*)mY8W~chb`lr@e{`|CFd@WfiQ>Bs`*i~Lnjirlh$_!_z};8q#bg+dQw}VP%`Ez32>WcGNdtT -=lKZ9SELuZD*r4)pE@n1r0V)%+U}|{*&?{u5wS(!0uFEM3_jJ732#kyL5K@@tDvMyY~RBRu~_TV?|L#0)HTR>-$&W9(5W>|EkxToyf`i8)I_(Za&^0_C%dP5 -ZR8VPx``nU>ASnC=S=$<^)r?y8UsHSU!F+?4kDn?jvT&1g_92b)v@W@SqBdo%718*vNOAh-^mu}}uy~ -k^q6j|}S<7X(?8ou<<$*q|A@cH8D=7!vlQ+5vO&d0Cej^|MHuw|YwS*y=ya_;@x<%Zn%;puj1$`BHx_ -N)bUBF?7F(B})f+Z;1GJtZ;{wtX=(;nMFozSCAX+RkWb-XvwHw5%VMh*7pSv{&iLa|e4i_H`#|`{r;xZ3J?W&9lx(UpT(FGkRkr$c93+T5RNO_V!YdwBM&eM#IQ5%+D-TZY49oR^>02dKFWqO)q*|GqN}q1Q4>(*lg`uMYqu|l@3q@?@PNQo<%f&Pn^znVbT -3@|UE6xe8A^q-4*RBU4-9UAfQUU{-&FE1Ejkdn=iI=`IFOw8tW2{>{&xR?Ww9@h7eL$YkKFa8mVe3TW -swEV@+-Y%b(Wo2A^wGuVZHEj33ETyFczQJUn7ZVJIyQD=a4-7V&CSBYt7xc6o#vwV4F$MCIacIYw6GO -alY!e?2N+}I&$JO*D3}IYl5zsW4F~$-hC5P`}1PrcAn;!VY@@UVKe#e#1m6-^pNfLS(y}4>CJ%deCx7 -}vQc%8>*vqR5VRnB)bipFj)T*IJ#5cpNtRGLmmSN_@vsB2gd7H$PQ$FtoPKVaJNKpi|TK2X;B<& -bD}Ix^0*TiLp~WmIkF0MlbxoT~khv-SyAhzhb&d&WquZJmYP7uc=Sr0c!4XgVn2=Z#f-%EHmI(>E1+( -WTaW46G!yKF`9Jrg-!cge5~lK6hxY92h1T#YyxI1XaZsf_fs@*^C<{O&t4X+^~y_b_}+w>65SD$vbbC -v8zKtERA`T<6BXm+QAnNVmddc_QJJoikXR`NKD-)dU)^dW*b_xO5dvCo^AWULN^keLsn;I1clsF^gK% -@WgVYYM-%y%$o$kZ!hrz88Dwq%hHf#KgQ3n?45^pE&`R&_Vl -dI))b&wqp0X0SnNUW9~VMndoJrJC9oOG;Gj~|FQnMfZX70?r|$HqCq*@J5^wnav_swBrkv`M-X7`yi0 -}0u$Nx|R@o*CVu>J@8W9{KD4tpnzUk~}Ny$+!W5`#$sXJCwGP#hr`g1|_UCK(37;guJDDic|$Uj&0ZJ -xu@#mV}^20Hho^;D#6j_!OD}o<&lBQlC@8^>K;@d3=-rVn;A&l>!w8l6dLpkyoDL)mhHOAlrcuKscB( -AA=Bn9$Y{GHW$aBE0B3nzx6pV3UVz8^D_3008a#|m*{02_{zkfuOHx7Z$AD~xic@7BKB&>TPbaVzbQ` -sRwhD&^fXg{Sm?4Ek9q6o=1gsidFyG&bgQ#Z2N$q7^g}9Mau(6~f*ZhJNVD9BWM4T+mGm{t{Vt86^VO -Av0*@F2>s879>OWhG3&{5$1r>kupZ#(lXs%mS#4B0+#~Q~!KJ)vDA#i3u?S7Nl?O?3g3lrHs@aQhp?h -|TbH8SnpbWLs>Q5y7SlU`FpK3(F|p1!&hgQc5K9$>U$L{%Sc!Z`TudhcXu#RGP> -hAlvL(AGchk3YH7^UlXw{++2F^?SQ**HIzsK%o7R&bJnn1#)E*aGx;oM$yPf$HFGzm$Fin!ptKG4v#9^^->$S< -XLfCL>BjOR;gqNW;1Zd`6dOQo!ack@}*SJ~xalm7%J+-A)Rg-e -DR?Hb3W$&4m&_ -Sl)j!?Qxrt>Ta!W9UdO?{B!Za@= --_KBAn;5Z3Y#HSOWp>NTug3SvLmsY?P3Um~dhMa0W>_$$D!Osk*LZ-?;wqcx%2H -Bv(Ru%JZGQN2fn=dzIsO`7fyUTH$JU5$)ZE<~2kc7i8a&=YM6okbX-Q=FkzP0l*i0WRPF`cI+*$I;SU -B#>B<4y#RuUS>B5?6qgE0gsKj3w-+4qQRG;B?-ZG=(g%ON;4S90 -Y4(H5JS67-rS46(97VdV!8uQnbJll7-+XJ`0evHZ1fmhl=o=v<(Fo8R)pOK~6()C&)*~b1yZ5hJHOTO -E1cnKhZ0~dIoV~ktMRVHNXPO^FO=Pv2n+nHv*7gri&Uc1UH!{)bu7me6o5w|TmX_{Z98}uX_-C;R`ut -vOx`OF?A -NtOncCmb10wL6}L>x^Z#oSl_3v@7?q$&r@~xpbkPTXt`f?Mb%+Dq5q2h&?KJun)|M5>FGKD)-0!5HH; -Bo*ys)>-89NYk$0m_hGDkN28MbXdO4J4cV_;mW~K8ySqF?$)LCSfIv4=nU=gH> -x_C?X6`fArT|zQa|#hQfog>4D2T1%mEGkZXYg6(?ix0@TPATUT_xo?I{?~UFaP)BYqWwT`Cc%M+^d4v -WLqv-U!k9q4*OKqB6R~S=aw{&HoavmrK?q+1nn}zux?;_W#WZe+kk0LJpF9pHaa7bc`<|M!&iKyE+u~ -Wxa?I48b5PW28~yQ&ng(d$p}(px~E`{*7dS+aWKX<&~A?Z?(k?=t;zZN8zU|aixgVEBP3`bd93;Jx{l -C!u9w{_g2=q61w#!5d$iFfsj%JWRdXXSBXOk+-@bB`O9f>C3|TOT$l0Jl#o{(Ol! -hqBm(L(QrwQdV^xY!By(PJLV=C}vsMt$*TIrr5Bao)P`mXE1N1NpGmQ8%OulaJ~){&0D1n@PuZRnN** -rN3`>H>!hyPuSgE|pp5t%VoA9qfZ8ZXK@u*k;ot^SYi>0VgX+Q`pOT;1RzrGMkUBvfaBc)YtOnmsi4K -)Mxao{UWd}{(5l5TjIymrDwRtWWCK$m7X^<@a+{btzrwLDUN&%r7QZaq!n2o$T?K?1_)OJoy)gmh*`$ -Iw}h8wiGArk2lC~-n_+7cB)ug@Woc^om0IJbXe|4cZYxhN{MdJt&a$|^&2I-Q+=b+KwRBsdHTl@>D=e -&BPg+NBjUvW^^>=mv7X_I6-Z#n(+RMqS_`a!5W^ro$9u- - -60j$+ASm6(6AFXhKEc#HwRzr1gpAsbx9x!LSq&rj@$5RkBT_lBcbbefyXKCL&PZ;Fb) -RLNX`;9JM8Ob5A!Kmh}ff1hJHi9Qn?&kp2>Q(m9?02ZTq114aztTwZ_Q*TA^(hoCb`9DLm9e_P3ro6;jD4)(8Xs6Zh_H>y&RO{fOOg84!gUd@h)XL20B{D -yRwFsh~iItffBkvKkE5lE?Y7mABVP0%3F;PMLuX)EwNj8ud}IuK2CJu8rHFjjrvlqG; ->_<(7A3^rK&Vv>q)>1MIgZIDFOcv1Yp{~A6Xdy(Fvu-bC)MbH$+2_0*j$@6(CGUL5M`^aNYfyXrN%lX -jH7hK9BKAwwM`bP^4^y3Qirwa`9;|lYq3k>w*3iGE64D`0bRDxg$HFnnF+fEs=`g0ePgY{8>9!#dlEg -;zpy|T^1b=q>@SD>rPmkRVZ8B_eczGrz@vEcIBFM3Z|{6hO{#Ymkyc8i8icY|aEI-}y#EmLRhQM0Ta> -KT>ZXCVK+?Ax -!6v1-7#shdCtjczdAPPTM>)Ves_DSMaksd|R9bL`swO}Xy}6e^miy;D3B@y3(AArqr8?fz) -h+ndOqg)_?B4!OBKDeR_x)ErwK+hs&Ho(pRjc4;CNlCK(NP(!2@HC}YxeK)RQd3QwZ$dLDJo9xaWw}- -nVhYOlw)m|S8^d^e({yv}D&cvYGk*8ss-15H0->MP~^z1151nfAeD2AH1LUQ%=l<+E?qk{}@joOS2Bh -zCm%$6x4Xz}TGf40HCpB-()uMAJ3QY;?ZcGK7=i7(L|6(m)S58M+ -ff^KNngo=GTb;uXmy&q8mIlv8v9v8p2_yfYul>5tRE(G6S)t>6nM6OI(Mi(4`qo@^`pE_;6*4>R*rI6r}u5qo`Aenfq#@M0RAsZvr!wf`R-b -~9>a~NG(#T;zkM)&@?4GnkSlGWcvWy-g3%Mb+k`RIZ0e2#nSF^GIunj&7b?YP+)m-@jf -!TKc}f=#`>Xk{Ri?O#x|x|aE^+tf5tT0o~~O(u%_!XL$mvt&yTSWTh!jqRaSZ -K(XG>;_K1Q(T8g`qxc%=rIU -|a$d0wi{ehUC1=bC~-y?dIl9`@K&4`LT+s{~_4N*Tir}#>7%?971X^Cf0oSplynvbQ)%jcd*$jp7J>7 -=^ovA$n5`cfwk+{B7--(K(-8#l^PR-L6?tT-zVYzI+F9~}(8>X98?O=tKtmB$&|Nhs&d55_)|hpH^#0 -+w#$RUItMIkGc*MXv01f0J0fgr-oTkL$WsBZ_fEo^fRFHm-h5G|dbX7-g>cZr-FgeoRf#U-?MDT-ihB -qF&S9#Qs`9Phr@dm-FF^vTh;Yf%rsN|5A;|__i(r;`t*fB=0(VU?uhA3}Otgeg=KX~4KEQd<+ll8{df6R<(OpMyfj?w|7*CBbpHgh_UxZ7JmJ8_o*TJCR$UD2UG#bZb(yb5q5A -wYhdHRO?zY3gxNGMbSBv_*UOb<8ogw)SJ;QJqRANo)E{*O!ASH7MpvTZi;DB)0OW|n?4O!8fb53D4)0UnAztX%YAL ->kWp;X%f2XYu)-c(ElTy<)L~`s%BcR_Y-q8;FFPqe70xZ>cHR?;Fr#2=Sr)|eo>h6+Qu*k>@||^Wki) -Tfa2FI!ZmwRtWj*&n?U7sORG;`dKX2J(Nui*8V%_PP-fAfuI#tcD?ghTv_hCI^nCj#F%$BfXSw7F(0U -3ivJaL$Fy-gvuxT29l$9ogq`m;#GI6_k8SatTP+>hr2D~|X3UG=fU^$hFX-8>RWTXqVJkfgjuv(#NhU -3L#~2Re*y3}aZ#l`CZSvai~HtDoDpC5{KpyC=`fB01d}j)hD+%!m3pU$(id@Y;s#`YU80yPIv@m^j}m -*>NKmWy1rwy{u!BmF_>UqeK)L^k9TzBU&-BWOy}`59_gMDJP3v9vQB -Wvq1Ml;ET8Uq15%gS01WR#4#z{Wj>D8dxXj{&dv_b?Ly -I>w=PG8DebJ=hX3bu|N560rRnnCWb#ErD*N8+82Cde*Vmb#Xya3Hs{i3oUjl%CILx=9z^6KhE0e|}&~ -b`kfbM4$DEMVw;u}EN_*LBjiZWgXRLJLjrvwccbrgf966~!flmR;jE8hmyA}>P&ka>TVYb4mq@i+yyV -&Y%$eo#6>fNh8M*6Zk548n+nc{xZCprs~#$v)#RdFGscRSaXVeSi#vC}ae}hU}%rwFU!gkPsz6W&~wH -nMwLv-Y?FeD&&rRRjvBeVM^eOA9N6b4KO5$Z%guerNblg5}j_3_#%B%gONBwW7aWlBMD}gvEt53%>^G -xB1m%6hy6-Jo!@fVA9_*FODy0GFqTq{7hVF8lpVjc)4ZH|p?9m^Mw38!atU^Wk?ebGD)QAb^WGWro~p -+UAg;Ln2bs%QDgkp^)fv2{D<9fx}NjJ((0)Fl$!dy_h!e=K~c|eb4J+tp)1=1U -Pw(q9k(JRQL(H^2EQkqnY?;>yX1HFYQ@f7Krmbg=BLK@mJ9O;J+g<}VTYb4ZCx=GFYJgbr$U>RtEB-) -x~E42J3f|5)U6IQhuj65yR{eL`xdTB@1C>7ZlZ%w=1yCx-(Pv%qV0BCATc+%uv%%i-wft;vXx!#^yOR -(Voh<^=N@5Eg=dNFVI0!jC>faMI+r>ggG*!UmRCZ``N-G8am_W7~>)bXQMGW-Pb3@h6Zt%wlzEUS(Q|r%(kpUVyHO;I=0#E)8I~*x&3(VXTBE?T-zNTIf>lpNl -0(KH_%7(%|ER___6!|lEr&eT8R}d6-I9&mUrb*GZYsqHrcOxZ-fv-Q4-BBC&>2`G00_jKLvAo#7a#g4 -`GlYEEOdWv%c+EG4@CLkZSXD5K93oJR0V7bUa{9pJpS(4tXXqgCnWb)*h4-+$H07R=V?!Yl}mBZPD&# -l5j_wY~K^^sdeyBmss-bocz<|bWow~yGx-BgDoPME~>n0a`Z`SuG>pa>r@x<+t6d8gH&Jj+YXgBR^XY -85|iS#rk(l7ghf&Wn&h2a^3Ah)*8hGJea4(My69S5ijR3ze8&ky-qIK=^+_Wp0-wEo|MQ{Ca}FL0{LCK7; -1#`pSA;N8$SuknTLV=TReC!^P-W(*brSQ$?r$)2tE^_n$g{2pKb6keep@%1abGXDr(p&#+}CwM)&lQw -<8>kP~)!DdwxD^}qtUJXSG`2hCu>-+gCKMA}jxeq$ge0R~0a0~qoZ+{23&>P<3!(<&8_PSw!ON&pk8W -y4?ao^$`Ci}`7_6$S5Jvi=Ixd;5}Ut;`vjW)JXK<&=f)2DXx9vDe$&s2Nd7Fhy7WA*`8XaorI$2`pyb6V@!C$U3N>A!`nV(e;cz_n2lH&=m*cdbl~cPZ -W6v~#`^JRSX{g8NJ%u+j$?0X88qB3Q^EE^3OH`iCOVYVwdos+;xT_L5Tqm$O(PkbxB5|Wr6H1$FmNo= -3?JM8AUAFy(-`ot0#YJt-b@f2Vj14Q}{pdaIOX%3w(X9Qc!3Hm^2zNjrHH=+-uryw7tqVu&$$@4jc*@ -TQ>APOnU%@T(JG_Ugwoe=1`5inpBY=hw!W%txc2=}QRt1KJpipr);t)}%8 -?C!?*NLRv$Iy+>hlJpb?ev(%Dv7Ej1!K2YlRM$EzfTA9>P|9wJ9W&#Po#5ND@Eo`zPuX1Dq0q&+1$B$u=^Gy=m_wSao#~N^hJ`!k&$D+wcC@RF`_gZb{{U`3WR3 -q%;qL$5_&)>iKOOtGNQ}~8-v|_;+lwCtfCVyEICBPK`A% -Mow3gToAq!p+LY?%RN2IduUGN3U6mtW37Z>WdPBu1(%^g+!_(U8hORz6@ys>P=q80|HkXDZ -ygp0xEYEAVzDAI#sa`!5gm&_APs8q)+qk>NK6AMhavkd5?{psFOm37A@2=|*J$KBB>qP1`Oi@q`n#z2 -3o3`jFHsr#yQue{pfdC=EPsy5{j~qc3P1JlQ5o|VC|%#@`mp{uBK~J;6sBwJd+QFnbj8L**r4#flJ~R -v-0YqtQ%D$cgZ6!K$ekx1;Qx7ly21bkNid1u_$Ib?hn=omZeyN7rCsW|67H|h+i;IhqbX37XzdAuWmSrnyZU$vF -AT*auBRBca4K0s5YLTR#$lh(!x^G3GT#}k>zS~+W0I<`VxvVHH_|uEqq;t3*-cWHFJ!_MJm<2@C-(<6 -ivKVomi0O3O!y9QX)t{OVQz}BqcOp1@58oBirmGRtZ|Q1dAdLH^xh -oFKGin|b>}RzrPO80FNZt&gznQAr`9~!akg)^3y^{?5RLKdiLNQ_kk%XgVOcW%6tY`zb)QU~Xf&=K(k -ads{+vRUBpz}%NH;UB5(&N0*oOjNQO~)b1rkYYyQYy%jMCdg&X^Mf^mO4U}zE2TDI)k8@4SU+;wP07rA)28fk|oe{2L3>W>xm&($b$P|e|VV?TTpY_9+Y -iaox*@T2g=KfC0M#>A(me+!ljg)lftvNX+71Pb&cKCJ_1-b@T4P^yu={jZ6X4<%)R8 -wW?Ce$M{lb3orAmQQ0H325_Oq`XHxO1tUP?$gm`z0g7Cs%YRMy8Y0um{nC@c5B^{44m6(n2yK|liFsQ -hkS0yOyqM83T006lw2PbOm6@7gm!!hsL@@U0@jOfz>|7P_QjmN50&@p_()SafrPmA;1IIe2Gc}w -Lk!BUyi+I0{w{Se>xNBV=6z*zo3_j{wXvEE(2|6f_5Scl -2p1{Bnne*07>uH;EYahQ+uxMWD&%dLuD+yT-h1vgwJ7Lz$Pt@gMf?=Bnj185ch#>% -}=W{5}Icj0$Zij|Yj=*{BQm4)P|W+H$C#o?v`y-p~g@*EmO##@`qewBbQtOz)vwM*{8&OMH$ -?d{%aVdC~FWgyh3%b@?;bY}TZ2RHfY=BTo#?Sk(^vxXk=lL!JKTEq9f)r!^qgedw6X|Fl?pm#>5n=6K -+Zp4W_I*5-DMUiO}NKTcnM5}rXP{Ay4ywxaxQzfQEfLvXzYqeNn>96IULwPfZFD^4` -bi$%16G9IiHCct}PNu#*QezbL$nOwv98M@;&nh1_j>$fSC=`wL7m1=s$*-zq#Zyi1~W@cM!vpG|pfQM -q{8&ouR5`pwBfT#!{;2M3=FUdXN0*|~}kZ^zUMCNH} -J90dX1Iw2$jy-4dN`O9Dd{k_=Uj}VhfjI_6(?&XO5%D3Qu7h?9|$;Q_JqKka8{TX74<;{leTZnlb``? -F{?`QHS5Ho0wVa&-Gk#-+p^6|PK>nDD3oxSfCdb{&{TM7fV4cFD0Kqvf|2agMJl++4epg>0^r(dKp)B -5L3Wt%$c3_L+?vp}2j%9>+Ii9_w#lzfkZN4VMbRM$2Unmc|Hj}deh%9I(#=^h -nu;sqm`lFwOTdpdw1MY7fTvr|FnBl_QgBeJIIobv%==(bUkr+$!!i9My&LC*3d)p!2DCpwxp#hW*%c6 -YEbZ1CR&Z9+1tQf#Dcp))N@uC5iK51)+&;#d>*~87=}I#2aEeOiMLiI -0s;n4=W30aA%?BQv2YJaKh9P$tBL5A<@bl(KlFw+mP!(kDj#Jifma96_vP1qy5cAbiTx(8!8B%v+$D+ -d}zC0bF-C2t)VeQS_^pAT6!_px>JNX_OL~V3sH!@ukvv8@PNfUQUZ`xgRak9|?H%3%vt(7pyNB4P?ko -KU>d@qOItvcVHBV+~0jKK*)HRlqwWD3QOL@2iAl3@?p?Zx9ZD=wD8;fi0Ice3%_@=%-~TA0fxHSVq<`p;%YQE~chFhWmOL;XZ!kwjy#tDtLNk=dq59T+8HUzIxYz7@=!3vfW&hXIV58xg~UL -4ZxVJ;GZ`ZInHU!pQ*6(*i9%uicWzA{SeDiK40rxwL^k|W;}a@+seT=;jJL_f;q1-%N~t5`6#P=fSrp -uf$!1qVEg|JVh&w)sm9Ac#+P56{~5TvpZlF74#VO!z!ZeW37o`MRR2`Ql>qBA@{&$pnSYdk+Hf3sm50 -BIT|fr#RSeFoameSvOUk@r9UKHE83N?))+ixHL7V9s^iVjE8=>DcSSf&G*elFg?}Pm+c%fhYr+5xpVR -8E3R1BgIk2DVZ>WkYyNY@(iUX5!L414d=3t7zPD4Jmwz7-BecFejX%OMR#tr7pdp<+q# -#=?a@}wH@D}d2kfY&M+$Q*~RnHnjJ;IX8Kk?)El8Ht;dcPnhkQd1G);$CAwcTvI`-&t}|OsJTkA0#cV -X9Kzw~hvPIj=dYn=B#^axyQ5_e|u2)E_BK9)F?Pd#~na=EvP|qC7NMCeVy>i|_BVOqBFnv4Y{&t5?U` --sVrw~OKdb|f$(-^prGK}K(fDy$BGZIn_Yu>V6{*aBMOfI3fG?x^UO2bM<-MysBVK*|%FxNYu+FPlfZ -k~_bZENuXGVJh_hG=BJF;|qt4$bqnJQqaH$E`$MP6TX4-1#vc>VzN9uO5-oH<3D*8+NE*4MVaAD`Bx*CQL@170-%T>u^lRxU6>w)3c^_1fJ|Fe1z_5sI-#a9PU;&S&8V -MgJIP3=fL`lcT^waFdLkn)3WZi2EDmjNU9)370MTmzO?iaB1eRv#)((!S@X{4^$#%NoK9(C&p!#Dy-+ -7zHAKQ9tzKNho4_egxlze-*19|=vJ#4#DV6`od%6 -lu5h(UQ^e2h3JXg|qFbKVu)UK=C0lj(NbkMstn?)NKfKC!;NY=_JD`nTVg)#&*8mkkVUIA2)T?`|vck -X9CCM|1s8wBezz0=nO497XQKdq#MA2x0|if+Dxi`^Ya3qdI6k(nM}JW6)MZ$$OJ3GJ8VZM}*t^LbG>F -P@3AVFmPFVAe?7;Wr?WCa6D!Q!wHtdFtrs+G*@GdzXp;Kv9nWcJ=DAv+O3cC&Y)}GjPS!iQ4HK8mRVI -r?h%J&s6@+qK3|(~#6BDYPFkye#-aK6j&2aC={*{{Jodu1=MLEM&yjTo%!|%j3=L!4KG -3K&qs;l$FH-v;m^7Se(XQH@x{NQ`bD4m;eVAz`>LnDknsUvQuPJ1F3C&nZ)w+J{xc#C|X-gMNPmuw_` -z6Tr`Oe)4HblKONt6XUFO@)H1-#qy*{`|rfhS -6`s*h@VjyK3p?8@=3T*BoS9DtTIZs&AjhQ?MxijAz9uwLdo0VDVg`se9BpvrTySCgZAEP~F4@xSVohr -kY++3fMzH{T}Y^nWOB(5aMm)#^G%IGh`oih*EhMuAt_iDLx5V+n@8MMs=Y~-4&Vo!nfCmJB15gSPm{+ -JAXcFo@qB5?k9;WmMw7)G!N%~0t21EW4AMZmKK0TOB?`EmnC0l{Ed5W21bIEsU`>dJUl;*_mnc=~y15 -(8}eR_5}Gpg|TEa3~6Bt*sF}kd8}$?0rN4f&;8yVz567DvEwp=?gmZP*4t>zs%Iva2q(~=AdMFjoYc0 -fN}cLn_Hvub^a^mqFEq*pEEDXxeSEWK;bw7$z}4TTf#7aKB2&3@OPw$-vCk+_+K-+deT&VvdJ225{f_ -rr07{A>-uR-w)6d_DD+z@@>Okqr6TEfga|MZ=!1%Ak|$cvS3lish}>+{=h+199<>en=@3AaKtJ8bF+* -}+Ta?iEdz98&ixmec)oDE!m ->E6Fe{F;OhYDOIYdMV*~+9DC)7dLsikz2r@wo)LYVMRyzvhA8ttbe^|0I%TgG3u6vns^B{kD+aF -q8L3$4vlF4AaaLKUEnz4eSmMA-T0c@@XT&%tsxwKA?_OPaCg?qHA2tLW+= -L>7bLhQJzjcSMYNw&WuUoCz37|CdW#ml~yR$!xA~TZVFS#bYA?P(xALa*j8qRXydB8_%s!_1L^zl7#? -l`8T1)}?cpI%&3M`xne`_!MOinjE5>X6$kLB|9q`H6|F_##&uqAUSDEo@GzQ)q?-zdWm;I##<3}N*|6 -_OgB6akuJAAtkVl+z=pLRE|D_!;~%Ontxr$d?dnoNQM;T^*Q3+#2pL2}TIOn>g{u|{fZ9Jt1K>%Z7b? -R>p-jpk4mEQMV1{pufq0IWTWKY9t8rh}WV<6R$c_4Ag4IuY@IvfKV2! -i7z`XA_A)BcjYF2;H(`a}DGLG7$eW~bgy!g^ -k;mLliab^=>2UC!@P!Q#YZuS;=XD%9L=YJzZ-(Qnv_3|L4M7{KQtvQl+O~N(63y;iOs-X@hvqyL -|xW$G@9f+;gcLs-v%8;H!Ru@J=&>|>%rYk++Knp!{P!~keXWq&-xU3S*eIk8eMF1F66Kir}-8FMZr@MOEY%92P1n-Ib~8hL7Vt%Vy7oc>Zy -+o|nNp)-qAvY1RG^i$-=$TZbM4QNFB}o0iSlxKIveUEs*5lBi4Esh)WJVUP^tmh?gkB?iIupf|w7kCRJ0Y^X<&x}07g6&aq_F@BXMYFF -H_^hwd&iW8Cx-mpzivnuN46bIbO-cuMCDTJ8mnkeDW^SHq75$Dg@JU|R_pj`+U1Nt=i^!3UCu^wOtz{ -eng`~o9HWM^P-J*gr@4){7lGp}%dp6g?pOcAp}vL3Uk>w~9LT3;A -vs8n(hS&kLvUaexl;Fp1-VkNuf>9Z0L=h20`b4-^NqL+5bpHlHiEreukzPLE281dYh&-#A-Dzv7zMWU -*0EC@;Ma%w$WQ19d@ge^ftAh!Y^Fe?ANJ}=Bws}wV0-PQN(h1m6yP5Xz!qR60<;N%VhA{a1TurjD~Ae -#k(C6jVaso8J>(W_#1Y4@rIJ6XK)dqF3hN&bL~&R83Vm2Z0o$l|fLjp}y(LfSWbj|mV*jc`tT@DY8sy -$7NJBJcz2?pzsuPC#hIqFXch*S302O^7z8=U#fxbB4GD0?A75LW3;R&><>jG3WzB`P+9eh7F1wZsz*p -2ps|CZT6-`sVUpPcvhV(*AGD4$&80MU5voinj_xTkRpCjG;CghyXk2CdV2tRck{2x9=`djYz!ziF}#+ -(*XH`CcgFN1Y7lJE066lUpKo2x_DDmtizuAsY}4@j{#|VzR~r6BBQ#`wSaHc}V0;)Bz-;oY7v?Xp%?| -AJb@5Z~K%;Ofs%J_(rbyLrc%4L}v1U%0^IaIfpza`%cHryI7GX#4N;B#l_q^vU@s7=Xh$C-H3a}Ew>o -2#1UU$oQ94sR|m5(S>A3!>(;XOMuIv(EK6U>KI6JN{;lTqVlRU#7w=RL -o_(gJ+F`nh_mFa?-$K%4y%i>-rfk+YDIJX#-?aBoQ@dcNyqNMiRV8zRQp -%^;eL9Y$7*!U2@)%2qiP1SdB~3iyB}JYvdfXDDrXL(qQS&5|v5-tQ!?3;u^mf)pPk=B6<~i~ZuzSNI) -E&mDLeCg7C9Z+a^J(Ag*mp?2jomV+`So4YZhh8$_?hk4inVeIYL1T3bi3)2cSFU^h(Z#p -IK}UjMb0HVU4QvD|41T#H3$ -ol1+NtiQLwWj;C&agaDdxOkWqMz2Z_mtav@lG8xf{Tz*)_@b%n7(n}6tJK>m{t|I~By8wO!Q^)4*2^N -~Poo1JKqJ)P_6+28KH$erFiH#rH^XB*t42-Tg>3fW&*E$(Gk^7(%1`lS*qIN(B9%^2!1Jnr4RL_1E}v -4)Rg?s;Zql&^!c;XK$Lx<2p?QN-Fl+|2&HO|_=*?po*cS+EF!CdOv65saJ|<0}`(t-q1A6DZ>ZL*`EpAPVt2r>N~qVcJ%9u0PFXh&2?_iTQ79{WVt?wi$9cZ -E2YL{UlOK@JdfQ^WaGF#CX?56THj$z-Oq@`dn+54LF^uQ!B(@M{xe{dv!Wc^X-EP0GD%?S+#ZejJvxsAqfFM#d%IW& -)J|ifcYCERPu}+CSeWxD~dkH6nM)P&ehSrdYbWVwm($0Zj!%8MDIBz(g7hbpz#F*-N4s0mA_oR2+24urGc_XP|v0B0#OwO2vTBDgv?~E4f@r9-6#F -+TN1A6xbYJspWe4`ai((6bD_dzoBkQ1>~AfUlJSA9yvA9Pn|9=`gZ+Ml?-y9Z|>Ib^v$<1Z(R9I87mC -}1VO!v+=o61>R!jdkq#hZ@=oWJHQ>@A8x8p%w0*w1ol0pFU%Vyo=UWP-%;az#*c(?qRK}OL+y!Ka(zM -bwUyjEsTmoPN$=$iG}JdtAe0IXt4BouAm-pizo`s -0naSKG>(_LRL8DK;yeQyOTbl;;#Wrnl#o -)KP5ZvR&}@R6P+`$3n#t5h)(z+cu@qwtxkPM5$KXXFRg8kDa_{7zff%z- -8kS;H0cS7k1PekJ+V56$w85!m|lt&1U8lU97yfHF8mkuP5~pdJBkPP>-xpy)`p=|x&UM8+>QeL1j$Qd -@6Qz1(r7^gWQY1_gTpq)qFZ4(`G!8_xp$X6wnNv7Vq_zqcn_kEhpcJ?^hWhD$9T0q$c^Qt&rUv49=f` -CGd(Yg&J*8kch5^?-{kz6LI2!LRX9BRJ9GwwyGr7p`}bEa0eC13$ENpyk_{K(DDN-~=!wj!O!$IS& -r`FC#(`t-f2uZQ%y;O!YDZ#MvMi32|a@cIaD00#K*bQ{2X48aTQdyJ=VW~RKq74^KKINIuClC@Xe -p)PyZ^=_ft2%PPJH1P~9(gO5WY(UcZ0iN(Y5t#C7Dir);bvi%;;D);|*t1`1vexd3-Yz*|@s{|6^9Ts -p0z0MHr4OA_;YJIKvB+aSmgwG~kV{8|(K8nTp`f{ptd6{?%X3(fd*leYV#B$x;t}w$}g2QV+eY -^@_lYgG~Cxuk)0GT{;gRT_jD|c4m@BxEv>43pFC#M7pm{p2JDXrIIQpI4quZSLZF&2RfG9=zJEV4i(Z -vwyS>TZBy=6NkJ$+vIoN6sDC`gS`C#|;+G&-3-99%|th$@pHh5#(S#ddYDcnykRExZSbgL%mEaWvlExzpQKGH3|(}j;OLZ9YWQ-Cqf+;iF51Q0 -amKI?ae4DF6!1)rG&Acip*N69n3AAUvtCwgZhMjz4pug)mmfJ{0qTBk-^)cm3H?3^;iC1e;uo&%(^nG -hQ43=A-iOMRhPfACOZ$^fBhv!L)JdB?wpm~U&>I!zF8D9lsPhs4IUMH6<7|*}Mf<*(8^;iB9%2!5grJH --tz22E|9dSy!1ZRkcx*>~ei!OxIN?!n|uz@oMbrnqi+O`K_gTC-OYMd#|+E`J@e{zTEFXHuCF9;*TeN -Ip-~3ih_+o^QkT0=(I@R9eg>K1jrAD01t-r>34kR)k%FV`62Mn(gF8bH4I>p6x2{o< -tW*!ka)bBMEq;jn@Rx7k~JhUW$1$UU>^ekIXv#Av~~I=E{)<#k&OZe-fbK-7$AOB>SZo}61ljVq8mUN -w3iY#*+#Q09f5xZ}Qb?UKgfcF8+lPZoKn7#rT_pGWId1S^l1q(=hVatJ+Jd1LA0_Fg%;fb1#^jhN%GZ -A5*u`misPJ -&4RB|vd`3~vpb73QK^F3z)EmYSbbhq -r+#HQ{4`CVZCF25nle*_UKgS2TsQC3G^v=%BnnQk-4wS4nZ{R)8U8VJFdT;#H%WU}oHt0SnRrxup<@R -q~FkWiAh}USR-QFpq`zBa6=fWV6>(j0dk2s(&nP#ltk$8Hz{ASpv*2dWl2To>k1={W>W&N|B#e!;1F> -LhZ-cQn}`z#1kxD-pgYk0%HbiI|*PFOpqooF-LRJiU=QhndR5aVnR8etVqjXUn~SOW>2Yx+#vcU3N(+ -GnBo*N44vGEFI5cfyBlnCd6d4*TW`9|;?hm3sE{LhFR+D6*8^t{C0p=MK4>B)VUAot8S0q#Ld$BWl^V -yYYakOM^HW_F!S<2wej8`lvfqMn!C3V&*~%+lY-cj#LhOn^2{-OE0D;X5vOMc)!13kDW(nx*PNqwSLA -R(Hv)2;I_*7PDSONrTK?82gm67CnY#5FW`PuGx@H}WkpDb(|peS7buuI7nQr|I!g?;| -AT{?5ns2q>+AALzn~HUT2k|piBmRH4m=9h`&4y&=}MT#@W9~3Mj%L87Kt;_Od7_cU&=cohnd1%RnzW` -BHfyV=zBZ$hZzg{1!Gd0GpTn?{*6FA0X59p3)Ke5bs|hQ}-lU_Yir%b4~sm9k89qrC+$YaeVo;yn&k_ -yO4*hg#F!*>o>JDAEhxbZ&^c37O;m0y_w8%@3k>5x_4aCuRoveXS0R=WV)Zt7W&n6{{*zAyxkHvZts% -!JQJMCK9(|9QYqv6mYdP8Etxp{U{BnL##MPdA?8Ut!1BSeGFgJSWQ(YVFcIYXZKv -(^fJL}%$ZLN`E(@gDu063yW#TZ81#4AV4SBV_P0w$=;2E}=8els&gH`K;cff^V!)3mwu9Kw4A5iN6?O -8Id8YE?nzgSTG1(r1t2dYD>chh}xJcu24lsmHy&mW%PMnwmYq;BRa`512>D0O)Wh5L%)t>pMRHSQMRE -%oj3amBOav1xL&6*yF^28=-^LcKuKvM#OP!y(Pi8~iIp*@%n->JbseI6V^BHRQk$6J$e|Gfe@}y;oe&Y&XZ) -O_*aLLyT9G)ZvF4Bz*)ve_vLTyJ7nvBwz8lE1y%Mp30{#(lA$v<_M(hZ(58@&Fly{c@3;THph!^|p9hL4FxPi>xqlvy-)TzBKJ$*}^z; -E5@6y1v~`*<4hrd|qe(gEKx8Qt8G#c@O$*XS7TIOQ@GFEblE??}?7^WA7y3 -Bf)OO_SUX?Tb@P1_zgI`QN@OYZ&Wt@#d=3^%(^$b0KBLCxN(YpXxn!i<~vz3Su{l(qD@8ojNn8Q-#aj -q$S7@y8g*gz9qwD3oZgcgv)K)5_Eyd>&X{m=Yc9ZNujgByYX`u;DUkW0zQqK96=<{HS;QRNxLJhV?$Y -w-6RXb$Eqs|h($MkktSpb{#b3B;MNvGZU4lfSe5wXt>VP$Az^ny9(8CZ@W5}sE&B6w}*AxKY=|+vbaC5e{ -=f{}t%t?(Y6F$YK9|cwV)#tPWjyj$P$!TatF=yiHD<6q;)Ei(O_g(%a*j++PDfNlpj~76E)JmuJTCX5 -(W^;b%0qpandoNmN5yzbZPTqZevU6MDw&f=KERq_4OUs5h>lAQ{~!&p>rOyCnfByDlsLeG$)$0&1#Z6 -@h7*~m_>8iKJy(>i$QX~OJzyOLjpWx~1zo=wCitDI06)lY8P?MjI=vD5H1~?W)VZr(oUKPnHcijzPc= -e1&27Uxe^BzAwIYx{)S16@?ka)zK@%?H;dH%IeROy%x3$+@gj&plEK|&KXA==xH~2@+-l!&Xh2+O51s ->P3kUY@3g0C5@igESH3(KkjJVsZv=s`AGYdIjLmRuqaDG55ij@;EkIyg&Dw~7LW19GV!ZrMp1s7*K7J -hD$F851oyZLEPo*Tw89pLh;;M-py}LteaqRG$tGx!{!-7XbFJ99`-S%^teKyoEu0Kafg?-~8fW1`qC8 -dV1ZAa_ZikSL{N0iO{!K5GfNQD^7X<+{@iz{j1)RC$x-%?4mu^=pj4`Eu&SiXSxT3{_Q|a&YLFRVB$A -s`#j{g?VI?X{pRF-9(QT=n{w}mdKh;M>A!%;ZwLg#8)W^HBlpni-+nnM@&DBtJ~8uu@cMrVrW>~H(kf -fHlVCcr=h9cGLC?EOxv&#$@iEni}zLph~ -6O%LB0;F{V_01cL2qTf5G^UgH-Ys%BJ@~FMhj4$M4S3?2XDBq!Pv6ExIiNA>S4jZ!e22{zS5!8{pU$4 -8AAP;$3;0g#QXm1!M1UoNs>urv6)Pv-ts`99WqFhiAD@h_axnNu;^i5k?Wj%PDmBX|0PQiQ}$LeU3IX -+O@cL5y`s=ZKwnTZ_vCOA7K{v!}NjAOvmz5WinIF;k=iN+S9T<5qD$L5N?6(hbp=4V$i{Q4#nk!l)F* -G2ICEsy@m=|F+`73OEh?2cG8s67!Q+f7($g5_q;oFn_C9ety>+sX1{jH -S=?1Q7Vx#<}Qq&c3Psu8Q>WkqycIIci;x|x3N@g}WlwE*LCm -%6BLV(#PV;zOpuHh8t_Mlh37rf+2yq!8v8oT2iP*kMN%DDnpFKq$^}C;}5Jvqa|DHZ6WVaW9h~ZqO=| -gL_i%-W-##ENV0uMtQ%;r5>lky$6zVRc@Euqz?je68f92XgMTbR^FyBttd=baqZMukrC@jOOkf##SLE -)a-U#C9xTQt2I=T2Wa*u#0@G@ -!=y&XFJz}^z(W)7<49vxk2h#t=}X1*BBVpia%%BIEE9>-R{3{9E-3#@;I^zXE6+E(Naj#;_2m2;Zi0( -e$>EO#7j*PomZEt{O(4m~~@%3wl{S@3&$VZ^dD#&hIP?DmP#Z^1S57^y7n5lB$=zU|0<-O>KsQTTmF+ -a5pm`(w=o_@cC3kZ800jSb_G)QswqaqbmM&6zOI-3It+a}-Ldniwmp>R^a6qt@c8f0W2B3Is{p$C{X_ -mA_q_!*e3H*mAAF(Mwkw0bh%^e>}RWL>0EhtxG68+FM8C#knS`k^;OZ>)jKDxLad$SA}r6A{@p{qN_D -Movzq|!|HHkxVC^N21GO^4o3kytJ7+(uZ`&dnbJp&dp5*Y;t#isUVZp-K6EQHYhk`ovpy__%LN4Q5XI -4nq3Q?IsA%fB-~q*?OAna$=izZT2^u@F!}(c>x^^^h$|M&(J>N&+<@*tPfvBZxP*XW*L{A+IkG#~D#k -g*y01X*tx3)2Re#P99OU@=zSpLgV=^Xp}b=7o;o4S7kYKu=HCv`#?dJpfoOvKu -~_<_I@8w*=%1eDTkIwf5~eVWBoPeRzCt)e{WLVD_b~}5+o2PB=Vakzk6??BB1k=N5AYH(ypNoIS>ks-lZeV -YWo-w{?WgDyyx5ZbO=KIIGvx1Q@FUTU%sn@K?lKe~+7)Y05X`g7#jG4V -2#b)7{7X$FLcAXJbFnnZGW^Znt#V0Qplf^!sh!E&o03{-0U?zIxz4v;2Maz&}y!X{khL-P={S4vl1ll -dqXC!1u<2YdUqhmKfJ7>4U8f*CdojpE;lo{E(-bhqpMS)_Cj!$H(M+<*qFg!U3fu$~^+Gc%c+Sn=!?7 ->f3IVw1vXUfpKKw!jG=A2A2^%zGV3t$f%p+93vHNEo8JW3_7&ZfTp2k{euVonS-0O? -MS;Shfv;Y@OlxchnRmQB1MJcw4)uC#;C~H~C;i>c#?h(MB4jZkQjbX6-Fd -PamY&H_^q=aaN-k^hli*MoF?NmTIE!(8Dyd2}HG^F6o`>2l5iO#~-`r^S^5UpM4HR;G!DZSd+;SGbP^ -H3`q7+a588lWW5dDqp~C#4_MQn9hTViNK36q&ro^Yi<|toJxH33^LI-W47Bk!cd=8c)--KoN!wXCdkJ --NCe5czz0yr`s5B28^!dgkTFH(5gr-WmxajCyIU?<6s>E+Z5fxtDBu^7Ah?C|(fi1c$HOIfAgGXEN#J -tS<|#n3mKh9tBPMr73l@k -_F6i+Rj{~C9@=ecExwK`WN|0{3T6%jY10Rfr*Pu9VHA-m?tih@@+^1k> -{#v6QUZRM3>N8;3@`rhxDw>7Upk=CEanfC$>y^VuT~^>RVvQ#=aDVhLaZRfYuc#(wE#s@&kFA)ST*ArYeGj#eK`Eo~_6I;W{JhVf2ljf$!+)kc^?7Jh_|t)G%Eq9 -YDh(x6;82dbd^|F~iZx@-DCp&UJFIyFC4&!~xm8ukK)>Gc=0F<* -mNeE&2Cud&*>;rQtc_~kN2`FO^NY=wXLctJ@f?=~8OaA#^#Oh99d+^1T$r7>L -WwO0RGAE;(WOUpI+I_qU%E>HonoUn}x|eDzzph7u5t5*UP%I7-tHilGp;Q8${RXcET>l!PJt-JJFZ28 -X_N&GUCno=A3!5E$D_?F7D;IN-M@83OG(wFLPqu^%+q``b|J-Nc5z`QOg=c3&`x*i$O|L?43F(@ -ICdj8=pZNJNyNM+h|wvmNum6x7rZCuVJs-zq|3EHzCB|lE4JDSN}h3O*ZPdyP~9fTN{Mz+jm1x;!Q8} -w>!)GC^GxY;>LMraM!QJ4O^PQVW@drH4P*_6f$*M%JApKdlC4d;vw4?;bgF<&w|JCi*CctVvIP1+g+O -C8!bW|5N7kEA#UeJ!rAtJH!%4^%$VJWWEc8b4A1@|<*s~)|7@gfqkt#)EwuY%x(HHciQi(&FWDj<{^) -nx=Y>6~8RE!k&b*KU2K)G|wRqnwSMtT*?L_QfBr^6q2JnYOh7s5~j*(`@7qhaOm-aP0CT~)4qy2iAw^xuK -<&vXe{Zfbes@E%0}F&ZbouLkByU@fRx2et}Ykglgr51bP{oQv2}Q -$`mIAuQ^z7#jgHBM!ReN0J?3o+ltT93l2TSz1l{N^_t(s5hKL?5d&aWSwNfQ`)pb&!R`}kka)1Y -**1Nog1Tu-$m9_@Z_jvEgkg1Li~CayERx`5GCd1G0ekQR?qLq -_U%@$1HoDbWF+KifRP}Y<1_dCjVx2%DC{8BgRi|^KZ7k$Wnud3(2@pV}bar2$AmRpzi?J4~eyRZK#7x -~un>(4ImEf8Y(Pi21J%D4NZBDUA-i}YRWCidb1g6@rKIQh1(gx)pypQkBebZ@HO;0=9iO<>60ew`NkE -Mx}nQ;ZwFp!B|-rgm8+;-e^5{TZDC&h>B*t-Bhe<<6>`-J9h7E10_`}jTixEo#5 -d!~ZK_w{e+N+NrLB7F~d?$wIzF8;FUAXoeFWB7XoU;hK3y({?i4|t|}ybx{(VSfUe>2Gm-dUyB)fl5z -yuKGpj4DR}gAoZ7GeWM6Yd*1#$PT^Dn-*=$-dk6GAxi#Z?6F4xRbd)HXt{^J!tir;EEmM0=1QhmrI=TS)V84HSC397A+t@Z36RE`HzuwLTDo -b4t@ofG(Fg!~3pMAAM}z&nc4)kjOu7&|HFYru&RqsV?vW1-~8n@Z1QLK?q20?2kVLj6vJdgB}-FUWDUh$RVwD-egfwI4q1Cn{4);<#Sg;}UjKI75-S?FkerA -Kr@4mu93Uy_xO)!w(z7m)H6a)HjDBNqO-#aw2c7kkIMz7^5I#+QxSEt+DDErv -qH}4VfSMT!v-Uu6SJt=2}IVG6A@zsws79$&cDFIx_NiHKvprC9~j@^lM^X)RF?S7l>P4v7UJQY7z4E$ -b0{t2MIGtdHac^vMj>fpsu5(}JG6Xc~u#b_K#PXwk=cM4zAUZRCIjCnJ7aK@+S-RIaqjc6pVY?GL5%e -GD+qSPI`a6(=UE@^=X9HE8}XgyO+zkeJM=i=rtp7%c6wnn~4r1gZRz3z(`VyD -aWIT8`4kC-|DM=gdb4S;Q`C5vnzY(&8*0P0;9ssNw2cfXO4^ ->oUN7!$o@?VhdC`89nvSuQ{@5r!-tzuEA$h?D6-oa)j)WBM7OGL8w)FWt!MgvVyo>w-u9QhU0#Ej^s4 -IXXIM<=N@>IfBrhqelyanP!Y}9Ip8L4Jekdp{^T6)NB^H`~!Qbzl;jLy8eC*4E||c@zXQ>-JszIXJDN -EX|ocw=c+bVPUm}tkV3ww(IwR0!9={PlY8Y5*;zb+{X97a?{Pzs?E%5Y)Cu(MJ(cfWxm!%K1rzj#j&{ -5!v2gTXWV-*mH}C$+9%OevjQ0c`@s3*Px3uft{t!!4ZZB^9|EJ6QJF5f!YG> -v`@M1xnkL}u53{#-CqdkJef~27mhB)IaS1pn4sloeZ9bpU_MwDAI+WzUWL`_v@BuFLBBs9KG)SW-JEM -G4VK}eG)SVauP_pMcOK#M7EUNVV>>RFKuoD_2LSxR&x@r%8(lz;Ng-d0qGs}yZj^hqN~C8FCryfOBfP -rYxu7Y#8xhnVZ>C1;rz3sQ43)KM=1u#*Mjg3c7@#JQl0^O1(OrG@WTA4D-Az -ydK90HE(!LsOff*KkQqWNTT?iel$ecN{7CwSiic&L(i5&+esMjk8wn* -L3fE?41)hYMM5c1sf;O -UQww?C6n|F%mS_*qYtONEdvjf1RPjVuMDbBWbz@(sTGgW#T_V@f06H@V9#I4~$T{S;#iUDsy9@ -41rI3Y$g{;(z -h!{3cQ!zmC7RtWe(x$cnx(0nA&H&IzeV>_we;9z2Qu`aiQ9$=Sqre0g+t=pAp6eG1CTp7tYQzBa%9Jh -?*>EsuK{cJjH0l%b*0yyDdaZ#8t8CiDhwfTBVwX%YA$^_L8=r8#5%A5f&c^Bfg(-Eyf!_nYDo9l(eDn -gdH&h>$bmD$o1%<#2nkN$lM*CvxYE)!|63DaNdJIBRAnTis!4cJ<_Ty$TD4a_?q3~3ajZQ>qbVyMyP| -p(7f?BcsJ}(yl%#IDJ!8>x{2W~Vy@c<<}x-N&_Ae=9WKr_EhORP7zdAuCQ6Pi@pXCT-R+UIIEfxlLt{ -pIk=R(4DSw+!vE$Oleg7NR>|c>@I=?<&Vch>hQz%TJDDq?X*grn_lSTdF!0)v5f2x{6yyfPRe|@xSZw -zXqNqD?BTkRYJdUtM9I|JFiL4MxljlFe+Nqm>M&)##u+m9Ie?k%Hs22~Jys}}Lr3f|ZhlzD_6rit;V -XLyTJ+Yjl&6-Ma^`I%)R-#fB&CXzOO9)MDO?WS%3Ua@7FBk=vA;R?VuoJRP@n<b~)m({wP0DtmT{L1s)i@)1v%EfHPLr)PD3gPWcoWY(P&bL1b($&iC2AvTOy}Yl06JzN#dy5aX -QDeD?;dqV+)U}NvKsdx#doHrZt{5IB`*z-SoTyC0ycJ@%DpSubHcM2u*$E1HVR)90SvGd=XWpO+p -xUkLssbWmUig|}8TX`cc6metX{C?8GK4@1+6v`=)9H@JP6yhLx|@+>vE0yk!r -3i5O=9LZXdGZ(PPqjxp7T?KIT|OXh)c5ldJ|O=+aJ4nPCDEgC8ZWPWQP~R!4lm>Gq)XdsJ{9hjb{8rv -No_;%wure(=!CB_G6NA3IB3^zIR7hp8*H8Z2I@cyn&?nO6N+uL04i?(DTaz~KUWiLqjm+(il5-WY0uX -IMXSc+3Qv!xef#JkS$+WiQW4z8-o5y;qJ491mW-9@XkfZ-GDZiu@;Wh_!*S2{tkV6QjfF -4~E6Smrl}N=3}hW>0tu2yF2u?CUIAIH+#v^4A&>BpwYA38z2cMi8C6~C11?bt(Sv5Cv90#>ngiwm>14 -R;QLwWUvy=C+ty{=m<(j=>qCc~c3pw-l^GLFU=ISq|DNE-@5v`0rYe9=o|DxmxUOnb!tg3dVI?^wOVg -5VD?5k0N~qxwBH-*NM`FB9vYH9HIFsFc#0aDZ7z7$qmw3&VH9Sbj5aJvO8qk)7yF_pRRCimtfXq;wm0^OP$=I;Y@pQyv)(+|NY -UvLEEc!al8oFO$=|#FihchXUZOr3&c##!Obfv1*RoZ1Qq7RhoRTrqk&}nz?u{^Hlf&{emOhz1nFG!3G -M3o4QBo1~1p6=Cjcs9?Ie#-d!)i|WxQ&rey!qWqF&pUfjv>6-`fLB~jSJ=)zJ(S5Bo0taALO*J7Kx}W -RT&iV>xJE=+tN9c+nkwc@fP5`8^<0uHq9o$`7|V>qARtk;^zlz40WO=W|7&X#0@Swv1gbsPZ -7DR;8cogIM|-4?Py$1{84g!+YDJHh8ebypQ3=Mq=xXg(P^fMO81)xVU_QGjVpySwSm -yx_aT+W-bG}XYo=(q|gGwuMVaXLwm+R}k~eJoZGiZs774owHmatMhU) -w`d~_p3L7*K7CJH(Ekis`WpxToGhV#n=Eat15I|TAv)jZxj&dvPVED56!q>>&)+_@mS-T)Gz5lqUp58(!H2U^LdQ&OvFI%ww|3a4j(0cU^S=vn!#2?7g$F~3JWXbp+Bu -iQpbFClxmK9J9k!2S_GVafWa#&w}1~~#<^^SKUDa3?^p}Ts&{I%db4wYm -hqrXElit(7u-S$t$wBWv@z<)u#7u+K`El=VZ=vboZk}t)7q<`O;W)cvZ+T0w7*}!VTQ ->M`=wi_c~sM(XyGlXkyr8P3QCD5Qkm(u{3JX}9 -7gha7%aC`Jrl69JJ)>QtiK_``9iEyKDx`>@A-;32PIayd}R -!#+}S{}xkZ9(O_Kkn7Ax!iAW>J-N+Tkn%4XRAQX*V@z4z -_N9pl3<%lVTWA(8+bHJ}B`dDh3PNv>W~22ux8kL!&IYu?qAl`yUXa7`4)WCf5ISYC1{{)#ik0T$@s;IPRk>kpmmmhfmM>3 -RkE_!+C)lO#Z*RItsV#9#om1S3ku(m#88Q6-(=f|u(xQao8czl_`2Z!$@T`QytATAGJ~wTv5`p+Nc}5 -sm)hfsv$}+|;DJpjB&r^8As{b8A(oD{M#u!D+>X4To+Z$J0F;s|}Q()Q_vj6Y?&QGCTq7P?94(n=s-n -enQ@!&mkr}d1SkJea&XgwFh}Tr~g29?!S0>le>BPo76m#xcQ(hUpS1lz5J=YBix5KJLvPB&%Ek1@4w2 -f{=V0TUJsY=JO8Lm_doSYUt8t>omC?vsA2r+aPE@`-IHuTp?0@5E#m}{V@OR -TWveTe6*{g#a7fZ9fFCIbnY!>31b+s!m -1#d&kJ8w~H~Kzea(9~a&Mt9e?kyGD_j@ZvuzsqV$}5urpR%Un_XmG!#`60V;5kyhk?JuFS+s{>*G&z~ -W|t?B6N#=*;rvPFK55i99+3NJKC@V!k9?F2jV}hf?~>szbocZ7`#ZM>{Mr5eo!bNc?Ee1j_CAI2epYk -*!E1q|v-5E})#^*t$TGjjeUYGN)eN!Wu$llR=p}*HO3)C=t+6xDUNm|*9@C@a4q5H7mMK+tk#+0Wq}6 -l4&}U@=8QikaCE}+8;OL8Y45iNNA@lWON);|GN2UbnkvA}Y^39gF-R+T=6@46|)(jd%%em{~(-AKUE( -TQ32&tDIN1nzSn0F$rI#j9rv{uEgK$2!co=neqd!1u?0SI@-NCdll&hm+7?+Db+t_c>z&?~bAb-t+H -hI#JbC^!%-Sy8-m2+NuHs^$v(8G-q0cPAGHm_vqGh{VN}_LRh9$1IZnI*``#D+05tyI#=O7W>o4-CBT -9C&sIWoK(C3nM9iWlEb{*PL#+_ik=^%+x4l&bP88!4BhW4g{Lj1#4=hsXZ|3b0a$WH7r(t!+s+q1ZX2 -55`f8Z&_=Fr!f~w(jbUX)Duxx6orgjDC4|u`xP@Cx);Q~D$ujC!dCjE@9`~cu@ -uEW%zG;ohtpRd6uaDg@Z~x#F@l)R+41Ig8Q@eH!neB>n*c*+CcQZbc?;w;X`#Ao#1Mzb}P3awullPd~2CfvepK` -AOq3?zna<4TZ_5SFdhPUwT{FE`g3`B~?M4W84` -%$`+y4e&8s9IoooAy6`;c3*e?$JILV&>RfI5B!R69_3{2?EQKx -SY;*V3hu6KT71R=P+3I-pv#ln(aRrNLS%uaHr3)`oh_w(VsN1ypbop`QM}(J_gyo1rwEVPU`M-VasNx ---5>AloP)yUwUwium2CL@@QO`bstg#3@=@b)yGY*i4FNjZJ$8=)-#%C8`fV~@3Yl1lQ&4;erR{g$an+ -O7!6j#EhqJB#)K9f_h-GCX0svka&}o4_?pyl%1JE0O`Tj>v23OoX_yr0$8+faR9zPMg<+AR7<`Wzf&Q -E_tSL-^eDykiq@l}ABKuH(DsLE6oR%0uPP@*2`fd;h{z-@U*X#hRFX4rJ2Gw!^Ry*#gLTHvPPJ>zZYzS|;iEM*6(BP+UI;aiq8y_ -N``QrWpy!*5#_V --_(yV(;ZDF9wHFKg`yKcdj1pIJr;JE0?4H=8e2GgM)eA2YMTvKD6jhv7eQ?P)Yhd%SzmA$!^qAmGyW{ -D&BarvnI6C}{|imv}am8kcPBakV*g6}pm4+2^(Cu5pKDtaN*vc>8>uS9}CUw=sGDrV6KY3Bj|@8kcKi -ouL$WGO;barXWd#6b0OCr;g7$yffCczNkH%mvpIwK+T-=;d#~-?&6~tMDM&*^idB?`h~LRY>r-cab=( -FDJRjaT-WsFEL}=3%;h9I)Zqn40jHjfwN+LBIQ4x--Du=$6h@Xx;e3YvK{Wi|{trj<9N+bR6W%@}k9& -Rn1k-E$n_REAeE7|c|Kq6?2e>M3~|M&k|-+p`L`?inorT_MSzw -&!i|Hpr=znrYuQ1&B_BSaB<>un6}HIkJ2Fk0DrJrL@>&lcDI+^=v0X!5Pqmk@jOo25IZZ=@kc_tR`I -Z6W;@PH($WZ$0>(rO>;^5Be*sX`0+MeD|G*w>Z&<^n9`tkT~Ai00ix?ZtP)WHd}yCzD4i1(0p$hA>O` -(@0~XeRJ`?w_Gq5?VpF|^d4I(lj(f6y3I9Sso?~CDa4>l26XB7-uu{Ok4!++6UchaU>DxXe)SXY1I|J$Vbj}50640ZU6l+f)50uGV||_=`Z3)S8g`uk4% -O7#D~0J$88k=%u;Iq`P=Wnn97z&BvSht1jP!mye3{n(+B)Vdnnayh2)Jv40eh$En^S-xu8Gb_?lGTpuInfV&;kyy@1)_~Y095 -|gjE)1oH=`*{rYZDx6+==LCgMbPU3$Vu4C$wf1Yv_|OZSPi$7V+sKU4sf7BMe5721V-Q#>dYDTo%kST -9yK->l!~f9JtNS+4RoG^uu2Q89^{vP>8_cUMOV7zX80#Qf*3_NEsTeRe&HPzED@BHhm+|C! -pjO;4Nj-YWf8LfRdPsnTNzfD5;|MCH*-zCU*h20%H=?%~MPouP3BLBb<{^_xw5dP=KeXk9O63|b@WOn -)acXuU0|BK4@=E?YN0Zh@mp~@Cz?+prX6BRVug6p4W&#ArlAx?ImGW4w;gT4pjsrS)agbgQq?i!)@WO -z~R*TIV&cZ*-m*<c@!A$M(hZWQu_^}Z -^dGq+=c(szaj3sKUe3k!9LHOj_c3#vJBxz^DsvQ49A~@=?jiBv+!wA_L)KcU25Ji_GBv -ePGtNT_dl&pY)c;{A^i2U)4WR#x9q^Gx1}c -vzMH%@R0t~LKMQg*bBApcda+&L~wOR6PKD^L9TCRL?B!%Rvuiw~^Bvob)I&C)xoP_xMPk1o -RS5YBB4&tV=KL%xFsx89Z%4(g@3D&}!s)URh4IpB-cfq|Yq%xu2!p02=WbG-hkxUh<)Jhno?5s-^LT? -0^{Ol4}cAcmFvCGRR8Dp}tO38_%A^5JDZ_ai;|i)jdOGlPc3^h(5@nUls9uvLSp-=wRmH2#pUqxjp^U -ugbU_<2?4@DwgiFm@0OZgdLlJKY|m-LLXD!1*^!qp~#A%NX$bf%0f9 -4iR4r%|9I($)26rJ0%Q`sn)fd7{t0|jOVv}Rj}Mu@7+(q@`!-|om+hl$vhN@2U0+ -h!l`MO2g};7$_tzW*u3c{CkG>-wk+017vXP1g*p`7L$d?S+#<5b=o@|0;A3P`>!|w;R&m0VW)Nt@|qt -mYws`h}6*hBUqs8po;GDZ6G{#$^lHQG)bt!nZ9TX6A9di$eX=r`E{FhW*dK?yf4T#+bc-V#~0tywoX2 -{zm5dTR(^;v4D>kqOh#gUQJ6H4vx8nwotno)VicIIv?xxH53?4k3pDVaWUOsL8pS$7X%GpNqzpc-O3D -5Yk;tq+1Ahpa&l%xfAo8Uoq16s_QI|FdEgMYw(MHrv|Wdi*CJ2*9$UvXB?hS2PyI!k~9t+rU5JGKTX@ -iPO1=O9?TvVqma_7Cot7xwdCq?s(Cpr#M%riDLK9`q0U{5$T&5S30o-OsD!t8dffP&^KKG17X9hkU~L -uQl67tswOSLi#{1#;Iy+@dd^OoPKxeM1H&u$0F}R=E%-HP3c --I0tABa;U&Hm6L;rx-KeadAFfAqaEI5+xNp&Q97pk${e`X6}@efUWcvn-y_AvUVh)ol_gxm(pd)XP^i -_%*xy^G+y1LW*&IyDI~JwX|Qa?0e(}{?xlhz0VczOk)kAhQ?X1XcATuuV3n&F5AM*%k~a5mr| -uG)@odJy`45-anUk9{&$<@3H-;vUds2d5UkYRV}VC8QvQS5awn2W%|Jyl;&jS$*g@lmSpGY^My`k -X)dl2poI#d2$>@f&;nH%u1%mPu%_=i{Psd5@g?Pg&YrX!#x`hLf;SEhcR%aiL!u$vyTWp+lr)zdym#) -gk7=b!Efe?9lpD?xUnS3&~(hn?Fp=EF|U{;hO5Rl66}Qt) -f{kcaghbv>nyNdQRd(*EA8WEsTxzH>YK&TZ{Gx71xke*eehEj2tuVFt8}?->F#*TE{|9>9!`uc5Zw&OCbuTnB;Jv#f6Z%QlyX0<#ul(%y05Wk4r1%w^w9E$G!;#}2TmCvRwb%% -OF%ra8ey?M4x@&)n4}+5 -lMkL!SpC&Sd+Yz -sFb;QolEmHXr^CV2_QbY0mw%f_9F{$u -k~BgEPKPu|0Qdcag6w~zYoB9F_e?YFA#@4w2uU*<2Ngv+Md74(T3#QbtC>FUR?lQno(fYo={n)`*!w; -?e{)ZiHY*G=TOia{P=>9}u+pC4Qx9#T!@!gF83*ZV}V84%jdUSWR@`~v(98VCF0Fl&fB>W6E-qv9XWq -6!fuy9-y{03FNu`fUOK?$`CF2m0Y%`OQSKm3``eWkk8%1(mx?*Z1e4azEFl{DKL8E#h?%>ZaWlTo9j* -lUoMd<%pV87BtU>AJl3`0R-c)bLuLurt?VuIi4@9gLf+5)g$ginBL=_Grh93V^jJrD5o-PY3`Ldi24{ -#*L{6pSTD^kq>;xEB_k7!I1&9|bu=8BQ++?@ak6D;jXPT?z29xm!b&xOry&rFn>-?<2o%YEhZ-_HQHt -C+CnLR}ql`pbRdu=fEPD2~EmY34@Z;{b9r&&%pG7>671%z2hMEGdk<6TLeVZrp@E!$H*Pn05CC+zHx> -NQA9g=Ih=#9o}r1&&$*YinH!amIq3<)nW2LgJQ_n}(eF)ln%2{xaRqOurw%Pmh -0nK3Bf{X5eXN3IdqDvMUi@w}&g -x=R4~obu~qpEi?~d3NM$E>9~EBtkUDk4i_9!L8Ka3Vw#GS+2O4>QB*iXj~OXS}nMp0laBL$GSQ9^?4u -T($AM+H`tv?0fr+)T5Oc1TVo3`e&R__h8p0FU;E2eppDBuxcXsh{c;j6>&!IgC2s -@cb%x*Po)b%qIv3cbY_-UVyR%bZh8;C60o?ZaU1;Z~7l@`?;-|}}a&#x}EJR%BENi;q-M`@JCPt|&2q -A>VPY6!ebF`JgDM{Wy_%skSi`wrgDVgFOdD8qwTR(a|I8myPJFTrWWAd!Ox1x47XJ1v#-xaHdWA?_|EO(*(_u_$B)dR -zmgtWEPam-{z4AO7;Bu?OT0{+MF%An*Nj!2V1K^D6Nrnu0TEQ6yr;{BkxKrP3-k474juY%hfGz!WBlr -~I`cpH$v%t9A!{o1$+0@Qb7p#nZj1T82uEnr!NUXEhYk6&g<3y^AH>1;bus3SNb -SepmyM(*xUEIy%DqTP{N?tANItLdCRYmeKdNwP=4XD{Ma|bMi_n^E%4)Tj{xt!T(3ub*N6GV*cj&9f~ -(d0H+4juk3@49+iwZm*GE1B9v_0&eXZL&+8502PtW4l&c9@G|Da?2*)$rVjE;v^G23oje5{s-g|#7kn -c{t}@AmUQa)g0Tk}&^eF7W$?t?(Ahwfp|?%ZAf2cad>f(c2f>j-C~^W^7|UCel8u6z-OZvoH9UM?z<} --D;eTdsU0_y&IKn-$1terf$lpZVdcodXs#(G2>x30lzhz+TToViyD07^8V~fd@Wjcl`(m81n!k^bqmP -0cz)A3lehu{b_wV-&!LY&d0b$Bs5@Gz@oyIae!7ZHBR^zc?a#-IR%N&p_n%9ovPRG<_LXEpeQ$w>cTuq8VoZ9(q0eav8EQ!}2PCJB@{2U -tMp{^MkXYEhA%#+otJq1N?@6HH=LutKZ73)F#rx{A6cuh|O0q2BRce4wSqtk_RXN3jUMLE$?I -`GNuBl9?y`=>^e|W!iGUnOcvNYNG*uwEW}l)nn}o2;{bvcT*+GPAMH5upbRqY4T?$!LAh@QL-NA?fdWM5=vcXR -jO?%)@j`TyvJznRbfdl&u5TF8gJnYGLs0F5n$NymH4?ev5$nhx=9+v;%*-t;rQRxws-);7zjE(r0b!*h&#~;5l-1F9zr2mvMSNu}j1sEW@ -9M0`;#a*|%+U*D+KLlPsgv#j%qGPC#ljbUmH)_v&BW4~wUr%HjNM9th7;ahA&uhb6sPafE}=e -3)i?#~#_+wcD?5yOE;|ETO|e3bo|kFuY^(+z@c0PzBegwtZnOJ$`{SPvdq+Q( -1Wsw#A)emywCpn*q76Orq5@9iyNK~ZzvdzA;J(?zzG%lc;m4)c9X?A6zS{bJsI3AjQ!wU5NeSMD;%xm -4Aqdc0NY&?hI`~v)?#N;sDm?8z3=Q}HynMKv{EQnQ^VKflL1Rr?SmoH8SIieZoYc%#UO#4=_c>WgJE8 -$=@R#g>%D_j5+R4i->`@=KJpGX10Ndl?h~Y|VN-v5!^myR8)moRx#-l$ZXs_S9P|{ORRI(S3G?Iml1Th@lgZ89YlwC)aEA@TvLe_>JljR^}bK{~|79l2J8a3(1B4)6 -pBe^8xjGE7ne9UHjkTh5VHk@}oqPN>^EnQW}PDwT(?rk+ZCBcyd|&&$RmQadP&>Euew`B?XVYd{KyTz -K1^9_1FBm&g=dJ_3;8fpfvLAiyLF0rwgN@A?|Hf9QG_axuPp^CjuCJCnT4IVKR$PDNHlNv(#PReO@6& -Xc4-;2hcsS3yDgfOa+zYBT$G_wC$4~$I*=ek|7D=d=h-3+yodN+h^C)ooa<)tOC27c@#j|ATae2; -yX?~hfP!DUNc#?v;1MsM>(p+a%lSs8bIgot8IH(56gu%Pozw~!iGKJUcJ%<<$hqk{lm|{)>GT7qlZM` -nQ)=^XEKxZ1cgV+tenvR*gpJ>;I!#R=KS41c`E`?8P8oMt&KnUv;UaVHqYDF0Y9H?AZwIye|NN~K%-8(!Ux}grH~acd5&e&Teo~SFaf$@7kNPbIA{0u&B#A&641q8Lg -D42&F!kG*Iu;(Xw>Wk9kb`}jLk@PqW7{Q<)Nc|$V%5oqH5xx;ZGZ0uCywm7eMdfMqCd^S^zcCM2XXY+ -D!-Jkh2+tI!j3(Y3=YAV4$Xj$eNFi5^?C0;TC)j#-AAZezComeVdQ5qqr_akKr9OJ|9Lz@^cF -KNxX|bS^3a@U(nIuAO-rDF?9hxV#MTkJ|9Es(GKU!6-&-s%6EyI=k54=j-Tq+lD9J8c=Kyy^YCh3?nv -B6h+2j)_(PfWU`t<>S%BY5vwX4K_ic0$H~R6-@y^#O5_sx%2nT#otvB|~{EK}1mwnczVWl*V6ggC}&E -T`mevaMuZT8DCMKpf$ZUA(nc8d8`pzH8Z%()%I^%fnz;{BQmt9(Kfd-K6b69Lj&FT3&*A+R> -S*Ibg{i)IWvm~b!QT~o7QKGc!Ug^vG2Hro)$%K3Vxw}!rE743+c1$>O?dM!pqxQ&>vcuW=u3hRTdCW< -q4}PDo05r-?n)c2$Zn7!c`kcMke;}TIP>@wmNwyWI=uk!Re>Hn;3RCo+i;t=MeWw1lySl~1(rX*#wkCC%Ucpzajd -nw6;>~EaCCrXr*jv+W&OlS&a!y8`shyzU)_87;=~A>uNZit==<5~CHFNoZ3^EpRlN1iNj%aJfE7QVEv -qCm4N{W$Mqmo}rW4FCj10OU^y_H`U@77P9{T3CN%4L4Lu7>lE4$L?eUbp5@{J_&J;dxs -095yIcC1dHxC?Mv*W+X2qn-M&M|M;meb~6;wn*P0ACkNmD$_bylVA}dqvVI(bBjvUgGN|$U$5&Ua~$Z -TtvAco9NsxVopifm|%mZPC;POJKGZrTWgZ4rs%K<6H%$g3|5FYNH6F5LA`VEBo!T&veNo=&r9%rie?K -rl>MS*Ac)qvz7=hLmTjx%A`iLELVHhE!tnk6Irm%hYz{mB0yp8+chFO~o^+A(o0V{qItMCTp{P*s3Xm -*^>$|SS?v0>ZT04C2-+1|!n33ZY$Wvb*MB%75jc*{h>baKn+!-_ghrS7`-qthv*0GuVmiEBp2T8;4N& -x-uC4f1@_%k4Iho?5qa?fDy2iExEy~136OqsYSw10H8L(1rT+63^0W(aI9?Cok%y3^CoJfw(%?<9e== -joqHGe5Tlero(@_1u+x-71kBsdsnvim-mB=N&*=s0pND_R!k~dEW9>g@aI>;DO%;?G4LwtDces7i^2h -m}Ka(eu=`dEeGQ&ZN?KcW@vi@XP9}UBrJ63Kv~RO)8*2p>3zj5a>hKkPRi0CVgLEaaaBRJTZk;vQ|O` -QRqmhW7r^9`&enUyGWy$`UyvtHD>GT|U+}9>xz>%wDMJSX -aT46u+*lmoZzH(s!D-X^Mhm@RfQw>%*D!qJp-8xnrLfYgsgdFiL1+KuXju?NziH*nL&hG9=$sv?{x~$HsPz|$Y5}HP$lVYCjY()m-sh1aa2uMEEvc)N)-_vvLjtXOPL5M=HI##EJi}aiYKi9E -v6u^txLV&6ka#ZE*%o*X7fv_k@t%qXj|hV5PD2^6CzV}e+7qt(`Z}@iYK?No3ZTdS0YIjw#-Lq+akd+ -?)X~0ybheP6=co5J<30m%to9#}ogp^+dS9bC567y+dA(J$LSA5iVmEy^jwq1G6Y#-Xyw)DfZ8b`HV`H -68u`kG1y%x;gzTPasZrO+M%oPvQQZ>;o7%*Fd)joqnB)x_ks|7ax16qWiQP|-H`Tr0c{!FjlsrU4(&KsuLqqf_c>LT~2pyi9ef%MOphk6 -~ZVfUNyw=%2vZ8lmZw4OQ&kh_P&KCZwxp|1)vcli6?rUnmm%M{z;R84Bfg^_WQ}o6&e!UdG__iU2C4caR -3(Wbp=XW9H(CjtwSKF!_2d9(jZ(!xOW_sO>#~BAPp@A7jH|QM=^{P!5a#Vhx#_*;!O12R)=qNvGRR4} -R8K~ifR5#15$U~~RkZSpTCNXnGcRg?zu6UPj)nLzuH9MZ-r5;_CtJv4h~D0_>%B>7b*)T5dcQ3`NCZ( -bF^oS6f;L*}>9&}0sc?7o{K|DJSch{@%EeQJU>DTV_sfchH4wFhIs;fLcaI`-uQ4OnThU?)c?Io|V+k -VAK+`WnVrjGbrN5Q^4ku}umZa4K=@Ee2Ei&Ff5jWUMR$QBoLoJ9N&rBk_bfJFs*|dG%7e}aWR|Ep_mC -O9nj%D-FM$*f-)xeA;8PH-{3~Du#f-`)A8!{MYW+M=+q#DX4dE7&ZfCHz#!NBKD90InT$>(~JF)tdUG -++VM7pIdnG1&Avy>ugJ5HrFrWJT3*Y!1c%=IHCnCcJp-1`@E@KW>Mj>I@4{m964_16vU>V@@?GwT(NAK6lHxq%K>q<+Cb=WdEcUQfJmIhK{ZI1#1sWj=QPKe78Jd|c_jQf^OfK5E{PxBrBA)e#V` -(0pqbBn$-g)t?d)Lk$M@wQBmUg4L3&{~+nBI~FSZXYU==c9bx%mrqT7FZ&^~Q}KQ#U$Rb|cf8`D#kwJ -{N-;|t)i9tNS%eEv> -b)f)-fmD5wNd9i`%RnJb0vrOxkqO?Kff2`#ov|mnNYp4B3-l2EC{&I396P8UHArH`>>kMjs2#q?x~q& -7}pghXT6&h=es8I9OMw*oE75Pr5Y2xQvrPcOYB_Kmp -R(6cm%ngfkRBVp7k17eGVu+rK^9uKKi^Y8BYq&yUtn{26w`U&^13_ev;zyEq9dY_nCiM6_rU;Dxyk+A -`YtB@cLe})l7e;KCF#vo7c%ZWa!FoOGr)X@~Kz|}+Lw*`|Ww8D6FZm;%mjq@3`fc8b|t8q7w_E`@0w( -7Y1ydur0cwl-?=SL8E@nlov76ss3(n;Fj7KBiFTt9Ujb5!8X2-C$|9xB!v%xlA#23&=w_XvVAN4S-i8 -pcG__zoyn(#0ed -z@l+Veb;&7t$hK~%bBwwsLt~9gY(-U_GE%M3({lJwpHaH1sgR@Ut{S)R5C{q#9K;ie{yS5cXVV9fy)W -5h6Qw^N|i5~@JI)_ds)G`d;bSil{*0AilNB9ATkp=%40r%2mjabt9;zQYWqDx_e;B>~QO)=w -CQ=l+7FkFX7<>#P9x_+T1OKb5&4eh6X*;0L}@2 -XEy;4jvyNK;l!1v*Wmc7na2RYG0E*SA~lf&89Jh^C7k -VFnX`RF9+xQF9E_kI4t}|n_r0>Y___tM<^?TmppFx1^D~J9eh^D2X%W9`6*h_MYL(}yT9>(jQrQnzXG -!TVn_YP?`5BVqZjv_krF|!McG4r5--Jy7-j`X6?;abXc0ct^)EpiE`Ij%k^RZoky54twF5+D<;QL0Z -U_ye#;Hy!`Qf(Q3#+kx-g-q)86MfdX+qAnr=>o?%MN}X$j8LPUyurFrD{ydg|y*A>wElsICVw_564;c~yc5o -r9BTvD51%LO=)XcXMctw>vMnvrOHJaufmcC?~32Zuhx}}NX3SO`9TTDZ&=v6SYqYlReidf~ASzBnla{ -xk@vYdxEkEb_WOgVW<8dK{`)10A)1tzA;@4u;SG^Cti?hU0C3#=@hW;oRbM$jsb=WO8kKB|cps2IG(53%@G -;+qsoo3h(Q+}b-jw4Qj%Zn6<(Zr#6K}XrcLn0s3x#Dng6I@79PE*BIy ->|Ygpi(?`SgNJ8R2j&_v(NKtkfeF-3{%9V&rAzL9~_7v18*2yl(KuOw -7tjOo-PCM0HB$gF_LAZa-(zPLVBFAw{E6$u%ks5%ajM}F#gM -2Rwua|PsGZ9M}djrL5rXGaBV1;%h8hyL??C7E)%7(-;=uzeOf*8QQMkxpNXMmweW5Mv7XN~EI=#F -%l0%BG!;pop0IM?zmvWn(Odwrhc%SG<>;*VUgBO_RTA?XS#}{>hERcK9%~2?;S(+vln*gK(-OE!tv0k -2oJCZFElrSDoR?&snZb8tZxkdpb2k-Dr?!i4yMc9D0B(!@UcXwA8=bjJj%JtrXCmLzs?kn8^1M0496W -D9G&=C+Q?z}#Nw3P?jM9VO$o#GVfoR0GES8#H{8D3_XE~PP<9YSvyt?(WsGiM}0cuEeI-HFskE7BxSz -1aQ6Mape0uUh2xa}IaFN#Nn<}|et>OIhJ_(WmmDnjQ{E*4TXLjtE`ovr5Wwc4jTMV9JD?)VQd@OkNFy -~WS74`pIcu1{Ufh}S5KtO)q+GK&*%xN+7f)%^;ORVP5$pfCHOypO8;p42dMOuFAM@P0!A=! -$Jz*mAP5QnzD5Yf4_{Ckeu{!W%$*Q%U?vnhXgCn^!w`xbZIJLU5cw@s3b6wZCE?+z3O?d+33`Mn4;LD -Bba05_Vh2G7`jI?2ikXRnmy7rl!FB>4RYYj8U-9s65g+ogg!*73?C@ow@PWt(>d>?cKk~Y9`d1hpzOV -3u#dhR`f9i#TPb8L#j|%4WLsqum#eWQyt~az$r@0t}^7nKWb_zhA$L}Kb&XmDS{r5w)U -S6O|fbLo$?iRr^%%M&^PeV6=bb4~kw -5}|=cnV<=ic|?`*%22^413GA``5enrrIG5D+AVWW2T{ulLMBWA!_Gjwdy`<2@q);YhKCOe5^F0Y+)og -0iOj&eO)0LCjgO0#HF9L$h}7NT4B6(X39PGk$}6;zYhOutIHAp3bHgL%;bNqp!+frupR+hYz4df5Qnh -(w*(76H&7L!j@}&WWh&!+JEY>$@s!>Zz-BVI@v}(D~hPIO>4f-~x`b*B7H*CIj74i91HUo@F+LeM}K% -(eJJY@qD>N{ez{d!#T9ODIyH!cb(1(T82&<$VJ40%mufpeN0w$r3OM)!Fn+4$mlVrZNE)dY`NucyO0$PDxTLU@D0wf?f3{r?|9rSZ|CFM_>OiIPFtbKEM-?OALxCh#9n>4%JTU-Vk8ec1 -XXYluh7M7W6b&5%iO!i~LGoS$cqz;24>-x@&yM%J*39lP#J9n#9)4STK06~z=WD*CtgqM^;#Lf=`#XD -N0K0-sv7Oo?Smj4W#XX9|d2Z}($Cz_#NIrX)NKZF9;A%dbk|wFE=uSF1R6!-LpPYPCU=ayAFBu{28Qx -xNa4`W@#J_vUZJ0}W-P49^?|64xX)Pmek%25Nlei!Bf*N)QL?Mm`9$aOCbv!P;Emw*WXLwIz3B#y{UA -xFl4anUq_m(p1M2@_s2Q7umsCfWn{#Y++p1BwOB7hB8%=iX$NNu-^zCxI{RJrfuUmeDN#9%jM8QJBBnIxd2qjP -wr*IMke+Q3Z@IVrW{#$aSSnt>Zq>l747&>BoMEp_xfRBhD3I7HDp7xYNj#G(G*bl-!{4VK{xV`@gr-v -Jff(};>_Ju(86Sl*U!`%b@i7tJ|XLxcru^{-b1o}w(ejrYm9M}q?4pF!rYVEk^aCd=6t~d1IeA#i{4u -GJ~u#YiAA5xd(@G61mm&CEZpnnOEByeL{Cj8FFa`6TY=_~czYxM6DKWC%lt$lI);17VF=c{p7Ij6odm -eVai)R@T}9UB%{GX|)-iUH2-cXkx>>#<=5e&kxeZ{JhEpby`Qtsu%4e@Ox3c6hY$-;XK$W#LQO0KR2J -vv+|rc8lG(*Magw4K_Q^(w!f_jI!iah5oKy#~;T4=U3ek=TP?ew-HYWJcH@%-=E%VJ$aA52R({=)Ne# -bC$k|IGBr1yHE(*_jA%OU7QEmsy=n^Y>CiuKIvSgo{p3O3)Nns*7ZqCU8a16*^a`o278&C3|(aowOpqF028TPD}E3TzNTD)T -`RlKrQL7EyFyW))=h^Z=Dl!L9lqC*BFWBLL>L~-~6iHZerWv2~JmN4O-8M2vF|w%3r{RCg+=AIq8X;w -yba3SfFJWEnXNH%*OozHfNgb&uBs@tnnU=rwNYLOqNeFWH?Md1K{oAP>ynq%iy*K1(951v7vmhsg>NOg`-SsS!vw>~cB&4BU8M -WOr1c+rnruurM00BR7vMXWy2102&Ab)<2a@)U{tbzXQ2K~JJv`<8XzBrYxIz`W^(2GB>ce>- -H`FHoNc47;)vwg2#?W`wGA{X}77;u$y8f>A+h``w)$dmdVRehxqs~-?<;K~54*l6yPS_=~MQsInFyKs -22D3w@WYF{bPYLnA=8vg2Y1H&oT@xE{(ua$8QmqcFB9$lVD0+y#FzIE?{ysRO_^y9c`W8%P9tDh_!U& -i9@HC0Q-(>X{_V_?xaF#Y{+AtW1mJB+anUxkR`mx_7EgJNNW-3~|AFtf;3Pc$Lei~#{r-_ybnDvnC2MCenNhXEHJq3=y>l)I7aX|M!L7#Z{f=a!Dr|^Ez@5*vJ6K2+=4-a -H0RR_O)oUZ)2czkG=HaW2K~9k;}#5BP>!DKuUF`fo@1JsX|AK?nSRq!M8`9O&@B4(87Xv}?Hzm`;&xO -QeJ@tszX87rBoq9Q9_~{&UjLp@GQ89exZXnX3paxY?}q!{cvOThf(6oSYWr_tww}zT;{(wYf2Je+Oyv -Tr_o>|kM?JD@jzHj|Z^OHmfmp*KqI@B4_?koV5cl837p9EQC=`dP9^Tiw+&|bF-j}J&^9PYdW-Ol*4e -|Rg@K5Ro`?h|K7P>#T=eyck`>uzvjYQn%?lH2u8uzLn#&o^I-agCY19~=#>hF`$x~q4edKH`8 -H`txxlluJ#I{sy-&iQ2UyA`=zhqaWj6wFQMB=x$eWu;xijITRLj0-F3zaOq>x;QIJK} -9=re-E*`TSC3d*GCfVXqHNl%^$NXP9%CX*zi(abjEF{GZR@mL>!FPe`nrf$ -tRw)ogW#(c?iMrwuX{H{>8_kF~;++VCNGx?nK60CAD+8RkQ!nunKtRCO+(uNCH0*EAcOdh`Mu;7$md- -SD8np0I8R4O&pnTFBK8J~ksD8xy!Wtm=LvUlb}4TkcjjOD-u-}>IN6y9g=?(i##`WN^gZ!9o}2mc>ZR-| -wd?Z`_zSnwA^F8de*DVH9}}a|6LdK{wpfEE0xTrlz5~OVzekJ3qo$T>q(xsY(%C)hdNjSdfjo -2C3wiXRXHyMoJ1a3qpP1WQ&!^=>H0{|r=iJ#qoeZvvp{{~-m{*x2G%Q5`(q@VK^@!twjr_|y9B$A{2GQp4hJtR746u)RcA3+WNM@Av}Dfax7f8yX#LXu)1ZG`kM9Q%Xufl)_f`peZ*;?Q2gz9cw)XrD;pFlvsvQ0&NnB(TGH! -akLyD0Uc3;gKZl>UbQiz59Xu@osx_pPX7-|B_<@W*}?_?v&wV)|-`lteD}pdrg!8Fn(*@W4{8nJd%K^E6X0K1Co>bjFF3 -mKVb|pZim9!{-z4Zd6E4iQ*Hi!?aM8?rD;xX`;!AT7qbt~RLYf;lk2vVHYu#!ut2Poj2Cr`?c;B?gU{ -AN1m#Wd)0Fai`yZ&%qu&=ErM)FzmrIZzPFS{-~=Y%l${v^t-xw_`|rXPF-kN(-Sb;L<;DVqZdGe5g)( -Fh?joy`g+wVP?6XM7!f;|kUaK_Lw3J99(6o=>6Y&LS*^h~BH9+wbeX1ZshrW?+|HFCK8=KMK`1^X_W6;rw{Tzfi!cIE{MJ=&m(IN)6v9?Fa=!HD{jqur#7D~<^W@kpuxaW -C2S0b3(8qJt`-p|gXsJdRSZ_lCdy3wtW|qsh2-yLb7ct(dNaJ$uFSj@TG(ODD$CaP`!e?$TDJ63=pK5m)*0& -YH8#Rzf&mU+r`v-|LffNyaEaVUGM{wZn)9p1xHo&(lOSgI5r@<=kv}by+XZYkb*;TwTwKxIz0pR<}jU -F+@i34C^l%^PYvvcxeFC7R=O^Bko)M>#EEb`}dMl+!sLplROo0{vG+-*X%uSvzT}~xl-)aV_dO5YPfT -NXOSSb6u3xgl*s_JW>n!xk7ubecy5FtyJIhLVMPy5182yL@l=50i%+kyT!vh?&-1BwE50$FR{tDYK+b -zL3r{>G(aGzcp3e7Ytem)YicEoJ`5WBSeS}=pcWIyM%QVgl3PJddBTjd8!Ce86@VBmN^ZBNu3h5-GJ= -6}hYB@ahn1}xpwMJ!yk1I!C-Q=&O{930wGt^0~*3ifpBThhD)Coljrq7%cIGa}4a}fh9Z0!8Nmd=bbq0VZ3L7NLnR**CrS6V_~ -v%D&!78!01K$sB}oV;9Gqj){t24=3kEwUv&p)W!;%PJd_cR|4cuEE#Ws?$_e1Vuhxh{ZjX36h -!sZPa_IGv+R$S?SPLh;5O@N}ie(7TvvGt7_kV@mp>xDY&fpE&-BGzDtAKs(5xEP*B6qF9hE017#JBs{u8jWdZ&(S;0Yv4rLh@1}ibz{7y1p{kd-f_cc99jF7H?;)^e -fa!@MXb`ICfGUbMhO>6IAmo$?*`{I>Z|VQPv3QHa8E0);>dgg}DA(ftri -5->y%Bu;`5hLOKRM+k9vE71?rHAEgMcvNt3V|+m{5&Y4F`l20;{e}L%WCKMG%o85y>2QhefQ7`53fu@ -f#3c{P4*0-J(Wkls`4kcF0EdMCLjL5MjpL&cf*;HpFmyzmQ}C$5*n5PL!)Lq?28KR=!bj@fj-bA@gsThP~L!#IQmi0f&4{M{}LT(hf85)|1R0Mx+meuA#xQi$=0yZ`zdb^|F>>hK;8KJ*J -``N*(o22#(^u?A-vnE4gZ5YvZ4sJS%+es-(hOEB(M1aH-N8rBa2J^tCDlH?C1wo4zfn>K7XmWxcq#qq -!GpZq;!8OE^Zt>-XHGL`;Ph!8)x-xKK<&yImENvUqXD*ESxkLH#m?OD>jk^ -dLeHFWA(AXDdc0ng@E~|Zoc-r{B(RqRb0-MjyE%ElgTUmngDM7!P{*x6wFGTTqjYps8|i85;ww(nQoo -OMK#8(Z%C&6r)(c6pUUGX{>tc!eBpM{C9_>&LSWtp1j?iAkUF -Q{DX9s|Ul#ygMh;V02^E6r*a%;CwRg@zl#9pLM9-v~n`N3s6VIj2=qm1_nXc6h^i?^x);&JY*h0Y{ZJ -qv9#67Gqnz=qhrZNhU)2~u)MC{I_ndm{pv-fdm73a>iZHtat|DFL*cuucNXp^9{sE}LE65xcg7O+6T$ -v0)Q*db~EHon9J68Lkt-fjP~!*Ul}A`$d_6Vc%-WEy!N%xe8&4y&Dy#ccVRpV+)UqJ$a9Z+Cv;>aoN7 -2X1R4WM>5(b3Up-LM1ekwiR|vf%q5Hy+mmW*CWf2hsfn{mgc-TXIlyzKPLP+8VyA6nJ48cRxq&9^Mx+ -BU)Vx(gO7D*L%;#KW&o_eaFK>S|&cR(Jxi}CO~a$QP=2GVL(coW(2CykL*G89?w-*gm^m-h?Wp#p -?m3eV1^K`mJE%&`0|dojChdj8p%;aj;@HYE+oAhyshbDq!V;l4#U?u5t_Eb_8O+D;he@ESy|k}0S3jUmEv4E==1z!NWi2yp`lSSy6V5JG!A)zel+crV7SJmv~m0BnEsqOM=tJ*?xfX8~y -g~zPdkGN5uOF_o}1PcwAndC3$2@D!WC=pJeG`;0em0DjG2aV4$wC_`Sb0_gf;Q>ryB>9aot}q528C39 -;2j5ekjTZtScRCcLtTOz(UC;M4|OJ}-c_)|^@6-VL&~JA6{DDRECPYT>Gzoa$iLsmx{FA!c@8iLTY`> -rlQp)o%)UH&&Dr0lb~}EB26{z2Ks(-e}MDnZqZmHeg@5hL(5Hy7boMew;aTdV3!3aL)k3zMK_Y!Py(hROE+*fAB*Fn0WVU^ -hz60gvv?v(Wmo*z&zxo+tA|A8X-|FEZQhblkIaMRP6cduexcbIdu_<#QIy$R2Vf2(xbN!50BR~8>}&U -f-psZQ>5r! -uX>V3LUw9WN_3*>{tv54md_oJFxq`9NCAzEFnJn-_!w<@Z@kPQ6G&FEIPvVV0qts|JuY2SnCL6ej$k!xsLKN47T^n(eTF+UL7zWATM5vHl&eRL#`Z;>6X@eX -UQ3=0H1&{`f;A6m=~y2dGlmv_+A!y)MXMa}Y`ij{5g|MK=G%Z{RJwAh@dICJ7&(Kj^%(f1%)&1gj<5J -HGa_PY-n`=w;Du -K@kfks15RhHKdO->QOZz5@f-&vJe<=KF*>|LeU-<$~oKIc^u$cKSo-{?%7d0mQ+TL3 -Haw@o`y(reze)}?eG2KSGKvP!Q8B$W~NyB}8laS6z775_+at7s9kh+W6v`}Ij;+0EAIC)ilj1Nr7iPP -Fx+HS5PVu!i$EGDpxt7o-On(iV1Hf=J)QL%#QKY%9;-uSybr%P+r;Es3*jeA4GOq7)WG_m`pIwHuvDw5Pqn|!TA6JtkpNiyk>e4^Cy1;V-mF -X?TEmaCw2;sR|s>CmR`j$H+r}_{tqhz{@6D!RtK&8J`x!RYcMC#>-F6<6yMPz0)(1196J0c6+}l<<1gtzgtl6v^tGvl=f>F2ezbc@IGv`(W1jU>5Y1 -YQa7UtmO&b+|htBVeE!&p9CuYK}8xCIz!s|dtPdUWbU>h7pL+`Ijq -iR2wm|Cl01*yHE{a+6HJ8Xs0<5XoTeAsIKjLBfr?H)79WxtcE-l%0pdWYmT)G14O<2Jb*sJUf8oGTB= -+sHYjhV==fnAIG5l5CBi*}>z4p)q!XW -x|x85BH!d(_cjs@K>0gv#m^#GYTAzX*_V4GIr_Xei`Ng8K|f=J)d#TU~hr@%tY$4zYm8pKj8@sOV1(U -^0V-c$gq)ya-flV{5sdIoQWK><)qq+DSb;X-JG&%@rFKVqbMgC0h)Yhs)kf8My}lnuxoLeiFr;?|>;n -~5%50S78DDPBBZZn(?R9MnrtdT-UYpHLQg$&l`xX7{IV!`!>B+11TXr%O?Y&!D&_@qFfUnE-?;=mMtJ -64+on{)RcoZG*Bxs%Zz3GnZqpEODZUpwf6>^`ZJnv$uz%?6JQcZmxC*);(UZ36$0zZthR_PpbuQk@_)gehXH%WK)Zr__ -E!h^5RfPOUs>&OKb*k+J816|*6#s(GSCA@zM?V>F3DlpsrYyLgmhOr2CZnMBJ-YVc@CZiwH>J@Ve4m~ -$?u3^244DB~##)g58k`$v{^6O%B91fH|8+l?S5L!^C873ObH#JK;(&F}doPk*jy{z~)im{oEA%Vj+ja -9;o54<_9mX1#u;J%8xt9WeBtdwtQ<`z}ZDfA&hhq$&U4D!)lqes_Q-ITD8SXXb@Wej?Hj{|fXIi5!zJ -X?kEslKhOS6h9H)pEbtm50hW^sgF`0=D&|(Jpmup-xxoDEQ%bHIXl!$KT`5;b_d^7e(>C+*)iyY;YUd}Ne}%%82e-;u}_lqqtA;Rv^Iar^gACdV$W -YXMA_f7{C-4s*xR?Al)Se9>c;e6F`0a~M-Tm?k&>>g@ZsL=*d6s4zafM9s+o7BD1ooIOZe@2cq?Bg?ITfar3is=`Tz&-0fiZJ=#too#UY|c}@Mxwpxb))AjJ6{{H>_mD>aU= ->Go7?E!yue}8m)ACZY~<;Z_76|-MT#r#n!W{)Z7kbWc~SZ+-o#-Vko@TRy^Na;t6mjTT4vbAuxNss4b -s3^sy+V#SA?>IUsvNe~_TY4oEHXX!J3PI>xNF${=Q}7;IW-hBUAUs^+IxzBbx9eN(8gH*T(GC4JpxeV -_PQvug9;pT4cL=>Kc;$HNz&n~=7w%mme5C^5Qgy>2UG9x6fN0!FPYa%>tlq3b@fxv7JWmTv?k? -yJ7PWa#`U^*KE+NO$6R11&W)W_#3)56K@bm7EoTJ`s!P)MNUkI%wwQN;6&x#X9SZ_VgT+fT>2d2buXm -SN+8K!Q9v8D<>oyw<)_!PVh%4A3@c;R<6Sa%_!z*yIVqIENJF&g>D7)oDre1Ki=5AO#c=DWD$H+jCx2 -?#C>!bt571eBpR!ZpfNOw-@z%lEiFKcvk(quXW`*{>oq%M~zQ*AeuP9IVs_774*dA!G+WhsJ5Jf#@m0 -L$~p18hvpw>E_b+;H|vJ5XkIMzOtAz@Sc`Z2BWRrSR&r8RZc*>j5*OY*Af*QI>pz~=x!8~@nEB>t@ezRt8&eFi&HGs&43Xvz;#^~J?iRpaa+4>7_k8;sewG;@Kl`ja`}iaTfV;o*yQ-63XDU-D4vu2(tqJqHXDwMl(1i733q>;xBWn`c8F+ZNR`t}mxzQ5=d!%1(#@fS~h*)$z-5Td*ou!<}v%l<$?DxZuv+DIr8wdOk5#9aEFWa@kn9)Dsj^r#!q=n)4JKk6Mh;xOW -Reh>q -R}=oh+AFBi%}dqOYKn`&cDVT08l<2CWF*WXtrj=_QX!u{4G*bVe6x`2nv3j&Uli&_r1N --BKzE0)2g8!?X{@KHN(=V)~Z@+Ze?k-7Q_K*cNxDVqy1bn+u=WpLgt}Di`s1;OxVVx|XgD7zN-RtT=9 -KikIcE2GO{q;^?!zEVc_?xrof2*_hHr*M!es^oapCMLRg|{oI=oxMJxZy2!^np3gb`uLVqi#)QM(CO; -u~r72@dg|nEPIi{sLbBrzp>M+B$m^ye~Kgrajh@2P#xsJU<|WI-CN7JyqKF5?{`c-8ATijJH<}r7DzE -l-w2ya!g-@M0!*(uo|?P)eHz04bpZ$}EZ#Gb&N3ol*bOU#g0NosfU#!EI#smc7W}%Z(CKWs8y)fr)ZM -yzP0s3W0xypez^oa6-mSO@cVB+HyORJ3tPYl!2dHHa*?1K7Wmz0Fz!`+a#KgKKgm=g5C61WWGwcDc95 -3Tl#?zawL<6$>j_bQ0q5FI@TC~u4$m1&a@fK5owI3I%1H#&cM{Uq=N7Qoq|1#g!k22i-fEI5QAzQ$XvZAYbtxXr(`nyS`D~i -ttX6b%xh-pW8G*7$u1g|nQ101!0~giG5EkDQI_6)wY|_r5&+P#>8KVMC87JPAQ1P*n@;&Udlu%8;ZJ- -8($%fVNRV1QL^X_pQ4q?bNw-E)sw|81NXUQvGPWYtgn(Un?mh(ihS!@||1h#WM^-4B#2Q&XU^MCQNCw%oA4 -NmJ^YV?f@H$aqE{#jvzgFr8o6#5f9?AN4@t=%Fd$2WzGc$qVsJGkoqP(@9nKJ96BFegcFk-Y|+k}hKf -xr(1)<9Y33S47h>t4YhtG?S(HBs{#mLU!Sg|)=+98t5OaEiuvR7GMK@ks&DJIva*?{Eu32nxYS1pfh9LR=hCJoRbGNWSE-vm-IEW2 -imGhmm7GD2b0>4q)~DtN=tGLr%nJNQg>~pDaBx)i{2{zumQpHEzC -pqxg9^9kx(a>?Uh>!;_!oIfrqlAZkU|>ofneP4C`+4^ZAB3UV(WVRKM`H>0nT+}(#+XCsO~PfU2tBqpCy9RNs`}qN=YO|HG(C{@bXk9I9YtoI3Tf+sS!P_yB5 -yj7n8I3gxvD3gG#qIxqL%;*xk;2-`2gK&S9VM5fa0;UQJ_Qy@Nw@w8BWK?lB6Y8qZ%cvhDRW6%Sj!-j -gz-q8-GB7I38&a`%G!CgkfH_oA=fQ2eyL>CvaK*yv^DDi#tn#&8+@Ha@WEs)}(9mNqRoVgXp^14!y@g -Z-g{`R_iRoDqP$zRDEe6!cuJaHvfEOD3;Od6orC4PZ`Nj@$gLS=tVM%h!hn8~MvnpMac-5ngl -k6%M5qAzor!`hDYx&{-!CaJ=v6(cue*bg6!%IhRj!_rwST5~^4o$^HgVxoy8L!5hA+Bv0`=#&z+yTk$ -b|?Lo9$nwSpbn^feL@P2c$TsY9$IKH~09}7eC5||9n5z!q_&h^c>5lGJ%JGiqC|BpgdWS&jg{dtPaMG2_*o{{4!bYik_&>x^GMJjF_&7DJhnWtiSVU>mX;yQ6K3|`7? -)z)i^j@k3QzxVcl5}M|AG~DSSK>$oVNPDOC1LAMB*YORbJc!^S$`KEbWQjpYvI7U8}~?A=iqsg -L-9QAsSMU&(FDf~PC7+V21HNeWMjv|3sziiPx0cmVXlF}izAdsidUm%2wtgyuV%q(zE7jCqw;VlW&ok -?-_#@rlqo$t!bf<_BEwPet;9^c63KcqiwRhdAj{CU;!F>wZ4GZ)W&-pJ)obk?{_dH|y=QHOP_VR6Lx) -293Ej#tg_3*$C`h5H30nPg3!`L>AoC(u;^H+SSpZ-3{7LH_>WOz+wxWSjs>K2z|rVGD=w-YtahQiz@S;>>i=V~~QavhL-b3KA4wyq`3s&{fwK4J_AE(79h(6u_Fk1`i?QugF{5MGAfWF&tZOtc=auZx)nEr -)iX{;#7d^j||&KR)b-s0#fCRqcQYq9KxiVG`Qm)RCGZP@wZWxHU$Y&RULv3Cpb2Ha)7vpPF>>i_lEF)0;*bnhpJYclaX&x)h`?W!>CIC+o)<=XUx80eJ_o91NrjhrU|gL%c}|cpF_;Jqf+j6sYo#cDbE$7D#7%zVjf9sSJY$67M@Ieoas3rv9_hM7!i2BxN<)9G=GL)0Z(34RY& -ryC3^W7jqEy#6BG9|@Yb+Cv0c2%ZLtO!06}8JdI1Yi&ia>4PBYeA3Gk6d#X=#CUGN7VD -Vc2J)BMsxv-RH(s0dvwgu(j;8dB*r;`>Cf)3XT%aRGT)Zyb$T&O!6(oMIaC+ -WmAYb3h{sHJ`h^;htl;do!ExsP|`HnvWG=6gDXv5f*!KyZZ;&r)p+Oa(E({_2stGv}*;B)VK7ESD!RB#xc8M%v=?S{qWPVZ> -X-)}@1rai_QO5#-H8WF~RKw@rVCDQnRJD|aJ*oARX{=S8<7;?%+p^XjDT_ZqRh&~Iehl-E?9igB4ObA -~ge|4RQbd9F=!=(T?7(##EJD4)CuRzoH9j;=`UOzM)=EL)(inq3)x&*6uTTHMlqih0g!{~KmqZ?IC#k -pREb>JvODyd@bub7w_WkN)K=9ad?X)0Ox6629Qe2LDN++jSwCwS6R)s)}i$@%ddUwvINq^T&u&GA5bh -e8J(*!)0@X4Gow?fm{^akJthrrarx;$V*1@u7K;1YrgwVq7!lE -aOhcq`zvaRtvq$=mWNm+a!Iht{P=UeL-W2F1$~OQ*q0f=Yxvh{% -+I09U3m1XUp7)GyYyI0N -`-;H+;6%Ua+{6ztK#YPvYJfZBqEU*#PzXf{WCvdu4dK{F`T2L~>i|sDfxZqrgC40M3OR~!#|Y4loX`U ->75M>#sNyF&`!4$0!5a4A;MsxCj*Jc}(fG(HVbDRNgB1rP%b^eMf(|lJ8a)`y@$4tG_yPLb0pSjxj>H -x5nMOws#vLj>+HUqEj|>aI-_U#hj^{;S{w&NfaIOx-^$ -4a+Q2s15)%Cf(%`e?BM{#uU>cg5G+w&=Ig_C%%N@@dH*tMoSuXLqocXeOj@ -GTE5N0qmlG>wtMHHbu%4g_t%Me;MeQQLkL>fb6QXy>*Ftqwoz0M2FYM9tnz`dPKM~rMo21h -oZusmK{5xk*>0=sJ8p_KA9{>?z97ARwAVJ+W{W#dfN9Xvq?(|5VY(;kp7>S4&_28P4#ADSePcl4J@a&c)`dQ!; -FCS)B!uQzG`_+@nIC{$8e5`MEge02eEu69nm=|w=>JHk_!m$1=S~s*ty9GCgMFN)U_K1CxeJqWOOF9>}(j5rENaPo6U=OKf8)NYPU%6|5vCPjSMUht -!0mB63*4fwc6?8DuS9%+j|bBbr@aEdANPp3!-F<7F-L-Q3!JsFD1zDSNy+1qXZPVe8|-R3jf7taFxI} -Y*iEWp3x5D(7+92tUt)gd0M2L2s~c&r-u*BqigXCG3(^a*@~0W)X8>p32A)TO(*1yV|Hllyqnj&!jo5CU+Dx8{k4u;Uub%d9skZ;*)MkM*HoGRR(SERoalE}^t -UJZwFSix8YQvaQbG_$(By7GKT!rr;}D8K2!ih~eK)6@V}~W8;-helV@FdvjvSgs81bo6XGh-j$k~2c= -rQcO1~mJ`4F`o3{UIxR`=_BvETiD_Yy}~Esn|{Iz?33r4h*wg)-c=4xEMg55?;$I9XvkeRWOr2+ -ZCROpWNOniy-yI}l%l}kV!OYSx)+qb6HTsqTR626HUlP6>gMB*oYi2i=Uz+c~7*y|zb1y$^`IJ;e^ -2=HGi`wVp$%X^v;jNhd~bze7z-Iw?HViR@zRASXLyw7TV>GB-CK*d3?L`-P1R4OaTfxAzC^K|tl^A4! -dufgT3@vt89ay*!~_SHnc(GVc^WPKOEHDeev`IzE5(!9$_Mbb@y2f_0vwtPX-8qbHQ -&u!?tvo9h-{}S&crq4AsLwwZ5jc)H>52+&@SDnpT^f9(BK@R2CCMwYeqt6KkS-n{LaW%c{)|rOXPJ!y -}V_9-JSM?0Y)w$NCvj{Fi8$miN$t>6k&)H2J!+opsA+I#`2`RW>`{d>iG0) -w&d1`h{4PIe@_@DDR%k$hxt@?%hI$Kp_S_$rz>vTOJr5PW}Nn*7Wp?=Fa<4=1w+9lP%%vQJMKaZohwo -(lbF>tysHTzK$YXGdQ=o&Ui1?anU#%&+ZkxA;s;AC2_X(Ga%(dk_re$C^>(BZY__#nYo>EIw+U`+}j5 -2G9}h&_9ufrt}fYX!7sj#_5EP7l)UFWcqVKMjz=M_e-}KI|Eb{Nulv2D&I$Z -Ge(!L5z`x`74z~yV4ZpYPQgm=_ONTw&YUrQ`7629FdFCv0(T(BJxVXNtCw4x)v|^PmltzH*N#8=}Ng3 -vlFa4W>ln|lx%|4TT8Nh-F6g@U}AYn%H9Fj~n(<#Vn&3T8_x04+}K%5O?HjgYT`XuC@5Q -fpSOyEk-^=a1A2PlJow}y520^rxq;6;yeZ?)x;Rd1t_bUrZpsA{PT70u^cB>#Nx{PA33hMBLnya0@fP -*gjg_-1f4x8u&YHcU?!*?6q7LQZWZ!*&^GHF(8Vw2fWvBYA0p?jQgWL!Rk5J#qi`ngAT|MQsM{ZeZ9QOtmU$_M19O}F?+k;TPfD~a^5#N@|Z9(k1FXh -4aRgYWi;ZJ@)jQu!xdiI1L;{qlR5@^fd%CoZDtpD5~2`#_qb4+O3LX*aL0#K#F!cD2g1bGk<}IOM*K8 -RjEqsQyzi1OJ6y=wmy}Ut6fod$T^PBJc4UV2@9B(mBikNLI+-~%icVn{2;J@N9aggEFb?aJ67hE_6th-z|ST2C2$NWAdz>c_ -w!u{^2Px{fI-1#;1a2xcS-n$OZ?{B~eo~b$K@y;fbP^vV0PRNS$f%U><(b|mLd&%8b<$`CSY&Umc(`! -x&uXr*iWuuUn!L!9H>|3%cDCtn||j$JMMcE*^U^vK -1De(OMb@i64VUWHP!9)LHErg#BhiM5ZGdGH>3Fp!^F4R!IxXg#K>(~&wcZqfAd -;-o!7GGlbj6d7kl|~wcTuXOrVcx%m^y;L}gKlU_=qo9XvKqdIo`RHE8^&QTQ|7q>_;PI9&Q;OomE$vB -5AVbyarI*M7>zPq<6Ht|?0a6kjNq(XAgC2aVzaJGMr$m?=|JJE{*0YaIZ;TTW#c<&+eyP1q6eR<9_N^VXf2ODR!2b-!X_dZu?5q)fxwLA$b9h7*GT8Wz=35X3 -wDeY*1kH|S}o0%;jl=ZvOY$vZ{hT!xf=^MY}D#c#2KPG`9Bi|)cv4~Ge{dqP`8t^nW&zXk&?lNS|m}xF~!w6Jo{4XnhrLmj9yBq -2ut=wO8AuQ*vQ94Z^9xOS|`PF9f*wFdu>Eu~VYxTE1$|g>fQ)2l(z{P`$A>bbd=d2Yb_Mo8&ucgZr>h -V)qoQMxoaX05ADlmjj$`GsCQpk)@{cAa_^=h+E&Vop*30+fQbdj24O+ArelHKDl%QQKN|)q(C7&Rro1 -mZDC;5$uXd6cknm&T`Qw>3uR+w?UmfvtM(*sX`uwT_zV+Vv6E|T#L_bcUUlT -2agtM9l^%l!FJ1d+{bgx7em(}8iF59jiFD#Zz8$fzCMUEfRTc+uHf8ja%rrejsnoWF=C3}Y63~sPzL3 -TX$Y?i)0;>#s#9sjCoskyQ2&@S(88%*QOLu-qi=#K6lB$gJOLFPev -bxi94q$Ist;jh!d6~*(ba(BDHGm}B1YO8H+4~dfrul9&UVK*7S7+w><`a*U7d6WqpjHNklX+>Ac*9%X -f*u}&!%U2`Z@+We@_k*GmgE`feX==-J|}vkPA1NQb-(m!X(&$*22RmEn`FO|TUZ<` -jHmV{H^I|0ci*pOyR4$MPlj3ID%`WYeC=C=h2>o^uM|NQ)K1L+VE}SgcEDE -`Te;Ks>h}8B`{3Cc=s#~{XuAhH7Ge79n{a4TN6{vlA?Kj0R1jqIWmP8O5BXNwvF$_lu1flT#Hxz-99r -+MA`Q5Q6_yf%7V_JJhFFSN0m9(%{}z~WZq3IBwZzX -T$MF%`-9gIx)7|>3=3KRA9zV0v?x@N8W71Svq}zR+lZV{9$VWu4>Jt85K;2m -Bp7Vliw3P=$hGT1|0UWX7%%aeL3(*^wDTgJ;X49c>oYu!09fi`+T7i|tv^TM3ZdxXEEGHbR$0;g_y7GrGdG)ygp&lJ9Z -m?Ok?!>HrpSClO}&BOqg5>kS#d7R>Q*lrxIHKnG!quEvcXdN8>)||S=!L~yq;v|vCpl{rjrZ-lwht-)(@d{%;s&2~jQok -K{KF&To3g-ZJtf6$pnoBbtaNP*5G5_2C4pueNiyqffD37;-9Ky>svY3OPMXHvl2_IAwWU3Mg3O9P`KV -t=K&P0`vd@x512A~~Rhnt3LW=K}lHnIy@GG)m`e)+@-z+?2;`gXZwrFy-qtG5s-=E_8MlFeNW&>Q4Fw -a3e4Se#dp5GaIC|0aKqlYdT#XkbZo3M53|JNVj1ZgV~-%BQ1DX(k=;yp-51Duv;X#EQ8!Ut@nrf?o_B -2AfyD_c5!ESqDk91C+|Htc&FA7c1ixiQ=bXmNtj5l*9^xA;82jZO^;+eOW}yb7=8BU3Z9L3-gmFb8HX -__yXF*iVACi_w;bu5^(Y+rM`&d%%2(y -X~gPEAk?H!;nDMPyt*^Y_p#qu#w|MP5U!T|lk&)3uP)X47g>pGvE_anck -NgL2DJPA7r?~~;DictBPfZLgc9xKvo?WE1J?8r&r0(p+}mfc|b>vaTrsxB$A@GssqCdSx6n+e^~#eW%Ki5qkM3b1s(*C;K3(>3n~t5DCsl+98BXH)-I=HsRJ%|+pWe%_}znJxh&*`x+%Gl@YCWM-cyaA4>5 -cOcpsRViG=5S1_Lw`CW-yR-?ngiPDy~YTg&F>#`QG{p;>5LL_Nn~>xAXpFi`@t7@@}%V69v)Jiipcgg -Z-|wSje8xZTpsvqIq+Pl@~UtKoZu$4sG@5N&6;u^^r;zji1e3=Gv?g&}#B^L)`exSx -$QoHLtO?0H2z)BB$Sgak&jxBu0PrT?#X -%k_4ByIN}4(m9<8aLrZvVAEu3(dX{3FXK5-@-&F0T-BD%(5sgi?HbqFJ%0d`wVv}g$T3iSfSi?QASGE -Dq{JQku?b2)({U6QWN-7VhsH9kpwesAj}Bn1JCy5&tJ2y&u04-x?;vCVM*R{6nsGDVMAo17TVo9 ->k0>h#fx0-^n};^-sjhoXYVKv+~*#=EyA#jqEvfO2Xa>UBS#8Zt*0I$hL@ST51_h|^G1Jp*ULyGqH!n -Na;k>*$DE%*rLZ&curM%%6cD0jIx;n}9FCi8UsAO>7@>3DKQ2;-2re_0Vc(mR --Si_2HrTMJpka9C4)qKcj^{puyRR-cEdFSk01E(T)uO8V^cohv4nju%5sW|>&K40%Md46$cm&mI)q86 -p(EhiIB8HjaN=$ooeoLd5@S!SIwXp6T>Eb4~lZpPZfFkVA}8pL=a2iW3_{)V#j67ohmKzJzeRQoTP$Eql6DK8w1yp__+BH^bt -g|-#rudR^!cv{z%DT+{Cs86t)i&IqB7ze!R+w)~TJS9<_7`t-i1(W0@W@>LEM^Ps?-GE5vT}u-N@8*w -^oIRShN$y_o(*Q#|yn&?_Mz5@C2pY4;4821*>FCf;Jlz&U2QjflRH?n4BPfLmnoXOVcv2p3eC6<#&~l -*4k+;RP(|XVM!|$_g+LKEu^9|xc<-82|+!PQUN_%t#Au~owOsegEiI=0KzZnh;AnLR*t;W7W+>cVqh1 -Yn7@l8{s7k0V5%tl6thy8bOlc}4W`48v9{DC0T&pZ7XNByfO`HHLl`~<&6TQCjL5VeCU5~q+oU`3BPC -m1DA9Koog(;J21@20chSgFgu_ -6goNvk8D%%VOgXOsFWl}Ig*Tz0jA`Kbw=dF%m^P>-%ow;)qgk_k&pi8mu|&_YKS~~DEE~oA6fE)J)b& -yN_G^szl4w<5l{4}lVZsM2ovnUYv`Y&E#yF3)IZUds}A@&j&%Yu1r3o=Fq>lcJG3=_NbZ618!2)Jl7B -%```KK+$<}Ki9GvtwHxS+tQr-^+-n9OrN)CJuJY>lc;dO24b;mxxfonEI8}1$y{VpH&Gu8Z;d{|>xu1 -E9k%3i!3Ed5dTDU+I?>ype-gT#TO0QpT}5<8ggzY^X5*)0Npc8`B{i@=}Vw{N38;XGn8{~AE(hSCKi)EBy(qwT_$|clNwJs=S`vX#>l8Q#*Nx95a2=p6TzZj<^C(5}~1yt6U -3f6cEUai)UhN~b>U8kACLsp3FNiU62yiPVsbmB&!gY;Af6lVlTkfzMQ)JB7^0!{fm1%Plei+)NjK~3(6q?Xw?&CvI97mTwB+1Hh^%?N-}!D53vR6lBnE -qNq0-w!;|t)L;qqB~Htw@qZ%~Am=JeS1Z;Op&W?DUyP=j@Gt<>Qz`b8KXV5XcM-nEDWOi)(km?R-B#WuriON)6z@cgKzliYZxwm! -$2b1I7^m~lJPc4yi=^P^=`z95a4S3(E!ms*VvMArUrn427p@1XHVAKkby`CqetDEb0?nx>34eyCg&NtGP@?pEOW%CYrYQoW4_(8^=&iaYI|bE)+EtKLkB|gI4NqGsM;5(bR4YKjLAOt!X;S;R{70*!*ZjI_4$UTx -^eX_$*o=eoemBE2k#vjN^&XjMVQX8%k9Lj+fJ6agw$%4<7%GdgGVgB(OCn-#B%kO9X!oJb9Dxo+yheq -2Fh%|B>a7k)IRn{qsyA`^yPS4Y;O;@}$cM%wv;&jtPHSC$I6}qhkph!o=>}SwLLNztgI@NQBbmE3&wUZi04YL!KX2-Uky|M&$br%T2 -#DAs|e}d$30(yd3y?SzIXZI21}#;gXp+c$9~0T=qnu7)UMnr`PMAzi-p~_GtOh>&!L)*!RsoDeDOu_i -JKIRVxiAnTW%&D1>nujv(6?1a1;+AYKD>h1@apVCrWnuT|h0xKto|Na8J1pHCKPDAl?1e9Fxo0+xPt? -Tj$Orbi10AOm40MHsp|4_t(+cjhvW_689d -8Xlw^oy}n&c$9)Na6SPR7?j7H1`Ua0MxwqUVKAb(wHVRz%oV8(lnF;InQPZwMk7|)@GPzSTB4T2lF*9 -)<9xHbB2BDULnpJSAJJ?^Ub99_S=NL16PPJ)jqXD3j=_W}kyKiFT>~E{7*y}*TmhiQ!cv6d_R}gL80% -P4RkO<@CrSH`#h~75Z}a{V$Xhx?!kP=X$EjIjLOz9onavWjmau1mxj&NzhY4|oO+O6`9cz7jw-owwSZ -CkTU-svkj%EL%r~UYN-M_(y|LVRzVB$~h=WB@~6kFjTfujToffz}U1V%w50j=A&jwiW(`y-r~rJG>Si -tbim7!e!BLy*mPlYn>FmF@mQ*oN04>_;9DWVQjgPh9#HsI6cygnv!I4G-e+#%9lmjVqt-7D-9?SqO>z -(Hw6|Z1nk<+5jw^ZSBf1+#06ouAYtTi)-ZW%d|d$+;J(suOt%a)&eD|eWV!ONGt0WJlWu6jBI3$o#r9DH-Crn@%O&eZh%}>)NKPeAo&7Ev)!S*yt~?;- -6h6@H5x>XO{{5%ys_RWdeV8oj-#YfzN=E=4;2-bAp4%Vgc33=lrN&tjxAm$jD)-XbVPXE$GfB%ee@S+ -9hl{syi|Oa*^yV2?%}8O%=?-a~zWATMhbnNM!+BUO^R32K9NK-{#@04B&b6WQA5P@6#jE?pL5!Bf*UEhBQ8Q~QDsOmKxHC<*)H@qDmhV3cYAEia~LPDQe8MoFt>kI2xTtCMRzL|KH~l)VQ;M -UFn--FbH{)s*MkFCH&q{arT55dmnsa0JC&@l8Wq)#fd!omHxA=(p)Ltg60PNcljPrWuk~!LtlLrxtbV ->K5ktD3l#QUQFRws0%(H&R|~&SHov2`0z*?^-`cC{31x5iLt3n!#bL%?%4>%l$1t}$QtRzCQul2%!AX -c>jx6@rL1kF1dXb$(w3n%FjiNZBxf*}K(aFuN-y)-3` -FEg^&h_)}0Hx$do`*Le823uZ_T4B5)IFJX|!m3h`(0nouxGW`^2$bc-g*Uhnb&R^0(R3Xe^{$rGKYRR06h$bCzz7&Z(C;$8!fo7a)70K)<%n!Ua#^ -yWHE0tA-`bz>PN#xbxCMQm!LXBw<8aq-*yr0eld){Wp(}{PiH*3h;>`@)n8({*<4x6pSdRn#XizTO`m -Z>+?F)@|5Qy(UGuuuz+jX8Gc;i8%yS4l$;}v9o6x})1QL=%|^;PS0@Ni>7B=BZE7H+GWf64qRH|E#vH -|Ccwzk|tboheZ!wN0^JUFU}|#Se5T!k1u%ZB{)1`H^MyJkrAknh}^b{s;;&O`+ -s(L!97A#;kR$$G8mr$obVNloM~vP^|CJM^Cv(+ej0_RwT(JAa^sZ)*6eS8%KpxZgV%(=eDjta^~WavMVB-j;J -nA`%e@*IMsn~2>S+wHEbrF13HOHrJ7+^SQ+Fby?0J8usAs&+$D)&!>Ac)K2V^yad#be{m4c#LAbO22k -q4N1@TRV3JaP+4(JRk7zlKRw#Qb_|&-ch|gI=FKDFj`}4{!O;H-J8oU_Xx)|NaN(_djj%^Fs&t-+nRC -Zvyr|y_1hf{U^`*Dn6qKPCzILQz!+)6po@KNMaNS!_aQrKoJB<5h(H92n~Z<Z}A-4r+|{(ZW#=BcSvdv+r-AfT@%52+90+IV#Hey3XZo%6TbI*vR&VTKz8L~_*XLis|d}Y_8OtD=| -_Y%)v~xt%2j1_jO~h|Vh=f3)ABd8N_liR{tjrY{HcdAX^{zU`eQ=3PaGGYg2-nYEoWGCzaAOXOl{Igi0zMn{ewqRP?LlsP@TSI<=oYGe8sW -o3O?2`L-W7YArWn&d6#0tpyl}Qk`Pp*{Qq-5`Dg}Z-DA-#0nsE&vM11;z54;qnn+L;ki7rq%sCF9SwG -l8tr@};~Qoc_qMO53tl?!7}uoFGZDZ)GZeAULGTQzQs&#xxX4Y+7aLcf%I7B -gyq+5A95RDBYI$HdQZTTf{@iZJ}=y0)ux~^Y6E;H$E-8HI)%?Ye$FZ#_UhCP27A9XW4#)73(F*Mub4| -o%$bb4~5|m!WOi*xud;#zMc<~Y|ENpwztL8EgT}~7W`JYyJ@#)+u&h_?f4VgWO_E6T4Ym5fRJA^e53h -p{dRB{T%*=={YzjeZA+1%^(Aawg-Su$>-!D)7~7<3oK-3;j^DDu|EaJA{r3%9e>k*7TsqbAvddj#Z++%?eNxOzduCp0g8H~SdqJKp7GCNRW3I#vp7LzR$Mf-mFph` -q!P&A|v9G$ZdRa61oXbFZx%W=R~^3RWFvja%DxkldI@pnhJb`CF*$D}v%By*@2vXc_WxGx^}Lfdc)*(dgm2`1{r!d!Xes#)|^6QpkGdm<(=HV&H)U<5<7o$jnKntNGnPqwAWl7 -<3xo@Xlc&}VSQ72k0g+ZSy(9ROzH`%KG?JJo}A_kOyf*GVc|(;F@ajI)4>{+;7KDt9S5yG4EtY_^tG1 -1>;F~QBx&=zK(vdaeD_J;#KM1RM<4OyOR~PH#}B+MaG+(H3sfAEwn -jYtpE)9zA;2XZh-}lHnkHB+NPl~YAa(mXhv*f(IB|7#MaogDGl%AI-9Uby4J-%5>koxk{H|?HQ4Th8X -?_l8@VAj4+{+0;%EwOy`o)ncngkMvJFbFIBpG#o0Ma=4IQmvcv~shg&X0Y;GJ!QI?((P^>|t-i -u3o!pu=ybpwEE)J4bFdA6*AAZ&%;Q1JRwg%5eJ{iNbsR`qr#&R6mCfj#cbL6mGy8c<%dzOYN>-mG0_c -_rOEH4wst($)~8dY*A0&HB)j>tM;fTqn|>dynGIYqgQ@i0o(WAJ9j@c!PUX5CxEhORzO@Z=fE?oxSno -NZB|jqzclvCm~`=Hm-Iy(d7Gs^?S8B4%`E=lUe0`8vAxjS&CVa}C)zSPKPuw_#Fwt~h2|A$*4;@9L49 -2`tF-4%I4)cl#}`*i3;OneMUr~OYP!fu!_27^N<7{mo(IVHtI(a{euCbq+T-z&dx=$;K>{3Kv6)g>dW -x4rr0Hh}JwWV)h}}%8r$b*ZcS2}=U|PsDrt|TkFG2@H11Kvq9}ZUT-Gcmh?u2b!v-90@_iW!`nE%?HnMZ*QaEc~#vMl@PA|9JT)mepZ -nmhaLO^N@(g(=Z)X_!<)OtAaX8r#wyRCkKO?cZVNz62KjTOq`QUxP{Bn0sD9^2qv)$f+&MmCUlV$!c) -&CmclqOF;$P9wW6%k0ktI3yb^$(FHexL)rhGWC$Dc(#Iyr?8e)OJY5gc9kL5(c_ML{-_y9D`$*&QfCcfbY~x>%C6F!>}QD&Jmq@Z|a`0 -@5?TH7&s(}KCIHer_c%fSjUM5YpTk#wv6C}=VaK^(#^? -A1PDNA9n#PdjrB&NC6c{BEc$v5)N4qcH%@8jp*D%?ey_hp5oY}#a)3+a7mxzJ%BMHdQ47mzOt6J2xdq -LOX3VZg%te>hIadAcIVNamkok#{b(8wATkM)a*Id;Ua*b^(7B}dRZaaZp8(DO&>0pMlBvMULVt8d<774 -h)7ipLsAp0H^f{SV+6qPVH!1I%cIP*WKbaqWF=I~goOGy)>uYlH&-~K_Ah#|G-?ke9O)V-DqbQXXk;?C(#opZBGo -r;X)xPBk!aNBD!k7BBE0YpoE>OjQN5Bvy$UZta5tjl^%Bf_AZrUXWqY>@s-nsq4W$juT+?=A5yjODen -mg)O=+L3UCK~`yC_~6z+^og?&)1;eM?1Xx1@QbU$m^$e^XIthw_SXTx>nGIe7A`aZ=H&8*E~weO}!|C -Hs0B$#6)hj9kFXOt)OWw=As|S*Wued3XyKeXR9frt=?O|UthH19AX=n*_N-ObOT;6weLIGp7z5fGIVz --rJxPRq474DLt%S21mCL0O_OzpP%ENZvDXT@(tQeMtt{c)k^Ci~(ov{kITza89G6o#N&FPMC55w2p+_f*3IPH{yG)%MOHTaO)hT!O;xAj9#Lz=d4yaBJcF -6`|tWF+5{MY6Vkjt;w@P^q@}8NCE`MBXhk%r9B>^HOG!?xO{;((s*1NA)A6{Zs-zWVvo{SZ+0Zt)9cy -!Fyg%^apF<@3A{2r4cDA4au}n|!jX9N2{VR?K^jlC7)Z6m#d(>z^Ux?<^4dm($&}}pAjrXx -q?H%r~WzSF7J+EX@?CgmOq7fW&y__a={1_8>h)xJj~@gih>$XYNu%|=LuSLPACPQxU7YEzy`N0YPjG^ -It?os?Ev~foS!d4@Yb*jvzu3)5U5I^(Ne^BA(8{zHto~_6H+<5NF(EID82O8_*;Xx?1 -H8#GiG{hY4%l4u5l>yFKonL0=h2jY?XI4#}dXB{wwFy$_OeY3HJAA{Egcuo{O5`*UDPuE&HG^s@NioW -@ZiE4MyzGnH4JFxm{jRH6>-(S)AYGL+*zi5TMHib>L@fa3E*St^U7_~X^2XmC)*lOG*nWwk=!pRTX?0e?*2%#AxbaTwXIQ`y)#;Az25GTvUgv^E^c0tU3GGSDYq -r=sd@P0Kr*V2(kHSsw3?kvd+l7@zdl#gB=Trf3|5hv<=gU1~mZSepgLi#>@MZK_dNiL@F)u&%}OBt1r -*^G{V`wzM7{si1rjNrRj?ZyvHz7|xh6iIJkt0Nd)C%ga~6l&?ebBHxYqK9VB*5bbO0Qts@($zY!e8EMo53kC*jLnYYKfvzhdkHPn>#c%@(jzSaqb6;2nCgYiOZA3ZlmkV^TD=WDp*5{#S -|F##DM@lzp8%fU(c*NDirKK@^{ctQQy_~PLGB6n{Q6onNW(hxD#|lqWD2_*z#?Mm&-#A0kOhmyy8$VR -XMY`pHR?@(ZGYlfDHxl;J+TyB|IO3`h`Q-K{y^R58IpqZ4p7)IHxBd6)7XXo5lscibdOVHz82DneJ~n -0fGHsrH|)n3H^sX2&qyyjAxzmtUtk7A^6aol7QeQ+LMnMRMyh9%-1E56vgy}wHE`=TX9Yu9fMkENQ=~ -B|$o!B~Img&(Nu&tLQvrb1 -wfBw+|dPzuEn6huNTf9rLZ3HGv|E>Y5!R7*+)9W9{DQDh-wr}b0IS6r# -`aR)GYprxY$X1HALX@a7=fu}R&016W#}fI3DH{p{TuFiAZJ`KW3mDIxuWa>CVQd2(ux78 -|s+5;cLmtLP|Nq^_l2QNe83Es(($AdH-_fmX9HvPx*qwqnT_ioqyk6D0bq<#Rt&*Ew*#KgUk2TJZM;v -zzL=6s+n`H8fG2Z;-NVb_95V-{1tpzW|ONG)~O)Tv7k$I&`J>G8zIHb3jeJ{saSBE${gAPWWZ-;|5oR -H35)*>Ax|*kYia?plS7L!6gB4~U6tcgN2*Sh> -$H32dg}zj;8XqK@VmT8pgMC3v{Mo5G!A@U7|RYoG#&w7A|iV=o}8`N9djWQmmzQv1Lc%Ce16z35EAJ#6A#rb -q0a_{8gkGsOIMnSA6V8;B47Ftl8+jr|Jj=zJ2Bj-+!gtvosdgLBnppi~k!s?QbF*6%8O@wE!i4l%RI7 -7Rc|%yvE0G5=)H^ubC(lFZxiS0WYD`z^d<+QMyoMT44792aSf7m%vhc49&P{J3B`P>Aj;wa?%;w(>u0 -3w;l9eXU#R7{RDtM&>cRpVIqayyM!{inHEa|HJ^5hTnJY1V8URX-e1NYNCy*L8*HttKqE4_sjhW;n;& -ciw-T6!>Pay4y0;*HA(>fDDe8)8-sZ!<}YrlRyRCOqH={$P|nI{14J>0bf9_^Adm*xl6bg>@kSv)Lr0G>jIX|{x< -uS0N`Uo;w&@?s0|bSNfZvcoF5ZzyJIA&q>3Xq4@|wozy)35AL4d<}H4cp%1f?LU$rG+`%QJ6@p?*~+h -2~{l*Y34kD)&mm5M;2&qlm5O&5_~b)x4T2;KBY-7)v6kaXwE)r9_5)Fr@L(pFmo>fo03LmW);Kbm7s$ -@w4=OxIvvS3#NGopEf{`L+8hjh44<48OMW1P1lD^ui*3LA8(rTe6MdD{>dh~%_UH^ELTb#DhDNwugBd -!wLk^syxgPcNX}#w-)wjk270khDfH=03&*kFN>{T=w(UZNz?Q}52-c_43VaDR35rgR;m;%3vx -b#l|tIIp8ig-mfasPPzO>FgZul*yu`oZh|8eu^cv4UHiL=g%>aEkmM!a|6BO$*!FcG*5&K<#F3+gxn4 -^%J3R<3z>DHY59kHLdmO+gcK|L75e+QRHsGfbN~dY!i~))Q+$X0Kw^|Z@E#clFfZI`;o=`3WzoswYL# -Bhq!%;0>-zA3<}y={MasW32iHF>qZFN*3C$KU%?8u-Xa|D>QLFX7`6#4p?eFG+{QBCzqBI~f^B{R#J` -KM7BC?H1Yzla6Jb3@^~1w`7QRMUj^q}RSe3G%S69Rw;^NOxRt#y2Hg6~k*kIOI8%sw2J7)!a@3emQtT -v?eBahN=krpgJZSu(%1I{NecCF)+w>@9b2LYt_N2gh>Rz~b|T!dFECGd?6;g>A1oKe19w;xqdoU@{Si_Z%)2R0@;*VuUzJpBPmI+4*(m35%5bCbe$$VGPeVVq{Mj17i({!47e+ -RTdB>oislVkauJ`5qFl!hFP+ -gaCC#zmZC%s$Vb#hM1dr?Cm5JjTuSE_SMu2xZYZB!N)mnR>U3LVSH874LUJqB$HqRc=KxKG -@5NLl2|SJSvQMR?7@f^J%)%unpr+-ti(W^RFBZw-2?q6H?Jh>F=EQSAj~Z&+m9=961cE -+9_x?ey{`;T@rEIa_Ncgy~p>ZckX@#ty9n`+@g!da*pxnWG*GJFM+o+7_%=_p$>3&PqS-yLb>`DsYcO -!8vVE9N*MSuSo8!XUdHUo$JMzrsv`LhCoqhm-u;to!KzgJpo=QNG^MS=kqO-R*>8xvQN|D*-YmXT542 -AJpyyZvvGIHs+7xX#4o -0xYF{BO;5W~vH5&05ju`F82R!<6poBU&=L#}{{9Z_|Jhy?;T)hF8>DawKgL;x7qXc^Rk;p<;(VoHxl% -~Lle~K#0NHR2RP+lE-#HYr_@DSO>7*q*Ma3Nzb+xNXafj&a13X2Q@!s>xVdr?qMrSUVKZJ$mjdG*!Se)K&OZJr;!%l^q&~)JIeV1wdvnJ89IJ`?*Fm -5v;6xum1?EwFPlY6`ssmxZ_@r)rFmHWTc5t_?)bmO|NAd62vWa%{mOs)C0lV-_-Xe`n*8$a(qA4;HGh -NT{=1#q2U7g$^ZJ_0h2onICkB5H6>i$&@Gcwy018ZZM22kBV%wa3xV0N2_`9f(++P6`yQ -k&u#*2g-V#D#B@0IK=)O4d*ttc&uw!N;9j#yFMkA$F5WM?4klf5fk-Bt#(?Zw+P8M^Va)?KV{mDpKY8 -MN(m1)ST28@w$bt|x@18~EH*&!G)7ZpL8YK76~mdi@nDK_bdx}DHJd^5NOY}_TG -q@B-`TQ=vo@8lDt>SVrpUuSe$cUFJ6T}hB$1=eTR77qh~PxK?~9r(r&iS8Qa$3EHGqrSEc$|4^x)Yx0 -8TJ)@OG$!-E9R&D%lx@1Jju1J&ei1}}RsqXEG3?|KjCJWYlFQK_Qsqx`J^FU=t=Ia2^!{93odtKd`W> -Hg_fcPHWQC$LpxX}kX&I2-Y{9mT(7vtw;|=@X{Q~~Z4g22x0{+eo``-Nm{>}~i-u(jp?1tqz|09V3<` -Z(}cZq>@Ba7co!3<-l79P|9w`t~yM6LEJT*G`Ra4noY>2x^`A)7|jay72%)5@+LbhH7}LF2d&&f1{NM -Seu`sWHL_OVhlQc3RfdFVb@7IDWwSz&=tcth=*c`->!HE+l)K$%Y7a%c&{wle<|3cTs7XBypdehCkz3 -dVoyIC5RChiS_6*&O(0SD7mNI^!S9_JYbAfr#mVk#-?F2+y3-kjC%H1lw8<}_k~hPaHj5BJ~%wt8k5i -PF@hLAFoyKP5Jw1bphJ8a4F7Zvo-ZDqZBaq+SN6c74jh>J^m#?vuTD>skkBmCM9t&&ZBEUh07_u}bLRWGk8P4vEQnw4O1K%ABTM -v*1WKwk@oZ4hjUNiePYW!7vzUl%C3STo{{Kcfq%MmE;`)j|@$UT<(gn=Zq?;~PST`Z66(p%cQorTAS- -y*Q|YMR)+9Oo!Lfh$y(cXuR33JA)cfISTAU%|UQ`JbSYcx6msN$xFiW`1v8mFGfGx3OX#P2{iQk-UN> -ud1NDYVNjTq;v0B54B|PQJD-)bMk+Is;HzB5URR2c9-TeBxvE38i&#r*rF_1}q-q}_-gq&tLnkH4M56 -NWSLlvC6^NS7Df*Nnv=^wq(@OivoXWG%KcDO&_FHBl0^Yrq@xRH^$A*GJ= -Gt`Lv97`eb7I)~tL~@(&|E$__jyPgb0As=wUacT}(7;sj4s01Ms8-^RqCMYC7%^x@#Q(2whU&v%ujg8 -9Qiw#xxF^-Os!c@yzA#C3%So{_Ils$`LRN78*`Sxq0bcJ=<{=)V!CHrv$D3!Yb4Sie$lng6mgFx?3ZE -~nWHysaEE^C$k}HWAHOZ)Q!J1wg|x&B%s5CoCR00)f_xfkSQ<4;R0#YjpU=~7?)qhn -G`;?T}^h(O~EpekOKf@{yNv^x -?KWP>kH$jcT`q1nH%0gSgr!>oz|igVK=iXe_2yWOZY~sCNlk5RsI?@8;$8bB-E@(`orATW(>9BX?d9{ -vkNV4|MY$yakuuFc_SoC8Mw=v+`GphOAtk-btVJlu1x<8Akz$b|fXXHO7bm@ -sRHg!Gp759Ha5gmH~CuUK&}vS_*O5=LxXor|mQ^j -wpzLVx0EmU0(V_IgZcXxeiAFBc>j)(K@@D>)Oa;WUNR>QJ5T!4^5ATG1mwV -s)S0yWf2=zGoE6CNZZT2L1?B<9hA=+avV%N`BB}HlN6X}Uv^Q}chN0FAG~Zj&*VZM -8ih?bmY9kX3)>YZNL}Uv7xmItq?YQJ2YN8_GfFavie5~&ekpx>bDViC{Og#?8wvz3!>!NV{Oxm5nGYB -b-3B1`);H~q#YTr$2t>|;ML>_(RVa@7ohUjA6@MXZtY!K@sjtqXVE>UwqfY>qZV~Ue?|vWB4ch=Hu~_HdcRm7t -C>xih04(QN)%7Lhf(MDPVBu{NfzhKw2JqXSx_@LA7bz(ehl@Qn|vRYt4^MaM8K256H{eFhY6CvNz1MD -3td{=W?*y^;ZUz=cRXQc7D=PYrh9DoEi#0Iw8tBZQU=DVMoX;8N!XL-E(q1opGZ%|ifXSf%qRWig?5&BFZ3Yg?h=S|9rXz|T678MM -=Ls-J4-Aid<(3w2UYc`#)YN>l={1wlN(_a_pG3jgsRRXG3SKjVM=XZMf){6)exWw+m6%M? -{!{PK_gr%Lid70&J7*_%4c!cdd~173eM(y!qH?JLNsu9 -Vd3f*3I-D*+k(Wc$YESX0H!C;fJCPf+FaKYCm!0S9qAj+e*SFcehi%Ha}Ku+Y-UQg5>7Y8j{8_9X;Cp17U&y6doM%w2e -gurOb3LD%^1Gk%+RS3Aefy?30ogmzhm4ZW!WLW^6UGF6G#2n}5IvJ_P7IX$C~pZSa%)@@NeJtu{F%KY -t_*3kO=X|0&=Ibc|X&=lyjE#7{xu+FZ{HG*<-1;uqBBR9RN3K4UTVC)&rjQvschA^K4mRRAD=Yq1J-_ -|2^SHIyq@4|Qf`p;ry{ew)l(BLLovSL}}v%}fEZCi0{{$-;ki_cx!^2)zU%6=xI -9lW9Wdx32fhsmx7WAP_x7~x}DQngki7TFsy)5*=JgJ@BsH^VdXOldWKvKK!^@i~ce9u2FXMNEeS`2gQ -y!2EGTXp=Q3xIv|n-GBY$*)9GhN!My+nCR1ME$z1*B|=NKJbm)fu;2T(=Cmy=}KBty#AByB+oQoxA(=`HuN+?&wq~9Oer9zmXChHMps=@A1qUUe>{}rd$BRXgV|7Rlvxtx60$`4Qp#&3(t -U6c|RcKJhldrvG%r2*_?4ZUtf|J|cmW!6>tZlo8(S0{gRUdA5i_k#eb|(4vB~l{w!Cqd0i6fY0nVNw`(I-&V^>HePg>i4n -RS}-Y?kTjhSSlnqo%iVKGanHKrjCc}EbLMnVv?i`=0y?=XOIHMlm{&Yy#$x}r+-eQ95Pf{1PVtUWVt-{pI30mlqSzeCz!G-8kApsUWj47S- -l&!a|daVZ>+MYq3H>?{2T~E%>H!=R%M2i3Sf*AjWr}M8u4E*b%TM|Mx2L+0Nap=3#wb|aCNYhO}5sx- -j_co52Z0R~9wnoF6pVQrR9Z6UG@clW8a2H*SclOf?N04nD3EYJcqi_QOD^}RXTk+4<2kEYY3unKkKa# -!OW-o|cYX{rc(e^nCVz+`P!A&%X+GWS6o!Yc@B=GIMT2Ztuaz#712=Pr_5d{AlVr -+91m^NKB&X)IWf -L?ytIBp)CUw0%-OEdP^yxj+hqXQyMcX|H*6W>4Oc(g(a|)$`Y}X7%(L -U8hlHAvFTXrr84DviTHWqTsLK;}0L1=N?t4uY*u-Mk}%M((2&YPlMo0Yp7MQhRohe*$HJdT(EX9X&(i?j>*c({BPDp?`pXQVY3h`#J(RM#;f(=yGy;>& -~vqvP^y+*VD|aOP;;n2{z)@IYNxV|R}FoIW8A#FuSeRpHA$@j>Jck04#CeGRMrFH2k{Nf=N>xaZuF+1 -BqPxBRRwM(l!Jtdn)*8F*t0L2TR~!8CjveFzFPAweWADO{I`t7x0KmD>Z03ZJ-;Zj{BHBv`9DDrgnvO -1WHbH-k#9Rl|I{A7w2!{?>aTM2ckPSNeR46`GVxk6!{pZ2N66jKXm7_8>96>17qn%&?7LZ++PgAq`U9 -aYD{toE;Is8i0D`xL3ozZ9VDPTlNABvPo50^+tLNRftc^b{=k*_^+3|GV^P;u@ --)#JYa-LbqztfI?*zEnQ15JRi(C)`{0#|OLmvXLi+H$t<=q^NRf}=QoFpo{{)2abBL|(P*&G+BrX8wk -9o^ETb@#rqt2?nysN?B|U!y80v;u=4D@+{>+)Ih9&cd*`qdV{<;JbSQS^Dxo-z4Xwfc`yH8dtbKXsFG -#-&R68)ZPV=%^Ki`W14xV#NFWBm8#9POBLeaD*PRGOMn*=Q%u`)eU2WE8HDn-g9LL#v?P=}K3Op^Vk# -(8+^l5WUsN0I&aA>|DfPR``c%1UBthSYZ=y%69A=E=7ZCjUr%;$B|aqXr^ka5qc6GCv8!PF!1~2&%#izrGxGyCHSS*)YdKAcG-vtU+MRWY11)KF_rEYdU -in7A|>WNNgtj?mdCfaLtw7WIGy&Q_R6=rn}T5h?+ubO+-QEsr ---mO=vvD_@?Ot{IV^)5yh5x*kw#cZ}Mno`Fib{|XR+8v{-#91Z1**(%`w4K#Fb&V`Mo{x`K@tRzuH+! -$5trF?4`-T(e@+@G(gRg0_e_45sPSZU4=rcM+vcW~PB2rYOF*HQI_#KTU`fkoilje+PwSHi5CU@~f1M&}-z#S -XLPGE91q+m*F{tO=E>sE>!G8`;eR(qeiKIfFG<(?rbMR6F0x6$uFthD07yYr(!>~KfX4d-R)-31fOio -vMqg^@5ZD~u&51HGtavvqtU&`UpIOmp6dIY&Bp_BF4&k@mwENbQh#R5)qtg`fT2mjI45c6 -?d25^i&X0{p*9m#?SXSUuQwh=y>J)C&Oi#%cNNO1@RfCMu5{G~w;OuVaN!6HoctiseH$8AHS$n-=I<_ -tcoUMRdx?A39j9j5968ssB2s)RxJ74M+86wC6NyxDczAP@-|?D(t=px@&clE5@xD#gRW2 -FpB#V=Jf*sXR%c|=9UZ5kiV9qledIiMH^QpzO<=ZRohX0c@=?6GH}>Y@x?$gjqSa2I4=D)<(gFO4nH> -ha^U%<7GWH4MpRTLqOQ<4|?AH}eB&yn9G)wy}B}yThdw57@Q{nCsZ7xochLx6t0t6BCnJmKw;|y%d!y -caHdH0PM3oRhYyH3xvB%3Zc(Ez5GdEp!e4RZ1pi0bg|uiE8F;ZIsNTh^!Rp3v%h`&KQ(Mhe*IRLVf45 -Em{LSBj(aOsnTGnj`T4))1V7}fem1!uP$Y*lG=&n(=V7F&#F<55iAfy8x|5WK@Dco(#SqVtIBa1FFdz -ecIG>|Q4th@@oeVwE3Z@Xe!NQfP=Mam-kP^5Wb~Y#BsKi8QgHf<*<#RL%@FJKMId$}4r}R^D>RFVq5D -_v7bl8v+B_KgVzYf5`Fa92Us;2??GMV~ehTKF&A44ud@KcsS{}T0U9}jGr4t{=1Ml#UL&iu)qbEXhdK -Yms#^VZ4-U^Vo|L`mJg_8aq=7peVJ@`1qYQ(&swDuAIr&OPD#v$2j^@{^2fDC}kKTWWF2`zz}@))8 -YoB{fI3;?-dya^tBIi^p?bX8e?Qq%3|!V5<}0-*T%!_ -Whe*NKA -dLB@T0%9elkx(reWWH+V)-I -?kG>szWnO5aKzaqKPr=;vnDox~r&0xgY|CI__HLF~-S;2Pj<$+DQ>1v9i;J=+imaXaTavG$48GR+V5C67c;_GHRkM~w-;8+x-q_$2Q0T(O~rweS}5j+f< -`2z^@<)ZzsgueGL+CsFzx`1j=h(!u^nkE#+O;D7v+s<`05?$0*NSNjF|-3{~AenEbB!+f=0kl)=fU+o -v9*f*Y!jHN{QA`)_MPnT#TRxEguIuIyu%7fiHMlPUFYnIm+y?D? -)K~qD^cP2ff7f0YI=KFYtX2-kRth%nqEh5H$YH -j747USZT6jeUYz>zUS|!D$!T%;_R|2u@N(Zl^8>gZ}dL2t -258`|nx~$vIjD6x;w3u?TJseP!Dw(qyKe~2}Z#OE}a&dwH@SU6F;4jOSx5ih8wJ@VE=Kg*jHL{=uCmt -GX&+jScK;71kv*!A0bmo-UnN(wa4d|0c!rB%z$=MiUP~CkzC6)XrW{ToFb;)z -+pv|-r)l+xbK#C*)mW1!UV}-tP8|fN4PHDZa$X)%tF{(>7cVrHTT7&v{qaU-gcjA -j^nH*Tfk3z1UEo`Cr --zZcL)D;vA$X>#Maw-WE%hv(MvKvH{^rX-;ruvuTwT72Z`Z&|HkZ`$)nw6 -mx5F*GYoFi8q3~k7+8$x6)wm6TKhD!}adS8nbg3i=A~@)byAc*lz8N4Q2!!;?>P5NGwf!pp|V6X2k4; -!Zn$Bt5hg9EN1>PudL(B8!J4R>vMPCM_b0q0!2qj#)RawNi{Q$yZIQmePk@V8OKB=b>d!GQ9eGF@bXv -!zr4-C!ru``+XXvMB{u@Mo~`!k_M9&A%)bn?gEV$IEvoCa%r7=}gdaU+R;1t>B{L6u-R+8v_$VsK -n)+G=ZyhdOed!j%=jUWe;3d4!GFFA#6O-E;w6$2qmSjll9Qg5 -zw>+!FD@dYoG-=r;LnZDFLh^)ar#iNooV0+sgd|GbFW#eaOW!?wGYw|m$=sxnNDK(Ig0R>Jx?hPS93Z -^!l;=H<6lcAEtK$BRBT|NU3o@2NdL-0R(~V5H?o*8ESKvjGD$jV8%HRWrtunGD&x5?E#fGc2c|1rb9t -GbTT;)0xO7C=6PkXb3r0vxy-en%y*HR7L42eSv~)gHjA~RRCcRGz_>ro0B@Wak*jsvp8y8h9fUM$ZAmqw$e?kEirbgdOPO5lok_9=)2i|{*Ow -qN92*7_J0de1>s1UDkM=&^A0vZDHpl -g5zTfBRQe77HS*`=+=FHEDxg8tYAOep#F7GZeZu)WQ?3?J~dO4l&OL4`ny24r@xT`keZmpo@QqRx3@S -M?Pz7Lb|lI@JvnO%Kf;~MIgRWy-yk1qCYTv+O=X0I>Fs`n7E11eL~lZ-}RdEGY){=@>~a}~YXl+T1gR -R?_!^)en@^;`6~B!!n++iZTsBx -1{47sqlg-AR3(Fs@Sz2Nqu@g+V!X<&#=QdL6AfUpu&2Rx4cEY{AHd$MBl%RsjWmLf>6*hqVsHSQZ~^; -(PQW0*3;DVQtBe|z)swo)2xt>)0#@9J@*oTyMsI5!G~1`4{Z8$BLWU2jXq4u@vI -ZP38Kot%mv#O%;E&TJn*ecCpa|~-nrqxBf-a!I^VnI!MjINWr>F8fJb;f(@=x8c%%*Z{&(M>QI)YeAg -p?NlpRD##~ciBhWS?DhA@+-egJ4yyq*YWa1CV%jlgWCmfGTQ_rW^t@Bprh28{PQelgsSu#g+zGU4ol* -9M+z!ddZnyswGL*U-VdBDg-UzJ?0?8_dW!fi>z>@OlHH%8?M5{ULZRc*nH-@li~fg2~}<29xow%FYeE -fm5^)4xxvE*GlHd&cRF@(;U;GfN3pEt-(X^NhcmyINZr$YY$bij=}C(f9``fRlo(GKp^t;=z$a9L2#j -V4m<+|7u{bke76G)(*ZLD0_FtPs*GSCa=0Nv#X@66r`Hc|@@T^8Mw5_vDW?aL_y7ingp1I}Qm;L%2t6 -P&z_o}^2tPgR`ll>xa}qSU$(xt`e7{_-_{FW1*?r?9w~%ngNXG{)PG`tNg)6&VdRwclKsJ@lhqte1YGeS(HFU>Q(s%i4wk-RFpix%Hb#(=A2yw&p{1WPkmCZ -9z^3p72Kyt-zG8Zv?#@K8?KnPFZ3ChGgVEDh7O6qc6Er;J(80G9)wT_01+904HUdqpw-=z-vG4_w0j7BIslM_jRdjB9pLZ+0 -i*Qb065_WcHs$&6th!BF%RW>f;@00!CnV2h`Xkqbc09_fRMLYA@8OIPSkFJcWdB<;FBF=+-=w1%eI9B -O^8Pc>Oiihn*?&*LN6P13;~29UHu6b=@2V55uSig7a%u}D!^2m?~eqq#S5Oghm)faJQH}%E+RA<;>Pd -KsOJFkv`_Q~7w!e=JVDyDCIzr;#Mcwy42C0k#=9zu`8$JUvU;GyY{HroBr5Jv^8#WK@Y(>9E6_v{#_t -i354Zxi?ml>1K3?A@yxmAGc)bAqYDb7@?@w^~?os#;XeJMK*bS`F^@-*nG_+k|i#W9<*tJ=x(uA8%00 -Lu>-vRs^fOQiBuUb{y5&V4*z?%xE1^AlE0Wkk=Lf|;Tw2a -$>M%dHg}Vap++6jG{c*F}Ax3#ST%=qGNB!ie;gU5a^NQ5Hgwl5miOl!Tb;Q?vz;S(NzY=Srd0AUP-N^v;#5DF)<{TRX^;! -lc;qZr0^vWd}*z^3eC4mLjm;4O-##FB6#c+FtlF^x^^HyjKB!Q_#NS0DbG!sL+*gCnF<6C{R!xz5}#d -qw_RM^W^D$0*7{9d+zu6y@dMe`^%w*NgRU6Gb^RlI$s)gL^eYA*{0{7{Ig7>?}Rq$58@i^xDBQ!`U6z -xn&4wv)U|LEE%)*M&A;cUMp$Mme5x_Hcj&ClwA>AJj8caT&TKVoxm#U(g}|uDP}n$Z3_XY-Gky$HzA7 -8eW#Kxb4JG%%PE31V6l1j5lzcaC$tVu2|?aaqx7?_P2L1|`>QC56f%5Wib&AsS7Mk)DO!zOVzh0xLl? -zje8kR`JZa~6pSNtgSeiF|Hqy1jZ56*NLLD{j^^U%4NQ-*iRz07tia74$gXD6v`+4b&8QCawn%C8(m> -2tVhh6&A!18mzHrDt!3)_y6H_4nwwnY=mac|qcCfxinC6`zZKJKlxr!L2Kb~p(8-zUTPuLyGNU!*X8b -)PR{?%40d+-VA0(UAX=+IY -t4LX>E!c_wnwyBT+4Ku-kxkqa0onniz* -Z0uF^5jGQWCQ1XAYwh;f_w1uq#n_WjYKg8U>AN+6C7QbGse;aM#d%i}OQR#JO{|;v6BkObGD16Xyo3p -W7_d_jYJ&V%p^^#*%8}{hRtNA46`9UpZ^FYoA@zAiO!bRLkE~6P@cdeCC=+zXkAe8B5CY60_oww*X9| -)n6^Dy6#%S-Z*3~6CT&5~FyXVs!vcA6)ctdH1PpXWDeboVj8yH*xRt|# -8Xg%=Br9OCd9ActT?5kbR+@JWn9gIwyF2IT&~V~ykF@(8aklCFl+Ey+soQwD)|tJ$SW)eEbIGvve*OF -Ag?R4DEF7LG+v%H6bix1UM0xzOp+n0Ra(=>z@1u!Al2yzO;JZ)ks{0Ax{g;8~mfx`Kvw>u15Gmx5yg))>)+<5NW0)?UIwFDTM?d7(RxI;;<3qc6;73^#dy9`Zafa5U(!>R>z2=6h*-_#w#_^cblbkJ(xm=c;{tcc4V -jqd&;Q>*Sk8~jmXKfh9K96y{GKQ3wdAkiN93O*5-OY9`2ilzU&`I($7~tBDVM0f%f03%YuVYeH|G7xx -fs0sW5xts!@B>*M*Rm#1pC4s)b_}PzOxbTPAMGCnXaf#RtOv6_PHsotWGL=@6s@D>_CcoGoI|kDo;ar -bwEAc|cw8gVNA$pd(|}oNS#pn)_A{ytdKyk?uvA=jwVg2F+DYaI{GF`8FF$b(M-ctf<*^P1QSb&L!3i -@dQdKFmvXuWbBsZXNFMPyS~M*PA!wSG!JL4m!6JoqYScN*zF@5I)2coe3>UZb!8xPA)BpE{^jPan$rR -2GD-_Z_jt}PDv_Fhg#!OaDJR#*D6Ibh!uGEX^o7j*WRPEiY$#(B*q{*LQJk63F$=*mHN~8m$uW~m@gx -XjKc|gn(upV~grX6PVQLUPQL{lG3I=^5u(u(=##8!GJawA}Fc?Td^2=t@8Dw0cahSCP1uOxbMzBdD^# -?v>GeQ1%7P@@^s0Tk$9CQZ(M31v@Xh`rDLE(;)uagHe1EM&4!P(3&1#bnbk+bGc%D&Cd%D(@S;JxXK> -r=8-p9)?JAMQSYY^v-oV9+OO+$oXB+@ZsJn5)}7I@OTB(4j!V`@_MX%D!PKk+S5&XE;hk-68Sn#^2{t -eGW1e=)6MNwf&OG4?|0u_~VH$=Oxz&*I;w$+JvDwj{*P_hS^Z?jP^@z&#zK@x-Q;VyjEXr%iW6Y0&$X -kcH%_hz$tCMHw33yL~SVUwYIzsjB?aY=Hr%-(kh*jF`^4RXp5cgkm@ITpo>cW_v4kNz8 -)g?9;2k>eVf_v8+v@vXOcUUDfJwR*@>5dsuOI^x~zVQ|KHyQqiTb^+H*a>9!HNu26{< -R!;+S@N!Y9%iQuzqDpxvORWS!lh+t(Mh(o!5kRYCf3fLWb|}?tVBa)cDtEX@kqd+!i6RBI2*zdw?$G5 -eRO5;WG=6mN35`(Bb1eS6B-wDt_vBgOtHs@%MyQd3+r)5*t6;zx`mw-J!X9(W@v)C5+Pb$=5cND>HT_ -FH`rP9h1#oEwfp-tUPaj7(r?>K+LT@My+RSvV!iydIJVcWz5%iFEsmk<;>X%bctshf*Kf=6US7eGkHl -R6j2paBZGAELFU2*G(p&}|AIy}vK+}`^0Ecu}?8Ju^;;@ncQe9?$5TAFZ1!9%SVCDc2+Cbz3X-X3?|3 -AT@qy#@fo_7*f4ZuET#MB2Yh2D8C{6btqW_c7=L=aP3kCz;UaB6@X;}diIq-979;&L!;B7*;mhmcJKr -=a5lBcOjLp`fY&lLwF+x_DxkRLcA^Utu}XbDkEz5Z4P~{2&LtN*oZ$=Is~xmfK4ysbd1BVgV~0m-@i| --Vat7bZt7e6P$=hx+nF;ONWcWaza@m64?P4a$QWotTKQ~AHu;O#iPFPpE3N*d}DSp3h@7T7+!>QoA~6 -$cnD&+4^9Bm-lKz9ydpQKBz#vciUiC~m0&Fyc1k>Ss_sHw%e#cvo*M6bLX{vDbssnGE!?}e_5QWyvVs -x9X||Ia0D09WxW3GLpg8*X+C+aL*cuJH!Zt7iH6}lBYn=q-9fiCq_QM_~e1J#lz^sZ`c7XNIn{P=W&U -7qyWsU%knCQawC1V08Oy}(xMFVC+V37jZNaEbb^&fkzpr2H;y_C8^e~6x2a*c;jY2xIdzVaqO6(&p8( -cdR9E6sea3#>&&p2eu9{qnlrNmGrt`~ar(MqZ6YJ8Xn+3;dT)US|uPFG%#8x(+?~czC?;`?^cBVDu`+^P-#p;pq}*Da(|^!YhY4NlYy3SA`1GJe%{I;^ -$)B_S2^A)~I@Eqbr74>T>JLg#XY8qop4bYocA|s7R5G8gC!wcEmiSN?TIQEw -t`;VJO6mbzCPu#WB!#+Sg-Q?pQd&GDca`uwG1rFpGw8TseKbQ83JhhN6bPX4gO@YssDL2m7jg? -jll`%|KLdY9}0sMkFfL}`-5a*5*n}tps=5c>%+-^o>4H3jfHybt2iwQuSciIYKlWvfOKkXPEHM@z-;j -hI*39NDik(+?QM=FQ!lI-DzK1+ijA7um|#=20}720NCA-kveV^Kgj%M3{8Gyd%l~OwCeVfWsb$RXTIP -F!K9GqG0zEMwgyKGaEiPV99(Zdsm8W#24iS#N`iXqV{X9(mpm&aI{Akwm)d96IB`hK*P -qdw^6SbEk;hUAB)6pNy9k?sk1=JkXS5@y&IG1*N*fj^GhqJy-~29Q#f7Wbs*7EzD1P=#ESs4>H0 -Wy`{Yq`@{7ToMldvyC`&0Bah{W=v#6jUki0x*UQYAx!X`)-}+nGC?~AE;i_HHDPkJkaa?L>=ds4nZAO -c~2W2x0S3wuZi=07IgI{Tj%ST4|&^Y_tkb@mUo=On2q%6+*hLHfLIJ@7Zx%)bAkkn3W6zKf80esM>i#^j8H4FjQ^qzqu>phANPMc -Pj-cKNC)XGZT2oMa(i0O6m=>7=h5Jjd)E0WBLT4c=3z`y86W>+n?QmG7{Z{WTKYcq37!KZl+RX5c)$e -F8Yapqb+77*>d+W_-$G!2Sl6g>5;xG_>{0J-$%x{L1|A64RzWIBN`Xvv^t8Ib6u7;Z*jSKR{;P`G_?8 -i?U9>2~O`76`?Y_`a6P4`biqR6|LsJ54ylr6dyZ|zXgU`5loA6%L{Th3xz@ckOKw*hV`TPD|h<*_GL= -R!n;@N(q`j?5_9?t&wKE)5PlKcr)0NCTm^?F+$QPKLqS~l9=c*F -?LbKb#R1x&ayEoZE{VNq=}{=xUmFy0Txhb!w;v&yK`oX?`<(drexX}ZilALap%RsSnn|M+)gg@ipUae6&2BNmSg;#S8gqrp^BZ(R%n_(fZ# -bTHn%51mWSFD@hWn2TH@l?XfK7U37mb;)FJzMK#Jcr49-m1zC;pWkHsNl3_k`!L*pev>}$sy%<6_|2YLY8&aTKVGVTtf2mf&+We`_NV#$f -U9W+XDNaO=ou$44zk))7)FsK!?Gv~;5Ex)H2SIV2lx~Af{ma*1R!08g}#)8go%{^+{PG`x@-cWDHTJP -$fxCw6R89SX^EhR2Z&)HWr0WrC>Wo7J0vr8-$-HJJdn^S3;Vai2=iCvjw}H$CgQ2jI0rE{%%zz+dXX% -I`at+|6YV#eL4CtfFw~1pIXz%1K;}~^3_XB@Xgb*i40QECwn*?ezoao*@-WS&xq{D=2-RUV4@|_LK*kKR9^ZMrHqu`m*}+_;fXlupn^?mFhr@CZ2W;Jw106?G$;^Bi -;Ri7;)-69Y5X^!L7wn{LUV?5PqIP2`>s@xpa~2tz$%JCnRm$>RCJ#H3OwvuZ^E4_J`ozAcFJSOdQr%6 -5!&?le_|`8wd8~_)~QMT(pmChWyn<`?zMvUtP41Yli&QMfYV%L|zPaneRL^w)UB -V%M4cUD%RQLD1s4rc3z)uCMCNE)ZOn}Mw+qbO4!`b^_V!iJY#qVEFMe5E_JIyPa&HDjZ24%gZ{O**4K -HLk}*b9{Gjrn6}$&;*W|iFw*evaP3)WAX;&c*iaF7)yDwdgFLB$(W=!ffz^0%hQL%)=xan7I9FHrrp+ -wI@$F)1Q!=Rp{q2N=dg!IxniSp{EXIdJy#?|REgu(hATlR7j4#SBOwPoMGWu}-?pSy5r|bXyEKBGgKKN4?eLe6G -sharb;eil}=7fNf8rTm&~C&lB^xsSP=h)znnJ4%7)SH(=0VYU2QoKx{u -bd0-2342pLqg)V)D{K|x&;CVPTWq>9yPY6R{5E_gN)9vvoJ)ePjrBH25Z7hJ*f=(2_FjF&vaqBFD|Db -8;S4ya{X+`HlN=Ul;sm>eKf1j`z3~farY@#qT%(fRLnK9+#*~AS@l^RZ7|sK46pMz183H>D7@h-Xgf4 -t$~<8)|M#?X=;t}dztMEcS$dBR$9cga#4^{ClaYAyQ^lK@`!G@lg*bWTM@3MS+gY>2q!vgde)hV#7Z& -pj9KlSKFyqYee*PXhg?YPR>~<)b@-!>xrZK~YeiYndw$&p;<}xM)fGD&jf;vc&ZQ}A(lFlkvgG!dU!Z -#uK}qji;Z;-Y1vOu?J1I`6o4`uz@m4lpWwt*HXJz7o$bEa--)alT4oaR>$)O`}bC>Y(Cyp!Fb8QphHH+)00(ka#3mL5zk* -)vOrcmI^;(A1?n(Bx?C|dl*l0VRKOv=}sI0#Pt-imF=LCM4#q$f$oX=!Z*>()v`6I4NA;aTVLZ%a!11 -E#Q$mraods|ByS!3sOXG$eeUdQYlBXGP&zDdxsl -#p7kh`F_X$9B;%~Wm|kFabcXCe~~4!qpwqbO)g=V0M%M+F`hdz=5WMaZAa^Bpni7%V2{R -(GsfWMf;(e+%VNxKPB17-+ApCF@Ci^$0|XQR000O8HkM{dw^<84nF0U+mj(a;9smFUaA|NaUv_0~WN& -gWa%FLKWpi|MFJo_QaA9;VaCx0nOK;mS48H4E5bdGqkQhU983GhLY&-O@bwD@lFbsxb(+OKeQuS!#%Y -OSP*^ZMwhIN7g^YM|Q_)$8A*_#lRC0 -7Jp!4@w(MP?!d>jbcHDI(ZNLuEm-nobV<4~3|#IjDt7M$`O>y?Mov=1&$qwe;sWO9hq%Qaw|InGBpiG -(0hjOt&+ZYp4b=u~Fp+l|RNN4F*v}-35Lx=JF{UBYN2VXESxOU -m&hYpQU=^_2ukGl$OfXQXXd~(jXY2T%+oDWG(594dYL}(NCHSfDHpQeJt#&H=S4>Rra3$Sy-8mjz}T~GrZO-qrs$wL#JQnVV*e;1j9DMZ~1`Bz#M{oXiePN7Z}O -<|3}I2Mk%ZUzlQ#mTtClz{y$}U!qT*Jp8tjGp5;GKO9KQH0000805 -+CpNmR3|!hI6}03b&I03QGV0B~t=FJE?LZe(wAFLGsZb!BsOb1!3WZ)<5~b1ras-CS*t#S(ohf3;;zmGl_7hkpeF7K;tRagBH=}6JHVks{J7I&)Mi+ic$QfQ)FAiYuS{!^8M;s7Jbz=`hunbMv)!i+Rd_;Vo-ILD=q2U(3tP9TliC7T=eR=x)AWOlin+tqe$uD3$ -asbDZ1lboj*vug60bNsy<3BE|o0hUDZ^$3enz)p#h1NB5@>=OCsV)%8!NYdhv>;`khj(ismkd{r+}=x8gQ_{P -@A6{@^J$hs)$~%`1x$!sY~2^98KBgNrf_Mj-RIMcP -r8&nuA`=!`bkFtR|C6O%KeO@&h1rCp_7K*$n={JuLIBHe3tD3xaJy~c1I6%=}w@rP_nD@GpjcL#wXjX --WaMvoQZAL3_o<<%(wR7NFi^mFtx|97H9w=CC>aqI}lNLxCP5Qib -D>IAacx$p}0YLfx3Z)ZDrjaGP~)_Dzu698ntn~PC>KpmX=|o9{9oM*;DPkm#{twUSOQ35P>m-TkGO9^tLI0|Pus}8Zi|=*@Aw*6JV%enxH%Uw32;*N8YUQz -eIdRDb+IH^|43D2qQ%=c--wE}5*&dN_j%bQJ?bz$`??L}{lX}ZdjJEQwzoT!nO(pIrU{Nrf>n&UU||H -bU(qU#qO3}qzUomMdR56suvRt}uu@oeuZDt`$F>dH#Rlf6p?F}|M+9>%+?#foxY2Nz>^x?ThdaO_8|m=`{0Ml_uBmg-B-uu9H~2%@AlvYT-*CVGb+6vnn$Usy9#6E3B -|fqL!j07xi%(ctg^}Xaykf~q8}ZJ8NAo1DKOEI>5L~Ke~}M$fAhz-nVE7<)p*=wrl8HxTm)jt%QBw*K -^~XlF|UI;!B4q}f-(VR7q#3$?A(JNB&dOomIBKjVnJK}2I-=*Zu8;+&Ewb(5P3m!ftw8dp5rI4VE7-~ -9^z^(arb0N!8N_)*yS9xzHf)RB#cv*psj>MaQQ<6!Msvr0G5}<<%q|*PNBGg<3UcmZ6L;X*zY~q2=$D -&FAN=L9M*LI{|BRqRQ=JBdxnk@t+6^UP7(Y>hdSNDixdNXBMTI`ssL8x!XR?(IxRvq@f~fpK?Wd!eq5*x-l`LIK4K(j(gWUd7F{^N$08AE=+&5%V7G91K9#)Y8GBJZ?%^BBk -&s{G(-ddT*K8a?#1@aGehybLn5+NMm?Xd%mx)-q(>0!x&b?aQ4k4)(PDBAi6c-b_b&2*LaKRomRvRl! -x5^v%#VTwVXkdY4fFOih -3xe-7zQbTSvpcukAquklBuvYP0#~`|pA#+JM -dG_f;)}r$!cUo4O(ts^o5{VLt=CpmdIv6TWm+@c`Bd`sTnkU}M!_{dz@`0}orb}eh3HE>rpWyV -c?>GtBf@4!{Fdp_mQjYh6fa*s98jL8c1Qoe-kmv7zi_T4uyE#JV60-j5O_l{oNCu#MFY(>=ZH+AiI4+ -5!M(=zvApMCRAd}%4lHMeV*Dss81o35@IkUz%BgLQn0dneIu`yk^JQ -LtOjm+i6$)vzq4e#O6Lr4l^={Of_PEerpkVwg+uu)wR7OfQ3;2gb3NvY01g}MoM)Ywa2`v>N*$QvA+j -k!nm3|jtO5~UMc+yT{%V4Gd`|9l1TWdM -U)5Vye}Lo)6h%w@CeXz(P;na(3IP^IJ3g+RkjiGUk_k^l&;8TN_XlzFbv#H`0y)G$sRJ=+$kozZ|d#$ -xa=&m8KSQotDIg(7ei?C{@%{z}o}=tHGtScEqPwr$(ivzq*KYSFl$Gip7fU*VbNKdpBx1+Mcd0;+iFg -21$20?02u5hL%yNcA}>+bv$u!INaHgR0#?s2RSW&IITX+&adt4iCLsaKqolo+|K|{3%(^~2s16?p37sS_EXO*S~=s~lhNf)I!2p;3? -y2V}*n45NeNr -ik3PiW}3c~bgtyBPrTo=|4L06kFk0oNbnoPWESd)ufOMg4UmbPfL{D?JxsyB5J6B{tui_aK;T0P=9<% -dNmT|Fb+>4E%6$ZpWjBx0^ydv!lLL|3;Yl(7dgB#a9T5`<6~&=p@$?`jeDm#* -NxbRdnGLf`WHxJ^H#hcz8!OUC8uq2_0U7&oIZ%CaB_9a)b^#jmMc|N|9RBwwNJdK+H)ZbUt~W1R<3f0 -&IJ$c|h{%`TXcq_h#-?*YtZfWSfpQ6haVp0+h{<#gDCnPO1&mNEhsEK|f89iJL3sECIig9bBnIT#32U -fw4i20{W+#*Ox6`Uf2*&T5~{i$5ikLz89RQ;%)t*Aq2VN6Szi(MSHrjkFMgtA-r*Cd;dmCjoYXFfs9v -6n;71;LvV@54w!?wCvxF&NfbYv6y<%v@RD}NE+0JAC>{GJY_M-R&4$<-B`*JNwuvIQtOl9<3Nz}n+Y# -iYU9L4>O|Tah&gOY+@pCsNlH1eMxm02)0#7hHEeb!u4n|-J7&~BQ0wxUB+VlfyjJtJTQJ!D7{EAFtuV -%Q=6dgs`IcBpo+xV|?b?Bb}2=&qc?h)*=N9TOF{j8Jo2<;C3ChIlBq435%BWr#yq4A`|n{V3UA --nq+tDUXbmcB -VnqijnLVPb1@C2pIYm7jApT2RoOi%z5rpEKptu3#!^E3e{H{i93ApfwFTv+iSQ#Zn=1Rw16S<`6m?a{ -M1NJMw>Z1<=0j6m>Iz9h*%=V;npv@$oi%}V&DL4(&K-EA@o|M;SuMI>LkTT)9?h_C2GYlBl65JdBB*n -{$GbmsL6S8+`eBF0y%@P0vqu2vo0YjSCCZfoInD!d%02~)v}a&bwi6fgR6bp26DjTg9XC^<}>P=Pkyr -nvc~FD#L7CHeSXxb7R4mV1b|B^VBlisLs)67)i^oO~8M(XtqnyrJr|e=0wA6|8Ygu+l}Y${Q@(`KLv@ -$Lj5=E9nz>FRY -=D%ANT*13h);gz(;z0&m{kB3&3t}d=u;z}o@pm%%h_yS~#M6dV^%}i*68ZXCsq41ssx4cVmv(`>~6Rh -sy5l0%fC-37iyqo7nnHwqdK8GNbJ1(-fXQ_t`}p -hmx-ClgwWNmcyMXt8s}@R4R&8mNDOAHmIR`I6*L5}uk)#XQylOJ=zdOR(HEWPx4HfGD}j0fLs-SKI5W -AH>zytE<Uyn#I&5n!h6t&EqnBsV{c6mLQwRS${osjCL@-&Vi?a$@`OTa_rC}Iq-wvvPZGRh9N -uL{06&+@MvO_wPJUve2sCL&m#?>!tYDp!4xa-I^Y4*D+(jt+TXZSMvq>&sEVHXGtPPZIamDpP%z_4{M -KKa_{`kS0fzxd2%QNz4-YY?MCeVVpoH*+e~x7EearAgr@oD7IPM}EukQGGcgbJ_fwrk_Pl^KeaKs;jn -2b>#(6{LrM^wQEOtGqOnupCXi8Wu#4sz#6#Wim*Qwz@~PrbJhAIQ5G6M~%@ml~Y#P|pYy+MFa_z|66! -n>7NYGaxgVpWifH%!MrvRQFQtq}O8ufN1FZ;zAD^KU!Sd95aDfg6hX@?^p4L9|m`%Xm6IZj -8?CZS~eIkkbqlpQ}r;aB$iuo9qCO@Zl36Qtl((I7ypDPuhF1>}s?cIf9Yfww6@Ze(=Mc4jW1%xix*{FIXa15ir?1QY-O00;m!mS#z5aH -4e80{{Rl3jhEj0001RX>c!Jc4cm4Z*nhkWpQ<7b98erWq4y{aCB*JZgVbhd6ia8bK5o$z4KS>I0s7}n -U`ETp6MZVoQY@RHe;*p(SnGjh?oRed{|Zw`R&~W2}+a{so{krxOngFV)2M~aD8ry(?H&e;zg{`<-yj -qTE5>Q1l6?0diXq_f4#HTSP%|=6Ap)gEX#^oIG0%N(%i}YA2DEhlL1{sH?PnltO-GA5Y1Apf(dRbiZl -xnTol1tl(4Q8B~%G5YbpO4RCKJW -vj6IiLojEMUAWrRtC!_0J5!U^x~fHH%DY`RmuBl<@9r15Psp+!5{FY_$Un&!i*?XTC+T0^rPU~HS~ -gN|WKY3E088Vm7DN?Ivbv*eDr?AAOwA7|)JrU(xnONxs?n60M>P1)=SfNRXd7Q6i&QB=AG|J2_5{O6@2?s=((zY+DiI0+`0uX3ooZ-JJ8?{3PuG#&Ja?q0`qe~Ru2k^d$l -)TF)LKuIiJ(K-R{7z641$!w^fUX|qk9Q_cTX|OVDTJMN$AD#sea%0e#MuSy+q0*jk9dlCHa>l9g?tjUzhfUu2Egqvx#)05hU+bgh6r6CfA| -s8eA6DQYmXQEB6`8V6puDFLCXzCMewl~kVTxU=xuPGPGxK-3LVsDr3xrxb{*C`_*+;_6>|G$FV}yV^! -F_&7meh_(I`C47)~)Q;S6}6i0y%jnDF-SXYdhQ+S`JaA8>tulC74DVOrF*DC}bR=J||DXA16y!CEDHw -2g{U^tx19*bUUESH2o+`wvh{0|XQR000O8HkM{ddD^4OdI10c{{jF29RL6TaA|NaUv_0~WN&gWa%FLK -Wpi|MFKBOXYjZAed2La_YJ)%!z3*2H$w3W?=OW}#$e~cE(B>9`+32WC%&y&CQ*!CIcXrj7v>njZH@-J --o^x0&R*~KC -%i8E-TEUdxaCV&EKXlsS7z`*-Zuk{LgW5R?@B1iwNods666S4fjP~jIV2RjrJX<@>v}lJy?n?3s82y)Lmt_7zR(_f4MwR$kZK+VwcLo8%6% -8JenHCC(ixPp1>baO9KQH0000805+CpNt}1y4}l^807Q2H03HAU0B~t=FJE?LZe(wAFLGsZb!BsOb1! -XgWMyn~E^v9xJ^ORpHk!ZtufW-pBbCaG>vX$0waRXq$JV=T(@C7`%;ho?EJ8MGiqsO69dD=ix8L^wKm -a8DxY@Zo&9sq7-~;fz@Vx*Zh{MN+VpZowbv_fjc76B^pX}}J@9*z5>hC+HTb*s{T$Ot7zrxSn-swf51 --#sq3Sd;NEGjM93njKqv60PFfQRP&3IG=!B>wrUY!>)>kprZuI9Kh0Uo0+^%vCcL@Oyh>A3}2jZ7W -xo@?7a`*OX;(_B2};s>~g9gTOcVaJ)yUwzY0;A5{T~)nWr!D7La`7wfv&0D|QhfL&XZwlj3lY5{M?8m -rGMwI$lwF6o|t&ntzazHXWt7W)nseG25PcddEyb5pl95Z?U!pc=bq>VQ -3nV}#H;Ul*8+HQ3xmP?s=xtsTok -w!f#y`E2XDqCco;sT7Y0r1qchaRuuuPBy#xzhK8kcZ$RGa(n2K!7VXW%@%Uyd+ixGD?M -^RncuzFp$KKw-Oq_z6(yA5pn)pa?Yi_{$s;EG6K?w;71MJ#*0~lM$vb@3W+olF2Yk3B$uTgo?0Lx^1? -_R$;dH4F|>EiwAi_;S^cX$Gzphd4#J{95TKs-JA(rdJ?chz8{W3N#SI<^^V^wewADqJ#X;f0;wlpFog -YXlszkrfC*-00cfWN+`)>o+ex{BXK(%lhHu$CFc7*3+ZCmv4VMef`tv;^p@re)`+u{mFm4hUZ@%iO2B -28XR~Yga{Za)v8>ZR``K!M!_ami6qH>tpUf~^8SnrU#vE&y{L1C>wrvcA1U7;^`5*ROEJ*(^`u#O*{LK3Pb1(wLZN+>Pii_>QG>XWV9K6(#wo_G)>4JPA -1W!FKIKI0131L{M`ynT-^ce$_I8enG6x$?V>3!V=D4mtG~}ro1L0?9HG@X-`qpy(Pq&p{eLR@lHV!Wv -pT;a;z;2v$|q=19+ChWx8XiofChVr0(hYK{}Y8D?F9pFBX7i+>U2xmS$p^m_OZ!SJRN^KC1(jX-J7jP -KUnWq_v-`ScN**wW2)&%sG_{}r6?JM(MfS -B^Gn`<=WEunc8PPul~MZh^+jYXth0^0^nyI8*)bNlXUx}a*t{=$&DxtB?(uHVAqX(h(J)flLF@Y-eNe -F<_&o;Z2An{&P8xL%Yz;aTqj%(^O@+DwNPr1=651#~lhX+Hh3y4f47SQ*)14r&MymuleiR(MV+TvTh@ -%0aw`x_u%0ca17vTLFz(6dq|48pA;*m({nXfuR?!%F%egrgo2^twA3lZ~m^ZZC4KUvBVB8FW920pqE^ -+Br7ErBGZb%E%?*YT7`=4;l7bORoJ2<2WWk7V0MT^03CdxQjp6Cquncf?#;pb?C-L&KD2sy<@}pvN1D -I-57H5ht#Vz3e_d8ijG22*nftH3wjs`F-Ma-Hj)fo6QGM@#z6EhB@@edn*@tc -7cGqa%?0GS6bjZ&MD(G5IB$%D0hJlKVI+^EBER#ncd;SA{b-I#*_#i=dL4+hA!7 -Sc7HsWM$+ygT(AjulcQ$dc9GLbD%3SeBdpqItAl&cUU7^|Fd0277n&VfY=%!>+PTtb6RK;48T#G7 -)MA0&+)<0NR8Oh;C%oEqt`Qm=?>5;RbGOKqUnPyIVHDk!j4hU$)rTWu^aJ{9{*J~I1!sRiPlSAopB+3 -79j;4tp)AdGr79tw}gC9S+K4OjO(42=gP?fe4ov%=s$n{+=hg0mdkqsk6^cR=`Smx$zNWdCZJ3&+IBm -8@&x}mE2^w+&YzsC*2Uxugv^LtYM;kKCO!hZPtUE8cd8SVbyc*Bh0&QTQggHeDWYz^Sv!D#5nJ$gr*g -=I;MdFv<_8kgd^IGnAQ6e?JF+Exoyo=g=5!FyvK|{Z=3 -hZ;dgPj0%`gW~4m;T2K^ODT3+LkeTp#l@=)%vsVJMI22H~t?Fa+fkUHfkTfKEZ)G4-6&z;htKl$vC)M -{l6v&>kIbrnEPqm59IQzI#QDjS`*)augiXdEulTLSwAaYfGMx3iQfQIiM!JAk8)>P6}T*Iz-r} -my~Rs6O)E7th$(6uJt7bM5{3puKGTYBFVE&JC#`;0~C~NjcR(gXeO3YP -(rfrPzQnZ;Lu|Y>Ag5a^u)@hW0J&bzr))TtBs4*seeJDXS?Yk?TiH6}p4sC9BEoB8Cz$0v?m?SLMQK0RBo3 -Z*VL9vos%2`P=D9+TlRrVKEVJ!rhev}4P1ttqUo*lIsaev;(YKJM#|D#=#``>%#^tx()==X|5E%jmRO -p}xAV7@bHcJcA}=;+RE*i!}v#?%FrpIOT^ri5)9s^cJ(W5IYPabWDVbp(bLcMK!6hX|q(<*0k7;6o4; -*9?Dt%uzWniIb@fIAj!Yx2v$JM+NFH9B#IgbP<1qsdlN(Pp&hlzO}{iwA2G--%{wco%~1V=#t -j8`;bUw~YS}#+n64;Sib{*JOddD4LGFC|ZHf&x+B5K$&KAj5J1crnIeyFx7tmj6>Xxm1@M5JDM=5c_nMWT)q~!PPObS`E~6<>Fh5X!UgCu9_dr{0eSH*H=cR$d@~3VTk5fYa -?2Pv4RLOu1V$l|fnXgc8k!zM;FZdFel(+f=7CYwsXjUl%5*Ls8OpxuiTh*kKzJ7G2J0y5cJ=|+u7?JQ -71zP>ZiXv@6f$4?3QYND)kfRmaNRmU8e&1hI0*Y|QatwlbRu)wb0!hg-Q*`$P;DDS~rY6`s3mIlrBLn4=}_5Vk(}_#Ldnuy!Yme4f@q_c9vF^XY4PsOOY!vIo_!5;Kr7cn -U@}Y49mfTui-wYbm@2VqIh|lNVbf~dHB&NW2P5fy-x@zG={|nmdx=4VTfi2qzM|BDrWs(ln8&#NST&) -JEd(?Ns4x_uPO0aVi^yP4Y~=X{orQT<$;$+DROhI5zW^6`J+3LCoJdz+n?6>VY|eF<-JqBxO)u=N2OHy~BN^tHa2|&#JkQTP<$t9Vjmet1xcFZ%4oXX@4>Zoc_%)3@h8Tr>sag4ByAeI9 -gj!N}MV#_l*~a*uDcX!4e-ev_9Byfh)zeWYZcdh?#2E=?zqPkG{T(zahdHRu -VgZbbLTPnKQJE-r7hy6CAg#uERv}}WO$XOJX2+s1gm)o%A9ST2F5n=6IuvpH4^30@SN)eFW{Mp|DZ>j -BmwT}bvQlK<%_vU{+z^szF=JVFi=KDA%GJ2f>au|3KsJnrchW&sagm%oFlN#Vh*`qb*R&&x0RY$LB~R -beukOfqK!dc;(CT#;PSG^;5th1mj}RQ^)n3BR@A~j4}fS?*)T|3M@#=c0H{@rV{kpiX}|WMkLl{wO6uTM6B -mnwIJrM6m2|Jqy?MSP74V){?I`^x$22XVu$9vM3dMY|3P%ahFS2-RAKKA$*K}xN=5s-1PerqyUW%@C+r^ZU`!=ytrEpr^8D-Y%3ys0pidKi=RG}tF^Iz5Zdafv&>#kKjd{g$2ry3LWb7 -ck`l=w^Qpos^u64AWyqhoIRI-MZ>>uggF+B!1YC1o1gRTB9B}=jqaf580_4!4}2=;+!LzU7KA?X$f3w -xzoUdfu(6iM>#E)2&a!6$ -6`HL5aNVXK^%^=hG}!5m3UV~~?{Of;|JB{Lhg&g)(nhUiKr7X>w$QY=XTIT8Wb_pg6pTzudv0!6eTv5_%SmCQ$}$~6CYp_RyX#no?zxZ;X -qwN3>=ni2Aw?Z+TY|O9PoBen3(yx#S)u2IMGU7CKFms^rls18MDD^*EEZ5t*wg3HtD3Nq%wq>9!U)tM*B{Y*%TF$|fIWDs)68%%aU8iCHA+XhqfSkaHsM^iZ -mI=azq5Dkz9OrMY#ur%c^y<_6uaa6yHY3_O={OTsPzb9T+Bc8);AqK`BStm?KnVkSxjM#MO6PCMibDGciwh3m=SsN(TTo_nPT60~gDZ71@uIo`6?~@gMa9RgI(G_IzFbBm1YFWGDMtm -m1ylzhUa0k&&fM=RFwB^S`;7c9+hxN9SQbln2*BE4ISNx6qbL77*|t?u9)Vo7$B+(Q^b76m`@Ru1+#| -UAq)Xp>Ua@x(iqU?-voP5@6)fN0`a%e|XD+=>1v?qL#_K;2GonPfi5OA3{4Sy63eF%)Y3fChCGkk5JK -##$XA{x4wfSaimA1yE@Jk6yZdII8EkB(w;V=fu0dPcPf@HebV8&pl>COr+(M7-01Laq-(m8~~E?sSt) -%-6AjDPwPkFfE`X07)tQZ?u^rzs}hsRypU3J?64Lb|S0&6H@lVy&)N&YVwh*A8**DHjGMC@Q8a=247F -464fAj=xx-ov9ZKEIxK?O01ej1IN2bgeuvXpYSW}yY!^MTBw_(3koFm1VF+*42C`6n_+1Ds!F*9hT!) -!F?JF^-Cco3#$Rf2Vx%K;t>bcFyA)U;A3Yg-i~rIMH?CR7HZySoZO#=I@aLpI8NXl&F>2ovhQKPy&NK -1V(O1Yl$45s|EtQAIUZ4TSz)0$0(S=gQtDtntal`jI#*JOUU#=ws21e)5V_wJSaXLq -v&{nfR30yW^kG=S;OLy+zFzdIQ@tP_l}VR6HzgS~9 -W(btrdfb-~s*=?z~GNTernh+kx;^T+T>8Q(`%SGwe2J^oF8fn&}D|4usS9lW+-iQP!LYsuO)m=iq{}V -978!W`wM`7Q8`?vj=>6(Wd7vs-;%&tF*Bh->q~#As;>T_n -)-ifG)}J#3`JofE(GJl#hw5QQ;K9$yJJHUa6{1a%j0MEDzx5RbLPCdH-q7m;*`+BTM!n!_=V7s~yH;gMISjj*I}?NqL@pmkX>jmJ@0j#A`mO#Ah$4k;Vuo(%Y-tY -LfZEh`d8OR^^m620BD2giv1VTqrzvPR>v;^&D7;^BlzdoH)w|g6-uOcA-0Woz3Lf^+RBB3Q5kveZEh_ -b{hwSEEQbuq1j>gI8%E3Cv(zJGAjoYDBqdjBV3EOV!LfdEo&>qCsaf01>hSz2dEY`Hh;6ICByU9#Gb` -G%T&;|l%f|{5hUFhN;YX0Ta*zoq|(>cjUIQx^<#3?E65f~^G>lES_wEaDQhwm`3-j25j+UBQBeR)+Ex -uzYqzh83*5qzB*47Uv$&mc(-PzmNuHw!Sd$XKCu30#*kwM4bsoGCJR`8Zzzd-ny(oi``=U~9r# -m7*(0o1$b2nUp;MRCstEcgKiC9B<0a1-BH5^6w((;&F}$?zjs|0Ocjw;=>IMe3#DDqaYSKE#09To2f&(%p7rI?%(&=)53YkDbdeRV=(@sd&{~Q-1)Q``S6T~~4WJ(c9df#{!9XXLn!3 -~I9-bToDbQvs4Gldsovb4UMF==umwb0;C_fjg!ar(1 -Cc*nHfQu8S>Q4Ma@AwFZ*h}bP9DLmWC4Uy&cfe9Y$bow;B&XWeh0oILisQK^0$LIG&Qf!Rv$=aIcNoT -!R~=2a470hwDy;E`m-~zfQpb_inGg8}z_U(zk$)@4~(heE&}B81!&$!vQ+%RSr6u3P=1%Q@=_O+$3N| -B+2kRn85RnzI2;aN8QnK4^&*#Y@E_(0IN@9{P;Z3x4woVm!0us#vf^ko3USF?YF{0g}NNP&T!x^@H9% -5d%Dy=ZD!Au_=y=$HbBh|4(N)y()|G?P4)LJ7B%8n2pBb{6pwU~m8Qq)|9u8iZMsxqZ@fd|ZYeX8p>)+|Hy;y(Gqrt{WX-^?_kOKw?HFi`%V#m7u26o1wOGEYTpl -(oS<&;I$C2l(#C%PBAXYp_%CkIa2Aee!A*gFiMDcP?$IvOqhqNq1I -Z!hb9}CYOHsEyNZ5d!1$@SRoXDy02ve@5<0(;Y=!oaR%2@wADFTXMTWJkaC4o<5sZR0P7lo&CmA#@)E -F%Ski$q3;q%3bsO=>Gu#%#;I_lhl;zKJeSe!!JM4FJ4CQoZih1RQxe;#>3HvdS%;so8D)_V|2w2@f0# -O-I!Ail|k}mfiDC3zms0v&omoztbC`XcQFiyMZ?fAs&qE=dzTM}NjaN{jjcE|5_}~b)u{}cW1~v)Hsj -*mu0kcjyUge&ot%0u5m!?bUYhk9LolXN!-dKQu6?yL3{rt}aWp)^IDI)--8o*_8Fwdgq!Ydhzpo(rf6 -`z=6{)6MxEN!~g_YJLRd?|U_6Fbf%oyA1K;(3uwo-gCN25xGroCYfV;O!!k(|T?jQPAPIrGN)5B7dk+ -1GfSru02pRjDiilE0_FpUXiJrn~Ly8J+OV~_E+m+IUviWiApS&LD`e% -XvH}kPY^MZ(QN2GadyXaT`KTmYpx)0@a$ztAP5+iM5uVM`1tEDzqX0EQndh!JWq^SI@gN!~B%57M||8=uV>UiVjW1m1AARiJE8$S^XqDZmWG$H@8N_I1#kmYcFInCd1Llj>GT?`za-fM$2E -L!StD;=PCMp3UzoL{x!zTFIFEj~`!N)0z0;TnS(q8y}jJ1YKTr?WHuPrn1*lV&)#q|9t$P2L4RErL%y -tbQxAx??LCN=oAS^KcZich_x?u4$m%B?aX{r5*);M#q;V?N6i}zxw+4kMM{?gik -r6r;${wy|Bs}UPjBTXe8IEvq(rlnns-cvP66G3x<(6uZ|)ZXavTD -`HPwc$>-#LG=7UTQ+%Mi)oWB!%=?FHf>D?GgSJt`LE-8SeA>xNk8Uh;yvjounfU7&q$t+1UF5>?9@~B -MXABzmc!ih6XbMD|=|2XY`FI2zhBAi#vI5qH_O(k)LdTq -*dKt6E=nul(maU~fl+|eEjt*0g0W*zNP@ped&2Imo2SN$=Wvtnv!)?*2=#s8f4U`Z=iVnII9G&U^xjT -1$dB{n;G)`h%ql%wPb;Ew?CC?db>4c2@?h)2>_L;l<2wgv!zP;_hJ9gg72@engn`nD~o-J!}sp2JJp3 -;yjJR_v?hy-oBUCoOB>(Oqo4Xq~M~>WqFqYJpwAT*N=YfZLcYMm5z~!JqzF*}8& -rSyY~t^BAw;n~C>q6j0iePU6F6VEy0jXh(N4D|_rOZP2>8PmL62HW*)hlg~?i5aJLH-ZA>$P)h>@6aW -AK2mm&gW=SM_9n8NQ000SO001BW003}la4%nWWo~3|axZdaadl;LbaO9rWpi_BZ*FrgaCz-K{d3zkmc -Q$-z&6v7l#^+v$?SGDRc0K=ZL*iIvz@)$%X%b=ge=w+sS>0ewUhhX?|lFu08)*YS1>fcbkG*Rsww(tI?(2mCq0A4f%~OIPKh{;8)N|cqUYB!dF%f4PFDLZ=FZgOBt1 -D?5j`c`D2ASJ|bgzkcS4o(UOzn&3r{5pHe|0fGJAMB0?DXVf{_5rHmlqny8em!&sa(ZPUeD>}M5LJlL -?mnat=l(s){Y4?zLvZBO`Om342QA+WDKYR5YVEC>+D7%el3k6`7x1OkA{q=i?Vzj7rQf=W)%<_0K6;! -dX}CiYq^Oh;$+cOmS^>@pDPW|onIVZyc`Vd?~~(`7pL>{m;Ze#X5!J&(a~TqfPpoqYgIAQD -#HARb+e*rU!W+>lN_)xbHsBzB~*a=crF3;8(0shoh{<Vw -R0tG~)u+O4h_rff7Ko8j}L=q10rYt1mg!v8l*)G_$v;Bkjd;16DaQ_G588IaSD$3ehni$FGDiSxc%2v -Cv84g>GENprSYXn{`5;^J+Tr2xA;(1@E4%?Vc)M0!-L@?ql5@rsojRM6N&T>5NfewiioV4+WtNIK2gb+6Ffe36hY)E~5oMuGjPD|9RshsF0h@^r* -CZUH>xsA-BNGR!fvy`h8Y2O342N726zm2-u#W}Uq8zkC0O|uYxEu@|didWx%|NEf;!GPVgMMqb?$!zK -Kn;+lBaligOK=ISH-hr30SW>b@!ZJuf&U7y6-przmokYPCB^N!)gC+tF`J2BhF~(bU}WTQDn49(BDrq -)LM$s8L(6k_C;?UoEMLBhtWXCst?f1ATL7lL4-qJdQ9JG!=keikFTYctj@WmG@IKsg2ya?l;&wFr=O_ -tMF`x&7FykY<&OTV3~v4Q5jbPWHeu)Ia6Ew|4%Sh4HVeSzK?SSb`E{SvZ-gd=!vIT{6 -AJ+vx};MPKm5^5974zUk@K@PhgIg@=7^62Z)O92ACYw`)3-F5j-J!@ZFUxP%HuS<;pftkyY_TFA&iq` -)iHkNE%L_?G*i=L^s;;nWe=bAbI8lJ$l-R?%?fvvg1yRLf+2xx=D}XbGH>O)XZRJJNb*Kt6g{g)H7kv5?2F$p=uz -mP-SurN?4VkJw*~1Xrw`8B{+q?L1HIYjPCEVg6|K$1~4LfuzTiI#kUb~ZaPY_FNOsKi5a<l!%Awd$eBA?;1q}Ih##Hl0L7PU1B69@uF4zwzkCOc@{ID&{+~)|UMG -kIg$baD95_ck%Iq3K>%*%v~+VCn(YHt6g0kUOPsSlyV -PN#9t6Cw1(eUta*h4Ui3_*@~nkG~vj>dqb%dgyU__dmTtq{c{2SUViq>!_N*$ebBc?}#KAYbO?Ex3#c -U_6R^j51hvPMzuSRJb8(efS+>|AF{EPSE>DIR_!2P*1c35H=8uL9yjH9hldS@cXB49zTi#XzU*j44-apF?W};fR7kh#jF(=s>mg}>~ -$;P=t*WZNqC3~V>|$Jo45enS@|a&kEQ}=@`qXP47+QkK@5m_f>ZmTfn17yAU+~AfMt_GOuT_OsZ2* -QFqiuV2=v<@;N?!^5>(jCThs1)&dD=9SyY!I(Q|Et9m`02>a$@x0U$&qKXT(lGTARUc;`&LU*5`;+p+ -ox~u8sTirU^mfI0OPssV~4%X?qEK{`W*xDy;yU=~ZeclDYSjAcuPR9)|`)vHb%N?2QJ_Dds$a5xa12Z -d3x=^7CDmhhPOsLTI&}I(K6vW{u%XnE2L5=EzPNHa -Pl#*#ax*aH@8Dm3?OE{M$)f)RFcVb@7HEH^nG>NTZm1nOxv)s%C52^&Mj*M4%JURk}(L{sI6IwLO;C6 -9sQ{rD(SF_|LJ}DeJAHlwyp!B -XuVc?$$4sFT_Tzg8KCIA^b0(K)#o3Kqr4`1eac15{%=#TlW7Q5^_K+Kn;3f_H#iM_nN#1nC=Dag8S^^8>dftdtD-_ZmrMGn8$boj6(n^z -=$!LUMnN@^R9ny_#txEG!6t_yI^~QgV6681*1i{nj(e>{d7{woot+2u_}#7RSH6)`!M2{T*C -#R;(-A#%SylZ(}+<>2`&!k_Yl8&Kt0niopiFqIwm)9mBk=;D$@ -LnspycO-=Dn#U^kd=1-KT@o450KXK(-OZ_MVz-+N{giMP1LahqX2?v``i8}MmO7Mt;l-8kV5B(HJ7IP -eH0FwvO>P-LhqXsj&Fn;;uslz~aP?KZ(-d#$Xh%Yq3cBhun;B9XJjLa|# -`*VsKj6H-feV-T!$tdLcUv -osa-}0U_NNWNB_@Z_IHBVj10SfZOcd^|EUWSs76?OuLCeaj-+|H4dt{!!xK>hU$Lg{K_bZe2Dk`h1!* -#vM535yzS+@s@vziBgjGjbKT>4ob!OdpOW;$J=q@0(u3612{a{^O8d~tgGeCU(e&UpC4>BZ2U-}4f*C -B&47C%t+l(h`eD)HcJt`5M5-RjS;anE6GSydrU32b&uW48gD5-n2r&oV1Wp5VRBg?(rC=){L_j*c#$^5AbPN_q?0p7 -~hoinz+38}Uka3fR*c32cXdzj88~06gk^9@llH=5yRFw6q>-rHm;*9kk*js$l$AYgT-ueOY=JSMi3PI -8Yz9Ve}A{9QTteStkU;6h`4Qrv~uJx(sf2Z$n9cxNOz>e4Z6qJ)e_8A5R_GoQh-e0S~gaILj@1!0k}& -WPvm;rA@lYI9a=3ro`FPg=SgADLp)0a2Fj4XR5E0+0g$^KRjKuQcHeP>*xjLa_;U50DR70#jzjv)y+1 -iZTYs?5@-r5fT#N{BAAa3i+p2_pM+fG;T -KGXF55wF25Lm+i1T4Df06=ZS5agcz*rJcYU&kM_O|ub2 -vsuc@D@ZV#l@#wf9v!*LGF2Vv&$CU2#ebG`;Xe98*rHeR7;v-Q$)E!W@&@z@NJCk>z%`XoeA7xuUG+u5xlJccEJRBE2^&oY~^tkOtB3hdq{mOY&|qF9C$~_&Oj -%TG~ho`O0jHe@*XJ*X?Iri#)^+1TaWo{XC_UKn$p#o{gT)gWsC1DdCb;Cr_IyFL@XN*8Cp(p>xW(G#l -lvYN4Dc)VQXtq*OQ_TnOZMz)>m9U`sLHu^SSi`ostVAIt42$Wl@E-M)BadBA1G>nnbv4Wow#F@A -d96QMzhojF24!DNG`EBWJDKdD<|SLCM!)=s6zh+u6kR!5CMfn7-vuwgV{wf^jr+NjQqzK1>7ui&X2Y@ -v?n10%Wu@NQVc%h8+0w9=S@p$4iMz;}`pA? -%zQ0_EXyU85#!s<7m;X*%t`Wh@th&54odeyG2FmZ&bo)GiPM~<0{LgvY+Xf-vC`u`yGGypMO+kVL#&! -|Fe!)X1%4nk*)c1qaCj*yXOE+usU`<_YA-vC$5tkbVs@bo%OjHL)f5$=Ej-N^TDdo$)EL4d^($4Aca#>W>1dJjZzz|TTA+c*0#yCTjM*{Gy?$Fb+yPt%{DLNv@MC -w*%hqo@C1xHxS~V@gwh#Ht2A%87v-(UOWgb}#cd1^Qm!ygxyo*MbzI~uRh -IaC0EnMNQYgbx8?~;`OTNH_)b45P%nf7XAS6`wo9OKa2RUV!K$yOy%nGH88Xz|xHsNOn(#m!2+Oj}JD -P^|{XW+DT;4FSuey30r1n`@GI=acj_1$bzj^-ZRA31^9Vel>gQY!QpN3OHvh~aehx6tGuI@Q0?+L?3K32I% -@(dL|%?<@wjH0dFjQxqdT!{SN{Fs-}T(4;;mLR0)95g4*HPtVxecZSl~f=c~^d=S6kR08ud=aO^1#XGGd(&OQ!&t?P*^-L~V$Y7ZE{Y?G8eqdcHpuux8_5Bxp&X4}acC1FCb`Pk?QyJU!HA8QOUJb7Y}{nExx -X{KnYxcZoMY5ApY9u3ko=G9i}>C_~6xK>kiI`!xHw=QAwx{-29u{y)(7eII%IXo}ZA{W_{1U!1)77hQ -j6x+2mIWj{mOROV8jqq~>EeSZ4t^y2iN#N_s3moadM0VV(6+riz3RQpWvj=6v;*e>chJaFGQ8#6p@C5 -frF<>(P}40s1w_Os5ZsixA$CgS`y-YGg-;{f9$s1=>>Zfk|OKm|g)v`M5+((nn3ZtT|M!Ppu=r$%UeR -|ivO=#~WC*+J(mYz5GrjoT7Ow>HcRO<&@EWP1P&k=ju0GM`6^lO)5FXyBUdSc$|YnbG0t2jYhY?@x=b -V%+UzU|154bxdLvXE~CDu5GY=@FtJW4OjNEz+gX^uk!dSR(VlH|7iHPvLVWsg_&N!Z7Lde_F|iDd-4a -Ql`a8U(S<<4=wzx -!4kqDP`-vIJ)y&mGjRH&b_SFrg+MNb0zLRFaDVuckEs*j@?tG(miuR{M@1YPw~_JRh=*HGG_*$OM@RID*=>N^OleBW!MH!a5H -;Wy2?Is7%PDMC-Ej0BJ?EB}r}v3mNiW2ekP(_8o__?+L{K2znzWw5nm%i7L|G^3|#uc-2a8cz;>d=T- -D+w^qp-)#dIxw0B{BAndtlgz+zX^c^4h`{13$JzqDg71=?)(zl|6H5rk%l3*$IIG!HuB@!?M@LI)Mfi -E$%2aCs6CbSE~EbewU<75P6bOcxmFS{`+2Ru!C3=_!5DYLhO2OG^=Mu5booc!uN$*Dm{q)XKH -wWB0j(4Qi9sm5!bow6u@&wg5UtH>x@$#u4EC9>&g-EL0*c(gLmz-J!?8db1l~5VFEd>Opl2`&c-5J_r -2fq?4YnXIde5QCO7lv!!bPuNEpDm;xl`7IxVqfa)^grI8o?o2L?S|wKan@`2`7eZU0#kH*_f#8Vtv2 -$8cH->!mVAE6R?G07Pj0$dr_k?Zq@y>muo&?B7)k1lE%fmLzX#nw;szI?c>iJ|mZ_Cf8)ZyeSvFqmRP -4Jjg#(!>P2-cdX=J@2t0eYl0TZi-epj~~+{We{krLbOk3ww?`7;Pr%r)126Chc`<6zmxi5Y5a-CzsG3 -SP+Scp)3zWj7kTm+mpd-g^>>2C=z$yXC|;C0Q&`|KRTc&rJ^wTgp+?F=+v!9>5ywCRw}pMTc0t>0*sf0EPPMrdw>zb@=uiv`38Ke$p#Ef?4U -~iy@EDQC0@vaL2J-3EsU>Ry7}9O{GdC+kp}f6kx>(-VU78rDOfaoxSya;E^s{FS`xo<&Io2x+mFQS$y -3=5wZ}BH@Y5+@904@j^oidmlnHFeVqF9!fT6-yJm@jo&$XFP7#2p;t^fhJxD;W_0`g&5gyH?tKRWJ(} -~|vspFV$D~A_$f%Z7(*a!Jx=tqBau$~6#2@SiuMhL=PfbthG;TGP3h~T&dHQ?Ajn8`%o*%TM;yhP$+S -{QZhwZW1QY-O00;m!mS#!ZWhwLf1ONc^3;+Nm0001RX>c!Jc4cm4Z*nhkWpQ< -7b98erb97;Jb#q^1Z)9b2E^v8$SIurCHw?b}DFp1nc9CQg|75cP_SE)Jpr^LS0D>|@+d^ifSxJ+`E&3 -Ea^wOu>Cn-g0B-u%uOGe};isH{l`GdIp?ou?i)n!I1>m*C~t4R0Ow(-_atDQ-&}ywOp_1H{%-E(VEj9*-{4e1$A39#M?*l`8|J=f7AFV;^g)u|38wK -q?XxMqgsge>-5!h?NLdJc4jQX9{h^`s#Yh%lDneGsKV1S;vXlaAF=+D^mkT%eb5z+;C<_ldAmpDrB3~ -{p#4W>t9Foz2(XzS+TO_bh$^}wqPm~IU^~TVt!fDyy2wufM>TdqOia13!hOhGyZlm$Gxl`JE#u(l-TO -^NL`B_>V{hN)cFiJT@`pa?~ePIxhv=%?8wPapBeZ$aqOy(zZSR(Yq~ -R#fY;bdc1JQR74LuyxNk%4 -KzTF1fPI8z^DPPT|U|rW!tkFi7;;$4a9HpU5Pdt+mB274x%lTA4Tzyp;y_7GFl8dSYyu{57)EZl@axw -huUrD{k8|OFt^pzUUUox6{2w!hCH2TJ|98^6|{z(N%a2u`#RgF_RkzFV;Q%-^i&U=j+q4^XF_fJ?hI# -e_>P)U*`P4<46B|i9Zpni@zM;LLdco@h5^2ti96s6!DAYJ^2}Ngopr!-sk5BzC=|BZbAE!r3*5<%ox2 -B7y#DcPZE5V#^=&ufp50?vY7fWb61#SaEs++_Fqs-0|XQR000O8HkM{d<{-)sd;|ah#0vlbA^-pYaA| -NaUv_0~WN&gWa%FLKWpi|MFLQKqbz^jOa%FQaaCwzhZExE)5dN-TK_n<72eyWN8-is(kak#swi|{N{g -4J0EuC#Pw5X6&JY(2?`|gf-#~75-mW!Zctd`x0i&!bNd9fT9r6i9MhZ{3U;>V}<5W6uG$8&ajQfnkQ8O-D!essXXi)iiC -KsQU3uB7=Z-=e>CU}D&dV-0YQ$ATrO_6Fyb#P#=l#Er9uE3ApkIDdKZYLCyTue@N>?tDA2LZ5GF6{5@ -tm)?{Xyf8NCOYUHoFl%hAXn#0K^XQz%XCrK&?I#zK&GE`GthpTN!qa>X7nCAQV6I5y&uM0BBCNG7W0< -z+yM6IGSCdXXu!SmrLhE=y>9dXLg6)=R6BEBoCyXX^vC)0bR5d&xeYg-Q$eUiH6l1fxhZ#uPiy&7s^BR0rN5W)By4>r< -P8eBjWb+=~Eln4@{mOBu8B&)G%qiUr80a<0R5BuH-48kA-*^H&&^R}s^2ciIF_1H?S{6TO-TZI?}cVq -JqlQ&qGIuM(u#a1F4SeGnvBM^bye8_kOD5KB=QlPCXSgV=>>zX_Sh^Jz?RJHQUW#&My=K@2MSD#FcIA -2^m?4{kfZZyzCGWn8|L?%R2C*q4*vk~%3c1C6PNH-nsaU1 ->~+JM2Ptn_wE{T(8fHp4I;E^eW(*lN*n@GM6%ZR=3XxOOuh>D3U%(PaRIJTwOy7W -Da~Z0DS?qSb$bJTImCuWYo?oTfZZ=xj`*k8w79O?}|$!H!65{9K|VERM8?Zo>56N3Ts;S{*!Pk7q0Il -qKMTLLrrm6DHCo&p&V(80 -$d+uir;jH}H+cp-7i~$9E|)p_3=@>08Ka@|XzX`Jj{$t$WT!Ms{sB-+0|XQR000O8HkM{d3+S?<4j%v -jV_pCN9RL6TaA|NaUv_0~WN&gWa%FLKWpi|MFLiWjY;!JfdDT4qciXnLzx%JiZhKD3P$DETrbvw-Z7Y54-~R3gKmrtHr`y{*XF4abL;@EV7Z>*%*cE&C_rx@hlWaZ`RXN*xgi -m&MdcEFGA^%ZHRjQyWlT_{e>U?%~&KHRi%RH`9DON>(mBdmBY?_p7F{`p^ndF%gXpB+n#1uQJTbW-@F`{?=Og?1Mxgb<@dlOf92KO*$EewS^#}!X>^n`bA_kKevL51$@~-@SeH?Bz-L!}0kGXikg}TrXg4d}y~5 -%bCE*hGiZq94(tlKkko+vcTDIBupKq6J;XFH20f!)Db2d6>Cwg;;57X?FH{nMIo~?T;;eLc}6eVDC9^S>=}kO5-o -r{7I&6ut2?~BrE4C**DXDPlVRN}X?NchGMESAJKQj*uYvu}h+j`%2jSz820}9@;FYF;J4lgbeg&v+!waa2Q&rB~f?mH{m$YSc`+dJt)RZz!Z{2wvLM -soj*X9oNU!R>6MGlL1`kKDS*{7iO~AVyK# -8$uQIay#e(~b<_b14sRR-%pqO;8^3<=H0>ne$WS~682KiZ4QF3nxGB5e!OfJ`Dx!UCBsqshmRgS+yKMew$oNC&3wVuRz6U+zeyHn1)<4CgGnKy -%LewYIG!M9$f$rloRA^_XC_g&2Y~^a%>wT%;6PR64;O(p;za^T7>ge_K;Ir1jhB^RzO0rLa7Jp51;n) -qV7c@mnFT=rW+X`04ckL*aK%}*ScjmC!_sL$n&Wp`$Lk&V)VgH~33z(BtCl32t_*(e7r3UbRV -7d<9Zz{BwsM@aBP7Uj#sTPr^ilz>UT0xR?h4zzD>~P`s1B9a3*h8qa1}&J^(gQo;V+D()E`e8>?j($> -49o%rXu^_BlM?lxx+H^=Wjqh8Hf%#%0cSZ1R_U;WJ*U%>F1M=jrKV&(B}p8sZmW5Y-sF5Rz1)QOfXV8*p(WLQG~KZGx-QM4L$@P6D367*gQ -V91OaUCC44rpeQ;`kPmz&+Hx}MwZ$oI)>%BvM|HQVPG=~{UiEwGA_@7bBZsWu+q5DtgbX0Sz$z -cH{2t`PI+!P!=5m6rDobP|NmpzQ>%5umVyF)C`In%YX>{Kau -k~$F>5{!S~=OqGWEy1@a+~SNDx -jg1pa2OvoD+aIS&KK%pNiqZ!$)As(=^0TFBkZq02mCUD#b`H4Fc0$E~7mq;a7y)f?-nJlN6s!;+DQICdYwC{@ae4C540f}4s)rnXt0?Qwfyw+G#SrC}=_A|VjVB#u)F -J^#Y5aCuEM0}PZLd@3*k$uFOQnQQDJLxmD`u5JL<-KAnDeS}$$(FZ0yc-flrAJ_&WKBDDj6kzfgM2HH -ptkN<;N3~3rhz!V|Dd=8d!rqurfo(v7rvMZ+8s`gXIHukzkw8|ABe42M(-dr%(FY{;2Q6Z7OK)<9SLW -bm4Ma~3tXC?D+Co;I=1b1?-&umk}r7#zDHxX2C$BAUaZ4bau7mq8OOu9mBsRwi>~1O#%`lE -)m;^k{X7>9thlo`IGpM39j?_U)nu$O;PpJMw~MANoPKPRKBu`59*)84&=jFGgTejmP|b?{Gvvyq@Qps -RVayk~A7*$njotZxZB#4Vs`Q%o)TwxrRg@H;BRFdF6L#&DEGQm@ -l*O2&;K%@}Pm4h@U$KdqH_P*Jk63(`s6`L$-eTOt5SvU>!>y#VV43~6kJ}+O2F-a<$-04}2)keBL^Px -O9zaAs*D$?4s1Y0%pVb4ShGZ$wc>5rJG+8*x1av|pz{E*Epsw27g3jzv%j$S+(bL#ZM;Kt3f$ -f?bBgO1}{;_rQ8;G5|pZj)h{|iLuy)ha75BL9eL@0f1JXj~xTEMEk&9q9bS&oevfoW9Ci$^Jn3(Z}dV -|?fbT7%&p&84En(~OC6g;b-4oCaPwN3thHeyuRhMfCzYa87VIFN18OJ9Y9d!nV<62aB)^90mmz@3uuz -GxItfIP1N++`F7lN_b|Qvl`H}X2}f)P@tX4P1a0WP*9*^Do1K*qPGgB(5kuOW#wcsw7_r*c#(zU^6z3 -we-cf=RH0NwjzY_#CCfTky3-R~co8kp7IEW)0GNY4oY0|hdrwF2KsvqysTq_vWe;H)mg%;3nZ4~OCWT -CyP*D>xArEYUSr0Te(U@DnLQYFWIL-4*GPf)!k$C&+slcEG<=rwixbz-DK#YaSr!n)wl;4_so&^gWyf -m81Ci6a#X?`v8%o>G~cVybmX6P}JU*~46c6H)~sTSwe1Zb*2Brc5d!?EjUdd8X6AGY63n8+sJ)URW)X -mbjk%}c@Oq>%gWgRdUB@n#f=%`V0;o|aZa4*tA@kJlB0*4tT=!smtq?BMn7r#*aVRIlCDu6Ce+MP2jZ -Pd|24JfOzh=S8v#9Y~KwqOQnrclGq+sCi|4cc84iDUnx%DMUdi?*4WZkTy=REoo#{L$w~_H@M+oyqZ* -2`wRXa7#Xd9YBE@avHEE8L~$INf)JZP1G{Y(wVG*b5_OU9HfjNxz-g65LnUO&!0FJSFcKJ2(FP!oqbv -}_&(2h6lgcRs^6i=HrU4hc0AYp>r%gXvsX}tv(RDQZM!9TX|J@{F-JVIv!CBo}+c+%(^1 -Bho6xax|xXpzZIQnB-xq^3^HF;Lbuy3D9JLw*X=fIs!lEtI=VUlq|5I6w5bC5pdMY39r6E&?!`=c=95 -dQ9P`0!yMd{o31EQ7I73-Y4#EUlf@9Ask9_pBuTGYpAMX;7WJ*WnD5+UN!sr~nE{HEke_{K9(-3M~rm -f2&)JFM991_xJta%WwPm^~rk>ELhf{#?bAQSAA7P^JR1!Fs76S_Dv1^cCa_gv;*c(kKcU@Zvd)Ujt(5 -%sEyxDH<%^hgQscDe&S0<%GOi4t05z|(W>9U_{=SA-zZ=I{Y<`paen@T;95D_>MrMNSR-3BcW{1(ptT -LDI&_1{etZoi*O0Nq%J)q93BI$!YpBKSZnp#i?Pe65ik4W~H=QSeF1=(2!fw|LQ>2O%5kq^nOk*Af#}MYFVL) -JJe1R1c<=QzBRd^+IigRBeaLbaw56bs|9yV4Kz?7(S0(kW9x?TwKv41HTjle^IJm`wb3#UsrK>+MLiv -1gTuEIO#&AxWZaShGx>k)ZFpOimNWsY&ASNCPh9ry9F$N0iq?-+>2Eq%4xP>ZBrc(K=4F-aK>dKa -8c}{5-V19vP#7!ucVm#?O6}ZY(U1R -dDFCDN=c7WcEotKtW(hSQ&$*{3sbeHRB9SCl!QFfDPN*tiUxbmnNi8mUeW%j|Sb!~zH!xBjcfD#$&5X -@m^TE;KO>5+9z>S8Exznl_-UVNZ-ofGU=)qSH|Lf7$dqM -bX&loW#2-RTdS4GlDinrOGRMTjMVSzQqu%^f<`jPR{DZ3NV)S3e)HNe^$^SBxs{l(;XO;cC5UMeWpn8t4oD7D2Rk#l-~f23E|T{b2jH#S$ -_;xg`WSGc*t%E*iHg#!TKfGqdv+8QV%^hpQTX7q`HO)#eArz$XeD(wKyco)Ic(wg^!IIRGZNlC<@Llp -v9K!G^mL|34wvsXhcv`Ms%hz%InBG6&*?CHiTbAUd=I7WVXyc!<(+Bm^;k1;8YbCl2DN^L8UxqM%q;+3dI6E;AGw%ysV -;`q~t@W*B0o}``zvr_kQpB{rg`I_DA20zx~6-`(InlF<0UC=| -R^E#X0#9+^XK_c5pLmwm+)Xd!8MDrvL=wF^YmbU_`sDj1`MT2J{Qix0Yh^3AQr1Qn=n}+aj- -C!j>iRD6y|&5m)=01T?`U{9D=Wu0oD3c0E<20A$A2`Nk}EZZ=;g-HIu`mWuz`Y7kZqRzz?DBMQQ|L_x -sMEMD5RnsBxtJ;`at0C$ORkP`@PY=bLew$0q9R;z+M*9(u}YXgnPQvnJzn|K>)p<&5ldb8{0||61Gv3 -rih=5r+>NUNoye{OYT~{5!Z^wCW`}xS8pX_(}MO;_+i~G!%P+=eZ{iZA#e0%gQ|RHHDeJPfk5v={i1? -zpAjyW9b@jwxrX|WT#{?hqpKudLQAQXu)Xq+{NK$mG(Ac%ORO<0@6T_#UW -}d`u+rX=5zXPpuBBKI}ktEM7(>YJ6GGaus(ws*!k24kwbkvnGQj?oM7Szpn)i{c`c3K%3X!-inIdNb= -Uf%v&kGY7HPDc#F4lhVMVmeu970pC{zg>6RYA|4}%K)zoI@R3VvS#K9FWlgI>*$nE2*XHKt?~PiJvsl -LrQs{ZhlamY|>!J68Xc1=M=I28lHitb^-E<7Ps>XP!2apN|(gM@;mQ3M>4|omy6lF(TaAU84n3P8WjA -0lS^9qcsg&!v&1i*lb`P9!oaMg)TD5V&^0zR_!;YhBa0994H+UL5GXJShAo=U_VK&W>SFJ;kz3u+Cs3QpCudDa6Gm$z8wLJRQaZ5cg=;2mooONwQepboS_)*QU4E#U`B#qL -&}|RUu3A2_X_K9emMhwvA@zywSN1hPMwEYT!K?a~mOY1?mHy*HKn(0f;v5YNb7>TKw_JQ5-3_lTthNY -El)(T48R0S5>hHLa!pmb5hbe>y5K8!dGhfYh0~ejW=&W69?1?nhPfyg9=MRFn?3h2__QzuKOjM~Q5mT&lrYMgX -udKi<&A&APj$gOz6_U3aG{HbsFs24krU=708*Okk9jR(S>z5aG4>w?G9VfP^yf%)koJCb -yhIlwTjOJL+Lgkn}uAAO7*`waJji}31>N88`w5A3u=+rj>z9SB30-pEO{ypW1dqN*Qv~EG_w~#hTnk} -woeYsR~=TFde*-r-+9nJio8=iR!liEARNqM6#5?aYC?|6y=b?-#yTkv!gn1meF^}YG0WQD&0ND8vs>u -+|&9{2a}TTJRb&9jWF-|^+8l&d|k;jW}^^NS&rU6K%j7P(aI>iZ3WN?q`PO?GK&Ct8A)?hSbf??>V>p -TQ4;V1EWuxC}n7(9EptpSHe3%Ku?_ -UA*&pUQ=)fG@uNLtj%(9|FNgbv2VNRg9W-N`heyJz&CEu_8ngL1{m}a8ZUroKNdfG&<@rY{?{cS-s2} -Ulm0L6tq&~n>ynZNsn1E*SO+e~44|n*|ILY;*4+hWHWx66o`bVC{kr13^Q}vgI{S$~+NaszHRT~+RhS -6HRN6OqV#4@_h13TaKr~@WAFVsE%Z$Os$F4sr5HeLc+X*v-g4L@x2M3O_qX;RlK99wtgdH)XGTV> -3BhLC?miTO|}ipS&yW^`zqLZ;#^Oq9faN1O-_b9fjCau>=Y@jMG1|JKY`o-wM``Sv= -RpMCp#w9<{KYV-s{Ad`yJQ;}QAw2l*WYc!@@V3FDp(hweXlTv?#_kvwqa&)XcZYe@4e-&PO6Fc0;6Qw -T25Ybegng`p$g2Y6SKMz8G<@GNp~Hu*3F(JhCw2I6>!fhqXk{=uPJ`wG`A|E;r%tXUlmyJuy~P_A)gvjM~~2-=X%*)W-fC`Tz3n -PR@|TEA)J)E_CC7?FEU&N^lZDRjIhoal^fq&<#-8NkXz@*5Ti&&+9^x+Ka74Bbo%xdjUH`eIN%D-TE% -^J;mwxoJ=_!9YIQ!3uOBz;G-;EzYormJ`gjXXDVoL^4bRbIv47rk9v0jZ`@xUZsc&oxX)#PB)pFAZ&e -|Po!b#xTz77;#et|lgYSyO8b9U?zcCMDv-#P(t`kLK(XlvD?`KQd?nDV!IE)^tt(kbT~dgt=JmYPj~X -_#=|u(ulFbh2YIbU__4NJUL~Ez=7;kdnf-HdN#sdt?p*ze$+~#H-)KFMx4eXJXcoTxIBeoBQ^1WU9yg -1tKd{u{5oPn97|u -VTUj!@m$4)}3IR9b#ZEt6L) -c3#mVurlQ-d;lRuvPE&T59=O^xYg^BmmyLV?7@L%!nUH1O|7yt9q7r|4iu`z{B%2Z8nd%pJU`kIa&5i)A<18BJZ&i?{XO9KQH0000805+CpNuD?-#km3i0B8jO04V?f0B~t=FJE?LZe -(wAFLGsZb!BsOb1!gVV{2h&WpgiIUukY>bYEXCaCwDPU2EGw5PZL1u>^u!aAZgb1w$VSZ6R-k7Wz<%b -&^)rHK$wUb}x?m>$|59yGdJWY^;-ZXJ>ZCUaeN1nWJxexN7}LXa3i(P8hYvR(lqkMPt&S*GZ{t8U72WKouNXJdm>mWBD -EJwv3*V-E)>oq{+9eM@Hr0try?#}hSh!sy%c3s10CnPI+5scNqm4%w_TU0MB$fdnp7j7Hl&-_D^ey!)sNh#D-WW~*1#@k(~**zdeDK7XyiZ(a28SW -k&F3UE@41m+atXDbT=8>xVBYU;RLOgZ@0#PzW!K#F$XW_DMx8O8uK-S&l%M`UKh8;by=>@F=Nr_X#VB -wbdDa-Awc#|_0YI0Gv(om2drJ|YN#|oc{m&>`35Rf(7dG)OH<&2>r=l5btmNbru`)I(2+;um=i8^&%e -jkCNFtMxw3VUWip|F&xocI8F;(d>{V5&ikkY(J%)hNMRf9Wv*2uo~a<>zLuF3 -d{*JWLy@huyACk|OSQb5y}rOg;(jrn8xEtAoZ!JZ?un#@=FSS(cAaH4XxI)Q4@ -?_@x`Ps|KOUo}%O9KQH0000805+CpNpWmS{EY$t01pKK05Jdn -0B~t=FJE?LZe(wAFLGsZb!BsOb1!gVV{2h&WpgiMXkl_>WppoJUukY>bYEXCaCwze!ET#C5WVviqxcX -bQEDgZrEV%=2PdeGZ5g|fPu2oM*>=5a?Jjot`wkeAR7s?EPB1g?@#f9Y>%AQ?4)3Bl%%Vv+k3$&G7L# -xAPH)fY!MflE1Wz>670_d@5y7b3o|wiFRH}rONzj;aV>M4oi@+@~nSE56@OzcO*&={)Sp+wCDEXzdjl)y8dqBIE1z@g5W0y9Xe1~;vR#H|pRDxznWD>(w>RIk5%L~SSoSN^d3T -FdBx($HtN)~`VwYM@J^D-ev-bx7ZN%Yx@gGvKnJE!2U=Du|Edf_oz-!C?62`Wj>}v*j+0?tJB+FAvvHZbpB -XTAnebu+AS)O9KQH0000805+CpNpYONF?W#w0HQ(x05Jdn0B~t=FJE?LZe(wAFLGsZb!BsOb1!gVV{2 -h&WpgiMXkl_>WppoMX=gQNa%FKYaCwxyU9V-gkzo0Lf5ice!MK35eD)?85xZ-u0oi3&3*)j4Sw^GgrI -41=Qt%>_phH=i#w+VXg4t9hxFCGhuqv`Fg{iUd4BYxzx}_*&wlYwU;O& -Nh|CKOg^#&;ID;M~|=m^ycxK4{!hN?Yr0CK7RiGn>UZQ-#`BO{r`CT?YFP{zyJK_AKtzF`t`^6A0A(S -`}WPdkB_hWGoQVDYWl^yw;$hj%YS_H?{B_+|J|DpkN1CkeEb`I_UC{7=HvHYzy9vc|Et@#@85mj|M}s -&_aFR^?)dTB_aFA}zy9#%^~X2gJpS-kd;Fgt_4)&SefQUX)So^+e*E*ldmPtQ-NoPh_-BvLzy0>{C7= -EN@#UNE-+cJ@H{a|x`T6^IA3wbP!w>zMKmPvj{!{Hy{4|{iiwnr(gg6@vArA_edZA^yb~05B)a3{oxPazWurz|LX16Z{B_X -=JERb$9Meh`#=51O#PC9{O%seJR{i1(qv3mUB&Etpf-~921Z(nr}{n^KV{ofBE?B@4x)*Z+`cS&mNE8y?OI#ikZy6HOc?a6 -#uci{PX*s$#33#eEs&@@As?!OE2&DJ-~0jdHm`1zrX2a{`$?^mZ-<;-Xh=q_5arze*6C2zxlr1$|BpX?{P^8}`sq*q@|VAS_U4-(KKuBW|5V3M|J -+SJ{PaIN|HoO6$4`FppC8Z9=6S}^zdBcZ`qj!>{AwNNr(fmVzdD}hynj{Ks7b@;tov72t{Ohzi*MebK -0Zk_wiO}_MqOs>uvh|M>f-+fB5=Ok3Y3iQ-QR)`SKsXe0llV7yq>W^{apR?H51){8#__k6-- -mt1rL!+3&yl;y1tk*DpWs|J$#ydZB*)r`I14tQM;O{+kbKA(v`*@cGYv^^1S~-53AYFBtmB=#G0`Y4g -?l|7h_Kj~~CZ!aQCb_3VGvGhX#@*Q;ZW<5jEVdewiu`ky-aDPFDTvwwGF_UE4Gs}a@BdhuSJb<*N^zI -vd7k9Zk%%x*Nst8r%k)C=AJnf-*voUdAX`Rmawui5_`m9LJ2zs9;=jVt-^8L!4%$E!2e*!_<4(v$LdBAJ)+qoul~rRw>C4 -^i*cNT;ha3{o`-tA^lS9YJQuI~WIgzaXZ5Fg(8oNxH)iBGnf<3Tovg@YygjvhMwp=BC;GkOrJMI)7V> -cQI`==UI6`spelZvS^k3tNJRGi3KY9#0PZneUf&Z*65B&-+J-z)O=O`Y$5Vi}U=oh+1FU9jZS?3-{_S -lZu^U%MWblxAC6Df?IudC;)*Z0`{p1p2I_OG8;i;lB;F#Kmv=uy-6d$R0LTJj?O_C2b8LUnJC?EmZ!` -wmRnr}57D^0=_Lw$XUT^d{^{ -tJ{hrTWw`6S75ix}5VfAeB&b^Gs(ev9YlQiNZ(+$SVRrgW7zP)Kt9KGBv;j#NY=S#mh{hmFmdG{r -Ll#?qz4t^cL=@-pBpdg8{be^^ZMh7HaUCb-3xboSpOe((lru%TDdj59X~G>gYA<0V?W)73X(7wLMg}F -yjrjU&}bnkiCA^QD|2BUGdV&V$g37D=$Ph`oV0q-gCW{i+#L&@sr2Qfu`u6ilH8C9p|uG&L^^GzuEh3 -vLO@gbg`yKAeX&7{Vas|;{94s29w@uz(tx=%DkKNS;=DDXCU_j^ -8DpPg)%p1c$LGFYvCp&pis*)D4$BmI~bcGt3cm-Vu=0Qz3Va--ajz2C-&mmZd)i&;QB&3?aLSPg{xo} -EW;n6`d?G1C}S_2i?Ue_fc5Q**cX+gN9hckpWraPQJq1Eji|$ga-VdWex!d|(|w_ZY2kU^` -?%ki`E|v60V;dSRzIOdp$C1m&S=%U7&-kSrU&+Q^^CMa!f5ph_J0ok@mY7j?SnDP{WykIr^*L~$Af2~ -3j)iG5l_@b7SCd@v?wzp7iL^_)dS1g{m$pJ^}70+5zU^}o)P_xK>zVkOnk9WyiyAg;(t1GjuPQP1ND^ -lyPjLGPyCYTVf72?;Gk=f^X#r(yh|XfQ7*1N3m&H=i<{R~8$4YSZ5N#{qPb$bx4 -kTmvN&K7>ji3hgOT?Z?kVdwp7(RQeHEt;?wuY1E<_d*RN-7PF^nGT!bDr>h0_U=Di_?)-ba6Fuo}n2r)p35Rns$q2;=9E)HDM?dvIu-mEZLC< -g>W3O}op057gZ~G+&4rgy+d}vGs8velX?j=S=73MtLI#>y8uV$Y?_mW|rr~V5s{M>eP;g{e#JTBD+7O -WqSYlb_4>3%MZMlqqdd_1~e1phmo5nq#`vSC{mcfakIoc6{GwGi<_L)F0B_fY!@fg19~yCt@)-w<=xQ -`K%Y^6>O`?3&xJ4*ZgSEnIs>xm;ab0zR+fUj!v5BHl~Gi=!V-XAbq2nsJ=8)A`!++dja{WViQ15f7;W -3|=&Hn3lSxMI%)MZZ4bSU{$X}>vyq;!;3xf(oeMh)N<{2IGj0*@@+R;JGOp63KCC0ab)9k`>6?WX@?Fk&LZZV4bua}`tke=wa -IWEcrB0fj$`wQeY;oy=GUlcr4w}_Y(2o~PF0JcN(oQ;<&26^r6=>i&NDuax&4Q1zvO|AIk9+Wx@Q$Oo -{sqU_FKdT7|Jl@W*XTSl`0=z4yzUjWjybG@k`hP{dpyS5{>az#!m+hEDq%0m57;pLD4bVCfuLZ&3d^9 -?|`NnTOLxsB=|LvR~rmIuN`8S;RrnlD2k^>dsIECEC%MIo~TzW0SX@<9gDR0TkG0hw_e17TRhOA3;%c -MXsxHZi@5M)u^B4%;w0=^7}N)zWoy=xCBq(nnuqpFj=gbu2t5yKCpP5920Xz?IasYr!JepZLZ)&zVB( -qh);0XJ<*e{amPV$9cp4te%RRvR9jX!(o#E$`CulrR9WsK9#1|w?&54UK_C9Wd((!Ri!J)UMAC;*%u~ -4o(N>GcNm*QdUg9g;hU_%JcS|RmF;`aM$ux>a4h;IMHTO}bcO4w+dAi13^BO{_$La~dt$)!iv&xiT6B -ZGTT+kdF`OS%#Ip_TiI1#PW|$=vVkHc>8#=?1$OVZSCJ)>FYE#8NA$f0m?=uZ_MssZ;h -_TKUxlC_5nF)bIsfv38cs*f+N-Qf`lI#JA-(6 -J{gQba;IZ2auWU`a95{9Qq}%outEJxIofDrpSmcRdhfh73r5zvl=6h*Dn)qFcf;LC6bRnw(2P`vG2fK -Yd?L!NSfg-dxau~+o*Pna57rX>tY=|lzINnV6DFDI)?000S1GGdG)}8PUSW97-!9VtO8Slo{4;Q>7wm -!jLpkpS>xDDmPpDSj+kV5>eR2?%wHsTQ9pg2xcNUNspKV0}F2<*V@o=jdh$0e9{$@1`~Clh}yRD;gZr -G;UXL%D&llvaJqS%*d8tgJP -CYu8Sdf*&%m&nw`MwrZqbU;U186_;inze_aK4S;AKiT6Q_-==K9sjpg&P8O6Q5NIim_|B$WEE>Yhk9M -gr%*B@*K_yVen}68J$s>;F1C;%ERCMZ$fK8?h~QKW4Y3H~fwp-N>9js|%?)kV?zjDtZiFQr%qk%np~a -zJf{y8b4*C^^p=|&ILv)9T9Zy4rxy$vx3G0 -zbifz3-=#7@QwBE5XN4=(<}^~LaP*B4m8qaO@#2%HN=j$8LY@APD#q~^8zZNH?2t``$$|IQZk+fA!ZO(YYwwE=?Ay+t_=%YA#IwvDP_U1+KEz&^{DEK+rP^zRUxLCVm>$>moiP -l#eM;{ci2e?jeQpe2 -N2b00>k%cwz?8&X_S01t;Y3VVVXXVch->;X&I?#AKYlGmgN1Q{Wu#1hK?}RLg*pFO3nW(c4CKqLvO@x{ -(`Rm~|AKLYe8bYScOPXDStlSFSjH032QBG_ej-isCOF2)oTKeb4dG{;dKdb$;1m08zv -RSETpg((l0;2If6mTvuu$Zzc)zY&ShGNQPw;dE^NYdRM&Q;D6JB!ge#2ynfuow(jHD@sQ%Pb~HTGD`* -t-$xfrt;C_DdYH_eDSWWia;(kLGZ@;-3vn=7D3n+~wyECh2mjA`!*Ft|Z!gr;~ZZbzkABQ;@l!S7Qr(}kj2P-X5A8+H~|Jpy~jh42b_=Uk`rVTQKY7f*AlE>t?_1l2~Ksr=$8nUiB`4UeXhf!P-cf~f7c*l!DKN$g{71gY2_yMhy#p@gEH$;Y0DfEE%Qi6AhheD(krEt7v(+RHx>2)fIKVuetcOO371(malY -jHmOGCmQo4#W2_dlXsr?i15!+Ooq>IafgCpeF^$3cHXJq{`u0I#Oeg>Hv+JvHF$BubmtVRI-zI^9SeL -?N^BN}a-Di=m%HHZH{p|qAJE^xn}wL3M(j^^$VFVpi-V;n6*>bxI;~wuR)lTZFNa0C?ta@ZS-kVIZsp -RP%VbeBcq4s4_Z`y-AojRGT;!>)bvjENRz?^~UZvU@$X>I$vVS3kj@dk7!v{5~jaHilEyl_d -I!Vyk1EpVQ_1|De&l0cM*1>`bxBOsU7jf!lXOCD_-oxD@@mS*MYRcB9F5pRSGnO3 -#VD6V$e+TbnSgKF`Z0SR~^ui4En(aKVp~IV1h8p6ITHJyFT -fMtNoG_`_h|38+F0seFEOzl<;C?a03ysLE>}3YZlw&B+q*qqnS&H+rC}!65_1!)brRyYX?t9y`aDpk7 ->oB1fLwF8(#v77_JpfAB8NG-X`PtP@`2I9R6ru8B$$5NFPSKd1tML -iUQTItks3;)NI)E9g_dZ4nfEeRtUgV&jXBI+yKwhw-Ro&}EJ`Buz!;?#lt{J8P0EI$OC0&3F1jpp+oH -?FQ*AbPTuAYB#~0#e5Wm&D;!XU%K`q}yXz`HXpxkr6h^cnx2J3_0iHMKqr+I*v06Dr5{yjmIl4KGAr( -=>WIG4^0#_Q1bBZXv?rloR;qo4yYHV3D)r27?y7%;Kb$lZ@=TJUKl4{AQm+_`@&>-2#r%ct89XBl2{& -U+s4OI+u$K8i5OEFn64k}#QU@E!+4J++~N;E -JN0PT7gIrTZ=*_4;KQ8Cu4|)Kk$TT!Sh!rwLlGfT^LL;urRxec9?7d&9l%bVRO8Ikyt^xCArfh7p}B -8gx(Cu1>SmJ_!UJ4}#)y&qQeQ&ld4HZIBU -f?ieq8c+b1-Z_b|Xx9EaQ2U1P;zJ=CC~H+-85T8+EVqG(&448%_k&>}1e~JC&H@pb~5miuK5q7QhyzI -1-Mbg0blSmNV*JC;r*Nwj!KE5TRCNLsF*)x18MTfl?cQjAep_Xk}6;xK8WI@R}sd^h@0&F(I-y -n(U{DVf^T4f{7}^;C-GP7K<2vpJ+ -W^?jm5j5N|yRMZI0vSwuVk14^!6hR6D1iV}5sA#B+06D(!l|VP8xbQzLy4fJ}-mCzX^-=Kv>Zz_ok$? -1AMwn+?&KB1vz2F}6LX?zKH77r;h#$@`)3&iy-Ll%){+7+zc@j6o~SxY`cci}UWc{gS0J9%S=SWs|hS -0~Ix?5IWpoQxbrhHIALKnZt;H95MN8RoS`oL#K7G#7`$$Z&C}o&|(As>=Yzhz?EivJ=6fkH&JVJM?uQ -~sSJYub#5L-s+sXZT@l#Fp=4Mnt(`>)D!P;{I2cMFPHG86Wm1)j{wPTuA$EzhqLI@t79a^Ww=fi>%P4j -!{oCRFW>I(P&wxX2!!hH4@PH>*w>92kKrQ!`!P$1Z)&V9)o2cXvZ;>WETZufC@RmWal9hKCS)nuQ&y_ -Uoy?(V26M#la|Lmt+H!{@QJZyAoxj3Tp0`xTj%m0eK1v|&~eW%b+6~}2nvG8V)vqeXpQ9XRoD${?6N~ -onwVH7AlIttaqN07>k00DseAS80DN+dts;$zS%qS24*y{4g7Z$LUm_vI9~f?7$Rq2En`FQ85~jckD4m -X{$!Q`2Yw&B&n+sKp5?sjG95f=chM7yE{cH0x5EF7nU8d!XNRsM>O}EUX5gtgmz1kMs1cl2DVun-S{z}YWRef7jCQ5TDR0DmzvY3t*Morg#6>)TLJxgoh -=03?h{98(K_QZ7wuY6TcJ6`#2iW6*c4VmnfeG$`*k@tA4Uga1Uss$3ZnwL&M ->Zyc5YE*(PRH$zR~>*e$BeiwX_Ww%PrWQt;{{f3*5v_?<^A#*2tV?7Mf%0awv;$g2L*SOb>y4SS>mCI -2gVnkA;j>iy|sz4Ec-hosr`83bT!8N7FSam*c|AD&K$woK@sCeH8+hh=AP@P6gtlzYA(6cqJR0>#C_#i1kiflZ6*Uit!yZ1h(fXhl)i -hhO)`njKuUrk(QNjy^Upt|QgtneW9$GOl6X70cPs$IHEmbg$eJdyJB7y=TCu^(o6ps0gwiY*^oJcr -s~`+d~Co=@{L%C9 -QVnfUqo_LgLP)4q&w``hR{JFL@l)_kuA#%-v!3x;elP>kV%64fmI5?btJ2Ww8)H*R^L?)4E{x -oUXunFz00fQjEV)v&coGL;ebn;tfsjtO0|mNeMg{OqLeb&W07$b*>SWRfROU{`u}m%|`SHB0sCi4A#Z -Up+N?$?A+RHU|f{w4KLaN6{=)*8p{thP)7Px162=3{QLVK{ZYsl9TBa2tYwl#YnQ}?=tTa#c1aT<8SkL -AJ2En>KmL~?12$!JC4&hHr(1>&?qN9B^B(Wzy5R}xYNYjQ5*cU_F)L{Y -Kqe)478R%yt%X&sSjd5qbqP{AgD18=OEvEk>=913nS(0q<5rBUn`iaGNLKJYLL-1vNfd;5_NsU;^||}SFTrP7VBsb9f8aBPkbT#FTR(~W5su -u@oXFFac2A);EnB;}=dkXzEIm*p4RlF$%MLsqNuYm*yWx;i2#Wk1&(b{@CLEFYUC%YBdp(JQCi>wa#1 -bhG0396f9jP`aD`aIN@bf{f2{HUc;=n)Ksq&!)e?C2qXN|b!82i48LEYj&yyPigKp?1mPX;R*OJ6;qa7sxR}QqhnX(G -074|&VP8W!?Y6pa!%L^TTdoJyw1bQz;bL(GSSCrBVr_vdr#}LSk>}G7lj#6fwA+kgSyuPz_zRjvpi1_ -XB2(zZOu$;L-6t7RwpjTR1|Q?4$oS{G1uJr*g@UviE=wEy)`rqPZffXk@7`3rvCJokk=4#u1=Q4;~<_ -6m`oX{o>GzNl&RK%+3o+Ffke9OvCH=W(umY!)qo3sYN>@`TgQeN#-(Vcyj8TG%bnWFJlivrI*NpC^Mu -!=o@yhs$FjBoPGpB+@zy`ra*F0zFZLjVO%F#F5S+t|83-xX(#2QN1sWaVc(in%pxLFco@dutk^HXW-! -umVFZYtZ8OodM?Tvh6t4Qd67Zg>oAB}ra!b+I>Eyw_WeAG<~&e&zZ@W`2wYAG^O~x3bp71tyN7kJ!_z -XBzbo5Nbq?{IgcgVDo6_Im4N8;<53jr_sZv?t=5+=5O7}ci_iE8)nF>Wr_KHvc6aO9?0<%1bfX}CT&QsA`M0cJn$`7QRF7bO{OlUwHOz&PAsBy%X080Aq@_c7Yh%1>0t;soxR -#&4>t9d~0RTklw$?y0EZnHq|9!&B1A6jh!^ACxFqac-C>;r6B&PM@4T&595^s7~Y?>h -^Jla(vc)}bYn-=H^Rm1qgVqx4xMTUf`{SMd7!N9klc4SP2H-L4YI{9t{(|p91OD=aBg|QAe!5$sG_Wd -w}y4OkAJUre?ox;+6ri%9A#9teRP;LhgqZILR=|iiZtZnir85SvTf1SG5HBGd8EaNbl;*m_;XOrod_b -K4$S+O-e_>EaYk66^@4OPpiX2-{r9d;b+(L#0)fMTRO)p{T6sZtMxRH0hb>qJ6}sWQ!|r~=` -%5^V^;EhrGPzL@l6?9sPVS*3mKN6yM()>yJf*3F*m`<0a&3Nw?R`0{L0{f6Al@{xK3q5Fft^S370_aC -{()ce@T9=5lJ*U;F#A0EuJ^HjcxhR??Ef8?L}O(kukw{dTjxV8s&!00dpxb;obiy*|8l&Rc>m;T -(mSWWo*xI6{jM&Y;qQKEvVWxYmgWdMy9Z9Nhg<_qsTk@G!I}ivocV1R1?Xy1A}1B9^xFr{FQz?ZY_M5 -_2v7)|1q|F3WQjn9bREzai-H}3Lo`$95*M}b+4ZPMRRWAPkc-qaHc*cPUzE%#tdeMd3eD1o -;LEY<#p<3QGMl^WBP7m+V+EfR=f;Td$pgvUJvgv6Awcpjn&|A3Yf%+252BxR%w;gi4EPJW6^^WQHaF* -PI{wMoQ9}Gc#?gYq|2kKrQR&kXb5m`@zSrK?@zX=As@@B_bXa)UbY?+tsIX8!TZ`T_Q>t6S51D_C+l0ch4S~{k;7s>RyLu2_e#h;=MI31Hf0X -N*Fi$9F`H{A-I{%E)VPb@E`CQm&Ld1jq$y@lssr(;)ge~ysib&C7DBvzpz8oySU6+>=H3D-_#E+qTG4hO1`Xbwxpm+$38O;jSEex&$k?+fZ)&-8qY7d`;zEN+*QeVP@2gjEW>%1+3So@YVj$oU7@&*`~PpQS=1l+Qb1Mq;U^`4wgR*0d#nsE-)oH|T*9SF`Pc2mjIuVxA~H -xe?y@aEgS8PB9(y#(GM!TIkk-MfEjIJ8pe3w!-P7lF -Z1^R&{95-)L^p?Lv9!IXr_)I9W9Pkeg%Y)cs;oJ5=FEoEQJC^V;sMh$H$R)yy`GeFucd;X$`t$43H3% --b`8w(IQC=wu#&0rVO71h$2|VF7n8cz#pMdidp^jfEeohjMC0X5$-Ko1LJJ-FWB=nq;e#3D6Nu){n@; -b0U7ks!K!TuT?qkO3v)U{3BAwo*1T5*0l55^{Lz$@N(dnkSXG$q@;&c_L_YT4rjCEH{B5EilJ~#x26nWyu#w0sE!C#M&* -(o2+TEEF(>h9R8=%h!&k`2qI -aG#c9;VLDZSI-m&f#&GNunElzn(X${175=zAoDB@8qPeL#63-U^1Z(zWNUHhCR*qt~_a-m>N`Z -ZyIz=8v?rKjapg2T%&!dsQ7+g>|fU?komvGXm!8}1cW>tbvz$2~3hBL7BUx0QBz*&W)$bHBo6f?iMQt -u5?0^2a?7buP52-X&L_5b5GEDS?p-$ -#U;a~CWz8~_5i%Z*w7+8nvnYX+ip_#x}-pq3!Ma*=V`Em#_~=lQ`l1{6b-O%kC6*9K-2}Nf_XHSYu9B -?>s~<`GcCF_)HE}i-n{L_kp7j&aBRU_w#bzbqxEZQhxS#zTp4of2kKrI(|+n!P-S!&;O+ZByix-1SRU -{FxZvYSzH)h~LwfrrUbi4}?R&o_b+6~9lGb@c2c`;4S;dLKkW3SrX?PEW2s1yb5VgX747r_Mw=$`FJt -;$a#J-iq4`7F+FYs6KDo24%_}$v)4o;_~rIu!oFf~MyGj@J+Qulh09G(`|@cBh?`bnkrvUEJX7Z966T -5wt4jYCGwEWhJ46t)Iu>dfO_H_izkx^mLPX>E^nL|(tmw%(VDIT(P)rkw~D*Z`}xJk9qsf3nYCOzK_- -^>xDwIJl?g0a;xZIa_Z@7IxmhbG&U=Lf3`smMuE+g+Z<>Zh4^Yb&y+`@X6B~va!{Rw8KcRqC>i#mX<) -*(nJ9#uJ)Tb{?c6a`P2dKo=Z;~7v6<$y?6==dwsBz -wav<=KdL+Lx)d4*JtPz)O(OWxrngLDFTAyd2)8B$)?>pBAN@X1O(O-Wh|BD|7ee%)5@CGsG{8m}-d-0 -f!69H%$s4tINW2-`-Oq7|PS*aPZuj9_BK!!Elb9FXY_b%k=vOa;E^1A1|^jFBk+MU?B8x!?I+zj>-}> -@13~>ou{|guAT4ZtV55Nc*y8cA@W|TwSRK4c|XHBEGRO4KHVm`hN2SB*AUB4Z?*e{mzd(kF`PivlceD -w*LQkly{F0v>QAAj>WvU!dQXr3uNS`UqtA2eb?ROp!U#sR@YI$i6`Zb_o~gIYZD~QhhCo0ua_I$V{woZ!6 -<5W>EL*s9@=Ai;m_q|p!*=I;hA=YvR%Ty`~EY~>&x(BMo%;mp3L+vIsB4oj^BGYaZX$wO2&uZ;ZdFxx -EvFC5pRjx{DClub#pM@4}&k}vB2=F85|%ki6F%rKa5$~7sq}tm(f}<+?iXSQTKXqQfYbSgOFu66`0`r_U?TKO-Rtluq&J+gy{GqsPtA?r)NpulEC|!{ -WXMaJR?YSHtq))L-tWTR?CbUW+i(ZGui5-uFs=mDpCN%kqNz{+&tP>#^-D?uyq}dI|6FhCcF;xH^L}m -U(!iphfq>pm5}b;S%m)=i6SCyaea+Qtzn9Evl6r5ON>hbGk2I#2XPnnISh_KmGC&$khHij&0wpdyW0} ->;V%(&Q@AU?v+p;jC0AD^YpLmOG2R#kQ0$0$>aRAZrrOzCBh25jA<1hAVc%)1Iw=@z%}!GJ;>Sn?Kz= -B(sxyXGx3@CJn;QE)G(~rpQq|;Uml98K2;@b@Zl*HMA38OeqTp=P*V9NMsqjb_JX?Ca~DkNYZA$kq#u -{|kbFRz6!4ZquS6L>5Di3f+C;eL6TD(I_I-Ghy4NAr^RV!gr(U|xzO!FwqKx3+IA4HzYZttdaBa5feJ -|cafAhq&>)HuDJCBb+3cuz~MI!SR6DxQb%r*SYnu!vI5q^J13u8vJ9AL;;XY!k3zv~q!6m9`?TfI=w*I4jSk}GfmhFpQBLHMbI3PE@UvA&>4LMPW@IjV -W!<4riLPfon{;-1OQ@`*M3TRL7de~nM1JM&HdozD%5530I*QGpgdvDkwHw+YPqSse$z0S8;yqrpx#0N -)q`7K3$RmbI>JhE}$#tR07E5if1Y{~NZ_!O5nFx=yXm!#MEUw(k?>Vb9lu_wF25&0~32>Eu2MM`!(bj -oBL+iu?DC69wDJAh-;Gbf(^wS>rG4C)t;`zwOf|5#NMSkvAx)Iw+#*SN>qe#sI(u03ynQ4c7GGl=B7* -w#>Z1(Nr85fXZF%hU&AH+p;G*x#$cCAcpKcHrUlbQbGcEMT#iM@T(jD!KOCF}xCx*mA+tmMF}7{w}{h ->AL&nUNw&Z8qAVhTe-8r&=13Q&oXi#02FoN4;*l|WkD)V+OlA1d8yc^e!aiMSv6+$UNg^XdSHnBo9aR -UmHW%jc=Yg29{k_SuU{a@@$Qj*kN4X5y}C)YLl$U&6qfuIfZ%zJ@}ee5C29clvC`RBj5KW?wAg+WSJw -4-6;7&r(tGyN{^??S?f~^-knD}+-umwG#LJiS)R5%1vbL_hAg6Ruj2A4gZ9@23FpWXqlPR4w -)L9A6OIt$37g4X|i`b{5>oEIm~BCuMi>a`98o3IaKL)UWOyU5J~Z8w>`Qg;~O5en6 -=*0wkAVZ(Tqz46qr58PXpw!Em6>Yls)fdlI=v-}O-0MvI<-ZlM_1dkL`yEPBpVVIU)jt-6&zL(`Ank8 -bz+?kl`wcPEFV=EefMUbR@aB4-u*W+*Crk#B^Xt>gIF-iB6L~7N@(F&?q@DXVOg#QWd3y}8xVq)J{RfUqw -SD6^I@ff*wkjd8@)`{stfdcx%XfNs$1fWU1zQd1mK2ii^P3!^c{xw7z_fCWu+lOtD~}*DbM9Gk#Z3!p -vYlVY>riM!);v!*}n$f5VsIAjnv86Km-aZewl>N656ZODFbb`DLFH1n(@R!f2Lp0iMO3%;XpWUsm -Y!QpAAT_3({yjgFd7)n%EmHzgs;%VI+0Zn<2n)bjOfJN*=JzbqcNc5|FJ;_*cwxrq7}?$%Obyb7JG$d -+HpfjwVbq_wyrYH`|r)h;%pLb&_ItWa0EtX))zo3kV#u#Aeh4sdae&7$IBaf8|7YMaH)49n|VP*tmL< -4f`E;-u^H-YyPVE$<}pEDmL~i#ppydE>HBmV%f?M7#`IRc(Hk5Ha_v+n9N2tcCO*me&HJ5f(UXL9rH0 -VX@~I6|Bpztl$vT;t0axOw#g13nA^QovffZtoua;AoYubE6aPAIO?%DeXzW%#~UK3-d+}DgS?hyLlo6 -4cF}^?t~>ckZBqBk2En{8H11N}2tA1E7xDNa`d&l|3omqGTb5TFs0!H~i=64YZl}~sW66Ro(q7ACmq( -2Kst-JoB{_s1EiZcZUTuKMvSJ++u?+W#>eqc}b$MwlVe}&0T^=U1tiwRk$cj*75f?1IU|n3i+oeN^gS -gOs3(sv``w%GU?(P>^LH8|{jQ1FOtD;+os@eq!TNH;ZE}mJG-Y@SS_G(=V!mU)f_Ls7egzbKj6>c(HU -cY7qt*J1a=!I2WT<5Sn)5a;v#d*KQ;e|yR?V^NxS+aKRBEeV^xQvSmktK4w>RvaJL3)N7B))87aSy=qI~2Ul+ph@lR(uZpFHU1GN_+#*6kO{O6wd~AA@SUYddOk -C1eYxYC-H+z%z}%>f=h9Ndbfbi1rQ-PFc+kBf|`{8L*X*p>*X*p>*dDf)Gq{XDgqQ#=c?L1OE8=Q#`>b -`@btl-Rea1c80EB{?vY;L; -D47b1b%GL>pr#@~%t53Q_}PK~9Ml&C#Q<^aWChW0_lvBMV+}H;0k92#b&%Bxl2-x43m`_|x(8lpfVu+ -|93YthqQrgaJn>Zbi>!cd1A-Y4!2sU{)Gfel0X7S;S-__P{1Qa#L69E=`9aVcH>4*>=zftEg1aE}3Zk -7LJ_)@1z;zG2)xd)ce7?ZR3p}jAV+kC9z*@&~JG&Im24~`ftZz`<8JztMj${PIUO|dD$bhtae_!8@V){x6=0}<6b0ZWh~R?|JP1dFurY8p;<)a0yI415g -$rsncRB=|AvmlV9McZ4b8r+ -ms2mLr*u=RnJHuz`ei0a=>YxNP$lwQw;UK{n;Lf0CJ1CA0>Mw&j!XS$q6q^OrUqM+`ocmH7e3tGPfgu -GSxhx21TPmF=$W>8dTl|sqY|h8c -?+0`2H-{0coLXIRGF*aeth<_z<6^`$cLfbA+w)6MY~$R6GQDKfss)4+uQ@;69$RJI8hj!#tTm4 -1*hAB!!N-(mY^Iyfb>BLUl3{pXg+|`0XL1Y4^82-biW7@>D3^^GRr3*Nu&q@WEzmT02BmKY7m_Tp+Zp -b6qG6j_0}=wJ>K?9g1mN6p%zqH1$8|^vLdLz3Fu>h6oYsws9X(-^McZppmZasm<-AaW5hkb?Uw}gc|k -#5kh}_N%7Ox^Ab}f{6b0o#LH$oqz!Ox~1Z5=w(+=uLf=Y@Q`!Ym)mhKlxqSPWNgb2zWf|7xtJRnHz2k -G4)X&m7WSmmn<@q+5dIO2ASB{2f5&fFB1qH(;~@dktu5Ktu!3Hi=;ad>Qk+$BSPAMhsY60LudO6`-$x -ngl>9Ku!TY3Q#~0$Ob`c>@FQI(lWQ+!Ds1yks6MS1@+{?8L{9zSa94cD7Xv?D}vI*pujII#wv%8$Z!l -OI0h8=m;BBsXX$G_zkEG2#$OPzfKvkvZqRZuljf3keQ*1PSVZumx4oL0xcA*BO+51eF&-;xs&cP_ -7^f3C0!Bf>_(%!Do31jt2x8`JkXbD9{fI^n*J5psqe(oaf-!Y-)p4GJBD`n8|{EvPOFim`&sd93a4;InkUh!ZLJAWI!2#ey7FkmL!nG(nmr -NR|W$Js_6>hYXlez*K_dS*-2v;InkUh!eCwU~NI6YJj1F0@0vAG)zFgry0~~#{DG -^_$=KoGDVeUP*)k0R0buLK^0}JO^;}fU{U-Sew9i&H%J#HO+h_Gti0zJzl0oikUj~3c#ts;asdGV4!@ -J2oLa|Q{siUr%IE!`bbu_a{bdaJEZr|MMde*kCKr^t1%+)vjZ2WO3}}B)Efy4o1*Kp?0a(C6gCeh>QY -K>CH{6w%p!O=LstU@ef-Y0db-*8t*w(!@ylT$dtNJF4iEvPMj|fGa^vCGkm%CGZ38kJq`8N_GwGv<9lz -m?^mvg$lG{OYJ4iwY8QUNi8swFO6mgK~4RW_ZLN-XghUZV=Y!P<8ldpIqFF|HC$gBqW)F6Qxq)UTbXp -jaCGN3^QG{|=b3CkBhvjMgJc{bZWR-%XQIIqW@x#S!U#F_feg#2|+3$NFhY-%dPN1x?hBdj6skh2-Cp^holH%Gk)Kh4WSRl_yD -;F$UQ*r0VEF~c>u`+kR71s09prZI)KmtgbvVi)IKzY`|%Rc=YT#3^f{o&jEU#`Y*;81P -xP92A4i`>2H4>&!hWAru1Gk-5ME11ue2=_tSKa{ojh2o`KQVABDc4%l?Sqyr`$FzKkx3Ggkt -Ujz#_9kA(uO$Tf`VABDc4%l?SrUNz|u<3wJ$G(Zm1x2cAU*?H#(fuM_0P6619Q{csbwH^DN*z$@fKms -PI-t}6r4A@{K&b;d9d~jY-=h0Prl8aTr4A@{K&b;t9Z>3kQU{beOz{akK-B@N4p4P~s^faz=zftTQ1w(E;MD=I4tRCIs{>vg@alk92f -RA2o&2S(%_tA6U6g>s(ETD)5bS_lPhEBJb%3t}d>!EHxU86*ewdE-=lD+q3R@c2=94%K-7n$<%MMs}z -_J5|9Wd;GVFwI5VAuh}4j6X8umgr2FzkR~$F=z+ZbJ8qOkFMoG6l;H7H;qwtAKz0DK<1#V5i|#Q0-EYfOLbVgBolxz -8@0y_P1Z^isJ3-nB(oT?eg0vH)ognQ5Y3Kc=OBnr^1Z^j1J3-qC+D_1Rg0>U1ognQ5X(vcKLD~t@PLO -tjv=gMA&+{H{`y~n5p6Hl_Zzp^^;oAw{PWX1hw-c_No}@z;C2%{RHuFCx&F;96;@JdpCy+aV+zI4PAa -_2Uj77^u%SFq@0~ak8Efy^nEfy_qKZ@en1ac>kJAvE@n{E{H>1c4_A -JVD?I0#6Wlg1{35o*?i9fhY7kq2CGpPUv?+z4N^F4#l$x{Z8n2LcbIGozU-uekb%hq2CGpPUv?+zZ3e -M(C>tPC)7L7U9^t@?tYOP=yyWD6Z)Od?}UCQ^gE&73H?s!cS64t`km14gnlQ~JI~v%rg%1?-wFLr=yy -WD6Z)Od?}UCQ^gE&73H?ssQ9{2H`km14gnH+B`_&ZBCiFX@-wFLr=yyWD6Z)Od?}UCQ;5z}|3HVOHcL -Kf>;GF>PJolIBVQ0HvWCiA(evv>qQ79U~cLKf>@ST9~1bippI|1Jb_)dU#0=#qV{5*E{B>~?F_)frgg -1Zyko#5^ScjwTDUjCKlNDG)d!Q2VvPB3@gmlk4YyI-UR+@0X=1a~L6JHg!v?oM!bg1Zyko#5^Sb0?TP -!Q2Vq&btU6!`l5KHIVLvbSI=cA>9e-PCTWAbSI=cA>9e#&f)NpmVJo@%LwMS-}Xz=Yl6!@kROc`q{USg3%c)^i{9)edq4xVH -$|0Zrl|GOgz;^<^6X2Zy@4PR!!hUwY$PeH<0pAJuPQZ5pz7z1BfbRr+C*V7$6OY^gz9(^ELcMctK8eZ -fevQGL2P6sboq+EId?&a&!QBb&PH=ZZxD&#i5blI=nJ)?HPDpn`x)ai!knV(ZC!{+e-3jSVNOwZG6T+Pk?u2kBgquSw_Dk_>g1 -Zyko#5^ScPF?z!QBb&PH=aEyA#}<;O+!-Czw0I+zIB+wJ-I@P=AGc~1a~L6JHg!v?oKdwg1HmSonY=oE&~}2f6QrFW?F4B -jNIOB=3DQoGcHUo-ilOX&5hBobg0>U1ouKUmZ6|0uLD~t@PLOtjv=gMAAngQcCp0@_A8x`>z9dvT{Wd -ztNkX;Li$lvNAW0zY1ZgKoJ3-nB(oT?eLbDT^ow4f?v60;`LIl-LsCGiNCs-yy+X>oEXm$d!6Of&N>; -z;dAUgrs3CPaaaTeyW`$dw#*a^l?@O6T(6MUWFJNOvn240=;>V#J(ygK3439rtZS7RQ#U*rc~o$%@eR -VS!ALDdPWPEd7%suNV5pz1k&0&=7N2TbRE=`lvJ`$c{L)CrqT*mT0C6D*xz=>$tBL^>hT35iZfbV8yN -5}lCfjGgz#D0aWd4@5d4(g~4Hh;%}v)8jzuaENq5q!SXIkmyVg$uKS8=)5nL#vZ;TSUSPd36@TZ&>4Z%uY&z4MLL4e;2r! -+w^CFnS?iV3~O($$RVbcklPS|w9rV}=uu<3+NCu};w(Fu-DaCCyBGxsGZn8KG~SO=Ri3QC<&>hx+nLncsl=FY!i0DHU$7N|Nw)d{LjP<4W;6I7j`>I7A1I={$N+8eS2s!mXK=Dws31 -K9l{Q=sYuRVS1>q0|YWP5^ZRs1rb)0O|x#CxAKu)CrSLm~`fK&o6!nY&v1n37byXbi$?+Hl1khR8zpF -6E>Z&=?QX80CfVU6EL0krA}D9mjqBJfI4B*37byXbb_T5ES(VPgh(euIw8>siB3p#LZTBAowYe47Vl+ -3coQt0VCe))Cs;bc(g~JMuylf@6D*xz=>#b!jfzb(!&hkrDeFE|WkxqzoLZlNCosj5+L?o%bauSheo=&+99P$z&o -0n`bg&Pz{;C4f>Vlsci*38hZxbY8o@1Y6eqo_>_(2o``k0n`bgP5^ZRs1rb)u<3+NCpbF6(Fu-DNOWG -eeWUI*A<_wvPKa~@qZ1gNz~}@7>{U~~eb6BwPq=mbV5Fgk(J35- -r)bONCh2%SLa1VSeeI)Tu6f0;BEtoucrfYAwzPGEEbqZ1fC6=#TaLZlNSouWxdbV8yN5}lCfyl(qO-D -^Um6C#}u>4ZopFgh>A$+tn0E=8s#sesT0gf1X-!JiBMT+huBv0E<%hb}mD!J!KdU2y1vLl+#n;LrtsF -8Fi7pSgI9pJ;Z_?4a4^h?uPI_oVqrbFTDb;}%4^AkqbqE{JqNqzfWlkmv$J7ZAFD&;^99`^!PGR^9LL -bv4-ZG3aB^$Dj{RpraZJEL~vf0!tS-y1>x|jxKO?J?}Vxy4M0r7g)N$(gl_-uylc?3oKnvAGm0_c;KQ -%(88AsTKMRlC&Myzzd_5OWzaHc8MI7VCM}beNz3F}NekyUX_2%@TGTzi7wcBEELs*Vi!}kuHezgr^oby1>x|jxKO?9k+d>?zO?zLdk -1)DC|bit+zHeImkf=w4}x?t0Fc<)F05t<=EfawBE*Kyl7>Rtt?x>wK%wYTDdD5U8 -~5F;pcL8t4uxe;}*1*I-1bwQ~MHeImkf=w4}x?s}I@*mS -|B3pQP_>4HrcY`S361)DC|bit+zCS5S;f=L%l+J4(FDcE$urVBP*u<3$L7i_v<(*>I@*mS|B3pQP_>4 -HfYOuAswb$_WKrltEueqhrDn=aUN!KMo~U9jnbO&4ssVABPgF4%OzqzfipFzGsXejd}({USB6>4HrcY -`S361)HwZn^WwAg#u6)fVu$G1)#2T>q7nz_jubcDJXS8sS8S7Q0jtG7nHi7)CHxkUE*k$$PK8vK-C4R -9?Q{fUX4NNei0&gb-}9(US06&f>#&3y5Q9XuP%6X!K({iJ*R`d{D)@yJQ)V1`$dR=)&;aKpmo8k3tnC -D>Vj7nyt?4k1+Ol6b-}9(US06&S#giI{gMJ+7tp$Z)&;aKZ#RRg6y&-f*9EyQ$aMj&3us+H>jGLA(7M -KXkGK7j0$vx;x`5W@mptNn7Ua4h*9EyQ$aO)k3vykM>w;VtfJAlC)CF3 -5F3t_yNqP_u$u7v#Di*EM`EWNGX#A;4~QzsMBOy5Q9XuP%6X!K({iUGVCHSC?P$h7UdG{vBV@O6Q&3w&MR>jGaF_`1N?H8wZGN_4-76BxU|*af~W@O6Q&3vykM ->w;Vtw;Vtw;X@{pCF -vh?jz37X-T?*9EyQ$aO)k3vykM>+jGb&c#H+ZE*N -&funUG=FzkY17Yw^#*agF`yKWnc(ETDwY8F^_!Lkd6T`=r|VHXU$AlC)eDF8_kWR#wlmP5TWw_lAh=z -ftTrDne*whVGzkn4h67v#Di*9EyQ$aPJp7LkEm7v#F;_Ny@l-7lg8w65tTp=BeQ`xEj5v@W1^0j&#YU -DH`YZh+PWw66QIYK%emi~Ins3us+H>jGLA(7J%u1+*@pbpfplXk9?-f>#&3y5QAy^J#&3y5Q9XuP%6X!K({iUGVCHR~Njx;ME1Mu664j>Rt;}U7+d$RTrqbK-C4RE>LxWstZ(Ipy~ou7pS^ -G)di|9P<5?c7laAuei0Z@b%Cl2R9&Fz0#z5NxIZhx}ekrr7kFSL8%K$T~ -O+RPS@IXL1_B!7pZ|#7nHi7)CHw3D0M-p3rby3>Vi@il)9kQ1*I-x_JU4V>^vDt{-vPQ1*I-1bwQ~MN -#&3y5Q9XuP%6X --Mku=-u)s;@alqB7reSa)di|9P<4T-3sha8>J#s{;ME1ME_ijptLx^~X!GtDae`MDyt?4k1+Ol6b-}9 -(US06&f>#&3y5QBd?=1^|N3(ri1a02^a)kvzX99Ezd|lw{0$*3y2sIRrGW-aAAXwn*0$&&8x?-P~Mv- -^F$P@&-AlDUkj!Z$W3vykM>k7STpMYS2uM2!#;Ohckm*2@nzxX9!>;hvK7`wpN1->rub%C!7d|lw{0$ -&&8x**pDxh}|cL9Xk*gbMZjQo!p1S{Kl|fYt@HE}-?P#|XJD$aO)k3vyjR>jGLA(7J%ubzhQ?`tE)aC -!lo!tqW*fKw;I8w*Vdcd={F!`$e39)&;aKpmhPQ3us+H>jGLA(7J%u -1+*@pbpfplXk9?-%I#O9xw~H^30__B>H<|4sJcMa1*I-1bwQ~MN?lOuN+$@BNqrzz`o0VX&E5SXKTzs -|QWuoEpwtDWE+}fJ>b%E`Ujo4{2zCLj3us+H>jGLA(7J%u1+*@pbpfplXk9?-0 -$LZ)y6(D6^mX@(Bmu1pXk9?-0$LZ)x`5UNw4MOp0$LZ)x`5UNv@W1^0j;ZU`$pYs0j~?1TjlE_PEfuA -$(7g(T2(?RXiBLkr3eHnWzc}HT{o{rH+R2ClBQ6rhT<>OTdCfZOGQB`aM||?I(#GuUOkMmYsd4b4H -rcY`S361(Pmtbb+Jm+VMPkxcfzFz|sYlF0gb#qzfWlUS6(e36?Ifbb+M{99`h(0!J4(y6!IpLiN5(=L -j}ku<3$L7i_v<(*>I@*mS|B3pQP_>4Ht4@&~|l0jBHP=Z8?eJzmrifVu$G1)wefbpfagKwSXp0#KJm* -=+)#E&z1_rVB7#*Uponb-Q2W2S8ny6sr7*<_Hm#x}ekrr7kFSL8%K$T~O+RQWtc(t~;Km?zN!Q1*I-1 -bwQ~MKwXy;I*=90jMw7^aY!~VAB_D`hrPMVAlnhzMi+m^aY8&Ako+J);rX_UJ&UE7<~bwFJSZqjJ|-;7clw)Mq -j|_3kZDyp)Vlx1%$qU&=(N;dTx%0O6`6ZEf*~pEf+0=mO;y)r53FCT+lptVECUj3z`w**X@^3_j-Y)F -R=6lmcGE!7g+iNOJ88=3oLzsr7y7b1(v?R(bv+^cv|k8kv^RnQz|d=U&q -?IxVBqGCD1z(=s|Oqth}vEu+&iIxV5o5;`rR(-Jx@q0(JE^l#ior#*C9L8ldTT0y53b -Xq~D)pJ@sr`2;>J!d!*fBBJKt&y4MyS=B+Tcgu=@#*Pc_^oUOomS9k1)WyUY4x1p-Y{At)9N{`p3~|% --5F1oJ)getrTuf-Kd1e3+CQiLbF@C+N%LRYL#I7-+C!&3box4 -w_Rwh$o%Yaa|D5*EY5$z|&(U^1{gH-8`fl&vc}ZGEr)6|nMyF+TT1Ka3bXrEIWpr9brzLb+LZ>BkT0* -BKbXr13>+_v2|Hkc<(=s|Oqth}v0x`lr^y&>!8|k!>PMheoiB6m7w24le=xFDiPLY4(Hb3kX)pLckkx -m=ww2@BVUmU)owdW6MH=WkfX&s%`(GibBW29Z@(<$~@bS~_i{(4+5YuyB`}DRJ|gr8d!N6CLHdy-%OFW~z;J+DNC3 -blOO#jda>br;T*lNT(%qT0*BKbXr2EC3M<9$NTxSPK^K39y;xz(;hnQq0=5Z?V-~iI&2ExQ?s;$PD|* -tgicH7w0}W+{URrblO9wJ#^Ybr#*DqL#I7-+CQiLbJ{PRr=DgicH7w1iGe=(L1ROXzsN53dvB-?*KQT1Ka3bXq~D6?9rbrxkR}0h -^mv&uQzNw$5P_+Mc$?e5>#MJq~~8C20?x_Rwh$o%YaS3$UKyre$Bk&hLACdtS1e1w8y{zqt6~=igrR8^qaI -sE=<8>a?3qyXmx>PCMzelTJJ7oPB-kO!zl$gVk<2?WS}1xJPeW3+l9+PP^%}n@+pww3|-5>9AR1Pgc` -nI^XB3I|=@c+jO;{PP^%}n@+pw(}p)KsMCTvEvVCiIxVQvf;ugz(_%U;rgQeZy7S-PxSgz8P^SfT*aM -od9Uc)Lf<~+@b=p#=Ep^&br!933x3`nj_xb8heShONV(paD@+N&R5vFS99WvegWS;`=18eYk20Np)s! -prwe4nrGT=zF_znVTg#f@02>a?m(tLn6>POIv)s!prww5m?4>aZ0uzeMe;^Yzu8=KjWQ#M)Pa?%U*H?Fj`$JH}o9kS=o~)DG-?&Xu8|<{ -XPMhnrxlWtww7E{3>$JH}o9nc>PMhnrxlWtww7Jf=>0Kwczi}I)HrQ!%oi^8LbDcKVX>*-6*J*Q|HrH -u$oi^8LX`PnVX=$D7dwuKYUeoeAEv?hiIxVf!(mL&{)4n?GtJA(Z?W@ziI_;~|zB=uz)4n=iU)@>kpI -@HImy2G#`DtmLmey%$otD;VX`PnVX=$C7)@f;-mey%$oohdT)(PxiT3)B6by`}VzO&E07pE6jFYaEDb -N=q_#otleT&K--+Fa+`bg9$U-?&|GAFe)Jbc`mcrFB|br=@jTTBm(=+E=H2b=p^_eRbMb=j*FGPyLPC -{Isu5`|9xd>Tm-aqL$WaX`PnVX=$C7)@fgz_SI=$o%YrF`s&V3|4!?Rw6so3>$J2^OY5|>PD|^wv`$O -ww6so3>$J2^OY5|>PW$S7eRU_MzuVg+wWUs5>a?X!Tk5o>PFw1a?X!Tk7ytAe=Gx#D; -NuUeZ^Dw5m?4>fAdG_39579iy3|uMW>vgLN}d=c4x2XPCM+hzRttHHeIc+)A~B!_knaK` -Wv^wYK@)N*lB&8*4Jr$oz~ZBeVx|VX?>m6*J*v7*4Jr$oz~a+z7M4H&)>M6m|A0}&2`#bch`>oU$5R^ -wZ2a4>$JW;;RZHc?Xc4hJMFOZeCJX9+-urpr(Jg1Wv5+s+F^%R{C3!Bhn;rVX@{M5*lC9y_K^w?K~wd -8Nq*;_zj2$c7TRf{op#x2hn;rVX@{M5*lCBIcGzi$op#u1hn;rVX@{NXdwuKYUehi+?XuG@JMFU54m< -6zPs_Zt$WDvww8&12?6k;Ei|n++PV4J@-v`nu=WpD`iErl*&tT)!`Z}$z)A~BCuhaTEt*_JiI&H4g<~ -nVz!>ssyA4unze`$lAHrQ!%oi^8LbDcKVX>*-6*J*Q|mey%$otD;VX`PnVX=$BzJ+^bq-?$A?OY5|>P -D|^wv`$Oww6so3>$J2^OY5|>PD|^wv`$Oww6xCmJq?{%{>E*7T3V;2by`}drFB|br=@jTTBoIT+E=H2 -b=p^_eRbMbr+sz4zPdBZ-?)uV`|7l>PW$S#uTJ~ww69M4>a?#;`|7l>PW$S#uTJ~w6CRuIyt$J2^OY5|>PD|^wv`$Oww6so3>$H$fYt4K;VkeNlahsYpRH4$KQs8Yn?_>KO^c)V@mp0 -yX^3I5}|DMvjDF|n_-{-44ZTt(TW}L2YBEst%FJrtm@yfx}&)ms8$&|;e#P2)5*5TD`ovhQw-?+^W(d -}b&+FYkUko~`gC|g7JSe=GiWT!=T+F_?1cG_Vl-|JgH_nLOuX_uXL*=d)Z*4Jr$oz~ZBeVx|VX?>m6* -J*v7*4Jr$oz~aMeh*RSiNA51B);rpZ(HNk`Z}$z)A~BCuhaTEt*_JiI&4cuOVbWJ?XZ*Y^{tAHr-r|A`GE$jg?3tKr-k-8`**$NO;_9Ow9QW -2?6l2Jc+9!cO;x_vw|?$5ZMD-@J8iYoRy%FA(;_=9veP0vEwa-hJ1w%)B71&qvU@pPipzaX?q^^7r^D -Y}T(bXp_j&lW+3qD{umL#R&HdVH_tIQ)-Ql{!b%*N?*WEQmohf=8F-G$hcD`=Uec0mc_xmmxO^hZ+6Q -haI*we0McQ3gm?3DU#tI&C)M=@it3Ej`Tn>%BeG0Yf;F%DxK#yA}RaNWao57#|hxBC%(U)bImq=z{Sb -TQDyKoF9Q+OA*e%8hoH7P?Fh0J>R -$y;zkft_J;ip+5A4;-3h0MA)`Y^hm7vNi$g|-jLz=nYsMg(Y!>+2-S>uU4%r;C*^=EoAK);5CrPJG^9 -jEcLuBXd2EhzTWasR^G9-0ZC!My_{WF7;*CDU$S^1mqjb%TO+duKy2Y!_Be!HR)-XXkm?Y}Y*;bjlK? -1MmphXl`VO-XXw%m2rJG70r4BzQ>hkl-P~LxS1F^fx5TG@B_C(MfQ -pxRCYc_Gpt8UFPt39+qavfbeX7W)m|aCSMK(n?MK(n?MK-tdXX0dL`PaP*`+)3^GbE7}krk2EhGL(6A -}b;*CR@F`M?Kdisv@d-_qplkg8L`NAhIH|BC=w>6=@Y|6=@Y|Ee!OM>ylD4#hQScfSPjwCX#>s)8X$f -F4-THjGBy^jGBy^a{-f0lTDLNlTDLNlTDLNb1uNl(c_R&lTnjVlTnjVlTnjV6E-#g{h7zvWYc8RWYc8 -RWYgc1^E3k{quz~9$*9Sw$*9Sw$!HbfWXB|+CZHyuCZHyuCZPMR<4hSn3_&$PH9<8&H9;*YcwaERUjo -c%MAp~-H8+Qa$nX9yL*4s((~lVUPYg+J*^umKa%*yHa%*yHa%*yHa%<*P6VMvN$v$qR)XyeOoDG_^nz -Wj7#EyC*H9kz147Lv9bbJ>>Q>R~+e2>IkM{7i9>U9>+`mJ3&+OknTrvO?>mju4KlwZQo*T{vD-tKW@i7TL+W%)D -63lL;ze9ox6DOqd5V^zC!4`49!vk@4Fz>6UI2Sx5_>ka3g4q)6cS!Kzte{G9vOh?I4+;M6DyIhT{xqX -7&z@_4XUv)4A;yOoA7acNj=#fvdzfzz;XQ=+5Z=Q)d&uo!o;~FCkk>>R~+e2;-xjp -3eklRCU_r!D08V?iezS{1~>LIG%ef!P@%(>tpvWLhXB72DJA+m?a9wK{)>>;v;iS>}uLrM=5>mi_rfS -&B@jX4=S1oa}Q7eT!U>P1j5f_l*%@&sO_^TdVV*7LuM++BPB2&wDGa6~V_C68Ri=bZ2vljvV9wGvI5zvc(UIer#!cjOKya?(=P%nad5!8#IUIg_ -bs24%Km{`js=G&?_LKjiJi0VaDuV>HG4rhZ^6YuZUMQ$&0dy(6V++O7NdiG}+h}>S}_S#2d4@O=u@_N -1ddviD)yvXfEZZC3sk=u*hUgY*7w->p+$n8aLFLHY^v0mi$Ca*Vny?>ju!|C8nZf|nSX5GJ=$lgTuCb -IW$x0z=Q(t2}3aFf!Tl-{KDW=_2cC>tC9C%>~j4ne&M>P=8@f_f9wo1oqV^(Ls*m9xLjHz~b$uX|E@l -hXB-v$NXaZ15(sH<7)G>`i2EB6}0rn+V=S_9n77k-a$~xS39G0(ukBn}FVD?@d2|-9Iq~LA?p;O;FjU -{&y49o1n5@AbS|5Q#KO+m0t8cH$?R&s`qy;aB(_#6WN={vRCPk4M+F=_|3$6lh&KGvT6J8W@5dWSZ@M -)6VRK0-URgi&IK;c25*9T6I8Y+&psZc^?vvN8EBsl(t0zy-lX&Z8pN$X -8o`KI=rvTq`L^RPTbmi@1P4^!+zRI4*5XaARv)O`1aQ^AMuK7{unybs}h2rqk5|4KI9HJtxtoZ0`sxM -Tnl{4m8nB=^zg?BZ1LA;D~}{Ci07LxS1(`1g?DhZBN_cMfO&nt@30LxLaX*@w_Rg!bWk2KdG6VF>R-c -pt+15Z;IIKBVg5$$Ity**mpIBfJmceVDEuLi-Tf=h`{#=4|jGybs}h2=7C9+5PtSklTmcKFqEUk$va| -_ApGb4|#pa>qB0j?_A*KY_QUE+U}n04Z8P|Aqnq8cpt*c9<#rP@IHi>O=W)%v+F}>A42;+&<*?A-C+$`g@pNAM*N;S2mvhJ^AhpXM+#3EPEOM9wPe^SvEBNy+rmUvM; -mi%k26RP`23pu_5m7C7>?>eF^CMoeSKY558pdC8IAHeaYxcMqe`elF^sh^(C7x*<|DJ9~+PVUb6X;&6 -jNMW~KZ}^*F@yC7v(we7*aDGY0W|nNwe4`4Y>QSiZ#aC6+I-e2L{tEO+x#eusJ-;`x%Bmw3LU;U%6g@ -qC$6Ut;+Z%a>Tb#PTJUFR^@ywFN#@Io|H~xGM#jIF9KPi6C5JCLe97TU -4qtM(o15}$)8mlGn$p=*&X-WWyyU-x@g;nKjWK#1p7pIb9-^%P$Bw{;1z+;{Hbb^qGGU;whYZ_M(&mwGuR+_y-3D!!+lA*1_(ysvZnTC%SPJ5 -leP7P7fBRiCM|Zz8?}fAi8sKVxs{yXsyK;HBytwSXMECyS!{y5*0}OvZ3@{8Z`|hKkcJH4UXc(wL9rTJl>wC!tjhZ}LY?l*t-!|?qRV;s -iVxB7c!20Hxz#X$WKKGB@tcgbiMx8!1!i%~9a$=9p)qwy!-k22am?l(8?F7 -$b}k#t37CF~adlDQY#S)u2{`TJ;g%Kv)A|&5%~CYOt!oss^h -XtZJ~Tsa5ro>k`n}E~<&|ariwMGCBkgoEmUyz^MVJ2AmpjYQU*koZ25Do8Z)dQ#1E>cY0ztbMH?v5E% -uh2AmpjYQU)hrv{uFaB9G*0jCC>8gOcc_e)ztG}}E4867f;Pz^#g2-Pg}?cE@#Fx5~94=)S@Q_T=igl -Z6~nSA%=B?AO?2r5D~2-Q@m%v-uas0N`Lglfc`3QkRN>g=%)szIn`SDX;+e~nmgk%%lpH3-!pRD)0rL -Ny50AXI};4MH^t)gV-ZPz^#g(%zeXg1>)a4AP2F4MH^t)gV-ZPz^#gVmbw<2AmpjYQU)hrv{uF@r_!< -xW^%=;M9Oq15OP%HQ>~MQv*&7I5patbpL_@NGU=!2-S#<(@Aj;Lt4etti2v-MW_a$8iZ;PszIm*p&Ep -05UN3_2B8{+YP9ob1Ki^fR7h$dsez;hk{WFa2r48skkmj@14#`eHIURmQX^uioj;r19*1~BQlpIw@r0 -xXk{U>AAgO_*29g>`Y9OhBqy~~2lSuFU+1UQ;pDzCH=JMh4<&rT7BP2DD)Id^Gk~00p;8KH24K6ioce -@{7C#pRP@dT#^oEmUyz^MVJ2AmpjYQU)hrv{uFaB9G*0jCC>n%(}EsP;I-6PFrXYH+E+r3RN8Txv`#A -*q3+29g>`YFOH`XFs^qd@j|b_Aq1=ml|AZaH+wi#>CUikZ9DPQG-T}cT{3&VhKPE05$&R&#orupBRI9 -0#O4*4G=Xz)BsTfL=6x%CYBh~nC(uPvG<4s)X#TsULrtH(WpVA28|jtYS5@bqXvx{G-}YOL8AtZ8WT_ -eY5=J5H-Cz=LJVqZP-ng$WgyZDL=6x%K-2(H14IoFH9*uHQVKu~05xYeYv!b|5Op}cpEG;OK+K;&)Bs -TfL=6x%K-2(H14PXspof5BPjkNc)0`AyP=i4Y1~nMe9D)iz4g56l)4)#yKg}Vau+zX!13L}uH0PT?yE -37FVhn2@0O -#?PfvFT)gkbqX4&h8UH9(xqh3O^0}GzT`_fr%^zH5k-jP=i4Y1~tgW00%=^JrtR9w##n_dl-Vs+sE0H -bqs1Ss5!9d-V4$SL=6x%K-2(Ha|kE^H2~CHzr8nyvqB(hfT#hY<|3#V)L>A9K@A2q7t<;HG#A-~od$L -q*l8}ZdCfjIhtopjX^^Kuo(6du30@CFP~oS6p9X#!_-W -v$fu9C`8u)48r@08|ML@x(0huDw^}?z -;EYpo=0-&*aXY$iYtoKg~sCL8k$o26UQ>>GUF?IMd)vgEI}zG&s{-&v$P)EySA!ZyLO5@TS3=25%a?X -)Y#FoM~{T!I=hUnmW^I{c!kT)Lh%;H&{IiLB00geE-g91QmH2bYqaGL7wI&por5{oV;(yV5fnd=3dut -uKx8;FMr2C{q{Q=kinn^gBlEKFsQ+x27?+5YHp&6Jq`9W*wftl?hPk}7}Q`;gFy`jH8+ukp9X#!_-Wv -$xtUH8r$L+saT>&F5U07l&&|bIA@VfH(;!cSJPqJ;xvfUAWnlg4dOKSwMn};D@2 -~=Cas{;fKGFhR=jEOrnyNg&NMjF;7oIq&3hkhvI#NG{p@So#aSWLG*Ht(O;c(**((rKylHNNx^GJMJn -bf+5Ys?R12GN6H21r&Z8s-{P}4w712qlQH20>5N%ZC=oFzc-#)WL+OoKDcO*SE>ftY3&0g;%0{nPMw; -?i6mE-x-`E*~ymE*XPK6j+*vL}E&VDGjDH&+Mzg%~>I~G_@t~6az5LLp*_{0hR_>8enOFr2&=(Sel1e -VoLMyNt1g|qi#+L0j2?%24EV1X&y2PEDf+Uz|sIq!(t&m6fx-1pi6@;4Z1YDVu-l(Fa#B48kA{Jra_t -JA*gKDKvOx--eKlch-o0EftUtj8i;9j0TDszVMr^=G$_-cOoKAbLs}owiYX1IG|wKBlwwMQDGjDHn9} -TmA~MqB5L9ewu%&qjDyB4;(qKx1DGjDHn9^WMgDDNBG!LuP_VC|%$HPhCvyU*7D8MuT(>w&VuynGwm$ -afwgDwraH0aWxOM@;Ax-`4oh;aPtpC111<&rU&M0pUPrg@1h#554oKuiNM4a77M(?CoEG0m&*-f&I`H -4W4>P}4w712qlQG%sm|m-u;a9u)1VGrvaS?bQ;iUK&Jtn26P(GX~J|0HVxP`VAFt212zrVG`n*IPw~J0i7}>U={zFH(; -!cSJWU8H=rmzE1)BzJ8n9`=rU9D1?%$GY!r(`T84 -{mki!CeBal1EHVl@4d^u4A+rCO8QX+J^b~}KsPtyW`LUkZ -U(p+fG;!PN$g>626`Ci;l@0S@i4~27!PASjPWqW9_`_}FV}s!?#p#wuFIDj@Fey)FJrun@iNBC7%yXl -F~S&Oj4(zxez$@Kl_giL?SUIVM?kg9r!`OlE&Y?>4~8LCIL(Wn8dOuVM@Z3geeJA5~d_fNtl -u_C1Fa!l!Pg1&UbHmW+-e)*pjd%vHD4vk}xGz~ -pxyc7lp730)Gpq~Xi!NdUy8*#?TABdWusWlF%igOG1~VJ^$=;@$c} -mC8$YIlb|L+O@f-lIw(O*f|vv`iSIn)6;C*ma3WZN_dm-CgDxOo5YGJ;Y`Argfj -_e;yadm$#u!Awu|a>(8Dl97eG@j3V9OpB;-lRlaMDNPhxG95GNr{LY#y+32~CPixP`Wgggm(lJ*JOM{ -ysDy{~&yNNeTkZ1Um_K((W?N!`{OXS@=oiC!M{Gd(X%%1|;3t`R7IqTsB-lx?lXi=09@QR)w8Bq -B*35XI9B_K*Bt?-jr1SQx>u#; -dX!A|mbF2E`i!B2vp3rt`tTGYuB;-lRlaMDNPePuAJg -M?@I8_$>B-THP^-n^agg6Ot((Z4~W7xwGROCs>laMDNPePuAJPCOc@+4M032_qQB*aOGlMpBE_p$Nt^ -*GF}$diyKAx}b{ggmM8WZHJYPlBJsswcrtf}I3A33k%?&IJx}T1R?^p{ -K-laAx}b{gggm(67nSENyw9sCm~N_nUioP;Y`Argfj_e(zRW_SZgA@NqCd+CgDxOn}jzBZ&JO<`~DJa -64)fLNnn$TP0k+on?G4HT@tz^bV=xv&?Vj9dnuNi2xSt=B$P=glTaqH!b#|o&?TWuLYIUt30)GpBy> -sWlJ2!ovED=|lTaq1OhTE2GKqCfLYIUt30)GpBy>sWlF%hpm(Kn+mza7O)|&`w64WH9Nl=rZCP7VNag -!h>K}>>}1ThI>62v5kNf49ndw)k+Zz8BkP?Ml0K}~|11T~4(OoEsMF$rQ4tC@r@30)GpBy>slewIWmI -1$Pulu0O)P$r>FVj+{zC80|~mxL|}T@tz^bV)2=60oFuKT9H(oCshNz@!4x$#;bg#6l*aOG1}~E(u){ -x+HW-=#tPSp-X!9vm|20iBKk?OhTE2G6`i8E0=^WiIqzNmIN#bSQ4-#U`fD|fF%`{I1cBA0FwYF0Zd} -ik}xGD7t; -dvvDG5^&rX);Bn36ChzCPgWYwfe&5h0eF2w)PxBo-}+MN7hzgeeJA5~d_fNi0?poFq6&aFXC8!AW}eJ -0iq_6TwP?mBgYY!AXLX1Sbin(Y7F4Rv<}2l7u7)NfMGIBuVdnmRT$~5uzj(EeS~yk|ZQaNRlc^rwxSZ -r-dmAQxc{mOi7rMFeSbFMFe8KiCDfQmM@9rO9GJuA_+tih$Ik6Ad)~Nfk*<81R@DU5{RVtd;iXY6R~_ -rAd)~Nfk*<81R@DU5{M)aNg$FyBo&d)_AE$}kR-j|`*#+cu+LR58E798(uyPrNfMGIBuPk;kR%~VLXw -0e2}u%?q<7cSi4`Y8l*IBSAxUESl0YPZNGc+oJs(7pgd_<`5|Sh&Nl22AB)#AK$$}FhN@DqvkR%~VLX -w0e2}u%?BqT{ll8_`JNkWo@Bne4U_B(dOf)j<6pWFb}FbPf)YnX&22}u%?BqT{ll8_`JNkWo@Bne4Uz -WI{{Cqk6O@+BcjLXw0e2}u%?BqT{ll8_`JNkWo@Bne3plB9g|Cksx5D2bIze937q8H0EtNn(weKqP@k -0+9qF2}BZzBoJvQx9ilKlJT&n`JRydCoIyS%u(xqP^Mxn!VWpkbh4pk~u>lHnx7NrsdB+jaF~#ff1h!%AlPlHnx7Nr -saQCmBvMoMbr3-N`{rpQYFJ -lhLa2@8BQ{sWH`xilHnx7NrsaQCmBxi%$I|n8X8tItYlUx8BTIJ$^5drrgZo%GO%P|$-t6Xr({gYn36 -Fi&wejSZ)1OV_8%Bc+$`dWDH&5TrsQ3;6i$^GhJd0=Mwg5(xw>@nJz}glG1O$J$xxG_CPPhzn#}SgLr -jL43^5sEGQ?zv$q -C)t_kJ+D!bygc3?~^*vi7qiX0?gSO4q)yVoPQO@@;UCmBvMoMi7vL>5jmoMbr3 -aFSWTWF*Pn&ytwsCPtLZLM9_gMv{yq8A);_>0EnLNGqIVILWMLGLmE@$w-pD>*~#N6C+AyHItDfBT23 -#UB7)4NGqIVILUC5;UvRJY@RagGSanD3~+t-re}nPl?*GH)l7zy3?~^*GMr>qGZ{%Tl4K;wNRp8xBS} -V*y!*#4BpFFEl4K;wNRp8x?{@P1Z0`;kWg(N1BqK>il8hu7Ni -vdTB*{pUkt8EYMv{yq8AkqjakL^6nE5Xm5t&o_Uv;KVFnauMlVdn1V_lH^L#(cVjDQaH&hWHORu -B*{pUkt8EYKIgkPoDw2R`)z^yMQi&lU*8GxPGd-tkt8EYMv{yq8A);_>Dp6%ILW&=J^$RpkWpC4tY$K -tWL7g7NivdTB*{pUktA1=&b7}F*@Tk}C;2?zz2SrqRx+$))-V}PGMr@AFd0cQl4K;wNRp8xBS}V*j3k --0Oa9hUn)N1T4U<76gGdIE3?dmsGKgdl$<)rK*fOPod4FZzT6y0!?fKts4aDF5>*d4Wy<9Q|83mCHA{ -j(7h-47SAd*2OgGdIE3?dms@@{R<-}N|T6htzJWDv<9l0hVcNCuG%A{j(7h-47SAd*2OgGk=3?)keOh -h&0C29XRR8ALLOWDv<9l0hVcNCuG%BAFFSY`-v! -89Fj_Wa!AON%FnlC1%#07&1eltjJiAu_9wd -#){0UBV$Cyh>Q{W-OqfO)hEV^j1`$xM@EQ@5E&sdLS%%<2$5NJWMs(5kdYxHLq>-D?q@#C>JuYGMu?0 -M86h%4WQ52Fkr5&zL}r%^Eg^UXs7c -wi1j0YJHG9F|+$as+PAmc&KcJ5~ViE$z0LdJ!R3mF$OE@WKDtS~Ywj0^=C3NjRAD9BKdp&)0?(kwtRB -xFd)kdPrELqdjx%;F+LL56}11sMu56l5sKP>`V@XFqdnR-hOXG9+Y3$dHgBAwxoDagm`QLqUdu3hZsS|M?&PZ=1QY-O00;m!mS#!*fEffE0{{S&1^@ -sv0001RX>c!Jc4cm4Z*nhkWpQ<7b98eraA9L>VP|D?FJow7a%5$6FJftDHE?ooVr6nJaCwzfO>f&c5W -V|X4C;#mxZ1dj0^1FGkQF;M*0K%BMv;?2OCy^oMH(cPK>hW7^jETt1MKQ#4(C12do!MH_xeY}(Zgg$( -@8R#|G)D{EAi@?QpbvV-E)q)56$eys#`hm$YdQjBlS3HgN4*Jhk)GOKxcJ<;!27xSAeOtQ2J --*HOlf?Gu+rC5h4A%1r~AJ$LgrB1EaMKWHJ<*2TSpYnh1#8D79PLBQ?O -vQ3heGmGAv89iKe&5GEO8&+eq!qop-<7^{Mw1JPv~_|_)tH(xeve_6y1_SH|!Z1UA5pvX-QHaXm{`)K -R^hf&loG(Fg1>=M)kpfT>3s)ew(kB6we;%dz@wQZ25SHTsVze^zXpe!WB&f1N`xb?RART4NHz4BS -WI9&#XKKHL^<=>;$Y@AlP?xmpsWp?EZGaG+)?@?+UroDDrqO -47;P?CVxmakFS2j{R}YoZ+k;*}UJSh>S&=rrPtR6jb})T!x(k+<{`N8SMbxLy10)IkL!kvZ;A2^VdaR -7GeHt3)LuwiWd;4iHxcxZz^L9YA$`N_*qtWdCKQ5JSr&C!|Q&ew?ov@Dm-kJ^cqkrK~ls3xCVe2`;ZO -(pixIJO8U8LSRk5Q{dv6Gt5u;g%)hnw_xbB2KCGIwF7Pk^D^e!hPdkFkwfhfQ>;&bj(^a-OExRjSkJ6 -iPUEh#iaTU9TCN0*eA;QPFxWv2WJvo?}&=;^DgvjQ*W|$quJzU7w_;^!1AB-m6mIAm8Zy3E#iIYenxj -0*kHa5xmISvR|K?xYnZP-{o2Mjn(uaJn*6O3s6e~1QY-O00;m!mS#zLvR7F%1ONcY2><{y0001RX>c! -Jc4cm4Z*nhkWpQ<7b98eraA9L>VP|D?FJow7a%5$6FJow7a%5$6Wn*+MaCwzh+m72d5PjEIOdSNYTU& -bp@*+SFMYm}J1VNB)9)it6OG_i0Scw!!%8s$vfA0(_`O>|h^@A;OhKDm}W)A)4m$Tm4v!&8!s5j8<{M -zVbGMOZQmsWKiwrf?c!OMGeU^IfU&>D+Ss|KNye(gZ&3M_WUdUgT7MrENwkEO>dL0y_k>6J^8KYz-$Y -cR`_1nBb?%C)r75u9l;VDf_@49jo9Z!~4FRk|`;x45``cS$dP4W-ews^|=a^Yq=`eGiaHoG;Sz -B>7l_He(W$Q)kVF?A08UA5vD7lhR%9p|++4=P8vm2?Ql!r6}s&_ZEu+RLdNITsqVA9*g)sCs?IDx#`j -?qvz0TMU-ICD34Y)M9jF;t~zNQj_+>g;vve6;HE=8oIH>>T8d(W*3kh)abi#DIJU?pGED^>c6=KRSA! -u*k_u}ooeQ;REK^S#rP0kn%NBDH2oSR@FwHFR?^h%SG)SU{Qe6!IOMav}Fs)0uq|%P90H!AAP;EMt5g -O$@uSNiu#s42w-13nL=939}x%23bVL71;KGQdwhiIrrng&Cmc -s1Z0;l@1_+oxq?91c4zy1wxQ-KX&}tg3o2+=wxEaeKhD -u@vIr{4s-vi^nI8|8V&zQVOb-e#+U*pMjh2;r`cW1Uj51ZeuFgID8>S8o;>kB&ylaimg<7N@KCfKa=< -jZ6|~XzaL0=8fu5qv@L5;pqZTe7PXJEL~TOtvgV$ms!2#$U{!fFt<@vH -@(l|H436omtw!`&SXZ_4O57uoII9ViFABOIljM_C;=YmDJ&1WvMi1AkGkKc8c82$Er`Pe%Dje;@2$f@ -Qi7^JuX1$@PDg^iw^DAI~?(UgjrE4gdhyC10jVgVy4wbgKpjI)>2 -F2_s^Hf$kl+<`Nb3ntlpSYu|V-}C*sHvld2*MA?;KD4 -I-OV{>VGy6yX1;|diP04Mr*`@G>s_tL{zde)Q%y&RLmE_&WssOG3Vx%17<{JBme+80001RX>c!Jc4cm4Z*nhkWpQ<7b98eraA9L>VP|D?FJow7a%5$6FJow7a%5?9baH88 -b#!TOZZ2?n?O1JZ8c7oV&abG8bvn4k7#!y|cGi&qV~51X7B7l?s|XD<4RkV0lb$Ao{q_4)4=*#!*qCI -c^+|^;!}L^FRaZUr)&~dKAF4I3nk~9+)*G!(gKF2e^$+v_?LqEDVK0;0x-^7R5$PETKhMJ9QPs=iNcrVXhrLZQD2FFHwG1xCxk49Coeg)^)W-8lLS-AR9>< -%A9i8X7W+?iOaOg5~*PDqjw?qZe$op%#KdH8gW5_J$T}My?Z(7M=_v_ArDYC-9Dx#{P-&s@0=w5kgbC5`oY-&E0I^OBb83r7MC^kQ36B*TV6}QkSeqXQ>EXDzLZKeM3UxJ%|2mK=)&U*{de`oo)3-dKRr3-aL&z1msIG{NSi9?8J69mDqc0fso}9I&E+pjjw}GrtH9prwz^?z` -Jwm#VFM`dDqZtF7++8_>eicnkkUoaAH*2zrE5X(uoXC=LcUjduMVc2sN4>t^?!wZCk3Ta8YKE^pgZrM -qgo+pPDlt8KdLweM~_jS^8uh?R-im{~?%n|KJ869r3p!Z^|oqod!$y&)9zJsLaT1l;Tj2}F^D5ScFa@ -}aMSkv%WAYz+E_DdF!TQoyN*Af7Lc^ptRqQPv -S1RSh%FA+vsMo>DLmz6iE}ocF4syAnRud|TnQP_qfu -R{BNU}qX#}q%KN)!y?WihXm+~oX06w4-nRPfD!vLE$zSxg$ -Z3Gu76SOR{Py|?MCO_?eztnQ>A=-lI!%^jsAV3gXePT^fY>I-P*@;C3n+oHE()1>CG>5xt!}eA<7iab -g{~Bh_))K+ai?VnLUgrhScv%6rFxQ9}0h1I9neQNy?c{C1Tt-1K$SnNEogcUuPn4kt^6pWKZWp%JMlM -5vfUx(UmaPqWMB$y~@%`{mGWip?gaOVwq#+DGIoXP}VCtd22}HpkaU(t{a9l$K-UV`HXgM@RGdyfleAl9JM+qp4 -P(g=@ki85sYl7f3Eij?d>0g}D$0FS$aE&BTv!<1{{;X)b`vOkPSEXE8-r;>W$*=FFODON|!79-0ZZpzF_UIUhS=j -MqS*a!wpC1%eCSdQO*)AEp7K%+i(|8x{b(Vu9(n=m0{3vru#JQ+NrS2nuGCLDU1r$me6lRdfsOT<1_1 -xitt-v5vR(U?8v6*cf>zglBDBB&Rmx|4ei(f~3uvj1jxiBC4)m0yPgHHW-<4-0Y~cD>ig~PRD17{B%H -bgh${nfLn9{h!S)q$A^Q30q!>vfNSoUycl?vPM2YVpIU~eJt$(lZx`}>oJ~**xaKT#+hHZHceMHzHcC ->bz4;3=;numTkr`cPrh`}-p@G9`2G -$qf;S0_&Xpfv^m_cJW)stTEFSBahY)Gd3$w?24qxpo;RwYx~QhXprAh0~bIXdi^WzwGbjROnWD0Ota$ -Sy=l(=BB#OIYr4VgEQ!$=TutOyyIm98o@>W_Fn3RY*Ja-@DRjp|A}#hS{n@r(v=@ug=Bn9wOg -p3k#S6}xoKWeRx{vqq&{{wIYqEjCi)286^I+LY;wK+Q(uK4T_b$eV`41x-0T?jn8h+ygOd1Yx}4jlAY -*XCBMVr5Kk>9;c&nwaMuy3__zUA!|%Hy>b?{yd-B_)@fV+w13`GW)qYQrzqWQa%kd!AK8NTfXK@pnw3 -~0Uk4VCA!;??b2acd%Gk@nC>|U<+`$5<&nBvBTeNH@XIV6ggu*PiHSk#o>ling?LzQQ%|n{Zd?TmOZN -<(`%ub^H2*;eOxz4Q%PmG)GaZ20vL}pzzdv?;u>h|&vwSk4*ec(e|OUVtp8@sGI>aj*|d&1hI@7XnS? -Ex)rz%ZE|c0aBiSL7_rj*_;W2u-4+AR0;?8X_*WQR+RT@^|-y4iE7?6baf;0BYVAty9UM4OCR{qLdz> -RRH1`eI<;`=*ZABxnl|Fpq+}^EWW>3Gq6haZKHm^9zSgP{=NLE5YNUjO0Lk~>nE9s{PYAlGan;9w6q2 -KWs7~~Q^CHgZVGw2PsjxGhxT&@b80l-0p`Tez8{z$o#KB0%pcl62j)Tfe-+HN^3%awEB|F+j_yL9Et) -fLdme*x>KCU~?-g~O_s!0;jk@%$`(r_uypP`_<{rPI-!tUG&#{^CheGc4GHy9vhEhhvgV -7q~PIzQprnQy=O=7*`*%OB_s9!66bkNSh@VgSAIWB(ge_||K6SZ)v=6@TG^1<;os0@pB+|L`DS^uFCj -g1gK}1#ZAm7;wqQp};J5B)H)e`UPJxkRd9NK9~|v@}Hv7E(`GhmrJ4PRfSR|FBW+wRcdz?GJ_th;{>s -I6;l=~jOnV4fkwXxo!i?#38K{9fo>4M-1=&mqek_LUY1H<3ZwWsqC^|I`H3t56Q+K`j0Z1@Z{Y@rq5& -ge7MqO}wNyM3E8uIzRBUzg6(VTqn>S*12eFL6Wdam>T&M7)gaIbJ-=w1Wt@OAbTT>)F%E!_+sJ411;= -}X_#;A!ZJfJ{D>CB>az+DO+a~Fk1U>ZVe)0-1d?N21Ag6PC_bVz*7Wn9NcDH4SEud;Qn -6dn+oS_YD66qKMDu>!d?wjCPgFICaxJYm}q(1#1M9TDyIt$hakw!lXT7*Ep@O`xW`uMbj?(`YHcjxo& -boqQoz9coBQUy~az744hJ)pD{6DB6+1TY!Q`(G)d+Z&BX%V$e}I9g5zivbolg6v^Lx7k@U$RH)d -Pd+mopbjy;k#;+=62(X6MGGc{WrrUCRtQ)|rdDVM?P3m0XiR*>RtY3pBIDSgY0CfN8z6J}4Q5=~3gzk -p`FMmWt%VYQ<^(SF47`z|ZXVIr4Xoz4lJ_%~@@`uAyp9qb=M+`}8nH&~)_U@6)p$YV#0L#ZxEpnM#4R -Jvl|o$NZYvfy;TcY>VzR6elF}OYB1(l{y}q?Gqke-mE%i2?4r&sUj>m6G3 -xQ&E9-oLAzL;7Q?g4~2^-sa*hOAB84TA|+g}$iA50aYcT#^QOu0$|IeE63tvCAJI#cX#wj+hx%O94L1 -3zjv&zt7bj&tipkpH%usDei!3mqpAauChdG~(MZM3LjDyKS9F-tc)+gmuRqTBe(FNwTe=sPcFqLLfqR -QzE4OyVi7^0iQAv(J&S-s@iUwzaLEBr4|O9KQH0000805+CpN%$G)gIfat0B#2W05$*s0B~t=FJE?LZe(wAFLGsZb!B -sOb1!gVV{2h&WpgiMXkl_>WppoNXkl`5Wpr?IZ(?O~E^v9BRPS#ZIS~EMUolcX1azIWc@q9 -cGmQNr3|i7?T9ea(o4AA@e?bvWlN?Ye+C-<6ZS3> -TZU<~DYeXmmqt4r?bg`c0l+jfc5lxt3(q;F)eiA(Dq#cGwnD14r+U9ZH|=%@5NF*Z>MBL^;Ol74j_ni -Y`%3yodn?lt_0?VDBP>J@!hWZjR4Xet)FLqS=pYazRWh~0RzcmuGLt -ife+Khgs!U0l&}2E6Zl51Yc@jGR-B`qLOfHKmm`a_a-cXijyKzU6?MzILIn9>y*)$$RXfE?n;vnWZkzXV(0J$+x5Trn^8n^|y(s$f72F=)g(n?+P -u55k8a!pohvB+oYaxKDa(~5!=4$blR=XAMFm&GKS^SM}x`TJD1N@tyWkJO%xgy#wx?Kdo;||4hGP!)i -NaqFG*l)?BW=V3+@)~m3B62%^7_ulN?)>qN``TtY -DFE7^A^lo|<^j}a*0|XQR000O8HkM{dz}XNq-~#{v4haANI{*LxaA|NaUv_0~WN&gWa%FLKWpi|MFK} -UFYhh<)b1!3PVRB?;bT4CXWNB_^b97;JWo=<&XlZU`E^v93RojjlIS_s4D@x*FU_;m;(Mp>c8=JVZX4NVSoZ7pxVyPzhDox1qU&;e>Qt2}m#-gcbsl;{>i626;j}}o{;2(hUWqN2$~4|G?b3{#r!w2Xw8Mmv1kt+Ck$ -fN5-OZjUSr&oQkI8RE;Ugbi5R1FqV5sW4H+uB@|Yy)K&+9mR1rS#M>n@2u?o@$Qs{;~X4bDtaIQ2asWaFOw8swsqR>6Yh*nHH$H`9BfPh%~+M9hI& -1cjcF6e7>GHDKHi+jXEX?W4U97(2IB>{}8tkC`d;s6+QChgD2XtsL&-fR)rcYCv8XF8?sXhKaIHz%`R -d){wOXgr^cN7GK7Xv%z)IEZ;om^R{x(dLLHCW-`BWPA8uJyb@ovwD$ -o7WAHELfZHm?XAS@VESHk7bxfc)-d!@y+(gE5G3?>g%(Va-jxZUyUG|_qn2?#gbtd>y?xhc+`etRzik -lBr?9+ss5AWZn~zAjR7$K#sUBg8JsRag=fe8%)C=oR-gFy=B6sbr*e;EYOQlkzgbR;CEcF@*o(Ce@NX ->U8Y$bY-&~hoU+{(rWGtCz$HXbbwb!(MR~6d)hl|S^L((^Vm3q+`bag-U!>r=)H)rcsqQhS -~~H`4Z-)4NCB1=0;ocQQ>x~F{L%$yQA~6JQO-t>Q9d>s>3gO%hdsjE7(}`V#H3)o!XqTD^I3Q;#0%w2 -mXrY~)@#4HNE}RlDp&=wR+UAw(dMHx-vxu9f-6HNC -nb!XMDh;}12R&zqy#)nClmkI-k@DE7?k@fUmX<|8#uA(s8l>TB1XCmDJyqujF$5@Xh$uf$cDqD{|$vh -B~AF4KJfe~wmEt3`@BOaB2-O9KQH0000805+CpNg(^(IZ*-t0EGkq051Rl0B~t=FJE?LZe(wAFLGsZb -!BsOb1!gVV{2h&WpgiMXkl_>WppoNZ*6d4bS`jtl~YY`+CUJ!`&T@P3rG%yM7@;uV5mu~5tUvqGcwydZTfcbg)Kl -N6x&T9Y_4&GMjHpGv2Ie9UkLld-mkDN&i71EyQ3DQBR;*&Ov-|0e@ZD- -o6f(%?maAbBCx;$R)mm4g|TK3(OA|q<7_gO7-1}|hz$`XV{)7flA1+w;tn|DHzvk=TR(+elHnt&wxyS -EQyEL(6p9A92LGy2d5ec?L&2D<5h0~Xz)7Od`q9KKtEYo-OHI?`6U;j}6yYr=bla()N9i(563kaI0<42L -jb@ug5M#ZG*Q+EP0twNj#9cApEAnfJOQzguu4F+@$l9J#-*S5`3#_-- -@?HVIokItZ8F|8bvsp2wZ)*2T8g<4f8749|1(ELExT75zIOBVIM$G-SfkGY<~Fq0b+m_d({mpn*rVrM -EFz>&W`x+kp8CD!D+SdFEy9i!c!Jc4cm4Z*nhkWpQ<7b98eraA9L>VP|D?FJow7a%5$6FJo_R -b97;DbaO6nd6iVbZsITyz4H|#acL#W0;?W&(H>G7+Gru5B&gz4nPdQ~#zwYjmHqmC4$vM-rQJx8;_;g -|Gw<1Hw=X|*7e6E^O_EWZ<}uw(W~1Nq1-9L4Q?X^5S-Fu~C>oiHNjjREKT;_{3oT#SI_#8NVI6y_*mL -QPr4rC?>&%d7=_O$I{L0Gg*^*h4zd#Lq%HEY?&j{^y2Q5qCrO^&cyEnF3fSBgS9^<8D;klw!-7tPGC2 -Zi@*C@61bTzuBL9f>b#YK3RBBdw`SVtK2-<7-mi3rhD*cX~hwXt$XH-rv7c|-HsZ -;fsc`L@O@lnMLhecnc)lR%JE$(0qh4(hHgGdZ*NZ|HnTwK-5JG+C~sJJyF%o`kNx8H@a#spPs2p@Xh4 -Zy3w7-MO>K59xv?>@W&Fux5+$^TSHX63G);vUW_u(LTi7_Cv~XXe2*1LSqB^M*bnO#D1t9>_>zK-Tt} -SiLrxO=}P#}KkVLz#A`HNlY(9LoEfaVpuxwe6-nLjZs86-z=%oTaQrs16u_rKhBLoGl89(j` -4bSr(WCq)|PIgMu-MKq7HA{i|vQAYDcHl -O8j7w$5jH4bWCQu$ruH7IumQsR~fsoZJyB@A}xP*t=QuMDG0#=WXY;Ee3+|8h|oy=ewU%7>yul55h&c -j(PZoE{v|PyT}C!{H9@McM7pZ66?^UlcsZkv^7dP>+={wnKNu`2d|p$n6dK{oYOgd#_Kl$PxMKp*X$& -$KOx8)q1XYb=c4^>a|*Y-~3#pKCZcGH}P2XTbvp{Wqb~R%e}Dvh>5L*=nW$A?u^JEXIU{?6wk$D7Gp8 -F59scX+ty!DO9KQH0000805+CpNvj4X#!LeM0QLp|05kvq0B~t=FJE?LZe(wAFLGsZb!BsOb1!gVV{2 -h&WpgiMXkl_>WppoNa5*$NaB^>AWpXZXd6iV#ZreBzefL)k>K6xa)VR9~Y#Q`IR_xS>Z;)&hc`|5eWD -})GgQOCuU%y9|FKOZcTRus1IXrV_IPLc3L;b>W^p9BYhCtZnbH#Wtv&JkyG}__U?u_040GQ^+HuluA@SM@Q -+9STdO4u4}SK+Fyr%rT5z1!P=0dX-rpin7_>sUuIJGRf9HI+n0dn?zaH@4%hzQRH@5%wp|rP^3o&^3X -fCvRw8`K{51`XP;yYb$J3tCU-2awhjLAa+NUDJd12EN9X+@DRe2(Aj5Wkzbfh=2dMfb%t_k)}C$Qjv7 -8pmo#Pvru4v?EvlK9Yb8@8k7df*F$qVz+UB-rrG_<;I -h%+M>ltM;uT0&q|gm}%nU9{aIQ2XsrRs3Xpav7qR<<8Zoox -q#(9idA=0hXd`ye>o}~6n8qYBWFsHezXZ -nyZSVoWW{VPX^byPX5q7yymiWB_&_V3$dGQqY|tyU{l!nwxS@atTrjb(vdfyu~ewUSslt5wIb$`AJXT -uuyr4?jiMf5ECaR6C(>7YgsZCVNiBhMg@2UmMo-YZT;4EIxFgo<`_#{TO$#q4s0+n`DPUUh^xk=3xF1 -n&)g;``|6jck=#1pTB)9edkS~7HinUcoK2Les!3dl@c!Jc4cm4Z*nhkWpQ<7b98eraA9L>VP|D?FJow7a%5$6FJ*IMaB^>AWpX -ZXd6iaOZ|XP@edkw<)EAIw7J9YPZM%J-6k4_r5TUy2u2#q-6R>V#N49A%`|Ed{F9?KgnZ{|sla7mW*8Y!If3ueo3m^ksm+HN^5?9!1QNY7IWwoJIj132rzcAMgz$n -VNiG??h1x -cc2$VFt=C@bJVEbFvv3LOJNjWZH-h--MS$Qz=WxvFyql$@fa?FA~awm%ywoYMV>!N6Y-T|YG+jn6(VR -Ens*B97ItzAJ`pI&YHF&pmc21234#J4IkYX7^9|0;SmKY%4W7*McgOnG51 -ku1g0UeHoe&^HMD_vc_1*9M4zgA3&`o*-u;G3lLfAvZgE@A*aoSf$&8Jks+m~F76rY7h^nCqPp4LbeW -n3$kB8uLdUUjan`tGsi&skw_mYsb;BYVVh>FQ3{tCK2+@hQX=dy`-NUK -A4d}2R07WCuf*S+bRV_0;bM!8sxgB*%r~?Vs45F}4pO9bRMB4QnN~DH(L4(B1pa^qecHMGtl`PamDH) -FR%EF3#ds{6LbFxk4BHWn6d?~#_$MSJRy{#gw*0P+bA4UfG17Pj)#>c-4-td();6&F?cv>NRc$ -$~y~;#?C|sNds3 -A%&fxpZ$U3JZ3Yc>5=t1gBL!`*(5J2+5Es&nxicmI%4d26x9-F|ro-CE$jw@U0EchISi;DC9beHpqd& -7?jW`y5V!ZTC#5Q*|)}lQZ=?A6fb#ADNH5P6xe|I~z{>k_ElYzhuppodH4r!jkW`)aO#J-42I< -{{T=+0|XQR000O8HkM{d)=2%#$pruapB?}JE&u=kaA|NaUv_0~WN&gWa%FLKWpi|MFK}UFYhh<)b1!3 -PVRB?;bT4IdV{>gTaCz;RZExa65Xay1DMptUmq?gmo5zd#f^d+KBLo4_DfgyY>~*lt*mP}jqE)|pXZ! -$|3sA;6TL0K>vY^LQ5K27ZVi)gn;w2Mi3uKqcdrk -|L5eOjmKlms1Hvu@)$sFC)8IM -y55s==b!;*7(o=ka23r0KV3g=jpt64P%GLcM@b1`;6&dru}fG5OR$6wO4@FigvX7DK=}4gfkJ*B*sim -pp`~x*awzLas(G00;q~AF`ZB~!;ad7q;cAA|x&eldclQ#ci*} -m(S*j36z$oe2Y-z+I1PabYMET=sE(pB9q*vhKX{d}Wv0JcNsSU0lhn?Zx;~y9h7y1ovBbZ89hSFJjMT -~&H@VPg+xqp~IYw!r4Tcc5HFnPR0U)*W5;`t?!-1)+DF)*%52|rq4;uzqrGiu);(YosOdy_}XzT2A&I -^!{P??=#rVQVz$wIBMe5ey$j!~1ck1TdB|+hW>GZj;wr>|n@wfIDf+NVr~@Ed3FWH^c-z8)o8J;xSv& -#XAZFmdIjt(iwVz{}itmk)QW2^;Itl-)het3*yz4p`y6Xj -jD#BF|M&x?eLbhz^dRf!WvJUVt#*uw5=nOs{*+k9?1xE#QC|Ov<5gt@XCr9NY$B|H&kL)N#smNXw-pN -AY=F2aZ7sAhQUeJr>+#3LsEMxtZ>xD9BYmm)hZ8~v!#_4$*aXQZ##2Ng225|<@nZ%hqXA)=fT!pv_&s -B)4@LZL+D$iAktMXipxEjyZh^z5jow$0&Rq(f3G%_1;bqCKc|0Y|5xJI3~5!c|kCUH%kYZBMwIYFG@I -YFG@xhZi|o|_Uk#cX#-2mWTG=-&>vt!`S`(2S{3LF-awRB`#fI;r~ -16Q`D|p2?fObHOx<|*N1@Og-y3DaXb;xC-}s?=zxfaK{>uNL_w -U%!-ftB1@9o~t+S1<7*|Ofx+S1<7*|Ofx+S1<7*|Ofx+S1<7*|Ofx+S1<7*|Ofx+S1<7*|Ofx+S1<7* -|Ofx+BUs^QfNjgFVceHFjQQM -t3_vx^fy}lcVwd^P@jJ51!5nP{trL19J+rFoc*20_|-VR@r!$d-kl0&_Gf*!_IaFQP8*LbWR-VV3)@Y -Co&f3`EXY@27h%8Qua=h?2Bv%Njrz3JhPr-xXSr|aRr|MlKMJxcZP!2E|q?x<(G!(Y$Edbqu(M|?}S_ -I*tQlD2u22JC)ycSr)_gFi_E^2<6_0zQpM0`S)J#8=t)0DkbRDEtXfO9KQH0000805+CpNvwRlMGFN0 -0A3CN05kvq0B~t=FJE?LZe(wAFLGsZb!BsOb1!gVV{2h&WpgiMXkl_>WppoPbz^F9aB^>AWpXZXd8Jl -uZ`w!@{?4x$H6Lnp)Ca99T@&RbLP$dk1o5IO>P2X=$FNs>7wxW-xWE2p{lbfF+Vr}gU|yb?d1iLjXJ_ ->XuKFJbV;By4{qd|1SHrvBXE%n{TPk**cM3?MX}%>Gh7Nyf7Mp1^bY~UM5;{gDP#tHSS2Z9H55}8YnxE0jRB|EgwBT#e-T8jx -eMJegsHXFFhz~(5&f)^o)kv$Rcxekbn~1f03)Wp!Gwov#UpqFl%WA5VUn1Q6nXtbO~_Y@sl>7rC`8aQ -Fi(`&1tc;Cj|oudbDhBx5eAs>|CEaSBkA)Xv7%V`RF2Kspo(9jkZ-(Qd|F-=fZs+n2MDAN=!1hX4WJNC -Ilf6@Cixjs;IX=5dt7wP@G^@MpovbL)NpH0b->d-nmGMtS@wE4nDqv3^2>r9vx;?&nii--0OO<4fTC8 -zbZTsj)VAeY-y@8}+z@2u@VcNyihjNr0{A?GmJife_N@6@Er)4Vw~!S{R}IG1QF`zcz_hJ-spGQrE(I -I#UmHq|(5G|PM>mx4uD4yn+@O28{B;?1YYtG#1@=%>alU{~>>3mek@x`YsglWKMfyH~hflXO?t=(Jm@ ->4B#`^d8>7`+S~)+6~KS$dD==x%Ym+Lz@`qGMgDvR3)6U54j-b4K^C_RwaJYhaS*Ewj$swCZ-n5I -30512J*~Pw6QXMD0^C^s$KCa?*)hKOGqFrt15mauv>ry7&N;8_Z&Xw~AyoP+CZ0ONJn;g&slrs04)HJ -vbe>vDc;&s(H=dYmifQ+#;Bdpl3=xgTNkrcxpayl@2gjKpu$4wuCf%*N&BvYmu*|s8^N3wGrVp#|Ic+RzYXPrOOMMwozl}n8uM&=!QuTXRbDD -*=bC@p9QxLd#z}xY^@u$?MH;O=YP0blP)h>@6aWAK2mm&gW=SgKE|ITO005e^001%o003}la4%nWWo~ -3|axZdaadl;LbaO9oVPk7yXJvCQV`yP=WMy=z_({)Z}#NIn%#oH5c@_z4o|MCAGfAgdN^ -7&65KmPoOKl;hffAsj9AOG}+|LyU=`0V%Ieedy$-@bqR^wWob`tZ${Uq62M@z?JkAHIG3{m1|N!`EMb -dH?*o-~Hj64_|%x-N#RlufG29{+sU}U)`Vi{<}XX{rsB`-+j1K{_XpJe*g8y-@pI#`1qU0cmF}2_ve4 -|{=09#`ttYh|6kdD`1sAY_m7``|MAoOaA*AZ*~d?>-+%S#{g>ap|MlZnf1Diu{iVMAB(LB9@xIh=zkB -?}5C8IUJ)h^!;y?WO_a8s_`s>FRKKt$Ci}&BY|MbuAfBkxrAAbDiyH6i}^@sa0|Ki)f{NL{1JRX1l<) -{Dg_}PcA|LN0*-#z~7ao^#0kMBM{e)h-je*5v8KRy3lR9}Di)u%5%{ZXm?=F|K4k8eNz=DUBnZ|tuhf -Bg7|$5&r|^Z4}s*B`!pWq%jMcaJZ>`SpML_~}mnyN|#A@S8vS)gQk3_5C>R`n&g^e)sL4%kaPa;eOw>|M~r=Z|~nc*7vVJU+qshe)hQQ`Y*ox&i&uD`Fjcf^1kUmKEA$d0>(iIteD}xu&hHHV{zqT@@W0+Ee(*P+|M>G?{Da&7yU%~|lOO&3=a0Yp= -@*Y5Jbw0rFMjd)4}baNAAIrn*)PBN*-wA|qwhZ+KY#!J&tFRs7((+_`n|GxM4 -m+Zc{Z})z{f1mc7`$O*MPPlfy{nh>7e>wTBe*U@j{PFj9FZlI;bm7OlcR%{&5C7X2|M-i4_}S+_{K1d -^@ozu>`7gfs{BM5wi_d@hlYji;2lxNGGpp~v{p;s`@HapH(LetD^Z(;V_wVluF6H&Z-+uW?)Z7vCfBp -2+bTVK5>g)H9&mON--v7nNe|z^oKmOwJf4+M>uKn5le{c88di(6!&-&~U=lbmG>v_N2zkBR;K6}*L^R -vggmfu~UJ^HQh&%NF6uIGGiefD^q{_NeIL;G2-Z*hm`uFtOPx_`Icwm0JbueuZ0N&j_coo~k*>7~3(2 -FJ~u$#5><<-X1P<-YtC_e;5vEy-Kn8SawJzHemmk8@}JSd(JA(qs^yy?Z?4zLa~vf6n_eaew;x=DvD< -=N|U`yYBlTR!pwP)7~Yp6;~gH^tz+xo#A!eKR@rMf1La6J8L(0?hiiNGb)D5H9O=V3O5vF_=vQ}E!33 -CU6AkY(sj`YFOyFGRj%woC&RhCba&OO>oqxT4H=m -P3%AwqKx$C;qp8Ip}9(!)Ce7fep?b9P4p*!_>Kl~jc6AmKT%bMptLaC?v?`%~6{b2Wfou?En*CVj6S -B7`#?$<9vBzWXIi0=%aE)r_~{&Y^E36a`z753{*fvdc}-rRamhWCv;h2Xj7Zlrs@`Lquz&D0Fq-Sl&L -V}4W?Pbr>m;VJ8<3}af@b3XIb^tR`Iy05vp`_tE5d-s1SG>dC%NA4lDdwzyp_~>o0mHd@7L`=!;9N -|y;EM=%TZfT|MishJl?yjwT>VY(&bN4$B+1$@PebIbm-cZav%~mbHE%%@cTG!<2=Crk=^Eul^wvTN2Yc -tdLO@Um}zSVs0zFbYE92NXjy7?*~7uCnaM1n3ME#U{ci)2`Sb{}^I9ldI%pJY#zANPs1_-fvcQ(tFJe~&krhSAC -BRp8#b#yd?$(aHaX<2esj9 -!cylGcQpLxnXOb#`_OCd6Ect-u<(H|=H>*)W^o%OrBr_%8}Cpe|^p!qfrpnwGIy5@}#Ub5+V!!0dcT2 -G~D{cFO;Xu0N6^UlP1&yTJ=y`=_y#K7HC3K6+Z8J@YE{O7@r9{XBTnI7dR@o7V%=qSF27O?d(I-f_yy -dm(ET&3nH&c){ahR)&f&?UU`ni0-D2%u$zd%jI?=?F=SpwrEBWe!(Vf`$DX!4WvG3hxw-q1pO;#GY9ygg)AinUW=Iz7Prb%d-@1Bw7P) -H16VnFzyO7L={CJ(2E@*l)Z+fcwr@Ss5;9JcwXN*^^Z*MaJnF|X%-JHU`L)HDiuH-iFDszYWCe7t(2& -m(2v8nIA|4W0O8(#Ag?#t&~)70QDS1rSs``oWFMQfcdPMf(j7v0`HF)}JiH?zqw?mwrf*2I|p9nHH+MVo(`a9+mv%XoVkeD2P4f-&@58pLzn -og5_AAf7ROo%%a^ovGC`RJC-Mo-wcauwvF8p1REE%nzRi{-^% -&sZ)IB1gxr_>M=izd8oNa&dgy_D0)%!H0-?T;oeFuuQhM0E>HIMHUo<`Grg>47J7*1Oc$RMPhwN9mp< -~TAa#OwT2qlVUC@Mx4)&=nKWl!}zI8O)1Z}Me=b5=HYfti2*3&cARMgy|`ShLZbToP?pNZ#GtMO^ir` -^{?T1=-dio6^8I!|)HSQ(hk-7QCFB!{ILq_BL2R!=RvFAq9NR!pUsf6nxMW}Tk)n%JBUwuHl*G4j$&U -6QP6?~}ybAZvOJm#bM8%?;1ZsawOzv!_3IhnGpjnibxjS-Ny@Gf`C)&-t_>oyVJYq?fIUC*9+WDbFdx -nPWfA9G^OiHPP(>;`Lv0cug+vD_x%Z%;Z}O&TATgDic)Kw5@sMGwpF6VET@UKE*ChuGbyv>7Qnb&!^5 -brqCqQf@i5T^N;6qn%;9(3R5W)Uxp@CareynIsLhMn}=F+rB`gauyNp_GuV2@rKLQKa1UMR{mA-!^O3 -ctm-V|d{itqeqQjNe^zrw|v0fdeA>hp9ds&U9ZeyFtSzugm0_Zw3^i4(0-Av$_(oa?2*{o?%+V{iYxi -pgdbNu99$?Q9;6`e4u$Z5S*)tI~ub)2hIh~d(?a9W -4r^;fU^Od1=ORnJoytDLpmxj8Y^E<6)Vn-0yjE>XDdj^e3JDs4g8J@YUSoM^TsV-4_$CF~86{@}>G|_ -9!WY!kr1j4e~GtJcB9X@6y^)|B?5qiy3!7zF~XSF-M`1CfDPFYW(P4&n0{_|hn)l%+P#hEHecl>bq=V -;S4zcVY`^Iv*YFV|~5FaJDkzT?&9cw^k7re~Dl+{k1 -o1}=-{*VwkEV<{QxU}qT<69oE`>7uXK@>3^2aX#@o>zSi&pKjsl7M@dPCedL`iTgg+HS1+P@ulQ+C6| -%?enXMwJg91&b&!a>^cB}w%Osmwd9L9;Ek(5g$@|63bgt*VW`ZF4?l<&6&-t(RGMAIJzV5nP;<~0SYZ -~{9ye^-Y;utw%4@${U+-fXO48!Pph6>Gb@~T^>q`LJ)@AOC#|K}U4}LHFx|#{dY)&lH!Z~& -yytTB;%>HgXWI07Wc}f+k4@Saz+I**_lpd*{D;nN_biS)qe1slJOlYGf+4S*HX;?xT-k%w9Rw9F -D+o&9-c(M=0P7Z!7) -?k+LNJ~o$2F#n|BnIKJ1!;)O}s^c&{6t-^oz_bj{Vvg{RB8AMwu6kfV!R#({NBZ(|x;uitsbxuupv%b -}+X=f4!v7}*TBW?%O&4ax4XCzfXNs-aGAU{ScPnUU}LQPyXi%Y95_nS$+!%`mWZJ;%v)Uo#e!UeJpB{?QohHnrAAOl8g#OfRJb?vYTJHj6atpq}}N8S9>f -kKz7q22&|&d8Xh!SDpoc;ZX;EO))$th*iJ@Ud_$jPq)m-%=6yeuUd+l0lyD()MW=?t=Ei9A2Xx2nY;F -}*zjUlYnFpGizp+sxws5nKo_*eO$Tp`Ga(yTw4m4`UJD5=P_wN3uXw#HYrT>M<(;99$NFqpPA&7|H8V -7${hAR+Cx0g<&U=28SV-v7mVRMrlS`w!Gu#r->}U#Ol9-CTie>grgxeO3OTRFypV`P^un%;xOd!t8R$X3~U?^W}Is&~E9K457A6D}^p9clIo8$CKlWg{+X4L3unG@4$;hNcndG{1w2-UgfETYC+U -v16WXx8Hv1=`3xNcG%^NPC%sd23AdnB~la>OZzoY3Vg|<;!eht+`<>)+WULqUkMz!^4!=`1g3l#7sbD -L1_q>-g4!vpJv@SK+BD_O+`(2Jawdpd(5;?Ct|#K=%*jAP^zg -E^QIoaorj+=)#>gb8-w67i#ih>Gl7b^NXyWJPNPu%{aVorjBB<(VtRw&UP*c^M-qb+q`VhtTF;9T=E3 -AO?c1!=B=OzDj9?kB)(Z!9rfU{aau`F2sp^623EEdSuBmfy|7$vjjA_fJQQ%>%xtlX@tEg#u!^zXa%% -?0Km*v5lDtoM1UeEGS1{xhp|zZ?)O^b{3a@o#FS@o;nhcRWg=2GF ->1-ui>tH<*U6~@w83u`3H7T&!8_jvov<=v(bf1sp+jTzpHK8%jf)_9C_hzrZ2wO_jvD&fBReTO<&UCG -ik?iU}_!5SIw&nOVR?4Ci&9OaR-kE`an*4xCuH1gT;neOtjC&T%)vYBBzCV!8jf+wCzAFyBB;5xi?C6 -n(h;*IVQV?Hx?JPlQkgaf9Llf*SUr+a=JVjRty%|l3Hw*8oHx{P}FFXtWp*aO=?b+~H)XAGIa2X -v)%QEp2|yG{Cm2qZ1-te>gjlIPQMYkY-LaFsEmkk!mJ!`?aejXlKf831_Z(-6NArn>mV3q%C~*JW5S7p0+R$WH6XqmE4SxduC*_v>bt)E`f>1tU<= -fUzK7&n$rjIy1aL;k;M6NUfioaoOsTE&GObo3@0}H)}EE-%wUbT57>DJrzg`h?rEMTIJ;+XGZmR|ehz -cqw}h9jw`r5Q$Jz1Tv$>$9tXE5!(o`6;1O5uZX%J^}a7z>CbL-0RIb|Vsa(a?8ZJB#){o$VN?Rk``89 -k6lSbNIiwGJ9*o)qp&|Kt?Ma@yd|RV_~kVi$hdQCzm3)-1WW#KVDzW%GLtecJ!Ki)Ly*JmVzuqXJ3C5 -|&NjHQP*Umh0CHC#IP-qpCNqdF1jUPG)v`P4y4V%s!YUC``K$(KRPtj0=~}=pshf;JhAb*$%#Po@8!G -<;{+(iRnG`$p1NKMaBzN=}cUckVD;pPn<<(w}m*Hsz@>+A6W7{ZPwg=bCI%j`K%uV- -b-hIv7e>SF>{zA!!@X)=haHV`yVVbHR+Tr;<^I9=A3k -HLsP?O@EZ!ZK{8U^Q1CKQcR=_F((tF|!bBtZQh`WMf&{Tsh@j^P?&8jGF^4F^MN=-G{-srdE4udA8!^ -wdc^7yP5xTu;?1{pIvwZIy -VOQ!~3A9iNRA(>?$yU^q{;$<{jubMa5DHV#;DZJe6Q+He{N!Ow -s{9hlMQ#oI+bO^C@HLoGh3*N{;bfvuQIYg606#M7%qEJm-#Qqx<;uB$*t_OM96&WWgy9dSHJJLG_%Ae4EwiY*x< -tM$kScuiFo9dPdJv0oXA&^_A+bBb$*T -2qmnyEaDJhgl?Xjn3HtxaJs@1^yb==10qjaA!C(p_v-aR>#Z_4fJ~gZ|?9+-ReI^eqzp@Og5rn_D0`k -Z#w6AjNxUDF+OH#<3@~;xcUL9A7`*&j)|<9OisI<ud%ycXUVr5*P0`EF$Y+4maTwQG^{zRHDxi1Hz$TCnO>az-(~omx91skrgO -^1tjY<%HT|L4%4Iuk%|@rQdCQvQE~A+-%?8$-RhFUCYmXU5%lyzHVcE!AHZ#^lyp_~L5U-imIQP69g} -Ixp=T=cBuglilouN((NUk{s0mRlcy+54~oHI*;Y8+dfXfA7x;^u7E#r)_>bISB>uBQ)kym{61nX{mI& -XDPYit0IcxF*IX#r!yHVRxil9dy0#Pz$zWxoU1?&2q%ls-}^<;VHD)6__5|qTtM#32kc*2b9f8JCyhA -YYyo?C&PK$1L*c@K*z99v)lRFIvbsnI45h*q*JWde~G~F{b?6H>)rQ$^v~}<{qeis`eTA$z5n{-zxYF -wkNz;?@!^~A-hX=k?RWDh8oz%3-TSZR2>tK>@agv-zkPo{r%uGoa5=BAYRXlI(^5TsAn*9dHq}J@g*970wOx+2y+}}&8%inWQLTU#G=8mkY(3 -onT4-e2+qm)<#70N_I5cmwE7jLlH|NdCnk*2%Q3k%yUq55)@%S^v^5h4!}6V>IrX&aIqW`1Ei9kbOlT -b8T_$^L+NpEx%bvs%C4CfQ{iQQW^usfr1x?x!nWY~aimIkdH$s#$aR -!|vcRe_GC$-WduE$AXsQH*5A6sq>mK%1PtpJn?c4YuPDZ_MjHxU6^1w4!9WNn!kUz^7TRK1cqaOYmWc -sBo-ZW@;4_NA)-TF)1f*dx@?)RnL|7Id*7kJnB5$cs-@ptPO4njxWGKO5OIucIgqfp_?iXqv}y2Vi-Q}e2a-?=SCApj)TytK?Okp`RyBwNb&dV;xWtW4S%lW)z>AhyAK4@l`<*e#**m5}{vmAn14u7m!gj;Xl8Hx$3nB^?ca<*c@#zixhY1lF=Sca_SMCWpxVA%yM+87vv#r0v|-cJ6CC_O%`R+Kzo~C%(25U)vd_ZI^o6`Q2}m&6Z+Iv8C8j+#zvixFy+=gd`zJN -D`8SBq2#i5-t)_gcLb#-n%K86DQm9>B?b`^yzt?*JtGL`kfq74&|C9ak*x>W=T;}+;MSdI3vjCY=>&LLp9r>jypqf;>gT)WMfI=B+vVM0ciRWq4ufnn? -(G=Jwp+CA;BH4B?hM6*(;wS9XwT`7J&z|QY+7&o)!Y8!wn4JV_qMd(7HZo%dULWp+l`xW?H3!&eTfMt -Hm*J8B`_@EwqqI_=Wd#@Ex@+J6B~eR#{%}8me`h{+d+xVRyIr9_P)3C5Zif(Z8^Jb^=&(1+gXPVRX2C -vHmJAF=k1uoouNp;W7{!@4WPGOneCXvcFbYhJlc*qY{wk7!wuWk;+8~-HC0JU<2*Vs5gJ!zo(KcrW1PxpJ^@ -=-F8m5J=1M#bldFQ_I0++&TYqK+w9ynJMRqDzm3jqqjTHn+%`J5jm~YObKBzFHZiwN%#CF?ZQmefgV$ -|aa@&!-GgSY4YV)dXe{nPR>vdl>FVcwtTZG&0;3nAH#^AOwxNQk;8-aI*g23L-wyCo1^=*57+XmmpJ= -+G~wyn2q>uo!F+m7D0qj$Z^LJZ7i-nN;ydH1%Nx9#I?`*_ -HgVjxdA6;rZ7XZr#M<+RHq%b+WZfBx4!Z!?M1uXSZ9i+r+>H>iC3WG+ZC`5R&~0C8+n3t*r8Y*}i0sZ -#oY<4vHlnuer)~Rb+iKc&nRdJ+Ns`x#tOW}i+-&Otp_hL7&T!TwcJFZLwr{lU8|~S#prhNq(T*vGxlI -8SlcMLtlFjFOG89I0`YK=lC9zPlbF=N)Y8o5!)ujo}DdHyk}<%7jJtI+aAQW2eIuzY}*fahHKuISbQP)4W&0t --pqKz-wkXxo891b^TN#nH}czAN$w`O8{KYdyMgTnwwu*%R=ZK{r7SkD-Msdi0;%b32<@h^8@z7ty -20xPuba7Uw7SviMys2NZYH{!=w_muiSC-I2o&8=bVJb%MK=`PP;^7lU9*Zg^M-Wd0*r1jy20oMqZ^Fg -843(Cy2=7dn~ZKUy20o -MqZ^EFFuH4|B4l)v(M?7-8Qnben#n5^-B5Hx(cSZ1L -#xk?uJkHjxZUHz?hpbc52(NjE2bw?XL!r5luPM!Fg4W~7^uZbrHp>1L!Gk8V7=>FB1Tn~v_DQ4f!9Ji -77d#-kgLZali@3r67%}6&Q-H3D}(mO-Zfk*diYyZrDNhdlO>1L#xk#0u18R=%Eo00Apk -*YjIdS@s^@aV>)8;@>0y7B17qZ^NIJi77dp6~gX=@ld0h;$>;jY#hd#Ys87QdmTp?QY3Zh=o0jhRE9>d-km;U3P1f}p^?Xp+z|##+H$2_&bi>m<-(WF)BthNtH5SubV$_XMH%8 -qU^}a(9#8Ed#-5hme)QwR$M%`zQO~t$`od7!X$a6FEh5%xzcZMPerf!(JVd{pd8>4QFdQG>@Qa4N87< -FURO;9&M-2`=?Q4xf?5$c{VPnhl=qi&44G3uW0NthvkrEY?{3F;=Oo1kujy3eQxLfr^;Bh*b$H$mO=r -35pGVAPFK_nFF@st`qI%5_R}9zt((&5(2E@{@RGDmG(g1j4GYTc-Hqt*>suUX} -j)@z1Q+`8v)-8XODymj-|&09Bb-Mn@4*3Db@cs3>x1}W&eq3edO8@g`jx}ocat{b{;=(?fnp1*>gH5+)nGZaDOb( -7akUN?E&Ag@z$#PAg@7QgS-ZL4e}b~HOOnM8B^vqV_qZ^4fr+iYtYv_L*am51HT4-4X -hJfCCvC^%^iw{X(aeH@N3}Lz^{Q{gTCGw3J3fe_%-lrtk*di)4<6_V1U>Fu>oQO#0H2B5E~3O#$bcN2 -7?U-8w@rWY%thhurWuqYU)FDfY<=B0b&Eh28az1duJ#*Kx}~60I>mL1H=Z14GiJUfFQ8}Vgtkmhz$@MAT~g3fY<=B0b&Eh28az18z447Y=GE1Ln8@@4G{ -N@E3e97+m0_KtOkfVgl5J^-WkVgo%BamWK&*n7oE5SeSi#_Rdf^Fxg829gaV8%Q>gY#`Y{vVmj+$p(@QBpXQf&QK)a*r -2gNV}r&9jSU(bG&X2#(Ac1{L1Tl)28|6G8#MM^;Ml;iL1Tl)28|6G8#Fd(Y|z-Cu|Z=4#K!!^0R;&f8 -#MOLaDEmJAT~g3;Mc&ffnTo~ff#Hs*kG{1U}L`Vs3xtr7=sN48w@t^Yv9+wuYq3!zXo{?@*3pzn!kIT -w#;CI!3KkkG1y?R!C-^I27?U-8w@rWY%tisuh&EagAE273^rnZr^CRlcZQ;aw+3pBnCO^?7ahDccx#~ -6K&^pVgR}-|4agdhHRex`g0co>4aypnH7ILP)}X9GS%b0$WevU>d^Py$Wr`G#HRe=zP}Tsf0aydD24D -@q8h|y}YEadnszFtQss>dJsv1-^sOr1Gs)1Dls|HpLq8dasfNB8M0IGpeWBx#Zkp)W)j2Z;>LS}b{^N -u*-sKHSKqXtF|j2ajA)7ET*rQR6|5ttea24WJr8HGpaW) -c~piRPPK$2T={88bmdSY7o@`ssU6Z=N?2KPz|6OKsA7B0M(eU2MwYcL^X(N5Y?;Z;ROa(4XPSBw`u-a -Q!0W3o(9YeoEQ-C&QNqvtC+LNHN_`HP)yK{KpcT30x|?z2x1VNAS};gzOIZ}-WduJ%i^%m4J+HQvJH# -Eu(%6rxUkrYnc>gmdnVd5IiBgRsbS1%+L%ei%m`)_H@ID7G^Wp={&u?6=~Sj8n07Q_BTize!nlXi_22l;78btM);iO(K(uoeL8dNo?YEadnszFtQss>fPGZYwDHLz-6)xfHORRgOARSl{dRP}llj -krLofmQ>pM$H#GzY0xY0M-DkQS%Ki^H8FLvc`Nb;x(10{~)bFT7$F(X^r^AWwFYVp)EcNYP-~#p0Ibndx8ek?23ifY8fZ1nOhwpgu+y21gB!8W=S&YGBmBsDV)fqXtF|f*J%h2x<`2AgFhSq647 -@LJfi%1T_e1z|(-I0Z#*-1~(0E8r(F_Je{88%sK^}1~(0E8uLZ5(`Uldn6G=D-jkpPJPmjn@HF6Qz|( --IG2hu5^fc&caMR$X!A*mf1}F_i8jLjXXyDPnqd`Z5js_i#=hzn>jrpE7J$NwEV5IS!V*?5Vlm;jbP# -T~#Kxu%|0Hwi6<2h3iP#TOh7-=xlV5GrFgOSD<=?oQ&H1KHP(V(M2M}v+A9lbLY6L>W6XgudOC$X3yq -d`W4j0PDECK^mMm}oH3V4}f9?+nER6b&dEP&A-uK+%Ar0Yw9f#&b$QNZ`=Gp@BmKhXxM4GZYg%G@=U;O$NlY-BU^Kz#eTRE -~DV^vbqe(^+iY63ID4I|-p=dJEWTJ^f6Ne@aO&oe>_?n|iobz}-m^15?*F5dq$m0UnlfQ`Jrn#oa2B*nQlba?t&H0<=n#+qAc$)Au;c0TyTN;C}Oy2a?|9d$xV}+CO5sT*%F>6JWY6-@HF9R!qbGO2~Xc8JxzL=@HF9R!qbGO2~QKACOl1en( -#E?Y0lRsu)!p#Ie*@t2sIIEBGe?PNl=rZCP7Vtnglgp-?i-iF=`UjB&bPHb52MmLQRC42sH_6?osucA -Egr~7&S3!V${T_iBS`yCPqz+dS@swIBIg#B9=R8MwT;J&WJW0>U3B7r#nM2G3{@f --ZZmmToZGz0umGOG~sE&(}br9PZORdJWY6dXDA%#X~NTlrwLCJo+dm^c$)Au;c3Ftgr^BluZdxTngsP --BGg2viBOZECP7VtnglfoY7*2Ws7X+B{#g%;{=}$>Q4^!y8IGgY{8D0pfl(8qCPqz+niw@PYGTwRs7X -+hpe8|0f_i5tCg^Fx(}br9PZORdJk9yaY4DH)H3@1G)Fi0MO_Q4@H@!0y4%{@kX>!x#rpZl{n);N|TW$BTYt{j5HZ(=G>Rg0gomgO+1=(H0fy4(ah-_$Y_$$B%?`2lZ+-AO)`2-M}tQbk0 -u^XFq&XA!Dxcftmz!cXp+$+qe(`Sj3yaPGMZ#G$!L<%B%=vN6O3lfspyFylLr|mU(GycV{RjX5IDT -(fLAFl|D<4SqjV?d#1lL9i16jy*?tXxR^oQaQp6}pg9B5beGdJPFFSU_D}ChH|+KLFVkzmLQ9jDCM`` -^df%ZCp`}Snla^+^I*j?9D^D?q6J(mSG-+wl(xjzHOOun{8Hy87nxHfpX)@Aeq=`opk0u_?nn`F)iu; -9;CL&Elnmy|jMw&fK*`Bw{OM5w9q?^TxC!?haN)wc3PsE)0F}fDJ -o=|^KbJhv}?)?o+doKGZaDeG~sE&(}br9PxIxbJ^$sx0*IR?H%)Gu+%&mqa?`t(RRuRqZkpUQxoL9KM -5a0au-lC1+%&mqa??bniA)oj=8$P3(?q6;OcR+VGEHQf$TX2@BGY`4Y0rO2Hl;p=J7pnkE+LXV!x#rrC2%v2$il+Vfx9GCj@ -tN8Bt{uUV{O)WoQXQInu1K}~|1@HF9R!qbGO2~YD(Z*wLGu>((&nU -apG|%)l;54~uBGW{siA)ojCNfQAn#eSfX(H1^rin}wnI_;*n -Tt%WuQ%qx62D$tSeRhcoPRefIcjp$+132GA5B&bPHlb|L%O?aB{H0PgDnmws -RsEJS$p(a61f|>+132GA5B&bPHlb|L+O@f*PH3@1G)H_38z^n7VrthAA1!+uxs3uTNpqfB6focNP1gb -e-tv9_rRZXgTXQ)%bs)jNvxVg^|Efs`O?l=yRg+{tN -DDrF~5^oh&<kQ7xcaK(&Bs0o4Mk1yqZr7Dg?MT64;@IBId!;;2PXi -=Y-kErMDEwFqhv)FP-wP>Y}zK`nw>1oh5vNizQyW65DjH&XVLWJ|o~za-ugZ;7`LZ)vy7ZOQiBVMr2^ -gd`zJND`8SBq2#i67DdiNGVc^lp>|bxxrryro~N -*n-(`MWLn6ykZB>)LZ*dGYyQzebB*Gr#ZB)Fk90@6Bi&QFDZcsOQ@W=sJtcX%(o>436as;p7B?+!`mV -rfans_a#Z8Nw7B?+!THLgdX(7`>re$hgMxina6*8?M)1swCON*8kEiGDFw6thx(bA%&MN5m87AURx=j -hC=ik22Ey)zU9w6w07c0A`tiA4f3Eo55rh3zI|#Z8Nw7B?+!THLg_>7Ai);HHI4i4XC|D*a(G(qg2=NDGe^9xXgtc(lN1G0|e8#YEo~6fGW -FJhV_~q0mC1g+dF3*6T2-YvRztp@l;WhZYX4HJt+wEgo7tw0LOo(Bh%RLyLzN4=o&8IJ9tR;n2dNg+m -L6-Wdu49$GxKcxds^;-Q5?3xyU5EfQM%v*urMGKVh+S`f4#=$)ZR0H6gx3xE~?EdW~l^Lmx0XqX-jf) -)fV2wD)dAZX1O;Xl6TUs1jD6%{bFU}(Y6f}sUN3x*a8Ef!iVv{-1d(88dFK?{Qx2E8*B5FE5PXmQZup -v6H8gBAuY3|edIW}a6(V9+9S6cHT+4KpBVodS@sc5NILLLZF2}i+~mZEdp8uv8R5NHw5f}aIH3w{>-EcjXQv*2fO&*Gl -NJ?{)f0{ATOS=_U@XK~Nsp2a-Ebdv{v*v8*HFDGQv?2k476L5-S_HHRXc5pNphZB7fEEEQ0$K#L2A_0LG0xbku2 -(;j5!Ow!91wRXZ7W^#uS@5&qXTi^!v!O*li+&dUEc#jSv*2gJ&w`%?KMQ^q{4DNS+_Si6anItOcZMPX -d=~gD?pfTkxMy+C;-1Ali+dLLEbdv{v$$t*&*Gl%3VasxEaX|pvyf*Y&qAJsJPUai@+{<8$g_}VADF*EU*Z_l)P=AAQ_oB5HsLCu6<#@FfNr`w)hcE0DnrjblR& -V!Q8BAW#@3u+eBET~yLvv_9V%)*(4GYey&#dvx8Qgeg@yw!_MKKFv7Q`%wSrD@zWOJp{@ulvyaVP-dab>vh+ngJ%}WERuQ6fQmBzD~nYYt1MPon6e0E5y~QzMJNkS7Mv_NS@W-y7o99PS#Yx8WSuD>Jp`OAI9YJA;AFw -cf|CU&YyJ*?+B~6rSBSC@Wf96Elm#aXPS*U>?$bFC%6d+^*N>731VUMavYtZ^h_Vo65y~QzMJS6<7NI -OcS%k6(Wf96Eltn0uP!^%A`Rn&Wlm#aXP8OUjI9YJA;AFwcf|CU&3r-fCEI3(kvfyORKg?cqvfyMP$) -b@(Ba22BjVu~jG_q)9(Z~Xk1tN<>7Kg0o%)UTmfye@p1tJSX7KkhmS@SP{PX|jQi$)fWEE-ufvS4IAX -XXGRi$NBHECyK&vKVB|Ki)lS1`t^wvOr{k$O4h|oOeY7hrBZs4j{5XWP`{{Pv11MX=KyLrjbn}n?^Q` -Y#P}#vSDPy$ahU6n?^Q`?6=A5lH`))lH`(PNwQpINwK6@QY9%xRx-H% -Ix$R2Zm9`WiMM&W&XM@NFkqsgnL^gpUY!2BRvN`0PVMpUY!KNDvKeF}$VQNjAR9s689pU>O7fKCDalikrz -B5Fo|1?G2H6a<8Dt~KMv#pl8$rHn2H6a<8DulaW{}Mwn?W{%YzEm3vKeGE$VQNjAR9q8g1j?S{|vGjW -HZQSkj)^QK{kVI2HE(r@nh4+rjJb@8$336yfakw8EQj?EmKIW}{= -GgS4^v7uu_$A*p#9UD3}bZqF@%(0ncGsk9*%^aIK_WVP04ILXgHgs(0*wC?|V?)P=jtw1~IW}`_=Ge@ -!nPW4@#*KG|s-8DCZ*1P!ys>#>^Ty_ljT;*`Hg0U(*toF)S(~=D`NzBFjm;aIH#TqV*FntZ{CK=br}~ -*=Gsm7Ym?nu09veJ1c)T-w{ZcwX0391THgs(0*wC?|V>8EQj?EmKIW}`_=Ge^f&QK6Q$A*p#9UD3}bZ -qF@(6OOoGsk9*%^aIKHgjy|*z<*<4ILXgHgs(0*wC?gWAn!5jg1={H#Tl;+}OCWabx4go-Yh-)Y#VIj -T##@Hfn6t*r>4)V0;Bxo-Yh-yx4fL@nYk}#*2*?n=Uq8Y`WNVvFT#d#RiKF78@+~d|_yl#fFLv6&orxQ*5T#OtG0_GsR -|#%@ms{HdAb-*i5nK3qu<8DK=AVrr1oenPM}=W{S-e?+n#HRBWi&P_da}Gs -R|#%@ms{HdAb-*i5mlq1&>nEuY#F=+036n=!WuLQe~3ip><8DK=AVrr1oenPM}=W{S-en<+L^eAiI1p -<+YDhKkJ;n<+L0SgsOlkNL&S!N4G|k6HbiWQ*buQHVnf7+hz$`NA~r;9hLFr7#D<6s5gQ^lL~MxI5V0X*L&S! -N4G|k6Hbm_Ce$S?e4G|k6HbiWQ*buQHVnf7+hz$`NA~r;9h}aOZA!0+sJ400u5gQ^lL~MxI5V0X*L&S -!N4G|k6HbiWQ*buQHVnf7t%@3O&Ha~2B*!-~hVe`Z0hs_V0A2vU1e%So5`C;?J9zSe;*!-~hVe`Z0hs -_V0A2vU1e%So5`C;?J=7-G>n;-W0Ve`Z0hs_V0A2vU1e%So5`C;?J=7-G>n;$kmY<}4Mu*VOZ8a6d-Y -S`4UsbN#YriM)on;JGXY--rlu&H5F!-j@Ee_P(vu&H5F!={E!4VxM^HEe3w)Uc^xQ^Tf)O%0nGHZ<({ -8pNiCO%0nGHZ^Q&*wnD8VN=7VhD{Bd8a6d-YS`4Up}VFSVjgbfHA5cYf>XA{CEgiQ#W5H=xfLfC|`31Jh$CWH+L8xS@iY(Ut6u;=SIn-Df3Y -(m(CunA!k!X|`G2%8W#A#6a{fUp5!gSbX*JrLBipdmn8!1k;gXQeiiznK9L!X|`G2pbSKAZ$R`fUwzM -_YCxKu;F0C!G?nk2OADH9Beq)aIlGB6Tv2eO$3_=HWBQefu0674Qv|NAh1DTbHL_+%>kPOHV14D*c`A -qU~|AmfZa3DGr(qm%>bJLHUew}*a)x@U?adrfQ0i^orhiTUn*MdqK#%_#|26(=`q%WY>0i^orhiTUn*KHYYx>vpujyaYzdke2_-pXj;IF}7gTDrU4gQ+>HS_B;13mO>=GV-xn -O`%%W`525n)x;JYv$L?ubE#nzh-{T{QAs55B-|?HS=rc*UYb(Uo*dEe$D)v`8D%v=GV-xnO`%%J~Plm -zh-{T{F?bS^K0hU%&(bWGrwkj&HS4AHS=rc*UYca4D`^inO`%%W`525n)x;JYv$L?ubE#nzh-{T{F?b -S^XuWh#(j1)#0q_0U|KWCsPeNFnB^fl>g($}P~Nnew`CVfr%n)E -g4Ytq-GuSs7&XP_s2P5PSjHR)^8*QBpWUz5HjeNFnB^fl>g($}P~Nng(~#3p@B`kM4L>1)#0q_0U|lf -EW>P5PSjHR)^8*QBpWUq5G{Cw)!&n)Eg4Ytq-GuSs7+zJ`1a`5N*y;@-^gZ$ -k%+Y`CjwA=6lWen(sB=YrfZbukl{bUv3}1chKHJdk5_uw0F?nL3;=79kh4Q-a&f@?H#gr$lf7)hwR-M -UXomrT#{UpEJ>CmOOhqYl4QBal441*q*zjHDGc<7>K&?gsNSJ^hw2@wcc|W>dWY&As&}Z~fqDn(9jJG -p-hp~|h9OBvl9Hq(DM?C_lB6UlNxDc%ky4ZtB}GYLpg&aaP`yL-4%ItU?@+x%^$yiLRPRu|L-h{SJ5c -XHy#w_Q)Vni0k{n5nBuA1X$&utpawIvD92a>?@s#2z#Z!u>6wj%YrzB5Fo|4>Ef2iJ}dWY&As&}Z~p? -Zhv9jJGp-hp}t>K&-}?oho$^$yiLRPRu|L-h{TJ5=vby+id5)jLq{K)nO?4%9nP@6J%wQ@um=4%ItU? -@+x%^$yiLRPRu|L-h{SJ5cXHy#w_Q)VniO^;GXry+id5)jL%0P`yL-4%ItU?@+x1^$yfKQ13v!1NH6< -RXx=^RPRu|L-h{TJ5=vby+id5)jL%0K)nO?4%9nP??AmfLsd`p4%ItU?@+x%^$yiLRPRu|L-h{TJ5cX -Hy#w_Q)I0O9O&qFssNSJ^hw2@wcc|W>dWY&As&}Z~p?U}E9jJGp-hp}t>b*Na?*P36^bXKFK<@y(!}1 -QxJ1p;@yo2%%$~!3UpuB_f4$8YTRP`+Hu)M?a4$C_%@36eX@(#;8DDR-WgYpi_J1Fm;vI^2DBhuXhvFTIcPQSWcz1 -@X9^)O1cQD?;cn9MhjCU~Jp?HVl9g24--l2Gh;vI^2DBhi+s>gT-;~k85Fy6s<2jd-#cPQSWc!%O0ig -zg9p?HVl9g25nsOmA^!FUJb9gKG{-obbW;~k23DBhuXhvFTIcPQSWcxTT*kMR!1I~ea^yo2!$#yc4AV -7x=|4#hhZ?@+u$@eaj16z|O6MIVfJFy6s<2jd-#cQD?;cn9Mhigzg9p?HVl9g24--l2GB{z2@6@eam2 -81G=bgYgc=I~ea^yhHI0#XA)5P`pF&4#hhZ@5BuB81G=bgYgc=I~ea^yo2!$#yb@6P`pF&4#hhZ?@+u -$@lMP@kMR!1I~ea^yo2!$#yc4AV7x=|4#hhZ?@+u$@eaj16z{|g^ce48yo2!$#yc4AV7!Cz4#qna?@+ -u$@eaj16z@>HL-9_`K#%bb#yc4AV7!Cz4#qne?_j(`@eaj16z@>HL-7v9I~4Ea4D=Z9V7!Cz4#qne?_ -j)x@eam26z@>HL-7v9I~4CwyhHI$&Ondx4#qne?_j)x@eam281G=bL-7v9I~4CwyhHI0#XA)5A&@eam281G=bgYgc=I~ea^yhHI0#XA)5P`pF& -4#hhZ@8k^h81G=bgYgc=I~ea^yo2!$#yb@6P`pF&4#hhZ?@+u$@y`5f69?lRjCU~J!FUJb9gKG{-obc ->;vI^2DBhuXhvFTIcPQSe8R#+I!FUJb9gKG{-obbW;~k85DBhuXhvFTIcPQSWc!%Ph`NGhH@eam281G -=bgYgc=I~ea^yhHI0#XA)5P`pF&4#hhZ@6-(R81G=bgYgc=I~ea^yo2!$#yb@6P`pF&4#hhZ?@+u$@l -MS^kMR!1I~ea^yo2!$#yc4AV7x=|4#hhZ?@+u$@eaj16z|jw^ce48yo2!$#yc4AV7!Cz4#qna?@+u$@ -eaj16z@>HL-Ef1mEFO32jl%e9ow-9!$1H8!ThTbD01jXgSt^*Cl(U!Ke6|dYhhvF#CT#nF`gJtj3>sE -;z{wOcv3tmo)k}tXWp2_cw#&;o)}MzC&m-wiSeX(QamZ16iqudUWppoPbz^ICaB^>AWpXZXd6iU6Z` -(Q$y!%%y>Wc$7YTSnhv@hsER_xS>zaZHtax!RXWfP@HgQOBDzrI6%rEMJGRVQh=JDizaZM%K>q5j~0G -^Oz<8BDVQ^~ba1f!@h&x7xJWGR>^qNG%j4reKneD)SUYncl#jdu97J7f1B0H(RIhj?yTcrIw&>=EByC2YXjHMnZ)sgqn$@5`6ZfVh|*QLGfDA=gp -Lj_nf{2PKKo-pY0DjqSLr{}3UX2>XNPQf;iP=$gRLlQ%SP{MP8BepsXA+6vnQmrBb_&XoQc#BQlEHRV -E+v{5=)$iyPw!PMCeo0JKc6f?2@t65kBxoH+P}%3ZxrS=!QL2Mi(u3Ra%hLd)O_!#|r=<^ -ciDBYo^+9S*tD(kW1f3i*K{#g5v2TeUH;Lo-Q75kqf7BivAstWVKTgz))p{_6>*wU@}OPZ>Wg-qw#3* -7}yU-i|HWCXgEtLrg@w$M#*v4DrhU77;Vm -3Vxmak7uhv`)k9_UX0I0|uZG@|EJ+*RrDrQKJD9#V-37{Pf4dm^JnGWFJtPVJL!kv3%7?N9^iUaNyVN -(%htx@o+M7?k-pzIIpPL@hGDGCOhX&KT|G7lktyXDGMNzfRx57I1J8Ra^kN$<9Xi_UL`;F%Wry2YC{` -6Rb?IQKYd5l^m@~zZ-Doge!SvZ*-PEI9&InP~~>0`lQnLMO-zZ}F^M~%ZOI@5Em`Yk(~Ot7s~tJTVta -PAO1@atTrjb(vdfypRowUSslt5wIbDv$B-TrP}$Pd{geuV_^r)lTT^1>v1v$-XIlBl!j1e|_JM&L0RC -tI;EP7Peu(I(BlUMa6##wCroE=>u-Ken>yGegaTS0|XQR000O8HkM{dB~POi-#`EWIEVlMG5`PoaA|N -aUv_0~WN&gWa%FLKWpi|MFK}UFYhh<)b1!3PVRB?;bT4IfV{~_Ba%FKYaCw!TZ?7fAk>=muPf?P!T3c -%4%0E>ZWv@rV7|)=!F#@cS_l?!S#k8jBW^Ok|?5E%O{i1rj(s-m@UL5+Kb1EwsN2`&!_KRKlw*CynXWesowwc>GAp7$FH_8eze8^e5>bAwDsMOd8=>UZa@9tXIs0j$R_^ -b*WcUTfBACzl<&UTK7D-i`1H5Ouj)xYc>U__(~B>@&&T}fo1gtM-`Tc5Jb(JP?UNTT|N8Xe+wJFD_VD -fY_VxD3k8i(u{px=H9IBTuzIb~6^dqD8_0!|y_U851Z-2;+{c8L1_4nJC&tGj%k6*obQ_bfmPaR`&VD(^oKO7?Tg3l`!|nYfB*7XwvgZ5{`I5J|K*Q=`h0u -;<3Dfz`u?Y%zW?#(fBsc|H%B@@&H1aAUVQsqn&4MkKGoCnS8spJj%NdZ`0&#Y{w1$?|2H4~{-e+TOz( -g9(dQq3_}ORM@BaAd_Wt(C`=5UP(FcF}{rjJ8pZw|5PyYDXhwp9MXOEBXhM2+pM}zzyL;Q6%`R(f*$* -&&YK7aA@O+EUbb9&!o2VZ`*ee?Wpk2%d>KE6nc+MeeU`R>R6uQ`1A`qf{3-n``R1Ny7&#n;=b*KeO~K -Rmri+ez#DPqUk?{C$3(rO|)+-m~p!`K9Nt{_SN>!Do5-?_PYJP5(@`uw%@#d^Ompv;r+aJ@2%}W -Z)0!U_NUMCo&V~;?Zc0M`~T=ve)5nFfB2^lKL1x%#J_+4`0C3v%jaLbe5{q0284Nh{q*|V?TZ(GJ+>c -SKm8k)@ZMSB)|2VXUi+UfA!^ym-)s2@y*+}-~I9@Km72+dyijz|K8glei` -i--{uuhzxXHa-Fj@>&wu_;+y36{{cQPGKhAe=9V-srI$D4C)|&NJ+xxuVifhE=!hOd5*0qipPW?w;Jw -D%l`_9zq+^3BH_Um_T{o<$X=l4H>pRaqB7iMpM@zZ1O?dSQgpZ$7&ukA;zuphrmN -BQy}-R}7pERTNtH*AeQz1&{E%Juu^EvxL?@4x(J`zF1eo+bUzr@#I5;lX+N%jbXoZl`?GfJ=do?mW!nlr -Oze;8{VdC?fpMxWVQ&UXH}@($aa&(gR@{;TDcSKh0)n9o|{)AP8VjX1e=Kc9`c=a8^O6^hww|@KanZ^L= -{?(R*|)r5_IKv@+Kinq&u8Pv;Tt=h?tL;CSG9ZX57`Z#dGm4eTd{Ja=VCDW^^9BRgXd87I66JbD`VxC -bLBH~`CjXG$BrGg8~gc?kFlQh{m6!no%zViw=?`!-mr1yMfY3wD4*VuW*Ue6ZO`%n>C>5?o{SCc_WrP -V@IJ5X)|K;-FN+7ydA=Cca~{0p;GMg}7|c<=H^yS9@(!~;&~C~eV#GrZZ~oCoaN~)wT^L1nfoE>})tT -q)$4YZ*bMCx1X*{21PFl-3&$+vp-+jkhwkw;=hxF;Q3r9X*Pv65JM?PQ2#PT6?p7Pd>=WO$f0Fj< -XHBVjkl2_vYnbK_IuH{{mRx3Mmi@hCu1<7tidB^u&u`U_D1)+?OwUU+sN6JSLTr6+0v%-4#7BhYaV>J4Lk~wYtJx4pvDwE?3s)Vg|Dd`9S&S$%EKrOl%}?la|12AufLLur6{YyPzyIh@aQT?`^j8q -Z$qc%&m`e4X)y>&rPhxG}yUP1+E9&~sNb>n-~>j4Rh}&|3VdQC!^1IK*nmxQS2FcVG_4f4d@8A28qkk -+teAz=wT#~jNe1Fh527^yLaZF7xBSIMWt{033AU_c<_ekmbR8Tlw^@5f+*A1 -{xdlUdJ6=lF>dbgk=SENQ4?Ro^yHP*%D%4P7J)k9^+ZVb#Va$9S0Zv1(F%nZsL;smNNr5NhgsDAqN8# -ImOT`Cn_&z%7~>a@EMI@l#V8W8@5EJ&n4`r^_FWW*RiW1ao;qiPl6Y|e$f3vxar{E0Ss)y%PXyA(A5z -+K-1Xd6OOa&p(ieB0M1;(tX@JzA56+Yj3l5%;L?pI;mg2hfQtx*dk~(x_yuQOaVTdeaS702yL9d#kJ( -8#uvI*KLJ|N&a3zqBTjzshyTTdfe7b}OUrAf6b`zI$7xhIy28)^1o~~iXy&T*DOv-FA$muIDU^d|{hn -ob2oF{lsz*O@#z$N_z>IA(!a0qyKURu`)Hxn9`Njl4XaE!e>a{7@IuK-Bv+Rfc=;*!aV%8Ak;U>QOOL -ja&-PVok)ER85T2r%9?wu@Vno*hI24Evn-I3zAfkim2U9oE#jrh%{NJm=(zlrlxj*06jWQfCd!A&xun -N{3fydw07*9&p%8yb~ZFw3pTe=%Ith#0eiRQamgJ&oDN8%m4%@g4)5vM5IxEeO%1j#j;8lne(RiXBYw -FSKctE2OA+*WHsdSJ9wnU(_qP%IDkL@Bq*WuL0|;z^^cM~noI1f9q@y5b0WTg4H!vpyPlnh)S(s -;j%J;Z+uRJ#0D)9~EA*4Mf0Z?3Wio;C=wHBN!;~T^`;9bM(=D@R7@v&H3=1t{yaqHGq3*x)MB=K5l14u?D;GHQVT9zRTorO(4doxS1e7^W${d2_PMe1JA9S#k!n(AjzJil-3W|F=! -1xd6k{xhNcdQ3B7UOaWHTA-{WA4MIHjku5$;&=Y<0n^0FWmi2T+&Fn*AQUv*} -|qy^Sz}92<&n&7#Z_+CW`Rb$q*+gVLql%fCTrt+f7`Ow#r%rD;(Xgk`EE=Pwo{)KjJ(Qp#$X8Mi_Y44 -{jTj)ucm?v+VawTr$enx>EJmjtJpn=Uy6ikWtdrG;Dj$6N*Nd^M<$ME8DyNCMrw6Ze+p+15|e8OGXM; -4~wZ|9vzAcdz`pKn21~h*>hG%Iv?<1U{FzZmKoARu2dX9fC#*keZd{#W1QWkVZrJ&ErZ3m4-txP2=6> -hW5D%Map;6E=}b@S(jsJ;Q@}t7!b}eaStziZ-z>+0)Mh#G4w@?(Fn1RIJ>g4E{mB4_lWYl^1!Q>GyvX -UH4eWgBj_DxAGYD0=0L*O}r5pF1;1V2v+6tu9#6MghpW(llP|U6~@Ruk{IGQj0GGq?z=E8LK(=`t1Cz -v;Y6Iq-5%4IwTlkNvg7*Ht!9P%~B9L&Sqw#YUTn&ZTG7jKjfBj;_Fy|XGwPfob1>LuZ3T{g)uUZ7Z$z -50`Qd@)KF(?}9`-hL-xWa7!GV+8Us><1YEHj%-?W*WKB)>Be?s{sDeLRlgl{`t(bp>){BM+0=Qn{aXLSc#5;RPT(STz>6~)O6B!paxMl -%f(&dxIkDpx4ywrH51C{U~EMggiO!zI^O-z{h7Y73RZjdoslg1%*x7CX4zc6Fz(F2>{B2gq>?|E2Xmk( -j%8^hZtUfo(9L6gPKoBMC^c#{1dwAj`IO7*~o>L(ea0_;oCqLC&WEWzi%Q?T|YqjhSTe`CMiM7k;|zArhBNll99bVTgqeUQl>U@e)% -&2j?;Y^XF3Qz|l2DPfgn!xMT9S;(yb1?GH507cfc29@1h$2V{S-k=+4e%@Q=g-aR@5GcEKf0-pF_e2}^q2>Dy*q;tUr!Lt -u)BDQ*xi;dour089bUjPPZ<2lar0o{~3A8TNCsq7Rr=2#*{G&F-Xc{)@3Z88kTw!jd(OsCqzpviZ;nc -6mD)Ud;(JwFbgGU(3}%GTWs=iOf6M5-^_>dJeO@CM{q7_exxnZGiilzE(B^0~c?Ql7;214W9@?AWO(j -IMQ~15j21p(4f8iLpt3P|6psREUb$~2!d$LVR2G|xr8Dn$0li5V8M*wFfxFiNv2L3zGl_4(uMbj-fBM -lf@4W%${k~&lm`xssFh98x3X!%j8K5Y!?gA+elo*AuD2;ZgWO&ERd~5&(XdIUNqiH;yXYT3g0WF@nxn -3;s|?Fn)o$XFbXGi?!0BLt4ZaGPY6jq@OV_io)PfP|aR0y`a@%EOrh3%dyKz{|Tdu9?6yrWS(RI{l!K -HT`GyG1oTF_kEp#NEgE>hz|?4d#3{SD&V^qycLd9m))SYE% -etB%6awg=$-@;*MoTXTb%{aF+U3^|7&n3GYPv{R5BR9LMzu>FXIkYreVYKmsw{$}NjT#l4o6j5gee0d -T_Ik_H5oxe%JptHaY=ezCR^0W(27%K#P8O~AnH@nPoTFLeo04T`}BM>A)giB(%Um{Jl|z422_t&pp$Z -UiWr+76Xs-`l~6$7c9OVm*_nAWw{7N+WxpN11bc6Qj!AK3&03l`VM~Biu`ZVhBhrbM7k-ZkT@Y1GHW0 -uxZ@mVL^uweCIV?XPf>kq0I9cjAvl)?!AYmN3H>8BriLZg#g=KyJJgVKqCH<6_bE0Ss_};L_97`;px| -1d80B;=zYLiIT+ZERFU8dr*vfunijxT7o1Oy?haWL>_P@bf#8*95^{u^oKW)X#=VS{OEq@?Yr@v8A1) -N$k*)L~rYc7WA>I$PQ*?zo9n0N0j|uTULmE&N&0j>`c{b^h|B?| -^)PMT1GjQPkZTnE?k~8co4Cd3Xr -uIyq76Fyd&?!=W1{{=a?%wT9Yamm7CWT!FCQ)4rWCZn9Bt2_vhs9}2(hG8fzfumZ7W#_I44NqMBSz;v -Dn@SqEbY}stiW4cF~taH;rI5beq)P={X+0cgOtT1K3Lq+du*uvV;T=w -5sAzb^pY4i6;#x67^SeKCnyjX$HB=*y7KI9N;1M1c1fjhk`c(`fN3FWxpqN9PPbT**@bjGq_mjOE3qA6MxH6G^6@+~qY0lpO%qlv;wwL97dJ-Yk|~R6g -*|87csY3cU<+L+eKDc>9MFeF^wzJFBpZ6CB1(dRb*b>seNE36D5Xojb0#BM9h1m!@*BSAQ4!l4W|!6E -ur!#h+m{B#bKJPJku}#fH#e44>XIpQRynq(hSbZZ~ns09ke1Ve4XUV1mk&^RjP<)=uvLh?v&;5DAEwo -3Sal7f!8oAXDAz)vzw2Fl(mahBEg0Cr$#Ej|%+a@`mk=R*_E81}?`MCQxz}e>O|^+EvFwZcJB{eY;pk -_#H--+QUFQFeoSFs4|VhaiA=O^PpNh=FLB#d(DfinJ|p78Pc1YsSBaoeHol7Sj8x&Dje=FX1knj%=xM -~gzj~rT+54~cC6Y;dr@gTmP_L-)EUSd1=qY-%tE52WnIx(J|ssP% -RIP*622(M!nXG+hj^6A9+sZsL+wf(P|c06jC+L+)U>?wxG)lP)j@1E~gN#c1UxNzeFlyYo$SuN`&RKr -A+R@P%jTc>nAIsitm#b-d*2c8tepvRvChqTNgfaps+8p?hr@_#DNZl0~1aJWPz#e-dOU9OUD|l_3Z2R -USy^Gfm*92=p*`-{X+DB)?A{pv2JxQ^f+%Oj!~9Ya&USR2|0RPobnGp20i|#d)WKM}C -j0lso*q~27EbNBfgOV-ffx^0!6ps>N;G~L%t#;A94p_4RitBRTM~PRw9G_??R@2BzrIOK(D -4erGdNFHr -e|gH}1gZi*#S$l4wdxP$szlVVfkC$6VB-hf);D*gI_?zN#n8kg9DO3@`wWkCWzh5~!&)f_5s!_$QGhF -Y6^zU%EUsJniApnF~FHHLl_k)0JbB$-oO62*}Lly_$QpefJC}r;yk;^3+0s%dJ8LIFx{Zft3YIK0A -E^?rIZHQx9H`wwoj5Bc*a)_yJ&p9HDx#z?s3~P6oGFiXw>d}K6p4BdO91t-}8t>>mi3lO?W8BoVtCc5 -zlOYS~9;Qr~0#UWO{9DE21KsNsZFQ5lT@FF^Lsf~Gl)>O$d}SSCHh07zPI9O=JHc1hxaW=iI1~eM@tC -d=i4`qNtXv5{X;~1`xXb-Rw+yVSiFz+kOII~t`pE;`E7d2Et2+P;o{&8FYYH1Q -ANm@cQTOt;;`j>o|w+KrS4P188l -njR|VP8%>b!2L%fWEPueBth<;L&1jeG4vO9b<)jc4;p0n91~XMVM-(Vmra_c46=NMQ4FHQH -A}uwxH*bHSdtIkY{@pAbi4mjO5K_^H*htP1>M#>)0z0OwE8>A(C|Fq5<3W+;cQhI|KLOnXHP&1aBs=|C^zM}E)!1w -r~du>?Pto>Pv|4nvi2z=IU^UYPjte-D?}}oL~r)?qH!P{24b^l)l!dk!P#-m|7vxVcKOelCC7M -;G_?>K6E(;vNoR3ZKz?Hi|Ao<=zEYWixAU7Q&4VmA@V6UI+Z|QY7W}wYm~d43D?ra$2|38XHj<{IE_l -xr?VwH+ruF)0nFU<;RX@ -aZv=aa4JdINI<4MKTtS5t8^-0X7Lr4EjY_xg#nT?>UMG7#mfKY=JRWM*rwI^iHncdFF!4>Hj1d$83`{ -RchCBS!mfqvQew?$MtAPjKHQdEu_i-G68S;_Fj-Nr08yAH+Q*%=OC9l13Z&&TBJJ7u@Mg5oarsBKXec -J_eOzA1c96rGkV8^9Yx=T0{WGnAJ(7kqQ{G%I>!E>a`6L`0ZC#}tkpgr}TdUIr&rwUvbCoYHj# -kQ)Rb)b74=D53F%n~}Jrb*OPL#*DNeoPCs_$L8 -~q*xBS1s(cYG9%&z3g4mBNoloY#bH1@TqYPcIAhj6_XFMQa!>G~b?L8{WFmO7De6L|15}@;Q%iS@!^L -1NukHG2f&3lk(Y-d1b0@(YCZw#l<=()h-%;gnF~^F76wvYml3BOrV!yR1vg$Yoy4N`^{24}0RVB>0H7 -;^iQykqasrlB6*C3};7Mr{rProaz2%S&B`&b|7z-iT@t`YLmxyra1nfV+6l1KsOj7wHSz?C8Ruos`r~r(FcP?(s$UN=>1Mu9vt8HeTrV2Gy}e(Yc@MOH}>Ldv115?*pk!A3F6rp;QjNQOHlCb+fR5!?mD!3cb -N%%{)t<%l)I%25P;bd!3R1%hEXfj{A=ZwvBE5gUn<)Wh$je4m-k#uBI`Zmat=frR)LSDSfvOTDV4CvMQALD@Tbt*-)_gCb(mhGMq6cY*s4l(j9?h^x1+{g4N -`*P%*{NZQpxmEU3ExMSGeovJobE>GlWo36L1g#`fM8ipHAuA6^A_u4^Vo$ZMemD-X%vA;!hlDyF0F_q -{t=57Wg63j{!tVo*lR(<}s+$YT3Lxv3eLvQkqtU)AgbDCR{Fsf)$dB2PP1uTLSx4j$2SNU~xuMN1JJ@ -N&vweG;&*RmD}$xg8USpZ4m+FbsuclTO@6xX`_I=a^m2iJC~3YkAiEO-KTDKB<)FZVDbkXy^;r^JyPQ -X}Zoy|qqb@#`nL*TJ9+g24hgHw$i6i^5Pyz&1#uP`cI3XSQ7pf -*s@=pT9VoZt3{OiUI9$OAJj{Y&SftRyt*-JKLplu~r>qo_)X)iE<$q6guO_5>9k@f2BsS(gp8A0Desl -#xhuM{Q%X?9P1-}?>kp#_FI)!srUsBs|*-bB=Ie*8Y|g-D -^h#&^?Jp(HdcNpw3+ga~Dd8!H(S_$603Q5hGUFHG48HdBl64#EI^;k!=rZM>#!2E0R>mkg1?rnItvZQ -G$7yG)`>L;hygB1H{}LIBFcwy^gN7opk~F?!6m6;~XyXPPW^tS&1 -^(pm19`5i7{0C(*RJ<+`m%8eV2sdNk|14TfU?HJS+5$t)w4ybghQv?k{zm?&=|#TxJ -CN9ni~?k#R-g&kncG=|`-r4VOQQnOxP4)KU2NMEBY)W16U7t41CE(NR#?NT=}V7Ndc+gmYcC8dFcceW59VdAi>cef4 -X`~2+uJVTnrVrW3irU8^p-yLME5#83gogHefemp8|7Ch11*aP!+*gq(Km`N&27u>AyIf}%)4&?fbMlF -NCo#b6(sKAINd2_pmx{uK6$jPgwId#3k5|^eqL=wS*2

    Zx^;XqSAh4i}QN_Jh0Ki}j|LmV9IauG*(S5w22yr_6_hLGlnby_nRmrx!p?F8$8!AJDxn -^%6|%LJ83h_XU{?v%p~Ho3iBPStXKE9z&xtQIsVxchle<-_X5oo=Kw;;c~yzG{wSWO~(c+(1*KQlByd -e|7CUgbkBlhxRIms!V}%=f<>J&(&~-eZ}0fxPe!P{A;gmquO<92KnXd`TbAh#`Fzz)&sn&H;ZZ&Bu~z -b)*ku3=x^4D_iDH=jPj$#=vM~M=h#IU18Q4Q9v?Zwlc=w7#@ -4g$owTp>F5dIm#0l&6Yww{aWYEtYxo83Aotx>o8`dbtzb>qdNQp7|!*SjV4S-a&+*abgdb(o~{l-aj+tF3cL^fckag)K{D5585{EKPH@+yZb(?e7f+`-Pd*$#trpI%?Kp85c)qc -aQhILlD?>P;o!QD-5R>11?Hy6ElviyU(SK}eO1vk=;VHJyy;)Z#a3W@k -RH#!fl!gr;=AQ -0$@Vf1N)_&C@`BFnbB@-2(8A@+jVbaro*({cwOBqpp~6Z%&tT-bMNUbq!G()uW8~HSY@5t2!%46N~BWW*#KzQ*1@umaPVa#qtm6Ub|b*)Hp>Qg62VsAnZCS4ItkgD -z~M0G$LNCI&Hf^0d^lA``s@35(}#oy&D0oxyv6Q-C^20WY<)-;Mom_#vT^k*{VW%WE=z{_j%)Abg!F& -MdcP!uWm1ZEN!Awv`~w>={&0EI3&eaRcHdGcV$%!TAe3$qI=y~nhT15!9Fj~jsZ1v^WNH-BRX}ngi1d -cdAEI%-uIz?=Tz-b_7K)_0509*Id{Re?g9}49-t{T3U3VuH+(q#Y48lJUM21-H|{(Z-RodxnoAzuvF& -bD@kGu`XGxQKLW+jUZpuc=E9tH%Be=1%&Urf3y;9to-EA2zNEPRvKX%yALAR_aJ4X!0;c+JN#J)%HUa -p_J-6hY(s7@aUqi3`{_iK8hD--2jEU5COC=s3T2#E>ES9Oaq1s(vZtslgluBVFGi@ngjI+zy`0vsK*TC&S%^7=BRWaH%iR -Bq?Q~6C27?hWm<#$0m5lCuooFS;O;SNI65Y*WuAHrSRx?yYMB4!iyRxdgMkr83s>Y;lY8(Lt0USF+6D -*z{1>glsQe#H0pf06W!}}8Ek#h;`uF>?X<>Nn){#)ai6ezOWlftUV3zkIqn08`Z$ieUH0Qxy4^MK+Mj -B1*oe5ns*i|!X(UF>4xD -O3*`Dx(?bg#?WhGjYY&a|?%DuQC_s+3irPS0J$D2=_avy_erXRLg#s61<-d+lzl3^%{};Jzr{!}7bIh -JAzk5=6N1PZm{+4`1-V6*rfHO~XAPj^JrQQyvkp93p(D))jKLYWL(EL(fTY_cc(%Q?WciVQ5_jara0L`)dG(B7jZt%nwK -3>dNhR7P{AtJv2&5&6yWifp0;h&pJ?}&IdrqR!96XXazA?A@1C!lv -dkMi??6uUPDA9Jw%GmjBm=1q#l4vi*paA8>gribd^U|95};xx!YDa6Sco(p?h6!=C=sPF0O_>Oi$wN0 -?l2JBsh_g!ICMmqQ)F*dM+Iu26*jvyWkSVIdHN=)`8S`x$(McZNu?pAwr8&{%)sfjKR}g=wXw(&AVOp -3HLhI&r@0af`^0?H)|SEv+W+aF!>ap*fkIDFSp4vu=(rO?<{n$4WZubb(~(~Gx#0bz!!NEIuhsso4w# -0H%b>BP&syY!m$IvLlSNs$U^rzJ;S#|%TsL+vvqm=Het;3g_gI0@}%hAOq3ACu)A`3sN=2?*Lf=o-Rt -n^Y68{pQ~)T?b-9CuEjA=OlbZRuocrd#SmhLqtz=v^EEoT}+Xa`nZ{`rT;nIoK%TVZ!en4eMTRIuvzU -p51C$dIXP4l1|cMM;pV_N85r)S)Hbct0VWPIHXg-~J(&S$cnX99$JcPlS8qGkyOX&%)b5X!9ehVFIl? -i=)ITC)X@yp>H*5$$n_9xidHrtknA7Y7iILH4LavxHf^=j!gqsq^zZtJK5jdFDzPSct=61n?qj>W-4z -|M8*}?z_~PzuvG{99rsLm#fEJ{)b1eBUbQi%=&;d5&K0-yFvUkLh%IVw_0U2EPnZJmwm!lUCv|=Z_H# -P%%x_uE>A?(#M~OTBuPmqhO?!&ajS~!r`8*~*C}h};neE1x@lSn^5qV!O?p%j)TjpQ0i9*t9&xRc09K -W*f-`)xzu;O=IdPMr^#i#7&OX3-zwjXB;%wwwrP^Fx;Rh}(5a7FZPv^~vVlWQ->j`Jf8xtBzKvdV%p> -3Acbvd9-Lbj(ec2{}f)TQp#qr^QI1;%utBU6-asM)tm5$?%Of=pkEznwNpNdF4rim~x*#lg?g42}S34^w49~UTxw;h~@b4sd -AK_8DwHp;>_^!YT%vSsLI7un<05`lTF|WEbt)1cm8xk0MtN^CKpJFy;&S7N$ymNVIeY@KQmo)hY%KP2 -pa8BJD`ChqRatXL}rB3zJ42J{HJe)QuobWuTyIsyNA?a#}oYy5zg^EA?nSeUMh_LJNZSK{i{^r4u&9F -$mtHkH32JUv@OW=1FRq^J@ATWjfG73xJ67vkUYM!C9nrCGr$sbE+Y4~FDM%AS`$RXnZp>r8-^ME0n#%(RxLp$rh*4@UdU39M<6xnN_ma~A%arsFw`a36}mczXZYG2&6XIWGkE`@l{a -Jx!Z!6xhU5AMVE$1SKFDwkr{+eA{aJYvah=N>ZWDa9UsfTX#hIPdJPUld5!t$$!^Z2AY!vJexx@8Q7^T->+hNA}IuVE^toX^3Ha($F4(`;>h(mQ=9OaCrDfphlLKxT -0*M28xdZEx&GoQIf2D)su!=2X&fz|6ma8YaxSPX;g(|k~?H&houT+$mwO8`#K3!J+)Xb -roLiZ2?!_ev+!4n_i0bH4zP9f|@FMT!pOCDMQflK?*xf^!zwZ=n%v_Ed)QI2(2o21(1esmF)sI>AzV+l8c*gOgbf+CR_Peh5~(UYyd0_e%v3Lr@{dk -sm-IwkaO*E-JbxqKk7!!{gl??8O>46Lb06@Fb>1};?nJk(Nn3KX7r_TuHJ!e)OxBZE*eJPy*vhcArzX -+F(uJkC^d1-~<$C}Fk-fqRcx4EKv6d0XImW#?6Dl^Mn$7CxyA#-LWRsr;~D{;s!tPA+q -1`_N3N;y@(7n#C5B!}chB{8dNiy$mkQT#**G((7wi6V6E(!r*?n(7%;pyR?oxb$unse|B9$3=+GCsrjR(T7dIQ`jTXn -_d?_IXH05HHj&0Kw<(p&X3|H~QFydT`2zm(!?W*Lov@LK0#@I0#+NGAxErI1z;R5Gb5i1M -^g=DlN3}rTZDhLYM0-~A=~BzXH*^V3T_wV3y%Wf}Ty?uOoeDl_yx&7+#?cfkIBg#GsCRGbul{fOIjJpcnreo -Ezg?h-{f%R1JZrGB3nM$h<#LU>^72ZIspjEASB0q<~N;Zc=<%3xDuuqh^>(SyUOg59gZwx4Ll%m<2DU -U-Mg2Ynk{Ik*zkiI6O)I|Yn?fLVeHX0X>Hkn0C(;((`!Ry$P##VjxR4sNSCi3eALXds}13Rh6Yism>R --0S2DLUBCFYpgRP!5REfV>ZM|ABZfki7(8JgA^Xs}uJF#Vjwp!+94k(W_jVAyb>i=z^w(Ep+H>|&^Q4~55yqhvcjB$2pW -XKpjsQ0t)f??2^6!u@D5jEQ^)~=ihWRW56a+y?+S{oL3K4K5C&OWP~8Y>1OZ_dy-w~46tleKJJ<>wQN -XzbWJtgX1duC0ML{eHVoBim0}eaje}fb+K?e -L<2Y&Ai)qWfp-G*5}=m=SOl0NKn(!^h<@Gkb}?_*iv9-$U!boGBvOG&BPc5eCCdOH2Us@%s{y$XPzpg ->8>CnT7S4FFD{0u0n_0XY}wm;$*{z)}Y#vKXa^3ly`w@D5jED -^!+(szt!e2V`)-^#&wi!0iR3Tfnjfgi^r61nf(UxaaL+-mn$&`Y=x?I}cM{veF=H3sWZ&n;;_yQ4v8r -jGF|?K!_Q$lxu-vmKWYxyaX>?iH(pA2I}Kr`$Ry`2WQL#$G`*%o!~r>Kra^@cM-D`P=R8W7vAAYY{i5 -E;b@Sy2jYUDI2}|Cf^u9?(Fp)yz@Y>wR|F+;Vn?f1v@l@J%_ -=r!az$B)QkevMW~geU6>Y9vY@~lsD}cTO;FDXYG?t08DwYy6%_J2a2R0*Ogs)$$AP>xW+{>b#VjxR4z -_}hGN2R!Ib)zb3>0*M{3np)glP;#=Kx^`CE=jX8q^%)KAAaC%<{rJT#2oi9+23Cqzn)yfC3@8LEamrE -Mf8h&Y1Y61;FFHUh&CLTUl8pblUY;+LZs^guDo3-53xwn9ZaC^`pK ->hvOg%Z#c|^j6tleW4p(9;F#Z8q8L;I6nHO+n0TC8tTmkS8NZ^3z47kX -EhK%FJB`9Wj$#<|72%`W#hg=S9O~9%ItV+Oe1Z+mY7=#o7S$vSO2ibERH!eXj%M0&tCALDU7o>VYHWp --KVb(L2WP -3S|9p-nazCEHAvnmDmcUnPAUfP_Yja5KdhP&Ql0>C$Yl`z<(8i=X_+91vwm!O#Cg?G3TTR~GEXc$7;34SHmzZ2}P3HH6 -G3F%fKKhpqS-_ceoN;p#l?7et}FUP-FxOgP6kZ?^3re=JZd`(5mX~}7TR}Sz$O -ZzYIUpQ@8gx*t3<{M&eKDxq1(myyxqx2}sP$MkEc!Jc4cm4Z*nhkWpQ<7b98eraA9L>VP|D?FJow7a%5$6FJ*OOba!xaZ(?O~E^v93R9$b{N)UbLSB%O -Fk%${wU8!x<2jf7BE?>f+C{Jjy$FS;p*Vudfm|U{y7)+Ii|ESJeCb`4b>6rbiSjMT?N@Xu*!{6X!=Ii -P7H5&E6Z^aaUguA({yLo#s+)tt{!9z|fO7G_U>6=te)RQF3F2t%FO2WhQ3|{|sWcRGU3zLX+iOx+6S{ -@FaBp-dNO^CYMDWLVKMKA+#Srv3lO378FNG%roH -20uev}$EaU!c5nxQnsRqAvZ{Lz2+n6?%}Od?*V*50x>tOMT;fNS(x} -z4_4V-CXxR-t>r8DI#w@G??D~j3G3Kb)@-034Gh20WUsvJ*PaucrtIg3)20UdMe42 -d7`9SmJE{3pmK;vfa56cboJs(59=tHsO~GK5EFbQEIf${4YKLWXrsrJwTXs5`U|*?LD^tR`W9&$;bD1 -@^1(pRSqoDO#V&|;a9mlF@;^Dbm82z4p&JJJEx@^Qw=<5aDonN`WDt)E+2dw}4wi}&45G;11NAN6c%Y -NN7a;-(le+snh_g2$8+;F{oc-Q&~P)h>@6aWAK2mm&gW=T1X^bPAm0066u001)p003}la4%nWWo~3|a -xZdaadl;LbaO9oVPk7yXJvCQV`yP=WMykB_~c)=|K`0P+&tI|NW0|9$v -n9_xQBEeEo3$=H2$P-t*z@=Z`*n^YHGWKKa}Gzuv!o{Nes-d;E5L_Ya;~@BjS%-P@Nhez^a?9{ce4=5 -77+>4(QB|4|R#zI=RIKY#gj|Ki>KtL>YgeByuosuxc@>xZA}tG;`;{o<2fZtc3xdc;3``-j^{uU~J^d -GFip`Tg7br@!96S~L0N@y)xZhi`tYY5wBvFaN22vTc8O@$~Q8mk+Q1^7Qci_N%Q%_-=dmxPAH4yYC*~ -yq|w9)$50Eo?blt#HxM!bbr6Sef;*_-)dmL*?xNbaeMjV&GvNv>f!Brd@aPg?Zul{zkYnG&wu~;>fzg -;_|cDVUe$CM^}GA0@8ABshQI&(kK1SWZ)>F6@9*E-Kh*#;Z|f&p|FHf1ZSS9Wx7A+%;>A10U%UAOAN*wv`ls!6?Z(s3pZ -xG2F2Ubt`f7W4<72<8wXeU_18bsxE3?|Zx!-<#d;jf^ukY#!_1^8@o_+lBXCOKh?nN0e|@P`6vHUpZMtGXP-U$`cI7hchA25{L`<#+J5)N -^X;SU%a5La{p^!Je)iGx?aMzt|MH8kKK*dpzPi8vd5Kxff3(Q|XNkY9M}GfUEBWgF-HV6UZ`aiSRNMQ -u2Kf5b_T7uW-q$w2yniT*+Fq24{P5HN*B-upeDfFQTOaxRg8pWE_;!2q`0j4|+tWkYPFdf7+1+}|-}m ->fjQ-_^cU!i4(~CF%{<^l{tNQrw9=@$d|L*nUJiT~{Exmj3&FlN+f%P*cSd;sYFP>ig{MvR`p -MLy)`|dyIF$WLMdg}J8U;WQ*|M0l@n}4Yn+TP>+3s-;tLL2o$-{buGh4cLj`>Yr0k?V!_;lBU;LU%aN -UfmNqezs;jZ9*sr_W)`K_l%2BVh^QcMf^`-4Nf{)b9&NlA08qQs-_wcM5>t0W7S>uY6@9a^}pEa0T!!c` -St!8$fHQRp98fMlLvc}a%ulYFJUXK|qYAq(uZ)et!j{4#;Yhq{Cs|@p2>s8AV{bVtEJz=jA9;4QIuMh -W{I4_(vu3EWqF^N%g+D6T3>{oqkXOBjGXU(BDWoy~*+C1y4a?~4-Q8UY|zir8Y>(Tw{^_{h_^*NS?MX -I^iV`i;u?a^KXPBy*1^H!71dSg8`ItGNrM7@bIB)dHCB6$92%)6GVGreuA^&0ipnohrJaD&A?G@Bas= -=>4&;l}sXXljXjtL4Kc$_4_9snP6beO>Ie7DsJf?F>&X3rXf#3pws?W3R!M(Ko)H!7=s|o4~|dTs6Hc -J1objC$uuWG1C>mfUwfF3C+!GwT4-b*#=u(;if)UA87|Gu-61?ziWCO`>n6Ki`qywn0>2dC==*>cCbk -0R<-t+&BegL|)`Fav1?Z#a*Zc+D2_a)MM{*$HZB_L2#(JS>u|MAz1qhjSwyr~AaZN$i(rJ7|EZ7=UEU&1GAG!;{@HUjL1MPB&KUe;E=jDy#P;PeM -0tUm&;PE2bMOxxkk*D?%4X2XhnK7s5c+OWbs4PVh%U3T=U$(Mzf2anU3Uw>R>Uu6!=kpbiBjYwZHj3= -CKO2vP? -pw6QuVWFMh)W?%UnxUi&Rf-Z2Dv&FLM@lSnk0XE-a0~G4Ds`ulMYD0MUlDbJa(-hV7h*F|rIyr&3!d4g>+VzAw%)twAvAr2Ky9w**SxxYw4IIn` -QE!rb7S>WA3LB=7S+GG+scjDrw5lMP_c$S&o@k|h=s%Hidj4ts*g514HNKPMi*4Q^7RM@*?XIAm1s?^WO4HYJg%iXL;>L40`WjiKcCIM6kHxt -8N``9o`jPVrP5OnK{+l6IyQc=Ywe3@WJq^$Q~THSb6Q@KpPHIPOOE2k6MVqQjw=9U5b%H+z#*tZ)0ZD -Y0#5386WQZbPc$QVt#Ttn={xMn=u#czNyKNd!fNn)%AmKp8DCUzNIHV7&QnRk+~JCS7@_+-brS(T{+; -12-D(Sf6<#)x4TDrDm-z6T~=Da}~1lf}u51gKums(?y_NZ;If!XUNcwvDwu!8`S8*&()+WeK$%2L%Tc -V4|eHq0OEzL)+tF~OX74V4z2CM9e2D*1TbrtHy+ -r{5>qq`qzKowHa)sbvt&?IGwC+iwH=Uf5&_oKmsML1S2WC=v8N&m@lp`G&c5U!EFlSP^k^T~!9Tty_l_ZtPyg|C=@ -7-c(H`-904i;9l-PnMu!dK-#66H>WsI4flM|s$(DAG~@ud7c~UItO7p@WERxSU~)NInNFi`p(WYIg(p -19PsOp#rW?#AJ>QaDTF-r2IjQtnk&$b<~?gkp_+#mVOt73BE=FQ%+d_G^pqm!i*g|JPsf2*EI?smYs0 -lsRBA5*$$!ZjyVHIS31E;cWZQ3xDo;N6DGkhw<(yc5e7)EqDBYDP@f$Ha1yF4h8cSMiXFdyu)t3VTUmg8iPDmS6+dKJhhBdIvC}ifz+1UM -R1t4w`CX9MO0U?12s`BnGk)Dp@Bn<_32cQjf9$fac2i>ij7UFwHe0IVJQnj>p1TQ>;M;6HpHlSzBuy0 -h)>=;4k9}N@8rz^&9Zf!9zG^TM~f8!DV`=9))1>5F7qWa8PG(;cpMstRP&^k3~D_#DM;@lBpyOKLk@- -LOysH!XIuGV3cl?!>AQW4O+#uc$5NPENGEKO4~u02gnD|Q^trVc3eSrgL-^Y-ca -vXaDYJ|_SJ^+9+o97n(K1|G}$omP3q;W!3DXf5kZ^KGq4y6&vHNV@??LqyO7jyh8OZv1L6bu#%?+x-3 -BFm&4LR90wm(HxjWD6b53PZeJXze!LWJ?-1%RsCcA3(;Ye+V3!e-1ARHc>fW&|iec);N@Q=dg6Cqi>= -r=3Nh!-y@I>>v2j;tYL^ciEBx|xGpPas$y!~<0OrORtGH7CJyU_m&3^A%2De9Pvaz -%UjsLWmSWK=Ul4rs4B>-`9z-y35v{aEMoJx0)tH2TY=2Xu*u~l(;0$v_9}*Fxy -Ek63AbWT{|{?5T|Fw=`x@R(1ur;a!eo-LRus#;A*<3AP5hT*dp3=tD03K@K2w>(%|Hq7IO|!IdOpy0D -%YK+$rg>cCFS$Tl-BsgcP;p#a+6T?+Q6A{lU8S#qnZ5|bGHlAcL3xO4Y}kEY#2oD3eo98U0tZ2hWPn%Xz_*yZ#@Gbk1cweuy^%l0A%>Ky0%8y= -s`L(Epc#h+85RIm9;3bvU^lJQAZ(9-6*+Kqh^Gx+qCQ8>ehGpofy43~I427hOmuN;)_Ma&z+OYwqoT) -_f)cw^fo-ty4XE2Gz;kp9YnWaA_7V`B#6!Lb+5>brs8+G5W{!%(L1Tm>#HQ~Bpg2H+7;4rD|*KHlF%4ssAvoNuL0nS`%NwN -g%1wJ4q{Ljbb0zQBdRi~J2my2{%F-bSXZLEacUXkiPCD3hQsBzV?~4j -(qU~1UqalWc(~PGyC3DU@cA7}u3ccMs1z(2f%pSx)kHp|rV&OBG8zGVBasA#e5+t2*vH6-*mKcFAZ8< -rodWieU2bH;816Sj0C8<&rP^4WMn&vS#2fJ1^({a!f}IrbWeT|1??b+arK+VViy%-E*)K?TV17LX-wm -kyjdcdhb~wP!idS|oZ(Sz11Eqmm`>@*8K!IDb(6p^ixh_*(VA!^JWRZwFHF?uCk)Gr;c_XIUVL%F?*< -!=+%+RNd2Y*1EOzRYI%z)2NJSBjJ1CFLm2`b`fGf}eNorHRj{ErJilEkp?wf9zo0dUH5QB15DgMW~up -nCc#OAPWIvKE~pkR{zjApj2eq-H;Bv2!v_)M7O~To2A2CUL`TwrPl_JV=twVHvy}j+oM{y@@swY*1Aj -{9)w*T*@F!u`;wYA`2OGYqC01f|!4jATF3Oh}aD*yV;50XI0)3^Dj{@X_Qt?1v@j4Bmm)&p!+`V3`!X -}Aac)cK?bJ7p=l_VFWVcXpfQ`Bh8zME8`Y%hI$d*aV;%B3QXqMu?fV)AY+&FZ} -x?P6uF&L^;580bm~R97FKhG3-&J&Sr3+8tr6=E-5p*IGn(V=O+(5AWkUe$F5JxKpEz2)zD7E0Xh-l)- -j$ADrpp7mDgyNOi5~Hizy}CIn{MVOX@fvB(-YAHc1iy?XV=JK|`)+G$1Nw{(4sI${vuXJC>>Frvt?%F~8u!&|`kUs=tHJUqZP1zAjCG3|0($ElLQSFArt0(luvtr1(>cAEawf7?Nk;Rs$xcY=2b&$v{ZXm -pNWDwG_8UNG2p<)2#>*2RzEKiQP>$G!D`R)1Xda81%B}9yOxiU^Oc=0WK#E6ZRuW -5P+Tr?>oSsw=Njiolg_x(uW~v(LCVoj&w#@UP{JSpb^vVwOmYJwa>@Z6RB3{lu~flgP{()M+hfRA(Ih`iF-ijPf`j-$P&_HNh%TE%Q1f -MaAWZ;5ZCRn62n0KzjzXjrWE`l;9*YyV{IvMBMj$Th%VW3wVTnQrJs6?H9|_OSW?27|9ig3Lv9T~v=s -OxfqDH{q_LTxK>_hOUv;eO=l`*{gu#>GuChP~{1*Sf#UDu4t9OOXb5Cpp#>C=)(FheqM9|={Y) -(z;^&r@?2$g_OXEVN-Lh6*gn(C_w|AnaEx$8XtrN~|_)2QxELN64PQYliG)z!j|??M!)W)eWRf1E)?& -r9)km^g%2507WR~cL`is=&5VtGd$4NdnH&l#Wu240EpjRrtTW-X!*B1a4qg3gCm)8o1S?0|46NsBFJ#j@}FdrU*&SPV+ttmc&ISE~M^;7J^&v_o8Xu+wHiN&B(tI0{{xbSLrffV#4E+6aoRn#QPNgSwO|wXg}O2=R91}|cN`1J0}^+sF{1_)IlL1>fQI@m5ma@+ao_D{nUpStV`= -zOEroAaE={|uv`9AEo1@t(1+}Gx3H*qGl1i}PiL)!Sf!-Tr`@)Bs$g)~C( -YNEmm(jPwDz~`NfBNyRgGE}5qs?$A(_%_*yyTxyC@lQT6&G8Lg*4VWq@-`SxApVLj?WL%B(9fnVLz?J -_hdb5PMvr2HRA_F>F1^Gg6mea4ZzXDUgC!MNpJ7yDYbCoa_mPy%44y4YJ -K@i!L_E)WHiBoe%??)jPeDu89K(n?S;*O~S!;OB;M>kh#KUC4U?6FPyUhR6|Bkj}Mt}Nf^j-$qq=QaY -;L0YDH*hT{gOR=?&2~*_e5sY7#@W_JzZN{iulIvbTHG&s5NKD|4IvqC(3za9OJ5AXlf+Ku=+-?JXH?g -c&wy00|xPtPKnNDYlt5)kL8HDoS%ojVdrKxy*2E3b-<)LtSBnEEY}XVQSif@1)U7tCAfj4&_2^Vl^5s -hb<~4?m+1!=Q4TPmS*Kb*{!MDroS3z)^KQ%=uXHVop`%R^@PPQ%?0Z5j{ap#TbZm%+Jm%PZ-eGqA(U3 -59!9@l^XoBK68MJBXjt2)eM16X2Wzl{V<_guR^Sl;wf|&}_nlcpo5u^!>xEu=uB_UDeheZuZaFek!Dgi6;nW|H?iS8Q#Gp;=WL(sDh9{`c4`Z -n2Q`n_9MX2zyBNhL@g@nMru9g2ZAy4Es;UD*S9N2sG(-(Cj(K^CfIKs3~)B?uzZv^iAjI!B-h=DhDxT -=L{NgxVn2Lequn?lv7EQ*6}ZiJb<`2L8Nt%PR#7`zinouvqOWw}Z1c -1t{Q>?eO@5Ep?@6%0K90I{sZ+!bXCKjIu&qS+|SD=Ao+UAi#ckumP_&ZDTtff_1wT_DS`m%2s*^hA-7 -cOF_z8s2EADm847^N-)XTm-0|4AzO7UwlWKuuol;+iF -&ZObI$8!;3ZHVP#I&dbb9#w@U%8iKv;7P=JDpL$T-Tz>(4pxfVOF7UhN}(H;2-?}t&@K+U)A2HVZM>| -#Uz;If@5ceu^~Tx{2>U5IOZ=fyDTuEo*>H&y*jC5DF`2=~O+ZcbU6SJNC3Tb`Uu2-OxS*_&bw%C!M1Y -^Ny_?yFHMr^GBVZG)+9Hf;_&+t{IyLOxdtFpyI++mVVdjnHg@M{*kKfC6k(1-lFz=75PG3dm2%az>*x -B5n1nhtbejYl#OL#}l&&nT@o!2zlBq98~kF7gP2p83+x6-ex)^hK_`ATkREQ$JwTOpdIS;{DouzXnXb -EsfZ7j-IfJ;0Dh;RdI2F}gEr?_OQ=Pm_`MC~`6#uAstclSBeOSB(?&bB*|CHiifOw}ogA@M>X!5|?5n -O#F``1n3`~AQ1F$i|L9=qK^E?p5mSJk0Fx7U5yGalvvu=xOVeBvzuG2DDs9KT3wkv^iH#!56JY=KA@S -T0>x2zM%j^NZL`a-0%sZEImA6P?=v=HJjk#??z88%10F#jo*6MTE#V%dQBP=)WMYoKby+%pRkzN4R -8|{yttN!=sg>op8o8a%zXLJbFkhUZV{QQv~@oE&0v1B7MI+6f<={g}I0Fn5U&IgoODA1+`qjb6sg-37 --d{=xp#7RKn4H&gu^321-agsiYPCd=SmmCtw1QC@ULw%LRkFb?^*cZOC^2Cx?^OzyAhHBst%4~4f-Gh -T_JyYEq!$KG&6?Go7tVd&pxiDJ!ZJP!wD)!LES1LRJ&W`kW+{iO8n@}45;p|f4hbj1?ZSkTuhZwX0w{KaIl04`nZD}*}C!S)a(U+q*298O8iJo -5Nyo?2AyrEt8r^h`!%yuj%5(j!?TR-SgB-Us3|r&;seQkxH9k7gnNK8jGudF6(ZJEnJH7mbfj}?;l)o@Z_v%)n(L!7j -;nm%iW)$YO)WZFHrvXq;^j6-?09z|TUVF{sg?7K+{czUXqqK@#;sU=sJl(7d1__Ot%(#Hf(`#K70I-1 -(*Jnk8W4d^LeL2GLK0f%2J3Bf-@m2L`sS?hJK{XTD -${2isPRKGDAL45S&-&Yk>$wE4(sPZUR}&B-2A&wq=Z?!LTm>U&RXMWFiX4i$J>w;BLq3PM+1HUf3tiG7`!+rBqAoH -#F9Hj;+G$NFBF9JwHL-Z|d$qw$O%e#$IfrL}RoR->%3%#kXwRJ6SG9MMYS!1|`Nk!I+ymh^Zwrn`A6^AbR9cmWB|ix|`$*e*X5`^kL)dWU4{RKYRT{TY90!JuS-8*X!* -|7E{DYQGz0q39N`mYg{;7G|W#OLM9hUm-FUbf<;5!Ce2qqj0VJX|L4s8CcPi_4%+98@kvF2i -|Q%dlP``AxL#>0{fLo(?`8-1Gp4!-UhfR+U>L0pWignNFnun?ypBr8E$#d(8#;&-NWnG&`cP#CXHzVteg`iRqlakSoKpnbhj63`-b9itlHT4?&&4kydGCEvaIVW< -fcGT9*nWb+|6Su66o;IYMLGmOV89&e6`h-P^D=*M-_}+SO|ne>8~fhLAARc3+hy|t)_A+k5SHv)EIhy -qcPcRN!xu1&w37_lzieyONxGr>+yfS#ag|(Y^hF3?NlC5v#7df0+SWWgJe6s&0_ -Zk8wLEM>i5Z30d@+$X*1<9DmC_QpSa6P{PVZzR4sArY%?9S>X#=^IE!t8&lXnbDYWx9g3CA#+Dm`zUAUkhek-^IBiilW4+PrrD5A{!yr%lqzlS3XD -XK>PEr-JC+BZ^jwD9DALk`pSF*9lcl>Jl|n$^$byDY;!s`#?L}yJg_+;54WLY-pjGuqUNz#5*1cX;hW -teGDUjI|<_9QalWS5VKpkfwE#<@?ejR2}pZ2)oN}J(nhpS<5*`^DHPQ|;pDqO8y8)Y&dbi@iYIoEy+c -F=qfH4fuVFrBUhpA&cjvftw;9@9L(g|}+Dc7(Q#vFGzPh)}7GX|!YRglZr#E?=;t@}PSt?=Tp8c{NXt -!dR0#rbF?F3s79k8ip*(kObPfjyt-Ie1>N@D!3phN0*DaJC(nkG*MlooP81;s)5_nbwJEDS?O~&C<$;FedN=NpP+A#FiH2@_kruKu9Oo(bPNeWt1`SbefT< -G{lkDd$S&K<%n!uRw^bP4FsqGRbG$?AO)G7uuNcM@V1$>f?ut-W^U0|*)GJ;_*uD(vL>PU -_%izTjKv(^t}-=u4C0Fi3y5VCREGGW*&nKg_Q1gwsKEp&C1}#zlGei{X -9bBdI6RT`-jZaWRI;I7mKrC~@Fj)EV5~vYFK7x3HYI5C-cs(L6r3E>nE3o838vaYXpexYO7LvL5@sx& -DDuFADVK$$r{2M!x1fDGrBKMj39pqNsf9}mIy_-d0?EXH90y9IDMINuhq4+*a -|F^3^Tfb--!2geft9HFBVY4fy0j1>`Sr?-;?Eu=|zaDpQf{F$gxCr28B;1;k38S2$JvI1}jP}5`$ClN -WB{Lq1e0$`GRCS8FEzziVvBx=NTKD84In$Hmum}OOke6nI(u$s4k^v^ndQ -kRGu-~L~88*L=4JU9URRyW*gOa&TLTw_iO9Ww|dJK$`6!ZfQD_~j!?mTRmka!ctOhDEo`lhK8$pSAts -{3gO}yScPxg3O58sJT^z)~Jb{^*yY8v&`oF0Jig -9aO9U@S>CTO{JP|yI3accNRK7LX2bz&8?GT0_MiY85c_Elm=bs5ILQtUd!~qgk^(#63X(2Nf|_m#=FM -Gn$CS~H29t`^&6pl%hLTQ4;H^n<6}ikFPf1+?PD$Y=acDv>C5ScCueXicUG`ur{5o8lxR&xE%_-~sgd -8{p4+8`zW|Go%n%gLTCuw1t5m?-$@Jw`^Q;jiXW?btG8iQfCKmnr*{f)^~dEtd&Iz4<#!f;JhFP!gH-rOUlNi&6Afv(Y_S_gv1RHUealtP}UQiJwc< -1R3uo^+a!%9);*gk)2jlf8G$TLgYR5xfAV1x9yYf-G4u(pltId-asoY*Zfe3$Wtcb9-V&Jvi7;+9%vt -`-D;~_7d`V8v5;ivhPnqk1U=Mbb!x=$|mDrq7jUE)y3ibHzILt!v_9N!{vj$^_HlntJ*lSJ!dqEp -bqHqShUkcX^qb%EqSjasV@exrS!MpTOHJOx#PmvXB~lAWOBr0bJNC -JNh}sQP)gi4s~R+4*SR?y+XekhGoVm)OUnZj;Z!JLy*sJFIEO7F9vD&0LWZAH~C_f&aozaB>luV2NgW -#G2FORl_g4tkpJ!AB0-!pa~zAq=~?i6RbZ#7>Up-S&O;6v$^RQayKZFq!9Q{gk!;uCLQTK9L;Wz6N}e --7z1*&N#Q(bkR=E+hbcziMaVwP6;um@u3~t&k8GS=Z<8pVgA#fUol9^D<_aS=&!XQxA<+}6wnpd|44( -jON~Z$JO2Wo|6F539=g8XQ1T%U`VuOHQ^CLaW{9!`VN9>T@M?&aYzPS)4o+z7372x4LgLHIMfg4!l8#=|+Y4H+( -=HqpX*5r5umMBor7{qjpKIQyYY0p`J+aSxPMVZo77n^ -TEyO4ajkbVi*9W}2n`%=uzW!%?0zSJbfb}$XiWhG}%$a6N5(wJXXSq<>7%|2N(SFBF})ss5{H`F5`rdlFV*k){?mh^AK>-oj~&)O6F -l+!ls6mtArgV)+U;?iZ&!9=#oSrl$?Y}?zovEInZjbKneYsXj~IoC6VSOM9Dt|~eHZBN0)4M-nMM@?GEDzi)cfgamDo8-p&(7j6#UreLTWoTmi$ -;)ZY(+$TfSi9f4Iv)|9P(2LwXK;@ph{n#1bjxaxYm;&OfBRMC35Jo9Mw@oPQXIy%K6 -Qh_?wy3~02ZG)toYl!Mv5HBqtX@cxv;=mthLw+>@DnaUH%-c5|1Ko$h4A~kWe^t1mT24?LMsZS4eD>{TJo@&v_2+cS1BHWeCe^|)$R``>W;DtD^p=g)4`E|8m}H6pQKC$-y(;Yu-dmoffj6*ovh-970r5~~!$4M&3JV%tgb4C+|X9|q7Y%|u -BHdf5^3h7Wi(l(utP%O9^|!Tmt)!_wV0rxBx;s0B&UR8(R9twOiz3}PY<3#-n`e3m<&ir2l&$f1?i^L -Vo1NOjwYqXRmo*Yj{VYDriQPfDXJx>O -SZMeGOR--!fkdK%H=hZc|G?!K0tLVb(e|O<+S_y>(=N+5Z=n}v -YH3S*B1{7&`xZ=OHw>+4L+qxagEUT}UeFAI#PE0$QP+!Hi4b1$EaLBiztG8nO@P;99r87Wc?J|`l2xU -Wnx2vGmPty@z=wW$c8jh!zwix(Hzhoddnz>undqI_u)o1CfmX<7o3@T^#DoNofDc+?5+a*#wm) -}HySS@~K1xEEgp-|FLC~Yn?rXU@-e&ZK{@TJ5K#w9ue(F|0$;iZkIY|!DIa>f`i%DScvXh0gp-4hz89 -w@XTs7yH;0)JYBP{5G)XR1X2#BsI4OnF~C(Oj3J4v*p~m_vUSQ~^PwA`gjQA_PTx^3*`Hl8ECbg~D{U -9&;9tOt0D5MWWL+B#}vt0g<4qgOHZiLB!EmCc>txKjN~ZK_CkR0?ARA^pcj-gWm+ew3~{Cc0%jq<((( -C@x$5zL*TUE@;zy2}mSCin#(yX>_I(ko9I#aBF$mp0HY!< -+gr0OlJ5pj29=jroowVYT_;xv8n6Wk4Yzf(kV}vl1a&!+=~(2+erP*BSc0zgOk*h(DKuB!7_WCwX9v1 -E<@C@_{u3P_f#Q)MmJ^+vGa+ZYI2S`W4!l2zX1>qeBD^$zO9~E6ok;!;fN!m#wA4w=Hkts6<{Tebjr)?6IRnUFjbL?%#C+SeL>8kXWtFwjjlc)F%Sb#6f1kgB|yOCErly;U>vSN`F1$|B1E -yz%k3NO5=fLA6-eF`{@5U8%xtC4pz4s+!AT&kC(u{Nh?jL-FZf>bDbc>?H#5Zb-qkm3&2L+;jfBqR$r -Y+!0*B#5*I@hZ1Oix9-t$=Rj>x(|4|IpxVc!sjilaBJ%xJ{UC`&q)(1alHgIB=x{;$SU}IT`E`Jo7^O*>fxeO>gTr5AfzM2iZBP1Qd3laoPz6Yf=w%OCZ@32It9W;to{+8{B=ME(OrTYKJ-p!T0Vr5 -x%?=Z!y9?fm+Y{SZvmbHr?c7)h|`Df~omW?!aG{WT?PJgguESYZo!8y)yh8;x!o&hK;dEp&75dmeZaKQN%U|wd>f8y!ano{TLDy-Fp1QGcMjt*1Oi -);O~HqOPJaG5>}3|O%GuXDZ0~QTX&?Z#WH*K^=h%95lj<5U7?WKoyHD9wGBD1#30Igy$uxOq&kW47Cl -ppeFQix7`2Kv!*78|>0&x8a=K@@zaS3Bfg?*A-ErEiGSFKkgR$*NTnZ=Hlxbdoyc^b=r|=PKVKVM|>>L}+f_a>yRxA -27mcSIbrkO^*RknUGs^sO6xnm*yOcKMFfO>|Q9CV|t-M`P*%%7Q+>0aHp@bJ@$X`nZsqc94p%hXb*oE -8D~PL+!VY}!eoqQk&Ydj-dEwH4Ny7(btY8tlv!B663B}F@cLG)eX$CZjNYtBz!DFWo5~f+3cPwyw9OSy#Dc -pi+42)l@4m|Bfg?|eURDJNT${44Lc>nX5ENFlq_d0eEXt`U{5o&jpaKhbi_9`EvRTshpIEoSI~9umiRQqukr+dC{#WKzQmYCo_BI;m<#1nVJplVO?Itk(2$8P8qDkm! -4`k^;NGwbT#-m0!^;Sk>t3OO4$nDHU1gLJUhrOl%%0_4EDNb3-rAz*XvUmYdBahJO99ewmnYA1ff$2#3ar -s6MPtC=H%O(~{Ss+NstkWmnoVEh6Wz$$+->ox`Qg!sb@Gz5vMs7P%i3QCYx{`J!b!OD*hs~hgXO&2Bh%G-7*tKaCEOxbKfu}yW*JA@JJUD$bglig@o5ZEFWeK#kP8 -#Q5;s}R5aFnfl#}?7y+Q7A@U3-W~Rs?G&c(0yo9ktDJYLbppagq>m8n2y!{|EEyuhjjn2vC){`r4gMt -{|#&Hq3n5aXtNaHZv40$0tQNm;>Evr8J#B;5`YX;L}MNfL4YBNvM`z1PJ(|Li!l6Xo|1en>4A+@mXb5zpLYfdY6Ma?U%c-b<>SPe2^D?U*7D%K~)R5_XRD;_H -Q+$coYyv4m-XEA8r>=@O1&;{4u?3CfvX-lQyu5zerFl_Fq+cdyKn^;~P1UOGdJA)$KH(DGKJfj8PKko9+{NK -4`FT?vzmWJo4Cd&(sYy@xb9)?s56olZ`BE_ST66H*^>ClLuw?{R_mo&FY_%i9A%uroXoHzh6I1$~He$ -L5%AMR;s)g5VG+cYa_-4EZeY_l${HUds~?-L26LeE=#mB9hYQ08yu>Bm8?;{0VFt -(kVNC@oeL?lR;_`YD2ciuS+KM_Z0l)4!Yh=HgeU?3vu>Bdlri^LG);5G?#*RtC&Srduq2yvEgu6W%3i -hshV2M{VHSK&l -_^&V6i9UBomRx+@*zgmsE6zjj`$;Fq?KcmbO&TNDMP)(R`bSjS669D(Dv#8mtK~rxA$3AdMcprRrsc` -WRmLvKHyVo82#Ul~fCRp$vGWIX&j&5dcXm(TdbPm?(&XvzzA9rV4ffDqKoqf}51ACl`bweaYo58Ax!e -|G)#oZo2-yq7=Aq4TO|w`J5_Uab9LC-DmUOHea)mLmjkxJ_X=1k0s*6I29-u-MkS80{qH -eI4@!6nbJ!Iy4=5!dI3F@g>&#F3`w0A&gI5$5`2wNC@I}30({x|IXaP5NFYUnD1Lg=^0vggGp^Hs2_E -Z(Yq2k$1vi^F@OUL14nEOUS872rhcWFD77#=}D0yIv4!azgE#r47T}J@Jsn1%ERxZJ+LE{dV2s@ -_~MfU4C}ig$rSd@UI+J-jj2d>p?QeSF>lCYr&=f)0*56%HMni-k;)TZ^ruo9- -RrZL)MKl-UQbVsffEm}F+#zew8=X+!(}uusN1dZ -bo!ogMsC5eIJPFD3P>FHqW-HprL(v|zB*p6#6WS@VAuv5@!-A+c^}wYthMxKCUR{(5Ob>tgx3>j(bc= -!T#&yH_I(SOngiW#O@BP$itN;ZX;kbUS;^FXzES0{j4tCuZ%ccm%8Jt46DVCLv^G2=9U++Yp%4032Q5 -*2ciU)x+n`-^jRU(~Cr_2I{2axRJ;`pK!Q!vGY0!E0R$|=+P^+*ouWpWW<{IDA3`Opn>F;J7hny#ilW4V+Vb>)eCc*#mF94`O -B@YH>QE2xO3S4dz=L31y?Dh{qC)u6sN8?A~oXJF>&=Tt6ltHv4*N_HNLks{lhM*XdldMu#iu=TJMK{R -sLFH2W=;88UFTO@S2nuIh`zB(Ovn<)-c6Xj>gpy97En7gHcr>ZDIm% ->{4mlQ0m`}L?lSlHd)^gDE8m6aMElR$il2hUI~s&Z^S8sui>;TfMx_-K26MK?HQ#ATr##;bBqW$AvzB -V>djQowF0!<)U44=h_yWEp|4uzT`ChnNe)HpTd>23bC@QCk$9HzayI+8dP>;Va@6aWAK2mm&gW=S8l+x8j -*005B&001=r003}la4%nWWo~3|axZdaadl;LbaO9oVPk7yXJvCQV`yP=WMyev^27bQlvpr3DjS|kGAY=8h5d)lQf)peDBR@y4 -~v!4M&fYIZY?YXr7H|I9()v(Jykloi45RObaV_QVT_iDVU_A%KVT@iOAR;<%mU{bLvD(FOm7c&#cUWE -t!)16%6Fh*}GgE7@?}FwakT=Mmv1kgR#dC0MpXgQ#`jUJQuWWj)?EC5;kBR8eFya)Jtw?@cHu}fVi3- -QLGfDA=gpLj_orSCnbr|-pXz5jqSOcKM^6C3HzOvQthm)=p%ulCvRxk_`T7sepsXA)(YDMmrBb_&XoQ -I#O|pvHRVE+JdE%pbn%<9sIN>R%O-?sT|iD??b%lD?BU~lO;dJYN{_7BLd~+?Dw(5rDs$G3NjN -%$nA^WPH4a3wmI#dv;E(R!6HA0qKSmx?#_ip;K2pc$Hd^)J -NPs%*P85q4WiFL|dlXaapTA5RgmXCabTD^@`&86Mc)*G@h@X?vV?taf$|>kYsgG62OpUh4u}Y17J2vl -dq_Vhm+}K^%UrjC#(4=%V@kvDW+wdt|rNP8mF{er^`h)iik4iv&X^BYc5|rE&*8?SXrD -g?CjioxBTnXpe;E7@HI&mxv>Nt=zt+SzP+j#b&l!*jhbdYgX94qwrxYW2?O%N6OpH*DY4z7zce_ -J8>oM|wX&SS(17;6>Vw{if~aMvIDnmT1}6R?~;m6(2f(15ir?1QY-O00;m!mS#!glC36d5&!@%GyniK -0001RX>c!Jc4cm4Z*nhkWpQ<7b98eraA9L>VP|D?FJow7a%5$6FKA_Aa%FdLa&KZ~axQRry<2T_+r|< -8u3vGLKZu-?tmM>f>eS6pvSf22${Iz^q_MvFxm|AXk4}pWPp^xzN{Za7s5H7Vv6^j|z%FH78 -cZUzIFp(gRa-N!(*Y=8t@<#zd;9MFof -=<#R3FASH{+|jk3V58G>x~gpHND&Rhob>q$+h@ZNNAPOwVu5-eSl2baFYl`$*hhOzy7EZ*SGbyBjrD* -W;VJ$=UnM@r}BEe{=or_Iw17Gsdf!l(|pIjf&?WnHA6^TwW)s712M!ycP_mv0Ca!12abkF%m0{h*)jD -%Z6!@FIZk|X%+gDO6Dprs)1UU3490Mw_{yUY3)0J(?_F$dU1k9I)6xEf?I6ANai4YkrqWcP^X2hXz6r -}y~oc_PL78sKOCPZ_5K!5p88SeSFgV@l{`B*n3qMSMlMsV%3=oTVLb|=Gd#RC)ipo#3lZ%VENcBCb5& -GRc%pgDNhZk`&B|;=o+QiGrC!|^Wqja0(H#{R#^y(ew5Y0enr_H8#UfAs169L|^F^%}h8#uvYzC96Nk -ty4)zYMtTpTSjK4~2u>rMLB%u2JS@HMGhW)DbpjfIogXuGxC=S{~8*-LTa6%)e6G|c5zS!KQ;lqiN|J -LROq$?d!0k3YWn+wjLjWTby47Sg%*^m?7-aj~|;ljkp9(1Ocikwl#pErH==|B5feOh>~d|4l1?{&{N= -o-oU&1zd^<9=1G_j>!8Fr?4CJ4n5=*Zzjp152_VNr3q{euSP0@o_1MBhTP_a5?Qq_kYMI>?8+-rJekS -X_&1-<*#HU$6TvYf71Bls&y(E1%yG;na0LyX0xCXbA$ArNS;t>~JRTl@`P&6$SzBaAWr>ZDZ*y`iwqS --TVd?eSAhY>pqN<@?nJAfasK<$p5X4DjS-Rl(qmK#xu*)Na{X$nY1cml?*a0&UF%$oVd^J=~Ba=$cs=O1PuZ>GG%V!0diT2n5n78G=4$GY -h8g!l!lbU6}4lmsnI#Ok%Hl3kC|*D0Vk}MdSxo-IqUdx{z$D9T#1b@HtueMe2Hr>RSn9Q{L4mV -xaOmqcXwVo#zX}YocSQ{ZaJfynyW7=vb$=P=}Asi)iy6gR#>dx-|WyukE_oVRH^hM-d)#tL4$lN3%Cvs`q+STi2(uqbA$N3fXMeX4p+_S1nDBHLmkc8?IsB=Mhlz@Aa95pFIx|ed0FiK2K2 -Cf|Hr4B(zNr>(tL#cfm$qaWPQ(lyrP6t5*`e122X$wU$X0-LpA)F4{AnkcDnj7)znNy($2a-#lZGFlvPm?dA -u=vQ^_t+>jy0=W}W|$eI9~bMg_2_+=sq;&Cyh -eS%Ha6M?E3?`GDBZmmmis5}cc#t&3B7s_w^R!8dO{M_|-6$he1wBJ^fvndcx1k_6W#L7fPFxdh+!LQZ -_ew}5rYzz*qOL{QL#bRtD~=IyQJkel$c>z&4z|rDNg0i8-gl)6k}A -}KbP+i9irN*Ic|d51$QBtq*}aPIbz0{>5(l?;9>6ypIxnNz^&!bA%}wcVPgxM$7Hz@~f>7iL$>#*5L^ -XvQe{FB0Bt2k6)l3S){5n82hAkujxdaLMHFvMGI!PUDcF=H}JfWr{WzA;#K}22Yq#PD==fNODuo%j9> -h3BbgxH)*v1&y&O<-p81;0Q>^nW$Y=-5L%s_YCaB*PN<-^iVP~U{%>jsvz5nv28@zFF4N0NM4p=9M%Q;TSTZGjg2Tr8)voK -n4n0h&=0dF9STVk!y05Tt1&?1(bh5sr>>$dt~YVz^&LVRUt?4QYDE>;p%mDM`NcguPHpZ0M&luCmZbf -)JIfv@{QsnSNjuSt_YPN{dtw@}PD?PJqIzFi>ZU-vfmXd}M4vS6Cwi^rHi=g+Yw}Nwu##V<8;Ieup4D -xR_jxFYkXDU#M5=_>0j8SMP47;ggwLGCiNlgV--=o*1{}>iv~GnENIE$^6(YxjvV_fAA~(gFm|kcei6 -Shy3V;UlTn03rRTz9LUfwx&G#{>83if9Wg~P&Ebi!=sXzlz&bO7Ld;2ifGkGHy`)j5EmHcAYYBHr^Nf -$C*hG3dxw;n#&fbo1?q5%C@5WbWB;1Ryvn=lOzw!#a#*i@&Vrf5<(|51WFGK#2)lLo${yDjQ59Z&VpW -d8*xW5`t=~d`o2bb^OOwM+fbe!_w;2=tovpP}N>r}@6%L9Viv6-v;duXM)zwc3MbkFIhvwqZF)hyz4d -Y?Hpx_t0TT_LM)uMwqxWeFm|RK5N|!wGxx)v|-uqo`Ts9kWmVpXDY -7PWPFZ4hkFuJMj@QFdyOQ;hsgvsbwa1*+ld!MKkIN~26q9C8S#Y3ZfmI!Tt0m%AolW`J)QfV+%EUB|Y -teDW)Gq>i@V1e|r>v-sAu1&yYh>%6_x458}qrAwU#QniN@ -*M?*E7d_|s1k$&gyWm{>DPKUCrAFBERGki*8M+b;gLSWoMsA45qxPfgVs$i7N9d(=wmRZNkEZnuY33m -eLjrc_C}_k6G7IjWND=Pps6FGFuTa4^aB{IGyeo*RB{eg-nJ1OY6tYMY^E)Y5!R;M(%1I--DD(;NBDXv^Q`MViWdj$Bl<7cEWnVB%1slfD{@ztcwv9F~ZL6DQ+n^Dd>MD&i_8hR*mnX0{Qhz)NO(4)GRLAr0l5sbxCV7`H -mo@zWOuREBf*={Fsmb7Abv|&B!%23~rQ|*+p%`ysuA8aaW`XV?97E?{BEui*Z0Vl+7wS0b)OWWzEu)@ -=E)*y(5)0x?yVq|_2&;7|eUk};A%!!6>(;w6G7@bc{qP&s0A4MtCw-1N?;g-JYOONcw7!F3B)k9rBo7 ->MD?(lP3i_w?+;L#JtBRsppc<;*vZM;cw>_Om@%OyEgSCY?aiOmC0woyuk|%sJ0EZL2F_pvFG&_JJ1m -vy?*~~*%NY=SB_oXgN;yC5bRlJ`D-ZWC-NbXckx{Kf%>CihNfI-$P~XGsTA_evwTi#RHi>y4J0j9L41W5A;_a?KfkWL0k5XOF}yoM@NBl&BZt=WO5InhyrEV2<yiUEB0RbvinGWi141GJ%sQU9(jCpQ!C?DQ(@v+`;-4pbD+F@SzuJKHE5Wb_{-_NFyX0Pv|zJ3}$e@Q$rf1f`MPhN&0_4yN&`% ->OLN$soM=O16Glf669%eBmRn}Kf^;Q2lQwrk|)PhX*jj%M0wMX?N^D)jtLxG)CQ?*gR8HKOOdH{H42e+^ID -`2fN(lZSOQuW@%FG+y`SOr>Z=VNgPd;gPO%OY&~l=y+4A7U$bObVH$$}c%_~gI=e -Kcy1RqJ&`~D7ob4#9LS^OKw|2xDj4cUe@I^_hNq#Gu?Cl%kaM^9IW_ta-@iN>u)7jbD4^`6~{13G|K> -a$2*&{ -RFws|JQ-cu<1J{`NoRABP;q8-MUWc&rbL3zM1+4b$4=vfU%wAaAWu*Q@T@qa2%JaTvQUhO;NxBaxum;VJ&O9KQH0000805+CpNtdsh4!%|Z0Mobt05AXm0B~t -=FJE?LZe(wAFLGsZb!BsOb1!gVV{2h&WpgiMXkl_>WppoUX>(?BWpOTWd6m6guO+vYrg?vUMF7G8HY% -;&{923bLN_3*?HjWvVh$uGake;qx#w9-!Pnw=P0?_TFSL$d`+2z3V08qmTaff7 -dTR`^V3Jd;R+JPe1$Zm!Dm~{PpiX{lBmO#cw})`sn)V&(GKI-o5&lS8ravzCL~X&GYr@{q>u-|Ie$}u -V3!Jzx(coH?O{a`Qh!m>+9FAp5J`9zTUt2`01yjpTBwa;ngnr56^#je*O0Q=XclJZ?6x3qt5;N-#&kM -|Mko7pZ|Z=zIyxS{r>aa_ix|%$FBJL{o8lv^RM4Mzx?q0&Gn}rt?@tJ>g7A?`u@j#t3Q9Ze)j3lud>$ -MRs7ALe|&xN`t|h%zkPpw@%;Yz-Cv%+IZyKG+czKHz53G+`!Rp^{^$RBe{x;FdHL>Nuiw9V{V(rceRu -uh+AaKWeRzBQ{>Kl0e*5Nr{@qotU;XLb%XdH0Yu~*bqo{?prcyZm==zj^iTk9_pQn{W2x(CQD*@4kEgQy>20Z~t)p`uY8C>GhA#Z=T=nr}_O4e|r7u> -s|QQufBeM^ZxmI`TqKz&))y}uRZlE8uI1ckn2}(clGw}-(SzK_V?WTFVFAZ?@zA!arxS|EdJzqb(fByD|*DrPr`?uG -B|NN`}_PalPb$#;NKVJX+lP|vbBqzI*%K_3}>)WPSW*Q5i78f{SUQTtCH7Re|j4}FYelYjGV9>7r@>6Bkf -4Y8FesTZt^Zj|L9}leA7yB`GC;rE>Z}yL9(k@t6jK7QeceDN7j9=^ze{RKPox6Yap;`3kr;j7Po_uGH|;*y5>K``tT -@7vpffI5m9o%U^%?&tHE2KR(+fdW&hrl=t|2b)gLdmu`OKU#Z_kAm%(N3FX|dEYN@&ZHOG -sq&GMr0S#OX2qCH~1=<$dbt;BfIM%g8>+$Em&ALYfatiGtZOYUFgi&9p5QEQDCe_Skn_>qW^2&2Rg6wa#7n>Rcud(z)!GJ -o;U4pHKUb`l3Aco%UrH+MjV*TsoKi^u0AMyQ0VLv|@2ue&tM_VLxFV_RTo;Mt`d1VIS=l#lCTgUGHvw -DFKWM@TA_1UGJdepyVE?J!BLg6dx2H6dx2H6dx2H+%PB>6k}qQphWaj@7m+W%-$DD -1|@@%LCK_KQZgwCrVE%ZDGK)(4dz=gqv!6!-H!)n^)a7z7lFau$})Fj3OL)^w7m&Yy&4p*!h~Mj2;dg7>$zA1snA5-ebL`Gy9yx^;R;*$0tc~9X#& -!2#!*=aq+r_D^VT(rDlc<5wJ$3^NbnHXT`m}$q0ok=Bbp-YyceD0qw33-u?CxbnefJk;_S$f6bjCIZ6 -_bMntjDg`IiDK#VVlEk6_}9MHMkTl_nz(7?!jFJw$nvpkHh}4OUmH0&cCpy)JuO+?_Nuuu)_OeD=+>F -BfRl@wPn2xqfKKjE{z)k)1YlvrP!W7dxdZzJ2-3g@r0G2Ng5t>mHS_#vJQd_rc=S -ZU0`oN(Oqxbc5>L%J?s~AeB*tv810g+v2NLKg=e;S*j4tWox#0!j}*-O)ylzd8};eJV(ow0gEX~LN3o -On!r8E_x3C^F8|c;D6pArTu$@I4cjJNefB&&dR%~d+hSqIE_oZ>^Tm~0<+D>)}OzPbeY|j#UgjQmtF7 -9BxbB|kPnbs0r3@7XSh3UI8Xl1ubdYZ8e`fGo)JBZ4$=KBF@xjixu&iaFKV^wPmF{XE0H|p4Zx#8nLv -62He4CBVS3pG8md%8Wq-j#G(aKm=i`TZF9JB&R;fhV!YpC3?s05K-QE<~gEyx32)|D_oy2aoiFy+or& -96am=c=vu7#Nr`D(a`^0V3fusq0VeR0uGmM?CZ4(!cN!p9E2CI+T(HX5>B@JWst ->fTq;Fr|FPg#H~3owr102KSeqp<*2M}`HMu^)Q271LjX5-hxxg}27tUi=b_>%LHu1-SZXEWnioxUv9a -r}l-CETffWv|>2G=)O>5QevKQ8~7!-#RCU&Unt4qRdLs_x&!xeUnps>vS{E^4}yV#8#)+*QSIM#{E~r -R(l9v27XDyV{72Dfy)Li8@OZ%*cVFTsWt -G}z+(fC4LmmR*uY~0kL|V(J098fg3AUj8@O!Xvf-&UAldNL8c=LNu>r*f6dO=%cxnw#t>LM)8)vcolK -lrIJ0-zN!&7Tuq=AtJMj9AtV5EVO21Xhf!Km#ECBR6FGj6f{l7Wfb7fOPahLhI7NCP7cj5ILPz(@ll4 -UFLP54`!oV!tnx$TMy+7&ov&ymxuK=tgpTmd*Xs{Vy-u2Vxu=lesb&jqqqMFqpF4rfp7fBHFQMfnTzi -YODD%jhQ;DGr*f6dO>)sO<}nw9dM@qmbQRAlZOq1Cn?R`$9<|*_fmqD0ZOOfno= -W_=5W~xCEEva_mlSzhwVGNg&yQBp&O&P!dRXYzE8YzEBb?BlHSx&4y;2PJ`I2a+8~b|Be-WXEiEpxA+82Z|jicA(gSVh4%@r=#Egdg7OKAlZQ=F4VqI5=eF+* -)dxkD0a+N$82?c?G8pd80lc7gONV(ahr@ASm|H|hkjou2}U{?>0qRTkq$;W7{R3<1B?tXGQh|HBjfhh -=k`nXAA?VVOLCc9C<#VjJqAp~z>y!WWv(j&j0`X`z{mh2KFoNr{FO&cyqn&Y^+b`LFP!g;Rure@P1B?tXGQh|HBLj>KFoJ`%FO&cy1B?tXGR7IVx&4y;2PM -JE04oEtHNeOKBLj>KFhaDlFWd`^3^0P@v@etZBO}hZP5hDpRt8uZU}a#o1{fJ&WPp(YMg|xeU}S(1Xp -ViM1Q;1$WZdz^#4j0OWq=iesC}U%7#Uy$Z+>4W2}TAO8DM09kpV_@9bD|=agQ5*Nr06AD*;vldlFy-U -TokF4|uKt1vDgdU{i+iIpNtb9@ERhb)nt`UxHsUa6g9DQNtKuB)~|3kpLq^e*40`z(|0R03!iL0*pjE -+DMgoik7zr?f)!!FNfRO+r0Y(Chz-3u4eo26p04o7j0(%l*B)|x6?!HhGj06} -7FcM${f^T0a0Y>8H9~OQ|fRz9%0agNg5@002NPv+5BLPMNj06}7FcM%Sz(|0RxZ{h3UlL#?zzRXhfSl -VGN`etNIM}U0Xg-LZ2a)g4sSGd@U?jju-0{%DFA1;`U?so`G~~V%E|p8;(zy&S!6ms&E|dTx2}bh7g* -#%{;|AZhFO&o;i9Ja$l3*mkNP>|BBMC+lj3gLIFp^*-!AKq+5#oH;3sw@WBv?u8NrI6CBMC+lj3gLIF -amqHCnF`mNP>|BBiZ|@7r!LIN`jRHD~UaUFWEDgl3*mkNP>|BBMC+lj3gLIFp^*-<8kW6FG;YHU?ssy -VowM)2QjfJhm5Ljy->;0SF -iLmE1!5(ub$p(I#I>`8)=1S1JXCK#DuWP*_iM&O9{WpD{D$>oe&!7rI$WrCFnRwnjjf{_VECK# -DuWP*_iMkW}6h}#!RfRPDC=J88*fWOBLs(4>0305Zd1U6$^R7!%82}ULunP6mskqJg77@1&Xf{}Url2 -Y(XCRmwZWrCH7J(*yH7<*qR2}ULunP6mskqJg77@1&Xf{}Url2Y(XCRl+&+!sn>PbL_dU}S=k2}a=W_ -Jwf?q%?dTg5L~U}b@o1y&aJ1QvB)C<#W$OAMI8fu}$4@CP3L(8FI~1ZH)tPz{mn43ydr3pmaoixyaq60=^=29>y>62M=?NW{A@n4J688#k5k^D=L0hQw$acdZbrSnu{rF8tI=Fj;VV3S3zsl55U -NQfh?&;bNsLGEPKhg?Nx|e)Sk#M!r$D`45Nw5cbYYb)eC>)y1=F-!x$A8(Zq* -F=MQR@Itujrlcl#v;KdP`~Hu9SV%iVBk8J9Bhaap^# -04(2U{9MVJ>t@fwB$u3cVTj^x2aPA3cHR<1YY2B8g}X;V7zEYVL>Zc%!2hOENz8QA<98AxjU)y!-MbJ -)!h#)&cmD^^};AtX#Wbt3c1%p^jtB54L|N8=BSv#>cZ}-7Uoh9|GV)vpBe;EP-TS&FU;|RJ6geIA)YU -+(H9F(!6B@$?-#CYC3q>gE*Bv{SvStM&qZF(C!o=t?}qbukgWu4lN{|JH;9S=o}#ckUkvEAOnZ=Dh4r -X7j?^3EaR|Aq*;P2Z!eUfdC<~!!kWsJjM&)7lh+E^9cOu$q60Z3)0d8))voZBl$q-kpX@M@hh(am};{ -v-?$VL}?wVH5yn1gZlnx|u~GLr~8Yd?9i-6HZhi4k -kPEBlaGDaPX+G@{0sOc^#y5CO$ie)7M9Thy;+6{SO$+Erx3&yc~|l$7xZH-_q^C~^0p6{IhfEkoMlcc -jY`G5RA*EHg`jqjT^23JTwy)1nQCGI*Q^jX)po}LJ5I)~Gi0P|U`Gr7dLi -FZ`VqhYEW8%7r2zm6@g{wUAi&8i>?REN8Mi%2o~oH#v;8O$t~lVtKG1TK2(`NU?WOo4&(O$ckz?DPR_ -LXNiy;JgKlt5o@>7M+#1m$ElLKa2k<_~2P76*%EfVicm=x@bgj91`0%CjqR0x5KWQX-`zoY`hf;U?Tg -D-AV5g`eGuNtcV1NMYq@ZFCmmJ5K9GJPzk-Fqt}@9F -SdR-adGdu%ejIJt=;xBT(1ra^Q;#;PTqE%&W42OO?#qbk3=xbGxy#IW$e*VRL2dbvOu{O7ZNzug11}8 -(f94~=;#Kq4@97FN|k(UfhwuTS9v&b8PEr=z#jJ2T)`3BxF{1mRp{#rCe3=cUs4D#E7`vm$Jex17qh` -BvA@8#DRDeC=1=Nu?0Zcory{B5oVx2pU!sNqKB|DE3renFEH82j#c7SDxO5`aYCxm0S4g#Vvdfit_iEZ{Mr0nH!uC&y15kMy51~@)*yj3h -ZPNMj^1!n3xB{^2&nC{Nu|bMG+>Z4)^`d(vsnhT%no&hlgoYD=um?#JEn-pBSp~_YKPvIf1!Z)x`qeP -(JNa%@(ofYn>)N2&Nzt4OuwExzb0Ia~ST~ExF2G($fwp>l8XDa-s_oQ^?v+@yVYDm6K;fQ+oKYq6tS~ -*LtKID^8VLGG*H`j-p`r>0_JWeDE$;r^eo4K5kD(D_JToC=A!fed`3tmD;|>*vtigp|y0XYAD-^}WdR -6P=?%(Z~v}vG1L&Iu7W-4MfcjEUePA_6YtFDlBlirl2Yja=4CtXeRo -7aD_Wp7o-ldu`0#%ZSc3l6zKkhNUUBboPSPq51BZG6s(X<+Z%9Zl~(0dDk1*$yDmeJf5yJ54w@5S@73 -Skb#8-U2!5KTqx%3h2#K5N}+72XT7NCUN5-bVweP9Qu0JM9VZF%!UY*X5mRK{DhiJa$`wHZJ{W5wtBI -;G>fL@xZOW^7T;mJWAGI3s*o_2d?Jxlg)Ux^jCsy}@s<4s@zH}7w@XA%)D=DYSVpLJ>)FEktUoZ{b9( -s0x5hNHOJVAxi!~5eDAX_Mk)pVt)cl#xXZGaOUYUk-(BPT~lah3Bl_{0x`0c2wvEv-1gu$YLyNIlZ$2 -fyfE+wnh*Be5c3$b#;Pe_gI`{a9Vr5RH!=4+>zS3vSBgT8i?>dbeNFA(09zX+bGfLGlHgP+9gX#=XWN -W3VyUC*w`>NY`-B03JSR+{)r&z1uITS}alr1&Km+-NKK71;fm0w%pG^-^e9R2N|Uwgc&Bfl3uIz_>&9 -({ux8oT`y=k22W++IA3040;!RYN=|!Z-ox6xH{K7zW$lV=w2)HqH0Ln -QQ|@|F_LJK$VWKkhO)3?`0FISH8rkV!0T(WZ_D(9 -wU+}Zc|biOrU)k^g?fzhx3QpL^3+2i&VSyF4i|(4gLF~u2>^Th6MMrR%2T(O{zw^MF&SRNYW4a|2!<% -Y413E2`=J8SFTeB;gnMo5)0zJ9I{l_19$XroI51c7h=EsQxD!SKF)vwc`=xl1yOLm760T?`ORSJ& -rK{~>a!R2@Pl0|LaMwjULJLq03TJ;N^Zc%1HRvsk>&qtR{Su@=*17fK@I;gKrjDpnsSsSJxB-i -Rs56GAjxC>E}M@}?EtYniTZnm%>jZaAx0J%sR>`R2|pwpo>tdB^hSk)&{gMlzN}VU^;!X%17ors&t)C4S*n%a*V22-)bAWwm&7B -g0_}~oPYh6OD$x^tX46qStv>?my+g@wA0HWOS}>HQsSiDel@z+t4hb7UgcSImx>-EEy6xbx3 -&;j4|!S)z8fe=gjD407jbu~x1B`yS|I@%5l*1@g=@xfJI-umZ%Hh{>Np{yZW=%Q~SvmR&=jLh5?J*(ln;LoaXxh9U*CAWP$-T#nJ -Wq*!o8Dt4s~jfaJM(4Ro(nx&uXAH^77>EXvN&VH9*L6%M65oQ=}FQfy}d1}M6>kW%f&!*rjp>%}lt$D -_gM6(`sM8&4w(MPrDw;N4g#@t)4=1H=X8+1F9gyK38x>63`4-wDMm(B(@P0AM -mw>{Tv~6i|Lw%0_$7G%vWE``%4j<@kO1N6e7|3uFJ>T&gy4MzlILG-S1b|DKZpKz`-z9wU -lrTtVpo}v0wP`Bix&rrB9yf1q4l)0o^^ntKD?xCa4Ns)Dz1sKrcY?w+8Akq)#bH}mhUP(q -`ryLo6dT1xMYk@djJ~8qz=5Ui3kxrUYbn(U=#}T{g*^ge)y%w`b<}%s8Q!uX8kOfy6&WeQxI+iN6uw< -jJX(&bzXZGee8{^ntbgxA(1Gl17b5akRfG0uM${Vq&7Pd=OA_5*0DvIYKZsFEdsq(FJtms}#%32i@!K -6BL!4s}wKz#Do*k4v8_e1s*$2N4YHQdf#42AS#_yJVV -G!cUQ2nN5RW@E=36TtwJFosNu>`$I;iH}n+diMCm-ff&1In<)d -54OOc|z3?T(jKv{7TY2q=p&S8IZ8pSYAQJ({zu|^8oU}5fL+t5cEOob+JPty=Yn&W}4@B20V|kd6l8I -oIfc*&*^3!}8&Zlt}5A3sMx$DI*A%)W5ZJ=#gC>a^k`|R!s)irjv+ZdY)7PFdQTH+mN!>vPO-u1$lEV -HomWjzub;EZF4j{kBF!i)8C(v4^$Ovqzdm&q?@G^WYe_9Fs1;{et-Gw`@ZX4K^;@j)(BEmb`gf)lb3^G+;e%}vPEOCATKU68{T -7Bpfc)-AY(;ZhMGi76bKH&PsWUdoghRfJOo{-HZ1sTaN^&xsT)@xyFCbc@4-A=lPxa$KNJM!iT5atXZ -){1-Xwqemnw^VB6eT@=l~nL%}->Sm4M2dj)s(?untBiZ&)>B2Ll8EV4Mb+;G3goN!yTClSHva4Cbhi{ -rU#25v8(md{h6~Eyt6f19%Vn)wX*}~~xbgxApnv)HJONB$IMMfOd+*u*goz=j~No2hkKh{BXx2>9a>3 -6;6aY8N-ZbAmK$#kpQf%lxc`z&`n1KHAQ&PIM|N}8X3V68cEA-m)@9<-Cj>BkS(;OYsRtCnstnd3%r7 -Cw*h2xkauzau#1^iKbxd#&NLf_jjYSF@Rp46(R{Bm%2=lvjqiez<(#Z1f`_t2hyNd+|%yBY5cxA?>T= -bPLa1*l19SzJgn-yPiHCc6#oDBZ!L{3RE2FcIw@JNtnN76>MnfE=HybXN@*&YhzDZWf`qbYkaX%rC_> -!<@s*W*`n8VYf@+=QIcqL3r5t<@972p?usaT|idqmgm(rsL0N1U@GYAh_Rg_cX_^Q|ash>ec0(4a7 -1|lJ~>JqarlCCzl{_MrRa5F%>t|i@t=N8?~9k@$Kot*DSPu -tMFHnNpf-cdi1kgQ>jgL<1t8hQxCmgXL&+83W?Ke#yJ;-LuF{MOtWjB9F3kDrkZ|$=kMjvL$J7^+RIg#5-zvu$%fY8?g#i165^NX -Gs3^CTd0_R(FM0834p4z>SDl6K5*+i?i|*Alaq0wj2_X -K3eK7JHCb;1>n!G)hV!EB)1DJ@Er3fVb^7c#6y|#Hg3IJ~8xg@A@dKe=@r!owus>C7m1?%g>@iiZSX+ -YXt^Pb1(UN7@+?7J;Xr^kpLtb+IE5kyECE>vfwyCvVe4C|FGVOK&arX6m(j_$RZJVcmK70a5XWB1*qO -4xqmEQF%8kevd}T-3wp6{{f8V4k<1IIF39JuGTsBve(0lho5a#L#Px8kw^A*Cml7$FZ>dwP;-28xit=RSEia5`COu*!+7Ehx}M;yn6dP65kOdb3JPE0z -f)wYy;L2P;U6I@;O6sow_H*~M%EbK{a34;S2(a`LdJkA%MuqR!xUMSRt<_JYZX5Hi@kfyfBi3=OL*Fv -t~QmTw@`chIHIvo@4z{Fg4(QuVe6Jf@8_tKP}3g;>T%I&A3do6HfS8CTWU`iC2q%EBdSlF)oX9{8ha+ -LRK(qwU~5?x_;<3Zi4#J8@!G^D%@YPRN)9kRace5%8PdBXnUk&gVy;9q8yr0hN4(7hJ6h16+)Y4*k}V -Q6r%^+#Z@@)93jd4cET43m3Gu9yJTl=xjQd7R}$Px}byNB}jH|BM^Zfjnwhg=TOHr`47;H`_5h$c-I= -W!&=^-D_DY_%ts%yaqNdcNkn&>kggPa4#=7*}^(l&p62bXuDN^KkokBeo33h^>wtEvo39}FO##0s-y* -$FjdsUCIj~L#Hp^G_DCu@;e#`EuT=_9ACD7Nz*4MU!rI=X*qrL}1PTQ2^%&jjHJr -ydPCDp_1?o78Xcu+~4^0zurCA$%UzNhd^qU*C`|kKc-RqLZ!EC`ER)Z+SL}Uwqf_ZldY9yI#!VuHspg -TA(%63iYu9JUj>R!8N01j;d$dFKM -hcO8LK+-N-sdmJ>Ss1Udq02Nf5N@@zGAA-~h_en|C`vM&H&y1v+aT -nBrar-9xq%H$shH@E`RM3Z5RvIWQC%;PCvGL_Qm=+V?=T3fIYdto2&SgWiZ0en5$fu&V&N)o8)i631> -L-$%As%ZJnywgdAn$w*MVlh6$L2-?CkAa>r05IROj9WYb^33CCZzpi9ZIZZX!N7Uvf2=nO8qvA}HK*pd&@SrAz8Io<(t6y@KCn! -07rEE4239Psb_DAy_4jGOGfE}hMn7t@@O3F6e_6*%C(JYUqen%%*nT5h!qiwfH$a|_n#2_6Slpqcuaij%vnJZ3K=$J1i=F6RPCQe6a8+hrm)aBGexCIi3XZ<~7z` -@AgZoo5-8iT&0I=x>9@BJW-&h4|Bo>r&=YKg`J8#BQq+vbv9kyD_CuwEqGYphbsq1gb|XFk1a=Pq&II -u%4yO~Be-2lJv#6>{!T~tTI_oyqz&y~cgvT;3ljI$Ep28{mDg4?v(^hqT1@s5AEQQU?eGL0-D^RTQ;4 -ZTb(q;}NaM@BH&yOSF8?9&@mi*?uQZ2REFq?wy-vLgURgGwnw3$Xs*2Vvi~3R6`lag32eE;Ss=zjGQZ -3H>x$|U5`?md6m>_lL1`$cvqiSY(G?C{T;+DQJjmnyylE7*Ka9v=L$I->pm+WtGrxc5_ekyA(tXY-rR --3$zYo_P!QG?>lJUWvI_7GB`-TTRUF^_YSKUje3!tU{vf)kRkdqb85wdb(eUEZGL?NAk9y#-(J#NGYq -UKwYn+B|a>!yl$cF%qtza_(;VGBya+Q^Xgc$`hb^@aQ@JtQYkq%BEcBi3Z!Tf9_7e7O;CtUHY{gEsYx -fw^6QA1{zjmD*N4D_!3X}J%WRDVU;4qJ5?JJx{Y!(5P_;kVP|ne%TOqD7z0?Bqkrh=UYltv&g&_z!lo -tNIR_Wo4MpIgt4$RTYP8*6p14d9!<|`yFx`1M}x~?85GGmHRtN77u{=7duWallT(g6!PDzPo=4Nc0~m -vL|m0>Bw8r|s@><=u;*mQEi8bhtLurM)Iv!I;MpngtPUdy7PCCnP9yj~`gw_e2mav9^Ip+p70A-60+z -tF=?&zD68c*NyLpZ(?e-H#vs%*z1&^!)nmzw@4mx4hWkdiCbR^SkH$JKnJH&GU!nuRWLk` -ybwY|Mva!Q&q?guh!CygJc~D9Ez$-&t53n7mgr+CA1tD7A=L)B~@3kQAwK1Oc8v;Iaic4Fns3 -1q<@!oi!*3NP>)Cka7rmBOoz?H9nX{QEwxIcs59=2T@lr_<=q<$j}9ZP?#*n=?^jnK_DCK>J3DrfebO -E5!hB3_oly&cs5w4gVboiu?LP%0Qg|Y12IRiQZ*1B2<(DDps_nYSVV#hVL*w8Mb*F`qN=0_?ut9D86C>H)$-vV*a -&||hVUF-(-F`1!()fF!MHY?PvDJPc6K#z8V0h|hR&=tH%$Rn2#`P&7S#&QVHzFrY;yy|IuxtV?A>{0P -T8o^m@ewztQzT0JHwxNYn%{Kb&@t6@oXDXq2y4ou0ckoGfSds@8P(nh(j%4T64HXR%lv4tTEuwl{KgWwytF!`(olP^FsX+w-m>+m+ -{A!NIR7z*hk&M;o(kjNeJY@o>r7L(S56Y~=>Qjo##O#m*93_S?h;PK%Isv7h};7JGjpQ1hPdV?U4ena -vSz7K-Mq2bL$a|)BRfm;a@Ye5#Q)gzPkGfX#Gs^PZWdtM-(4T$rOASIw!10FD%il{v$f+!>Kq&xC}mM -3mcJ{w$cAg0*kHb)%S5zj_*cSHAMDmVB5Q&4KH%ka@vx`1rs3Rz0ekU8RWM#P<4>jbU5#A4Tr?SL)`* -B1Q2AS~{lsDW=5cyJvZRwo}9&0fNoI3mpjmie~sU=8tXG$#ThvjmnccO&4mHaf!xZ~lu`0tGn4&TQz? -e1$qb(;v4;>cn+lB1t>03U0kZ!%)by)e`-ody@=ceCPzQgl&TQ$&?)FQp -m$?|N}=um$ -ym_@`HCv@f>64C%~6byv(lg97a^fq{eHAgMaKm*zFA1QX;xtM8WuJ*V9m!(|8$|o_)J@FAj`i@Lpa>9;gG_u-S!OeY*&jnZ30Gcx -}`~e(^GIB9_&MujKiT*FjcN%z;yp{`~v*;JmhpoJlpZvTNhY@BApyO1IGQXTo2iBTZp4Ng6cN;YOck% -&a`&-10od_UmU>Dngo`pVT^nu0_#glX9MIof`U%4*cEgcqzpMhY*tx!d+|$}55)UwNF0WtNjmQ_+E9P -dG-{5jZPV(qv=aaI)HGoA@56~Z;@LKRU&8QXN^1X>f70j6Rn-JtbehY-(pvXVi#8EE4>v41E7cULdLaCp>)Qw`O97! -1Sp1w364yG1{Je&z+@*+8xlU4NSyLI~W*+6B9w0_{Pd=ZS8TfbNEP?CG~|k;l-HkBi>!e$X$GA)bCmq -nXTTLj%`2x`6E#qH=G@LWy>>g|!jZEFZJq^&(P10o@QVhWZp{ZwCd9jjVw*rK_7YG0{%FtT4mMF&93P -KfcKk#k2LyN*XgnEltq*@{*Ji`GD`yR4UIFLa@>9}+q)*xcDku?0I|0`{}J(Hwp#SffMU00KHV -nJi3IQ7?W8s=z=x5lF`Z;xQm%f=D;eWCk+(VAoTOvkqb0=`=D;0dE`RiF+RMX&|2M?nGLm|c!$Ik`$U -@X%7hq$^rg!&Mb)7U^9ajm`=P`WXBTnWOS$iJqA>dmKZ|vh@6(Ijx*j}da@GUC1Ypxd_f-=Od%opRDFKi^2xCbG;-Q@5ko3G5Yxje -m3Tw&m4F^ke?C%L)mQF`d2hX6Q&|O{Z;Zg&_YTd{h_I?iA;iXd43*ES!$5h1rHDl1BuaeeA{}K(l3CY -zRwOF8>>aO!jP1X?s@hYHQFS|BK45QuQ2QaSg+DQMj5WPgWk&c^)U;5e(GKh2L-~E7iwsrR!X|y#t1S -;n&JpQGULhpi9Tswyl;_rk=X7J7lgRrmn=DeYJ79-yX_|g92#o=walMNRNE~(?D1e>$lNiogQdEl2Ro3&gWsmrG3nG-@-5BZb(Hq#kFL+*?JQ5isuoYaLLr9Z#*3v=1o -I_V}q@4vh`%B=^*Igx1kbHHyu;wc*N6v!`^B#*)LsJOb=tehKoZ9DkB`xX=$gAO4Q#mrc8os0k;n<0p -o07^`1b)7zDqdKhB$$5x3CcGk_|p|PQJ!0Z)l(r+FrB7f_lLvV{tZjr7Ek5OQ=P=qNq3@zJ$A`bsB6w -mUjK&#Mj4w?cbeoqROFPw;UkSsUM?DX|Oq@~U|2oJwyNr}7O?U%HEZYf7IpGUDuMot#UyxU5Y~ntAe|TstY4jjF8)U&E -9JjDBT$ET441{v+!_d*msR4B@Pwr2n&*r@0p|K(S(phO!MVA$ELm%A7wOPd2T3txLkjaym!veN?&tt^ -1O{!QnF7%J4DDX3YVt7wTnCB;cVZBUTT%9-<*P7$+Af65L$_M_gZ$c7>3_>R)bYN*hM&=MNxO}iLp{N -J?x*EphdF%SnO^#j^&2kXRut!s^;R`r0spV4!;lHZEtR -4jGORYi~EFp-ZBhoks-E%T;!J9k(;R{THMZv-k9k|6(5Fod(`%=zN;mH*&!5>mI$G<8H5h$@2O9sYV5 -F*8asw!W#-4p>9~K6NA?ftoXt5+%QeyntLai#yK>0`au5PrqVY2D8nOtCN~Pt^DVz~pqQqqn{*l`cDmc-JKE~*Y2{~vF| -Cc~L1Fg@#@w`Dwn5#+1?2Cgq8+qZ!lLi2Eg;yj*4b)zm?i- -J{d_Y4C&YWS*L1CZflwOP#?8wd(N&!1DytSf%?V54>FEDe#;_A6CBNI7F)H36CHxnwbpl5N;XnzTwt$ -6VkG@qeBR>i}R$n#VbubJ^4UdZ%VO-OT4n`L -5fNgK$KRFsiYKAPy*m-R=J(p6!fLz&&;7q(0q7z|4bjY6M^b`x8@f6SEw;C){!)Bd=OPTE73(Uh+8jN -4=ktK`Slfy|BbA)Ep8Qxay$>1bBo_unz3>)&?GDlNT>n!H=R2oCxe>KOhP}FCiO5y+z -7vBHgbj{0EcK>2%KwhsRA8W(WJu!WOHsNwL&KiD8H`?8VF;kAr64;%`9Umi}?fMUFXl;fk)7kE7X8cYjY#BfLKy?+v*P -G-&vuLy+0~Ih2f-t2|ZDIn$SCA6EEsgXL|Sawq!699MG~G2a?XTy+dsSL3jh9NhdF;@R#STVYYVmJL( -Z6+tXJNO3aOLs=+N)h^qLJS{k}Bsl%6Yv|U^MjMrj;O}BN -Zum4t8jve}9=p!VRd#z>#HA%Tp*@L;^I@E`$0&(Blc2LBwn@UjG{lpz;}nr3a%ix2d2jLx4~>Np>As- -@<}LBf(+!^v4{>h`=P_G}@HU&Ixpo~*PnRIVuJ`-pGQ3=6kDK}1^D+@4)#Vs*2NThIEc2mn6^@{E51p -nZR`ZCbCmSR@uPUT%C-*xpxZr_8& -`U;Qm~j04L@R4v(u18%J$YVAfeMEui -1VH)+fTjlB}ieqgrHeMvKGDi28W0DvNn(0S7PIB{0YF4_hc+(Kxf?b!k2hx7UWn>4}Zo5KXJ|w&P+t* -cc)4kXz!m!)6;}~!<_a#-w@9>RXdO|>EUP(-9Qo785DiIMz?hOveVt2XXsSkDF%@;yu-1-sNXgoMAD# -+!I%z;4~k2fCshvWkQ)hCxQfiL -*)N=O%Euur4?~vi)i07-#S%R36hP=OZgDAnCdUqRZ( -7;8D7A;z|XwjlYW45(RT%BaONtTLgLX!|WL1v)x>B(ei?2~?$T)-4F8s9m6X^t6>TL*#`YWq73FR2{^{_LRU2QYwIFXemI)x-_ohJ9NPYG#VVGN -ROA{|G%v#KR~Df&Ezl^`(M^~#`w8a65S!XTtTfAOoFVd3!IJNmF_rge&v -&jIB$ZoG#2|@3JgQ?}+{Q`M;)2=%?>|ej&xrE)KIRr0{BZ5cxvFmjkm1Da6DQoYtAMk`7OC}77-7Ko( -u{I=BTlWhmhs*xB>)n1yl7~!liNynRaOKQPxH3Q7Ee5bOpzg2LiWZ?OMpGi20X=ac!VBu9^K`?Q!tN8 -Ilbai1g{V2x3 -F=9%SrfK5mEMaN_d;ozvy0*$hLbP10HS?!X_?X(_LdRYGh0MvbN&zu*hL`2`BT!GQRHT9yfKbz^^KQB -wA?%sp&j+p!h5aLDJJ)(K;lKbSETj9*+avpYG@EC(lbh{izeA*6?LC%%f#{J)eUq?_mqXtceSi_%Dgq -f|iC0?AjFU-t~+dy4S>U;rK#+Z(|q*mK&^pVrCNqni$4T7~D}}bfg!l31JQh+ucLcS;kSHnV>KM -_;XrkZQ{~xEKykRIapsS%04?eMgr~XfFS^&h&hNcgC45l$Qw!02yq2@8A#E=|X8JkHbX!r7XT9eIy4P -0EyZ}zcmdk5k#X!C<`eWg1K&%s=XBC;l+9*-I1E@MLPa`S$)D&6~j(4;t-K7wfCtGuIOIK;!gb><}gcK>2h5hf&w>m(7Jsfu~=u=VNVYPRsJ;RO1Sufh08xz?i#A`kb}k;1ep2 -8FKbLZ{}b(n75Lw0DTE>Ssuepjot{x+(P>PC>qGk6Wc3m{}w?3Dgrvwy@=RPgQ10D1fsA0JnS5zr)E~ -?dgMGbg%v3XKKUo+y%_^_ui0pV8u|9Hxhn{f;>@>cigJ(GII_QzVU$WwJ$-BhJti%A3{>riiHv{J*${ -5R_V9&K9d?~;Go?FA;t&lCB1Hn(=sTTPb>OhccfvU50;L~))d&J&$^wllu`c@Dn|b+7 -1%5_Nf^E>DEz-ScJ8cccG{2(|Kty7m%?I(WL~Xw25L -zai5W@nY)l9VHiI%)SPX3t0&8`11#E*qUSKfyylUv#K6 -8ZV!x^q@LYndhz)orS=MHQ8vKjl6Yh@x!X)!J~5pp3E|-iXA>u9smqdv3I=uza1*tc|C^?liUBtaRzLW@eq?<-RNZ~KUY*B;ngm -eDA+nuK%#iK7AJTVKSOy%IxyZqg;JL+Dsf2m{|3NO*8CJNMSS{8NuIvw5+=V&;sF(Jy7RGJJY_`S}fd+pGAs -btW-_EI})sh1ClCHBA-m0*aeI&4NFsmmjugFGUhZ|fXR0k8YikXNPhQZkZ>=B>iZB?`Ah%9Y(!3YsB3 -_LnSx!e>KGG=J$!5bi@3;Z>;@s)B{pP2%VQ(-RbzEn=n7KO+&Hr1To;&PJ?QJ#=3GjFea2c^v3a4g<% -1h6lkmCrONi^h;@(a}r7VxSh`!GFOgnOZ<$bn8l^nyC3jN>~>cEq@$AO%Tq`7GpB3vFT6cpUJv=R(;kN7UX8-`zM7s~v{1 -@>`B=MbM%_gZZXkGsw!rCE3bP;yn#`!L5e94)`4UU_AoK_5&{_{? -KilHFeKWQ?FanNBc-R`6$`P@EjCZ{JP#q(S;A$sF!{I+epy9J>x9@l>Z4Yg#3dpYJ+k$?`qNsOh>#tf ->N*--jdWZi5e#kt{|-?C0HH_!7hrU9Z=CfZ-8lE4zpN4v)MuTCzB3;q)0Lp+%Xd18*>W_^9;I*d{5M0!0cVp|j0@JiFX#@*|F)*Hc8tUHh8zJ}ZUzUHh?d$v$UIA;PjD$2r;^nk(cnlD*1s;iE -u;)??d)e1HM3`WC=0kTM-m^~ARTw|RBNg&_4nZMO-F7Fem(B|-7Ya@ -=3aLaugv(Eopdk$gs!WK-X6rj+u}8VLnn0EW!ynqi{EwLQYJUf#3f;zdY1TysV|5hHO&0rLglTCC*FD*548?*`i>Eck>1NVHasW^l9?vOPG -g-Pn&@e5zU$Fq$4;Vq&7miX0b$)t*G*QiWCcrCFxH$w@+mzOoy=^qHsr8xFx%MJybmiODB1P0rZc^hS -(!|*WcCbmS4@Nyu)uDR%1RaTNQY8s32DYWV}AT~0*YO)Fa3}h#ic!I5(7bu+EC#p_D+xZFYa|-o<=JV -cv!se`G)Q_Nm=F)#KxjnU2;J=9LBT@sL0Cq4Tq_;aen7;lT^c9yASQgZ{PLqW(^W(Dz2ua{5cmq>I`4kie#t -N+hp;@+ln-)q4L&mwmJhOXsiu6LfB6Lh7#2BtiQUFrk}r;ZK=+!6!xI5`A^=Yn-%R;IalcDpNhqpO)~3JSM7cHv-g?7r_nN?ZrPcq -k_(Rm~%wzok+YBRd=H0PQ}}9QjlaqyxoZLGn~xcrrq_zmp~dP{8<{$o36n_NZ;bBw}(J3(P5_&>|u}W -IXjey0iI#(7wzr`bg%MJh^*Ump!Dj^B=`(WYh~2>j&4%Gu4kOXM|e0Wcu!~>XP+e=!fr3hDxGWHXOwF -hCw^lNR6hf8(>j<4J`x52%}Js(>AqC3-s?QN*Ce?$H2nDYssCUi8cYvgXFRj^^OeBk6@3;yCGOhBL}(qP0=Dj~mEnb7pYy*`>dIW-99CpLl-4jKLn(RQN# -&ikdV7{TqIX;PgC%M1HZOq=1^J*)_`g(y4`g%6YN;Vf6bvlxtKEyqrxdmZF&6V-U48c($1Y>eXq!ALK -icNK*{!C1Q6fDUp~4s2^TZ<@C~3;hx@4+!6e=S)gD!W|^)^6q&N4_m|vnr11N_owA(WQ4Oi*0JvPqI< -0-`b`LV&;d|DUD&nBh=Ya+H>5iWPcg`VjfXoR9)3Xy(3<6L@AgZE2`|Lyi7q`+qbF+ge#|`66|KVMBLo=0o`lr_39n^RvV^Y*ZoMlmfdD?qBi7852gh1BV -IsGAx=|S8*`rhBHZMuhwsVKDiGi`gBPAuON=RNOvGfS=G0*cXN_-$d+-^X_^Tz(JVy7LsHGF3bfSY!b -kK>aIZ-vIni{iR=0%8TlTav$K9Z#>aetB(Dd%}fHUHoBf`{qth#OFwCZf+omzk(B6E$Wc#7xANiR>|v -I3^0lM8TM-6?2|_GLWG_+aB`Uo{k(cHxrS72Jl0ZL=OYeQ-{D0TW`i=f -8QGF$%u0&atimFg5CHka9bd+dhQh5wgm{iq*7$s42o66&U(rO_qGWflxAq#AXTG!l27m;XPd)RjqGv$jwx30 -aT^)uv2|M^eXM=URVKx6V`{>vpn6=dIJ_|GQqWV$M(7-Klhy`~H+&R;?~CX7hxkU<=#q@Oe$3|+#;rNkN}T1uixipg`W+uuR=%7srT31A> -^Qkf9kOe$c2mPzRrDHS;s0*aLSfbd8egLU%V6WuG97n7iV61Gpm>q%%liG?SD>m=TsM46NLaT4)OBDq -P7Hi^t8@z{Kvb$+6I<-#YFER-yitdy*jtdy*jtdy+Wt5Tv;qEez#qEX`T4-?%h7e1k6qhzCGqhzCGqh -zCGr)1||of4fAof4fAof3yvp6Fh=@ChXcB?l!3B?l!3B?l!3B?tE!lo*r5{D<4=w7+-2_=J)L -CK(GP%i)~fE75f38gXdRV -IS0i41EZxtd6>CTgpR&}t&InrN#g+Nz0gYTk$0Pjs(b_=J*R1*OzPBsI}OO|(!GE!0HsG?6+@R8A9>( -?sAj5jaf*PV+uDX`*}O!Y7mjD@d3o5~hi8X(CseD3vBkrHN8$B2JpW)Ea^Vw7f)&_-Npv-dq$Wz9iIQic!bD>*(GyIP>61+MBnv!A_D*uS^VZ -o+bgx|agpy!|oXsRFGfBEkQYe#@$Ry7(Nmxws5tDqtgzlfvClgwHU&_y|cl#wzA6?(Re)+G@^FLp2-u -~Omr~emFO9KQH0000805+CpNgIXa$V(Fd0F+Ar051Rl0B~t=FJE?LZe(wAFLGsZb!BsOb1!gVV{2h&W -pgiMXkl_>WppoUaAR(CcrI{x)m&|J+c=W`u3v$hd^mfL@e+KwotMp4IgXup*0Ei-Q*)_Q%0)|%#f?O2 -Nh%)SuRnmmgKm&?Hrd&&x`|?00`c1Y^wW(d;b+gL|B08!uTRg#+3C^o`Sr1Qd3JI1SMfyMXR~MGW~Ib -MQ{Ipa7>Xxt6XSJ@ksuA^4bQ68*`sb -=^=fzgBZp*rA_19)wH~kmgvAC=oTRv}8(W!;Fd+d804^=d#ukE8AYSoFoqkSQN_~Coq#bosZakyTKD^ -s@>SE^ObXSJ{^IjXC!DercAnS1U2kGe#Nw?*?wT$bzmrre0HZhThGRn;m{v|?+@+7(Ia#7yMcO+=j3y8B+bKM7Up>t^iFs%dqJNDd@Mb3H~v>w2%~%>1= -Bx2EB~p7f(wYi%@Wc`zvgTIoWRRp0GO%U*xb9qNTXXjq9mC3dY^?$(j+LDv<(oZh^-cy}WX&)#JISwx@El)eu|=y{e|Dx<@^6-ND=AtD`r%#o^1-v(uaRX7(qiH|NLK*W%>jN*s#I!> -gOqqjzVASK{*B)#b(Y@qrN6N^IVl0QKHZ$v -VHs}9j@!@zF)6yLIV9sluJ?7T_hfwQe#J>Z=_w_3+Xq~pr0Q^BA2>SQGHr#6Yq41fAy85@@zI+HuXjv%o>Po$7i=UZ?2B7-&~x%(pO|0&)y#X&s4d-aC(0FR(J -jC@a8bMn$3Vz{eM+9#eGo~B6&Vv6^&@s-*>8-Yv~quYYl!Q;cB(WJkI{MO^$SrhD+41ogQ~@wiid{l|=Es=AOGByN5-K5Ym#2VcD>Tc0xLJ~&;!ZcB2D -(_v+s%f9QXC@2pC^_i9#0%*x_G~(SMU{38Q3r(sQVEzrm+zM97tlQ*8^`zcRkYG -;`N;Cx1*Z_1ECT;LlPwopdw;XPnGo)OscfjHV3Gpyn)4Zx;N(rApVS9FSM;U8?ORaqo$fE}Uu}4FN@z -mwC`6WK2m!Rwo^XY>)yAO}tDnENnD!OqY5&L8TZbG&3IjWUg-u4H^TYU{M0mQ%F%6TO~=9)Jdu(rKb* -CslyjssdV)d*MH_qzNGq`sooQQ!X{}~oj3*~E2SUl*GW=Nqm*4soe{v!L%F6A=`HnR$9^R4nNl~O)P{ -K|f}%O8OQ%{H7#OCJdn}tF^N6zo5EpxR5bXmvv_ww$2xkD4j7yYc6nlOIMj*{3B5IMYi^Ss$(*m%HAP+u)*s>vyl3nthCb|b+AVpF+;!!)GbfwZw5IW%U1?_S2NXdf~PnODM?u%bLdP{(m7dzD&P=d2ToiGbB`W1KjglV_sMUFtulzBczviMRTgOEip)h|0NnNpHVc -d%{$S_$Z86p#i?(OM=gwwx}oK5dbd@Q#zbSbI+s1rwQW%cq!rs_$<1fJ8z%*cPxd72^LOTMT!hoBZ)_ -cfis9Vag&q&H_E~dMB>6{gfAF`_!JRV%;ZNKy&*kenLu|s#3*142?0H{+l1$b0U=Bw{^YwV3io(QM@D -cQXq_@z!^jYw9eogwQWQS)(}^z)F-I6A$;BR9GI|P~R4ehK;Vp;rk;J1zhKrp?3f)1tvyu)Dn6lk=4- -JkpDeVoUlkN0tbcGTSJP9yNK=AAu-|}(gk|Dvba1Sm$74bY4LLP2Rd`pfQWkb(I>mi9!04t+0(O^%-! -!!a2xlJx7f)ueS*8#azMuW(d$N@!I`5=QCPe_+L71hv?GYNWy@%Bz -2B>X&zy<&J&A3M^IBT^oZsKUURQigeyMTFll!Y6l`Mzr6IBXTF1$FJkjaC0|yq4bk5DMJ?6CKL7NZh= -X}bNFOBMEmHAPZZk3(j5yCM)^)KyuC%!N|@@UmmQ?XF$&5;K7hg;5Im3Y(IY;SYw(_p=xH2fLkT>rMW -i1`$WVd8;kz&(Vkz2-fwnjR5P^p2B(jxgH$dyXJ -EVK>@AA4UIQ!*8gN@p588Oc-Rbl)RP8gf)}6QaEnf2aIP5{wkHC39k9ckaRs7(8?`C>sWZKrC4*-uEn+mW|(ke3O857L@x0j-g#P*_TK~wo=x -Fa&VTGgxsQ`NA};xDN@W)dD;<2=ffjG^i|1SoMU0~x|Fi11;8G3z;mfo -&TmHz`shV+shLn@*S?rHnHIHp(V(tCx}Vz~F)1P`;tBN`htZiE_SRRv*Ui<6RWK%Tbx_YMlQ5wih2e9 -rZFWCd%cL*(3}Yq-Me=vP|I(9)1H7CP{&!WNkR);OngyMAn)~RI-XzsWwuQp3(4dLFOCNtp$q2k*XkH -pG2hSvFx&lGYwBCWnLV1q%>4LSLJYL{j{b2<67VL8fOpUVVFm$m5j**(xjM*6p4t4tk(u4F)Jl|=L*0 -aE*wn*46UBw$Jqms!411cyE1KWN;SUt>T|f8faYV)!=J2tT$L(}%8Yi-OL{Es_!~32*)Sz9DWJ?)tqR -Ej+_SsqhwuS_##JA*2%!U&sn&)gtn~WqvLKaBoo(b}h>@%Ec7>bZw$t*!tp)S0u9p7RMhZu%?lr@c=s -#AY0iD+ArN0gtQN{!>O%<+v(N_5Z;E9Fn=gnK3)Y>?04Pd(aUsGOeV!Q&ga4Gy;uJ|#jMlBul-#DRVN -;JGL6#&|L-^fjOqC~kb0gVGMP^CT`vnWX4>C@>CByA!bu_DtB0z#PJyJxLa8Py9k4JKE9#DsaHWwIo; -CL=yC{VMLTL3Y>>I(x)S(X+8~^^C|ECEw2FCVFH3~m=H!NYLranuwn#c*l&hQ105wyig1hMyV59}TIz -MeoJ#1K^3f2eP6!HDOAo;i!r}<|`7iB4c{nqacrvQNB7Arz-~@z2ft!`E4CIen+<63F4^&fBYx|?y;G -_wd7%^t-*&>UGZ{;wNOGXtYYPbu9i{s>v$1*WQ&VJ{fGo}+ed`h^pXWLp$+46jxDFNXVm+}-Ocof+ae -pe)k=!PSY0EmVVFhbh&j5F>Nl4!sbj&H{jwH+h-4m#pbHF7DF#E`npSt%a-=X-zLn}WOKkGH20&&|eB -?CUwgC&b|=k9htFoJm0k*p}x-@W>lwjB}sYBVSK<^kcUi{^SZ^7iOGu9+ZT4m@~gxp1U9;I_z{I -$Ct(9h7FUy3d9Yy@|tv<3Wf;*rO|#G{$-myJC=(dn$o)U`Aih&H;A9t`LyF{A@g#H<(oO2*L>Hw -k)5oZY*5&sUKWggxO4SCcql<{b|XX5k}-fyC5kPE=w4Nw!f5Nu4$5hl=vfirF~@ -|f2G%&2~UHk+@DwiQ1Y+oDpfa_8I*t784wmhF!-q5mz^QrzB_RoUI%?zL*Y+_&ZCpZR)V&d_yxx-- -h+qKeD_z-^-e-MMii)`$2DYSq(arfBu{myqyV-8H;?UqY(ny@uzIs5a4RIOf`s{MVl}++=X%DO}*TubApjM@s*-f)kkvT1^=H^&xe=xYNlsWmV6;RFByZ-F)s%TkXc -S==yg$p&D-z(Gfg*YkJts>kk_0oFSM51`PGw)}rVi3~WVC|{g{A*OVYpNL=+^p8=Nelj7>q*bqYN#=% --qj-NXU~+S=lUpGZ3{*R{qf*#rN8LG_@eqg+XHc`@ldpi)NdA5H==u}Jzy*>I9fQeYXieh%k`m0zSd2 -Mi5@N%FsN&fUHcic-ppEh{OI}USy@c2RHF`r_(k={*5|9bZu>*WZnSPy`>KKF9?bdJUOGJ&*hP|@>fX -+JiP9p>vG2AtR|>vJ->5$*ZtP`Kd{Sbw(>A;)jbx0~M&_VtKlktpQPx{e)K5fTLvQ!0yX~*+8Q6X(e=Nm+iR9yc& -{UVbK*R9DQeXSa#9+)`ri{K6&-+GCwHb3jP*Z8s8;_?avu7#dKTM1LUZ#7KJ=zN6F4z=2+3Tgt$%m9>~@YwfmUH^P~JqU~To(PxrzcOAjB%rwMug?Yn6aYan!Whf@1^+j3*JV5i+}y$WWWETCGoGv@znK`qx}K*5Gf}^e%<3UGOfS=6M8>6e4_U! -xOk|%qst%C$TDymOpQnU%k0jqCW`(a)FPZe70*Xpb;=49p!)BL3v~D*&z5y6aKTCr>p@f49KYQp! -9st-!{}OurtPhw>|t`ONHmKB@12)FAqMG5bGIO9KQH0000805+CpNk~!&j0_6^0PryY06PEx0B~t=FJ -E?LZe(wAFLGsZb!BsOb1!gVV{2h&WpgiMXkl_>WppoWVQyz)b!=y0a%o|1ZEs{{Y%Xwl?OIz?+ej9E* -RSZB)Yc?b3Zrh>#wNQDgn@4I6S>P6z@;Y4*n?yWM6r&#MPY^XL)=Zr%@sfvV|6LAwFh*#z_+4`~CeYjptFBdLgbKDvM -oLQBJm&4{&u?iq{8kgq+Q$FwrZ%BleR-T##%bE_9*uuX<5Oa*(BE9$&8z=Ii3k9#(|-80BBYd7Rwl@x -6E}fUp#0Ce9zqyDVMD52}**I*;-PV(li^S`^t$`3=C{iHB?@=20qgy@-q2JcuYolrDbBa_HY@i}>b&m -R9Kk;SlOl=l6x-@WbgRaiR+#6(4k}b3}8#x=!LbluzQhP75ufLM&;mxU) -ID}Fz|`t9t~mDoT1EPmg=xY$3v`uq;EU>bLkzuHoKzeEWx1ft5Lw0ru>+ju4kx8;Xg{K9U=XZ(#C11Ct9~MsZTqp?^kt3jjxBrVhMdG;v11>r7ynaF=_|Z*U2ud*!=n^`uUD8#u7qN`XxaME}?!D- -@x=yl4ZFs-e*Ngq96C68z?D*w=xW*5T7owGV~)3Pk-HVsJ!xESdGVfbz5bLnBPTtG)M6ibs?;ZRg@$T -Vv*^B0u=f?>V^u2%Bz6qAyep>Z*eNsSiWB+w-KisMPvKu_XO#ek@B$)~Q=Uik)ks -0N0cMY2fYcyha*B7$@*UXmz-ggz=>GLiwiSPJ4MF3NY?+V{1kR9Q>s?3Z652X*032skui&RFwlrxAxY -=(6S)hW!$@gSP<8qEZrCQ#i4k_kLbXl)8rLWR#Wyu-T;$TXpX3`~n%S(%v-8hEU3;hK>Oc@D8g>-ao_ -IRvW^>=2q=m?1AP)2jwv6u3}8q<};P2rQW3+Ql`+HLJ`_5fr$=Y`_I^pimOx!~;+&32_N=aHSOF6yy} --6yy}-R9!c-z$5mRf{KEQf{KDl2-?YWhcf>}Z8c$ami7GwsV1et*fm7SsspB1UYBsdd_RH#m{PK`kUjmT_`M#Id6mgxeG6 -(^v1L^VY3pngO_&}UFTrrMEJwVOU8GdF?v#`88$w!fy*UsLIar&4>$w8x92F|uLAn5jfZ~c -s?Md{tw&003bjX1GnJ;*6yg&|QrAr>Qxj(zHIt~CO0#+%tf$g0;+rB{k`Rol=H#7Nbs@U5?#ON@HdE> -O!)^Y=;YRmqxS^`~a9h00aMM3H+z2TfZuL~c2_Jt!ChUBcl{iEf`j3@P=epPfir{Zpzln1kBF9qkd$Y -;TESxEOA6++M0^v?DkyE;zYux3k^b_o;S*@MwTO8VO&gT-rP7o+KZ${=xl-|~GX<7z;_{d9bT=5=LH? -N>kR_oug`V9kez)xGXyNbg05kmJRt4sFW=}K-9g6hRb2*`yO^mvkM41wQww}U{fi5YsyK?FgKpr3=se -4CeNtPf-O-y;}iUaf1y6p*UuRZXG?5vVQKdg{Pp1{@Yzze=`W=CJw?LpIdOyt^xbb@od!#eerH -x2L_E?b1!`}5flrpgFrf-ey|elmQhD)P=sp~Y8}0FhVD!Df{iq6JWF<0HK=Sa4D3wL>c)096srlh7hb -Lof;H#v^fUEGiY65@mXu*qlp<=Jfazk)Ra-WlpK4*EN90D)q@c>Izv6ExjX*9s5sW$Iwl2|+gf=+ -1?WQ+Yf7*CDj05TC!2`L~BI_x+ib*g;V5x@PY+J5i0G>-_~dVBKh=R_wQ#0(KJEY4m6>JfRK@g3!11r -y5^8D?KD`pYee#@beGvbK}iSgrP8=iJ;-Yo`)ohnD^w+M*kAMV6xq}4=V?W*_rqT`1~xi>mX?MWaZmq -r#ppspf7tqFuRUDB?Z3OnQbK+Ts2dk^36fsQe_YIR>^(0WIFS}_MVMeGUGOwAD9OlPigk=gK%B>iur4 -2f#XtUYqP_|9wYc+rrXZun-{XmvQ?;jOK#w|XOd#BJnNT}A&*M%>C!h68&FH77IG@x@o9Z*#-4pqS6TJhVq@lLeQ9kQJ%nKru{iVXZmf{&?zm(f0j6p{WBqlg -;VJ-K9)wU6T!hZY8?n=IOgHkz7SO=tgb?WqHoE;oJrTTd3cRleQJnDIDWJm;?HmRaaiu$1;8XieohF?PJSiR*TwtS6Ff5!;nkcsM -WzK|5uVdD*x#ama!5yUn-z>g>*T*Ox7y?MiHAL3R*y>wWl;C+&Z-x-U2CM-YT9-rF3wXBN%`>qa+P>A -S%UB5)^Ad-l0=*e&@r2i+Rr+2~Hh9uDf8Ve57u`|Q&Q-h4|w@@6aWAK2mm&gW=Wi$w`2Va002Ej0 -024w003}la4%nWWo~3|axZdaadl;LbaO9oVPk7yXJvCQV`yP=WMyBSX_+UNcX0D%@#+nF7 -(0&gFYGLtU+2j!d(B{B$;ynK-Iq66+W3!9&6CSK%I_&^S9u(>BD*TT!LYaNKD%SnC}nv(ONv@O0#ULk -o&7V*G5$82C0F;7&xD?o9JtdG*9CMh++{+WO2V%4(C~VUHOY4I-qY^a>dfD^x3yO0d|1CUUs2H=hp}5 -r`tXY1%#sX%N!{<$MA#X3aSt0S(f|kT~?GNdc2FVTnfQoi+~HpKAxjz*$>-4`TdckvgJYXhttE+>j@x -}Uf)I6F=0KPQ>EQRc{C*)9Y>2r0=F-s%X$39qXCnD)9s7wG)Fn$KKmDY>B%tkAJ=xA=ZwKkzJ?liBQ= -851pZSRsga~c`j2bN)daOQVn?FU-yO6NZGbjJ8=;MT5ppzvCVe)93=JPb4lyD%JKPf?U1C^>VIhWv7& -gGL0fr5regO3Y3>}0^tqE&u*bqa8m}vlYLtJBsz^rUY*9ar2sm_P!qXzZE5&FlKngRFN09FifN!?H(M -zCZga1N_R5Fb$=)Qpx}GlWxPSTctA7~*4yk73CeGmoKu3@689UviEn&=6{l;T*W(P|F9Enh~7hLtKu~ -ATA&-ATA&-ATA&-AT9=IkQ0y-kdu&;kdv}fGlm)oaS3rrF@TuF5Q!m@&M_pwkN`shs16_-KsJEt0CK^ -wQWHT<0Pz6gI}qQ2IFc`se2L^sBwr%=63LfHzC`jRk}r{bg(XsRi4aTDh)WpBU;z_!3&uh<)=>13|Hv -AwW^kA>k|AfP5JaVB3N`SZ2@<8`kxHBk8is~gF~o!+CO|wIWCQw3>Y=KE-wbL1H%30f2?qEvIL6=rBZ -J`(qw?D!Okp_U!FEU<_Yp4^)M{d=2}q2F&`c0Vj2M9)$YudS5#j^7#@~>pLj$q_U0{n>YOc8V%JaS@s -X)}8+31;#eik-*G7nh{v@ZJ)?RXFIPDN`gxW{X2JZS2=t@A-$l&fkS)_DEO4r+2B(x561n~J7trb?%Z -rz$6c*&z!Q=T+o1*1SiAMI^O~^8{JYWxAjtOCnKXHDWnpID#`VA2DFXdc%q-iCwUbPSWt1T%O70nOs_ -OdFFcj{DvRH{yhfAbu!CDTXB*ye}S#f -Pw{ND#g6H}Q0hNUTdd5=XwHiG&$~FC#>E?Ci2noqS4m!!0Eaa1jfdNV0T=qd;Bvjq+Ke;Q4BubB>^t!f}=#Trs0|zi-oY#M4(}2QPt(O9#l1rNqRVNA`UqhZ#9Xs*^m;+k1-RinzgIt$!L{H`o$|J`zQ)BSaKM{6`+ln2^EOTz7s{?pSRh*qWP^T%NZ!t4SEFdIHpNJy69cm -%gEj`ziV$z3(tJ?k!LZ;l-a9 -R@{T7yXwLel*=sGkMY#JH}+^0I9Sf0cOqV-l|vsS8ig=!Lg^>j{Dme#C~S -gBwG7U=vcB-k~ICl-@Vq%lJsgKqM4$QvA(Ro+GBGVdEC3I -mC}MT4CMQ$gVCXc$?rv1SgD9R*#c@D$tFY^bZBTXre@X%{^=2WsE#?L;3Po?ZPeSaIJK;?{z0y}KY|M -yLDN6D=!iZ?&%q8GSNovtTb&b{4wUxYQVoWG7KYMek0{x%HF=5_QR#nXz%u9b)C|iL*tlT&W+to~?Y{%-!hVmmEGn($^w(`>^{>Ty> ->XVAgnpN)-AO_&1iFuD+e~cv=($pqbt6Wm8Ea5e-G1Y2M4PySe^^vbMwKoyTEmc%tbi8|Mt01u^LKV| -A}Os!+3Ufqre>zv&UP1bKA4P+&(EATy@b8Xl23o`)^pldsc9xuNwn9!Crs$bBAx)(Zhi|?r!rd1+P5s -Lzawk^^Ju8dW^l8T3^$8@rJox;-xQtyjalN(KGt0!*NvR$zPD|e$AnhyS=zyB-3bqmK6zoLt;YjHA9$ -hkB9W_i%RHA9543MYkKGXC@#x5hrmHJFJfOARm9~?-D)EBj<|8F#rHJ0 -001RX>c!Jc4cm4Z*nhkWpQ<7b98eraA9L>VP|D?FJow7a%5$6FKl6MXJ>L{WovD3WMynFaCz-oZExE+ -68^4VG1y?S4WMR4%8u<6_aV(D-C)xsa8eZgbWm(M5voXDNp9*p+<(7^k|~mUah*nsdqE>GlF8vSGtUf -%w4uGd_CI=i`tJOaF3wL*FK)YS7h%tOrlO?(kivJ@#L3(8wCA%7|IY!)nq0UIW9hL7noNsA8vrt2gv`)8>Laxtg7b% -EGa6{HetxrVBToL-&m6PrxNfM^vzpyMc_sg%uVDl(CN5%bcMlO)d5@NNY+UuFA8=plL^q@U?JjPBF$k -zNxl%qdUkdYwNc@y35p6@_(yNp&M^5RoQXIS6lAoNec6L%>P#APvoVCu62KSiz9A7}TenfLAC+@@OHARRG9?5r#FxV(-=kkN~G{{i4>d@yh?*OU&C+!yg!|u{EivNZ_h8zZ$C-v&(3czPj -7DM>}pEKbbUO%JwN$yaXh8#57X^|N9aCGmaXFQ#Zh-%z-qILTdlN<-uhvabFzNc!1nW6=B1$ie5Qo-3%lFo~X{D?fP3%9P$`va$f1wz;kFcL6@9{t}%uVrT74Cf+^^mVBAB)) -Glj=8*4_;F!BH+8v_*XlS4GA5pai4!(|_=8KI3ac8LBB#fQ=wX&^ox%7)?L7^2b{*(40C^Q49C{79Hl -KLH>212fs=jSu4{-tM_F8dy#RE$KHHp@E-6Q0;@O0on-9qs_Eo3Bf!`dkJ&rIjBAmXR>5r3c3e!y38H -<8DmI20yGWKCt-Z37|1B0`I7=>hDiDFZy<9*!a-a*I6&V}MolJwoIWZ7&N!fD3L?*l1D=)K%wY|O7-5 -(LS>}cmC8<@4;m)Nf8j}!!mev#QzPt|#7y)ZY8U}PGyP%2WbTlqCO3UOk#tzX&5>BcE3?Ee#FFa@8ah -}4Mq31_6`caMk&#Mv2M&H#)iTtQWJyj!|weejpQYR&R%}R0{XEX8VO2jjf9l-+cXY?<^6vw$COZeNm1?vNex<~DjP ->c}WRY)2;p+dC)@?yqex-pE;&jDL7y|TfnwiP@<{v3@<113UI+p8M7qw|e-d^-+;tOycvkwnSlDP1O9DS;oMOffWFQL^ -_tg~2U@@S+<87|?Xz_L~Q0<@YQ7e|;Y4llQjO$T!D -PZg9|fM*7aZkylKp%I{lXzH1(eS#kA!N@K3MF6{G)jzZ}@(*QkK&{hxBdh;utyyAo#%=F2D9XwN?-Zf -QYCcuj=(3fxctm}eiq`X*FjW(I6KdKbbiCVvVhD>FF`YvvBz%0)7WZ$*z=_?Nu)4b-8ZGj!3^2t%8s1 -E%tj=5fAYW*AL#swv;_%|c3Gn7{#3Wk>!)T7X3-{il%TT#&au^o2}pqykCSO*6t8zu~ESkRG)-x;S{o$lQ@VDfIn@rRQI&0Km}R!+rOOQ~ -jLn}|>JwUjgsHCuZ)1lUjcyhk#XPUo8Z(Th@2&enTRrGzzaARW(XS`=-&hED5!h;s#H&nio|Q0nl9MM)Iy^DmFrsvE~oBqU9ZYX-kRvOMDnXeW3NGdH_tEq{g% -e73~QG4CL65Q*HRO0D4hx1_}mhOIz{z!fN6)xP|NPjHgNi+VuLsGV4L?%v$t^2=3#d|P$P~e&-A?;q+ -T@|tFgIk*e)D&?>_B&gvEi0xsCD8wa{Geb!|tbmRqx+zGc))Y&(_K?+3gAv -ONnbfR|jIyYsimYXIyvp_y#PTKM)d(#lx%`lXoO>yBp8U1HC@1iu5fl -+eT~kpk95xef5{h+cdMwY017W{(z8@L7zHC{F$ioPPsQO9 -KQH0000805+CpNgtwn3Z4l705&WD05|{u0B~t=FJE?LZe(wAFLGsZb!BsOb1!gVV{2h&WpgiMXkl_>W -ppoWVQyz=Wnyw=cWrNEWo#~RdDU85Pvc4we)q2^9dtAxc{uKN5)wwM6~loUwA>Uxqj_4HqycM;ont%0 -WLNv&_bWS2w{N+TeOOu@+uhYwUtPP6ytmi$S&@cY({MR -UitT6SSrS;nKz+=~?$PG(^KlG6U^0ZGT{f<+_wA)N#P4VgBfp-NQzQ;l=%rw -|_dk5g#0pBx-#=&ro@2i^L?bL_v;N16Z?r4ilE(w8XADDGZn=w1-U;Y`qyaeKh1b(8jfKbHbJW5 -*Q9X4m+J?*R0-6kFDg_qAlyzL1Am0Ym;OkFi6Sqd36CW&+U(~HI;D1d9@V&!3@3ACnT^23bn3ml0XFodFS2u^xbG)>ggG_b(|wDj%b{_D-dJ)K -;Cr*9|2;mP&=_fMdO+c1k@$T|71lfVaKXcc>5IsTIiVd5vSe2rj9q>$$aNv`^LxHkd7U`#LGPX#xR6mo7%9-?G2+ -sKDO6h3GAVu?2BC-R>tj8dE4Vjr=CP_B6wT(o~}WYFl)rmg^y7yby~f;%if_n*M}Jcy#$rq5B5GU?R` -);dxM=c8~PLG*Bkk+wfNyZ&>{(zIuRvFvt_^Bj%x66B2#JV_o1(`4!e!HmWc@(T)*`h~dRfZ;Gp)vq+ -;&;bXy+V`H-A48Q+<1ia`k13hGK7#N5WEzgrsh5Xn#(Kf>QDsJj&EBGToGp~c)RXEuV_xb -}NGvyse3+UxapHg1lFdIOsV!lol!`ws3Ut~_>xj^|$A7DAx&IQkLR44_aySE}P!bOaI!7X2QkaqS)1! -jb4;(eGd>y5r*7ZAH_aqv=B8Q3otoGFWw3BD3XZjDtovkUU^ojQ4O4Z&7EEvFQO#uLnsEryGVr)?s$I -_E||*s*h_IZ2GNejvPx!!~m3#04^QAhola(#WJUdeuU2V16x8IG8P^50`ySWl7fxIFrh*RayzUB?p^c -&7z)P8#B42Q0%ihN31+aSMJ}5Tk|a!tN#XDW7Dyh)A`lB8L*!$OI|TGuL1uDfTLLfzFU}T0YmP;UN!P -(bz;(IvJQ%xdDbTnuWEZMII1sSD80fHbc@jh}B)E`-#RO*Lf+;%npcaHkmxTf~=q%~-BJ>UuK{5;|!A -!!f621|DDd8W<^RSQX@H3deof4s75wTyv01yebAYuoxBsdjTCa6cmNPvJ1PCSlh0xi_Gh{G0!B3PQ(K -!TY;1&&u3kqDimg*Y}vmNkNHHRxs0U>$H-y5a&<6dN3hq7?f@qW+qyaHNuBFmQgy_>&)_UUr2v=LY@5 -j^lLnJW``mT71|Y^aany3e)CP_U9Zr{Ue?Z)iY`;_OucOrXA7!-TJB?WA%24nwg{J;^)|ILz9i8`8c# -kM|_~e{;tg|@&}Pk^ED@tSrhXn7guLK$COsHq}5H%nD$y4HUvIj9;^X842y#sL`(J1zCwkUEnNdQS(IR94%3|+bS2#HqQlJkv3^7<3JeI!1PA3?TF@VTowmq>#RT^=3cKxL7{F-HdYbTWv)cf -oD*_^Cr9Vn|2r>~J#JcG*JE83m)68Jb77hO!mvSsN$&|IFNhTgs!7y{Kvv|H$C@%hm|j@56Rv@vngdN -0DWB8fH@AUCu*vQ$+Yp#o+=bN(T;@g~%QbPCyz&W3x5hYdN^#&CDD+XFR*~u_gSOaP5eL;ADE2|uz@Q -Jkh8IMG7uLbEa$hL#5OqbYYTa9YR<1XzY5`fYdGRb3+RHhbQXH5SsVN>-ctI}{6&A}`w}Qf7Ezc?PUN;I_(ZN0;ZLFymlr1kDwxP|M7QW)T~Cp^1g%7!{}Nf$9#Rqrt>vHShcz*`WGQj6N+**Dk{>h5a=Uj-IdF -iF80QEpVpVjYdaN`jQGB0RqK9rr>@Fw%!RUP9J`u0)lJtio)!m9^VLh*=HbtbfqF{q!Un5}mPJJElxC -iobR8(d+Wddxd~4f0eAe(nS=zG0=B^M_Z6NJ(FbP}dN)17^4m-xZNnH0tZrG)7ZELAuzHHU^z4 -o~z6$t9P&lWpVU3ztAs;0SqvUG)CC;KSo9vP}*!H;F5{c!VvcA=5Ywg|!bp*xMqHR-**5Y7iUMR;wS$cyPG{mN~)oh!Qwq)b_D(!w}YTg%g --LB=r&Jp~7Fpy;iVlh}g1v_>%b;=|2gXX(csMo~JvSZC$s4Hch4>pn4szM!sW@{dX8!D1{QZ=)(ZEs# -{cYc=&8#8Dcte%*f&x!dRe?0Lm|1>NAI-&S7;ola~hf(Qctq;f4{1q2}1oC0RpYe_Pn};hejs0Jsu6* -y1k-gr}CjQ6^ZllEKFC~Wj4H{^j!$F@v!pMkzC^b0?pZVL&OO>W72En-(B+4#OB`Ur4UKPfFdow;r`_ -1|fe+=>zct2GCn8N5U&-yP=O9KQH0000805+CpNl>?%e#Z&`0QfKf06PEx0B~t=FJE?LZe(wAFLGsZb -!BsOb1!gVV{2h&WpgiMXkl_>WppoWVQyz=b#7;2a%o|1ZEs{{Y%Xwl?ON||+c*;auD@ci!D3s$&61R3 -%PHo>C=FN}}w72K~NAFHPoSxI -!>G8?=^$EQ@yEy(kt+Cw;_UPt8(nXQnCs~}*aXyojl=Lb8XOgBdt{)#4Su%~Qyr5~C$gH9%Z1w{^^fX -JV1S5Zw-(;H4WkLBJRUV(PKbKWGjpx#Go8(!E%VM4vs|z@$%e<)XPYW4Waz?kymB;>6aiM(8mze6IqF -2YSiBBdGTzJj*=_pOHbXcHbtQ|(Qgis> -{4<@&5=iNT%w&ORE*H0HGR5GtWGb^#Qe4tp-76mqQ|}d!>jntD&*6T>?pu-xo~yaP$)dy^68l`w)_5E -ca{Vf<6n6yG1&H2iVf@r#BxjKHt#M`4{ -^A=<4d|{N~GB*n(+1MgD3}$>SUnoDrfb;;dQ%I2?RBxjO!c5l8P%&rWZ?DD>}7Z_ZDyuj&286&=y#(b -diA@#nLnE4uuAb$M}pvQKm^rKVVs=_9$N_zo@~b1*rRRh*<{9r_ofw*$1vInm)uuJcW45S_q4B5vS>rX1Od?fF!>|H%c%{W|jP -2tvn3j0jW;nd->~?tQJMKx*Z-;xqQ5Z-&gZRHmw$MEzy;2$hT#~7V;Dv-j9?hSFoIzOhY{=|*hR35U>Ct|!r)* -6!wC#0Fr2_}0>cRmCkRV9pTKxB83@L3APfdF8Q?RhlpTPBv3fYdF~$+$z%}>~DrS%;MNK3&k|T5Fl0v3=$ZmGBZ@?7%0>;MJ{YZOcHW9fM^8rFO)bAV4ws7@(AP#2uDB{LW -v1P(?|_gws;2N35Y77NTmsb!KfH21Tc)$L!=XI3(QV{Pm#I?FOgCqY(PAL*g!zK0%{bHCAtS5z)(3-A -wZ1+au!O~p$Zj)gu3Q!l?1PY;3>&CZ2sR({?cwD{-?XipX?&Ki3Hs)qDoP95>+)j=q95rx`}E6yXhuj --Az;-|JS;SY83r$GHklZLDNkX8qsu<(CQ|tQDAluS$7lFS;oF@GJJ72!Dbgfrc%9C!S9^B!hW)pe=KA -+mE|Es#(u{AyQIK=>VOOT*<`ps4o8uB&Y2im1_%3MBtrGLka%hjRsYz26itTeaVGDvNy3)bwqW~=vB} -Rt+v?8Z?7oJpx*QvX8$bM+=Yj90@Z1rNJH9%vKk7U%4}F8Vb!}XzZ?-yv1lZuue*}&d0=@76KDxH{W&Kd|-N!u7wQHSDl@Kuvyl3nFU|`!dU~1F6~-yy7J~(HlY -j5+$L_jD{Cck3ztT)+ghkq$F2Fzxv-v#=9@KVyI2>@JZ4sAtxUTAhSt2btF>8p@ygV%1<^9YTNU!A6L -AZ}E_Z202Zrgo7EJ!dz*VBU+Eo@@#fO8gChYy0-4l1AgZhTag1*w-hqZOcnm*ueh<4KxeLEDRd2?k)z -mwg1n+3fRb!XS#rW@#-Xhya|z&&Rc0%2v_tPrLexixRSFX3%hUdzD?m1yEI)fTonXIAW99hjk_b;H_) -!!#Q0#$ad`SM6EX0c(}7jcshakKl)OU~0j1JM+6p&lfr~i=84co!``4*sediKWmL>1ZL^k6#|n;tpme ->Xf0e!mJZC{16-N91N*~QQnuN*70=qC%o=3eXt#lIAiA@0>si=&q8$cCf2>u6p4DKhxv;TV2>LTJ>wo -S9vjqY>$Gmgps@+;CVsU0{Q>8Mec6MbyiJ@h2Sb-D-4uqJC<1Wy3>%u6|+Z^ME_uPw#+@Dh&srG -t|jKzg%4%7=Bpic~c8r1UB!Rvf!x&mxF#gb1^ho(8AF>L3>ATc4IDe2Fs6kW#L5K<-laY%x-F;$wI#; -nmGL&*wPA&UsDD4O3a+0CaG-*Hv6UugfpMpQhFSO4hI@=e~`O7+0d1J%ZI_!(}BJF=+;8$&4RnU1-=? -s-=Xcm+FCGByN!(X=InE`#p>*S+yt|JPwXnu&QG_=b*hoKsj}5(wBTmJ;y?`Ce!#73Zei%IjTVX9e>` -g=?;#Kl0#~EmT|!e#eU{y_@vD#-2l}B~11^rW)o2zeJ5FH@bo+KUt%0x%yDIB);Of9iV^{O*I?~2!WS -&`tgFyE}VO@E2z&ihT+nQ%>)Y0p|k@&05Eg>W9z-76=A9NA8sGKagTzAvOdr#Z_fv*;}+aGj_$=!$8{ -P)4zXS?{-)+f!y(9eX8Ch+Y6yNY|J7W`6iwdrpA90a~Kp}U~^5wsz6Q{(>K*qyJhmB1G(bI$s*UW>q< -!S`oT-@Tv<-$r1&UW=d~RbPu6h2*X@ehxMwSoc$cIcIh@-21!w3x=#>~|lzccmz!>70^lJ6MRfB9i$`FOLOC(}5+%*#Z*IB})kScN&;pA6 -M287rf&QqE7Zd-W#wnXD>Vz~H7>$brUJ%IZ9Rl(_xV>Gj2%C_0$D5kCe04uao1Z<%clZSy;6e|2a;H_ -M;OHwxk^xIMZ{WppoWVQy!1Xklq>Z)9a`E^v9pT5WHmND}_euPE*4w28!e>Bh0+ -jP64wo6JU+Hzgje<|DD&2^s=kz%%1L-G9HQfu^f5PMpkXZx9ChrRu4ts;i3(@80$QqYtMa&#&m>{N(i -N?vy@UT%Y`lwv2t}y`$-)qU$_H&i3Cd=#gS+1f|&FOw!N!(Nw+ciUa^0K7NW~2|3Wu$@{q?*^a}xDNo7Vi>+&&68~Z_36 -5r=hzSgWQa;2!q7Ueepdr#|ZMYAZST+QR677rpyiqiS7S&sQn**spXb<--H!yJveRQXe3IQ)3^i7r$D -r1VjxDu*>UtNSFLVe&;hQ)!_nDrl+Oibu=TnFey#0HL!C;wyIFlZw%=b$?ZPffkYjVYD?8eS)v|qEh3 -BH(uc}kO -tFN%r+7hWR{HcUj}beFz76j}iuJFLpq~wVIuIBXrC$=b;11Kz;sr#XC0UmH^dT!sJ@oPza|0D6yLCY5&{1j~biqk36o3C$j(JenbRiaHQh#VSgYHO(_sXah-oN8IRu(P37q?`0)n2nld -?96hLCZ&kU<)2cZa+#hIBC&oIYjO9{`n@0DEI`XFHcOx0-?rC32QXsQ2HVm;T+=(@T}X0I1lhFe46Nc^dIW}L -!1*Iag0+`Vupby4$*vw=1Jfat~9|^0kHz2CITn!C;EQ470p47hDJl5#u6un3w;mmLW|P~(S9g=2~

  • +ZI&?blA0YW9v%ArP}qYOmRP+y@{6 -QofcK!E0iv_@P$E+qJp5G+BaSv}sqPCJ*Vs}hrH -Q1WCYww6Nuxl-v&6-O{J_y^h-agNk_ASCab$iXN)9NB)v@sJG|4?-~>>uzMmlK52pPYeP?i0)3+1F|o -6KRzND1j7)*r%0t2QTk9L)rk+`=!eY_;Ha>eK1=UN!H?zH;3Uuq1V1r@d^jQ4GXa -@{6wS`)0kZX`-Zu4u%_M!aeBjP``qz@WV-o -eH@$9VWpnSja!oW?pn{3r%t(RF2i>eF8ggB~o7HKev|WvS)ot>=Eh{&XXr#H@htXmY7-MV#lC>AOwtl -bouSK>NOfm_5v!Mj7y|F#PDEOq1`RyGZ>smv?-qFC>seu2TK)+Ap_-R<;_^!WX0JVrEm9UYnE}W+ZJ*Ygm05M- -<6-Ue1ikayd~2z9k!HOn$6*v!FKDmi{@qh4T-YV=vD^q-e^2x)pfjJ7fII(JH>&`M21ikg*BzT^l(hD -xu%czmwE7b)fxD<+_f>t(RFuc*H*n*~UW7yj4?!%& -K7p>hkM>r%}BMhG1)92jkG9m4(LS|7GgoxPD~U!yPZ`MTtB>8!?XMewr!83) -FJygqe@gwG1>tx#TbcwHyjiqx%8Y>91CO|v_51l?N2J^I=_JF!1_gl|`hQ5JRuY~O)*;J|(93R?}l5- -|}`Bj6=yTWFfDprz_n2>9!ceJA6wDO&OMC>x8OSsJ$4^~?4cspojsUY?Qc`;px*Hn+TLUvRaVGT^Tc3cahov20-=XaA08rSfdRML$#G1kM1_iH5Ejyv4J%k7%{=TEW_FnGaa+3wz`9MP`M-)F2jM*B5>Ma -RfZqy@MAd4U(xYO;PX&xtF&!9&Ta-cf7#yU*!0+sN8;bU7P_>0+gE!ZwLKZ>hdFTndt?)=i)hO -$&;JO)8wI>}f-N@wUkJMUcuztRox$$is}SsBUbDH{{g)HI+`j;kEB|`g#QHd(B5#`qKpSS;D -_PqMv8ZS6V+GQQyZ~z8<;MANtMpw+@$4naAJtSpK0yHRx%&UdFR1xyg!He;;wHzfr@;U@(#T%ZqBz7p -0b`>4W}u^Fo!S$}!+9N($v0M1?A^q9=v+pMd8V0Q$-MkN)^$kwjnA;Sr_TpON=pP)h>@6aWAK2mm&gW -=Z9ms+oHR003|l001=r003}la4%nWWo~3|axZdaadl;LbaO9oVPk7yXJvCQV`yP=WMy}$dF$F -$T%u_;whpJU0wa^W@;;44VURfZPOoEt!+G85^`%FciyJRR@ELI`+T*XBg;j3H%%#WE;;D-KZ -ArZe2KA){gvs^E}}Ha-}@8ju?Rzf?<`~z>4&SQfO`n;agT)&12=*t$)b6lN}C*U5=X!mXkMr -P78u8I;->PW1nN~YWlJnpFjgqLiu}iNF9~>oPOl-ra>*1~-J60uQXhDz&Cann_<#m6&^K&9cw(@M*lZ -P4&;shhukQHAU^0eQ{{eorMx$1L{BVo9SPC4)0>_s4Vi|BWj8RE9RBLn`4cvD|)(=Fq+TC7v{6OB@-E -qI;IA9M((1KxWH11lHUTXxy$!IumI#nz#V`+*hnfsEQrFe!W7XmYhlf>mg6o>wR<&Dt6z=yf}%CO8H! -&&8niyg9DpH;&^gwHx&gyadm1wMmNs0zGEj=h8Jxl>)VlGnF_v%;%Z;L1cLH+%`O1P;P&K10)XAcU+y -TSSUf?^}qi)lIYZ$!ye2fQf^WLl<=VcPB1BCBv9WoHW&BZCOh3gwbR5XpUJZbeI}yVXBDBfx?PLsS@i -8b681|_1@6>9Ir~S!2e1l8FnB3X;Py;CG88zb}^Nqpy2MSFR3f=rI$3j7!1a9 -PUj+1oB71kwSO6hS2#~b;0v~Yub)cQ$edep=6sSN%q-dKR+1028FGoG>&{<<(8n@`XVp_@CSG1ls#Z% -Mc}q@x5G_5v6CD>958c0p)PDaB)9krWln2Cij*S*VZh2 -Go;0b(5#N{d=R=G5TIbJ)7V)*t=W7#R`+UAO@wJnFv3^#L-HLg!h|g-CgiCK4M|@VwH^|H$fMkB&5#Q -are1mO2?}+a%oe!jQ_%=vxgzFGrr+E@Cy=ffrbyB`TX7&Iii_a!L`(3`lwx2ff*|u?E&`t-w0&YIpvm -4->iEp@(UE@9>;4ds#qpXgyP{@T=Xrv0saQv@T|4+Tg{eO`CV=d9lAYW<0qeX$zO*tmF$QP|us~rDnE -172F$gf0PCu*Wcdo|7ESJZS4YSs&XOBCIy?ABmI)7)Y=3+z9SY8L$*P&o~~<$<^;@m8|Dv9ZKM9vQ?v -AWWwmkBZZ2Iby*qk<+!IU)|Kol<2K0@i3{%NSm6B1zYyC>U7a_BSogu?N?f@f(mZn|5&(br_MQGHDJ^=0qk_XZA6)qgTbJ^>%gdlUWvvQ6^EhM7ax$K -0_^UOaon)Oe!!x??IKcNCt9(YxfpvaR(bnfqew!4+EIkyw7iM?9nK)7PtNyJGY;{NG9-ZK-wQP_D5j&z<-b4A=_>Bwy -9=S;~t&Y}@wZ`Yz%MHH^l-l_deJ??Pr@HvVPC-e`=u--IGejc(GNO-n@izuM_N^oqpgiWHyoo!fUV_~N4Yp_}%7r%&BZtKA>9>8AVG` -h(73FG?3QUNAixIhRVr)Y2i7wA9l-WgH8dDEY?5LK7^6vFs>h$AQy^0zhk}t%r`1P6BqzZyf6t8`2TE -0@uLz+1Ws>7^3AeQ8ExtYGv_iR@!7Qa7+Vj*7Crxa2(Ps%{=Bq3FCdOQiNJKDz_^1yj20>WM6A7D@9NDM$^ZkSb+p -n_xhm2Lwyc5YSk8Jbtt3MixL%r1YfITv$R|0@B -0(#vPFG#&x#Fl8YHt=1~Bf*fR$wumR4S77khs1d?KSwB6O$WKI_R9v4q~o_A>zqeXljP(Ka5dAb`rk` -TQ{hD{ojq(kaF0q1jp%Q?RczB&jmD3v>+sz#$5K$4t_UDX|!YDvwC5&C-jPBL`q}i-i^ -fUU}kgOg_nJQ*Rxrr-x>f3^%|oEBbeXG2(Is9txc6~v~?cZYr?kvz0s(DYy4Vo5KTsqoO`IK2AQx_Dh08yIA}@iHS6JsGL;YN@QvId0OVr?g -xsS6vfb0+$jg!!O>w>&CK8oy%sx0(X1kCyU=Yr?#rklP`m00p=?JIP9kB*qb3{fR|zCzueS%W#;=?zEqB^!QPRB>-}WcxNVF+VQ^=&1x-VqCG$ -KeqcF8K>+p+UYT4pv<=1kwRSJ!B&sX|FoBdkg0<%w!aE)hJC8)jb`?AB|3`+*xO`;aiuC}V&Q8{-#28lVhWP=_GlrPbN2;Hhf#WrLZoD8;H -i}rK4-5)-bhkqX9;@35@~Lrb3#k57koB_QBdT#JF)WRj>gVR_^x5Th?yi7_Eg{(*K*X0)?zs6@thcyw -($(ASBNz?4Jl*T^M@7!Ho3hf$d@;pOt|Xc3hvw(%KdGnmH&9Ch1QY-O00;m!mS#!LjE_560{{T82LJ##0001RX>c!Jc4c -m4Z*nhkWpQ<7b98eraA9L>VP|D?FJow7a%5$6FKuFDb7yjIb#QQUZ(?O~E^v93Rbg-1ND%$buNa9hM4 -}wftJGd}AB+o>mVjj&MbYVm7JCibEM9AOoyhm=@2!Cl1C{P9MT%$Myq$S(ytLc59~zCPvjxp({&*3MX -*6H@Kj;_OcB@VCj?+?$t;m@qU!|Ocp$GMkkdo0V7cX2JWRe}zhFeOx<%w0A5VQRsi;uzi_Dv -u1RHNZ%_lj;MOFp;0W#aWiAqCg~|Ms+#foD5f1(QQ@earH+3`!+w7Nikt8 -rc~Vm7u!cfzxPIa3VG>{ER*OxE`8wwA4@8I_n0}@}$gLK8`atN=l2sH`cBk?h5$!7cF{xZ$z0F%IR09 -I7R8}aF+b-HkPO3p?DK?tv%1NGS&dI3EzG9qvs#Hk{%SrQ8m_t*L&l1bi-;_rEUZo$PZMllcTRhRD7lz&< -e!2%1{vZq24@_cTNC9F@rCDw(G!BA?ap-?Tg*TebXYrHkelm*}<0zuZG9-@zFN|mYYVL&;tioU!jeBs -M^OM9*%v&PANt}Ulub?Ep944eWjQ#|B4H}dw?br*4%?Zb+mJ-9-6xILoP^x@e&x?|?p!Xy)$`w}bRSW -D6cJ|6$u$=up>}Hof4FF>KvxEf^$|oWN^+YPAyEIb9I_QIk+Wv4b=zkb|><@@m5hA~OXuSCPA8({~tC -eYdNxkERv(>6B3avJf02aEh&qpTWcIq-gojI12Hv8O!uHwn(2E_anrU{9@&!Pr|m&wnI6UQt6d(Z%FH -^YyEVG}hSx!5Jwv%cc`5jH?c@#Xg~7b6A;H9#A&{nW&a5V<_ezFr*`KORA?Rw9|f-9GZ8ciMHX<;2a0 -TMG-D(RwX#f3DXZ!)11iyXU&7*Mzo43P)h>@6aWAK2mm&gW=ZkoXPQh5004$e001ul003}la4%nWWo~3|axZ -daadl;LbaO9oVPk7yXJvCQV`yP=WMyiiadsh?M|=m{cBY56(Ejjt -g+5@-!txV{1p2yppiPZtcJCZM6iqrQ1R_na%Dbz@&{%pXPQ;I)~fcOuJ1#*y-f`#TA=goK3Fg6LvcNb -oP;H>b6^Lwz%W$Q|#aRp&PKXXvLYIu>0treh|3bD86ML9P74X#qfyXRbvy^B9>R<6R -@!SUvhwkq;q3^k=AB73LjUS>odjk!#>nN7Ty_mZxU$NyjYq9!KZYr$%m83nL9EoIlx;fBz8p&@l8WcEfZFfkOkAliAsCz&JU*m|iTti0IER7FUz`oSlD~u@iQEGFx1 -n-Aqqr?D}SQ{b@ewGB)SD7-BJ))FAJMcnwY7M=+8rp1OXJ$gY2Z)0;qm!HV6vj~q_3$9=e>mGL3p;to5Fl2 -lZ^JOQrRvn;YeNxfw_58sx@TPvZsHV9R3grtxPACMbz_K!PvTgF!Y|U~lD8m^( -~I|Zt0n&Z-3t=-j=gTdEy%2n`eY13HsNkGM*?oUK@dGL^D7*K`yk-AFuIR!u;CT93D1n}2=-drx+Pk- -MhG`mT<|SDzbE*;kscTPo}S+q{Jx&w7yQ2D_d4oR-~+)QjP$tR5A=Ln@NGTc7JOUJ9}50Z&mRi@P|qI -;{)pns{*MHIB>97m`V_b$_|8a=3%;Z0j|G3M=Z^({T=4tKQ{b-PyOQ6haKU%iT6`(^OFe%n_)9(C6MR -q4_XOY5^H+ku((_k>ztZ!$;B!5n3qIHL*Mh&M_+|zR`g+}ZlZ!=??zAEom0V*Asgdmz7*(`Q(=bO$6M -2!TQ+-i1;TXB5kWm~i+Cqg#S-1RK`|Qq*r#xJ^%Ycjifpl5f3+@p!uQR{AgfCVB!2f#X1#XgLwO{?h3 -K8vO@t}vScJ6xcEeGn^)e4~2DOcJ_Qlc3Yov2p|xJn4!dr|R~sQK#OLhjG5$AfXJl@*!b>lL3AakECm -N!2!MR9i3O&mc(ME(j&vpoByU^&N7FQ<|)(i8FnJN>K@#=>8EAR}eNk%Fl)B+8+rLOz2aC9x5~L>ichoV-JFm21PD@-9 -0$^2V}c(UV1A76Vz>vKY!@Bnw9tWAPymF@N-A2*okJQfq4+pMA#s$U -P&m#|NE~G-dgIgcafU+SI71846{~I77jhm45@q84AV6848J`48VB&F=1>L=I5A=4Bpjv7|p&s`gik;@ZPk;azU?wLk!v>W2G^YjX<>1VcM7WK4lvlH%vZZkY&Hp -MwVY&Mn9Ys^ME%-(H2&ay6joE=D>N_O*CpAe&PY&Rs1?1sXz-H+}ji6gtA^;?M9#d>G{}hC>-03ii#|->^GnY+9|PGvDHqA9iIK7^qS>vQ$(dt -4vHFe;w*LUpg0d2h;ueTjl@|E`hYlpobAPV@j2_#;4X)}d -mZ6@&_cCn&&lr|6l6m9NTQWdPt>U5x0(Pqkfur`x8(q?h2pZ)iPELK#s83cjQW=f)oVsLo5qH$h;mn# -~taj0MLQ6kV}@`%nQ)I7khPXnl=AkPDE6wjO{W9^<}^!8_n@F^3`U5T8cs9I-OQu_}Y)~JDDWmioj!& -0NKF|4l!-*`s)BrWUxX|HefS}j>NU4H++$S0_K7JF7dQzU&p@nd-wHGQR9^O1aVO$d3GehEm=$DT#u* -t19+c@~9Z&mwW;Srm>vi^P#&ph8#c`LAwo5|{g3s;Cb@ywYk9i -Kw&nSW*k{HfzOXkgUSSWp!!d^E-c?{{G=&-fGR^-@lZLQ~1wqayl2U& -I<0o{|LNaadGNf5_WM4$1YCd$i>^{S1@h3jr~CG(DXx={2ktdP((rrI@u=RB)aG9!M)`PTSYvPR4)os -SJdBtQY^PA@3PMvbPDt)Qvtazmka$A0(9#?^02ENwI6Bet9ib=pAUFhzMIWAyusfcauNDdH|M=%-K+8 -RW!Z0cyYoc9YIooJ8uAKIW-U29UkB_HF18}!OC>o@4_Ttu46&Dp$XoK9LT@yF>zF%@dM37d -+U8G@NjNK&Rk@v*7MqkR=erb6+tshA!00xg>g#mA;X;>c7e9GeP>BU7PpY$_yZ78->CtY-!+&#gL -w0CL7y8W&b*zF+wO`rXP}hcLtWbiejHyFMPqv?DhzH}SUBc^|Mt`(F+uCgwru<`9QR{qrA0ZN(!#gcX -{gI5(`}=>*x}G`GR&PaFJcgFkKXrw#6Ga5|}O@l$1Xq`donE@+nudU~?#f*Ka -_PVa*12slciX(aKSYLvy;3~JIc_P&qMDA4-M3HuWcRrR-UIXECicsTF -Z>Z3UQT>*6FZx%b5pe_bws-4QDf0}jDF1-X%fZ2GzLdTF>s85b}UA8QEI|A4g1I_Y{#$c!Jc4cm4Z* -nhkWpQ<7b98eraA9L>VP|D?FJow7a%5$6FLPpJXkl`5Wpr?IZ(?O~E^v9hSN(6>I1>F`f5iYnVSDSkc -6PHsx0?dCqa?=p>m>UYxt<{~G943@MU|wJ`u_TTl;khT&b7ep8VC@PGmkTGW}c{vi_<^qxOcrF^?O}+ -=)2VEkGlV$GqxAb1x=QmMoK)1m_^i;b526jK>k-m5u<4=p1IQ4371T1ZfMR84~!6b%+iAZOuKcKuC0Y-CLvS*Gmha6KiCx#A5Ha}FuNXAcTzHr$S`jNzfVr -lC1q6B&e?OCcWQ1XD;9vhjigj{>PT&@>7WhY=4GqZeAoK9*&>?ySULA2c -X;!i;&b6RsFa;2z0uA>VSXBvuY?$UgW7G^bNNY>d;~$cQ5TCoH{*11YbaNVZK2HKMX@B1YVJ6Gdob?1 -f#3VLOV{-4FOJ^{+~H_?cOQ6eKbYKmu75x3-(a)Vy!^#M`FoSzzu<8{@+RHsy;ah}!Pd%&O@)P8cz*$rtfc#5)_qk9e -sbJweoNxXi>K!!=pO7)6OlQU*EFG&UnmLJafGs%9!W=_IHHaI6|xKjOFkZ9*2jscW0}g;UHi66oAK06Wc)ii8QZ6^PWC(41e^N$~(L4;yD}GdMVAl!_B3imktiNLUTZJIRi$O -%aTQ!pc<$=$TF{v%UPOdMecb`>*n8q -8$Ok^KloyFa!<4GC-?cx*&057}t40ZWPd-P#zlM_b@aIbIZ80meeas8AZ$3$^vo1snZ7!gE;XH9MuXr -8zxdC;R+40#H1gxZ93lYI0$lr4T&-{WYb=1}Lyqz=a<1Khi5swQSc^r2Ce43UY2(LLL#?DA+>uK?hU1 -i>WWr`*;ZOs8oDznd98Lo!4*^xhD${>EHqEsFqbg~QILaWch?e+dF(yseTKsi`x(EbN*G0Jq1PJ$m4q -@ZA@QB!*mT`h(5(_u(8dYdtD7G#wx@o~ixdnuXoIOGF{d)YVCOw3MVq*G#teyh_~nRkL^AM&=`s&+xI -PbMW#XHv}dFH5Sdn4eR~S-E&8v^OKXq)rE;MAz5*8%=}Xq~A~w#ovnETWX$9bmpE?Zj*4Q7pcg}cp=i -{4<`BGBv^lSM?PkuY-_UnW!|4yg6(s>8OtBg}P@-UOdVO{+(XABK~azKjO3Z1=p)5T>YQK -L}c!Jc4cm4Z*nhkWpQ<7b98eraA9L>VP|D?FJow7a%5$ -6FLPpJb7yjIb#QQUZ(?O~E^v9JR&8(MI1v8MuNbLcKw@)*6}{C}_W@X-Xn`UEs;bornZzMhjg4%l-Q2 -IgaS}>W!WDX!kjTtD&%8Uixv4(T8;r&?n2h^_nKyvmzrYQ>S)d7z<_?nti7)l*>I;AVV+})M!7WM*Cz>QMB8|g89TSDKtK+y;SJ~6 -2n}oOzta63=LqDq>QJoh_YP!1YJc2>iq_^TP+tUD$O0}at;e?t6@PkRWCHiGU-cU6x+m@UtKr;LJ7c> -sArfnzEx}oUjQRCU?j{F6G@R;cu|5s!ll*Aw|vtI+McQRmMVnwkCFkhRNp=zfOMIOyoFrgY#fe{r{q5FBf;mk+yg!xox3Pi)+%5-!r<IYVO1N)cfa3>*ojgGK)y5#8Q+GG0E}= -!fIwY~Xn?d{{si=H10|++R()3z)AK^9OI>U~?%wl-L%ts>p984v}&vv68qXgmImv{)Fw-7$6TIA}{HyT00>UVKDnxgT#9X)FG7w5y#p44kfsT*is9_wc%F0@S&sc};#T-h6FM#$5%J8i1s=kOEJW%dmr^y -MC$+Jr(=K9LZP#smalf`)fR%@ma|Z^q?-yP^H?>-*aA}umspbyJ&sXc+`xnW}a$th~D -I&vc1Q|meXEhXpSs_I1yVPZwZc7#hs%30INST;kG+&KUYqzvI##V`=+x0HTd96u&|=@j$lgsvgAXW@n --k!_BMyxhfXJbXcSV|Vqc$2@u$xJ99ek|%@X>06czBBm~V+CU*u&Nxra*JPv{Jw1b-!T`r|Kq#1`V0xT-!{ -b{k3qdK9si%0V5wsI$;hfBDoP!EAFk6&ah+Xvx?WMmWpR{aD30JIJO05bpp0B~t=FJE?LZe(wAFLGsZb!BsOb1!gVV{2h&W -pgiMXkl_>WppodYH4$Da&KZ~axQRrrB-ck+DH)o&aW6LA8J(UgH|V98|5TINWvWugo&!C7oo)-!(QxN -w7X8?{`#A>4LG(*)82l9d3k2$nb}#NoHQReA6|{7FdhwtQ*Q|8 -CvHZ@Xh`Ca&!6%v9+L?|7H=Hal@DU@qMFlbh(||$tQ#9IVTO?5&CxZLTa49tXRa>dDKLiXjsdBlmkQo -eMm1cA3CKu&ra`w^8U34C;WyJ!Vs -2c!ar8Jj5d=?4K)p-(hMutG>QhDp>R1hR#%qs8^j?E?DKJNVk -4&->HG-6`1;)1;;T0i!FjO@FF~kreB!UUE4QdI -~%QAx#IK6sOst>8o{@P!9d=(9rvC2Y&C75fcv)wZJ3yOCA#RWhABQz`4}Mnoj!U+x^(_^a!cj4guHS#V>@;)u5RP$YS|89L@p3BB|A>gyA*ijW)$7MTjGNJVr75bgVr|o5 -ELUpUv@*}2oW}6r1G}lZ^hjtE4SP07{r`5GwIvS&}klWMj=)N@HTibu`GA^i^z*z%pPN{^NXN|A#)VH -&vR5}fz4;&tRE6i5i6zye0!aW$j~~#&XY1G8` -EYxg9wF_Y4!@cakyQebc@&MbUT@8$L_o -zXJDRkE%dWu&Lt)rQ_QD|Hvd$rXJb0A|Jr&>|~8fltPg=%noRsF^b7;r0$U!$U(< -U8+%K?_3kNBHj3T**Bq)>Ykz&pU?1s=tjPb%edleRub19DGOz5v#$-Cj;V{w-E&oxm`F*8H?Ukyu|3C -e7q1oAk+`p3Qn<_6d`vAQxpWZJ}pPhXc)iu3uaCbi~*^;D)_XlH|y!}GSS6sU?@)u~(H#E&(s!Pu~w! -x)YqL9gYv!stH=Gaf(pjusNk4X{o0EKU3|5*y{UEjpYcVz{VTV=@!=^ITzR<;=~Uh=( -wJv62+P;J;q5Awvv)1Nq{_aHqj40VNIhoH&MuA40kz%w4^T@31QY-O00;m!mS#zc!Jc4cm4Z*nhkWpQ<7b98eraA9L>VP|D?FJow7a%5$6FLiEdc4cyNVQge&bY)|7Z*nehd9_% -5Z{j!;|DR7Wx7CWE2XYDYR;#YM5>V2jqZ9$vJ*&1tAu+|;5J$GNUGD9(-#8&a5<|P~oz+TU&-^^^Gv? -$(zo65*>0#v{?`<(`X3%4QtM!3KeZ|QPDB=|%UPc -jZ3lA4tap&wF7k?eweBLRyD2etsIfkyfvk-~E$f?KOq98%Afl!XGn@`&-|3j)K4@pRwg#FfN{`8q)?U -*&Rz6|M2Ar39l}gQjKK2%#l6p&bM;R7h%bh75CCFqW2sjMk6Z3xpLh) -SPt=x}0&@}Drwhdw(bp^wfN`7>Aq!?=`^cn(m7tc6NtqFFT`ar`3c8Six4+w~gK4JL5oIotdA`a?^fV -A*ie|mLuJB9Y}3;fm|kK4oPmrqEGuE0}taEe4%Q9uzGwc>6l*C;pwT=&M^E97W*`h))Ti;~{yPlvt91 -e}{Ov|-d9Py5~5L3<3N+wtgT(lgPygk&aG#?%>kYvKh$t{7Splf2^iyb=rAD -_>2(`pIjK|@9FxXW4pV96VYW+jn5b+v#CrQtC6gyM3>aWEv?I>uXsr -#wAlVzGdvJnQ3Vrq@8g}!S=7w!G_rxP-ag^u0?sO-YYJ11LiUUa-^EDb%6+YKf0gdG^6F}&ZCS@c+!^ -eOefg-}ogY<;gMZyV@JCy$9rqQ&U%{mb2nVC({tCj2iuz!7fJ)2&Qdy}i1!3CUyWj3t}8O5Bmg-5B{s -St$CzS$LWh2<}i=^UDsO2rFYA$HaNVpHVLDp=kyninveQLLl0nITB9sHOXAsjud2iD(@vGd2X}#PrxI -q5(1H)w}!eAFamycgK0C)(Whs{vkHw-fFe}{QHkuiD^}`(K!rRNQz`R8i7Q24|x)f>UmAWAY$q{|iTl*gsBOC~Q|=^kS3hfyq%KXK -#E-$k;4E2Odn63wgC?J3rb1J>MWe#GQk+hugB`el(E>OGJLj`BELA1ny*6BpFM?Zo?z$V685%wusPV; -)wvd+q>x!8rsZG!l$`2&|OJ`Ei5|VcrvY7R<}EcKganLOD{T)wy$Xjm4P2*N{D_c80b3cIxDq5noAmc -h-7<^Ycx8Gm&wIlppgKrI+;_=yagtfWm5g+}>0oJrb|)64u6~fBCK$w)TG5szrjWjHenITg};^r<2oh -!0CZyslPWeVbQZ)p@-WkLV^s -E|#6WoH`3GeW5sp0ADa(ma<*RvbhAm$vnn3Y!hCC7}~VYPNll#=qVZU;wPTX -hwA_1W&7%C?X60&(>Dm_|T%dtLE8uGY%^J}Q%0g=uI@rGt2F5-Imb2MW1bXeZ2EO)P5kIuPuKb9ayXv -(5;5k@VIoA|7YmG8AKdN0Cf9y1PC@>6fKL4O<_DThO<6oC4I*67x(@AJ1}ahDV!zvBY=$iiUI*ukg{2 -*vlmg{8GlhAV%-32XpnG#A{iB_X52A2zoD2VtD_2kvHDdAr53otycL5P)h>@6aWAK2mm&gW=UWBAZs2 -3003MH001)p003}la4%nWWo~3|axZdaadl;LbaO9oVPk7yXJvCQV`yP=WMyAWpXZXd8J -m{ZsRr(eb-kEHZOMTsCE`xB%5Xd+p!a)zCn_)$de#TBbx|C8YGp}{rV0i`6fGyZmTD2bk59~GeagxyHN(^(U%6P0WGMK<}TVy;h6)Br*v!zmN1C4$YI(K)sl;R@X -g)ZmdS*!*RHL5pE%1-)H7{!*kk*cAaUnv4GiPS618DA-u!8MRV14hDpYt~X!{gXAEEmfqp)}>S-f|jX -yCt>%nl{v(b0EG$DWqLBgKqQjiq#}MM6P9jmC>IH}V^teencjK$G+n?LH7SKB6sV}1dA{Tpv5t+Z&BX!HZQt90k*X@56BBK^Nv-FBtU~v-rDzRp3Txp&*rl}9m=lIgw^yy5oK6cpwQ$>z^B}#*SDVzyc97huq}vs`7y(u -t=L`_Hhu`(Cq6htD(cb&{$;{qH5zgAghz+wg!*t;KoPr+ -{wl6*k(f3#{gOcl(;vk6+;Ajyu6u?RDHX{WDYI_urB0+HO|p3il6CO9KQH0000805+CpNzTxtyLJQs0 -3-_l05Jdn0B~t=FJE?LZe(wAFLGsZb!BsOb1!gVV{2h&Wpgiea%^mAVlyvaUukY>bYEXCaCwzh+iv4F -5PjEI3>*Zqw~l~qfffi5py_to1lR<|#*01*v^28WP^6ZZc-61(kQ6OBcDkq@f^aTpZihq9X0yy`A^7G -exPtXgBdf0rYY-|WC03Rz37t}c%PlZjgT^ao=ul)ca?Kj8S}4oLS*Niqfw!H~7TCrp;VhQ%oDJeV(~^ -A6;8%<>e2C;c&w#$W^?LQ;pkI-Gd5}MyQ;cAxM=z^@*Z2VSX=WWcOXt=;m8GA60m8!99ov^%P+L -n#dZOtqj?0upwlWdDRx%dTt}gWzSgdXg;XT) -Qo_W$rf$7qhh9N%Ue|LfU=Iq<2Y9LN&nd&vMjrRC$vyw!>AJ=)OPC`upVtDez}Em5uWX@SISa?43&&s0j{T$dg+#l^$xrYKX@t@7;eP -F9FElLA$RK-Py#F~55rhbm$n^Y_>iKfiJe^lfSa%RjxHm;eYh@^C<)11lwQ%G`NmXfv9`b9wnBsxr~u^_k1v*4t_xxg0`Hwp9ik -H3BSb6|#Fabf46=mNnte098f7QKfFD-oV7?acOWycl+e5RnV`Vz6<-J7$rE8yBNw;Y@>YTAJXJZV_v& -{~O`CEsheopcTyxv`7rCK_b@1huqLEk%U63vgDH6vMjpZYp6p3J@tpSq$cv)0gWn`z#B+U00EGsRvK& -Aa{`pK^c4YUx*gRs5i8~s>mvsL$0UMbs%o@6>eu|psxWB(Ac`<+CzpAYtpa(a|*6}rv3(p$MKEuLwc{`pTj@ -N;+~c(<%ui7tlx2C9<}m)e__<)4ca}@bsL^_vjn4HkMNAx(I~##p0oMA5qWv^u#MmA?mgZ=aN~c@4Su -JMBTo75{lBbEa9?#SZsOO^#=K${w@UXn6Da?F4ItlN$`LW_Wx7LYsf|BKrtWXl!?%*QAC;4mS!DkLP) -h>@6aWAK2mm&gW=RKV5jEHh0022D001=r003}la4%nWWo~3|axZdaadl;LbaO9oVPk7yXJvCQb#iQMX -<{=kUt@1V?GE^v9JT3c`9wi15NuOJc>l3PdEv=0je@Z#**c25^=w~Ouep-2OVmgt0yEU6+L -+bzz2Ki>=~iMqte_V^{TMGoga-wd;^%Z*qr*IT=-)p9BHrYdVIHUe?r>e5z9sl-SU6=Kx5NF~;F73LkH|M5Osb1 -^!QoJuc4I|`()Hxl+P^-m9J;rOZrsUbTVAN*VeC|3vjlyhf2Ap2bpj2Xl -t3Ol}zs^sjq^`n&|c>70#5m$|R-Rvdpk&ip*fS(fekVhA!^a! -OZ!I1;X=(-@XCk%cab7n0_IyqK_(t-txn*MrCix!b)AJI+}~<`%j-fd=CStnof$YY2W*8o?}FFu|{ZN+=I#V#OH}tuYYb8j~^IYF6DA3apyE~oUi4DHc4@PV&VM``*bF-0qqyJJ -jH-dzvrMV)4<&K>PU9Nf$V}2m;3|XL0dj-V<-}zARJ_H?%rg`qiNApZf1Z0 -$3A%JsD8y++*Q>33-QW_EzV}7FfqwgWR^01)3m*x8lGl0;bR4+2?RMg@7Ux&{BvD&!x;}$;|Y#Dl&2c -ek7|Q=tT}zl9td;+q;gHel}r)FNR_zRawQvO>gaB(G?b4V=s~Dt%LJzYv)U$v}P>rTsW`kc=C?tk|2n -4)IUoSq{7k|y~+=ch||bk8%z3f^q{8*$40%4!y|{ffR$qAm3euVN|@BifW3EXv7;Q56_NEUBI5cS&S} -X;-`-hqCm$5qxD{qVj8qPQOIFKM`hzbKUo -n!kUA&dKQL|1pIC~s?5-#1RikwoE)>+_kY=K@P>h0{sTc+|O@pjC)j>zWXsS?BZctP_cyb!A)1H!Az*6Nfex#!c*(dk>w1f -pgM@E)P$+Ho}pZb21dY>3+%5H`}D>`z>&46hUAE?xPu@g)f^4x#4-L^wgr&%zM>1y#yD)Ab^t;L|HS% -@xb6PFunl7BqpRak$%BVGULFYZniG6Y8!5f4waT+|Cwe7yZ4zK~1r0vkwYyUg&%K!k9NsTRQ%A -=$>;WTIb#R -ms%R8lKA9@Ld0DNSvFSpejRhbAZr$j`UrPUyV~Hoh)r5j!=JYWj=X=lZ)F1(sTowTh&HTQKb{{7HkHT -9j%Z$`i8<-=vkylDHhOF6lw=Lea{8Uq-T^qyh>!2-P|xO94SR!mX((lmACJEUZVEADM{!hxee^+kd|i -?jMdu|R)(N-Y~x6CUvp8nHQsP~L@dLHoHv+qqZ|NUIZ}CPtY`4mt5;2gA!`l7b2eN;lE%?5%AUU|%X_ -V;VOp2vVkPV7k;Sm`dD<>s$-fqVZuk4~>Z^zx9|hnXmE_jmMTBOem(h6LFW-G?C|>j1hK0Fk$&Yuv@z -j6x)ZI=`zwHDjkiMtDz@5o^(m -SewsJ}~3#kvae#s>6~nTeGuYloasNBU{xTy3EGgz|Sy04)=KVJqMnT+Nd7dMKRhz?=$j~xZTnr3s;zq -ZG3)11j=Iw)~7W%mz+KM12}>z?A68P^=H6AynQy`eua62l;@IINI)NoZd3HS$=dZ-W9CB}fVuT;*x_KK1hF6H|hU;1Zgcn)ceusVWJBHy+)?b&X=3s=r -I~vq(uf;9ELgBq=M_tqp>cFiNFQmRIOF~`)s=#0{Q+?n_A@zt|xrL(Do|+CfPNL?D0JFn0Tj(OB^GgY@u@OnDiwBx7(k7sEm3Pb9xqI^o`(z%5bb} -o?b8gf#s-WJ)q&6RSPPTFFW35hQF;Aj9a#T8-oZe?GAQ231$PLk?rLxW@lln;ZY2rua?rsL?G(u{R)? -R=kG&wY}jw$Av$r*!s_+_aKjg?yH&erms=$fX5GNYysO16lk=9J9u8}n^l5~ihr^C`%}%;SZsx0yZX`E-Xhi6hB%aY*Y>fZ}J%T`q*qny -}6QGmlklq;}8Tf&}0wtv2#s=)-$J2bH}M@MWpSv;wux%p*m3sBty -D`c+N`8d`%9o|Nt4|m1A*fOwOuLx#nxqzhC^< -)!f->LbtZk#w|0!IIjVu^gAp#;CX@5$wkFrxHm?;M|(I%oIdsRQ75Z=2z6S2qii{DmC%e#anPU24UTS -4ed;7$#D!^a;Gju3C3JfF#9;}K7oU`F`pjWs2$kr@rq2>6oS06NabcKDn&NR<6xQfMd6GI+^$`dk87d -f`7%6OMI^EV=!@ST}s?(VpI3=Rp)g>XdsqDcK(e8qVwA_?(F3?FBcvEO=qCQP`#7A+6M?Y8BM+`MpV> -snbI6_0u=gmIUnl26<05jC6J8PVYf6J5%3E;y6LaSFAP5Xg$d}nQy>lLqr%{uVn4cNSZd5|a4JA~2n8 -ckQy-A_SdRI6(0zQpl*`h|(VFtbzfg^0V(7x|M6zg*5;n>y}j!`7>t{!F){#{=C>L|jldUOT$B9U1Nj -Hu$zA*DHqAmCS5L%iZaG=m-HkF)DHwrC%{!PT3{=)a^^v^6sns2#V%%BmS@>0C%MQLC$I^ds2Y(vafmnJgCX6yCP^S(|4$9uN8kd(HJ<%CagoYk -Kt@aM?eio%i{8{bWuFuVtAa+mG8CN -niu8YmZP?Mzg>`s?H+r+h~KzC9kseToz9+MBN>G6G5oOsEwx_jRP%oVB;pfY(zXz@R_E?t* -=o6Sj@b-yUniR2posAP1JNFRb{^e-#OQMEM$PvbaZ -^Z#z6;jocDL8)J~{pI<^^=M)6E}T$f-&Y`B|6W9R;InIEYhA^IJ7M~e-|Axc4;?)g9QUxfjiXEOebJ} -V_Q|ea3fM@RQv)p1<}K6B_WuV^O9KQH0000805+CpNdaCB44MuA0AVcv05bpp0B~t=FJE?LZe(wAFLG -sZb!BsOb1!gVV{2h&Wpgiea%^mAVlyveZ*Fd7V{~b6ZZ2?n#Txx@+cx%h{}sFzgXK<9(xh8EW5FSpxL -dI-$u;hZp(_+xqHQg*sEJe@-(mmzy~huUl$18T51dgn62~1vQ45xmtRQlo39{j7owE^B6UiF|s@6O8k>}as-(;%;`Dm6JS)KAtndLBHo5?k~zCQn}B4E -WTa`pN*Dj0lIypv-bj_j*lUEnM)Bqvf<*#b;Sy-&1Saw?a++U$vvCD}4drsRSn$w<)vq=5Q@7r-69Cp -i_uqa5H%v&4xOBK}I4IqK_QPk0JA!LC3Yd%O(EmW?Y$XqtjGX^}_{e;Fz0h6%7M2KGuaNfKI+1v8Rhs -b;d(gOx>R#ERnruxV90934)bm307vPb^H^JhKxTs}Iz~3%2dx3SC}}BRL&5Tz;Ut&H<$LW}=F?6{ -wn30)c+zpX2ox1>s7%)JnhS|yv;6p@AvtB0L`73$2X-a3%yNasUr5Cu{M@lP0kF7Ye~8c|&9u83^Z?{8Mo@Mu#0w$*Y5~eHSqB;>A%o`g1gTJ3F0S%;U3@`3!(;)Q#{VI6Qd%A| -%h@A1+2)&{@6N>{`_aT&dNV&yy!l-l5}zb*D_iIJm%)DpzkoPIJ4?2ZF!Z#@+wf11T!KO;y`Ug1k{(=s>aA*BbOk2sRBc5Cpre4BD3KsjD0WbriLvO3w3+;I3fk#>LO;Dq%5%GVl -rt2C(Sch`f>bl^6Mr&=M4ErW{5ul_ -HL!dru0=H)F0Nm*vu?0Aj{b7|3E>fIjmb#%2u&ogJ~-;)~48x -XTgQuCruqRVYZB3A)I!z_Bzg=qhJpAd#Oaf{PZfmxICMkpZtnO^n3#R=O}UCY*1Vaw$4B} -6fOUL=g9pvh>qRUO_@W$%$8)wYskf7jR;u*j4&iaeqyX7CwX?qFqKnd*fw`rA%qQDM6=0OYpEQ1I&BH -!?@^K=Uy~0W6#fFEmg~#&`1^kYRBUL0sp1=fr<{M0f9!t%JpU2S2AW2W}822^(OJB=w2$|L$zjNd@~0kxr@buVC}A?;R -Fc_(=&*UtF=tKAAo5!eb_@igT1axR_US$oQ4w>|A&k(bIjr8*DYTivxz?8Y>r2QWyGjkHOuirh<0QqB -<3kd71T=EF#}A+=wSs<8PrVpjFzJ^Z6=)rG_9Ddt73|ML?2J&l5Cz;$rj&kv9|rw^Qpc>rk?d!$PZon -`G|dGA9^%^-Br@V*gZu%*xm6KJNAwqDii?#Ig(J$$KrM*7|4!D1B0)zEX+O=^K7;_P|220$HYwE=xu& -r`_Fu~Dw=@@477HAX0%~FaY7X&+OLG#YJvK^hT1AHi?Q1;$6txb6Y|yP*Jb8U9$LW6Zt@JW-|tu- -m8S+E##%pKgq(aaQ91zZz1`e>t8lN@&{D+Wb7Yh5Z-GaScijKt{P2>}>(Vp#*#T=lqZs{mz;z?w|&vy -^SEd_;n~j5;{5`k3zTuUC8|F^0daWQA(%S|Nc>Y-!<1(2xrz9TGp&>b6(8 -fbeh~6{}S(*2hL`$-!kAI^;pCPJ^Nw8;$2`KAHeJ@m1`bf--^@amD_q!QA3xT^F}3H8O-&Z_ObKL9+& -loTs~ElwwHMYIi^!cVz0u%aa_kZvm+rHN{PPu-U-c4XFmX?nML$S`~5)2BfF==nPZP93(u|wQ;KCM=` -cDvdBR(gIpL!K{o!TMqodH*))$@C%7iUph|=|??~`AD`TqK;J9Jqn2hz%o*f#aHyDJz^vhbBg$JHghY -%kR4Ys^s>#1pk>zER%8Jl+0|V!x-vj)80xig5{9E-~F~CDRMbmx^4ktsLVBxPK4hRkkSSX-Fj6pQt(w# -7E7JT5i6Nbtf4dFdN#1^@;Ie6{t4*CVI5&t8)aber>fgV(s90+BCEp$`y5=sK|n&`b=``*{MZb8j#Tj -iJ*JO1JP@^2^S@!9P4$-DD;Jb&}^?DE~bX?YuLpDx{79z#n`{~2JX^Ro;&rJ+|8DizozV;f^0{Rxc_( -5&sVYXff~)@Hbs2dxyeF>D?wSb$qho#8j*J -4a!Q~@O&c#1mmYi-+bFoy%m%J%!!Y0LW0~u7XD_E|%PjWhz{DP)sX?j^QijIjG^^ZU=VZkl?jNOi2Xv?|=qeRP;|gEuoek -p_!0`Oge8Z7FVKPvCJ|CpluHTkxZmta)nPCQN=T20O)k+!CaX%HiN*JHc-p=E61mZLk=at0wlR3<36e!`XH<-FzLD8s&5X7@g%}qBvb~@A}0M{-@D4-&G2gE_mFsP&aKsXlS -jAFR>iSrQ9&;Ac801NXr(cJ*Fo-~x+DI=y5sXDB>vho+K*F@#9$Mz7uCNARwdPLNPip#22Fv52Ks)L0A@|doZ|?o?L8C}KSt=Q;RM}(tO*F{kc~!FkJM67>kbWD6Se`A1^37j#;i@W&i_41{ -`K?Dv@A(g;ezSj|&indRi@P`Ot**CEP#F6+r)%ClB1kd8WT5H(T#I@fM^$q(`(B+wePmLbaj4!`wy1w?f?*0r@^aAZ5 -^eAo~jL?dwx=`0Dv9(+a632Xf^K0!h7yfR}x-rvKpEeRJ_+Kf`88?7#T#~-%PcyxbtEdCs5)=o*v*hrUBkEX(3yUgEO -I*L|aF?~`4)t0&ViW^z#R5~m0Dvos1A8uzO8hu8M?CZ;wti`JCw!lbp<)e*v(6>XtiA^N(m{m-GJ~3{ -K+}`K>nck{BD_-i245{lTT`j#U?uI*bv+eDSsQLX_4|Mn=)&$Q!Oy?@z=wDDv0|XQR000O8HkM{d6@O -clqa*+TH-i8GHvj+taA|NaUv_0~WN&gWa%FLKWpi|MFK}UFYhh<)b1!vrY;0*_GcRLrZf<2`bZKvHaB -pvHE^v9}J^63jR+_*2ui)AUBxMvj$4oo$jylLBZfi`^Wa4CsMO#R;_#|;dkt!c)M_ugx?fYHtBxO78? -qGlsipC<}{l5Fss;Rfhe7@@1u2J)OQfzm1(vd7B%~!qG=Fgj~so;%1 -I$GiIx?X-%ZQ{nLnMD75l)#5(`MFb_GLyV -2R`uWT!8AGF?%Mq{`MN91BlT&ic5QM&jcDdGH^ATSF#t9`F)g(;R(Z05E$JdxiCV4HvegN!?e%5yeNp -B0jizogF!zOR&bzA9DxYubyerk5pTpo){d0Df@XI4P!IT$X)Rmbt8PlLI${L!zt?QD{ksiKn>b716k7 -y53vu{!PTjnys|rpURwR8Ua#Xu)`7v87ooZdqw^_T~%r~&3D%;x2eW9ARSQW4#-bvKc -G}FZ=d9$>Wg~yM7&ad`!z1*m+;1Wc2^WLmk?Gy87 -%oXi5{*j_8ZlU&<2dIvCM4*~~%C(jJ1b!|}&Y`D;K_i&y!^==37>2-<6&a{SOo^J@s<~s(numgaNTP} -R@-x`7-?+rQdT69fm-mX7LA`1qLq#p_DAIg*L2EEDf|chFBy6WuL0a -Aefsj2fRU(hzEJhDDN9dD!4GYLVK>=??}?*vfZxrRWJ!nir*pXO_xw+gz=H7Zg>|&gWyT%GEUCQ^iA` -;Q+EVqGu~0RV_7e5AoAv1#GxaFUH5;)zm!EJGCTePmd=@w(m)@Y1>^{TqSv}bTtAFQ+xz)0)DblRdNG -=R?D&jB74Sdk|mrsP;Xf6&;VD+a!Oq%JG`EDqiC!6M>VjnWt06(kA|* -RMo1(%fhEY>rWmw02`j;>iDngtdvJr-Z(Vr68Z9Y;vMlj9{y{9HicK-s7H(_S+$4lx3H=rD-*})xI;2l={il0VYgX{osTC|I$G#cRBdgCGNfHdPQ(Hh;VKZc-74N94&Jc}Bqw-@a3TeOF903F;vXN| -)j0AFtNOgvAaPiF>v@sEVl=S7{Ibp3PQNNDg8VDREFwpuO!Sjp1GTRViGn65FnG%W0+=PR;v%O_ojyq -)C$P&W=@Y;EZ#h%^q$%<=brvwRHx-clo4WZ({6-Xyrzs-@ZCwFDA$4F~)eX&V^^@?3M5uAYv#w*cp91 -L*Kw-|Jg33TB02QLRE^;8*9(>B|a9VaKII}ESm37wQ!N38iHFAcVO#y>w|iUnm`7{4@`{O2+n^YQJkE%$vM8M<&sTVqx -G-o40M#v9Sj#5U-Zp{yFmOy6$l<@Nyvg;)^PIf~$astFH7gDA|G(S+qo9VDGvB#!I87$;AqoI}tzN?$B2&!bBqS)A0QK)!Y<7&tU~ -i7!vhPlwaVq_WT0nF?fmV&4F+&iMH=Y*D5?cT6}O8v=Da^usB)I`vx`Os-_k3Ru>C7Iav76(ue|Ot;1 -Zp-Zc3hg*X^#)hHG6A{aTl&WaMb(UdPA*%l9oE3;#Dz$8l;(IeCV8@#JVl_2ThwHQv7$&L7$IRd`N+d -vOoc4gKayeX7DVSPw8Q7WY)zR8$u!sy{m(fP>f1Y6tzT#+0Fy><?M`3n+wMY4b^dnk` -Sp>p6;Umiu{_yi1S_C{!Md+K*)8xauS0Jfv>aNTU022u1MFYo2vb&L(wNgEzZV=ubH_cBrh}T&K`h2r -E9$3x1chb*cN*W+*_PjM-pftRFksj8&#r}px{`l}AHlBaH@fMip1+A}iY=>`|)&lp#WC2l3Z*G}9=77 -_CoNBCVE-ZV{hcwO+GTaf{scb)1Ji`Tum5A*Ju7R^m;QPCk)2WCcGp3?_l|lH-O!z|j^8D-PA6~tmzr -T2W{^rB`slnaZlPMFPgChw{$h3k!{H@=Z0Z!V6(08bdy?sx(55?N_;8{#OEcON8UJqV^r-75|!e@wrI -Q`+?io#!LbMa$2&#AG_OhQ9|ku_%KeTrJcoMaT!cM^;*2Z_WR4<@oJ$jcd1Cv$tz^4+BJSNPfuB7a5Q -N$#KV-^Nff7O;qPUV;#4%PcAyhA78OZhPRJ0RY>^J^*>=zy8~|ihPZH5V;~-xVv4VYZgcH8u$kqzs3_ -ZI-sS*@=PRNz;-+}A4R@7QfW4Y*KGbuH-&fr;k*Mxj>OAm`IS_*jqxAj6fAR;jK!vaKb%uGx -VKsv~Ae-afEV&t06^}5!(E*u=M^ct-(~AkjZk7-*A3A4Eo}gM04z+AhGy3_C> -cAlZ`-KPBul -D(%OrpW~35zjD}8Aj9Oho5}AN2PA)+;6iPh>;?TI7m-n8tOZExxbqwPi%{_UZ+L1s>jEm5HT^yQZ>P$W!sA2&sc6Y74xo_0thLk=tThoS^8uUaG}3>TRvcS$pwQ>D;skx(0>pv-$i{hcsyYrR40yngwd*8fAPh@(JfXrjFc9nkF`lfMd*?brxnt66?q -l^3IY)_)j?%GcpKpU0=9Bp7(tPsqwhKu+d4$-I>umfn1;ys!nS1tPwwqaF;j(KQgheuC;L(Yr*AnRX* -v~*m-2ef!J6qsp?Nh_O79l_J32RoONe0Bs4pB)AgC1*OdSm<<%dE*g6u44Lv_Yewg+xf@Oa_nwkOeNr -XgdOOArL^TK(i-CWvn}53%DYmV#9`~gPY(*3ag9uU@dwB8yV&jJ&q8H?2KBT`Wix{03J%j*Q(hAtpOB -=HRdq$ehMl@rP4cCMI@s3xJKHehw*?IOA`-6NN!9l) -k705NUrjWQ%+p7^N3OGau8moR{NQ@&xKDrroj`~2OKPe%sG_W`8sp0wZva3=t#rdV&R0F+sSI~>6lw9 -RRfS$n$)($dDZe49Jau4)IG3t$ebu*0i50hnMYgVV@sA|{c@@2QB<_(&l{f(b?3aNEhZs@%~=veq~Q7 -|@Yv-0r*ZcZX7)amDDw#DVn85XMjJWd9PtejM5|?r$fN5#zzkYXvwQBg+RZAp&s6on~iEdrFoSXaDwt -0(2M^Fpb8ujtvZ!$&(TQpcz`@orWB`H4@ySds9s_P2dLX4I)H$k(P47j8sAdB~C#+2+-xvqN}jq+>a| -mwt~lDjgb9Ipalf6h4D4euyL@OET2|>j4x?1d4i{qj2rU+MePTAa-2Mf_UDs}OU2&y>WfJjl) -vdK3^H6BrsV9%0e)-ft;E~<*h&;P|))R!)QC_ek0kN`Fpcv4pOoE1fpzG80i+|X;6AoiYk}<=1gngQ9 -$(kJE^30G*)J?kTqsk9j>{;p@`yUhS1U-2(_vKI=1zgSB+*0@@pkb(S31BBvvm-rS3e3-#ta(K#Jy -3DsqFvV7=hIdm1Hf_4Fe+%Kn^dpIrLEAcx%hBt(+S7CAXax|(_DUIV8memsURyAh&cA2;N~>cs)vYAy --*>U1zDAP5GC#U2+Ju*Y+@Q31D`8?C9BY>iGz7QhC8FxHYlny_qZphK$BK50~Dj}?<_*7Gn>m&p7V*v -x^>z-3XSm-^H;63D_{)f=DmxUCKg`H>Vzl2$BRj{_bTMhj1nQuc(2xBI6|Pc)N0inR%XjG@Uh)1)AcP -Y2*UV4;qL0H9|xMMPuP7XoJfxla0Y&Oc0QYUt90A_cTn>>rvpQ^Np+TmcMwV;F$77P7HdM*!V%Fc`WmY>_lc=mQyLtjlzb3ltjZ}WCJ(Na;K -xwh3{%4tc?q=*%N4iNM3-XCL}EyP8|LQJ+dg^&DxMt6ZuOKM!4?S{CvW~ujxRG1OnS_$+rf(JuosGFK -|2Ya6&;9%rn?X%1}$|5gpxra;IkzafBv3bF<2asumc|c{*no`5U5fzc -Z$ZsK(@BdHS=N;PE44}8ZL?l4TmU3#)*Togc}NQcBzVMCAT426(ofarmE)JQ_gyfBFd0@ntnOuxSejb -QVrfRz@LQu@z}bzUPn}9cJ{X7iI_)YJKhZKG10*=D|)}*dENyl$r5DOWv+;`c!^x`J_aG@K8x#HVT~8 -UVONh6`Zs`jFdetugS!ETq03E%DF}`}{riCdDKP7%Ap1{J*7Zlt$qi)D261RhIL>b{)&qlD>ren&cbo%CX8D?fp%=V|%F_XX3y(5xh7dPDjJmU20NcPMJ0%&2n?j>f%CXuU^$~^;+aN1){Z(wm3KLQ2D -qTXDOOPdS;y-rH+#2Wl4Ru0rSjG{2666R`qq_~dIp}3sgz-6_-w)9|HkH2lo-asRKT~U;Ii(!E~4B2T44Aqc$ciJ -#u0BC?qYfgYx*F_$hus~U~iMe6q?ZW2w8sE2UZ8-PHA9Ug0Yf#*>J^V}VY}m0Ebc^Q#av|O50bf+#Aj^jw;(I^n>>^P~Hhk-%={ftB)CAvkS -rDurUG7MVyn)DB&m#07rcbN)kP3Ux6+m>cS4(QNZ_~`i>O&Gx_7pq4|8y>wzYXqMf`c9Z$5K8;0B%Jd -E?+x&WgS8YHY&safM!C1at(@pumsTV-jr)7<<)h%|g=C|!5LMZ1Efdm>vAYsa^!I74Z4!Ej_b&kNMbDX$Y0SM8lo+T -R1ajnR={j?-?&Bl7H3W8eA~FA-Ud4lf_`(5hPUkL=9GA4ik1*{PO53Et&V1dhG9P;<8uor9t=$H=fP- -RR*MBT6Zyp+6sp3gkV7|wFPfY@#alEuc -xWOwpi_cD^hz>{r&?)u_Z{Vy-w`$PbBwKb_=Wwjg9zU7F|DJ~XeD?SmKK&B^_s2<^oMRfQ-T1NwKo=( -guU(WDJWP{R;SWa#Wb_|142Ig*{msQPP;ZkafOg8my21$ro_Iqf&DUN4mP=3z*M%9T5?qOfJRXoum~j -al$zGJpK@|5hZ}Ed1)y_z^Tjp4t2*T!O<|{~>igiWn93Xvz&O=PdG&M7(gy>C?D;+T8(7s<_8Hy-=*Y -kF4v%Q}%Ja+EL$!~G`^|#~8ru{uB=XW%rS(13lN~cJ`95#nROU(E2rb6;Vp<%f5#loKv$g}aXnwal6p -qSH|dhQk%Vdz5HQMM5w0mnyKx2Ok#(tVMww`YMN8wx0Dg_rWm2_ZB@6f79-xff!_DZh^CaOga-B9N7k -L^FuX*rB*6j4R{Yz-kmMnIL{>gHLv3BU#+|g;Ma-6CDX%CrBW+d_()Is -vIm?lbQ;SJz&&i=X-=eFq2cXNS_@=XEvG=EuSm~@M+ -NFXoCRMg;zPTk51R$|>hE_Dx1XrXwv7A -kOu&rUS=Mg%8a9G{-o*(jmd26ictgEV(bB2H{#{e?#WxPaX4hYdNCT1_r*+v`4EN$o`gEEQ=EE*0lK~RB>#Qk=U*N4PG~PWDyWoWWSymRve|1c&M@ -V)l2*&GP6*a)jPR;dc_U*0B*n@lCtv*GN%F&!pNMH&i_K6UZ1g9@PJMQIx&{{9U8SI1p6X5ZOMP=HsL -dgG`V@x!$}|7*M)6u4OuNndknvhIYvs~7tZvHXUrbbB!D1an43hl?8BV?1N3n*69$-NMm+6^cks%D!B -q-)|-BvCc-3R;}Ll1pMh>*7`#lnMvU1H>D -A$7_Jw#|y=>UMlyN%Q7U(Dc(S;LhXX8r{%TeRP!L+4zFW{xK(<0K<}t(7%a=H*@d5;E9S(c=pQhvQpR -_vxPNwB8!gJq8FV?L#$@bE%#)*D$IlITBS_Sg^1~1lWGoVL6AXhPLKk99tS*$3QRwPaVY1_9JI|3%>gkWpv^t{F-6NS%Lp^&I(iEsnGkhE*HHrNu(9>CD)FqQL@*-2@I8xu0_InOuZJd*W;~o++3>v43dw@|GuY^qjmY!3kXV@sTu% -2HOIo4`IWKPl5m}cddK{k6#fP3`v-r9+x*2ARB2Rapt--d@bXuK|X -HeNnT|$kB~I?%HO-@%_r-La$bQyj}*}4JwvvwDA91NC16u9{O+(`>{_b{)~dz&?_`R#Sf=8Cs-wwwE2 -TM^+Va!<1n@-u5dgY_{&iXF}488W|0j<5@X3Tj6vOqPci)8kVVj&n9aW|g;wfQYbh*8ZbC!g@aP}N`k -0wjY)bA8mma9hZa-Y -N-eHzlU%BC8s{dL|9^jwI5tc_QF_6;8956C+7}m3teNZ0d(0;%-|sHJCC5xlaEI)IPcpeK+rY9+Z3dZIGnM(V?DubOeP2CcGm*ayJH8gg;nHb<^!exjG6^GWa=Hn#jo;5yTLothA>@#Kf6KlSp5X&Jf%5qsPtsiENQlKEWJIr3SFI16EdC3z1Jj5H8K2Hl -zkprL{RsGSupd8U((MMeG|<*wfs=Fx8#f>> -aBf!K7?bM7Xwoj)Cz&HQ9;KLjtwfC!JZ?7Bwox5nr_Zb@?(1RYf>d#espKuULo7QLd`jaOk$)_Cz+`g -R#h1$8#_Je_$}UhoWK4&Z~%}1H<*o(FmQh_Az)C+u?x&n!2xI5{Nvs%W -6!MpoLYS1(cfAYWFXB}Rcl>cRVP;xv;z72h*X0v4U&H4LLh@yi*_V?yFW(8JM_qoqtMnK?d@6YT?07`g!UCoF6-R?Tx4eOKE4eHj$Wn -EYu1spsup0)FmFDEn#Z?mck%{}3{$%Ta8rs1|dp;t~G5iSgUHXpw>4<89G@_|kseZ|-;cM`$P-6(wkV -g|;FG5!$f%x-a1^!oJT9`B|Jzl8?;Dx9}b#W%L}3rjLy4Q;vn5QznU^c>(37|on|SuA4AontV@EJEkF -mgYY{qVl@`714CyPONF-+-zE?D$^a8R`3as -WMZ3}P;=HEdAaP?99Q#OlGbB|az@KgF{A{6hxk&iuzC81$o$A0(KrJL{A~(|O!(~=I#6&3!fL=NSiQK -ps;&$kFg6t1B+&MMjl^esfj!rE0XU*!D;V>v#m{Tvf|(T==WEP2tv*GB!U^Mhhhy4|_~T&Ce7x@pQa;c!_+)ldBV^aI7X_2FvV --nHMm-gcj41_ii3r`~pdD8B&r|9ib{12866!a$7w9YN+$e*;9O4!HxCLp>r8myUf5yJJQKQt9j047cq -Eg3%9Ns;QrDFV0@j{VYjRd9^=q?;N`kD_$_Saff@RD+_$h<1ZvPUdNSDDgF)BBdpJ-9Zh -5cLbZ@yeQMARv(KH~dD_0%zR^n*KqH0wjcwUu-W>(9kJrP8#tC@fA#9xXJNuFJo1&ssDFcQc)r5}wza -4us@KLRki4ZNV~OlahBA=tY|f1mapfX9J)vk@vp*fCgl_4CRY -`Vf5XAdaEU&z8s<%qFwt!))TeI9TlDoPw!$#^ -)s({8(0bLWn((}7h*;fE$dCMe_i8>;a8KVKFK^FxFDrgHq3|M&Iy{UqN|{xs!}!>2&EqhHb;3JSHp-Q -ySZ!*QN7kN5H6-7AZW$R1EozO~@@F)L^$L}}RR&ZQ~m^3#}hyYYL;Nwz=}fc{`KR8$?JKMlJmgUJp}N -G;tja^lC2#0rj^e-y!+PsDt0+<&$oMuJX!I(1{F7xJ*d$Jx`SSOC-vL8DN -Wi}(k(H${9-d2VyEYz~4W1ye>#b~HxK?RK#!EFwaJ#R94UFi9>NLW!_3i;J|@^*~}s*!xCOh-X(?xYfwNaTLQ%40+!KUaNxcY`FU1T;Ko)}2wEIg47EBI|k4CD5G;?z~!*Bik5rR -0>!%}W#?*Ml*^c42)5Q@(*llPCa8eijrmdDQn>U-(0EFpBqcXi^P^XgpF03O~UlRg6Y9KbGf~m+~*9q -7h0eZiIvXh{x*xNS&s7Zi5*o5){Amexy=1>QxZ1Lmk>>ikaCayDidK%{w{& -lwsPnUqj%lufFnq`!PI?q@wM*uGhfiFtFaMn}nSkBgC0tR&pZ(tuPG@2;QR>2{!X8gHYAxL-jqT__MS -}LOr60cI}0<3$PEFdp$V?GE^v9hSZ!}2HxT~Ludp~pAr -eQc-j@npPP%fWjg%u1B<;tI*&V>muD!LrA*gD9duMF#8!QR=Ql#U;+MbtZW}X@EN~?@T(WWCKErQ1_2RF;hPB=yhCn4OtEJ7a^k)Qet2yN0BQA)IO`{J^fzhg`%@DIt^>I#& -kFsM??b!z{gFQRD!Mq9Gg1l!PIp?~4k!Is8?Cu^zh$#aFPa_85!_w`{|ofuST73vLn3hxv_p3&cVnM- -tZ%Nc^rIR~q+mY-9I7Kvx>L58`5Rb0qoZT(}h5fTUe425rSk#0V8zagiEEnI~PYELg0hgqWc2%yF$=b -|>hu8)!&kR#9rxS+B86^f!oA0w>7kg;g21B1WL`2!Xidw2F!*N?MTiiLF=n^q86H9mT6)>CK=w<8Go7+}$>S%aoCHZhWjQ*y6sfS -gQEyTK=?`AE1(6zd1ANsYP4DS<{xS!vL&%ziRyx~1h+D>AL{?->7TFRsQ!C0yEdcYxxI%;kl95mKan3 -c*TosJjeLLtC5sfWH!|xe8yT*_IVhX2X`Ds)iaMVY^sQsm~du%E)Xb8nZ2Xz5`s`*f@VPi!YW>leqyS -Y))pDbhoI^B^p@F)g2dMEIP7j)?=aF09u62{2?(W_PiN8m~yqf%j70_s|TckIlt!GxDx=S*KXmaYWRr -(wlKt)dyE!hUek50tZfpYn&focP{ -dt7vdq;fK%rJ#i$0oJ7N_X4vQQ6H(x(!2_{dy#=E?7||I9^sOHpEZFaS|HF6Kp~&8`V&lPB4Wpo*2{B -QctBb~*=c$>5?A~Lwi4-boT)f2v6=H?o|VJp%UXA)dsONDrjF+InRbKpr+y>aJ1??3+Xh<+ -vEOeo^kG4{FR>6gb*ReA?b@2eOze+=21dAbjS2LKmEO5_oPcqRlAZZVr^K*bZFpFy`-92o;4YGauw%n -7N=>4Sz2#;xV?2DK9o66@MfV1?X(7S=03hDCYQusv0V!l3YFD$E9zO4v513ielQX1c(x>`+)WR2m-+ -g|oZHNYTGERan$p#!uj?cgi?<(jGcx#5N_0r$sFc$eB6g;j^P{?5}Pa7q2=ki9i -*p--5g^{}$lioS^wIa`Zo+(r2*WM0_U)ZcM!g8D;?x^yG)M~;56B8EpWn{Wl5?vtTe;j-g1jN-+-+anO()k^o%{c$c2by#X6b(pPgl1EK$gJavb_E_uEM2v^ -s{PPpjRrR#4_O^bJrW!%6yG=B-Nr={)rX%HMKLGfJ;?-s=L(f(^lQzdF%F)iO@~i;_Iv{t*@oC7eb$S -hY1;f3VpheNSUY+SNshrlzf-I#SUlDj41ORs*F422;3EMVZt>7eP4nva1s_-<9k+b!cKfnh;24s@gM5-|D+gbVp|T0By~_l5=sTClkd4uXn2X>c3a?nqIMH4)PDZ!iZU^?`& -++DB%obMV<+wutx{0T)ocwwIY4qdhXdm$MySu=wHVQ5AZxsCg&=35Nn&JzjuE8N9{yoY?-6Ia~LWApa>n{rMlE$5n(Z(H$a(`;*uJ~Iwh9QSoik#!KRINwl%SAW-XE;6_e_@4>kPr-BQl#sBNksE%CqU< -CjcsnM#YU;m>HZj`P@_H%D4g$$+{5hc -!Jc4cm4Z*nhkWpQ<7b98eraA9L>VP|D?FLiQkY-wUMFJ@_FY-DpTaCxm*ZExE)5dQ98L3juxZ;rYq{Z -P0Nu%J!10BcwD<-?E!GA&UK8(CCEDvdX6zkPQkC6SVxbi?$;7Rh^fFAwjI%4@MBNs>3JsacW`zNRqWeP)N*j`AXE6fa~uwBtLb_7#(}S&mpyMblflWwM2m{C+l@Wh^J#hDn*EqExI@2?~WdFIY); -Y)N1NZ3-0!w5kf8QpH92M5KzTW2tIJcfn#cBk;`UbN%}>q7ik&$o!>&JpKrfb0Ts=2?9qmy>M00n(j! -ul|jMhBgnR+L4Px`GoE@gMg -L2_aoz6cAFqk1+bw2}Tsvd}lZ$#sRdT~<#_B{{6Pl>p(V}4k$N$2Liq#}<$`pfyni~K@%Zz+!gk -lf?^jE=%!6X`!DOTHB@!5x$7Z*lHxg-U@H -Sr{dTmouQh_8~ofBll2oSZ&g5-K%-Y_`Bs#WJ$lo7vOn&&da7jCvM5^XW6Dj;0Mz0}0b9yhPJg -jbvf>6@URgI0zzcloe&M_gJH~_ks1$1aei2JDvd`m9R@w@>FEZ?lz)KDKH-U5S+(L3yB^!@4@`)>Z-m -f+e2ljA(4nN365kuKa%r+91)KP-BMNWd%0dVVkN2+22`e;2mN|>pR!7k@RQN`TrWGXYXMf-u~)1cTlm --&m9j@FBpYt68ErIht%t-Dz5D%CvyuZHrJ`lZLT$=Y&nk9r^3Y;M$am!3Ufn?GPkMagw%Gi3)-@_Wer -~O6S2&r`u1o4XMZ&ckPKtr6zmJdPw5P3QdIU~tT7VN=NZYUqU~Y9lL}$4j -TqU8Z0{VeZ|681r4}Q{z5Ub&8~TDc^2At)TY9U<+3c(&qQY}Wi#{XU18{g(lPu&kOvY1ntW$z<62e&l -JdnKPiqpbmAMGKft3kI%>rKp$NrKChB$TYkm&6tBti=a2oy~Cv=&#T -bjrE@XL~Bw^{`{+(;rZA%6B6j6(7rAV5q{Dn18&@itTP|$dZL=DU@}Y#xioGX7YNsFhKMT-w_5+*)J4 -HJr;(PFkxSw=$!V%Yz1RB?QDji?_mIW)j$^Lk^P#D*w~$D3$bSvwY^=`Twj1oy0d?p|k6X64lr=e3lC -!t3^e6^Js}q4y-3w$D(uc$CWi&P3T)a6aI=fg?zTU8!X`Xdc2+>kD>Y!c&9oaqIfzWC-^FkQpLIf)kY --k-U2Q&1Rp$;$|;xgR=se2Vb?y*gmL^JJFuo%cglT~!`fbYnQij`r%U=hLJEL;o`(4x$N_(MZ%c-cYN -u?_)L<2&dUpM0*^cBB#1c<`qM^la#73>m=}z4vgy^Z+{dw{ekV*I))@ZRw0ASqq0}TrG3}2XzS+0R^3e`>p6sC5b9h&wt5WQ~EL;U_kKo|U`t)A2(l)-zOYsjSx8LqqUGt1?bd!6qBYvb0#%Hql;`2W=nG -XJ-(QG&P@6G1WBi6}}T_&deF0JExF(nUp{y*G59B+T>FB{$xy!asmWaw1D%@rHU^-c?%JQTCl+bZ1R -OD=0WwX!WR$dQlhkvK_u&44 -7cKVnSwLI$n5+St5h%r{2i4DH&9>}iJs<3a#H)^aX -woLqm-fPvWq5~i8Ttm4kQ00c_d-UZq|vgV#agej$fIG-T)d^!At25G+6YRK`qd&_h0j}Mey -*Gc0F=^v}%wY0z8P5`#Pd;qjNo72FP?)MJQWHPwME^AB*EG7XKjAQJ%f`0}0L+m8SOTSSM?1A4V1Z*z1maCwzgO> -g5i5WVYH3{nJ^3Y$f-MNzoOWzj9*!*+qTw{9U((kNy_kt#ou_|o6rAw|iS?PRk83`^vjnKv_!!_Md)% -Chs$8!Sr@{h*BlURy1_!*cx2(sH9)Y;2Zw!ODBlPWbTt!?-N;wD|tu(C$7ZI2(BL!0*vc+g3cE?J!!J -cI&j)36$+@^E>|Q(Yjy5VwOD_;n2Wc;>D!a4}Qp3-gTRwR>fcM{?4+j#SWUi))vcJd)0Dt$ct?T^xWL -s#Q#s;0QNOE+*+aTNZw$N%0Z_M^rApT_#qU+f)v()?jV0y!(#!?6JHBlwa^OkyN<5`WC;~bw~eIyw$*>$NGa-fsk -H30rd0|v5fQ|hN@5*X4JNMZ7$Gr^AnE{hj)m;#*Uw2s1WJobY{1QsxD#p{7eFWwC+N%Y>xBkRScK;ge -MH>6zKnlFow{K`YvowO{M%?>T(sc)%;Ujxt%!R!*5qD?9Ejh5xL`VVf4ZNqD^NAZ$ -MWg$HJ5dWM+~IZC;RHE+cBMNEcoyVJVlmh&DjKTt~p1QY-O00;m!mS#ye4ww*U4*&opGXMZI0001RX>c!Jc4cm4Z*n -hkWpQ<7b98eraA9L>VP|D?FLiQkY-wUMFK};fY;9p~VP|D>E^v9RTWxdOHWL1>Ux9HZBRNMiP2B1Abf -e7Va!#9Rl4k7mZf<-Y8X_SHHAV6W(z4p=f4|+u3jva{^}PnW>uG4mL#jTX={-rOm3^PZ -Wv#xGH)A^=-(4FoR)bm(ngkrvO}x3NSm_E-9TQh*RohoCQZGcGxmgi+BciBV5buXpB3)c)_E?Mam8yT -?2a(@b1937xJyOVuy-_iR@Y@c4;GFtn$XiY@%Xb-&P6JUCg1<0ME0gE8ZHY_oB3&36nc6EC$KBN|NQy -W8~5ZBJc(%RA{@Mz2L4Hw`$VN1u@z>jKAO+m`!eM?@GqXXf({$85B%<2fb7iAPjz{>zaTO{6*on>D-5 -+-{I3CQ<%KL`+Zrp%nSk$DV4a%`dS5*YBG*SK3p27XK?lO*AJ4tQ9w%PF4r126bm)YFJfi6ifKg} -)Odh?J*Suo^G`D-9CjwP@bMS5eQBq~KeC+3{p@aq&L+;p{*E{_*^u7ch4=y%9h}<^oBk0>l^mfYU1a! -4v!q-w74|XR1`5+=^O(K)tC+ChI9|_=J7l2)5*ao|P1NrdU(57JltEqF|Y5M7@;-@CySRH3-^-8NfW_ -tkW_9@$W#b*$7yim8>WmR*QVk;2{jM>+5cIf@G4L-Ssu&MaEKIu%&RDiHt4xxCY4mbpD>ERAmErl_34 -Sp{A|n)I(Yv!V8FJk{|gV0baq#$bL4}JE9skrD_N!!1MIjDdO=uKpP|xkud_9o>F2uHP13hqR8zQTa -W`vy{bWBMWc@@n2%}&)DEak7OLSzD%dTrC12(up5XouN`&A!^`?RhJR!x2;qEUWtVV7{*^-pd7^NVnE -BKZ+BS1g=nJ)7(y`jZrTc;ucX~3x!;QyG|zIX)R7ail?F+9e769)Q|NhVe-fj_*>n*{B-DC;fH`r5fA6!i5qiW+_ZCDrpSK8rn6bd0|Cy -xU1GZv-osKLc`MGma?<#dO-)EbT+X5_;FzGuTDkX-DvMqaB9z4|6TAYY+R^0Z{F33-;@8Iz<6GNDl}& -d0TVoj$@Wx!jvmm+dDP}OSF|^e~U}&POr#rdt-54?!dr$E6D8z^YutD8@x?hd)&^RJ+nqRK}YS2UW2eFg62@vTbSWN)_z(kr=!Y&Qd9>%3f2s6}&+7${H{nFM=lY1Bk?8w27=-_B6^+GB2-rkc*XbQrT+4C -Au{4l!`5$5BT_J)X)%N9MQ5gkl$|Q>c459Z7RTCPs2zqCe4I!&AX5Og?)K9ino%Q`#u&rEF?cih;cXu -OksUH7^vLl~Q$pfvCv;)I7Zbl@b|X10or&_2NFBRg(r!8v2u>-BK9$Oz&g6T>@YLBLfoXe*sv+`twYx -l$%fjqq*R!adc`^Q)nn7fpF`M;2Gqh-NxVR0o=vrUgRzTWKI$hYYXt_ia}HXCJha;hBbtx!Vwd9RW4H -r`-i*!myEZU!FX=8ON?kfCR>`kHAro`kV^@*am%b59A=?(?hqecx}*$mXLo!HQDQ!yIEt@cy#kxnwz~ -*mc1f4(luH6&+gxqUwUBa63O=8q?^=bN$|+dLP=%&w)Qb&p?8oS -D!Us-s}5k7XhB2<1}=1j{B#W=iC%s6+MQ#aEF9$pV)|Y)t>h9RNZjjeCR*sg6Ayj)w$wMClENi?l^8# -!K%`$&X2F(H^T3YFG4(i=1zl83dR)tKu|=;ThHycIp!Tc(YRcU8IMRg3B?(R3)<8?2vU&pD3wr8 -)>4;B+jw})Q9nEWAY42WNcuxD2DI6+4+RjXsEx`z&6+TdqrvPuPru?WO)!hr-)?zshL#>3t#a^nHb2l -pIM?Lg@?25Kuf<8trNTUV-_@(EYj5-!qm>?Kh1d}U3~Tzh5vAl2wN5;qY!tzdD-D2vb4+~?gcW{T29XShq-1qG<&=SXet*O9g>V#@z=Tk!&^Y>g3OPAakb1| -b4LM-2FYRK=h#gS;F=oG$3)FVr%MMgUq%e&;xGa_59}zYx%((jywLiBNtT0pKt9BRim?Ay+cK9;(tpem6{DEhMaC_raW=6$(=^cDuCpfN=-AFZZ7`*_%pee -#BjUVEBq&V&2df-z#>esvF9)ZV)?v=2{nWhs6c7;j%Dgi^D%&*43|wktHW$JLSalLM9{AA&`WJsFbmmq!Sfcu<+_*Lwid>kJ!4(Uu|v9C<3VkQ5rrBAwj?iWi{3sXgScWB+oY&R -=YUEd0)!h8~e -nvEjF(2c*cg+sMrZ9Fc3)-KcSNP)X{=59}p}?&Ar%>8_D#!v-LN97|RT^4weAq#MWwrJ^=sS$Wl~CKE -(Id~TAuP~>sXcY&Z6K7;7DIt=)8r4H7^9Ky>ICOw`$@Eg<-mA^ekbX!T-HWam^5AxQfx0biqipveyaA -gZMxA;m4h>>z&%$G37g-KG1D!E6Pof%Hjl;w?lF?;{Vq -J<=Vo^~#aa%sH-#7JE}PO7wm;1(W#ln9$Ej)eOes&q3yWR9j=OYouIv%aK#(SDM@6kGsCSSW)(->%dj -PBzk1#gCcxRbXtww3IB~W&zF#*j?`v&hbbhwq*y}k(Qiiuv$tNl+}+KPf`Epm?ckoZP`Zf#qd_%6l0oSWBSW`Ton6M_~QUXK3t<>`xvU6{tkH}Ti;*Zy(b -(IK^LT7_CX{pvJ4k7=JQ81+Mg$b{F}*wxxmn06Q21Bppxlh>J<6X~~5jt8PMBgxjV7pX?$IK{I~V=#6C60aSI3umO -kC5hQTOw(jWf1>a9lX>GsCo-eD2u -1PyQz_zC4Erk62H&=eB@WO<*^Dt}eMGZF>W7Ja=2mE;|r|c=~=ydg&8=5AktJhN8w?nsk!ty@M+ncaHf;CNGEUVs2`B26*_!BHQ>wJ#mf{IJ -%`XevcRxY+;4Cqzy8({8{*}1n$U_aT@Cj*ydoJSy1}q_g0{i{<@un9<{x*Lsey#Lt4?ba2cZ7vw&;UP -8l$_>H1zO!M%fscgWuF2sQXXYqtk^cle_`5Yd0_`o)ZNrd!Wg#4-$H*Lxr<|tij^15F*p>>mo@=ujaP -XPj}4`#oyN{C4uykYU#Z&Vu-}-M_m{DbhYX0UZQ>*7{lg)-h)|%?`8iw)w@mSZ2Ii^v#Dnsg=ioq4$9 -J4j|Yy2MnZ7dJuau;+w#&Z^j8jY7Jq5nRvFaZv%yOR1{8MQpV8^C?F|eCvy0>L9!BJP2>s4Md~YG|pZ -KA2_6d7yG94+h@Lf-I0I&d37_EF%a`!%y^)n$c(`U9BSD4|wH909HYhns{Amy^M5&6DH1!0S8@l^<|P -rQpjO1A>Grb~%Gukg0u|VJU9^))Vdjo@luPZDNQ{jJ1n04(0tv9o0kW#wvyzrV2 -{|8V@0|XQR000O8HkM{deKT~fmsJ)K2TC4>IqXexXW}YU6D0w4W?FsDDP1TiJpg6rLq^feJ*=#o3Y*<;M$}9L7{lfp(@ -mAk*y_0zxeZt3C$=Vji59Tucjc0mc6*@aRGvAl8e&V%2SsW_yENVb0VD9r4_B%=1l)U0KItG>%@>*_D -b=mNG3zcCVL)&FL!;ZDEG?$m#Kdia`Ofp&5C|Jn4k+Mv#*X#SsaCDDHTVQo=u21gLjf&Omfvbc_Z|(1 -GGBfn|N1ITGV)OeiA=wNl(LvKMo}u;?9!Uw`=-=HzSg^M -9_^F8Z>Q-zWZ@Se%nz=r8Y^t|dkxO98p>_xm@LtZzb&TPFwINrn5n4`{&su -k_!=`tte}^@GYTs;oW${>TU6i9u-Ci?W1L%17uL($A)2(}!8GrqN18EBk*6qvthqOdrSge`-pi04 -7-^JinGK9~s@~K>3s#EXxdODE;VrJ(rL)7Fz>c%r9D@5?+XG@P>a6T?j=F1f3+nH~5kps(97|ZDP~~} -0T;M@qy#{9dxhX{^ba_ZZS(_h&oZEO~Rv30Y{}|bHiDHN_hv3QvQcGUa;$GM1xURvZT*4mQWOalquh3%aJk1urT!eHzCBAbok-mF#v~tmsxot^iDM--eJgnLf^ezFQ5OS6?8A_llt(kl=IVo<>W5?PWrTC5FQ}pHG73l(CK&>wvWI(+Z-0 -MetL<5X>#<;QBCMeG?sp`~2Li@p`71%>H4i^4*R2Z_YdN#@8p_@v!RhE^z!Wb%X8pq)$+XIfjGb;UXs -4^>LH%+m7D=lUG;2ty4Cq#^lz=p;Zk#l#=DAyEk@QO%n=jHwoO)Xt~FU=d?CSNXK5L=NfKA$R6iU(_h -~{zd14YM`NkI$8N*I5QYjfNcsM4`YSoZjO^$b?B6u93mvr>Bi72ArRAZYKv}`vVn*u#HZ?mtYf;f0A! -P#B*hQbXGV4Oc4v-?7M<4H=k$9+4X6WeF>br|h?*{6Bz=n!r2Q83pZeZmw>%uxf6U!q%ynSM2N3(}Q- -Ys0be-~KMQ!-F@I6@|H0pSljB+cpX8o9+lDGYLXoB>>|?Um3-iGOCy)oji&Mwo+cfyfVNGEUqI%U)LC+li~8z|X$18 -Nec%+0e@jkMc(^EC}@ZG1I>K4RgOq^y@XP&cpUHZF9q-|4S#5&o3{3I%wc=M81d!82etkoMg1!fhl_C -?dAMreVq9o`5-kIV6(4iQiNQ@10z2&Xl;eY#-Fk#H<~@_KyO{bvO7@nxbaODB!;nXDjNfFtJhzZx=|_ -)fnH4mw;0KoE(@@6aWAK2mm&gW=Y39ok1NG006g0001!n003}la4%nWWo~3|axZdaadl;LbaO9oVPk7yXJvCQb# -iQMX<{=ka%FRHZ*FsCE^v9xJnwVkwz1#+SD^BAB;Co3&+be6+`8$-=WE<)ayPc$OFL;sf<#b4O_3@IT -2cS{x4QsH0EBo>U)l*jSP}^=b{C8NO=nfPW%GH~sHPJ0Ig{I6St-Vsb(uFx%=Pb+42P}CLWv(#E|+$g -xwGX3UyJI*-2Eqvu-|2==ho%wNhmu{$*m|GSf*wV^x#q7h>aChRaV`|Tl{&V2PX4Xndf4qWLea9{8x{ -UAHNbj71cYrG7uB-!%FPDVUrUE*T*9)Gxkob%2b#i_W;D;)!wOh0786Is+&zyJc;yVY8Ke>)f4 -cbPHOpsU7}W%EY@>%*ohhmSn^uvdo#9_1+NbDY{owR^)dn`*_O|u#do9BZobN;D2Q$N6WOe%!9En44K -5dMEzvXfB`p2$@Mq0DJ*QDJPbx!^ohIvfhRc+-NN4!V4w$QQz5#}uoSdw3Ue~~?EaysSj#ZO#`FB8Id -UZl5A$*NX!RUOzhd#oA}JWjdd@WkQLWEu>C6=4L*T9N`1+Z~`I-`4Ezx~Vn+b{1PAEw^i9_AtT!Io$S_%Je%pFEGi@uc -jVvJkCY|M8!4_pb`|Q9|E*&R(;S*q^JEzFbmHP6&O#Yn~dGZQ-w35EjEXG2(B^aBO@gilK3n?p$vs7&H&VC~bn!Z>d&tTTBDtDq%2bj+IY6aXBX~Nj!#(>jw;={Zy -nSV@hf2X)k2mFf&@RB9Lx*ivEl^-R8~L_5L*dgZi;q<16@4j2wzJ -(lNw)R+F6;wPp$}HR)M(f%j(H+^7aQ+akk@lTZyV>8CdG2*zjjrHWi5)iVK=7fNJ=gxZE{I>xy9DU}W -Z;)-F^hFLxOkiJ7+%Kre0t@FmZYDl;4CN*gq?NCKGKhU{YEEu{BpU>!JTV!Kl{%7q@x>YZ50Os<%jbq -3rlQ&?xLEj#Wy+mPc?w*V)(d=f_dX&=U^mhADg#k+ozyPXlBitp(VwhtQs+#O$_2Nv*YDJ!`~HP@Qzj -+}3`@X0`;_Z5c)E0nRdfOA2zKu!UV?@|j;iwHOd3Wx?iJd1)H65x_7R(S(#!X2_Gkvybg*{s>FsdnI0 -)Cs$Txg9_lUhO3y)f7l-(w<}ba`WMv$J=in=kIQBKD@iVe*o?Joo42&+>0VhMOQx_!SOL4c}-)&%luLw^B -GRkm_qKVZI3Jn|&y~%{2(^$}%@!JKv9PSZif8u&hyNI2`MtQ+7;anQt`_J!BCm^faQJ`_+9 -RG$0tM;+bbMa)J}Z&R3IuSTmn)8982abdGsNp*Q#N^uqGhKs6#c;F$D&H8`Ul4f(Vleo>tRwAwK+rb=tc~3!JnN@l*C -5{a*|($}YUne!JjRE#3CV@61+he@ -dXajFt|K6C~@EHNY_{ZTaAB`^l`BAGhy6!RRNX)ylud=IkBAQH-Wm9Ve*0kmX>Iv*X@PG!SDzAc-A0v -4-zG@1Zxd4SMJsLj=4;rg}25q)xK?G_w|B}q?euf9cNB9irt*(g|~)A+IS*yGu7$MGb;(}vAM>Po1yW -=A-*E(uyy34y7g!y+(<|8JDPly2xLuWW;{iqi4p%)YHJ8AUOG;ex -9M_{cFp7Z|eaMVH3bG)+{JZwVWeyor9kOW^j;mH#h_wt^HXpo<+`fI_uU|l4dp^1B;)ba1w8{ -eQ%*W^GfnU!C)|9m`HOVIz=foIOyVlSM;Ry0&GPKWFyxbGl%bF(oy{Z@sVm~{VAKdvpx*^A}GidfS@N -7rE~>zi#Y;nBc31r)`>4lk{OXnda$(S374I89_EbcT+A59P<0bdSECZw5q6m3R^uXy(` -QU=2;p%0l!Ak4D9~;Y*$stRU9}ewLRzr(7d?=0I4J~Y=;rC@rNwki(kCgNnTY8XMX(}6U*I`{oyHJT* -2aOrSa*Y2#e%fz-iZ{uw7FWE#E-CX=qQP?$G95Tx9JZwnPY43)lLAnOIdF6u%qO7A545tZBc -O~1PX~0iL+O!}V*I2Y8JrNxlWG6NoU$8uWYcavep$DGiWg0m$(1bv5X1;LFkKaxPjZ@oS2|m=;m@emH -YK_uS;!>K&$jgB!&7BBz?Nef0-qm_97P#nC6@5>sv4DB8q}o_Ek3aI&glxE84u;f7J7K69q+!sv9i_( -O{RSYm`s3^ewn&y8J?P0hi{cBFa(J>Q+4pt2;?*zi(lnd=Db28O$;6@G3iiT`{cUNxi1+{Q;A&SUGlj -rHeO*#)k6&;dF0As;UTwJI1ZDknE`P=XTE|%yKTVV>jY65nMTAClm$}&#QY~NK*48it9QPmD^^_V43? -InMuoQpzm8#SO^yvAh_qnP*Dl7jo1Qg!?tyFfwMWNLe9_i2sg!66nod+>bGKdb(seoU>IY}09Mng|!vt3T4XmNm#GPeFFkz(ST1=R!;!l1I9D-&an6m>%x3GOjCeBo9f~*`2hN`l4%O=5-hw}fIN2n1ooEQUo&WYPR$99)=v^<&|%0OK{@*qe0h^(ualHtl6PfM0Pm@B$a%fwJ; -xbOTA;iPpBi7fDhd3pkX7Ca~U~!#IqgfPp5ba74M*uX{Z8#w2`(pNZ*moNF^WKLU*B$f -5&maYFhVeThv5WIJ7i@yaTz&T0vRT)OhLx1n`ZEY*8u8}yi~s)36 -c#@MPWQf`<^Vrw72_!8{y-sO#8abc(!uIcaG+SKElz5ghkyYBb-LgJ@%?NC$~rl*K~c%}G)k{yELNa? -eGQ>o5q2ly6-hKsUXo2c2~Yljq3N(PwM4(?GMY%Mu@!3TxI7oHUcLD|>hm0NZ{FG-8VKcWV+|H&wT(k -P!qDp}!H0ND`(;{ta)=tiP$}8jdTR%C9faKDCC2o`Ta%{nLd7EEMn?GD{ -vsjE>?&AjQpyYgjw5bit|1;C~BYrL|X^J&6=B`Xg_+g92C-zB=ZPp`>1c&_8W9)C~4%hj8Cjqh=6rff -HiK{FF>##o<{YNk9~K_|3Lke`Utki9uL#p*qn(CL2mMT#z2Bzy=t4@pY}&i{U7DCz}gMFBJlB;X2EP* -4jk=jv6xx0=gwiR^kP&XvA|3cx$Vvmpc*%_W6ZtSa!DB+rxz1b%BIX@;!Cdyux*aSLJrSM$C)KZY_aI -~o>dr2#n{oce^tr0UiTW^e{>fb%=`G)OYPBUD}LGA?N9ZbC7^e<^m+AA=(!LOVF#KkT8ifY0;>pGMloWiG+YXw{;7pN}@rnnoqV@u7AK+ -i=;R=DZt#Iy2)C9*3UEvDO@K($tbD#rn&p6PZqt$-gGwi3;KAE8#&dfzRR648UBlwbF -)sNpq?ldgg>2U1y5@Fk7(BWbD@JV1cF*lwbS3x1@E}1uAj$Mj6D+eaJD1SV6sQ6%t{F9NV)PN?8MJSt -Lt7v8&lA+P`Q#=^y(W6f=)ZjiyvybPa$+DbLtZ!as=r_N^Z|$b1k=lQEjd)c;nB@@+2W3^s9azkflQY -$&=wT+~d1+f%B62}6J|$yb&D&;c@y(5X9~yboS~2N(`yY+0u*n~E6uur}UJYTMCQCvs;!6{*CJR;CPW -hj5LG+ -Hjf=1({r9^#|W^)#EX&;<|&m?e}#i>sk03or$;F%CvqNw%;GO(=9!qc6&Hthu`U>AN&1XiTnFaGl+I;Y>!Q -NV{h!}6lU^4^S8zjEF-{(BjwiK_RQ@g;>C>y*3*+?m(whA>!#iiV<}hDq#Xg?bJz)P|ME^saei4x-m_ -b5+9|9bP^rHpwKS_J*f4fNgwx~^Vyr5qB;?m?`NfN^Sp^9XzAo(*-Ee#fu+7_gHv9GYyrW_-WHjHYT5 -a2d>4>LI;?$B@3j>lB+nZ%ibK0`> -9m@!2$Ykt!mrLBY@ie;5L~KApfKx1H4yTx!$N(78G{!M1X*ARilp9t%Et%=SW8shdn>^OH|=5g? --Qi%sf)={L?^jbAZ*SjFe1U_W<){G}_;)zZF;Z-8^gtfAI-YNnik6zl$bWFD#*KPj?P)h>@6aWAK2mm&gW=Q}5 -00062000000021v003}la4%nWWo~3|axZdaadl;LbaO9oVPk7yXJvCQb#iQMX<{=kV{dMBa%o~OUtei -%X>?y-E^v7R08mQ<1QY-O00;m!mS#!5n4CII2><{A9{>P40001RX>c!Jc4cm4Z*nhkWpQ<7b98eraA9 -L>VP|D?FLiQkY-wUMFJo_RbaH88FJW+SWo~C_Ze=cTd97JnZ`-&Me)q57aj~#mxN^6}z69_?wsm>}G# -4ap`>-ekTB2=jBvD7Cl6Vi!f8QBiBvF*yEPDJ9Thws)&3%U1RXitAw5w%Zu_z*GUh+y3x)nUDC5!a)# -4KlgzfX(3edNOadZJYUFT`ZBLxy39V|K4nF*Hy)e!*Vt+?bd -&pJz+G-te)ot69h&+2J1Zy=O3exQ3MLXkdO5WgQK6PGuZq+KW9n?MY!jDpRtgZB`ZMJ_$##=>MGkY86PHEyW~iYvP3KpX?kzCUgIEQ>vnnGSgGb4f)$(BD8P+peCRJmU`(H?V1MYFpWOb~fsd{DBuOhcDFYJ~LNb~Vz5{BI9LW_Ume(LBOUOY=IAyyaa3}%hqTc=qdx_*N+bqpfsc{f0NWw&1rCZpX -7K#~S=-^LyT<5INJIH}m>`bG_51uE^mdiNfbrKpkiFv+kTxALS#j-qt4vS^uD~iE2mvoC_L8zu#Mk<< -uL(^i!dL-SQH3z?LKVTb5)M~=KER7#eYN)4c9kP!BOFrUuXkMBsJ?VXUXS7N?EmF# -LjBY6D4WW$SqE=%R`uy^b%N^J0o^e={#Ij`_M5bD>rBxifN#b6)UX2RLu_x_#UCy(Af6s^`Os|y1U^L_<~2hCk$<15MU6f9|_w*qE3rcMo}PGw -p)OQ(1cZDg|WUMm|Ie2pB`zJCg7-1%qp4gFdW!wQ`KzVS~lG&b6Qt9>sWvoWFyh-P%4oi55EQd~K?WF -|RD~_0A^vbhnp)>by^}G#MsuMR(}gkxsddzekZg3Qn&-hB*h!SE&&nnytKN=I|paxdZ?yIC|T5^)aZOEeJ3chgdThb -oyH0>Vj-}ax_WANid!-y9uXa+Hq8pSrF3Cfr{`JOM1*WO;)HN|E<>H%Ba$NZB=Q$(VPDJKc9?d8GnjoRTt=!$x4~ -cC);sFXQdV^Uj!|&evduM2!YaH*bx3}Mc)39T0%FG&3#k}$d*A2uUbPjgM{&izO$6_@T{VdJyoQhnx^g@Ojzaf~e#x0&ReWVG6|8P)_LtAOcK+v`M -|E;EkGu>O+u|Vh8GxCd=l3$wQ)y0QBNz}>rIw4uO&6`N_$RTxM*wsa^IJM`zA|DPceo*shlS3_Ox5Ev -bV?tfSG<}qDJ*x)u!OYv_XjEUsi>RB`+rl@b>(Tg_N-x}|17ZR2Cuj^0yv`D$?=Em;#o4}*GVF?Ju|h -)+Dv4R5D)lk2HWDz~)*^~|!d9+E^qamE{U$6-i@0LgH~S9bG%=R1d&+`DC5!;7s<^2k<9pz}J_J_a6wr1vg+`Ek^jtV<{>t+RbjQ -@z9~h0R~(zYKT^@XrVC^hIcSC)Nd^HtfN^YtiU-S}|4IlW2f7thKBTNgigZboTxGi}Xc&o2 -(3^WePqWbE_T5=|=FpCbcfU?Hq3~$~ZAdl1)!+etPVuniBcgS@GPV&gq1zCKkiw6ikX!fX+R|Z4?O&R -i@rYs4j4R2p4$}E+^bTOR|Zwj?D1PybSZbbdQ&c4Ixv&Ag@lcz<{aQB0&dz&2eeDkt^F(EP6oe49NL# -=<})x!c+nb*+um4F2lul&~-0hHO}1@=la_9jcg5M13jw4H#CavQ9?I|;mrvxViq(hcH`l4 -v4s&{o7hXFW--=R-+n4MaZ_YJCmN_dxN8j!XCsSXQX;#>*rZUgM{F3i`>FY_0SN_i{Pt!*t#+T{(hmp -ovS(~N?rR(SL%(;W*)c&wtvaF6218%%;J;v^bf~*HQDCiSthyIR?ggi97>qrMY8UP3jaO{lXU*0H`U8 -7UhsOqEDJv!Ri;XV~jubK97kDjhaH>HH+7!3D#Wt{{6@?}Si^kONFkWz*XY6gDO(qjGHdKGF@EgJa-e -IGhbtwRt8&7`s@Iw-F)jS`75SdHV9uya)(yI2kVa_dM55Ftn?ELn2ef{b3dL7+;xyH<-Mqn;Nb6@O@2 -eXe5me68iL%i$}|1rB?-+f)*MfW!sn{VfL>)H1Xl;I}pCy;nI&@aH_aXF>NQSi?H3XBBpvAU;$Mlt$& -HCyOw(7Ea!WL2}-edkwf9T+52hL)PrQ{g~56!)0De*9|82T;rWn&Hc`&zL4<-ePX&cVJ2 -nTBc&w;A9Ta_g>}4d@68~P_RdiLS+fA8%wK+>MZHCR2AdDYBTrf-rlgIWFm^ -NNUicAW@6((!Cl4|sKjwk~%p;ez!^i#z4&puxHzeR$Gp5`xb~+fRJ@sBZn%aw$i=P;r#`B4}!op;OZXTAmwrs-GSj)LX1ja-nX6 -UvpMwwkMBTrylqRV*b>+sjXEzNXVoUnfC>15YD0q{kU|p@62G5zUT(uSf@D7iY`n9;8TAubmTSkpR#- -JDflgP?VRvS7?z7{>NT5UwJeJ4O_I8fi-=UzUH~bBk%IneZT00LLh)8%5<3In}f4H#QUbAm$On(O`UQ -M&~9_*bl!V#JUWHH4ET%>66A~{q<&?$!$(A?8ir=S9p>0)v$@@;&SO_RhXV_#tpl?h#h>uT4gjZTut^C0B#o7WcVt()aURRrcwtgSs?<4~v}v_@X08z -%$@8au^b0rHJiQ``RHS1reBZydccBtzJQ}$@&OgAG--DiO0V@!Y$9Tt$$m_vI7iZK>itx|$QHvzN{+Pqp!tBr36r$f)6Emy_q_O=u9k{$h5V}Jl**oRzlVdnNZOFs%tc3@D%c3htb~Qgg?aI=Ac2A=7S$Ddt2sbHdxBzEEPke>as%D01>m=FuG -^|TVq+W9mR5g!ZINi6cuY6vk|z&AuS4rQbR<%c!Rz5*3P@6cSp;)Yq9sBHLWPn(OZBmi9(T_1NN{s1J --9FY8%I`M^tZmL8W;uuJX?F}EO%%<+ff7QvmNY{;97qJP)h>@6aWAK2mm&gW=SLDzYK8>0006m0024w -003}la4%nWWo~3|axZdaadl;LbaO9oVPk7yXJvCQb#iQMX<{=kV{dMBa%o~OaCvWVWo~nGY%Xwl?HX% -y+s5%be#Lb>8GtecOLCG)*|0OROpVz{BrDQ(9oGYaz>&NN#34996zfTUdw1^+z~Mnz$@oh<{zxT^U2sd7I~8JG$SjXlG#4n@R*#r>Xka;Z5xTm -3R$tvYrsp7)U0S~FQq~J|dnrune-~?MANs%{;#f+r%*Bo>qTq5m(@-M+9c#Muld~$m$SVn_fWPHp1MQ -?AdEf4buTn9YV&xjxI2?zb8Bx74jDq8R~Ng|XE{6Y~HBo|2d_O{^owy&KQXb-ePvvfa@Kk&o0G8_)-p -#=P0nsRz_9eoIBl992bF-lXOR)iB@2+Nv4)a|ufSB#iC`fmbhQF0315+r34a;kQizo -oi>tW>4x6M@2sDI|1BfFiCASwhktdoiljth|tDqXR4p3+9P5R#l05QGU>f_@-!a2U -}q^kxZ~$%UoWJ8blNbb0>vYT*IL-o<$KW_06SjpvKoTSy}*192#@b*+@}PTvvMapPlI0{|xnkPX1YX$ -RX{OLg{voa+)##B4xh91mW+ps~AScPybH^Ie{T3BhnK5^6hlI3_Dg7_6+Ey=k -f$u$d-NfRILja{*|u%T!1laVo`D%WuLui&DR`7;)Kk}%RmP5707=L-l8_6U_7V!r$_=6jO^s{*_SN6K -b5Psl92l9uoz12(yyz&LD-?enq&G&KUKgg&M5IG -O1sc81|1SJ1ji{{ZVwMZs5R9h*;gaJe;Ds+wU)I)S`=n$$kJ)`6-5fSnAM%6(6g;>C*VQnX-FTk$2`x -CD%jc3wrFcS~ky+$tkO8;HBB255cuK>jgZsgial6LUEuKg9w;)VvOZ}($ukyNmIr<)67F1ea?VZ{^xla{91~LHFFdCuVQ^DC1>0_HM@AMN0+Gef5^<~b^p1|4JAK~ -n*o3pc$vod?O(7AbnwXE~GWxEd36_)$0SH$yqhWi~PYNND2T;C_b6FVTUQv`hW)J6DK?E_|C~};3g;S`_w$JAi_Y -^n3h;9Lcs<*JUuWwDFs7A5WlWU)0t%^MXbjTcP)-98j_y*dMEp}Y<^GUt6_iNqc*55<=yr+GQjNnRok -4>7wUbi2;`?-$W0v!^ynE@{jV4pCSreKLK|L -9SD4APtED7_DNfii*h46me^fITjij#CGt| -b5`E%@DNh_#sKTvWpjL2-gSl0?uIxe9kV2vB(tBZqtrI+fWR>iby<2dqC}EQFU>jwbhl7A<)i<`!zU~ -_Pa<9N?j+grDHTWClD>1~a+_1hJu~v&~WlSBYswb*Fw|dw?3PC$=*B92+1xrpE&x42yP*b^07xLqpm` -mw}tSXv0q;B1d9_n)s=fd(IC6ufoOReP&EuK6%FAEyy;SdanYm+p5G2YMazt)mv%>z()L|KB$&4;xJC!C91$7Q7nwpJ`8_7n&ot#XlFFCFGBl?)!#oT -5E{5(Zy)|ZuHulzj?cOJ$*m<8ge}*!+Sr@Zm1tNCe?Cn*e5&qmm#GlG_Lw`T68`dVf3jFm5l -l~g9`-L^Dd{?YibGivNSwomtvj0kaKQ7B+%ies`ABWaGhs%?hOB{gx54=|R -yTDaS`pf0!5lnbnJMwsHPh_{?{UV|;Rg8ez!UHQ3F^O=FGyRr%>VRWgODe>34XMzNM^e0@4CmdG1NOW -W#c%6wA5{fjAzjP2-QcNG|Xc>{E=WY(paoylS@@4)HFT9f2^c-aR9 -i<9}R_&}X38-g?O?crzdy>j1!bYb+%jOvx);%-94?k{E?hK!Ot5dv>fBL?B5*=MX7 -0mb_Bu!|5r#K*>?{X!=*OBuW>4YLkaC^OBVa--os{pD7yheO%LCl{JEo~#jT^9ZRnwwW3=TuJaBdAK8 -_D+&;}2k3kux?=|0K08|K@jM!Th{#)8Z7)oAfyHuBErlhbxmKzBjsmv23< -`Ic(R|6rumIIBcOyg`mh+I6WO6pFTwc1%P%OgNrVteU*~b%)pW&KIM? -PJS`q}9Te9_F|GkyfygPWKIroBH77*l1o4CNS9!GV#aw-dkdiiiEs=+c%m^kP7fUX>XS -RVN9sd76z|I+dE*k)_^VLY-GeM&M!E(88_3r^He6)r(vK`O&JPQ63Y8bKRNm1N1=&O6VDbVsXZnyioy -VHRh28|&s+6ko6fHHNEq*~@7GP-XYk4BIq#OU9;k#OVCC63jO(v2kyW@*&>ZQPai5Zx6Avn$KC*92{u -$NqE%7fO@B$e;K!E?1z1J=ZmR>3i2{UEf43(8CFO-|z&^<#+ws<5WVRdlpvrEV(IJkkat)rk9HfimLi -9U9>jxN1gzv}ZchDXGE@#Re$Vf!Rwa^$Q9E@=a=!Z%9Q^Yp$NF3I*xcwF-`*#@UE!YW7;bD+UENzW-# -rC6;lrWf@!JVUqcN7;%^oklmlXEE4&2hUdQ*)9cyIXg(iLCs-dooSneOGWp=DqH{=fH`x#tkW^9bmeaZ~&c}t(Jaa4C))v-pBV{TntSQ-yiBdDR0VLP{$)nn%bT6{5#K9$3N&E4) -~UmW?)$$iLh!PZ7i-s>pIB;5wc=0>uRIg-VF#s?rr=y6dTCe&ZGYOg^ty>1^@sg761S?0001RX>c!J -c4cm4Z*nhkWpQ<7b98eraA9L>VP|D?FLiQkY-wUMFJo_RbaH88FLQ5WYjZAed97G&Z`(Ey{;pp^Bq(e -Lj(B2BSkTN^BCyP_x*S~|x>B~l|PyGGId_8s5!t}H304~aw`AKo4Bj?ej+oSvMLRAh|btw^ -Oer@xqy=;-Jux_)!<+ci;DSqiDiMo3c0JZI~lqJO5ID7x7)g?L;`wkCzhsvPVmWn7U`iXBr-aGG~;-A -*|Bg@Ga)DGIV@ddqm^cZ`X}^>}`%a_gRwxhf%L83VmL{6)sV2#$(v^fHA*`?)(X|qJr6F#j#srmG@FFpah-)PY>^%d}QUr;@G^*=XT6Ivu*+~9^xO*T#Pvyd4QXohNJB2_UHX -TZ-;6;S%jCtOTiokp_qzx*q+RiXjXuzbTfYA20TO@ -x5C8(Ik*3Mb8~f3&n@lubu$nxh5)4y`F$W)I6nX~gALd)QoN*B+gtS-_6>p^3jjukf^vFiDu+WbJN)5 ->f>fn37x37ENJ*uF1P4ZfUgeV^k8|XlycQf!BuaCtd>w3bn|D31aO+GGn*Yy9nPW9R)mW*lU)ss5w^W -C3S8r%1=Q~IlY%rr4XF5sd3i8d8oKShE@ZZVjy?OInTeWdMPHY?|!h8FiI5Bi!IiGbWXNzWORf3%Nj9 -gm8MO-V;-#Th^u*(byrSsjg_n?wuVn?`R-wb9$-4SF4z;W2J8i)_hCvG`!=Zd^6*$ePExJl+bW-GRj>np=zsf)1Ds8kh>7OczD2@Dt9-cfs -zn+Brs7SZA5JD7W(1vpG6z1S=rp|#ahRBaRw9es8&i*fl8lK6+9bGXTg74-Qd%?HZERa&*Yf-twJ)B^guLm${HPqz+L$4L@u -4j?071eykfQ#iC%*di>KhM>IF&9i{2cNCQW_RA6tGn%+zy%Qi;3hP17c=|)>xnO=dc501OJ#?FMPf`*=Rf|i#2lg-I?%5@IcdCOg^we-gbqzGg0wc%=P}tbErLIoazM5i&j=BdaV(=$qhsBW4~+t6wY(lOSRhAb_p0XvK`gxoTw9GJQtduboz%jRT~D1Oo -&GR0#Hi>1QY-O00;m!mS#zhU7&nW0001O0000w0001RX>c!Jc4cm4Z*nhkWpQ<7b98eraA9L>VP|D?F -LiQkY-wUMFK}UFYhh<)b1z?CX>MtBUtcb8d1Z`23V<*S1n+r8ZdDL};9Hg|EfrEDP5OTo@#JxsVRF;T -K -M3IG7-A^-qB0001RX>c!Jc4cm4Z*nhkWpQ<7b98eraA9L>VP|D?FLiQkY-wUMFK}UFYhh<)b1!dlWMy -(?WM5=yV{|TXd9_+?Z`(K${+?e!G$?E@6esE3w%FjjxZQ1v3wF0D+Fo%7BQR)*wt1FBEh*XcE%v|P%# -f6*x3pQ{@*$DP;c(vg%nX%t_9Kt?h0-Nc8+NY~p+)jq#^rRz;*=Z1${jD6u5!av&fcBM9sGGQ|7!%lz -QtcJ@z*QH^MnaJis2ne;%vsQj1s -?!d!m)>gCJt{&IO%>eI~`gP)C788Fj=gRB{uXDiB;OhjHXZrG}RI}@K{QIzbCsBX1ZI$)7hS$0}|9iz -_fE542uSjMgTS)9x#DUK2Q#M4T+=ets5^f)R;0lqjpJF~tF{hPqAnWdTFfK)lxC+BXFiEJ&j0Y@33ag -HfmJ0bdW_CpRcJE6g!2}4dSGC;m}xFYYmXiMW&*?72~zVlG^Yfn#_k&?h*r#kNYhCTYns67^{* -R0^H*lo^q3=BpLx-w>bL=166}M^iG+5>>6!6ODi^QfXRh#Zt$1+a4K%sMVz*V~8B2KFV?j3=M>i%tgx -JAY||yvg^*VTpZw#fGrJ6U<}KU|T`GtoG!?h90d#=pwjd -COy0~F%`R@Jgr-;41{ny8c{e1iH2WPxQY{0wVx^lIeqUj>`4cA)(f4s -}W@*35IpKFDp(%L-g5P1lPxt08YjY+aS;q9gEy7}!>5A{!04!ZQE}0YiyMYCdZb^U6@PNZmafC;-=MS -!$H*EYzcZmSm_YYCL1#v+G7%5|c|{{_#`@I>6J3_*{q>y#!}6Q()-pM$m|DLCWXJggtL~&T4)&nD`$T -r&_799(SPeLs@DG)(`=13@l>{=Kd@n*wD{;6LC4fZCf{3xx9J6#rPO8`}?iR#lz5sc%ByQ_dm`$<2E} -?+d&h?mhpJv!V5ecSwM=&|DIrhFY|yt3_X>EGgBF{(~SLXvG|^;WGNpOr^cn{?1N25a2RrNWVnRI0Qi -*JXo$|uaVOyjZ557|6sZtfcycK9(LoP*;<2*GSN71;K@5bOHAZ5w010t7Q23Jc1?-o49>yL_QEUSgp> -b|{9s-jO#R>jE0`U>Byn5(OEw3M%*@g|caS6MU-;vDw9mi@Op<8-hYdIHCtM6P|?qva8y4QBF -oJG9J%mnujrd4j&Ec4UH}IK!TVZVzL@5CLMmk3Yo|Y!y26H(kEp-5Itxwi*l#$#bN)Px_0K$n_7=uX) -xSA?q*IAF&Yuf}kVjW%6w*VfJqjOTv0dCG2AK#` -bnKib5Bo5azlFL1&#hhI` -LXQCHqnJT#Jg(64U)6*@0>k=t5j+(oRGoE6USk_B9dj#A|aW+#E?R9$y^R -jLtV1s2?dEq^ctT3^6Xq^}t4mdtQ+1e#DMpW^28brX{F_W#Sr~Wjq_5JX+7$48O2Ji6B0Z0$gM@vE&< -=iwnv%8|W|9G3=reMea{TLB&b#st@1lKs?1%YhPS6!H-?e5U9{NMY``1s2_r0Rmr3)Ba(Dal>?0d)*e -nm=5}m^{SukjLIhC?&Fw%cfJXwlq3!HMYq#D39o^Yd*89&M7eJ3$vz{X{peVrs4viR72Y2!Dh6O9Nh@R -d&(MCI_Ch?LAevtuqB4zijH&*-rhki4Xp||F6IVwqq_%b91u}yeA9^LIKA=bFXZ?&-Svwvbl1*xGgk7 -w?aHXfegWy+NJ?%go3vPTedZyYyf>>N!sa=Obv&CLviQ#nP8MpzOa)&3Qym#TS_?24w}AF6`Q8VSs(`0=b=~Vw -z<%>zcGPP_kgzx0x#p&bSVtMg;1&`6z+}!uM;p1)*2!N%L9L7=UHZ4@5#Sv{U3qf3RvsB3}cWLd19>nUHsEaZITUAwO0c!nja*g3^(;^AP{Yp|)?@s47R~!}q3O-q@29S(hB -c(v}GEG$!LEa*)0|$5Xdk0I>#2M`oOIc73>&Gay~N`ce=L`Dk@MR0TRlji-z#-Pn-BFpG6kaj> -BA7>sWP3`7aEGSFBkcWMKW;;kw{NE~!o4Av0*>m!1zRqg -QtzQ%7zSAi64Z*yFoaU=>e6|vic{d=dNGl-KAm=;x#nll&R$crBBiDnjEs)KBQ~{#7ldBDuy@|a)&$o -dh@`fAdpxE}0GCik!B^^y-uptHF1RvZ -UD2-%x(YJ{lj{!nwm4B{FV@mmFz9e$={Q+rqIV1qu^kpv^qpWv-pYPvySPq*BW>rz -kD;a-}0_K1D1R_zCPMl@(k|gYCKkMQ!nQ{~B|K{UmBcjuq{p+236-wpZ5Q$ueFPbl>e6;HNIfVmvGME -4$!O%myPHYe)*1#R4C#+VC(4UdLa}aZEBC3~eqTabyp@4dXumE$>+0bDO`vy|0%OC?zLz1qQ^Aj|U)bG -}_x5l8IbleG!7kqm5Ztmn!3wmhCr15$5l)K#0Z>Z=1QY-O00;m!mS#zrSWQ|;9RL85bpQZ00001RX>c -!Jc4cm4Z*nhkWpQ<7b98eraA9L>VP|D?FLiQkY-wUMFK}UFYhh<)b1!pgcrI{xtv&s7+qROw>#x8m(= -qj)%1fNIbCan%RcxivJ%63#H0|YiI1~w4tSOQuNZWdE?tj1C1wewIlI3_Z5drKj7K_DV@xkSAIQ*OiV -UPt}uvMJ0#}uBUJLbhcXW81#Sei$I#Sy#SW^4F4W^UxOx5MFZFnGmI<7Ar#ck7H<-iRH)e)IZ!`0oey -fk%J3n*gwQ##0eTgI553&C^XFL;z$0!PY$G%PqT0-6-S!n5|OIS-fK2+D-5Hm}N0kwPgw6vv`@gL4>T -iz+AEgasZpHfslw-*`u3s;>;By_5v3uGC%h64UaN6!x~pX2yL{oHD|+x!WfRI8lSr%&;xd{R2Q}-0*hLZ)Phhz3-cJt}#^9=)t`Q-BE4|esDO)mdn|Cn8#joI|~>-luCU{`Y ->HoLe!pG_fmc6oaK`D}LiEBgSLmsdCJe0DLr0kSt&RHY&|n=X*t#dLo936hf!v-8=_A7dc(advZwbU$ -9r*@RtB<~Os`&*zglyZ$`CzFJIyg)<<0IlKHghkB+L)5{wh>V-Tu{S6Xq@o92?j+Ftm$!BQ89JyqtSJ -!{cXTN^BVV|zf&!&+1VG7($KAcZwWze+K^U3UD%+4kklV7KVbp>>=g$O8lWM4i_F%#>Yz`xU*+0`XB= -Je|FW)8_Qv~_+{Ab*)Hreijl&lV__kMpYwY$gg8aIT09U|voo9Ta-CZvX_-pBK}DBs-f<&Vkwjk!3?Q -xIGxGQczjPS>;)ta>rr81~maRPQ)P$4@ag41C_tZBQJ~NQ0R;REKMaN24!@!IMr#fEsC;j!o^_VIBpJ -uIEC6y*sx)=*iTE9^-Dt5&yLf(pND|<0Og>g67P-u=C%DA>>;%0v*4>dBvT47TTpQ~6`Uz}811VSm_1 -Z<)Ny-o{l_to0M2YpGKg03*Vnh~`I -_qx|BPMIvDTaiY`LTSwrdla0zMNjmIzD<(q^92X_{=kpZ|yO4u6TQJ?Mj27v?l*5>enBui4&eCla7Km -8CTcc5t$)7x)Wb6vGa|N@?G)?2Qit-91^gT$ZlDShxO*u(gKfc?G){k#%w0s`2-(digEL`7rz<03F!i ->?2P!a*3XX4l03iMK>EyD4PZV`fFhV38~G7<2riAw|IfIEuA&w1@RRNZ-F1F7S6f*uk9HZURu)+sTbgLWlAx|%wQM)Fw@37Xd?cYcz3)+ -1G+F869W@msyxhapdkqdl5zOJt&u?*EzxxW-vs`-I2c)e7lJ1 -SJ{Qax%zcOhU!a?-M2%o~;A?{Us!>V#CVnV@e3V&`zyxF0##5+S2NjT($uSzy7#)*Ld4Mg$BLQqfYSf -of21N&6fJf$ENPQ|hb4M>ir-a~hNBr^RGL9;pn!~hd8AK)PBfFqdZG<_u7oMhJu&1j3w%*&Nby$h -pXysIl;*U&2amVT -t{!Cd8&pxp)q_Xe)&)moBvasM=1XE142FPZTj(=DPt$;wxq%#G4U)*~ZFQg(gY}tX4b_0gn|#0=tcOO -Q%rBDdgR!Xi;PHqx_l`+NjlKSHiBN6SW$$#62O!<%ZXnGELA5Y~e@$!9f~l)&HbawEuzMSswpQ&DPQf;dN&~`xq0p0YI3Rvb9>Ep*Iri#hM77Y# -c;_b_Brh#g)DCJ8VgbreawyAcY)VDV&(Er`K;^AG1{u`9KoU!`OvKSR`k9$S!f~VFQ(;D@@eT6EC)=> ->uP7P#D708VK6RQtKkxO^|SJiw1QKT4;q5tJCB0)S#*`_fh;9sbVxE|NcLtHgqtJZlleiG5Z@V97Cie -j0(~0>3lSO1x$;sR_gp(vif&6w1@D&#)ZWr)@U$<&RT^b4ceSTpI1{^2li`w9b?j_y5)o6WwyInZB+m -UcXf`DaNRTA)!2p$+1>iGtr}Ljb^qYo7Kbi%1HqXbWmIepX+%h62Y(*aC5khKmFU54iqvpc5aszTp?y -vd*4H>9Txb}QX>F^qWO!yHD_*NKp~_`2gkQGE=<`4X5$*(f+@e8Y_@T9RsU!>kw>e==YfgZUlyc$2lw --E7t!kLm#8MeKfp8MnyLaz;dO({_{F_S}S(<~|2XcQC!F}D;v_?@R?Q)&|1;990d#qrA*D>wjf;7P(r -YUaUiiCSi8f`&lu|QVl@vZ%i0hb&t?Yke0#jnYrYUJ7#g?HM~Yvj7&$OYHUP)Y>FO_g4G%; -Nbw{fFa?VuDJBZ;o6PobhkDK*03(OlU@J$RFo#{MmB2Jo6LFq;%894eAUT1i$Q4E2VBi!p;7R)&s(o8 -yswIVxEFstY*rB(eownH(y5p5Z5aa -(v&S>0ZP@EVf;1-RueTyGuc5qh>|>$OAf`GT!EO3rtYItz{!VBYE -WO)LJn+67tY2_`o*LG@TY?WXrK4!BTy{L|7C7oKK7$8+wse%Wfy+xMt6LN44(=h`Orm*8xvRyNjbQ}U -^!H4ns;Q@sep(Rem8YL_u9x7=LTJvVjc1)=dV1@2t5g4kxl)I`S>7*QyWSh7@q}C&jOD$&V2Ub -FyyqY*vTq_pM#^TQT6klsFFX8T&DIwfBJ@s@ULLUs89g-(@ny`t#B^e-(xmvkY>`Pt#(){e3&h5gZ1K -Q72G9w;6s+~l+xES-HwoKQ?5jwY#n@VgY%ag=Pr_?8=mnOer(KY19$v3X^f&_-gNWeH -4j@qNJmbW_P7CU7)D0*mdkcn1X`g@KPXWYK}T2^~Bm%M>nVPJ_9N72pwBmGDg^h -X@kT4nlZ}S0(hJl0$?DsErW9e!982Uhwomc3U%txm!&2l1<5`vn7!xYT>zs==3m*9y -z5P-A8>@7?{1%zptujcK;$JUB!`7#*f95A`o}XK^k?#g@Co-GFIAiA=4k?iZ&Tg{mrUtcXnUZ?{3-c) -b~6M!H&u48c6G9GwmnRcCuZAP#AI!?<2`^t=~&hLuisR-A%HOSZ`Smmub9=v(j0qB^6_4ZX2zqO_(O0 -;kL*OH9^QkpX;LVMQxh<{o;IneOgX*icmS#-Qs;U-)E0OJ4E%#yM>8rY5P#)4&HzVZ%ZToIyu?=LKJNpu2C{+M=aEn -?-1H5h`k9!P0nkSmLtyIq&4CMLBNp&l!p>@O2pq(!&)r<{C#_1*fRlQ{AV0Q2cnwr&&3re?gMf2=p{Ah( -ZO{Wdq0I$-yh9M!dyHbH;)diR3RQ(Z3+u(+{}JHDoGa>De3k=+^o>$=VIoSiE;0?3NUIof;MV?e6~^{ -frw{e>>Z2_5bow6>p|$Iyq}Mq;?1<+Vdsdtt^;RiMm)=%Iyi0G3wNM+0oqC&cFi#@;W}2($RdrWo?ytRo7biSwS3q4w=N -TWk|6%AL0p6j*J4oP>f&UT$c+iWTdko;M>c0aYppG?-L-6l-mZb5M+KPWJ_FNSo(()@3+F5|t8@epW4 -iad8&iKj&Z=dYPCpYrL13Lm~&ACtPyp)bS4Y^KZj|*{N+W`A%`w~WIJN<OUIy(8yh)y|U)soTLWsKSn^4m2I}7mG@>?|U+fbqwemkaKiobXX0o=2F5&p~gKXtt -|e<6*}5nWV#sTj#_zRXeqRLUX69}b#<0Llxw8|BF%*x&13)_gp`=a2`87*_xnt`LY_k?#&it_V8y#5=Y-~^HUS7d$cllf0q`-sgKrM)JfIRp^ -t#;g|Xmk@IVeFfbST;1h?ChEz!HiLWivO9Sjovz|kO)cMOLP2A@A3wDR=~h^n-Aj|c_d91@z)?lDmwL -VdeoVH|8#M@Ct&|KOM-5>DUcb)UV?(>fHchlM?LuB337+LieIn%BeWvz5>6%*liQ_; -o$o$^2A!td%{X%#nnqXcMdErov<(-0v&0v4@E*RtCSOABecKv=i1YHjER?iyF}-bnP`&Pt{?(p%dM5_ -))I7E)W;yFgTsh$PMlS3FVV51XFM(>;B9WfgMAeP_uSbM&G-gNCZhTP9kdKlndM@c7-9A&r@ryE{vCMs6bQ#A<8$Es!a$s^w`z{D8k3C2AJu# -78HrDCR8#y+F+(}$}aT5^&jxUgoC%PG58L>Yx|T9RQF#(?-~6shfNj33bLj#2r?VVSfc^{0MR;@$%E& -Hp}VBFWc6cD{Tc+{cx_hfMq3Lz9k+p1iPH$-5%hy>W3OPe`pnQ|Y^35MpPxUKOd9;l4$ai -$)5KCn=8BhhXvpt&H9Eq#m13OWHZZ0T^b75&=6}+xZVyM6-UMQNiVUa|il^rOZH$znqZiTJXy1`)!f3 -4k7Z)yok|CK=rMlTRTMD<`dO39DmNW$E|%5mrx!gHBB@;?uPCfH7l%)&G@eG -Nzjgtoe43J}0Tou#Rq1W=GhMNkp7=>2g3p?BO0vo$iYl(Fi@kgY@vU4dc5TG33yR>zdK -tp^PNYB+|;>&DYSHQdq?cB3koHVc48>z;3gg|B`8?f$XN-T@uoCLn?^^cl-2D9CPn1=dC(po?R>2uP? -l{4fyNZhQ%4;y(oZ5u0VoPU;M5I3NwNbZ9`!Ms&l=aub8N?na~-JL?>NXtHXUYAI0vqzrQArj -r_k7sp<#5Q_k-_Fnw&+3o@<{6z*fFMp`3t(N&u*z!`CHN^Wu(f4o6MO)sAK^YiNZ;cKE{ -7)G=DoB>*o{Y*CMA7KZ`&wPujePz@K?hAZfI(0R -0ni!%pG_`Q8Z-QX2Mkc4U>q(yH__V{g_g>Px>{mZ*#@O|*0P0zIQ1d_>+4(76c -;LJE%Viw1s!Pl>T7U2YUpI~axz{P!)Ulx@%E~XFNcp2dt_>DML`YSEp=6AR&}{YWoa+KP}%2c<6fFr* -t+Ey{ZwR@g=E-5azKmnoXYWytV+4saH%yx#hmz?o8AdI5#p2Tfm%2u3EGgt$f{#k>N1*paAgLKUA4We -p3WM~Nq|3b&oRQ)$vqtX+A~NW0ZjX!a@wr1vP4a3W0oyjqW8!+c&(AKbcnnxu|^FPSO?+l299N!=qyy>vFT$HY1Y+D^0 -Oo0;^QTkbV`16jj6h_IJ_$v&+V50p`8n_Mvz@`((Ed^~#JQXi6YaL3eA^zhk{-r9RTXP0?RS(r*zO -Us?u5?L}zl5J)5=NIw0N5$p{*n?tY3=I;PL$Y3jK@dZ^T{6NhQ79F;Vf`|{VW27l4r9(o{PBB{Z9^_G -%H0g~9E#X)? -_Z;v6+*a&Lf5@H!aq*E+ORsdeHnT!>M++%PiTnCVkjuEou6W1P*jENUycAY89;AjQI2Z@bPY7J%xq0Y -V{-;J?KY|WE4+cgWS|L8nN7)pff3}tSiISqzZ8m~8gWI=l+qFPc#Kkd$t@vzje!r2rgqKRL7$_$k=!jsf`+`T%tEroj#6@$S2wD`G%nUG7m5a;G=Fa{={2Vu_Nct6E*XeKK;&_*qlbeX7^5`t1uVq`mBq208a+81eFXmmnepWBsHAcS3O1&%r -gO6=H8+5B**e}Tn<5;H|Zo$Me856JB_bP;k_G`9?N6uCc?u16O2;`>#X1|;{HjwZ2vN!TwhP;&SbHe{ -d$QXd0Esi5#))FpC@WT`h0yhxtT5ux$81XsOJ{9hc!Z~DwYg? -lB#bqq?WU^Lc_qpbYy1X6LdHEL*)gA;@S@8tHU*NWn5OE7QUSd>7UA&kvgz{jLW(tbR+sU!+Ua>mU!i -!1wMCCeZabI+BWbrm5uUs1KLhoL78&Z$ -gNt4gANVup#tHB;CfYLm~sSZ+(B2&ILa0iPK^5vkUIB~ZzR+{;#h%lCicp9E8)RE&*f -#V!ieJz6C*V{tiE*C-9`;{JUtAh*oG^u)Eh%P;1q9s0f!84l40%Oz^0s$Op864GpsRrC5%*rWWwSXE5 -Bl}beS6+e|D^ZgUVX`1x(I#yGq>9LoBRR)<)_2Mv-PV0oS?G6SXbX)cAOdqJe#%2raLD9CAL6GNRK!~ -WP65V9E|z>m?6aBo7Qg7BV<@jd0d>%Eyh)VMhH3RXc3Lr^&+kIDuY+wLE1saJ%7h^W`Vt84Oj6MCBnh -|r!yrZDlaPh9LnEtQLa47UwV3hd2WP6!FNSI1MaitvZ(Sm1lGS3fZ -(~g;99EDk?t!k6>DS<*?0Z4bUSn`+r>a%gEh6m}KN_3UvJRw+B_&X1v#zV1VX4(OK#WG!Y>KY=DE)6Pt0K -8y8H~ye1DvuBX=wL*t{G;q$v6-Vg>M6H;-75yKSA2+!>8k3X_s=A7d6_!Dvmux04(sBJ_L6!~%>*M#G -3xlz+D>N2~CC&H+N(JF$cmhT9lr41swoXfXNDqf -PtfWHy8>IpXjT5B{u5m|E}L~ek*WiIbMma)9mcL{l=RGbyGSm~#P=EW71SNH5gWp>;2*OxiL7U_(O -DAb4Bd7%!1oQPsgC52!4c~G~8=yy0k*O`d -fM;4oWdZ3@x254ng~_0RQEdUXhpl^2#qD4Dp@wpJ)FyE{V>UbNYfzoV%cU)X8l8^cdki9sEIvm6h$Lf -v4TCI7O^yuTxUmP73T;wk-zqLFj|Tq_P)h>@6aWAK2mm&gW=RmRXyx4i007JZ002z@003}la4%nWWo~ -3|axZdaadl;LbaO9oVPk7yXJvCQb#iQMX<{=kaA9L>VP|D?FLQHjUu|J@V`yJ!Z*z2RVQpnEUtei%X> -?y-E^v8`j=@UAFc60CeTpCUP{q=N-UKho9=xb;5VFbC29r!kC%g9T?WQVJ5H`oj%=dkg=lcfB+u?1Nk -t6Q<+nYKWG6zhPhYN|b;EA%h!5J;I&zWeu1>aO#&TwUu8Sd8Yjry7s-6j3Y(MnJf&ad0Ww|Mdc|_6kW3~H8(rFupkmyvKuG+uwwnMwcx7W=VP)h>@6aWAK2mm&gW=XBooEroN006=c002 -|~003}la4%nWWo~3|axZdaadl;LbaO9oVPk7yXJvCQb#iQMX<{=kaA9L>VP|D?FLQHjUu|J@V`yJ!Z* -z2RVQpnEUukV{Y-Md_ZggREX>V>WaCxm){cjUT5dGbM#mG_QJL6mkX(_doN>P9oi6#*SiVDB@q=oSLd^$JIYq5|?O65}LUKvfLH4D04`#C73MFiHcTpscGqyzM;wFW!CTad%Xj?G+vJB^;|hBRV4|(m%5OaJUm@bo>QfAshuR@!cuMu{%;{&^u}v --@Cakh7nPc2L^Sul9v>eUCU;rf&Wyb|K0R*CWplr%dOfwMjrC+@uQ#3EO6!otbV_G5IL*Gx9%hdQyJ3KU- ->Y@Bl6tXf@mAxi3D`TN`u=11O -O>RJr$Wbg_{U1Ww3?oa%E{>OfUbd;EF$(1*wZ7o)?R!bHzz7t`USEb(a_3;P@$S+kq -*bu=zTdfi7hEwk!#wfcM@9divo!)LBc~SzEZSP|bD_y-x$#Fef-bGNS)d<4(Q|T3dn?y$PdYDVm2z`gbWGF+WL= -Y3nAkD|wpNm_3$CamH#aNPLV4!N3@y^RY^88U!z#LEZV+s?Evr!Ir#(V%_5(*iDZr!G9lVsLe0zzWUQ -UP^J*GZAfWO*;=xN###nlqAEo*L8WI(xq)77!r8tl^Uo+>o#134sEIG+nEa*tC;xwZh= -!AvD)vLb_%Hj;wxFh7q1zr%LcLb0ViBxpG3Qt~&JPpq**iVcyNg33Etl_Tash&{N~XnD?PO@;9lRJdb -m!@|uPOF<^;TIwPhy#FKn_@HSRS#_eyKvT5bO8I#*_^gkP1}Pc4t;zm&H-T@K=rrq`{!eA<*ukq~<(K -LNDCJ@%ivqVfY*ZHoIzMV^*0uAdT`c*CfJcoXCXOwT(g^uR;$(UpL)#Drj^Jgl=j1L00&%ah~uV@L{S8=qRz6ewPbN1*o}tB(T*yFd -!>oe#_h3D=LZl}q|0I2OBR%xb^5rB -=H$`MJj{F(|t+$v3kwS)EP+}I_ -)f+WIU4rgyK&~$9d&1aS$9xnik@$KgsooZrK~N5LdU6AUx~_O~s?tQPpPi&bStg?D3-e6T#S7ZliB`9 -*m@2blMZ+J1W;?#YHi?T0@>BC{)CG*$4Sjj5`&V6V98^jCS2I!s+fHv)N-@u+_fYJ19o@c5}N2$K`B4 -{R4XU?%n>B$~=zD2~QXXfSfB#7jXdCiEL|XvBX&nFH6ud+uQGyF3u65^7|qE+e&_=qV3PuD_cpOv?o% -GlqYu^U~kUqx8cyCrT*^JRT$~?pn1nhsxUR+$vJep2M;OPexYC;j}o~Mb;5(#XcKgrraKRLH&}rBe@1 -&~zY=5X6?6Q(&)%23gZ6;k8jMpsb8b1nGIA%J-6p`-Nm*fPUCAJ!_)-a-`5RD60|XQR000O8HkM{d8@ -Oljb^!nYd;?X>2cFUukY>b -YEXCaCvP~%Wm6147~d*7U;nZVB`;a2#N+VP^3YW9C9Mo5*05jiCdC#lE3e6w3OwzI*BuU42Rn?^$wsN -bz}r^p^wDs{7$GF6=8~3-`p*++xhzN=D879Qy;yCUFd>-Y+PhIh6YQuINf`7qhRBd+z#Cy8LwAuo^@T -*Fkr2L1{)RxP9QW)F=A7)8UKzbYN@?HAnegM$TDY0q(N}et&LE3By$syZr<$s%B8QWe*dIF@m)8c@!d -@Y)%|t~wiP_Q{CW6wpRy*ziy$ksVR%K39EMVsW>a@Ws56_S$~m0p9+T3v)}N-Dx)I%L|9jepKengZavF1?E%rOUcFw -Vu+}ORo$&nQO?WFbm2O7N$w|dug*VODhcf@3UEu^D8jS9I$xyz<}lJGVJ2SUx~$$jas-lL+6)YXLJ7n -P)h>@6aWAK2mm&gW=WV>WaCwziZExc?68`RAG01+f6u3%udrc1p@*%hNUW2Ad(0C6AEEX~?Q8s -UBQ6MRMeZRgle9=qn6n6m<$Kr6_pJzsuRy)Y^sL$qO5AHcHAFJU=}=+&@nE_kuST?%mNsYo+G^?_fnjD<~B=_0$6YE{p|L!_&*Zfz>4_yX6`h$!w+c& -Nx$a1#aOJlZLwoofB_3;=ehGx7_d|XB@d}nnk@4HP1Q=L3#9GGAVfl!sLajYdjFD&P%PDhL;IFN}Yx0 -5W)|p;f1I~;nZDRT)59aI3~R|u+%LFQ2|B`y*gA5L8D8G*{W8SutpiF3@@{ZQ!;OuW;=JUPshBF%$WI -VYi%RNI{S6GeE!&JUV&Qw2mcKk9+l;D_@o9I2PwYsJ_|E+4ZS%CECG`Tb~K=!@330AbgWijl~X3Ub#X -*hutneHC@#&5V(V7ID~hw5RPR^W|?!ittzgmqH9* -BGeA>s;xT(W8dMg8F4llP&LA51$35SU9>d^q@!f|Wjf#~XZ$t_Y0=>M*QpN_S -s9P0ts?_}?%HL~BrX(=ZkUwnz{`BYL|(C-kjEsA%!A=t{@sN~^A$Z4b){&6o1n5aaqT&5xnoO=_Mr4P -Aa4oje{*br6J6;Nf$Xr~eZ!2cah1!u5rH++0%sVu%?TUyj5*pc422L?dQCnKO6Hm1CS4m~o+;A6DT4? -HO*0G1B41=LPal8FUzUF??tUK>f>mC?9vh^VMk|XWs9-fZj{w9ohTfpPep&Quiz`ojb~2QMP_XPjk~5 -~#$*akQFu{YpC<%@Pj?nFr@3{1#7%B6vd&p*tB%rYA|xLJktc(sPrWWcn!2Y(K;weC&b8A3t^4Z;q50w>_SB+bxetdiHPxYg9>=& -T7<5Ao1X_7xiC -KhckKnC|BG$!1k@+JpR7^+K_RTV`_6>~9>y^=Ui%q8U -~kxrQQTF;3tmCq1q<< -X8rNR2~6W24)^YqplOU@X-@QAwNA4qvFcHP|m0lniXA*ZCMmgYS7}es#4$Kif63%kIi-*?-mHs?`!V_ -aCpCR?4fJ_wUbKTDKd6>pw-EDZRN8#@6aWAK2mm&gW=YJ!ygu3j002-3001=r003} -la4%nWWo~3|axZdaadl;LbaO9oVPk7yXJvCQb#iQMX<{=kb#!TLFLGsZb!BsOE^v9JR$WiqFcf{yuej -<@2pn%E?G+ose>Zm36MXSM?r#DB+jQs;vfrmZh+>~qgO_cJX7uOLZ^Ml}MH1ZY)rp@ -5{4myN=tefA0iONqpz!wn=oIz*Q79KH57ifWSGkZY8Yr1uWxd7l=Gv+q~Q*~Q}9=ktp%a18$S1FeG`i -;^hxJ)HWEdmT{6`kcrg8F%`{6$qgI_sE%^O9 -d2yUmNzxLNjAX0U=RCG-v)RmkU#@gGS)(KrT!40of+(#71paAID(Iv~o`uIX5u}1Xj@W5CFJQU65)Ce -wI<7pI;AfmM%oOD;wkDx9@jK2jDIqB2C6`X^#0gjJ|a7lt5rDQ0g1=pjMVigcb{H-oPDUIx -N{_D^2f=XZ2dGI13w5mNfmae-ItbMjrc`4|2v!Z6eVil$L8eF;by6k@Aj&_iU9n+AIx1(;6LrwFkK)<$k1PVA8zgq@n{;JM%$oc)w}Gmy`aVJ!PIwDQYIB)8BT= -+ro8ibw7E>>-2s+w*qbSz5KJ&;d+Il;2tWFB=Xm!naPGH^`QM9}^1 -mWw$2REmINSlP0=EZR5`p@k1KU`5&rnMP1QY-O00;m!mS#z9=K&m@0{{SZ2mk;!0001RX>c!Jc4cm4Z -*nhkWpQ<7b98eraA9L>VP|D?FLiQkY-wUMFLiWjY%g+Ub8v5Nb7d}Yd6iX7Z{s!;y!%%?BnMl7BP@`c -fS?GrF0zMhgCZ?@Q)ubQ;)W)*BxT1a`rkVwC0n-B1?r1Tk@G(0%}|XsTPn+@_r2w^B)RR3^(5BLsNQp -#*0UxY%kxfDZ(_qPck(^onjO0l(0bpMm6EIhc5$iXdNu<2z2eSGqmO_m7B$;vVV%^Qm9@ssX0w_blCE -q*DC4+ZnDw`;m=S(AH#hP5x#jB3(Y|HhvL!(qxnoU@RD7r88xtU0d4!=9BcDZF(FrTIVK>DKzn7M;f! -tlFvg*9M^W%$B9>jRJfuNGEn|j3kiTg}-5U!rgW>0cZ}G -T4SL=W%2DiAt$BwOR22mCM(DCWi>tMS)OzJC2hgV<6h>>GQy;V4IKXGJ!ZN<}!zMRS*8Wa|$LD%(#L2 -h9ujAbvuWh0~_2f=BNCLz&1$7z$)k(<3W2>2#-J@6y8VaoX!$k0|ms5P(q%_im+PKG(vHS`Q!q0378s -Zo&xLWeM-cATmjBj_9xlCOYB<%#t=p@b{;G@_7zpZG~SPgG75G#8GcqmRNx@ -IVj~J_)EYDr?Te5-h3Gylg0Bm1??V`5%q_d$^H=+fxS)&EoEmFB8>sXqntd_8`0Po?lhCz-Z36szFQZ!Y?4@D_&D;{zM-rQnOs%AN2$N|j3Zs5SLZF -K_w5(EaS{b>0JJ7g^Yz2ZHCbe*sWS0|XQR000O8HkM{d6p$E-84Umcs3`ydGXMYpaA|NaUv_0~WN&gW -a%FLKWpi|MFK}UFYhh<)b1!vrY;0*_GcR>?X>2cYWpr|RE^v9ZT5XTpxDo!ozk*0`u(b6m-lT`N#<}S -xaW0D{x537TD{#2Vv`l-|$f8S0@w$ujw|8dvB2tpm=5RHFjVzMGnP*-&GxSEu9ZS}-Q1l0@8RVqfR7tSph}jKSxnEK8Bq3s@?fdajgIV;XqJ?{7t|j^ -3;HO4c%yh4<=K@O6Lft*G{}lQ6Gh%fy~%ANf`|7OMRI?CcC!Wi!igvK94PcoZs1(~|E%>KNu^1+OYb3 -`KJF58ybS(pHZiJ<_jPCd*B}Z4^f-lQSBf^X!f(8s>E^cKezg@}gjw61*0Svr-()a!nP6>pRx$*D$!&iUdm!R)vNHM%*vS -&G1FR^eLIFBzMehB|JS*qcd=V`SJ!)3^B$ZH<(TsMkz{iBWE5)-Zxq`LbzxSp=x~<~NaXbulUh!1{Sg -($350(Ok#Fop~yr{%-`P*?p1Pupd+m$va;NYP6eh&=fC7Y75PM7~~R8i!sXP)mRfmbbAQx>AC&U_Q#s -ugF9LrWE&ka2!yMSZ@U(SrIe*lgv?L;HEi5105 -#{Dfff&O|)SB{C@CRBqw8GdQelZ%QZ6WwU+?5hms -T`v?kjBM&6_VO3nSsswJAFOcXt&xPj(gFE3uER0R(L)JxX?rcL=Pi_4?<-y;+tBi0HOmAF*4wQjpA1? ->gee^egu5LH7YRd3$fXN=6;2({a5L=ISn9ShX@6T>7esn!rtb)y!Ef1SLh{lAFi7DZlpP~(okOaq63!br -2$c<2QTjAMnS&ujFU>P|MrddN#(9PNQR#)-0|RKpmprd1iwL;J)a3l>GrCc%{*9e4?uR*e=K?SNVihn -ls>*iXLvo}i3qUMD!{jCd`C%Vm2Sm20!oIEC*Gc70c{q0%m~vXq?|o^#7fi -$AxfJxS6=9=1Xvr$qmGVFw3C2`A1?*eC!Y|q-3}ZI&_kL>j00{pu?=Md`_ -&I8i${UkonK-oJCLB-&yp~<~Ef2*bL`0yI_|#fh3D2W-#T@nhkM6j3>z@0uD$_H%*yMmkF`6^jLG$Fx -U8bl1!jpm?Tf>@9*Jnk|e)4JPjw9uK$}%h;oKR1y|sjyu=)tLVr(4s0h65g~auvv6i816KoQo$9f8p@BF&6IZ+1Bu+XQun;mK69_R$=*^fhp8`g -8;8>=Xv$-l1@cWa!{C;I4o0FH|GOUDMKU`vggYF{#8W@M6q`hcpqWM_95T?rpm_?1DLI`HW -M{sy`RNv&quRl68~bJ&u<91f*d!e2N)Iz={aKe2H{44HX+l>4FtPUvm_#Fu!?M-3AqIn#-Q5gtq+yrVBF{Y{No{Rl}IrUNw4# -)jsg{GM{6xDG*at)zPTAEbSCC`Vxa7hQqLO)wHL1Gx#gg(gljr3I-v6LmrZy!toReFjC&7hK3viB^g- ->nYvPXb65UgbK(Bt+56={=SxE13bbhX9n_&DG>Lh89ot0oO6@qOYWkuy$Q=Sra05eGeMMBS#uwD8B;5 -=}(t}ZgU+w(eKDg2@R%z^_IpeRFz)ZBg4?An)4)*F2h}b2%HVCX7OQ3(J$fli$l|a>uwakyLDVn-SJ5MUpus_@hDV_WR#1}m -k2km>KoQ{VsxH@v;A^5*ZNTSFLd}^QcIm;bC`06%h#G>Wa1*pxdNfR90dO24|Y1XnuM9RbocHo4D}0 -F7|}Z&FQaTv4d2Yx9-7@>Dg)QoLj|Gc -jq)KGuq7VkqaiwdMQpg|FjI*mf)lA%bfG(|WKhPN_?1S{9lZbqX{;rhO8|ydgB(jza72Q#_izje0kVZ -VC1{?QQ$A%jw)*5fYBTuz6ylH1dJG>qgmR!G&M1j=~S6Yp(riyEhQ;ObiEyX)+94o2DrItFWmS5}`ui -m%5j=TpGNFBb5Dd;vLBOK_ly9L$N_g0^+wM+eU*sdeCD#ZX!tM30s*Z|HV>)RZ4fc_>FgR<5mwA|$ -allqF~cyGk<0u=cL!!64AJ;tcmMex&1P`498k>zC>L_V(tskL-}v -OpCqmE`u9OVXthU5%z^1SlIsRbiRh$M7^d@_eC5;N5^C -mWQ5(01$k|7MG{daLwS&KTTCPZNFxH;zrFGr5Z7M%qLUkJ1VortjlQWy@l^L9h`eJm?xk>U$ZzW_u~( -+8?+Id7M5706;Mia4eU7vi9@>NPb9)2u;2%}+e$=yv5vk_E9b2D04Lt@6aWAK2mm&gW=TT;VJ?af0 -03q!001%o003}la4%nWWo~3|axZdaadl;LbaO9oVPk7yXJvCQb#iQMX<{=kb#!TLFLQHjUoLQY&01@7 -A~~}C&acqe5esaW%iU%kd%5el;ic(qN3!OM+6{eZT{`NhYlB!ftv}a@QhubkRrV1& -QN@bornPQP=D={7~HpM1a}pQ%`(wbKWsJsE)r%b-|_!1zWd&Zi(~a=rq+edQsc*tMxTaxp5>9aPK}yD`};%|i! -5LLX!VN3==SPsOm#ch_V*2k;S7$focK_*7EX1S8s_*^aICMm|V`snM?`2MH6` -{6AFl~Y=$;zq8GD3(ejDt#)JB3lS4=K4vQLKGQ3uQO;eO%(50EH>$kwj)vC1zkfq%V=K3j+%zJ1%bmg1-HzwJAVMXlekODHc$b3^*C))Gf+C6A|a_DuHc$Vg1PeV%=Ca>9Q -HcBm+}nN!5jKGZ)pGV%M;sbIM>#lG}P#Vwxth@JZ>s}1%%RW!BOt(7oEem>n5%EUbMLBZ8W#n2by(cSDcZGooTnrBj5-ki9}8c|E$*?Qb6Xp++iqoUO -r*GH9{cv*GrLz{}s7giqtJvycBO?-$vU)ohMbuqR2ZG3Xy2nzi&t7LdX7n{mi;pxyW~YAx_03El8UrK -U~0L5deneyZA6Bv4)@k!h?Um&E?5!JI%k59Sd -R$5+CFiE!~B6CCrascDM$4?_M(`$ID>9p=;Mop)Ioqn~nc?-#8P+*-%Ad3H6v|I3RXKuQ1N#u@n)ZbS?sK(Jx!bES8;zhw{K^8cP -G785zJQ)&C?tnDnKD+}L^k -#o#Z4Bl8(ej5E6xwgIyA-Va9tPP~vwLIEbWOFaX=8Seno4T31iwsy)~8%gMW`}xx+Avb`f6t-m-h{Vn -N@5Ig3!0=Mt55zdP`zJ$@^9Z={K> -twuRdP?^<%$n<@rbcf;QUse;-CyUcCFre|a>0;6dL=+NBrvjpC<&=khpzbZC -9N2f_v7@&6wKn!qSK4IBFkTeXp9pVjY7Juax&*%ywt^b+%YOyM3m@@Y?dWsHzi0rtTP71ehie|A6^@L -Dn6SMp~QE24s% -CQT{xfjj0QrsH3x=)$Xg`SBK2s*UllyQY(QPHWZ1fJj*ZgkWh_up#;f-keKtsggT)P6k&%Hehs-n|o_OKrR^q_72RKcQJi9G=BR^6vW^I)VFBu9Zafhyj@hF -a86rW}+J~XgGvTmbto}#%8$3W{jxWqi0nOJA%Dm^EsF~zxO56tcw1f6PE^{!o7bjKApR2fGK -+7llPN3D+R6^)+Wi{*9j@B;km*O3klQfSfm=D$Js*)e&ecE4u|b|{7>>NdTdQ)|=cDkgYz`BWeK+ -di@#N3~a&OZ>x|L)^0!XDyyxBs-~>At*%RkAJl1RX;<8kbZUf@Bf|Et95LL0c4F1V*y`MIRZ2-I&?ab -2;S_s7>IlrGTD;`8iXTl@vm$bg>0EqT)zC+2ELZ$R1z3Y>7?FBzH#j_s!&$+4)#ML>5}0f -Xh8_g`Qhh-QuHlCa7*oXsQ4Txi2D(jwLRrGp-tW;8<3QeJ_!M)v&Qzz}Z%3lt?#8GuRNk=%=c;|zXu0 -HeMrcVMMBO7wAoxCP62(tArbYwH^~Eo5e{07U>uL%*dT=U!R5 -snIbcU$xbYV!~BNavKUGL(*#Wu^~1l0Z7T`-`pd%bwybYpvnmsQU&TrBpo-J%Q&Wgo`xm;JFD+xHze) -Y+u5*U|~(M2FgRj~Xzv8MX1|yau~;EQVhYdBmtrV7ik?JmIU8U*7$8(vbb#zXtn8zKt3x+3osh%oS&) -aS4ze_$(BWB^C2Q_+UrcNJBtxR#fEMG>Ff+o|Qem$d$#;{i&%#G$vy?G(EtHlGvQD6yRlUvpGjIibUY -^CUI@N_qKB3oZN?kvOlTS7ZW?|+M|T@F{zndKnbWxYLHH}09P`Z?Io7&NpJu>+vvyYyvD@oLG%o9E#! -0xTEaQouGtx7OxI|`7B6>V@-Y0{=j(^z`w3*40x6r;ny|X%SEb6jXoCPLO`S_IA+9xnFXYuGw#x0mZz -mxZu(KPn_O~wWH&PveIIlB5+zF -X!h;7(rS!yYO`gtzVZ8($84si(8Y#}_Q@n@*iRee}9;Hy>G@ -YR(vK-PKLbt@YRP!46Lt6;YpuIRgNE(g-Ty});P&7)I=Tu+rNYq!)-OCl)cGTb|#X2r6U#b&XGO)f(1 -dQLhrN!)$ILfoxYO4Y3^IBEp948CfEKQ0yLBJ2fUY;yeX% -=uZLNRyU&q{bt3cJ7l*f}GX16(TNlCyeUCP0r12N|oWDST>yk_IbjG;y6gdnOWyaKKAqMJUKAT9-0kE -j9^%Gnd4ZvyC|r{_ACUPq?>Ek4goc@(W*gAufSKE92Kn;Sax4a4!(-AisuZ@Fmv(6N88Ao1s90mQ)H8 -jQQAj7r%WI$mwX%L?uppZ~v=%eD(06H1wW080me_pNJ<2ZYW4XQ}Y5fDqYgQdi@e~5&YU=YuGn0vC+L -7ux?%9`i(=Ki8U%+zshJkqEzdEe_i!n96AKbRwO1u+-#fHFfjrK&yvlYf@%>KdgLfFDO8?f+=~i?d98 -qSqpZ9>)HVZ+)v4I5!WG>?G-40$~9;I!*M* -{h8oe8>2MSIm+)3dgmqgs_@M!vFs-KUXww<9muE&(|k`t6YDbLWIpO-L{Is6}0cTJ}xpmlAxl?5|cLU -R(LLKzZ)IgsoEZK7Db!s^ni0uAHr?k=^I})BdDN8WZ0GBeIwhL~|oPdjWz%33@>^r4)v;(z3mIeecZ_ -@Rc-s6?D=(25gn%tF+5RyS{@bGM0JR@GZqu_fZwg2IRQI3iH>&osZt*MwJtUTKWsH9mkl|>-EwufRERI#Z<+!!vOcU>c5qjnXpCUsgyr5*i-h1J<9unP+FBU{qGO%xK~G>^E*1@k?@y!^$ -NS*_Y3N1$lLXLc7PEd;2XDD@gdu!bGxi93sBc^m}1D?h`$H~SWZm8!6P>&vi>3ya*j<3Pvh|6Okv@Je -vC;xBG=PRq4sy{r2n4X_TnC+z$K>xcZ~P%>kn`0 -y$ttrv}2p|tcrQKEivbW#QTj8zwlu`-Oqn}&(5EZT$Hx2Qt|Qe@qWZr7nOZPHKz*cfY3VP9~jX&QgY~ -wHnwQEr8H1=h1=KZ+;NaD4z^IKXOr}!v;CV^LP+j#_)Eb!6vg0LU7gko9U`jaXeiD>b+Viwk=F&JGe*sWS0|XQR00 -0O8HkM{dZ??X ->2caX>Db1b#yLpdDU8NliRit{;pqvxf{<(GYan-*GT~*0f=Eb0O%VzJo~(Z8Z}0Bn -g@8z%&uKf;-WN+EuvqLq`|Jz7)@sKVi*;u^EfxzVcYCER<13?@&WeTmKDlIfTfu6v=3Qf%QPoFbnUy= -CIy+~b5j9&K+1A>9BUj8SCfiz8+zMv5+_F}M#q7WhNHcp;$+dLbEQkgXda_1kyl}6`;`N)ie}4XE@$% -L8&p*7mTipHdW(W5TR ->K)8@TY($>OCq%`H=_zl+LFXOqbU?8T~1YdCI!0;jWb!r<@X;)4GDtFT&%dqG0|LOQtsb4$b4U0dOO7 -<}HLS!#wl$(D7kgq6a;H8w>iQw#89>!i3dL6NHA#sq6jorb+G;Q^~uyOx_yb3C+|&~yLHu!^^AMHm3n -)eP*yBOumF!?(RsO*8h(n)3Sgo8o2`Hz0ms^*fOBBX9Xe=&6@+>;LW7YGL- -^X#wkTTcO1-9oS=f_4?J_)tp`7kLC5X_{4YnMiiCWU0=40$8H8Gg -sR&C<*Ln)BzuB)7pJzqy&s&e&NRzG@uijIiiv#+$||!!_RtE3`rEE|)*4RxFpPcCA{Rw>~cS=Esl!mQ -DLB^+zJ&P+UoRf{K?LBWG)`7vpVBS_P31#x2|rRQw~YOP@t?2MI<2dl@7oMeA@0YsA{3O -BslRp~c^$BnU(dKO}&avReRj*bz*L3UYTJ#Jh+(@g{45_w(*@T5 -y7#r_zjed|HI-N3JQ3p}Uv=DKp9uh+5>*T1`Yy83PX><>@BdG`D3H*$3yrCc`8o<952a#2KaXkB+31R -#X21PTRm7Euc{-Oz9*^ImYtqx^>``y%J9ggpq+;weT{ot5_@33-Sq1$4+=;#_+mdm@Oz<6PYPGxN>^e -obRG#HVv+l<16u`(vPwJ7=j$~Ez*YpZ$i0c4@#O{@(&hOm4g~z=?(SXm -T*4dQ!$wcpbM}r*a%I@dpWfqQ)gVXK3aA(~gyKaUBZs27BU?prs&!6u=3{T+i;fo|_DpX>Hwl@R?jhT -%>FOSVTe;b?7ww_wYo9c8bQtqgsIBPH+QNai3FsAe{}K%rr6RJ^58Y=dDWzeL(ekqxB#3y;Ij4GWefJ89i&$ko -FKD&+inh_xsE|^DX3I%_Y_DqT*20CW`g$;SL$bB}dRjYLK`(T6Cq`5$ofmc9m`$Zw~xuGPbC=frKvCF ->x?s!IC?)pKyY~r5d6`t}XHwkd|s|P@WI6KN#rio3OxzEE!VE4Fx0SbU0LX0IJl0w9@jAz10k!36|f< -7Wy7avca+`fY$7&I?Xl;mP)s(LqEZ7AfAJGzD6?Sjy|F*4_Lod4WJ_{66QqAPrOpiroMVYKv(9N!ta5;kkxv;;9^(Nu@t@P3E)LJ`q{pz8{v -DiZEY~(szt05DLI8MgpybXzqOYEqSgAkJ-bDDpS@MaVU9l%@8n+RQAP+fl#bVJyE?z9=xee!uJ?6;`e -fRMlb%>C|x2o>-9NN2qC?s^u86J~pkA38et*Ab_9Kief*FrR|I;0E3NMaICzN8_MCDk$+1`+Q;iPJrJ -zT1MIh$@rq{i%tFNKKjXXnKJ)E78~ySxw-q0Qlb+XhKb27?jRO?${vJB@kdW{0Jmsu*~!7Y$Os8-6fT -RtZUfM_%n?qN+cFi9}9{=ooy&8TzAnI7Qmg-W_OU-jkn!SXjysFHK0BEqGkUOS{13I;%5-#7Q_A!g=H -q#gAyEHj7I#a6e^uS#rVU>&e0Pb=k&y=kNGmX{ghVq(dH&TYG -fI=>QULAAlOGt7^izfqDXkyT9Zkl3{4a0Jkk2riXn9k;Y*izk3XQ5sVMWScPiWEj(De2bb(rdl -!t1nki5aGx3ag=&5>n<&)edD)W`d^mIq{hI%JsjX5ZPUT$FjIH|j)BMR7I(P8NZ4@Npv9^vR6p)Eedi -YCMA8pNrG?3JJg7Wxrj&6#rtXpgB+NGZ`lz3y1&l$}*oxDFA%j(=00bvV&@0*l$^Q;b6*MhYqIYp31+ -5zxA7`RC=A@7kyxPWcwiTjAAgrNm+zzA#6euQI>X&FQ<^k*-Bz98=DB`gsx`w0nam<`ex1yCFyvnb#` -6c@sb{*}N#lRu)MJ>dh&gokp?%M+180FFL!}a9wOX|nY6vA`n#>sKKfM*&vE{~8)rdyZbUmF^Kda>Z~ -Se(O5K*>pcM^H+NymKt*%E&RsGad|fa*@W?j_Ppn0BRZcD0Y6WF+rwiowg85|bm -eTTx1W4X3WJG_`XeB5p-JBR<2@tU+TO&z3)9^}4UWkOam`h=HDcc5<4a68_vwnd0JeeM0`S=oA1WdwS -n(MEr44`&T$)E_@w$ng$D^;d^wQk!|H)L(xkH#Y)U_-8f7L4zUeGjy!B>8 -slF^rBm&EE{cCtL&TREY_E*O!LtJdH8CL{_s3*uciQ4aCRS6Fm(yW|eCPEaA110{Bh -F_E0MEXzSvFoq#FGZ=?X8p4@-G7z4?4^JIDpdTL7QuR6wYTBK2gEs*EkdUOf_1s%+6M=10$t#g=Rp&sihz3zflVHS9q-YsZQph3_vFP -7f?$B1QY-O00;m!mS#z3nQJx=2mk>0761S-0001RX>c!Jc4cm4Z*nhkWpQ<7b98eraA9L>VP|D?FLiQ -kY-wUMFLiWjY%g_kY%XwlrC4o`+cpyZ?q5Ny5lBWHdD8$z&gf=0O_N@MpxDbL=!eC^OG}hZgc4Pf+Ku -nm-^`GbD8IxlDqzsljLD<-Q(8^`$CYSlWi^3RhJ$#Sif$eq-+?dWP=iNdvw5) -)68W#U^dni0`QopW5c<;IHl#%ME{Oj@H>+&N>}6}wApz7>^7<}Bf@+v4w5TZg|WfquDP|Faclhc~6+n -@ZF!xd&;v;?}Y+Mx}#AX#&qc*=w53mJo#|SXoI>-un#G<#M(0qtxovXIMZP6|4A;wr(w$_{MXmc9a(uk{huI?~zQ_P3s)+INi -%R)H7%6)-mZiGg0WDtppr{Vq$AA`qPJZEEU-%BiOXPII^9ywc_;$ssEP*H`u>kb(EISba^Ua8ru;m; -pAwtAN`FxHkSrSS*lO8ziq5BN~sA~e#S!`AVHhWl(nEP!$%Rfc_j{FF+t9T#*@1SSz0b-1$L_a}d+d$ -J?mC{(3LW?hil9kf@aTS3Wy -~gE;A_lp0(q8`XYF}FX8vba`^XC0G2VA!>y3~Rj6{efIt=7WyfwviOfZv7SipF2X%|;wjjW)HC^-31s -08LO^zeJ?*BFZZujzLH?eG;?HN?8|aF)Ac{^%J`sXL7D_9B;*Fyy>ifJwuJyZas5&F%~h{4F3<_bfy< -x!*S`|(#X87G=}JV5#9RTGO*o4df#;-p0k%rSm;%UiW(Xs{GF(z76U@r@+G`~!~_4GUfXPG%Z2+tD-uN%27nRd`^5iRlsYqwwt>Mh0{;6a*aSa3VRJr=hm}tW)V?`O -uBj58tn&zcmkFFAqniuBFpSDH(W@!xK{yuC4{tFx0O(tLFF-k -oc3({I3vKV&5=%=muK-Rw_KgKtkub5YW>tgNe;39>!AZ3MJQx?GHe6oD_k)YV&OnV&OQiWGNvVu()+= -VEh+_hh7?Xd0p!rA*SbeTNDx}W3JS0?$x6VfxU?PPXvVql(LXqyJ+NqJkyPn6<0wJbcJ`fJGa$!))y( -JCs;dFG=>R+#aoL -c`?=f$h+2cJ5GGwACow01=7s5R01yJSI44*yM)`&qv;#1ZeyCjkH@sx{@H7>`0BqF#&txUlLRMYsBx- -5f@2%06=N9$ys#@&QswKxlLrA+f9Xw0>|NVrxQhZ$_d0?e_7$yc&+af&B1fJ;lLjgYCs@#EQ3c;)L_A*uZ3#jp&0 -;B+aBPCX!&?E0jz4yj^E*O8=uMtBUtcb8dF@*L -Zrer@|6fnB7o(trN{o|SdoB?hupOl_>Nr8GG?p`iWax?ouE6_QBe&dkov{N`gv*&|UFC;TcPg`6Ms?%liB>k)XZ*^F_$B-DfV&P$VAE)qfD4<#=*a*=V8ELSO8GA^ -l1;A=J~a>2;-r)S5PKYdTeMjR26@|fna67qN<-{)>H9326K@N$x?|NU5;WHIX_~aE^(iPfRFu(Wm@0ZVBT!a_L&&LxG{fj!Smxye1aPZA -{qk~c61@3oms3~qoOB#(LFk1?yL7LikLj}(gE;F&P1aLsg?dm$=akdttgTMV1*P|7F__fzNJwAOlMrG -i0FJ^N>MU*6Anyp#h7cz$<4gw`22XkNMlnZdelJqm4ZU*Fda(Z?~QYOL5g2;?0MiNnU-|^@l2tKgDip -97{NB^{lSkas=iQWJxIeD!eW*+&PDrewTE`We=wrH+HiJ(|Hv1WXJtSa;06quPi;T0WHA}&-dA8=(91VBAfHbJ~Tp4&nz(^)mG=c>9`s@A -u{qKhQ{n)&pns+E|P-NBThx=MzwFLx$3p2f~+M(~gm2dgm`P-a$0}TqSN5G&9mk*XSURI0AT(5)U@I; -jr43zMpdRBV=kf?f~1R-lFr=)1fvYS8@K%s&pmyZB*31_ia5vdHP3hr%)B%vW8Zcz>sKF>-Pa5<|BSg -G0?nj^c#wA6P!6peXsh4>>n6fCf#t}qwsvf4k!Dn^B -vsVP>~C=W9Wc})$~dchreit;%|rXFZ$E@t9(INKafBg9Sfc8V8|xbT2z?jdCa5e`a97+QFE!BJQoOe_ -TT+@pyA+Us-9}KL;9pP|8dTw*D-)0NJj-{ANPy#l3MysZHp&qCp3?T-pcM}-hF&u|jKkaJ{&bwEBNFF -0eebn7N4=2<$XB^XzG`bT*a8P-+6rXauoVDa0kolyk$4LzD|WS~D6qZ@^7``W=s?$T6&Vp28X}?Ws#I -lBh^d&3?R}r1!IN1bSrDiYtjhZ-g@z%CXsXkMxp7qLcL;E@K`93ND$u+DIo)>{D{)w|P=I5cCbJTJ?@ -idA;@cUBucHuOc#R3>1qT!w;+$P!(aEO`ps64PB@-EjeZkVX&ONp8uDp)~zR6t5KDC8*ZgEbpH_F*6H -ygEq>pd>7BVUOl1W&miUR?rZJ>}rab!1XzyVA$lV?%kC*fnBp!cLz`=xl-apn^Yk7bDePgs|}7+w*Z_ -?ZLMxgJr#X(q5ghvNtgeOTt$LRNy&HQ|#W#%r+zpV?`W#V1N{gs@KSDPZU4;8mQC8)xe}NB^-N$*+yC -lcfGaiPW{5Ryl2r`MwnE+KV}ErT_|;?B+j5whMOaUgmr@_IC=5huVr6#a&X>erQ$|?2VxGORt~%it6z -{->*=VL%pJp!>9h|Gta6*>7)+V>jJp_A$?TRMIE_BzPsh- -*Je+kgTERgSj>cFVJL}-C(fWE1{zDo#Lq<2uX7(khMSa-rhca3V+8P71PGQ+3wB5;N9JV#wcv7pp%^i -jX|T66ZqRD46~IL^vDQuT3)kc3~Me@)&s_VhgybBJ_z7QHgk`H?vA-<%Yfs5(P#aBvqe4SHUivwzO#` -XxIxlzQ5hu0gE-*}zqF3rds(FEz0r(fE!4vDE>F6ZL5&t?I@>wtv{O%82y{Auq%}`+fp;A)iY4swa{= -EN&x)%B)(8yndw#rn?E&;LTVldp<8$OScGs7nMASg^XC>Sf+x -3~kUA3^Xg+8<+7f{`|!`h6Y&e+vCA!m9?1#GBXXFSSv6P@Z7DsR`uGS(FD7 -2}|)%#b^DVlFHfZ1kC3TGf)XTiGwEYuB0bX}j?4v{kbhQb%2779D$=gr#w(@$Ah9g2WJMKf#r$xaLCL -km~Mf-3Qd-VVOMiyRKrUp}?cKodm)S*w=*B$N-{9lhmgqW>RUja4)3zGetS`u`hM -n7I_DW-mJDT%Nf1aUan1R_5fa}JG))bLQU+jaO1esCOI=4Zl}5Csb`)C={(^yJ-o~dXTI`V&iRQmZn0 -$Nu{Be}9l~mYoI?2p{m#b^|Hdl$@xy;ioz${A>twS&wt~mu_=C1TqGg1RCgDr2JKFs9WU -hPCYWP*=aMMNAS}z9>?bNED4{B-n6STo^2n&P5FAfX)L&#@Eg&v)C)WYTvt-`-pRQT`g6aVa??hKGm4 -bgUQ-9I;M@TKNs;b+eaDiK;Y#G(4oSe+I4BP1-wPLkWWrlTT)Y(Q$yT=>4yp5l*l6lF1MX|hM -#AL&qsV6Y@&zP^t@s|)oZ!RageF2WzHO1w288!48c5)1inxW$^U;_;irAvtiQ|B}q?0rr}_EQR7hU0u -dks1G?NX#VHb{edQ2Gu?6j!B^3BvPBz)6W@?lZZ<5qY=-@GRvKd9lZe%Vv?`_Wb_;_cr)8-VqP*HseV -ia_u{BMb)Z?R9-aT=!HpnoH4CL0wA`F(Nu2JeenL|c3|-%~`=Hv@HnKHze{JR|mT2cen$d0hZzhHyW! -$il$K;V3AL1djpDdINR%0q@9o#h>RuNg(xnTw;TGffLyVV;ZxLaHfy0;{j4qtVR1Cy!}Sx>lWX5d~bs -xwtGo1SmRUQ3<3-QKQ`w!PS`K6dtpf23|t$?|>ke`tcBF->&S*4A9qJsETee?R%?j%i*;CEw|8(FmU{ -)4&cuvrmT*v%PS=1O6{iO9KQH0000805+CpNyn`SivZe-t=RuY1O{J36zvD}lpW;d-ahiUzz0710S8~@ZeLCK(em;LbJiPq+<@?Jk -`-T0}nz6WKs&-9sQU6@3Hy$oFt+@U3sbhM%{y_{z02BH?M%Y4aPtBDMO3oApp|bnF8G$H-(p2VY9k|T -V$VDyWVd$;7(`y?f8#V>1f{Ue?2C>2s(1G4kU^Lgp4%~=!7<4oNrVf!Y5`-bNfkVJuGT<%>xJx_ZqJX -cbL9B2DbZr55Q53#IK##cqy -yfA>1sUSDw~_zX#l+*)GkvRJ(L-b_S%D)Sy{s4!OVD;%s|kaV`DVe#tx)2(}QslZ -((qBus_4&@|5#n8QN?#5Bo;sOL8j^pqc@;xz?>EK$)E4ddFBR+()_g={OP~*Sr}A)g;(dj}uHUjZGW@ -(Q}#tqq$ypApPVbGP5tFJR&dvnXy*S0H%2rt(|F5UvL^kZ4&`%5;8miN}(l=0Zd_FNg)J-Nw53N%&@T -o=@j;0dC4Mqi9k3Bu-~pBy{|)D6bHF2f}TUc6SL<(D5F7PXXpj%miI!|F~gb*ogJ*pu3x;4Ez?$?AjO=5726t<2{;09}Q^>jT^A -%sVk-Jv~qkGTp9xaArlFYZwNjhX}?h#Dzih{M$=D4GND3k_Wc?@n=|vMMhRdK?B}$JGMLL5f4#DDKK@ -5ijynoQx@GWKWp|V~6al3>vs?RAbBnHPw6C}w>mE3ohGwjqw<8~%z;doHxK(-fwQu789y>dz -~iMF5^Ge8gq2-*Mv3=nn$dNj^T6rR{b@?ppN=*YJFJ -#Q`>G~u6{79fXgLSPN=MO=Y~dl?EL%I&XqhQAMFnU_Vf5|jrQ}4#kfLk48{xy2m=DzfB+Z(XABhDf_P-2 -p&?;FNE;9W1K=AnjW(Sa9Nj{(0bhs7#g;__o+@kHf*T-#V^<=cPf^Th9w`Sa^T3=>(+ -#5)pwA1P{$er$OKWm`QZU8Hl+~Tsfu1Tt$RS7j=<$@qL!_cddv+mvnIlZq=SRjBa8L;fG_Hl!h)HNit -3-GLe#6%0RCCN~i%^?JQ~`e?Q-$?i*angsz;z0MwS2vveGTYxCGhHBuLilSx?~)L -Zqg%y72CsFB`Zlgj8AXYa86KwK-}K%I??W^fR(aQB1474N#oh`Vx1Bj(!F9??s5laOOEKy@8@{!(ZyA -%ePZRQ?C3Z!OFxy9pihLK<(MBveL8)Y!yzqsE}M6>5DO4?zt@m;%r}Icvi`6wI!=jbIHmYX<8s8Yi27 -eh~f^oalqtz))as;uK&ht!==r{8Gw-@8nzkG}&p-Cqw-7@bAO_P)h>@6aWAK2mm&gW=T+gF&{(%000> -U001Na003}la4%nWWo~3|axZsfVr6b)Z)9n1XLB!YYiwa+Wo&aUaCyB`O^@0z5WVMDOsSBaie}qAB5~ -;Ma#)G>PyNZ}TZ^lz|!@{nn(>PLWjA~$Sgy3sv0sc>Elo=i?)D9lw>eLuzu0jffzxHU| ->(DH#aRba+G_DltfQDLUeARiPNIQ_$U@~cEMS|uLW6tsZZ2WX=a>-2`mXAPpQx=x8C+ -D1$<>1x(H~kaVogR4M^7f4~O2vVzGb+1yVV{n!9&?>Bo*+*l>a1zijVR!yK=e5T{;_I;8HdWT8)4Hv4 -inoz1;0zca7PDxRe+%1af0!=MJWp%*?yRGt?i2(KyGlYG?UA>uokmL%vxRu|yB2ZsVHf4Z$QC*U$(Jn -t~8Ep6{fw9(%oR@1zw(X%2Y%vPMg=INdBJRRqf^bWTbcIa3`fSym9iYKcCY}m0BD7_YbM*XZU`y4%ro -(GN;nV&CPfk%h3rhQ5hUPC(`TK^tBNdiNlEer9_2cTWTk8nP^!Vnc6RfZrtKzU@?yxPC+1{L+DcBh7c>z#M0|XQR000O -8HkM{d7MngDx&;6Lq8R`HApigXaA|NaUv_0~WN&gWcV%K_Zewp`X>Mn8FLY&dbaO6nd9_zdZ`(!?zUx -;k5(K0INswG5kp%c)#S&mZbqdHiSq8DBR`xbDDR7sueCeV6BfS& -0|7u@Aw#fG`HxaKR7}DBU;e=I6@1&p^=gfX+R=I#%QuQ?;u79nqPdJUM{ARdqp^=Ap>e@^g4;}k>{;b -o+iZe5WU~T2}fx}!35%kU{ZoZrUiTEy&J~ymvqxX^RvlxzUTn0knbSrJzNFOGmG*;zmYatpNl9+$UTW -T4$q}{(y`ZMD8$<5tJ$#KYPD8mjWA;*;a-9%Bg`e+fNVI8qYhf9QK0VL;^daW2~%=?X--e*1>?9P}nvW! -w~5;f2@-Ev|Mzd5%={T?dk{J9OH;2;UQQdW!Tyo#^KSM6YK1ii+(2KP1t9qG*{cBO$-!dvb#fh%$5;M -?~z~50Cr~PLB+7uQ9^kel4Ql+C5u*ytqIQcjC;l4GxGttmRlPl%#Fm1C3Vk1M3b>-ksE1Nm<+-4&RNs -gM(lp<}^ACGC#YzxI99Ou*^iHf%l(3F9*f2$v|}DD11Z#hP`&rW(AhNZ+8rAw{=N*A5 -{L~7-#+_=8ubd+pcc;w1QWtUoQmw~D@xkVh%_Zp -~f&|A%5bQGW_e3Wrl(MTJvZyKenn(AKH@s|mdF$vgKExB&Vq(VTGULIIUY6YfI5Na`vaCmx^q@)Z|R+ -lW%g}%WO4e4b&RoS8@!HOXU1o;?-$aHHY4gfymprm@mfPuUxt&bl3)kSgkZqtMwvVWF)J(HUH9w^>>*x5K^&mZ`L=zWn?1?|F&I-nE}%s0bg!lyACfu8Ko$XR+(U%uHK`dt%AeHIr9YfKX7SI#Nuf$}>v`Sg9OLBVVaUm)-Ims_%%ygPXB6HCG1eRnd=l{D -y>DLrH)^z5S}Ea{l<)}j4q(6LO(sNb*mWrx)bk!fI*mNx}Er&4}Rrn}7S}O*!4~Y!)X@Ii;-0%UQ#^s(b|_?0O9q45Z{967#CW%HR0*li -Fekr$77H21f~m@MshM-!(i*<;EQL*4r^@4rpIg~DNd1%6r~@gFlqIEjA>-%NZZYN<76kcx7cy^0EwaY -IGhY+1q-3y3w=kzFs{PY@}3Nin4q3d6NZxLc{EwhS0f@C>7E=lZS=?pXX#X=bvpWd>YM*Z0nXJ_*zq! -*&1UBZs&LG(EN}9EcDSDHQpuB}&xPWsvEq+!f9=arns=VzQE94q{&wp)&#HT&)aZwo({-1zn!~>Bra; -hzN=+lOGN%%(*;k8HWZ*LFwW)Y!tWaNFp@Pt7M$#WH#T4nk6ctTg8)t=TU>`F&X?)1;qjxref;iT -UODe4Eeo_nmxMhDkb|IEK9`$+Q}ojpZcUdP3IIvL?@zx)lkonhE}BK$jl_S!Bp&1vu!(!kXPbnpI?PgWKGmRxgXd~|KB874S2Lt#pju9? -vGpHNO9KQH0000805+CpNl0%+zN7>I0MZZu04e|g0B~t=FJE?LZe(wAFLz~PWo~0{WNB_^b1!&bb#rB -MUu0!wX>Mg?E^v9Jmu-97G!%xv=U0f7l7yupPS-XL3()li3S*RYFkml1mW~>O;{@5EEie1+DE3kGq+L -JkB46%vEK54~vD~ZP5^Ao~{Bk6gr5QGw&1Rz^-0zAK%Il$`cRg7aWG9+Tnv<@^-`@YL7dXsCI$LBkqr -8-5I?u)2h;mBedS|uR4lgGBF1>o%NX-{FHocsdBXQ(IG3ZAFk=%%jbmkVikc%|0@6J+1c|jxb_~N)L3 -L^33?C9j_#fdO;D>Rid%?h`4#;q-@tGFe%;4D|Re!|&FRjc*=xI4mI-`5*=8e}#;b^WLJ?8|DI-aXBE*?; -}6Vvp&*VtE9LeOFiCr`XMoZWMc~tyt_uX-Upr*wvYASBI{_l(%j6&Sm0?tbC=rH8o8X&xV8CWx4P9({iw$EyVB{^X_uS3F;u7ZP#PXcJ;RRg9;iFF) -+=n>%8#?YwW#cEWk1GN;P6$|*D*G^+vsqIj`@L!=c@VW>Cq^$O -+(%qDY-`5|+g`BBYgt6Ar5zTRPe%zVJ?GIy9g<|j_4^IPUq=6B3<=J(8J%nRmo<`2vtnJ<_>F@I+M!hGo;^(ziuG -v6>L%wL)Q9Qi}!Pmn)8{_OaJbIF`CQ|52X-&iP0xUpBQ~ -&^oh|YMxPjcV)Tj8Cq|zbeI)uw^pWTz(MO_>L?4Mh5`854Nc55DBhg2qk3=7dJ`#Nr^hwYsL7xPD67) -&XCqbVCeG>FZ&?iBk1bq_pNzf-jp9Fmr`Y7~K=%dg_p^rizg+2;>6#6LiQRt)4N1=~GAB8>&eKh)L^w -H>}(MO|?Mjwqn8hteSX!OzOqtQpBk47JjJ{o-peF%LBeF%LBeF%LBeF%LBeF%LBeF%LBeF%LBeF%LF` -WW;v=wr~wppQWxgFXg*4Eh-KG3aB^$DofvAA>%|`E=@kuTS^`L{$IxHa5TI!q -O9KQH0000805+CpNstYPL#F}&0K^CY02lxO0B~t=FJo_QZDDR?b1z?CX>MtBUtcb8d3}{li`y^|hVT9 -rBE7_rhV6ABr*xMVdMNaZ5*DMi#)*h51xY4JOaFUEU;g&ykd?(o!zDJQ6 -Adx$p~y>Y7SsvK4ZbRy8S#sMxUq@+g6n0nJVAMG<#3y2)&SLId)9A$_Xd4Z*OtZgG -RdW}&guw%4%=nEk)wO}liAoxTEpWszVZ0L=7>LLLdiXEw8Fo0u?BRUQTc=U`!b`%Y0Z#*N`iv*-UmdS)(C_sHrBh(LVvVVQ -2m1dyP>cfl(vn|YvFk}6stBDoUre;i?HZ_@_&HR{7hG>($VCxJjas*qL27{V|S$hqD?Y`><@<*SA_v> -#Cf*E4)IRQr2Q8D5a-K2|{gd+Ex*am;xY~8)=3-F4>vZ0{+lr`fPYJ+kYu56K9Shse#2d}Lj-N}6c5C -&#T4RW_Wy3WHPDy4_#ElNR+AzZcvx{`O-FIms<+tg)WJzjG`b=QK+>%`ml)FQO8>V2uS20Wls$e+#KO -pI5R;IHXAa)nEw3=}oBcp;n6hA*=7f3`whL8tx#n~s3Uq`hjC{f-TH7wI9q5P)h>@6aWAK2mm&gW=S1kJ74Vq003qM000 -#L003}la4%zTZEaz0WOFZLXk}w-E^v9RQ(udtFc5$DQw&0>f(ZM1;M2lA9s77M%OYtUZLTr#GI5uF`< -q0q^(=dEY$&wGncu&es(!G}gSK6#OqV3Bvpo!IC{Kb=+P)X20W}ppPmgPj9ncA{tf5k#QC^n9_)t6iJ -t~Ji8c%D_=#{3WutQ%fLtbc&P2DK+PMyYxm?TLMg$}AkUCHG?;$2qPM)X+MIhRQ-h~PPJ&I+^nJO7pd -pEON>qEqb^Zd@=3c@Z5FoW(l=8y(olNh35kplQ%Zg;a89LNG--RYDfG%pNvOK@R^ooch4?` -sGmcC%wtHAZ1srKW}qKZ7Z!L`;!WehSYTm^@7!I;m1s_H;TI6wp_InZAyeVla>`^tB8#2)UT7x+8=z_ -K$CwkKnv0$$(pEqyrCvX#oTHru_7TXZ1c&rB137w~ab@(hZ=Sig?7OpAvI|L|1YTiDcn?v__T`cdfM` -tgju$og5(CgqR-uM}MEI4y82`wI7u5hj~|Va%8Ny-@9b2Y{1%9MLuvhkkSho^F@O|B`LVeQ4>6g_E-v -H1{Tm2Q?brhCKNLP)h>@6aWAK2mm&gW=Sx?%(G_$005N<000^Q003}la4%zTZEaz0WOFZLZ*6dFWprt -8ZZ2?ntybS}(>xS?_g`^UI>jDML|-fQVT>Y8A0Mr{A%xfzWr@=n@=LbEXs7+}T-#}rG-VQqyd<{oJwE -sN*iI_T9KtZIbuD=qg2<~#NnxHBR$H03Q#zJ$N%aX -^2Q+n@<^WF(!SzSbg|1r!c0r8Gc@_-(Al~ms&;$HAt|tc;@!9!r^^hUSHRl1B;n7qRO;bi_p -`qRO@>WU&b-$WV{25|lb1AB$sy$dfWS;OSjQjaX;k|M!9dsW>4)XnObY+$7uC6-POc+Jc#RgQB2@SVkarqBivJM#P!iaHwYmzjA5j -{~X8{s488!|X%&~S-_g2+dk(eY38ZD)-7ZBiY7{G?oa4^(+9A)?R -%43XnxO&uOSm4f>9`GWJw+jO7*fTvbOPs*+$emVe}8P4yLk|q4r-i$Nn*hPqnk|**>RqPWu+C;H1WZ{ -J&XvV#D17Vq~~eR_w9Luru6TNb|RKTv`zx!MMyMIT2_s8t*Szzd=e@ovX{2Trjt79?it16O;%K*<0E_G8!xRGUD(KnMrk*xK;tc% -9>E*PzA4BRn3Zom~DALw2Ezz0mq)OK&g502NXsbarjEhp1qOh3FugYSH+DC>tdj7`Q@6x@T?uHP2yz6 -4H#KLI>(x3*$;2MCewiwN3S8#L<)>#qOPtTtTJ9tf^?$0p{fj2iU#-{TCPJO_-|h5Nu%x3053ujdwzX -Nn(@+hUYXxRk-86q@&4Xq~uNub1l?d?{L>02lxO0B~t=FJo_QZDDR?b1!6XcW!KNVPr0Fd9@i`v!k~4-M>P0Cu7Liq-pc -yx!jp{w`ntzNp`wvU+&ytWRPv`0wR*Qcge5MIg$VYvK?m|9iYEF-*Xgw%u{lqg -%xGW)z*x%Z_wop^ofAB9@2j!2>5O~O9qtkGQ5s;BZsR28j@e@ZFj2*>M!ed*aT9>9B$yefnha-`I1UJf#|TA?KhF1Msc;Y$7s%NnZBGlb;O@^WDP4 -}5qF%yTVdHwQiPlBrHF>?ED9dVEfqP;A%S);hc?(#jpoP;j -TB;uJR|Ky0dToAt1k%v_BRw%iP{#erEbhp=?%j7JLR$)b1}Th_n2JfOKolZJ@D&RoN)_wgi^(8uCpvstUPYXmsFk<{CxTG)y2Cd)e?Y%zEGtP4H!Ud#k3@0&Td`QdH9eH<4iw+_cc7WFh4t+YoLXcBYwqE4D#d}8Zs8t8V0GX -%gu)#-qAt7pv6Rg3fNtbHz5y-rlF|BpcV=g*&dgZsD(0|=*Jf92ws7ASOzH6-SOj5r;OyW_+pIR*2`z -+4hq{&-+zCL%G<~5GU=~Y|p|mY^Dj*G7i9K|(d}d{nM(brOf@Bfo0pK5#bv)wSXM$Z*QhkJ$9aQMT;- -Jh$WUJ8+qGmy5euUT?x&*Wl#$c9!xdciahkPclT%XB9PyI0=>j@Qmt>0QU3cC*UU(&-9aWv1et!r#{c -8K^aBX#-=-hYAi4a;Fu8IUXtG8*g})c?n9?+lBu#UNyugrtN!aWX&;vwy(K-yLCk8yhTCe=uWhY#Ag6 -;VMl>c2k;1z2Yzj4m`MmCJ)m9`8%oT2UrM_>!Bl-oD#D6>n0{S%pNdM3=*NMVf2Log_dQQisdZXi?VS -*wd3i?Cj_OKh!>>er1;x6P7E_l`LcXYK*2y;nkHn*N-&_Vf(!w5gU}xyP!sm#Vfy^ICDw>dgQfIChxZ -)+2Z`ZKyW!2|Y9RJ8cQd|cq75NP{;L>B)NL@9pj_k^f_sX;qo-OxAIP1RKF9g=z=IxNz8~BiLiwY83p -`-}J@C_3^sL;*j<=NCt8pi^1f!a67)Nfc|Ki;ud&%}32xfvbsddVy -S-bGfIfq))~T2LZ}t!UEbI*`75&IuJY**++GZLRlPRdixe?|Js#%d)f(B*kW;19vR4OKel6dP0ui>tz -{Qt7`gpKb(>tj3~cNoxW-R8on0Tzahg15cs)#H#yFt7n>4W0%+rsOBtGS7m?0e0Fg_Y?J}YiRAKFxvB -g1_{7!5y5mCWhuDQnS|t&JU2E_Oal>;7pKDNm{s0S%NjEezs=6uYnRo?#86^ME92-TcP#W8h%i#={$u97LHm -&4w!3-=V@RM;foIRJ!-!ry2H5id~vJqh4XPv@c#l*fNO*SiI2x{0YBncXWZrqC=sUQDPW?%gz0j#!0c -e{$fk-6kiqi_6$?RMWoNEC3v<)cr02ddH;K)6#%Wt3*0`ULt^_(~J3x*VO;%trWLW;Xk?a2v$OLg6Sg -6ifHU0b{~zgwm86ur-Zy}o74Pkd~FgBDbCMZqXlTZU+88}WRq7aar3ow9bW67U{DzL(q!|6+Muu;vI| --d4hrKUv|N-01t@}T8MD8e%!V*dSS3(SOjbAjoFN7ejD_bNAOjT;kCR{&F`C;=C6g6bwmq0)h=v$;78 -eOQ>CGt1KC!&DC5JaIQ`$mo2@W(B#e>xg11*q3$c-Yqf0JWLPsw^2On5CEvjmGTC6VX&dH=)9$U8l$j -}}oCmdd|utfqe8nwl_>*sm5eyu&)zd8gks!of9AOmB$iYGyjCpc!NZ -*6U1Ze(*WW^!d^dSxzfd8Jm{j@vd6eb-kI1_H?&S?TivdfH@*q6Jd4P4kcj1TBqZ))dK-)OxpH-ytbk -7YishF|aFft~_UkLp4Tqgwm#Wy#Y!I@3buSM|RG(W -rnH-q~H0zkGN4tl(oNSZ^!yH%A7L5V;7e>ptnhY-|05K{*`^5vRPLL$Y`wxH8Uw{7bcT$opMbk7VsRh -?-rGhz&|}V(1 -CQ8G|F_$6-jWrS}_Ae;4aV;9T=&Qngd1yX}RN1KrF&Sr7|^`Vv$<14iH)z2}y&jw-^!`3jqA@St|J4v;Dxq46>;dbnCwK?X?*837Y>NVG4#w-L;eYAD_{tv+Gn{F@FsZ@1GM)3VGGLXDTc%8 -FkoAps!+PP$FJG7P4jg-8rc4_yVC8cHq08MT3&fouR^FGwFIsGgbx|+?2`32`yG5Yz+xe3gEU~{OX+e -gAC0n9G;S2J|iCYrc!Tyuv2%B+wi@nhy7_Kp+;~m%n3;J5Zn@D)WWqlA_p4W6nzP*}mslSiyPrv=LMY -fmw?j}C1r)E3?Ig-Y!6S;69JTC|fNpLC@BfTXl9XL&2&x?q>2vsq$Ny(OGWWzdzL))2t-Af}Qk4#wDL -jF?|2hS5Xu4e+|6Jo>pePWihLLHgFBo$bsqEi>ll_x(QB#h$tLeTj2pKEcBb383z`N=DvT{3E{kioX??*?4R2*TYm -ZgYXM~IT<%#q^(iC&VI<3EX>Bd>E$rthbt&2eqPUJe7sd?rKB@T;9cTxbv%dgTs#KVL)2gxm+gHfd`$ -F*Tbow?&PU(r10Wmn6+uO7Zt_*CSqVLLWr#fcV43v1iJh03u{gM$FP;EQSM -$m(n`AcF%Q4w}*e|O;&B&|qBwCt=*+CUH_|-IsmR7#KmquQa-(aK$T6n%14WenAQNQ%#_!)`3)K{-jK -s!~hj^nJP(6aWAKaA|NaV{dJ3VQyq|FKA(NXfAMheN@|S)G!cz&sSKLR!OR?#q)|FRj5! -TM5S%T3n0szOcK+?Hoj2$_l%v}W-G|UZahA7GIQqA7&Q>a8t=RT#)ufSGLG<*RlRq>#&eQP_Kw@G7pM -8ytF{%gosag#CP@<*wb1m$EmUe4xU6T|_v3NJSjU~WtWq`D8FYI*-XB+pccNeJ4Dgy&otIDc0zpG)E6 -InOulL7KkGJgZ=Had&50A(DFCXLK_901<5I7i8&Kb}a+~;V^7?XTJDRP8WJ-3$JA@dY@HvQ*^gy3^^b -rt{7s!=V%qd-J8_|~h6_eAsRiMM!Ts!sd{nne5~Mhc|jlAM5e3pIHbt|R+>bQcoc4gB)LzyQ)&(uf|2 -Q>0dC*T5Y=^$?kaLEP?X*!X`KGiJO4=XfZ6JR~D5WFNdd$$g1n8XBBrT? -Ok&Z>5=u|_4T_mJ%jAa)3rXbhcv=;xapPnbRMs|%FpD9|90MF^-eeSoqA%<2%%`j|Bc^zLc{%k>nC*A -Y3z~4RZTV+tuAuTol>@ruT|GKR@B`UDxrdhTSUWKogsddW0EAoc>-j&{4hECQwf=ELd-98(%e`u6k7v~n}lMmQC??nimjCST0Z0DRJo}&3KA-DRy)xUsa&)kH -o={LRU084i`|ytKEMgD!I*r5H-OtlPJ_POtZI~V2z0S6s74v=LN`vyt4PMV#?%{?r8|50F2|WF@f}4i -UTZo%fwEXbC1QY-O00;m!mS#yF7SN -rl0RRBj0{{RN0001RX>c!NZ*6U1Ze(*WXk~10E^v8mQcH{5Fc7}$R}3ye4y0z!rRgOsY|o*mun5H-+v --{mB$=%L{mQcCL}7I?@_aANOl!3S#@f-378wH}jkX^6)#+jM$d)@H?Tj)>3lSB=Bju9^m}%b -b39X?_m6_(RiVid&0h2t1Tg<9=LPxhJ#rP<@(+KA`nhd6z5*wpbrRWwT0E%g+c@deL(p=cY1Oh}Yr|%R%gg($?|V!c;fA -0v2&P_a^ktIpS2g$%QxHwMEhQpu+)!lXx#$d%tTBlRhV<-%dM-X(>fK&$^+s4}ST9|$SrG4IA@3;Qs*6 -|v0JR>lhD**u@}LGY>nUrc!NZ*6U1Ze(*WX>N0L -VQg$JaCzk%YmcKi@_T-TFRKyBY{Kk)ZEaSXm+o3xty&$N?wuHo+{0bmzXr6A3Ev -KYC7PmqF$~lm(A`9aU@Z+tIDciQAGGL7ez_vR*JMLS)}i0GxIzZ`#sP0_EzMqprt74due}_?3nL(%Iv -*7R3%UC-4iV`AR}jGalV-mxa`1zv2@0sW0sfXRz3Sz6r#{TYLslqFCt^J*$!FHc^($*H;ey?{Z{dU9a -&c1v$6r%QpsX*Jkl)jfWFrw4R|VIT5^#|3w+{5S<#ebrzjKAJ{4P=%(uebvzG?$^@z5jWS%P!aVjNif80EA)MB*u9MT4P@ZNR? -b&3g*aEB#yX2jH_Ax+E*WD~xZ3AnecT*&>QEdIYat0$7|eQgQo`_-k=oP3mM+m2V5Yzf2b#}Hf?naVg(Bn?ynIg{jCX${KYI1}6L~O3Su3@# -Yxpn>2PmzJ7BMaKO9(xz8Sr(ArE9^TE+V8Y)1uRl;rwVUinQOi$QSL&u_B1E3~K@|>a0eEI1~ZP68J#)(BAhULYk-_Al;&gUSSOXlm+ -ErkHvC%Ef!07Nw+DRfZ|$y0-6eo7)B9i6saD>D_EDo3jSS}pf(1-TcZh+cq^r$%uYWBS3o_Kj68G9loX`yf1!D;45v^f$;GpV4RyR}ku_lZt^Uej5yKh$`O -`3FPRXFe5I8H|aY8_OZ{99LxC*Bu7dXsS#8YR)7p-FB6<)p -i$7qb?Hh~0tu&6AmE`av^FUH-_|81ghR0)a%WuZ60s|eVLrx-%KerMb*B%cA}0O3&S8ng(d4}&z3#gL -KB4QMx0lzoJZKx>R~CEK4N;epCu>cs0VxiKY1gFxB~0|sj8x@de5JZ5t95Qr>2ula7BF_thjUo`wTpX -xubk`+gufl?0QsnHV1tC&IU$%Ck%!rU5#f*QOUo2%E%q0(vTnikB^0-Lry+Z#9zOx0v)m$)hli2AAH^ -fsOl3017z;R}W=*s2)dZ3tI0Vw`IsmjlAIwTM!+CBboA*eDNzP%KpJ^^ww?p4I!W<>|$Kwi8fOt_x<1 -HJ_U)m}m&*$~pTLMX!BiMQU=SmBrWz!1zup2^HwmkPU5BzAnW&0pAaUX>#A{2*vjqFukt5#VfuBiiUu -$l>b&yQ3hQaNGTaA_PyM)|os0L1n653F}#9?FZc-hHQL*^Epnw)D!{MVuBLWiO -tYh$vZvgOTLr%aY677FH&U+$lX`G%u{->c!fbz|_Z$|GZI6hxE+~S9 -Y)7-raq8`}yswf1B69e?CfIVZ|bzWJnhGjJa$jBCaqv=p{OGgMx=&?mvEt-o3s5=%t2Wy9(ui3I>&_uo0bAm!N*yBn>1Waw5@rCU|K -#wGG=^`g*AYVq~$r(bS=`Ns`;$6^X@psQy?RzP|wU;!cjkD)og7!h}{LarEZB}@9V_$EaF&=Lfw!-q$bFK-)MGLg;Ryi3%29WUUB -u%pChoIbovdL+9ABBe7hoBSm`v)qEh7R6jYNA%tgJ*!c?agPRS(U=j(MWjybr`+&k&!&7_-q)HC-ZTl -qf&nos`s8jIv-nvDT7G>Y8u$|}&J%F3)|`$7j -y#R8IAwH7g<(QobfEIU+DTt^< -D~G$~6f~=jWz(fAs?0R#4Bsn)-8QF1*V301>B+cEWcb5#q#I!{8@g38@)7(i`{5m5D*woG-fPwi2rj%UwtT1m -_&s!x<+KMe|nLNAIS^{&+GJoF%dXcD?r&oTL*Xv%TXV3Ri8@|2gV>3yUbwyBXx4>8`3`vTU_GVH`0yu -5oPZLk3gq^Wp{*t5!&=~PQ7?6t+7Vib?8uAM$qDv`&qoHva;y*AASRGU}^gDy%Di*$tw&V-L%Dl*)@L -8fR17TxV2+5ZX3Lzn-33WR^BE79vM+Ad#9F7Pg;6XzrgO7mj~)qThq?NmZoh^yjW@OU8i}`G)6WAYfrC&>wTIVFR6MMXb323pzsgrR^s1 -955_T})9${2Z6F#+I&Ba{^*2ax$^Y=pCseC%(H6{sauzc;J>?ga!{XFEWzhFAWzY9Cvjs5}6Rhs1D6e -W(Q@H+AT{wQs{mX2k7Hu$vu<1%#$+13I(MMZ$rSqM+SS*JG?47K*AZ5-9?7@J;*K!(8)#jCMn5^rj!+ -@B$JVRlC3c+^20*(M<0ca7yZO}hi@#H`MM4p}vts=w9R$1E%))WEpTvnvY>8|K-~ti|pQmr;d -1KVZ?4dKw2aU?g-C -ne*~7HH*gN{ve~HKO^l7Ki)C@yT2kbY!=-`zMPMWt?yp(6B#mbd6Iglt2Q-0omjvAEJF3ZRbBepn@9h5dKcI(?$qASr}|m -EQWFYHjJOUGVB#|H2IPBGm^MT`0wG`>CZlEZ_%V{*jWG#Wa_TfptZ`A}h7q~Ok -(56!z`GMWyxW;fF`q9jeXN_U*M}g~2@h(<9;78X@kQzzf`a!$dax;`hN73EQ?=A&*Y?Mf6+pkNfGwdh -T+2-9ifzN$Dpmn!^ZB*_)^&>XPY12to1OBEH!^Zse1LbGa?X5X6_XA3lBE@x8qc-y(I*K7Tb5h90`Rx -a8cRuUav>*F|P4AIzJBqwQ{-Vy@_D|&wknT7-4M?4O>y)d{1BQ5t^P=R}a16OopE7H45rWJv`?4RnH1 -Du<&H6;^^W>hlEqs9m6C<*rPaEr-(P(>3lD5}(xNo~oAiQNkiAOuo+fw#S)Ml8}o0ck$;#uioDmuaHU -Z$o6a;n3&>d3aqzv>W)IW;XusG+!>Bf7q;YZzEyVD%8GO_GInj(*m|3z%VJN@;PuWjj3oVAo!QXir;P -k(s04ey1MbemsL3PalQR>CRYx^YCpPx?L=fg8Oxd*LTD@90F0gO3NEqGVCbFC78Cf>Gq7!Xclei)Hl+ -S_~F7EJid%)0&#nGOz*l5SqXIpl43wW#;wezL$G-oy{;kZsX26XNpC}VR0+E3C4C~d>qHDzdb -FPG1CtYa(aR -0)0*b}+}b2zI|VGOpWuKCWbYS;kfa6XPP0VO7=~@A^z%mQ!J(^1k1YV2!Pn{&!YmxaL#0kM?`9%Wur7 -92ZylhHd)b&4?R04ZVN_;je;c3Pq0KIC>iA*7PcPwiAos-kBcGdtb?5)!e?Mp0Ej~EyTm6xqI2q+Y@M -bt~U$}lKVEQ&W6DC`n>2SZG}5ZQX0R4XT17s8-MkFrs(Is*N5};j_sw|s=W~?&v2{WPyRddUQw;`YpM -~;#PyYk0pK569_>I2&hSw}C`!7&S0|XQR000O8HkM{d4@<{5z -Y72WX)FK$6aWAKaA|NaV{dJ3VQyq|FKlUZbS`jt+qe<`-=BhTu#lA6+T^YmpbNM_oA%lOLGRE8 -EsDiLM@y8KSy@y`Y1h8C-@P+@NTeitAD1f%)CjURH8Y$i=SNbd6nm0oW!H9+Wf|f7M#z@XtrAt&vdsK -mEbRDaC2IG*61yF*ckbr{l{NfSi&h@576d*e%H}PTtq_%RqJQK~R*8DYRJ&k@oHZ@^pr8C+N+Hj2a*v -fz6-ZiWmWDTJcF*cUNXMl3!F#x+isfRzr*+{7-kYBTjZun=T8&7wf&(3|3wCfM-_!h#?%20D=+Sg7AM -}s4Z9CRxyjCr(Dpq6#SFPmRPS3X@_%q`wV+C*NwqnUhR9dh`vYaBh6Z>bj7x%3AP=C*xw{QMt!Z7gV` -$B}~?CBlL%viRQqH9(}+hTq!78qD2$;L%E1(iSQu{g_Wx`#YT7K=q*QKiU7(AfGXwto{7#N~3S{~`tH -Ehdfel)_2#sw-du;>=a)LbF(JDncbA74QQeuL&hN#BU9AxA;TMI*(ITwDL#xCg2OJLD0$Pbi;8M$v+mP(8fnZI;D~RppAjrg8_yy?%Z7U>#vZHsC(bSu29%N!o -M!e2bnBqKBbUpbOHc?)Q`;rA1+*8ut(J6RkzGg8$c|YYPhAidMmMnj_C9Qmud`pp_jP5rC1jtJ=-Cv{ -Ed5bjzw{W1Qrq_dbe}=jhlO9lNF#DCXzU(Mf-yj?C!jvi0ZD(Mczeo1a+@3O!HB$|zZhoK|02!#Mg-n -nJwivh8Ta>U+}O(w5x9dc3Q|mR2xJ&J?a?g4Ckb*6gZV)TobkcBQ%p{5Q|%_2 -dGOQY_FY{aG5?#c?Je*Y4R9DvyM`r;VKAQ)%{46dUp$J-4|?Ur6k}`2Dh8`6!IoICdsc~N&+3-kGpTS -@Ov!JLPUMO#X9Br|HbVsZpoBi6h%))x3s?rRe3td4kbBymbHHqrG3T;b-uzZ@NKeS*itT8A#A3+->g; -QUUrWi@kGjke>G(<5dvEy{a9G|#+A|k(GZUeE03%n#2qv-hS+XK_f?4k^&6Lo=G`LEaFZLA_8wB$o9h -Waz;?Zp&Av+<#n64M7KErk4V~55v2VerYi`6-YC!(A+b*9ee%{6eu_Pq&Qi9NJp+GyHP2^Ho^@J3U_3 -$hJGyjgjZB+p@Up-4Ogua4IdJbW-<5<=cfhCPZpFA*b<&<0vUBRM{7 -1-PFpv$|SDo6bs;44{1-0@N^fP<&SL`c7@Ew&^ewromvu#S7m{K`@Mk4H>`(*k7_f;f^ESPZ5M-YzQ{??<5T|uClAgQACjDg^ic)QEKdF2iUdLw7Lo=b9TuSgz -Nad+oS&JKZu?6NmSqW^jRi;8g_=AgCJiV{4ff+7g#0v!hGN?>e0<`7NzYSJDfYWQ~A!Bmm@syIKJ&6l -7=%l{_a;Z?dFTiq=QM$`TepzjWPab}JH+`Bgr49wyCCb0GZxd?(IWY~A!SC(g(Nzy7zJ*wxT@dC6JvG -8Yc~|91}bU;}}*^-rQ^&-39YFv_-l2MV;W)B|txTkQb@1<1%hGZz0{bgs7=`Ljb@Fk@L&te0Y2<@;Im&oAsIfu@n8Yei^n6U_L_5kk(Q``sc=U1UKK0odG>3uiJ!Zq4hr~R|hj1Y9j= -=>}@ya{dU!}7mv( -`NG=qk?RB+%V{H*&REYglgkKA>S@1Ak0O_3WcD(XlKPubPD5If;WbOv>kR##Yr;uFRIYo?KCU?_2x$J -z>t=Vs}-+%1-w)}1JJIQp=wg~XF!O$i< -*7kWRwiqg-5*YPkzGRl=e*PDeO&;b3L$4`%{OG1Df*`Bo&LQCUlEH!NpZIpzZbF-tPU|`yoG%?PDIqT -*@9>|LmvrT)fx3&S$xHa`>H>Gxe6T2|u#fcpY)wY|=O~5_c(w^PS&Pdb86=N&n@qnlP(_(|4_SoS0d{ -^Jq;-Q|NFXCPT8hXL8CNcU}BfYZ?AI3TWP(jH=3SM))_z=hk=v1@UM<=%W0 -4)1E0KF$Xf%BjC_sRXghIW*R*$wqrcYGJo6za?c14zCX*Lmi+QfG8I9!ov9g)}MU^Pn;PZ!Lods -^v~8bqGXWuV0uVlZ;Oc`)WyyI15}{W4Cu7ea!wYw`!){;}J-r?+j}sH@A%9borvo91GF*&G{s+3@C)E -7dV|`SrVR-oDb`@vaAUk?XJCzWdeJUw!lLms$u_I^^gZJu&*m*yH?u{pJSDML32;heR8BKod2?!r3h$ -FYKWA_KFjN{18kO0bYr7aGU0Wu^idFSF&Qwv_gT2MVkO$@MN^#iJhDe>Y=G9zJSvQ@7m0lR-ZeRonR{ -Dh?5Yr;z_NpJ`&BnX)t`AqN~3fM}1S;;C>~xN+r$~-IWcAO(L5EbDiOCH)(?IlJ3306L(Xu?#2%j9CU -<2RRVT}x41R#^nt5p*&ZKRBEnapWLM*mle -IxJSAvsQ};HAtR9>+oTCh&%B*vWzkQSfF*cCrLLMOM~SZRG;4hr^R;yS6t0x%6Sxli#*W&GtfOkzeh6 -&G(@bC^(0U#en?w(Xft$H96+jp=ihT^bv|^c)^!vHEV@BFJ)P)WSOYmOm|D=>E?_n?C?fGAqu*sIx_z -T;qyG<3O9KQH0000805+CpNv=YK+$IJ90G1H|02TlM0B~t=FJo_QZDDR?b1!pcVRB<=E^v9RS8Z?GHW -2>qUqQ4mB&D*@VcVBrF3_bJRv=A-Il%^ahQLU)%|;Scl8WsW{qMV@Nb1ci`e6gsy6>V!eCrrODUlsS_uf5$W3iR9u?6@q%OJ6``)7P8LnC9Rb4)6143XNB^Tr@QyvUZZ)pwPQNfOqRT -&MXLBeY$TW1kexiQa#raSRy#p<%vi0tsMILVYbk~74&%XSHEAc(%(Kl+`j9i(`Q -!YLMAoMo+!GIJTs}lic_trRE$v7KBRVc0^r4;cBy1aG%{mqYDV!Ru=d<}b~N0kS-~OT*=&YHFiDoK2o -j*~#;k>DTG0~9Cjzmoph}SyqcYoD8bbRWUCs!6Uc7kGZj&u|hq)nbj9~k)7Z4Mo5!h1==*bhwM5QSQd -Vl%cFnM~D0EB@uf=d!8gBO^!MgbBmIvGOdwG=zD7Hy6;4#f#LD}ZDDGCrYm&aWhx8PS|=;k59I>og4& -E4DGYpmL|+>*DEP<7+W%;AOxnl}f#rA%;=v|VmtF? -vc1ot{GnLEW;g6e%(v~)bUSjA;eX4od$YLn6XGOgXeuYy_K%a;$@TB)pk9RFggB+7NjZ4kKb2wdfh&avTe8m$hh~FnO$ -oJ5!nT}(`skWGy+gH|P_PI2EEl@d#u&{2BHA@j;B}XE0tW-}FbzlNRVRZi#G@ -KBBXibu2q9R|wDUCT7h`95}>oQjssy2dy+&F$ul -;PKx8H58zD=A#AlEk}Iq)0rtJ%(oquF4(3+A!jh0dbS?)72VEN39TlLdJ>Q|t_x+xcf-6nhX@S9T1xU -0gPK35w&<5<}Q3?5zkpqPj+JW{oiP8d(Ag>Buu>=f*9q9V757C2$!5Om!j>0o?+`MekYT~vnF5RJmgh~ctGh{Ib{*}VfSb@(!2S$+=L=gD8o)jUru~39sZdyhJ86rkR@r -17W*83@%4u3}sdqG-IB0yhf@F%4C4Uw;kzA2jcER;eUHKqWWAYKE2r2+vhe0dz7#oy@lkN77c?GM)q` -_Yoz9ul)=rqtZXmEhCXSqxM=;e6KzACQroM&-!t%6tKG$^um$XkT)8;DKUcSr$>EBwDgOE=!v!Q?M&HsLi@x~bh=g -;yt80a7E(YS>~VD^T|z>kIt1Po%Ai;>#mOu`oA=W*Q+K;DoaUQbKj=gaq5`7ZDY#Le6L>(% -`wt{VvJX*cc6dp&umIKHx9SLQ#yJd8)6a#yM`-;QdVCp$Q>^}3yyz~o;J-r%Fzzfem91QY-O00;m!mS -#x|Qy(P!1^@s97XSbh0001RX>c!NZ*6U1Ze(*Wb7*gOE^v9xSl@5kHV}UIUqQGiBp0^OXJhUbPp1T -^Jug}IkDq1i%PDvP*(Cni1db5MrI`|)@zxs?PH-0x|C(~$hAm{-Hzv}RdCuob~3u+RjuQsNQJgScYmI -qp5NU~+4=45^=&5`KZ!goT4T!XI#xm#wMvBUNrC0bmZNP^UCFFfp76ZLWx_N0kBD==6Qj`xod72n)|+ -T8sw-LxW0HyE35X>b*P7jJif7|SXuc=&5rfZoJjRzy=Bq;OcqNM*Vq4S@mRv9}d{-y|Z^7p*6Deyf6g -mPRDZs73ia>*wfL3Y^0vo6vWnwSi -)=Mz@3>lP_=n6s|SE?30=!(}_wYcD!7XGV^$jU_%uWBRMs?IW&Ne!pBa>7NsG6*LVJlWXjMZsj`d)Op -S1VEuG<6^v4MO}vPr+yG7UEo!no0yveo-&M~6Lq;_c~Q~o+%vx+arDep4ipXpN}f5p$poY!1J*prR4{ -HWZpa31;I~HmU1dr@iO5X?+Qo0RAWq@lowtyJ5Ks;4T5y$YVoRtPSr-y$vTuH=WM0Jv>Yu!$b4u}#Dd -Y`AD{LW_3UJWuAY`(YMMnSsQ{$R9Dj&2IYs$blUSPOIWrR;ccD^hm*AB_Hd?D0YK>dW#hAh -InAIh86I#NGLbqu(4G7T#MM{9{H5`R!ayG?P9$SlCC|-d`(88P4iK@S=0CWGhjyA<6 -1*Hij&P(Ju+}X2W|9+<>q<@J@y&4=n7itUKi?yg6!dm!4uk?mx1ibtTo6_TC2|JVxvncYkip&~-5`YX -LB7-diS&mu*(OF!7=qvzpmyPk;bqg`O05i>ME5Gc#uvr5k@aq(UgE?*HIpf!sz?`*}v`6m?SsqxamKV -O5J%12;NQEE6#&bc3LprQ*I9YaTzPG=SBQaIE1^W!wRQqBF{7!kr_tzmzQd5NTM1;7-bdLpRuyzTgI& -;XjmzHnX?fL2T?U`n9)zw+b9-%^UMWr@Eoy5x42JL~t0`hFbb!;WVpivm?ZG@jp=0qX_!Oh-+VtUg-d -&m+}o#>ZEy3Ax12Gd~j@V=o?h9M>m%frAzNBBGXTF5-~&y1>a#g^;yza_XA=3 -bbfzwc5;6*6gYe!jT=`6KL*qtWp_L|#FmEFlEdVBp>N$70{tddh>W@>IAiZ5kl0h@(s)NvP#S2D046Le|y%nL;HzUhly -KXy{cx?nv!JSqKO^b3RVGUD%>4n0w&_<%UUm6DP+1^nOE=Y}ghQVp8d(MVv>g*lyvV?&+L>&* -A$a~mJ(6_=ETpDgZrjS0P?OHl^bLzN;$-w -Olm?r>DRcqN7-I>-WKS4mgIsHI2PNqpZ`Tke+r>m_q%TOuQ-F>B^5Ge*>^?OzOt;KOwWtD_T#R3z$;G`Dbd^%NsAuKY8{_?dU&HO9KQH000 -0805+CpNvnw&KhOgJ07MG_02u%P0B~t=FJo_QZDDR?b1!vnX>N0LVQg$JaCwzhZHwbJ5dQ98F$5OJhu -8G;a=tIy7D}O%UP~#*p(yq^7As3WN#0!keMjI8QlP5Rp&Z`jDcucZ5;5U)v|NQ` -gIfy^j5S{M0B!bh ->O?-{E4u%Dmvl#rY^jX2eM{e+`SM~^h68caI>pCZ-oun3u&cfAjIJ6g6YXufv*JkZyWH=mBIM<@6gFX -O>{1xGdwIIBdh6Wukx#Ql~5*?aGC?pB)pc0Y{osk@vtYw2+p9>>e2WP|E7R}O~1kCBCB6?ctEEKv -cXz$RVCUiUYZ`6e7__Hh_oL=o5U=JwRXK5ojxgenIxN3EQM!T;>n7pWaPcgco{w@jQzynNBn5wl>Q$VVa4f+ -r9|uUcp3H;124SZJoe|N%8hizJL&Q-U)OY>B(L1$v4`+VrbgN33;g`g)i`IWq~xW%5Gs&Q;uRIF&Lv* -y>m&7sGJwBpx=KA1P;fh5R6Ukk`Us(Qnku6+-Azb#KWi0Jy|g -b-X#<5;l`u^xIH0RVT_{mvNZD+cL~HLu-7^_+qH%-!>|}J;?te{nobRgI^2a6e9kRWHI$4X&W -@dsC9`O)hh`8+$ZZ&CPpi-<6pDdSG|p&AH^-M(DkbUFd8v@DC-C;DIg -)LSJlU|uy>sjOw`R_>Ax^3(?Y1PL{it|6T!gRB5P4UKVNmW{E{+0qky^&M0=VK@%fi=3kWw3`hALcE? -0;_!Sy=F|VPpGe}bm9E5KW&s=0T-^ug0{uYk8PXIe0yb*+Qn@NKfEL955w|K^f&pF4Rs5PFDfH6qbti -ww1vt`g|~{j@dmjc_GsvSXoL9?k^7AArpP$%fR${6^5eZ6pzk$)qN3M=+sM`2vbP~LTv*(`w{C+M|ED -`D?(|UdeFEqaLhlt?(QR9r_K%f^y+=5)K&1w6^$@Ra=U-#K98c`KB!&liP!%o^@=yiC);kGw9r;wi_q -=nO4fTCWacUb$^gmEb0|XQR000O8HkM{dmp60f{09I4F&+Q_6#xJLaA|NaV{dJ3VQyq|FL!8VWo#~Rd -F5GMui`cnzUNn1R28s=ob6q8wCqK@U1_DNs@_)W#cCCqWB~V=IJF%({QGytb`mET&S5L{rd&Xc8{#F+tm*=GTCZ%V0pf6Y@;F1nK;x+TgEp=$;LwN|4$}iT∓CA5E)X`fD9S2$r -NUScikY{Lx{>Tuvy*^2!i{M`r*2dL~%ex@gf=K^XgRApM~K=QNJN-x=O8jeCW=Ep|J@`KQBxM$b!cVd -;_!S*DL6|xiVe{!XB&4qyxRkop3TH1C=D6G=wF=+`k&Tvw_*WjRzP=2a@6ZQ9R|Mb4;`A&NP+0y<=e} -3Y{9+v*utp>emEn#sem3Po&GNC|#X6rU0*$(V4Zfu_C6+d7s7f4n}ZcJFhdvksngT>>7;b%IXx?i`x= -V4qIdvOHAwo0?JYBbY5yI~YsG#{!GszQLtCQgFq6*$kxk9Jk_;)(Boq#U?iIzF}Cf;P8V<0Q`p=CMI8Y#^Q*g|#*m107h1-?t+m>*&jCnZ*t;m6%+yebtpNR=Wm -$HGW2Fa72n`|O7Q^Qi;5x^`Fr4@cbBHxszRVz1fu^=BHMjd)JnsThN%liW-1){dh;&2?y2r!YyU;uS0 -@vBhbCs~g+9)bv^L%bVZkOy0*E@vPn>SCVkTza?jfoeh8KF;Z7g@(1Gfp~2%;_9$FsK;OXYKz>ij0@; -0nMFvlMZpK(`<>~D`(QG6neHC5R?e!wMRvMIeWltIdZ+8r5Lxok#?<2Rx}#thtqqI^?E)X_)}Df*LO( -zen~^xC15LvDgynT4#fD+6j#uM!IRQYl-*@pZnNp4Uw3iSFMWl~F!y}I%@?ocsZ{=zY#UJ_y6)gnl{~ -+aYZUC2s|G7SnT)g}2;AmcwA0hPpwab=*HqJIiFi+cezuz9zOZY2_EUWVN`P7lDCzIamAI@v$8~RpVS -KG~3jD%F5KHC`mqIBl-$=NFCC>Rf*o-djpccCdccnb6W%BxL_egOUzHpDdM?sGO%pl^q3tT&?HeB99m -#4S_ubcRJ0edmL{30w6CNM^2q=VV8o=kDukVwB(q58~X7_!ij5*+XH4EA_ej5U_xYjrawXzS-kE)67W -=clTWO$oTm!AgS!?3hDObp)wuw<%+nV_a0(kLOn8GF?Nd_n^vxTXIzzW?Vk>0)~b34)_(Q**%C2SkL# -|?7<8`h>F2;yX8eJ2mS;>QohrwsfPiG^T|nYG((@d4YcOkfKJ*O&7%jZdfr{c5pkTv?6cPao%?f!Xtm9xBiz`TQcmvfS^DeuL@R;&`@vNUcse9ny5g(6~ckvF8=KiWq2CL#>RUewmI;RuxKOIkO1Plsu -^_x;-GJcy(rgNAUtPWO+fEyvSIlqMmdfIsBhF5WnwtV#4C!rmu5iLLU0{-*RG8;SNs6^789k*z^BK9O -yRjMoZdl_T!ZRR~gDhOC;9Kq1}{{Ag1Vbkp9vrp9H)2Y=_f_Y@$8ak9OXW3s$RP%m?s -ENAN^-B^3T{l0};9?pj;7OhCJ~F;B^gF+xiQE*OW?tY96cO)9*uAgdp`IEr)MIu9m2ujg+}KcS>P+3e -Wy~Gwe(opd-Ifs~3|$s%!EV{o)z+N0-}$&%Ww;Ms8b1`|_mP5bUQtee4ciHGQ^eTqVwu0d%<+V3wUQ% -ww4_|Ob+20#QCnHriWPLsYVH$HIBHtB8O`@*=UFQ2+)QoPnUv$}PG!EVRNQ(?jj@37p8Rm7zt -*^QiYV|qI@J+b6_jjXd{Wopt32%MUZu83zU3Zl*`0(od;C^sDtWfQZ3zpB%U#^Q7UIKM8hQqgnEYg|f -!*kkrZRU%#U!6BzacGJ^(~n$8e|O}Af|ZH2Z;X6^QdcV1wDm75)9)nHN?+_yYev;7jWqX0ay*K0#VU6 -NYr%EnRc<(Nlmm{usdIXpLkF<`15ir?1QY-O00;m!mS#zrC#iAl1^@s+5&!@e0001RX>c!NZ*6d4bT4 -0DX>MtBUtcb8d9_&WPun;c|DM0X3YwB^A%(I5$BC16$9Cd`D1uGgX+oJBJFP_;N4C>a>9oK7p2tqwq% -CawaD0H+eqMjiiy!0r{++4woR}#!go!-oN)u5wpS(2WW@)CfBy%_rvebl@j8p|Rxl}Wc32l@Z*9MOg- -}k*-$r(x0yf(GsX-dSblFAS|*0QJ#Pu)5O1#^J{J+GmVy8Y5i?KwG>sFHNfOD0vK#ZxQH@F$a+WyL+u -sO6bQ;K$MzaxxL+B*|o%i%Cn)L}vbQJ5;wc>3Y}WPZ_TaxwPaTl#;3=6K%-ktfD4CmY)>8VPGbUf@c= -3R>Vj#0GeF@&-V>!!H|v#744F<*4iA3iZZ6S*3YU~aqQB!Ee5Hc6SbumvFWo~l6EH%RoZ-#^Un6=&HI -;FN^TZXJ#MgEyIpS^14yf^2@38qo|6eTsWz+-<1kv=4xyf+C0|xM__Jb9kPB%--|_lU{9Q_{m~HBZ)t -BN>FD233gt;gTSK(O7B6hY!(aV{ERR|?m=>Nx6+W=DJ)CQ`bDu#>mPpalGd;S7rY>>t(;D2!qx-WEONvuX_{&(%3PA)&PjL_lebNHx+a2e2+%s>F3zAn!ML|!x3nn|q*~T -0;~pB;3wM+VxYHExM;W#G2mYg7RGV&hZI*qm%uqbEHH@6J3ePd8yYWNp$k;U(-s=0q!|rtChK0_iU-*G8w?B{!%|o}L0~D=XKj6zUl0A^S6a{*eL{9f -QGPxD-pQmW(6ecxiaoqvw?Ep9T=OMr?p`nr{m>3GkNAOb~6AmDfp;@1VJ6`8lWd$ZcbbfCe=wC6of$- -zT*Ll^97rfBLw83Yky_hU8Qf&jYf$`kw>E#&8A7E{8od-AF@=R0Dz~)1hIFIMCOYf?-g}Ju-hA48xMX -q-aNm&uIZU4Wu)XDcMkxHw089>lG{*U25{04Bx`9qvL~v2>u=(kk_xt@up&wIanP!pSley#E-^a9Lw< -Utz*TLKocNJTqrLj*QAsHJxnKZ0W~)(MER)K9!!m?^x0rA5oTJCaU2>{%Zd(QL>N?cQ4D@LdbbC+P*! -VOsp{R)!O6kFk&}Oe!DldHLj3c(wBrxXm~N^fthPDOPMjGS1LyBA|He_%3i!)jB(WP(^;FgcBOE3ZQ$ -y!pl8GdAL6?m;XNn4FQe<@J3YFYXxpNxxkG9?sm}*?YjX8iy0gNtIokWn3cEKpU9(^2LU5)O8P8Q@00 -xNYQD_&Z}V_c2W+l$fl)j#B4w`w`p$%vN&M#*vKklt{+u_)07cUNR^mKBgNc(%?ftf&T^2ANK&-M_!R -zP$ePj2JQFL_$GNdG<)=by0Ff$D$Bs32miDx3wV)jvx>w$zmW$YiPIpFfYIkbS@DZk?W?`TCptPj$6M -HK(n(AWFyE^I_KnT>+IJaa~};0hE=f%N586^S{v*(){wvoBCWtjvVc_W!w+u|Ip_VZ(d7<%M>ZK=>0Z -wKO72BbSV)>OM5|G0TQWX*WmByu%EhkXXq|0pmOoVxN4Qnhu8RF&PsukM>u*S98?;fidqXrSe_3Wl%{ -W?$voBS$E}dOixKOv_dIBM|@qnkpWFWMz`Qh7> -0r<;n~t9L^ULAcLl>rYA7I)8W!WR0aQiSdFt51D%e8|UUApA5w?qL$C3cq?HAL(ozY>k7bP3XC^pQL3!9eJv8D7jW(GsvxY~PJFUT0 -zj=)=He1WpbMJrRoMa9=(*a}8s1(uA-p<_*fBY64ZKc@Rf7UJC(_#)YM;&~ -lyMks%}c!NZ*6d4bT4FSV{~J0Zf0p -`E^vA6JZp2?$dTXmD<)E01Z^6zeINFsiY~>KD4Qvn(vh^iTQb7}fgy<%2yg*V67MSi{knS|7ytv3vb} -q|Nl-3}1ZH}A`rSPZp0M#U%ozOhn4he-ESa->$=S^|UnVh&lEos77p|Yg^Kjt?p`VM7Y?TC?hRRd);wK>Sr#U-%lbKjhUfQT0MC~^ZU){P=I#4#Bg@{FhVJP?y_G=d0o -lkpC1y@PuS=kV8y0~Ul?o`$nc&Ve4Vhb)=rfH9|sVUCCgiN9I#I0xhb6e8}$TejY$>m(E7kRHhKrcA!{aw5DpOYS-0BlZ_3dFaFK^_qJr3uETNw%~EdGg<;C0-#v}lqVqdsWb}NQ0% -)36(D4r^*Tv&;C6Xq9lZ?U7kev?LSm2_CIdV+<4DVZa14NVd6*$EAj^6)i$Wi{3n=7X7~_2)y{0iN13 -;wcX-X^VWozz-^U&|GhcwJ{9!r5R1Px|?><_v>vQhVM{Tp`CADv%y`|n4rdv(P=b%(?5VBGJG*r)#Z9 -UJ!Ec83>ie9gx1`Xe^Fz8u4lUWfGu=T{#t`h&Ls4kz~C-(2;37bbw~OZL7uJbwpYyWjV(`r{v2cW}Wj -`{O}xGy;Jiu)#I!ee4a!Z1fI*R51I#$FBO_@2`67@_GnU;r>nUyx+aT)i3(P-ualpgY)absQ2F=017- -}7v1;Wx48Ha5raAE#;14P@#q>>8^WeWAFjrLJG&fSzh_t1BSQAW2=;Z+9d{Ax&F~r!9d%${Z~U%@&;Z -{q{5v1_uLn3D79J1bYljVbZ?F1qdxP^HPQAwQK(ZkW{4kOeI;=bFk8sWF4`ToZ^I<4}1So@^fJqdG$% -qNz&>OjJCP68(Bmz~Oitk59^1d=pOAyfrtcALta`h?O>YMp?& -9fu1#2O65^q$87h>;o@J|4jy=8<>D|ML_i`e*OxsDFVhxa`B;&j6~6wknLc)B5|Z_ws+H6a4?x%Rf&i -&;Qu!;DCPn=;$cmb9V1VVK4>Vj&mn#pB^!|Rsf`c=&ronU&2Fr7S2hDR$?SxsTYD7`RGL(-b>RYby@= -$;0egeM$!VH96UW`&(aoq#w5Iy0|{d`PB;9BtRC6<`6l+sTES<)GK_`3N;SPv -lO=~m!Kgj6#n5vTr=`*&CTo<~Rg(dX-&_2~)iK+Hqx7~`A>|8E!j21=mi$Z{|Cxp>+Eq;g{jZSWqbNl -*1{9>H8NDH0?*lLN{VVGP|RdKa5_r*wA!_AM(1DVv0OnCUo33Fv%bbHeViD`XS}jQT5}9yE -0?^|XnQeIgfDoC_SIvp8p7t)5+Z}fHeNKKD&-};KO$M-;9_Gk;_>2#AAVqdqXSH(n&RT8tF|3I!JhPA=FOVu-<{3!sC8#E -@!oe0O+2+v`8QL -4J4{_C>!&i+KILnedI(GY320ukuZX3CUZ%mm;-ivfRa`sNup)qe8`j^dJSc7!3Ka&AXD?qjF|su7Ur^ -unsS^QpaszZ#q3GFHX%s>D_A2!|&;)*DZqRDFU-FuFWRSn`V-#FHgmk`K5zfA)fTWmT -@NoB0e#M%JD(q3wBc_{@b}`ndD+iyeSCj#(fgr!#;q~q^Tb%D|F*wlq2PtapCoOnj5p+5XY7A9$68>l -TkMqnsxhrXkXrtHL66~YHKzny8Ye|^2O+d8<5D(Hy#*Q?1YEZbwC -am92B@$XG`2RRwt5u{Fr-KZhrV|RNV_&cmVLU4UPk4odhOfQlh>CfjGh;Tm4-T$e%@jE3~9X0To?<2J -5QYU?d#J?X&9Dq%=*u{7fb6;!t@Laa2Xa>oD3AET}JF4+>r0V4yV%&eaTbL=XeS-odR4!!BYFVFGSl= -aVg%yEtN$4LKsvX?4=|a82-U@Fp_xQkSBf;xu&p6su4Go21%c%mp4qvmn;v{cPyWwvm$u<=1K{@X~1R -06epD2@d{lrra`v)nZdK;4>8r6j2hIl^cYxH@$i;XoWK$P+^$?62Tr>p9zi*TfKiJy*{m~=q*s&jP(1 -bt!L$Y)0$6$m&JqyB+jW)EG%>uCl_Vxkbz5JpXINX6Hj+UM)u-`L1rL)GBXr`Fj9>x2s6|(6+Zf- -N^=&;RNbK>A)IZ~#Mv}W6$+g94d2N(Yr&gCr+A5)7#WL%W);Lg&pmU&o_+ir3gr8C8X~vH#Lcbh-BFR -GpKi9>-J~6lCgms)n+WqB7*VoO$M^^;WQ#xR6pQ4h%9rFF_?SI^r*3q7$lH^IdZNbcLdV<1LpfEXgqb -V{(GHEUGxmCAIMWiCps^(i%78lby6k=GttRCMG=TcrOP!XRks8rEW!LClA#1p%BP@LAo9I;OAd{0DKr -gbBR!L?PIYx#xrUY)dhoFL1*I4pW|%({8n^#$@Sbv|+!) -S{N-y(RA7p8mbrk}8LG_oYz;#9R -bfzR|P>}^_PvIy@ucZ#C7cat_vy~9LJroUmiS>Ud1DNgy$^XCF+iEM;(HDgRE1MH|GRV+riSOSBnsxG -0K6g06|$DCb19O2tTAbp4{{2-pzk{fg@Ugk-*Ik#E2vf#_A7W$S_RjXR&s? -@g%;a3e0qEg@)=c+F;d{df%-Az36AqB?%G2z(@>q=+13VvM~iJFzvDp6H@1B9MkKrd$@CM8?a>9sOZ8)Iu@yytJT3{P6m`Ac1l98O{?tr1py3IHn28BBW@j~k#&WTra%xqJDynNd0VTfl -AOJ0<_1@o%f1F$(^xsOVdIVaoz<;jMMM&vsv@_iZzkAOMaHe -IT{YX7bcyj^fYq8qH<8Mb>#ndXjU$(G~*)9XEK!D~2EFFFEsQ@G~mQaNlb*P`v&X6x)#9<5&7(9jkhtWMvlDtB6ywxb^AlUf)>*j%B%Qi(+K*}OjJa~dC7n8m~+KS0SX{%@3e5yw{Pq%417W$P1`6)82QDq -02h7&g6kD#6bNJkhER}T;hfCbS8PwT9eFe;KE?mk5<#EdVI2{E%#An37E8ueS|CgEvk3dM -md+GW;TTNJEf}#Un_DYA2{V;v^k(t(V#p&x8^ie3TT;@H-25Q{etN5u;|1PTNNNyrB=&;DYF(p4_ulp -)#DN2`)T4-So&wVM7;^=gk_L~aw*^WGUZ;7v6UxJ6V?g7hLEau`;}_}pclcn=y)#ahwc6JF54kE&;dn -;8(H8wX|h=?#oK87p{yt{#xft0Xnd;9UME0mSTfo=JRrI)udNNeVlrJ5U}7uH}?ps* -5u>wH}E0bhQU?>DSdejg^Atb+V(lQ&LBlx3Q1-Fcn#GQd^Q5Pq$VF(Nc9tWj9%uDsZTt+--GbdX~~3w -yq(>ow*)APJapX-(jdcw-Pa24>R6(fcF}WSof0`^zv2w4^84@35wK2BWQIHmZ#t?Xpjz3@z6toI-l>l -zUldu?S>-=b?BprMz^Vii$!mg0}>m*hAFccXF1HNO_DCOS_|&MqRg#7tNR!$>VK3 -$;bCHA4ohmYb`X8+hjI)ze*aa>OF=Q?I;dmXN;mgaEm`WyQ?wcoJMz7u@~&vJ#HR7k9bV)kj0Lg*$g} -0tju%ohjA|>%4=v-==u+J1tw-l2(yFlRrxg+l3}MZiVU9GmnLu#YxY4?2NA -DcQ8`=-Pp(Y{@4q<@JhM{ksZ4Fb7fIDoPY*>~jYq3s{&}j+atGtl~(!1n=-}AI1qZL^tXf}-MjMrX-WkScBiZg^%{)YdBXcl=Q4w>cjw76>1e72iN0K -Z@h1okuF&LL}f6Z7kUfqSUD5#58K94J3zINvln|gAyANQDHa8@F@%O&ihhwx6v+q>zrixz@3w#^$y`Q -A7td0#9bTr+z-UalWuPb{9n>{YWZB}?ar5l7dhCl00!?R>vcEv{{1yjKP;^jjv45S^7O9UhwJj~Qvf2 -J7i0+K2Hl^cL?Xe?+WMMGu=vdaETE{Ai#~0Oe+iM(z42ps+DU?6IvG!8+1 -Me+z=KZ3(6Un#x;3^JKMJGbcs~g!lGi`-p&;nIrRDf7~G4W0M(?fE{czUeQQ#w6-%l$Pa@`@D -XI8cuGZ_>NK#u#*J@yb`se@*DQ?U*TqCEH?oz81Oz9A~y*(JUGTwpOD2ixbrHWroTv_Nb>4mp|SgqDR -Qi;u$qsFE-l^~GB$s~2eHw-r9{W77tfv%`fsZNhq^ioM4E?;h1p;s{)tF>0kqky}B=Vie|SL=nW6i;s -F6RQC_#L4V(E}q`5SyY`H0ccs!R9){dNz*6tJ4LPV4mJY-_iGvi!l@q~LSYb?gu+M<))d#> -mGvQCi1IeON)~&N24?`0pB;p=8rzJr9?_(ZgsXn`tvY9G(yikMxw569_K;|vB6*11xwkqXlCMYcqHGT -9(huy3_@@-X1FRKEJFQWfyEl6Oc9Hw;s9lim7AdhJ=8GG`MoRKD#Ba^?Z}#W%oprDPMu4n7l>i85DBs -Mtyb`DZ?e8{2bZjAc3Mw -n+a|I1%48dVnFu-wa9IJ#OTu`Ha)hQ$gw#m$b{M|&n(f$gtw-S8B}s(PeIK0M6iiijfBU@%09llY}ko -yzR5OI1R&9bJ|CMOd=uP!Sd;s-Z_2Es8!F)u$W!*A&g6T9PUi#oNz@zLKJ4Sy=XWFx2zqqTkt4 -DAjtYb*rXTmjR^?jS{7nAnMzX@J{w$Yf%v}U6EB-$QM*UD*oLDfSjD!M_f^gIgmyDH~0JE^|CB7btU7^o -^Q`X!b_-d`zxW2d~J0#?*_U`zi8P)h>@6aWAK2mm&gW=X0VXS_iG002h-000&M003}la4&6dWMyn~FJ -E72ZfSI1UoLQYODoFHRVc_T(96wFNzEzN%gjqjtx(9!Eyyn_QSbzDbQA(A13bC7xZ>jzb8_P26>Jrvm -B2DeItof4Ii*-G08mQ<1QY-O00;m!mS#z4PBLxb0002z0RR9K0001RX>c!XZ)9a`b1!LbWMz0RaCv=D -L5{*O47~Rhxiv_u=`pIDnT;t>f?af5ZugyS5M}WLx8h -_C1GZjx=dbmq-xv!{hOxgLr0{Jaqu^d=h^qX+c&Dz;78>gL}2GM`7r*7bOoiPCnoA3z_uMd2ka~IauY -J80IX%u%Gv6uY#Y3*Q=bNou?*lzw-s)8TT!|v_$m?bLjcLmgz;aIEi5anqJ9PP&Z?Qn^DUI@`I6(u*) -u}aPoGs(!av?y-E^v7R08mQ<1QY-O00;m!mS#!MWn!qV0RR9t1^@sT0001RX>c!Z -aAk5~bZKvHb1!3PWn*hDaCxm%TW-QI5d6<6R!9h2igExXF4139B_Y{>OX5)5P@$@KuOA_?^JuA5f9~w -gj(5k7q7o}8PFS2%f$vu)utBaQVO*-buCYu@CY7Yy2=r-~l#Cxus?p&RKukh%5$zimD&={Dv1LhaC?sn+jrv+&oF=Jt7CE9y{kWxz$NeeoNm -v!|49aG&zt?(SbuS+n1WiWbtD1l$X!V572}3A$ejyLV6V%BTsIGzoncOs9;M4QB7J72fPU32{dU1_32 -5klEgy7rH#4=NnX((5a3*;LG7lBg4n$^-|Blawb9`v0hAF%O_nZ86t%D_!Y-F~1T)Y)^!n{;gaL8RlO -gS|b}Fj=2=F-hpwU!IpbuZOpzmtya*=*M1Fl9wg%0yL<&GptL$xwpQlBmaAczb-I_!&Sj4zhPiE=pMU -VT(B)q4|hAI;Zrh836p;OeHQrkVLaHEdrLn2cRO@bar(w|y^2Kdh%DL0`v$eS0>Ye^VW4xCN41^th~F -v>2x#;TP)h>@6aWAK2mm&gW=Z)XGc$k%002r9000;O003}la4&CgWpZJ3X>V?GFJ^LOWqM^UaCya8OK -;ma5Wf3Y5D5xNK&-Ypr%H% -ht86HDnnRyGm)x_{ylVwvhSv*{s#`AJ#R7%M%6lpb5vUK{F&3a`jT= -kl@vKI#T#Uj?_hv3WZcpPXZ_B#fqSi$p3P=tJkV^}S~10=d~thm=J;n>g0KSCY^?#_AfB;i>5k>{o04 -#M{IV9Z$VwrfjsJkmQR7U>^0nEh4e0RQJc|mI-oW966$<8cX|tEyoO}@&4ctPp{KQKW?C-Y#WtQ`N3n -vsLWEm&PV%Ap@PanLXhaR71Ek4_En{N?8tY&UotOIoyJ?=-ehBQ~Z3S%hlut{ow>wN3k6i}hRK<2MUE -l{pk4RxR(VnX)AB;70hj9$tN*iJGkMCf$OO|}Ee3vPKFo$^oZ!A40(HKaod2a6kV59ENvpQ60wPk1n6 -QhM~s@l1rEGj-PP9U{7g&1M-9f<9hQl~=S8)5~=dUIml|i{@+g!9?tX!+MAM%;hd$(GCCnd*+3S1doH -@?jG;XOgOcg$b27BmqW*=z`a(#_6xwmO<4TfmjwPUUyhCM -O8w;Ezs34>TU+hu3~#&5|AtE^9)d7++gEjYK?Ui6~~f-qES*KJk(D4vmpb2Jc2O%!#7+w)r5@5Aq$y2F3xfsv!j#VDHp<;i%t=dWg7%QsP7MYr -~JRHf&xeq2Q*JQWhbVP5OM -%(M@#uo*$7?bTfYQ&X?v2uqR6iq?Dow^_!Ls_k4^JHkxZ$&X+{uGO?HZvMcQRNcU28;c}!mzVGE)(+ojAhXI1XMa -9?p-29rL~+3qh{y4*nSqmW@K_c!74PWlwgL2p*^2=%I)nFgeHe5(6?q)>47%%%;RVT$h!>9Sf9e1lUA -@B1J?niA|Md+0m7?Ms9)Fp)J##w3 -q>W-4g!>K|c0yqE{{T=+0|XQR000O8HkM{dTxrr7fB*mh6afGL6951JaA|Naa%FKZUtei%X>?y-E^v8 -ejzJ2Z);5XvABzZ8z0{)25AOpXu}4a?zK2zdq#Vhd|MU+^{1#@_#0ciYINpAD_+-e*9&Y~ -~M8O9KQH0000805+CpNgUve3TX`h0G2KQ022TJ0B~t=FLGsZFLGsZUuJ1+WiD`ex8z&#qxmMGe#PUR)v8CGTxHGmxLnKbeibpU2sna#dfA8+%L4Xg%_H_ECH;GIFyNlh$zOdlQ$;slC -__-^U*o(T=Y+F`>HC*YkzFnNa>|(3ro@Lp#Yda;fjFtOFs+RE^Evv2-nR#Cdr=osrMi4b_wruov{O}8-Z(Z0pK;C1aAxSuqdPp1vlVK~D}Z*(1455k$U| -L8UO0qX(PpTeESGf)s_G$?_!#Qdc1cRhRKUUV{bKR@hab;hoM*4j0Y-^5m --`0zTB+qvt6keRzv3VLE9xZKEY}Efkt}|=eDUJ^<@E&C>Zf1d>Ex>?>6d?4CP4ikS67#>uFs!kmsi&p -moHxIKX0|YuRb_oQ!NUV^m(8Psisc~ -#D4}XTH;wx+3j+Lp6p^mhd^^^KN1vJRVBZfrAto1|pXAknA2ksDciBu~-jp?0$I*-w2}AF*&-$s -0aDjhtD&+MQs_Lg;qM!1BR~**&j1F~dvt?ELjLG9*_?idbwb6_c#2*(5wHu_(lrjU9f4eq8gtSTnSDq -0U~)8eYJEh0ZXL*a6G4c_Tq4ej0WSpgaxd#b?hk;I0{2RhIL7CkQ6l5GBYrS0Gt>>gi7E;H%%fyYO -V5g7*lgR2WCP^yuAf>|Y?_m->n>}fK_M42)J*N;!YmAAE2Xxn?^N{<5TyRre#yA=-sTwtok_(*yEyx* -Kpv&5OSG&ZUzPYn{%F#SxR4|7(Kp$x}h2N+ta!NwxXe`eP^h+a8I3)NkP>51h^K*e6){*lZRt8?pl=jK@q5glOl-FzZ;2XLKpu|=lXZeIcp!EpK%L9Rl -t-pg=Dq-EP08Vk@*pMf&WK2r -VaBqK;yRZ1U;xJU_`96OQUx$aSBT*Vw1SH6u{qk -E>P|?|O?$k)9%$R);%b_4^X_ggb-ews^sc7Ua!o82N2kaOSqu?Q;$D2F)rD!vze2CWQ-Em-F>*)eBt^ -wQ@pSg$r;2SU=R}#VV@Z{hP4|1?=TaBzwRK`~N&X)eKS)mWBxeh3VqeFZjUVmTrj@?-Qu_SN5ejgQW! -##<8V{(5&A_{R{eNv*&kVfhhpHh;-y%eQFVS4>di*~h -22?2g212d4ZX@Hohh5cF+M{pGi0`GG;zPFE)fQB{BSgnZF9(NURX+cyawZvxD@_{iQqCaSuppc!EJo^ -1sf9W_Gb6eAe+Tu&+WVgYElg|hI#RWa@7wtw6&Fx_7sc(5IH>ub?kZAFIxT5MRwy7lA!e=MN8F-!3w- -X8p&oSqW)sl!IGq5c)vk2IG^<{04%V`VOqP@yMda&*F=i@=mU>jc&s`Ht7Oc&}F5E=MDE*-CZ9qkStu -+Yq2zFv%%;l>}TxO!4|?&uZz(_&h#&zZHYoFh15?c)#*_+JnEJuE7a$t&ToVgA?>Xss9rVb~VKJa$Ab -x^Yqu_|9@%EpI2kG*)yb{)pUasA-o^a@|SU#ZzV7Aug%(YYwY6OvX?r1SN#48r(a-(fDs)MI-|PhN-NTn -DaGVZVW3DyUN;d6%#YN!?_Nhaf?pc8Qte#$I*HKcInMY9<2)@Pg0%(o+BRKTu)QfBF7JJOwsE7V5G#=$(JWI%--y$(cZ#QO~T39$jnTDpKQjLaEx_Qyqh+8dBmk -D!7@dV7oTTQ$0$nlM|VNIRr#jfYSmypfs>*&4(gfyOu6yV(8-}`$+V+K)!b`vS@mAm({Cj-=k>mCQe% -QU_tOn;Pco1KPTN`&E9wA|glY2oc#~HSfD44r$eE+O|TG -YXf7I==%rXA6<{a3ryWz2#44qsJ+$%-d}@xgP%ATIVj#(GStgMUOSNBP&5Crn6YX9{f5er@JfqnyJmZ -R8`zY&Dxd$PY=z*#77>fS+e>s8p&6NRYb(=th~8%Y2GiQLSixXXV320{ZfP3S=-m)??@QU4>oT@765_ -G1hJC}>?3(fJ0y2SEa1DsKHh4Lqwfs(yT9KkDNPKEm+Sp?p1{zgD{Gf>JI>X^jLM;w7)dz!5x?i_V0$ -LX2w$A?Uoe2Z|hjA$sSNSdsvjK&V0dr;o)g6RXnv8aFo4r)FqZuu6!`v4bAtq(tPzaIzx*C;}0Uv*(Wl3-%rv`6C!v73F8*VtVS>|S> -_tL4&QB}2GCuTi---orVQFbepcZx#kh0O0USnmmg^9!DytVF7 -9v^<4;5W15ir?1QY-O00;m!mS#zTZB@hfEdT(QwEzGX0001RX>c!cWpOWZWpQ6=ZgX^DY-}!YdF6d;l -iS9%;CKBB?C5F%MkMs8#9K8ElXxxZMs@vAN%r;bN)s3)n=`lq0R#YNMEm-`-}C5qH%N}-)Na+H5}5;Z -pMIS_?>@cl+g(x=+x@ujMNuTxu50@-DOW>V??+Ma-wz(h_jP-HT{YM0&vsD1dZB&|H~X=w^}q5yUMvBde&UF^iV+E$_;jug&!W&aV1s7 -hbcpT{cn;_v&a7dz36y4LIFwydkoTk~n2SbrP?=gjK5Y}%$;1FWA!(Ud!JtmZ9ryJ{O%_uu)?c|y~hl -mq`LWU&wu%woQ4Z-r@{7POOVp{5>c+}#LKTdT8fcU?KEYE3y-x1t!vex6*55q@myYBf)Oc>T?bZ-01a -s%_f4rf$oPs#p!hzOUyA{8Nl=0U&R%_u~!zMPHyq*{^R9JRze7B|fc1H^Q)V=d%Y1Jg=K!EbF?&PcQq -vg)V&$XzjmzFf*X)c3nmwt*WNz4)dhnH@Gi~eX|jLaa;A{zN|%aJ5S!cep9@7`PIwsUKU?}@%GQgbV?Z@RiX$g%gku@_YjQ~cy!M_T|8{9qO`nssAhMVBJfN^y(>{ngi!Ulsfy(l-uC_are@AKqU`BA*0U&Ri77OQ<#Z@53A8 -IXGEejp0S4vLM!zIoEL{f_aP$I6`>3Y`8Lr^%bA{H7dk3~v6F{_@LgMAr*mK2`9oK%CyZZr)Vgv!_2W -RrBjfN~#eN*0vd;D?!0QjQehE+j`(3s~YcKe)s*G@4o%^t9Qk_zrOz3uePsL!L}2y0A1BIHdO%{g8v7 -kk~5`>Yh--`lk`qll;d`-=K_A3_p~?F7aV#)Z)n|MA%Ebl`v6A_GwHumecF04h7KZ?Zo9Tb4hbWgp(Z|J#SFtH -&R%X8)4%mY-2+t`AkdX&XUrs%^4kNchmEXxc)T+vV4NyYI4RGhHV5_FTfQ2XO=vzIZ^Ndsy?n@k-1kT -s_`$E7WlLKXSmbHXu&saIM_b;0m*33k;5a0s>0#IG^jf90oy%VN@^ST<`QQV2C=C|Bpb_DB}~U$PltYxFvzmyPynfrC6j%(DMzvVuHVP1bqC}LfO1? -Qkz7?msz9EB${Pi%Wzx-GLtu=+>vDY~Bmm${fM8|Sj0|FC77|CE>s8DQ9suT41MQ|}Ei#%K^`La1=t!kXGkpI$qdv>x%OPE`RmC&GdUFa74b!hj=P -LxfuT*f#XI_qo}E>Bs3uoJNTEDHTXE>U91aFLYqLq=tNFt(q_Ca}sXyF8W{V@u)_-{WLvDL0qJDb)EUZljrL4c)a!4PrA^hF;6|j= -BF62x6k6W5a2z=rn_TJQTH{%iR^UH9$RjH*aZ^(RrLtX`i@whhp)*CU(K~=M5!PDkB_&iL$yMeCVwDo -34Mwk9j`Zg@=?=>Jb+rZ+=B -|R#gWds9LEln>8_8!1-5A?s0J0)z(ARalDr@K(2 -z~JKfM_Z|`Ot=&c3*ESXgB4pNLE5L$r@Gxm{Fd52@G`I_6SVYR6PaaHQuzF-0N|*5rFMSx{S<^Ctm1{ -Cc6S{OCGxMrjy?SF#chI0|5{xfS{J!5!PqeuTenX?(4+XYFvQwOF@Oj24*pwTTjkR+HInRQE&B=3|U0 -i4nu{*>D#(Sx;*nnVbu(7rJWJCJ+kY$B`Qpd!~^qiM~Z66QcG=I;x8nY^1jgWC-SU9A$qp12P0t?o-isUIjB|c>#+bGW -=i~{=cvdSN{ACsj>wH#fkNf)lTx6*LKddWpk74g2_^j(pkMVpkO5K{(7K`r?@ENuBv@m1#8Uta;%M2A -1JcnfMV0L`^@J~nzgw1)41QZmPsk1JdW53-Gw=-*DC!~O0ZwQP=4WhrwI(s45wIqK^GQ98m9SRF{EQX -U$e@123MmYk-g2>8HYQoWEin$@_=`U&v%t(wNQfSYnY|jMFhbQ+8x3aE!!z1({vzI6>AIJ`b#Za=B{5 -KtXPStX${VYcy}G-p);Gzny1p5a0hZRNEmNss=+N*8&7y54CEE*7#&n+G&R~GRiN1gPRc?3ZUR#>9LN -qQC?t6QU#l!+Nvt^Qgo}@?imsd8mTd=-?T7?a{b$KX8RK>sZp{uJgGgarwvst7?OBN7 -74X11I6K6@j<#3*fuV^I2Dquv!dh_mvXsMn9+hrlux}VR|EY`D`_A}ZNvfP8kWw(1__5q -7=1o6;BKB+`#!LR`75M{Pz`|lSvNyXCCbAx&s;!L2%A98p0pM(P}ME&~$8V=p-}=XjoWx+wPl9j`}$HKw{-WOMtyxmJOwF -YmZF;g&n^wYfIfflW=Rou(Eh3!;goQzX7Chf%1xlv`q2#xkNn4{G(*dPhRC~$tpfs(Mm_-IIBXXwJYO}ip$l`?*+vQ -A5k7mnniK0C&}X`?513>ZCk%k~dv0UYhSo(0Vr47~K`#qx@HMNfK2@NrDn9)L55$g%1w57wMF8&Dl8Di!Oq6vSj+lM}st{UIJXB^FHeTgl)LHfo+5Zsc%J*E4S)gW9e+}TH|M?BVF6= -al4Z#U3&AjsK)f@dAsh*jhHK{R)yZCx7ak~WIZ7iNq9gJPP`gN;B1aEW;<+q^LZKA3%+CC#9VHZ -l)VuNkV_;%UIGZf6jqkZ%2!)hH;>n}sFtDkxW&YN7LaW)=TW@q&fnAB4wn@iiKd_ybIi#d+z&lF(Kz| -zfnaaR0%lfW73}l2JSxl)9b2bETs_hn76Ld^A2SaYC0rHz`P%30aY?xSJ3DzzL(F5z0W8NcxzP(GfG! -Y0Bl-_~7-nJ$3)xJlTxNDIsa<64?6PWQ1=plCrdWi8RBNPQ?80LOWnlXhOZT4n{fd`Y^v}<8Nm9$-t) -y{6y){q@q1v5P1b&--1BVdmIDXg`W=qb@MBt4ER$)a@H|LCr?+kT5yc+1D+_>}w(2F>L>r~E3@MdiM8 -l{ndD-Qa&)9^)8Nyx)k`{yIy)AJ9T!FCfdfNVVq%jNIUV>3VdJjt9NnwM;TWcoa{|jqox731mqMF4_2 -yVA4e8xStCYQ(8D#b>$lM7m){Yqtl3mvrSq5kA@NU}rQKGgjK!esWUQE%#3tBMvvoKk1Cskih -fpYYaB=|kYG{)?A$^np6uWC6dw{+Le8B)k?aF4Aiueb-1{F`F%(cnm?^Y(>@d?EM9LR(t)T{FjRWo(!7k$LQnulb)A4U%eAM&yeNjB`EDAaEDyY4W{lkb3&Vy|UTAqyg`aF#WPXt{v -M(}k-mEN3mDU);s4w0ML^I33`v3Y5-wA2;|rOBVzH2}oCUi`3l(kr0uHn>fTbeC~snN@ffV$!g1zMX+ -w~fD#_FRQgpcz4_Ja|Mm6D%jEUIdONJ?N{r}hB()9^MxrIIi0C(7-h$Q$y- -ZeoWA4U#qlO5PK=naokp4oho^h^?+eS;DEcSJD~MM#kCTgx1f1EMo>0+~p0z=v5JL -`Czz%YcLBTz9tRzAwyw8rZJ6H9J==qjexdPFX@?A*Eg+u*Ymz$g7YDfhJTndl8z(J$s4ML(4 -y8(-j9s3R@RRjOr7p{L-X=?E*L*%0#p$ApLW|!^G#o&4XEA4|(7-P2l|2#76h -;>wffbFVn^0MhI{hXRw|r;LpTr`rb!(cw&ZgBebWj!DfjCB7Gjq5_$or>aCXCifbWs}+hy6;EEN#n14 -@#Pz?4{pBo3dE_NG`rv+3$t@bpD$C0zWQ&yZ&_Q~k#xCP4^op85y<9~ -x%kg@Gs8L(3vsE+5>aTXY08B-WbQOklG@ROp7&6%vs$A)!Zbr=%w9l}yx9KFhRr -4^8mV!gw-#`lOb;*(hKNuvTJU#!t;M_{jf3g$#bhNMNqeGGsv7K^4`VC?8(y}iD0VQCi0y)YG%_o@1p -UU{VG2Q+S@{kxO#04NeKc=8eD6Qv)w?P%=bBRXM2yMrVdxNns26|K}GXi5#OW!YE(;Ls4-sP|~<^bHt -&3lp~sT=hpZRW$=NZ1!a0Qg~AorQUktTorzjWTBR>W+@G$8_apd%!V8(7i_&34n?;R)P!KTe(TTDQ9$kl|t@hV?S-h?JXcu2IqT5+h}g`(a({rE}((mj{J1qO -Y^}7xIwu#TCfI@^{3X*>-fsXn78AS7p7z*5Mo5Lmjf!3r3{(!50n1?*BJHVlS@ZQ8sNCe?L75Vfm}m) -};wM#L;j(F_xQhEDLG;367bE3&jHsExz%ZTC_$liqqPpC*^3M)UV)?6OmFWY{1Qa4fITPi|h3*9g?95 -c{E^z+9y1<)=zd;Oh}3*u-SoV*{Td(gOA#qEaQkmrR%)V1e*vrK?pb -HP^g|{k!I8rQ{k-lAz)MV$rv9f-4|F -RK|S7!fjzsBPA0NJEfFUSJofD#gLWt2WbcGXZpN`2E}uNP21$LtLT}-d?$DJ_V7;Go`?`Me -`_G=|@$pzQiXFm%Pn|dB247J82M2m$t+F}LNiKE{IpMP=JI4`t@U`mOCK_C*9t_bwx^(y`?5`!MI9b8 -2L|aB>9ms4yXN>y@vfy*pro!i8@*dek1kWC(2q{3F#wnqyC7NTb%QRY$Zhq-OYLOA=D7gY -5OA9wx>12oZJcan(x>z1~eDrq9{y^+}>Ta{>c8VwyVXhsPTKwJ~9s)5^PZpReeA;9_y^hLb{dCTMW1( ->maNn#^V~lJRY#}p_MWL$r_J-j%B|hOCtsh0ydLNWZ!q~V8@loWyU}qJ%6+0Psy_*3>u`2KB9!-^HCg -@b-7z@%H-2!@(Fm#9#fu}(=W5+akQd|%&$j0B!d=4zdCHDenPQPdEb_SWg(THEL)W2SToUfv_}*eKCBffbx#~Y#T1AI(2qw0pI?$wgXBXsHfbQ9 -FdK)7#3sOj_C$W7uB&CeS@vv85K&(Xoer@}%a&JSUZoU@kLu8^S9F>F^+)Y^I -&(RzGtF1{=qtSl2#^a3b<#X%h-dF8D)+93}7CgQZ(Hy@k!uDO`<3QV|5U5b1p5<_RRiG4icw22-n^8^ -0gp+dE$IGaJriLm2U7LPN!m_APR)>7w1=E=pX+~5hh%j8jiF?saFUO;Z~Xn4x)njB}8t-wE`B99hBZ= -5s5=mrH@4oahHi?mF>$s{m`bI5OQC!k_sz^Lj@<6*!x;cF-)Sg>*~6Kte2EE6^q@d| -z3=gi1Bi%B$_}hHyXir!P^>o~&4S_v0_7G3R8mmLNYL{M1FFyFlYImK8Y%L}AMO7B_@g?$VF7+3v>y(<_j11D$kTFSgYufVR+3=&XgFD<(atjG$pa>HoHhPjWJbmT3sGNwOY9D$Em*yBn -3n$5<2+O9ID5WIw&kNQ9YU7AEmbbe-%C4TNX?v8c8(!Kd%xWZfu|xbz6E)>z>3s=0j_TWl^gB9ak$%G -*GC!!lwW#(+eQ54Tf5J#Z>2k=f1onc=7Sfi)6J?R212HXaR1l1@xVv!(#C^wGe~YOb-$Mv6y6gdG)UeT7k$`EM~@zu}Y+ -OU7Cqs& -La>-Myn?|ACGw3-53?vX;P>3&Uwh6H1MZ-|9kOpO$*mWmNVGcCNud|eSCCjO%@%TnS>Sqn@&CJG%?{u)MG7x#3TB~!$$DwIL=`5dldKm$#jr=8Wb -!R5wcDhbIe$-&e{?N(M_1SGuD3uqfeDi9w#YBnEr}d>t9#R32)Mv?J_@i=G6TdWC4pvSKr%`RT<~EGU -y}&31UVzC(1_}F`KBo_DBI>T~)9ikA>{6Wx}_H=+nFU98C<|UZ`{}#2-la*-v#?b(30+K%;{?UD=NnQ -WTF3%gXtM5{!&BG!zu0x%!U@jZ^%}UAWlNYQ*;VyH9Ye)0ukRK<^Rda8RaRuRMona?Z7JeFMs-;pY^8 -qM|aTmwl))8y-yDZ5bTsta6cCvEhrS`Dg2A&lcU`dHxv!Ol}A7bMJgVkGY3R7)_NmM47c&dtb}tAt7g87XhYX?cpiSqkiajYdwvdhG&{%pBO|VOu-$#&Q^Cy(3#v8kjLHCjx@q-i_UEJb;HCE#E)eX -68%ph%Kh$+BROUeY-!+1~f^Ko2$PkfXi@d2-_2xL!~svgDQA=%!_b2L24zOzWaM6=oDqz7i4?#e?-W@0fpj`kmx>W6P_@uXE*iWNaFpo1?t -hodPHZhCoQL5Y02rV4tSs%!PfzS(DrI^csB56Lst%Sf8b3Y)kVxHrdSlZN)~4&g`1vit2oGdb+yBQyy4;!QSF%mp(0gNR7vcBdD=F!t7lBtVn2UR-i-YJ(9 -Mey29T^u>YsK67bgR#F1;C=gb0)=#-R7}W6E?WuAjZ%ETM%@|kaKHdM85n^=J%sqj#Ma5vTAnSP(HrwZdP -FgMYfvJb&K$;oUQP*HcUZG-ZT+EP6wf0T4C_>9saYze@dTcR$Hs*FxyomU^rMW_aYK}2w5FKcM(8d=v -Mjy-S6xGOy@@i64KCCM=d)APxNUO>C*I@1s*a?;0JPgn!ew2DEXUsjSB!lFSob^#()qXg2>R5J -?sO!1FLj%1#$heGSpLr|RwJ4hc7Os#+w_cesCXNk2QC=1cL0+qnWa0oQ^N`WZ{BaCauAEN=x{(3!(@) -P{zDWM~$K=^(sHi<{|HmKxFjYUl>nn7C^%(=3J8{7F-0IVQ_IZU#{lJ_JTpqc`cUrhsDTty4tb>Q~<( -od8fX}h@Gxq}=TR@&Qt8jenW0IBF&oU;LXbxE%D^J`3R60 -WbIi>jX?kmZo! -u4-w*>yy;Qpk!ubi8?qtfs`J^;DYc$naxZ>~(**BsUDMTL+KlWBu`azzVa^~JM+WC%c1}S|#qOF)90|!6%2%tZuEs;A?+ -o`(Qn*Or-F5A^DA$0G$sHNDGqv`Wi6^7b9wsm8U>@xyI$hgtRYWM|Ig)-+W$o*b!fnt&IJOoL3b<2w4 -lr*BlP!D=C^DGD<4zVxV$K$nZ)GFu_{K)w@9Wk`^c`lf#|J0SO4n;;bQY%xLEny0XN+l}B@UERqFe*f -Z|tG8W+c_1h9L9J517Q=O4I7%CMM9KrI3H4DKbmlGQf7;TUw@v?QsZRAqgUa=CKSvUKf{qWZ8X-tRc+ -&qrp%{J}dWPB)=?2;zSb6@`qwdpm0FXb$pbB=LNuo`Q7Bil>N2N$88;GqW2+%@d=98#d%`yH+Q3P2DU -J@hfZupiRsc~5z5*>SqzkcywC8OX7ozRzeADjL^3J$oXpn~owmmT#hn%z&&%JVo-^|J=%PIIrTlNaph -rRD$u7LbjNPH+9Cs*`4BAKLX<2Pd=bQA-=OX*N!J~PZ -4gaHvK5Q%U`UD%a$@d%LrHTD($p_v3=G;pLitS5)8fWhGI|N8dMr~mM+qDutpWVUOIziQ+X~dsbGj@0h$ -G7+zMsf@Z$A#|>n_io9db_OGvXu*=TT394Q@UW-aWCL9%M35POho6hom2xlpEI5Mh?6WcDfbVVi?sJc ->eFrDUGT8C@buDV=sg&%GsH`I9VaN(zRwI)lj^~yBR$nkarbSnYgLWLHO`fuTz+(bKiuOSe-M5Z2heA -(Hr_IGQ7}vU%bh}YUdKKC*N5ezD&>+LkXzqOo#a*L*0edZ#U({zY36XDDZUX*SdyMi8(aScGSa&V^~z1k@?KF2)R-1Vd-7)B%2D#PM>i%i`rW`hpC^w{q;3OBM4L#S7ij@&q`Xc}z6NQpyc -aC4ldAQ;3KiV$AX8wR00x?@ENG!YD8nmfR6y4(G*W@Qqsjy)E0Y_yd5 -N-y3uMJHh%kd{i>BTpqg!Yd~KJ>V$Tg>@R){!o+KTx2Vezv5GyG(b(Y*9 -dg&LPh%_Wr93&Ec**@X+r>dTVYMQg^D-)nqWGu*c**WpSN`;Q>=2&mlXd+Jsbo%Y(|7WidF -;69hxFojeRi`(CTj)oGem`hmUt&dzm`vv`$U*9;OXhbzAHQWFHzjxB1?Va%H2p)^<7*lk3Dlp?ql$t@ -Bwz4B%9=9Hq=@=O>dP}&7;D&jy)IDt-t@!Jno4xD)ZylD#N5+B<&tlr5xaNKEqVNK~b?f439;%DrOC@ -h&Q{!2N3E*LfPLMCiEvAXf0>wD+yFY%=ZK1oVgLG1n -%-tEEIOV3EbCPl0XI+E1i|Ad)=3?iGOOqpaY^iEi=uIqiV9pmgo5%r*-k1=80|+7EwL-68hgr~_qKE{ -`aBsPVZrYvTLL;b!w5vTZCe$G!>@H&oinKjo>6!=JQc5~sIkg}F}jRS`Z##pFU?s{>;Sq_K;;3A75&XHTc7@wRI90s*)^n7 -w~`<)iiVoViFhIV>L~%#L5`Zn|aE_?PO(WTsR!Oyf1~O3Xfe3poapk&Sq9-L{*A?|>)sgNZ;X2Ng5p7 -^?-4$Z|bawz&w+)x3Q%G+@oV3|{R(v}x~V)W*vh$IwXM8H-iayUMnDny`4>Cd -kzY$F -0_XC@5SGgBCYToIE-?O^|`E6ki=DK6rZeydPpGo$RYyuJouKSsn`;3h!`$33;dhuIjgYvkuVMEkKUk+}DB>Xb_ASCRYjjN`k|_pFa2Kvi=q_wcyRzKZW2Q?>LFfI|O!+GGr@$k_`zEtM!n8b~ux4T~MF)r -ud^JPujJsh^);m%XQ8Sk1k}O$l#O*wJnm-2=7}rvUQkAs*2Sq_Cl=4A8y*1h7!>J7@tu^0UkoD=`D5- -V!=SytjKNlZ1`R```LQavtb1RjMI5zY3P>8=+pc?W*kGugagU{=0pb>r#3r3Vx|!D8wnv^6m>mT9WI -DD*4b0Q8*```Xh=uX%`$E%;&bdW#8yFf5Dnjo1v4XfoHDOG^e~09jb*Eie9#5sX%z@$2y{`|$q#-#%P -jJ^pY-$feW!B667tgV#mV7P@CU7jv9mt8dTwo2y6ofG|I@YR`?Ua7u;&^PW}SaYZ;)of}Vo?XA`qFU% -G7`hszl*+Cfr+M+{fiP#ku_3K>bJ?Mrh}At>b -0-E$<-_1yv~tx_LSU3pWL9V+Lw07m7;1=GDhU~pp!VSE?Kjek-tZ+DB%j&?Suwhcg2Ixwgv8jj&|J84 -H_4iD99P?ERVrB<8*=3Kd(R5Mn(sUA|xu+G~Yz!U^u4uj!plg;S!^1JqR9FGg*PMk7ewwSKQj=>fuN? -g$9`9T&A7Fq~>AY;^xV3AoKxclB -Gf$T_@6aWAK2mm&gW=Rs=l6JEp005qb000sI003}la4&LYaW8UZabI&~bS`jt%{^U<0P&f05--p${L?C01ViuE&gnGGY#AJ_8WA#VD)LKEeg`Z0i|iCUc~^I`D9^f)EXPI^;X-PQ$Oy{jO~^ava3M{^P%^iV?PzO -wCFPnbo0$3WTb4n8^D|M)gGw&^a>jB^jiADiE&xM4E!0`aGn2Vih@P6CP1JUd>ZS{(>OLsy_%(;WUBo}YG7T0DJ+U*5dInaEB&nDe>Z% -Kne^Y`R)C#U41r?9H1u>$*@%I7n5~_UZ4i-?RHSj7PZvW>Pg(zgkU|tT*t3*-ZSpTs_OPf^j}R2G5a! -!1I6pYXF2zLm-_5KGis|hUPCt4V)m;;o~sMN9gt_Ho!vQ4?wyD?9pAtx@~&kTuQHGj+|{DL&m2*6c>n -+JvOi~;P*Mt)PYpdT7j^7AJDybIELrT;&1>aIEBAw2{iHoA9xsdGTV4EIn-J>ZvRdmx_Dn1@3^OvJ9R -Ja)I{8gDYl(H&*p;uz%C!3A7?YhyIm%2@$502!^9)8f8MEo%@F8hE+)&rw^cLcF$a=F{`c+)cwj-&X` -o63dUJDQep8}7_M+cO!KdIw0oI!Ze>uQZ3pnK{2^5}$YQ5y;e}E31GbLI@(ZGSDGpQN*Q!NGb7X|)a* -X_w{;6j|F*dJ9dp5Rw(*0+FC5S06>ku)6#y1?^hFfxUKp!2$DCUEN84w-9x7CR7WHTT5%9}nBED6yH6 -v*pO3TiM92=;48=0cE1yi0;@d`f4v>LP%7ySy#ZR^zf$&c*?OiOT^W}vPDsr72>%7q!DWZeCe5<+GAa -s?z`fJhLr#Y*vk{{Esgbb7G;ajfE7eTTX{PXRac_VOTYwWnnovMN0H{!HgVdPZ;@Jq9tf4OviK|x#K2)eswTkv`XV+0YwXoBB*%xKe7 -fW;OGC~2F!VjoQ>1BmN!v7UGi~nGNiULqW+@SRA97r(yH_S~6-WFC$KO?fIl`L_Fmh%*qk^*$6_xnRl -*q0?~KLme?Bh7|^kyWe!+bcT*f8@_eUjPh&9?HiTj$W@3DtMbJ;)%Tn2v-=gBD&UD32fknbv@;tW{+Ky&zikf=TumS&tt-o01fewM%*HIsD=2YacdjHv9O?$1Bgtt@z^o_g{W~zw7&hx -_|q23%h@OLi6qI;e06G0)KxC>N5!Xx1W6S$**j`1FYY{1LNr@;(_S?A-O0xusrQqG-IK5DCSV`0N32s -?Gtn$u_#MVt;7Ck5v&YroK7qcKCh5u)UyAvpUiw!kEgi=c&o6vzZqr;G+8qSHUvdDGm_VT77_#kyK?( -dI0H$ulG`nO-?Y=&Xj9lc+4U<;I{Tsrn+A++Ht?nnp&JYMXOeo7ZXG^#<*Gg6bJWytWq-*Qnu7uahB$ -Lc)vfstOswrU^J8Ayh6*_JXD~I!ItqTF{r~CeKw$WKYZwpI1Q}}n(o9?S1zVgCdd_br+^xCGQb-S+VPfVwDX0u8XUXDJV;=ds)_y7dn9Z?b(naKNUy$udM -hcIiSd-NLjBcNgo85-l99Wd-&{IVWk1k4jlfvLN<*5cjY`A^|;Eo)5m8&((pxEDppMz)CLassQ5vIAXnW-4+qR6}2Qc(Letc;Gw~ -u|S*~7B`(1r%~?*RG6r}JP%y2}R>0@4?JF^ -_l%-0#!4O`vurfy563KdK`NY;*dSq;RcVu&2U#;2#>rIM>vHlqVLy_Ixs{cC&JR4t={m={u3PY3;iwR ->(nOPc70O5*&_|Ee>l)EXBcwuPhVO@u=wfZzweZGzx8T8Vi~`Huv -@BcV+3(exXF+`Mp7Q8^bKprGwe9dWL_Y6bc>a>frc-jAu93V4ZvL$WrGO9DtH~R7q4-vajD2$;iZ)4 -qnKK>#vkU$mH1NMSs=y+9^r8bhSU!$V=!6IV3CG6N;6)fG`LP4LvN(F?Ldb4%=*GhZ-jUj1=mSD(~aTMHKD9(5vA?J$t1VTcJv^f<~wz{R;eb_sLB`P}IJ!u8+Gqxp{p{r$zz6lMW#IimYGFo|2(SU -@vH#Rhrz~j;3sz4NR6b$`2LJ=2Yrnh^cS$HbD>Lz^iDO5)tF`*AqwOXehPrO704KY7 -dT*c!Cp)-`tgqQ7n{#@Qq;!R8AeW3u?=GEpkffG}qFYQT668<-I&?Y4>1|7a7QoV`E4Gxq;`Yp%O(rq -GnQr>*xctjD@Z%8p>AQ1GX!1u;Hi1cy(9sw}^t$>)Ovs!7CH^4KM3xu9A{N7wki#~eLa#qQ0IVme)@= -w{Y=g!&3R>uMxxE%x4?a`YPp=*o|5XRMt~M2B-T1(~AAjyapM2IHb#%>=*S30*4eFF)YE%T!Oz+g3i1 -gvEo5i{&en8*!G(>kv+;^5_UT(X1Ikzaa-T6f=BF+6Hj`ipZ#spj)t2v>}FP<=rPMVmtAPINsH$r^A+ -%D&01scs#EPj(TdK}eX{M$p~H0LI393LND5IFZF%LHu_G*7f%k|z{D@hR0RXN -na!FxsYx{xcBg81@`RGD3mwvEZ4_W@-Ft{l1h>N6_xSm3`03y|KIc7RA8LcoJ^3=6MPOxeb0t$Mb|bx -XX`-v!u6VU?}b|F>|>UpB7EqV4MKYk2b(HUt5q>J)&8@fte=KMs8!uT=pV%Y#Q~r_Ds)BXsCmL?F*(SdF$z-CvDpbM@XSD`^SeTgaM;jitrBdn*0 -NV-C8FHC#2wsK9=7hYw_cMkGe8Lw_Fg4tn}qK+-{W{W*u}T*h*(vwyDhVQNvEIZ!TZn^A&R&VtBI8jT -+9Ef42Q0nH#TmdIQVJn+M{jA`_9q(!!hr0|lCo=S8`XvvA)!B1L!(u{K5RTOueP6p|U`6M>TCrWd@VG -~GDlZ(cCaYP-mZ8lZ6k8)40VgwP=FDce|&7o?Jk^gcz -%*~sX`z4et6cyT6{c7kNB+c6V`jNC<`}fz}0g;C%V<~j%4JM7-(*g2tNTNcP{}y#u%JJZ=nzU>6mUCe -J_<@Rk!=~0-gu3; -&mgOQ}nQ!vGDP}|aBSJs)93cdfwH37jc417*g@z5Ys$o~K)C`5+x5ZkX>eL>C*mzf#QJjEgy22DW6nV -8C_xq>TC77O&a8Mi22;6Rrs9_GbDkGRkyGTv3ytby$^c -T_2OS}U#^u|%CVtImG&OMa^ex)yLEr0R2qH$i00{J%=Nf*sgo#kxTd%iU$%;fa_9QThii<3X-L$F -*<3r&yeS@6p-9jHHK#W@Tie4C69FhC74#M~3dmoNfH-O5Ndg5(lQ&Ptw1o43=A;`@e<~D(ohW1``t!k -wKY>tOV>%Jjc92dt!xBm6L2b9BhtAwjynI<0 -5Y45-IR;`qqQEk#PP%-oyIioy_;bY|BNpS#Zeu#@sF#A`Kp_0r74!1m6f%&(&c_KT2MO75O52TMZkTwdt>?-{;*p8kSviZ*T9Z?fE^_f8AV3Rv -fEI+#UQ*87-cjIht-VR+vW>eknmFaTQo^8jWd-x2ob*l(mfO|2P%6W_^lHktb`8LsWWQdDcg^5sWd}4 -#Q=S0E=J^r=UbpE=eXmNak6p*949*Qh$sSw_&CFSaVQU8wL&zU_8r}&$c3R+Ir@DHU!v#ak$p;IBwwl -NF^C!9%gjXZ^(wM^ZfwQRa(sH#q+sjVI%>#U%_lnnmO3}b}dd#vwInW^ouP9269VFXYIF4?anyrsZfTnDX{;Qm&M@gi+n^39m9OgL8vA|Ln>zR1>lLK -H_LKAIxp?@QA0@y2z@7|OEE+)4zb{}%!+P7V`~#Is$S^}wZNu|Gl5Gu0Xdd2O=i}zx)5Fm%LEOQDuVg -wt3PxL%eBi#U9=&G4^W@>&1#W7Qgb3d%ZLObKT9x5sz5~^{4E?r;od=mGfNdMR7;%aha*}^DT){%kO} -~t3QxJ_w4@-En(a`Y*J%s_sQa*0x;fpIAab%fYWbeu?an%524+i{K3X>c6)=u3nh#$s3ZH7a^D&-A-K -`$fTHk2gZ)&WpfaehM(IO~_$;>jzy(M0+G -L~7=Zk>nVT9o3ZNCjDAC`oL$?DmRMZf4jiUx_;TKy%QV?4WtjT5X5UJpd%IpAJH@<*Zo{p+>Hz -j`j}dz|AvAMNd-JHez>xRyGkAxsQxGGY!|+TWNGN5<`zL -$$ArURh;Omotr%9b;IiYn*G98XoX$?S9rPVD8;@hO9CO8drZBe@*eTWbdKCQz+C@wbo#SWNyc#Q=g-t -SsG|c61#!JH%W}C5il5Q0iI$8M5weRKcdgE8Xy!|j=)+{Z4wyoN#dM3sSZen=7u3<*W8&L#Q11qL^-BDkkc5m>%70d@t04(6@UKbkI<3BsW=Gs)djPcN&Wd6?UN53Wf*W;0>D>Dc^#-$zY;@ -DOHGp*AVU>*PA@|gqBhu9Chuy1#4;X3+8JP`E-pf#D)?Z8FW`V~jI>mKj;!X`1^0$JS%G$&SnJaBCytBFuP2S_~7$SW{F=qwPSj -=-z9q#6}u=oDQI?+#t1jGF)}jd2YC$_yC`p7IMNMuMbceSt3S-@bi|41y3uY8>E=3oxiw$2kq*kAUKP -pD@+Ej-ujHcnk7#W72Zpd61wbx8#pY2mCy(n~*UJEwP%l>>o50IV|l8u4nbTaVGeOrad)_ZQGV+rbuX -}Qd}w!lgy4_U}{(GIgn2Ec{jxx6$X8|iiH+c^#hL5K1)Ao)Q5yycI;Wx)j3E;coZ)GA2Lm|Q)*8pS!# -$j;Wy5pHhF95UACL&y`Nz^iv@EwStGd55_xCx!l|fF#aS5z+uC9Zjen&}R-kqdI!JoCNR_Ww;3vOIEbx03I -(Q}DF-o`tg){*h6dKb`kpUrFIi56e8`N6QeO^f^_{sbGgOzx+ytZ{Djxr^4`kfR+@2f2C)WXT8?- -;v91$M2?Ie_?!Z`Ov32^#w0igY$jblHl5r>Ox=FyIc|&hQ$@YurYL0#7~s+c -(Dh5eqvr^qU$SjBQFeo?Ey>WNT#}tD#dMsb6NH6*vC@$3Kf50nYw^PKZKFK=s}mj$ -MW_7N`COgm5H!<*!sq(L>`iu(+d63tfI6;Qiiy4%J_%w8=;fgoSy6QzbTh{Unc9<3&kbrMI@3<%I -$xE8r>hYCoQ#y4_k%@C;Jn4(V{$cF+yfg}w}SXQ$~}Mw_Zf6bt&1_<62GQ~U%~ -RpTTCWjdP9Rjf9Tq&qBcB3awg@dsVk3(@^o`2DYw5#fr9nVU&vFd7>)<~4t>LaK{5@$Vyez7na-X%VL -c*+{TyVs;rKz9bwTQlLWCWL`Va?+^H@4)52oLU!47PW%j2;ZsSH)?xiBQ$fEsWU*-4h2fwuC9D;&<4d -xAVh@_GH)T;3c(6UH)abg -r|#Oba-Xby_lYAz<0F|Exp*asDVFvvaRTx^jQl^3+>^4wN6FEtS}Jsa1x7Wu|BWy?M+x_6y$UFRQ2>m -Q>x!SFf)FW%`;};>3iTd&mdc)=7Vz;yfW{Cq&1nUFZlM(0>gVnvz0srJ+cF)GWVlxw8U1J;l(H&(9@W -r3bAuH2gCv9SsipKgGz17qN^)bRY0GsBwgJdu&wvVfZ=hxvK;hiDA<7<*Et(V9q7_2k2PHpOcy!|UOZ -`N)CHZZ1(gCfRyQfp%N+()Aev`%ZUBG7Yo5I_e$f0eq2ZfVL)%$Ap2JoA`X#Pa@QRXAb~vYhR78wM9* -(VhWQTI^x+=q5zaY_kf0)kZ;umO)!T)~oe8Rsn1)#J-hP=76FI(1mRG@`U*~_*oUQB@j{eB)wYYO8JN -ZuyuK1&Ygy>piL$KgkQ6_VkH{1vKbD<_VIsjsa$sQ?+zg7a39P8ZO3`R_21$3V-x{?dg8H@vc-$*xZ? --)SAVlP=#C$*YH)v1KzYT^@eH?UJ9*Yk~(C(3Bf6Qz;F@Z1FhRfU*nGz{`6fgEN+G=%2lOJ3f&4pY=h -#5}7PHa*=@HMGBw{>l((WGGW?(2gd@-v<}&^7P~oj@-)BsSGi;ph+paXjArw={mitz%6G|5kyD89mGw -YfUf=)u+7dPWMo}@Y=HX}jm1)kKzW)bMO9KQH0000805+CpNd-p4a8L*U0Pz|C02lxO0B~t=FLGsZFL -GsZUv+M2ZgX^DY-}!Yd97L7ZsfQTeebUzJTN3-WjU9Zg##l9_5v&r%r0g&nFr56W=m8z*Al6al-r#k| -DGyRlqiWko>@dMZi!^ESas@D72PP=k+N(CGbmP;gm=AEhS0s1?O<5x-gmq4dn*qIE)M=xYX8fyu5WnD -cDn{8_Pj4D+4a0d?^AAc -%HBTQ5Z>O26Gm61JR6pf}>#~&&wi^}11{x)o3czbYnzD+|!HbNni9?yW2f;0Hj;`5FHdaI66B~8*R0q@cU5jz53kfJI3Ix47c@UITIa-2-olZkwvH%t-*OX -*z~>DrJz>?J3l}3q6oUwTM$7=H4cJ`)u%tQ@eePN=o&e84OIYkJ;}4iolo$a$FN@T%7!D+aUpT}MRMmk;DZMJ#1&f#bVOJvau_X -3^bjO+&)xH>X@41r)lV8_xk889YBt_B_xL?7je3_YQZ0ftCMQ?mNTPASZbNf3*bViXk319>&z7R>MXM -u(?cz=OSv2zmJ9CvtyJlA=E+go=8>u93swAvR3wL@fbZLU4P~nq!HI=LGPj&Vfn&2>5>GZaxO_Fy4Kt>e*A3J5O9PYt=HxA^@Qfij8kAB}`xcmjl{<;T -55%A7>xq-DV1jwj&T%-=`ADrjfn8PcL1DC60sk!s{*@Gh=*!38w)#Lo9E6I)Oq7N|I<4;v8Rle*{tYB -Ku75xls&MfT#niwB1vV6%C0p)}y7Pp5EWeU{l63%^9A;gKde_FVJ%KV=c<&chKl8goi8DaeR$Onr>!l -yTjHi?`7Axj0|)O06JSPRvrY5nIe!xKuf6K?fsCKw!gFcW<-i66^2mX?9n$6$@c1I(WNz@co%74K|)u1u4xsVy<)36>g_sb|Gs;hCJtt9wz -kBE1~nYp#U{Z59K{3lH{B$hPT{{Pe>mTKgZ0t46n$}&?2PCFG^MnO2YJfE3&_mBqu3ol#@gxS++h@m< -6^9gqtUC0hKmj&~2c+D=_gTm4nf|W|rMS7Jtd^lC7c(!rYjBR3gGMubY84ffcL+E~MF@fcITq(LL4pJ -Jmx2n%^fkQ(Pq5uz2-x=f6*&1G{;}2A}b|V5f2{Nu6;VUB&?r*r#Uu;gBZpyqjzV0$a`{H4wr`xKTNq -PNtxJ&%~PA!)I(n?TmcV{KC-#@5ArnnYIHgz2J15F;eta+-HdppXB{eVQtY2Eu{O&sSp{Kb1pdd>#4^ --VP27KyQkHYMfNI-j6=32sg6_}fR_O@&s+`~^z10a(f;gH(1YY*+p@NZG^F1)%YtTYOY~khZqteCfNK10*u4I}Jx -i8sVHO;BS`pU$ZSgq8WMZ)Cg7;p@Z&v(CEsg(MXo39E`*A; -$JmB$rrJTjN&JcnZC#fTM|OracjW43-TT`2;L>Bd(OC74Z^+Mre1M8dp7i$q18ZKOM58UEIhj~o1_A% -ZMz;pv9+@r(`Sl3k!rKF>FfG{#@dq?VYCvyYDGpHecejBkhnJw@(A=;as3V@}fsHps$RMbVz4g+l3+{ -2EstyEseL}p9ce(Z+BE(M6S~%l8{Nd9QKi-)3FJvtw4VES!KVs!|n`BXUhMjt>DG8i)A;BZDEC6VW=^ -0S-zP2ouJ$8zI5HwT!1e!}&ySkWo1M{uJ?R5-EKB|u*aWeYzpP!tW$Dw)PPsPiUNk>FBn8#^tU(*Ozt -N?8PhHk;0c|Ym^G>YREH1@~%L1$}+*!{2`w+laW6%tMjCVE(&C+Z$8y~-=>|)7lt5{Eijx>&m(*;a`7f%X;&AHLp{Pmh=YBE9}|Bkv -P@vJ))F1~sKwz;;KyY^xnI$W%w!XWsa1&Vi`^qs2P7Pj;C8i8D4*w@6{m)Uk>u+60k-W%-x2T)4`1QY --O00;m!mS#!dTC~0sApig~Y5)Kg0001RX>c!fbZKmJFJE72ZfSI1UoLQY?LBLA+cvh}^((OU%_Wt{^r -P)|$F15;6Q@^m(vP{NxTI7{(n8@E6IZ{93BCq9bouzaAM3!Y<=pU*1mKAZ7$T182*74<3w3>MhEsi1MaLSUs_Hd#~>fb(` -J0wbbIRCN-npZaZSa4#zTZBta~?Ix;M`a!j7>NGRI>Zmr~oB5_JVp*Ba)y{maqjHgEW`N(*4gF+NZqi -L4Z)3S}s5#34dX`1_@S-e>vTYm}Yrsjgl!gV8VO@k(T~34qg!2Sw0pDSouma)cdRevIk>bu!{z$;5N@ -0yRQG6XOWi>%!mLWcyh_cCZAXM1o2|T+=%esLj%Wo#4{T}8;xXg-qluZcQwnT7=%q09-N98s2#5qq!; -}(f=S -#HNJ<|DT-3(?V>2xQ4K5fJ3&pWFjX8g2WIF)g9g=24x=uqJ+4JaLYmiArzwpXW->}})d`gFR#D|Xa=P -S*O$NBAX*n-TiDFnGl%=f0T9)fHk1~ThJ_OlWcDgk=$O$nGHfnMZBI_4&s`(isp<4;;0E(jxsx_It9cRJ!E)C#~zH?pii#JrEA(Fpa7E -P)Fwr4vB&m-O5ZL!?d^jz^>Kem=7XfF8aVz^sG$?`McbP|tt&#-ks99&B{`JGaq)GxZl5TM5bSC*kbVhn2oC%IcL&OL0J-{xU5ePNQr;2sYPSI!@i;1%U^N~;zTV=uF^ -_KMjUUjnO__s-p#}&CA&;s3zUdGg#L6At?+&k~(u%o{dP!k*aBxsm0X_)U(Y1uPmDr}R8ci)OE`FR{U -IoykKLO~Z+cP*D)z_DVCfW$ub^)Uz1M+;tU#9E=1w6#Ltvz^73Tdql{UIU(K#lu6sz|Z=;AjLe>zfZJ -AID%6K~h67xoIAT!Dfe2dK7!(&Um~Q2ahUoMAq>@JVJ*B91$5eHS&W!Isa)5i!%o=kR~}1NObje242A -}eYeKgMBoQm19>on2lG1=Z_(Nc2?}xt{$92al>{@CkVrHeQ5WJf^{5*ColLV|)x`!Tbc^mpw3~i*a&j -^e+X!sbBE6N#^ox^|F;R|JF=7A9Jr2t0|{ONiH-sDP_=<1oC3XmF6M4AB*U?NtY(x!QBjTn6hoFY82wfobQOnurQnPTuSJVtr{3rR@!DTpmd-3#n%VUQ)HtB$n_tR~!VYh@4n-U@=Uqp=2qT0yxp#Ic}d#B?goY+)z31S0{_O;{y?9wQ#!Rkd8p9G(Nd{&<28A#5uc*oDJ -Nn~Pu^%jOSy#x0I3wr^v;fI?VkTV4&A5lP7kJPFe|&&A#_l;j|7fc)3IlolT9=*G2YBaG5A^Z1*DmCX -bP1+MphVzmqM{bX0@e_EK;T?}`h(>{NTl|l2`?_Z)?(~O2~JrcM{eABZd3RtZFTz}UneI|k8$!XpgYI&Nj74~X`WYbUV}aiIRw>YA2JwS -b`^sKEhlBx+~mL2F61Ai}uS2H?m_FCZ_`Y1h2>DDmobID7Tx-Br(wyRMMmvsK=!g2QC6mldW4fc0b|{ -Qt0_g1xQBUy0uuSs|2j@u>2|qaG|ZS?9T}_;_x>E;OK~-e3#;H9((lw#(iR5WD^!GBbai2w)+j5i7*E{~yyS(tO-^??yNgs&R`BVy$9YmtJwP@FN|LlxHsFcpaRG%&?(Ry6O -=G25UP8t6bj#W&&<+#d0m{`7om?@@5N_~AQ3liMFt!{9`HQLbv{z}pWkz3#&r1hEg7f&PJk;Fv`-B(= -p}^hL)ay|hNhUcTl2S0&g;!0Z#g{e*6gt!U9l0e_fsf5`031{`B@NsMiTzQi0#Tc%ZKC<|BeCE1fiP$ -7V7jVf^k8)#3D)Y_>QJPz*b{{1mzvvIoP$$&Ma52>^d0P -A#=%<8o(5XlL~tP*+O8Jw?AxZj`83b#+uLk)X|#R6;Y}^uV7Lx`+x4(+jzmJ;5}P!GxpcdPQzOg@$O$ -s%GA5I(Qaguo$nv^Dus5T&&mNy>Chh0W+-SE9hr@Fd9iw#j>v47$vW&F?Tu4B3_OYR3^==7d8(m6C3!$iT4ihojHWKXSlg-r{rp` -uyl~C*m{67*jy|7=Dq#3g&X#pQ>&#=4o=`r{a#}eZ(ci0wMuh33kECkj&Pb41?sF5)5KqlqF0oVT)k{ -4yeNlMW?IVRS&{}96;kb};xeU<|55C=pcN<=wBZYO$P1w35q9( -(N2(JgYUsmTICn*g?w+(gIk9?j4;hx@6n)6LW>D7n8!n#Wm_NV5pl14lCL89kcO{k@3&+YZ -AV9*GR7fB{*{%>$aNYIXakPc5ZBQD%Xp#8-J~u=KP)qIHf{wZj`=)Eiwwbn*MDj?B!kI{WkqNe*L1<+ -4HFfYKT;2Id-(Q(+e5{v4r4CKPi~&|r3gcq{8Gp|GXk-!2MjXK9GelVqZc1Y+=-QUX}PrJDpp{kjw~g -xTNMEZN>}4n2`hNJZ?%P6?2rP4fgjQ`)XvD?0qF=BUOobp!4(3Q^m^Pwo&wJ!X&TU1<98{Q!rtc~E~e -k$nv~HaNO#Haq~oyK|VKA@0Wd0YDvM8U|^b(j#a2TI;=MbkpVjG1Y?)ydO=*+^I?nwnd5CZeb;B2U6N -$lwRg539jW%J1;2My%yc2-P^#WVy~G!UFXF|T@|4WO6z(ID@&cDAX?xLW3PBS7r7lIo5BbXTWC>kQ`8 -z5#w%&O!hL10!cF}cVwf^5lt7%0hdeorZ_V3dm=+>>p)d9BlCo@97qmZ~y9JBX@odcS^5TeV5g!KG1@ -f5!vXj*xgJPlA;;cLRtWmR~xWF_fGN3jD|z0N`HCkZ7KV4y9%sTGaUi_r@Nu -+NPBA?;}v1T^hhf`^uX0}j#2uT52JzUR7wvf^EmMFH`L>%ifpDlBOHXmSw`2vwljP^GBfkwyX@V46Bl -I)0s%@ab--JfxRG!g$CogdUXElOE70`{CRL<33D{t4TIZu~g-_m_r^W7wGn!%dzWS-tAoO_bt^++oyK -Db60JAFL|#Ukvh^-GYrU^ZmH{e2hb@8cSZ+>T+PjGOri2b$NkFwle>dCb$KJRVsm6%IRep&Axnn?iVI -)>MN;Pi6aJzd1_f@*R%9FHR9AAH7u#0ML`MNu9iPJr1O}9>F~90nFP7IYY*XZ*Z~6636|LV-Hb@RFX+sp8%O2<6zpHOcfL1Oh@FFrMgfgR9x=XFQRK -@J`P@``p{Ry-R#KC_5&{t`;59oC^PBB2I>d&FP??#q8(O6deMe;5Lc-=(PXn%&;7743&4R$s%FZLOW+ -zIYq=sFp~+tUWad=zkc=dr*WV1-F0_bAfT+Mr1N5kXr^9p09P8u@0LZpoV~ih+U&D07)9shv)3=*{Xn -JLe>-s#DOfHLmmf|)LNE3G!^w6=f;Kro%USuMVT6`3>gcX&;AVtH5nxbc`VG2c89&C) -*iGp^ijo9z*Sko8S092VZ=kuxsn#t*y;gc`YDHs%1X3Kk*KGnxaDC(;g{#cAqq)Ph%aUCRhcMv$p)C! -G8)c8^_Yhg^8HX#Ckgzk`m=u$tSqhtBp2Mw=l6d^)Vhodo9;< -sY-PL@=kEADnqd$`+WFiSr*M^fgr5uY*dmtTDhje#n&Z=*5rZVk2@XUzj2>TEsMcqz3trVx9ORTwsSy*w>~c50BrOavi*J8K=k|Jhw6V7&tREh -i4G5zB8zXn5vPA0>_g?o)DgMW(IEK17G7IvEcTQgPb#6v?-P?d(Bysqy98i|>7W%95k1nNNz --)6-#c@PN4gMrxR`CbgwerZV{sLU(}jwYD3xDncc#^cX26OCjxrV(O@oH~f?0%P+qaM_pSf{)gwKtZi -@6Ne7v=)&@-<=^8L-JrhPd#s88CBS2uq{v-=V;3Bmn^>1py{vAx1s{Y3%8rrU3{;ss*9c}QmnWZI_TJ -Xwqc&ZoO*={27>NxYjSmtYa5lAZE+(U?^%>n%2#rLhH>p08ItPp= -+MPN5g1E*CkClDBpMIQ<1yobayQi{n1hZVLdvc0hv4zGTAnLr`FeU5d;uIID@=V=668xq*3q1fj6}Co -9ssgI$kohi>j!u!9~?@sfRW2WQ|8~4H^8HdvQ`r4_w2OY^Di)cKuKk(LdL93qI_&S-#tbr!b11L8m8| -lV1PI*#S?cdjzlJt=JH%-FgVZUdIFJ9fDhcS&ERM`22@YS@n -GirI}5^Je}6NjfcS>ipye+vQ(DMhR*OS!=X8NQx-`ROG;Ypf#6pg8K;YsN*}<=fN!8`wuT*^yTsi7cr -#9gX{11|)qw;n$#Ge#J9R9Jep>y= -|j|L4$=n -f6<3ODyn7Xv{1fnAE`VNvoio>1}n+wk@=ojoU3Z0rv4Jhm>kA&U}YY{?F?V};CxHa8H0RpzXWxqrqyF -+JKxz6Urx!4t128$nZMW4&q8rG#y|559J(+L -#@zX!Y4WT9_g9rbKdOvuAK@yX-5Y<^IjrZ!Z5$NW`V@pZTi3CY_bQKp(1=f4_AX|+FBo(2z`Zm+P-r& -w+L6>YgnOHfAFq=M7{OwzF0kP;Iy~w<#Z9AOQ(ojDZz13w=hP$I_jn3;Y68|Z1Y;530g=42Q0=pDJBT -ULJeMW?Qp|kFB?kHsy;m{QP;OyiU6X}Jqw!!Ww8h#{&OV^D%@i1jU~kf*snEG93ovH1r&5BCr*1%q&= -}=W4sP0vYZ)jHiA8<|z`|+4aw=%@c9W?yfOLXJWiJj5&UhM&#^d*#G>H}qEC;l3rQKs|EmVKHcO>Yo5 -*X|#2j0!})_6O8#+8%28C8=MO{t(Ugg{(i9WGw0uw3En>B^QhTS=AZqBPY+y^OMT0roz}b3$|*ngW+n -)j8@6;ZuHcMYSZzrgL>|8icR8-13IFzR4jHbW6vwC^R6@OciK|uaNYxm}e)ae?2-mJ32d42vF&sh8Kw -iK%|rUl|})P{>QUR1#VknF+@qD24a@5-rGuzAOzE*OcN^CtOagfmvp{*F5?JqZAkUv#()?Wrr^qxgrW -PE$=kHv(Zv?(`h+sV{*2&4lA?0C;0Gk1!I%h!n|b!CQek8u-mfB7%e=xcdCmPtbg@POViBud!&Qpc)l -qj`<_O`+iN4H^@AU{w@9%c)vf`b1nrw;%@mvYwgC|*8_{7l>=+6v!O5fXY2>)5EhJ-L#M}g84B9R -8GM}-&!NJ7|nS1|L6$VVP0VE1qaV~q8c}D@j&?$^?Zf}8OR6EFTQ*C{a6)3DbNNnYcilCiabdX8ZLbx -{O=l7=7DNKA;wlwS7hvi73rH~&?{H9XN3;4zC59X(jfaw!ugomxR(qEO< -+7#&$xweIel)`qI?*6BOf#+vu-!Ub<;Z!R=YTTA!P%7KjlR|6cAwm1tvT)a+&wAiFmhp`ZzP$wes+QU -+KzvS-k1Lx;~cc6dvqYi2WzTv^6>Hy6f{tnbl9X;Q7mg&c>2KTbD)QkG6?3=P6>#87b3^n>v?V9l -TY?byR1twg(djlJao!8x96^>il52EsoeR;DgbNTzy2E6Rg`yC4q -({KJ>1JRv6j+Px@{RO!$L@ugPRG)@Y>PLcv!X8rAAM?A3gL$-{aREe@QLm3Hega((>{-}ll{#CT2b;6 -6hTCuwm9G&%rF0EtRDZo@iEWppJR{jC0_=7u(M>{>h)X0POTA_V|VI8IIUsF)bE_P4F`gg4LH~ppV#g -Y1h}mpPd~wR|DZ}|w*oR2Plj9mk{>mZ2zZY;)is>tIf}!#Ve5i(SN5Q8I{de+BPH*X?sJ -?tH|kgT1NeRmr -WZoW$IVEFM^w|uOt?)kg`Uop~_F_=EYadhzrU;c+ja$!AXq6-rA{bvNtm%%cCwc4DXeQ`!r3%Hq~ZVw -de2qKPLR%0$`M)uE7C+t;Y{-FeCgI(3auZE#fHG2*5Ksb^InOE81e&Y<5mjdKiLT$D3v=nJp#^+%lZ_GzgaxcgKC+{>gA`ZZ|Su%;4 -DJuFCeotxbI>4==DVBX(RcW{X$A)y8fLS@!29X2D%vNltVv1&hVUAnJ(1>Ye;w%SZ^>gYkzeSgT`9U5A{wRDDOahB|aVl! -DUkERGJoE-rG%8DuLcH?qCUFPW8?pZWO~E#IbNRBLK1f-u~+cf%*zI7t~#;^1JFjayB+T0|*B#OQioL -1)(5t_G-QpomP?CdDN_n(J=t2eKDu=m4+7PWDTL?T>X(Drty*6MVaXI^oFVp#P)ogIH;aCe -8-lwKc=R8vvTjL2{w?a%&OQktL31cNE-as)CHVH;7n!|4=E)NDads*0=<-iy%JA9^d9Wknlg5^J)VxcIGsxL(NsOr6{-x9 -Do}H7EGUuGSBgrC0VyQRU$WI6`0k!q;q8FVpZ3h>iqb43Bxof^y}k!QP-K=?9`Xzc~;DiPlD6a;0yH# -h{5%<|G+)L@1tuQHK`o|?SNAva4o_b;B9H>3MMK=ll*yG?)GCd3|!vuA!dvX(=4qwfMJC=A -M3zeOUD8|H<-mfEvnAV! -{Nw?*k}quviJ)aAaFy{%;(18T31&|9yCIvq(B2GLEC@*QsW$#G`-_Qt1Yv}J*t67shuE$Q+n=Ju`hvW -!m%^Vtg8IYD)S*G1MvkyYB6`wg$ja?Yo}ZJ`Albc@=l8H@Q|nq)fRYK|^((j8@!Zy1+kL+M)SBIhG^c -)D8Z{JS~~PkSUX!kfg?wKQ39i;#T=vd+=>=J1~1T}tiXxB=t?yIRAWnfF)Fxdw)B+iFUCVk|2u-8W#3 -%r_Ro3K-bWRx-Zk<6r8wBC*7OUm@Q&8muh{g}P@7Ug=G?ZM!`B&|2+P?M-nOW7FYKv;WUbNYvs@24$6 -C?V7dp>E4jqT+@$MiQhPviU0#X6V(RR-SVV=YufI3^Y|@9WYd%J=zjrFO9KQH0000805+CpNi_=IJE; -l)0Ba%u02BZK0B~t=FLiWjY;!MRaByU4a&s8XIF6?r}k+gHS<*<*o? -e5#VZxI|yN -jE2`&}oFuRzS{a{1>-&a>69+$Hd08EgD{_JM(mcp94Ka?_mzx6U-}>mCn2gIap<4z)uYP6x34j2k|

    i-_*;o-;w)}gy-#feTF0Q+s -3$Jr?bJqFP`Mz}3!ex}{_%9HVA(z@15=f+?gGBKu0AmbT047i%8;G4bfhCj$B4EP>`%a02>o9M=H(ds -%Y63GhWMEaCnmlF*sMS==CL$5+X&MD0c}kj(p{ODfhL2p<1pE!X ->!8ZsaJh(!z)*?1L}_Gvr!OIW3=2ZJj{PPb~FUiKoO}TAwzi&R0#1>@M{T8fxZb-;5?4mhrDFOR713cfVhS5pzs=HEmFW#C73;i -%TT*ZG;j(~5mBcE{j}kcFB+&*iJqE;!%B`JAK-V-D36q5g1QyvSYRNf+2;1&m5ef -dGNNkb8DNP(gkS@>z>Pz0z|bi>Iblw~6Hw3jp-30h-EdYZ%X!?aA2cD?T#Fw~MaVr7Jv1EPL$BNEemw -U+USFO&trg`hfv#lXzpu4zL`B|~`&Ldrjb)fJ5<7X%!O$#+eyoWtsqa71!^%0_8?h@Dfs((c+IrnqQ7 -J6}Dks@uuag<&u*OiSQ1Ga=q-1L+kZi<;0(%4dl%(Wku!>}4m0i)KMD@A3MVo+5GX)u0(?KtwgNP;gM -zlznnM&?adK%M)96!#uxn&(mJ)8v2fWkrH3XEPvemy`jp&HAGsHL*t9nNGhk{RJoK&CsDexkG*8Jf;S -bdc(Hm{pMPzkc=V#hbT{yT1GUS<~e@o}X6GP*1d6`>EE*S`a#^b&4a~`s7h@)1!k&`BPMMr$`D0JqYK -q9TQ0v$ED`q))SChno$^Wa{@y^ZNs+8=Z{#v*e9rZTgbwYL=rNsJln8eZ&)KzCJ$i~)qy$ZO!?^)1_w -Lw_OltJJosRb-KhkuDSLt@rFA#tdX#lw`|3f1*S^Xu0ru4DHQVT&y~n^cuni_|2fMY4OK^nu9ufdEr- -h9h282+}sPb`sMOq_QX`6H_X<(gH)O-s^yPR8`ZE5H19&&sIeI1y6$uyusHnim93g+#`Ip)#suyZsb$ -<5|k-`lHWZ<1Kaqf*POKVIGSW~;I@Z&Ax1H#Ov)@{4v{uLQEb){RS%gPy!om&e*HxO61Pn;R{*o1)L| -c}^s}|D7gFHkNxVbzQU#xgcw_6)o~{`llQ1MzsGo+HH+i)10rXQ{g^s)u!TxR*VGwbZ<#N$-~|2tM|P -R`!VsVrhAZZ#wI&P<-7&`1W#^6ew^3J>{T*6Q3$=nGIUKctpr96FW$;EOD}V!H?4ZPv`oDIYwiL0u*! -CM_+Z#)+EsT%NzMS%Bz1#j*`K`2-tQ2sBwD&06M%q=7m7&qf+&(CFfDY|^RW@YXvzA9fwm4PVTPuSN-%Ju003lReYLa5X|as&8uYr4& -YafLRvksEFTp>-^RHf*XFUI8;#P}c9fatqfYTQRvro%7YnxBS5po15r%8QbnS&}gWOeL=W|>`oPKbyoQm>}nqNO^cxZ>$l|1$E<%kE=4< -N~+;cFR$UX`4)CTwrYIUDgX9CH7j(Ih1Q3bX|Mc_=_p)~iox{L^V7FqHPHH6H|XDFt_rQ=ah12!XQ$( -X_7;bO#WWo(Ve&gFI%7>8ZV!2krDqqqi?}&@K>SJ)|e#%-l+1{}cB2qCZs6UB}GH4wO!7bSJ(gJYvJrHN$$lWyUztH+-r#XFJe)gb@B%ve>wln>wdnyJ-_u(oH*z|0^FU2@RTq1*rXK^W`p8z|qqhCQ2=jBI7cwP)DWMp?x_VP|R&}N|j*!9QqPck@p^YYo-x2LwqCR}@n>LDZ;PY1nP -PMtmy8E9EBAZVhJpy4;!DLZ`mvPzAsV0etIEEs}M@AK|kSzQ2uxWoOUkYQDWaWCMgi_w5s^wE_V3LF9 -M8UWzZxJBMSY%zkWnqkubA6MvjxwTeXogIMpN-nZ|WmPKbyIK=iZQHBFHtM-AV#9g_cb+7wpfPAwynoH#++}Zd`Gb;y#!CR01_59y=ZI%t?&0|R+2+e~;IR_2H^=w{U;nstfGxkWqrIh5lgrpez(^n_{%?c`_N}W+Vbrry%bO6SVquDGS$f))ka9?g&Y=aY19c7hb;uH -m(sYBkfj_ZP{I(69I7a!EaMuP3N{-k^IGfz#+o#yKi1w4RJaCxOv!EW0y488j+xb!dwaQy+pvcs^;E#l*s3v(8xDZ!yMf;dU -XXtt!Pp3obU*{r03MyDmLha>x{8mxcbyk-m#~}rFl|w)XmV?46@Vx}s%L69jGCo_S}xH&=e%*_Xu4xDA=NK(JgD&z -v|l`%`Rn|raIr6}iato!FKtxM-o(Q&uozP!zJZ#UcRRi6KDpewvp=)X15)B&IM%_qH+MyH-6N`Gs^Ic -QugN6(}4)95Wm%p<=H1~%Vc-{AF&fBUhimG!-h*|8QPkG2rJO4)>{E)2Fyc)n%)82=oz3OOzw13b1B9 -HbujHHgguA&0gar5RkgNI4%}C1hn6N~~oa->Y&_z+X^H0|XQR000O8HkM{daUN{QDhARuU&px2H9Q)S8($PxJAdrfG5ujVd~UU~6W9H?6=1r8Quc5?ogF3n*Dm9>~lNdZr~E8(_KWEEL -#k+%U_PG|MCk?(-Liu<&z&_4RJS?ZjR_cWa+CwX3L>H+<=oo6&6UO?D;#5i*1F{#qq(SoMiiIfqRzr;LMe;zT$cOPIk9tP>rZRYGfBE+oer_KMW -%rn*CC@Cq=^B$YpJ%@!H{loA~;kENT1G%lttqT(!2YY_5JVL*N5k)A8=(-y6Rv(tCsSG&B!&>jfxv!Uv!G0h7e7HY7~)E`If8BIC6a$^7E0sU$39n`}Pamq8K;f;&P3Y@_e?JasQ9 -HI3M|rm-r+awN`pM_B`(Y2u~1Tw@xGgF=ZC`&W1WrLBq#absCOlaIgN}Yd;Idf50iFb&F5%tP?i9pj- ->TP_kIP>JA4@sD#Rym@YsPHU6TEk0t!l*XO|Y2s;K%87XGO<1bK40|XQR000O8HkM{dy;?u8x&Z(H%L -4!a6#xJLaA|Nab#!TLb1!9XV{c?>Zf7oVd4-YDZ<{a>#^3oXPU!=9OHl(-R7K*YT{=z5+GyRL2#O3G! -78zl?I_BBzcVyst%Bw!uzz>oci)M9tF(nel~@>0^nZ;ic!TFZjh^Q{y!?EH1X2&^1U(+o<@V3-r~4`R -2V33yY_ePuqQ_)4sRAFSGv0;vQgx123H*~w(`SYz=99P*Pz1IB7<1C`QrMP| -S%+K<*)*yaQ!TKWK6BcSXRy{^v_Z&WzOG0PxHAop*4;If2&_8A)Fx~KyOewt&5AsH4V4d2@7SZ7y(mZBoH*+b|5h`zzksLsK9#b{^oN`MM@(+2Esq*O$sZ{m7Qi97wyLU@I@q4AbRdA<47cdE13SqxMd{LX}ZW!=4g$NTu8cCDdoL11%S*vxTS}dnJQ -(OnnOXXl+fNqt3Jj}uvE2A$crgY!ppED+{Z6V8LB)r&q -shS1{lL^7P-5wj+cHrSI;ribN=kXpvczFCO!t% -aidB1OF2($r|@6a -WAK2mm&gW=SA71$5v9004Un000sI003}la4&UqX>4;ZXKZO=V=i!ctybG^+eQ$5*H;Xv1|m_3vE&+c+ -}cIsI7I*<2$B|gXbWORj>wJ4U1oO~Sug$e&MZlZy7U6Hf+35`x$T)Vm$lIqB+0sQjlm=VS=HJ&5G$+8 -#$nPvk4D|O!Twk3wl&)RsV;?EYg6^l8)1}GxgCv08LlBOg3PNQdEVu%@7oD -L>s9NL*R&4DQf;Vee%Mz8S$iD?vp0bkin#RYC85Cr$^b!OCzuH-o3TmTsBdQ>Vp9_-Mh%Ic8f>KbT^L -9$99??Li{nJL9M{t@P=ado9jq9v!3Yiw*G184;yD3`BUPbB}e^n1o?B%=H3CC`0ks{ol!sI2L*|v$`P -p+CIN0$uqMVcqp;&X!D3O=P+JNVI2{#L}G2V=C^I~c=Ohg3({$5F50N;2y~|Nef|;+N%uMO%mq}gi#k ->yj*YxK6!Y5V>GHyb*Frq4rs6^*Zg3NJT*GA-No%YG54{bgwm=jBQOg!wOTuQbe!oDlQtEGn -u6MPmJInO96mLQ%CQiR!p#fE2_y4s>Mp)oeeAoQHJi~P6=W+~h!dDEyvcP8&X-t -u#`#|J+V8h{rk=mMQ)F}q}|bXaWPw5%)g1}^B{^TuLyZDyS%!-Zegm3BnIP(BMD6q{_yU5pNzX-FJ>= -(|2q0D_D6iF{wSCZ=@JboTJ3q*_xqTCgu%m8jVUMTu%~aND`4SCPsOSnA;sQf?{FIItQ~#=$hEtHXEu -OmaKNbdjdHTWkFE3QxFft4rNtrdLmCTV73W4bbr^WxK@`F5?GZPbcL%{7>T$HJcXxZl;cnWUt)1}S(E -q51oSm9|28OacoCwjIe?;Iv7OC$R{j86BD_m?%DGW`ZzOVA_X}%#HDdIRPD`^gQJRa}3AHxGvo~Ti6y -$%fwa@u?FbNFU7CJlf;zM_ul3au9Lw=hd#aoJ@9rp#Ll&!pCMzqF7wUU_WnOJ@&m%jNuN5&8R)IFvpqiZQ&V+9p2E+P2h(N2}hN7lxIfK| -_issXi{NGhAqQAF8&K8Vm#Pjr!<8c+u!jP)h>@6aWAK2mm&gW=W|u=>&ZQ001%z000vJ003}la4&UqX ->4;ZXkl|`WpgfYd8JlSZ{s!$e)q2+dN{-ZQUkl~hM_|rimqGxu)9Os-5!dWxH3Kntp&+nIA`6Ho;D~wsBluO^P+0a);~$)57Mly%e;E*Ot -sM`Xou-z-=W_Ut#MSkIOZ?077pp*b)MMtSx>C8Jt(bY$R?e54%bl`5+w&Am$)Z8kNzjK*I&g`XA94?5 -vp~Gc9hfM6ulow+1xVpqDG9RoG|A&u5=MUSHi_c(i*fO0%$2bhSwuPt(8YL$4+{ME*jpBz_R4# -_WI+8Ul{Mj4I;klAukyGw?BN>t=@n0Zg!7;6k2%C(Uv~7t3PBB<|hf@b98ic20JI3hWSJgt5)f?hq~2 -4HCi(K2+x{AvR_3di>)W(KIALGsj|F30<*{qBTd7gTy%SEUB^aC=`0U95m=cE -x%)Sci7oWWWZfdVoF0u0BfvC0HYWC#|Jyx^k{hiK47PbD8w2_{JJ2Q3n^EboJR3SYj0Dtj6*{Rk{b?R -#pH!8zt7?-=Q>NatvLnfiE6;=pwEB*&vMAt(?5_=eG~5i3o3-f5zY?^Ufu5Lq(3UJHfT#uuIN8$!)*B -hj(j8;g{Yx5G|p9RtgWU{Geb#_5EjkyG>px1VnG%I^kU_h_fPPnnUsboelz9E{Zq($ADjT#*)VyKmxh -{?dqJ$6u7OboQI!%#78A?3}<`KZCb0GCc8`IVO)I*;7+JEj=SK)l+iu04S2o+6H4JQ`Yv()Bs^YB=tJ -EZXQyJk;L5$b2z4OI;SI(c#L>Hz>&mWVxHwX6Bn&LC3CkM)Atz?_aeonDbXEx?Piop_ljxxZ*PTQ>0_ -<7coDdW{qLlYbVM2s-riBOOBw5{PGL)_I;Utka%zWF3fqD-DSg1o>{hcm)pJrf4bkg(m@`H&3Q>*@{w -1C5>gklo>!=I6B^GK2S6rU4e(%S1I#Qe+;ACa3esl|Yi{js;7g@Iz9Adg1@ -jJ90D>1cw*vT0-%{nTPLT`}=kW@G0SD|-e?;|nUQ{8BBUPvdgTyWIq+2D59+rL -cas}s1p|N3953>o4QbvMy>KQ4vyC6aWAKaA|Nab#!TLb1 -!UfXJ=_{XD)DgjaFN4+cp$_*RLR)hf1wS6y4h}5AzatGvGRa9UvHrK%gbc=0=eMNhNWA{SGgZC|g;p7 -t7?ieCK@UqDsjc<$2YbR?$31tZt+-h#a)!t)aR9pCsYDkiyVc!`Y!b;qrK7;&|~FpNJ?qRk}NuI!TN= -|B@hh!kIZKN=mCtUkln8w07q!rKB1OmTg_1^IjYg9qA^O4WVHTE@a&h(>gxEd8ifLu+z5p!aCj+iKT>k#kH+R)lXh9EOwD2f -$#@vqzulfuL4+@b~ANr;TaEC^4t=o&Lo0ytugf#^rj{e9pLhu4}2a-=FLX@$IkwU -COoUVO?thS_Z9ewQ8fC@7(q6Dv;Wg>eqCx0(VTYUxL^HBAfbN_3#XEG<#xB0oE0R;s32gB^`%&5RXbL -bDPS3?4atBc=}v1TdXMPoI6xjPlZx$pqm%x_ZOXPu^1EvT&p(k1o@Y1x2AfguNxksC2M2NADu>cXRYH -14{)bTBEhQ0DeA8Rf8T?Y1bY73D|cItnbPlrke6<4jv1s%4|U2<-Y)4O*_}<##43sHUw93_UoEuJ`DaVr-I)^2c*Hu_}Dwavr*$n?9nMl9(xAsYAXolWjvyq_>h%; -z~SlCvUJp|-~X{;O0j6K%rgKFvE{b_g&wDwA&2y)kW`HLb=`rf_kK!m-u#|<*HCLjtXg8jaXZmkykM$5f{ey{)XgS9WE(P>vsH|P$k(LgCMUZS=^1X)D33 -_K{rAP|a5Cv_svbeM5&?pFQ(A;jDJ&HZi>W4z2J? -zN^Q=!KinHotlv7w}rQGEjz&XzTrKa6gogq|0b7d0`Y#r|3U4}+)2CT;A??#dO75~uW39X6n{eGvZ3O -#vxiUJ6bb{rkWU!i1*t(iRsxRbqV^armqfa0AcLaJo6J5R|_zt7r8G($by1e -Z-sw+frLFuoN#pFLwO9KQH0000805+CpNk~-K>WKyb08bSF02KfL0B~t=FLiWjY;!Mfb#!E5bY)~NaC -y~OZExE)5dQ98!Knx$54QG#VFQBZLy)9v(DVgO)}qS-fsxKJ6Nyww%8pU=zweHErQ~!5+J2ZmBofIx9 --q5=?&yV9Ig8^(X-f@p%tT%&Z5h8YDl09-)%RplkEh&%6*)BDcSgzPJyW;0Lf$r?%0vaX%jVN;%w)2l --3n1e@d{+BG;2kZ`-RA0v;b8k_ng!=OIM(cP?B-O+PSzM>}`;!9CvSotg<|d4J1mYX2#y1y*hdQ{-Rr -*s}wTRtbVh3bJoqFZQP>rcKWZ2)3@>Q(ed-s_~h*EjOkKJGNP0z=y)ZxEqMlV)u|}0=(K}01a&I(jNO -8bg*0ef25EZ=UoIkG5IqZdqas;Cavu}Av06*elgZ@b^!yoGbo?SdIl4TJpS`?fhb-9pFyG7Pd+Fug^M -k#Y2YVNP2e_6T1sdmTk40$4xf=`PSn?d>V2aCGV#<^vsU7kKee2O^G6n;Jr(r8GZ6)9#fx@!z?DeVE3cdS|XC=7zsjt`5`RVhdh0yR -7I3s7Q*c76Ph#5;ttwGwDa<8ZC2|KbF7X?K)TWf&|Rf(xr>NZatip(X~PI#aL*1?xRaTug!UW6m8K8; -4`2{4Xq?Cb~*yp!P8dOzuqN%X6yxQzH=fuH&Ghp{2mxlGy;YdWQ{4cmJU3mY;>Q}n^sILm -{emv4jWIQ1o7!s5C=!mfE8Hwo%Twt8x!_dP}hyrw}c5p=Hc+o250cAG=O-aNS3i%)loJ=&J!D-ZasI6SP9dPn3c&cd)qH}zChA&(9^07p>GzPkWH#^9Db=13f+@(%9C^NGyaR!m ->jrWs=){LaT8PD#{u>(85uCUP46&JT?)}!nHq~{)9+lZRDnW%x>61QX-w;P0YKw$7Z*3pH%n5pvh@%E -_lx9{g#W@~L|%){=d!MgG#Vrl5B-a80_imDdaK6A;+f_ifIMJ;3HkaLj>>zHDp;bRFh*)X-Ja?DT;Dw -zN)unAlP+(*orKwM%FG$a#1N8+TAP8;dri4{mf9Ttqb_lg&Za`w%jFJ|Z|)T9;*x26hQr%u%)qF^S`0 -!hJTYIC~E3B#rsBYtbM98$YjS7G$rSPl>MO39HEDx7*?;|8Pq1X74L5RQ~q+x61|rD>N+t5;2%oPIp) -x*Y1~EQ&Ojt*Ct|Z6eh=oc4(I1T5%WNTB0mv64VFLg6vd^=9dy_KQ8P>r(}J(JiP8DOCuc0R}7ltnD; -ywHTSFaLluv>?(~{eS5CQnLTE$LD$v)!sB_@z0gB--ZOa0p0YpKkL=gq@Xzl*yFX(}8o1dr+k@%4U+> -=b;BQ0Ga}egwU1{)Q3`5tgxt4uzc@~V~xr%91kZl!H(0y)!A`{gO_$Sv`4tnb8$dE{LVIW3Ea#3EI&|fnE^lD_r%CXtXg&lG}q~pWTlVN2K-G*dWIK2uqWWW)G; -QEo<19H;gBUwHcn2tv3cJDEsSWP2gVr0774p092YW~Z$UoM}xHnrd5AtU3_a|{Dsk@dI@h+J#?g{F4W -W4c^f*n!~s+PtpKBNyt{hM}l3448hbhJ8wXIy3>fWt?A~Ky-tswTpi^9Ef|yd*;ruhcN7&42Nf43t+_U!1M&_aU;2|M>W)ga*$B -A_pJs15|60_P?Lg*ABTM$m9V`%;8ZuD`(B=&A4q-yc2{PF0*A3HPRHHNK-5$*FH-p+D0oq)&n9g+wn{ -L2<_GbxWl2@oo8bwhK1_japfGWt%hB7)CsHuLZI&WDJC*e#G25uztTlEwh3xXrfSz -8-O{kE+nf(>($fN*c0W=?2z;HxQ~KT+ycn<3SyF1=7+X*Bvj-8DzOaCaG+n)e{YvTWT&00=Ojia>q-N -KZQKpEz>l&dPkPAZ3GAf$ZT=VJ2M{I8eOHASY~o5t|2NahW4cR%+}A<)cEFOD#`HxAnN++^jMWP_NVLN+m(I -_tcfZm!je4n!II(bV=G65xb~~%U@|;_q&ogP?kF4~7zBJHfjA^zrik;qGgE7U>Z^7;cB0On6V0K!D|%D(4XeE -=1Pt$m`yEOeSXP{!ao$Yjz5|+F~G?MgKlLD;4%dda6|sIq-DJCh=njx?Fltr=QjPqd+L7o9O@9YzJ-K -jQ3V4{U%LQyZwqOdx!6B8zuucKhkq^$bht163#Wc_hNdi$#V@u7dXcIghYn=QLFhGJo-_t)d7uml&c-aFjon>tN3n&?3wO-G -Xx{R2=-0|XQR000O8HkM{d7NYV>y8r+H76AYN9RL6TaA|Nab#!TLb1!pcbailaZ*OdKUt)D>Y-BEQc} -;+_(f`Xzjy&IHLGc9gQ>EiFx+aeQl^FeaXml@O-*8>wgg7GjS-+bv`;D$R{! -;S`cIKF%jOw^58y*wQEhuv;d>w2}`LIpB|IJxUR1^z>e*n7pny>X#+>pIlBnukUH#oOwPY?y -@6aWAK2mm&gW=ZAZkd4;Zb!jeedCgi|ZyU!Ie&??kP%y-`sg>=tL10 -ut9mh$8x^`eW@IxFdhPy*@qUFqXXI2!0{`h`pE_)%DbZr-H^&pA6GiT2AJD1_I)*F^(%f>df$TB83m9 -8!03!}@%ip+nWEXS7>@=hA5)npPLNGocqb!p-Qvon6Pkt@Z^xY2)_vzzN5uV25LvqCI+Q`$@_o2|JjN ->N7&R>6KiA5Ndt1syd{XMwN~+bwA5zIG&Gn|@HXii;`t|p3-rsbu8!Jl_-!65%;Wjh!53#o*4TR&$ -l_2H{p)9u~aJxFY6ROa4C)oYLyR@qHsusqi3*IjLj@NT`t@2uIh-it=CpNA{S&6#kd-lGDrc3pAc`wy -`!v5`_pRT`unZ5Ys?aiCFQ2tME-iHs#iL*^5l@cxEH~!;fV(Z;Acl0q`tAm!iej1`-gw3NXsKba(Kx)}LyDa96F!k?$9}BqcFCCYut)N*f~=U^QR -!n&*~$6|!wPEN!(rcq{GNuZ{W%brVJ5*Na0o#Y*;Rm}h7W3{Tm)7ota859olvVY9pS0_7s@@uBDoFvL>k$Ma-f^i -H{c_vcGt*eCd|BXg=|&p8sqL#&+>08`YvmjGh$TsJ@wII%<3nO^)3Dk03L -)2aLWlfz+Bkx&My&}$n9E)ncXn)NIprz2cLj1){Ob}M!Z&1=U~2+GYvio{ab8;dW8_Abyl#yhbOmQ$5b1LROTq -6Q2+I}JC?H_h+o4HAddV}lbA#d2SLLd^UO2#BxTpuDa%l)km67aUv -lrBx_5;%Ike~LbUeAbfrV0mGDuYlWW=4{`jy*C3KNi)QqNYki=`sp&OwcWiZ7vQMzNy$O83@mkz~7;` -5Kl$jI2Qvi^Y8f_rS_RlzfN30+}txQPPJjLJ!)lC8ZgdSw%{Xszsae_W>;mLNWBjq-fro?qzks_`t)-{vXgWgf=R0 -9_6$L1l5_zu_V*(xA?Y3l+uVgvlD16kvoq;PP^`t{W37m&_Z8(V7L;z8e~a=Ci*1jFOTA%3641oB+&q -sN>Q3s$v|??cVS-Aj5OOZCsa)yN`+0{Lg%|{@9(!py3}-b!LfX`aA9+7tl!#nJbXho>YhG2NSHzvUz= -9WrWIiW?8_M^Q9C-;+0K&ikg>AY&!SO-=pV>k -D5y)KlZoa#Md3T#f1=I+*s1sS|<*e>zrrYW8VgyopM?qYn~RBhn=H1qXkWQJHZGmh+7g? -0{OOUSVGi83=N?gC<@b(Q`Byh9A&YE3!~4nibg0g-4n01#Bs;)q^fP`NP -7dke87O#=_1B8So;xg62^zMi8wg1)svoVZjN;2LgNig8WLPRFP|P%Ir$W}n~Fh%y^0p)C}?51{*VU}3 -LT_;h&n1|B8*aa0_Lu)#jQ5{Q}Elug+~Hrs1jVWUUvX+ppI8$8p0g;K$Z-L)db{343|0E-n`*FC!xDV -=qne2iXZDsf2jj6J{8vS5FRTDNnB&?TnnA*%2@^juf$ar$_OBZ0Rm_DoMP{DfV$gh(UPOEW#jIr4!;y -MY#9pX -wffB&v2ab*b7}6sZ*cxU==}UE{gs;6B^_E(-{tkarSX -j~X{SUjs9@S7=i5gQpsgaErnQl0 -~L_0T+g7uDbDsRi}ZGEZ#T8mAAxX5AXU8_X)+{8GEr7Xi>e~99F6urHF4}MRdDRgRNt&+RlSEx@gcr_ -iYyR7ux8WS4=mxAG$dGNPAQfk?e>9-2SW1W1pi+d#AzQS5If{f@HAIM^5W1&icN^h%F(fIuHNmz08Zs -UK$Q;n(b4g8nQ3^fQN+n7;lF~L08;+l?$#tdb}tG${5HRn4k$8-z*CLtoMqPTA{i*!slm7y)2tS4*y> -W?RYpTbL8?^@$enrkA#sz@5FTXNYxN$ArZoZJ5qmbY@r+Lo_6KJ^uY`R%aV4LBNo^6#O&1%QDbgN__Rl*l!}O)pZl5Mu -LG&W$wM1cy$an$u4vkaqG-6raWc9sa51{ezw4g;wm(YPdas!g(-f&^U-&aQV?PQwp3IR;+eY2BH`G7U -H2@1YDrQ2f-^T>*N*!D8!ZdjQ{v{0_&CqY6vJg3^>8`YB+|lzi)K`LjdbG=%)|7qmuZ&yrSbDPr -;o-2o`nxdW%Q`R1P(F3CR)-+@~^S2R5k4jK;`o(&g{I$EiOT)h#u3+!lUA_3>WvClwE<-+xGxh&OQ8^ -F9oSN?>9~P8D6SVgtA>ns%$QY)@A=Dz*QdSbsWu;7lw|RhdDphvA -t0uJSf&Wj^@S1KN=}Qaynx-0>WEcnP-=mE4*(=$D7M)SMkeAR+rM4MwrX5{`BOVFi;OzH7FwJP=8xSc -VsW!L=)ZgE9iwLW#6_pXxFcQcr&Jy;ha7sLj>5Yck$SP$>2U=NXTzj&X4JMngp4w$rIDfT_?aNEsq_hFlE1+RQVLBD!HT!_-r6(_B -@IHbLwG5qpxuRbnKpCj}X`y7o)(dTb)qvD4#G4FrW93eG08(;ZxEP-D}abvEg7au;9h|Y3n@R -$5{c!D@kOR%77YSagZ-Esu7l(UKXyWQ|`sC@K4?UqxrE!1~-^*otUwb&<9Irq+vf(}b*?UtHbEWrbtm -u3ADGxrN@1~K(NK5A4dred6F;&od(Q&HXdVL>YENq=0sVbde#)!f(Eqd+m~G5VUmP!QT+&K4zCx6IJpj7u`;Y^c-q3O#;iQ1}r -ul|mg^G1{aZdUNU_rdPd^qa7QvwZxvrp>&Gl+PXuAO15*0xNif|9rdjM^ZZVlix<~^CTBUGl -?Z|hy}Ssu7dJ#qf$IXP!QFbfwH&L>t-CfwUHpeoX~vfW!1-&QYJuV5wrc_Hkn?5klXjVQS>Zd*W{|zKM)sf$a;+(D>{tE~Z&u)Fh^#qBeD -zAoJwoNbr#kO{0I!LN_`K)@ggRUf{(3maV8oD`buEDc!fCy5fgH>Hgvr0lE;BB5OQuzZ7QIHI1!ZYK> -pT^zrY%cp@#qM8E>+MlMK4Xj2%qCb8F(sYrc`>~0gob)hKQ_RHUw$8FU9EQD>pzCcNI2!I30PVq>?dC -S}ve71aNB0mZXRPL=6w?NZNxoT>MP;KYgsjBPFxT|H4wr792I?|s-uCKj&(HtM^E4Bh@hoG%zq( -@KPADyloFzqGsUn*(U6*B{s`K;nvz02gERI)oRVxw4Om6WF<4av+btPi+`|Qj<7fR)Y`;isvwanM@B$|YA?Fs_+-9Mf-azPL?9S;2O+oAHvTuZry!c -HNG`^_u}`^G -o(5`e*cyIeQvCWoaRF9#m|@cY+bImEvney_qRj46x=^r9yvc0HZS=(0NO6)qX1njd8AYu&!jLUi^u6bWTkBYXvDEN|SB8VLb -}MF`ITO)3Vi3im0Y||C`XMfIT}UvYhMKF&2&>F|MSq1t7nb -xd13&pBI_}0xGOg)Tsd92g0#pbh{nkqcGh>$wC?gX7e^EH4h%Jp8z2=8k- -J-Uvm)#^|#u{hCX}cWi)LV>ILvNi<)E3V13;;T&*D(eDlrC9>4kV3~_*gKnecurwyH*4N`|bRgV#A>? -Q*pC<`sCLLDqq_+Y}9zy}B4Hkm&|bJt8)95QH1`3fE3vq+1wV@7+KKYhYrubt7U0vE%=Ox_5VJp1F1R -gtAajk^mPJ7*P_h()tbnaPS?g~WJYJMdvdD{5RZ^!S^htEH)DR@PSn>__Wbum_L7{nt~H@P2bJQr-&4 -DpR)S7&HLxv`$3&%+B0YRjHpX7LZYI>LvK5?V>!Cd;w*~f+*?*Qf%`7MHPjZ$bLjS0C&~U -EI1#seTV?>k>pywEE-;N?OP~Xqrkz^{j<@U__Pfl);xS~ux98CyXEF36fF|P!I&IANV?Wf0TQZ@O0?Z -p4ORKlKgy939pyE~qeNBa~WKNVClC3j{LYQ{1;t0kZz#jKVv-K!Hic1KmZ; -R?jL<~(f(=FNygTKcRSA#=%=ixQd;ah{$fm0zDYECFuiwF}=kxwF -3h+<>K18*KQQHG@zknx;pAY{j%Lkyem-MZj*jgUATu9bg@GxkC5v(%rZSSIaXOCw -e9=BG244_^y

    WgM@p9D~|&=DeKoq<(tAWdh>0{k$?%Dqycx#KovMT)7bTMf1k6H4?Rvk -2=ba<3#j8%1G>KRO5oFTt$gCD;*|0i{}?1_NKvPXK`LHhq}4&)rtM+@gUfN2Sl?--2d0(RKo&w{A2T6 -*$a%Mpqo`G4k`@;*U5zra)4O -8{>V2~Db}?@(HZDvLa$;XsG_whu7&5P~k;3&g#>}#SGy}g}UxVPTufLc>LP>NuyqmfAGzo#_nG!rbJf -dbMf=$fYpiVIkIP?g91*as8*p{aEWZ2fZ?-XQjvRmUq=rbA*`7ScP$D+^lX3>`04AZsz*L$y3cW}&h-}f>F@PEhfDj3pyhCXG8+E1ijPZs-m-L){ -&okIR#1EnNfW$aRZW6>qT)KGKP2hjCu{lK|ECXD2ioq+q(6b{g3}O;ZnCkKf!ayd%+4Ok-Sj2 -37dMxg8^yLsP{MdG=fvscu$Be%JvDi^UbhL33ym#RO5WLWLHhS)}bNqCXJwDd6^XY-(s5MJjzdpojbl -R{t>3>cK@#;pFC4%T0mo`3tt9(=ykC3G(7_u7u8XQ7;7ss>&++|Oi*f0r{lqhxpWD}fFjI7<9)Pe*-znL(_MO6L^G1g~8_k79AU;QJ?tEokN3 -SY0&1Eot%Sn>(a;m*j>R`}iJ172Z!k29C=RIoj+WEF54t4(dbk7J{}-W+@b3Xum@+s?JA%7-(4kEJ&B -&7_g_%JDU!J|6wu&%%sJ?nXSmo-adT$@h-*cK?*hc?5s0IuDrD`8R+4pmQA~!z$D6z<3?_exbIU;`BX -EWwA`cqd#G~e=+{&V6E#B59{b425#%0#(9pIJnxU3M&3*^X{j@Zi1FP2}6EI-wfI@pmV`8NfM{qEp&OsO5DxH?vx<{`v0sacGNoq(j^S1v(zP;0|#ZItZ7i(%8pUvrqjPn_&oRU4iyk-gde -!bo>$HRSHIai4FuF?^AQIIO_61>c%%DMJg@>T6 -iLu+SL&AQfIKa5Q;$%!5u`UBS?s_G4~ZNo-% -J6lw=27jY|++O&2hwdaOX!Vd%u5z*2ZC4C--;nJ8|dnL5Fm$QD<(r2HlxHN^S! -x=iVX6mw_cgkFjgewO;UT&jqWR$gXYHQhxc6Q-q>An8#Ual>38`oG3=)=52i_)cwPP<>6~y2A-}Q>P{ -?1Dk|_iUs#u(BVtUIJUSCQ@1=94L-Bu(Uo=d$oXAQa(351uVyO7Un_ihd`ZcZdHA43xG2b)?^G*vu$f -f6re*}k5C-^>l9)_;3@fI5PslLu*3FC)S4c*A5Wofu6dT+DTst}X|+sJgdpx`_>5nCTLDcBk*@O$cYv -B(ta1dei6+yhLkZ_bikXV~%NaPs_FPA=EYpJ@z!m=t(dn0y@*Wel%_h8}@8>tVP-=!r)C0#*r#~?D8d -LlM&Y)tj)kF1ioPBp>42S{r=WNA#iE`oc$mxW;^yj3kCj0^2|Iycl?t7&AGA{NqkhgXc)GS?(6?hO9K -QH0000805+CpN&CRe#WMu}0NV}#01*HH0B~t=FLq;dFJfVOVPSGEaCwzi+m72d5PkPo5Lpgbg^9E3ddugdf%%j>2P6<@7x$e$m6`EXBu<3KHiAwpV3j5wvWdgdl=bybPSv|-xtmDT6_6@jNf -JN&2K2FF2|>B!XI;HQi?mb`O3Z?#t13+OHZ@AAEs)6j?}ZLO$G&#!B%IZsF~OjEHlFxzruea;3(I40z -8u8mOgjgnTYYUIn+QC5oOqv^J!R#C8EwOV1&xF)+kdg+1N_l_1*Dp?J2-vC=yF=NQj>@#CJo@&~7ACV -WtM30|!v$FeLNr?F^(+3FZ?d{VMPo0y_(zb!%_)sJS?`_gS3<#Lt9j@MxKhCyPlKikE-=^QE- -zDUA`kLg58yQ(rvS&_O!woGoJJehPh&7Q)(o%5EbXJOtRe`jR{2+{l^i>UcY%K@^ZcF&7Q66Qiiz`8t -w~foc4@EkN5e0#qGi!Amf)e65F$DsPkqH8UPk1Ap@TdG_xy<8z1aW`ZT)%bc-{r+2SLl|uU8X9PF$*9 -J?0NCx#c+Om@|9om4zvz=Mp&iKg!vC(1;z!BIX6~FCvcFwC>L7^1!MzG`>vAKPdT3BXzMLR2twg)qtR -)!Ic;UbvZuHgu?aJvOp0|0zC%Jyyx?7*acor1=AH+AsM!;T1na@ujL9xuL|Bq3t$Ajv^F*3&FQcx}$- -ePT%-5}?nW}5%ax=KvUw=qQcFcFZcgaM+>@lS}Qe$(~+TE|LGJMotKq9K@!h+7$F?Itg%u(2^>`;Mub -XA-{zMrXT(6UMK&OPx0B6qEG0>uO>+9qd~PgH!h^ro#U1jP1J^g!;ydyA^nqh&${ssib~TYx>M2&xvF -I0aHX0TU1|qBm-V%ll}HZ54r~!r?0oZ{@YlF43HqjicsB=aP)XqPKL)sa>neuG@tD?GTxQG>a+q*EJ;IJ -o#7;vL#495*Al`X`{HH*JKzVAl?D{x|HY$O;890z{G>89!Z%a`z#<*+c=MeHrizpGFLVrneY7OSj`BQ -HOiPmykQxQoD5+)T!p!}2Ew{WH(6j|2E<`cj!j8qpC!sn42_FUfgBEMN>*LULFao!AsoEi6(@d$pRGp6Tz^=gnzGw7}-W+Ni-ybOt#0uO1e8`}(B9 -P>cBxf95XUp<{wv&Y3}>ZbR7Ug?^*!CME^blR{8%RG1%&LOc1f(LL?`~8l8c -`pIQNB2U+aZ$@-wtwu1@i(xjT^lG@HwfS*vZa~Kl+BVE1ho&SHn9M8-+NB>c&;I|*R} -6Gm{eG&B>UP^ukN~mbsM+#n!)-I?uebO4nFYL3I3d=x?9w{wz*mT3s6e~1QY-O00;m!mS#yPz -TPkZ4FCYWD*ylv0001RX>c!gV{=}dd6imikKDEq{(irLxWiSFy{ogEwm=uS!j9|21%f1q)1<}v -pl~Hh%bX<2qI7%PqW`@!e32-LyLY}1ww4+WhcnN-k;+?9ktE6cu5VeA5MDK+?FijTQT826^zVxcGhT| -K;B{etcC2c0UNZYz$he{1{^BA>wheFMR8%lx*-Bb5ML)b}k11<9E^29MzNXnbQB}0gMylk2)tP8x$sg -lNJTMveZCUc2ozu`(GD0QM!-@L+{OK+G+%wrZ49v)qMMKkjiYka7SSvy9g$U{)F>f7B^c^pUO=es+B| -Rpntrcmju4RtA59vS%k1O(vX7*0hT`S6qiwlqv=JSr+PIesIaU|&LQzYgM@IiC37D$X;@dwxX{ -yLTf@B&>^ckR0Y}fg+mIA8A0(jH#fBHcv`Z!01&fSw8f2nLSL}3S*KcssGpnHfBMUxU%iS3c05N3H^c -8yO)FBft`zBtXygD&+vAGlEiEb(Oc`5QH-v|1!U4EyCNV%+9(cD81EsBKx{{&=ZE;#A5-h)0#=-Trn{ -~^rN^9AI41JZ%y66Z#SH43;`IJqVl#0m>jbZz0WmMH( -TwPI@9I-JTJ5?>m-K*)wM<3?2Y6;s9v@a;YSb@iG2SY{p`+3#bG5aFcx?W=UuIcdVf4(S&Yt*zGYAm8R!jzNz+zyBl}ZA633g(Ombkjl@Hq_DS{Mu#kbc)rj8K^C -Ys~tgWih_(+T&&(Vr$V20yu&8#usHRol3yw@g)YVY2=^de!*uOfYYb<0R1&Uf<59!)>QqQtAE!`3w0N -c+jZ`i9>4IQ6-f`-?Q+`?y#O_{t?L@bQPN;(B^w0sheYT~&lnv>R?2Sb(Y~BQ;2q9Y1_lzYplIm8O>) -sPVt0-}zy!-`Njrvsh9O#vet<5rYiXTgzNzRDf+5R@XgL%IiTI7_9Tvn=Xcl-wM@Y%?ORn%%00DV3?LEXKMJ5b=qF}aLyeAN?*R6e30mm -D%hE>n24b~x3X1$=@-t?Kictk;@C`bLgjqrHs2S`|xf99?zVUP0vzUAoI?429$1Cu%@(8I3{()!os?p -U7%4+BQ&(i~?2OLY{%w0?SbG -1V1mAEAR`3ZZ4T}mIJLj$O=QU3BeD+4PhI#O%)ZDP+Cu_OUE3uq&U8?;eVM87cp%Lc8|?Z1ul;U)Xua -n_q5HP2IruejR77Rk@KPd2=7HbFl5eQmz@3Y(XVv7T!oMf;!pWJ|C -H)jr-GH-VcciKYJXiv^^SSLH`jf6}-0^ICrmJJ}&!4`tb+K-5O~uLT6slry9hSw@q>rzquc4m^>Za!P -8U$Lmtbj&sOx2&__{ArcsAt{GiB4wNhDR3jq`TUZ*Ps%o;lc7gG5Z>5AvQY{t=+dOlNG_DH#8N7Q`an -u4&rMRy#g$#b}gg#ZiEF=ccyLGVTtYQJ4Xr1jzAylQ?eNjhN|o&7)%YCIPv_9S%GS;>I$?ZI;i*f=Cs -L_w9b-#sq?-pb%%8h!~f>2%piRB4LAY9IMf{i27x?MW*vG)b;bhqhzn*Ax9L0o;S_EOFq_ZO>HmAS0# -GIdC<09{Zxt{B?-WGNuJEt{NJcQM$m?k|IigZ*t7LeqsY^|Bi8?m;8v1(okZ)+D@)_=Pd1L$p4`t^Zw -bs9whHk&_0b(>9$Gs!v8N$2G~v{96vUiFgaD!zcfb*!hjTR43IYvX1!&MIA+8OCi;a~{4ATT6HcytRoI{{7(G1u8W4yNm;>@i_T8c+ -!V6B@jF?br*){g4U{U;^t^iZwhltO6GST2!@x~b?c*PZv*FKOQ?f*w+7nh<&-Cr$`H^Ut -)r&RJw6zf?qq1>o7r@f+ET0Q0n2-337pgooAymHgs{Ij4x+ivtdb{3q>1O~d>}aNdC4aGV2@mOGCUz@ -V3!D@c?Sl)-`S-U(fHmjU@pMJW%f2o9_QRn7g^%_iu}p~0A1HoY*Qed_04cxC~%Xu_=XbZ2)$}WFo%K -3J*-fpn0OXS9XE6SycPfEXRd=AV2B*N3K~-Ay;}|f)3PL&epco}-Ol1nMwfnQPt5MSubht<*Pq1OtMg -90^i2$J=A)I+;@2B0POWIhEHYqu)rpVjvs~xtu1(d&Hz)>gXzeKq#|G7#PeJnOS&9dJ#=Vg^F2#Yh;p -CJRuV6Rx{=;xqA6Epbd2Pgaic%Cw(~950cs97Bh%t^U4~T4?xmI$0jqzSw3GJkj#?NZG5_nEu|(f;6)+@fyXfn|yMdC{M%R2N!M -^2n~=VW%#k_P=K&=p}34?1k)byBqv9nqC~G`K6oG@i;Ea9eE0n>ipkf|ULBaetfQ6&0?oh)lOuk*L_R -RANDoZ4#}8PW>Wc{d1qMX4O!sg`pS;tTeyAG%^#m9(KouyM+H!C4VGfm`y^g3)X*LU5&)JLqy2CtNi+HM8`$EaOUsJFFF)J5PW9dY+>MHFJK4;T|}hthr_-$P=j>5o=g3vbUCI^ -yMecB?jS8Gg@F>bK%D>`UzaK2edt~;Z9-NWjnQ%(@$wNyis4#fj{g;Ht5BC1ty2Y7N(^;MDUg8boi*FaOECaR>u}>AC&#F27Iyn8eEK#2^N#G?l?kN=+yA60zid+;}x(4q+>x{Lk;x}eEQQ@e+OB*W-=Mdn5@{;aKcInvLw*#e>i;IhgRDur|9Iq -n~1<}R-0Z>Z=1QY-O00;m!mS#zLVrm^>1ONb#4FCWX0001RX>c!gV{>S&f*`@TqS8{I7WcJcqV8C%?>67$X(o{ybgwm>az -2TG+(RSK6!aiFqd&lYU9!2B6)O9UXJ-uo>tsHN=N=P0>6>986m-kLcJ1H!M?Ie4q9dk`aOzRBii|DDC -1!0+y-?_0ut2?cn(Q=V1^;t>H%1IctQKxBd0wH?hs?;X8;wf+SiO;GFttwG>%vf%sC_-PjA&1GSyyos -_zn4&|SPS7uL9CF>TJj4wMQ;R?$r@XF8xeR=95lsE9sBoEm}x7Y_6+YdopuYU6(9C((eM^f9!P@cGt} -~-ajpxNc3fNAOe@EnDE3|sV0xpxBK!$mO)QsH296aVq}2!#pPQSTk55C!8cu$yJt`}lHqV3&n-EOmv| -pCoI-$IdSs7@UwPDsN_zKu30_5#uiR&cZ4_FW{q-75%r(=jN?^VZ&$7E8me0An|!8?naUeQ9gEmLK(` -ijp(%uLaU6OR|Cj~TIzK2z&T-Mhn&Oj@3i;#?lKCM$4K$_ujtNdtg$knLHxq8WpXG0HG#Kx=-YxkV-dV6 -qSZD+XM`I#97cY8~j_)hn-Bucw6oy!#u?*v?goxO!$Jmwe-Wh-R?XkgeI%zcRau4E2*8t=cxNAe*g%| -K5iML@RUz=~|wd3MEE*GoB>3YGtt`1EJTfXWNL0Mc|TfW6)nR9LLl6t)7wiV9^eF5hAm4nM%_ar1rbD -OgTP0*Qh>fu$L;mBwU;@2}+M!osWS;}EO&1ZsBiNQ3&AT(1LJs4BfmQK2e*ji1mVfVrfp!JnNSm>6dp -LA1Ep42*6+hP~LsPG4d)K36z0G%uvcUg;HK#sHwwG`aJ53clP_8{RW^NpgKa)_v1*mQJrnP3a-*n%umB$|Bg-zBk&LM%$_Yw -9=3-7;jBC!x1`i6cBEx6Q-rhf2-s68ir{Y!3}iKg6|yx?9%=L!P)h>@6aWAK2mm&gW=U=|l|dj2004$ -0000#L003}la4&Xab1!psVs>S6b7^mGE^v9(T5WIKxDo!ozk;w~NILH-af_nOBEHKt$Q=Tt*CIu)1=d -@EUU64COQJ$jyLOBI_s$GOiF)xm{Zh0-5NOHaa5$WK=0)BsS+XSAH>Ob{NmyRiQW?hYwJaJV68n3xXv -YhAIONsA{gm3hDY01WkyoA9adH$@DpjoW$GDV7q2opsMSkzN*Ia4AxMp@Zq2J#6yh_ERBmR_E5B*eQ@ -3;qOrOg18oTZ!EgWL=pAMtj2JuHy)=OZKx+I+xXZSs5jZ#UjziDtRo61jJ30V7GAE -cqhv`FGLu?;O#}Y`TNE8I=ToVORdClN(nDzZj4A*Fb!+&Qg6i^Zx#II^QScnxgBo*B0Zyd|9MBU1H(cZ%Fw_*HE^!63L!K%dql`Rxdwpx!5!hE8!Fi9$2f`uX3uHag;KR5SBtLb -p4@o2qZ@E|>~3P6^C2GU%)vA7#lqLzsp-eqFM;|Any1;yv$;^MOaAF4D9K^;rxv>GkhDHEW+ducMp;YAw-s?o5bTMgHw13>{0XW}v9PkN#&r?*A*cqvw*6d0L?5bnfk%E5e#(-9cnQE#84U<=CIN-Jk@WerX -+U;^MuQPc{v`K;8?GL=rVmK`xjpB)@j=2KU`e*Rz`=5Rc)+~4*{HrI7y0?;>T?R6Fz7T7{)h-LIygg~ -YCq>bU^2?qYf+>YW!8cDCI&?c{lNyp6x3I{FDdl8tdw;a7oN2r%Iu0`-e8 -gHb7r)XqJ77hdk?1VQaOE5qpcU;jEw^}DS4D8u3zK)J|rSV9lbK0*{O~(iE%nQe1G&DCbM5$vmhFCo2 -+URgbyY9L4f(c8d&{Y6Pnm-5>g3JzYp25i!;}s)@n8r-`bnwYmtG!e^!S?wh#1NC^kQv+qlN01EvMS* -A(p02y0y_Fl{F@0ko1TQzqvLZPeDT=?*0_Q#yB=KWHQNkyx}7@H1e`sS#YnQtT9ZYCTXtsUw(wLuk#i -GR2G5FxEVtf%O`UQh2K#;WR(9>&R^wgdz2SM~?cy>1AMhG5XIQ)*0nagMQ4fE -f(+IIhV7j9MzVJS|DQWo{|E}65n_MEXX1N55`nOb&A6QY=j?506w@hEN{qEVt--`g&58J`6&eMAa~BI -MvPYms{my>zxa~<@bPbd_^<|0E(*drc9QA=GuMi72%@_Oeh@4J=vyH-3{{wdemCLtAjlF|LHvsZcl5W -7;WBd*!d$djuWq&x)Mh8B42HKjHS(VAISmoTE!|TL?^70iGJ&X>)C{)btnkLO_8z_o=jC!f9c$I -e7G*c7hd2>&=&g&=}4!D7Gr5~6rI6c;#5AQNNfPJdjS?grAd-&eiUT8O2VmOZseb>I@)z$Nc}G;Ke#f -sRCQa-Xy-pN3~Cn{tRX0aMytbw%XWVBLte9d2nL?TgZG8*XRpm@?C*|b=<`m;sCn-Ymn@S8l0p&(>qp -yWV0;gC4t`lS4Pe;kvDP@FXOb1Tgps}jyGJn6x4mR)Y6Nh}{&o8G4d -xl+(?1V(m+T{yb8x}BZEn!j$OiM}oN$D}VR8M09+HvaN!U<6K-TC2joTkE! -Q=%J?xAde9YCAK46DA;q4Gl)$Jy-^UQZdo-kfg_kisp))=;}e5RG#ruFcu4h^U`;8??~#OpO4S+^@ME -ZZd~Ce-#00P;oRNz)jF$TUU}Rw=P6j|D=s5sRo1eXoeN4n9O*r)c!E%+X47oyeIHO1=E#vmyO}4ue2a -%3=vCH3XB)?tN;WsVREYgd83PI#)!}0+NUJZWMmTcy}^0d?XC-xht6##WY79f*LoI@JJBYiZ3p^e1wL -IAwBmzOQuQLah=Z0h4rjfQz8KP`m9@`!WY|=tOUP%3MA*FF&j33&93;bnh^HP}QBp@f+L!6y-a^s*Td -LN{LNXV2I+4jz7c=~!(D6?b;_v_pe-}hx2D9P~=-zxK-OcNX$G4&spun)?b*PQ9c_pO$0x~Mg!zP+{) -!pOk-CYGthSrygf(XHk;2$aoOo*vGb-oHGnL%^6n5*Em~P%|BkO0BprNz~CvcHz#J**3 -7-WkKsjw*Wb6^Va6Airm?y)S!v1QO)?7tMa-Xb!Y&7@|!izGkKA#eGa7mXmx53!+L5L&NR{Vm{*6MjR#upm1{*6FplR0yTC9rw+405v!_X~ -Fb~rjE`Sf~9;t2{H5emsUGC=a&ZJ9hSR;HpEOsLF*p1w`P(GIsp(uj)un@%h!iNH`X@>vL~C1EBOlq; -8FFDRcAZnzQs`?j^mWyM1Ao;t+@w%xvtWlO5X}dx$KpGccm|Z|E>MX72>%_jmwENUT4Sl!`x@bms^rh -fPY_{_>;y@bWLdkRJ&Y5?7}`X0ghj(w{ntO*{$D!<(Yg}3P`uoPMBxAhJK>m_7XVMMUXg2O8ae84jL^ -e%CBn>?j%+EE@*kCvB$xYy+Sf9rg*98LnQtNP=yrH10$1x2OcuEFZe-wTrgbVA~XU>!Q`RJ|6$wT?I( -x1^2a|5uy!%%mC`q?TBJ!O3|Ijy8h+eIe{X#|!v^gd}xc}JN)bW?T5ZpE2cIj&qR7G48t+KRSzGHxvo``DXcr2-)WfX>Mk3FGNLCLsCglR7p=xE^>2pl~hZQ(=ZUe`&W#_0SRs6dqqL0Jk(o-mUHVmQ^ -(+VB72h3f6q8+Nf(u6>8Z+mkD1S7cfM1r)k>+y24TNFZ$CgnOKpp>##+5?n7h2xx-<5NaXz}LJ99&^q -U1D29Smb;ZTplkvVOPUDJBYVqy#=@HX#TJwj&8970i`VZ(zTf>W00PW+&f?M`tQe#Xum3fO^)d8g4`J -5rvOrd+C&)k+>k&jfoBexqlf&Jmp)9FP@wJ09$E$rn>QCB{}J$P^2#KP0l^SW0FD3+|MzzFW7t`e#@A -kHGYgM_{}IvvXNo8{T7oo|%WC-u(h`U!T-te^!HU$uWXumRZZ~kb3DVgE*v7C* -M6LWcv3|0mSct$E-)@JE@WwQbS-IaW^XTLZgg^aUvO_}Zgg`lba-@&PR` -FO&d*7WFHSDXEGQ|Cct$E-)@JE@WwQbS-IaW^XTTWprU=VRT_GYIARHwN%?~6EP6|6|+xmMV%W35vdi0A`z+3h -~y1bD|dIgG4EZ9xaN2qHw!rc-vt -PTAaKuYvzW0$fdEeBKyM -JhmCN+4zoe~`zTe-md2X8n|{DaPZ;kFeAO3t)(<#qtpX -BOP)qaOd|TY2GdyBbJz8lbo(h|McxBpF=sMdrPS~WlZln#8ERim4WbKKXLFU}`?$WP -Ka>drAgNj_1Rd%tdaNDK1aD{&Ax)!3TqA*tzr9<(C23Bjqx)D`+;M;GTKJ~@6~WVVh_y4y5KKH{dh?S -A}luP&Jb+MoPZl3eWGIM#_uOWG}_71aZ-s^`Q&jDHmJ|B^oF1Wo}T6}z8MO9KQH0000806LatNyi_T! -Ug~U00aO403iSX0B~t=EjcbQE-@}-X>)WfX>Mk3FLZBkUu@6aWAK2mm^k -W=X5tf)if=003?P000>P003}la4k75FfK7JWNCABEop9MZ!cF!MMX>t&qz(p(G5#2D$dN$vr;hBGvIP -h%}XsxEXgmjQbCrr3t3TmgXgG|#p%WOoqn_w|Mi$g~naAxx^+zPrIJ#-NcTSc* -KX>Ib3ONR7ccgsBLZDqTDGhGY_~`OScjRw-TlIzqGQ-2}T8P5h?4!h`RRkdncNrrD=KmI7@muKNQcuJ -DBnPfsBho@%(Vci-Q?24`j@1U7;V4@LLmXi=KVNFRe6aqkv4{GNqoLe3f3%xjY{J&&L0Ae#|INCv$?o -{>vGWfJ5sj=H%v@mOg=6WuJcp^cYA}I2vI`&vDwI-_*UJy|?Uxw3(6Gc`JN#nV#{BZ}(wMhcd9vPIuF -oSFbcp8S_H&@La3dt0(apl!-Vz2ntuGh_Q+3#FMW$YmK34#Fr2EY%hId$M7%@_AtU%yJU_6*g+8D$rm -qOUHLfV;aX+wv;{j|jL2C@dqk(Y_`g66uIy%r-=r(sM_Bk3e4-Y^ORt@=_uyTP@VT`u!kwW)eck`Xh^ -5Eck@(4?0WaB}R?!dx*HfSCb$Ap1L)q6&!R}B;T10e#1pbi7{NpTvue|}NvA-I`M&-!K?7dG`PuoT?# -yQB^-(1txCF_FQQeu@`-)&;bxElswyMh5g;)E&CZCrK)*<7`O;+J&~_q64mv{RpJVI7*Cp@2J{cAzwe~k}YVAP+Oxhcz%Mx{)@Gq6FCyApGi*^p%*+kyBf@&ehCSZh)Lk3 -T*o+1TY6&yd_-mL&+dsx$lkZ+NNGF;aDKf7e^bBtN_!!eUvXGIhM`6*Pz_8um{7In9P9Q6VV@9c(o<) -;yt*9k5$&)50!v_+{M^&lS5Fm)svm77p`Ov8QUB;>4)oQg_@LH@09;s!N1$az5`!`Ta0|XQR000O8I+ -kWh?nZi*UM&Cs#$5ma8UO$QaA|NYIW90RF)n0jb960fZf0*UQbj{gQbetMNt2_<((V44eg8zdk9kJK0_%O(om2SoOwQKn5zV>%5 -zj*L+Icy$RHkMu{*7kC9a=$T_1Y1r&g48s0GYLga$&@o6~p*pxoBIJsx{P38Lu8 -m!RP>i9L6B|sNaF8N4P!7)gC8|<@`}b`6LZ9lH{4JqU9^}wCU9Nq1Q4@^{kT5>i^lnguC&g!*wMeowP -ZRcEQYq@&)B!&ir#~lk-1J}hpc4dV5$>DwS@f+Lx -l7bq9-B;JdD68gI1n=Ml0w4mnp!&q!`}ddwvF@t^)vURbrQTg1_c?d+b+2eaka*L)JI&bY501^p-~}n -J}2EB1i={b5irh&v2R{o2w|FOA={{n-IXz0G~AyI0J^UBrM^1DexbpIBFH{7w#7=o2>B5&Z-Y4Qli_u -UOJ0Rt#Nz$3ux2?$H9>8LkNv$WU9{67Qdf}=daJqA>5c$E9Qz2_^l{QBahylv3n(HD+!SFpA*8%lo>G-lk0229#+1{VABd^mJM0} -$SY1zES~~ND$rZfSWfM2|JqBGK(qPa0S)bfjEubkt7a%1RV0$AV6VREPD1$fJZ+~lF%a$4{KhGUbr+s -d0ifQsX%fh-GgBV1Dn5n1dDGqgu6zKoa9gKgLIpEVNsHmc`0S#xx#YL3jB9|p-XGQ -m5B&~&i_b5>ine-JFNS1^dD#7cq8E6fz9;ucfx(xE=sZ@Jj)CC{M78Un6Mfk> -7`VnAc@HfSg2W9?vo!X0nCFPN1&T6tQ!HG_ln>Aj0o_A1{I$-8G6t3_J!RR{dFckjX1bnuQ)ZHf^HXR ->DF3c2X0kY>g2%PWii4qa7HZI(KFmt)OP&?$eUi#yg;VWmIoZ1@i%FMgIY{`M_BfemOoWE+Rgg*GIcs -J@phcNXPS740%Gskn9{dW)G24+g(aIq*IZi?IOdVH)U6CAG4b+OMim>(p?l(F;uxP=51{#{J?n`X-k` -S6M7a$mZ&IFm|22iMX(x&CtNSpcdNDZVcU(p}2aA=GepiFV|B)Qy_5>4Pfx%1q>XxcBBfkiS|b2c~+j%bii}XT0%iDL-|0n$UvwAWAqR+kQvN(rBK -5ZS8+@u2^6r@n_uW5hxiW{^AViLr^!G0gIf2P^Sl>8$FjTTsEVQ`nEAllEQSd -&CaYXY~4eC0^<`fl?j^n2JXv{$c13vS`fZ8?G+Qe&Fb=k1N19X}ggJx8bHa#mp3vYM`A+#LqJN8qPYD -)aEGWw8&eL{;f)E*yQ(?5-er;DJ?Er-O97fC65sStPqDXR*{)4S`?dehTW`Ln<{gRxO3!-+WM}?fRCk -n3P&!TN^j?<`!nbxQ12=o5I_>#=VIj+fO3OIEcM*5t}()#^`*8wpt#(V>FUGO%0*? -dMHK8^M*Kv6uPW$;NLQ}&@%1vtDZ9#Wa=pov3$v8QC}$bADFiZ#P(KVi2m}c9YuF!EM)N`BW{l5QaB! -AMrYK#KUhmNeZG{Gsdcdl3-0^_QxPrw4i0n`h0O7BZx5_iWCc~?d;f|fWj4zpRzPlcH@WM?JFjPLLN@ -^`m>j6NtrKP9_i5<9=Zr?15D(H*k!;o{Mx1FWE+smzx#PfwvS)GJ@#ZZ>kI7L_WDACNG&Olw+78~)6i -K}D`o)h?L3v0OIdInJmLeqr|!<|I5?Lrft`7rUM#rPX(NNs+TwjfFn@He*DGnqb+X+t -hqKOHB2MS~3n;PC)G(_lVI=9sxgRF?tz-gl4Eq{-h&I<6UxC*)$<(WGK5C}A>fQuE5@{@u9;&Mk=}1x -~+6+7B1<04J7K7en+)v~%H7lM6rfWtMJ(TDp;sh(hW1PKsdls- -RDG2|7M%gY<1MHjN0n}pMWv9li)Y9h;SrFY{>&BznQsd%wG#h7Fw*m;hi&0;Veb$$w -I4=%CzLXR*;T)G=r+;89hlXutOn|{KlE*ZINvS_^Z>UX?C*i<$7fBn5CdItjx}B#Vgl`w&Jv$IN)@J- -L*uv`VYpzZ-AGNrj>=UKZwb|b|a*l`gGXew?DX{lBAZwS9}$p*X(xasyf;cC<=TtVarF;*1ncE2O2NO -2R={*=*Xe`@Wk|t7*MdD?>@k5SHhzQHrBtUV|W>8av -0o+cW?q8&_v;5Tu{4}+*^n|}N%#E0G4kIfU0O%9nK(-@c@*H*hV*z6%|t9f{Fzj`7xtHH8^@NdlV4?~ -p?(W}IBl1NbFkW)|V`J{o@BZm~Fe!@Sp>pV5cxZS~m>IKq?LSvSYy@teKJ^g -OTRtS;lmMkk@&N}DM+!P|8kJ&Gat9~th$ro^s!`k&~U{$v>zPg*L9Ct4GmeP -;wG!v%5$)k1DU=-Ndve&}1n?bjo&{sFR6OEJ?G_?*(5}uqp0%ooTd55;pY;|4>Kl2V1e6sJFryx5R`Xt@|fghV9skh?kwZ -L(y;KUvntcy_L+>w`&RP!r%;?n3In&wrBm+nw_Sxa@u4#&{%?ebjpuW2VdVrms&XQ;vO^a?kFo0D-CciRQF$r^icaSvQnaZs#7Ru(I}pUd_ie&o@V|=HS9GiIhX9B -)P43jo-U>zM45d~!_yE?~TT1q3tM03#Ct=%KK|uVwvHN46Ctodd!ZyG#N*U}Hx{5|}ecEvQGiiGBP8w_isJ_2YP#d-2=T;y4-ul6;bYvdQX80&Iqs~g -*uZB{3rqdR0)EY?TKc$S^PWq+(Ok>)O=aOBRI1S|3>hKbFlYKp>p=9NGFq?7)6DuN-H|v5zfvNXYIaQ -EcqmB5FM&$;z$hd_B>)qLJL2*m{Wx8$a@(;lrCq6>gJwzP6!bw& -x{COE+eg9sH@3{e=G4AyrVmotv^*K_$ct+ohr6ve6cJqQ#?U}L-(>JMJ>e0bVw7yyNlKhO-pv -)ca{GjOdF`>evM1{UXlFCam8@Ek`@J+6=Op0f$-I*P)bxo@={3W9$!)RN`fifV)Cb>by$1TegI&92J{ -NjB;7ey?jJu^%c!`+J3IFRINO7yAM{&nLfg?k#yGfA8#m=_w^FN1d_>i6$dSG4m7Wc1%{j7G^1vsY|2 -nW#&N;OExLHPa^bdmNcn{>~&KIvxSLg)P`MWP;_QE(QcTH(#n2}?zwe#64d8oD7l4;yHz~zGUaQMA$Y -B};pU3Wb6~jwl}C76r%AYoHiTsD>5O|RnWkdkJ9?7dkuBq6-}&H^A)N9u{>E5Vc|e-JFjFt%1WAuC{g -^v78MV@Rn6mn$p9DT(0B%N#9gG0qdE*C~H-AUw_*VHIboGy>epsa1Df@>VMWY$+C3Lu|M0;TZ=fvdHZ -kzRQo$v!k+`Q_SvkNOje!JWsv8qKxAfbXjOzojj2ob~YD8{7fc$|ljOm|z&{JZJ+#gIIId*KK=;czyV~NR8V);HtG=y`!uO4AKB0&(S;?Dt8Y(JU60#>+nArKF&MK-#LR|h|*Q-J7baM>I+i7eT -{KXBI`H>Ky}*m_F*cbr={S3LGV89#Lbpj7vgTIiWuCKE*X^}NJ*X}+ijThsB?5(YRep4g&l$s|HevRP -Ws?t%heb%$1KYUs}aJ~oK_liLu>Z{t@kF7-Fzy3@Mmj>!N9+<)u*V1Fp$R59$Lem3U?EHQ%dsg;s*k} -)jHK4^s`-F17UJQY6pGdD4VEw)>2=sC9`WEB)}|^d(E`r`mj%8Ng$?7;BCyYXrifne4*9>F^e4uzn8e -bkhlC={QTv0rQ#wJ?kq?yb@0{1m0M?}(} -hHX+0_JhhXL=q(!aomuG})}7x)ciDurUWK)%%bTm~jpZBY)F@@?j6Irm)PcWH+OghBcpq%#UbU4suYw -Ri3<@!+BGUVp_-y!!&H|2d&B_HeMGlR#rc)Z1$)s4-T3Gr@`u*DHgJ>Gj4=eD+DO2Afhpmqoh;ordzZ -4%|z{Nvcqlwu_P;{;zNNGLdu4!mMoyg>HZW5=tf2h!F#GH12=EEoU?{SN(o0|BluXVxlARachSM9{IS -fgVQ>{?R)hQ#>z%>&g!Y6?C>yzn3}xz<%DfJuSz2b+0{?X6nOTQL=M{L(xm{xjta^-n+)&C6L>?R-Q^ -1Mc*f~Nc~?M_+O^(XqottK6o{GGK(tZ$cSr$SfG@w>}$`oCG*rEHq@fek^ufZpM8@)yLi6rV5&g?M`V -lfnmI-u*JjIew^}=-rwEHiKGZLG-})_48q~9~W>W_1=~@?&0*uEk1)+A?Z8;^@OI*tf=t(Puak`(>HsmL&o;34K9pa;S -yHwi7af;m*TpmEEo#u$A&iuAF0G=1R}YAcY)EG8Vz*Uda>$l*2=u)H=_l~e=5_sH#5rw`mL3M8#qs8? -20dBRWz9{31}SMWxKM%5uR>x;Sh2}r@D2Fsf5|X@y(;2l->oMn8p?o;v{|A=UE@NR@aKTgl+ePBVd(M -M&V?<#L)bUSKMRuA{QxmegOvoe`xzQAzwtCy4ky`BO9fhw>7-5=G<4C)%C|9-`1{fLKhW)~9MRG$uTL -#QWU?&D>HrvyEcGDYsu7~Ey^l>vO>|I1(!vhK-+R3N0iDL|HJcygQ7j|VTa6>WN*8)Q>IeBm277JgQs -oMDrWKCfqKL|l0N=M_exXKN?b>gFKdF8KqwB-rjQ*?v-f+nsEU8k$()|S?@zO*(hn`{RbRrB;VYgBV! -QYGIf1o$uSL;&2zB!K*d4)qfK@3k*ri -;eFqYsO6*_AUv|K*d*wJY)Y;t%&~c(w*Q8Lv^jRue%@!?c7IrkHPm8`3p1VWet+Z?#^(!oYq4VSJqfQ|~y$tn^)&>TK--R+h>ShRQ^>b$G~2x+RfK9ewJz{2TY -L%>7MfwX>vCjp!m2RE`2LZe(lHg;W;-1|)N+>(rwFV7q=lPS^e$-u>BX!y -1BOqVAx*}f}G$O!NN4uIo#&5=DQI}jqj^ZQR!VVTstuq-9l-dIp|Ukw;PKx-BSL$u=)*O6K|UN8I+ -PLh_bx?a6m(#y_@m2vXCpoigudoSqq$M`PgGQ)Du3gpBzOpd0?wGVeq@e@&)NzZbYjJyZ8K(Y>}BgqN -lac#PZysWpR`3mOW;0uBF_eXp84b5c*v{_=4#3S>oQRUBbdy4S$eV^Bx1_y@SyMTbwzTvZ#C}n=A$y2 -ON+sq`YrV{)Ve15%SZ#ampOa_XB=SZra5d%rFXYSpqfKu;zIfs+*;5>iha29_|jr-y4&@XG@0Y6IX|t -kuD{62ZPSqJNS7f?zh$}Zfq0qC*n*(tmu(h6W@p3He3JNiW}GPy3L2@<&Bk8qP&{d`34sjiJfi#5oS2Ik*5PHA$>Ra623KEr1Lsfseavzo@(&bR{lqa -+!GVLi_{Sr%#*OGo)e1$wa5yX}!41-Vo);#3@8Grv}2Mqa|60<+XzT{Iq`^l+$nvG|=0(DNxGAjo<11 -VJOcd8)d!bpmeXX9veh7Fx?q>L7HM;xZ4r|-n+8?#P;dqY^T08MnJ$WW@AnCP=fVt^&1UBj3ee&6sL>o8Ob+9j2Uq%kK7$^QAS)nLM -aaE7Y%}wFroGBYZdUceH8i6?Z`hel|h|`Q<(;R9@IZ7gxBx8YXgOom~yr=H@-TVLZ^}w6;n$|R4sK*# -KLq5CpWBlo*MmoG4y1O?tNaCdGt8fcTl82>Jp8aH#@# -_uxnld!Pj^9n&pKv7{!X|r@c5R&@lmQ%>R6AkbDbJd#e;~6oT%a{t+{1^731Be9<~+qU(Ds0*Wf4Lhh -ReFPb*IZ#uSS#q*lp!Mh83x^yGPUv=l6uFWq}Mr#lq-wxIn)SB2jxO|n1HH^v^Sv(jP--a*5?3knFj; -bk9MUM6Nrl0a!_%UdN1z29N@E#vUDmOk^wL)GM#xm*=drieD9dD{Yn(}z@@%wW->)YLc -FBv}w7eQLY%YB_-q{h_dR;~6*V}K%aqbODTOt`KA$JQY{9kzT+qVK0a-!l#0TJzEY<%#LUnn~BRJmk8 -796FQBwXnwEJrJN5l|uKKJ>i?8z{x+>$S;iFSd+-)J5LzYaUV3K%L0#c!T@15dLZdg5}6I!cJ$#Y_z#J -e}9!k9nE8Oo)<;45yS)p&D>5q=^lkv-a(#P?Zd9(p;B`jOhDjMQWGwHCEU6MhBLPK(e#F>VG6QA81pESKC6k -3^pNI&y}RLg!#wJOcIUGQf~4?2iEAQ?Uu9&jXAKdSx~>M`{!oZ|8j5YpZT -8mW$X0gK9L%${(4FoSqWlGOZ-@cmpG_>h9~#e)mA$$?Lcx2JAK$~Iqtvl?H`==SXOV|-S20GQ7+Um%v -UnN_d*s*wn>>4HH>>SMKZ}f$yY*(QDv*t|GgXhq-^ia_MOA<$|(*pl>w=YTxR`|o!EoXY|23;!A`z{G -AA%B=M5x^AgJ){SN`+vz&q*CL?2E`CXF9i%pj(_6*8njl+bb_fQY~y=ID`I_J<4gwAeV3P&@R~b<*EB -;BR)^>ypm;&Zapi9MCg%SA@_rs1eYw;ioh5zX5MmQCUbpH$LOLlS>^=V=k3`9yBJ$?LfKJ4~Hs}93-v6NcQiR|KR -H>3SMK3W-lD5S9zRj-;bP9vk<@y6$IikPy?3I4eVf1~@9Ye0*OQB!D1<$h?9HLbnou6sk|*_ze^qhkaY)j$`2j-NMz-}~SH$T#+ -{S1izkf{(o6%OKJ{Fif?ubc@NT9ZK#n$82|+>1bHNMwXf_PGEnQ{vX($>-KfxR>Qx|x@_tcXaNAWh+t -Sk(j*^i2JH0NiZ}(D?n4qUPxT1>cWY(LtE8D;XD)9j!EHzGemLTZ<@bc|RVM_^+8Y>)EauR2e>BKg-G2%OL!C+Kx~sUs}yL?v7(Gll6cXly(aN -I#RK9poKM*V4Qc!9)8{k{_W7K;uHFDQ_T0JZUed2fa5uj2$^%z7_jX5itVQ$0i3Ec3X=QQtE>^`8 -6&)IlgPP19ya(WVLe*`b1>Cbs{^-5O6Bawb=0w$Yn{!~PIN$fBbgMI_O`qPl~ew=%ZKbH -ReF->gGCiU(vQNCfD%@XvbWcT)WyPn*&c);0_fNY~trvv*15?S+l%mTUSAGAGZieBNbf`?!2VO-ns#K -hx>ic7>@%+;x^ZqTTw^+Ecbt*P4#C3jt?TbJP;>tt+*axV{T)8;W7WLDhCuu(u3eR0T?gW=-G5gynEd -H&G`pvYMG7>!>w}U?5J@xme``)q9C#A+XeUvcg2WO(7VY0Rh9VZ31n0tf@Y37eL!p+5x8w -r0$t-t!PzI+4I7;xX95N1tIg=3<0htT?i={G~nZluB{4@JSF$03c~{k(?oH`@DqC#`i2Prg6H6+_#@qMeD>zOTqu7zxXLc1Gf;&<3?bejaXbMO^>aBZ~h^h4KsFdqABQOC_TL9JB_Hfb$J&js862Ap~g! -M4m5}P?7SAPoI|3aGd~npJA5&1d`@K(@aE(VQ;E>4$V7YFa+n++&@Z -&Ah8$Yy{E}|fAjW_^}Q%j&cG@D#)#=;s{4C?}TxF%Hr_3RA&ncshceH96=F%Q`(ePd}tHC*P5UyTX&# -NcqS*!x~Zt7%!83#uGcWrqgdp_gx<;~XMk%kZGi>< -)vECq-se{-iyI_MdjY!As9v*~PxpOz@dHnqs+K;<$uRbwUve949bakn -2Y;D&HmqFu!ZPn;38wn-?$;}h@izBknerV_wD^A)ziQdb~i_a;ykqH&CyLN1`#h4=BSMFViwF4$#3~! -k<<w?{1SWji~p=+~I3OtHoEfmv7d)DulZu8brnPT*c@#ralTo=vUiSDo; -owwL4SlOiM+jEA)61!=9 -)wYIH=1D|$DLYIt4}243+t=bzfKY0ANWU(7&QUWcesMaR!Ql!=B#OqheOg`>&dt`cd;paD88<3O8a{< -)#?7wYud8sCOYai)@~@~~U%MJExuQ{hVFhcwZk$F=R$S{5ZR9g61*5x<1q9DRMIBe-&9ZkfDN4QJtO6 -B^%w3d&v>_3Yfm9w#1BpjVn4yE_nqf4**eJpoTc-$K=)>7;%k4=AfLF -nA{6zH`>7j0RID(<{vunBp=tUnciLLHgF4VbPDk96Lbo)Z^^$V+{kEZ9t7q4y -ON33V^g{O`CD6#_tp9TJKDgE=JH_)vpV#ddM+t$zw*+TG7Y4SuHrttFxo+C9~pmTu>-YnkP2dhevUB> -=$JoGwzZ7)oDxdpAqy0Ne$;lCeH-jn|U{saE!*)_V)>ud9W8TL$XImHxC>VNuS{I&HM5Y7#}99lE9b; -v`&$}_~6lG)^u=V#wb7N-VzD2d#y(1iYu#h<_x)}$E9q<*4S^WcwiBT*qsUtAy4`P_E&AxO{i9<**vs -hjONG70hg>=*xA?0rHtSo&lzyWQ7+b$FJQf5i)^*z3XGP*g#ztXPO}$-->M@Y!B9LPsrEMrcdCtl(#7 -!Z#*L>aoB3ZIeHHCdc)cjgC&VcS6#!BoAaK+fg6;BT&MJL})`*lvm&+h8_k|lnw0u-iQ8#{p)qL+tAd -%E-!R*!J5n+sd($qgnNjqQO7ASCnNV2=ybm75UzB#AsqL3BKc;C)kfLtx;@( -;`?UEALMtBUtcb8c~DCM0u%!j0000805+CpNlQtjWitT)07wD=01p5F000000 -00000HlFQ4*&pgX>c!JUu|J&ZeL$6aCuNm0Rj{Q6aWAK2mm&gW=Vn=i}gbd004$A000sI0000000000 -005+csSp4FaA|NaVqtS-V{dJ3VQyqDaCuNm0Rj{Q6aWAK2mm&gW=X57{yU}&007}A000pH000000000 -0005+c93B7waA|NaVqtS-aA9(DWpXZXc~DCM0u%!j0000805+CpNlTeBA$}4709rr*01*HH00000000 -000HlHODgXd*X>c!NZDen7bZKvHb1rasP)h*<6ay3h000O8HkM{dn{PY_m?HoHt9<|f4*&oF0000000 -000q=BA2003}la4%$UcW!KNVPr0Fc~DCM0u%!j0000805+CpNxpiPn7ax90J$Fk01*HH00000000000 -HlFqVE_PdX>c!Pcw=R7bZKvHb1rasP)h*<6ay3h000O8HkM{dHuxp~U@8Cr4x9i03;+NC0000000000 -q=8Lr003}la4%_YWMz0RaCuNm0Rj{Q6aWAK2mm&gW=XdXcbSn0008bZKvHb1rasP)h*<6ay3h000O8HkM{d`Y438OAY`4?y-E^v8JO928D0~7!N00;m!mS#zXiI$3p5dZ*SR{#JO00000000000001_fsWe -%0B~t=FJE?LZe(wAFJW+SWNC79E^v8JO928D0~7!N00;m!mS#zj!N9H{BLD!+l>h)00000000000000 -1_flBQF0B~t=FJE?LZe(wAFJx(RbaHPlaCuNm0Rj{Q6aWAK2mm&gW=Y+|rUhv`001yK0RR{P0000000 -000005+coe%*4aA|NaUv_0~WN&gWX>eg=WO8M5b1rasP)h*<6ay3h000O8HkM{d)>0WG69xbP{to~E8 -UO$Q0000000000q=7t40RV7ma4%nWWo~3|axZUkWMy(?WMpY$bS`jtP)h*<6ay3h000O8HkM{d!60;w ->d*iHt7`)Q7ytkO0000000000q=AQ00RV7ma4%nWWo~3|axZXsaA9(DX>MmOaCuNm0Rj{Q6aWAK2mm& -gW=UWk2aRR{000F8000;O0000000000005+cry~LYaA|NaUv_0~WN&gWa%C-cWo~3|axQRrP)h*<6ay -3h000O8HkM{dA$zv0swGna4%nWWo~3|axZdabaHuVZf7oVc~ -DCM0u%!j0000805+CpNtswpT1XuL0Fre801^NI00000000000HlFOF9HB?X>c!Jc4cm4Z*nhlX?QMhc -~DCM0u%!j0000805+CpNdQ)?I<5c!04@Ol03-ka00000000000HlGxOacIKX>c!Jc4cm4Z*nhVVPj}z -V{dMBa&K%eUtei%X>?y-E^v8JO928D0~7!N00;m!mS#!XR7Rx(0ssIg1pojb00000000000001_fv-& -h0B~t=FJE?LZe(wAFJob2Xk}w>Zgg^QY%gD9ZDcNRc~DCM0u%!j0000805+CpNl@mgEK~&m000sI03! -eZ00000000000HlHNPXYjNX>c!Jc4cm4Z*nhVVPj}zV{dMBa&K%eVPs)&bY*fbaCuNm0Rj{Q6aWAK2m -m&gW=RsN#_%iw000&P001EX0000000000005+cg;fFoaA|NaUv_0~WN&gWV_{=xWn*t{baHQOFJob2X -k{*Nc~DCM0u%!j0000805+CpNqG`W@Xi1L0H6T?03rYY00000000000HlHNRssNUX>c!Jc4cm4Z*nhV -VPj}zV{dMBa&K%eV{dJ6VRSBVc~DCM0u%!j0000805+CpNylKh-Utr>0N^qJ044wc00000000000HlH -bR{{WVX>c!Jc4cm4Z*nhVVPj}zV{dMBa&K%eV{dMBa&K&GWpXZXc~DCM0u%!j0000805+CpNo*!GeE9 -c!Jc4cm4Z*nhVVPj}zV{dMBa&K%eW@&6?cXDBHaAk5XaC -uNm0Rj{Q6aWAK2mm&gW=RfI9{j-t001oz001Tc0000000000005+ch-(4>aA|NaUv_0~WN&gWV_{=xW -n*t{baHQOFKA_Ta%ppPX=8IPaCuNm0Rj{Q6aWAK2mm&gW=RrZfVe0K004*?001Qb0000000000005+c -jd211aA|NaUv_0~WN&gWV_{=xWn*t{baHQOFLPybX<=+>dSxzfc~DCM0u%!j0000805+CpNsD{MLc9O -~0P+C<03!eZ00000000000HlHVc>(}%X>c!Jc4cm4Z*nhVVPj}zV{dMBa&K%ecXDBHaAk5XaCuNm0Rj -{Q6aWAK2mm&gW=WnMR{YBV0040T001ih0000000000005+c^?CvTaA|NaUv_0~WN&gWV_{=xWn*t{ba -HQOFJob2Xk~LRUtei%X>?y-E^v8JO928D0~7!N00;m!mS#!8rw0F91pold4FCWw00000000000001_f -eCy90B~t=FJE?LZe(wAFJob2Xk}w>Zgg^QY%gPBV`yb_FJ@_MWnW`qV`ybAaCuNm0Rj{Q6aWAK2mm&g -W=YeC##E*O007Mc001rk0000000000005+cuYm#paA|NaUv_0~WN&gWV_{=xWn*t{baHQOFJob2Xk~L -Ra%E&`b6;a&V`ybAaCuNm0Rj{Q6aWAK2mm&gW=UAoX*sh1008g+001BW0000000000005+cp@RYdaA| -NaUv_0~WN&gWV{dG4a$#*@FJE72ZfSI1UoLQYP)h*<6ay3h000O8HkM{dJ{6~5ivs`v+z9{x8~^|S00 -00000000q=Axz0swGna4%nWWo~3|axY_VY;SU5ZDB8AZgXiaaCuNm0Rj{Q6aWAK2mm&gW=Y3mz}avL0 -06`#001KZ0000000000005+cSBL@taA|NaUv_0~WN&gWV{dG4a$#*@FJW$TX>@OQX>KzzE^v8JO928D -0~7!N00;m!mS#zP*4QtF0ssJg2LJ#f00000000000001_fe4cV0B~t=FJE?LZe(wAFJo_PZ*pO6VJ~T -JX>@5}Y-w|4E^v8JO928D0~7!N00;m!mS#x}ZK6F)1pol`6aWAn00000000000001_fz6cy0B~t=FJE -?LZe(wAFJo_PZ*pO6VJ~-SZZk42aCuNm0Rj{Q6aWAK2mm&gW=VH@XCdMR005F00018V000000000000 -5+cRGR_-aA|NaUv_0~WN&gWV{dG4a$#*@FL!BfbY*gFE^v8JO928D0~7!N00;m!mS#yNoKcru0RR9+0 -ssIX00000000000001_fpeh(0B~t=FJE?LZe(wAFJx(RbZlv2FJE72ZfSI1UoLQYP)h*<6ay3h000O8 -HkM{dUkP(yXes~zVV?j19RL6T0000000000q=5~i0swGna4%nWWo~3|axY|Qb98KJVlQKFZE#_9E^v8 -JO928D0~7!N00;m!mS#yh3d6j=E&u=s!TbZ>HVE^v8JO928D0~7!N00;m!mS#zYm$jR05C8z$ -IRF4300000000000001_feJ+f0B~t=FJE?LZe(wAFJx(RbZlv2FKuCNX=Y_}bS`jtP)h*<6ay3h000O -8HkM{dJrS+~jRyb#iWmR@9smFU0000000000q=BOV0000000000q=9^00|0Poa4%nWWo~3|axY|Qb9 -8KJVlQoFbYWy+bYU)Vc~DCM0u%!j0000805+CpNyxWFn5YW?01heu03ZMW00000000000HlF%fdc?=X ->c!Jc4cm4Z*nhWX>)XJX<{#OWpi(Ja${w4E^v8JO928D0~7!N00;m!mS#!1|LfZd5&!^rI{*M400000 -000000001_fmV$J0B~t=FJE?LZe(wAFJx(RbZlv2FLPsZX>fFNE^v8JO928D0~7!N00;m!mS#!yn2-# --ssI20Tmb+Z00000000000001_ft;TM0B~t=FJE?LZe(wAFJx(RbZlv2FLX09E@gOSP)h*<6ay3h000 -O8HkM{dp5nz>TetuK0Db`g8vp0|fwZX>c!Jc4cm4Z*nhWX>)XJX -<{#RbZKlZaCuNm0Rj{Q6aWAK2mm&gW=Wv^1y-*a005s{0015U0000000000005+c6+Q(3aA|NaUv_0~ -WN&gWWNCABY-wUIc4cyNX>V>WaCuNm0Rj{Q6aWAK2mm&gW=Yt60Et?r0000^0RS5S0000000000005+ -c09XY8aA|NaUv_0~WN&gWWNCABY-wUIcQZ0BWq4&!O928D0~7!N00;m!mS#y_%uDYLwEzGBZ~*`t000 -00000000001_fs_6P0B~t=FJE?LZe(wAFJx(RbZlv2FLyRHE@gOSP)h*<6ay3h000O8HkM{dU(}$f4J -7~o%a{NF8~^|S0000000000q=DJ91^{qra4%nWWo~3|axY|Qb98KJVlQ`SWo2wGaCuNm0Rj{Q6aWAK2 -mm&gW=SLOXZ*te000sJ001cf0000000000005+cAlU{0aA|NaUv_0~WN&gWWNCABY-wUIUt(cnYjAIJ -bT40DX>MtBUtcb8c~DCM0u%!j0000805+CpNw118WzGQr0Luda03`qb00000000000HlE`+6Dk{X>c! -Jc4cm4Z*nhWX>)XJX<{#5Vqs%zaBp&SFKuaaV=i!cP)h*<6ay3h000O8HkM{d;$l6}M;rhEFJu4!Cjb -Bd0000000000q=7r!1^{qra4%nWWo~3|axY|Qb98KJVlQ7}VPk7>Z*p`mb7*yRX>2ZVc~DCM0u%!j00 -00805+CpNpBVat@Z%`067W(04o3h00000000000HlG(_yz!QX>c!Jc4cm4Z*nhWX>)XJX<{#5Vqs%za -Bp&SFLQZwV{dL|X=g5DW@k`K0Rj{Q6aWAK2mm&gW=S>=Qr74m002vA001fg0000000000005+c2Kxp8 -aA|NaUv_0~WN&gWWNCABY-wUIUt(cnYjAIJbT4yxb7OCAW@%?GaCuNm0Rj{Q6aWAK2mm&gW=TXsA46h -U007i!0RSif0000000000005+cHW~*2aA|NaUv_0~WN&gWWNCABY-wUIUt(cnYjAIJbT4#aa%O34WiD -`eP)h*<6ay3h000O8HkM{dqMMnVuK@r63bYEXCaCuNm0Rj{Q6aWAK2mm&gW=Yj}4&o&g002Qj001EX0000000000005+c%XJ3;a -A|NaUv_0~WN&gWXmo9CHEd~OFJEbBVRU79ZEP-Zc~DCM0u%!j0000805+CpNgfPsHCrD502F@!03-ka -00000000000HlF0iU$C2X>c!Jc4cm4Z*nhabZu-kY-wUIUukY|b#!xda%Ev{E^v8JO928D0~7!N00;m -!mS#!BMrszn8~^|$D**r^00000000000001_fy}7~0B~t=FJE?LZe(wAFKBdaY&C3YVlQ8GZ);_4X?k -UHE^v8JO928D0~7!N00;m!mS#yN4R0B~t=FJE?LZe(wAFK -BdaY&C3YVlQ8HbZKmJE^v8JO928D0~7!N00;m!mS#!Fq<#JgNB{u4MF9XI00000000000001_fkVv)0 -B~t=FJE?LZe(wAFKBdaY&C3YVlQKFZgX^DZgg`laCuNm0Rj{Q6aWAK2mm&gW=Uh%cihZE006Sd0RSTa -0000000000005+cixmg}aA|NaUv_0~WN&gWXmo9CHEd~OFKBdaY&CFUa&u*JE^v8JO928D0~7!N00;m -!mS#!Z$GbFh4*&pRHvj-400000000000001_ftXkb0B~t=FJE?LZe(wAFKBdaY&C3YVlQ)La%o{~X?k -UHE^v8JO928D0~7!N00;m!mS#!s)2|Pv0000X0RR9d00000000000001_flFuz0B~t=FJE?LZe(wAFK -BdaY&C3YVlQ8Ga%p8RUtei%X>?y-E^v8JO928D0~7!N00;m!mS#!Jl|Gi70RRBg0{{Rc00000000000 -001_fi-Ce0B~t=FJE?LZe(wAFKBdaY&C3YVlQ8Ga%p8RUt(c%WiD`eP)h*<6ay3h000O8HkM{dBvqZM -rU3u|ngjp001BW0000000000005+c`fLaQaA|NaUv_0~WN&gWXmo9CHEd -~OFJE+WX=N{Pc`k5yP)h*<6ay3h000O8HkM{d000000ssI200000C;$Ke0000000000q=8j$2mo+ta4 -%nWWo~3|axZ9fZEQ7cX<{#CX>4?5a&s?VUukY>bYEXCaCuNm0Rj{Q6aWAK2mm&gW=U?)e1IPT0034?5a&s?fZfa#?bYE>{bYWj(Xkl`5WpplZc~ -DCM0u%!j0000805+CpNqy_mwf6!503{6o03-ka00000000000HlHMbO-=&X>c!Jc4cm4Z*nhabZu-kY --wUIW@&76WpZ;bY-w(EE^v8JO928D0~7!N00;m!mS#yI?VTja1^@siDF6U000000000000001_fhc$g -0B~t=FJE?LZe(wAFKBdaY&C3YVlQTCY;c!Jc4cm4Z*nhabZu-kY-wUIW@&76WpZ;bb75|2bZL -5JaxQRrP)h*<6ay3h000O8HkM{dpN?v+&;bAda|8eYDgXcg0000000000q=9jj2mo+ta4%nWWo~3|ax -Z9fZEQ7cX<{#CX>4?5a&s?tXlZn1b8ul}WiD`eP)h*<6ay3h000O8HkM{df-++yj{pDw&;S4cEdT%j0 -000000000q=AW-2mo+ta4%nWWo~3|axZ9fZEQ7cX<{#Qa%E*?y-E^v8JO928D -0~7!N00;m!mS#yYC&#`K0ssIL1^@sn00000000000001_fn%5m0B~t=FJE?LZe(wAFKBdaY&C3YVlQ- -ZWo2PxVQ_S1a&s?dWo~n5X)bViP)h*<6ay3h000O8HkM{dMtBUtcb8c~DCM0u%!j0000805+CpNkqz4s&Wnh0CqM204V?f00000000000HlFSq6h$RX>c!Jc4cm4 -Z*nhabZu-kY-wUIbaG{7Vs&Y3WMy)5FJfVHWiD`eP)h*<6ay3h000O8HkM{d2MtBUtcb8c~DCM0u%!j0000805+CpNlhb|z@h{I080}904M+e00000000000HlHN*9ZV`X>c!Jc4c -m4Z*nhabZu-kY-wUIbaG{7cVTR6WpZ;bVqtS-E^v8JO928D0~7!N00;m!mS#z1if^g+0RRAn1poji00 -000000000001_f!N##0B~t=FJE?LZe(wAFKBdaY&C3YVlQ-ZWo36^Y-?q5b1!6XZ7y(mP)h*<6ay3h0 -00O8HkM{dBEb+b5(NMNOcDS9DF6Tf0000000000q=6LQ2mo+ta4%nWWo~3|axZ9fZEQ7cX<{#Qa%E+A -VQgz<{p00000000000001_fg$S%0B~t=FJE?LZe(wAFKBdaY&C3YVlQ-ZWo36^Y-?q5b1!FQZg -Xg9E^v8JO928D0~7!N00;m!mS#!wnInLL3IG6`Bme*)00000000000001_fidm~0B~t=FJE?LZe(wAF -KlmPYi4O|WiMY}X>MtBUtcb8c~DCM0u%!j0000805+CpNh{v+Cn5s?09*c!Jc4cm4Z*nheZ)0m_X>4ULY-w(5Y;R+0W@&6?E^v8JO928D0~7!N00;m!mS#yICcJ)g0{{R -R3;+Nn00000000000001_fmQqn0B~t=FJE?LZe(wAFKlmPYi4O|WiM@OWNC72Z)0m_X>4UKaCuNm0Rj -{Q6aWAK2mm&gW=SACaxEGN007Dt001KZ0000000000005+c3jhfKaA|NaUv_0~WN&gWY;R+0W@&6?FK -}sOY;R+0W@&6?E^v8JO928D0~7!N00;m!mS#!2h48%z1poko6#xJx00000000000001_fo2E^0B~t=F -JE?LZe(wAFKlmPYi4O|WiNAaY-x05Y;R+0W@&6?E^v8JO928D0~7!N00;m!mS#zjBMI3(0{{Rx3IG5n -00000000000001_fwK(>0B~t=FJE?LZe(wAFKlmPYi4O|WiNAiZER_7Yiw_0Yi4O|WiD`eP)h*<6ay3 -h000O8HkM{dJSkYli2(or&;kGeA^-pY0000000000q=7XN2>@_ua4%nWWo~3|axZXUV{2h&X>MmPUte -KjZ*_EEUoLQYP)h*<6ay3h000O8HkM{dfp(V47y$qP0RjL3ApigX0000000000q=Eht2>@_ua4%nWWo -~3|axZXUV{2h&X>MmPUtei%X>?y-E^v8JO928D0~7!N00;m!mS#!jX-ebE0RRA80{{RZ00000000000 -001_fmIX<0B~t=FJE?LZe(wAFK}UFYhh<;Zf7rFV{dJ6VRSBVc~DCM0u%!j0000805+CpN&5Yy5_16n -0Eh(u03-ka00000000000HlFl76|}wX>c!Jc4cm4Z*nhiVPk7yXK8L{FJE(Xa&=>Lb#i5ME^v8JO928 -D0~7!N00;m!mS#zPEGt(93IG5mAOHX$00000000000001_ffE=B0B~t=FJE?LZe(wAFK}UFYhh<;Zf7 -rTVRCC_a&sc!Jc4 -cm4Z*nhiVPk7yXK8L{FLGsZb!l>CZDnqBb1rasP)h*<6ay3h000O8HkM{dd_{wdj~D;|d2IjyBLDyZ0 -000000000q=67A2>@_ua4%nWWo~3|axZXUV{2h&X>MmPb8uy2X=Z6c!Jc4cm4Z*nhiVPk7yXK8L{FLiWjY;!Jfc~DC -M0u%!j0000805+CpNe=JEpc!Jc4cm4Z*nhiVPk7yXK8 -L{FLq^eb7^mGE^v8JO928D0~7!N00;m!mS#!GEkGtsZ2$m1lK}uF00000000000001_fy__|0B~t=FJ -E?LZe(wAFK}yTUvg!0Z*_8GWpgiIUukY>bYEXCaCuNm0Rj{Q6aWAK2mm&gW=Z;5ag34#007Sm001BW0 -000000000005+cT)qhaaA|NaUv_0~WN&gWaB^>Fa%FRKFJE72ZfSI1UoLQYP)h*<6ay3h000O8HkM{d -55q@_ua4%nWWo~3|axZXlZ)b94b8|0ZVR9~Tc~DCM0u%! -j0000805+CpNh*KwFb@L&0Nw=v03QGV00000000000HlGL#|Z#%X>c!Jc4cm4Z*nhia&KpHWpi^cV{d -hCbY*fbaCuNm0Rj{Q6aWAK2mm&gW=YQM4^I07001Tn0018V0000000000005+c<;n>FaA|NaUv_0~WN -&gWaB^>Fa%FRKFKA_KaAk6HE^v8JO928D0~7!N00;m!mS#zJ2vU3K0ssIa1poja00000000000001_f -gR5Y0B~t=FJE?LZe(wAFK}{iXL4n8b1!pnX>M+1axQRrP)h*<6ay3h000O8HkM{dX({S?U<3dF76||V -AOHXW0000000000q=7=x2>@_ua4%nWWo~3|axZdaadl;LbaO9XUukY>bYEXCaCuNm0Rj{Q6aWAK2mm& -gW=VDXxhYT+0010K001BW0000000000005+c;MNHMaA|NaUv_0~WN&gWa%FLKWpi|MFJWY1aCBvIb1r -asP)h*<6ay3h000O8HkM{d$g9t&!UX^Tq80!E8vp@_ua4%nWWo~3|axZdaad -l;LbaO9ZaA_`Zc~DCM0u%!j0000805+CpNnB7pCt3*r0Pi0F02}}S00000000000HlFw@Cg8LX>c!Jc -4cm4Z*nhkWpQ<7b98erVRdw9E^v8JO928D0~7!N00;m!mS#zrDnMzG%K`w1LInUH00000000000001_ -f&KXj0B~t=FJE?LZe(wAFLGsZb!BsOb1!3IV`Xx5E^uXSP)h*<6ay3h000O8HkM{dE+307XaN8KaRLA -U9RL6T0000000000q=C!D3;=Lxa4%nWWo~3|axZdaadl;LbaO9bWpZ?LE^v8JO928D0~7!N00;m!mS# -z}SqnXx0ssJ)1^@sa00000000000001_fo{hP0B~t=FJE?LZe(wAFLGsZb!BsOb1!3WZE#_9E^v8JO9 -28D0~7!N00;m!mS#y*v#Y{=6951pM*sjH00000000000001_fkVm+0B~t=FJE?LZe(wAFLGsZb!BsOb -1!3WZ)<5~b1rasP)h*<6ay3h000O8HkM{dX>g)+)dK(kEDHbtA^-pY0000000000q=Eh23;=Lxa4%nW -Wo~3|axZdaadl;LbaO9dcw=R7bZKvHb1rasP)h*<6ay3h000O8HkM{ddD^4OdI10c{{jF29RL6T0000 -000000q=6FR3;=Lxa4%nWWo~3|axZdaadl;LbaO9gZ*OaJE^v8JO928D0~7!N00;m!mS#zucispl00000000000001_frJJP0B~t=FJE?LZe(wAFLGsZb!BsOb1!pcb8~5LZ -gVbhc~DCM0u%!j0000805+CpN!(>A^ZNt<0Q3w103-ka00000000000HlF|A`Jj=X>c!Jc4cm4Z*nhk -WpQ<7b98erb97;Jb#q^1Z)9b2E^v8JO928D0~7!N00;m!mS#!jAj%JX1ONcU3jhEj00000000000001 -_fxagV0B~t=FJE?LZe(wAFLGsZb!BsOb1!pra&=>Lb#i5ME^v8JO928D0~7!N00;m!mS#x{=(3^?9{> -PjUH||c00000000000001_fqE4;YaCuNm0Rj{Q6aWAK2m -m&gW=WnnC&jq}003wO001cf0000000000005+c!%GbSaA|NaUv_0~WN&gWa%FLKWpi|MFK}UFYhh<)b -1z?CX>MtBUtcb8c~DCM0u%!j0000805+CpNpWmS{EY$t01pKK05Jdn00000000000HlG#P7MHXX>c!J -c4cm4Z*nhkWpQ<7b98eraA9L>VP|D?FJow7a%5$6FJE72ZfSI1UoLQYP)h*<6ay3h000O8HkM{dah$& -~caZ=9qCx=xF#rGn0000000000q=BDN4FGUya4%nWWo~3|axZdaadl;LbaO9oVPk7yXJvCQV`yP=WMy -WppoMX=gQXa&KZ~axQRrP)h*<6ay3h000O8HkM{dd -9qhoGXwwt$O!-dGXMYp0000000000q=C`q4FGUya4%nWWo~3|axZdaadl;LbaO9oVPk7yXJvCQV`yP= -WMykR; -KX>c!Jc4cm4Z*nhkWpQ<7b98eraA9L>VP|D?FJow7a%5$6FJow7a%5?9baH88b#!TOZZ2?nP)h*<6ay -3h000O8HkM{dIfAWpXZXc~DCM0u%!j0000805+CpN%$G)gIfat0B#2W0 -5$*s00000000000HlG3`3(SYX>c!Jc4cm4Z*nhkWpQ<7b98eraA9L>VP|D?FJow7a%5$6FJow7a&u*L -aB^>AWpXZXc~DCM0u%!j0000805+CpNx<0c!Jc4cm -4Z*nhkWpQ<7b98eraA9L>VP|D?FJow7a%5$6FJo_HX>Mn8bYXO5ZDC_*X>MgMaCuNm0Rj{Q6aWAK2mm -&gW=SCX-8oSL004yq001ul0000000000005+cZvhSfaA|NaUv_0~WN&gWa%FLKWpi|MFK}UFYhh<)b1 -!3PVRB?;bT4CXZE#_9E^v8JO928D0~7!N00;m!mS#!(Rlv`-0ssIv1pojt00000000000001_fei!>0 -B~t=FJE?LZe(wAFLGsZb!BsOb1!gVV{2h&WpgiMXkl_>WppoNZ*FsRVQzGDE^v8JO928D0~7!N00;m! -mS#z-1}4T#0{{T_1^@sw00000000000001_ffEM~0B~t=FJE?LZe(wAFLGsZb!BsOb1!gVV{2h&Wpgi -MXkl_>WppoNa5*$NaB^>AWpXZXc~DCM0u%!j0000805+CpNv18*IdKF40CNlg05Sjo00000000000Hl -Gj3Jw5pX>c!Jc4cm4Z*nhkWpQ<7b98eraA9L>VP|D?FJow7a%5$6FJ*IMaB^>AWpXZXc~DCM0u%!j00 -00805+CpN!CdH%*h1+0G}QJ04@Lk00000000000HlF(4-NouX>c!Jc4cm4Z*nhkWpQ<7b98eraA9L>V -P|D?FJow7a%5$6FJ*IMb8Rkgc~DCM0u%!j0000805+CpNvwRlMGFN00A3CN05kvq00000000000HlG3 -6%GJ!X>c!Jc4cm4Z*nhkWpQ<7b98eraA9L>VP|D?FJow7a%5$6FJ*OOYH)CJZ(?O~E^v8JO928D0~7! -N00;m!mS#yRWppoPbz^ICW^!e5E^v8JO928D0~7!N00;m!mS#x>v&Q5Z0{{Sv1^@sw000000000000 -01_f#Yrt0B~t=FJE?LZe(wAFLGsZb!BsOb1!gVV{2h&WpgiMXkl_>WppoPbz^ICaB^>AWpXZXc~DCM0 -u%!j0000805+CpNhMFC6yHDq062&M05Sjo00000000000HlFVat;7+X>c!Jc4cm4Z*nhkWpQ<7b98er -aA9L>VP|D?FJow7a%5$6FJ*OOba!TQWpOTWc~DCM0u%!j0000805+CpNiCu{R2c&R0E`9z05kvq0000 -0000000HlF^vkm}oX>c!Jc4cm4Z*nhkWpQ<7b98eraA9L>VP|D?FJow7a%5$6FJ*OOba!xaZ(?O~E^v -8JO928D0~7!N00;m!mS#ygjr0xcLI41(i~s;L00000000000001_f#|jl0B~t=FJE?LZe(wAFLGsZb! -BsOb1!gVV{2h&WpgiMXkl_>WppoRVlp!^GG=mRaV~IqP)h*<6ay3h000O8HkM{dAGO=|8Up|Tkp=(&H -2?qr0000000000q=6**4ghdza4%nWWo~3|axZdaadl;LbaO9oVPk7yXJvCQV`yP=WMyWppoSWnyw=cW`oVVr6nJaCuNm0Rj{Q6aWAK2mm&gW=WT?nhw -5J007gt001xm0000000000005+cPY@3PaA|NaUv_0~WN&gWa%FLKWpi|MFK}UFYhh<)b1!3PVRB?;bT -4XYb7pd7aV~IqP)h*<6ay3h000O8HkM{d8-?V^OA`P9luG~rF8}}l0000000000q=8#!4*+m!a4%nWW -o~3|axZdaadl;LbaO9oVPk7yXJvCQV`yP=WMyc!Jc4cm4Z*nhkWpQ<7b98eraA9L>VP|D?FJow7a%5$6FKl6 -MXJU11XJK+_VQy`2WMynFaCuNm0Rj{Q6aWAK2mm&gW=Wi$w`2Va002Ej0024w0000000000005+cScV -S(aA|NaUv_0~WN&gWa%FLKWpi|MFK}UFYhh<)b1!3PVRB?;bT4dSZf9e8a%pUAX=80~WMynFaCuNm0R -j{Q6aWAK2mm&gW=RntLmg!a002}m001`t0000000000005+ct&$G_aA|NaUv_0~WN&gWa%FLKWpi|MF -K}UFYhh<)b1!3PVRB?;bT4dSZf9q5Wo2t^Z)9a`E^v8JO928D0~7!N00;m!mS#yGqI(LS2><{#EC2vF -00000000000001_foGf#0B~t=FJE?LZe(wAFLGsZb!BsOb1!gVV{2h&WpgiMXkl_>WppoWVQyz=Wnyw -=cWrNEWo#~Rc~DCM0u%!j0000805+CpNl>?%e#Z&`0QfKf06PEx00000000000HlFirVjvcX>c!Jc4c -m4Z*nhkWpQ<7b98eraA9L>VP|D?FJow7a%5$6FKl6MXJ~b9XJK+_VQy`2WMynFaCuNm0Rj{Q6aWAK2m -m&gW=Tsue_iDY000Xt001@s0000000000005+cd$A7yaA|NaUv_0~WN&gWa%FLKWpi|MFK}UFYhh<)b -1!3PVRB?;bT4dSZfA68VQFn|WMynFaCuNm0Rj{Q6aWAK2mm&gW=Z9ms+oHR003|l001=r0000000000 -005+cw7d@haA|NaUv_0~WN&gWa%FLKWpi|MFK}UFYhh<)b1!3PVRB?;bT4dSbZKreaB^>AWpXZXc~DC -M0u%!j0000805+CpNu``|bDabL0K^Oc05|{u00000000000HlF}!w&#(X>c!Jc4cm4Z*nhkWpQ<7b98 -eraA9L>VP|D?FJow7a%5$6FKuFDXkl`5Wpr?IZ(?O~E^v8JO928D0~7!N00;m!mS#!LjE_560{{T82L -J##00000000000001_fpy3a0B~t=FJE?LZe(wAFLGsZb!BsOb1!gVV{2h&WpgiMXkl_>WppoXVqa -&L8TaB^>AWpXZXc~DCM0u%!j0000805+CpN%7@pnoJA;0ESEe051Rl00000000000HlE=%ntx?X>c!J -c4cm4Z*nhkWpQ<7b98eraA9L>VP|D?FJow7a%5$6FKuFDb8~GjaCuNm0Rj{Q6aWAK2mm&gW=Z-Q6}Wl -^006lX001}u0000000000005+cz1R-`aA|NaUv_0~WN&gWa%FLKWpi|MFK}UFYhh<)b1!3PVRB?;bT4 -yaV`yP=b7gdJa&KZ~axQRrP)h*<6ay3h000O8HkM{dq}=7dBm@8e+YA5zH~;_u0000000000q=Aj!4* -+m!a4%nWWo~3|axZdaadl;LbaO9oVPk7yXJvCQV`yP=WMy)LLZ(?O~E^v8JO928D0~7!N00;m!mS#zWppofZfSO9a&uv9WMy<^V{~ -tFE^v8JO928D0~7!N00;m!mS#y``ygu`1ONbB3IG5z00000000000001_fdugn0B~t=FJE?LZe(wAFL -GsZb!BsOb1!gVV{2h&WpgiMXkl_>WppofbY?hka&KZ~axQRrP)h*<6ay3h000O8HkM{d&d{T~b_4(bB -ntolF#rGn0000000000q=9qw4*+m!a4%nWWo~3|axZdaadl;LbaO9oVPk7yXJvCQb#iQMX<{=kUtei% -X>?y-E^v8JO928D0~7!N00;m!mS#x@Xc0Bo3;+N*DF6U900000000000001_fjIgP0B~t=FJE?LZe(w -AFLGsZb!BsOb1!gVV{2h&Wpgiea%^mAVlyvaV{dG1Wn*+{Z*FrgaCuNm0Rj{Q6aWAK2mm&gW=R2F4Gf -wN003bv001)p0000000000005+cV+RlbaA|NaUv_0~WN&gWa%FLKWpi|MFK}UFYhh<)b1!vrY;0*_Gc -RLrZf<2`bZKvHE^v8JO928D0~7!N00;m!mS#y6e_NEJBme+6g8%?G00000000000001_fld|>0B~t=F -JE?LZe(wAFLGsZb!BsOb1!gVV{2h&Wpgiea%^mAVlyveZ*Fd7V{~b6Zg6jJY%XwlP)h*<6ay3h000O8 -HkM{d6r)PD76$+T-xUA=GXMYp0000000000q=7~|5CCv#a4%nWWo~3|axZdaadl;LbaO9oVPk7yXJvC -Qb#iQMX<{=kWq4y{aCB*JZgVbhc~DCM0u%!j0000805+CpN!PCU=_v;Q04o;&051Rl00000000000Hl -GfLJ$COX>c!Jc4cm4Z*nhkWpQ<7b98eraA9L>VP|D?FLiQkY-wUMFJ@_FY-DpTaCuNm0Rj{Q6aWAK2m -m&gW=Wm-upv(a000mP001!n0000000000005+cBT5heaA|NaUv_0~WN&gWa%FLKWpi|MFK}UFYhh<)b -1!vrY;0*_GcRUoY-Mn7b963nc~DCM0u%!j0000805+CpNjDCd5N8hn03c!Jc4cm4Z*nhkWpQ<7b98eraA9L>VP|D?FLiQkY-wUMFK};fY;9p~VP|D>E^v8JO928D0~7! -N00;m!mS#zPGjy()2LJ$M7XSb-00000000000001_fqGpK0B~t=FJE?LZe(wAFLGsZb!BsOb1!gVV{2 -h&Wpgiea%^mAVlyvtWpQ<7b963nc~DCM0u%!j0000805+CpNyj{$K^+tT0Jlg005Jdn00000000000H -lFnWe@;xX>c!Jc4cm4Z*nhkWpQ<7b98eraA9L>VP|D?FLiQkY-wUMFLGsbaBpsNWiD`eP)h*<6ay3h0 -00O8HkM{d000000ssI200000IRF3v0000000000q=C$N5CCv#a4%nWWo~3|axZdaadl;LbaO9oVPk7y -XJvCQb#iQMX<{=kV{dMBa%o~OUtei%X>?y-E^v8JO928D0~7!N00;m!mS#!5n4CII2><{A9{>P40000 -0000000001_fhBqn0B~t=FJE?LZe(wAFLGsZb!BsOb1!gVV{2h&Wpgiea%^mAVlyveZ*FvQX<{#7aBy -XAXK8L_E^v8JO928D0~7!N00;m!mS#!V5}&rK1polE5dZ)=00000000000001_fy#pr0B~t=FJE?LZe -(wAFLGsZb!BsOb1!gVV{2h&Wpgiea%^mAVlyveZ*FvQX<{#KbZl*KZ*OcaaCuNm0Rj{Q6aWAK2mm&gW -=SLDzYK8>0006m0024w0000000000005+c%!v>HaA|NaUv_0~WN&gWa%FLKWpi|MFK}UFYhh<)b1!vr -Y;0*_GcRLrZgg^KVlQxcZ*XO9b8~DiaCuNm0Rj{Q6aWAK2mm&gW=R^~ig~;S001Nw001@s000000000 -0005+cm6#9!aA|NaUv_0~WN&gWa%FLKWpi|MFK}UFYhh<)b1!vrY;0*_GcRLrZgg^KVlQ)VV{3CRaCu -Nm0Rj{Q6aWAK2mm&gW=W4-pnOmO003bYEXCaCuNm0Rj{Q6aWAK2mm&gW=TfDrf2X90 -08GA002G!0000000000005+cO`s3}aA|NaUv_0~WN&gWa%FLKWpi|MFK}UFYhh<)b1!vrY;0*_GcRyq -V{2h&WpgiYa%5$4Wn^DuX=8LQaCuNm0Rj{Q6aWAK2mm&gW=WY?O0aA|NaUv_0~WN&gWa%FLKWpi|MFK}UFYhh<)b1!vrY;0*_GcRyqV{2h&WpgicX?QMhc~DCM0u% -!j0000805+CpNf5AT<=p@P0L%dZ08Ib@00000000000HlF5$q)c=X>c!Jc4cm4Z*nhkWpQ<7b98eraA -9L>VP|D?FLiQkY-wUMFK}UFYhh<)b1!pqY+r3*bYo~=Xm4|LZeeX@FJE72ZfSI1UoLQYP)h*<6ay3h0 -00O8HkM{dt<;VP|D?FLQHjUu|J@V`yJ!Z*z2RVQpnEUukV{Y-Md_ZggREX>V>WaCuNm0Rj{ -Q6aWAK2mm&gW=R{kXYqCc004Xg001@s0000000000005+c^3V_faA|NaUv_0~WN&gWa%FLKWpi|MFK} -UFYhh<)b1!vrY;0*_GcR>?X>2cFUukY>bYEXCaCuNm0Rj{Q6aWAK2mm&gW=W?X>2cJZ*Fd7V -{~b6ZZ2?nP)h*<6ay3h000O8HkM{d%)z`q+5!LoPzV43H2?qr0000000000q=7`*5CCv#a4%nWWo~3| -axZdaadl;LbaO9oVPk7yXJvCQb#iQMX<{=kb#!TLFLGsZb!BsOE^v8JO928D0~7!N00;m!mS#z9=K&m -@0{{SZ2mk;!00000000000001_fpOgs0B~t=FJE?LZe(wAFLGsZb!BsOb1!gVV{2h&Wpgiea%^mAVly -vwbZKlaa%FRHZ*FsCE^v8JO928D0~7!N00;m!mS#y5kQj;?4FCYBDF6U700000000000001_fnwnh0B -~t=FJE?LZe(wAFLGsZb!BsOb1!gVV{2h&Wpgiea%^mAVlyvwbZKlaa%FUKc`k5yP)h*<6ay3h000O8H -kM{dLjYkeiVpw)W-R~!G5`Po0000000000q=C%t5CCv#a4%nWWo~3|axZdaadl;LbaO9oVPk7yXJvCQ -b#iQMX<{=kb#!TLFLQHjUoLQYP)h*<6ay3h000O8HkM{dZ?0761S-00000000000001_fk_Gx0B~t=FJE?LZe(wAFLGsZb!BsOb1!gVV{ -2h&Wpgiea%^mAVlyvwbZKlab#iPjaCuNm0Rj{Q6aWAK2mm&gW=U_sp*SuJ002-a001Na00000000000 -05+crxFnWaA|NaUv_0~WN&gWcV%K_Zewp`X>Mn8FJE72ZfSI1UoLQYP)h*<6ay3h000O8HkM{d$E^s9 -1qJ{B6C(fsA^-pY0000000000q=6S65dd&$a4%nWWo~3|axZsfVr6b)Z)9n1XLB!XVPa)$b1rasP)h* -<6ay3h000O8HkM{dP=7HWL;?T+83h0UBme*a0000000000q=8%{5dd&$a4%nWWo~3|axZsfVr6b)Z)9 -n1XLB!YYiwa+Wo&aUaCuNm0Rj{Q6aWAK2mm&gW=R&CJ{`IR005#H001EX0000000000005+c;wBLQaA -|NaUv_0~WN&gWcV%K_Zewp`X>Mn8FLY&dbaO6nc~DCM0u%!j0000805+CpNl0%+zN7>I0MZZu04e|g0 -0000000000HlH2ED->3X>c!Jc4cm4Z*nhpWnyJ+V{c?>ZfA2ZcwcpMWpZC+WoBt^Wn?aJc~DCM0u%!j -0000805+CpNstYPL#F}&0K^CY02lxO00000000000HlG(F%bZ8X>c!NZ*6U1Ze(*WUtei%X>?y-E^v8 -JO928D0~7!N00;m!mS#yEVLM;#0RRAI1pojQ00000000000001_fuuAM0B~t=FJo_QZDDR?b1!3PWn* -hDaCuNm0Rj{Q6aWAK2mm&gW=Sx?%(G_$005N<000^Q0000000000005+c#5NHCaA|NaV{dJ3VQyq|FJ -o_QaBO9CX>V>WaCuNm0Rj{Q6aWAK2mm&gW=Yu|nWhv7004p>000;O0000000000005+cV>%H4aA|NaV -{dJ3VQyq|FJy0bZftL1WG--dP)h*<6ay3h000O8HkM{d!}jxF*#iIo6AJ(U761SM0000000000q=BtM -5dd&$a4%zTZEaz0WOFZOa%E+DWiD`eP)h*<6ay3h000O8HkM{d_=)VKy#fFLJq7>(6aWAK000000000 -0q=C9d5dd&$a4%zTZEaz0WOFZQVRL9MaCuNm0Rj{Q6aWAK2mm&gW=S3v(4DFQ007nl000yK00000000 -00005+csYww4aA|NaV{dJ3VQyq|FKA_Ka4v9pP)h*<6ay3h000O8HkM{dOC^RDv=0CP1VjJ;7XSbN00 -00000000q=AJ?5dd&$a4%zTZEaz0WOFZRZgX^DY-}!Yc~DCM0u%!j0000805+CpNe@fMIKK-30BI}$0 -2BZK00000000000HlF#TM+c!NZ*6U1Ze(*WY-w|JE^v8JO928D0~7!N00;m!mS#z=LWJBV1^@t- -5dZ)d00000000000001_fnR440B~t=FJo_QZDDR?b1!pcVRB<=E^v8JO928D0~7!N00;m!mS#x|Qy(P -!1^@s97XSbh00000000000001_fw^rF0B~t=FJo_QZDDR?b1!pfZ+9+mc~DCM0u%!j0000805+CpNvn -w&KhOgJ07MG_02u%P00000000000HlHEbP)h>X>c!NZ*6U1Ze(*Wb#7^Hb97;BY%XwlP)h*<6ay3h00 -0O8HkM{dmp60f{09I4F&+Q_6#xJL0000000000q=D{u5dd&$a4%zTZEaz0WOFZfXk}$=E^v8JO928D0 -~7!N00;m!mS#zrC#iAl1^@s+5&!@e00000000000001_fgOPn0B~t=FJo_QaA9;WUtei%X>?y-E^v8J -O928D0~7!N00;m!mS#zUqNSHj6#xJ@S^xkT00000000000001_fjx*30B~t=FJo_QaA9;WWNBk`V{dL -|X=g5Qc~DCM0u%!j0000805+CpNvaxWyg>i}07n1-02TlM00000000000HlGyoe=c!XZ)9a`b1z -?CX>MtBUtcb8c~DCM0u%!j0000805+CpNoYc!XZ) -9a`b1!LbWMz0RaCuNm0Rj{Q6aWAK2mm&gW=Q}50006200000000^Q0000000000005+cN1qV@aA|NaZ -*XODVRUJ4ZgVeRUukY>bYEXCaCuNm0Rj{Q6aWAK2mm&gW=YRwVyLbG001@y000*N0000000000005+c -fu9ioaA|NaZ*XODVRUJ4ZgVeVXk}w-E^v8JO928D0~7!N00;m!mS#!$A~Q391poj_6aWAi000000000 -00001_fn=c(0B~t=FK=*Va$$67Z*FrhW^!d^dSxzfc~DCM0u%!j0000805+CpNnB~t8Grx)02BcL022 -TJ00000000000HlE$rx5^fX>c!cWpOWGUukY>bYEXCaCuNm0Rj{Q6aWAK2mm&gW=S02j0$ND005RQ00 -0vJ0000000000005+c%cl_laA|Naa%FKZa%FK}W@&6?E^v8JO928D0~7!N00;m!mS#zTZB@hfEdT(Qw -EzGX00000000000001_fn~K30B~t=FLGsZFLGsZUukZ0bYX04E^v8JO928D0~7!N00;m!mS#y3-I8{* -ApiiLh5!H(00000000000001_fso@70B~t=FLGsZFLGsZUvp)2E^v8JO928D0~7!N00;m!mS#x>M#OM -X2mk=_8UO$o00000000000001_fpY~C0B~t=FLGsZFLGsZUv+M2ZgX^DY-}!Yc~DCM0u%!j0000805+ -CpN#I(vz7rt;05fU;02KfL00000000000HlHW4H5uwX>c!fbZKmJFJE72ZfSI1UoLQYP)h*<6ay3h00 -0O8HkM{dH45H4sR{r9Ya##u6aWAK0000000000q=7##5&&>%a4&UqX>4;ZVQ_F{X>xNeaCuNm0Rj{Q6 -aWAK2mm&gW=YKqO)dZe001Bb000sI0000000000005+c8afgHaA|Nab#!TLb1!0bX>4RJaCuNm0Rj{Q -6aWAK2mm&gW=U}#Y9s~&006iM000;O0000000000005+cN<0z(aA|Nab#!TLb1!6NaB^j1VRUJ4ZZ2? -nP)h*<6ay3h000O8HkM{dy;?u8x&Z(H%L4!a6#xJL0000000000q=AM%5&&>%a4&UqX>4;ZWo~0{WNB -_^E^v8JO928D0~7!N00;m!mS#!vL+!w%0RRAl0{{RQ00000000000001_fpb9;0B~t=FLiWjY;!MWX> -4V4d2@7SZ7y(mP)h*<6ay3h000O8HkM{dAU6ec-~<2wdkX*n5&!@I0000000000q=8IB5&&>%a4&UqX ->4;ZXKZO=V=i!cP)h*<6ay3h000O8HkM{dsWj;XeFOjiG7A6z6951J0000000000q=8&W5&&>%a4&Uq -X>4;ZXkl|`WpgfYc~DCM0u%!j0000805+CpNoOY4c(Vim0RIdC02BZK00000000000HlEmO%ecbX>c! -fbZKmJFKlmTXK8L{E^v8JO928D0~7!N00;m!mS#yvRM_f?1^@t06#xJg00000000000001_f$vcg0B~ -t=FLiWjY;!Mfb#!E5bY)~NaCuNm0Rj{Q6aWAK2mm&gW=Rd^lDOvr001Be000&M0000000000005+ct5 -^~MaA|Nab#!TLb1!gVV{2h&X>MmOaCuNm0Rj{Q6aWAK2mm&gW=R&J@=Ch^000&N0012T00000000000 -05+c##<5qaA|Nab#!TLb1!pcbailaZ*OdKUt)D>Y-BEQc~DCM0u%!j0000805+CpN#){@m0}J608K3b -01p5F00000000000HlGuToM3qX>c!fbZKmJFLh}yaCuNm0Rj{Q6aWAK2mm&gW=Y6a#Y^K20034n000v -J0000000000005+cOKTDUaA|Nac4KodUtei%X>?y-E^v8JO928D0~7!N00;m!mS#!&z|6%n1pols4gd -fV00000000000001_fnRwN0B~t=FLq;dFJfVOVPSGEaCuNm0Rj{Q6aWAK2mm&gW=SZ%-Y@_S006x!00 -0gE0000000000005+c!G00|aA|Nac4KodXK8dUaCuNm0Rj{Q6aWAK2mm&gW=VNsY8_z&0058;000yK0 -000000000005+c?TZosaA|Nac4KodZDn#}b#iH8Y%XwlP)h*<6ay3h000O8HkM{dZZwraAPWEhh9m$0 -6#xJL0000000000q=A5t5&&>%a4&Xab1!psVs>S6b7^mGE^v8JO928D0~7!N00;m&mS#z-aWq#E0ssI -91poje00000000000001_fzz830B~t=EjcbQE-@}-X>)WfX>Mk3FGNLCLsCglR7p=xE^>2pP)h*<6ay -3h000O8I+kWhZ!-C~DgXcgL;wH)Bme*a0000000000q=6-#5&&>%a4k75FfK7JWNCABEop9MZ!cwTba -HuLaBpdDbaO6rcyv%p0Rj{Q6aWAK2mm^kW=YQ5y3cct$E-)@JE@WwQbS-IaW^XTaZ*X61Wp-t3E_8TwP)h*<6ay3h000O8I+kWhtJ{J -TUjP6AZU6uP82|tP0000000000q=A&75&&>%a4k75FfK7JWNCABEop9MZ!cF!MMX?dO928D0~7!N00; -m&mS#y_txRE}0{{Tw2><{b00000000000001_fhwXB0B~t=EjcbQE-@}-X>)WfX>Mk3FHJ>MK}11RK~ -PHp0u%!j0000806LatN$y5^m0m3X0LEPa02%-Q00000000000HlEirV;>fX>ct$E-)@JE@WwQbS-IaW -^XT2MMF@6aWAK2mt$eR#TliG(h+O003nH000jF003}la4%n9X>MtBUtcb8c|B0UO2j}6z0X&KUUXrdvMRV +16ubz6s0VM$QfAw<4YV^ulDhQoop$MlK*;0ehKz6 +^g4|bOsV`^+*aO7_tw^Cd$4zs{Pl#j>6{|X*AaQ6!2wJ?w>%d+2&1X4Rc!^r6h-hMtH_d5{IF3D`nKTt~p1QY-O00;p4c~(;+8{@xi0ssK6 +1ONaJ0001RX>c!JUu|J&ZeL$6aCu!*!ET%|5WVviBXU@FMMw_qp;5O|rCxIBA*z%^Qy~|I#aghDZI*1 +mzHbcdCgFtbH*em&nbG}VT_EcdJ^%Uh<#$rfXmjvMazjtt+Y{4fL(0@tjn1(F!nz|6RBOjouLCQKB%tCsn +f_O;(TkT9D!5I2G1vZWcORK< +*}iONjWAr8Zm1&KuL0jC{@?djd+x5R}RGfYPBawx08>U(W?WmDk1T9S4?epCt{Z(ueTz)EC*E`5mT15 +-&2~-DsS-6=uU3I|BmObEPJI*Sr)^2!Om@h-$wOJl_c@O>A_3OHg5wqIeD(E7`y@m0ou*N^~8Scf|wu +`N_HtL5`*k&gASg%W(oQp9a7<~IpnR_S}F8z9z|q{`1rb)-o!>My0eex)q(ByedFLGyO7=Ikq8}(HcH +6i;acy-%V$hD`fEosH@wgA+8z#{H{ToXOd_?&uMj~(yRVmD7BE?-`X6FU!78rkLs#HE1jqSOWnjp~Z3(}j4wN{#<0DmEaw +w2fbN$l@K=F!>KqO9KQH000080Q-4XQ_aV~HNXG>03HDV01N;C0B~t=FK~G-ba`-PWF?NVPQ@?`MfZN +i-B_Ob4-5=!Z$M%;t!XVKc9b}U{5?(?Egj!;iWEo#VY8e`cO+3psdiM#D?U$24DrcGE{QX%^A1rwho7 +bo%%^4nEOe11`ih5ds}r~C4-D(by*bnzy~VhcmspFPs+92he4iKm495?R6(6IB9*bzqWO6Z``e?dj4> +$ei>cuLo8^bh>J0qwmAsn45g@9MQ{TAMQ=}M~B1K+Woqz5;+g_LK&{q3XhT~awQHE!$j2T)4`1QY-O0 +0;p4c~(=FwQ!+P0RR9!0ssIR0001RX>c!JX>N37a&BR4FJE72ZfSI1UoLQYb&)Yo#4rqn_xuX$SgsPJ +3leY=j7%q3*bq8})@_Z_B-k#f{~ot+ASB2V>&bck^4xJALFYoL2O3Leg*}O$!hKQ7DMaVKUUslOCh)if@+itrPZeClT~ +1iR*^N=_&VilHX7ezR{Ys!P3i6v#8#CnCLX(r^h#(D9Q2`wcYz#AqB@vfzGIq$A8sk{)NWEK&TeAplO +P?6fq6Q1^6a*0l)grsP?n#H~**AHt%UnWjY1bq&q0|@WSC{?>xZeNm!(&pOOE&dqH}AXz$)6~;-HFq; +xJFdD4^T@31QY-O00;p4c~(c!JX>N37a&BR4FJg6RY-C?$ZgwtkdDU9 +obKAHPf7f4uG7lkBlGE!=dmYW`dihW;o~E`ZcCNi@JUohoY@8{Q1wh-1$NzhG@j(J4?IbgOIlV{(b{C +7?A9fc@1wrttV^vAk^$p`qy{EM#ouDPzHJmWfRJmkLP0Eh5`jUu}2}!od0gsCy2o?*rZyPR2(bSUO$% +<|5NYz|kB9(b;g#Fd#^2(tThkgbn-15A&&!1SkV-;QOc(aEUs)`n$97g+j+@~e@<#e6Bez$)8kE7$CVsa!Y&$ksdzhuK>@*WHllam(J%Bz^1 +QFuJ>TBK59wcM7qX?8>Fvf*h#xnw(L7rDKHEljCe&?`s#rJVk^W1OOEdeuJ+V^6W(P%hAYhU;hjIOt? +2vJB0fWh56koK;Ps{O-tR;9d?}OpA)80<2VnFw5Vxw9d@n9FLVJJhuS000yys;B?3CXqmx?Fhd=u2$L +Ckdn)rXm$@sB4hWuO=_IQ}D!OgUn}Uj7lOnIGY#4r=RnmQ%m5le`faf>hg931HhzU-^Y`1Ea-aQB*l>FFREh)$5jY2R>#sl +UWuDTJ2(W2$w`i9+Bh+a@^EZli~*{QY3)2@XMbNRCX=Qyv-{?{i!Xhm5ElvxeI#=`~ +D?LO(6baf!usZ{VAodthv$pdbX0 +|q%mA+?{9piAA{!!stmrt0q3V$EuC6g`A+nT|BM2+3s>qh=U=AFthLvH+k0!N|r9wJ!j!=fA$q`6UAS{-Ga +(eL*g?2>_1%t|33HNce3`{^o3NV21-EIponyvONX0_bnWq$thPGHCZ|R4{P7TcWCqn9dCn}ym+Ans=a +>N`DS#m=&*%-GN%tdJ~G8IL_C>r_Dv+ba=a! +VgIRWan$LZbsL6xMVoz~81qqTbPQPn$khB?V(*t%SlRv3Mo`_qk>@hbk}Cq^~|6y?>LfkAH@&35JAK5 +1H1mT%Gd{5bFm(8}bk^PW|M^=@6rHY?DanXt&!q{>@Tx$k!rQJLg}2s(t4ScUH=DGin8&9C__68M{q(n|SlK>S*QE*7Gx8RY1 +y_%fH?47QqMfUPB}6DyjinRLhBK%obEtt2A~Q7~W)A$hSzb)&uj}Tv)}7c^_QTFovq0k$sq=1AQ=^!BI_En|a4Ggg|)oU`+0c4al`EZ^4``D&Jo4JP3a?0bO$_CR%5e3 +;C?%8VZV>f;=0;gV_JE2j!!vqIu#XFj{2k`%(_hZtog5#Zd^}r!I6FFD4`YhLjqyVzYvPBOINd1HRAuH7*&S^3x&tM`*%12XpXMC8OX0OawPi)z@ur}DW+Ps43vA!#)8oaph8K7Qr=zY?W=&dW*ZMPZ18Dt|G;kK{ +AiVjxgnL587Vp4G7UWB8xZ(w71#7OpxuiK^#`{kx*0~-@h@ox+-R-gUCgZ+yuT3l!DoqOa7ypKZ>Yg> +z|kR2?e8i|`TDmVHU%*J>b1&?5-QBhwE>Opiex9vY;4iv*of}Mn21$91#TvwkZQj%szLUU)K5W{bCh( +2Yclp_+C7LKSr6XH=Z$l@y0|F&G?qQO;cJjb*=-vK(_jaq);Q-KvB1#&Vlm%a>)MdAlWL9EkQ4GqgQ3 +#cym3KhY)n&Bg7+YWJ#OscgtVdS0DZRpX()g-PD3 +%YgU&v7i+@)gc%pnw*b)O0bLkv_MU&s$$2iVCK{?YTI$3Ud-omnn$bD)cA_YTv0sIx$}Gdd +HE%@ukz>K$yxvWLX&7GC<|s~W~5iG3FtTNLAap0_*CC#LCUAJrYs>D8_w*_}zSS*WgOg}n3GpON#EHz +!Lt<@@G_s`eq-R!wn@Z((Y6c~b9xzD@s1MAzcYD$_XxN;c0jQirMpBeZU6GkcjOB60 +4Q`YS~WSoHZvD_R*%G~z9xkChTKxJ1E^rGw9VcCl1l(C164}0Jz$lMqMWYkNE*J~={u=?%UHGEOpX0q +^cAsRY|r%$zgMlp?`EP0w$iWxjR176^{_S`Zof(V1*vr;4qTY(QrNgTe60CC*NJ)h%*`!1Eywg(oQ}I +Pr?VR6({Xd?{0URA{RqlRR%j;=CEU}SaFrh&@c(BNS=wIUSH=(Q1dn=jzMl@*CZk0sr`CVmFM+YisCo +{Pgk555Ea`*%l%j5uPIzW86SMD~Otez{h#5(@Q2izPO;ch~?nv;iGy3%%Rt)Ri4qs&7(D(F)RuHScAK +vM`S-<-DlYcDGhPL|{Bsb303kw^4`BcY)HCSI|*hkQHnVZOsfv!E-gd539!X|u&fb&!9z5|JbT+#&cS!ES;s51 +gt5Rd=K63LopF(|!6rx;Y+xYW{ORIiT95)Y&of1ZPIJh=Szb)z;%J3Lu_uZv0WMh35$G&8jj}$R5XFj +T1gnbG*Ql2T1bk&U_VmURq)QZC5GxrMj-65NRU@P$SMpAP6EhtCjA%oeASnpPuM6+0U_`>XZ*DV;m~e +PGttebj=R^-C0J>mK5*~iYJo@Z>P6ALS_LMCiAsf$_-MMkt@tpFfx#M7mC&*5ZPP4P~m&b2jzCSr$XR +p^E&V!}?2T2$W4S>G2ZU2)Inpmx>A~WXiXY@CS5Y>w<>B@Y^zD_IeX?Tft+?=%I7ir;mAnISOy@Z-CX +52MBZ +08mQ<1QY-O00;p4c~(;e)76y~3jhG&Bme*w0001RX>c!JX>N37a&BR4FJob2Xk{*NdEHuVkK4u({(iq +=s~|8{$h^QvT~vSyB+j`u;G{+D!XFL?Vnwc`wJ9#kUEWy<`rrG^?2=rP(#1`IHmE-6#C@5a_jzV{i^b +xF%nwR@FDtoMM^(A2#bR-FrH{2~oH$5(DD}2`{9sMh{VvUZud99cXzbOlF-PG}HAY1k{iZst#CJM(EA +d8KeE+p}+ElV!iMPsK`7O1s)9hYVg=x}S<{u@|O`Y7^j?6o`UkP0~)zpo`cUH-x8jswo#)9%=6kDguo +@6d7Q|Vlm`X|NYVrG~yxJ=cjTrtP}zSq?~_7v|AN|i5lsd(#|okvrs(xyAp9Hq;0Q@O^J9g&wj`oa%B +vb)sP$8OIX{C;HV12NRCW$w-`W)-AP9qX*nO|M=&f2SLjJJY~kG>zHpqpk{jnM&IX+N`BJWX@z5ySgI +JP>tAhE|Tt*d&6T%#;VS;<<-?yp>`r82Lmg)ONuo+%B^+HO5p2mDW3kBeypzqKJdyPm1~le#qdQhJVy;s&HBxYVpYXwJHFUdEMVhhn^4oBqqr=o7my)Kl6X +Hq~G!5$hTa3WDiCk5MroWg=I(OQ!LN56$Ex)$%Sw=u?%S{#1!R2nZHyW|=%D$Mo+4x=q2&kVddgENoX +F%kM~H55&ZZ573Oqh#S(JAa@oOY@+L%pYvm;^Cn4L*T>GsXGLc9d^UArY#HD*))L^eUc}9@ac(=RUw{ +O(>A%nL!)@BsmfD#mOzlU$}T&Fdu_4D!Hu=cvZN<#Rk>TmDr66wYH6gH)m$c|GjiQKCd;n-gQjZMOL5RNQliq-}*DPR8_>VzZeX5t$RYCG%o)4uZ!yn|PB_psYD>vOTB(v55wwz%%}$D0?;D5 +9tSdNjhJ$TAS*}lvR9QtW>N4|ft*M~t;G{gX`A5WNJJ~~fJish +6W8sG$bBFd8ugSml7IjG$2Z_8m-MW`q23>;K;MH&Oe2{iZzCaBym;5hJy-LA9tBN*TuxCVx27d|jg6u +VYxFWW-Jm_v9Ja+DtsZ6x7QSNIi>2w9>xbVLZQkegb0SJGwqbgRgS$aV!B)Oz>Zwi +@}5%Gz$H8n7a`zDHyVRRiBp`ZeC-^$Dh_`qMFlG+a;$Q%0selk?yP2#4o&4_)5mqx`T7Ya+o8cK +yP)a-ANEKOCtgY=W4sYzTQKkcAH}Hb$zPkH9*6)wibE#`j5~4^nC7Nw~HyJV}ncwqf~ieYY=+2JB(8u +9@xF{Qc@s-9ET^{!WVQ3$tPtgeAKbp`jCV735!Zt$|j;`Ro*tF7gTWMct?d1MkaE9c)o%uou@CUtTp5 +}&Nx|u0as%#f&OFnIJ8!v8d^_REmQFdDe|7SjqGBB(T?&X;tN=d>UZoQg9Qmckhmm9F7btQ5k))&75r +}#qp@Dm%L^H<0=@|x4M4>j#62dV{(Ev-I5zp5#8}0E#MBYB5{t^w{s&@=l9W$w?5i!~62#|dCFs#%5w +(|Z2Z_4;b?ZgDT|c{91u<`*t-l@~zFt2c9-go7?gnWC++$L+dV|OVAXD>7vl<$U%y%BXyI@o?lp*v*Q +5nLP3<=TKF|bX^aZ=l1L5~m45$|S+jW|1w=#CR&knT1Tcqrdzzye|TY*MY0^V}?B7J5;pm7c>CE>5UD +=_>s%@;GRota}$3903*>Cr%j)fNDl6N$6|D)qt#^+k}2jj;4s|&!P-ys2Q{F!tya|sjMkCCrLlFVg{G +XsdEi`1`nIFe-_R3jS+p~=BO`YoP-EL`!ZAv0D+|As@Jtj%#zf|3_lq6`dF8I6QGKlrZG*IJp*$S;M_ +k&F%X$0j)1QBXAm|l0y3r^623u&W$gn59e-F7f(FFr;#x|4)FVSw8H-6qM#5H~sHCnuKzbngny?Q85t +l%u0iVQYe4c7TgZEa`95>$F>m~fX99q5r29V3Rl>4r3*Mc2#FtoHKdg}A7%CG29KBoie2~KIP0Q@@Gz +Wi@^W~33$Vg2@RL-BQ77znd|A1{{GNf5kb=)_<;bkXr?JuyOFYy(h(hg2(uK6oI2gPy+(*ni;jf#ynM +K2jBH>nIHonS(|i8+d&mT2L5MaT|e66mB?n=xjB2k`6gy2|KouR2;)d)#7(t}HxH18|3lB!DL +6rGYF1jBsLD-wX~;Nf@2gKU$TFn{=Ow^g2Xl+(i`TCslD9)VU)a>Cr>f{FBZ)c-nzY?D;DFDosr33<_8}GlmACBq!Hdaop=IM+oB4)ND&j^o8Pi6S?Wv*N{(TJEe +mfa^TDPYVVRY;{5HL;)7huq4eyf|DM<(7CptEq3?0@r>Xf?8Q5A@1Mz}*B5xaKs62i~PO{%STE&R&jI +`upaym&|7n2U3BqS~Z&Ruy3LSJ}%|s#P2qjAnNP@f03IOYTNFU*(`k)ulHzqDR$#bF23~X8GjJ3sKbl +%n+v1-O#o^SN2P)SYUExJqKIuYnkULHY~3$W9#>}xMV34}UyfWn{>1XnS1dnUOweg=u +&0?;&ukXDNiNQ>x*QRXb1iy`pe}2`)+Dri3s&gVPFgZr*pni=Z)gLsNEA3--n7{znBK#Yw{-G^cXn&x +4|Iixc)`ZX8aCl>?!mfXft{#l-~U9)y?4(2)gv5Z9bFrWtpNb`b!?(8MiiTIm(3Hyc1#ZsJ)4)ig7=NAXHLYZY33{pB&8s +r1i;8+UXAYv%!G~9Wc%E^Z)C1^EsOxI-~rx`OuCAYmZDQb!e&onY7BYNO98@2?Zd3QuicrJ;GIV+mid +dfi=A$)OFPkibA8O%^?f*ZH!id8?IO)79oAw`XPORXj@#+v*Yr{$W6k)#c;hiT%`^HRof*mcS!ezxfG +62eQHqG~hoa$t>_$jna?t4RD5i+On7_n`3)EyR+M5mqtg}$e)c-loh)xEilAIA#c +j347t7`ay9E~MLX#9smFUaA|NaUukZ1WpZv|Y%gPPZf0p`b#h^JX>V>WaCyxe{cq#8^>_ajtPY2hQAgM7ieW +X7ZMnvG3z|!UA!`-^Wu5d2i+3vsuWNhOM$t& +%*s<13z5Oz~=64hA>HinEH#mB@>%xZ8~fM=VcPe8AX=Vp}Pyisww^Y)*jKLS$S;uxOKHYh3ja|FT4>V +lI-3r)(>#B}+7rBX-Ysu;>DQ0EE@8$n6SIy-7s61_*#vyAlsJk5BU +5h@FagHDYJLz~naLBX%wn{J!AZ_q!5)UY3Ybl8xB=bqTOFoKlogEOOWcuOj|1=d?^&$RUu;m?yc3l!Y +91pT7Zd{8X&7^rEO<^YbD}c{&;l`_5TcBCC%`$}$yF?Ohjvu*#&e%Ril6oL+vq*}oiA=g#5H9k0&e3G +jCBj+IbzyPW50EqM$Wjo|xwH5gncTTSN`iHIG05{ufe*)w*t1W3yyPX|AXJcSKL2w{M~gAr4e91aFQU +0%F7dmFz#xtUy?yqmzf0I?If2$)z{LK)8#*KhFLU@*D(7~}ez`0VY)<@MwgH*UC8AOnCMEO}Ofc0FV7 +K_BnoK*frMub2vT6*M-HJR0aF$3(3b_lKLw^>MHUY5*S4^8x9)DfwJ1#GF>VJ->W?a(*1#WyNih=~Xv +7Rq+-3BvMXmZqD9MjsqnsuHR2T3R$g_Y{n+}M#v&3+xNf%X~zN2H+lof>+0+(HjH|6c0RGo;*TfSv=r +=1I?G+qAJK5Z6ci}o<;ThO_1WnpzPvu2Tm!X4b)@MSnO{h^{f^k%?{J>;6^|Z#JUKr*jn6MnPUFjq^I +vO#E(jku0vrr7Qbkx^t7RC+=xO2@Gy;Tnaru5SX77^SEoUGBaw-McjxKvt$k`AEQnl1w&d1YE7$DmB>n=^9_R|c&Tw}!J2+Qo}pliJlnBS@&zz1E5NdWABr| +e2plrk{(YdSPlbX2z*ivm7#PzcAARB!e$2)eogfN;l@*2+T3RE*(apucRs~@SFbeB8#J->Tj->@xv>C +WpB>*8UFqna3py*>G3OE9kQN#it#1)szq*QEItl1VK3~T|pqR?MxyNVv4UI1bsmL&aKvw0XTP{dWJBb +0qC69HSht~&H68MYZ0sWKB)2z(f^S3|=_(9YQN7%>IgkeG;pW{RF{)bP_VRO4;7>OH`^X^mr{B5>u)= +%0niL;N;kEiX7^KpewYC=wGJBJ?5_Dn1C&9~zyS4d{=%1P_LDz0)9cMyN#Mp?f9)$UyJsyF(y4WblU) +go}+5grs{R3NMqP4}#&%4Lv>IH=VgnIKfz>ez^9%4oXjjR64iQLm^nNK6(@4Q02(Xm`FVsLkxjF;y42ekvepIo*=8c&2p*kW6pS@c +F0XF2s~=V4A34<5FZ81^dC97mIzej3x(FBk0fD^TXP@Sv}R0n}iJ4t;$3%0pL03i&Zl8#a8Fb_}_*#+pzpWJx`C0cMAlnrd4w=F&X|O(x-_f +;Ize&?OykV@No34K1e($x$rdejFIiDKT(OS&Oyl2?CF+BIYS%FEw?wKWLIXL*_L_Kka%cq>{T`in}FO +75F((NKx&Y_JX0?r4SQKS+(`v@fcu#7hI=tV6{q@Ht;*qC+f$DFhri9F_KE|T5d!~YRwHKR?8mAC3V< +^!|8XkCRL@fot;6Xcp!Jv3k?x$SO_6}r5e83wt=fxq|v=R^!pl$WdbUR6igw~V22Ey4@8a}DJ7O?)DN +hEc|3NH7__j~JV4keSGlt%_{u=Ym}mjWH3>h^;8F0FLwvRVmKrJT$Q8Lr9F|Oj)f5ix$OB4*K56U=5s +ToWfH*Z@A_d_7AK}ka;1H_z5IWNIjFH%W6MsiaQxo17u%oUin^wp&+3>hlcQJ2fBt&w+@aXUwnw={E3fUed3M1{cJ@GzBm!;e{5xYbAeOeb#M6y_IbfO_TL=x%}L+-(D*LP9S0oAef_fBQAN_G@6eC8$KVj``=-k_(^&8c? +aNx2r#b$soo8|*4Da~l2aObv;w5%4y+xrv9GaP%xZmGsSUJ~x_X?@t{LuDH;5+p~HPjYZ$^(m%=T->8HW;G5E%pi;@1g*j0rX7&%dVu@027%k +)iBj%q^3b`Pglf+P9FKxVKaE6E4t5425m|;yw}rf?OD^Qob5)l +xh!fsrct{$S{JoUGQCaO8;+tftq1dmUJXJkm&4zv1+Axu +orv4~I(IQ_8@a?M+;U>oIw+z>({mVi&bdF(CvI=STr@1#m7gtZmdR&OB89Mv_MsNUo#S+@ZDV@f}lyR +H5%N=oAkW+7YEpI835ucq~l%53`G_!XovP_}}_rhJpHvuxJuEwpZSyD2yKqNE#9B!5p|kELr;|84{co +MQ8ZYTr455g^0{H6OsREkqDYulz>_jk?6te6SUbPjBs3aXGE)i_K>XeUpdx*Vf9dwqcS6dw;8K^Ecf& +V_ss_z@lDejcRV1G6lj{-ALc-p1jVP#p9df*2*p+9RWA;B%k%-xq89Ex|i}?4Q+@R*<-qWE&SlcjL6q +~(0SX+>j*hKD{O?-6M{6Se&(ETt6N!3RiW_yChhF;8d-rpIf&7Vu-FYJlR`1F=Pa +FTqgFB%N#&NOO#Gu?2S(hA*EGjm77FOa0oaelDAX~k511&W~WIrSK!)q$P?m!sZ{5!BcbXyj5>e|J!( +)ZaeGh*x*?Fy?87dcwut({UXazoluEZ+l00e5-5DbUJ`-C2tn_&2mpu4&DGblO2X?djpUFq#QOt6>g? +1A$@RI1r#HA{Z5ZvP+ILW4jBa4*ZhG%U?B9T#JUIQ%?=UBo`#HRt-0U4`4p +ygUOoGK1{CCvuxDM$DBYqhwYNx-Q)zfv#737^kxoDKSLS9%<_lQu8LnT=+~Z0vk8r@KrBgY7XtF%5I68bf!$>&6*S_f`_3ZYsT^ZntRyydsc4L|>kZvd~RoJ&@$ +ikE7T@VWku)Buwm9-w@?|>n_?WRXy$5!%ckQ@p2(@@Q^#U(KE21s2xj{pyCh~S5#W+vb$G^dNQ@=P<% +1-T|HgVs95BNDScJs=R!DMnQA!9bm3?g$Ys#pd)roB=A+V(`oZ8hl0%4s0Psub!E4(+z?RU8R+mwT6shwm=>*Vuke +2eg}m$Jop^qiz03w!_y&@TiZ7X#g>fBReJj6h5|x55AF4!(i|qP)h>@6aWAK2mt$eR#W78W^4=)007! +C000{R003}la4%nJZggdGZeeUMWq4y{aCB*JZgVbhdDU85bKAI*e)q4yG7pieB%Yl)mj`Wmt2~YqyJm +9P#Fj}t*Tn<+z3BpEwf@GRv=R@wi8jQQpws5uD4}Yto+F9| +Ne9_Kfk;<|Mlv_yNP&{CG|x7mKps2ky(=YM0_pq<-|@evofCFt0L7^T;8qbl`^`i64kE#29v97(a_}K +luG@xQKmNWMyIM{__O_af-k0o929oD>@znz5%@5{wKVHITlmTIOFW-+uX(+!fKb4Fyiv7GWi9>aU!+k +z9uLd|r}Pg$m|Et!pMGT@iQ%kL8&%XNCnrfRjS-)+@}jDAHEQ)awoF5Nv??tilz+!6bu-UdrA;O2g{9 +$%btK-YLRB*FD2S|Z#^7d#BpshWNHJ|HHjZF&NEC+fe<9lxhX{Yrg?jH4b%-qg{VX%`kcYJ@giK&|h6 +qRRFRsttoL!$qLRTXC^y|Cn)rYqqBhe~Gg! +|JAj|6W&(5+D&wTB-VlNwj-0!cxLyn=F@Az9ok3#@o$|<5m*L_yW*Z=p! +fR^uvdsnBPX$O7*V8o@Z;%Pa{hCRW8MbKHN?;|n8t&!POWMTno~uyF9$$>x>#3a@7?xFu($9Vs$ukTV +8h9c(8OAs-Rk&{XU(TV{!D~2M};#L$HgzOu-~muo#mC1>DDc-(mlB;T%F^VMqFviX|1Pll+HU5)}+UDWKz-0~m1VbZ5@qDU^QR;^0ds0tn&AqcDs`Xf#{AM`d +HN=+j$Z1uAt|}p3~_ScQc~T5NOfM>gAl5I(A6EFRDqYzz>~}C>rX_~4YNBsCac%VRVf&uO +Kb_fvsp5)Q4=?t?Ki~rDWY~<<~7z_XslQlx3r-nG`hDKd19MASG?|0PWX0VWHN>z=&BNvRdBzu%RM!zcHKn9T>KK~pwa{ +anP8<0>ntgtJoq(vJMw+ttHfJS&LM&O<|Q}&7^`w+fW18t +rtWWYjQGBvm#VT&nADF+S^x3{m7D`~6TT +VY?#IFmd|Rf5CL|hX3xoBGUAV{_db*_lo}>YwNYzlsP58)15F7eQp69peQ`KZw`-75K4c7*8mTG{I}| +9LN!)r4*utE+5ixFZak`O#WV>7GYKRy3AR4oTULK*7G#M23sE8 +sqJYlH;YsX*r|$jm8z<9NfJ(y8^^Jk>*YM5USQ!nmO*mFsF2cp&MKSFcXB&(2@{pEb6_>aPsXFO&QbozQ +u;Uv|h9WzJgM75br=uC!(}wN}RRI!o>)N+#0SOq@}Z5iV-&n8yWeJ?0C>u-F2UFPB*Y4?zlD275|0*5 +8Uzt{1r807$>8rX^caa=~SNQDz0DXSW$+vBBDAhE%DNS_Q@+y|~`U@ +8@5-ZkQtTP(_l_Yse;5NNW^{VsVy(n>!FNuEeq4%T_4^3bm>>a9~qsi4{^DNR5n+I$y?>7W7OzcKsD3 +pe=3WUWrmut^>>m=0G6ZwkxRaEP#7pKtXqzYeLR4T7*#pHOhv-!D1bh->a3X_#h#3+lf76 +)C%<)uAf9f2I9eIDOqTdkEHX?a&u>3k-3u?|Q18um$(R6SWf&k?wqd0p?aVgnm-;RoW?ay>qxGlsWe==QIT+& +q*l1VPlCNY!hi7?1g)Qqw&*^HrtkwrA4BD`bS|OY;5k)8Z7m)#)tX{g}>`7sF{vayYJfP8rs~y#*Rw3 +MN%}I14;4dtxX(1jXB^O&1{wd4dz!l4Z@3nqMR(Hl7j@-I<*BU!~?!&gm@l`TuYLItdezhk2e7wnNT}7C$*)3 +Cu!=Cw?FYQjPAC&sfO)rL$5;JRqCEYp4^n2g<>(fTGC`_({|fXUsL8D$SV|%ESRGSO;9#|qJFc{kZ?p +(YwD<2G+;aN#kR$rJs%|z(hBjoHK8`t_bc9&t^t+iH26?~yXOqDH|yGo-bd&W+}~sihF*D=Q0r&xbpR +*qrP4ndQwOR#+)E8sgk=U#Map`eJ^0_T +{q6HC_j&{dQ}RVfyvWD*zA{RYIp=5Zl+!zYQZ7K(xnDG!`LYm4PuDyhF_>RrnLo;o(aq8S}%S>PMc@9666OwsN*7nUgIbRDoN*B4T=fVkNw47cO27l%3r3$Dl4qZ3tM|I}*Ly6T9W5SJ__(5jbawvh*}ID`; +>}0#;p4SD%okyq_Rpn4#$ +w}V1L9VCdwM+$)t867IA?{Y&GLr9H4BpXu##6lg=?SF)nd_>vEt+O=F^xmRCX%oMcXkoYdL8UXk1o(| +l8@T<5Z#OrbhfVk{;%j%&&2jm-C4PoroiK8e`3eEEUT@gM-~t3N8RKe3SH=$FdA%63R^LHnMb`-Tq(={QHn+EJiR#frEY&o}o(aqVJ(X+(lQ8tM +f!}AfL?WVl)m7VE5(0n!5eo7T4V@mNQF@&)(E`#8q;JAlw9%^#zO@4l$dZ^u*3@+(fgMKmYY_r~r~w9 +sA!P=Y=1HjGun~^I!gY?06?A9NCzETK8ftC;I*#1HL#JJv$P_juU>qg{OV7MntL +wdsaTG*yXBfo#F@`N$g_eQYPa3oHF&1YhUcYh|q=neMuib*2~$ZOmxr1OPS8GW!mS`=$EKS-?QumUsHJHVN<(I9- +96BfB6RO^w521kd#WMAfbC4l4$$N4=-O>>i)!xFPg=iXegPV?;(97|G4|uYc&|V$WTHPg@kXg}VfG?4 +5y_uX>tEDwT#k)~H>W$c{@&ePqB7r}r=Tw7` +(E01=4Jl|H3#}1x?x)(x4WZP>QyJipm2BIY{?jPgO}F7+ZMR<0lqTS4syqM?V#|!f6QC#&)Ij#xA*eO +xVMGaa?Yp0^rioD>F&KQ+lvqFD~rM0g`Z=)y}nGcbc3rueJ6IvZjRkjn|~RROFecopMsNKs%rYP=`^a +ULoN^9F&eBB`_?nhBS#xHs?P_O;#ji+e0mJ2K1C=015ir?1QY-O00;p4c~(;=0Hdeq0000~0RR9M000 +1RX>c!JX>N37a&BR4FKuCIZZ2?nJ&?g_!!Qhn?|urA(+Zt^8Egw|$DPJ@*{zh~CQ2f3Y#}KddHcC3tq +@2^;@8JNNSVP_raS`8T*Tm$)b{YrMkUAOoa=FbIZ}RzGHQF@94?0kH8~#P4Zcdo9X!4RWosSOXqx6{B +88ePs3^bK!%zfD>Y*!HOG402h)uz!X!XeoYLpV35d;Sm%v~khP8MJf +%P)h>@6aWAK2mt$eR#Q9GlIB1O001u>000^Q003}la4%nJZggdGZeeUMaCvZYZ)#;@bS`jt)mh(<+qe +;a_g}#(54HhYNP!j&4ETyWT#7Cbw814n9~KLPmMEK9nN&$?H$t%gduN8EMEXav=*!`Z0Er~daAr93%{ +PoZb=o+l?W{5S#46pkqH9#n-aq)gwQE($a|k_R@%xP;T7+PCfBf*1t`kRxEi)cazEq0~VCxYbCnOi#ufbCrf72{SVo7PL~DQ5O8=0+reU<)XVYjF@*n;?o +s3+tBt1b(ArPr{F3WU>Lh%pP^$))+L}wG{_m4FF^{><78GVj5nXX9?dq^Eei@EHVo+@bRFM#cY+Wj-J +Lt4*9;it#Y!JiiB~9i1e@o$-)~*Fbp!feG!?yBj@&*$V{jwX|y8rOBaphPL4w^=@EEQ)*I+OTZE@Iu3 +WAz_A>&Z@=2hMBn_C++D+-Vj73C$L&i>(4M-B9Nm|U9MV_n6QH1j9a(T?jkO6SkO1rZ?5P0KTT0bR-; +dtN|sGpyBQ+$j0`@(81ENSCiC%8e+_n0yt2X};e2zzc=ai&5Ei3!H$u|Vda1s?O{nL{7wRb3WI@S?Sj;>pUiGeb+4!co)rbTtlg}vjmE;C@e1z!YvB=w)Wo +&FCtniHn)Tc)ac_ILV*TYgnq^{uegP%o_g!3Ktr*FrT-D360k)kfVvkbsdD^w4xX6EyOM;(Osu4EQgc-2kr;f0}fVIZ`kuuDB8peNsIaLBx_jxBs +rKER3xPdOO53F7ErPuwliP~4w|>S6in@Q3u +iqrQ!Yhz#dh8=$OA_YSL!pcJYiK?^nrw+f;d#vc&FnK6dh(4Fyv9`=h0bDjSd>mHh0`xz%BUdZX{h6QqplZhZ$rtraGpNnF()eE4EvMkrvzFKu-%T5zME<2kyQ%~2+R?u9uPv% +IFP7uhS$>1G=c0*tXzB*RqwaPA_>eBvrM%3Cbfn=!7LLXcf^W;9$&6<-v`5vFC8H5Uy +~4Of{HadEa6h?FTwbsx7|PPqFL1fpm9)p74vZhqA&-qUsMQG?hT*dT0wnWB_s2L-Y=l1qkT=aLiEQyx +Puj;k~lG2?mV=L@20OM(hIjiB>>xMs}q`EZ+rC#J;ry6&+?NWWm(Kif{fl2MF~|WcB8Z-MrGEXGj`SL +FFHI+%Vwi+-mjW7c8S|pD08_guBc$N4yJ5A2&#bQOh2IMouj>qSlnSByqF*zp97l;fX!;qThx}$?Gs?G|FN+^Xlf(I>S1UAJ?P)TRH4cIuKnfMl+jk +_>Xp;%Rc}7d7_&77qJ=EK!s@fjr@mKI1k8wzJTIo5~WTG~n +G9_vjBPJ>#>Z$t&nPmV-nN^uB*(aToK)mq>n-nnw9j-n?X9n7rUlww@E)^CFRs-!!^h7oT9>sgXfp95*bX`4Z8XXBEODC~Q*hd$2)Hz3>uO3(MQm2N;YVS!Rt;s +jj$Nx(_sZIL$eAdz@Sdx`uVIWjaWPqO7E1Qe#_)K3Y3Mn=>40jq1-&}<6n%!lQ4UQ><#nEZ}He#`BT* +!C>6Rhri>|2t&tn5(H0Nb@Q~lI84wP)h>@6aWAK2mt$eR#T2=^A6+)008+I001Na003}la4%nJZggdG +ZeeUMb7gF1UvG7EWMOn=WM5-wWn*hDaCwzjZI9cy5&rI9!75m+TzG{dIJAX(E}AAA+~IQ9AiHgGy#_k +2M6H>XM3to0YXtf4eTEceQS#ok{$N`qXE?9V%t$t!(w3Fn3M(72lKy$m&Ayg*;qjAEZTMfS`+M2mhey +@fj%zbgDwB2G?!%)wnpLG$!|bsG6&sdcwZ{#6BMZCoyPfQ^{86-}(jYG$I9-uF3T>on1ChIjapV8w!| +s%WY^~5OuQS<};wdXsU5mmh9XPy`?ZfM^_&lALK;#uYj>PZ%>RY#Xj<^w)!;m}>+zXqRqT+pRbJ0FZt +=dMk_AIF?MQt)8NHi#wcUn{?FuDoL@3AVhXbWM^acPA;DE$C7W@@+hvb*ss=ZJbMadRbW0bg0s1S(#B +;swObZPVqny(6c0JMH&=&N=nd1Nt8waizKt|R;3!(tYmt{yuU0qL@7})t=KA$_`I}d_*ZJG;Z`qC +|7e8KIG*=hp?Zr3Si|@A=H~&gjs})5Y+^`Fwm%*^_+*+FFEpJ4guW<~fW;xm1SVS{P>^9Q}aojRv^_p +G%nSQq`h7VTryQ38beDObnQQ?Dh?KX)H>q8b~X3t-~{3;zu*4bV>mGWK~I}m7Ld)+!ZNK(|?81h>6nk +;rh^7vbwjIfckd7i@C6^zPZRx-*-$RAWYoTm>R%bZSImoh)$*oHFbBSifC<;*#!JGlu5h}UX7^Mc*#B +eM#o^`GBdZNdh~5QKvN`K6#~-F%M_l43tB>2oB?k#f +R43Z>jEEcN91KNwNpGvGKPGEJlJU@zU92lqBoNHVZs|xBOC_EP(OH)M?dDo*1zrEa>s}21zY|CIZ@s+ +f1-pLgYFS8IADQVpU*K3+ME|uw~hCc{KNCxY2OA-MLouv`ru +Cg5G*Uf=54e0`kQ_#oqtk=IL(GThnM7V@4JiL~%B;<0svSfG2`#qj|L%ID^zj@ysEl+1= +`%o&_2p4o8?>nrr~0$u^l9(*fb4e4ljzVPF-?UAS$VrC;SEdt%tYk5G*Y +^+y0;HCG>W!UoQ813X8V<$zN9w!`Ia7nIMbZljZsn6Ho8^XUL((dvVB4Ha?-NuHFg>93T$jJ1s54Pju +z7rk$4A#?9JoOZ2Y+;uC0c?Uv6pra_ooVer&R4ZCrRRv!$2$HAPEPF0~ddANEt$O2QdjmPS|eecC#R?9t@1loO~Ga#phx=^tIbbN4uc`gev5AoJ2 +BLcFnAU1V1o%781aYr3*?tQQzu~|4ug2Ix|;V{HI6sADOCr#2NC0qCiJ&L>P;QjJeK^bTi0Cm|95ZVc +9xv#0Tz>$O#zC_PmDW7)>L-To4ii%l>|I{ULw-3Sg4I`St_B95|`U7w@5{5<4}lz}KeGva_!C#ojta< +^S(D=MwL^p=`z6=0)F|j9eAwwB3v%3~>J71plJGGy;@R2;$~B9UI8q;O=Z(nLyJNslj8>RA444T6IWI +Snz_q5tvsRiLjKey9g))yQ2-;JCiS3<7t5)Z@L3Rqv)f1iv6<_G2)y!os}5=>0G^8^lKB2KTYNNxM9o +M^b5T-)JY@5T}@J!w4M1dvG_RoM?OL7P~8 +B$&%Jfvx}7>Gk^uP`car|TcfvZDjR$+;M*OJ;IZAG%l%UT5?bD@2HtRIz7GQo6ax?)>ClnM;)Y}HZ!+lMSW5>e;O^s2n9Krh +J4lNpV`xD8c*Mbp`3018M;92M^q{&t7Lm+ndw5)wSyDB^Hj^X|%oIbGEOXlw}_$&lN +8k(dM}4l$%eZ=S}cy)8;>2q-kd}StL^?F^`ji|89w&*{i;GRy2|# +_=MSyk~@>l7+hYAGY7l63UdU6xeUh5lFf5Tuz3~|mL#y*W)iRTJeG;xsX|)umHq4pvicZkVZni;+^5npMd4>c3D+0|XQR000O8`*~JVvbSILlnej>*DnA79smFUaA|NaUuk +Z1WpZv|Y%h0cWo2w%Vs&Y3WMy(LaCzMtYmeJD^1FWpqacWcYIRM~$DIKeNYdmGplymI?R{7*1g*uj)s +-bxly>75{qHw3d{dIWz1{;j1Dn_+XE-yQHyK6I+kU&}V(5#Z?b!|dU5`~=R?Uvx?>VmpyXo5ld(()as +Oxw9m$B;kfj5K5R#6nKR@I?v`+?Q%ZU;d6XDhO<820*S&-FL4ABU=55z^t<;XZ2Sd2>wJOW35iu6fGd +47``$zOBTNvbWt(wM|i{?8DgAd?itIRhQ*=yeZorHr(D8NJNHP2#t4JG;LFDi@N%i=S^_{jNZ^4?*(8 +!g-#lFNTsVeV +*SQ^D1_iC{kPi*`0e*i2;@g7DniakT8++>n&>7`Jo5R=~z}?oYgs-a=ik|muqup8t=JG9##X0$qJWp1 +uobPsk7a1i04YB+|AVGlxse{d;kZr!%Gg4NY6XGOy_u53lX>#pdF0|Ue#EX2^lQT2jn> +{YhQARe_BpJmVVX7qm#de=8ZMeLpcXlntnEEY+k*%1471Y0HDjTP`O>lq_VX|my58TOjc%Te&w+uQt_ +jwdhV`K;Oeaiy!Ngx*PdwRk`f)BTyGlwEUjFKFx_i9&|pOmkk{ApM|Z4aRF&BKN@0V;~-)lypwG%7ke ++k78g2Xy}3Wygo7uE)2Mm>Fc5v+}%n$3%e0aIASux_>o4Fk-B&jB#8I7RY&3eiAY&Fay;sy?s-ujfo+ +p-WKlYSMM^02c{9m8^_u)SDj%~d1JwfCmz$-mX$ShLoMDLaLq95vkJ)QVS@xA+T?@iX<#&+g7JP^xwR +bSV#NkjhC2O1ds4&EqrVSCBWR~1^AU;Jq_FMAW>(l(4GfuqCN5NHOo=|GVrZ}6kp%{=P7Iaa2tmeoBK{AG!s-sDSy5d5q667>Uv0PRSp;Ap1 +>YOqG57WRICxPJ0wt1=&1g4S(&lCZZN^*omX+rRMDf}qNqe`q#DfnjHQKNWj1QPjR0JR0hq*Ru(r?ma +k$^L&%K;Eg7=XD)#4wNa$DVsRsiUSv_u=~y&fK$iBH@J5?t7aSca$g)pKERS>i9RKX2U3WWe=01@^5j +U^XO2X@z}?*7N*WfWkCSHS_oh{3rt(I^~FUl$C@%$fr)7HWgCHh +W>$37P3xvo1b33m&e_XL&m;or4yyX(JmUBnzHFZ1|(v$z#{c9O1e;)VHT7B%)o)1Ii$IoO;Zj9^S%ho +EYO)>gEN5&8S+f{g_*G)AxjcKzkI3#$g) +q(%5x@M#n!6l-g@S}v%D@xra30ilP8T(2MgJfY4KX^a28u$Y$ +d+#31H@V#g=&FOISUL;+GMd#0#F&ENHTE`D@mPV)#1xf*=f1mFG=h3L6dYO=Af5t<_g+c3P +h)mx{uhJIk_-1R!jyoLG8RCC9aK$V2MrW$G(m8faU}CYijkcV}m`Yo*p^op+%43DOHUm9TPC#~VNFjw +%Vxe?|xB*azp#D-=5+m*;NJ+MX|SDvR(mo!^#$iF269|Bw5L|B6>2)$(pW;@hezH_9|g!1|)Z%Gt@EQ +C@DTwba84z)Y|(I={41BAQfnYP&!T-t>$%Oy=~XkMhfN9IKPV0sRZ;V +2?_O2^H0-B=>7v_PJ6%l<2jnStIv6&aof$jOq+&Qj}11ad70bsCH{T)u?d3TWVvU=8$WVR7N3=?y7< +74A5K*t-LEgKq*ZBss#Nx%N-hR{JR0cdwrXNG4X~(v;7J&_bYKTCWe}Tpgxb6YSR{nbieBnuhxX20ms +%pHASZ7C@e{r5d4c7kY!}Huzc#t-20WsG+I91!gzbyZzW8!8N%-{Vv}IZ$&Cg3&oPwpU>7IWqs~s>LC ++9p*$baJbWNbjq7i>gqd+g|uHidYC~l-&$Kt>4MIkeHQ~(xBO_ +vyZKpip$~9?%1wD%_-<@a37EMsq;1u4I|;{3k0W@W6I+5)w!N28&*{5|qBSk|P-`buAjbkRj$V`QNdl +2%Ren|d61C1|ZnNBf?qP0b*5*m!?4uZ&WG}VNaq{2T4j?^o-s}X0zkGQ)SM}G}e1wXB=EjZQd5Cel<- +Al~ctTbWOTkqXs_kNwN^!wskFAjOr3w1l|eMB9c+kG +^`O0exUDlDNRx&XgGFoxD~k~*KDZ3vXAWC%YAvyBwHwVgf7KVQe)52xE0SDV1ahpk3 +s0L?9(5I*3^W+|d28+zv&J5L8=yGJq?x&g4o?mjNid7QaAG=mes;F8O{x>}I6(Zj6hI;b`1uIm9+kgv +2Ju(DM!Z8&45Hu#HLstrHb{XtK6P6Bp%`75qzL#_M8*F;DV8i%Hd_py&525q5@ALjOBK3ynX? +(07QWVA;PoL}se%Nhio$*Jje*#cT0|XQR000O8`*~JV&UflGXaE2Jga7~l9RL6TaA|NaUukZ1WpZv|Y +%gPMX)j-2X>MtBUtcb8c_oZ74g(c!JX>N37a&BR4FJo+JFJX0bZ)0z5aBO9CX>V>WaCx0rO^@ +3)5WV|Xu*zX2#H%(rx^;n|U9>^d?jlLigJDQqnYP);qDoS`_P_58KO|C;)1X{d4onw2wx?aK3)VbwDVh^&^kT7q(GX^qX5{uq@`q^ +HYC+%uNbedgFXTahcCr^Tz?_IZL01TvK~(qXEJEyIR^@mesN@CtSu{7=OESXuq(fAWRN=T1oviR!sX7 +*c`aQ2%ZZv>E^6>Vdc=PAS`{Jkj-yh!HeY{IZBQ!(>oNeyBvPR=0neJp`UaMyz0nBxZAl%LC|iwv3hRFE31~BE7ofAw%M`sos>e(iDC@nh +zp$cOw6Qq?*Vaiu7;pYqe!u<++o0q&356AV~`vvDjIrXt3I?hO3N)sVmz3Yc>cyGK;8N{xBzG5rvl4{ +`I((SHHt(_9>LvD6Fa>dJ{rb~xH7>o1g=xivWnB1R8)amH%C^tH=$MtK#+@+U)f*{Cxhb2$e{~_#~Fg +>Rd#h-JQ-4p3b^YMkk}4}l|fV;#k0wwu5r`7E|}`-U4bf!L3C}Lbup8}sMPA2>tmYSCfO((9X<`&M20 +80X|jyR`u&56ZG_64IWZ!TEMVUi#!0hiZCzn}J2z@1{n3KZ<=B3F5W&2njc7Q4YaE@dL40u?A^?WuNc +w~61x`u+*qQTB%^?+{sW0n~vSZmq8$d9#X?Oy4HI>2xnGy5!<;cR5&lGrkUf|yP$Rs0FqNt0X-jyZU! +!K}X^@(U7(g9h=xbDf7;~Qi4nPvF25;+nVPu&i4V+iOW`PQSGEFA@HAcYozM=`hLmJM_3strmobv5=B +=5tJ4^3A$03-S+tK0Lg?`|zRo_3qunJOr?|faowc45%n-(Hsi+r^rh?0NEh58JP#i--EPm8Mv1^g-av +dPU;NuIt?;tvf=%50U?1_s`0b>|4*>l!&ATc*SeWJX^dSt?0C~NJ6q&d6GEm>NfNS&` +wI8e%TEHq!HwJY`1G{k4dgdBFGV1G(RLJL8mF52c&G>v+?4dUnH4{f78&wB4sL-JGs0+!44ZEK;D9W8 +}b}nD^P_;{e~4O9r1oOJTHFUTdii%M>$}Mgd^=Sx(2|q5ll!VR?4lKdh3J;~2>*EhJ|KsInXxS(b252 +8zn^2NeFXESLBn#NjUi)Zqo4gtw6VN7~|;$Mtx4SWJ3wn1M?9Lt2hC#FP;R0Lm@NcQyv_#e^3s+m1QG +b_QO0HC9=`A+wsS-2zB~5v|(fBsc4ufi@L+KJw}aMdUvnH;j8wGpvI8UTw(`IiP*Bdc8Hp!tHG`Wre| +@m#}1=#1I-T?Uz_|SgqoU25SXVbh`r4Ta33Bm<>_hu!<=~_f!@|)qE8bV;As$yvr2g|4I1xga!4>^?#CnuOmJqdrn_QLU3VU=W +%?#f`J_AIHDQhl#Rppv&lRL!*v&5mD|D1W&=ARN&Yu*v|nH7KTCH +=T^bW`%v7xrAZTcl8_S@wW@p{YKqS-v<8z2MYCyaDCQ5O8q%M$Xn +AKakhB!gMup>->6qof)h(ad+xiBIbsW6V_kG36*FxZ@QtOFv?BHYKbhC`NHbkF}uFupbYUF?)kNk4vBn)U80 +jZism=>o_Tvt?QPVvammNgoydroFyw{!^f>8*Oh3tpUYJ0Et#u!#Iq5UO*LMhSPA*C62!@?thyQgXM& +&6OE83{4$gJId57YLKZ3WDcT&iKWQ)E=o4RUCAB6)iUzkE?1QIO0MvvuL;Lz<23@6aWAK2mt$eR#SE#e ++IY-003?t001EX003}la4%nJZggdGZeeUMV{B_f5Liacd +WCXAUfdet%$mBinou2e2f6h)(v+Lnc`*t*V>swgt!7qVirQTb-@87D=)R_$t0R;AEJw%;wf*|)Ei7Kv +PmQ)Epgs@g!VR2olwJYsmR@9%H#r}p*k{`&L$_Tdx1e3;){@vHeg@9aJ-ep=lAvgj^-p5HGY-rrm= +KJ&%xl7GB?SX@m|^oO{GeU3``Krz*vfxp7;LKQpqbOYT +p}NqSpl5>IR+D_-hiGi5pXK2!gZR;JU~P@&|hYGZ4OZ`cOLEA)Q3FXg@5;8f^66UJ})r9Vz}lNBqGaa +zVIhcCs3qSi0y-=+AhbXAK?=Fgs{u5);8!|ObinjiK@BhKMT&e@DTMor{IUq&OcBo5K6%`~vqja?gao +{O#Id@>r1Y&!E1Hx3-q$%T!X+CYa32C+dBd5+f926mTF=ifDK$e$t31zREM0pz2|kj5-nD%XrdLsfA; +HZohoU*KV5O2Oks0spU#Ax|BWq*Tsm3kF?9(|CUHx6GCJ&mM@%e$Pk;Iw4MXfTZ~g5i`OE@ +&XWT-z7MWQb`#SCIQe4i~(XT#6$CKaKuO!TGQ6F&a)7I?Oq&A-P(L|c^CjfAljc3+QeGuX{qhI}HWWP +i!hh?SbJS`H=`%vByGyihf9$I=B))#F}c^n*}FEA^)@j&3rhn`{Psi?D*6k|<=VO7DZCuN#%wn3pSTU +0FC1>SfX#%>)SC!$o9%?M<8Cy$Bxa-)=y^$c)*4UHY-4@7s|35RK)+oY?&dk9=TNn|4=LXmmlTL`RsH +izOFZ^c*H_>Nn`{ov|AHgp|S-$`8wJMIZ=#}qF;c3!NZHxGcupD8-P-DQFc9LW-yh5|#N4*B3&?TINJ83xd40`1EGbTW!)Y8bjI&g!LB=R|l_#_O@wjMW_Qx@d~w}zfQQA0bQY>p*i2|#7N6y7dhn5>BYHd3^wzCMNo@M3QZ4m!kC+At?O*o|9c1DJ#^+N` +@qlWnF$fp3IoTYVa9pD*He+Dj(B{8ZmSIXsDg?08j)n$E{LsYiZXWUhqqj-5+4*mIq29!fpD17BruB!D>i`XpHJW*=-Y* +^W%=tun?=2^6T-CY0Jw5VLuZIYn&&b145T1?r#sQoZyWk}T#x`!5TEd@FnX}HX-;f_0(*McHCKZkzj4Ut)o4g3oHoGuo~JtE& +TX;bAiV5$hIJCStoZ9wd-YT42$zi=x>YKS;#+15cLd??Im0)Qw!?c#!eS4zI +XcEMfG1%g8t7E6>diR1_Lr-W+TE#8(Tt|nxU@7|>bg6*2lP30`Fl#ka7I0GSv!1!x3D3=;q~zFdF>Xs +`%M1Swtv;o-5c?5(jxlKI5GYdYMi?L4(oEpF89;`W1!ZJq_);kU6nTXYDtqOm9Q;FJB%lEWp;v=snAV +nN}s}(FDpbRpHm2#IEY+oBm$U+RJ&kzs0b@C&w*pbQG`NRPxuu9re}q<--qQYtY +mF=PR}%+3bD9@_+WuPjbI1gidKH)w(VN374Lu5W7XXcJp>Z*)z*%UfA0ZsmI8|L@!vSz0IWL +>o$z(SHF@O9KQH000080Q-4XQ{G90L}?xX0OxK103QGV0B~t=FJEbHbY*gGVQepBY-ulJZDen7bZKvH +b1ras-97zt+s1Og>#w-N(;@jD3EOegbU0C_isEN`n#3AO$(xR<;RC@Vi5LX%08lcY>;Jv`bzdMT%XBj +BR5O-H;BN13cW=LLZyiO^NwZFBy-;Pam({TlJ@+Z2zRPqG^+&&5~BDcC9xjtGt{idA^I)Tj*Bnq$vwE*IBWG_epC3-DmAuB`JP69VAtyi^ +V{tNdf=rdZz$tt54>-z1O->RZ=&)iB*+V@>#8Pq3a~K@Y?$}0p53>eDEOe8tiufb~ES@}3h%J7N>q^Vb20+MQ2)EXo@(wT!>ut&n +V77#b!Q>D{YPoH@W|Frzw4+X;`I?}`SR*&_WI?I7i0C7#x{RnAy1eu7uc;Ht6!6Rt7R-R69a9b7qE +VLx2q5*^5(1nLYxC?lX(t&^8+l@08a9;y50h}op48>Z9KaJHYn@3O44`93gE=sHgih?_9%-iP4es?dR +8QxpsOK$oaR|fi>*z+^RifGD}Ox)`_<*D)=e{;Lo?I;`?Jg0^?zQxyz1+v-=|u*#*Vhx0Pnr5>#}#8y +iAjz!!K7aFK6dx=dWMRet2^^2G(w8O`XoF8us4%J`kI7q4OqQmh~oSXTZg6UFHVdU+C#MKYXBfRxI>; +)9GS&@#cZHZBpZ`s$IK=JtTdu>EQ)E>+4Iji`PlB)^@E|*Oza9el>f2cJ;a+pWJDaHL2+W`=c%Az}hD +#Cm@Yfon(!k@phSM#PX2IJ?*e8H{K`CWs0$}{fh@4De+jBcJH`{Kn1gL(}jv +2e(sCaRqOrqh-h(0EEsCJMy(b_1fdB>}#G39u>|pgb6m&>x5xAEAM+mN}kW=+bmzqSm6%6**8; +>)??{l9~;Sg!|!ilg}4t+_IJb&S%V^5-LJJq0lZX|t|tx5tct50NMcW0f>EL1jqHXg;3?U-si3Y5wu0 +5zrP@{t)W&|l&Wy3Q*MZ)SaXMgqsAgLM{{k}rZjx=@Vv`Q8n{dx$%WwcRa@@M{CwzTs!0_6T1|af~^u +xl*5{p*c1rC(Z5jrGoTMq-sV+8^UM;nXfvESLuPM6e)_;%FRnf_Le=T;J*Z^egvf6l^3|TRQ`FPX?IVhkN^nwck{s5(3PTfwsS8{(=@5P7bB +PLW4frWChtmFy6LlQPt&1i}((<4xv{sK6Cr@Y#KAozpgc)7FyP{OU$;xc}ynU2>G6kav;DW+#_AA%wT +WBs*$%W9&Z9%n<=q*07e`rPEff0Z3G2P|LbI)9e4xQK(5iwCgaJqVdj@@L`=Gyl|{ZoLiv`^5SB3C~F-+_hQe9jevf05PGV8~Aqj;3`EMiv6wl0Cii#9plc+sbmI5rf+z2dAsEIszp^9? +5!_C2C*<*dT0cht$Ix3%ykKM1Q9-_ +dHxTWSV0H<07jV=#c-2YA5&9+N&{t{j(ZIxB@g8{JBOW+h+z&=lOrUybQaVr*T?VyWxK8n-CnDk%y7d ++Z!e&y*lKAg+6gVCr>PkJPDqktaV<^rZ%*26UVU$EFfSBelTwe6vsS8m%TTTuKCTs?4Ln(Z8%4iLNAmw~hwb+SNvEiYFq4(aI)%B7}VfY&H;Xhe9)ni96XUVH&+42 +S?b0o>mKL6_Tt;))shp9Z=@=-_Ce0;eEAahKI)L2QJ!mUtk+t|irfW(VM*1~&^Cu4wmPF@QTzCzA_q1p_5ED;7p38rC +=QJ7=+9ZUi09jwWKJeB~_6?n-Q(^Lco1gBeH-)3OFKRb{at$y%zvo?ZU@=I2*q_44xa?InXczkGB3=K +SnOBY}N5Nfy>STnvx@#(LY;f#`i*YYP$n={|16(IJ`MrZk3^ld&MfwzZ{&ZOEVEquPr%mm +8Zl;qG$rABPc@)dR5~Z`-GBQNxh21EPP6Kp=%@`fayiB?Xc^+Wo3f*lWWK*W^G%p8$sQrb)a3HYlQ +pqs{&DFHcjM;tYMTi4ePuw^wiJlnsalfV$Z#*8)Yp}O>4A2R~$%IFk@CaVulb0Nf@4fAj&c1}ci8#27 +zwYtZ9DgOztq-4(b>lrVyPBnq2iKBhZz8g}1%>>Ym!0%19QKB%<%Z4ZK5NtUSPiogh1cNI{V1#O?|cM +aD4typlhIuE81^FnS=Wzc!F@4~oddS=9_-2EKB3)JXhvFJy!>gRixiv<(Cudm!D3_t-Lm->eYV(N{2S +7IcVO}_#;OEKQo~1zg*1&_)c@Q7hm~A1AGEW7 +2`^T0v)u^wsn4#1k9Z1BR~@X72mR`T +13l^ur4Q^$#F62^=o%AeL}73O3|S|&uF56@6M&=2d6pD%gnRMwLVf-0h1Dt~fN5%er?ZPUBMKyPa2jm +C8k7^h$&-wn8v{U;ag%IxNDz_IZJo?4sva$;N&G_BJ!&}ocR)9NLeY`pK_S{FahW_XaS)_WLk6)>g$C +7pxY`gqvMU0F3&O`fH^u6Tsbn*r1Znk_f*j4Eex7tCQxBdW?f6pfee2OZ{Cw;24XK9^HEKwqle{e7(b +;6HCSZ~mi%Fgp?;1v@#JK{Uj%{Yt<}(;&=@ByW$Jyf;2mV;5u#6Qlq+nvH65h%+D_Go#g<7|poP^yNX +)bAYWMVTmLXJu5bPX;o?CC8Sg}@bb(4&TURuHfLL7S3}m*CO;Iv)|_rmMfqwxwx64{S7j`jlpe34#M) +IJc*R_#@2`DMwSZ(~)iN$pM%a=8pM&e>4mF_52ETC}x&0wdDkik_o_M&@7;bGa_ +l7+-W?+KtWs6+0N_h>x-){%DgkV&%jt3ZWjk+4|^W`FWxVnL+1;ltZ)L}Gqa$>2=I4ez#64saiVW{1UiTo82>9PY8{Q30#@7#R3mm15`?J42NudG +MDL6bjW89p??7{gXP4bWO_u_=m;C2)WvDr3Ajtu1GH>v&IqJkFjpR&8M&g~M<4CFx(nv_g-Hymlb*vFq_N3t>Lld#a8;N0!3O2z3_ri}*lo~Fv!v(-DwWe-1oZMgZ;v3cDVDizw +n(j+exK%BSc?-d1i^_PGi+=Z*Yggoo5UL`dp_=RDsDibXnchpZDaCBEuRn}V|A8 +df=h!JAo(kpy7i8wd%3AFTIKs`fVWMuQ%g((fF>e`Il$U_mR8oVl~ +s}hwdtBy{to{m`Ps{v`(rq|cQ5}gJS|C=|JQiBGpz@B+A^)j(~sw0;u;eW45CEX8chz_U13%#->y5zo +gSD^GpbD|KJpAux`)Sr2IgKoKWyFW5bOt}yP^NL7=S|Vk!*OETgyoP)(rDqtbTA88@wnqfpJNd9GH6Re|IY9ZCs6*@-7mzONw>G*O`~%L%%|j(Yl57KN95 +T%K<^7Y@_O-2MEfFo5q}fEh{#HY4lRy@*hZJ%Wb3n7o2oPQ)r1=i1@H_{OvZdEYlfGp#z=73SAVmQ{s +x?FX#hKPf=t8rI2Ja=tEEne%_O{%ws*4yr4nPv0_Cf!yyaz~S>pn{2}gDKOo`&5B6BPN5!*vzXaB+q? +kc>n!tMDUSrkiZDNF|3h9R{XsqfT_y-P7eR1Xm-w++Un+I6k9r3i!eu0}*ItJwp4da9mxR$8cgO&ujB +m?nI^1n9~H{U<Gj1q>yLonNU&t`*U&8F4hbhozwD!0##0zPFDJa`fFUI+Uf;(Y|~^-P6HQ^k4Ml+MR3qc +S;QQ

    dbPd3PYyX07AGvS`hkw%MWAP9FHbZJ8U?NrV}*BO=vurX56-OQbyUZwe&UJ@9U%M+MNNV3DBsaj`2hU1R<{GMf@y&HYN>c9(;ojoYn&m84F5`8d``ZoOj9SAeT*PyBfV>S3~hrd +(&>?e8x9tT-9h}9dp2WwbD&hbd~NxUt{-*+6|nTA-{u_Ci^Q0)fDR6wv)AD;XqSrwqY?X9xV48)s!#} +N^F29!fcU3$4Te~=X}l*8*v-@-3Pm|eA-{mb(~_gwQ+bnuanK-*;Hur +&}{O$&Y{2W9u)-_$KJUUelAx!-9BRUT^a696!Q&}byp${BXNfIogNc^Z2&v=qHK`-T7Jj3ymW4zF@oy +kF;;S2qRi@HoP@n1Yu=Qf~qfpg_+GzLxPaOXtKy^M~dydog+TXXEp=H{p1_;X?Jn&$zWO&OFdO2IBNz +I+D`jE*VcFk!sU3IhqGl&{oceh@g?AU0D*y{G}qz@X%NiUr!yz{FIq!9@n{8w;S;PDu<#Kd|6I#5h2m +t-v_Mdx5Ci=9tnbQ2Oq>jX6ki@#bRo^IOMOr?`piwQwG~*Q*j2owJN62Eq0scLBwNO3LDqJfH~~(s<- +zp#;7){@D6JzK@4LdoDCusqnDpM)RZHE*NYGAV&5E|69OeCKbmT-BK{Ciy^W8d6^W^w^JTj@{`xf3iz +2CayV*d?7$`BhD-4qo+#W}_es5II*t+-hy(RU?!+OGB#&bO^#(RCE%3esc +v!gYKG`*NLX)+&^**;-Ns$>Qr-PLt`S=uv*~C2#wL`X4{@`p!dVKsfzekDj$9l%T+zs;p_)Nf((Om+< +`UOYN9y>>!2e#Nl=ydHDz&QRg5Zyj9=LP0;!Ut{gt$9#MIcmh*IMSgcSSF{c5hJ&6jh*2>wag$z6$#X +dDufC5hHP9Mh%YH0?Ihy?GF98LaDw%8#^_xL3#O1@wC{Vk&q89CfWULC855Pjw(BWfVw1?f$JPOny#N +R(EXo;w{%Pw09tfig7Cx*AfdShBJ +`(vA|dS}QP_QRiJW8YwZA=d7p_jr%lJVG4iwouu7JH=^x_i&0SXP=O+9y)xf>VP~}0d`_n6d7W1_YE< +Y6{NkTpUfp13~_Z4V?%qnUM1-cHr6`1%K*yAX`0VlbjW&-TK6N28rSzh%Mf&5>#S;<)2G{}eo9HfPbGhIo)sAGNB +*)g#V#bj2qTqJwk;V*Rc3)9>@a2`j5x0FsPmwEU{Z%@{2fGGJic@c!-^a=2S~`u9wCTHe8M( +&SArdZm-Zvu9M>}VCwyaz6im<1GqU`slvR@6RGMWT}0@3{I$1&l3X#c3VsVrk#s&9(R4k4mAKq$iPiT +z1Ie5*MR*~YvQO|Q(sfoYGO2Y)3G; +X{mh0bhAx}CeA`30SIIlel6`bJ}Oe`V=Hu+tW(ek+tsMK==8MxtHKmyNQdF=B*Ho9Kv85Yj|gEB8(@t +Lg{tl&_wr? +S6~0@v{T73nWqa4`^Bb`2#sAd)8?))Zc9oS3d*TJZfP<-S_p!wfgd*wF)OSTRrH6CZW_Id6rP+B%VEY +GSJ??BGYWcim(fS)7!-r-(;-v$=5`b_7Y>=wxKXh2aWV#bMkY-D8rLtuM!fil_XA+_Bgb#47@XY5x^x +%U3`27HVu3T8A>Yqt3Tq`&eqDi{9Xj`Tp;r$j*+B{n5`@pRgq=9(B)wq$CtsLRX@?!hPH`7Xj0~^T@? +}{CBu&>8UbQE(G$*5fUAfIzy=3Qj+F@Mc)FrYi3ocV6PWJMVb!@l-U4Wjj23;@a9xvyGjAjShdr_1== +Uq-z$tpo4aC|^8RO?D7tOd=brdVBv(WK0XcPe92*rP#~jTTVn79`FFUNeS`moA7!K}!WPk3K96>SWNN +=mS-x9xz}prC6-B;A{jg^XeD#-9o-S&Plz-o-l``!^3wwgUj|@y4J2gtdbudnLD)xlF=S%QwvdH-XXSEv^6 +x|*)VRc*&3o^iGT~1e{j&FWx^F~K#Y4XG?>b%gs7#YJ!hy_Kzz!vD_ +nXFhv$tMOu-?r2WLx=5ZhzTeT_U?vE7&CTCwQ1QX3c|lk6@FV6F-mC#>QcJn0vUu1QBkg)jE^2)_q*{b)*8J(6w0%21DB9$O*FM| +@B^sY6tF>YlioIo^!_2T)4`1QY-O00;p4c~(;(W`@cs0RRB_0ssIc0001RX>c!JX>N37a&BR4FJo+JF +Jo_QZDDR?Ut@1>bY*ySE^v8;l0j~RFc3xeK82MPNGS)1l&VtIN?EjHJmr=3iI|7ts-CI61@Gg- +tk@Ic%N>(CYdEc`{B@JzJ>>y=~6FNFc28~+5e~DruP +;TXD+?_}z6J%loVt}VQtQa;e|i%DaD7}5m@bfL(4JKZxQQ;zUgU=hCz1bQbAt;4b!e{am@R|E5mNRIP +)h>@6aWAK2mt$eR#R$OWPFeW005{7000>P003}la4%nJZggdGZeeUMV{B?AlhDcG+6Lto-D&#@SwkX@G#3k8hO^8}WHy3sPrEWKcs1Ud~RHhuH^W+w_xGSO9OH;q8m#+MxP&ty}e>Ld2q8P(j=ZhLXIcMEl(q +mBiELhXvC{dhcOdlM*UU)283b*lWRhCBfRF>;3%dW&nD~t_iB1|ntPlAJ8twpVX81(du_{&GG!}&-kF +Ju+ek@N(ZTKUS3PPSBw!^Cd><|9hLmz*!e*Ny}m-o!*l_SxEjI(byq3EKcBbbxI}Q>E;9;m5-_*b~8u +s*|RLy2ny#{f0jX1Q;BMJZOS@&Uz1trXh!-W3R*!&M>_N2Y&+}>RPd}jc}te49Zf4x%tj6+; +<4er*D<+1(vDWs)x=`Xd!WlSu(J?jO*}IUyo-=U(sidcUc7+vxFyjj8+9Ue+A%|eTVR{e{4*f=SDCfs +nG{P46Eqhy!npU$spK!Zf=Vcj3SDCLP{+$h!(iNkLS)yl^XkLn5HkdF=M{K~YR&ZI)MG*ak8iHZyLcI +6fim3wy_Xc0ppw>(aWUQ%(IyWysSb3A&m~3^jJ~v+CjN6i6(^@) +vB^K(-+#W}i+>>ZZ@&Jyr1b?-N1le)LR>$qck6|_JE9t0$b%<6>iqhE42)ri8&g?sv(+CMV#pL|jpr-fDhxu2=bXyK +Hi%a4D1ZoD)Z3bY{Vf|7BKZne0DMO9KQH000080Q-4XQqOyBujqT?j1EGu{mr(NrNQSCN9;2?@BO>h>g +L9S71HFbFlCHzdy!A^3ALe2RN6ZqV!Q-2+CkHit`y+)LP$3T?%v&dIk@0pJlx2hdI$FmhBtU%e|O)ylff?m@f$dX+Mif9IrFM2 +9;t%GXgj>+(%@{aV_^?pEql71Lpt8l?u}GuE*Bd)EQd&U^xrN{l~=;>v&6A}rl%jQ3a%TIZkMOeL-B} +(t8zxaZ``RoqYZYbxr8WsXQglMu!iF2cF0@)5d#NBqt(!-3u8z#8nS?6h27D(ijKQXkc{$(9M?0e@Uc +@*W|_!q-~bPLKo>=wj(ZSIr!>Y)qvB#4fu#0#)4(aFLQ`ttad_v&+6$YV;5#_Z)nzZPTKgY=eubBikL +a&eZU$QD%PU(d!Iw6Jr6Um9RX^v#V(TjAVos~_uCS0iS^kd_UAp)l$QpYh&cdRqXxXJJLiw` +G)fY&@HpsaN4ii=2o*tTHh;6R0r3RhJk^vC^Cx$)o7?4K-IJoCYFf8K~PsKFufGkdx}n2L1J_XXkpbY +iNYC)X>_J2oZ%cx?e7c{`-QfmiX*jZ9=xa(={n&paj*}cU}WjI+3l=oQ4RL+( +?Y&Yv`?GhL67?Ux;u$))JfV83?4XJKdE&;WwZpZy=-+$6iGQ$4%8AZfc +YXz>}K=U8GqQ$CyEqZ?{XWT6WKyTu7|ya=3C(w#Q73Q5&=(khy1$DXE4ox#)MgXeT}CvM*!oV)--Mu) +tjMn!*9w9>?L}?vLKXLVz6T*K9VG^Xq*`IJ|lz2{b}_%*Vw90cuhi>Zt-h2`6vGZP)h>@6aWAK2mt$eR#U^Vgc8LJ001N +^000{R003}la4%nJZggdGZeeUMV{BOi0Bj-E$L`4Sx+QWLzg7U(G +lNwqF1C5+bwUm?h&XprF)jULGfmMI!Je`*sZ$hX?L{g#hz56#TN~~B#w0Gx}HgCzNcmZ=Z=U0Rk +c-LtaHj*gD1hDu33^-QV5-&xcBEc!iFFm37#QoL_#27=VV=WKOE-~xlbCLNCEtbg8s+Q~KRF9n!?2jp +bq=<#l}aiRSH$JcB_N?0dXWtmFWY;y99_FK6!rh4}5>Slb*$muKcQMAlVjn}c6r4y2C9q$LJ$R#7R;o +Fwg9{5rpv50wEC{*Q3ox&Gwud`SceWtA;kDZ7Z^ot35 +G812&%v^#ru}BGRwxMQCLurF-M>oo0Li-Xbxsn^aVazUkh}kEEr(WJ$?}>u0|ZUT1kxBYCPG38a7DNztx!5;1f;%i)IpK$6vO +ZL*MLkaWwe9QytNDSUr?*UF)1MC};-51JQkbAbOCY1vJ~;qN6=H;lE0p^iYHB^%y@rK~{y@dAZafVPey2Iw^`d1x +AqTYAwD0o$cNmM8%l@)hAT4$O-_Kqc(1y;-6GTCj>euj=9o!6C`nF{klf-=b +Hi-i*8-Y>RTHM@zeL!|_88Z&|R6_~rQ70}$uKI?znq48ghAZQg7mE~+iAN{-k>dojzjQU={C4}6ZRpS +_83OJI2%k{)8$h;D7DPbxAhl$skT4s;C!Gl_u=#ZdNfiDKI615jvu^gbHp6@O7U9euh +oVC1iYsrURUC16;teea|Wd=^Yiavq&G27M2-i($|d?wniaXqYz!xK#hV0nJW#YTij$pBS>lG`&JlhhZ +%r?mJGIj@Ha0{`sg5vB)9}fqG(&OFug7n9Y(81T$D!XZavcHRJfN}Z1$(%n8k8q+_Bt9J#N!%y2wF?# +*wz*nCU+N{Z@ywSuw@VU=$*fFP}doi|xu&X~;!au8|#UvBGuu#sj~29h3Jki8AV<+9h*x!_aewrY7_f +K|BilJJyLdVn+`~Rm>$uMvq91GdxM`nV=ZA@DT$jq+Rmztu)>jJ?k3S)>0UiM|%PIC4xXl4r5L-iN$y +4bQWjU$yZ*QMHh92-?kS+H!u5meY*lW^+-8Cm36cEenEx-$b7FS1j`YRf>5;amS4j*>`L{*2ZdTN7Zt +RwLxJNJwOc{q)5@X~J`RWw*y;tAott@=s@Jd0+rM_bfOu1fQKgm}`cnzXSLX?>8*EOkX9~Iql=BO7zK +)eD40hQScf6_@Y+4s)o3Mqj6*~!PgE578(yUk07KdKTk;zg4^Q~DzCQ(=;ai7^zDL^@S;v%u8OXpndh ++&uUX2+}DxYc1Qn6|nDsdlWovb&sS6L#n}ys-(5wb#5CEp~MvN+G9ldI{g+ZL84r$0yJ6CzL_=rk0vs +aVZ*XRjdg1?Z5&*Q-C@i8$V|9DQLodOIrlF0|WdMelW{~PeHdabyIt0vAfU6?t3~?TFXz{!gF%P4i=t +4!kXHA@ON6A>JL%qXSW?QpJ@sJJI!edR(4ylPR%F&CsONnD=Zml!U;MkDO9>iUIPx*M53w&Ks`072dp ++i$ANkiINZ-ryF6eJ08sqVZ<=BAPxLj^wsjdWDY;^MIR(D!Pp@@jdaYFiTDiFJ@pfQOoeL#uRAP`qwj +5eJ6@hZE1yDfF@mV9fsgOp5-t(Fnxp22iwvD)?O_EQJ0CmXV5YBv0`!{ER;24GMZuWIkd+>goj)eC2to}8jkTi +6Rn1IWL9JEYsq!;8d)Sr(#k?;8E=sMiPSHTAV#Vgzl+!^;YeZFB)raA5c8k_CM-E7LE@X_vU1$is*E8 +qsD8bHV$tFTwI1NsI%5_M9+RzOkDP_jt?7gNTt(VpU_F0<)4$JpvMB+LDPTMPE%A*gSi#01aLSm7lE2 +*D90k2zXrXIm^St0ZJxGPD)?r}BW=F%-KD?WcmIq_t!a&@6_Rmd68-ujS}Pnm9QO40wq{Sj}8YA`-Zk^Yym)w!Yc3vU?x8B3W3p#S!LUc#XL#ab%JQisvEC(O8QDg9G|41W9X*|0L7 +k_svj7hCz+=W!mL=Abb&V9Wj)Rvv8DmcaF>M}6TXP^(uUU1BEB`?mE7ICg;JcH*LEX7uZ3^ILZ}3F-i +kly!<--n@kCxn`Q`xZZ~+Vuf??S3D@G1XK5vCPZ;P@)4L;Y3!=sY3=P$4nRE*Ze*lgDYsrJ_xhwv?H7 +Usg%K_!M(B}={OFm|W=>7?O^4e@%CxxR}hV#6r6Wy3(L1L!kiEB>Fzi~lE&u=X20gK<1}0q&SCTDFBG +raSqH;g1O(aSa604|^4^Ui-~+bfL%im;w%=&L0B@KA6uS-+-_EPkqdQjfj&O3&Z~w?kwjp6l;SXgHlI +KH4W@2nd$Oi27)0MPx5?7tCzI8ipUivK}YytYY?w-pTy=zyanhk_*j|)cS~mXAbYC+pcb4sqP@nBkg3 +7!xLFg0rM#k6=Iy_>l_K8RP`3uO2_p21Y+zrhcJ2`U*lM>&>|P~uF=k`I2$0t;b79~tk5+ugD48oSm0CRS(4#7VO?~Tu!T95uroXtfZB6|$ +8|GCKzRzOVz8$p;RyxZ;6aB;sEQj<p8Qe6|dr`rz +P(Ie7JtpT(og?x(#m2%FIV=7GNN`85xfuEo08L$s$55^ZW=lPrIarW+)EA7P}s8^N#ObRRzjYQK0JO=*K6ekd}YS5%@qq*;av02PK<-2q4+sYCQyxV% +8--+q>&kAS~+aAF;0c`oqKF~+y2*N1XIq~$ecD?4WK`-Qo~&3sKCyJ_5EGTmN!=ev$vg_2cvhgEJ1xKbzTbNZe3Nm7As +L%UgG))FN1w6(ek_Ym;K!#v(OMUYpg6ymaA3GZCwBH>izeB`*=OPyi|)tPqT#}5DF5^N>vTht)$5#(O +l^jw^Jj37Ne2Xo3jzaXIH1VI#;FAe8oqP*k6=2OwCy#=X0U4DHB>NZCK7LtXm1s#4e?oFj?hgQ;@9J8 +)L=lEiGj*78qSQ*6vO=5)ZkkEqm*}poAAEZp+TudsRWu5sc2JMwBAA(}?#pFS#+}S_=rIMRDdWIOktMaeJpDM+vd#Y%N%!swbA+Rcg)FOPH?ZNP073!SF4 +s32ulA6i>>MeQu0p5Vcu}z|0b@v|uL;F*Hr>@NOyzeu9usu6Yh$F{Im?!>Crdjn~pH{o066VAsmgqdjMzS}FE$B}iAb-An;a2~ +d+Hog7`gyY6;(yKl}}DUBT;BCeO*2m|s@w`ps-8@(aD3gNINt+o_@q6Ks&BO*B5V@eAC4qZj41GfO;L +LyX{cN#hO_FpS~XZn`+M)&3h!W1gOtby5_SGh#WpdHER!MdKBOv%WWB)v06$tkoSY-ffxgz1wc*=HrI +ai?CIIqAhXDv|m?@L~6pcel50iHpc#cXDK<5qm2-4;hW7Zva1q8>LmbU+sy%@M+tG|O>Henp81o?kFku{*JuDXsx;8dO_@FoDxRiH-EoFg>%<@JXB*zqtmdMfl?Yx%wVdP4dT(`yz7{ +R$`#QKSWnc(Lxi^>IHabP$`ONYUP%^FH9Riqs@#*a*n3%$XPJdB(1+1 +Itk9&9JE*r{23>xMv0G2hj6;I+8mKFJO4sH}gd=8`;#YWW! +-CMIw7VD+Vm#_q4VhN^LEFNk&7giRcQf4Lo1+iM +gJOd7pdJ|ItKGA0!%DmKq7n>FdD{`$bVZDcQ9s +g?_V+cRLHa7FDyd`IF%3yU`i7LTOaF*1t*y=3$;kq4-pr^iIjJ}B`08Y}sW&GQSxlIdSFc}x+Y+H-2@ +N-Cb#T&mf~T&`ZEA7G(3&YNMja`IYLqp$a7Y%uha1N}&n@#$(XL>zO-DVMQI7*b8Zwb_7)@f?x|gakE +e2~Ch%&3J5V0;VP`lFO_rWPVaaq`>YFZAvTL=#a&V2Mz@bFoQz(+zT3&(TG>snNk9y5wdIAN&p6Jofc +CLppSCquDk~`efch_+t +@CnGchN`kzN6nOZuT!4dB0vcQi}DT~&x1S6M^$hg+87FygYq8l4P!tpQN`@*dAH<84sde&ZT4R2D7Bn +A(uD7T2aOkTZuovj%C);!W9zw=4Bo5|{1fCJj~AH1oN9Kr|$e$MAz3&(q@ZW3LIixhb_#egW%lc2wb} +?{9Hqoc!~r)6>)E?_Rz9j=JUc>f5cWS4pSn_n}yQ2w3-etN*eZ{Z18R%e*USz!!LEtaf++qo-`VGZe# +#zhN&@`roJWHu)pG4l>wHi$SdCqUh`n{`*~VH$}UJ8EyG6Q1gbjnVMP*ZuN*=+$&kIpM}QkBJZW^@BG +l6W&25*-(#*lXQ$ceaJ=_aNWddGrq^?J(lRFek`t4Bt%7i7i;)il+JJIVXsq}uY3B)6vR@+2)E2+)3$ +v?XuhpVMrr_Ap9!ZGQDm_qwl6JeeZKOa3d)T`7h`o7v`pOUdxxxsPTO>GFR#9ZcjwT0#oj9 +_007aX-69=QV^cIzB7E9M}A`xPCaXTg+)k-nvrKVPuJF`m2B*{)L38#>*5B4p1hrsbr-Cd2W>`c?|+J +E0k;}N@Diu^7>$6;vQR28X?Od4G`H2ljrMYV^ID^)b5=(qLJ1mTEB^O~tf`(r-`v9yC&Mz#lfW=DVCd +Hn@Of*S?-UcMN&3x>;K7mcm=IfwG&aXil8G-Tjs-fQzPl}f{rse8SX=SBleAB-)TQl@5~=}a*mUk2>B +cT^X!JN$T2Y=N_-dk_K;lzcTSkkGnE1agg*dO*(KsP(*Twi?F&htf`lqdG?^+cpy!X19r>jcS&Izo8 +#s(=Iv2=O~9S(C)oP8s@aO^r%d6()cR0`g6%Ku8}Cbu^PLTztO8E?R&4|{`DVtP_K`p{%7ijpy9^SAI +TW^Zov07i6_xXdLa9TXON_Q6Pe{3*uv*tKXG-bA~ct-5$n_UCi3HxV;5NciG)3Nm)(3xOyioK*1aJ&0 +_c{6s~s+IEEmDyy}*|8pSG@gl1=s=&};=@qp0*606h2CIS~iRgNTFF{`vO(+x9BmIY4x6RauQmMM{xx +E`FTe{NuyhYv}unKWkO$bg$%%KoljL*(;gpX$JC=r8aP6z5w +TA4nC)xcnMkv+l0I_dS|Ade0VK;>?KAia(sY7o>-qLUI@W5DyCJl#DKEKHVFpTg!dw?{NF+`7OFWib+ +Rs@ZHl8>FMp5hY|n)$wB}CAOHXWaA|NaUukZ1WpZv|Y%gPMX) +khRabII^ZEaz0WG--d)mv?k+c=W`?q9)Ee~3G<)w4P5F3y1Nb|&fFG%lI+CA}Lg27w?;w9Rc<(uz`de +1rM#S5+iMQlk1LGl#<&4YW;>#bU8uo+?sN6uqviE)$hx-GQ^$_3@Zh1>0tlv%JitP5%;s^U5Dk+q^cF>!3_wnMrLf7~(452E7jiQ&4kvSgx@><-pax4h8H;#D +rVTru9|@gj<%#X{37>-`?sGCyl+zR8nZ?ArRsc72t1bzMrs0<5(YS*f1ZO$Wb0mipn}va;uTWOr(C#r +nzj)oI1v3E8dKho%GJX61_A^i9bZs(T}vI2BdLTX+=bu&jaV=L6^EYitR2ErscrAB&oZ%bAWbZhWeD# +*ETRF*ii_hEdS^wHu9ktyWSl1q$pm~gRfJ}M|7AR*nR?T=P+OE$^ +Ufr2FTZj;#4r!0Zo7(4#1$uSZip0407%c1^wJp=M%8oou&ligY#WkkQNP8vlW1?CHeB1eEK^$ZB!2jQ +?u*i-$fbGB`8Lw)>r3rcI7F}Da4S0DT4|M2D!FMc$)ru}nm$-H3Z2j--$GYMhdX692gF(Z{q2IZE1S4 +WYd^5zBpR9xU0)jwtYPvrWdq)obrm*v)Um{1D_H8Mbc?rQ%N0N;WYQ)w+9wXULu#aE9ST12nA9>fehz +Uw~&gvZ_q6~>L+S*?7cHOk~ohTqW>nUO*RAu>w4dDByFSdP$K!O3MS=Y5oJ#@vQs#dH=EB5ECl>FRsg +dd;;gr>uWd0-rh=iD0wv%w>ulFwb7H3%)1@ZAp8Y)@wcHEJ_Q#FI(YgwmhUnGFc&*`ytG#yI162Vv0sd!w*$pa-Iu_Okt7V=V{DpU*1DebltO9~;!8AF4w|Bk +3)vptZI?0o?mSh}36&%fU;4Z$LCSWvXI_u50Ax;$knlL%&7nb)n$G#qrdfE`*f8+QlFL@N +a)$w<5nqOzx3E&x7c7EK;-_+UJobcpV*oHkR*^LabckE#Pg(8-X)7Zpe1)#F0Z3d#Ky2lIexwx +{MPc$V@>Al)47w1LhD5nQi5A(#7?kMyc#HOHZaWC|OQiftlhTwWy{ufX(A(M(3#FQx*Xaa(_o?xxP|p +IVSo)QX0P6Jyo>K6uN^?2wu{e_&p%%`!BTeuj +!^ls-72>6e&pNM!grx8=G+XankMC-yxi!Vq)6f?kBU!$+quLwXc2^z%gjm40u0GOKVm`Eb8qT;AVs@a +soN8IZ?&SOml}7z5F4&>X^B;D5noqW@K%yQM!iR>KjDC7#P5=+L;L8~md7h#Dkbq3Zvot@}npL +jw_4A^bSOid%{4LFbfRT*N5ZJ-B;XoHDUJPBbv7Xx(7t1>j8$qgu!tni>aoqf!f%avBt$5*8q7rZWLX +nLq%19!OVG%pAO=;eoL4M`$;mzp2CX8u(VALoRs{TanYsYUtepCRAlAjT|6c95Cjnzz{(d#~H#UaaZ=}d_9IWvZPE425U@ +Q|+z~uh(^78uoAKqQ2H<$0P-&}o6Z~lIL`3bXuY0en`@Ykz%Z_>*j{}xCnEq>pr9@@5_yV(|N!({`m5N;oDQ23!CI4E9uUgfw +FfGzqDuXmtv~@-G4mAXKP(r3#vinwaoJ`y1v}#K~RO~abT;IbssVR=x3G-mJ{{Sls*eXn~Gpvx|W~jh +{s4=!~#PC~J09+xBDTSI3xn;Omvj7NZV3Vj|V^)m8Ataf}!MK6jdJmAb*H(_ +_OUqJ7XJOP3^GM!;WOx4ktuJ*~;6mf91nbhQbDcy;T#3!V7r7cz~Kt?BAci$8l1r9xvvvFFYXPW9~bV +_v%4j#J34cI8C!;u^tQzZ=W(f}Ca&sRUb`SAG@qlJLfd4)#H-UGmAW|*c)HUh5Kqy?A+0K&@(;Iyc78 +H@UYR~M3sZxYF8=FKjR$B>w{c2lw +&@SW+r#umWf{$W`RPErtN%l$Jz+fn!-LYfmb$>zbP|hVO;^0T-sm6}Gx8w=`ci^QF501V9dH$HKmQ9P +|{OSW>N_1iZKGO!3AtdEa7%rS-GVjaykAFxWT~1^=*zUsR;ROx)&@UaC1sCn-vfMH0-Po$wEloi@{7< +O3oO&f-7|lzspu&{32R%G5$__*I83;WHJVD7y;4E88agLtMeRf2Yt6sH3RQe83$(tgN&(dz;V>Zlq8<@Lao=ZPZJ^*;ofCaY5$NDGDd(=(OQnr;0{HUf?Y%YkN2pfagI1IuixiYV##upM5YO4uzd^nHoTfgUAoeP+<~)2dKlAvmvB#zQhlK2_$92CTjRx(5 +rwls<2Io)5&S$*I~&fDHi1*5m7@l_5K#v16WZ<+n~DZR&2f0!`ZWwKj?>PycS#s=Wm?{*H2=_$q>I&(jf2E{7v1ZsE2Q=q>D3Tnzk-_I{A;MJ)Ce1faZaXCBQS@wkPiPH}0ujG2~yELGj&=vKIt!`ey56m^aZQa8N%m>J+=~aI@~AvnCYJtG7O=sxu9E0V(N^F^>a6nKEkVW6w4w=K +tITrpU`Yh(tU(Em^7YGOo$bnOAE34ehXVmWiC(>me>ZyWd6AGzza=(4MKB-iRIYIB(8uKBpY%L!t>Yj +ah!j4@g9lFn4&A3N+nhQYY3bj7CpYpX?vGVqS^;)J4r`z}Y9F8F3~@bizK^(6aXgXZc#a=%I(yhunQk ++?4!6tu&{53Va`Tg(y=m-I+PnX}EhRQDnd#68Lg2a4$#o{>r5&)U*eW*v#rOrUU +5mJ(BIhW6X8GYe%Inv<=?x%Sj$sO=wurkw-;;q1`ktuN|_N!|W=G$)oOR)d|LB6&(RPkaI0imMD44(> +2qPnZi?J#wtNmZR7;l2|fU56aiFJTvJQ$hu}h_@@KhKZN3?9m27t$Ruku_Rh%bg|f_6@47CKb3%QIik +!KrU!$?)1yJW-+->|Rk`q0JUy%wTl2MDcm#BOdJcj3LS**X@5 +ewt`{6Z=aT23*s-82UEE8n2xrzdbET#irqik1kNCpkfyt#sqGU%`8X$ZB;}acoUt1s*|OuOZ5=(y3ti +n9AGcQjO`C4GbS;paJU%Y2 +k0>FFH2NJgl5SW##}R&n%YV7o{soyD!ydrH71K_0STNSh90DLskvw87_!UMXxnBq +S5-Z{D|c8!UZN9quhtE&!EJnaI=aysq5ef1b)bshfAPVcJ>o~*L|=R>1Nw%i$KFegGqv}>*F8J-vZG1 +_%Q{x_3}2F|xF>!9DL)f4Glvf^mSE9f&?h9vG2f+=8a?J`;_{d1Kh9so>PRKb4Srs}EJRMF0oY?2mqa +Y`@3Y&LpHKA+KhsvfWqJnkexf0ZYmquEV71GO>D2C8t##xp3HleCf2G7>9z{}HZCPhxDdl|&Rk6 +fq4Q(w-AhP(BoH*|557G3i9U+#VO>nabPqZZ(z1?ZG +bFM)9K#^3v>f-9gBc)DrsZH-C`1!VOT7eHK(V<&y)bhxQ;mSO@d*!dmTpCrwI{cmZT(H^4^yY+>79|XZ;IZ=B>vCwDV +8VuWb}LVYLaVTiTBH8_PpmwSuRYHs-mQ}7H~ +B`|K;N9azA@Cq2^yP3yzFQOD$J&FmQCH$!TqC0**I+ZM0ZOTCl}pJQxg8L80{}OqEQ}-4QuW*~x(5FJ +n0|po|O?`It;!kaHy?&HZW04a1t97o}hY&Su$})=G~cxfYOf$<5k}n79`z76v+2=7bolzf@|}*DFd>% +Wqj!ebhH$+}f~!XSiJ~M$H*so93;+#nBb>3bm^_7g(BKe8B2Q_|a+EQk`EG>6+?b2k67;_4S{#Vaswv +&We(2o>C#U6Y`u?WkxMCGs6&~87@ZM7$SMfT1_A{aE0r>b>8yaI3^bg@o`F?Bu~HnAsjP4*)b_uL{q> +Oe>HTTdy5D+J%87>Vh?bT7@Mie_QXcId-N#U=_+{PAO?=NczqMzMhA=;-;wWvgk_NMiVDL9J$mr=HuY +LGXiogfsJ)7Df1gu0*=Xz-4atyv(}&4uOdj_<_ZWOhl8|9v#|_Wy+PS}t?L`nBV*7AZBWj^Fk;?W=?u +Lt|i2ThOx4gkqE6d1|g +jkU~qUnlq*@$tJs&vtPy>$NTxMIJn<0j|H-X5$4;<%&ex!O2;*YuVVN|u7k8)5`?>MBCBjHpUS@9sY{ +(D&V$U~&(K7m1b*yxJR! +`c2}E2m*w2L^rN)LjQ4^J>)F4f`1Wrauri?IPX-l3xAu*FVWWuww5_XJug)AHfv?G#l7%Qh#xRafBNU +oL@h6sMcpgg_hX8Ljt00QooVod-UQr* +eQdbSBjoJNuJyjH4oLIJxPB*{Jy@j1h&x-H?Pr13_D<`Y5z|8K9DnNme7(VT;^&te1x+enc-yDS+ySj ++eFj))4@eozWktG4*%;c6BWsaO&D!Ba(y_69RJZh_pjr7a!fyw1Q2z?Y(G#QgvNeM?BD8evY*icj4q0~{z ++pwriCM{<8`(6eOEoOg*q@H%gPO=u3W^LfESn41xkz{^Yd31lPHBJECKZx1xZ4KjGI!>En*kA*|1}@h +q(TB8$q#IGV5?11)TQ^;f`g8bTrCog^_`Z4c(er9|;DT2Ef%@a2YvYRoYjoaIk}QzKN_$7MB&N_|O3`!(U$+|Ux%s#&?#w^n_5EU)o1gEOai3$FeHq +X>{0!LbdSqvE-UXh6djqz2h`1}XH)>;=wvA*iCViQz%J4-7T#(OZW{s>AjvzmuU(OM788bCV5IvIIa* +9=3QP72_`41LQCPW6^m$)Ii3Diab|1eUNRVOZD~$svlZx-bAK@_w8<+>1ScYn`$iC7t%Y^PQ`Wt{uee3JTkOLw)xPM3mY2Ts9v34Nyx11QY-O00;p4c~(>XdH_p +U0001V0000X0001RX>c!JX>N37a&BR4FJo+JFLQKZbaiuIV{c?-b1rasJ&UmofG`Ze_FT~ups+GP8$< +;pC~3-=|G$6*Hp`aPQbN@*g$_`J<)t2scH*1-GZ9*mYV(2AoVfbRM)?f`T!O8zsV`QJ?77H)jX><@T+ +@d7A8~*OP)h>@6aWAK2mt$eR#W1KK|^)~005W{001HY003}la4%nJZggdGZeeUMV{dJ3VQyq|FJE72Z +fSI1UoLQYombzF<1`R{&tEYL545K?@LXw;?)DC`5^yVVa1X0h*G-(ps$&P+X)hrDcV_IQb=oWt`jTdT +e*VTDPtr6^9$9{1dk(o)jtM2y9+;HShz3P<%~WBN6zvjGH`+J|4=Hv@X>^S?Qu5pht!%FX#cE!-wvjx +TxUQk7z4oo@R`6crZUrA3@?$ayc9=5T3gx&#S(ZzY?U?1;9>w5)A6}EB|MQ?q4R=w}MH*?+6{NK;TFJ +K!bJYrR$*kRy^$Ki@cBV_0N%9qZs)U6?_@$r_3e7Dr*tIIJK$lQ)cI-fk($Qi{ZBQZ`(5-7)x4{5w_@ +LNMlGjm-!V_5(A}kRWxwcMr%YGTwM`#SUT={@6>ovuD$$X?w6$fn!Zb(%#hn(0;&O+EmvJOqr_`{Jaa +FuTN6+SqG)dH;+tYehwBwWy0TsEXvJoiOTF&5$}#g~=0EnC_J6s2YdT$JP11Le#LZeU>>o{LiCxLGu-4dlQAe0XPmSc_J8+>7=&?mLbl9A=cZ;dDG%nG71K4XRdOaOnWP`D~7(@m^jc%9c +zuPz(hRu{{MAnLUd7R>|H;P1F-rIAAe?}5*1d|QG@S +pz=K|@yqD>7cs^l2^v?- +Ql^1>YwSkQfn!m1D;Nw$qbjU1Q2Co*(9d3Qke_dlF2*`VBi+*-h1~Vn@1jYV0rqn?RSM;yafF|E0kqG8~aSHXmepGxcT4+zT2>DN!~G^lx)G&Um9)@QJj~kt-UUzdo|?ZyPj=DE88O^pUm!+a2t=!!g?QhH?Ip{RKnt +^nBfoVBYo53xW9kKp_mywnh#9J^h$>YekLi$P6{Gmm+x9htD9IdY{kS59E?9u-55yu8qM?mkoUSW`ZM +kiF6EKN0cs?8b6zGn_@4v0h$jIK9Vx2Puwhe%)xd57I+#5ScJuoDw7>}XBW#phA6MXY|h(spk$OVaA|NaUukZ1WpZv|Y%gPPZEaz0WO +FZLVPj}zE^v9xSzB-8HWq&OuOKuSL~3nix>%rqv7SYdPIn6wX)wv|KBR#nOSG+47PTZ5k2lDF?>UF0L +|vR@+5+7G6UXGaUp^jkihV7lWo}?tE19<&r-#@Z$@IDBP)w|ohqJWV!uzT+-bTnUr(_po$5vbQ_huR`S9` +Mk00mk`d=3pAFi*bIQgfH*SZju8mRdorI2%WRn+v?6t8iu6x*#Ak1f|fzzUbMSxKL4)vbj|Ql)#IB++ +a(%gR(Kc9CW~ZgqzCGg{6V`~*QjpWj!i=9y*~3)oJ#91S^B?6Bj!WEo0`XGD74tZmfP(yT04UdSc8-i +ZU#J8r0P4I~PAj4e@7ZG@%5Kjl8fps;i;8r|_Z>Jsw3q^wge(I>^khSh2I3z(;v0@)lgSB-fk--`#nW +FP61i312`@EKHjcNXYlWX0{VW#?>lxyDYG=38Fsbj+gZ+xaKAH4Aupda1B*#bh30Ws_l}I6c +$>Y5mkbf*NKY;VIfSEaZ$l=EFB75v704gk48l8YI2^D_=i~V2(SaxGNTpbW%UK}Ci$WmgD4MrSrkxba0c-_bb6 +yfZ@k7?t9Ipl@xwr8q45gOyj@z?9FEQ%mwfJg#+EZO7S4>g6`|#^9}7ovYgq0=n|ae4w_h#qI-AX5-J +Z!fO7LeQ%VX=oYrM9rZd7QSx7S;*Xk(a%?mSra%7lpemZSxYSpL8f1~)?BA%8^jX3s0F0uP0(f7rg^s +70bFtBLf_HoI!}$xiD!p~(+Q3uXo~Du$IR_x7AuQPg8@2a6Fa%|NQFn&68d@-&lV6Nyq}sjY0o-=($V +MtTntjP%bDU^qMxo&~D-A5IJ1_#2xuDQU+sA_*sqK4H>pB~JR`g$d?2JIeI&$lVI!oad0#39#}eAOGj +d&Qy>h{s|_mt;1fYe90as+p&#f~|Ld51GQz9L)iHA~Rx2MsUQk4?j8 +>jIL*d>ji#qHUPoTB!Fb|`LL)Py?X+%XNjM#hj)$fJtNz}JD4R0=RI}j{>iWcCwlk3p`8ThuGk#CDT= +)9W)TY+eE5$J0s(G~;YBO#iQ6i3e(JY=(10msZ{;|TO{g>v2P-C2jBe6%3zCP9udnvxDT6EKfSG2r_Qfu{6(I +Ny+~ZxfxgmMp39%AQ-rY7n|-a)D4NcjOYI+!xj!l$!!Wh6{Ot~pFJ=E%@ZzpaFN?Y=ITRe1N>e?dG8i~`gO|QRZqOW +bVYv)MSAD?@bX1gHEVh<1}G%+;d41u>kbWqpwT7N$?(+1%P=djUf}5coOQHfyPWV4(>{yAx> +cibFFkaGZ#b?Taoz69$un7cFk@iQenV4YaB`n%j;rtvAP~4htdXS^et675nR7zMiwftEM;f`!O$YCS5 +mlCs{l~U*KE!Q@D6sH$|C0MSovB){R@48Gis(OkKyRwt#Nd{yoP+(e?M||q37HL`UQkbo|1X +z2Z0J-NM-vTIM?#Zdpp%PC19Kor98=X{{fFq2BNi+wPN-KI@siA*2Wle ++(7ydoILxvfo*f@v->YF_V+zu+8#(G0%D}`Mp)?hwYKMitLjWUAI2{9v{`qUz;mqg1BKInjdZpEd4r$MD{dhZJJD^N=V1QY-O00;p4c~(;zHf)J80ssJ&1^@sb00 +01RX>c!JX>N37a&BR4FJo_QZDDR?b1!3PWn*hDaCx;5&Y5WV|X41t0TUbE*w_Y!(5yO6NGgkThV> +}VrPT79(S-|xs@QMPV-X+zJ&kg3a*yR*7}Yc}qn#+NtDWyZyDzVfTBpdD!iiDwRgV99Pm7)d{QfRGB6 +~tKTX$tIn*&(xS_UZG+5Vyk7=rLM0MHV4Ww~QZw(kutmphHMMy$oPd;f+LIVTNi=XjNm{h%9{attEG6 +r(EVCp@D#5IUP2e_al8*n`y63lAt)9^E<^nS#7|B{Vv1bI5R@1SM0-;8YvPM~Pv;|crJ%qQEOiaQC^y +^L=)xw?(4r(3d%o-aoUZxgBlfxKZkH+V~OiORU?7em7kioYf#LBIc5f&hp7sBw+$xCW>wABAYsncu*B +4I;qPN$QN;FSy0cK_PMGOzBA?MnoITYK9q +tawV&SJB?_I}7wP_}6oT;W>ww6R%FdElB0AgHWvUMTD5ba>jV1<>2*P`|pR>DIdrj8TP4C52xv$CiX$ +2|5|>U{3kYB(4lm_3rFwZqWo~}`m`@{fEvGP%{!Q}Ht*TSn`v5sw2g0A@H4)(w*ne;-pJugJO@x%yiL +2&c=c1k(Wo=ZSWZi>dPxJEEj5_F@&5o&O9KQH000080Q-4XQ|fU;s?`Gk0FDa)03-ka0B~t=FJEbHbY +*gGVQepBZ*6U1Ze(*WV{dJ6Y-Mz5Z*DGdd8Jn0Z{s!$e)nHNczLjQv2WR903Xn08Bnad1W9j)Lt7ZGE +vMQP+mqz>(jfo)D9Mh0=C*Zyu`H4w$&V6Ex-W7rK^%ld%~F!D@`Xo*mGXQKc_E943j&q&_lT857@0g2 +G~3mZ8!Dt_1O+YpkY9f+K0*MtZCl?d1UQN(!q^EgxPlQ$^9;~4mq!tETFBEyErOvd7^?vew~N +j7`gV&Bjuzl!W8P2pT)Ttn2@x-Ba2)q6IUdhT^(frUa((k9aA4x-&+zkV^Yb>aC&y#+dH4Op>14zGea +E`@X!V8C4<^m}AB@)5>+tH+?W*Q(8O&fZgC9St3`N +<=HrzJ#^{5$aX9lc)$KThEw_tibEx>A&A231T6?wl6`HF0#0?Y8O>C|U1;GBOw74+#<ak4 +BcW|0u86;=hSnxa#^-FdBk1=xWK!lCY>GKWV3r638Ud9&Tr-q+npnbTdCa}sj$S4r{xOtNQeQMbG#Mv +gZWq9h5i*`u9s1YxNQ-Us-YXJmfPQ*Hb4Uh4_JF>pFu+McJo-n6&-*xQNpc?)bIRcu&wA$uBnFR(|@@ +RxuB-IIF~MN(id_gKf)El@uZ%~B7`{qMQGj->Yb*3^bzZ_e!X|0brJLO-+gt(lFWy*afWmTPsB)AI@U +v1Jf0tL(xZ&&$I3VcN~#ZO%I{GkwmdP@VdDHQP)f;ye6O^UNJKh6jBUaL#Bh0$!{&HVi31K9NDld8)T +}6z>_BbXyHkZwS18!?D5tlN>~A9?+`wr9_K6tdb4y%)VyEtSGv{frliH0e2tH5HzjR^BY~QH1AZv3_I +wfp8ouVBqWpFQ=*=cuyN3GI$oy8kn3ZB*wG!;^n!X8bKGu&?tT@DRHrws)i@@^z`Uj=i=gAShs}vBd3 +K%|>utgW=_P+a0GSZZ{TEIp876HnH=EUF4qx&T_N912jI1+#NU%^vRR03q7y+>ocKsU|xg3E1!xY>I!3_-SNTUS}sky274$p3yb{E$eAcG51od5CR +sX2|)?2WLjbp=P`xCEM+aYBwp=PbSPa(XMOGHdKhoma_w4byH_tP@>oW$jX-LwdPoZ472jFI>zL>5KT +;O>ZV{-R!$UMUDQchR1M|uEZLMrvd#oeH|%i8s@(8jsGp};M3XhG0H&M@!*Y3dcXJn$#r?&_bqy!(CNlzioo|0_S6c5zpCL!ZQ`4~_j^hDH +jQ)~;GM_LTc3lMV&|0R*jFIkETjwk5=eZ>S2%*bb|SRT-q324tYMb^-qJQmHKATpX-AoW61(W` +AG*YEM;b@Fn$Zg-aI<1-5hs1ziCd|Ol?rUi%K$Z${)UTsOmDoWUvOsB1&Jcdtg1Z1C11^5BUI3+clhU +PKJ$V0)K7D5sQ&Hu!$#Z3^93W2#LxDEW&HbUXXAWG0TP(>u<2A=ZALQvSLEXjf8V5&`~;$WZEH +LXa)Brl#Z~8K7m>mQB+6nEX=gD#mHb>HpdSJ|E!7;K^UOM!yy4g7eNN@$W4L0a^Zn!lz6I;jb&{ +Z7+~4r&Hx5lgS<~hLUmt3Hix| +6SHqHQ=8)VR9$T@2r`ivDh782R6D6kE(1bHwuTO?9?Xyf3ei&tO`-N2(8&~QDCG~IG?`zW^DA?V)OhY +~@P>S%P^%r_>ejB&E$MWFcw)A~&*l4-d4sK~-@H#S+q!Ab+QDQA3rZb|T?h5n-CFDc>@FNCu +uLSz;0-|78W38>&Z|hi2a>a)~477pG0jD^+s$%Ia`W>_{V-eoHFnWYO^4cL+o21d$h^J +(&B&UuQl?(hT`#d+UG*nLL8j~j(=MIkeWgd|H)IEV7VD-303L@yC>!16esOu%oh|9^_Gp}1G{fiXo8L +ZO_bgVmbDC*!_2sf>>F(OIISKNqTTWZ~N9S4_l-ZCO2`q?5N7L5Ba@ASG+$OJdU6?`x)>e?t1bQi_?; +x)c*@DR;)nTP2?3-0bPtp+ywjGJ;sX~c>zHKBBBL+0#kO~cXVs;Tm%O2^Ki|R?$dyat?;xt^n*YqKVT +9>p6ZF59qJ|`b$z9_QEoLhxw&}4R@I;h>`T}Zz{$V0Bj&Q&4;CW4v*kbtVOhNS1#_(9mW&DV;oxSSqWT6sbe}7wgIiGQj-3l`6+ktM716ngwMJVDy1)?GaHJycP4eP{La>E%i)=_2as +M*zV39Ylxlm5;s^$JwNmwuC$x`E)NtVmqQS4Av4q~80+KKgMUh^*gc|0IngOcmXcN}`S-Ioqp+2873f +_>WDnGh@6b}RFJ*t0+0<~tZ#a)m>Oek|y+)mJ&HdwQ1wkMuZ%Q0$IaOAyvx4J{i!;dJP)}|t?>jG5sM +>R;Q^2TZVHmQiCF9U9rO?)a65J@#!}f+;H7Y}~hpw-+=7EwOFD$rTj%w%@n_-+KWT;WisX#?k^>pY2< +jhGZavV~DM77~dNrl_@&C3a9CL>Yq;9g<4^;V9=3f&EiZxlH|?dRd4=c1CL@VZdXrpCmX9qJO=RQL&@I>P~> +bbyc5(FwqR(z2#WrTTM3zk7pOuNUuIUC!RM7@4`;y;OW`i*??^H&V|t+UB%s-+A!(>i_TdX&L6oQ?dg`u4B$E+8?| +T_p=DZ6{dFRHz9IiXdu`CMwrvSL7RSM3KZ7$iOo;5nz{DtgP^1jr5}So_NO{U*kQR=j=+eA#_s?w1s> +ciNE6ncQIo-|3ZAmjQ2B-2Rd$Y&apxD?$kvDu#bT+$O={NUk%GKqO!4&9RW|g*pxSH9nU?wFxrNZejf +q9ahvbsy|Hs*nPffnF31jUoSYy4um<)z_T4TPz8aNjFTbDj0ySEOxfzb{0r{|``00|XQR000O8`*~JV +T3!3Cp$Gr~OVaA|NaUukZ1WpZv|Y%gPPZEaz0WOFZMWny(_E^v9B8C`GOHu8Oc1*;;c1Z;% +lbhL +LbSm}daRZ6x*_NHe_bK(#F!kINIh=m%4r_8L3bQ8HLc{Mrdn07WnpL>57k +y42c~?s12t*rs!Icq}2-KeTT=8S5o)upi%9B5GrGb2acM>q|7Sx}vMhXFr(n>U(QVS}2ipoMZqpL(`x +B|ZOq8ZsK+EQ9^^clc>$KcvRwk_+*fOX+M&!J32147Hba%z~WxuN~R?9cL*Ys1W-;lgsV&&}P%#nsKt +Nfg;tq8^l$bz(2tJcZSA!%AjYKtQ&5_b8O?THcvcD1#A=&b`wGJkgZ^4!W+-qqIU7@jYmq(TYfqE3ys +VoCCzQ-AgI$SPKTtR;yLXE28)Ei9VtWr+Ba-+Nd?L8yob|jJ*9oKFf}8Rs?<=@?Op`ZyL*D3QO%T +%~&FDhRP~frQD85x&`aOC8p3lKINoK+3yH*5{?ifDD6N~XA%sx_Nnz{S%ClWb9co5(O{UAu8I5XS_6g +A%rZCUTc!05fV`hs5%?t%!eXTZW1Qb>-g{C`q(>BvObErr6{(m1%XHA(!1z+2*D8Av7;9XJ~$ls)g## +hn=jYc#9kIivyI^FmYybh<`#T;~z=$41LFQM{FpkRrMR0JDOVUhztNBT^APRIq|TN<~*R +Ly1%{ltF(wn9e+CIrNMpWVNEAQg~D2rwO~a!`F~serykMUw3{!2{eHX+Ah +@{7h~Y>onlE0AeIbTAO|oG*pnf1cN#L6gPu%8>!>SaRN6WD>FVU2N_<0>3u^)-APEc&OI>p+)Wf?HLR +^oqqU7I>oikU*m%a>jtwGGu#W6F8Ty{9f@ypIWPi(+mH-Eu(ctd6lP0(g%XBTWqGEqn-Kbv!r%qS(!N|d^kMQK_kaiMySkYB(Ab +t8AI0lTkVeReDU$eZoEEVdx%pBbalh}b&jB#Z3i7L{gVsA~0%{!1x9UK^!7IpaBV}-tlJADDYXqnOnp%3-j-f@rbmj9`DzzzvuU-RY80A%~{47TkqSk(YV6-TuX3j`X&MD%rYLb +zNQ-s-rbSzc+&wPv-Ay<|KZbryNxD+6I>NR(twYi`yh3+e3CicQkHj_trP!Fg?|8$V5wh9Wks6k|o^;uOw6m@_og-)Dr|kk@Ni6#Zy1DrC^QY_IFK#z8AqQxWw9!-D-lJcg +QVqD-^GlZGSOJ+iX6cM_77g8Y`$pTAZswSC1+3=+%{$3OY<{oZbFg5*M(h-Wq&7xx;#0v1=Lhwj>BwH +y_PsuZ%)~OR)~O#pHh$t;hz20E5v{-!`TpI???s~tR_3F5B@oR54HR$&VRNm$KMS4CrGRdQ9cK +X~d@CSuR*!%#}0>;bp%jnJxu!7sr1vv8Akv{>QsdRdMU}@P&Xrq25?`>`$>5vbT#U|GA$nAk!ZlTf+e +0FAaRqOlBi2JZ|Nkj9qR6arb!dE|XPpk%jh&kWNKW~~NIk9X4MCOI{0>^jGah-lo)t +^C-}Z6BOOP%gGm}HF^6ss1Y(Z9uVjk^M^&g4W=3?ZO2u1Rv2eAH)t{H8W?I%+z7szPu(`=Kmk6RU_Xd +9fp*k8Dr)g7KdcEwMLHVC_0l2ctSnOi4!wV#;0Q8rzZ~R*>6??}vy3W`cVq`kF$5`$PD(+{QP;@g6qJ +Zg8&DjMbWeNE?Vcvofz=w +wK0}dt%1ADyb-w{K3(~OZkqt&VLt1Q<2_G1cHYn2I8VVnvEQpmbT8RdP0q}y`;wT5x55}T~0x2tZRba +OT4_Xi^_N)VsL1useawrO(Pe75GN(U~tT;c@@}P)h>@6aWAK2mt$eR#UyLkZq9 +#001Zx001HY003}la4%nJZggdGZeeUMV{dJ3VQyq|FJy0bZftL1WG--drC3{U+cp$__pcy46q5m4+a8 +7iK@|*0H()?96ie5aAYf_f*kVJIDoHt2fBg>S7A@caW4Ed-kF8k86)Z@ICvp3v|nr(9all8L+&eBdc}6)NOPnlp^~#4*V1*b`98^cE~_I$JAwP +FBNbTB_ZS$!ZY(2R^r4!w#gq>nTg}%xz6e0Zg{dIaS6o+ZxnX!Dz_+zjt?k+~1!P!IS;zR1r)PmU>7$tg0J~}?%xLi(xA_5i!eGnZ)TpjBJa9)f04 +{ZO4-^WCM);?W7~?(HN +_sbuyoT{4sPYj`|DiPqA_i-~mylO@(3U)ea(N`LPl9$!9zq0G9^=)>pXd1*O)H%!>m9sC(d&I_;z-@T +#4D2MxkP`FJp|3E{b^(#;i_OmSXI%$pBWIF1zFvazm;?RO&{FyMiotAH_89Cf*zN+TSgC{E-%l7nB%p#{_h28&~4E-cLU~ +@dheCWgUSkDeD+cS+kMk?9frD&DV=NN_Dn#UY33+b>NA)Q$_FB=4?%a3dxEOKFgcs!X*Wew#Mrnqr?Dnh +_ZUw?tTIL09^3sjr#rP(R2DXP;#dxkNJzcEP11ALfWu7}E^fpo&eq$p7I_}(xH4Kf>-eR?=2blOmsv? +r!T5i}Ix{jeImZAExbiHRje@-1aN-8hSv`|8Q+i*F>sMQ}F+yw>2`7n_~ApCnCsY?ChK;|oM~(D?*dD +5SC^8XvwEQ}><_)v3K6^v`~I!A$}$6c~c4a7A{`HxuVavT3=x9?N|hqP!g96KTO!0Ncz2kI$JHk%1v~ +)?cjpWI=d}5u)Yy=SmctBKmwmLiJu1!MQXTQCStIYAt1VLmvM$HF&Em{=}=#Im~ef*&m_RKTHuCa4Uu +n-);MgsQu5{@RoHHt#xo>H{YmHHVo51J3q}_pa3^H!%MFq>zznbbIc!uJ-Zlz%fpAvW$%*o-8Wn^p`y +<<;0*9N9~a-_l1o{f+iB&aT75lN(6B_j4~>+@zpxoK$XbEi!mxtmwKU+#Fn#h8Ht#9D4!x6M +OD}wZv*P8!0{{Sr3jhEh0001RX>c!JX>N37a&BR4FJo_Q +ZDDR?b1!CcWo3G0E^v9RR!xuFHW0n*R}cgW$f&LKY(TwiHbv2*DX>j0n}tA2Bbi-_WJzk1ZIJ)oAt}m +|Y@ShQoWUj3f5hX01^@QQB!{ED#i?c&CTuI-6a#H!LV?LqBQ`2Poc%tC6mE%VGF#KW- +e)4*9d;7^kZkU?Dg&Y>i1uMx+5j|OwpEZ3ANw`?UWMMuNp+h@WbH1uRb0D1pSg_SuL2ghf*4T-wsX>c)Y}Tg@A@=RzI@N$1I6~zXoog2k9(nmFbx)vS2o8F#|;4EzlAT7^xBI9eM+4x#Lej +EQW$?1*~k2AauFBbGYR@tTlIpmm;K~5{d*D^xTiHp`eIdlcWO~SdZnZ0*?yv$B +|nSp+hc6a)3*228)#FF*vUAJjZ*m#Zql^ifi-DNfPHt5~^nF?U-PV|aff+Y6F!1=LGWVC=vyhCQ_!_h6Z-faK{d3{V1X{DpgmMz78XL8@-U^B>_U>ND&**%Xl$f)Kg +<=9Htdh_=nB7(Yor48g%}{cIkii?wCP__hvhGM7tj+oh`nb9j^RzbB{0ELwJ#JGMGI`KJg{GcKK{5IK +{-<*dz3O=#%{0MuQg|WWx6TFSXq0^w*Tm9$ayI4o|@;4A&ta(i76@6aWAK2mt$eR#U&o`{3gO005^30015U003}la4%n +JZggdGZeeUMV{dJ3VQyq|FKA(NXfAMheN|Cw+b|G*_pcC!f(?$?bC?UIWb2@`kaT_NicsvctyPvhNp4 +rh{`*d{9mi?Ayx7v+eZKp?yNhT$Z5(O1ZKT*oVmL}&*Fx3P(Z1TKGP)(Ya~(Gp$Y{9dvWL;;UONn#EZ +4%iXfSl5qf96VMsZ0CDd?VCV1;g5uF5IkayWhzVjXwA#h?=G6tdZFZ?_rQeZRci>~`-(_D)DkeQ|Ttm +y7$`?YFxPySmt2Vf5Yh_U3CiZ2p7M3R_GF26)MerdLlkBQHicV7hl*j|F?;z>s`mkk;U?!(GCd;w>bP*+ZQZO6i +cfsFgOr#;>twP|p~4XL=ZbO2Asml8pd~1zDwJbxv-yf`J78^VhU!gP)~yKyvU1>8*I2o!qRrtTk%Wv? +nHPAeYAok5Q>X(?cR&2b640kN~grpT9A!v}w6p7 +8}aTNKAifp^`B2Fcznd-z4Kr*gY7EAWjI!G3zDqu3OU~0-F4LO9K51Y +e7(P_1W#h^P-W16wq$0^}<+9)$Q0VkjSBB+9wGUBC6wmll{yOKLdg5!WRauKWz{2 +sDT(&!&hO9KQH000080Q-4XQ;EZrJn8`e0Bi&R03HAU0B~t=FJEbHbY*gGVQepBZ*6U1Ze(*WXk~10E +^v8uQ^Ag#Fc7`-6(g!DAZ1a{QFkxVO0Cpe+FqgvIbi^|V7p_pSyc7kI|j@`l=a0}Z{G8p=bMzzFyEcsg=Faeo*x0!mFV*|iKrLq%srh4APrhcm!OL%M +>`Q{__tWTiA=PWY#jyuYA&VcK`bN9*X6!ow4ApS$T&fP0BeW< +&GIT1t4V=nJHUnspj1;M|W*a +2L|$c{U6hr;(&$@65TX02C$b#YNX8Jf3@KqJ~I?Tb^C86s`%~LgFvV6N#X~*HO +6I`oQ{AiZV-AgM8I*YLUYHh`gW4Y6uZKE!_m<6pcvtvKvzxN3d-q?QR|kY~Eww4y&F^tyutM +ouvqTK0}09h_3|%CVt9Z!`=zBySTkJ^g1^U`fmt+oQ#w8;Wrlc2bN@8SLhmN}|4>T<1QY-O00;p4c~( +;`3unU81pok=5&!@n0001RX>c!JX>N37a&BR4FJo_QZDDR?b1!LbWMz0RaCwzjZEM^(5dOZuLg!Mj!S +%IYmvf<{?GY$xNZP`&EJm@lySmttE6GWhkpJG%tF5&Yn*ESnNi(CFXI>TB^;{^_^8KE&{c0z8hj_1gE +F?kH9{#}XK%_lW#~$eX$p?1K(YYc5-?Su0U#L_$`r0eX8E$j*oxzWlW$bBRRCk0mTogU7Z?V3wxFbRW +jhFX@>~VekVK;N+eoQ4$2DgFmqHd|@s=J1pJLMCEg&;qxns*&$jb}P%4f*x2;6s0A51!(c3adc^dqbp +W{&aQq^(sf#-_OoIU0*M9TA{L#1dICM8G8W~RRcHyDM)TxtQCjFjxUKJziMJl^Aeo4pM>BdM;}3wpf` +gix!~$EA6S#4OWK?BC5cqq4eGTmsTCIidCG3j{N~eq)^nuV>1ANPw7Z|p<`Q!U;I$eHEnnN)3$hC)UH +k1z*d>Cnnk;(KNl{qriK_aeI*gI?&1_zpy(L~o)IArspn{wR93XXVcb1p{R=BUDvi9`%f+|w+LG^=Lt +#sA^3Z+k<0#x{7_R^|ixFc1StyZhL#ZuaUbKXm5(U+}P2tG-YnD28Yd%y(gxDcI*5rpqBZFQm{tEvSZ +8OIP=1b*{lkov3)0`=#0FZdq0l#}QmC*gg}*^nK=O;fp)O2KKn483`eE;u9WAZ$#!LQ_XhiFga3)EhE +e1?FhK7;j4_Qt6&?K`KbyfeIp>=rqe-Bn1NPr7VnBH@h)Prs0*a7oj|ekFe)UAq4 +%WCuuBsO(iP_H;>hKZW5F|*)?@04^4YuqFbQ{Q9v>VhQ^jS+VwAB;ldkTR)nBD;O*y&d!OYVGyJ`$)+ +9c2VzQ!>XO+abo4!5D!U{k?g+HG80gM|K@u?I0n#SHyrA#V!aK01()lQ2JVCNO1&s=%Q34`J5$3N%23 +Y5CTvwNt60}vn(dgqLmo +CV{roD9w{?LtJJSR$t90K{7GCOx4#OM2ua21AlBWEOTWOTBa3H%Knr$(4R5SyXi{nq6jq{uGg>(3;Wx6U8 +6xS^-hNn*vjwtYhLRRLf{JQ&?xu^Znuv8Wy~CwhL1bRNa=aR1;? +CuM)kB%YeWXqGCF-r^$bOKulLw2o^o$(cKXW3RE5FH(b|su^)81NhWy5Bkz2Oh|bdxv7Y_sK*VvWkm} +ZB8>%ZWF8YHAUD|xjh%9rNSVr7W6(|2WNvV1Na95S`v^2fQ@4-3CasK>(dD^F(X2J$G0+X@y;V~)fvuw#3)|5C| +3^B|@)x;`218cJ~kDf-(jZI;Yg-WY`lk%GwNVbh@s}2VRlIG&$B%i!l)8ye1r4Nr;Y(egXO&&Zk*N5= +-98+Kd!#MZNb;%||pZoX6i6*PxP)h>@6aWAK2mt$eR#O~ch$~Z3ybsJFby+PX2nh5jbiU0amqSJCpLR +a0-EN4w1$!I^q<@&5e$?b*de`s>-N^C|m#R(4#-rtNN9RM)!U%NC|ulzhrw6{4N8H`}(TtE@bdjkm=% +NpEUHZ2P?_GVcVKw_j +}(znokHrWTBwpnw{+jP5Y*L5}4x8WbtyxwfGYNQbNPNs&u_D)N{dz6bXA&(w5j +V>y(rpsnwLDQy6yC6GB%-Tv$hj3=aL(6&)&X$^L7dw_WU{QSEwidlyi593aGQDshcT#S;4qvnPC%p6d +I(g^UN6o(Ia37Ho-mE@HSg!ZDzOT)lJdV6=EgW1y)sxWK}nttc5jJZBv)J|1b3PxqKMvElC~$OaQ3-L +%-B&RTg>s0ssP{n=*O}{b*|)+?A##`DcqO`q=8^nFyjyHR@-zZyLO$F!b<1=40>@5);)+!v={kk~ZVQl8w?eTb0MFC9Yqw +oH!jcjiSoIcX))2je-!EXdauC3~@ovBiZI_k66z)<$HdKI)w5yi9N&8k3s{VIc)#-IvFS2q3x?2>I>7 +pyjWlv3Vj-QJvYj)4dvcBcZ^M)@G30%%V9pqQBuDfz6he&}hEv`{QZE|!}(q^%Fzj1L&VmwXZ?a`92X +g4I=EokC2lQYLIub;YHK4G(`BaogFcsO)$SYVga0MpTciy9`P?)WjfhaiSU43rTLme9+(VNo(5eB~d6@~J7p`P{Jk&)Kx&A1M=wH>IN%ahh1!?J +2&NpM{yYrVUMh#cDYosF}%THWPSSOHaEy={hmgm4vz;cP|{P*V<$aQ}JFR-pD>2FOiJ@+shFkUUIV +~53=K@nMBOzxXIYc4K>$ND$75-)XBR=|toQZ#No^jEePS9L#33_h%VnzC$AXtDV%XPp#e0Uu$7r)*JCXDq +;@*x+Y@EcC$-Vjl^9s +Rqk(smHS=ir3br!q%65)B=e}9x35$m9f8OaySi3F+n9tC;s#_2j+cE<|!9R +iJLQ()({$y374#Z(Yqp5k`gZDF=b=&JETXJOc!C)4661dQ4Xv(vS-!f%;h{SC)=~2>ep0&5QbB_1nu! +Py+Lny}I=T7k9Ug0joixgj`=n#4>vK+^nM*<`?_(e_UH@It40h6FPK#|~L7*3<5r;+?S)SYYxLOz61* +o79gV8Y&k>j^Ta+_C>TA&;aynWC0>{OtVv#j|(M9{>Kknx+o!^l#F@OIUotxee8_&O4MBWC{{6h>7I= +#o62R=VupZp~0}#s>4-C*CLr5u+azaRaJkB?OK6)4DOff-+v9D=)?x0#1|f0`LoS!6#$~nP2;SFbHv6!+2+Kk3bYwPW-1T>Xl%9Y +|UgT5r^#jRM +24!+d-yiy#lzm9L%vQ2P0Sg5GbT@;J`R)fitv4T7JDlvANu*NFJaJyeak}Ivs5!i8!jOayKhhvx@U2P +?Os#U)M#>#r$&6j71zU+(cK0?b8bmti35JK;gQ+?Qsg|Avh7&(q}~zozM^a7(nl(0{oOcR^P5dcE+&+ +4;&I_|B!DKW|km;uCoRv0?!Nf`8Z$@thr=K*y_A%8c;K!z>EW-?F1%(v&9O>A0Z0I)`z*UnE^vlYb++ +=m3XL!CArJM_b5(?B*DC@$({-=GdknRV!AkB%T +n5S=sPxi9|SS>)8?*#nm?9`a7&KHrQPkxR^tNS04EKOBcAE(czA@9>?yP6pWEC>{ci&14s*OGRh&?JF87>VhBfWp_m>hF1qa{nzAPXaDco6WyLZqnBrl%J +UfQQnF)Q*wDQdK9Ic9FYPBx%paLC$1mjAxR$tApyVn6@0R)Pt!naG=B+s<@`>4D3l+?pIJ9U1Db4OFY +gk)$fL!ijQZ5IpTj*e3OFunuTWz04yCF>OKpnkw8k<9{>Ti*>lg`_5>Nls_!cZ&?t +FeS9FlTLw;CiCAiadCf57`5xx2vq`pWOS>p&V3dmAV%rx{JDB~=Dq}NT|ZCzS%1S^g}ZQOk%xF~7YjB +k^CkJ6bptvliOQwzGueY%CBQvYG+vyT)7VrGkCSW~8!bE@hJrVP`Ir&K|339iN!1%8XF?c=^+BiKsKL +_eg6di4uE2FPt6AP}wmJeyro8$B~6kJ7nx6l%>X&8Z1^#dVKo+ +6lMy*W>hxy4Cy@zu37eTml7DvYj0ld0UUF@V5vaMf&`8RT4&1T;z@Kkr0i&TYij=u7-pAOeMgxtOu%e +g)Pl#+drBic#WgRPgGqr%iQ7<`cYK +NYXl@!iPlG(ICf51qQ&W3$>7<~q08hvVWBmvst1~j7-^C)uez~VXO#4m<42854Y-=$r5D=gpAW5=?z< +=Xmqlt^Q6&w00kJxGhZhgI44PNQ-$;gR15-B2*-u7!YRcLHX^QMn$)>7Zdi9u3;yCwfP-QJPJ> +lYL!@E}_5on08{Q`#6?8Q0|?7YGbdDwOH4;u;iBa!V_Bp^f|hjb2%IByELj&^{N;4KBGzR54sL3%Z3_ +ag=)Cb^C)$q{M1yw3`#gHH+-{zV4JIdgR)FQGIxG?0xIav&OMIzUWaUBS&|}4`Q}y?pJ6F_MWWQ~i(6 +G;Vb`venumyBq;kqJ$$BdH3Dq;8OWyK1$%5XfB?HGrl_c+Q*hx|tShO={Ge-t!fNhA=u7lCf~KBrBx|_Y-}sJf#Np^v%euqDQIx!M_6x>~+ +#%q>upbEX6^Q5KhFb+Rs1dTtBsQ#|f3Y3z>LaVa0Rz-Yawm`qC5cXbEz`vgRgvR+5bh2v-Hn|Y=FTr~ +k3uz+vWj)2R+VUN+#JCVl2O{KussZRIV7|Y|FK4CFo!B^9iQA#pd=nT8sG)pRi+y^)8J~|!LHn;q*JB +_%!;xD#)0L~gD~@$Reyy51rCMX3Ir+fBZ1M<`toYn!ZqLrO$K(5p?m4ups9mW2yK--tB{zNHMm11zs~ +X<2%xsxCR@V{-e97}euyiEB^fU1OZk>M_@n}0JLVN87g>62XvEtR*!;$lpa@92+wxPT8@&Cse?iYZP> +p!0gM%U?uIh72!6ODGBPfdtSMm9aF24mfR}@ax=Fmf>p>zgzyHjd`xO +8%&eDjr|;khkzNd^#YcUtnETeM^+{M<<*(y4fn45a~?P)4&Sud40+Z@kGX{;R`5DB=pIA4m;0fP!%e +dRm7#^DuBPd`#r;+d5dWgCjvdmL88JhM)&a4V*HuT*F{q*|>DU^me8IsBH5FG*uTVPAiq?c)`}he&83 +BMmh9m%^?T)@k#>-Mf_Gk(_fhR;Lcwqa9=rn>{Z4l2h!&LdCk~C+GKn_kmEwmm;*QEz+e< +uu#D^$m&wt0QcmERKF=GTW19&P#FI#}Eh-BKiVEhZ?dMys&-D}_qxe{ZzATy;l{`kG@ZyX)2eMiLdHOyy6pox!8#B+V) +$Nkc@DAzPX*Xs^JBjrBMf3_Mf#iC`ZpJ3ccX>Khj9Qf2yQ8|#}{ALJbFcox?Qs^v<5{(W-%W6!SB}LP|^y9Wvo%`O8o_AoBBrjlzt}DbU|%~`nZ&G-%j +-X(_oT!DJaz|f5Z{^tsz5#a-jXbD;QP2RrXEP!@iLO-fJWunnsvyaa-UMmv!7Ux;3|#-L{deHILnT-= +ICL6C3jc+IMq%!3WG~4Yp@az4QZCVVd@cK{C()g2iI@a>pZjp{nN!A;1R6A7K4|I)3bA +Z4d}Oxa#bCaZR_zzas2R$!VxUJ~-@D4f5d%hTDDs{p9_qstD4rkEsS1SAcBYD>s4u)}Thh8%%P#WjLu +RQ5+H9Tc-50mkD=_|X*^eG2)pFd=gZUBWKZN%C-b%M9h5JEhEvK7ESTcQRr==#Phk@y6lZ$Is}N;m5* +yAeIb!+y#7PWa7Waj(;-tz`W+aC%3247Y16=+Cun;{;F>b}q>qEBrC>R|wJZk +u%i5QpZBIP*nt{Y~8k7ae8uc4Vt4{U>N3PyW3_b(D;O!ij#`pddF)*n4yqsziIp3Ou1|NMXJtWpAPSQ +@|{RBa>uMzU2G+IJ7w_(zz2l&)4-1j`b*xdwq-$yP}z^358!9|wmbqA9NmM;@O+;Styw=Gp7o>HGvStY7+&*FqcPO|MC9TYt(b0=?I0_#$7i@R6)Xy4ps$xFNI) +#dlYxS_SEeq^^>pnSpvLPkP6pRwZ?_E|^gbjnV!&sNIn%XBP}X_B5u4E-Jk3Ul`+GZBipy_T?zprD*P +_n-M63+{Q3w96-f-1<<#I~<{~=lMhaCXb51hi|k*0e#a;)dhjpY=7S7V@3AoUEO_Qz?5UQW%OA?4nta +?f9W&hMI~?XD;8z2c>AA1Bk(=kNH1q@c4V}fp48s4U}HeyJdWW1(WuUI+Ou(G +XkJlYICLmd5H=^_H`II*v{8&4o%uv3P~bOKR=hH;(nsSEiYV|)AF>x52dY?&Gw>rFO~u`eV$grZLd5s +~G#P!6nfj+s$}3jfzXqLfyDy{QfFTz~0bsl=B2M?RLDrHsN$H>5T7qE}aHA26bRd@>Foa_V8vy +BSbT>l`K;Txc0v^;0B`Q0Rc%ibu(zN6HWxDlMi-YV=%_6F#?Ag}`6go4@B&k8Ea#eD_n~okKafMV}r$ ++(uDUgXC8klxzM=Ye8pr@OHYy>CR-rE~>uomgDi|x}Dp6}Rg1W76$rkeJ4OxE=d)UbWL5*Dcmn! +thI?Dlxg2ku|3w0-ATYmyi*@coWW{{OLXqRHjwdL1pfsa^|sT)kND3mr+S8*MOci@=aVr>dmi~tklYY +85n*c!CgnMN7tjVVlLx3OXpN+2)!qPvbG=rnT)27^Rsj212FE#TN_kQ}i}I0|pCoYSVGWgj0JRev==&IOw5?agwb$w<?p2mO0TF4%Q?cgmrouksb--^?;)>H9WJrHxUL1B9<02{^(w +G6-Klr>`q|H~&QiKs@8Tu+lLxam&oexe)D5X@SqOsT?q%F^xwUjCNFM1!0Y8L$iW&)>?y2fb0{0f#Wu +U5zblYQr%=^Ngq4p=mc@IISFySJB>JxCjy;x8~)A5Pkz(m($0CQ+Q_D)nE ++o1-d#}tcR4LDlkXjOL=d22q4pTh21wnrobtngn~nL}Y@C}3VuU9YJJEsGCyeK2~)_7Ga>PMN$ab +xJ0nQNIWP&z6b7TJ-O_O3f_Z^9}b_YRRfFT_-m6%Cb(C?M^3NY(J8!cu0bP@G!DbUdKQXo`p?FROl{yNom6@U)*MXq`izQT)k3#ZI|rxdtSEm{0T-}oOezW}26hlx7Br8 +m5{u!P1S0iV!cD#8FBeM!!qVYOP11foi%dJu9f+B3VUS +nv#&Jq_@Cc)p*KsTL> +o#jDtTJz-A`1d|a)br2W*`ttR=Q>i$V&@8Ukt#mp_IO2IXAfiF#NkmStx)Rf*4XK~Hw!BB2-&JWR?T# +hvzv_-+I(TT6p{3;!GO9KQH000080Q-4XQ{z~G$36@I0IM$m03HAU0B~t=FJEbHbY*gGVQepBZ*6U1Z +e(*WY-w|JE^v9}8eMPOIP%@Ug3zLL-Rfo$3z+A9D%58f*(x$RVubYb9y%O!vUFeM^T-nopkb +qt>$Kw0nE-qfae;-3(cUz{JvA~A@OCeej!{EqCzHRH8Z9$$E?G10PFYMwno)#i2clfVEey3M|QHwF;B +kO5JdukDWrjq>~^eFgdKfff-}P{aD$R$K!a +DFNDDfp_oqN(@O^3<&Mm(cWUnzN-1A?5_9Gb2Q44%dJJ|>1L>D4;FeWc+bjq=({0$Q;iFbLO~(5#Xr) +nJPviw`-5P#TCMml^pf)xfzJu?=Nme@o-hC3nNU|(1hzGml9agrZ#e4>uq^x6&8|(SYRmMA_(_uF6~A +GmKQK~xAf(-V0=*DQwk^6RX?KL(^@5TeXLTj>l_T-tz<|43Ue{#9NY{2sS{11VfpOvEN3;#tx!il24Cvc)8cBI$fK*)^%?(tOojc!Gb!}b7|WVB%-_b)KAQmZ>dVEHePp8p1$mygi-87o!@y6@DWu;SEF= +u&9a{HZ9e8A%kDiW;V3}~@N@*Z_A!JJ^T6_pjJ6DHp`tzacY#DT* +%gyI{x-3Ku+wLM5U0TwKqggom+yC;_>!d^K-726S7Ri)WTAqwm=a`uA!+BR%7LtM@wLL;2aM}*WtDE7 +|UWIb6qvz9g4p(>~piR|$rJ#an&0M00QW$_EGB@4F>tnP|L$=*_TqW7p|U(Eh6XMgDW7Eky->|5&JhW +#lIz`nJ<@58>OPQbb_9LfAi{MU2->lR9gZ_Rvot~%-UPCFrF%6_0V`cr>^q;#N??CW+zYd98z&YDy$Y +1&@7E=lH;G<1D`HV+GV2%bGfR%e4`Vs>xY7fEE}>FR#l)b~VZR(BmQ@PX{`*fWS+>hQCZIOL%#Q_W#co*Lc35N)liy@ce!jP7tYfwUT5+WPDG5cbN$DhtYlW9U(K{R)IoaMGS`H3Vq~ktGc7m}#8UDgnCdp!^rYlZWR~UoS#}j&NoBcU4Spy2N4S0vUbLm-;L(Q|L?Ra$4*6X-VVE@bQFH_vqVL=YQ;NGYwzgnOVEPbW@8OnZU$agW!ML&0p&F@H#j%r4WuS@ +BjL4~O1fCV2HkVeQMun2(C|U=MMpjA{+o7C&~$?1X5WH;LNBE9y%|5`QED~x`sFwQD8lAH->Sn>!?U| +hVZJ@_{{Xy0BLZIE-j?^Yxn`A4QOJxg{fS;4bA>OeppP%2Eqv%j^OM$*efWzsP1bXa=dczHNP_i)XhJ +<$DFoz0!4=`ZAt#gD6_fvGe4uTKp-C4Eh>AV@htc>F+RH0tH_+aX*1~rdDR->~rhB_+3;6SZOF=m(%O +4q~$y+l|8apCIXzVa>WQGY~gZ=bz!y8uRi6>6ag|`Q`y)Iq3WFyAhlH@g17zIeJPrbQH7Q3DaFEYFd5 +`ar0f9Y(bmo^#ZX3}8&H>KloVH4LmNhZB}0?b{8WdoIcnne0|C>p9P0L7zK={Xs4rOq%xHdziOsVH3* +E&VBqoELbi;V7{vbM~#4)-y;fctb#3nZvmOKnQG?=LpkOGKq@zg8i~L23i +k6~~SX*+Y4X>cuGp#vcrkXt^Gl9j`&gnExE&RkQ0rQ^hB?tGEh@i28ah> +K8m7FBzs;}IxE+CrXWT0!ed#${s+G`!iLV#&mMJ5qPCH@MOyou3E`vrs*z^%?B>l!-8)puf+9NatZwFd&>r5Re8c_}5!^YLG>p%NJ+7+pg#W4UwFl=uasfMlw{=C`YS``$SKnYinR28irDOkH +^z>A*WE33fSIz-Gy;v^d4=`AGPz-uS5-nZPTh2d%GUr=GFOXIgE6toIh%AH`9R8`9hP*xA0y}Ip$0e% +y+u+Kg(qvgicXe3G|$|p>lVrqC-aABgdy-28h-A@z&_N2)%L|ixH1!F4-@9y)81&s4h0 +rf>Zb1MgFqg>9V*EB`ZuU4~Vb_2Fbbr)&mt$KP#zH|_CgTRj&$SV~T5w*-K$GM_5Xmg`1ijk`vA5E3v +ei{lYs`m*+=&HlVO#a8j6a<*d2V-E*#>_IIJWDZ)JmlDHqK?e?0M5d=k}r?KgA8je|IAu((sc!lST1NlJuF(a3Fx15%gB;B&aUUf7rYCF;5D0H;Q7(n +FIa|8>cyVq=3}}i}1|gEYA`UG-3wXL$2lYKXEZM2Q&S0Bd{xLAKqAO$-RCcddjQnNn^TdWN=Pyp|WF8 +JYvP!w6_XELMe?;G&DB^B_@6Esac~eG=m1}$GLZykupw^tp$&ce!VYotkdkVj)HU0nrN(~#z{t +1snQzX3c}3xNQS%vEnh#aNkWqRQ_Gta;%aJQ$Q=Z*vxNMO+!@ZY%;N<;39_nkq>h^On<=FUIvK+DuuGSf50yD-~<-~FB3C5#^;6R^` +OKQOJZmmq~N-gVm51887)phN8##|a&`xuPnBLKx)8?GK0sbgu8f`zRF1PaIEXg)>7AS3DHYxs1)7))r +v^x~BWZ{2Uz)s}yg)&0yt0Ge2V>}j;F}TuYtUbtF{!ZUE7gK3bPP~!9dK%I8-#)#IQ?_M2)+i076A1O +8p(4j_vflVTw7mLPj?Z*qcJIm&_A)l8T(9#B)|-^;00Z<(o`+kzZRu`J0fz%g5}zwB=hYdT6KYQ)W~b?X2v#B500F$&u~n#7c+8_$kk=j)xm4f+b)7HR=yG +j>mF;ZSlb{_X=aXa}f*4~+)O(0&kmpCp&tE21Wact)E`LReuxQb`il8|8k2r2A#zNfm&GCV;D}wHZ6G +vFaw$|_6A>0^ip+z${h>HOJ#TJ2LH(Uf(+&+AsZ`nvO^DP@~H?M|cCrz30Urc!JX>N37a&BR4FJo_QZDDR?b1!pcVRB<=E^v9RSX*z~HWYsMuOL(ukXBph +u+SFN7kF>PoJeWa~G4oDgk>x9SnPzuPtP7czboH5{L+Krp3aGp|tCcGIlDXZs1T +Hj_AQgQ|KT#lB75hCEYcJx&{(Oo>vM*hZIa8>`F<$ToE}r66n(9h{fHkY5cX^dvyuN`L%(J&wx0i3mW +PW>cayp+631JT_RtCX$YEPvsqX5mIsp>S!47s04fhf&XpT7}MF^=XzpSGB5p$u-N0N|%)$jrb;n)^tSR83N#;bjUR +ntDGuDjJ2CW?%DcxuH%{}v10HMbkC+lX<4tR3JyXZ!bOKbH8r4Kz}9j!8lkI9l9|uT6b|Lwti@S|lCv +xUGHcA7QMuZ=V~*YTcnEdQyeNt+4*?g;M!S+bknnFXb02dj>=hd`|iBQWU^$25ll-!O;*V~B-ydNoJGGv86m(=iVcKY@1P!C ++klfZlRL2!MV`Btq@^kugUlQc1vl`yd!6BpU3tGZ)C<+hDK6KzPOCfJZ +HqR^Hua8FVWp0z7X99?k09psNY;We^VSV0KrH>fj!NYW^&WtkO#0cxlhO+`RJ(_54OFqxQOOglA5`7-XFkORGV>;crL9)9)V+sZ`uHbCPK2#6N$ +eHoG+o6OA>`McZaIq=kdN_uZry49aKRRQcoeZ +D2jyM#fd?11i+e$cAtWTAh1W}2lf*=_}2sPp^oFq-gHkXMm2pw$# +X?w%LH>>ATBJOeRzFC>{q2Z>w}xFHzO_OfTo_dK?;1L^Vc`0^P4HYqjw-~B3aI0(EXXX^Nyb8wn6_0S +2j;fCKHhHF^T#a>K|+R@P6O(;aK)O3>}F)9zzuSdCiDxHJx6iwZ@qX+N#K_y|6>vwEkbk0>mxdz-fuY +aqlV0PHMP;JS^NcE$jWR5s3Z}`F)ZZ36FeFANm+r3`R9)B5w7JguHM|VMPKG%+$CkMPssvCcu#N&k&z +ZpX$*pgOyOm3-V+47WIF&J-i`D4azNYNWThy#piZk?A$2^ixoZ^Zb?w<{E2ngCIiPhAwP9%S|H>V`Tk +`;SDq34PoL>KwmjB>fT3i=b*;HSHd?w2CmnMobN@9=@gw%FIIy9A_)XN~=!1yhFeCW;j>LWcUXUM>B> +ku0A`VoydahxrPiPZ8;=+-{k9O^tw9XM&8yd$nee9_{9wD%gPT{fP@7)=Kf`igSXTRAgo1)}d+zsMZa +Gc{$h55u$yE~jHKRI_XIq-icQ)A#|+HUe#0X*%Pz?3!%`bjeS4^T@31QY-O00;p4c~(>4LP_-V2><|q +9{>Oz0001RX>c!JX>N37a&BR4FJo_QZDDR?b1!pfZ+9+mdA(U}kK4Er{_bBvxHv>gWg_WEw+e9G+ilZ +JH^FZ1?t`(AX`8myl|_Y;yzw3S-#arTMM{=^DNuBAh$V{i!g+aSIF}zPr5m=ckCWUy7HYT4%bk0zM6) +mQ)nu#HfvKvggsvsCAAc4_D{J$uIaNUBzPxXw7LC#g`;ga7!tTXEZa%kFArtnoYI0SIB4M;JG4xel@$ +4wejncdl>t}&l@V3c|+A&>=QkD5y6#1_*D}iH3nwKCFxbk(8^OdM&wpNFOC^wG!#=Kr4sTH4trNC +-C;+;p0QX9)G&IxqW;bqddq)Y(yg*#a}`24{&0guUb51!gjLBHgYT4qRC`=%(W^HvTVkf(P8-^uh)*< +i_ROFskW)wW->w7NX=5`BM!Vj&{~v{J+dsGOeX6>)HQqDs~3CZ$o|Ij34_1sbV|RtgXQH`>4QLBnOK1 +&rb@|_W(TFg&n1|pDCCA2m&lq=i1zayHj!E^2XmF|;qGPz2Cs@-lnpzGykL7NHlVKI&pXld(AK~VPU3 +W7?7K><9a|_0j{1rH#Y%j~IOlF#zAHhkOv^8A4!;mCM_}?12WR^=*%VHa|**kU*rd6qH^j6zcfMt%YRtag5lukz7okfC=V(g#HfNs-32DS2AddM)GT~0eFvc-9@wevFxW`P(rq!j%+3v% +aR_YUk2+H<$(6R=j*RWnoq1XG&MR$hj5D`&4RKL=2sLYZ^1JSgYkbg3ANovi+=`CSLsaL9&-Lif6UYh +^@VeF!VBbVvfC!_s))ZGKE<%qVLGjI&Of!G@4`-MWSDf#`1O|H!%@NhJ@>yRx+CzCMA8G4`P-lt)aKL +hi)+lo7Lt{+*i|hT9Vas;7hy^=z4v0^T|VzA-EzxD8XE(gtF9^VJkzOeF{ +K+6bgYT8qS(+Y6!Q8M6xD1yqd4Y+X)yG{vv*@H0rVw`5Ad}eiqZTXp`0{^2Wt)l@~tw`E)2avM@jAvNjfY&N7^5( +=|a*$r14ns*k@W*hq7RjTt>H-aN4s8UZNsXBC4y3C73Y#q90K +MuR@E>yIgm@KfS(TS7@4*_j%4bKvK&Sg`KrGOTFjcWPgt6Iiv;^g2>D3|^$8h&&_Zzbe$Z36I=}?6fmRiI*%CE}Ew4 +t>^cAxa(9@2+;w~v)nQp=ZLU>@)%3bJ|Bio61rBn7Bis9XzATxyy7pWbh2T%yOZ~!Eo^|XrOe-lIU50 +PRwusFM6MEFlk45|45hD2fl1X}ePQv&)3g{a}Xf(7k%WC^RDcS!qdUYU?cLdh}4JMJ(s?@wu@n23fgR +-BLNVgF>qDFTkjY71GvnX~VpD`of}{}<$FWVysabC;pnjlbbb7sAoH)f%i%rkkJLZThi{(d{*}snc(h +$YHKASWK~1nLc5DPk*}q@&4m4_tW0?(2Jc(_>sEKsNeg6aY9`OQ8B;(_S?CeH{3fKbHnIr;zlC!$#R{ +`E^Lfjy!|K#GE7o1<67xWbj4&{qtUZnU_7jhJ)Ii~MKCowX#m{}7ow@@27)$NeqGC|VRxX-y=v~z{kK +{xJ=$C1m8e_)1C&36{I87`R90u86I{5#`XzP)XsMduF~>XL(9LHMsnrVrdu$A<>7I#02GSm`tEfM(;j +-93@kF!67p!SooBxY7M-c6QVp`KbL3ygk7!{%9;QY^$f~+?8K!XQfHg+pcVF%*ARGOM*;uW5=4qwA+N +-V&^_x5&ARQ7%J2&DC88I8R_b1?1!=Ev_a4c+cn>&UDYn=7EmW{TBeTi +>%4ybt*l0IYrOyOox6&S)MTJV?0w!BWaAyb`byB17gHCm#4zDv>S1x=$)1MB{!yMrLKV)`;Nt_ch|Udhz|5+4 +SV7Ek_Q2K3Ob@Zmy1i7|CAz+-P-5ilD~^2=9V5Z*(t^CsGKhn*k2sK86zSTyYyeZS+B%VN_XUG$`2)T +PFc;wAIltoyTFEyf5PpWB7FzsVB(-pCU;Dd;qmi^{BDXrGV +=y{N-F_SbXCNh--gIA~*;_mfEMaFWJ-TT6XjZX1oDK-w>}5dlcUYd``WTIw#tbOFr^UU7I|Bhuf&*&U}_#ur;l1oJ2+i|6*Ffo69|ICgvhg2q0F42B)Lr ++g;v*aR0H~bAq?Aj%ohi*JPxL4YuYre+6L%x}6UZtN{4^v?Ts@JwtJm*M$U58G)UaHjJ;S4Ky&gRP)h>@6aWA +K2mt$eR#Pt{mhhPb0071f001KZ003}la4%nJZggdGZeeUMV{dJ3VQyq|FLiEdZgX^DY-}!Yd7W3=j@v +d6efL)k1OzJsGLdH^%F8wnLDQnZHU)~sLgq>%n}|fJBxSD~usx;=YsNN(8qB#hm38;xjKc1Bq!%^IG6;m#gwt*Fc(opY(A$ay9H#!T`W= +c+$uLOJM2D4n%HJ8Ppec!#lrlRq$Ta5=aMkv`U{vuw4OyI(`%tV>#R%YB}S}FUQNn^Qf4OuO4 +%;Dkl{r!iBhcy*^$+5BIa1{P1$wXCgxA;M8rPtIb@Il_Ap3y=pG}b7sc}0Qc3JyT9|wcRV$0R%}B1ZRjWdjR9u&9Mt>JURJfSU#!(A3qyxv;5$z|x7H6 +IXS+?D5HhIO3fzPrJvM*ZPeP!RJwat~TFLlLAoJi~b?FlD7RYWnWo{)a|D5$ +x@0@Elo=R7lj-%rH%4ky=jiCk1yvQ<;qqH)JS)Wq|pf$bFW_CLda4dK7yEvg;t;rf8Kp>~ED^|e*>qM +ww!v%t+4D3u0kfu+#3Z#baJ*|9FLLeD7{%~M+DNc+&(u;3d0jr?};CQ9d@IkWq~Y>?Jta5;zv?d=mKK`#peh5UaeW_X^wTYY-e@$=x#X@0Uwt=mQGEmIO}H`^%Dc$jfm|1zi$~kLoAW!q{@d%_GITF=r +IY9MQs*;rqzh14Ys!wRJxK$3cJ@6szMGMIX}x)))u +@E+d&py>rsg>(xd3O +VaA|NaUukZ1WpZv|Y%gPPZEaz0WOFZfXk}$=E^v9RSZ#0HHW2>qU%|O3Dg(CCeHb5k9gvYcCBgbr=MB?U1cT6>g0g06NrY4l2ygsPyzAiDyWXEeyEZ0(%o;f&lHnY)*pdyB8j>rAv*0Qn`w1Ki*%x1HSQ>{Z(d1y8cg2m +;G;Lm(McmLnoJw1j{Rc+Z0y^bo7z3dbjm{b!RTQ#&GY7i9zozI*Ek7Y=A$1$rGO;0yqr8Lwm9Zs&-pf +oWU{IuRtVJw4Oqq&U}f`*e#%ec)Ux3iXO^GFqtx>I%$cA)kogiKC8poWdKWH1!{fi?uN;+-^SK2pm{U +y`jct-idtaBCPkv_>vV%9*?XalxqpZ7%#-2IfMj2Gg}hO0KVp*ZD90BwmhZe}yjg3$IA0!JqHG#^m=N +97rJW&0w5io)BLzkkj!vLD6JJ`jEhb)@;Y>j&jcxjcwiLK6Kjh7rh?|dD$wtA-ADW+>ti}=MmjP#nue +e70L7b1Y)HU5XJ%H@D$*71-Q?y(gX>AW9*Yo@$Un;tngeBV)N`N%$pFFR`x~D+#8~XHHK7O2L4O~UoX +h&iu@)8T+V_}LhO2uh14?Y{D8=M4rkxh<9vCvvT`0+VX_~Qut1t(?sIiaZ7?!-R2B0n;F@;a6jJ9^r) +=Fx$XnoTk?Q3%@ww{b0V4#1cJ*f9g9|;GA|A~ExvEW!wr=SS +>7{2)jnIC2b6;Ep9yUhV10Qe*edKQFVhmMfu)`)0dv1>q5bK{pP#Y!^T8GV!HW(fL7xkSni+b};auFX +Je-TwsyY|KHh3RHyxDcx?#=dm_n?vS-a1=fTZeE{UEWh=v1gZ(m5?jEw%B??&BWIsj_Q31zX_oP8GE#+GXDX{zUD4PJWhKs<)6@7x#s44v +THW0MgJ02$IT;l3kmcVwjCig5yao=rnVqkvgrpr=t-3Ytjv0F9+H!&VeO12|NCpOgX{%M0LXT21^o-a +~bA0Z0pnpzuB4X7VGJq>mIPEUiOL%Ywi}Rhd8=~d&7rx7Fd#VYuE7=UQauugcvCUX#!*e#5Ma46SfEq +V(EzywTwF)Y!)U5H)a-t2{Y>tV88iUCocI$9mfq}l;i_dB17NepvZJU%0?^)RSsj-6j#qSoaVxz!46G +MAMBd56SG|s+5=>_R1tXl#w@(Apgp67Ov6RB)I=kJQB%EH;x-k2>q2>yx +|F#$4}#WpMhypS={UK9~Xr+qiTcnjZ4y?q`XNJ*hgWqO3;zFcT? +qagiDu?fs3iwII>V`e5!4`;3C0#*!6AkOCK925%ztew2vSD5pc-%f>g5I`!^ha%i6GYS +y8i`GO9KQH000080Q-4XQ`2<~E;|7L09pe804D$d0B~t=FJEbHbY*gGVQepCX>)XPX<~JBX>V?GFJE7 +2ZfSI1UoLQYl~T=W!!QiK`zb_sSPNYqpohU=JM=W@20I2PQCd;QZe?eoZ$BkzHjfxe4LQj2_oGj#*)? +#YQ)XPX<~JBX>V +?GFJfVHWiD`eg_PZH+b|4;@BI`+Ze|0CA0R-7rTYPTv0@l@Z_pNqHPP1X+ppiBE5p@=fQ)xO3cRIqcv}FpQC*mE&}e9Vn7+2W@4swg-y?Wg$Q +*l|W1XC)+OPjU7mk2hl6~*^Zt!M;CG|A~Cv5+(>;rkp?yBg5Qmz`>LVgYG*DruAw4*kv(a{;4 +$e?vDXW+`z-lP2oP)h>@6aWAK2mt$eR#T7SymDOu00932001Ze003}la4%nJZggdGZeeUMWNCABa%p0 +9bZKvHb1!Lbb97;BY-MCFaCv=_O=`n15QX6n6U$E2N&qQyv{2k%0Hr>(h-!S2-YRA# +06+4;8u?Tvm#)GbRr1!GwQ&3ZF?7nk~?t{O(0a$EY}iQ=-lAPL`p2Hlu?R$I8u^}Yq_Q0L^Ayj9v*KX +6hry^Ls0ndqhz~o3>|wp^W4g08!`u~RgcLMSLMlq-HFJaVy{Wbb^=|9ygN&KsPmOckBo4-u&h_9Kxqu +N3~25O?k*4tT)OhVQfXGrEVGC5qz>1QX%FqFjgmS#GyjMKw9c8ib%vPNzJT-WwE6>3O9KQH000080Q- +4XQx<_rxOWBs0Nxb<03-ka0B~t=FJEbHbY*gGVQepCX>)XPX<~JBX>V?GFLPvRb963ndDU1^i`+I4e& +1gq91338xXnY-vT%jANz2jn8k&?ogy3lH*#_jx(9=vF7L?@dX +8ojis6iRnu*J~jgmF>6!xBG`3zTt&_9;{fh03Qoz&32EtQJPm3r-v`PPIgP%L{q}YtmVZuKR~{xubng +S%0B(sJY3pbgXSgI+Rrg4xnF}XK9 +`{yoNkquU4y~;%M--?7JeukuC*syhm9MlsHm+08;ivXxo+<_HWIw!AYTWlBTK_OfI8h` +U(>P%7p_GQ40zXcg0v?Z6m106NDtlp6-? +=y$r!Yg5qmUZqB|xf@;5JkH4RBWTQZzGX!Vt^&!U?nb>&BLNkmjmeYfkx7Q)!uCY(e`B?{l6LSVx +x?YdCZCD|K>>WwHMKZEsHc7W}dTyAlJE5e__$}K>S(&ZI?7afpHFm;qYuEGQD!th +Y@YH${?E<6gvmbcInUfz_`pm+m#UTk9wjR%WZdE?J!Fe;caEv&o*{_Kztz&H3@i(+Ch@C*|O?dmLAVL +ZrPB3Rsm!PGLoo>=_p2*ouX&$SI7MA5@wLMgPBY@uDHslKJ$Rfg4P&%VK8o0vUvBLlA7uo&I9Vt>d`1 +<-QAn?`D)ro^;jz`IJI@f{Q#31GKY4JL*$d5oQfh~cXn=*0d+NIk@%lXx#0#DXVJ%!&~Q5k-|S!noxa +3bf8|r0|rHwAk}QQYL1}feQ7vI%Vv+G>uR*j36*xDameSca3rCWKV>XTN=wvIc=jR8r%Ex>es8^|GLU +hz8;f&QF%2+^~pA=!T?6)D4jzB;2NkHsS65nhh-(Z)5I<{4B6|EFpAR1*F49lN?cO=;O2@qLZ9-2aw? +r>+GUXH8>^l+Q!N2X}C_^2t0`5SRR3_yUD|pfb)V;-czZ4vHZtq|?D+QA1#2+2v9A9F +3tW&ENWs*s%e7$-;iDGb%E2VIf%Km~}C8Bp`6UK69Lej-(NH`=O=GL2Xzfu~I93O7PFiX+4$DA}i1o0 +#xEnj%W(m>?=`Fd2CoMl67oAC_$X^ +7=*xRblBise2|^vvRmF!)yE#uC;4rN$-KHKW#e{jMy#(?mTYErTy)|$>(9et=RI>C$}8AxR4o*w(>zx +2jj5evZUpBk*Ddx`R3<q&kXQs@(XCnkeE@Lrc4kGoFt~lwZ>3I%v|HNFIqjQh}#rON4U&*G%O14Ai +2e!+W*$u841HE>vo>&R_P}~?>f$Z82x!pou|V=OwLr#|BxOZ`H-Q72L0JRb?BJ9!OPi4yk{;n13RXgo +;t*U0D=lz=p#E@*1Z}hI@6~WpvdB71*5p3dzQIYMp78qh)#931}}kb6BrOP8lo!UKiBDp(1i;AK*Moi +j0rYVpicz6KO;5yIo(@%f}-I(0(1LQ`}ws@iJ;>Q%YE!|Pyn&08xngt}E?;g7F?+LQb>E{JQQ|w +~$TGp^swlreg1);g0^!UV~vf6bw#eTnKcO|Hz6Xv(M`)p|{1p0B#sWoDYux9pKj5k1Zu{`Tcfm}=<#L +EMW7sRU_H)cY+Pwu>w+>?xprjT`u_dTq!Ap~^g#JBTVh+^q?Z@6aWAK2mt$eR#N~_vuN4@003JA001Na003}la4%nJZggdGZeeUMWNCABa%p09bZKvHb1!#j +Wo2wGaCvo8!H(20488X&tU0w($p?%C5(w=HAp}T!tlY+(iAa-@xVx0E$4R%{mYtc?)bV@qdw%JIA7C` +2YM#hxAJmB5FT5CcQ4<>*!3?7h3>`Y}Oo!I|rN-evMbU3i*Kv +!%I8A?EhM=ZTpJCMvaUspe#UspJa^vwRNsO7)x=EOvB@q$S%bTar2x$8+`y&MNxDXV} +#tiXDe?htInD3bUJ;fF;FDO5fCyp?5S0e8-HF+1nnt;^9+ti1G*UmI%2@o2pAW5OsjyFgeFkOc819%y +GX0`&V^G`pY^P!93+*IY+u2<4{zLACVyf2;!#K7E5T-MwmnrAF}>IbvH&j~gAcRJtsxLk!HMFHYkz~F +kztCW)^ME_2ePw74)WoF+J}uJ(C38x%2V0)zD7H>c&KGvtMUuy|)>fWAxQKdX3H~rWOAHP)h>@6aWAK2mt$eR#QViOU@tw0015U00 +18V003}la4%nJZggdGZeeUMX>Md?crRaHX>MtBUtcb8c~eqS^2|#~tx(9!D@iR%OfJdH&r?XwPf6ucQ +c~gq08mQ<1QY-O00;p4c~(>2N#Fa(6#xK!L;wIF0001RX>c!JX>N37a&BR4FKKRMWq2=RZ)|L3V{~tF +E^v9}TitWxwswE_UxA@#?@EnKC)>I0j9T4h>+EiJW|Qp1&dr1Ckzo>&7*m8okd}3~d;j)(J^&CPDSO* +I^r3EVR-%A|gL8i86JU}gCs!LKwz8b>5arz^6tF^+>;cD60DlU6v|qbe(4WX=! +v@XUa^b=}7_5h5yJS3tbuaXw_CgM3??S(Wp9lQ0n!%sMhYQ_CM6heQN8nEbh{stWEz}t$ +uHnY24GtZJDa3Rw{i{s;#P;EBJh}s`XarT|)$f9r{;U!c6|HIqZN1_sir&z~6-|OL|}bvjO>bCx_nSou4~85rrDN%hON$o +-K!Z4by6^H&d&h*QNo`!GCT}PEK;Q63eY@KOop +Tiuqm45a$;1unt{6p5LL|*VN0_0E@qi4lXc?86*^{2yQAHBY9XDN#I&0PE2iNVKt;2k{H8T;+IaQoeIgVby(>u(6V90o^DmM(fPE8 +v8#^*jP2mpG5t7--iX20*%8kJr#w{FCB!Tt#rJPsf9EiODzf%GBjfqs>FesF*B4N7YLXAKpqNj#9WRW +Ife8H}jXNg;!KT#N3?$ZUW&#$d4>Pf<<=VQ_Fyl$#i`kV$gW~hbx1I;r0(bE#*)+`#;er2}pRR2*0E|J#36Z*X$dF-jpaoRq1-I4>Q?ch{a8r$X2~5h$Ekw@K>2@+1!u(`53e(bX$I6ziUr}Vl| +Yn>cW@*v9MUg7FCt|R9iK +vzXg@N|OD#yw5JsGK2?<%2GAk?Va`YMEmODn%)4*cE0BsiQ>D9YJn{yG&Myg*yeF9#VsCQK{Txb^zO= +aX@7g!4a=g@j7c-0Kh?jhX+M(tsvPASU!*i5u|{SU)j`2tiXH#1%~PUb1I(owp$4jB?I0ov9|13RpM$ +R+-r==m?OoOg`0{qt_fy1WHXeUVd4_Fz@rYz<8fjCwGij5D9zPfyPhQPRMVis2!k(7GM=!}>f;uNzTG +o}%la?Y`!7C4;h(_2Ym&JLT>2~;aw>iW?d$4gBlfZ)S9u4Cb`JVvyMRn5la%m4E-2B-_2!=&NEl0X4j +G}!WDDBH?P$o@)~bR62%!NrBIfw8_huuT9)V^LxD!zN0G`F4{)D+IxF_%zWeJuF|Dk#iOCHEM0fVdl{ +Ea%ie4t&x+R=2I$Uy{Z#NWU9S^&aZNN9Tm0N8t!Kne!m8gX|3fj5?0u5tgSYK>?kcEAF91f0ZZ^VJ~~ +*i397z&;>yfss3q$O@Lz{XMh>m)pw6Gr-2;YuW&)6n##m&xqcP>TM98W28sIG{_tzTDGi)?41){UjqV +4yIs)m9tjXnu`T`vk+GJmRgs~uqEL}ZjDvejZD}8b;EgmOv>u6UBday+9oPf~%=~h{wFaN;@qir|z^jKapUbXbzH1sH~1!j@tBVJFW4N2 +h=Omw){_o}FBvF3!+3)w)E>@=6OV>ob3RJ_eo&Y_hQ1r~H9^5yY!I4UoqD33QXj`ERg#NMCY8G0c)M8 +nZ2DT{Z~y;!Y~Q_c)Z^kxNSf5dyKB5(yA1Ov4gH{8E(|JNFoYOso276?k!^c(6o}wY?82_oynlv5IPn +Ehk9iw=os*Jlo)AiNFw;-EqqVt%T&Np+{`(9037qMUy_4P>>!t&f}9X!%$VGF^;kWwvKdS|Lw{GTZTS +SN`B{JDDH2M_VK{(z^xGC4Xp}FXXm)nASBsG6wu;K9w#}*Jl5hw9C8!6#bP;I7n}+@N!W?kG0B^*+=8 +VOFkhI`&`L~dw4mgluKnKB|Zp@q&$ei>gI?2DT;@yMl +?A{5SDSM^1uCWp0@quA}>&DoaZd~5nmgG8aZ(Z+^(Kngi-licFou02YUVuY&47w(?B@nJ3v4WTjA!d@ +K=jZriSgVrU7Bq(oVtkN9P^Tf*4aU8S0LmT%9{0?=gLeioqrwMEEXtNx7)VzdO)#|9kS(GUip4`YTwDF#0yqJ~g{Cwazfw#HGiKPWbGo^1v1+ARc^d9ADk +SOmNnBgW_!aFYp-rF6&Y$O<=zzXYLhcs8%DS}*EintH=4IM>$s=vgik#t{ +%|k_>P1C3f>P%?f!t==lb2f{mAf5e(UVSy6SvB=ixQ_07$99Mu_Ai$_$ZXgBp%jGAgH9`#j6!!0wMPF +B3>v^nqjLoPDhg@{Y?Yg(bOJBOWTxq!j=BuJra0FIw*tWr^VVk^ha3vs7Hx`e1>>@3IC@v2=w8Z$BYV +MEZXi6FuPJ_Aad%NB`;pNG6%C$e5TvRdcHH~AgS9DAgu3P8Iit^S^@M}?J@&TpIaY`zuOK1Znw=qT*^ +|o5!Gbtu647*aiH>&3T}AG0-~hvlUsZE|@8tTr6XVUyUqE4dyRlYyWH8=Ou +>)hvdTS6X<*G#dkx1R5 +7;aH+{BZ;usFk-8GN;?lL(qqB{Vk)ktXmfDWGf#HMEeDzZ|b$TuO&t9%yGA5$c}Q067EDf&~XVrO;%N +p7Af2%1_5WdvVq|PUY3rhQR5j~ADrG15sg;9j{evlCzjhITMPFRQ*&Gcs=pnFG@+f_R>-0m-(QTl7_A +@azLxAYIQV$zP^dDe=1v@94E9Zsmh}m}jKz0J7NXbO@FHN*vUoZd+SIZ#SQPjsBe~+#ed|VI1!Q&*l! +MS}76jbL*>X_w4hA2&0EEHniFVse`22>NVl04T4HX%7Q*5kxCOPoFnkdJoOfCQ|q97D^0VuA;aMb~XW%im28Th3E@A|7Jnt={uocCdFyL~W14q^<(>-_Ep2vOT +Za|zk5vie(Qj7vE;Mux`#4t`e#Nwarw{@$d`}D17Eanwf*PLgQi?~(`1l^covTNrVeE)6p+)^y;=w374rL=Gqp_t^Ix6 +q*wo-RdDgN1uf8jL|4`4eEx6Ncn;@XH84S!gB;CflBT0K@jqxXBc(R +@A`>x@i5rcV))2Mrrvd(o#!k-NOxn$Yrng?Vu*dVNc87_`Ufq +%Zvsp-YpA`W!cE{CMwcX<&c(YmTWsR-9o>VUQtQObC6Z;p&nuov25uQvv#cA(WNykY1y!5_;McH#mUG +aS?t|SPKJa~#~gD?%g7#l3}^K))Jav1_OIJs%Qd#3BcUhi-zQ`SwqDQ4VT{1eZ)Gd(?*dF +lk|6t!G-<*i-3ra4KY;V>Vcn)Lw<0WaGLJHV?s`t8FQe#PLE-Qy1HS2IObh3RBe53UebQgCN9v0epe; +JWSslT)%?doF}32kH~_08r056I^hQ=XYq|DR){-+bSgN^#M)klNoALfzEaz5}&ZdBnMzC-4 +3CVnBzFRhz*H?Kl?kKUHxa>3qYNMd4l_Pz#Jtdj4xh4&)*#xMr@XdjS4fic0OKe`=E$X*zKKLhW^g@^ +qZ^wQpL@DP@6nON(FUG=XX9=kdg@5}Bgx`sXcHM*=YE|c;1LIx5V&r3Lp!@!7S_m;{ZBI90g84&SJI} +|70ON0Ruk3}H0*Af +?XwW}px^2FUc!HONz|FReqZS=_*%&dFlZ|Cz3yRId=TRONy#`_5UQ_v{eQB+G +C(C+MtdO}cG54=zT}MrAo_fQFSL8r3seXRA;~{#B&R#KgMTX5e3wPJty*UTC3VRo}0aum`ymgDI4W1O +x)mxi}%X^&W?%?`Y!2k=DQdGmyIJyRcj6p3%ZvyhQe)I}hArId2v%mLRZC_G6kKE@Df9zi8nujlP9W8 +{ZiizF=TSQd)139PkH*IYrj<(KQ@*CkMNVkowG+($2&6l6&_ASC-7Pwx3u~}AnNtp2!MA!hrO(qdOk| +zs)&kHKiGJ}Z{1qyqG!5ive@{pxpZN(0Ai*=>#P0+KmN}q9|&HIa7{(i%41wOxX9xza#1?_l6-PsQw2 +wU-`{^^bQ%E>f^5LL@%Hz7=q+!(hNitR3;jW-#`w=mTiv<{N@Czv(S01N8_2O|&K~S}n75C3-CjO9>;2g6VG(|LqFRX;w=RS4LNo{OFScXvkvnghQT$h~ +u>)XCuR$0u6Oy>qP{7tyYUz8UuxZ>cL1y&hw)BXNeA-Qy!-VBAVwT#gHD$5EKacb<0I?WG?>j>HF$D> +@2#m2}W7buX&2UAe=V!RxD?AMJ@$n(ma~8O;m_KCPI*9gWC447;unF>fkt@#b& +AA-k8uzkBnazr5?UD1a!b<$A#OD)}eziQoVKoD6@(w+bkB_AY`Al|CDq=yX62+dtvOU_R+j-2s$)9L> +A=;+=fLm$qrZ^s{%{6Enp~%%B$|TTF1152`l&_S{N*3Zegb9#6r&Lsa;*|dat2~=nO`Jfs)7R +343cOdKO(U49PhKdLy?LPoeO9KQH000080Q-4XQ=hR{A73H>0Qijn03`qb0B~t=FJEbHbY*gGVQepHZ +e(S6FK}UFYhh<)UuJ1;WMy(LaCz-LYm?hXa^Lq?%u(kOc;@0HJ6FjurW09~?o`pqDoZ}6YEOZ%9@jm`o-=HixdNr4p^&mYrOQt`W~~o~H2mb#1 +lm&YOK#HFddNJTA9No}E>@ebaW(x?XlovsLCn)yXz^ux+lc01fkJE6>(#vlHFT9+A^eAGgh~vVI}EnR +o&K%FC^siKmr0GspMbz*)6*5%T`HID*mO=Z+%k0V#*dMmdkfbNz}9W +W@iF^zLToc13%cdvRvIfsa02Qw{kV32H#h>>X!%X`>g36H-}okc~ZZH$*z9hNV$DnZ6(f`-~3do!yX5 +b{C0LWP2ohGnlFJnB`{8F5PMqHZyuV5Gd;I0JX9$lyJjV~DhKMXsuk=614U1xM7$rGmm>hyO}i_*V%g +MP+iWc}($k0haHKb)=i@)pRLQbkZVI(*_7>R}^y~#b8tsWRf%z2sR<4!ntRFvzr?QpxQjSroE88pC75 +kfR<2dOhJ$z2jM*3dkYMMT@w5i6e{3Tk2ALTEH3PcUao>T|gM|lC_!Sn-Uo`SHD?QruoupCGZY3cCup +C3JXT0HvCmye#kc=E%u;hvjPZCtlM(_h1_wa_xnk!kA=aUJfmtJKo8zdTgiRZ+`s+boBB^kmg?BbRTA +mg%PSY7O^4)Cj@r|Kg|T&wu#w<)iN%L0*h>Z=D>2-;1s(_GPy@J1YuUy`m5caWM(@!ek~UC_C)d`cbO +Gw!_EIX)f|HQmUQ2Iy*bt(ozBtyR2D3BC;r^XJ_9bg+S86dlKarB-byp=_@VbF96#a9zL&Lo!RkT0Io +3K|ByG(&uixX=L=PLc3>+2Yz9U2R&p&0COGcnnCdJj@y(p}Hp@6IY=M&NW}ZUe6}PLd>ZX-3%(=LPgA +p*NBNXJLIRBO&&8ZU}-uUm25)R;6z_x?qxTQ1ss)AEN@JJWzPF!;)2ST8itc+lZ1dzN;9X-hk?yJ +5B_M?gO0c^mYy+w%kQCdLX_&?{HS4S>tq2(*UuffccTl5EU&hg#Ou;e{b6JT~*Wnmu+Lw1KotkoBP8J +5$0zz*+w^^v3fJG!_rJdwH~8ReYf)nlm|5K+MBrrR;j`xWad#AE>Zvx5Ap}qzaIA>0 +ViDD)o|ebv1Xk;st64u0f#V&I9e%HS*Vk&tvy)=y0-H1U7#K4Dqa~wb4UIIR5EmkYPvs(WhQGWwg#=d +0K(?(zb%-WJOR4e4odiW1_sImD10~yIPX5LxBD;>_WStLyplPa`D1C2ufJ9F4&W#UfjK`>axALE1>IL +)g0QTq~9+acnM5Ybv+c~MFB+kkrA?_l!A`Y@#znQAqEQuk9M*>)vImQ-Dtjt|Df+`##{egxg$40*93r +AzOg|Q0GXjdeJ!_J`WG3Tnhq#E_+zA=koi^aHL@q1++E7m3J48^VZBVOwZR+!$`Icx5Xo(Mg|^ZZ#Ku +aLS7ile6pD~Pzx>Nnt=9?Tg%QsmJr{qt_jRZPfQnq68}>clYpt*B*p~0}N4Ob`4u)7NkPt0O8X$+3Ih +mFcpbV;T2B0evI)D6XN&;yw+g;UZ|I{UME35A;f-5!;d)S<=8-jeb>P3Kwrpgc{NbV|J5KB{)i|<(~s +{j%VM#pQO+}EIvq_IUd48;L8Pq$sfCpZeqyaWzu#rejl|OUad#JN)OYm7uA +_hEj&guIV|0a}h1#DITAoON!V0_iG#>yARtVsSKmD4K??*cNLOB5ScRA} +hH(dcWzEewaZio_W{W*JqYdlMS}asV@8eUsh*PK`%y_xA-UeSntcUql>r$v$g`TZB;pc+{U;6#5~o#^ +f7dY;Ea%JBfOdnwaUCuP1tBTto&i7w`F(4@Ob*aRu317}*=*gm2ImF&d}+b_KdDVG*Nq;1mS}!`1QLmg(6Tev`kFg;%3># +dx4M;P+#H9C%47u$6hv{v&ATc+?3B9(K{R@f{EmB8bj=$pTEJ+H)kSA|}Wg*yUdg6Crt=O7G5ltXK59 +D}N?MZVd<_Ey23RGec=)g$L0Td^-X_`*Q2c04eXNUqFDNh2y)ixyH$>5}!<~R-eK0$+E5z`(S{F#uPZ +`S?UX0X7dUV6T|$uOL(J=Ey-7UT;N=jYlPghv}p{n`gZ+F~X}su8US0tk +|1%Blus!6Wy@$QHywR#Vux<0EVByRr&P;%50Bb4mK!U+0ym +eAP-gl`fH!p;qNe`sV$RKS(3)FNWnKl%-B8ej+g*Va+Jj0QABSGk-=qbtYjOPO?T1Xzc5GW9psx#RN$ +lA~M?*vAr|-TDJpbbpNbptK7+Z{)aIsI`W4CBxA;IpLP!sC#+8<<(6VymaRssYVN*V891Oa2Bg?dx%_ +aOhtQk&*uJc&bjgb)1H6UsASGvo_p;{oh;WU&J+iRRGxPLk^lo>Mwt4JRJDlr{cr3F;}AqL$ZZ%!I1LL(>7#ChL< +mCi$SVxI^vJvvC2u^-qKR#nEQC{`4rors_5XOIB<$hn>(ms~i5+#IRO$PBWJ1m=-@^&+AHQ>F9!X-Z( +)QjXh~T*3BH^lo*v9cO4ntWYLqPAxMNVKDsX3T3f?#G!px*bR5z4LKoAX(kfbbX@@01>5+32wMEbo{j +76ckzP}?enzOl+pUB{6xEbgQXn3iGFjK*q}h#sO^148$f-FK?6FHJYvZ#(XB~MZ_3>&5NKR*rIy4k;P +}TMZlmk08Xi~K%AcjYwB888qjQv+qRjpq3X&6t{;zn$41UqhBV}*0 +s?@SS!gastA8XhkDQaKl)+Kil +UWB05N>-VdZe(bB6r2DR5_ftUrylyz$cPh4hwWq)@vlx)`FZ;Be7fso23ekHMtH$nHu^WxV_Q4?h3S| +5gj%U(?&G-bzNBb%I^B@damX7P98F&(>*Bnm}pMGg=|~~-fO|-#L@%<=X$`Ipdh;7^^bE0 +28B{uAn`~!!(!H(h3p$Y!~s%_2$8Vn;Qm!9jBqZBI2mEdV?K&WmhO3sN1R#kpyccD| +D>kOXPNMbY!HYd`^tzj%M!)lDS&S>^SC&XFZa2Zmpo8d +MXE4A(0gMI_}0WH%+g18j?%%?HtWZ?Dm_bLeC=QZ{`pc0liD^$|>SD^r0O2t)fz%wsQZ-8aPB%Rd2R-?PdqtpX{!d+e@8=-_4#lrfuT!+C +SBE%LErfz%Tr4W=!F{~b>IQj#9K$s0^-t +MT^WKt2=|B<&%@1@X>oxlPFJ<72!-wkx}d63Jqd{(4RVwt7f}6lG%JwbtXOBYegzE1U5YrB^*!vx6{W9 +{?rGOlN>#aDT*y#Au4^3O4E`r!jWzVM%^|y8DH0#zReZ4tMVz58iS-@NyY6WDI4Iivl6MW`=(qpui50 +*X|aTx0ZGtU&!wD6zRbbyxkkX(muuh8D>`9$1qfkd{5bsI)~n;@On<_7>V<1eBx(;-`CDG4amJ-hykK +xeb=~)eGW!FV-W`uAT;lMDX>UeI{gtB;h(egTo(4X*umq;iAC^N*f1$87Rje!$H0Nnvob$1AvT=5Rv+ +;KOHytPiL3ufv3a`SJF1(;lSL_p34Q(2v)ka2Vlf0Idxt6JDqMMUyw%G4NN6u#qOA>Kza_G&Jw)jQUZR_|tJ +rm{ZlNZE^XHf;9~Zoi{QOzL!>#kf2zz!WpH4)hJGsJsAWGLV#lcSrn0VQNwgs(Oug6ztLN9YBDhEIsR +sw@tW}#x`1P@dfN1GQ4eLj_cm?qSNqg=nck&eoI*m229SnKtB!Oj`$fk-%!y+l#N1CqYnp15EMwdlyn +6niqwSQC|bp?U-UxOD-7IlGDdgU(;a%g#WpiijT9;+Q^0!H#dCcL5{-aV`!`obD|H^m@TGGHMY;P?32 +<~LS7+j1mp3>qKou##M4|nUg$Mu?=4P`(y4;lYRcbcu946KC8u+Pe_5jO-u>#al7QU^}OVnos2agOPO +ia*Vps#4I>ls1^Ru`)lM$l0@j(P*DO#4Yt>~}WA(JtpOf)Y8c@u#m*3~CcSfx`GdK(F%*6=@inlwR+a +CDIrESxL}7DORVs372u)ABAC}WoiNs&zwJ#oc3Ey*8a!419g7-Pe&g`E2u27C=B`_+MpeUL +c9lnvq#p4HU+Zh_j +ttO9lcgYL3j^`mqGA=BKArbJ2AlrA`@zMwC{nvB`OHBz%xjLp!xM-~V)buM2>2@wO#w*IOsLUN4$GIdG@ie$Dd9e{Lt%Qq;Ibys3Z43RUx*mu|(Oxg4 +s) +6_nzVc6&n7e=u3;M}ERbV!cr+p&ZOTjFU6Xw2@yD|Ug;UiL5W^K5c%fTtd{1!OTvbcfbdY9G{d@quto +_ZbIKPyTau=-C3iV7b?-GKr$tDU-9lH#i_d*P*@02+#CEip?%1UxxhB?~|nM~df8lQY4AbF<%EHCJLp3Z|FwYr8=ryVtCV-{Jm +e?MP3e4?*3{1r1K9<^=LCelKQZgGX8fw}csLpzk}_OS5ojqLvCeYB!LEK7kb@nWnd2Zj&A +i5r-~m^pau|9)tcQQ_OcihQvc#Mm96;y9x2f*AFw9d_5KKMgj)O#3f(-^omvB;Z^Ycei +-Jy_+0>jkG3hI2JeuW^kEn4p&@I=c!lb&8!~*}cuz|%H+A*PLFz2khQf*PJHap@XQ*JC`XPIKLj2 +%0M?$m>NFZDfzNK$aJUy&JS2t{(!Fg0+&^G(zsv%(wK`#;#luql1jP3exVLz{9DzC2J|MOQX;T$XztZ +m2^f<@SYN)JY=UHVJOK6o93(SiRdXKz_xoF2dyI5~!aCsjXk9aHZ?Sbb6ZS7$sT=7{U6j6V14t8Fgos +*NVYaN_s+EObw}#0N|j_GWyk#f3XceH*18`5jn1#*~z47l&y#`jh;m9Kdo>@C=^tBYsm)$hiaV(49a&go-7R9|l(e_Gks@ngTqGEd8K1u1s10yQPI!~5j{gnJBP7?|XYNj)U!tC +0fZ0%wCGPqFWM5bqfQz4{KUZv*OlUV=v5|KW8iq6oku=sqQ}B?fRL(dC5Xo@i2w=< +j|dNw?J&;h(c%qAr5JQWIsVG%&x|i;h2`%1-LGbZH(}VlJUxvsXtuL3PV49IAgx1sHx*GHN)&BK$LTlBk9LckCETNa +^vzEV8dYYsm=bQ6Zm~6N+4O0rW;IPRP)3VvPPA^S&w}xB4o1v(`3`NzI(mwNR{lsV`b$#C|611CNzCd +JR?psQhwU?SOsBu3jC;Gv;4k@o@l93P&mM7rGKXGuNUSuB=n`leeC_I$*5{5mKEenp6J#0h$}IgIx|i#gp#O7r=ATPAbm$X=Xf#qD*1O +wH(C7pBZ9h>8f5NIHFL3t8X2*-Xx*!Q7!lVe7qd! +NF2P|oE?`z-bpi^Fi;&>iPG=CYK>G)C6k|%3Nr{QXtHU$ln1u(_PnOo^=x}KxI>T<6Rh#vzPu(=X-HI +4Vi5+o0+goaDuRin2lSn(Eaqi98M2}9=JdJMnNwLXKserPA&V@VQ)rxG19(G-UdCv3PFEI_33ON#n{Y +;j`pDNNTp6&(NFIyw2F<|)r7Ms?o2B%$V%Yv3{5FwxNoeqXSA5j7?S{eL>(TJw0mDFC_ZkG(&9Bx&n)u{^hjpr +C%ck3brp{$3(qMI!CLlS?{FE0~pL+E1(PILAffywY1VmAU6cLm@?c|(GkW;u +bpxbJ3?N14diG6fN1Ntfn)H|fRL +M(Njzp_$-5@bIiq8L=Nb%$J+8s&hiV(&u7`E|VvZz-0aFD2CjakRPwXKw~<{5-lnl3iZyKgv^YPaw06 +AvL8Ie@~5;-K9&>?!aoKT2%mm7$(YO?VKFeh}W(8w%Fr%a~6i&y{m_$-c|y^eeajOLYKJWgQHZld1sd@L8VJcx_}U=?9D{-5(>5Bc(CbM +uhn-U0q>hYt&mxnPuS@94)fv5>F5cOzmHi0yKV+2$ho$@2 +$Hyco_Ro@(Mx6GACxls`#ElBOn19BKEqpZZo8Mv^)bm^nRuE{B{Lt{o{(T*Zzdw#5f73~e+q +}b_BN?I%Tc`FPU>4&~GYku%fK(JQnrpIm=;SYXf3BSD&-en5yl0E}#|Co-x9505 +a&50!_{uo6~Q1c<(^fNa-K~CD6?&udbn?vbPkdwP#sKhm_Nk)uj_6b@m)5I4{QJnW4_BL5sq}bAS)9B +`Z^z9D5f|iov2_ige+yS)mx@g~1^vxqce>yZYA?@mdn@`S-E-Ef&h*WxM>C=zF8^gv}H?arTiS2nGxR +0XCO3)}GH|R5!kW2F6fXrP-@CQE@Jn+q2T8~H3m0!Hqt4Gd&r8pR=5XtkdIk%CofqQ|Di9H4$__AX7w9$c&egr@!Tvf&#-j{*-i@*fQ+_kX?JP_aLF5i +CnjmGg?`!P2$F)b%p@kZ{Z!n-Hof2%h4!N(JNc^ommaAqo+IF(Y_x?_Is*owdQwrV6x`&y#kpKd1+G% +gQdY6rkvuCSN}Z%@P;Kehvy5D*M0mZF97T86O;Z$?WqW=S5P{>3aj{d-3|_rG$7(#GtJ9hhl!vz+;YZ)4$+jQa<5Z?KedVFG0uTC-dpT9d!OrZ!UjtEog{ +vSVsg&THCnRcGBh!CGi|WX$n(_)6(&CKe^5s`pSw%4;p3GD~Kx?z$3d_Cv@)4$%@n2Q@j5KDTJU=HR# +Nr5hTE7xU^BeF-6A)zmprojIxIN(*G`$$JCsgX2WL4f#Fk=X3fCQl2ImyT(u77^f)i$~~?!_Dwje*c(oNbGI%bpZWUbL1^gmBmagOa=+RJrfkMuzQWFeJ +yUi4aJwbX2M8SiTL`qv*sc!JX>N37a&BR4FKKRMWq2=hZ*_8GWpgfYdF@zjZ`(Ey{;pp^co-@dvbN9OT&(C8%z)PwS+*~M +AO?mnP2AA{TjTc`a{V7>goN!iR +eD0Rd7JH#P49-iJsJRsiKeOT#(_B~~Rc7BTq2cN`nyzRemoVUQyT&~sd~S?$7j%k6^3!XzouJx`Kx5L +~E3kvBFNAg^6?J!s|9aiA3*F%B$)R?r3L_$l3TpsBro?TDf;t*{J`@vl_kr_w_b)=J4`5CoY_jUku*p +$j(SwLQ{T&NmDL5E?EwV>Wr$gx*PWJot=8qf2fNx-iV{I0G}emPgDZ +Gzb)8C$TQSKt^_UZYevELlyc#3^f4vR#D+Xo>0?+fG|#L&K3z*xJ6vVdZ+2{_{2Y85*_U+si}f>bvy#WDpRC;-JMvnrp#`VoXSuPg-56YE=8p_f6c) +s(qVVE91|RWP1ryEY4$D16CrBa3|G%X(i2Zw-uhf9PCy$~C2`Y*M_a=^l%bZacx<4XRJS6oDh<-1SA1AFt8RWt^t;GI#~iCzr(pcVFe=d4NKXdmkVh1%pA&60s=vIg5A-& +klyvVksk9L}LF3$u#EVWlfdWD)`t;DB4mdLpTo+6ZlI?C|R>T!LQJ_NIqG=nB*+R59sMzcTn>V4Tvgh +CZ|loY6`zf-bGp=U~hr7__3}(-LvZH$#s@gI3}haBxz$PuH*Oth#b9`CeK6#M1p@$$F*wpRE_N;I`hQ +Pnox6|Egu>YeJ2nJMeER!XbmYmGe*6;r*pF$NYcH_W7Yc=QhhuM6ZP4&%zE3YeR|~>~NZj7kkqWP!~h +CLX~1CPep-S?ybm39BwTvii9nDT9zF6;MlTSkuott4DdnQ!>&jG^MChw#l-&#Nn7dLoG{l1A@jtY;9L +YH?BLMW&f(*&`}ed+J^~Z@T?xzyr1_MNvh_0A&e@VwNBeq~tfC95>n0wcyg;lR9o{|z)5UGd +)Sd%_B{oBO8eQ~lAg5ADxEvCo+`AYyC~~ecc0kWEjSFo;n(HfY=O3g0(?e`ij}?j*1SO;u`QJ5`IL=( +Lbu=M0`HZ;t=PxO>4-^W1)!Bovm5kzb=#0J1KG0YaVx6zue5ng8jNV#UnrBUv<1MzhnQaWvqz+9B@B$hEAXUk$?G*^`cyUgz +%Aw5OeE{4{j8bb9JM&t1-Cb~nQxf2y^QOT+I|Zm@N~q;}7Y(jy|}O!PuU$An(2TATZ4_Ls=p*G)!R!7 +uKH;Q_dD_);_gLwABO2e0kr-l($?sU%g~kl;ig2Nr(=P)h>@6aWAK2mt$eR#U~1Yoq%R003+_001KZ0 +03}la4%nJZggdGZeeUMY;R*>bZKvHb1z?CX>MtBUtcb8dCfa(bKAzX-}NiDa54pZNzjs9$JT^y6!1H5-a$AwkqN*jgr2&ELV +x#;q9w<6@+mLYhfx_W>cA{Kus2MDGkhvx9^WW9EJZp`thPKE@-N2Z&}2!b&@Rsb+h%;@#(v>PZyh)Pv +t6?lL#?0d(4oWZr*km=S3MMGQBgq@-j_<4q=&2Wgb@Z`kH}R7fyi~1f(y*1}TGar0lKz9RWYzT^xN3- +yQ-5!gt3X`!qI}MYxQ9%JOg-r||Ec%oX5YO{#nn!X0Mu1CPZj%+y0T%cG^d&+=OyRdFH1RW!Mc=2G!c +sQ`Bz@9*?>cET`9k}wn_aYeIU9T|`JczEJCPW6e+Q3UCZbs+9euac+$v8xRcTsaM4T-7GZTsGFS;M7J +eEzD@`-e%C=90sm;c6LxNWDZx?3K9UU6B_fwFaAy6oufyM;Do^v!qQ?tmi5$5tZj0F9p5i1d=lPm|N1GB?u +20H}jLQ3)Vq9|7CU^pC?a}})C#R5p37RG6F@u<{OcAr11mEvyoCDSOdzHXBTEz5+Svqp(~z*jHVTmp3m0V($&Vs3dCW0S +E0aptV>;s$2GY)vc>j4AXN7pC;080Pl;LH~bkzvl{(n2=Z%jfw7k_0E;v$989(lxJ)&HI#1W&`V85eW +SOG3RvQRgwB5Al3;xtYW?A}aQ-!AG@74(iNnVDN2-f(^=t0H*mZQ@wmA#EzMGm%A?IT>{eMBxBKq*Vu +SW6I_KjI60Ut~+!7vr)ZdTG?O#$A#+Do{%)vNTzX`)DnW#Ux6F0%Zg>E+ptsB=n&JL&tMg%s)yIHSuA +aZ@GGlH*D;-lX%;wC`MC}Z?Ah6)-22UFyC_sioCY;)H2fhb^M*maU7-Edv$R59nFhqIt9+$XZRuJU`k +7v3jR2ndw?BADurBn9TX=@Ga-^}5+z-0ew);9Bui89Z*Is)5O;2F2r(X#57I;dH#b_+_2xh0w&&0H{< +=G0k2FB@I?(hQ;FXZl0i6Lrc@Zm!UPRzDp$S|AgWBn+LeXj^X$I^IkqnSBnn%EVkhr2uAqGv>f9r`Uk +VpJq2__#D7y?Ga`~+fcm5F;PCb@*gpq4Ak3mC0>7#TlIO;6($T;VzEN~5a<6oo0 +E+rZ}@RM2g4nF7cm_h>P!elu;z@!1}u!iBgi~L^CEDL5aTp6S#%qWpie$6abW&zhaBu6tm^L~an4O5x +`#2XWYh2M~i-Y=RLH4B2U9tYC};a6IMCBWOm@OXZ|S?)hT$hK#t-jbRywkZX26+{_i;=9x19|j0r8w; +%f@3VUev0h&=I2(h3(KnzgU^h2u0m7>R2#oHKC|X_-IBc6|mj=SOd_NI2ZfD?sMQ)BE +dZxxn*-h%5)#)m_P+K@FP+TKR|f?J~O=n%1?0A_tFA9g?v~6_z^`Qy^S8t +I{|EM6vLJkFHn6Yj|~~eRG~Wi4ZP|uYeg=Lu(-OF>udI7a?PI1W?I5VC%wvvkz4|Y!)P4Ol4z~~PF|= +3&fTwU&}fo;8esuVraFUY5rjX6cTw(pC-1&L`gj33l1AE`)1AGTS-_U{hW0!Y77Ex%;5)Lh{LjNPh+Ebp;b8( +=&+WrVT10ryV}ZKa9LQO@#046>)dWpJcI(PHi3$ifTzNA&CJK={waKqNA)Zq&POTnSe`!MdShgGEzIj +=cOV0h6!GOTjt(?u%63H;4YxM^PndAVEWb2fCU8d0$xO2_Ws6}_-O6peb1xp&hWAJvkZF2_I#a23sAdlexnwh)IOZg;^I7e$y$cK +VC^_QwZbwJnx4+$9_#}NF$Rv5_`!2$%x^LdsP+veGWZ(7k1KvFbY6(c?$Rsp$2`#@KgXd2Om4PWN&Am +ZE_L_}MICIYF3uXi4mA&@;U_z65RTkc+;h}RdK{oy%%eQhI5RRN7@jzUefE|QZ!mI1NK5-hoP!Tt>9a +8;Pu12PDcVcqq(_p05kA~RlXL^QHPc&iZ+tWpHH$cU&{Sz3+z$b6`#81<1`jJ0KpdhxBLYH$r^GO43e +H~z1?5B<{No6%a*k8A6rD0_D5_|+%cMxw!@f26DF<#_+OxA +Eibh3OckkdNQ2Egmpx(aavaY3PVfhi$-tO)*tljaz*JK#(_iL8Sd2Xn)?2q^ZZTJ8S+^RH+T-)cDF-_ +Q15yxe;gSU~DFUWJH5r~v0UN#YU1n=EtQV`R%aW2AiHM+U69!n+d&$^HnuKnQk|MY8Wz=oA#EZsO(vZ +%`7(L6$=>pJAL657&k`z|#f0OtW#g;X`lm`Z^{TV`7`M3r#{8JpV`F{O5{^>NJ()WU&h;%U^Kw1KBFg ++%s-ve-`C=cz@VH*Is4Rh{$Z+@=oU>$YY&JxTGW5#!xn)ZI_qq8xVB<5Keta=;YN;jmi4*a#H3QR?F` +8>!Asy>WK&4zyvZaxU??T7aX0JtR>Yni9GNgMwuPgj!r+LDXUh7DvRzIS<5oax2s#DRBm0U-)<_Azdp$~NGW+))L~9C;M1n341RBmTP@Vp?!o>w3YHJ~4tJTU9NON53bL243z$9E6a_={=Us7L%|Luj_Tg1W)U?IS=+Mr%<*JFiJ<2PvI_cL7myU?`dNtqe)a2T{> +%8sbe5+TbD^{_|X)%o0QZM25{;Hp&~V+jU0&DxD6nRtTmoJP!o+Kg1X&!xf$Qu_tY=N)jJ*w2FJX12z_5UV_R%r%6nd<#pa?J6JlN_qi+ZYaGyr{(5{dnzIRxK5?IKe#Xa +*^qZ>N6fYItx!us!_f3tg8ZqZBwR?|~KLT;5{|xCqJhHS`ReFhW>xcz!v+d*xY@fElQzi~6O5U3+s=(Z2E4fL$b +2Q?SNYE41p{D;o%zS3>$6(_6MkfWHE?bV^`CR*qolK@}fNkC_-=W@{i-h^pdcoPg0PcoJhyG-zn*@~l +iL=(@Ra*Z_#LuH&%_so#YdTw}VvWkCH+$IVUk01Y2geU}vRlAccyKztzu1jNgr?*O}i2C}jdI(r8c`< +zBB$Ts7XC?p@)`RrbSh?x6gSmHif;c`tX<v}@3>g*#4D<`mapwm$-YuHzD=9-?_sHIQuK5)rBkcVqdu$! +*R&j{1-SR=Q#fEw`F%Lhg7*K<0iB^fkpqD3v|J@IM}K#_+~5{>dzA^tzgcn~HPItnyNA_MKh%kUA*u) +*jlbJPjRk1UYhT^%UhmYu}}apj_FP}*m&j--$>agc%(>=2v|EIwC=>cnlI*0U +m}C+L$#qaksfXs}5KsiEil)hmp)B-LWdrdZMGji|IKXI*Pp@HA$F`X3?TkF)&A7$JEc!>GNcfl=*Gs2 +q&!A8%m##Pr*wH(P?wI(yq(?CtyqP)h>@6aWAK2mt$eR#Wqj+MRm{008e6001Qb003}la4%nJZggdGZ +eeUMY;R*>bZKvHb1z?HX>)XSbZKmJE^v9JSZi(>V!%Fl@P?tyyA{h?VCm2fK_Ji) +ZL^U@jil^&LHpbH9Fn^E^0wIWA(qH<4$t*Dhl--;Ugg|MRT;M52rbxNuu`dKwo;nqs#?jlNtmpRl +?ZC0y<37bbzG?`qnPa6TsvQ&FntwF!tipnyxQB7H}rC?fY)lL+QV^M7dtChsQte7Rn#x`1{5VJkhhdH +EcncG`clRms${K88kCKI`>mA24oy~ZK!t1|7AIhe^xt1T-e^s}-wX}&F3*nJC~cm)j%d)MxVwG%GCoV +L~m2@h}odiVFc>~DAfdiY6>_7!J#v{_ip^7Dto3h(Y#$XeK}KG=<_vQjQRxjoe6=Role-b&a#L?rBEa +9YAXHFYTx_Rk9M3Cp>$6VF`BI)xTiXwaowRR}sZ-w4E|HPyYHBk%035z0XQ`Td7?A3r}_Cf`YI8(xZP +*RtrQs$fCZRE5x4zaLagm)wY<1GfCD(%Dv4_`eg{z^2KBGC^pzXj38u<-J>rv#jD{cPSl2L7WIhG5l`M +rhmNI3xe{?Z_B&A8v)7sAAlSn6}4M%AR#;cdFHg{ +>2GRYp%_hJf3zW;w2ZgC6jX$&O1T>@3dBWECq;osD;@8@(Lx4*Q3hIrU2Z@syGtwP}TvX7Tl;R9SK9J +Z47KJ(sQgh3vhtk^mdLoeTAfZuBqDM(VokJ_l{?fC;{pTntub-Y6P>2i3(;C;0UtRq)~DzDPd9i6{sF +7laj)&gel~4?aix6rZZ(aX7|oj%dhhey&jJt`YF^(OX|Eqzz|S0!&)4LEQc;R{Ak7ElWv3;&kd5tI#% +h!(TS4~M(`M4#OQ?hyqqic;p3+}l&`RsJVH6iU>(5&+W_dPp&40-Td`V+94D^4GDJ*!jo$y#B}Wj%UKkK>g#bpbSq9a(VBV +&s9O_0V{E8o~$#E)6{K;53kplbrs#r0!Cc{-A=dj0?oQ8bsv4z+_Y?SLw##RDr|tb=HJ=Q}P-zAQy>ZI +}r+*4Ab|n?Vv{aXakVK5lcT=`b{ODm~FiK+iSMim%;(g&=DQ- +1gIhZz@nUp(qx80{iQ7NTimI5@&d#4?MJ#xX_dFik7^CX)hXH0s@auit{|Q*YbqkK5#2R%V2~@JF!Y1 +IxibOKFDX6D?^8bY`>BDhAC=J9lL~Vu4xv&CGo{jK%ahS0&+(f>RQO?f#`46kkR&AT;sg3F@L?h}6_e0+q;{0ddF6nEqx7!R^qRhG0hW++CJEw#1~Pvqwx|!T3%2F +?#|O3|@ZT%_+)leDNcwY$IN-KRqQ1}W)P?`q}ccO4HpCCH`u +n1^WUR?;fetr_AqB~w|Shu3461L1BTIUcWN|0HaZ$x77a`7*L>IKbEu}HWDX+6UkGDTJ_~n++47Atttg1p#Dfo8PPF^}c>IYf9MdDqk9BVWYSG>)e~y=wJ(^baA|NaUukZ1WpZv|Y%gqYV_|e@Z*FrhUvqhLV{dL|X=g5Qd9_(xZ`(K)efO^* +)Q8x!in5&r_Q60F$aK@4>|~mtX*aV73YnJZn2jt7q7=s{`rr3llAXH|Uc*;c8NL6a)7GIQ)ypZ`#wiGMzPOav$W9&YEGlYt+L< +*lb2e$i;%UMUz@Pj3=`hJf4?F4HHlBJev#12=FBF7Dt+u3V>_AVU1#3Kj3p5OtV5h2o|l +Eyn!CHm>s26Ef(_Wh&fZ^cv!4i1s4H(6(#J`QfL7@e_Gbm7P-`GXxLb);OEYG+QraAsE +jx)s_uTz<|hje!Nr}@VYJNxDCq`Y%Fp%->`Srn`Ws>=;8G58A<*raL%$Z3`*F=g7g5Ih2472awRPMqA +g%m|FlPp*;*VG1zUiXY|b+ni;TzF_0`SAZ_X^qXq2A)>+It4?5E4~)bj8KF4v+Ndn9ta^OW(s9#7!-^ +!nrZ`}2<<&;Rb^(zELeh+;8sntB`@Bq9Lvhp1r*2NlI2PAP~&um(%@Qnf{%wpt8{=o~qou;ah6xl+Y+ +#PAbE(YvL9R%H6K3hKkGv)NFzkn@_?8?ei(OS0AtCdbrr-Zs`d9SA~u#mk&s%2KdT*if@2*OooH5}D+ +MgEL^n5=i0PId~z;somoT&G)AV@PCF&1m*z%iLZw=3-Dx +M9)A9+ar&@E#on=38;-&nXTy(gik~i)JlE`3xUHFax)3)MT&iYSFYc<@m#h2F+Su!pd{8y*fp +NxSO546dj3Jaj5)_CxfNi1^DreUy+tW`~2~KX0MDT5C22X*bm-Et +}bFasYuif{5RXV+)W&2_UX+uyJH<8}(wYY7Qj_LE{nr*`jwKR{)AC)n1T54X1&TM!_vnkC-xr7qN}H& +B!OPXIMjbLv}!J2`cTYj8jiWNB_O6#+~{T5B6eR+(AANP8MU6>=5n0%&YI74&X{9pu8wSv8HKO=6=cQm$+C!6B(l_h^krl-V +bI~)m+2#~Z7>q?b*F!;z4a|x?BC6Gq +c+Vuld^GRqIT5J-#YY{3m1pMAi0H7Qiyhqnj`+REq4^LKEn&(S&#Mq}>SMg>U?r5!!*u+dh7b}48dUc +yZ!|Bt>SdZ+NF#)hg#=3`oGm~iDprp)zkjq+RjiRpA45_s^Ci>(dK<_wtUn)%YW+FsnRd%TIcX+V-3R +=p!EM&7`<28+n-YRC20JWQ%M(u7w>EiNOOT=uo=`RGc#XV6T-5l*X2~X6~WxpX@Xmxg{YE#wfQRc$Th +WaKmY2YkbBf?0CPZfSzqz8Ue^*~*&eVqpj5pxo3+ak{q^PS}kQujs5DT&^;jzuYx +nE*efW?Ck1h!N9V{cLSYwdX%mwKV+lrzV1aIq9#b{A2fu&Jxu$e{$MJoF9_dzkM17)Fq~Wa44^(F{oS +6PvK+l1Ry6bJ*y?^7m>?z$ed>WcOD_AXCLH`xAU=6Lmkn7nc6VI4GK{fFXpnkhJfpiCt{)Ny_%>nJvS +P?Vl>qxq&cuhAu1GTtw7zpk1X;ldrJSnD&gBe~y<_t?<|H7-Ud +mO5aWRJ^V)$_KpQE;$5LSb}IjM>0iH?S_09vE +F)WIJm2r8Z{72g8*`U`a%ro~Nwj-KG(6PcUUpxGo&;VFjNoXsWr62$JQYJ6OW@y!(~&;JF^0h4g$H`v +b%Ovjz;1~6)K-O{2_mK^U#2gXCsT^P*#tB7y+=RHb)S77FlM|HdTcvfC!_xWP)h>@6aWAK2mt$eR#UU +|S?w1C001in0018V003}la4%nJZggdGZeeUMY;R*>bZKvHb1!0Hb7d}Yd3{vdjuSZ$eebU*^*qoGGtR +E|VWp8kj39^+f}qSGSRvGMx~mft$8Oo~OtOf7-{ZdIvSR3m#Fwg5r%qK_Z5rzW)vYOmwc01DKd^_DSA +h@fy+2u&*3@b_9)x|P%rZ%8XE)TSMij~fc1CK^2Bkd>cCu+I<@}>|Vyj$erDn4oU0iDnu#47I?26-kl +3dVD(`Z%7psbG>E}v>q6xELU7$pQWIX<`L=5?U(mcsw{i+1T@Ri +wg$>rN889nE3@W`f~BRl3#mI{t{Pdc +>8@-OjsN^r*F&p%Re7RU8*(jPy?kDsZ4EE^fJr$|T)u~5g_o+ja4Su~Dowfi4;RXWSV$}ZTl2VvKyXe5$^U-~%=rygGn+QhAQ29`GB{lYeG8IU9EKzL=9D7Z=J+3u4VP_g%PnOdwN}&&|u~4j-JuUgdQs~O4&Huu+w{R^j2> +NW*5|p?d^ucDhAhT%`}PHO@f^?U1Lrb%1nefZMacix$xb%9L0~VcBst-eS&%rKaO{t#c>p4vmv05!Qt +m2>}BkK&|%{-dLGv@MBxXZt<)_?b7fReQY#Ok$EAK^XgxI{=pi~yUA0&uzpw64h<4JsGq$B{H{-RjR)EJbJQ=3t;DPN@t^y*v +k!0PH}9vPXY+Syo+tkSP)h>@6aWAK2mt$eR#Oaf8puKd004Xj001HY003}la4%nJZggdGZeeUMZDn*} +WMOn+FJE72ZfSI1UoLQYt(3`*+b|4+@sVX^iAt3vyoX%|{99cMueRrSB0q<(Oy+Q6HHR)@n`cquS1TQ7`N3)js +39={shvkH2qMC$(;60s1Byq|bmZ9!u_n8LS{Tk6m)OvG%d-G@ehztTzdi6h=6(E0*crg<;<@&i(# +HOn42)G_j|NJh+~OY$S2FPTZpCR#k6+3c~%Xfn9kmsvYGMFHfzBMkCHSp&Oi!z-m5u!{=_#CQBg;sCG +ufMa6AOv0Xap4tV;5Jr~goTErH}ks;M@3NfUYv80h#3iI{~u4AZx>DwYu8!h=%rN)j5F3T=x({Ibo6} +(UPOf=fOa~Dm4jy%@r@h)OW{a%lFAu=MKeR+<8^@G%n;X7ALOTzPqZn7?VwAkI0|HIf0mHfxby)}P01 +;WByUSjcTglSb54AX@XViX<$U6a{l{{c`-0|XQR000O8`*~JVE`z@J2MhoJUn>9r9smFUaA|NaUukZ1 +WpZv|Y%gtPbYWy+bYU-IVRL0JaCy}lZFAeU`MZ7vPF=3lPNuW1+imV;n-?po*ZAceC%rZ2Q4k49s40R +2Ks##M{q{W%K#%}MI(FRjZo0%0N#J>Z2l=KHQWLhJJdZa_DY{}KZg2jeMB#qtVzuJM%3aE4(T}`b@|= +y9Qf!FcmavG_kD?KQ&+*yRd|&?Se3m@_X7>EsI&D$hf5GHAN<3$^KJu$u_Le$`O0f-n(n7Dp|^xoXKY6adAU)p1pPtr +lk4#n%xPj(mZd#oC>)CI6t!Nly2Bv+in0kFzGhFVk??%-`bl~QvdMz=LLukBouE%#&Q*Bz-OwHO6H-a +j*c&8&o7fV7q60&+2!HU;pJg+dU!Ikl07oJ-pIUj`4+jSsDqi5sGdn5YQt-sicLwi!>V+a^F>l#uM{C +)kjt~9vjcJ>ZWzhfk{29dk~Nbo9)`$Pt0d>ewS(!d6Tq`ha9g6b%3KWt0ZBA^fZvH0cEgA-L$f_FtoN +AuxfEKYA|HW#nO)^>k!M6KegIjGMn_<@k}oP`tkx4eH75J6UyWCz5qchzu&#E&c*XQ9b1zDg0&FEoz} +iP?PL(46po+~3Ew|?ac#Iqk6HC(HODRew_10X0kO}UO6w8+rviDa|h_nmwBPmx5%u*k3%G~$`%Z!F2K +-Uiq9HIIJ!L6jI%1;633Bwn8R~(2VNhB*}k_@3?x{?K1jz8e@SkhY~@}G&lu3hnG64~oqq{b6|o{UEx +3IO?74C5goIg~AU7ZBe8o~stocs%|Nor&l*IG86E!WBTF8Ow}i$D?M;A(Dz#ZWN2_3ZY5CQp^*Q=UYM +v27mA{&A_$5XQo8)A`KC!24G4HvS2Efwy +6_P`wPLl2I@T!i&8P+X%V!*g6u14EdZm1CR&gZHZyr+r7u|yI)e)Y4+7D*j1>(yt_dwND_Ed^hrQTW>0|thWn5!^P +2dRzsN^BGbO}_|`x+Z0OCcMmp$T~6zpTY&`NAv$>LItrHp^`dGuC3069A{cKgzh=S}RRasjUFe90&yI +J_|q<^FnC~H3r&2OBklXej&OfWHjpJg}|J~z~ob*G<<TB=IPSl9R5)f}Ohpa{7f7+GD&JjucA;I3K`ZM6~dX40^!XPq +fy$R0sR}D+nFc0?V9zuw=Rq#l}8Ys$y#YcTa!+)gLB9DhsklH#eY_5BV@JmLh4q=su47%JD0#-^A~L$ +)8X~S9=Jp^-0fXU-J#mskC-KpF2wD^C?EXk0pr)Oea~i5&aV*`3p2v9o +Evh`jETqJS!7XTN9JricGa%_hlg9TyH2HY%gG@d#Z&}5r+s~g{8Zjhci5a#zkE=GVK3dp4<@KW +ww6U|tubTcR^sci7cCetd=6T&Npf*EEH`^|Zo2%h$wx;M*WbOrX?9I_@G=<)aP8(R@5CCC!CEAj0RQp +v7EkhVCAq;8|lFMD-gju55dmRmRiC%7bpR!Vu7hq%{?-!yftfWH(esCM4qF&SpGd9TpS&C``chE)w0B +EjK2oFaL|JDtv{~STZ|9pLRI=EuE(RC{(mNx5^uFCiom-PZ#SCU0TUW_nuGwp9g9hS4hQ91- +5EDD_Vd|4XBe2$O5z5r``v<72U7bNRxPaAlOXref#Q=BwR?B!nJ@5 ++102j$?;tAk1zAfiw?;5}TQZWaS$fUn*S2V^XJ(Xq1JN03cRNfD8)!V00KNh$0D5EypX`)K-6M;h`lX +Ij0CaqA6A^hLO(E(jr)vUOyWCX;)j(?-b&fhEthWFekg0P3c-jf?87o6-)Tb^fNrrig2k2wJPQ*Sw;kJnhxM8J{SzYuh8v!-E +V1(H$&EXqe`Zz^w-d)azlLPaEk3np@P8iB1ZOEXT#5o0SndJHA;hBgwu@RA`vZ2+wV{lV0xeaWRAQsB +fsUgGk+rQRThc1mQJO0w_i+dL_#wqe9|F@i;b1@^j;WaufL}DJ9nVvZlCK_(-yGdAo%qt|Ek7IN) +z072ITY{DZzTUd&jup{-NpD(t}SOw&Gdck(gA~XA+ZcW<5~H%r2oOI%-Z(az<|uxX2;Qb|>=n6x1#lOLUiEOWY2&wzyEm#1eJv*(AeXLrpFS!72k +rYnu4wb#7-`q?BF(FeHK$8z0A}+%MErv0%3FmKoxufR=;=nDtIjaRq$Wyo)7~uakpT{%(Wje0I?ym77 +bL1HgvpfP1DE*xgANsn*R_*rB_$?I$mJM>pmZ?}xEWHn_u?)QRs&G$@VW#<7W~7FFap?e2Q8U>9hgi_ +x3ULEo&xdkq=U0Zlfb`~Kb|Rmi7%en*L>WH9T;6L*8*(B_L1$L`g?Z<0(V0)+|-&b2<&4cE8fi2`x1- +adV01H1|ftHWj0kI9o)3(b5#GZM>r@so!~QtC9Lf{5>ym=J~MKlwLH4qLvKvH+V$}--WARAex3`)n{t{R3bP^Hm +{Ac!JX>N37a&BR4FKuOXVPs)+VJ~oNXJ2w< +b8mHWV`XzLaCx0r+m72d5PkPo5Z;H%fvOKMutm{afB?A^ZO}XfftHp=HoFq3l9V0q*LQ~0g_LY>5(8^ +X6nW-8bF`vqm9`8TF6yiSWB49c!Y|fpzZY`9s(B^OFm}g2eSGzC%igOauo5aE1 +$m1dKtpyFW71|o+k?eD#&V1fMuBb{u)O4!Uq?9|0P2fD$V&{P7Q2p=$t$IEx3{+{+i^6xKbNc+e*VCf +SHYQdTzxIAKL4pubhvgDT1kqTUks1_<2~pfd?vB!YO`8VPJ(9lLvm(l<2$#Ovj$}IdBwu4O+-b;ZBXcOzHDa2 +!Q=QCy;9b#-8Gma}p5uo`r-(D@p>HDCA#M*sWi6THk>ti5H4%AvS7Wp~!yesq$d1uHf3*V1FJM+8<+M4?|4dzGWEDuJ)2wnp(a|_>W-)iD&kK8XRCY25 +I_JE~6jE2M|Qe5ep3vBLhuoHYx_KoUf`C4nG{ni`x5RRNANKoew96LEsPtIFv%7H@$4)GScu!v$6(*|OW8b^)Y0+7I@4m05+UWApqY&&o#{eHFFFG;wVZebN~24LO(k|+`)jdjGQQ$air@c<7DO({YgD0cO(vb6T}gfY#2XcnNy-&KueibBc +4;i?byMA9|bl-RiQ4iqo^N5j@UdrWTAEAbxdq9m6ntxo)y%39e*6!w(l&VHS@lUJ;K$jGRivGgjAC3G7f5-lI(Q-bHqQ5&Mk5P|4{@BL%eotQ<=nOl#xM$eDL@#A#?9|>vv)LUOAE@`X +iq$GqhXaXt!45DwIO6FQxUN}HwL||4GwF_XEzS#=8<8@OnUCWQH?xYWVBrbA=R#%ylFRZ&><^Y5c#A{ +Er3qyFjNTd~JF9NsmRM6gA7b_-3LM5t77%J>dPTZ6YtIfXIqfV??)%pSOPIzCE;{haSqhh +nS`#H6KhY6f7fC>~MY&U=R%O;=#~o2skp^Q0Ph6dat207XLzswsXL>p1i7*k_7(DSx5=h!{uFxI7OpB +h0i=h)UpH>eWZlSjtRdtn>`YGtjs__3te&m8@z}*m(XAV{ORUS*gIc0$O|HReT`27mLYtjoI3BMLLa` +3#~3@3i$SUYpg7KCz=_n>DguUPYO@u{s&M?0|XQR000O8`*~JVd&wyNI{*LxKL7v#AOHXWaA|NaUukZ +1WpZv|Y%gtZWMyn~FJE72ZfSI1UoLQYQ&LiLR47PH&Q45ERVc|wEKx|#&nrpH%qv#N%}+_qDTW9Zr4| +&W7N_QwC;)M0NoH!X9+#4m5*GkaO9KQH000080Q-4XQx!0vPk8|V0Nw)t03iSX0B~t=FJEbHbY*gGVQ +epLZ)9a`b1!3IZe(d>VRU6KaCx;-yKciU4BY({tR@?9^#MVhp`D68hOR{+2r(T9ktIP=f(AkUy_Dlej +JW7j4Ki)=$UB}JZ(DH6adALXThE=`?BrINEkB?S${J9uvp#~8J|M_&2}GaGvS#d{Ohj*_=B=$!2djmrD0hQRM|N)V|>HQ3kA^Y)iB7O?@kxlWnvN0iI2WQfnD$_>(lXs%6A{2~w;wpu-sZK<6=_i2)= +y8!1v!1KSLE295Qgu3B?~WgT9Kplu97Kn0)7$QD@%te2m{B$Itzife2Lpp1$_fa +J>)h(ug`!&2BrH!*nwlQ-#9&P|HzV(##k{)nq3Y&kQ)chb=LH|xj+u0Jc&ze~LphsTPF;&~h9 +dRU%xpzxRkdiK+0L~Zy03rYY0B~t=FJEbHbY*gGVQepL +Z)9a`b1!6Ra%E$5Uv+Y9E^v9Z7~5{!HuT+J!3lk^I>l&T2F!rF8cQ}5XuBeC@-Wv^7>SO#=;D#ITQA7 +J?;KL3NXd@VEcHV|9iID*hm4|#d^-C?etP|iL{T*0<+>J%{4MLst_8EJjVKC!Jz7&C*{C>{(>-<_ZRI!iCk-=`nWX4BK@Y?^+YemDcfD +A&SMFnxqhi3VpMFVoBEd3ui9f{n7eT=RkrMfBI_7oYFu)002x$-l2oem+STKYqR+j`u<1UtXZ#K~_*H +$sq%WXJ0d>-4~CwA#1<}s+=uJdc)LE1M{)=`CKY+DuiH>5cYKq3|XE+eArl9JD&mkmhHww;{gl0Db(1 +72lm09k9EZcF*$kT!;Ngnj^CLgTUu-w(ZSC_D&mM8p;b<{Oz`E-$;RAZBg`qJ=1UY#s#+0Vfyx?)Kf% +;SR8}7iHXk5DVOZ9V;!Eh?Cu9~PgYctO%-TQmbD;v_hjgxclU8L0% +%E9$iwNao!rqYz>Ejss&RlO_F|g~_Rj{?&%bAoXXC)(I;Zju=fne9_{yKbO+q-Nx6{TkO(F@UHp{;8HMn6r7eFhggwt4~iXm-FV1CTn|(6BK3 +wB)CySjnZtP6<3oy(HB1s1#X(Z@Fkz+f3?J)JYQ1pkS4;Jtl9+>!+$w3r5t6R!>y~8W0nWE9S2jzOVc +M+I<_tB`+XeX$5G*moIO>P;mn>ggn-P3Nzcan-BDs=_UgSnH8HH{fB}~dPvt>8ku*uW3ts@y#@U-tVY +g=D^|*3th3fv_}(?(=6o=cD_UYPM6C-<1)?1Q;3`d9uS!-d6ZZuqrt};HPKOdGc;10ybu)=ICfF9*T3 +vZn3YfM|eF|9@+y6t*G>XGiIv8O+qkSVV3-I7?2=3dUPsA=TBytV`T(|D>?I^Cl_y5=^{KQ-H@?py$2bW_j{z5v#xGD7>Et4792kbj|Aw^S`Ez6C{6-w +=hQkgF2p~^XT0Nx)Asry%zwc7L{;lCTDi{^*q%DG?_}NlZOK`pkZWH +}z*^~VnBE@p9aKajs>$)z0$#1lfN1|kD0NXCVOSJ5j?>Qo6747#+v>W1P5h6DQPqZtZFO*J&(1|e0pw +bvxJIaD%^}TuSUM?|OdOcF8x>l(++3UMaV>UFscDb|cmhuirG^{6J +V5Y2y+{aF*3g&eMy*VORZJCirX!OO^PvsYYQg^?hoZNUy`?s&`*O)_wJw99IK|7kj1s(>XpO;3c617}B)uuG(@@S0$ +_Pkwb8-KneF9BH=R3mmKgRCz`ji1Z&gqy1D{aUR`;`0v9YsGAhjli@-LyIDbF#HFoQ@s{!}2hGk(A#T +H2$a^pJ8ecU>j>d19u*0EQudqr(3BP@uY;UKdWE1AvdL@Nt~YSk=Zwh_B#y%h8Yeb;^7@ZydNv~{{Q7 +gg4Vbx((A4+#Bfo2>1;dry`Ap%S)Y^ES}FGWJ@t+X{UG+8-LJVXHgH`pu~`2l!iEGA!ds_sOI8(lqZu +@aG)~BkyRn!gKvF8_sQUN4)(t2g(C%+(2=NMboYxVBs#<`&cZ(N3*@7{WqMs*n|%c_88h`E^hnH1M%# +gM#}ubPm2RMa$=Lm7u2)tvXY+J==T&Nt;+cB& +GTsf9{!*-lPoV#7SfT&B=MXp!3CrWJuxR!_$MBd|%!xO42eQS(1gO9KQH000080Q-4XQ)1|iV`u{a0N +4ot044wc0B~t=FJEbHbY*gGVQepLZ)9a`b1!CZa&2LBUt@1>baHQOE^v9RRZDN&HW0q=ub8qIEuvMCs +{j`Tf*S3mIpolTVGy)5>t#)m3Q2k682;}aQZG{8I6x2`7R%wxH{U#Yw59|9)JuI2vg?iYaBa<3)Su9o +@Ui#OXrW4=<8F5h%DwC>{)oCYw(3RmAnwVw8oX3)MredKS~fz-ugJCNFg2Chqb1A=zcd)}7rPIp>x6F +qwAR?&Zr3Q`99$dQ3ID-pZ;h&VKp$i5FH}8RI2sFN5;=qeX*!6$&L`QNK)^^511GpcklJ~n6t)$C>>? +WHP8shwhby>Y+VDH6g?(ZrFr(Gee7qgCTRf75)Y5ZaL`nD@s$;pF7L0ny;0-14#(8^tOJ4pl8dvSa$# +?blaQHnQ>&09iSAQVg<~E~4P!4^uPi+(Lm#tHU**=TAO7WNfxk?U_oYJJ?XG&?zBp81}TZ6g#4;vqYz +B6<6m!Lfnj`h?H7SW;{y>T8}hx2_NibX$IO)>hvyIx_3)Qasc1)a?2hWO=bWU`7b0$vN{aK)LV)Mmv! +df~mDqI%fF)fLRLi7^R77e7#IZrtzFWzBqkv+c@&o3oBzwB8xPD7FL&9g)Q!f_qJ#H9T!+vk70Zy;`O$c$cAm)fO8SD8Y`vx(q! +3t5olvt-U((+USM07vp|HtVhHp&NTUVlOiwQfwZ&3wLH+A@}q;B!cq6qKc{cWcGEBs3dHG`*Rsl%dyG +s0PD58gm4T_bD@_=xt&pTd?E4k8vbhJ>`ceFjIxVs*fwh~tP}h&q-lw(KGfBM_SLqJEh6a7V*4W4{J` +BVpld=mC*>zB^{R>c-buJOS9Qy0eFdS##|#r^=;JkwR;nDV!XjjhT~^Y+`|1Rw`rjsVd(x5WVv&M&1iZDY-|=-g~K{Rd1`6<-$1(2No&}{UnM1|4JeQ7LX$1B$LLU>{qYp-YW*gDE +=8r5M#YW{JD@D%jDJZQX^Wqv5_h4@3P|#XOX6K>1i87tAks>@9)3QWrz$L_)^A0VbWG7cpTVY_nQVQs +OIEPi&l{9C|n%?r)glBt5}X{?$w%e>h9n$kwt?u7` +TxJp_xevY%#MtI=k?j$Q*QlqPIP@6aWAK2mt$eR#RFqbX4mM003Dg000~ +S003}la4%nJZggdGZeeUMZEs{{Y;!McX>MySaCyC2ZFAhV5&nL^0;Q)8*`63njyuV)tE>Os-33U3yca89qDf>D1QrkWeHY4Cl~jh!>M}J_7J8K1fkMvbc{y)hW&5F$I) +A9RSu_JmG*7iEiu^9FxYA-YQ*y=3rUD|&eZT(K#aVLt`t0{WGPm86(@OVgn}7yZSq3Lv%Cp +t6Zne+;GI;M~RH5CgHVNB)BXA7L4hOu8Q3MPzqDX)cYnL%y<+X*i2@kXrcWiHh<$=i%f4|%z +NEud&uqNoxh2^mTVpGkb9eAbu9guv|}nwOb)h*Mb<0+d9x?7IH>?FFBU(Fm1AC^l`hCY}rPmd3&)DM7 +SJ5{*WqEzX)+|#S$6MR}_uL7i&7F=skpK +=-Q;!b3b69mDfsFcv6G@5Zn%9*hOmf53ncEQsn9D}{VV}7BThVvDIy}x`5i$_EyNpvBNP7*+TLnA~xv +DA>a!Ppxfyr*&{G^v3H`S2VhCHF$-DxpEkT)0$C-v?te&mx)2G1r>%k2?(40^nTZ)-%}Y?dBy5*y +QOr6b0B*vxQQb&~Z_*my4H?}H&SOC%>EQMj7;r92(JdYv!=nIex2N}x_PNR1$DOb{@KP}GTGei1#E!n +Db(4-yAh+gu0xS8-o*I5JIb&ua0JfI3N(J~c)borP$-;#K{<#UbYc!&}EfU_)?TG?d{6Tim>&d9ZP5ILH+qk@$y8H}B|z=6&dqUkSrAZaACeqR7VJZ$z5Ia|W2%#n +}aW`PHkKT`zLYR(!+mgg4`E(<8+h5;|&N9a&h68-R6J8-WxN5Hi3X+3@+8aowYm37x|H4#2AyFSm|9& +tF$n&{1^2wDMWY%4@x(3(I$jVQ=W5`& +95B+k6(ul&f*aj*OpDo@+1^t!pxq-?%7!thUX_yfd4^{+h*vLUU1TUzq$gID*)+Pwu0)?zB#CcMKtmN +IJ07JC~tKkXGBkq +B!%`x!Uh~O#II`$CqEM^y*i#LoS9HX#lXFP@0T$DobqS%1M?uBE&FnGcrIh=QljC~3r@xW0v;-3F%U! +IZMN_mN>IeUM8%GQ8at`RFlyLKXIAIyxwD=DMbxmkq4zu_l{COr^HZR)25A^abWdeI{GA{$Kh6FW*SF +oChlI+RyNI@gDbI0ajk-*a#hox1?ufQ8(Hg)p9`pts=GfP)?xqc-H_B=M57Qmn1@#RG&bv;p+aN5kjt +r9WxlXJO`W1h_dILM0|Q(x=sPTtKP!?t!Z-$cLi1jn7N5nNn5;QJY+QLMhGkEk&Nep|af<0yd3kTkR@@d!F$Eb_B$fzB(yr|p&@B(UozA&{aS@7wRg(S*PkI|>cAh! +M~+_I`&iFnxRNi-hrHU>mJBGDo67R>HFjPmx0d;x%`-!B12a~Avs#m$JNkKa18{|=lwD2v&&)SBb$CD +dZrM*%|%rs8|Zpg#Z+5gB}r6-JFJ489CH*5G>d9Zd-s-$B+*MDZ>1rCUlv)BBu!#(w5Yh$%IAinU;w! +~k3fzGTSNUi@*9+SW}2B6*n>H6#__9w53Xf`##_0`pl*HHhP`^rEQ=PQi2TEYGYY?Q^tC1ss|oL;C)) +{8&pPLj2Ydfo(w?v;g8l0L6usImzsI>Jj63R;LiuAiOuS3Wid~!9xP3%I>hDcqO1cZs+~CX=-pVdsU{ +Vd0o7S?V^1oJx*_v^b>bVzcB2%+;>Yw;+@O(q4Gtyjp|8!+m`XxF4hh@{<%uOBTX$0TUKGov(3$B#SS +CeX`Ins;q~_2E4g;V=;rwLfcr@>qmZ=$d*SmH#O*z_t@OvO)gj(7`&bLL0mXM~8&Bi0?l8YqQI|IB2o +G)$1PwnDF`u_s!534A#|Th?orEg5dG`DkrcA?||2)6_Llo?1qerJZbFXmiP7j)ywtjR7A=ZMcbm7H|p +~s}JDw|9SK)DUY+c`F5SFn~v-#Zl{kflK#7@e;k|X=j{U)rzZr>Isx|KorDP71MN{q({ +xWSKRFOI&s{bST-9q#Ki_+JqJBJ(obYG_3k_Ffhuf?vro2l_CB_A|$4#7Q({6S3|9)})^?{MLYve-&V +;+}6ow6QzBA)rKx|F~PH7%#Nd*7q`v8Uz>rwlFcEnM0INS#y-^^k4p}WO(7x1CBeuiA|7&WuoeI_c?v +PNFQq?H+vt$%W^vpm1&4q`i7kq!YzAQhVGrBu`zhb2dLP{#eP)>5^p@dqbclJ74#1QcYRANm=mRKhLH +3jb_Dea)KhX1h#z>OBQST?-8q_vn2b5;OndI8R9W#4F;WbL_CB^NIz5Fcb((a30(k7Fp?%aavawJL`l +T(Mr?YKLi%4e*(CDFErCKaJ?&P1qpnlg1kywd)Amjlc4hzM!PR-0lVk4}8<=7&jxowNgtR>!B9Ch7XSEFN)wH?_aUy5R5o8Cx(I(*2vEZ4S2)Aasa!wH +gaoo7N}53d1fZ1b21`p648&r`|V0~9-hNM5ZM~f)=3C#R=_e~PAMMX#KyIau*}nrf2XxlfEP|o-MLH(Z09OL-MVtFCwAI#%zgmNC +T>7fz(ZiI0r=ffsSPaBMVY76@LMYho9P_e{~T0Hp*-M3jXKc?@N_m4xIdLJ_%DVW@YA-I8DnP`?F+2X +A>-IMCY!__SwjtdqSpCcLH)CN%@qf7qLH^|E;YBP(NsE7jV;QC6t(BH1q%vQ!J-c3Rt*k{RU*SzmBCi +&mGryqmEyp2;0M@oVf6;NB+=mNmF)x)MLZkU`5g91O{2pVD3M}zE=+Kc;FU6*@8cP;=bN=p{rD&ic`!ma9;nao7FewHCG`f7lm&OE!XT3Ud +HU~0dj>jM`g=UnO0Jp=Ek6{dr+muP?@EoGVH^LUIPB%1Mc7KNUn++pW4X~HN*9a3_`t*uieC2Xj*{$Y%9W(^K +wA*Ch@g0KXvo0qO%y^0&ZY`uZ4NhkdZ^a>L}>(Xfw>C=S%hQU_z_;(DH7vIu%U$S)FI%KCs +6l`V9S~6WrQ6K@jY6D>yLui5Z&3PpUbKL2KUn#D4b15-e+`JwOJVFiow8cG0%_!^vc9leo2{Kgo8<}} +LFrv!JZmmrq0|x96z>GsbPlNicnlPKyt7jF?m+x4yA#|5{)gZeN){WYt;Y%JZye5)lAwA&i48q@_d^4 +!E)CpwY|lk?4%m8LUd(~NX0dAd;3#$6cmfY7c!JX>N37a&BR4FKusRWo&aVb7N>_ZDlTSd1X*dtJ^RTyz5siewP*! +`_NmUh4M&w@OY(=Qz^x4q;({!NJhK)c>lg@M=43Fi^enZ?#whzW4`4A@;(v+GG`l5$iXRyhmcq>MwE~ +fu=TVjZq57y!j2LZjF691j)6{)2f!nSh}%fl*MP}^cnSq;859a|55@S2L;{!?syKGad?6=m{Mao*>&2Mwy{Q!REIPu~ +DtS&I4PEdSIu{Zm5s2`~u%|sc!JX>N37a&BR4FKusRWo&aVb7f(2V`yJjHW2>4ze05=w@Wuln-U5KSDJRCkt!?e0#iY{V6FBm)huXxI4E;yUv2lHvR +*w@A{6aH?X*@qUJ2*rYVT~x!m+|ucKUv)d{{LfDpm3HPMX|0$Y-~LOz=7=?OW+g7;97}wEA79MwD{6R +T;QxgyyBqrS@EzTt2iruZ~x*mt>=Qp4k#kx3Y!sX~1D06)%;~T47^d+4w`p-ZCe1k%d&$p-4$V3ItXw +0!%t~(IUqWGF-;Bbs7(nrkmAjm1*HUyOzRb#dT&YdA?dPJV}zo|9>)>sC=zOke-D?LR}_;vWDJp065E +(XPGs7b*SMA3zqq)j(3XUk2Lk#2A`(z>3y;XR;gCz)+bw*MBvD8pWHOkKSak$848=ngtru!aqWgkU&gFKm +AC!dqeSco1Y@8f=$7+2+*RyQ+wD7K!H0B_J;w;C3d)6*(HNsVNtN9Llcz-1@X15A-m_s9Wj&WLFciFc +RROd7G%GT%~Y4g2-w+c)GLz^=5&BsQEmDt3E2&JZM*0uxw6N_zOQ)!2^5)|1l=G4Gh2XN6E^-Ph%IcA +^DPBCY3uWJvQIFY(9Yus8d-&kBipxVT_R5klpYM_`Xc$?t_TIR1K3hMr_je=#{HBc7z??u615UA`*zLAY8z)2U3{mX(_M3i#URF=$SFgK)>Cvl71Fdf_d>y(8hdCyQ5_ +|(i?^a>Bn5ubZ+b&nHmTnai^{jKTp@bz$O|uxW|Z~B`w~}D-f%cMw89wt64V8-GWz3Q^p#9H}m~Z*cD +YgPCh?5V!6HAV<0l$Q&7-Ekha$9J_M%PUSRnHNNn(?W0=fnX@drkWUMR*ml!@%*R;Qt@KQDa@n-d*!oC=1WWz +#XTlreSkv^&KUy@x1s^tJrXCn>iQ%yc=HEMY;?;pmi=a*YTp)rO$qX9#Oy~4ueI#PrNq#dYbMYjbG3o +XQO;~qJ2}AREyy2V^0Y@V4+QMkCe;aL0uQhvHBj4|om@;LL{;NV}x6M9$h_+}VqW2Or4O{P(!(gkLB@ +N@7Q@W#+IxWpy4Z`SjU|@&;d5!rCoYwm^x;b<&ghqKwn=4#!+c5Np9(A9X6kx)O{=(egJBtj}&6t}%) +fTs|*-dyo#Cn#h!?!2w=SVycs;o=aTz%C}f$onD*`n5uh$`l74dc>yj8YN1c(EU=LPir$fD!^2Psf`9 +?urFx?4<^!)`y~jRS~bYLGYE#CW_X?Yt%OBl8@09_YO&PN*>udG}}n?2Bc3Brbf}kaiS@9UAn8CQs{K +ap@E7j75k3O;G9#M3=ezeC#rT56S}gFkJeA5i~w>vesd#vwsa$DVod*j#UL^IeyS~eoQ-+!=ZQ40=e` +tfR(}CdO9KQH000080Q-4XQ;LG9=Fb8E0CNWb04D$d0B~t=FJEbHbY*gGVQepLZ)9a`b1!pcY-M9~X> +V>{aB^j4b1rasbyZ7m+b|5i`&SUzVJ*;lz(r~t_-FSp}|)4%EOCLJ%TsTi7j`kcL*rz4R=qeArMLKA>$+2pd;9AklX8E2s +G9Nf?Z<@Y)bgGk$j@aaG;)VhfsbaJg2TD1vWVA8aXk;!&j?hEd$f+ElmO^{%=L{4D83zVD4PdfS^$g7XyEUn(z=Qd`t_ +g`xSip{P8kM#iHw^_w18oa2VG#nZp#8b2Rf#f6W)hPz7*=P<{!&DPt?3M+&CkfZL{~>^lYK0FN*(AO9KQH000080Q-4XQ&9y+WxoUf01^)X03`qb +0B~t=FJEbHbY*gGVQepLZ)9a`b1!sZa%W|9UvPPJXm4&VaCyB~OK;;g5WeeIOr1k*Kue(6_F}*Va@Ya +|if+*6W&~PVqHH!4DUehQb+P}wGn68Ul9J7-I>a_L&yU|cBCFb153Y4dX-x&bt))JL?2iAC&ZqFX_R? +ssQg~=;C6Z3EmNm-UIt-!xvv;c|H!773g43);BbO#W=*3NTk7b|k(XowtvVnEIcH=R`aXW1LX7GUViS^*f59+FxEWHpM)u;ptNok*&4p+3^F2mBsk +1;5dW1+Nc0qL9vcN$E8P2hffNWu}AUogF+Jnla} +e%Z0e*uw?uo*=yNX8}s+v%kPkDcq)X%OBx-5t=&4vqU@=DRumOgCN1Y9InBe$oaC7vbz1z#cg!EgR;? +i$d8k;MHF-H-}Q0)Utvtk=LY`v23bxl3|()2?SWGP)hkjTi86P?qOQZ%dnZ=(>?9dmLAmgWsmB!`*uO +BjMwmfGDED0h>r-nx6Wk>$y%DuHY{ULBQg16>Z}AG#xrgKa_Hj4S4ge7cQJFsC7cnhRBtD3R$BuCryT +?pq8teGuHKFg`)6GE3rXwZN0efhm4vyu1h>zYN!sx6ra^8nFa9 +|yk$@sZ9c-m(5bCV~5|FLqs@`XmC^u9rpsaspt8YC_q!$c_+Ehh0*5X?EaHBp2ZFc8mSO??SZn4O{11 +ZX*j5);)(!=wt+S1%FS0y_}k%YNcji +*Z4>arv$UrtqBwyBud+WLFKsZm8+kY|df?zd+I1%W)kR5699M|@PxtWi?Jp3PaH^n-$e*aa3Pn?`kVR +O7`I*!H%En84ph6=hT1h$p!kU0Hqo9Qtd#Dwqz{MoTd0H8qZ{2~VwLn_uck4_as5}v +o4wQ>d-YqOo3a;B>>pNsMP^CbodR#DS|tE^(_spm`HklC#X?!N1b|-&qRpoiFsfQt$LE=Si%v`p?rFq +lBJ-_F*c!JX>N37a&BR4FKusRWo&aVcW7m0Y%Xwl#aC;K+eQ@q?q4w|g~+W +fC#8X6y1S%w1A)>m+mL?P-H5StZBHX>#LU>cPMZJTb7!oVt!&y-s9`~x` +M75KC2%kYz3^qjxGNNV1Hq6;2DcCb5%7>8%x&G_cZ3J{*;DLwJx*tmTfxhGePARWgdAXrwlkHcl8CEs +fp?x33@)htr=_<|g8`BO@0DO~lX2x=~Q9RF$MVDsyd;N@nlnid{OnvbFIWx$5ew5^hRaB=nF`<&-WP& +DZ5l7ApUpBYcGb8SiK`$_k0V{ONoo@B`kL9Ud6IKleu|AEk!G8G=bNy{YgXdpZ9MwBN-SS3j`x9hvj` +`8@gNVm#$HK25&97>^sHIQD|YRu%dAZGQ7^ddKn$e_HLmL2WG;s(q;n>HJ&c>NdGw7k}O+IVhZ}a;e8 +(0nwzzN6FjE>mPsp?J}H=LuffIMGEeeOVij=v7GqO8TedSXU1ZBE`HG^%^OclOZ_}IQdtzq5W{okt@K +q>yA%pJ394eLO9L8g +dFy+3$i|Ctv8@W_;^-s|=#gPV2p%!xZu-oHkn{~ZyuL(MxVkw)aJo>X3}Yuux;3zHF`QOP>c`lb_}Hs +V%(M7mBK8yUVeGr(0ljiU3k&v*zz57nA6i`wrg8kKZSxhm6sqVOM|s-d@LliQoTu*%dj780>(}JACbY +5V=cyGQzT~B0F`JNXv8oN0Wxtl>;_L~FxhRz^BWn-=^;gNA0`y5%>!JJcI +Uxk{C^I1q3;R!yqB=1FwYLVtBI+;zn96zq0H6_Q=O-mw^}KzrZ((=6GSkwq^*%x2$CM;rsCzM{DoK`5 +`W(x8Tp;tf#vrw3);EH7>$o3M1{*I{8ZKyKkM8)$AXrd15>Pib$KPe`}Re+3?lgbW_+nU+F@WvSbgw+@g155R(xoxl%*AUS#=~R>Bz}q?0`J|(>c_lx(tBK9TpV7>XZZ^ +y=2H;_M-{h(dCGC7O`L2I_j>rG0R2JgG>nus%I)X=%Z6plr@IRq_X}lO8`oqV(anG#E8fQI$vFBa#Vf +X!cSxhw_1cXPiL3ZaZo(K$=rL1=p!l=Va0G^?O$41%dBd|&@nyIcsQen~fN4A4Cr@MVWzvQSd~tFG%b +iwv+o2^ot)kKbHDW7C8f-$G!mAX8G%IRCKI+nc+bhX{hkg~vnx?}_;1uekL?fe4);#uFAaDwrm`5?h< +Nw}refxg+H+aAMyG__NY@D910^T5<4y^xkXxG3(>td+EKm!loP7=W@f5f9l;2;dR4sD@F{_{c^ppm~a +`^+TD;w%0SjadbYEXCaCuWwQgY7ED@n}ED^@5dElSO)RLDy$DbFv;)&+7BOHxx5N=q_xGD|X3i}kpal$5 +vtP)h>@6aWAK2mt$eR#U4zE>cPj005pZ0012T003}la4%nJZggdGZeeUMZe?_LZ*prdVRdw9E^v9RT5 +XTpHWL2sU%^vwsNGZ46u3{u?E$&8X@mCKUYnvHf`uYWw5?lNR7GkXqv(IX8NLoFd3T$hfoyDv91iF8n +IW?*`;M!#81LB2Nga8w+PYG$=-Jo28~7+!t5>2|RohF}_KJ`Ds^)C{@2OMm7vcoZ|5eGh#rIN)e%d*v~Jq==1Y#O_ ++-4#PMO1|2U;>6TtK%(~j%l?wHFWOq=?Aunt-|q+9P1WV>?KE_JWhM>nP?Yz)Z^T&mg(}#Drvu+%XFO ++LzkU0I-{S+bPq0zkkeUCZd7Q%+If&jdyEt=FZJ2oAsss^?b&2dycD49nCuV%niNk?x9OiusPxyF!#; +KLH8@^sK_*C*PcVA%iFsbl!08N4-8uMi2Cvnd;PbDr;f+EZtIdrWuFM#h()a3nUiJALa7WZITpcYj7C +^{~ +%`p2O=zS-8&e$_FEWTy!p%){62f~1FCs_5?k@!;ohptjd2G0x~!+CM8#vaHSG2EsVcgN$vcJkK_p@LxYk0RYPd0y))77fsdu>v7lGf82bhGu2fs(t;*(6arC!W06u;>2_Pn?2o&0f +JVf8=T!jSnl-1Jh0f{?00PXu;CaW0|%B%R+(fjI$4&*j1fZjMf#nztB$3fC7HCZ?|3|qgfEQzS0l9qG +vz!BCz)mZ$M^_i};0~V|Dvdsj=NniW3WQA9Rc`zLPwDZ4IE|yXmmbzT!%yF^@_^iwaeao8Cl(aEkE&! +YJx4C@Y=-($jOm!ccT5qL?)zKO9&%GP(KbJbtw>KFHgyy@$+&1ji1=Z@e{W62Yh{|tX18?d$yFS*Dk$n%;qpkln|jIke6Xwl6QNM5$J3{HE!8X2y~& +h;{>v6`-XqOFfU2Sz?`e&#;s-9#4QXWu{k2`!jp!<{(C$mtO8@gkA#j@BXEML-{b-xt6<`9zxxT#*e` +%`bihJ8Ky{g=L0_ONj#(=5Rs=P7Ublo!bzikzwd+6@XU@)T2SIDUhuJ_zR`(p&I2G*8J}g$%Xa+JTRM +ZhTxUUc@q2+Le(={X_JizaZVGZ1rz1$;@@QjeLU_ysq3*xxU4WX**V7xqvscZBvGVlF+WR(OM`TjlKf +aFCiYdW-4qQrs307z=~CJ;;vz1S;pZR|@YBhXl!Y0!p(wZw@=j4dD^h~}CU`}xOj*{M?X5jBMGtt>#H +@Tv$=>s3`B;UR>b<^ulTq9`s9Uwb}kyhofEeKHZsb*S@%EMQgv60xVnsdO$Hf@!4lJ*)y9s%+aNNcbZI*<6qXT +JTk-@$OMjebZc%V1gtt#BqKxUty}5aB~k%9Y +(%yKY-OPf!+1ADMql$c35YhWgGT42LGk02gVgrRil#U7NV>nxVfH)uA>_7E!nh*XFT;Xw!&{2vg8~u^$_8I#LtA|;h+Ga&Sf_GhK)IIL#^lx5@&%3{ecwxn|O!UOl8?V!on?B?8Elp7n~undn4#?2HhgM7h8i@9`W#lBeuQB%+-&^kfwoI-ix^k7(Vl6;%F61?^D_x)B(_1IT5 +LSOU@@9lfMW75%(gAVi>7XnS*5#G~)HGB6eu0$ +XqCfd`H`GP<<1q448RsbtkjAKKsO#nY;fZFxwBdVB>0|uV@T6Z;YH&%}oYg#N>Va9UVu?)#0^i@8bUE +>f&V#ix>Q4r>Vt9d^6dwUd61w@eo<*N(7*+&avnz6|f4W;TYV2ol+>Ol{0Tum3~eId*n&rWTb3QmE=07*PZ{w`ww{F<@`!V_P3XFX2ebsHmYp7Pq+6N6(z?CKdBjixZD~l2K${(_F3d +H1I|@gU>b;vAZ%WbY)`P(1QZcPIZ3JJ2U$>34(8w?$8Jj7~~G)1*WN?Yx$^X7`bgc&y6}y$a-W?9njvJRxKuk!$5>I_Nl2)xS<{5-H-Pmu(zj#zMpo3&9ia59x@GiM5}(H +g||y(V+Dog4QYIm@VLgV^TCPgoSjCnEobb`|Oo=E~MFsq&KDGk46H&(ZKzCHpdt5*vGEY5R*% +k8a&@tVS31`6Oo@U899m~62nWSz^fr@ABDKXodcY2Ak_c;#pgiN9G=J99r>Y^3TiZ*vCi +BefU{Z&W_}6tH#6gb>E;=Prm8k+9;J(?G>HGS=6NMAz_4#|FonnT!M@CSK?y^{kyHY1jiWEZ+J_Kru6 +u6;*OJiXkLcI?>}Lc3$m(xqq2ISGi<&69W4d@kM>>tU{i_>&A@+ny^ohj_)^9jTU;ltFN!uVi|NMb=f +B=LJtitmJeE(z-{zZc_~{*1MMHc-u}o672ma +EVPpn$5krtE}wmO3nIwbyX*pgvp&1sAS{7jAwmWZ@i9^kTYpt0XPiKoyP{6&*gqHoKZ+sjnCPgQ3VZw)fHmL{*`9{ZS4k!^&-w6Y5#fmRaTfH=CEE)2H<>Vk>o04)w=`4yXX2wdCZPp9F@ +br&cw&*1@N3qN!7^!F-H;=J__gKhCO%h)Ht|CuW&_oMp+^q9^hxAR;txrPGvx<7SU&q-@_$R#h-N9G8 +$}haLlPYbAOJKSrz_mkw!?k`Gw``RIl5jtPNhfZcC&vXmnRT3+ScPpG}c`>7mjvHukaA2^;l%m?DVl% +xo}SQkceUhEw)go(x5())5+d6?3F#U>bR@_e9Hj_^xSkxd@2yrh`Np<7Qt#GpDY!rYm1)LLE5S8qVdD +AIv19M5*J;^u&8O=ve!+FU`C3NuHrCL(Bw&hvoTS}5~A7jQLKemVM@^_VCxzCzGrfR;!eu_)HxROU_J +4%G!${V+OQ2aLdSI^*czK*w%#KbDhxl^&&zs|nHtpez1RtlKt@8#dI(;zu;D_dCU*voyfsCHYOY^+NR +jVja!M~*B!xg9be*abBQ{dg$I`}>ta%ntBsU1BL%!b3rWGIx2v?V)8U{054s%%SMhA#M6#Hb15XT|`Hw)SRp_^Hn83ZQ!E?*uMxo8lM2iFu|Xod&xnCHh$5L&9`|Fh&| +3jBokvPfvQdq56fTxI}3Xggdzzt`@ArPmRC&()cB*6jYp16VbflQ|r_x$Zb0mHZ4<6R;Co_y?H6ic!JX>N37a&BR4FK%UYcW-iQFJob2Xk{*NdA(IlZ`3dlz2{eq!hr-*-? +*$)#D}Qh0*LljRgsfS8n=#ZY-gAK_lzBHlD1Uh0=eYFGjHb2dv@|X|9o}z9a`QF2x2r6M^v7Bp%u&WJ +kK)vsI3R>vfklS~=V{Cy1M+hS}tWbgU;(cXS@dQxfm)cuOOz{b=g +2T!BR+>D%)tqj2phAC;Rd)}63fmUY;JaCJzmBb&meo_0%jC7iv5Mr*mtfG&%EvI=gIMDuouuOMGh@(M +1vq*N;L?vY%fq??SPM2FoRU!%3!x+0c3iFf@~E4-PDk}-tPVMe!Yl`HlA>S24%`KBRJ@Vcu~XrA_x3n<60V~_*~K^f)uQq6?ebF~utpe8rLwG*bcajc< +6@3tF;}^JP~klUF`=R5F0nccq8&&daD)I}!{iWfAE*YtfxhRKlmJo_TbkJsMt+QNN_%K9O-c|U&=w9> +_&{#u79WBnHF-w(x-J~bzFGly;Jl5x1UXiQL=5e=Ftr(KhA8qvWVmfBh?05=AuL~z$P8$Uk}VyS>k{? +CR*>@*aML*PPcAat52=5}M_gN{``Y;nxQOS<)14f5+l<8)!C2j^3L2}Y+ra~EEDd_H8?;&Ep2pko_Wr +-qI&iA@_=I~L}}?zB+F!$DW@Dgu`4lxfImviG?0#OZjwZjK(^&2|I$f%=KKj};<>w +ey3Zz-P~c6>y2w22~Mo$Yr+4%dm<2hjAO0J)OP0D>h8r6DAexxtz!@k;EUo{!hrP=Wub7f}Vq{kzJWu +egOUC?4LuQ89~p&e#auRdjyw$8AZ=R#VS(V42k|yNSS|7{69>F#$j>P`@}B$4Nyx11QY-O00;p4c~(; +|#ZZvi1^@tn7XSbu0001RX>c!JX>N37a&BR4FK%UYcW-iQFJy0bZftL1WG--d-B@jJ+cpsXu3y2aC?I +XBkOKW=zy-E6U4j;A(6}40AP{JYwzVqW^t&q)1V=5;xgCO+XQu;vMhhx#y0J21rDTM#N*NU(dXXq}PSrmb0IcelLFi(A%ILgteFwP8xDd@U+gE2rZ4)A{V=d{!KvemVb8T%P@Ll9A6$wW3i2 +ExduV;DEfWdF~u5=2Uu3E&>j +a2u+rtDC|8^2MvbX7s`52grPBur)>IdY|IJWCMN3`Kjb2%YHWHws+L~*2ucHXz$0V6L9Og@=Kbp0qC> +pwEMUh5PRIvpq;8U^Ex-Gm}ct(Q1CLTH|dG*$V@(RqnoCAGiwN%iGhp#9fE#B +l$Kd!ymUo)HQ#EcoS#C|y0G>`SRV@-1QsRu!kmcUs6|PpQv>Q8=>&ivBweD>Ys#D6Kgc5H48_z+++kL +2q-w=Rcfo6c)Fd_crEzYdnDk$>5S>Y=EA5XM)Si +vh&PftTU?GzDi$YE``zrEM#aUoTIuek9u%j(hu_x;@VBEeU4 +K(kSa>syCu=G&hNy387$eP|y2S`h-F2`$%He3T7i;jE>tURQ?#J!lJHzf~$t!3EE@$Y3POf>Y4*a^hs +7O9FDzNX~3W3_i2;-WZmBe9g150rI&36-~0uktxnLpz+sz+C1fela&HsjIqk2>w+2Ef-J^-k-5I2NsbI`+}#ij(C=S)LKCrg-?l_j)&gmvuZCKihK&Nk4T#s;&_}-IUj(Ha^3pg6 +pD4`d-iKMHc+p4}kwTG9%-%i3(2PlY!E_aMvd9^Un8B9~MXOGYTv&q3Xe6V`wN>(4d+nIl@(B+de$xx0CsVn)KIyu&jPNprfH0gM@wy|9Pk`5k13*}1 +eo-R(er9ql`=qs=@4?@rCV*`YE|1HVvRuKs6SFtpetSxx%8I?}`S4nOqf`E8ahESKys$Mpg3P($)ikF +W;Z12jI1+#NUqNUeR^F3^WFLL&#pTlU8eF$ +2(i98S2n1T9Y&No_BdI3NV*mG>8B!7@%W2w&R+i@b<2OS#2!i+g`M|zhe`M7+q1d~-yN^K-%w|c#iy} +$bimd~DdCSYKxE23vgf^c`-PCoZjL3qB1)0<)$yK#a9(O_%Nu!G3em285Y$LTu4U>B$cfXQCqNVx3_aeI!_=Bs%TlnJW4z(<|E!9PNh}avMg5ql<6gQQM* +n6-%VmGysRmqEp-8FT~{VWGc{>`&5_D#g#fwWk{TGny$O_W)s;+m%qd<$CR#>hfP~9%3!D29j3(M_GTeZ(d>=MC0An#ha_!PZ3ibDj>-#m1x6Fqv4HnElG +Dx`JiXBSz2(d*$V|2UOlCP5;$MZ82qV$0T?rpvx&kCG5kvgzD5jwx+mw<(sO6d7QY`w&uW1FxD}>RCF +9Hw8>=>7vDj_(?F8FO$Sxz@?DOsWct-4ZJE>U%D#$yh +&5j|yK*!twx}ca4qTU63&bVgJxeY7+`H8&+qm|&nrqT8M{;{^lF +t;AIA&QGi->%LcK6_t!V9R+XvJs)##oo2`}KCgF1)W7$Mt>Kx?*vOo>VZCNSdslCXd>|4lU4zrkel7K +NJmZW$L5H=2-RDJ_w$jDE&IWX*=5>wtArEIxPcWd})d-(`usMhE6u8T&Q=!zM`x+vsBS1Eg5*CNA$;a +vv$@$lL*F8H$?6f%nWS=#`W#wP4>C*dndqJq*^E0k$&pIp-8(`8Chx@yGup(-sDnCl?syXBiTiY7%Vj +l}TrKKe8Zi3ac_NsuH-!Y(md6!~|61QLT_mS{-J5|azXbqwnykuc{&Rf@hLc2L^D;N-^m({>O(38hyK +P3L{Bj8HX1%KkGHf&dW4m$4;CQW!aGwPL@Xoedm|6WzLCf4bJi$KI#jItqmE?nqi8$}IrG3WGpa#mcbHwKwFt|BSK&0SQ_phOB()bFy$V1ocj?8Z6zmZWX^)k!3Z4?q{x!_?!? +fiVQ`V+Tv|}wXQ`;r$#qkyl8VfsRfX%_!w{$1chXl44hzx!D7hqfsDL-^&j4KAkhw2ULwz +mn&{iYUnrT|sc_a`qN`2nDI6SwH@0aKth!6|KoL?^X<rKqn4i=~~G7VLXXo +2n`Xs@%*5v;<`e0V{(!%^^*IjV%J%dDnS;r~zE1y_+KV=K`t=rvfZXQj{r +F9rPDT!U$F=QH}Nc%B#b~~Q)`3q%a%Z7vvO6kYi(y!i@`-dA6(ZE%3mFHR*s$tr~puXCIJ|lzRd +DyHw0$czp!QnhpX-Vt%uzXh$v#oq +VC{FNgs2z1<4s}aP}?Ai>0Q+n85Z&H;Lkrj0q0A0*(e1=wb!{I9#{3H*pf*;)2~Z*1%CEAEk~mp=(R( +~GTj!(6+vmR5olxY)dp36R~O?n-1f0fM}B+<&TLnCdi<53g|zm}-wt=g>=SlS-_cTOW8$WjJ=|HCDlM +RR`M;hl-sC8} +7?F@1YIl(9)sDB0zOWu44FPOBcHBVsX8~vSlnm$5L4{#7R>K1Z%&Fd5*ct5ngCLv@ZlngPC +8Z_ySl1=KU*MTrRR!sU!|8&kPYj6m{sB+6-d#X@3E}>Y+^kyRHs+(iUa0LWGicA|^ytpu@Z2Y?gH^h! +UlSdU@M(mFVd>}*G3iEm->p^R7)|4AkcOUupB>so~js$tzlLs +7D*7V~Zjw@EhdU@_$^H>QbeHF`W3Q3`ySxM6x#xbR-d1}0+Uxz{!Q#5vh7(*yNQJ4U^exQ|o7nHG(P1-3X0FzY)bj +EZ&T|1VzXx#cXJPZZ~B@SlBQAESve+D+`pnd%I(7}9^SP~lKwV%B!Zz3My~4wO@K+-HuDZc~qG``u^4 +|JEbgF6<66_%E)o{XMaUOwUPgJ;lvjgv2qQ{U1Wb#B)VeO?%7I(TP5hvWGMofKFqx<=0T*%`t1PT5m +1#GmissucPSX-7xq;txk-P6%v0eie_j9YZvfA%tGq-5I?XtL973J3b)*ITd`Bg7>Ws!Kh=za6GF0)K6 +!(Ciz>Bb)8l4zhOtFqV%>`4T%&(8DxT)a|AH5Zqa#OaH2F5W7Q-#^DYnmHF&JEBRP&BZklD^Eb$n|hb +w&&M23CI&Fb8%7T@Pm9t)x5U+Ri`O@UnPCzq8%Y;c7MWXiL1PzY>^zNkKy0|s!46|HL%Y>K##-7{bMb +FCH}Ag;VheW38UXK03Hr(OgLym`AEe$DIS6_fg09LsscPJPdk8Hru_)K7QV)`7JX~+d5`qA^Oo+_8!l +Oeo`W~Z)SIehF@*Ops)5Zy~u}ApTa6o%eZe9dXB?(XzdEiwJa^b>5a44u-#cujqR#7Tfah+9>%pX))< +Xf3nqf42jVW#qXxB4xbD96u|PxnOy>zYYV!vW7zlL{&sUdo!7kYLcZ`I2{$@qTlk^G>Ys5OloA47(8CDY?gp{sD>)K<7s8IIw8P6 +Ne|0_gX+lIx<}FUHv&C{M0mUwr)VI=cFB`P1dQ>oZnuxBfxyaFW}Jz`r8_wOSQP0Yyg`(78cb5X(Nki +SL0dQ}<$nPx(ya_Fy}Tl`#8@f?;Ygy_WJ!Y^rK^c5<>*RhaEhp8uoVU;q2XjG;{fdZ=-T;4AYno7)Hc +8}}GzgvHpy=P$ksPwBst@5dgWojpH!{-XZ~RFrPJcVKG!;0RIysnf8KkMUmHgM@Ywo^??)cX_jsS_A4U=s_Cc+b!#yZ@kV^@EA=hY7HP4mt?pqxz(W%Z(uYe#I*qe-T{TZNf1?tSdD#2X5B +QvAv9E;D&mF|pnx0%Jj;yc1jA{OfRez3>7zJ~BjEgPNxK>`4mOhV_wtD({0$B&n9Uq$?R`6_T6_jj4!LrVN9-qZ8 +l?iaZyKjcD{3AFenzQLO4joQt{PpZn|rJ%uU4D1ZA+>~(DG)4`~i^~Su>!)3&5)f?c)(o2m?=Rm+m)8 +-mBBcQnhx;h*b(E=l86|2u6UYCG%PF!mvjPqw{rw_9jYze(DAprDF_DXEO_FDU+Ch^aTf=CV0qS-;ir&S&HStg0cIjh17(W< +697g4dnxIFL|eiePIfu;?t+9G$LnCSQ^^I`F={8lR}je!`cnM-&pAg{sRm +#wYHMF=Fv{na1MjOg!B_znc%9|DkqMdmo+x!&j9Cck_{R&AKpVli@dxX8!w;1Xv$O#?FLFFnM3b3inR +)yK?Q*nRiH-h3JdFuyY+<0L+l)d2rWUx|#ZAhD+n}QRTsmUA|_?`{$Rn?b=Lxo0&7 +)&iw!#k(^+?)P|L~&_Hg=n3c!E*JZoil2x17_>ejyi-LB|v(1024fYWW@UiNpm4U%2m1=7rHfY`X)U+ +dB!lo>#rfIpKS++qhmT4#~3aqv65djI;}_4V86m)9SzFR$L2WjJTOtKcSTmLW_%kTIFgLVn^28Yt7O4 +rUD}^&naop8~i(1-z*pWMN%;P@GQkG$dyj!^e$Ez@-$1Ct%Xi%@E12j6<65;c3Mhy6-n$fe79L{!Of6 +l_5~k2s-mB+gr;F54nL;mHVz}F^s6Ft6fc$Ytx(|`@#0fP@CLZd?5}E0;DZ8c&;oL9-Ql&S63fz=Au$ +t322Mc@Z|wDVlrw79&BQ*=N-hJiISO?-N&G(e->W>EDf&0w>d3gDRMQQHF^I-qAESM>bK=HGxK@)@#*XK2YG5@%``UX(>TX3Ej+$LrT0qVu0 +9yyCB~&Tr0JCDX3{C+ek{#CSEoCVsFNeB!xH&~$f8*5mH;$za^F%-v`4iE1RDgU~Skw6p7iK3Y>xGZ* +v`ruBBGr~DKu5)4X(3%!^Ib54K-GfE$oRdg@+dOcwc4MVI?OF?WOA;Sb +J~fCEQedMi`h-%j%@<<`J|-wmfO;hx2m&7o{L3wU662$VK*Ltm#3&4D$zx_(`jWcW0Vbb%Z>#mxyqXEt1xJ0@+TitxvMCH3W +%D3-s=q?-32Kts5s2~zKJ+scS-2NabF;Z8(!5`b$esN +tG|Mjmc&5AH8?y9g@Sw-b1UaJz@+-N-nl(&1vPTsL$zJ`G0bUvOdYI=%q{aK(Kzhrvx}#ao%Ls}0sPG +|=jgLyO+Vx;p;Zk`rqjWrx#0_uQI87fsG>Loq1Mav98qbCayllEuj^Gy}e&XiOpYlE&u +VF$`-VpqKJO0`q(yQhvxiA6@`9n#8FNImNnPTQwY1ZP~V`l15;FKJFs`*qK&>m5a8p??6vbU`0n9ADu +7xy5E-ECuhpppV&rRwEVi>B!js)6Sz^+buF}%Jy~ubmIMnHd*DBvu!PJM-gShqv#MJzlzH>%q8_!_(D +>$5d}m5JoSh~_vUSB?RVD(+tO%6B|!jjjTmmSpY~O7c%P_5>|1QEdk!r%WBb+pV-MkxYj7!Z`gcYS*S +3$7sn$16yWFa_^@IaPt_IRmE?;!?49WX-IJ%j;|Ay#H-Fbtyr`?XkJLoaJixU$r#FdGzrJ&%8)Amtd; +vAKO?pm6{oP3}rh8D`M09PzC$xT5SEP*xv2m9!0ySTmc@jtIBu1nS> +1xKYgrhd%=ML*jaD_0>nmKhCl()5|yS3k$|kl7M9Tiw!u?`Wd>^}USVI}jIY^l)wStI5G3 +QWRRtmwTMDM&+b!p4E#?qdC0rVA8ph(h2Wt~Q$gnfUP!|?f-`zVGFHgU*9v=5KPm!(1X@?5Q%j`{0;SA)-VDo@j3^skMTb2?1~3q5Lw1q4;aG077swy3Oq)BELEiOiAm!Vv-l +cLEEh?UDXm2{GgH^OVKXaBwED@Lf*w`_~K=Y-fU0`^91G!V(0t^zb}qb*rtVnwJ;?xRLUwL6qgm(fk| +t7kZhwRTP{MjVFIc?E7M;=w6Xm!;^T+6o@8^tNAt8+5F2VCx5o`!DZJq{ +S!q4a||ad3$X*!5E^c-)N*~Eo#b=C$c?-&>myn={A0ftWZ1Q?&!H+N!Ep?0?bp|bpd$GK~^ZMXO+8IX|0Jt#x$TbN6+>r;AaEZq2|Fd +NY1%_@#s1i_^_m6D3^yrFJXGgd=h6DN^`bOS{nH3;fQ5Jm2`V~4gD*@@2tX1r)skZfL7|Mqe9fmI!;} +2?qA=SqdW+g7|^2AH8|z5*)X&kx%)%4D1#8JX5@QHBfRy0iR3U~AJFiNtpBGE+?TIbXm}Z`Qrj5^ArI +y~TxJKSo7sKv9ie=kGfz?*94QcTPDa7m2>{ni5zahtq(zKG<~=ufNQ^j5JM@^+$DEZYC9@;jb7}J(O^ +{LVNK~WNmEvG}E^_%8`7VCr+5b7Gy}$gOd^8aQQ-mbVY-}9pj)&Men)4RPF+Sk9JBKlB_X7`f5opglA +X7KEVse`R6so3%xjqmw4G7y!!2X5!#_V|6_S$(}e6hKHHb9iZCksw}jnL>6iYmjV^zX#$D-5j%;MB3`?oW5_Jj||XG38fnH9#n9n}FEu(M +IM)%DWG+O(bELuYU0g7sD~6JLwKXu3msv-R%AwmrPlp8!4Ub4PQ=+vj&9)EPPHUq9_xFf>?)QT%7d^+ +^28&NrOKG>OZUo3)LL$Nn=3gy|(eaFQ<&@9Yc2Yjl^w&&b*y%QbE4)4f;IZ98wNyH_$xjmOjNRv%a!( +~bV;Iod!rUEyXuzH&PSgVCn=%B4h)ulYc_dmL+fs4Jbz;ruQ~uPN5t$SaBCZdEQ#XEHTFmzimTj$M~L +hiB#`D4D>JD82Pk!=>h8f&V5GRdD#rl)4!l7GBxGU>UUy{C2>c+C4SOqszZ$ztFJheC$#4F2LABg6YL +yzQaM)e1{O*Rg-SgQo5$?=IJ&jKf4nny<*QgVg5Gm`mWFlY_#V9kGy|MO1PeM2kOj^GLJaNg_xmEHnb +pEtLkgh($BB95JueMAByQ^D8sq9qG6Ls74e$Vd4Al-jpo!0Z>SX>q(7Awx+7s}dI5s3vHA1a?eX(Y-m +Akklpiw7O_{#)ZiQIxxfpJOx9k1bob6bH+T!kH3?TAhzu477KHtKq}Y-|UtDz#4y8+Rb}Epq=p{^N-F)M7a+;gcW*+5P*V2m)Dz0tV4-^*V^LjFVuFy_1Uo=nWo8}a&*vH739H$!@}+ +*xW(QK**(e89D!w09j1GOS;F44m6zt-Asr4D<7S4|lm2;>8GTNd<;@BCZIS8ByH|hsAP8@+z3ai$k;W +wY*KmM8IE)<)g?^QW&V3RS3Lb5JRp{QE9RdS^?R&iRx)`DjWzq@m&A%}sGZR|p&(8b8v3h>Rg}L3Xwq +}FX*VJ~$;kEik#}mI|q?gJUMLT`$YWmYWf4l1>{aFF#Oq@xdt&6IXxu76pPN&T~tZh$iGBR~p`sES>s +H>xGc1UlEI!j%BnBv>cDt~-(7^li6wsr--QfQHrt%)GHw)>ln{oEJj6NyT3!fHZQrWwO9rt>7sO~+-j +;Zzzg+CY{bq;FUdn_#7Tfzjq3&Fg12<=Dwk*-*yL?=nbm(>3Xg2)Rg=_!sRiHg4jt#-^C4lTO)XkAM? +@i~;9;(~!u>Wb<|*Yx)qYj1wUbDz*t<9HIxUlu5DOBIptOqGmV2%q0CL;}q@#HNHJ6JfI1bd*@EiWB` +d#a3mwVO<9MoA>Th@9T(P~b>IC24W5O)D8YB=zWiV|=uR-ff8*fVq`dWO%_~nfyU-N6{W@;9N_wEd=} +4UO>9nl))uUM0)?ZgxCNCl$RMrgOPLs5q@8S>pv*TXAQhG;XY1C&@QU0`FA+%?AB~*84b6h^DJ)~bw{ +ZCL!0|XQR000O8`*~JV(~nj$y9EFM+YbN$9smFUaA|NaUukZ1WpZv|Y%gwQba!uZYcF+lX>4;YaCxOy ++iu%N5PjEI43r1kfFvk(d=ugVsqNNA0k?=96lf3#OL8b}MRM8Qr4=RczjtPrcgb;+>cN(|oU>={$FWu +^*iA081;{j4+QO9#?FeoyYGIWg3}SvIm-%G=h*_lcMB_gN8fzx0iZC&i-R19h57&1O^UIqLcfZZ=Z~w +X;!Fx2h$_%zqWJb`LNGA9>#N0|cuORNwu9aS0sw~5hPp-9Q3a{W{i4dzKQEO%zsx-yS8W33oHxCbgz! +F7-+Q1q$g3@v!>4szjB8o7AU_%gBiXKI9Z;Lp#ATwA<>yYTz;bC}ua`@)(T%5cPg45I2r-##{b9{3)J +v%*$gTtfqC^`$LN0Y%TLi!V-FbMBL=0Cp9LKP9K8OVr8NxLC7#3H~@@M~4LAi`J_34O7OK!md{y~#Bi +Lqv5MFEb&JrGY@BSQ#cO;8AFa1g>IOi;W?O2y@B^L#PavY7Lg6WKch%1~Lv+7RyDUF(TS9hI^G(#}p` +;B#Cl8LGyUag`eOlzoI-@`+Q-iCvNL9zi0vOTekjRaxy6rj(QH0?iX?GPt>ZcXj>#e&)vGM`QH}=*K6 +xfUm>eAQ;YISY=}PG#Cu1@j(k|a85ku#tK`QeQxzxJs=NY{1eyd1@AP$#CeNlS| +Ubj3BZQ9Ry8dVHDL-eUtGyZ4g +N~@pS-0}bsPK1EwqP$Od98(V?qhI&RZ?01bu^|ndYw9&LI0Hf$xNx5ZP>b7tD@q*mk0`1Gs7o_LTz;8* +>N{Q|$PIy6;cHZvU_Vp?^VIxCCVO1)~ara-^x_>bVR_uyZ8k1CQX={QQLdr_*+A-fXv6|s_&~1l}MEa +-DO+mj5y+n9tNz4R=?aKoiz@)t*p}jC2Cz6t(g1>3`G$g25DK`(gz}fb!Eu}#{lsR+z%@jW9SkTDexV +hsl7JuBNdDfeM0Z>Z=1QY-O00;p4c~(;`uX|s*0ssL21^@sb0001RX>c!JX>N37a&BR4FK%UYcW-iQF +L-Tia&TiVaCv=|-*3|}5Xay1S6G%NrBY_zE0vdNV+fckkp~b$o|{}-gJTEV*;*m~ch1i5rqG8iIp2MK +{(P@4rSw!OTNg_1SZ;-OwXvSas#Z{e_QFe}6G~~4U@R;tb2vzS=wBPFLTN3mgFeYd=R)saqWs`gI@@k^aMgB%#%-;6ktGm8p07Cld|@?tks_pyl%AuI+5JG=tQSqp~GC +z$&%2e0^4#MqMaV!nevRN&K}&j_yTrk+y%mHWEj6--{@@gXhs-g-$%4KU=Y6gPHG%;T|gR|%t9L@p&n +^EMU{~@v+F#Io=yLb^@#h5A&qV=iRxprx973Fhz3LnHtheK;tk+&YHuSwMCsw=-{6GPKeG}GIOp0y^l +0K4tIgD%Nq^ZQi1Q^jQv;SMU1yu_|1tq7f~YpRZD78*nzws#)ues@^R#%B&UHLl_jGY^-&oydM!LM08 +Z?;|ucK~z*Z$*z#pwtMb33|;!8q$BXY1|V#tJLFEQDIprjL2PB@j2bapd(V6%f1tefX`oade4sx)*B^ +@xqj_%v?Bm1$6QkP)h>@6aWAK2mt$eR#N}~0006200000001Na003}la4%nJZggdGZeeUMZ*XODVRUJ +4ZgVeRUukY>bYEXCaCrj&P)h>@6aWAK2mt$eR#WNCue~A$008w9001EX003}la4%nJZggdGZeeUMZ*X +ODVRUJ4ZgVeVXk}w-E^v9BS8Z?GHW2=@;hDBsR&rdwBQU3slgA +WIIpo~nu5crQ+8y!s8~&hMi;DmIc9lx3Js=MNM3 +U|1fsGOElaE{}r08}e+v0p=?XBqQ2DCmWaOEa5EzTz5y9LwS)SlZ%hJPp(oJGkKE6-n;0s?u+f&GlkR +8O|7i@en2TM(MGVhn(xq?C+mR@j6*Ox^@^^85Y2w11Jn_)4y1r&rlf(}n6eXRS*HbT +P8xEoJ}4cS2GIZfJ1$BA6{p2(yrYd`W)U(8zTe2LbZyQ+(i+QP5YOivQf0bKl;A3D1eA(Gbj@XJtYk! +;dB089mgo3`xdB#DF!$wUtS#<{mY3=Loc@?{Ae$Nm22HpNrXtC}89TJG2k}`p8byzeqM@=$75l4Xy(a +1Bhu@?WG9%Ct24+652~~u}c<;!)=w53e163yiU1R8g78MqK0{%Qmab_Lyl`4`Q3rqe)Wd~eLSH=k3Lz +?`I3O&Z=71V=QYLywkW`xbIKZX6P97YdIrU%)Dp2_?aubu03y +B)A(Z#j-3_#`@(m_!ItXd*wZ5ncnotx1Ap=8BZSG6+ +iE3!&IZ-I32Q{}Alou5!aYOWj=FIIn4M{uc`!a_s2@_212UaPM}6=2rocy{2DFm%y(fvfN2MCqw^D{o +s1fza|rx{B1+qLq$ym-9&ZN1NEBLqrJ7|>}?;LV=tj@c55mb#|!Lf@$(>@vD<#o&`ptLu9^k!c}w7cx +)Y#$qU-OB^)Rx6P$X=|j3G8s&bLq>fAlt!pSDo-<3c>+2)u&1r_$x6Vz*eqwsVKt&zynL!TQ=ItTw1&FSC$iy=mLKiXz2<1 +77_CibYA5cpJ1QY-O00;p4c~(=zz6u&A3IG5qCIA2;0001RX>c!JX>N37a&BR4FK=*Va$$67Z*FrhW^ +!d^dSxzfdCeMaZ`-)}yMF~wK@kbFg}dRf4+eY~mTQ3myL&@TrPHk?}<7zuuAOe6|WU7YbKWD*D_a2@>VfH +w-vLW6o)tON(pb?(>*IbwGF=ey}_eswIm;zS{TGl*`(P6s|zmDhUQ-=o}RX@0u?EH%9*C9JLzws0ysZ@ +;~M|0%nEJ+?#ZAuUP)R+6=%%I;a!u%1Nk4V78LnxB9EF|&(;?U-P7&K3(aDkjLrSyu{Dd8gOnOlvgDl +Eq??S5!)py|xU#t#>@`?I&4lhJk?=4nSe93CK(@=AboLkZWyqH?VvQ_zJcoJHgmj79VeQ#(2~1xCH{= +^V$lw;$U$ZudBnFY&oyscL +(OXrIE#oD(PZPY*^IxoImk~e~XW%YG0AjI_ive3HN&o1T~|eh8(5ZJrFj=|B?>A+#QLVKnOrQX(#^<+L&9d%DQDe3S +X`_+M!+0()NwggjFL>IM27l4#@>k_l$5GlGHr~|KjzfLb(&wikcO0km5j`i%uSU9vXSi+o~WaGO}fa? +Mnq_2H$fC!VE$tl7MGPAk7{K)`+6qx0Ncv**t6>t&(8qkV_&td1Rz!fEDQZ!dOF;fEY~pjyxja_f*2s +aFRXTr;Z!m0D)l-K?wY#cVNdzTB$B2k+@qgb$czY%!+7@X~bE*WGYU)Q-(B^`Mn{YP@f~qwnq~v&O>u +oKA=&t&Rbr4GD-+Xa!q%f=~$E((YWfQ+laTG8+wt_(d8=^w +Qxd>%^x;0>$eU{69oKW-)5;m*K3Vo^^(SzC@rU*J=7rO~7q5E@TC!^a-y;NUM6N+RuXbP=7iuY;e2#b +^F4KG8}<%QK)>p2W}EusaX!uB?j{@bpEV1z(K~%KX8JG(7rCSs#VDS(*pHDJ_Z^kxie5 +&!J?HAP{a6tjlpzen0fQXvLyr%q3BAHjPFzBAY@v&%Y<=n?J8_Plg%>< +pn(_n$2(NV^b}$OXH*+kDV{I>V+83rDO$|9<@rKhg(Y7AK?x@KCPV3M`oh{vIpO!onTPvf=n44vgt!S +Rcb`BYoJ8%`$&M7(_&9qB~RRI<6%K-O`q_cI^Z%=+62Q-R5MdkF^}D +#?vJkKGF54cjcz{mEajm&C|RAb=b_fh>=$Ug%$|&jG6+UZ}2qtd|xDVPay5nTjtAv)5#E@DOIP~^3=t +kv-pn8YpN0uM;!Lk6JB(ioGz{KDOg3$LwHtjJyQ%`6ZsD?bwaniRbex2TTX%bL(d~(n(r#X8W52`KYX0!oD#M*@c8WW>2V4H>(GR`Y^!_bd`DSM33 +YwC@`qBqlwG2eOBwyHJQ_=mlpyX*yp0qusa5fWh;%NX!A|8fxJelZmV8G_MKY3nbMx+@~zNZQ&xOx_A +N9cM=wmuqZ>(L^7UAtLUDK+*wXL^U5z%SkGJ@F@VIL0Eo=vhMw`lwHQ*=(NutuN7H9{LE}GNK2RwyMr +_X)?n@FlRP2Mv*!Bitn3NxhbHg!YGZPmoK|J!X +|?dK4e?S#1K4zK=_8&0&WG35^oaXj*M=2>hFW}jRW=G#6WaSMGt^*&aC@{VZ&Czw(THjUB2h!^VLUCF +)*YR-A#x6T0erS9Mlihxws6Yj3p&pbnNu2$>5C#KIGtV}P$1AVzFm*h2S~)_GkZ +!~QN3Ia0H$Q!-}$bGL1DC(i6Hx8#w~zqvB&1$`4Zpt5km4-)KGL`hcd +dLs1L3(K~pPxW9Wylg(^VBP)B!@q0RG-5#&CSp84wMj$&Xd`%W#*`%-uIODRpWsDmu-tjwrek4nnq@# +=vYC%YuYc8o-7|I1i%yn($(C2rB&CTGGCI;ov_6vNu%7eF`tj@Kz@{<6kJq)maE@DRZ}&~u*7iTyaD? +?M!=A5ybKLJ;Ai#&$AIM*R`jPa%gOQ{WOv2GiQx~hFjYf%Q{c)%R{2(Q| +T}$fpx?IQA?w_?gu)(>vs*q!t+=#CU96Z(6FcK>2XHyn@nKwM|K07cF_t_FnoTAh7?EttZj>ocgk_#8 +wvRnLAS=}n%v?ygkJm`P)h>@6aWAK2mt$eR#W^sWd^bt0016c001KZ003}la4%nJZggdGZeeUMZ*XOD +VRUJ4ZgVeia%FH~a%C=XdEGp1kKDGI-}hIrvuCUZosrvqxB^>DgC>qI2JFP}OLKr>$ebC@uDTjY5jDG +>1o`jxypR-0QM2O&y#sF1#=8>vkbK_XsM&1xx>K#}lonEKOWk+n`p}n6Ep|;O%3Albsw6(Rn9XJv7nk +DQt%7$|)jX8-jo38%y{dbmZ<|B46>B9rwQug#R!Df*?3HMn65f}!=yBqqKXfVwF#BVBycW3Lre8KKt{ +|(`b6IJ1aZ&Eurt3x3+}yw-_RoE}SN*Y7+CFI9Z~7KCU0v+DW-nj}?##S-2Jjc+MXx%!uGB)jgflM04 +`#s&p3;_WUfipC+jMy=H+K@3%@L%wa5l1T>T)Bi@@G}laJP +hZ2wmSbP*>ZYBYG4eS^8!831M3Xb}`QEjlT>_WiLGgy1mH{J1lSA8e-?fY6ET70jz3mSkAUzK_<`_1j +}AWiIJ3H+q{C;BS!>v-49s48xks4exqVjF?P*;uI4toNmKQ +j+iw-Cecr@&P5=VKA9%(VXQ-MiOsWPPLF0M9Aik7$j|Rv=)WT^zb<0`+z&t+YmMVXQPrC(XR8DNZqrVmcWI}_M;6JA) +8y#k6BRkf80Z*r=>`T3|nWY;ljCF9BE;{gTI~lPft7 +4C}G|i_(6f#OwGFAI9$Q67Z->Dpx4T(^ZZ8jFX>rUkR~mP`NhS>R_#OquZorp(l$_;%)mhZ>jb+j>`T +iW*9o@ZL98UML-oL~)~Jb!Qa7x}uEn}(kp0E-?<3`KO*3ZBdOl`6b2OefMXojOv8TfUPR_Os8T>vc5D +#U4>mN3!f9XAMYqU;oP#P{|6=23VS;PpK8?1uGt|NFzCObVOlcl8L5nzA +1+c|)V0YdlaVpTL?297Ps@g3CiYFas0V)Lq0Yn@puQ#ET4t7Ea3H33LbqyD?nhArb0WVjhPQ>Gu;WCaZh(wIZYSp!!0RAU{xo9KMF44GkOzEg)|r4fEJ^m>EHW|Jj +g9!johM|cOHrx*N)OoV8^CS9ZxOBIbr&&MJI|cYl9%LU?n{Valz@-7E9QD-}rCi9CSio}5 +6J!+lZ$4XuCZX>#HIJSFeS-&xis4BhFa>YDK({pG-;JE~vI9esk`#VZfWKi>Qlm-kbMqwQ0ds%w8oV= +fB)!r0Q5M1rrUBuiS?0-Tk~8~EyKHNYfrIrmV#uSAuwB_%mC!eB1gTQ@5L}{>%p +^EkO${rT{XCC`y;Znby*Y4@2wDhT2YvnY=?#eRVGaJq{%Lz`bDp0;q#r~moazy2v?IL4j$BVcTn_ +swT8hne*^r*Y_gprLPJWA5Zl2qYEZWjzt%f+=rDb?IdmM-vgTkAD3*YYSQZ%$>0BhlE#eKd396KL +uUUESe~3uA|hh^0TafinX$~cM)@LqlEi^6=`;hEFx26 +1=Jpl;Gd$MEJsM_B?$hv?Bz||ODbuq$X)$@y+;b`Manhwc$7y`bVT}DnFk=5@%pM`%rXPWc$!!2u)&$ +dqZNJSuB%hF0xR+JA{eSHph900li()CFFAW~>xm=p~c4;##8Z072Q=jtF3d(2ZliPrR5JVCqdkt1zBaP7>%lc^M<5<)PZm$Fo7QO~PdH?3+5 +@mCX8v?z+491Vle4zE;!1WWHHc|bOg^>>eF+O+)pvt`ka!az<={?Kg^>jn*?;_hpVf$ok7liKryWyUD +i~IlR1o=JfSa3?V2Gu!4^mcu+ojb<^IkVt(D?7P&i~qwU_C&Kr_jiE=LjUZ6EN;MNFlc|!z3@`v_yeT +&bLEbP?7KQo^gG97hNpoEXabgCI^}6{-tK}F$f3rSG1n(3xW}z|*&!`J+V7P3=QnS0;~Kd4w%iPH8#} +j62e^=333`q;C89L@oEToBV}NKf7&Z)B0|TpV+0z~{#|KQJEl-k+mm2du17oIgKtuLt?wyqQlGLAB>< +4tTn?t{Bc1yHbOUg|Vf)HO-Q)5cei<&lcK+h2}fI8Jy(m3sF1qZ{h3Qj?B04CBtnY=g&DOn%d#n@RUlFJQvM=fX^3huzmCLhCq +847J7_;)`%`+@xr2qBhR)q+0*A>Vd2LME$40ufWuKY!*W6+Jhr)MQ~SEknpEZk@#A6V0i8+GA1 +@B>Y|5Yk(mqhbD1s3RrfTb0cT+71^fTElHmcH@j}ezpfWN1P~hZlA&@L_yovCsVB`qmOB1~rX@wnGA^ +1-o5`IC*&7&lY#|Tt*utDb7>3PI|q_Azu;6#RIh~Y4OLyn;jj6}UPWy~r_1`>aedf7A`Mpv{Xfg(TBK +BaLWhdT(#xAuVOe3I*gJU2LnRuj7|MDg*znzGJ9#&*JmF~Z>UA#v<15_O#gKs{+r2;YlcBK^5wm1zBD +HfUcNmx0n15MVXg>^UI@(_7;V>JvR%gr}QNxjaGkX^@tb50n5%^2}W88|)C`sVobT +!_7`&TMlC&&i_p +db_}udo-k}$2cALc1iAvpY`ZEfFyiv+aTzh5f;bq%3HysvG)-qoIL=n-fk4qL-&4jxHyIf!{x6oy;;+ +I3^K7LcyWH1XlyZ7grouvB0j$;0P-1n1B^Xa172 +{9sj$KaY;A%@vpXJvi3jrGXKVI|v7bE4k<4ZUghP3Pi}?`3o(qi)Tc{I`vBj|w?0r24O1Z;LsaJ!ze25Ey>C;{1Ppjmk`ncO +qE)WXCk=r3s#1)xV$ktL5eAILJS5d7^g@(TR(@@Fi3gcb(eF0e0q*;1z)go5CJUei+fgnVK08C&5#kO +O2w@LzuQ7qbRU%x)FR&kTzgb#G21$(t})jYP*zxU6-H{0@eK!NR)P40E_MFRzXbF{beY;|{WKTv?xJ` +ABFzGFQ!gxm8@k``pvU(IxhF0VG14*(MX0;lm$J66Ag~LBfi%i!Yk4ynz6^qWu-rf@2z`HWDNvbRhPi +aDZ{)xVWv2qO24;Il(A&i@Lp)J}kKS9Y2}djti0Io)0ss;d^U-(?B@7>Cao!^j2XCgW61djARDr!k;% +6e^@O_OVW1Nv(EajMyKbamK)ANhSMqc~SrIkgYZM=iodm<|VGA?XDGtcuTK3BCqfDWXcpw%??H*% +gP8=Wu$!#qR9y23aYQ+@#O_P4a2$@G)iXm&=HlX4u$186y_czUxbHwtzNYZ@qBE+gb8qw*<*I_z@G{RVo$ERZ5&Nd*Zu(832H +W_SZCB|Z(I+X9LVA}3M9&G`7h}t!_XKCYCv}G&0L+ud=S28>acDWdKk&R{7xq@MQA`gATt40MO{E7H= +75zO*Z18b7NJbx{IrM^KR1k9PDLK>+n7ITtxl^hYHw}780&AXucX$`u#ve6M}ad;a{zf4}4`L$h#+I9*4%;ABH~BJM>V+DJTii)IbZU>g#LWc1Z9zh2p94LM7|=+ +x3;XUJEXE{g1zU+1Qe1^$O=Z8&HGh=uQRFagQ9=>_QuJIsmm0a^kK`vVXjw8$=N&*b}E8CcylqJ`whQ +;=R)N^0xY<0J&s!-*&%klUhRPvO83!Tg +doUnh-aXpMt3ad9eZmbO1i*Ry5f=O$f%Da)K>8=i5o|ASfOYLMLdM&8I+2RW1f-C+@ps0d4FJ7pt?`f +=EqaF9jA|b3k=LYF~nb@{qDZV6j>bz`#lwrH0BA+!*XMDSOj%z*H(ZNua%MKSN~!HX7nF@-{ieYU_;A +)v93*RYRUWeTocEyC-VNpb;Vu9J(6{9tQA!3%f-%^jndSm7^C;D5kujS)HRR}dHlpRwoAgP&WeMUvOJ7 +q6ceN^5nwzVTGL+1CFe1UkbNM6R6MIB^(A~KCsKp`kt;J4ZPRzCDA2dbNE`c$Xqm9e_VI3sC{O~b|Fr +MR*!ri8#B`IgZ4*UYyq-`{r);QXBYBfx{B-Lu{p+BmQO_;V->%&bpd+By$du}OFBTd1=CA7R|k=f~_y +4gpNEdb)2D1D7~HTEH|o~?1y!4tcI&A9)lDGT`_v1DYy=%;tJstUfF12Ken*HVe#H{B*Cw_ZmY7-W|t7P|2eoGF)A?G3d;C +`Tt?dvbSo>!d5?=t)7S4#aqbg65Z+7xH3VRYQL!&zM$11!SJRb?&QgytWruwdeilYgX$Ld53L3v)qP} ++bYCnv<(2oItanb_lKpJhbiUGR9CiAiHD)c6)y#@Rkum+TmAjQW$T2>WBsSF2;FC!?tp(epO9})+6wO +K3Ix3g(l?D}eeok0G4iwHrFaVQZXpcs|hiz|zqtA)5C?zrMq|CI?#uIREfJn_Qs-*RJiV8Pgt2>S@`lbN4)MG!B%I5lhg7Y}FZGr4KtBHQnG$)4{jUh{fI|yYny`>L!@JCMH3{)A +FsZ!Oeara1Kjr>j>!Q6)`&vH;o+ffyQkVKsp+-OnE5fkC^r_|50p>WM?Wp4}276!P59V?)pjH*qci%> +O6fZ**Cbe<@{ +OKNMW7AlMO#dwoZ1G#se0jPJ5@vhDHL#9woQR!ZGca%QUUfpv9DXwoi4UC@MN25B%x$|++Y`iT>{ta7 +_Jt|pZYjQK#_p)x$|M2L@WN_`O!7e1*X!BgxD${`U?}Y$WI&=x`^2Qd(# +MwJdb_s`|@YWStbxx#v+(uY6)$4V;(dcNMW%=8;Hj +z{rn`7|Fs8mMRaDx#HX6`dd&u>@w*gDkDo*aD0Sj?(CEy!JvJ4hylcNt;rYS4TY&|IvNMv|;VqZYHO@ciS=dq&5EU(Lsz@F3wqmaUv3ThxZoP=Sg%{x+0;t|8qbj-BcK8VhLmD?f*mM`J5TPpTyJ +etItvv;os$ap5t_$>oP`|o9eqrQ`ycvUtOg#g1@0}Uw^3CVGUl*QQI+*bBZa_5NvzQd@pZ4!U{a +`Isd5pmdP3ojU`B$;X|eQI!sycgpEu(Ja0Y(##j*4OX5UbrozcbJg?^y&%da48{22f+j+mcI)O86C*T +i5v>azE5Uc#?oJs0-M@$lHnoa)%}&DzMmINK5{%v8D?e@T>%1nuZG*PbXt={@`w@Cm49$R2)#R9|pgg?(hb55gga)LBBhxfC3d~>0JIp2IKbNfyIlR88hr#9 +H51DM^Uay~W)1jS>j-ASmpP{wCsCe^z)4OkK*bHp?OCgEw5&pOcUssmH(CcyC!xH21vW|bgl;|WsDup +3m_%*YKAQDfOU6WmRgB)5y*jrxly`_W+nW#EFa(`*!Mxyr#ge-xG#dwDyI^#=ML6hzybAxu#w|E60dD +UqY-C8L*;a~aDrCGB +21Y2BKHAMnr3k|G+I#y?=4XqoIGPdsJq`_rujE=@$Qn@RP4jJDo@g^lrwUtyy|KBaDH2kh +?3y#{oduLxH#Kuf62gW=@n%##kiuG|p?CSUbao3;sF%0Zh!cL|UEN}n5AF&Wo;eT7QV~$_fq3@ZQIrc +j@xI(<&?4FKfhamg--Hh#wO9KQH000080Q-4XQvd(}00IC20000004e|g0B~t=FJEbHbY*gG +VQepNaAk5~bZKvHb1!0bX>4RKUtei%X>?y-E^v7R08mQ<1QY-O00;p4c~(c!JX>N37a&BR4FK=*Va$$67Z*FrhVs&Y3WG`)HbYWy+bYU)Vd4*F=kJ}&+z4I$Z?xD7nMe3od6e& +IIcGX^5Mb+LdY$ow+Fd)E5^Y1&@aXvQv;DQ*KH#6@&ilX?ANokc7Y|&y+iJS%Fw$#GL1&D44ErO<)0y +RUvjZzdvAq4f_g#<1Nu?emRPhQr0wAuyf(SQ8#Ngjgo9z%rF+w~a!=G^W{8H3?ElWf9Zm66RrM%QTfb +Jk-HJlj15XM42iPf6UFi82n253a>{t{4W(Q`HAbT^&7*ho0}%7XJ>Du=lm5FoV4C`3nVXlM6>7gvQOx +7BbqzUwQ6(dDrm*8rANYn&lk@6+Ca^fh??_*O0juBfw8 +gL$DE2hvydc1xn9T$hq9O&-ZoeeV%)DJ94=rt5-$uG_&+DKQ0!qHLl?)j&-jrGpbiFVZchN=U +C#H$BYSz=gaqme)J@;h2Whse9vOWejF&BPiH#EEAj^dzJ13dKE4T)qGt~g$^B}S+Kj|z~*-LeS%Kyxt +P1kZ`td_BadWy7b7)fC>^JkQb!Rw?uhd-W(54jzeJt^ChPI&|L|a0tj8uNflnfp{>UzVW$tBiy{U@oj +5%>NdFjgS^;Dvqw(7!RUit5m-MEtwbN2t(DX#@QX&Od%@s}Uvldq2K&9lW#u}b^~P)h>@6aWAK2mt$e +R#Uy^YJwR8005Z;001)p003}la4%nJZggdGZeeUMZ*XODVRUJ4ZgVeUb!lv5FKuOXVPs)+VP9orX>?& +?Y-KKRd3{vDj?*v@z2_@N;XopF4IEgcO635o-~y`9-XkZTHnZ#4mhH6k@0lcZnr>lpNo>!&dGp>ld7l +5oD3y#7_Gr+{NY;S1dum{3Jp|kP20>AXp6Y%$4I<0)Jj`*%XZ#&;K+&UfJRv_9J-GmK8d53&Y +=%*j@^#iKdgQJDz!$x%p?=h8>vOzlU5a)L(2LxY&@6)d22c}@n1>IOa~GA+Iibxm@E3;a97olVp|1A< +D%npxyS#*MC@Fp$S06TUVkHxO|<@$WtqZVQVT0o6lJZEVJ9Jr4EUM5Sl{qpK`onS7HWMfNC|hJfwf+b +AguHEa~(+V&<8SmUN)^X6uJNkL&N5v0y40D*uwq&dy$O*zcKdS1c)upjMW87rKUtGH@?$(6+1V`u(?* +CcqXGZd1n=ic9(+l73{UGxs6lRNy7rG&7<$J%&svfz!xQild+uw2dft9jeEoM27XzVA-3{%js~MN4%! +Bgu;aZ!;bDLm>CU5*{^C=`$JJEm*dpq8$;lN@Jsf%Ht$7=vl?SeB7eEc)0pi|ARh6Svq>fR +?FoK867S|M2Y*Gr%;Gtida@SZ)Fu{RL1<0|XQR000O8`*~JVr<)D?@&W(=nFjy> +F#rGnaA|NaUukZ1WpZv|Y%gzcWpZJ3X>V?GFJg6RY-BHOWprU=VRT_%Y-ML*V|gxcd4*L`Yuhjoe)q2 +s8U-5?vAqV|hp}!9)-g)oN>LT->}a(mBgyT;=zrfyv1O;p+PoN_@4Nf--FFJ5^cuX7!VA)X1}nTWnzW +`-6{(FSEi|Iq6K4in0g=jitF}W(ax9~iW|``GV|{=$N;lK1aamSd(~(~Fj4SQIYSFUopjyd6Kanx-a| +m4NCuNQ9K>Kr`s#VPON+Uft;Y<&jkHK>o_)|e2X-nzJxGS?ibsKlI+1*6~PqD$$8Y; +ERvYGzhH?7q)S4lpD6aH5ItTr9vWn4*wtOq3gL+b^_kw`xj-Q^2YA7EQk)4l#}|b0skY$a?Z8rfb$~D +G6^-|wbC*%&gE2OLvW-8Szm_dCL{R6v3|v5A~azLSO)+>wh2R&CJ*WDfdwA~V69pRT^%yItD}GFkZT- +k(K(2i`xZoD1_LeKv+}!~rdG7L&tf@D(8kYI5A4Fv3gzH*qIpe!ng!>XaBT)W5K{S@VlT8vZLmR}+7# +rHe0slEN{EtO8wkAylTwe3?_ +U;e!LqE2C@Gl+(nIju%#T;jDy>@#Wo|(_a>PVT0yrWcj3iK?@=r8FM#BEA{G*Jl?lSkJU-EdY0wv+%w +-zhXPA3!Bseez2D*gU2?;u2t5UkB+K!6oG{ArVKi&>>X}P`EqD9Xjl3L2k^KWuO9KQH000080Q-4XQ{ +p@lM(64RKcW7m0Y%XwleNs(}+%OEi` +&WoSA%R4Y}$J#2SN3#B{kwG`u-C>ifMwreY$4yFIS{+MJl1nP?|SxRh9I#Sk1&B~`!j*DMtTI;z(_wMa`e)7^e^kG8mviq~OFCV4W=$A-k2z|`PM^ZZQFz%hV2Mja +VkH(-ECq+jQB9CMY@n81HSR#xbZYSovNBWw5wGc)QC`7{;Yq5juHzDL)OZH}p`>Mk(>YY@JeKr#6f{v2bv0S&^VQuDFfgw?#I5a?lF)uGBphgf!>5_Q^aZ5j4X4Fqx;W*>p41|t|*uEDgAcV!=E(2zfik#ROmTY +wgV&i}Y#VsmDE@4fsY@dZ1se7bP)h>@6aWAK2mt$eR#S(lq{Tr3004aj001 +xm003}la4%nJZggdGZeeUMZ*XODVRUJ4ZgVeUb!lv5FL!8VWo%z%WNCC^Vr*qDaCv=GO>fjN5WV|Xj3 +QM^q{V^*MOvvGC`BLvt$IZ$a*|2n*0GK4bXg((JN`)0-6p~pXJ$NaX5PH;K`Y}xsIKF(BRrYPlGdmJ+ +);ZkOx|1VxROke6x3YP@(E?54ArRLzIjIvhG~t)&KaMTdi=(%^&mUAi*JJKnhAmC6oyNO#zWK5abc+) +D{WMbc0n?ulxWHWcYSGnQ^6Q~oOMV@=pGGR&129G-Ph>L%Tlrd%d#MZG}uBla?zBB9#NFoaK|cvIRSs +dyFFNEcG0X@{pe@gui8%G0Uc|YV`ak))=rt@Nv^{|H_g}UltlOoD;`Nkq9vHyUX{GuMMt5C;R^f*tGdtR00o#!N@y)}xsNx3~i{<$d?n>D +*7enYHNoUe5>If>bW1q$&N(@gtnH0@`3Tc6s&@t>cn~q2Yv}3!Ci{IR#lV(`Nnh{lALv&LxSn9z92%p{cB|PnlVS5ofD7dp7AmaSXfet(MYC;$i5X}VCKdJUDW!Ro-uIY +oQxS;Wj%>J#l_$ykS+Wx;H}Ue~T4RKcW7m0Y+ +r0;XJKP`E^v9ZR!wi)I1s(-S8%mhKtdg1do`dQx=D%x#s+B7y`+I4OCy;rMG7Pp*9iRIJN(cGt+Xi?s +1KIK84hROyf-xMdLcDoqHQT}BkEp-%KB0tqDG32=u?j-;!QiM^oHE^nhIXB$hq{i&kD*lNL!f)+fQT)i>Bw$>$%<69ejbS9L$4rj2v_k3 +R)XLkC=A(of5X=g&Nj>=xP(F7#y$)-*+Ym6IvrSmeKggUb{c#&9qjaBrFhDor0@2amv#l)Ra0yS{n*> +D@2Hz8@5jtE)$ldx5{SAnG8F594tP<`~bWp8>Dck?KPdW1Z>jz3!Gh-+*zMRCAftXZ{p!JwGj&dG>Hh +WWMA!if0ug0stwO~Z+xMfJVCY6S+FKQ}1okqvS>;L5BI1jAz#Lf>s?*kB%nZexrwz(o#?Tbl +C$Woq&Q#@veofh&mk@ipW(dgFjxds$>M>$npHkXOyROkV9VqeoG1sx7kBRbWhcM|U`cK%4w_>{~MPk+ +cWsU(yb4G7kpF1$=V|PxE6bITAX-h_lVN-Uwro76a}pMH#$a6&DcmS1 +av=#9kaqPVgjKbhae04RwpF4)CNp2%*fa&`_y#ii6<1bPurFkVRX5BV6|9%*U^ +HMubPRVnkus2W9pRB%?>tc`@6aWAK2mt$eR#V~K6nixQ001)p001li003}l +a4%nJZggdGZeeUMZ*XODVRUJ4ZgVebZgX^DY-}%IUukY>bYEXCaCuWwQgX{LQpn9uDa}bORwzo%Ni0c +CQ7Fk*$jmD)NzBQ~%u81&NKDR7OiwM=<5E&m;sO9rO9KQH000080Q-4XQ{h^v-UR{x01^cN05bpp0B~ +t=FJEbHbY*gGVQepNaAk5~bZKvHb1!Lbb97;BY%gVGX>?&?Y-L|;WoKbyc`k5yeN)knn=lZ3_gActPD +&(>4?v<1mHJRA*GlbURrwrPh;`ug*(TTgeQkpSBzK3DfM;jdFf-1w?0}u9FMy47;@BfdWu54I;Up>0h +HA7n2U%bN&lcUN3w?nG&)AcGE?AajOb(iigPyuhs*bgW25=YcpJ6T?q;)y`)M2RTbNG#~BdQwNMAlLl +Tq=jgSBv8)`-Y=Br|z!-_@>U%kQz|A_nJm0dt1z}kQ$|GJe_;=V4J+)$($Vnez~k*t$lPSLt}S}qa4BFe +vzif`{ZCDRZF|<*Qiv8-0j(bmJB@DERXtlM?+@_k9YpFY)u~D37KEvt-aNDxzKJ>Qr)ZI$nXiML-|4>T +<1QY-O00;p4c~(=*3H8@=1poj(5C8xw0001RX>c!JX>N37a&BR4FK=*Va$$67Z*FrhX>N0LVQg$KY-M +L*V|gxcd97FLirh97{_m#{4y87k)dLK;kkSnlLIX*lKhof6>|>9zw&Y0i&JH2(-g6}1Jy}xd!0wE7E; +^U*TpY*o8>mHbWl~uunnpOO73+*Hz}f?o960-I!Sx;QCZ^9kisLwnWZP-uSflG&s(O6XHmZznSt+gSo +opaYWe)0bl>VI#?$x6- +TD73uEg^U+z^k{T)SnC2?~T#smRPGxKv!&Wh89N2_x|Y?dvJV+%*ZZPiALpk`eTt++G6AfWqSj8DQ{X +7_ilU+sXh*>3kfYZ^HW-rReoSI|j2M18-*4x +U*;VjQAb+=p!qC8I#s4VA?RHshl_oacRZLdiivg35K}dV9g?BI!CC&O|C`xCG_v$pQ6`9dEqA^PJ8-Y_fIGCPnE=Wo-5Uyx$AqBUT_U}wQ5u-78NGGTz(zN0dMlF! +m9$859$v$V$990DVxg?;l%2KR@YD!7CsVmLm2`pGm?%}`FMM3KS~r?N{{$(><|-Xx>ICaII2mor=}bQ +8~H92Xs>I~kb_H1wUUU@%>BSfM3N*N$?Fu^fz@J7}^J@kC1b&>rZr1*E@dsa6697Eb`V^CH|mxj5({W +HXI+CL4_?b%|3ZEUOl=<-S8D@jogK7fqI@>?Nb)bZBM6mC0l+HE4FQCA=7-vj6mXn{uR +VFrZ#MtD!G>j$(Z4-uRSntdY0s>5pcYzHvd%`l-!OYDrEL1OW*VCO6al}iVWOYe+V**_;&;eSBWkeTufsmcZPH#dBTC?1u%u7uf^~2iS7Af+y^yHrXI7-P;{D +S6;%Yuao$l=l4^N{bIewO82Ua4_ol2J54Xxds$;gWlrM`0<56fV!}Fd0oqg4uhUlIIdDuc@|QO6T|C2 +f_gdg0G(h5w%!VI8@(tuz0gu(0a|d*x6UF1j6dHQYh*rn7jYMW~>6kE(B)>tZ9LYY~ +J!rkA=qUuycB))Q5XiOAHFw7F7GY=R-ja^_m6i^3`W +dFVPxN7->T;IF8Et_83)?{YBsAw$n?ChVWC%?oJvw|yGA;HnhaR~PcK4CwFeg73 +~p|o1JXX29-@B%P)h>@6aWAK2mt$eR#T6qSSrFG000zg001cf003}la4%nJZggdGZeeUMZ*XODVRUJ4 +ZgVebZgX^DY-}%gXk}$=E^v9xJ!^B^Hj>}%dkTb6la-mWf_teC5HmcyHMk~ey;fTTd8ihH!%3JEIB!#xF`lNWRPd-nI2$MNI896$c+ +$@A~|(W7F^U%n-(o0gA(j>$6D{Od`!YWdpt$jZLHto*H{;Ay&d;rSp~XNzn)o1Q5M-M$+O>dTqGOqBAJ17)pY0@UXn0yN|`vm4+XUnd&Oh77wn74ZK1Flbgh-1-mj +Z?3~Y;rUZ_`Ctq*05${SPN#@Cnv9tAD_jqUp&wmwIF6;S}Wh +*>U{W;TH869?n(MfvI6eL?WT&?Bv`esa(K2I$|h@8J*%s>t92STZCZBiTjkGymNrTNzuD0)r{cuF)(QpsUxu!dcJ|6H4l3UA85t^gW(uo-_?fU1 +(m+xGT-9Ud1rgvQA^v&kGg2qF*oJM_^l}x@qM-NT+=a%Cor8=|U|_Ga9#Et0uYDVWac)OdWiqo|lF0$ +HY$sNbT+I;Vz+Tb!4`S2o}OqdKSjC!sB?hx7VWtEkzlIE~2Qj>)D>HbDcgu{oAYZ1{#wdJnvgl!#V53 +bD%u^e)9$}L8NPqk`>yC&eamM!c_z6W&vAm3jpt_ufJCJ@6xOoIJHA4JVDr2Uuh8CLWR6Fr65cfK)<= +lb#c*N&S>MtF;v$e*KkZytCz|BKRgU)xG^K7OVa~}wGvOrIe5*eDUSf{0tRvvs4vEwWOx~I!*Di>Qca +Hn)QZKuU>4P=cd9Tr3K(WBvoP4h>riGe6MDh^A7^EJ1zYt+6XG%;vJr7`k7L@vmN=r#mEPU}OB8hMHn +0jC)t0KN%SzYzR>7Y_B?`ER8?)xMS&&h4k--+BaHz +w~X?_cE1r_BQ5yt!F>Y14Y}xo>Z9H|E$`!mt~<)UBItH>~q+bhy0kD%S_Me4s`08CIU81V-}5%{=WM0 +#~&4F?-#G05VLgyu=89MTBk^xrB+=9TZ~SrJ**zxTUAlkSlO8IIwZu9jv_EYy$oj1P4OiA#kkID+@T8 +%(!=kfS|=$!31_wyOrR*+zHx*$QrqAt;W9Af2xk5w7p8oNW|e&bdJq>j4!3QhUMof-r1%uHK)VvXaYyc@t@AX)HTqbmb6NSala1wDY8*NZb_neB*hH#-O?U2keo@S(+LHvJxl^OhBgg@_*S`*m5ixJ +^{B%YilsjY+XE2JAo@1&(QkBm~hMIKgmUfzH{ov9&&6WRK7CE>HO$i&Mu@aBeyZ3D4blLbJ>O#UHCM0l~BN`DJ>ap{p2_4x2X>?vi7j@ZHG`qpr +fiI4Ox92Vll$`a2L7H=(bRA19SR@Nt5LwBu!>6;S3?Eu +ZjQwjRMzR4O*=7X;E +L1!y^wp3+Z>58m_kFadt|YrbffLWj#e?o7YsbKL17F4XS6x|Liq?y&!J94Wx?7F#}Sp*RC(5h0@U_?BU3m*!Bzd%qMEF2ShM?k&K%fpXDhSJ+DO3Q=j<|rExSb +Dj|2yCSLIrP6><$YUR$RTp~D(f;38z#Xj5?z2HQ>0CQ2&UV$yI#mZ1#z_|9YtBshTVF2Q^^Az%w5eIB +9v_j9{Ofb_oHpf=t#zBw?$yoWTSwFywXDO2b7?&`I(xNZ4gS^fQf~#IOqy63qj87do&!8h|+*`rT=hz31-%WGclNIwJ#}8p8Gm-ewT7{u}@~zx$>i20U~9V +R#J!gxOn&UJv}>!ij!?jb=F5;@}=^2Vbaw6gnDGg9h;4gEs3uJE-R)X}c$&b#+_z~sqR +mp}kl=|{V9?`J%BcsxLAfF5|gE#VfgH%O=+YVa7=YnFoB2`UiRDWC{yj`p+?BnKF~Ej#c=^cvdqPH+H +?ny!N94eA2GUUs)RIC5GY92DgNJ~?2*9hiVp#2{uy^igUo;f4Y;qNq0yL!p3%w?T?P)PgT79}Nl&Wmm +T&G+BI)QgG3bN!A#!V=_}jtN!k3rMS)V6gMd7(33p_u_kNj_u!vX@rXj^lhPltpE~`Fg*`D4!V>^u=J +OY4#|woLDuEyt01R>`Ah2X1guot}CQ1oS^ylAyo;xBzkXV>E$(E!U95wy6f~5rF4#oz^y#n&*nd}o$; +0WqYPL1%l95JR9?k&tgSgL9RmPbbJkgFv{n+87>vhsVW6Z0`f?M>m-G=|NqE^itpll)lN0QLDGw(@4a +M>ZsBOFt0ec>`duJvU=G4m<+KZ8R}HFPbgUo{55QNN4K==n5^#&H%>1Cft$|!G+k?k$Qzt@Ff5)L~Y? +x-XvE{0osAltY&i)mM+?7WD=kVwqP$cGU~AzEJM5X!9k02QsM-nM-J1%cnfLCRS6=~RArG;;382RLW3 +Excka$gfjOwv%j1{o!5<$|Z~Xq{{XdFz2;!DEvq<4iM+hTr2n@!^ctW(?*Ihxj=A2p~G8hm?frEcBii +nXka8&sh+Sq7cfqG(&GH|`A+O518h9Hl_T4941xdTO0)qcO+1yIeayzeU*fttRml+#4YLjb(x_8#&{>?Etm*skpf8!sZefkCu3($EtAz1nr}r4607yWfi$~p+p1X{9$t +VR>y{Dl;IP_O$zhdMhgs8fx;gyAR}a5(HHVl#R!@aaq*$Wx92caO!GX=mCMc_@?OCgc4i^<99ojNpH` +Fck;?aHVmh=sb=EXmpwgw=80btGxH=tW<40v8+{C?p(z!+Kv5vNi5K^JDLaXK2q#gA(5s~3g(AuCdtA +()>fHMxW}04l(ACV_1b9QmG0otMn%G+TqQp+(9VTwY#at_$NKSG~~=ef^!x7~-^FrZtShaX{E^P2|UG +e{?j}#hKn@&)G11Phn{T2zpyWdo-TyIjyBBg;1@d13NqHsE2;g855O*0KddGZQu%H&nQf^{aXuDcCS3 +O@5P|ER{a}E^_U*wLb)k|{<}1`c@FfkMsF{4focTb+fn#Uo-M`m0?~8!%i&(S@D%X^Ox$GSV)zJlw2C +u5Nxiy6L6QxI;~1rB%*u@4?YX=fL{AK8mqJ6NeAkM0#=@uU&=rD*Gxd#n;EM{Ucp@=ofUAr-Su3C)cx +P^b!thTGJEhqx7V1A6;5!2CqX;m(v#>D)GnN$S;dxMnz8Q3~^N*^s3I{;v!%_Sw_y8DgPa+-Oto@u1j +48RL33WFBIlImGvxy#@kuzyOMky +7%Nk7)&=!;r=AsxI;a3zl1Z}Z0)hGrxGCN`6T^l<`hL@0#BAkZi;uEr{HeCx`CXl+m(KK{MF@$hZV6h3v@&5U#j_2PR_s$*5p0-_*z(2{-KvD0K74#=)(dLYEDipj +BP|0^V!`COVF_#_bl(-|=8Gbn&g>mU1u`-$vDzC|YYoBd~Y`On1e}3w3}n??->6U`L-L+*k07#(NMw= +-(;$HCMV_Me5s*>{XzE2M?W|C3rdP4GAnVkEwjX)}z2osU{3O;tjByCRK|4A#Jn93DBUEE6@`4xwT3@ +Hv~qXO>iV{!o`X5m0p1(UuZ*)%=@BKFKczaQO&!ObME!TX}@qX`|$3evC=h-6kqI9WeEGS6LS=r_Tr*9KW|4rK1TFlM +)GE6HAtAK00jESv!sbVY`Ml00DMf#hb9j^-{5Oc*D^TXfZgU4piFf!%lO61vy&IkPk;VdNknaD6Vy`va=a5mt)0+cMx` +obB{NilG$$qN#X$0ut`eFk89QU@Q6!YJQ>IK6vMMGAe2X4^!vNPw*C5?qYAhp`L5!T*+V*EG(KGShHI +2=pVocdA!Fg1}m9HHG9UPwps4B&e$-d;1$;X(aYJD-hC4#zvzMM+7rnU4}Ix7XFlmKSe*Kvp>)N{2xH +!t6850vb`DPz(>)!fqH($eOzJOA%AX@p&(9l|948HJDiB-1O^|+V?R~kEy|&Ujl^)LWXz}V83^F`+pR ++5&3RIKydhbPydgg*Q!aq;;HQ*@i$%*qnvk)zKj>4EQ<*be4UM#Shc0MlHbt6}Po$j3Kk1+?aFy;Lmd +L5VKBJ5}Y8uQ=&6Ti6~3>w<+ev{ZB08PwSzl8)(;5}Cf_VWknM?KpY5zE1ZQMG_SQsnjrjt5O-*Qm;#%I^|9kRzR +9^m_pz1IU-ntVvA#Di>l4AthaFk;FR}arw-F8?5TjQt}ob5ViSdehd=`KF=JK={52&2Lhg +A95SU!U6%~ghd3-s1H0mrPl}8QCL05vtJ<+E@MgfeJ-{}>F0F3#?^yRv=zH5?_;R=Eclzwu1$OHA+jO +vt3O1YVg6UN5@1o?htij-oIfQrGt)WvmIu$nCNoS`_w?7#$z_+U~VHS<|ZTti$#dVTrsR^h$w#WkOj} +Fu$8`+OQFX^6GueP+;mY$ImM!O1f$hoP~}W-wM$JYk47t}0ozI9PxW +t_4702tA+aD_D}VsVZ#IRU%Mr{m7`6WyS03PrS&HV)EU-R2Y{22^!L9Q=?dsve6Tmj@D7(?eRhRU~pm +tT5kbTvP@iSFu;$%$u1S?r=gL)WJ#Py1;#bLaFpVBr2<|)NQd_}n{W&rZ}EUBg{QkoqlDf}`DoMHnY@ +$Rw!gWg1SfSuvFObjU@z+B_qPRX-n%o$8P19Qu`GbdgE3kZ(^IQ3+M%C!Dy9)4u!W&f#2`J^P3Ia!E& +hBGco0vdzQ!09p?-VP?L0Z$suApdNt&ks3OF>#39hv)$txG(0M*05Zf_zu&C_?F1(WOy +!m0g+cv7Q^%z@q;@=Xa}ailWIYdY!}~-Y`ljorGg3xlg$SDdN^ckF%OovU%(6X<2jRJH7QUcG=3Kk+e +s3%j!)g4V$pkF!aRencM2Z1>;ZxHs1-LVmf0%=7KJWVj@U1QCZ&-Ont9m@#ZOREh!RrFtM2(RbWlmha +d%qyH5`pQ3mXj0MmAKhhOA#zn{l3&>RHd*5U4by%`C9C7nDIAACAOX`;6u9yyw%KDYE}nZ+8%#UCq8bit_L*S?CTm8&B;c7>nl}77$!zEKJwBpnE^}a&b7qU_lPSM +jf8YZedxc$W5dEyCc-zpY2KO7}qd_mn;$1msbA0FK&={kNX;dQJRPMn)B`rv{aaocx^2A%OFXm8+0QupKJ`|@C!1(8UaqwktP|y`AJD~OR{|K1(?tUClfR=nJio+un!}bmnP +A&?%rNmPHpl8?cG6lPN8E8%&3?@|E=OBP?3B0$AcBt9C9^J{sJE4yp(VrM+yfKZ%dqQYMdb)MZu;rn)AWBk>3&;ax_4-UByk=)hkiDMBI>8y=9f3tKmyj^g-J3!Efy +c!^{?9stRg*kQYmy^Q7Wx1qYm&JNqy|Ws0yhC{8ADPML9O#z7j~f@dz2Q$AulfecAigT)CtX)+GJ*Sf +09v|F(POZBpIOI-NGC?ECXlJ5$S#WoFHhEf)OsYuhK;h(@UM{kTDq1*mQjXi}k8?5kkm*?>S#=zVa^J +P95%C}b~U^ci!Z36mi>=v0KiYb5-5PY3AR9QO#ZZi!&%LF`MI=NbBaMsm7E{mW3`aSmwB!nQh(~*aS# +}_~ru^_0<+Lq2#dm&qMp2^(!8*v#$0?RhW(7h}c_cpqH^;oh(7&AB7Ga0v$Lm_U;sLvH;YbV)caUiE` +a7Sp!*R;|1i4qZWpDR$K +=1FZBBB|z1$U8Y<5K1M`HCSa$>#1A4V3uj7#rUBz;aPy$l1{rX>`(cZTqnkV1<7aRJSjPCk5#p)O?l+ +^YVt{8qY!5?Dw312;S>CYfkknN;b8L9bk_{>bC@pPLXZB7ZStQ3?G>FAmZoU(D1zC&cn9`}%A9JAp-d +A_;_Si(Lyj(5`E53fv8`YL7+9Nbr@kb{|3aJXQm#E39A#;!`$!+D{zAEtE2H1k+klnIOw{(9(GllyyY +nR>XO1D(CLg>ql(L9C22Y)zpCSb8SQtyJ`y_tcjBQ*6f7Qy}sIe{t%^%^@Bdyg3?Zh_`pN%>D%p+O=!ROcW=@G4jkXWq}8y!ljNLT`c7bwVG$dGQw94Jb@d0<_8dBpgj+&d&E20rjvs4!{ +z2w|w~Uq53cNyLY#>DX{$S{sR9!qTSSdL;k&`R9Dg?*eO;eB0ODbpK|sJ&&#cm#xfznk +wcLgU3Dt6~DTK*@bJ0vCXF@?%MslF_^}V1wv-Nsie5YTI2@KbxTyuKxZBs&qiUQ*G7+;xodVna~~qnsDHHfj3EEH+%&tyvpYW(!Eiyzd6WV8!8~z-cstR6tFkX`*F`8yvIM(`(P^R+?z19$3WnSP_60OGX*YGv +9sWawy|-+OTv7FFOf!t-WjHFTG|sl&5m6b+Zs_G{X?LSvl6D=X^^nr;6^=G>8hl50YyFDJ(8xVCIKXD +Ebc9UzZov6a4pA-&krLpKtpM(=1F=TOmA+G?`hQ&mOlwOjK)e(#9jk#C55Hu`8yWT6z~K3ys02-B{Pmtn3T`aZ@XS1-bc<9M$MREM~%ZT_hUJ9WWP-eHNmQ*sfXjPJ^POOuj6Kxb4T`@-)Oe?zf +em91QY-O00;p4c~(;hq@{F^0{{T&3IG5d0001RX>c!JX>N37a&BR4FLGsZFJE72ZfSI1UoLQYtya;h+ +c*$?_g83L7HshK2L!yZ(0y3A?1i>(mmm~-oTybKRg!ZXO8>nhTe9VAw{)R~#J1+l(V5ZAB>bwCaimp3 +KlFJUHqqrJ;Y6G8X&jmz;X2t@=)qxwhBG@KCF0Eri%mSy^cMCYdT5u7U0N#VkGD^{lQqqv;jNp +~80eLm8Zt0_CD>7PwLVD&mkRIVJ$1L-C0E0V*gw$tnkK)Oht_d(<1X9>||@zRV64$?K1b%VOE5{sebhl;whqYUc9Bf +p?tNUbwp?)3_)Xm7$;LT;(ym@hq0@@Z7I9bqtXw9<{xwL3Eg~O)mCwlK<_H6KfIEFv#dKMn5YmHw-@s +CB0@s=%{Ay;(s5SmDm|kumR(2mF&5>HXqFJYF7{zwwvs4;74+{!a8%`I;T3Kirp|?&53Fkv>q1w7(Xo +<|Fv)SGo$fe`{rqKG}CP#vNu7Ry~Ta^)qrVMZ$FR}2TwCUZGo>2@=FWak$=XUA8;G|8(-0JHgx!lA(b +O`Xj^~|ixPYFgk9>>apP*ROA&0CkY}$#{DktHI|V?)KG6MgMb}YLvF#JXxP@P++03iSX0B~t=FJEbHbY*gGV +QepQWpOWKZ*FsRa&=>LZ*p@kaCz+;U6b3k@m;?Hq@n}EZ>S>4n;$ +AQ^l@%Q^@+b7XA`SAgaB{8E1R`0kCGfLv_XEW*ogLeiaiX=WH +*pkt{&5XS~XpI@GmnTG%%iEEjo^@fNVQQiI4ttAGH+OIv3pS8B2oWeub@0$>%LX)W4TH0)62qJizJoU +e4Bo@>Dl3bq%e5;t__o1)zn6|0LH=k=DXz#~=}>@7Ew;dzru6 +h)H>upUoV%Iy}8Y(J&4zcpgAY2==@$J$5=N1s*4IeSrL?VSBkqs(|YXFm$~e0Km*&e=~D3{Lb?AS+3) +MU~4YsX^i#cS`oWxdJv=jQ^m|a|@cD_HNbd>YA5D?i_2*oR4$pR4uY%Q#d62Xh%ktU2RhkjyyETPXq6 +4^oBP|ylfiT%vp`Ur!KWFABqZ80N9m?kC`CWIfo$`kqr>8v1NNui&7;8=+H;I@XhhfYv4R65B_#$Rat +=cn?>Kj?`J?ku|#QM|Ail8z;H_ZlWvMqbkyMa^KmH*78d%<0SoR~d}s@x5cdSF?B!9YlsDO~xE4gu>6 +QCJWzHf)17DXS&5Kd4>vf&-*u`7Xx`IBv^3aqWnKf^BX)9A08(VOlxd#HEIfJ3eBu#pDyZDd~6%8Vm$hS!O)C7{zkisOZe)Y2!5?r3nFn38#Ds^M4Rp6Ci*!}9?jzSmzO`kU#%X +!U(Nm)&54&Yg4R30lJ8|DXr$P%y~cz;#Pv$19o#Vh^}e`dHZ0RI)XwdSc9uO0@CJB_YtvII2sYiJdGV^hE5D!IPBo2827CB_4^sZp +8K%L>NlXBYVzBn3ixuIfNN<2?|0fv$&W;97L*C_D6aQ(b}Cw#d9Ys_S4(%oB>0+Ghp|S+P~7&vkcuWL +`~N*_nWHg>d~%k>&54vuS>qeKTjKReId5nmTl@Xo9q&&F}00^P+VcaS-AWG1YJ5M6L#*aWgx`nB`$OB%3B3)_9C{^jD*lPxQL7W7gO+7l-a +AwHXP{ANFM9BwPR53s#7y}jPGiR2V+?V;~xNe^q76&M6DZD0Y0qb}^?w=pS +tPG>_bxoGhSq4y>=o8I0{Az+t+14`}NjtpXTaK?zvMSk}P>2~T87A08D@z#q-Gw0anETGeS3UN@x9Ws +$(jkY0WJPFS^0MUG#cO7!@_GTx)E&z>S`&v_amwzQAPzXd2z&FU>52JWKCMqEpMBnZ}KET$ +OkJT2jj)2_967Hdb>>lwJ<6t#Ota&4&W+qTk)`_iZ^wFVF#S#Y_J9o{%ASkyNQ*^4&H1Jq@SQ>)UggCVj)oG4}J;n9u<7Ub>CEwzHCXqk2vL*47x`$Zvh9+-)i_>VtfyDUpfl0V(0WFz_ipsqYo2#oXF@w*y#Dj@@CuuZl+S711S9wUbdx +LDLJPJ0}pXH;OYne!PV&VjxUn@9on6HyQ%yI$!l&h!>{xtm5~x($%FMm>|}7_AOD8YRKs-=B2^A!q(YUAbr~^6_rkMY+4~KjUIO)z|ryD;y^}@Rhvw_XX!K(>m4zG^ +(+C=*|q!_dalu$R=HK;!3pDp=HVDb%K5$8AZ0C{h0Dm0r`0|+MX_LG+_jLg37@Z_>}7T@fOYzG!wH$q +X`L-#m`rorglwT)(pm(e>(JnjY=TTp{(2Y|*G!fSHe7)i-{IQ;;O2;g;B61R>I7npByc~;4=v4MRCa% +iMxU9)%2e2w#Zh6|4wudo=HW7U!Qn#lVk-S)?L#hlK4&tFQo3sc>yxuI5>G#U7!bqIhqiyole8ax_>I-TJ +xcMN;+K8e*TcBE_#y|7aMECxvlkPQrhBqT2kfRH3ORsOr;}2AqKfL&1L9800WG!BK(k14;*zQ_pisVX +ULMh%H9X$G{g6u_?Q)GHMYCWiUDS4jON7F?CQue?`Df=?@feoJ*R*e{?aervKrjJ8;!oD|-VyfW$by6 +RvVIQNyo<~Pef&@*qqwqR4)&S+;{wn0Q;k0|d(TniRf^pHn7v>)}Bhd~3B0q$lrx10Xbd;4)$GgBY_R +W%gNg9Mi4;0LWz5HV3wUJ*tnvZ=z2=R(dpH2yzo@r40P@D>sZx=&w69*6OX1w?us{y)+KW8%}Ji+tiB +sWb(d&3*-uJu|GIVl(WtYLi^G?uLko%NyZVqs9JMt!XATgE}JDwNP@9fm!UK6q0hAX0rNJ$8RIUYBXZ +#+}F$I8`^*hbcykC5yf`>Ks(&K1}Cjobf@l(fl#hX9~rNNhmy>>k)LS*YD@A8jK}$y%En`--XN{0Q!! +ruZ%k#7#`RM)HQrOKs?&S7@{|QSQBe|no+>+3A+VkC(F*t!K3WS#JT{#R=rQ3x%Fg+!W4)D^yhWKtxE +fnIk*O$k0Br4_x5tpR{>+0g3b^;2_o=l@gLIHSIrk-{ZrL7)X@~@m=?@2g~Qu+gPK}5jyL%hybty8F) +Y}44~|2*^icO)-y*OjC_i))-(ecuMcA9S2=5hmcl~E +X@YJSz55vFFG_@>%?Y=FoQ(IMoQ{!5a25V5;k2t(Ql$#!k$q3W4Sb6=HAD-=^2{X1@`963#ebrvnnRG +TV}5+5~94)0!E#{;0w}iq0$I2Q3ACVSngt0H9b^FPd*)Oo{gPjcu!)q%fFWH-Vcp;c?O7K@cQ|!*?vEvg_8taW{*4O42?cX)0syprb9p^Ibb&M0g +|Wq5RFgx?wftljs5C56|<|+>^X+{pe0uhP`vU;?eTNEz%hIT1SAoXc-Nt>XIp_V~2MZa(E27P}AexyB +lxa?Ce38;XS5BlCT%>g;yf@*bx2P-=g3Mq3H&weQ-VKD)5P(>8*D>NV=n0C|0w{aQE)I;C=NiHTP}}X +mGiFssI4Ub2sW2>;)F|_~O%AFW=xZ&i(=60{qZB{OecY%lf#CGA*RsPpTLT4<92LxPA$#rQN^#Jb}p&_QJ5t9)sWPuH2T*+nH&lMr;u_GH~by +r=kQt%q-fz1ytLM>hE@1vz!faY^`*;o6HeIl)}p$bju`2K1#Vnm_Ywza^i6Fv2|0V=aF3gO$hE)&E&x +n|X|u4|sct@14PE~Euns*dH~ox%pT}UNq={=qBdXeG&O!qh6NcVfty|m8g@dy7e0TqX^Qa1R +OO0VdpZU4u?X{eQrsjRu|zAiKjS&9I2X@V +*L`+};_y3Sr=@dqL;0PrK^iCEY`vH6|oy-SZhgdXESGbYG%h*m*`~1C|+I4F8wP@4#6U8;3+};MiOA( +1qOdWW)YA$VS7dFSDfsJ|@Wrh)g5w!iR@&7)kC?j|b_1qpf2Z^_gt=f6AnLj%@?DvzZzCPk8NQV=lNe +{7KB$-0rKWlV!+N3XVGk2LBSk|L~4MYF{9RL6TaA| +NaUukZ1WpZv|Y%g+UaW8UZabIR>Y-KKRdEGo~ciXs?-}NhS{C+vnP`{t#^~y8@sdZIx +a&?ki{`YvH)dACzJoa_W^(a2|3Pm_RF#li^TK3UqHs=@#vlW=cZ6{Evr_GWl>7eOumVdm+%QC+zCBMBa<}Lp^Zl&tJ(_33BetTVL)9a*Zi@M57emmbZIAAj3tu#fGW<>=PPWahv$@NGaHc@NxYLP<@K{ptjhQ>v|zeoobn@`6|=%x^cKWqi<*UDK@f_?Om +;IV5ruxx*c^|G-v$fzCoL|C>; +`Tn+bpwRDD$1hWio2p+iMm`!1+vi2SK?MGO{A@t;&j`tYFduQN7&bHaMX1OVK1tM{0O2kzsauwx%jFa +jri*;`ZyY88L&{6i9>NQCh5tP$3x@G5y~&J((9s<8oF5txFEK^fHkK=x9SplVi)2#1^ui9U0h)6l%)W +FPmN&Wh%>bSd-C}a6RKTFn92?lRl +Wvt1F=BdktjeiVuel9NHxzv|CF)?B-#<6pyk;Fi)~U#DlOYIZ4I&ssN=}mt}pYsnhAn+ +40{1p?S5))j}*JP>=cV_~__JoV5$_*?bs--)9|ff^tk9liYtcOS#czn}gI_W#5CZ{B~x +-w47F7ti@PS1}|-k!}!r(HNwrfE8LYBpspcX^4tL!8(tFy=s +%wrRgber#2q?QXAXZX7fq%3&xloF26KJUd(w5Y*{xuK@)kD~sx8I0klcRaCj!4qA}byV@Ehms0I&HJi +*#KZsGa|(7p5$a9b^8dF3W^uYXMD%m%cHY%+z|9l>SU7Q$O$Rs8bu~L2-X?ROTddDUA6FRCR+fII +gtSq7HyWXr+|-^<#OuYJZ#Qzr(H{-Gs|!@aaGr4*8~D7b%y%7H>P9l{t$zrnR6*F&=+Af3eF0V<858Z +ZUpeN%yhj`bLovj9UOF>i2WbvbrwMAOA=`j_ZD{ZpoDJTTiC6G7jj&uqq!UbI)S;7`@<4%A7W}6xG*%SXQ#iFhrL5u-r9Nnb#rfduFSj4^r^A +7)&gy1iM5N`P5@udfkmJb1dF*O=TEH-~x$lAGF2gvb=h*bXHP8)b=Q}`hFxa7(5D4L)HG_U9AF<+iDZmksQyVw~wmkVITmhO_65Y(airFY +1V2rx)FDc2!y6YGmd0!F+aY8*^r)YSo5`(*`Kjp*(C$-|?b9qv@nP2NdRm!PvQaf~rcMB));b6z4}K`+1m +LWJm=L#Fwzxm!`k!mS6N01pU2!~`3&x?5s!@+&crEmx%nNcPz77~+zz3ins||Q1y;u)F;#$&zl4{&6j +YuXK_vNZRd@||H10xZFjQe%pBmIi&3Id@FTmx2b_jZ;a8^8v<-DWl#8;&rz>OLE~81C_K;c*iFnt3k5 +S1UszZ^WOpDF7vb=WiwF3xFiBIm{to@&Z!;t4<;VmudXzvw;5+knm$NPGN#|-gf*y-k*<=W3h*C)zRE +QqZ)^9i}NRXpV@gZt_bfMBL^^q)ErtVZnum24zHVZPrL$1oy`s6GjZ29wh%b)$6QSp%>b*_;%A73EO` +-!EPDRjsi*s>+khgW;_!wZ5-i?_Y0%RH@~{te*t4VGVUM2uewJ2;J=#rsIw|xV*efEw?D;S+3r)zrTS +t4#r#tM6>nro3<)6Iz*#>}W{Faa`hvLK{byXEO3=2Bw5oc9G~RZ$;iT< +#rHGGp#m<#<-8+A>XEE5Tu^zK`S0-L1tx^>?Mm~u@$b>z=-5%#NpAw) +RMyt$Ie`u>DD_~6qko*3Ri?bFZ~{cV=_@}L8eHS(l#`*!vLHtL2faJ^Q!vlS4`w4i16XY~Oqf)XGs~N +1feH$#m~SBD=9*h2`VkgB=3B~RJf=&0{_}EGUTN~suSzC6AUW(O^&Kc~{=@EP-_^2nfuQ%6_FqQ>0Rj +JhHrNnN(qetBzN0D|^a7&DTx0YUousQC?3^y>l{|n}sBf`;;8d<(RG{M2Ma1!mdN0;-iTEsi0_TfG<} +Z{`3Byc#$bWja{WlE9I-EV!#(#y;?t1LbYW;;k0)2$}nlcuvXoB*4t^m&S3flChL@IG+O@YmWu?Li9z +@$$YLt$PEWr#}7Mt@?W$ksXKS +yAy9XjXhKFka=h#E{1DB_ApOaU;QlSt&N2^eWF7%L0=7rVG`}G(9*czn#gXJ!GM;Q<$wk=BH-}$$U<= +sR)d4!XO-V#I#bBy5_ZGUwUS)9k480|_JPT^)RD9f=~}Es<8VLd6(^MYvn$4D73+M<1eY2E1PB6KoSR +)F%EBz-fG%os$9@I89K90E_rRMPf!eim#0T!4W{W`Jc8}bnrP(@yYq#uj`Mz72 +W1@gur1&F`kHAOqe2|#i_dyB>2R{WEg!pGL%;4=UTWzUuG;;H9{m-2SjM=UfZZ;eH8dy3`$%3Nj@)#1R?;r1}W?!ArN +t~m)#JMG1-gVIW@pn_ZVY`0fUxdz9tqA91sSrAYC>(r)y@!?c9i63vB3s0DARdT-BnW*moiHryd@g?v +&RNoKd(kLf(FuT3t^k1qb;8SpL84*Vk@L|B(WqShSZMMNJ-qDmWG#&a?drSo&kx2o^lk>eT$2T#Y9Uq +J;Fm4DkRsOJkuwx(IP!^Pihjm+ghPBLl7x&QfHEUCuHiE!f71!YXmR$J)EnVMYinDE-80y>+Gvm>B5n +CLBJv4YZ6%DBjqw6lhM98$jLyTVn-anm+hY%^=>)6xwoO}^HWJ<#+{I1M4B1AE~J%HG)b}z*L0<=L?8 +|{owKa_TanC={6*NFF+47uY0n#|2PWV(I8=M7|%bX@Ot1}5sITulqHiz{BE2MT87 +RVKD*NJHqee2`R@sliBCI+sB^cN;hkUFmAHq;eEoFxk5qZ8gP0Q_`IKdl@L5eGtq@5SX6CUALqGJ7_prmXLmU$Md-};L2$yvgv*sXuR8HdkW;dYl%RykQ${u=U0jH +ms~f48zT~jY(=K^@NxUS5_om{MCyQ&vv06%mGWp;3I4MMiL!XwQG4PDVWz^M)jt-*90JpKLCKYF}DxN#bp03s +A@8_Ww<$dcq%-cwfi{Pf52dEkBS*L2`{(2?{2f8J9t~v8%{xs#CTzMs6Y8G3f|7c`Ruu@Ny +@od+?h)t=Qon5k27-3($(^mHX(K>HmU$UyUjeG$G56@mw}B3-9s=()-O6e`Cup25Ap#8MnArP&ziHlK +a{TVeh}N!L7Lzp-7I4lc*RI#iQ1uz3`kzyH^|fFZj>5Lys0=v4|NAwJlbO4?S#r +%t@{o`n}VQJ0hLPDA4yt-P`qtYuc8uPL0o(!m5D6_ZTljjvvtYu>uqA9afpZ0BCbC=}AgthYw-l3%2~t2h?(Sk +`n^9VgYNwR<|MnYt|O{b7C-S>x)lQam34*3zV{&1AKE;gR2O*YY{|E5cW5&JFI}IZ|cJ8SU%xQwNtP^ +IokI5hBNcVIDKH#Et*YPI;7wU!M*SP?aA-OpQ0AN$q$;MIlwUo*zJc8nc2ZllvS9oas{pw1PzGp;C86 +J(aDR4H#%tsS`ij(Hdi{WLET)ddefw}x;_Ac0|~L=;MpIZJ$v@#*&m-hJAlQx{-iKiS{JQ314XAV!fU +7Q)~N}J#E0xdc>XnkQ80TJI+1)eQWO`wBke_1&4Lm%<(j2ukTonp>QcSej(6C%zI4iHCxn%8(x?A6)xMGld>GVG3rV|W$d){8OX6l@C +p)%XLa7<;KIP$*4T*h6;Z-MIGNKU$8730F3=&l?VMto-i+Zz2ZP)h>@6aWAK2mt$eR#R!EM9=9W000bx001BW003}la4%nJZggdGZeeUMa%FKZa%FK}X>N0LV +Qg$JaCyx=Ym?iywcqzwu-wU5nrPWLZPLs(Oq=*|>lrup^`pIWJsz4XLK3qQ$&%Erm7V?X_dM_hs9k$* +?yY9*l?dPfI5;?OfTziujYwAQcH3Up%{p1OyPaspWZ1O(ZIxVzq!+vPN>oV+58Itcy0(V$x=BWCxF7e +u$WBg^-Jv_2C&Sp+%kix3#=33FZT_^}4&vmb-gRw1LhWWbw(WLM54P=k4ZW(Ltu7cg`?22YUk6>V-`A +D;{%hT>>a93g_3bVh4;_wQ7X8>97Re)+m003P7Rh56)*^W}ioU$qibe9g9^m&2InG7$TKsD-nkAIHZQ +#)f1MTWAE3QOSwY`GeU9O8>4DG&OiXo_#b>;H1#5rZVvcDAlpxXVNejaPk!w_n~HlSa(dS#v+YdUmdS ++8oVyVvT;YjLzCqFFaumQCB#%W_-)DvGAuiDNCVpf|vjYWolV`3Wt$vL8fJ4*aFKngk%aZyx?ByY9=b +vu5~`9{;UvFCUe#&N0;5OQo7Uu8~GB_M^ATK-8iKQphg$^|mTRbER7Rgb$uHS9RYuxDbov=k`VCKaGJ +BAD4nCO!xk*8Acdwi4ULjecQ*HZQEt(v|5X?7?vC1EQqeYYb&uGvTfa5ss_*Dd#q{;2xQPa&1@&evMR +^Y^weMtQK*7Xf>Ma89&vvK;8ek9!rh8>xjZPSvy%iq!BCNuf`_u{;$(hDk2SAMrbimEvlyZ-Kvl79jc59wJw`C$#I05L*veK@Vd9xSdGHD3gU&_RJa +a)J!Fw@o>03}^Zq{hF@5tB0jKnsT@#92VjuaJ7L)aRK~x-xV+zHL9W8mItb+pNs&!7;4xk%$~*I7VQV +*#-p4V@}J7$oBVWBR{|%?zj{%XF!^#1!=c$z$&txxxnOg-W9)&O4f_iqA7JPjZ~eZ3OymvvJeSrXv>U>K%62IFd}=Kx<%PEZ>XeU +dA+C-T|hsV +3jLstNE<%DN`^F?I23?Bpg)MBlvFC!U~EGrhzR^S+>Gha6kWD!EY~+J1i1L8I&Yrt3hVTIVh@^=aSYz +aX?DfofDp51T7NSxB<;*TaPnUI(H|MdpNL1F$-bqQ9l;2g1fpAgrsHL<0Kty%Q>J{Di}-kO2f%OJsFT +(XK5zZ>$AF9wdtH^q!GCUwag!%qq{h@W3v_?&BUp~{M}dYsTrsX!gN;-u-V7x=ol?fcvz^;alL22#|~CK@-$#>NepNW +1cYMw1j%U^aoS2YVV#5G>l1S6B%9YgcOglo#!OOhB~u0x5kZ#^r`6s~1~wRf4r;nFdMOz=qilEzn&vm +e3Khi+YKB*49yYL?Ri=y0Y7wJE12KN9Dw$I6fEpG_ +8lp6PZ?#vF^gOu$0}+UXEJst0vebf%3hsNT^>f?U>&hmVRgbN%uqMd2g6#^cGWHx+t%nxZu$mf~QjOT +=9h^Z9BhLPP2z0$|FUn7BgW5u*1zNU@W79Tl48#H@KrZ9dJYXaCB>*;7Gr@w2v$=z~z8Ll$?9XYGbjT +e?qY;zVIK*m`n8J|;>^hhg(To0J +$bzw?Z5T$TXt`Zev!|3q@MJCX3b$ZYKc#D%hQB8F{?EL?S3>Rw?<$qCW9rggM`Bwpp6BKj8IW65 +c&HCl{Haiz!;Myuv__rl(!*igs8CQ62>{=Ghk5`q^|AuTP|=1SmD}U9B4S!dcN#MSN5XMV16SG))^P} +*N)I9s(@u*Z@IlcMQvncXnEiy29O^e*eTIzCTGmPaYWEVdx4x49$(d^o_%3PZ>v9g(M++mQgRE?N@tH@VIq`baTM(rVZYudV1MS`&!D; +gH$;;%Muf9v(zItwxl~apg=H#E4NicJd^tlFoZ~GebVBnMTp@WmKMggb++l4k%u`HJxp_q-mNnT2YvT +P1Of!7Ths<6^PA>|ebxH_DXHHG9VZ6j};X-^dzYxi7ingU`^|BA0CU`wpF9EXW +AfTfdu1g-}JZM-FAlPDUtz}Xf&=%}!E!c`XC8T#Cf`T|6$PSn=2@G8)lEnzg|zDtxJ(*;zJ>CIkNmsd +JAcmyA>#fbBK)9-;UR;X4F`z0b`wck1jaywK~lq#d7no#-do}_}4NOda?P!>XS{|OL`{|I+N^6tG0$y +EaZkoN%Kcu%5WY|RjWI6nb|^P2$N+};pjO#pq&#-L5?{r&=J~pT{{5iv0YfuM7GY*I|zoaXqGkBl?)0X83l0g +c7&q}!<3!hCmzX2BLZnfeswG8sKa>>Zy@pv0}ivf&C{O3P)Ptk{L><;5M2v}TIJax)}|e92Gm-hSqn{ +3;rpi`^^O-FsH8*l82<8jNz^Vtts|J|c@&Q7w?t0V413Z!OYWKd%3%I3(f5f@v_EDx2v$yUCd!_b%nR +z&+hOjlLCpIV6~_+y2dj7ll3GeW3Jy8CRhlqEm0+(QsBpam-#Fc1bY+ldn0w@FHv!EGc~ +<$gP+7U2=cly;8hT->`7s}j6F1Lz0&L))WaWwW#WC^@fcNXS`j%e57VAJM$RKs=(YtZ3>XOOkF4=11~ +Jp#0eU7q|D2>~PpNvsVfNMTN0@BodD>sA+*s3%jYKbhCpAXtOA0*oZ8C_-QmGr6GsPC@P3!KU1Z1r_@ +%gBgRnjWuBz$LcY08GxoU6Vo{2j(XG)OF};wAJkbX$pLYT~rU(Os^!v~)_Cp$A##SGd0qi%bkI@CO$2c23TJsur)`Kf7h2dnqm`iP2J|vDC5^pRD*6A7sd_s%3 +nG2cgH75%_|vp48>HXg+)88O99BoWjPje`^|wFh#R?6jq9BKKQDiCtw9s2(zHr?Ly7k-)j{z5zxY(~J +hM1=5j|~_PXCtd!9QPy^y_S3qrKMQVBZ5T1Ddh{tPdTz>A&{uA%6{O!bU6;4b~CZQnW=8+Fw_Zp8`GO +x!iz==R$o|syu+{W+4{tDq_Owg)5<8CZBcDYqf88g==P6XfGy +AK~>&mwMo;@$KInEfRDE+JoH3eL(?l5jZPW&PJ?n^x*DHsPUJwOq*nJAiT&is%QdXg_XMb{jWF1Cnrq +pM&5%-76az!Y^xDKH&=o&{VceS``d`At_+?NVBqLV#4Q)#OtLhRf2-Al+G5LGpr!H{(AM&a8Hb*DLL* +2;@R5FvSYg5zy=hWr&1Ss?*ZU3fg&T0h`psr5jE`u7OSv)zuVjm_YZn^rC=;hyi$P#O^=P-9UUNGVd% +ioaiZewaQ(GiGmp4JClyUJb##dja!Mq{Mjk|L6!kYX7(@)gEL8bQfirLdWhHxU?aNvJjwIqA!)GV&Ki5cba=hjF9GPnxq4I`{6E4R=lw +LqWM^?9$x6W;ibdDiXngc7|2ZrM+3*I0|n52tPwXvLxaaBq#19TH^VH-lG20yz=srU4pM{(=j!wyhGGj)bB~QCG!Y6V41XI|QssOx;|L^%bt%*_o7CXA%Ua +B>q{!b6|myFrtt%D~I8Ns^x7H-8T{iIGI`C?y__p?oN{S*zYNm2~%x^iFqO(2{oK-6+=!*fX+Yxq$m` +NYxDvE{Q}MG4iX<{v>{>iXPAIsR#eU!&1zv`*P7hlWZGAQt%tKSAip6Iy9-faAVOwV2Og{i(JI!}fUN +KZX|1rkK#33^fkt~^YiDQVN!ae%VN5!KqbG?${W+>OTAD+K%X&!IJ~)JnI#Xl=HsN(isY}XfXSr%DSD +vDeYEBj|FS4PUdd!kt^$iC_Hs#k}|B*-!>KH+dY3Ym;8sY*-w_a|PQQYjSHCU~p1x^hJzb^YKAxbW{> +vllrl?Ca#JPgT;SEOs{HCoe*fD>Mc#^8n4#agg)G;MbQO=JF9}WXIMxy0`Ks#qQxSv}UtV9mygq7yENrmNlqs5To><%yex +|#!242pJ$lS-BhM1P~3T|%q)<$aicB0yEI|I*;CT-VRtw660u#?OOluIp~W|y&y5h)jimz|VY5hTifDfc5L!i!Pn5G_#Fpj&dwfMos&K?$v3X% +c9lwr&tCm>0sD)+1~~!)ru=_!#lrUHMKjJWXEXNuA*Us>W{Mq_f|TyAD}P36FQQg_)ItWLtspG$c#l1 +0IQ-GCqLy4|}Kzt(*O>JIqm1?2#FZz`Ngza+?HGqaZ2IPgI#x6h&z6=HrG+7#hpDlcbxydh(yePj8<+ +f4q<}v+V)Y-3B!CDtG+BL;xqBhI-$DD${Go`wT+5tjTNKZOf%F@fPTPkHf&=g_SSrVCPil+KxJUY7$J +6XCtMfU^kp)!)m3p(gyC!0#=V|XzY)skR2*$L%)=IOJ+|7L|CKuiCS%n$TiqO1>35^{M@&1pFJiyw-M +58)q}L4QGVypCOhEwHOgcOP)dFhGAlw3sv)8Tnrcyk8j4U!ptPqk{mMU)#B7kA57vJ0x4J8CO|e!jmvaL3;U1GTTrFRC4jF9l+92G +-uqT(#fP8PU4?wNFHnL46SLtgXqY8Ou4Q=2A`Ym-QuHM%hjP`9L#blT3m^|(15kVvKWj)pX^&1xIp91w>W4&uUvQM`N&)uHq*vEMzLD;$(J!w)JumgUmUwdi%EPYhYM*1p +hNEP0Q=T>3(!34D-mm6MKH##_EUNw^s9LN7fe5p&>j~jfYy~L6ihfhOJP)YtUiV9Z>rWPk%eM-zgr6L +-$V2iYj_W226C=nT5irV~c*tq-){(FYN_IA)AWQ7V$`(tM46K$*^a+seh;#r#1FyV5pf|T(-pC}FVD +_1P+HT>M1u4J*s-h+C)3~SVUdtagJ%0G&-hkETl<*yZc~?jPq=THpJ37wtfNWM5Ga)WY-(Qa1sA{7--KB +Ga1k4?OB4RMPUhcPLPnMX5WTnCCu2mZAp7 +Z}LpR5XN>a=JLhp)xb8R-ZQ1}3jrnW^nC2W7Wnw{BdS_lQJZJrG-vklUE4Ed1niO>wfs;Tu+(pwDseQ +wOB0JT{TM(jc?g<=TcqJ%@+W#*v;XZ+@Y&pB8NB>EG(uTV#-dB^f5fqdNirx>dupeLg@&U%jeM(i2qk +Gu>k=HOXU~x@y9)hcxTl{t;vmvgG9xB<2k9P&V~=;K?7^)s%cD^A8)XsJLfNJi@qk~IRHx!HWYI5xQM7}!|9AS1pn=~UunRY5lN9Mbz~PGB$`oAS^?lz+ImQG^SdmB1UYARjRfIP{6Kcrj2a!ft +yD7Albl{$&%MK+8Oo8hCI$T29p>(j7BZ{1E|@X2Y`kLi)ceWq<~w58dJPc|H~gfpbpH4kXrSyz+qUEmmb29O{>vKHs-5Odqjc&08WL-nQ^O)j7zKQAXRa#d9k>FP%)KB4Es&SAG +t-*#|@OVC1~=bY{lw2EI8Ajkz{Mztk;E-WlaL>1-k6i8WBpnZ=w>?) +_M}(?#QiE5fZqTxKsTO#_@=4B<>1`t +80gvG@%lC!=LRZ3W>N3`-Z4(g5TNB%n1&MA1_{Q%Y{H+ze8x)hq5!i78?^iQZ$>>AQ6@?dO+?*7mhuk +duAmLs$)aES`L;22!4@3R?Zo`{g?Zm`Gc`DL} +xj)qFX#NT5M(fSMDtZFja6S7M9mr@LR&Wk-HmIPTtpQVjc@$IVfAU9^KI3k;99@2zyMnRZVSSf;ZM%W +hn$mMr%@@|vwaP7}XjNrQR`ns|o}7ru3Y-noEHU`=;zRdai~#|CB&{~KfV!v}lDA3orYMPI80{FUGgZ +}NIm{-XvfVYd4beIO`zK761MaW7W&M}T&wp^TgTV9?Io0iA=C_#tEIAhyl5HRy21%w-Khc1a>L(mhp?T<9i_?;j#oAUJT3eNCE09cu;i2b +q$bM4AX;aenpvmPDnV6kludh~6pe}!>20=nFavMGM8I|ivU$|?N?2z#QfMJ(7PgHiV*--R4%0t6`e(% +fb&I#>Q7S0!zZ?jHDEe@os^lt2WRJ?j$84gbYk4;&yeITHW8!$ZMbZ4TW&OwQj=RfYAT9QBzsHZVqip +GHZIUi4up#1wdOVyJorcwO9k9iXz0Zw^4obqeUgI3d2oF~R?41Y~G3HAq5;T@*La^$*eP=JJY8uo47z +e)0|G29E7b9pr(Ep(Ji=oy$WJ=*nz!cJ%`))2i-=Px6SWVs-j|TGjOOpN>>Y&Z$2YfUH@6u +qh)(D+Ntu(j_B;R=BL?d%fZeMbT=dQ~*zo+=g%~;z>^N%gbp{a)mflqa5{vFop-NdYtLJObePCX*>v` +RYSco8VSz6=S`fL2}0{<)1`7yPZ?$}flgmtOY(61V-Y#3gZmvvfzt| +NeaIrLbbi_lVGYMuckSU$z#?TUAd=^`?wN(>`iWD4^#Z>Bf`HZqZ*96#w#@&e+0hjJFSdepr^W0;)zJ +?iTTL@I|P=l51+0qPb85qDv+j9?K}GNAWycJ|QSXQ)-U^mCkxAd3cNuiK*vAm5*r{de6x#gL;b2kp^2 +B}o0w0}bcd%i{5qr_Xx}Uh>fk!#JCb2s%+qZ~buU+^?&<2 +F1+f>tuBfI|uN)4OfHav;F+G2gB*(iId%!EUe(>AMyq*o``s`%| +FsJGj>~(zMM1sIZ{ke~h~M}LPkEQwhI+P +-tIDy!PpLdPFuhPhu#Z8==x#N_A)871WUQ2gpcCZc5+;-~R{PGDst9RbTrDO>D$f?w+pu#k;@Z}G8>F +drr4JUGmruIa*xRF}KYjge+7UK&J!6+cP030B9?ud!71I6`o{Vl8%6E%74VJr@;7BV+qqod~Fc&-w<% +$?`I&2~FY-n@AH0&iP7$Nj +DjX%o$y1@ix-=?!YnL6tc_BM77ozc7Gu##l*p7XV-uQ9TI(O)sb~3{TU)C%yVnVkLC0Wu$OPnb6maIY +dvhP>G`;N?;aMTpoDUZRv!M>m+khh4!nfnx-k({sMJf2eI6cF#RhT=UpOF8xjFBr^kDX?C@a8FsBf1+ +&8?-JD$IuCx1i0L~7{3l8AdvS+57}9Si>5nN6dpCD(D(@ppM`jIia`$ViJ>U)~%5#cW5H7Eq~ +6kh>Zf3)6}!8tUHI!r3eA{4A6=qVd`kX$Nj1Z;HV~S=_`9XU4b9>@(+4=z7Bia@(;zCtd~fVs#w}|bH0HI=+{Xv!8^<=l3n~;4){Q`c}fmQMRHj%13Bw)fFwT9ykdudL!f{t>TSpM8&1}50ni*2 +c)7wG03%muNAa`mTwzvxRjh;{-f(u7b82X8l*0sP5^Qw{FxqNn>+Z0{^1MDc_HWn{L+gK&d`_`acGk| +x2V`&NMSCe;YYpMkk_GOyV3o{5zJ5aU0PbZ%QD++HkIUBGf&t@Fxw5oPYnlFqCjEFGSwgtIjc@kJ(Rj +{Nh;K(wNr1|UMY!^K6O20?9bn^cd7x$gg!}<{Lle6!1zK?flHfdAITtxj2ngNx72c0lNpd>?A+$Yfk&N>k=S&1$|*CGf6!1%v0O+UBeQV6kn8RQnV=@GC1>f_0`OLp +LEdvUrxF1gg?;Q=_^`v=7s`EP_$#o-=1x$5Zwz?`!5J$e`U{=>KpUotDov@%bxLte)%q0dN1HwxTny{ +NyyIHTbA}kSr%i_hQ?a?I|)r1_&wZ2%IuQ-H$`sc6bd4*>oQUq13C;iR$8yGt0aO9Xc-`UQ389!Ha4% +9W5L@Nkv{b(7pnf@imsHLHng3hjIbgJV^%r0B+ahF-o4L^u*IVd4fIV5_|4IK#nyRa%R79geOB&v>ac +{rqy>3o-VR?)$F7uwGI3J8kXiBqxI_Npsjpg*S>)mCdU0VW=4(c#T=_Gn##{>gF +l^sIaoL_gWm&2CysdvP(zL1g%0WqLjiR$(+kadjT`q+as0xt!!+#NOC!*5Hso?pZi{l&Nim}a~oU$(1So +tf?FLS&;Xi8(W%Bp_sqKo=(H!y;q{hbh6$IAVT(e}8jGBQj)tO*ASfg_24F`H>r!vJP1v)eo9^uE6tz|opI+ybUCNet+iGRoqSg35zwJn8M!eSbF{Lmcr#B>Xl +I9!Ol76+^(+CL1x6E9NOdnENNI|ieJo}s#CMUuHWzF}SLeZXKGP?8ewj*sPK&{K8wp%(kqr%lMG~ +C}S6-W(6Wx}r-e!Kjej;V&Mu~61zat%#;Wi7&4KDB_r)+NO75Y9|9NB|kHiVXLbeJ0`c4<6d4+Ux2(!hQT2 +JH;7KCoOHnS-g-ry7yQZwO1!G2JB;1z&NRLJi%RcWZ#6!xia8GoPS(TBqA3tTNy#QY6-U`89Unj3SIQ8A1ZyI>L@-rlB+sp0d8tuGlA4$%c +B;y1e`^}HB4vSXIsaB>)`Ukz%>kE-8?je#A-BjB)vP?wGqBG%vYOK#`Z-SaoyOLiODJhSq^1NrSl3=M +WqUTyzOlXs&gj)B(prOpWeRyaX$;PLT*!G;ilL`X?v@B(rOFIF%SRA(F_egiMv!jx4LgrIYc!*8SN|%^loP|FWl_aqi6raK+j83GN7|UD!Pa=q$RMpzArO9NjcTk<<_HT$0p8Z +dB|xYTD;8d1ixZ1-e9brN-RWhfC=R+Qr8h5}sa#vm4_MP9`S;i`90!60g%6`u*NI{lQL`b{cg&+04^L +$8Zp)H2sqok{clk^ +%?B?0Q-5v*bO*hwT=G1mVL;f4p{aBL`Kue<&L8%fbB|uMvNIjiOjYH+QGud>md%x;rS$J$pw+zo^WCe)V?I71C0j# +SG;$iT$V$9hhFZos7*;-Yx8M%8Z%19#Vh5;aE4Xni=gJubAn%*5dCt?xu(2of<{DOE@#$0zT9KIQb-l +et*YpEuN$<^A)ZupGoHY+sL)c(d{zCS8vDJpO{1@>GfYtvd#YhP)h>@6aWAK2mt$eR#O+blrnz>000# +b001BW003}la4%nJZggdGZeeUMa%FKZa%FK}baG*1Yh`jSaCx0q-*4MC5PtVxL8vGu7pfkLJq!jitZU +ODXwh|P_GAPCEzvd`nN&$Cv0Lb+H%^}KoCp3JKlYF_uX0UDs3FgwQ^$bO1YZJ@Mg>HcJNs0%| +@zC`>5?~ZS;=0LnSP0Ufvj!wcpl>MF_}aEaD3~MER +pdLNqFp}#Xc96V{gUb(im-iz|2I@y(5DwNVN4nAUtyOAVpb(XeElM92`Ai*TN!gf@vc+blPSGAoMv&J +B1#vR-p%N7{e)Y^pWcmH9HmMSc^VruW3|0vZWQLD%@Iu=8)ERn#x5yzB>uU3k)cz-Jv?1m^cD?tpEKM +f^KH|hRz&V2+_skj)?|6e-k94rh|;u}4a3INu*hWI{iC3fq5f|(=dvbr*ncT(~6b_PceFB1n?f=14jcm{nZUb!S#@^;JbHtszw)Q? +z<%AAGoh8H{oid{L#pVy@zjvMKUDQj3)e|o7791(aS%%6_=i9@$&X|tbn#1$d5&jOs|H9hs#4FcrJ~3 +DY&YucnBxu?~qYvG{rz`!on{I-VCauw?fMez`JO-oYZdlixBb%-aO +b7OM%01Q!yn=3{sLLMH(FW6bO;2B@0%%UYwVYnUZ4K@l#dGV?6qae7IPSl*2m1@ki!^<#1A=yop^R+g +Wn}kIVU_IZV(7uRjw|@RxG?_Bw$QO7FIv}71b8qBaN)1Zqy_aMDMJIwlp4y|v!@~U3L>{g)ypM?Vz4) +2tzxJAE`rmia&oRJjQ3~gO2C%%DVxA1B`TR?5&`p(AKFuvaodJ+ut;l42zW2|e+<@^I&>?CM^>K{f2f +G~<-H087N-HUqr!P$xk9<2?s{PsjaQApFo2fnEk8{qYBt8UO&qTmS$f0001RX>c!JX>N37a&BR4FLGsZFLGsZUv+M2ZgX^DY-}!Yd9^%ibKAyt +-}Ngt@MJ_fB$UU@omL&S$;8fFPxH8AJ9RU%1_p^Gg$M)?02Iyo>c96q_KgK)Hz__yAh3J(?Ai0)vnY3 +M(+y(X-=CFyyV=+4p=qjKKWNNde{^rxO}#0%XPd6s34U6Yt8_na56Z!3AmaNDv?vwK+=O{amkci +XI!Ja|89m3psrpJy;mSFZLrjtjZ00TmcImG9S*urOVIt;@O}a1QwRdDk^vtYg)zbFbf44%w=#v$m6)^ +8Ld6-uE&J0EyeD>t@&HgNE_u#m~=vdGl(zx!i8Es;uvH2mLz(Ah*0a8nJ00RkPhXiw21P2u~y|OIa7P +9stC4y7#W^*ShQMOu&`*cMG~HfZCawa$gS8ecBUZ6+iNS)lIj{tMY#&&4QnGvd(w1S3h<`Cjr`g8mmF +>+N>xIvwTnZ;WW +4u@M3|}+wRLtu>#SXi{-x)yI;ern@gHck|f`EG9RQEZY2n5RUO42dmzE4l!ZV*qS^4fvI1saQ{zIcj@ +11FNNeAKK*1mn3cp@#KvJ;FwgiC?@7^J*@7|@<(GBL9^f75HFu;yfBb?@acvs6 +Ov2UZ2^Rf|o{;2HAVCmt>jwyT=~+Y(r>fgUW)i(-aN=cCuhQdR|lIdh52Qv;fQs|b3gChXV~QgJao;? +LdF74-i%8WX^f5c@!RS&0S+Sgi6Mv;{nW`TYA|UOrnM%Hg)z4@+Q&>veN;<^bfLLt`Mb#wm5*RQv!?B +^ZAJy;*6PmkR+jB4F4P-mgVo7kJZPBYfB+Ll#=&raZJUvHyV4VM&S9b1~$1sO<92sJ|=&nB;h5LLsj} +yx1rlK-g$4H_g5-7NX0`9)LuQIGYkAg=m|;FIO~^4cZ=}A-gj$=!>6Uz5edUAD_RZ*{bnwhoS8+FD}} +AeV3#41ayF?b=q{>i@ud>(A~-*xPZwvT>)*D3JF;(xu5JWyhRjn^s(U~qV}vr}>)0`O{T4<|zRzoKo3ljjBAQ_7>F@yN8yxL2miK8zR*=E1IV +&)L9hNfllBcpCR1^T0qEmG{m!`0P?2agbG+cm!fpOX?^kR+z}XhECWpAHiJi!0?kxmO~7Yxb~e+gp%f +DNxIzXyWn#AkCq`@b_hYDUxyjbD)E!j94xKxwTw&(mtu1B}||fA0Ez~Rg|rd;3?yykWa+l!2`P#uR&l +GZ;M0Tn`J>~1xB%hnDw&2d3hqDI)g#xJl!c4CLpNry5@kort__R=upsNXwP_oovy#Uv6vis*UoOA78CmlYx&W8Dpq~LMF!7;9(EUhA%`_x-1t=a_^A +x~7>)4G95}7Epz`iHTJw(pqgIPwN1s8S5l?~XHbqNl>sQ0@S3NKTZ-OgG5wH=c@ +-OlWe13suhPvwqLj!lDu7~%43RsX;1IXC!3sK1&{KcjQUmKoL@BS7-1;B{Ng*us^l7yQTm91@?DY9rH +cy0)CBar~z3Itp*6&K=89mtB~uWJZeZS{!RcX#9z#1zo`yOS4KKF1hJoV +k^*zjJKEFY=spqCIoPp^|)3B2^4Lwg6)v_}eUpk!LWZ|t?D2K@fZ`lum_&x(vGL8X1ffaR_lM@#zZo) +`+AT33n&MidSwFLbH{02If$0(GQe~ro&`|k21%zs_&3+ug<;8z@OC9@R#1TO%Xx|iVS)obaqwdz|435 +r{}P2gjI8oEn84`2mD^Mm;FuLL-Z!p2;+>J!d74usOsfR{9Uq4mel$~0XFbj53Mwmj_SDU!E?ovh +(HTL*E^}9Y4s!xUmcmRW(B)5f^^LD8Zo!=86?$Vw;1pQ*Tad8|dByF9TvpcT_8Up8s*Jqo2|c0P^!pV +IcB&OS^d0*Xs`5Xpf(hRVGj%F3F> +b@$T&#$CrMx9eDTV(HR9Rh-Ql)`SkZ))pz+!&C+kj$P^D&(1;8A+db)Qt^Z`u@>gPgM +67*L}0S*n2Yk!k-j~xdlKt)h@xY)R!;L9&7P`#BS&MKtT&MHZ_IDdN1T^ZOwO~B=Y#RiP~a$A?1Bcp` +8TMp+v;VWyHEso9XXYB_%8_=NeXQr6g&g>R_5mG8>Hd|R}S0!IzT84$cf-tbR=0Ypt%I3b1If!a=`%? +K7j&6P;o&gOzaKogAN|Yk3+_8LjhH##8a2z~5eLVJ=*P+vFH0E@-ECZ$xnjnK2 +%LE6en#ElwM-T!%?fG%dAJDhy>x=3Pj<^Ayxy#(n0?gklL>>SAW4#|Hbau9xos?0b_ab&A)z05Hf9li +p-@Q3U6qkloAJ_g>i>tD=!7GTGPq1U^%oquAUfN$yA~8=bSF4$3ebOZ7QRH?2B@N@e>5QLzio)3IZ`B +hnicd-D%xy`Hb1zG5Wt1pYFEUd8Ba6hH=HUtfTuM-uwdpku+JPIW)gxRq;-=ACgrAKAL52Co=9Gi +c$8kAc8cNwC+!+BMcgG7wYvht}d!2O*S!D!gVzN)f8%b)1v<7>&C99WId{FOi>s{Nst7nc|> +#zFX}(7TT1lfm0bu7DB_&*;F&K6=E4pQ_Gw+^z?rXxyuX!2m=H9k<~!F7P|mt&T9u|D3)3SA?NQvqKh +CUyhK4p^0**GVr9y$7jrAz|t&|M}M%9K);LjlZb^)GNP`4_yys1QQC4LDru?WP-lgUNy*QW1{e@ +W_tEFXDC}4r$hddC1r~#68q&+EwyEqqF$o!5LuIs6c)J;mv{W=4_uiF-$33%3*m@mfbnG=PP1Tg1-;$q4xzArl)n(2H +G2(?WPsnqWRtqNwhh$h9wx@k+Y!b*J5Spi#*+lq*e(XB&piFchGDr}qT%<}x#vubJAH- +c*7gvd4Z6~i2u+a55ti;hN+E8um%(T;~UZk8E@|Kn~#{XqiooqT@FVMke2|{0toFOp5_azvj1({z|7s +!%&`*Y;2TMwd+Et=vX9ct6^`zbP?B&O9Z#C(84Rb+VQwD%l}1?dS(cDqva+`J&AL@>o?R|cxT%p;BpA +ckv-5N<{tRzRpb@2O&~|CGlFZ!tl7-}*c{cYF+U$A2f%#Ncz51#cOY7=hJCh%L*3QrfuYqcNwb{AZ!WUq^vBD$$83Ea#E+)Z6qkW2CbnF7c7~{75E*=@ZHt=+c8c@5q=T*& +P$Qz%+|Wcm};}>IAq5vjS=(lp=F&9>JqjdN}A!b`#eeWDayTN6zm#yAbnixEJ`ZzwZIO*6D_x;8g*!f +KK35GNi;$!omU2=!;&k~P +Z5aelMxjwDf~TcY%<=TIdE6gJ*OkoceLD;7KJgm6Wc~{Sm_0``&914cRldGEG0|VaCzO$S#%3uH)@Z7 +&ogllC-lVt;E5+!3mi)v(fw4aC->Pl-&--K{p0fT|TW}vCG#rjrg!4KZ^EZ~|jkYB4;mqNmxrtFfGB`CU&y~pv8o$)9* +1;sEijn`=gQDC)d~hNpF=;vA_EpTc@t4unIGmssue-fO`LtD$tS30Rci>El8N;=YynYw#$1KDTIa_MRvaj5S)ps0w-Wc!>&tbX_Dkev$Qo}N@1W5BV+66X`gK&fQ`_ba>I +YjBf_{~luF#sJgCj`L(ajq)%+F;`ePh)E9Y)1&>|<-uk+P`#3&X+B<9XU)lQ6VM?MDYs?jzKE`ZyQ8P +Bp&q>~k!V2>KFvH#}^P?$nyI=VG$s(d%6t?q-^w<6!*#?-kj3}e?|ct4yYV=A|3;lL~QR0PCz&AmH2k +3F!mig3C9iuU%YTqS}#;ykM5*BNq`S+jO9F$LC^1D>k98F&iJ=>!Aowpi`uueW|PKe(Wq+E05+AI +N=J?3X7%P;cR@WBQC|^^;@yd8H0w`e4FldfTclVNS@ZqSWOVYP+i|ft;7Ph>A?{_jYEVbJkK^9iOLpT +~v6fi+ZF&0LJFa+CqpajV7Zd0LjEK5(?q|JpxrenglTO@dNwC83+$}IX|GYm>VJ>DXxYL +`#uBu6sMSOL~^>$vnc}y>uXH?%#RsO0xPX}`&s8mXwz+d}Xbuqv^-}P5F)BJgVnJmVGIm&{T#-6zdeS +Iu%a25vLN#-#apgDjog?E7M8iKo-;+KdA$ezg6*a$~=_CMdda7a{Xh7nU>><0=x`=ByWh#R53uZaKXiK^NfHO;u@HeUPPcoll-WYy-QfB6*YC=r0z +Rl|`umvD@UpD(5QOm_ynW!>~KG;>99r`b9jRG88tjk&ejdgzs94CAVhkL(_8D +vBKx@$%607IaN86HL5dKNPP28@Po^P~Fot)+4l;Kt77W4xuOxp|RO}rZ4G$oO>pf<~pIwGiIX%#opTj +J)hLH7r*)=sLUx+J4KnTG*d5pht&Z@dse}tfnPXIs4y8@`nQ}axYr5Ui+2cMI?Una5o!JOcxb2UWXq# +uf^TqO`;$KHZ|8+3~Y4=RRQ=E1{XM07kEGGJVxEAj_$Me?o?fBm`H +ZupVuTGt{9-sf2hD0|=K|V%=N4mMLO`nwUSKK|-o&wHj`N`s(7}9Yr)L0p3O;lnoH7K}yOe(SBHE`>9 +U*RdYyrN=pbJmbfUpuFM+}cdNRMf_uHn3qn4C;tmrZ)Yts0i^$cwgPisTd7IgZJR+V+~EwfPJGv-`p@ +3Ax8zNsz&pR4WW0bbj&9~!)?R>jH=q+;Nc%d{gKq*a`^o0%O2%PCOA%!m)_pyy*Vy8OCs5`Sb5;aIVQ-3c3 +gQF96yZL`$Y#CwVZSEjSnzR#g5~e6Vk!s18SNEivaC;JQ|MCavVE95kI{6@1LGu@+NQ&-b6pBQX0_5w +41a7J$4s#YSzI0JqKRPC6<;}a;dm~Dc|$Q7pmhDTy_wn;$pevBYN7thTt|RXuhOW@zouGfv9+UYF*>q +XbTIZ8r=&%Ydh5+FVouSM2{sVp^boz!?)-V*W+p(S_3`T3=};b+;4OD?atjU$A8{c?32ZTm^@;KkFV+@q+xSJZ_ +EIJ%}9@=A#mOI)$@l$T^C9K1gNn_`rahPg+TgGGfpMF4>^AoOPPgW4jUgJ$0Os7()}&JYbS2mMp37%w`=`Pb2THk4%4RpW^tB4KD>FQBiN}k>bIE-#&6lF4VPE+I>#sU2;B)Yf$)o)6AgCD0%AS +>3Q}7Mm2DF<&@cH3kKCNh66c!Pca|)-&r7k4$Uo1Hx`2RQI3Dn1l#I;wx6VDIydx#ovFWe2bu)aqu#r +Jr=_i{QJGm#=N&c&2Hc)~Tv)StUXds!_OY%%H3T_gt9(et%db9~mW6AsANU^?vvw}%=Y|VZ5`c$tXWOpX +w^vEfHu1B?`s`cK3Ugee;Bkyqia}Rh%*G$2TL{)xTq7_KC%)a6qe(l@PEN>aH!&Hf#a}iNDn1B`Jt@O +82KLC8(Xi%fydM)Zi&XlV8j(wr3=eAZyqcJ)ay(TCqucr~r5fR}YCTN)7yON+OY_OhYdVUxIzlv0C$Q +|16TjB%WUUUpjM?>|*5!%#r5101mzqOAF4#hTz6TrMtHaYnr;mF(0C2COujueWs=l*<1;;q}r~frR&LhwHExHnvK}p>Uax*ZdY24~tK ++n4R_{)JcU@aFqD)my&Lg7xH7(=flB`|k+cURWi9$b<;An4_$Y2L2#&Vh!<)jcQ_`GLeY)*r$Q%(ljA +z+ZpZe_mOf=c4LZ}}Z6X>Mz3?? +322H&0DJWGxI>}*)tr+k6%OJs^8I}Hn%ZZiRat34K*6Brf_Dyk;TeW2*!zsXu2*hN433_OnK~-(I*;j +T-yqFd*?6*5JNm?hJ#XWZ$v@;mCFORUsKce;&2)-DnK2JxU_-(Ix4Q^4yAwJtfe +Vnbk%MUubnjM4G;nwV?MYRj-1Or7ZTGf5S0COxesjkHs$SU_U#IW=siD5ZW5_nd2vb)h^$5-Bze`R0K ++GyZ9O%OV6@LVw;~VuL{1gmWCwhG)xZ??z*KO)Fe4;)UqrYDU${;UkPpp;)?BJrvxNE7PU)Rg8Zfvlr +27w3brU16w51Zw`Bw;~|>8Qy-_g{#K-kx=30n=OA(zSj1I(39%EHKLMaNWLdO+0*cRTT@W#dbcLtWS1 +6jbOjh_sGe{I0qjlI2d^D(1B55reh8{RhHowW0|T}moveNjEkiF(-K;EP4$GX=b)o73zA?il*Foz$Ap +)?!BDg5vo4LQbZKvHFJE72ZfSI1UoLQY0{~D<0|XQR000O8`*~JVQbZKvHFJfVHWiD`ejZi^q!!QuM>lJ%Uz|8{;>7k{EKxs>I +G{z|QIuVt<%I+qmzprd#x5R1bf({yKW@n~mYCu1OYY*U>K&?mh<>R)uR +7IbtiuQ+FaD8hN9}X1H$gbasplw)z)YP)Fhq#tzk(xzHQa#Z}0#o}6X|;$))y?KYb;^E|m_EH}oK-ip +A372KkbzaXc*W`#BIfGm2T8$n+uz(iU^_mcKK-P)HxdBpO)kaOt5VO4w_5q)IriF~iOguDBz(CM^@tr +LV7(oGY5|BTfWGx1_+CVL0ev3=V`FNkA1Gq*#{>2<-Ahu<>%&&?N5O9KQH000080Q-4XQvd(}00IC20 +000004o3h0B~t=FJEbHbY*gGVQepQWpi(Ab#!TOZZB+QXJKP`FJE72ZfSI1UoLQY0{~D<0|XQR000O8 +`*~JV_%`ETzZ3ufh(`bbD*ylhaA|NaUukZ1WpZv|Y%g+Ub8l>QbZKvHFKlIJVPknOa%FRGY<6XGE^v9 +RJZq2JIFjG}E4U7FZ3DIjw~r5NocEBKB-j9%OfZ|-`H*dBXoiK;BQnaEjMO|Fjk3u!o(95Qt%|09jE8S2x4l!A?R<=wk){1W5bSF_VuGqe*s^+XVvl%>Neb +cTh@hB=-`P(2naT0Y8gA6`AV@+LM;D=zU6$RtC5}*J))^^RWqUcw!?!ddU5=yaV&x)qrSF-5gOULS_& +j=EHn*J!dvs8j{HZPlDR|$X1ITN1>(Q3pM>p09TY3(j!uoSR(6kR7vKxNpigA+TvGdfc+K?-FLGvCWj +^+153E?4X={EsYud^B0Ovg6j!Ye!p(@!94|4((fyD5 +zISJM_62^fE2zx*DLnkr-bGo#b4O4vwL|Qj=U|wxs^Gb=?OrF}N>si$t4zP*+sxE4_@0t^AT2(-ESfZ +F2E5)7zk$;XB0;+YdzLqTEyK9ywpoyO61}jSjK@TEB00@`2K^zY{mW|;OmE>tpGoQ0z +#GLk3ij~%UWJM@Ph{wSu4uwbiHzBi{=94Y*CZ)4@^`L|uez@3qOVGSe+N49z36$#d+ts9sa8Xa2#E6i +r9U>c*?l!zrtp>^v7Zc?M83zbojt*O{q!|GOf5c{?d^hZxf1?)l53h$R4S`v{a}~9h2K+C8&G;qG|k# +hw7e4u#=X}gN&JB4_zxZs$wST3Xh2&q6fMwgCo9=s@}3_?nLLO-AI0N30Amd7D&#+$AD&)ca+0bT4Fre)jO +T+w(d$9MYj6cX&VLKgOOi$y-Z$5wO>|r{#ptlgTaxVA3=su+-CFaN!2#dWvqhfQ{3P;xIh%n<7HG;v` +O-7du*x$>AA!r-5Ph7I{QiD6n}OM5xyFMI7$KvD3P&~*J!#U>|JroxldS1y(KREQeWN*_`P*Hi&(HSIjhR +AwO-?XwODAD+e~U`>UGEXf9-Bf1b=A}oD)m>uj%fJceNLkL2R6x4HtJFrXbB4t%L~Rk014=d8~npMKQ +J95Sm30A}pl0iXgJ2A0&DRuF&Lzz^26qHj9XP=eHVm(56k7_}`PVu8Y#MMFzkqg^d~Eh}LiCiy>%G=&EinKbp11b9VTh~;vM +ja0d(8QV1Z7UT590an4FuN+(di<=f4%SAKU8|{Nej?e)=6%JQz{<1UgY9MWh)ZBP)_Pk?9SjdsT1dl} +;v0=~Ee8n`u`I+qI3O9-n)JKJ&ves$6EfNRJvcUq`Y +kU%f3b(bx$_LOFy1e2oN7EtjD3VWc+y!>aY(vsc(U+gmF4J&%o7D}NESy+Y9O)zmY`Jtn`49EZ-#pO9 +mh=G$o{1@^Qbd62%=$lj0eeKHDH=BZF;a?C5Z6B_wf2+_jZ<*e#uQ%nPX_zi-V2^KFbP&FXCN5`z7-#tuQ1m2PV8tCL+T1LdV3N$Ok@#@X6LXm1eT#OPP=7mNNVIDxSh#)oB +%GcQcvzBnnq1~g+IFHB$pzCGC_NAn!B<(Nfd4&WV{8;)di*Hnd^zS#k>X!XsJzn9qLNt28m*)W79%6c +x~8F23ZPy~zE&m%8=YiMVqpxBQ{XV;cG}=P637$pof`=}QVA;P2=D@(s{#YL7T{ou9{mZ%!R!P%MpLf +H;f#)Lfe|-6ksb31<(Z5QH#pZ-0|E-GhZ@&nWl3# +ww+l`DL388XDfnK7!eTPQ@ideC&zq +QOla5wA9`$IG@fOu?0SlHvOybQ6!Rrjg8=iT3iHS@s(q3E)*`f8xCL~%Iqo0mR$%>;*-}tCqarfJ4Od +esb(bLx)Bc-aF5Oo5Og!7>rd@ymiWrD)%pre!`wFTN5?Y_ZgRT6ciiwuxQU7$6JSl%}-hIs;g!%)+=v +sgjmK0D)FEp48kv6l^&$;L#EY?Qr}Lk^Zy4&oS|j1G<&m+66VOLRUSF35bDz~Mv$1mucq&!}gamJ%Rn +zPl`A?!qu#s0FcM!0i}>$sIgi7+3QkTNmd|A|1`EF|nEQ$AvOu+5&9954ff4yV@~%;Jiyg#G*xJjmxN +xc$P4$u&YhJjHDhB)*i){j=_i#DRQu?@siK +#Mi8QJz|$p2SNWxxeU5;JFQc!L4gv=`&!Q>N0Ao6QkSj&|ynbPFw&>aU$UdjPDw(TlN~D!Ol?n8&8i6 +eH0PgoqeMXAl@DH;4on#SFjrr;(WYNAj*{$>=gy?vg13Bp{rwiwP-2B +~G=%oaOvKG1y)rCO|I%DiL%)vNxO;M=_A}A-Gg|BLjT1Hh6i$G39&_omyAAdG%!rJrj`|b^RBQiMpq^l3pRuV3 +$+ZS^EQBR3Dh9L@iy#z2Pdj{g>_z)2lo*&-oX-}CN4$4_|KhtYV^VCP!jnY-Aw6OT5?442M-(vx4+^b +^%#!|u2$q`YJJs4!uNpb3~hcO0C3H%|bQ<8}8RwVvpZht38Oeh*m;hWGWF;5%4qsxEWve{*sIQldi)H +0!pS#$3$|tqf;VoaUOq));Nr=MmL6CfRcO8&+ce4=LZPS`D3vHbGRm7HO>6?1VOx#&t@yGP5Y~qoHKXiYXESIe{-zy@{A99$$MA`IuQ +EtF*OuIy1)3=uT0&b?^|y?XG&HVG@CKpx*39U@054^e}(3A1^0tlR=I}4hYc!HFSn6W2Yh5qn)UF$ml +9!S{|dvrJjS8xe8pmV!J_B*jY(!8!H_^xC>je9gwdM5@D+DdDUX3`zXn;l;RVpL5DpTIx!d!K-Zkv9? +KkI#qT5)NF^YqGWL4tFrztvNn3^3V)LkJ&(RnHiv{T8N!H*zrE3B}<)v7t94HP#JlUy%1IfbFe&tE1t +5NwVBFX!%anw4Iw37IM?u3C+4d)uI2Z?R0Z695f-bWQDKO5mVtV*Zn&j$%&`2yxR2;sK;%gcd8}CH~%Y@*p8HASUU;vdquQh7kM8Z}qed3X;Plw%@siIMdTC6bQ{LSW6FU?=0Yy})LE;1ASsF~9W6Y$@L$cyFu?{il-$AVr>NFVj@Cj)5GvpFjK*sADrViP5%?{-Ef?dq4s06PC7AG;{vtX^(C1*q^mf8NL;W?mRcu!t(794ceNM7n`ljYrhpJ9LFFWjHKL$d8H_FHF=IIAC +uc1{1^vHJu{O*{LBNN5SORe0wc+_l@*d9lhkUjn}mt>W->6z@6j4~ZqL7F3@;)4Qm21uw~4OQgNa|cw +eVV~uHDrJj?{F%IZqiB+K|)f$}skmQ1z|^KSp(k1Tv(X5DGWbB20K^^+7<2LU$5pQx4ZX8q_>$avvYF +jkmVhk@;I#hq`n%%N1HFEFmDA1%>`t$A=mAMXLJXn+be_3%mqOVO~=z#)ZpB#e>T9cpqpK#vimHMbg) +OyuVuR9zzimZh2_Fr#^8P=CEV~l<$+m(Cb=x>X~;nb-NWd)u!)(xz@0+-)z8z;kylSW3ZEwufE~opY} +$lcd~aboS*LS+TLgj^W%S)=9!6K?Uith5~M}AXrYE3g~Yx>Gw}(eOVRGsdAk|k)uRi^L{Y)x=2u==@Q +q~ReFX<>(y(^*l?4x^dt&gyJ^(K3xzC(l&k&E}1y>m?kJG!7;Bn`YWxAA0vSGwjm|}8lo}k +m+j>V$nGg3*&6q*++rE42jmB^Pkb|N3e6HLD;}zJQDLrXBRE*71Hm+}I`z8Dif$2aZ#@uMOipvB6iCI +pibz^$34g>Fa-HU5C3c6@z+pn +<2iEyPet<0Nekk#RRRB_}rWK~?ZNz=|An6W3xdXMoH%u*jl*}tfvDMcyB$~GgQ@AZHM?~73BlU$kLT_ +v@@=EYG8>LhX0HaxOm^gcmN +}pKeZ-f4KTC33o%GSMnbuIF<_bvB{kkL{I7A&Xx+ZZvdp?WWCQbZKvHFLGsbZ)|p +DY-wUIUtei%X>?y-E^v7R08mQ<1QY-O00;p4c~(=y03zQ=1pokK6aWA#0001RX>c!JX>N37a&BR4FLG +sbZ)|mRX>V>Xa%FRGY<6XAX<{#8VRL0JaCy~O?QYvP6#cKKAXH$G0Zk8JAVYzo!2$&7kfz%Y!;l&2Dr +O^-3P~k%i@y6VKSWWs<0YGeVL^h}l?HIo!Y|{y;$+;BYfPV&^lM4-!-nNjFPn~9h( +>5nJePJ{-4&{XiZu+R8a#gj!VZpJz{jsbMSQuceL96?iO?6Kg3lqjwNHA#YWW7s-k85K3L=DoR}3=!b8sN$_fbgsRmLwl2uUSnsTncgDcjU~`u^8xCq~VTqIkL9ckGx!t&O8q2&9b +^U4AU}6k{TM)7(%p#KI^3T3YDG{rDaawPc5mMj|y7g@^VIg{>5CMCj@_3L%7hxt#--$NYK6H#QU?$f# +}lRjOi)F0_to}0vXIqS_BB=-t{br+@{}uEWZC(riIIWNINHKo)%vPqBtK+=$x2O^8-8PNK!+AW59* ++VhcwVqVmF5f_od{%#KuMjRN2KM7t-yTWgVQW#Q?6$OWj( +IDuse(U(Qsq?U^x+bZRGeWB>g63$~G`wL1w+=hrEndQ6*PGUu>{3nLD5!br?%{s_CCxqQ$Y*j-~o|CARACSyDat)@3n +F>r!K&biSTe4vadt9}KntZvwBvr^t5$y4L%@lD15{ejxs7GL2%Re%mmDX2jE@Z78fFVWKbKQjGk^Nyw +SWcq!{{Z8ODB3`~#ZXUlt4u`kgi`?PX@M#v+LneotbqLHvOZ_Lj^yzUjU>=|Yi6ZWoHB?+UPvqMSe4t +lov_a(ks+zEpWk2vCdF{7dFMN8m9U#kOqw;s4Vy#4kvkB@7ay)VVp +hDMsuLP$I;b+)Z3sVKg--44E<$#Y8nYi)AR<*NWB#?Rw)q;p_q4wRV2aS#^?oqYt}TFCD*o5lhw*tu2 +v2gydGJTp;kYN^HFA;Jgr%^Mqm-$;KOm^tt`x|)uJehC0^^O#PNw%E!aR2%#Ar(YGDnGCsT$a-qu#cM +UQ5bVd!ap`Y5H4S^BxZb*QVgO-LX?lGV8Vl4r1=Vjj*o5zsa-^uaZ2w&sw%2bQl`f)XtTd2bdbS- +Ni`v0_Hy$(n}zYjDP|HS;FJ^#9x*e?A`1$+*E?rGwL!JuZ^ya9UiSW!zEV}bHQuS-8c@xk0wX;TQbZKvHFLGsbZ)|pDY-wUIV_|M&X=Gt^ +WpgfYdF@>5Z`(!^|E|AcOCvA^u)HQkaR=S0Xqq|~<9dlNajqx~gQ3Nh#hM~nE@ekG^1t8A?3)k8N#s8 +6&L3K7AG0&N^Z3o|D*3vunwAySKCcP+l#9q{GUX^JPvC~bB4X;c19rEA^@SX0ybxBTiZo|nKcFd6f=3r-B1m7k?zb<{Lf6S05tHqFvDJuVk8A +Qvq!+aV%rHBOWGHWmCk7~bR;8>Brrr^d-1ew`L$3(CniE6xi&`v?3oG`QhE +$H;%%Y!+?R7(v4cgTEX)(xOOURDzQep5-l_amjqt`i>Utgj~4PTFlM5iCGbFt9#O05E +kI!_q9tkfyS7qG{AS{Beq1EtyG{%zVx+vMiNW%%M|x&t5i`6vWPHg|k>IH$3(aF_KF#dGOxfpO8z*t*?bNwEfglJ@f&FGvw)vVfZOzq^d81<(N8|lP +lSPpV!378-H5~s&H$5^#dfRThyp5O5Q)wMbmJ3q%a^XlDjj%MG8IlceH%-aRkcrdyyehfc(sg>plOY? +tlCt5anQ|O0U)Kfg^?=3EuJ_v|L&zK%Tx*EL#tpDQw}GroHwRjNGXc1>;-Qy-0|1{Bx&!2{f$QSEp(s +hbX$naf#zNbm^9j9~_K-FpLn1bHUF1BQn^nDLJ9<&Axger;sBQD8CN;0WdiZCLq-wL{a#3NQv#@S)*F +-0rg3EK))NOWLB?Hnt1B15Gy53liu#5Q}kvHqIs4mihNq)OOX9ZD^J7e*Cf8^9N2K4_`lY?6XsubAfR +z189J6;VljmiYDZ>I@pJ-i1q*l7Z2Bfbr2j(2mYxkMHJZ`9eEH|xALLMH@G9Q_&Y|3mA%RL^8sKQn-T +QV#qy>p{X#(bF9WG_J2MfeBG>GCLO+D$V;0DK*w7g_*nQ08NczW} +k}a0aWVC42LxPw&l}Sene~3UE-vYcMlt7A!Rw8#vl<(OLxFrVZo=6f`b4V)InuP?cKI7+zlIO;yVL1l +VCE=b2z_1yk6Pw~H09h3&ZpuQ5Y-j0|Jij--b9WyGhbF?R)@i^&gLSHK@2X0(;h5E(3?;c1Y*N)K20}2@SjCEModW>7oW@%7Cp$uuR8El?eFJ +-Znu2dulQ8H(Cq(nadKtlsW#oETjmPW=cl-S=H07Z%Xh*1Ye8vKmi3he1-6hzhMvyvL7Ab4>KJ~H&X(s|P`@9K9C@O#;KIN(c +sO-W78C&v76*{z@U@qciBkjpDYDu>rx!!3tb9218A%8{cEQ;#B`4@oa#71(Ms!>TFKzNo6g68EbJG*lDSi#U} +7hn;Sz(2-Itso3hu;h6I`=;9&m*&4=n$baTI~)L|!ChaPnL-X8UEcC0$#@Oi!IYc}6efgDBJlu9SSyPI+0{jDrUCr*DTAvCh@MFp3H6BB$LmRH1a#xFLPVend@qo9eI%IW1^KM+CW5{! +MMt`CKZVg4X5Z-)LImxP6PC1Rl+8rrFKS+6V2k#Xx}zJvbh{ovvh%DTRVA)vSc@$RIDv~zUSwj92QW3EulES!W5b +_4*Z-;L_o@0jqPXw5gYQI{-?@r=z^kj?Mvcu)^{^sal^uOr)Vp!&)YM5uI|rZ+`0koJa=W}IEgq|Vt2 +EDC$(asavfgzop3$vdACrvL#La*MeHFug+6eP*6}Ar$fXdngn7vg6aSjNxOu3bmr) +`Z*;A#Z7(Lq#4H|J?!13BbB(y>qL>5vp`)mTE>FzVfR(uDCem2b{6^2I|Easka?^#`6Dg~T$A75Wr0g +|Zg{<)>jDW>-D>8qe0N~DKqx8i3QRrFWy^Mt)P#55C45-9-(_v6_&&18Z&PCLG3%AZ`=lLwV#)i5*(0 +eZd~B(G*F_x*O~{%Czd{WOG!;_g +tp#x!V5kl3KAZPFyf;(6`4I9sMN5xW1lLbHmQ_(yH}v#A-GWXNm4`8y1|3?;Hf8?vhBN8&)#W7`vbU+ +H@*&CwAKtjHYqTO^nHfTFi}Rk;p73P<_9a?7fE+Wd2kD7{!c+{r}RoQ`_r^D@bTxnvl%**xu*%TL%F!QqIy@ +vEA71D@+wT@UL-$_1!zed1}uo!!`^L9aQp7;_T9N#vXiueD2RWma*0b6TT{8jCqilDYJQ9V1sokILFc +#f|NDd1w2avXzfY~{h_)ZdO)I$v`CA}tsaqOC(>H^#wR-7ZrDYv5_k^#Jl{-P=9q4**&vP_fwZF~_hn +e)xbI&NAR@=kl7PR3Vk6LJ%9@QyYIn!odH7(CVUw1n;F_>W(n01#FgBWoq?J0H9bp7mQj}Oggf!<@Yd +(B>KS;}&d3{X9hMw8LI(w2gI~!K6Nepxi0QX40s)A!ba^N +?PgL+tPY)dv%F7*u_51Dx|4_1sJS8B;tX0~uzf>nusH@_ev?<}N`}cFf1Hm2$+D|$=jYuu>K71e!#~w +lvAf%5ihD<(^4ip?P7l>B^iRL#FTd}6( +;*=Y9ok*4I;6|4x8Cpmoog}rcO}m7r%x=7&K@W=}t)}0G)IC0*H1&ex#u!)KRYk(5V9B79Tr +qWFwVnp873g9`l&=aD_iFlEY&~jegp*$&2=Lm{wgNl#4x{Oq*gYtIRFTjZK`q*_4Zi*Kw( +c!278$xjS1t@Dq@Ge33{Tg!fyo{SQz}0|XQR000O8`*~JVWnpN=CK~_%wrBtVE& +u=kaA|NaUukZ1WpZv|Y%g+Ub8l>QbZKvHFLGsbZ)|pDY-wUIW?^G=Z*qAqaCz-LYmei`k>BT6^l{-ED +%NoA00-O&(B+)Beh2KP^3aqGpo$af4{2gSM`I^>?JthfD>YSNH(jhtLt6WOtsy$ +-5{22GssUvUEQ2j>dt!KEQhwOdvl|1Z*QyS_H5m?TQMAV@VC&9@-qRy%b!2IUc7qy`qg*u-v41rx4x~ +GgZk@D*Zxg5AEmy0H^{EMsU^D_+D=`4U-f#_AIlw1qb}Z;Te-U2@61a-?1rjs%3580lz-jJW+}~;hQP +*O9(MB2rJiG1;vADRwVGYED;D>%S+!lUE0=dAF0|+XWYx*7Y=+*f^j&jb*464q_vjdeekYgJx{{rJ|D +(AB7#@3bKUB4S>3!R6Vb;IN71Ce|$Tw|ME$P;xfm<=8PWEkmFJV6okRS2R9!d5NZuETx)aT2ufB`xnX +R$1o8)@Epg%_`zp*u{)Pa7#~x{-QKep*V}k>2NuZ(%pP>V{^1-wtoueY4WL-t)V`-78!R- +#k4Y#V;b#hmB(lLqU>ksNla+M$%1=4=#_MjsicUNlb7Vvz5uPqSuMcJ$t0Q9e8kZ`nq(`Oyc2@EMMyc +@Z^gI7ot7YdOQKrTQl$&p>{Ec+KXuqRHQ!zn&=?R_<}yCjvc^6d`DcT?5Xa99lGtzAFvt>v+C0GqPkN +c+-Tx*8vCl>(ooODdq?O<=BJU)I27=GAWBGyrL_NB&*7=UNeXqG9%1uF!QCNaouUFN8b|Sp}2`2A@nEsa{Qv_bH&>=(G+#nRo@tW&H}OD*DMkoK} +*`YG@aC<}YzU9)aAgp~2fHSW8*T2@2E);_Y)V{1C1C!pAXANzHMl=7Z}fveWOVV=<|2_p`SKeWrXK2t +JQmwk`hyar^4SLME!xfwB0F!0BgIU8Sq9gZ3WM_P)Vot>dy0UCC09E<|^`+K^VFUYzr7O;$0FhE=IJb +RT*&d3nKO>{A?aO;67uK}Dv9QbPqQut%ra!U8Pb0hRJ_)g8p`M6>)Tq|l=c84J}BV2MRP{V*o9f_s{q +qO29X^27YO^I6bpr!{~AQm9G)v#FPmQcN{*He3?jL^(CKkl(s;@vEzzumBHc_$Z4MbD0CKlc`ylm$5j +GwX9*IbYz~4Ta?|hy;B#z~CG39@!e&ZYg0}jX?Ogx^3FdqKCvwKG&PoFLuOZ;i%mK93WCr0BPMaglWg +GNM992aeWP(^S*7gCYXqef2Yftw*iH*{&T$p{!>GCcx?U*UT3s}LF_6RrxxS%4GbR9TzRm2Cm5gJK6owt;4FCkW?$8-#k#EOJq(&DxAZDorIOFy8G&H34Jw2rpzqM%EA(m2aB6Kw7V +-9XU@y3R9S%Kc|aZ+y-{_sg*LmsjSGrAd#1Qw(RpluPC0@$_-rZ9eZpVuuyYn%Eoe^b`I^wD$F2De(y +o`<@o*MC6B0v`5ou=+iajT|A6&OzcsLE^ZhK<}k&Ck(e}PC}Dwh7U(?A0;g4|mu7*aIV%$goLe`E@8V7#V5 +xbv$`Sg+z;hHHjNjnib4KHsX1KBwTsz*Tm|c7)52QZF0S8zUaxrdBI8LGfFqe_i(fsco1RLVOkyS&v8 +}pd(*({E}*~(MfMw%HH)TjLazPcp3+OpozHE@nF3YB7*jZf_>W`+{3)YkolQSs$DnD~jSUS$>ubhc@t|u*&llqIhi`u +XHw2R*qEwqN?AAFcnhN*}ojFkrxH$tjqm)vD+YFj@>=a1huR&1FTl9RH~ey76Gtt+vXe|wSahs;)OA5c?(fjkzy`1a~|Gi2Me=8A6c_9a&MYbUSH&MMB +>g_ZyeAS&WWNE`Hy)L8vbgJaU(>Gc#n?YVn03#S$U5C8Xuy$cz(~btKn}F-PKAgKbGK4CU+;I>_vk1_ +*2<~;-Ty?v6W|_?w4h^LLT{|FW{d{IYOX6VvO*YVrRQMGWiPUG=4wf5ICcJlo5`2n@Rpm#x$SHn<*f6 +HRO5Zwk-COzW8K#sUJEX;bFAimfp-v$pOw^9pWmfXcwnbne~Dq^*I0j-;U +)ll=!#u2zK6fUz~V+orggwA&_+lYF1KX-6F9Wq2O}>ZaO9#&2i%B9R*40-|O_qHR0k$*k@O&yVmzWpCRu~6THBW4Dp*Mii>kr~zz +WP@e4x&;~v4~$$%{k~o1-U1DMhsEuMlX4?1CtVnKQ1$|>3SuVE`<-(;bfdv8oDaye^*{F#$2wfh*GB^@GfQA6TwVjH-#g{V>53mTU3}3)fD!H;7s;n5?d`}frKr+ +V@Taz)Zp0wv;(SwvOkF1uH +0=R8X^eG-&l_xASUQ@f4zlq2vE0ovX)?s`uSzImy_qOGtRM`&PSLZ-ktk6#mPX}bO4EX%F^XbwqtBf=1uyX`F1B8W +7PQ~jj`MZW5&yHek)YcWsXX)BDPy%b;%Zu6o7=zQK}wg-H-TdjZxdLX2D8RBa*aYck$2={Tm|;++cq>CE16j|Nw;<{e57n>{ZCBk +^5Mq)^#9$A_m~8Z@G@2rZTMXICKpM4sd9n_VPbO)Na5R$ei&*BDHrm=?v(&?p_~CeiZmyD&W^@7&+4M +MB;&?OhjxqGE-OBtTRb|N#yG}~#5~X4C?w^rA91|bk+UOGpsv#$7LYWP9RvlY`E17)X7pd##%k9xzlpym`y6@Irp2MwHm>h-{YOtZJRZ8J9V6=w-psS1?R$dla +PzT!WDrrV{+=n`Kz?16p_-IQI{D5-=aS?Y?bhj6~!&O0$V~Q!f-=$-5owxc7ZDa_6)5N>lq$gsPP`j~h`X;@RvE{_lIR)*jRtMc{omT{_$N=6^J_q@13ihyCZd{(;cYjr^s%r7p-2) +zEuq=ESY+=fCxVD&4&0RscNIJe#22bGwxNl&gm<>d0(qV5_qe5_s&paPG%J^VSttScNNb<0zMvCiOzZEZ23Nc#kPtAZkv7B6)V9S>QR(ik>z(gK)=TKa%{mK&hw5 +^Fe95NniX`JXI8B;bYl{LlRiR?;adBNt9Se)b +Dk*B|h!~CM)3uRe}M@4#cw0!x<)FGKBgo(3OpOF?)v@aMf!u6B?p8|Drz!lZxdIRdhbI`%bX-^en+rr +t~}VNW(rB)1(R_eA(2In0h01*h_noe4CyVgxZPRt2$ruIRU7J;x9LfK6yFVAL`jU|It`jX>0Gs7oaOj +1&KJEOb41?bGnxV3-VEyI^6`e>!CatJ!>i>rTEj2-+QeQx|NcSQr6g%7{Fsi>k*{txof^&URLF +{&FK^1_9qjD#PUO6KR6*O8hZ|*u0Cm_U)vxI_lWfWX2uxH!)FuM888zgW0kG6*b#x&bJg^FD4^^?1DC +)$Gy$hNo*;Zv4`#L3uV@E6VW8CUkjvLdo26kCP6>D0Jy;RZrqH9|(!U{7Sf(D$3yPZxRJs8POBA6kdB +F4{AUR_Vc;}iZ9^tw!KyNhi0Tg-sS+~F9gawFF)8ItfZUxQLp9ZTe-l}qw=$&=*4Q^Id(VcWLY;B)NA +c%WDrZW=(klBbv|I)s+%my!TS3|^wlsCHtcR>-@CR4kjh6p~daq!F^{<cw^JxRYr-~!{>QQ9)%*S#(;a?%siE%O_E$En$k%oMUoK)F>< +BOZ)%%D&ZV`Y+HZAK4Nfng*aLs`l>v)eQ6+qQ^O~2P(HSytlT5FOdBbVMQwlWDi!PbWj++yy)j6!_6n +=CNFENI-#GFD5L!Pm0G;2@|YB^h-hhRrUj<&jpI?+l-2 +yBiBds)ZRWRClQpBbVYJHMQC-92YNIK4uJFv_Lz_BJ$|80~L&!@(Z>zECH>qW~Uu)sa7WZqmm<-!ovN +Z!_?{OrJ()8-dwgc8q<2iRVKoc`YQ=KVp +}!fEmlia48XM0YA-QLK}4}>+h)ugrG#Kpp3S_`47%`>U_ty*pHm#Gnol~7G +$y&Y8P6ooy>Iz&iyepqHgqnA76Na6x;t%te69~qrzQ^febY&uoyQ%M7}L@33l{#xo3cdsEf|zdcc?j#g7I;}fB +)%Rpfi-T0V+s9^D-!pIZUhXxCri4F{bpIUL}8SWQufIlVhE(+FCqVfhFN1;_vV_hA8S0l;u^fiN%6tV +aepv|{1ugX6pu`I&-dS1EU1mmu2GUeH4i;d?@%rI2IGW{d{ +B6J+XBdC{iX!{nqfDy_&uPft`YeM()kZ<$Ft^ujs64{&jj|SRa9a$C))ks5p(PlTKR;KHnhg|E%eF*M +F+aHM|#Xf97(LRpRiccH~}-=Zf>6qw^oCytkjs0iNm~g)*rBmbl7lq1!#|iV^=;b^k)$z1s>sG@@AXqB0i +Ft|p#{`CNP*bQ9T=J9F~x)X1x^u6eNJDkziBU?$xmEgl_A^$9MD)&8k_awo +@1=E9_y>4W~@(xYDBlL;UnEoK1kDCgLeT5_u#2idpW;_Om-gRzIO_LU>#W`1{E!h0ea^;xiHrM7<55xM#(-jp@=;^(N2@oOAW-i8#a2KJ;D``j@;@;#^y*j7hwa!ee#3|Mx(6Ai3yFgM`RBc%moHC +`MoO3-P7@G!Xt?Hp3NmCf?!|Jj4q7URZQzS8 +H&tSwPD0dk;`gb!>lf|!X)#T<~%GhUEx6Vc?EN0VC&t+b+XZSQ^pHs(s`|kbaO#IyLsWSz=IEatYmV3 +<#Mrvy;Ra3ucth~-uttCcBH#wno*)fpaGXr4sQYc;Y<9YTpwIK=bp9?p%E=gCT?ujMb;3J>(W8aE +g%$G-oL4DXGfW+51)%G!ZNB(49IvirhzXkp*E3_nnf~*Gb(Bjb&Vpx$-oFQv?}LXPx?NBJXAlU5c5bf +i~WuW;&keiTezY=VN;nb{&i*g$$%?F8=A2CrYip@{+$P2UN-s^)?;6|5Bo;(B)=RJM!TamW~SHG#ypG-!lYPrvIQ{6`PL!MbT)$>w89!98gtY7d4jQS +s{UN!tshN+|u_I9nE7hzS`IUPo$!**uh7$o4(me$IX3qb##J|TeT|LL2*y=~8<$$kT3@*82I;&RaQ-0 +-;0&PH(@eM>-*&Ah>n*=aA>@N~)#rYx}b4d#~n`LTb;WI+kzNF6G*t;c*IhK0W)-_cey?St74RPVSSU +h5VkmfDmQ!IGQu4Xgvd)QBLJ(@EBDBkLVlUzgC4_(`Kr!-5Pmo)I*4Urer&CWSxZZ^%eJI7N9zCmMq^ +PsxUJ2-L9I#n&+7mm(u|lEGiv3IV_IRpF;U>O%xdn}Pip)z^YH;It)(XC@X%w>_w3;lIDc#St0PViHfq-wl+C2z83y +3OuVHOps=F53fs;8FVa_jjrN^d?C^C`=iub8j~tcT4Ab$bz@6aWAK2mt$eR#Pd}T +EDyo002oA001`t003}la4%nJZggdGZeeUMa%FRGY;|;LZ*DJgWpi(Ac4cg7VlQTIb#7!|V_|M&X=Gt^ +WpgfYdF5D5Z`(!?z4KQrltZLIArR!!3bE0|O%J_suRpCCAaJND*B +mt%kGn@#f8pCX>l;PAcgnS>d!$=|`z{A~Tda5^0%>t+djK3?F5VGb;*Rmw6mTXDlPjn_kL)=|zRK`1) +*1X7@~F+BzuK6&XCG%UGN{AE?eXAxuAPz(#-kKm3CA*nJPTEs-?>Lf{o5=#by}25mwhGqj +@laj4XB#iE?S3k*k{T5gT$TtVzR#aB@wC3T+5Z))|z;AtPWG-Zp8O5r^THsEUX!cWjp6XE-)H0b^K&x +J!2;*LUR{Hi$bCx`dHV*V%d)E4NM^evQ(V)v9&`nUW|E2AA523F}Zg&4W^HR6Y@RYbraPERRpb{#kZN +7m?NQiVcgS$z&2ma#LyJL{XQS(^?^(9GpxmMHvUd#_1{(jnuF7S55FU2)SLw4mQ7C{_){$a`xlfv!Bl +2|1cM?%iUa@!R2(N5an~n$#$tq5^ROZ^mRhr$VH*9BXd~;9oCbjf?IBT +AZS2${aKWjr6MdxKs_ucpNve4orTzECq(%X4;wd@VN>YiLKmk3lbc5x92yxNF#vbdo=8z_iKSqe`)5$ +^z+Om8ZHdByd!TZj(;3tU_UbiItf1weoSPP45m?^kU(*jGy=loQ_QYVQ)6O0A88Us>o4t5vI +;vpVoL{1-XoYl$=?0Z33}q-SKoxMtrEb>xCB!?aNK9T?hjKfe%!QHw--|hlRlEwtciJl5RrJ;vZ5HC` +>o;dVqtc!Vz4{Yoa-{{wS2i=Ua-5lwW0x1-Uo6Bgw9#9X+#2#LrD0!M0A(UKy7Itmg8DN4rVV2J*_=g +Dlv5!R2~7zThnq^^#x6PpUmc=qITO!Mdx1XfAu_15_BT%$*Vpfu2Rku)eGN`z$xHS{wzBwKTU4;GX|C +~56VwJMRiM#)hv-^|WKbUW;J03%&%R`P8E{NP93bhnNdqOIEhV(c!GFvMjkW&0DLb2Z(q3@2j;dyfs9 +AE;S-1)IZrgHVGT~c7Xyt6(U$DBLP`-@BNZgq8I +(wt)4L%p}-40LVMt4S65oeeREWFd`pp@|I#gGYht_3VL_+kHkAXyR5@)zHNbWA=dx89_YYVC?lVu>ZT +)*NFe~RTlxmLB{=$YoNn5aM^%%uOeFR9a8KNwW(pft~Mp*$Ke*lq0m^525#e-pK}y#(1QIi-1zASMx* +PSwJ$B7Tq`t};-Jnr4V)^TbXlGIL=n?H=4c+dd9rAR5)73P#Mt0w*f)n^i=&;79t)IoN9GG|Lr=tc@yVd^7(x!d&Rb$}f$ieq)d$oePET4R#$LNF +W?D#Px&BPLKq(mAx}e(MGbbd?#6*Uta +WCLM@3D$GTbpbX>HYqk*m@~WP1#zgb?aOV^cQwyKoo$<1ISGysPHwmcvCW2kFwHWuwn+h4%d4x{9?wA +r1GVd>OdF~)bG2Whd6(n~GjQvMou}dI8wO6V$JWx%?8C@}3g06&US+Uw-}V*nKlmPlrXpREOuHmnQpL +Rcc0(>{TFPj7d#t}K`^N`8>Vb8ymuosiGLTE-br6tgv8`$j^(&;4EDI$m!#Abw7 +5!-&(&ckc!JX>N37a& +BR4FLGsbZ)|mRX>V>Xa%FRGY<6XAX<{#Ma&LBNWMy(LaCxO!+j8r+6@AxNV0a!Pr4pN_(-%#Z);Vz+P +nr|gai-09JQO5C5^9QI0m?ec*LQ6IBmhcslGZPlNMPSDYj4SYqjbyGs;b1gm8v!)=^pKQyBlq^+Ozi5 +$a>5C^T(g=Z;SWe+`j+z?!(`v>?^r$r|hm3ny)J1Keb9v*>}95dsFs<_|HMqYrOoi28WTO+Q=p^UPWC +hooiuK^(rd4${Vd-Whu1j_}#2btNFUU_b>eQe8r9E&b;S!DNEjh#e1lL^Rk9i4`3{{*o(ULm)70Ep8U +h@KYqM>xc#BH|LO6Y?>`iGU)_Fqy!-m@_Q7A-fVa}8?)=y8>u4l5sVU|ohQHsW+PxMVp~*DAc-MancO +%{I>W$KHm$fYs3Us4aY;onzQ}lQmO!@3+6`kF`BzrH;+;Qdhl;PO(NK{75e-)k4=pFl@$T1ML-3g{eW +yLc)*|3#pk3zT?of)V0n|IHYfipb-ncm6vnGW)?b;XSlCLg`SvB!uOlZJ6t;@r9=3s?5Mvzyv@c{*n} +GC4xsFs&+LiR-OmCEmhE_V~^H9sct+z9&95pzql~AiwNcUbCapFYGAW9TJh4x1u^R?k&W!NQKPml=Ui +Rq_2Xf8wkTq%&k#IAD?DD6eNQzhBGW)0V^TpucqC|H7RSs +<3zP-1BXTuM_Ic+YI4D2vr;NdkgI91EVD3!LRvv_&ZzvR5*-^6A(Y(l5IKEWFji#L8=>(`kPfi&@Gsrzj@z7cLV90FWij%R_=#Rn}E?2c&^Gg0KtTjkuR`W +g8jhL<6)=G;cYUb>pJK@vXF^JeMm$O-|W;kz_%aqii*=k;jf`o0w_B6L4M)zmozCXmeLjw{xTu^)qgD +4_97|~iAr26pn3*QQ-k1J4}sJ#`;p=m1ONLHu%0WV7!5^4K;FGSwC&3OW;&x%6UvMq{npd+fywEyN4X +5Mws@Fwve;fW`FjXU{sID66PE2XM%4YD#hDpt5OdXb9kK}s30g0^6nYRvwiq +3(9p)X<^zftJKk#Oz%@P{{woeaS(40|M<&V5XeKf+-h}bP~M`_y +*&^+)MXQqe}BN8ny>{GGD=8?z%6t{EUP1IO1+vXjTiH9^HUF_8Gt^o`-W>XB-W1@vdlr4Y|2Ew%~_*t +1wh^8bx)8H{;T9#w(Q4;zvmI7yA44eUQNB09n08?#035azycGQi=32cQCm#JfUp6D6MD^Ju~;sd|Je1 +LAv<&h0#OzzGsKrxALX9DX0`-m9zff^o9G;Nbh0hFKm3 +*vVJ|ek*+vVIB}512V`zaD5HFf#EiTv+lgwegBlNXge$m_X&(}=qMCDbp;}}|~?^Z4`Z0w=r*6R}+1J +E@Fb!m0jZuv@9vOP`mIJ^4Kl~ONXRqRIlgBc`bq+U>2hMvuwtnd +Pd=3X8=0!6d8u~_SboLCV5Tb^hXMUA&_fK%<}%RJw%VJ%vKHrJ$;y5r$uKI!u`?bkhgw_TSV9$xl^mw +HuV0O%OxB`qK#GaH2)m!ZNaj|BC||YV~av(gCNzRrjWu#LLKAiK!2hI%1M;yyKUi$jPG=IN6~lIOYJ) +LU{6teq-l?U>#f#GzsU-8yN;Y3BYkFCpS-e;fXpPi8-lNQtd%GE +ecE~cS?8@2&BaY!>+j~z(0LuaDXg#C{FA(A63BCe8)k6+L}5|W@=ay$6`-2RvRN{KuU1gAsTM1SwPfz +8bBPnf=HJI1RT<%BGqE;*=3V*FjdkT=k9UEPUjQOu%}DR#t#>c~&1K0x)YE6TQ0y((FqLaCBVqPV77AltcXJjMNQ~_VEn?Y@JBdz2dmWo`t)Mb(Q_@WY#UCWLaTY82NPvuX1;V%`MJs4W$>GS2{O +BsZG0>7#@&(v}V+h@e8SQ>H>i7iVp0iZTN>H5I5QP0RxAe%oSTN_Iwo5Cq-qn)K7o@SO6&4~6<1#b7w +pkpB9_HB;vH+P}-GgSkNpKr24kh-D-AD9;GeiRn-$&$}x%^U%^|KtDO>ILOx7Z{ctNa2}R`9bQ#;pWp +9NKo37KFJ5>$^%LO)+?r6q?~MFo{c-#olQvpg0>O{4_Zb3_d-(=vwjlbJu&%pAUqazEQ8!cY~^XPT4i +>0l{FF{&_UE78Y#v6TF{Z2LquUk;0-PbQrmbqmOXQa6d5Y+t&DJr;^@yRz5L2A3yi+F1PcK_-Y)^4F>vqVSajM`J>*wTuY<{ +pF()zX}OdFYC$TO9V)#TG=IvCp^x +L#fkz}v;@E7ij&(FH5H3-_LNQHz=*F|{??@bM~qlRc9zH? +EOXT3_B4)yJ6@SzTiCs^`v}9-wS7jk#Ys0BaSNBfVIN{MtzUbD*TFcNO!f)sO1M2 +WEat5njFtX4vV-;le*bBc$2ai5jFM}%@7YvyjdHl4b-{IvHp1OD`Dl@xNT?;d*l2C+e@=nj_C&6dHBD +}HHxtvZPHVU-q1EvW6`_+&+AW3_gcbnyXz$X&9R7+7_DswH(P1e2lUvMK#1b~?5@k0PF<~;H%+m25z&E46((jyd?S49qtv^ +JlS9{L?vb+xMB6SZbHU!O3^Nho=W~5WZ;CLQ;+xD4XT|ngKjF?}K*k6R-EVoO9KQH00 +0080Q-4XQ=5KpUlRiW0Nx1z051Rl0B~t=FJEbHbY*gGVQepQWpi(Ab#!TOZZC3Wb8l>RWo&6;FLGsYZ +*p{HaxQRr}0a*033dzi&Bu>Fzkb*9cbEdwr4QRa40r}AGACQGCDGQ~NeY=A4U|A7eHjCSen^`3cca_0`Z9ND@PJr0BZYP +%tW(?RMHwj#j$>9_(p3m#kqHpCff8EXjB;E9ghC%YGCco_wLRVmSaJq)Gp`W<|SpNDI0DY*;+GwST2g +ggJA)1&sNR?58hBl;AF%NNDa}Dgx_zb1W^wPV!{`@t96YB*XSjfj$2wIMtYdv-1PsZ#eZmRmucxm2xo +_7x24d4o#uO5vSxX_&tC#tR+RWo&6;FLGsZb!l>CZDnqBb1rasUuq-2Vc?Yt~1Fkl7@%Ovl)s2Va>Fp!F{vaX~iANMOCLC=cQl8t8E34N$!Njdup`3G7;Ofo7e!7{s)3S63b +P)ul$g7QSdt@+}D(vKNhd3+V1Mc@6*X>++s|YJaLT`qx@kHA +ua2SHOkOkhRh2uQ5!g{zgCG4iNx3(Uq5Dm=trO*U%e6@CR3zI`HSfz{FR7%EX~*0}oGbPj83O++{BQq +#$J_A4!!V)Pb;;2fKhWS+&T^#Am9XQfXV)+u +xLq7!nIm2(w!piUIMv;qm7v!h2pMxVAI&5&G8`w +%dY5N0|(&2ket3~#&8xJxkdNN^ny!Eyccg7)ERhD$Fpr#UwD<}RuAdu98H!=AwCP|XS7xA@B^9GM`i5(<=NHjpaL;7lg}0FATAg4*H;nwkpwmZ0Y0_sxI7i9x2)DD@kE5))!ne +eIaY6zdB`M1@V=W5QL}1%d{dW(W=-TwGxb@71S6E~Q0I=^DYA81cY((+ee(Qcdp1$D0KVCC0OnhDk9- +3XIJ7B;k3=z%0lBaPF`P4zgY=LnHwnKn(<-+HwiVK&5MEugEVdE7uNt1)L?#8}czdkGVS#vZ?q15|rU +7lx$0V>IvYn!e%tvuZloz<&buMPvKE`@ERRkqw4k?F(;g@agh9+Neh2-N-*W)OL^ +wUj`oT9V7Q6mzmk1D+jk1{2$2OkJ6FgKZ|w$7G1#1toBUO)oW_QWei=R5BHIOkY`Qs7NasgMX70<0c2 +QjPh`q%53wU*Bj_Dv+}tvHDOsAI>3Fg|>E4O#i^TV($0^A0zd?cDi;6njbIKLWz5!560|XQR000O8`* +~JV8W&e%>I(n>Y$X5yF8}}laA|NaUukZ1WpZv|Y%g+Ub8l>QbZKvHFLGsbZ)|pDY-wUIa%FRGY<6XGE +^v9ZT5XTpHWL2sU%@&!#Ky^L+K+%Kh-Ii~_pRsSPFS5k?H&Q(C3swk322ShTjKP?^`}c)N2RAQdF(dE+QLosh +H(@jY()WxFONWhUqgS-8c}+FT$eDTbErn2(CqMQl`#78NZ5)tmv)EIRbzh|lVBa@-06l!486mE{7@F? +_wX#rMh=TjsY=-m!1}Qv`rXX>X+bQ@yO~D1j7P5&J;-fGG_pJInT +&kw9RS7x2u-1n$_5%#YzF*)%LW)&iPlnhnj6k39jV2EUky|qgb=eh9?&va-9l|ahMIjbsTzQ_anw~X~ +KIw5aHbz;rHXvBRw!5S0#kW`UzBzvwFo!_>-mL8kX1qagUvR*9ytXnswlB)>dG#3q#zxtiI4Mtq!W}P +I@oe>8P!c6%KM@rrmwFleA{uai=w6BQW);MC5^OK##2m3}te~9YQDlzO=_r1<8Bwb7W-uN!z6i*(NGC+2^JinQ;6W)b(CHQKU*yZt(E}+x_nrKsc +?dQE4T<(iTznYtpSFF5LU&YS01z`krO(sxa8jn@F#97lKwpl7HRCj6lfLb7k8vU2N`yLbA40ZzJB-X+ +rPcOEnmHU`QgnRX+@}mA)rN?wTg7}xE^F4Jm1+JU;tS^=SSM%2{V774aDJM85FruKkC0OES6Jek-;a}y96VYPxjy~p-&Gx?b? +^hU1sqHpT4`Y^Q?&|Vx$4d@tca9VbzH3ePFz||<;SRK?yAAqJq#~)Ykz3da(#TLw6TvKE+K;&dIzU#8$l{limakS){-tWLq%ur~!orEAi7S3?WguxV4|A% +cW(Ibo+-X2U~CVS90TmB#H-m-z2`t7&pI$ux(moq$hzMfqZ`3Fwa6>aRf0ButXA9Lr%!H+$O=U7Ld=N +kW+|4D`9Hh_RUa=jl}%_9*q9=6Rd%X9TZZS~B_2bGFQaJ6YdHhn@^1P5Wr*vXxrXT8D52#j+Y_Mc_4H +Ju0W6B%C_ssvzn6wDI>^uNL9wBWm}kU?#c2GNw?2ZVcsooIW2BmV_R0{|3Q8AO}4p=JPhwW}ZBU0q+p +357~vVL(AfKD4|NHA@@HrQ+Hoxf^rhBj2Cn^Qd;PjWjIhsLfa*Knk#@8o)4hYM-Ck>*x-Li- +8ntVZAd?UXG@)sW4U)-0kmxNJGzMw6?F8|TqT5F+B8-_&Sj7z>)JAOTvvBU&+q&mH8RIC{NdKte?%i= +7>q=%!z1^$!nw%VEjx!pqPfB-w2{{;}h$c{g-_J>5oorjX+oJT651lTRW&C3|TuA0ngU7Op$Ix5~UW| +mRusZ9Ov=ESAmr&|^EV91#bl5X-~;=GJHYZFUMe5$X2Th`!!Y`Rv0OxBqy$m`y08wCu)s1G=oKJt&)kZLF=2C^gG`9C +J!?A1@U7}$mKlwV?2f-iA`CjUh>PjUH_Grrqvj6TGFVCvHK#4Kiw8>h9;o#E}Wg=A4ya0=H&!`el(s5|s6$)Cx0$ +-4z*50PB6BPwGb7fDJ9!y`8n7L&8_ft`xseP<5jpgC<8r4wKlDV2MF=(bt7|CiJI5F}kzU@}kL7dbMy +Glx%%z(Kgx=zA^D6bJ57gxN9u+qfH=_D7@$O1U?$_s +{I(->?x1Msthx(f|z9tDU7xSL`O6vh>dzUB@CFF7-N#poj4$iuKk$Rj9R$OUnQu?g}vnq&jGD-l +&M7kog7~Q)NWNcTU+V2o9pL5?~konI49RA9JW@0lQU~;AU4RR__8y>`;V4h!4l84A#u6wS%DKdz5(B?VNu}L{#dx8lfpRftC#YlQkkaIY60ium!Hk*JaYe< +OSjM)gi2X&Oudvd>qgHc0+7$iekdI5HLthU|18L^Dz1D^`WA$EzOtD#DlhZZnYVu{siOE+LuXL-DgGL +tg$VwC7Q@2~@1aHO9X7er+Ug$1gxbn8ANAJGvX5gS9+tSOZma;RO}m={%0U= +lLpQ8rLsmuRfZ!~RB292Q5W7XeNHeJ0#>IvaS0tOkgCT^P6NY_A1_qV%a0#(mp<6|s~_Ti?_v%`_EhP +LS$F@S7*Kajj_kOxztn+hkh7KLIE*u_ti)KTGBVamz$-P_&0bLfKLTCjy^UhqzA;1#8AZjMW7H_lf0e +XApWVH1QY-O00;p4c~(;Z00002000000000V0001RX>c!JX>N37a&BR4FL +iWjY;!MPUukY>bYEXCaCrj&P)h>@6aWAK2mt$eR#QrNIX~6`008#`000{R003}la4%nJZggdGZeeUMb +#!TLb1z?PZ)YxWd2N!xPUA2ThVOogQTCElsU;3vD(y-O6oiC|1X`|X+Dubx9XqleP23;XaZ+@pcLtgVHEHsFp3+jz_0(d@LvoO51mp+k4n4QEU!;i^$ +EFQ6kO*dgp|DY2m)$sp9~1e(MQbh;TNWpu~dg+~d7x9FZ?CYEIx33t6VZel$c0(7UI_AvhXtxhFrSjX +6pvl2k!JIcVnT`ouLE$G4ZgQJ%_bRUQc?$$Sd9tf?0$IbmSdt1NUJio5tCtO`K#-a&tF*<_f2{j&z6$ +4sXau(us35|EvbJj3s_gKq-#X?NVCG14w83Ihe;z>HbC&eU{Ta0-tjK$*b$8;=U` +3p8$VdV77pc{+=FTn{ZA31{DsPrXLkPANX)R|{Ly_mhMo94+AZm9aNW@h6qE7(6!O9KQH000080Q-4XQv-5B!j}R70D% +So03HAU0B~t=FJEbHbY*gGVQepTbZKmJFJW+SWNC79E^v9BR84Q&FbuuxR}k){#!?$w4+Dawz%D!Ww( +e8}24mB*iqt1iXcC4^LrbJ0e0^NBz(xw9`FL@npo%S!7p0Se5mBXUP +rr9sxiOA}%8Zvug&tQ>xp|C4}q+>tCLE(&*7|DvICkOIzP9|+aUSn5Lk*G+xE8SY-JQ$wec+aYIrUkz +ricO#IHG4H42$>`s1)5K7gI+kdgH*_nO|mJa3M!>Kxh%)LrcAzG%VCEtErGp@;pQ$pmkQ)Ji8{lR*MW +;L73_U&-0B-POz~7FYcV&RjVRNVy1J;h0B5ijVoTT<)4&QITu-N6T)__}_?3ROw$V8bGy2}!z%&)|3( +(~-1IbOfH*OEK6L~lp(Bgqw(w=fC(BqpF4t=erXFMd6N`{k=GSM9H;WZxHJQ6H?RY$#}%&Y5nH;sM@M +3PaAvH6WSgi7oiKla;%$Sg!=Q|r+SBAUCHH9S?^RpPh6?1!|EtB=RI@qo}&s~5iTRkf;h?zjT!C90%RiiQhRT +CnWmNYb-$4*wyE7)43keav#hH0xamKkI)N@$n$YQ05&MpcPB%F!R+gzV#@tU}V +Q0U#PRT-=Fc19E8B_r&lP9wbzH6)#=^MKEOS-3EUg=BwHL(@_AT#TWXr**b*Z=Vc8=7da^o`jo|}4Mg +bMA)o)Ns0|XQR000O8`*~JV4;ZV{dJ6VRSBVd +0kc8j@vd6eb-kEv=5d6N8V#XBNJcHQSLhyaozE;LRN-QFvpVfU>xZjXbW{d#10jd6B3;6i+?N4sUE2%U>Cp&{hbh6QCJ1yap?$Ce3e{ +*atEWN8ot(89eZ<#T{vec@9mbYnkKv^iAc3Kd~yOfv{V;;+G_Vju4^tj$`SHWxuc75RCU6Pi##R8}j9 +xRtOq~OZo{-1*vcWYvR4AS8JL7^eg7D^?AI#OjC!R4zt1E{TS&0;>UxUIW=nyY5s_AQ$el6l(+G5_`M +-3@6V?~D^irr+J&zQ4V_3;v%3%dsG7aRX&1b0-p~EppR3wRH$qos^bSGIai7`L~mpckq{&-YkgI)T$E +&AQ5OgQcn++0(NBmfM%f?H{@fp4OM_$jR7=Jd^Vy3r6Ff>VquTOFC#K8x#C_q%vzl&L+aaP>&?f_YPH +$L(=#t+K^jyhRV_--{><$*+4I9@z4{FQh23a5J=<}Cu42KuG?v+U$f!K@F9Mr!=wKptS|OhYZ9=gN^8OhN0RC_{lNC>fLHcc +)=3$Eb8)|4>Et)qaUnbnLl?SCU7)c|;^}3Nj$o)iZzz}il~w2`sjU&hNDDe{SRYv7k{}D3q^>O*w5(v +3g&GZ7cft2a{&J$06VKF5lfev&NeZF2C<09?%X8lqxi=+o7idv8T$0hA(F~*RB3Eu=r$Xf+Y|{2`-!E +QhCR2!#`+@4`3JuOLlaTi0>&whx>fipdxV{*c*@0=3OjgT39v-)!CevxaP$JW(hXUvhACR)1yfy3}Gb2izJ|Pe0z +Ja(_GHj#afuR6|E*&#=deqi9IVCaqjHhmLB$rp<{vP67@Ys7K&AAuIf0UNQ@jp;Y0|XQR000O8`*~JV +wzP?{a|Qqa0TloMDF6TfaA|NaUukZ1WpZv|Y%g_mX>4;ZV{dJ6VRUI?X>4h9d0%v4XLBxad9_$=Z`(E +y{_bCKQ$9>W9VJfNv^C%k+b|3ThPBv=een#Hk!YKZBnl#B#|ZM@cSq`lw4Jwg%LhlM$cJ~&J$F1h2!d +aE$!n%Lurh@(EsDyoR4;Q86v9L@x9WKC_jIM?nybMxv->t)b?lWi1QPyGoQRXh(k&N{ +``VrG+e6K8DXtmGhCES&1r6HoVMa9ak*9W-DA6Yeeo=has75OGoXGD~m*Hz8&GbvfY4@Wodh6PKLDrb5r>jDv?(I}DXCSi5DPS60Dq5m-%BXSJow={whTo7OV;pXY3g2u=1F-8CpM9(xGE?Ok$v7sSLH)1eJF1!JD`GHk6%f(WLI$XiJ +K83iD?uR|yDo>qqS3-iQt#Rs9CSTu|RrgRCJ$ja|^E8Fmg?~W;jo)&KE*jHV4v|JXf+~uNdZl0Pd$2P +DTV?u7}NffWTr)Eh?n3-=_A8ReBh=u{u)@7Bm9%eI&c5Bqd`Q%mLve3VooVkX~+Gx9NB3;?M>Rg?k%_%eR+U4T77u +m&xU;;BM-P+)5#46;Ty>Q9BRdMX!YZ(I7`p!k9b;!1G6hbAFF-9+S#>CzW0x4$L#9@@DdoR2~us9Fx) +WCGlqRR7p(s_3*DFbF|#7xb8~MGYZYO7M0kZb>wMRt%GQKSS*k93iqfC(_+3?pT_&TTO8Zrk$M{)Df& +;Y<@~0?W;bDZF1I{$ko%`;-@DFxnr$<7WPI9DSV|28L~mDX!|$@h7MKPU=hVh1uES0Os>!LFoGinXhTYBP*YlZTwYZ~*r{=&#b5ML^et|x46`%rVI#*!@*u`Wht%&(a +HC)+xe{+X_V$49df`Fi-W{OlnVN}qY6S0LazHipgzbd+`J!zSyMam9eE8~VmxlXmqa!Kl+!yR!1urf+ +r+$fPV}Gv_5(tt{vG05DjkZo)pfyWaT#V`!?6+<`)`KrMq?f5*GqcHUj +kazqrVrlLz6&nzl?-?Yid~6EP=?Zpm&rDD@jm-UTPLu8#`6sitKJPTH&aR-&aivz2vZ +eL#ZusVQ5ujh?HlzawxXY6on^&90Hfg2vDde=xQ*$g>6Q_^d>K%y9lX2<#(j8jfXt5JGtTR_-ikb@(~ +l{IJ-X@UTWdi!HT?L~huAfsqj%ccymP$lsfV3xV9QDk8u-Z7}fX4sZtr0Qqgk=kUC#LBjJ*V3HRnXC|SN{4!ahTIovHiUEA?ZN9 +zudM(+Vx;|Z@D8{`%2cX98@%W%Hi7Wr;_#;8*`;cTq60fhDD}TO{<`%^_D +a|Lgc*I_YIbABV4c7iZ3oM(%{+z&v&YJ?w=0W7eS)4sSE(`TsTleuMXZp!4m02H{U~>p{D%rG6vQ+dc +XRP)h>@6aWAK2mt$eR#R*fJ+7Dl008m;0018V003}la4%nJZggdGZeeUMb#!TLb1!6JbY*mDZDlTSd0 +mdd4uUWcMDP0*lb#^aegKIa{S8uKwT;k{vP$@S%St@Br^)QwnKi~-Q^x8!Vh%G_7iEDY^q%`C#4`pbj +KWBm*pe}ZC`@z8qMO|%qJi(_YH(W@mToM5?!>!TZR~P`5aom^Me&C&psE_@7PpkfhEPTmaQOw>U08Li +T8T$^mrwr)Z8-`wyC#J*%PYqtwf)}G2T)4`1QY-O00;p4c~(=c!JX>N3 +7a&BR4FLiWjY;!MUWpHw3V_|e@Z*DGdd97F5ZW}icec!JbY#u5V@*3!)Pz5kzw>6N)LE<7Uf?z@IjwC +h|xh1)B1VjJc8FDX_EEO$KzepnIdS=cHo#%OW16xzK3c+uL2f1w&Zc%#&-WVnInmz%wSds(^w}&TTH6 +1$;OVfI&v9VYQFO7CZmXpDZY#8f7nf00k`=7mDwz6gme&4X|?(RnKXYkOl5us-Ah~NDE1AXoroWXCJj +zqOL_K(0I%TP!{gl?JUYt05x&|GCencx +TP(8d_U`?MyEiwF_Yd#NUqAj_(yyQHe}@I+$;})HfUHu&Ie0Id+Yy~lG5<;TIh|)fe+>zeLTRj|RD>$ +s#yoXU%^4T6|ITPiCwm2-dgy<=dJS(Qyl}5Qu5ECU)wqJx!X)_EqH|)6^N8f<&dLm&w_j#Kf+EDVvHN +*|yqD-MS5hHEFlU8$M16tU%t2~D%FKrfevSqF(#1aNBqHp5xSs+g#9t#Qauo~$V{d$N(OsTJ>%27oof +V&SsLY1sXG5m5F2Q&be@%l)RODVd*DgbeC!_A!Vo%3FsCo#kLlEE^tfWwCA3CcJL_rHr8%Z7aB$a}V& +^6^xGGJEJgqWB~2-jEE!OGacjX|!nx#(F~tkJ&>XOldTh)Rc+Fey?3=hG7d#R*zH3NC^8e}Xp)-7B^8 +ly=#fDPtIulLmPOcX1+_)px-Pn^PqsYcE`r%!G~t9*W;W-|~?Llb84Xj=}8Ev=PV3UxlytZ&( +iU3uJ*=fxIDw2AFr>D(ph9LEs=?8}LDB>(r3eU@go(FuhuHcC`s)Ss|2-mfWWYLc^eD-^!9Sgw>e+5(J#F@Jup +ikZ(z(IX5>Y2m7VvRnT2<3I@wYPEP86IY@K9G+yg>9tNUo(mcA-Zvhei8Ws6b0EcO&PI!z8UekxH{_a +a5|&DH-n98qE*hfwiG2aVBXO^P=fM@o&wJ#*x(H82KQiBSrFt*3yiT!P(kEh!fuC6HIt4?$AoSHa<`Nw-ZQyPEWwmf>>Iw@JDuV=(+&nw&g`rto^v-E?~0DD +38XNq6*0TEOQa^GT!~P4)cw{9Ebp{Cqg%)7hwUw5K~qv!g@aR`7g?dl5B3U13afi5sz#ya6iexHT5}k +s`G!r`hFybjAA%@{f|J3(@>6nhem;>eIYCUEA=xk=UP)AzchUVgE@2H_#X=J+G@oh$UWoc3Du2*Ui2 +W*HoVCV9H%TA6|m><)|t7Ee?6{r;&OLhKGmnB`v2Sex2-uYH^whKscHuvP`Cc!JX>N37a&BR4FLiWjY;!MUX>w&_bYFFHY+q<)Y;a|Ab1rasw +N_1!q&5(}_ph)RDFF>ZIqaz=RvT@!(nwKqNV1pJ3b~;eoB^BIraP-v|MyfGFoy1)Rg{Dac2S?tRnOE~ +by@_bm);5`dAr-y^syIxRtRZ9qb%!G1+B>{p`oF6b=O8R;g57Cu|`q?<#RCXupze&~^7SUPt#@qQ1(b;ilQI%JBeZyzfC)+Z3G1oUDPx +m>WZ_@LKZIXe9q2?{&^*IE*``zbuijQjS8sJi-RK<0F}4co+A(;DP)^-$dCTO32t7BvV(~LCetQdhoD +$3aEtThZbU^23-P;;4)c$hW4RPFJ0OWz2yN51kRFlIc-EbpAe7dxcP5-fJrI2ks{JbL98^!qSf9pQ_8 +}GXTto1nSaJX`&mgyopvD%GEs4A0NSGDWrM_VP^v|x4;fmoIpvXpg(>8S +E@^a2gEILF}x@b+YZpz)6T->dp#`g$fDqh+V5>%8D`0Y~bP%E~rs(jL8Svtf$|FCY6)0jT&q*H^yeL> +3Luulx82_|LUxeB~HL3Kt+^U3oH`t8HQoJs+ebpES%pv7_U-#U{5uYCoC2EnCMk&FujtM8Hw82R~vih +I%UGL4!ATMXER*v#F3ok##VIQ;96~}fDvD$OrZ~b5(dcYNeykqq@s^U*f=-<$SJLiJxL7CE$-10;i{D +UcC^CWmzLb-k7wbMrn=El`GUx1E2XJBuA5+`Z0xfGrkv1B8aVu9$OY?w+ +S=P2dH@{YFVnCl&TufYBY_AvW8YnGp(^DInH+yhaak8_nlR=(udUsM +qTBVDvo9fPMG_pIH$862{{H65$rFuKBMacbATC; +5pw7|5X9u4A9U|7sEc!JX>N37a&BR4FLiWjY;!MUX>)XSbZKmJUtw}*b1rasg;Pz +7+b|Hl`&SIkp$Rzdp{3A33Jd+Xl+r_?hY*Y+k8QP%Bu3-ixTXKSBRk$CyCJKCEX}+bzj-sN$nBvtfjc +&B$dSlV0JHaw7(Pz+JE}iLPf}gND`jFbKIPP$!B~wXyWS|Au?zD29ODnKy677w~0+&={r6Ug|YerfpGsa2wOB8deAvTeHI&1TC5tp)->9r3TNAz3(j+$py5B?r;sxTUq +&~>AW;rsF&}Aa+oBt?TqzcTzs<_-`v3^jWsR!L&pJ(%O6LCWZ3LvSCn)vT3RcVsRVDkXzirOvJ=%-tk +Er?cW6sqJLlYcsa&y>*7ri8-bIWo0s7fqYYL<6bxjyYt3}v7ewW&mYh_kKEs{J*%2Fs*mhcj8gJFv6b +*ZG@QdE9Zau_9L8otuSgC;RCGPz5Vq{ary$xMqDGb{GtB~YEi@)bX?2f#E9&o@TzkP;GbLx#|z7q##L +c0%jW#~+N@QrFlSmyJ{y7CUS1vGPZ9_d;!1+;mvMhs_h4RhT67K* +{r99Pl}Ml6T8(7%_(cPw@!KjOX1`(`J*~Vt!;kdCF$;kdD*IqG5i7KdD9BJ+04;(fBakQQ`kb_oo4xW +yw&11pfGqdkUYVgg>P7GzAP<*u8XA0*a+j#+K=6Ye(>G&zn3#4_arY43> +di)SDR{NgIWS?cF_Lzudz=5~D0KJ56BV-XZG6o{~&2drs$PX&>Vht$T@u$M}~eeZk}Q@CTKr3I=Wm7T +`&ymvS$$7A;{83p{%1kX++9J~q|d8q4b#?`DBSP$n$ewGI+rfX*28#D09W&lRI4zEv-cm&4Z5_sH9VU +VaCH=8BrMhRQ}-xT@d5IH+TegRNR0|XQR000O8`*~JVst49#4gvrGkput$9{>OVaA|NaUukZ1WpZv|Y +%g_mX>4;ZWo~0{WNB_^E^v8`l;3OHFc8Pz{Z}07i?t=DNgxzM_RuaJgRBi*_vDx$+j1i6NJf$g`R{j< +9XDYyOi%XR=YD;4wm8&ETgX+xa}$X6tx`Fw`1wuuPv&HTQmX^lQ!V5UI`c{xJA(J7#+cyo_1Ev%n-Xt +HvXkXz1jgz#g#{!5;0fD;5z^Z~@6Qh-AdM}@4}^|x`6u%Zn9K)>?c=hC#u*>xRu^0~#LcE1G@A|*pA~ +1*;flzuF1WU08U)Lir`PX4Uw&-gmMDwnQLYZPsCbxZf*DZXBwnN&^Ce8in`4xIrGy4SQ1B91W7QUGV4 +bjFmc`&jrZE2IbdG%+gpj8_&p&{*UgvR_Rw|7qY!0l#d)J!hwmLzI +J&-1ia@W^y429g_97lsAAr!GA0V%8LIGhu|7B?5qd6Z-)kd0Z>Z=1QY-O00;p4c~(>5CJmEA0ssIX1O +Nac0001RX>c!JX>N37a&BR4FLiWjY;!MVZgg^aaBpdDbaO6nd0kUYZ`&{oz57=XxwHXN=hQ<3Y(TfeI +$+3xA;1n@Q7ASY6O$!@l%4tCkCKu$TL344Og_F3iiY4hg3MBN><5T*Aa?{R$KOce3ciO(-Wgk!l0cz; +B^QzPtZppTgCTcmN&l;=YO#aY6Ppl_Zw()1^9J*rP@g68%L{yft#`PDyN{rVn+o)^SS&uHi)<{M0ig2 +?##->Uh4g9;UdyGfa>JA2d8S4y=EM$qBl@%;IAMahY5^ri4%-?&VPi%?@EwrIh?21klOzMO(%s|!X_d +UGgNgkLhS5d}7GXEX-aA=A?2#C<8Kz0{^vt*x1}`z=DZ17SN@q&2Ci5dFQORPv0%gDgGIpKHOmt_6G@ +yc9v4$hYLT~Vsaxb#?K6!!@nTR@s1cG{B<_Go@wF(0RHob8qLpJeb*d-oitX5{EmKc@2o@cJnr}64M` +@k7JSkqtz=+TB1oDiHpoxLo{bVcsS|Azz`#yLmH5IO^zky&d%!>=!)=ig+u#1@(B6H~<7e3~nMKK|H# +-G12=3k43CH`Pu4H@0w?6X`Tt8m160Gi%r@6aWAK2mt$eR# +V42Ti@ph000FS001EX003}la4%nJZggdGZeeUMb#!TLb1!CTY-MwKb97~GE^v93SZ$BnxDo#DU%@&bB +Hua6X471*3%KZAnxKn2WDnT{2@nK2TB2-bWl<%m_4?ZW_dYYEUi`9~c0VK*H8UK}JTvo9dea!|MOkm8 +&({+j9*rA*rH%Cc3oGlwY`Q16ZoRJhCog^fd*>#lH5-we+N(QX>7|IT;>t$Wa;0pL@@mtRx>B9YTe(r +idqoe@?%v={l-o7BUUgDG)w{X)S=SJ`yqb$kudMXO&c(0V`MLN|Yg5lfE}fqQ^+q>ocB^m%n|f8tT>D +*T%qnkT&Uw>hvV^IuP?f~WR%)M>`c4&ND;kHewX9N#fJ<|;L6|yQE9EnaTGr~~ew~eWd8bhtiv(m&|D +h(6Nuf%SuT_2%te9=BY(hUT;Gdm|Cw~=7W2%dZz~3WrS&FR^g{dEVv6i<=NI1O{-UK7Uua!_`sdBIJ5 +IjUc_E!^HwrItH0%IB*jAw}Y#F;tSw)wW?zE|-r(=l4LQJ>1ettd` +#){gIQ%-ts6N8+l2rgdT=-kMtR+ICFEBVS;gv({uQn*jM!?wHmw&={vparuF@lI!2Q@i4s +Jv)f;$FsjQ^puy31AL#nts|YmGf^G3NJ;teiR$h%bj-5x6}e1+8PdQ4KMdIboRHo73~h?CO`n&tvTl8 +uO6hh3GXI96)!C_A@%}*nNt6EQ`M8y1L*-8LQVcyYtXB5@&O++qo!|^Fs*}a)W2n>GY$OP3)&BD+5AH +q;G>|MUi!ElK|sPexuMb0CxwNu!V>!>Xe)Z_=wD4(W@d2HFK@d7%B%?dEdB;r%#vd3P;`nCUt7;>Z!) +D)bp1w&(6}$tH}{i_FTlt!l6Q(?t=m$yd-3Q?iIf5r{p9Swada$Gae0#VIe8^ari9^KEkL7QKVLcC|D +(noiI6}?jL*<0R$KnZ7k_r;-U?WhT1cF@FLa%B=O8&Uc8o=d$z%%;C{(xLlzFRo| +Mzy|5g6ycXD1voJ?cO?3E)zhhW|}SR(9ctf$x6<@NQ|t6$K<(~4F?d8E!~8&fFkG5OoUV5d!F@m1#Nm +zm!*e5hfwx%lq8o2|4fcR_=k_wFY6=WeKf>FeWNa7HrhpH%t=<7oL6_!f=9T&21|$CLsav52VQ3N3L> +hY-9+jRuy1VgdwE8^S4IKvg?VCe~H$@LZ7QQgQ1CQJ|+nyPGhZh#YXe#{(l!5`)$u`=sC1TR0s0FsA8 +JF${v;j_jnzEQe<{tXflr(!(x1%JIMN1j89RyO<3%)p(3k7=`HlCpc8n0|C?qgKEJYZ9c4OyExee2NC +p6G__u8iTQI%%(W$nO;e;}IxKe?4=XcjQstNhxVG}Bb7E}C!0SfEe$*#+T2)2V9QqAZtLi{9$fRY}x^ +=SOK5&}S?dm+~WDu*`Br`(wBk{f?id;QTyd$C*nk)zbP%fb}r*2zm$W>90b7rhwku$MFfsalytH6|Ul +`-MOY59L_%~}iMK!JrW4ykDJY!=w)VxXkSS$h709F10CZ+`gtUb6 +MgzD^W8YNT&tZfCMhBEF#)o~3cC)fdZHO9C8Pb>u{y=CW+L{;*I4}eeB-EHnxj!83dT%oVw0mME2L?L +kXR+=+P?82(FcYR0zg=Blegez2TE)_5=EV~fuRm&B3AS#(h?rOJ(OrytlfzNhF9kI`-ym)|!&>s`iAQ_m;KFpUm+zs){7w11lO +T$Qi%!Ise*59yj{_N}}o9H?s0yqsm@nJ&4-6&M8TO;}ykS@z{$C9?l0^VfRtmRU9)?DTY#A>a--c*~~ +H2<{Dx3}B7-5+Pq&Y%DJ#Sbrk{7cXxZj}+Y!p(f8l4oZlL`6S(m%azK)2|qpNw~(R`P!fwlWynPp|u} +ovr$uJ4%UH_&4e_Q?FU+PSp+p2R0&yOU|ePI{{Hcu_;>jI>fP&~-gghLKfU^O#NPwAV~)eOwi6t0k77 +lI-;~7#+o9#2P(W9w>u=5_f+4z8xoktvwu-vhsObA%=O~7B*z~~G&s0+$YHPh(qyM)x@snzI(p-+wL_ +7dJVt!x;O*34+;{}6lj$G>spPF`{r=#9?@9{8Xw<>UjhiwrlCR>MY6)p3U+SE@Pt8bBr6%N3+)qwEyV +9=hp>>YDnq?+4iQWi(km;zYeFj!`%Y!4p_;z`}O+2Nppk%g8Prg6L*B-0OdcP}EQRcIQ@Jcqiwcz@mR +b2jZ76E%FMv2M&`@sqym46TViKYhl-A$uhtRCq_9U*yu6s`cU0=x8NOqb1lJ_1{ohme{G&w*b?8V0O$ +;fJ3_Z>wGQv(;X-E!`sVGPn_RXbVZ<~uPU?b989~(7HH1J;3o?7^OwGz(8q|m$mY=_&5eC4tDD1##rr +Dy2opaa5_7^%oH*A9W;U7=%&)+iL6eicRuVQXXTxPNTHm{ZN21qd1H*y7m>o<8{_gh~mjc+UjO^~3Jk +PnLa5dU(2x0Ti?pgmOYZ%cC!GjZb;$xpW4b0fynI16Vv)+B4;{Uy?*61r>vad7iEcT%MKTb}PNYYt)c +9>&WbKlN0rzB#I#902I$&SOEY4%mM%aAOHXWaA|NaUukZ1WpZv|Y%g_mX>4;ZW@&6?ba`-Pb1rasjZ#r-!Y~ki&#yT +0WSI-hM_~guDhk65E!$9}Mol%4CM8Ly>#yI{T5RWo%S&>X@4oxK<0z#=sf7Q@1W272n{qFWW`t5oNMc +P2_$T!aWSSZ4A<8o)&Oe#VSS+;{R&&L2FO_4dbekIMG9|q@dO|)&VfY${Ur{)jjo&8l2$UW6ijwDf?~ +rkH3=ix9FE2}l=nyV=Wc5hu6+9nDkG2UHfo{S&N> +QT=Kdlum`ut%k_vxFZRyO6Up+fWyXUC%3|iEpUuAoH?Q7WaSQ(9Jm4~yxxXWt6=f4WXc?b?F1(b5|=Q +k;o5bjq&)T_g*4(JS5A;$P)h>@6aWAK2mt$eR#Q^tJykCS001To000~S003}la4%nJZggdGZeeUMb#! +TLb1!FXX<}n8aCxm(-*4MC5PtVxaZmCO-52D#pq3W79mds9dvZS1o=C!)#B{3|jnp3L;zKe#ebL?!Vc{Vwrx +^Me)GFFSi&QxoKYM8wEok_zd6H&KHTT*8KWO_5Hw5rMWtqU8LZ-QS+pSo7UaZ>V%wo>H9=ALVVj~f@F +*~SUYem&TK#p0jm6T)Bgkt3zX3|rRm@nfae}O&A?57ddY#dgof^ +WCDU#e6lH!ibKA>6U*pO>=#~Zi*S{N5N1A%~!Ak6Y@?;>>ubO{78mNg~QC9rahO$D%4GFvHKQ?1HQ^7 +F8~v$i$o^Z8cTU6&*KdfxVSrY{^wHV^Qx$pDy03K$CTrMssLKB>>q9LCV9UN!;n$`W|$8m|{=<;^+;rjDtef9Qo^X2`= ++pC-FC~}fzt>vnlx}Xr!xtvIVKI4 +O1D@4KBKbyP_e@BZOHMJYz#>-Cy@O1$EPJt9;XQ2XhfNV0=cS&2kjR`BcmJ&^0=J{d^Ro+zGgypEc;! +F%N*$*`;A+6WMB~J6Jptv>RLEQQRN&W8DOJmx`1DSZsCLhn5?j!->R?lK!Nai<8n7GD>!KUtW3|3V%v0^Ew`KTIl}W9T6WBEd~{FAKc@L@ +U*}35F9=@h%*@d!C}`HocMY0%MIR`NoXJdT(tgou +*K5fN~95Q4`MknOXD;kJ`ZgH!uTxlF-W3?Zb~7~U9x&Kk}F5CPI;^ghEN0&D8t`+x$-`XKDCv(6_c@F +4p8CmKe{Dm$ZDLDL`vd*ffgJ2Z0qEWJ2MrXC@l`uNk7rofmcPhn59SN{X-8wZQ!x82aLR(E!X;~DKjm +L<(*`z~;cZY6%lbAdkp;1nG5Wwn|umx+Jyu3it^jBb#H?HUa26mz{`7}|uJLCsm?G>RQ$g6~*|&S93h +0)JQe!SVsJw&e|XR{-1@dul~Xud`*g@}kFarh@BmHSjkJEW10SFp{&<-y&|6?3|*XlcOM3owN@szOPy +8+`SGtmJOlzG6XayVkYw==CQc0$^BjO>z>H_i4sMhaL0n^l^~d|u~EO{C)dV-z#k8NYaNnHY-nIZF{} +?mI|+S`3{W!buzB6ues4W=8g5+746&qAcU_Cyn2;vhXEjK{cUpM-3cPRKr`B$0K$e~juQ-Y@UZQYcz` +gv4g-3$f6U1cl4^T@31QY-O00;p4c~(;qP-p$w1^@ud5&!@l0001RX>c!JX>N37a&BR4FLiWjY;!MYV +RL9@b1raswODO$+cpsX?q5NuA1VWifVFEec*w9U%^IU?Qlwdj!3bnprffEnsFGA%qv(I%-I02p2y;IDk>{gX&TH+*zT<~1-I!oYTgWN3V5ZlJkL4KztCFgF~r)++{A@6X@j +T*Unl9dF7M4`BC|g0*@VF#;dn!vWeM&*q0dj)mAVnO)+K{|v|87xSi>WG9>0vAeeYMmkSg;%%alex36 +^d}dXMh1iWMAI!at7(oLQ=h3T#=)T-rU`NxNk#UklADw4FT&ZOAbyWAFBMt7y9e-F_$=MsH-qJ;rP@{)!fv3T%1; +Or1wJT3HO0C%g&ud{O%^vPyEi#sX@TE%|6#cdcYky=1y6Ga6M31uu|pg<1q0GUdpGLv8^s2ePuM)-ddM_02jE!|!Y-35T05+57ryfJ1Bdh +2*Lj*U(xn6AOsIIxpOxN`0JQMOi47{FRo(@J4ICYENLW+`@k(YA{8>XLr*RsrMMl$2a9re$9;LlLJ6v +#u7s55T06;o^a4JRnpUtKC~!nxv_L3n-y_7J%7d5t_1 +LwMEc}2XqSi2Cx6p#o4zLfn1&sF~1GZ@roLcqnmw3*GI +Zf@+Ji!25ay?E^Ho%HOK3b81|nv)y+)Sl^tDcnR_Pk?(CUJ{`A5E=VI>{)@ +(F%u9y`?)2zD?-He)O8)#v#5rM)MGyt?M`)$PK_2>saxhM-W`0#j{f0~Q>99w|zqTv!CpR|h!}{z?$) +gB|qpt%qnB8B0c$>DjC4ZUsjYcmp?((GcJ*Qm6U41KB8*o0}PuWg9g;uO^AdtFLU&%sn@uwQvRE(nw% +iPkPw6@gUK?9dsXzAS&-|BYO&{d3T5|oDS?+%33{!Rh6MYW@Qx5b+ef&e=m|JB`Wyehq-d$h_j-;*qt +E>D(AhL`G1FJGRpcj!S?YnJf`yo`zVd;uyOxV~$=wR09g<+Q@dEr{7~SR4SpgXB7SjA2Cgc?pfWyZ!q +gUW@{VFoCC{`|7VriD02>0SS4&=P(9e+~|e5S>el-i~+O^KUx&15ir?1QY-O00; +p4c~(=`e-Wdi0RR9S0{{Rm0001RX>c!JX>N37a&BR4FLiWjY;!MZZfa#?bYF92V|8+6baG*Cb8v5RbS +`jtjge7n+b|Hv-}NbO?n?rhaJIKW3){$`(AlF-_i7aTJZqJ8-bqfHZ@;taWNm3-!iGDY^#A|vlqAV#X +wmh^&`~`&gxH!0*8j#T1}Lx^7`JSE=!xSB$b;?1P%E`G695gn;~4Z5g55b>K_oyQ +L=TK}!1!mtARfloDxe%9A7DWY2O+>c@)C@ktr#V(!8B1IAHl5u^%6~ZGKw?)0R>d-YmyvKmxNuu&Qy7 +^5)<@O(OG{l@_CQGC~m+8;Uq=UjqtVtCo|dJ6#KRQpjDD2N}YN>2BlPu&8%OBi71|k7E5@41)0p_lLA +>6kdI7^4)?~#Gs{%8&8Vk)XJSL#!MjUHYQqQXlgHcR67hk(n)1lUe}xlKaMKn(RM7|sdVOXJPUk;1nH0*Oo7>_#&&urX`_*g3oVs +4Mc3?e5&f^C>=UXz`?@739SfEMND2A*1IvvOZEdJ1>^4;ZY;R|0X>MmOaCyxd{cqdG^>_Uh2O37nl%|_>7&@UY<|Il +gz_A0_YgPybL6Ilx8j8p9NXuH0|9$V>7mpNayK6U80b=U-?%n(TIUOSA1(giC%xl@|sraRlP5rH}k8IoEQ6$yh-vrS>-e&a{x_hUWMeUtQ +jwoJR~63w2svyY3}oAaCb)n)wQ`h5)F-p&6xj$+G1YWC7XG!>0AR!t^#wVzH1eDu3|Q_&ju=T)*zCP(Cg*Mjgh$=G^ +LD_Tff;gtYw6acrS3Q4_TA|$Lx^F|(ey%#mzN=gaX4Ipx8j|@LE`3EGa$T7!~Gjhobq!e`;i#n+(tXY +<#oTo|d2NTh(%8I8{2m}Nz1C-Uc?;&R`2(P>VP^Nhz1bJQY&$PI%Gu}vWz$!`eElGi*$@4tqk~n#U4-72IR4Z$9hAFkT~HF5W6M24a=!@A&YS`$>^HIOvEdREe29)EG6rMY+^~|RNDRWbt0&x> +NX_seo8uv0Xg}BtT@lLQLKr@L73)KJWqlPJ! +m<7tI!&3_RqFypk$0A#I7)eA~6Ba}$M$#)hVnZpe=1Imo&ZR9X|pG%%+UEUM;YMfG`LiEHUudx;F8wq +W9bM-aMYH39(pEQ1Yo)H(QwypxuSzH}mzHLVqn*3}|)h6-`gp<-tR-0l(Zug?gGK|&kBthQxDrc_I0xjR14`0=yE$v4wpQFpQhp6&(YBf~3joxMo`# +ujNaSi+B9<$?^8&IJ-IiaeDk=dVK9o3^<8_Tvo@iZ`l+yUx)VFi=?^(2ge*}*ODxgM*!*Daw;7fj**t +_Y<%%~*V`+FfhN|<)F@k7+5unauaLl3bT6cpIYu1x?J}BJOw%%f@Z6{KTu}-*7^cO-8f?xAFh__PO`f +6YLf%}n2bx)Y_}Q^~gEX)T)R^)r`wB=h$fH4qP?AxH2oO`}dkfl>pw}R>>X32|trUY+V`yU9htPPc+L +sz5Yb~^FiZMMTIlZTO!KJ55$;0Zf;1|j)RZ>W>Vp_{D7NhI8#K-WK@tfZ1PYXHF +f1U5m6}TOy;c6F^Tp--^8K_UeeF5jy_UX6k}Xh0{&0;GbkQA=n+;VQ8@7@h1I?anAZH|+m57i}lw`d4 +swTSx0w%-g*29!@{u!A3Oo?YmcMGj4iQwZs&CQ`CW{btuVptCLB(Jr4^G4GpJY#ENI +6~5F8!y(0?pOBb-RCTdC +@PN75>s}m)ekNT0TIb;W$>Avs!A)$LcG%4f^KhaIb1bSscf^Dk=RJ@>$4$B(2MIQOhN)K#7R|+tfvHP +f0VM6=l1nW02Ny6bm^MwXe?S_HxVQ~EWtTq8rr9^Rjs2Tu*Rmk~Q8ke^ +)pk}Pn&=Yct&9z;hC$}QdXpeYX!(jNRQk?DkWF$=+m1z>g!$g`_~z>3YH24%VX%Hop^N!j9Ig03i*d{N4sZIJ{X$p~trWJiEBK`s8A`l}{9kg(04A2TB +taw^XAtwO#j8HQl$m2@9d@R%FP{0wC9-G=HehY~#n!M;jj!{JAyFpehUr&-d8)8z~ffS$@>3hK3AX +xcNlRt$;p*`6#y(h-KcS-Y%y6=~1KgIk^s(qNCoJ|v1+ReVadV^TIQp6c*`1BBwPb!^$}bv&Ekcmh%% +hme3Toq|F4hAw2+)&`Tk(=w{nB#*5){V|K{tm}iFR?~qD2Til90d!XvyHSD#3MP)o=4)LjSOXz-y+?u +RB3n5Yt6j-J;U&8whd*qKaB=UcZrFJhsTY1QD6)1^U%oej^yAkYeQ|M|ez0o)|yn~j}fkuA@f9HgB<_o%|58l44lMg$2oZA8ttnDiqnX&3N0;_l`aJ +I|8iFBCn*9b9C^n)|_W`s%i48=cve9MM$}+tfHNg>nfQ*;TBjKsHEyoi}2Gk42~TI_iz{KslORXj667 +65+tb4?^5nkP7F7vi=q&O<9sVxTqP}5y8=3QCvzc*Z~3VZNhN2fzbq-Caiai%)3Onyiqe_m5ni)!6e_ +X1%6`XQ@JCEp9Wk`mqS#_9jqMyS59Cz$yZ%uzM4t?(&W_g)C;FSPXBl$i)+d#Uk$c^*PlN-H@51zglA>#-;7j!i*GZ+Nk0cVNOoyE}*y`>oEHL0$&R9jYEyBte{+5@&aoKchbh4kmlQT|f!SWkgur>mDu}EWM+ +(2r_&aoIqKa$sm+G_fHT5%*<#*|mI2zm%xCj}N`BOXthmNy{bVU*r!M(Xsd2Xj~1`-RD}#u4&YMPMCx)EJ(aDhq+(PkshV|3vgJ`9<=NpWRwhdijl~P~7}UE +}MYj=%KJ-TpNeOqQz0L*|eC^bBOV8LZycn>4rLUzbleHHcVp +Ev~pmIrTl@MCAt@tGC5R(Mnq)o}o?(E9Tl<~&&feB<;U&spp?cHD*8P@7;_XPP;=lpn=R0U222B9RRA +3UXqZY|}7!+4~fd((a_KZO2rxa2|htSgXRTys}HPY`V{>MBdrY1K_1yo4 +_Ah;lF}ETkMx!S5Pq21)={rKZIt_(!?CXn(KnyTCByfG3iq~fCjl}<(FbUkb6M;QAMET8vH$iGx>?Uf +BvS(dOR)@ndGF$^pFMKae$i5x3FkSn?R`WOlX=A^Whn6PyjG=FX)N;s+!huLNipr=JHK$GWdDhg+1y9 +P^=I=kBtT9{vm$LrD<$47DrkUkK053tx42v&0k26CTF6&sW0s9M^0bKewa8M9+es88r<7ix~&k4$Zau +0g0R!7q0AMY`?5PI5IRQdW_Whx86M_8sh^9?iwBb#WEa{Of@($v)Jlkdi8%4nYKM$z4vRBJt?y>FMd|*Yxm-xLirGDD%ABWW`d<%k^ +3ot!P$dm#1PTMJ?CmtxQD%56iU_Rhhy2tPm|W?AorD(ZLh3-d5X*XxciPw?}2wW@VA&)7MGf$b*AyU6 +pkU$ct9qwRtwvPh?#erM_9pwn~~t->#BoWr1b}E-#l$pjF?NrbSgY*iEB&BZb#0cLyEbXf_13iN> +KwX>s-y-|7j?OYu7rB`5yn$2FW#x=t*qO!%p3iBk>&7cfrAW1E8*kcSrzAHv6M~AZQ5-m4g6)D%;hYZ +-(12|^B(!G0Q>_1f8P|_kS@N>ilp9NyyLs?k~~jlxm0(|%zq|DvXnJHy8vo>2)b)a_3lm9w0!kW%?{s +P0utTBWmoCONMV2VW1_Ik6T}i6F!5DZMe(gH(z1@Kn`K(GiP +He&GF~UYmUX<&3iwq00ihreaRt)}Gth*hYk*>WHii`iv58=LfqZJjKV7{2{@qU>_k+vjGS0K&MmNy+F +$#H%&0-{RNpVkdY!buq<gTn-eQN +)Z&S&6w%7t&G+0Fb0^^-01sLgDDUxEVXwww&WI7};023~2prhglJC6)C@4(HdHyIjOB_X +>FyJEgdz~-jH&mtKS`9b8cJ|hB#6B&rF#j{B-IqqBW#Z>su#L;NWKs*)xNyxG;^L_YVK8Alfd~!8;cI +~VNG`GHIX$4qf!#*PiPXd^ZmNI}<%^Yj0@_;?v4ruco^Y!{l +NJ;2K9VIBNh4(6~ltFy9e#QEEIm*@X{fB7@|e`Vc7;v>k!lP{m2ec4Yua8yR* +ABsOs*-?y`NRYR5!gIRt?h%>1lk=`coA)4&$Z~rIT7pA4+7^4WneTtovVSFM5<)Ua>hc1t`yE|dbdZB>A@r +!<190F!pz|dph}ITXb-oad1b;n+*q|0&h1%emdei{)rFPwjzYVu$L6~g?FgxjT(1G#}8~}jJ6(9u-Wu +1XlO>$v;vx8UU`uVjasOe?hN%jM$Lub%`7O+1!?VUpZBzh49G{x+%_#mpZ06+^bL8r@@1#Kc`;J(B3Q +d}}v9ULq~J%&N!sdE>iQpT$(H{i#hhSFTtdQFbJ@)w}3CdQY)0`n1~!w`Wu4~R7IVcm?Y9tZ+s#1$iI!aM~d0fG+;6yrJ8M{p +Uh%T$X0q#!T)7+?JK;o|aqOi|f7Dr*lxYklgnTl9ZhGPP>q%R2~N8UxGA4aIZ4uDY(-!%=y2VzHws$|UAx2liB>g?RMbUw-R +RaOs>m(_PL@eQLA$V+HG$^*k$SwyOZ^@~cZ&J-h2-uqpX5Y*`wzIS^x?^AhrN84o;^+c{244Xq>$X9u +LTFI+&<_au7Y11)D@PGhPhR8!2;N+w{kB>LVsdS3O3EuRqxz7?QZx|d&=nz_B{$v<<6dV=u+Rd99=V5 +R1s$UV_*lz11nOI44SCG`+qlLI-$YkIa>W$27KB92 +1<78r1gM+JI7r@d8jF+w94onHhtmj=ib!yqE$kX3aF@mm8iv*H|Qi$FQ+^aTYgF8fHhSo}f7!3ZE+&1 +&#RVY+(v8z?hj-MQO}aS%rNW_)DZP;f~h!}MwaI~Hr%)G;)}QMf4H=r3^uVQy7ZlaExfn7mks)Aci`K%=raF@ +jSftF8~jy#>STi`CgNU=<)QaZf__&27c&Xt`lh)d@m7rmB`lERw-SvNu3=03wUU5|Xez)CvA<#si}Pv +EfIahI>p?~V68w1xeHmd6tMY>gC#Lot_s@W%QQ9X{RShF_XM;HLbJ^8B2LJv~^+Q0DgUD3OhkP(|WjH +Vq-Y&ZP2NNM{Kv)JClXL8@WHxYG_IG=oPL0f3%zMOqILWwbm0;UbY-Ib<`BJ?P-q;GIX*Ok@{x&iaq2 +d1zw5SYqq`rq#LBDN@9;Oc#u_p)mGvxFsfQC}beNav|oMyc4Q2tB20I4_)Fe0ZMGX$K0im=f`X1m*vu +XngT4B3;IAPYP?wJ)+dMH1Abs{|3_?puCy1SIRuo4 +q`r!6yTXB?IJq%6fWWG{r4(zwkYEwv3ksnqaN1&WPk1eTz##CuEb#NxQFc5fr+ol!&=7Eev9hHSU-+E2b(?&ij}1GVj)fz-esMs(TFrFYl5ybTygqqn9sV_ +GDgN9WKgpmejCl&VT_x#?{GP3PHe3%Eh(H9QYcF*nx1k)SmtM*^95v{_^6*SBKc_^duC|RRg_%h{&n9 +qMVw)PAZ?QE<6Tr6_p%Z;R--F_Faj1Y_d-7r~x>+A&yKnfGccZ?OORyYYHhLoXknQr{G92GD=n;GEbW +nMM9MS+BGc^5^|S85Y@_RjuWPYfKaM7YR-4}0|v1ODp)h67Y%#zsCg_u8l%i4!%YrRZ6U(^6`n_Jxz1 +6Sf{W8tQMLUW&)~&k2*X|~hSh+UFuy;4FJ7GfS!l36Eh#oqmldxS~R&hZt~4`T!02|{_){ +z;BeD)J006D$Rw5}l3#Pl&ReoGLjgDxAm~Jy*NhmG^;FqrL&=EPQ8!fdfa+?c57?F`@zd9SAj#9!2F4 +t1>vi8t@Fd;->Kt`J~^IC8?dDbw_WqjJ`jA{ml<=F5_>1c>VhPL;UgLzt2ft^)~Dk>9$@Q{hY}>6;&p +2zZCRMnzRXTdnA<1Im)veZ~&SI}k@kK-L?}>$%_Ah;M;aZ66=#gVTvu-f&^PAEmPj +M8osAJUJAf&mrp#|;+-%plHQDFbkhC3ljJ|s(h}JH+I$2AJ;0hbV1-iIE}j)zS0GHAnmAEk4(;Cr8g6 +Wp9WvYm0Vr{W4FLEERIICuY`z*KGnGI44ybMn&6N^BTfMk;>Z)mX%p7Ps9Zp?X~fLoOw&v;t^cND*7bir6~@=CRc@1*St^QG5R-!kSM+L-Q<#*ga8=`|iQHFk; +eXy34Rf0VMci}HIL#>tU2G-@?}v?)TLh#fTGD|M1@*{`L|S2eZsuC6hq-VauXuHWRF +WZRgI_Ri)&$Ln-}Jup-KLU#iW6fs%=tjGBync$2bD8@dh5GWd>c7(`BCesuU?To~!H2QxW4r`)q11o2 +dQQb?-^%rrT&HUgG7^5>1GfwO%wdnF|`H3oRdfj3wS#0`y>-cvXTLMIV>L3iDB1S}D$_8*z_d^{KY@J +y_mC(qf29LL4^Y9efJQX&5jpbvjQb?7d#g0Rru3xMDG6~vamE30Fje|=0Ja}cjiWF#n!S-$OE;C-qbd3Yz^&m%|BlTh=l?%KHw4Jz +`?5R=?+A0uV@&%M<`&@dWXT1PJC!fD^*lb|Dj-6CG7Tkb7XmBo$dIuf0w+YgR*_3Wm{-8oW%wD>`_F} +sDQXY>rNeNB8;g(47E8{B8DC$MGEp(=4Ewc#92APx)5=Lo3kz|-pK_LX<|T}QYrdw&}rB(n>GpWgWeP +nvNIx*?&b1`DiJZX0AyydNX1xX3v^$-p#-ylR;+Hbd6y@(O7CkIT$dLQREsHmoIY0bND~yPNYRxE-D* +c32I@$|IbQn!#$lw^ShT4d!^)yuakHFI)h?)tdfCwRz=!3U!idVr09-*Lws{VI2UZ%(Wc^c}R24Yjy6 +#x{2e=0uHv}W>??ou3 +8^a(6cElxT~Z<^0H;=_pkApq6M_toLv-~(-e`}6YR>4L!n11Is+k-gFF|Iy8R9x-dvuKT=EoU0Y_d6i +etPzT`2ey2Luj{#3Azivk@+H+AWp2vo~Y7};UEyo9mz8KbV8l!=z+w4 +FhEjDPR1c`uDKj%DZ#{;utiVrM*GiFKU4TTro75TB-N-6W=E|!D(=OCxKD7vZ%8N2F_3nCkL!45ux_^ +_+j+?GRb$%R!-p^X9bkeHnJ5@~xy_N1#R@_8tSR?W#{ +tJ3o1VMS7pURb5VQbhBs8&HJrV2{*6#?{uG6jY;X)3%K3}_vA92h~4^j&Db=8`^F7##&8<@Mz4wF@Fm% +>8vH3pmuu`bD?g7(G|npe26Rf&c0dDja|(m||gOA98DHq;az+WFFjBBqEHt#aSxVWY=I%2zQ2SseBi=PLGXuYqO`l=aWQ0}8GQnh?!!QdM??vusT5IA%#VgYmB@3i# +1B1trs&qDt-SElXaT&meR`f$DxZx@+K6Pa)Nr3x0(G7>D?ZBWA*3stG#yy?nEDR*aRC>!a!5DOW!R~1 +G5A2k)PmZOMR2<9sm!db4q*%SD@aIv290Y*(6h`V1%xI2;hty4QMqa767&&=r5RH0|Xr1>qhGY&QL(` +Vv{_Vjr-rUro4j84i2Qd0kC1>V}CUu|bXIL|>rm$-Wt|Wsw*iW~Hhd16HQ5y=xP@0B#CVdyCz6GHl?l +siID`Vhx&aoln)aBGvUuc~&wFYhKPFwIH8^nf&%GcIQc^dsoEsy5ZfvS;VD@vX$9%H*c7}{3L()5<~Q +sY%thDBM4EQv4Bpd@ri1D8|urR-cGPUXW{G@|ojmR{02GG=o560nPzLUFc_v6W(o8eV9Uh#$nQ8SWhwJg2r&_8q=IhzrqqGddqbbbP2Hv9@D1lMV%~hDpdSFPJdUJ5vWcb#|T +@6>ZqF~aGpzwk$FrA|5nFH_wcyNQHUSc;bv7_JE;A!RzVo-dUR?q`mC|97wX8Se>f}xxE}NKXq{pGNe +MOt&w#el=v}svD4qzT_9@xY4xK%m6;(=(55 +4vOSRVnk_C}HSNq{{i((mB*ySg~s}hS1`y5lh&x?QH08!?K>G;!>@2JW6`BPQirk_3`yQ-?uXA(at*- +T}dL^;ZJTFM3s6rpX&hrU7XX?ruYFku-e`~1l!>RQF24S6M1!PEM-amzIyAgejE$V +{Xuk)|8#Vz{b3@UUsn#q-uAu1$(-uC1N_)7rOhZ26uAKKf*xR)@7P4GTvf};eW$NJAHbN)uIT>rFFZ- +QSTX>+H1s(S$QWpRJ8Fzkf)D~^7PwBNbwB6IZ7x@xAaRZ){g;oxPNP(Xs4tvpI=5F(sgHGbW~Mkeout +nDgwRGd366k56zO>Hj#5Q{^UB?X)tK>bf?keOt-o=`35P+x0R~o7_jO+rPH&ozM?fiF>;zU^R8+1kuY +XR3||{8GSM{>>jsaG_inv2d1MCEFTT+Aqesk9GaL44pIa9_v9v?^AFu1^W6jIcsd=Eyma^*EB +H*$r|Qfj$uymQuzxMbJ;LN&mTH7ZDYyu^#QkX$k#^@dr2xg`U&Z}M>|@aBqH>G$eMgmoz6=d(yvkh0) +}bDMv`m}=1qE#%0s7m()T!N4)ZhfOnl4eCegksml&cG<%*d@ZJ;_I))*@@-uQQ+nL +Oo;MUe8d>4pUmJ2da6U?bc_07tf*NfXD`0=>Fg&Vz>?JAOM0Pxdt<6jxsaKM#cryj>zPM;NsC^9uumE +S)$Q9=&|LUDHRBst8P(SpQ~E&+f^w&E6?IjLrzNa+I;F42+|Tc3C1oC)N{5lTwG+JnjaGyNkXq`h2&d +4=)l$xOp(4Pqub-djL$y?oaY0an63JcjYM1osoOVX12K-E&V6hIh;I5IoNo+|89dsETGN +J?vr9$FIGeZqx_nQ-#!P5X-_2vw&p3&8FxOx$UJ1Sh$i6!oEY3O#Q%|1S&0YcG(ntXnn_2*Kn8vVUKB +XJ3lCeG`1=yYr`2q5ePh5g(H8nEdHij=T7G=t=~7)mfNKtF^)K@^{j(~l^$$HF=FfXTv|{N8u!hXj&Z +rm5c3J3R@2P@wcgoQdb+OYs)~MueaQo{1OY&*Cdr1W!ZJo?$F6c!JX>N37a&BR4FLiWjY;!MdZ)9a`b1rast&>eo!!Q +tq_ddnQt5ymA{n@kc5#}jOW$nCM6{v>Ia^}-9!%=g}t#+>s{ue9ly42FUi +V&zcRS!8a<6!Vd7%98VIA$YNdy9d~j!7)!f5x1H82*$#_sLw@0%lu(#vz4wr*i3TD__LZt|6>irp4El +&dWzBKtmIp?DQyDoiSz|=23w_x?6d)!5jW@KZTYz1H@EfQEd4jP_1`T0bm&{Ewx{ +2j=jl0IMV7RSD=t(GhT^|d#(8PVlZ9*RyFSlN670yobiKw7jmL^D1CF*Xm-b+OvB>{WO9KQH000080Q +-4XQ)WGdPksad0Ei0!03ZMW0B~t=FJEbHbY*gGVQepTbZKmJFK}UFYhh<;Zf7oVd8JlOZ{s!)z3W#HS +`?4~hro8xi+~gjc9SA#zL5A91QH{SWF``+l2j7+*LO%tGG#k^Ss$F39L~IX^JYli^->v9527?uwmRZ| +p_NU;MHAIZb_6=cTiSuvtN}7wT>GD)MbDH5H5pt0RCjL0+n8;S9;e;g-f$^cyCUnMZz1wFJ@0A$2BMO +)oBp-Q6=*rA67+!;#w=f16FAmAl)UDk^oqRUH%9r%DXQS#fh*`h7(KbT->n@v8seEw{NUOs{yf;6!c@ +30pfF1cA0@bq=OZ^#z%>|FF~iQ4lIqwobl7Uzaa~TwDz5vMZS$U)O%&NOA>*f0y=VjG%B>}NE?5V7o< +>nrK~2gHl&|@iuFm_d*+`K@1V4L=*<74Q%<5^T5nrJn +wSlch9i6A&bgo5k&YPzr3oYP$hbc7Ch@L{G;-cD)E4XZkerckC&r>7vL@UXP(hDgux?fmYz*ie*iXk^ +q&n%iaNnhpGZZ5|KO_O#P*UivreUnd?Aue3PFNI}K_N}WS`ASAG)K)0*O-TPCV|-KjOrG5)H~O(BHl4 +6E?BFfn8PvL)Z|jUv6fd46EhjPtVyv~yMk;OGAfV`XH9CUkY~Cm4FcXfM!0r@%w|+y$Ql9rKEc0AzVQ +k{2uUm-)+Y~`9f4aujwvdi%ZXCmH7K2Pc>t5_ok@N6ql1ah(}`I>Y?7G9^hI)!bgWNMtxK_{X&MFa_I +z>iInJ?Zu$}U5Y|;c<&t!{MchVmR;Zprm(GI#io8(djeynb>xiu-Udb@yWF%xW=C1^~F1*EhXbMujwW +uC0uAVB8g>+|*5kF)jJtOBmN(1wW;Tym@Bu%$NDOQdm`G82^pPZwBsO%_IVt7&$yiD;aQMKX(%zHrbY +%EMss-9LYyT;5#V#z%b5>CQ&K5MBo@;fF1KXG-9BN#Yy1b6Cp(X+cH(rsLIONIPa}D@)fgqagRdV8N! +6%&P|-=@fM+sIhW;=-sIrZ{U2qluv{EIPoBSi`TMk<-47iEf+w*`qV_C)DX=(Hyb;jZPfL)hnU!J7Z< +ljFFn&5su{uQPKf~kYX6@(UbJOsRkjklIt|*-DYR`teb#^YGn?2uB9+UVhxOUj)%ks>6`Wu`U98xF5^ +}qvKRG!DvL@65>65+9AhFOWOxTt?`fGl3(N+z4$?yWgDfXfHe{lN8*Wc5bf#BA#Lo&A>MU&aY9Ra~}W +gbG`@ugwZP#Ub>?y(bC68ZcBxa}%zgX8`*IKB|86Dx@zH1T1z6()T2AT-{0D>n^r1urb=mP>)oXhc5!&A?=U;Jf9_kE^wr0Sv8R)Pga{!~s5-iRhW&{E)oh>#LsghJu^{?L@DciE!1%23nVFnV*1Qz6c +vmQ7x_Iw&$#p!dV0P&t$(`;8uZCp|n{_Xt`&BJ^&2|H`vY?=;6#s`VNj8Ul<)2%v+~npXifEfHn{B7_ +Qij)=3~Iy-OI0^%iVtchaJ_xxK0baJIJONh`KQq~>o^y=RxkD;+vdnhW~94LY^!4kLx)7GnGxTsq5NQ +8S!qB2qTgsuZ^33sdMp=IBqu9j|t@FaDMQ92>0PY8u1dgWsZ?Y&(=Ve3XCWv_0Fic&~faxpj(jDS(H# +I+>4dr0!>btXomkq~)KNzZe51Iy8Io<ZxAA?paP<1SX{-l;hZpeiwHAG`qdeK|r +4y1?Mbi+SilyCk{AS>It?v7y>(bYX_=$Zl81qIA}Phj3N-#-QGCa~xl{6o^iy`f6(gKbok<)bYICdbSy6EzQ!6w;=;F*d-8Dv0a`EHe>YH>FB}Z2-sI?z}tk1abBD;$40>yUuTJXTP!Yu0?^`RQXV7%Mp#4_-SP16wZF$Wqld5kUXWP_#H#*40U0T1U(e@U7CC@ +wI9PpZ0cQ}qC6=w?kWb_vCE%Mp-v+s9<7z3A{^YGz$JA#yOAmCxNjy2HqPGnR*^Gg(jb;t4s>`jupcf +ujAa@nfln=EdP92AFeh`T7K<;Uk53N|+0gMB0oLSfU||ut%y}YgH^n%GLpx=<>#ODR?t%J<{4*aRi@Zs!3~NA +bAc*H_ekrb8TLhE@!EUNY20MJ&^I*sZK|<(!JexwZ*Wk6a~HvujW`-5U8Ml}_IE0mko?KwfZTc6HC(0 +qMj3{MU)U;Kg>Z)Oz(;lfGTmDlnHdQy{m?F%?Z>{_>?Q_fkl&%UpKbnW3aBqr>DGW;y~GTOrOSPTP*p`*il*ssrWp@Y0>`I54;ZaBF8@a%FRGb#h~6b1rasjg!xc8!-&V@ADL5dfCv_4-nX%wuOc6(o0Vv7-y`>ia +NHDoTWqQyVrJ-`O~ILeHlFR_xof??l~MG4Uzl-=okYhc%Uw=;V~hby~8zpAxTZsmxGa_(y!=kU=_a~G +^2zQcPLlwK6{U%yeCY?nq)Q&(`@rQ;oNhcn$j@q3l-h;Uhc;kLN7PDiWnfzxz==;a`l52QC)g9B~7gT#5S-+(cw +dC-(ISkpIJAq8>24P0im4ns}HtGnXZOTL?R4u?rUOR-*uabQlwS^Hc&4HT;P=Fc<+`g5SXtm5}6nd+W ++r*j%x4l|{qV^U1ku@6-Z;DtA$Whxhl;w?+B0B=)Ozvt3@pkl^j2oQLRUX|AJLdb|6p^0N`FYPBeJA+ +1)EsPSK!8R(Xdv@RgMUl1vwwncIVokxYK``sP7O?n`Y +Pvqu}`M(>$BmQrn*^vfTmbrHKNoz#jK5nIC_6{rk?;Eb(z65*OX7+x|9t}2uA$fP>xj1!(cNVoGFPV2 +%3XDIYL&tj@`s(WAI$i&A`72wodBE^5Y?2{$#I%iwyr@S)gi+mtl7GVafY%lK +>%%wZ?{k)>WH3!v{&(n=<_Oq=l6=LNZ4W5~4PNM`YffLZGmKt|n_|kcG> +u`K+$1UecOy>Xmc3xZg%}H +C-QGl|vJ6kNmU=aj?fSgfw(kJ&)Du5J8w4VKXCoPXDMNLUGQN6w`^oAFyu6x5SyVkft1d;zqEAU9A)5 +?NLGp%xob+JJ+2>6L3Qyx*NO_k?hK%}~IW#z`81&hD0o3Ylu^l^qh-k-ts*~P%I@C3sMsy!%Yn?9s&r +B*FLc(^aar+!U>THUhbhVRE=U-vp{)J=`jq%S=t=LaFApCQc_YqkbbW1=o|c5Nz5A2vDP6ku5i#@1R0 +$2_hwjr%=K6NGlkxWJb7Q>FzmiyUR`xTqC%{ILRQPX>%rabRw4sN1NVDo5Vk)qB_>r^da;R`OdE4dW` +j3r0&>h?P2;0!gmTp1b)VAB)b`i0BhT!~IrjMD&zuhKmr#M-mcy9-J$fl +Boco89Rz)DqDKq{nZ)KB%x8T)i~eX(R02h3{4i{cou2bc*Q-I!%eJ+_)=@wz=U@`589JIrHL9~J~Q(5 +RAD7id)B!SG{c3~D5G(ysFee_MHln_5D^A_r88BMA$ppPLAn*$S8GSCb|89P~GNz8r7 +sLC<&Fe_w;_DkrHFJ|kQ#-bX<=NDO?{S}ad>>W7$t5?ocxx&kKHyazU5w$;fl+iFi}AaKZ$b1rgot{5 +P!WnO>BWifah4+MB;Ily^#{^FP)h> +@6aWAK2mt$eR#VBC0E@{C002rS001EX003}la4%nJZggdGZeeUMb#!TLb1!psVsLVAV`X!5E^v9B8f$ +OcIP$xH1y98y_MIc#1BX2X=L5P;wj11T6QpSuhiu@|5*>3RlUh=;8wCCDH!~z9QdW}g8fa~a!!`Ykvp1*rLr&q7lg +ZuG@R~27X!abcg!t_r+O53 +gjw@K%$cr*T6(ZCEaGz^h-Qc5T-gR0$CvLr37i?DM-jA*tZeQD8gP6(yZY|p{n2kUoSUz}c?uq(0EJHb8;@qS +|ajZmyKn)Csz*Tlhh2x69M>kLRanR{ +)$Hf8{5Gt)*DAEx#Ab!V#SQnFGlaHcD*HBDQX4?D#+N3n$Vn%beL7!5hJN48ip3S5Y*10_X`Z9o_WYH +@8kuAUs$`t?-VJ)5e>PDDDZM0vQ2>Z3HiwUMHBWRE0?1NE;#1s(N7eO7B5&twD8wR>X0ExZx@9KtbEm +A`K4srohXb$j*9>h9?5oXatBAe$P4}QCX30dBrZ8jWBy@!Q1Glz;t66oU0dq{xlWv{*DSk8PT*xB|u) +=Age8Jmhs9o);ZZ4(7qkfHoQ)?{5>HHWRUZAD^z1iBc-%;#UE02zrsDyq53`Zuz`Un-~c5r8gNDAm#r +RH0pWx^sxi}ijj@E#j1}YxU1|kWX4{P0ShJpb)Ra*)RNR?K{+!j)@V#5ud(d3LxuK^Qo^H|9CfQeEIi%bh;T +O%AjrF6qxw1p#q@QnelBJZdwID!>6pT$;?u`K^pOEv8bh%!|ETdNx}MRB3w%*M>S7uJl-HrbgN^}tE# +{mk2(5j(-FKW1~?T55S$?r5rIVd18qSOw(Xl`J0Zbbo)Uv+#u;ozjVaTtwKm2R85ZI%M +<#!0@|hA{W+v0d6O_T@>J$sH8E2PY7Kq2Ehy#b?&vOb(C`?Pas+{N5ckccJH`2D9D?cz*xizqV(bJ75 +iCG1gGPmt&6}F+wrOuNRjMgK+VHI*7PnyTso`X+B;b2o#qw$fELg>HVBt;1q7O%mLdVP5D(m+L<(#m`pcNmxzoc-TAwRVLDwAx^vOPBnzvZ0*%gJt!&~^N(4@8aOOM +M?9vJuJ9zuZ8Kx=o39dJ(pGj!8#BsGT%gMS#8qaz!h&PXqL=vZ5K;)^UV{(k;H@iGYW#Q3?;?a=GTR6 +6KQg)onvXAn3Je18P#y5-ZOUIIpqs5dJzUffIFOJ%R)%dY`}h{vw2MjETKL9bst+25B%0^c4XiL280; +EmR~?L5lcJ&%XQc7G9r$EfH4PPw}!P3|gPT2|$717OYU}&K50Vn=Fh0q;0lHM!66@-`ecKp;vLVASPT@Bg^Grz>%I25FZCIC8FlQN +lm8?4-wZcxb46hV}8*MyKKl>u2|B%OA>> +xNwQlr7N(jfvdZl!`cqV$wI%x*tqplGL$>rh?SzP(NpJsfPzc5nl#w{Mh2p}Hm!&?Fi^+@CIY8(*=XG +*l=M?h8RBLM>}D`N+8z&eTDwV5-z9IBS#k_&Avk9AhC;Xb2KTLn16TvMP@{DNI5CE2>U?866&adq|UCcF0QpQXd5Gki$AKs+aKIA}27I|xYu!68#Kfi3%_7J%N5rlkNTw+-^3!4L>8u;Dw=2MWh9;0|+V;50%QAAv +$>AZ`f?1OFu0Zd_O%W`$(+$!62{nW-wL*;T6)9xpVj-&iP)V~noGhnxebwatC<$qQXSOlN8O3zbTr@6 +;}D>k{(pG=Z3PLs6_(P|<0rlOU*dJw>oN9oubf%tH@^XGyHUmo$U<WUV?$>9>cu9KE3#GjiM$K>dPuW(|QgYv`lFi=q0;=e}k8paqJ>n +@#5ZA+-`g{&3Z6z4s>0@&cdw;=4N=;>jW4b6q4xy4?MOoP$5HgnraO{nVVaFgH;^{Eb&t)75?qNEIwSVH?|xSNbAtpn?8m+*&j+JX!7lo?4PBv0nEoFDi(j)1DK^iptk&P$|ww%3UnMeVD +Z9Nmy(3cI)JYsWg>7w*4nGonOSv=2$1-TEs$hWZHEg(G_gEKfsPz4kuIwCrtGKUoX}$^`|X=Vc9P#^I +@tN!TXJ$i4l<8Zbc~}pi~d3e-7dr0?!7RK2RizBYi$RXB4dukLq<8ZV`ve0VjS(Cr&~Yen{ebE}IBw4 +b4bZ4FG|kNIQr8#Ht&b&cRf$7y4|p=J-lWDL(p9#8%Bo;Ab7EtWbH=wJb01{?LzCZvHqPoI((Mub`33{)yD22a4Yn5zh9yT3 +Zh7Bw$Z0z|sv}YZp9H_xrI%@Fy>#2o}&rt)I5yfNlnFKRgZg}bD_(7z72TEEVzC(vcUPl_%u2&uNl_z +|}n{v;_ARTzu0XL9#GtCGjd+xe%xC1+atSd*Jq6JIIfRHV^+24WdJyWVZm)V?QUXRQl{9uZzL~NC)A4 +`hz5k#S5dJu!lQ~bE`{>!&E!%w(+Siq0N97zykB}4d--7b1N9EXah*aPc2+%P+Mb66p`1HqY7U{c?6f +2}bcQXeUAiQ#J0JZN70QuTsBU?_*;J_;8V85Aj?dVga=K=3V$MmKq +$yEdV}YrQlYZ!eNtz@@|P=C@6aWAK2mt$eR#T5fn +iJaz008bC0018V003}la4%nJZggdGZeeUMb#!TLb1!sdZE#;?X>u-bdBs`lliRit|E|9RktY*sO0jj@ +^l?*Va`h?kO>LiRU*gVOJRFDwCFCfA1;D#gJ@((8-37phNL`zmwB?thh{a+bzkT84zE;{Yp>?HZ(*2N +DXsc9d!iQ3Ax3b!XpUNaPx4ZDuihW(kQp^gi_AFDC6%V$Q8|K&757NH1JiCp<;+|K0E415S4>j-(#OK +u^W*0KEF}nmYxK%o4SGAR@;$_UP54HH7!>8{m_%d@GYFQ_#1kM_0lE%u?BztMHz*AJsK4yAvwGguzDk +C&o1+9{;urKIal%^Hmi!@!#X6ZFh_|u|!dOeTXMm1HwnqPyu7gCE1L_5rZboPY(O;)*Ksvuh^&Gmuho +-b0RxkjZ-GMRqP+I?L6DOFDlE&0&Q{uKMBfW4~ax7~2gn$SDTea)aVmiyT?s_iD8f@%H$Rp~$0_ +Q;#W~Jcp+SK66aSb~pE}*V=()jItUvdP12N(-!5aR@Sv#TqBL9K-m6}TH9i%EbxBc43z+*&O+jTNRd9 +Q7Ri)cZnZjv;^vvr47~;3=PC+lxzt(tyo7k3D43<=YYZ5Tg|nP>8S<>NALFWn>C(iG|bXB^;vio;qam +ruKLW&jlT3NSG--W?tfn^H^Keq|35CZ}DJD2HwMj5<~w%e7rKwc%I3LgK_UD499h7kfP&(>v|ar;2m; +PeB_xk*V)}*VvZd}3vk>MaNq4~6FRKUQSy1W7Wyh*hI3cDfbbh$8h`epS1y|xyOg9=$NLT+u&K8vSRz +O6-9zd*`w+ho+s?GVE0fVg${L)BdqFDX3};BhlW)cRo=C3RAjvgewDgUMxXsAujyb}z=tWht} +qJjCD?`J;~-5_K{FkYN{PY>uSd!w=>W$y{mb_Fn_?6gJ9M?$JKX)FP9`p*50$NEbTP1%z&6ximWS_DlK>4UEU0qLq~|&MvP|MeYH@t$D&hZgMLjZ5+FRR$C(+DKH9h@0w<`{=K(JGOtdzlA|OTPmiNaJ@F^S^k`DV47@O~S1A(VvNIKhqz-8e}zR636_ErPp*odOh +Ng>w|p{ZC4&cY-eX~uJ!#hz1t1FO!U#R7+~aNHfb@`qizXA32eq)<1~X2U2@bmCl31^^WzNCvcmc@ku +-O?B!6w?grGMx@j2ipx~+hRMPBF!%VuAN@A`3415Njl3i(N+=tVaj34$D9A(RVBiG9V +EPi6O`xj{t(PUE^*EWszLeFi$2WMb-_jlu-#}S`BRx>yoPp=i@ZD#VX<1tX)2YHV2(6Z?V5}z%X>a!`AeK6s2+uDoMeBS<9h@>TOl7-#k`jF@&kI~<8r>}P~a|70C<~ +b8+m_BHeYA#b2E?W;Kgy|45K+<;>0)$etRSK$~j`v@u3Ao0AR~$al*G^JUn*UnGo9pECyRJ4#cssCh8 +K;VhCiXM5hy7G4;vYj{H3Tk!RqdL1!Kfek~qp)SX`D6x=;?f*pYwZC*7DswcPP#)stJ@iZrBCm!d0f&AfBwLh3D?~E9Yt?Lb_zhAAHE4c6hjXwma>qcTDe%HF@vHUi +HpPyz(NK1o=FsP%R=F$+NDmcV0fTF>!Af#K{Zdwif}dsX@8O=fn>dKj9R2yl@(YqpR#4Ss&i2CcoLe{ +8y;z@LT)z4?efIqP&0Fltm!Fc==f8RS+u!~E%Rl__Pk%mjg%!5|^fyjnC}>BAaAk#w;nam!3*uGFF44 +eF*3taeKm0K7lF+E(cjv9#pV0nL9PVj;>%`uQ3NK0owGk%DO32sv+EuLfCI!cxqe~{Ymo82OU3Luaj) +`T`l~IHiFSnqK-R*IjE_vo>#Ndt|@}Sb;3NmRqkn0Aw&dj+DGrlWvJr*&PBu50vu;t-4)I7U|0B|z02 +=TIkLdRYoz&;g00_p|Y2u%u3L*05wf_5*auf&j)-0Xy3NStOa- +Z@}erbc%YX-e7w`c{JdH4>&Fvz!k=4wq#ESgp2XKREc|XJ)-;5oc#(ENq$taidwN2@DV9){BKSF%bER`HTl +nKPqk9kWrJR$WnjX0V^y1=76V5NjOu!bqa;I-Fgd7(L&y~F_|71M4WlUlAJ`6EpVZN?N5coE7g1Yw^> +$750ZO5Gn7Tc92IWy!Fm(INC(_>aPmsI7aQR`%BjO>U|2?8d=p|XN|8e>DdfGEow*G_5j8i@CSUr3;u +-^R>Xw2~m`-aL%0j@6aWAK2mt$eR#QBwb)5_f007D& +001BW003}la4%nJZggdGZeeUMb#!TLb1!vnaA9L>X>MmOaCz-oZFAhV5&nL^0;R?YS)D1%?${l*a>i} +sjHZe0vE&(#9Sw~mad(J#1Pg$YPHFnvySo72d9sr9gF92s#5w|t#bRHd1rr3pJ1cW(r7%p3oLiAGuG6 +)=5+5)!QQy)o;DHeazS%E7U*vjK(J!?;j7eGINS&r^JQy?ASW +-<4i27RTv=mjmj!Crh!c?<5yhfX!;=P2(!mt7x|Vgm1b|YcC8BbB!05rl{3HH@cRkcGs`{YB<^exZ#d?z3R)}kh3M8>YyGF=1RmC7N+*qUEq7+A#&kV{x0P%^G%zKt>bkQ +psJhLDThR*xbYZr<@9H>8EFbU_Qekfwy_PIAFB*atl-EV*4DL7Zxm7~}0Dyj(cBaT2z|KXDGjvJ8WRA +ZmX9_+#L9l1l~?a1ghI7nzS1e2A_$M+}#;$cP>LYj*&7Mhr|%t5DZ|CGm@n8n*s)w3N<;%;xOSNhETV +sk8B&3xTe)(ufi9N2H#EAuqfp*W8TsXpWlHMM@-E-DwR +&o0x<$7V(^kE1@A2C}|ozqAFlnwE$5>YL(vN7%%GV9o5d0s?A0fNvXwB-hgRe!8)42;G&(px=gDAM?4 +2r7z<0LeJE%OV0&Q>^3B-W~&}MSlf!@G64B7jN{){z^Ebj+{d(TB +67L1QE5%(l;?)SkG*~89ljU6G*fdq?h9IH9$^!4V>M3!R3uni6RQ%K;Z<%ENUkC&^U45?NGppWG)PUY +L)7~CfnN$nPyn_zm{hrSdZj>eoCJAxWrRLhX;qa6*GlJ^3#D`o`?CF;$Yu41s)R5CI~19^Hmrm?Te@< +Ssnqng21U{0(4~d&k$U#*8T*R;dsqej#U81{!^5bq5&&Kj30Xany`xG%Fh@0%8F&-w25f^Q0B9UaNVv +6{J%7#)pZW?3{zZhCUbR?NIoD1%IKYc-qYgM0fD1#Q2vp;j4;A!H*Rm)}*l;VbqR>f3V6e<;IJ5qJFNZ=c~X?n@j?t6XZ26@9-ksCp%h4vvSdL!u +}}4$pJK*dIk=}tU_k7A;sa$~i7bLN?eK+NgJ<@v +vJsnw&@Sk^=5g`Z;T)o6=8Q};$FfM~iJXfEwFd``mynhbt}nqu*g_yUM_&F|F=)~XCkO_Fck+49HPSU +NR-lf`U|p$wymL5+5EmCzv)b5*dlqW(YFMk>B??6DC8n8D6pcebQPm3%BO1i#se#s`{TO27CHfKZm}B +8o*D&_V4z_YM0)(~==&uE`P4C4AEZaHEMR&x3PI{6>a +%NBjB6|Mh&~=(jJemz`X?oeM$r&TMbx7OCLLmULyqB)$`|day4PbpmU|Dn_EOtVszH4@T{FKcZvL^k6!(P%_-X~eh*4E-Tuu>XeJj&ClDHdjR>l`V@{6q!Ml +IV212(;GuDJ672qdod{S-KP^iLo4;6>BIu~Z+z%I_N^+uwwU7~3GpnX^vJK2%qWiVbfA7hiwkL8yTNk +Q@dU4QT@{yzTp)#^>M>;MZ2YwW+y#wnVaM+6gE^+)C$ApS^uoXUP?mIA1MCr8L%v|(Lzvx{<@6GK++7 +5qnR1KLsl`tr<_@=Pygl?EppG3PEXz2}l@Z~19X|5T(mvq>jf}q@7u8-dl9zI0BW7KKAy__Nf_D~MSp +7STEir<6`(LWH4%-5gFSyoz3B?xElQ&TewNmIX>LjLkb?ejB9W)k+TKDomG|)#d-Zt60IrJ}uCOIu`A +K!~r@7iXP)6M;4f>Uuld8&GOH^usUplhxRuu{Lk1%KxS`d}t3`VjQr&^Dn%qBG%*rfWUO1s|AT-mlXI^=pwG0NYBKgU3&wJ(*;Jj#^jT#EBBRKi=TD4{|$Va#bij#z4N%oY +Wr|5Q@OqE}75N22+F9RcRZyFi`lR;DG~_T4hy=)k2yb970oyR4!$T}tUB@1 +KS7zM04+d63KDT^R5{6w?IaTM>fkuQSuvg}sZKCZ0=dX}MKMzjq=$(P^@Ev!GJ8l!ZPg@4M_l7%k_S> +$GcQi?O+F^@S^7PX6NX?~S+bGdc|Gtuje5aeZT2X(arVl^e%~=x#i+UNK&Od*VvoH%egDJB5rj~Y<>H(1#D=eA$_zjcZXs=%mc(cB +6q-!T{?pBf^W`RkhtPKxx#Af{KmNZ4514;Zb#iQTE^v8$Ro`#iHVl6EU%{y_HgfT`*&aFrDKK0&AV84-U9({5f`j6tYi&BwmE;@_!~XY?vV +1?fV1CF+l=$%@`B5?3fqVoCg_%|%dgH##yU|-~p6WsSfZy0WejPEyqVu$cxF6UH@{PM88gkke(_!7xc +F=J?eYm-OfA{cfQ4|~O;0wkzBd$Z+;}%O^Ho~ET%XbHy&un;X3gFLlpc(~^7|scm$t<78IC=-S7x1R~{Q9xBv_oH3nFEvS*O7!rDeEHx{+$9SrPA+;+%s|#IypfkUUkM|1NR?4)BvA)oKAM +eT6jX(>J5T^3S`H~3%7w@KTia|jude-UFQnNP@jcL_G@lUEvwp*mn!zbsbIz%bK&M^F;rI~cvR}1LW?= +(WI9gwqoNCc-43=mil97Xn$Pk>TH@;9G+=l`ixkwDf9a2PGMi$rE2hV=_2n*_s)s+eUbXp=9HpI6t{p +!iqYo^SCvW1uK6uW+kSm0iW9N}M#lPubhe=CZ +*!$a6&S&sAAd5^aq1PUTx0W$_Da0TADY(d%953L>OlcDke$#fupS>r4Y>j-Ka^x;k{;BTV>6-vKD1>p +4d1r-_gV>t@gVnKP88>rV)B!HnIcxqKjq>(t)@hm9U!=R)?G@ZPcrb25yO0!~`Uk0H#5J0ewQv`@me^{6Aen-G`5ryPW7q +F4Xd>&>IQxYNIfT8Gw{wZ#~@bOQ6P)h>@6aWAK2mt$eR#RGmvL_-1004sx001EX003}la4%nJZggdGZeeUMb#!T +Lb1!yja&&cJY-MhCE^v8`S8Z?GMhyP0U%{y#A`iCO4I3I1bB7>vi(*+DG%2>BE7Y?@*|*Af!rjTPvF* +Q)yt7{HC}{%(v1A^PR4}OknmLmReWO{55eLU#>C#WI>jkb-EN5v)W)OKZ8((e|LD?2$!$Z$e+7iOABi=R +qnf3j{F;b_y?;IH27c%}!cOPe2;ykv3n0K#LVYo7H&%A_gH%Le;h!G+{ +~iY?DLL`{3?UMtjEPpxgCi_dC +rqvXdaO?DMEmhJ7Nf7o?5`ui#nSIDA5aOb8Yu&8YUGEf{n8tC~8gp6hr#^^fBP%zP3fLQLbkWegqS^` +4r{3AlkT%#r#do(aX6Gp+o^iFTADut_?&+j6_BOgkm7HWVC3@$&ATZ}~yX&-EJJ@a%`= +^ali_Q&;m7InpXXetmR{Rjxw7eygMXH~=|notzYPMDwqB?PMTL(6tLEUOcl?s}Dc-_yh1jvV%;GW6Jx +@4tHy;*wF40%9u^#-7!r?3$Qnn+6I6)wEYC;+0=;1o+1`&NC7Kx@JfM}rLB<5N?3nnD=I6yjr7OXSLm+{rDbaKY*Ei$LmAGTOnNo;xSbJTJ&bfI;`795#iO(M_GienBBMna?5i^o&O}#Ya7Oei4{Ft_ +NQ~>p93+=~*~_+%Kv7>|&B78Won>*AGTENbCJz4cJJQE6dc8Ial&@m~)j2bZ7`-2AO1oi9BTRUG->X; +?VOWlsaxi_K?v4$O>r&lZT#o&rI59v9$a*@o8jU*=Yw2ko86M&f0KBhgpZv@ZM?n_Q8qjW={8I9q7ZqivBpErtRlrPrLp`^1Ajjt!}Iy +~PhiJFa&R}Gw`aB^2LJ#Q7ytkq0001RX>c!JX>N37a&BR4FLiWjY;!MnXk}$=E^v9RSX +*z~HWYsMui#V+l>kL;`sg7Ax-Hv^wd>Gz9k3t}XoKXp;JLiy;MA`0Tm=PeBMV`xdzH=d?D +Eg!Cx>gys?wi8Mw$W_eD)!K-JK1d5gEU+Auoa?WYgq}MEutt|EM!f4s&=!1Rg1N1YbI(gtE?7U^NnC` +#cTUGUD0u+h4Kr2rziW~>^jux-ux&Fld^YxSGn!~$PR7r@4x5e-?IA&eo2}8vaky~*=6~?Xv$WBw&IS +Xky&qKrTylDH*F&eUdew%-tbzS*m<|fmC$Xk3ZX}%w0YUATl}x&sz=09Hhr4fAR8l8!>deuEJSC~b%O +qEv;|(2e<8f5c9F&B>?q7@VR*?6Uo75Vzy0Ioo8R&u-n{<(#k-f^zq)2uEWS+H(`2z&V3YvxD<7_GBg +`xMEzZgJJjZ24#kFQ+jF%iw)g;RWgAc^t|H3-e-pdkXn=Lo2=ACA>e^-j4Eis%E-%ZQF4HDy>9A9JjM +do!Z=u0WqEC)cO$@5r?YMl-WmW06#0H3ne&Io;*TB4FmO9n3zcJZA3);7XgjvoLW+)lDVs27)vOF&JQ +kD>s$@nJLzK+OGSXIiKj{*QW%+KmtRQNr}98SDw9p`6hk3lI=S0-)kYgjHNC1xd;^6QR^c>_xw+MPnc +$0?p)oWtgDVl*;&Xt0d5XwQG99l*&TWxYXi^pmDGUKkLYvF+_TZJywwY9LK|w?ZqJq#31hw%z)#0IZ3 +A+WLY8B04onl!IBNxKj5AM#S4zx=y5@Dm?q~y;h%+JJXFi3YS~6Y8Gt3EFSB7^5;FP-nk9n`yxCcx^* +~|E^K}ajfoVAqiE5v7XYj;2kXRia!<@=3Y`N(O&JHAv&S$uz0(;|xNq{fYhAX2n#PK@Xd!>gbQF1$zY +s5!$oI9Xsf1rh=j006bUueuowR}uNpCY&=(_JM^96gCrcIo^%N&di?n?Mv=q69A;aRLjbkfFX4IT8lu +M24IpceFpSEv)7Z_930hB%A>U|!fp4RyoS{sAWekSc25&L3{vc-N{Ogru|z(XW#I^~?43s{xQKNVb4zJ_&~Ujly&P>v8(c$v +NyB>hffZ7c*|V7fK^QewTZmHJC3W>i)W4LS}xTgr0BnX>1X{#)xN55vk<=;kp_ET-6oY(~36 +QawV~s7>0ss0A>QgJm%6^xCkMU{5T|`xO|CHGAKMrSRtI?O4PuV3P`dSOlGdioTt~HLZ7I!7ZJ^Xlko +`G(t7hc<1;a=<|d+nQ#MmQ8LJ_L=z8k6ZV{4epYx;cB2{w5ecQ{REVX}R^f6=WI!&;KD2VGcucn>1Pw +47Rsq`KOW&i8hWeP`R_uVQD*0WPSe)^zS@KvOyqg +pK5F-xyhoadiiW9ysil7f=BV;0z^zhMj4(j;BXWuPY5?C0|y-*HO{-RY~zfXQ5Jnw-{DbiUTr6G6~A< +c*vMT92DXaYuCqTWtYla*gUcRLN5`v&5x}dC(0H;T=|p18`!HJz@zkxV`0UlGI<5(gMDD(;R&h#%BpH +Xz!m`k)t{OUEk1rUJm#9+gX7rHq`laT&M+dLHR;csxXuPygy$k3*Q95b<>ZRs87!&TX)QUAwT0=CuzT#jYjQhPI-miFZ_kbhWMKBhy2jQd@y;1;8wP +SMAv`Qd<>TFsXtfg>xrO6B@juGLI!hRNV^8=mU9ZM%esBB +nse}f$DCeKzMZPi+`{~>e)TRxrS?ff#tqRh0+}}##wTzCBrj97oVcGhXiqqYu?3#rQ7Uhkt|A*fZ*?W +)mvhJ0jK@M85j(Vc1oso>l5SVavtmXyg0&$mCMe2_E?Rx9x%3{NeFsag#`<{AC1Fa8`P5-8*E8}VxzZ +IdzDy2uHjvjY@ml$Fnotw=r-#yjM@GhMcv6L!aW;=nEY3P=jTG1>O(h_L +7%6BHox1K$Z;S4IN*#z4_c-2)g;{m>~b@ekHsq8FC88<>&19QWJjv$}wtxc&z6@><MtBUtcb +8d390EP69y;zVA~s;UFZq(ZrKi1Bn+8Vz@44w!p~Dbm?@6`1IBVqe7U|B;Rk_0ZcwR&IAa-N3YaECIw +!B3z#!yz|_L3B&VKJhRonF1dLCfhzA(8nhgO44HLQB+<7#9;PI^WfePf +C(7)TUk3<}-XVBc*I)FXWWv01*$6)rWBIw-Sz4{5v<7@6aWAK2mt$ +eR#QPhT(9B-001cq000{R003}la4%nJZggdGZeeUMc4KodVqtn=VR9~Td97AUkDNFVzW1-NSVapOhA3 +C0Uge=gyV58tMYFlIMwSOmW5w8xZMtXj>$l1V8oKFB4&eeWm+SG>SA|7qwDm;l{a&d3rm?ys{@K7c5p +nBIIG>Y$jTc%mnUnk8NWO`hMwo&M<++8eqW40&q7$wf&;BjynyY*M1Qu%HjAYN$FyCH3?fOS!b;X?Jnwqn{-YY5ht(I}Y0p1v|4H|%{%EbX +;pLWWN1Tg2_jf^-QmRm`NUg*dz# +QVMkdp2`x98PO6rvs*M}2YI(;Uh-0ewpskzMdz0jsGIuU?uDuX7muFW*@={4iKFA<@e7$v^c~N!Tx4M +q`@rD3c^pQDEEk96Z_b!Exk~nWOa{E^x=nfVS +toBlnc(HLmi7;&I%sfe%6Z5Js#-5a6ort8dtTy7%Ojd*z7N@Sb6Z4 +@9~Y%=|Am=53c^uGwk-6i?OF0a=KB-f!v1o}47N-;?}2u~3XG02hHgSwwIP+pEdc=ps9n!W{+F;<`jv--svTTTcG_ayN_@!Wx*gkWZ%%NMvkv66WwQK+4yzVVBi45q>fjC6=pWJMpI8{Crv7xRXC?~L*k$)3G&%6%auQg&O8j|8azz(dRkG7RWW)B2*U==-J6G1T`-HNQsF3~ +frK&I39s#Qil0|p%!)s|Jj4@MVhYNraAFS=j<4MpUL?;!;Gh1Ns52_ROdrrx$e)Gyl1t0&b6zcW=Nw! +6iA;e@PcUh9@+x!nuO9KQH000080Q-4XQ|r5509_RT0E|Td02lxO0B~t=FJ +EbHbY*gGVQepUV{=}ddCgpVbK5wQ|KFbi%S_5tB2jT>@Ag(#-bu!B9(A)x%J$A&mF+MP2}+nyq +=q1+sHWz-Uv~o_!G~mfDqD4`nu$aL4WQAFUv~qRt94$KEX@}SnJtc_{+X*_&C8|ziQiK>w?7MEzg0yF +&!KIhy3Y#nTP0L^loa`jmAf@iXQstZQk7%&VO`2R6UA`4nmogqJUR{_!Qx>|Lfxo~9fdoEJr243VfxnW|tVRb)xLm2oVxEAbU35lgjB`7Xp2j@eSiB82(xr=zO*y2vA;>It +p*7ZFB!N<;lNkqS1VP%y_R&&ncCZBPG*!RKXFF5hKIKBiw6VwIQTeO|uKt1PBPbzc0;Q`vs8(VSHJ_s +@E)Gd-4mmdGroC5B}l!u)>~Q5hC_UWPc~un_BYLSK-g+7nkooybmwV-<?CS^f6q<=de}d!MfPBzZY`{cQ60+u# +5C?2qp}Xbpp(ePsTnh$^5G`*?Y2L8?V>5I`1rtzgHHmz36YL29jH4pvkPt)1$)6vW{zgi-Pu**b&0+O|{lgHtvM +q+8k0sCjAN(PD#8ngw{z->c^r)yYuwGxGlShVB?k3a{R${ANI%S%?~Y>xcOl1lM;DolU&3#doFRWz_N +#8UZ1u;a7Y)UfNA`X`T +uFTt!Q<5(=JbLBJ1^db%vjwOL^6nd&}NYT12+qjnx8AZq5>$gmxhc>|ilN{|WM`(wpKwvk1ip#rfDFC +^}qVq|H_$R@DgpnbHLV0x`o(Y$8UX(N8q^={3l&}1r=0^c+}Ir;t>wFX-y%Fqmi%l6P1y?S@yjTwFY< +-^7QgjW~m=boM@Y$vbMGUUYqtmg*#_xAkdEANQjz5wYgWwa9IGLLP0239YegEhbo*irfLr4ayhir4Vt +c2+aVNWwL=yFIGuNEOP}sw$BWL<@E+`T~~3S0YWuXo!g_I5+UWLlTCcaLll8ZoKO$!_H`#(f@_cE8jX +rAO0JSY63DLopM?RxSsmGvw+t;1$fZuNaMj;&~?f(lyL}vfu}xBdfX*DYSVp0@&VSbK%vkSfG;5B2}l +DzBk_Qa<4(WrQbtR*5**fy8@C11pmN+Plpz80WN19WC>W}!sdumGQR^|tB~7qEBOjpyz@cD%^PKF>5a +~zcH8?Bz0xb<^^MYs5^1mCGvE><=u!zHk>mhha=yVX2TWdvX=Vd +ASql!dN9tk&huY%mV14K!*Yucm*2384X{$tFkrfr;tN5~%UBde!yEJ|AhFk;%M$cpli%?F +wEo+=HLzt}A#jKNZs|-*@Q`NPhC@G$=pY6-*zsZ;-CJZd-VD_i?@NAcQkqiuX@C9rwp3V#9<*DDA +>9_89MLXIEUY}_$|hzgnSd+=LLm%wo~^+tm}3R+yJBI`6|v)iMt%Ap+Jp;0ZvNcvQ}Vlt79R%xTw(yc +LB*~2P^I1r6z26GvsG1W>m8iRua2gsb)TH=RX4)BS*l$k7nMr>P9sA=>PH5V*3XfUeAR)Wn!1_TXTUV +@0YEY(ZZlpzX|WnSzMe*%JsZ-LX2IMJKJK4t%ih!-6xh(3l{fF4UyRsmpLmB9lg8`K0u)Tjs|tiA}6O +Z#?G4f4?F%&8|A5P=GBk9?yh|Ne)w{P!QO&S&fwL<8h#(92VVoyrWhO>a8{qsI4d$>HnZ92iTx=TOt1 +DwAZ8lmKtUVSCirAfyN^AjC>54rWB=l}dLwAekenvo4JH$TjfGZfLV9=$mk+XzSXF8_*Qw*;1>wvOqGiHT@nGdl)Svf^Lq*Xi^26m*t~{mj!!P-d +o2{t>tj%o7Nb*BtniQO>pmkO3g;BQsXUld1uZRX`CUX +_WjYfwelODq4HckFlk=P$({o?}W4XVkUj$}Qyyh*eE1Z7D)+l@o12wGmO%ypRi-O@9Z8_@mKg#D1;j@ +Icu{bt@P|52p}r`pqyc)P=324)D=34O(}fwpJs&edNuYMm>YCJCnK}f>OW})VVMbrA1NutbRKb8-h?U +Q|-q#+I}OmV7FacuGQQ|2TQ&Ygxb<{=NRW7iDzim5laA*8O9{YD^Ly0wiF-;7JldjIWdITVea)aidv{9=9fWnD}aJD(@ub{Hd#x3sel2*hMacHf#Jc!?Mx?;&~x|RD;X0htoO9>J3D@?j>p=9<7{iy(NZ6H;~uqM_H5KMjV +K3gx3|wq9cH<__u%70&mFULE;@32G;%%3SAPN29wi|Hzr-*^*}q(T-QZ@EF@-O +beM1u)s6}C&8exMpthdcAa{!&#%tjh7_c`fx!Lrqieo*vo1KHQE`9{r}wR1oBLog7Bnp{dRQH#>e +XgHnHSjdgAq2D)%D)p2(&L1Uxe@JPBnA&$-D%jx+kWCKy1c6YMWdcjS9ENlX!01yS;8F)Sf5c8tX$DO +Kltf2wE#>q7gfpcrYFk@uX^Pz)Ndj#!FP!wEF*Pzva^E`xkcO;$tR5>AdCF7PT4g%7Yw3D%ZJch?#%^ +|Z#Qt&0{O4nK^0z@y#bK>6Wx%jvjz$vD~>5t*Uwskkthlo)c~{iMVy;7^+myZWy#FlS2(R9qTYd7lN_VBJEI +KtAgR-;~df*lSrRn}CWaN;2nj&?T6oGA8$SPe6DwW`FPDK@%ln5>qy +{SSqtZ?_G1A;V=rh?u3V)&Nbo*fqN6gb@7vp-V>5!lO`5flvjAdOt{Iw3#_TAp@ +X_jLA41`0W*=qq>L=@DgjKIY12~`E=p||K7SD`6FTj_#v14e#X&=L___cn>>&h)jB_@^Mx^F20!hm& +$qf^g>t_uxFjc~LSuDN|R0(yhZa}OW(Mu>NPdSsK6Y$;A$dOpr1cyD?F;Lk +6a_ywGI2%#XDvOTLJ&DO---cR!_#^{H3tw4Z5MmC7F)(aHm +ExfN}{F0iu6WF^s-jDk+T_muPE43A>NdhlRPeVL*&DRT)Z_z1r0Bc}Lp9syUtXz5r+6|F;ic(T4-VAz +H|S_sg#?JXy!#H&^dGbs`pkmi&d7k4~6wkP5M9mUs*YP;2ZZ@3Jz#A9w4lOTxTYZwhn8CXx8R|}`9uK-TkVj-3WBLKS +#P(6&57E}EVi+%P6@5Ef;MUGET{`#N)@$8w4d~ls(Sat~lMb}1`V?g?%*o`5^;B*I6-;~ucQXdW>fk0 +%jiQDLcTt}T`Cb5JWDiF*i#i=;X^^8sM_&VuEYR<||K_}XY=}c_NCZ6`lJnh`u#whVQwnb4RvZKI&uY +rRnH#caKrtO6O$el>*4Q+0?PAdwt^{P=>eebC^f~lOeTI(27H@luSeQ))o%|+w>tbEg@=`p))cE_-yw +?U&W-f(xwL(H!)FcC^`Z;d%eMcX!Wn++@nqg~5Sx(ta7G*>5akWse}`SCGbiVP_Ybc$thQTU@^&>ppf +YtOi5EEw54l=A$uqj&A`0G$I?wx15%g9+OBspFYU%(Tb#&}CRd9+YJW6E^tkj@txwp2$u;ckQN8)AY9 +jFKjD-9CfbvUcl-uJZcfX_GJ$PPSCKkm0fz4RLfGq!$JHE&-aHGcOo3k$PUV(Q|e_7u5=@M3B&ztv!6 +TA5H&f7i15?7xk0huK$-AQL@&*svlu+;r_TlS?XAAfiEN&09ZIzLGa4R!Cav^o5PtrvX1% +b1XKh>aiu9+d=xNja&p#Z&svI@mmyrVXHnKoVN>pnU?^2{MoH4}%n9A+gTm12!2Q{U120wK2owBIq_X?-2t0l5>q5a@*^qAvT3z(tk7yBx2- +T1%t8iQt+!jLw!^Eoi;d9@bB7kGtf#(`)-Bna)=8~Iy$CF=Y;!J^1B0|2?hEkib`bhelsCF*gesTpS7 +n{3Ye5V@8Kcddiq6T}=8d$)pyC%sg+t%X1Tj8pQrERxvjV{2BW;Egt;6*UxVY$xKqXg~1o +#sfJ6WA3t-+{5{3I@~}T@Qlkg5mQHb|F%B>EC}3yf=Y2&^A()eAovK{se{NMr+4uLvEne{xxvpj>|>1 +SP)iV*!2Kfz6JNn(^AfrXn=F%S`@WF_kROZbViCgof?p>yiQ_AO-Z#){Cg?QK*|T2H)Eg{wN?=5wy2; +1i`3vTJ*eoLjjSPAPDqt=?F`AD(5$UaDGrmRxQgV_HTroORvK`XE`#Z?VHgR%78WpcJ1)%Drub*YtTP +$A`raq-Exz2X#!U>k&M0<^e0Pgc&jwskaRvL-LcPJP1M9q+CBt-BO)`?U(gEpJgu37GH3lo*YsdTi#h +^uUIl|vBq}j+GaL%RL=wpC=U~Uius>^E2Uf36wIgP(j=r-W$t=|WsmDS4V5*N@gvAac{Gp@`DBq +S8JumMpotQo3tP;lkVJ#D^?|FndJZ#H6Y26Llwn^Dt-G)Td%!Uek`TCnLIcFA?5nh8AliG**OG+nj=# +f6`LaXX^4Tv(0Y{}-BEVwiw`z(bN10uTG1(1iPVYDiyTTroPl-ws76SmjNsp_XTMO*HTbdW}z#~s3n!O}PH{fV%ghTye+k3DfXF8qEdFx{uGlf5 +uh6lrp!ySijbyB_Fc5A-lu>9je7npyd}2?>gNBdcnA;!V+%KGhJUFUZb`sEZeBQ<;GjgrRBt4^JC(s=ukFI&ER4*uXvu* +BhHt1zt5!z?<9>1Pl|7nB;)^gVJBBXRB2b<16EW*Z2@}^8`Lw^HEY03#-bLA(PTw+JgXV&*MK^jb{SG +^AShtNk>sGkBhJC~pQzxk{{+$=g;Pf^Lh-qinDMd5zk%h5g2cYzID9Tn +V*5(?QMl;j%T6)xb!N#z&gq;v`Y7ofZ+j{t>LO3q2gWyujR(RZe(S<|t<8m~@%2KAV&wXP?i{ZQ}40a +38F!-1P1>n|CWm0EpG7a2U#;*j_D`gmN(CcGBm2!aPH^^pg%Xkz2Ve4?g9N9o%GZl+j&hhhHGf|k)$Z +wJIM^aQ_@pQ@tciJG2Q8R@{#VqbVjIJW64f0>$cPNy@k?_xR<7oC9Tsdk=TlazYAo6|HhOoh^lwM|Ex +PMUda>mY&remI}Zm18a+MY6Lje(DW#R@=qiG_?4j;-4iHGNOmJYiY2}=kyJ#CV@oaX}r9nNPl=bHkWX +pV1GWu&UTLBxzLn6%cG5n9<6wAq`hg`i`MKmcIL&Q$Hdr6Ozsyd}C9J#cc`%Y<9HL +9WbD)u-K=HDPCE2MG4BbA5FCWRdf;AMPvp`%ZL5jo3EyS7Jma!O9KQH000080Q-4XQ){t}Psu +UXv?{XOhd?B_#_*zk6r)K`tM5(q4aSedPl8QWg6E{#moQJ +v=!aBRQ}Sb3rM#folw8$i#wL1YnU-Oc78MgP71mXn$uLi4Z1?&T0{^~ClY|vl?7o_+?@21ljP9e7RvS +H%$|&ZAp&vnK1?Qt+5bX&HRW^PCpR8? +lyClG8f|e5jEzBHLJoTP7qZ0=TUN&$@EH0bVYO71#WIWI3mqq41Q&KpAu(F3Zkh~#`k`bU%^C*qCB{Bj6dPMg5lSG_*L-g8GiCWqDh=lxTVYb)+pTXYAIM +KHK7K8ng@q?#wac*Ej5URQcb!q=QhnDCLvQz66cJj3D|m&QaDR85u}XQpSi+&#%fTdulC-E9%2aTWqL7T>$ryu +c8)-6@5C~sLY0@Vhq8V+O@9-0J7BL`^G8wH9>@Fq1X +rH{l%ZZf44ARKz1mXD86}61(UvL;27G}GPpS49T|<#7%{5oV6s&*1d~3qI7-gXTSFb33TDnAcI#sx%_ +#YP-~}73)U@o%r^I{ap>&dNnKEm^VJA#=m7W=unQdTW?Y9uTSX6SCRvXR#Z!(&SloT`hN^v969zj8*5 +)AHB-lZi;s}2dr4ZtOR`?b||(2o<~-OaMc0v=wD&cqQopi0kvLrX;tJ*(oz)Wl!)MXgeiL?Mi8HLpKf +@S#Fji$&De<#IggTy5Y+!7GGF6NWK{@1#->vP3W_h+G*4sW`c1pg}N)1izr_wkgHM4Yk>o_)cYF)a=&WWGk$L82ZNlhKe7P`u>ij&}o?c +3>~}9Yvo%Gthe3vS$t%%+!rCF19DM-151ECyA2aMkw{*Pm|-pgf21biv$*17KgIO9Q5lB=ZGJ~;2q0XRU#gIiW9#12ZfG)of#)z2?LP9?0?&$&p~QM2=W2)uS1fM*)w42rRb@DXUKdX%VI)XrOAVQ@ +%Gwx8JBJ?Gs+#sJ8l*dc06P%I-LMuN^jeNE0b8B?LpJ;d*vpcg`QFhU9yiJ^g#KkQ*mVU-yU*6N}yM6 +3fF{J}i_^WG{*ggR9>)TOBqX>;}Di6lPatVCgDZ=4`N06=5SEV +e|+}8{VgmerW6ny1KXb1t+$F?-9owuQD`bS;Cr8nPxtfqQ{e +?c*BcU+Sz6cgCHzOdfQ=DAnKqgno*s@QZ)ex@E}jRoRJjn$%1^G(mVx$uOaYc{40~{kNkCqcGld3m;S +!(#&~q+oOnI-qxzDq{J^=N_XobDbQ79P|@y16YqvfC4H#8mXu0PYPX3FaQmG>=8vOUoNvx3NlBp2jw_ +~&=x@4ezut4I+K*Mirje-t#DWhIxXii3dH9@JdkY}ES8QGZUWD^&3t99IiB)Sc{wpi1n=GRnXSM%G|! +0!)EIQ7AEGIPd_tPH71)o*R)80IE +SiQu+aHxx|QJAixOXnG524{@P_zcm07rxQzZo{TZpa8aAu3G!E7>hxtH6}Xn=zZn`K1=)-60!*d^H34 +`5J?#~Xy0S5?G6HyStAP5iHG}9k6cCKrq#)3Y?dFc>;A;d;3ZgnFC6ws@2#5ttn{_&x#qGhK|4l4+!s>v`UrY)AJJ@2t*szBI8! +8lP0x&dg=WVhAW$1l0@`h}uJ~yX6?s8o= +O97_Ow4w)U(;iI8^?-}aAhG(U=p|G9_UcE|(SwwVO>Np}Fm#wE&ez9J +^9!4n%JsUmBG*v=-Vf7vzmrg=$Db62e@7^spZ(Epbc5?Kky$RW~<&0 +9TiGH|Rq!0U-8n?zd68YTIF57dng~6p;%8_`~!X7UW{pY&H?Hk#f92cR=3V(;Vfp{@Jw@pfzo{JPMEI +;dA1Pqtlb)v*XjCvy@=7R=sp3w9GxtV=ZoiOkoCy@>5J&a$&8k8PMwK4(GGWEWNt~6ylo?&-D +0Xr|-?_DD(5h$;smD7tZN<8VfG@s+xh&3E#;V;c-LE9B$ssObe{#$?Gq~?i+fXoBFZSW2QYQ>V`Rdkg +q${7FvsSYX??$IScyPMg?u(>4mlonorjV=zCPiVreRnz{RF^6_ze7fe?L6g)!fodAPw=FQJ332PjrMO +w{tD{8v>(DrUHcImM63<1NJhti)!hdtMvB5EK8?@ymHi{EY$ZVl_r(umcKPNY@!w#Cr%gtRR3pu@lo` +I;wM3VzSFF%`)4P|NjY?_@dEIbuJq1zi<2m$Jt_%T3}HcvRSMQCDs88gE}_PsKKgPwPVES5BF`W%_pnja-Q$w&ojW+O*JySt$<}*D8}O~?*ve +DCc`2}kwhpA!m}CE{d{ssoSGqT?Dlf;RuQm*HXDMR+hWRUv@%~i7pwJt~mp8mOYfw-?o6xM$UTZ^mDA +E#DgwIQ! +%rSEn&1LKTYhZVSQR$$k|A%y0O?@Rd1sv@WAZJ2fhZJHTVU7P+|r^1bnh=YJRLKEy@RO&r$gc{#-g8ErsNB4FPcUpCZ(&++{D1TjupO4a>!iy(EtORWv_i;U)H1o +-T&N!fKF`ZvH5E|}WV(J+>(lCBs8TznEm>8vCskpdAX4QN~SL2q?&SMXgCgq_skSR2@ke>5eha@`}m*{{8kCPIuQr(np +rqbv0S9ehSwlw(Yc0ou26Ii<1=xBu45|1$0Aga3y#rSs;0K~wtR|E#9|6~#>OC%=NN0W>h-x0x-N`~y +%+0|XQR000O8`*~JVb1@;TK^Oo4j#mHxBLDyZaA|NaUukZ1WpZv|Y%g|Wb1!yfa&u{KZewq5baHQOE^ +v9hJZp2@Hj>}JOl`6{GE882_B~B`KvUR0ZQ5+JZ!HPrB1Sxsq@_)bX20#ELII{BCs +j|ff&}eiw`h^CQB>5(<%Ze+OR~7sD{4HDD@>U61vqIKgE2||_ebdOcOOoX1NbK;ERr2~;)YtY_Dto22 +eJ85nZa=)}+o}@lw8>k=k2bB`v2Nc02xi_dju`yj{ofBSv-59Wp8xObH-BHzqv!QrU%U`Sr>|ajyv^4 +Y_wG8`>bq}+GOfPL8yrC2eBX31r@Yd4@A}5}ez$MT)lW6FSRE~oGg;~J;w$w+g7$Q5J*s!?u05rT74tRlGpF}2Q%MX-DFMPZ8 +QD>XrT1Nbza=`O;!p+o>Hi$%J@J6{DAZ+N@DeWB5BqKl +qW3mclH*rDuyQtMeVtG7!<{eYbsGZzQxMVZN{eexbVda>asM1Y7bAG84$PKp>|KUM$)1*K9 +3ir3r-#9RL2vyS}Yi*Y=#j)VnQb*aZ-hH;Pr_hO?ag^y9Z_&`UD`@Cms>qb;>8ZEPc<*c(}MJuHcn9` +aRQfp+j7*!DFLXxnv7vSRdC;l)N&oc_Y|OZJ2T1yd3#>ZHu1Nvz;i*7EngXyHvw_$|yhjl?^m#|mz5% +@v5{zj)DQ1F=@X<~7LwX-Hd`Fiz!&XbBJEqy!QI!AL-I(y|^h>5Bop@qsOPdY!W5T69m6Won>>Q$dJ}zUHdP8(uE$TVip@QGR`$`F&xk{%s0aR-pD4$uV* +vOO}SB@1fx)`DiEc=^0$!etHDp&OVM|eo6+>(A4!r;yrtUfc`_jfgZBmpMBJK(9f3>Xl&TM5p}86U?+ +!>S}D5>S7Js;;jLzWB@0r%1N+Tey4O4nxE*r$6^upn#D8Ytn<2kY4B@c#o++NU#TM6p-%v}GTd<5B-; +s78P*)a{tJD`7oCxdWz$Aj%a-O|6Z)<402;LFV&t+egL^q=P7#b;%VJ)!7iY>Z +U0M_WdB~x(VZGnOrdI+wT$!gLp;AHrE8Qir_wvOzwwcJp^3Z&9v5Uv%AFnR*QJg{Iuw(;-4RBug!)jK +MHmx}Ci$KS~A70N3)PK%e;(K3TI&~Vz}=e!a#9Z-2>AgiLwvW4Q+X2q;s@bCvy4~BS|4lSXLBNBE)4+ +W$Y{`ZEz)mj7;e6YA))}Vw{56c-Ha8%^Ss^Nv$hyqQKAU_<$(X5~q@L7V84F&)@Xg=uoOzKgLMIrC92 +IopkF|2%zj@puxtx@bb3=QI=kdpNVfP6JFS6U@~K%WaVn@KbWAFOr{i5OJI*9Pb@8jd2gTtgqkI+-cq8P4XaVksZJlk+k%Vvd(I*K-R!kO1E+cnfkQ!)Eos1HNP_kz=i{aMdD;NiSzHfoI;~QHhZfn +ioPWiEQ~H*v31`W1rGlE9S)H?WXHAtiua96EAvz +F4?_|q|(TL_?l_>*7q`S>qA$!F}2`>+lcKMtd^pjX=uebtP>A+P%GV!`3lu|RG=M{{7hGj4tz|^0OO= +pd?me0e1;OWqPIj0pd0R*UdY6vrAQv*F23C-4GG#n*{)M7b1n(@iUqeZ83VNkPnj+F|wBI&YdDpcI=nN|ash2E6EJvY6 +a;}3sJ2)i`9%C8>K%_3Q#43vH#?m+c15zo^p^*<0%mOZd7gl2(=8PheBIDe2`TLU6BLpF5(GHJXEdaaSk~JMoLg1NQNQrFwa +QIHHzb%A@0aZT#H*wTH5F_ZYnTQX2srVY`Z|bw^RBJ+F@*xA%3Gg#+l!ryazW0os*e{@}A5OfI|5AL( +MJfo*$5@7aYW6Cu*)K1)($QZP-z(~}IR6=ckJ$NEO2w@&h}6bQ+ +Hg4DN!59bWN!s{7L)^U1oc26X^l*F44p{;WrJ8?5Xu1s;;M-z +xB0EcmXM=2;I=jZthRd*8j?tPN)4?bVWhwnM-E<(C+X(n#FbuW_&W1tYnOjm_*7o88@@lQcsQ>iFMDC6l(Mbaf@~&<(rCqXO*Bmc` +K#Oc^>mcX#3WzyU5AaWj}1-g-5|0}`+B(IfIkaJ88&B(QkaU)9dN7=~i!o_jiYGkWufJnl42Q45(FJ{ +opODfdv{idNPbJd)c>&&gAx>@v;>{*Jcf7G?*buNAP=fH*MZC`3M#BX~ws-0^NJ%Rz9oCdu-$r2QJ)L +(?rJlxQUA!Ni& +TQWNnE0ifjhi#`YMp)NN_H1-)g{HxTLhFbTBok&xFhtnUSX-sM^Q7eATt|j#L=AEluY1|s7XIualZtFrK46 +{$ho9}K^@!ImP%P8-dYgB4bKN4h8c+)5)b6IA=|9U!zK(WuX=#9J>_ib+8qD}1d31+>vGZZ4b>3xZbD{8k_j(6*7Lnn4$I5wFxTk9OYKQ>f4HP%B??(GpD`>5{2Utt +L-%j%eLHl2v#4UIld?D?1X#H>Kn{`D|-xh6x%(21tkySAj +kNi=xHDfgy~|01)v&^g?#A0%u0ULszTV<`~F$k%gXGzQGKMCF%;d#~RUiIuA8YtrcaFn`|Q5h=Vpj7q +`r-`$Un^Plo%WyIQ{28!Gx9E)4Tq#)D}YWTvT3N +gla0$(JRuzlYj>w`u?Ve+!CWHoaTc1-{+j@l{&G=+Ua}iPZ~OJIsZp8WPQsH`lxTUSc4%>mp6(l_iUmPvqrhI@3XF$tMNVLkkAYY^szs6>A7 +kKnC)?64Ru7u$I5>`!m~3T#y|p!PUfMBH>s~teSyUeW{A~Kc>^j`D54J)WLa +iq#X!7QveEbJ^+%uZmrJRG#yo?74H|M#s8bnM7Cv5SM&L=e1s;y)66Tiy9UkvHNz5qNmz`C(4laZA}! +)AWrJ&iZi=BApiD)j&I{IA-sA-tJdy)8^N@NXFYwYb#P+VMz^`@Z?D{Y&hse6FSJ`F#U|7fUfMBWU!L +Avc*w9{;B`N%aunwDITe9@$dlu_6eGjobTlp>4nd2)ANSmlsdnHkaiF@-b_4wTJci$L +id+)i8ZKa_c)3mP^kPp +Zb=W4a46gwd{YXw99BjfoQ@TkRSw<@cUoV7iSb;~M7xMXw3gVk1qh&AgFjR+4lDGFQHtMcYwLP +@ag@W`Q`+9y3lFdRqfL}ZRJ#+mIx+!IQKQQ0aA2p4Evj?cmlv&`*O<+=1}JKzP!sB|%L$WL7U*F0&ph +F=tq*fPqBq7sXN!=$VcjW}#i+YOJ1B*t8HYj~v1Go6v-F*Go-3D`#2TKs*)ui^$ChQl#Y1baMg>ucUo +?ckgP6_ny|I1tNeyxgaXT(=g#vB0-a{PxzL&J+20oYe7eGHz33Kf>4`E88q9ZQg=4=lxsZci&bWU0g^ +nKTgW|5p6OlE8JXp(AhmY$mM0WQD%oIvbc17Y2r)Y)a2yw2qS2kK6Apbo_~P?RQK7xWao^-^p$9D@6T +9}YMv{Wj*u(LV8lz+?A<=YSW-b9&0B@g|=?W(ZoZPFZXB5Fv=~qe;Co<5_q?t0VKV;!eOqiXm3 +r5nzZ}X70BY%)#3cfA47q@Df5p1VHEg@z@vkeS1m++`(rwRh)awpVv_NChv4eU8|2?lj;QJpD$_~9Oq +3aa-{+N9Kpv?gUe3ceAVoPY3pGhr8dG^x8f|&Zud?`ti)*Rz&h74wJLL5%#K<@%_leOx((>A#RcMV|q +DtbW(8=DNEVf$uY+gJjco`n2x?Cpp?VB6!Z~V2jmc+-S*P;0T#=Y<*5yWBQ +X1WBtZ%of)`18*~pdX2+GY8dzoJ|vo_^<<}^WKbFwkJuj5E;ambPE(VQgmGjcTbvFteepPYplTrgQ1X +$2>rrvvr}uyUp=>?dc)Q`0w*Q+(jedoEM73!-@Og=4`%@x$gu(<-oK8e5G5{$1K(5CMtLbLje(qH97m +{$kohPL6MpF_E;EiegoV9^JUMYS*rINQdLZ1v@^CHKv&KO2eD#x8XCdHCWp^sOe2Y3F+AkjRKRjqX;5wmm-va-;qmu%D +W@E~mdujK=KT2tNEjv^yZ +1X-^fBh^5#|Up)0H6Z+aVED#xwjIG&X14ACnZ!%yc!3Fq#^DI3~p$shQVIyWdWNN9={fWld-Dg*{VcX +aXV(iz1z+mx^;bZXpvL91f;Y(`8;O|3=jt^$%vPV}!0IRGuTM3n*)J&ts<@9vTgtR~+<4KNMHEgZrp{ +g90>k5F_>N3v7tqHnIRhRS37G`+mS5<-U6--;D-}9Cj!2>$N;R*+2w0A(cCO0Qj9bcmF`az +U`5#i0Y8mq`La`q)B8(okcZ@Q}tvT=y+6f4|5*sHyx7lo-Y1E>Nec$FRoHj_~ +`23Ds^ +*%C;`L37OY5ZlcX&+Gg*(L|!C^O29Mnp(Emd8y|Ae)I3(;TE1bpo +SvLKk&j-kP20Q(M1Fpe_CI=kMd2$Ze!2R(kFTBdKoIQvA*CK>>u09UR%Fu4S-j;O(iHgKtjW-Pi{fim +i_Sj-8X+t4ioVQJj1Azt$mmw;q&wJmv4cDt~HpMTJUnzoU{lhi!KW=fj~XlDjU94KxbTRF|dh;2joY< +q%ASJ_TwaLy%Y_AdSP=oJZ1Cbpt?Gjr~w2zL6v7T0r=ilQwzIQV{PSi{NIV~zZ9#|TyEZQV*ImfC|B~U)8Y?P9~d3;ct2fz;^Hv1+|g*@T)34J%HI{`OU?_J +{iYyZqgiA{FYZskDBI_7 +M0yRq3SWu%WK&c`ZZV9qxW`2l&!hTYk$M{HPyoHAZnv@%bMH5(vs^cZ|n9lwS$lf7d!6&rtm)Iw&PfN +FVs$RXZX$ye!y{5+lr;Apj+un9^jGGv+}p3;$5C%b*ZN{b3{jLAS^DmMX-UBgD+6xims;xJs67F&<~k +DhsazUXuhg3{;-{Y$nF0DP)h>@6aWAK2mt$eR#RJ|zX2x(003kX000*N003}la4%nWWo~3|axY(BX>M +tBUtcb8d97DXkJ~m7z3W#H%At1ZXpjwGneM3sN9D9RkQ6 +F|Ka%MPu^X845rfJftF3WGJEsYh*i3PyEfhInDgk5Rjp!`F#*3DyWV5)OL~CJM;Y>rm< +}JGOWLv4TBzdDWqNvuXl7?XWlU;3kU5Yh{!UO|LrBF@Nd%4ymB*G3Rrqw&xC4E;)4=OV%m4iOQNr +^qupTWRoQ+t{0Z_yy|^#DbEqJGu8{ac1HJ}^7x!2!}>;>_4wVmtdqVTAKlI+$s=)TwrxB>AV-C-YTbK +do!Hi@s6{Pr3AnBMw$w<^^!6CV;TlpqgJ?JaK7h)JTd8~E>i`6Ad&VJx!E^!o ++tt;({POJ|KyhG926plP#rO>DTG`M7JOoa8zFa1ON>nM_M+(OzG#RU$W27zN8s&Zlnh;yp!iEG4V};ue +ZAxgg^Pvu6aH=WEGkg{9P=Wvc>of5~W`~T?S{mE2#70ZScOb*4RVPN8&wH2Q2y2J9Ie;)Ott^Dq^-H7 +c2A*^dq3&N#{^2iw1pLf$Wn&8vq^Z0$e+zppeRoh9soP*V2pY>mlAm`p`b0ASCSidJ>5bLs%*oMD&?k7eO|fVq1-iG((dV2)DTQY0in!6po6K~qqoC@f>7DdOrDmc3&^BHYXlDKzAq+;C^dY_~ +ylgEkwSVj%!u70y$e?N_+QkvNdW165eJhU`8Zk@_Q8J>5L8^W)xmEgiU8lPqC; +5H;sFqz!BXg9GG0f_|Bcr+maxyUtR7D12FasvD3Gw@G#i&P&WE*I)0W(6H%)Vjr@Py&n!;Be3{799wT +3Z>43Px=SYKH$jKiIg6oM2&P)|F+PR~%rR1k+&`=NK#Es8NL}$H^!`(ZpNfqT-!q)&*O;wDyBF3t7EwO8)6%mvxh#RE?&oAA!XxqPz{7DsjxRKaQGi?_?d5bfGMHf=*3=B +*J%o$hIUu?RBpC_EPaj?~!&v7s{<9w}pCB@e{jn>NKIRkC2(_f5XeI=20Q?@<+}<1Rar?nPS2q9% +amt-%3bZxWQJQm5-iW9T;WWP_^%&YWGh=483{jedU4o?mdztf4EQ3-|A2z18=GJ2C)(*3pSsCyN`2_n +FQ8JK6A3`V9-$Q323kcWOl4^Jgzkmv1&XrVwgYoVne|TD)zX*|cT@*VB!0oZq{V#o@^>*!SDSTb)Vj# +9~19=kK@7a?nUq@trB`-9XkhdOgUgGiQkYO-I(7`5qZwH=2hXmUTObd+XdB-#O*nJAtOUoadmaGtSR& +na`oag0bNzV_vG_AxCECbmV*7oX#8m5ti4uD-YlQCrXy?Ujo7>UX)~^{OOInY4qvfKZy +K)P)h>@6aWAK2mt$eR#T-cbkP4R0017n000#L003}la4%nWWo~3|axY|Qb98cVE^vA6ef@XaHnQmN{w +r{mcTXf&W}0;S@w9cjZtS)`P3@f6Y45%yE7Kw*Gp0x_N%>=Q`@g^W06+i)B{^}^-aS|6v@uCwFc=I5g +Tc(;fp}UhZp(CbQHv*^KK^_N|N8>}_oX;WOZa#p^Q{M455%){BJ)ZnVwoqh6!nD^dy9Ai|EMoR@rx|0 +w8+Ji=u^?h0zLJqH~1?+xGk1q9^Z<*sKv6903xl#G|i-tHxs$2MVgCAF<)e9oKK{5s=RPsOi$FJ9~&ogN;Xh~pRH>G9G2;pyS=5j=e__KyB8emXqb9|{R%f@b8+qD1@w$ +rMQ_6QtLPlnw&Zf)T0~a*|Hd3G^$UE#sLKv*JpYIdn%XWI0bO9LNgLO8`oi&eJ-s=}oIs(biV4*V{UU +H)m(myW;Fj0KqR2Y~OlU)c~#{9G<25@&420hK?PATJr29Yrlhd?#J;Yx9<)g1KYu*Ols21^OdIG51h1pZ!R@kGK?P53 +{*tuKoFA(A_trFoofjTX0~=`x>;&Y)zC5Tgt+9@`k72`qwt4F$Zx4(MKsId))P02k>pahBG%k=O$&WH +k&9pm-%|bO{kA(7Bw%b18B;fzgl4TY7=Bi&5PAEc#p|5_n@0Bg^reGf2RIsDk3N=qu>v9M~X1yr9WqC +XuxwaadPd03o3^p!d4I2y_~|dnT@NBr+uYt=)Q+!o=QE4L*s!p!Q9pxrIt3PUOlkOU9_P1W +62si3$#;=XF1F$cqJ;DD_{8H4i;1AiIZRua5;P&sR5QTjODP_I_TSt7AOkz2Y +^;B7g%QqBapN7zQrQZ>EE9pjGz8^@D!xc4?vizhCwGfkCY5XvL?mEA?s%#htEr3*GCG>MLo|Rgg|cT9 +*l%|0J9~Y&EpX)Arq8WTLOO5vY0DugrZKLKHzUJza5%~r*W2{AZXs~r<1yQI)u)<-;WnO5t}zBus%rO +;^xh%yr~b5t>@*!261}3@SYO4)t5O=MS2`;(LZV(#bPj|rb^SRo>ihZ6z_WT_-#@4Mj&F#q3F%i+

    Wx=2OD|;*#H2`M4ua4B_?s +c**7)Vo;eWxZ`ThRk3+P=@MKZriOW;6|YyF<}6-d~7BGAD@h096 +USNJ2@D`YJPCE_wxbH$)bWaD3c(%aAJ66b5=W}bQ1S}jL|aG4FMi&8cKH7e_K}!}`#?VCw_cIKSoexDo@5f +}4#MD={$|Bs>$kg5)+iqE(WP=J>t&gf`iJnabXJ{@H7!shfHmubsckjW-9~`qOl+e2??LisC*L_V2Q5 +REi!!Zb^H*GTjKhH`qI9*tbOWhmc}3Ewb3n!CTpQGm3+R`rd0mv373)4&y}XbaSAOy1!87s2P<$zVZH +k0pG_8lKpU~HEP28RUo8%yULe*irMBk6#`<*z_Xr?d^%M`UCUezvm`Uw6NwTex~p ++?oj@01I_2Y>D#o}9ioc36~0C^Q(+4UA^Ql0iI&DGn+$(P>iTIHVoK6#Y`-0wog==&C_2)M33iJvcgr +=U)z9oB%@4_fAg_UK{~BrHqg{006I^Wv~D3=+!?4Z?<0#`qB2_b${@!*oJo}r_aRT+y1N9N%YC9ulKQ~zFu?h5`g|=Rc!sr5~udhEDyxJMR=_@e*tybv4|LGA5-#N{azI_ +f_@C?`(#_9S(<^lvmiQBCi;kgM`hd;o`Hpra-R!q8HBG1z}*Ltl|J$fJ`%`&Q96>{#q3N68^Flf(pt9 +%c1wd&G;i3WgqoMzU0)F@z$6zxr2f4a;v=gk-}!i +{wCX9Kq3;)nFUZdwWQLpqd#io&uD+>enQfonSBNm(|97;EN7V;uz@m4H14AP6W`7LG|=$7DSs#;J@|Qr^Uqn&t*u$%q@YK_w-tWyKZ%gh1e`Vp&d5Bwob1(;Lsq0^e(D2 +c`opf=N1^O5#k$hEYZcEmKBC$icEz!E3A$Ps*Y~lTlWui%j)LW0KYc$rzUUjR_@%g%`~*Xlk;Xs6gCA +14W=cXbdfiCCu9*SK^#;CGcz=gAuN|Q$c-$AhFJ2lpR@lx(*_M3Q%EL?L^CJM`{4?^w9zbRRsVF{-fO2pS&{b=Qb;?D3W;q7vUvgm8sZ8tI`vNM1AtilYX`1 +pH|TYN6j;#BR#n#PIYfCX3{YR23i>b-GArgY-{l&r-`Fq7D%1DkU9%}B<;)7Gi%o-1Sy)f-MKmU2fk81B5po)zwEbjq)Z+X7V>t#=K;!Aq8RY3D;pB|OQ +k9xi^zZWw35$NsjKDq1!My04s>8J2`e8QimGJ(CP`Sd+KDoqh|LaT+7mnR2mQ);zhZxf3Jumnt7RS6% +l5`T>6@xO|uD|_^4BZ-Q$)!ZIDI(^8BWkS=B3O&U~#zuEV-hjM5FEoq;ezalCkPfDR_(A5Ug;O9spN| +)~dAq!tL$Sl}eiqO1@uy#Y<^u2@$`}anp2RU0{FgW()w)u8IzpWTzj9$l^=EkI&vW4Cw4wXY^r%1O3G +74l$Dh@sjVCVWStc_zKF{@|`kFdp`aEsMw2Z5T#ZoWSqZ&UpLg;#q9iop@y2HdVQ2a7UfxdX2Mj%eiq +m4OierS#F4Dcr)epr_3e9jG~ecFlH;MT +`u?+e)ZgEKX0ug!N~gUS@s&gwtj`!Tv!b|MF2qGDOPt +@gyfUB}#B5Tk(e}1ls?xHmc|8K$p!0mb?HFW?%vX9|vmEhRu1plQ5GM*J$z4gt*-PJcTwfZ(trJQI>s12Pvg(nBP3CA2_sSyDLkt!9a-nbu_6MfBcU +!xIY5m%M;*H~}Rm?_WYlnw%E{dao;FIe$4MCDT57Iali*~bNI0#wjk>qlvBZ4_C3(zXZ1^(~!W>=D1v>^B=1oVjgKTDRlv76_hH(eWG@4NHV +nNOxouI{+(c|dx=bwHi`cGk^4Rlj&VQ&QcsnxCblIcpG8({Av(?b*=X;b1(KxU)SbYr0(2Js7N>R}5rw9i#_w_thyRM*|p}{l&>t&q2%3{=(_oHoGyxiY5Am>OV3=WjEkm2Y-OpKpBl?1TA +fn%BusN--)z2Xlche#3OH{?u$_%zU2INDZH3<0Jkb|)n%)3CVS84TXn!Ng*%)Es3{O)##^h~e!0pD+Ode9&LMivryVgh8`ubpngM6f +E)($*Ue7cC5G~Sp(%V@jn3po3r2@Zd3$= +NRE0<2)lCNijh)QdHu%-qTg}@4&)F^L;)cRG}+7tvGa_)wq*5eyIJ61Y(S>pxGTk_aduD^Pz+w5TZ&Z +}PFq?FtZhl2bJ(UZAxFx=#%lxnX%TclFs!09Bly1qJ=3RSnUT?@r2?3^nk4L51}`3nIMU%B6hPh9y7L +;2;b=MReqGkk_tr*Snx=n$7D|1*ZDH4u7;>^c5{BSfIABgc<9Ku+6gd_dJH6B?Q +=dvX604o_NUMiv+_zli@UX$#a3~v{K8=}VMJ2OaT*$S3b=CTqqRDXKoDv;>(y#HPIiJ~V1XuG8a)~`-)X(mQ_ +dp86o~}QJFA*u5AaCuaDV*l@Fz$0vrkSe;GkCl!PMFK*H*+H9$f+G%eq)lz=#g9d&FLR$s?%wr~yRrB +j4ch%L*Q2R#JSWcfogdQZU=+W(&uCSu@|69hy1cY`2W+s7KiBS=ITOZW29-x}^ZaJ1T0~nB0wW>u&nZ +Zn%`N*SfWuh;Wn%kmAgtIn6ewYQ3AUz8HV;mtMq7Wlr5JwDSbLF_x1Ho=-Yx3nRG5v>9gt+Nas^c3Cx +!ShT39uGMtjSFy<`zg^Gb+wS0z2Y9EpeMQ}WOaF?y($F#P;w^l8UeR?4A_3zmwIR!G&o%KQ}-68!3ermyyTh=m?HMSk;;LCJ=t* +r&xuRWkvWAk$t0>^I9N($E@b;Bo@P%Et?r5(EVukg&|4IZD!qS3L@w2L9xTOavDgbF6L-Ez}6onPqm1 +LuflNvO2?OX9P|Q+N-$4!!iq@upjQ)T^+jB-ixEG!vr`*K5%Q>hWBB6-4?vnn_tBi>$kuN8+mRmW>n} +tJd3*(Qf(7QR`s>hMo-ylcg#4MZ=jx9V$-$H6qOe4V=5EWCQxV^lT6SWV~aX67{_e!23FtUrC|}nZrD +tP)(qo`qHSH?qGnI8HEN1B`D2*02So)yD-Ey_<5d8imBeh(Oso3MO8I9ZX22Bqo&*g+0wOM!^=7nX;k +uT-Z8|Cf^Y=d^jYB>T??>YRqR}T*xb=z29@on*A14`pA;o +8|8oq|2TZmEmL_+*K +gC?I4LEDB&7m62zH3D}bHqAV7&tZ%ne#y2YwksfQ6bLeZ#0>#$DEUqdaK)*PDD<}0})UXGKqnn-s{(J +Ue+23?kHdHXcsxiJrLL8(2F(zCZqV;hSXIW)AEC}0RVT_(AM=M!dfY7{gHXfrgHy)p%zxi~@#uS1rn; +`1zG|PzWrQE^RloJZ08QDE)%vU&9#-Mwp0K8wxY&sNEHBa=<%om;S=+&#e{PxWdlOesK33OEC(y4-gq +VbgMrn>_Eq)@)`I8+D=`1s6ZgAR-g{O>xKWfxwUl!>wpy)qcVA5K|x#K!~0^S9b!jW`cQkLdL7HZ~y`uV>X}jw~-#2S610K@J&4;XSJ&041j-t&zT#^AJ`6uGyLexo##3Rw;h@ucZd` +x?2qG5@XTOA$35tVl`J#4Gs}weI5XWh&iITSTE4f`;KXQNL3T+SC;hmbQM@AgSH-n<7*-Z*s(FUd#zm +GUbO6SjYh`|`U~xdsKT{G{3C&PB=Hu^fp$W3|ZH7{~pO!$XSX!@-^Y5n-%xz2TBcTY8rn1T*I4~zD7| +@X24prO+GGCKk4;}hcRFDb?u<)zgf#9zHSX^U55%TS>(t4>9DPB`VhjtD$De45~HRI!63`Tb>qXbT$R +MLRIudoz0|&3ZO37Fch7EMycpqYWQAh06ngKp3>O<; +P|=0MWPRDfKJ2St1aT|oV)Sy)=S75>P;-!KSjSadu!5>I&|(Y!+PIm8lc_CItqPEb`)zlElSk~ysn0n +-Po{waq#{fFr+Cr#uu{}wRPL!U0jz!PePREBe<%SA3>y!qj%pU8XInjOevlc#Ml&?^Jy`f2hVbdhGyJ +DTpZcK}KI0prbiG_3a)N_Anu(hc^jwbqP5XngGO8Qm5>_% +j6MBfhZlR+o<-!Qa7<+I+(uP^AiwZ7y?6o@!L;cswWs8~PbI=&k1%jL8o6M!m{zOcE9Ua=FH`==o`qL+_lOwz$u +DWndTpBrZK5UtjR4nFhqv@Z&x$xPK2+;w`YNncS!a>g7_Pu;sUo;s%-qS?!tq6n$t)eF$Mjk9W->ZEJ +SMBk*@(lp4j&z>R0X^fXvH}TleMoyCPu+#nk3Z`S{r2YoS$^#ryY9CMVwgeR0VmaA$t8n(F9QB*muh8 +byuxw5JvZ9o}y=g#%|9MiWf1ax0g4tIH3rdT)@ChPyrRnC60fj{$djBrl`yTD*5#3Lbl#dh6OD(G54b +S(=8zDiTYkt5q2SZTcmkk7aocpWhlm(W?;z-NNVRfc0n$ya-5~-3T)r%gXnC;WHat)$mBjY&;|{q)2Y +8nb1guGl~;l~?7T5mBn2wl02GnNAsAMFY=RDhIU!vOk0#U_uq~^qPvvl1PPM_7^Y}m<9iJZXJV_H7E7 +cC93$m69Q`_E#DYL!J?w~0uXtf7~e`pv$D`JW{I&q2LdwwX+InPYeCs +Y}-*VdQn+jL-q>is04o?lU`_3=AzUQo4DrK(n?u7-4X4z4W2!>fNz|67b5(Bl)nvyS21L{dDlh#5CGd +?^v);rstRlideHu +PG0$hhN*T*USF^a!du*ABIy=P(NAvhgr5Ouo8X$ +Spz(Pr+(+>vbvCW~a?TjM!C4DkX$d=I%}S>c-`kE;TsHYX_g^x6b$Ov9K1N2eg|t2vXRX#f?tbk^Qx+S#6t6yMROqUlf&k%s7Cw+L@!QMI|tP +LOj_mP9cou8KLNSdVtR_eac34Zdshavs|7h}MKkt@d&Qj5=h1M7UUf9wHLfsbnBBcdKD(2D4;9D`|U1S +nUo)&zlr?MkKt?42TdbZd>HulvVGDVsr9&@G(`_`&SxYy1lM9TF= +TzSy2CbrUKd@?5(h5_-k7ljSo@|jOFyzPtNd9cj!&zvqk`Kus{Acw)T4~fAOGq|dB=WSy*!s7fkSZq! +=oSsWgqYqs{lQbywoX*#^hM?)>3h~sqw*?OhCD4(VzzV$xWZ*Ztto_nTn6Qgu5|&@hs9mkFd~Jx6_>a +3aWdF`f8DF-SD{?8YvnG1`1Gh1Y2j6}Ao9MrLZ0#@2#Tp)nW@acst>w2brBx0TmeuQ}^LyXGOjI%~Zoo=@ +Y-tD))oq)H?BqhPK7C`+!O5F)?{sjo%G@g*yqMVcp~GW~4o-rS`zFJxyfN?BeJ^VFeR}`ojcxk7ly@@ +tgnGF$@d`B&61I1-saYqPpD_aWHT6D)CV55`}b>~@%-mCV@__05PA0CF>XrJUK +e3x_t?=2BMDr_MCeF|apO>T?CMEdbDeseH6Z-mf8Ulv`=>NPl-Y|mxgJ~R~$|htERBI@0h3Nl +z3G8~G-ltS(y1xk}o6Zir2{yxp)n!>7-n6aZ;u9+ih(4`yf +ciuIP9*{STUA3RCmfR43}YuyyfvT-4d|)7sSl4g9oFXvp9M7n4Bx`FJ}!_dkHjH-pe=BHQ5MVDg#}o7 +jt5iQI==(FpabiOIp!uk$JnSPJC19M2Sv%gJpF#>EB5`S;OSbnVYLo(e=qjK`G@v+7)|{ulQ(2MuP)% +Pi2l!mHpE-k(i`u*or*`-7tjc0P##x{EXAOCEVgA0Gx{nMG!>Y;a_9yTk2>V +bHeR>jU&Uw!_6cOE~~*LKTrm*|G*DP2NBhe6KpKhWc@B0ge{84xP`U|*Ub1Qk(NVkAd`5g|>UWCo+alWMMiPZ&z7%E2>=U{j`aY__fvl_%&bz2z&F`4W> +eS_zjPXvdg={T&uVjxYdAOWl~Vn@p)Pv$v=>FosjckKF4poga01(UsrgXzyC>Z=n=d3CVWQUo- +BqR)prPHyi@UZ*$37gpCBG%bw?dvl^4@hc>xtLG~VW9%h#W&@Vd>=y +_=Rd*bQ~3y!bfVA(SD0JWlW^&(c|&4N-xbz*P?733i*;7?2pF%0+KnD6LaZVv;%fWA}R&5zxb9Ck`-F +RVR2xwkxb?Rv5?0<(SFU2Vgi9ARfwHg~Gs_#!nT2vD1`#09QtP1kAkv67@cJZQ|q3{_zRvghR11sx}Z +c*QTS^7j;^zxnStM`5C-@yjE#!&UnZKK}0HJNnow0l&>lJ@P)O)jDj-$M +qV>lx*b$-m7u5|wJ|1_({2kkjbp6)EGn5!tWv-au<>qqepNf+2(HeR(xNU&SXYbTTG+b3baP(-PZ1>} +HyK8mdwpFI^Ro`WA73X8+w~J2*#ckD&PK1?9Ka^w>z;f8g=Uy-PvDhvZ*b{VoDfi7+L>KB9zTO23C}a +w~R9r4zMpTPu|UGbf~z{iFZtAFr!7pA0}D>%V%PypG^+7#TtUcGjBl99PbOLdLpb<85ncWJob@b>iEf +_c%)mnZ!K==y6NwV`5`T!E4i6hQ*}QSm_Nu06(n+VhiE>=OS`p~1)9tEYbs)qz^-oop+nxtuuw}kqGvuO>PdA`{dpN;%+vP?eM!LXgjA{Q3YRvq24g?eja9% +@1@oSg49Fa8;?Y>XFEt1*8BvD7*l{(Cqp-02J>aKiR+6!)&^`@q%}wx!p2Gm}PoD6VNkjUVXlJJQdt( +fV*rskw#%#ZPm~mWGA4W3tWa7%ja%*x?|P3zZpDg=A0WrH3rTg`XCYV`KnV^tj_ +Za4<~Q2#7OvsiCunSdeWLG}=}a(4wd+jJJ2kL$885deu*lGJd{ydV28UNCgjQy0&eF#^Pg-Ffv(hk`> +E@LhAkRTHqVit|d2|!fFw*0eV)%Zu3Y8kZmXN^kCyLX=<$yi9zK~Q>tRFM1y#emQM1UkU`{EOxIi1#0 +{VV%eam?B@9Jo#duP|xoO!fg_cmVOfRzb#^ix!R+BN7xb?3>Mzcsfh537YG8D&UJi+9#2YC4<#ap333 +fgLn#5w~O2G-NY(K9*~Di&%7mX?@dy1g4PB#E$#{k!R?N==%_x1yL#mL*T0M?gxQc+WW4qCt`6*+bmf +1wd7l@!R;`ZaasL70?FNYucJZ$AC^{O54i-BtifO%M#!^y6#pG6HzOL%9o|a&7bHDjEoQ19loP6;bi6 +A;&E`cg2=6yQvK_lEKd%0jM*hz2Bp%vGT79*G83R)V@2;O#aH94D>Xw{#kx}ScqY@eu+ZjE#+PgHr*i +4lX7HdKiKPt+zm3baJt=5rt}EqE#ja9Sw!Q@CqLMq>_S`P`#(9HmZ`NL5Rl|D0=tsMxzuFxBaCH2lS# +(aqA(BCB0{-r05mRV(tRK{0m~)iBab-fYZs_>CoBpF)48!VLcEi}?kM$fLg-Tx;346|pPOsT~rga|`) +z=AYMGh{Fc4yr?uEu_+KSZNgheoG%VgFXB2i9lpyJOikx|H0Zq1h^jwvA1$@tW;Phly3=FoFVZn$kaL +yVKIF8rz$p7hN>E2q_H5ba}S1L36k{8AHhtPj6y}sJ)y#J2>IBIRMDkve*_Bp`w-)s9>-@ej(>k)LxA +PHnEPJ1=B@@3JgNG?MtoG2bDUsl^IEyWv#57WF#**hkJ8t0Qnu_ZRbFn&{5?BbFq5c0qcRl>xl3o@M` +qv(G137d5+P^kN6tj9q!sA^$(>CtsXu8;>oJXvC<6KK`SKEJ*>?uqtk<x%|f!Ki}I@=9iXU4H-IyO%#WMdBpkc-hhvI;lar&GlQ +fk*X64WmF_7f>^}mjQ)=Hlp_?)|FUmofZ6S7zwLr%ioh$f)Nu^MK@7&)ld1`C?CtEltx#Qtzuv@;Or#uUxWIW-9=}M4sT`nchLI?ZqRxtrE$lTIW)O$Pj6OPO-%E4wDVw7u7XAZWCGSbie-0 ++QE+e#DPSE>H##NyFFK~YEprYJfT?kvXfoj&#ero~5-a(U#4h#=d!lgdgcszP_&l%eXSxi`jX&4A)@b +!|Bc0W||$ba8~6l8PhA>Wp8aWI10{loyU)4iGCmT-p80kPJFwyceB)dP7ay0DDK6@Jsw>)#F5ioJ-Qq +!sUrooma9sZ=z}|^LIH7{-QE?HEsGZJ6g1yeW=MMC^p>CQ}sR6lCFzFCdCZ8EG6i8WIRTZGajpBCzd~ +3{~u6G0|XQR000O8`*~JV1`i|L^ZNh*@+$-Y7ytkOaA|NaUv_0~WN&gWaCvZHa&u{JXD)Dg?0tQI+s4 +u0|N0aNeJMekV)T-vtz!8q+j63-Ecq;@X&*%;K@yZ;fdB)5lG!Bv>^Cp_js;1{>T|jKg>5Vn*qz;-ot +>SXotG!U=F`n!l#i3_YA={qlg&TjlZ_|AG#}5?IG8PFQBlI%-fXe)1fIXjXNw}ax~_t7)CqRBwstpnw +zmHq1n;7G8l3lnx1(?8NfA@wcX2UI$}-8bASr|ExQK^~;HrqSDjs)(NfF0EJ_$zGQE?S_gDMZAY!S@j +qJ#?hu!@olS_U`~baA%8veh*JD)UKo7ZouS9|uuc=A$G6h`~4?&8KlzMHRLXTiJor++(oefTb(IVZx7$U`VJl(ygWKSI{!yEcyn}qf>7T8*n{BR!Ta;0SKl8Wybs=e|Nh{5z1+pq|nCTS0Z|pzjXJZ3bL#=}e_6%sdii-? +b0xXqiHY@UIbd9|n6)`-7($LQh?kD~#lj#iV%8qt}+p3_yL%d?q)Yw2%*Qghg`wdLWmX&1%@}>7z|p^%P5^Q=YG)*o=4*`jwy~`eAXHa +U}qT4le7xvGw3DO-lN{{4FLRj(A|(fj}w^eC>=06mQN3|@gRuP7!{>9Pvn}DvjxbF6soBR{eZ=cl|!!E37!S5ZVUc(AAZ(ixYsy`P|2B0dN ++fSf~axLZ(5r#fZJ0eJtBZJ!UVtWbi@i3L;@T2Y>wNqxx>lvTTKOybqnNg#I#2qaNj=Au7D7=j1|4R*!vAm|26EJ1)z<7 +(9FP@6x^bHtBWlH85yFdn1KGs>sT3^q2tjVtQ)SrySb-Q3u8KO28b%7oaVVw&Db8(3V&o_Y}JfSVDFV +N}L8_nqct73A=)&@-|jOC%XQhvt!3dcpT41A)z1G8^0>;*SyTTO_Al=XcN?Vt&(L<|#8B-Zrc~gE&r& +H{rGjSeMUVL8k$Jsosn3*^S_VAsRu6;0{>I=2ejc$2Qwy?rm~l;^5d4{YG$zCmw@jys)x?lhGDl2POg +iS6t+$#oN4y37k0Q6tRZY%!2s7rrA1h3CH1*1K_@&C+hKA;$reOi*rO2!iMy?x-j}^Uw)E5UWE%6E-L2p)Y$ZEe +J6pll*4~%9!T0B{uq5pDFhDLu$8B-e4xZ1d>FDY&bL54+5@v1`!*acfds$q)fTqQk&P#cNR^nY8-4yX +e?)LZMnAQ&ktG{0axNjCh@@stOfxpH^vvOiw{c)0j35^=L-By8Hz^dlV0&(T#erm;qr@FM( +QMNy21{SMoQ%mDDfa!VFKZfYHXqhJI31d7cVzlX*6xpR&eX5zWLq=s0dEAi4&s=k#O>TlE!y+^CAho) +CnDEU>F(Db2bZ3jr^Oh~9^=;_^x#DF +jo*!ZS3niuoNJ-zv<_gb*906tW?|q2C&0ekq0kDFZJp6}a3Wb!W5-z%bxQ96-v$N-XMDk?X!iiocbLF +dzrZyzNv`Gv3B$mm_${$02U6HEUib95A9*7-e*oKXUY46{v_rCp7AhyOd(R6g<&ac2WQr(2n21WDeS+ +1G#pFPG!OH~*0W6l?!m%(Gyd8EWyEWKa&x?87<>|f7VO6RoS!NF63@MD-0p13^6OFLgWDjK=-^OV^!! +45_lF$!QHY!>9a6-Zk5n~_3-#$nT{VKnS>EJy{BRWiQi@+QB?0!_1^Ei0&^)4d;9z*A1nA|MR=Vd@5l +02m0emYT`y4dqkw1%-9kUB+esDKM`06?K2p(3XRir89a`3A8VuzZ0Z#Ff#*;rt3aX-M-!ffj^-)5AydF$i-|Mo$W&^wiW0u~Y}-K<6 +VAad4{3`OuJUB!<6ONW4}!9W(&-Ii@q7bB}xR6CT~G8VH%N;`yM9z$m +4N3yF8j%vwfyir|Mdy>aW^N7yj}qXRTIkHHK?dXn`_k-w!UbDeV# +P2bk|I;4_p3JGj2HX|F`JM2WKdb(FYzy`8t5Z=I8mVJe^Mo>bE$3_{HeTF*I|Ss +js9M;~%1HRzvTAFU4coklyF;*GW#l`D=BMQ4yt#;UDSZG#l~v8DFo^_rh%knaxW;(jf`SP!NmJQ^A1_ +Ghm$zi67Sss)eZ@y@{$QHA>do6v_@&cAx<#0USIRFAB^#ozDa(Hw~9HvX2C(M=7$kFVm7SmEp +@nVpl$n}LYw;2&y2&gL^35Pm<$m8ZVE64wqc7Jopst5Bkk`E6Od1Il5tl=C&Ufc;j}NmdsTTZfj;0F-MG +^7u*~|q$9;WuZSWUs(GdN~ln-xsm6g_}-cRHbO`CT>wVkBhKz++y6LHj!X$Y`;^Usu!A0R17ljokjFV +s)o_`K9La!yj4hz@0`nF=7I6O5MQUe>Z=^^20TDP`%1i^#s=JZ33qcToAQ~{$>_J6&qk7jW~6R#arKk4z>0X9-^Mh(CI6nKaB47V#A-pj#|v3 +5Uc$s4iwP*NQg;EDMU-9fOT8W~h^4(WNp(hiL1Sp1C8K;CYpC;?O+&boAIK{j2Y!+P-k?t}(|mM8I4z +Q^1i0mS{ILS|&JA2icAmgV=p*plNHz}DB4YxvDZ0LQnxW;Ym^N+aZ=)Gcgso1spNX*3a`Im+MsTWHDfx|;9i@Jx&-0>K(26~4z#va-v^ +9HFI{i(5UuJCB4?7C~{c$`%LQH+r`=$hKouA +G%M{{+>~3DCL +qk8>fq2aDsg{RE14(Y5q%iLc{;&Mq(~zcDanC}qa@>$VG&0+WV{i7Oh_Asc8PJ%*HH;i_f{x1>r>;7;SkkK +jACfp1rB6r%^epcc5oF}ViqLwLl{r{<21$V$ReHyf^2szp!z+)Cdn%-+JAWRH1wV +<=T+%C{uI~3md;PvJRe{AlVYN>dw16ogPZBIiaU||H@gq`4A|0V(0eBIzqE@e*rAZYUbYG+ITGx8f4C +Kgh2s4i+4WZGqF88R@aS2dutRCV+WloZj2J$M-SXMb-%#in?!afWaG*&WxB3>A7chIPXm?2DJKQ2cRl +*21jF%Veohx9P`LpONd4PJDE7Fw{7fdXs2#a06|hV^=M70DAGP0@Ok%<5nfA+5%o6@&>iU9EebY;nOmSj0TCR#yiyTT`g%ohK#G7tau9RCj+0i4 +@#%&yUTt>$Fcd=m+69rGxym~5ZsnD3b%!8Xap3#Oaps3VX~V^nb&$ZWBJz*aI|>o0RsOv!m +t|P7p8UvO5mXE5~u-0KBur?QpR6sQrSFNqmOuzn$U2BjBFO$@~p;9Myk*hrej2=fea0WShDCzCJSv&j +H{0p6z;_J^+Ia~Z-S$Y4Zb#m=r&KrCEjvieL1rL`YNNlOuE-flTlT|#*m|p@_eYwm^|GTYXQa-&f*>; +1-=ko;4h$t{#tnL7}9-TY^#d8BHB?c+?2qXd7sxdg~4#amq1Zr&;4e=x(oPX%GdZ1x$ZD7q^Y4^ifG? +q!x-hM17JjI%zJ>K7CoIXZmE`la0Gb53LY?9Ji3-wa_4uUTyTK*FOyj& +Zo>|=8eSAlXDGjn5Gqzai#OhPIZ5!w7+wP8b*Z;(5^*hgV9)V;Bb;%Ac2voUiGkgMnyoFB$S2rzGLt3 +rJY4v0{}_`GNbpX)we$Ug0<&OHl2tXFbP0kS_TG2q{2I5WH3b{A{d`n>$S41zFXBg)|OF`QvSoSVJR$ +LP6~!rx9S_`Eg-_=J+Vc2z6gz?o7eMDc@3tJ5*&3azK>lO?*xXGwaes!XHe<|v!ZD@8zEdKMRagxPPs +`0RPhW*diS2Rss{-+GzRA{0hYxCQ{I5S`fa8o`$4z-i^8M$0ht*5Jh5Z8?ia=)=U)ssA93E%$}?Mqzi2XW46*&kS`1%aLnwuQzy&y5TUcsobqwfJmZ3?5!W +T&ArA&X{7^4sDGaN2ERjo#TXhLc%U$$WOPSSmk>6!(SHLGz^A43=WY}(*TKa|$tZQvSpS1UU%BJ(s-A +15>%(nirr~1`1#jXk3?WBi +R+^ywg4(u8bdhz2n(_zLnG>?W_DYO*KISa+ty9WcD6SZ@vvN1qH=rW2zKPa0gDXA*o6nP>Uw|s>jtsz +t&-xZ=m!1OjHw{gFI35-XnkF2Cle(LD&tr(;U?FrQp*a`bwb%w)eKxl5$R$a3Lv#8?XKgVo_m-onBGe +IQ-}|`RgCywQJpIW<2fl7WYZyem(dvuM(NCZmyW5tl+0?56!&~TVMT+ycD8|21(_95q@Ia;t7@T3_%86pzscuaNB)G +bRF?jC~&XtKzqEeR=9?r~k=-zZTplsOV~c?}Fr&((Xu10pur*(5$7s-!}vNni%^br8uOW(tRU}FQr +WHGoxr;h~hzLH5o`V~e3G)9gPmWSmjo~Z5;c9B_dgLCOHBP;=hsl9}ctgDo657TIY9UK*l8KMZ$L$7yGz(I7-qa!w6E_(^@B`ss)m}6ni{WQRX4U^yE@vZ%1*5>0TUnH7GG+V{L^q +fXF2Xs24tvB|b*w8vTD^Zrpgx{~Uj+VW`a3`pj_y%_0d{$c8^<+AY$LQmjF62(hjx9!y9hGP-jXv-h@eCKB?jiz9geF>Y +g6%9o=yM;M#6?DLO8h>Zp*h*E418r2uFt3A7~>Np_!8?jKxK-v4?noM)}xHydrV+@{7IkU!9Q5dkK0L +@FBqS+f@>^L6<9cwa3)ddBKXwbk6Z&GEwT0t?t$8@KpVagTUX#DECkgIW+;iAE(8E})J)9C +|vD8(=p-(1nvTtC#g_iJ@lYQd$Ntr0tW!RKX5>7m^wC35?wjWU`u +o=zqx!m`bhXwNssZTN4;9a%M>(LEZ!H0q*cjh1z?dQu;W1aOgDuB%nXm!Yb+b*Wo~A&7z0+cSG=RK`N +qrXi4GqXMS=jMnzN0EWS-94%UgEcqmef0`%2I)rWF)L?t&a0EEJ`??vYU*-#HaKnIVC}Nc=I +I<{I#ZXYI10S>X46E)fW$yN$|NQ4Kws*eR-8LJ-RfE2&Xc;ld_Hk)-eA?XB=OqT!Vcx=p!x#_}mYt4) +C<4_&3KitrX}K@?InZmHb--Soc4sIp&)I%H@?r934-07oN!170I8+L>E73L^-{t;iJ5_idc^4>-PE=uXUeNAlWIRv0CS~d7D#6 +<~nkAZastB6-u&*)djDMTK{xILPFX9j34C5`ibBPKuhMQztW=f3{xxtN#O@YwGdh_VUJD8VKN9R0xa8 +h9Do@iY~Hjdngg5gw91|LQG5Lqy{}`r<1>14mmU{A(3JaglN7*S6wE7O=FbqOy-FQ(H`Q`l6wUZx?$u +RMOq};M(S>>$E`ZO#JLPjrjB@H2@dl>!lB?@NUe(E$~lh>uIF}xH3XSa>j=>D-SF|6VuoqO#~MR;SyT +hS~3yro}GAApMoCQgn?8rxc)?lvAbv?Lmjb2F6#yGtj1m)93?`iH%5yk1do4w0}Xu>%c^hE@OBNq6G2 +HG5lvQ5X%>XMwA;Y2FD_lv$T~VyGa7P(Qft+`3)sBQ8r90arcDWFiAD`^sHV8-G-Jv0q6JN~fWa-l=C +WC3_(@IUQKhj>4Rr}=Ebmpe_FsWyB+jPS`C+y&1=Hk|2`|}~xx;63R?o~HS=hc}|>cp+Di6s;Lpn){4=UZI +3-a$6t8{RHuX7JvWYA8-yXxvf>Q>!?Alyx#)q;yTGY$G)nw8}wyp2cJpAsH0C#Btvw-8441WRp#rc34 +`8&)xf3^)`66&;71f#F@la2{xKlcAPH4#0Ko#J7(p2TsnM%-wDBoopYWhJyABKb~_#S2AeANgo{$>Ha +bZ@&S$bY(h8#=tPx?=Do+7CNdCxy{S*bdH1UkQ{!r}8-tTysJ_1lKA2uswPkCQpx}q*nh-5Y +mEHxDOb{Xrto#nfZruiT_|s;-+LiP7z_^>x{ogKt}8(;B~OpOeb61~@?F00XQNC&!YmVSUeTt&yzmW#sSVjRq27L;?Xw0b21kCMja$Pkp9$gMDSG+ApQkr>_^rXF`#p(JoRUP27ZDafkI5fIm=@5bn>0! +e27@#ztHGe7tv1ituCs*xRo7Sm<5P2h_rU-=2|vnsifM92jE2DgJKGryFi3@hgE%DXYZaeQMCX?`Yw~ +fI!%7H?RoS3|quatRKudv$@TlBoB;ko`Nq$WMGq?{5R0O@rHr@7vkfP9|;Y_+v-sv5;TRik--69JLkS +@y^dK$|RuF~nTMi~keQ%G4{iiF&)~84lKWJXH=}>Mn}=tB!ka#T*DUuYTJb9>2||QJ@!$%SW8 +7wRW13XG@}7T3vYWV?^k6kKBrY2oq}&kVIOOvWbhWud=eAvffKEg-N^U~vbPN{bQ(CAV?LKv6=YOWZ=R2)*yk +N7q8@L&26zCqD)N`;XBjkQPWpz@7~TOycHNWf#TGO$sZ<&UKbd6U-_GJWlLxh=jFvx21MGgwp?j@e9x +4`o=6~J?2}&jsb@$dLy5RP1IjarloD75Tq!zTQ{*@O +?=Aug0*9Iih6Q=VBq@@A)ws6t7Gry>hl5NSJE!32cyt6z$2ph3Ea6a86>3?ErQayD$Tqppbq+&^uxXA +Yd5{ED0dXBkF)u5fB2AH8+55dY~0t9E&To-GPYlC3WfOHkT1bx1I}MXbkmcuNSce +h)j7vyA*OUXWc+HuB{`+Vjlpl6q6;wq3AT4@!goH<2_WYwVZVO55QPA`0~py*IG}a??fh=gyN5tO+sb +-(=sZe`1`C{ku>s%w8UpP2W#TAS37$7X@iH!8JGF?LXrnbRyqqG*;r4>+a7?Vu(;rC71yW(OZ4^ol_V +k%DsN803vnUB>M6yi%1e)1nRJ*E?oy(urZbd7Movc^T1v~C@rR%sr#BxaL3&dfvIo85H~3;)-lUTcH| +ec-Q}G2yETjlpb=e`Bb-KivN~FW;blKZ1dL$26Te^%~Gm2pN& +bH5*75DS|}l`Yu!|@phh#qF}`Iz1~CGTeYd~w8jU=W`5h7KAm?3CZdz0uTiYCG{zQ?WBZJ^JEoyhQ)t +TNCaS0MFgfb`6>KycKvDkz@d< +N6McoAq(Dj+`7(wxpKi_Td;VrY|kFpI;udUGXmu(_tlmW6f=$!rNY|^jg>yY%3ah=1C2tLR%MYOLxJC +=p%=$!5u(^9ONM4qrRAwBn#**2^C3t}{FlDBTyv`!)n(OnKEJvaA|wH06||0`yGcIXsDh_EDNH**s^; +jljl!hzv^8hgBgK*3wQ%>5j&?#Qd(O^-I;%m?N&tf=0U7|MQz*q3eo{L_eNPEOnR1Lo#+Y=BI&DXb(k +Sc;Yiyxu2AMtD~(+*3bqum=5OHluavpS8Blt3WFv{ +-0Hewn-cM1?e|e%)8tt#IJS9IJ|KDzm!)Pwh2+dk*b(gHkh8zdYQW^tbGt7tB9k@#guPHh +-MzNx6Xo|L0<(#!JqAE7saSW&rr5(&nj2$1-bzJF8tKk@~q?0|$F(F)T{@2;*39Pdsf#c}EQ~-$;(A6 +}^W_}i=yce`~w^4bH1PnhAq4<*)0RG--{pww=HFG(7h_=rY6-CoFnev$S*NO`(E8~=iUR+1;(8)t0y5 +MHlSh4S;m@)j&lZG@6qPR}R;iq2zV4T_)qT9C}ed+*+_i#Y4l7Yyp0_+VQnH2Pnb#M_FwakV{FkXZ4M +s#z4rOCL*p9@~PSvKt>GQPZYoM<|KVho*{pP650>{e1oB+Jv#DO5lB)J{Y$T;arHczZ29rAOu8-yRD= +c$|-_=*sZX9OLYin30QP`tuD=R_(W1-GIXd!9M2t!kV}YUt?H3O6JsAE*nBOdI4qv!)T~N0vWSPj7^7 +7sz&UKz>dVQ{B~R3o@8hn()uyePikVm9G$6zXx!Z4!}W(sQ3>No4uvSZR$@0wd6n8m<&L| +x6%zI074xs2mZO#1dKHW-w-3|w4@Qgwe%i}D*8n}dmFhgI%L>*)OOt$`?Uc)Q3_xvF5ldfw{FD)8$?% +aGJXU_T+D>U$-JH7(m)>e6q6cTAEQNbv6uvimG-BM;FvE|N9kL-WVRHLY1!Pm|y4BQr=+u1@ +n>WY$;P2Jd-{_eA<9$LGsJF=&&w<2>yA-O$G&PCXZJE_1p7m0CID-7FOK=K`MQ1*6xVE`;qYLn=B`!Z +p<2)|+*H!%$3*UYJahkr&#|sg!%FN5e+8C(s(Gi~=0!akVdMY)x +ZH@KVdpU0-Gb!W3GCXy{cq1N5dE$)9sTL5M`NBGVws=r +)&K<#{X@qfAD{fLJdx^W!&cw+p@aM6dbmnv{6Z+8teut7nThLBR}><7O#-9M29uN9Wskj&pcMF^7Bi; +AmoUkCVX9^g1`}!kFMfY$J0kl0%~a~RnME$E~=WXsNC5Jwzs!8cXl>+;S)UB+1Y;S^w88=Gq^fE^Ff< +5$dX_&8{uA?ulgy>*@jiPW@d}Yy!L{f9hVs3q0z&}NvFEoFPl#1jdGst=4YLA$m!cUR@qhInS1UEYsz +?d-YELPyPn0I)-t^{xP;ZyP~LEQ?PBTJV_kugMyaVNZVI8tfw8CDh&q_-ahq&x(avjgyCJ$kBP8KJwT +2_5%@N6LTN?txF%lz$VAOjV@`nSvlS)`T>T_0vt%l>}YaJn8cm*NR%NX_fypka@D>^CV+S8>$G0+PRM +yRUMRqMh(u?$!G0^vTERzl>rEfV)BT`QAeH>*S{Qp(|YlZe&!(xA# +s@EziVt=WiUi!I+0JgjBJQq>GcD#gC(*>LyW9FkDv$n~MOagVR74&7AI=t&NMCv+X>7p@UwA*&DB3vZ +?Bk&R>pfpb46sKW`y}9Y2qkyGs(>l)3BXGKExd!Q+Z$6j!@awUi9Q!v?qX?y!+6+D~_X4_gmXc)H4k( +5{bA=8nX8f>fK}gn&?*sK3gg#F%Z<(wGFOz*Feun=z{LK8J_!|GAP!i43#qx88(UPo&GN0MtO= +(Y1{;F3?Q{`eze!u>Rmx87y+Ng(A%FU3az*Swq`1-yqPeBvVhow)Dy`414c;KsKdQ}$V^^O&*RbRRGB +cL1AUOllYV;|R$ti{cSVK3CEXMiWo5;!Kl!ce +1m0kbuBPLwb+Sd|<5g#bydK|4eloN9wW-f4TFK7TH&J`HVj7Had&5HtJB@y+G_bg2(*TG$ru>bms@Md^Uy%sSwa`>+M}R7tUbDDV~$e*FvKvD?GgSM< +3G?vH2VNV)ovNu+xX6J9b{34EtS_7nv9-{t|7vlg4b0tx=CYXA$j$T&1E+HLO;JfcwCB{XsJ82@;Kr;^eQ%i5;Q;Z94 +}$;=A5#4X9}#aP&X4L4+hWA4_;zg6X9Z7&abYBi69*2cO^R)CR7O!Yh)$6P2xMpV^yMEJwib8@~$gvq +l5$?@GM2|QchkC9LZ8{++9TqFK0glitxtYy(B6jRc9enk;l#(YD+%yZsgI!9kqJJD{1p93VF#$ +9NAqjb51z&~4K1S}mAM?pfYucTZjeAfftrl&VhFZZ@5P$&59fOSng#8Ixa+8fPp +4ai1Y&zK)0UD;=pp#_~s$(sefj<%3e#dP{}_Ep?$mMaVXh!*Up}#6-2i&&S=*$L +uj?E_vBlhw`R7&qzmN^>t2G>;B%-65}psW7T!?wJ)s!aQ}hmd?VO=L67kvRy6Nvtga}bYX#~!A+Xi_p +_A`?&+NxYHk8fN)P()i(DwkzwO{9(AR?fvR2%pdloS5!w +!Sv8ZY)4Fbo@bQgmzm#3&Mz%vL(-&0XQR9{3Bvwe^=*!r0cMZEOD~e*8v +YGS>*xs(aahnIw%sbTGJz5D})iqu7+b4y1P!&7*?ev<-v3BF@T34^fuh&1p(i?tc%@l*R9Mzb)*!I3$ +)4+X}D|nswWePvVA!>sA14e`$d0j7^&X}lHon`MFhhHp{50%?!TA_Bx1UF$509sB&Re2m~J8|kBYo3H +!&UvY*y%J@)-2GoLFV1!u2TCCDR~-eqpM7#0x_yk*z=8KQ?& +L>Obh0bzu;%yfN&QUc*NVK7hBaZtcKmdk1kFU@9mpakN)i}9UX(R!V<30TU*$$@Y0O2TKQ_&Fa$}xm_ +hhDE|C`R(5mK6rbd-|7M%Qs)`Ccw`yi!=%$MY$U$jd*2uvFnSDP}Ok?qbM2>*H3MmthCe=vYqoJDDc* +(*g|^qb>u9v__?_Fo^pc>{y_S~PR?COA0xNAT+O^_t1}BGqKd0va>a~E2&tDxJ9|z~B!JF?-UIj-dXGhS+5AP4&y*qs0H9GS +a8+v&dygh}VM~AOJgAYML4%sJxcHg@;cRGq1W>FtUPo?`b8E8?d08khR{R_^2&vXY2{czJh>D~Xlo1c +H@gZ6!!pXP@cJhEZ)YuNeL*!G~#rV}``6!!0nV4RFedjmU+2pgQw3$bfpen%%HHGRlq$LMl{zRbI0J} +?mztvq0iuw+JLP8xP^kqm|kcH*x&b%%yMHD|FG}_=Oc8GUowbgBg7Ae;{j&U^jgd{>irw$+ +WfGT>^_we5K`K6B?S2)se)eV20x7s28<=a1jw6_L!`&rKHi +f)4@~Le0`5E|Cyy+I-v4@7HeW>`$#0DLJ58rzaus;%(Mto1G{HD6S)htRM;YVEA^qvZ0w@S*loS36>9 +rC6PGkpo0|sDVX6GDiR)nQHQ73hi?wPKR)mO@ZHh*VIO$?`!hsu@MHF4^<%+hK=c^qWz1Z*5?QzTG(q +qGf>&9|$hk@Z3F0-MH-ETJfOpKG-m7cqOE|)Ro4g24p|v6z$EBskx>Fzms=e|a{7GX~^2ns$-GFpU!q +S+uB^Hc^=o|K>b2J=bQ$J=`*GcfDNkVio3rsUv98=|jDAPvG(t;z?gzMzAg5RUYwh*UD +k0V=T(lZXLVCBuY|M!14fj68xKssBLudF}YS*ZPU9#C!qJf$Dtp{48{@|25B~$zsg`YIQ%%Hp7`dSB{Fa261S{rO7?Hw +0x%Ap^i+&gjsmy{S};uUoE$3!v!zKfsRPfrOi9uBmQsPUEJ)#k?N&jQn3h+zqd#{CO-iKoL#;Q{sJO8 +$EpQX&wAN@$js{eiB#Q1$$S7$vpU%-r<2J@S0!)aKratWPvY}K59GF7?ODt#v+dmw-Or?0~Qn;6}ahb +d;V#Y*mK+$cUpaJC=j%g9ffKoFEJoufwfVF|WCT071v9M*mb%K|?%Y6+<{y*Gq|ULZX{agvEKm? +uo_0cuM7I`dJ9%Us<^q8E6PNuy9`J&7$RDXftf$yGw8oCHwF#1C^=LV3#!NGo)nc=fUuP%I_*hCYWKA +E>|TJx&- +Yq0ax*Kb>EcvOA0y|dNDYTe-LufE#-8vgEl{iiQ?zxwj4uMMI4@TYll8>L7%GHuLYfHztK51i(KL~VY +Kj~b@jz~bW|(6?enGhAlaxt?BJ=38f1b~kD>OfQzUdAZBp&C=5I%DW@k0tW)NcD|Lno-3081kFWC!== +0yD%f(1WO>1MCD$(DT?$!kUKiqdPVXbYz02qd3Q)k}btHUaiQ9R5UcOR-q7f48P-dC=VK3a;-r4Q-dY +vG|Y)62nV5`&RYva9bx+mUq^6c9DgfP+)^wzp(`;tkm)pjj%x-Vb4vx%yfS|aXh&nlg;ZDw@U@Au*iZ +MXU~Dg8Vny~en2IP!Z8OX>Uy<-?jim>uI4%DvZeov9GiN{6WDj%lYWLWMIbVhZ*NO9%#%%+ORkjWCr28l4!MvxrlrVErSXQ}clQ94E^WrIgb~5Dk +Y#e49{gEY!6|AGJnKFy=hol9%gt@ +G;x>eDf|mLwpyx4ylmQGM$1T7vY1EM=>0r@Ox8X`0|ad`)NZ9hPng@Gh)Ufgg=aB-^m{)J?H$FusdD_ +ZGo!+&!X+ww+_A#S~=`=fr{65jJcWjiRE(_Flp%>qa1Fh~M9&(Um7+-CJdZo@BSc!f3UL+vwSNDE3M; +M!crLo4BD0^>AWoa-t!H9UyHQo4fT$%QhIO=3!~VdpdAXVhrT_U2#mUaue7%De0%^}rpBff +Jl94WE)~Q9Jh#&xwI>t=nT%5Z^90qY%BIfF1PxXgGg#xx}uY{F`xS7QwxHwsrgF$*pr3knroBS>()RU ++5AjSi54Wt93_FmA0iHgQS+T*if2?X`}a;JG`;nsv--=F*PbSa>y65g10cB*r#+6`L1hTR2OZ_xPV?f +Oc2!4%qsH{v8)?Tjd71oTFdrmOM{iW46fbiCl`MQq=P4!R31KUl|1alqvWMY4%^=2iYA;{aqhfw)v;9 +1z-Yw>Ec`XNQ<%6h}%}j1w4@AOkWPgf(gIq?0VP-|;j369y+vLl~!&9^GL$G-FqUtrY&SxXXJM_vfId5>$7Nd6J0Svmd8oeV>)6`(qM` +#p(hz>dXmY4ZKMQvx*bQMMygk@F|l!J2!0Bxk#lz<%!#SP2sAOm#S7Vzd~b@FWg8Q_nnq61GvqG81q2 +i_&SQ45kkO7smoQTUwcWEuNWOGQDYGF*X2eoyc1b28D2nCt5`*|roEM+Y#n>IREYC4c>2zhLOq5QYwd +|XQ?7tbfx+XI-G;Jb-L5H)RVG#FZV%xry*UF@3o=F2y3}qBu$I+O>tVSv7g|N1?V9+A@BRXr)=l&-1# +tF~z&tNwCNKP8`c{EQA`h2>*{6lnZE5un>T6KT_hc3rVpq!`6B2xARyroQtD3PI4xn+m7YSa(ZG=3_6 +o&-8`v#bCdEFJS5dY0eOU|7NtH2FXT<1>NN?H%!DciVsUMm8&D6#}QGz+o4pL%;MW6l|rL5f8~Lu#lX +VrlH(?p#hHnGRefo;B&XDr69Jr<`j3;VXQWEM^oE$;fBff42#^Z9&pD4z-6H#7c=EfwcfRaT)slT;wU +-qAunOdJX2V*k->aaY#MEyFwZL0v$&&8_~yYKOU2AMh$YJ!c>Wvsj~F#KBEK;1Gm~hOQTxY5&{Wjvo%Kydzd5H$ +e{@t`wP5)mK)VbR=c<4Ar!+H3{L9F#PyqeV_1=hEsVWhw07A5H;F1}5k+L+C4A0g(T+F-VdD)J(JnNU +5GbFiZ9SPzVd`M!(#1FI4U95h4T=Kr-+60=YNCE3;<=l5O5RdEi-NtkBro=Zoh`h`5y;+ek^hd2Pl1q +1D#7vvV8sn`YHV++clX-g?vWq6u61A8+xx5Gt-WUXb|!-&De6Bc%}K>`hs3MFjN0N^`_lpe5v5yj=}B +8Mw~m_~@4ts@0%5;VmMMa5!WM3^N^zjqf|)$Si-QG8;Q*uzWDravTCs^UYze{m4(*!nl1`p(r`Y*Ma3 +K?rb$AzEcSV6`aB%`R%5F-(X7|u-7vf{KI${$X^SH+DKzDdhuBv!CqgxFiTO7CxO9V02M@9n<4bNMtk +ahGmTyfH_RWMjXD`yZN;;npxcJi(gna#ZPdwbM_03JN;G(|4c&r^U)Ea|+;jHQhxIXFCqD*` +AQq@D}2D>I1`k)Ect2^7=u^2?DI*w4<(j&1PaI*)KuDv9d1iTUWQU~NtPM`w1-Arn12O5DBUhCt<38N +OEnft)FrJOm(+f&P`JWZcm*v#P$yGc0S|=ZUpIWN(7^}gy0gOYvBv!2@ugQTP5D`SPsfkm*K6B45*o% +*u&7P!^YnC)W_@2ivfR=BlljLd@{UL68xQ6gEBM7D@{0cepLh%&@qdXwtg~%5?6< +WebUfCF=*<@`gLrkYwAH6#QVgthF6NR=D@PrZtwrS3q^S2=p#tl69Zgqld0SSx9!;L9DPgs{8Rpv5)! +AxTrlF$qKqn^VBW5Kj*#Fg^fVkR#6Uv3i(P|J`4frRD%i$kVWjA!7wjG0|2`X{4m2LKi +pmPyAO~&v={n7iuv%b61Xp7Pz5+G-vWWAOn+jc*XwK>=aC;NGQmio+0z~&RP^@c#_W&(Tpk3DHdtg)b +3n+piZm3Wl6xo#dkf8wdxQM44^t!K-(KQYZOZbLDh`pk?Js3{B?6IXOT6%==*mro6PAtAE<&r8i245w +5(O9jIqoX~nUQrI-T36_wb6Sk!A|AiS4Da4Z&4OItB$K>>QYD66ukz$yzIlEyfvP!1yJVx=zH;jjvoY +uE6SM@rCQ++C+l1{aCUp3U`hpjP!B05r@u+6`3`2;A&W)-25$x};@#4X;gI2Ck-6`)XRWMeqRQ#-1t@ +wGsidEI_w`x_j`>$M8_1>%3-fDi6%hcJM@B{0hKS?M}>?lvuSlAkk{EjPxoo`ezvKf(XrI@0mVm8wu_X7;Rfj0~ +F=K3IHV+_sr$M9FslU)GgJmJaqVyhc$=|Hd8uJCrj>_Z9<=|XzC2%GKjByAvbSunsD!?zA@#GT>l_>2-Wa{0BVw`A{s}l>Kr5@=5`n#j^Zt(W#>`dG|s~4Q6S> +8F<+Av{yFBb@5w&Q+%{`@D8CAF;b?EkWF@MP|E?PSdb$&(ICnoPUSJ(l&ENa_O7)exMpQFfAjHBQ6Qh +~Odab@(w)$S)(~Y)Oi*hP~j4z_J-N#ju|0(!9wG&?;us5`JDPw0EJ=o(f(VK80UoLcFe)k;c?`Rab= +L)~e(h4fsvUO8ERCVTh<@u4foOHsM{PI8p+pHo0x|7JMUP-T>$l_X3F+fo(|*!yCq>iW0*6D~9;6*an3FF; +-OstI+BjkB^Y9hG@-IgOfxMu&?ecnf3a&aF{};PX||S1>M4uVH(WXabH8Nn_bais`f^-w5ZRhHks7dD +-cONC0iZ3dv{^A3hbuz!U1^#wjT>}G>^b(E#H;fNrCYR8 +L*eIKoUYh`Jz+*!Ovv*)x1#p|o9_mBCS1aKlQBXL8BJb;SUHUGl!(bV43alfND0?l%AT>(L0)dN#Lsk +_5jkH;4|~qu$=A7@%MyPu07ioflqAE;3#3qA^wJf%$KBMHmJd5}u{#W0NKkmEW&g0B=1CFj;71sBqp* +Fm%^_Gp8W<KNl-XY+yS@l8B!mL{X566f5KV; +jL{jlSl@_42yEdDp-mpnV(*Y%td_=^p4glru5@q6jkr`I3W9|*andc><6Y)mb4NVKc%1bOjrKV#H +yD681tU-z4|0DVgS133m(6^q9;X)Cen^@E0SuG9;7{5h8@<+P3^kYwD~ttVnLXyInIB-*FeoY`8aF&O +JmJjmRFE@g@T0JF9r_hiGyH{cGo&^w2XP${3n>#CszviqPL^G3G6Ex1S8Wb8RI{-*QdntBiqSzJJfKA ++MH%mavIKF=1M0T3uuS|IopLc{@XqFdd+n%g$GdAs_!+`~Z6oYBe%hD!t8Q?1(z7gyp$F?*rrPsze|v +{9k7@1(&k9K$7uOf?PQ7(@QWZzpidSsy^J_Z(#QsLiKFYAbjuoTW70KaK>|-Wl(hR}Xt$3_OYjy@(HD +!>TA-})MJ#G9+7@&yBAqC*nEZI|Zg+J3$eNN4G3DXc;BvMbF&=5pm4N(fUMCiK5G5R)@MUv(rC6+20X)Esm5?=Njh$p=oKFvMkGy<*Cj{$kuii6L?_~;ND1j +gSwZF`pP&+ee36#$Le9V>T5Ru&1-Zgy^KMcqUcD-1y +{$gDPhy3Ao8s;9er$?lh5!IPU0lRy3O&*{yFax(t&pZ?gg>T+;phTtSYqilZ$olxFT+ +ziZI9zB!t8H&$`0vl-u?Wc_TXF>a)?WZAt+oT5`oo&4e_XUFntcu{g4i>Bm|HMxngqPv`^Plz7@*^7b +zH=e0D8xNpQRsdk)rXO=OmFZ|vEpI!!ovgxP6(^KPW*DiN>5kb_qPm+VuzwPnJW(HBKCVHO5_@{+f>E +*)a}*;9K-!~-}b+i)t?(%%RO8`ltYsq>N;N5nEvC~61wV@rEiXP>DXW+bPs=Q)m?Hb_T0EM-yDft(Z} +d+g>agUBqUZF^N!(Xfq|)XG(a(2(L!S_=!Dp>mRmp}oQF(}GuU+XVC9EfTh)L3G^5ysw-kY^!L07mau +|)o^1F!rn-Z~==nUnv5@jQ={M@J?tuTcjhgVTyq?9N!AkkAq*wj)lrnV;mONGf4%#1|CX`~ZRo9R<&I +wDKI!Pgq7ff@{){tuvS73xVUc}#T*ObANqHb7EZRiYo?tWk4W$~!U1qJ3i$$T+i$wlqd3qoj +nRStPx*+M48K2hnF;(hNp(+R}qE941DN#`iVxVy|N;jf`8HLeM>I$U!`&k$|WY*6TSXuHO(H!r_G7Oa +vPo1|*<5G#BQN1du(l~UZp0hv-AlwnOC`2Lm{*D$V>UL0%HBL%Yaiack8ja)ND$NTFei~q^V785-E4O +k^A!-+SanoBPLCFcUcT`ejn;*En(@Ag&?Y-9Ry~XyvmF=BzdoLG!H*f~+L6v`Cd*0QB%*G03h$)B;Eq +Ub$(hg=tLJ6n1RdIGHKxoO!FUdIF(mX@8I#Q5CohmN(Hg;h$priO_T$h*lJxZ>wG0k +tyP)vf6ckDr;plpOEn8Fg(2SxUVxHy|B1B2oW|M`y;o!STx!(!Vfpk}zq2xjZ=iEe^5#An4r$*>lrBq +uZ(CSkrz|)sC|#5KeH=$)^Y^zo9N~^Q`sX3~3KV!V>PvG%YQOn3?C`C3lF>b(4 +AzNL9fNG8u9QWJS6p{#Y_V+_6TUtg1pgl#(duMp7j0~(ec25z+Sm?%!xs1(!WM7&kI_K;(q^>+7BCQk +1b+z*{3R&(9RIN(c$a9CJE`TTd{M2tTbtaWbNM@?|IIUc;JS5xT$n?8Vcen!tBx~NmPA!vEYHRDZJfn +aOj?m))JBAei`Ko(X5MY4A3zI~QnsY!<|VwGc{nxUa&T>r$fgq{3uD1j=iQQb;|93efK9sxUs8jvw2B +cuHzi~HT&$BEh(=U6V#blmG`4C(nawlQzyR@_PAE-j*y?q^+52Miv;Wz06#dKtsSMWY@x-_AkkX~2(f +{m<6H<)F8ce1RKZX(dYhZ26LBucWhXPMeJS|XZKda(NvyMh6u38Tsz^3*k;W&~xD +C}mCFe{rBs!51d{CmOB38(1BEBkO^71lu3DDtI7n2gY4?nZ7)UIQYjqo<^JN(k +c;_3J^&$U|+UULZ!6G3fS)+O|MHEmn%umuY}LbC1RKlBE_d}bz+7z}#79xxELz+<6?xLnQPka^!V+I` +7XrxLwqH_7ZgAJ|5)GCh6RCPA`|Sc10TU-beGcy06Mrwl9;xV@F|xM2Y(Y*GBwSv-D9+2opxwglPc@tc-|wz3TO5fP&s^SR)aw9I9QAXG9Jl}Q*SXneqy>#A8J?~P7FMA7e!jy5JX<5LX`< +1mHeL79|Z5vu==jSCTdq2VzofEcO)g4e~YjMd9c+G*r2D$s7A?BSQN^(x8` +#wec-QNb!xUZE}3VipIVmmb$M3|NY9MSqq>hdHD4kUwynT^6O%_%=@WcRD^~S{bs%Ghkfdg~1Nxbv1L +#;!)To`D>#8EpDV(TuFD5Dt9py*t +L@y8^RwIcorCcKp;ecSo!&rWJvjyL2eNuO~N|ADA?JQ=}sibYCglHtR)<6Q}&z~41S&_lso<7uPA6r5 +bP;X#8-1HXhaskdsT#@1tHZ1ai#&7h+91OA`pWnHpQ5phM7=g;1y_LKJBnEp_VkA8K*8&=?qZvUvZJ2 +7O2t+@ma^R#>Y=spg$$_gJPw}cy!7FNyDX{hQYuL6{!13DaC@U)+?hQ;T9KYY4LAvki`MQxY-fm$T4v$fu6{2iuuVNOVtyPq5+s +bkseSKlMryOT9`DVv$AhzD&Jm(zuQ?~N{z=8OqcI#-;Yk&^pAV$hhl?In$vJ%P#HS{g9NSor>6G!C?e +WG(`lW&wU+&3DJ9tn^g3_-uIxy_8|&tM+#L(npW@Sw)le=`_l0?wj@3Q2*BwJdRkL5j*B0Xj_6ynqHO>ik&^(jna1G=0IVQShQ1Hfe#q?7Ip@i##X0gqu6Mw9- +(a--NZ6wAsLAHB9WjZ9=uq!umzM*9A@f}K}^{8*z~Vl@fPG7FEkMA?YV>a=0S*~iLdJh3h&u}0IF0!8 +12O9P3K-MHE;-OVLd3`92%>!qcO8~e;P;Zo_*ktb+%o0=CNW2i_?+VlMN7g@xVjVFUzhS==$XiF7Qv( +nbq{HUE(REJY)aR-?e7y^BAXVJ#)*{ua7}BzpGS!Gn39}`K6{(`S{>@{^v68a9aF6m;bT3JS~CIXK8<~Vp<7`CJ4hcZo{;SbC3wF3g6HG1uZF!k*bb(Lz$Z>W1qE)OS%>s#~;yJ1 +zL=41AO*Rjc(SE1gvRv^~8>QMmHU7cxSX52B4>v$RqrD>(`&Rf9?nw^FZRB=d} +!9c=1&_<5-*^+%eHJwj%B%@{X;%BqQ2sLr*_*^l0RX;9-x37x#?SJ=%w27skqT1#Oz$I(zT&wy4o0U@ +7SkXtA?0E$oxlhcCAoOQE~NNXLTENWe`F%Q9MXfX|gPA&h4Ozu;#E1aSpbrXQv|cP)J0?yY|yI6c}Ac +MKr2}ZDdx1KN!deKMFoa*hNM;7@$tAp>b?GpC(m>YnEg9mnz_%93+O)9<1XoRfz!qanm+~DIX~Y=Drg +t$3kP+!oHCW9hJ)F(-Mt|H(T|RWj-q7QI;Jd_J#`W#@uZ`_BO1AeJU`y0{^qVrkVc-D(u{Q94J5HF5))X`@_j#cy>h?IW$e>oI}Ea2 +x+{4cxYz!G^bNXsUus%dp*?|62A@(!=g_nSdd#Zeoj#^&kRD^(0e=uu>1x_u9*WB|AAS^&$U!PXu~X2Vobt+vN@uu3iHlR=4<+ik@o4ENuYt%g87d&G-2Uv)xqJljbaoo@852=Oly89mP7 +#qUlLYDnvJ(}pV-2q@CFSoDx@kFF}+U|B!TnMS+z*xR +}A3M8tevbe(v|)dYCdu@q6=DvJsptT~FZ7@aVk1Dq@a^ng%5C^f +UVOb^hb-jSDO!jOF}7aO(Ur>72#X9^XdwO?fY+JS&HqM3mlJUrcvo$mG&_MJcjn&)C;c6{kpG#mxnJG +;Th#lM0-{dudk(i!kA`;ki&VEP%1gtirKcegwBl6ZVlWd-9LIiar`K<)y_FKv+8gPv2S@2Qa2x^{>OS +77H3zU*{^5JN!jZR1Xv?tSIyU%lBsLbHEbYb1cP^+vM2jo8`wgt0(nXE58lYmH|6D{T7fPigwATGL+} +RC2CQO67HamvN}7h$8rp=OZ@fH>F-?Co|EE2hXuAoapsm;^J{sCY;{^6R#8~!N4LSMkeK%u1O`T73#&P;LKO#=zgsuAtReLr%)xB) +7cK>NJ&WpNc>?hJb3x)b+6aM$!c_V#ht_#l-3bW8h7?(d-a-oZ-bmAcjGnAZT&uone_`QQ%!>CwQf@5 +S#u4Ji0ddJj7Ymqtpix@`}fc$ylZl2(@nt|8Qr|+jG=iZgbZ3xLj7=ThZ@rvpRu;cKPFFK^5;1 +Q4_T(Ne1)wlt%l)DMC8cn$zT-o5oflbP71=FVAA5)NODS05+`*@O`LSbd0}35bJ$i8iKL7LU(r?(|B~ +nUBjo=PxmsAAihz*^$p);HE0KC^1yK(|BN<#IKyYej%|9xg>4plR5*3aOs6#C%01>i?QJRO&X+9L%m8_p4-Fx7jF`OoQncBwMrUeqwhe;u%5(Gj-a#m+!^b)kx%R=$W +!=iuYNW#JiZNG-eF1dtZD;Bbc|8z&=9B&-tI$cqqZZG%lX^uuHOhWkk#xmq2ldH@v0ceG~rBsDjpV2m +$SisF*zzzkt(Wrf5(X3#&r{KaYb#_1|-#9~R%O(}sFH4wBlJ;u^%=pXCxc!Gtd1~1;KaXz;|nM=|ECB +ywm+^cMuWh_XK>=mRI37r@LSS<+=_E)ZPrv*0T>gZsEc!Wp!inlG5@=`1wfzWT{RaR6wC>%^O;-Y;kE +oxuX>kNcc%C?{?J|kbUMVuQcSCk~sW+4mrL}Lh^5nz$cG`W#yAB4zC6LTtLRw*>|0;%3Z3hW9_<)jXc +4CBg0+2dI>!ykx*se3S}P@t_V(bbVLG?;sd*T$rh;*K&R)1(SDm67o}#FHSgI_%kCVA2fhSV)5Ds^F5 +6^2ODw!Jv@vi4)g&Zo8BSU$r@N?1aA9qt(fwkIOmFdi>h51(6@_7iI#{AbM)&Yq#5;McFR@&`tf +7#pq>d!u;Il>M*XMQ>!{ns-qsMH8nIq%`e59HczuL>K@Xqp?A8F^inD +Q_CpMM{Rb3v^NJw$NrqG`fg>{y;}LQ79{*$^joLhrG5Ijh+&@+0ce^K8vGKN#eNBF^7>VeW~+8lR1bO +Hw1^rP(EX9>yZ2i@-IUtW5P(&920k;e2{l6O*?GL>QC5i$xP6wh2t!*5D;m`XZFgO9l_AgPBpu)h*mv +rb3^6QPm9U`WYCryH)Fe8{ID2KW6Pt4+K;*%aBN>gcf=s73fH#r=R99@#8ZmC*jM} +w*YhA{&7BuClkF6>3WS$P&f-UO|itHp-N>PJwI3ak4l(ZbnvjL#)>7enkOxdEVCq;3yP0PHjmvtHA3_ +h7a3g;Lzp2H{&30`ZA7OBSxN(XAjwM~Ejg2iA8$rP?TL}acdTrSiW?3!jdsGRd|tB5Oq8#d9L^M`mBu +7UHD@De5wFdTYZ;@eUdjsys(V}wl<-wkK2NJB$rhZMnk>ZE0C1-Juvw1hTT^QxtK|6RF07axmEs8e7Ic4Er8qj@Wx75HhVru=SQa}y0cL6k$Y?A&u+%NzXfC$OIyLkrZf~Q +?~EFApyFpX>vOE9eGL*<7!Rb`0|>hytFG*UQOR6XqW;>+z2wK3Q3<1m+NW@Z$I^P)I}PYW$D)NLuvf! +1U@~Ht_SZ|=%?&gFM}6SFR0=NXy}3Sq-uhK2PWFPIw||9yc79bZXo^0R_pJzY7I%y%K18j>c9!+s-B3 ++^qBjbbth<`owd>twaVgY$FQrDaJE5f}3CUPBMp=1VCv{x9-p0lT>7eZ(NxqBBC;E<=M5yaL9m5%iYr +__ezfoaN{-k#?ow#~E%Hg4ugzD~1I2CjS6(Y4hS*}*|)&i1%Ovw=LNBJ +_mvt71|^f<*G&t;0p!V&o+Hb10d0y4oo(5X=FM|}-2qReJNYW~1$LA|X-`5&DOmqf=@apS_@dpu^sl3 +FoYs@4$WPUq@@c{Xn{q7FU +PA)5pRLvS7xk~kxXS1@Mpc4L1TRO!%!=w5qsRiZ3SMw_MRBVVrFYSyGBRld7QK)`)@&)9OSAV7m=d-Vi4xy&9K>-pJ;dMT(WlB4`3oyBICG$V=HV5SGEM~dh81YEku`?n&O&mtkut=A~_VUhDI9$!cvl}v=OIfp4TVx@PV9;3=gK1W0x)c@YZ5e-CnU_dR@*{l(Zjg^Q}D|JlUEvQ5?-fNhZ?!XVe;!l&J +9OteEl)%Cvy_h=fuf2~(gpQLx8}g;Dx3=9gOXo364V&ca03`1#5Ef;flP&?7m4=+%D%o|X)M`%#D7H! +dtTZ0mAQfIFkCK$c*%58gvL%9z+Ax^{Msq*7OV$#fczlc|Z5|cbRCW?|V)T1y +IY&jGe)`&*j5f)%&@>ue>k;5SfIQNB7p)0MVgh5wC68`Ku%Alh~GNM>D{TA{K38(cxTDn^!vEgGxFwS +|jL}HN!S>#-12PpCgaccUAO|=|XT=7r|VVsNd@;4x*nDP6UiWY^-7^Ftx76BXmCI|Z<=yf^ohalj?ANwVrMxxmNM{TCYJ-}Iln|(g0 +(nZy3uSUbgX37?(THS>OwzNNi}r;!bDN&t$k?)Lr7=np>~dm&Sl4N%^tLTNdwx(T%OT1*^>!KK=t8)J +XA5%!zur173{I0#8`~5ItBhiUKzmIal2 +hzn#25d#LH#%q%dzY7pVld56;;@(=*m}QTy9#fU+qAqH&@hjLRzSMmp1AsEu)681Y=(l&Q@T6KWStUVN_LV2P%;@9L!so3=xsXfOPu^sL7 +!nmypUaIQlVfB74w?~1*2ZE*2rwHFiVDGz+iP$MFV!Ls1GA5T7=pz9w#%3C^J!hj!rfwK;qH`@_Ezu+ +Y&mhi@$p+ty6s8W>PyoHXd7Tbk-^)VKq}RwDnjEz>-sUJRi+i!KBXo1LfyLQ4P|f!y26##tBLvDoFSV +3e^k)Us^vZcae+K#hyhTa{Qq>Pb>0L9p@Z(geJ(3<*KnNu3>8UWcG3j16PQJWAm&(f+d0tQLJ2f;KQ2 +d25nAr1|R48?GKj6LUEP7v7m0q+}q-qB!IKUB1pYl0yTr~)Ga3+5{y!9jLPo8Blz#z-Q9z4zui^#Acf +eFuH=tYk_k!eu#Sb=$Ag2l_T*Qb%JAO +x=eKkR#j+)XIlWqLPD(6UZY}#fd@%&xz5(M1@H5M!2}kdl@{in_SOqkfm5*m&jD0y!Qr$-zdDGRtd1%JaEG<92*vZBD=u0#7fJ(Of=SVFTliG6-ay!%N6v|LmOZj)@!g6Z;$=qW=R6c*Qp^_-@oGtNgMt$MbY5^2?24PM9>ukL +v9GcMO%=O*ldW(gBqBC}`~1yM*M}o<;p#*XVf^k{c+J(UmrB@XBtlht1;^c`JFD4Kot%XbwH_1Y&OTe +He*LZnV{m-Nmlo#Tj!JhoLBY5X7&TF=OcofT7=bI +R8{owpDF&6Re38{^J*(Ou$bZ%EZdfGW$~mjY{h(~*$C7wIu;-8m^?Yp259eW#C=>cHZ&L_Rf(hqCz;Ss@2tddh?eUN+%rJ<_&#x +frvUZaV4DP%n(j`bcyF2bb*-94}8x9XI)daNb-h0gQ^WT%5MJFdNWcpZsAcKdzx-n8kMC2EdCIcr9Cyp`~#SV?KTOu+v^P`V)!LB%1{ +$4&TBKOMg3u^nTUqrbJhBdT*m81Vc7q^y>F`;pK`)r7ioZdjkX2m>TvOD|>ZFstl0e)_t)Xa18uya +PLB*%0F<^a2nQ1mlN+YBZHQPhC>#H6+o~}&U4{ZIY~~P0uvw=Y)U~Qb?7*lO_}|#6gu4>u^AG42{i=R +{Rr;hjZ{hBEyJ@d+j+YCV`u-_!7YDZA?vZE700f}JI?Q;3Mw6k+Zeo;88 +DpVnf_lI#<3$yk$XoD?!v;OO~?J^WeB=o4Gy+mVJ#lKV_Edk%FX4cH_Y#k3m(< +J$8bpE^h-xEJkVdvbAMWJXi@T)Kn`SNUNDSZ=lXET_l83!|BOFvTmOu=5uIH;G@X9$xuRRd6R_vl(KB +{Tu^@-gr_W2q=_qBD=xCc@XA!RM(B~6fe3oIH+q$D)057EmFK$RCwRL7X-$?w4M&@1hEF(A1mTz>@bg +(#7Tofy}%8=?V4)Xby_+*YcV;OWnpUK}SO`d6jej(3>#jMDE=^i%Q$>4pF!F@6=@xNj +$7i3;&ZpxG7vUkK>qxc842=7}rH`E;PE`n~7=gXWAYk8lFc#5Q8P2M0mh5Y^_2Y5v$Z6thcG>z(Hy@= +)a2#G~3=xHUd?0!j{1P|26SNLFqT61YBBeARLqxJ*0EG+;B_ne=Ndyfv3~>Z^*6Lmz&V2&N*v(%d;}d +u0r?VJu7alCMV$2UWA7K7)3W885SRPNrR4+sfiLgDNgp^|ePm3|@s5GCiog*1gDwptMa5NdT7C^2Y> +WV37OCh%23H(>$Fs5Io%8H0Jy7fALY!@7XiH;M#*srvyJOHP~^7kMW~PV$cbF6`V&eBxq;&MYY2Wcpf +f8o$>wN^8E&k$VMBb0jwxqB=KP{8sdGPXzeBBr1+vdWbh)ip>u(|I_$vM4q{l3ooDb2K|%d#pUcAN+- +u?aYb3KldbdFY?5!@yVo0c^VWn4w8qo3d-v}_jcGc)ya(eypA82)#~ogy`^x4RJCnV0iH_M=jCNp7!O +Cece2H^>T>JXP7A3U9bZE!Nbz{<(8H2TxjAIW@-%^Zq0q2EaZ&|A?mGcSvBZHHnx)D`JxhJIvCdMy6$7U&CAC2r--q$%+DW(aOSVZUn3E0j0bem$YB%l;eDlHNdiCxsqvif@1F1s0~ +xqB#AU083kRC|u0aYu7Z;mNCTS~*;69)10~xAwaA+KViN$;Mo)p^z8W_-!^l9gHt3r0c(09##-b!9&ncA+l`gKW?sYGdf95_J!ARURNG1CXW;PBFja}-D%Q%QC_Iy`AoBAR_q_Ey0 +V%_NkD2Nq6A3KrSKm7FT6SJw%3vBa&ic-opQ$lHZP@3six5DL)W57OOO>^ZoRaGn7Zplil-KKYcA*x> +}h0g@HnSR+_B_h4r9$f<|uN2bGc_(O0W7Uqn1z8HR<=9F~KBB(0gs&!h%#rH&dtMw@Vv}iKje1~|JH& +IkCS_egGh$Vu9%Za(GV6r;IwA>g`;3RWI*99dsyz?|TOZk*7TuPKPa$d22xt70R_UX%4m5P@>FuWnK1 +fA{(?60ucQyIB@E2sce)yN8QQC_x#2fL9b9n7aif1suCp1L{tKOn7!wvfKis@OLNgWmP}-I;%#97slZ +0={V^Z&A$bwfeJcnAhT-!G~|qB=T*E3_XiXg_`vL*jdrY&Pz26aqO^>|9>`SmLud^%FTeg`Y75>T_x1 +Pz09l<&V>e*18$k(%?v!BpUC8+aH!{4bCXQ-MS$QFZG>s#Q}WAVA(!*f6Ekgs?5l);;o3>r?1EF0iI5 +G49Cn8z^kR36t6cpGjP-4XLL$Fyv?M1MW@PVRB_*TPUSF)$H*2lCU)MJ)b?VTxvh}*X^Lk_J9{j2JJP +#)%vVaN+1}f+6%^^9m^=Ht5N}z1LZBouvw$9r-uHe=^`04N;WDr?=3I^Pum{K6Ojf1K4{PA$Lj759=! +(*Dz9MiVu;>;w~i%Z>{^OP3@)&Yt)wO|phJe<^m@Q26p;vY1$Vm#zYTBGorv3m*Gru_bJ{ip@|Af=Gx +6P>iWz>ls7*RYYaplNT}mI?<355kZ4J4vmV?B7+Yl!7jizARHHq7dE#*`re& +Izt;rqJt>h3MFwgrJ3h7&XTwNkcxn~k%EuEv1jF>u^H{AK2%@)fb2B-=Zf=r;4SciN?{yD3R@ey~QXdo;nhdnHreOPpb=p6lb|>gyRry2EcH`bHMjDicq< +W(0j@%HW?-*7gWVwW1Wae60SQxAH8V8V~+jb&&tmJ54GFl9|imu#)71-cP4hb-JREJ;>g5A`SieYqjC +c=63bB-wKU80MSnT`m63mg}d?FZ^e$}KM!J^s9+yti*Hve#ldsE6%oMnu~IQxxKferrZ98M!uE2g9Uf +K-;HpPQW$$IA3rgq?+5Sj(Otl4?V7sUrhRBM&v&EK}KO3qe+Tr5q?h^{hrtQtU#L+9+F$LdL;`;S8E> +f-H-NSJFHsigLu>CkiE?@+);ZQTV7>_V$;}lb2UtXjxAg?>fw%vEm{#GkUSGK@H=E;E+HtK{m+8i)|$ +xo$mbKHBk%NGhni%jbieDWlktOVM +tdC8V%Q+h#u(UNfJwZFjg|AUN;0gMIB6E+$o0-6v0CxuSVwXZi1|9L0BlOI(hX1s0=uWI|c$n;gu3$prGgU+Q|<#?>qH#TlTYx94K9ZZO24;P#L>m +g%xY(oQ!^QpNHdHbRYnLX%AkkrH&-s_E|*`~xb)p|8JvV;aL!Q_-McPFn`N&99{WNQ2KC3CqV$W{?1b +v+WzUtYaryN(xb!vu3?*3w#1skQ1svJt-z^}Ba+zx^T-6~^Krdarjsy7JxV;wv&!YSkOmYu=uEW4bZb +(x2{v&lw}%$PL$mz^sn_pzN*-t`?9cdiRTe{1t;U-Bf*wurGxi^u)0ySykcu@PISBe1}$uU}JsS?s8twK7I4ZY_P0!yIGsx?f8-(G(RDn+g6~zSAh-^Co4yU0C +Rp3Y{)4G^Ik*l0v^lpW^v*kq`QS9QY +e1DG|+POY^%Lfp-Z%tx)xd6LAN4@ZaHqeN@o9h5=f}hzM(6>xbXikzxfk=r48sM2@P23awktXd8PU3; +(#gL$_hC1Zw!@5dj4!Y+aFykI*`(j-UX6St(A +@P!o>mP!>Qztu`nUsz|NooUA)+y&#-z@68}@4D#NapH3Tjdn393pz;4cx!u@k->)ptidL~2hK}Ky_>C +*$H>i()07P}EJ`)B=qbLd35OsLqAumll`=&o}hisgmGp)m0^Yx?p;}`v;yidjY>#Lb9dhOHcNgUOGef +MvG>GwJMn%4t#o?k^u_+SyB;O1*we9gN213Y}8Y0rPxZV^)e-E7SQt3_OR8@^|7xvy^2F;pC#O)r+Y> +#pvP`{#BFjZwId&O&LmnVIGi7toN~`h=Vv(ecO1{RRShh~xlj7*ZZQickr|QXhs@V2F%fX_ +!>w1KZ%F_*ZU9R&qt+8Zu+sDK(Bs?K7jeS1Zyj;_%GycqEVRqaOPhb7Ki{*qOnTy`t|Jf>*?#)qY8$&OLhnOSOTZ3D2K#xH8i +`_DDX>?y^SL18TT;6uesj$!|~A%-!qa%09;A#3A-2;1%%D5S1)#yxG56v*1_&W|1rt9TTj06pOU<~_2 +{MlltkUFZ@jYe>g)OX`tCZtdiKz76<@YL`+{FS|Mt7xr=3?Xb|3A*`S4O6xdiCDjj;sZ@Oh_m9E#89#3t&F;j&w`0Qe0gG7a43OEwV9y +?q8f`+DLa&=A5}X0SeW94mU-0RNAJY)*h8&;F6Ro{*dxxAd%-ayum*U4< +l64q%zO7@u;u@mj8gq;-^C@SLbbVAz4C>54{2aHqK`wU0uS@ngAzklpwMWQbQtd*YiB&FP;?UI<*B6a +;x@^Al0BA5IN)lAZNOBQZx#$&6Z(7x8v9Za7}{poj(+xDXsrwOV~`pY87M(zpq#_mmSZg>U%X$(+$m@ +AXyfLtB;YqbaL+v?TPx^^629oV=Pt8rg3EG_+qq1@-e&d{_5PW^qWty&Ef!XA5+{2($rmnH`c9F +lLGP9-=~nH3UjDwU9DkIcLb^XBHAj}Y$+D`?INRvXyiQdc8MsnV +cQD`>|)kQ3(cs+`1j!7#$kspVVd_Hk)Z@BD|3((x09gIuHtKY*frg($M*slB`^pN$WhhM!a^bh9v|D}*T=`6 +cbLk>aa^Z_EZGnQ##v?$?BnCtCs64mJ5S&CG0V?sBsvu(*)ioEHf%l#N@}Ct06&trnhJOEy?0d2}sTe{)-Fg#&NZjkSsuPIU9x*}ti+w@f2t73`6cl +|3u)%u4n}GZj|%1lqxSg?xU(6=8zBvYb?|J06~hWuUB4*xj?{WID!F)^0URjD@A^7WWwDv`XJ*0~_zK +uyBJx6c(_l?$5Y0|CUxdXW0mogpQ{e#4MNsDuXKsE$Zk;X|zBAS87krvn24?fW0G0)|l&jgcz1a0v4; +-Ol-#DEzy^y@bT|^Qe&et>qE{wYNq*o&$<9*Ky5-ce{ZA2J~R>-3%H+JC&d;oX5t*1hWUgtg%6f*c;=a+8@0gqZLc6?%4B}L +hEHQrXgc!d!Dzvh@mSVW_AKv-SGsYciJW7-n)5;ybQM7f=loyURlU5nMQxf)7wV!YCfSs7d2a)QPRse +vgod6At9E216w^GMe>{N8Vu4N=xdf5HToh|O<)>yAra!K1z#=4BbdK4# +-_-nIimJX4*jq7Jh(4eZ-Z(U`p#Vg)M?f)R10GSzOQ!^Yr&BC9cv3U}r*M*BS_$~SM`2;iLwJ +Ud6kxmvqPVCm4q@WRDdxlVqQ)T~X`-Y*qYRT{&KRtFqkz^1{WsYKGKx6l=hG_3Lng`V8}uct`q}oJFE +Gsw>VGzT%rG6;R+!Tf5(G@GNLl;^5qDL_t|5h?D(suUEPQEcE~yY=R_Y4yd)di6=Gy1cZZ68>+{7Fwh +ic&Mqoa83lEcp{6Idv`H*NXPOY17r1+T7qerwsh0YUoDoj5hlO&BQ5oIjQS786&dP)1`j?e(zuSRvP^ +bY>U<^YZ`%xGc;iG8>|YY0WEx3nO@-LNgF|e0Wq>+v7?+jqM#Y9a^cJXWch1v+jIKvM4m-21OiIa1yY +U!NT)^88Kbss%)B|*msf4v)N0xmW@Lbd~FaHzdIO+va2Am_VL*7_0uyrCDE_GGar#ZPu8O&v)5tC7(~ +|&9z9vNcyzDCmlPx%UryvSk$OXxI3fEn0DfKnZ4;F~r%Gg^PJpbAsn(>bXe0C`@ChCrz`dDpwD`BO`B`RFQ{Rlx?$f^drC$?`~s +wd>20zghLMSovn4sr6K0C|qJzsBVtsCb+h$B5JMoJ_)0+MDsT2b7s0x*9pfuRp=ylH#(7T4&`jLgk;P +h4A(qx|h|+)wL?ho)cCsxhf>CE~?km^%~f0_ng$n1R@vVjbdn`dhQLG63Pe)HcYVu?Di$CFH)^+C(lR +O^XXHRX;e0H9ot08;mjNg)LNzAtJE6~POXAp@sTZ&p26=U%>z?p$SlqKzy@DT(UlQ%ifo1rd2PUbb6M)-xX#5Rh +6WH`g@UjiR{JkH690hK!hhbIW8rUQ-xWD!=u7k)NeSY947&qi8e8wWsnQ!w!Qetg9n&gdW&$Cb+>|(mbVyopXsKGntDif(xAM;B)V`y?zywyD|I{-$ +1DPezr+HAwn{%w2ypj|+f-Xd1!FLKfv(7+Vm8MpZ)R81-vSei)160srW{PE$ +r#Gyhd(d*G5Iq>t5B~CcPvp*U_C64sHyJeui9IhqotohQeY2;gg$3m|fWL7f@pW1MOXEaMP@5t{mOPI +zXO?fya10C7Npr_Ej`~VyCMm?W7+Txh0BE4Hf?Vn6^&254dOE+v5PU) +pywT9KG7YC>&#Rv!i-*OS=3?ISsjU8Sz(gKoAu7}&Uz*#m!)l@8M>gcb}g+KNW0HgH&I5I`+QS%(jvJK^!9y4=~yjX#JIZ?emhG)8 +u(t!=A1A*l(rrN~{}N%1BJ +p~=uYhXb;;RE_b^s#vf&RYsYz;kFWKo=*h~0I*eEQQ$650<}c)&*TwV5{U|eAFd_U7P>UKM`7G62Ic! +XRoWZ(ll!gqgZ5|1=U;reD74jK70OnDFFdSr1^y1TRtV3(nyJ$BV$SYTxoUynP{O={EO9mFt<=MOVX) +I>ce8GgQO=W8;28TZh2ghEPul@jsRLwEG`O*i3kcfHO_oF&s(SRV<57on@;)I)$YwiuER03KESO#BsZ +W*?hk3#loBLaI#JQFLEl_VkF-EGuj*Vmyk0jUG&P(sAe +Ji)E^qHz`0Y?=ZPq`a(od3E_w7?h#562Q`Q5F&Ar@{q2y)*x1?;QuJ8=smJ3CeZRM9XHx6RU=xnPLCze1>M+FdUnq2zJN$_#zo +%b|!dmDfke}3-YmVd2q}~bHGFmc+72#Eqj(>Xx7?#9XZCg8OmX_oeR-`k(seuJ~k(#adJAJQrF;U>-D +X9hHtNdsl|C9ByVPN2W5=MeVFM00v|_A%xIYSk +BjbAk2b8!q+I$2Bc_Ezs-kX+l3{K-%n!EWNX_%g)Yap>1`v|1B>_;gJ@C(iQk26&dB;mGsZCd?1Gu2p?KnsFbCerv>kx;v$<;0r$TB?=incqluD2~7amA(`A +1lJ|2qu1|^s%Fao{)Up!?IeQ*8ApzBSKB~;wI$0BKW^cx-h?E?6#8U4shRyzo_DaY=r}%*n**iX+YAV +uQZTToh?4Vtfr)emy21k1Z>4wa}~8O3st)XJptKcSrZ&zqA}%_#qUbPs +M~KFIc_hFfZ>^ikNel#j+U+cBvbmBk5Lpx9ku#eUU%@wNvVjn89|aC~%IoC?}HkWH#AniN&*XM>F0b* +P(g*zS&cKIgI&E{yDSj&Uyed^QhCNK<0svWiiQlHjjo$Kq^o| +2XXs^OgwC1chhK5I&(Mt!)WaTCNd#?;EzPq78C*Fj}GaF|E*TSnkZ;Wlq4*!!ifXLOs&^kEut3J|^F! +{lOvL`bn0sxFYXx$o#f?V0cvsIF0jw78 +vDmbpR{Es?wMvq1KWcg9t^F&PX&3Q_)ZN??jB`Ztoq-v3t;`>!PSUrFo_mc$}VO*@N%9kN%cWK*r?Z%jZ8lQ)b8cc{+jFhM9CQU;X|2-IsgGqy3kUp!UJbeE<(%ULEYdOupOu +arfKj&ywel4)Cvnw9p}en?dr9beDc@Qp|PRe2t$ffRk}PvQ_X0)%cbYP+I?hcjPWmZB(_>z$HjMHL9Y +(5#lU7`b7ZgTa6!R#Pokr9fluN*fxPrDjv&&F@taoCN-W`GnEO#jO1Xs-R~dgBYfDwpAvPZc-T1)N>IRCQy$@xZSccD>Y`< +mzKOta2R;qRZ9r8`s2dR2jce{VM4g*4Obt1OEX~Rpp`F^x9O9x%Eh0HLhmzjymW)y4>`-+w_Nct1$Gd +PK+Ku*QM-oSRKE7x*nqA`#m2UpSl5kQHG1}_adkWDu#&ow19^%B52?)>*q4?TLCfNOw__l?8;00rI+| +??@*1Ls(ZNEpaD4a8BjicHry~D#PLIrBM1s=h6vgyAm~8^L8em$6iM|fsF4C?12Y6jO#djimC*U5n12 +BKgFFD8M|re}r@OhDQb~{f-^bqH<^k|<6OqB-z-n_g=_?KOf3xYB-9clWAlQc0xH1-vq +iGDX2eb^{Le#mQ>!udAwYD7@RtqU5Jo1 +p?&ct%O+1vM!SHxa83+eqTt%OC+wYtcotwhT|FDKy!OJB-pqnl`WCRATWOwSMHcE85w_8{ke +`pUJEETDo~@sc343Zne&`8PTjd+up?>ut(R(%s{y@iWGn)G#tOpV%_AfDu6JN3*o{05^$nPX3W%0fM_ +#$5#30z4ppQ%0T&(Lu+QNkL3C=AE7zo|-@}zurcVVv-gKhIc&sEO<4Be#CY+V@#*^04UXNt2`N*W&Hk +2lJhK^*YNLFe=&S}xt#?jXMH^WC-PokXGVm8OFeSaR5Z_PCve_XR>69_0Bi>nUplb!4U2w}ng$)u) +W0^J*9b?;@9X1Cpy`+f*1c;t(%iu^TSB4jcR_D6-Cy;aEVYgIli1;m>kzu?%Ze+*o2h@ty@#P*_DQjH5U+sB>t_*;)di-6X_=Xj5c+xj=ES+hv#6=tDn{r_{HR +y6W35z{BtcvuFl~H(OCrH)Z}Bh;*=l%l)&D)?z((og;#jb&A*P6+8wd=0#on&o(<}BeI-{#aLyZoh%( +m!C3or^K+S99YC(;vKU16!%t`^8|vu`=;y4&DkZOhT9L+N^)&{dJO@&)=;0yV~BG8Q}G7x7qs4&USzx +RKFbGoIrI#6B+JyFNnvW4<(75gQ*E8F^V?WZ|X1k>n>2FJstnjP#T7_)U7Ar9Bq)RQ4Xe%VyrenIte{ +e0=QSW2p5ph^}{G%y(VVTS==$oVCf^5~S4R1i0of?@OHHx7oCR+Be3{PkbAdT`TuZ^`%O-^h^To8 +n?o$19aFJf-fz7!?V-K*-cEz=n@%lV6o`>g{IVFz};Q6pty@S^xPN>gUm9zU5d +%a{3JqOWEYKhz-|oIf(xD8(vxgpBSiOSZ0M~r-z3#dcM}OaFzsOa5ObR@QTuEG2DhyWuIaS|uv}Rbm5 +o0k-=1^rHOnVZdHC%@1zNbUA!p;s^U*fd!K9t(sIxAl4434vKBL&QXPpiSp9BROlD5DTC~ER +)MN)?7Ya)p8%?RRT{iM46qu1R7;{2CGqdj#_ckIM5gjUR;FpraG64$rNP>E +$vU>IMT$TGz^G&Wgf&@4tinOisN_~^8t3`-z&7P?LHug?HT(@PhI2mkRg2BJiBqB +`34_J{MKanKl_x>RJ=a1mM^xMfn_6W0Z*xqoWUAE~Owhl$ec+=~LHk^C*YWcn)#5YC>4+HO)+JR8sUc +^SE^^%PkdwQXcX&4Xble)1P`C%VDM#NglXLPm$)N}i*PgsBG_wlqz_iDYH9EyCz=wxJh`@MjQ5!EF)9UhiVLuo_7%+*@IcNY|j~6JfzU$wnX6DCazMd$umOJ>0@OfR7&Lou|7$cJ`kgEa +}j@5A9AgnPaiJ^%WbDyKhrXz6DFbCO`<2tC9gIUZWJ(>ndtFm8inpPYu%HNe}2Dun<7`P02a|EwEHWUa?j9E&{bk!*<)Q5qAJM +!mo(+1^hFV*oKky;+v3$(g%Z#k5L39ZMt}jFNTA^UL&-qNH8zh*j1uf3n3a +W$F->UCE`Cb!GdK$2^0K_KY&%Ou1)$Koqm@-Lz|-{ld5Y4GlNgxWSS +mAcOIKFP+_c1?TKBToMudjlK|XGTJtrVsA4zsHeoJC?N|B_L3xuxc>vV*K-h@(G+3wYq01GE3h;FF2bQ1?L(<|M1xx29a}gZOhy#?h>Ct +yT8}4jrZ8fdu*p}JiJ4#&dGQTxEpnVXbz#CwZoS>1W$JjkbRkSr}JKBs%p8SNs+|nlgW5W+%;4N;Sf0 +eGq!UxH|z|Hv5EE0VG!t0e#v=|`y=3)Ve*tjrMfEH*X#M3vem2EvEvAIF%QY&S7AaWOfye8R%un}8Jw +?k*lw4}>0q4BJT|`IdIGKej$Bu*(ozW|Xw9#v;o$|lguA|-{LXZL2|FEUXei#%n3gA^It~a~4~&R(k& +bBnO{U{XdPZrj*{L@7n_7W$cRblQlsRQSLg2=%q7h`MVukk2-Y2)Z>4?bev+Uh02zq$+I7X3M;25bl2 +9|#|>!RO(jUjv`Ga^Zb@>Klx$W(8^`BiQr!&=^`sOdvrMcgb}zMI#;rL(&+wO1w)Vxa}rG!{syISdvk +q_o8r%`1xyc`Ue~mu0u>AEjGxsB}!l-}ws~&hVEoQQPulyw}~ESel4=$C#mna)MtJ=~;(vy(#;=9W_4 +Rw~uWdx;yN62yz2g!_=5pbqaAKIvQ<0IBQI-&lk~M`9XMUsM6#hI(bG>3M9fH=N7f0ZSg6b1GxystMtK!Hj3t~0@xNNBCA^}kU;#?n=0 +@)oKe~v7PE`uvtJDg=RdsesjB&!ljgbs4Cj5hyGr1;-NimQJ>bt(u^!`cjVizRI1P20!Z$&B@<1$7tEjn*zxfJ}4RXG0p^CY4h2u&|Z@$8sh4~$+m65?htEl +tCJ`?YW&tbiduva`(jzJe#Y;b>qsCpz4^ZKzt6F3vP=_Z9$csuU*r~|lWxQ$|BC|m;AshgRJ)vaQ#i` +mcU%L4xns9GXxSJR@ngsLPFaO%ct>M1~@35LIAQ|Krp4106peL +o4r=uEeA~5VE9DHIbP2_404D^7GO9??iF#Ho!pO)o^`YfHEWi#K|X8Ok1N*EF;60jWE)IAfzq6xdaJ3 +u=EUudAshf)LJ8@D^>9Vj-3Cd?>NvU9$c9Y|E*rg+q-U@ZdR5QoAS0 +mbBvbqlFK%ecJluLH@5+7Kp{ET?c{y(3RAlE;YfO)JkIj&w9h7Fi$Cv^;8zuXVjv=fpVG8vSHGlruliSr{?onF#IncP;QGHws*gan3!aP0b-Dnb)(Mz$@ +mQByjC5I&)$!!^$5`Rg8=B~U&7m`eb_Es@D+D1M{~7{bD&bd$5N<`*%%5Bjs!1?_ +xft@1-x*YwD(nQGJ}deS#D^e)Q81o)CRZ&E@vd-%qNOOk^&K+Dkzo~_*+_1@8NEuWF*!J0Xdm|4@C*D +g7et%6Odp7IvF5`SVkhkha8>`{Ygziz+m846G=#nX!@a%5V>r@B)kLO=e9lT@YOiO_FtB2FY6UAiJbQb!3W$fLHEgeXu&seN%SI +Kg-$rcD-jD^Z8jha>h1YR-(~05_R&Dj>!^r}7!Yb@>idia~9KV^D@n(1m%#kn*|!X!Py&unT5F8lBq&Lb_{}81m+uPB~L$A5&gm<%ba>- +BqS@5IX?YdAh#~^{^6l7u&N?(!c8r(t>O|(ll@pg{KfCIGmCs?Jhz#lLZb6=Y$D0!qG)nL)d$%qDa$t +;`cHT-RdkNZasF4QI=`Iy|VUNE_|$8#_>knfd&XK;ErvT)|wylvS|NmKJ4=l9YYMyOU7V)4JK#|Cw!C=M+!Ac4Zvs`%3NCLrJ +19$t`-pxg;nZfQr>g~WznuiO}Mx(dg}`1UZkE<9bH{Tg&)dDExG;7+#uR8OcQmesEJ{`u5Bl6zxk3Z< +c2#%nNbYK8}VGq0otN&U@6{lo`(7b?Uh1COXK;>-C1}o<4*9EJ=e4IR{Z+Oyg%rn*dRI_4%TT4@G4}q +IXZ=XV!~(L$_!2Royf2x>)}GZo`F#>>ZkNk8PSdot#juGDMf?stx?|axK*Dk=0ea+FH#WT7OJ(R5FB+ +-N&G~{7W(Qyq@GG8DFq?f@%bZP=VS=|hDNfJD#gR|@eRza6tPLO<2L#+Dw0r@prZk{UR&1i +gK!wbN0fWENagtMwhza^TCbqrUIL|a=0Poifab8RZWA7T|5HZzMFu<;5)Egw2Sic~>fuDFTO_DaC#0{ +<2JWRX6qK{Dz&sk~6Al^@PC)zlB8;#WnfM++zOPLCiP^UnubyLf +gpdGom^5BJskE4sqVT7ZfQ2G0htStsgstql4M^ZJ+Lkfl~p8+UykQSzd7i4`!mcBhh{f43ldfrR)037 +M5d$S43-SNX2&ku@6mV!Q!$g7nsXRm#NxJ_Xlt6pm?8@zN;*H8j$uecGf`QPsa1Bon7%u7MW%TnjTrbe|if+_L&amhe{#6^SRy^?BSke~NYXMa!zIp_1c&2CjEuWL6+7h~bawqhjCI>ziRF0d6 +);+f!YvpcJB+VC1ER`9jkQRT2~7WQ(*1@|3Gh1OQ76NbuOl5?ykg?3Wp!7Wb>j-tzFP +K7;vbPk1`&A$-kXWp*TX9eY`%j3K21;K_>>25SEVs9{ubh*nk|1 +B5werGzh~nR9l{TrAqWkz64ms^E9a5Z?zw^KdaW!8v@-Rs`v56r$q@4@r65uZi^s?n7|9FQ3hD>Owl= +I$4e2t?3l4IEl^9uRKbpO?9@FKWx_4+Z*?Rtyn35Mk7 +I=7j9r11N7S^6ix3ZNLw0TBNrnA6;7r}7u#f9)z|KjVThZYqgGC{CTu=bRZyH9S`X4XJn>Fn3~(vEj& +LVSVtkmIup-RYC9+WQ`puybUb`OwFYPCc@a;F3r3>)7>JQTMg>6LuEnsw_lNt}W9}iqG&e@P5et|}$a +^MG>2RJ}~Db1OA8!}`PCl-t8v}>m3T4}Ub@9mj|z%_+rxZ7ZEoDs{ML#*(u&9_5~DqBiYxKESj4mTOg=Wa`9szAMKx)}lv +`-l*j#i3y?0Khhbl?7xbDsdc6QJAGpzJBiMIGW`#e-^r2T-;Hh;=d+S{#*j +3I)267SvhMr@RQkJ547XS52-$ICe>QJeUthhO48i7V0%dy(GB*H(i?tWW0MqPD)U5K7}eL^XVX+RHA+U$Wien +x}#42j<+ZS)a-CTwqS>^=rPR3Y(X$J>v!#dP<_eWA`6r@k1N5GM&;`0;DxV+ub$7wO}+r*n96Er%7th +Ymg{)TiMYU{BBYW{46R-e!nDGl +oq&29pyV9|?MXb2oKEwqYSEM`=c4S9iyBN!K;m1Z6dy_z6T-Qa=<9M_f7o`m>ROPtq$qTr%KvDB^A{R +u(^!0Ty0U`q%ZMLXY$aqwu~24iaXx57!I#1nRO7%VEF6W)%!pDKn(Hv7%no$B5OD9o-=UMW3jSp5-6< +v-9T^tCljw^~2PNK?57H#NF7z_K}~$G#1Wx<(?RTh2F%p=SacciWeVL6uAbep(n8Pi?&iI^4QwpWGU? +0GIb0<-bMmiQ^U8f0}OCp5%bcCE{lRyt%M%cU}RKF9NMnd0$_z2StOvvMKQ&JmiNraQl_kRJhz<1a*m +a@HA>HhlQi^+Yz#zOTJZah@Ob%nJ|$g4g{6aTqv!aai&D<-qSwK$x8eOo&yVO@ZJ=W7q8A>ariypcbM +yXrK0ckHe}oZpbktW7FnjZ$&$)GOM%OnMGY@O6(&l00B0b|hkr6wNut6MQ>DV_Gj6KFnkWkjJk{NyL+HXD087Kj+YhKdZ8lHV5gqZWrlOK;G%DWE`!BkiXqnIm+ +alq;w8?I__`J9YNbif7Z(+|^>4t-qj+S?{?P;}c-KA%b+Zd?Qui3GJ@ly0WG{Gc9Yv-5H95!49n5)ca +M1c6?_;D?ZVzAB_9XK_iNylL2#JNwovpi7Gmwf}NZXTwQG?Y2aVBm1IA&SN@GB +A<8Ha**&a{6_UZG4VGyEK^#0%#OTx_$Nk!Z}lFQk?$b_-)qd&Cxy&ILJ3&PDs2civ96U6sc_HU-^`JjjY!#};jDgN`*X{DU}?(EoQQ!;j +1K_GLKLI*;;-biYPQiKGQK~MhhUnnbop*XM|zy79ld0Sh?x-#B_1Q0TV9`sIsdNatCvgOb`^7VM90W~ +{yyZe{g-ATGTJ=0T(Tk9KEMV{K5XSem40%39ns(9GEzv(3wMJ`fcCOgUfz|_dWmg?(AaeEHFC^O|<^O +rdY2fb`MZDEwMtX5GIcE?W69mM(_pc8kd_17cH(P=>8-S!CR0FdRnP}2pWaoJMi)S}5TctPe-R4~A^; +QFJ`TQ|3NU>-Afzjh}tb`)L*DLZJNFF=M5o|Z`r1Ms4liWCh?3OM-1%}HT72nu$M?ux7|T~!JqtKk1~ +>(=dCx0AEM_#_=9=hH}G3yW# +CLCUEM$H-x|3K#4INOZfoNuX$W)Nac3@*Ko>zHWV+jr=qh4K|UMAOfsORyXcc9qq)Y{a!C4`!U2ETLh +bdL8cZ&B_!0SsW8h1189zt0F5qPRYQuH@oP!X3iqofYryi +I#yyfE?8AWMZtbUxmi5KkN&52~DCXv)imf>Uj;SK&<$c`eqkzz{c#9|{}PxEZk(8PKCKDaq`ubalO5E +pW1uiVlut-88JbD-p#wi_{z4B@KKFrQ!?3h#Z +|^Y9hLBKz$BZ=;%a_U=HJ +t28_=SNXIx8c1AR4bQMISZ!`S!6NOsKFQ=)t?rrF#6c%Dl(&YHSQiPC%D#Sr&tKc}rb&ARACX8aAkq_ +RRf4XN$p#s5Sa4wAnUKq7|dzL#6$mNcoNsX#ibiq5rIy_Mi@GIvg%gBsn;M@>`m4466;(xcd*imot~k +)_Ho(f*whXA&6Y@5g3=$H$fSX1juXAab9mdZI@gsLn`|hgmmem$*K2>?WL*8sEUANp&`r&W=)utx;`o +TZmu&u};lUx?`LrHmUDrIYthqbYfJ|C@MV+&=J*29*-rT9CnkKMBuF7OgUR|ZklF$zMilDgXt=vZ<9; +XxF3vP(u`))2zA5IrSYq>vP&pz4+!5Zyksf!z?N;4%uJNLu*eKcAUkL_99~EhPa4VRD6J!vW>_osY +t?Cfz=bUSk5b%2Eo#^O;nBw)|ALbJ|<8VW?;>8CJy+5iH;^5PQna`$LfS5+Yrxu%^L8If#(}nBrb%l0 +~0%Q;r^!00K0`8GC*F)Gp-kbDvR2bScP(MfM#3clWpC#2#Jeb9aLHES*>a=ZW6L$u`!g>jX#d?bh5mg +PO_IEJG^^|Ieo>~SMZDp9uqFYn4^Gl`DXIZrsA+DP{*|L8Xs2F?FnA%w4kX0nu~WEN#`UVU~Is(M53J +dB`U8+l?K8wyk_`%#;<1;Z!F@ZC;T8V&CgFU{u`k?r*x+#cF8>Ffo^j&&(!2WE!VgGJnwhkSZaz&ql~ +0(<@KM)`IGYw9;R=^LX0o(#+c%AFb2%#vnG=(j8~~G8K)$#p=duk9iSPFjP4BfBX-}9F9Q7~hjcRS*G +XgzUqQ?68*GVP^iQC<1ja*dVsvdt6?8yVo-)jxFh%8IYJ4%FVRkvt>P9nK%AVj7DnVg0^HSRsHPFp;l +!aLZ0iTjbqnD7Wt8#Og>IR&d%>W!L0ufqD;yT7R9`=7pSs+0gZUCNTBeE8PVK5-Rp8ZOz_=x74f~H~| +*IXN98q_-2r6(FqK-TfGTX05OiQ~f$ +Kmt)VYz>HPfSL6$n9K#7loTF$4wDT%k@O1|xPwi|2fvq?y`8J(~mviP7umUG#!8FB~9c#arf?o6Kw4a +Y{Dqcy!>-x1uk8BOTLJR8o@e?Z#0Zw}(Np^R)H|{47x3+h8;rmW|W5IRW;bHY5Dj&AV#(abA5O;U&qm +BFghzf1dBY3My?U;(b?!p&V&8jMiM-e!qa*iO3qJja$Oje-j*_^DmQTfinVrb$T^q|xowbDHw_q&#Ev|}v;}N{Ov($i)7ebY7Fk2qNccE0KQs@Rlk1CL%NA@=5ThJvHaSDt9~6Dfln{a+)2x&Yhla7d`?}jpx?A2&(>*?fE{7-MfeKmfFDO1Iql?Ma#6l) +HxC}Z5WgnXb_A&}%K>kBBbDQm>Em*hvk`}=(RkubnUtNx7>AN4ejc{oSO2|lsJ6l +lqKO1viVYiMcM6^M=D}UR(V{I|^&tyq&B%_Qs+ccvCoPspj7)v&h7cLC#@OGP72Vk*e`w?58pO48*4v +?bKfCHA-9ZD%;T|e0(Azp@the068cjpvtfZreXeNi5{ho8jz&9|?$6@b3BH%n7Z(rz#zbfBpcGf6bs) +DV9{s72FF{BU%(@F^`w+Eg`YrRex~&Bmlyiz73ksxa{7Timm_CZx|n>-1W(be(uAv#{9M)Nna^nf)%_ +6teLu%^e`QmG|Pkcc^)KVdpQ0tQ&zZ6jYA~cC^SMK=eO$>1=Z_PU9nJo=31O`-a-DRSxguM|dr7Dwat +c@h++>v?`qc)u8be@a^};AZePlxE~+daA+UlcOal_&WghFddzvAQdD6gaTLc7-#Ff~gtRRrY$Co5Fv2 +CUaX>H{Y(yrjFGW20S@yE8ZhaA-vBTYUG4U +6KB=l80^+IrW4vQDnxKPT!zG02+nPM;OSCWfx$*@HD``#wJt*O>^(rD8#L!z&aSZ4f*qkzlb%adeA6}Z{jg9<_txYPzlY|a2$AkqYVI!=q`O{Mb$x0PvD#s*=vSsU!Vfjbl!x$!Ltxg{lLtxWXJ@^S|-ubn2v{qpw6%=UX4w +bM|J&XRVf}<5G-9goZPz#B7sgW>ux#fDx+**`zw_q!1Gj6F9SQI! +MOXBSUVVlTsxOo@Z`o9X*01A;j>Xs1F+xq4V^9%#>sJ>JNg3E7Oz!02Cp5WWffz^h<_AZT@apEtPaI2 +O*h4W28@Yzz?_{L8;vYKoV9{BHxvB`pdUrFMR+VI$YnQ!7}^u_{6Q;|@uXG_nyS_}3ooF-YF$oci==R +hRMYjKq>4Rju20n?eg4@xe9Fy7XD<9eTbRO@`x7(Zwd}R%4VD2H^csjdIuE^~{eZ=um|wc6Eh5GDUg)A))jF)$&rl*Vtv>J0imgI}pI@V?!}cbKr(Y0wW|alBhr{2!Pn2O+6=SsIz*Tu+kf;ZfCtr*xQfHu`)#b+Ll0@u-4{Gn+d}Wu0=FfA;woUsj4G)8;R(WO~vC$Y +x +ZzS>qR52mE|XIuj5CWpAQ0mDpxZ@|*ft%UCz<0EED+m{eKQhMDcuL+X;Nz@h|)V7LDfv=m*eBtO)(pU +4;hNQJ~}?GN5GzE@5(_RA6H+$t2#(Efb(vnjnenUc^?mCyt;={pHwxEP^W_H?{43ARZT`?)GU*CGaC{ +?-_b@;tPp4Ylm4JTyNq>o+?~O(v#_h{L08uwEbpqD16p&ef4MaFQR&I}Ed}3Xg$ntar%8{HjWK~&D&bc+o$x|a^r_yho^T}YB2M~-w>@Gzjo%QsPjLOX9`zQILSwjCcKc-;QwGi$2 +V-~kXgsKm3W5R0E|8E3av$SGOyIkRY4-EHKULOCq6A8fYggAq1oR{X)@A9Lp+ptgw10Yu{Q-i&@jpfs +03`P<;{jrg-(fk57!=&|#e+;JpcT*WjV%al@WMMiydWKUe536f<4=qrku%2xub5$G5em@@mlRV41WLY +PZbeWh#&-6KY>#kB$}|Z{L#B!pT+ym0jr;)8@F>R{0$v|mzC01U-8oKxX)6^g!ic724o)yb;^Ty&ZP) +cZ_9%6xXwUBLCeL<@ERjYg%*%Oel{Wdg +vy?SE=hYJ%Nig$t+S8A>a+2MInRF}{WoP0n@^L+{Tu~dSW;Ds*%ga>E|hc9ggRVO3U1`J7olkJ2%>PwVrGUOO$$^&+jA5Jv5+E-TeYBA+7`aK +|shjqCMoPsV&>CWWI-j&Q`q3#nD7gG~?BHtKM#Dr87F|ln1;Z)D!8IN*bd}y8$sGkSRh=N6yYYW7%`X +n+Stv6M0kbWfB%SqU`nrfxoFj6a_b>+`Maw&Y<8Qb`>{G?z8Z9^|aU{QleHB;T1f=U|1j6~yInBtu(sgAj^S#b68P +7W^$R|Rv;J(5MLQP>{bO(D%ey;rJO9Gl`8~VyVNdolj653Ej#`nnUjfZ{rn)Q +hWQ%H7fj(@67U)Pv$1pePm43o3@M3vqs|fFNANG6!>C{EG&kL?Dm1gHdxjAHA=gH|lfmCLUn#c+D>0 +;qWteg_jZ-rlqZDg0l3E4+R_gI0WYD7m;;^Dsk65dCw@HzzM^lgZcoSf!+Pzn47|~<h`{370 +o@GFjwzYOBzzkHa4#T>VLi>-a(Y438xbl-4${QdxoMCY0bBBgZyEW6L#Gv6mfg^l8>6PCG-8Kn{ZP8G +TKehsLm50araE`kt)Fc+|k*cbb4W#Hg_qtbtm6J)s7GaZss=1DSXAf1wiq|j&GgQ_cb@f>X7nA>;!<` +vkesAr>1X_XTUZ2Wu@8N{bF(*hi>~!!R4X@mAnATFB`tICtH_?@z^lu?t$Q)VnxW39MS10QB=SmC>Y!c|?-!LB5q8;kot6p>3)Cn$eaoeog6^uv9I< +~0l#kf(%xfi*wh*6?NkrBsR~=?>psr&U7=lW_K^l2lMU*m)x@Oy +FmQaE2{NPhRnNEetcucmYj=lMNo~-7lO+|ze+9k9E=CEOrPQtKfC7T|J(3!N^4jOV`M+13%`vc#EjuE +R9h&IGN>8!HQkl|CPje@^!m5aAwTi5(A;@M^{`e90eF}{lv8Kxjl@cjF7M)+LAD@@5A;n!jh`g64zFUcjLEgc{)pQ4IhXqKPLD#S0=?nAD+F(Zln5$0KK +44!=C0^X*gw5A7e2;F43RGvjB-8HQ+@yvVA{G^p89r>8P1ZqFQM+cd)cjn$2-P%f9vu7KXx!EHdVzC> +5)E4FY*kt@{s1!c0&$qp*ccPlUMA^ONmAQKtM?J|Xq<4cae=ygV>5=ahklioSly;06Ur;_Y!tUF`c=4_ty% +|H=OW~Kpq|svnd|+)JJ&6o&3#Q3O7~cRBWfowsJ(0^$=^7QQqc-gwQNx;70J(#wNpuSAqpV1LF>36ov +C66KKyxG@?H3zf)$|bCa!{V+{%29<~w}A6PMGF49sCj5Oz6Ga85@71%#z7d;ao97z@wxG1#~FiHxdljG{aah5(#rX^(xzEj*u*!O4%>c&h4RXP}uhds3$+5io7o(d9C%H6rnJob# +hW9suf4z10y|vI}%bE5w#VsBPmaNX3^9rv~DRD0P)JMvHuz-9WKN_KDj!9xsA6=D=IH_7U}{4#lS?`> +d~0u_c5TX`u|7iz|b9d02bXNZzt09KTV}I(&B&(W!^o8cv18Kciglsny>Y7zK}aQ<$xDB#Y6ik-;RXh +)Gh-)+uJ!qReZc7lM+xo_(^Nl&mPWfusH$8bC)_-d4U^H(l2-ivZC%CMuj%z}Vv9FOamaq5?{44(DNq +)hzg8^(4JjHt=n6?yM=6mYve+s2fp3_9)!0RvlCO3awoz;4_|mxtMn8Bhs;N#OFB1P(mZ6L|HhGPm?c +{di~SWeiz*@P9$`oJEW~go5uy=Qey7t;~t09iIYd-irk^eO&}KE{nY~AOe|IT4aP8@?B@9xy$ +FbwWX}v}phohp^E-Ep^1K@(dEpGwv?QRR|0$A2pJlD2DJt^>NEcfsm7B)$vUEOXr*pP|1uv3p3U$H*;UMl6h +FN%yIektB1ixFItwujQG|tP0%&sm)k0!X##!;(Du7vt$zojk9W#_z0LnP%0E7b541VGQF5sGUn9^X<> +R-PqOpPemJJ<3y$=9ET$F1tb3l0`uUI?fyZdIK=z#6;BYcUyXH%@mJq}(jt@y&vYD8*iZKP9L)$bXPv +gv8EJGd=)xxTU;2j{I^FbR>0ugJ;kl$3e@5>&adTMWALc-iIH_br2w>e{wR8H+R@=+y +@OVH-AosQ<`ZpY*mZ_o#{1uvcsur(LF@u`ZaVAMDXa}3$3RorOImVtDG+a=4CVCnlhixY)`C3{28POd +#`x9N06=2GQm6q3CD3`0r=}`=5q8b6o<4J9+J0|f`nE!c-mhA7yAbvW>K;FZNXFpKnmHkEz4?k@r)itPo2!y +?Q7ruOUbfjz|!yX*E8uyP}4Ob!P9+am^*ucX0g@3l&M}brHZP?or(q=2M`Wwmd`A{^>y>TWfFvfu=3i +7B>e?IlYlH`uXPdeW`tT}*6;9!jpK--6%5>MgV8#AHEb$lT6p`5NXwX^9ZQI(okJxD|--Wdqir2pj%G +k(B)w9*#eRLdp3PijP<)~LoBkrXa1Y3q@WKPHJlh1O(lNOfBH6Izs)F|Gv7gbb*)q=tEe8cDr=xcR_& +x#KY`hG!r)(NC0`40CYG(334V+F4|}lNX9Pl46Q)Z-Hol!fu!?L~Y(VU{f(cm$XVMWJj!R=tz*ji +$+X*`3cr%E**Uv+Br4C|519`SmUv>GS)mA*d=aP=B*nUZ~KDO>Jo}*e62W`~}`* +1_|Y_V>21+*=1Vzo%A;U=tDM$>FD67!Q<8%|Re@{`&|odcSoK`;KkUst<~@K?Si=!C!0p16(X@9r}H$ +xD+;6PZtp%tM!_u|*yD+Zw8B%neJT1N`{cQWoTEtef5X#(_#k~?^9d;hW6=- +?UyurL;KW{d%cO`^Hcaly1gpkKWuXra$+rMMC9&_w{%3R?$5MJxsRM$VS8(*_p||mD<0aCg)g!TO9OG +;ousQEuC%_cZs!N{8`Zoj{!tH1U6F1p=Fb4Kj3NRw@3jJ&(gGUnzHln7K!hqdiU1pe-bN&#sjDon(s9 +EmjBfR&;a3`z*0#s+HQ;n}`3bydJ%tzJ7i59)49Sb^P=CHNAOv^!}ZWb_ +0jaU5ZaztyBvEdb}7Lz4097RkAWg-Yh>`(w9n=TCA$RRIAJS!a-ZAmBoFLpdPj_KYu)=l+ls*$qf(F8 +v|>*WSDr@6CfW$OpJS3VZ@y$wd$O@9`SiljY<6=`A5CF8C^_ej47T}zBBuBdkEv3}(i#S5~a93%4$;>Sc4uO}eZHqNwCxw$Eiz{YoUC0{hrHK6tM*;WjJ)QiYy;>Z +PEwXPD9cXciYMF2qnXn<8DjTr_3uGo1zNzyuDX))69I-`X!y4Cmq{*h1L@gus- +Srql?uw1<5azSbN>Vo-rfTWPyC8c$C8OO)k}keBG|@A~SR=Yb*c6iFhC=E_R@^h%Ip#jMFNTr4tlgc5 +nUZqb4~pL|h5!<~gv1+6@8PGpUwQmbp5Njz+f$BsH(s=uPQ_-|G31gU +w;?S0aHg(=V;@oLldBHfPFxNj#xXKNMwH|}GXBxWeJ3b~_}m%^7P>Zoa~W8&?zoDOxwMk +zRp24m1sj|@h_(ANBMt1xln`LvsL==?64AoVG>cW2z|p+Y}-S5JN|iYJoL``w+5`d8lD-)e&Hs +1r!y3|ISWf8MzA=cCX&BSc%p4_0-P#e>6o}#aKMqEM986-;|J~U-7cdDU3@@`(zz_zyNNfh{K2Uyonn +vrSmH7+5Lus%pg1`;!N7+m@_1Z#g?(`>&Qh(7850m9L(82Wr#%}&OL&%+8+aDCv{! +)r+%wLi~p`rU-16{7G#Fk(3iR)nDz(iQg6pJi7>rscnQjpKDi&28Z*wLXGYXKW5v7Ie&P_#!cRuDYDt +g}d(+cB0XQvJhbHV$31BmZ<0y3f6n7_}=uZu7ilke8yw2tlnDCKxqq5&ZW8&sh{r19j>atZdn#1tZa3 +@Yyym;Le_gY7)PVnDXGmxlpZunY?OT5CAe&$C!*-=dK6)GFJJ_k3X#w0m}5ym+|%v{m|i`IY&qLb +}R0{K}V1`!uX^jMHgyf`dht>e1OL9S8LVM@E~J_*?|PP2*gDYlVa8HwDGRU$dW8H-Zmk9=}T7HRC{JW +NOFS>~aJ5-3_dl~p&`PGP*zeLZu9L_VZtXBDe~BQ~f;fs&u20wFaH$Evq=e*}oY@pzC?$UwSv>2u0>G +I!+3F`L+7BD)+rn=sN`&8KLh#+I^QQABJQJQ*EvOlJZ#K4n`uwA?kd)yzlItMe@HvWvLxj?pQl*d~T9 +L>rLfV}CJNKk5=)NFapbmf5ZS;xXd8+Ng>$cCW9iC*Q)8-3?vd`*Gj?c<8nA@L?@&oHV*#Qx6~Nwz$} +CEf5P%PSSSxU?Y6sntf!yJ@z^PTX$G{*SHKX!YJv@*;yW7K|ZK>@&rvK;i>(wogAu6xA|=7({f)EL{@ +&KajKNx2nd*uvo0mQuJka_#F_?Ru0>IwUaIwQ?GT_Q>%TleO+_#)V +QZ6AB%s!8A#;xZ$NoNTsXL?WVDlkg{^IEB{d8Ey++-86$#l@gQz_mtS{Tx7irX=Y||8?ya?`z0f%c1&jiv+VYIo1G?wc1`Hsw=35~&lFKd6hO%-Ho!9Y!5LaN0f^BaNVl@r^{0Nm +Nqt+fV%NH`_-A1(THASzHi5i#&Y$!0Vi)19SNj=%g3BFr$b41Tc|6^V3VCcehA-H9slE(Mm{IGBowFl +}>=A?$x;Bav5mRi1PVKd_3Qe`X6P=q1l=~fk_zS8hxMpyW&trn8$~zS7PYhS{=QZ15eQL?1@L_y(~^H +3l-;Uue_dNkTw%UuU5CIhgEte?%Mvd$9vDv53(lNrr>-k$))8lE}es&fynjKi@m*k-Zje0#1~C5x;8V=U!3%jYm^L%Cj8s`4FCB8znvz{hH)Z7g$giZwsS(w}4LTj}WXxrtE--ygXkjGmYMg7~wW% +800n!BuAISrrq6q3`z|@QJg_r|Bf&1mLw_`oP_vfw%``X*fwnCpjlmNYcqTubA+SaK`U!{WYn)t|BBB +f3YtN&ZaV}{-_v?o`oU>b<1E@f&Zw$u5EDz{082ztT2OB3M&SW(ET|Q5jTRo3)Yz8dK$;CzX- +{)V>K81wwXznn=PFW%dxpX!Co2mi_>fc;6oG{Jxozjtdm_j+!{Dwr6dIKm4!C!9W}HUqj(% +BqFkE_OIn>^Z@GrVVgAY{57Kwz$}>94w)a*;3;azP+*{e9MYUE4n}5E+gusD@kOnTo6Fb3$`muj^}yK +(#qjsVPOHF383Q}8w#170R;5c0*y)_(uoj!sf4<_MyuPra<_8X5Nic| +`}7(-$V&zb}tApTt@X$k5R{45mqz@Rvody8Gk*~D=%yW!D)C5Xczc(Q5Do= +BOI8N<0)w#OPyPmG2iPQf`CXF7~z!jK0Wg)(;ipQxz`>?Uy0>H%w|#NZg*WXu(42oCl{t~9)Xf5|a-E ++m;24@}6Hs5h4i)m$jJNt6bfLOS~W?NKN$UTYh~tZ!-{Z9NDnD;pgH>cADk1v6<;BJms1<}EDj)723D +K%+fp+PmMPgqZaB_tZr +`eJHPQo{^jyvS+*>27+-{+msKJ?@i1;hvrCq2ziAY +;Ha4W_!`5U{}gyl)xg70GaRn=)JV^fe3(dok$D1<;m??T6?_pR5Q1kL=3Ij<7ks0v@|E(Cm;qZY57hHFc{^@g5_kvnpaz|ZEP83=e_D`xVmOS4l6bDFv3i1l;XH^HY}sB>i<_BwY*<>e1gXt@fP +QDa^uEa4Ll+Z%fl?{B=D}7Tvpar*5|?8iyo?&4|(XZVipnekyM*PWYmNF1rCg0*@VN>%!FI`5Qaf*pT +G_whhqaj(#`WESKv#<{3f1du$4Z_ID=6_m$^FoLyzQOJgb-wmvdfB>Q|i?XjPKxR>y+n)ite-seX0*v +oSC8=6I1L+RNwH%jMYnCU~}u9R^{m8+wK`Z3tmcHVjZ{J=C?XUiI|qhPu@&Rd3S-mZjQSB#@fU=gVR; +^}b$9(qSmvT*_7lT`v`9d8g>Aqa^&2G-33dU6HtqAog!`b4Uc&gSVx1+!hgbFq|A;i$?rvPZ}p$MLI^ +5`MGg&snSp?-H}KWSuJHsOIn%nr|DEW$@Y=NX{#t)Kp-tK9Y#ICp5^JFt(rU`iW>l{>R>pAR#phwe+<}&nt#q2~Yi(afe +koRp`XLuMj_~(wyyI>Kj;$PKCEY55{(s4daej$6m*jk4*Qzztpcm))YVK`LkN9BIE{3~8Bai6rBgHUPtb-zS43QcL%fa`No}JJGNu8eVS*MWqYVCNIehNEtrkLA{f?+W5#Ga +7Ct0a^z(vDd9aL`8~<}(*wtCNG6BqKTS9(e5MQpxN;z56 +Nj|Xav{pG|(lXLHU+WDBCD1snUBTavY +US0(Zt1|AUdes5_E={Uw#dZhnqX5eb4rJY%n*nF*citwTu=LVc<6GXecVW6`ZCs~K!rPC$kz?Lno+^g +`Ve2w*Ei%a?u!Dv?39YzFyW~(*I9Blb#Y=K9N5@Pu;k;7+!Sy*WlYCXs+OJe`S%0B`*FLFU*l{s>Shg +IIi8Jmm|b>3pA!=?n`KHhx&uv_6fhc0az(DF^msO=(wJEl&PC?}$H=F$L}RnY>9Ef>I-6=Tt}#LLtc8 +gLEOE$FvdGM-75}NN9ZWUQaArP``f*f=&<+p9F(L?N3DYi5!A$k +d}Zc6O8GX+_S%huhl!5GIEr7emgK5aLDR@Z%M=Kn8;9SHYV+YSCo{EJ4>ReYdyN>5VTH&fTq?>s8LR3 +z|ceuxj9A$x=3)zFLBva}k(Ct(e4qx(8p>vJ~$!#7;#i3$w?1;E<0Ivd8@TKLyCe3ljnk!!U#|W`GUJ +l60Ggk7eH(n}xv=u~gI1SG9uxehg=SITh+CeWIned}3hW;rt~q-H*3W+uGV{8nA!vwe53nY@gfn&UF&n@KQ>knwX=Tq7d-qH?I^1moq8Icqa8g)HW3vzl4Hd=h&5GoU +0=Ht83dETkL<270sJ44C}RCLK +4xVHZCTU~PN;yb_>47!IGhE@a!S_9z+^pPpGvP+X}6V)nv}I{s*)s`^wjsA1{53*CL=1@#`;lzG7dQp +SVH**v8046THwUJ0*}V;U4Hu%mPGJy`wG64$s`bKW0X`>XLkSic)QozW+q--di!*_Qmt+?abryY&S$J +cySO2RWU-#?Mcr;B|H52V4B~qj0hrsVzmNH4hhrn1z~NzWzbKMy7M&tG;sAB9H=JSNTzFin=zp_TW-A +e+sJ3cYHn3YPxQ6e*=|ldhSbm6)wkQF|H6Kr(hq(MxZkGD~(5dcnsXfS<2aFT#tW0K1w~bw4^i72P^5 +m0>lqnmB6zTEdSCJ%)Tv?pRCxL +;zAd1?dp^dgsWbNf5jf0a&I4h0>T6FmrP<2h?rrERbqZ6^%`{i*?W!snk-dEJJoZro8@~PKaw@TnNU@szZ +NvSN1;N?$bvWqv3VJa6{N!~!3Q2(!p&S@D0vMEas@(qwGyjVc8a)PH|iH%5Fs{YLb1mSG3G=##@cKK4 +M*5o+DjN9ZBBiHHIV{_fwuso-y02lzh2vJxG8QVJ(rwEDvlSB#nYqku22oBu`H|cOrjdROhk3@QbPfU +hDqDTws)k2@kp#}uo|19CFY79j03V4 +`eUm%j@F(R*Qp2~%jnf?~;h|3efDj(&LZ4{)Q_L +`1eORroR?80j-h^D*SQ7A(PRA`b7EHpi0rilnf&+5im9ZnD{&6^pFtQl1#Jz+S41*+MVfS0NO1sAlSR +9s}Zw14EzTINOZ_rOhf)n@tMbBznagtv}s0ou3)f`$f_F0GWmrACpQ>2%=T3+I=LMx#;>8q=lq)Y4H^ +kT5dIJsw(0_YLuoNTo`PL6eT>M<({E`#!GP$OKHvR{>2gH7?Z^eVCrlpM2BhcXBw`SAcUsFgrHSS{BT +EtMXbWrHahPP9@1ZteF6{fdh6s>eP>;f44mKyS)Wn-uxorcTsl)jJTCqr{uei(-UUrq;H0m@9S^@<=B +=wULcanwV4SWl7ekMQC4}S{OR4P0SA#+Ywt$oCUcE=xiD{i)Qf<37?r{ph%u7!XE%$j_t~}=d6@8RZ9 +)?Di12XZA1wY7XvQhh=@uLKOnMqYa-x4A!pHq-27Fbczav!ch!S;%86c4M3dQr0$!lr`uID5-U*a +cZ5~hA63B;3aKQvdZhM%R1}4&U!nU*bPcIc>Rq{QGa8rEWb9@*S7lFp;tB~(hD3&b6u1)&r?Zj-G;Qq +pSIUT{kAA+%91vfgp&QvDwT93mJWZb6KYtDZreQ>!IoNU)}9mf=a!sxF1hQhyG#nm3YnPzsy;9N^^ov +yb%eOARjFi+Nl-$Tg*`MYO`?7lQIK^2HaB2AycP7{jmpkz(I_|y`T?1*w}NqSkNw51Q7aRTqzQ+RGDx +(Ni7!lVI7{PQ=Z$xxnb&xVG{N0G)QNXqhQKu@UPR88jU3Uu^EDRll9!_=;Pj;C39_(fl`N_~udoL#>Y +esxAO;6Manx#3{A+7OV(kJi3AWB-NwBraOWK=F4rd0XnRP{x;*oTNt!gZ9qjFFilo$ecdQ0G9MI&qfI +LK0x6eUo1?tv|=)^K18*8*-Ngliot*=Q!(XlE2OXCG^+_S^)W=OgX;8lNlJw~$IiH&!=!oz0XT@mk0h +P7h`!k!sIP;>H%C-$<#)weUWKObkXdcba-*ZZfubD@cJ4E`+247igAB*}+gT@cZI +TrCK7pfI;{VA`sr6c{GJsCA4X;X^86RipZ@AQ&!0a3CHwE`um0@$PwfD?%`*eUXFquMOZMMCJ^PnupF +R7wXN{qlpq|MG)>gP(u;{b`^{Xy=0d{y%>C!JmKm{$G6gzP&k9YBod5{_hWd^xr@K^nZT%Gb=3Xsryp6tz|1rYkSBlh;^XhVc=GcXPd<6^Mr`S8V)pT79`J64!#;B+kue@Jj?F7$2 +7eEb&1>UO{LU=-nrJHs-@ESYNSP#fkcE|PwP*oUJ_yEoo0J>}?Y#+V8@3*7i$o +HY=fz3F5&TdQs=;EKzzR7U1@L9wrqZZ?|#WG4Wdyj$wIf0BZjG=`Y)jc@W!dMc*0U>Ospx-tknMZv%0 +OiNJ@f{#d2F`XDQZGY3MAHS{HNusqUO0ltUow1Hu>>y40Hp4D9&Ftf-J%7}A_OVLQ?+A$6kaU3MSU*~ +Sf&#eJQr?@;{xvZNvClsxOlcgpy~9#-`<6YZ?8vjJT4)vkZ&kxw%}GGhz}&Fs~1@Th74U@gI^HSvXP* +gQQUzEIDANw-!e6%tU3}X=ZA*WqrCCWY*R-;)HhU5{*gK-wBK4$r0AC +3&#mS*yKmFrYvW{4=(c6jX1sbIv2g%HyDj$j_ymPfCmav96%`~*)lnTm;Lp3jCEJpD9V|+#}ngF4{Ft%}885U{K)~2hY?x2Yi1vrlDRUu*3l#zCp +getFGnpp#lcP*2MiJY-8 +=bA7HTGJ(z9DLT{wZ)|9HZLzLXkv%bW!8oEx&9;ht+e$*wGeK%Ts;7a!p_2`afCTQH~0JHoctm_+-Ob +uFg4(8+YjAB9Zeps(&V8`PQSE*LXeKLrtQP8X|zE`ct! +DEsGT;sgp`7AL%`;k*VBS#_LQa-*5NW1HB7n1%-Wm5+-F2~vVmh7hF9K^$U&npwqW7fj!NggMV2i~@A +l6UP34%aaH{7t#ntsrvfE$=DGjL5a3RFfhO}fZ7G`8f#K0`vw|4g!V+7$Dzq$zR(b8HngQKfEbO24|c +j;%HER{0p4=y4kuq?-mUFPZDI+A8ztb{8x|A&=(+8H{Eg8W{z|w(PGV +&Hh(&A99bI+uY+f}hY3Ws>7Rf0#JeLjYt?bH*3@dS5w29q%e!l09|bYuuH*TWp(!sXBa4rLV_na7i1H +;iQ_G!doC&Wf2MywBq}!`$^Ac;A~#JrP0Lq2>{oWB`KZi6dx5F&3N&Kf|oPHKp=K)Q8QWGOr%1-IT$7 +Fnqg?Y7lUhY{6w;ZrL(Zh{>d9ZgOmchj%a4yW2#63G!BzojU!&WW1a(0Y;9z1Lihd#VuYr5v14qa;P +aBEK(p+dy<_jl}v+8uk!GAzdn#9hZI0ceA%-17pOWJ^zk&#XMl +vvf-bkac6GYt!(?w=6#&BXrglE3#V5U}1&x43*N_;OGq7}-AcFccCU56AXZHhjRK! +}f&VQD198BY`n1#v79^Gx3a(QvcW`LUyxkvFSu*KH&g!pIf-et#9EJ(^~Tk&GDp(>mquh^f?ozYnKT4 +;B+%b+vzH;nr~{(S@=*KAvF=d1vg*X +y>W`EgYu}*#2qBZ$1<;X>@NW*8*c@T_n3Ok12?7iTm8D7bKgN*t@f1ph*C`pfA89gp|6m3CYKDen9sP +=&T%h_m2w_`BsJO+iA_j%B27Iq0-WN8W`6ld*t90rzC36cx22-A{~ww(Cd0VEWEso@e^&M81Qz(x)?` +AkbpW9wDEWylZFY)cUwRg>`r_#(CC!#r}ye5zI(uH}?AJzxtkYDtvt`I9K@wigz_@kZUWyW|vnzsC$2 +-!JgXJ`6gOSiFP?e_nE6*N++gvAkAGHTcy`u#077Yjvn0a++ZvF-22W0#8iz^Lyf@)}TA-_2H5zf60= +cv+NYSf^e9ng?#G%w0^(SO+e-;Cg=p%gs&=@sUx5?F6e;vm;iGUOU0!krf${|aDMQvj%4R7`H_&3oeX +|tC7m6*KO|Z_j=(`+?+;%W6ly}jFY5N&c+*@lt~Ch;q7-7WY*Lj(1ukQ~fm%t@7Ic}PnZw`|_L^ZEPS +1Jv6uP4VLxg4^ON!O{SzkRZI?x|%ZA>&ui=7QeV?5!;Yr~)w@)cs)S)!HS-rYU#+jrl7`>q}#xWz@?! +SG-X8w(42*I&QByI-J<9rm123+WtT^#+Pmg^nhK=)tTZU$mVgLiA1JZ5_m{u1VC +Px`^bmR4ueIY%0P4yM+)SCaGpXvklT@1#C|ZtUrCi)=F>1p=Of_rZ45Fh_|BzmWDTpwO!!Ulp3VLzm2UxH49jWgMa +@}csFR4nb_9)JPW99>Ou`E@rveLO5hGHs}0`(Px#jITbOf4&j`F*!WD`dyrV6o+TOi@%T0K8(ciZx@r}%S&-S0b=9Ri<9v +&q>j&yPCkDapZy}<1LoQJl{gunj<101)j1WZh>edgk=*I=3IyoGlj>Lz<)5Bkm3F{o_U=0zFc@$qh9b+Pvc?kcGuEytQSec{qv +#SZbj-alSs{;AU`0{up4kzPF6wAlS`6*Tt1q(RmLJ}LQBliEWoR*py@?okL+$A5O;QF2^hTN%o_(-6%12Jg0Jn`dHCH=f4e(gB9{b> +Mb_mB@3&UkO^H$T{~p#~t0 c{Aypn=R#0;{Nu^Nm;45Y`aGKamu!Yz=7r*ZT3E<4uB!g%ce|>!|4i +3Z~Wbb!p?_>7c*8F#S*W#xGVeOB^+aZW-CIIx^j(|UOR?)3P592YtG=gS#1yHJ8PThLj}j2h{`L_(X4gyOZx&XQFX7Km8CS;JwGC?7q!%)~iZ=M4I!<0OfbD# +{Cx&?}HoC2_5cnsQ=VKfK+D)(`tNTD}a$Z@QhlWvb+l*k6Rf)Xu@PX`hrXrRQM26UMGD6Vs4uSy1j-9 +7%8vBxl$69f)cy^j;&3ZX0%J7`W7?hO1MC4j+XKshT4bu@8@>FyY2K`ojnq+c=f8u($8b57j9Rv%>QB{kb)n}cXDQ${xu2D>C +jNVG8xs)#=(-mNUrfG)K3rGW{-ULI!Hh>?T?uW@)Ut)W30#$zx +CjQ4{=gv7w9nHiQ@vqCx17gnJ(ceu|x*5;cO|LKFcE!I+y@^J;$g-qv +aiDDmHCtdP~4Iq(-`A859aUg;>!)WqT@>xkihiln_P(k$gBn(>IlxLziqBM49!WT~Mi3!tA$*T*p7gpdI3mMLK?s~riCu6QDGVq>UjTZN1QhfJ+{PLtk=N^LLn|8V6Wb +b^0kt=Nz!WTpS|6tuukDSoD0u7kh$Z*7Nk_H4{&tB_ZPZyiUE~2s*SQ-=^FdH8jNo6>8?<2RqMFsvq! +sMOPNuEa>nl`1bV;?&?^;(@_@7Q^rXf`;(0*uPv~!z%QQ=cAgU3?C9E)sq%AUn63!jn0`Y{NENXHv1g{R2EZ2TcvkttE+0zHL+CMc#t|v*Sm9Xdo-Y(JpN6XMwaAYtpU#w +Be<_sO`|DA(k^E!z5tk%YMvVUig`@4;2=$K-ARH|;Mgn#cP}Y9@B&#m@_qZQ04^o=32(nQrT|O^RVC+ +cTEx*ETB9HnTQ>xmhEXCYPA&DyV^VLKESo>`Ai>p?z!WTsr0CM5Z!+7%9oo6P2aFv0trfSr)NFRGUIO +2dS{mnxr-mC;8zh&an{v}4t*o<`NH>$!rikk3@bi^+3t)DwUCT#saP^+7+5o75?KnlOaL^uY=uv_7id~;vaaIG~YQs$AL@bC5Z9=B +4s#g#h(QsRF>fNL^!@ob4UkoLK!8Ddg`S$bHRT!2v%QekmhR#?I2p_84u1BZ#-5r8BN>=r-0*!PHIqI +)Itt?Sr^X2UG|Gf0pL#u39x?e!AGDtE&u1#ynO6OpSI(dPtzX!IM0-PAZlGeXi5|yB3>r3C)3W#CvJ2 +r*NFeI0OE%(QqpL{EhLM%(}5Z|zNH?CGKe_gcT@LsuZ?W6@Y98v&O`a={F%oYp)Z4_WZ_^sK5$kCw%P +J(t*`^*b1IV$fk(4L>h_1KeRQd>EOxm{q +|)?q<=}H0oIhPSw-Ngzc*bA&u`#I&JSyQdI3=~gJAS@ctek8P9Y1-k`g;|iwdy}o&HqE!T*I~%H_x&q +gdFi*ApdX^t~|b=gMk;!w#)1_6jIhNKGu-iMLy3Kp@ZwTnZm^cdS5}XQl!`gNGFlfygpr>o?H}fgud* +yHD*2xpajivrRJQ|hA9_r5ITzhVjx^sMHEW1PsgXn?<*+?WE0RDLeNLv)+mjuNkodl*3eqy&?cegS(V +;tGnr_X;A`kcanwN?!rU(E6{Ix<;97E(@Kq&+2og{mA(Z^5tE-DkncTApG*d{mDZjSSIQnH=MQ9|fqi +{*1iYiW9+B!PkMd00W7+^du`Kb-z&s^pN^I!DP29n5SqHG)=TVc}JyOUX3@=ouU-HaOk$?#M?ekZ%5A +QQo^)MF2r;|FdSlW+b|JgGce<@KTCCbyErM|f+JF<|;grCx#^iLcG{QoU;<)#NI0y#&cFO77TBn*3GZ +`UqwL#ba0eqtP+77c09XaP_0H;I$76I(OQjma-x%ubM`g*sI~lXBz6E&~DUgK)^v5C*C}cmB-KSjxgm +&r!;2x4ZaM_{vs9&-1Apz?pa1Uiz8)>AB!hNAnxeTH0lC|IncdKV`V+3{Qf +?!Sd#(OU#+@zXLnWm?v~wJd(V>){1|urKw3{o(400cY1lOgg~9PaA4!R`IK3n_geJ-F-6Z>n_2%_(nZ +(mLD+8a}t76PdZKL(L2$Q8}gg~62ItYI03xgDXTI=S1J3X0P9F-lNB2;#D*SMS0_r*gH`SC+ab6UYH@ +IIO}<{YdW&C}+5M2Y}k+Qf?HePBZf+ +f%bXkVuH;!SC<%Lz$Som^F=Ac9o$aEOO6H~%~f@ru&bx-?C=`jL3NU@VReJyPCH+es~H;%jTLrUPUDp +|$a2gkx6LddE!OBY$!Y_}^}n4;0h`YQ(uiO}+i+ClSnm|f=?fSYWZR=Nvwx<~!40=T1{fGfCGPE($bZ +B^v?Zzu|5V%*#l0nN9zCbzJleYKVrs1L>YJlE%P>!$j+;kY7OD`RSFDr84sAy6p@8oOlXb*yMaT%(L7 +vVVsSid$$!bg7tY|Dx#mp-`?~_nz$JiE%NWtS`^IoXV`^grM1R|M*v9~okK)8#oTpOXAx_UZxf?GF(` +?byey4ub6b|Te2zqf~Li2}-Ax)-y(-|ppWr=xFP(+95!i-*k+Tm|CoF|?Vg<-0ZL%XTIy9CU(WSnaciqDsI=Q{du=zfTf*JSZV9v3Zrk~Ks@*DM>9X6 +Zh}YR|F;HtGvDR)A3GOM)zM17JdR5z1iJMz5;KfTBwHu%=qH}2%#wKCtAOYE7!)r+3a*zKU0=T}9%H0 +F-R`Xwj4^VrW#y0r3GFv9`W6%+~tihhB>}_g(PD1+-;I5i353+^?`kxPS=E6{f{KqFZ^203^fwU&lCw +87ohnIaWc%6L9x&ij%>N$+iT>mpGasdwEAx`|$+iybSOOVZ#z9!NQ7hBOhoTgdgdKuI&@DB5A{@hA_6 +GEh}Z$e4e??Zq~&flVf--Z&^@Y^xQ)D5WorLXW>7O|0CC%%V*N~9MS&R=Zca1=JPxiKqV61;p;q=MIXKK0I{+g?{YfZ4KiEeW146*YgWw4+l` +WivF4NIdhP12?7=@V&WDi>4yk`6yZ(O-#_P#bUdM7b2!ZV5%_{mafl-j&n7w!>6c?u$@%Am6%xOb_Y@-d+tE{4u8X%oUc#*D6m?XHHbi3xi=@eBk^4_5Z?)^^=OFlFh5Cm@S89PzYoA;nH+j@oPm*tM#g#ggn67vkQ +fr>iR66sQlK*gegwtY@Knw7dFCYLeEn91xeeo~8!8a>f)M40detff3wms;0c7E0R|U-Xwi*;KT~(+gb +kxCo$1c0D2iL#HT_q0g+{Y8R=%e_@Y)7g8476wHpPNk;!wRydGYB#p$ymc3`~jkMERzS%4MTTIpR?=7 +p7J#aewyAa*o{^eDjlbzodJdhci1l#R!*N%-|=_n5uH0lbRd6`#QfozX{Y)N!91GAdE`6#h#yNOG#OA +CtZen69pkFcAP>}OSEuxfePQFnJY5=Ns1u01Fm0BGS&Tj|70AJxVX9l#!d7P8;IM_iR&~^?TEfzQ<>? +{AX9%IA5I$1_F_bb$>sn4NM{Hr?WVJ14YZ*mZ`rr+O)!pGvrzIB$NwsOf=&``-{~|L!7E@DLP{-=gK@R{zpI8=dYiMFAXVs0twL}bEfZ@bi(M-oy$7J8ug9ZRK9K+>x< +7uE8Zs`fTQ58&^2SBHFCs(P3=QF&#dua1_K*zTUh#(Am#yr}Ku^wN6wNVIYcilPy4Aq8@Gg|BM8Op@J +599+@%ak443|crK4KR0LKyxL!;W^*LAg;R+C>!9b4sE?sNE}J2lc%YKu}zIUNg<4G=;KN1U~EGhn<_& +?JV_x1%oCJSfFMrcb(58i`$SDT%+eZ4s&4CO5vQiJ?W1V{d-@ +nI7$*kFditcPr_uk)tY7yO(ey-9tEK--ZQj^s4RGI5^y-Z{XvyqH +GU$)$u`_kZNDjk2Uz|{a|S0fLfIlnrMi!2e_vBpbC`3eXaI2N4|WdJ$oh`z#u3?0JH|cXOqhyrZ@fJF +G9q&ext1Sh^cebOTn!|cknQuDTRw|o@&_06^T@j$#Wc3?B^gwAi6$XoeX5;PK* +g9b(omY`1E%$v)!0elwM;dM$CrFyK$hP-XW+7nnB)Esxa4O7Ak@g+{~+=N3MJgYi$Bi{`nk`q&~wG=| +Q`5N0+N5xvHoycYAdeTaE|M8FAk-#5#7}B0mf^lT(xl27yQZxsCsM|j#m32@CZcSS_h!Y>uzrMaUeST +r<)-<1uM9_hj&%OrN!-nw}U()05I6S%ADn7NwFE^JX#2%W>qtX(LMp8Bkomg!&Jq>~;9gGI?G+W=;&Y +xi8g%6&NFn-3miI5y_NP%&YpHn`5PFKlS6G62at3ym)y2)+I9W*{Eh1JeeW6*}shE^TBQkEf|tqU_~t +Sjw#3mF9Hf=exnP$@%2@gUl@vLlGC2oKg>9ZihGlQyhHG9P|K}A)k#W}}Sw@i| +?c)ASqg2SQIQ3q2xVeF}w>cL3IfRtyFkB!H>2FlrZ+dTOwm}!db5}hULG(G5GabB9z?2$CQg9sZLaK(7pKq|01tw +vJXf%1fH;SC*5kLNxN-?cD?MbW`_mMV)5x#PIGlQlL>n(;2d+gYsGFmYM6*bNw{l?&BeLSL8ldJuvb@ +chvb#iCFuI>+)kz2UJc4FvD;h~&eoG+Tv|G0N#3jBtumE8o(om3#L0m4-800;^2eUdBs0@CjFhQB`T? +$9Yycjt;JF0vln2qz^2H6iyAj{_?*Q&~4YNNH3L{f1nc0&7^4p$Y&kHPIA6ji +0L|ul8Q#gKNe~B-6_I-1?83KF$fNB`V}}k*67+A&acyeK@YV(qdp7#~AswnoBxBpiy%K+_2^V`mJkDB +sZ)%a=Jmyp%AWi+k&4#E{MRP20qT@;GSdwC^?_ui&rF}o3n;hvdFtx$yxw_>?F4oLo`Zn3KL-Zgt>`8 +_1`p+?w!eKHyj#fcMwMGer%{F}7WyX9AY^KD3(~7;^@<@CMY8 +?9B~Vklqt1=u$P@4HqeDnjODj$1Y6IsS3wZzaybgy7r_9p}9CH1^s1mcyVz&aSksp$G@E6wPlyx_=@Q +k^v@R`4zG?cYX^wyy>hzTss%QN!1!0c#X{Gqc8dl6ZE%YT{&_KIB5L$)ZHM`%l|qw_>oLrlDT!1`-L{ +b#Dx0=iYc>-9{GXxerwG$F%ob##p6j!C3Yh&>)i#W)GrHPV^AZirNDcP3GMcBDim|hxI^$TYO<~~&Yc +0L_E4bGWqk=W{%TlLGw~DKhV@_dEMiW#`Q_-{v-dRAK04#h0e@z3La!XdR5fRhXb^#iTxS~KU24~8hC +VU9AzEdd1d6Z@7MszLIJMu$Q{Fq4uzI4(5LwK}s@30g)a#7JL=#xETcoxcg8PXMQn_)ov!~vg``EM=pnG&A +9YY+O+B*rsjeLU-1eE-91T|-m2EmTO{S>g&0{kHXhzIy?}rtXY7J6t$e2|iBLFE*Rl4O;3*cAqO*Y|> +H5oNBSXw1uWMbk)z(OM7(cf8BMQI0acP!;gnBI1{xeC2S9oi-um +n4EaeH06J+$wW!D4L_m(dIX8XIbCZE%HIj>K*zaUBCM`lFQHk+6p3m(AUql?F{uMr$TZ_oWx}@+&qh+ +FGRUwe4@-+Vx_WUfpy=O1pPyhz>c$&N3Shcs1`X3uE4_qTwm<1g%pBD7-p^Cg@EE(Foi%^3&yhrHAWE +J}s$H@xTym?#GuVjgn`KehZ9YQwRjGt7WiTA0jv49$>JQ$XN?0R8gPePlg7-xRSz~eRpdWE$Du&f?XF +%DHH2GTWK{6$xtJOr<&uaETUqsx_f6Mlo9PbT>dBacU7pO@HS9B%cxM00m+8 +xwdZV#%wWYdD;iEA{s73Hf+^Eszjd=BwnmwcY6>gxEn-U7&5Q3BI^!DF1Ax&De_GnP!ccN!x=>}7?(2 +;pfXV-FX1E;w1vuBe-`#e`;f&$wSbj5f$GhWq)mxbr;x*+Z2pe$u|}jo@jFwNmUFmqphdUut4-61=pKfV)4>qSVh+Vof$sSacrQ>%?(cBm?73P90}h%?d<8 +vL#Mo@%(|-vC-i%$f{-#sp{N%~1rgn7s_8gn@(#Kvc;+ZA6**mzR;(Wp$CM5>p2>u?BXKSH&ri~Zq9@dQ~_CJbvP;o$xXx_fQ8q}ipQQaQf!-WZG+CIHT&)B(Kjs)Ba8Y1X +MxfiW37$X+QVq0=!v7(YYi(12D|y8cXGG0IYwP0)pq}sWhjdlw1_E*3bsC=%=OYp)b0rr_3GpQ~a#&bQ3ON`V7=xWf$OZ_k=;|W8YMUH3c +q9?u+3SH3>Z-2^{I!S$CN<=8^H^5=^7n_$zH9p@{!$95WxX+&cPAVgCV{0%RERaRaUMj``hk- +9@o`nIqx!4Q@-UqUv$^MX}F1BG}Y0n|-F`Na%9GD`YkiKx|r;$El9l0gN&a{KxxnaJI=C?SMVCEn90) +AXG@6aW +AK2mt$eR#QEaJPHp1006K7000&M003}la4%nWWo~3|axZpeZe(wAE_8TwEl^8#!ypXYa|+(;p65@Tbi +*p^9)e)Srok4GeY_eusqKx9;p3Gs}{PecQU$EIgGE9~CsNGxZ*(v`*f4Ixz*x +&|Z$53Ud&(UB*PL3^mgk;Y7f^>l@7&h~EObz8He%1}rZgg^QY%gD5X>MtBUt +cb8d2NqD3c@fDMfW+ykOKsD;Z9JB3qj9NhB&pMZ6-`oi?=tmDY)qNzxRhfTI&jJOBMSh+=CkeOM;tEB +n?_JNc!Jc4cm4Z*nhVVPj}zV{dMBa&K%eUt?`#E^v8;Q$cRqFc7@!6|8!p07J +PO&>>BV=8zgi~GXJ>~qvYM=MfsJMulpd<;=*+~dvaNUq-5bYL0y +ub@!3X~u(H`XC1P7yoGh-`zX#*@{H?enCT_`%=$yO{!MHbKly7c=A6QWMDlwwo;1yD-^1QY-O +00;p4c~(c!Jc4cm4Z*nhVVPj}zV{dMBa&K%eVPs)&bY*fbaCx;@QE%H +e5PtWsARG*n3qwfIJr(dnm!{o-Z747oeH3WvY!jhHjilnZ!~Xm3NQn|9I_VxJAc#aBk9YTdcRX608l^ +3>mj=v2_>X;CmsTkm2EIwLTP*fkomeB9ym*4TQaac0R}03PoL9WDpbhKff7xz7zxQL^vbt*gh=(Svctm_N*vGEo@O_ZiF5Ka=#8=&WFt(s)or}0hS-kW~ziZZnm-%045nasc8`d~(%ZywL?qYH=Iv&ArGiSVtRr>i_fz$+m&YLSZgo+H!sh1O1Bpruoa#s&fdCFy5=k*%t)EB0AjwX2xFW6DEeLnN_$e5Q +4%61@Mf(VAj`enTM96PXFdrnMT#dEj$CO0V-1Eh3Y(MR`BE+qG|lYdfcja$xo4iLHu2Wi`AESAQ(^;! +S>A*@H|MkZL;qFD$)ecB{aTv=AgCG-?I^67K!yKGf2(VXt1rWy^*_C$U!!X=`l>K8CBH6S1PJUqx7KX +hT%l{$dCZcvC~1us|wSRSFY6W8CF}Z8YHTXZSJGriRNvC)fKW?5LI0XA{UoMCmJF-GKWki2wtm6E-8Y +)z9KCqAtGHv8ckvqSMwpWk}>=KjPstq#}{LH8d3RDj$5=9s2<<#~_$V;4IRWRZI%EDUNQOG*WzB4gz*nJ=%uP?(XU@#`!vXeL*E%VIkIX +O|V16&N0;x3Y^59B1h;sYm@U2z!*qB-(kUb23EmO*aX84u5m(7uiieoXD?_2kBK+v0JyVQ+7Ln%i^3z +5+Y7$dV&}0s`IQChTF$4?`(;94S?cR67B$6G~hZ&+sFv*a6huMkEE78l!n3ggX&tu-6)~XVBpcz`dzL +Y&*oJR-nm +5`Rj$6o>4+<7**c6YKJgK>Q>YvoiU~^6xb6yVQ`0XL_jf~Sl%dO1f}j!WE4W0l?s6rgo)SX%Sb|jcf* +xT*F@}EM+7{wX=VrP?)LTvEXf*HS8&xJfp`4`!cs%&=ufS>BLogNc*U4Q8`6Nh%PJ^Ug)kzp$a7zHS1 ++w`yOMmJB|sYyW&++cc?kh|<`*++X5QZ31r2ffg1nZ%cXJjbn~B7cJrtIBs}!@)V7&aqEf?LEJbO6nZ +%sa>>Ffw@C3KAfbv}ac?u*9&yOv?r)uK(N@;!JW9FT}#7Z-8!8+{lzqIxTZ5Lt?2b2CrIzjBM`P$VVU1PJC~P-nZ8 +`aU0PJ1T)7Ql|p7G5|W-5UB}S4-zM_kUB|G+_v@gi6ugG;e{VFewb+S9;0Etl{oThL-Eka#Zh&!fH;V +JTMRIXW{!HOpD%0fZF^0m^z18yC<%Q;Y({p@}p&xp?yuy--&3jlBdG|>?#aBiiHGI};D)y*=HJ}!h03`ze03iSX0B~t=F +JE?LZe(wAFJob2Xk}w>Zgg^QY%gPBV`ybAaCx0k%Wi`(5WM>@0zaAb}16r4~` +v2*%wt*!a+cHj0dQGW4a$cu5_6bAcu`miB?*#jB$bLRlFjJjjTTX-hu(DAqy%AKnIgq&%XnS8&9h2XH +w-nuxITwEgjl`Z)w2@?s*#Ie7)2(suY)4UHBF|L;LrA4mF(*>db_{3mW~jDIa+-au6uXQ-+!7KV>h@@ +D7VJWU#xmV1TGP%oQUjI<=lqbncyqEF`=nnvn^n>5M<9Qqs#+}!q;p~GZy`oAz(b}hd!Ke5DmlsMmZ8 +NbP!I)OR*v}g%fdT4!{0v6&MP)h>@6aWAK2mt$eR#SzRWD^nr006fF001HY003}la4%nWWo~3|axY_H +V`yb#Z*FvQZ)`7PZ*6d4bS`jtosqFl12GJS_dJCq_FLGH7?H|?s;c`bm$+PLE>0b%v~Q1lz0yOX2*?m +6j{W)fAK~MJ0bLuW0V>BBx+YsL2w}*?a*DlCNCpoMv%vEhePSm5TKH{|F>+}zy|`s?+lXKbVty=4i0|&cnw2p9wlX7iHwJJ@Jkrp{Bm*p +?cwe7$;IT`@c0<+@%F0;+}->_SK+_#(mXp1Bm5fxy@Ql5wJ7FIEkgJ=xj10(19|^|k0py4F4{=g6H~x +l%~$-9t$_1_iGsbVDv+3XWZ$uJ$v(clVrPH<`aC)yD)n>k*m*?H(*S9E-B#WhFq_(TFP< +5GQpn-DnD^FA>npeoW;`K64I8!B)9F)4W=E~%{Y_gP*HKYs-x+qoDGD8xt?G0`4u42v1K=rtI|OY|75?>g?pjL<{pDq_v!beWjgrn>s7XIuDafPFZK +!Mh)M?EA5SF)IWZFhk8tE4iN!NUdBt#?)Zkn5e}eTT2uTp%o=|nf3OUuDH%KI=ylsA%Z>%;=FMZmD<# +Sz3_cXuhQ$k6W{pAyuEAAn*n5_h4?K;KV_5l_4A?W`zVk@4-le&SaDuCrb_Xz|y>3VKcaKc_Tg4|ZH8 +>8s!H4XMd?E#B*%T^%WD5wRDJzT`Vq^*5`pr +R@AXY9|}NoNJMgeppmP-^MV{u-Tnvx7W-oW(01T(A&=<&z%}ka;D +uB2II8`zL28h+3VYPF)|=h4vXUBc#HOuXUU4lni8o0D6+q)%AYo8cbRrkYYP0Wv9& +===!v_me$^X5*PhNlOab&l-EVdAGZk8>}Wer_UB1`>nQvM?}a3&4ZCi*ws%h_0Sk +I+*-5V*yM5SJ)_=k+>|lLAu7c}=BFyMgJZ1F$jt*V525luxX5Uzl_5(8I@E2pf4>-qr@UVlFnnxoNmTBtx^yG7}U9n1zJxbC#;s +q5%nS>BQ7S19zr+t1OoBi_wV61&hC%CJjXoZ&*h@1Xi-WuwS_sf5&Zl7JE#WdYcYvO9yc6ubDcOX)h$ +`*!Fv^wAL28(`~LI@1k9Y_X{TbMf@ZH+omt9>8ruj9#BmW0Ksok0G2~vXd{}7R;U#QMftzU0XY3mM-C +aiPhFUs*_p`j$vh79Z6fCK7jiP;6Rp$Ib&%g%2omr*;BxF7lkG0^nP=iP+B|g0eVL4vB)p94KWK8b{gGUHHMsOL4}cF +t#{Oh@^Dp1JtY0FB34V4GY@poQ#zQvmZFrYz6QaIrE?pyKrvV9<{@Zwsts=UOLsop{w~!z(S;N6eloN +a~&(t$aP9tY~W!9m3!BsA|*RysGDf~46+Ljv_b&38rcBS3>j0c_%rAblL$!x$m6_e7weovi`G)`d$4qf5sh?WLq`q8X! +>*%NFY%1G#U)_!`Kf^y>hf^7rcPod)o?zn92#Nda<#%I$W_u=-ZauE3yE#N8BrN9^rqSUJvd1vTdEy> +s21Q-SN=q!dtLMaHXd)oTl4A5I1oG09t{$x+Cl1h|eEhf4E?u +A%s!O60{5%v&pF;28IwTgmC_)$TOb$8SfVr879$`f#W6>K&>-p7wZOB42aFUC +aa)5~4~({&?OqnUr?Czb{5E;nGU+KR?&W@;i?(w*#s|EeYcyrH-;VYffOMJu+898YxY2t*Wkn&+I*(z+?Y|wXfcM21>WX6|z{WSze}MDWDSmqNU!OS+h@w!u;jlAz3@X +UDqhyq1Ef>&8>O*nxD32J7tS9-E2Ew-_rMcWxr)(9dIJ95 +TT(H;V(i(-Go}6R1#~@bBnfPv1680HESy$Mic6_4`&#N!QfXCd`aa+!1 +w#>GYE(h!fefmM|S)^cJ0NS#C!$t!?YTb9tmVf7%CW;W2zpq50?@>kVF*FKc45z^UKC!RBGi@W2dG!R +#F`R(NWSg^!EZz1J9kb^9G+Wr0S4#jyso-G?g}M{zy94~LkuBl_e9RE%Q*$$^1FoR<*brc_JoRGXHZz +%$T+>I-wZLrmU@3cEPk8ttbi%7b0LK50)5;Pqb834UU@&4}B!#&A_KnA9I|)2DJ937JAxQQ>4TSrnZ% +w-cDKzg#l736X=2Nbj_PlBD(|ojN8;%&L@NmIDPw+rK=6Ez +5zi(|Y|2oEx%g|N`vA{4oxKDMR=5#ma^c*>=JrYSv3+jiQ^^&sMtcCYxkUN!sWiz;a46;z8%%91pmim!|98fvn5BrqV$~hcxr87E-g_N +<7Em>C}yyC0RYv=?*@q+2r;sg=T9c{ONBk?m-;-6N}x4RU=l8lDuGF^|d|J*JvIqx;|~e?l`Z!lH1+? +;7daa@GU&;^R?@^>B@kww4u*4M_RIutxAy307bNxI~~CJw8<+6&i62*AMo+#MZ8z3KAmYJU0iJNKtY< +r9NnwXAHR{s=(eGk;c@b7?Auxm`%S1mvKl+F7#CMXM-p*?M_~GVeYe!UTu77M!f4NjJu{TxX*s!#irO +xnLx#Lbce0J$I#}ljK?zEk4m?@Bz#|0eMES1TZlWhmbBOVz0Up{Eh)b)sMcp<{zhNhX!syAvo^!+2ULm%X0xh%WZk$Nxbmj$W`pZYt_Yx8J?&E|mRJ_yg80XD2UFWpw-ksRy1wA&#wE +6ohqXI;_D_7;K!%RIjevjJlJgvo-?Dsjit(toD!`N`ih}ZA=9}$!wD-)!(y4?7K!qQAAa)Ze5_K>B<+8T;nAvTn|4rr0<}>Ze@GYM=R%IwWc#iJWtH) +YHxVk4e@5CUiOx!@6{<721k2(pvSSPl_#!LEs4QrtE$gPq6ugZ`@GZQWuKnG8|3<^S!hGhk@5xLHW0 +o0D+bPCZ6KA4JqGY0O&g#<(G=YRd*~JdMU5=3DN-P*IR5*-p=?=pg9VBL^&zpX;hQ&a-VEIY-;wcTy5 +5IGKO7UspI;V>g|;e2dQ#R_O7EYI<ChV8( +!n4qF%!RXIHuOP9R*f38J9e%3{?$H!{MG35DqOnO07!NfB~6E+WN?dPa_t;u^tj5l|E6$_o>Ah&S2WR +#4bhh&4@Fi7mNJ#5eO=!U@Bll&}Qm`e=|q`I=~>5!NDGdXJ#-~b2|PAv9@H@+A{B%(AibzLQOFvVWwwuWun*={C7y$T+n?Y61t9AWk*xQ7K^XcRKVg8b9XR6zW +pWbbW-yQEz@I|!VYDi2S$mG|LxM9+1jFo+n7n|S9m;DkaWfR-8tEOQ*8x@zIgcnD#S5pJ5S6)U686 +R-I6Ve?@h{Sh^!`JNJNnG@b%E*|l2YRe)Y>$rN;N4DaBgQC-;#S#bWh2kj%s +g*ZG|la#Uo*XVxVkKp0-ejFvRbciYC(1Iyf{C*SvrtO-ov9>VP}#@Y2jeR)?)8$gM1mxfN(*`Dujx9f +0PA{t=h$L4)Rz${gy2(=QzN%_=`$$t5~o1^YwAgI?mE*p@CcLZC=TWCjSdx5qG?!^6fPP!lWU%ENiw{ +H4^3Pf@e_RXF=tIDwTFII9JHEM^(e-=o8I3nH$;qICIP+&z4VTxvzeRZ#e?=V3@R_z-`k0 +j-4{oViehFXP@|xfs4Z&Jdoqtfov5EP&iTo7saqQ94@4r0bipcNT)i)UVM+O?$4sKt(w=6D|{-4tR4N +yx11QY-O00;p4c~(=DC2d}>1pol%4*&or0001RX>c!Jc4cm4Z*nhVVPj}zV{dMBa&K%eXk~SBX>)XGV +{#Te4%P#f;^lThpaL({8PUVkioMmZ+EwMQS7!$1U>TcSw=4r6f+{2U(YM +{SMFhNLrLaYLsF|c!p(<*3X(2q}R)(C=e+yWl^OmQ^`{dK+!dc9soazt)QDoKKKO=M^djI7m@_kxqbqKD +vTp23*?6S8p%Nu*qC!hsc%L|2m6Lcbyxign@T6D^W8!I^QSooT1FLm>3fMMmYa08x9VtCEp$Fc^T*lw +qaNA6StyQc0>bO+&HzMp9$7ju_l-u;i3qhKjI!1ddcGxbi8PUl0V%{l_{EjIJ@G8JgbQczsldY(7*34 +69Vqm3gn%1nQiwNn-?O-zudG!nKNe&D$l&dGClSR7!D8Gm;@K1j3Aojd!IGqgMn?r?WSaiKd_Kmc4Lkz2W@$W +71(5dCqx0}huZ+dhm}jcewCg|0NBQ3viuaR(L6ySj^30$wHZ0oT%DM`_Sfa_iQ8OzIYKA^G;(#j>wS^ +ZS*dk^n6-!)MU}_~96T1g^xv;$Ew_dpJ1Ay{TrJeBk6Y-u`Kd9kwQm1K*d;O0-uARK6~^S!9^Ik*d%X +Cf^p8qLH?`c?G^n)YyA+x9qC!myE+Qa6SWz9_4qSYJ=$pdP6W_SNuz0UI$;>Me~WBv{|E}o)Gd?C)0s +r5I^_Bp}G6Ac=2`6w%0iG(SLO;(_stu`2l6ypyO0`^PNk{&X>IqF&EoN*4~HL3`0)eftFB)kVonnQ9_=RcQg_Pl5P>o7}iH(Dqe?8j>mK6pZ&qYRaO +0t7RerqSUbTh~=4$m5xNx0dLP!3_D6;7{^;MVxqTens!<@W|_?+2N!oBj9%HlIgF(UDaG?gnVmikYUx +kT6z);}s+Imc-HF8wfTRHxQ>1C=(F4;Xb`K(b!#uHY^Ftvk62&BpJCTOu*Zy5Z@_d*ak%S_~KKXiB!d +($tpfuF8_{A3z)h3=G8y@sj!>2s=FUc!XQo(E7($`B<4hKh`fqnPHK?~78~`Lt26lXB|ZauqqHoa&3= +1nU0iS*xHDjHh*O>q+NAvMoMy(p!)nl+pR@(&DBCeQDV8!T4*?#!Zted0BWCWL)xy;Sw#R$#JyV ++zqLUZ$OJ~@WVbYd>>cs!X}?c1Md$a`nD`4BZT$bWB5Z`us0E<3k23SO_>jC#&_a!i4ACc2aQ%-JFOh5z2-!zgweb{Yc^g +A%IhN{ijipxI5G^;?8Rgh=ByP)h>@6aWAK2mt$eR#Uggy;>Lv006Ta001Qb003}la4%nWWo~3|axY_HV`yb#Z*FvQZ)`7fWp +Zg@Y-xIBE^v9ZSzB-9wiSM#U%|>KEV+uBjg6o&Fb~OI+I{FIXpGH+VT>t>@^CGYib&1uB*=g7IlM^J# +dff02Zl!$$&crLGjCeiX_8aLzP;LT`xlq~>64PeKmS(qe6y)K*^rjE+3vtZDLdj;8}-oA;&HK_b?n!k +DXlW4yS~tU$CN+w8g6|r6cdVTeqe8Sq1^>pA)A(Jzwuu;H$VM{saC?~6wr&u8oo9Atatj3DbVsJm1GVx>UH&x2!V>4Dn) +)~9j7(_jdiiiKe0jsl)=J#%D!pgFsrMSI6>iq=QNk)Y5PG6=B~FS*wx*h(UNtFY}e!%`)!%qmTU$DUK +wD*6+!F~8b4-bJKO^5qh;(^Eu_#)=F5(P>odJp*UQvv|N;~9B>-ViD36 +R8J3x-?*;i3x7>f(#0&2?UOmFbIR>x +Mxc&05}_ak3loHld7ydcHEXOZaAgVdF?NpB3-oXxytJAwc~!wVQGfw3ePlpENSldR=IO{5SjN>r +M9R9Ehtizt5e+VBQnHsv;I12lvcM`OQ|P{rLLO&?W#pag3TLJuN4Uz9c;uj`d`TG-vVYj)PD7&;#wz% +5wxpVZjr=|_pg!^@6kX4j>@wOBEa6wWvg{%%>?kqPE_UE29+Ff1m;|XRJ|qc&@IW3@azHnj$LWk~)~G +mHpeAdifYNo3lngNT&Dvg2mEux_o87Cm-QW_+Y#vDua{{bAK~M)WnG%F=*7i=bN_b|vGCljpW_${4z)|`O=U5d*aOoJA~hAj5&+~e!GH*a5p7v6JaOZkRJ*XeXQGl@yBAdx +o*K36%Qz5d_DOOP-{w(KY_Fo!M}y23R0x|w%7^NV3q!~Dri1(MlCkuvB`p`F7ak01tuyj-n0!$zM|OU +|KnPLrfn#jPgx|FKWFn@l3`A&GC^vjJm78THG~hQ(v##L+0wBhE=bDI!!A%V*(Z)KAYez`z!}~ITI&! +@q{?y%RX1}O*7Z~x&St&|2C9g&uvCjo-HTj4-c-wWdTfhK-QNY(x$jHCxBl9I7weEA +;{V2TYovq{6~7fNG_!Xo+bGC`nyG!o?^B@Bv1fEEG9F-(Fwdk-s?lqABH$cVysiQ*}Md*FRk;=Lr{ml +fz!wYp*0Y@?kT@f!~9Cw?@9VU%k2;dIi^ZOzeOw=zI#3=n&M3%$O@_7-ZVg#1CAu2I%N{TH(%Z!0V~T +UdEVwv@;H{JN*7flmTEvbqq*J5>K6BGq;ED0fR&3v}O}$W34ra>By!V=WYCL;X4TcvGF0fzL-#^bwq@IxP|j6!@qYyuBE*4K>9h>AM +=*{wlqn1xq_A*N*o_%Kj{br7ia{NfzE4(_$l6!-@28%BjfVHQ+iNb#N&0F^@o;m#fnZ$o}Uy( +kD{6e>$+LtG8XApkN;G~}47SxMKql-}`Nvs8nL+ma3HTT-B+!Y1nsgkwm6(hZE#)!PcXze?M}7Ky-q= +SaH}yAtgAdCtLIP7{ZYE=*pw{FwA*Q$`9oJb5Xwz7=CILgGJ$D)p{37nM~tSU>>eBsIJl(|GDs^(2kH +s+-aZoV}QuF^C!PW!{~$iuLNQqelokmWtc4(7nrA*~YfA4Sf3f83G1??GoyCsDSU1P)i{Xk?z1=p&tL +rOjy7yKoC)sknNU4#l9Dhtek~qf7}qdJRVT}EJ;K4pf*p3L^tJ_^^PdKMgF^i>fz~72i^$I0gbvq351 +h`AT?tsKfEF~%97a5uaX&@8BmN3Ci#AN#uOK0(}oYQDe)~5v`iMOgdj14qyG3Mo+7dDt{g-PKFh(v4N +^gS7kX$e57>;&z$(gXQ&r4lu+qMd;v(m8*STo|Z9CkzW|(K@5_d1pGoO3B5=(v*S{(Wl6-Un~=`WtSa)-~v-Md!WV1g+5bZSzGb#uXcG+tGJs$>x$!yEB}fy?11mUl$ugUXXvy8H7r0wEkBgOCH;Bz_C2gw(zvqSGaUQyy`jC)pk+J8 +wddb2!U(5myxE%vH-!_F=0~9F7&}^!^U@#szOwEKmWBB}HQu@WJ(TrHk4aO}@mav)1^%-VjO|03M&jv +sf+A;bqhtp4QPyTdFU5+~9`H&n39rJgGWZC-q(Z{n_F6JQ5lj_D5bN-Qp=e93Fhv~oSaSZq@{`LK1KFG2%;JXBZZ;t~yX*-z1r +6~qELg{_4W-$y$fn%MoWPs-Dw-Yupz`?QO8e+NWzu?H>$^*X~@vgo&T=+4=81evx9{qnr{Qm!I^!qPR +O9KQH000080Q-4XQ>Q(-((V8N0I~uA03!eZ0B~t=FJE?LZe(wAFJob2Xk}w>Zgg^QY%h0mVQ_F|axQR +rZBadsgD?!;^DClGAfa}z#Bl1(ow}o3Fh`_7kZqR!`nCy>5Sqz;A94KBp&R5`wQ6EOX@`$OX=Y{&itg>ID*-M2GEp$6uA>{iI5LLeN#`^9?Ncsj?{ZswGc|i%#C~Ka8iL{3q4YARwJxsBHjk +by?p_W|^xSy#0EHndf@7k3suOjlYj$0L_G~EIlk{`7MOAw&rthaaHJN%ktV$3Zewe2<4zFp!<>L^H(i +)Ex^hFg_fo`;zO**kV29*`g|xBov6ZXUTf^~}@tayeD&%HJiFX}k!5XB@p&yZ}&30|XQR000O8`*~JV +8NawNHvj+tRsaA1D*ylhaA|NaUv_0~WN&gWV_{=xWn*t{baHQOFJob2Xk~LRUtei%X>?y-E^v8EE6UG +R&`ZnANsUiVOwLGE$jmLsFDg-R1By6<1r(GO^70E4xzfNYi&9fEi&2#ZfrL=i0sv4;0|XQR000O8`*~ +JV{LU}Q2?hWFIS>Ei*9)~vwD9v +WB9ugC9IUaQjN?mwN`otPbt)vt#}1)!S8#)>iw(u)AMK8Ig75>?s3W1AKk-ZHk)OTv&2B!Xh>8IGTCW +iAiwBW@KR*&z8})m|q_Ql>)TGUjYsi6G! +z0oPL#seZR89P;_*TdV#@n6Fzn}`83u?H`FabWdh`vLCGtbN3L&Osa&aKsvLI~&UWVOCp-`uS4=OOFU +INBYp`)kSWh0N5>p!r`B@rvthPYK2mTOqmLAF=rEdiD19^KpA#-dcMHxza8RE+p0HK)gZTGAIFQ$7LV +~!R1_WnG`0d!enWmz^>ZphekD2>ankbg_DK4n-)@k^aZXouN~n(+c<_2EXFK##nf0)ihofHD^(zTfZnk{KZO9a;YL`L;2;X?R#lS;>WxkIPZo{rc{ +ArulG{G8dqa~foGk-EeBJMWx#cYu1VOHPD5T{D9o-|~PdVft$QAV!bQ?KJb@oMu8QafZ*S}KEN$`-u` +1QYh(w4_c#?b=-cQFl`yr`%@Opc5g5O_HcPZlKJU-CHI!8dQ4UFHwm+f2Pu1z0)6e5@TGVF;-r!QVtJO5xa_H>hn +Pd*2hCuU5=sy^gr@^>C`FQo$BBs` +4^Lz9$jko4`}r5<2$8L@1XR}b5s)dWiJlgdP8ep~*cANLtKXxiIBZ=k;sTyB@c_m~i$O=qW#oDgbGPH +LQ+j^hQG@s9+dfZKn-*L?xPgEgJkAMwREG_iZUN+7JkaBR9|6u-VQp>BPEH<#Et(Au&(#UuSsG(LapO +#wrcZ=14VaTRuRApq7tj^qAPYhD7driFuC0R*FX_nd|8n-n*9H5HeethfpSd?`Zj1Pv5*~)04omjZDI +8#heJEhHh5Z03a-;Q(@FeN+R<*;}-EnJ`OXCW +62(=C;u#*K|Vk5EzaA16oI~G!tj1Pa0DCYUBCk-YJtZWGUqe79F@!BFw`GSYd-&N(R45$)R#9ySWg=e +ent2X92^38Y8zg%NmFz>pZUllZ7!bWxFZ}*E9TRm^w{^Q@#F6QXBjp`s>)%z8e5%YBV;6u$Ky&Gfc_J +H^|-qa&_mnrl`}QXG=(s+VDe&_}Hw%aJo_o +Kv>fJvAgm@i%j{DE~1pOl4e8_E)wQlzC{s&M?0|XQR000O8`*~JVsK-IyX8`~JSOWk6E&u=kaA|NaUv +_0~WN&gWV_{=xWn*t{baHQOFJob2Xk~LRa%E&`b6;a&V`ybAaCvo-!EVDK42JJKh1XLeWjw&7Ubf3lJ +51UcF>pFG3K1r4_x3YMN>Zuil-T}$ejE2G9zm&o%ws~Oz#WH}GFW$VyhVL2rzOAPi?YUK` +?7vvZJHbg>hGOVC1g{5RgX^VDn(tgAa@G`iBwEu_!H+rsz5c4&=#&6t7nlD+z+FFI4@RnJGxS#9SbDg +vtvkCFsw2yDW%Y2Uuxmg5cj#+y}(3yh{XfFVZD_ +B(!O2#6MPoE4^K)c!Jc4cm4Z*nhV +WpZ?BW@#^9UukY>bYEXCaCu8B%Fk8MOU^G!RmjXO$S*2UNY2kINzE%M)=?OVaA|NaUv_0~WN&gWV`X +x5X=Z6JUteuuX>MO%E^v8Gj=>GXFbGBOp2G4emJ$z8sfTXR4I;}SDNX`toEUVn^mqh(vwb8kxL4@-#CG{!^g+>l&v-7uU$o +njI!`RZ5-!6>wW^F|Y_^n=mbPGRL5ocF67PR7MptxBi|&RyrdfhWVi)?VL*4^T@31QY-O00;p4c~(;w +9HPC@B?176^#cGN0001RX>c!Jc4cm4Z*nhVWpZ?BW@#^DVPj=-bS`jZZOpydvZKhhrhCs*gjL<_GM&9 +d-^grV&boM0%JB1r~@2^7XbA59>>NyRlw+FM&(!9cKNgA*G9-5|sUW;O(}VbD#2Y(i>7#2bv-FzF^lHZ-+Cn +GGE*x7o7D&({FQ!)U{h8#>&?_$I|SOuQk{4H|3$dcy=88rh_8cR-d~1^5PuH#E9oqPOdz1o`t7lh=h1 +c!R^61mA?=28=VJHaNP8m`#E$SB$X@k}TIe##bm<}%&k*v1mYxC>D=4<-HPqj(lxy*@(4rzo(8w?3W4A?g5yn2{N<_>^bXe>-v)EFVG_xF~(GwiQ*ojYW8 +_G-pdG6B+-1wo$1iC-#xn{DXki9zX#W6mGC<7~^&S_>h%Eqf5+*^H}yqpWYPL|Z+{jH2yCKehC%lzps +pOg?243T3En`Lo@6GnD9zGEBkVH-jqJOUrOMC1jBSG#l=o&~N|!Rn@L*W$g~*N$kM7+OA>cpcaCbB^a +{uk2Q&UU+2CPBrTX!wq^)@M$8u$l9{o#)5a620IsfjbrgivpGAW5aIFUDQtbHPV`M;a2a!Si$9!8eWu +Ip4MN=Z(lsUNTuM9~J-U#qa93i|4LRYVa@Fc}Nl%Y6Q>6CksH%9?hfyq(mML$|3wQ-}SNo7ZGQa4ZKG +Ypty14}zWxK;+m{d+bM##w$ahsH>G12$#p7SOm#vf1gNoQrysgRA5#(eFtcwdbsAr`i)xk*4i+fuA~E +%SGbB;U)6>+|h7o2~VpZ@G}Ggj1GC1f$asQ%DW9YA5@~tV=>TvY7^+WcgxTz$3-tGI&yBVHCObXo$8t8dyDr1kq;%uFo^?GJbGplo1dC$v2W=5EUz|UA +>*#i&0BrI}0UQ>}lVFg`N`+G55ENLg~ko5TMuKd^zMxEFc)1L2*dD@2+zdOv(bU|_})@44_RwF4~Jj4 +#^wjEmQnj2J!H!E)_3Xh^l&TxjS%nvX=N5(#qWV<3V5yEIat#sR&Oq086W*obQBczO@9VYp*rgdtX2o +Da?`!J`2d}a2zs`zS~RViOnQ+2!a=m)|7GET +d(lkL5G=dUe;fn!tKfa-;4V5g2Ai$Il$dYa_coW7O9DoVlMCn3<7zT}B;)^0S3A_oyg&zn&3$Z}}yQl +>fFrXPAO1g>TO$_cpzi0$lh;LAEtK>KMih_ky@oNAS{yHCwhhBIUG8-7(VB`jkHYu}-0kzT%g)GDjkY +&tB_`({%6uu$T4H0he1z8JK!71P0R#y!6omreih+GXE!-@x`( +?{;3xN8qPe+DLrJr5hi1NY0ve9pnIoB()($$H@L=6&t-Loda +Ftb!Rm%>rtxYahP#V?qE1;O8}meXC9QZy3We+W>)wufad$EMbhNX2e)j9XaCGF)7@pQbH;L$-I7Pr +x^ttJvkl2l*^#etnrBCmWrG8E6?hJ+tv6w`HRuazLg-jf41Ni_DHnZ_tUtE+j!>(7 +#7IM%i+IHNX>j}SCn;v6EKQHi6&t4ZuIi4Q(7TbdNpQ*T41zQu2gl5>I`V{iS6?%W#=H+SI*F+Wb%(Q +$D_Ge|<*PxTYrmwr6G*K2fdtH%N}3A*(XQ$g6lvc}@48F~p{W5Qf*mlo7(bNGpCIZ&|lpQ)K^8G}D-zRc!g)`NGZ +>8`VH!+-7@yuk5|~P>nSOS=A_$Tq1WI!iP)6Z@_*l;?fDCHRBLmEw>jSt4 +8e>>sdEy!P#*>;H940GX1uFVZ3k`l7jhV;_Cqv_(IC`ww8G=>G-K_hVlLhWW(*9yp8JvVk2F2m8$etvNUozzf{rXtB0H2}Jbbuw(Qk48S$1zaqQ1TS)c%B)fQ!0Y`){<`po1_JU|@oFc!|a1WU-0Y_ +J7j(OtYpN*#>u&a0DY3o>w#S>A5S96NbI{ZG{3AUuCz&^qUp1VO|tq;+a1-4Knl&xC%1)lBX=Ph(AIG +VUFaHV2jluy-SFCUI}%jcKfiG3cdl7JM^dBj^4i7?|4)(+4UZzub%@ziGa`5_s0<*)tpD!Q6LTh!IXJ +DaxZ*b*o0Vm$TP9(7B4bHf&kL9a#u2oFmQG)0EN{48JNE +NUSE{Zy(^(SUw>P0{-)-q3vPauo3!ocSWuq9?DD+k7wSs%%a+<_XXR8a@B)T&IuIUVFc +?7VTv1rU)V!cA5S1Q_8HjlPg~kXW;PP)D2KD_g_02s58ZKRR^w~gZ~adv~ls-Y(h-E#oIF53CgGo*=> +1{IOPv865nA&v&(Tl+r7Zl+Q|NH?&R)qzbI_vNVRc?cJN>hW52UJs?WATXZGZHEz{MeCOl3ahn>TeSrNSCWVyxOR_9*1$+e6FJVD#WkCSWT3<=F%a=UQ2h%Nc +R8dyRNRC&D;Q|szG{Adt5hSK#+Yo6k&wSfsP60utdHx%HlS+0SV(GdQ*(t$ptj+2}?*p%6!FVu%oT(q +>#Rkm~=XZ9@)doFU!3-S{4<@(GN>McGvU1v|+zL!IU^y*Zkf_^sHC5*~u}V76BE1xv&&XNiS0Tq%L +B4k$Cwb(+bLo+rK^gM@(ZFLEt1peMM=&H{hCqRSk#@1F%LdREz;qUx8jGT(AbP3lJOtbmL!A8qg7VYZP8gGBEHYxw +u~f1NM4}>jH+;^kRJ}dJ~5mlwK|j3^p+06#g4ZBj9$xI#ZhfP@ddSK(O2?43-ViN%e4-7Z9+&IOSo+P+&_}Ag%c?ln{J}J#+Ec>v&qBIw1MmO?rAyio$RTVNV +@uYF=2ewrMm{c&G_h1^YG*cTz<0B4x6B1XgqUw!U8ZfX5>j}_XFt*}>ae)ID8KTNipvSBF5{!N^ +<86YT0;(Qt9r=}s&8I&cN6?L&JMG#PU5uYWyTq!BYzyZ{f_G%0Ulq$M0>-K;nF=)sWC-W5t%(Z-cJwc +TnnrI?n&#t6>~_*BU0(r@)5bY$E;+Z%sEu#WH?CVW3XA0NOFNl*Uxhwgfq|){shGlgV`lEPd?i{EUcP +Nx^;ZgWz9UskCVYIu8-QRY%0I3WK*HCKiK2OkP?Fl^Og{c{kn6qdhEoZgVQ~)Ztr98 +U&6;9;K#wsBDPqZrxOS(NW5LK1_m-@ekP2MCVf;SDNiXkl>nOB+=nSLhHe*l6jBoT3Y!OEx=th|Q|V+Y`3#l +azQCV>USAacj^iGdl!llPD?NA^?TnArwl%aFz52!)Su14XbhO7B7pjz_KFg;%Tw;cUTRvcUu`!u +VqwhSp=o=CHx!%@I^1{iEr?Dam3&S3A!;0TvO&ZSfyU8seq<9w!zaS79EkB7|=9cqSHjUfs^GZaMUQZ +fSLmLi#G|eGy)`pa~J$qdlbyYkvS +sus?lVr-;C}P55(hh#$UH+x~2bq3@1Z%Yrlb>k=0Ltr7eMcFQS0O)&tN4~_u{rtj-!3j|&0&EzS7x84 +sXZ_G}h^Zp|C8sB|TvHX7gGw@zPiv#|h0e*)$ssZDx&)_B(4c4j#v?-r$kZ!#i$1G%jUI(rmOEbMuVn +2*9^4$pYoEp~JLjytp(#Lo0F>}BRM-TsJ3pyAGX8{vDe;Q#hI&bl=JXZjh0+fO4SHKm6gZ6X#Ze*5ET +Pj{r0^p)q^oJn9=TByd(!Na35*<*#clcZf2l4`l9slN`68JCkP(%rdWuO)63Qnx=3Ih5|39%2HzSQ|u +=l8psQ82>P4o)Odolc0h)Ij(;l1yO1KjJI}OdShrFXGoeD +&-R)8w!uJ&$S*bL^(V?1d&B*T@FF%JSb46f&SZC#+wicCG`oqX1cFlWtkv)tfe}TO@9T;c_JibEM=e_ +7ATa*AQ5Z2D>Wp&!6k^;xkc|ab-lZ2T%mtZdtTynR&gxkx`6ISY3R-iT{=?nQhRpsMudsr#XqECv1eq +|nAhqlfjRCW~I$mF^*urnNIdd)qMvL5s8intvMh{1)@Pdiv2@msd#^mZ=f{f$^VRbUG!W9iq!^7He4=Y-d5@2Ob|rd?P>n~ll6{2HY<8>l>( +*W1pwGA)t)yY*yXfJpqrgx1ds34sdUDtW4HT-fGyP||6XE<17ISyFvyi=S@1imm +0yuYwO)SQf8Qy&&h1HFqH3laDc8MSQJ3xu~SOo6qtyF>fawV$m1^h6onT_I*5pGJ*8Cexs}&GpRm&;i +-ayMDML%1QIq-f@~du5`GLk!Tyg>F~%M5>2=nIAvx}a~_rQq(^qrOm#1{bW97$x +*4vv4Vj>ofdrrs7l@F#1t-xVw%Ez-BPGvT=i2y-l^MkJwl9YM0|;v!|vW)r`A|w%o@F7_b!1)HIha2V +x3&95S+&p>77>N`4;uzGA{2AZM7QKQ1T$9(>*q_BY!=McY9yaQf)m?Mk46Fd_1!DfxIfm=z_8ojojgW +`*@()!#HfuLuMj11P}2!5`uYHk4=k-Rqk}wIw#g{YtVmN)UYi31GC?v)kTzaKsxYE-NenZPW^RE{vV$ +DAs60{EoXn8(f;DQk*|~iKs3cH>(ao{CgCClfM*466{lWd=xA}VOA=SOEIh|whs9%LITgj0%qcQnOgq +pUbh?R|4N7cCd=mi?fGmH2TLwo#OH3~U0{@*d0FFNZf8b3TE_#JZ76pJIOC*2-H)EFUEIe6;w-|0}5i +Uf!#K9L?6D)y#&;b~Hpb3_kc(Ozhl3+tJ8wT{p|56!fudw+2Lm6=F*Jhjg#cXelix@&0_a+m->~u1L3 +tfN}6`bw?E(bDzo9s+KBtUHiHnxDbt^xQ<&1^4zI=k9|HD@^){`7d0T_zCAN6_nZy2xv%v +qOfeYT+{O2wn`kBE5dri#;p4`y<`P5DObQsD|$;KcaPwh(PRtJLJlQh}Kw$iwo0VXr97o>>a>!%Z9d- +Q~cbuRPB@jk(0Iqsj6^=O|`K*5gMocK;ByNjOT{atlZt#FUW1j4lSkl{gbQO)BOy-c;r$vK~H<92mAIHw#kt;Ag&aQ_r +^F?o@I>_+5)}0f54b~vc!IQ`@S$2@3g#z2Wa_n{JfIke1C{0;u6*4sNY$*5rCeSb2 +#3zE|cX`Tiw)U%&suJOO>I$@5Z3vjZQJ$5pxNed<}B*tuMb&(vdGZL0frv +`=m(#*a%)RanPnOolXw0lo`#ZDrdF(ef45uIN}YxnYgbVtd(INDCJ(E2Supadta1!!`x<4cB +tnX*Y?BU`0;O-->!R+*8svZf(jJ)!e5ko;S* +;ol4PosbbN&BTv#ysh!XyEY=3BBP(n2k?jw+X}XD=)*%O5QSV&=Rj7cM5&dB>oxJ{AlR!dY+LSy3`*s}`=r`KI3jFZ?HK^2Vcaf*6?dk9 +;u{p!KuyYbgKSb3SPN$NoQY7^Ru7P)xj5k*RR8=PA8Jlapx4YEE82Ceft@AdbKn=Z=P7qBsgRSg=qtN +|)#~@aY)2#GG*ecyuw%=qvn9!wVq6-2m(vY%$g;^&1q^#KPhDwVn;|3(g1jJft=$;4QI?6N{H2E$$m$ +vd*H{A)HtYd-_rW6!psZ#+HTbG+6=>z`{rPf?}HdD-=upGSL&Cv2UI;$}jWHOzUTZZj|s7`YBynl8_M +bbAR!ZKVavltmx{8KGOHp=?qB!WgU3bUpZyQ^B@2_@Rk(~==s?;YqK6`qW$THq4%_C{gC!V9(ovD$JOc~+kpBuxz&E847Wk=)_7dny<*qTdzi;I!eh!1%1|*Gaje##Zj0! +VS|@Sz71uI>n`>?5mDLcfA63_Gw+%A({&jutYg_62oy@ir6K5lxSc3wiWvZ`NdS{CCDT=X> +gW5LYgGM6nwkzP-xqdiHT5@tcZKaFy7|*_uI7K<67upZ_hdbg3lLX6J;jxxT*t$V0)|~0bb#+z?jX58 +NfHGHeY({L+YllRhD&OvnCOsqJ!7!+}6W7}WOG9y`lxz5WMxe4gn22{*U{&Za8B2;tts+0z3&xST?wL +@!PbYNMu9EZAljItcx*?oTc1xIRs=loY$c{$R9-j)4k<1jDC|B)k(L6{|4IZNdE9ym;$;Q5kcTw7RmF +o{2qQ=kbXb`!l6f<<}fm7;c~ +28UXX{HC-f#D+dA9|bkDcP2Gx6i@>I!>S^!W;9>q1|%3655bzOR!hKVrUld}6k;t<$}w3PwNK>m23Am +@^3v_-dY4Z@P0i;>8v`w@@mruWEb5v>Wf)x`9Q`87&J5X9$N_4Oy=?aqV0<>~@K94N(kthp#=4nu2ya +$Xj9ux$bdy2~-yeqG6D{be^vqK6JY~XLqTMkRI)K6>QLOy%tV+v|Fjd$srZr4(z^yw?LZ1tDCLgjf-! +}xJ8ZC5e6UA1v#v877xvblQ-EcGu|bEAEvinhD0LHWKbLPo#|%_B(Tq#U&aNg@szdc3 +b5osLZhyrOv9T@4Hv#WO}TU3ZO3yU(4*IGT`UD?WD25!VSWs~}gCv>1f8G@I>&1rHd8_g?K?vLGL4oI +Ju0nBdkNA>OyKzd9?f0M#Lj^TPUe9!i8;S*lkP=ah_%Dow~q9qP+n;hwQy4_KAc^BCb_&7UPS8&_BFw +1<7D$wgas$tlnK=2&K!+!WZ^_miuZ4l1fB_)YZB`u?fJC@H}^zQSbAp5@zh?K57>Ob{L%tZg*y)T&Vr +x^O^7l$BZ&zghRKrJ%c8?8wzx>BN0aJN%wy9qwij6x=ft$1X1 +(T3(Cb$GemlbWulERO@6c~dq*#{)HB-;hSt2TgH0ue#FFvJqH0J17bmKBr;-h{7O1EAi}S1lVwEO8f__+3f@{R*i>8!BCd7x9ve!Rv}ixHN*nz| +``;1hs@(zz8vFLj+4!1s*T!C=|BnHZok+T9!*Cn9~US}*J`5-XLG>KA*35WGYK5ozZ#$xy&90zD{b3v*Cb$5yLGDYjQq<}p&t4fIXWfl&cE+|*!67txCYb +lZ0b!K(A7AVzEY!~k>sP;#as{0_I4tfuS-BsHGGIWE~%1Iz}a!3-)b$v-FZM@xx;-cExK&_=zeaMQH< +_Q$`sV~X6uc7tzfS=ToHZ8|I4@rY4gygkZ$TnIhnvf^1&k2kp)vIsr;!EI-sY!YtmyY$> +?g?1cO%FT+13=~1Kb5}d(hZ}vzqBbuNf)7Px-(~wt!=opwQ727KS2LW=U3m(&MSCdlGk@H(Z8f_{V<$ +s!9G!JB1NDhK+jYgT5MJz+es7i*6OgH$t?qKEA=E>TqH2B!G<7{Wdvst}@>7;6T(vBL`QmGB1^VOS3e +Vwh`5YOiY<*4Hy5O(HWo64{qR57X$Fku264_(sYcj+yE0s&2W>k0}_++_}1;Zr>W$VoTi5vWagM-)n@ +IA=+igC(7`CN4v3eF}1zN)h#OBWAy##mnBl6DrNUKY!FrAWU~UDI2H5&9v-XoTqV=3!;FpT1ZIl(@-r +b$E#$DUuE^qX=E+0mVE%?hQ}bjoz{vvjuY0Rou^T(ra}yIy*yLOJ#7@uWywgZy}~Vs*g}Kv-|hetIw6 +IpORw04^aeC%$O;PDwwNw^N_Rtz$b89O@6Dhij~p4YO>4zS?9*COLOL=Ut-rhWLcuGanVPjm|=tAImk;yf>>X&X3(Xkqj^W7|PS~xxjfp!w4 +&s$WmEwuEQiFtk4yJ(Nw9oW?46XJmI<7}tBpVGY)!PoG=HubPs?31gN<+UQH^(&wM{rc-3BE3Lv0|S| +GueWDswN7|%IW&4;JJBF*OOoQC^0f-MLH)zL{!_i(POwOPUC27y}-A2?HuK@MI2hRX70^7h;^tZaK62 +2?4&mrc6A}hyn?-qx2H+ThTG}thAtnpq@SGcH@a*}dx+t6csmgHEM{x+2?-u2jsayUbDTbKhu#s&gWQ +r;0X}9tq_5$G#P{oCyUyD9w4M&kDJ;V)$A_0=r}+d0mGrip+%h;uTx%6-Pu+XZibBJP#Fi}SQ;txr8R +s42XFN;_p5rxgZPrDzO-)TDJ80gXAKBU+z036&N@-!~JCh#?WXC;%VGReydb^9)9d$uvJUC^pk(o)vH +<1z-M?8ctkUJr;q3(U}S?Upk+y+_Q4hIh2QTsLx2~MWYtl7D_()Fp;iA~w#-lMngyM1RC&jfHmt+GlQ +FEi3yD+J#IduQoSoC>V4z57JIT>|`{wQ7ga8#rCEeTdgWAyBVN;xGF9C? +vQqk-Umz;$;U3a2lK};ZZP#5MRQXFuVxb0ACWAB5YB@$Sb-D6feeXP;`;hFtV%(0_6=fJRU4Xut0eO0 +Zjg7s*vC=3xehD`-0$?X+oUw%cQ!q1<`{v&ij~Dk2dTG77S<34<#SkDH_#}EZiqy`L2$<@=Q+Lvi{dY +u}l0wpaoy1U|8aN<*POxEN#>TW2qS--YQ>zZiC#**F*ceBZGc7wBL>F*Zmow-*3>s&)u3>!zlWso<8l +6wicd)-5!3d{bR3DO*_(4MMF%we(WB?jSO$0&+me|rn^Jo-)zJ6)qZxi^6mM&-I(nSi&J@)FLHc6T_` +*XSzjef6%uxVGr$i`INqFE7(9~SJAKzTyrNkXVhG8pS_9%iL;{EQ3@GI>sBS|QM +|$=`u%wHRB$4(WpQTjk-Z92)#M8eDbpvXnk-%$^HK7cl3Z=&-1YBk9WoqD>0tQo*g?BxnglOd2Bi_^; +qTb1kj8@Qm)Cd%6ZL72q1#0A&65;xjNMiHJHJ(-XS?ku`rvrFc%*mzxYE~q>r8dXaBtBNSv$vFMV9O^ZJaOS*k@lW}?;d?wB{GR@1N3;{#k7U&3xi +*GjNT3x$D&t3%{PW&Ri~$p`SnN*a6(Gug5^OA=MQ2wAD^%+J+}5pl$EDWUtunkI+0eFr>7&rXS+u<<~w^?cZ0Mg}m>8)hprw>=X6B%SrG+b1_Z6G)V6^pV{9Y(baqy~n?zrv4=q^WS@?548P!7xoX8B?wF +*FwMX;4PzvM5HLeBC_}>tilZ1t;RKA}4Dog039^CTmdUWC8U%rtI1PXb01Oi^u!>*EV$dbOBu2i@kfj +&I(B$SVA%=;T*8#EY+5qFriW~+nNKLT?gao>*q9w#q1(bso%;MDZ*>uxHq+*-_l(j`&X<@zWvpR +$2RHZVb;s*)Js?OUCT>!bYDtn&($WEFRTTA8qx@!<<*@>AnW8&Js&V<3Ec{LswGs@XTg5>RjUUTLGfI +Eb9X+=mYbyB)w`Uc-)QefttzmfFS`_=DGJx9>%|_Xng0X@zgBfZLH`C=>iMCfI8dbt5L9LeHIBI2e`l +U_Mks|e}CugL4WrC{?6Njetds_f7c}5Q#q26!V?K$bfVo(33_1r8%_C#J3l@Y6NyZn&d+E35+8{qZOv +Uz2K|<#GA<8`8X0^aGuyMrAEBL($}&?&f}i(s&NlABthz0;tG9KEYuYny*tYj()(|=~?}dHSc&N9t98 +FPs)TInac!DR9h*Jxd0{UPPWiVW~)`KQBU?wIQP7kW$nbUzrC2^Kl+38-}ornblS;EOx!J)gj)sCuRr +pch(nhDFhVW-W5f3%YQ3Tsw*F<&Dl+BxL2te@AGVQG<9?j8G#eE(<$kb2tbIzLTJoyB5}Sw)W9Xm>i^ +9?B(;9a0F9JASx=??yryd4IhZ=icy8p#m^Nk=xvEh{zHPy$5Pl&p#sf6HWyg-a6`RnL_x5p4!SrKLWW+b2`|YPZ%1R$c<6UogViVsTOGiqEBIP=q6;*7}tICPtP(+lF4t60!06H67HgQ0OUno_5HCit2QoA{p?BtQ$kEbxmCpuZm)+lA_mRBT0~2owl`tqO_MnDht`Vs0i>9W +UZ3k2kLc_5$%k7d(>vu>8kUR;^<=2KQ+VFW$4A?*w%iZ~4SQ6=`G%4KW*kowRFL(PIIIG>XPLmiKJI* +R!L4bg_lkMd7}_|5F#+pL(`RHK)H;1RfvMIoa3f@R&oH3FiW04()v4H%AFn2zHH|ujhMJ+o9=Gq6ET8Rs6?EM<^mj1-*{}W|y5i@qPJcM%uYD_nEV-aCxul#1;R4C@g*${=l4iniX$ +P;-V5wOlsV@NzY%W8-5^u=GMnX&X@q0OkIPO2HIi`FYPMo+bl9Vr}C`)rRvC}W{+us$Pz+jG +=4wi^P%Zs-}RqeC!y>0a7Kl1l`3Tb1>lG#B1iF*y@eI(ncW{~K6jm%Ty>5qb%#zg7Nl%>)K>^0D59${ +JrX~o~E1^Gi^{6`+D{+@@Lz2%|mWQ?qCXIZRscQu^Pa3PZ|6|Yy;;6G>BcZ=D< +4u0Bl71c`Ub#@vebYQeNZ@Vt|W@{c43{RXTD?P;pVRi3=^-f}MTH{LtIZX#9iAL-ZQ)s|kYj4<#Tc1= +MAx1M-^TlLrR*+ZZ>7x|4HgY+NXB>~UGrp#rTdAzX_;5eVaP9bGaFn9PTk=Q6G)QUwH{Dy2Oo8y|Z?t;nLM7=y0I4Ef6kS-)L^a7EL@=PR#Ge{}F`$PhR#jnEc0c{u;%Ax_GeQE_ +{_=EU{$fwRc1E%6tf3WuEbp|9}Hre_d@FysAEsXh|at;f0q;y6~0+;Kddq0`MaE66%d#wWz@V0y>7o= +bplU%$UHJ{g~3=l`erTF<{`=DROBEz`x;Ta|ec4N=FG|@!JXhic!BZEP`NRGO*bZ`g%vylAeJgukw_1 +*|CD4{}RQ*lE^wFe}&@ai62|sIok)@ZqQFBiF)U +q^;r>=ziWv{NWUlq@WeAfF#2YRjq#_p%hyBy7&-Ls4*laC=${T9%y-%EW6;LS?kewg7t^}Siag6Tr?| +bqNhL-TIz?xKtVHW37^veeNFiD~gxV&#uMpqL9mEYW3HYB1qlLZhud|TPgk}*md6nt_-)NU1dkza +v!#azI*3|iR|1}1^K2xbV_i&mIX6OWSgggaaxYr6X^IZ8>9&+Inc?U4P&EES%U!Fip-A!)BK=` +95{JwiwV{IiJ1|(I%L*sNl&)LAL>IRf-|_3ZEP4E=T+CDx6Pco +D-<890I51?h2i<9o<@QD7m-tvQ$Kt{*UHPbtZ)#R-_Xg`AYn6kH<5Ya|;^UJN+lJ=ojzxSD3{B-LBGv +RBu=cC?=?s>nl3kOGvnkK(zuVF{;v(H7t}Ki)0VS>I4K-WO+!WxGtyk!EEkII~Chz^(h34ZFoN5vILb +LC*;xZ^50malgl$q@0E7BqfiL0g-|-xT48E%iT-{t&c|L0bgczKD$fZ(r;K#eZ_4pJLz?Lf{laF& +K`ZFhNr^i6SILF*t+ZFoobK0n=ZWg3_-o?%{ALK>;=y;1p+;5O{(tF>2~n1PU;_Y{8FhDD+Eg!^A5)@ +&$ePwP}C+$~t^g0>Nu9Vjv9SbV2oUC1zRn#eovQeuHf+Szx^Md)w|mSyTW{FPl%muL9L1SR(YxW=qLy +s|aKvI`HtoFM}J?%+e=)+q9oq7LGwTEM6oCkSWApul&C1Pi~*}Cx&oHGr~+gb0Cj|E70iZhXQQ +>5r~e)eWF}5obBksi~he5AR5tdHwtJ5o8NIMy$l6~YO_F~}1w-*a}j*GA*TIT>xR^p +GDlHWH;2Dal>{`s*<@+|(`cHycYM-BZu!~Sv9(7!Y6AGi63{$|9w7xMFg5D)BmFK=lo&Q@pfbPK2mQg +0{aK3E6l#1cCx&xB*aqW99NEHyvw-RDj@?SSk}`D_v-?0zhF(~iI@>nzCPZt#v!EhuQ*%?V~rP4i&xt +7$vg^)|gu!g;&eJ<$~Rd|f%Y$eM7u3>m~!Z>*3Au(jO@*$NuXLAVGR11J?aG3~bjAw}os7LSCMhk*cbI|`ku6?(s|dF&Zv>&gs`H7UE~n5XhKer@v)EfH +%E_{6XF`OkmZ=l`p1{vnPtC6e3?jJn@v7qPd!$MrVJF^yu1F#lmkg+KI#e(3z$b>}7{X>7Ic>4QE+*s +ZMgYb>fmPkWd=6uY}ez8WSZz9vq4NM{YVcl%PWP%aY7r$^`-lfjkbsu2>H_r_e^+~>7!#Xi4go|QuBS +kl!Yz|~^R1`C~c$`w1W=>oqc;&xS46rN@nEnQEc9+Q-!?jBuDor_g!(Jf6yHKe;pS{O2VavmvbxZ!<< +t1+6;l-@r>Jd{_p*^s!{SBIYO*LQ8(BLy82hEK)akRG4V7VR`Wi`;aM>ATbS$snH0nir5M5{Gg)R1_u +n)2$KX6-$ic?6#I;ZGPWBdWiF`N-tVrf>?Z +BW{Yd|l8p}^qy)V`N@5A%oxzHcL`SV47UN~lO08NHqXlD7%HZVrv41v%X%8(d9Cr;oPPJRVVidl>zu# +U@4NUtJEELeib0DBRn-jcchnS^|Dn0zTBOnm-S(kd_|avCCPsw>Fa3t+KvGX6%2c+GL4sR@he~stS{(;;Y(r_xMoBx2quxg +gl7AK=BL*+cwxw#8^@cioX2jfy8d>1`YJnA;PU@2XnyYd59kyBNT1L3(~taB=>3=Ayp*~Q`BRp$8d_>aNt~}K +qkPnqwWWoVoB-NLpa8-<0rpfvs*NemsyJQw0nifIxzVyu?0Tq&Zy125=+HX2K!7IWWHbJ_wuHt))H<+ +m@cl!>@+9lwI2(j#tss9KLp*dnnTU@xpeDJb)vQFi5gmqhxhiKiaD)sG~}?xp)7W(i<7N;b7)SdqKNe +7$>|u@<>6$pw*yCB`qKqT`t#~iYKKAn$dE_15j+V8=nvZwd4KP>4X_Mh=uT)t*1oUl-La6LgHTZl13U +J(9p8;HmyJ@xes=z<@9&+a_K0-yrif2vx9Sn`Dl(_;Xr*lieCD}Sn#u6iV%I_d`28b3Z`XsxZIR3VA) +`@pwXQV}z!;t{(T>k&ds1rupp|E#xQ}!++zz)jdUOaj&LEgPtglX|(y@EMkt2MmkK~a|!(I+kuMS<IXI?_9O^{_AvfMXlTl5+^14#vId(F>u+7)C0_CqV#U$#^T|l#l)xz-a_guA2RJn%1xuuG57xPdm_g!dSkj`gx5Tcj0L9ZC7Zr^az-ckI1Y6cKtIEEKI +QkCA=UO8?y7grFr`vm-5`_;1vZ=XD#ZXz;mMn{T~3OROZD8u?uf;`Y+!7KY*G4}2ohRzitSg6vR!@nD|BR1{+4UV%t*BnC|MMm+Bk`ly*nJZmh&ywzq +UFfc@fJ9q35bnzS3ApVL;(JA!*!q<5bi4eD;1IbD3;qd6=dHXrd^3(S>0yqQwo^^zxh~na_r3O;T%8$?{udh3q7nT@t-<#UeR(=)SAH05kP!*iNGR`mc-T9YoQ^fr;Z@nunI<)gmagq +NK6Pu^M{4l>VYvvbu{e7ou+v!W=)m>x14X~6yeFod>Z*25qf(Wo&{|9rte;^&KMH+TJx4?n23Cso`+7 +M-!&mZ40*w|4ppT@J>aMrC`iQOnreP6TltfudZV*UQqFrIUF5XJF;v;F{$lXY5nu*qM^sdqKoFokqbucT2I=i=-);^ClOM|df1bcz~Q;Wc1p3&zb}s@K(Sc9S|iqVzbgsaMsZ7vX_u)>9wJxVD1f +b{9cf5IX)rbPf*jr*?%5K(4XI_fy7n**10G3Q{yDm%2Q$28=K7`Os=$$P{56N@sKZK%DDIm_eu~-Q)S=gW1WOMT^0KFeU@*xygVTcheEETd3-=Xl>ihrWoYLx*DSw!8(A4x{_-ZsP{e8K +p!I3Fh^kMQifT^cRX8E@xjwZz?_M#|e%&S*@RyRZPSz1){X09_*U&`z%1~)uNYS_C+qHXr+v3fCu^sD +86c4fV3?FSgW`m>_*%Wy3LESKOx(g}o#4|!v6EZJWeY7Ul&h +yba4_m8kLw7|P^Z6-vbOUL`mV?q^n0vKaPy4|ADf@dhf+P;{VE9j4-fr@nLi!&lSd5%QxvhHB!M9aMX +f&&Y{f_z$0-~mSKhf|B=)H_-PT2nY;D6UUV+)}SOf0vZhP@^>qgoY=I*l&(~k}#Y^Qx0z +GX(YxprhPWkTB+&C34}a&J=#_kO4N-_Y;Uvn$e)dw0}&Z!1!UyO-ViI-$14vWiMJ**UBP&BR;)mK4>p8_O(1b#?Kp1!6e6Hyr +D-B}JR63Kn95cXB&o{Qe-EG)G9P0fR=Eyw+VhRH&{e3!wm7mbrQ<SCaGxZM+|03ZV7F3l9>LmG$dGo68^z&H +TAY>v#07X?;ZKH!h14db09MdEhz?36#@QJIa^Vx$~$2+!suK!`J6g*9s24+NVeTuF$2SkaXg2PpFAmr +NdjCxN6Up1BzN_lv%fl+k;kT@B?6|4aQaybx~nDNDj!wGq|^LlKr$-eP0?c?Gx*siYk_6W}l3d(>tZA +(5qSyiL6r*Fqgw=c;i%jLIig1np(2n^Yt+di{okX?TC4~lR~EY*25EH1_MJB#Z9G;EkF1wXS%P!FZNq!e(G+FAyiCc)H>_BcC<@SKe`>{>n_*z9=sCWXSVSvJkLO0~BS1}TrKqf;>EE~`?4qKJ +4jNVJG6#g06Ss>|-HM}7ww5$OijB2%~23-duwrq}uzk9?J9(JAszL=UsNb20xm^4L +>3j=MXl*lj&Dn>(qviN}5a1ugypIe+(0Jw8WpGYkinL3;aIO8PVlm5`s|u=~u$@VSvgDl4gAglJ`Vu-~d0~ss&Sf2ZU+U51EhO>*v +G%*7+;nvyLpVryX;iz8{=t){Qk+D9^#AA3|*sb+5$ldyy21!t(Ed4BV78Iqa%vkXgPj!vj8&0D-R&DM +PMYrgNH(toVc9c{E#DWGwcmS)GU{)s&B;8^6`bjWe!Q8jb=fx9b;pzz#qZ4U8nMAj8rSe9`sdJn%3ik +K}~jlId}O&HO9Am6`g;4_J{SUfI77Nnh5=$$b}x7{DWqJdv-3wv3}U9+`)^S&+g|>sik8#CjuqER|bo +RR&Y?^eNNnLk$J;a!K$*6%9O)wlukhi`nV0q)5&mqZ(;0a-=>^$A`sp!n+wX_#xl3Q}x_Tso0@2`h*l +N_r#swN&;x@aBWuJ43oDgbpoPr`qS*5l!NWuZr60Z9mL|i)$wk6W!N!!02-H?r>C +6?oSq8BdRF%dy%$@lUt*$khk~ITal92KJWU#lOlrkT*JpZ5D!D>B5Pk#NxcCBf*B>O--V%qpz7ABgpn +Z}ihaOh^`}+d+7fCutlYY^5o4z?eqII-BlX!0dq4B+D5r3)uK(Mx|2S*rIUp9*CDj-m#Sl_#mXAvuws0zb-AO3m*jvpz?fJ +l9L)~2BaOQx9zyiqpzQfFhwIu8|P8LTNKd}lJ<>Et5f_^%An*ZIO~SDjJ?2v1F7)HvOqKq1V5IN +{f@1Jg+69XJKi3TOlcoBY~v@=azsl8-62YkuLN(!C;n8khn&pW;dyw!0P%boE*Xs#O6o=;8cpvPpR56 +o6457xI%e00j4hx`ne{6h-%~orLGo@cc&E&L+Z{uIAWqX+IY<#B--9ddUMfCRH0q_B&N&>uN8feLRN8DS +X2qFBfHk02DmrW;?B6FmX*>`dyD|c3SAt*4L%q$(w8F^w;1hHqzC!GH_}@<6NJ{(~Jg-&-Xo`CU}$Tu +?V+AYA+aKl(Zw~lKFzm)Q6e7egV~W;VsTF>!1=@jga?<*YBeBy4k|>Fad$aGq>mM)QN-7jYr=<%P%{s +YV*mXx)p(M5j#_;;Nj{}y-MSCe?c$$K(ofez=z`T0L++V#UbHSy(P9i1NY-`jk?jNu8MCRUph2e +m55hIOY(D&D|53p9r=$KFvi*F-k3kzofFMF5AcjB?fl(BOBcIyZ?tZi(wDEc(*+};KB8xYw9fMoD6M^ +mJXmTs)CtEy?|%tg$)eLR+;MMfTRMbc@zD)4FV<_Zhh5x!2ebTCWiOCT3fY!FQdx5Z+q9*0 +3!Kw_JRLZ`04z-hGYlX0<51rPH_6I=F>!6txXrZRPZAOH+V*mw1|NH?f9~YfQPuZ^@ryw%3-Z#lNZ3$ +8~x6C*F)*Twv78%rc +KOODYZ4+SqGTQV{?*{lOsQc`0{t&lqA0qoTZkx}dluoX~UXgHF4= ++O1m)#S)hEj|K%}H8<+Y_wnobVAG%<=m~-Ksg30GxJC1l-L9<SLP +*XStgM<6C8&H(cv&&i@CDsAAdT){lKalsG0;`X#ND72LGaC`$>tG&B7EGkhS69Td3*+~Jkj>$*tOrP$ +)&tlM_y4>D8-yaW&E2?vAcWWr83~!?1mX^EE5<|_)B@?EACFMh8Nr2=qqh)lxj(T<|hGf1}hjgi*AjD +15@sav!*!QpQW@cg}O6r^PEzlmD+bq~mTXZs@(uL>9AO(YxvedV6oHiPKx+2yrXNfLYw=Pd2hdEti5F&dw!(~8p7rN}9mUiA+Qha*9aZG&t)C)<)$$af0@o +sybZDi=bj3XrfKLcY--8zjbBVbIZ?gkGjN6cjMK9IYdUDcs)+ziDH0!LhUDN$H!oKz0J7I@Vpgq&ECKxj4V6G$DJ$ve(zU0+<{0 +$m;sk<^t}`CJg585)Ne?W~I;>X~=ggP=&Nh30`K7JOYvAQTMta84-sh7pFM?y}@*}Jc?9&J3@Rg+I)p|~ +|aA5GM_Z9vK7+QbjX_(U3qtvGX)9J&L@zbz7SxvNM2jmui>IOC`#z{L5Iu`VI!4!-DzlVO~AAo``T5v +SsEQ}BQ`7NWmO=BiY4KLkb1|1JyO&LEm#W_49F6S#UI1rh>#1SK2l}LR94^g{-xjzKxo1tUkWi>d&5r~zF07p-R;-F?e8DL-C +8{_~tZ^KK!Z=RiFo?jPc0R;gRb&$_BeojI8f;<2)|apbNFcTKwx_!$@YYlS|4w>_BsPJwc#ot&bW3g_ +d&MPzx9E?8w-;;tg```Mh@xAP4?(wl9`w7kTLx|24$+=%TSLQe55_hT9Auwa-;I!?cq`+q7eK}Ri8UfksjX(Ui4bqv!+$L7Jhvjnz<%$$KeNV_&b>Z_*zCyNmAMGO&qwIw&pIFSH3I +vZ^tyhLS!uqPRla!(ZbLjX@MD#SYd*AF0{6qg@Rvr0X%mFm+USiBMqOJCL7S9b(e2Ql(_on4UJ@&S;KEBN^+>NuH?BM|>GK4|UG(bOr%KYt2!!h6%0NkrwPO@977C +}XDk!(NumPVrV*_OU(l5v?u}ioDQrNRf?CC4D3|=XX9-zt9QU_(xPH`yI1IKE`F`CQ_rcLjrLM@GtgxVYdL;smucLQ6w^pZAsd2l#Uy5E?gre%Gqg<{%?YGiBs4rEJ`C=B_w2P +3?uUqgw+W@;_9UkIpcHk$9OAg1_f5Ro5S{yw^1}Y&B89eIh(L4t#B?cnX*ezMfC;#nFA@;HWMBc0hMG +W#(vl}=tm|XPaX<;-u-a!kr4jX2juyxEXR4G8m>0Wd%17wMc|NHHPAL#UhK-xb;!oPXo&sg~Vpr6_th +^>1b-v-YR2*e-^LtzAlAPOZR6vHWk#Hdf(09VY)c5RdmZP1PSukeUtTfqmVHhA2iCj57l?G>fZ`{|Kz +gE(T(UaZiy$&Z4Yz43-s_=ZO4#}wEu(H5gy-$S<7qoCjAGf;RNHrq;1=(b->Zuqv%n@3wi#tNyM_t7q +(kAYjw3#N7trf7Q$P`ssQHXy|}#nMem7257B*|B(YcKUOd!)pga`x_WCkYD!3%67X(r=^X*T-qPQ>qaVf2fIrNRy> +y%K@8ax+W-w!x-mQzm8P=x4I##pKl?qUr=l+%Usnuu-A}o;itPsoYSbPx*=v^<4Mo1amAz6OFwZ1B(A +bnM27Qda+BdIY2j)L?EwaAPHi&S*+X^y3L5w$0J#-L+g-mYN%Y*G!=VB3%eE30+oc2?EkSc{k&#tKl~ +WI^g)i6wii`CycZfpSvkuc~srQ^S?1hqEvZvFy`O&M>T(2I$I`!~XH=8q2xKfYe^G`sfX$tHy1RQ@!t +p9Cuj`>xCKGcO_1w3B2<|Qdwi!yK!)|9LhW@th7gNIs`BZ%jI@DmsbX*706JfkU0R+k(Mq8R;L#zMNc%h;|Y&ipENw%K7%z$v6}9q +WlQ{Il9w{N;)JVgvml%MR(%`XD`hL>aa1cI{DIqbifhtJq!tyIBs=zi<6xDlsqUEVRF=?Efgjhz#hJbp0z4kr>e={l6UJRRnJ(AMkXQHowZ^W8+tmOhurh*|Nt$eEx8f8L +r6qDqx?F~9)CZ!qESVD>;v38LhG>y^4;8UMQrH=N)jg%PfQ*4H2NnI1PY&aQ)l1Aco-Py@UM)BPUuOn +gTKbhjXk>AZ%lHtKBZjcP{=x$(p(7tCbsj%CEWbQtEEnZy(2}bi&4cR-H{miIbTg}=tLN)TJqh~Q-@H +W#<5_Y;d7{WU1~O9RT4ugGMM&dFj|La7q?zHh#>;NNS@KgkVPs^Pr%XA^^D&H0Q)U@bA|r+6k+Ju;0g +*K{gERhvAnDf~erLz~KRUrTQ{Ml0@XzySD1xmpiQp&+qtptO6uQz$5(CNg2Z^94j$*{81pqAGM!k~F! +EZ~ZZxY$@mc^$&ym;eX!35eEAqoDQ_`EF;$G6_S6;r9*O&8r?MEJ(?5^QT<-xSpLZop^waUQ&pyFA#GaR=|YCK}%?`Y=kRdx=lMM +vG%s%LoWTu-i!TlCuwZ-x8rQR9kV~(8U&1&6~!*k$#qVb|CxcdlMk}BOrZJ~{b~ +KlXYW+aQLh?NOc88EUlU8%dcdv>OG>ec@74v +i?tuKkTo4{QpNz_!Zax=E;5ncnDufF9oek7^b%2^7a2K<`Wo(BP32jFiH~ZhELPO$W}97L3pLWo0@mH +q2^|IPwgff$(G0b8i}{EKaOq^Yn!SmwyTV%zf)7 +gHmKeliNWo>D~%<>{XXNZU33#RB{l;P2;A-}+^6N%kb&5z;wfU|_~3rA&Gchu(_6o2vbzLt;l>{sF}8 +JOj`%l#cgb;yNF^4Yfg}`JRdYgzex4pKKV!r{i8nf}3nnY&4Bw0s!85e0i`}%`VKGguwI{F*fU94p$= +`_)TrFfp!n-W2Ymwyc>rVXsi+lYyc{1R;w3@$3P0c?^vaFPOXUvrb8O{Y;8FTsT9RvULmVdot;Gf>|u +lnJCpwwLbl5krS5fPTrJ4>tq)RVq74p&ItHeB4hL=4`B|kS*vgEg8o=;KR2&Pzu3X-_R +)w;!ePCnQ{;jUI@>WZA>xAPe=*hy6K29IAEe&JMf>d#IX%GFYf^I|xzv_w`}-O)-smx9prgo!vA{OX? +cxpClo1GJ*v+q8F3U*z(5wpY))p~2ifYgqIw=IX?E`uGxiir|Z56&v^A#@{b+!(0{{8r7QsF!`My`73 +0)(6hX_#Mv3!YK=XyM76R=wxILF*UD3vYq$7lE`0`}TtlJ+&DfHH&?qqY*mHa*a4SeTlWN5<-`&EN$y +4No!syY%*(Sn;J*%M4c6VNrpPciZQpX#mHh*TrEI+-oH39%}AMVoO^k8r5U7)ov(LR%I0hr%uw+QkV> +TIbRUr!d!2pw;T<}W(oz~&PZ!_*if`uGKlzkMGr>-J<1>agW!xjy%&@4^Si{wV)7?p;rT2-_Qvs_Du+>L19w8czR9c1-93ky?DZQxqmb4s3abp#5 +Cj`I~bQSL;)a9XvzrxwImDGD#IoWsugPw1%%k)%)qw3Me`^k8}_#j#k>G1-lQ!0~3N>Xo=u@7tr(%#q_vOsPrrLcCsAIN9z^Y6(G* +?j%P-v5+J^DO({)9HVHl3ytGe>lO9sEa}vLBiyUumni101U5SyCN9{Ll{XBAVDD{NPRjALF{&~S+)z7 +CL24(kWDcKCpT%+l@t@?)-wmC8*7Gt(^j`1jiy^$-R6ar?8@T%@ZNg9l^$c@E**+*nP@WIp?pW^-_~S +T2E6so!5h}D&<&y65Z{WXD{l^yZ89C+HByn?XExi9r$nMz{?#gWIeXKaY-p2~zNwnS5%J;X!i9d +lZTW+j6|E`55f$3{#WjYp^i$jZP3md_yqEAeoub^8Q=PgOLuuSo*b@o4H)E|9${{u$7Rl&YZ>&f34wP +1qU-_$Tzk{N>LjGfaND=cY-^x +n9yMj9bX~O5&Ck{40^9wgm_e`|kHQ=-kLEGhz%a9j>U^)n^7|7-NlUJ5Xt+B~MZZKOpJp^ty09Kf>kg +SHb`)K%7}8iY?5?8J8syJw`0~V%;X{-BH!BTRAPTa`3v5AXJBWw$)Aqe-RRc;xJx=la{;%$Y5TwtkEn +v!ER3aHyXWwA3D8$epDyD+hqIbil>Q5kJ90dq;&s&pjCDxTv4Fw(_!FRATuj*UFj+DR4gw$I{EZFS^7 +3xpy{j`;z8h~fv)HarD&ub#!K2WqDAOcW*L!lPhNn?rS#=UB(gz6Z53JbBx2ZONysCas9fi{RBIcF|M +v;gz8L1WaGOvr%J$to%JJ8=_w!x8t*l>u{S#{;w}d1JuWSVdDFlZh;?vROa8Ki{6a|a+{*v8o585Rg6 +LJ%egz-(s8QUGMv(GKEH!ibM7-Un9M6umD3Eh&0P`Gh~m6Lq*3u7O%Xy`^X)|2gq+`sb-M79NR>zRqR +QzyH;B}#5eUF$lzE}`3?8oaqP#l$wNws9(STU1v{ltsJZ7POTOHz$aF0BwC=8Md`Yha2zuBSjguqFv8 +^9~?UqF*@<*_oX_1IylCezYdNC_WE3J)%ZQP#?cKQip?YSC@{QgGw*zaZtFv?v0u|@Fjj2MDFC+>;yx +@BAkEPx{bL0A*KL#jOLL0V$TPn#3c#1{6pg)DUFbnrgjjvN@Ya{2%o;sT@|Wf17PbG#@{g0lv!pJDc@GW5plAnrY#snx}vQq6USojvt#jJ{6{UZIyt1HxgloW-OdU>!IqLG)07}LlfvBgqjI +&Zq=Es68(4diBgHWWGlXX#Mfo%iNxAoY=+i;H0_qVss2a`4o;{0(l}alXH0%PT9R$KJAgnWG6R +W8dY2&n855#pV~nS5;w-t~Z4E4WDInYTwk+d?O7CzKR*WUX*Cb(Xs+z6Mzu#EvlNTio>z0$P4e+Id_X +M4+p-ma)u}t;f^g$6CydBSoe9VP~V^9=Dc9M%tSDtjCpg<-P6NJD)nq=LZct9=g|`Lim}>LCf}=h>Yy +GuceqDK**9_!O;aA#6YzIhT*W7HFrC(XuvYNOrT69G3RKf!2*Xc6K!0g*6~M_u>Hn~^SQsZ0{_!#1o? +_sC`WWYVO&=di1<>YEvCrfuLLa=StzSUaV?~=5#RI=RTsyC!ndL8HXy3DZg{h_WO@htgSah(M;>R=19 +ZwCU4=(ly4Aj2xxqXGcjU6FLrVxiKqy|! +rXz+3P7iE(KLA&{dZ&&Y@JT5jNTGuikAJV3LZ*oeMEm+4l$T)pf9=4{ +2%>v&ex>fBVyIBGZZv-Nax5)mtUV#+8 +TN7rxYaz0=TdqGp27tC|9JSTewn6HZI_wSxNw{Ge6>n)bJl#!=pzV_DTiDzKk*(im1$KnmYIxzF8#cR +*9CYnhxKr0Ntc9hE-NyS5QmO5{ci5tXj9eIKtD`pT}V;@ytNVG1S!CbyQeOcKB-E&~u+h=w +e?#VcZ@nl?>YE_#AnO0&`;wfY3X^)@UOZHw&s!Fzh(y +n>Er@??%p{{$zQr7zE5a-CJ$as=T@275o`@w=98ex11cGF1?+zW}tDC +Sn-m+2uimFN&`za4{K>;5P`0nf3OZ?bmu3+T>o(g%Ir5Jpi7pSUPwRepkr%*OD%>BDYKWy>5<;4{K%S +oFw?%GWy>i_=Niu$`BPkKJdUsR(0j=j%+f3mM|_{V*JL`ImTFdQVw6%+{sgGdU;Fc1N;b+yCR4KRU&D +Di14C$!V96^&LX+XSL^)x-@;lFhbAjw=-<)jIkXk0R@efQt!I)*w=CKEb7h$;zg#hQWr!=z +VtWxQ-ldk;)30zGZ<1KQbL0beyB~D#D@8VXxY50Rau(Z7hTy%?WyQ$N10mcRYLfIz`wX&Y*j5g>LOZr ++;nJPquKR$yj@I88O&vE@h<&%z72=W#E=hbR$}24pHS;);x6fNS(_af$7kA;SHD>{8!UYtyNte +KU?uzahcw0Vx7p__tuo(mb7M)KO-F*=s)@08$ZLt0(AN8k36JYN&;rRFFkMdEZN_JV~U$7ZTRN_rw=rl?{bJOMv|jBKD$>a)1qQ{lZZm(^up&$xk!Gj>#Zb +BBC-X937ZSQphe +YtS7;~$3OCyhOFd1=o}3?TYlEyP?De{)-BY;ic10M4OhMfV@5Am_H<=f?qW*_?|@*@5RI+D*W8yOz+r +ArlvdJsF5o0vp>L%PmM}HlcibnX@s?>^CQE~ +AcLn7+t7h(12+ehh45n1HoR!Juxm_sN&7p&%nZtV$D$%roO@rwguOSCQZQ{o7R8}-nYKHPa4!K#dxee +haQ2HhNT;q_th=4hXfeOv$JdK0LN&RG!~=DF#0U=8dh&5ZhNJQpFp#d4J)oD*(;Dkww{+3UyM!u!6MJ +|6RWZ~fwe8!bt+;}_mO%$qTkoOsaR|tN1PLIJZF9sAPV8d +_oKorY(S^~*n^9ZB6t|e8=pY`iAx>rrl!b)zkGz?Vukm&V +%0%@WNRKcmc#7D(@Y<@&h|X+}fOQBtdJW(vqu +07}a|`OJ>X45Yj{t-& +!YF9#^{&g+}$9pQN~4;)bF=0TRPh+TpA+Zi0#ywUMaLiynDTPtt+*|sd|TS)fpwPhI2zgYU`aPAL#ev +Q?B{ngJ}+b{&eFae_!gnimO1ntfeYsiz}TS5Q{w?WK|+8hxO#+O6!-2J4wga;r+tXOp@*yZ3 +36KZI*DP403tDLqkOy?crHRPY=-vl}Xv$#Y*+Wvq5A~vpv$;=#ccHU%3=14S%$r!r;XpZG9=GM1=*PIw +1&+I2u+DbrlhLE^2_4m{O)B~E6pHBuD4lGgPrYgAj-Lbqxw(v8=BJ>K(-n**6eqPSiNor4 +$arC-|GLMN__x}w*siul{`q-1iM +j9dA8n;R*%r47$gM-=_RGA>JilwW!kpsgtIBePf`JKu|PqVf2XkzXg8CzMf7|FUl(d7oEi;kS4pb4;a +oR9;6X0& +15Ct>OmUspqFOtfT_3)C<#<3Z!LZ}yu@%JDE2JXN!EOko5zcBDh>^wWH=sXoK}{TE`uo-1x9K=;=?Ph +^$94Cw>89?oZ*JsEtEL*v7e@%H?*vc0to4wsz|SdzBpa(1{9tq1yjl@=7xdG(Ojbg)Dxe)8dEI6@~_~ +D`zJlFY8Ti4qVM7D{jdM)!(Vrk@cdu@68`rSgfQq4-v0NTh2#I8)8z45e?qq}9jg0*r|hv)f9I3k>M~ +5$jYz$o_$Q9PV#lkD)A4`(wL$j(=ezl#yY_!@CqLpM31TP;ZwY(|hdy;}im`nrlh`|akS#5q>~MmhTS +a(9&6UfqNQuN-vVtT(w>rePBmt7`x(a(TV?8B^Zq2SZwHL2m4X`rC-9!WNVoLI3X>GNLGh;4nryfpliS4UiiqnyZEZs629W4JcS`-)p;*{5_^)qf +6QX_LM{H#Tzb+)~wBn*H-hTB7lwIe0d6?PC!#B6~uS^po=8yVS8pmTz!p|=s9&;NGz?J7CfW}?E68)z +u3eSn?mkEc!WHgHC*4#r+s3$_d%WU!2G=;Dm4Q?CU<+N3|IqhqadfZxL9g(WSH^XeU4yjFdCOj80qJ0 +rGt#fPiEFk&IRloHB&Dw4Sb(suKoxTsI&K>W1D|;+oY +pm)IpT2ojwzrv|zW6F{HubMaG<%tkg!=l|tNy9N0sL6vm{}{02~3SFhwCz@YB1)x6_TZDc?MKdqs(y1 +$ZmRf=StS3isJ4L&ixyXBO=ztNVcYHa$f* +CpsOQ~PQ#Tb6!?52Jd^CyO3~o+LFl>TisKZ7SM;P<97{y57lx_(plb1erTTj8u +orM;xk;B^yG}az74!#)@`TB7u9iP~s%g&*FXUhJnoH))E0(O{Q#FQd@s|7b3=zgBhY@>=jaY_1*_Uv^ +?P;wgMHjw;_|k9&h13Ka&yaQ?4|_0HZwahYOR?oFl1y1KmTEMH#Bw>p8gQWw9EAD5#);}T!H3i1NEDMd1QU5N*?eMnnF-SX +Ye}XQtOAH$A|{@o(>=o(lQk~t@d0Hl+1apM=f12`o-|7$j`~h@&$GVloJZ(Cj7tzZ1=hkWaA3(MR^>t +&h4;leXsTxLwWfy^aX(Y1r0LVA4`!n<)&V5g`VgURrODg`1!#7%78Z!soAq4S^p%V`UyYcbOaRORl`KRCfg7ts@sIQp(nvvcf>xfy1v+~55ujv +4I}@KKlgRoz&YGh$zgC~f-8ouFt`FPy4y%>nnT$pwy>f%o^IV@7`gpiNg_u6PTh#w2qdv9nXj;mL0g? +-<+AZ^LvCefYdU{;L +wC&5tu6RP^PW6y&9NMQw7AU~J_<%xi<%pN#|m^7|b4ZWck$Ukc{S3cnjlY<*#~{!uXhx((DF`E9t}x4 +Qv;yq*8a-2gw{&QEvqHw);Sx)FUplj!5wv~+!|y1P@<=Z3D38;0H??3kY@qO@(vR$;hv-(f~v&(rOK1 +R_NDiwRWjy>MgQEbeu$tdIbQ+#H%(w_ZSyOUUb&$E9)o(zD2Q`6u(U2bcOCtvXeBJ|g}GoR`L7)8QUI +3ve8j<|^aIOw-XqgHz$PKx$3qr@YLP<5`eKJG``hY70cg)8Sd}s{#moG`iY)YY=6lHIiHmLoo!{G3gz +)Z{LWSaqgS;QS4!A=u0CU5i%>y<`j~53ZbTe+sy?ZkDR?$gPD#DtKZZytdW=?O&Xas--XCF)hds@HOT +9jz8*-3_2!coAkf6yV*w1<#k#^E*+H|OL$CKZx2Iel)#B^0=i8qY?yvCj1zkAcGkBRl^Y#4Fc*WI`dp +;3mK{aXak1Wvgyj<~b5`Mg%%KT(8xEBZ}R2|gINkW-k?%+Mg{u!m1Si;2ZRN~$^*9%VA*`Ue@K&;5o5 +dQPg#y98`D94q?2G=k(%VR9J@6b6aq2RQ0oW%D_T0A6W4chEG*!cCQDv +5V27IrP9zeIR!22hiS0Bnfy8!XFma{apE&fW_hqr%yPSEy_s$YjH|BKW7tReK%DSiT#&k$uLQrOOIQg +|ad>$3a*Quk&{j%r)7=sRDrAI~vv6@9nO&I5=>AQ~Y$NpJKB0g?dGef?z;nYl7^MefX9RmZN~hqWR?f +dDBL(}#~a+?_?YEFm1sI;AAQl9kJ?KLX=C`g4n*2rwgVOOnr6&{bS%;L6;%c++$wn +klo^meieiSTA&$dldgP>OF^r3MImg{iU6?LatPPkW4R6R7=VH+h^-K^l1OUvN?w74%m7jXgS^eIFAM= +jV)Q?;-y=!{$XoTz@j&1|-$TCqw1s@|M5%?oCgSZ1CpyTeuK5g~t&6CMr`1H}-Al8V*AVz>3vN7 +6YT}wMV7Jo`nKnl}k4pq_HG& +;y-{o291?0W7wa+?o^{x%@|lZVjP9nW7m*+KkaS6ITe)?Z1mpOsh`9tjiOZw!daY=fjLU1eAPxxy?@>MPVpE>FKx&y% +Eo|sadGP?_t=jrkM+S70!(^NKv_d{@tH3!iniyZ^^|yBz{E)xoAy(BX3{%>SOlr_VcBFX`H&#oEk>rC +kO1sjCz+{5D4)~k$7<}zYpHXaP3ukU%0qLt2MdB0VoHN+UDCl{7&K0?`_V8>QMR7{1yBG0D%g8Wa?tL ++5ExA5&Msq_=YM+XCl*Xz&)enLdl|pSowv%YK#iZ2Dl&oYf)IOwnME!G!GH!AE=+gKf>3=xO3J;HOtO +G|;ZiSg|2Y?BvpMdG#e)3m1`qxkV0hve?BN+^57#ia!h9$|*?Ri!lA-PTNPJqc^k_4QBC4iC63O)2@+ +7D)PNziYMeqrv)fqo>=@TUMOz{#?0{21M!g#fx3W)rWhEQ!NFz#fVLIAMRK-X~&UdIz$Q(tm~&*inmE +phyIQ{A?pTn^G~%!1i0pg5KlG*RsuwXXRkwHlCbP;KJC4#}Bdj@kJYw{=U<=1M2;d3n&kdVPQL@}Ph9`u^nQLC +L?9g=;lriI`=ZycGP9<@PWfp?gBt+Hku*19NPzEAo{lp9Odjy(H^oF@;3oo?RWn;T*`xZhnXb?)JrvD +A(^7q>rIEYC`77JhBIJ*C%{_?Snn8rv>E>$J%=;sA^oZcvncp>_=XU;znAzuJJD1icij@2W2Y17CYkJ +4pQ==3Wa~Tc5Z>>jZN<9xTXy_gGbqW==_0yP7T)dlkbhzIUN%fp&AA<$;W+?8^aTXvV6UsO1qPkM?{^ +O_v9(`9kp8nJInBTwiW$-H@a!rJP$RtpOUtZcF@XXD#6}g8Gl72$E1*Bt7@v{vyA`p?7f|E(NlWuDdl +lw^5RW(T81Y1WwFsZN5(e>!L9erb2b+R_?Kw+sdNV23b!w#&aN)9QD)zd#xZ_l&dw^7=2ais4h=;<+c +4NMBt_(d{JUeJPY#Yg_@X4qD|gIK!ZWI`gS~$8;xB(#dR<_D^N|35=O5dXqK4X3s`qE3BSv^eMh+R#M +|Cx%Sx3Uj!&H#*f(21wZ3?mj4Irl+A*R4qJ*FGpbWR+f$RwLb$mbG_MXhz>Qtm@_uh%wSw$?P +VaTLQl{=Iq@0_e`uZte>A+#@CaiAZC?U9Rw&60@pFfS)nChX;AhIQo5IR5Rdx?|RmS;sIG)X&msS>I{ +D*)@`;rpn?7`xhQ7ogp19bazQEsW6zC^~mM}BFhErajm)E`=rB#G!sMkvBni#8+Qwe&z?RTeL`Q-$iW +w4t?wGG=9Vas+;gZrx%F{VT7MWv|y@eoG+}A&Qx!Re7`fbL +YGdoy&rI=W{UeW@Zy@SiB+^N&%rMwP`u=O +_q;sn{Gt9jkJ8e-3@dFuSSuht#^q7wi<1= +us8!Y9KrZ82JVpE6t(N=)+;4jcM;!AW6{lD9XR1_9=FU^6EK@Yd%VehfU@=Xb1-L+6MIkprr1&a!rdM +IwzPxZ|LBSzR;|#NDUpT4#SclR%P8QBB~M1J>6FWR?(?tF^~o0_*y%fl_W^UdBRK1mT_79$Dmps+@Dj?8Uw$F9KmG1U$bx>vIxv$)Vmwg +PQcP-ut8K3G}&WremTwUl>Z2JWc{xBq~BBMjV4@kdz_x6t2aBYP*j=2%1H~U!?`bAfx+vdP~hZdgsW# +Fi*2c5i{sd8Sk!+HH+hRT7Dm$@G5M6YRnhrgeE>vGwC63G`5U+qZsKj2*Jft#6aZ`*~ehS@Y~fOrhT}_M#4 +=?Mtpo5f-;-ZN)2(I*mKXm{{7H&rWd{Eo+{Y)Tj!5a%iUS{fL7lCigTqLLc~Ew@_TOuP3IxKU2|2>ZO +mLdngdO?2qAzfA^2}mW0ValJu`$V+?wY@~y4IXZy&~Zv7EBldJMScp9e(5~bOnQ0l*Pp08l_Up~k8a0 +XN~I78wbgM-izf@MF|)G!3-EHWQSx-r;w0;Z}ombDRKPHt8wwX+<|^Vz%hE@KkHiapd|XwH2WQpDKc1hk@;KPn+J~!GrVU<{S%w6=S5MhfPV`RamN3+bwJXdef&#T=R+lJkTZG#GI8&+Tw4^{oP=%HlTP{qhSXW(y9^!kK@`a)FsieBuwm0bKa&&8A+v +?nQ@p3lHfhu#r(l-eDseq}Vd<>-iaq7?mXG!lv1wgJUq$9ZS+C~#aU_{(#kPQ&QbyHR&TFA{~L{_aim +8P9{Pn>l%ZF^E{~GwvmeXqRRRq;qN?Trhffr-RT}cds(MFvo?KRl_hRM7x&>f{{{B#STS^B#P7-daR= +pik$cbqi&GNbE8m_xgxg~N2rk&%x)q$?t`!<@%7qEJM(&;d0FwYK3u_6>YfQ=UfS-dvZ{d;(3xiQ;U( +Z15mk)vQZB9mQ^XrR()_V}9jLH|sbXH4P%x{{O60=Y(7CG-C{umx-gpGnY;AM4WDwJx6Bm+H+51qA%Z +VSz$WN{os-kR*!gm&-heL13&H1iA;*};vPCRS78M-3&MSdDIB4ro!d<%>dOdrN6J74SmLRO)+6L8!_x +ED&->g$!9-S7^=q9-^GV^@^9|QvD`V4RqF`b7E@m!V-kPxM^%ahg84pj5ydUm* +_8#{Uw?|y)pt4io5AJLq>R#3l(KBOg)>E|_K5+WLHS_HyCcey02E9*8K2arZ=PR3mUG-Is8hFlnjRGVYe=4#;?U)s@67$V6o<-42zV0piNgAORbFPrnk%Ep&LryKqaeL<{mwyQO|dm+^@wo +{9`N;kI4>vC&C~ogAQ~PF@O1H~9W@nJfFa21eqyk@(GIwFac&x_W00zgJ89xi>z|MRz8~yEuSw+fQqz +m9N(1`BySuUwcKY%LznDuZbsgt;qa^wm9$k{fpyM1YRx*``r_ic0B6)>oRrC02HT+E6%lUyv(bylvD! +kTDimXKVt{aqG?W$J9ecOSE5E0#+%(RL$3EeUoGZ12van?T$nZ+lI^CoSr)=vnh>YBG!qm?2}_rs9$? +h)NncgRWInV|Kq^A`S45KXVk)uVG*_FAH^hTmTphs4X?fbaaxkh{}!;AOG9?0EFn7SFc~5y_CyFAdN4 +CdXYzX_5kyV(#&cVqO?=KE>@mO+2@;8{+2UJx6FYd%PGA`muA%*f*eSERT7VlZa$Y-Ea|2DV^J6cN4l +UB~HnHFLU)fF~UdxICY`Js%&!~9vSk!Iyfw&Ipp-GjAF1yil1ls#5mUL-qT6ZYKQq8NT+@4-!H5qSL^ +#?nab{AANR}g;EIQxWgxeBfoyb_%gfTz<$H`@Vfpr+`nkBOoPJYmG|>T>HJ&BdS +-uhImZ^DQa5h2)U$pZCh*Jf-gqEKHFcC4@^IAfKh$!srJ}l|%`^Q^puD#Jk9w8Nqx28wKlz{c9{$B)U +kk>+4*4N62V*FjKxvBPP=e)f5~DbZB0zE+$6+M8)`g#Lhp(kygl;mKO{1KIV2=RA(2$@uBsfq{;Tfp2 +Xy%{H=OrQV!(u@nAB4AU1JEI}>2j!TY-@|J!DFy7z{Q~30hHSr*wn_jUzCLFiPmZ^j)7#G+e*LnIVcW +#EeQvnyq2hIod{Yl(KZylbz<=C8?d`Ufga0R-g4WfII(#o0v!_xL_pYYOG23i?P>0t8;Q;9J0e&=H)j +SZW+c$Dsj0KSZIO2}FY@HiGHcmeB<~Au0GYM!_)pob1X-1hmFE7?#xQpGiiWNMHC~-9SL;h3BL)-13+ +VS-;+JHc<;!DYKAVe*1tm}ZyvOlxU-|2WA#i0-+Wlc=r=di95GT5OlF>u1{TI~5YIJh7sY)7Hk_V%O( +<-%<<1IcO*vg+cB83ymUE-JBoy=Q$e|Dbr+BcfUA9ht}K~s{~m9@V*mee2n$06?VvOb-|JUr~VegD>) +R0|G7K#%impPx%q6H(d}0&U+^siF6fUSCcZeDdIBPU3FI>I1UtIn+$v28|&p8tU|oOD!SlWF^9IlavU +>mjg{HjcM1)XXqVThuS!H*5b;QCqJ|1ygTkh`4T-(Cz{XinpNDTVR4&vZStKEwY}Poc9?rYm!Y#oXx{ +nkcoriIu{GVd8|N?%WbAauuGH`M$+g_LWK2VVQ(iksn0YnCLiAsIwViu~KEjaJdOk~MY#}sH=AfwkS` +QcMak(WjD!dwF$3;8hmdf5PI>=0^rH>@gg@>@OXIP?98)|j$&bhbKBf0AMpfG4p?fXn&^Xz&JS0M1<+ +LYt|Fw@m=vb@NSk)n8I2UK}wgEX6vP@3W6ajx!SkYB1Xa0jt`3=dIh?JkbYi@pNFj_X1Z9#ZwhD#>^e +JEIxuY~kM?t3$=DgweKnk=&~q(c~$;#b^bI>kY=3*o=4B8{aFtyc2dOBY{uqt!NdCYCVx{=_FAF$d+G +b8o~O44#!s!303O!uKIvwYVUP{UghtA7Y??VJ#YkX;G0M#N6|D)n9EMoS%aayQ4k3B4n-z?3tm*QVID +A2dYAvI?C`a+Th{t&{JQgyhbyxQ13|>AH*~EzK0H6Gs1d5op*VDeySo$0Qr;Vq_w|np{^^ekGz&$?yW +~K!HyMjTtc-5deQo6dTB%pp=LbcU`V%gLfQ)B)6Fg&Od8Q*ME62*cC7{Gfe>?j;2<4h_$bKNgNbzy$M0g*X~tZTi99 +e5Tj-M@m;Fs>S?78RgGrW;0C$z-c}F409?7Ukhxn%jGR+%ujkEID%Nve)hYmh&j2Xeb=(*K}*%p;R +@7%HPvc3M{{e+0*zYf>CQTbs8_4^^OK=XxUfcBD{St=pdV}&N4nA&3vQsIe3eg8?84-e(-O5%F|~WW8 +V9cztwR^ps`& +1;>PwSIXb$Z<~Td#1m3F67TcTG&S_bThqaikA?%vT?p?%>LCBsY0d8k`4t$6$YKfJarvQ?|t7rCOkdK +>0P-)dSOK-Pt_?}w^uE*Pn}6MXU)FQ;mQ(1&QXYqbumX;JdZ-6yawwS&Ak4~j`$AmvgTuDd2EGLV1AJ^xJQ`{!A&uJ +j2H)W5k4;1FjZIkQ6MsG6?_lstwrwnuM{ey?0CNx-gudhq1lViq6YAoXmPx|JV&9hfXS6G0{cw($CwBUq9>TmzYYxGlfZC0!d(13$ +bMi#UvBNFVgC0nZST1;|V@D#BPeWENV6zLQ+DuLM@{>7*}*%By1P?&?Vs}@WWkj6N7(W$};!R4)U%qaLL6VmYCq=$rfd@XI_+KX$U1 +`+EuW=2ChBGJ$CqPmoRL0+~c!mk5-EBsf+k^0 +D7t}=f<(4F-VG@Cg!;|z(J!C!B>S_V~bKE{)xXKq(ac`ueU|5z-P!xVvA8SA@GGNWK&hHpLEan@qtq} +hG}DR)OGck|PxE+s*abbftUhuDi>X_f5TFl|cyV8zUIQ$@zX2*Y^K^5t$+Ca(qBsGOi)?8igNy}R@l- +*}m8IG4gPv%hFKqFc%3#T40TSgc9t#3SFdjw{EZ7cQjqu^k8f+gkRYDiTN*UtwX#_I1AD3=(pJC%;ug +@$ei;f4v>IyW$u|VOf2Kf*=+znG9RwWl9{pP*v)9y`(5dhw)&RPn{E^;LRKpby|9{*x|X)z`TaNk9gH +3=^NtuER|TGC8rOkUOB^U{60)dd)N6UKQ@mBYo8_sx0K0s9qFALm$+&YN8R0Xa30+j%2lhabG7eJB8q +F`Uf7#Fb5^Uzq0ShMZbRX4_1W+!l@INyV-jLte3MloN<5T|TRtFMaTk40ddP~|4ND*bo}cmgU|TzkE) +&^fgQh$$$3t3T%G~2nA6LYq1?g*9;~og2OEb%*OQ}Ymp3wP7mUyZNt1;4$=k+zETyF>6- +Oxmdz@PXVUAa~@H!l?;-to!R&H;s$tQ9bh8uLK7FbEo|#IQY|3{uWL_ +xFH8-C}VolBcxy-j^;Lpl^9H!&@_;Tp%hReaG%Q%qRrw2rNIaXm_>V37;Nap5kS;z)5(DmQ4F+RxJ{?N!Z(fq!P|PzF#&XPz!WwH7fVubH#ECVc4D9|0Lf6;Hdq +>M+kn5dw-X5fr(N({I4%FD;8b(@@*AA)WfQ%ewK@#^?z#h}a^@qdxh}$!=k;u?oU3|YW)dj3$FFlI2m +i0Uyd^Lm$6q}agnuY^K{8X|>j*4Oiw0K!#Ag +a2AiR6jiFXSjuahqu3jTj&FCiGH+?EcZZIzLkf7;#B;KQRM4D@VFc(hPm(i>~M6w_su&JkD!i-$0I~e +Mh-RlNlO;>=?^%?s7s;_&zaaIMFlf>@Nm1RsHvN?#DzTg$+2(GY@j_)f^+k(KBpHLw70GZB|C2iDg3y +KcS@F@W4~$lw|t+hQ0?ec*B_mGcQo%NQ9GkGDdz?$BHuJFFT{lQV_&|PR{|pw#cM^87;K&2g_32oIV| +obuqPFpvtc4{{T9pD^#+hLV`f)ZB%e%TLP_;VlZr$~>lXOW`p_W)@-%Rc5Y;JL`8FxDCs=vE9Dh00SN +HUIn`JOoc<*0tq}QX+{u+5&Z>xgb*fmdDRL`&@JZAT!wEGk580dF+EAL0cDBuQn1!US|j|YBuJYu>Pu +&x1c3w@5a{3*yDl{=b4rQMmjfOm!4cu5|3=eP8@*_1AHvuBQ3yPt#^U#l +ME06lfYu15bbSdYtrKiGqRB6p5Fp`6U^lY7t|s^XaN2Y#os6EWz!;3V&sp3k#{%S0W6YU6n+8}!9Ig= +>tvl-Zn8E!<<*+s7-N7}*h&CHkVp(Pbt{amL +=wOz2TWX6dYEpGDeI3R{&`pv4yYuX0YVIf5DA!QNzu(qWwUspL!kWz!{(p(Y*1}U?k$7bzKOpgUa>IWgm7#yddcUFaHUA|lL;s5P{s}5W-^23fsN4bL%@0&wv>#C!_h% +?sU+4N}{o9QA&jwUY&#*fC1$)emi^yw*!PZT-ru~BG3&rLVjy@r)%a5765Fv3PFWeo!9B$FM8u?sThv +1Ch1#+)!GHSw8@6=DRh|}Xmw=pY1BGpN19Ac+u(B9s7BDn2>bTv7>=>7Hb4$J;hsny;e*y^E~qemXYV +sCpRo~DA5(*#NJL4h40x8uaSJ2ZZO;bWtBn5O62vTX)&Po1)8cQIHuu+=sD|pGKyn)nig3#9-SpvFy_4<>4)C8LJGjKPF?8&cZ!`ZjyM+yi0;i%$|%HMCAqreqg@)y6-47KAlPi +MIwwjI3e>2Sd(?H>#w&oWF!|jtex61Bv!{F;JN@+VAAHbglqD#NV;GJmF)& +j4sTmskA)^n0)*#&!e{2YlE!>oS)TUo&NnrDt02i~*O+vS@uW0j4At?ZvAXp>?GpZD`X$?_exDG-|Qh +=`@aSPx4V1AX}g%X=~BKi``6@T2u7E_#VvB4{-qg%9ax|zbK8}?J%pejKFd=5A4=e8a<&|0FKI3fdg6 +aF^qP;|jkSvuclN)sYh*4qEsoySjw5hwAdnNonG@)z@rEk_HL>veUXBmW~jIX)dp(g2dI5}Y`YWaXGH&Z?*#gKD}TF_f5)MPEBfPgjN8ZQj0rb +g-;c^n5~eKNPnxdxns#!7rL&0FJRPU|%1q^qmR?CDz7I!eKhcU3rmxcM+N1k>|d$dueu%oG0BdT8PrG=spmDywK?^rmbGQobT9dn|+G9aEbEE>q$g0B^9OPh7>2E$2zdi!D=jXFrEa(2&^V5MNJL17GK7$~$%-`eigLjGvn4m_qMXP7vcPi!EW +lHe)I_!koS=6X~-vQOt1i$Kjp9;s{X4?7pHRQBKM&Vr9Q^r`umeCs&gww9PTk+J14{Sln!noIH9HIN# +o7f`}Iv(36*E6~|HD#eY2V@9nr4|ZQsBpsfwR=vhvm(T`Ezcm?*gqiLnznIHYUc3xbEasF8-PJ6iAzH +}GWf!jW-Lf0;_Bx}Ac1D{=Mh%Q^q?);6IOc`C*kQdmTrmtCk&EQsvbE5<4Lx*a9c%Wi`K)yQ%|5V~VY +1lPTVAa;pyTW2Sx4N0^Y_WnqicD48rW5312i&7)GS3VhfIoa=Nf~7hf70f_-%S;@qjs|C(HcvGVJw37 +^C;1^Q!&HOHl@Ypi*3!bvIpm?y`fL>S-{;I?|9q?RdjR+n +Nxv?je+)7I;whg&%wLE905KfJ5G>B(3=SsO7>4>(wg8;ID9{sKAz@v#xlLU_ZX0!M3=$ZDE-%>zyjCL +jc|hdKgVuE&-&&b$v_;e>;QNdMB<&;1F~K&c>jV(1k3C3W>-8&!hK8Vh3m_^42)ME@W>ev0oD8+V)FUJ2<&@^*&h3!hnOGlOx$09@e{Dh}v_lz{|Bp_k)vky>QKSU9CxMB +%JIr)TATrZ^R5;G<0;kSt?SsTP8Y1%CI$v7(1CcRuvD6FXm;Q;CU*ToAB6>%+O%-C98^qB&gzh`D +t=m*h)Y#ZpFxA`$`h^)Sq4)UlX|C(&&Dg)xa&Mkqt#hDjTY5*qSs!kv+ILPmWJ}JZa9J@v&OA}@$T7v +qwS1mb>=+MtaE`c!viFLaMYxX{H2jdsM&%gp+4HrS^G}FgCWRHTd{{Ue?Z?{ol~fkjxAb6+KAlGejP` +Idyi2|T>Q3y2XW#G$Aq9BcXp*}5~P6>occSD +JP7zXdVhY*=`;I(NmDei=H4^>tJ)?yk}8S(fnTX(HY5N?}+!Mt3)}pk7qpp6rFDN8vUhQxA7-Yv=o)X#|)k)~&tN*;+pUWGvFX)8BF-lkW0<&cr!yWP}Hs+k-1LJj~b4c^rR(L`Q +O7lfkfma;!`=DMk`Mgj4E2QTSBr?krmwH;SOe+;@bJ@f9Nq`f9b`6PuaQK6W*60l$m-PNFqdZenxtbp +SqbJ|cqk41gR8n_YG-J6THGCytY?=$plS7>;iG(|SZG8^8%0u2N@J>5MvXo7aGTWc7BnwSMI$RGomq4 +!1oYZpmzH$}|3wha*_re9g=Ti*7!hYbo{~E*`+qAyD>p#YqUmgD$xO_SGdt_l)j$i<$pbSBRqyy~J-d +{|D^%>pVR6yEq45ViS3i1HZZP*2L09oKt!md2zbMlg~8`mKKF-cLNfLmEYgn@zTmGpo-;tZ&+=`D19# +W8%NIqP-sUy&DP^GG2wpkgB!;NHBl4|H?9T<;Tr3k8N{)@xuSkixB~2mHfQ&@NRBA-AbXr{j*OLQ5%Q>)awH~BNN7yw!9p9OKhU(f$?)Di{rZ^&ZDwcm$lzyBMmxcu@ +#TRq$cLDu5s(gMI&=*ws>n?tg1@&1J)FkK`^zsqdPKboN!UqxwJJOxVx?L{po}(Y4@gh_-4`{ +NF;MB$4>loJMv8K$krnpjD%vB}4%)FqcDKSFbZ%E6=<9T0Zd +Y>-Uyqb(uMk|8=}C@#D!Uo8S*c`>2HJ$Cvuep&l+v%t7Z%>QIWvALS4g{sTs^@Ja)9q_Yqk#5vBx6RW +)0pyc)w>*|LS)z^Sl$mK_^DACib^w7lt+SeqA1>(eqc@F!JXBql$Y?DQIul(}DA|K{gzjny>y`)d?;Y +h8@2(uTGSs{$}TXD)oT2>_khDWze4oTezUTP9zM(pcZ1TD2X4dA*>NBo{H8(s#wTL(V=r$vY}a9c4StR2^ +7X;XUj?ul-eNB&|BW>~yqaF}Pp)x0WqE7tFr1AKv|B@LI;mn%5W%^03iFLG#;Hu6d?z7zd)>C`rFZVP +8(Ff4{H)03*R`v?_MOLIzw-pS8Uqk~U>`;(UGXcx92RJylFsC$O2QAVq~p7WOa4&hd2nnU+y5SV_w9QyC*%$+fB2#J%Gt0$ek6sD@@1|drW%KNro6zD$g9Va~+?qxpqV{_xk>S3(>tEX5Izq_}CkD1 +S0eRfgS8cZ3OA#v=bsW(NCrubU<%Qb$R +6}Eus-;b>1-)~)DlvqX)v}Ybw6;SupioeX +@8{ra_*(98zf=vyWKRQ~qDM^|8>%vo)agXx~Q*{zDFk@H}ekm6&3YQ3-L +WgvRzxnE+oW_g&?~Db1R@g|p-M?K7efFGxQ4oRSf1ukWis3lPp$to7>lggtJ{K@CL%`PItcftoA^PB{lR+rXKj;gJhGSt*m^#|Fz^|xGgP|-Bf5Ppw+ej_bTG`_+zo?)nT1s?XKaKI^{U`fO+zLl`HG4VdXOnIis_@K(R1O&OQ0{!?ivdVQTfMLiFKnx +54LxPZ2@jr1rnDM1cW{b;a1$k2Lw(uLIUu*8lOE+miN=A0{(4R +VuLK`trm-2qFHzdX=xs;4iQ6Ln<-DG32LKU+YSjZey7w3i@;y`(-ARqyfESIbg!Pt~h80+?APMMEfvX ++71r>k8cJj>r+9-4+oaQl?1NbFWelOi4Z&(e#~Uj|Kxr(mI)-eEVu{uV@HhwG>_aCHHO^gNO3T$8Ep+ +q3I^uWO$uNG|_r+}-}?*j~Ya3`ZT+Zxj +8MAJr9oJ)ee^XI2-yct`j&T=5EwX}J1bdK*qWYW7z%E=q*J+(HtdpIJ(OKf^Gs6fOTc45E13tOr=7zV +JE4zpEg+W}ggk-2pqVztVX`bp|tz=3)?EJi?|eiTKN7P*4i+PpV$q6{VZ8R_tGFz^;BkJ=5Apqxo;+D +mmoMl%SVVW9+=I0ek88a`s?J|4kAx5Gkl%$1kT!{Q7=;tjMS8`dtGQc&B~V0EK?#Lro$*_aF}RIEb38 +W>`4N78y{yz&v{m!w?SZ`VMRNkmtkgQ#{m{ONvK1te_>o63_A+`e~tvRT9=GYRctnD~^*>7A%^(jx(1 +JRc0B1)(yx#A0w8gjuRI7!7B}JS-ZUO(o@OnRbyFAW9BYu4&q)EW&+;b=tM7)y>}g)SDH?~2MNM(wwJ +p1ol+)?7sDCiy)6sPU?;q|veYR#+?zpNf4e6$%kDEI=+1B#G>p`vQENbVPb1vjS-XqHfFq&-lVn<9cN +hxj}6cbRG`11$`9@!wwiQ=E+Fik>&_+{9o%nDG4h*Sdj%w +!Keff^o1b*fZ;wx#ubRT6tq(5O=|~={up#M@huy6g{Bq3*8i`Se28xI8{7t$#6QVjCGkWVn5bOuD#ZZ +sVcT|Kn1VS+76oF&wfbL43q^w&Pc#MK4m>0_I+i3g7%G|1~;(1-s2a!Vu%x6p +aP?aFXD^pq+K6%TdUh4wp-cC*-^ +97x-L123+ZvC4>hme@jZ7 +4uvEad0=f`EA}2Z(^gt)f{%lWVC@Wh$J>&dOy06c4quw}pC2NE$+>2=o=4=3JWUs#d7TvUzU +Nmi$n(kvh59@U?oWX2^s?lxE)!Z02JM(uj;Nf>eIBU@~uEOyPyi}-h6nc8|-T@K0~Y2NNo$7Wr +HV6H#{Fk}+nBV6wS;#Lavu&|ALD=PA2iq035{u8w|2mqnp2^z5;dwzT7jG@WHoBEuxAcR#WF_K%xa*8 +g#&on?IRsed^0N~-GaA3N(m{~z5IH)ghF@Bj<~Q-ts4E3kaz@!&tEmjAcU^+WK_FAx0f$Uhx#EujSTp +H@&ykga!x#w$^Ykw~?$o>vPj1YO|eWV}OAx$N<(wY)vN?G@9aVcoEx_yEtVw +!%PROcBmYA3Nd`~_89U=E6^cF77hUu#L9ug>r~v=!SB59LGugTFN&1-$L0__ewrO-5=lbaEzrO>AuUS +Cf>E^t7^TZVS#qu9#NOaYo9~{p=HR7C#|3PT5mrdD5IdkQrGXxrOs%%n#Q$;Ilvr+cFGEndILn1=w?k +lsp5dKEaR-7B60K-;NTS8yi+ct0y0PqhbZy)pYyboFaVe(pwp8R?@$(YlYPw{#Y^a(ixgW2H~7uHvGe +w+uf+!YH!P%-Dw41P|>qW6>*TA~V0o-ho%ByJvfX3Q>=v;yya!OppHrf%2fK!yDa)6b!OjYW)HN&zHkzXI9>ETh)mwRSq1#<-c!<(tjx$2xXxgKLvT~0}<}MDCto!BmmdwB8*Ey+#Yr)#;o) +oFLgRG-;V8rG4uK@4w?ZLlF73imddc%5|Qma>T7pHF4<@)ZfXIJYHGgZ~?^p|$V+k^8A()7XZr{pO^@ +k5)$vUL^(Zu)~%zLCqlVY@T(nCqp}9h{v_WWQ(mxe+26T~_$VukKrmrUFlo=NNk2^GtDJ>kbbSTx9RX +V@g5LeC&O`6Illf=jFEd8Rf~$XBW?DF1^2ZcMET^B)-%I3-t(I-X2cgDhK#5)_e0Py!hxH9sJ8$HSok +Tn;q-Xuf91cQazuT()Ahbb)rxQzKiklNSm2;;D-&us4Z1{8Djd6-79zXTlL<@PJJ3e2Tg +OFs|WwAj{IId#R%k-%}Ma?`jfd3dFUtQ;jx?_(jz-0oCbG%`#_05CSua=KlqQmlRRzWTlkM%*oRfvgg +=`u|KoqHYr#M4|3Fz3!x;KM)c^f~OYM-}x_=zsQFV*EpPz*IX`a6SQS|+@`zL~oc(KGVd}ViH|o~!piAVVzUqYoRi|)L=CYS!K~zu!na_ +gO)LjOW@8|QU(W%$r!+X~zuF_7}0K;BgWR0j2mhw3>ji(KG`npKTGaV1p +_Gq?g9Q0MUAl^us)x1GfcSake2Oj{EKSj=#c=AbuO)v6)-P(VSfnh4Z+)Mv3JJ>2sdC6Cm~{Wj*k={! +3-|aE-g%Tmz~M18Ie68NCEk5!KS>ByDf1v+taw)m>o$3pCF`SKbK%i(*{&?oY#6Ipnvz1#VO7U4v>-W +dVaO_4K?D##{5KuBxppjaFKB-<)RkGvo_4^`jv?|*1kITG8 +KzvJiU(1iqsr~tK)}|z=3P_dw_j*-yJ;|afKq&ZKhc+;u(OSn`K)SgizprOw5*P4hel(tiyDzS+SpK? +8H*WNL<=gAm(GZ0X-){HIW2Hx#ZWV6+bKg}}&=>PY#t^?HE6(=%TBF*0UYx(KzJC9SyEvOe2Z;h+-6! +4USGF%r=5pyB=RO@ozW!FnU|ZH<6#|Xm-oHgkRjwu4c0%ZnWK{qOBd`!+5M`uAv(T3+P=Yk +3x5NDM}~1&HOV`vk==)~gBmndff0!so5ElYKw`BzV=8(ES(}Mi>7w80XA=5X=T-|&!P@Y0ca(?0a444 +GftEk}W<-0w%i894aqa-hmezFBn&d$s0-Jkrjyv4bD7csOx;~ZFTXqlc9^lqETFlwqT}>`FZ|Zd+l=H +B}yIZXnn4yJ!m4(;n$;c0h=ma=KA6zOC_)Qe1>WWTQR;~x{6Xr}9cKTLe>Sbhr-7*-c*r#}&>G +xQ@xEuN;IKtgzwHC#FiXnM^Z^4+S8V=qNxkX-6YQi1XZAqGvBhZRkkz@&EYUsJY_^O!H514KtyW@C#I +&0ieg8RVgJH4Vj=ECl2n?Z%^l&|!Pzyp<%GZ$UG+0^>zPn8wwG#0?V=Epk)2ddM+Dio`sbr*jg-FHcO#}-{06v8iC5t{?UMQ2T{os8D* +R$MeKwEhR2^|7yGNR6=T&kdGbOHOI>Mv%9sczG1fjQ}rgtE>tP-_ZB8q+FXw* +Fb80xt8Cc7N{H2o|0#-y)2cIOc$Wb>EwyYOSW)qCBkcQiedh|BWDn~60IRJ&0F-IuopgrQ^NLITn0j8 +$yLvZb_Fjj&?#_xQGDF*&JY+6a`LvO@O+o82uF9fjl=kVpfD-rZJT#>`Y(F-F!KBUBJQ$(Ir)&sueAD +?+zmS@nvST%@HH&)FBtqN9RHIu4>H}0SnmJw{(l*sA&^h|&5n|iBXY9CAL65dddbj;Mv7&}nsj?r@Hh}k3`#mybWfgf=%nE22fe)tY!>PWw1)PV;NHBap5mfgonK!2%vsUO@(% +l*HEXTCtV_NSPK1IY{Ea18qm!kJ*zCgRc_F$+iG_{*XFt&!~z5IAtn52O*EKf +f<>x^oxp>Yq;R{0w-)m@ZD$3YFDtu!iy024t7J;pZM_P^wz<5GK7+7x4|$eLP)k%w)(u-ORe>jii*Qd +zl3(i24PqE1W32P)h-de1`}pFwQYBIwfXi3bfU?J$AdWZv)5g;3g5Hdr>-Xi3vy&RO%E)`LIf*7N>kS +07<#Y_a--}GY?<*;jYPQ4oc0&*;LaD{EQa2m$JwbY(6PbR4;>YLyCl$$dz&I_Q?SRX5z6EEteiH?%th +!BpIkZ-u_~bgm&g+c6?B|{fuwR+NzSW-F!3dDPaeYF7}LE3lMltLJWMcLuVM7dqUrhQ4w9s(c~5%uq> +mOMsHW6I{l%JS$ix~{WjS_4_}eVuTe?g3+&l3Dz`RsR<(No%Sw-7t;u%%+4+J>a%Ml(deWp$hb+BfUE +0t>S?ark;m^!IW3+p4%RE{v%L7~^O1B@#d_U0sb|C-9fwnz;Iqp9Tb%3vIOa+0o`)_QVEJD?*CS%mkTwjk{nYI>RlGRL`QB?&Zrv)`0C4Ws25pO^BSh<0{a1E`0iw7(MV$y1IHY;d}Rf)68=#>f!9G#vAaxsbJfeGT%y+9pv=EROed~N|R5Y5 +eIrmbIn06nAa+YHc)IO*Bg85=@1G?Igz^vK;xB^b#*~y!>R8FneH`C)-&x$c*9MuvjxqBSZ_(X1rp-s +7)y&qU-KE|^P>iC6rid|SwF#g6s~RaxcUudaB7BPPFFWNYvNnMP&`!7QKoV+Sm}#86{XN8<_l5!RXzh +Hn{MHZBXzlC%$CY4yt(-;tsO2BHhrF&5quI!MqNv&xCDG1IPb=ptj5Y{=+eIu;I(*ih)a6yy!UT4As@ +<6lyN3Wv-f&zcbDl;SCBY+_>HnVraQHdG`GYEek|qwsatTgIF>)F;+^nHD%#md6eS&Dhi? +uFWq$giah8IX(wqr4uEb~Nr#iTXC0+8fn%NJ@S-js?0pAjxk%kI+S*NeD5n$DuO?JQbvkqn+%n9J--n +p}`aeGh5{s$6nF6;3nGQNl)^-go#+REB|seeixMF5gW!IrC4;?Jd+{rCRrc%I8rVHg%}r=j&*o0`SIl +u_J3}%k2VMQjv?EK_>V{%)EAtFxFQE8c`)IXUvv@8T7FiD4dA8z{Zn?4oL?TJH+(KkTwMR5n8nCOMIk +juX`?R23SEGykk7+MKyVT +rE?vUzET)GX&w`y+n8%sBd{af+VFF>VXm~s{#wC|6#Yz}pii^&Mog+Z7S>#w%V8YBH{#qeKqPVGRJ)_ +@#QrCDU}uD{BmdC6)k^{;$=hlNt*Zlq^qBqU|t$TX5%de{@nKtgP^>BfN$SQ8-73`V2X#(fa;Q$i#(z +qRG_DTB(@fcz2fx)?Mov~|;P|BRViR-Wrv4Z{<~onpA)HXwYYReE8J^+coh?L?d=diK(j2*SQCI*rrL +>Y^eF8>?&i_OTeQ>T`_M3~0geyF~-Q)j>x{I$8}@5SJUoYT{{xHA=c`c`B8*#8(OYT5qP-cSPWyqP8d +?+MqpIyn#2W0s6URS;r%3wPQ=ku(W}pVMicbv&ZRy^YFD~l(qurl-V&f?6n1m70K{K&oPUxYXn5#j-I +a`ey-OBmM-zc!>h@kl2ZlA7wL9K-xl@E*cg{s%Z*+kmVhcZ4kL-eXZ&Q|fFgZMZ6rP5r$H{MhGvS)i) +cLbtm4^PJe+R}yqzasA84pUF6U$l?c~Ycm8Xho8sPvc8c{vm$WzI`hHwF;q}tlNm_{;fNt)4VO*xI_q +)C=V`wH4lIV~g2^?u&+7EDGTxU!p~5P^2?X4S#p|2N*Jsw@=Xi-r +oQR~eqWFNe4vw<=NX{=#{NlDvs4~G4o-Pxe=Q4+f3ura%VmmLLde7p;Ba~~vrzb<&?-j%zpmo;sT36` +ifc_h2Ep5e7?Ln-9{L=9xAW!g`acWex9;t#D5za(72Klv{B-(R9AJL=i7&knkF9E5!o!H%E`c?2-_7x1qGFer7j&=VhK&FoY3xxYn;59}ik)~ +y}wrJv^ZJUI$lc9;dhM?V7fJ5ewy{)nf8N7Q6TdPI7JbauRkWrr|q{*7-e-H|5piTRMn{Sy3WoH&Ym( +vKE8bo7M9=uu$`=10>R^m#y({7c9d9$q;p{tC$k1@TC&j6hnzEw~9b{Al&C~OBr@mCNt +Iepm|=}m9aMds-InOT+KWv);ulW&zc{g&AJUaQ +-lP2(g+|3U3(e%nq_b!{h$;i>42K=c(jm@HVzC^|m7a2Z(Ir%%!q3pW@|WmEc|kigRz$ODTDvmjV-R#x!M63283?ua-*iYeKRMwNddIQQ{0AjPmw~SyeLApiIs#?9CkeK- +?s1w|OCnx1dglK3@wf05sHui^=M!EB?Azokw0_Hy +G~fJmj%dMTHY63~r_VmUXL=qqKQBFIX>jh%zrer_3U8|@!oC9bJ*%UxC?9P!jAPpil#jnaZ*+$+$D~J +Bm;nUgFHOmdBA#Cf2g08BtjjGLCbGRTK>SLLX`f`pV?f2NxwCD4wQ{t=RSV*nB&$y|*!1&LqUk_Dp+` +w6bGMZ8<_HfcZC`*tAiVQ9r(S832s1e7Gs +q>D|y8khSZ1$j=`lC0EK)~Ki#E*9`(VeH>fi`VaTc4F$=LxY@t+kokv=`)3+4;;jPliB*u#pUCf@U#z ++6+k7=*=%J@Q`9-3IfWq6l9-dV?TNmZoO50!Mov1fZr@B4S}|g=2WctAuYwVYJ)RrK{%ivwdE)OU89K{hYp@ip;DtmY&Hjf7z?7E~g0w +IN4=2nR(flhdi588&jt%KTIkV;QL1^l}cH{t{&kg!$apa!r__*gX?kWZ5zc72c7r?V~OLkzVx9Gh1(Buo4jB9mHV=di@Q#Jz;#wchxr9Ss()%)63;@n&NDs;9hou@ziq>={bqbQVFSrvl~nT)kT?#=CEFvo_!YL +2+oxZ*)O!&n&UokHk}>g#2&7(%(4w*I)_x+hA!&9f$t>M^ftG{{A4PoIG+< +B>4#-?< +rm57g+7D66{N64pQn8*$&1%U_lIwf`DO$n +L8PhjcGw*S|`lKy`QmeeR_dq4C|Gay?$%bMM*?w$$hFz`gP@fgVP<~q`#kQd;ptugg}1$kE6m)l%x6p +C6|Qa;UFsu?57@6J>#@Ig8ho4)&v4*}F7lZlx1F;gkR0|t^lJg0WB*E#Ut(4s~32_qA}o~&7=-(c3)R +gGrB&^)}emY$R?H|5EEcro?jYV(RH?%BH%B&8j^yE%te7&RR4fI~2<_4 +4v{0UbyVNx}(V^kO_nAuchtbadA_$uo4jSp%61=1sM9q&JenZ_^pJtzo0JYjZNd5J|&(Q0+;&HdaY*K +o@Uy7dQ!QxpWNaRGZQha?8#I0Q3IrUKU;2anC+Vw8LCeL7E%8f?dT(K6h;3~XXOu$5Y9c{VPQd7aflD +NctO_Y_6@j}zX>_b{AEeVac3z(2^JF_2LVdW%>=G|#Xg=_6KdXdJZdvpZKJz%QUnD&8$b!4-Qf5~u5XKu`h6uQz4wePuX^jTf-sbAL+bi^Ds_Gmrt9h}(%mY;sdvKC0#u4y;A-2~ +$wqZUWn2xWxr|WBLG`o2MUshw`(q%J?4|NoP0-Ciqjl{yH~hd3T8#X2u*B!+R-D`9M4-bozX7i#FePD +4OMG+x6fDs!-2k@d^EX|gFL&;ZHh~UprvYtTds2mW_I`xI(~*sJ>uIy +=3$t-{NYx?*!nR^nHRky2i))`xy&*<~bK2!GNQyRN_60%I8i1Y^+#mBK1{$%mZm8 +X-vCnbo~@VrgwTfi2j|K0&oK#DIyph!nEjc-=ZRY@@KBy!@um;%r5ip`!cei@chS3Sojr$7NZIcp8E3 +4z5_nFqby~B1r5C=hvvPq!!;&7N1inRvxlU^U!-;n)O7u4R5%QONyUlSalN!9vhzQf@(wX3CQk-n%|i +Ah8C#wVD)MWMd(4AXF(q>W*J_bXKmdKV)NQE5ML1axZUZn=3G_YaQ(&uw?uXfhCksW!-)>b-7s1|Lp& +9n9VVG1?V%B`HMEQ*-EJo?e#Xkak_2#+^0qOSCGmf +?fm4;}by7B{5Np0m0ttC&c~v9An&*Cg!zl@5QLu{wz(`^dIyX{~s@Jb2m@_CAN>?G*8zNJnM(A4h#hs+X4HptGC$~%i2SG_-)~HOXoU|V{Ol-Ihj)DUmspIOKlB~be` +!+wDpL9r=y7;|0o0A|zo8zvwZ}g-a|uc3v}wnzab&cz@Tn&5FL1B}oou}9#!%Ym>?`xopbI~q{GA^DN +YGs)`A>aWx-JR!7>L1e4UG1v9Fh`|sxRU44a|LmQGcTFII3po)%1M$OH;G{l^^Ly6@I#!fBk%a=kb95 +_I!Wm@qqvKeE;q7zI8zZf7J#}+boq`t@%RF-q@&?d&zv)teO4E@@UVP*&gk5~2=WKUh^0H +!xW5ireI^MJHw)#}W3A~E})iZ+Yb->`Kz6C~yr_~FQD?hCj;Z?v%JHtxK&Apbe7&cWLS``D7t~fTDLD{)f2Lvg +=)NK75F&FiwVAbLAj61@`9q^j59<o33r_$S@MEHz{6KPv{@=7R4mT +!f&=_I3HwX10A^}D>~VuQuQB8DAT=7vs!-|88ssMkd+f5zSvoZRqn@tbW``sYp@Fz|5VX?x{Pb)L_smZ~Q5J-+5l5A9doRS +}uAV))1{0V9tbX%Va;X}X$`gi&_ifIp%xO_K>9DA|0U*)HD9{j1O)DZyP@n?Si#-F$F+E*68Z)V|-X5 +oFMzwIl%paeejhgK$#eBv4O5fEUG%*i1rYI6bsbNR}) +%2(mEp{BqH$Mc|Z3lt8Pi!$<27#LkijkcC%1ZrRU)@G@v-e!oa@$f>|GF_>0M49!NUJy;UdcA#)>LaH +;JE%v*LtK+9DEBGrC2mg(!{rVrPVcvbdEAXae^cx=vCsnYz!;e&ljlkgio*aby&nf2U_77v_@ +~=-_p5v$GaYTim7si|OAY_f2@*d +R6~RIsp6gv4i!*o{g0y%LqBoyTGI!@~{%f9XkENG#}Xp&MxRb}-f~*4Je<6x0&=-!K}o4EUwcqhMr7( +`8B4Ws!bJcv;9-JQ1BLEe&u_uSn)Lpk<$1vO&K&6!!5b$dxSJ +NPfR_?y3XIa%j2m2dn|Wzkuk)wG*Ofag;fAgSvz~V~5U0hK8bc{u5}Pa7(~B0MO2hyIVg9Vmf}YZ1N`Bsg|m#a@4~*x)ZMwm|=Li>)^F< +C|@i{l8(co#65ttL@JQcm2s~`!jHC{MFF@?#O`O4ej?M`zLCiO1D&;Gf(vi(N)%bM>D?>Sp8Nys_Y9l +FZ&K?n^K`F#%x_>yI*$0;vyYW%-3{Pk)|){H#bu+RTxgx6;u3aB=zM6KgyGulop%mAb@xlbQz^;bGn| +WD$5h<39X2b6NVrhOMKGp%atJ-olv1V%1ll>Ut4kTT9|YnXZ#90ZBG~SrHr{`WTgqAl9NY-m!(8EG4) +)@tBU6fot(y5LOVul140q9Lav|-8XDX>Ku=)z{_()o7b1x-sS+&qWL7Y?LDLeyad7Z2<>Ni%Cc?dMSV +c9G8InWvt|E3+-Gl_B(mqKlcj3sP-mgr&X%kO=wokQJ9t3eYHYXuohkt`Q +u-(e}X?6L8r5-%Fs%WAl#LAU_#7?z&a46Vr-!A+ +|??P_*|-KNNq5D$Nx!%<6_f>cXelmJc8Xbb~rJ5?_&R|9Vj*>9yf|csQ*f-?5Awo_a!94;Zl$u=e%a^ +^GB-{!n9H^iqi|4FO=?Nz9_Hh~PG&jd6n2>nh(ydw0h#!FWPMi{fmEo${MQkW%YFpicFa$$)20@rW)4 +mOd9jS>rHX8P6}SzVfK4Oc`ykF~rJU#EM2^vKb^b)-!oFS%55rF!@nGV25#}5|ymvJIs1IeI^gj6Mn8iNUANr7Nf`_?3D8i_Nt}`nRz +7eSSc-J2#R2(@%-aGmr<0HC#Xr_`!#}4?HGMD+xzRvR9dG`yLG?K!u*XOnc-}g`F6mUh^ioer48fv#VK9PgFejjw=cTh_U?o +HxN-J>3KaX`@S}G5O<9k{D&5zZ?9MO1A3GoW&SOX0?d$H8uIM6qbo7lKgg=Zl|8@UYDonqT53?Zuj_# +8E66ckVIPamBQa?zWX0up`Txb-mgq;yH2aHr%f(U0$_$c}&#}s^vV$Tr^xh*~;bJ|gLM0e#W26-O$Me +1GZ!@fwquZH`IVnYI~&`t2Lj+bFhEaG_@9s@|%_$6>UvJp?ui0bw226A$2UBJfmfS(qL_KVZF36c*D-yGLQ +?uYEFzXdACv!3`i7x&8Bi-5-aRD@6wJ-Rbjk<@wTj!QassJ3#s^Ol*nspYMe8}q?;z=I>OhDiWR^vF8 +uBB=+zK15y+-CvCU>qtxK16A-m4^YdOAno*24&^}5H`8~eZ(X)Jx81M2g&3-g9yDv?6c6HVW!) +|RT&fjIeox)f#fzBy`@RM%IHw69<}-*88~Y8a>Zh3O1Hr|G8UgQCOu&2mV$hv@uy81iYRB8z3{xUlt-7Y_7X3SoaGu>|ZG?9H0jHZv-FXwL@P7>r=U2LgjP +ZZdlmw&b&Q=|ABPB3eN5;5_{QQNv^wkmR#P6YsaLxi1hcNl;E5j`=~xY&me)Rx_MFWf)0$V?baPM?n@ +ogX_1P()?7g`T}Wro!mMOQ(w7WbDKz#IGKh^~71;OA*!qYa7W-hE!^g +8MYhN={|K0Ssjv7LcrM6;XZ%uyhrlL|4{^Gd`4-r?4tWf-lT*k-si1Z?vlih2x$r#-Nrj=0Xp|RGR0MN@NJw +e&NolmK=jw-#u%p5UT~b?-tB7&+!+6;}y6}`d_!C5OFF?dfE8RV@;qlUE*}ves-5kzY{IK)$(^p**rt9`(`}_OE>k +_)@5UOV*$vGis2{8v?)6EtSNww+6c5Yc=jLp(tG(EUv6^V)y&tI#zZ&#BpQ<+Yk6fVLgIyLVbSg#Yg0 +2;au{0n*8Y(uJHUJR9CU2Bi`m#&xUTR@zy;~4`Pqw^^s<` +RQY)MbBFMMJ@R`8@VBFW2v?9WiGe#VLJ5?_DVzjZm>{&nmEIZh?wqkIy=KCzJEsH5E9!o?A(pr9k7fPF+154zR#kQKtw9f|$EbL8L)0gn!o{KHqCeIQ +PfAJ___j;hEVYVEiuBR(?1pL$LhK5*U+fS}K?;V-%%pZ?$sI=YsRa>{)ue+iEyaAR2}`5wEFqrpV_n{ +n<<`j=9@cJl|_HbBp_a`0LOt-`p$WBG}0+jha0u|Xr$ZE$A4M-rT`+lCqVQ+cJ}f71^7kfi#v>FJ2RW +nWmyzGw%1-=_2O!Z)-He2a{*2dx=&b#1?mcPwLnU9ov~o+Y0jzl^fsjjwzNNBJgyYy*Oy6Ys&1+Wy;! +Cjp-EbiVk{=(V1rYu|$r#a-JsV(q^Eomj}i*(}ykt>QkS#Ta;*5G}po2=9q3_2&M#0Mb}nuoII!U&4{ +aGy}%O5o;q5ZiVaIE>poX>BF6iY&1$8Awl@8oQx;TJv7(3gbzT3>xY#$pd+r-Bh@sTz;&zA>B^u~SLY +hJd_8b4??irGSQ~7Dj^w@U7FA5$u0~G-tz^Bn5{oR_WXv8^A3?^&@e(D`-OkPM&>p|M`PEQWv3uY-PT +$ZLbe|Iuqn!MzUVu!KvnsejapI<}?C)JB&{~MrFAR)l+rNVMGfnnqG$(Y{evkIk0ms`+Hcv8PI7~YOp +ehGcpew1fogK&IvHF^yOjefY7b(2rM>@wSDVjX} +`1IR77euhnLN-;w?LuPQfBO4A`x(VEo(809c)vERc7LgHF9XXW=e1g=6i`{snXs}Tp?FWd}b-_xC_2l +e9lun~Rs%{?xY1H51aW~SUZtM%1LS4Y(Cf`=!Tx~YdxM6RKmn^9=`}NGjD_rx`g3opfm3^G|Rtrr25Z +Ux;8DAZQ+|DVgnS;6P-iPd{HjH0oo$gcnMPI)mo_#v6N9b}$=fb|ofI43e3Jz)it@EkJymkIhjAI-9i +-3pFT#(B_H2A*W+X1+S!Yv8gNdph6nySz4)TaG8DH1yGbfwly*y5MaXtIW%E{Fo^4Eebc@bykiRTUWoi8>hh(r@f+`|J21P7W{o8X(k6=>(EXe^~oM?i14#fj?2SvBwU9!CBg&Y1?KXTC(Y6W># +9f(bxScY0;)-nz_-UfBfZ+zd%|t;OBsn>1k1?FZPZ!_xI$?Of*Z=ez~7$f^=Te<#BcVACMYU1*)A7n?FWNHh-5R7ppO!z(Azpt<7{R(9|sVf$=KiA_-$A)8hs?Z0dP +Azqfm&l1NF02bC1X=okva#T_&3FdV& +sG&{Z&1$uWp#-y0#Z;v(@5tCLt_p~VO&}BBQETV#_c2#ukeE`M|A3MmN<ytp& +_ct*8dy+N8flJJ8V*J&gqO3h5|82@E$}p6B2gL7_xbwjrBac)^?6jdCfjKdfDAcZ6zs7^?yIRxLb!{U +cU#l@uc+Y^DxXgUaV=8B?od(_gv#mGlQwVVh)lHwJzxpwtsu0Q_^Mg%EqK}BB=X3bDwmR($`+T%@PhP +wkvJSU1_pn5*64-!XG%t23UdF{QT2DVi~jD3Kd2S`c+#JJ5d_A6*@;zrc>aj|5WLF|IlZIjn?SxcKT# +hI^+V3DIO=Ap-y}}cBP@!Nhf8tr5uqQwQRMJ8_SZRiG(qjJ4+T^3&_XS+Pd|C_Zyfs_(-3vAI)Ax)@z +LEoBI=-}vV&F#k7{{AFzR(_yrJ= +M7{z?LSo9KMX_uPrQMFkS+lKQTO~B*bJQA0m&GzhB*7r@PE;zKiZl2RA(OFDDievFJwnN_G^Xf{HMOB +uZ~DV!+Mj>MkAy8X8hR^Sq#s|fDb>i!2hHx=g?BPD*;~fZjZYqria;@=lXz8vl$i)&wo?iLfWXCc{7$oZ72+ +Fb5K{&0IiaX*H6o5uwAOMvc{N9(!r=8C#BbHS4&J2BymTAg4FGAa@U3g`f-mNJVx$X8zVzk>`OV`|o- +0BaE|*EvLT3$~?M+|#8$4dlk!K-6p%!cf=*;|lDD_=Pkm>B)VA50_8$ILec(E0*)d>n=$V)Ka$=CBK^ +xRp5#RRc>0}T6p-Ist(t{~|(4$S+M#2XqBqWDasO_X8{ar0X+dRzDiC5SIlAz2WLPl$bf6s(%;%YL1t +0E(-RTlgsEUJzBOv6zA|k`Yz(&*6XTk=aq63J>28gl&066SiYC9y`&naeLNY6_ZaR_g^+|>#V>h5MrLOJbqM>++^|HylXF(q2JAitEQ+Qg+ePw5Utokf_ +Uw$nAAuad?kb7SWn*Dv!^9*p_e&1;oZ7!2dr%=kGcFb<<1+~LD#>-R?I|^JRBP}%mT1%>qr0cUW7`(E +Rgx#^1xUgb`7jZC@_IRqn@x>QPrZoxo*ys7whd2IiKi&R0bbwa%Mwy&=NTTzXU!2bVGt*DpI;D=lvb+ +l4+df5PSU0M-p~frviYzYP)ygqp^UT+?=U#%_bu}p{#ip5cS~v8DTp6U*N;D|k&Nj&mBThh9w +>d=$&V@PWa5=476e2cQd)WDkEe|Ivg|=+11-;88d4r_UR%i)^)ic&yZfKC>%Yxfk78Dj1R4U06TOMveEK0Uh$G +sdVy_OHtavG8CdBI0p-#jF7Flh1V0MT;GQ#kEx@eef8T;tK2`Rn|=ue&3j*o+STgHEfwtz-Am<*)cVl +zAVa)IaU`8U6gdQ@-P#UmpHLnwp|O6rymPKp~IIJhXs6@k{6v-Nf)C +Z4KjJ5ljjnUEp7vr?7vczwT2!N07K6k6LhoIIajIN6a|>2wEL);X^@VM^NwwejSQe`GNOh;&_TfJ{kE`sl!Je{SatI{!RY1ScZ +9o6jt^dI&x?wJPA(@(Qe6-Z+A9*Keg@pOQF>nFgD)T8TnhVQ#)FE;!yAsc=uE9{Ga-RbX{njHWAy(hp +Fk{c|YI=@D*>AS@TVeI9+!1gMPS;Ie-4r$|V1|t)Y=`3huyH33vY*F8w7i-pQe3;qk@S_|3tH82&V>K +OmUPz<~an{_|h9pX=%gUyE6etu1!1LD3@u5IC_DS;$*XD6f}_3q5njg;a9Q^!j>H!j<3=alSmyBZ0jnm^(rEewkA +QTnS8T9?*?}QZxu}pwWPyYf=_E8h7dGS>h|pjmGjvI;KQgqB7L6X+DA)0eQ&8astjxR1pC~JU!MBGhJ +!!RTm{B*)Ekgt!=%eeVz`I)Q(=F#VjbnH;yo0wQupdWw8T5Li$KD8$^UNW=1A043)e4rv}OqDdH{CLm +QxaQZKPQov?&jiBZG*9CV3dy@=}V4rJr}a2L9i4C5?x=}3epngZ{{{%sRc*hr9GK|*sL>oVtxCDS&z}1lCguw#haq*)#xD(bGdR~QOn9zoRLhoi2?%+cTk`&Goth_Fe4{f?nP4T&cvY +Vk&y?RL^x@fpJTjY+8lY=1$Np-_JNoPtKFdqQ;A{#hL(3*NbkeZoB4`My;&vrUVdLj`yBfQ$z=%{N--IC*_3H{1{UhfB<6HZdwb9h?O7D)u32?M;{knC_n;Ync(w +YS|*fZpe=ymF*r*JAg{!g7-zz+hJJ0f~dPP&`08?~=4=QHPVVspEJx7q^jE+NJdc^LQY+2~xcLH9Cgf +oyRd9D|q~+plOw=QArYQNi6LC`YpRUPZHfKU0N7fkf_|8${w0I^Qp^u`)z5_XPmOvC}m6f$ooGY!O@u +ON!zYn_GCd+K`QxFrU4z4uKlp(!I`HdsVatg+t|bS0JweJi9N`yJp<%BJ7j1d=KB_!gTj1c*N&|x1KA +Lo0jM7@&AzbW=oG+TesjnPqFWbI-+lU2ckzHdI3>4w4xURB$}r`ptN(_=}dR~-=`|7BGS$;`Osp4q&d +gz#$Z}z4SI}=5xQg@Y)NOG%~Bvh?pXwGoD)PCzcFv$4fOrFdLq$>+|VSI8OnoiOForT+8$Bz;6;*6BE +wir#}bwVOzdVZK_4uwt7byJE~RELX??e8%m8s!NbNpJFnG%LU}t^d!f+2S%;?|>!qw^}H=xHuXXSN=n0I$2AKlea#%kd7pe-+Zu%dw^@b=zA>ZAQxCPZyr@N{naKIo_s +J(~p@8PKYd=19=VJl>UZFxw5DrP3O=AVr>5b^|7g((Cu@DynT@r($+$Sw1O6T+Glj?MTbjE#>-b!5$k +;+uOk+%c`&Wt9o7f+0>E_kS-_QYYZYle3M${tMK-QqA7GycWN#mn$CM!s1(%h9;vDCcR1lI48LL%$?j +pfcne^w2ue&vSn9U>YB!$bM6clIx-+}dpV{EPWtpVP9BF6b{8vcFuYj>dJrk +d_T~ww2#%r2W5TQ(qO&{&+*bMt9_a>o5|52ogdO97A#Zdq^(+$XFb$e(2F)AEyUGB0uxtRCMT0>=+G= +K5}C84*0%5(gz=f^+UnpqgJ-VCn7r%P4uTvA5T8~i0Mc94E+q|<;MU#z9YFG(K8-9$qz_~eC7nxPsif +Ew|&L^IG!Fw_T)2V7<~X-0(}M?u#Z664ieErts{#*gNgKq4jezmANDizgS_$2kep~AMRxArjM`Cq2Rl +KGSNL~n`%k{>9{;Wf(Xus@I!^xdAaYE|#_d!bNN#ru;4fq1I^<5CTPu48q8)%OA>GF|yM +wXm6(h?#YMXI~dc7=#jm#H)*1}CHE;|UT@1{)(U5PD9#neCVVALe{SlR17DD7)0W8VS#;jY&A#fH%Df +32U!aV#xdJPQWG&vt$@9Q9En{f$z`moCUnkNe{2|1e`dZ+yG#)>nnfK6iwS|M0l0CwYYuCxuvxgBQ#&0%-UC{mTc& +q7G{bR)Oxz$p0-IyXT*yHaCk9;@+wH3eXUz4Hgz%N;t7t&vXAhADLC=H*2ZNw;}EgK-)I(N*u>Sd+Q) +Tg3xG*qnc*qQH-gxRhP()Uk6Kz;(2e!jCcd?bPq}1-PJRgR +v|P`R4>ZFp?LsPxE5t$t$6o3@5ZLo!*?P9`5D_|6(%w0iBq)Osi;$YMC*ix2Z#rk5#&zg%>q5Lmcdtq +JhX?WG*ArMn`qp|*94?|k`tkbqU)WPpPb9VXqmaEXDr{ucT~39%OPe^+=dCG+>E^N=*;GrdG$Tf?Fvl +;rjlqJqs9|1d^h9c#qpN6Bm?1_YK78aT?x;PF`xZp_L6Wqy=#|wR!>2^R$_o#5(R9RN%3|_{Yu%QZRs +Yy#@xE$u$>b|530&?%l*y`9H$bH>uPUZAvDEvI*|+Nf|soW2BA-;-6#8kfAwOsOuQ*RpEv_m*&@6?dl +N12fPpT^$B(4+l{!^wI{DGTpw_$7aHF2)-ftPbypK#lzD;NACrQg$qFGA1yUAw$Ls{4tOL05q +)qU+`&UW9FE1k=>(1_g)a|DzneAUZ&Xu@J%?p# +wxm#c9`Y-r)_j4HE)g(z2dd)P0wjk?U!qPuuK_&egA@313*Xbr+P2a7u!t1b1z~?H6#X3V%jNVUnAge +0wc=D=ef1dt;_TlUK2-MzxY4k24_P1#qEC(S>Oc_g(jc%5sIhN6!aK$TWTKdGEYgKdT8Um8oB$-LpDh +X^Jxdcuri)KE1Gzr#YI~zWEWu(I>G>U{S!d}*-C(smnE`s`*`k7;x0k}804T;%rZ9|P6PLl`VRz0R5m +LTD7o*rU*3XPLZ(My!2c&_Jq-BDrb!p{(MHeoIZz}Ls+-SD%xWm#M)Jss(wCc-?DTB0Aq&p{R^VCe3d +sP*?I_c?T=ehX21-7T9MpLnj5F4jF!skRDPpUNu&7_}#C$=Bq0A0Wtvq<0dpK{9spo(dGa;(1c%nri^>tz+G>$-&APZ +-+T}sj4om5zzq?5pkGd)j3+2mb%bH=A6e3guf%nT;W?eU-3VebM4DluFn +@zm;t!~fkpE-UP5%Q|`U})e$e&U-9VOZLsM%)NF~342$7~7&9YjoU^iz@?*pVbZBP{t(MD%@7`;h@g( +ofx#I@(4Lw(#tzo08d)N!)=fiX0O=JJd|_`kca<}a~wXHF?S?NQXf_e>X@U=K6p;$FQ-RS +DgIHTp+0m?NqP(bVfaz1jXw&)@P}0s!am8@561 +7e&ic=iPw2UA%zz(ZVE6@du!otlOE5vuA&r|Ttb`eUQuvTWMeL#_ar1^HW@@;@I7_{og0(e_m +kX^SwQN~h&yo`+SZiJPx}F!&yIm!QL*o#bWth6>`kE;C`&2xyh)2Q8puBZy1#NyugH`7v3XF9nP? +6N8C=OuqC#Mcs4)ZZ?B3Qo92IWvFz24N?`mXs@&V2h>fPc`XBT%+o^vRUg9%bdd}!)dv;`w~a!`4tx% +^RbvCuQ-X_b)AOqLxqYQ;yYIn!(L@l)>`=Zmm^1KtFNF*STf4DDw1s6xK)j4uL6}`)6&pIm)6k&N^SA +zBgR(9AGF|ZMQRbuYmO^g^Wd3m^n4hQR#^CYbXHyZU{w!o=Bjxq^p;5GG +L*4VDkvCKPJ+G&ZeWjZIjv%&vJ*#a2x9bk#9DT4F17Pj!$`itC{yX9u%=Gx#XJzTfC;hB~ +dK~|DLgJ54{W>D?z0-a(EmqLEXSBM_f!8rTW(y-(8(Pv~dMvu +MW=;7k%&#VMR{H(mTyF2PbRe^rmn2s{uhokWD)zrbKm}7?|9%#4=jKJKn +B*(BlHdwZjB0o93#UvI{@hsfMcMP4@rYxm-)f`n&h4xF_zT{{E1Z`fuMhH4y^a2@^?<&AIYo5_pHqi9 +5d(^KhQE~|r}kjXsPjkG$7e;xU|OxUlYF7$L|B6|qlxxC;Mkx69!Hq21~RbXlf>nAhFM3LcB`#;c|3X +?GKy?{^ZS0AuV_3Y;9BADRJuH$74u9G6$I>1D0b%va(C@XBab`E$R6imUi99o`zXRo2-oM^THV6Cq)< +uRlv9yP!f2i(R1azsz!Y-yA|92Ws>CwxXrTxsU7Aw81&?*ei~4k{8|E6m{If7+p-enr^Q3p#CQ=eNK^ +GwDk=?upQsYn$?u`nmOFM99N7LDSRd?0e&{${}+)I@OlVfSV +&-o4Q3EBol2!vY^DxY$c-vyx`H@WMw=MB%*02LV_V?yaPNGlv=hvGX!&n~+jlF*%XObDy+Wpq?xxaiy +2N-d_m>R!-_P^t|#NWizz&Er8ZMr#e!<(|L%x5I8-4OTcgR`8V!sS0?9`4++ +3n|#AeKY>L}@M`hg-m=c(Oblc6(gn(n+xA_xui_TXYn~uHWo&x@d%|l2;Cf+5t+TBs=><-+(RpxPk~FQ;jCoV;!w4181p +MqMAt;~D(s)|nJuisZjR*%cZ9yb~s9@&1APPQ?m`aO6a_nj`vpBiUUaEBNc$xsMsMjy?lAt2mvZq(B8 +d;-cfkJ=5Axiv$1l6%-kr)fZs~8l +>=zgH-Ix4!qK|~IqkDuv2!fyy93s&}(i1{S0wQo6BM=NCDH4GobT<{>twcm0=}tWU3C)iN4)jBXkbjZ +Ngg%i|bi_J`$mj0e;_v#eJtEp&M;0A?2Q+bv*&XrTrwFhgjH#pLm4c6h +1fNk7AeByms)?g1eFMHLkJknrM1Tgras?mn|mV0X|ZAjh9Qay*2);iG|N_k9OX5q6X?_B|a=`A_|q5F +9-dQ2C4haxnGy)HY7X&zl!y-I{G5MvBZw(aQ% +#R!TWm?Y>ykDL4@7~tCX0wvEeeJ$IbhMW}WI=V#I{X_1e0xyi@88fCDf+J{7gT;>t@K9Uf}3@yZfZbu +gZsnnY8JMe&|i2NykDR6Rh2>J_`9`ff2-s6{%#nvdUb2epCMLRg_k3#=ozi~sOAlJ@_;!@cM}WMlV-e +&l+Z3$i8WI2jMv~~Vd+Q;lQO%5_rgwNK`f_x{}f3M;#yB;q3V_agFehI)wG6gkIY?)_B$q@j3N$%nP8 +`4yGapB-wBh8{duD{0!%^;Pp-T9ed@ygbpZ&<&tEf<%rYWj*c~fwIbn?1fU(zvwaQ?_4R{Py=yW!mjr +Ms3>h4{&#%Famf%c;SFk{A_jS=Pk?#u6u^A;e1)xg4jfS2?k>yNy$m&HPFID@d57#sJPa2vc@qJTL){ +T^_O$(}+PPwtu$49M;~!dE{*&3xBeG}m~@Srdymf<`+@~MQ?vq=DS^zr(6sf2666Ajyo>P +h5Qx`#HK^Mie9B!gtr2fZnd4aJt63^15l~;TPlwO##$s)Tl^>A=1(t8Y(0k;mZ@ix((!O|6FWa%BjEuX7FaLN0w@&c4U(0XG?;0!`^B-jPu8v66D*cb9}vuY +glSH3*Y6d%;(c2)c{gM6EygK2zT(6m(ziY2mCTD|tEQhmQIK5V(3 +)!(Z=i_F_nhR*dmna}GP(40u#vW{FB`)11{yI>Hz!Q;mE`+M4%sRN?yG_(+Rle7RO{Y#f=O))=Zq!MSz6UoYPA%`cg+l;}KQYk`9y3Fiwsl(A%((8^fNl$tvkS9JRPQk)tsgYIC8HbLr~9(;v^0+L>3Fy38 +_97&oVQ6UpQZu)gT`ektVu(r#mNSI-9VARc?v+iX&L77Z@7Y#a#A_vwaIcj4Kz_fog$Y;q7zj5DxBJN +G@5Y;1rYgq3wYS0K6&O9!RPV-b0{^ThcL#E6e08#>C_lF^2JSzEU8bYTe%2k)O(R@;It$+hFRarX5cdGfof={E3r(J56pj>glg +Yc4P1%qg23>%!Lfb_HlEZz#Rr(Ib|b3cMPfJ=nYPzThRkg7$Zn&*&~dEELGch!HnFKGavKN}&=r)ZR +_`qX_o>g=-bQ~=rPy8DH9keH@N`!19njV0J;Vrs`+F?QfGnA-30Z=)( +pNFYUcv1K^I)JS0JHpTrKU?e*#sNzeiP9&c(G&`7NsY<=}rARmp!FRTV>dTN$fLJnVk5UT=H=FN2 +IqWjhH)SPD7td{Uj8c`tE^-HQ*KqoAX2|4sx~rQX9ss_5T0@j*`C|Zv#%&%u1XluMR_F95h)W&)J$$|kKhG=f%Mt}2`-vJ6mY_sTX8I_N)?$N@ +^7C)_1(CGYUvT&wGeE3kZt{DfeV8;aS{2m)8~iNP4df`mWZNJH>j=BY*Jw+`O#*B*Rm<-EU( +bqe9c)sGjgy^@0179=TLTf#D7;6b1CyRy4ZV@zm6JlR7L;3W_#|^qj4&X+idBK^Z2*;-=4jtuaHx_z#cLSXMbmD@r) +b=RXt}sz8jNpJo|VGu#mRDRL2GS!>W+R449yDQt-F?p?s#&p?xy+Xh-JT9)eMnH9R^VD+$I>9$72-E4 +*rJZ^Lvfu<#D>lRYAKe+5lu&Zq03nu%{ZPbx>Yz3|x;a6TDKwioJql +5J8j{+kpmhd@M_HM0f#*+B?LAF@I`I6qnJ|c~@oFrb_L5lZbizo3x8zE_b=n2FA?+<&8O?;m2)LcXPz +Au07@YE`v;7-6tbugBe$Naj7CwH?a`9Mu +OOzhPdg4oE0zTjn^-+$1(4T1XAn*KiuYQ02i8@f%m!{SoUPaWWcJpbU&B&in=%a%1Nn>T7+3_U*j4fs +Rm4Ae)s6zniP>;$EhR*%e{ptrG!jGT#&E(XPO(T(`C-+E_<;UgW{6nbyq4PYrvd|AtCw-KCQtHT=5%e +(vg%N)nRbAe2t~+?Z2_mHIrYhNBnKVo{Gl(G*AQ(|4BjWlQBOi|^|>+KJe5CB?5uAg^c0$=2zm4 +XXYEgoj4z?mGazaD(beIN2~MsaZ3|t%O2F5(cT(+wOLNwd<$^N>q3q6;Couil3GZ?-x;uoyd-Z;4yo$V)7mB-SNFL>*-A#KSOMJaV9xR=gQ!#A)6cgc|)TocMdj;T^X!wZ&36~x`sM4S`kJBidky|jP|qCQoLo2oJp_6f +*!n!$$C=?^-_eMFFvOsH7DM%_=h1)#RRHG2%Q>|TeLuCd%)Z78RNZlFRLV!;-T +JSP&s&6SWKwSkr3)ldAvx8*H6_h^m$%H{WXYWNKp-=P2~;mnmPmBW3XisEV@+#EW45k$O&wRJtRGFT# +|PVabEr<$!YAf^tJkP>CbVAwRcws}6zp0bymvJ;6B<6f2UDOh+7iw)!yOWNw4J2hpR +>s4i7c_S`_#ajzp<~elL5hHi?zGl5_Pku6O-U_&{G=ujzq(p?6WG|#3+A6!K8KP^!?Ue6?5BEy;wTa! +Ef(3@L2pObHbbwacYQ?Y(jQOJLOHsZ(4#FXj+CAsbkzB-Li1ecZ=c`?@J!me!7T(a0gx|HrTCXaz15- ++_>G$k8JF`LM}Da7SX#4&Aqs!Z{Lb#^iOm;UGZuVl2d5=#`uT~0Ai}sOA6e0A{zx>2Zz{rGf+E^zX%d6trm>;XpdF(UVGbhlPC +w?k$1dd_+`*DjBjO-TCt%$_uD2T(+{Bw#xsFAnG^p&5V|Z{d3$2LJ +FN>$tynTWLFsz`zE>BY4%VQMiU(9}N$kKnw+!xp*e&H?u`L}YH96guwJpZqBKbGvr{#Wv7zZEO_PZ#? +tUGn3Fe$%;$qZE!}6ih%A2JMiGMkxYAArvK$9eiOlgkv!MJ@j<|Ch9<62cAKXR1k$6MYv-Gh&<%Pvd; ++k4uq)uCp!Hu`r5%7_TlE)fzOVN4vNv}$S7gZL9c`52PDg&5AK2vl295wn9cF@Cp7;7`q}~E4xf(174 +ex;M-L_*DmmJ2_E#PkppU`!{e*pQd301A@y}E`jD6(Nk)t$-(Vsu~F~}5sgw+p~(fvm7KkamB4x+n`_ +yv8LXIx9JQ-Fuy9%7IuZomh~{vPE9$2Pz>(&?VN@t($NUwTUlO|!(Ni`=92(Y)I?y!sUmy6iXz1wM*M +$4R%(Nv{B*C(S-+xPHMw6Cd5u-aJNa&McdtI-Im#E%Ockb$s30es1M-wE6mJ$92Is$gh*ppB^dcZ2kT;f{YP@g +U%k5-JKlJE2IR$TwQT~DG5vuw{WZ*$Rk1CypKNr1RiJneS#->K$91zuA0`xy&o&A?2p89{WNjr-ang} +~2onW7VOVeMQ6t@y%YJT3JAf3Fzk!X)Y*HK!!)~7CMnX!=`#_$fS|hWHdR3_ +Vd(DWgX$skmBsfHds&i)oEIWNTh)k;7h}LqEjnMjb@xM7*Er&5Ive;ib@1czH0s;#m|HQ*#CqYKgo}d>B%yQVcsFRH0Ty4&*Hp7fvu#cyrQE?VWW88CuwCd4@?QNS4?)(O>I{kjDS5UQ??uj}lepN8EPi@a4xXCl`3`o3Y +p1z?~uFWX;WX~B1p&1rTqn|7D1CR@Si#4jHgGZ15Epg2sx*zm^(kcGM#s1tW{#J<^$MA!doTgw1qHr7 +~QJ6+3l)`9y$DKHa5afq#?sra+{PcV7UJ!*p6`wE3i1aA#pxI9}eRR+qqqym3IQRSQGdX??6ht2}utS +H0I_7$lkDuKSLeN2uvOBdj`6L9W!{P2u3WW}Glpi?7FBP0m(FjWpV(i@uLSJ4+90epe{<))bcdhYJyE +!r``|3wciuka=;77$OhClNg@NtjWhtnH9(iVT_6wlV-6cgm%PLU8Iut4+2#ZxTxq$?^rk{qM5_xt{x- +oL%O&1beRo(1?%9OB_wfd9lH9-aj_G6etBAs)L1{u76I>>BuI4pE!a57}Sx1im~~nPu#Z{+2>IoGK2c_>fN;<#IS_VOj;C{M`SoSfLg(Bgx^G_gSggyKWJAvT{%59vfUg)njU7|30ZAjJ2bLh^*FEa+bzN|c>_crR#EE-b?>s%-YPWt(q +4^($7yo3T-&xV$F7j&&iXk*gV!Ne;AdH~N-GY9i43fqn6oC)~-+%SpoNk64mWYau!ZnT^P3<^xXdPk1 +r%Ih3dDA0j`)Q%au0yGg~PkFs(66Q2KRR8f7Ris- +8WWwv3#pNaL1&ZH{Zi)3~L{@p<`w)k&F70fJswMOZ$tNOJJ^GASK#V|(%rV*5^fYAkA<}S%Xr0WbV~i+8eEuado +U8{0x%_(~pr2_2%nxk<`=JfkA?It$bzNV`h-%kJIZt-ypRD0gobRPhTaCNsFBw2i*B4bimD*Vd{5gtZ +`!ovK)Gt}ZHKlTxBh0$}%sw4vQDc6aLE(>Mq;IQyuMs+H_q1EDh?W<}Lrr)R7DK9$BO +@|v#NFPUF=r~SqNJrfWl1zU4?OBPd#`4&QoFhnr}xi{xVQ+1b4#Yq`wSW+t_ypU7!B7IlBilGndA|Ry +Gqdjq(rn{h)sd}N1RXGh&Q(-VqcsXS>vn#QZX$ijQl1xQl;-a{Yuu`$IJ1~G6#A_P~Z=Mv`BqwBbEDZ +k4dhkhet<1k$hVv|Ncdcg`#}-{2(rzPI^LqKJR8&>#E0-$W0eq9umyeh)&S-47x(OcHw>fgU1+6 +owKQjD0tFIOuWGBiV(27#s8WBZre6gIYTlhtk7W(ZrEm!}oyT`vcSDXC`@fK@@#BnLX&(eIJp2dcuf< +x^eeZ=tpxWr4K>Fga0}``r_&A2flB2cF|{kZFjr*XIlDbq^FLCu>JoB!BBSW8AU!4iRe*0JvzpsqvpA +982V@f9nlW`(;gimpGkl$Kbp+;mD4}%5<45m>{xR9>ibyFyS)u1>p2&m3Dbb1Nl2>S1rNo4D|q%qD>9e2LBesZ#9{jtgl`EE-YHj5~&zz|!}HgdR=q>W~4ptp4Z+vx;67+|m!oen7ZmfZYJ__e1&nLr|9zwR^GNH@b_)9stmZ#j>33%Hn^k +^eH!&Q>AqdAQnj}#YB1w`)ce{D0lR+>+z!-vlKj}h!=6sSv>Ga_6&yRF{j2%h-W9sM&u><;P+Q5Iro| +dMEy**^j(3iXv3P!e026qo=8VTXlZ<_-yqI%LmYfHyHj@TxJ#DWgbH>sE&qC2|&c +G&NMhau}zP^=>LPniu^U?2Hvdd*#Em{xGa!)9)0C#?&4w1}=7r)kSN-+Xe9GGnMELm3i>G;!ntuQW{d +ND8+7N5~dng)rVZja!~XeLmlm8%oxi}ewi4rGv5g!u{I&Zcwmk=feRmarIqE*#yahGOJW55~f&CN$TM +H0XQ+&UzZzk#okM?LDNCS|QiM*;<(Q?moR*iS?jsniBd_ih{I)L*G@90&D%q?`7(GE&;xn+O~64v>r2 +j#;f5KyCkmK*B+x$hN~_6rc8a$i}nQ0iVZ=nFcF&(E-8Is8BPOoYxQRAgvznH@^MDyr;Arf%xK=u&+( +=Omf}WftAO<5JcD3S_myFlW?sDGyfq0k0A6Z{G8u0)%v?yZ;;Q=<$r|r4lMr5R(SNBO-{F|krE8ZDkMzkuHa1ixvK&Jh>k>oNJh>!H~1@jk$k7q(G85ylY@osn{ +jSd}$pTb^k<4(zh-82ZN8ZXS=yN?K9doUk>=c!YIAS}j;b6}jf!2{el7*uVn37ub($HDGF&$=%$h5?vEk)O-jiIp;o{Seam9?oNROmv1dLVSX7_kiF15mYoPWNESm +{4`84=EEcB{5XzME-ob)pjzWmJ@l<2!7-y1cL$4y^;p`6_b!klfRYru8<@nRlgG7Gu8RgDjad^kQ(GJ +qxnsvgd2zX%oKKY5fpjR>uci2O$48LfSx@al!S`;LNkfJL`^C>QiD6?a` +SBtWt&e{Tq3%65CgVnDRUNx)rfnYaNO%iu5*;q1%nthRWC>(Y`uBfU;02hrzPtJU`v=fJ98`lQg6rwaq8ph@4dS4z&sew-(l410f0-vq +Bw;e<`9h*9iRW}XX{NO^oVEpS)4n>1W0Zl;~oZl6SJ`(3Jc{&0o?#W#vi79=m?#)M +zOzq`ZN=^EaIgW~J%+=LyK70p3tl2l*BpMcK`5A`QR7{iucLynIQ=8B&|lx0G+?m(9DX`cO6@9!_nUB +D4!%C&AdF1;HD)^;KOZvINcZ~yxsc4brFdc>&Gr&9eh%wrBnhXV(m`v&J>2XWXhykUQKi@=}VR(Jx2MypFhD2VT_yIh5btPtswUTQrbPbNxq;zpq4`3%sk1%ffj_a$xQcypT +arQ1VRnq#L`|CxSYGc&5KjdilT=xkhSz2!_G~PT9b|#T6+}+O-OYx$BPL$7CZGZOm!eACM9Le9X`I +6*HJ?e)`(cp2v9`tBYkBxhw>^6O$ONc=9!+Zn6Q#cg2Cv}1-Y$=(JxLG&|>3c$YXrjP((&Q`RU61yIH#Zjj56DQPoRBg**n8Fh-XCjG55`6y10O(QPNoSBx$1{4+~w{alVNS!tzxwgH8X*z +yOgS+@Vtx;b-fn->Gdr~f4JesEqtC^WUNe-N;3X*IWaC6gVXMs2Vn_>8`P!Bk;-ix3?rb3DS%~OR;Ok +`CCq+YMu^dkn@f0u)S-_(P@0}Ma7kQo-(jrfVctrxPcj?24@qHCS@Zh%>EjK}$QW!@bW+gH}zTGwtzKier$Ud7|d&$rQfhQRq%I2UE#pji +j0xinAdeUZr|n#)p{m-h)ePbEoM;VLt%BgGmt59>`b-=8ig}*J4HD)optxWgcNt!^p}C5Ywy4RFtq+LtaF +O&U!QX=*LMD;XVd%`>TmGjf4bH$F!3*~^V^9p4Bz1)Nnj)eLpViJBu>K=3Gc@V0w<~c#~ +PKafj6AxL6Z!zKNPY}yMbXg@kbTIA(2u$m^`W`lPoO@yl)#RP3yJ+iCr5dP`W%Vj;|P%+a56!U-hd?D +0qvjSMD%D0pnla7ki(N^tkv3x50PAIu5!7{k8oo7&4`!hX$!wG)-NFHT;ol;vTEonTjr|U2q%A-v4%GeqT&V-$^?zmx>v`;zY&s9LrOFVQ2kYSn +mgs6ZoV1{4017_!%%VeCzu9o#c?Y+#q%FIX|8@J9iuvHt&d3K1)_V+i-nOw` +&=mG$ZOpdOWcJYDmtQhNQmUEfvb6YZ6h{qJ?}SqVo``0!T&Dx7uIVmjzyxH*}qEvcf3F>+(poIshg`a +)Z{1b3zlT;2uOhQyP~I-!zSxX^w*1fP+EO5OYd}${fjFs&K2t5?MJ@KkXQ5TNI8q#AC +4wBoVcW(c%5`?zR=w;0ugLt!H084NhO{dcpbP)7%i4nllQ5j!k!ijLL9MYs6Y*gk@HD6_D=rA%81J=4 +GtW$?hyXkitw?TKy%mgwvZPXorq+icdXI-Nt9V+qcpra#k`l-;Kq*iZ}l__3Vf*9?+F%1%HL!3{WVuy +orO-aId58U^8-Ko635D@HCs4kI-g9BW}dFa(@NKQSe5}UOZ%RB2T#6LT}Z(U&91%i^2=O$Z8Dg~f;)m`2?yQ* +g58|iaU#I%l2hU07ps@?fOMxT_|=rJDa<{aeEM4O*gu-hHNIa}qFWs$N!X3j-M2rHD|qI7%HCm90Z_NNP|fg#&bF;Z(@hhw|J$Zm +om*v2`NnsYTt+k@2!1vv@2Im>puwA1zVm<#7%mhx_@MIp6^Tvi#Q?C1G}zMt3T;@J;T7GWQqPh9%z~(CrgEcXkg6;zq5|P&oBW;L)H5kyr +FA_C)d*0AC7eoiBUbPVO^pzkKHDJRBkfe9#Aak5DVmhYg!Wc +A{z}Za0#*;LtRe)Ri1Uzjtf0_H2E6k$F@uL~Z<^p>5s)AM3n@$wuU5xx`V?m6$OjcM>lWm6nljd`uY% +Lb>{8hi1s)ydxbRN3btt_BIy~PvGNgR{sbWkHHce$zmo4GgJb`x#31@C!^ysR3kvqj(hcl1IC5TfUfz +7dTiI>FNyu)LhM)6Q=1Q>Q#UyksOkKCz^T84)vPDB{%7W7hBscTz(Vg=^>r1`Ke)W%fy_y=ue3>!NyxCQ;ZAiQ%kqhP1R!}}DhVCVMlv>BY(>cA`BOu>!Fxvd&)Bza=J4)_bih^8CE9tXFz&~ +OKV_zpDFZL8_tHV45QtrXoSb$#eqBKx!GPEifx4P>rot*^m@t*{)!o04;|Wi|hjNKPlTy#s@ZzeE8MH>#(2DH68Qof~M&B7{^UD)5LF;h5T-blF@ZSG;k4X|CGwQoYU2J}jwuJ{F}}ChixSKLdXF=@06@j-z@lLJH +|m4@Mm%koh4*WR5{J8w^k)70b>!zej8uoGCVb$ecO$>9g@B`{746rQ2Q_qEadxkAdmu +`vuCQj+&J|w84zqDh~N$0n=+&c?o6`gy?wQwqhvuuE<7hOXKxT*d?c^V$t1$9;{$IxEmTRw}Kmp`?8c +zgQ2Iy`Xhv%gPu{NNNCMb@ka_1phg-~Mm2pP#ZS|LeCK`h&s#*Nc2w>OXnaclH@YZ~{U}m_jKS +rf?J`K@y`t7>0HoJc=Mlia?2k#rilSl<8CcWO4= +golvN-+@DD0Q9)o&%d!CO10&{=TRthQn|BEJubru+5EpmI|tUjO74K2E@wxhH1e#KSjFa@&*NJ@+wka +9-`_#%$besv@*5z?sTUbJR?FQA~YOp?2Te-`yGTtF8UrodJq)-}(f;r7wQos5RV(I6b5JLGdvVrMk0@ +rkIsP5J<1i2iU;Z8PIjv4<6~*lxY9>5nRIpz8$~;kQI6e;`?Ig!SG=@_2v<&l$|~# +c0{E+Z_IIP!zMa)37*m2n0x)Q2Iz4uBI1UGeypt_z+6vu@Ec+A<1*LXd2xhdxTdbsW(fuiAJRV+Yf~( +)G=mPg^YuPi%5!(8NgbDd6zTr{6B*P?SXS@*3ixb*iP?Go5=wURZGunODNiF!N7Vy +evo)eYl7)s7Ed;Fh@hMCTjA~|g_Ukp#1z}{C$x1v#rvq%0Nb*cDZa5Lw|YCc_pegxUjGtUp0-qEV0<- +Oo=}}p)*A-M#n>j1qqLG?ar%*5`>)Iv^#5P8^@pwfeY5ow*7dif>l9?-n>-RxqZYdP1~Erubk?bwrV>hcMB#rQHUZREsea2s{sLR0dd0J%2antN^Jm~ +rB`r(o1U+Ss*e`f<**zWgOm^NcYLpF;ysM>_+-d7zwvF2)I<8af15A4FA +*&eFPtn5>4$uZP<^&>r^?G3p=VJGD24D~to7uoMLJ!LUeLfe$)61kOie;#TmZx?Q)(@rA#=b<%!a^Au +sm83_vf6ogTa5<1r;YHAH*TT)_tdG +}|0(@g+uMzqRp;qj*8ZMaH*N(TB{Sv03yYMgtZH*{sv=xU|q=xT=oYit|ZI?$YrNT~Sz~`0NDI+M(Sg1)t9kldIOWf~FTEdV$r@mOGunvFCfh=8x##McFWU1K6 +K+34Q5e>rgJRsL^B49p$1-Lt3OTTvocqpgsqOXWx$_+C?mK99v%TMq<>$1cZL-lek69Q-1F +s&1NR!Ly_L^#_}?N)nG#xvFQ-HzN3)Xo{b(G=!`r~B&e^vin>*xB`0gQ8lpg~PD-y$hCkCA<9isYmtRXSrACHXZL8Unz!tfSN=i +%@Ib5d`wjgY?mN2aSM;)b=QE9Yh?)$iA%b791JQR16N@dxlq)g*AOTI4WBo`oaP_AxUBu}RQ-DrCa*OFW3j3xrg_E#j_2$FQ`;`UcBi0z5T4BWWGo(J(j7 +%Xg(sEEdn)HQw5Xg=3OsRLoq^B#c^WpH?~zu+p7iGkx6KJ>;(On27-!yM#Vu +3Mv)@-Da@Mmy}9{rPkin;e>$Gda_pix4z<{ISCSY1^uS??v$j5^mHn|FFsL3%t8ZQ|ne=^j05pDBcQX +5-$#Mh1TLUsvGqrs#UY7x6@SrZ)@urr +d`44Hj9oKa$SHhg#2@sIexJ9@UE}#Z*e^`$h+wr+#_LAjRj>(9`GbrZN`w1LCjp5}J;1C#Bjr_i$p~A +}XNq}65Zwvc93FZ6Y$lMZJ^0xUjx}Dq8c;w^$P!+e!=pt80FG15Vbb<#)I$UoCkz|qu&8riAbtLr3{1 +`)ttbdh+p0`V^Lbh@NiawMi5U*>PN6(`9=1*ST-=DbC})L(@B +lrsuWQYR7HhJ=bT!{%O5fx)0uX6AgkV*Pr1z&XHErn6T^tTwE-~rxz5wMX2w?zKPrDD(%8j~W?Y219CrRWHUh>xe|)Z$kdOAL#9`6qKAr4`nE8C~4gb +@Gae&z9VMJeKzR3x2>@|LDX|RQ2a4eTS(Km?B9S0uhu#aRh~tHPnPxn6-_NBq*FjC=~u_FI$LgoM&Rs +8?D$V*%M`$rb<;`v?mR-2Sd{@_GSck89V9ttYO~Yu&^~q6W7FU4m +_8m>~J5n#MYt32a8Me=Mt~%W(mxXg;~4p8y=;cU(4U0gv#~;yu)58$tAdKLy7JF3?Twu`6b5{Zeb +h4@ruxvaXAiEvX`0<9UVW)F=vz?%R?HP#mD2K2XwWC%yMrYo|I-}-KW^#o?C77gqBR!NBv$Ry<%2YcH +7H~-Nh#i(=t;%B*YM_NtT7Fn#tP9eai!>E{=ekwOTjT|%ABhS((rcXc)N7G+BC-cyfE9W@t69Nb$U1J +}S&<8feOt5`L1CjE>8hL_DpGZ_R43glQ=gVEj4st54#O4XARx3`{urU)T$oYJDxqtxzn2Q(X~TsT6=Y +rim_b%T^;#FUVShH_B6X(AQVjb2uH6d21dq%M?X;LlTF{lHplKN+n!#Jshyrm6Y6d+y=>w8HyFz@%S; +pb@aM<{LMlkEeUVza{v2yN8woqa17%gaYr%AZM4E&OrulM1;)NAA%^U;YoFwm>&g5@SE%d95$+0aWFu +)8~Tm`E@6I%2wAS<^e7%?U;D+ac%bSLzkSMb-UVVl2|xEWyKlXbO9dz(G+p&zFJtwAhZ9vR4pQT;T#< +)7Z$y)3e3bfY{Un`KmGn*P6soCP(ZZyTt2)GsHaDOg3>R6NnD(I6k>vnfevj(b-#!JCe@0q;lvK^tI} +lf~S(msXjZSrh1p*tE*IadWaZ;L-G-*h}o>iI^rJ;nzZamh~s%HK*p!yJq*GS(FShfD;iW(;<%iqXaT +8;eZEo?zm=y8L)Wpva*Y~YxmhMmLbz<6ac$|iVK18*x6q?$2f1+tj!^54sz>j-R-c +FwH-Oxr>pv2b5YSa|ze!V{+9BWxkv}mCmF9mk6AX(|F=>*_yf!AKIS8Bp$DwVT3YrgdLE71`QSteF;V +bbQJ6%kQf8DBvc4?J|2l9-gkn;fPdxZFmCke@+^Ke8%*g#Zm5wo?#!t}WX{)Wl~W>krSSZAKpj~14jCc`0zI#&{Iw>$VS0eTucH)cy)+uw8G2H#>GkmP(D=&qe(7FM!o`2&*DmkxkUMuYZWG{@%HNgjc^f@2?RSL=h{v#Yq&Q5Co^lpCK +%S*mGLg#>-9j6gRc&Gj80BaBB^N_Gzb4yzw-Ck<(gVzU7jj4a#gYy~$lu1>Fmc=_W?7$vG%V0u;1&l3`mBG2Z8buLmJ;%bSt-p1}(Cd0)|gh`o?NVH?*uM +z;;Eh>Y6g5dIQj`CASj#D9vg7SJdE1YxQF5Mi~G{5h6k7QRPV*2ykHu_`4&tvuv5M#Z0@tO!yTW!_L0 +u)(bF4wj7iPj&_TY+HYKR~yp$mF)13NDG!8Ci&ot5$EFT3?PiV)7%~)=g4v-csyMKS9(Q*Ey?;@7GlpC3{}LQuOAyPY2^76-p8yCNAE?4{VE$4O|~3L_rXG6U3ndEaT+gz*P&IiZre9*4uvRxUHYLOeAu?Dx>4UnqX<*JeH;tUdiuq2pk8W4a+;c+b0)C7tNwmJ4vzr>Md7(bj|*(g`fXhaa020Ev-csJ^Y +pZWDzG#zNSWWJ!KLZwve-&Q;ub=Oqt!rAW4*v0IbaG;a|GY)W$h#)^XIGQz{XF3M^he!+fRS^h9P +Fd3zjovq5a7fgWySNk}W&IWq#KoMFVhgq5MkDH=2=FHb$Xty9h@@?Fau83Mc0Us1uCe!T$xAjI;*|d^Up +KW_<5+f1dg7y(dG&q7OShip^xLVQb~u7z<%GR%THpSH6M~QsmyS6cW!szXp^8$4Nn1a^Zu>)pz)0oU`J*&nF^ +Czz)itH`pxc``ogo@FqnBHSZ|_weIk?a~co>J$@Xki@u~$AUv+iVPyT2ne>e7Q^L&>3o?SR_-&tA4K~ +==n)~AuJr;P$k`|Ya0BI#q>P{V_z1t?doo@^WM)xlAwUor_K}Vd53Q~}^pAPc61bt7o2Rb=Ke;aA>P5 +;qFwWFM0&};wKheqp{*Zx17;L?9>%m^b%e%n-+l8-0;t4aD}m1IHnzxw1=cgO!L`oDjJL6G|G`!oO7Z +|RDwf{*2wB>rvalHb~|`}&?wjIM}~LNNGes1OaeGGRpRQzr0D!5fS=00kyHB15+M +U>nyf*vgF|{8Lm&?q|TnE>pV;Y$L&j*l>L39LIYJbst*4qO>sFR$U<-v7)+Pi4ma4&Vt+7B`aLrGK1- +MGPX}{1>WUdRt-oL=fNVm2e}xM9>xK&FuT5sWQE7 +_&2hFH%Zz>zh@9DF^#-9~R+WMRu###0de?Y2E;;X@%(f7Jo{V8B~g4D`4z6dcn7zlh&V6nk>^)VFf)y +z+Qyp=~?Wg}}KpC>A8UdjeND;$kU{O=b5zFZ}{7)%`^QnrS>U?5U+;65s!WuOSQvJl3)WTS;}AIOk?E +05cH_3h$Yt@RV>RZ(+W-Ah$^?(Gr6Xr_N%?1o7!`1{xl?mnptjjT{~22|4mKhl8oru4TRg!W_QA8**t +?icV+ZrIQ67w}JR*w5}4@K0{o&+ZrSXE$t{*zs2bJAjXg9W0(-{29hjB{;}FZqm#H37^WVu=U&P4A+9 +@oG#bn7_do5EuQYl43~_`23pzyCQFkwz|9J7}Y>H1FI5;()0KJQ=%a7?_6h=J;Edk=t)fEF`5T++Siq%!E6?)ny-n=+ePG8Q)ti{| +SK7PZX|dm)QQ_ktLyTz>5rKWOLJ3aq%(;FpvMJ<>+mpv)XsZ2?NuQCaHc +@%MKJ98Yksza5lSfAENS-VH1Y#t$Ae=)X2$0f;x;<9I%_M$cOBq(Z#N>hp`cqOaBp8i!p*6#?w=x +eB5C}eF#355Jh(NfuRsI^H&8)^Unayu@b+M1nL=BtDkZk!A{lHv(2YI6UW)hyIsxJBa^QxC_PiOobu* +#ZrlN^OTfq<~HaJD>-AwpCfGZ|8TKxLd`bNTC_7d9pwB$Y|b$w-mZN&O@f9YS&+F}nIUB3M)m88;ga7 +m2&o~p7`_I5D;*vvF8A5IteGq3x1oG$QZUiZ(PE1XVS$h7^ +*p4M)}mJJdQd?=kF6+WJ2qt@`4e4YZxZN@^|>=$At7u&(rA|tUE)D8vgw?CYHL!a?^s#9;*?AM_2v#? +y@HDi{H}ah=q~O?6N47TT*1?)DCa`A-h9vYPXB`W!$r2Y18fRT(i-x{Vm?N7h5L@4tuLx1ks89&ePjB +*KU=Hh``4@gPTt*=kORkmc(Abkqy~xqx)_*j#9rR!6qA9s%po&>aPT)$KVuBc>NE?0h9#PjbaRebJhT +J?trs1K?9|Vs;C#J>g;SkSqGZ$bG}E1vwpTxdyiOE7|7yLN9$;p@X#)<0tjPDQH#I+$25)qQfDaCvVk +L26V)Y3%F;1Kt_if*}XW>D7N+oIm$<+y97DAEaFC3$P(SHE!9HUEIV((X6CU@ykIov~Io)#}uymdbR( +0{Ct}+XlC++X +K-rlJlDg{XUFk3#s>ci)%;`cyEMHU(o;;gl^f +eDCpKd4B^+x!U5-gXJH$_*Xjw{%VVVwL@Ur;@?-M<6PGnRh}bL#%^rJ0ui9oiLPb`O}HWW9qin!5Zt8P6FzBN(R+!r^eG@O-|58N|A6&Q@kmh|=kJ#Nh{+!Pt@7DvvKG|AzKu7p8p|<|*3p(#?9rSNhPL*_dOAcTr-&bHxd#F13NHeC;bzNz +`kL9`sB@NhX%j>&>oT=l+>n*1QJ(bwp(i1%lTx?=w3aTrMoL5#ynJ1R`hs(B>>QS^Swmq|{#o^oV&v` +8h)dR;Crbq662?y_=7k-?A +C*#fLVg7?E$=5;$K(nqm)Z;5B$0RFj=bAG710MKjx+aa$Ux$J@j;CT?uw)XkWr2|UKv@M0j-6X*p$R1 +AvA5n=9WAJ55_PmRm-VS!;gDDJQ{!J%F6e#os_A+lz0-7nnI{Tm{n5G}%;)+m14?b_t(u)5boexf5e( +i9g&(Z|Wvyr3FDKm$k@=(xIak|S>|HcmZw}dNcNhX?R!9W!M%L2D<7mMi{jge4sD1LN*Z0>0Ys8vjSh +y2;Y?Hy@g`PdAh2t%<>sxhxLeIzN3|z5Owq#&PE_Cn;$-=#j<~Un|nTUqi9Q3r2?QtC3?i +&H8I;;ImR#1I9%rB3BN(j>6wObrS@lA##5uQ2*#svl2H1ii +S}@YCfmbG-$Gp*_p8$(_U3(DQ!7E*Fca2`9p?$C +$RQ>Et1Y(Fd?596?qiG`zw(%3|dRKDU^6HA=TJ?-x3bz|(T@IvnYSzYV`M$uRlf9LY%JWd44B4)f90^ +!eNxvwyr;Xon{LfKq%Dx3BIGa>xH;i+;khzgY15F`)#3li2$EfN+c?Q3yq07{Vx+ASoOtuoch|82(cd +O0Z9-2-hGU+9qLLk +V#Tz&bcF>jliiCpisw5z}6)NyOB2S4eh>!NBR2oS&cd6UMzOUtQ5zPoqk$=Si`6v(UZabuJ_C4%E<;R)3^F+4WkQ%r9 +WFceRDl-^Hv7jY6=SL+o!;Wus$9)Ne`dnTZQ#LnHgs%%4AESSOZ)gAL#qV8M>fVLLztv5NWI2C~| +1m$0tr%|+0!0Mh!1rBPs-)b$1ay0t9lM6DRt9WiiXlxB{TW_Y*Y&t{GZ+yF5qt2(&p>v2OwAE&-@l2+ +*K1OrCwyJ-a}*HpJBc`EzjSCZzwFTZVGJJ3BnWWg+q>KcZWq0Z)7Vp*cJQcB!fGCE21bYzN~$WUU*48u&l00q8zcN}_*C>avyih(}~7!W_13w`-Q!1y3vcz>k10zV^P +sQOV-Yc)Zi*Y05UwGd%9YtU|W32x2DXL<4%h89`%Z85+Ht%LscY&@P(4rnsho~Wk}MCdMby?8kxFR~i +GvK)x)!c7Ad5oGd!9g_fU5IFJ+pOGJ2d@4#5tnzE4DW-2`2lvGHxu;o3mO@YYbeSX!$c)v^za399xr2 +PB=~6xA^YM|)R|MsGk7%2mz29Qc)?bMw#;V_o&GCr%)5U39^rZp*XkB#@Ze%DXJkWnW3(SD8W{)~>FZ +&E?Twb|^Kg%MSp(hs7JwFsDKS`4DJm{xW3Itc7G9zpSqLk?ebwyr8mpRe)Ez>+mm5=tt$ea^ogw(@1* +B1*`hqL045y@Uy5SAi9jUXks*_QZVJ!I$-mwX^^a?q}p`e@D%4`g)$dG~bHpwP=ZoyrcT~Ln3-A5V;GWH19U-=l)?5F=yE;TdY|a_k^Y +-ihDRjFewrekJI%3Jz9|NM>G^aS({C2bv3;xnUMq53;dVV@c>)JkI=J2+`@?!S~4R)u~>k=<_Xwd)g) +dkk??qvO-o^SEfH`(o0If4D`Igc@h`OI)1g=eh(0C{wQqvC+?V2==qb4gGJ-9X=42=&?b%Ry?OTmIQ9 +7{df9Yj+-B#=h=8&b&{6y6B6te9XW5JotG`5q_M1$0*;pEtzAe3lP=9bKe19;cHB@d0TvAW+t^gtUeD +}w`*SR{Gi;jNyt8v9+c_a1<|mj46yFh)!rMV|6c9+OX(RIIZS`@qYCI3SM!p@nO{Z@XH(H#pHPB$bc{ +}y1%cbVn`$f3v#Vz*P*s-LQGbeOBme`nCWhVwkgK6XxhivKnw*Yi0Jf-lFJDm?o{np9!VLwKW|uoVTF ++RV4&X#y7=OCP2s1?2P@4cGqmw6hI}Ol>L&s&Xo9?8{^r^#XTxK6()8WAhi$e@xj83gUX1ZRU +U2H!i$bw@!dl?4S*9PdF1lZ{tc$R2&DvE^5eRU0%I05s_nV9b<6>!Ctp1BDvt781V_Waj#z_+9}uxGW +ut3B(3@T_F<;$D`ky;^>ybdPzA;mb7t1TX@hwddZwqBQmPoi_yIf$~f+y6x@=t$>GT{W`7wQE!fT?bH +z_q9R0^vV=Zpf7+xr89tegHxxWZZHj3}_#V!^No(9u3#%tH5wFv2f!jvbXH=qiPriV!A(fwGj@6n7H1 +6FejtRkWjy|EU-kgrm!d(0ZhZF5}$|{?>o(u9Op{f?^(RIYeThAa5o;DA2&0c}_KBKSbnBVE6`pRA>* +BuZ`y;xrQvW0kb8#KPz+EAf~@tg;AC2=WHcBrCCW`32#8HhoQdw6-8*GYa&0?-vj_}Y($elCSJONKsr +$)(X61eW$w$=+w`h|xJMdM`T->e5gDp!U2a&4>EyY``z#kz4q +-`6R0;mZWcgGm|K9+N#Zpq}#pG7Seq=^>60x|2JOeYkL3J*Z3~mzrs?A+JrYYJxhc{Kx*q_AaHc`o)` +pU6i)rT7W^RYR?>ZXW>!YpMK*7I~PTPgj(!C-a2irp1gv4k&6W=A!Q+yktzD+XR9Wb(ozA3s-M1;54G7R0}(%xuvv0}&h7h_4A8d)b^JuzADtvbwC#BJq7s%0r>SaDQ50G(Tj9fG@FSxGO!L>|PQ+YMom-)2wY +UhN(H2cNL_0J;CD^Y1)O1I2u;CYymXWf}K2D?H&!PTit;3CXG`tb%m~qB+@@b$GXXV69wmr57?`7U)? +Ae&Wz$RtMQPE2hw|Ao~2s6l=@M&-bOJ$n@N*zvQ%S +}Fk+{;Y0fxutTWl2B458hI`(pbAA@=G+1G&wjPaNrUs{S&b%LI=l92sZ{PvzOIrrLozUS0ij3Cta>Rh +l7hMwJJLhr-LOE{)opc-f)1Qaz~nmy3H8MUu87ON!aI5@}BjK(B3?7>+u*CrrPyfN`R4ie4gEDbwKngeLQO@tB?m~VA{_ +c0~A4}PfRyWHE%ExTvOLeBcCo62xLbE@k`*WzT2NI>B4+a*gDz#IvkjUN+%mAF0lJxtiHK`qsVwe0zf +4hc`7@cIkPi$%7NN`OU$QM{jQ)x5Y-pba31GtIdN7j5Y3LvcO#FP0ha68*TA>sz3QH+2@N!O`LrnU_n +W=g_DyIg<^j0`(Bq0LVS!rQ&zUl78>#r8v>40#2SGf}DhR-E18P#_*tJ +m+LLsux)UX$w+3!ukMjk4RV=UQ5&{XHM_zijfr*g)jxUDhlQc#&k3R53zS-Z<-^p$E41oV6a0?HJ_7AMe#hLhyal)bQ?-N!qvk6O(KyvGYVbmLWj5=VmgSa6)dEtLY^Nqc# +gM51+j5ODWsntG&Bug{i)Dr0brOIQw{ASZV!u(#^el0@veE)+4qk&53O~e@4AWdp@4-9AWaNZ0Gl5;q +r9!R9NRRMEQHrsCHqY1C=lb4wYLVBmz8?R4yiA`JN|vQ}w7&LIPT#4wgW=MeUAF5YG7*YBQNbc9_CZ>P$x0?|aGnjWtJrlCh3 +J`FI{_Ib*wuLnGfbR|rfoJV;|-iRP$=jHuYF)pCct!bdQq%QCKWqrD+cWzcHUlASg@FI$%nj)M*Ds;w|EWEE!q9(t{cj;E_%rmmCR0D;M6Djy|pwZJiSvdotbhRj|E1Vr$Gzwp0!rY+5DoJ|_~}v +qwn!ALK6!7b3e!s~@#b^vTe_=#hB*K>5Ex(i`BG?ZqE5;>>*oiK0D3i@+#v1>J8M~ +7wvv~yh*SDl5nmf*v_L`Z8tLEimDzfG~3Z;G=Ct_%lC@m@73!ZzDldBU|xmdy9!qbx5CrX+d5;N_3@@ +d$1*DvQY+6rm%+xoYgBNBb>4p!Pygn&9fL= +=-`CR`Bg4E?TuVTgy3yml@JX9t4?!ORV#)XGiwpl~2ua%a^b8-2uPSx}QKGfEtG;mB~iEbdS^@;ALtg +ELP;Blvlp1%g9NITm_=L+hX;tp_l@94J5pE0)jn*I199;8w*!GJrZXG@}lusW7L^Vn|IgM*x(-gDK25 +&QW5jv`)1`O5801OILZZFlETskD0U%XmoZGFKu2?W6`12u;q?|MWG0+2#TAZCad08k<9(HiZtNwl$6l +3}R&{Ca#Q|BGxicogZY-AY$qG@gYw+eF=_w?LdeEP-N}F8fgoI9-*@}UMqGAZ(TYslzsrWeHVg5xn87 +=3rLbOqGc_jM7B^ew;vV%075ySDWz6__s(I;);mKtC^i<1?V9^pg2AAq8u^r1vmH3YUX9(UejvhlHLwh;45X$o>S%0)Lk?^KE@ +9y4lUFl;_d4V<76+GsI_1BK+N^yI*o<=#xW)_U-b_8Shq_v)4zCT7eSa_I5W*3DvWhNR}rw)gEl`D~= +t{ImgHuTnh&d%Do7_&F@1ll>Pi53-9=DYNfLk2~bdRvUBZ#RYC-xJJ|$>$gUI?w~jF9a8J`Z+xTe;CF +5j@E2@o;GJ3xX?fX~-$Fc1J>T*0&mK82#rpsj_CxHf{Q`O~OU>?!hIU@yA!fAPHZdTdf)gU>`6c2&)@ +i4~+*amzoy?2rG8sWCYRl`E>dh2z!3p#QhT2wEwVGd_eG!u~Il)_SPhW@HvH!*lo@(Pm!pd`gk?Cgew +Ne~<#i-#_w@M7Bs?>!G%XAmbDB#zlDZQsTu4ZMB5kJmx9pX$5r;wA@Ns#r?l4myGHGU1sxy~{8TMsLP +0YQ^O_mqp?U>_z^{+hYq0F4xO4DqtLOdo*CB0~LrMT;<2p*?z9^8YjZ#nKE9{4SOECju6gCiPJ28sihRDp&hbXx*B_Z|DP>eYxI+bhhaY|~TahpS{0_Z;t6F*r+vUp6NhNQgp&U-Gp!( +U_k=gHH(SdE_2~>#>-|rO$#+99R^5o_*ZUk@~W)hLgjm71X-EBZI(LQ_siI>huAeU!j*bFP!|}0%bo{ +&;L)u+5hQ%{2NI7!yf(;){@XZJQCfM+>>zQL#NorxL%Ph7;n9^E1=!v@8At6Ch1RMEtPI9xCFFy`cnJ +EAZi~yLB#vaM`Bw(j5gr8Vq~1$@MainC>Q?~tVKWiZ?Jvp&nC~e5vs9ZYwcZeID~CU*{$6;+6s-E+Iz +I&+mPA|p23E&acs-9hS2unP3k?`lD^w}A^(FQf7xI&ZLs$K{1(=J-%ztcxaUtGE%(vx_wOO?hTwpI3g +K=X!+s(-ZAEavzlF3nOXb!bj6jIoJ~FObkIYBY45V<;3T-#iPV`pxo*8{hwr-s{pU0*wX!lws@H?3&H +WYoQer|ct6F>P(EUqB4QNG!ByKnp3T?7AQ+j~Zq{XY2$D3(6ypV`-?mwe~lZFO94W*83^?!E(UkMpqJ +dSdLwcZAu4v&?X?WnCBq;ytB{En6RqK7X)-bdcY>a@0TRi`m;^&Tw&An+o8yKaxJ7+Q#?T)Y$L +X^T|LM^AJ&iT{T`kO=Jr%s;8)tae@lAzq89**%Kn+=wdOM3xe-1duqeA@_vatxa(wNJNj0IC(DwN*Zb +9+@D#N73B8il8T!SD7e#ZjJ1T(rob9UoLL&_3>>-(b +J@fZCit$D3uIU5IF6q2gUqp4t~Ce__Wdd^7yXp7Vfr9o4 +N?hU|lXKLo^rt;z=Pot?qXx7;dm$gYvUi+e%A){CmGfQIXytxh^dA2;|*fIgdtaY)2Gc^b`MEpq&=UmpunX|jiE1l&LSWuh^VJX^e(-JtLD4 +ysYK#PE{>!93`dVP=Q2d|a88yy-?jkSRE{?|K#v0B9=Ya4#XmZPJb?kNn8z^?hWD)_(`qqxS +3hnwIZr#KDJ@g$t%|btV&qfgX_B#Vvrwj^+)E$=b?F>yV+ZJ1K{#GJsa)mJ$Q@eLI3&p(c|$Nw*#%OP +V~ihXIKn*UeYg}+cp7RM{F +cb8UQ+FB8qk|O;>Q#|?s=9a}iMx2luztAL1zEpiT1Ryv`G2WuktxNQtr|>H?7HL?e(u +z#V-f`bIQQU!ctNcBa$6?SJn&=TwAq#WF%4d@O0sF}pDklhgV4gwjNTE}+Hj3*pfq_z6_tj(U%v)W3L +a~yNysq-yibDkNgH&7!mTbDtOj_Uh-2`OmX5|>q}~5&lmzNC$+g*zZDVlW&;U~p(ebeDt~!z0x_}11z +pZB^uDe2qXvqMV)sWi!~D2ty&+D+jvDwy8Zn8it{&}5UEZt}w<#T+&PLEQ>e=wcC!1V?xesQb6IVfp{ +N~Pe5-R$B)KiJyS+wAudi|!o&>`*5M$?>5Nq}WG2Xl%c!)UfU92ZA93Fs}dLfnB^MyR%)0$x_@a_wsq;L3WBd~#K0doAkqtCo3z^n>)P>W! +sA*vM0YZ$tI+o~MQ+m(y*f{YgFv?V#q<$$`P@-Q=wPJ|WJjd)8-sqwv;-i?jcOgnrt!=MSp<5+52i?X3d;eIGOv~=;)0+K +lZSB!D@v|BQ4Ogs2J+^*1g4vw0hw2a-R2*sW#nS*EH)|&nb9cL|Yw|6&@_X6F6KMX?l+lIf2>|=0jC4x}U}oA4es*^4_d}#-vT%Y`iE} +NAC`UzR+B%Ko3h~K_UUNveV3_A)J&!0F9l`M9#sHNmOtmGlVg0(^bYU>;RVb*+k#*Dp%mkF=#qK;1gJ6xYG5`U(^p`M2y=Q$4Egv*yenm-XrHIhtwJGqZdG(2v9|>mOJDA +prg7S3fQe{OhEDfPj>CNyS#pD*ObjtTxW7M~e*S6zi-?lb+*Oy;_GOHC^=TqCZQ)#%#b-MqU-qE!awc#zbmGZX3d9 +a>TIE~g1iw98*fz9(85TFo{u=FnoQv#|uXh(FGw1;*i4~e<#Z0+@qn`)W-SM5bn$KGe<6JXAl$$KV+A +ZeNF@}9rXoa8s6p~B0Y9omQq~zs}a{xcu^4(oW)ryOo#TI--r8swAP2#UA2lj7GG<5n(#;K;)oS+Yd{@4gN;M$uNyV-}kp2dw}Y(k +@GT?NAZkc4_ldm3sy0#+x +&|tn@GATpa{5fJF4d|*h*pyR6Sd>I=h$sjp+`idX;+#95)DzVe#y4aQF;vBGH`noO&PhDS%QP&34A*R +rnZjcp0wrD-^U%*?v-Q{tB&FoV}tawRpVGXQ1Z!vcl)Q*@0!2XxsHUYNu4pJ7X!BfVz0ZGphI#&6qcW +l-rze18}UAd+=HDSs?&M +FRRP7;ARDn_I8Y)9yO8bEEo3B=3y?><*G=>6i$vPIdz##XmsrfMhD|v>+3Lhgr_2N(Nogyyi#t-~jw* +_*As!)^6*xrBk7_FkP3a#-@+Q=#>Be;uIHWr33aRBeri52S1LRhj#J9y>=iYh{`!SD&_*o3v~Yu^v~zRMtOQo@-wx$75ow+&8?f$ql2Q|*nsidX!wz40s58iy9sZWQw^8v+aej +Q!mDnc3gK?N*zR5Qk8t8U4SrHRi`XMlX4FeJBYihZbMpes?jrSc;G1Ukgn2c3VkS#<3uiM?f<3#TytBd1S2R+A}9pm7>-gXv74x$ipOH;r?DmZ)sK%{P!b>QGDz}il +p%hlsG}x;emLnv>era~ImKx7A@8J*#Qp)c;!pDbL$d`XNAjPD5Bjd;BV`zdN9O9F(~5tE|E$J9#YeMC +{J8-vIk?2A5476h)DF}955H)L9+%ksFP87pyz}hcie7mEsH3lR79Yj00fpK1Y$UR1~kV<`h8a(_+fAQ1T>Q7|Re^E*lz7Gxt_P8FP8}j5KDWo*H{>ICKv~>l^mnput^DM +Sk;CfYu2~qC$|^5_mLg?EcJWbd2$+NF&!HUZ+Mv+StC@lgVRAO^<9qL|PYCn}sWSmo&5xrhT-7c_ce72yK)v6p!rI^J5Ruxvr%vt4U=F=wtTc3&<7xBBcouD|K6$cul{j(2a>5In +6%kxQ)Ar?ARAq!U}JA-`~bwdYHE%iF*umaF1A39ZiB3g@ExBS|d3>MIDQh7?j5{9v@^C%`H(ivYxiFQ +v0$Da1j-X*=vpuQRvP^7!5kb9|1p}&;u`xyiPjxpTtFo0yZn`A+xGOML*OdMZ@3n>|ow2^)mF965X8D +nrRRYbnwahWjeUh+B16+PV=j^%*XBJ1eC=SJIwP^wwF97{?dFK3N@N +Q}6tt(}d#i@9VRa1NTZ&az5hNSQzoEv6*mCM&6vtpq=mVidpl>+Ef!Di>#d^#LH%-Zj2~)Vu3jauXY9c&M}jqG3v +$}1gyiKkJ}#i-i`NL!2i1r1^Jr}1^F8e1^J0Xp%99~X#&M@n1U%3+uaL9;1rGE5VX6S{dW{We;TnLZU +R55&0%~5*4Rhs_Rs@?j_L&-{z`_&cn$J#I>gT%3ho~Pb(|bk=7{{-j){K?6y)b6DmZw?jtT~T=!=BOk +p#ek-7Wl?8hP*$S3y2S>+oH?VuXJ*215Vz_N6qip=$vH!F~DgH^#0(sUe65e0 +S6-b@Ztm5^$B4cWqI`w!$!bxGIB7(e&(2H_9!-&ehC20>w6(Pus+xbb4S3)hYml$}!6FLYUGt>%vY(Wg~2^@T)UJu1?@M +z6g6x==UrN^qD*_W)u!kLkoF83<9zxe}1#6qBNC6KJ)Yw0D#!93iSCg}x{_8mJp6riov8rkO|C6Rcuj +J8`+~z?AH^Z-spD8)i(`6Qo%tJAg11yx9?4e2lD^%kp|tiha8KLti?C|9+pHzyv6rgto@7F@bDHp~_I +CMpVjcl{S3T-BTR+&`2MNY>_&docufFZH92W--EF`KM1b?Dr+r4n0!l5laP3N@FdG#x1@D<|9Z`Wkhx +@!eAjm?wmFE9rL>f>o1Ka8{b +SO!YtXRX2@VMWbgZ?3@PyQ%#vMy7JSl2wMkyJhJ*rLh9?yeJkFGY8S*O4lx%=$cm(GRkM`k@k34kVeN +-F9E@|l`ocn&F +ECh`{j5kyW-xQ3JE0$^H0jjRq%bX6nQMm%rdDq5lHT`PJgKXb0vWNLo`L#nD;c@IorWr?G#T@r;NUU% +SyYkdo*?;WgNTt1&SMm7b0;)|#3xuidi8sUR$fHGXdIJSjGaxGipsd(P;3%1TBKu(LQ)-hBseS3$Ybc +uRel)~rox;3yeRMpF#o$jS*@GUp%p+3c|Ks6P*U9QNwrWFv3Rk=QT$uPB)hB=k@i8U}ATPPz8Z>0`-K +8>`X2c`q3OVQM2;a=bc$x}gZtCLs)Q*Tnd!s=^qNg|%Y=u+&eQ!_M~Xmxj}7GBS#CuKb}Q093Fp1u(% +7IlbCj_hXfAdpz|EfHlrE^EzsHF_^kZ6#`WqCB8YsW5h4*BzW-3j#`w$JO+;$KOau&}?J78G!;diP6K +odV9p>PL-)4*y>qjCroH+q*f4z!H9xO)F@xYWzlO9ce+Ye$>puG#T2p8TD +SHBv{eq*{ialv6WnY>8EStUO@3JzhpIZdx$)lI3@ygVVA9@UhVe@<`Husi8Pn3>M@QK!_%#sl<2gW(+ +ov?BAI6qOmt#5?=?#~3p^wb0J{ycE6o4xz#9FVI0*e4I0*e24%+|G5Dk$8fzTMagCBGcr}y9*qW2)1+ +F!?Th(qiR-lN-}&!j{L%!#4{vY_Et8MysFoI1ut2<&+4 +ml+o%K2|+uRnVg(Li`z5$Y=iL7=BJZoLC9`(c<{fUdQMGq5|sZ;D|qQ{~kJ{@GJ(&x!X;yjMh+j=j4Z0ck1pDDWiZ47ikAmDcxX#1 +xa=nwqd{}clq5D55SppD&6Qon0Z`_{0vhjJnVNes(XPD^=gLNQtermO%50##mjtCJ(^Wh(B;@vPUKd22&k&zjwCBUx_R5pD8mG;B%f%|-8xL;?I0N{gR(r5+f^x5Ks=+MhXr#rb)`w~G+)* +!*K3W~#}|nH8A)vWJn+d$Yu7k-U0+7+3y=Lx`(#tlSe>xbk2T8!J-cv@qP~WY2OWhUiM6ggEN7`jPXuiu@im##6EH09(RSGp}FLvynQr~A; +&b^VHZDi|NBPB4~fHH84v0(9!~zf@t_3qa+zBLwD-@~Fsy9Po_{nR+ke=2K>yzv5AFOH-L2ZS&x>dWL +8^8dglh~R8OR_mY=TLym*&;;}LkK!u$E_UGu|$HQJIfb$> +KqbzK%+d4I-on +fUBbRe$U0P9oUi?ThwYA0aFrQ5IK2A>nzOIUojPj*aw?0Z<@BBfHMvK7LCv{=|fi-$qtn^^6zg+!l`s +J?va=$CTT$|TnMGiWbmp|mc{Eyu(^;7jJ3_Pa(m9OzHY+mNo5SPEa<6j=}>tKZ`^4Bj+?+;_%SN<>8< +1N$v-fvgXt^f7qBEPM??xstP`&Yaj*Z6t|KUl&4Ki=Or>-&%H?K{)_Gjkh-2iyA&ruJWsQQPE0L6dw~ +PlFFhO!N_4gFl?|Kj(GZk?S^VO%LFNesCa_9D^G%cHpahu^rgO@vq^Z<*cv|v3`g +iQ0xm4e)O53z&^xrJKoyOJLZ3=XrhnY?kBHB1xIdehjg)j)I<>axXeLKM;xug`#J|~B#+X|UzppAdvu +vV)4v8S{=ii8Wqug?Q8J8|7T{krY~kY!MWtJ}<)MvnN9e+R+Jpb#4=eG_{NlD~uRc=Z-ks(A=h6a!qm +=>2BKrZ$dyZ3pEN{Omd+EZju5%0-$fJ8_mB^9dz_AXY63ez^*}m|KdWUA<`c1K8H5K;>!$%wNS5EqVU +jRRh`r~a?%ENWrR`dNathkZ22K&Qt4~MnAZ;v^cem+|U_B}7kgXz +C<2+7|b(lOQ{l$HM+_XR%Gw3@eH)pOeR%b)qna{?ZR{_)xP=F~=;x4FA*viFt!_E*vNe~$-uY-jj>Vd +Rwp`#v9^zwuEB`v=6YJ!;J=(UE{p>z=|&Xm;?7;xPjuWPQR%qkIrXX?q;Jl$6}uwb#30#Yc0)Dgzs_l +_I19&(|IBD%}m%K$9u5w2WO17Pt2)xEB0U2H2=0SM#d(!DXYw=p2$rPH*77+up2A6A}bVS`TRG9L{e7 +X28YS`V|^OXJi=qu{#!xzEEm)EAP}|pD;))D>C8$t{)l`|1^>r8|5v7P%xp97k0- +W$d*z?!t^tBCXg#km6oN>v-qiu0o-dkC6*B +G&<8p7xV0yq2$7qTt#0-`IjI0S*OtxUW-1zT@YrmV9^&TfNZvx?_L6ptUFnI1qD_umurqX{T1M@SY$S +)+XTyw&b%UX0~R`HAC40JD6jSS`2_A*4Vjl!E<-I+}69yn9s7Zpjad6EH)7=w2;g*!<|a3vj9htda4R +x}vxrpQv9;!e||+Rh+U@!kS*yncO3sBvG5WDaf#Al+^_2C6%HU&B(%^arK&Jei>3l#gDa!?LaS*ez2# +52dLTA^N`dy20x~Pbd3s!hmv{&qCjxm)6HkN5Tdo&dLHYW0YhhTT?R-T{-w}Ti(rqC@o&;Ig2iaUFK2!K@w+8{InL5e>EZsu>HqWpQ6A?XSz +rGn3w}cfKVR(kFo1@jJv^fc93n9YMj-g-3O9Q+6ov<%$RSaR9X+J@XI2tNk2vONPNk061*3k2lcNmuC +yeOFYz}pFE>NGLnImLE51fF0(l69!a5AQkD-gu7-hRzPTlLQbScBxq^z2|_kH8MO9rQCl86D9f=I!wcQvOODc*t>TBLjL+SW`h9raXA7sSbeCAW`7OZ9@4fS-`(&F*6rrITmJEmfq#0-Ki)C$w{ +KaJxS#pEFHE87$Ki1whnHR$#8;n`nn95m2jr$|`s{&rvcj%JBUvVMiS1RlCg(^;E`W1ODCol&n36Z=r +KX?u{jw%}KS1leTP4AH6hhO5)5VU7G<)W?M2~}9Ub(l@=MGx{kGkIR!bCSEvQV!JcO{Tk?`}hE^uzAt +ic<_Sw<`oyOV{U|N7~($%d%6ixz#y=!3nrp7(G_x&=2A)CmM6IM +0k>56DHK&V+^6I4kx=c_HyXyRsB8it&x!lcvK8@<3RJ-tlU~QG86TRd+4>5G +I6ZzC}(S5rL=c+dWv(|Rgz!McWF9+FZIH6>5VJU1Mr(e^bi-tm2y8)p9~vsO6 +=>0l(@GSi{wKvZCt4z43*5todGQ}&%;7dmS)jBaN(=UrZk`3SALw0?2G?;Bgb%L3?%y#L{}ZOZ+|*>2 +~8E|+^H`B`cH9y9=dnZW!>W6MEvv%hTK?IZLNtRr-CR@ZXvMb`By;A}=T&sqVjziAQg5vPsXacRny>9*H{PPA@SNIq_EQ_SXB=IE{s2`@z??BU^HC +mi7P>K;ibY|__Wrvow;JYB0%obZ&E)kymOyiq|iSfQuY3KU((9l?ruyM-5be#8B`e-5e=bD*f`RDURf!>e%eO~$wcu*}Ez91!(*6OZIRbxI5V!lgd-lr~|9=^}C +velLA`i}C-75O@~ZS)e5H(ShZdc1q4h=3jH)yH@0l%Q(R+?w+>M~&w!q5(u@ZTN=%iGJ<}1946Ng?1w +Dk5u*l_~dH7y>MTLF|H0u;a{To7xRBAA7bI(Uj7%gaQvq<9RJ5I@>{a!rx*A^^ae*T41;K#f?)_EFzV ++t#XH)VNmjzXU6cK=hc&A4C!c3C6D%@kUH`{JFG%K$PgipOv|A`=z +pYcKIxhr`R#xY`m;n&gdKXdba=dTUv~#~@rR2egpV<7g8nqmen^RZ@&Ja3e~20GCsWwL)DdDwY2eTd_ +^{1=VKCk|cqk1VbjN?0=ur@KNKxDvlJR~zMkEQZZ+Qyf8+Whq_+37onFUMWr#-;WRMVI* +U$`E`Gcy|Q6VHO@B{h&QOvAuIHT+dfF!)Npdp;CrEwtPs!5+hveJDBm@^(<9yyi*0hx_Hec;v-zuZ9i +6UEYSXes`>J}41cP!<-ujakM85L!_Y6=W#?J>_KXfp`?*E=%;eudu>k+;7A0Q84_e~j!+VUSo?xX`j; +r7JkAbgaQt|H}pz&KOMa!c74c`keeg~Ytdx&4yG(Nl?N3{d`TWJ^ITi)f35oj4JoSezMy40eanfOqsis1@Bb;X4@0KC8c?6iF!mb{!3oAC9i((UUyAiLR&&Fn*6=CV +U}MhcR~c>ZCl0lNA?~33dD(qM^FWCDW=)VPD;-`>3&@BM;zhv2)w@nsa3>)+J`E?mDZCAmFGB~nRSeK +F3C<$U=~Jkb_RSxWRDWGIGCYH~d%jxK8bmHqwWXFCkHHo>ne&7t(x&G!kqBo47Z=M?{t09?6UZ6~UZtjogl;~GU5+X;es0F{Jb$Md_+ +8qCtVa1JHYea5>kAje*Y!GhHL7lw9?s&ILd@?<>)`-qcF1eqRr1*G +BC7kB=NdZ;(M<)ZSl38Ci;1JGFuW$fD+bM+OhWSM=NpDsI=Lo#s-!XvJu8*7VWxZGlHf<40oO8{Q2dqt9t7U_TXM*ea?(rT1!;=MMqbo#EPeC%uWkUp)A`6!}8Nq0LKeAYJhdLsnVjKVkWZ(w};%{QPPU#`}ti&8nBfoQG8((CcU=O7;f!DdE +WRISJ>CT>HLxZH=O@};v)a0^Z%}886jbqpfMbVV4B{45cJR0?T}*}B>4!^e_^>gx{L8&L-MF+BmSqwg +*@n0v7@)W4zG{JhdV~GW8p(#2l=Q9|Jf)pL>|ec_#>IOU;SwD +2|iVS^vDbCcm<`8qPrh|>gd67kpAfOV4uqeAF2DJdjApS3%}?j#K*P6V;<)(Nn(|a<5)KSEtezvVzTb +>!qfOelkO*%bHu;jCWwKrk~r+|Sfq9v<$d$<3x8(rUyDF02Y>Z}clP{^fgg~5)6p5N-+7=%{81TzKRO +1IV3zxBPY>=pIRjPPT{!TYgZy4_xcouSAo<$Gvd5V7^Y=ay_)3g=3@{!I%HwbHK*hnZWgNbKRSWHlI? +y4Rw;l5~5~6P9Az|?Pv9I7C;~?N05c==pAmAGi`tRbPe?7!M;Gn;!c>sJBJ*Xvtb@!!NNe%qjoG~B0&m}KO?~>MD +=G(NoXeMk9TCRA-QRAxvV31xIC0$wtCuAl$8~J(7$NRqXv9w>y0N*CWwOdglAFdzVQGH~g8?(TTyXa9 +Twg9qrrGD!5;YNdgUeNNgNkjZ%t9dIg4A$ +s_Ek=zboj(jBGiR4$rCZl@vc}DA>;A0Wpkn6jTrvk5 +Ymdkc!Mi)2eUa(Q9$#&k)Xedns?gLmU!LfE>xeDQu%l3-r$q>Lf`yg+~RwhxeUVdHR{;sVtJJTJw7xQ +Na@YF$h2|_1I*|VA`w*%yV3M!zccu{WHFrB_j(Lq$#!KJIf2}m_E)CrGb58J4+W}1XKp+$+)l}P@O43 +@g2qV^T)VHGIS4#cKbb}!Uf}ZVa3MMcb$Ftg`&B^~5?i7SpfpJC?q}(pRxe1zFEc64_ag7Qcy@O$%w6 +dTtJ!=+CRX5y?dE(h#QeQpGLm4KArMyGuffe*=!#=J+?{8)m;L~%zMTvF?Bss}s(?Rbm6dV6PG@qQPM +JPr6l5^;1}YufKUcjUggcTE5T`ppbPWfJDcsoZoo92uBJ6dC{Uz;O;Cb;KyaS&_?$!~L^aGmI?W;|K; +o@-=t>sIlEf{oq;#1kQI751GWUdw9pv2+N +Nv7YN%{4t2SN1QDF%Kfz471K3fSygfg%n-@7_S+MyHGPqAbbpi!S6_P+xW{u%T$p$ud +|9fmA#Fh0$mfN6en+s-S}zjwABp}bZwZw#!zts~{R5+2mZ%%wOwR($K*hNC@F)xqkjpmL@sjc6V5y~{ +GI{k7+p9p=r#FsE{JE^7%19^W-GvOVJeH^10{9NG&8wkbrOQJL9V9>=l@UH{g_VSqg8yrIr{e4Ny^Q{0GbM(L_s)Jw$M6^YsT5)x+qe=ONzk3H~yI}? +_wnCwssu`z|`mowrtcF$s{T3b1?-Hsm=f`c<8GvnMhET-3g@syl)ygN{F_^{Xl`e#yC6+pSv9K3cM2e +sbZzVFi-8s!h#Pwxo5Q78gBjYcQV^m!TVaiEcc`2H9*mVwaa!EHNgu~~v#?LR4w)MdqP3GR!*D7qGRp +v2$0-D*3t%g&?Oe!yC?VLrJRxOhy#_2t<+dGYzVey)*+R)J`Mnirp!&dVdTS=LH!B4>KL140SYN`$$B +1icusoTpz=B8|RkDij-O)7`|=_uf$ExWhkoxUE+J(5m5>0Wp|;Ei{p;tTfMSev`C*$?b`m-z>y!T=IduRg;Ag~t*-wZUzR72@4-9r#d#;MWP6+q9Jnp(=@N0Hu$57YX!>UAUtBzX?l747weoX4+LODb$AAsjGaWXXLI{}y`3sAK(A +{Y!1sqLMkuRKoNFrQ3x3AY!O>ff+YpiAsT&OQKo)X5zCLedG&|I`CPGEb>!e24sZ`xl^Kzfc6R8PA6g +HU2Q0qFv_DdhSixpBzi9^89NO|kP4-s;P9S73*Dm@96ZM3lvC~N=FpA{U7L{s$pxVugvdj&^{!C%z{D +Se8SfT!%Wk7yPPUU)Pkn4C96ag{q)_>J_a_7B_br9jg;z47nZ5ihkID%Jknl?#8{@!vBCM)~C6|D^VG +zbpSUY^tvl5{?XV&l1G*b-rXtmVLtJX0#gwE>VG +txG5YB9+0ERaP^jQXGZEn*c`776sE332s7u8kfzA*?B93Ae86U?F>7nRoC&H7D$dP}PoQ +}EohW_;D9u6oJ==G}x3QKVi}X{&X<^_PU|I+9%=LxW383!=4qsV!>1=8FqTUTYV +$$C1L^#ABp~wp*|X{CvSh3apL$EQ@lZYmNMp~n4{a%Y31J#zf|pv&IUB;dJIRf +|Ip9aEE3p!S;)6(gh<)D7V!GGNX;3mNCvek01Llag1SwqSFrkOMdz5$vZ(#|N>~4A2F+yk%+dVJJ=Q|$YhgFcy`-KnQ+_zzN$Gj +OJ)>U27WUB9kH|^SVXj>DK~2bYE0wM$>U>2kE1>)N_9QB{AsXwcRTEE`Bs0+H1(3Csxi&l-cm}xYXF1 +(JCv{1j63UXQtK=#dc@H1Ux6Vb%qd7x2mlrW9qlj$NN5>8U>yCw2l=8#8s2RmEBQ2I$+;^1FLEahBiacDqvYxYk?#v_YrW-#c!}DR>#Z8~0YJt +d?HLP`gWgJL#_q0M&M>Q1Edo%sRUe9&uW?n0alX(pGI9i~@S=h0f9zPI^eP`Q%>M5L}4m6oMfabUC1c +yEFb%1oMbk<|Q_7n@DW=acQqMS>%jtAl38nrv$@aT9BmKa8=f4cn!L6Mx4`5OIxLSf3?n}OH6t8J?{W +8z~4qN>4lNl)GWE0lv^kiGkkUv!3M%sTjVR;yx?3{J1Q7Z=Da`5K9Zis&B;1isg%|W$iEEcry<{V1{ynb@@!{jHTf``7%#Hhntuu7Y{lwqKwXF&v}sN{ +`DY;@aFojwH&s10^#Dp=45A`(S|oCNy?VUEV{TiARwHWLPn!-Y|mYm+e`;i+nJh@v?p@u_D04#gs5IxdB?UFZ?xSLO;st2)H?#<*Q3?!21ValtVUmzfB>|D +FlfHIS3Wz9~+1^W$BH~e}J&AXk{N8=RX^POHW8spt#?(z6?|w#+mSJkDyl-dasyQ3Hn?D!0(g`eAo6H +vXCqK3G(`?q*c`0L)ozAc<-2!?*uJT10d3{7>7s6hTGQlt1sc`aToPb)HhlYRKe#*U_Ey>`dF*rOAvn +i|!ikwM-xZ3l?U=Xp+SGI6Hj^(V>?=z+e-@K^)EK4LrAhgvu|I)8T +5xT7)}Kl;35Q8eXd=hbn16 +J>@2?)}5MS=#(5(2hM19DO|GMVL6$jYJ?!Q69C=Qa9;+rRt&RptB2LRa$=rEJt!NLO-0{;#yJWwI<@4 +&(X6#_qjg}ec&KfuCt(ay~FJuLik4ET3o;eiT)e+L#Gs1W#Du<%GhZ9ktj27VhizSvV|pD%5$B6UeMy +B|=>Tz=i?g(xyquHnfcgD0KUQ? +oQQ*VvTx-VTQKZ*92tHp>Xk>&6eLiTw9~+JTOnB#16Y$Gyh4ak~xMvJPJb0%bz$@BDJXs8X;RUl-L&; +$Ci6qZBSZFL><7Z=x!h*bXsWgk^N^qbw%eRpVT-N5=7YL*bwBb*`7zmwgqo-2Luc0kr5PLHsCuAd3FK +tks68zbg|JXQR?7xn9Dt1$ge +~fAJG1PlF4S$hci*}p)Q**rs%eyT<((cHS_l}@nQRHB(Pq4#w96!MPeNh$0n|s3XE)&S7eYJYu-oK^;GTewDIfpSH?jx(d#sKmN6a66rizZ(K0fGZb{9ayM^gT;+e3N +cm;P4#eWcVe^< +iw2CCEbAO^N=wJrVdvJJ4elbG@ik**_EdzKV=!hON_;y#ihnLZ}fFzI1wm4?=7xH~8zR+hvWqA>V)*Lj#uO(#4zcZC``r_}JO%Q3 +MKp$qVLTt1`k7U#4z%ghz6VW{WBFq!j1ak+cF>0*q!FIIAE7xV1@C1gPjhOCNpMf6+k3J&S7cddyib6 +2e&swDC_^!B5pRnuTCuvH&KKxWvQjsdnOYhth+EV>IH~AM)G!L{-cR<`Bp%_rfXZ%>vVT_u*>=1kI62 +bzq@C~^LGA|&{7Jt>@+Ff0trqdXUFB_Iv<1<90kaKLw&*P|tE#DgkO2mt90}_;Tf6xfKpQ?I`_YC#Uf +apoKO8z@pTK~T3fIDN(Sxr=7Dz7(dCSXS5VZHWvx_P7L3Ff!y}d#6>e59{=TK)GJH8_xS$@KSdxeV5+ +jhXP-JjqUG-nD~BDx#t5uRURnUMA3p0Y-z0XT5qD9^MObUbwb6J7}4Z9?kQiN7lTPTG4 +U7l1>sq`n(PU1c;XRosyVVtrX5$R61$cce*AfHc!Rl4-%!YEQ-&2GNBcZeGl}^Wr>xh(99rRaWx|K%T}B?89M-P5_)-DagcPhwAO +`1ln&&mdpG#MsDQkT(&(eKtPrn)*Xvtm(YUUE5V+G9-Qt{RomG{msx!l6x@IBQ97JcPH7n&}o>ttd%Pbzb2RDrM#R;0hp|e? +rIfw+RtGz(5@j#yE?SC`(X3K7xYq#(@SFz5izeL}xs_%eCq6djq=#0JxNr?99H^`oj?M%nXT3_vdCyp +H +pf@6#f}+#Oa+SBFI~R1$_@#!#j11pgkzw9)|SWAvfCX8foZL-iSrJPzw(4;dKn}{T|4hI>Oja=ptfoy ++GfMB6|k~{BBz&_O*#$^>A$W6CisQn@wcvW{FW5N +-j%47e&;U8o!TN_+#spnhWYhgGRVIf^-3z&MBW^}*qig`Fn|5O5$12v_wp^whyMukP1pb16`;>UeOvl +ds1Ljkmh9pGMg<7cc~$tx +8`vqhrP7{Han=wKdFM$K8jUZaTh7c>WNu(qC9Dw9Oy=&|sTPTfg#Kn*seHC=Cqo0`GOE3XE?YuSJu5R +g)YX?ZcS5_}TwpGM_6Tob4h9&3=jZYKdju1Z*gw=?i7fsvoXeUG{)cuZPZ@YEk1TsdKQi|)e}z^h-)7 +5^OWe^ny8a0*^=vJQ@S*gW5Ww^^I&d67nfd<55If +vc}eK8Ia>z(~uG@KA#@FuBNDwglsR3&~356L9=*d9d71Q|tKrj2{IRVUi>W0w!q^#W0NCmFPE6hEND5U>ZlsPrXgj9a6WTIKlRid&l +Q@CU%SQp|?TFE>0ipX#2JY`P|zC+J`uZ_k?EnM!o=k`<%b~;^6ly?567JuI;fWUD3A}$`<&;zk=mxPu +gy;n}Ozz+vYR?{qI60@&TUvn&m0$uptYzJQaO*wHFPE;+{(#xa +0y@lh4>X`DwTiBlbc#fHWe(sO*IPhEDdf5>P@vPj&wm)fi@_deT?kqs&rSG$+DFO5F3eO6`562BWW~& +O(!(HiDp_lUK90oEJ1z?PsK#w>N6epYh<25YbwiPg(|fKmpCh^&4EDl$y^u`Kd1Ub|-oKdS4 +3$q>?cv{Q_y#ci;m&|LFUUEJCr%GoZw2Eq+5dx_NKBY<^{rcpeico6o|C5I2jEkBhs3dfCEV;3dVFPw +d~nUjIiMmq3RjXB-LnmDip$?mXyl(S*sR6W0C^`pP|#BKX$Iy3MgY6C@rf}22G@8YQ9-M497<&N#C_AY@x;rKcA)>6Cs!1Fe9= +CvO9F1lS$)_exMH{k_Hb4H`dX))yhs$G&vFyfZ5H~t9Uu05 +fw2I->ckzJn3`Pr-v*k7`|OVZZO*z==hA#K-^HcaTy$ECx#s}pLGOpJaXPF67MU}iSd2jg4GmLC2_=k +Vac8kVR>B}lwo6;e1QXVC>->VDPf?@+%-B6bhJj)iG`tZBg?IEBUzmu*rq|ytdp%%BNAxu_~N~KG&N7 ++qG%hFk9zCqEQr=5r-W)HaD{DY^wJ)_RZ +*hnf~xB176JKMZHVx#it(6s9d8C-GM}oZdpEkHDJ^<2HId`yOpCx(El+}n0eF6&!w1(nY9w$wJ2L*~1-m_4>}_w1ASCyMsJw&(|K +`^|zsxc_1hg`pTuATWedI7Y!3v1@cd+aVl72^dFk{L=z@K=0bsTlkE;UFqmI|4FI6pOT<=9!9?v@Cj< +Cc?kNs|1Y$&H41x+bYOd28zy_OyAihV9oO!)9bzxVZ%lA6fTw#vygh~fio)%^mCzm(!_nS}iC}Lt-{4 +(_hu&dzvX^|~a3A9((!F*AziU6puCBck%3Ztz-;IVsbXV@!qHG-BCD~y5-&FkD$U4{y=n42i;VzPO_j +rX2c;&^IA@%GEh#>wXg3Z46NEG-k6y+`cj|$AjheQ{u?VIze6Z=?f5zPnHyV=|EUDeENXc!0^^P}2u+ +WvoxTD{)jveP*KgFK}l7kpdLV!tx8>6?L&IUl~~AtI4o08y7Xd=%bb&$DHk$9%usevw^TK +eU}hWj#GTKT)nmscf$y2l9&%(Sf97!j%l$%h`IxC6Fy>lcerfMoX&zPfa)R#%~&rx1b$XOtItSx}Vd6cI|{B$`xbkf6vW~yqM`l6 +*?4;fQfK319MDps*14@+^qm7*6@@px$-vMOCM54a=71u9#o_halj7ak8Yp>9X#pbsO&)b+bWTy!v?y; +U$f_%b==kMoAWhe_-&lI1_-zbz?pp2nGP?`&qgQl&^p8+iH)dNG#&~Yf&b7^*mf=;up3NgeH55J$nqTI%`A!{ovTPzQCHf&y5LPm(|G^amgL +u#v)4kf2k_>b42{Nulg1S|8d2Sp)Y|`BuwJ;_8Wmi+i&m&rZkC@6bvE6o|S~?Pvc(-*G;(u~C!1uTFySw>p`XBgt{Qv9pKXSY%syG;VjQ1p +}g3kGwxOa%5x)kBSE3~kkJkw*K0qJ4v5ORK~_vG4PvkF=x(xJ*GEpVl{8=tn`{qvcviT*&?9A2>3tITQM$T3fP|M3|R@r($ ++vN)u-76XlxTA!2ZKNp0xx;{4_oVzo#e&z8PT&~@+du?|HSI4rUb_i2THy$1qDjoYGGLhcims=k_MQ; +2J}Rfb11sB9+D56B@l=~S!>x~}VTJaiMHjw5vLp@}&pWb|C5p&dde`ph!7cl~eAnzDI#qgL>7lQ=>-hJwBxtjIl;R`gr3}=XD{$RPLKFBKXMd=fo!ly=>B +YfyiEW&X9*?vmT_TlSub^_|l4^q~jnyCNP{s??*khnrU<@OjTP}b%aZQY7>q@>D@Z0xgS+LwWTzVHBU +!04CkCsz=RTc%&zWz3clwaTnZFITHhmB$e_u8TPMs8cEtN1t*rq`E1*9TnS!ffz5ARBz~5S(Lyq?%Ji +gGakEQP)s>Qaj}YYwCE(Qz=YF#A_$m;!>NOFWN+hjB5uI-?5EKc9Z9qw)1gD)dpvRTb%5d73reHLpbF +#FoCY!EbXQNe>u5f(7|H3zJU)k;31rKune!t^QMgFnr%qh1!GV~YI)-2#UPp1`l9D7g~+7W-i`a +yb_b1m_XI%p6jwLV{(`Oo_gE8`*R3=3-oz&ap*w$$emKdD>wAd2ytBbGT&yA4@ZY7`}w-0kiEGFXNyW +Ju0twJ0LgWp%16dLQnFE_dBD6;`RPG%SpgQ-tqamjv4@3j+Z6J`AP_7ng>8X0lS<6G{ +zrVSnw(D-Bo|nmQW`FK{exzdU<*UZCEjJUyp0yt&|~YK^cn7B5O&oE`qL^bW*4t)cL;U2ar)g=e*?hV9kTjZr{vi&u!=`{*EOr%yq$v!CGYn*OENjtV~|P8;FcbNR%c*WW46yCZKSU)w?Si!nL5 +7b5nG#CvugrS{-7jsA`Os-C;&|Kq(CaDUOa3Kc=_va-p{_6_)J7dnC2nL~Z13H=)Ddwz=3 +o@0YwasDoojq5q6H)sGcl_>(4ghv1|x;V1sJ|2K$}^#2rb`dl{Y7sQD-&=F}saP3e0w)I +T6AUw6GU|(aJ5@I)a+N(WO6{?N^H{M@u8cw;-hMkjJ?b@DRAl5yNgBmxPGsNJF@s^uNT?+Ab0HZd|)Y +CBHjCHV4B?E-g@(I4~NZF|qG|2I$!SgMsFZ>mJCR;So8q@(h7YmBAP>(A;2A$gOC0F!;^=Nhgt|K#O9 +KjvV59-B6ie-3GLwR996Vvj%@Ra_9DmAZf=Qy$zv`d>?(!>cim81l*MhrX?vNErO?52q#BC?gU$~slt +6~nsJ#Dyk4JuOf@l?uyY{nTJ?8#7%I_(>Fk=yDx^^WbGEZRR|DQqMf7A;W2k^^yw|5uYyWy-Vc706SE +ig+ox0TGeHfeS6UM^pcv+f`LXkyC8aIT||tP(+xfngjf!d=3J^v?C07jp?I>cj#Oo-@>-t(`8xJdzec +t!JPB9`u9)(0s^y&mJ($dT<0+$A;D!AfQ^gM;4K>@S4qNg3poB*00>pg=@3mU8TmJt-!%RanaH5 +(u3nF!s&d59GNkeoHi=pHT^FUr{Qo=%ECx(yj2iT?(PrAmFFlyE`LFs)Y~1PlpA`ulLLndFD&Bj^Pz% +8hvOcge|+f&m7bwwKu!QTzuzlXgE^71mwOZnakK$x;E +&WPelZ8B7AwklY1+$sBrS&G%IXirFrBmMEuD+%AkM3>14L=@qL5(OAjb97jjgDrSk1l=@R)g4Bzp +N*j*2IY^7~n1!7B&eU^XNFTqsTcvy|_}_D-*a4U%YYZ#V#--ydUMOWNn{z#*LR5lv^^Gk7Eej=NrnBy +dEd{a(M-AE+ayAP^(IVWRtd32IT{<_xa0czjQ*K1ekTKiS%tE>%}$5wAWce* +WNaATxF@;AXu>+)XXw(JKK^VsnwG0uu;C5YCy$$e8A#^6UruCp@2C>L7-CoXId+x!_!U$L%H2l!pqM@ +#hGcuh`~i13oDg2kln#2+R{&Ht!gMouM^{+$k*9tg^#d|j1(OfttUFg4dd)&yLKL_QqsG$NCX>zxJC< +OX6VCxSGZmgIT}Vgw@`=YzC#4nr!7de9H(RQmTVa<}t&lEI`78dEG?8hCQ;i6Z@;($NpjbPRV;6$1Jn33KCnK+@J5+@kjs1u4p8%rXgjV#eHM4==^?tYe^XPn?*HyOjjJtqTU?-7V- +CoR!iw;DzF5wpFeA=nc*$mba+^lhArV0+>mB6p{gIDA{L(fdT&MsRkqOnMhL*!WHo?!0C&2hb_5YYO?JNcOHBHfeOPcn_z<)!U_PYW89%<61xMVJD7J +dC0NGzK<&+Kwx04dW8hPkBnYKYp>D4lO73l-(wQILr`h!to!%{i>ei+kNo(Kx=l)tKfC8n`h6qL&7SMdPD2Hx+?lBZr$<#n4i^LOXt-uha#97W)Lvy +Mt2H$ZYG(BEY)%BsLK+Jfj^1h8<-5T!0ws8O5T= +z&V3K}#)|erW{{#5K{v-JM-Bo{vFYHtJLJ%6oA#B4cn80BgBPbf7$-S8uCXr9$zFqI^t(mgn00He^{~ +d7Sw&Cxq +ja>O>_*$gFc@4$(x!ET-Rkr#60lqH(Pr=vackpGev1qh`^va%IOoncYxX5Z#jbuu6>}p{KKJf}k}q-jXZUJ2_^OzaeK;o>0yxc_P#M3(oY0(tAC;O=%UsI1scuhJ7?-(NJY`p5~x?Xv{K^ +Lkb$I}X7nDxAmFE3khuzTj}oM>m6jd;u8af1I{>W6@8b^}BOFpz6=({eV~qMv^o^Z%hj%F&N(<3#MTV +M{ttDA&4Xh6h_ET4Na)G+IRY9F4&i`)97u${Vw$F#k6400|oCAUP^tASk%tOwzJ?jH`>U}#<^f>N0#m +P;L8BOTN)~O&ogYVu%RRtZ7A~##G>E*t?;{aND}+S5WJg}6WG4Wu0uuaoNI5Oz~1ev?|zDnXKk+?#yi +$h)Q)x=NK(kgu-*4n=I)#^6See_$K*6!zB^y~0_~>&mSLw@C0sl=>d>=0T+P%ttX>s@>DgN!4#zQmk!*FS(8=Cobr +0qT1kh@`1f01P5kD*(y>r2u?wQxLVUUAg~c{ButHClI3RC}&RFVaEz-4l0v2x8_kTQ=y2y +BbWv?@&Vfmv$Nb4>eQYVDONxfSNz8A+JXkEX2`k;DboI7_e5xx{MC)q>Pq+YX--^%6cf9XsPdU%0fT} +dtn)9m9iC>Phlgct09SlC6W6(v*63RlOv;9M4Gd{MGyZBmZ_Y6YYITtvrOtHbqRwFMnm1%+z*m9@=)n +ZsE*pjNp&R7D9TQgE-o2F*Bbm3@Sr%SQYn$yTp`z;F#>6I!hdo^_6r7sGm?Dpl0Pf&c6NU4m0MA3LCN +exIgkB2I;dDzQs#9$bt6e{!LfN;-$kM5t>Ook|3_%A=sRUjPh+48E^^R%e{zNeKB~xke;6fB>+?gDvj +*aeKGE$2UCb*8?lU9>nco}nixeBTe@X$*^xseE;^IG+2Kf`=?xrNuq6@MW{6_?lp9^Aqjs)waavv=_M +CwFEO#lu38Sw^754fz=lbcbX4(p5K72jOXcK2IZ#>DB6mF9tl#%jJah%`m36fF7^ +`R?V<@6?4PR?c~U78GgQUcOv#MP{ECBW*Mf`SE+6ncUWo1MH)kS%o?+TM-bU{GX$|0aMzcTaMu^8!TVb5%5BJL?U9rgpZp#T`;zRoDkxs_GSt?u>!^<>m^+47g-W!a?L&xOMm +4J#qU;68@eX3CezmxS9F3!suEnSq?uLp*mZ6`cGC-Rlqm+@T7^le1*$U$EHK!Mwj8-NotWl6|&bSf4MVgEJ0 +J*WZ&{)Oxx8!1b%R$~}9I!GM)%8QmfcXTyUQqF;Ot;T8d%Vso^j6Ca86!b%g_fcnwur8c&m5o1p$`5~ +ElHxW;(#XxfOFx3Ului%x)YIx<>6;V)P5MGCw_q$gnxkY{HTa~#+7rS`6nso5Z^CtvyK{Vy= +Ui(auqHeeiOREE9g-)ox9Vf_FbK=5y1+kNaTk2C(ya>TM+WekBl$hSbTq$`LYa)^SvedH_EdFfNn;bM{r&;MP_Bc3p7_9xIi9ge^p`LJ=`DikY`I(+kyvjG3b& +tZz&=DdKL>OEP!7K8+>z%+SvA$D$6uKwWdj(4uesUq|x;Uoi4Zg5f>>{uN*WmM`$CZ3~uSz9S*O6!-5 +Jph47TO6U6*OPIR1KNgF5ezucfMzP5$_o*n(YtG?$+zq;ZF#sm>C1R*4e;{--+AsxL%_ykRFf!_uT`1 +SxIA?R}(7Vy~uuEA)+ke8!mmT-*Cg#w73k-Cr4#czQFY6Of2z9d0| +ujblCEoA2M+GrvSvU*kMy+L+S%W1N@0&Dj2`w)eO02KfGVes?$jq$%poU83$dDI2X|x1Su&pW6G{Z^3 +kI6CF5c7$ZLW!s|A8!NyZB_bUTGgt;yRI1)|+bP(Djt1nM-2yOxP^p8{1E*6{gCk4MtkeVasdnifjXa +W6MI3vn@GDq-nLpUf#@rSrh!N%f_=SxK87vu=BG{TMXz_%x3T|2*|~?omdENfu{p5nRp0 +>1I_q9NBiK5^eEE!L%KfB?_pj`q4-WAcD4+oJ#WyiWG-itDMl({gs+e{BWr6Pq?}cK8L1GI%Fc$C~$4 +JC3QHx_zACb>0Sb^<}t5#G{?w~0pbW +PHDW0L{Nf+7a9@*t3vXCZ3Qfh&B%~Y;;$xa4mMp^xdq^=VRjKmzbfV-3KclH3-13zv-v-m_F^|c$x73 +P+vw#Er-2#-TyO)h4=mGHExjt8r0ZLzIbHw|mG@*E5nfPO|?-;KSLI83Mdm3sn(IP6hHl<8TUe!%CHw +Awy&Guh(y@|f#<$*v2ebz-Ijy59pZSdR)VE9~t|wGI8e9YBe4h&piYmOfn{a~|kWZ`~kYdKzR0$7yUj +Zn-W@8sqAP2htyFdp|S+Bi9RoZ&4DTMiwgK0ehnoU4EjNdHGiF1O8L*xGzmnfezHv%$>Z;UB~-I)kE@ +`yLVOS17VEQ*I0@6seNpGKpFy48w#92H6HFclrab}jTpxSf;&~))A1e|=bAUfhu5x=6^^V}JXqSisnK +1Ox>CR&uP4=-hxO_(-0?6QA`>e8Y+V4Cjc&*MIg50p1@04xt|e59h{<7OU!5#t6u{h8Mk*F;I=Vfv%O +%Ozn^>LYXV$C|4n#!%fW1~qa@rrU3 +XVuU6OmzHm2WvFC2mEL%76=bzjkAV#LVMv{s;`-hxls&C01+HaSu@zNjt1XQgU;#^w_3&T2mq1Jp&l;!R6}6*ZEvI%<4Qv4mRDXXpI+kz_kKm&@(q#0W(Z5P9z}xx!e +|{U%;L)c#lCiU^Z#NNsLI)7QMmH-b!AuqkEciYL%F`gOcg<$4ov1@G2fm2b|dC0WSI=SD^}y%?w1%>% +K0^kwc{OQ?G7}Y!z|2I_mBDVrA^bHqu3hr6|8jp$uNNxfFXHoJ;T4tKv1(fV5PCVvFJQS|QQ)mc~^rK +d&Htv$$o6j?XyHkroc-zs;oH!V_5=d>eH({)f+tX6arv9BFSHj2-)<{pxaYkhdZ$i!u=7Fs-CGsB2fVix-lIYPCVs1cA$YsF-tn7qn#pL-yMVQ`d( +rd`ys=?qm-pdMl*BM=X+FMF%wLosILGV)55_~(yhrOX(%4S;Y6EM3NqiR!Wbj8wFSW_w- +#MmLFON5kjXqLFm#+$9ZP*;YDPVkzrMxJ*Jh;)d|eB5GZW3 +lb(c1`^vrIQy@8qFe}dYBW6)hEVOJ-ZA7$W9;bvf8exmE7kh&%iqc= +l&}}2jZHA}(xalR!CaAvxQkRs|T@k{up2!Cw=C~8i;)B-GvjYv@?LguZDPG}(_2YZXv*|j8-AyG|Dx= +`uvjMIIlCzQYY8fXlc5Aob*D}BESX%fc_q7X02R?BniMv&z%)u2PdqX36O&Gjj$o +7V7QVH~9PVtJ3rE(2%HNKt@ka*;d%Yr>bnBl3z%bV8V?3LsU9LT4Vo0)d5G5?_)(IVFPV3yVp1(o~+w +}yPGAuYZi4mSHj;x!Kjk-B~`F-&3sVGq&FU$#|!+Jg0catk=Ih3F3K5QA8y+p`hjT?!9kkzBFopXAIx +IT!eL^6~QJWadI^d_Nf%|G2$M#_-Hra+c$IVfz-I*bVl!$f1ixbBfk(1XMKM{*4Cz57ZfMtu*>q7kX< +J3ht_|?CVP@iaOp(w$eB-Qv>Fi7j*~hD;Whf`l#Hi%^#ScHn&AZ{&*biP$uZxaeWE5yXy8ueE$gqq6t +NOH*O~{A%|DrA=6u3@h=v6$G*U4a_zNRB=&(X>q}PP6x?0XC~{+$VhrhJbafo?+2~l3YIUdz5aBC6KV +Q)~Ja&h{v|aOj?ru!TAqV +WRYu~$dbMVTMU6s8_fm#yp33(W{?OUD!eqo#_^XM&(|mxS|OOvt&7l)pd;=IG+&eJ{VMeb{fg*9>CQK +!3o)&V3!pSE7LRsX!EQ{dA0bj*%$jmJOFSp(IFal{174#(fcG}1gLjXFkX`!&DpdS^gqrnbfLqiN62%2ZROWbZ +WXGHUv|_e6d2(q&HO_jR9I=Hx&DnMPCe8885U}9jla=)DGS%)GERYIRe7jDbC9ISx^!Jm0N%NC7kFbw +4CfX3^&){En66I}IcUiGFXcSHHO5&Y{e!c3fNI&t{|Apl~$m}Oe{lI5YoJL`aAP9;=VS1y(8!LvePo; +n9Xm6t5hs4P}T^EIWr&&Pm&GqzKUneB?ykY$A`3gR_;iH1R(QTJXf4BJu(Vmfu-a2a}zO%{TZSI%86M +9j)>%nbLQOUol=obWbKbzRa-XV1FTnp&Eb#50RiuWcH9N+or_8QyGM{kzBy?pw1qux%2-kf(^VegcC> +(XsRIHLAn@mpSqAb&?-`L~2yyL=E>MN6-hpUu5zpdRvhIqI$Fcud@CUgc5zxLeS%YWZl587k{iE`_{&-9 +iqMwLtZ2+37O~N;9jZ6oUY?pA +5$Gzs5^%=JGO{NUae^OBCyy#4Umy6*Q297bxT%4q@A}A+}E@Fi8gCvH~fhV} +WYxYI}lVywBzex{vCu^Q9*$-R_?5KG*oUk*wSEP{B9#?p8u~(;y{=JY94Rfa(Nj#&5>ctX!vhf@hJ1B +Mli9qIa88qaIH~ee^JYNUx9Re0Z(%k*qb`(DKlhAm#zYq%oeEV;>sGO_j!*-86nbCg)4YEQug`ynEom +hs;zt9G$@1bS|%L_e?o|P@Z=_*MOH@!6Df3LD~_nP@KXzTvTYI2=@)WUZop6EDtg@N)_`9!9Of0k3HO +Wf(tu=rM12QBc?e_(%D!C%QaF2|Co0eQmHP(sknH%OBWco%Zqf)ZEjTs^clxgBt5s~zVI*Lvs4%eQhI +(TCX7bzLwekHDGEHCSyY+3Kk3B*e^r6}JQcQaxKn$ex)sh*cm~dakqYo=B4cT{LXV8M;qE76aw=x%d? +TAUeqM)1LS6E-J>SG9ensQ+eVtca>BD2G0aGCz0((5Ya%b$;s}O8^kxNiGiJ=!g(tKX*88=_`Vy+?di +JgxFe|bEP2d1>tdSemCZ)wu^iBPNgjTDcBu!bJ&S|gpeETq(-(D=hy&3w +z6cQMtE@1Vc}2ILE05ipE$K>XrS7wA>8(c{9at6P^2<><5pji;Ns&7g#EwYYd0Dss=4H5m4>BKdba~b +hbq|Qe9WC|d@b`l)ux23x)0M}mpDn0_of=X+lSD-XK~w?wtW?+-hJ5A?iss}#1VV9V)RSR6=L7`l6OZFMD1JNE~g*wlX+pX= +WkGKujj$pXtK9SqHn=~y@{K8?~?JJtishS^Q`qAy3?FyyX_UHOhM$`8#@cU!skGH?yJ@DV +${(kqse{=i$-2=bsul(8_UfwSYrkX0OoQ=7Ep#_sGOk38e*6koX7 +F85aPn&_3QD9mo<(Em3?u7MA8E6W9Rc^L-4#o;`ocweYT1LkiI7*(!k6~SAH7kj>& +6a_w5=8Qfd$q)iqxV?+GD<*{Ex<_-XAdFv-a?D)bn+cE~00-ESdRXSDaW27fdN98v+UQFD#t@3kNm~gy_AXzr_Xk+aV-=8+oJT-an4RdqlE9;0CAAuMl##2qp +Gaf_L}#27nvj2FTts8NS;(DP(V|+!i9<2)W%kk?zf%@!P9(gZk}R9NS+Hd$$g3z@5C~1p?Yan%@Qs(J +m}ne-jo}8HGGE>hrj+Ds~~IPqW%cXpa0MX?QO1cUta}AFBVjN=Ud&`9OCM+XLeC@%mB;7% +fH92wGUTqOyi}E|8IY;d%FDe_WM}BKe!$Hc8kBeL*RF}_`5p1u<~ah~P=g-lgb +p`O14E>5}>b>FM?yZ7>-Zol%3!a5>rWQ`}$>K>7ae-t|XMogT&=qUlJu8mDYysgsF;7BrlYljxtp +0pZBVe|)2fa>tsBTmlB2UIsU3FHaP>czzz;+(DE4neOr0bf#)`l*Shp}j!cJ8ZK$8`B>|E*Eciqs|Vme}KbiwQDq!~-p&%)%N +d|r4e^oIRZ!+{#o8x#o~D&uO|%I%oDAvz!L2jXRe>c}Px*taKRbV;}?dHAj!xU%E)(m>2ss;n1$6;-# +mV?g!L&&TqJ=)>;mz#lt=Im-l+_0j{@+q>0;P)l=6>kqkJa~B(Dx+7sPPqoi^&Up%ef3a)JHbIE8CHi +t;9z#R8Cvx?d*V%@7=kTn_#-HekBPgaA?fJ-8MHbECeKh2IWdqz^NCT;z +zQp64aHL5M_9+x$}>S(l2ZyX`05kmXdVaX20goLMgE9PVk3&@qlFwOT~0~5h;ejpGuz1TMv(WiD7mZur_`>f6~6Zkn0&+kJ~sKeTlG69 +wKvecJ+8K8wk64U&s8PqPN{c8fpq`1S6A^}ZF^5eZ5s*ki@hs+Pe<)y+u?ozOuY-S)LydPRlwrCH!pq +r5RkjE;d{z7-U9;S&F%yIJ*fFkrfny0S3$|XtwHhL(HEk>W%tUycZJ_(oRkC=kgN`{W4lC;KePMbXLi +3&6a5uE{x&$Y(PQ;HdMt;HHUk^TBp+hfC*<3Pw_6STymzhtXz%*DhWy{{U4JNTZ+ln#ZSOK2odnV1Np0?-sppe14L9eJ+phJs+Vd=dwox<_zT-UPl&+%l|iZ)ixA=D}q{D0M~w`4cYvQ?fu(bdbmW(Z +Q86r1-C`nbrh|R{-QX1P!{pSD?g0FE?f?+NQCyKCh%G3@1;XrHRN&_eiU9N#!00JBdq*}m+ns};9*Xo|qhi4p3kQSpUNw6&JOb*95v>RdEBb% +MeKWP3HyEdz~|NeFE`qwuM+TrVmXMO^`zkk&S(EZ|yAB1EHn1uFzUy7nh7^QHWq7Vw+n^p*fCTSESDH +26V7@-h|`V{|Qc&FPN*$v~j77w&Hbx^@Bm4l}5)?jL<%hB6cHT^uhv7NH<>KNM75y;!=Z->C}?X`mLN +V@w4yq)^?0syk3()Ms4*+hPUe_QZ?q`U0@E(R3s$@&e6X=u-g>`9cjy5fHQdopUH>_qg||0i~&MB^P7 +cbUXwNA3;F;rGlIneJ(tUH*~yZU67O;~$xP;Ge^>x3s91nq+vng7|dSf?*?zk@vWK%2%fFCj%Gj*nii +pvVD@Bd<>f?vk-c?tw(FehWEL%CIgnR9b8y7MDSKK{prMf=()nuek?%V&tF1Rhkin#_w&H_Cwz}Y8xD +S`%Ocu4%OT-9fBR6E{dNr44{8GSQw1PbKU!IKU%%s2zh6x{zV^5B-P?<7;HUTJY3$_#@AdcFL*5r>3|H#)?t1@XqtE33i#fkXv=V>KRR654xA0x^PY&6s! +!#>vIvZgug(JnX4t#m_<=3J}|1bz%eCu2y*Qv&j=GjwS9NE| +)FFdV%%}2=(M3XGgQk{kzrl5PRqQBds7Haz~(YeNnpqLX)FQDn_U-t9&lTDzL{xRCu%A-9lhS>1P2uq +18K?ZzID3^Et;qz+R%ui>poCsiGPdMu)~QlfJt+0(YSFgsT1ITvJyNsF9AX6`k@hCsxXefKFBp+4bC} +P!5LMUx=Y*Oh*rOaj8{>NmQjg7C}0aw@Oe+UjgjnA?*4I6&|#hTZnR{E|7kln1fl^xA|=9;7xi-y)e5 +mDfqsUL_M>(%6J(mv;>6`5ZjJ+lvl|Pk-G&Rh-oK|PM$!jD*2Rh;ln-AO`i(x^L4$S=uu^HG9Vi_+@+YDR +dKrY@|%IF8AJtFXZ)B=n-}xvKdz`;%2=pLKOg#m8n>FC8(~PCgPt%EI#RDHhBcnx~Oq`#OL}&9|zAee +N@N>wNd?GFeh$-GKb?TK)YW}M&7ir^BNx&d+U2lwAb`9A0XQ!pdpWigNV{OcEW=UTKPjaf~2{lBzY4TGBw`Yn_jh$H@ZFhOkRBYYwj0k3JYeJvic+-!C&IhlD +;peA|ggTJNrx&@S|h>L=sTf>paA?L(Ctz*x%za=T1)s~pF@RgO6b(K30#O%o5#(}@g+!u1{#@^lrZaN +0ah&=+<)p1QivxPJRC5KRPMao5(u#59?R7%H-zbekM-g1JauqTAi{#D%!a?DL|K7AwV0#z9Pr_bIcwnPG13XtA`wU6V1^9CJ*h_0sT?@{eb8elpL8#SmKBQ!Lxu0uyYwLz@>TK*Nr=Y!oi)G+ds +mWc7DSx@+=vxrDeU6#N-9Zu8l{(qPTt;3yM&){1Jbde$BH#o?v +dns*0pVmEc#=zE^;CItkzm&k3{rxMi!6TYLWoCatK7yk@`f7|sh+oUUmsFwSM|QV`(Jt*e%{jXt +vCUJuNw=H&UuTm^0V&l8`S&0f{y5}{61#9H?6zZXZH*ECpYY~`vv@y8}`}#0{+Pj`|N%J|Kx^!W~u=E +%2i>=UxW1|uAs{u?~x**SA=`EPLQMzgiHMBm7KI*EF%C`4}9V>uOJ{+ZB*Pgmkts{M2<>jJc=YoSYO> +tr|HMa9}He(6&pLtosV4G^;jT0;2y3$+HYJgg^w|)FG$vaymmt4Xps|o(&|gHHaW+f+vlR_fU`v8$h>&A@DO~T%5qjE~vacbO_L-;H5}HKV}VM)vh9~11$7tG+ +GjU^%(R6Z{?IMoF5nZ*2T9M#)@?8f$T2_w$1GBxyBp2ieC9a6H`^aQ1MV< +!EERk`D`@aoll0SHS>UIaTKHh*zbG%cIxiiuh7Pl!x_cAFg`sta`PJGp~?b@B?;#7=0P-VebgEE)t$2w!wF7zK8w^MDLQT}#RWM~H@<{}`?=yCHL3^!31sKwK|g +ibU>kLQ>JM!-OSm`1yR8Ptkbb}yn?*5|I!#Izv>B*f7TI2EFgVCdue=(-Ko`8F+29JcXK4bPYrm%Oc* +!ymYljoXBRC(<4@}n9+{;f_->Il04`JE4fWS*_R6q*WKe{UXL)|&>%~e4m(OC$q*Lv$ZNf>a!)-kihh +lINQ3sVILz&y3h(c#3Iws=pX(vovs3Gnf`;pHc9VYs>Gso|uo7Kt$`D)d_Cog3lg#n2X|`q9=r&8L ++mnkbxTxG^|J59l(CXA^qL0G<1!$`9PZYq>&Gj)i#|t6n&Uf@%5@WEqTn8nKK>~A0!s3QHjYdvo- +s9+k{2LhT8%%>4hRc2zu}QP)f6*yOeNe%D^)nOM4p3CG(`<6HCJwA5qD#PxW?PTz4V#`kPw{bN#qrRs +D>`#mXf_3R#DLb`x{sZ?Q5z`%(}LBT0Pt1W&2Qh>pItypgW!Ui7p +s-=In8%t26`Nm@;A+X^rN7Y}aiAxOdD7DAhafIY$3>_H<^h_rkzr@LR%7lPxdD86%Kg%m08UY<4B^4v +27ZpV{cT?W*@3cVMd+&aCBaMvOy-{ds9EUqZ!BE?~l{)y3)eoY_U&=uo%xt(w5G5-!bZn%`#Fzuh?f8 +8(pe`?MTZ2OPq`@x|B!AXPyQ5ZuJf&y`vz(E41a2$bAd;_=JFNpx*Pw{Q`PVDSbGkn*0QSau|8>&o-H +yWgNl$a*Ft5m*Qpnr;Qskc&k_nSiYeb55k(^FKjcV4C5FnKQ|q;^kH=nK-!cL%L)aQZ6~(LK48?c2M1 +V(PuHT~Y?^@EgbX+Q#&qo63qeT+a8GHVj?F`~ELX?7$j-x0=m}JvX(%^=$_Hout|YZGYRghCS~+zp_7 +jenpc^JyV|UVb&rOxK)SAH2zTr5fH)g{*UvSUcT<>coAIC3{5uc +4EL5wN7S?|2hx6Rfs6+P|t`Xv*+A3IBP@(a-2^sD}ChhIFZSNhASEa;2=Onz;HS +q5&`}VTZACs9n$L1an9U +%TKm`z|X#ZUtLM%1#6a6koAKZABNHnROfNBF5GCi`?c2(rsR1#lo3F%`1j28U;X|%nqwNeVnVV;dhXN +|!4oUhbim1RI_sSkosiWBG*X>c#e90&py)#)5&@wYcgw-VtHAVf0PFB{GZMF*D(AW^Zk%811SPS34){u7) +L1z2e+6^At;4II0}E+aX>zG7ThHP#k=W6v1`({=vI(#ALn>aQ|;{ioo(NagT)q2KaIkB5e)VqL>Y2bj9ev +%Kh(ELUGiAdXb1lpWFh)wO^|m2`OP_$b9iPfu1ktM$i|ygSH5P92bdxecG|QH(xV*L;=XLCAEoQYm0D +&mu^u)gDP0Tc8x(O6*_HgO>_q8N(Y8g>ueFW?UkarN4|y +Z&!-snw@;eY8G65<>Cc&cOnDfJZTD0@hu?;qp_T*o9n%trbnRT?bUMKbrJ<8E*B6@U+RmEWKoUn8R~5E#JwdDc-YAAc +JQ<2#E_rycfHPKNGhK?Wcs_C!NZ&uYk$uvx^P6wZcPt$ERl80$9isw`msBR>jdfQq<945j(HO4!58XO +}@7s0$vfGBbOs586`6yPZ{&cIyI1rN3?fQrXv{EyK(vK9fD{-wyapp0&@ip*!tg#63L@T4S`oTsCn=z +~xjq^uRtDR#xvnLWVS)kO7lR0Eh-aWSH9NU-EI9?9jV`PhuZa%`QVku|8EcqnSU)#f2JBOKIHHdPm52oP@E{rCruHG4qi9`R1!0zl}w+rChye$BDC(KAv` +Y1%Ad6BM(5}fZ2+-GH=&Y6M*iT3j2$gJ}1o$8^EL>J8{y<0OFaAUDJh+r;dC!9Drp@!5dubf-xRziUL^g2D~SHR*J0|yJG;3rC}9(Ij6b2@|d6{iw;`y`HV*5-w +W&wYH>kC_$HDa}i6OZeuhf7!M&6~9B&LS5nSb`DD}B +EM;@OF0C?Otf_e?dco#}-q4rh(Tbmk!T);3rh+G{nQ)f4U>^t>8#?vU$GJ=&0HK>?Ym$?j?_Xk=kY0y ++-@)!iFIitxc0R@F^G>zw2jNF7+eln>{?z1EaE$SXf-xNnbV6r^@LmRXI^|PY$ +H_!Q|;rwj;4>%2iHku6Yo`yKN+wDLYvODk)(5E7%?48>w@ZBVbM0d~>6K_v?=-tucU0byg-t67>n)*B +iv~l0v9|w8Y7QgLwHpYySy9je{9`<%H+y=zdo}t-r)NVYO?r)p@Dg~6}dlm^yb~v@oOC|fijZ?$vp6b +cbwWR*J=}LADXcZsmjQ1W8S})VD14&F{aP~$Ix$jmJ(~g3GZ=$6=wehELX$NR~#sbm*6fXU6?Bg=NYm5EwT?VjU$A5AeKd3;z +Tm}>AjQ)cP^y4?MJ-8$qlxapu(o^^d2Wgyu`I;YWg`Ec|+yDp0DH0f8gxfQOugl!v63F#t$Z^m-#^w;u3Mww9?z1{aJ +VN=UCvA!9P;v(oi-!AktT!9{nPN(7VsB$>mbJIAk;(P$g9=qrJ*y)>P#8mCz_7QA<{~IPIQT&f%FuxK +17F*p +Raxmec}&Y8W9NDI4MfvC~s*vc0NZEd$^&tufK2@dwb4P +$Q~;5FUDZxzB!k?J-yJk;X_Wn1?bsZAOEF&;>HhurQ6)zcMFO|zNfIalab2;S;BQHOynEk+f7@C}uy@_&@@-d8;Vl{*YeF=xApBWwMdyDGd5v +YjoeN*4Db(5mjBNYp*K_QeF3;*=yPht0;C66 +QlD)aX=_U%-b1sv5`EtS2X}!?H31@BY+Mh4I8pB3tYL*WYp&bW9?whzSUey6e@99y7+GKII8*gQ&RXS +dMvU5`H*+zwN&(0>*6hqRGNZqSP81(QC4)3#~gYYR?&cJ*UhCU{m^#wgudSk1PK=pzV6?DDSkmE)*g9 +{(sD9GwBw^FOZ=J+r+()5(H?o>f1;K440)+N^tiBDLh_bYyWEW%`Eq>}pDYe{hir3zt`hG%h)<3b)++lz1($j^4E+WMh8LZ`ItAL!^dPuBJ|-qx<&g}>=N4=x +=+m)jGJ{#tDO-O~;DoyXgEPd8R}pmrYf5@mwNTE7$^Bkl6m*iZVi))-&Cu0jKipJuUyvgXsP$tZ}hx!PxK;ec(z`*~E?x{W--^{%f$b+#!;2T7G(!1kW>ZV~XdZZ$xEWoejgeCoh3M>=eeL=97(?$L0B +qP950SEjH_2BlfCkxIG?gUi&@JX2vH*iJ_fFcpq?8seIkiIV0qGY7Yk#UqTo=-Ta+W!!D3Yr>XcG%Ap +W6>~GAu}bV}DSCx_{h_hE>@if4zDH|A7#B{gjn!`p>LyM%da~K7!kDYdd5fnX1&PNV#MO*EO;O|+5W(qb3Cf)aMl;>ES26Oo51~bSMXsxNO*M(I} +YY~-)ZSEMEbzckV6ld^c&-Oz7XK*Xymh`0Ol?46=FBBwD?cyRBSQ)#|1=Jd=j +)G=qpquIgE1H{fe0BMcmOA66k1AP*2l_R1CsOK9I@B0H|I*3gaAx1*I9m0gXpykn`uHn%bnJkA0Jpe4 +5$&@XYU;Ipw%k=lN0dGh8S3Kn({d#^S=rg|?Ds@e^p@QLyXz(Yl_&+n_2b}ud*?!2ylIVu9NCYNe0z( +K4-5xJ6Ou;aO!=FK|g4``p_YOeWTblQ!jQvZwUAp^U?JigEo{YPiFaI2B6}t--@~&9lQ=#Mz%g{H~!> +Apo#n`@kzfsPO^WoIa2sd=Lm#q9M0lS@#y;0r<&-T*9Y)4W^y3a+Ddro3wlp7vIv3(c{?gzj82!d}9e +dzrSzDo|buiI5X@qfd~+ktEM#rj*Q1@F*;{AQ7_Gev4%#=Am-A*4O}g ++TBt4uf22`dc!-Q(e2ye5lqg2?0%sfyYs8~wL@733vlExastowHo+}jUhhJ^jl#Ws7{8jlcK#P}osEP ++)a(s{zV-+D*D^Qvdj_h{fa&U>?ke9=P<{S5_Hmv6>N0`fxz2xenZWN{=fAp4;PdPJeZ&aRT9bLsMLT +i11BRM8*}EbfMthP}PfsDkvZZH}CZH>#4PmJu%L5zYctzp!3I^*97+%kbrD^3H91Yd!j=sBe(whw?z{z7J0q8uySk21NZJ2^r&}kxQlZSkn?h5Z3YH&XhPTC0^gROVoC7|nkj|!XW&IZWS +><0Eo&|Hgq8D&PBrc@nHwImtREuut?wTO0A2>QAlYswqamrb0M)8o>k&!Q^>Cbzor1SoxQFbBCz9uq2 +bgu$sTD8e($n?jg-;lbB&x))N#Sn8y;%@e0rXN_42CNJg2g&M#f*XMe^UES~?sXPj@D{&HxmOzroYI_ +KQnA;h?^zy=^iGl>-P*(G!(_c@UH`lOu0T&tc)7x?($aT4jB2I}d!O1Tz^HcuGqrJeQ6gR(ey^pTO6K +m0{iZUKk@vOG$GXw%@*A0|g-#cT&+$hUysYjt8JRj}*^lUxJgsQ=+z4ky=u@Y!JRkE{uYbg|&qpN4uFHnZpSJy&Q9AsqpGjs#S*EMua&lY2hEsd3^ +>A*V(oH{+7aVAv{n&S++f7u5~xs~EMHNtU(2dblu?3);gaS&;lCqct9&hDyigz*# +nehGKVI#(W`84Uvt*zQ-3V4t<~7UcyD5~9I@KS0>0rPF}yH9%m$t@=e$r1Ogb(;(#Wl>lr4HoAO$|+G +&ibYRvQZ>y6Oa;nR|Sbv95Bqx0{lL2L?^>))5=57(7P^ksRI1c)AyJ7=&`czyN`+dETmv(otX&P4#(3 +9FBrRNg1Q3b8-PaX4~UhW$VJ6aQ|{P+kyj&IBFvpHy$yqp%=+T>G{&Xr{qg7bUd4Br!ZdCbP}Yy|VpfYs#F-$L9)qG<9+UM7ML3%4<%rviSn|*Hnyif3ZTz9ZON^SQ1 +Zfw}V-+M=M{Yu)=MJEFwLK*h7($;o;R*lCBqam*M?|1TJ{RD#&`#LH@LoN0L_QYpG!B9VY347d8y7sW +D#(R?JWx6dG=7jN>$=*THe6zGt_drv}y`@qog*Z2bOSU=V0n{G_)pY^>lGwm +D35Pgu7(ADzM}%d^2xm8c2D|Tq{|AV1SIV7s`0_sw9sjebe~BUgZrUF+75fey2%->55qs$hLV^?l +K^vaLKJBTS?65C?Z>C|{jtIY$oaE&0&H!S&SJH+EV`5JkVCd(56X-4wA>keLp>I2hB!8y}l09#La?`` +q$yHaNRy08Cj>Kc>(o&0UTB{qDcif{GSI(NPoEJt<$huKX=Wq(Ok$fEtTvFeWqi_w1tB;B6@7VzQl@E +br9*SYeePx)qZCqu^d_dq4^fhsou3F$8LGpKwY`~I4LzGUE^Uh~hF4E)n;{`r!D-@oS1b +0EMENf3!35;Lq&L|<6_USf@zK5vd8Y7ANnDvh`m?OYJM*BAG$q@t;J1|Xa@u`2mKir^ku9b4-p2IEXe +PhE<2`}Tyxm9;X=uW}&3S24lYT_TU8U#Up{WOLMky!cR<5%eC9EcR7iY1bhZHD^jUT2qGd4w}~r+;IS +Bzv!1u%O!J&Jh;Tm^Fw{|(LtJ% +3cCR9Nhqr;M-fld5}Q5hl8|F5F*zdiN8j>`XH+8@dZ2@s_yf`V}jL`Vd}35Xy`62c+!(|#>^yvHj1-8 +Xo5(0nI7_7-yB&hj^^oxkmOU}DdfWS@6B&xt*|5hHI?>0m5YzBS}R90G-tE!d;q9H8`E;e^q#O`avFM=^XP4>7P>Aw%>=20u +KB#*_D>48$=(f+bzM_`j-vcX#}7nHLz{v(4F+d!>;jkL_kl`IsSq1U`;W1c}Z{fp`r%W!NAfx)_8J{g +y1oRNctF9Ko`@7eeinl#P1=dOjtx~$2Pn!bwOP#2`P^lKF#bwWQV*uhIi?1M+Ayq405zv)qeucKPjX! +XrH$~qUOJ>!ZyE-WjseVc*ptUq|vTm+WK2}{d1uIpPct2%K!G9-$!=}gbq~b7Phl=SC{XdQr~W>+dJ(Jq|ke-x7ZW#+pBQAZzSh$p* +Z*k;_16f>NY;k_B1+??5?I83D_PKyGz{PXV1HROBe0GxgODx^^$s7LyADqmwvWDdp;PEZCcgF)cq5yelJp^pgLq +Fd6ulTefcbGC2=5xc83j`t{hNuO4Q&O{|Oc&yD6sM|Z-FRDW$W&p;omV13_E+}d}&g|S>-@a&p$T!-O ++1W{gFXkK)V{@8`RyR~q(eYv#Zc?w%$_*~)LOv#mDfGn`Pcjq6e{w$@bQ0Y_^J(B}^$#v$5>G +UrwPJ$;=r7Q1Y|ht4yF+p55anduf;$`x^|=z7eoh0>)p6LCQj3cv03ov;ccT0p-VyY0k#8J-K2*3pqz +|P#sKt5}-0mgAx;{GeGl=yD;pRNSh>Ij$$%Y4{&T&P)2X6<@iNu?wbpDl25{16NhY}Krz<8jKEfBXw? +)aw^JRjO4er*|kwB0(2SD^ME+40V->S=h{k3ePVLP`ToiAEi4jRg}UI8N_}AY~tSPdWvH@k;epzF!e9hK1E*cR9F)6(T7=&~Ejg7{bt?iZ{l+=o3y|c~B+5>`pJ_^>F07^+ +9ExcW#54pA3chv+e3vR$@3m3uj!oTEHX#TdYaO4`z2n^4CX%JagirBdbz?w7(lkjGT)ox^x(Ue`PBVVc>My1= +$aoo{(m!NW>X#t*D43^CfiiF6$fucdxvX;kkk8UX*vT$7;ak0UD%$na&y$=0X?OS~;WE5KqWt$+bS%@ +=9P%4<^hjecs)YN6-s)f3OSbvyo=yKqC#Zl3wOj&IBI~+}qMz<<7917acZ8*0+kL6e2Sq@Ju;R`{n9t +{$vmYnNp9+)S8a3}l3vCJI>g1RUaSH!x?zqJZ(aKOc8I3x4s*eSyOudomyhr#2#r07u&Ou7KDt7rpdLa?TlQmYBr!T?vZmYOL{*iL3vBAo)h +-Hpga-5As}Wh4I#E7e#O +}KN;*LqyKEwV(h@pStuM|8A=s`7s>;lJCYKfH_Bf`nde6dBUYW43T?3PJBQV*!kNll5lwyf`~VaTyvp +YF96A&2Ah*#J}!>oBQM}G*0?*5m}>VG)=_7LJsn|HQvMb9>!4W)g1)%5m0s`>B};`{e! +`M`I-F~g5#(h!Ei8(ZHH9fBdy?q5m3BtoDYe}_N}gSP*{&kOXZeKVCx-#d}Wo~X~>EjyBAcLhq*eY14 +uzwn+9Pd$cMeVdMChjhg^8z3v3!X+^S9) +&Nd2y5Am#k0o!OPMtKbCzECfyS3F*{(6eXCcP|!I`VJaQn4~DdQPU+WM<*moGqg>lf_{w>_;lj3;;tW +b0MA7}dtF_-@uXy8jSP%b>8=Av2EOl^>ID^mU_Cpzru~!*BR-n}Bg`FY~>ca=$tMy7CUVFnvu|9r|kg +`}G*`_24V#e(D~(JO8?0NaMSEEFYP!Z;f7U7`lbNIQpxT5l8zw%52h=4|OK|(1D+J9r!lM=(#egSK&+ +NY*lw&FSYzYtp>BV?EoyViRJHRmjy%cnndXsA^1-Qeu{A)I>}2v6-Iq`8a436IMuMm-x3l`$N{wPHew=$-6!{m*DTO97Rb9t3cEAhC7VSh7KQj4yA +^wMp38k7yzfJYIfq&4hQ@`&kA2AUQ6oZb0SS%B&A4^&fM`0StkHEa~$;^cQnZ_|Gb@lV0_w@>5X)BvE{$#2|(e6h@LbfkDXL2M{B%4Hlv>Mtr&@x^dWz%p%|(ZZ=Mdo)e^;Qu=)U +2&h27owxA@!`717tjXR9dnd8mDHQmt4uHkJ!@31=JbQOLiu +X>`Ta@2sA-}Z$EB4Jz;%y_bz1c2`hjx#OZRGaqUK$PVbU%;xSd1fYj|ytnO`#is{@a=w;k?IF@SEz%; +WL$ZW@#lV7#+O&Ov|`JIT!ZN#Eqjp8qoRhCr4dH(Czvqd~2Kc!Eomgps&Q +-F}Uzk0t@_t!440}T5!#1u;ejXOPJle+OU)Ee$~`kI-fF-LX8Gt~z#MT_~u$#x>Sg4^~ye2>ZB3H5K#A*k+AA^dOlJk*Z$!wC$vI}~E(b;Eg0CE@9_r}i2AA}}jxzdkcwm7Lcoh3JDrWZ9 +@U6UX+R1jYGKU(WWKLr11W{R?Dru@zlFbgbyNE~O9B-Gc$!7srgA#3@BO8}8D9U=^-tF^2i`lU-&xnh +x(~T^%6(dB)cxZ;bq@WQLIfp`Ua}w5&2M*~D4l)6GhB_>5qwdQ!3GB>zpdytub+TcNhupqgZjfA$IVk +#BZDj?kdgoSNSp$%d-7QUT#as(u$0KlUC#l`dg?iTKbHMAccx1;)p$>;A^YG{(6bq3Bb4HvhSeVxVnB +gH>js`)pgLKcDA@^-KQNZ-xq9z{Hde0htN0EuniN)Y=kBIi6Mg1d-)(8*QB;5++)T*WUn##VzMXuA*Cha{IK!5Cp^0)QLKbI#1A9R<7mPd+;Qm-$s`*`-y +Dn2^}DYP4&_$7grW)vV!A**R)QrzXoM$OV`EVHhbSGTxLceW#JvCbbxBXl52-l?+o62+4g!<yq;vO3+5m(#2mD(pCSFg4}8i{yKy*>vxlJIgINgm`Iu|^7alL(Z4yaI5oBhEn+$NapMbB) +i`DRCoI&ZpCJU9sx{U%OCW?HL!+5)61dT~`4fyv4HE(GRALJwHG2Z +GUa_=M|zezV;Ere~`-+l+t*8h-$1TL1+6a#fg`{ePUCF>OEblekhq3B7G!WX$_UmnF#j`(z)Wp=snd^-W?REKlYt+&~!7y-u8VhaRD1?!{`zg$Nvlqs +v%$BoC8R~Q|p>M(_K5TH7oc$t{ufPv&GM!_|QL|oL}Ir4raI9@?RO1g3|ML;S~WlAoLV2N2-SRM@;AC +8YMAszL3;}Kx$p1}~5vrC+AW2w7q<7AJkAFUDz1+NORcyG?xJN|D&DE{ApP`^CsR}hN-970hbf{+xlp +%H|FFcL*z5=Jo!#}I;mP_Vgz}2H!JpDFUgx>lE3tfd5_ot)81j`O(pl1koj&5gMm9RLZDsWLlyg~P->^ +k8Szeq!27$V_`aZx3vcV+{!wq31-;$Gu${2}EhT058ofyT5kghQ6VldJ5i5ZUt*kXK++VWG8+ro%6Zo +{FC*W7`$#(x9KJ7mP{|S8B(G&0+@JZO=)35qF0$+@_oRoV=r}1#jy;}wur@pcKeAF-XGSc~7A$#xnS{ +?SnNpRP`n%Df|Uo}&WRS(#Q{xEd(V=Z1Cmpz9`bwuL;y;z!o@Vl<%3+CKdqmy3|M=0{-p>A>j)9o>SD +H<`$fFBamYOL^MrT%wp>; +sc7&iZSzN(|Iv20>)34{%<> +PhO{AaW**`q@TUh()ydPp3NI^JAQ7DYUAPG|tiDD>)Vleh;4Exf=9e?{BY@929Z>4PvZ7)84H)h$mA^ +6U0W_#lF(-^jwv=F-$6@=_DWe*(bUKqR`-{+3^jW&eX;|}!JLZWZ4Dq;&mzf#6*Q3s~>Gj~^n_cmXKz +BP*QzCz;NjEhSCn?U!R>=qTbm&62e}YoT+CV*n2K>@8%*UXGPxQ9q={aaeKg +Xd5X2X+4CCT_1V3ySblwpWAbiUHUAXHw(qwn`aKPFf6CeZ&Z>aVmi4!*`pzI4_)8DRpA4cOSM2<-hm< +(ReuXoLxJ+({9(dO~Upb6Q06QL|9OA)4zG&B5B_QYLG^K7)gI+#iJj&vzDLgYKJyyw+5>YSbv~nF+Q^ +b&KNku@oB*uMtnEvUBq60(PBvuyOVM&h~Ehe#AcPYe0bk{vCpAq%Lvp+ryfza>WXbn|`0M_}Wt`l{y; +L>c|q*=9AXVbfexT4hzl`b8jb5pq+3|)Io!v(E9WqsfJ;1Rqx_UQ#I%GHG(@N8qWUE(q>_j2ds+T%E| +x~j5mk*B>?J=sz;n{XyKEVV3AU$NT<8c7by0bm*_vO}xj2ekHDM_|D#?RNPrU*%W-d_Jcq&7wTl4ZKR +zkl2qB-rGjTg@t*I=U4%p*s1R9Q>6zRrNpcH%5B_u2$P6~Nd~qCq}O8F9Z%ljlnSU$waA=4d1+&clgNl9cIjV}c +(jhqOO^`N00qqVm8$Xw=SKS7c9XKlNqzx6kym<@-5wzsaPTUn2e59$NiMO6)mE9psLxp#E`#IMV~C!TanU} +@lkq~$-tSn+3GqFKm3JV0|+SyGh8J7G)wHb7Vz4qpqh$H)Yz$@ +a2kF27C=!f>Fh9XA?$yelp}bvbo?nr=?Ur*@7r1wNDc;Im~rrwqm3U*V;+%{IK)Q2`v}qe|PHsiY??W +S-?56&$!MvL36HZmt%n(+-*P7lz36$hZfczCy_rsHe0WY6VHJ#%*3nTS&AG%bX9}Z=YZv +hwZUn9MAme)WUgL(m5=p{g~u;h7Zs&Q%(f%dMpRea|Hq}8wKU6MddvHxkh(4%?xA#>+}&rns_dAstcs +ha$@Rmck}JDH#?JSGJq=|G2|RXf)1;SUxDaw=I&N&LE_5`37T{Z=I(Mbt;I>;qh>sG7%r8NA*0&KrBq%D{JOrf@5gcs@8{|o!k9At +5Azgu)~5b>8brA4x9*z)w9MTPeI=3QY8?kDf&TyNaBtvP-7?@O^AC+?i|Z6LEzU+|rK*y(e&OPljzuS +j`&V7({&`(1ltr<~Nc@)DWLPSqr?SWNJrLbWoqzO}R4T?Jj%OJRMLQDgNsMD^EHqO#J>2VjzF!L+Xol +YPNRH4vVyGTJ>nzf*jLa5E*Q +bFA>Ysk@JHduX_8Pel@WH-n~j*BjeWL+YQTWg?nD{*X@z{A!3f+lL|%?BL1>X=5CY0Q6PWU4E!I|Cez +0Bh?l921jjG$Yz1ssDxxDeWl^C>!4|*c>&oyL(UjA`=S*_HY}k;JjdFfHo(h^3wYP*cDd9mD7I&%#7E +v(AYr?oPa&%1+94=G?YYj@p@I6g#lM9vEJ;Oj*$uGqDq9b=XaERq`gwOG98YKYsvxR)Mm&YyQc^FSM& +%&nDO)_yiJb5BD>Wb2lr2*d4iGh8GRc}I+)xYHJ?qg{fJ51e~j>Q9j9plq#jm6>?q==% +7AXO~O{il`|IBRRQJcBN4&DGY1EcQ{`mxOF?QJLAe2%p*vxw>m+*8qHul0i4pxfUR +!+Ln#!lp^2+SK&Yr1Q;3g;&XaKj{zCZk~qj|t4IrKMc5=-!5pjXB&@?IO8ASN>r?yaRsN*=rc^=W +V%s{pU<~jiJ8W7*QoXdj6=vo=#WekP%WA$ke$*zvz1n`pddggY;#>h8ba0ou8L}#@UvO^-bRPN1%_a> +fW(aYksL9@K>FZd@d7}v{V{8Z%_y?JaPwSY0ouauH9J>y2*>co|B5!ou%!=2qnhHUvsY|{uJ4JnDCD8 +?ksJlHlPb1e816|yM22e7S<+&F`1PGN+Z8^ypbzT$g+|jgJXr{WXbA{hWCNhR}$j&%LS@KdfwoOyGm{ +-8#h4~ytE8&yU&ano2M~OCaYkD~jl{=KVaZTcAcjcg3rY3P?#vM=|^F%MLy33NNz+D)7;VL>vV3fBD9 +U|4j;LkFAA&Ww89Pji1H}v9hJr_~>OBfs`2(AhnF?yhHOc4a8lVcI~>56v~SK<1~k^J3d3=_Wi=QJ>u +vf<#iGRW>Bc4B3Nk-wT?{(N``IxdZ>2h93bD;1ky_M-M`?dx3})n#YNQ0Sni#B@!Sn8v43-*hIrQrFW +%SrpKN2@g7>SRMh#wS!K*?>UVUIWU(H6Nd5ur6P=tgVCSlCD>y-#+BmV10TD1;A +^0)P(Es|F(7|+lpdKbk0}g-1VM^9)=(&p!A(GLpr6EUikV4WM;k0TY0NnHH3vg=yIPOCnEMH_{)RqQc +DG6pY#QHDcUy@!onk?yBz0xYbRF0l6r$!n)gq;VL_1HrdWh_+THW_qbWN#aXc!A2A_2qB`J1b-UcJvl +#|v}Q7N@?ni7&co|*+v^D?Ng1Sat}0?FxHVV@WE7+u4%dum8Wns2VmkC>pTeB8v?qjQZRE`ew_C8^aH +w(!CVZSY;KMO6iL9qGtU;wJe}_wO8LO4*;X#1}nPKU9SEIES=4$+xU3lGGpnI)PldTVH(P5C7R_&NhD +6woy=h;r!13Voq%r73~-Ezs!HXHNLHve=@!Bfkgh>_x?^KKi>DJeLn&q_`)0sgd!;t#%UC%F>+N@g)x +LiX&lf9Mo_=3V|=BJ=xTUJtdnDF1BMT;gbv%BJ@af2~4<@TyBMOQaBrY)xwv5ItRJw*I52(DA4y<{%I(`dHj7A%4ZdnZ%!pVbQX!`w5$U|foBS$Wpgw&>JZ +D-JyDaaygr^qTzS@nAuLviM~uzTiIghUK@MC=S_O2RD8Gfl!NsyvlSP9y>+;FUHwr%RFbTlr5J^tFU! +XjWUk|E!YpgwzIn}{xM^NV!`lwp>sRW4#PF|^JVcN{kIHKSN7GiTpLHv=veBuTCNd<-B91|({+Mt7%Xs`RKj8x^f4KY4wj>k@Pzu0 +Heve=&);Dh`srlK5`Q-&=vPb5-w6r&7}H+~ +>BoLS=uiAijLVH6yZK(ZHI>=dORoFIc;8|t(Fvqbv)Vq$=l9Xhi54DN91&ECv2hAVSu?*J#JokNX)h +u(ZegPE&}&E;dr_0?k(Sd#{#ta< +Xvt9a?US#<1ViNIH-_z#4;A$jQ|^_l4ibNw4kAt2IXt5$Y{ohC7Vq9_1KTq$ +doe|tZHKupFSjp-20O(wdZ0nIA4kcl^Xn +u+PC-vOd-W<_E4ej0p!I|QLW0hc&0Cq?q_FUcJzMRG>|qNRY#eT^4h`r8&k2WcC&9xfom-MyHqi+h5p +D3iAd^x*$Sbp?rs6@q?~?&Gg|lGH`i?T;2h9_Z)rLrCIb!$o#M>WsNPJ7<<=y)YLj!ZMN`UpIQsTFxS +-&rew)>SKihldJH>Rw6W6-uC#y_91&Ri}Xvme;m=^R56@>fHv3%}tR6q83gB1>tPx^QSjmY+DUhc_$M +JI5gRU9c1)??wv0r+a(2CP##ysg}8vN}h|j=>pzruP+X}DOg<3S4=c1t5Ln=mMMp!f@ZlX{csY>Km;& +a8UT&3I?r$W7bte=^Q66y^8+!ixDgQ8P$+#Q&yjr6kxLqOm|{SRpJLTB00n>?aEQ^gmnI%*p0R +5c)P@uhGAx=xa;T}pZK~CW5)+((+cK|1r?|AvENKIWAJ2ES&rY!`jIXo}+}Ar3-?w*UlSp}4>rY1k<@ +NVdLSkmpRiD8!FNW|)W0_-7?V=zK>P{v6xQEGYZrl814POJj*sdz+j}B(BZNh<#HG1ltge??RXa!aL#vCiu48b};RdIbH3A{%pxmEqS!3wV9h$-2K#I?>^ylyg?nZn9puuEliQ +4nt@D;RuGk=Dd4Y+Df^q(b%Etn8U#esskqVB$NPEedQ7+O3#u69gV+riu#KaeiEV?qsuyGnq#S`8h6g +{Dj$uQ9difH0gpUWzZM!qlv7Rm_3(y>C<*nk1F2Y4ZT9e^OFv7@JuIJMGACScV0Z>Z=1QY-O00;p4c~ +(=H1gJfv1ONaS3jhEc0001RX>c!Jc4cm4Z*nhVWpZ?BW@#^DZ*pZWaCvoBO>g5i5WVYH43xtM`Z86Sk!A}DJ>+a|r(78=1wRgWE52P$|nXabtxt7u1cy>$V +sFN&hk!h5K;|PJpgAgMO-J5CurPS6U`uZBwatdD@-owLHj%;DkhJK**Z +^3@@1B1j*NYD$x`9~H^z&Nn4}rK2=$zVVTvOI5R|sz+4*9D~Re*w6`1|5b5Jeo1RSg~>aIo9x +<>Ut|T3=n@orGJ$!^Uecnp)Sh&Zs*(T<)v#sX1IHvTp!VACgodMVN)S!ew3Rn5^77FZ=VsdDQoPI=<$ +{xTU3Ck2dJ*RJv`*@?P)2v#$Hl{Ek#W4)e)0dWSwu$z}}dC-Y20!7ERJ3nXXcjggH$vPbcaBk&{)yqnV+_5N+4lRGJ(n +i;zKN;U`A}GJS%IBSo&Vn=gY8Gpro_SYY5!o^GE7pZ(;`y2f`E>YAiXF&D;eY+qf2&7l865SmCoa3EC +A2TO`e6edAU{LeY1R=`G(4$rX02&BDAFtf#7!22XCq%FejxC$DpH8v#loc4J#FkwaKQcuK8iqvPhvNh +pqma8|^`Z~~Sof_qARnkbP>LVIyU6m(RxrXvQFtb$8ak8-a;8qvV6;aOB!klM?4(j?xeG`E +t7EgO9Pj9-OmooVBqVTk43NXkHURajNlz-JZgMYJrIMR1+S0r)ZTD`E&J4;!g7=VlRU&wQv5$V5xJc6 +|LA*cT`wDs_*wGVc(&pVadgp!C23tFbXLNjy1y%EXa!H$;AZspw%mqvtQqpR6oWBO`_^CWVpKKLa%E! +pM3JwG)|_8w5rqI8uF|{lfJ9Il)^Vkl@}u?I%0b?t4V`PS6*BhUv9acqzGbh0P{x04vYi!1|HkOVaA +|NaUv_0~WN&gWV`yP=WMy?y-E^vA6R$Fi4MihSMR~&^0gGAi0)k<5UR3Zr^R=Gr68bwiL2 +7Ca9awlaYQ6QD5Q+9*Cx3<5X6MDUYTgkQ^mhg-XqhyWFs6@o#v)*!?5;bt&}9%@Z +zHz-iC2=0=3#1{na@dXNvz%;~mPp>e5`8LIMZ6M=35a>#xXNF604#?@;`hG^GX$L;J&B9yG*Z`@Z#*B +?$fs8G%PC;)WViaA0bSuY7u+KCAE>hZ+j47=Z5mKTX9z`%mNHm5?)F1}3g?n#uJDg6SJ$Qio_Gr`|Od +ih3)~qC*;xlGUd>u!eh$$<@gxONyM9}Yyu5Jm@zVv$DJuMxFiOz4FzG#?$JPDZi;^<#FTa&U)Y)pbTgRQ#`p#Wvzgt{vze8D_F#u(JDa_VG +Sd?cDi_)?V&A3HfE^~!>__bNfvsJNagy_d0_-RyRn6(V#8eg<3OWD7*2k8l2w&S+cU~K?l%@sFE55hO +`S#s$X&l$g)GW`w8}<4Qh>^zEVHH-SiJiYaG&S8R^r9@z+G`UxL=4`*6`Dkey~!ey8VPqO9jmhsHo#_9}&~Pp{BBo^7`!#d~PpCLeT997Z(S2rF-k$-1?Uflg>VDg*u6hwpZfQZqvTiVkP +JFV&VtN^%pCqG#WoU)31}K7_xnLNhS*^LLOLQ)#~u;o}=##y#BQBPi{w@@$Im8-6}Pr@FLin5-3OycT +12+ZczCj1KrfHRxc4$VM>C!=OU0By}zbYCaXzgfz^~Z9-e;r^7$8=?1pBCZS2?25jK1=iWPjPn#9RN` +fNz$STZNFasnn6uX+*}tRFcc2jy%Y-ctfW=N3@V$O`pG7EYc`|kY&fZ*L*-Jsw#|n* +jZr6aXxV?zjdLFR65G^>({yZLL@sy8!&hohHU1s}K`3q1>0|XQR000O8`*~JVIlqJu?>PVf7J2{x9{> +OVaA|NaUv_0~WN&gWV`yP=WMyvA=%pc`RysrD3F9E|RR1=Xm&sx!G&4z3%6Czx(a~wvRvk^OG;O&!2qq=@(yrx +_$ilm!JIi?LT?%?{0p#J^SHq`||PrZ}+dCzuG=|czL(ozuErs@W1X~y?P%1{_)43U*ErY{`TQynYk^eEjL*(SJn +4+gA^d`TC2;yXSB3UT)w0YK#BzQO_S~>!)AiQ9r!h-uvYDTRG1on)uDPf3SV@>ecp?_rBSl-o3ed{QK +R@9LXmSuirl2fA@0?^Sw8}|3kd8ZGUm+}{uVuayS;tbzWVj;4-c=e@yArXy8rI+`Q +xw5+V_ulciWqX@8A9s9sA?<>%-66i|4Pm$GeyJZ?gHAh_~DG*DpVOc#O~g`0(=n`(JtK=hrV|IQ07M- +Q$mM-p%2kzxZbR{O(Qkbo=w&>$}Go%~wBv_v-#deE9SG7k97U+-=X_Y(MeZn;-r$Q=ie1udj}5pFKqL +&h8(#yZiV%&;I+}2Y(-({&jm5tMT~mlOO!%6#QePFSq;GHv2= +&ef$y)#z21wv)aD9+kSp?_x;bWZli^G@Aj`xp8d<0-#pts`r@zKzkc-e=|^8Y`|BU$y)o1A*I2)7>Hf +!`!USJ#F{;PsuiyR}9ghb7^6Ar0{v|%~(Z^3dfAZ|F^!~Fa&%XHd>#w)ZzI?iUw0-r_(`QdU`R4PFo^ +D@#^Yp7Pzy9=tZTtG}?%fnKnZGs3|7VK7k0yV7h?#tO_xAbyt2a6HzsB;ui4MMcx&83`?{~4xFYfNcq +PFL;MSlAA|7#6jJ-q&#MPI`}pFO|-<@xLX8crrQ!t=-I;#ZFkKR(d^*oQyAesTXQ-uT~sc>DIJKYaM%FTecq!QI +QBKY07gACB_jkMWVm5C7TuFZI~A_uv2LZU3OxI!nA%$9(tFSjXU{QR=&wR?nBpUi8eJA3yo?`1aXfzk2e?N1uQD +rzc-Od-~+#Z=OB*@{4bueiZ-9A&gk4Pkwm*h+&0M{ri`XdLfr88~Ets&p-Y4>nH#7Q@Y;Pyc&-E_Vn9 +lUw-@9(@+1$w_iT})2C14@9*7&(=4~;*yG38=WTR(+?L+TZMfy*HvW3XkE8P2ydC=#~rypC72iIE0?bi6`IHMme9$3!FW443-^4mCWqs5(poblIIv)z8gJj@m^`?nHLZ +zXy(?d6JgYmK*-=vzFn-PX=u`{A3TCnF}U?PuYAoyoY`o&6@}$jNIAccj2`vq +`4Ml~VtX@lu^6Q`x>I@9H4n#r6T^s^*$2bjSr2~WS@Evubm=u(V@674_IGD`u_B%R#?jV-zRjn|<&j{yKN;p}0oz=+7gP#A}_#<1M{og)`>sjQNW7E!l33bt@}g-p?={XSLA%Jtnjq-SO=#`_3mBNQ^#u6;C*#wQ +a>e@p!;TKqh#q0E +I|C^gQHv*R?Z8F`%M#WSQxNa#?A1}P{+{(FY|yF2D}?=oIkQco@o0!8X5UZRJ;J(6J!6*I9-VG!XNxz +}{c7)q?bbckZPB-GkLbdTZpCad=2*s3Vt!*~79Zj*-3`Xf9nr=S>l>RE#}UgN7Cy7x+HYduv|D3VSB) +{(cz?mBW7+96txb1S?Bn=rp@U(2@oRLNg=!4*C~o?+v)cEY7)zKgJ2l?ln73G{5^EG4)YKa*&TBEX(N +(rE{he&TuyHk_IG}?`BtrOFo@nE)E?73LW!9E@iyz-dd@D%aeL01b~$2mO0&O5eey +DRovXG1Q$)5)5aiCxC>#IrEslktV2G$uLr4|X1}#XB_ngT;%1jA%F3{A%|&ZsH9@3HK2m=seth`s8RR +)*VABd=e+YZftC;IayFV%EaQ`HMQnEv^~~sMh9aAEG*;e#hK<^v+{(Kd0>(o1K(`})7|kD@l?Vzrod> +{)o$IxpH;R?OkO3vG*&AH6y4G>+vL%)k$6lPyK8yGE{kOe19V&m%Z+m{x!+owH_M_1pSOb +Lno}EW)gtmD8!AuiS^^;pX|2zpF)o_>lt&JM}ZM;kXj$ImVpgJu&AAP6Oh&voO_fC_`x5RIwyDy9|VL +bb_k7K;duPYu4aIBcJ5l;wHh)$RA8R2>dJ*QpFv=CoM%t$ySf>x|x{Il`HXI?Ov7 +2Z||;vDWnK9b7V_TQML34!#ykUd#9UBi8MV?w5lVtS&7U7lY-mcu|1@Yq`$c7d>!EG|4;=$pe_PQ!$U +lk}Z~OVudVd)8!E7BVfcpVuaE2f`1Y)5l!$YvE7FG2Q +C^8LXh5f_4L!ge?=iSkrmnMevDJH^7P$d%;Cm7k(Xk?Dz*L*SAyQ2K`H3|5P7#T +1-)EOswl$WVOf8Vo#m1$<&}SnXkxgFoax?tx1p*kU@wvURY*aN5o3X2}NN0AC+C0~2S%47|sp@iOtc( +TQ+K2K%(L0WPT^uz~L?toWKX9z&ZEhr-w531ji1HKKJaRMRrfShs+I7)2+v8N6HKvk|zY(S?r3h^W`t +2teiNbZ1A$V+gk52f*Trsp|AMS_5>2!4~E)I^oX0`rGl7?uKqJK}rD5&Lm?LZ2~MNE7;*)o$RD0^p1-42C9D;Nc6vJC+z1br8;{TZbq?>^1Du=~yzta~Hob;63B!0G9wAwo|u31dG +Q5&43ex?tKt64ki@H$D?B);P2&(I83_Ah7DWHb_17GTVp_BL=33u8W??aHJ&hWLk>JXNi2&I67FKE0# +;)WyRWfpE?hmqB{3|ZJ-r;RE|7rt3;Yj~l8TAP(lBuRLv`kGZ_6+aNUP3Q+HYWho6c^Jg(4r)1DL!R< +OnUD{#M*(3nPjS0*rT!4If94155=PW`rZ>VFoS&}?}o>I7i +nR5q6k6k9>*6|dioTsel~%n_8YxEbCt`~z07K>7*7n#OXXPK+%&*xjjmFNes@c7*|@E2X>zM@$88oR^XBiA5)2Vb-8CXHlB|8$MzfK1_ +a7!%FQ&gFOH@2VQ@sXiBZn|Y8PC>9*FmA`JHTxQJFs#04xBqF(h*4SWtY7u?hGy;$yMgjWOVFkGi@dP1 +*mRY6QAxW70d)gehG(rT8S(htJOnN&xpATkF%NntHe}KUJi$zPvRbEx-ErTfOozLH5YNPiui>|}vjbc +*3^HNF-SnWJ?g7SEbR{@C)6bnJ7(AKMpG>qAhXMTAC9J<=W!}jkxR7Jj{ +LYfQA`j24T}^@ufz%lFv@lVmk{J|xB&X9!^1TIFK6y8$FNv?m?5U+W +LjFsDB0VsyZspT%zbqGX(=qDh(<{@xNuO@ix_5)Nl +I!!h}otQLZ@4;&6cNp^m6HgYoW7yNDR%R*lri9?kWKU0*o=z)dsah78Z>#2BM{8m&rfzb(rtY^baR|$xMGjyuinFmN5h60M8 +xF{(%*Ox0ddh4zdxO^bL-q;zGhTC4M-;B^YcY>{cc(J`N=4Hp%iZ(#pi21J@upI*l;&vS~LkEQe7&?J +QDQ)@?y|$admVP30j02z=d4N(9)+GQ+(2E0C|d82SJ*E^KLDdCLi3(kE|i#HhwD(_^wZU_cAtAezLcP +BubkMq~#B(%L9#3DGPNhW?Bl7~u!cKwcw43_L9KYO1$|p#glGth9I86h8Zj}$bB<$rrkz8fb4Zn$s377sX=-~YB2$X%u+PO?q!$nyGF^ZCal85f +yhVJ13-K551q&9omjWdYJt=$uGiULaO0%>8{WAV!CaSh#&-(Bi?sYTzky4l4cOeGuVpMGTa6vnT=Au| +g!=*{!DB`zm{p)8Ua6Vo!Y9<)uK5jIQo1jjvhd&qErKI&? +rTZh6MB{v=YFf0M74{5T50&H+R5Qw>xyEptpF?+R&H&?D(3tUnR>{x99!w!>bTvYfj=`M`?SOOsYdWT~iFyaHMx@H}+kSzh#7jz}o)! +@a4&SfVL0L;LbRMSdIX~k+mac@JXk?%&rHfd7qA>q8T-AR1wi~x`}PGbpO0HLrlgISHPQoh4Q8YA^_B +qJ3{qms%G3EYcMrG!JfxauklBmKi_@ss7W)5DDbQj=(v_`z8hw~3Ap9F_U;k^7>7OJHJS9zbPaCdix8 +l9MHMtCF>Jf4K92-cw>tCa>^Rmc?>h@;k7;;U_^r!tL$U17MOQ)nKu@Tvcnzg9d@8)2fKgOpUFg +L9P08Va71vI(U9*V&%x;DP8-X~Ff#!dFXkL?XK4tWQAHQxS?Ckn4O~*ei6b%griRWw_fQ2Jx#bd +Z`VZ#FHJ;_s<%r5~agTUn#VNa0^-Iomzo8ps5fWY&cvB%M?>6Yy=>4?VVNgb +CL#-El7rT;}zyXFER<;E>s6FKtaQBlxYmny*Uu9+hlJ_cmUjRw1*YZo_02<;}ln1f&#Xl3Pphkt5#6A +Vhsi-p`C-2tWcA|RANYT6YYl43#UeE8hD4)6i?rFCzSXyMegK7OPY?05E#h)#?e@(&9y6{ZU<#z& +~1g;jvk-FhhV^=eJ*xYGM$4^3373h-&rlI-I}TkwVZR3YBSHJ{eq +ON$uYI2M_tPx>`MtkQQPjpej7wfav!x3lwzazrj>Nx9Rgy6xQwm}x_Dr>)9fkz)1ib~t&E-1 +;+;)Cub<;D(eBFNP6k3QdWlnK_UpfEjh0hnw@v`JL2px>j(QSV5K^VjEWc5HB-6%kcOjYn}&B09X_%h +lTz|%rMej?FKICNI?2-X;WeV_qN#gQwzmAQ;)4hf}Ds1wcqr~K*<;gwHgw}yOV3PO4QMKgZo52V;GZR0|z$Jy!X5?#xtSpuRL=GayA-7 +0cKr#uGjKBy|gFA>#z+2$qNk*DQ;VWJ&>o^#e^FZ3s=~1WM4R2nc@LC41#VomkK88J8t@6N5;H+D{;_ +(8PP@5~&B<{2%g27nw$qo)ax%k1-p8kPE&|NMmc3>3NQfK3VC +$uyGXlmeTUS}sdog6O^fbeS5RyR6)x8|bs*uesN-a&iac=rohuU3(u*PA4ZTR0qewrXZu>S$ +-n-+W{^?@KDh|bjyOQ_^3JrSSSiYoepl!YM1_l|CN!)3gR^?I=P_N{Gu-*m9K^yo;j$Q;5g_JTUg$aG +LRw@>L)!(^W|G$f`QpE{r#$6gu@CvS(h(2R)JuI+Q~}!A$<%!Bd|9-#lQeNH1pOFL8v;LtRUs@_>uiR +spB-YbTEbMw7<4%8?*~tf(EU0dSio^nGzd95J9;E1@OP)oqsq3msH|QYz|}82_NqhKzm(=mmmWS#Kan +f&jw#J*d~=aZ#74=mN2jQcEXpCXSH4Ak)pN2({V32FwLW@IF!MshPO*$0=m>};bR9&*7plPobV-t^QP +z2Qxw4}oGjGUfXyxz%T8D`ma$5&uIwNfVQJVv!*}z5z68^WJ!2xcSRNJ%OX_l#V4!AE>o79fxbpx5u* +-E|`@`Ls_Isi)S@7HHf{I9=&TP3__LpFb$OP}Pksvh7+RZW_5x=SrWTn#u{d7 +7p`{Iy;jw+IEg6&qnAcdGS%a7ld@rG5+L97MW-^iG+ibhwOP-+5CL`M7t+m8{Hl4SH^F@kE#D?-vB}` +z+*c07QoD0Q^Z|Eb;%$F75AY?B9PuIWJxP?7WDx<-(Ww^HA($a}6=OeENJXhDm0JJ>0YFP09BE0kD;Z ++K#8#tr-@0YNT_q20ep$J5|0wJ9fjG{$CRruKhj$7loeduSpsNKhhNDaUCU +BT^vQb@V77yw*zvy0TNxbax;CIv?(5~=(PxFUAt0EfqMb}W=+qhC3c(bOGx;C_m=sgd*1>|BL4l+bEe ++6gRRW8%tCP59GQL|t?i3G~*>y|zP!L2k`T2K5>7vOCX`C8<~`S{FvF^x}ZG#}^F}_gYjS +99O&OUV$~+VrZ%Z*r^>gPOsM8T|tFNl&K}n9sMCZkbNPpAasbZcxJnSOWM-g?Cwt+$A=qQGQq?XQ|JO +Db=Ma@x9-AhszM;ZVQP#S*)IALl;`n&mW*0O6@HxZ@z!A2e(Xj#;Wo_YaS{NWVa}mEr)JpCbwl^ss}) +*>Y=8(@vr4CnJC&THpadJFV$pN01!4;+jtocB!6>@F>v+(IejUE1 +e%rU0df^rOA4B8MX{N`!l3|b45-ARQC;cXn2>gc?zL63VzPx<^;k>`Ns%=Tz|58sVOc1)pL8(st?#<+ +X@6BLTrO()0>09Li|NrdS$&j;YKZW_R)H49#e1|WQO1Jh- +<4v8X9Z`Twe;i82u?j-)I4y2E(-Aa6GdMw6*D;fIh<;MYTZfn>H?4iqb?0V>uy#`Wp0sN3nGCmw$&DY3LhDPjT7`RTDfO +Z(h^>*0atXI2%ONP$4QO(1Zb;=GKE~=vtDsHeY6M&mFkL_|Z#f*R&G4*R*S{ghP^_pwH +&r&QQXq;Aq)M^S5mYykoRq__?DTfgQZa7mXw;NwO93|pxRF~KpFFuFku7v^hJb2^Cc-7TSd)lTphAf) +XEF6n49wywaQfiIqUP!&4$G5SfmO3|g7jUMXMtazwXqW}$Fulr>W=w7#G?bRXzF$$4KW7!+tu{AB(MS +(HEg;Z#=TI7VQ+8_T4D7bJ*w~j*`f@JA@vgB!%)Cz%#iDgjmolgQ~2s~_^(;s3mU8JOA&M&&x+B|}SA ++p%9C{VO!a$ps9!)Pfx^rVS}WfF3unusIyTb!6m&jE&G=g1~vV}+0;uw3Ul3Y|7rP8FzHv-=;dqD +SENQf&Cu|f)MVq+NpfQXpFE~SAG$unC`<)@9ippb)ZwPo*e9?-oOb6muv6{$vBcHsckOTn;EyEd|vSM +k2)e7Pk+Z%Ve^SUvXzy4Py{CY8CToQ7p*zk#*}I^^z&kRjxRP%pLt8nW0c_eD3`9^mP27~(OKb2;GRq&cWa1*V){0 +k=OpA+09ddd1I2DbMA^ty`+ngEbg!L_P$f`7--T_`NHS2T@kydzw{fUvHLfTHR26O%B`A@#-LWQrM%_ +L3F#;}?u!w$|4tme0gR9BQl*D5N8|t1~RCrMO<6LNmW{$)IRw;SUs_qpwd84+nEhU^DrzA~WC@*EuPw +RTafv^YJFj!pkI1})odZcb%_KWVdG^>B72QfjB)$=Wbd1F?qBju@{$Lx0BA3h8E1e-qG{;Bt5p&|~+a +i;$RKnnM#3Q0e12Yrp$i#A%D+Vu}vWW@``#)Q8Bs2W8P=o$gSfdEv7|TmlA2c1uoasP-q=TPA@ +}qRLZc^hgkf!YM4S4t)TtrRaZu*I(*5xbFeSxLLn7f$tvB!xuEU#ff8_E+|>e5SQ?YEOxNwq1L=SCWxLVySD*MtE+*%-1A^_gu1j2ys=^ldH-BEZCmUcAxb<9ZdO3PT2`xxEpXl_lC9n7hLgty_ +r$}};cB$?bYM{+Z>%TtPexS2H`cIdd;MPK4sRF&ienIbh*XRFg+DcwoZ!30gGTMiw%hwhyF)!)-~0|^Q>mU=ZA8Q3CnR>}?#nM +m*y73dSy!s=EOau8%)f~B3oCx$;;Yy>D;E1mK|3RRM_$2i%NL;4tQagb0HP+2=X1eb5dD6(GI)V(Uf; +FxBED=P$GfheM)PAd<1b$3=ZfZ{E +=}Nn&aGT23F5a`Ldp(pMXp$znl)B{(JRV7+U#GjF$WurZ`97W{JZKgivG~+;HFU3)EU4ojHc2cg1rng +b@!q!d=2V4LCPIF0XeA5&cQaQ!ti~J$s3Y42m#{Aeh;8I44M3d?uSG*#L-jDAwakpA`uRW>iAvkvV53m5+RhHZ1^v7=Vc#)Rbd>v2$Z- +LS#rh>rQ!}LPPgD$d5PAaHyEg%mg6NrOxyj1V!cV&^T4+g9g9v9>-H7t{fxpt7zz68?&{ckg3HK{gBY +{L?D5r%IdSXPol8GB@Y>r7K}v4?DtbPCH(`s*D9Nvf!);NG(Ca-Tbe1$E~_u+LqjDMc}%j5BIiFm*I` ++Sb~z`k!q$_CFkZE&3_R?clT7we{SY~l$GyAyELQdKL>IygSb>)H*oN-4K(GxpVaoF);>@CJZfj=R7y +`zFx2iylt|if66iF-OmOY(w{2ac)KFt*PnsR3Y>+Rz8qZ4py+MAtt-)XkTio&UW)Wu@ZarxaODhI +ItfL71!t<5%UN?QX>;$-oHVjb%1nbBQ(hS!HggzA@h|%!h6+~?Q$N+8uauuyxJlUop!Tbu$2NxNq~%- +l-^8X33t1yGnJ84a7)HbfcH%;mbq9rcUkuW8Lt&E1ZAciyEo6(VICY~87TT2Wsg47GMV3Z?FKHXlKs6 +XEn!DfN9nrA;%_!dF!IRl^7$m>U8{AMQI7?}?a5a+b+4WP*z>|2J7Z0ktcip%Q6cuzmHK<`3r(X38YP +3&?h?a#t5>_6$2s%ZEM5c%bEG5X3Dw3ODIus>H&tE>cMEBfhf|?hx+b3I$>5>kqX5}_25N>1HJGsy3RtCD~qP$^Q#S5pG_2bI2+kD%`xT0IYP)gSuBmmxrZL%p_O5`%myaF$B)CkO9}-P?1MU42vv*09J-M?^EDDftyx +^HC_bOHFeUS)60_x1_~)8RoTaCu08KhDqjHM&9lRxq0!qlzt(ZcRur-+(bfN=IT=~xlugDpjg-eYtKv +Sbz-h4o!v4JG_h7$yQIgp-JjmjJJ}Q(#EG>0z;Mg_!gxRU#l^4v=*PdXlQs{6euXa +8Z+?plZ9#b+a?@+q(^!Q-KDE?p=`YMzG9BZaL)Ts#zH800{Qz=$ZG!G<2_>w7Ge_6`jHmKBJ=DocJ@z +5af37VMr0TQw*(tQrqNFG9uEt@H)EJ(Ji!lETdRV@kl1{*<$+XeF^|Q6V4ExEfET(LcOThiA*DsV}NzoRk*>2UF}ZqFX4RFu5}S)R#HJ&a`jo9+`~x>Bd! +~a+OGw9q^XVEx_dHmB!5Qiy(wzYhj$D}Z<<*b*Ufog=OMm=wCKryfJ-({3ZzQf5fWkYJbF|23Io}|56 +B2%U2!0)O1uy(l?AF&2{$@xNNANYWF(!0s+%Reix=u%rDsuaSYj#* +FJS^X@j_^@;9$aLYH=tEgoGf;h&2-Cy3Ux`@{|5Hc?@y8n8zwHH{z$CME5$B1rsBSC!a_On@R{(D9!aCyQ3 +J{EmL@Hmr|8!H$O?(RasEoy&x(oeAMDQL92L$f#OW3&k{cFt(1!=NZqUFfA +O5j_>&)#2h=6T1cdHhG-fi}tmCVNJ$MYu4e1D9-S^aGHgvC*KsCH;jBK#NofhxWN>&Gc1vJu8P#4{|+ +;lgC%CWi_qJ?W7(3dD1=$^9AIAniW_Cu#FI;P#?EV&2$C;LqdhN13R07-j5_qwU#I&6r{x|_^OfiL4X +$$(ehWS)gr5GNDMyll_8Df+$C8#Q&WdE0;|n!NeM-GduC6uW4@2nt!U(LJ1cXo~m@W22TyW;EN?-@qj +uR1z3`ZomX%=T-@iytxQWEMNVuU2q9hTcvE(bf0MBs)zAFG`*N8pk(25soSUEgw5}8m-T#fFvQW9>5d{AEE2O1e(*MjL%%pu1g#G&q8TvjboB +1YvK{m>xG?d9q(y4Nj(%#g1(atIb#EwVN=%!3rKqZR5+$@p+wUetsVx%!raAGt5kz1HRV7B75&oKxJ; +^XWd2L>myHNOh>~BqDKhMsG6Alrx5Mwd+nagro@13fj4;T7~n*%SAG!tIlT&`(k(C(%UG29tDqs +`57nr&i|!Q+U8^UO6NhMmTYmcaQQu_ejp4yrofSUO+Y`0(DY-m-i$lU86>DKD+eKf(gB!<}i#l&b5F>9%;|R9JCvSj(^Evcl@ +C4klH8>U9Jkx|+s$V+1uI_cWj%~Ip!+{&}Knp0N7n3covf=0(Ia1}^*(OZ~89s)<(JJMB%`bJF;fZg! +Qv`jLqCk=2sxD8}hjXpK?lrhPI%`aJ6^8I|=Ez%eUvzb^dRT95^2iFEsZv+oCwAsB01ud|_vCc4Mhxp +~80SDu6);^o5WppIQ_B-qGX;hl88V2=EWMEtfINz)h?)Jy)OM6}L+sH^_EHbi(Y?0i{4x(}BNQHcG)k +Gyq2HmbLsT1V=BA)!s&>uk^Ex(g$z{LlUdiZs^DLIJw|Y8_@;-6iOIMJn6;xTh3FfSZ)6lf!g~dZm_m +%uvNB3HhbC02e?%EXl(+TxPS9T4YWhwa>->hZod{|Yl?XixZ@uH)99bB$3yyt^z+E74cAsUp^W#$ztN +LmQwOa5@7z+mS1B%&+prrrBqhiB3dNRX8DJZ8LnR-iI3^4Tm1;ZLpq4KG>*Q4K)>jc3pp*Zit4@hFVh +0=vh1XK~+kss9B>A*|_CRBF$QngBvzQnj$EIS@+Lp7nWd#Ed-4O9HBW^9EWfuBY86UKGcN!-s99zB;^ +tLA +d~_J(~L(lP40kUDucW}aSk3DEP_yu5=zoq0FsC&RC2-{8-hR!-nyzfo=4)S{k7X0%1hO<<4)G)ISx8> +F8@$rnWd+eKfZc1wAanigdl%i>7!pH3rQv`g!e0+36nt~v7(Vggyws?yHTz1E)ZA{%svX#Ptd&I~#a6 +rEuMYd1!EDHKU0i!nHjIyi{h*X4KAz2aFmqSfG(XP2#kvF#+2<#cNGKro6Rs^LlK;eA0~Y3vOQ#IQ5Z +Swg#$XUSY>Se!09C3M>(^96e1EfQI8fxO{fVZAQFRyl4}@J0QN#4ero;bnJ +t56}HdE|c_nN^hjG1LVs!57coyci^qyMbqIxs%KLa#6ozq3>rF1OG9?m)8gKI*wkWrG+~MLGj5`L9kM{kg(?YVttLBoEbnx(gxv+9c!0b;MqZE(;x +0H9%%h21smtu@UZFJlQgmsmX=QYI^R^d5`d1#qu?270qEFB_c=O`_<9C)?_8sdBYBL9hkNXh#-)33!2OH9ui_&`O$^w74k9E +c2c*}(Y;n=NZZU?S$qR=NcIK(ReF`9Ko$OO!Ea +fc`qO_hqB;M-whx`Gs|n7h9cGgrs^u!x^Ygp2<2oCyV@RgL|(tmw$4k%>E??{zhDxLKm)ER&?Zmp;c_tqqfrFC#yGP6RQVhhqk$M*gT^hy9=3PoM^N`_HQA)MSZD{k(7%;A!yEkWEI)2U&-wbl +9A%zHWIAHm@MFDJeC@gpMo)RHYR*%EM=l11cE(;rUr)0g5b9*=4?;BWI0>pm2$nx-lK?no_ga~&kOLp +t%EOGPRnM=}Hy}fp1y_>TR(O>c*vqz1{d#OhL=3+BG(>Wq2HeGkO3D8ig#Pc=eGU%Y?@-GvLq_Ghs>1PzauKy03h%r^3xt&4wShCa@T9ue(~a)2IG7WO +QQYhh;CP)L-lKcr&*^2Lc@Wj~%(#MVm$dJ?{|xH&rg<@=Cz?o4`tmM0a7nku?>(G=6PE|cc=J0v4o?c +4_KCWPx5Op?K$^t5*%|ML0gHJoaQf8@b`VG+k>ZUX=B(_ClH+n2)q(+ME`NsZwQy2tc;fO)9{7q#z+QTVl>DL5Q|3E`|WRmEvr~&4Y^4dVs);u%YFT +8{9wZdezrpAd=rc+1b=Ex`D^@kzb75C;Kt49tKq-NuIJ8z|--Glq^bVnpbd1XE197Uqu&28EIkSrTIs +&;z8H*x4xd<*%?1|NP40h|c!qI+!~h4h9qws-e_@NT&An;ME2$3kIxzMNxCQq|t|p7G5EqM&f;Y&yEv +0o-mquZHEpOS~LUS3q!@UQ1yvw+TM6dEYDbRdQQFm-2$^{+*8QwPc(Hu6TW=m+&@k=%Wq^)M?0Adhtd +@YQLWqoDR=Rn)4Y5UG$H|Ih1_y|_sdIQrt<4JGQO#MojH2;fCV +9EYpEpnN+=J;xn#`w@~`YmQlTk9PDX+yxB70~xK|A&!kD6rt_lz$!|?0_55JNCXy*5NP_y^jbEXbS?5 +YE2;@7(7f$zsb!!TyPAJtc09*V23x)L^c^OOpv=(%yfuVZ;oQu`%Gb2nb`0^Ms(!K7G|OpYRbJB^3b1 +C~VrZ#nczl;#8RKoqAfgnK^0D^_RThu6`)HfcQ%3wQa{Q~2zd{X!FM1c2jwLDZ{V@Jhmw+@|-vcn|&6 +E_}%@dhwE4kNdei764>yt>_p*QpXvfbox?N@)fS52f(qA< +Lfu6o}}erFDDe!7p?>XOUYs>CI(Y2-y%hoAL_{$Z$lU1{4VFJk~yNH`$ZygrIf +NDUTKb}bjG#+}5r^7`anCOi--hDt(y#Mv7B^MvlJNn^4!@?qg4OO?%|C*df&0Wh|D=u7^&UjFNHl +>MwO1Tv>q}>z+CB{I3!s#Te)p%k>pO`hTLT1*~ps1ZqiW9ap>h8@OahK8~C>h*1wHPiGLzcd^w_cmSedhZi-;RN@ApKE`tPH6uft2Q3muab;cfSK~zGv +%F^?@4sAZ&mG`iOp?8^+*{v0o_IPqPYrpV*wRVzsoX>sxQ}1}Yt^yKYWgR+Yhva7h|0`l?XaoF`OyJl +?%tjyvztof(;t|V36Qk3%c%UQ`&2w2A^S{!#+^8kZw{Z%ly$#wjU^` +AT`snJn{(JDpAels6o;yb&?{K$^K^aI1GorCG +hTng`C~IKM}JbrvL#4qE)X`%NGAoPm>PkywqT>s1FrOvuC7uxl&tUF2qgh?`8_q3lT*+W_9Ow1*d!qVBo$4;)y(>4&d?24Hvl{_tnh +GQQ)jhd;!XgPU)DV0{>g(<9ReqSDeWN2}`87fMSL4E2<9)?4VH{0oVV0i#WUcq}Jb?#mY?dd}Y?l;7A +zgWv@ffQ>_hBw#ygzRtgoG=wc&aZbbN +d=}Jhfyha0nHN-$bzUtu}zie=7*m^*>q$Fvc-{cU@O|4#msdA09QW=)YBN&<9bCz6j%Yue%=hyz)Rfe +QbzTz9Y*WcgV{BKZ80|XQR000O8`*~JV%jaiiJOcm#-39;vApigXaA|NaUv_0~WN&gWV`yP=WMyAWpXZXd6iUMZ`(K!eD|+d#4irux^asD*9!_%R_xTMzhK!Y@?_A`$|g#Y21zAQfBg;RuUu*ac +Y2bRJHy%8)i#@#AL{j=h7%eMjnqO>Y%(V4Xl#BX~;=T*W6u3d$ +zww_w?Ep@+q`3n>m(>oL?Me~sBXwHu93upUEVxzs4>k?(Q-0k0p5RHZXMKh^3Ru=SupwN>yG^_m9=tK +RmO3AeqwhFH0mYJN%{VRk$P-RL=g(l0HbbERj;YsN1qp`pjCX;y;LZ!}7PEhUH7VhZb(_~2_c2G)Btl +6TPS-Dm+1$ZP=){aRy+J%_go}C&5A<01q4GidOcOQr)&cod=Y#k!>snb2)c3^B1dfgH}=tnnq0eB116 +)AMX9+91k7Mv^1Na~t)3-9p)LKOOnv7$9o={PS{8w|*$pTouX>2g8QqnV_vi6)v)b#>#HuQmHPTKf3y;_Oc!SwynU9g<{+s4 +qRoi^QfktFmlg%%`$4`dGNfilLnsb`!IspAOPyHDNj-G}byyDrf(LFC#){mJ8hTq?~*Be$lYPO(n6!a +DXlYu2bA{R=6#b;h~!=Eed{LrDb1QO*i4 +Hn`;D0=uIg$SHW?p(D6;4Stva{2><}YBme*>0001RX>c!Jc4cm4Z*nhVXkl +_>WppoNXkl_>X>)XPX<~JBX>V>WaCzlfZFAa468`RA(HGa%!7aw%IJa?LwhG(ekl5hkrBc4FluAegOd +=#!Gla0eexDu*kdVNJTx$2~s8nobdY+!?e)?s`$H(~}x~$(cTXfsJs<*mzy1Z>)eV{F}$4AH18w+ZOa +wL7*qpQFbBo*BSze~@v@qIFx`O>j<5R&6b;cIdrQ$AWQZTeD6th^Rqg%?akNWqYF4kqMVLMz9fiUh0- +e1)&!GziTX0MmUCM&nK>Y%N?GEDT~+l^rtHbOBXkO@*r>RWB}H0wPzuOf}D=4$CU)2qnU=!i`RH75F- +ogBgxlP{mgmA-c1}FLW=xQ79*LLfD}u9nk$kj{`qGKTPGxL1>2yw%RZhf>bcnb8PH2ErJ<2wojpOrHb +HT2u-%{o(3V-PXbpC7d$lcT^xsZtwlIdOB>#_`gAK4c1kzG7a>k_KO1<`!Qxx#2ww@Z6-{|ejn|fH@J +71vMyL@0-dOiF35j{u{Z)htBXm}-F15voC4#RDw&wlAn^Rs`#HJVI!5iUnWjWUb>yx@9eFHm&?ePl$a +nLzvMFT-IC5AJv1O&*^$7b(cyWgW)>w!MjI-Odp_wX9Bury}jzX(ZArl6opI8|u-dV!4t;I7`edWRLY +%jRvf_rSYvG<&Ujw@Zz7hiY_R>-3se{o7iH?)#nlcDG(8>I$(mF&i__$SV^M0XYd^NmpoFdMY~l1Kg{ +yLC>YJ{Z+utj*ws!*$9#8VlN+hfj=_m#ger_Uy~e?ALtUzLkaGHeHrfpW$oV*Nbi(O^r8wO+yCOh1zj +v}$RVH`UJ!&Ox(t+N(YqSfR?e!`%1QNkrApNAVr1Kg>aFW1CY9r(qhT0Ks4QlVo+BuJW6yE;6zct}-b +V}W>R7$D)#%j!ZM8es^-hVR894E_Zgrdgsh5su%O{q6xNlz7ZmsKPx7TT2_Iu5C%j(qdqqx@oCEV2hp +|edpW}c{>B_Gv38k{|^o~6{^T=Gz^{&?ys8(7Icx`lxpy-01~vU&3&SG}2dg7Qf2QN^F7O6h&y?B=TF +@yE3qsU13xjvUWcs3K!Kyk4ds1VatQXQ8Aj1cT4Q(a7q?f30@^=ACt2>(#8@yH36Pu6=t=Z>d^2J2wK +|ey46d)Vmm~lrJvUW39Fss#ML;U9;7^>)&O8o*$WcR}9IrBx;al6_oIXv3~v_XW7%KM96D<;F-7{3C- +o>x0&!Aq>hlwfib3oQ^ns4kx(#60Nn`P=E*{_xEf_`Ws4VDIJu+up{$5Hzp$?cr;nQwsLJMH_DYn1IVBb3nBLSvFk)>u!x1RCe{;Vk5e7^n7f ++BkYKMU&!LZh}^pX;kuU>9YCqtsH3JuuoLJfN|6I`4H`jE)>_?iUu}__3YU*GmdJ_+!x69jwe7MuHJB +z7LZ67pgD}{&Cs()>`-0`NA39NxYm7dt{7MnC|Pwh5J#ZZff4C! +qL&4wb-0Obw83OB_-LH=wqh}zAB_CDD1>WFTrw)6|1D4WX_K=U$H +Hyd3cm?gCB$wuoyKmL4*RkIA9OYxgsKu;+|8jRQ^tC7K*j%j47cu!N5RU!V +hMnvBf;fwhm3rL4;cYSl}S3@x=?eR;7gl*sd|sN)M!uzYIZV&QTKvgiF#GIIEu2~(gk +&{56XkY#(>aZM=^Is(BxiN{KKpGn)9_6KvpT+w9aX*tE-mT>0oZm0SlHRe>#WpGuO5@;sZl{XEG#JlW +k&3z}gvEA13##Y?bf~wgR#@`BEGF~i}SIeK&U2ePG?dmu;?|pkTV2@n{_oN$lgSa!E^ggbfS>m+K&Xc +ACQ99Lx5DjIhG#R)kSHGglH|LyAPVjRmQnE^be(r{>UCoj8gQ!MEHNZ!A0TwbN%ec&=heMa;j3wxV1} +tjK_;qCkU^ZnO1RE_+`fKG=GF>NRxSW5xI?qk_7w1T-g@o{lkuB0sYvAf9Gg@2M`)oUrOX?4uA4ckY= +z~d}`)Ke8sZ-Ah`;mI*`@(^w{?Pe8QV%Nsd!)XsJe|~+mHz^%qnDW7D+Br%hWFJviSG`I9Z63figs>OYyA&GH7G+fHjiyspU+mH{7ge|Y4yW(K +uy>yQ#c;p53fEeIc-tWup_8tw(c^oc33o893Lt--`Y8#!6xvOSkW9zlA`r_xD_^6%d#|H-g^*jZY4U_ +}H&ntTcU-x~p`(uJ{8}ebnm;a}MU9p$`qro1bxBKr12SlFwoVZ3F6nQV>@VOY8XcBugS?fS7(F!T}3k +71PHodIIwJ9;dZ&Tkc8XKi)Nsj8vmpSnPoJ1((X{uMYXsYk0sa`=KKCkY2C|yV1CS>$;XCQCpB6erv( +TCVrrI|_Ir{3THu=`X#^#5PP^G)jg{dZQ|KlvuL`wo>Tio0%4iJF&pZ?d1(DgFUaO9KQH000080Q-4X +Q^o1<1$P7h0RIjE04V?f0B~t=FJE?LZe(wAFJow7a%5$6FJow7a&u*LXL4_KaBy;OVr6nJaCy~LZENF +35dQ98F%XV(sH56<6q@D&bsRfq>e$9F9HA7kmPYn+>s_G1&XC+zki`2&bQYr$GXJ?*$=9w2=T~$ +Bu(0>|^VKnUZ$MZfsj3&L`pvL#AaRrMF!bI~mmrOuUg$Ufhv-*<@!RYsP8%rKAt26|HqqGo^kJs3T4k +fpoA|LS;h1#J86@jh5B>yp56R;f~z;Y!G{nR_9(;oy3Q>8O|ppjV&VU?ta=T|Z9uyzSuwXx9bT|?*g_ +Gf~qboZbu1k9YTff*XquNY>T^pv!kuVPBGX|8`E3&1ne-(kwdTJtU305P;+6-*PmQ8Le;q!N6knNFN) +kwyfgBKtqJ-rYhCHh8f1uKNuU=)iMA&@V88!o<1mJ8^PU*KDJHtUWTFL&fiO4BLTFGJ$n9kpNxl$ +B=ECPbV7p8K=jSBmI93`fJoi(@|+F2?=&90rpabYa?^Erz}2s5^t{ayFgJ`yRj?aWAnGvr6Pb;)o!(i +j)k|GA>M(`U{oU5J3^bhP@({IY3Sw1&l^ymt0gsq2xNvmqJQG?|?@jm2JVB=F~gXeP`7rl(N1pYWl!y +!A~7>V)DD763i)lz$1bl2&Hrj9+a`p^|?#2?N6Of`=;}`-2qt6$x`<~fBg8)rE=A1L|SdZLm5*qQ`Ow +)Oto5Zxt?2UqWnDAFm2H8=!DeC{d#1aqn)P4Mxzl3X3XCDq*c7jj+#e~G_y3aRG~e$$|cTGX +_1?MGZt>)wx*#N5+8Cf=1e2hc5Wq1Rr4bck{AeqADwzvr{SQUmyuHQYn{_%KV-(82G2oP@2=SbrT;kN +CM1_u!Z|ij*`hE1TwX0sAcIog7_@@9Q9s7?&5ihZ-`oADi8i+0U*$InM53trcP@D3Y3g^h%&&FG%rDOk4777nUO!S)nIss$ZW(okuZw7r!&qxRpmun#ZO&6znTCbph|^mE+$Ztypg`S&kq5;o=; +pevGo0nt**8mABNDI}J@Ek33M+@*YnX{&T-}y%hUnz=V0n0=YR{AcvxzI)0&bcz)|Yq@R(kjH(yl{G{ +mblZiXs_&J3N%nREfr_lCpZwvc{H7Ow4rM_uTIsWe3;+=c}>lgRl?Z2Pj^iHAHYw`(k6|b8gKbSNyi} +=`ribZ_>v8$wz3zGx5_dr3izuE$|P-M@doOD_3bl&y6-GX4mtk7J2LJ%}6951t0001RX>c!Jc4cm4Z*nhVXkl_>WppoNXkl`5Wpr?IZ(?O~ +E^v93S8H?HN)r9~8G!_bR6i^l|ukJf+EaFr3bZ^l@@M_=8T!o}Zo5VlC)e%av4KM1wdGBvZPI|CCYW(XEn?LZ`U#*Pc +#AMuEtLpT(Mdh&pFm4PzxU39(P&QABYfG{qrhM^r<8Dzen~5`m-5CRft;G8wBBUv&~|^973OCf4@c*T +Tz0K+D2td}+$ltSc#CYMoJI(4;q=P8TYUiaXSgBAT&mN;8oP{U`z($skslmdhORHPU7eCq!4CexMr}t ++d=wo8VzaSxh%YwvLsNn6C@`^P-sV<6XQ4p%NfK8p);hbiwF`S_n$xFnfaMZ>flL@;yab1TwYufmBAG +3jP{vyx+uu3=2NWRe*RotW4-lx&`_3^p++fMJV)HD4}8gCSOL<$K#3gg-Qi^DJ6WCt}7@hRqR%&b^R%&<|`;T-d{ICh93yP2~y?yB%g`kQwAgPSnHIjorpb&vRq5NnmC{I +P0M=fZ8EPWX-F0XGF)SrH(Fa6o9KV5u%3t6NBw@9$nBsWPUp%_tVPh|xhhk~o&Y;cJe{g30xc=46JAB +`8&;e1Y`>lyXwraxPZ2e*^{jBajcH`nu_gX9Xag_yxS2J#N#5Gpq@kVKPsGD>aeUyvV~3=Qug_q2ct?Pm<61*%>m${S?J3G|g)J72ckO8tvo=m7~hb;~O0>AvMo +XUT0@#e&nSf4e-)jWPB%dBVPU&?1aueBW}#g>)B#(yKonmvmw4rKGA#XIRE(FUI!v1*OlN0*KMRC3R| +dLwQ@rlp?=>_VddLVXl1ko?kqqtgdV_4->*_F+*c#B-`nVgqLQ7az +2HET?P21T;*||!oUCJT!F^L(vK<)n#ntfJl{@Ggad>kjPe9W7_h#SHQjZHIF8cmglCh9g9Au?Efb{iX +)XOV1u@e&4?P-|nXWSC9IYX%77o2PRl^gnXf!L^<>=L7C7wwi}$+@XC*J?0by8g|QN?XUMK-LIqWD)(_L9XpI0Mp;5P0{+C+SzOD{ml;=M$T04bDKBmDd2r;& +e=Tcegy}h?vgdOpm!P5qdeCxHlcQq9@ma(1W5u;XxTdp_OLW-)ffV8%?WY-y}O;^QHedn;5UR!x()Y>g%`=`SA~sKMrH8Uz*Gbo +TBzXqSUV>I_2dZtreS!tN-Qpd(X5V~wUUUnzJ88s=19^j_q!ZiTt}hl$z=*Ai8-`BwBND4kQ|mb~jmB +IB<40jJN5fwZ>V-QPp{{@%8h=FJe-rscRS_86yZ`WM7+0OG-Bdc4l!Mk(iz(@_PH!FXJ|ZJA# +QFl^n*fUyCZ8v=3d^xfZ2kZ{$tTjTxxFH!5Ro|T(|^xK{mDBiV59)4rXJ!Aegb?*-B4c5Yc;Ldy!7qIEa8= +m=QCWQP0Nif^<5v*P=t|o+t51YEO$36MsNY|b~ia(hDhZ^4fNW((s;t1`Q`L45WYFc7jDs5XTTs?fet +$OJ@j&m}g9TDx`ZLccsdg<#qK_@@3nDR5T`y4`7S~IEnV9RD}#)kV+wN?Lz5J`PS^Fj{TOdS=g_I}96 +=is@KtWcCdjqy+!~1`YrKDF6TfaA|NaUv_ +0~WN&gWV`yP=WMyZfA3JVRU6}VPj}%Ze=cTd6if1Z`(Ey{qDcw(0quqx?0>~z?vYyoYZNJB~ +D;FD1ss|Xz65gr9_XUoO*x#9x2JclXXUc*2lZYyLWew?$OcV4_$UY_xg0xyXy9bUAnxvyZVz}@I7iA( +P+kWXXI4oLeZ5@n53ml|09(Wkv3C`VT&5IYcH2h!t)a^Sm`+%(kZzE81V12v$2>nOj%asG8Rti+~TX5 +YZJZznC`VH?Xh9uIHAchAnso!jK`WUan;OG^Xi1!A3y#Mh=cACb(Erk_q8-&%VxnzS;>{oospBmY16b +PXRr|63iF-rrJ5R<(K|whj-00ZrJL!zsvp!SIWfX4Jxi%!CaY8TjKt1qsSAolPKFa{OL!3BNaV?{+8{ +pDiAm&elv_`$UFSB6O_*AJ{!7mN +@r!FW6XE=zY|#Z>tbnqzyHN^ZBEjb02tuq?e@74O2%(2Ps%rnvl!`>j(x$NEaMvp%G>)xo}9S-UG +Za^Kn?+ix0tB0G;fbJg#_jki?L^NcsDfVO@B6&q|3dl@@$%LJd%9f=+qP-Rgs)S}@!Dw^L5)(xND>7g +HuO2F$PeZ*3Srd9rGNoL*7Cjq@*}?Sfbr&e>{RGKwm4ZO^YtIb>8*1gV@ +ve?e^)r_J`9p(Zdjymp;_(fBH!gIchY}DZegCPoV+RMk6&kqlohYvt6Ctig+e-9y$zFqtS?!uwDrg_y +=2c-qe%7ICj`ctR%g8^CtY=A?tl8E1f(u7)!rQ0y}^M%YPS~$7SyX%>^VSE|f3?Yq%r=Zzg~-K*}i3E +_#gn5LNZlwpMeSGP)Zkx%Ae4G%@!gc5n<>+i8gf#zjn&-zj_zQHU1gWX4g@&+UMCCh{qRBWE|@Cc@%k +EGx`3H@uL#u+0r;@SvVJSYE_=Qt?}(9=Id+_FkUD6WfF!=FDLHEdpy3D$RYIJ +V0)RhmRSN>~597i-2Qd+(c#65->P&#z`ZQ$(QUoo8l1X@GAiBusBs3A!7!Zv$nnCg=3{8*;=T4^5t9X +FB?bc~kdF6?JWZ0G^x;?(v2)l!`n_ekkV13Rp0^)iZeOi`-wt4wuE2lZo!r0~IwCqFq#=tw*r2Q9^X| +2SVtcH`GXA(o{Uym41lL%6Z2%q9ET%B=L)*L1k$te~|)kK+Pc!GT0xJde0h1EXe;|^D)XQ#H3v^C_Xdba~C|A=AstnXv*#;*qY;UE;1gEPqrf<1?-w9ja6}_!Jq9_wRQQXyhlMX))+beaHV5KQM`e56dJ +eID6U^tt0ssK81ONaX0001RX>c!Jc4cm +4Z*nhVXkl_>WppoNZ*6d4bS`jtl~YY`8Zi*P^DCajg-BZpiFzsRVIh#LN&vBoB2JOnoy}S`HnI)r{`- +y>2vw6vtxuje^WJ>f?e_Zz!|`1*!!#L3sA9AH=p>ZH$ceR&Ms**p9pU$_Q{PG=@s(yb`u(kc +5$uv4wECX$4wVNe3l2R@fSREiDn8DgBGEG(c_k$eClQknX5YkRC!8pN(by))ca=1GLu#S@??J$!;A?* +%)`T6{h4I`|e1S6$*>}M#-GXQ<;-?1mUm?n5(G3rfztXP)K?z1QWyZ!b_tEAj$ra`#{z)g6nSoob99- +!B}*J5A|D^T_9d@(KVFd>dwsWyCb-CT0rVXwlX_zt71WJ^hGL#4Po!7+^dKcI2Mmdc0XTbh-Yg&#>-_ +q%hm&q^_5{S9q6bew>u?Sn7gt<`KuUBTaTw-%Lw+4`$&y>clPt&!sob&kHLurkfvP|y4#aEDNglX7f!yrs|JtE4`nlwliLPbA#>k-1zN=Fe`dd=JEniF=I`vA+LMa{{5Cg(ALiwpzX4E70|XQR000O +8`*~JVy+WEXXafKMKL-E+A^-pYaA|NaUv_0~WN&gWV`yP=WMyf&c5WV +|X4C0FeING?o1vU)|tfJVd5r09lQRHOM5@i#mNQ0yjsK0(6CD~3|8`$MZnwj@F^JX~R?)gIx!>93#Cg +W&0ONR6?nMcobA-3D;(sIXXZp2n7CMnVxCt<13KZTSm&}K_1Y(eJ`I#$97L_YFYF7=)p(mA;^9EhKBX +H&N4Fcn3qM9Q4d%Hr4TwW)tVObcxqduljyJflrjGyZ-RGoEW-;i{dZUUW@^ySu+ZaW*`lP)dq@tfiP+ +ZeDrTR1#_BjM$V;o1VS?0t?ZUnIE(ea%)6EH-rowIZcbo?X+s^hcr@b3^SEiDL0&x)wz2^V)s<(l2WF +~@J!f-9zr-`D*Hnl;0v9Jyz-_}WhlpI?YJrILBprniYDA5Q+ncx8&tC>H&UbkPejU<<-{!Qz0K}UN{x +IXNt+0bH0Wz}?}-ce{oSwJSXk&&FgV=SGWJOu>M`f@M>qE#c#WhhlIVsxAcM0KoGQ&osG4>M?ePIZ6# +9lSq7A3g^1PINFd&w`kC)%(t0jfA7y2H?aX4GP+#?oB!;AhkOrqFJ0b*2Tm~s^o2f=h0N8f-5AI6jM^ +2KLA8ZT$VB%#qfrjQn4yc|cXNf^^&6))z=Fd#}eKS=DwJSXx|;vAHPhLRa~Oi0_5{(|;eBq%f5u~&{Z +r(9s7h~bIstN+zQsnxcw7m$;n_at&s+V$zp2+R(q@3-!P<<#FkhCU7Y^m%|FrhZ7YAOU2^A~(FFzRj+x^?_+di>{VN)-qUZSWog>7as4R9wi^I +E6Tk9}x1xt5A@dGldJ&E^B8&3-9BlY6$ue)_JT~Fta!% +3&pNhPzkF*mT@c{Z#IYzxdrPV2S6(pj&2mdm`g`(sP2pWZvFKGhMy5paEqg`-tqjqJXjsk!$t*Ao^ic +D@{bX_EHOku_IC7e9{B{pLBw-OU_*XiYoG8~%>0IT%2M<~cW9x!_}-?Kx +^&d13*}Cc|Xxr4aVg>(IRKs0q6kXNDUR`34Ol?cHy@-CsyJqUD^Dj_K0|XQR000O8`*~JVLXo%E+I +iAtkhF5$pG0Jn~j}zk^19z;y@^?_OPqw0)FioKl9#ru7PJIiS!>`>%KQZj%>@u3xl#qBgtSD+W$VoAbUyPEH|jG6 +-{IOyUes@twpAY3=ppGn#U3f02H59vT~gq~To^3=8(JLPz{>j3@QN0e?{;DjMgg~;7K`~YsKFto#o|1 +c{sRlV$V(zNnfh$Vyg?Py@9Xy2Zai}bEM{Tg`t^3lZa=zHx7x3@TdR%3ndb(z^35ti7$p72v6b3&x?I +2Z(;mLRhNKU0-aewJ&8*X-@!JXK?Lg6_eOjnxF4tewDLe9ppLWNF~P!*Cu95*9bT5rlCvId +6;!PdB;5FCw&mU|akA3^a<}S$yi?*({tjge;E!CXDb`i84h$e@2mSl*;~*Q#6Lvz~FWk@RZsYR!RuSaN$A(c`~NQfyW5-buvHlfP5CV +j^B|%m3+(L;Ru^kDQ`^gn1<)WpRip~Qs8*daSMJTGDvm&H$Ge1n7{s&C2ZBnK)Okh-8I@F^A?x#^QSW +<0dZN`}>D=8N$`p7>kDln*iuL4(o+iD}x57>RlGw;Tn$BHa&Eu_=(sup;P)h>@6aWAK2mt$eR#OCs1w +Azd003?e001BW003}la4%nWWo~3|axY_OVRB?;bT4IdV{meBVr6nJaCxm(ZExa65dO}u7$s5#66ZpyO +3kH_N{G4h9dELUPH9zHi#>)_%`V+tl5)Skvwp!in7iDy{sPR-JUjEuGlMTLn;*Dy-+5CQdqZ~`xNtX~ +4L`ye-^oWm+QSa?udjb0h(>n@25Abu_0`~`M)iboUdd +1jqxf=Xq-yKVTe1L5nEDImJY6Zifj2-I8ZZ*(d$X3JsGq1w`BE{pw=5+J5wuLqGbMHdd%1&%2~Zewox +>9m2AGI{l#1dvGUmzNnsy?la%|QH)kc>bzMDS47&T=I?@*wkYUaCT$|DLM^9Y4TU>dg8rq>lyBb!K2B +myHz@EN-|fSk_l-A|}AS>Vd)m$wy&U62ae%-H;?n_C;$p``baQa4l?=`>PMRT={q8SI+4r!NFZg+7yv +V2QBPnC#LnA&@V8@)qxB4+|JfAK}x$_XpF(;|=)|({xJx%aQnIn{ooCsuUAuPl*%2#Px^oDPnNvjlIR +A&3@!9rfv|xXy(HJ<^zA>4Ijn>ALbAKd=|J4K!8|MY{_gQ`9N_(kQ+%%#%LH%bzb@-wO3PuX$))jgw* +B;IVlPljmUQYO+8GdSY`E6NJZ!k@C1Z3U3gZUv_tAU=`NvE{q2(IBc}^L_sNNgzfx*JK;a{v5cDXOQg +z`@YGYj|1B&fk_4~ay{hxY$fQNuA=N@pU_pe+cm#tQ!|58l~HU{5CbNfCl#M{`nmjhR^O*a}BHC53juf|p@bXdamTDx-CzH)VAD{qJEg2`96AcT5Zh +v#Z|K)o^-h`!KI;EogWOzmNXGuhVL^qLgV3u8wM@w`;#>=U9M?i;L_t8>^awofu8hy0GgPbhO3|UI5z +?xl~>zQBo&HhUx|s1*3VI&ouH#s{xtj>D~%et_1(gmMH?}8=1oOnp6TB|K3r!y-Qh5X9Guj!pIDY$0& +?lS|mgg?%^Wmkbd?h#stEU(*_R1w#GE+(((|iFv%KpJCy>9itVF!w4K{83F?9k>^KLUV}zscoeGEi3o +V7z-dp(N&%+r_Qg(b42tT|Kjx&7k(cW;wz?;m+-pF(B-`4tEzrZ2(QN-<2jUFqi)A>G#Qym^5y{eiIg +ZaRg^PM%EivC*@_&D?3!Sugfe>_VsI?YJzPe>?XL>tghzpbw7K4Cr-7>C6&`ifUtRM6~}ZK%d0Mblu~ +ol5<=Aye>cg$!IR->zh +SztQR=I?evTc>xv*w!xRwD>OSL&Rl;Eu)OzmV5N5@f)yo0v?q3JMkgrc44tG??_F_O&$+%n*fG_3#;e +z>?oSZoV*M`nhI(Yw(fygetggzlI^s9Uuc)UcLHnmtGZa!ejm%ZnAkX|D4=-k~LKFCp$<6Jk)09m;^0 +xLO;JCg&^UtMnG87|5j5j(r&cK^n=r?+oJ%1VS>n-$}lR4aRVBq-e#+l9*;lw=~d92hd{=}31;lIL^w ++QHp=Ip+Z*=Y7Ky$`*`7vdV$K*fDNJXKy{@oLyU?R5j<(aNYG4-@)2sv74#Kl&F?O9KQH000080Q-4X +Q^5*gq7()I01hbt02}}S0B~t=FJE?LZe(wAFJow7a%5$6FJ*IMb8RkgdF`2PQ{qSv$KUfQy4DwJ6>t! +q*SZ(TqO$T-3D%bT#u~yzQX3L#65Qgh?z>+%Bny#_g2?J!30mlM|9fV79{N||=!k#8dGoT>g?6ja>>5 +otZ}%G4kl=fiI)cGiLQmQEwksTHcq0k64-@Y%+i^tJQ}clqE|^3BG3q +KXG7ZF!yF)3Kx_d+5R#-CL#dgj{fiz>L=dFw&v6{b4NHG8g(Gm#E)`#*}Z|b^l_wcDP5^>HvQSqu}u` +WZBx3w1mVM!+Whe-7}Cj+NtjcseEh!Et`*e3nQk%Q*a^z8b7Y)l!T`^=s7sJ8Y) +s|*%Yj1Is)5WgdJn<@ed4$6a9(X2!;}7zO-ge8Y7@Dd}|G^dN%{8cW>caUDxZ~!R;CPVy4lG>$i+#Po +|EIfpJz!xWOC;jsZGNy>W$#`gyC}8r;(8FIt0c(=gzor$Zh3b$!rk+_dXD^l$Wj&uC@=3@M`&Q!=?E| +4OliAtxSYawG#`JN|O&x467M4$v9FSUgBvW>ea@qd?#hna+1wL&tOP7xP8QDxqg!Ti|*@8h$Dp_YUs9 +jn%~{tMyIerZ=-`DCN;fxIY|R0t4k2wuPZD9M4l}IQRU3Tsw8t=Cpi1m&q4$d4L-OTasST?0(tu5;;n +xEagogE8Q9HWn48nIvrt-ZC4rrZlYA`>ib=zZNQ&!lFFxZu{Qt?D~Bg8S4rhKTa|1UYs-@^AL;UO8|m +`AP9vSh_otCg<8=kn6?k2NbOl~lBwdl$6-ig*btTf3cwLEfC04v;+NV*}fGf8LiI+JuJuN#qW#Op?+8}T|xI +?3xK=_IeSNN4dni*%NiI$fMbW3V`k>!&np4DjYm$C{eD-qTTzuR{7c=k1!7A(ooUjqw6Sxm(h!2Y&XipEG|}|$Y+i&^-5~tQA%9Fa-yXa`$+1en!)Qd*(0>XKa%~}KD1m$tFV1=34QS +%+@*;sLPm?u4WfsB*KT(2kU!c@IzxCn#(u){)w%2RzBJqD@%ORm2;aX8#L$t)pJ7=G)5}?|UZIyIOr% +nDD{p%EG_3VF#()Vk7V6HCZmhr9s5dpPn2_hEtM8f1^Nd!=Yo9iq9O2&2#63A4Hz-TKJpLz@Y8gE(r) +iDuuL`fds|sH@P=zo44=Vg4TUg<>bo{+u;aOW);W=AW;aOW);W=AW;aOW);W=AW;aOW);W=AW;aOW); +W=AW;aOW);W=AW;aOW);W=AW;aS_N!mkcxa-rv{`Y!coj~kUK^t+J4ft%PO|Jx8_dlXp~{a1^-ONyT7 +gu@}*-mAL?8Ko!4=-|6-$rbN7QH5Nla6le^`81+?q)^Qi9^H4(1KK*Nt=*H!z!oE%4K7N+w_2iOoZ{A +R>g3punJ#I*i4h{gLt%!LnMQXL@wOwqul=1#FRDx-fw*5^vaxf5Jr3g`$Et<0Bb%$cl-5Leo-cp-CmdV4zU|w7xoTAqYK!3ALz;6b;Vo;xg +d5Y??9t5Af@a4Ss-14*tS5L&+SH}i;A){k#5X+dm{bX-;PMXim7;nq}TeJKYT2#*;Wr9iyJXt=iy^1X +8U;f_<^MF4@vP*z8#W&e9L$Mv|>PX&&=)1hKGOJxc|3@3m%Ofi>c!Jc4cm4Z*nh +VXkl_>WppoPbz^F9aB^>AWpXZXd97A|Z`w!@{hv=UYE%_;gbS@sx+d!B2#|!9U&2OJR7KWe7TBxVMZ4 +>?^wZy4+Zb%1*Xn9T3f_J5X6DW8tS>H3KXlu@?+vNnYj=lUmu~x`_6ItHy{KG}zv48~VksnxsIBIlgr +T+iCZdQnlwMMZwMJzJ?MYz;kRATaBemh0)Pn2@4&aB}nviWcme%W7ijY~Mq`|A+D4o23n8r${>!Ie%@ +;S}63FEOVrXAO23s);k)pm`VZ{Pk2ij(jLwW5e74r^$_4cE_no@UZk(rPh_t$8}bxX0FBOMp +D<$7&)8r~15)aM3~(mj`4Bb#wNi$nq>Mc^P9bSLcxShQ|?{ht5mqQC3;!>;Gi-DpBB8n-5XuRZOzC +N!Q-#v`xWAo4ihN$kWtC2}tD0+ee7CFk5S5t%ggN0iqfz-Ue@_RLY{kPCDaF&vT2_NQtXDY;DYg~?vf +8xjkW%GT+N7U&&x-%)kJvafF)O&>Pu^hXmQCO=0gfd}(Du>kd5q?E4Htuod@2QAoMy>2$IE}L(znnY6 +%E9V~S4m&?ML@p|og;r~7a2cbQQ8jmT##+sUTzVEw1m`SV3CWYU485L(E2a&%8z7-pW;ciqjIex*_gBW6sxoTgGeogtmBg*vRAx5 +Z${I0#%E#mTV6-3z$p!8;bPqyl%(N6SaTG9*nzI;D^agHKlav8JbS@ZX7!z7}3u}^ft`8SR8cFMqE~N)B(&rxKbb +Ae7Me8+@NO38@Wlkg;pxx{3M=|eH;vl(Sh-kVGESIPIB#oLKeHLA)UC%UiE;sR$#zrE0Vk`424E_G8s +*ZdF1tr)N+Zc&G{@|R$sj|guiTRtZ>P2tsxFBoFv7U=2iWO=J-idI4E>I^OY5@PNH@?np}m$5!V=w{9 +w>MvY6qU=5t|{NB<>7&-XxeMvK$l??%&MC+PXV#J}g`20eD~#0t=N$Ms)A!Z6mFOZ*4QHRrK-a4Vdzt +7GbYc9@na$EW5K9OdnF^>p6o=y>NR>42}E$A27I&UA9<{WKkq4F67!r^DSJ9DIB>uLb=^&8Ht}BeA+|NED1XA&_cU+x_-Cq_lEb^fJ+rGR{=hqSk-;t&dYhFwn;N +9xe#+MLk{F$X(ZmmXyUi0e&9b$x_Ie#%5!(0;`h&J`7+oiy1xucQIu|xU49O+KaS;`ONr#xSpyWTPdC +L`iY)Rq`&vDWrC#8!RsID~O9KQH000080Q-4XQ}jUtd0`j;0O~XV03ZMW0B~t=FJE?LZe(wAFJow7a% +5$6FJ*OOYjS3CWpOTWd6k-Lj}=FfhQIf(NQo~-YM9EXtV^!7D**;iYcQ}4XypBZ8aSAvY5K6I8++Mbz +wtZ;_DW`?T_l(LRAoj+#^sGm=B-=b{#Wil{PFBbd3^TZ;gjbN%l*esAN;NSld-o>Zj~4Bcjf7E|I7aB +;<7wAyxo=kb@}P=-}}qUi~RlJ!>6nLn~R&nvAnt5@2+mjn;diZa9Jlvxj=PJS-P`i|vlRcl)Ws26A3x_(?{CU?4{n#*`*K}oGg5nZ-0jNs;oZ$|$=LVh^Wjr@b8%IUySMx6jeH8?rd(XT{mbE)^FJKk?%#dp) +u*esxgEH^*&RPzf0f~nPhOVCyX)k%{J6W?9dkF&KE1x&zsbpu_iuJr*Sm6YT|V;e`u!i0dITfSzc5lB +9g@7#{l4t>`JJ_Y-5syuO&po|e@0cNaJCpW6J$#M{aA=W?0aIDVaZ_nRX4W2bM+{z|g%Q}+2 +s63mVMmS$C6@5-m^-Mdehr%55lmcO69`0LY`FUq|qKbODXJ3qho0#HRPS{%c=@)xzxZ{RYJRiZr$v>Ebdisr|6e-1JY4-&>-lgc0ZJx!y3bN94NH3wZ>{o^uK@I0qK+P_QEk1h{~<7v5nxV~ZNPxo@} +5Va0>>NwO=UOvw^|Lk9R_~eKGBdFXuNy5)BP*(2h-SP0DT)ak+G5nPF{a-ib1BR5od7U=$=JHeG!)5L +-5#Un_Io%@#_~GLxjO}Lk=Ka;-^6<-Nh}Kw3xpU{=%5Zn-`8%fh>+c@e>{J5R(m+@{u?mXGe4gPuVRStrmoEYOY+{wRhOD~rG#d +kh`ObERE#@v&Whc6%e?flh?pP!vQxcB(g4`w9}K)TP_9f`FqMSEl2kOz82 +rX!Va$X+CKj1kWJ>z;_Reuon4mDj^ +$gc@&RuidNf~8sX7ub|P=Ld-c0`rSwVFa(2Us5__6Wfl(kd$Tk(!v1&*YX_3I){4=cOWh7F`h4!b*!e +@?8>~)aU|zEo6c8;@r-bS4>wu=H}BxGzTo1N4W15Q(9Dil0p5svE*y*%vhkhV%T*fn9?1$mkmo`%W1? +(u(L=*^ErA_Xqd=6LKU-*0fD2ur4F#ZNH7`cnBZFFhIxHpGy{?5gc~eyv-IGDKaoXeMu&wC=?=lBhjq +tvTyJFG9lm5PN7vL|{)NzBZq1$4AthB;e0fAt>)<2pFrOoTHXg9KeVKVIJXW;T`Kv?P?WJhi;&KeBA_ +Rue@st&(Uu%*9Z3?U8yp?yD7JJl%6i6G;7W}W(X&_0!LX|Y&u(~{6zWN0m8n<_;t*L`RxX_xoQy`Q37 +K2gJPSwrF_>cmRT;N)_htwH5GLM-~|wGD07p5&INKW$rLLE|;5C%8G(J(w#N|u_zeRv;-ibRSZwz)~5pKFpzkO{3D}-G~cy_n3>6HUSPZD`9sNFbYhcBo7!tU@SPqif0y +VZUtq>R7z;cx@~(EyCb##-tqw+ytHbkIwI5rQS!~)!FX@K22$tIwpELQjc`X)^R2@fk1ZOQhw3S7k{#{Xo*9w*gzsBCIVGuxVjwl-Pj6Z*HD>kWkurp5LUDzzJdpg +U<}|>=H>?s56R6F?ueaZoB087CgVbu)`Va>)=`}lJSxJ*D5aib7c{=46M#j)uB7aLQawU#%0lwYHb1OjvxsTlWT77j#+N|PAykg?x? +ln0RwT_RD_UOiAq)7via(AWz?6IA^yF}TyD8nwA^u$2BrbMsWU<{#@<1V4z|W3vOpC#)}{&cDI~HXe% +94=0oOBc>Zra$s{(+-Y?vd)qt;(hr1sSL6V8o0+bRe^vA9ezN2Om42*e4AZRjVHI!{(-3WHNJb2BOD`I$>u3;f5J4NB9 +|av9n`(j~pczTfA~a_+cVx{@IA)4FOUK8=osFDR1t98(7Cq@2I$f119Vb ++X&4Z3?h6<}eT=D=1Z&68gJS$PK1h)L^;UvPgzy4&4$?`XZjgVI_$OE4vbyym7;wSnova|GdKsHqc=AduC&=E3lj$lU@(G=fu|HATmeFJ%V0kFNQ5ZMyHy ++ohO`b=3Nbz-M{Gbz2v|+NgKBgFHbycz3XD2nKy@|rsVKS8@JQ)Zb&?4^3jZzy&%jCYkx?(7jX#Z>Bu +ggtrsX+0Kaf&vf8o5ya1oF~GKhxEr(tbWpJ|66_MEcG&zPwLD?C(f4qAwz7)u-DGS1`!>?V>&z)=;F$ +!#i;aSI6s)KLo5@#xWJL`+s?Y^wTIsw^BVoaho{>OtC?TXum&$`}lN5)2>AlVMvNxT6`PUhk8yF1%FfvPlVqS|GO@zto#mFvbvC=3R7FK1=eQpGq7f@|%fG+{!RU9?C@9-XN2P2-1jym~ +b&~UCGh}qblsZ^{WWl>#NZQ2uJw?YLg8ZKJxnOIxcC%`P{g*}=2!(p+K8q8j>&}ync7_HrER}>d*oQJ +Fxn(A+&zkl%+Psu0ZoKO;QTv)!_ZK}Q_D;)g`MCDPpdO(3V*P@)VWyNfoK7?U3jcoEk3N|CY?P0-$#f +$8eG68&5SDJlLZ&bjcHOyxAVIxQ&V`pHK^=x1wB+LmSrVlKK4NR-ckpgRX`8Bt^DU($eK5vf1=SAHju +LV6~f`u(lcBwY08!xmc32b51DYk!zKvp%2pAq(g1k|J45Io79f~qX2bb>-8C>hGSDP@J=%48i{?ARcO +5B5D~kr9G2B0+4RQZ#R|Ed-Vsf&i>#Ow$O!6Ct{8IVlMoekRXH@HDkhmf +_prjJn^8+HC+$d_b4#FZUSW)e@IL)kl3fp~n3TmYxnGZ5k*Mlz;1gtMVfI!{979K7KX|S%QCq~tmagB +UHt1>!~E2=r8^noh7B4L7t>(M>QMJ$FiMrHtHl)*_5B%@9Vs*_^Vb+INPrWos*cxWN*BG??MLJ&NoDj +LNw5~K?Hq1X;Re;gofoLQMCPNSYF%nJPq)@hcDQ3%&#E~Qw?aFz#Q!_n@?_4E{C@aS?r;AZ5jY16i4@ +X-30q&;Zh2I)#ZAc*G|z;0YDj@TnXx1yea^+H11W~YhqG?i>iAj2GE#~F#(6vd{^_?yLwDcGz%QR+`F +44chH8Nq)v6=XTu&OydpolNgDCnGuQE$KuoG~Njh3cym^#m+1j+13AIHHjhzbm=3KIC +LZf|4QcMt@hs;kc|Eac=ClOQ1nhzsICRMCbscHZ8xM_F2+us~r^hW>?+?2#F6$-eR>AsG%AZOP;nM-> +ey3Zt5lkN$-8YM%=}v@BAvQQKVQaLZPqwc4d3#L(C>85mUnIUTr}a)l~!nU+nm$!KnWkW%6cWft2&7r0Q3oyNBV??jg%9CA-9V+FWmE9Ql`Q}Wj{ta83|%8e0UF*cbGf@9B +O}i{$tRwIW@!8ta@61iAY#kNz!;-8nLGXw^oD~TR#*N!~XBuQ^76nbQpyL&^ +gn~v*Q2E!keT6irL_wJZ0UqkJpqvWw;xI!a+J_Mbkk83pp1q=ff?NfK2oR+G@7S@<0NiJVGDdMr~w0PEml5WRO~91WVg`4&<#(^^oPbQSf6VDg~$kln2>Fgbd@c97 +5y+*9(F}AtfgMmCYthKrAQ;0itD0)=$=rHVD}ccxXR?^Q;_V7}eGF@vE=@hZ0FAYd96QBbgl(p0sLYmIgz!hipE2P+^l{^QM#V +(WIc?0isCUS4?>ldu)x54DVoR+GK1A=$ZgSRLaiMs?`&ba@M@=h6-PU}#YFJ15ER(NINgFz4!U;JswfY +})9SS23|kCg}T#lDk8c;EvFHYl0(yKFXm4!BLJxf^JFxT7l1pX#mHrT%5q-!9! +0XX~*RQM=~d}F$c03sZ8R=?Nke+HRYy+BiBs6fNcWTI8p1Fy^LmK=F`OGQQ44(d1_`RN@qIFix0D9q^ +Kl@nWF$A3x&_{YwKKZ5;0+P26+O|Ajs+~lC`hZu?a9Z~-% +Uc#9oN(Y5qm`x^Eb+F`>`dciXCULKdy?Rci@}XlM04)8Ig=&YOLfpEfk#igM-Lb9l-N8)X(9|yz&8`r +Ayz5Cl6EfZ$gnF5Y-bWlR(+Yi#|R#d9&fu9H3I@+2{9^*tq!u!>-I1~<#nZk0t8cs+vXQL_~s#~6UU?*m$RrTAL@f?g2lxA +9`MO3}QXA5$VC6yO~#TW^{esQ|&+?4Fe;NlU?>>`Z|uYXYNPXuGk?U?a# +~5VWW*!`z1AG^j0$&4@227?u^3VWwcStJ^;!JJlaJ{wQW4)L7Nl9?3>f+SExo&;BGOHg4K*X-?K?;e+ +5jtJOs13G%0CwBL +lmgsB3p1aYSnACJbCk&*Z}rf|8$n*6(&FK;bUF5*du6m!q%iYXc=*AB3}p21+uDoMJ8Nx@)MN5|*_MZ +3CrSxW_+R0ISl~E01=7_Ai~x@|87AwcMXD@pd5dXJ?d+aq3*TI%}vMP&P32p$_G@zqDfiX*!L3D;=RbhYShC@>l<8;?9RqRF~-U`(dR_kRO_VZxN(a%htpoQ&t_)Q2X- +b$H0xuMhNQP4z=RqX}i{Qz;I4X{c}JGe3x+=R-9-8m2KB+W{In@5?&GhvUdJf5{v=@zXWY+ +_JUh=F>T#z-$4)$=E_NUJA>uOc3PAI4Hitq=Z{s;T62*1= +h*3l?E%;5Z={N7IyKxG3+!@oZf%Ru>09P0;b-wA*B$=Ba> +)O;NCVX9C~ya=^^tBqx-aeR)Z@OVH130N9&t=nxiWPdm4xGrMz4o+Qnl0L6Lx3k$sUhk2`I@FSu*)ZDE{A8%45!_`e +5gNo8ck_DiU!kUK>hJ7dZsI}?N*x>8>X3+YpI2z$mC4YQE7fir9@!tnljje)-iOXrI(0&Lzk% +2T)uy6MXtJD3w}*!io`lZ-Fc$fx$z@SDrb_21r(x~cmhPzG({xE=c3?_Rtl6TPd9_k9L-JT=tR0hZv~ +6r|b5?5D5J?V1XlMYxQ}=;b!r$Ee%GSX`pMu`;wgY09jHQn74S#fV+a%tCbWIA~u*b~cq6FtkbCP-oy +Mgxj03Zr|!x+(usd8LYstW|f(zns#>ukB8aQZ^u!Z;46izde#Hw@LySRav2Z4dMWp4C3f3 +GQ$37JX*Xo?1!VpbdV%8oW&H(51h62on0cLJJb44`l +)9p)$sHsc)QbLMI_|??3f=_jkQN?|Vec1eR-u2Ghs?xJ25mR$)y^LAlB{!aDX_YgVWq{c9f4q*7k?Yt +Iq93H$lZd&t3dk$UYsMy(XtMruB$MLSQ@cqV(#F$FNExoc+nkT6(A&++3sM~HP)JFKD;J?F|_qSIuOP +G*mTajVtJlyGiu-ZSi6CXRK1ZGqV+C`~1nPMUTctKwj9j;pYKI`5#mw2KH%3Ag81I1VbLVE5%h-JM^# +e#m_$*X8(lxy5SqFz&4XqIV$g>r;`3o~Bxq{IO8WzOtHbaM#^)d~*##3B9hw`fBBaV~ +Iqm7U#=CdIX;&+95uLZh*z0%!bXMB3vmH5j~G8w1lcvOFiK=`KvkO&7bmF=DQM*7HOp9?9O)3F^N;>x`!BA2@8-Sj`FD5Q=a2V)y?^uK_4d)jt +Gn&~?e?dK|9$`Z^^5rZ`|p2zbN}+iyNAc^7Ma{bzUY-oAYC!`=Ti?ES-=xAE)a4-b$25(95vJUq_7zkIxV@$T-`_RY_Z_|K1e@yJ*|{2Y(^?%np +QkAA(S^Bgh6zr6f|?aAxc+cU0xyFI&md-wSFyI1o~K6-fb?(zPcALBKD_4e1ljlXQ$pI$uvZTsT>^geUu~b>y^Wb}f4qBh_ZV;U#gE^-zJD1ve +|rD&?#bENz4e+UtLwZ&UKzIgNQ=a_g5@TZTTee~~f$CD4Ae){zJUzq +(TPoIDG@t0q2pM3sod$N7;tL?iNf4_@uetCBv61BYu75U-k|F1oK{qW|mzHi*}j|KhRcK_}6=HcD#_S55i$ +WBP#KkaUe@{j$!4WWPe!R^*kT=e42-(JTSd>OZYa{p}%{mJWxhsWFP!-u!;xb#m?;@&-{w7;Lqp0@3) +FXJ!&)qmT^pZ($g(Wtz469a$z)kn|&j7I$Dk9TighFHG%=Jnmwv=AV~cQ +Re{uiQi#Pv03{WV^i^rJ$7je}C?-qCd_~zyP>$vcLeE06%55Ilyr=NcM;O^CrAH4hNw#7?H|;fj}(8(?YREb`e^*ArF{LV*ZHTk=eqth&N3=D?scp`oxP3X)<1o9_hS3QCF)G>8S +B6O{u=8yze?|~fBt&=ef~hB^eko=ul&#Vy@?xRHoy5*t9yDs{`KqM?;oVSO%40`hcJ|{|JCDOe1m%A_ +TP{-=Ja}dcoX{j_8nUG{f{rd+rA5Hr)3E{^z0AMZf@L{ub=Dtw`Sj~QJpJAi^y3;m)bZKZ&p-eAlV>0Q&#ym!_J@z +3#Rb2*37eX3)7bah)b`^xCf;sSE$KF__PC8t&-gJi|2l4aNqcopWV9&NcA4-^4_C?aeoi%SMmYuAN8c^Ok4FyT@wf(K2(2TSt$J!ve5) +@wb$nk27m?;ISV!@h1H??=41ZJDU>sZ^!Vmcw*w#lNU%ar&s`2o4qW~INio8GN~gzJzO3J&iSVE?zx^ +Zu`@O$estbFcKc*ykJ|VV3wZ1<4lrD;`nj2TIS9ic!W*EOGpn%iz%!nL81Um;|q#_A|5FF_R +urm1^DjTV?ckpIR9%Ha<4+WRutCjTW1idi;GqV-l`xign6+yk|(3ugD}?2+7$I;rJlt6^~HTdi)v}H= +e_b{xajf-)v`$8Sm)=#3VweVx!K&LXUXGjHJbT#%9H{JF`f6tPZmaV{+$R(R3Qy63#a?L3Z~c%3k87`(UvwLmvg(;TtTqpgP|}nS!M1-IZ?!rb# +7y!V)4>A`x7jk-3o6|hDNGBdv|u&jVFS@h=#a}eLf&iHmmCv+$E9&YkIfJJg +Mu8$XN(jNV`2y+1~j4$Y|6=A#NXoLjQ9_BP^;0{_~F2DZM`Yls;oxE{ju$d`#ZPBBSHiam&C0H_7s6Y +7>|Qp#kw3wzUq_nHE@Z|J7S9Q5;0k2vBs^jY}mPw?RdmsDSS5XzB>rk1!uO0b<1i +ls@mDcm_)o&{Ce;zjyWol63+>FV9P?+Qx8cAIrPoka~_uR8u-XJF)>9LjX;@dykczcKq?Mig=;mX1I@ +v_?=Gcgb89RfS`uC<9&xOJ`-mAA|BlWSOhW8YMP|biRbIeXWTcS8csBp)E*_SssvrfQ6C$)Pl`dgeS! +4ITV+muU8jr>6%cBaHd!&4GB__usRz{9y3w5;$_{L&c5}*! +!VX1(kzP3y<)p!W_>KF2+I+Mp5GFFqlRw9qQnhHn7A;9UW@l+w|J>oarFQ@iFt1-Y-n@AkH$vrYhVJF +BfIR3j^^S#VzZFB;%>zUC&E0iAKCpy@WOcVDc;NYA$(S-Ze0UMz!GDk0yua~LcAeD63+?IQp0&fC`iU +`$GAt|}M?3nNA-xt;Fs&` +iV*64|8#?^*HY`*2J@NNeB-SlGglAdCxJ7RwfIhk|94X=?-x8Ml5EM4h;^p?N(PASGNS8=nZ&O1)G%6 +R<=O5Q2h&5mH0|I~YAS^uW$V=z&zR_{IQ;P%kRTNsQ=HsQ)J+PFVv-z!I0aJwz>GFhV0@J%}-@L3WSb +iu>6&9Bd!hkA!~;s}n&T62-KSZaA_uFSIFCTD6Fvu@qo*^@Z$(9joRCQO +rV>y;oij4Efg`H_QPMq8r*m(WR@JGRZ5HaB(_kq*S1~wS)<(guPB48n;*iNlIAxX`oB*- +i#KVlUEiNUSJgFHK4B?dOmVdJaXT(~4eat#~-OIE>8W>U~=G(XJ5j;(1tf{>LhW1+)zp2Xp}z*usU7! +sqw-wdVJ<`n{#d=LZ;=Gia~n0$VutWi#8J?Kl=&Ukl(KD&6@lmiPv0GE}XYv2f2D!LWBrEx$$3@LO9L +?g8th!C-bEX5QFoOfmJ#||1qMJgA=p#oT^j55?>jMp$x +%{vsqYv4hu(3~f>iadZ&FocY7h$*&;QEuuh7MI0mDH8^#5F|%%wG%Vrq{=e%-tL|&y*yykoL}#63pksguV=I9Oi7TPC0WtOqlTa&*Pb!%Rz>w +1Tlod!m;>hEhZPii|lF{q1f`9P$70ZrWijuJI +j*KSU91GI9QDf%VQc%#I+eioV%~rN%7c_^BS0drO$+36(Gc#@d8Re_Yy05vRg!@MHIGh?c#Utce)l;9*0Li2KJ!WJ3Lr6 +c%4a!i*DuCGnGXEIB?RU@6n~sq+Vae=KQp+3pXf=?)Df46^dXQ%tBp3;X%NHD+rMR%Bt9klbERZFLo8_%PjtB0A{orKk}&`ElSUc2y +3_3QiRc9^N6X)?u2Acy)+@~-q2=3PQ$}uV5S>FNKAt#NJrMw4s7`Xo5Fg;=G7_qC@4A10mL=ui3jSZW +3rSdL9R*R{1q>KvQQ^`NwRlbMks>Jh&HQsl6a7{ZzK&!N~GbsHd9&95ToZ}DCp~v@p+xu2te5})*6Qk +y~#eh#)`#{#1CHp(1U1V+x(!gTI`#t6M!#n75o76>cGVNzbr)rEL~p|;d=~sDj%50go1zsnUje*Zl_` +FfhkoZG9L(KdkOLWmE43$iaZZXNOG0LO9s#1Sy{rW{nZ$YQTEASSgwFa(&*p3Go%VcJs>5?m$nfgc4Gn*&G081XUIg!nvx-)u?f2aihHSp`km{OvH&u>@-nI29AIwnctri( +IyW23vV25@rd3u!?pT_#%X)xat#f(7$Tp11JhFl74;qjLqK$p(OORM3N%F_N;oMyM}tgaA +E~7Nl?J&GB@*5G3Ku6<3@FEJzFPwt^l+4B^W%FoquM7?~J}az?&T8dZf7NIIOmuqLjl)FsC!0ZS5W(n +*5hl8a|%w^`pfhPuuwm=ZO$8wlG*x@O#{D$K}Kz=wT9j1H8$fy;uB#8N1I2eb2EtmnbHi +DBTWtc{FSRS3IQZ!#V*9wlIn7P+3fGhAO!JJSQe8mo2+ga;Zh*sGI}4!KEkh +<@II(kldp1G9rXX|FuWFfhI9XPv^fhnb0ipmc*p$y5*sj7WpK{+dcuPQ +NTgXY)eKsvC_t&G_6hR}4HpW2nJ|k~N2ZSvDh$ZpI{v)ivXR&#OL_FiAXZ_$y29dp7-`nFA)Fs +T(3eq?~Bz+)xXGY1)dIXC&!WOgbB)A%;kA9+vkUI5c8LJ)%3Ha)1FjsV~T89~M}-V|nRtZxOJR`9Spg +G(pf0Q)v*0wT^PI0HO!Yy)ejDn-h=4GB%UXwZ;*QhuLf_r642r9QY%#&@mHlAUMHGlc&`(opC3ucRoH +F9Sm`a6}SebB$O?T5$sipBxyGsLsg0Gt8o@uAMS=NJ6xl^}V%i^I$>K|aqLanVn3)KYN)id_8r +Xp45Zkmub?^f%Gn516WbMLJ5{9BQ19Y1xu9mrt7MwcQVWQ6IOv4>ivL+K09h!L}=@>5f1oW)ni-0Q|( +z|KNs%87G=^&+G#zlqgYx8mhEakLLXN(GJ)EKj=+$6uuvrykCq_9gb@{xo|7;$wcv{vW@P~?!$4j{bb$Y6lD%v>o6R!~P9~a5jxz`gf}e;+Dpi(Vr

    A(A8wg4i+N54F+|^%q|YJy0-J&rCe$w!8Pa)K +w?e>zTaPJ9YQVJ-4|Zufr$M%RbTWwMT}#FwdelRF5MM%q4^3qbOi8FuIY#iwVW!VDJ*`#?$!7&PtioA ++z$;~073}JvWj+a)FY6X*+Ra9lsDRpJ;eB%X4OQ7rmlwchw|vabFi5s)bckXT+JZA>^W**U(lu)~c|| +ne+yW{?6^AS)4Wu-!p?O5#4OpP+X%4Z4!n%`BnugZ36qSIbA8rcaw&sv^0+~xxc?w>3!<=+#VwqxWCO +C*(g;^9#%n8P>Xyj|g1T5J+k67oxzfsSt$w0&xFc&YbqB{sSnS~G#h@q4NOR^NlW!*w8LqQ~I00ZvCP +ubkyU@JNs1;$169tK!sjU=d=3hsR1B~){8Vfnb00)*vktb(w)x=D0bte#fU*vkDrhhGn)9{*PO!D{1p +&Lrt$1uTt#MVvJc7xy3kQL9LRXWPQ6f +cX&7VFmKj1}1^2rh76cFFU#6qJN4i0VN|UkdqTH?l;QI>x`6@inPk^8fJCg!OT;218LtOU`RxI4mQZY +M>U`F%^8~j>*s2b#HtC@~MF9T8NV@M}ZynS-4oFf(ZB`+SJF0_Mgd3K|V>oS?Da_i*+lahfTza_!h}8 +GVE~kHO<9aa(qw{Qt;E{1caAn8#_wh4c7@cFt4nll^4?38CU@}O`r@{dwR!G>5$Z$oF`F=-Bx%k8Z$* +I$EjN>C3Heu0o3@xJL*eNB=|&K!*pD0e1Nc^pz&ykcv+U5Q{?S@cZ5{CpDhERh;VHxQc+DVKtnnPy_} +1z7;$Ep&0Qb_@0p42k{nNBb^_Xtc~92fPD&_P>u?@KkkZO@d_^gTV|m$%0}czJ({{{yF*dR2Oqu5_G+ +G_AK#b4Co@(te#Tm9iHl|6E1@oG5hr`lMg?Wi&v2NmS2oIU19K3@tl%!*W#!!k2VBOF%lN8osrQjMk0 ++vG5m9dT~8LlG}xw)VrRo!xSCmMwjVr3~|6qpHxszj_TduhB*qzhtQC_<1VW +&EG6)V*r?*FMr2<=)TbKDA{te)4-+t+JKb}{`dqFF(rt$-7k2>%Sqv5}qj=c_pr|Y3RR`lKfeCC)qs~SP1KV#ik-rrPWh0PtfTQVdOC58tOH01T1M1D&%4j1)9`0p@ +yOjaLeUws%oqUWDvlOlBU@ti{s*SWu}mBGi0;^H?8pQ9W#^h2QdkfQcVIX-39q}L`eVARda%+-E;_6= +#`E^x=l!WEfaYqGhkwwbag<7qA5MZ29~%%x~-ID5= +Fg4P*`B@X5s8UIV?tzpjfaSk{+4Fd!|X*UvVge`t`il7}71=OOggIBkTh|24P9WI;F{`6{zu1^v!f<LCW-t%T(egF$q2_7WunSU*u-H;F=lF*#QLL-05?ujU@oQ +p@_Al)X&PUSS{pz3rQ#-f|VB}J+~;B2gx&*6QBn+Ii_>_2P!5UZ;$-5}j|86ibG3$M?L9bC|Az%nM90 +syv4&s2J?8=BBsNOmcRFjkV@B;6*Vc-@OJz#wgG!bud80NT0$CyLlbf)2TMSV7uYfX55TXQ$LM2hRAs +Q|rjGbD0d%OztV}Czo9SO1DTjl)K&wgOgAD0?

    Xcbr*q}v1sCuLnQ|7wa1hCl)nuv4XiS*mR?YBO+ +OGqJKTo~6YPo_MT*DG3oWqum%wpNfwd28FDh+!eU_VP=_8m`w|fpw8I}0tih($dXaAdd-|DBOK!CfGhMXB}U)6lwLItFj7k1nCvmZ)g={@Zgs{$ +2wljZj*SGulhEdh=k9>Ju;Zw7kjy6sQAk`KF0PW7mJynyTheF&taH||fdNSxSecEwV-acqyDo>YrWMG +pb@F}CCR^6pwZ;*!RB1<4eiGidLmcVAw_VoEX?O?(39yr}DTP8~rCBSq6ur*aA7IJ0u+0YPHXnAt$o? +RTOL~3*246Od>fsTEHSwOONS{bDk~XW(RT9loX-Kzm(}5lUuN`JX#46nYlE0xeI#X-25*n@LPAi?`Vz-4;@+eoS()0X2e`ggSuT4Oplf7@H@n7sdPu^K3a=MbJrH +3Igdi0foiR;3)z1CwmP%Yxc58k)GKIj?0V6Tn=J1q5>7Ff5$nvfu5N&^Y^T_~%R?koV>K>G{)a(Xn`OE?pk97DQI#ZC@ZL5dKPO +0xlLhi++meIN0;`8W30V5ym%Szu*8ixJWw-4>F8LLDU=6y`ZGQUI!j7>8m9xEZghmtypFdzzzG8veQ=P@Xf>({V?M@Sy`&VGIq{YYif{gvki{$YC7DO4%MWK!Omo +l4qjBXQ%>*J4IxDqNw4HjT>`dEA?1>aX23$2vo^(+75R3=0MslX0w-8uIh*4WGE%cBr=2!rk+!!A+3y +SHZIEs&T`1koT5Fk2&U`gZLVbsVhc@H$q~qvTq1gZwlZe=ZD6REc3c7@jX>9dHSh8YIg)e{+Al0<29hQWGGEFt_@Iw#zzp5$rhdRjCS9PpGx-I6zyEe`0Jcy{dR+Ypo@t&Df%fC1T +xfH}n8ExF`4<7z&;cgAmZ8dI|>vJluHuETRGCcscC8LocRgq75pQpwSstEd$hV8bt3AI#y6v_wH-$_C-o+t_Hj@-G=s*PX)vT*)o-x1(uGP9-yKSg{xqnEviO@Qe2^Mo#G_Tvf*fl~n99 +S2!a3O5e{eZmJK4Ro}wr;MHP=j<^tEEIe%OP`xeYavpdTS!2*(HzP@&rQ2{p6vYGTosFHJ5BDh@@L0? +aoX}mmGLH9sUMLD6&6YZP!Pzp%WqkW}yyx>Je?wS?&2v(yh#Y${1xfEfK!}EhcMKS!mBg2Bzk5D^&pr +Kw}z9Q#2y-1)4TUw}qbLei&a1ShHn^@#tir0})9BuOw!y2St-?V6>CG+8&#YPy4mTkZvhW>GSj0i7nyP#-N43X%S%6PASF{WF$ONfrOByog7c6NDf%#u|;f8{Ok7Q2a6llV?~j&K +G>St+7-m*z>jwXf;W2o1@3Fq?+hCgeKn6BuBNq=&INaauAR6CShCwK~AkJV>`y%%EF!2NSYi8W)^6kf +4yhRQF9*7FlG5c#)-24!34FGcQ?E0qGVpJ?MdyDeVd|6{2OqoR0TewWmNGcG1&roDUW0)Cf@8+-=-lD +h=tjPy@<1^GsPL%`M;YCC$5#3IIw9Np)^07L#AoY)BC=HHYZez@0qrKm`oKfaiCk5k=61iP-)y$xpJA +d8{BEAHW+glvN7gZ2iYlK)S6GAY~RD#QgHh)HfZx(nmecUde7~EamcWiiFT8BE+w9US<>0ZP%Y1a(&b +YLOF11D2MSxq)JZ-k2S&9&2~~Sfda2L4VmgP|5w*$gLGS@#o=GGysK%1P$3(f?Lw6<3U|6ff7>+0Ng^ +(B0cN7u0&`w?1*F?dbe8F%tkB}RfQvJpC@C2ZGcHwzFzar=BCes%Y{DX^`SGg#x6`&}BmnrNU@LMGvq +T%=(A8=dN@gi}${v8-1i?tgsKsu5oqJz~9@1?hAdflu1E_XB0tuKJZipnBLBqD;B{^==5}^8ahZ9Eje +i?d5w;Av*?P42ZhGJj93ba*sI?=mL*HH3$R}p1a@cTdPGIJ!&`XQ;yAy3A^~0P&$}UvKnnmByOnex@8HO?&>o1$8Je +TTSFQRv$9i8OJMJLCY4enP>}B7Hgi2#PaBDZ%q(?uFX?OG2v{<4UKegWtgT>xjaqQ=P#Ieg^nLWPNfo +aiTWvbgl>I8Qh5)IZGS1UO;34fr7T@hsCVI|bnb8|qt%}@-o=jzCk!?za5!5v<3hiOshFD?S4Q~Eq9# +R9(KPhutnd9`gEkZu#Hd7%&$dxQrgp9MatlDeg5Qu!^Dxi138ffE|w!yZJUo7GUVmIBgk$&&0*5E6tu +nRCO9X36b?uC~|OHRTiuIl(qLM5lN2R(qZHYhX%3ru(x41_gt#(>YZob0$VAHvX~Y@Mkvb{C2efu2hM +dvq7;~U>R%R1L~g#ji{;w&tt~%=&*HM47F#obUCyt7zk^f=9x_{YBLe}YG6Ge-4-gC8)hnbB#wMLIHw +6nq!5E76FayJ8I&?h8#3$?xzy~XxiZ&`DG6ouh_thtG-!l~Yq}tgx`lOX_V;O1EYuY(0eVeVYwPz|@$ +muaHd`7sn?c;mkWYyVJZ;DF#147mNvU#;9G!iuOv*e@+qEFbBO9;~r`h!-jiSw@K>`!X1V!dxt;=FT9*}PHjByPc4W5+>^EVZwYQ!)JGz}qw+i56g>LO>uc3t!1#f?26-4+itIW2Ra +z8$1WAsI?a-C9KPUw~4~j`B^i&DyRO*^c_b^Rg9?ZmVQig2E(A#io+OCNAB#ZQ7@+fSnYD@>mu4DyMa +FDiy`!O4e~H2c+BHB@3K3g;5ixJxlCDjN5VgjZtSI1Y8)1w~-ZUp#CUA4fa?`B+1d$ +Ax7NViFFMmnLbN}i}d(YL|@E@HihOs~_|_GBQ;u0*!eVyZic&sp`n1JZ4UK3r_x>!!yZ;L+VJ>`$?Hz +*_)D4hh(B#=vDWtk4SU7~UiAxHhjCupmalzZ6M(H>9vEOR}iUOh1Us9!fb``6TSkP@7;ee*qmom2sBn$WQ)F92(HjnHK)Al-He8EkKdX_1=!O6JX|8t{IS7~MT{MJUTNb>u@ +DfEl^l*Le9nNVl6O>_~Jt?Zm2PV%V?h@*oQub$dFg;=w2AR;>>kD8$qCB`bkEAl-H_*ea$iv&n-3Tw{ +z&8B%=k{3Or48<*&_3`k9Gx;YWq|0>2Fbp|Xfkvx76>=HCZKC|J&@VFEI#g+j(&2u(zNYqw)pN+rhz` +0^KNViFyvBg7t0ODB{zs@p&K;b8hw|l&q^ECCMcE&r-24h>0e_3PCVG%B{t4}nTU(=ikiL#*!PFpi!1 +3Ur81{pq`auM_(c1^CsM-E801=u|c$$De}5lZ$fg2`RN*h<8!^M`I#FaeH~k!tr|z0cY_NVk-|F(2Y- +06wG@Gzj#r1W<+qxGWQ(JY6VCegexb6g4{FzO*lx_keV}wRz$pLZ+%{?dMzQ`N2yizT0}P)3^5K(QY` +oYE4{L)ne!!kZw18Uh-Hwk;-|F%jqMG928Qd;*=KaYm>VPX>_}*$XfIT{aRxWjzf;MX+$_R8|R${7c` +5V8Vo-mZ?;q-Kh$O%$-VYvXXR*lX-K!6(8nHms2Smhq-+`J;IhN&Eid3P6Q<1Bo;&Ml^ME_`-)M8$3Q +4y*Nje%ga+6|BjNC7Gr2-L@-Qy7(Sj??Y1B#~;JgYvj@?)(rq}x91Kb7ff`NsnTSv!3M34IxspNY+Pi +w77cP9`cg9nP~chodi_2kAD+kr2Frc^0Qm@bkVqVH}%3iy_GL4)1X(wq;UUgCDhVZr|Bd?c;N +@33D$M!wJuaaJb)tN6D%lM|0I@iaW$4O%kRDN_fe+djnO4*9kw4TKLZ!RjhS+n0T +g=@46UMf8$x32N!dVvj|lkpK^(A=T)%?8-_n;@rf%+1FN^G5Lq@<84daAl>%4Gu5L!WGiT0X+R?p5+m +!L=FsFQA=|^=d15zwj%P5iKYd*8AJT0_plY6HQ+eJ4?v}vnw3^LA+i84S-Iw4Y1`1p!V56i^3d>_bHw +UEKL~@g0A?0Ro5b;8{)C)wF#B_AVt`_Ka_O*GoF%hi;8BDrdC&wI+ZnJR-*hHUAOT2(gci6;z$eh)50 +kc@Ar~SH+(O1JRs=>59;3}7`fOMPfjFV3_<{@vMDoF@F&w?Y=&#so8G9CkoO`FqpU?K!MB-?%s42Q*X +E#>rtlS`gYK%|{P>oXWxl@;O96HTEX*7NC}A&JsA5<=&C#mA6tH+hm0Iz(>mbpUEj2Cwk0I>p%9=G?J +PGQDQiia0(?;bPs^X=w+f+YK%46bd@{b@w0~qKRydJo|+VUhUnGT?ndgdn#Z+F7kv9$rMW}h9x0Fn(U +FJyV19%1C9gMAW3Q(J{$0^_121^F-#u#2R>M*?c~rrj|d~fq0C%lqqKFD +$Kgr0&3BS@Ol45=XVE`t2_=M80|5^o;mtfH#vVj5@@(kg2Wu%#IV_4Mg)wMpMGj1{54w2hg9n=u3UZx +ALC?VyZY0qy9&5T`gr!OCn2W`n?DP|n@C+H=oe;W+NQzAkPn#x^VSh3Npd{cr)){&Sq}%P(1^c +qIT43#MXE@eVZv*|rwr0CvZ4iR8U9xo{d)L8ATDu0OOUR1wuB6XaT8qvG0=&iH2abe5cq}*N0uq_-N! +K2cIy|Fn5ke10w^gNUHa>gykR=c(!5*yyQdqdF+Al<_PnqgR9xo|iD2LroS6l({=gnDXWAv* +iV!zl0VyU?c?Z?2^DF|z0RKecltyTN^MxAGL%Pj}XXTiLLBaP_6aiyT&pg%U;sG&qE#Sg9sie%4vL +%GW+1llCt-!lOy6u*LZO|Ti3oZkSuk%old9(q{xp|Z!>o!9VRK;dV>ch}g2zgUE4h9+}jA4tOc`{A4->^@b+U?h%%|!n{t7>5y)VM|k7BiU%5im-f@H8XA8{i7 +MszekuXUf1&UcqOFLWb7fBcbq!3wVlz)Oro+bL!n^*_vj_+|Gql#q8s|PAhOr{OM17LS_SoQjF3MDgb +Xz^(*CSZ0FhL#6Ha!BRb#S6emr*4|%Gt(aBt?1@uv2YwK7=lHX&$88y4&06vAkv!p7$&IASv6UAw3j)nGAM-z#5}L;ZdPJ+|^Yhv>tw1G9(s +!o=?y`iXJY6r-9B7$S09wh`bwMQNlr25`i@JZLW+LUjx(De9rsX^5Ub}a(KbipZe*c&`O$pYh8quq@% +cHnjX7nRHUVOkZ!AJn}>AE3d^Q*!#qH!!E&dkFM+EnxSmNmrQD;wH5f!(i@3oV#xY~!UXN?B5z;aQ6@ +*NyVqHIY6%uz^4f~{vcJJ^5HH`4^mw^vA&)s4@nlttUH#U!59kNU$@yuiCtk`RRh(&Sa9zmH^_!Y)>N +w=P>?g200tsTZvw@nHVr`rB#n*n)AZPTWxKI{tKuY0KKZi~c{<6=ojvZDuSkpq#hB)?mLguZ$%#qQY* +A|C`X9&*w>M6`MSmO@W1yt;%q{4Ep&N8zCjBu821u9!0LSO*@bX5pvrlDUZIR!1S +{Lq)1!L!M0cS@34K!+61w44gd?0fFFG<2i}e!}!Pr*xiFyF2{iEx?6$$Q4z%^{WciFnPw-23>(`DEwxGlrpE6nb>|{(&=(%I!q#!W{DWGg +)GWI4Dx8h(!BIBLdyt;zG<$Lbr0N9mJ##8Rr|8-*eg$p3VJke2!-!G!H&kHXCC;0&0^(%9pn!vps^9; +h3l;8@jq3p%WidkNjSTlc&p1MxC0cp7s3#E$-W`e?u4x{LM_D8(5dO`T!MSVqS0@$?a5rW5J6tegc88 +s!!Jduj_n1(0mLH}YYPOD}xgJhqdx;tbB{%KqS_(+Fl&ZTGU6SuyRWQGlrzx&LM7YJFzjdR)nVVq(hR +ya^kiL=^$D5Xyn=}K2Q!&!o>x{9zI!PmKzx>04E9YY!liHbG^afkTtSf3I4s?=@;orz^H>XyK!=+TQ^A-l0i0*8TXnM24{pa0ixVy1 +k89uvSO(c{kzqqNQ|+C#eBDF@Y+EHeoad*9%nXvSQs&pRcpYiq>DMeQTLI}do5Xosg6AG(sfTgdm?FLE1+=fATTcW8nlIq3E{ +g0^7K*|L7}*d*r8GJAe9gG9dBUBbDRAD$j1h@3;W<~Rki(-k)4YUmD7I`6t0WWMA;39LM!g2MB(!_}A +kkTK5SQFX;PX7)t4fdsqZi2mNR`MB9yL7N3d>yc0`#cu#*97Z1LsjZ{nF8kKUnv}U4Z0SC9ri}&kq!mmlg}$-dP$x*J#j>6AGMkWYi-)?N>c&8LL=}e +;M4@rs>NhHx8jH5O-B=e%NoGPK+pE?@-GZe+0?h9{n-g;11R1rH2M?=eD9E;~cENWIt>=g8Zmcw{qep +?MshQe0Bd^$o2~Uo}U{uExHTJQ-G21v0%jzXa)I0^up3H0D%H~z&6Hauvm$BP8m@HQS2#VhfKd_rpn1 +#LcTR|$T?LkyRFTV`zIV@VJ=4(Ur^cY(n2I5`eDvw<|N4|lzAL3?|@~RZFAr;6bh{fCNo^%WJJUpmca +`EiT1aGCtQ6=pU4`vtTJXrvxS1oBSv_EV%y$0ssH~_&xqCCuuhazk~tEX~3r!a#aXoY9hNpRQ7nqSW1 +7Y{940qM48c?O2{r)^eje$xK(*Ib!Uz0)ZNe9L5 +UCLa>2>#Hk;J>}kQ_u-It9toy;Q)L=HXOArNJCQ1qXDZLD|0&%O>DPj+UjCz*3y%PreyeXO0wOQl;gD +6#kXgzJLX}Ylh!mRQVf`(84Q_)SGQc(=i6X1EqPohF^*2mqsz!IJ7?6>R$Ajg79C&0Mp2&7V7Fnk;~8saoXGp@ugqsQ3(_r5(A`y2UEOrMefdNE!P96;<54u2#slh) +XVJfOrMBH_Q?g~6S-Fu~D2hzRBpnszhg3=g#%?G@ENC4|M_PJ`%13@>WlFYWa`G2AP(NkwQc*HOMNw& +)3NMXz_-RXHcRxT(b7S}M)UxoL(YoF-{`!=#4Ody?sja6@bW6R@pZ^BM#qxkcrD)OAI$E$}`^wqAlgM +aq<+}35cHHehh!9PL{Z4bKHdYpNM^Na=8=BXCYxJRiSfk|H3R^cW<(8S8$^9FIJy2~bN`)rNnRI)47~ +x6i>8nc5^dScBM-OQ`Ck}1GrnX-0F!cp18-1h9$un`g;gwW7{e +&_B3u|&Rk`<1Ojggynm<7)@TE*VQ5;T!#!X4wF5A-X1oY1kt&xM;z-(~P9v(Qe^AzJL&ozG01M%~Uzg +E7b)9YUz2De48y33a2mh94;2&G7YqEr&Z=*kN1YX}FyWUz?70TK2`<5BX`s2`4!=^&1 +2I9pIi^Kg-jqUAVT(0sX=&*DKq6f++!Zp^%7^2y>Hl!bwl8G9j>+qeFL@j3`p8EwTYW6UW_Y!nInMau +fOW~hwb?D_@dZ0G~YoV4*>db1L{gYElex?nlaw~M7ugD(BmLy^$m6-E#PK9o7Ahsqe+rM_{#DV>DC-h +b-#?(cek-uH->F(TJKG?+gA&m+=qwQ_3;3W{~Q71psYty!ag%&++qO)BMOzxEs<8?&G9vIh*Vi_{zEv +1)}#w^H*F7VR=|lbP%@M+j(6d)Msr0Werb$@Am8M2K@#JDj4EJm-p^s#o!3HJLpQ#;sPWgme4oUSQ|4 +Zd?l-3#>*?tChsfS*<#bRep#!A6Mb}bly>QX;%@P0JrB_Bn~F5AnwbBwmUyd{eXS%#7Zr<15k*>5T_+z7%ePuP>;H|sl`Q{ph5_(gK%~g0Jw$C|hua`N;9COUM&Icd-{ +(t4ur+<3(Rr&JSXHUO+@w9yU<=3D6bNO%H`@zKr<>i~}^7Z}AUvA!B-ImYpUSF4+hw|sU|G2rmy~^)D +{q)P*n^#wlclYJh?alSu$MP!g`Q+l)k3M^Q^LUd_et!Me>)X4ZukXv+-`pNBsLoUEMR*&+qc6H;?7R&ps;kJdX_VyKn!bJh{Cs&w1 +}dd4BzHegD_%*K;MG-MxLhzxm;pEc3&MkN$7|Qp%sN?*FZPb94Kb`Btf$G>G_e=P6rekrf6-j@68*EbJy_-w>uxqAEh4|n(Z{7-kUZ+?8opMH7!I +?G|!kJtA2%+sllQ_m+Qs_VS;;{`O^g^3`|cU!OdG{^YBd-~BP~%}(dloZlSj=BJ +-yg0D+fb$|8t@m(gK0sj2-`Dg!>Pdxeb*_Y2=e#h*;c=qzEr!QWVFTQ?Wo|JE%Jb(G@vv0qA^1OWW?e +lNGe)05^QeIqN|GLF&=I?Fte{S)Q8S^GA4^+w19p`5Oz&;{N^X``5p|Rxb1DyPwLN|Je4eGw_;G%f}!8V_81gPk!=;yik`m-oJ +3R_b=3*7uqt8Utc)hzp!k1Aw$j=>L<(c>kG~4JbQf&bo^?~c*=9Oxcq~Ut9|{$hxOz4KYo~ij?IVY)>pp$y-Oqu<-+k)U4>&?w{|{C*=XUotp8D{JKmGK}t2gCMa7_$}q +doup`NhRNf$v{__sz4}ShA$Fk?wzVK_ktoxF`jV*84&iuMo|5;< +Fd%Ns4e`{@7FE27e25cFjt?O}F#-5?~yt|zFaIag--^Ow|mTkGLXFk%-ZOcDnzwGO=WPE-)GfF>3%YZ +rh%W`bzvG`N*lb9SN6P8k3EZ8^3 +b~PgO6loN9~s-)48m94`XGnOGd40<~0tU*+$0SGMVgQ-?Flrl^sV`+m0>MTr`GPCGNX&gSl#O{U!&|BQUN^1RF@Tij~451WV$3@j$IS&lqzEZK`ahnJ +IK)L6*MIlPJz?wd3L58K8H`TRdr?O7M{Kz_SNw5}Y1K2Q5KF5Bsr+qJQA509o3mBfq>NfQ^o@-?jak@X+=DrsSQ?XK-HjmFd2-i{D{<^ +#ubFVwjmL^b;*NN1oUn6@8L)%-F&n|*#BW*30&?wmB2OMmc3|zAb4KNO28I_;1m;-D$>NU=IAl&S8;y +Vk=%d>q0LH!+4rO%QYyrU8nvLky*_wm%*mHto!OOM-U#^_bwq!?o4l^j#iJRC2_FH*j?_hFVr~~Rh6XoP@Rp4h#S`&l^Wfs{=-jk +l%Lw7NGQvOO7n&l)c`7xz3aFN9<%H?(;q(W`&CM9SGf44uCTwy2Oi#`ydiK9KT}89477`NCQdO*9EF` +LSBHYpa#AS?I_@5+2c9Y^M^n+!bxv9#Nl}ZtCfe~e~mSbGgexOF4YjQx#VX2z)1%40#*ay#O~Am${Sr +-1F;E$i4rTA55f^S&KP!;^gv@W!xi>b3AKqfmGjOiFalHJ2(wT^2gX>+g6m0Hh_4j@4>ZMy0I8VVMXb +?iAV?A%@Zb)5ZXC~f$l5ps76ppO^A0dSPvYd`A3zScMlile=o1o%!FcOQT-)?&O#BcO57-eKi9=-P06 +ZWwW{S_i05*U)uU-U+i5eI>FE?;{K$}9-Fw2vu2SUVYvd~yV)v^*}*p>W^*btZymzz=%YYq7nfG8?O}xrdPQz~|Qo*n|ozxcd5+SNb2JMT<#l$$b +%>SaW+KPt-Q)DUzGJuTP)YCo0PQ*3waFM@3#{Ae2H8uic28wzAg;d?&BQ4zZ;*mXPDvNjO +*l3nlb*ogVCKU3A7ulYe06d@O&?cc#Ut_;Le68wo+-jp0g! +?W%hMwJwTN(7HDQz2W58fF?rNNv5_CJ}{HSWeg7r6Gz%H>>TjXF=s-#NPo)Bc9TcuN|0Jt)(z7&elI3 +Ni)cf3+SypycI5!hz*piQfpP&MaugwZq=$`+Vl@@O>+*@jWfV9q +%iGT?k-Y^6}>{5v0#V$?$AE2lK8Ogzle#02|2FfGXju6^KZu=ECDj+rA}uq)V3vd@NbFd(^QIRS?)XB)d +9FCx1_9mqnlWFUGcLPLit`_srED*<)o!HpBl5-W$2z3Jj;H3^q5t4G*uCp;$g6153$cVTQUmPIl*h}z-UnNlGKO>!+CCBc^qn&c4D78vFZ^MUll81cl0E2wUek8h+mmy({K33&L71p_fVLPsu@JQMXNkNYZ5;yW9mQ1u +iWK|WI(Biw+KuF%$+|xva`QrtUs*7?O(wcinG_KNS%oJ9ol`cjEBtUe)?lzO3jY>>J<|>DoCaxDSQXy +oTe1vspQ1v~4tecOMT4D`d)(Nhy#EbJZb0#Y$$K4OYC~UQ(A{BX9173EK%bg?FgPx89C_g)Hj4Z{bC0 ++nNdWQ7DKo2A6xN&bX_eJ+d+ysuh7D@bARdB$ox|mH9p$AH$Ff?gKJY}2;XIdZpaIo5qSR|1@5xW*_d +IzVs;B*XVgJ`3xY-&s}6CiD@gn+Z^o{}IuKw%5CX=XJuN8ndSU~6#lqQx9tRt_!z0SI^y&V`f?Yu9Ss +w0Yj(A)=@$FYeNmd?)0v^*j68rpAi_5mVYO-C`*n4zUY`C~=)Q0GrIoD`J2)Jn2|vr`gvNcb5|3Z-vY +whE1B+Ne^+2g59?%%%T1*hhmgFumf5I1H5AG_!f)T7^~zPaA=6sh4|5TIiyk*6a%bC>OG)=MjSSzn*f +-3^gIq?x0$IS*zN;WWXIJJo(j4|K1a@eN`gp%hv_+VP8Dph(5bDN>vaqPdmXwS89lZ%NU<9kSV4`iP~ +AoXzV>Efjk2rXo)UrsJmg8p9#0 +N}t_O;<$vuJE@y)ic>Jk|{b@$c1Stzs_I)AA#i{M#S;){wi}&gV^NxX6mF#_?l8H=~S{s`BV%6%2L}T +7ZNYxHxlVgZ+CPXm9mO>BX+1+qqoN9c3cXmQZxo0PbK(Op^PAlZ;{7kBV|#WW<%r!e^>JqOqcO%lU+2 +WH%0|uIg}1n%TZ&=Vi;4{^Xe+$Y-sQWC~2_JrkFZq7N`Q{i3CcsMG6k5PsfUY0K#Dog@=GUBo7xkYtJ +Jti=N-G*L@Q?5k9(LHa30C +26aQ?*Y`BZ%EVypFD&r$E5~J0;-L5O8wdyLt~>m92?I0F)s6iO3Gk&nWorK)NsNGi0`*0XB9#(LLTeO +>luqL${W0wyS|cw^X4iTODd$hPr`aTjEhg0(Y`_!!$@w@Y#4HrrJ*8c85qT4W& +*4)mmIJ!`6SDox~KSst#3=8lqBFq^Uo(WD0}!DcrNo(>15RC908Mu82fs-Pbx4&YLPFv&{S(m)m>XsN +0?n*uSv5+F{LF@)Fxmn~Kz_*>FjF#nY05=JT21ni7JiU7bPp!*(oI;o5r5V2=5A%oN5&=eHo%hpCoXp +ClukOM$*P;JvN#0upZ;>D>$!);EuEJd5pCJ0TJ!K$q?L(H@uhF|7-;@<~2Js@KQ$_}R+5ay1~(Iu}9! +|s{&S_KEP(ME)5iZZ>)!vRh_e`UZO;e=$qFZ!eklrU$hhH@GX&_IZbhVe95NhSG8T%%aBDN?gqOld_s +C%X<>lE*b5grjH^7`8V9f+7FSW)>*(L+%{zi3nAG!!b0+8h4CG +SgPyY4wW&{T=CcT~Q|*Pk;t-@QtESXvf)6fW_Cr-ewxy6~uhx2%`x;Jf)p +}OfcJRX@nqfix5RO$T?$o(tXQ-4fI-~wV&NkKFiPQK8p>Kn6R9V7eLS{WyJr5W*HotW_jdiv1vFe^cNRRRG@tUEpiq@4l5lD +xL~ky;B!hs@O*VBla;196C>rHO8hWgYr=>;9k=QzF|qxb1*}Yj;Cve6HB(b@YlK8X0yfo(A>%+t=@zk +@!G^-YYJsVfr?7JgjCmBk5HknBZNi|IMfIqHf}PzAG(j#m3MQP#AV5HRD!Olne_k}nz~XwUESEYAphf +YxH6c{_ILW;OMJsjCfDcrJWPVY=RfXPkZG4 +6SExjkeawwLO%>rQjo-%n?=S1V*>cH9DT?I!qlWHMiSH})30^|^4DD{%3ER+F|jodlAbI?Lm8_j1on8 +`F}jvpYdmh|KmZPI*n2r7@2$<}J~5z5KXWoJFbDm)HtN6@Y|g%dWj1KC7K09Mr* +>NaUk-L)f;E7ei0%a`ej^PmWrv$brTv8@kCfW?!8R#9be?jHSCIidv2(e^R1+}yxTsOE=F`=aVm^_X| +*CO2k9_~n2mQE|6SPrd7RRm9IHdCPL!Aa>IC`dN#RKbB!I!)52L3p|t-GXc(o(rLH)M}duGQ}W!$Ly< +BGlZrl$tAlQ(1(zTg(S*|)6@?kdgQp9k~{}Tv<$_w;{jC%W4CyL{We_>k*}+5Tack6q;B%V)m{Z0ntV +B)$>)?a4#@y#WW}m#jCO30my%`d{0-$bfNJjp?2C4T$`%iX>3~s37s~QvzVNqZZe&JJi0O8@*==VIVo +)Mcw@r&Kt4faB_sQfTiHp*hsDVU|?nEO%rTcEBp{hZS+hRS-pmf?ewhCW%OX1szrK{CdN+gByX0Mh?V +Qs643HpdYNhTP2;_6~H$a|e=AAYEbOsnNE4N_!ySa*ZSS4y$ff*;_}*nvvvtRW|^^t%n~*HkbPq)xd9 +-5T%_mG)y%;|Cm+m@NWh81{FN5ADuu?O4l~^T>JMRV^itl00w +OP05EKdpjuHl<$AhZ6c?JEkKM$9HInr=sxPaaXtPnZYK>$A$*N%M=z>(U@`xWT7OL4W6!hfEo2_AR4;p`})jJCs>Q1LUQ3tMfSo^c0zuPAu;~*)Z+BVG +IQ33{rx7vnc`BYYjqNW%~FIzQ?R>*4%-Eys>5^4&8xSMJoR{8DMq%S0I9@Zu8{}rETaj5PJF@*LQ*oz +Hx#!^F!Jvp1kNTdSF?SWG?F6gEdA?iLWKJ2yrr*4Ds44S`2Q}(i{xl_-kEJK)RG!Hkjt3r*UASzT3Wl +>&Y5jJt1R&Nz*bc5MUI@^Mv7_b~0K?tp5w+GG+$zl@}sm!9vts19#f?-dFX+?u*Gs~h=4Kj4_0!b&rK +rwr#cG5I)2w{~-ShQKuV4JB8KGca^!(t^rb@UhR+CZuiBgn_QO1L5nV!2`mB2qtiRd)T!|`c8-+4ZJN%J;UUe;sW`2qyMFEmNMCu)f%MLdJ8n?giuP2dKmSB;^Q$`0( +ha%sGHlb)(ru?2G`&Od$*YvTR~^MD0WbpI9Lm(ZM;&9X^XW6hgLOYC9}PNB#k)(rj5v;kffA6&@`p(cL9nRUiAvB)d?tB{Hqm?|WX8P*FeE+m@&OcytgomhQ|~R!HZu;r +Dk>rJ$PcTEYPT7vP_fRcceOY>s!mfKZruv2AYPNvIHQq88*9}x~>swf;lgX5|=tT4q~&$60uL)LzRRq)v?*OS&(HE>M)HXtA`zhEYzl4ldZ*8H#1hKbb((rs@((isoCW=i~q&?3qwdK!O^(&7QLYM@ZY%TCBL5lnYI=JK_`GkD^jw%@@rBwwiZZ#YXp2kn%swy!o{iqIT_S8^)d0U>N;1&azB=75RsHjp2R<;=#`h}BZR{V* +CDd#&)G;&@;-LuF&NwupGzOdRaywO`D(KgB>~5c+L4r^M80AuO}J!s@u&lmV?!uin2?_+2?&81Y*-Ldz~=Vy34x>5CpSkiE47}Ar#KrGFYg+BD-Z*fO8R@p-5h`(PDUJoBdnn +i9|m$4rSmv%fe;IzSu*iR%^#NC98VcrKt`DQW7SbojzW^<8#dxbe2<^vWGhMY-Ob@1r~+YOdl) +;WkcOeA?$WH$swkI6}I?Mo9Rg1P5*WeO$z8^TII!L88Mq#q(y@TCaB|fVq|UN)uGu-{s^NsGuh%tXhL +ACCNT8bHmVvIwQ9dccG8hzvnqTCuH0zUZno}u>!r9mkanPHJu(zDd3R)JjBJQPc~CTU3IK1 +4R@;dyIdGuMOCTg4*b29*l3W0q_a5s0mXh8-5nxmuc=E$jWW&OG1C=fE8gU?LXD3qw{I%>9E$aFx{}7~wFiY|HAQdkEnY-Kv$UJq +Ez3YkAW&@|fi=b3N>7_6get2Alu}hj$ByUH7xA$|eTYYXiNDQb@ +6_jqs}mYgODz6#MEeB+R#?id1;mMI#q4_5ynQ<5ZW2@2=j2g3x*mn&{YIK4qPq>|QG*?%TsjhRqIVhC +-aOvps$0471#YC8)G}ab|%^m2=(KeUf#Rnhx-hmB-jliY1f0U;!3QdWeNRi4rH +BipRyJIJFr{vnVF`A|W4Uf9 +k?pW4wy(VXyGMNaheh#DghYGCo^_bh`OniLSQb1u&;c9L)a2tn=e(%vNP-Vh*2aa*c@3$5B+Yw21w# +gx%z8b}GFc1)t}^XjsO4K#W8&kg~y(Q2U^j@LNqf|B2=T?(tps5fJHrVdwC)ND*-hF&*@h0Gn$Q~b(- +!yDG~YYqrQR?att8mR4RI}z!i(uhj<>8&8Im=Jxfo^QL26BQv74FZ6&0su`O{Mrz*s<=btt{h&gS73v +TmCYL`st8!A9pf~uR4%L+TN^2cc5_LuXJfj>TbZW>s^A3#Jimy+z5~nUyUl)TUy$Cen7Y})vDx=0{!) +UWlL2kGZu9;x=j~PdMU+LNo4S^F9e^(zi?|h05rXP0w)zbRQ|}=XdiU1ip2JiH3%@HsahXo()mum2Lg +JyYi#D}k$k-$xyS=L{uX>RTC_Dr}T06RV5H%2Zq~aFM=kaAB`t-J!rFdlod-|x+n;Gt2E>m~3QPf5jx +4}MfSh*6p3g>Aq!+yc!i)`E5$CfR<9eg;s%?lh(6HVV-Rc(y`g#R_#bP@tA5=l*Fr-4-6uel)pS-zu) +W<_+&W<2vJyWB8UDrxr9p((|e>-T}|pCA0=?JNIBT?E0Y`x>yUfd_(fsXKpOMdwYp +8uh&&XmicuRYE!T%FUFW;F2*rC5~%P{YTCRSwtAZ3$HF>o*|w +*0vpvRkPFjtT_X~x|YDwDa%kZvemr6-!;Xq=fH^k>Gud@>nc^4Vyd);8SWttKGa2;$Hi87+zoSTRTpji}KACGi~Vy!y%fxz<#~`j7 +Zy^%x|;%l~mS99ToVX7H14Lr?vn=V+d!LQga8?TDG<1nShAK;c~487W==QmCeolCRHU`BSEh}c8QyjK +2q`=1m~tD==RhrBS()wj_ZS9%y9n@bc06>65UZQ9fwW?N$%8dE7NG1^?N;-6Xl(YqVhrJKdj<{o;#o7-0Qv{ze)6v1cpmetajGrYCst<1x3 +@;Kxp-T*UI!oDBaEuGp`2Ci>PXalp{} +7D)w28quL;?z|9S7I|Z@xHovpUFY(}%{g#&D{RgUMhn=)GLISlzNq%1Kr_N(9yio#%IeV7!zK!mvjaa +u)hS|X7%_2wn;P&d_kqZ39M;90WA5cpJ1QY-O00;p4c~(c!Jc4cm4Z* +nhVXkl_>WppoRVlp!^GH`NlVr6nJaCwzfU2hsY5Pj!YjKT|%=-!a3QroD~T{e)S@+B5ji{q;TOGfhc#FHbNt=lIN-VVcd$AL{lV24flylHNG&QFk~=zR?@C%|?^v8>WesYpI2z#NA0VcwvHN&tS$NKARqq) +8d`j5BwXN~g##1Y~rp~8NA3<@kJfc`BnuS_NGj?pBINx`Y811cGRo>W^yZ(#_(MZ@IG?i*?Wl8S{3O# +v4)7o#0KJ*W3lw4V1>)=vhnaP>LKSS6Z)uy6MXtJD3x2J~@o`lZ-F&6mJR{;aPBlGsduy+c#kh2M5E7GBU&+4j*CjQ!GK!&KA3-%&*p<#bL6Ux8oRu@s@}pYZ?7Uo3h3yv2R=2=1c)Vt~-^>r@6aWAK2mt$eR#Ocr)3TWo000g)001KZ003}la4%nWWo~ +3|axY_OVRB?;bT4RSVsd47aB^>AWpXZXdA(cjbKAJl{_ej5wLe&%N~)b}+H0=qRdMV@bGGcUon|t5Ga +iV9B-RwEBSNmyigIA4);8Tah8+`mc2EPCe2r-Q29)i +9i*|o{<&Rj@IRgAOFX^Ki!#YGtD?f_(!^@IVFJ69bzwklwE^Wtsd}eJ>c#Q#OOWVXK2)PLRX4QOsvBc +X@n~XU{4~$XBAHeYr)Q5|F>9B)cu?0#Ixmu?8mJYxQf02LH{~MFoVZX>noJ8_Y)DtL!Wd=qS-A#dKdD +V#sYqw4FmYmqJmfBw&f-7i1@dNDllr#Xs!1)KJCN&LfjE82JjrZ*~uH449T*r|d>( +pV)KlU+df_yq(Z=o-$XrbboP%&N2x3RtT?j_=-IeYjJj$tU%3baOMB+kl{ASGQ+Fc$_g_#iYzGCD$s +Rf#foWCgJirNv(+f3Fft6FpbqhKN^@hGKi5_X+*?o^P6m#=GmO(#g;~)KdEGtI?5me2SfsOu6ehUE_VZ*0(&uTO7k%|6w1>YS9)&91GG<5u$3B@m-ZwwH=(ic=?hRT;%g4YOQDp4949 +nz7Cc;8Z7v4TJiJGTZ{07S$>nlSBT(#!!zlK+!_IhtPj0I4_V2ZNpk3eiU4wCl9d+jiW~zy?Xr&4x6K +Jb2|C7FSlEcMO6hERLnWWmBJqtRuQV5HgpyWo#&5@N1&N~5c@C2-mP3}H-fE4p%`xyka> +qiiEmd01tf`r?B^V&dZegjkCY@27W;JgaxhN-9FpTgeYEvCOfUOwz8^O&;VZ>o8L8-aqMr=fZd7$d`K +ro0C+vijx*{yK~?VBY9{z6s4`xU~95Q{(Ii~77?hW2P3t-$6Me-;@W +Z{^i+I8>&L4%I%L9kWyIB#6F3>7p{h+?{1}0%D?LsQpp&tmy8`x3rU{>sZ+UA;3Bs;Yy>YboXl7kNI2Mo$SweQjaC?h +egzl1`U7m0I8m3yD%jP{<3gb>>=xl9C&^JukTRSX1Ca@DgjyBV +{)5IYuHQlt(-Jq&CFe(t1d1y&PNS0X0bMZ09)5QPh^IT5$lB33*hUkz_ly1p>SIoSa5N!vlZc$ir5|fnuNO%x8c7`ISI`&reI2-GRTWUSam^hG)h1A7 +g7PQ<_r@7&QiOCbAi32n}r3z1d7q5?OhhYdQ1-2kGH0-PTaRx2nVK&rC58&wX#um!K#^r2uw^|q=z9r +BJq(pG?%9QG9EP$0S`BVE#y2PI^y+LyS~LX6L5)N+tD0XApkBNXwAL_C6!Kh;cWR@8w<_4KSJ3vE)Sh +#M3EG<*A|)#o^D==@WjQSaN&#vEsaKvkOxa3N})@c*8>?rBb-=*HH$ONzRc&1ZlHUdW@41q$S+Xbr}Y)I74R}kgflQSJJeWR&JE> +2FV25Zo4R!VZE^<1f)bYxtnWkZ=)pLXGB#`3c~!_M>U2mBmlVt3Hdd5uhS|?9c*^caGN}#raWcM +F7<)k8dgY#CGxE@mA32R(y7@ +gx)p*{-O+{kRK3~aQ--GqV^SzBCZQ-yC}V^HTf-(>&F54r9A7@i$OLkmGlZLn1#<6wAxP}Msd+RG8#l +&x{?+%0wMxsap^O_Z9q?$)NNSY^G&L5gZi%kbwcb$t0~!EP9}ka@$*o}UnaBb&dRy@;Z5=Y+k&*q|PpWQ(%%t|j&jJ9&Y)u0t)eix+SP_My<_&i +qfwV6UwXn`s3A7+QyEuTXe2?1%2isLhsm%$u!<~|)F7orDhPQ{Gx{b#;Z+!@v%&9yLi;{4wxBDl5d!+ +*-d=>B)wTrgwUW}&t35(F2klhwC)H4CO^?76yC-6m_nnU?ql^1rM(66aI{s=jKb&0MT!v4l!P3jKOL- +WFOY5g5SUUMIk%zNzsed{<4whb@$+I8AmHy$MgN1juBlMa4_+_{@c>b3?r;$q_j3rty*D^iU4O(klB- +RZEL_@dTxqL7=g3+f2^_^=+AIXs#f`TTbHc#n4uG8El??}>xiLQ^h98c~=s?+zQoBOxp+q==^l!SiyZ +4tyn{};Ri7BVu;f{=Zl>C3CPXBQ#ANTMgg^1sHnA0W{Cvo|+qAMYolOL`r;G_Sh2dN)4ZUDk>Pp%yP9+_DtxUbC>>9)mU{Kv;#FZDyWdO=QiFM +BfoH`f%FZ>8foGN=rcDWnYrV;XZKZun569oFgl1&}5_!TFHj~sNxdcfHNb<@$klGKnFRw{Clz^9SSX= +Tp4V>j-7f_B%CWBofDANIY;^0z1P0IiQj+`tLib2=;ObtLicO5QGQ>)c&#zS@ZtQEueEWI^N5vR%SI% +{rdL%2O`>;voEbGSLHTDDc+qb2XsqV-m`f5vrMo4?^hA?1dx#EQ)wH&Jk=lspxY0X{R$x_(IL4eKJ&` +WrdFv3BbxSaf~X+QS+BON*^6&z@vWTu>SXb!T;)c#x~rJkMhan8A6Nm}40|ta(62W9)#|VQQj9UYhby +_2_N`z4MWuy$EDh8fdF5@)co%S?MqI>0rV3p+7cD<(9<_U8IZi9(|7X!xSIo9^AE<7w)p7p%v){{Wl` +-!N=WJbSS)!c^h)GW&bj0Ty%xL_+v5E8zF4YWNi&~@fVDp9R8=FS49z)e%P`HuBQ{7@f +t?nCr*SO`9vph{@c1d0=3XT>gdN6S)(aDh-Nl4I_7#z6>mJ=sqpS&gUUuftSnDbqG7?AkURN`XaZS4% +&Qk}%)9j%+1+8x@YTF|jDGW6h>w!Vsiy`lQ!NoWGWYoR(`{3aQ918cLDLP3;R5NF!GquPY9TA +R{$nJ^gOI1{)@Mq9=jKFgyYzF`dj?4o?q=a}>M0d*7GD6`4@J5Wao!2h4*fg^53s0&{~kd(n&4lMV0Q +B(<{LxEfrRHXPOtQ9H#4xD$ewh$*S)Rms2N&|-S^iwVe-~{hX=^AI6tH2Qga+6#(^FSJswLT~C)o_`_ +amro2ct2eR(71^sxm8%{o`%b)10b#Rz}Zj|-Oh-8yU+$yKSDEAnJ;ze?wV!{7uYn^zWsGDOOU_Q+SL>t~uo|%ZiQr){}U}^9Sob(MdN$_4#si?Mesx%}mOQrLVWt>({NXxlnBGP@r +D5>XZB6XluNpX_R(YW02*L3_{-&kwfN;yOOL5+=_l&mFzgOVbB*eU|nWZbaqek?R$QgXX&O_m%U~q87 +ZpRZ~WV~AP}A2zvfP${i?PXwat9{>_OhZL$QBXmb0H5qHGb~?Kl4L#4&PI2B#U6`JnE-XrS$&uN(|B2 +i=W#%O}YBw8X4-uJJs!It3wq)Xk(&=Ah`I{C*z1cttEQcV9jaPF{r(_vJHG`%2zDN$neb>>ppNlg_>9 +2^;R)4aIj$@S;nC?Hc;!^D}hR?~$<8e(wlqQfs;#aY9^@Cvq{Uow3XDdcBTTmtF6OKR9}d)83Qb!248 +>LPdP|=n3_D)&3x=eae28hm?pcXoO=p1czd|0xN)+|-!g4W#dBOuaoII#8E>`tA|^D|e~8Q?GswcD +a#b=V(<1h*B}#2YDu=%HEd|Bu(vuy2mtZHLXqYWS9@0H+NX6YNG#eRG%2XzUrznyrE-i*?GX)FhpgE=lC7`~utG|X>B!@aIS(r^y%Xs*P9IcoSb=$b3-Y&{Bi_T@*E3UrmypRJbNeZ=;?CU_#p_#5Tz{Nl(RN-KHz)ON;{ySeGc@HfPCW&X +(j9ESffh5syI)^(-IJq0aqX}#O+pF^@z%l$20{})h80|XQR000O8`*~JV!W +ubY6)*q*v19-M9smFUaA|NaUv_0~WN&gWV`yP=WMy%g}QzRfPV8Ja-M +MVBxV;7)g%0sU#X-NtL?~BkPTUKLQ-JI^Gy_`=!@jF>uFJKL@tA|wI%bQtMnI|JooQSMXKKb?k$`{Z7 +@Zy{D^@}f`fAi{j`Qqzuzx*HNKl$w^7oU{ZKVO$`A8!6~b9Z%HzPx{XU2Y!BpYH$t=Jxg~KmYQ}`@5S +rS5Nm3<<0HQ_1#l>lfU`=;$znrcQ;Qr+4J|;f4#oFe|P;*?tdyzzcOb2{+sKk$2V8+uK%xLZ|?6N^W( +$2`v-lu*Kf;@A2i}WUh3+BvEF^iOa1&*e)Hw0#nzfR#IOGR^YZNWw!GxGkLBg{EY(b_xYH=dHnRh<~ODM>FVKM%Xc@oe|fn1rF>TM4xh@?efjRg)6e&J=kw=O-QN88a +P{zkS^MeX`no*c|Mc{?ys_Vv5BKlOo2$F>aQ*h?aSxvp@l>wv-u~15L-zmW{_V|AANcD1-P?Q|?)vHa +;g`pcbNGjEzAs;2Kjxj5KV08kKjhPV_x{J*n>X3{>zg;%caPWQ>ao1zyT_mZK2u+DBd^XIDPP^^@S5G +xakurugUf@oMLYmnGNwH&;*Gf3D^`I)0ir{h{3EYCL@G`T4I-!QY?sZMnJAus`SA=N~ +y>KIq>PR^`X*^8WGqr}wv)IYR!n{L72i|NQOuugkM<{#^d$*~^#DzIpxU-{o&}rt@EO{q{&Vzr0HbzA +gDw4_9|jAM(a?fImHd`Q<-nk7r-J`1-}`KXdnAy?Fi2^H;CRSKq!Y&&qetUcP?u<@aAdds)8w{^fVyz +Iy(7DX*@tKTa`|`D>H>f2R1S9P*d@oXNM>Pggg$kNeU8oXh)|H+cKD{CxG->s;nH*Efl%a+O=;-G~3L +HN3sQ`-`48yZn7Ze^+jPDtGr!m*sB{H;J7@-#@Hwj`H{Qy-d)*`TVjp%O73c{p)Qm!K>{4)y+>i^jEj +{_Yarli~GkXe)^|p+4pd><1>rHO8Nd(e)FIGSDt_K`~OFy^2tRG{OXFu%BTA2;r^F$^&<=E$Db14|NE +)@0+4cVJ|;%q+`doyaGTGU7T|qOa&C{Dz+c|I2W(H*Z+^bJzrFv<2hI5(uijnVU9%()*Z=nZ`tD8Q{p +!ct#O&3(jg6e6AMf*FUw!t+7q2wd^Y6d>?2q4VbM-+c<1x4L#^?P*?z_*qU-MbYXP^D|a(q6`s(es-G4jdx3hniUw*2AZSAYS{_rl%?XO+`GN1Fm0M-}3e|d3{llH^wKY#b +)%V%Hz@cS39UcY?t#rLmYeEZE0FQ4WAa>)4nV7k8i`RakWPAvZ0w+}L2tV6lnBRu=!>*qhbdhx%WXO9 +|v?tycgzWm|!w?BOK^7;S#;oFzLfBrK6`8OA()Z;P@uF>?%V$-u}G&BFfSG#ock$=s0Sz_8{@Li3gUd +GYqW$j154D0POnq?2HxAlwsV3#?VU;4X+sfG6b!p?^_9K7iK6CI@X(kU+Y4T2vdX8J?MSk#0JMv1|WT*U& +X3Um-7Id0y5&{Ud6qZt`A^DbwG^GF=ONdTQ=2nwl?gSCk!$HKyZiL~hdAVdb4{LWc8zQ1+^_CPzhZS4kCxZUh6P>pT=ZP@T+GO#$D +&6rXoBmYF&f|F`n<>w!8etr(R7+YGim6_G@8*fF;X?I8QqL-MmM9IjXiFyIj+&&=TQvk6!nn|U(^&T?1{#I@KBHnr!vbyl;KMKS_c91eHeLL`5kx3&NEH-(m*xm3o~01&cS4)IdDu47SOj{_c<>D`;g`^tpXdeltyE++_~FJ&R`CK?Ud8V +b;uvfr8K^){DnPbT;_{$)0bReh4X6-Zu$!&ocJxVG;UzDxtT%}To9Oc?yWf8a{r`v>TSjHT*HVfmzL4p&Q!-+i9|KCm!Gc`5}8cHq^19o;Ea_ps +6&ChMCqR%LNX`%?*~jgc;#3F;ayqXxy3W=D2BDqQY|G=L@Es&ClpU=mD@38hP1xzB>pC8b@0Wmg0 +cH*XUU*r?zUor!>@yI-AmbmHGHy+Xf=RAlmVTr=@R05Hnp6|H*qN!rn;3{%l=8JuSgUZ{{zhckoIS&BjAbUXma98qer>M#o<`PLbQn20WO|h5n +*D9?|iLjz@GnqT>4^4@r*FAZ0HF>9nT0g%Z3Z$5n0P@Iu=kL=@#exCNIekbjLknw6 +mc*?$OgdI)o+BY}_xbC7&zgO+4WdP|5s@!7)!#nsyCVA +KFn_tsIgY!~1&MIBW0{Z~PPo>^NYEVFMJ|@Mt(-*UI34S^Fkt>zID;^uX~tj@SEnzwi=_Yc}-60lOXz +2kbat#{pxfvY{t#)N!MZ;Q*uA&|}hL?saSM5@>OQKxRWvoR>q_u(}O;nGHQPt2hlPbt4#P(9p&bG}rv +D%}W}*BrrLF$q6P8650qJ8Z5cNF&nEU?jjufRO+r0Y(ChVAQgq2N;RI*R3ZnX_&}t=m}N=k``bjz(|0R03!iL0*nM0f%7-me +8bsiLyxi7tvA*UtPt-NU9ML}a=FW9{xttmlN^Y32sX2`7(sZH4hB<}_mt)o5)s>;b$H2QtF6%2+{_eN +&n#p;{yjN~Qxfu2CJ0?7&_E0C-}l8Cku*fk +M7W;Gjn0L2OvD^RRJv7Y{7BrnMi^aPR>NLC<8sFw{r2_7m?tU$2>#R?Q*)7j7iC|00Y+xGJ#c}aetCy +=Z_vI5BpBrA}tn5_yFD^RRJu>!>k6f01yK#}0I)swFecu5746-Yv%vY{uCtU$72wklApn5~N0s&MTJM +k*MoV5EYPI?r{RtQ%OVU-4L!j~1tS%VR4`J(NChKM`qscm10xNLG%(UmzCM$eo(yf4XiY<(!ff?Y&9^_z(@ll4U9A}(!fXqBgh=t&;yJ#Fw)NWV!}%rSZQE|AS +xSrf{_MBVDs6~6O1%4(!fXqBMpp*I>_1g<6Jj*Ne3$(taPx_u_qmjpo=x=a6{*6NI*@34r)qMd`@`Q6 +px8zL0uSkp_jl*8uX*dI?6D1Fw((D2O}Mf5cy@pwZKRRBOQ!%Fw((DkG*aSUeduz2P++{bnHn7BOQ!% +Fw((D2P0VhZ0G?-IvD9-q=ON(ER72<>0qUUl@3-q_N0T64o0B4+0YY=bTHDvNCzVbeA&0qRTk$%P(3trN}N(Uh1pjnKRiJvS +oXCQ{kKNCzVwjPx@eTJVw%RytVeU4ZWps6&Crqc|XNkb1XGQh~#abZRbxo+rf+0YZL4D87OBL +j>KFfzc%03!p83@|dl$N(b)j0`X`wvLE+p5ua*0agZB8Q7BnMg|xeU}S)i0Y(NGK|RdPNDnYFz{mh2q +t-nxykvlt0agZB8Q2r_lH9@c1S12C3@|dl$N(b)j0`X`z{mh2qaS-*c*y`O1FQ_NGO#CvnhlvF8+w9~ +0Y(NG8DM09kpV^q7#Uz>jK0TBUXmZ^304MJ8Q7BnMg|xeU}S(1)a-_k(qKakBGe#4X@%$kM#ehV4PJr +(l?^??%D|osFfzc%03#EOOfWLR$OI#3MAlLbZy8M2`#7+GLsfsqA978q +GzWPym4!V)Qq +G2+U}S-j1x6MaSzu&=kp)H;7+GLs&2!!0B@3)9u(H6)!k#QJvcSj!BMXcyFtWhN0wd&|8Y!oS>e~>&l +9rt7mb@fC7~eQe&{Ud6(`g3Hq@ibGq!YDeLl2{e(ZlGmd5L8>p5q!ljh;qNr>E1?>FM-zdOAIwYdJlf +9!`&-N6=&Q63cKr#|?T0J%gS>&!A_}Gw50AS?O81R;5R!N2N!lN2SN+C6?iMj$7&3=-KGm=-KGm=-KG +m=-KGmxK^V_r$?tpr$?v9<|S#)jvTksv(vNFv(vNFbI^0pbI^0pb8xLek3o+?k3o+?kIhRg!|@z<(sR +;t(sR;t(sR;t(sR;t(sOaGMUO>~MUO>~MUQo^+hW}oJr_N}3NmCz=jv!$9XYEbXLYh@9gVY-MeAhII+ +}Dxr0itTx}Cfva8F>n4SJDz+ +xN*YIqP(dxB#O47YeuM|ag&!6c&Zaj8l=b2%8W=Zv@1!e8397U)0q?}A~R!_3L$JEk~E&dQ4ut5@)E= +3I4Wwv@fgx;L0~iXQNbz|xVFQnP@3{CbKF32b7ja2sd=n0I8vCM`EG0Ead2KnGO|FvjF_gAz$(Pd)^r~v2r)EH7+m0DPAu$LQAb#F979F))4 +0h?0t4dg0YhOn9E77(cJg1rR;%cPjznx|p@uyTrSynP3u(zhBv&xG8aGs@02FczCjvLLoWM>M0>;997 +!J*_n1=N*T$>RpblXTKcPFWQ>)?4g&w0Q(H|6{o7e&#L{T+xIxmP24c8p-a;|ei{V+LK0U0$s#m%8=e +#M`_G1fb54jt*|@af6OJxHRH^}Rr4o#4fwE`<=l)`_#^T;%oi1l)Ax-`?ZIOGC>m5v?l;7kZiwZce ++Cn7VK>#EvyJb<4;`n`YIOa66QP(e72Aj=joGBIxwG^JIBPdO +Wz-pnR77Yq6Q4VF&oR;FvTU*UTc^GRg|+BbiQNm-%#j0}SuIL+Adom2%n(W1{W^l6d@Sy&RJ(CWC}zqhcyIT%#o^V-!eXi7TZ1RISPsM1u8Opfr#o<2`ScZ98tFLG&SPBaOQ^i@rkhbs81{i!*f9f +9$^(9RO1PgPY`^}M{ZL=-RH;%fHxG_?zPU6$i!7E9ZLQ2H&96> +%Hj$Nma-ko)0R&gzbTw+l@46eePHWOA1!J{BO6eR63Wsu1HAUHd9C`Q(=AWCUmcnP7jAuJhnLDt2JcD +=@AezEmU8y9+4<<3=bA#0aBL;h-`aBP2(adO&qCL0l=hU|$-_DBTdp1F1+l(naZ!nJNFm4@VMawKT12 +HW+v>-%y&Ds_X{2O>~3B_|(iNF~0x%At+Rf*w$TT=ocgaEfi@lnI_3`MRK_Y24%`Mu6#L|DtcMsaaj@ +25pJ_2e^&(&D7XGLu4bbHMKcaBo*3I=eX3DsD=SO%0N;w)n5}{g61jZ0yL3%aE7YF9T(5h-Xn>zHz4D*KphJNuZdP+$uR>7q`NouP0PG=|ccA%o~s@4q#hG#^GGg2*;?6MQ@mXMuhMdq$kNPa>bK$WNBA*$3Ww%HH)C +Xp{S2bPV;6#%RF?0|eKHWI94;6jg+Z&>#^M_E3_<9pMy3RzWi94=27UNJa(k=Urjn +&UaHOec{@(@(rqXQqi0ouvjNrvyqw){HCe03$Pn0(D2P^xalgR8hc#oUWr8mqwNR-#x;$ckrR13N{?0 +5?pB-z1%0IJoxEOGMHK|LAjx_3bAFST`1yMbjUdJ|6G9p>b3x}fWT)T?4j~J)&>|{JIi({}6#SL@an5 +h@l9&oqh$s=mO~DQX!>QLW(NV=zNR3Xy#Nc?w4lOe7$bXIi=U|U24KPzSvRM`qae9bkN^k6H{wlH6~?&9OFUF +w#p5czp!&mI5s#*k;4HQhus|)|2RPx~jjBQ?6T*Qb5^)saQ=^nsJktP;3L7m{3ig$Tj8U6jBu1PXi}D6bw){mS_pn2E$@f{6*@KIp6rD?lm_5R2 ++#F=?Yn>d*ZJm|5mrv#fQlFXyQQuO0+>!ihL=RM;bSINkv35JgHGB;qPI}FK^GOx}e#Vkj`3vqG +gQv>CwW)(Zj1-38FgMFGm^h+I69Xj_!|IRI?ojvYI8jr%F@hk)7VKGe6kCFnv=Lo71go-Pociw0T87H +prgSSz1KC843oj7~gti6AA7dK5D-2e#?!u%q84#yv3iMS-r-C=hA&0oDIMBu!b*}|D`I7u4NiL+C>12 +b>UnmTg#e#?sMo_U(kmV~Dz9<+GJlp9vcik&bPckx4a9jq#MLMu-hoW3IDf)UD3Rxf#n0`MLfFbWvpK +89(i{Lk?)5Q`IUBRK9TxL)iQ>a)dT?8E#30dq?`Q%cldNU@}p*m%5uD|y&b+698a2e9rmAFVuY*$RG4 +efhwMk?4)cu#F7d?&VYLv$q{0-PC_x>uJ|L3P(MYgSMFs~}AWk`H);95Jvi)&j;lO+*d0iaVwR-pbGH+e}Y{3DbOmlJ1yk`^=)H%P= +hjnp6gM6!$uq>1yEs`ur=C0?EPo4mxRMJlkeIP|1Tq_Wa9*d7)A6wK>n0a?EwtBG-8_Ms_?{i@`#z2B +&Nb;^(mKQ*YYlS22QeXlrcX!a2SWIgb7@M=c~awgspYmJO=(JeBr^b$oCOU_6x3o3~}rbxGYQKer0D@3(Ee1d4cw% +2mc7oYK}r#6oztEtX34fR1jBQ_yw612TnYK6V`7)V-Rkew_)W&c;nFStpbTz|d)3rQjGJN=H~SwEQZ) +WHFj9BTBUS4t1|it$LwO*HmUeR-Q@@o{uS;Qq4re6c9`5V}cBI))nJx(%7}@9Jdn(XiHScEe5aZV3PggdKAKA=0hjDIA&o>ht-0UAToKdV+GSoO3%*oI(4rl6s*Jhs6-&Sj`QTFxC +O!SLKI}!tb&&ch1HCkyrfX5;&UfmbP__ug=mFGtNXwOwqOZjsKbZEIl#VT&9x;2;*B%vUVRB +-A(cW0`ry!0Sr+Iew?##%)7VfRNp^Z^$p&mhXDK0RC$FaNwYcQiJ*zy6=#q&sa!1&QsiQ4K)=fGs244 +p#NQ5}$?1i}7{IrwQy*eU5Af5-Q{GzB>r=pf(*3g6!K{TDuqL@F#JMr^Nr?<4K?Anu8Q}-G=btbFObS +Q^aaLFw?8L0&&$7Nezhmz#f2D`?FI4-da*3PI1(YVP=yeVK(Ac)bPim@eXBI}s|0Hdl(#B}6M!C67kA +%z39aD`Lv^C4b>4kHmlj*=j`P$^n)v4({&h?-va(h{6ju@T$>en>Lx*eC4HbxU5-IUZP`o@M6`Ae`t5 +K`CNTifBqNQJfR1zVQ}JJ;Tc2A(-cpKz6mFvDGk&A)HM&BaO}+>L&{C!jW2@f2O7Mj#VUYGhD +asVr=2j`LGqg85PTh#Q*HGO*hSXe9=91hZ4usv&89L^i6j_y9Kmj%FhN+SRg!HZXoN+95uOuU}Q(75* +x@9MhYk@dXK4Iig%%MYKL^?w$MLBQiI7F=YK9BCyy_&K}%4KqX$1tw0kOiuYW`*NHj%7$$ShCS&DikA +#Q}$+)jkfJCb+0Ct0j)4sPU_Gtz$BPj=|*@}$FsTkvIR% +0cNToI@fi`3jr*6Bd9M^R_%2zf;jpELG-Kcvl%F4(@Y3=b*rhNC_CR%o!9HqQP5ilmi&O|>v8TAqctF +)aTa_U|Sb!8@h%wJB0S(li&^I0Rrd#O`(u@3_m~$no5LFe +uRSWRI_nnj*XxIlQ4!%iqxO6k0l;^T=Ww1rlteRp|hyqhw3mVTCu*zg+LAn){eIE)Xo*05PBe{R%I-Q5>iqk@Div +$T?zTAd>Z6aaTX7(XB~Eq3ojvs66iL_wm3>wM(KS{_jFa)Xu5TbF;u~#tR|S2@DAC~(V;faanVbbvar +mh?-Co}3^AmRzi1Dli&fj{MrtCIkjJtvCBL+zF-?kXKOvw~9DsjQ1|DjpjJotmI7p$Y#Z^y5K?{|8Xm +xsbgX%!o!t6X{>M0P>xSSJqF-!tB%$Ds#rBhLn}ipu1*pHU3iqPjBTDro@xZo_KjoKQVNpsw&Yg@y` +%q788%{fk%nfA@Ac^e9Gx3mdqN{dcfaoJiiQH=0bHdWk4LE)~Ek@IHa_jd$7I}RYY&$xRk$1hT;-2>E +J?wkX}Qk*2-Hzgibga&((3j$|MhA9+N#SP<9U&6@^52g1T(Umf}@KoN>;Sm2S-%DLb4T->*29$nZ0-) +-hwBL6BHm)*2 +1R0^ASGg$BDxz0Xn}%K;zIjwo_iFSs7+5IcQ5CCRYM?qe=7Dpb`J`+s;>RLA400W98y`~sk_6k_<5Ks +kH1QA#t`dOw3;UqRuPMQet`XAhu@qCs>Gc98B4tSg68*->OQ?H|xfuljSLC@QP(ylHBSI%@3a4DcA@T ++O_0ak%AAo6~wA;tIkEwet%D-{$)|N<*B6hF}-b1e-qLiUfb!JsZ@|BmtU+GRbm0+f{!)e#4dvzrbDN +Jw`%PLF9>ARvTVa*%wLNJvUa#A3ai#im&VihDbD9>AWoE55j-74z9NT{m1Ym@5j9@6_n^i-t8wx-dZ) +vG|33cW(|weEM%19h)8q*xA>Q7@p2)s^l^{upK65uBYYL`S8rJY0q5cf?o6u7^D0-0x!oCKqmc8`aQ? +LtGfQizEqC8^3@PlTK +fDV3WqsZotA;mH$kEn1Gzhd)1^##dVeF3U((RRQD?3Thv}Mq;wmq*+Q@Ekm}3HCvSQ&PuG0$NW1(=!M +~JMlCtN1qwdu>7UEL@rm{E65_TC}Z2c~%G0vowI3@+ZfBBurPvY^Qte$e+iNd1xRR`vIB +&TsOPn49`)wb-+AZSpUZcN4ip3oPMKMLlXVU>|zpRF$39E2*RjZ=9*_)g|GH#eeJpD0succ`pL!3m=DHiayE28DtC3wg*u~$N)q4&8YOiaIWqnf@mzEIukl8l +4dLOpZ^Q4|xAEdUD2yGx*^B$GoJ!ZbPP3eHVsyGrP;oqr3}y;i*gu*nvH3<>H}Q3bmJ4y0mE+bMhR!uKaTz5Id +Y>0TvZK*E`i`T+l%>c>Ww7FcO=gIXlC-5vac;(TJxoydYNV@U8zu$t$dxKofuL8_fmE4N&7ee{ohsBD +ezZ;GJjsLZyk1cETBg=iQYMufdSe;6)YytCz@DNXqPZ&L=#;!LC)xAm^Q3Z)uoX*J +TB{-DO7rpLCaU`e6;!uH}=4U;AL^i)=4p^OO=a@O#tl?pD$%4cMr8|HhrN%d^ +pdGkS`j^Nbt$baun~JQd+Nn_Gbf*epF+NR;A|I_Y +24cbhK>3!XxJ7S(-1~Tmmx$V{hD|l_dfN-J55a&RTDLB?snAC1CBzN)fw&Q>z}5S%+Ri(1L;@X#NL-; +miNC6nL6y_Ea(zT?Is~CSdgxVgN|PYQP>Y}!HK{N|VjJU%mu&ZhB*(8*HaoTmtl~1%ADQp2C4*Q1*rx +OZyO&BH8&Vridq&+W(JYUq`W=yAm074TSJAePNJ#fo6(R;{%b*0wRGG5+edX +%=OkVQuk#R5Wp$fVz?qE!r(L<|*J?5-|B=@Uv1IY)&NRK1aEFuQ_!rKnIPmA<30pdd;aTp3T*MnD8&tEkLPSCyPHA2=SBLtrU_2(@<2IZT3hv$S=No4mwzh&*J?Rp}w0u2OsX +JfT2LALWDtPVN$y1v^#bDKcY#v(Bcf?iH+t-YvLQ-$R~*SA-Fgk&m@qY9uzVgp{^PRT@FZwN#G|JT~8 +{)V-SKo)Xfa_OI&5mkKXP+`EpnDTAu=T32RP<06uplD)*oR3k-fouE?pYLw&{F;!P}n6lT1#zkjuT-u +kk{6olxwM8TY6}o_vUo^mV0C@##Y%uxbIIE~v%*6r>9hxUY1r%Mh*V0C|9K +n+?YwJtmplrm*@?@yWn8Ru}UI^ovJpZi#AG|K?qd!DC#UU)S3!qwqk(lvg;ozb+4hc74PdYd10j`b#e +|Zv?>%qhj!hn@SqxP9j_3t*De!9_I{3=yu>A1xT=>F3rnwtw3CgFCQ~+KyQJ_E3Yn_pFz!nSQT4`Y)_ +mvRDs``}V4VbLRehiM4i$%?5KHGDROx(dtykyj^u}w^AFVm_C6=|$ap5JL^Kn8=-v=XxN*V+J@WUDn$ +zFKBojSo$l8g+~IU^+_b&hJ+#aF%W6-!?T;H%2}RXa;XlX0fU@jgBjlBz5Z@t#C~r~+5)UpvXRoVV?c +-uFt$Na$#k6zQojMpFEa(@8ukJuBPQRnf$&!KGj+D3azB+N*n9>RwH?hssf6a%|HHo?etowd#7Sr%F> +{cBTqOggBSfNVH-AD$e)WaONcA7960`)xP5=)j~-QfZ3_Q)3uBwSd`^)O&Y=fuAi6iuZ7<@)B9ej$uh +)JR|PDAYlsbqqAESh`5}YFc9UG#bwS9+&P^(aQ$T*s1N9~N)IiJO^^Uj&xf?|{R36E1>OeP_rhv@Etv +HezD*NdwpOcy%=eT{A^2zPhQ#eV=$O9KQH000080Q-4XQwDh@YEBaX0J}^80384T0B~t=F +JE?LZe(wAFJow7a%5$6FKTdOZghAqaCy~SZByJxlK#%Gs2zVexD%{OFMIQ{v#|^Yn;9Djz%l1=I66eP +n%20HSV?V$`}JFIPi2;*#`euj#Eshx-BP_|Wj^_2R!ZX8v*~~0hvOHgXX4fA(ec^UvH0QD`O!~eOWS8 +V&&2hr6z6SqTh+x{95stlRGoO;{BN~h7wY@n-KMVQMc=eyzOKr;7jxC-U#DC*&*$aGa^2jQt!S +2_pBPiMKP&rgUfh>#*s7^J^|ifkTJuE>EH0YX9-p^m(U%Kx^JqpKPgS&ftouhb)v6b}M|(nk^Uc?4h{ +@&$;&8ndm%42yF3YZLKb8x-lB1^X+v;Ycmbu&QeXEWL@w#X~h>L1{+g5jCUnmKC(Kq7av0pW{Tfb7(y +1HqL_EBqX*_Nf~nq~i>1p7`rHXAW7YSEU9sBQ=6*D}Fw`etG +`pS{$Ce6+a(dULKxZzx__NRhm|3mHq5U)!n^<;6kWXwME@OD#6tNua7T}UaB64Kb*cgy?(2uKRLZVJH +EOSC+C;qP+S~dUY{Pld3AUxF5X;ToL?Ou2ys=GP_b6#l#;!QmukqnMrm?U_C>Ys?9$&V^L9$W^+K$Qk +EJs6ysQ+WM4?>d{_%@!xNhoOvtHGO6#AX0mZEO@NIbNa!j3}U$hvA2vTvk7KR<{>E>)wV{;*aixKiCu +s-+tGWZg7vBz|bRUN?PxsQSi@&02v?uc+SK-6;JiY0F~167y1lq%H4T<)gLo)v7nXW%@lTm+H&grhVM`Df(qlx(>&Ck^Tl; +%jrj$}?*x|e-fscg?o3;J91@LToxxv#_us%6jJ+pZ%w8nwE~jmDraXbrkUyWHyn-!k`G8vR>4#ZJfir +yVsh?Mb6{#N52?QT@|v^#A?K3QE|-=iF&+tU1zY=3Y6|bnbMK8~o-Bh0 +idJdKDH~2J-2)UARs_h}oD|0LxyI$7ct-YjQ2JDcSF+v_E)Y~I^@dl&PH>3{SX<$%tCY-|pK=_F#79t +rtUg8!Nd)Op?Q5GK7&@ppJNMj&nHOe`3=y_N#_jb}~=FaC%{}3?s=McjX&r&B&I+}qqPUbLbBa;M1Qw +C^Ia~7C43*HE+(hVvHh-gEP?1Lq^OLok~B)@B-4`7Q-`h8;R~Tuy7q}1Kl4YPGJVcW?+HI)LfTa)j={)F +=`H;_Ny}-JvU90(0{D5T*EAx#rGD<%&%`5B>f}jnnukL$G$(cGOe+HigBZETvYs-JI4b~gv4;oIK7d0 +@T%Ke>Nq4sC +=mh$vZtC!;j*T^K!c0+7>%8cmr_aa4WVhSAgy8#}WBJ@EN7N?E%kLS)W75kWxmbkZeJ6&NG&?Z7QWm> +#2Co1{^WNy$7OsRpzG0tbr0sv(b3%1qmmq1R(8Vr@cz@ERrE;f>6RyII0M$G#;{1Z`#h8~SsP4pR-pd +dbeoh~g4LD2w&dBZ6}ou$g&XkrvSlx*Vq0-0LY&gd08L2-qMGK7rV>A&-(@@|`Az2VS5>GCA^5JE3$( +r4tY~;Q9safc9xe!LtD5qbU!eJk}@Pd->Er6{XEdgsles&K$z6T@aIqhNQ=L7?~)0Lbee5qbVgNB&hT +g^N2$%kVX`5$geYpamw*2Y=Ebw$G@e0EHN;5CwXbeK4UcoQd_^uKW|+(0BQeMb6%Nr+F8VZ}^;w9y;V +6P5{dr$dYa#?TPZL%U5xemD@)6yi_5tDUzzGGV4J5_-0par+e +jaEq9|)RxEpsBG;z90^PW;LdN?VtZF_U0aIIjr;A>rpy>0oI3+nAfVKQr+G4(;? +bP7K$@2id6X!EYurG(}hQK=iz>!ZRkKB)+TV +*(MCjxfa_NxM9MbDcoEQ3%SI5_{qzXwEreh@M;S8a{ji0@)#%C%;fiO^lg_IDKi}(AAYB4NQmtz@lz& +`nT7}FIFIroV`zE~0V>?eK!t!T8FnD4&)Nd +H8l3*EJP|g?3>%;tgyoyAzWK1W}CgoIQNMuChy*43<*-`R$zCdJExS~f6O|D3uGjJxtDpo}W2SQRBw8n7=gEBZ4K_ZUz +jVK2jdP93QL4PJFrwBlPh}^NC4oMnXpQ5GDhX=-DqKA7X$_vA56#POM_B4q%G{20)Zz$lqejd}~h@3C +1;bSL{%jwuOPE_-Vo{+nT*FAZvLAxHwmNMK#Qy`J-v-JRM4GB<*@5z^#4Fx5d=WIxuj3h!q7D(oi3Hp +%iGlFTDijZE(EkRYGEqtgQUt$cW7^ZtvHI1FAQ-3arXj_s;RG*$IjpMe=@r6t(bkGhf<#*|XdnTT2P| +V?XJ=$sLINi&G+c$6-9AP26ON2HgQ(Fsg{C +t@4ynXny!JA^fR(k#}V_=P}sw50=75P*qmNw2htBQ@7# +07bYX{!ge!ZtueFpto{uXfAbjFdUV;RVqI$xwiX;(TaO4>P(GU_wXq)bF#&tqc4Vc35?O3CBU{v2hNB +pivu4U2~O1C*XirfDA-XGVd5H9)c?P4~ad_tuuOERkDd_;)^129ad83MP?(2FK>*%ouLYPk{oc;bd|h;%__4Sz>ar`{b8d%qv3~5TZnwGlrJ +VPB-|h(Y@9fR%+4;%r=%pvV`eHq6%eCrs*3{+T +v^p{Cio5%DIeoq^%Y_4+yoeYa=+Ax1H8Hp;AR7uiQ2EA{1832P2_G(=UKry%{ak56drUHo4Br#&%11)ING3auY;^Vplg=bN +_G4|m^emP`F4W~ZM!HP2>Jvu3FuZB+^_=1Ss-jrLFqZ5aK4>W8{{5G(b*(@zZRew{w7?WO0b^^=-*lF +zhk{7JL!OD%(b7~LLGa^AP)$Ew+MMqDQ7>C!%6yIdEydXdVcTDt3YQ%3sfUgc2wp=kXevj#>{FO((f` +FdlXm0lGc8|$VlYqiS77D$~G>#ijDEY +f1{Emq_041^v~r?|tv^%u=2F$F_ONleSSpCHATFmcCOo5i~88dyXeoqV3uaf6+bmjJ|iLhqNumV~`LO +7?djHWJB-n)b=0t$S4p`&qT91;?ZQbwdGA)79XCFm?Fu$@bUV0lR9|+&i;Boy})gzFu#c#6>rVTy4l> +WW{aXPhC$ZS-ZnK&r88`3g;vGj%W9z=a5yskm4aV3j@up-J8CK_*7J@2dl!OwD>m%KL(_ifEOnSa3&A +~ayD2BrrYUj<)@X?Rv1Q`-d*ZLdx%^;bU~>ifgfP^R_2@7@jZLsGBV}cmmtOsNdNs^Rx8$mBq$jh(jb +gLii7_K1_uhf;NGxMDppm8G_^dAOH2lB1dU<+sJyQdHWtaF*MV4yp{?W_8dprp?-tdd`W&du6KQn0#t +4xC8vQku||K165F+KZ#xNxp0^tNC5*2}7OKBZRdc{qO01=x*${aT8@f6p7MZ}G3+oa~uD@e==f98aA; +IU0}}3jxWH$alFYy7$-rjnPjIKWp^umOQ%nCQZJ20@zG2$WoVf6zRjwD$N3yz@3LhSU)Og=mjbic6lw;CK>z~JOj()uOatHmhpnHV7W8m&^67? +s?pM&aht;QY=eX_M@`e#L_Qd8#PX*D*6XZrc@Jd~de&z*k*P)h>@6aWAK2mt$eR#V|nhIlfW^40?6Dw;s4A<^$Y@8e2y}q_^bz|@M_cl(hFR!ef+SfQUx@&J +wAqxw4ZLXhgY^-ctzjW_#ZFzn3`jdALHkXH^>$gs*|F<<9ZEUWe7;ZLJPH(JljvB|~4pxUZo*NAh#y> +tj935U?9h#>}%YCk~Aw=5k#dehB^8v7gP7aH-uZrtVhM&s(np5Y!101U26qwUVVMspjz^Xy>R{Gb)k52BwjI5hB63 +^v_FPqW@z27ZRMUNrEttaaPK&l$MG`u;g<%{qCW!A%3dz&hz0_!sO-*ag1GI_VntCDyuU;FlT9TK|%D +!bb2F)|y@5uNcfO@Kpnc>?2=et=Uk$&Z=Y|`3AcZ*2$X;W*_+$>x7NTuNlm)`!@_`SMpm1v)Or@-GuA +{-(fK8`#shP8`$>^%=-9%p*_04qYn+-H|R%fSepj^nDr4D_!BnT2K|)v!QjtW9~}dK&RUy`{(_-h)A& +o)M`+-$7|gEvYX-Ls{0$pagMP~<-JsvGE?6zUH}E2%;|m5g13i9#O_o6)WF4D(Z2S +Q;d-uMv)o7oya%3yZYk1^O(KK?PgKy#yxA7|CDk9>lS9)q7`otUv6|CDuNp1U#QxvuHt^2LPF?!Qj|k +D}xAIc(fD&tysR#w@4E8*nl5Mr$ABjlPSKH(D1XZ?rB(-e{d7Z@?+?2Am>qz&^+uea{DZ1D+&rpegbO +oFZ?)ljIFFMc#l@_+7H{cX`15S}Q;7RfZI!WF@Q{)YJlDt7aljIF_lDvTyBX7Vd@&;UtyaA` +k8*qxe0sA0tz@^ArvF`Z{>Ds9~uu(Uj>tfU$=2JIXr>GmPQ`8MOMcwEmMcrs!jJg3Aqi*zFjJnaf7Kq;51WMBV6OlDflO>IOPV-9S^+4Mm!wZnQ3@y8)Nd-9D+?iSAbPdOpLhJ5b%2% +oKG4E~dI=)Q#3Ys9UDG0hgj~z@=1o<*D1Ey3smC-MEqzb)$75>INryP~G{K2|lol>PF$ER5v;)p}J8^ +A=RBv-T9V@MRlW#VyatanK)71t$ganm87T}aEiK7WjX35%Y@UYJL>jnm{?S|%rHSGQ>1Rdr3@39>P9D +}sN3RpTd3QKVS>JkQ8%u;5OwFf-5#l1q`J{ZG3vIs-4^Q3ce~MelDdHwa=ZPhZYR`@k1R#q=%f^NyK% +cIr{sL;ow{)gPLaCN$rM#LIw?in=){NDZJ}CCn +d-I}Cg{3^>ULn5>{9A>qPo%Ai|TF@bu$Ls0d@O~DJ`m7=5@KPscvJKFix +9QOzA{*SI6s?scuc%H}*iTty!^E<}1ngDHF +accI{gh6qThY|{3`@D&O=&Sq0MFhq0X+Md(urXrQ{5Koc4C+~i77pL-7X9hz-6dAUv*okyDElBzUl_` +%=2zandCbF1PM%}WQvhvg|Th~2}hB}Ycxc#LPi!X%%%fqFPO5M9su#BSM^oP4hv=#)+lgfH%6OAg>t +XPBUMbx#g7ivt-q)G=js3=>OCDWh(SVS?6E+z9VebvuYDWrj)qy6)+F-58HzhrZ^k?tJR@7E@YOx5Y4 +#QMW~PqxBSHO2A&I+bj-bBrq%Lw#1Y&!(;~3jn=b2!^BBU>C-ToeoQ%auX}gyrgT8v`G!e8bz8h{uzz +K}Zc9vQQQbbNJ2WvRd)jxf!US_%%|-N*b++U +Hp8al0T=(n^6BpDi+LAL@UbkpncfRTtZOM@sCKlB#qweZ>-7>?(4RxECl0A{LI`mbhx}8wBOmzdE{R_ +@4)QtlW@s=DJb& +CuW8Fd3Lp}KK(^`WngofsxQW6F+M*Ug@>ioKiC33boF>vm$8Sg0Ft{i=nelj;~I(|m< +yE)LFhM6?RJU1S!X75?s(a3;8?9^VPT*Zj-P2dyE~s0!!X)1?0Xo~ahKbL`ftGd +Mmc@aV6(%z3_PIC^um|b}IyLGBI`da8hzyf_)s42(qHe&IrEb8pUmVyqUN?IVydOVhQQfi?CNk=_sBS +0J?ZoT0P`5>OTVhHJbzjM<+d|zKsX{AEM8{8As2hD0iYaBP+n-@#p>By`0<_S&ZW(o>lPi+CJLb?=cE +is~bvvPMnPK9Sx}B(QnPFm~ZrPTcnz~DE$&smU5p@GDMcw%u;q$lTSg1SyGhbP&TgKdgeOTR2m>X~{y0`z?H&bF6NTIh115PnF;6e*b02g|zu;mC7z$xYiT~}k +v`|W!e}qZ?K6s#ojxe#DJ_R_%+<;Te4LHTzfK$v3IK|w6eK0rrF2~$vGbOu+cX0ZYMRfb5?iq;gn!0x +%b&C!Tv{1K2bc@b>wX6-y7u~Ycr!1mdbox|2bz6=w$@fbn)D4;{b#Ne-qxg6syoI`Dr%&MmeNeaAOUZ +859f)q3T~af5m6;opHrLFZZ8?fhE~M@&LUiX-x9spK7cW|vzUa;mDZPpA{KKb!78BiIMjtO)Klg-5!?)dR$*JKdO5Meu*rlbaLXWlUL+~#N6yAaks +ui#@v?eIMrcp%XS=}%x&2O?~}Q&i1j7)qV6g(cjf&OCoea1{gPUA&kc23_T%J-lrGkn$c +~=M4=H7v;4Rc`c|(q5X`sk2$$!m)h`PabrCurQGo+MR-Cq3?qq^C=d-qE$o8V_a-MEsPx|1uJJL(oK4 +V0}f$=?Jov$|cZFWH6rah&)i7V5UBZYS@hbh4`34RxDU-E4wqWth~eyDW8E&M&E{+lygR@uh*H^Gih3 +oxdT+f$FwUw?%c&!0NV8H#$jCH(Gny1#e!nz-Lmfx@T~JNlo3g>YfsHi;kX}eoARsU}90-4mRZEd)@i +eEqWWgjJll|CcAYnrLnr%l-27?D$m?_{A%V-I&rff$6|H6NGUDny?PYg7OUIaR!V=O+mcdRM7J!Z^eM +W1GPlv(Y;x-TIF)DaT5}g=?y2v`nWyz7(>^q?^6N{gvmYlg`*GMb)S|nl?zy3EQAjCUU*aO96xk(c?7 +=R{KdIY6NICuUORBR8-Xgl4Eb6A0Qu6VxnY(80d1vmbi0)l^e2L^83m!8{ndr7Kw?%YU-sq--y7|b~% +w040yfgRI51w+NxvRq561yaS4}9fUmw-kqYjo2Vcs_0~!fXsDYFORc(V>YjJ%&d(@;7RxB{ffYS +7P_!@*Xd%^&>)82{Zc9cfJ2TM9I~HUcDJ{pBSX8%H>NbW6tG`y=HFaN^)NN7S=(*7H64~)3E({aP9(V +`GmpE8nl5cg(mY4XXZnXAs+^bjWrsGR^p|$F+sr$;L?uxGrl&NmfN!|IXyCNrb%T@-;PU@C<-Lm6eLB +?K=dku)XSw%H<*VKJwQny8QyD&^FF{KN`#Nu`PQ{5871cc;4bptM@x&iwzOk}DXt;UMHew-du8FshqfOdV6!)IB%U4PM(-ivw}dvtAtNU`LLN#eq) +Nbz2q(T6W}E4ln_|7Nc&*VH{X)D5(d>IPayb%S=xsBWOsQ{4_02ZDM^7$#_3S=B +A0Zl8up+fX;_zNYS)y61+vEvh?zAH2md5$&e5P`Bv7SBvVFojoOrDf0~zd|-(KUq$P>WvW|t_LN0+%T +|~;F-)8+4h-n5Zr)FQ;%iOab3@%0)$L*6f`Ys2Nn$-4)egOj7-=2l~L_h8gmURiFet&bYRvnyMp#>!e_v^iK +=TUk5R7!A*k^zX-I9vp0Lt_)5MZ#Xs_o!MN|a6G|V=T4tkUu|5oac1r0=*(cWvc7h0((U?iOZR(XxVo +-7TW{RAzPYS_QC)0fZ=uaOUxEph6>bei&zZ!=J8yo6^ZX29f9bU6_?Y@Ouj~`v?^*a5f&e +DyyAG+nhv0D!uSvvT`1ILzbJGylA*iAPdYwT~F-yRxHk#+61dOd5sJ}8wi9#>XsX5&z*9w@1m5DkYywRciLd$~Rpnj4C(8_Iz$fbr +Fyg^vp#g?G=uhGp^Kf!P0}OgNS#5w(4=1Y)Fzn&voCX;8aI)S210PP-8}xyv>Ky=2)r-*&Q}tr>!&JR +cPOVhE82?tPUW|WB*PB=k4S+2Meh*-qfmZ?SFz{-CT?YOUz(ofBF~A-J{{&#)fLHCgDtUIAtmQR$paK +S73y@dzeSo~8KLyAux(*<(XfHrs(e(g%MK=J{6(#lxZsvBMfw+@9O$OpN?gR|PJ*-bf0Z6RTVj%9`PM +d+abvqpf;=b*48Hk&+A{sJJMqrU{m=jg8h#uY6tZes_4# +Rc_~Z?zkN#r5?2+t@*1YX4m`X?PRBxSYkL?wdihYVTb$`RyM7R8Ctn`RH2!DoeAO*j_&b7+15H{PwND +l;_w>9>Tu{7#Gt^e)(^JDI2YsT+ktaaW#wGZR{|xxSD>#!AF3_#q^TjJ&LafRZNgn{Ud;?m|oK3-vZR +d^tQoc0OMl%$u%Aa78kRaeDrO=REQEJRo@OUE~b|>{5xQ3W|B|71DKkbAo-NP2dMnfUNYeSfNwQjO)` +%E2$0`s{{)cVXm{QTl;3Ip43OVx{{oP+!4g2u2LB2$uBe+_5=gxtSJX{L1BBjJw^}k0^i|r73+g7ru} +olGP&fG$`WkJ<1$C3zq3_V9GT?(`%1;rh?zU#q!QF($74?&*D+H?h4foN%5vu&-W`f>BsJi8nLRSe?_ +gqrIX%ZB1RhT-XGUBQhlSy7DP~CJv^7|Wvswe9ZFvGlTx<`RFsf>K4~AFPzFiUGlZ&p +4>#n!gsPhlJ={lnNZoz?WDd>}svahg%{fBV{Rg)B4?_9CUr@bc2L +FHoteC?UBmT@k5_vL62lstH}l={sHMM{nB}iN&JHr{UBt+691r0KWMR$iGR?cAGF!v#6RfL4^+)fN@I +JA^aE8kcrpG#kA9#kZ&KyGKK(#_do@kv1Nwpb25XwiN$zT;(KMCQL#owA(^O6ms#Y9LQ#n1XT6Hu{pXNiWkAN!g(|l+J5>VxRnh&i)0;;@E^P!bUK$Z7tKC~JMsPaC|hgKv3RomoOQT=mCON*oPGYKP6a&vMA?ggsO +${c(6)cqWxae?@gh@9O(X`MEDc%IO>x^~*v4V2#LU}k?osH +zQD_9CHiZEcL^ON6T0Fgh<2s=|7-g{2!P+0(|Dy+Wvp>eGg9poC8gSN1BQ>JG)Hy+){t%`rN!6RK*%r +@cXFTw5DGyh*644WITFp>b{aHXRzeB-386Wg3`1?Tq&KR +B`bZ|+)SnZ|mREd1pyCwN^(CRIuJm%bP)nh-_Zw;{gjxHBP$gUw@LK}o!qDlHn%LH`GQ7(2IoXzhe^=92{yBCB4of)D}v5nMJ4#6kP3NgsPh{DVN;dPz +fGe7udlpJxi#5*ihkf1S@%t#^yS7g}CbH30BhwS{}bZu$n*c(HIOIY9#|g8ox-e{u2?7_)7%q +KN2yhFB8oE=)FWlHDR%gUm=*w^l?nX_b=r8&WDfQbn~Ic{>Jf@wNtCZ8_$h~%DO&2935U?9dfF&+|`X|R!`7hN(rA5YeRFxZxpaE4vH$d2&yOzOx +)qjh95N0AA|0LJ6hA_8n?JYKCKKmnI8km;v21Q?P2PS#lo`Tv(Mke8=b1fJ*17sxZyLIQW|JAO +nnyVXZL8W+As%X^cf0Uov2o%IH!(b~I-$0;k#Q1k9qpe1TkME +jkk4=*aro5U?&3S98&HuPcRzLf5_>gi!Ba5w3F(7$bCtZKohGMyS;73c2#uu~H-fT(&<@1fWDoFS)==^Z;}>tSAvunkB +%qJT~z~lR1QcpbVwQ?Y&E%63p$rOP>+U?Y&E%6Wr!Tp6R=j=P2qJBx45Z7=--2OJ5V5>+k6xg#0}dlJ +zJ2JqmRC6aF4$H#!T+WKBxZWV0rng=DfOrD?KRlM*%AtVw4fnXE}?A(^a6DVt2zq_dDr*0i04q_ZZSg +=DfOorPqwCLM)jvL+pcWU?lmgk-WNorGkvCY^+2vL+pcWU?k5g=DfOorPqwCY^<3vL>B{WU?k5hGeoP +9foAHCM9yRS(8#Z*{n&aoNU&#ZJhw7tVzk7Y}OR=KTu89q;yU;Yf?HVn>DfB_9ko6+xs(Flae`^tVye +!OxC1Vg=Dg(u%DAPX?>H)n)F(bOxDz5$1PCSq?Ju3Ytn&8CTmivCYv>BO_Rx*l%mOGO*#%qK+2kQ8j{ +JHl$6P2O-jgQvL+>CGFel*m6tVX4U@^5wr!FGrL0N&B$=#fdsRn*Qr4uMl1$dLoq;4MWldVVWU?kDU9 +wq|5-!=Sslz3=$(odG$!1MTweYOzeyfWt3PA2#SzBJeZ)>mWV~4?o%Mc_l1}S2em%Vc5QHZB7#C04}l +|bZw0Z>Z=1QY-O00;p4c~(=T(R=Fm7XSeBgaH600001RX>c!Jc4cm4Z*nhVXkl_>WppoWVQyzeEeI!b* +@Y{%F7-ue +%`*afmFev`>nS|`CP?$ucRid{8p{56~PSp9vh#)CJ$607lSjepFpB +JN`RU@XToKK=qwFJDhzwC~Jb2@uF_}ekZA*UO*2(`U +wr-@++K9(Gg<;oX*kU-6i^4Eui{Z(27=&SbK`s!6odv~k3m9e))pf&8CWfmP!xqDIfkk21wHP+j4TIU +%4c`EU6)!ig$E9e8tC<|GS`J$dqZ4a~cP59;u)_<=VFkvG>$qq+Y&mQ>Jn2K`F#KPN!|>~dau}C4D-I +(~kmRsyIh^Eho%}EdpyjaTu;uWik9Lf%6o(-j>W6W8({Q+!&0+MBhr=N56dVT1lN?4D`8bT$DGt|6;j +lv8#&x8#9JU;`9G>*C-SQFwhao3940Q@WoXugtC>Mv(MUuln=v00fUC*7vwKy-qkhZWK#==Q)7;=ik_ +{(xC4x{fBhasmp3^~PN$SDp(PH`A=io*yeABWLNio=jo9EP0YFys`6A*VPDd3$hp_I?->JdGcQ#m&Xx +0{k#=ndC6k6o-MuJRAnkQXEFl`TQ_CDTBlK$b5b{<}hQI`5eYlD8mn9xu-Y`IiDX!>wJC~t;_Jk7^-d +JhlP3x$SDp(&c$IgF2N6@>k|Ahx}JA_7(M6Nr?dItbbh!-9A=O*pThuGJ`M|hSl}>PPsL%u52KSAau| +K*1=*DH +;2(WABTaRsr)cHnTEsYBUjxT=B9-Fu#lI)epslN5I8)C{4nsG&kxh#5}YsQb6C$0V*!^@ +FCpyHp>;kE3w~IS!-5~41BWr*wq9auq+-?7dWmd5ynv52>*XcD%DLwy6iGI&1Bv+@E_0u*ki3MzVdL` +>xb>#WOU!}8!akiAhhhH~D?hBq;qvklW^lO7yo4Tywdy4lGB&Ow#=>$K0LbTug}g+W94<32Q3!_%@x$ +nQs=Bo@_vx}ZT*y9Mc3#35Kdi-JEkCT2AJ*bEiq_L&L&ySkDg&^~2`)VO-!0{j +grW1TJu@{II}b^j$`Ncsu!FTen7C%*PMoBd6J?^WwTS#(*8d;WG0Qdi4^5AD%;A0&+e-EaW8$;jrL`_ +3DQO4sWNrHGE&qJ1`Qai +s?8O{rx3Bqr7_za?HA9|1hxMMPGu{su!ePkUAunMHhoNp?4hw!5@>J*i#2jY#>B4eYsF%=tjzjRn0*8 +hA;W<3VA#fP-{Ga0x>LrBdIP^FSdG7VYGq0Br@)EcXBhOnaV4qI!ISyFa8P*T$<%j9*bo_=ipTl#=OQ +7`(_vse3UP8|g>zywl*Xa7dMZC0@ +6)k6Z@Y0=s2?t~ZmrDx@Eq!g^*F5e9EZSRp>C}ZKb+0sLi}(xhfDOsrsO5ect4$R?qwl)34z0f_~H0D +4t9%eHx3Jacn%yE@)81v%dD3WI1Jg?^Ck2+TtvMD4dz*&gHP4A2x%-1=LFj94^xj>*a^_{BXQa$JV>~9ELpq^M?fv +LoVa}bcN(4^f+8*UcwkZ40Su_CCcM4)G6}Akf+EG>v7na^M{?7!)##}!(rDL4(s{hIdE9d4?`}4!;p7 +kUP9n7HlXsoj{|b9dI`w+IE>aMypIENuJ>_3F8qDXkdqvSnyX$y;4oUJI1D+(VaT)d!%kd3%vSJr^TU +?ImcuzXEYwQ~b!%GhMG`oS#?x?E@WV6baCW_f0zNO7|t%2m^Frl$LaLa5_)!6?=cP`EfF8L#+I42!v_0yguQ!y;qVYHqmJuEy;S7Lg&h^jSH?C|za4+|I;(h>rOh11sJsx`J2tR +1!-HkHGAr>zxn>ZRd!*p$;HbnGzH;_WcXB4%iZ1rDQ=TpV^{Kg@Ay{jlY*p&W*6YV~mTHXZtyd-ZS;e +psh^IQ#TrTRogk569KRY<8_5wj4H-$@V4e~iPaKX9M-eLp!Uq +R=?bvJT4@O#JFK@&CvaHrrAQ^(VJAKWp3RKqu;sAj@T3oRdUL*~IM90D97f}O9L|0zQUM&!_QL?(yg6 +)4TEdB+;^0%Tqd9Ez5|+c%hn$xX{4iug)5DOb$x9f)VW|0@;t +pmb5=Eqk3)!VZ>ly9RX<@jC>S6S3svWMyc9@Oo&bGsr!zOdsR!i9G;XlCPGT%)na9Hmtj_h~S)d|CF_ +^cha7&aBdkf%E1C)*B}_%K}|7}m1GC00vh7Y}d$hv|efegM{K9;TzC*7!hKH*7I%DuzwZO3d61>t!Vb +49_?#Az(QB&2(CBST8G4X0e3U5kK@oBt9e-!xqC9!;?PBeIe59-LTdp99nKzr)EufL1=bTBKrlQWxC- +)FsxTAL0JhNyv<5j4x7qhAu9pdzNon}r{ply`5r9+xs2p6-Y$}Jhl^+(;5 +(0;{o@hqb`TQ_iCpnBQJrfUF)APf^YnsdS!+P(fql1U}wO9^Y4lfvoOH2>%1P*7PEFt*e1>`XLF!h9= +dYqSFA8I*lIczG2_3{#MboNCYm9ncPvY+9|<}lPLI1Gf&@I@R#UIMMBIsiVdT4TLi4qFbJ%3+W-U)7r +MLL{`#Rkc<`ei&Vs@FEU94r}exY58HOi0W8TrHY%II8oR9We*RQiKa#lO^BA60t&%4pc_^?UuZm63mGH2MAT0uOYD!(R8Szwd-Ux-jfd +x2f +1{41TVv~t~3bDn+KZDqg#hZ5D6x};j*79cDP!1Dsfyg8JHbfrLpF`vk-3pOMvAm5Er!p2I|}Vb5hEQKQC0tX{9qL@eFO({>QCZoLK*v1q*}6R~1FK1cf@vN_uQ7a$g&qrZ +g6=V%!spQFEm$mi%g5cwSaHN-HYR&<%SK~q(g)#y9zc8F^Gs2VZq0K_n$cBCDI7RJ+VZE1%2O{Ue41{PRkw#yp)i9t|v{+V13Vi;00x~4S}C-+a(^j}F;Yc3MWb0 +~Ash(3Ou#4si=>Xe+1vK7s!OOL!1@R+N`O4cMl +`p-2BbBec2T0|M@83z~-$th{HEC%i9K<&dzDqiD5XT06AFhrYh;ObSJ>Vn^#Q#1?vYTlB66v*s{wC@5 +g#HTYUP6DB^hWkoHWST%P0d@`&D)9Q_yg6cS2LT}oow07mTTE^JzGu&`Yc^aBcVS>dNR@%NKZ`bl^01 +*hWcORzmxO&6zNVvf132{V7s4!EoaZFm%V}$HssW$F8xU{u_8635*{KsjI@ +DF!o#HU`*ia)lKG9g`8vt`PThQiWPYn|(oN9d_v+?bBy*>=`8LViYi%M4PyANC;pt| +$+rqpT>nY33J@3l1gT+w9#mqsvNUycOdwF6rjE9R0cC1xh=k@_*_t-m{({snzIr-2?J8%3zbPm;?J8? +SllmN~VQ_VP+ViA_v0>OKG3_dMQ^TmeK&px@G98qbc9p;JpuR+^iVf<^q^hi7L@dflyUO9z@oD&Wa!gOk)$ws}kg6sQEAvfK!{EGhlG;^9r-ea#o76Bk57+PxsV!#og8wF2c^ +wxM3TU{>?BMSbN^`ptw}YX0k5qNzrHk*}Noo_(eL$-EE6Bwq|C(ek&ggzfqCyKM=_69reV5LAyA!LE- +hOm>B?&m;PM&&B7lF9SUNc(iDry4-LA4T_)Cov-L7&o(Ueo3+pV*`zpEswsM0SKaVI_opY<@QYQEA +nAFlE+_4HPwJ8>|$F7Ve?{-pu+F;c_8n$awb|ASN&83y5TQq?zlG_;fppJc0D1bmHDFZJd6z*1Hc`RM +V3hmKU1D<^yXv#b8?mq)(JLZ9?UM+YnZ>dh)az3z`X{qA{xdH4Oj{>tF~^*!qMcW-Ta>ixIc?Vr8SJs +Wm)cz(PYcGd>#y;1L?- +x;dygXQYpcFS#r-*Gq4<$b=t*6FUF>Ge9R{z&a3EPuDVy6)ddU)|NUbKTSaXgRcy!6U(=!DGSW!L{J$ +!4tug!BfH0!85@xf?o#D2G0f02fqql2wn_c3SJIg30@6e3tkW22;L0d3f>Oh3EmCf3*HYt2tEux3Vsv +(HuyOBB=}wMY4BO_dGJN>W$;z-b@2P~mGO_q4~~B_zB+zr{P6gv<44AijvpWYEL_01EG^w}@_47!^4g +uI-RrP>9d@t7?seF`4!hT3_d4uehu!P2dmTGwujACE7CEexg=mq*N_mJDd90L)Xw~QkN!Qe>(+`risp +ZiRlD?_cpdTcSQ>#foNIIuhi++%_POUcmAQgF+evpcstZqFOIo+Q1ROEDv)>DzwZCX!7PV-SuM&73Rs +3#+D(|pvEk+*3+>dDC4G#~Y34cupN +=Y_d|-?4KZmiYG89pBv< +9N*~njz_)QmF;`OoBiI(k;>7pAFW*LZ+3e(PE>Y>*N?WOva)i0(7#pL?rtAjza8~9`-5ZGZgvKn(eT* +LhWx*sXt+J-Z$yJi_ttiQFsxioI@pR%-Wx`zlE1zj4bS&Cqpg*d5UR?NusADyyBg%jAtNRVsn8|OlQOeY}SYy`p+iYI43g2NLn4LHA_gE +k7pxzfVr`>ZvauMb%3>{N|EWHPJfBi2bXE9c`(W_^6Z8fPGjf55J=tnsJpOvp-qc% +Pji^^9;2@3SeZHCcN$4YdYq&*q`tV(r=3)&kaERr>ss>3vmsB#Y|>}UYyoC7;+mNq6y&q>nJsde9Zt>cz%zSUnC*S`qca=olKAZ6m`&^~ +htHO9-8;ZmOP?K1&1{=&B;RM}XCvX%K3jNZA5LaN^~^4w*|W_xCBwr95LWFdSuF +@8d3Q+tislxH)770K-UXOOeqQ)A<^{aQcIZ0f@^+cO)j=jXE<q9p+*i%RV%0L)Nejd6K0gP&I5r*02p(!#3n +`Y@?LPu#G;(u?=-1Y~ur`+P)51!#3n1w$-5ZM6VoyI?*dfpcb@!-4V7WefAE($NZkCd}s$2&<-r19aw +m68)ygKYcC75ahc6(y`KTvF1)rKwDY~T6|~V|>JMhr<&>nrDn} +DQ1XhWTu(>6fc;Itw8fMnEqe$cMcBJDApcxaC%%tO2E(1v>GQf&v&hPr6bwu(oV5ZV$Pdk47Tp{*v&L +)$~Uq|k=!A4R5}94e|KIhO9vw@g)3$Qji?QUdO1o{LCw4Ff +PMW$UGw878`9nI_;UqBL%J=DGRjioVJIy18B=jYVW +`m<)N)6%tPA_+Ov;Gpf1!~{&3JUTc@4Bxu(b$M)|k=6&R1u3#0g@i4bb+hN6G^2DnXlHBOcmn!sZ2Sz +;7z2jZUV5Hsr}NZ9_a_2W;(vj~Jkh#29`&%+bSM0K-&kXz>U?>pip8e0|k-M2dW +7LoOoH1}QaWL!Q6Uc9M=*F*`pU!NL!X*^o76L!M;u2vm*PkTqsQ)|d@>q7{FTHD*H|XEs!g*^tMX4OL +?{WR2O7HD*JeEFD2>joFYVOGnUJV>aY*W<%AO4SAf|Pzz$Vuw*v1A7^$JjojtJVs&QDnnZ8c%bm}r;BX}f@S +Nu0LbCUz@mTdf^&0d2!7?INLVaN34M+ptRe(4-^uia&mC`gBCi*H;;B1GAkZ+J-&skY|`^8`k?dSvs; +r%q}n;F&OQVC)x&P8=gXzJkd6#BSjnSFrV49v4)?8%kHy<3uYJj6mkwLN1TKsApB(Eh)=X9VfJD^g)} +hR?kObXvNF49pKW4xjiAlWw`GU6lW?Rwk0A{kYYd5YdDdtbXS59)YYd5Y{>BmkR{t#V+cn?25oj?d1$K%E7{tSIiw>?wcc-;9z&KT(Ka}3!y0Y7$B<=NJ2J1wkfyaGS +vJQ1|>x3Cxd7}Ax~ +CT6qG^~_fDwTzLr!Du^)v=8ml5g%#$NLx+93?l7p2Rl0{Jhat>E%bUn$VI&1&j4+-p7+;7qU|)V9m(I +pjLPv-yUA#0&}Osmp{*utp`ks8NZT$Qnfc<8nMc|#(vih<+C^ +?*cae@biL{G`HtE{CqKMh7f6r_+U-Nnl37Rb$vwb=;5wpvkjyQP?=_JuE%VWrrZDAK#q|H0>&>l_Lyd +FaW!=+6}AkY2l5AxFysP5nI2eqVMe_%*Q7WDmoF1D~6-tU*6jzBHRZb(CtEIJg479J1s_+px37V6>g=tbsf; +qg}GKBO+U*&HjOhwwf^i7;-VULwcuOQm4I`k@mctHt8C)v#&p3qwb-tCah%JA)P?mu(QSv+IBl@(8;{ +t%32(>4Uu+PpzUH0y8+sEJ8SILju@PFaeLSchPJEckXby!hSx(|P1qwiZ71;v;OF&42n(Q +`@S5tGx-zG;jNn1{BSuw`65Vi%8~^>U^x4{bGJ^ +MbZ_+GTaxP$zTRkjuG=9qJ67HsslDVmCN#G%oemu$$tMY;O%40uOC9Vaw>Wi+o*FS)8^Z(=KikyWN9G +7q9rsk4I+zAkqNsB3F;Nco3Nn?FCsqk~wWwtB1Cluw`65a){sYR~)npj7Lfi?eZ-7oBf+d$O+H(L1e~ +kR+MM9ny;^7c9_p>CyPhym<=Qz#>FGczf2qQAzY>n`LIUXroA-*L7N}GhxTZ~=Jn +JfA@)iQ0go@Vig)vHHbIc-~Nldd^!Ms4lO#)Jj6MJ}~PF11B2wZ+t4TjWwZ%%ygiOKst)y?E4yTF%Wi +kY~2!532v(n)Ju~y)}>*k=nIpMs4j&5b~)lW=idRuPrR7Epn+XJhhjF+WGs~XYREPYyJ#g8}dSVZL}_ +}*LJWaazUuw$f$jYz6Bwl+CCv!Kx(^4NNl_|)B?PA$*El)uk9is$@kh&%T4WiE2Fl5JFBPm;!zu_-xO +(|kU(8DYD0FPkj#nN^we=euN`F6)^(Wq?MFLlzZ=n3Uv}27-02TC +(|;%jqjI#;PXCZh(W!oKI7qt54ox2SDg3E&zO%h859pcB##Z$B&J!nAu6*;t>5Eq`uZyI(^|NPAUOID +S{nXi~F0DUvasA?@(`PPK)+&3W(UBuluY=10xGctHq2zE}4wYOTm#a#yjmtG9*T?0$k{jc4L&?o?xvA +vVxZF~5o6EQ-iL}U?QgH_qT8$gfwIXqUPT#pnz2M#`v?6hb7Fw0pp^uj-Xu!wo6c}+aUZ}v3i}tvVF% +QQlRAA7<@oEJ|JshuAVA#X)ITaZ9aJ*iDfe**)6>aERz3@PxRxd_B)au3Phg!YxM^&v}jDJ +s9O9DiEto{2Ig>6CZHCCvCm +SM4Yj8K1U}YvN>A)JrIk}(eFd#bF>DL&(R-1CzQI@3s`7JUMNl7pUnY)lBsq3jvVp38=^|?)=`~^v3{SW4`28X(j~anb$X%Si{RxXx^b;NypP`pM +QR%Y)y@CDM6$fLaN0b){|@O%nKeBRN@O{AEy(YZEDH@9(BC6nmI`eA=3hycwFcD9XQay;RM0+9lB`tF +!5}sesgqD3X3bMCAr1^o>Lx_>fGp=z0gKCWLKU63oD|xk!F`R6B&oAbzlXg}a#Cll3HdFOlQL^GSZ|X +oPgm*$zY+`SE~+HIOtQR#;jFm-3dw1es`KB_N2ZyYz=8iu)_GcHQ!g`pHH?~wr1$@!=5jto>p*ikpW? +v0rG6s}4#;vk!voVMJx3M4gk(9INTWkkv`?p2YCJ-`3xNts7cr?LT-|@4WO>gHa0d6W826o&C~<2sK)(`yZ2>-t*LG?SDdYde=iIh!BLdBMBKs@~>YdS)R3!_y3*bv`kb_7r*o+ttJf?- +EO2eY2!%#lhm|I9QS{boZd3fH(iC&p$Hn;&q+=>grWL^ctqOigq3~0*(jswuKPFigSw+hE +6O!dz2H02kQS&;)i)pDtdqCqXZ4Anw3|B}WQ&opX5w4I#7H?Jd5~&h1 +YHBG|55v~MeVHNKk4lkxm>L~*ol&A5rf(sD`YNd@MV0y*sVu*Loy641DfJCfHWQOm7l +OeVf#zE)@F?iLx$zGiI3X<;KCjOR6jk{JR=+IZ?4ndFe9G!_54b1lz +jf;=`-glYn990-i@v3c8wiFtaIKA(%C?e)&iwQhHPD;mnj3Txl!Z0$sk)LCb1`(|e&8m=Xji(iaiieHXjiN6z +nH-0sKEq*Ngpv<-8vp=ekO2TG0001RX>c!Jc4cm4Z*nhVXkl_>WppoWVQyz=b#7;2a%o|1ZEs{{Y%Xwl?V +W9x9LH71-|wfGSb*$c$yL=mPXYn5q)1Anm65C%g2^({j-`oLGt2BqmLklNgL%k-KztlR;y46{K=26(@ +o{j#IK0@|d8KcmPeNBu-P^a@w?kjobk9@In?$$EHGR9PfAjDEt*$-tsb}u&51+Z->p#=&->vQ4AKe=C +H;&Yfefn7K#$cz{zj>l|XY|ssmW^y|yfhr#uI=^qj&I-X_IC!u<2P<~hCAKS@%`)e|Lu22d&9x??y%O +oy*C(+YFEP!cDv8sA9YWMhp%==mj*lC-Hna#8-f$H=LY+|QSWXy{FgaVyV!fVyL&(U|N2DjT7MV*rB2 +kgx;HyfyN&bLw$7cpa^ck0_Qg|M=e93hK6CbB?PTr2MlJl9)`{AKTJ1>f=&7So0D|i2baXT9RZpYO#? +ksFMo*Kr^%FHSjh>04fx)(CNe^+qpCLW8v4?Y{hZeRyPoi<#3#8uw+kTd`ZD8Bak#Ex&y-3=|xm_aBI +JYg*LlfIxCeirYo+Hr!qgP13ar7#QHZl4<$u0i2Yoy=!>U^HGtz-1}Nz`D!Umzoo1K){*I_~y5Y1+W( +4bnC~-5nCerd`rOoWo0`X*@@6lD6?2xfMs-80?XzaR*^GQ3zf8_6{IIQ%v_ZE_~>kZ?T?ze}zt>HEjznv&Mw`30Er^Xbh&2mh!-80`AXY4h6$@f50I_C4tXU8%7Q~tbv0*{1SP*L## +F_=M_6D(HL993s8y3Wh1+ij5tT+${EQl2gV#R`3aUeGNL993sYZk_EPJtLYNr4!`DG)D#1atuD`E+Vc}1)QidYLk%qwCEh?PJQ>k>iCD`E+V1&UbB8N`YOvF1P=*UuE> +aQmAhv9LkVa$qirC*HmRJ)KkJzvvmUzUxB3=ZKm<6$BL9AI2YZk=b9&zBSh*@hwb099pBNkW_h6Aw{f +VgCjI0M8W3cWy_p@=g;T%<>w0pbD`aUDHk0>Nw$D}f?b-XNA(6A}>nTNAt@=0Pk`#L8O{ORR}9Jz^sO +@zQ(5#VBHbYk~*yEIi^lfLNx8WgrHlxjc{9^6o(zcjXuB{H=+_1u?IP%e5v0i(^gT@-+gs*??H0h>Kb^p;!C}RIr6TBi;EQrfoN32*7^VWo6LCku@DnE#QJz +~v)xEPPvupm|}hKLj86bvEygcFz5X-EI27s7gX;D0435d&GN6dqG*_N*@SFw&&#DOP>1=a+si0AJSOBAuh +nkdpE2F@+)3mq~LLu(%pTLL{u187k|T$DAzgIHoslndfgm#;0?I^tzoH8FohJOht-CLo@JB9?(T!z0e +HCNex?ku?$En{@;=vw5L|FV?NHBKG%)%T>fIh$Y23-Xks(#J-A{wGod{Xy)nhy}~n3R*Q$sv<7LBlfl?d=)WoO-Mkz4$Iey1hK4GC$lD+0AhlVMR~KXC= +gd~*8LI?`>&eN9EddwV$FhBvmn+Sh&2mh)|xOJi1}3$`4zEdL9AI2vmUYLK+Lb2kbqdTAYPPJ6VSTae +Jutt7V8KoRvp9=MXU-^#5{L9xzP5$h5aaefdNrHB{Yn#cfgh9V9`iZ}zrGf~7D)`UzE +=I&BQCXA$Ag%E#hG0-p;!>>A{4RaK& +%9cI6sJaMXbC*EGgE3A6osXB-cs7#X8*dhupf-SDaNrideBARs|_ywE!UI6)_KDe?_b~5U;|fKZO+QR +=^|XK`io!i&Vt2V%7@V;eg|Cz;T#&CIXhj0motXZ-)jfhxtF^9IzbLEQ +k3Q>-a?zGK(0(EAqEP%jd90i$AH;?OvG0p@h66Fb)dcTMu +pnjyF<-0WK`e15Gz;Qc)avE{;*9q>Ae?Vscxat((FBC^ZN6rB5Nlo#dG-1jFu|N^CizZkQ`!1T0fS7%;j#b3`8_xXdHGdGBEM~$q!=Inh=24a3E%_32#MQkTt=Bn71aD6vQ$`4B>o=7{cW#V%&|G-XRr-r)}4f!@M9a$RZ9{4p$ +bjKoIjBHUbVWsYRT@;U%?*4GLm%GgKgUZPyXRyddVC32%#-uhkV}5$pVd*w>j*`9Z8X5c3vsQO<-~DG ++A}Vi|}t1aSt4GXyax-+Y1?7fsMOZQwAS$`yyFZ7UAj +&rB?0p2OZ2G5>xu>r9kr5u3RSVty4d?@X+gMJ#*2IYSUm-tT^o2Rvfk;sY6`QB4U9t;VXz)4lk%fYy= +$Ujfu5*n?q(y$Vzq4+K0pVG6$K8tIlEV&0)Q69Om`#{EdluuU+Fg%o`K=U*@PPbr@daHN=60!y5lM2b +p!N4&q7>ucIJlD|G@83ycYV5pjNFf`6;d-kvV?wla4Y&KDQ>o(cw5^cCdKEZa>PvOr9G>@z2`$jW +z6P=6rMfj(F_E$Fnry|y;=ELc#srx)6^LEiN)X%6f^7HWYfK2<E6QimAOTEzaB@Rj)-VrcEPAw2FfK}NCyv1_}IA +m$aZi9Ji{?5cYE8>MI)irRJ2{L9Ch+W%t1Tn9Oy|-P{0uU<}#EJtk +zsrOw4aB@67JyiDAm+DS;}x+IfLO60R?7+EjKyn3Dq;v1p@{MIW->Yzh+W%t1aYZ~xS(2{?{+`S^frg +YBj!Q8q}IgZyv;F-x9T){#AF;Q5WBV&i0!BJeb<(L(S%^%wQA9X>tq=gO%(WAT^&J8uXr|zHA`a6l2{ +8!tXUEpmc*JRv1UoESrYR*{uq|TnkBIjkXW-MHY|w^OJXe`u@;b6b0k(Qi6!0yD~T0LV#ShJU=pkRBx +bz{fh1NOiIsrFzTSjlN$l@U%)%swXZ#XL4B-@sA)F#Hgi|Dj@Z7zLP26dMUfLo_tOc65BJq^OrXq2a^Vy7w+F93;g#|fIJl*Fpi#H&tX-ksn{Tu{Z%R} +-@&F27RM)^i8~R$*+IIgNbK5HB(@*vJ9exl_OIBn?gUR_Uro%C*xM&&-|P@<6VGd6e|KU@N +t~gHmz2btxJ^85Uy<0gtw?M?(p@pVNzAV&7HDFY#O(IOzM7c#iHjj|rHMUAEc1zFB%Xy&T*rOl;Xu@T +MdE4Oip2IalQ%o&Ph!@c5WLwTa3>T?;zfP4W3@;O;U&_^^g)JGwLMN8#`a?%%(CeXv +`5a_>(6#^_FG)Eo4la@`KP`%%BwySoG1*`W5yV7L?ghc<=Uv06L&N0|NTL4Pz1yNOTDp7&|^SM5?~Z_ +l34^PTIv-6!{-Ig{?D}U%0xhOF5F6nTQ1zDVYrneMONQ*QDBKqslNoAu;lLBp21P4c0j +875OtJrXp-TsGUu2ZE;cPg}{yfu=39x04S8%`g$j10DsMwldU6J_s~z9;p|+o7PR;Og;oO?ZSzA@)4j +J=CRpKJ^-}BJT@Ci`QHljXu5oV7h+)^o9_JJfuLpca4~osXjy00eFr=Yw8A{t^6aiML;!<{ocEVSv|`U42kx%J|U5T%pr4n_@k+;K2NP=(aL7yX6!8thS|88*u$7jCbghYt1ub&H +7{e#j?FZ-i!m$rQ)^hXRTOSqV>uBfX%8>owX6iH7^?KgOUf{kddNTUZg{ro}$S8XsHIq9gH; +@{Dx!1nDltT?~fVG+dXjpx_C!X}Ifi4}*4W+{L($LHnGSdMRBSI@KOvl+Lw3!6=<b7!&cQ|l;{S?qJ90 +3dk01UM#_fY9-06=WzJqc51l(wU>}&1Qa#KO`pJH>HY||w>iF>!X0@EHp{BM+_(S7EUonWrfE*ecYWh +Xztu+6pUa-IAF!#35XOLY?WZ8L2)(`O#-DAL44zlF_hqRlqMzm0L5X$v!b8{=W7aW~(^u+6juqy6yv5 +Vv}wfxW+pVVh|SFU&VEZc}Xq>64GPB5An`^Z50+cbjVqt_%*I)fT#ili)*)+gw|25=_2>;V4y?)8t<; +98uGq`{aKyY%^`cqbl}oWrl8RxDPt`$PoQyjJ8Yl(O+WNX4-aB;1Ldol^ePZQ{XiW+eF(g(F2?rR&wa +JOZ4z{4BI^0F3$tJ2dwab`Eu|>j79`<*&qBHhNCoH_6J}YXe&UBg6#3Ovl_%Gm;J$yFl@=HThHaYggLv?B4BI5($Lip}Fl>|5F89Mf$FQZYhTOl0;poQ%vcyYbb&G*t@Iyp}l +`aMpz$3hAt$H!=>vM=RJQ6VQn?3mm!!}F!0h{10Yc)(A9Cw_sR>ZX6uT$Y=jZ_T$$REaGJCI-%;Vh1X +4E!!1BHyf-F$S*FUtrj#Xxw!g!)<1)qA{Rz4*v$jRx`Uk(6BR+ssUMZ_$>_EaRg;Q{%Z`|aWruC-okL +0BmC^4>1QH!6F`oCi(#8%;94KXVVfgp|HH3i*yae*ck*Wtj+_%v;uB2WN}W2K^_v)t#134iZ(!KU9r! +K5FSL56?lMG6(nPKaJRyw#0mD&>Kv%qak?etIk;zXmY*k#^)-+c6z|ZRhZfO%qADB#UW7JMlc%T`77s +EC|c;=dXAH#MK;Spy1Jq+7<3YYHhF>1LA)8ieCh6%#Y@Hp<+=C=uBg@a<{4+w(s4=@_$2S0m<@Y`V`d +juYH4sigj^Z}7D{vk%A{9yj$1dD_ZoZPz@wduL&;kPjwrUyUy;~!zv-i2_dd@Gm#yC30 +F|~3BeqIm%4x*7s0uQ%`Z(!ICA3Ot2UdM2h9?YV@!f+&C-50)((J(#GOXHtmG)xZ$WDG%@c&MJ>g_~L?Ft`2#!dB$KO^gnBWDM6KzxOa|^Mh;u5k{l*po?E%m^vquf5tG?=a0g(dvz +nrP`-5e%-M^zleMe8{>|O)v-d|`>q=kkjxG&$y1P$W!+O6v+U|F5cTXODrPtpXyt04H_HlGt29*!Ehsq=|Ao%=8#~>XZrE+$q{lVUFe{|>Wm&dP-zdHWf_|5S*$8U|_9=|h +wcl_h=`{NJBAC5noe0}o9U1U{&y_3LZALHU$s&9aX +`jDtOeYg2$0%FL;1k^5VS~YN%<&L*W4wQPY|Sdd4kOQPZM_!2vQY#4$wDCYIE|%B>9X!w`<&y9KirHxaB+eXf$Y}{A&Kz&bX%QsO9Pi9&8 +6?ge$75Ou!OYt@9@A0?X5Pl}nASot^EQr0R19fi=4~90Xvgp-X5Pl}nASru^EQr0^mj8_n0Xt=BT*6w +58!yvnutAsdu5@nI_0FFnZFcKcX@kmrg!UH%SiP}hb0LLRy9SIM_PZ+d55*~;jF>+N%NEKu +D6%sVW6BUxU02ftApbt@nB(C}8sgT6Ay+nm1F5=~@ki@mXUs54~*_lxxi7O4WuaLwgg+zrUuDm5GByn +vlPlY6|_T{ON#8tgSg(NQD<*ATNW?vzJXr2lQ@TaPftb2t7ern6BkN}|aR7fD2t3m?#B`YMrk6aZJ*e +2VfLINEpDpzR?BViB6Y!AoGC#-19t~T=V%EYybiPCjbBdaA|NaUv_0~WN&gWV`yP=WMycaX<=?{Z)9a`E^vA6U2B^hM|u6OUooI0vI(-cdv<1alqiWT8&J@NNH~PVVY8CP@`lx}vOBV ++D9W=o0wmjDFe?ED%>8OE=4!6ydBpSN^9(;hza(96_j&8Bt}Z;qRL@PdU%;nZJ^l9O%$alQt=j*#hcE +AJKYVt5^Wom+(~V14`%i6c?%Uru_>O~(b6e-vH!mD&T<$-4a77{e_C2}1b+K`2{nA6LPxm&@Z*4zx?y +0ry^S%BgXjf4H}CHT(a`p~mBz8~CrKLyeQY3v0CBzSEDNIdkmD$<^aWPQH8f#HmLgJKi|lxVEp6{WUv> +8rK?){fz^IPaPlx=+ZkaczO9yBPrldi@lo~ep&3@*6?QpoM?DYz$+TQt>Kn9$Q=QTgM3yTB-QZeG~8a +&@aM&Oi(|hc4${@|7sTG;1ivWY>`c-E{*r*jrFvDsEe(HJe58QCBK8*Ld{yi%%K4hOCSvce3s~HpZwO +d?LM_h05k>3??s-LRY1e|F2`=Uzm4E{iTq_{gj6nl%0{E@hV8hu@(skmo9)^ +M_<;hzY&tKpvtxT)cviDT=t{<%QK@<$C@#KcQ?Ym*^ITmr_Afpj +C_AU`O;L(U1dzXkC@L0qRcr@b1M~+6^*t|G*mz$M}aTq173CE^BLB5uGX;>HP%McjZ#B5s +gOiMVl$5^)1A5jWrxapMzB;s#-sh#SW!5jS9yxN+7c;>OM;;s$IIH;z#vZtQFlH&BzfQA&xpvGXXz4c +;+{8|Y}njlE064R|!-23#U;z+(|N&bvh1VCWKYWA74iWA74i12&0Uk?u8%bDR2`ay;V3-eW25I*B`o; +`Sr%DJbqbiQA#L{fHaz=JQf@6m`GdzXkCMUF+>4#izf+=^YVS>*aB6t}}F!4oxa#61PY?MK`TrnrMxB@W^SJdWaa5Vu2dR}(k +T8CBdU(Ijr5QE>;fN*u)Puu2??+hLU?Ox%J+gIOgG;&xaic+y7mxTm1FagYgGB@V@1Cvp3E+(E388Wl +IVXEfq=c-#)+20RjR<3vXCxFd1%E%a90Qy^}f$>gjO#h2GCy7X7vF`OJ%apNE*;s!jLRf0EaG~%AuaM +FvovGZ8O?G;Wsh&x0$i9?6CN~(z)r%@tq>}(P@7-kf!#6jGEOT>*M*FoG4s|4g)M>q+19L3$zR*B$6f +5klotHh7E{j3te6I9%Qqg7H=S+7;%@VIe|(LC$6ILj-`N+_Phq)EQ0&u}ZLa9af2hxa+h^aO@c!$L&Yl)uT#q)>9r;f&-6al>`{a?I3Q4RpK{}+s`TiJgZ +iT;a@btq;>^+uM;s__F;Bn8XRpRGyJFF6i;s$IIH?H3}iW_JRRtezXRtXqp48 +;w2-V}Eb;&v$RYT|Bck6Un8-Q&0c&#cECgt&19=WQJKViC9Bs1np(!>E#pTP0o%IMv}~fKeqOIwu{(9 +VDEr(<%Wx)~J$d;#Mqm%_b>t;&v!*hgIT7+zzY6p}75sJBU?+V~=H(1X0{|dfa|iiG#Ry$4+!GHc9Uiw2aZg%t-`B+L2q&vO?xr3NDA_2K$6cr5o*m+@)8h_8+TnY1LXRp5(mCl++z#T_qe>+0)U3E29(NF{1m`^#abxe9ZNRCMxPyd~K@|7Q5Vx +0A5@N2B5X2q8;|@UFUWyw8xFCuf3_Vuoq=UHiBvX=KYMvbs;8|_J0Xo{K5{ +Jh8xiF=X@Da;CSn4D#6|pRNO&`d*&6l!{e@zxE)rBgSfH +xA`mywF%&n@NwvJsD{(u*N$g$6LJB&Z6fa~XZZG0?C~oXM)?6hH;+{e{S*PN5gp-q3+<+H>xC4ZfI7I +N_fPSM&W}dkH6t{!8{SQLNMAa2JvZorYa8#@K!%6wP&#}in^T +PpySS59O+%r$yA*>PyaXUtpgdlE*Rf1!TGaq*~aXW?sM&dSz+o8At&nj_i-WCsb-HW_Ccsq#Tp2DD#I +(ge6xB*9}q}bbcG$3YAD)Bbp5^n=8@iyR*=9mIp;%&eqEr0{K#M^*P-UeFYZNQ_fqu>|~h@D57jvMGm +PKjdy9C$D#-p1Y~-UeLaZNMeo23+E8z!RLR1Rq)AZNMgPr#hPykMKMbx1UoIL~sL+#9dtPSt4%7EIVcO>owC +vJ!0c6i(l#T~*bsh*B|wiS1E$NQ`(ZimP1AZ~|M;>afJRNP)xiSC;ek6GmH$lHs;+w+-CI;NPKbT&C@ +r^GQ+$%18*L3n$aiCa8Sk+>sqFA8z{32w(=zz`Pq#Is4SQQTgGN_^UHs+|&W*I0rZyf~Iqf`d$djdzD +r0=PunI-3+vP9*L~+zU?JlULlc;*_Aa2K)631Xb2XXsFlR +=2vPjKS|OJhoO`;EBkk+>sqFA8z{4F+^5Zm)j#$vY)pg8_Xk?f_27%#7mpYrkcI52^=WW048;4WkH5Sm}lq}9z!0LHQs(IVb;dTgazqZLb1$U6KfL=~Xq6Y(t8ySf^5_cqSz;36+ +OK>~dCg-vHrkc2YoRT_;+pldh$OKb<(WJxT_KGH3%-iCQMc$6Q9eEq@ybdV|Fc#3k+qF5|L3rCQnsf+ +mN4qNqv#O;?%;uzyBTIL{b>^# +yE3bUO|`gFV3I86z7ao&uQ5M#J`dMSCiBX39EUU1&_o28^i!98)KB!J)^-{1~nl=unmAoBwH@wUS#aR +_d&Zg*a5QeN1(;BDxJ$lJv=iM(AKZ`auDK94<y8!oVbH5S?1L=StD^*j|I +e$!z*r_#wgwH;GJIz?n$Hz`*P*x_9=C(Iagecyo4eg*L6Nv4ao?ZB?U)x3haSf&afFj0tP-!mfB +{xes3z`es|0jZ!}L-P#qDR61espSp|~C4q=UEtk4D_weIw625_cr-`;)l+6t@>~I}~?)#69UzB|gL*W +K>BIt0aix_9N~fR*4QL#VJPOj>Ns-#EqRNr?_h*ZonpS106|mgNse##=c`HZt#vt+(1Y3xUu(G;UwTX +)+}=nx1((`gyL@L;eg^?B5_CJUKHX6IvQ~U9*4O7!pUml23iAg12%~ps7c&FM$*8mDCwd`VqHZ1 +CCbQV*g0oQE@K{ar?Erhgc@qLEK(eiDQ{yz>^|whgDMB+<*?n9b~mhzqv~4Y-V;I8vUo%`i=AJ=Np?_{YLM~`cA*GzS-z+udQ#cZ(eBhdsq7O@3V4_u5E9xuU+Upe7e`ayuC?q_L% +Kly?AzOqw&C{%bVx=m)H91TbmCShu!M!&~eZ9Hn!Bkwi?fDZJ(!qs9x{J!A6(9`I6?PXaf~O!$JEbtbstiCJiZOP+L19M?QGZ)k#xo|@Grxaz4{ZGy|5n)fupbx+N +D6I}SztT*WbFV(vSyi_l)ep;#*S3fP)dmfKosa{aMSy<=u&cqhA9#E5?ktHtAHWNh2=GCGvZ8kYlokCoKv~f{0m_OF0+ba!1W;D=FhHuPpjAA~%U +uEDNp39(5RY*y5g?x7Rw_Vdjivzc^wLy?0P*OyS^~s#+iD9C4{fU>Ks>Q6d5;bO6!+-B-+-~?J^EXK@ +*W)qDDTnV0hIUX?*Ynt^bY`)nYCP;=MjMFcgS+F=OX}>*|c1c)KP%S-(D`>g+~D@DX`qm!N)+h>X*=R +CkNjJQ2pp!Udh3C15`h%m%BOm9)RlSaI5%6J_b-pkyfEP{t=+62v`2S0JDlZ#kn5`rV7IKH~}y#s8iH +)5}2w7^n4265~?1s!21BIdRj&0{{&Fg(<&-I4Up<7DnA2|>M1IJKR{JayQutefa(sli^~5Qpem?cRQ> +^g>JGJwD*pwbs;FI5^g(>VsfyY~MgIy=K6C#DP(E`{JONZbbpH-eK6D=fD2ZVepd^O>0GO53Dar)DcU +485qD+u`H>;>!TzLLkT~ZZwibI}fG%E>Z@^|Wzx=DCK_#1U8E2&c)^#X(HF5ww@iqWhlT+wv~)osE7{ +*zI4pE|_@@nJ^QlUINn3}$7a)QhYxsI21I(V9b*RXjUe3}$7ui-TTbP(6E{V%Ps-G%E`SWeK9R1}@+Z +gQ}{P;)!G>M4jv_#l5=BsH&>?sHYiJkKamB)-x=QR9P#~QTYh +|52NxKyw0e62>+K+`StX~l@322w1{VMdD($Ul%N1nvH_DQInl(=x0e~s$^%uh{gNmtu?#nEkI}3=kR_ +Whi4qf_9QIxkr6$0PY`r8(PJry$c}bL>XqSzbLC9V9}!9pLG8?`>uWrA*Q#)O(w`M2VAh3H9D)<{^EugnIXwgGr(+q24{_P!cIisCSPaPSPn$sCSRwF +_J2H?(gmC^HKHU3A?wq%ebl+kJG(9ex8(QNfWM@c~9wCSM?@*f4|GP +syE^L`z^**y$RpnuQ0CaP5J(Qig8tM%J=vCjH`N6zQ5mQT-BR$z29M6)thp?cy3cNQc|w>n~bY^Q?B> +(jH`N6uJ?0{t9nze_hrUay(!oG8OBw;O|JJf##OyduJ?P4t9qM!fA#08dhzvsZ|_CMiJnmJ9k`b%nJM +_fyLaaXvJgZ%Xb-ab9fkrsSQJ9o{r|P70JS5m_hrv|EfSxq~x%o>4`@__P-oCHY;Td?}UOX_md+l>8Iy` +$zfxgdXHS~_mo|Irq@5Qb-uUpfO4{Tdi~YSwTr#O2l9V=AP-VU|JzvGyl{ +E#LUw@Dmv?s7*EYS6piX18zqPuxeZIH7dU5U2;Xh@Z|GDS4dQsJU7{@$**IUC{?$wC=hik>FK +z9t_t&59t!}GC;qcN!-A=omeYJZY?!&#_rPZ~abL;D?8@;}Y6%M~^ZDXhBdz`h6OHZwx?e!05Z`$Bfg +HI1$9(-o7H@H2xGx+S_bA!(hUKxC0@WsKG2CojjJow7stAno%zCQTI;G2VQ4Zc12&fvR)?+soXe1GtR +!4C&N8oWOE@!%(epALRD`1#-$gEt1h9Q%ngZza9K;@aEw6!`${8QvIvbojC1$A>qEw}#I +TUl_hP{KW8+!hqo+P_W;Oc_nZyvJ5krth3_)TD5<`#}g6xkWNDM(@2tr^CLGGgvgss +j2JCw9^E1b>&JOqkCw9&b%gM~yGokJrD(LU!c`)19i^f`Cgmv1ghpL3Tp12PEM=iFsqlq>@FIiF+SkK +_gWoV#p}lfr<{Ik?3z*_QYgXbA0*AlRzK+x_?tt1%1DvG-W+nRq)Fgz4%u8LRl;@^r&6R#QhpQG_%{Y=leBEdP;|oHCfhN +Tov^hzxXCa%l|`ZKm*nXQ47ES9&rSIWT(Z0T8c=`&lj|<$Q*Cl7@$^2+FqxD^op(8*>X6NahxZ1Qksu5OnxltnUJ4{-T76a5CvlJE+_BmWgu3vO$msVY^QQ0KRewdcgo2&1t3q#Qw(|g_oNQA7Qckw>{G*&~Q~qHk+mwHllkGhIC@0$#f0UE$RQ9RA$zZo4*{ +1NrN|uzMv63YvXRKsNi5WXtQd-7NmPpE2$r1?}D_J5LVT-L6w|Gs8xh>{K(rqW*D#KYR5zB?5yu)r5A}wNdz9=zbo8A<4z(f*>3Eel#)~Be0hv1v7BYFtF**c84?=#l+q$A});Z+496k^N@Jh}8h3B#YhCMw*4 +Y07~5=+yzkT7U3>{Qn!fi0x-KpbQgfxEy7&@rEU@K0x-Kp?Di$4WGZqCO6mAInw=utF;MCh;f{e)rwE +?@a;L~LcSx9>B6!`(og#P)%AF$k>7d7fD{*5v*GpAhhLnTYel|_4V94ie_0>_TF$S|=3m8$mFydT8Mbd!k@bG}w(GY|E^TKS>b1)^c^E)d}^dv}4 +fS~JrH;xfr&7fAKr>_r{#J#}nl^>mCn8Zqi<#Hb@i9Wm;NQOEumb;PJ6MjZsksN;T&I)u4pWF5kPG&? +}p7BSNfVa}N?AljTWTR`^obXGW?k8_4Ve(N +ln!Z6JCI5VH?N_{C-;i1v%kMiA{6n~fkZ>gl$OMvyxiHyS~1>Xrw`2aH~jyF8_p(F>vn@*BM%f6#MM$zBlcf|{)$!reA{LG=7Il!@4xUfv7B +-Y(y&ycxvuc8hKh_I5egpmq>F`wb^`c|QnyyX>{+4I%99@~f41gs`{Ew>)nN;j6&6JnsqNtH9Y=-W0- +DfnU+QCxq)|k3Vk;;d(hI%ez9jUQWa)D$!mxKeBmW2-nL`NZuG?^~L0kAzZGWG>bYzxLh4+P-}=axlz +#@!sY4|g{*Zhm*Xw7J%lG!GTTG+q)KLgh_>|21`$1pk=Y@lCp|J-M6`8p_K4{IAW}e9z}Rh8#Dk=OtR +RpikQMoLE5p66o_Srzc-^WAi6|$rhwf8OVhtu&I>;J%pGPDG$|LWU4zfqyDXSp)zS*~nXq8*s#JTW0vQ>1uz`6APjAPDiqgj6k=;pxf5NOT~>(~~Tb>_CL4JBvtoAi>j9Nl}V{0IxKy73X() +hA|2;kl*RvEXptt-|0>+iZD36yQL!rO5^b0aL*Pc7~G(wdo(DEyo-6TD2KyK%CQ}V7i?0q`A*pdyVQCLE6U*Td~%l;B^PWHo4p=v;d9>>PtT0KxJoAcD^0p1};|`CnZsee!cg#oZR;Thet6pUyt?K@ysH37AB+h~rXTh5FS+Lk +Xqv=<#SKG>7LC1-;b4RakuWxLupZoJ>uL?8?mYsXNrDHzU>4u#FtBwhrq3E~7Cd+rXO3=nTm=u;?;cL$VD`-XMEJvJ +K0;K{khE8(O?Uc86pe+Pp!whh!T%yg~MdWE)m^gKQAVHgtJ|>=4N|tnmifB9d)j?Q8UiWE=P`HJU`S4 +g8`ST_V{Aep!t+k!%Cs52H`yi7Q>cA4a1{wt?@5(J7K`;QL{;iewx3ei*$X*#^EJMzct^f$xXWEs|~E +`(d<;WE=Q?82uvI2EHFg!$`J)??u2&c9Xs~AbY&L(m|G?PZ^43=#vn#n>CUuLtiN&+t4TH +r2yH`cXx(V^mVi=4E=ls^#Wx>KdGAWj1m*e&`&F;0aH@QGW1Ca6(Ae>6qQ(peq1meSxMasrpsxwlDAw +jeW?31D9S^a%?YBhV!BvJosvAnyqrTCE2a;P&Jev~x}IfAu9(hQvAJS8=g{VY=^Qwl3#RK>)mSfG=cl +7gl%NNMXmC8Y-0y`Z58Z`~Qqc9Qwo{hRv#_LKSEY?JW)7d<@t~%IFo_a +V>E~+12Pv)1WJIU_WmbyaweDKNX{&+sVpQyh@=Yvb*my_Lx>i@&@!3&dJ`?uEl;7W3PWZG@rc<#dU!% +G*gJUhI6_339W1>3=+tw884Mn^L;W*x*9$p-NdB**E75q5&U0BdwRS6l}sBA>lG^z+{YC-6_ny`rQ48R@=F@Sl@zDE#KbwaGLV0954+!JY)|Bw*=wBmO?ParhK +#7MC)d3-%9+~}vlt^BA()>qLxg~xw4|JMy#E;vUBYw1BB!0j-;s;)k_<`q$A9z9H2c9E-;7cZcw9gSg ++7~2#%%>mX2c9E-be|)B;5p(4UXb{KFA_iC9PuNpMdHWkbHtBsa>NfjNBrnMNBn4?BYxmH;>UP$#1Fg +}@uT}3@dGbM{OBe}{J?X>kJ-r)Kl;xRKcdYMKkyv!vC-^a( +9Pgw1g1qla@Pi>sIz4D#(CNW-bG#3xEhPBSI>-CKJqi9^M*Nh5d5a&nB}e?YEyai*t8GIBzm)hf%>{{ +HXz_Ez&pAElrljDPTKtUQM>pjxeyP)g?n?@O&f;gB9x3r7Tt61Sl=!7ik1N3syrk0uyrAHh5kKJN62I +nJOZ?=@CvbWg;s?CpP7g=?oW&2kpwk1q81Vzn5kDR}PsES@mrDHTrKI9_Mf|{vIX!?25z1@n4-<8#ae +u^o6L|YS!pG)eg8}Ylb_Fe83KosHr*{j3o23l%>eqJn$7H6wn&IpPOiPVr-Qa>S4J_1@d%h~Kl~ +mnQXa*_@o>=ZIgrw_6eM11@Iucuwlk9BGN4QY5eVg&w~v;^)%%IpW7^+YsUxDt^GLna#;rJu6Q9Od3C +$m$Z6>ieF0nuB;y51+5<69zRF?TpB;x*LicdOR{<>74nWMSrr_|l_LHX-`uT;`0=dxA%3aVBP4!T2X}!t#P5LkJ$w9`!z}UBo*Km;Y$WkFioa3(=3brP09Ql&E{Wgs! +QC#Y(MEowA%4@(`VzmiVuLHM{KsL;PUQ)g=B^QT&+ZQp8`kJbqz~ +HYxFQCwCd*ca_ys)#mPsoZPLK;*UsiPC`BIsxo6$d8*31>WUw6m8{hxEZM;2@k`S=fqPQ?!dg98ZOg6 +IBPD)eRc2QnzpzFd@RC)TG5msg{L+#QuBtLiYxMxn5kK%kdHfX>=M2f_E}@tc)an6VOz}&rGH)93bG3 +SKTk27j**EcHoJ+0M4jaY +g)`)x!}#XY~k)pHuw4vwEuW_@!14cYw>c)x!|KE5+~V0GF#GZPM?spr6&WdW7ZpJ$w8iA$~#`Cpfvwl +x5~>^)$pEoTxgdl=yL5{FG&uDt>8MW`_7V#m`thoZ?3}1&N=tdeDEtvdo@KHhBI37f1Y@)r0mW6~DAr +kMEPa9PzuU)#F?7lasrI8r)^|R8^#HRfwNc{J@)9J>?a@)Z+)f5m`N)$ImH#S6^NPUQqF){YGT`0bUd0_bvEcCG~K`&j^0b>E +X)pyCQzh;ujLXutuA!yfxLoX;KgH<$i$65%F_h+?DR_au&bm4{)KIV#JT;CHHn+IXxIp$uxeB_@x%V +l=z|H>s*z&O|m)3v%C7=VObT$Uy;QxCH|UO{2cK!Ru6h9N&K!Xe&OD()Z*ueA9y_~HgJ2pXkRd?XGJW +2#_9pVFIVyFhtv{3c}AOx4W^&0$;DaT}Q+ZykIsbm(;^4evbG##lIrN&saSb5r3O}fQv +qh@@!7V>S?k$4~ZY~mb7{};^z+Tq8~qBUi5u_%Z=jatR6I9^1)qB@e8dU&mKQV{I1S#ahtoG)#K{?me +lHzrtvc#KbjXKe&03PNE$zR=IW>Txy{{GA%3ai_k4Z}a7`3H9_m^sevbHEDgJt$-;yeR45wr{e$MKVd +i?yjlT<4Eyy#E9 +}G^t0L#?S5TO0zkEm(1n_GnY!^uPCWUKP{H{$-{C?8b4?8GsN$zHYeH_6#Qsk6XNFtf6a)WOXHVjbGj +1zLgHrxzx3QLuDay&TaL0h9i89eh+mq`$yxlaD)n&0&pAD=()it+-{OehcTwhy_sK)y?)a8(-uEr|G5 +(UgkA6z>KKdzW^f0_HJ-3VQ3m)I%viKdH+r@CoZS6`OevbFiKF9mOmwRrPE5=`u;OBCBTnT>e+%E8vy +f59_WwQ8%=XO2wevjnzkkwzmt=&y?_&MU|9DYvl*UaGuzSQGefS2s>bHrbf!w=j~GN*5cANWQ%{J=dq +J!ET_tny!ZS8W3Uz*Iht~ouvogRkxxh#I)7QdAEg#{a!(wx$*UC!x|p4)Zg^l*wF-S|=bT)_t +4iob47k5uuapITSyAyyAr%_p#Wgv8(M?H*73o(nce_jY|-{G8RpD1OfBVTiw~f(=sQ7gp-2$l{k~@k< +pyNBo@PmzHSbh+kT;f!o_<6u*%8J@4&y2=S9uyZT>YLH{)&eyP>tIg5YA3pM~=ZcS!sQV(bKcrMt$Sv +^b^zpK4n&g$W6b4slqX))AaWEGyEHYY>;TpItT6>Q)XzqA@Zm(_#*muvO- +CVq^knAO9i@k^~9&f|Aguz@3fVOEc4tH)I}e&1O=oX5`*KWFuD#P3M)3rnNueIHs{9 +|{YLa@67yKJ`e9KW9zoWffSLb#)-glJ7?^*D3Nj=izTO9981wZHXaC^JHk8cUj?V_7n6zb9I=h4ySv% +VI8-H0Ffa%(aJuDQj}2!6nR()c;zNBh+*)C0Up5!Cg-AyGrBd4(@Wq@8;mHbZ=Lx_@%@ztklD0b8-iFIj3h$iQiQ=r&RH;H1X^8@aX7rIDy44OydXc +$?8G>C5c~})Pw65OzM#mKd1PG=eJy0JyOLlC4NTn3#}ekieFl>K}!6M;$LIMFSPi56Mx40zv@{D)pfMT3h_w)~=NI(SEstpF6j^ro3O3;1?d>s+ZtzlPrF6e~( +G#fA1Oe2XD|G;aidorr&>ot}v7?UMWGKdHx+;^&CJZi +>Guiyycj#n08|go-KY(@dGben^Q>qoZ@eY|3pbW+__!9P7jyVhY}jJ7jN{+!uGpw>ZU* +<~6r^d=o!s^`L!|&G`|;FSL39Z}w*w*Od4{ZOz_papKoE;L*|fuU{Jfx+?yPh+mq`Su^726#se>zx4c +8L;MZ#A71sC&0UhlPv+TwR*!GRUzNuXB42V=kMQ6wNBmN&N9yrA+T0aZ>hZm~>#8;MjveoRhHs__dIKj4P=CB!e?+}$X}A7**{bl +#33e&FTOIk~JJ;03dK(0|RX9*+1cI=Cy%>fwl=Q~a*-_|d)=CE9$S-(s?Q8mng=tRBGiNaxIoGSm5Sm +(}B{V#B(6{G8(Fh#&0>5`HgE^HTvd2w@k@zcX!QVIvf} +56UugAk9zUA<`TiE?@k@!H%j#ju@i)ZZ5dYy--4TB`JHSQ9U%xzl;A^J%fj0-Z94LN<`0J+lfj3z_4e +?i`__G6Cbe!%yerZ;ZE8@p6Yfk*!0WQz!oYKwRnh`&@x$D{LakaV2DSn3do6X%yKeg^CkH22TpB3k%< +8jCP-1#k6Nj<)uo~nv-a=h>8{FZcYS9*Sn%jT4x-(q-QSe4m#jW#Lo3rqD#vpI#IVL|r=KfG8s-fyZh +pMdv?-X|Asc;EC>L%lEMeWBj3>fA1u(<9`4M>#!Ghu>9B599EA&f;Hn-gi}^ZKH}Zw@DU1y@H(hg*iP +4_(>K&`HhD7O+O9sn_)J@A2h_DcHa=c`BNS7gUAaKKQ=&eTe~EglT +b`U{HC9V_{}gI;tv|)PrGl3-~3q}#gBe6;(ujp>y&z`)s((4*-2g+Pm-PB)}7I8bZef>_JjGI(LC50? +*xEwyD+i9|I`n{R#PIYI~;8WAtj`>A>>w_~vZ2qF|)5YmzK2zOfL(}8FX#W;m9_{Vv0o@qg+)Ww-@1d3U? +OaP6~Gy;cg1|7U5nB_ZQ)Q3J(_HK?=tdp4(tdkew8rSTq;oO^fG(yk+q=?PX@sT#$*yb5Xuz@x?GhO! +#7)p`Gz?G0@OXc^EI^cFv>48yeb4j~1g1?W{+O(S~-~qs4m~+If!_;|=Y^M~m@>vBh)ojx3&w*Uo;Fi +`UM6l#6%AF1=j5cK+MBcd;c>=N*l1$zYiH4F9$`0ExN5b!rF7-!&Pryon7 +ofc{NxP72P0zP3u8qvosNF(}93(|<5v>=V>j0I^#XDvu0I%h!>QOd%0F?V7D+LhdG5zsDUQ(e-6b`5t +U0)jQ#1hlKyoOraLUAo;a0qwdq?+OdrMceHY(5~1ny+`LQNbb?;-?EHF@6m5tklv$h3(|Y^I~Jt(=yx +qh@6qpBQ17=5(s5p}p#Eo1t9J-KWkJ0^+3FvH7cHo_(^~19?`aF_m*gP5&@+~7wI?=6Iqnk{)cZk$^i +g`&f_kg3mA+-4v!IG7PJjD(i|L)qK|0Lex1b3q9p)cc&;&F%1TR_81QZ{Fmo2CR3e$kDSWpKPrX#v)U +lS&vw9ijk&;*qB`G*$N0mW&b*DR<4vJJ0WOa&CDvvI>>I-W4SK`&TP#}lSA{6`kl@xWCr*F;Pwo3n$784X&n!rn+@D*JF1Z(9v?yJ3e_=to=Kj)xloy5;q`dG~7E~ei(ji%Tk5xqdG$2d +uu?nc4{w`jsEgevp{`L+~6;VG0@kVXwI~1lfkN0Uy?>>g<`nnBNuew$`fIC1{OoMdp#{hLqb|wEcP<@ +}mbcMbQRE1^N$Sy#AqgrWTccCs+VEuIAWs*b1WuN0IKowV<2CxTE-zXcv-vHHXFC8irh?W}tbdl}@R8 +iRn89K;hFSJth9#BIM>AcCNbiw_2nTSiY<3Q<3R-ZFy1(un#21eZ}1_8C_{XyXSu)DRaRbnpYs9s}TA;5uHr +=G6~+_<;^NglHM`@dF*TUF7Nq1N=Y-ZkMV0L5v^hz{Bj~BmBSwp3MkkuTdOPHY0dMO+EnW3Qws~9#J+ +`cv6k}h_b1|?Jx=?!c@h$9Y%#jn35Q`Lo1OOqm6Mlv=|v+refR-tw#pg1#mBnB#E+Lz|AnSB*N6jxEV +&8M3~wbH^a!22=fu+W*CVQWrM}dFfyf$sf}?nj8ti324ma|BUjp(!5BBgNS1aMAZ~_{E$wUuvww)_$l +F=u*}p_|&)#8wSsBGa`-f09df&G@y`I)5qB*7S->XVfyMs`xC*zAi+&EUzR4E-J +Ww5J&%$2-tm5n<4anLDp_VjljO10oI?5v%(V>ww_UZW5hpz!wadr^q>ws05U5xMzz$(lxT7Ma^in4>g +-vq3pwA_^$sG$})UChZ_fK`~CBcC=3wa|%hf8GYHpIuz?Yk*al9ZbqQfc3i+@VkIjlpRF*9^mdrBu9r +v^&s~50UL(4@CSf(-aE$kb-*gf9`2+>mYH-ZvxgL)uP`5ss#fU)wcoDEYI +%%RWbICB*ukW2!*)dcLAFx6PNsV;955vITsmeMbtS`>^#zvsDlCi0I*)LxHmrpte?vxsdAB)MSXPgpM +X`An7J#lKm-AonnnE{tU1tTwL+zfc0tx{0qQ(wH`59B +V)*5UH%fVJ_3bde+XC~=*6u(0Ic6G7~uZ`))bpwGh@=ocrl&u2&`W$=^du9jxv3+DXi~g_N@M&y{5@0 +K5~9DGOEe02Uxw4mQ9@_Nqv#gO?G9%=#8{$vhSG8=Z%bJ!V`61**nsbse8o!jm)PA?2N>57qI@P7;gQ0fK`|SL}}?eGI9uHfABtVvuH8I4} +hD0W*%Akhi=W0rBD4D3KOYs;8(U5&FjlopMK_2upL|9*OJuNk}CZl`>Q+P)R-|@065NvQ0@erN)%B`1 +`0P%=SD0`>Y=?|SuKS1)n(!d5S5^&VF5T=jlc?^5;NRPRXjK2$G1^`cWRHT42hFDvzCQg0#k#!+t<^( +Iko4fTdlZv(~riij2IDuPvHs)$pOq~b%xeTwH4sVN3iY^9h +N!=c!Jc4cm4Z*nhVXkl_> +WppoWVQy!1b#iNIb7*aEWMynFaCz;W`YbMoB>{l}BO#)P6FZJKBkdp!TJ3C +RXJzCKIf?Udp3ce3d6)w+c8p&k;0HK{B@@&0H!x-(4wW +zMwDk8TZj?xz1=pJ`p)-JyS}Gp*-`HwM}JHm+Q~^xU(TpV>P9`14P1UAXwXF50(&gwAMRc=>o^#&Y=jfBpaf|3vMYL@=`YGoK9gaTjd|I2M&p1aY&iC0O+EE<+bP?6 +Xai4RJTb$W>=X{GJTyWl79N~Eb$r3w9h!>P@LIsIOk +A|*=L>O7P)=S8M8C3`7p)jb;}t9KERIi*5a_Yol)fAuJhJnBFD}d6hpgLM2oy8MX<-g{UX@s;MDo3;sg$y5h!N-j ++0h#r+(3i7UTW0bKGLe?>Z?I$NiFXeHB;I(R~b#)9I0fr64u=2&Qf*4i^!%Bc*g)pod#IQmbRtUoiV%P-2u!0!YHDH(s>V; +tnSHm!UWOEqS7{e5v$M7a&*nw#CYFJ00hP430+8e_0dDUl!yD_;!zs^L(kL=9?~;J0d~1o5_#8 +m2c_Q^Od;hB54bF(igH#IR}D})u_=MQVGS{?A%-QUgsF~U1u?8 +4hBd^nMi^EQ!`fF38($17h+*grhcP80SfbPZYFK7Un1C3jV|%G#Y)Tjrh7H89rQP8o>`ZaVRZ_zk!~U +j(38aRN0K?dnFaiuqykW@_9mcR(z8c0DhNeWL-Y~{6^oB8pp&G^*#%frHP{aP-u!b1Mri92FE-@vDiP +h9F9b^H+bdUy32@!^gx7C*D+Js>TYf?2VF(ojDu^Pr0*4}CuW7vO*PJ&@L`P(;gkiEOd@$VRx?u-k>%K(iuZ9)Gu=2*R5@1+C46860zR#BE1eFpP +!&nVNQv#}C0fw6`SgQlW1V1kf%hYh>1#6a7hl{8>Y2V1t_hgCgc+5Ch$s1M>!vb&E8^d^sPJm&oh84n +an7UzTN?;7DMlq};7$(ZC^%7kPh84yzh37G>*&BAC6cWS9iecD9fMK(I3}ZE%2r!%=3~Pwt1Y$UW7)} +s|6Nq74H=KB4SW+ngO^F0yI6)Xr1Q<@dF^nrE>R6(S2g4+(mt8l!$pvdJegb0H2r#T6hOsvsgBms~ri +KOW8(0lXFboUU0;%CZ-Y~?lq*9`~H!N793#En|FIWp%DN$ldP}rw#SXL>4F>KhBa6luil#pOpHL8X+# +IU4pI0iM03)UJ{!+43#_^RPRdrDvoV>Jx-l)&DwcinJxHLMVZ4a9J$CA!G#hLbwfa2=*ZEozue2?r_7 +S4!X|x=?D^e~AuPN=PsqL&2Ir4M$KZA!y%F0t`bntgB-Wx2>xR8e30$xy=%R~24MPlj*A1_M8m_^VAj(DCz7cSVPNaqt?xJ&U{a7#@N)2NSV^hMvZ +a5T%{Y{D0@`jtlF!qKahQrhiLk#0eiHP?bE-@u)QNyw&x`6E)B};T7HEi$l!^N9?ecCs0rG&(kh@pKW +g1TYLb=DN%yq5;ZLHhGlibC2E ++L=$sq3ylxm5tofS~l8r9?F)Z*z*=VH%_J(~;i3Zg$#_-zgH%#xYriNu04ycApFf7~X +lDp`f+wMdS=jVH0s$qjLjO&JR!J0q~2f{FJX2uv6s9}vT>|H6L5r%bj3^(fyua_FGV~MV$eM6*%d4e`{DkVebvrFogqcu$Djy3pQAT)-W!Vz-7Y;qOk7%k4aBg-8kVSGjA5*XHN>#LHH^!K4 +aBg-8cwQXSVIhBPXb%RaGy&JF{~kmalM+Uj$!Ocgi*uT8ir-VzG_%Q3}a6MmknbzEZJ_j7ByUA4a+=< +5;a`X%S>UP-$0V7VRDQYJP9t8aPN<`+2_(g3^!Y^CQ-u}!=cnLHYL2(FfJPws9}s@fhmElVQfmk?hS7 +>j7^E^7#65uY)XVu!vQh8qSkONYM3bO+zF<*kZa%A2V29qdBdN=SPV;=H$qv%lB(fQ*08rHabgYMch+ +z;6qb1s(OAPJ3v}#BxEFmS)^I2cubMR+Z7Z{+PC_7taVxWi7>3rch8PxDqZ*RaAIthlu6iQ%I0%EwjDIu|ju^N`F&_%9>qrq?;rUbXp +xtFZa8jhF3xLhsN0v)cC@E5}pPr|=ySU}-Gm+Is!@TF +ab9Jtd7>=Q8*uQzB**Xbb9mCj@@UD~awuU7wbiTV>hVdiFYPh;JEWxl6VA#KTV+qzU3E2>Z5~AjfI(q2X8FsF?kQBz{YR +ytutr&$PC>mCf!rtX-xOYQRuI5c)=t*cuVeL!dAiW#7h0ecD;(lnME9u>6UJSD}>|9(SdpA}M!_D??_ +={l)h6Po_CmTxCQ8m1D3`-X1BF8X=8?=VQiD7nzoy#ch4mvHM@TzstVG2iEG~8$h9kwJOg|Ran=>lDK +XV`y%4vS&z3`b7kXvAyiC36-n%YoI0jNl@75hd1zk5{9=LcCL++1-k +rvBl$JZXffPq?}m4s1okAtU|2y6Lr(&#VT~}X17R31&_!M+A;WNq8jht-f;;G(t00yi1NEn{zb6sH0v ++}wbRY_QSF2$P3p@!-;g#|v1Qc$vXjnpFytx{t@Dj=-mQXZ|DO^X&`69j5ToTQ~eEj9WLBpoMFpaPwsnczv!<6n5rzeH;n@2D*5)a0C?gu9A>+Zg^ApzS6>pfWp`q +mT2KsbtIxGR^u`WXBwryek8%Lf3cdROafzAd5dAZK8G>v-$2)Fv6?{`ZnRiU(#I?)Ruf=Yt*IC`5W@; +$SW+gjs+;H{+`6i`kc5-!dnevPeMTqD}-SMG3>p?rJ!aQV;J@^!!n5o_Eu +AItKnKa2?{T}TCL3sbk5YQJBB4{SVfEBPz!Vr!`d6es=6AMU>KH3U<|A37{+Q?Aq;B)h84oFLKuc+5( +;7%?=-Bu)vyG^tEz_UD3hS57lwI-&Y6Ue7}kwq*x!^mQNykBMrKK!1l&#nH!^#xVTm{Fi(yH%8pLpQH +H@p(lmNpLH5|D&91Vu+sFSecefW{#j-*f`6oyyLlnAw-1onmlm8*I0H0-@ZC)ivq&=Ots&DEe9j(1m| +C17~1mgo$7!_L5#Z#8TnhH(p>ff&Y1bhv8RKnxp*VMz;}L=8)p=nTZLq=imH4C5s_12Jp_7&g^0j7|!=zZf)G!^pxu0aF!n8^)bRFK-#~s2Gb2v +ZWsLkOBTnQzR!wPXYkSn1ehZW?o^5n1*$YJP8D8ylfIIJLt@xDI(u7rwP4%gyJPtW-XdNx{$tvsC2j%4)YFcZuZUl5_pTXP`-o?q=&ID5lRnZUqZrRfiHnuIIu4v*kVmmv?kEQ-oAu +}9M*`#VT#rSdbn9%q7Dv|IBk%_GCf?vVeAjfe2Eecb3?P6W-R`2sC^}x<*;g$!)o~)#=e9%hgE|d#(G +%b4-0&W>Ks;U!r>+8VG1|b!%?zMM|fUnrQ>xvH=mQALCViJlCm|)&Juz2u!0n4jQ)9ESD7*dO+9u&ki)osxP-$5#SlNiA=_J`WSy>r!=n1(5)Mm{_e9*)MBAjU7fovx +0q=7j6k+|MlMu%w?Zlpe+$meddXw>3-p>2SS-q;5^ZVgG(Q<;&qf^%CB`gy6T31bSFO4hz=lG~%$pmk +6bYF^5HZm@pSY56d_#)59h85=7ie92V)}P41&qw5=y{f13B#POJF_RtS=$pu!bD=t +{;~4)8Va$1=~wtUqVnn4EgSN~4{=}&Ylz{+@yf==gXyK54 +#3sj?cs}~-QjlY`pv;)aD6(Q?6;;j2h-N}XuGvLp0!H1a-RskX!E`jF|L#@-Te)f+ +v`E%pl=_EZ&accIw&(XhH7Y2KK_JpnsuI&sT+JE@W#ul@rcx>!7(QY4)%;#TLDjzaEAJ%!;%!f@LPUgc&9^RY}Z|33le7K#5JM-aA9`4SEyLq@bAM +WMhz8j{yRnH|!xX=qZ5=!+#k_1ydmnAVnFC$wIy~{>ehVH2#U3Z;OI% +vY()!o9P1-bkls0f^LpKM?p8ipQoUk-7iqkN$r7C59Ifbo7Y2hL%B&kOhGrJPg2m$=ocyIX7mUJ-Hc9 +C(9P&k3c49RMnN|tx|lcJ$)YQ{?Pf%maof#^uHm+u5mUp>h_2qYn-N{QZ8syjZrg4~bkVlmjOdDOyBV +FKpqtSz5wYBiewl)9MrSGLX7no*bTj%@3c4Bn8U<}e3qO?Fv1+C +Zn7bkk(o3l9TbD?dB)j{@B^mCg42gFrXUq@Bz^26WRzItvd0U8@>9^N#@CG>^^x`~yJO&dY-Q@1}Wdc +IL%@H_c;np)#bG=CRqE7yi98k52v>zfLim$L4X29tC<=lO(vh}mpE +vX$*-|n98A;6kGR7zD;*b}KeUw;`4I~}VRdT$*JBRa*~zDIkArDCo&3*#mxF0Kd5SD9tcu9TVxMDaHu +<&80w*&z@&g`l(BAnAy^PxGhIalj^em#SXlUo3^D+l*Itzu{I+F_v4#*D57dH6?`vlbGv+A|^1^d|5 +-Q*WK{DQ?^H{ln${DOUk=^B2a$1m9V>}r0Y&o9`_yG7;$e!*tmDKcj@n>`S9ip=?r%pR*cMdo~0W`0V +i$eeG@%(&#UCEu$i}ctOh(E*<(6=+a7&`!!~dF(mZ;D!!~Qu6Gz|Vu+5piijTgxR+hV6#g-XZITIn;7>T5B}vD +Am&5iOhobLsG|P~N^`{)pGNfVsFAm$8OemS3aoElzsmY`FIc#T=WWdn}9JWc)SNYL@Q@EeW0~*x-;jm +4TzT=Pnm%}zm`bMAq0f+5ODhmH0hwT?DEx&%5qiK@;{H$kx#L+ZI`u>>xF-Pqz=4Ubc6OLvX(t-b!!} +c4NPJ-+=bF6>J3;Qz;rwR7+L7M$JN7MZJ^xnVVs7;T=*6c4iY$uV_^6alTZ1bZx|20SL94b2JuX5PVp +{Bh5hQn5?=4W^BHICXTB#AcrTMB0@YdW9V+23*4W=PS$=ct`QdK8%b1BY#jq@ia2$l)|a!Wmna#)_jp +MgNJTHbc@vvw!ArmLVnnFC4aWNV;_PuN=-&q|y62g)^N*XZI>c(+oEk4N7B`5;2IKNMof^kJ5gfqiKF +U8vTFcXqq2=)z7}c(KJ8$wqs+~SgAzvX7){vrrD8-n0vVEnK*5%Lh8{GzRgkl1i1JsN5*O+(oD +1OaMUt}a{DevZEiFj-{YtiwshR@b2OVfO8EyIw#m`3{|86y#kuG?8>@r*bP@z-V?_{=i>+m26%Yxa** +hGym!_f-{vn6c>`2_re#B9m9UbDw9JK&dG{isQuzf_NL%hq;G`l`!_Z~;n?0S^lPdS=>5iObph9L;oX9{qr$cJA`=;gQJ1O+FC3NSWE?>C;nwgp|pzY#_h+!o??_I^R0mx;)ywu`_() +?sRAk>&wIGh4J=q=OOE5?+>S2yMx=qv!~L(KW+d1bpO=x>Zt6$ox$#ngTamTKvxbXw?_LnKh{y~DQ!* +1TjR<0aI$rKuy^+Vv(bIdb?derhqH9t^N&CO^zoQ|oP$p9?v1VwcDDA$`=jaT&TwmD*BZ{MNBdosel> +rLBiF&na +_T1aP9i`@WmT9M=#ylxxG8yo9s^y?%e&-?8~#S%)UDN+U)DIS7&d`-kiNPdw2H!?1Ou+-FyAs+xOo2@ +U`@!e0XEy{{T=+0|XQR000O8`*~JVcR(3*&&lF&W~DH+N +;o6ik?6o)Qu~RIO^Mg>JwVN3{p`#(c4462uhL9@D7bXDamEB}36-u?o27DlUQ$ALUZW=m8DGK*(6`N# +-gxftPh;ahJ-KWNW!bs=0!)aRK#OO-B=Z)vM6*eHfB%2tyV!`Nk%3B!&o-#2{ +h8zoxCHc2ff6uStGNgw4ST-Euj>n17g@YfhLQwfN`3w!UAd<0P9&+lO^R^Yh-NH*bN%#WE; +;0BYgLLz=4CVaXk(JGi=ycCvVauKFU{@x!$kA)Ztd}jfZNIzJ;_}oKqk9#Z#8Mq-VNfyrYRGQ?7oj3x +fK=V=h?}2e#CvGY~N}yiVQ?|rn1!IM>tH^&W_mY4Y==2(VESF4?)x9amBb@*bBC~TW4&IOaA+)^OPBkDl&P7fXSwSm4+aUo3r&hA}GX25OCtqk+fH(D;FfR=eBlj- +JSSvpec{Y#U5#2rU@2hNG@A?zM(67!L=Q-Kk=68B0@4$=sLZEX7kaxe%C1+$1jd!#MOOEN_So`V*MDZ +w$-qG2B%XaIr&{>$7U;i(nSTi;z5__rRwR2vvc1$+35^J$I^$R`U8*a94QM3S8@`SGJBwYsj?F7-xD2N>HZIrM=}|KY@!Pf63J5;sjXdABU3c){qgdbGz36xvJ;B4MJ)f)zG +>qChJ=nAr-uP1DR_=O4#tjhz1Y5&ncLO&dR$cvAx?McZb|12)mD(GL{rBotAHw`>CibrQIt$2soAC0C +eypD7is+qmAy*TaSD=c~p~B8yf%tdYv#Z{nT#xNhP4^`7;Vo$xn>_1JuZwh7(bS&gwq4}D9*y(Jwb$g +tU$!4yPPdYx~X? +2a~1fRb$e~q#_$^s!5kwPO?NS5QD*626&5fAx(^ut=BT|s`*%Z^?MlxoT^xkP?MF4ro@zmb(pvvK5GB +CZoP(WAYZdh#u5+6Oi3g})_=?o@We+R)Uu*!2SY&!d_}KP@Vy79DpWE=YVV*&;W!IA +PB~=$G?CLMT;Wt$E}E!7q*Bcg7r8PnR2?K$4jo$^FF9ECMZmU{aLoJH@$k@1)6hN*iNmKt+nI;4VB5U +|jA```56jckfu`hq&Q51H|GPRNceCpcP%?dDu$;j&G!1GV&7J8|ZkRqBY2H0XKY +PD7)G6(mV*XNiF7L10XUI@u*p0TO!#DZbhr+{Wbg0?b?v-3Y`yWtC0|XQR000 +O8`*~JVHgyz`sJcjB?J;w0>~JhqEloUdkm{HyJ)@6mHX@a)_@Jzq!+1HqTt<`H}l^72v=9t5 +8d@2hGQBHd;RgOPj{n9?{B(bdsVw4cf~Z(Vkx8_Q%{9VBB3YsL&UL9b17a~C%6e#zE0Rs$c6)>GzBni +)eZ3SHmpfSWGbLxKW38z{)Tb1UZ+w7z7a|$c-0%Fvljr45nJCXrqKQO{5bXRD;g3SZH5wt>lqrrbI>NJLufdR;3j9k~D`R*(C+}4EZwrO= +sY6%LaAqknmu23<203e8dM^Dm%DNQ&^<0cSb +OJ9cW<7Dnqcl=CWx{lKwyU#bUMSXCKs_&4*dJ_u(gO&EB*&=ZOJoTO4BRsly!_m-vw%HGc?zlgj(O}| +Gm!@6E9roslMZ~MB_X@ +Bbc-tG|1XE?d^P=9>?lN;%(R*N(yskL7?8?6>#kLuaM>)~+5W||%D4OymW`n`d@5pp>*=r&~O2P+{td +#lxILF^~kZE0-8-PZ64SpDP;vyZu@4<9~cf2&<`lk7vt5qX}#w(-1r!g16@%gdz~i5QK4J64kSsP;)x +Y@&KKZj-Egqm`o_;~af%6lkz-<uZhGeF4 +lCHuab91+uOYbGUE01@!m8^$5T&AYu0@h8plO%X)6rIMwPPxSbcUKP!Udf6VSK2ly&p8Yc)S%qht2$q +=^sjx%vigXYS|YPm#JLSCfH`o~`q!q0yESJ?h+NfZ7P*xq(E?_uYQa9gZJ9pATmJ4Tei)JeDe5t~`7j@ay(FTUg0$hm!Szl932m1`P8*cR9@-Cyv +{jyPo^#^zkyfKi@G0*|%G~Sb%I&EsrP{i|;Ip^UZnWc!Bj9i`u_XO9KQH000080Q-4XQ?1hxsB!}U0N +e)v044wc0B~t=FJE?LZe(wAFJow7a%5$6FKuFDb7yjIb#QQUZ(?O~E^v93Rb7wTI23*7S6pdcK%x}rR +%&|vCHJ0teD687xx2glP=7EQPiZm^2Ge*z{mCr& +LBGM?)$VA%;WRU1EfkX!=#-PN^q_wUDH$!4c;Ut(lWdr=+)>IMPnuGuFdWP#1v|?k0*vR$5UFBJI3p!nDJbBiKRA<>Oq6L?REzgH{mVnNlB +5%T8g;k=7p#GNdm2$5vv08)otT%M2H@kd8SavwGmr-N9fRz(-f9&qty-(Z%X`_)IP7@=G`f@1B6EFEZ +0K0CfZ4kszGNdR)(33<7ob(;~M&d&wG}NHqd2&x&An(`qdjb*awmQev4vb9*Ky~JP +|JVb$FKnRck|aDd$I9ChoF+|4s2%MFj^PD_81xl;l2)7w%dnZ!1i0>qfgFy%@#4uXe46nsNPuRorQ=TE--;dnkB#4!zL5%nnSMe}j6nDinF7g0Ei2Q4_x`B7 +po<}H!mB+fv&)ld>AhY4x-qd&o3iw0#%8}`CsbHZ`dQexOPdHKIQlv=HK^P=P==sk&yl*X2OH3EAFd- +lp*u$=te@69HC>;S~nvxEgP%7-EY^-yYUn$*|UdFVqAwe4=F(|*_a(C!c|VnlxT&|v!Y4{xNqS}ij;N +v*?$vo^ZObE8*~02cbw7a$XJH}aWqmpPOaF8fr4F6P5e6^Q*QY!woJAIB94FOr`ZD-IUH_pkz5ueu*P +-70F-_pwW^XLH5%BdUOs{PSO*FGdUzs(@Bv{jrMaBXYTzd%aq0{&)b@YKdePcY8mG&$O#w%ZZy0w-z> +RM$4tZ{kdG$EtgpnhghwbnUFZjU#42|1JXaXz};HEAa9bdYqZ{I&I!)m8z&mzTl*m_eW8Ee68<^aS#L +BenUjl`nKSP$Q|E@=C_CD8nL0D)GIjFg2H(+%`f1vKt8*Lt8#$!^15ir?1QY-O00;p4c~(c!Jc4cm4Z*nhVXkl_>WppoXVq$t8}_k(L@Lt4F(?t0=mkpp{v=J(w7?|8uc8HCaA?fyZ?m4y@f+agN19S%JvwZUKk7iP-z256tNGOvr-Vz0M +9?mkq_fAsh0L2lsp`YjWg!QQ%DGksrJdKl}kHjGbD+U+lthuLI|nX$(pjvB+l^i|EGplKMl_xXvW7f` +w?MS-?39{aN$?1^dVr{+!vC#{xceLYX}@M8qs_`Y%5K{;fZCW($!t_oh%CQR;{Xw_)0bU(PPsfQL|0_ +Jw;qfZANlC$3`ydEnUG3pukwb|=!pn^j94i-L@jg0N#BvX5K$BjXO>;@I~*2qA?v>PC9BB*z-_1NPpE +MET)t-U;FdDD;B4a5jRJStpxdRnsYRyg1t%XnXhrGK7jgKsRC&&gLPX&D|R00Q<7<9a +PFdQD7jemTEeW9fRh4UwKa>j2Qr3Q1(p{rpx}_fX@7Y1Com2UPX;IBAENZfC*!mJXvB`shwOk|91O +=NN0)1pgUzZ);)*QY;y;C6tmzGxm +#>?4qe~7j_V7QRfB$H&Ww3}RAUc;1APa2U*YURF01ofgHCVPYpiX+PL}tV3#VWN^kZiRSs%N;AJo{PA +4X!=(*xk@4a3kiqp2H=U5+3{^~3sSpGyuR)k9*U+B#mOJMKwho_|Z3^~GnY$r!H@XPdv$)_ +JYJNlT8(lRn_zg9`Dfmq_zbW`l$#2xMAAz?7ztvUag5Of}+k)R#^V@>oR`WZ8-%;~Bg5Oc|yMo`P__F +?8!S70btCsx;+!TDXtHuT2RP%d+-&6B@g5OK|&CEyOmf%~G-=uKCw`NLwBKQ+Ee_W9bpv6ag +hHg-a3&ICTW6RFa_AI++jaUo3`9^7Tb6FOnqm +XP-S%`D(OZyc*BasXD8{US$Y{8$`}fer>EYLTwP2Hj6N41&e6tjNF2pVC>+O1vS>ROE0N;?3YN&9X2x7LNsswR?|>p9&*wv*z=NSk$Zo!}Tsu9sH +Dxqp*VcUG(UV?6mdn(Vfu5=NdT|$6ZRlE~ySf01W}%U~gJ$d$^wqVZHV8{`En^TmCHz5pb(Zr7R8`jc +18E9Y2I1Q%Qk5=Rb!!m&djapVvvg~1Mi#F0awaO@C +B961ENv_qhuraZqxAi`l!=n&MwFf|`L1PaFvA&ZKjSl%MQh_LnZlz6Cl+J`4wJWqL^ry{$G5=%_PvNh +M;oUWRDtTPvT#0EZGk7c1#sbQDt; +H_od|d##a?W?-|C>6S4v@jz12$Q0_a#>o8C_}tc_2We%#95$Lpqf(LEvJ(^k5jV@!SdP6+Ggd0~&GPT +%WvFnvYJP|0lk-22mr+mmQ1h{up>XVFNE~??3ddfC#F3YwaO`DB9C;ZE$6kiSk(Z%x>}5zCc^L}FUM7 +ncyi778JjLw8-JgTX>-5d=JnwBj4!MgT(qg{6??EPKi{SlJS)nyEN);|=eS=tivAh^-0h#OrooH@RGfHN>KlmROr~IFFaHIH6dV!8~sBFceE)B3~?hu>mNs-=fbmPpTtFI@cN@WE9|5E0U!r#`NsLWsSNy*AK3%pq{f0Vh#=8wX$`6F>;{;K*<@Op4vJFZ +;va{mh5jvPch)rz+0^Huq}sk0Sz`Y=3>}jN42tYKhoK9GpmgIIg;!fYB^2?te}3IC +oAMcIO&B%MtS)qN?&B9!n+h(slAs+qlR|auT;bzhy9H9QbB&Ky?U&hdi)rO(q4*>wO6J#vpCXTijTEd +uVt^}y!KLjti2?Tw3ouM_L4Z#UJA$BOX5g-DI9Ari6iZ$aIC!~zE`spia1t55=SaX;aCMp9H}6MV-+N +Eq=FQVRglDAX&z%s%wx<3UXl?3gfKUiu`PwIn{HHhz{|OgTT9zNMcYb?TfB3J-joeOWMd<$+^9^+LKN +l^uFJ5*DM!(WOgAw%SC>3)5<1vez9;kztlHGIW*2q-HM1CX`3JgC)UIG9uBC3$$%%}4f3UnfOWvfDHC +lg@emOqYjLu;nS%pDj6$Wos;ms<%S%o*Nu(S$;esR%5cC7AZzP-7FtT4B8n@2ns$XkUHS#Lnpt|5C;& +CI66aaFFNbTe#v2$O6IZsJjGvWuG-taTHOjGGv2bQ1%Wn<(QY2q!U6I0@QE6za0>mO3{n>X-%P0G +_}DD}`l;N@r>x)Z^-~o7J1v+<1I3vzKk^sUlFX-k{S>w2GC!ZSfFplF;n-i0c+OwE<{Xgp%_k;$0}A# +RE2Ou=k-sqD(lUtOyw5NiHKUpSwrWOOT+IT=+6^&yJT5^0NY}uU!pf>)Zz6tr32tQ6Fsqbp?1GxxydC +Pbl2p@C_o|5WOpQH!5s7N@#$Fb6>7915UfE5_6Dh6JUiY=8sWl&Y#XhEmTDsQS^A>8ET5Icm)1@~h58 +2kW_MW#&+tk`mKhp2iS{+^M?0IW-Os%tZAL`MYl85Z-T6fP|rE6;47jX;QwVbJI=AO5lX=>)S{Ye_V; +-=&wd%D)!^H%AZTJOdBQ~8#&bj{lHma|OFdUSu<{qPfAo9uZDO-yaFbKiQA_oG#9U9XpTBR~0v)o#jpRnkZuup(_z%G|B`S{2T)4`1QY-O00;p +4c~(>KpmKBa2LJ&07XSbz0001RX>c!Jc4cm4Z*nhVXkl_>WppodVq<7wa&u*LaB^>AWpXZXd97G&Z{k +Q2{?4x`DOy49SOb~Ko+fiy9U&$`MXrG4B^?=P#b*)htA*!RI3ap))eAjxu~2+PKNB?6q$NF +W^il>f#dx{FKUYX|FSqu_9!ebqmkK^puG$#y&J|-6%DH}n=3i+eBl6sU6!UqQybn)las$6175G=u=kt +v@iUJ?@8nZN0t1xr&-C#<67Qz&|V*yim{xO;f?t!__Jr;zF+>qAhEL`qf*)@ucQ$?sNAwG8hS7IFZ=4 +}&xoX~~U%3ez))?q8vTy6A6xYvg997=CUAY3Zrv`SsDv(zUZ#A25S4t{|^82X0vqZy+pWb;VWApx}Xd +v9_-oKC1cc%t9iqfvV>dHM=kgaWq+a3YDX)`CMYtWs{EH&7e`9_&%)9vtnPUcWbaGTL{0lYu=RQ+GI` +Ha)gSlU`@qZ;$A4I(i(AZ41F=EOW7OW|@<>E}lc=O2U&U5|@iGG5rbQ4WXd$Y3aT)gxO=LDxX|r$a?d +k7z!CIVtc`|bLdy%a|)!c)0^VR9i(R=x{$JqZyifyfPB1VS(ddza54x+|nqb-IzEHlz=2@HQ +`6t>(|IPt6w5bc~btK4cGWPXwQ{N@cE4G@cX!m=f1ILT3sra97ON(OR&S#!B&Lt>g?IO36An95ekGAE +tyXh*se~f$Jd~J4nfeRGT^(Vje8U8Ygvx_eaD6kM;Qr6MQM9U#V2axEJhZqgndE11ee2^`2U_YB^E0U +ZE2{&(+i($A~KgI+HpgV +b4M_ZxP>RPx?;U_XG~YWpCg2xaKn%aF-kI}miq>rHv-1(hsVC{jC=pG5$Rt(CxN$G0oZ^)sZIuy0r+c|s|@B7A%K{dXni0*GXzg$zwW&;_&i&l=8U^8c7A>(rXY#wuF^T3o??=YDs<}QW_q@S5 +nLe~rj=%VO*i(~F@CT{)*G6nwBo2}FLk^9j1D1t)DUF85wF&(L(NM-Gd-EuG#^b!8JmhznMD4oIi|54 +#glPPi8G)-& +%}GIem&9CvQTg5;7uYdtZgxR{O*EvWxef6Pjscdt2z*+2eZw&)si1^IiLZEq}S?G3XiRJls~UX;$imc +TEY`%xS$iX<5_3t=;Vn?A!C4urvDh?b}td5WHb289zPT4Es)}J#mJkTYH459~z;J#bd5ZudaxgfuxJe +X5DN72h!qC@%2QkSv7DT0RT1-@CooIO`F16L7m}XG8*>F#G!$$Ouk_e@q{!Zc{);Ww>uO6KwI(dD+36 +}hBjx)AY7S1W9yQcmCCFgCMTdojFDhma4e!&_HcmpQ;L23f-B18?PK3M!rB}%Y#aN8NWCO%e=l)PYI; +7(=2HOHzhcrkP#~$tE{1mbMvLn6!dyQ#1G`;f8tj>K-}od>GJ~$@0i^*fvE3@AGyB+5>vee5Xu +xsHXl8kG)wp^3O>)1HsnfvOitEPZkS%oct&iR7Cbuo=D{uUOM7{{n6n$k^8;qijpZL{#nT(Xx0)-z_ZY2 +v(UvjOxM@BEz!EQCw6Dj8{AzTW>6t)zozjn-y4cP+$)h(O*0kkPHX37X|~6eK6qr=IFfKeE*9wIKAT0 +0TJ@(eug*_l-N01*M++Bl90W|Ax{l={P~VeVv}YI5)cbkocZX;sPH$4+!x)+KL|SS`_No8L6EkM;E?^ +7S?6p)q(GS&dDV6pjjsAh5d%2XthrDV%@1aw@y4C7bj{oM9565FLoH!5muGETS%yzOnNacgkQzh$dt5 +AgbdTJg_P2j54T2>P~MG0nLd0aev%6`90<)es}w0WrURkCRXcA5(Z +*OO+b?-gCcveGiwHhZi-SXSYiox8-zwhnk&U>xGWUUS3?%V9Ked#6%=4rk0F22~Dr^mxyCV!$d5&(l` +mHOlfW?;)aJtDheT5yI3Q*i7*0Uk9@&nIp>Pxn9Koa$alHXA)9k-&1PvLLS}?aG(Oc_s_X;6)R!vX4; +5#IM>JYxi2J6PvRLyKOic|{T32*?b5jSzLGcnaU)S@TV*Mu~^!3`5AAPp&~&@X)H#Hsg=#{SaD8Tj?EIDKhR316taXAkA%+cBU6T0 +68$L^^k*^>xvh?N-+P&d`8r^q#Yj|FxJ9vM9EJ}e>6gWo`vw184jHnb#%nFVJ;MMV3ZD=%_ZpR(GTkoITLD +%tpdg^)9pnk&}xUFHQ;Zc9+^?SZkLvcA@r`RU*P?A4WJO<=UB9mwmCSsk3{*Ll$I2cDXWebinha6oM5 +yK6cug_1vp2P&W0gqAS8$mG0^gV#!C068KPm5@SF=#Zsy&HKjII +^tA!qo7;N*=DPmvrcN~Uv2$#Z(|tVg@wvRX7%MbQt$14|N~ZIIHqZ-rVkE@68YDw|Dhj^{X +x06O`biWcSnTU%|8II4_0CZNzWeCtF~7%q6rft-GM@G(G37P`jip#WeYC25y7)uv^Z+<(Dbgwtce;u$ +=>yYQ1l}`FanRy`k@KtdiaR{(Wcnt7>c&atH3E=iUe2nQ*U-R_on!JGjGVYL+fVNp(9Nc;Q4T7X#N1z +Te&igLcE+tGP+l2cGQ1SLjDD77elR?@|kIiN$gPmJkQK;zwet9BqpeTKmX)VzC{ +0B@Z?ZV!-ttdq1;jfwwr-aLm@}+Scj%zKDzRGA^b1&=_vUPDxBzz(PLcTWI+b<_}FQG%1VYd|)qx#s| +q{Bb;yjkEt1`^mt-Q+cjLuAj?6qp(_U~d%7!!g5R%7&wF${H1=TNcs{iq4};ycj5e8I4BNa)WqH*?)6 +(*fCAbnM;*4ojXs;@uWw_9bY(g8ZK_bT(_8Aa#BN5qAjPimF@EYdt%n}Hhf^P0X6|?|6XAb@%l45XY- +k^Tf%+`{xylJhO^yTD++|0^iQc8 +C)}&u$FF)4z@f+bMAc&x;6(MKFaD|K4@|8yRCmYVxj~nR#EHts*v9z@-z7h1+u!F;Wr%eX@6aWAK2mt$eR#VoIXEGoK006!Y001EX003}la4 +%nWWo~3|axY_OVRB?;bT4yiX>)LLZ(?O~E^v9RR)25WND%#>PcdqAD(DCoQk`^d)YB0l2|a!Z8&y#iS +;k&quVxqRuG7*_e{XGL8!)+4I;&E{?97{aZ)V57xTt>Uwtqhu({Rx3kDWf<4kz8e>5T71?SkAjqlw}x +F8qkPGGxRxZR8IgMSe@F6$P-hYJ1m#;D*Dq$DUavw@i^-lBvLe|Ckva_*(|kW)lk@_=ZcN@l$Q3N`3& +Crc$N$Kr!F2kQTcH@idjMEY^01RBJ=^ZiCwI-~R!KO7|9ZqKIbJSJRAXrk+`tGU-ZT6ko(fs=97`fQ4 +w}tFJWW(Ms_RT@orZWF$>@W-Ud=AJRm8p?tNoDlHXbq~+3lLt!_xlQ9LpAjLwiQ+Nnr$QR*nQXzgLL% +!TuQ!GNzu~i$UHhQn&{di78rco)~Gr<(tOyfnw0|XCwz=USxYuZ|yUKdK;*+QJG5W16qPt?63#&K?QK +QpCaq3c$ApDjadl2lapjjbQu+}gkfir!I#Zm4}^t5Sl3X-Hfouxn_KKL7}#-!MkBU=(Y%jH4zH;7gwe +?!#p6QfK@~pF6WzXY4-Sz!ys66#Yw)_+}e%0E3nCh1sES0F3&x?gJt^w}aupeYDlz4cu|xap-O`qYh0 +wGk4IP4?8oO&S%q!({B+ujO``1VpfTqOS}Z+MncJu8J|ZwrT&QaY7`iSwDzAF+8i*Bi6S3YWV`#R9!6 +5E621s21ic}?Bq2?czA28`!StPU7br!4n;80_)ui9saPr012rY0Be#e)9zKf((O}drZSl3Ypu~*ma_S +I$k-Bp`t?!a>Hq5in{$|Z79t1Xq>P;0XY*1porY?WMqKl*1c(I}1#zug(u#i_&0G&#*;uwA%VX@gPQ_ +`#YBwh!wjCyqTCr6>Ckz~pmhXF3aTJ`J2+$=<;>+G%W}H0+;kAk6o&v)uyhyE*a$3#~V0KhGI?qe-tn +%siNgac46J%Vu9EL{2MBe${HVAo8_NFjCU>YAvvVuz)abOY%I9J?43J&7x&yw~vmo(dq?KlM;tk?%ya +}aggcifyl=SCF=*YjzY`YdcGCs2b%futiGqI6IqW#kJLVnQdrOi#$1Wi@Jbo>(JvNqnBy-@DZtDw&zM +cGC;SaNPOO-Tl{kZ24m<}Q`C=9Ot8>6_KKWZ;puRVS0!zli#J!0{Z0XJ4~9lA(E>VL<-p=Ue(|Jq6z=94Y9^6f++>gjyY$!*HVzKnN`PqIC +Q4^OWdkxKTbyz)9K*Is2Y%@^QUq}dA!Rg*2Q9sNq}GE-}K_p@a4rvQ3-spi*6P%80~+DTpQpKn9*6on +kF>r%I`4yNa_{ZzPO4ioQu_z!MUukZG|?qGcX;dp)3tMy+=<1r!Q)!M^1m0!1CzTq*gG&Z>>QY?AMM8 +M$rXIhE#FqygCx$C(Pvp#+dd&m2?p!o9juYB9+^jpH3?MvaPilev5o}cw7Zu&9jBO-Zp*8hX(>X|qTk +yXC&zt_vPG!L*FwSNInO9KQH000080Q-4XQ^$+OgLe%80M{@804M+e0B~t=FJE?LZe(wAFJow7a%5$6 +FLiEdc4cyNVQge&bY)|7Z*nehdCgjFa~jDJ{;pp!vFZYlDUiX+w=6yzB7QNOH#Mxs}nojJv06Cbob029-1F^)_&jZvtIYS-5<2sS?}unN4ACb@ZgXQ7o1%MVlF +(}W#|5sGa=cf|BG;4o85Zio(B>m&cY5P580H5+zI`FIUuzT5(!;TgaTw2{GPl1iU-V}vCtqI^nD&m$6 +j%~S}r3`ICdy}PvU2=@&ok)g4wkn==V;*?T}B|WUVA_PGtuqYqiEyi;z{%k65eGXhMXM++wG$%WjBPv +KuaWaL=cDC+EHw24WIne^q&Of;Ws^+QA*W7VbO{OIBxCVaP(CU9ZE1?g}vt<4>0|8G;xIk=ALl4CpohvThTkY$W+;(g$&|_u#OM$O8svc7Z|6x$^3Z3T2?ceEtXirURl)+Y5g +$wiWrZu=rwa;t{~yGj4H@>I%UFBVizzD{(wMi^a1Y2CY(i*&my-537};kyTcDxw?lT?|HM9?-rSt_ho +9bp7N)=`46G%I<;oQhj9mq`7p}2z2)JzDoPPkv=~=he9e$$Kce=xVdoW;~s~dL8u1{}<-SgYt=?%NSy +}7;`v@IBy^F+m@Op}sR70)1Y=|htU5?i=ZZ~YU@E3rU#$`eBuh>kjzLrVY>*$y1K4Mv +kQm769PXmX5FVa&Q1Qre%<9M<%~qe~wA#$xj&eU>0CCRt(Vdz>ij|z@I^biU7m-|J#Dz)KXu>O<;w)|H#1j?f02TV3fVt9@gqp%maqCGD4EK55k$Yzn}P;@D20B=|h28D-&;IV; +caijyxXFT9um?l-d58#Dfh|lJDX=9G5P?B@O0XN|uTtfd1@I(^B@Jvkc8m3EKbEOzdPBcx;7JSqr|N0JcNOaM#@)zXZ^IaO +dh_p3yPY@#W2TCo5;J@u|S8hJKieRNcscVWxXgYYy680UsIcnBm&&7@z@Sk49s?9xM3c(^6+6UfEjNm +!Q{VPZRXaiJr!O(pqT=}lTp*PZz1K!mP)$K%9n#^V~QaD|w@=dPItfP=_X81d*jYbsE*g&;wlG~wX`N +1TQ{Rb}_ybsO+Gx>Reyt=>4ksr))>tLOa=dF;4ZLz=SB{Ck86B)+yK;wznTE@jG|?2@ +Fsl(PRLJeo>EOKE-Qky$N+mPRY-`w^6fj^LRq9s>Ar(l)uwJ5nHC-qOA*%XA3F0kETi=z(hJIdu +q+>v)jmbX)jD@P?slKPi$fkI@y$fx~)yZj#QNakP0>`%|>jN+F3kJ9{jOk&AfljQC!IC(#W-rH`(lNn +Giz!}ElRE`}c?Ia>aq8oq_NJi}Gs;&?o1keO=>76p0Ya!j=z^PRGCnzY<)=0Lr(catG3#XbTseHjUy0Jq#5mIg{m@C)<6n*E*7^`l50Jq8=8AMeM0U%c4+7wj$K)ATKO6m6)ZvB5=U8k +REV*O7JiYBD^=pZ{oznTlB4fhf&}ir*?1%DLlj*03c=5Zb{ATx+Ei^pjb;tviUOEX#PYX +!`N{saPB<+0Zq!)C0Z%W=%_B2T1@B>XhVH->?9Lv&H@6!@=}W^Q(W00>&DeC0T4D60K8S_iaF7}akT> +S9vI+u*eNhWezFNmvl3@jcS05fjKJ}xqzgT3yTi`T*ja~>(E6Kn$_>&TQ2au%)(749FO20${wV@>6M( +&!BZude%C`bJceiZ4wD;rIsMp>*Q~YeOp0$sRy6bdUr_CA-`lr-)+(J<$!qDY)?oEa588vwrvw*kL$r +vYMzKd(0{*PO~8|6Mv{P8Y3B@BJt4&g@8;tzk@nh_4sA8b@U{y{ +JoCuquy-1e)+ntq}NHt?@CKrJ6cnnwpmNDvnXHNtEE=+8SIyT+K(d_y +nF~c?K`8E`-xrF_*Z&Xo8H&JM0ZUoAoFj7U{wZ7Zmn5b4H*i%w_Y#Z$sop&KQt}6FbxDn2*?=`VVp +gS3`A9TAw9ZM2q-rPBVI9|9E1b(&2v3#l<#Me&6^h)Pun+%GY9;twteoJBU7^%T!c6};h?S>d>^5zMS&JgF&bw^}~iG&RDX!x;&;kQzXA;>-*g14yg- +CTH&d$TU$E(b?HhPuT}U-gn~M3r%pZ!*b=rDb()o;EhmhqP$;XB2Nmfl)}zk_IwdJrr|!kBbT_*yOnN4W(>r5= ++PIZ^bj?;i&{dPCr=0qNZpSJ7AoN%ISo@dc8s3S5!m)OIt-0hZT4|WI*^KVMQz6*_VD4z|MAy{vB)Mh +XJPEAj?>pl!95}n_{^p_LYB~_wmzd3`U*OiJ#~-rH2B`uaO?=+iY +*->%=diB^R(u1YvUMWx&F*X?U9gNEw`3fh`egTWvEm0l82jD(EpW;rja6tHsz}_dY1Ork%_QjHMY>1q +&ye7$@jL^-Si~40~>>;C2v94?HMD_{#Uam%*)!Qw2gOrxKoxg3g<`&O{(Sz|Ee{;;XMX)&L=O-n#n7q +wGB4cf1oKJ#cU~JgT4zY$}#UX)FHb?%@g0eXWyKtWzY=;(2;6J>Ne@7ULz-y_5*zaXpqLghJl^PtIHL +4#7sE4d^dT?=I-;yXz3RMrZdJ^f*~nxZ&ylcR=l~7JxE{G%_Tkd2!8*ZPdem$evg>{R_G}QbUDw-ik} +?t_I%wT#c`9(A~n(=`|ZZsv~t$D4(e{tW*mjYJ@3(%yVWE1m+Uizi}u;=d-Iu>`8;sPS>Q%?O&W>?(k +6N=v5b~@6aWAK +2mt$eR#WxAygfw(007Pk001EX003}la4%nWWo~3|axY_OVRB?;bT4&uW;k$iZ(?O~E^v9RR$FiCMi74 +ISB#PuNQn!nPL)ejm4Hc5!A7>xQJySgk71SdF4|ourN6%83vdiXJ*TRl;+grr`R3vwmzU=sc(R`-4oo +LQ+wpC9n!3Y3;gi_Q)+GdM1Xr;Y6A_1@Oc0ocO#aO{k1VNH5R=Vn?LsGl8Ag#Kd`B)fs6eJ*&Tvq?gG +R^E2Fa9VxnOZ*m=u~`)kdoFg@|D;Rb`JAMg|jDZcE0~RFNvUHd{)yHqah+pnrFFOB83rEf{bPUJIpZtEGPnj(XuwFAZ_QeYx_)X6XG;~SZPBGvA%d2v`5<8ru$4K) +kpP7W(-l26!ayXFpQR#vCKHx!3sWu8EpYUcTpvzWkB7NgJzk%moS)9XX +0#)S~S76M~Q^ksJ+02%p<-0!F~S>VR%@3Pg5U62aekty`kG&cpXL!!69X&P#Ww9dBRP-y~8l(cK=J^e +w1ROkn-5iAjMjcLxi#6Yp|brQU|ivR}BD|{Vz-oOc7A1IbOOemn^r+xKDYdN6?bzzZhB#dP4oeBsSq-?jjJ#9fPvW}a*EzQrj~W|BIYpotjQRq3xZUQGgd0 +_*4wv<;#L73cZKN`a*GypfwxgxbMyzyU$tv%jyY!T?HLiwa%UJ7d7q$q-x1m1^XYR5|qSj_e$zj2}P9Qu^@#eR +{}0_lV0Cme8#{dvC5;>r|64A*(qXDhIQ#+8Vu=VeFG@I_P97+JKw=oJiCgEu~0=vT@d(}seJ9Y)4P!Eh +0T!HZ|p%XIYLT6?_22c?H)KGJZs{kh=m4a;kFcXv{BvoXKHuT7aX_^RIW<^jCAI>XU`*P{DYkN$nCxb +}YJ8t>0V*Kz*@6aWAK2mt$eR#S&;{S~MJhuegrrE5KoZi5m9)|=zS|S`;Y9yF!Z?s}W?u2eT5CSN6vQ;P +z~CP#wIFF&Q4@;9CCIcQbIrmR(HYec!Jc4cm4Z*nhVXkl_>WppoNY-ulFUukY>bY +EXCaCzeb08mQ<1QY-O00;p4c~(c!Jc4cm4Z*nhVXkl_>WppoNY-ulJX +kl_>Wprg@bS`jtjaE%>+c*%t>sJh16q0srq6yH803CL>`%$3V4Z7Jwkp>DaQ8p2Y6i6zr5#+z`3@OQS +Vh7d1mdN4b%$u1v1kF@D><9d_wt$BGAm(Hl09Hf~qoxUmRa6lZN&s +ap(lTypjG^1LZ}M}WKuiNSmitNt4&J`5946jh?eOj}2#XzSuI5MZ14@(E#|Jo`qjOsj96&2?uZr39|U$T_l(rNp{9WqHiU7q>>F;HJ@i)8eFL(FU9iPe{Q +A$|Y}x{6dRCz{96Y6Gjkfn8Tvj&PiXlKFY~MJHqi5kY8bGip4+umY+Rz`X1a(V-&A>2?L_EGa(T=%H( +j~-lqfnLNbVLi-H}Pl`-yXnCsK5B}DTBwS7t-+f6f;ai%UweIFHc}Ck3#+7X9PNI7T*SsMPtQj2T`EH +vLmO`q4!!YRf4kdkblMuJnOT+P?(V{jIC`+qkRFPagWrwAlpbN_Q#f*2+I`nE~?{ZBJ2!44gEGPUZSi +L&9)Pztfi<1;UD)}(O&E&OQ^Wy=o0SkD4&FWbCKQN-GQ`JY8BQ!=9X9U?hHQ6dj2TNH}EGm_vskDftO +T+)ila0`(>EF@-xZg=1j@*#r2OKdB^7+d^=qm(=oV6XI?R0RmJFT68n=TK3`p1(R6wI2l=tvVox3P44 +ZSO`-TX(7u4J2k7+3j=RBI81S^SSg=OJ*OJ8w-@{vzGk|WnRlEh{mr=)@n5zd@)v|m*6~}M$S4gSmg2B1P_``YUlkvJ4@5BGev}@GdSS?$OY@0E{!J5u1TzZWEnue$fQ57R3}shnMka2`)2jO{@*dj?mi1Qr(X^NHvdzg>U%wE6&UCsD( +AIPQE)hvReIaCINrgOnfjk~iMw3>;nxXyIP>CR~6RH7|a`jw|OYaY-)E-8}=fT9NfPwOINV%7-KiV-5 +#Z;Nsa;qQ(Wx%N^+*GFcS#g;%XShqx$6{fZ(Eta>R-qyGU=O9KQH000080Q-4XQvd(}00IC20000004 +M+e0B~t=FJE?LZe(wAFJow7a%5$6FKuOXVPs)+VJ}}_X>MtBUtcb8c>@4YO9KQH000080Q-4XQ%t2gx +WW=( +(2XAkzHcDccDzyL2ODoB#>NKY*s0V+Gt)3K8fmT;xHy$6gKfYIwlQ8ni06qgEfCKIhkG={~JhfBRaMrLUu$@HGs&q +GQq<8AR`s%B?Hl(m|R$&=7Dyj;@vqF!`_8^&2WfW(k1B|FjOj3*^o>3&06MzJzgqUtoqHT2@aEC`*2N +cwjV5JVEv`b}8f?w}-)#3%{qBQu-;e*qy?Q6Oh#Cd$;XvX12uR%z<8s6*yL4>c89Dy0P&zz@!LWI;2Lx&lTUHne$s +l1jhh=Umxdb{9TM^C7!346&0d*kEZ0TreZDk7TVdO*+a-0z3)i^+iqKwKD#XXrU$k@$<>t#_<!~Fm0qB*7phwx0*kduyq1m1$MX6Dndig9{m{7Be*x^#5_x4R!*kD3M +6LjQH-bJxez45d}Jp3UDm^ED3irm0`G~MW=Zo<3%|Yx>s{$-!7O2dI5HjRKL44i77h`O(pIc$`3+AlR +fh{+uT+*6q$y`;-8fdl!gIMl>V-Kqy+8Fr9qY+0F70WX|5PKG?(zPKqSQ$=6^R=8CLgGFdxw$^j)kg; +^EyCyWZ~yumHBZSYAcjB}tU(Lv1R6g3ef&1tsc0qbwkBL_1jECR8PS7cZeu(-Dyq>ZvJ1alnvLUuKzu)kwn%K0i-nrYp4gt&#rsQ+zdZFrtmNbIvT3mKvUG=4%t7SX-=fPvf)k;Eg-o!D?K +*)89$d&Go~wcyRgN46&lh89Pinhksw5$Sy4jeROd3Tt9+C%SP#BW{*Tw@#Q +{cUUx=qtBe@%=X<0TO**j_WGN)TVrxQEICOgq!MwQb}2Z<>}QSTemogdiMg+_-VwrpEOYvgMPVR2*UhaPGLGEGhQSNc>N$zRxS?+o6MQ$$ +lGWRO?I`<~`Huo;~KBwi64zek}T8>*;=wj!RDR2hQWg_FnC=1;QaNfbpsy9Ay{^z +uT(x+*lR4ul4Jg={d|sA^+lEdvtA^I$uC0E1p7aMY_I>w-0IU9={wOV(v;(7Iw>wT7)JYucKzu36Wu8 +`e$hmNjeLw(eNxth?4d>%R5CdT2edo?6eW=hll*K+fTfbJk1imG#`e1#uK3Siw +FV?S_W0}FsQ092%M&?B3WM()sk{QjM$(+rMWzJ>J|Laqz8XAjSUZ8`K&cU0$WNyH_<}&9a^i}Z3F?jK +3U}DY-cq-q+O5V6DHTh!6g}NSpJ!%2npMp6Xa_iRl#Mq(`qX3&+1Yl)&J+dAHz=Ff85?vHqmbM7C%E& +sEIStqXWckFEC#Ohn5-k1XasX6iX}wlyiGYKFUCf9bNkAs_bB?$Gz~grV2lQ_W +=Z#6T-9ln0_8V(r559V-&wOri@wR9{ymwz@LpTcu+s9pTnp0X*_0(;0a^WxMkeM)5ddr-T1up_`Ec-i +)Z@;{0weOtiXCGU>hrNvWEr=_k+Y?aW+sK=1U2lT3AiBQ2wSpoG>q$m(45YRddpuGN;WM^O||xykXunZ<({^9r*9AdC$CWJ} +@7ekIZ3n)I4RLHqV;pOg~P?OZDch`7s#N`(|?*SRRBSzVy(1VWoAU?u!jR?1xMLzM`CHJpWmQ?K-@ +S2(7DNE5&Ag6m_Rq{krIZh>p}EQ9-dG*o2Y)+6hx@WKhFYbGx#acosjx&+u#f0bJTR9>k~c7@ok>z8u +>Uo~>P!=)RQa>{Ud;Gb<~#_z{9jU5c*`HOx9MjeS$8v9$pcGPlY$_?y40n3gcJBg`1L!e!%gD9jv&Tb +~(LDy;f-i;^#5yvPduq7+|x(XP^RY}vYP`_5gh-xeo#=YcMKxex~h5&<(I^pS6GLWRJD-LeTMu2Gy{3xm*rc9?TRXMeX5s^BqVqx)`j~)l8NqM +%j^GvX`L~FZ{gec0e%Pu{5gJs)A%EPY+S_0@DLusqhQF-;q!P3&*0O`$x-klspf=^T&L +xSg;_0PJEy28W!q2R%h_4ToeOsyA2mT?hM__CRw)CdXih2E7*3p=Sk@uKB64%Ee`gS=#V;0=sQLwH7o +hS{v&+5b5^a~JryZo|o3q12-<8$qvbSGVdW1S^9F6I@(?NWusme$Kn0W+|K8aM-65rnK*I#we_szDO3 +ga-vjDNuiBjMHGo%B)L2LGApu?fRsCh0b67`JoC^Z@e-d+vYy-jxMX8q%J9r^eO!+wK@8WfeNo=yf +&WLo@p5yw*WZ6TAYJCE0R)WunwH;g*jH~^9Ed@A&`CG`-0gAzc~9dKo%5X-tLcjz5t1RCBlvs5O#6LP +W?6@?D4nX>F;*yAKUgACrdC34l;_c!2-f2x_k6HfUqY=diD3b{wXOK!zGyc2bmYbI`qL_H3`7-3RK$I +7>X~l+~jHtYRi&7HeehX{68u_ahIHg0~Q7>RvB=-P#bX4nzTmk>VQ*reZXl~g}_-?jlg;9;c`k(G80a +DK<2WgWhQBXKxT@T2xO*RMFQ7cWdb)dqsuHi`3P@+<+nOuo2qy%VmP-0Y}pBcw(M%I4ya|j#&gqN6ua +XoJGAf+O1tR2nN_$SB&|>RS$hBrS#Z9~&JI>&g`lRqU||d}Ok;pT1aAz2Z9cviPf+VT-Fe{Pp~FVn_! +wkr75)DR_5X#8!x#d)d_u&psb!u~)c%2^#s|aKn(QC1`*kRi2w`WBF@#_DF9H%tQF~M*BQKzo+5&|Es +sD~NVGs}D_k8ki>{9#$ExXy}TJD#mfBRmwkUra8NLzG&A +Eg(t$c4z~u^G{jtQA7H15dZVvr+%N&hd@E+exLk(+SdU5q;2|fFNv$Nca?3iA<*QJd@aYUOboru;3j0 +BmD^ukRvRwY+Xf5B{Rc)BQPF||S)WCWr4reI(X~enu)h+`ausl`F*m$w4n|svjS2<1OGHNB_>FPdsiSnNDDerWjuMaQY5g;;q{NquOHM7Nze>tKD5 +adG>$rGU==JitRPsphaJj{XKqf=>wKKHB>$fXofoik&3zqleE3mX*SlQpUj8*-m7WEe*VHMMXq$oXj9 +D{#R#Pmy?q9B2uv5JaH0`Q^0pERs4+Sf1>RqYc!5H{6wlnzBTq%ZV_#kXt#-WhiG?+c8_THiS~eK4~h1OXpf2ZglJER_KaxHiS~kMb3}Vdv +{yuXO|&;edrP!;M0-!P4@A?5mL}S-Bz=se2T6K}q>q#I36efZ(!(S@LeiroeTt+{lk^#qK18m6?Nzzj!Jx$UxOX|uCQ#S8Y^qrL7C-&fg_dzd6sZy`!ibt^_kg-v +G8Jo&o2kKT!I}gzTrtspw%A2mO$c8u7o(%h260dT@_<>#DdNZNFEVyF)jymI;ii$t=4IHI;84k2o;`n +4dVi&pI*ww1fIer;mp`Q6h=K*}J>(F7E<>8r{9rPCP&K>MPw^y9j#UX`4$ZK`P)fRLayU$lC8&l&l_9 +}4cLR{>ZsU|nllke1n+tD69J}e5%#>Ob8_@q@HX6amW(d#=HZ?`{9<7>ws$nB^IITn5!V3A%Bb~vY&3 +umf%Hr87TK4?&KK;Wos{Yoe<9z}vozawGG!aYj+iwjPC1k>3kJ+_bKcs9zTrXN=SXn!mtDMENukT*38 +U9I#396rg_ceZrxVb*O}y{38frcG-b!c13thht>ix^4Pl?T>yTLF2{3M?e4(68eYANhl6Mof=EfFS+z +5(9);B9aZL{s#Jmnl4*%Z2|&)axrM2zZ={3Y;S7_|L~1pEx=5H02`(?{Z0^o4{ +`PvaQbNB4sg}j&&3rV-omCO#}X)sm;~r9j$%ChE)Ely*dzq{F_yRYCV5`$U+P0FZsrif#>JHVZ2@4eF +r|v0ZaOGRJcVD}uka3{s6VUVa%CFCHrMyhf#4fqTj)~a +))rdwg-=6v=s2NEzujuzN9%chd(rj%q2+9MwIu%i@kL~x`tOh9v53$EKNk3+U+Vr3P)h>@6aWAK2mt$ +eR#S$EYt^#=008d*001BW003}la4%nWWo~3|axY_VY;SU5ZDB88UukY>bYEXCaCuFQv1-FW5JY=@#X< +@-@?lGEUBrq_5<(goQXY%4wwIi4#oj83{@yZ?Ak{FBdCUXU(vQlQtHG;8v@1qkHt +ANJFFqL}HB>Hb_^24zz7v*t6C;7OeEc5=GG_geQVF&rzZQVA{Zcs}D1QY-O00;p4c~(<{b0001RX>c!Jc4cm4Z*nhVZ)|UJVQpbAVQzD2E^v9ZR>6|mI1s)2D^xWHLoLhL;3SnvaRB4YkT8$ +}rY5QEmP=zdSQ{gIC9#CR-ZbEmBNAa9Jk*kqve +?N|MXM3lR#1y0hiDr5`au6elS}GWX)3OTDEOltAt6i&Ej1OWCI)oRUpZ5ww#bCp@K#V1c|a3gDNArU| +cc2!AP136GD8;I89~OS_0gVM5+Ad`Wkq5&%Uu7Vn(}}*j($?wbl(mMl^>|i<%NFACnD;{g>14f3>ao( +tlq4ZCj&YYE~5YSuw0lEuqRnU7c*{Rr|&wcnMHD!HE|8gGeO`41e2OyP!%?p<>vmmiesnbXwfoduQX9 +!SNfjmswMwB9xH;;4N$y40=szx6f%m*r(i-arjl{M}4zVN+q5Im(17gZ)H#aK%`2p)(u0(nF_;}gmhi +T^>sy50z~efi~=594ERd`DHN$vf<5i@G4B%=B}7Afw|xSEXIP0msbaX=gHDV$QpO`VQR| +$@nF8n*PUNUG(Tf`Jlk5e}(J5rlRAP1+{}n#LQ~P%ZnChst&II8ojzy4|iSygn7h4maDYL7Kg$gCLbj +uFyvyJ-tkET07lWjohx{O}OaN8Gq<;9-PnInx_yInuPosgNhJi&)g&q4$Dq_7KPaN~-heI$dxoobnb(##}I!%snohD1TPTdnrbx42kfLLf^sbwPL$ +^Egh#XZdCi~HfUjP$BA2!~Vscf_$Q{p>OD@j=wKveNYC=mqXk7A5cpJ1QY-O +00;p4c~(=)#1YeR3jhEWDF6T?0001RX>c!Jc4cm4Z*nhVZ)|UJVQpbAVQzD2bZ>WQZZk42aCxm-ZFAe +W5&rI9fu28zN+tQN(`lV*Qd!o;P8{dPcE(L@mjhFf#4|@F71F2L +-PDNi;<{Wgqm;C6r)Qy%eDT +`8?X;potc!_7R32KY;p(3eceot+(vIVP7p2GbWtj)a25T&kzXI|Tj;Ni(q52mK(TLV&3$qwCw0CJ9SD +{`fNsWM8ZGdOh`vF9o4QC{f~xELagL;C{|C*&h%Z$S_{wBT8fnaY|^)Vo8#ezih~Yr-Vk4f}&EU;bSH +l=mZRgrywec@_EhViqA>!ISnQA`aSS>6~aj1&YVSTNhO;T$qCO=g*D~zHHU_Y +$7RM6Q?}*oIJtd6cW5btB)mQe!J$6)C+IWX2@Pb=>W@BCe+kR_9CP3k*w)542;Q;iZI`}ZZK; +@n1yd4b?vSOu^hZn5v*#MN&$Uwvavm$twU(__7U{j(eUV{gIP_Xk(O0b1wbgk+l@kSb#>Z~D;DMNfqQ +yCeVv|7u0Hu^pRaFbr#)Ddz?~x&e+yF{7f?prAUael@Y0A?wkQwbq8i`^!1~-jjrC~i2`@cih0RfwFaVCO@3pW3~G +f{K0H0Vf!eur-=lD1}GrMuy`SfZLM#`!vl*cuiDR)n0;EjcJ!A0RaX#GF41tZY!Xz{W5klgsOK60B$n +RfTD$7YtTD@Vn530tpq-$)q$a4K+w^45Z0+RDr~aCAakuit8ea17s5tKy?!3+JQ#+Q}}l +^gIK2w=_i3+!Ry`c?;pIo-#l36Ls8KTI5xF)VkaN +7BXMyodjq~|>+BwdHclWP*`T%3xQwP2H+a;eNcc==$^+rhWb;{Rkh>puWot1EmOK_m4z+BS|Sb_;A$1 +1T)bbnK?X3BYpbMKGasuc9o$FhY}I(#rT(M$VUExqZr`s#a|=5|907o$8V8tp{kpl$p)^r0ZBonGLBiR2z_ZR^%l|)s{9=Mhzs=+Kqb3i; +cT8_wcvjo%0jnVIqAca95NI0NiO(=VUK&6L?(aTbcxmA~!hSAHL&zUINADx2&)}n7jRa!Vv!-ej2zh1 +vNR~!bZa7Hno@7$`T6~LLIS0KEVBcPsJNXt;BJZ4lHlFSU~kvy*s3&dgG)6N`yjWijjDZ<3<5V+B9LgMJeQ0n4(QLXtF|GvKT51zI`l +O990*PfnpvH4GWPZq7t@zYoM$wj&P616P!@s9y_-*3ZdbO!lweNpz0MxyCDK-2CQ|6za}x=>|&;1C36 +za4Yu1k+Z@CqR4g}pS1AZMq168?b`6NO +ZWZNY!XO`_tt8?DA&P4qybjFs5H89U;KCUD~&2=QkY=Km@;b +PaZ6QntGSU1tzsh3V-&GSJGynSoc*lNRm!0c)ul*N|;V8C9x%%&Yn&6f{wqTS9-O1H{Q740r=Qu;M+ssMCylkvX9O*Q9Rxk+ +i)xv2us%}olU!c7%`4sKG~Rc@+Bu#KC{9!?SBR4fC-ZELyxHBD?~&#QMm0Odn-YtM?!8L<_rA +8fjtrP8r-tI#6K#L8*mt&Mq8-Y`Jk9;>fc*74$LcemaEh)urk_r2b_Hz3~rpzpc~2<{#jg4ez+el;MK +w%>K39`MF_bhkOTsG|0W%Mj0lP}F=ItDMW3Gd;E6qwz+aAUNCJj)PUZZG%0Zu}dAW+LKhh(JanzibK? +@u|bv!^~q|#IHpE>$E6AKxdGj_BDhc(x3L~abv?DOHhxKn@yZKRh +{atRGx%eaT4hCHm-|WmQW>=godtu)h7sep)`+TUx|HH?1uP#)w9SOcBp?KZ}nw(5eUq!-bRco?->n?{ +P_Zl;*IhpXgK-Co$Xw)f!-58Kurg}=+><=Eu`RY_{T+_HySP!jwppvWK?KjIA=tAz`xLMGg?TvL`lOM +#6%jdH7$+O1PKZ)D7wdws(39W6#SV{%+MUb*QE5R-NO!=BIwIM!s4iSYh`yU5Du2FAxZFNbw2-U~TA) +$|gN10@eCSbFqReY;rZ22xj^jc{#BxMnm(4LeGiF4AaCe1FHfKrwW$O-(;`ExuVQ@DcMhdG5sa|$=uF +qgDm1_Zy||Js{?9k9U3FzydX@5J1xn;N7bn7v$}b8y?yRGHi1s*~A8I~AFd5bz8N#t(0QP&|L|3c1#c32HX +#>_Q10;{O(im(aR~GSdHzB!nq|O%~JNE_jSBtRvHgbx>gC2y@5}Y-w|4E^v8`R85c5Fc7`xSB%udrmGOa773|RB%lH8Z~#=f&7?Irb~Sb=`S;k)m +y_+%ja1d18IRwadE*(Z)UhVMKN$F|Br^vl;S(bxu!ftLuNEo1zyAP>7c0RkjUf0ArN#s_B7C`Bt?%yF +P;h~1#LJ^11I*xGZo1u0NxL_KZ##>wbrcn(N=TX1+^7?miyLUY@2u%TeRHNLij)Pm(I!`wfeh}H#NLE +NohdpIJ03BgL*EB{d=LdOu)E73 +5;74Mg|2*An0|IZmb8Zq_o+LoK3S^XMfaIVu*LG^lj|vo|lmGpd~!S1v#?Ehko>NT~6yGWMw?!HLiLB +K||b``2?^d^gqCXx}OBx#5_858L*9e2H>!T!GMJt49o+%#%Ptl=h`5}{Rqq_A>F(>akaydqaTl9Rzr1 +!vwFUHLG(01Tkt0nG_@P4GkQC`bZNURnPht;4|b=7B381EbHvwnPILm-*S}A0)lqPCrY`R>R1Y9(jw1 +KFf=gSfz}1zcf6yisnh5rCk;Vtkq#qJL0J0|XQR000O8`*~JV2pbF$Pz3-092Ecn9RL6TaA|NaU +v_0~WN&gWV{dG4a$#*@FL!BfGcqo4dCgZ_Z=*&Oe)q2!CE6Oc1m*G;H4lm7bX|Gv$as~ksujX;2(*~7 +W`;Q4U*9tfhQVC2Nn2@AWQ8;5_8rb`j>(8_b}W911o?xeo`}4ZeDat`U`NhR&n|3o}cRDZ|7f@-J_eg`DDm7}1o|1eWk3N)5=8ceb4WfAqb4gKsnS+qv%g +xk*T)8Pd0r2rCU|hrsX_~I7t21b_L6Q6NmB^LA?=0=LDz@Wg={k2KpNjd!_qvf!!cEsyT5~QMDWSOS_ +M@KShV)vz*0yq&Taj;DPVi-Nfn-+&1E9p%9K-S@vTLeVPzwqUVxvjXSdUFqpBC`DyX7JF4$fXRZ$w?|A~k$n`hOH$z36(;)mi%$y%AsD +!a8srDDDS#7bi&}H_yZ-6C<2Eo-cr1w_xkxepTHY5f&4$w@MEpp{IMDy`JKIPQEc(eQDm!{F<_VpX~p +4#NJ*v%NQ5VhyHGmv$PAo5|Zit=)qYoItpEB==C(8{Mrg-& +MSXQ^G#kjBhPU$JFtWCiFDIX#g#Sd7Is!mBO0o;ec<;7pP2y41N%;AI28kdnrWwKHpScx&dw*jX= +TC8q)lf!m0s2LFVdSvq!nH}yy7DGC4^5vQ7@2(`u%BA<(Nv-N+M2pG1WB7U41RzCMO}kW +Vc*hWxPsZ{7hITEkIh^jVdUWkb;`zBF6ATNVoeX_NU>FV`8xEk#70z#q(X{6ovZi8LJK$&}}^@yt<60 +4DZ9oECOlNA204+FNQ%}^{-l=oCTq?84! +rY!P{FN47!iceyqjd!GoS6?fu?aq)VKXI1q%dsb{tr=1R{-HE!o4|6%GlcwYu0V}luwWeg15ir?1QY- +O00;p4c~(=RDAwlg1pojh82|tu0001RX>c!Jc4cm4Z*nhVZ)|UJVQpbAcWG{PWpZsUaCz+*+in_1^qs +Fb5>gBa-h!Q`Q6(bTm^e-ysK8O;C$yKrY_iOt+1X$!tKZ%;mtAI-1w#^7eJC&9Gjs0uaUFVWvEuRFOu +&0aeKGSXyz{w8$O=x{ol_6a#}{yV+)Ml{C6L4+p($xWN(fwE_4dk6l;-@KmifNA}j5k>Dg@mH{dkye+etp*a1OIlRZQzf6P;f`Vm!p1vpx_e)_ioPy`u)t}LeI15l<+G$WetrmzPT8FylHyNGa+~!WP+qF%an+Tk9TRqk< +hFbJee|1P`W;pz!p4da)Bp-EqJL6X@RJq0<#WR6c~>P2f={Mv&0uN)ureApCP8w2@zYSEm#r=80TyWV +n*`NDsG;|Zp`r77i$)w%={F58aXr&F`!I9n6Mx~>BJ9bz>qU;j;L=RIn4Xkg1s1%chTIA536H_aRI1w4AR`lS76(V>JomY6$uz~> +(wwUHxhMPAn_~s`W*~nLuF6t7VVR5_HCQBOn{>>YB*{s0Ib0p`|$XkJD{Ph5(6RR*Sl{ryLCy7y+QDl +wT#k&JWH+0{xFeFx}E3l!kyF1IMB3RNzMj`pBbZLP??7sCA+2PlUssl^YSkudlk#y?|`EzR3F5v1QIX +89am+NgAi&>tbW2aD23#G3s-nRImTMkiyO3-Ob(2MDrgf5^=)FY)BU;&RQlf?BF<>p(n>1s4p861)JEva-@^!nSZ##9 +GDfUB__~AOJ{T$G?a6L4FC@E+H|hGLQGsC&i#_577nfpJXlPnvVAGv23zo(rX4LpVqgv+X_xmg>hS-a +iy^N=f|7mEgGC3!q+Iv6gnUBK|SA!V$WP$@68(8}|@KX}m6IzbcvVAUpAomMgz*6XFYHve(?=!r}w_f +M`);%|)HNP%pixOJ)SPt%!IE6DSa|bu2qq$yLGR_GV$x81gF}+h8QBIEaCU+pBM+RkJip49jvtn1|W1 +)T1LDq)<%USA1u}ITgMJrQ{>;33UmI~we`xdNr`TJ>X*y&SK00pwvmqZhbs$u7zw7||$xNjcntqmFZk +#We{iG*WuOj1>~#)osQkm>q +8J)O{v|thQ9Ji&q#T@r;i+7{b>O6G=4$~k$pha3HVFD>TXf~Ix6f`}Y;Tt3r2!td_Qu#zO&XP?50>y1 +DS>oVx(*zckkh)+G9>;$X%&F%Nd|Q&dU1~toMwv@B?p%~xx)!gIO^4jBf}6ss0IF|3+X0bD3SC^cON% +jP(L~LUwZJWNXRWAU6IBM1q~y5cDqE}#%hIH?Lld0>z2R_l(ZI@TH}}zE51A)FTV`STjxDl|$%hZNI8 +titnizHYU#n!ZBJ~~|BtHF`mRj3eBrtU{%)}xSy*ver=5_=}t&;vN%XGVrC|k-YtviV-k)u>?Jo^77; +D@1FHSUttV|r|EPUUrOKc%)u4`*{G>=w9G)0`3#k;uW^a|&ROgHCg!*(Wh2Puc1v|DVw4%WSr{c^C7) +)yto-RXOeS_|c~Mz0GP%n|`dF9HPwuZS04%(Z2e>wf*GQ<~)c*EQ-go`nH@6aWA +K2mt$eR#RPX68&8P002b-0018V003}la4%nWWo~3|axY|Qb98KJVlQ7`X>MtBUtcb8d4*BIPQx$^z2_ +?|?XVWHYHSy%v6{ki>WefCo#I1Py@;C%Nnu|O>;7g%*88; +}AqaO3p)an}3)E`KJ&hyI<*8};!`UOx+0|XQR000O8`*~JVEYrOXU@HIs7oq?F9RL6TaA|NaUv_0~WN +&gWWNCABY-wUIV{dJ6VRSBVdF_4ecH2gh=zl#$FEw6*4h38GOg^;3>^QPK(T-zzEjh`qqG2Ea6cK>{1 +Avk_-kh_qv9EWZS65ZnS64Uo#lgb^5tm6;+#HEjGduVkf7#pL+ZV^> +a$RLN^F{>6Bk|<%;nM^7=QHtbRzz!Y9{rRpLumFgi_@Y`6Va5SnWy6Qx|x@SI4@_-eN?65MY$@HsL9| +r_>fj2g7J!`%C1*U3Eyt2G+m@cgRsuiRJ=SrK6!P15;pG|5fzCzK6`cX=Jcz#7iVwI@keO4H>=8pm`- +P_W>uxrsmK<~vV!KATB_)NXbxlgCpt1NG_0_X{Ca^t~N6oL)!qEpiZZ!t%~;ZPi0onhkBW3jeJa>EnsiWK{l5cLd^|!jcNt3Kf<3|11-xY;1nIh6@#pJ-%R +_878d^5%|&Lwb@QuC$(o?V+iy<6nfGIoCa*HyYA>G>Hyw1oZo;+vOp5$KWBFZMW#9ONZy)*utSkgDh& +G{bV$0H5{e7IN04Ra2I^UbPu4)rJ=vf7;%57Ugx?cA&2;D4?s;=nixT=;^E$QH7dW%s>ej<^5P>g@_| +ul#M9TG^s({>Q#jd!Y=T%E*Gh|kJb{{bOrNWR^^RSf&4SdKdlLT9L&qQF=TvS3EndbW5SJERm^R@*2Yw@ +I~W)BXBnBTt$(c+Ho$zOGM0ZIq?26=c2u+3Hbp~Zwv}vQB)H$?45%nm12Q6r1+E2?W7=XN +H!m$ph^;vj#Fb^TwhYVIBp}EmsY_k=nzNc1fIws0;8160sK<8C1B6>Hy2a$`TDyb)j1{AOY-(UwcQ&Akl5+EB_iZ +?Hg#ixf){s3zmtr$cgpN78=pSHRh1>@+^BO+s%rOhlXtD8s2hey>c#;*Ia=K=RLhBPM8pc1{jGM&AmY +(!GnCPu387p+C+?iKYc>d~Kp!gn%f%pW*T~;8~Bgq;t836M3vn14tuG+9mzwbW>$A5npNmb0q); +gSf=ZchYSA+4bmG9W>9Hp@^G7UF)M#d8B7CJVG6#Pg_%V$@gYUsb>WP>CXsuG3tU(6VCT!9FO{2PoA1 +I5f$lF)*>mJJgsX7oR(bcchmVi3_-+U`QB&EJQ%(!4i3QU1*aFmP4Sa*p!sZvY}GQT@?k>L)k2=V={B%QR3V9+{-CJ3jvziq?IGz9fo;W__VqE` +8;(NW+^@+~40n{E*SX!C2-IMEuq8hld!xpge#Qw$@>kBt%5k@TfbLVAUXyHw7qCVQHDx!zx*Q;_}k=; +b;d0v6G1ew6dcc!1vh@Ro3u_a2TSK9U!C;|Mc%ev`69TpI@E5IXQlIe$vTNKw~57L9UGqLvQ;V0cybO1kQWsVv(8G|1Te$i`WA}p9%K1MYSt_b){jH5Y7OLf=8;F=-hlYo^Jvnb +0~KxJ_Tq4x@a)gTUt@N8AkT6bt(;{>;);|jdtn(-@U|O@W!#9~Gh{V8x*ZCWujj +o9g2o%3z1{eZKQVb%^NWpJ|a(wdUVtV%a;`Hp*vzG{net2{8uWwJ^oID@2A}GB`$0@5ptxRY-C=9_6G +~`d9GoR(nD`*S?9T>U-S$3PQ$KnpjLdlQF@#cV@(1HvNSb4|P$Oeu>OTfH>Zt;V-*?FX9r4};*8@cRN +tz1QiYG2M6rK~LSHR$RTc#@zRC(8ulZG?TbHXAiAc*M8F +Mi|@bxzU$-Tg5{+_wjVsI7)2JsMDnOQShnC6D`*cM;tJMW=xt-LnH@Be7C(Bj{YP52q6 +R2Y6O1$p>MT!oZEq3)DL_sagda0T@0mjq*g50>IZeV@dFXGVH+AfwjYaV8K4I|){_{GMjg+{L>`F#TV +Vg7+Jdvf03xi7{zD%&u!Mj;RSAA91_7U~OfW>qAEQ73q}_IA!G0K&fY5}XaRh +L|A%cOdnL?ptDa)i)>!FWVsN&(FjS=6+zE#i|3XWl|@LRo-NP6ff$UOofcbl%e(lr8K}4yr5<}4cH}r +`0(~Vs%~m`s{|f~(`k~$&@@~v6CiaUTXu6Dlw(x%;z;B}2(ypPj2M%l%B{|)GBJR-6X+mbfMc)#2W3* +$;fv|nU&Mb1`1Cz|8dJV+f_Yq9{~x +g2nEl|2t*ji%#i79-AV2ZVuj$yWSC`%^Hq{cLSfq~&0%5QrBd1qpfT=YL9}adg+jUTQ5{#=vX#herY8 +(-IWSoyqmI{Sb3!k5k6oASfF?dJaoKAWGQ|C1p1>-*qw>OJj{w3!@9@gwugZ=V +}ztS`Tl|yX^QNN5TQ~o<32snaPnhMCwHkAA{eFwfP#*D-g7&QSW^pF^#F$V_?Zi|q5qaDXmB}!n;i+Lm +$S|>W7W?gQ=3~`~TBqWeB;j|XaQ2jU}=K-lLxeXtE(L$DUWC!rBtfyW7%xy0|A^6>KT0q_;Q6W>1O7d +&5E>}P@bSaq@7spqxp*(@x7Ts{J`$_m2XNHhc^!?L4 +u3GzBw&eM;E2;p2qwp0_TYjZ#vUyZTO;ZKN?+{B(rIJ&koxE!t1m!P#M#Rw +m8rY2tGJ8CGtY+KAtH*ZwM?Gy~5NvGF4GsJ*cfGYNU*u;4W%fMCXRy_tRSJHBVV+srq%9s +pbLR$)?sWBjoZeTtRu1=q?)i`~3^yVf8e|v +Nohn!53n*-EAoa%xK{%(&4ds(5=B;*IX&|A$;g7Jk_w;VPf!@XzO>PKdO;1gQ5}v8%K)EJm>v3h^Ve( +T_3C9Y1vRCU?#<&lB9xB5em=8y1$*jsJR@J&7b-vAMrMbdc@zX878w$%?ULS>0qR{Bv^f5KzYym1+WIHG|D+Iq#>1J26Gq4KXja^@HG@KDAN +VYLrM#pRdBoFT84WZBfc%NcR0d!td3+Nf`~7(&9xZdk65brfBy5oC_I7q6T=e;&&oEpi464Ei+q|##S +QA!;P~wM$@$5}$W5$BD88n&!;>Z8mEsiwVNLv^{mIm+1HEQ*R6~4HRYh#7uW0V1O6^E#S;)c80- +mcvYlzWNFoOcX|txPKs3=ladqivTts|H}{*AP$m)r4wfCxX3Dba~=tNUDC?pvt(N-1dYnmdUG~Fp9?* +eWW{9N|I0apdN?^y)GpFA)YOXz@uE*nyZkuq2fD{ZA#NlM*j)(<1wuc3zXTfgz^ +~^A9#BIQ3ZP*uOgaX8QKx#pj&LS}jeJRVkMao@D8?N}av*0b_7^G`aerei&RH{BR#$eKHytvM`0kKE* +&O%4vQjVS62?WFB2l13@Y;x!c;!HHSAgZ-LJ(Albc~&=EL9`YK9sCz8#i8>|r0y_QnNz +Vyl)yA>?3kvtJ?2dLAY2cHSxbN~kIG%cGd#mq==K^O>jE5EZbxGwRUww?^5F54PoE;iXV9OL@?Yz=U8(Ih}^K +sr0(deZZ^)IuK(@lz;~>bOAO2ZCyI6hnnETa^>U`!gN|z)4C~_fun~yGsHi+TGR}kU|!Db9H5;xxGp< +!qXFZ3IJD#USym)dmY=}UUA_41i|10?)B>y~I`b!)EaOaRX|gD`^a1UZM~qtc?7_aSAJs>cSkIwAELX +5vo}C|`o|5m46`ZoFgGTlM!>VJ&0EIxe`UA0?%6NT3-;_VpIhbb<`7NL8%u}LL8{&2S_innu +?}<}>X%{2RdWG68f!@16e?>2Ll0tH_lzHQn +&q_j=jNHb=!K%0|%KLWJa9MfB_UIXScSJV8(;{*t-v2UCVxnV)jc&T1i1@dQ9A&dsiF!-vh-I9^`UkB +v`>Mo#6;tGZyD_FE=1>!HS(Vp$x{&AThu(hutl0q3U=M?KIpp64>7joIB{kY7Ff^HPpZ}|cO+VyFV&W +Jr(}$-PrWdY)DK`OZn9+-B<&+l6Vd($Mr$_eYpKDKT{H4O7uKq(4+Mp3s8`SN*=%F47KOx{ZUokr~!m;dzu5-$cug +NI-O8eZw2X0Bxz)pk?8xBUV`&cOq12yaGbpp>bZ)ncb=V6XybZNGw;E+Xr<{PC3-cFbqR?4`4X7wFmk +`6cF+eliQT?z<`YoZrj`wgC%7bwa8oGFox`m=PCq>cq_^iPt`C1CVKTZZ_UoKnpUf#ka<|zL+`YrQa? +Te?c0`9e^favvV_}=DQiPpKSA$?HFlxX94#7Kx2iSG{OaETMf>qEgnxLiuhfF6S;1Nqnz^?FN$t}RFmcuc)g&zNoIU~W_tkK4y +vl7i+aHS#xTfZ5r3f8xHrrQa+BcX1qjIlJJ76E7suGCpDfO-V#1}0~g8Dh~F*jv08Mqtc^RTW%kK)Zr +>S$c1zD=;HR*B0fpSF7Msh6b#4#aSKBJcZaiYB&o8UgL6goo6v+c48yzq6&+r71#7eqUuxS}QWuoS;k6JZ +0NN^YO=4hRJan@RZqYZAp=(o4p-o070s(U^4cBYb)8tx6^4Yb|a{hLR&xt6Dq2OlUf +qG8HS~P?_{+W@;V$Gn;!G?Ps^J9Lex(Y;vG6!`N&~%XFm+ybMYTNCLD8G-^Fg%D&8r4*7he)}^kqH7# +A5@dREtkKfYj*KTk2*kLCE%gZ4M5Q5N|JD4mz+jdiTBzHeU}MAxr8yUtmpf<7^I>eN^2(@54n0+dXh- +=Zji!WikhY$1-e$igOe;SYH5iCuyLzbORgMPU6X2}GlCK3hG&?xGCJGD#iCG6)Uld^TjuZJ!o+EfKK@ +3Lq3Z845Td+|bwD{)%QWP}qH%tx88{lr30CO}Cna^)JgVpFhz3?5moPb9z|fAHZ7V>+2E8M@&}211y- +Ap5y-cwVl!&lwT2k483}!vwh$jN{b^nv=-Hsf{i%9( +9gt^23UYaD-#Pt^!tHQbr9!xXc)7+R7m>JQ)e|iR`yS8P+7Gvl^(77R! +;Z|HO*FJg>`A<=CQx5sG6_!CSSM}Tp3v5yt(uYO<%Lohl>n-!!FheW3(VZYknj-s76X@j>FWS0m@8l^{Um2tn16IltZs$S-r) +P@=_1Z<~)*Zu_Z0@&fj(H!#C(db6zAQ7mE@e%R>|{Tp>QD$6g0u0i!Z+!nyi3ik*of5p0>mJz3|_&)5 +pOKy&__}WlWu_k7s_GhrcEqsO)3<WHp6ha9(F +e3Y+(JH9ZtImDMS2ex*4M9TnHU{!AyN@^Vh#8(l#%>CV@^$eIlFfkd8g8ATUmo=NMh=n33Jz7w_M{SG +R@XPE-ie3BDr7;c>Zm1ny(DOzTH~`0Ue9zj*pc1l1R#y?ybODo6%j#TYmk$;1c67S)P^XrYT-e?NwQ{ +(%2{I;NUe)F#XIa*cN+zviff8^c2|uyj_dUS$pI>#T^&YKi$4JXr*u0qwa*)Jlf%VUHX)bFPeGQt5Oh +<&5>Wt?6O{m8=$v^>jCYJ=jd6YhXhU9Vl;h*Ck&OKd;i8RUTDsasr_+*hb)8cU7hX_8$u$QsopPk%J52@eG!k)8`|0Qg&s-GXilupKU)v?`M{%Bms|~9k>05KQ3*ZNIb +#9FZHLzxhHVn`F1Q`oqT^W7JoVUx2te- +hO9jglWfV9NlBFw#-tC0IL~g12yakF1Rd@=nzqYnKHdhb!z)L1wP1Vu5h%oR0Y4?q^?fP{5986{L2E5 +hQBZZAH5FpGImIS#3n5phd(8=ip?J{3-Jz63%5_zicCu=RYBHxg?gR +6Gmsf5x`rB_Mv`|MttocIk%Oi|kF7e}7@V{u!ulfeY^*9mlf9X0})2TdtXA9cyG1i#mMKzdEa=>Wsif +oyfPlIeM)+6!9$>d8>CYRaOWNot_4^?x@mqTI-O*m&!%b#fNan;xj9&e6Ry=2jo1SM+9zBwi@}*sm)fh!Kh&IgXXoAhRnUlk?Kw +>3g#%CLd;b=GTJQ3D&U0f51_K&L~W_iU1>?`Y%!QrFcJn<8-zZ^)XkVb7U?_YiN!b-+x7Hij_dy2M)) +fWgYL6?j+YrEpG)RY!naIDj!V8-e(}JllFUO}=72Fd-x;AQ8nIP(g{<*zBx-m0q$~Y$0D63Q<<77|b6 +%&(rdb+oQJA-g>4&TR)x_>tyLXAaEAJ6h|`#uBMEgVz1|wK1vY36`~Woi!k|PIMtxf?LqGqj8&gOQd!J%N7{A +*kfj1!!PWG=?gJ|FE3ADoGBM;#x?|fK0pRD>@TdRbc6VI=Zh=TyjILfCE13P(t%DMVhgt=%H5Gjr*AN +ZO4mufx3NuYHT{z2XixLGKXG9qci1y20ZS5o9x_*yP~AUR1QV)T7ww8Lc1{V^M4g3!qHA$s^jE_p +Ds0xO#?BYRw!-ZKC!Lb=mIt${R10!gN$ODd4`L`Jpv(M1DI-tf(5dSt^BZ1lqP3f|HK +z)NQIKo2}pySn0p%+d?TP@K--0o{s2$t-p5cuW^enR|wMawkXV<=>+2Izd`@pv@_1qSiL64;NkwO}5( +r^o*?Bi9CR$=Tpj+*McUXz$OPvTlb$9>NZq7nKb5CD^Wd_-km*aQIybLsC8^Y{g7cwg4ZA9#CPOebkC;?It4VY?`#Jm`r@UP(2AR$FAGw@u?-p%l%g)lXAd@e +m}Z3EE8_#*B6kS=s_RHoJhyNIe=k_o<3H`u(}tm?;9zrK(mxV;=FSERG=#wtc=4;&*Gox{|0xoN# +D3pyOa`_it?l_jBi`xo#rPxGP((`q!Y1vEGRY5@ECPm=Hs5Rc6ukBDT}nu1DvkvlyRmzS+)d}aI6jzI=nOm_>CHK?RpFRKCl4mmZm_VYbfM#E)A&6%l=l<8KzQsppaECTj +ZN8`4$S0(lJ+p!K46NN+r3S&e)=c{H_>19ti=P9uKHTA1}!px^FhQqVxZL#V3CA4K#i8Y1Y{Z8Jf;UX +}4>#_w6jiihf)1O!17~^ie2za}|yvdv}*<#u7L?@QRdW46yiJG2g-Sj&-O4WAmdHh+cShB`mS3x#wch +n{m+yOo=7t5&1YRtZrKm_(%&z{pOLa17x#tVoW_xPR6U)#O)HnBcHpn%{muLQH*Dd;-K#zO1AWILA^M +UEyD{K%x>EgD08p})>Pt^C1DV&1Rn5Z0x9k#3~Cx<^*3a6R-+*)giuz;iWpdrxK*2YuGbh~bf?z8uW} +(^cxZTFGg1Koz>|ipq);-b%%`n2dwf&4n2Q-<(t>jyH7%zxU^D6h3AO4eJxOV8YG4xU{uEec1FSYZNj +@Qtbn%L%qBx>5?RckzJIQmp3<9amrUy>I=rAI^`FvuP9qYx-Uy)h13U}9X@@2^6KLB#p%f#_l3om(ZS +!QSNQMagD<964}T|L<7K{@%NW#pf%yV}wUFhzy^JDLb*G2#P?GU(so^VnMFL-ZqAp|5@0U9g4=VIU47 +ie{bRB?tjWrhQ9P8uc_(~uZn&GmM=MeP^u2GpJ_-;BmoW9JmR@YC;z5X@5$C7R~mxtBqA}K7a7pBl__9FErx>{~xtM~L7 +L#mgdRjC?X12o=8ldn18+n0~1PDv6?U8_U4nn&`L%`A`Ykwh^s^F-~AM{xu8vYme09`Et2`51SLV`v@ +w)I9W+;0*?YXXdQ|rY)BzS@Gs4M=5VV<7RDZ_&Yd@+6Y8LbBdwF&V36L5kua_Xzlhoxii#@9*}%{QtY*|AyR|!~6c-Ob_2boaei+>bKy*cKYu``(CheU-Q?hK6VI)-{~f&El}-P{d9cF*CHO((jN&d)dj8+CMhVhyw@Cj# +VVu)&=f%U^b;HHY?`phYoz^<%6qLwg)hfwF7v|FK|KPXJR=G}m*;lRVFUM!EzQtGYzJ7Lbaq{MsbLsW +)ufadR{v%$epS}AMW6rBjMn{hx;g5V)>&x)r=y&F+%kR$KJnsdD7ia%)|H%mcH}-&^pY;#_L+I~w`s( +?~_x-&tjb1-oIm4a)`Ecg;D1k(U(96Ecd1xpH* +>JFQPHUID{2iFzsZYU=%9~Dw*CwKLOYb0ZO>xPLYH$2yL}(&c@<45v9JZn!6V^mVY*fm%Nn2 +Cyi~{4SLUxV0^&_~YLjZmpwee0e1;nOfeyqw@qZAwxGZtC-iulCDw=Hx4JS39jY4;fA48xj=cn3BS)C +P21xg&YGyZ5jmqN8HSfOCEX1k^VRS;h+jz&7I@!dh%bA`_TFY2F9uUssY|5fmNC6z-rH%%Vq5jM%WOX +i7j=6Ywy_>=Iz*`3j74)T_(dJ+YeX_8xShwmrGt#6wXPz1g8y;jw3V^+P}gIl3xfd!?oR8$Q5fyW9#17GX{~f +fh^<8>yY6pb&img}NW1JQCZgM~f4veiX~H0UHBAP>y3YpZUfu2jXz}MBv|QAbhp(ege$sE0lmzIe0gvY`0r6b^rGx=Uv8tPLu_^8 +a9innGsZrh_@1ek3b`S8P$sB8RXA!*}z2m&bj4MXM7#|J;xAt8wApMHT*duGPVxscIV;cr)@Y)$D9Qx +S0FOKO|>F9g&?tQy$Z=Z?e_p{2Kmf#XA-=v3ntBxCCP^zjQ?2?1q(N#~1Fb@2$e#L7maTYxXQ|VoIx- +Ohr_7{fZUo8cT2OgUcyrSaEYe)5z6wziA`?ksQtXa2LpvyPhsKNoZqgWw8X0#(AUgfhzhc|s%&l3Fq3 +s6e~1QY-O00;p4c~(<~Kx6#)F8}~@#{d8y0001RX>c!Jc4cm4Z*nhWX>)XJX<{#AVRT_)VRL0JaCz;0 +YjYdPk>Gd!inbJr251nPw;jBrZ)u4$t605G(s*WrqX3&gf$U+U8`Iq&3H$cHUq0&nXn>lr&yHJ!JtEL +mSyfqC`Krw9$Jx>0Q8sTDWqoy=ZTjWWAMuxi#|MwIXU%%smRHw(HhMPBzWD6(FOJ|pf5<*ui|p;Tzi# +U6eY5QEidJObHk*1;^kq{|q33%cvX?KOoxOg4Htp~Gtf&{+vp26lynFHI??1eG_a1+Qb_b6hJ$ifgHv +94q-(=-#U5S;bdjKnG1cjjB&i +~GJRFV!#CMR#p|t(vPVK!W<(=$`AQEAQ7uf31G#w))v$%QgQcCp%Shd|q}q*4h1BtZ_k;tQB3eY3HJo +E$7V&fU5TI`xXX&@kTzp6>SH-)YB``&${_F;AfJ3Uw4}|{wfxK;Z|pD+qAOdO7sQnL!o+(UdbOO*{ib +;r_WD6oX);|@$&5T>8rCz_QSuQoxPle4-T^U=jGXVr_cT~>@b$2Z~C&*Bd?3L6SG$QvMF2I*-17pU=f +Six^32??YEPR+tkHM%z#E_a@o=GTTv`#;vQjllkBdA%{O14z5mNQcrIG{^9Ov`jt>qFX0xKIfO1Z<^B +lLNEiX5?9r+~7{|wafqV9TVAr|gKd;C1nCd<#Ru3ps3W}vJ6=q*rC{t@PcgeF>;lBBpP?CwkYZ8T$4% +wXzgKfZnQ?!$YHVrZYQw+jGxBv(ECxq|@@p1pbX>h$&VXiK2672s7hJ$U}&{Re9I9vY4E7q8!cIDPr@ +>|GwEj{M!(vp4Ua=k(YA{{HO!hqLFozc=vc{hROKJv)03?fgkY&-ed+`VP8{dGHsnzkM@1`|$(LaY|D +x0fR8JgLzeST{e4$6f$Zqe-`t8d`x2@E5LtGvyPYqQ&F}m)@#6Ch8XLJ!x(@W9@j4Gn`~k2K<9v4h$Z +lrvM&4CY}AQrDTlM$;O7OjD2uA>1PqduNDTE{Opyx$-z&SUZrT;l$2Mzfku94x6EMHb-}-43IF38T6# +zp1_FwMLxQ58jSE2^42d5vNeGA-A9O&iDD^avVYk7tZvi@4)Z~_qf9+6n~h^(^jvXYmZ!7_WXW71O|a +2(D%{$yb4#bQAdpU{parhmFvWQDV(IBUHeGuva}E5(Xh9+SB8`@nGVwUtY1%gC=A;Lo*L)v-hS`sZ`q +<0C8#zl7}wU4U&^Yq6>N5p}r{zyi-dj;BD%qF&ICV|^BU3W}eKcVZ2a1-L7m0o=~4S-M2eB#ZnD2o&1 +T%0~TmUCxPWL7LIr$J62CF`=F(uYt-Z0CQe#Km?1cs}tJE6u|-4?wx~^k)+^hOtOZABh7p03wxD!TD3k2As5bUViqD@~F*N^?QmjVdL?@A9$DZimr#l5mM=ZAleY%C +C>+{|~QZUrGL@@iOpIdm^mZDnB94a50+<#(oTMK~G^b`)&6u^(@mn5gK!931>5Lqn%p};9%cJmuNBX= +Ud6)XoCS*-zbxy`J)WXoth#I@rdLyb@LXJ$Mg8>q%T3*HU}szS)^ +}q>Fen#bSlN)atE#MT;FMpNpkRPX0w=w}mf5nb1o9Nx7*XGrZBwJhJ+-h66F))zX>9>=96tL=A-62+1 +u3g~y;J^a2AyZveni5<=>+JK{h~~TC>P8zK#Fi4;5gio1{vjf02ldko58&4k7?P>5K>WsVzKWaC?o+B +WI>JEsa}{n7AbOd-=bimJ|TF2y3~9eG_z;jP7S_A9fC~GS^|o&R}>jE@)T5v?yiI#u{G^+d=M~dyApW +73`vJoXt0s?w*(`fhZ>WSPs+ah7=?jbr0iY24#aJ^xHuBi>SUyXQ1)1sRJ>lDRmG#;al!C!VG7BHcF5F^AMXt +5{U7t9Y5wI@02I(Zy1Y%fn2;&O8}%E{4`J?X&Oc84|%iMTZkR*(DZqDK)6Kw#BzT>$2#lvuI|EPX~Bvrc2yMB^LmK_IOsgaY)TM}^f%pq^Bg%hrN +Bswzdc9DtXdvOUPanq*P`R)Sn?1Rw#h#p<_8b0L5E28J)_6%Il;(M|C6dJ9T`z1TzoK-A;kh-!fWTsI +3%tT-z?l`y>)MZsuZbrW(!fd%@u&oHOyA5t72}^8L@k4Jj2GUM#72UvxS~4AT4-me00zXCoLaai +X~2-oJjbHL@=57_)WR7>&@yCxQyB4W4&VPag2(hSv5Ln&;eC1igw}pZV3M6RxStq^)WJ|49+9RNw=~1{x5 +H2HoGdF>r+CHsWFvYaIkR5_DTZFvog;qPrI8a7QF5bgsZIF4sfIf)Dq^H7`Wn=`|CU>7kEFjVT|QY*c +s0+Do$~tXmDRzv^e7IO$p?@0&aUBiO}B24rvemNeSo0V{py&SCyI&VHZSDz%YYXl3thp1y6H+j2p^>W +pr#U?D7o(EZaTWSLyMSu3%mNIGAGDwvuk``eg%p|*gG*ONwpmV#|Dp)wRBIB-4mznmzshL +YUXk6#;9x-X?;LP=yFrfWt(cNsGV9b%NAAz<~cAM*3rmkTQ}LNX$1=_TN2Sak;{tvHE;>BP>$Sdy5Z; +)jEf6&Nq{(mpLjfU-3A0dOyrY6iViRhh*85v@h4Y#Lf(=n_o|vZVY|=%?x#ZCgxrX2cEXd0Q;?o}aPy +%)uD$wcXA9RW&3pT3#iymG`6sa?)B@QAUj +u({pfM>2u51BejGhVUOmv^g2}oVlh-o-Zg{8HYx+R>dvgfOeOF?%b48Q6HUpMj+yI*(sL3Nl5=lpAED +7%=?k+SP>fS&IEl;S`awi=`wQXS5?imDZp!f0ypZb(XSz+^cmbX#igpmrqVB47@nAfXCR8Z_T#;#UF+ +^Ll&s_UOy6z6QRrm)<$3NZIrO1I(|gwU#U#x&!hZFmyMou<2-wkja*y +FwI8Jp`WM_x3T%m-vH~yT{Ro>;Ue-VYr988ZwQWz@3H%?1-qVc|Owbr#J~s_DK#!HuZNfF2jsg!0@aG +ju!w+q7TcRf)-TVUd^e?~4?h5;^7MahY5nY}49=z+~RxmWFPWJL1G@DMR0ncnG&i98u`x0`gi +MNinsb-znBCBCc7DNs4xtcjV7^12Bf3Jc&4ED?}Bx$vcoCK|@6sM#|@TqL+?<1j6;n4x306rIE#iixW +RFBNd0ArUUIM}|F%F$FS{Ao0{-C`H?s^G#K>8h9Yb^b-z*P~UJzI#IOqYklE`5fFZ7mNg(tO-KZbY&8 +$^4v-{d3MGe=QmtcadpIL)6(^D5Z$78fOTzoQ@F*Z7YR^@p@Ip!Jmno?iGz6kZpE!BiSm%3ygB0;{OR +#s?ZDx?$Xv9z|h7N;5GHxC?9407`$c1FZ47Kde!^^CDMH*)%wYn>|r1-5iUC&~PF17WA34A4@$U7op` +6phjOFtye0Ms9t!e-8~!)mhh$K|F3OQkP?#V-U#NQrvB)v;sty50*4U_;L>`M7bMo4UE9V6RKT#9A=G +BLtXP07vLMB`~=2JiV+~_683WlRWljhZ>yd>}WRm7?9jFl^|cLqZ=yh;!(c1&@q)@it5WU +W!Tf6J*}H4ng|$n}SLZjymGHEO%Pnf*tvyfNNmSBZEdyhq3@XM`y}C&Jmzw0m0wsN3qM$G%(s59hTS; +9PUce-}IZ!7%mrHZxZK@rJJh84I)t_zyX<(~|Q^%`FEy(({LinT^j?W)6`NDZRf>nsRD^U_;6xZFBU1 +K@zsK3&yt?_(Af!bBgoE&YQW=_u*<(23HKLI@w_kCM*@)7_37~>pEjqRyD<9wrQ&_h%~V#-*HSUJ^D8lu%t;&3ReI!&0b4WkXYgNLM5-SWe)`4}NW*Gknz;tAnvDepCvF*&A3o3= +$a;s4%fIYhg29k(!Qa*3nF4JT_aVU12T8h%?hnEluO0|Nvt&u)<05{eI%{k#Jb +m&rCaI^*Vc(9Sd38L3pc6@VWU +RLhmSwxVsWj#;~P|9h2mD@ByF~vS +`8l1rxTFZ?>p!13hTAzk2q-4~r;g$DO%JgUx1TzK-PMl7W&5|YTbm=23LPo7 +cuL*)YY8YDEjJY?UsX3I6IDYE^cQ%>s*BYKwM+tJ^C@!0QEOmGA^$SEs+z-u>;l#$Q=zHvtU*_>9vS^ +8&s28AZV{YM#W5~TXUCAzald0dd=1JLJ8JuDg$FZF<6^NfI$x-^z^awnMmesQ3&B=mO~II=SR`Gwl5K)WuQC2DpEsL|qj#vAlCn=)K=-CoIY?w<3`c4?*u +#(1KjHT&$nz37u75R=L=bx^4m?Q8Ere<-V!+o<53F-1uPTh`f*|=hgpmpeadk7uTlHsiQEX%*PnZZd}x5LR~%B7Q`@Y;Y@-m3JSg6}~*_B4q?QMXk_5B2WTHp^vs +j~YyN{-pbe`is}z9H{UzPIt}&W$}O79)?V+M^Cyx4QWUd3DFMsn}Oz_tV*(MQyg0e&9NQ*v1HA+Bh42 +TNrDpaYWg*v0mfz;2L?{F59B2Pjssf)4{hVh{iu<=8#Chp(bGcqFMXv6Sc;LL8f>;EqUu_r!dm!lae= +6VS&SIP%C$v5c{IwK9OVchh3JN6%h5N@h*8E!V$H54tU6hk80u%)UAn9JEl@+l1Bxl%!w2*kkOf|)U` +zI{-WAH|QmO)ZVv$tPFhdn+(85AU22mJGWPVXbg!7}lBIVgj<|>aOL7Qq4bj2zO#&i1_50Fd8Bjs9BY +56jqhy%2J4;aqbTp{jq8%<=+AL{T!7uOGUBc0YeyhzeyW?vHTUs_5#Rwtv7?zMH6FP-vBOs6UJOjA?~ +ADQmxIrwVn14N8CA%%9vBJZKRI*~U3&{&6LV%#RimFPI}i!kuST9>*3XXl7W+1ES+*d(5SrXr01?5mL +M=co=$z+^nqrDw>~(K9&^iVcz))Sl!)kTK;`O?=v0={lj3NsAPvE}z2c-MbupIwu*EGL!xg9e;-|>1) +YJnkBo&xic$`&czM2Dypld1$l(wyh}4Xb>|XZ>KH#N(H8r74$UC&h86@Md71-O>f0(TwLVcb@6$AaghYZO}_ZzD{LgcefdZJ2LHilUw@;Xt +rlO)c;#VmUw{4uM)Y!=m||x@m)12GA3A=NK40bzUY^q(A(HAaq~NKHVimt)rnbO{lvP=?<#s7FiYM8t +=U?xD$GO1_jz~}uT%dGxOb&{8dr4wbjscR>!4oU)!El+EJ!X;*rlPYV3e;-oiq7loPhbHJJF +iHH*tM!z`u`(FG_#B<)zonxRA~iUHQdQ%r3wAO4L|RXT&YW(-w(&J-P(c0~|rm@}irUWhAWnN%y293x +Vf4k>eQcCa&2eS-q#`aE$?rd=8O|A0{NGk%QXd%pMSer7t5JQ1M@|!OurV3viWhy5RUMnH`4(RxVK0K +)s+>H+X!^K}o<^vO~qNFAKfQ9MfkzF!aMSWIeNoHf{k+7dQ%s-MOk6AD`APrD}W74k%@J;eil7tiOEb +{^Ei_KY+WIG?ary-N9c5?U{X_tDY`H(J2X;H$!`8Q`yjBUQUOGJL<|1mT8Jw9Z2HBQN+SENmVq!QfAm +D&?yfn=DjmOm{B%*N2fz$S1?2e?sZZi2OAhp_vXk8pk()cmR0eIkck=8fCCz#&^25|)%PO-V4W1r%+S +aj9MSLtkK}x@=B;_gQb~r_AhvDdE>kJ+>T#1Aeb$IG|->heqxD^%FmCGF-pqh(&Kt?xOdp-}^v1Tm*6 +u@aObK0F{Bz%ZB1*cgoMn>jQ#XK)@+2fb&2}k#C1!AKcjd!~YwZAR8m*{zZf^nbl2fN~n~IAw<~XI96mw8}Luhoqo0jD}2eX5q8nGLqq(VKVF3K}bIgcn{-W9i6FIB;El!m +IL?MT&YGt~I&7!vi?mGws4M6x03#G5^9>3Mi&9+TTDqj(nSwB@h_RKjq-tWqtDGSu=PqZ&HX-ObroiM +JL(@7=e!>_`)Fo`?N$>)Ymzf(eILiJw*3kx37`^;%VdE2(c$Ux`r#`~`rGC)tWc&TvU9F-K^?%1*{eQBBzh1S~6Lfc!rO7TTaLT<(^HqLJ&;!+gm4`0aRo=NMWoW{Xes +J#asq~SwE==XY2a!n`atvN{zk41|=Q2 +7p-&CXfB6?b*T#$GXUKJlDBx^f@QaMsS5r60~Ji^NJ?s7q^C(NPw=q7tfjVD@6jDU$n4ZUot$`^ncNz +c7i2~>h$Lske~7JdMMS7fk4bw5CS9HyD=)2(eYE#dSx3lHX1XPv=0Tb1<_N2-1hgT*_))1nRuKw}TiM +uUKQ1mt#^#dxo3xv#qy!qgp|I`TY7!xc=wx}PNrseeydYbgp1#YuF>yH(LlNt(z^Js7y4}v&O+8rNmV +`{_V-A6;OFnbW+hN54TsC?wwf}dToz}*LBrgSa^Pth46xRL0YZlxrTt_cz3%BwfKwb1;>1%aU#9nFm< +&I^d%`8$;(&|>hb+<^=*mW5xuwXCKjLd`$`GYlh{A~)G0V0Bmp7DbubZu_9bC4FADpng8YoT>4kqwUu_0rE_r3U4brmczAWH5#vpqdT6Om=i?$E~+>EOP_ +@Hp${|kF!NX*5i89v&o5voe!dRQXGBxhi{_2DVTfn~0cI^ +Mte!F#=U%RtaBtrN4%eD0CMQeA`q%0Ce@*|XwLV_N+aE^a`;FA7RMLS*N8^nOVRGH9G2^YYGHsybsyO +G6g85`Q_cDhpL5HT-4HIfVUh&ZPq2~SPi0iI%JfPGf|rC3j6D+*TQw9j*53lU>NH?j&Iqu1df!txopD +cF;!ujD)JXzY|yr)%+AJlrni%TZ*~+n}0b(IpuB7JZEGw6Gn?LZ;hHtk(T@tZW7YtL$V@+B7>ufvYRl +(v_ur%H7m;yOiN{)ydLEgOe+e*kqSE;uP1HBgyBu=sYXth)`Lu0;9>nHd@rV3}n~fMg&3BtbY>1$;sP +d8G;^V5(1WgBINNzWjRyz4h@Kpt*Sm(%qen~evC^mgBI6+y1%UYM2VtUczD3H&JxwpymRsppI*u#7xT +lZQtzbY>ILo@hwV*DU8uxoWf7+9>@!rw#qe3X@(W5ha*lAF7>8+LCM5}24x^|Ti86LA@N6KzV^NlL^C +nhBCuAb&*@71tnO0}WBWaW5Cpi+$u9o<50#CTH%qIXKlkD>#tH8`!v2|uPd&Fz7-NBh2;x +dT9pX{FFQM``{ITqC;DYT!ffiEYIu?^jC4XmLCQ@H3)9>)%Ca|^`Wa>TbD90y5<_OAKQY=hmR(>s$6OHWN#&M9;{=Q~O;xTcg<*WTVDC-=1tlokYyRGNfx=o4cS +WS`l9-SdTZo0~iMMKj;%KxT&<9b0r}Cs96g-v+O>oK*?Gv#0aJ-M`AoSU(UJ25zHkBbb}i{g10 +7~$9p_0l^38Y`ylyH-sXNOC-@)nb_T+R+HkC*DPqi#O->`^?Y2ry^YbApgs0H6hqb7opE+x+^wyuxsNYi?KYS8ODK2Un!47ez3TnF +oW@=0MIP>R!OF}6`GaKSO)C>z(A2CH3!xmdf24QQzJoAp4ngw;v8(Y67ABCSI;9KJ_U;fmQC+jpEm8)Q_zt`i{A97s0U*rQ1zILJWf?+I{{H&5O3aNm2efQS-(%Z)$U +9ylZougWjY@!;flbi7xz9e;Yyo9d>1uQ3F_t23fm$4UX!A&;$8|fpNR{e-U48`MhF}YadGkXKfXJ{w| +>BKl$J!79ZD~X)`FyJ4YZl`MpjN32ZIm`2>_h5K2b4Ty5T@qgt@mPAOVGOy{5)*yct1FyOTPqTkEAZ$HkH`LGmN3tDRUr& +xL@PZs$mp(W~})bwyS=$6@T@4oV^x00T?~Mmtu)E@o^!3r1-Cyvd>@l%?YAqx|lV&L3(18trTHJ56D& +0c_$iMQ7cGQ5DV@ysEVOo<*J(z6tB!Fel-8H-x{qdr=lVub9@alf9*@KMISdAa +owu*$_|E*3Fp-lRO|%P*dv1wYFD5@sUVt@dzq(qrY~qzvGwb5lOoVmLpK)tV=OUa76`YjyccK#nt5eN +eS9>*fVj_22KkhzKZtj}$M-9JJsDGPg$<$YqBNWRKJ#-)TF;@FBFSCNFCo&t)QZOAZ%f(uue +&S=;T!n|*4zCHJncloA4eN#X3jDTME; +kT>!Gj|FmDg^2_X~;t;WM|4C~&{C8CJ|9?@@@re8&U_=we5pB-SQY&`KOFYqKlF +i_M4~mSILn#OPU=%ZchAig&qnSOTdRRnrB3AUb%GZl*%ilYB&se8BQuiD~c(z)69YZjx&wd2)ZqrhF& +MX1q0xfHdxr*eAQi`e=MXh(MZr?}_FrHqEdtE5cU66?{k48Y@#dBZu+yefj_6NW1vD1;5)_H!&QH)sV +c0$cVA}Ve7VM6Nf<37ZXZ46`CS<+Bb7$SFBs$$8b>C;F2o?3pD7P;Kyi;ONsf5&Bfo%%xwNCnjqvto> +$ioWq)2_`e}Mz_#oSRYAw(aZaHipWcpXpw`iADjuOK1qJ55kYsBcc@q|`JHZUv$^7UnI#y>``|%ZIRj +IXQLmcOP9*mLQ7BK{IPkwYJsdLT!iIE7wzJSgD#Bo*!&n9bTz?&h*5L<6m$kwW9u|IJvH9e!>|W6aCV +)#}fC69-)i;Wty0}2$hec><#=6j}obCY4^=(mi82?K%=ZY$@kjf1knr3e_-3)uzGK6KY-EfMR3klcstAk-8#_m65C`>Kxh|$kDB<3U@#dFY93%6NhedYzM`c0bOwZ-~6{tni +6DrDAu_hxp5U9?u>yq7`pV=DA+I&knGJiad1oC{{k4i;!>7mJ^ZIX0c4SOcy{X8NjFGrMwM{h*6kAx{ +I_KyGJMT#5?q0$FB}{o~Mpp392pWhVm#wR=OErDAbKb}oTYp+grJ^c8^8t5<^;F{%3BExEM14M-F$HL +l`^#?$Py>Y54hq7}wHh~=`Jm-Hg8wm?WlHPLcZzzZO0isX4ayr*q(f7>2zlWnmWz +O4=4tKjyKHn(1gf2@U?k^Mq4Bu7cM5za8N*Z0K$VJ6Ss$xul_661s??_c`z_3H=$~k`TxmZ>tPQAaA^gJNeu +tkhg6~@qPUMd9bocbl8Y$Ha(VK0{+OcpJu)^Q#WJMR?74dh;Vc794t!n&SPr;R_dLWXp=E;a~l|%4-y +_CO}$`nB#}U8-08pz)B#oaQ=T0%y0YI>(GFUvS44etsWBsuu|m2+f&}n+(2?FhuKKH7?4Rm9IxlhG(< +_;w+xh3mAH&y|=-f%}V9`O}%odHJEBGIpmxx&~MQggNIYCR@B?wtEuE?Ih`Jnb*GYGN(q>iC=D{}4}E +^StU{+D1da>QUTFaR+A7`jG6Po1NcX-K5g6|B-}C9}^+gOBNRtZTy1YxjLr#2l8#=t9TwWjG+r180A6 +aa5Hz0^R&=;f~K8gS8-ULGMd~x!6rD%>q9v<@M2`WOO>3^WT1|AG!H2Otx@@y2N>!0<=}EL=RT=#(rb +8Uq2&@NBJ1swZx8(eyYv|9Rc?aBXZtxyy%S>j;s%E{LtFi$sggB@X?<#`uChQFZ}!CPneW8C`4=qY2N +u5ig)!%dmy5-`Kk7~uo+|=gariHrhUMzv40$AfAaGa_1KChyF~T3fL-VgjOLT<(Q~WAqjdYrt9es3oN +c_IX2CWSKP~HQm~0$$A(AwXB0*+W=67OV|K&XLezthr8fJf0&at8q9Of7XAn8?e2ta&2%#dFxx7_l~BeB&5}9EY@}qi1cNH_=XsP5_n{@e%d}rmEZ2oc$?h0)Nem4g0A1 +O_3qEdD-$S|6G%v-T|9Q^v$>PYFDXt+wRI<6*m~e&|Q=IoXwl{R-s&TGq+NC5!%mGl3|kFK8O(#obww +hC30?M=mJJTY@rnL(+1J6cgL;-L?Ena)!YhQ45!_=c}P9h9-tOM(6K#XoW`iI=Q)PyBzfg+A}&b{rr7 +dL+{L>94xm=<%LR&7QgC8{r$6xf*DJB4L(e-gD#_{7WB$&VY2Bj&^$D{U@U`%SAzj|b-E<@cVxnCVeM +NeQ@nh}s`3Q`&cFcThg0JeS5d@qMWtZv2ds!%sMequ=8@|3`isp;WwBR{+BJmeWK{v0^rTMtSN3RH_z +NcFyt^_AGMPae6(Rhy+_rOVg7kQ(%WPEY8-_`IxQ6F)YY)@hK)a$fjdw^?O0WX=+e5qq+VlJf#mK%rm +D=OI)abs&rWTQ9c&9-h9X&gJSBve+7bqo#t+W8M~ltfFkKGY~S_4#>jvh;#g$ick`@Lu?-=z#H>(VHG +;{AI-d@MbY8Pl9WOvFm(ACQjZIbmmf789Y{qBMQG8>EJ=41L??Dw=6R7>~d393(E+h`}`84K!Hf_m|; +2jyPn!&XY{a;4_vOS;X1^=R=uYZwx`_kAvZ~+9Jw51GkVbwE~6n&ZlfoUd}bp5pHJKoB!(Pds-NwG;7 +*q7!9%gW!m2?Q<8jhgA>A(ASz+3&0)sfzq1xI1VPsLFhWRlrp|BzCYS4WlZf9YvG1piB#m)osqG)4rS +RB(vuMQx>oys538xu{-`M%bvVt-+zCF1M3z5t~b_ecEN%cWAdC}G>bHHwgcj-#1V-9Ft_QPh(*JCcRk +`NhX|wcVXm7%UAxGKU(TNrywU=w=dKti=b!4-I7t%Em^l_}SNPClyBKlb&qfM&Y9|FcWz{Ix4)NqpNp{N_N~+6z1L(aKU|L;?nnIaULYXBzIkUS%?M9LclI2FC7K +e_=-%H$^mdD=RGA638E(OKjsPXw-xoE@P30W1DUzj4Lm6Eu!W{}cUede1;7zuS4=a=K4LI5- +2a;bP7jq!7h42;n4c3E2|ND9TpKi~kVq3~M$zt7Kabbe5`L2HZ2Ln3&`(TUxx{Dz0e)(}u}h@YRejO& +O`DLg^cUTzRT9X>(WSR+WKLEDBPT@-m*^5NkPXjWI97ua-)F?g`kB_+3DRn=y}F*?N!y&;WhhpFLb%) +cvGAwgq90-rh%yUl8au_`AS4@hXs<*yjj{1#tbdEHd1prU%j7+fvvBtkuF`AnC+fq&FbR=pZavxEm`C +h9Lt{7DUOJ>0|XQR000O8`*~JVGU-Un))W8$15p3~8~^|SaA|NaUv_0~WN&gWWNCAB +Y-wUIX>Md?crI{x?Ob_}+%}f~zdr@nUPG#+T0XK9%=TlFk&o}Qm54D^p;fkObctUZCFM>Qnq +a+=QoMNn?CRy4t4M#;A}&(#?1z_cUqAojySG2QenSt@?kw9?rP5*>*V{Z>xu1Et*?~{Obp{{4#CUQ@| +$!ZM(t{5OVWM3!cu8Os56n^*i?W;e=b(TDB^!7XOB$sL~Ud#V&WUc3dUcZX#`nFW*_Zu~5Tt-);@+wx +foQqYH<>{iVWIlv|3Twmt94ahJbYN~t1K$jhHrwl-RrBTFTSUC=J8pQgDmkXPOjsPd=BOL7? +!IaWuj+>;rH&{BSJf +YKh4JLTVisiHy$L1{aWOydy9d;DZ$JUcV6Kn#p1$BJq4JiV|vH$s}86GBq?)yEWz?aY1DDq|!8)*3l$ +dgnNWB!3z2xaC)-tweDi$dzWOy-c8-qQads-6p|xZ2M#v*L{zigh@2!*^ +h^we3T=a4)*ye)ww!VK!ui2w4wW*B6}4Fr*rij{Q++qJ(C59&ZX5My-sNo6PHVr +%9sC?HAq8`X8bz`P>KOgGq6n?uhR+Y(d_Ub1e}><-I1O65(_4sDgoB^AQ0TkHO9XJJ(M{4D0?<*F)sf`E#j3huXhXSChwZy|qB_Oyn;SnU8 +TrZM1&%^gn7|cs>vxdNe4`kfZveB1B^Z~PY_(ET)6!)D?_Jzt3GMD1fG^Pxsli>6eqicl;ay@!$z041 +9JP$Lxh|8X3fe~Ji=~M}(+inM~_)t{&b*>SXIWdR*YS-!UFvVp`nc31@z=5uyQZdi5{+w23ty}x +Jx|`9gnd!2B<2h>A7+Rqj3#Z_NkkRL{HHZtN~VZc;N)x1El$X}22bQr(7mTt#x1MH103PhGipp0e@uR +#Z06ER%7R!m=l8~C#+^%$&TW#Fgxj6w0^arY5L_c)#xA?3boRw*j=BX$sI?$i!Yh`qr-2pe=^%pk#IW +cBlSTXQcqCHoqBzB@v+%-hMs%=xRL}dg-W^0tsiJPxPqr9#)KxuNWtQRmeI6E#7q{Cb(%zne< +nt5YNOPWocv9e!a6#T<4DxKxe!mq1qbC9%kS;8WgEc~nuvn|7W$UU7_x~4apSO?CTUvVE{bNi0>b2|o +C|BwMjpC40><&i4{clbMG-Wy7p>|F9!}yWk=YF><{8OWq`?~sL3UZq$e3_@G@>#I+9w)-mcbl!!s&SL +$OyOZHS+dE`)}U|7v#7p1fpb$qsx8ZIa9`qLJ~qk7RdbN!afbtC__37Oy=VKhnd&O^27E*ij}r~?BBy +EFthY$qrgGHk>R^Yh(d!_{p}>vc!)WOfN<3uGJ+;e7pf@=t6naThh2xAhp1T(VlM=xP^rWdKwV2Uo)9 +#=(JUm(8C(QEaY>p%ON5kb@qjj?#_Y@YvuFs+G&kUER}9Wb6Jj<{xy|xiAf>0#@;zn3S#@wqz#A6Gsk +;geI8-&-fd`&GwIhrSKP`cnSp+4nkbjtHi%~Po)8+JBxZP&R$=|AGVBc%f4@QDhNCFb}L-EX}w&Un{9 +8)tiBt)cY@DWty3TBZa!%+>u8j>PIMOgP=n$iaVt(r|3Jg8j~7+N)(c?R4dpX6mN{lJw1-a6Y9y(k@7 +F4$m!;3XGlsFCvkM>3dex#u|GSS8`LnLZ@TOvh9O@xy%@iLEhXF;-%Sn~^gm>8!+7W9Y51y9V`ithY> +&>7B8;w5)Jg)OEF0F|3kv1O`xWIM*^)rLr5CRN|XxC!S@&Xk^FSj&xRWI34%~ic? +8}lPy;@WB4~lggPBYg2zM}Y!d&(#-Wpin!`YxD-CMIXRaFHhouxX*^%Jz;zRzFj}9W{te!D|sl>ofiVPm~Jh*XOL@T*RlPh`o +U;2yQn?$EFe}n2F^;lY?%GkzMJqyr8MyoA#7G+I$6p#a>HWr*}q%mN};6U@u0|+GE&SXQq4PO<04cLN +^sV1}^e(6k-i@JFf%ec3=m2(Ydn|<$8>aartdvGJ>*Pn&KQ)O4#arCMc=Y@_@CuzKzv}-2eaeUob{-BYz$%#x4{!@9;?XQn7Qe(4zzjoGjd#Hmrsm3PYN#Ipv!Nul>t+&NGrHSRv!u(q}V-7t6h^sH&}auV}qS2CPT4i`WCAY^~yP(3_t5v0vbd +xl3P;@?K=Ooz&!1h&iVVz4JQHd)d_k_b-0G9X-RVylvI4aN$kvnq%iXsyAsE3QB_wlHi+24XVLCQ+mA +v_0W6sHx6JhI?=8fOu3k3QB&Om3qfWV)`jbCCAS8bgSjRh;pW95)EG`jN$>K>d(PDAp8rPRi_eWP`c| +8zLF$~mT*%O%j%=)^mrB2SY||2X_HX=Ln|W$elBF>p)|Y@jS?(aR1mOSuIR@C=1}H-d};}4HQ7+&^Z~ +@!ZBzycQ%Ey9i@&`k$nmAiNPLUIc@7VEy-*%wVB0+7wrukB2sEe=NaATmv3f}x18&LkAPeBW)PgvIW? +w~5CW!nMJHcPZT7fNty6hhbTCM9Y_dm^T5QH`DaB>CBwtw6G)7KjenP%2V`0K>NbBMoxIV64of}{rTn7C(T;{bLLm$Q*UC8`11{>o*quaUD4HKx9*|@Xrqe(4HCUPmv +4l94{DE-|c%{FF*#cq;}@Gxw9&7!URXqxy^+Qv_Q_w~4|d45;#G-@hVib0y~R)Q`>zz~dG9u$%Sh)tJ +C$(x~S+x+pck+b`F>28N$_Fez>e$b|44;pnphH1ag49fof-(K$Tt=R)5Dl@zf12Scpr@^t3+`JRw?89 +J2u(tIj-f4$>e^@(Mm}qrKL0W9x8VB#kTC}116y9#s*%2joW6t_RnfB@H@6TelS|Jte6frk9qRL5tomb4btkZizxbFDfl`Fs +24XZmxlFQy`$|`lT7$ma^@MLNfnM2kq0{B%DwOk4qcH$cR)IWRSXvqY!g_Pj*G#afkyEOAqHcFo+kwt +#uqyM_5uy_qU&l}wEcMVy0L+FAA8G5DkNZ}#vcZOW1djFi?e6 +oC%uHB!=dZ7K5hvO!k=RXshnaqhzu4|T3@i}f)T>4`*|$Z`TPZGgVeDl=`n^!3XI*l$?-+i?rOo0O`* +?*pKDALo(i+4YB1oheFeIc^>1s?C$B*d+S*-SCrsR*Z7sI5BG!VmL;nmm(BG8 +7myaMG1GH>d{<0J~0L~RDJsT#2qUW_C!u+n$q&N7%ojTDhKZn2$?L}r1W-Gf{q_3y)Db@W0uENl=&>{ +jVv-rX`9*c&o;;n+XXOo<7h!A?>en~S*jiF(R9UF+PKM^KDsV#I!SHHW`k+lD22E&-**9WX*T;4PCMj +pHt)f&FpFg5;3XJ-C0pcda??VIi#@N~%{<#l2E@_@Xg|`XlsvmO`Ps#~DUu61*aGgWOx{Q&TzF(Q{ue +!4KodWW73~FGshAG+*#k~oSEmg&d0yVuRv28iZYERKlnw}_4ey3=gmC)H3^2AcwkMl9I%%eKsRr%Bk} +221<;Tiwe&uA~w1bu2>WVi+i0A`3W_l#W?rg?Q7LzV;%K4|Ow=T{fZ}@d;I&d4^`l>HX!i&~9j_3Gvf +@UJYXU5;L6)B{WAeB9tThr&_aPc;xxkb`+vFk4o^QKgo?K@-V!((eH%O%z7g5`2%PoeOr@D~==P<8`~ +x~Xh8I`Wy2n&-xp>JBt^igN=cKImPn7EgwU=H2+TCtdx3>}BZywG#2%#kh7&TU2W2V?$f16d}dYb0d@ +4#8FEp(zfhtZceC^TghH>Ni{e`Bzzh9_s~|8=eFByP><}B{>^}HwuT~Chy9{JNXMD_vY9-My +X9?Fysds+MTB%XHVCV{Os?zf@01Mv{_%f4nJ829yYt5rWwe;$p-0m&i)J_Z5_905t*e|v{2RT!M-$s3 +JAI7S;*b?OwC&#CduCz0FfcxhNuv;`voMo@yZ50(dfu^Q&I6ADA36`wqu;Wf3;Qj^g9nvKkGR$0qsr5 +#kPQ_O#Fv)>>@-774+CYkyWWm8YHj0i$*%{=wr~qdN8q)(0Q|-(WJkP9OoqsxDk?j*CRSd+h|$Kr`!+ +yxW@S^F80B_b=F;vf0IJ5|_^yPN^S`0$=Au72-rUao##2=)u%-V-Eyvk22lH;y9FvJZOW>T +~;%092wAwo;I*JDV<}sKB(u+M0*&hb*j$jp9*MrIVKwFW+(Vemj>H1j`NspM%1(UCLzMFLmN9K@68?t +K5enh`iv#{9CO5)m6UU_On3DJx2!S_uram?&s?b+kO;+-7HPV*nQ#J0YWvzYolVDT_Ec@%-%affCJzt +a_La-^Z4Zh~b(xKT`d|Dw0ifT!17<$oLtmotzUC8t;UJ9iV$&evK!l{-sl#AN!EFlm7Z#>flT?fRSgTVYo<7?f +9h)!bm$cuRU8GaWRv6%p +E2@UmvSvYmGW0Df*@se1M8|sfP8(ktJ$C4sEv$;Dx*j$z+7y!U+Qo(gP|){1Z@10|XQR000O8`*~JV-AQjCA~OI0{mK9U +9{>OVaA|NaUv_0~WN&gWWNCABY-wUIY;R*>bZ>HVE^vA6eQS5yMwZ}r{R%`HFTjL?E#)!Y>?j$x<8(Y +Hop@}gCwmk(1ri{I76~u_D4A*Jzwdojp{h^-DLcKBJ!f$eiv+7~y>8ui-8u +gVM<+)o!IN^ctMcV_6GTr&!TF=J^Hcce)8KD;k?ew($v^T<48^|9=UGu_Y0#8GbDagxcg=NK1TV`)bC +*j +)oyYWz$34k>ncldo{q9N^K8=u&*=Trsw%5V5S#>?Dp{_RNl=t9z}u`cE2rD_dN9pKrMOO7x{pQuH=P$A}ud;db4J_XZ7rYMsoYa}~dK@&_n`YYV0J0y}iwrtaH) +8@y^E?iIc=0WL8p%umW+gKKZcZ4X7EIRI`t}^gJr35%O*T#8wbWl{O@c5~{lCihvHtX|xJ_1hO07yIf +Va|)ap&(Ev!&CUy|D62K}{}-;SrcJ7=L13q97zTFm2k8C=-)8CGxRg2cyX!2|WB+gbG7j> +3$_SayR@u=J?lM^Q3$@*1fc`DLi>5q)>FDU_o5wG|dHQ0Cf1qfU5l+J_MOFCs=%>4nMi$C-%gQ=JpJy~^ws}<{?vvMA{ +sutZq}=}$p#i{PPFRbo9Mb3eYsvi(_cOP`tc9nzM4M!?yILiLgnzfX*Tub;ls^tlgDMXqz~cIQJO7iD +wEZUa7>{djk?BddhuOZWFw|4$H&J} +F`0h+=sc>k)dKhE&*NaR8KV#a)Ms5U$1=!z)Gj!P1~_juVOAYPc`+|5T-4QWq!4JmT{Zb;mAR8u8&IO +U1U_BPk{JxM2AmI;C2TSr$`3D|!E6UVz=#)l1)GIy$GUcr-7)IpcF=FJ>P@!8^^U@ov<$Q-+*WyLj6i +mj6qTM$Iuu(sA|)=N!s||{8Hf%y`YnK!YHF|ciX--(Gq*I*lct7tl?V|r$dTEKDi#YOT>vc%?J?;7i% +b&1`XU3EF1A1v;bk-$2QFv367|))zBH@hxy`T_r<@vFU}Q(q|H%QQ8_~qq(cmeUU5mp>=l=;gHzb*z{`=2Bn`jL +EDqw3!@As7upGczK^(%Lp+iv^H%S#Qf5GiRA7{UutIzZo#CiU%%FKQVdHrU<0;*jHUbb9P!R$>&fA6k +WQUQeY)iwpe%&oFX7vWfD^~7L|O#-6>-bn%gi0F6M`TRN{d8ZC`JIY6sswQv24zop7Y{zdMZk4Zi-4?QI +?W3Zlc%zSn7&+uGQZ}=R|tmBGpLh;a0@6)(h(1|rPv}QF|Luvs06zU=DG@!x2qOTK>0Y;so+@%})hL&}8KHnuvzmX@j1`RLrb5ZLJV)J((Y_F4U&2SKIZ*%p5z-YH~$CbAA0znQx-0X$vtxyB$@Ofm +LAQxf-l9ztR?1pu>85zY^L +~4M_APwHmNVK!60HretTcT-^Qu@X=J-N$QD};?y4MhqKNEJ|ep-JhG4}r{6FF-6O0p>1e-BdwC8r4H8 +5-j>%**t3pLl&thd`^mZJ4&T=t8s=7N%Pob4j^yPK?K@sn<2Y*UMsQUUp|W8G3Fs6)z>)Fq{`s +pWX<&~o(XLY5&#wQQq?)l+lY0>E~q}?h0z98W>f?2O2Qk|Gy&0Oz$^mvA_A_Zc?x`q?GmYvETR4i_*J)-NkkH*n4#_ +4CgKs6%ibhNgxWa}8%1zi$H6c8W@OR0R!!5Si%pG-*0)yKW9NG!usdjOF`O!V@q4=Y&SwtLyrKSS~_3jo{TnopZjlaOg%EXtM~95g&q +gK@{Ary*zYKq!E)5(QoRA`(65s9}$ohIWnqb|8Ioa&M42RL`qqqlMlgKrbOELXIqW}6WP<+ +i3_*Qb{+n!92gf*^<8g2-Q+wRflw&n@$Fu~;Fbl_jY6amjTMP}b{$(M|K?kZS5uC9Mcs_E46Iw#s{mT +_b$P+w2oR5yWbipZUi9-!&loA +E(C(fAGWBZ<%01k&aSZOlGHSzsX|cf9TC8pdEasagMt>@qN#wLR_0YFUl}SC*hS0f2S9tjg`i6!N+R# +;JodKDOt*tnww!HA*}frDC*CuMw&6!{zDOYhw5h!~N4}0KA+)MHgyE7P5QowYMmFiscns5HrA|k;fKr +M7nUrnr;jD*a$#v5y%!EEIi6P{toy_yc)TI5?$YSr$Rwq23N8?tTh{ewXtamVjr>X>9)Y>Z%A+GfA(( +&ctUIikjLo2aCzB!x{3-+8Lp-1p{}NR%WyrQB~YaeZi*t8B&n@(Zvj?_F_OHtFU`iP$vnY^lGmwd3s&}g$j&-l9AutQU!a1V8}=`!?>%65YQ@ +X>|>M^qEWdNcH2F|A#ULHlYu4M4CA|U|+zv3}rk|V&CYnSXgKkXPYBPOD2K7A$ +l1_XS`P@&+sB}v@4kDXW#~_`D5lyP~FC?7!+?#mfa}r9N7sh}h`K(brAClJsm*Tk(ZZkxZ`{A>Cc%d& +{!j4uog6aj(iNz6gL75CCJD<()5~S8C3w&YlfQn%w{AQo-IAv2d78Rum^{YPXotS$`wiE-up~=j4uv!XihIAG%X3vPbtu>C)H!p4Ez +6d4MVx_de^HUq$X=|eDOF|Am@$80yk-Y=qOx~x<7^DVDL4L>tqvJe-xkBs(z$BW18Vdtn#;pH={b6Atc;m-ZCz>m$w#(>vEOW)J0IjEgR- +?l_f>%i_`K61t^N0qtJFeW#}}`n*J>zLJl?=Z^})?jH+D%r9qoT6#cXGT^%`IcrguoqMZy$ +`c-RR)^0o_>O>9GCC5eXzx$cRT79oiVZ)mpdy(E443#u6s!S{&WT^Z}XbYlrF>RO1qU_mFio8ln_M`H9iFLO3zTd`S|`CbJ%aF`tyCGV2aN8PN!m?S35_z|PyO<%Re-WFvUL}&4%QHWb`lU3Kr +rZ9f{`siHy${HLL +F7JSGpmdmxKd($$*W8_U)B; +Q7Hdrn6|(lG%SZRn3usH)gjFkxO%YgJW7!56_f%r8tZQXvwIm83=TBqADMCn(*vVs}Epfp~bN9=9F9( +qf&kO4lo96_+-*xt*`-cIK3MYD#F^k&$kYt*Ms0qTCIOP()70tvuPIL3)G1@bMZ?r3u_~s=I7-z8O$Z +F@_MQ$~B6^6sIjHc2McnzIhjAoQ$KIT;a|}@HoHF!8os{?XSc7G_8~hwCm{#J|}xR#AWP(*EczbvzX* +A90cLOQ(_P95?}>nzqrwJ7yE4~uI24;{nItPQBVlCMh?c>a4_y$Kf;BR!TFWFNkwpDo^_{JZKRsc0CL +eW?$iq*I`FaU&Bapt{H6?%V$j7f#Qo@0Yo$>taA|rc)T7D2+OJ({Q!C@0V>nfuy=kh%v&SV2lz&D^Rz +7;9DsBCn5?yP1LSd$gM*Q>F5q)>$EnCX9qpN8Z(hfjHYzStDcG3Bt!rL`3C6n@XdN{78N}{d#lXp(E4 +&9RHp?G9R!;@1g7!>j1`4e?mMgBUcRbClUcNm3K2S5Jx+f%64MpQdTRDx#|;0oe%S#cNUNr6;Mg)z}y +t0}f4SmzuD8_|&$GC}Yw(#nXoBeS*7y?72YkYN7dhc~drEsxeACmCFM(K_$RXAEfZW#FBo58UV^c&;P +OIkFH0S)OPZ1x(O(-W#|`Wg~k%*iVOIf(%rT&R%jfq)+v6F#ERyPmAhF{hyBCR1wH*bBq0_Rqquf+?t +h{R7_x78zKdMZApSFe@Xk&^i*+WQIAoHXxBnJmHd*85HAy~fFt6eG;W7t;hA}VB{If%E9t}b7y5L4v{ +!+bg0WaI@be{GFJAi#w#yzF3sJ^Xkh-UH;j2%Cs-|3>#*j;zR8uR;3;*f_juSLE91B0*JvhoV@56EI#{3z#Y2Z +0E`a$tSJ5V6CZ$mwjqZbw#@K9Uw;4HeYqjl3+n)@!mKn|wMc-c01t$huqk#Rh^=!u8nDYaIB%T0^{y#qTjb8PUe|85vXf1p2E3UrjcoCs^-h5tJ4VIRr1CUcB-{)d<)2dx +#9@`VG<+sW@JtI+mFZ6H2OM}NBLFT@_p$C&LV8i6W+r=p_~$easOA@4epFm@(J;kAys?x}T|}hq9COK8z&2KMP6UmwBY!GDCD(q=HltJ&&w^+0Kraa+_3J!|9UlLm7{$(ezVWgGtp|T +{(=>_fz?Hp!>;vFR4GE7bieC?&ydP2Y@TFt~0H=HwdGa309?{??BeqAdh+#KaJ2A26_+Muefi?3it3X +NKO4sJ>~s2rc_ZIP(FE!v>Q-9bxZjWzH~8OmpoJ)X;Hh}E0xeXypLE?-UrEz3h#vJ#wg##GvotvD7v5 +Y%^=HlCoX)~0cOr)r|>}%s=tezQQjoc4l{V*^i*>KT%pX%**!Wh|t43mWtcM$75ug17kRCI9 +)6aeYcjb?n4-P~mfY4c(woaqhay@c8wY5j1xE#p{noF3|pbDV(ve{|u+!yx69n`Ju>BDyPlU;=3FrV_ +Tz{3uTT>8}*lgtRYJBHX|-vEsnl-?SpGgys#_~M+|p~G}NKv7j6r3{vtY)xtrq2+hSU|%Mo7wJvsg5( +di%m?SFkVemMEl#h3pPpFEiU^wZnZ*E*3PLHG|R-=kg$LjuUrZ-<`a?`DE#ZZXj2)1)J8|T8<65a5p9J^C3NtL+l47X2mxGdE9w$aTWtey2dNT3urg> +b&he`pq{rXgpZM9##b(U92*kW)d`)k6vTH5NdW|TlD>s(@XMOy_EeqY%P4OO@+MQ +S%#`x8LBp&XqrIC(aTP_A&F+ywy4`IjGW)NmIM$u3(p>Y|0fKgB5H>G2hVjjC~ee8kAh`cvP1-*H~A( ++S&-Zt7z>7OZ%N5kRyU;bk{FhN=vi)|XGwjnyMv=_Wk2M4Rhy?E&5{)p*4w1YvF;P*c&ASb&V((rs8* +>fkvCGhr1k&+rY2j#+=LU#+e~JyH3>y6O$jkQH58CFolPiTb^I6j=WFCT=tcxV_d+sOM>CN1*-B=>D1 +Z`MFlPMYuf4*6cE~mB +-SxkTN!==`=I}zzrv392X-03RxM__?u3a|CKQ-l))kHTNnO$s_q|@t?BMUrWFz$yAU{yd7unfhP+GUJ +2h62VnrzxO%H#km}pgn#8vw9Pm?7_&=_Q|Ic%u%P(?ahc^GGstPIwnnxIgu}sCfE +v+X(C>1f!G5vLY@R_Uf9qcYrARy1Hwt?)oBOLOW=cf!PzV^eIEIMFtlBv@+tL=M7=o*g>J_%q6-i-lf +?A~8j5XUvv;aEURm<`O#wToJS~nhk7sA+21#>T9+InH$!I7|2oHm!HE8zDTtqsDAEzK<0U63`P%{b)! +8w%F%A{@Y2sFiwfaadBfolMCSZmz80FDSH(19SzV}nW(R+IRh&6uT*2J|I0Qagf?tWFfxlN3vd5j7Ck +J(6uL1txsPi*}5zuTgs4M@r1sUq{!;AiZp9X{q!yYvUAAdWuO8nHOLSd^x6c +`YzOTXd~E`L0(=G$m~*`0I%-(hrn1_sg*?|~U&g!dKnvh(wr#e|I}~}!4O>Y-K*3*l%hkz_#Sc@sqwa +DFGql-f2?|ouL2C)y1EXqAJZh_^gk)=@MDg6tJArKYba98;npoB@*Kg}?|wlz^9W+->zzHTgPDto +6cCdK3xP~X?mY#NEtZJ%QCn81d3Z;Y~M_y{HOE}pS^wahtH;;eeyP`)_C>9+rt8OtMCrS_wo7LX|hf~ +`(*TXoy^Pn&GgHk(vL2T45&UT!Bp-G9HgEU9oJ*jDsYa-V8?u0lZ`XD5gvq`im$0#}&dZ>CD(Voat^1d}(HNr +7WgX(g_6X+1n!d1uHh%^lVwNeqyRZwxlLhn-5X)*-C~(X9dvaO14k?WG7yBkk7ZPq+??>a;#DZ(;l7I +67#$U&ME(A8a1(_){=P=Yxwf2GY{+}%KxaJn6iVfn<>AOOGr4Hm+K4>fR6LXHgeDN3{C)_--eI)i88h^@%(Z?%fyQ)|7)|IEiwQP59ebA^EpMLLCkS9&BMmePD +c4@^ePGt|0F_74M}UWG^PXT6hqSKr?^OC{PrLlX1iyV`G}kx8Y#I_QuzJ{$Ka>3ihiIy~g2SChj)y*s +?}E9H1)GN>oFB@79u!mUFR<*g_ +YD8K$pju%d5^YZe3;f%!|`?wk|JiBqQOxBIfg#mm8h;D4*-D$sH$Z6MPL7d&NOpEVr>{!F&VgfHMAnb?JM@iYHK9?V1L3?s`nTcPgL&V +4a{%cWGlK>W?SUV4t4Q|UuCyXFhIR}_%x|^&#()t)rp)bI;DFAPl3hUUxM8_DL;)=)adjIiLI42W$KR +eI?XG}5v;BzW#|0J$ApxvL5nXw#G4#L!$)r%b##n6gW25MV%?*TZZ=>PBusk7&V2iPrjLrOvgdl1#vaKES?-y@+d&I=nz&Ew;m9rsMT=4GS}by<-ys&^5*f0JSL%H-`Z9JxZ{O+ +fkw-j0&X1(Fdq3qsDI$%2kLQnDU5rZxOL2{7vL;_~lby#DZu=<@HMzy4_S`S0mYB!Bdz*D7F}`b^=DG +Y>CaX|5+*(+>rCLZE*rn!{oS4}u=po+wUZvfE%zvJ-fMIgJ=YBc>CmZtsF}7~fZPD7`gbnlhBqBjIfC<3fyFMatC<2SdU>)+xnn}AV +h0OL%MqGsWJJKLG+c%D(LNk5ZfaCEM^Dd`qhvWt#6Dun_a%FXEtHv6ibjec-+gCx#SmX5v4A+8lQh6| +ZFPU5KFhi+O34j*%|?SRNJNl>lyw`5l1Ung6(>`Yt9qayE{YE4g>O0i})-0o&57|4YQ8r{X1CF%0J7q +283a@FTrWuK3cm2*a?C2G)L@U%_hy{2T~rlK!{$8~T=5ip#!LeUN*#NKZlGROQt+qpe$yiQiZ>r|C +O|$#TMv6r-rbc_TqeWpLKi_dqJzNxiIY%2V^oayEf}r+xXtHf(897H4%-wj<@B!Jj`3bpM%FykXLGR_ +L-g75KUru0r`d!6-FR=jC#%)d5x=kFEL-_Y8{QQ4}=jZo|@D!H!ZU}G4gM2=i^G*Er7|znsyJU=^5S? +VjcV=MphtAkUPNg<^iQF2_*UjbZ{lGl|h?&#LHhs*a36%SKzQZ1ttF4M9N}0h+mRfx+`wcdf#A`H&$n_9`K*MVq+8Y|^cZ!Me#3$g>>-(j%UMYEN>IN}ObKcx4xo=R(2H(5nRmGZBz(R +~<{4(Tmyzq-G>3HMfOH`PsodKlhi5Z8VYeUODB!~J5B&haKN*|-FCwGNe-O65JtixX8N8LOJ9)Azcg@%}#tq^pLxy +K4Z(gP;{Y_BX{Y*&GbT7@pk!A|&DWi)@hTvpkVPR~duuJ)!q!=~TMg5ugh6u`bz>o`?yx^S$ARZx)0R>t?UD4}^4sMuxC9v+0%yqEXgi|2K{n*&!tnaDq(Z(X{l+q4Vg +PT{@RQm@yeUp@7WetJOQaG{=6?bp8jCL6Ac=KJl9bTMv +mpDBWsccqERxpJJzTD+7(ccYrfP2jF-vY~v-p1D3@Xh1lhFPcCfDfeH|z>!S7mtP{GRkY`W_s@t1XW6Ovr>OHclF>Ij5icTpUIUdXeqy|@+NbHu(AH`zp~7p +-I$GTktaVMb$8(#Ua!%ECxkBbSPX=d;O!b?PD4#K`co7-@ae{lA<%8VFK=E4Zvk5ei|)Tuq6O9mD@}V +Avv-yh4$wS#Ft3HISBGnOoZP(wu+LNffrvaZ0u&XPVpC`fA9cw5*wSo8u>w=|tYRUA(U%x +wdZGfsL1Fz!IvFG?O3zp1-7;4v^VDb?FqjUjJE({f4CH!yurZr#TOE%v~=qn%D;Q_dK3h?D>YOVAaFn +5m8QmcCJP2mzf`zzenPQLD3EW!L`Z=@-lZ8A-$!8C!?-a=D}t7P-MFH#~da1Bi$#-~HuJ=%J~w+!(R< +7H-DscfNyLRqvi&u}jJQKm)ti)B6Li@Hu!G=3`rcoemJBeU|9JyrN?m{Qq+uhNx|bF_=KTETd276I$zi8`*W>gnX +DuqIPn2TNnlZaZ?-h5pa$zV3Nt)Vboz|w-GY5~;)QPar3D;uzKYZFyzB&&(S(NVq6T4R&5F_Os(s%RCao_^xPQvH?=vep3ZhejV|$@j>WbucPSULm`0;fr#xN +Yt~_(Gngg4DS@*97>aIl!iUYAyIr5{8H$SipAJ?tXlT3k?WBETEyO+M(Q=*5ZDx3Cau4&kvBO8i=Y*n +@fW$K>z_JS(8D5np9?nS!RL}?^dcz>khu^fVTRPuc=PbUHKU|&xmKkEHo$2nO$TC8_QJY|Yj0GxqSnV +6TOi+g4%<>v3o272?KZqa3A%LaN}qv_Q5O_D0TnbxwH=kHa=-i3lk_wy1&xX)1VB)%&qU-??nN&?NdW!CQh>3#4lf@*IR +chOT!2{?r)>x1I~1&3-g}O_-x{X42e0Hae)gC`56?o9%Ip=Shu+A!`hFZ1P+4>Oz0~x@=sRShUoC!A) +_10aGGt?X4R=)cTplnI?3JNAIy6w!K_9_sS}2+%+)`Z;ZhvSc~?VR;h%)vJo4l#_Knw9i*4SG+_8vm2 +EI2K}EI;+%cOw=*0{awLUk~Wk5z1HN2bS-hU;GWxf(k+CVirdSN+K2n3>+t{zJ)OfD0pbs^Mx**Hw?ycGw3vR=! +D;haelg(In>41XGw4j|i&c_R@Bwlwlzc{5GRz%HoAcQ^u-4h71#1kEaZ#u!oY-1ovO=7D_^y=3*6+E; +Q-jarIPG^e+5^=p6i0V&*W{c~soOF1U1k@5FcQB?9s9YnUhg4v5{l>N5Gx6M%QLqH)3(%W5284bs(VNA0B@+h5} +Qpx)tIzg+of*0Uqsw$D@;NEW>-6roRgaDv{wPB2Cec6D_P~SQKv)p2Fxiyh!x +73^$7ACl!9O%;g(l6EITN`>g61<@Nd^My>r@aT$=G5VNhw-b +z_HG6LaW?J0C+}Q3-Gb~T3n-hIj9(iZzs^E@V7G(LBOet +IKA@!5ZBT>K_{@YLw=km-@@NPZY+qR8qg_giEg=da%Pu1wpMX(=x$aMZ+RCN)#Fov2Ld6k^R0p?Fsbf +1n6FdsvQDnBEQ8M`X&WmYE>W+R#1XSx3m~T4ViA{S(-0cyZ4ro&5>_K@o2X8*A>SGQ)@E(hE6#u_zp}n6EOTtK9y6E9X`#v-lK$$u(rAg}NU(Q6ra+KrH2@g52LB-IY +9}zXuF{Uy(HQUFe#PLgSw;twLpw^#=0zD=uoJmdY%b0TczYFDh07A!}p@h{idRONyb1&_NMTW1$@F@0 +~We=g9|4;HdTI8z{7g}io;P*1x9CH=m8*sZ>nlb#q@|fwFXczC{ +dyq2=idpo)mhtJhyd}L}Q`#W!6XBwl!}Wc7QV}V?>K-~GJbe=n5a0W9aP{;US}Y4o&nyn(>b?24F&NF +iH^b|W_pW4Z1&QPoc22o9Hp4arj}WjKk^oGBtsPj*7yL#e+n}is*@M7_0N|*vA|0ruw9wEeDEp!8%O& +|hQczRnQ%ZGebOED8<9#?!jrZfY-5v$DVy;?dc^)BKtSU6lcIuD*4^T@31QY-O00;p4c~(<+rZs155C +8z%IRF430001RX>c!Jc4cm4Z*nhWX>)XJX<{#JVQy(=Wpi{caCyxeX>;2)_Pc%s5^sj2D>02Tv%90l$ +z&a8>uG$9?H)U}%TOdFv8D)>AT4XAKYrhP07&qVqr`2d%1lfVz{9&QKs#)-J7STDlWaC-WicK78{Z6e +20QFPEZ2E5n-|PI@YvqBFZM?8-%ED3F6JU*7h+nh!kn}BqRis3NJQqtsteB9hoghTlZ!*YxGz|k#q8k +pFmTUxuHm!Vz0RB=zCQMF!SV6o$>rg@;KR|$VQc!1zx&30vwxj=e_Utq@DEQfWPjkF3&aZWdRKB +36^b`brc<7y>_F^gTA&6LWu7U7wNU&b4>OsD1s}2%XBp=)KtW*(CR5bPbZtQb`2vL!HE1ewqoRBMzex +XH`g_dxeb_yb1j+!La5l+Bi8>ByX03eb|C5Of)H{}_tijp;F26rL34S^}zc@NYj)5llAU6pT50Y#uuE +u*HrHB;T$jud#-*{Ab;{k&YDBjzaYEQ}}Es$u~k>4*nJRia2Bn0d^-~+spMVLhZ +x0*0shIzOkIAeAR6yOsGC(=AfXd}C~nw@ef(%}`uNdU4zNO`Wl^Jtd_d!uwN6dZ8~1aUoDWE@a!VJ%(Z%`Ue~0w&?&w@Sd^ +q`Gu#r2~*g4*Kw1zc1yy8UQ8dSzM#Cn#0Cwtmdb+I{|knUhS7++7^^y42#w$XQFlH4&`dyt_Mu-l2_uCavjWi-$6i{BsRRaFW@<1(;ab{bUD%;U?_P8J)SK0Ek~(Op_0Ctr?THVxMgCPg +)FwOz!hJh$cu(0TWd48~jpE2#8mlsYsRJ#JJIpRhXfL%l-FZDmTTkbILUI!aBjM`+L7Fc>DmuXV8_r?z-C6Hq3 +HA+`A<~p#k^W5o~-XKz~vizIM+H^duwbR3~9Z;NG2vm^PF4s?N3o+v9a;%E1_5fbs5k^(y#! +&p+dUTmV44T|FKUyTBMLVTs16l7axP7T&Cc_5RdvhAk=lvN(q}nz{h%VO^0_=Yj2eDFz?9^4)8rC2oW +UmpqEBd{Z6XX%uE_X%|KmksuSfvX*?LGww~(y2E#d1>!=jdGch1XbH)?QF;O16*3x+hCL66Jrh@kIqk +cKW1TNsehVR3V`pQH>M08cm}1=5j8|G)8wsg(Qds>Wkh0#8l0eK%vOcf!B2HSKqBDMSK!X11q%2<1;LO4v(|oE$Wwj?7OGbGj5%S1)vSX{!gLj`Z7`rnfJr< +CBpj*hm0YUDxUInz;QUNFqTdPJ+=5fga}Le|kk=65FJ>DO3nEH*35oA;A20`yp;9&rgeiQ6%q5=+WSL +hUDHp!hZ@fDAD8NH&IlzUPWC;4DNob)o2LV_XQ`^eG!sPS^$OOd-E3aGpTVNSh6M>Apg9<7p9AVNtyd +$AmAbQ1Tc!3Nhk3xVoP{<@DRD3oN48CLnz)n)*1yp*AFl9W?MLvq=Ji5gOg6*6&wEcbNBw3V}F?Yrtc +Wl@Kc4XMrb;~*zQjF8E0zKW;e^L9Qxybh~^Kw%*s6!@H;29w6I+?KR8%8|>EP$WoP9;T49F&S$R6!2@ +7^>8!Mjte=hso-e1DZ7itGmU{2C5-}yu%VdU5_+7P2S@4!)Se+FJss&6_YR>eNAm>=WX_9dv)kxOCMJ +tL0+LJRM}tMT>Kp7d{w1V-1*yRmCbQQh|SS0fqXXO!S9y21kd|*6jK#%`zkbiEzMN@pt>Tl4c3IItyu +JR)U(N>zp;My@pUU$+w*>whts0P?*}yvdyN$ftwg2Es2&~}Qj-3t_$g(xw%bh6GB2~X1~n*Z>&2re>X +Ukx%1@e+(+qYtp*5Y@r=|ol4`+*T3?xDRe`n>EjE3zRBI7U)c^h`bz%NV8!RzuO3iUZc3xZA`3%*$5a +Ylf7T(dR}v8~=dM{+w`qG_jcDwrTC)^o9S_NR8LT*u&-Dv>UB2|Bk +*;}THYcYK|FE>ioUnr?$dEY`$mEyI~L2*-_xiry3^?sppar}Pu?M>YnF~Mb6Cyt*qr_QIHLVY=eN8r# +6n2!ST7;cbn92%F>atJ$Ue_F3+uoI|2%CWdCb?FPL8)65il1*c|s#=o_+g$Fq!+MtG+KgAL)f2IP34 +A0xaZ@;pP<@H7;H7#lbrswAnuWw;e|JR|8Pz8sQA5aH{BWOm*e$0~lku28K*i7Rn1pRW+@py( +5tHXL`6cqb%7!rRklR`ORiDzY)_dqJc7d$J}l?@zn^u3MRFE2z~rjQ9ZyAFj}=Z#~K%S1MkphQWSvS< +#eLj*g5EL8pYR1aAPY?5K_guUC5>JY--V|THTmgO2zrN?m +XPE4;`t=Z@uiPwRB&u`rl>$eHW7tsuu5)$ZPB5$lNQQy37HX(HTEL2^^4I7CMM6+JT&VnSZ-3F>Va07 +q?(urGO^ZFx<}u`Y+X<~p5U=yvEsaz24Jul+E_PfOlFzDdoM|*F7HHigxDa=7*r*qVGV)w&QOl}tl^P +*)J!$_QnOov$-$25c91#T&;mraRfGu^EbxxD#U68Lxfyyz_kOTB3t5f1$fSF~F%?Lv=ZIaAl;XvL|Q~EF98F_PZ0M2S#_WYh+9X-^)h|*iM|TMf~uyGClA~h*EKY4jxz^JwnUwT5o=Z5{9T-|H}ZMB%kV8jV>Yt6h=*wIS=A&hEAgSyr@C#@@9+ +OJ7gP@~E)n7#>Jh?I?u8aM}SHZy$nw+QsRmxl-DQndG74WSS`N0`Vo{{zfBdYP5wV^#`ELrWmCzgW+8 +WynxBLKl2%h1Fs39PF@@wgZG`NGiw@#ke6A85#5G(yOXIS!bWGhzv}&KEv()}pT;8}?KA%^^nUuV(yP +N?nDr>7?^5@8wSQRgFU^h^9OgVtM`?0v{ +yyLgPH1ufGt*V2ptu9To4x>;F{=GivYw;L8-c!OEHMKDmY5B%#L>6#=B7l-Hxs#(p)61brCS>(HAXs> +)vsvqetBA6b;tmMslw)Xy5C7&D@tmw%2J+#0lnUlo^?9$4XqAp#vukSumtY#`-YX4hjK`aGk*M{3YgC +s(uOVCPi&QO%!?z+5*|jjIGJTPjy46wqENZ8$NpW**Z?Zb9x$vhgM^CGwP00)2i?JA}AS!OYUyE59c^ +uqlu?)_2OM_NS3?N+sF$AAS#B|0{ax3UhQw>6JIQgwN=J;rQ36#O5wXncItxQ)p&%3dIP?9vxRnV1AL +>n&Du@UuEzU?-G@WDY!Pr!AA(Vsja^{>> +H-9jRSn8nKdDw-v-5xp9Ceeg&htCJ?tz3|757jRV5cY+T)B1K8tJx)>fFLr7Z9;7sM

    s?{~Lz)_oP4U#H&MpXvvyVj?Y2ud$6H}F!OiZ|e`u?NQ3g}`~)jMGh2?AWO&9fODpSy +Nc!2um$M)h6`&hh?`_pfF%=ROWcWI9-qRk0Qv=Tk`7-3ZDoFJ8Z=r{H>9+TQ!7gmMlwc1oU>OZqTWpm +^ESGkCupNeSO_@6N^<6HT^*yeC+UJI7TBcu6}(cZ_o?abTnNB1mbey=8o&WdG+M_+H(<}x9eT|P}XW_ +w%_pDp2Bih{|8V@0|XQR000O8`*~JV^s9OJ*9HIpeG>ox9smFUaA|NaUv_0~WN&gWWNCABY-wUIZDDe +2WpZ;aaCx0rdvDt|5dUAFf=y9KY89c~0~?IGK-algfWi%$G#HQsE+f%4TZz<2Dv9^{?7QPbmMooN}ILL#Qapo +A=A}4=2BTJb(Y;3>N_x9UUD-r&MX+wgw(}EEBg}Nr9Y5P1P5sG^u%^6jh +&wtJ>CpdU14V#e@xAFZCKW3Ka0sS4-LAyfM4d`@Jv5aV^H`N9h)v|0W2K#MQk{d=^ZAhnD&6y@G5L`vAP96WKP?abA!i$${ +J(9Lzi#{t@xj;yzm>K>hdCrRx4Uns}-4(%NQ)fRHK-QuVLe2@!{;`{c?5s`uu$HVF?JutbDv;iHeits +(hAQo4euUjO7?q +$^-cj2J7v;DC`u)|(9M9+Rj8wF~DJi+n$ZkgNA?(R6>qEYc`S$?`y6K+08ektyU>^V23ij`Z~KI7)rxf1FRQ)kW^y~@ajXj7(*c1`VgB16EL?Pp#L}@V`PVbNHnX~8O8%tJ*@rg- +t=NML=}D(A%H#=K#XX{oJvrn$rDE3FomAy$mb?gveZyt~jDRjdMzUIKfi;+)C0V&^0 +cPIBCaB!Y=Y*oY~t%f^TD>)5JM&37l;WvS}ozeKB=3^x|@ +|=+rVYN5%iT!dSL9W?gWD81BsaGZL85XA6^taE~!nu@uuaGKBUaRWn4fC@X~b{ifbuQ3HOJpBq)0Z8f +8{YDCGmLPBCl6F8v%uNW1}DO~KWJaL_zSobl!)ZfyGz?yeY1inH8!&l+Z=J~#|qo&fo8YrS5}LGJ2r;Kg;ocZG6?Z(ZgE&rpJC~BQb4ugv>MsK6Gfq2+fq#p&_G)YPV35nHD}*>!=2 +UM08`ciX8DjHw6y6%3_M846eBXf(Qh_%I6 +>&k1Be^ +%ED_rcpV2vRQ`{M`kUQL>4Q9cUnB?krwM$s0B9ZP7v_D=ItClqKal5>VOSmW_JcpQKA&{p6)Ss0u(VY +@<)mDEtYpM%LKPQ?@a!NJsX+n*)}!P*Howsa>}7#iB9w2jgsaxhkRG2QE|1`|;9A5cpJ1QY-O00;p4c +~(=#)IC_^Bme-#m;eAD0001RX>c!Jc4cm4Z*nhWX>)XJX<{#JWprU=VRT_GaCz-L{de2Ok-zJ&K&AQt +bVyoaob;+zZ55kNbg^YWNlu)~3Jeh|2^9!104P~a{J-DK>^F7+QcCW%*LQr0MPhenXJ=>UYiAc*!RYZ +Qn3ZW(TiV=H)8F7B%A@~R1;(8ew?udQf3Iq-LR^LuW>Bu&dT{BX=IDzrUH-$uZKZR-{R(O!Rlequ#XL +W7p>+=bq#=Iv#fxTEh^TC;$@@90xhd#*2L#`@in^}2A8af>6sY#SYRaUV|hKhg83ZcEIoa?9e+1HIzD +)P7#~eHHgMFe3ijmi$58(zJ&WSFNajHI0T5!ACv~k@;8j-FP_|7F+}c%Nk}8S3ZgYXf?u +C0j{Nb!+{Kaha|E>LbYNStU@jj1R4k5lnZvT%=0@~_aG_GVScWX;*zCM9p)6ILr@koh722g{`3nRe$7 +P-Kw$vp?)Hko3hhQ8Q2OCNaRCUM)=ow^d?Ul6pP_-Bd|Z=@p2C0;0|j&XZ5Vgh&MbHzh!r~WtG-g^P6(cMhlQ}_ ++x(w!nxAlMkEY95ME|8R@4lJu@Qg1^YphKhnTRDuJkV%9n^e=i!cM3K<%Exs?EzJ9lbqziN9Z;*JaKc +R(pWFsmhxyWrW5Nmu9u1*Q@=bZ-L#2Uhv#;Tq?pk3Z*56{>6(59~o#7A$FEQtNn$(R4nbN{ +D!jsRyjNyGFcIgli(>F6sW`Y~&ki;=^L2(;(`p|GE#xv7}NX7v^xPOD)X6_-m_qBhtN{tOtBU>j}iY= +5~6ZV#AD8Cd#;#3j!{3*(@i&6a2>mc=EQ9$A6b#+5c2$H74{%aDYwy2&sWGit6AjE~BwR@+Wd&#Yj96#(H+4%>F$xIk&@IHAUdTEvq6hC__ +aocUtXuT4{Q9wVL=^DJr_HfTJU5_Bk>HINsqvH{S}9n=(s-Bqej7h8v*&SK|UWg*5f1{pW6BLhdA5LqWWJp;CRd-(0)>+cVNgAU7r4QzzK$!@a3zy$jpCkvz+(qX` +{S(Knt&vPc}PpHw-8S=F-f=T&*lia +za0xXb-9>%e=NA>>xLx|AywSRWboEAHm+0KoK1F2CiQN!%0%UDvUNL2Ebs((0c{OAWLwgiq8ymf??{Q +V7GD>NNREB#Tx48#QZt2OJO2R8~qkHGM0H-gbg4I)so^as26MoVr3>)izzI7;^V8uq>wk=Fcf>ypd3N +P*2bmtG?cAlIZO-SXy;{2I#LMLgA`mu@zphy;ZGGo!^Xm +c2B#+!p(Dkme8zM$IDzU#)P4gPI{fDt5d@@|kcO+}6H%TU_0*j6DRt@Tgfp~KWXKX|r(GBrbYP6Wk`A +=&{caI3}49(&=)Ae63WR#Pz_+C(`%F^bH{O#evv)9i}5IR6=M?gD5uL$JT!SONQzbntN`v@PtICwcd-ha^ +wrG%$H?vMVSjQ+Vj`s4WQGaWI(4QJXo1~H~QYYaCZ|n&GxN$YI)5#pO +eSU2Y?CnQ(Anfg|w_-tbTgizJXmBN%P6;QN?!<*#RUdsE!LfCYG7LXQL(#j>>NQ0`Vs(849k^ +stq&RGR=7+Qtn(10_$N^s8}cldEHS_p|$sHp&V!$HA>qi`Gq`)QitcoWDQm^#WSeGA=1m;h6=&I +fI#DR>y?(UZ5s$TrbZf=w(PH+Hg}b7hhFLY$@5o02yZ-4hEJ)epHXZC=4D2_EhWKftTzuncYbPBI5j| +XB`t*a;lRR!#}2>(;CCZUHk?~>xejyZY#{B@9)Ga4Wdhx2YIw*W~gLOvMSN|t$ps& +qp~y5g+JnmCSXmR}5s1bY}68U|vx;+Fu*f4MPij^AD=`Uve9)G2~h5EZa95JpQdSSb<}{5hDk>=!awF +^nzDR0|3XO%4@TCHpwbmo-d{;Q`{H#<7UDH0(g3{5JgOi@QI%uY}TqIIU01<6~-uq7O!YQ)8{(Z2SycTq +FJ)B=pGWkO3?-iHklBNobn(+va<5R&Wh$W|6jb!RgHK)0ty!_%vQQI1mpCJU`a-$IpTE;VETWd%7SX< +%EcwyYd!(>2C+TN4SZwigTZ@LUVu>ryE(j2}UGawC+IMa>(=;XpbT&Mt`I2VAPDZ&OZ4)0kLdHP(jJZ +n`U$<>?)dA@-y_AomFjY$RHSd0;8wWTOpa|#_Bn{RrG$?U +>RO>N47#blznuBbJnMLTX_;y8yTp7mk+(EhYM(EX9cp=Y0LkJh{=sg+g1S4$mGb~2wuGHtAxyI25%Jb +!x(>{&z-IIMJZTA9d0-4l7GJ^H9^+AMV4i;UbVyZ*&uJj=_PnI?&DZrTVB7$`T*t)b5AOBkz@k}hm8z +wOy!@&KsQP~P|AEGtb}r@ZMFS3`=>826n+Qd~u&0y&C7b)wjXLM}ukcp!Q=o +eAOL1UeB;X7mGyqJNTb=)BCVA!G+KwW2xSZu=i)a>_k?7+G^%SVEvh(O7|+0w)b+sc2&is05roL^`u?fK^1HIks7}(gH2VIEnF$))5u5Ks +HXGg+e~6SM;|IpfhbdxC%#I!{V2bP*DNH^({tc;7ABNohy$cbW2_Q+-hJ~6!?b!~Hv#de!Obf +mhG!s_*Fo;l}4|z88VBoT>+rm8I>!F)8$4_s@SaT5xxvC_l;W^lrx?g`NcxN_4M!s-fd(8iWQRA0;j? +0{7VT5P(v_Nr|z9_3m9$Ef=|LE}G@atCh3%-MH2DWAR$5viy3q~K6j*Pt-$0}0(uK!+Vk!=4 +RF(#&91e#B$OE{5{fQ$nLF4mM;wS1Q`MLRJgcG`$zy*C2dCRULs5PNxj_~bK;fvf9)31MaC&xe3pkYH +=J79X)l$J=v;W|VIawe-+B?*oA}l4yBQL4+8q>$k(V#OD{=&8;VG;>EDp~!jNF@lnP`dUt$AWLkJpEx)866f$`Hb8x- +Hw31toE;u|_M5j^6_a}-<=Ra=a2rP*8e>_88LF8cTm#=F-0uO)`CNpXCNB{6M2LVhS1<3VT65h5dR+t +Mdp;ZfRYu#dA6G5Bh7*u_wvt4wz*4gp;6m~FM!P+4W6|0Z^}bxhM!KxaCw3Fi`JNWlqkrgMHUG>RNgW +auC3U@gljYTL+bwOVBw-?Q&u)wN7M^~|aYi25uU{8-xKQg3ZhlA(_g{5xp)Lr+t$H7wa$uDZ?d@qz*Y +7jfq1@uPYZbIz|L|onC#LY>NdmspwHQ$R_7I<8 +6R#t38*-Wzpx7?aLVd?(_wy-EHWlEa2j@7@A$#^D`D%PQ~KfW +cu=99xE9`XG0knI;&$Ke6O7w51%O3H;1i8VEzLh9pg6p`|IcTdWK7k(-j{&%CzM-G2iB0> +v={LhBv*y@t09;uAl^#&6>~rt9PY +oulS1L*4%7a|c*AD6@%=Z`>C4z#PhGylYrdR*z5nb7e^vP~bgor&j+4;e6VrFH4wDEaVc&BK`l_b$Bh +h~!zdq!;UNxut2Gxm4a=z$p3GrYEvl`r1$pRInebj~ENIw(cWV0)f2t}GReq<3Qbi{Ri>%!Er8Vr3=B +#{=ec?y4;{UZf8H5Rwx9mtlfP-yGF|M;U>;>e)yWK}FI>h;fD +ZHX(B7wP$*@ES;4BLDJ$7d=~cJErxw|Ku9>>kLd +DS$Ipcowj*y2KB-wo_!(`OR$u^ZA?_zT3h1TO3m0e9J_%rc3mY@dIy$Z!5`wk7Yj^%`(EntdLJ +MiFAV?4R+I2!WUiG>I6(A7asMQglJ#*$^e2VM;Dx4tu7#ORh=fC-3Ix{IXu4vDHt%Kvbw0LXj!~5$9m +6ic9%0d5W8ksK({GhOfE4zxvv^1XtP*($&S|=oDc^(|~Jqinr=Q4BY8L{3V*=0su2iAY#~0rYT6~Z~F +#~Rzx?n4Mb|pKnHr2y#WF=Z>R1+LM;&hF5^m0g9Bs7tUHV;+DCkL-F?4oCrmHMPk{hKs|F +8@?i?v-($XYk1v=Ob#Wi?|w0EZX3+BX!F`PCv*+b;H9@ocpFBn`^k7^%kFa|n_SDJruQGcvWTtOkG3!J>vfJ2$z6{JAZ6dl7isPu>UA*GD{5#hpOs?QS`D2WY1ZJ$s<^aMj_*OrEq+! +h$!YgNBsNUn1W&*CV~2>xGYzmjj7xJ?V96W!EhBdrD7g4#L3PIo1GGuIbNR#sVVGJ=o?f~uR8x+C*f% +j|wu7qjLq@jh)Rbqv{iaTrDXi}-?;7`dKb7-~Z$SF#2sxnN^02mMC(c+lOJtG4QnwmX-556_%f9>arAi&bfGCQR +>`-kJ7q1(4cJ?YnQV;XCx!7W|Mh1qWSS%3Zj2SNFG&PFpvtp7n5_& +r-4$kiO9M)Hvyg#tJB8wYA$dW;?A^t|H#44-uJLSNED}f0I}ts*O1N>CmwI!YVH*dnxD<3~fpp9Hyz} +KPJ+V(A3mJ{@T!5?D(26jUnqc_{tWs(T!Lcm^c{lY9< +P50GPi3gu|;q&zVg0W_4p8f0j^+8vxNUc;n?S4Chh2rq?$c>#b9w+B)m8B^(7w_Kf$)M&cQucq(qn8u +LfCP{uK&eA(fQ`BV#!xrTCmru;Px1|f7&S1@K-0Heq&as3KLytHf2ehW@FEqV~_RxY%&!^t($Of>?w% +J0mn)rH*wnSO-(k+uP(Dnf*BWaAE+wHRCD6)jf}ld7e7t8jvOylRyXMSj0A;!aqC!8Mk~ymyBz)Ju>0 +V-}hQ>TSspZ%6XYYrgi5UMLDOgVP91ShbyS=JLJpd?LQ_fu2ChZtxFoR`|?&L^RxeRPUlz!@NQddQ8u +sA4Og!-&WEQnb`{Pz00#@g$Wbj66E=iUS*Iqavu=LHO~sX^cXC^852l%0K?lY&KX@I#@~ISHvf7!jU< +96ZuO|vn>3sbw)*d$51Ec{t2kS^2@ZX*jJt4T93Cy1S=|Wu6toYv6b-_p(n-#ZD9qjd +(vKR=Uug_hZ^GBDXiu@p~)NOxmX=s%sZr+M6aNr0ArJ|$Btel{&Z?^*U@HscfiyDt+6C^4bt9DYmsNn4Bg^)I$LyDNi(K-qo=wnX;n&0t>0pj_PszlutKk%Q?b+=pzRfC*R7~zM^Li34qCy>d$ +=Lg_}VGr(|B4?v>Na`z19-_`j$WA>cx0PwrNb!WlxCzX&AOnD^q*x)^6%c2MV#z>s#F)&k%|tWfaG$c +$JQoD&$F{iozNbfnuj&D`jY4%iR*hXL0R+?syoTkxTHa2(YGqyBV^-`vA^YW61m= +^WA)2y7KZpqBjMaEpqN&h&7Jr=^@}*zajM`d>+x42Ymu40wo?l+qj +T<5y)%-7$`JdOijnZ^OGp4pjFU8ts)uYfsL)2(d4PpH`hFyANP@t7!RDiS6U|t9&j2(DIZk|i>}*Zerk-nAuOwjzfsAunfO|At)^DGudu;WKFTI8pV%V4xN%N2{WL7E@l^vpVsmvmKF4Wy}@ +Veq=gx`(>Aw)nj4F^YJ73)|g>%P^9c5PiF6iDbaN=-7r7r6-!)$j9wH6&DWr-rc;G)gl!9FntbcH-Dm +omV)f9715LVsv<~J8FlJJgB#8sC9(Zj +WEoD&JZ_J3)OmR{Kiz&f{-f4)8lLT33vd%hGe)%|7M!JL_%Dgcb!F^zn%(oPGo|Bfpccm!nsFp;$6s* +u_}N()LL=&%r9pknuRTE4RD(^Vo6J){p{jQ*D$ufd~ItE(JooQOk49z5fdNHL$YoqL(KU*8uHJp6Dl{ +rhq?m+l_695)r+2o=l^oHtBM{h*IOYC0Lisip5Di%Q9luA&6VteA=7zg=cW7Q9jq68#3}^P3PFmw+0w +*43I~+dQRlo*gqI=AMR?kKj=n})qD112d)LNFIHqq_|8lS%Yk;cLz4V9xpUqfh#xZ9K^f3alnJF{E7r +5?%A4~|S!*Md3j9;45TZKXDz5&-+sAmj&Wt_<_vgU$(@kQx&Lp;JJO&d9Ouh(QI1X$b#;!1lfqO)}k+ +ty`{6Z!E8MiSv+Zoynnp+8={>wVdOF0X1Szj0xCBLy$`xyFFt!6e1)rQJ4M$p!a`VJ6Etm(f9WTgR*A +7!nW0Y!EFx6IM`A5cpJ1QY-O00;p4c~(=Mz14uQ3jhE_DgXc=0001RX>c!Jc4cm4Z*nhWX>)XJX<{#O +Wpi(Ja${w4E^v9RT6>S%xDo%~pMs5XuzdBZyO-;=FuH9oX;K78lS4Ks&>Dt9TeQurED55#yUyXhduN6 +Zk$QN&cQt~nEzS&QhV%H5)RV!H*)?axBlZ{_XwP|M8zR7W>bA4}nWe3hmCHqt7^R+E27RcCVb>R~NPG~EoNOiQxn +>zzoGvMNeZ)yEmziaMc($%-p6V=wXhXoNa~*RHLyG0?rGXHk;md=K_dN23vO0@b@B7YsgQwyp%PVO{Q +bB4@b}sYsD7<4Um)LKTN0gVdd#Gv8Jht;_a_+2VbS +jdQ?S(e7NdjT6*2`5br6a{ID8jB%$X7{WX2F)-(SXoD4OINRwgcay^FsTGLJ$>MbjD15lt#%(*=!9rc +)iQGx~+sN_EO +J4E48=13@Ks~2Lnt!$$e`YIzKGY!-x0z8T&4}Mvv7srgNG9_}EswVnkUTl?RR@8~*{BX{O4`T0R*rRG +g4jErNt4;Y!r6{jDT8=kWn$0f7mfe*R5VlLVOYhMa|hh|PXUF%H^&qjQg(L2l|S}4Qs!V~{~Vl8L9ky!QOCTL~8DI#aHpHW&we@+3zA*(?!%6B6oQ$ut?%AliTT~_F;o++t|B$Z +X9e@#tr2jXCu{L#oWPa-{0NDVgRMeRtXLg|KK8tRE3pxg6X0R+lXosuUWpsXr8b61|uxoaJ0{y^7FJ~ +?^BC44K@+xieuSJs%_SI~7RNLI%h3tTIL{HTRmzNWs!7RUPV@i^$z{R85AQ9dO+oZWQHt~ilku(r{5(q0qJ6z`Eq}PB-L=!VFz!(5YsR9l55N>iM0lOTVBFQl-p^xurB~BE`Ak@ +einlEnZqj3qjgENaeFfK6(nS?xB-O1vB=I|PS>(D`Axwtn1o6-Y;%xbwGD2FEOycnSp=v$*z`+ExrIo +oj|6U@Q3?c1za@l0ouUp`&;>Q+ySjzZkiNfKS&ZD)E+K}xi?aszgIw2w$t@~b#gXhYfsb~fnx(ySGfg +aFP6$U&8D;GOG?7JWx~on&EIvaw}pOuZq5g^_|7y%tUTS4|#{Ond-ETxPrmmVFFWpsE#?+i3EI-|>lG +*luN6f}!G}hOG^hX$4-2CUJROPoN;f--7;<))O5?VT(COf`>L+^>$yRhpba+^;3|ezl4KX!F3r1|MD8 +(uqJ}Jl0=Us3Cv6=KICj6Y}?*@J$}hIBP(sNhO23#$Wuames4>%NKb>niYF8Yx6s +CdJvg5GkIcv?O9bJNpSCcXCb-2{@5ioHA!!o7>I2;Y1Ep`HPVxcn|KBWq7No+2@E@2uT+9}SytSOG}?4Ri)M^A?045#d7*7ksw5LS5`)+r*b0a|)jf +?OuG@vCJ*i$ti;ZrhxXT4AGssumxP=B{95?zd!?Tk0K$H$(fiM;N92rnx;zBo)>%D+do<_m`(pLnJ{e +a<-THtWb#Q`>h$N=?``TZRhe#aF-V^2fG$0TwxZG$=uA#k?PCjIav9`sqAig5y8o7$*B)WX=k3|)jif53UsG! +2oA#9xEoL(p{s0D8b-(ORnc()T10%G@_~0AmG5dc7EnWZ~M;v=>)P?48gl04R4;$ipHd +1m)|2cnB)*}WcFL98Uk;XqZsM;XIBvrp(Jn5FGW1ttSIko=*XHyz-JjQv>aw`Bz +VHd#~x;)h_-$L}~P%&wJ)EF?8L;c1uU>gY)^r5UD6cZ;WayjZmNCqMsU<}_28+qllof+E_xwD-0coEa +v)sc`Qw&@+7t@87_O#P+Vfd$n&b=Ip7hd-Wm^;I8F1_Bq1Oz{=p9K<8^PzA%isOof=p6N~j7k?m2LgZ +mb;O-ww)9>_Y;jk}Y6k2{>!RlMEOTY3>ASV}kP$<3ezL`bB!! +?~jQK8hS%5{ullHh_=()QHdc-=C9^HJRkwR +2KZfAVGT7fFNE^v9RJneGZMw0*e6k}>t0Amug>?D^}MajyxRLfmiQdx4=d +$x}SfWrM&>Pfve$Pa_9x^v#Gx>0BhsV^*le=m&h# +JLny-S82A*#d4*X|7yUVKRbFcg1^3FzlbE-Xc%eOr% +V#vou%ie#)PzEbnq&-bs}gk*a=eWlzHlqI8{wiq$Uw*M(RP*_5809f&!%v-8t;7q`4z7FMREuuV +?AQ~ee^l>QH!TnL6t;0ktxKzK`i~ZDN6{yJ^Xd~3tK?nczdo0USAiGTS(>z)UWKxj>Ht&D<9$w-uPx^ +-vZq+t}+l@$GJnxTx>H+j8qY=}P7h4kDFw0^QX<3y(-yE0B`9BJglZeWIjYfzUNRL#LWRX5{k1uF9B} +lwSsIX%`zs|)y2*i?0=kXcw7ZADN_dI4X{(aI9@Vn2>|fF0XQk1OnfS +>uw#KLO7JPz#ayAQT4-HWS@3u_*OfNEU^040;YfkXQ?mOVBluaJCU@1-hUiC3BJh)^5*9c&qjX?K7HeVU?0_|;G4nEfA9vqn^$iq*SFK#vp3flr?&(EFg(~ +{ui6R@Dowg~=&#dx5%WQ%nTNiaV0JqI51;bjtfsNPrgNTN?k_gTBoK?KRv12UZ*^pTj1?JgcK+}~E1Rb)Y +fithBvppF-QIabAnS)Bdxjdm@FYX;mhL*@ZSV~)(Q0@Q9c}60 +W#&fcsEX>h?Gauz6NjHNb3nh*KK?8*cUm5cd3<0)$BZByZ0a8)ULlAbYj*^kqYiC?N!%<+os;$e6M`B@2y$qZc^qi^UYxJpzZo +?GydMy%P;_i(O$rYc4$x`ZJsNK`4NQ9bBeaYj+(BL)&r5G|`BzkYU59jk~9D;QK+RBsLyx<+)f1Yord +6Ar)qtPFN5srD9o#6VG7~ncVy$m2&|8NJ@>%*Y>hdanY48}RX-Jw8$2nj;yxv!!)oy{m5abF&=iPDw| +EtrJC5KSU~C07h=H)k_}EKJz-)y?F6(AXs)OoL9CpUQwF7L9n8O7Y+gkn_&-&*6ROp*D8~R{JjiGi}0 +mk+~0Z5ki=N1n0{4(G_^_gTH%&#=xoO8;mnIfTkT5`i9MeUSpn+(?r2EdrOJ(Xq&wTvh6*)TYz~@{B? +8%;tG+?2{|qMktNY6Ct<93o`kO3(jsn0r)fq?W{D>lH1iN6Qr}w^0tiJ?F(5Rpy&j{0WO=w;hsR)kK< +NP^XQ%T7)F9C`!I0UQ_Ac9s7Y$t`5U_)KN8I;x6oQ;G@rdV6a1^~<<8vw!U$0z`?l7okyGY40LU*h;v +1YJ;lSFVJ1{jt&ZV|X){YxuWUaui-$=4X90UBu6jp>6p&JGH|kQ>A2%=R%;gJ1|>A3UL8Z?JbcNMPFD +h+Kb~@x*2ddXYue9&8bEkpY*&x&M4X@_O*ZYcM9_8RSz}H)nZ{snq0(ZaVy-&V&NUM-%W&zIPsqSZAz +=7CV%lL(%7jW&ka`$`p--vAI{Y3$e8v%oUIJ}W5$mKE?kjMF +g4!tkHyM47Q^eA-jXh2p*4dflHZEk;y$Nsv7FBvW8a(ZM-m ++#OemSU4_XVg@%6)^AKozndb9l&S_r^(^HXTg$iof$9fa!bb8EIN@d66@n*9LL^3Mkbuj1S|Acg&^58 +Lg_Oy^=vx)IR%#)>Bbx1z~k^pJ74A$wd0)oyU&6i_yKZ>x(6UJlo`(q`t@j8rFo#KIB(m3%aRlwnDgX +Ul!hBf9X4C;HHhpTz4|D|hrJp@dIi06jYZ3n&ZFP%3_HK*QM#en1|BRhAs9EEa +Q2B3~7i1MvPj=G5NAZ&omD)ZC)4KXBoiqObpjP_hNpiDzrXsG=OAD>fOlH|VTHSC@&MwZC-X=-Vk+P~ +SUye`e=GB(MJh!Y!0`ul#ay`FirUV;dFb8h3v;h+od$-b}78zeI|?XlMeo3Qi~%<<5je-|(e?5KXx8j +wCy7-NOzTfTm_(4N{Vu?f@H>6?WqX7z(j2u*|V?fwe^p0J17V+h2RyBVUi=PU~_6Z4bmIq3fLUOX|Nn +x9!SmU90}DqCP|gWW}naK)|yOV=SRk;D|zE_%Gtru^KS!o8pB45<%%H=7_L?5qZ} +@I0t>|PmUcISh?XZ(D2OtL7CZUiU6vTF$5O~7Q>)Q4ZWE`QFQ86MOvj6*^|6+a>K!+B%i__c5C3 +|&xJ-I!-U>9e%w`XtJ&7V)-Ub7n@>bs{$XMa2!=&)OpjPlC={qMc-Zr+?;G@*F}?%A`~=L1b!wMl8;X +dM7sDIg>U=(DW_uA+}*cusX6LbyRO4DAu4M;X`>GSO84(BYiT#T<#GD*`~X#0lhuf-q)_{w(2m&=lrd +*q8+>0`k)RAX`I)1e#<}j%&HYCt_71f?eRp)s<|*zE?bSk3KmpqVNsZ`;BqsD +#YgcscrBLH8aX$a-jzmMq%$5Z3v(1?ZE +7il!~>|Zlx6rtN7W#KO2jtt6^2w)*fPh74-XHppY%>}t$rnvXvP3E26ijka9IbSz`>A8xkD0~Hbh`_g +5;dp05-W$3e4k-1M6VXo754xdfi&`(8*g6Xlu0ywyJMs{!RcoWYCn*-X#Lxzot=CL;EtH^AO$4FJWBh +8SP-qvx(>*=7X{};ciFk5yg#q?4WI~3_tOJnY4*aY{;TQDgaRC3e2a%hBdv-h{5n{X>6@uEu_*kaLt! +_ftkAAkIRV8x@dztC6H$M`8TuZacp`dtGw=s#KA5SAru{>X|Q!lH)vJN&5&k7SRTYZs46K0I$GgyIh_ +uI8pn2r;NBu-4*ySRW}wR*Uk);@%W4%cKum$e93ThY;KQ>|Uj-5y0328*ge$vXz$&(Sa%$2bWy+Ct*^ +ycqYQoIWt<=FB{=^Y}IkJ<2)%u4t-{J-uDsK2;)}+W{$SUR<^3SjnOCL*`5x_kQF;LLz=&-c@?(t&d{ +HL0Uc2@FC@5=nx>08T8+bCF7SvKMI!i%!k&Uar^z-Kz*d_8u^5C{gQzb@L#|GEfBs$i^Nb=(B7U00OcRelxObRMb +@ai%pe?Yl?Y)u#z{JnnjN(RcP~lnef`cmlyn9I2EjaR4es=7!~yG=TXT9k6A9!H^sL;)K4#-(oZlJ!1Bqq#S_ +jOW=ixe(&)Jy_WL22TP$TP%mrh~aYiwK|#wOtEV-7Bg<5D8>+B?Q;zOWcj& +e+EVy!3Ga$qIiEQb}Kf*&zXV!T6l7LHf#}6+JIgrB7(|?iWA@h8;iM!h~SRy;NMS;2mIAynE{JBPDnl +*3J2>0LGmnizpqSLw=iaIwyDp%e==iYo`WOq_HX7Ir4E*j#q}QPYlT_6&9PdFr$PofzGf`4DlXe{O$A +SL{-gghThgmY@p8{)FTqVYR``SFon8`lL)H|ordg|R<1(LZXxQXPg8ks?nIU$uL&gHpiJqr{+&h?FDw +j+q-aa*?<1`x?aR)kN7zs4)c$Ff|G_NxsnOn2MqU9Pk0q&ah?94b06)1XcvT)4LAgGBYK1@erY+|No@d%DJTSQw +QsMKq-njVUn4RH&v%F*9p2?WaXtgBi)S%4JAI5AUYLHXM-(WFxcqUyquPg!t2pN0C9kPX!{b+Og0iE4cJR +3sNdsM1i@xb)Vw(Shnz*<(_%H7bi7i6<|Cb +6G7Ut&eT>1D!w!U%q}_Tk_Eihp^C8S#rXgypr?L~Xw?8a^FHPuvHz8NN!n-AG_k{Ymul;DTa=e;tO-Q +0%+n(CkEkab-Az-sG{jb;WJmFUB0jJHw8KmFpB-XLmalsBXrg`WSGu@inAhD@a>Lk-GfOf7X7p&rf$Y0Ch&wv#N0$&D+@4bIngv8t{NvG^}cu@A|soksmbdXZisfJ2MX^py-y;rnkJZStK0T#kP +6+#OvWBu`EM52lAb=vA0a%^`NcD<`Qn!C-jP1mAm#1i=))8hjPZ0r$u +AsESBGBg^Jx%}XVit>tonBAaBBjRfI$OXDw#*Xjukv>0t#*$zekfQS_gj4~$C#6^U4X~>P~TmnYSA%m +=OJ1mf#1eBZ5G`ZWo<4*<^w<0HdfkrgkJzvew{eb{r#nx?*M0Y|Lg!|;s(8L*%2708o@!(BvP5`vr{@ +Tg?sGuN$c#BTKR!(2-F~)~ESk~WLGzNG!F(lGoQX`oMS9o564o{Q&>fE*$x49Lx)H=3VL!_zLm)tvf&P)h>@6 +aWAK2mt$eR#UKm)&iHg0001b0RS5S003}la4%nWWo~3|axY|Qb98KJVlQ+vGA?C!W$e9wd=%ByI6V6+ +nIxOc0ttrS5rU#Yj4mi~32sa_L?yTwvrB{otq|8OwHRgqD}khwwV4d7*lJs!r?gVE53PNkhqm&=AK)& +F*`TNa5z|67wp8yrJQ_+!77{Y=Id^6^3EDn=zn{SR>|2D?NFpL@gRFz +@&Gul5V^S}S=fxoP2uVpc>q`iCfe#7E-ufEUs$hy3>4c~pZ;lc0ZJ@nwCkA9cW`_}5b2I0}XM;^^9yQ +eDeJKtTkdQN6$dcI!9*54gn-_iNYnTfyUKfi6}5qN%3nKm;64xLJ9vh7-8A!E3cGXWk7>MBh +o7%MZ>P_DA9=`!hJOq{hhM#shv&o +-dV{d9=Fu>D(5g8Ns~+SZgmIm%_k9lh{b@8D)a#t1fiZ_~!Hfp2OxAEETxd?ix`u}!O#2FbX6}V&?$_ +X)a~f8!`7Qu{mZjl~Be~L9vl#1Xs{ybE|?i7#QqVRi +6qx!iV(YCJzHyiifkc1h;QNdI>}P9K?m3ZgL<46Rm!On#1%s8M$)M{x10rOsyjkttRt7t7%|#O~3RcY +8t?rHeyXoqNYPR-%yh&QIlNBdhKeke9+Auw1noAFz%(K*+z2&Lj0sxF-lHzu!2#J2P+J+i!BHT%L)gT +_vPryS}Z|4JKN*+{S@j9x(4m4?*RuCA~g<5jXmTMsK$Q85#^o@`(6NurDU3_s(zza6$Z(IWeO7YC9nnUU)EEcII07BkfPH2IQhOD7Kbv8y^52!s2VshsJ!C~D!$_`P8R~)gI#BcVDjw9T@BpTSUh-%*zV$*CC!tw#k3Hn*=bj7s-hggM!d +W+|#%R~s0#~o!0@c<4@bkm<5??wZakgA}##GtuXRfOxssR5FiXStieBGvXN_sKe{9yi)Ue#VVW|CLrCEo;v>hLPjY+~}kbE$FwGDO!VN(^Ms^ +Ubel8CDuE}@TnD!$ht2TljQ2bLZ(fDjIN%Vm32q7akYB>`Kn-q^KS{$PMey^KuCg6N!ks}^-2J8vAdwQ3yb(pZM;0k$BcRtq>arV+__pezgw`ZZd9ARhzfww<3EfMC{U>yq41nIeqgfoC34jUsmUV#JOHKxw)32QR4Gw$@3Nf +n7>aw>4wBrq!-8{YR+Fqv%Lb@pv((eqK|x}CGs@!Ct6~!mbiwzrX_BpiQm%_4ZaV@Ihkl#{t8^f)toY +@3xIPm$85LP3*_eYsysAG&9On6BPtYYDE7XJL%B5gY(9a$-MiEcWMRW1^0A1P{B8yZn$OSm?M50Pzqo +`XYQq@0EI+?$Ck}_2lLycc2k2dl-Bh)3uGS2jTYH^_h%#HT-G?q=yCK@9@w%fheoJAP5RPUiJl08 +|Aup?-wqz%LAzv%p)qeM~(J(KInWouBAu-DLY1jnV}x01s-w!UvEcQ?6yjc2*vUy$kJ!7r)gv7t16Mv +N(SOA>eaWKDQMa-7V0T;1jvrzEtsWE>Dn^Zv1HrQpd~BjApVy1GGjcya +f!mjY2^Vd80yM~2Uz8$zt|xTs2|Q~L)k`Um>LoVqVCU}k6sF+3r^?UzVwZ7V&80lf9LC$E3Jsc9y7eN0fio9rNp2`9cmQAIXwYq}msv4nt +PdO!E)*O(7MFWu+Uj5hq}XW6aYzXti%Wq{Bne7AUr&`gWJ;~H#9W$kN-NRSVG3eSzcI+)#Ck{>HabaI +y%b-EFMupWX;`IHs_wxyczv@tAZ1S#xhV~?QCLN;fo30#{RU9($i&$s7|%yy*IfmImDOjn0aBe)fi_j +C@r*qLT+Zr?81hGepOXhcq^pU@Di +s3+7TEPTaxEnK$Ja@PG($g9Nk2j{AQRAHV0o{XtE~!K?|7RZ3 +nRhpV{v$U>WgeVu!svflHD&B>mlAj97Pk-)X)<=u?y=L4D$mFpoBuy;M$;_N0zCSlD +fiOwz+R}V5Cl-HnkTIy_sM%LaZ +DfFmsu#;X~sm5;trV^4FP9uu%3F0TsE%dsJ1}E^qEMeOiB($2uN~2AaYCWWdvxn22}nN5Mr}~l|7_TM +S;FY+?dS>B?X^w`^*k8Ueb^$#_!(%(>ssb*8^<12Um26&cNst8XbZ~QLVJ`W%VE=++OeK;Umg?Bxr7* +$xUv!jbR|G_MQs-|29g`+FA?&Ny;sI5-#& +4}Fvb-`M2qUwKY=bgzIsY}x)Jo_YOzU#`K=DAyt|H$>0CMvLM1YmSDY?8hvY*K-VIoJ59^Dc0;BuZ_2 +Sudr$L%R4#nIlFD!1>%6u%h+Cnq*)=Zod22MAOBW{)%v(@1nwg2g{^SzW|_{8cPkvFyNT$!ZkCdEy~+ +^Tj6MV4J@157hyL4IsSop>W(Hn*&WC(8`y@&!>eyPm#?)K`PHW+Ice+X_Rh)>~}O@TniaCy|>aHs*8| +^N*Si9coU+Kw~!m*q0Ym)hz;2G2wq3K=;&#f-aPaG)V*P%{9Zvv_@YG&+m@0YWen52#L58Ut5MR$_B> +Fme1PB)TuzKQfjaan;F+*+359JJ?-aKp4>T48SU16Fm5suqNyqqLxVOKEm3@0 +t;LD_Jb^Vs_&=Jr8Zkh#dtzz*+?y=U(}~eAa%dZAHGd{}67#^5fgm#Y2Xh-{BiXV&HGqVude6V +s04gl?TFqWlf*p!)E$KnY0oFLOv^z!wETrzb}sc`#W!{LqnTQ7sYd`{e2w3PZ@)uneUeG8U*MW +H!J*vaLfilD+G;7$I9Rx>hehNj-i$KdztTlmyVQOLB2cXb)1J +)wQ+o6rMenIvDv6=;rcMig@osDu??x3r2HYEPiACiHV9DH$?YJj@snU(5-;N1y@+!+oL4VVNu(%O_?t +Zx`wL*Es>-i=-b4)!bjP3KAa{U9B2~s6?*Pj{ZV_d!$7y*X2Uoz@e?hpYl3h23R!TmBSpW1`E$|Y@JD +W@W-S*?l`p=1RW5cb%vNX0B6}=7P$aXCFup`1xjxtAq`zmAHB!+4vM5RR;#D^GSft$%)z?3sdYffUOD +o@gGEyZSf)wS8Aw-gyzaI^k6h4|i!4gW9%g+1Ms=Uu+GJHwK5#!PD4UHoK(16 +f2y4qku`!$Bvq5mUiu^b`$()ODyC+2T8d=B%)^7y~!G65IzsQxn{%UbR<*ikI&><@-H3h4W4-W%u*u( +vIV|IVJ)e1u@HTKA#@T^>(E4g}=)TpZ$^=y!$TW~8{2ja_Mkp*-XbFF{J)ndTW)nJt4j>bXm2d_hfSG +@%lT_H|ZEM}PNHk%C#4UO@FPk^ys*ElFQ_QV1J<*4m#SRpp{GW@hxabd9^cl>dEnjG#IdgZW^Dn~F7D +LABL&~?YrwZSMy9In9)%K_Wa4- +pnG?#Aal5SLEa5qL)Yo-*c-U^G+I?2#{`FmC08w`g_G;YA+D!Gt7A_i)Dml+94!F0-7nZ;En19CISVq +J8)Z26IMl@?;Y^WW<8Leyvia+agk0eUS~`a!85aK9-CE)Aj!h2a+Oas0qm`wk*uvVuzC~82@Lew{yGuv;Br!e&B3Nhmk_8yVr_;iprkJeH;j*PzUfl@7TVF~37U4Pso +q(X(GF@i6r-i#yOy-~lxSs+#^PBi=@~bh)7Tt+0syw3>)t(V%c*zW8G(9Np!u+;+N!Hy|-1U0MOF7>t +?p_$9i@O^yCK0uCKc2$X7r9sg1o$Jc)*H0fbs$l>K#)9p189u<%q7Yclx+Qmpo?|mCiq%SC^lV6ztx6lJQ9J`)=D>lDp{D%Q)HIT>d-LYLliLf6oc~K7%z%WbGUsAAzMALg0X +V6-f?K;hC>f%%nU??Az}>bfFIV2MT}HA$S(kn$nBdC3JB)R;r88xncD&O5WpJs%(pQDX2wjy35D+W7~ +mbc0Nl|vSP<^-niQJh8eCbIWQf)(U{A1`ItGAbEW=IT#c3q78gS_Gk{c^aK+z +gE);0Lzyn!fhSE!STYPjE7kQTd>F+n+YK~RlbLny9)j&)%NxsGC^NXB1vUaQW8I#7XB=yuk2=X6VTc* +N9Q`5+|zVhx?m{1!*R0V0_4G&FHDjnu`VdT<@m<0NG+FWXY2>?1C(pvT3(^Gn*nR=)z!VRm#DlSmxXv +i*mNj7V1b%~lec4?pn}RKT5BQH`ncbStRhavYn=fGCFIGAU}a)W9ZFi${B*&jx>zR&IOqEpS@Eb(abg +b2A3wcMXI_t6P^X(uMMc;R&rptEKPfyh18$g2h@+c8opeQYu`60#YeLAxw5z~IakE9c~xHfDL=>&HNnO+Qf>G7m +y2q%fd&l2RlS(0(ZCy2GqNGMI|lg4Afn`wKV>Gt8g;N|;U(%ii|34zKqR5N8Cs#9VW4HCJ_YD`NoCT}2ufpx}d?W;&*)()2{)1Ia`SECPx +RIoz3gsciEQw>=~hW8JW-1Zyu~bLw#0!&ch%+6b)F*g@6{vWN{-CtHzV6~@BkMd1e~c`8*61snO)nxs +7$OE_jl;cT+j9ml}>>*e{SRUY5WCMQFlp}J5mFh}7|vJ0!4KV7ILKZlSsU%5}t_3D7WA>1v*ZDDEg<{ +^ZiAT|Qezlt>IxvnKX%$2K51yY-0KT46soW}LdC2=9w;4cEUu|P*Jmj8vKo8~CnwHSxtk^vWFv|E{27c@=me2Y%61$KZEziY? +pjGT4J*9M|e`P6disR04T;-k%2TRmCx{mEeqW^Qo6-2)wBRNbHwVo-r%Q!fEx;SLuE#i<-_!D-@a9*D +U9=(^-^u3&~&jmbWrE|e~et0evHNaLU&snSoSX=twxS3v)tq-7<5F}Pn1*Y#RVISg)v?{f+BC#C1f5>Y&Xj(P}Fgh|6pKJf@ +K?xN-viwy*)Tg)6z77hHm`Pjblo~e_)d}dsvNzGoflHpGk`VL=`~Bh=&ySBxsl{P) +GA|6bzLUGU`2TJtL_0ED)rTw~&Q5Q1y`fpdhiG@{?J}5utI}KHC^*-1qS?s;_9?3qa>WhR5SKfZD$iq +@Mki_oIkp!3!e11&QaD9Sb1bydPum!jJI9s~J>`YR!ch;k3A)hiBIM59~*Zb`uD~JeDfYkXgBOKCu4D +1Qo*?^6pGM!bTR;i0aiyo`Xc4O{Wm+pG2MkEwe_=;h`lve}-Eb|F#My+8LZ^A3mf*vE^dv6a+*|m+P# +##fU_0wyPC+Z(*Q0uo?#Gv55j+V9WHNlNMQLP>p&5GJEkirw4zhoW +}d-NAhL_uId~&J%{!SwJWgkKS}{J*J*cWtO +9iAE;J+Q1xN{IN7`61kZH^O6Z*{y4xTHx**GrRvJ7FK{clp2fztM08*b@g~(;poEAJqedC-H +f=zfvUW@=B)&b082;;-DwF7pynzIQW!&>%^Tt^9GKVF3*0|piso8QLUi!)EwABKr@# +@eWwHTn#-Ef$s9_6^=GBAhn=sU_Ud*rDL}O>BRSza&rs;XcG{1w}bqcqiD*j!( +*?vSZW^^Uh3@&h*>d8F}*YW}$wdAeT#-(T>ANQL)zQZu+@l>RC5ZB#q2i^STf^XwQ!j0FUd3RARza019P=u=ZHKJW836@_LuLvbsIwa~fT)JR`@U0m?)E`D8pYHaU}SDw}@?O8XMu3!GjQWYzXaw^9X`#tGw~ +0&@9pP>{A0J$bh>DG7-Cwp!K&yP*bYB?|Q8n;)hdyr9%cD_Lopv~sd|k4;T1KN(2TQ6j=rjK~x%;tk1 +_98{Q{*}!9Ufl_0DBQ`bVT3Ce#R6ezeEO-F-6<$Jq#rPKo17{%Mht*cN4JIySV;W})1`020{F-tnDbN +kCQt&kXKG%i6+fU%{o=*Hda1_t4w>}rAkKkGQ*xo@Ofy4CiQiwkO^9^|X?CC(yWn|#yV5M0K;;DC()` +!ef3J^@Yb5IJN!2nQ75ZfhJ+8~Cun-R0zkzi2y=u +>Kyr3Ye$IZzOL~f!yjD#YpRP@qJ!4Jpp-EyE0^|RlBd;M*44P&QW{FjsHkg+ZsxajQxni{O&dw3w~UQ +c=3jYbHR%L5igF}kmb)@1_Ne(_u1(MAV=bWz}U +l@)wovr7o3N@_i~^_K-K=DHYaf`}T={zHLQLW|cY*FE`0$`8H`yzAXrU1>IF1FQu8AT!Z$v+|p_Snl& +&{odP9SJm~6KwG;D2_+Nf`<&)_jT6J*2djxxH7GBKrHn|GXT^aber51ZnE%V=#ZP7j%bo=NwFgDyI +0&ZM@<#-#f=Ug6&_?H$MY1a{ccHTOUQ?sW@cDAe=q5ikpibTKAo9 +;X1MSr>L>;c|LrS4^|_?!~I-Nu7>sbUNr~f#ltL+FvO5&4fG(VXqlBC>(h|(f!slUtVPR}apa3vwCem +Pqg(aYPv}{wT){#IuZNs?KTIvEvT`l=KtM0{9Z~{Gu?a|)c+Kj*d>agPw!An$xB6c9(xtxLjwB5vuFs +X_v#X?;I#m?P%UFP(ZJ!{owTeSSyaURZD_$Dn&5pmXpA($BKDgo!HaP=YEIwgogrhzK_M#NDu6l-f6~ +`D~UV#^7_o}B+cE(+KKxPDrHTRd8Qy^wP!tr*fkA)7|JBS5(S1tQ;)wNc2tqso{7V7)`6b8B>8TaFpSIXMiBQ(cWx4Wu4{bXENf&qqC8 +KXb3ov%twLB?te5VW{?AEw9OE?_H|$`PpR8kX}B^BsCik`T1PbR61Ad_h>ee>3;>VP@bcQDzF?GOW&R +F{(wE?)K)-!_g`32LN6~b_o^QPkH4=!&7`jj{?F(}Un`SXEO5Ge{nLF5Wr~+R +7so}W!ekt$*sLF^i||EClSv~L00=K$?(fMOwly^VdIhB9&(1Y`a#k(l^fM8f +hj3nD0pnz%p*>a;o>EuwJ})y)avhnzezDw+u=Luq^?Vll +;Ra_R~R$EkoU(w)NYgbZ*~~U>Vz~wLMmhz%OCXnAlB;g0C4}aIaqQ7+NsJLd-&o?7gB$e2;y>YMqbY$ +$Q1Pk1rSFYxrEyM7Y5FY@$;Pgkm8UD_ponxXN^1j|S4##w@mzO?1N+`$;mK7n5o6z*KJ$rqzp^jdO)u +vE8g^Z%Srgg<0Zu4d!MI<|Tx|({LTeNnuuTEw|FZBV5ZI8u%{PGLr@bP#2+*4*;Zfmck(BT@Ac`9JV+$f&;1l4gVmL2KNem +0pC|m2fQ=|jI1h=E<0o@Llw)aq)!>Pape+V&AIt0jxV`t&DpAuyuVl2?X4C4@0uHiHxRgahH>;(x+u; +8ie79fK@3Y9m48n8;PrQeKapF+lt?i*0u#Ypj`vlJ(L@Fj4Hd&&mgX-jDVAw2~`8vEe(hL*aCYn9Gk4 +dvx3ZGI7*&zm}9r|^~`0W@EL_5)I=fL{Q5Xdkd^MABS_2&eDD+~j +ZkayX14poh>h9m7#6r{Ex+Mqs{^UDNXfW0$ZA0;B}!QxZF +#RbK|)Tgigc6;XheS)=N{McAKJN;*DOZa*ax^Kj;|LFAne0Jtwx+fns|6}^XJHf`6>Do6teXqtJHb|7 +o~)`b^jcBXdda5{!0L$6lj#1RYEiDO2v(j`v2DuPDW)JIp*3rXav&JMf(qKp_2wZLr3^@-Y>QtBM3ySIH_b2P?^EgYl_|w( +T&kTZREjCvJCmHVTKe$(R?_dT7w0p4su(f`P6>Z6py_xY8A}ik;A{e{@4|^f+px>Y7yKGA#+&^&C|SD +<@GMYLb{Y9>e_lME3E!3}Ll2zV{Gd#ij8 +Tj@!GV!ed);>vORaYCIIxwD?B;xR&x;hbw0X2TG7IW59YBDZz^N^}l{sRNR;7hWnn1Y8u6KyPUhmM?Q1)i;l_(PrAo~GrU)_TN(*FUjb~LU=)+Ee*K(tf}1^T&Be(nX@Ai8FD#J&wJ +C&r$np!?ESFl7(CLKlEB7n0$TQm@8*aet!@H#purh8rB)e&cj9&n|Q_?eI6_b|MS*HlX@kmAq6Ulrwd95uVU6Oai^R-dC6Y|uTEUy_p~!x;z> +Mr4OMlVsGqQgShGi8L-5#okqTS?Vubb?+7E9*#b;x09vl$hq&DQXzwv2Fav+d=VUw)OzFQH}#<&z8Ed +lNIZWgA*;&U=LZ;FD9}^&mszScqF9^slT-s6LY|qjuN4KsuLjVOVcgb6$Wh(@TB0Tn^H%wYE^RK&K2UL5vt%Lc&QHuJveX3Ynl +jH`I-wZnUc-6IbH4X%)mn>}8xRiDWNisv_^wiBqNn42}fw(Q{%1+C_o#5pP>vSWREpz-KirPMKnB+iJ +mLl`(x`BcFp&c^Cx+wQfy8gej@5KcfNc>zm5G8X94}fnt84;^F#rG%P=dzrmI)Og41}@6QtlCTvJ=TA +Ve-7F?RAWQvz2Y_Q^MUT|r5t4%SrGfau&{f73$Cf=#{kjC+z815I(`X87x&!dtN-$C!GFIo%B@0K+Tf +0ew!2gJ$x^T=<|wn%O#P5_ShGs)x$xD&d1kO2@$IOCW{FT39o=;J4P)SO)!>2l3ONF{u#huqkSySWG? +E%M0}8nOZCivT+6l<+B8I0F^@<*H#DfU~wL7#Y0t_$??#nv#!N|Im;yHEO{ivT8MFHxx9#c93`Jmmie +{;?QMb>`0K`0*G=-92ykH;x>@eJe;T;#=NPrD^7Vrd-A%)*}PF(!B%=GOIE4WvZqhNV{9Qyj*!;|(XJ +C85%5|>~RxAKG5s&J8+Zv3A41$T2xKACvna+HGT<2et=wG_^K_dWY0mviB`ctG*Yp~Q7l;%P~_t(oe)k}q97*E9S63)rz$ww$My@FR+7n`P-osRJ +OJ}iDSuH$IR5GJWC4Wu12l7v)kj8H4L`sa2|C6_d1%)p?f2s-8errTjamU4)E70;PqO2 +`A}UlM0fyG-&5wNjH@-FOa#10)Nl)%CvQEEyx0`--)B;wGu@;=y^HuSzb)nW28vSH9NB1-?K5)Pl3qz +yS@Uvu$zi!D +lDw2!2$WoG6uobhox|_kX>5m{TA{KAOO=4lJ|1$C5)v%}qBMfw9(2X-Z@;t;8UIFx~C*>dPW~fb%$&D +}jN4~r=@+CC<5?twyx>(fYo1cj11CIGL*`%k5PYj~8P-;%RM{H^(D?kD0NyCCgLhSH$C2R&>R2Crtn} +*2x|EyVc%c(nq1fB#|eiFAM@QQwf|8DVo(3W_g{3JFFBI=5Ah5O|v_iJxg!P_`D+3+?D<6g`~Cagf=P +JZ&0Q2?G{fTx!ufVvmUBMXP)?Qi1*ke>``8OtD}Q-1O=yxo9P*-gd*fczvLS-*+OUqRGO)FT+xslS~& +f`wsz^4Sq;nQ*0Bq<~c8L+r5}X4jiGzp_`LP+@c|IjGPLM$bd!w)&3NLidGKtW93E +W;pk?J7NZoWQ4eXr*HL4PCRPs3G-9qtE_C$sv%J!L;>W2DYv3dg`4YDc7BV%s2yVMxTLjn9oBh!`+z4 +Gs@7-)ezVjb{gPD>`4K}p}xO^>0Zku5No=glFy>^Yh3g@%hD*W2ta22+aACJ*lfI1H?(Zu#O9#W%f?B +>jZtTH?z#q8AUcCWGEHMmo67qd}_kDn{T3t;GF|hs1 +}dVSi31;%13il~R;653xic*4_s-9ff%G1c4QkEhusHP&%-Y$tjc$>LPeC0nxbXuaHVFZMDR4g(=TR<= +Rd5wDr9Z76xk(Wm1UPj4nWdykO3A``VdV#WR-zI$SbmG9B^sS{YW!j66zR)2(DHbC +HBhSNWu@k2OKVB>@qb6>dVsQlxpg~;z4xCxEh#fc^q83_om6MSub?nrp5KhRQ`pn^4!YDn`X$b5bg+R +(%U&7lW^Dw(S59hIZQiIVOt5KWYSZ!+Rjiq&v&oby5^N?JO^ymv!JN44kkJQeciCJBipy!{lNqR0EP0 +t@uTAqZoJbUm;T1Hn8sZ^e`hw4^poReBNh-b6WqJeAq0bL`}_fO?n(s~#v_^iAn+kVRNj*tV)HCXXxy +?AlL26Me;zNy?z%^~~-Y7j9MWvl3W$Z$hw8&Sj0mY+IG1zod2$qQBw-s?96tJ!!8E4C=J|mqFUW74TCd#~tnKI5`!HF#;QpnjJ$8=f +vV?v7VK6{3IWS$Sl1ktDm7}2RRr^jmPtR%vb>U=wlG{S#mL5X9qwDMV1y6z+2G#VP_Kr+g!F^q8+H%X +a%|a1xn_vc&UeOEQ2J}CDnEYPVqiuHO6C6tUKXmiVm2}j +qNG&y1FbxaN)%C4ds?s*xw`l)ET^_pa&^&C<=Rf=<*4h-Ccr^+$f?y_Eu +VZ_@#~hXJ}NfX0vV@bstA#t7F%X|Iv10XpzV4~c~wK*<2odAt}pfGc{0#DFXk#(J93*MARD +v)Tl3BcJEp}8fx1C!SWjPyFsG~ZDupvprYO623MiTgzNp2GQd +qL5*n(5eONulJBByadfIWRG%3!Fztwv5UULP(Z;StK8%dEA)$Z!U5kK5P1-r<$+~ug?^P#cuC!ZU52= +^$+%~HwmbvlTMHSyc>m>nz4QHw~ +mJ7{ChL!2qVYTWbf%TR7zs5gs+COLKXqDY2Ocs1j=;hgWE*=X2m?-wF-w({!{8<9GII#MtYK&p6+ +edW!0n!&yaT7s!!dlgWK?&Z +YQ-|n#aShh`YDk(Az*>Nwv0k2Hag>(g8O7g;RwWZ;1U}a +yJ6*2&2-5PR><$m$AX>v3iw}d}SG0oMC>M^sFBD)6LnAUPLy)PLoweV5y3RbYkd&TPMx9h)1~~doA=P+h(7Mlp-w@< +3<5XaVlwI+$5MG$bPL&{A_5Cl8#_eJZ4i%#Q_ty)rS^Tbj;P9)6@mo%H?%OB4|d?r!96~dmpl?Z3mI3 +)PPKOZINX^(BJxLKv%TH>;!^?oc+YZ{bj`&Uampqpyge0jLU@*ja%g$A=ApFLyJZut65E2YM6M=@UUZM;wJrU9)2 +Al;siuoY!TD?4|Kp3~a@H#^EC8W<^K7r%Lw=D(4XRc@kTAyKAYV)9aZc>MjP_WmXy7cqqppOEV!aurL +!BI1KEmt^lp)ZU@E%@`a-)ZAQmHJ+QzOP{czMeS)nF=tU6w+^d`Llga#O17$fJ?kB +Ej6Bo&fHj7k=_TzU*)q@FK$`#hWu*lz7RLu%w+~h7*NBUGix-y} +tkrlG>6adEKMmU=1(`yh(PQCcpkr8wot?tHA)8k7* +jKz3tUv8+^HA4p;D@Iv1VIF~lKXew`(qsn*ux#ZtbQ3SDeK;Ve-!SLCe +;`XPLWsEj)ilzz$6c^qE9)zpBq;VfAlzWqzV6rJ0(n1B)&u{G;p?)SN!!;*x2-3-QYSO+5T?hrWo{0< +T{?rVOMnNSVL%e9ps@^i%dC5OEYMuQ-@>KKv<8&K4Mh!KRMy(SZ=Yfhpgnt_vjB8Ap7B32> +P-UezsKRUumvh#Sa#q$TS-M`Y7CSgP(djIvbasHJM_iiPSwFjN(eC)<;&wdbE0s{V!mM*-z~<@Z+UA^DdjFzspNlv3p3$0)`ERp~Y>75yAig6YJOXrtWNV*FODTFK; +Ca_a*N1W}?@U<3*a7n(869`H?}FTXD7UaxG1CAB&Y21}h9wmb@glAbzK`#OxT@JjF{Lp3J^KVYshUiX +C}SD-!$Ta-~(gh@KxVqUVRO*gZ`?7QSSm4jrBw<KRCKrmif!{|Y7;HwU^hmaO531sS?Urqx7S +c2WhvQIyzI7#AzK=Z46NUPtzj-VS+1rmz(2H1a-Ef`&j3M8BFjNK5%U=0d@o6kK#GkPhN(gykegTs6( +k#`k(=JbDN3$N0+QF>)tKTE3tHbo*SzR0LF=8I3UYDV;zBbB;6gU(eV1nb)4Peximw^31Z>%`M5^s|e +4yQ;gcX_#T!#J?)mr&^ZDob|#!zRx)iuN&3|08eHT;AMAxl2g--%N~{L7G3ZIf%Q=*;+gTC6aEv}rZ1 +eK$!6;~=QqA|E&07CupIROQ=Cc`zMEKgNI4C%Ix-$L;|^G2m*6L>p|hM+mNg6 +c_I+8iWzO_oUx~Rwj_}`WyVKHiF`aLqn+b{S2TiTuvzr>#bpF+LQ>L8SR;jiwPO&I69ko56D`CP5FixuG@#d?C=&dUnZ3g&KXb?KO`+hlItICm +6wZ1#ClSakTW`M@akpfo_4ghqL3v@Ettr$EAxw|c+<;cdA(%6$!m~nD@k0_iNH*XmSl{aMF&F#%_Hoe ++C8@C5Z+c<9T+uT73Ujs*kO!C*ak)F^wmh!z4M~7fGbtz7n>npnnx-_%2^}d@dpzm!;Ya1tXt@mZkEJ +c+s@pesVL6&)YyvWfnw!Zw8epC4UZ6P| +Algz>nw05E+A9QzBeXnPx&hQ5l4i3E^G=xLDJ&!g-ScnL`J>%&xwK>)c}ssml%-6A8`U*%;d+^OBv{< +Fl2<0kL{t9(<_FDK>MGH&P&CjbRm=OCD{Dw)b9-vQ48jF*^D}s7}10GKcbT5luD`-R5GhxY>cP#zxQV +@tCvhjDRHC}3|xf3OgXi^EajSX<$9!?R1G*lNoR;&A9#==Sc^~73W4?~2xuY_&;*#%e3rQ{1qmq2sHD +})j0OgS0xT`|LljkM`}~$VM?0+ef9^&^Dues5YQ@Mj>g=Vqaz_)8Iw_LVKA +t$V+3-w}VbR=&J?5iRN9!eZKrJ`3F1Fs8VRpf^k6CN~_tH_tpWZivHS`KV172`5r)N9wDYeNN!guD90QR(r&{T4{at((r?#x3!_P +QU$Yn*Lt$SB*mYT?5KGd1L;T)9ep%8gQNAzViZ)?^)ZnE_Ps%4sPn-Wn|5tU7q;YnD~vq-9l&6xa$m9O0y8RrO{kBL) +#|fZJAzVT!js6rX!2CihSb??Ft)VrW-&@X!@@RTUMwUDeyvKnda5*uiAj9Q%DTY>54e+Erl~$uahmWR +fu^L%8Wu!mf&1Dx<**w#6~T1=kFh*iQ|iWA{@dC=8+z)WNqkBPg15JL)ofuxGKkda>7xE|%!U=Fnm(4 +q|DvKypQaw3IJdEagtVy6KXSFKfEAhRMO +EQH%k3vLmnHa%fLYpRG*&)byDyB1&;*eS%#8pKNop`tC72C)PoR&g!m_QxSEb_Fn}}6&FwwpCf5{F{i`j2bXUVqmxDO(f|TA!?jTU9sZ^^Ljf-~Ub-0lbeRy +5(V5&wcXnnPpTG1>^SYN#c1J5s})>l9}`S8b9RB(ZpLA-7{lot)OUe<2fYjhv;P(v#0u4YK}b7mCApJ +qlT3+cm2G96XW`vl9Yx;d2wXv4k_Qv%xIb1g#&iz>~upqtt|bW=O}!+L{aM-#?T(?L9ic5f*Nb1m3AY +`=m=Nwm~&sE98_OAhT)uN|TtWJg0NtXGI%NpFK=~_@4_eFM6K{(teql`0rH6%wv?w}G-Xa=CqZxcQQ@ +%nCsfuM*iD~!!uQ-3>&7HN~_VR0AgRDAedvcD +HV2glqnVFqNY@|xKXB5O$V@@NlU5(z}GCP=y+nL;IKE{(HK(%mca1x +V)>Mfe>T_y@HPvYHNElO5s?&|BM$(0DOhuQ}pm1JWWK_zaMp{x~w>3+u{_Ay1DvdOfmQ;uc&63J>o|; +i5=9EM;OMB>a{}wf)datRdkoT&T1IYBZ-B9RMZoji0;;DVq5#f9RCBb8HW!4CL(qZnWq~6&yAEZSm{o +;d0uCylc0M0`|IQksWrW@jL9*hH??4)U9b%Qby&o}kA0>z3)9AmV%JZ%K&1uwd47tn$WbP4VSTB22$D +aVy5BNv$6dH0YXQp+cNQ>l&6Cn`m)d;ldpc{>HBl>nQii&AJ&g1XSGYK|5(L*WOo9%X`7wEWKQ!h6WP +D+>RscmNqCqm2(Q1VWo1llN$-_~~V8eWV64;P$7`??DB&;fvUAB)?m(Gk4*ZA{xujQTJ@SvZXW+ZRP} +Mrg#9$VrpuHUnw(0Dh{g3L@fhM@NU{S3aylDrd9aDY`R(R6n~xGsjp#eMeh|@6AvIKqLx({AlrYAmQ^ +&aMy4x#o2iwG0WBeZoE+RrTaNRktuxke;Tr{KjZr6WB8Weuwo_jTPUg#XPG&0aWH!NH2mE~ie-j}7&h +^fbE+Bqft!+;VHnRA&5lW;|>4i9YpzBt1;)(NV+$TpR7Uf+$t#XqEctnXACeb}V-6y@KN(Rw4YTo7K$ +>}asU~hYEgaWI)s;QKnuZ~n;pS@CnJ*Ti`-VoSr}qq(?0xkgs;7}x6Y6Pw +by7WD+E;3IdgLBC6|^ZnQ+xt+&s5oEHKZt|;gZKsn^D{Ry8HRBvPh1{v= +6Lzoa9nNji#{p#P@fe0MtHG!d4v~>UySx* +u?=(3*I0l$n*g<_KsJdHb7FA1r~xP^)sJ*yK~0wLqq<<12ht9ASnrcYyh3pB2=o9NDntO{!!?l0GZw(96pQH8Xpz_hWe@yyj<^I35~_gQf|p_YAuJ}W>adk))8Qn?oVN+6<1b6SA}InrrG=LoJe+G)irnyoZG{+ZK?cc+b +TT7fh9e|1_hJSYAorxgkM(4AIv4t%52ir1%(a$4~>s&2pgHK!GyU)G#fJc{~S{*_KEKyt)-zcs>Pg(u +lq7)7#Rs-~fBq#D|l9xF^qj}@jUWooq9{UbeAVEO4uD$+Ew=h0mCW4SagR}IEK#M8~Oz&#!oyd!)R-0 +kSt%<4V~I+`zPUMhmW)6}Z>{)on}STv|ry9U0&OGSK?mkN`vRz(tCDoT@HD(Ijmyi^?0Jyg){kMdCA9 +jR7rPk5;KYeKC`dZ?hqd9UzLfel5y>eNVx5qj0_qdZhB(&no-Hem?6(@S{)z1tLV#wd5(^?6m?B^DlX=s2lcLQT5GUD;7}3_jRuoi3zTx-9x=rq?p2>hMm9CX$4 +NG{}ZPbrw0;FD{!GsIIS4=AeL}ifs4|x2ZrK5eBEgU=1DlMI5BXg(~2KlNDS|lPAjmM(M~Hq`Tf_OR# +3+FO-?IDFybqnR=6kr1E&?Vd79ITb-(|=JYs#X{vcRYj39 +gVAzshBC#Spm|By-x~gvS>SyQD^M23n+u4ya1v*^?i}b1q>IyQQW=4Q$^0xsBH!BWLAhyW<|4;c?$kw +@Hg-KPNoF@egJ=)MQ8Fma(L|uXfxFwilrFn1wuxcNKX7VDQ0hgp!OlT&lq$mj-Lhrh~E!GR3{uJAz+q>)_ +ww_H}am!UczvTdO>1UhOm3@OJ0Y#kz%$Fqs&Ct9=PcYtOJze^jxFp&?8e{sLXym7k#F0(f6Q$Ibqilf +7F0a-!!g^vD>$KKIE%j3^Rp>$6F47_{(ZdfK-Vw4?t=K2JA$^ylg5R~?7H)ahc6#Ve8@=RzA~bSaLPg +$LynH2ihT#ds)>U(j+_RjD80Rrv^C;y1dqk|Aps0wknUsZ2G7+`wW2SMp`6K1g{n{CXYPX8Sd +VXywtCwim1sCJO=!|zxxdzWf_!=6`!SzY`OS;G%rP3 +nE&x4RU6N8L6gNa(2l6K`a%U9M#RQgQ>vXMezknT#HNtl%k?4ouw9yNzKN54^f#BmPKe(NIjMOi%k{A +m#@#Pw%SRY7}jnGu$sjAMyA?wOEx5cf5rE0@Uv=-$R5l{r;AF2i@2hG6-pXO-O~U8Tl>`X@FfKhlwi5_kY`HuyHca}O={Z93e8MF<*iFQBbHtN+BV8ZrB`gDSgXi~`nO^fj@X7 +P{)TOnt-BK2CO4Li`hEkwi^osh#qtx?^(>KPRc)@f_7C8#hZ-pborjj2M@%!BX`WBL*IqAfw3&o_*_m +COXZg_tKBxF0<9yyASjflIsh^)iX2y5TGfv$feZp~phx|68ne=ikH{)WzWCid>IqWU6!B +P;i24BVNy^d(ZSIuD>3a>M3pJ}BMh?@-@r>y(}_WF`Vu!;|`co`^mhDI1PV>@6}RdU?}(vk;20TC~?{-*zt8F}-n;-?rUB&0>lSK*y^WpnYi#CtQoKU(}u;vL6VsVn|b;vGMy +rYtEACf@NHqO!2~>BKu;R#N5^|1k01hVN60_Zao}Aiif6zk=_n@Qzn+)b*?Uta`jg+Pq3h#SSjTm%0a +*)9bgjNe2-Ya_oSLtmaIT)#M@#A}?Q{F`8zyl@IbM2Glu)FDRD@ze}fMjZ}<05-9#1t%NGY?jdf-(1Z ++T88R3{75UM-l)<2RTB%fXD=qq^p +?t%wkmtbBc;lJm#eK&ywrr>X5!Ou_PJQIG!EEx)57Tjely55_=oA-v!Qal6T3i7g>{5&OKCOUAyr%3r +gD2D?s78jPR)9AoSKSTeIQ>{TH8`om$lVU4i!LkEvNXK>G!IB-87KLTbc&agkL~O=Jq>O2B#o&lWU1F +sJt)8{ON*O?ol$ +)wJ>x8Istt+7K250?47i7xp2SaWEoEh81E5ei}rY$HPd&(uzB^^K%80Bn-;cgLsv!sw&vnOVaTp94@pDDu!BbUw0SYOuMMAy +F-qLKQ{)=oFm+Di<`8gyNsS1$9eubJ-cbh>%jt!Tuu~Fu@gTVBGFHX*h7j*yRJT>>4~MQ%iibsSo>g5 +eUV*Xx(fBBt5E3i%Ec^x+rZ-~h4L+Kyzz`bg5b(|7ymG{jhpE+ND9axGAD`R +FL!l(6j?!+&MD62h)Lp3KC@gWS_g}9oN4PkqaX3vgK8-1tW+RU*coU7OF>aq7Y`i0bk_dv;tEpT42%I +%qB7BNh?+m_KIQQz(TC#88<3pI=oXRgHyS&1gj1p?}G*_u3Zk5qfQwRT9VjYnNaHEWYjK254U(e6@hN +_y0Fg5Hv-RfDe6Zql_#`yxZ&;}nM3kGm@YmreM;*n1a%sH*LMd=CsTDms{2_)1h%3ZJ0{j^-esK#(X3 +nh!)71VW%;2DK}K4wU1F>8)GZk!_=G{r>se|`2jXE-2`b-Um1@Av= +R18ep^`?=QMYwx}G)wBy +p@b?Py-`RgFKR(&PTJYJ~oO1M!+gJc;cdq!5{Zm#xpPQKuZ>X_v>HMqidC&BFm-3k00lG*-z*yIbM`J +So1byw6GoC&V(L>8fj#Fs2$4(#ciK_jZ)Ta+;Cz5PubBTp8Z-%J%nA+{iDB!N^-w~WI4+cs$wqh#y@c +16j`OeT>CcH4ug%5=6qODq>KGZE1#7R9|j;+?MH&25=oQ%~xC^;diY_1a&78MXJSbUhF`p?)7~kN +nj%p!s0UUxk{46N(gi(f$u9<*%R{cOX=+lRqwv=kAY7aK98kP0{%W!1YV@*rZZEo6QEV*PNZdmO=A}z;YZ*)>VP__=#uUzJkrJ9!L@7cMEHChE`P^ZY1=MN=MR@Uek;O~%chG_obnH%{NG0 +teN)`8k +x{sComJ3<)l1O>Q35dirHb<29DNtb=Mt!o%z=JY_6>T0UDXI)>gAqtLEpyc5X_%-aYRow&y03ON7R1{~2iUpiD+im-jg +~L}vl`n2gmBx~lSf%tVi#mmJJhopcHDgJEIFamO%ObuomSRb0N#UupZ@OraoTIEQi!l +oAkyA7Im?5>+Fc5|sjD +Eq##OW~UvY+1zLB;pQWv<~a7X;L;*&*#4b6qXhY)@hRet3seTu&+YW-nx;Z?TGVDBk<^R9>`pHNjI%C +YpthJXe=$zsuyEO0klwJ*u?L{~zr(+52aj{EaY=&GnkaUbx1;R(*0&4+r1;gUZt(yLmJ<*ebZk2F+>9 +>l3{M^a4HX;#6>bc+v<$GP+Q|E8GgsTB}**i#Wo=|)Ff36h^FjJ}TC!x5|AVS!_ +3M=epqXyTpPZ1Wpk8~|J+dgy_)@i@n0}r}(O4DnVn~hIswL`h6-`qt)AdM(tPY69xlz&*^DO*rj;R(5 +9-IoU|^wd+lrT*$bp~^#h3y##2Yrhs0%0u)*$pQp&0~Uok8Mo=6@V0Hb$-{1d#>DL&)!&h$aa-%)iWv +>=kD(~arFc4q~~tqw(yRLNycsWdxw0STo!4-o! +(ttg}$z6U(;o4E7uf*=NG0c*4ASj@`3cjv69f@BGfGlu#Lb816ANO0ed< +FrUr=CEbp{yyn;Q8dtVH;M2XAnx>&eG-I^L2Ry>11qE+muMi+sn^?S?%0tpp%J&_g?zjOHwBgp%5h1a +5**r48b<*B^6e&*(V=IAt~ZPG`NzjFHbYIc@qH|Jh^u0v>q2%*_J!g;7C%=)&_q{4;6$k-`lua->#k$ +LB{PwMev=cZpv>zV-z`I3MW9|T8wwSKyJc{z47ZJNEDG$;ZQdqZVE=*vWkU;GzMGwKfxfs_X3=IE8g7 +yAm5JPJkYbo(Nx_i24QMOWDi_Oyl&87G`;vuv;9&Q2P4IP1^bMO}uzLy8DibHXT{D2h&?NL^ql8@7C3MlOFSgAPUDJguFSL^cY1}aN7_pD +gs8u~9gohQ@EFR47(J#2k<-I&^)P-dRS3BiX%yv|Sqb?*RNWWAnUu4*Jk24k4EIOD@byOYT7^}awpsl +MN7LuJhhqp&{Tjg549vlUtl!ZTOrCn})`jc|^Xf*EVB +@0|t_n0L(OxRR^|rv}I!6T*%-$nA*w2cKj>?`3)EF+^Tm0I!#hwQpH9Tayqk?2@oo* +#ciAk(AjwK&>sec~tK&3+KosxT3Nw{F>W#)orU&Z0=nV`Q{y(O?L{L;b-#;y%pMl4J5UB;fi8INI7N; +%3j7d^Tl39*p!a;TjfCB#@E|XD6RHjXg=GEiZZ_LoghKar8vw`h)s}XkYXH{b&LCi#rrJ^2>Cc{1`SW +!CsGAZNXlx(^Yrp3bEV}Jni^repGicq287PkQSV!f7s||7k}i5Vv5Qf4FNUZ#kEq}EX_3Jym`U_-BBy +eEv`0y2=^Ex{EFFQpvbPj%Q}vq-pURsJ^{r3nYpaot6yUT5jTG@UE+;-p#I5vwFc)pG!Lp~)ouWZ#jl +!L-_2WYly301@_8_Vh+_L2x^6{l}t|RCn$*gfzUP>eP-L9Q5Jr7lnBl~o6!Dmkt=1|q4V~2h?bfnE5` +-MyAT{RS{BZZ*O^wM{phpg>P6mGu3YBW*Uf>!qglSkJ{fgMARkCLQG6=tDy%hKSv*FA{BR&|s+fPXl<+=bUT3;u%h=@u%pJQ7)m;*#w +FQllmv<*9;An2Ir7UVRxllXmm6#YdBfzdtOsw|i_5h(tp*A@Yly$;V6;Y5ZnHP#V=LPA`gby^NM1offC+1Dj&+2@^D95*S5rK{qWIbgSor0B}J^!3BQ +xT+j_%KrDczxT3`)m+%^VkUmsf_wC-hzV7d}UsrrbN{`vzD$|cn$YP1o(OqH!g7N_+;DBzT7~4btM52 +)2^Tap0wTV*e1!`fPaTnGrc}*17Q7Z~7P!v|9VyYG$Mz^MlDM?XLI>!?$`+BJhQYkP^dXsb$Twlk2isU1#&>}9?<-xiF)^oy&T^i{-VZ{+;>6oytf%TxUJ_YLs!nzLDw}kaMSpO-k8)1D +;ScPWhwZd8s>q=q8U?n+(6@9w2Kvs`W%UPcNLR%}a2LBa +~*O6o4GX9WGiDzrBH2y2a?KNr>uf_`DG74!?MP-S;aSg|@L9TZk9zDpknE0l;xZ{;c|Lzjm1vheVOb- +l25g!M6D^@r6ZtT=rs6$@(staf1ygf&Z8gJ4Y&*8Z?g64t@6#t7?BSnm?nP*_8RbtJ4o!fJ-KyRb&Wd +Xun5!|J1=9EZ2dIq*n;^+#cy1nalLItA7v!a5z+{lYp6*6lfBIytj}t}NSBm$%7F==}CckeRn@fO36xe@Y<;}ZqNtj|LCf4>*dJ}FR-%Rnz}7b5sbm&XX5eIOy#cSNer-lz2`TlWgdZ$!9< +y)!wsACc;5_aiCXv0dN#l>K8DNtKnWx|Sb0;V9QtUfM2Z!2ZiUh*nDGdm@k`9lx|4i}tqX(p^}zAdip +8btlsQfYN{0QQ=X&OjCZzkjF{xbrl+16|O^`J5ARL45nJ+(sh*llFR5Yt=-fw+dCvDIlebKzB8Jx%}l +X-Rb>oj?9whlOUH^T563s2j>`t?ZRelofq!AI;k3nD@N`t^t-g*Eo}RmTU}?u!X#=11PE$b6JuR| +V2f0)dU7_o3z9%Y2rP)h!#RTRzrGt&+n~r31lTI +`}Trrg4e#JVkJv;1hy<1Um@cA=pCTid=ch8Ts%jG9@x4GS85ChRhl=YskDn<^?iq$*d)_j?B6;J$Bfc +XN_mAS{fWg`x68bgu*4tFf(OOdF>{)hTrlSYHQiubzLtj`DqKGxEL +k1(kH%WiTk!kKicgeIVq*HgD-UO=+t%|a*t!D^b$m^G_x(!K#SI$fo(l^NzOW@YtYLy#$))vKZHPj4Fr<7X&- +LQQV1v=FA-#x+_%TqGu|-MRFUrmZx5_B@nJ(vsvcXYr;Y}$p(Sp +8k4MB2rZz=)+d8=_vple$*-QT1!bWy`gwzTltmkNwPWI2Lo>*YxSG&0jKr&slNF=5V{^e)vezHsq68xE9#UyDG-P|qfUgBI|@(ej3 +u9m3D^m{PnM^2>Uqw?qE3my>z}wX04F9KR}8k+M3K$QaYb+M>KZ@G8UKlM+$?gp-{l&g=8QkMYg`&+O +1SiQwKY?5I^&PK+D4MaQ@n=7A200eia$;T>PnvOjBIbNrj`cw+PhG?~SMX5M>yP^&{$EAgoOW!Y5HDH4Y%nfRNLuCD{IMMj1xK%|_@GYzNhQjb +VnnKl>&KsBFsFLei@RhQ0dPl9^YEP6(gURIxy-zhA$&Y?RC=))C@8PQStTa_xjR`oWz+Y7Kqu*Q>;YY +4_3D+vc)i$VtdnA+%eM8||kvX-EQ9=X%=T2|OPJI&pe1kwH${-(@laDrJ>9Yc)$s!%xqfsAU1TMX3_s +a60GPBarm*E5!l8%M`FudT6~`fC#&d%Uc|mj!cbquiXSTymFct% +N6BIlT}Ki>PcDWk7HCcl{N94WG~Fv(h7|yKpYdn4M&ijU8BS$2^(99dw#HzAhgpzj +&+%R&-s9Zw8R9)becv#JZ1|2NBSB6PZj;au@Z*Y?rhg3V +4#UG!Lg$1e!S-J#%7?DFY~Ny;fwg}0lEbJS4JyIo?NUVQ@U74l3&Wl! +qc4|)dw|Lc~pPm+KxMl{Ykt$CnM8NL8c!eQ{BsTF8C+oHec^_r*Z(<<~`o#dAGe9Cw9g1X(>0BE)?sJ +NXX9>@0*p7?u?ARHl+%es8J@;#Kmq(sApR2!L5$V9*cb)mp$zr*Pj`PPCyvWw(^T~%VKxu`c|Q +;lS#eB-QEU!Yhjel)ESk^Y4)~g9M7hT{<1ngxq7g;d@+e!vzU7EV&FODZjhN_UbuSEW=@h$KPxL2?(V +pDqF`^5^eNt@HHGymWl^KjLxnh+rS5~?S%?PfUHy%e+rYht?C#oC{h>lDj*^-77SvxB>6dSmMrcy64m +bWY-Y+THbuMg%!PW*;yw+D-JqFw*1D6jn65Dh%eUJ +-+M2Q3k~$xbMUhB=|Ds00lHYT9M%LT`cSdxG{9Xkbq7oHYHA|lf$TiPFm66I%nu(uaJ4lY@N?}TkdMB|E!_$2XN(2oxlnI=t~si{w +NXVitZ4HAA;L_|oPam6OA7?vV^E>f?;F`bb*n|`E+y{B{H3A>fx8JjLIobchGyVlB0uBO%U20419VxoTi!tqvuu)asH +>eb(imEGmbfWo4_Gr4tht_Ei{Ay$FlU*ZG}h*1P78}C-nt4--Wzf;STI6aX*B(s;{EdbF7FT#?tB`RR +{TqA>f!_*J6T)v*kyj^u}iM;4m3gj)Kw!#39dnT2g!9PT$--N2xKS1=>+PgoyHX=xK##=4#5OngV{uO +RUbF%9>&uB4UvwsaL?iL~Cw%%VQ$u3l$;8h_^L)K-5ER*Xmg4ws-q!V24`4LEKCll +2yIUA`%vzCWkOiId_+!h}|*Hlx3EGt8@!4*P@Q-Z4^AxTlBDO%d +$!|U5ymzk%WGzsXoB{R(u@N4&cCGq`X{SLE6p<#8$#7XR9euPn`yQua4Zh(A~r|@rS2krH^lE#AZyIn +O$fLIBEh0jOq1`sohel(iyKn>GM0(?UxDV+l`6YD+I(bhQ3J?!CD;q@U{b98#y(x?*1kCnAUU-f4IuI +LDNnXHs{y3Ku71jr)?R4(_siG_n$qlUnmR}x4QN^4kd~XIZ*A_J9wck^(PnAz7Bqa6C{6Lx(BMrp3?a +$FD1TWr_?$Qj+D55*B1Z49oMK`c*jaMguuFZ)X^^sQEq$XS*;K+@emA1p(Dx0I|L61s)zbyB|8TIm1MYNT{F)JWM- +Yf2dsk+r56v|3Z(46P|=aqiP+szytt@sO-HMb=X(bXck-x?5AmHSRL5`o3OIWxd{%=x9Z6${wLN1;PJ +dEtNE_asFm4m5%3yBDSiK=G9ap-HsYHm4@ckRQ{_OHI<(>R8zULc{P7k0mGO +$2$|yxmtvZBhuFNI{>w4%z~7aJ+6{7Yj+mGKm!>T +Qjp%56e(#^hg7RJl=AR0&ShD5^~SpHWnKl>4|}(NY=u +g-~Nu68q~~D%AqIaV?c!LKn(4*SK0&a$Qxc##$;Bg^f)ptAw_=KTJ*K;zu=VD%V|YsHPG*-ME@c(N4M +0F?+q5$~9MitC~uYTuW*y)d0V)rgDd%M&MJ1fm=~gX;V~GPE%E>yr)r7iEz!Rs2nC#R3bq0Dk|G+R8- +#b`&Cpnr8}j09hFMy{YE8~r8lgka^evp1d3B+#&9c6K~em=lFB*tN-C#Zr=-#xBlMvtS}K3~{aPx~4* +!E%D%V_XK}+SCXPePd3CXW1EtOqocjHj$IxNo^>rd%-k#;EIUUY|(;>cQkNPO^8Hd0J1Ts6wfktPChL-dZ5H2Y1EZuMw1`|Z}?nyk;|) +5XrHL>T?(`XDXxIaJqQhHTi_}I{8;3jeF~FALL#qXh6t76-evKa9Stc;0(tnB5fTpzJ^`l +;+mrB_Vl7J3-3r?e~(>X}nlzv-BiOl2@tE`$6D@~2ic;z}WzTqDuy7}<{7hbcF#V(w_a)gdt +wAxV0)cSy{-P;E%e`nlROG3z4lG!zRsKF)65A?4GmZHSPgB-E(lkp84Z +h{*BK=hf94CGv +&^W&d*|yeBDH%uXPn|=4Jy+N#;*B{Q?~9RZFxIFX*Ok%p@^3uzDhqO%}&E-UB?Iw%4F8V+Kc>}{F)7M +l^YBFGYlPxieq!A5aLgX0|v*l+DLaitX%jfX7yOYZzG+6o3pSES6OqjvCM?=YUsO277OqACevv@;nE8 +iFR@i!V{|+Qfn=;+XP@R+Y+&|K!tik<6ml$rN_?i89?t)7C +XnCd9GRT3Y2_--c!bF|~ZGM*LJys0oxczM=1?WY{LQgGq8s{T~i;)dO<<+2)~6V +&ih%X~!)r%t(T*VTT>c0tqj{9AUK^~_=5JI!9Lq1?k!Zl*}pUsinYz}9N2Ooz;gIr)uEUOR;HL|Zwf|C4jX0E&{%(8iy;YM@l&yKEP-?9kaMVrJ8dLwlO0uia&vf +42-gKp)t^6s9htr?h^q=GxPb$|X98=7K4pPl5b;I}3Sz^OCiQD~gj{Bx4^?Md$DUKJXyP`V2iQDYbO% +(;hP3LV!O1@2UVv_XdwPLYSdVZGrm5KBO(o#M)(Wuj+Sba@b<_Aa*AlF{v{u{C(T&eO=intVenYZ!+}Mvzi9P2Fp5ba&$;Vo>VX;9{S}ZE<0bG-IWFh{F}tO}Ws~Uy2cKr59~ +oaPkRGNt$aNM-r~pH@iLx-fj2F@|aTHCZxP{k8y2zStQfdpPLohrm6#z#g$swo7Hd3{aoJ0ZTbb!m31 +pN-8&1NTjcSXYiF<*;#AanTsneu>ffttWYHtpRU1<7I#9}OabZ5TR*nk2*%8LUQoNxPO{z=S%DPQlj_q}^C2z@ZkG{!X;De>wU>a|aHQsho&W64&HjFzuvraLshDYz~|$?+2sJB +)a^Qse>|oWwT_BN|f@$<(+ENHQQv<*R}~E<>_JbJ%qxD+V!Bo!Bnk% +`I(S=+D1@p5!rX>m$X|GXUo|OsGh*Ec%+)Ezt>!tW5ODhumc``+3o8j5a#%K +_8?~Ry3gvE>+Pe8KzcDRWR8oSvz4~=yB=mvT+`!UB(rql;S8kjR$aHeU@HH<4lSCnfy*;Nh)%kTvz7% +hO@o%em#n3h@9pZli8qT+(R^L;7nV=%STaf1oy0KQo^?}$kJEZ +b`6+tiJwwXPO@5`O7vHI6QP;*Be)Z;`n1ow-b@+0};1qYNNnL4f;f#`m76a$xGgx$=it9wtg!y?WuEABE9LnRqv{-_HgY?5L{%Ce7{l;0N94FMeYl{iw;wBKj6YgRka9$s}pe@Z5@%Wq +A&{|DjgHI3rf51r6fYV>}{l%?p1YmI6mUk=&Ro~T`9VM5oMcyUt)dlsVCCayHt%Okzgir{Rb`sqok)$ +((<{q=(WzMn%>);lP^I3|7}v7J9%l+z4fPMoZf_MZ~FzuHA)RS^Am+86A6i~{YjhE7vi=$!9Vg<XK^V6Y`c%p+cTpQh0%wVGZOU7OmpT@Tsapy)! +n5Yn}Q*H3I%98-9+M)L44S+xzaabHP}(T(zmlzH${7;ulP>5YLrg7)c5Nt?n`}7Zq(Us!Tqeb(if+Y8 +(ry(n_HnkDa*rg;_S3GmF~M~`%Bi*+u9Yx+0ur)Jexqs`=uE-!|@ +ItM+kK3Z)?OO^6=S(+@zhOm^)QLZz`3A4|FpIZhT;{NQ7=RC|{_DNB~Q-URoMWSN^pl4b5pNR~bX;~{ +J&L)c6qr`&WBHgj`G*vu^;VKaAe9(l}#0GbN{bUp;oqP#NksaVHx1DBWX=A@YB~&40IO^DFWxEA +T5T@{6^fb(}QX0%_7%tKeY;H1f(ZF7K5%K>t^X`=nlQ;wT=d54_o^zT}CIy%vqk&3zI~tlI@O#s#ZI3Ip^}=$x^2{RSd&z^1iYr5|7E^N=#MHh4@}2(G~C4W{)i%eNC +b7w(!mOz8-*c^nF{OTJB@(*Sg%++p$yU*rglsz14ffxAQ$bz8kTNY?Wl&IpT!94enq(6tZW;30o^{3U +RB)r7v6+(;#YzBU_&IpFu>MGy1niu|VI-1{6T0S`0Q{Z1?&oiazU4`3qGOae;nqc0*f(y%nx*ymWHAA +4|M+Qu)j}EwH1fY&+?Bcp7=}K5HnA|33dOJUd8)_?X}n!A}IfH|yA~1a}Zb6QmIo6a1NABf)zFM+s^O +^tb3(M}k0tkp%Y;{M1#)P7oXUt(oNnn6 +HbYeTw>BoemT0l<@>>j6~~Y#%iePM-K{TbLcOh+$Xb1Y%CkVLN)dwY&eT!Q6inm>>d`wBG`BqDO{rIZ +v>l1epRROQm@AE;7CtNE&Vs`((DR7%fIerZY!gaNko?~@2ygvi&pB~vlXM805-G}wIBVDA4w2%(cp +m+&Ut ++!IADE`;5|1`+(cr?H1;XXnmQ+q}-hQ}5;NV`$a7jc?m_M!)tqb?Dg +X=3D$bcj?-#`>j0!diLra*r#v5pxbT_?muAQput1#2pKwT_?@9)BTOSl-8I@AJ|-eEYV5e^nAp4H;_n +%sFkxciq{&IiQ>NZKZTgIvDYH^%&q=eS&&|lpx-WZPPHx`))_j}2VE%%I5BzCS;o>3|k!y`wkZDV>=F +YL8_U93=%^}{f5|>zrvNUP|s2{7~6C3f=JaP{Zt<@^R1qeFYvepV;$5DGqq(5yK**B4TrF+=8CVbE)# +}iM6ix5ruPo(~BFqL+Qa2Y^h!U-F>??4|CAbOSn(Q{ZtpN8Dx$OdZCgj>2ubDjvBL6qc^Emic0au0-l +27RDiw4EI40|MwbRZv|ZQm9W0{+YrjmEvWx2b6GVRjoytuvEirV4E3JOd~!Mw4p4~r=qV2U_s +R9&!IOfyG@iHIAD$&CvADAHd#_R0!MI;-0xcjF4hJyBLz)WC!?+8j~l2R{Lw~t)HxuVq2mi`lb(U^|GZaP$))N*Tzg +0x2Q?;x2%jc*qMAOe@A}c(zM^1-!ON6d7`F}ZUKe23JCyd +G}Us{wpgE{n%>agfq#lf)xs7C%m*nPCBLEO)LOhA{~;`_#rn>r+$;*la(m8I{Lu>*2ySdT-G+PwZ1RN +EWmEbVm1mmj>$U0J`77znQ+l~f(c-Kk)iie>aNn<{|9kkQVNPkHyi=9b)pYCIA<8sY@I#R3M>E8j6Cf +mBO9O7WzC8}s)X$CMhY&IkqR!@tcUuGe25ai)#?ytg3)x&vG5?bn=nON@Jbk!_EWRQt9Ek82b!@bVTZh||R9pFwTG;goFVrHGmOdzuhn +J0cRv#w<3k%{k10?51-?w8H12bun4Lb};hYsQr=to)*x_>a;d6S89onGYZzGNGqv7Ma*mHIo@H+J;q( +(-?n2TaxJe)m|Zj1=C*um0Ux~DvoTJ!PL)RS}Wx+Q2a4AQp`VcsZ0@So-+2zv60q&qBQ5z?_EmWG|VB +&-6DF5v=bVnHbT%GBxX$D%G*R5K{@v9Y_fXeUq@xiJiVE>ho3$wD#{Dbu)&lL0tSR8Oz|o*1^f0y{Zvj093c-As!9a$A{(KDVzYm^(>;8h=f7kn6{})X))6!o_6MqduEYbYwZtzbVf2 +ror;|BlKxB>s$#s7c6bA6p={7-X$uKw>vp9UPDtN-)3!M}PA_;>$)jeh)fq(AMO*6)T%R_Jc{exvl() +7{ubT>Qg|mn?OZls@>-GUsyFin5gtuUh@*M;`slV~?*{`^1w^J^j~b)~$bb!*kF7?S+jm{{5ww|MAK{ +H@*7W<}F)a-}c6vZ@vA_yYIbUUa`G$#|Jxi?cVd@-hKN&I&kpN$A>@p^s~>8d~x*H@h`vn`ozg^zCCs +NyYHo{AF9v%>&LS-=YBeW;pdB&YA^qCrS9rA{%sH7;4`C|A_L3?)ElHkJz +2;dd2>j#*PcfRJmcL#{RIzzDi?Xt+D@EV}C?r$E9SdeXGU}#bvGJ^u#fZaTayWEIBhLEq8vRE!CEpn* +;aB`4;Q=*;$r3wwRpsT;FnH?6p)$|keWw<(kMJujj8 +YkYQk78sc8Y}*4%j%T102Pdt%QboT!PW@|;2@VV{>N{UhZlNxZiHEF3ilC8;~v#y3Rk}VzX(yS- +vfe$V#*4WoOQjL#RH&FV}8s+LQdKP@0Jdnwyx7&rxQbHsL1E0p^IcqkJZ>e5y08epCb5^xjXYjcl>&Q +(wk&EjB9ROk2R5+%!u-ZhAnTy8b&*%(3M?mO&y-E19!J%7c`dC7!4SdBosyYI3bmTJz?ml8tHxB}B#2 +Zy?oUKiKj!EVEO|F*`HILUo^=-((u|Gcs+K{JhjTmVnfBo5dQ?Z;(CTI%sxg&LB%pK|eKOBYvpo734$ +#Gp)Hf^DH?wcSa5JvRLP3=A?>Br0Stq1Mauy+AIN7$BmL|3h9y8@q3C`JF?PT3!8V7G|9{9&d;=EfDQ +6(saB2ijb*2)GeN5orirps>5|)-HmwF>(=4LOwf+J2e4sUX(%1o^*QeXBcfWvC1$%HxgOr=VqhHT{L| +b-t?tCPQ*p1P-K2G!H&;*}Z=h{)Z`2j(e0_rI;(*rE?@@xxlYdN3U#8L{=q+7dif7_NdAbLsdq)EfyW^X|Nj2JN;<4FGWB&yVf(`l^1 +fRaBwKXYDQwk1XFN2jM{=G&&*Lhcxtmo{6Q$Eq;KHc=;MUf(nhI7kok6U9{D*K{?v=>O+#E&EUD+p_< +Xel7d|pvK-f+~-O4e&1^B<&DBEnAS4A5wlySXa5}8AJ5u3=4R +LKh^Zv88m|5DEhW?GqG5+<2{*CGXa9hjxZ>? +naLYrk$ZgOUtB_bo$%KoCz=nfaoSQ^q!w53H;iD72o;i1gPqA)#SYye73LzW!Q$|M%4i0Kw3>O>Ziot +v+|-5oXYo~Sq)gVVCJ8FMj||3nfe3Iqv>B!nchC|#n>O8+rL%?J^Ks&2NZHkKG2mmHoDBcdPhh_{ +2uARdDKr};IO~}mz?d(IG1k$|w>nGZ>C+Co$na1`DTI4#XS*4&g0d%pmlpBSnLcGtC7Lzl_YMCdvwOZ +>=39IfIh#MY}z}THA!|_q^u(u{Uk_h88!L7u>>;wHI_gS7@u)ELr?8ck>^v_Pbi3;_v=WUsDs8;e(dK +w=5K+_T@WneBFLsP;i4`sa}JT1*CYNU@>GL@WQFqVrnOfvp{lrU-M;*&i(Z-wbU>&Y+=hQ;md#Me0 +Qm(sIvlrynpJ1^HHqDtMlSx@Wf>b7D&bd#ayW;wOjEZlepnqY$%`$INCgqbaN``FgVWpRVLoyrf8JC+ +%ZDY1I)w+;y{8;eRM2lR>Psr^@?j49DUe>%5AyhKSy0O{z{0t;E#-5%|(mP`hp|mu8j-L2P!7SH0)`F +927PVcZF>0Q9saA`-+>;SoQ+`RQ)=W%el3AkclQuqGu9aAi`V}c)O%j0Ix~Ywkq$qQXf^Bls!}fol{~ +tf)W5eC1PN(tkyFM)T+I81EH}LoP19AKB^Z)+`{B+NE=C@z%{C=eRj_-j~Uh-5-tnK5h2Cj(2CxXy@g +>`oyCa=326tk6LwpRSwX#8$g;=(j4>G~;dI3IG80&wrCxDQj_!0{i#ac*^2u- +#>6f*&XMCxiB-YXTM1vR;JidJmn!}a#lJ*JXEgy%Ui?*gU$5ZvoRZ%Qiu*PNzmF9E&lP`vAGs{lGW*X +ye$76ze?YU(e;l>{k*E31OxeHr&9?tty#L7O|8*Wvp8wlfA&}$)553_z>X2tGUAVNT;{3O-Wpn-Wvbp +4$6XNb}^WtJ*Zlv%P8;))WXeQYu*JRVIm|Jf!e7Iu1PT>zcd+OOP?DVtJv(uVSfAM-@n?M7|grhT$2C +$T)Sx4`U&yW7u%pwWQ1S1JT35F63CI})3An+&fC13=#7tHJofkbeM-~_>Of ++GZ<5F8}fN3fe<2SGW(I|N$@HWI8OSfhkrMX-#Zn7~GmO)!gK5K&V|L^nfKfiX)%%jocsXS_YGfA$43BZqkC%y~tdZe>9{g9L98v^wisK +8+2^#IeaLy__frSO5K<^gR-r=+drqsH?|bzj2$-0kK4hZOYxxovd4o*EJ4hz{B{DHjqd>)|9Dgz~7aOc=oQ_VMD2Z;u9qG?%!X3{74TEUimo?+QrWyB*&A7kO +jQj3p+>bQlE;ZqfvALGq4YPGjqQX0+99pYM(=psDtiJoF>)1v^{d;+ij%9e&zX#dKzfb-9!L{`6SO31 +??-X8N|9;{Xo%jw~eUJX9j@_%t4~hW;82Srfsh*^3`WZNIAR9Ms97|44W|^6p%x<^K6;)hJ|HW+0nl< +c&7hYg*zWF9AFE3}GfBreUcI_Hdnr^WOOfR_PnI@tS?<@9Y#$EI09}uqx_}RK*-@5tp7cD}7vwUCO0l +x1NdGDfk1HGK3xb!}MKHjAR`}XY(Yh){6#f}GZdNcRNm(wFw`P`dU~DwUWD-b4y30a* +rmMJ$^N+MrVj6vejWMap)f^q`tp1BB@uq#z5_dDhf5;;uaUp}PWf~0I)F`CxB_%E$n +zOQcIMWliEYZIr(P=LB@N}t}n-r3Xfwx{+TAQIA1K87RzSkmFFEJxrvY{S*kJKD$XJ3t{;ptuZmFFr9 +OR1P5CYxf^`uGh=^L|6d1p9vqyYbm-|10eiA+AwKfZQX(5auNOj=T|4cUt1@<5uVDo>Zw{TyhZsV2}$ +-wc%f(Hi0m!W7r4pZ%&<&(WN%j2qrB*ZXYpuafgT$0B+wNzxuaAj#CB1OZ>B*BPzn>mA^d&M=?u)&D +Jm*_e_+n?j_oq!xVMTny`&%a?y*F1P{UXl)b?MS3Cb;-dj4kfbgZugP;LKA2)&i(ud=xOs<%BXai*jm +trKnGzJ}fvmm<=90n2j1WiiL-VvzV9|QFaq1Okhb#No?xWscgoK8EkRdST=X=T$YuU#pcbM$E;SX=sy +Yz3)y=6X!ghYlTLpM3HOJ9_je +`|`^#+1b-a*x{czJ9+Y?;ESrNDt6)Q5A55^oYmCSuuGRN30}eDF^I;81jf9HX$-3vYfXI9h8lZ2YQO> +fCN_!>Vbk~owvgY?*6`JAD}R-J!VjtaDeFk_dr|zs6n_-OkE8fgDgIoFzks)A%PIa-6#o^9UrzBqrue +5c@%vJIGsVA`;@c?xN{as?#otcx4^jLh6#pxV|1HI@qWEVi{zXmvL46rZps7zDO}|&!7~8ax=HxHZm{ +v)R@Z?DTe41Asp#Gi6 +AkMF0F^sprm+>Kaj8Axw@%x`*eD!OLzq*_8Ltks+2T=Us6hDsQ&!+ebDE>-{{~X2NO!42N_`4`R<+=J +e#Xn8)YuxesQwn!e3YnC`LzKevl)?^5;UuL{)1I@RhH!Ra0%sTR=j_sI&Mv>o*_A`w9ltBZA3*U(Q~Z +e(e-6bjp!mxu{#uIvH;TW7;%}$;2Ppn=ieIgXZ%|606UFaJ@q1GIz7)Sd#lMT8eZ$AZ#>7NM#zu@D9Uhr7AgEuzK7r%L^;T1ej3ICOk +0DEBWJ>UD2rzD3fZ`t;9}yKhnv#!-2p>HrJf%MZ^y$;9r{W(IZw`+QkBE$*_`qvm|KQ-eQtFY6jgM8I +v0eRdNf|g$P9d;Y&w$?J5TDYIjg1){o6@~QXMggi06|KCz9|%cbT}eM#HMuZ(4n*JFK`U(6+lSFxYJM +R)UiW{yGMxt!6HD<0K|`ogl{W85qwzjE#zqj30YzmrhCm_}>;ZAdboDM~E!cQcD?$0806Z^o +N+@sfgpFV&er7A_ukn3ICMHA!eWuOKEf&JGMiI4iv!cpEBG##XKmAGPsBQZyh^UpwP|jA7S(|hPE3V6 +&o2(r98HWAfo$Ng*`@(2oJx*)95vLbV__md`y&rWxUE=F%d(@L=4jFTHm91psZpd-77UQDPe>BT6ybq +?V|}ppco%Jni#3xKm5*NgF{+*8g7aq&xkR^%R`4oMAZAIq>Kq4Z))e`HJq#wDM&pgvOfQm2m~2#YU|- ++hJVCpV%Jz!?~n2!1~a#5-)?+LMEqzI@fu^=^yMeouYFj2IF)0J#vAEJ3U6}1GbF+k)6iSyA4)%x7%? +Vl*oa2nn)In?V!}r>@@BCOpZG@JoR=5X8jDAtAVH%1kCEq!nt&!{xnefDkv!M3oTAEe6DLj-^2lP#IJ +fNa!h*Zlvdk#9#u~x4ERABDo>fjmbC&dtEHf-1^RdSsV^2Q$BzyYlr`h`T>)CV9Jtt&^& +6_t1dGM{b-V(CHr{yoOCrRGeM6$w;9Xr@ZAAKZbgHzvp&A$2O8+Pi{DOO!w&CZ-T!+tt%fF|NJw% +di5$>eTlPI>HW|ZO+PybAFSL-10A)?RdZ;dTSx=l!!*$SjkV`n*bx36o4|Lo`}twEnjdGc^3yba*J$F +?fS5ot`aBwJSM{g(!zsR*;>S|_X%zo{ivJMBf12XIM)CJh{Q7b3zjDfd<&^(h<&-W;AJC;s7qNBKS*( +I~@$WNe&>*PD?h??Yi+`7%xA*XC*X~y0v(CMO`t<47XHa`T<85u}y-UyD{rXYZ_I^DE1rG{l-Fn>~G^ +l&)LG39(yS8m_?bfqT(4aQ0gKkwE0s?|=A7n8222q^bZfe)Ii~lXR4{BpDv~K0)<$Iga*hTN%<@WAv4 +6WfX$gf8yADvIHL3p~i@G_7w`vtk?W<>>uAbe}LrXK&z9k92rzz)Q*Xf8zN +)TNS_;M<_b==+Og(3czQOeOt9^MLmm9rc5OzREJPcA&z0FCH_Y}o=UpDHy9q}is2FLAMngG&m_~h^Y+ +=ZXTSUL#~;6^vFDqA{p(++$^81lg$rkCUV7^I@#C-V+O^B2)9HdojvP5ip~+uK2S-v+4!@=RP}dlkZ| +d8(Z*MYDR_$&uuK%=Y(?$=chwNQH|NQfZX&%krfB${{{rBH<8c+FWpM8b}D*pA?Uvrv&bNCYtoW|KNB +}u9xT2Frb@yCCysHi9!GiFQ(lj{jbaBgAp5gFiudTp%$?COyq9KGp|u=D243#qNG{e__$ym9nJZ@$S1KmNuWZwS0kpFYjM`|dk_@Zdpy=+Ge%4sl3Efcq(m^USNSzFN3 +#+qOay*`gc{$N0RwyvUU+S0<2L4cu@%DEO9JZV7QZo%h~x#~mm;|H#P5Af)?S@jrk5JSSOZb?@H2hfx +`>{NaZmczJobyPOXkIKaR7;tPQ%)sMg(_7f*g@MFi0iFeSX0?9nWB%Zr)?%cUr;=3!9ru54%zwn=a`s +w_qpMLu0x8HvIHRW{~;fT&hsJ#s%99AOkZ^fVFDdgn_;I9H`PyuzK0`OPqQ0*!-rbPw9*$q_ZZqT{|e +h>bYm6a=~9(xf_lz@iq+qVn+!5>GD9u;+P^5jYJ4jj=Ys0|DA@ZrM(&^AyXFi~%4JBYJ)?_PmF>X6z$ +M?GA-c8zNvdcQ*Mb?~P)e8+FW|Ki1qoQ5I6|DmCweZj{7;J<(We*X5`Z*!`1PBOD-!zh2$$DTcV1OWH +lyLXGWa^%PnVFwN91Hf-+C#WOf_WF;UTaI!*`XkPV?%{mU2e?O_^MU1@PyCSczn|v(=ZhB)Q(NiwTkw +DHz4ulJ1_llz8Ga@8m#7ax1Mo$eqg+7`>H@gKj=KNqtFHvWjxtAi17Gj~XaU|Rf7J1_XE=}ijC1qHoS +P1Ce&;^ULv|AlJ2=0cXz2Sc=e^$IeC2V@>+0&B`YrfVU+)IsuYy_^D*T>yf_4CYN&brSaYs0h_>{^YG +*BH3-^=+OL_^N!oL{WtJpB;ogWq#+P23Rvmo8lbFRus*3F$?=Py!mL9?M{W~0ia(7RTkgPdDwoLhQZVp`fukv=>6Z0KlR0{`}FBEjOt+}+8O +$mrsx2#H^*NnfB0iuKs@v_+9&B0wNcRUCFe0jLln`l>0kUto^u3GG>8A?%a=KgGYtSVsG!o(RGlv2)Z^}J4 +u7iW6+?#(?M3~03GfG>G^PW5558_phe`{09enilzAgNP`6KxT`zZd`{JR7VcxF^_K2@P1;cL$02&d>T +H0>EQDD8P5`XuU8`%s_MlW6GihTON(v!p5fd-dw&=6@RFG0tCCA85ZA57fFqd%(L&1MsJQkC#85#$Q@ +ElK*Y~DE=(b@HEkYCzblo=|sa6!ei1mG7V~b1`TR^MxQif7nOS@=eL0dD*u2tsr~O3$$Zol{?y+K{-? +IK67mq*oJxmE3uwT2&>U|8fAo`EmxS|I{$%2RCmNn78rIun{PWLpKC7B&kT_31Ew^X%NwLT3+cRiT+c +RiT+cWy4ptnUbmm1@bc82kVSl{oZRDfAkkB|Bo0kqA%(~1+-V +-4cvgcN(=ga^!J$8px?w;g|Q9KdrL$4n?%Fb#UuEpMJE0-(a^X(|AqRb)$_vniu=a!WtkD&F*kx2rA2 +a={a*gn=LZE1S{Y-YB4ad%KlS&5{|TR!z#DkM1Wv#k?E!eJb%H)#D<5P01&!eU%4K))cZr5Kh=whNL< +6-2v}e%ptU|+Dg@#q2A!`hOD1&H7kK~Kfq67_i&?k-1$QZ*uXqx}gXJ8D290Az4aU*}}rI$G5A{DfC( +o#FQdi5$l_}5fk;kc8(vxI1%wy>FK_@~mI(I>69-^HJ{-o@ACjpmQ$nEAum;oLr +jG2dyvzQ6(aEnBwCO+#ZkTB7CZ)jEE%;tBr2iU| +I`Bb2|bwCC3}?fE%{h9?yo{!Dzd^1g8H%#>-sBV-I3pW_Ig{$7l~d)f6*XVf{3zpE!qm=K$jlk*{PMS +DaWSiE?#ph3%Dzg8zO(WV;Dv6^~5-|>oD#z1>6Q`&Q*r$zY(_@nNlqoYv|{KXev6tWj&8K=|93knL{G +_*uVOLc+=ebSc2H_)Dmh9>%%=AOqqUw$e2 +9mqmW(E_|NHlVM9EChKJ^8q|)&jYGh&*<WsCw@#z1>UpY+6nc%c8Q^RJO3M>^x<A#N#476Z+_ctw{eoK1unn?Z9@y7L50S&0Kijf+UQ&NKRuhjQ);I1S(Y&{KEG5L!M8m! +mmk~vI=}ey&xfhLZ+QJ-&6+j5q@?8h>eZ|H(xpp(zW3gHd3JWTD0iAy3SI&YXcwRpxPgwwv|tSa<1qL +hV>H?d)*~>Fgd1ce)E#JP;vw=ce}yvot?NICOl@~vUS8hUfBMs(t^v>Fk*03jHzaPF>$1oUq+?qx%;Ef1-QE@ZrOIQ5lw? +J-_?zyMhLk57rH-eFz#-Q&V|dT%6#o=bn2`;E%FKnZpje(GJl6z#XzF_y@8!+5y@C+62-?A56GkqjXC +A{r$~U@6Ybou>&^`V$a9tM)60!TKPvUR{-XVz=!IVlWc)g#iBh>eRJYB!2=khP{wK;bv+UND9RN5|7) ++k#$S2m6)^_>^Pm3|@le)OP7?KPGiZ!lM7VD!w}+U#?{TC1H$r}c{Dc0V+T%*RqrJfdE~sy`S>jV6OU +#=$PxP(S*2VaSz6SiQ@*&y*+9m1)e1bYapRARKU`M*BBh&}RbD|et7^2_wxa)Z+t$l!-3W$S#27M78@DJuh0N@U~o_XdOtTC?h@bLJpxV!OJ<$o&g640Rbi%rpi@{pUxkD{BgnG+HzL +`;lNAab-ZH?0PgDk2+6fi{~Ndq{I&C68hciPFEIvz4t2dB{RjFwj7M5JP`0nX{yKl+i6;aFN3J;O@p>J1%Nv7_Ds6Qk?+Sty?GJgD!*zZNL}2g}xKCsJN4S`nTW7@7LiET& +R5s{-^P~M4f-NM2ouSsqWvQZs3M`Q1|3eXXuNtmw-Je&;r~kkKfbJw1~fU{YRZkwNxjN?;-!-9s6Nb67>_V^qKq-;MA@Sr)b(A|1K!c+ +qpw9jMso_WuR%2b(YOoz!MiQ-d2{@(?Tg^Au3Mm-K_hsb_~;tR2olv5#?>x=Fz$Guj^yV5Fdz?{d-y| +C_wZsGT;88QzmECvYoq7SKf&1O8rdY0A7ttprLW1iSTW202>go_lgp+~F+CO2M=_0x*+V +f$D`t^ma@o|uZ1~+y0Q(&e&iD8z;Kf>y6|vq&u!rDcqxmAJ+Qe4dHIvh +p078^_Kdb9Z7IK1JGJ)^c9(4d>CH$$M;A3&x(p9hIC<|A_O?eiFE}gac^p=H=yeC(Z5dr#4-S{)}YKp +Q*pCqJHTN^-D)dK7W>E+boiS@eRWef?gzVoSl10-Y3IaDE9eG%3AIx=Q%%cj`PtwME*avkbigT!#7Z# +wc7o3^{k1yey+&;*9gv2pL>}4&9{&+_Dr$ghUrAt#Eee}^J-~ib +P^9PKN=+`k8p|6H43poXTu;ZB~)$bQ#j|cmmSkuP-4%Xzc$M$RaV-7%b&>WgCWU2Fe%yW@1-16nf+I<+W3E)z0B8S;^OPUtebdBmd=8til=KDE?g@sw#{ccsBg$ddEmBkTaEfMRvSVz?6kG)syA162BN3*g= +gS|fNF@Yb0-J4}8hFhB$y9lN#~kXl2hdMB&Fq`*b3InkS{ +MUAs0(iwE)spToq1{Wh$rVt)Z^!mmG`_p5t`Km5YEm2eq#NZxBho4|gjHh;(!3l}cTDJm+;((b(i2dt +Z*Ebw4`9eYjxQ1<(<&xG|*@FVt^u(#n*_LmmTk>RnWnF0sFj|c1bz8mM?crUo|9x&>^;-S%EzYBXsSk +uJXxXO=M7svhz_9nz0my&;B+E|g-I1N9J+SjoDA(J_s&eW%#dMXKR5%Yb>=b#(7pbTJsydhKU8)Cl-> +#8p)YunE#`wM6j*dM`OVX-CBT?TlxeKhuNwfv8^^}quUgrS1=4UVynB$^?B6%Ho3b1^@7o1hG$m +_32H@`ucO4Hi3O6m$FY*q~L%@@FV3ro#6PThV5USpP?Q8f9+j+Tvb)}zbKPrnlx(ZWPv26<7@AIo_pV +O_7Oz|hcWUMmYN7CH$jw#qmB~gBOm0eEXxOoXpW&Frlix5npxVEmQL2B6D3VnKA5zzG5g%#S_coIlUa +R!^Pl_kIl$$fv)4Xruk~H8efB}0zhRxFVeJn2bgi1vq1`XIH5RPZ&4e-JU#c}WX#a(ONPqtP`N^wRts +088A@s*_99~@QtsBH~ATJP)@H=c+zuh$Td!6_pd2AqSKZyV6V^5nlZTw@8J(h`lSh#SZi+hN>X!{WVQ +5KLND2qtDVa@6ECx?TlK6wvx;tF}ph^eoY{TO|;DVXQLoG$Vh<15g``ds>zwJ>jPa4ue3oEWUFIUGE5 +{JrnxG533UT%JD+$^W4JhyELR^oI@|`my6UZr&AZ(}6%BIB(uOH@?F$d060KCC(4>=In{T2j5;jF1US +0jJp=VlgE-NeS;NCCj`H&IT{RJ#s85~Dm{DloPzg}pP!#Kdi3bm2_eC8z;r70e +|2>(gy$I3#=t#-Z(x!-p#+_7$#1f=<*%$z^jpXGT`NqJ$V2-aWNw!V=TyW4){p;U^mA6*NUkkkXOL_M +d|73d$GoYIr1=5W5$e!us_Vnqs>Rm!P>%z5hL6hGwKDNiw$|;<#^ +Fw#+nNH?Qjn(eg^G&`d8+#8s;R>9%W}|yV!thRlU3-&KL3;?;A1s(4j-?ekN^~KGFfKaq<6$#^L$En> +TL`u3Wj&%@rdyV$R8{DgHv*E`6lq*#j&Iw(!W=(u?adFkgjT_zh^lTW +;GwAet4X?*~+OA!@Fwgoe^id7+cMkOsV`G%pO%Q_&`mk;tlmq_t@=x5ZD!uFRT$E|_;XHrF(<#KV2S2 +Zinb1Fd7u*{4^~?NYAKGA~g}Ua&L=AnxJg6Bd%ok~%ca4^S#`c)%B9!|<4 +25NF;>7>g+Q$I42%nFtr(Nz*irtG?w|7GT_5Kx#9+(M)%Ag~IL2NW`(fONaiYr!#as4gy_t4ivUlV;@j5o?w%ys*~m?I9xf%7+P*f6)w +jTnY}sNSY5iOpgFXrd*8^XcFJJEZgSbWn>uNZcC@)v~;s#M4&Twl#wkAt|4J{Qgh;%URN;NNJ=8_GYP@6ErrCJFmOJBjqs9$}n-eIqV|?S +Hu8^@!6rZtMqnfwdyMC$!m^Pr_?4&PLn4XV0F?uEmz$q%0;J4qxL-hwn!Y@0=!=ju!a;5ML8tWMsIz+ +Y|1%7J@qxyPt5swMIVNXBc-5z}?mw`*2rc{Dgb0h587oLnqv6?N^u1jCA)|yW!HAk?t;QO?*~zPEK0p +=!_|mlQT2M=h#ts+2ch{N_tvma!$9*KuUI2PS%**ZYf!rA~`3s`y?6_nVCF3FeWW0cW7F6P9ST%9Ywp +7DA~MeWTcgwotNXjedQrSLURpk#J@Fn_r*8GQY^+v~`kRK&Ru7M#GB +GAGCN?&HK)3+L^}CFqY1yus2yVrJd4Q8KgX@WGQQv9 +m-ziBju#hNR3c+_4n!oHBWt7EmQZY|5U$L8*33-XRWVxpEh30)$+A@+B4cJtz4_pUe#{Xf2$AEU)A5% +&*;q!V$3z3HmZy>#y3XLXl3>=dz(|uRpuu1tl5!g@Q3*lzJ~ANC-}``h1emEh|ff-zooUwdePcz^|a& +c5q7#gS28Er8S6}PwmMbL`_5<1ImhQZ7PtV@N5aW%q$6R(At@w}%pl8133-EjOPbP_)SzQ%A9h50RR6 +dBogQIyG&URij2OO^4-t1*eXaiXe*0bfxP8WMB9kRnBJd%dFj7F)k_z$)d6OI=$H*DrS0frv2hb$Cgg +#AI(Nfx$MX+erg;8cO$@;JaHi+HNl7XKS*ko3~3fWTjEGuRk*cMjB_Ok=*BUZz{V72T9r8#i>1NA2Dw +_3bbrXA4!rCrpR-b>HWr|a|d#rkS}i~f=xX8gvuV1$~tnD>}N%@i|Wt}}O=pPJv8ln>w~e43aET&ol} +`!#=zKLNP5)0%FNk$DpLuD}9OF8ta^Ynf0VQ8%gw)Ka}Izk|2u(Y!P7#wpi$EKlT5@fCa>_lwD5p$Pi +Jt^U@1ReWhMdMnE7SOV^S=vgFHCrF0KctuF8}zgKfAkhcE2EteWpp-r7`kB@vBvL=L?g*aHZqND; +}6Dk;}PRAqtI9a61mQJ+1PFDHx3v_jH5=4@wrh8@)>G2HCvjB$<2GsaDFR~;2n7!{|kSOzt4|@j9%cO +;zkiIdWb$EQ9LB_#B}kvctVtlV<5N9{jL1nVJ1fT8(FQbyRGroY-^GAytT#JZoO`OYlYc&*aPfwcBTE +P-AuNX5i(w8!CV!|8u_L4IsKe_ooUXKPKEQHgPSlE!ra|J+K|2^K(fd)Ak&-3r{pxbKon}xI68|ipwH +4R^e{a}L)k6tR@Q+9S(MUENmPoIe=4Vx^U95Cq|4R&)RF2R)cNXS^;wXEchp<7G~nT0(9!uut$E)3m1 +rj_#Bq^s-D!8SkJ?|_qhV~Z&H(2$Zsp;=^Rz1&NoJF3I)$}UgfdFmsT@$gP=fl`#$5ha{ta&{zVi=}! +{i9iqcjnC97+-YW*(b9%G_$KYz`?&;OSH9sl3_ANY^?KlV2R +E}aKiULz~y*T9{o&h1V|rQJI(#3QoZOwWx6OeTU+s2Nt|FB-le8p0Gsc~wOnxZb%8tZo&V- +25q(Cp7Mf!e?9#)_W8a*~A7n*iV1&~`KukSU8kP +M@U9=n7g4*tDLO(QX_&EcFR>nYvOfQ7hDs)z8#3s;>F9JZ-A>h&Ee$Tw9_QYax15JzQU=KL?zw()a4R;Rn4NWK1&tXcT~ ++)fz3$JIxNJ3Od!_9B3w)qs??P+nj7ZYR)&GGMAgBU<NIm&IqjSdPFF{DEGO1Ua0WZWom3|SWe9v)T%23zEOLC|A-*K|yAz2c>0}OB4|eke38NioCqS +5VItL`_4M3P$dXcsPc}`-fVA+b`%g+J&T;x6xD#AoF5iVMZHh^c5qJ!upx(Xsx&}2*W6tN;sBmi;`7D-~b7$s6gy2ua{L@rpq +X`%qE=^SxFd;_!9#^1@G05Uin&^*P)h>@6aWAK2mt$eR#WT86IIz)l9R0t +qBm&6C7x0#uuewrjT~0ozW9wno4bt!4tN9f(?n#8R>Q2Bfyhw05y3tE7-tX_6XEM)BCO})e@B4q>&4=N+o%1`t^Shnj?_57|*H&g=j2ZC@g&2E@i9eNH`7cFcY>NFSQ&^Ar +m$@%VD}I?<>uy-v8O9T7l~ioc;`@#v)4X}>rTK;)-%DtZcbqASf$KrFT*Q!naqkL-HX>G_L4k#`fJyHe#1IkS +G*_*gsASu=rj(->aM;2?)r70)X5G3lYIl%m!zn4D*jz7lCiDC`1??>71!J3*mQl@-lru@*6KP1*SjX9 +Q?I>$?ft8%NTLa7!>Vz0$4mG5d%lc>L?faVQ*iysgmh(-Q{w;If7eOuwG^58Qe-t~XDK74jMerTR=zR +z(9gQZ49pk)z7W@ic_Q6H{7&C0b$>RIf$KNDPH;k-*dfEWJ%?8>+Wqbc1M414lDhHEN3iG@F7Is3PrA +>2M(RGlTJ8=u8oEDNYwR9-$kaVnF8S7eQte*<>C*16)RlENyUV*Dy6@)hdZ!vxSqbp@IMUvXJmtt+1{ +kH?XKVhgi(vmfV21HEFlnHL$s-3WtoDG!dUBa3xYEJSppO1LsN5=+vc%yCCD%FjCa+@35(mRQqkFa_k +EzAD7VF`~{*`&y8H0({(si|w)#f@p!7?VDIbdM5C06xas6uTEu5}WtbvTZ$n1f%g>Ivr7d4hG0&J}m# +SH|Qsd7)74D*3u4P*t15s6L-(Eq#8Dt?c=0W|Tjl%Vghp9wU2xl~sEFZkzo47iJiqUyW~+f7K=AXV)# +EG6?P}=3L^~iTj;&?^xov5BK-ceKy@&ac})w>rQ{btu6O-=P1$CfcuT0>9c^Fo5BvQ#?RrX1AIsFvw) +wg;2nMrhIh;%%DK3*Xw^k~(PZTiT~BEAB|7JxerA;DYcNLBw-5BqWfIC$SI~71T~BWx%{|>dx*E@Q4z +_}>cjHR*dKUDW3R(mILqubuHE4N==uY$x=Or2wt%>GD`!KDa1+A%^)moVZ|5?yH*MmCZ=h%sNJL#QcN +v?`__&M&wyZh)}_L5u&-Z}U?@Pb+YTAGCpNQcHAk|6go#Tvi_by@}D!v +{`U?3;l@IdCb1{+4zFH{X@yM>eV(R#{oWTY#?`+0tE_t~@(m-L|r? +P`K>W2M{733P(neRgfPlZBR^zJyp0^KPOyI9&zc@p6jn9ztq&Kb3kgfnDfX$fS1Hso=)CWm3%AICf5` +w4?#Kwk&>gAmHwH1HfVv{Bx2)N%I3@^*b)_6I`+uqS8gfm5!c$v5O>&>#&wXOsr?X;+Pg3eY^-cA=Fg ++{-S6czwE%SC2o(C2m2(f${sDDo;H#cz;7`%UB*7ozjVkK*F4fNp8@Ka5s@FnMFCA} +o-gk8TeV8xxoG%WIFV_jaSXuRRup5LYmHV7g(0rUnsPD5jwWur<8t2h4@SHj&*_vcAD~SmvqX7jIekJ +MzK5#tm(epuv??&PSGYCGYVLsg4uE(8dInIZ$ozd`m4sDA*?QuOHfb+A#Xg-Yd0C@24qvE?~G{S>F#_ +%9i&JOXm8*TQP)e8sCB(k8MKlJT%qSmIbzHY!Sr;fl$twlbrd!!@B3@LtiOg2hSwqgw5-W>~VCmoH|& +ngSxto`W7>BU=j5Ug^|pGf)Dx@`*+)I%jGr$kN}*#%tb{YlW>VU=!^X6(F8#?OG?O4#b}C$L(Hec}J| +!fP`(tSVB><~AnT-*9E7&6t*vnmi|W*7R&!iZ%b5n7C3{zqtiB +6bs8veYp&^4ZP{x!2waL4p^5QM+#H-jJFC$kg!=`R4`ykXq^qqGuR7+)3ySVQ?!Han1t%O|Plg(<&XR +#EwdsWeDMH#4L_K1Jv+}Gutv#-l88AhZTtae#)-|zGBQzi4rvBb_CHpg=p!8ZJpI_kua(j5c-tF3*%S +CFm_^oUA#v-*+~zwvYm(wXC=gZw-3oACa_E(f;GiC+u7r*uZ7GyOl0&t!Onb^k9dchd4SIFshT+H(F@ +`dp#je<_^5mY-aAWbRKM(#t;VaP3OSd@kBF{rh@@cwaB_oLK|k4_6=Q)xB@w9&NH7_d(bOyyLdq@gD! +~TP;WH-efv9S)KLz?ETf&p0ECP$IsT?^N-6OJO1tdQ(?Ozv8^mOZQS2FTl3TIb4e_Ce5&02W|pD*zwH +!L?d37yd$8GO$sas`b^v}LtZdK#xjU};gKxqgbj)NOV-M}?I+vsd&({2FR|M|QINUI-bKJKy0G`o*RD +<>3fMWu@Hv#KJi|4TH@f;om%!dF|VV*QQgOz>3lM{izzbguFiUreIFc$CkH9z(&{Md6S^SnXw?H?~Qi +BPHNI383Nxi +#Gv0zdO{pWB+xW%3yOsVtQFOVpR(^E&TJ_Z`dX%=sb3SxG!zs+RD38S+TanMf<++4fbnot`(R8LLVp* +0gw6XU?D@-StDHJAwBb9iGFvfPD<|e4~SP3{rWx{}Is`byeo5-6N}+&yu1B+;dsC`w_Jr^&D7}$^wfe +);5RyzKuH%e-3%g8+TT3md(ql9HJ?eTfob+xd@l1v@&I+7iGc@jF*|=e;Q@-y5-t@6c4t@>1@Ld7y2zPr=_Yn45wNHXDvRXgd6 +Wa+tzc~eJJcd)j!>Ts`PJv~jmm2SPmjS4jXiWgXJaOpr+p?*b!aLJ5-)7P@f{=USc1Ck1YfzHB&$KP3 +qRk#&}Z;?s%0kZ0{IA}JAFo0P=I{IV=kMe8NR_y%t!Kg67?Mf4yc=paQhNyUt?f}bf1p2^&2yM$HA|= +kxrFa&wrr2p($z*dg|AgsV@)hZ6W2My!7r4LnT!Xgs|K<#fudsUP`SYUhr4v=DZ2@3?;Rx@M@ +su$`r{_`7^HcnjcyH^I5M8u1hMopHNM>8O5Zffr9-s0Qhs6Y1>PtSMb;&jH_Dq~B+2_GxxZl!*Yo&?& +Lgtm)J_hvQtCiq}T^Hd+-E&FmMPc_LHeW$X9W~ +%{$Z!eXbR7TzYo@!w;GJM~Gy&C$d8qnueOtwWNe?vcI0S@0J>M3+F)AuyxNtb-~hgr8nLVNMB)NOxQ# +?OGC5kFJ6y`BY>WKZ=_ISU*F4CgcfW3XKI{c~8acV+q>yQJA@d{+UBr&%rgI>vQB%lEJ@OwY@Z>UAXT~dR&8jXi`Gv5&6S^oaxYH%3sc_QeTfZwozk?cdZJ=N6K%3+(X(b*$ +M^PJx=sjPcZ7VK^T$+GBQ(BwFG2B$6gAun3c_Fk)Q=-2WbGbpZ=$ft%vg>Jw)3;Qyp +`}S!8v(Sw^ZltM08v0hPl?7YSycRI2jrrasP2QxL+(+088_w}XoAf%$_9Cx+Giz!EZKy2zM$bIYzL~s +zeiiA$Fgx(;wTO0J8l2@juF<55>>TLqy-C1h@!+-EeN^s`s4ddbXif~?luuvZ8QS-7eUHG_QQe~bRK4 +Ad_(vhbOxPn9k(pG@E--;7jRmn3=A-*%M(84brPAh{I}EJD!&oojo$1T)S-`tY1u$Z8JC`VM>(_9Dy? +o6EpNE%~%iGdwy-pIn&0kfIP!XQFqiYBYE09;1!{1&oW302 +iysBJl;o2qlH4l?7iF!I0^lcbIJrzS=^8)ykCk@&BeTDXZ2x(VH* +#pDyzx3(*@qV6^h4h(G=_}wPo5xDqU8c6SQpneG)8lRHOi#3VU>g@Egl*t8Q8KyrqM(yu@EYHr0FPqM +w}1!5e;bjnHIH#W@%D>iT!ic&bc;sTSsL%x75%x0k%AN-)v=pi5BJkX{1R&KL@de2cSL&l)M`MV{_Ir +yXgCkq-%;48f005t?2r})9YDP)@ObT+YclyE(U=O0Rf4q=?P@#lz-6@LE7#iOULY<>N4mUy_ZUpe3Z^1CHY>!_X;g9*|S32_o6KFEq{yh)(U$2p|`;<`0kw=UKMCF*5chekOeO1 +pDpF~nS7a2@_}%zlS|(CqAowu^?Y5+Px6YkB>3H2*71Nt#J72Q#8WjmBai5(21H*+x*s-RJMsJTueSpKECHm-ki$R;^O1LFCdT8m&+=Z**9Cf^J?#)3mrkgj28!l%Yck+^ +~w(x6D{3{ty&UT6ZEV0E2KZfdl6%7Wu?WwYi0}G;Lp2?eR;D*8Y(Lp<EMHI&Bu2O^K7%^vu+oD +7BdTiX+bW(^7X}GoWe29PCWmfh4oA5hrhC|8g_0E+7X{qfJg0osyp^`b^g1u|kE>+ +C1F(Ayk+KAsM;K>71Eo7Ii{B)cnS3mLu{{C33HEzWNp9O8{L|5iSp4!iOK%{}-Q^pJ2T9dv+yKgkn)K +y@<3z7beq7^k2cye}iY^R9_<^@>ie)Zp3#_G!D6K2i$4!7jX}v>JTWUtk_P+J6kbhl66MscH~*>b3lO +gKr3YapD_~t)RQ)i{(qot2}S1!8e>F@;Y?ik8PrQX=!q!(+nd{89{q91V7Z$YJPPXc`79HtBRELs`(X +lTM2OUO6=2$w0VaYe(SKo=_gv7O=PDJjLRpDV=a32=z^!`F}7H{avjrjSJ1%AuR>q0C)@j)bCaX_!{+>ecF+CUW@Uu% +9Bjn$sQm?IhOdah(JJ|oZQna0-pT~oCvPI4qnGHXm_k;7eXn(YS5L?bJt(<)yUO6NW%KDpy+$2WihWN +bT23GU7i52!wf$wzctJ_kfz8kHj%xAH(v}D-xlpS@qrCRE4n|D+8ZGJg>Y5q;hZHjefP1oH19WF^ZNi +kDpYTxh4$8h6nmZa2SxoF8&)4m&0k@vdm_k2k~9&aervk-8cbPs>SjI`8-ROGTMbJNV4?zsndIHjbM> +ZMSvf^QbU*_^zrjclzGWmEcmymLOR9@(5+(dH;+hbpN|(e@p_1@|u|%ePrkhpniC8TeCslt%bJx%Nxd +D4WW^4X{3n-!SSz^{50s%TO1>D@n3!aY`0mCyQ36ehrsK(7Zu=H>A~bC1i5m#Zc`6=m_v?_au9H-?dY +#(=yblLYlgzQkt>_GC9(JAym7c#?dvH%Xyx*HYlYH4!!R_jbyu>f9mvaeo>;S8;ZvKli-B;2-*e+C4&JMYGi4DD-Xw_S7Ox8|acnn`drTw- +rj$+J+aE7FVRQL+>pk8^D_GlDs>VL^W{G>LFXPW}m`pN86-s%GW6?jb$mK4Va#5^c}(VN7V&w{RwJdW +w~=?Qv$wO#C;y!r>TJ<6XNzMD))cUX4al?slmK*XWO75$xm&^ljlO)>rA6dsja#f?Z1NfB7X|YCfl%& +Y{L&rgCtvZ8SsxmwwNpeO(&Ucux0j2=^D___^bDR7>|G +r27Ml_(<{mmR*R6JVw?tY-HK6hnz+d$yT;#XLf=1iUU3(eV5FE`hm>$PxtOe6o0>@SQ%0!&{-fE5A7bbRTT3xzPdMMZ4&k52wo^+0t3QY9>B4%^&K@ojG3aZ`F3A`6#f1N7q_ +*7EhxFn1yniQwy+Attl#%s{d`4e54Pw7-l#cpTsUK}R^&zFeKf(QZiTCI5^yJfq^SlVUa(|Y_n5Zv>d +D7euvcNgKqcK-y9KWb?xX+Wp$JWyQC$luzrNSoyKU@yZ7tyylq5anR+1#huS15d%dgC|^xbHWO1?)z) +kmljhb2|7C#)Z;_@!qM!+rh^Wn9al@eGc*BgvEeYN4x-hD3S2F_eIL5v3PfmfEODl=wl5g-6(L+HSxS +O{wTfITI~>HXazhrZMdDofv=oe*)TN_i{FI)UfuWr$;^>8w+D{ECd1D;Z9{p$bx%gA$(Mx~4RJ$N_ob +u#b=6tVaM?jxlv8zZ&D6kR#L!{vi)g<GIl;kNjAYV@mrf&tr4Ft@+b>zm>{Ai@5PtU4B`Vy=?_xQ2w9e*^Tyv;8mb~ZAoLi4 +U)JV+FUQ;`=0>ki|oBW8{zJ`2c(00C*=t~;Ar9C-A-R5m>9GznJ +9PZAY@{Ak4Zr)pc#kWh!K!XOW#Ki80pI^JuDEw$jPibA})M7tVmJAva{e&lwrnt~H{(Z2ocnk2Ym(}i +fxQ(0I_C?V7m|?0vgM2X!zG9YZ;IkAa=xERfT7|!Z?@Nv&Pq-ZFZ=`-=!iW0*-x(`Ca!g8W)BEmG^K7 +F0mk9zA?WP}9qy3KZCz}1>7C%U2k}++ls9(VB*xw~K4!UYnN9Sw@p)Kv2{g`^4K>Q3dBYt$(^PK2MH$L1yRNpysN~!5`%f9G=baBJxZ&|r)H?UKf%c7W- +*`4HdYVL7JU~CJQ6&}^i$ct1iU)q=#PisCx+lNS>ZbjBklZBXWL0!lc>$2m>M>3ie&OVVPc1W1g>o$zbSXxp(@nleIj%8N9p-^Um9rWK7KpbO+^*`Z}w!?)Bt`sU-0HglIO7d_|yP{zRtadb!5|AiSvm;!siosR?go_ec +ioo(orpK9lqt@TR2_0md-8u{%9T>jW?f8ZxwxMOG&nnE)&0XfJ1Uv%-aXiUe@hrzMp(V!05hoOS?Z)- +F9S+b9CszhSAF0oM+}iw%lgW!_3Z9fXB^fn|l+O?~Od>Tf0i~!GEsm84Hb%M?~w9m~Te-D8PsM!YoPf +VW6`j&WrZbZpmjIrB3eC@OCy`!#NZA``^DcShvPKT7oom-++5R+F!>$=2Ic(G~Q0QynZQE*oW_9b6Lm +H_Y;Hh{2;D3gIYgQAL)rb@?J7ccpp11GCo()AiHY8pAET@c4KiaZ#xgi_U)g&6zZWd$F57E0vf|b%u= +{Um+vgvo1Nv%my7!CM!rqp=PGU{|T2E&v+5<*COps>?TobLcey^DPO(Iu3J+T1$CwB++Y3jVfaim}*JPT}87Y=7hXSymlx|L&OR +XN|W#3#~N-pZW%mpp5rH&nAtwH2*S;@BRFp&Gk+cy+yg^8(7UysI!{-_!i?H^sFtBj7_{>h4kX%r9pF +Z!v=?xZlV6A6Oe)7kFaL)uR^m~^WbtZ4)-;e7%v=}tu`~H)%W2?^C4Vcna_dkL7g2;4VG(on+;;z(gV +NXX7SFYulq#0MZ;OB!TG5ifwO25I5eMC-+mIjxo6R49!NVxb2`sK4&Ng=1pHBhiObD%jtFn+G~Se;u5 +cK73PT~pOh!NTr>{l%{~@=$8)}zc`nE;A5IQ1l7|QC*3AM1|q1CK-nC3mvyw)1l4SDwseM%0}*!^(G* +d1zQfzW}3(NKBfXviQnhuZO6BNvBmN17Tr_&)r!vnNd5p%;x5r|^0{g*Nd{i7f#hy=#q#>7i$K$u>gc +@h?iFOFQ>39$AI^8Nl^Z2G(Oke76>IOYh!;&)$%}{o8eSxT7lZZ<|2-amMbB4P*y(L{_#)JbgXcMR9V0wcoo;ND(c&9AULqyN$in#IjphHYJ$Vz(b{N5r=D+_`Sz2-W +}2S4yI^+PE0%Yq90RvP=Y!pC;Q$F|C$5NAKvhSkn^}b7v$cAr4BNhj+>xHJBu^Vl%G!CtaoY5VU7HNodx2{ZzS~vfdlRc^y_R*9%yu;XHTVXntHIdVX|n!FwAU_L1Mt3v^SLn2uvIh%P^ +{U>^cN?=yLO4|B~`hcPg*=uPHAkK51FSid$(6V2;}D{toM&uEn%UQz^|GFQoZ_% +HUL=IbZE=@U!l8@|)3?)FTF@ctxpz{YYRz^UZ3o!NP*iQVaq9)R!^ei@2vgF(=ygE~YG1B>N&4#p7t> +*R106&HNMlonI0D6VU@Wt~(9isXXmacs`j;B3ZThzXje?zgxx(q6PCwcz+PS@tW_Hc9X9}Y3ECXlNww +u(Hsiuqf>*_|1unsyF<&#A2PxpGL4=@S)l{0IkXn`S>RU8Pee2@X_lduyzMD|bf4_X42c$@yja +OOcb-Fqy$EmZE=C3>q{8(Qx;kQESD~5~}3>y^JkiqOKk*s_iQkl_LMDJ)WaFP*zEn-)?@+Q4QK~kNVjqyYob1J*Vxe_y6+2xYMr8gt#N*;cZWNpuL$nh +ncL->g9qJ3B~M9wUiSBZr*#HTU(@hA_lE9J*3$7bgA(tTwkp6)VLp0JO7ZUKLLKi#+hgW@=qp}>cB(6 +b>ST3&AF;wI)NQj?cJw$}q~D_*yrjO)cC<0q&lG(S?a;CH(D@eFw=!BEF+~lA=SnlwFXbbA-fO^P;jw +!!+Q|+N^6>FXZLUA%8RqNB6h5zQ^Ar};`%-RM^l{$-KCcHX-oG;G{FM0kPPrO<(dM1LCK_M%cf&Wj-= +uCyS!Wlc0! +v}Go1>Z0`lNi4-#`Mn~iS>gLlkj?yPc)q8?3VUWmV#hDf2=}i<`eta1ujP@1eD@=X`7N?k+)^zS7iux +VThQ<8sBg9#bp)>UhsAvF&%&;_@P1928XPiM2GH(qb4ixA<+;q)n8t$lw61*Vo+T{s3&eTuMQY%r!Mc +16p3Ty%MGcEsfaY~lJJNDJ3*>0W_w{NZ{{?SO1D;2;wXOezx*Ih7ON +|;GMnjx;X6RW%f>ccR`Da6g#2?~KEI$X&glg-+w;uuK_h0(izW?~$$e&~7!wvbM`7{IzeC)kTN9PQU& +JR-`TMX=D-@# +J&X4H*;Jnb`M+M2F+olWG&+9sI%RftL@z{?<@D`w}ZZuTGE1&!f|Y{H%>5KRc5r+M0hP`7!HkP15 +Mn#>doVG8}eYjQOHm~wS$l!3*=)ndacgjeSPDU1+wDl$LPLz>@XLT~7vnA4*iMB+FBQxLEY>8srS8Q(uZDdwU>+2X@pBFx +Xy;rOgw8Nj3sPE)DTAwg;5sj~k`&o<3T1P(AKXyCQuSZkOl-XmE)>06xFT6@PwHY#dDTQwQB~LsoeilazZ5Ea%oxwm%wFk3FBaf-jqy}_l10kaZU$h@tK`txsK0h+Hxcr2l-Rm6)ce<|j9IBCwuLDo +d`c$P_GzU#{0iMd()+aZx|4Zb;He=LAUWzk&oih8Mv`La)gYipmz>-s`;T{mm|qWKr*8zOoTp0_HcY;;Jvd4`wc- +(301{JUSrzXkuxzyEpup&illuS>_juW9_NkKvzJ$3I!ezgMya|L%z4pOVOGcV2;igX8=|yB6l3Il{l2 +H2&S7Cy4{#>sAHM1Nmm1;UT*CcaXLUu`57T&90Uj!I4^H6WAFq;!uWLL!Jb{P6<%Q_ +_UXF)<3IEPr1^>2XNBH-N|K;ER82@7DOYs;3ZL!59RjvF3a875no73UD|AYS}vy5v@zd#S-o;jL-`w0 +W<$kpz@kia@-Y4^|IJ@x01-))v=aDTg0;`=vo7)d6+Ue4Bl?@pSlN^{kvX^Y%QNBcX_Tpm7$8S?QS?k +OLwZ#LuEjqeS4iTR_Nznzny#bL?tYvj`6ek&`y2XKeVX{{S;ra6apGb{F{8f&^}50hyLyu2=x;O#w(E +peN~{6DWr;{K6hCp?4KY|NqjvjgaYhd +h~w6)#=gwIz77aD)s2|SD{DEnjAOB$T2-%?e9XI^i#F#`OCo4mmjM~&6*y>=F#g>Z21P=yRaU`j(?60 +@qIu1Zp7kYUFuJR?|YdxtXQW@|0YFr=`CYKm+rrUE}b-dc)HY}^(87=8|FnD)=0W!;JOsB2wiG+Yr5o +)(WOS6F1hu(WMIv-*5V$}X8427YA^JN*4FOg^Lhl%3p9D6@g?YC6{RyvX3Hlav)!aa2BAaFX4i&JO@} +&TbtsAX==-v99kOs8YPo_AZ94xqwp|9HbN_yoI`_r%5uMv+x;mY^>wL7%-90pMjGE+olP33@V&tBlul +9E#PWq|Z_55XE>C2DRyG@$j#OBfKU2ORV-Ma~T_ZgYDUpaB~&V7Y8>@#vi?`Yj^Snm>bde$@OW2>2kW1!F7r9{qg;XE+t)7mqfXg +=g|Any5##4bZLC9ZbX+hXtKN^MwaRMYJV5vq@Su?&tC?XzWi8S+Mww|Y#zNX#g=c-y(3+UowrLmG(Of +~jvi}R-@0=2oWy#164YQ1uB&cR1JrL?CnIJ(2OeI-g59#(%w<^O>wT-KKRW+msfOZ2KVzpWq`5vf(pB +Q>oY_|-_I{g7d)^FC|7$~aBu3zAE0 +RBXkbFnj1XrpzXU+Ab?+k{Y6_Q_W|7W3dumRQfnG)|0Scfz;j#aVVlqx-}w%4taeCyr|ZRdNiQNBfdt +gbgaztw%MnBQt%Z>pj(q6VaQn#zK(EoYpxHWc-pjHdUsuDnR=iH6SkyR#=CmJT)oQd +Gil$34T^uZv7#*xX}*}J^0{8`C9%NgQKv6l&uv@qMOrgVdJ0=adP?&qV7m(I!N0m|)Zjtr={T)s3R(@ +4?b7mW)}{R>(yqCN=8*LjxAd~6a5`G2w+8hyOF5kW^xZvI>z{lI@eb`JA&dS=&}?Z-u{55yaf+w9F+n +OeH_u25=N&d!{EcMmHQ8`VmI1dW6SOCwetiq?Z)9{fSM++BE6N9C_9WTiE6E4aU9EhaI{SZ!d{`p#G4 +BfUQA+X=rqvbXBjxWUACIx-4@Ev=#-;Q&_Rv|{gH;W3yT$kJqdoMa`LMcmkgO>FxG9)N42D +Pv!AApZ4-Yx$hx9r&xD2>J_?`6%!5)!hxwl+3X3}Xn%h;x@0}-NHV9FXPd;D_4u!^=JU36b=V(goy)} +e>%1)juICMs>(jt(g8kw9V?n1%Q5P@Lko|i3-O%=@Aln>|=A1OjOYQN|J +UT-%+jX4JWl#6h+*#T;_c6!}$v^I0`Dgha)B*F%#F}C9pR2`wK<;bAysDCYVjU1YTN3zQAcqkadxgZVC3s<@?}g{^_x^J)JU7|8g2`m4;eTc5FP5QVYD*zU&6CK`9dTr+`7$y +zTPH(5fA?}Sq&82GA@jHlHOG^o_fjJ=^e?Fq8H%^QV^8bWJ>SOPxo_|JcI>>Y$=YSu&C%6)uz|4kBwr +*~phIngAzH51xIBR^GTJ|CD-b&otq4DNbzd{?n|T}ZY+o_%i^FY3xDE$&b$CQqhj9CL>fLuv4dZWg_| +$N$A5ZhHx5UA{4Y+$Px8|GKp0u^7Lo3=q51pxNVhal;Z%#XCn*6TtWzZhc{-eF!e9y%>J};1%E|!GH0hZG0SZ7n#q}xS>T#z2BYCz9SuePp6d8*mfyY`}ibv7X2;Sx=veUy?d9D750PQQ)vG +!g{9K^$F~tXC4H67nPIhS#%^ivmlAzS^P16y40W5;>^VF?*ZK^_+uxVWKJQz7#ng_6^G=p$o|fl($m2 +ez77sDD+-UlYdL0zaT~m)dQY%p_dig6b5!}}=<;Lxr$2du*5_z(M|Ekj(vkN|;Wrv+A9^)#T%I_eD}H~e%==3dbp53SkNSQVN$~BfBl`=MUJTV{vdBCy +YBvbpDx|5uM!o8?OuDkDoNP(#l}EmJ)`>WMrZIF%h@lhh(;lW1txLQTo!-ddd$e;ptpc90_1?;&>rMO +JSakKK{yeHP<)eCs>#>nV>had`iS>x~PjzE^sDsypm`|(20#l?8StxTf)LG4aXxc}D&IJJcun%31y1W +m&L+c(FpzcKTW3$!fE6wApbg&Nl?1}RWNl(8*bNICS_wHZPMl_JX`wZ>dhsF0d$M;CwKfx~>k4tpxfO +PyWjK}ZEXmnv(ghsT^aS-$g&k+n=LVOxi*Q@1skq&?TnxJ^=F2#9#vWxa+=rFQ^gVZm_ZI5My&n1Cfp +?&CSy^&e6CDGgu+F!{5yRjYZj9tna$ym632d&Zk#<9@$S7l*i%u+^MC2R_RHfzsj?HMq=W1+&ma@Gj- +Kk)j%ci?@{D5qOik{bA$rmJ5+`eD`M*$XX4?$+1C^!Kbs{C%B*2BUwmf!38VIv>$Lih9wSVOnFheyUh +ww%EXeqx*~6!t2aJa`uP^`k4b9`f06jW?xaPuJvo{iSrGtXCLnWXcu#4cfq%F7{pq%&rapGTC7FuW=; +EO4o{r5$^E*u$pq_IqNn;*d;GQ3q`Nyb9CJSqXS~q!#5HX3_jiiF&z+#vC}@=q+-Yrf5VSh4qm@geRX +XZJd*3~v(Q0X;(pF-~8tK#M^#SY{t+jT1MBOmi8th7gSc9EwkIaE4x-pGzQ&AR+q1#oh=?2}bfY}mfO +?Q}1gsT*XZg&FS0~*~X+hb0&=Pz+u9ZxrO4}M+hK92|f2K1d`mb2-&voUtd09?F>u0XfgF6B&M*{v>=3+Cv=6jLEY0Ctr-Msp)! +v6^_&)(!qu`N7mq5mgDG$wSY{0h)qu4w33p%>zPKm6WjHh#dZ#tG=fsP|OIu^yyu|P-1g;${C?}(1HK +Mr8g9yxkC=Ecx))+P+Ax8j1CWk|QvGk{sDHOdnHqT3Ay*VlIm7 +zIZCMoyPdNPl4y)q +d#A`$REWkvc3-MPz;x3B6l6dTu^i0EGUva*1bg3Kh8e-!@S!RXdJzZ}B{Zzx~j|uMMms*aP`lHoPiSA~HPwix##Vasn`fj|jw#+Q?MxdzKX!*iobHDI+?l}m99Me(wUN+v@ +(D9oMjJi<&L;LapgnJS9W>h0m^JyV<7KU%6;&4PCR(;Fz>>6zL_lc +OV#$?8b2Q*Hjd8)uW#r3dE6|7WzeSE?6NS#))TT0LNaH&IVy>Gp(m_;d%-@qdns-cpkxZ*pTFEf!vS`ZC;>m7%` +Y#4YWrB&1;yiZtz~B^NG~vlfbD0v1q;qgZ5#_2aIGqQ=TCXCz-?XayVjN1{y;mIBvip{Oohs20OFR9^ +yeYRh%ynrqglSb4N#?pIgP=jTFFjNtYzhSz=+AE& +Ule0yg4@bDuo+}x^e^{oy-TS~_bIe(B=7GHlUcRR+jSVRq)^D(%6M8B3v$6Si%oJycH9V<~{+ae?mWB +WCFw>s5Tow<<(+@i1lq3xquoyg9-Px~H%uB|#A-e48_bC2jZ=KFAd`j2$hP{zn=nm +dE`h~@*4@6G;M4fa8Iw#aJpGLrrCG;cpi%-jDwYeL;?D_U79^}}wtR^8wVi9J+leL@&t@F0zNC^h$* +C~n~Ml15elzoDaF{o&NHn~%Ky$MJoReQW7FfTy3XrkMQQ48i*&kgKkrK2uGx)!H(_3);Jt_7PmDlgn$ +Z~}%ugO|j9n^f)$UHxluJxgns<&`gW0|z<}fyT+}%Gj +Z=rigt(?ZEr%e~iCxAIXQ1b7s?@vk>0nmG(&`AND`sr_i~~;r&;?44oR! +A_T53iayk8gKWTN&{}_Jm}c_T2e%Y+mc9L_N0Yv+|iI22ch$SjV$&&z=`K+htG+peL=~_1$402X|PGg +?gZ?$EaG1d!Ve`nJ_?Yu^X?KU>iSwhws>X9J}^O>9 +P*|3@B#BElCwuXC(sE@^lcAVs55d85$b_l-h23FH|h5TkMzgd^RRm)HNwt8eUr}H}PJo5GrLcN#c`NZ +?cCqKs~sM(oO(D4J%<}7^lQONOs;unD3SPfdf0lJdEJ1Z%?r|{%wlE9zuQJ80&V*c%p7K1XfS@WZJYW +7piqnqY``wyY*i^oDyX}d0lCit1Kv;4d&(q%K-Se?`|{h5=1yC4(j; +qhOgm?az7`MQ{RDNRZyk`KyAs(U+QVO!STWI;=xLEGZ6~L~7dRK{J!Y8he~k8fj>Ef0K!-nAA~yX&+C +w1he@ZhJh5gS2y$>wST%`9)>6|$Q?J)!I@6qCqHz9u~Acw~xk93xaXiH=D|7W^>Jl>DPV@r}-ij}sbj +_dJDmsv0T=@&d4cN@q3lfe65ED<}g#uC{hJ630icG<(B?bI%uK;1!ezu3ns(?4JAxy=3djA0%_`DxGP +Puk+)JMN>n$X@+xrf93yPGLPHH_Ii1Z>5wpLUVfh_?o=Zm+X?Q(L-zG@E*P|^%ourD?aqUPE`?nrU(3O{XCa=J|TKp*P^ +J?d`z61X9bM0Xh=uF<7a>*MHq|ta8{C3bMHXXI)l;$g->vy#Cs^azC`WWkb0>3Bm`#OGG@!N)9JAOSf +>+8jj25t9$I$peuu=(K(K;1+VT{DT(unDJ{=8mKcn*n~``v9t@IUg0mQ +K!fhnkyGVW0{n6(uMaRbk(*l@Dh$|}ruR$^|SLPqwp-7fjMLCJV%k6hL%KL*1e)vUn-sxc{uJ7sE^#@ +>s>5O#R<3Ee$j%W52@xC3%cSvSU=d(I0uvzg-EmgB5c)0u~*98YgiMFAyL|vckM-Fo5+sRS;UmriS)Er2&J{p6*zhmNbeQDBJ~y1` +LaU_E6~@;2^_X3@wq_0eBeQF=J9o}LRP)A_!$tX_9m%mCv-D>zCHZ-rN2Sj;Z5i(rgx<8gtJk^bWP_# +7drQB*~3ze3wQxW75Llp6|H{yx|6@}LAeeC&39pib`NVZW>@mPewQQerTW$(AElwZ@8m}4M`cb%Lwy; +P4smPNS`lkb_`V0pFpo>#`PxP2~Y`IwLXazbR~#=nTJ9DY34|~^132oY{a>?UOLMeeq +<};Z=*x(A^jc1P>8pD!~EP+zNg1)i(gRb{DdBwTTuZ%wB!4mn-C9%r0x@|DV7m2FyTn;&--61?atHU@ +0Ydq$OeDyyb`q+_TkT>UKEGZUUnO&WVR8^6Fh#f@%C#P@}kY)Yrg4x*%3M`vzzfVJ!l`_lX$MElmfJ7 +C35b(&7VI@?0qLq6 +c@`uY=kW6b}_}U8>`JK_@l+pbk1S37y=%X`(cA8i(#pM+y)^i)9n3EN(!}IG#agH3?wVvqsR^dJZ*}x +khPDfWC)9ra!e^%8Cg+&9Y@m$ +mn-VLxPFO#O7{x}t90$5g&QR(KzvS&*&zT+N?=+|$_qh7&xLwFZnCs_C4Mdpr(GT1_wawAK(+{kL;^{)}UrO3NdqhqN?$E +k7X_`rqG_E?HF3SkK|fY(^DyF +#|SZJ#2=MoeclsB+-`&-=k+0w;36(Fq}6*1HJ7ib?9ve;)y&_x3>YqjXEo^9gKN@R?G)Km4b +cATyA;cIr!p%u%}7^#BiEnmC>nhD$iSM0p`=zo5P|dJp=@SPZQ42r9Ua$=*L$K}ILG&K%NQ`vq;ty3kL;DDk@q)k*+F{q0P30(El*RclFN$-30;{uy- +;v1p-s*uzvx(-h=Lk(4FE|I9cOAdEcqoAt(%v~-p82_=onGpnARhtoQlMMr=jLvuIA=Lx9QbN`oKMra +Di&V1N*+HA?4Vf5t2>icvoXA_qu7bZW>NxQn?&aV()@4!u0FxHMJ9hxly?yHAUM)Kt8X)|j#8`daqyk +Llaj@|L96d;^e!QihvxHk9XA$xEh^s&$SkqqSlckmAkL))tZewqjdWHBy$_!wEcEc@Fb`eFzb5CeG6; +PBp6A_@Z?~Av5navKtrZVt!6&YP&cfduo^9x^Kz(Rmqv73#{1a=L@Az)`ukd4!-O7B;q{CT!%~k>RrH +AbR!IYVH4sZ0jC)c-fd7oI*G4gp0X?|rP==h2naOEmBh^<6@cd_xaVEOYtQ~1nT?QGk07oGbnXd`Rq- +imK}d8Jrd+U4Ya^JYG$I)SI9{-EQiAIUsF9}%$@Pb=m7$e-qLa}&mK(9SdK3Cox+Hg}PHQJ$UQ?}MiC +e0p8r^7eFO{w|HPmul@#Yj`cW_WZE6o}9ivsP)f<_23lXc%P2M{f%_KGxULCUi18c9WKQGTeNZz_wch +f39eg%6Xpet2@IGbanoZaF|L&$e8xSBd6^iW*3R{bJ+E0wQLF6hQ|_nO#eSXIbWGDJCD-X=njSN49l; +B_a_-T}3B#kl(~=aG;i +sJ9*Rj&3R(?L^v_;NAv6i75FkYRT_)NuHtT>s^B^2LCrm0V$Vj4@M=LFriaGsUO^JTv8=T<(B+Q{#{e +9Wr_He@r~S?$EnA$fE3bm1~k8j&5&_ZOIusa6*KzE|`i>c?C7`&^SR>bW%XJTV?N_*z}YvvgrpVv_Pc8PewSwTJfmE2YgjEeUKP@q8 +_G758ENR&k}okO7=xcmx0gur1_tStG8C1F$azr%&u7fh0}X|X&_P`Z9auj^f +g>E>h}NC%2G6)u^ck_D#liN)iJa8pw`z8-^4Y6zk_;yPI}H>pe#N-+HP{AQbms!HKX^8)!P~U^PvCk~N*>u|VzpgT +=0N&bc{}9rj9K*@cDAl9U}qU`+fnBQukKimx(!GyNa^T1rK?1mlhnU)?ZF*M_}-t(d}xdL_jy`cN=LBFfI(^JAzg)*Cd +nkyc#)<;QaG&c1^qk1X_Cw=sP$W<#aoXk_&51HTpG8bG4~|EYShk(rE?W8fS)T+ZiB|p2JrJq;Bd!+U +kQFDutO!u?1fr9S0u37AY$3`p#Lan`wZ+_M;<#wa|UUg&CBh|v=R8qwPxgf8urA!vNX6DdBg1{wV5>c +o64LgiLz*oiv{W3DEkvgyI0B@q37keuaiD~;n>Ac?Ri78{~Ug!h9o}jcw8GJ^YD3F8U9v*TYb4Xxq~G0n%_ucC1HC@cI_DKE%$>o+crdVVBt5BpH!jQxEb8WMfg&u(VO +q9sPOqw-2Uzo?dDcG7e{fhZbcEeMds(e94R+Z?mm7Xx?Fy?;zuHW8kIeAiOT+Y*zee?v|4=JcRpm`?M +=))0ba-7YVjEP#J>rN{ZsUIE1Z7I#prJ_@#EHXNsOD88HCOG(QI+eo})bn#o3`Rk44}_pR*)j@i_xWL +)&S8&*=FGx_Oeocg>}ngJg55zqA5&w_CHjv=2fZz9H7z>Mcw0Q9m`w$!H?{Mm)PO8OPh+$>L#xU-xQx +a0Gc;UNr>qytVFE<-tGE^RKjLzZyJ2&weJJf1n1B((?}@ZF{`>kw3sn^L-LC`sU6S`v-n%^F89$|?g()L}DC(I)(j-E +$2T;$APMed372#XgOBIDOXa{PIO;qo7u&HFQZsH`Vu^K+q3EBU2?3Z^WnV>i8ChdhOlr9PIj)FH7YeR +!|1J=G|3muh4!3sG9_Qe~a?7f$z(}_bK4|6s;4muqqk@Q +ef-IUXV`y>dnyhX5`OAIe(st_A-(A3JiV4_reaIg6-;uer%q_YAnBN*zhXLUh;jkJ3Ufg@!PPe_aF_? +u=7W(X-Kv&I-lmvp?m5(q_M5eABfm>KJpYG52YW%H^OZ_@Y!3&n(ASnPZ<=yw@f?3Wv-ZK=H1V#$Ora +5B8&Ih{4?+8q$)n{cq#JU`$tw)8D1+x{j`!eH|GhI*@m)dzm`Unt=}_|;yD`sQjiut{-IvrPWZh|dot +2K&NNRGX?_6u5gpgydJpKt_hIQsYJ6QS) +-BR`EaBTnvK({zDV)o{^?C8==;g(TH +fhZG>@cZ$K0mb^nU+b@qWkLs?YHEA8A^hMDIVMz5htn9sGTM({2mB&)44PSKZFv&ucniruXx-_wz{Z@ +qR(mZ2o?M_I^QCIe)*fX|+P{7i#YpR^7fRmQTtzCH2k+k4rRnN3?UHI +PKi|yiDHCk?a;p*{-nNyO-KHS)2vDS>}Fj0pAy1O6TodfC)K;57O~lt(|*%l6G#h)<$?Iv~#pKOSbFD +u}C|&n?>5W4QS`GbnVpR!E*c_u^0 +a0jobd33uoiTI%=Pj(V4sn%Gf=kjv$|aA5)1r3#P^GJEz_raK}+XhK1vhso +SvI>>6r!>ob$!TBL(_&8eHayq>J@yKCnlvDRjM<&H@^*T`d;2kk*3H{Cn7yz@KRDyEcDlKH$D+99_!e +_kAA^kp0M`dC8Bc?O9{Dv?HG1S|laCw?<+qkvn522Ur(pGK%Q+;-=Igo)Roh +xm7T!nT4#pW#<^_TS|qwR%n;*z)X&+YrGHrm<0~PO70)*6OZ>pD2IKpC6V_L{$k(GdYx4}T#*OCHen$ +tFV9pXT?jEIH^r@dPWbm;YnnT6Mg2b5J9%Yv_-nYIMFwW54yU<&L+cy@fZFq#Qh1@#XTF5TDr+N+OQz +0o9_aKemMX&4JqK%*YD$Y0xje~{b;sQa7G_t2GTg-=#?!SoFKPPB%0W_&-QQHqfwuraHXW}pMkMJaV< +)0LML!nq*;qdhvmjHX7IHlru-pfo3!&unKK*H}aM=u%CF$PAqgZiy#Y>Vcv(Y$u +z56uUoaYKdXvuJgrdMRd4HOc5oNm)ZjN${KYBnsD!>ZRxR&}`Zd67e|7rFrMwD0k>1teMK9Jj39h3wbJsg$xYe-d8*i&kb*fWy1Ol@!olc +6{k^MCXtC;O(v$0OrT6_j7(S~GSR%&B4lFr`+AwMUQQ->9=9y!VmX@aHJ6i(EA`jYTE9`q0L|0B0H5H +Dn2W;K2(D>sluwA!Nq}*~z4F(+Ag$QhV@f-t=cG-|8#QPXf&kINH)_&qL^Z0~=j>7JMWA4WJEoOD1 +3bOja|W#x@QP7v)Q=umSHnQ$fFNpT4l0{N76|i)f!Q=fNSG-&b%i%{4}vZsbcs9>6Iq0&d~;cx3NsJ_J+n|cpbps&SY^G(u8l|_1e<;1ANTdU +|mjo;pXuGzup4BY2Sh4@N;M^-0@0h^|P6LePJP&UtQbrCj8IWVB@JQJ4_D}Fw6%n2wLPZ +$W+me9jgouLNGH96h`(11A%w#e_J-_ +pQ-hbXd@|m5z*Y#P?de-wi>$%jNRkj}E@BjDb2hJJipve!sI;_svqr;K2ka8oBu4lFlZ +M3Q2dsiR(ez!YgL0Km8tv~NR_E4KWjpG9a*E^^(qs9BJyGN68II-`m>VH8nqOdS1Mm4M%C40BLYGl~T +T~l$!|IH@G`|pUU;N&@$MW_Aj)wuSo4>6eBFuhh^z-AWe$qAJzB?xZv%{GGk?Eqc*Vs4L6JCJbWWa3U +Jq<&tfScpW)Q)n@`(e*$nB8r(f3E>E2n+6;FBkciGH8$2bTIs&rTa_Rca6!)2D8zr?7K5 +GQTP?3jjs)F?n9sz1k8_8tn|4D)O&u$tM4ml^n5|XFd`Bp3V}XC-F=_+b^ +Ik)Bb!BZHCtF`Voj7O2iV +UJG+bTythVRB=wmBPn`d!Fx{Ko&iC)7DE>r-Y%-CB#ZHIntsx`dgmk3G*=A0zcoMb)SLJ5GIB7c_zMo +OZd`v^l{TgPf}!_`JK=kuX%K&a5O~AMj4v6|IB3fxS;Z3^}g_qL}N(_(1DXHg`*;tUTkE3*TC-5B-3p +{g5`6jvRasa!8gQVvg*A2j}PIC)%88c&3e&Y!heZs5V!qd?03e%Xp|LKZrA?1a;Pd_m=iG#4(I{A@C{}=Vlqs&1x-6o|E}FC$ +p6fK#q4iaP$QwV~&EU$F0Wmv-cu<{C)$@%yq~A-G)7er$@y~cN_jZ-*`{P^hjq}-anobibgq%dneXe^ +PbwD-;HWtf6t^(5{}n`K_e+nR_;7zXUur={?)e+LPoX?U@3+X!@Q$M45-SG9H_Fl(IN@zv6DtpY>w$yx_& +dz0@DjkEr_MjT>$ER1ei;>JNBxrgW@dyL4z_h6r_`JlBW&Y6t#a!rr=NI9$N^P4Q^;`|&ZHLzXetDSH +I%DDN-An7KCBgCBEX+#j3NRPkfraWBRcTwOi14`Z5}v+)Cdg8M2-n@I%i +SHQde0)KPK>)`L9)JP1Eh~HG?o}BP^1OCL3s@j!9yiaXHTaC5(Lo1|R*rZbn!r{ue_u(=2yR>r*TIn~ +wyWOK{gX=&uY~R*8^3e8+=x6hU8)O{zxmruR!hI#T)5o88E>a$_h#maq#v`;U4NXfBtUdjEfiRBN~23u--E)qDSg#Qf~;g81g^IqZziJ)8o+Yh3_gI(mMP?#te*x>- +hfsT>GrjEMLD%gf6VL_@;n2p-zwI>kxQ&>H=O6Vq2?457jE4&8Med{d7C`YgH<6H&yI7Y?(YLAfs_ic +5SY&8e@0tN%jrQ*1GHPd{2L(?}Z!nou|Q{vOVb&nIs*K{ +G8c2T4J+LD{F^4vIqFcf9J}v`QOCwy#mX*ro3nznK+ygLj_TVC1DT#R>Mc4;?ZvJCsigq>^`>q^+rPPgBs&e1;O>;&J{v%x +afH+JE~DXk98@wC?%$3%?7Z1YX*W&YMg*?Y0PjFEsfa%#?}XB+k2FAhVjoeE)Sxh_@yYNe@C-@9Ov@H +IhZyhG_L{c|;AK2bVe>bN$ef9iD!Pwc(P(fgD6C1x3Z%+Yu+``cny{q3>)*q8K^G5V6RQeyft`-rt!x +fESzOcTg0(vHNUIZWRJufin<_;jmx!E~H?Xp`~hhOCKR#-DrFn(V&@V`2P@VoUT`rpU1bKcA +Zq4=|vrdNWmgPSRzJNJHj5t6thT3#%$xFQZH}gXcGWIUp>Q{R_x~^G|_@VN2Tsj`(nDtxcQ1EPasP)6 +VXnXSBMFmq=ob@wx^72lG-%69CY{SR*?~(oOG5YJ5aFuXu;yf>o_?t%c=QjFtdFeBfWcIfud6aGZc>X +YI{hX@59Op=Xz`cq;eVG21U8=v)=ZJ!-b3_lUVC(d;-`(iqC-wwXN=|MIAc*dbA^wi#*;HL9W%T%(Q%3ezlV$Awy +5-{k{{C{A`%AO^FYK>4_^?OK@$ASNq$qq~%rpZJ^W)$l^EYsO#6_#pW%z%et^B`Y*B^ZI^WHsEcvf$t +9c)=?g6}g4O$V21wn6%~zPaxE?evi@|E?IK{IbR1i^dzbysHEc!L#3mv!CZ;0nX@^IRC4_i-S1#$n%~ +0D}(oLTD1c?qH?&y3BBaWhtXZlXvhZ%)~+Br*F=<|wpVZY`{K$U4OX; +E!3K_Ov5$eG@h26=GcJkidKJOfoJHb-wJcOf%x~Yern?3DS2y-H!j8Q0JbPmA}FmzF4l$?+iZ-V=f+L +bN^osDZ6cRK^E9Y+uQ#C4BS1z*18?CA^rI_^DYl69p+oR@Mrj4B|^<4YJt@EPCOa7hq0e;N +C8*_o|7J7KO@I4GV(siW>b!*goZOjKoJ<2zxiB0%cN1aH#_(%pHC;jF}VsY9n($+Q-J9KnB&$w|)WAb`!G +-A1Mz7q{vw9kmWIMUD6PMPC|-`*Mux1E?GZCs^ZKia-O+HRP1qLX&3G5w9S$$ibd4d(w-l4SlA#yoi9 +J?tyY$(h@fuHfKwM9-`7UeQe$j^g`dKap6tj8nIQ)qkfb>QS5omJ!LcYR37$J#xi9;k((+GJcj+n`x?d!-wx +WlglAtAd~z{gng8dda9bVVQ4LtwHP@V)VMmMKfW^SPYVUfhn8$K!P>yne=4?6$_#gZP;J6vIoBaP*@! +Yj`=@91MW6qWTOIhelbAQ8r!sz>R`e)h&ZE&QnBEa*<) +=(OtPE^KgyGmbG(k$wCm~q{3mXZd}&4CPrcc=Ld +ZKm3UZ-TTrZ>pFm7^_J6XMGBP>+pAjjCa7etP{L%S|@sg4w3KqtXAKfK;H~e$@o0GA=i2yQMyIXqoQ8 +$H%+VOU9YEDlXq~Qg6ey1hrH5T=xSV{qEScQiCNNATUnjiKnBFRNw7Vj(7WQ8Di7)i{XOx*}_L#FL~d8b)*l*wa6I`SLWMg-m)=cknL#`d|Qq7zBm| +eYm#U4ggFbfri1CquH$~Kbb?R$ak>4AN6Ji`W4AONw3}s`Cm8w%x4#T!szPCunKWk~>IDI#mf9NGFMV +k?SnRE3QRB1?g$vFu7sY!=;%uAyC=%nd&$xf1Z~GOX)%`O?Ah=F1?!(y(UbBGuhRM9|0_+k#+Ej5=-g +~*Fz41ppVw1prep>nz-V_-07-#dTiGj$zcY0&u?*&M28#BQRfc92ri-4k!PmU8$BEDbW%jw|96;Bgjt +Z&-2NcyNH`)J=n`v%&$D);#Io1b&5gEl0pZ^n;)H18v~Z{zjwUj;MZbIcMtWpNuP +S^8VxhxRjj{`DX4v(!9&ip8tp*qW)Og3gj%H&EGu6aW9o|%jXw^UoPbNyVRxb-*i0x8=i3{4)zT**1O +w#t4qp8&)fDy^oU{@cq;wbwP;^*FUwCt`IdY2jop@HpA+zK03Nvwb<#FsVNOa%NxvxGZusYy7;}sEk% +`3<*-wtm=*KhShkLn2Y@#iOtxOLE8P^H_2c3!!Hhpm8$u*uP@n|*aq7bKO=i}0v+GQX8Nj}1lI@%Gvu +88P$S%!MuYfeSA~%&hu0zIOAkJI0%YA12YQlqYoXIoresB7tO_KK +K{!9;nU)!-l+4(S*TLW>9HmGGuV{K5cMjhGvlv +s8F@SOa>tz$fCf3=_7#66vYj5u0QH^ro?qbF+nvQpUjGZ@;ab_a*2}gl8&30}=JDV?r3m!i^+6LqR<6 +vX{o;_B>-*PN{W6a?#(jUu&|L;fqK#tB74d97RaSeXq&@XUJcn4&}O_y;A=E-wY#=Vt!eS-Jszh(Su` +f6`H8#((vhiB>$X3I58@G~Fr@|R`&iclWbjPs%G>J}r%6XQIGCn|pI(}3?16^kzbKDGYq;6c89mU~hU +@J`o;wX-FN9keI%3|s5>S!C?(Q@_a8FLaNJ-|F^H=nc1hJqk`beuGC1J7ZUDJmEi$g4arn;Vv0NO%#* +2Rrwl4fN}=o8}aV`4bV?B#u3+vwvz1IHbKSHmUegVg$vC25QbdiSUTkiYV~_AgvXu7KmHPK`xxh8*k_ ++R6KYBYje<7R +gxbJ(Jy#wHC>^)7xwZ7KG>*D%3ueezK;YsBMxSD8s(^9_jod5#rhS?ECZ|JFgDTOT~!^@jfC)V~im^^ +fMz~PT)hfcV85orUOn*n2+r}<@;)TFXH=(xfICxlq$bh9rL*!vN8Moub4iE?|DMW6Z9ztz6U)*-lYf0 +1K}NhLyX#_Hyh8*6LER@{&?vTpcFxn +5-&}X2{z6UMcU4}hNx1_n4!&J=Qqvq8j{i>~bb|GU!owQ+IjGNyi{m$+FqiDYmbVL;Cp}rwSQ^eJJ=% +42Eb$aOU=5v7_df9xQrH5WHpY!z40rUA2dgwXxxl|ABH=p}UL%n#Gu{EOnz-jL+^({vYeGA@QACCBd$ +KEfH=WW6spuG|I#1fa%RdGM?{rl~SJ>WYxVo$7)vA1N5>^SktM)MQ#_#p5R_iuh+et^0-%3806l#Z8i +uoyp@d4=4dBeWZ2Iok8S6_Rnd^0fvflixa|>hSvu^wq_?V^tX1?nJ#=zx{JjsdPW2yPoxT&ZPH +&wbyH58Qb8%abZ5*uy>L<0Ng8LJ;;m7%y}ph9IL+7iY-pGGEqoR^FC%WZjKTawSjwS0jQB;HE +}-w&cnHz-2CzL@Wx$8~BsD}i1^mbZmXW->qxEVgoznbrk73_2HwtMA@uiD2=G4M~y=y@L?`k^@4$O=A(loK;f;HLOv$-?d@gn&8YeZT +Yyk!vmUUNFaGYq_=c!ozQgE3A=XyqZ#u;T`_J*w@M4i(y$V{~1J!+loha3}7G2vY@XT) +z*%+q|1=jUi^ONzR$dg^MiX}U}F^jujz90P5hdM%e0q>XQede91Nsu8hk9$y_y7qne=6P0srzownojRY~B-PfpP@cBnE18Q3sy6J;>JKbT5ae+qs1Qzd@fbJkN +tyl>R9Bo1|=zuw~_jl4t)b4~+3HJY-0uRe9S2s}ty^(jvg$NsG372RGfNv*@;r^0P3=I6HtAwNBmNZW +5wUsgADNnQF++wE8nwnNz8VHZB)fL)us-)6P#FVVy)@0ON!{#K)2*P|`j)rq2b>6VG7I#6fOn&Njd7X +2E~#ngT6j7{L^ws+VtHap<79%UM}ltIpCsZTq_vg=b++30fYpEl(z`ziaP3r-nZaQt#*QU5n$Jfsuj@ +$l*{bbMQpc=;91SQEW14v{v9J?21vRhmA$R7)5rdb&F+vd&AVoKB8UZ8?$w__t4I3)-12 +O=Z)k~OBRZk{9t)gO}X0QMf>&Ddpoo3v#_@?AJ3C{gFG7;Zv*thd)AsLX$W%>&3Gq5FaCK-(F^Ho87G +flzcpFP;wKJ;+X^i*k7s~$AHdwbn!B~p$l+TCd}q#wYWlGnF^F?D$9_v9{mLAE9ly`)4Y#rWAkLHD5o +dQO{ZKn#(<)B*v?3iRSzj4NVOG^9()4!es>_K5oFyw^?A1F7Kkzb=LfPgqmSS0{n~`n38w`s^f$$z5x5 +JM6<%hpHXBi)XJ%bltJ0+zEWIuxTmIAMCJCt#+V%o{;6c5~9jaV0nuy&$87ML~)Bf#ra60t-LyYcd<= +NbylZ~;u5Ejx$A6Vcd=)WvX7*`SW5sN?j1iD68FbO(2{t*q~F^gZlfKO=evW8zxLIyes#2eY(y8+Cw= +}BrQalD-n9WgR$!g|fXQUgnQv(#zYMVPXqk14bvbJ7_jp^&fa6PR<`zu`jWWM+{3h)u%_MwDo364--n +YPnKjmz$fv$Y4E83V78-FWNN8TS=h)=E_$AV6=)f8g*9g&26P0KXbFU$olS>`fdXs- +G1zGUoPPbJln|l{ZV+fLE%ZD#WGYFwA#vk9b*j70FOAY +Gn@9>dp%-PlT{SZ*0LI9$@_Rw_A@BknWK0b^31$PU_3eMo^3fVw_^*+Lyx~fdv1{+X}}7h-j}@>e%#crpIaK|{x!-ThrI +BKsGN_3fa6(f5_2-_xa(v@@A%P^O794|?yd}nduHP=0e`y^)IDsMTU$)~2A;E!tt*LRpSDh(_utsxEe +1a|$Kl=>$Ry%k_#*pLtCWFUp8D4SdxHZ>Xj=5K9o;fYUjfXVA>i>Ou)DrvhU;=ceydvWc*In>ZxlC&ZhlXiuWA&aOxe8G8Ugf<7h+H +XHE6L^vfWjI?vADTfQN~c8l8~P3(!&nly=hvBgUg_tTqJQTHoag<+;WowNjka|fm0y +zd5IFO-y|U~9Y44b+2Qoe`mPp%#>r$3<*+l{ANGtV>ru)jrduKqTZgQ+WPECWdmmRwJ@fy8>0G}|3UleM +_<;VLf)0~4!D0N*G>3VUx571xZYNscaBmP_yP9$Ieg#!^ywn5K~9u?7R%ALjJ9n~?0f3l`@)?vS7J*# +XuDl3UY{-k`@j>a^~>zDP(Wc~e=m%1IxsN&y_TOQA(-Mtu%P#l%nRDjUO_4in(|+bBO% +?Rh-eOhuROO#Dt>`OtCukDlfDM)XY$RrF_qPgDfA4% +JDUx-4*9cIy}n$X_j5uKM{^K=M4L&&yQpOgnoGsA1^LQmyt)a*T|!J@vR7L54=TP)V6fmDKL*F@1B=)|X{u0$6`;zy~HnI3p`7UGiY$m*@C*JN-^H+IQyR%&%l$kHXJGDqD1Y5_HUInm7v +>*puO|1^p3CWbRe_0}%uH-L2>N+QEY9pOZN9sSVG!ht@f=QuiQagHqPPw?eRUms7Le6DJYJZBD}??W7eF<$VF_h*zQ`m)d##9RsJKW<-)NQk0wd?F_l%5@q-@H*#k?owAtS|GTi_%h?Cu73D$jyE?8Dn<=mFaDZo?U9!Aw +?rcs_eNKAIuzR~+BNYoW$| +j9Ui+y>P#YIoH`mNGpKX0)zela+IE+)9r-`Uwxn9>k=IsKTW~YB=Y~hHpP>~qgQ6mYwB6@c}{QDY4eg_Mf>to;1{++9=%tiy>RGrlD{IKCkkjYUu_v({% +1yc@EQk$hJ6q1iK-m5CGO1TI+9oCZxDP^HOkgZ5FzTqXwMJcumSu`R%|YG+MLi&oOvaf_o+aWc1NRpI +Ny0Q+~zu=Y&5u6YcRfD7W-fg);oHPD~&O3zR-AR*EnPBwu~JkdD#DOFt3!|8#}I6jEi|K&g-7#x%W6WAqC|3Af5>amB==G|{bXzX +=wD%roD`wnLj?MQpEZ^Mn2?hKw!kPFMKqGw5BQ^nb2Wt(<-0q1S=pZ&Bb4wsAKAA1;c8?=$U&ARAZls +4wj+azAM4^&)!SLe~70md;TfYE+B +sXU6qT0bI$aK3>^|}eMZkeQ|3hsTJ!y3@V|DP5vGnXZmda*yLGbEH&6C64_?|y<+lh~qDSd|rhxaEEb +mNvX)~B43N)*G|53n`u^%_1{$9MFj`uZN^={rDQ#P!!NL!;F%uU#YcHe!QwnR#<`w05rU9+9`XwAY`v +sH96o)Bcb{chl!U7UQN6MNjoTx6iJ)R)rU`=d_jM=tZMxg?A;{2VXyrSjbb`Of8iihDD~-C8w6blX!h +TluX9bRdcSf<1N#_C69-R8>?Nh|9GVT{ +})Ak3kSn5r7s5q82SM;_+9+{{1p4J!j?PsumE01#SBm39wXWYR5=ACg=+g; +R4qiyt2m}kiR^a-#D_x^BWayNYe!i~0W>QX=-LToF%+)^A$ZfU<3@_aB^*)!TwRSt2PTXwm@FIq?Qi! +)G;{3!9BWf;dMZaKp)BHlO3F#S(z@{Z%gu=Mc?B`W=Gy}1T`u9o&P=^w1@laH*k_)MGOA;6FKW4yD8h +0k?HEYB~a@7^o)?mW%5e;#m3&bcw6n|24`#>DRMToDRCs1?8cOvIjZ)cnV}n>FWu9p`^EYW}h1qUZ05 +Gk+`QKiBG;NI&7(w4W07eXbw1%X_kbac7pmm}w%P|1XoVf_F1dQ$FXc_NCi@R{4^9=i&?xK3G~@E$zQ +E{N>nh5`N7Je*J(S_qfajcRpP3^bEb*4%$2$unAk764yXKPFnkC`1I7Q)@54Oe(t%arm0vW(iS1A{kd +lQr8dKsMztMWK3|o=ntI<>`w@G@_lZA*+d?x*e-?B{bZ3edYBuStCh{*v{4LHo$MQ7fFRA0;xnD+|Y! +ok3Wb!f=gO|}pm;2fA$el3ngJs1u^s(-^;#D#XpHR}n- +gm>5ipQ{x!((K6ncu?XF%}x}Hjc;PG0w%vZx7n$L_o6+7;H +IcEB1Nx$ubrK7POP5I<%kvpquJmoDei8i37Myo80H;@f7j8SGcriErmpLJSjs8-PtM`oU(mOD^z0Z +6l!S^uElUB6zCfeDcCHPuq?dhtX)!fy|JbUyhB0k`K<9ZPB*6H@&6CME`Wgnr1uIH_%U6gw`85jLPwUjbSHU6D3dXw&;Ne-&J>!(ej1o{t3$8Zu11`2k3cWg +1kRdHgu^z&)+80lTO=J{DS9&mg-9~pZBE5918NeImH*zXOG;^d;64~`N1FV54TYcF3}tT#xrExM&6%; +cMQnA&~KCaXYAMOh;yU9?XiurJCx}bG#S0S-k#Cds*WlgJzD>r}OsIblc;^KD3w}v29( ++&~4}3}ZsPldk<-Cb{G3h+Z@qW2=VXjfnQCEX{xmW8&!HeO7ub_Y4J-+rJ))@1k&vuT%H&00xo4&^Hc +)u*YG<5921Yg)H0txt^w%C^Gs@(d^V$0b^m(x|bMwI(MfE#0tzJ>lAc$bPYZ=l`RQD@&QeboT?o|^er +wU*a>ttfXH<7`cvlejv$~dK12j3%DiysVAH!0kdYX~RyRsCHRJD>b +$^%}9bc7AT_H^6%>T*kO`)8>>dQ@J9m6D_h_Zd5r{X6l?8&6>t?*PtHzo*?fqQd%c`C4u%~Y2P>cf8@ +Pz0oK`9kM%z7jN@-5>0DIK*NK+y!f9fO)QN-cEm8LC-dXgWcQN0w@Y-&m-bW9Br>-+?R_U84ZfH9ApY +JGs|1{3@v!GwBW4Qr%jygXHgxjbS-({h%q1*d_At!bwiR+qa>m7{9t(4oiN3K2*k$s|RiOe-|RE^1TL +wp(s_!jWHxIx~DZOe_KZ&_$p=FFwPj!VUJ%D6#<23!U$Jhd?3#h&JU(;l3+(Rt3g3||E1Ig7P}uLu8h +%Y`s~TqJGvqpgLSCG_kl|Dw?`_t*Ou1k65Z8_WCdmPOLezh~o(dWimkjDwSV0kX-BD!u~mA~u7TZf7i +#i)!xG7b9~I3VA+Cnl5v08hXxG-VC>GNBxaAicrl(gLYqx&~EmVe?i6r9kJDp)xJ8{0O`jA?NEe%Ikd=)xQh%Bx0*#>EjoJ9UUNqqR2{eL+H)bmT+D?pxIZ#H?SgW+plD6ZU +6Gi@A@_fh`IUlxSF1&+YX|zpS4J`q@(p0^8U@GXbg>gFc;vnemY)vl)KTuaDV`w|eYcUrm{<;>`7S+* +5N4tDZ*CH>^iL^S(_n^VKpuv>$@?^Q1qLN{2J!1=YEy^oe4jAx!NW-{UrV9O=@*rg3z&^$)miNw!0=8 +s%N1V>M&fk)A$QqeL{qNKnmt30TBQ5+P-u;<*9I*afbCNm+Q*Eua7<(=6PcawH345=Oy7tdvT)Pqz>g +SU`T=aE0_VuLAn#~(rbafHSKf*Ob`F)UmYcZ$epu41j&tc9J@J!rVxmDy5&i@*wJyp{|&gG6d!nYsaC +Rm#eKAVoQcqeoaPNd)64b`jG&l0N$C!gWZkg9C|D|lx!?RW9-?#KFZYF;xp6n(tUSgqc#>{GO>_Hwbr +w#VCQ|B7A={HrAny+|LqiAMaGS87*zwG0PgPMoccxrhHS_ktLMmuKKQrIV<5$Z^K?OO1kK@kN}t0+?Xuox@=z~!$mbgFftS=X*TQD-6;*gPc?`zO$q| +>Oml1E4X(Rr^EGzrw9Sg=Za3A%o3YTNYbsXa=!?;>3YFrbe#?u!yo`q54$>W|d`5f|d=GZu%Rg>jiE9 +XZ2M$mO1ZNBSYcYUwD2Jrnf=wX=oF|0Pf9Cwa=WZd!YGi8}l`v@;7V|OKq9?5aZ?_BIJbYuGlvuga^ot~J{2H_|6SCh+7SixczyGB2dtog8 +9-J4nS2F9_W58;e4>~h@x6`IP8vh +qF=+LR!6>_C}Ucz3=*`f&HmJ{)eFo+f(OhFQ1NYS?i_)umqHcN0ZE^{Tv=`6cAis{52)^*KDVe&s~q? +FK3P?J&!A;M?X6s@$LO%Wp +e@E!sMhh`FO*ww1X?bQ8vb1d(s**`syq`C8`wCWqvI4u1N$;+yYMcNWwOFc*%$*`auWKODijBoJnq`| +aA~R^t3ptEgXk%2U*D-O$c5tFvW{gMxF%BEH>WgKd$gBv_HmgoNbag*mBKYm_3- +FDBeIQI?G@8XXmd*ZyYC!RevdQWWU9S!EoeL)#j=H1?{V6uz;-|5&FZc+I=;CW@M9@?3tH_$*P_diGP +i#Lp%L6xm)Uw}W|*77|)^tj9cDta3Jg?*7Odf0|p*W4F>i>k|gv2zl5ktm%1*Fd3a(2lK~^Pn>$I +hl^<2^1(efURk>k2^ZOa>jTa`6{%7u=0=m>9^ShdRW6H?hSa&XJZ!|Ql`k;H`N5>qem9a1)E2V?K(t1U7v;KAc6cXChJ5A-3%itLrSje&p6 +%R8XX`^p5-!+WuPz_B0m?p504+eg{t->lj9Z%$G)=nB-^T{~CC7Q0D4uNaz+y0@c^EhzI}{g@wcvj%w +BJsg(fyFty_|I#@3UBAKne%)`x>Dxm7S2yhJN9 +$|G+NWuD^lfW8_;_$Mk3*f4O<#L(rN!3DIG>C!cGq6v`+`PYP16I9?G*?5)Load{{ubSxz;m88DrP?5 +{9}e{;g=2W!kF8XG|-4d=CERPb;#eU6$TeF|DX~ueWs}j{bC63EJw6T8r+M{^G{CKuuH$Dr~B +k7wI99zHP7VDz}p%DA;s_I^IIG||_4QSDvE?b$Iy^X)>JWyU@E)}Yb}jE!3t4YxlUu`-2smqV{#j<&t +8MPfJ|J4!o91^3u7bMN<2Zaqsqd(**PpkF(2zK}l~*n2}@m&IN;8|`IUlYPCbR}5jyJ@n0b0Q~gs)pH +q#b;l-6wejj%MJs5}5=33Qrlk$+O{DFmkFhpR;63k!y2goDH9g1KQ4W~YMA=rx!w)mI@#%!2d0j?4ES +U$?$m2)-Od;COK>Jx}zZd7lWVAmpV{(T#Lk}%Y6pw1aotIycwrcf^Ge;km#v~zqM;aYM+Kt7_c_3-;7 +W+s{WNEjF_KD+cKR-jr+}lqdjllm8M^#)c(=IR`-h3dqMA>%6TYs)WCDtGV{b0jLKY(3XBEa=*v{>pl +V4iNQ|L<5soTb$1mF-b$ws22k;I6M`FQGmECY0#{OdS}L9d&18Ofo)BzbIyZUzTg +0;f;q|#QB$j^JlOpdnPibXqJr6+HyqRITlWk=a8GS=Qdk^!idbo?>Ns3N4{y1IJrAOPc~tXyeV_1f^T +Fjw$y#?8B<)&F2jGqfpI!BlKC?ZD)wC0C2~L3VE#2&=c)|JgSg*4qT04bwf)Bq&R%EML_G$cYnFoE4rmOWuHdk&buik$0ctyG*>xjJ#XOciDKC9eMX +DzPlXnE|0t`;ky}lHzV@ydcL~?@2-fvE8x3Ayeo{n`zYUCk9XHc-p%H_PvYGtBk!)@yZLxGKk_bz?{3 +7q8zb+g@!cZ4TNHVh^@b6TXPo%=5?ezZe>9sX7w5x%2%Z +U%o1tSAjV%C>GP4gH3{Un~th!rw2Kh8})>Z2Y9#4L=R){uU;P0}DZ$9YSw#Fm5$|sSl=HC~V+ +w@dy6#fF1lJW7hKfbj?<-?hk%9+8(Peq5Iw0>K>t^hxer7zy&;qOg*kO+i%1g@xE(K@H1AzX={@Agtr=F7r&ey<09wTSf2Ps7L;SISXF|t?atcc*tHVWQ?cj&7JL=!5 +4YNq*K(<+CI))SYq%qw6Qt-?cYuu{?wr#aGWcy-H&zktpFTae)>}>r#w8U_(I<2bUo1_W3{cAFMMWhl ++$RJx-c`o{(Wdi`7^rw<~VcYH@Elh3o4$Xq_IYv$0PK6v`#hRpiJ=^dOWY8C#;ZpJ}gUjcB608ccE&& +I4}?ES!URJEV9PlpS!(FXlLug&pIJ@hpCkNa)aCBCh^#J8s +LzmZm?=1lTA`qZ65nYMJuqovB&C7*$;{S4}@H|njAs#n(hWhtY7rg`bmcx4tfQ^zS5*A0gY#Ql2cX6e +rq_x$zO+^dIb4BBPJ38S5B^_{#s1b?mR0p8bDt(HDoJvN~SP`2P8WZEj!y^}dU?;ibazwzyHenUHdG5 +&uFztwA1?0B}@!WiS<(?cR;J@7|<+dzLs%Yi5O{6qD;lXX$&G5$WFhvo>eiDS(I?C##0;9Chg^dj#9E +rg$n{nBI^J)Vb-(XLq!{Sv<|Yrj0yOB%R#=}?nV1~^*qb9{SghVX4#D|3h~{?V|Cz5SD8ik8Hl@tZZN +(>`fyo+IyxDR(%&ndmd_meZo|miw$WuZo4bEq%N=>?@+$;~gw*;;O$~8ZdCB;J+>^`Do>_(X{P-#!w{p9@`v|G8 +~ri+$cBDkC?VFa*WLmAN7*FGvoK9u!>D)>PbfBvx)JWG4i_0JTWRq+KcVXsk|3s_-5be__l7tzft;cX +Q=s_=faMAGdonwkPLrqhSK3N4(B)0Z@xfVo~;(wex4ak7FX+q-zvRGtn3|i&jYzLa?e9Ji_}EgsGjOe +?#wp%S-$t+J@p=A;}VUYBkk-N!;W`aUqJhJGyVhpG{4N8ESXY2u-j7d68&-k(|n$JgJ-XMu=%3Yag6eZj-@B#`85$I7>D2%jQcM1%l>-D>5p +UVzZjLHWwgKL{UR+fG+ungNd^utQS@Y6yu47n=bJ7@az2c*yNIS|{MK>`_`{f**tfGYM7oqe(ryTOHQ +UomuHN6yGQT_((fN$Fe>3BwMq-JsjMv^}s=Z3I2cA3NJ7wZlBwi@wuln7zBb+SiWsb>@tNiSqN+ln9Z +dLzF8-8UAdcSQ;u219$pCkJJWkT8)N;vlCO8Y|jjBnBLo~4b?f{2X|bHCOY_CNc=kr>V+?P{W1Xj*sN +I8-C~mq&b%z4XJGiuRaGar**M+&M$kUq$(rF?=EG@_xNDUC5jOo!KhKD)R?)UL$1Qg3eE>JnAjCh{f{ +mT-|4QAH&$rWBqeb_MyR%Tvj`T5u<#(yvNT4ikPMB3=O>j>XG$(K5$-ih}-O`H%R*_N1DC09RJq)&#(7o`hezgV8&1YN)%>=+7p{EfIOitGF~AbM)VVud(QkTqC4Q7jj#; +OYcX=U%9!|uUP8XRL`-~L%TDJ$>5FQw<6p`SW>h@NIQmCv_mCj-W!;~p&6D`BavFI%m(#wc%(>N}ayA +yO(OiFn++%zD+lRE8Zwbp-e2b=UW8+4RAHTU1GEa3(eghL;@$wruETZu4ccmS{{kHzdnP=EjRHP`|ulr-;n>PMS* +>2zWud(?M{&Ye4J-lB&1g`nFqUzIbka-YBt-0lct@-=qLD&x3LY(+gRDYTO8lC^(QgZcE+vw|LIY8N$ +xzN`~XZ9e_lu*O3g6>PwBfeQv~{sYCh*~AHrY-SO%~|(D(5xcfnK(t|?1t +*0RO%lv&gwl^w+_sh*x?QBW{!(|+N15t5cTDtkwMVdU7Dy*(OmnVO;S0KA874jNx`Q_A>n`-(S^y+l-81MY19uLJ<2$3nv9=w +eUi#yQI!GujJ-))g&K=q%=u*Q8z|~!uHikogLRXbTSwG~|C-S4MO%!sa%b+iG*yIQQteENK@v-s?Qd9?>LMYB1M-)AYVJ}Zg`)>(?l-!6 +S4+{UtHj4k@N;HqwIsF1d~cP9zoVer_NR8cA0k@^ea8-7Fi~pN=g +Ld$)vqgO~V`O3P1?P$ev+d}1=?f!y7Ngzi$!HgCweXjB16U`vnE^aGi+w@3P)^+n91T8nWB2hZ^?;7E +hPLIMnmEOL4h{o{_7T5oP8U7LIIMf}S_gqkwZNmN*XSFUYAJ(*pwTUw+ZjartzAwW6>oM*Dv^(f^c2u9G4gTeGxaP@mXltKS1PI6Ag89l$vLr^=l&p +uCyAia=*Y%rA>zd-(8E-_LYjc|pKJpu-e9pPreZ25Hsn-a(3< +UM`S~&MB=0*F@QjA#ljO-X=YA_-I6@Qs%@wcBoa$p96X|P1sJ%QX`w+?;PShC6o0CSG-2*z`Wv?6xz8?YwaZ!+Nhn_s`bJ~_U;Hj;U&m8JB0J#_m0n)+);UKcX7+bNZdfIb>+>&$@9 +wkd54f&IA56g-!r{M|KMCRy}xI!M4QNGThgAPe|NFhHdMG8XE<|FG44?}{eR8x=);?=%F|Dj?<7t&C; +6sls54vo!X=6P%0wC8wZ7@;`K$jMygzqIDVsF#nNx|IQwGn|E)4Sc*(>x_GA`gGv1!+g>2*0cy +N9qIZYR!Q@bA04qMp8pn<*!v3}Zfd3Y6crdm?y4p4G2f^1^77_|7~1+!(*xKH#N-nJ)99&XM@Qn3CjO +GQHD=!)?5u=KKiLg@DIC%TVDN>@VgXlQGsE7)P?qvolif{)@^EF8bZec$XOSu1M;$MTj{GgHH;do+16 +98Q=0CX^tANqOC^{nei@FUTWGrj+xI~5nzrN+9@6fj^)|)0R2xFqAl*}V6q;lwlZd=9%!OpyIa)Lhrc +iiuk&7|UROQ;2Vj?)eJ+`d4P%S&Dfu$2Dz +%UbiM1aSAQoqgIW4y}ajS?%7`|8+vaK%5{;y1paNpUyY?HVCV>*kJS-)aSqeYC3lJ^y{umpQ@{BvoayLu|D0Mouaf0=7ava_D!j`rCO29#YDr_bE|i0R{sQ)Ey7 +Z9@*tLw>QX_|GjU~fTlBU;RPkBx1_(f%IslM3Lkaj2as=3YTnL6xTtyDhKf3w|)**{lvdEX{|s-jP-& +EZK*kv!N>jJV?*Z=++(Jnri`q6nWCg}l(=&GC)AwtsAo`)6t)ju%LdK_obc +`&=c_F9&Jh;{hl~K%BGRCCd*OEkCC&6jACA9De&?pmwz5%1co@)7T{s5sTTEEX9bVXe{LCyhC#NdL=i5=R(&%qHU^s2s{=#(L +%&ebHy%J_-BqS6Uu@P&bJpdDhXVojC`3v{m!UlEvgsi>;P;LE7+>L%``oMHf6dy1ZMEb(T$$b=;a>C~ +45bMS%fpuGgz&I%-AHJZbBgE&`Pr&eKdiP|LY%Npot@Z_Nar-IL}J?q102wG%j&szSyQv}=>+FoxhJQ +7!A)wPaam6Zksyf8rd@X_J_4{GTAd$IJT>59cOqSNR=?caeLt2tQvFMN>&nM|8TNsidJcdsD^ie(6_R +yA1q$c|wuNZ$9v|SJeG7V||nNNR?{@xTRX;+PYp*`#)3Zkt)J7MT4S2E^jq>3z@GGbV%60xSDY)44*V +F^I6K=T{w>-d)(!1RyL83>cf$}7r76P=a072ymw=~b9t_ZaIW{sIHZZ-zY@IU)h+{Xa2&s9OR!F}^lr ++4i|jh@SFWp`CAw?vBETzbnqlSj#d86=~PgF=e0U{{~<6j^tmPDvp0z4} +>8@^x`|e^>4f}FIdjJcPqShO{51G=z)PRQMQ3hI8&26;|-e4D|y8nhvBC;Me>mE*4>czn)6e6yz>e(C +m7ahV4faG0S(AasTI`_*sQ|@%lB-eAbWUE%Ib6lSn&<-U%0c6YWr +6xS4qeY)UT@T)w+FL(87SvxMheqVf=lpFV6I=@#=70r)QY4qk6~n4GhYd+7{3kEBNLF=0=dQAsJuTH4OeXPTZa?0iKm +Arc5t!aK!(8#j5FA+DP8%OUVbL^V^NkcPWQ_Efw^1k~qbfD?HZ*4F0Pdzj@6q*~~>-V-XFKo{rcp#G| +JsFAg4C<{S|+KQr^r71~4(?e~9_D0&*OM(y}}7wz-xtcIM!djxq_C#nB=?;vxRIUsv#qJD<8y`8z`q& +$r=U!Q8+i{KmqZRnBbu{O1?o4i4iIJFRK?f_qI6CD*hAV1mi7I}}mB&}@-CN)(!ZqA&$;To~ZB7LbvL +yJTBHaU1F?`mcFo8JxZID>VeZ@_8BQXDSbXgBPCz@s-X_Zr{d{_?1t$`42#(Ku^Q-|)NWM~lA?I`6HP +ACJtN=k>y=~LMYta5O^u?SuyvHx8nsO)gmKy# +a;{Tt|xRbQb%eXv~RXl@|k59UjXB^>~%UA=$p85g6coEA?Q!%_sa-DZlP6`h1!g-?AF;{ghzhB-|@ut +<`&voNG0iU$KW_nT1x09B9KT#aG19EM(rcw7+%)DM4BY8FcI>*3veEV-BAGnvgwFfabsk3+yeEs8{*; +k;wlq9i<@n_BV|1IwXMP&`(bu(?EwcS$wqW>{-XYB_6PCLZvca|1`r%ul>Y?*kU$9y$}g=}k&VT1A*c +y4^Bt%F^bNszLHS%!R~Sw=VPBjWqbkM;YaEX%w#e|aeq7j5lJN^buY*HRNbfLA@`g0kJ6*^EtJvO8}O +nQ{Y&cZd@jCaVox+B>7l#P#$8D=52uW)37pIq3CbqghB955qG*1CSj_$?3>tY9Jkt@-w4WL|F3JGcZtNw(mb#TfO`i8fo@0O+9T5F3x08;7!m2OB0oj%$C4h`GVd4baw6*$5ifBTLO5qHYsxi7#lveZYuWz=|`f9WBwNA)UrlDWM^K)RW&=yo1Om83 +EEtK@`Y;qbM4q4i=B^5=)f6$D787YmGx*_S3O*H$hleMNuL(Xxty{E=(OrLbxgmF;O$y6Mdc4PXL+ZV +T6f%<;vJN6v-FMZy9~eApnpD3#P4BkVjcNy#!Fj@^1M^`OJ1Auy{P2fWU5=_2LU_gKfGpD?arW0Js04 +aV`4n7r_*+S(U!5OTKFENf;sYCOg1U!BvlAVV|N?jLoOnqCyFacxd7@0F@FUGboLBX{bV>@mYA-0){HeX8?ffr=l3_ +awZ^-}vI7{g)kceyz)wBMxJ{8q->GRELE#*nI|N?YQ$QD=d%MsL}lY(L(p|F-uJ){euPW6+LqQO^=wS +M}Ro&DLt4W$kzi--C_JHDeoOyq<^e)erp!#D2M ++US*!Gcb$aP%VxfJRF>cSQyq)~M$cRa_K-yl2z##TE@vO$!3ykH%_lz&Z{T++bJPXIZqutN=_b_iiM1 +Pg54#<0-WHEeB-o;q`=U%pUGgrjB;8h-TicP_()JfsoNfYUwHR?_;yiVm(Ww}Era~}VnPS!)+IGv?aUmL7A&I(2Vjm7gio(9Mi_<^mnLx3C`97U1E13XxcW*qr!I>^p1Mt66O;F-&_5h +$|olG679QU@`*9$Iqw*%FGli-RcoF(FDEH|7Iie#1Id_@sfG<`nyBx$$e5DbEIE|pco#O+f8qdW=UP! +feIRJPTYlFxA9(@!+z&XL&wIp3y~C)dH;Dr3(cZD5-Ua;u+8;1an9#oVbI0(?Lj&NssMA@Rr@u2`6~! +r*zJr9l?OFYS0qghMA?LJmj-R0ot=5vgC{LtUYBuKsfZ3$o|8coZY)il6V%4D#&Z*FLd)^lTXS-$9<+ +&PokOt44XRYhAJ3(8{CaZp^xi +Mg7u`?lJhE%+nvTWZES0}M;a3p_n(@p}BFu1Q>SCGdjlounm~qtAo6Ie%_X1nv%?jcUf2ysdgCZF=5> +%>J5nme&qCSeCJ{e7$BbcYqcyWeg?j`Y(2CPG`xpT762hNV6pY$3V|`elY)EA-)}cPOHa$to#kyd?ra +O?gU+?uX+piwO!`Ybk?n9yeIVgx!aT-AMH^}jQ2sjx80^_B6VFhEy1x*OLRD9{W&`~=P%idKz9dGruM +e0cJi!Y`^3$`ay>vB)zD3Pfb~vbZ*p!Wx8?3sF)CB*7-yB=&jHRn+vU9;o_~Vh^HVK>A&({S3VG}20J +rD!1uS%?mpmZmw~VSw$b>y920P`xXOc>oEs&Jd6>)53nmCuR{5mIXa`@w#a}F$78~iFsU}+$@!Nxw7Ar}yq)wqtS$prj%HMALMBl@|g4~-f(q;U;oZjpf(3azPe;#Mv!rSt963;Aa?mZM7v<9{ +&i=Ms}Hs8{ZCZB?=T-lFuPyQm@R>(UQ%(*4)r`g0O;#b+FV3PR%GECNdFqm`!CQso1wZ$qfay4M_mqX +#UY@CVf4$+oqKK9uBK-tY=@{e+c=5^iM)sc;}`L;&ulE4&^&)gZk*lQ&n;7gl-n$4W-^pDEh^9kQvyR +FrP@k)nYeAfiMcOjR^393KZm%R4n7C-^I(tj +KL0rCEW{q-yDXIDx_=*iaQ;r4%4kwBFE{y5C)$V0ESEGJt25`h;fs6#)AdXNjB7S`A7u3NDw+Z8 +knD;jhMPirdMZtM)C@g*K`%*>a%c+q$MdsrgcRtThrpEu_ClUie;1-_;(Ld+4{JIV8@_#%N&bJ%?f8@ +HhZ1VTV(${AbZ%Chhku37Rdh0iKCP4-{4BlxE@OScmitbv(CSy)!5#I4Nrlpx$S0{o2lcyq|sR*LS{=AkxAjv94v!uR83d*S5BNB5A4@JfIzW;`me%% +DeUIoo~<61LO~bPuvjDhQpQDT2w!MDXO0>QT=qua*2WC_`mG7ukU1=3-SJ1@~x=PcCNh`E|B#>|7bUP +SWBqeG8C@lUN`som^DOuPd_+%4S)X2aNF>~$QtfE7+FKokpbFwI&~S(y4Y$f54|2PIGYT)5c|e{*X2v +Rsb6$Hoa89qZ3k{Ez1dt;hte__3rFPk~ls8noh1qi98DU4N3=k1vrA0j)TP_UFXVitmo16`3E3R`h=Rzd*^`pLn!l5dYVJR#4t0tZMO%vgbL2UQ9IT#dKSQUNnPVa1Hm~8$~P3^+S +8RwvJxEjiCSU9EhynBX~XsoFSf&cLEPNLMzT&Y~@*RgbQpVbmCTLd9tIt^+&6p?W|DX_?`j^{$eKR%R$KMUNodLb`h7{_Azph(+< +-6E|TW8=7j9zArNwyudH&?AQCQ8uIw;r<6YvmF+PGdNS`+&fTG>K&xMA@91D_Iz!sw0kbV?`Gi2TYwp +4{K=oQji$5DI>fE<>Fk8a94+J18I#UlY8^_W*5U6L!fhN&s@7C7XcgZ7w7AL-V-LTP)tUVU=3Bkyv5q +k0g%tF?VU2Z38F0>?`Wa_kqP@I!k4P)KOt@O{o;)V?%WsIzZ1R}Zi>@xB56dp_mHpUjT^84Vw&$58PU +T@8bN95gyEK=CN&opU^EVe$Z%q1eI=M4DxK39&WtIzHW3q~GA6|Eruj|1}+w;Y~J24=&JwxylJO4T4&DqmrdzQE&#_X&AhTtZ2@dSN6nlsKgHO8gtpH= +ui8tLc2=&zwSdn%&nx{n`*`Ger4N52>fXd*6PuE4@$3QrIO5aZCv(EN{L3-kNG{9lJEw!@R$rqtmt2K +gbCyHK^~qeyGngMh)O)c`wdo@8S?qD<(!_YZU1!vNFLP(@vbgJl3wASxRq+h&1siBL=*q%~Z{oNylZ< +>IC!LzzSKY4{w+vr9Vh3DsK-o$3zZZ_mdpSBs=ybzoM*7Jt5Wcl%RJ +iKk>!(y`fmU}|^&w{Ubv`O7(Z?i2+8j0m)=B1T!_hpP9#yhjB_;SnD9f^#gPo2cfQkgrBV~+TK?j03B +C^l|`nQPVmeDqp+q-|r)^AVfk@!&>#_D>V`Q8{aQKa~D_rh%s8x9M`>3SCs+80RJ4T>dzccQ^ +7bfOn{ye(88y5%@2P1OGjLeSi2fCvrUa|MssR9{%P3AHqK~2L7Kn;r~goZ?nV*0(>b +%M+%KK*jIcoyWuSD-77-O@{cb(z?*cO#ngK@6-Ugg2g^cQSVIplf(d)kwZ&V%jpzJ@YKLGKuIzTZ08O +M935UI@4GOl-Bd>Z;dxTz0_Mn+hI)`g-mEVeef4qAK40@i_~~0$vcjAYNE+i74JRyx}5VKoJp8u}oNC +k=13v-9^E)&~Bt%G_A}^vHJEUyHROj-!jZDc&QYvOieMVFs-nxuu^`{GxN;Z-6L4(2s_c4!<{2+-!?tk2)0OT +%SPyLzkP%I{$=+*_E_>??O`^5oA{>f66BQ=_pH{5Z<>}L6KgDw(jI2GmT)8OVTS8XQvL8u13X{7!**3 +CCUnC63XRYYcIn(P0{7dNI>&q~zRw*6?~>KGdFuRE`VJv|e~$Isb1m`M_4izRO()J^5PPhVKBRAV(5D +RY!RTeY>{)Nz_hiF;dAdftc|`OVP%ic}hAW3ZwGn)_x=yD!%w#ty|@oVn{~zAs8i4ZiO)wz?+%V!K^AHMpmv*th+`8oqY>GH?GC+S^JA +^=RAiEOcB4BY2%W&s4WVe-C6~`^C>o2U)z$*P&fJ_c6R)`*mv>wqNaV|3SX5(`B&T@9^HR2Xy-LBiDB +WZ+yi4tzY&R`Ly(E(~f!Yo1kYGagICO8}b***dJiu5KE`Wf9b7X|I3f;Ih>Eb5!)uO&;x#cpKa#n4fJ +FeI#R`+N#dJLdA5eZYr7%O|@rJ7L; +y3FTATB&dB;Bh6k86<Zn+Hx^0DgnPg@<>C-HL|4M>0Uzb1h1C$zE)O;W@Y^y8~hA< +{KEYPpU3ro!j>KQx4GA|`$u4FTw^3{AO1J|!^`Zt-=Gfe)O9+5`)lL5Il1ZDFdfw}FW@&*pqH0Urm5q +0++z#VWYu+SF-^FK*T$d?o3Whjhd#<50Y<*>G47Z98^qlNamOq4jJ!n~e`g+l)zzjEcvc?QnTHAGXH; +LSukc?B{xKduuiyjc=Ng}@*M%Oy_mBe+Cf%zu|F&`&)Cb%b<5qYczj-%n#QJca_Zvdaqpmtg=kH*%(V +#QBVVEMfFt53VL7ADj=PSfN5cfoa@Mw#jz1ZRNbJ=s?|L`C1PC4MKrZ?V%K0^_d@7_&v-+0kx!F&Hsc +&FJJBld1e#`NO4$UX4O0(V^Fh-)=_DdPUugRXYrm4mJ^1+4#|)An=F9)Abz2t3OU+>Y;pxYojQJ}MXT +r5yVJm{*WL_$`;}&V;)K;+%$ScYr?);=Ud0&L2%XQO-90y^XRq4C)f|FG)U&>&!u)nuD(X_x*5hg!_| +j;|~+Jial0wyc)mr^jm4%FkuzDI1$IY%aKVNfm2V38FtLuEZjn9aE{=~Qs?z +@TaE$aSVd`~xAceu&T?kNYwnL<6le@au+4n7tG`k%pXFW@<7eyQov3&F$-UE#jO!**~7_(Ld(jibU}BL)1V1 +UhN-4{rAT6#)B6Jd$abzWg9}>`@L}vBe^L@wjpI*Q3cr-xUckHP4#oh$ns{B3QUp)8l#eP_q2wW#LxJLBFX8W-bqjgE6Mge_C*J<%VJxi +U`OkYkLK8(T_`m&LDeqQ;%u`hc6mgf7S&wqOTzUUDzw!>4mj+%a^(C@dyqF*Zab`bs6U^;KQGk#k%Qr +hN$I{Kc&3)doFyZ3L@A-?Z^i+#}{heThL_dOv!6SrKyPlNVFo8rD`?(yL~493?DDs(LnWzu=QT5UKbJwS9Gy3p-s5)&w^(EimdZe +Du@%pVlGu(K&cDifdhhNUdjJ^bJI3|GaNfP7QK=Hj%hwZCALEF%o^$>RDCvxw$1J|5OBJ&x);rBb>dU +m|GU+nXb?-uyYc`lxL-DdJ-KYn(#PL78rqfXa+FVM_>qZ>g!+PnN{)1$a=z6`&Rc$BpbVHlCUCuG^aaSGNiT*l*zt7%pdKAZDQTCbd#D0Ak4#SjR#l68rIP8PrH}=>b +eX`$k58CF=Yf;bW=*^0up~5rGMtqeb&WXh`T>);mpI&WmV==a4IPUjX=j2=x`v=uvS-`ew@By(mCblz +jY&-(o@H@W^I(DGS58iX2Pg8<>ZRyy>8LjZlN}TgqU32nd@k~LxK2GVu=e)w!C8N9x?=?Modn@BbKX^ +aHv4vjSHiS~2IpXCX=sRjC*CD`nylXgs?ifcN?nw*b(68?44WZAFU)=w7*?#)2w4?Y&Z{>dREg#x(`F +`9x5cd{>_8N4<{kHJ?#r^Mzy;D(NETg!#>qt}6>!J<{eZJpM-;?Ta?6G6S)NlFxaf5!FPW_I|aJ6Zq$ +t^x!4mIB>&fG8dFPuxdh&Vd)IF9a@b)cp3JCR3KPl&!qE_LW^qoHT!F{KrZk!3vn)3?Vbvw4DzFRlX!f|51u0dgn7hn5~Yjd% +`o7<*aP}nNrW|ixY+jkEN+vBz5j4!;Sfm=Nj{Tk?(O%I2DB(Ap{pal4T@6|d2*D)uwH;!)8ouy32{m? +Jdo;7}KN!epD%lG==dVto8S99un{+!|d%^xbt0i09)0NzQz^=9jqPU#BnRek?v{;d+;@ALdt37=y(KjG>pYxh5|^->~YidqN3JAwZ>9SgxRZY=k>m$p1> +bTrmw=#%W*=W74%+$Y{yd_`NeV+i@dx*mz`0i%72X$+X7VaY9t(a?UpD@P>6;Ax?*#W)^nbOf2KT1;;Y!mR)d4IGzx~(;6yvLgX +GdV5tw;O7j2_MaHosd`L5~8UPr`3E{2m&xq(`Y2=(sZDp$%Q_J;o{vx2zv~YmedxM~~(HD|#I9XY->1 +ihFdNnHshO?q5$%3+o8@^NDF;jo%f9g+ln`fR#Opr{5AbZ{dQl4EW(W8G2o9&1vW#SMJakM!k3qVeEjn5QRQ-|d$S^&dAE1N|YaeLsQN1(o$jVl1zE#l&+y-=mlG$V5?Ikt!V6* +VZgx3N=&c4geBLkFn(=Aup&BE`PqQFlZML8VD+bCsyp})OHP;uU$adggLhcSgcl6*kcoLOPBfi8jHW1 +)^7PM}?H2lz?68gy=E#(}VZVB_`A+|3Js!=O7xr63VUN)CIbms-)~s8?k}<8*7liRWuW|nZx +aKspuR^-IQO6pZZmAwoGH${*sk7nQIbr0(OTuq_t~?s~MF&ny*gET?Z}0fb2}eJ8^RdSsyLSTL{D2+u +60~H(sReg)_n4_)jGYMn=FS#!fLS|n&^Hst6_5OJ^zXK4<{iS%7SK-``>B8JhU9jN|Frs*ZBu-ePC@l +P{&xH}Fic6}dtZbp8DbA^rC^C)MoIDy!S{w@`_a}QzRzoG6y+G#Z#5|)c0Z`+-zlAg&UxJ%iffehxK; +$^dX1@|2e5v +YGS=>LP4KH{_zX7_`5_A5Qqc=21414?%_-kB8dM@lzvdyvO5jpC?Ysq={Apu&QT?OR94`snT5DM;@fL +U!>cyXeU-t(W+Pl+}D4d@pZTee`AgTUejKGJe)FyhGKrVPQdSS&yK0tb33z>lW0Cbqzw>MXQW`;5-v!$ECCfiZZ0JaM7+ReDuCZGiRHQuN_E2Y9hGy%Mm-pLI*|3oNOb&gI>^8Ff +RQ!G>J7I99^-23u}N-PQoMaKCGWZi-7c)TuwkpV8FRgz>|(Ydd~-iT%iG=nsZ5*ypGywLDl)E4x*Vowuh5?0huEI>>v^AszO|R{`PsPs5$-d2|FE3*e>1LnU*_Lr)42ui?J#~j#IAts* +6fvXCi~N_^Ubg6pSqoEAl^B2ywAQ2@S8*)GN$I&Bwwriruku-~=8o8DL4`eV2+{vO-|{%T9Q +O!(~Inr^8d>QM_e5bp-p>ZA7anmIpBzrs7kAL>(_(tTJM>ujH{l!oRiJ>b5ReG#5pri8(LM{;Ly-+|o +QzA*mxT;udgv#z2=}(M)~ +j8_E_l|T^BNctp)mh)rL}*eg6$MrY=C}ZHQ3BQ5B2Lj?6j&!C%BjTbsTnD)w=`SGjeYa_iea)wND7y? +b`N8*v+5p=1+F>8-_MJ?W4D!16!fIdd1J*2zu$EJ%IG__F!$3tN$0~rT1!X{Tse-y%xlR@Ez<^7BXrF +GmgS<^q+x#jbnMYApG9^Qx*vBaifgjzJ=I?fcqxy9v0-w-Syy}HmZ;rMlon7)^lEupq+q +mUm>O|skx84bq&JtOgVnyzIN%#t^e{I7wye1*Y{&rQ0FHf6Z?}F>seZD0J{=y%;?cb=U3wx4+9&}Uhx +rPoTybqCd5~uTet=HKosYMb&6>4Wmnz;8`VNT?;U@}ZH;3{{QQWqAh$gmUVjJM^>uHyz7G0;XT3T{h& +?=UEr7DVKlEYQ+hQCj9QRCx-^IPgJk+7#Xv5@_gO>g*78DLCEv!9|3LAaFl`PmY0zo;N4fG32a$LHWO~$$t}>$tp +~Yc`W3gtk!IfR?Y3p?(ehu@ +~~{p$P}-&YRm8$TC?`}bM&s=u&74dQU0MX%wzDE3j~`93x{whsP&$>nk%f&Ny~EaTf1I`%fMeVWzZc; +yuxw4KeIZ42#q)r0VZcloOI@OuD$>)@xJSK=-9Ce-W2`8D-$|Ni@8&Jbn43-{qPf9O8mDWA8vpL_)M4 +SD-S&(^k3Zq9l2jEGd)uIazRctnps#c$2?(3kXs_S>TP?e~Ll_>L^!5BygeH%wR9_5p74WIU?;amHP^ +rry6Qd~JMxV{t+oT&L^57S9uccYzW92DUyP+Lj5%{^xPcHqQgxCmiru{;U<>6|ge4e%dy+{%LWRR833 +gkdA0-swmO1^|+sN2F;Vbd?;&y2;-#jh1}FMM +7J(zU%*qziP$?>=!(H*To07{?vLBK1q;zAo<_#+8*rHngs=1Sn<#zUC50EO4WFM>+UJ7S;<4CeXBRO-)u-6=5I)hkJcx`yTErir +V-!1>NxgQ;-W(m{ouYop2Mt*x-lH`D5Fi>TIfG_Xx*^#p>>M!N}ON!6gGY)kRf;21v-IL)r0KyK2urq)r06him1hye<6t*M0=l;$3tx5#wIvr%)> +sylCUkR9wW2AA4GAq1iHSq(~e07g^AH<7!S_^UWaek_&SZ)+3KlOqC`DMrc?<_mn|A}R1mRP^f*CHgs +y&t6!%g;HlkQ;|XxsqwcvV!@6dC~rX;WjKc7$5362;{d>{J$FrWeC&0U6XdGpY5%9wj$ZR+O1p+__HZ +B64$3fUCwAzx)%4cZv%FD1?&qIHpdPOz&H~$E_&Bf~rVG;q_SoUqOx|I>Ss>pAfZY};Rio2a^<12xY~76IB; +CL+;`*p}(e53LQDI6?zJ~$sslB?XX{R1&I%5}|LYKCI%yAc@w56jE3v5>~+4!Pm4_j~ds~Ug7D`0ybq}8^Ht?&52DBBRoKhSyojc`9r +Q4Xg94T0aT9&aSB>rt6FG^`?V7}x>R9`40f*~PuBz@O5 +5n#z1!c{>u*Keob_i*Z4{-2w7S$>tScf9%Iy@M^QY0q${5a-ED1_!po~t^;>3?#FL?V1OY}ugNdHXMS +xcfjBY$Wxa6s8?Nzd0lx-FC+aW_o+XQWvh{e@Xl_SWe&TwhHMqwb&~$}eto170?0P;nUai2lk4Ccee7*#$C^2`M +`bi6|rxAE`8UA-?X(>_swtZxoMcPPfwDTwP}W7hdHHbevWZJx=sjqTcC(b|M-5|KOkC1^iQH~GX)JLdIQlhMB|7qAX-3lHPJ_imJ_Wc`T@}zqNj+q$`Uk)sDbDpqESS +r5uH!eO7u3OYlvFbm*@(j4-u7DBIVZtqRtFYe*Fk$RNQd&AWt +l>#z^5_L43m$LF>0FoCZ!*nhh7eOZY2$QSj5IHZNa$1uoZ#_=uK_^9)yTT*%WCE+OnoxKzX6MA(mTIb +naol^R@4xHaKpn(%dmTM=&1@YxAZ9C*T0zmOB2@r4r(ARMI$Kk^pGH`QnNL{eeuf5TY +y9VG_*M-r(BPsIp7y>|!!OmyzfZ$or@omAtgD+_8)u6$bH2xbk_)iUHCpG0=gAFG=%V+pW&+-yQxC7xN4L^f +$5aA-i-3V_Y+?{ZxhJQ@MZ`ANZz7)7OVI$#C89!kI;Znk3gv$xvK=_a*d;{Tj(q5-L?d?t2N80a{r+> +pKPyHsH@@#*SPI=lpgK%5I1*bgqS$E1a{S}1!N_!FRBlSJ)$qy$yfN$?u9m!)w0lL&gNKhF(C${8nHX2($ +ujB7i>tev$AS48Oh@4g!q>0XM+?3ivI6U&hiLrzz8F?nnA(EX~wlG-5xN;VdpN=jTXgmSN2YhQZ-9IW +1X+g@q=2Hs@uaS!@ek{+42XgH^!25HqNsbm!S6%mo3oJ~Fb+Ii^CZ(~y;KlMF+aHQ!Na=elKJSukBEJ +jr(tZgC2m_w$IqS+>d->bE2X{aNhJLa;=(#Zh22WtsELHm4!aR3Ou7&NcjnDq4Tds^rGMu1XnM`31!- +84cqp%yt^=W@n+@#-&lT&ZKgb^8Wx+yX@Qnwx!G>W0dF#$l5cXFi!4re{oH +)Hm*eY3cTIV{zVGJj_!sg$>bo=7ZZ>7RReQBPGxX|Z@W5I5>852S3pJyv`?&GZqP(O+yJDDRc4XNt1# +Su&CSp2bttN-VjSJQ?gkQstvD;0>6p)iZy>}-IZqiH0LJ!Qrhqm>onoK>F3i7Wq*@;xl#AahyA?^b<#6ezrc&IdmOS4Ap6dG +=DNJd-OCNjaK0KneA%^^PD9=0H|O8|LVwb>t(w8jQSIeR;e4CH&C%xcYe`=%)`r)!=CAA7`|r5_@Ada +CS!S4qYTe!Y!P?8cKFhkJ4KK0I&E0+M#J#Sy)E?+tpzh(_FKvC)Yy7(Fr%jtXc5feKJ=ezV%bK^3dwI +NGJE7IR!dZw6oQx?a1XZ-%mFs4w$uALNgd9Xe0EQ-kF=g{)_CTqch>SQg7?c4mgZK`fiwfU($o` +()_9CfW0|%nk=*mAgI1yF=d5jMINBu)bc{2??kO7YCu*ql +lwUfq`SJcL&3r<=M(@&hJmoOLhjx3qh!pcYgo*a54HNAH+SZqBt|#h8)Sq|(ZoEzu7HK< +@8%Sto`7PW*Y^VvHoj*HuRG%J^R7XBp92qJ|#?Eg*V`sNqLJGl-TEttDz`AU@FwqJ +bC5ooEHoYNEA78;C~zB&Z22W?_-M{zS56aBl-DYf>cCBNKDNof9~D+{MaUo@K(6X0$*0(Eaz#lfQ!vT-q8OPdHIk|2mEj4*7DSN$lsP<02E^|1K$C1a=E(+^1;)7 +!~Ui`lws$t3q8UQ`P<=(!6ggy`1hp;!#lWZ4!B)w^}h-)TCa|M$qK{WYU*Mx%Le~jJ=`%S`K)f)e&|{NZu#IBC14H}^03#oNWwj9uCWx?B*0cdOW#^DdaxI>WHyg^Rr}n+}x+w&1QEz)$E*OvKE@P{!-lCeaSsu9 +hQr=!(Eoinqtm!TJmiWH(p-kFClh@sX*1r%^ios%!#Hvb83Ds@a}r +v6)c#46`#gpHCoU!5?&&*ld={rhtW_D18zSWusUEn+!OS#lus3CYuE4e=ox|f8pxiwY+Qp;vn~~_jhM +Af1W;8x&0}B^Upo}YPUb#-~5wd4gcNd|8KuaR;|9{&b#iur}W-6Ywx@Nfd|*Ee`v$ThaY)#(_@<-f8x +n4Tg$dR_4G6Uc=n&=&uxGHg%@9Xx#E>qU)!PtJt~N16=jH9w%2)5_A +JDo@+jfENgMvGB?9@4=OK8__-Ft)?diLtwr|%8@!uv-I7#KNd@Q|UyhL0FI>c*(iW5$jfA3ecn%FN0( +=Pb;%ELv>Mv*i~ou{)fFOP3WDFTZ*IEep~!TIS!nV&!eO|97YVzdQc_u>E5u#!i|%WolgfwCM>m5@#m +ON=`|gJ!fv(yqkpmTju|du>b#v7Ep2h{oTu>1yo#ry1)77Y60ET{ddx<{nIy-<{&PAh>APFpL9hOethB-X0he4#F#{8Gue=ar@()5ND +SP<9q^yT$d6B)q>h*&-(t(=*gRm!5YOH-=+h+P$tIdh)Jn8~sFP?B(G^7RB)X31CZgp;cMz>2dXVTLq +DP4yBYKi(Ezvrn^+YcaZ6JDyXk(KccM|JD^+Y=m?M76}??ZSX(Gf(AM3ac75zQs)Bw9qYgy=e=Ig`yW+2K9J4z!p}gZQTvT3JltLY87KU^BCvEHQs6n` +9QRR1l`nWG}{Wc76lb44gS<_%36&;Cmy;;4e=ghx0*Jp@Sy$L8jB3mua?>o*+LT5yau{jPa>BW(L@Kh +H#%TV{mr%;9{9fmJQcH!wk3#cvDHz8MqcVQvNp}GipHV-$Z%C~D=Wqd9-5ik1m^fwKVjr%vVOs#v>)LRX+OeUr2PnoN_!FRD(%&g+6QS5!ri65gnLN)5Dt_26E;Zu5bi1ML%5f;58>X +@K7{+o^b_tY(@*#YnSR3kWcmsBm+2=QA=6KIfJ{H(finG_sC^(DLU=IYaKgg~M-d)DIF9g0!fAv@5zZ +xiBjF;#QG`ngk0!i{@EF47gvS!DBpgk+n(zd|#|Rq<*AtE*+(`l0curJ|K!hVD|5%wosPPjGUO2VOptEIk#Yo$Jf8>Bw6gBD8q5Dp~ +lOV~izkMIz}{)CN$TN6$q97;HY@L0kHgca)GmB{!Buaof;E|c*Su8{E)u9EQ+J|yEOTr1-z+$iIx4x_ +#+$tN5_*q?AX;nsws2!|4mBRrOH24RIdlm${g;SwpI@H#1vaG8`xxKhe92wW}Y5k4m65w4fv2{+2{)W +Otuqws`72>TNbCp?yL6k&zF+=`Ro38%^Mgma~T>R=X0|Ab4Wf5Mxje;NQRm;MP?O8e(9fZz +4TAGQTnG2s=hn%35O6?#!>nupKz4q8!7#gPdH6t>X7D297pMwIDyhHF?Ap}5%yj|=_edYxRS7m-ib5$ +C;;PYW$=>krE&D;nnmxl*~HJLo~D`J?9F^sf$?z-ymFX@sF0sa{>3N?1FnOpc+X!%a`F8aFB{3V5iTH +HK>qFIZYO!U6pw@6ksWLa(WRU^X+*+F{tAg&ST?2KO#WqhL_AgsZzY^VdM>5(<U*Y@^AlA2s-^8WhSfy+i3O&kA9@l$@nl2|FC%Z{aM-j +J+3oqy4T^Q??J6_E7oT{cvtX`X<=36e;J6_d0kEe5nnob9|o2y)ibw)E(J1*w>PFKtMVxI2lYQ1pq)n +(Jw_zSrmW?y3mCy&=velmHwU3wLey(M1A)9u1e4c|%i!!>?r)1=d?!FCVq(8$Z<;b*8gi>Grclqji>) +gvE8JK~Zc+7m2s!g8WrL5ZS-Eg{QHBuk6cbFO^M35+qgXU_QA>PpR1gR)Fo;1Rn%)&JLX;wV!2JMCfm)$%8 +v*KwM5H%8T4*eOOWcfwAtd=z$yRrA3;o!9E`N~hE-iBcr=N>J;K&?`mNQ|Oi8riaZVUZK}ixAw>Gkzc +u#pDsP~pzq+yF%kbHwfuH$dZc*RVTN0}M7^Bs5l@oJ&m +sSEJkp`2^V)b!*Q7U9Ek9;WJ1XLtq^93pu4ldqxt{vZRNHkScbXe^@aMo}Rjw$>vSf(x^W4%a^4YVV# +=7YxS{~2vDQYQWCF`?>Vo8^(vL@FZzpeoLJgLj?2q&eKt_wB?RT +Xle_|ne~hq!@NvRJ2){zuNVtq}65$64XAu5`Z~@^@36~JAA-s<8bA-zX?~?iwevfb!;gf_95tjL0OZX +t+2Ey;i1V;*7O|VfjZxTrS#|eiMmNR*knt2@~@#O+#Igcl8oJ4%t|H~k}Qrd^`8!~;GdBIZR%W`>4Gc +UM___Dn&Cwz~zhi2ZelK8S5*K6hxtBL=VjF0eo>7TGHr;UVPBdi}J@bfZ0%{)#B@z)Y=(9Bzg6JO5DG +-~ECqlo`B;W)w{5l$og0^wZ3`v?~ieu!`>;Wwo{3BNDxNqC1$58=-B`IWO@i6AuQ)z +<-DwZu)v3=%`oF~sEzI< +-TdCo!7p2U|c3go=GoL4U;{zk%^2>*j{IpLQGR}z-b@oK`a6Fx@xEy8l%QO>j06TejIubH=PB)*(?l= +J#>-d{gdVELSq^SE+eIE46ee-AmYF6ZIHiT^U;D8jN|AlD7Zbp>(6m(NW(Pc1i$N+bRv!g5|+&co*pm +E&X-zh<365%J}`tz4HN*DaJ1|6#(L2+Q{wxo$xYo0k(`KBwh8xSU6?B)(kXBImi~H*wX(FCr}GoyQP9 +M*P);!gyp&wxvr&=`1cc*>lEa=3jJ__Z_euGLw- +U}JTrRO@olOz(<@>r^SJRnrDe+~$SgwHlkkT!{hD<~X~dr|(?{d@X9?#L|6Q3r!f#7^5q_3%1>wgC +%XLX|9aA;&H%t2vmiuwZbu`1JeTctY%Ga#JX(av{!gAe=T<4=7DR2p4xsFDz`w1cbDw!V5I;(KvFCZL +6*hW~ctCH)m;)tJ4Sg!jSDKYVB+C*JvWgwhO{7k}fot9kJRYd#@!g8GvO(U^V;%|}q5x$kMTxTWMWmO +WNwjX5Gg!2g>BP`eb)f4`Ta3kS$g!Q8Y-bOft@T=0Egttq35|-`DexjSy#g;*+h2jLYJNyu?1A$%`sJ(74}q6x_>OC^r#z=xz636IOYd@?e*8bnZ +M?hvj?`SJkUb5A7iHPh6_D#rmC-*z7scTaHa=dsKFLhKYo>p@zTIO`enM@4|&1=@Ii#7BxOGZlW+GZzd6)yNPCNU5SM;pp2k%##k{3!-Mg57a_8Hr-n!#lHGVPAnXASx=GjbY`4#hk7PTKD=0Pp0K +4PB3l|F$jYIzXr&=;xgwwOO#sM<@+cP&=y$6`&pBjcsW4YoNqs4h{*skG=`sRuA)bf`{y9r2r#JsRgEk9yj$W?v>wyF7PqxQq)-{H|76i|7!so2W +vhgHQ+F5m8f3simNyoziwB$hQ^Vp$`_JhffbN6b^()$-%e=p*KFT=hp_S9%0?sOcB;+72~8#JsY@Exe +dNkP;-866E}dJAaXeZ*$W~&L_C=5?=nTYJ9T(cs^I%-QAyu4tj1EH0xiud-`9hvCmSCJ(ltE?ZU+#Jp +-r4-cD){TsW6MUvgFK`8;t_zrwC!r$>H@dH+JyzgQJXXYScaKyXW3HeY*Yg_xBg +(RXa}Ry?EY^9bC5O52p1SqmJEnOjpMQS-hpi|1{I +amqTZfZhe&dh+_dl4iQ4BWrY`x}Rou7H&tc2vT!M +?Rc;$EOpg)tP!d_r>_Yac|py{(8N$Hq_Nfyqe>cC7a%$P*PZ=swF2D1 +^^L;l?pK;ckt%}_H$E8EPl9R&Qz47x;!E=KrmeHR*)@5E}x2T8MXB}Rlb#k`E`79z)!=L1kZ}w{qa*@Pfora--ZMKRkZOpU6 +r%zee9Noioy?0JlA2@7hj#Ww=*Y94`0@)_Sd|3*T>HKWZ%R6Wd;l|DoHwvfslSE`2oNAA`D-E&1%NdnQ>{Dz?jkPtFM~E_~)e#?HLDYg}RJ>J6PH +e6)DXj(bntvqe8;@{HWp&z`ulenX$r!54Ce^qaKfu^(bqy}oz9F74v1+%KN$yL9FKOA?_3W&iQb9XI{ +d?!~zq+wUx4IXn9sKB;;C^OboM{?Tp58y}r2c=Gu8m`-mb?mx6-Q*hnq>&6&oel_9w%lXS@-0;oI{(P +I5p|hX&t4VA9?139hbMCpL+oC;}?kQYaKWX46Q_Etu_~`dVw|cXzzWS4IXIK4x=G95cj}^BW`Z)U953 +jmhS@)7pM_*&+*yS^CdFJEcF<;Hh%WK#3r{QzHo)f&M@1vie55M#LBS)5vjqmYNWY|}|JLUcGl*P@g4}R|YZ`)2-jM&bGNPWnS2RZ?7M}{Nt(D>wY}b`KPyYhL@D?xG{QQ-EF?(dYfIzNFm +x?7(T3hu`VZzuS8sZm;?D*wPP29d14Q_=a^)ez>{(!1TT$OB>qwm6reJ3=A9dXZyIXUwj~IUf{O}9<8 +^0enYgqZpYo**WKJ}^zbJNeV0#Pe{O2zvdgy3-x>otz1R3q>%fNepW0jZ_MN!)^1dh62RL3{(d2ViWN +d|{a6sh1JLf(d_+!{1W?cT^*O`^|i%O3Le!c0V>i1{g8#w#*d54bf{v&PZ>PoLJlM}o~@0-!(1@?Z}S +5*nc+e-G0JskPUxyD|33pU>yb?dP%#UI<3eo(Y^*Vn~~UK{Ha(bjA`cXYlre`Vd{-+nRn^0xfUWwS=C +{h)r{fKzFw#(k33G^JO^6?4ny6@Gg|%ED!xv)=gTe7gUYvzF2I!$+3+l;5*!=&!Nu!g`*Fz3JE^zjS^ +6sV)<8alS75>dM`Nz4uM8`2LqO#}l_b=s4k5_rwn~7q;2j@wv}-?)tr@San>qC3t#{qmahuYXy#cK^3e-<&+yYy8Y_C-QlTz%MT)`%S*(%)}ip4I1&<)A>(-G{ +Lq!e8+Fg_DnxiU9+&ZvHYclWB0xI+MH@@z>`lt%=X0HJbumYX_rQQkvyaR#FF=WcDwAiYVx#2$F_gIB +H@hwiGMs^$V$i6i7`FnLq`KIdhfPphqT@^>YhKIFY3_$Gh`vz1ksZ`~A<4{%+!kM;?9d^l9blk)LZybj4*j2cbOvMAF3@@8D-=>^} +4P^Zqlx&%E!}3%fq;3#i*LxwR}8RuWR5~71_@6&{_8-fh?!Eh&)i*zNaIiga`+9VT80JvgQD%cyT^?(wyBKCBD9dU0p;OV97xoBxBp+ivj +?HEw^`xax~#jRVJjerCz0!QX!Bmo>}&WtZ}or`#0r*~ce!5m&#Q^6~wBg8RK#)BBAs|MGrsz`<5;>_7 +j?)RWWwp4_iH`Thg5z8P9FXYT{$X@f^h?BTt(!`JUz4gRyqr(JQ@!YiXb%~_gsW>+Ro2Ae7thWaZtRc +Cw-jdcW_m@sJT(_cOLs_CnfSrKn}->~uVPYX_f}4{WqmR +C;LSTnp1T@sHO+c2^u(JF?|d@x;?A70-3=(pV#Mb)Lh@Wk4meSg1D`soXMZi@c+;LCRvObC1ONM2-p>yVY5^EX-+oEi8)cHHP~54W#vHz +I9Z=N%h=jqCUJ+#6O5I=;5BY+cFN;MY$?u8aF5?>6tcK99-CCUNN?E4`o}0>F31UjC5TdFe+oFTZH!zGfwt*lkMmsqR7-K58J`}~KTe^c^4j`#h%pX1*iJV5$?X$bdbB-Io=3^Xhocaal_ +x_pFk_%59=W>6Wbb{j<=Q*wV?ZK1W{pzqUdALPKIWipZ6(wP#qN^9`HtlibMsN9`bnDF@EQo$=@WIr9y0qvaDM4 +eF@6C+fu)<+^alR?~++DA}vZ(Kj=%5e&<5~4Q^fBnzFWm(Zn{T7!#`;Cd@ERIf|{q&j#=Pr)^bJxpDsz;lmvp-gRk +9=y5p4jv7t8aaq7oFWHanl0@dC{W=e|T=PWp?!Oo9lyr{mKyiYoC5QYTpAX(J9f7%uO_&?q-c%ka^sH`$l8*g|k;4Nr<#Xk9hF8bDv(yjNU!}pl#p6?C6^=j6Y;7utkqLwR +++^yB9`JC^{ed%DtJ@3}4!bF!in^l`tu_*H83@_p6wz +rHVt>@XzyoqoE1MOWlSZ>o5r;^sF@(Y;RZdcISBd~{~Fn8$v8%^W>D<=1)9i_@ZmUO4mJz_{e-am6El +9R0g3+M2&`D7GKCf)Lk2;y#BshdhP9)8aURf#H4+cD^g9fxmeh_#1B_?T?DwG2qr0-a^$~3)mEs(_}T +6?RGq}#AdLX3T;`rX8RaJUkCSOrxE5UCX3aaZE)rr@(av1gSp6@RmhLDAvv>c{CpeW=U8mn29wQTDgc +k!5C(Tcu`D$Y()i;ipyb%|^B{x})4M0*D@5_@fXOV4&k4(U>S_6zpkGeDJ&$h$X;@Ndwin~MMLArtYx +oYc)9J=$4rewbFT1e7YRM8lBu8Ap&o6Yg>;d>-r0WQfb^vRez-GA1u7U5^Vc?_MW+wJ{HE4FwHSjTZv +Hz`s^@H580`5mSWmU&FUG`sSb{hDRH(B}FWy+ks~CSO)Wv`=u&>a#4Xf +%PAy_oCCRec_z4n+(Qe3+4>EF{O*T$j$Cu52^_5!n;FW8)zPAyWw{ooIR3?8%5j?QhJJ$!9rnSQ7TaL +6ZD~IlSW|9XRdBkscz +8-`O7TMTr4UMr^)W-{P&`D%SWDG^3#m{WcozCbeDCFy|c}{D7gDE6gp6~*{M?^qngj3e!ctQX+S&`Sc +)_;T!*%uaKhC2-5=B3cmSaJ;JyaH!&|G$ne6N(xYCKK*?Yr@m9s +4#P*G6D64qqW~-BrnS9dufB9y}a}!!;%+fp=2F!?jt@fKvnT8isqG +!yBcr|C*~n%u{Qx%9`f)?^mz?Dt-QwuJYSF)0LbuDJA#2rT;vapd{Rt(dzKazXZ-je(ad}vmIu;WBwe +dM8)%y?D=?Fm1Dlcg2z{-%jewunB*CwM$At(Tg@hidA@Vhh)9z?Z%{$DoL4=7l45N<`d!5XWCtRG~_h9_Bg4=9`kUE=V5C#Zs=IAal>`r4d52ya^p+E^+!DX@i+oK+>&@4Ax9LxmXF-{vZioPe +`)A(h^HHu7vYM4gD2y9s|);MM>Ydr*;xx8i0UUq?xK$d>>f1Gf;D8($XG0B&x1&6mF5Iq?%)scvGqS^>lfy{th +(Jf3~cN1C)_(!>Rhdry$5WWO5@Kqh?sk+H!iGTa*$TE7JS#|d8*+Z)%>Q>=@XU} +$LWfACh*t;4X1pzOitfI>qp!d{Ol_>Yron2Q6WxA=U$<5v9?*1^S_mys9)y7_RXTOJ{a>iKY+y}yL0A +a0|QSZZytgeo)?bV;TqmD%~V_-PAomq8M!T54nQs8{VcN|*X2OJ7U)0rKEahZHu0U!KkqrmNH8*r>ja+uS&$Ei-z3pTFisTmS-W?$*PR@HY_>+Ti#R}T3X25Al8_IIUqosqG(;CEk@XodAWeZqc&A-~ +#a@(X>Hf-+_Bdx64$AH6??>E28nfzuf4Hq8}BDfk~t_MgUv1fDqrdfp}UtOh;DHskgs=u!q@weI7Z@t +et#LU8@@8DW8Yu*>cl+I_M3*TGHDO^lnAz)cUQ4^c|h%@;j^>Tw7V+oJA>=q6FMsT}Td(@v1n;~xadG8Va8sJ|4xC`IG%fq$l^? +V)7BxtGr+G8rLQL|2t0FFwTF!IO2#h1&q{u@`Bn5L)bBT4GPL|E;2m`1w_$;5I;5XNRnSgUiF>52vE7 +5hF#yuZ?YT;{j#F4c$eBB8d=GP*g{yJA^)X|O@LNZ5B(~u1!e{Ud{@mp+2-B<4rQ{SmFX2}~enz67J3 +MW&t{&u^1HH-5P)(Y1f!BD>Rffl+jJ2LJMtsLu9sC+dMhK6`Qmn(oqvv#$~Y8WmmoiYxq&WL;vJ=7r&c^UkbeU +-1uP{ekI6eZv0`qZ4_<$15khX^XRU9l*4Dwj%OK=m(}m0-uYNh4_{kPZ&zjXs>|j64|`!s?+1A$+)l3 +gm+fs<2bZ=`2EzG!`D6&CSp5Pt-C|UcMRO7@i-1~ot|kyr(sZcBb%wa-UAhu4ZkSLOL*P~4&X +Y9a&!l87d_*f(@a^tuP^9#gQD8;eMna&kI$9XgWy)h%azNXqai+Dw>EN&2G0S#`-m`VTZe7v{0Rzs3x +2ZxoP_&zMi(BWJ_|j=-8UZE@bNBx$Kbxf4MUz9&eZtJr}0{u?oE*HuJEkvO!=yo&*=(t!Y}RJmh1YRi +3;?C6;?!kHbegQ745P|+kyHWOj6j2Sr8xjZJn$zL$V8Z(3Ib!Ah#jWW%o#JyP2Y}OYoC*wWYT0&s(4j +EKt-o>)>_lFhXgWm)Ju2OW@_!^EsM+oj688{d8;WQAlTdcnxjh%(g!1-+ro1f +zw<+AsRW70J2@G_r8w|+jFJ0hrD*?AnH7yTjsThp3h4YN8v7ZYTN{(St;f6a9hcpG4bcP&`DBXA8QY=vzc9h?Wt(kNiJN^ev(_L@yB4 +nFKWu9Y%Bt(Nv<@L<@-CM)W?Sj}U!{=m$jWi2g~mZKj~1L~kHEhG-no1w;#ot|s~j(Q=}dL_Z)}L-Z8 +UR#}1u5j7AUL^O)%G@|o~T8Z99bPdrhL|-CWMRY&W&xn3Sv{A-S`7)5`6ryv8<`P{&^r385nZ+xS@@o +N6XND*LoOg=Z8Jll&@V`-Lm45Z<-PLj!)JC}W-Oh +wszpf$l_GtXsRbm&rOx0`M1(WW)%mg3A7G2<7IF%E}ip-rChmG2VIoalE(es-aipLT}Fgw18_bDy~;i +*ri8J;h>MXf>1PNM#aD%9u%Xz1NIFtJ5;E*lAA9pKHlB$L5;s>^Wr$Psj|2%(M{YnJ|_FlfyX~r@!!< +&sb}|LrOWyV(n%Ss;U)(r!cr5)>=3LYotEIrQ +56h|gv{++0(0aXt5II0(XQSb}ETa=F#ACl_Uz3$U<2D2 +(}#$aRz5Sy+HYX)Y^r<*l$Zq@u<<*=e0^TV}C=05%jQy5-9git7}+EsFa|bz2qp^XZ;cz2TK$HZ9d2S;ttq*Y(b`3sNSTt!C#n!RC1Rc}Yjd;KgKT+j*#Cnz|OldZV>4mZW?OCK`{~yDEI~A_zi3n}u96astUSQ8Lf +j;sP_9HJ43Cj$7NfL+3jF5 +OagEZBVug9l4l{iG?{iX1fCyLb-BzW}&m7(22T#Os$eg@eG;IDUgLwG-mMJMF4FNzfU%cj0+UnWzWnJ +<+r^kzpf(lzgIKYu%}K(J~n`{k8Uy4TK{R`ky1=ac`Z@U>S`ar6F%{6^=GH`4j#fBw +aMcnepVUC0k4y<-0=z<>A(Z1EC&LyOmcxoZEPTrD5$DflfPJo(?N_kZ>E|9ZL5T>qCB6Gi9k@rZl|o) +j6Yi&ys~{48xGwv9HtTD7(IPo&1%N;oYC|9hT3{)48pj6DG~qZ70RK=qyB4HIZBP%DmQhcH$IbO_K=ph-YC0WASq2GkG=Z33=Q>LJ!M74*QpjLs +^Ddf?m*=o=>7%ff^^Gca}ma4t}9NY5byj*I2!(x3sFr89e#_hzh(V)m-+gL|H3Dnt6>ylO4W1=a3jzJZ`3LJ8wJIAh;2HH +cgQ##jjLs8<8UuG2nVzQ{2Rv$z0!!T1L}nQssP$isZ*+>(Jm&%I6>%BIsw;&L%Jv69t>t(l992K;QtU +%vy!PqO@#JWIp@^~9mw~vd`w}ir&6y=n~LAA*D1J=r}ZqbYaH&OV^R$9xVK)WQVG-u?n9Pk|Dehox3X5*f3CIvT2yA#4U0IdMpn1 +*{>X6wr537Q6v&1`Tlnh*BXfnE!sd;mR^!B~HwwOP1VnMujb7Ivx4#<^L@A2XIKlQJX+;#X=}!$LtTE +R5{}TCiBqA$gEqU6!uA0PF>{!2#tP;>%qI82ZcVjv-|4(~g0#;SE_P;i0Vw2c#BO5%9kfa<-jhr|LDxef*8f9HT1)&^prgAtAx3J9 +cUT?^^G>-eJ9Kz5DE~nV# +o6J`ZVCfTsZ~lQ}Ms?+ZdCqQ8#56hSoLq{5891>g?2SsOMI2>AJXipeCJn{s94T-zg7v)*Wd?mAD`1%Px}SjgX8K|) +UVNQme*moIJUpecU@MATEIZqslN?&IUI6J7vdDJX9&>}_G!+hJwaaCwCkg89=UwSMvRsOxLiBz=9$lT +j2_l%@@X%it3bE+YCtpE^DKh?0Y(<{-LZ!>ABUntvpQj=Lwy@nGq2&hZ%1pTYxMqn*0P^ix74*fKCIO +OE<-#1Z@`}cMz4chdAuHLI6(V)zN_t^R<=PO2ak<>mt>VNmC(MbL^9wy+~>22&!B#z!Tx(%=vup01fEzZrp8GZut;fw5G=UUPhwjb?Qv4uJ8D)qu++PBwMosamfx<5(Ys`F8;V2l=0s{ +4n;Qk|X>K(B{kFQvNt61NG_7snNV^W2ZRN4zJ*v8#@{hwQ+7f%8c_b-S+I$!GPd)QesTeE=WEbxS$*a +*Ug2h3>yX0DpwP@c$I_xE*tk+ygm*5C6SF%)~7>h@;f +^Lz+i5+dHHs0+ylwkYmtOK+ogwYnaExckDmfE&4m^^=QrOd)^Pf`90s^6Qk7tnsHy!2_c%pzgmC5xZt +?#N80Tnx1^uof7s5?d9j#HFlv{rSB?|M6=Sx@W74kxSvoUR>mi#`kg27C)*&a08cieuifc8|-EFL!`FglK99uj!C9hP# +)1W7%wWj1+)0*d@H#caQ2M@VfZq!JZo6k)e>>2YFpz`-OYp~New;I5=!I$H2npj4@y)>~1;~w2c6E^m +HyCw<%{qNL7DPUP!P3+-u2Te441oqoe!(0LSJ85DXjy=6K_~jV462N~@UwkwXjq?#*zz<-luO@r|lke +7)KGg!Yquq7YgeT71{WQ#Xhul)TYoe1D>sES?ruq5`m_j|M%D)xee4Yj{@j*@bvl75{ +r1v3B#Vt0#O^nBeV|*S({|~c$=sn;x99thnJAfsRX~GUzJxUXs0pp+0gqhd>u8BxMbgA?r5)ku=o97r +!RPuhn+aH6!iqpikfFTK*_#FP%^d$74*|Dq2CMvyh^O;0`4`_*iX58mBnfeR<6ofyR!f`pQc})XdxbB +gpiH8C0i!>1o81aIp{A)bm&Dt0@>x()}T}*loXb~@gUyOgn5*fKLGW7i!9Xl@-zsW8F%MHE|5>+8vsTPa@VJ#AZ8d!GE)F +-6}R{D$cCls0j=yw2yd2-S0VI3MYRNlwV;zetaI?g`__=X0V*{ij)h|7*ovy@%8XOFX`oAyKGh7~dUkz4* +QsYVX~K^Bh5hFjnGWYPa$|CzvX&>OD`IuySJoF4)T7D44Z|?P%(ko*|8#SI-7Ea-JpWcN^aNiR32Er# +5lk-o*KmCeD{NalW#N^VN-<=l7IrHT`!l)h_B+yACm+%l)Pe@HH+wX=PjB1DjtsHgCuEP8W_VkKx$!! +m;&N+~4fNarJq(`gfPHL`XOIrH1tB?^!nq{r$h1X!iRXTuQfY-NfL*gT*6{JR%Yj62$cB({+Jat@vjZ +si~)-2v|Qtqf +Lmpjhl@&`Efz`;r2;&|pv9#@y|*s(($s}zz4{yQn_;?(t-vuZrP2eN$Mc|5b+qn7w_adrCfT;9Kiqx^mS#97t;e)g{)qkk!1EZ;%WaE|oVmH)CIM`w6l(L +cZLg&lP}&ae$0pMU7_P~1>A9_{0}`Q{IAx@+Z*aqmw+283+PYMCRLv@MhtovWocFon1vF9TqDrgKP~A~mpteG +7E2-z6PF=s8p7-d{LxhKii{ZnEi)WsB<{}+WNl6iTd3j>px^+s&_wL;*jvP5sujh&Ui5Aw9iH?meD2Z +nipOqWpwu-r1Pi~ESYLb`>5yfqla_-jeqM!*+&HdqY)CT-9_w^&Ar2H*z_J>DCj2N*sZfK8uz^JFfCO +;_U`Y1VPk@wb7BcjAynXz^K2)4Ir3EQ75<%zRr9YSgGvqH>i+g5tEXe))gvx-YpeALd$vfM9w73c=_lTp(?={AH&85oDo8Aw6ew0qv52?k^%jNJQ1RNl(PCq2qIm!1A)+GVDdDq4io4& +DqWk+&1ZTvqyaK#T +Vl1ufG;2k5r1?KS^=mzyYNf$BrEnr%oOhhklWwrlv-mJ$qK^3X7Yc5X~ZlxC%<+ks`#^(3@*u?AOD9P +4WiOPj(lhIsA9lg- +415dl9|Qh0;4cAwF7V$0{x0BG0{pmHEV=C@#0B47|NeO*f{v +7yz`q*!@IN&OW~Au`A^QXWao|4#{8xd$0r-1d@Wu5aLfitIZ6{KMFU+==D2L|PV5J~iKPF`NL?MSgC* +25_-&W0$G;8u{AO4H@E-#H7~szUelqZv13w%1>wy0b@XLY! +74W}v!S`^Ez|FwF4fySV-x>IK1HV7;BY{5|_=|y`4g9UZ|H1|T;u577*Hb}30|EyG>aU3!+Qp|+$M)? +z53au&I3O%EG&ndcC?Fs(II63Uw|A$Gg9mpoS_lrsW&8`pAHl&tW$R1H%H){J@~VfB} +I~ck_l$o!Yl^UJnho1cn6$1qT72balJi*SCMv1tP=3!;CWMHm_Tvx^>fA=-9rUsl#C6qy4b3(15V0J8 +rzy3)gXjkMoAkQNRxfByvz#)NME3c&mP0(b%!Q2}Fk0w;y%$O*h^+q@TLMSKZLgMEsy&UJc~+n{K>gh +?BITUDyNgqXGiM9|#K@7#7ur4czK=%PoJq_S$Q2AIcthJzN#E-{ztlqBsn^9yBmKG%PH9V0hb$ZoqZ# +E~@gE9V~KPtGpg(QT +bjW&Y@-FV}TxS{@fRIjU|EPeyg!2`H{=b%A~gxl+{2eoS6%Kv)vz_8$OjPjtiiitan4;uvZ4h+1w)+6G~e6O-Q +$K(TnrijUGC8%DColVsHg#f5B9mfMe|;!xu*5?4 +@L!r2lRoi3E|qlUP3%u-ya@`aSV02%=UxTWt_k7-k?6A7hl%(2ki$#5km*|?A_q9OZym_(7@geE{m{> +OL&9JQkKrGZDkFpLLgE4*Ti#k8>x-Pa#j)0AfCH)oMOat4?Xmdibv+e4z7NN{o>%kgW~Yv!{WQ|z7yYn|GoI>$CD~HIDPuGICt)xSbA29H8|e&n~R_IYln&MK1 +_75%ak#g=$^(z_cA8BY|&b-6W!$&F-(?;$#S2mVErQ9n)^Zf^C +jO{0sBkf8&(@#wq{5#wo^kncB2zqaI1RReeaRjaMf>KR>>D0-)lV5AkR(D;8<2LO&czfgC)}C$seEodI?d`kx_}y`}Uu)cO{dL#fd3(D~K7Q9+?Q^H|gvsR +7#m~dzS|8xtb;I@7weh;8i{CXK9#^+)-u&9TTD5A^?5Z|h?zqO|YM$`(YDDo4h;t`uX|tw~pw)i?44-U*C4Vioe@Y_^e#JcKQ=ob6W|~mz +#0!&f3(&qngV-yq_P)Ypd@M@jZUg>K4-_{p=MK?tpReeU(dXE-riy>3_TYnMY?yFF)sabKACUIjF3Bm +0{*=X7hkEx*Jl|c{#O-uXS>&4?nY@=`kPj-Ui}E>owrV&Jo)VpKm2eMbI-vO +Cr%sz+<)rSsgqcj9{%d9uhx9dK&k^Os&yevo*R7qjP_9=yEC0UEd5xdHE>-*CQ;*I~o={1N)koH-*g4JrNi_xJBiJ!U2SJ9qAsZ@u-Fgv=#kW@W=1f6C*-4?k3uw3n5YDO;(mt +W0M^fe;k@EDJGrM6cw_lcW7y7qs*|M}_ +$BsP_!!Loqq&yT4q?cpPaV0;L1!?Esl>Il~e4{G==9qK5NiX$)ypV2=KV_W$y_CUwrL=r1WuI~>@7p2 +ey=CBGyOdqPL+7`pZ2zW|OTLn_uC8w7dPbFNIAGt%An6M{^S8N==H +Ib_kf3q`=mTmCuQ6&Da~8zQxjK2|Jk!=smqJ+z4zYs(1nHM0dn{qFQkQfL>e38gX7LIr;MmK)PMS2tl +1Pn4}$(7U+O#z*dt|s(Apb3^n~6dR_gR8?3S|Q=8J#NbUFGlXGp{UQ~xm^N!ErkG4!0{N?A~^s2c_!v +~lVg^`3TNK-vY%@B>ml03O1i|AY4FJoKxO^8TGV4`$fH-S0^0v-OYD4_~~rQ>RWnA%`WjGy0as_@J&g +rC%I>Ugx|(Jo*{eGV(BNlstSbWhi(U2p$|K-;<0a~9C1j>VO3IwlZVfp_6(irYuNLLx;@h;b>5=ebJxwfO~d2vXo`Nwd2x>(J=( +*cFC_iclZJdy@2S@f`7n5)u2YZp?^q{uX7-gC)BDNSr}kGousm^0%150%MC_MxC};}#+{K>BgVUb7(I +>&Dc7ji82Oip%=)M(YUSssPZ{NON|1rmNp8uUZXuq5f3|Y_~cx>=M`r-Fv>8oR9{?mPB_RM}V9XzZ84 +=ge8pO1rwNYL@{L7fM~p2>q@&-6*%Kft)ZE9G6}0poAl1p6;j&3w`r{qXln|6yxO7!T3r3_c8A$OGqt +rgV$+(@(CS7buIK?IZKRLl$^Qo2Ju0^`w;1-+_l}DIYna+cSMq*q0aVnLHTwOdbq-rcd&DOEq)0A^o& +7&M%0w>MMCLYC}F6vk`+AmW^}omuqMBmPOC>k-16WVMafBy5_jt_p8oB6nGeQ6g;@tbC}bf19$5_$$z +IlKN~*D8$5J)t5LtC=tq3NxM$Cv?Gd9aB>nV@gwz@Gz?jG2h37ce8gUt~alU2D!1AW`ez{@J{c;U>C< +G67@UVILBzfxOu`6;m$H33w=NRJ=Zn#o1CjSx +3O%Q?=LrjhxK!MOUJA}vH(0ZwC8`rCoP>2C>K99Kqe;y$pzzrmC>~s6jBd^tqbd5~ +@0I?8o+YH4bP%zkEc8jeU1E%$?=`Of^ckGP7)P+$?RJ@;pD!5~8P!!Lm$H*{=g!HB*JI?i1^3C#^S}da;SK +Om?6hb4q_pY%<*F(DWolx8TsF}nUmhPQUjz@&frt6vVK#VRX&7VteRt#jhx9`il>S4n7m}Cfo_kIf6c +i|au7nSR7vfg$dPBam@IF}z9^L{E%AUakeUf3%Hu$8KQv&3xi5B@U@UR3tyZ|2l0X>=*8>|SWPYQI2G +2ZyDasN;H4gK%ar%z|nK>CuClk0hC$j7C4Id`s39@zGpd~b1(+`7PDzU8#%wJ!FY>EvOBlZSsokCr?Y +C>JH@Jg}%31M~Ax)Ky2-{QI%^eYuq~$Nal=*sx(?6DLmmh_uokX#;cS%uzhJ>euCDLP(oxxW;O%?Au; +cA7jv-Uvk=WgL29EhxAkSAt51@gUrp%Rk0UinMI2h$r&?d)bnsDJ}xB_7W$-hbFRRi!9yc{rm1o($0; +QxWgC2_^!E0aQBhGcD=SNKUYi;764l($mxBgAYC^hYuevUwY{!` +N}J=$YslxDSEiRC%xo>a{=cGgAcA@$OHW;^^kKR``G?su>3TK^q-L*WzM(|W6aQF4B9h&(rZcKB>%GO +ufBczE(#A1pGWkDAR64zu>Qj&yCNFEOvGDO7)@4fe)(g)H`S}7w#uMByRKk6RWIgIN +l3)%y1TG=z?;A+pg4eWWx#?_1iS6qqm+<*W5%a$)+uJ%FLx6_uT)oPU^M~;*oI&_eC-F25lY^`V^9kd +PCN*;{bkQY|!l+iYQ>&{i9<>r4_FhA=t2IuGe*+FvSzsAWgKi(kEoIbr9^1kBzht$+mxp3japO-FOD( +BCifBLb<9+Tt8k5}W4b*0iJ@<6*FpQMd^G~|VQ5S)jp_nf0?E8LIZI+EuYBT;tbrI7;mfBA-E^hfu9h +z#4cB_<~BfA-mD&y&ta9(hDn+O)w#mo8o8%$YNl95`nfdpianq?hy?d%j#75YirKBeaKx_Rcxmb?w3R +v-pGiZ?rAyKXfT=-n@BxXs2OeVRF{2SxOG%f%Fd;FhE8|Mym6a0eLVgc`(+!T#GTjrrwiILh2BCbJZ= +zRJro-MW{#hWIn|Q#VF=jqd@EBg%!3X&F!MRx1fP04}RiPMme#&pZD4Fg +h-=ntq@^v9H)vA-eIYmKisf1&6H--~eU`&xRCbz_SC^z!nuK+nb+Q=7mh_>+j^Uq88EHzKN7Sd)^;?U2~7qL)(xF%vH?c{6q>ebw1w7I +#t{ZZQM={MqkjQ2wFVEDzx_~3Zc2Vw43>jdf)=h{a0^cDS-jS)jIuS313E^*9hL#ce2|BsLRMfAJsKY +c{QIkq7$gxq6){PD--tFOMQ^xJjZjmrC|OVo89a}FTwMt%fw?W+HPc16GI`WJK066z)AAo5}C_tSsS* +Ks~_<%46pVZ#Rb+H0>VeKu^Em3$j}^NgE#Or0_E2k@CW?(XjY3GMatyUvTQF^p?$b19jy+H5w3Pri6R +c_Y2lE&5LKV$hEGH2Y8L`|s!{EwC@8|CqlQ8tbo1@nYfQhUX{;BPU0h(HAk7z#J5LA?@hn&-j^ +3((k(eW2~hvB@@Q?jDL8{yc{cai}3yT-&cN@V?cYQzLS310rWBRFQi@3@4Ej9KT*H`LmoJHGJc`|qaJ +WR;@rtG=9-gZPdOO-yOaZu>GSDp>Bq39P&CtX$C*4**P%z}5hGMXR- +9Mc{Kd4h+#vL45dJqS``~jAf60#`r4^IQ)|oTwL<@O7VCKv!Ap&Z2h`;_~=DTInr +GEwebDdD?P;)|eCv4$_t(>r}69zcpTql$|)Ce#B-Hu88iNfc5S~zQSafw)w`+cY%qMm86UgR+|Dh>Ao +n=0(f3$d>mbADKCZ0s<^`@!(}d<>h9oiXO-=L+ielx=@qE9H!XQikl+b8OrTW=`Rrccpy%6Djxpq-eP +m4dnIq=FOYmhqc{g*mN!Z8Dh@U@VCd{m%fKz`U3HJI%3-=5d-r#4E%msd&CLH8J+joiYD;)SuyVcf)VK$%dITWxkPlF6LLbC(gat&rV5M@z +WLMdfU=Z&!0a(X4$f3BS`~eC$1kjKhm#rE~2kyEXz2B*Z4QfsOk&(Lgsjw@8q5~^E=#=XO8W1{c{a~H +RwdF7oIfM_gv?)U&hfK6JyQ8xXHCJU-sBhJ>SP%*}9s>wk|FzCmZO4>fiO*hlbBQa7;LVojCG^%vmr} +?PD%+?g5TIA!XEYJ#RYvpq}?No*!`4N9Ko^XY333*~L29mU9Z$uQ7;oN4h?TLHRQVCM}$Q-da9E?SXM +Wn0xX@|EzyMuID|OZ&UhMsps05N5xzx^MsFntWe90{yC3hy+3jG?AcGc=DUq}mXNV^(VQT)m&p +BG?jySP&)h5X$B#77M~gE@!(1P8Ow>o;w{`zu^w0RmHCNg&|4v$19M1ei#teNw_SDJaa^8;@(?ES3*+ +3ryoH^5bo%+}yPpA6FIw@xP^5r94>0sZ~b3zv8+qkF7`~vrcH@up7*_`3=U!|M^TKetMb8WN<<~v>cX +I%00(@#&FJ9qAruDMszzr1q=7rnQJO^=KGju;(jRgkvS&jHWoPZOS8u4^_BSviUy^R6&G^f4f}7H +3vQSLru?@(AE5GG%oTA@lY8TaK5}22`4#3SRF2Ep|Ln1YR9}N#^s(QLi{;OlY|)}cF)LTD97$W`dY|z +*`6ewK1Hw-;5>(!h`6}+K<~#ScS3C0yvr@j?$%ZRX{VPJ{3AjJ)aPF^Xy4VEsOfNd~WOJP~uqb^*zmKDSb@pQWH`ZsgL&p5Bc^22)9mnZ%Ii +ub06ssHyb9D(WU&(|FQoUUwkp<_19k?$=nd*anfO|tBvOdNe9P*{)pFDn7=K0=7LQ$rjIVV) +~Nqyj6G}CtclM*|NI1w!^{)54>c_618_>tbI7!#SCSZQ{O9p<9=X|GIYVIs$gD)`6VsnU`U132 +~MjHENXNyQ#hAhO#l{apT!aG|%UrFZ~05j6eHE{BfMQMD81hhK8#BSJE(f@?}jD1RceEj>&@ob1ID6!NbZw(06_R4fn8I_arzUO_?%9`363#YV0dgUL0%kO`p7b_w +MXJ*thDR{lM2K|KAuLMhv`W%^JCC)he}DOy9^oCnKl$C;L|Yvmayrr7n?QBL=7K8F@+4KpkU_gZob#H +*PeZ6a2G%*YkfRd!Y0)^nH}G!{MO*oxoTu68iqhpDp~YiRYiJw6Dwgw@UGbSu+qP{R_gQ~K8&%N1Q?x^_jj69iSc44zQhyFA0P*#TgQ}^G20rhlPBV +rx;u*uH=*yyx>1!s&r=KFM>G8ou@v-0>%)V%A#+t}A7K}NeuYouI^t);JH0lQWCfX5cHGbbwu{l#Nou +tQxW1MRzt{=I6`EL_7dUVP)p0Ueaz +qmMqS^4#=c9EYmyX>xSJy)yPo5Rc|rLt=H&SNfH9Y{teMka9PZ{#3?$Os%PbtfX9(qWPDg88M`OQ1&a_# +)V>iN%ANkimEZ{`qxy@9`F%N7-@8PDU;Z!_kiJm^ndY2n|T%U$)K_Z#vzo=IX`oF~~o=OeBY*f#w#=K +i^^`1I4Ho9%Ecm@6VroU^%~L|m@3Id^a0zWpLxmN_j70zFZMyR(W*RO6l&&f1FSHPJ$tOrGj(Pki&5# +xGB(BffXdP4IgXd~*Qby5=tU^$8yFoomg6*{pjfzHzO&^G=g`-`Z8qJ5B0sYb`|IfTRiIO*3MrOih?H +v7dMMZf0*&?8GsX#wJW0*Ux*z!-Kl|dz+?Ci^*f%eC&jnsa+=|jF~cN>ZG`7UB^tC&^u=8gl;psdz&W2OiYN2ojPr#Yj0@EWa>L@%Jiw~vxv +^t+Fz!%d)q5KU`~x4Gkr?Jw5OfNI5Q=7@^thPJ2qlU!i50Kk +t~Sp%Z6JdMb8`w`qDp;23@pteXzJ@%&m4!%&looxG&y_xw=`KQU;)tol +Jv6ls`Tpgn)KRqk>QnL%J9jE$cW5{&PdF#W~5};G8`GD85J2-88sOq(=*eQY0k7{Mr1~3CT3bQQ!;It +j?B``ip;9anoN=9nPti{XIZi$vZAvRv#ePuS+*=kR%up6R#jF_mdN(ZHf5W$E!h#-(b}OUkq6CF +iB&rRLf4?0F@5rFms}6?v6;Re9BUHF+NSUim)x=6wHrOMXaxX+c>*ML}gjRY7$@O+jseDD)`wEc7Zg7 +5WsK3;hc%g&~Czg^`75yhan10Hyh5`4#z<`BnMV`8D~q`J%w1z_Y-sz*OKSK4xP +VSHg?VN#*BFu5?LFtyNDXfJdWmK2s2mK9bMRu)zjRu|S3iXx99&mylPQ;|=RxyZlBQWR1YQ50DeT@+u +GSd>&`ElMs*DM~G}71@g%MI}Y0MP)@5MU_QWMb$+$MYTnu*rV99*sIu7>{Dzm_Aj;+hZIK?^WEw6oGY +O}Ng2r*sTuZ+l8my9%8cra+6<3OuS}mz|ICog$jtc6q|D^Z)J%J3NoHAQWoC6|ZKg+-SC&tfe^y9VWL +A7uQdV+SYL-2#B&#f|GOIePHp?U1E88dAKRYBlGCMvyDLXkkHQSzDl3kWvnO&V-o9&U~mE)7+pA(W3n +G>Irl#`s3nq$u?$tlaJ%&E?)&GE2%*?sK(_7HodJ>H&VPqwFG@-4BK*(>eU_FB6~u2-&4u77SwZe(tJ +Zc=V?ZfdSQwH-!&nwR-&p$6DFETG4T9FKmpgxpA7pn7WotoeeEr`sI&rixv&QHy^=a)d +fm5^<1z6Yf01Br$dL>9ysBo!nVq(ZhOkZUDmT3g@&Y5G8tA&_D`B$y28*&(?yNUgfCw$KCe@`0>EAg6 +f9C>ipxLpEiQOEqNT0eSd979o&Bd~s57a&c<0y||>fthlncy116I@pR+6TZMmI|sZzS*3B55xXCk04iO`i4=!pY5QUU#_f +o^z0FU-)1h~ntt#A0i4O0lijQCwPFQCwAALpn@u`ny?eo;H)sY_r%RY|*wvo7I+r@Y`W4wN==vY&ABK +=9y+nGpAY7BGRJM64R_{DQUJeM_OrGMOsx_O`1seOgE*Q(=F){>Cx$l>DKg=bX&S3y)?Z7zN-d)%M*U +foMD0I`oHhD65*#(;G-PyP!;e_HF+Z6Gv5T?WXX@nkIqlbx8|qh+wvXxrSLda@HWEYrq)`iz)8%Hmwh82o28)hB)T+pFY^Sv`jF8f +9DDM)d7LKGqN}PFKOjEGTw^7YvOBR`sD@rW_|Ze{Qv*(U#Kncn4f*<3*Tuk4i{w=hvzt@n!} +O}CY@RTJI`#A^0rIipeq+r{A}lDoM&I8D9aO>rAraND_078Tu${BCxA-+aL5z!Kw3mvBt;zE8D^8JXP +Qi#a9woAhHp%!-A%i9e@C0i)Mj2no5}yS&6-8f=JsLdw)qCOSu`fz=6It`DO~2VNnzP#zgtocJDK%wv +!ESzN@7Rz4)|G{KQil#u%*Mt+SJepY_?SjHOSe_$|&Dn(QWn#p7(uvqb~k@-JFvg_3PSx(jSt`y~myhQ+{_-9%vgHh93+bXDo$=;1u=mvHL>vPaTjMfsNj77}@A=ps4{K +JfSumY!kHk9EFRAt}9O^{qNjJ7K5?{=J4QemfeK}_(%jxJ3U@G4Nbdk(u08jKG{cUwA)9LCAIgl8Mo!)Kuv(7MFl7V8mzsQX`~=CNGP0Q5jDhN+=pF +mDe{M_z-)dsxcphc?en;BER@vyTR;P8v-U2@PfPhOm|ILo1?C=QUNht-~QXU@c}}XcbsnBw8XAYhe~gYl;!Bf?vJX*3U$_ccwHeDSmd;s9N)EyD1U +s5KST3}}*HQR!Jmh!z>0__#z-c$}x0o*g3 +Wv1L?2Fxeh@;Y_LHBh9UI;(7-(uDo7<4CjSOOZER)qMb(HHWK*5N>+zDg!r#DhWZJ%58xl4jMDcL-P9(ED3(jCVS;f%?{YSy?)Fk2v%FqD=A6B}{7V1wEF{FJWO6!(~N6RV3=qdOisgp2 +R^DP(1>Y8_%?`e1iZLJeffUCdA+)>!<1DnA>8*o6_}5crv40fdc+ang}(=dH=TgYx?=^;)QXvw^G +uaI(|pYIEoHimXX?OAxAII&d8XV`_>}ouNZgF$Y4N^++=A}NImCgz)DJT%;fz#z5r#ajLJ1Zw--N%pC +it1c0rK|nBJluto~Ge8{aoB)Gav*&!6Veou~h&V=2R#rFO?^YY`?l`1dV$Pl>IA~jm)v>j-E +rYGJ)Mv=zyH=vjBMaMcYl(wO>F+u|9(;Zdc2P#4y!wAwARp(|~lOwjbANWtLDk6q_7|jrm6yWD+X2@G +}446Q|yT*_GxbBYOsB&*ptoCH8TJr{FSqj8K=3=~kYeCMiux@M2QnE91rC;RN!w3Qu?vzC?21JRPF}& +!iF(gVun8ATIp^_F9E<^QD2Q6^i>(o+*+_WDvX5jIU!gDJ@CZJ;?Dl8ogkQ{vjJR>8w!JO}JDZGg#5z +v7%?GqNkxE=pl}a=G@D>IR-1&1V|C;0Iqpr2DT>DSt?+FUr6BvgvS6&Ed!8d&4^3?T@^gf0FRXOR0oM=3WuQBO6i>a@TzcNcvJ3LPbHoY>mtv&{BoEc1dYS7V7rlp8F*LeO| +98Qei_N7~+piTLQ#To8*ryNzz7$>>cd#)5ECCLw(AQBu?lc??D*5?N*#3q@IYa9{dv9nR(m?0No)&UD +v5_tEI!ur*I=FhhSPGmB3e5h~aDRYPQxEm`x?>B$zcI#}UWoltcoU+lz3eVC51UKy>HsS~0r1AX65q3 +)18?NpFTy6pg|#;jy39H4XH77YP0_Qs!P@~BOrcsi`uyh?faLcQYgB_B +DDTG#{t6XTgyl6Q67V@C6$P*yGJrTN;ydc&cAAbM2rCr@Hw09Gtw}V3$`sC9$xs>>oM!K4du+j5V8K& +q!J)(@RN`@?#H_>;nHMR6NCUv59g#ty`pIcy&MHz2SntMOp8=%mXKP%AY;Q5m$4X;94#JDK2$Un42Vn +5TMZKeLu+j!w0|b1ggh(N>2qtDT&W<`G%M?q{9qq8Rkvm5K|1r-sA#jBch=Vh7@7L;mNjQ1X&`rtIk7 +58+MicnNw>W_}NaamZZ%4{D~=?4bjJ_AM2z|hCge5vls4VR&H{%VDM_k0_NI^NQr|v*M1<5fd +OicslHTUGG{#Z}&P#zuU!=^I3UH!sP9p5}mel~K{zBo|X21|1ABWiigqrV%In4D(GWS^#C&BJ|8z$C| +Oo%@WlwU1K<9pr*xQb}I`qUY?yV$>q3SnZ9VKx$iPy{)S17jrNMZMqLji9dD8v+w9RjAT{hZ=)Zx +==OYUyVQXI2&7}oz-Vu@QkV0+3vkkB)qt)+C0;*>kg=}l;B%G*djR{dOh +iK>=NA1ndHog|pNmxMxkk`_)%dqa+fMoDjAk)k=o8Q`HdWLXOFKU +(;@FNQHZwJWNs=VIP=rAAAD|fnW-uo56Da~aTQnBQLMTZskU`1(2;;mop +B?+2Lx;V(^S|@1w}S_oWzRktd_E6K9AXksNU&Rw+&zQQVmIgkqYHWYA(t$1JSW39T(P_6Z;saX-i=JD +nY1S3rw-hVkEwi5h-`Z?3L87IqX1E3)rXw%g**n(UJp#NoXxmJL|ed?(J#DrzAy$Z@l +25y#e`h>diH&rO7#i}&;y?14Dh0^3id%IEew!_sE46zz?}A++A|Bb}cX~h<}~xH!zDp1?WM +j>ry|#Sw(iiD3yk#tXOI{AabFuDW}ns4?HR0a#*EY<+nH|XSwJAq~6#@fU%dLBq5feJ^}De9&DmcKYP +ExdP_dc@Jg5i`FqCrCk~E(f-!zBU*P!tIUf`Wbr~ao@1s$jq)|0Gfoc!0P)I+a#{g!u}Wa8)%mzVWAaOao&&eIaQ5%(Z`avNlV*G{cNP!r<~G(D_2ep$kt-m$0;pIupYWkZ@}7pdRyRMP#qs;{)a7$frjrjdpeO`6X_Rmw5R@F7HGfZR4;DJFBjP +?i5pZT31dLHa`KSa&{*4sV{n7hoblSa3412LUma9J-46 +HB<;YCs6(fVtT$KW%K%Vx2k9kc@8VUPuo(Rkj@F1H}w)LRy0htr0`a3@`NafuCTQ{p!SmhPG%8GGF9; +2{sCKMhJd1VsvN=4*VfpmmqrMy5l@CzqQsF2(3bAP*H1h%YRj#Z&N5M2OZ6lz%eFiQHfx~0COlG2}4R +XPU{s9mZ#iNf&8=~6@#BzNP=G^xHvr+=`1-973{&^sxo8+qegV7%q2EvdVA!v+|k?mvxszkVc6LW}96 +k8oL9?3x8RwvZmMtFMfWZPw`32+1%L#uUJW7_+b##qTvR3%{O2NMJaLa{Cc2{2>zwvzDPQ7#p7yDKfX +GrU2UtSk{JZXs%W=dlME&e2svVX9WE +ZoO#mE>Qlfv^{l?bn6ive65#i8E7y2`h+(>>6@LEL4uejr+C<0mzE-uZUj{&i=@y2Ay^8pja=ep +BueuA{_>SJZ>U!;5o%HS;&XrP5oVq6vb}zHoA84}q;~^`eIL%~67}=Dpt`9mZ6a&-1O8qj<*1T572j2 +QP@Uqp^hi0a(ce-rc<*J6KDS!6#0%5ngkiW3J((`DZC-Xv>^rIf!rkFvCwzT9o}6P^8s54Od4<`>pn8 +Pp<;34ZlU}1aAKMSS*tq$F%1xzTmzM%o&QYUJ>Ig>dYS7P0K_?(u4r<_vy_bkMuv*D$;Je4zeh7vz$`SM=<{&ob`Phox;=X+94lniH;pWS>#+jfIqy*$t4LNJ +4$%yl%IWyz_JK|Gb9j@J`{v3*h|HzRox~jJ9V{CCq^Pa&ih@!w-fs}1v&TL1l~P +Mtr`{YtL`A)J^BeJ_HjALA6e{Lq+Wvk@131hD1^RmsBJ3d@fEv5=^v0MOXVFzHBMSV;(e;fY*Id(C@t ++Q^;t%?0y!Mx0t!70ZtO19u7L<@y$dq+&dOgGn2fcI#abM|D1ZQBP-~>rK1)VJub5vgk5HQ(@xI=Pec +h|WhP?uN^5;>Axr*6tL!>Ch#9i|SMT+v#hDdP?Q5cF8Q*a+6vs?jlX(O6ik4Nc3-3Jx!)&L4j-g-ot@ +U#Lrd#n9&5cliagt}x&yNncYe!ysj$gbvkSflQk{Vu9%V8*OM9lQ|g8il%DlNw`8InKW~??5r@03oiJ +q?HpR;{$qTBnhEEF_`Vok>FkDV>Y}_)7}NPyTV&AM$Yo0tmbhy03!!1rbnnts_;Txhk-1OMWseunA-! +>sO6kr&U~?Q0V)S~V*>3O>ix#xGwftvIL#+`nutZcxJ+S?bEiR~v(!rmhxFzM4o~+Yk-qT`czDW&boc +3GrQ8N$o5*&1BD2iu!}sGlg&J0MvOBZ-w%H(iAT4KwjWtIn;@OcKKqW^so7guwOYObeEb5)>g*JFj&*2G4Ur2ck+$>eShOd=7kU9z;sI?+O%XskNg +SCoZ^^jZ-fy6uN@>SGHCBi8N*{6pL_P=#T=MOijE9Ww))!1`qid&&ho+tfm{=~if+T27Xs-kiRO=el! +Lo2b_Wd{JsBHy3c3hwDcCoEYOuog=(IF+9Ft1H-uRK32W<1R^WgH>uDwj-Bw}f$Jv>?tf5b;Ri3Ic%k +#s5`^+@pt(q5BgI`FAk~PPPXfQ!q5j!RR1;vOcUZh>g*P4jZ+IvUeJ)!iplu(wc-2`XMF+a!uftb!1{ +Uk)Cx^6nyO0WMsYQ`wSJ>O-XT_4Ad0{UW0}gsm)ZaxesaGxopz`U(U8_JI_jc4~;V-U6*xR9v#FG(_! +D@;)61xOnV&3H1EX{7bTx`5d2DB^`Ea +mRerVy^;1*up%Rr$bmV>q61)C^K&2XlQcC`%Lg=@jhK +>-`=LX_L@BH~DX;jP-SwBe+uPVSC*T!2!7=Kjh{wD8881g9n=D-kj0xEsa0viON{+j~SW#5OjVGG(&cd +>nsO1r^vdn!{@5k+K0xcs%$9l{j|^lIZZijmaWR^q`*gN@F?t853u?Bk;Z+Eo3;z@l8dDzfzrXxF-r@nyC?v^LVKuU!&WA)5{z@-oMn+g&}Qb5A8Y3&lTNDf`LyDgvUgJ|&e +iYTK=Shr!0Q01t~lMIdh8?p2(U2^KhPLKeA@;k(x_IaceFf=X_FuTW*mVRp2Q3s +ejT?znX$dijnHn%(x*c*gII%&s8F$s40K=a^AzF8%b&FAg<7x*9LR}kEYembYfP%n*}s< +0N%V#KWoFO9f~}|5X2(cu-}HMzs?vO=adX=g>xakoK{h&GXWY)Hv5!@qzdF#BzUS_b7fr_!z)bbh3IV@4b(TcEq?E!r);VwNJ`s4lvmHU#TeJ0Ia&@+=dlFuCXLkp$WWg%dW%NcFGj@qXM4w}7WFgMJjsDar#`Cap((^lO8XU8lz=**B1+?N +6X&MmuD=QD1y79<*71xl;$UpLamZm&&beJL$Om`*^W5*5spEV&LGW^d!tGWd*wO4k+~~)(m}eNWt^{a +3eh11XUKQqqAt?FI8qbCzwr1vQ?dSwaK(;Jn8GMjA`zMOog9im&dZ6$mUs-Fe_U7sn5r=emn(!+Li-= +sO4AFe5P$0hc7O<63>!$?W8?=?TxTU-+Y|zpNHDi*B>+Tq7qI$k%M(UY9yhlg8EsGajL8?no3(S6;^C +d4pIZ_O_R(ff7p8SB*YC4CtO(ODEL)peu;C6GyyOL7FdiGuY@0MH0xK^xC%`H8zkQYDZAC>@VkL8utc +^b+_pnqx|~~aM4v=4Y&R{pRCBnNpQ!yL8xQ_-cG3~-8q^L!d!?l5n}_OMG*x-GfuA^*NYG}9NfxCRk1 +L-Jjv^jtEKHVWXv09{BVwJA&FcYK%#VA^gG2|agUzQvUD~A*3%aNSVXF<>j2MqfgAMhTSe#kErdq5EQ +PBtaH}L$6AhpGMJtPiEHZ`)Z(L0dm4a2*&a?R7G@)eJD2Y{x}eDVg=tWJOtqn=xR{&9XVzL`(n9^G*lB( +pimBH8^=UG3^KHoS$UWp^g~%Zq7Br(nVqpXEJ2BH%s7S#(be@|lob2l21Tjw7i<0iXa7129{EMmCIz& +b%YQq!s71yy}$Qd7TBLpS(_r%(XEQ#@0}k%FJ+iGD8QkyV33*y&_Bd0z`021M|1o0WYSoWi1(+#REJZ +%$vkMcHgjKzN9xvjR%t@%O|YpTbQA$0zPXirh7w2WUI%WBTvU0_tG=+6vb@) +_@YLa+%^k+uC6v1VPXa`Mh~tK~<hKfy~rn{wBThOO1nM!3 +Krk|O==XOuduhJPzWJ}DeZ;P^^~hiTLZ~SfhU!(29z()DD6qY=C4z=N~kFCAXFlz>sq-IohQ##qC@2w +(Bu`^_Gsl~iVzwLFw)mz0@M(=9$Fcy6?kE_bG)oo*xWl*%fY8?dir{(mTlzBfSiIR2%EpkSYZ{}ek~a +i5=>KsP%>2OMFrm$B)7?fGYxxSHGr#FZz0%!TU{rUq!{`M@w`nbiJ2=D_e>$QlD~;p;3%XDtR_x_lfF +QeG_7VDbYr-78)TVAS!_m@Pd<-lu|t+&S`K8nl(M86S^h~`giso9Y$#;2pVJjjL7ouemZ5%BBkl&47< +MX}h*hH+Kv}`Z@jhhHttUpoh6Z#P4rGPB<2~Vk%^waxLp$JNhrcOY$p)-*C2~Tmi2u0=d>edhZr*{&9 +JBiRbY%Q=RC2p#-ce*W2@y1xm&*G9?Ouw8%F2u$lUyNzG~HX6r3B{p6=s=03JUlmv(GC1_LcU?T)Vm^ +m#ZuPOk;OMuR$z7@)&T{-}@37!|eC2!IP!r<5&l(t~=n>C-6$MN(gOwOLR31a#Ja83w8xo(n{8`*56= +N;f#Ldf9B)K%I22@#N>B15eG0qVwq1-YZ)iiv`?w6rP!9@I&IQiP{9KJwjaytj;-5}N@q*do$;rUjY2 +j&yQZ8*g~}G-uk2)*$r-IV<8@9!s;|awH6`$x23-zkM#q{>O@lJZeAFOD-f-DZlSx#Ml?d7lYs@4HPx +ndO0v7G04$O-WxIK{5DoqFpa$cuc|1)gyuh((3RVhWhAY~WZxuGEvZ{6Xgk-TCfi8xHZBV^6Qr%F+`Iu@V4Amcrg@-SJ8z +5@6P^JN~QDL<5j?M|C5ujyEIWAH=)%$hgy-?03={i9iSvja&_D3=wu$a-Ti0$)43T^H*IY)2sSvr*dX;sG};t{2P*v +gdpOyoXl8a+_3o&@fbATZz3&?G%CGEZZ&!S}s5mYW?90fuY^8pZ)4X|VJ2aD2Zm;r-pQ_ao`Oao+sCd +S61Gym%gvdS;D9ICG5Src-jVKT=|c1#?4}#BfsFP~yAnjcN{ep*>YzLor&v!i4yduTZNt5_g5~jM7lV +gTRD5E6jA32lCl{Esaj=e~r;>MxG6WnR)#QxND8ePP%y%Zq$)8NmBaP;~e!oN4$SGPJctLQz6gADd9wn#V^S&3(MfN7j(2-9?P2a`@y4AXR2_@UhUIO(gT@ce4n^c#GvqnI7VbCD8Q0olX4;ZjDz2 +`&k7+9)`vGe6XIbq$aoke*?;*Q1i-N_8nD7#vLh8GLuZlfU3qIbCAjmz0<2JQCHD6Vebtd8_dlXqQ*0 +nG^_s!v5kWR1$e2_q3wS?FSSS>J@6YqRFpNx1&PLX{vBHu5sa +I3pF-ErRHF-<7DF%Hjlz#@&`%-gvLK5m3c+UN*!qivphP!bA^3N?nJolgr<*&4U@h^3r9$xWB)qv7IH +vvwgy7HVeYp?>JY^!qyN{*#lx7N+0M*IwqyA0=NRY#b^@=5J6RT26ICXDyQ6ETSsE+2hvNe1N(%>b0j}o~u#_JqZY3?A=}{1ii_ +8C^kXKfZGam03j+vuSi5V}BOFYE7FyiLg&Td`VD&_l?{uM%S4Uo1?0przv%(sd^gqq+@M0`{8!UDtwj +;rt@di}5_EkkbyKGH7R0LLW(we68|nj{VhwNP}cokPoEf!I>q?ILMO#DC#7^9Da({iGQ2IV+Tg+atGk +;_==hoYDuia*@;@nBz7hbNUyW&&O_x1Bd#-Qe#4e;8(!vcq8{ZaVe}7;TyE1@C~s^j;^=xNfbAw#hXT +3#(HGAJfUuRNHK?ZtpLFxqpAlVu&OcC5JHQbS}bh6rC%Elm;^2kN=qp&hx=qNyQP0HVwUT8;H@9P@ZHfbl!g~ZzR_E6h8E8?(;cr4T41M +Jdw)Xy3svp}jVSes;Nt_6VKrM*Ks<46c=K)8T)(3@Z#yrD0I;F&Wh3)BObY?5vH79pr1!q#`eHmS8lK +Zb|EunLbm8W0UQ{4Kh2xlf8)U#10|dlIV@-TKh7)4ivrH&9YFRgLMKtE{c +B66O-f-_0CvgWYwpLDYPLlWQmeJP)(U1IJZ^aoIX!ZAC$-l45zba)>_PVpfkjbS%?z@^~Q-bKs>y_;~ +F$~S}}`|lT986zsUgyW!jWW`VJ~RARZPxJKw_sw)`ysfV;jF~v4MRg_MX01-Lr(_+;TP4u +y!(OEAYsBF}JB*3Dx;5XK@$xuG>sK*QU}%*{h?4y*81$K&`(=zE;@H-6BG#PcO8*kMXwx!6N^%wvYNdar5E_%j(Mx( +%K>e;*V54_|p-8;(MMkvOOq<^xJz_>2axmI$Hc(Q9sN`QC7zLMAN!L=5WJ-^sD;&PeGhSX3fBCHt{Vl +w#M;Z*Rq1p@|ABjII__#d&DB~j{)`)bFr#uWP5z)yX?ltpH3&9ey_W~w?7Kqjc2EQkUa@F>m<5L+ufJ +b_wa`uYARFR`Y8Z>P%+@?q*P>ZRw>qH<>TNBCHijx^xBXv;P0o>N?6gKIJ?xp@>g;$Ck5hhd!oEa=u_ +)SiTlKZ+aCHfNT5aA}0QBC!Fpk7Q@OnlWucawaR(mzSQW>XGdwMm<%@tctZ&hAFOQujioHfj5;!hd=NM80C4uhBsZ8!oCOxrT#_Bj7WbtBJ59A0R8udaLY=M#@u{E}g +_hA1Y23@S>4v)d7U{o0jlC82>N| +&bxCve7(a1OdZ;76eyA#f9e0T_W3bhQ`gO-TymKB1!<`}ZO+^SbwAPH}t^{RKT%!2k#r< +qNW5*x^sx?}ENxvVu!nfa?>sHz=r!Oqt-WX^d3JsBL?l%}m-YsZOOZ%6y4WC~u1_wgHKQAo +z7t+t>;#sdBgSo6=1Y-RS8?S=HCJvd{MHon8zmX!DDx#7?u8%z1zoGnTuQ@u2zVUfcU9J!^#*9v-Bfj +Ay~lM|GjIz|G~~QXn<{L)+w2iGHhA3IR*n*LfC4q#8u2Hi8>soOPW6{AvgS-$pBiikyv=UOAXNu0HHs +~{?3QE@@A>IPs1g8$wBp<|)2X&SO1hRp6J%p|Wk6cK6!BJu%ed416hHZ!vZlN?JZV&ax8x$(L&(>hTS +6q86r<2%ONi!G`gu+%XTLx4LM3?v-IndoX%>4eW_d{U5c_&h+Cgn7JSNHZ>LE^Xy~lR2wgx9lOKq;RV10PvnyhrSIb`t7O~*m0GH{^i|WT>nzd +^)FdIwgNW6Q&W8G;DV<38J|;qjeL;$W1bdQodjSWAu06@D4U}_`Sa+!p)K2liVXF>Y&wp8kvnwY47sy +U_d&*42X=m%7mS-1D`gR0yfoo@`@!pU*`u-z7!<5WxSah+ +WJe3JAXg=kiedaX`%6;!TFekc_&^67!Vs_^CYsY~Ugh-J!0;C9mGPVtAgA_@a{ +5eQBx-`3Emphnjuj?rZ^dnLp}&R)gWEI{m1zTM(MqG1Z61DhF6UdP<=!fEgIV=%^$VV +5aSX&5Qovnh%KjKe54rd?9!l5>d-;pXD&BJCu{ws%VX_o<)fi1&$g$O7}2@CZVofcP#%C-+y14xVq2@ +oULP)k+_2rZyS$_N5m{@0eS+hO%C6m;VD?`ath!7p@|<4UY1|xx=fyyUuu@82pjW-jgr}Ya-}1u9qPO +x8#+{-mh7{<%)vE@Tw&f&mYFmE;A;LxzA@+9S(WRUW|$&}Oe_Q2S;_|V?sLwk!4?JXRdKW9HoH+0rM +C$i>>Z#SpV$|VaGVKcVk%XtT6I{=L;bc%04V9m!}EVOdESW3$-}zcMae=i08_TfnS^?t;h488PbbFlL83lC=lir(tfws23RoXE2Do6mRohvtMWy2p%KR +hD?2k;|`i;rdqYnuTGm?Dn-MIhT&^Uy;(4$*=zfTTZLg*A+daZ8(X)*Hy)-8wuU=mJb;puo$a@hJZKs +zh4g{{A#n~bpa7kKlYKVnV(qpqVqS2<;U+98$i6zV*4>=j}-D6P5&1e0yrXY4(EUvmmUk120SjT%8t1u>S?50HCO$^QQn5aTc9P>-#M=cWJ;=z}(T)akBuUDue!_hvw!x$ZC;GDh5Y` +|A-J@5d>unQR=sicQY@6QWF%L=TSu#A^61o<9&N}Zo_nha%-u?WT(Yp+zcbFxOcP}&6JtK|xZ)dE39& +;`E2D8Qs{mW{m(3|(M(bZBU^H--!{HT50O4uN$T|K#j(n`$y@eqH+YSfSUeJLIC +SeGC{0#ZCy*@V-8{d(jf>I-I2|ALt#PnB-r6K$P_*$Za($ipQjkYy5%#j1uepR%qWIRrM>mhfgdy=ie +G@3{rV*mEt_qH}kUUvu1F3s*|S@-zHwnBUzgUoC})p>q5x0Q6#bInf6X|ASf*%j#w81Z2H|Du=j +4Hm_JRVc;`BEUa$HQEo4`WmwB)U2vAm>ml>-5i{3wvk6O+qvQJw-5*eBZYanFjKt5N!i@Y$pc!oztB= +94xshl5)9hm^rsZajfANctc>{pg~*|&Jaqsy7P<2a-%nIj>A92=w04IezmGC=PXxyM;wYd}`028BN50 +lTS1M9UUaOAQVK@0x2+&@F1N>B9>YC_O|45TSCesc8i9WeP{JM&h5KK|YAz)9dhC4P3AZ3OrchGxV_=dBp^C5j}CMUkx!)cWm=b +n*0hN9zNth;07q9+GDHu@h~bLTSYksWzH~q%D89#0k*6>7HXN9Pt8)*U1x~ZiTi2ea*+c{%ygc7P%7N5Lhz3?tHs?aqmD1=R-PJZ3S2JK%?H_NYXwS +1GDq_mB1d?Qcr4xV-Z2kN4CP!s6$iuieLW;Chb!1dXzukVq8C(04%evVI9(IzOmR<#GvO$?S>m)L`LF7VPtSDa-Ow~@FbP_|A388lg17| +K4P@VqxOT@KvGfia1U*>ObqPHdVw%;(`+R{N;;CZI1`6ezz+B)6b6Mu%}4;nj3G^=!^DCSQ6#8v?Pn+qSlewl +cSm^J%LHTholYO(C4ziXU7g02_6HNwOF(J`K0g{ed(w>ZC9JlV4x+S17m-KV9Ck`EuRV#ET70UBU(ZB +vTzT}W{#E)U}&+tHdH7@=g_O&hL*^~wnYmD;{NHdxBeTCt9Ge{CW=Exro~u^I)ax2QRALJQTQ0v;ZBo +>l1sc@=^s^fG!M-^vJospfDAY2vM8KQ+YyLl$D2C)JlwG0@9Vs1W=bb=S99)Mu6=sx@ +-~4HKSNN_xdiW#-;)f~7Lj0h~b@GFVS6-n1`zq1*o)X+bf}`uv;N;2sPj^ic;i(T0-RNIEf{_BCdtSV +|S)>!ST+J*qo~zJQ}%iU3&+Hp^l-f0nty3JYuh?#e?hsJ(={M&`!CDq#dbY)5w_zkrqhkU#-NeOEnK*defRgQ=w$UoqcsrHc9fOra +T7?|+0~L>|o_=koc;R%X8OF+d0ij0ha+GT3w6R{p`WMtBXhXZsW+oTkC}3@o$b6cVOmzv48=PG@;&_d +aOgGH9C;(^;=^Wf?w?HHSCwMaE%yVa_(=@j&F!R#=Y-7F-Gd@ebnFP;0X^iIn<_ZLnfi6FMsF31{AfP +wtKtUf5hiB<0pVy`p87Zc6int1NJLwJEK6)^vQD?WwIQg3UA;3n<%4rO2&)yc&$swS#4Tn`$;ycnW0K +gF^6HV))T=0QVOv)%@kow@erbHnJqS*z!d|mf+8F?-C+Epb^Y`4+>N75tbfI;`6bDIbNhtcD2+@IMwo +QnBuyDl(<5aA|<@T)el}>eNLxy(-JzIdSPeVm}559qO{oDZ?CDUKgbu7-<5On1ba@G02IL +3CUa!e4--{PpV5pB}xO7M7FKGvPrSfk8$&SRXzeKT3cPL*>M2}Eu>5)>&a%H2BCA%<+gt*>|Y?Qs$Yt +tRy{(@@;|)Gy*)xT3&137UQKQY4Z%-=4^eyaUdNRL=P$cDXD7Ov>T-(S0?5*(oF2JmI%IysN(Re4++caabHv^~#}kjLm;Ie=oPS^?8|ULDy-E8q7q+^ +QWoWs;Jo$pS&|(%Y+WRicjHJNLePR~w3;B9hkh9tz($`K?|A`6>cBdU3G+Wi-%SnS;2!B;B0okEkKSn&){R7yc9g>Xznmze9tnvX^HYg<*Yd{T)iF*VcjkAB)u7$cWL9ip(BA&kR=ILaSVSc3Lc%J2Q!Ce!8%5M +2GyQHxvVK#iI!osNN%!*IBOO>1AMJYD2gwF`y#_)AF&Q)j;3)tuF6TABSH#N`TJY=S^vz18Y(qMs~-Xxirkj1%HPKLp3OaoE>BAq; +H{>ap<NxPM?fpR6%{bGr=05lnmNbU@1}WvWpiDOfx9c-+syi?IBbz|Ff>E*9?~J +)iQnIva=>FMVb>`(3sTk{#2w8j>7ClG{QVDN +$d$4&=uvszB(GSX6-}iBSa>#iIb!8c_w7saN5IVN?NRC{-ssg|oD^>gW#gnw{}TVn~4{i6I3BD?`4f* +)vf$B}Nq3-%j-g^3>gMhYg)FZP;}{cN8+LS%)u;_{N@zAnFIpH$xJpR~_c-UP_ePN6>}n`D_|>lH16oFQx!ElPKf^t|8 +F>d)KhV{M#VOncpkeOk;!=*LbBsu)IRi3BsA^L?9q<-pQ*u~Y1*H`KN^9fb?ngZP~fjNn9(vKPVJ8u7 +L9H2ggeYekk|>yJ4`JeP!$-hg44A9*;44rV!n+gK=Ps=wP4hl$Vf^xj6ZJSO~8>KY~IDps4dE#pI@;BoS7xGYb%$tMG7U{qaUow+0ARTi4Pn8LsE{F>cMo{0KgIq?=QrPC +R%+Lrca_Oz%B3tOl+A$us1B(B=9M2E5yOlO4^dNdOtU5-onxqU2$P?&`3qa`{Ge9=Q_(vqqw1GU6u}w +>bz4XopFiz3tlt<@vvb_{{#`^>=N53uvzo8?dIrjA)S8lCY8}%=OCJavmS=kPC95wAeD};7HPGKWz+j +wM%WrMK6d7KN@n>G5Bep}U@!p2VDCbRL+C%m~2k_)RQNPX`spQg$w{+vcBY?myp#jMq*bl>c2jHUz%_ +(Li@Sma?96%`vc;K7&DXu}wb-=ZCITJ10+T3~OJFtN~tnc%;#<|KYbXGJdN58n>E;dVQeD}-wo{B=Wl +xTx^g1AqO2F70QAKaS8e4E~@5{O}@h(}h1&2(Bvr^x#i_prfZc!)1!jJC8Ss1-s?pcBNf$1oJL%xzQ1 +*wH32oRfHe{z^jEq2o-v-CIRtpb%w>PC`NWsTFr3FK`T{VQ;DYl5)%Q`&3I0M9vdDu+acfL2E3T~gy|tGdmG4Riqpn^?T`K& +js{{$nlGAIV+ +DPPQm2SJzU>`C&OL^E9i{x@%oKQeWb~@&`Gr`G+2z04b-YBxJ7H>C-w&4YzBW@p~o@&$eJUyJR?rV!} +_UMijAYeL>uG-(jo!dG9QjefK&uu>ZtluBWJXovizDC`ud^vFr`{7CF;~-)z?JZsJnQnX4ECiQ9-kQ@ +$#Gf3kJ_j{J1C?#Db&W1e!O8jxJ#ueK!KD;J*QXGXl=peoFPok()}vvKH|dDjaaI(z?D5n-wn%T2M*< +wDv!=d|SIzRP%E19YJEko%o}b^RiS8~@nAHUcBiMtI4h3W#r}nkRSSK}u`ap*o-5B$@Sjy|9*jVk$P; +HZ$QH$le=CB|Q3D9nCVeG-j_KWq%v-Ns%0p5eZg4^}}+#%wPtwQieCepVDF=U%qJ2X~CRCem3jF +0(hXsLxaD$sOVi9+4l6Wq=9_paS{!&;hx)HZ5Mx@`0ppgChM{q(H0&S>mdMGB;wGY$!8kunaG75{hD( +8)M7jj}(9CK_N6_I%)8A$HXw#deovdU-|qGfi{TgP!8jCN{)uOms3@8-RtRF$CGp +1XawZv;`t%*q-Gl+Xs-(C2-Rg?bzKGY1vAt3-X1})9&(tXHRv1}-Y-GUZmEzEhSOxkHV~p-v{?!HDvF +ZDCS5ffvhw=k3uBqSG^~6C*^4RL9OVi|up4v&Vn%8?vYdzD4Xl|eT<3KvT4p^HlX<7~djA$?qJ9YJ!k +D5&Q5^8-rr0cSPJKov~w@??1$?twl6`sj}ZJE@o3PO`EB~LS%G=E*`Ey}o`Yd5f(2P$Mji+}9LOsSARd(s +o0Mx2CxVJ%_<=Z4nm$u;C>2O0+wE)-3siZZ-)>~q8f~lVA16LvFt3xoWZ>uD%Q+iFe1L&s9lFC7A|bv8I*r}k36;5<*n(~l-X06;`mIK13x +#J}yq3?yYjUONAJ9U$b2DyEIkQ4681-)fauEUKLxTWGUxQHk0Vc1)lMB(kUA7xhXc6+F+#$mYLUzLd0 +_Io%%%q9}dteB>PLBQ$Y6t)}Ke`Y$yEtr8AS0;B0M6fp2qcH=6@ +^Sk6i{1nG}Z&$1>1l9H4_Y)oqVr6UR<=&|I5sK~c@&Ri_c!YKXES1!<4QT-&K#RKfVSueP-g*kOPA!e +nGGb}Ro*DeU2*C9s;TdFZv`a4v*BN|D`4n={h6O}0D0ZKaZ9d@7e)(Hi-qQo3;Q5d>Y4_-kVG7b%E8y +$dO)*NOmeDQ7R)~scQ4D%4kjy^O{sHju0KAG`c@~fPwXtem6VaHA!J@y|#)T7Qd+DGzzbIGTp#7G`i1NF=)2Scj2CyaE+>Z$to!Oa!o^+GM>=R~E+L&yE2(Y?J4{^qIKFY3Bl))4cLeu?`>&uNHHd)L;OfAsU@AH5;LK +YDX2f>*oVTa;A@Y|H$0AZLVLF>$kt8~7)v?0X@PyG5RYoa-cic>`p;avesiZWWd8i5Nh(cxgjvu_f4s +8(720x{zhaI&_vR%wHFnilrxD=`j>@b{htoB}oZ`&Hk*Bw*9R9YWYg_XItalzT`W)EwYxVCDrgD_PCZ +H9JF}XJ7b>R#r+hKS8(AGWNY`V~u8(# +e^%>*(8cjR8g9l38$AQ{VdgCe^j@*;5WoS_8Uo8B11&O2F9a|KFXi!N`-0zwUC+ +^G4h7)%--P|e5{~pF;HI_XIsSexAemkeX+FG3z-)*MDwAKkhTib#A-lC${uu?ZV=H +?TnJA{qSqcYAB~p9{G~0V*L!$vr_%ojXC=u``pb|Guc8IQT1LWe+zEO#cY?N4T7nZaJtz75&$eOyll4QMo*(|-Y!<3c{osnP(s}+FuI0S)#xZPti5c +|^?Au>XrDxWtgxEs_N-xC0ME;Gj*D*3&AUzH_6$4y4EK1h*B#TJA~V5i4}QK1ou2&|wzV16fN<-P*xM +Kj2XfS-*YjD!>v;nC`+CTy`3JjENgw?l)-%7F>>#M)F`ws!^u8_eeYJZUxjP?;xjP@YD(*{45z7kZxy +pnP`l8bMO~=);tw=?Xn+D6&yTdHo@gXM`8^Ny)gVZFMvN$!BL6V1j1EAVUPtIbfH0LYG67zkAF^cS+z +c|$m_|ZG&eJSqyTmr9RdgaUSpdQ%dEci`6w`i_9zn{E>C7O(R^1~s0ZMnpbd+3ui&o+>eQSqzX7GH12 +Z_%jVd&t-gfveg4S3%I6LTzJwT%!XKDo3rqc013k2j0-8h^!Bt&kHfQpwV02QR8Ei>QlETIzV579hS! +p*33uyJN%#DBSr*XY`LmxB#YQTHuB$}KP$kT%6wuGMm5^b*6G>QT_DS+xB|=!dA<=gg83#1XVL# +(OI8H>~HQSNFNc3F=`0&a3@Vi8w3ct%x+&;51@FF2#?Rl{}e_0?6NFzRHXtjVD?AkN%>!gra;(CsjW? +Ua|h}Emi*T#LTj|kCH$S@<1zqls;;)Fn|(iw||;0<^M}ur2Wlo1k`ZNxJXy&rAnF9x%&pF8L{{a^TX-0Cw|1e}K7 +bj(|N1AR7E=kYWY40(a>(p$F2-KCdP2E$$Y!B{op3~y=V&d^8NhcA>p$$ed#fMF`hIT~u=&e5gOsl1? +57U6diY_#e#Ir63OJ53$|zqvvvmDga44xHsxMNjY>bM!EMTzB)4)aYkCDJhD9*|1Bi;*Qa!_~ZAoL$T +3tvi@hts9w7<&R;eDL)Q!Q1J6f)gP#RCPlsCmK93EyOTA(NS62t@YTrNNYJA}0?*IfwfzioiX532DRb +xJz?)XuF8)PA}#W=E1PTNfm7fr}d9)uc=?*$lN_o-fNvz?8Fl08=sS;qB{F*e$et~~5BL%PaRtD${q +)mRemHLzb+|8v|A)UDuNI*q71%le{9-8S;uyhw{p^$hZ7&JRY-yTpmq6 +pG?wW^bMam=-bgBRZ_F@w^ebM)-+4xZ#Wk(XU9dIb_D*_`o-AocLzE^cTDehgj8$)PXP$h6d$TMfmsP +I3>n|g4g>Hi|$R3(o$#pNlhxdZ8>jZK-yLtN?810conCgz%XT87NAZM*o +4SM?nin)Ed-cd|_jn@rUlNZr)hX2WUnJ7h`X3pQrZ%1vX{u3*WB@&qQ{-C>5vACOJ=JeKzhYcB7+0@xy_}0Kp_21sPYtKoQ`f~}PwkG0Ew`Huv1M~iZ261 +Xh&|P4GGb3XWyGGEOQOrKW1`EYX4Ky;b9v&inwANn1u3yuO;=mvv6^~~SWRVdekAI!UKq2gASUCzpbZm{pHZZ7 +7QT2+ego`J;rO9X}#v#IAbHb@A9$`=f^j&TTXngi53XLyAH}BZ;*vUkd`l;#eH2ht7*bwV2{ip*Doap +-RWH%Lao`dD+?M(*qYqprY6Uu`pDu2T5kxIZ>#j=nFBaDt}-w3RO!y3RRlfkgm3y5~5K3&XBMkAPMXB +#3)p#2#rUfI+27yQ{L1hm{LA+oYirS*!VK!oBwSfs@Kn65QvJ)R)`F?P-v;hBgky6UW0L{@M{UKGL*G +&dhgek4o0H7opO89ztsHa1fshAlDODaokD?83KN1+rGoU83ev$clD;;a6O4)~6#tcbt|0dRO+>087?H +|7Aj}=WFh-RZ3rKa2K<2!tKxPqR64~1n^k*<6l@tp}n_A*@ +#E=9IT_no*%CIpuxMx&AvqETJF)$|?FsGfby^#2-->ev<(y5>;@-x-Z+2W7fCG +1JAPQO)4Mgm}ryaUo*G=%G^=M5QW=o;r0wRH`x_mC8?1sr>P%R3w{SEGiWVSo_sK^pRvX7?r9lK{ETO +kD9qiR4SwNl0FQMeB?VrQY|_sB-M=*E*g?*5f4fAIq;-|A*ra_+BCW-JJF%OP@5F*C^bJQm0j0Iq#I+ +-hB{X*q_9-?42GpLI{Q{H9?&Ab%hQk{Xa}%w|NVx-N--ea(nzr8WM?P*u +a?p{i29D^%6jW+PP97iJ!+s>}SJLRD=v|C>-%zc8N@s%pLYoKRIyna>GTh1lubP*n@f38AX)Fvmhwq2 +kR$TI%ydRgvaIb)BF^i0+MtshV=JFjW##5p)7!1CSz-0mives-$R_DITRtitaM~+bC5efua609;K=@i +pMK)K`b7ns+7K>^M4hk>TZfsbvs3=x`m=t74j%m1r((!FBYO|S)vg386O#ED(nbK07GN|hHT<_A*%Xu +)UPDwhnT)RvfAin +*n%BTB|LU+|oG5?l{FlmKL7l1uE^$K(EZs`&S{}8dE)tB8y4!)&;L}fL{W$K;%9jq@Uy +&)ax;C>ts$pTo9$tHL6X>Pwo^FhDfCqq^*49Ynds%JFW$?-d=M#w;5>5321qqVA1y+us9}I!^G{h8CO +F3~LP=x^X|vExcA*jftIf~$c?vS$0vZxn#pmObKg8!_*&mGg82k!xd_Md+od9pi!!I0m+J(c8e~s7}_ +Kw)Fjd07`r$jwuKGrDI*(LfiOlRO*3EuDPm-NLJpx-bfy1d7WzA9r(Oz0y(r&qk$i +BS5*n;Hy+m-2@XJh2F7BCd$mac#JfY=|8KsH4Ki%DT#r=bbD+m4re)KS7NQ8HerMM{+@3fX6*9dm@4`N;Fm_6*O(TvH(!Qg#z%%7v*U$^97>Yi;CB2VBM({3r{&^1Gm=e+| +h0=cU7?PlJe$1T9oF4&S(<{it@p;TL<^(Z4A!MI-i-Uzo{n^5dQ0-53@+?3 +*B-vYCWkOP`RYp3wt}UUk?ygDT~aK_DB8O +mKeI~Gy&3{1SKg$+a^WaVclmZT~&!mqg^_5GWVi`zvd62(*(zVXSR!`j5n2cd +Cr~ODA1c(lGe4B&hJM$H#AT6x<3e|B?;)&|T@#T7ckCMJWsws$}557bct_#yThDdRSDX1!444pC%LAN +P`5co_XaSv-8-m-HTAvIj-55EhFIvVIr)#g-}KA=wZ3QFHgm*q)s;#hS|y>7gN3XElYX#F3bI8h7|O1 +GAysjwml?$N&PUJfG%BWdb|G!un~f-EH#j~1ceXH0hE*98-QOGvr&25uQT^i_p)>yYpZz6BG@z)`hE25I;$ +wZ0Fn1DG`)*>!FxnD~I39o}7ZW)w^;b+g-wc5@3R-s@(E=LDTo3nt$0W{2;mIBzMK_?kzX9i9hr_AHq +AD%io8iJ8!w4wXhzoPH>G%Z3!)j(+>$HnYCaelu)`foNZ-lzdF;+*vY#Gg;S=SBJ>&R4_?DAr2j +iF<8e^zsQV(xUj@g>u5Nl}2u_NYcxtwL7L_}`cJHLORH>b`l|L*2ndpBP#FigRZ?nBC2ZW%Zt&7(1F4 +|P(>Z0@7y66pqXooRN?V>&FAR@;;h_v6!tb;af!b9Xk0$!$7K8#d(EjZa#7tKbnj*l?B{TS<=U*3oBc +WtE6{hq#GM5r8cowr$0^=?X{bjd8W7z?e)#h*dTHCAafT*mUTlz8AR*r;#wV+T2cqsoiFjeC8NerGw% +9wiy%Q6+)uUh&!Pxy?sVs`Rf%q031*bv7NHdvx>wu5m?8VqciOy%d++pi0&P&uDWpliNqcfLG4#(^n6>qN3z=__Jiu~9mViRV0e9>`7|KcXGn@}3B$&V +T2pf9laBk78ppd3=;=>})8atzb=uJl;~S;Q=ZwBaI(m*-fa6489ONVgp4Iv6Dc=|&0G8cx2gHP{Yd$g5~6HJ`~WBqA0jP*Z$8 +KsXYOWs!X8DQ7;VWrn?AcPSm^y*T2)?!=@b~{s^eT^;{B4vrgQ-HAxUqhpFS9bYdo#VcROB-p}`VO#{ +=#>FN?4`UVLYf6H1zcA0<_M)BmxS2Ub^#Suy>;025PgiLRNL4Vui!RwUO^S+VL?l#IA6ui;N7TlQ%V1 +i0e5<~z4$3Ik!0C*?l;9Lp}m(<(TpXlFM5HE;2;Va#1Q@f(O^fD3%h_qO^gsxhcHq&eT7P*rqWrOsAD +Fu8cu~CCG^Y^>=a}u$sBCppC58bCq^0r4UWNJYCvVvY?o~M&=T70{qd4?+Il>|^dpsDR$!Dc7xJg3+n +1<~-2HJ=sPIgx(__hDH+iPCf8R~?N`AuyxY0DVm)5#Aa5)cKJQN +8r#`1=E-2TGUtY7=0rXXI@HZ)nb0qO$mKlXtRxW1m&V$iVLrh2Q=3f)@%69U)TMvAlxTn@4 +wD(e=(xq0xl>-DaP0GjnXBUI#(sgVYu#Axaib%sxst^BKsPcmp-%Kn@cTtN$#j*VkX&vYe9VYQzPMO9CK}<3k;Pqje6`SDk!S8| +J2LsfL+n-A-QhU96$*~fwGX$&0+ZC}>>bxssS|aOB!MKqHR1u)IHzZV)F>sUm`WVw#psr-$^RRC$2lMVa|W23HvA8|~tBV{ExNaSE<{i?> +W6sD<^WDR_5995DsY>59Xr5O36n2Xo*44dJN$LHg3!!}uni;dS~O-^6V$bbfw} +AB0R68gk^ZEQzV}agO4Ng$9II*YxU_&3H{@`~(g~zp!6|l!r=?{Jtkmqgm=l)>m1Al#;et-vwK7jWF2 +RZXrNW&Aiw+G5W52-Z%>USu5V=c=c11gb*U35;K5NDDSjtEj>QWewAn^1!%wj-IM=0w>jePCj}*@#8h +@h8;N3t`0)iea>BrOLq-u@#PZdICPKs8O57_8YJ8Cf +BsKkBuRweXr^XWvchAS`ERGlCHy%V%;VF +6NZe#3Ghqp2RRGiZn)oEw})d|x4ddXi-iN)R>Q~KtRni9VT0O=+8wP13XtZCau9msC}on|H`-?+UhT- +cUSt1m`TJfTR@m;4^rUW4$*bL7(sxL +R;~I&FV#TN~UJ*`@d3G=M>Zewhl_Jzo$EH`a+tjQ@n(?!#g=RM+@yp5g|r?W4pmD +5l@fRej@eQn2bI_R=uE*eaWR`}9R}LT_vyUhSadoA?|!O~ne@dE;y6*%2sxzE8@A}C42s) ++a0oWJe(~($Qqx7K?YMICBF-{3xH;48zm+n&(OIlyEbeJy5Kn8Fv1~V`%KRry>3Ki)W0}NzG{eW1qz$mfIL^!>tDT1a+7^`o6ZI@r^PZ8tH+dLcQBd) +#68m8^hNTm?||_Y=H{KftG-50xs{+E8BPo-(ZrCgH(YsG)i3ZIXvr!yAIJ1I1+|3eAG`!(*vz3p-V$U +-e&iN@s6;hJw!WBNiTmI!*2cAaYb6MG^|t<*D6ljGNxxa#wby{^@$lzX}A%;B#2QFge2FQmc5!|TuY6IBC!Bp!hhq@VTsF6oPl=~ +~Qjc^@v$mX0iYjB4%v=uOtcj>EgNyH8ME26&kBlzcB;#66|npv^yl(-_wc+CEliNo*^7>I~^hE~=i_()vIR*_)H7rVe!%1$aX&2==FiJA&z*_a~BD3d+zcwY{ +y-0fJy)-#IoN6jF>Gz#vPr}MCPonpQ%0)k_ag@HQHgQR~Ve?d>G&?+8nXOybQ;_s{4^SgWKKj>ns2-r +i$Jq@!YTR>`g>p!-8uk9Zh3{`1jfKz~tMvOSopD0iY&^E!S~6P)bv_j0$tW4)3>DI5;8BWJdc%1gu(3 +}@2~0|SJTQB3*-QH}~1{1R?MEQG)ryJQ1hu^oExXm99A3zd +s@cmXxto!($p@Vqo}Q+6oVMqE}wk9|Chuc87>+{5d1`z+(#COfiETLvV*dn_(&$$mVw`IJL{9y#s1BU +&pkeq6$~swK}>AcdeGzCrnHWaSWypr08D<0(hEYtSxjKt&wjeF$Hul>0j5wI94y$leWhovClL~B>{9( +_p)Ylq%ro=#t2+ig91Q{f3O^Pu&}vMy%xBGrG!qfuz=8hy9;Rrbk97t(LU4^vqB>pBx-?~&=-xT{e(- +WYr{}fz@D^2y`_0gPI1}I14lFI2-6zN@?b2TgD=J~Egv8^Vm2f}tAxHZi%m*=@#^WbR0qYehJ5H^+P! +ZfKeCeTtO38i{;?o!1G$Av)9=&XJ_IK+u4gIQ-nL+mQq)}gPDau{9Md)W4}|8AgctCfkEqWm+a3h1q3 +Ddb?0Fx@59a+!EB$RYzd@L0gaSbdO2o%p%XJpd<3jTpbJS_`;P*sXkdj&;+?=8&afVRwO%UJ{0Uyorf +q?g7s9#Z&;a|6A$*ztI1$(*Li}|9__cU#9=J9FCjsc5N^TTXLLGvJ^gt0JFs;=p?t7VkdbN +94@#0vC5~W2H&BO_pWp;PWe@lPd-73p( ++UdY-72ji-?dx6I;unS6_RWStGO16N3&i5I2C~KZ6k;Lq7rBOyv&|_lVLO(I=iQgvqBC<=Pf;f|O{(T +>@-Nv6uAsYT4D?OdQ%>;UO-*bTncn-t?ypd-DqQZKft#@I}D8oM&`p-kYAl$DX*#^**%>(A&Tg}q3Kn?mDN!dxosfs8Q?=GGOapUDz?`MlAaxO8={0vbZKx +LTG`-$omhd#WEMja$;rl7q?G7&|Z0RY<1mWUPQ7<^|Wj$e}066YBSlHU+a4_zn;i{w$=vpP}-G%NTlX +nLxygQ&TRhy;7L1rm0%q)FQ-{Ns^V{zEVB0IFPq1)9qMkGgOl#(DdB;pzKpNpFu-q2hmo3?qW4ag5Ir +0*HZfzVK}jqI3?ZKR&Jk>iks%`04$zr_+;zjhI{i*Q%F7}FJtEZ*;8kw~oezqr`rz7;O^(7Uk*T^${?yJ>wwJ?`T59*6ZF$r!VUD!*j!VM`yd)#!v)TEmNY4JWxB&2Xt$PFdDY?NM9*Z-F +5t;0@I}6&^4;NMKPYd+=9onkuwnAhXg^8!f8Csc7(W%wdS97y +?4CpFka{N}NX2mV@H94rVn}9}c8J6F@Qh2eiy!KQ(`D`f>~)T~EUZEmE={N?`_}#8_`ZH93;WuMurqs +NNi7qXl*5oQ1>&G~nw3?(vlg1wW(K7yE1Uw4Xjq>!%Mh`NatZ71+%~HZBMS4d`Qk9HmbHOV6S|>}x^A +I~K{}7lc+}U^EAzT^QOK&~7+t>xfp6P}tz%R!Oviz)tI`B(;K)+A659L5DP@gwwQDkPoXM;+Dh>){n`r1xwrrF=GXR$T= +qnZGmthEE%KDQWETVpH@$v(xJSxexSv#W%UDfk?&qjtbQuo>c_&z26+HOhRZz}5-A5##9#Je7 +IE^`W$Yab`3Ht1$=@(!k^Ct`Qsg>@WXh))5+heK#3sMRkahB_%Umy6$j>tz-?5f=GaP#lxs2i1zsN<) +Tn}By>llvRkDSf$y>x!ca2${1WQOCIBnu43-bS9xaC{D1j$}BVN0h@EjuL?!#&A3zCkHUR5plxs7Q}z +Nlj679aQpP8BQPDsbx4m%_ARSI6mYf?`JrDgy)ZUw!NOShv9f#T;9oW6qn^~496py@j7-lnw*(?H^70f1x*`$EYQfAY-SWS)gEy(?DeM=a4yl^k|`2J$nrGU+6%;qq&84fme%;qo5CK7 +B;GMguuO$^u^VK$}ACJt;4GMl@ZjQ}=#nN1e6$po9Xnax6GV*{I4nN0$-SqC;RGMlKyt_}z+A7hp`Eq +3)pU|Gg2eHXjBB(N-GmOtLnMt81{7^okJ>l1iD$9`8&aV8m&QbR37+zS}j^wuksmI^P-w2eX|m(1vbO2) +%+e6wjDpV;eMZ60E&8?Zf)G4Y3IP^5JERtZ?v0j#>9AW&lq@l5b-P-ipMI{UOkMV)r5sG~G*g|e=#!qx<1e +L=PH>Z))C2yRrr@w(cE!T7$q?EfHg{I*)Uo^(vqDyN-9o;sRv&8Ykg;AwgWFk%v}_IYLRR$X!a5NJhX +Nni#8D;UV!%?=OB4V3xQ>o*JjCVrUn2gusPG$4abDp +!T5(k2H{N1T<+wOb9=P6hdizi76vY{5DIM|&J~AN%egL2Ww?4q?9J6{4fO8|54>9u_XwH_49#Hr(6n3 +1@-tO3G9eRh*W#;q~rs@)<5#dhMvu$FjO#z@@cuY}3zl +xy>!_2559Os``DJOmF@H3VsddfQ=R+O4zMbt$dlZ9s$rL6FZP7E%*;x7giUeSs^773=a&Jh;rZ93DYh +qw!$MVx?7M-P+qI?72^_u&6X1*aEYB1%EWiBjP0^I$r68kZL&uQ`BwR>z*+bc>L7oNC3shZemsy;?_< +LsYwM;BfHdd{16|pQ4ycwBdOC4086TXB6cfJn0vE-VrGMj-E$6WmMjGdP(mBh&?ZM!z}wkmi5J93Z8P +N`Hel#mL*LkG?xv(w6c|F*NOc|H+59VV>ds7Z$EO#`e1s(uyke^)uF`RqEbRjCQuID@GJ$Zo~4VV4zJ +SqFsCISu4cW+Jn*0mye@zG32~Hbj_%A0Su!b*v;s6>~V!2X_+ikD +4s%TzZSZs|J-<5v|Yc)L{2K94~=Il@TIeKO6Ydxm`5G|hB3;wQr!}VJ3IvHe*BH~15T_0)Gyjxr&P1n +9XD^j+s%DkYdN^iwdo@mC793PRdRXJy|fS$`#(Tm_svuASy^X4GE0x7*3>2~V_RLe)`%sKpk!W?x +Uc7ul7RE?uP!l;jvP?)K^Fp}Zpx%+d>y*^2GA4=VIs{3!4`yQ8jH|j2rHv9GgtxaTTCJR(g5%iGPzzqzv3Wdv7*!)Hv#GF?2vjj&iz?>23GBZ!4g+`?jXCQ@6>OuUS4K&1r%a_Yz~syJwk#Ig-KDdSEYcx!;bq0qFEQdAWXd +BWzC_!@TzH6PYBWk94bI_~h|}@liXo{WuU*)mq-%gT6m^_B>XizUVmKk1h2|fJA0W+IsNrgS*$S&^hmR+LxCmhc5xC +kS+6j^?eNGpAgK6SqjkWcOF(kXOCffIGa!d@W-NWTv3z02&cqK=m>BE3M?{9pWt=ogko?>^uVKVP5R; +@;JfYqQRVrU{q#G_QF#X>aBw$fC8XY1xUp9f5e>%ere2n9mhe=jHFcXQJjGa;4o;Y{6sKAL_Az<$=ae +O*%}{+2hs~ZKF}G;+zkNx=SEyM~-~w6uIX^S>GP)^;o&ER;9pDi3DS!SM_i&$v9ng*8$8@1nT!P1~*~ +MnI>l$?iPjBG1K3?}`J4`>1=BjJ;MYn0#j(QTiQ~-rz@@POc9@$<*3xGsSkd7#!%SSUJ;`Www#TfC6C +u;nem>#}Jzrza^E^G|95ZvBQViK_{&Ju{T67m&DxuadgtZYiCH;=oL(0CSU^gHlEgklOeDOJu5$TO64 +JRCRa_jx8neUVYv;)nOo|pN);65GvhXs-U4j-nX$^`lSDT{mV{dxE>q6>egs|-Yq; +RSZJmsh?|y{PH+LxG11Gn7;BkvY=}XFSc(sA@V1G{#80Q6|Mgfyh`kDb1oFBv8be5fA#_ig0vvOpzpT +mD!Po~2I6|XB5+q@AixU4`svY!+NE|_%`Unw5BVl;wF^PX@y|*2tw(D=wAuV9 +m%Q%6>53n6ICoCA)--81F1@gt<>)bz|)S>k%N?)G;{i`)4_kk4?qvB1ATFwS}0t>q8_^{)>yo^idQ;3 +u!0@>jFO&4s4#?BK}cx04#Q)9v9RxIJ+u1gqv10^gS!Ah9e+I0zU(Og@?SS(?%hL_7mcYFi+ev>W +gOk0UYw=YouaGORw8igO<3la)`ejd`bSGu=oY|XZaYHZIoY{&B<1*w9np?)~xP}CVzI8v?Vm^JSHBaj +L$SR@qp@$D;n^jy8(7N`v=G_~Ix@tG{>J=Gq>sL6EO?RsoP{a(!<3I?(MyaC +Q@}m~_yKnvOK})PIdWeD#Q?1M}fNu8N)y1_hxxo)ucnXUX%o5x9+UFinv)c1}MCaKMWu|Ujpdfi{(+P@){`Cr{4u|P`AEslgL+k75rULDs(^oc?=_`viZg*2xwj8By< +tS<^>yFyWf|<)-Zk6%H#^RR)%*NKJH5}Uib&M^{KmCm}ti%wrOot~ttPQd +;!jVpNG!L_DEbNBJ*Q +p}ts&K&PyFAl2>Lg; +I^C5b=$l$7$9ACz+Vc3{tqA%q|Gz^K^d6=}OjQKkWC97JYemp@u5#?Uil85UtaC-sA5Ctj2%4!hM$zA +;3HqJ>iUI>2)dW2bZ@u8kAq+u2(Cf7Pnqp}CBql^(Pcifqsu+5dn_}oPrWm>;e^Rz15Qa{w7rJ3By$E +$8s3C_Th6Wg`mF*s?a_Er!4X7I$Cs0f`^mL{h8tuor?0?cXjN+=HhjP`>H*wX_J-KS=Zd^6AH&+df_p +Ulo4ZY^HqI9Mj`jMZo%r~SOI`=16tuz$4sfMOKsHz%z3)TGGicDZ?uy>*w`c8M%&~w~WL(`5^RZJH1E +2^QxepNMe(=S{#^tY;N=r2^&&>y>0L!VYvLswr3?zu4w=0zDjZ9sSUX*G5zsDSbpA_LDCb;CK@sgk4Br;jA!SReU%)ID3FWE0o3?z=Ez^q9 +=SnGfl^?*tqXocF?_Z4rsC>HgNH8fF{d;enRQU6~Dnmn^9{K%q3BC5!Av@_=vcV;)pGr^3@VTG>##~$ +Hbn>G0|T+=IDLkN4zN#aNs8dUI&`!p8r~$ztfN<{d}UzVWRxf?XJQfx}8mwU9S*cM=v$aD&+I7*uYor +fqZ7s2Z%?$a}UOUh2ADqys+*dwOUlQ{X{UwKKrPL>`@{$rO`1$WkaKT9gMef9@R-)MWx; +C^=NP(&bM0Ly))s&|7U^@`1OU!{q}YwKqrak~cj3mU0OE))o$V_Ry9W;lP;(y`ilc(~`_Xw}^a=DcgM +U^`Q333!=_^Gr8CFF7EGghC@Y_2`3BAxuefQn%#b{ir6&OF$dZZ=pUffxUA)25dK^gMLUgVm0wAHp9$ +5bbNl@Rr9>7A&uODdu_v3+1-h$=9_Cbbw!5e1W1uRjz~;Uv6JiPM)#8a$=nJX7k|NUvX9A}p22nUc0q +@FM=W@mZ%YL>-1r@j8vZ_zy^(yMY*!S&?-ZF|`;dl+IF#v!EAKC}Jwj)X>W*PERGC6X}COrVTY6{VSp +G_GD3u0nB_D%uWGIgr07#1Sg>+3g1U7BxF_uWOV=2(x`*-Q_?7**$G`5RHN1mHC-X!YVoX>sw(QeOrG +`h*1r?=sVu^{px`e<_y&Ah!4&(Iz8K#VQ>q$pXa3+Kdf1^_j@+zJEq;+yE0U1*A*8wyLYYgnwS``{17 +nG&WVuTNzhb|8_JalU?qDFM(d$`{cXoF7zt?S0%*v!pGrH0g92==>J{LN|>}t +tRPf^nAb{3G~`ldJnC`MLc?Ckew=^e2{^cZE+Wk-R?^<}sEbUG2jyJCA|EQRNNW`3WS?LSKce +U0f3?$IWlsxUEU1kB6lT()>8Q=qI0#Vz>N^g?EP#eKBrt==UYW(0AE0Vk_3n#I5)4co`{>ZgJv%;1eM +4%ikocx9sHefaY&%ArhftSflHv!tU^VIwT%Rq3e|pQzSlKezW`%geYA$2r?9#j6?0F7JZSm!(BeewX) +5d-aD-F#MX*Q=!-(BH`k561}~h6T89&(Gz+@^1gh&MhAmWEN)uw#4JW3eQN+dQvQVZ3s&LbAC~*|kdh +)UOWQnKqF(vf99yp)fpfB!A?XM~O)4zeTe>abBLPG&I%wvVu7d_M3K4&}WRpbjX{I%|p>doY?-$YNt* +WaXXOwythek(A4#TI=oOMm>3ySA!!L??Kxv)5BI>Ai +fl{GWFH}%{gU}C{Dz2k#k+0+7JGky^d63PhZ>66t(k1B=_BP$8b6s*%4xkM`r|`o +(lOvJUWO`NwZwgsr>zPLUjER{Zf;4W|D%>Ra#i-v|CB%-qA54y1)E4y}G%VD2<-7ytF*>DOnApwnlTz ++dkCw6l>C>VfKPwe{<)eADD(Fd#zCy%BoM#l{bHgP*ROgwb9gTkFsBeXm(NO4hLdm$^tFZAjsmp*~ms +bLj6w!=vnB+gdgq?<$)(XX3^+#;a>(|SV{DJPD+T>5>s5GFXqKoe%-KQD}5F5VfcOH`-Tq@|~q3da+_ +pW4TN}$EVoNQbfH8wd6bXh~zFi#t}r^lJ6RcMsHLk`^`NZyGN7X+!*eEJuB2-KFC_APo%cpFKdh&n54 +u>xXWgyH=dB;FfuGEbWrovAN+66rr}Vn8Oll*g!p`r;fgnvL_qR3cjO^i=bC7De34{&sAZwNEfq^_xxNtRDU5HRDM +zye^hE-;%(PRMlJIIEU$qXR1%mr#;R_7e;p;>P67T*O6yE~c5qL`?);Qc8 +saVc$8#;HI16T$GoUZOMezIpGHli-ysv~lp=ZyRf`}|XI$P~du~{D-$%yHXG4BThWJx;Q@YFBvDj(>Z +ukwQ1V2Xm??ql8>9{EL`v$)QH#Nh!DdM6F$2bW=rAj>1!R@HF4XKXCm=-J;f09YxM-aU4|cRaX~KV;) +Y_ryE9f$Xjn4V5)Wr;P{btJ9FpV-2X%p$k1}(4}zU`zS!kLAmWU>oD*@Rv^|s=9uJ!cz*qpL0@vfM)fvK94G1WoSa7kw|N0HsSJ15uGvC^nDFwVcZM#VmGhP4s5{=HI+*t5A_G~8o+qK5 +iKNtLrYJ9{+0Krzo8(yfmXE;I+A{r2Ci9?u{Z_()TLDqbCT7Ei-DF8Ex(KsVJ*l(rw9?mc@X>?nhL8^ +NIx@%(c6N*1}_|Vl^r{36nu5bAeQcj2$W&$fz$LAk7EcKFST3o}@hqDxgK6~o)lf3T81^Q^*^>Wa;Rh +YWT2!pR69f$d#9J3wjU|yJz*4T7ttE0EvGRw8&H!IA*v{2v{Mbn`;F;G57UFl)F`Y5I8kVfoXa<{JOP +>{Zeie)$r{j3#cp(XRer1&s!E3Cg*SnW{~ANB%X!gh?7mtmtUERO_PTx#eK+}k7_h43dnr5be?79KZ178)j_+|;Uwg9}? +*#hdV)r&KAFhUTqf3R+a{CY}L@oIWsZ@xL7kYSbCTUf3Mzk*JLqRff7iC7<{p=->>_es8f}GC3R~g{0 +|07D2v^CLjBYzW8li=F#sv;%bC$d!tk|Y4FabeFmPXRchHN;CFylB{{7qO;`D$5@eEpp`3FgP*V2^x@QWM!CZh#4i6 +F3?Z~uGRA1$&?Yi1gXjyc8%WE=>inprKDA)6VS+I{H}t)1Y@f5oavi_sx70>`y4|JE^#JyUgJSOdUE% +>#Z-zBR9(n{pDbnt$6f2ot)jOQ`i73Miww$;;rO7^kXz{g(Tt3)U)+4&(Sw8^C*L#z4~IjgFFnj$BAH +8ytmQ6|stXU3B!3gpAq;nklxsV>U~C6)<4B(4=rlT@?rBLUuYI}_FI#U#PP&in{zi3Xf{N}(-lkU2w+%l;7^(rT9+EniHhQAvT}3y^Jx0`abK# +fqDF@!FU>)~_b&SV4>zA?5m=6%zb)teL`1kwlDw38<}bQ#3*SP=0*s4D;|G^IZC&a0bovL;1-S-*r-t +g_}|cz1*=Xuam;aCFz~mA}5L8byiDs(PCg{F)=6aye=z{PUpX}x4D!5{5t%0jnPo}EQk9!tmLqs!|yr +t9?M`q4r4e><}ic9jU3+3;h#7>!eJeUGKVc321PMAh{Ku*3?Ah0MGnh2+{pcH<8UX3f8_88haYe#b9j +}*uxJLyb2yX3J2_m(;SLTT;qVZLXE^+f!xj#`$1xbfp@G9&I2_NRz~KT8(>cuHa5INHIo!kH%N$m6c! +t9-IQ*VN?RZ{a9ENik&0!peb2-fB@E#6#bNDicH5~qx!|yo^;Pugm!&^9v;ZWf44i0lT+{|Gehw3kx* +UwZAQ=;0tcl9e9gvu!)z6A`H9b>TNaRxj7%A1+JZE)xAo&7cq`(+726bTaH+cQW*_H^g%WuAt{lb_6B +8vH@mN#>JeGM$VhF+2{{Jcf)X7LwqKdp^01m`OaDO-$5QfWJv(HT+G2c=BBS()r&AVt_obKZDC=J^v% +rel)co&20!tqPd2-8He~Y5i8`>3h69||LrdO+ej{1(~13T@GXLyYC8|04Qx94G!h3{0bd@t;cq73**q +VrIS2d`mwy|C%_7;{oJdl^oJKOac{=!A4mJ*I?;cMo#M5TW%A@9A&99nIHD79e)O@h?*1Phdrmv=}ri +1aMLVONzOXnff{9*3f?2;kp=qs;-zl9KAIxTs7mt^qmZkGu8&vElHkLJ_eoJz~Z(pA$_<5%NT<59x{U +kQ^|ub!0WVyJZZjW8s_&w$8!_%Zq?j!sV>%?wGvE=JBWCTp5?^>V9=A#TfE)6P6QP0U5(JgZGq9gL0@ +jy%ZhOh-12Z>A%MR=m+>&%!!LOtq)E>e;&7?nt$}2sSe{hZ-SWObg@3xG_$Q595OH>oG2j2g73+GA~t +h*wa&KJk#x290R&!I_!Ba6VS_=o?~^hNOmw&Vr~w#oaf*$Rm6BOJcdDk=m*_tIE{y=*2~*R=j-Pm5ZF +y06x_W>NNCSqVZHnG?brXN0fvEt28RzBI&ApOw?vE>Icjv|n6XjOjnK&tS@|3B!#u=x@n-Zqa5X +>`YSrTuXoiu0eyyW={7B0H|j>Sus-nlF#b$MF4HDg6)*2-1ct8;Q~ciHnq$C|b4)^E6bqq-tLnW2r% +=)x7-;cVbNBlXpppSjTgMUp7^Lz|J{#|XJ%{Rs9a2HMLQXb)ngeI2G~0Srr(PVF*ioU5tdOej$vV5zj +XWW5pgH`q5iXdB6ac5i_HQ)%hf&=}g{Lia4{k_zFn$Oi5YTaO=YT`c}A+6uEEWGc-qOVa?UIcWUoj{R +^hO%>g2@R#ih!D1W*E@oOv7VB!T&7)@YxeDTCdB<{L?(8%r?6J^KE|lCr=*tl_z(4rgloH9GXYuAT|crX=;Tixg$5Ah`P`mwuQ7^QP5^{{RIJNTm`-6CD( +Q1hhHud@=QcR`P_Acv^&IA{$o=TyBOHINpVPxm<0_$K^=^|_Xo^!n|tv-HOQ1L+~xrvHQKjdP`Eqcw) +{t^xmcDg|I1owX#jZMMaz#uxn$#-muLtEpM+G{2E<^>^d(qhyS-y&7{~CpD_Z-`3w}& +^ltRjg43~FlW&i40Lp}Q5!ootGQue(yyVB2Zt#^Y`&#a9k|(-8~5m$Sq+)Q%|YDU*o~Qkxw(v+yK{3K +H}~M?`aot5<7OK-_vU5;H}~P@y#dVJSB;OG`>E;sGxJT{{5&@gP}B2cW`mj@HxK0Id>u2dQ|ZCYsyrL +V%^TGGar0Gf4&!Epn_GOCIgz$~JC#dN-oUz8K*u<>*NZ0+@MnOUZZA<<0K=JD+dR$Bt=>k_KSCzcc~& +m)0s36c}ESA4ua*Y8Tri7rlnl*&(*Fo0&Psk(~{wH~!NgA9#8bZ;v2tLP +CNU{>DW@TRb(J^WU;KaU&yQ^Okmr^)}n;CF4EX8w0`fTr#5Mt|)%K-2c;af5$t9PrQn +{aX3)F9iPFP3~?tTx4GE_U0QcZ(F%L+R(=#UBCQ-t%XI!_xxsC$#!W+>2L2XyYK!7c0TyKhju;u$fLU +-d;Ez#Pd@eZGrxcKx#wSa@ehA|=}&*&`|MauZzxeX2ujPhsoEN_R?qcJm?|=C5r>5qX%RgVa`b(?wPcP7Py#RAV3p +D@J<^P{f|KGkq*0uZp3i{*ztG`=&=nrPCoB4Mg%=pZ%yDahB4(5A1n9Dkt@9SW`zk~UK4rY9Y*WLVD2 +QwZ`K$*H6KHMLlVO7@}7iQ(8=dPV6rixj)IcPsW&uX8&e5Ex_H0NaG+E-JDg*+Z`x1{EY#xyZ&jWsFP +=CHZ!nH`IcBtp!oE3DIU*RhcE$vlwx=F%0UQN#pNriHlwyz}$#$uGzUfzI=u{Cl?Mhj@8`L|0r~41|x +2%umYS2GZH^VKGSy^Fdw%p)M&2wWJz`=Wol0Pz1!f%*;%k&S#y?w$^65MxMi+ncLvQ{hM%t>+Wn8h?2 +1T8Y)}g$p#m;CXh*8Q63!vvU8gpJUvxR%{Ex=_FTImE60$X>c~mUwAv>bhU8ICcDn0;{<5;I=>{>^kZ +ZH%7_958X$~=UdA7Q9GCv0m=$-+DGo5)lbe#xP +I&`wSk;Z53BDG`ljWoI@mq*yjvN{tT2DOt7=3%_1vRylFZS-GZ6@mO&q9H9e-D=3qFxb?!cKnT^YZYz +#S)*zEc7V%iDx>+VT>T}ivq1x4F|(S3cvr7Z1q^BrDF|d6HVW!{7-D&u*5#>SnVpqm1s=)H>oT6TnOU +MW&z72IHKb;UR=Z)?Xh)uX^zy8n(bk+b!_>eX_@<4oFg5U)WzWr7ZOsu?|8415?W?nLQfVba)j^1ncR +6xJs{weRLo}Ubdh#`GV;NnWv%@@ZiCcXpyCuWoUz;UnBCF(ysdhK(8%x%$9%*^GpHAqfmXEbd_qeX1c +e<6bg1aAsBM-ASKY9Adn9l2G*x+G?R9*_?q7Ers>wn-dC}VbZ?pll(LwA;~Yr}O-lP>trzQTbOo@W?t +T?0K$R))d4+9s~Q`Pb814y?wRDHSpb&D82*W=fRR*44RuOK%WwU4vZh%+QG)%=hP+=1JObUp}9{&Ggv +MKWLXN-?|kA%J@l3aD2&IvH&V+{Sp|1aG=Rsl9#pGmTg_8_M=PEv+~3x;`r!Mw)ExX27VZKa1R~nksd +&LMEjE-bM>`><^HzeF>I~rei*P{gD<*;`9BEwNjGzFA1&!Kwx=yLB_tBVV0;H|BII*i*x`OOejdbcta +oJZk%8j|9MO}$VmHz&JrL3hAo?gjyLY{|)+33KBX_}=Mg41uU$h?q7n{akL;Rs6nh-w{60Ij8V}lyI) +d$r2mFsLiDc(uJz5Ph|v2NkLJ&5;QkL#8X>*wYukWJvr +!vzSO@j@tf=I@}nJ3$kXHTIue7L01pzN2__mt05PNo6GJl0`=YxMgS}s4pZc)cp5-AnO}_xrFI`XiC3 +hqJpq~594QuRK&uO6dCVD7~9?GJh+m;u6VRhP6LZ0M#={*YQo_+*qf0*>wyiK~52iW{MpVWhVqtR-Jm +UFqr$AkE29;NZPz94TBB>IpbD1Xq{fO3tm2k{;271=w|pA3k8mJHDBChfV2#a#;mY46d#}_?f=KF~hLS9NGWF(KT_eg)zH+>H2o18@YM$aaFH8Tkd!Dx*SJZ +Sp)lYU|t@KP^`wYeNZbo3^@$A*@(_6Hxd-7uhS*x2jyUd~EFK3mzwd +$fMU0Eidr2-+{yO(UkL(&?6L2m{|7UXDwv2>A_|3sgFV>PV;vU%C$og*=6h4KCM&=vdo_e1?D>wtr5; +q~L4)yLe$m4k5pXZ?_tEUq9$WgMe<(mja#p-cQSxr0GyL>KEHccYwWg(C7M+UdaHV&U=mR!QAod=S%u +M1?8&`uGNyr2mKx$Tdr2bXyyO-irB^qgS0Kn*>9>1& +?i?#^A?c&IRAW%kF8w*aBfgAZ*B7o(9yr9>TY!5eR7jKO}R0umg|LHm9R)&KK!Ct!+H{&2x|jeD#+Z) +P(zy@aWzo{QEwQVf8(0L(03`f>VN$g5AbCHSCE$6VeEu2d6{tfjZJXSr6^58|iMRydmqpse&R$H!)yMn9!f}z|&M1RKLM#pQ;XT2cp9#P% +w9m7Ss!s#JgH==*pzm2cDd5~^n{UTL!N7#{O-&R69s$@9A;9h>fvmh%#f75Gjk4BYaRR6`*g!DK@$gH +2;+pJrEI+k&)>c0v6zImLGES0V|K|8tMzuvdjr`*e?P4P_fh*W9Od%Qmx5`CBF5KV?>XJdq+FQj)JUO +x6AAy+mV +_=@T8L>7$9%sPcu)7v&sp5&-c8%w;v884y4Qh{0q)dJxo6H!`4Yd`s$Nys5|WruT$EGE}@uhNfR3L!* +BtLp48=woojsG9R9QXbbhD+Ln(aqnISKZcLk<4eWM#v|D0s_xsQJb_V}*zMaAUE9ct>Xxxu+mTG2L#d +tQAw!ln!A&Jn=v*N;DQmUA_kZcB%`zq_q+~sWLl{`=4?N*qGtBbTiQ5u)SZih*}8o$nKVQQ8*J=Z=jD +`!Qvm4{soK69*g2pylBlb4%qjTi0N32Pu6Aw7vPFE49F4v!%@w=GGMu9;3V!q;vg&95*`EKq!Iy47_y +wT}nbCg!F)vaQpzvaPqJu13GldCtML#ZF6;<(cTn7PF?U7p=*;3$xO#@tLW1@*ppx%b$^uRT%d?FI157IjuG_Pp~XBCYfpQ4?P5Hs%^U63i*4F;znFHA>WgP9MNizw`Ylvk?eU;B3 +x1@v7Q!d>Kf}c_BK{lJJenBdK;#)qXWMf$w{rclGaB5&$RP%GHJ2W6V|0!Z5SJrLd3(`uONN8boTHx= +b5a_9V=E??MZfP97`4$UqU4VQqz&?P?ACOYiMU7~fJ +NQR&t3!1QwI6cfcP|(!utPLHDY|KfYXUoco&vih$-vPC7{Vgds3#mrl#WUh==8q|5+qVzsXOU(LtoA% +=dY}4XTm*e0rCt*HCpm~4<6&f)iXmnvBqAOF +U(g`$DTdLjaqOBEuxY1}qsyz!ARTq*}=8`@;gHe8tN88>T<_yLx#_7;r2#I1G!E>~bboI-(v2$J>&Fy +yWgUGM`Q17-pb@KPYz_=4bI# +p)&Onx(TX}Bmwf^6$`y1{7jM>aWwknDe@8&M>dj}>C<2anmVGM`S97b|DoI?YLVI1l>Z1HCB0*Cb+{* +}Wz4$pE}%V7D^-4F1SrJ%{@_+{5ADGY9%-enm6e&)fdDx&In18s~N +%J|%?LcNnjyKD-`zdP#}=o|3|3-1MI9WrKefN9=Wt!xzXopMzb|;a8`J(VsOGg}yHVH3T6w=lW_fx4OD +a}dxTSaf +f?{PfR^4MzW|P~8Yb&Dz!9G5$NV<})b|JfgCQ)yZ{Z68yb)mhO%Q&VhNJ`h3BDw-Zvi-N0LV&!#{gVE +fce=7u)l%f27u!Sf}D5@_yJfukfnw2o!6e`>Oy)hBJIWz^8{ook6~HipF?1n>}i8^O;JfKx#X&j&wo0I!T?WxoorH45?!_9U8+Rn +e@B*#N(SuMzBJfVYhU*$40>fK}r`E`s>00iGMr^7&VQLt`LsV2@`YhD?HZ5C`Zr1@Z>C4&bt>PzJy=0 +lso8A%6t?Re)dK%Hoj$E{p>nfcjbla7`R5OF6*$IOYc-E*v3_aJrG@IR#)5e0AWz4B+$d)dRj4;9ubT +0^?e698` +nm_HZNg0dj|DH-H>ux|nQ!2&|I0bU0X7q>9}$VG&_a67A)KLYf;gOy7Qa6Nqc!T&aZFWtfX?*(XusaP +G@X9B#inB@&&&=M9FVf<1;0suDwJi3(GR|9Il$Z$hT8xtsnC~$pP1!@+_{{E-2t#69b`AaF8~~EW%-W*xXQ};4dB#Fm^T3)2k>q9Y5^|?7@x&(6 +To8}uK}365@-uJ!dCbQ)CE}uv|7bzxDjCWDqdcIuV#Z6@KX-Zv>JF4>ki<%t64pM0q}SZ8$JA%28+-o^Mc1>oVkSXy{?<KUfc}*E#Qp+dv0OzhXH(I3-iAR;3fF70sj%;p6&3B&`>yDVt!%(_Syk_33w +R5**kds0(^1@3;PVfsilmzaR49L#oEGdfJYvNHVb|bE_#IbQ2-}C3UfoSpA7J>-HbjWz{|TCZLb2{{} +^vO0PlVrU($p+0{F_~EDx^&{ONHPXA8ifC-^u5uznAV2Vu}ttbagw{%O`X)&u5Fx&?msR2=F5Sv;U0e-Zd0X-3xpRcpO0cURKw804jTV|MM5XUk2X7*PdSiI={l= +PXc)KRiFdd*8&WEjr9Xz0GDyx1~BY(@Q?NYKid!UO2EGWxZwaH{UU%q0M8wSdI$Wk0PlDM_#SW@z-Qh +D9tIrY{f8jmfbRrI4zqCtq2Cc^9{_Om5k}{HfRRU`j9?!J(5Djk0dNDr_u<j90S(e$ +VkA0VaP4bq(<#jH-jMfJX!L{|L$gcmTk;AF()-0XBTZc=!UqH@;wP;Z1;ZzGQWj3~<6%z|&wK15o;k_ +g4TXe$Du8GQh9kYXmj#}O8CKM0@X_6XnPIKmG(j_`YqBMiLEa6CIdlH&;Ha2(-!j^n +xU-5f`FkmCs7<2bG(;+bH?5#qU5%p1ZiZjW#a#}V%0IKo#rj_@SM@to#&9RL6HV|R`{>HgqwV9{=b?y +cqH9^p2&o#y7xht5;>fIZGnpxesFaJ9I+Mo<|%$;W*~@RW~}l`w#~cNnSZ7>|bDloUF#r*p$j?D4nGt +!muuw{>B^s|)+*yRhHig?)7w_H|v@%bnQcI&2Hr>z;ys1&H?rzwF)kjE0IasyXag4S8PIW-k8|v_G#l +^YB-}eMp_{o>DGe%@4 +S`cJ0QE=;5MLbM=%`(+tj4VAg@*B7_C=+O=qwPu0}aP&1t#>)^kO%GC_&zIJ1y?g9i>D!F;>DP0SO&& +asS%v;cZ&8duxQ&rr2mAPkw{Z%wW{8zypf8b{ei=UaZo2mbrnp2g`qM62j9@A%LNMBK;Q#F`1u*3XaJ +$3m!n3~X?=6~ze?KM|xnlKJDx6WN+1`k)WA$~A>y?HF;=G`@?pa>aAA6?x`1wAgZwE(=Dqx+?JY_O%~ +6!_SI3Yj^hf2wr7jyco#Y2KJK(QV^D=1i^*DBn`=nSU#=zyP<;6FyBoVI55upN9K$4|i)9-xm +0ke4>HR6FwjK^zaQ(kk5lSum2jKM@L7K#Kc50Z{9qzVZ(-YJYH5-MqYgJMe^#auTmcW;DZmymtTJA;& +U4w4**%wws)To*2LYmyOq~7-XUAwx%f`Ts$8-Kswm?fMcML>a~W{Lsx9CBwCr_2wmkmjVnw-4dNr}z?X>^cv}qH0(s3*K!-gs3mCdoFa76;yu_~VI%r%ln?Q!Idf=T41d^35ZU^&^h%SI +~iokeP&UPXq#t&kCi6f&k#A-5h;$hhMQnS4?qOHL>x{;Wb~om0r1^9sp>Z~kWrS^Tv^(&`nm{DMLX3J +OR`NeQ{{zWc~S4?RR4e)wUsd-ra#XU`t;```bb*6-fEd&z6By+-@8H{X1dymxpn*?LJKuQe*<=+UF(* +s){e#EBE+^y$;&?Af#A+_`h)lTSV&7r(3{?|rY3Pe1*X@?^Ey93|&Lu2m-4cMS`CsUL$WU(@btXJ+LyOjILYsz29S>?3apOO#=KM2A{Lii~V-U8uohwv* +P{5mC=Y=`i>A^e{pd^vSKYA +!3Nk-uH#UMAqoObVT2|4uwjA<|rQO*PtrEUySKAA%lm=r7D+((qgzYwL#Eqn-s$F;af2tO6VCqj5!(} +20YvL3>3hwzU<_&-DV3J8A&!hh)&9)yo1Xv#JaHp+Y!Zw9{Ml^VD|=Y +;uP6};)gGWw>jZ`RzOE22a4BuALTd^X+ILb1@6GV-8NMgOzryfI44=yID;Rzq!*6H!&lsNPOgql-=NS +Hq6TStH(1S;aA`2rGGnd>-L6k8q_nDOX#Qa=kApH%F85OByM^t|8^tK61i8#qcc|-oWtv7=94Lk7 +xK<48NG+S26s0hTp~Tdl>#W!(Y_FSJOD~B*Q<&@IDORgyCB-d-i{K0It`ZvQ3-MV>e +*DaBzaEpP54>uVNVMc2U+|anOua9;;A~MuyF`CRKh6i4)TKM~iSnncfiL}Vp^(l|K)>f_55gPgWcsJ| +@cpl$ki7;5K4eHeM;Ol&YpLRnNE5jR%05(~yPt~bYPrWX1Y~<_BNJcovw?6sTI(2$<5jXgY8+^P0Z!+ +Vm5!e4(r$G-5wVur}1H)Sl#>ie4OSr}Ai2>?))UErs#~**ZeoxGR>ye`I_?{(hu!0S^ZVHc#uvj9)Bf +U!8z}K5L7vqOTnl0gx=E$y3dp@b%fa}ftTJ}`b@l9e1$u(;*ZqWEA#&6v@l35%XZiy5`h#4gR8Goy}b +tq7<@ED$5yVj{whi`CRx3+)O8X6GJ6ZGQiPj~GqP^j;`ZmLzKR#45raEm#Tx!l!D5YeEkrXGe)M&mP; +YgGv}SR<{G5#bt^&cAFBVQL#@3V6h=MlbCGcoktbIAN-s5+OHm9i`CR#Vip=Nw_3J0{k>r?e&fMd@q!49RY`C&$j7eEPEnLZ6l +B{$n;GBZ}T=&DIM4eG{h9o{NT)0qKx^$_sY}qm;BO^muv0{bD71pg=C-T7$Km1VS3J3GvQm-7d4pqt2nZXO4^RZ4AIue7G^N?$5aMpL1ZM#q&kbdKZq6&*YW#6G;DPvBrXzXij$XZTQtw=n!bh9A +xFGZ}sv!>?ud&l&!1ocl9R`7=-X|C*=B_b_{Udivm4PrRtv)1z@fKmh)@tEac8r-!Fcb1(OrHJ@fZtL +N+2xN+0Q0kz$0HG6{3J$)KBZOZr7cJ~VK5Aav&`!@FrXiy`dHs4V5i6@?}@6*^X;ISHhPitRzd;2vHs +8;Q9KZa@cmzqy_dem(m@L08KHL6#s@_4gawLBkr)U$bm$Ewx97Xj{GPgZrS>KlO8pn4UI|2L0Hb@=SD +hBc~J_h`h1l^8t^&K?_d9~O&j_L1o$^){CWQ7{{D^p{eAoeef7EFnO`3tajHzIg=Z)g^#=f +zm9ihuans#{`|)7Em%2ZM7Zs7Q_|2Nu$Z0-VIJo5jfmzNiqijG$RHmP2{I(r +uJCr0tQZoJ}~J%#w$faLhYYL&HdAFts3;48u-)Zcsg^5xHS+}V2h^5yS;`st_h9DB}Ox^(FrA5UJte* +H49OV1uZetgYmpM5sR&CSg}I5;>!qsc=X2S1?zAMS^LkTnAHUz#*&(vXkfRm}>*dJG&m(9oV6e}3xl; +luNI9ZfrS?4a}K&lAT}I&|m|HmK<2$&x3Fg$))IOY712_Df#lL +Rdx@~69o;~21XPyD?Jj`aZAI803{I6ZRMx4u}HEh_h9rG~t!i5WzmzU?{^PW9>=$mi85qPqE1n&5J>e +MMZcI=oq2Tihb&LfVjbJu_V`R8KRyIVYF(Qm)~Mpv(1y>{^6!7bl?_uWaJ>sQ>zbiT#*){b#V1?>Ie& +-oPQa(3V^J7|y{GLaqlOFHCd*^jT$I+ulg$z!g +J}u6HBW!|gSR4xr3q^-*Kpr?kZm=D|eDTE>0)NPm?Vlir-+z}+o2iY@Z}IoHah+}Wnft;2=FOYLVMyq +IP*6}4=rKC*|LUu+XzSLk#4;z&nS~95|B%P$pMNeoa4#q*5VmsU$Pw`wG{6Ty-(V+@5pdi16H&}jBE# +22Z9gXp_=L!RCsC_BqJF!Hvd$6RxOuaXZKeJ_G1uY0ef##bMvWS^;~YMf{UzifXaK(8Irs{CAPeA*&y +f9z6DLH+XYd^S2ENb(&;q={f5_POBa!(KQRsf6&U=VDd`Z-%fN97lYR)t?`H0B(L!#8UM<4bddQE8dwJHzaV;sX^1~ebn`aRkbOjf+nuF}3gQ3DFTX&S=eB9n +#+P+r254Y8{DBtW0zCqbrO^StgXfSD^alD5zsqYjflzP8zsE6^hOh%fA&hG$rlB3{O~Mfs|JXvJM%%R +KFe`{Z#|)DGANtSnh|pbSBJ~`6g)E>~&<#lkY#e$9y@y@MBkTh0+0#V5m<9{$f7in*4PEvUb^J=DA&_ +mM#V#Vh9ruqv`{K05jT^UPIi$kQ;9E+g1G-+8eu4kEj<^6g_!)hRI?FZ+8onipU>d@ihTKb3k@hq9C1 +v6N>#x5O$C(lxG{`RLC`~4%(Q)h6E!wwb8LgaTp(UdYv~X0YKw`l6M9+Ok)b|uoBxv|XvuDs9Hq9Qlw7*U)@Mk&CZQHi3FZ=Tuz#n?zN(b~FdhJSwqy@STJv#a2dU|g{FlCPGLhmMq +2pZ5{`hn;LjfOraiFz_l-M`V(7|)9}_hL4a|S!=L@V(0{hIROCajIZ2141vDTYl%-q1AAWMfR3qiQ)|s-HhLuc1##j~q#LGl4Ut}7Jh@L;E+B +1BT<=9<&1`X1lL4&ks_$0rrVwhiC@rRuuzHpx9?4Ut*S2{|w5lIW$rYRk1?W9hW^J-^$KZ$7=--T{mx +j=`1Q)#d=4Fk_J4LW?b`Wrjxq!I!!P0podFHVc_c0P +4zX6|Ww?fTi<|-N!<3G+F}WkHVH&cThSf~Nw#B39`sE)g((#ACkow=LQ>P} +7h3v3b;0@e>yQBraAO0Te8u(4bD#SLl?bCv23)8S6xfA71>P!x%!PTDs%|0nD&Pa1dgwd>66HOm#qA7 +#TG-uoZI&pZfph2Hww9;~nvhZhrFZ7@BNd?}(3rFAtykQT(Tgn7JUY{Q${(?s6|HrdJ=p&|KGt;nsGS +k4e0DA@vHjReG8V&P7!|*VgIgDu-Vy5K5;erM<_@qv{9HZSQrRzU@24WcU2=vvfSCidtC*(!4>t#}&o +!q%|hxWc3O*^M|plwr`2DXKDOv4A7J;Ntuj0>S-7xK^7`wqlf&T<^i+fnNef_$_N}8`GdfTn)3B9k5cbS8z$Zz2PG_I=&KLu|oe)ZIF%79 +q!y8P)KUt5a#+U`7@JU8pjHZ)1Oa1TMxpNcX0Q_dnn&qUym5%agxpU_>o!+^GKACHx9n*tot7g +w@b@seMqhYB=!@pRMQb!nRcC1PRn#eIYKKJCl?7WD-UnqZU^&oSOziEB@_O-;v$L|KNut(THa&oeuL9 +bu;k_nEmDc3btY3*=+jx)!AJ-?~hvr8*C{{jAxefRF&AqRT@{r5%gg{Yrew8RY30h5gt(lTm`G#Cj-|PC=L)<2_Srmc +xe)SEtOw9w&wEylquamndLwhZ)1GCHu~@U``D_dK!hhSgZM8Vp`MbowWy_ZBhYT4KKX~xqi|j{*9Bei +l_3qu9o_+RNdh^XUY2m_!v|zykfe+UAz!x+i79dVYIw``-ipH);2Z@pp5K@oJ +6%dxlS1k`xK_@45a84i26j85ubh`op@O#_Rre8#ZikuniMmqokxHVw)hG_vq0>-X{0Gk%}%yQ7%^ZPFL{N<+Q$OD&FM0q-P?6_d@;>BVg1aoV)OerZT6crUk4I4J3X +3d%r=hgxj-~rpvchDfaD=p~IDLFQL>sQNOp>6*R<@l`T7>LjI$tK$L)=)b3#YVb$<3=INyTbbqixw@S +88c>FOG`_mY15|N7%*S}jT|{r@SWF{LYF`T>;iNGH_+iq3-%xohoSd~(XbWlM_?U^?~o%wcA%w{#^nE +c0zA6k{T~3c?WQLrB%FNhwbylXrlrh9Jt_U(O{hcjT$AAR(ZpaJ~Bz9HL(pdmUsntJx^DRgVa +iWLHX@ESbFXW$Jxfd9ew$W5U?$hBbyumRWv#)S`N+<)hBW_WmbgtFXi`T6-pidyrjT2cHluRi~gd_~8 +45%{odiE|4)RV?g*hq!BhDEwQJW>PEL-9fggPEfq(o+2QL;eH*&-R##bJ!b>zy+>Oe#< +-9XQdj@Ebez7z-8T#!ZpUa(t;!Q*k63{MSA<~w}pP|`7S%|gDyeWagG=O++}@)^V((q1?~cW{rZ<gKoJukGu)z&>2}jV4wM3<;s=+8{D1v>*Jz6htcOY<;es+Jw08(gD% +_;+JG-~3%(PyNZdI;UG;$a{s;Vl3)`2_e~#ZX*I=th`YdFzyHa8!nywg8W1~?zrg=N4 +-k(KJHcbDIl+6#LGJHD4mgL;hp&Yn<28k-YcS0Z8+U;}bhkV`FH7I`z6jUlz6JOU8lmf~N56B9P{guA +T=jg&xTE3qnSc8q?wAA5Jv@}x@lpy0mmL!(+*Yd6?}iBzPAQ7vihATv5BXSGJGyB{wX~y`b~I?mDcX_LqZ^JTf4AeUJV3+qJyo?{MJXp&#C{+5&$-`pSuf%o6&2 +V0z_J?V^5EPzkYm0jCMKpY=lf>%`FIQ)kDVdsep1otDaQV`m}vYNqV9*(8XNY4QB!#4W1<(oCOULg;8 +GqAptXLLDpfl0+HN%4bTRxH=bSg#-~PaU=|}cUM>#*Yac(=Db71_2p*443&NnU(J*(EquosFtUuSJE_ +rNuxJwFo}^2Pi=l{0?>_Tia4XR*GXE}u1#`{!ED|2y|<>~jm*Z+?h@Q8Puo5p^!qSFk6Jz1V}-iS}Nt +P_5SsetO!pY0(Q7EQkUQ$epl$KzxK>M=XM`MlOpy1=sKyZD7&edLe2&sCQyd8}%LR$)m=0ulcbC;5BG +GuNQ{P^*z?Pm=}38cp}$4$eZ*H^|Ary)p{RlW$Uk$wsldWU3Sq2F~9!V2iIpFz!Su;OXrT#d()%DK4z +-62l&b*BI^aUZu;yQweBmQAJFR~>O-hA2J`V?NuBgUOyTuwH0QZd`sXkpf8@Zx1@UL=;yAGfhW%jd$; +g+&U-OB=!=qpNoA&{ +rsrCqJI3mi#~>GH5%0VP-B8V`hTSQ2RT3TAAPOVwf+uV&~mlA&VJTN!(97iYe)ekylKdI5B? +8lqtjYwO8PPeG~8k4g2e;HDzn{KGd189}0a$jS01l=~{hh(jc`joE9r^5c;_HZtdH3e%D&CYYiCk-#O +DD>RqT6VNVl#<5C~7FOK>OY7?TyrOiKia91%`H=RCq`Ld+^k(14yJv;iHcixGDEn>Zo{2X)x7w`bb{h +6_%Zispn_EqiL-u7~>z5ts*eFU|_lo+#<2WWa9joPhV|6yCNzWQoB`~TsxK7e%r)^^|td=GeWz33a-{ +Z=1QC&2!6uC~9vLT3}GGtJTJWK%R8(1bqnyf1P;{!2;wm+LdwA##3wokd@}1E20yGb-HmfvB;dRyTuj +NY-kNy7mYEhxzBsnG^l)yYEJ!HiUc}c*u3Nd~OhUfEVybxQ2%MZO*HAZK5=N)ahEO{vSE^q)C(FXU?1 +%2R_W3H&6II_+7+4_eEIvu=Ls!lopv&JKx~q?4@9N{yii$dIFc`$XD{9lRv9a{V8*hm99dMGqK>A9^2f +VpfbdBNR +qS2JwGViXBls_$Z3I7DV=@}XZMp=SF8_WJJj|H{=W)h7KJ{{{H^dt5 ++|9o80%4^`y^;O`&n2#1yEa2n*9sMB!vDelOrJh|IL5=CJYqh44r&W8z4Vf(F~cr!FB*6t_jr*nq +o#tqooRUIKIV4L@4APj-;+Q*8Z%~$@C|rYRqiW7Uf?z8hEFamEL?S;bBp;g5BnP7|4YL|=D=&#tf6Jg +mWjP$_(tqG$(rH=&MoH0JTm@5mq4$~!C`x{E(siq#QtE_9RkEZ +3zm#??kY&Y3Q{j_rB%73j|wW@^QaX1{8d5lq6Sy|$HzUPt5%*=@N^z`l7+1Wy#GKN9UY}1GI@TjNl+_ +@9`thacKQ>=g2VTV{7Ltk@v4f50@f;lyjEJwc%jyc +H92sH{$t+vlOsfqbB)(vORuYAU@eZd7uJ4Q_hH=`#OqGvFvzKZH~bbF)-cEy*Zq667(?GK-ct8;u#bQ +>8~6t~AqL53P`ADwF7hqpd&u>WYa-Xhdc*O~Y%vDb9NifY$nS+0UJ!L|_%QI{RDK3+Oz9%llgQnXw`1 +LawbSI7ZeosSPZF(WzARn3ROEwrMg(;=$P)VEnv3t0%OJH5qw)*jXF^cHVftEvP?CnmHrT~?N +c70W7~hcKfHHvZP99M9?7;yga19P{63yqq0g|5~eT;pKW^+WZZvFdfAO1Xlc<0bz{J3|o?TGD^t;nWi +R?F;@**{ZpcM}Vkg7hQlMd`)q)iOLX{4#g|?VjzC?VatH9he=I9h%)edvo@Y?BZ;XoS>YfocTG6a +?*2F=j7((9*CjT-#<_o~^(Jz7^T7*op<`+%r8gy)*qX12cm%y9++P +oH;BrAu}m6C9|w7=Fj$L`@d`d0Z>Z=1QY-O00;p4c~(=jc!Jc4cm4Z*nh +WX>)XJX<{#RbZKlZaCyajYkST}oobCD$ZZmt6 +YW-?;$3TynlFv524&jHk+4K6HLl!I;mGKs +&WS3^E{m(pgNG()AZ9KZOax-de`LHxqedLaMjDaJXdux`jON@-Au>yLlw_T_3fW^S*Y)Md3l)?m+EI( +t8erB@uZy1vs`0P>2Gy8xlWsvrn=u+3E-n1*ZE3%H%aGBut!gJtE#NFf?zF}SIOlp*$RphI&za%R1L} +pqu@hXw15}q^QxSrwML`9*I%2Yx-igb(%Ibpeb?Mp$$X`QS94XR$rO9}6ztR>EM~L&u?B&pZQ-c~vRS +&qV-c%*-()oo)-JUOqbjeFrb(j;MRijFoN;qMPwOFdfxZue`fAZ+`7o%G+i_OR7tJs@qOyDYLqf0fnI +w~|bd0Tx>vB;|0LY@s%XwPRmw%QSe7S0xIbbym?q+#4pWs(Nv>J*vjLO^NFVx+ +2x{2ni8JeBzXp`HcgAB2NslXlB~e$6RJ8-sv3WNJbXukFbw`Tn|}*qISejpm=Z88dO$6TPm8kQt`U^M +$_lb@S^*~sFUOZ@^NyZH<8hJ9((xEdt-gb9^Z=?qW}ur`h-Him;b2M`Cl2f>@oFQM$p-Z#4+j`zO +Z+dTxpRT|4&h^p`((aGuUX}o^+VsKhN4+ksX?H(TO?SB|QSmpVPw$jf2`-APn-SPfGx9}fFU;q3S{6G +5X)$sK%KcD>TcV{E1_I~^Due*odT3BuHbMzJdYu5ZZD}FA@=Cpp16#`|yi(y%vp7YD?58rg&!sx5lAE +2j42irTlZlUP(NOkTA1m1_gjCbCC{P5RKv6IspiSg4f(ct-uwXHu4H@^Pezkc}B@1OnSzeYd*@vGC5( +=#L!fLU3YrWZ6&>3Eh@*D#>`XUx-Pz_WaHb(Q`eV8I{>xJt|m%b?*cOE +3T>7kLBKXYplKF6MPKIN3aNiU=IyMEQO)i_>Bn4Yt~{X%66#NV<^IRFVOE9o+*3??`Gz;i7=SghdS?q2b^P7chz0INrz+}R+KW(fkl!yo6^?mQX**VotC99pVD8s +{acAS8%aS|GGoaeKoS&WbC1ak_h@ugTT2{@FYpMxjrP4y_KzitF#kz!h6o6TgEd29J|n*5Ck1VY#?U> +#Tl&2Op;N5)llmwXdtrYW6f~hO<^HXAe)=<B)%(AobWcCJgf6S!MrV2DwP`G?r)mvW!0c0x!BbggZC$f^PZj|KjluF23rO4ID?CIcLTI +4W`2vjrW!#{lg7!eUs71pAnHmlb_Z&K!|ZcSS{K*z3oN3Qvb;Ne&kJ%K;P$@v2|#R6P49rypxU)`TdSJ|6D +_kae<)3;w8YiNqIlQM$B1BVW1`hw_%|FgPYfapa5sJ0hy{(n4tr<|0-paw +hm3Y|Y`-M9ora-L^kPE?C&8q*%c`Mks}i!WXd`RnU9n{Q$Td|IY;5xQWK{5H9-dEE(GGQ7hzp{jtt^7 +~*NMlYzZ%0)h9;yEx<1=h|pgyS2OsEO;z6|5%I<0dZBCNC$0M-A0pi=!H<4w)a2(|yK*1p`;(% +d{CQ(H+5;u}?ixAGIQyAHDwOu&m%(TFz7DKWDf>vzX`Tj=KO_+#_Rj&0m=2=u4Xp2~V1j@uQl@>C$?f +Wxm+`=+rTmDn0SdPuCgesd>`1Iq<`B{^?Ql=qdrKUt{4AtWHQL{fCC0UOGJ) +PP4V(0o4t7ok!V!ZiWZ^ab~9K4`ZrQlC}M*Fv0%elvf!B?ze)T!RC`wu^RpHll^}=a$>ch@gn4+C+@N +j^OJA0=Hy)Q-VBE=7a7z-9P(ra>T-M9LrXu2lwU?iL`6O!n7Lxp$DevI&>vh#DzZG*4jXmIB15{f +$2)nZ#~T#fEm{wsiNou3Yb!*`{qcTI)hj_x!F*p8`Ly6?g+hk0P%-`)~89;b)0@-%|HUYQFlc1>0nh; +u@CZU|y+pkM_l7=XD5bNM7DTIrX+!%+AGFwV@^Pc|1XlS9cI5E!({JMOoa>NCo-VND4;h=_FYIrBIFX +fI}?`n-(oPsq&0BggriH7?94)sy&JTepq5KEevpWwl$u7D?82xzWTUoDA5dwQ#BD#MtrFE8{e2d># +YS9n+QR6_Q^0SlC};@jkr|pVb04QbmUWy54vP=A?Z;K|uoZ9sg#n=sy$nMpgvv{E86NsD;TU?uh>k4K +}JrdIJ0TkqJFA1_O+~5f~yvbI3^WRH)bFY+(G9);OQ3c3eG98YfF)6+YwJo^3 +NQ+)Mzvo%ak-IvJWm?1iwP!vwRY$WFwKB9e?+mVVlFA&){*9_9UTCPEDC@04suz? +SSE%lFhU!~W->k|&V;uQmqrIB#-GvK)-{N2nG=Nu6bqf?X~A#XX+i*n8V1=$!B6a;Mf#m5HO5&jE-td +WA(cbUA=0;GnpY{jhKdSbVmx=e5LAmB0#DBCF+N3*6onTtBxjYtVid6FG@z<0{A?kn8X&sr1{5rx^(^ +>~kyB(DYT3i-jDqZ4_n1sI>0J~$2mD>$q#W2%G}S$6D@||lx8Cd~JLva(y$O470k{X@%QGHLF4`~8$h +`&N>ir-{avq)fm+yn@~+;)?_I5fdM(szv%vd=c(^woogW0@PjBdQzdAjuUvL!+c6iW!n26VtnNvS={= +2>ZE4p;zPa!Qa2z{qWuR-S)>1J8yRn$75J+ib+c9+MG4N{2t>r^RmDoz#>hj=pRXH05Q2r3N+;~hyA*ONLx^{$>DEMxh7)kn2 +A9foTow2gJ{+vLJ%ELtwl=@wRlvm3U;k_3dtR0ef(m6Si+k&|h%1m?SmDPSmDF`&x==xN6o&jWn_wQe +5E2Lg-2%P|z62e;DmZZ4c}CSZk9=AgRbvDc#R)6k(iBK_Zv4c{5ac{}|Y%0{*3-m@&T`p(w(|;UaLEk{qDkea;-3n6r=a@1=mP8O5Z;YaRFm{jRj +nUk_MK`rpK5RGkOlX56sw#H>5Lqdwzq#2*zkFV4F2qrK_SGMxb;01g!49vJ`gBd6$b3}h)ieQc$b~Dz +0C=^H-r={h~^QkXF<^*1%B%jh3ABSOYFghHS!4h;AjPOpAS=$w4c+n|=GcHP0yuTU+uPxOK*i!;M(FF +z62^dVvTl5n!;UfeX@{FyfsG>I)>tBWg)v!#@V_2FS6q52;EH!(z(O4lWwJ8!~kIPk+IKJ`~|76Vv3BEF@iNc3JU2zBU#N5$1y$fndhS9ipo^ecEDumg5cj%)yq!FQCb!*U{!M*c`OB0&y$9k0$eBU@@#PtugwZ)x7}VGx&I7@?OMQdD?Z9W{wT*TB +$>mi61Z{;9DH-G?L<(5sdcLQ!V{RFX;YzXqUyu%UaC9@zQKdSdxy(Qdc)*f1|cC;COd66fV9yLa> +@yWCLi~!fB@5rd?Y=O}8Rq_4ja^Jb+ls7}KeZ?GV);5tiUh0?;zT^AHq9zU&}K^VgO9Gov+F5 +-_X)N8EX}7-oVfu{k>ti(J{}fVGe9Gan(5#{JSJC&#Z=Ik+)i0l2VRBmJ_O*Us$(PN$4D0kaE~AhA0Z +s$$+;4=W8llOnAW40Zxn(s;YLylkrdTemG|H)W$R8^f&Hjn^a25Vuve2DWEmf|o1$Vn2f211mFNG2c_ +o-*@5UfavSvJvP>g||1vaNKq3~gh&(LatKka-b&kiiKI2F-+~MYuv2x-$$Wd2QM|GUMQtf9{JAw!}6sHlwz`NT_xgm}>EOELcPP)UqxfD*|*D +pEW25Z}Di>czn{WfD6O}t*cE!EX8XEa0#ph4f~GpA3FP_y|5C(5vn&K?>144Ao&sKbl7cKkt&TY1N7q +Dr0kx(uVJDar^?uOSMxbbtbiUNH2%O=WPQ9TVNB?J%q2!-F6gKLf}JcX^n38&Wf+=Mg76kkZCS-Z@PW +J_f!gvy1Q1QqipiG&U6}{*gMva)=x4iStS*#KL_5_K)P{B*}T|;; +RCe87K%God&m1J^{OPg5Ie7KHz=0bf)kzC=>*Txa+_R*LOHLq?sXw@I)PAw4T*h0T-um8Dm7`L=n4j1QH^5#F_*skV9v_QQt^J?u_CV3UsDKrDI~J-K4s`q(vzIv11qUu +I>kc7x*%kX?>m+|p6)uI@En}AN@%4T_qhCY9@UxRhJsM0CPmd3CS=p{D;v$C +wFktt%$=UCEhlwSc*LdjVyaDt3c^8nM>-!4$99#23iX$;H^BM*U(6AeVj;Oq*aO5Eb&m<1g~P@|04t* +#p?QuI49aCHnFW+R9)qZycb$0-QQjAP3AJsAdA8>OkpDFP6;d!C1~N!3(n#lY0(5-{=az-(ae8GxCg( +1AX%cNi84;Ms_hQCmr-Vv^M3c6W(86`U1JoS&bC%g +ic-Pk<02lE3P%VJ)twy0GfgshX>8D5}JfkuTzT-20PrL+8_d(+>_Nbici7;RhcTiT;xeb1KEcR#@N=L +Fu=~rj__Xup&g$H!Um?i2y17@QVdAsLPEcH{EYR$Tt0L;o1w+6rC6GO8cdxQlM*whxTb}8h-|tNXL|A +Z?W`+48;gS-Y@fixX%hR5$IF(HVr$psV|G*(}1(3a8M!w#T5MHW3=Z!U +g1#&ZYFj{o%0%*6h?^9<8&m{JmldPr_qSAAN-5~QL$Z8V+9@2E+-qgu1=1{aW4O{2p&(-@=4c+rJ(Ax +8R +DmM%@jsKBgpFT#I(AAsWXE{wM)IiWEqg=(K;BY{A=wB*aA1t*(*f9y4hUe|*6wVNRoG`K&=-TT7hKA_ +4E3{+`k1n3mEsXlL?HF>5XiH76=?RPFi@;HBJz=balxtuKK+@;Y~h{F^39t!!(g-g0zO0p&0oe$_8KV?>f>s8_7oJ`SS3@j>^64=@1N +vmHN)C5i%bD|0~X +e|x&$1s=wylS(jjA8)*O$18tE5Ihs>H}r%u~l8pUQA|(wm92qJfkad)E9v1E%YYsiU=u!0WKigX;rp* +qF8nOR>>w0`rOsa&}Pv{gZo|C704$|6XvfSm=qFG~=$8G^V2{Rg9t?!YyMwzP<%x#@Shvo_PjR9EQAa ++$Ix*h4A2hvS$>058}X;^4(@nLK8U2kPyARE#%k(H{N->yYtuGZ^m!8kKS4>81)nn8{;?BO +XxA7Selr_=BDbVGih1+2>74Js@fUfXkwPM=qrFmF%Syqx310L&zET|r)m1#6h&li_bc!)@mt*O1vxcw +9)O*@-^PZ|5QO&=Jl#}JtulPW@!&msWr3)$qaqMET>9jE>m4M<+=`%3^u!~NS>rmZO+V5Ol@RYTXjT$&I}F%AcA*^4bcUT< +(RHP)-$5k{n!!@tLG1Fyuse%wq3k&4ALxHo<~eS~xfFPC(rD8GPkTpQJpJl{!(|seqNAOOj^(h1a=BL +L^U=mRhm~nIZ!kdmcBn$3=4o}6%;^k?Q++(1-?N%E#!S2j%_&i(7%h`>I)bE8r7#vdO)9nB2c}h436I +$js4#Bpkm(#Y#P2H%dWA>2rUqIqypDQt;L;g~dIzDaj+GLZ8&pBo0z?oHvr1sIa$d;DtW1pof1nC>ltZ;ua}7UvZe1BG1qyNYC-TB_9 +Lhp+O)E`}9*AG=q@`-9^HPT&EI4lOZ~LOHAbxVVi28doyKSQ?fH_r(dmiQrrvyYa(cUd_97Z#y|1Goyn}D@69D1wv%%Lr@BnpO_u}t%5C6P> +v^y}5M42JA?4_KLZ7Ym}`v8g{aSbalo&~qcDKYEjL{ki8`5AD3`6Q$TzyyMBj_|De{Be7^Dou|KvYVp +D^I6466^u&p^vRwS)AkHH#)KKFoyf`Q2_=0q7ID#p;f3M=OvPPF` +}`*K>PGU&S5w>>rPWsiCCs2c=Zq^PFRsWrz3e(%iM%j#+O7n;_iqw2YVdH) +4GK)Y=gk*(2XIU3eI%6NgRBvC7gtr3N(zH3{6OckI$)94Uv~`<8U0haKl&-3FXvrhCw7s2;g +F)eSwEQMn4_II}5Z66$bvwggc2mw5m5T6e*(}_wvv_HfL1;gd$>v}Qch(|gL&|=5CO&Ob94TX1-W!_R +mr_ZD_NeZ!p{iD6VaqUv0>BAD~D3}sj7(!Wvb#PUR{@1p&E}^1$JoIi3ps{Ea{wo~7s|Ro-P$r)A;8; +IERQ|cB8)vx9fhhd2lUmd!PECWmFz2A&QS=pUm2$u=2y49^$IpNGfjQ)dA39;1ePRm^S2qT +x-)&?I(fpS<-fnXQ?Q#Jx$XItqVJ(XU_yY_Iq>{V +rlqs0RZ0+(=RR=|K#h8$%9fGfa)Q)eW+#29iY$uCP_fU6l6!C+6x*0>g1f*w;S{_q1RCqMk4MAEdvV0 +>UAN$;TLOSrzi9xCwyaJHdtIpHY#3GIyk@B>GPBQS4$vpPJ83G0xHbGkhhU7#;f8PcI?YulJGjJ*5Wt +-r)pdZ#*B1@v_oP*=Dt9WpmlS#TB${a$T91uvlJi$LM54rKE-6ot!2#zAcIdtwqN$=AH$$=6(Sehage ++RADTbAzsh0alN!jGrjxL}AA~*0&rFZKj +4IUNLa}Xi2^W*_Dyg`ZeBXhn|pHr!wy*BC(QUqbSHg;_xwTSUxdw*{E)CrQvAl&ktZVX?t1l)9#muD> +&<%R&_dfaJc_JyF16@58Ll|$J-yi8B2~f+dUCRC&~Jco9q8QK6??J#Or7Hb9|;BJ|8?Etamr{{GS$BO +!tYg_h4_Za`5-#xBDOb7*yf<{60FJp2lCFPQPw1*CPHLs{=xj`a;PX(Dg#jTA!V;%NZNgv0^?$3)MKq +u$9{{(Fc%%s3-${yk9(NVsUf;!B^ChH09ew^RDlL%bIMX+G{6lJUtJGf!& +vGfACXKe<)9%f8Mm+Db8AuCk1DKl%Z4oYwZSGM<;ssOdjdYV~}<4|AG-1>hG@yCNj><1%;eaO~N8O;g +fMR1WId$RbR4UOi2OFv+FciJ-&q>u=4MeRl2%Dc&nt0U6-VGNS>VD2Kv6h<+B{dDH7Kr;QiIfKThibR +vi339GFWe9diVBQQ0J;xidev0}Q6fn)_gu!jzn{x&-PLp0v@-=FC&MErRFexA-iLFJpLdz$SIQjvf|v +{!6JXJasq==3CIBPA-s83XveZD9a#v8E+1_*IPUUTcpT!aG=walmy$>lql7^O=)3}d9QoZ?+ps~@_bz +~l4J85s%dp8)M@yvJ`E}6jzRnVib{1-K1JoyQAu}(_zGZ$XQnR~Qw!oH9*eZ5t@doRY1FrLWvqJ|@d+TTVY)OenUoE1f2n0lK$QX9+R^X~T9&!e^_y*5`p5OLy@jlJ%@Xo|uKD>F9;R< +JGtS$9Li76n?dM2f0&qZ%qz>b+=n8rj~cCT$)qKs}%2N#$M$p+I)xE_7Qu%S>N1h@xm0l-C1X{Ts2be +B#@rye$y%e$-uDn$ZadG9#f}CwOj}ZofoPg7>1*zLzjGYL)pI%SDSSciC8?1T8CkO52X8jnnP=kKW1EK+aRE +xP-Hdsi)PxiV$)s7vbeuIRSe8f%8*m~`Bo7Xkl(bN>q+e6@>>6#7CzS`Pc8)t(%+qFz;x?>gK3byVnq +l-uNR+k$|6Aa%cG*)b%V~U&T+eDD5{&aONWC_U&FSPnA473;FXC;=_14D5?d>fPVY)trsg4T3#{7PsZ +HNw5gDGrUS)`ZIq%raf2*79c2K*<5Vqyc8fCrPhE#`AxUs|<@E6V-OQ4g-vzkxkHdnR$86-UT(Zu}N` +t*?7~!KK%q)em%}h4L$VM9B8_N5x_6pnJoPz-V)-XjX1^Ke}WwcRJ>QpW7G7i+6s6&0E$(`Txe!7J2VBSJ(5mC9lO4D4Px2h +EDGu@aI%3bS^?%RWM8AyJ-BCH6Md2O6E7Ud5?uu^Al+8`zz4komeP^NR)PBKs2nC<>1|26}7ypJ0{&U +s+=bu(d>hNR%fbSm3D}~7d5>(+D!9kZ{>nCx@x%2W}Tt`DG*M8okZ%Si@}>md}(MZ7I>&VAf*bm +w|8)9bGDGZAQe!kiR&g4EZi7CT@vhLXS>*Y{IVN64%KLn$UbCz;R_wbj}a8=w1*$-JTr55FC;q(--Xt +nVbDWTb<>OqL`K*s)kex;)|mI%9nh~wOj81bn#6Q4U!23ZTy4~gb4+A&uB31Gu5wG@xp8!3UB8l}ExI +16$~+0u8`=(Tiw?!B?>U~Yzi&_J@^xK@({>jOtSdz6v0)mQ7e-;HYzvet7>&r@^c1!D2+eDZZo$0@+{ +%X8u+05HjiH1Q+Jd>#X`1`J6?}ii$%$~D(<9_on5Fc!>EM(UeGQX5rxV$21q#4bfdCE(tTYBtMFoPJ1 +8It2?vy$P2xXiTe(>ak%1~Zc)SRP}7S!1?-MRimk(3&MYt0F*Iz6vHshQC>^q3PSx?Y}MG*(^Bvmyj~ +*tUmL4>^bX{}r<;Y4M*L;!d)J@Iz@}fhItO+9`ngxgCejBD{Y`=mT)H8( +TP5STbFV@gdD!M7{M08cK8@fhskz=sP||49YMtumF{jKGCGmwZ_`In6LZxS{Lxo;K@#T`N89*h4C&ou +kJX6HcAHa(UL}R{VGs6`fR1SB`p^;E8HO*LH8M{OJPSvofeOqUztoe?k|?RPC7x`w<0`4& +H*{``V4xekH0>vhb^tqd*B~2p$n6RE88&^jF!DAv9cB%v3?_)rFdc4nkYy|vVZzecC(I-1MBkCIk>Cl +{AA*NQGl6JD_=A{#|vYo>Y7rpgb3ya0kBjis;bU2V0Hi(83IFo$f8Xi(5_u>eULKK%e3yC3n!axnrfD +2xXuD?G$R)WEJi|vVP+?6sBozve(J=*<;$|?*8^R2@baDQP&Y4RC^|7(p7at9vw2j8_`?eEHP5QMp +$OZQ((Xu5GvDyZ+6J2C9Su$2$3EY?a8Qiq_UdZ=1^b_doe8iC~pg;8*`bMoJ0d;x$<~&WtQ6 +SItS|`TIHpKe923#_J=KXSge~4(RAq5F2KpE35(Y=tTEp$2t&I)n= +52PJ&C_X?#AS84fxkA6lj@&I@$A(X6b=WV@_Pjgui`0Zb5L2qDj4es*LD^c)AVjkr;uo?&b(u@ZZKE0 +HSO~97F~#rUPnOeN9HWn4=<}^er2x|?aP)Bf_mhrcti)B@p6J>PU#-7W#)HsUr$qgsn!g=Q(*dGKr(I +KLQ#8i$d67E%_s^5<*?Y<0NjDnYr*a6ZihBS25wx2t|1sQIi>LtUTdj3>R^^i&V@R1cz~ot3q +8x?JcXqPv0E?*8rOULS*jjWr0aot%>f$pp=Vh0W9=Ya78TIkqn`VC6ff&hrLbrPGgS{@1!?2&t!Mk!)e*c4AZV +B;WXoQv!@^Rk<~jd`wp1wULJ&L00CvF}xvj|_zmkVlydG)KZyXM9< +#@LDnb&UWO#P*>OMvdDqWQd&eG(0%n@N6`goH1=r{Y1huiW|Cj>mkfh5Mrs;M9&`rYVs-k?T3qRtyhw +4G_1DFH;-#kPlxlRO`i8Qi2xqkCeOP5R^X+-6|;pp{Wbm#vFKH66#hm$=3>kIV(G3er;KD+?H;tu^}p +F1FD&p#1n5J@ZtuzP&X?7U6j(a4OFqQzx~i_WQvv)vDyt!3>>~wTTea;Aj0PRBq)5sDmjt1l9Xf!7cX&5PS|S(W-%z;6z0C#KHAya!{l)Y6}LFo(J_;!318BOo2oO6q|F)!3g)XN1z^Dl +%N0g+0DwIz3>tv&%F#^+4Shn=QGj+kkc_?e=Fsl)B_Je0QQtDAK4xiVNd2>Sc~h7P;laQ*Rst&3N!;F +wEbmm%Y210)2bSU##wxn2jnjcwR@fJ%2c@9yB)}}Z_1?bZ^y!@D%F0+u$efrOE17PI-q0455;W>m^6K +p>rC0ya$gfVZn{=r?hUZCoatZ3XP4eqb7tD%k4)&z~zF6eB8}`L*+0#*dsuYW3i4*MERyUoTTtV-gxz +76l{;0CXBPOhHd82oYJw;IBFX3?=oF^43j2}Pj9kY&zE8t)8pZECBU&3K{4F4a5ZT#$k)V22>7qX~;r +1|*auOIfm{}A>@n$9SL?`RW~#oyt#mz$g2T@QYzG!j&*6ab +ORZzZ`dykQ^P1jm+ApSvnL0qKySu>b4{BovaJjME>}Ezw#QBdSDdR7Z+?gccl2}K*wGrZ;JMds&P-bK +xEAXGlS|Y5@dvo$Qsvx5RhLfq6o=fQyg_5?^b4+TTuwmeD}B4*W>p +w~jx~laOgQD-llRjc9Ca?(LkX*`#K^#HvQK-bP}jPKO4)2N>&dXkZOGJfup*Yh0cu&Lrw#apH)JK3ok +V{6Y6H(Tk0P!^n7qL=NAGp<=&Kui{k2`!Wv3e`1r5ZT!SkRU>~%eR2Ej(4scSy(Y4F+WY4GeBD!Vt!y +c|ds)}6*vyd(?bP?$;8Pn$@QdW7X8D2erIS{M-@rTD1k$N~MZDN*9VZtJ=F{^ +(;6HT11nNDexa-oOZ4b~4?DihyV};bZy4q@RhiCMC$R`p+9oOr`oF65>1jOJ1IL4{aXq?YtfT<=y_D-6QI!KSd}1_~q>R; +1u1HW4bM2e7L*w@$d*8iM}c)|JXP^9sYE7`uz0y&-f2N_~onx@P6;;{q`|5d9-`%R{zJzKYluUe)`ML +r$154zt^cKvP;07+RF*G~`^nmVGDxY1?W=Z459C~La%0b)Du8A2jUUrm4UpWv4uZK>%x? +U@W8Y8V`Phme{AtT7I>rCIO4GEX*Nl6@v;+0MH0Zx%CjUmx7minu-kaM7RQ~1ShwY`+M*IVi8Xo_PCP6q;({)0 +T>??Q#W^Q;&{rNmuJq}tQ(Q3zxgtECh|W1XO`gQM?Mffs^IyZ!o?DY)x4a3AfZ*!mKKw84uclO_nVOVZny?!D#G87|gU_2 +p)4{m}HH0BB+W6LIy2hXlN<-5%cKHEQl@#5k>Ml)KBON!j8F>?;KWz_8FLN$KNA!A|;qRM!3m8j#!?b +zF_(d|^v3=Ve20V5)@=4b9u_qw&#)y(MDDCcsGu$|E44m +nXVVP7A@MQ&M%JJ1zYOpj@)Ht|D`F@VEqg5rp`$7uHDQh8t2~D_7V`q=YvX>kBk9Pd>dZ>qf1uQ^}07 +1b_r7x=Z)t=A}i^7*Cx~%iAK7)#>Q%M_{9GzCZM%-BxsO7&bp-&~$VF(M=*(SuyXN>~3DDfg4)OxqJQ +_KQ7$ZKAUAP$rewHbSs`hwi03fw8L|0MIU2I3biQrAJ4>(X#wJgGL&GF5KTod`Fw&_s2LD?C0XNXt7q|x~G4@z^NOpowRa6Z$vL-n7BVnfdtt%(@$el +9Oec+%LAB7ZZ&)zjQxruyK*n&KV^~K<@5;3^4MRj{ERiVsF+< +c;azk$qwKreAwni=8%1&_*x@u79S7sU0Ju^{Zi!!vA+~Hvd~K;drbSOVNah?4@McTv8uPONp*D4Mu)f +mNylvJ~wN002c+shE8QW?@l&72VuAnMdFRy?4fDb@7!r=6fifK{BL?Mdmwns3W&2(1lk6^qfF`vPJ+AH!P3mP0E30@xUVo{z4>2m9F@X_EgtZt3Ih)iM}NA^8pcjV +v2^m>FJ!}x9C2bkaT-AnhL)NQ$XL%Wr|~kL~lskT2O;gMiI=kQHTN05%h3CcUGsM< +Wn+GG=tjQ&adMyZJD7!EUKJx;1dM*RIe!yTB=YsX7d%0#*;)qT2P67JsXbABu75BMA9Q>mQ?ds(IP>; +C80p03)LdOM78CfMJM7A1q-2g7IK3JI}+96JXU>oj=r;lN@DFM()af1?A#RB&zICq>Kn;Ji{?$(tSB= +=9U6#(x8*H5RmkI_^6F00Bxf~SiqZ8inBMkqcW3|b8{R#QbvklrhV6!nf=^|1rVftoL+2mJiTwfwUv7 +)DhfZg`b`^(4jGkuO_KG_&>I=YLTU>NU9CZ2H67!@=+`a*Uk4va3KnL>iN}AsbtTYX%k}lN2PQ +76Cj26^=UOiN5vm_sVhyKe>f+^H{X*ttPhSN09W|%)N+#*K=21E>1UntREgEzxhDq(S(u9wCc&$->SY +H~=|wBWH01#Z;<2M@H93wUb#U=J3<_)g4Jw9Q*UI6OQ8m|0IjJ1xRNu%;M_r_3*AsCtTd0J;gZyurax +`p%d#&(+0luAr_lm-@^&KB0+x$T`-9-2Ll-X9+;J5CbM3kWT~Q*INFasPdf*mA*;w)yO>+#AAS?R_nV +jX-|#8c9mJxe1A@PF5;0~x~>DeC*)igZ-s#BGj7g@XU@4Zs?_GZuW#f>WI#izvqw+(-n#NTdpw&Iw9V +)i6qQ21jLi)9OU9*hSW7O)QlUQW{aBq_8V$(KaU_mMdgvGu13bwnsRriQ?K^wqz0DhYO1F9A7h?{5Us +3kS2j>9Ft3Fl8GyYgSymiN%j>@Pp=O2$9b1q%pDz{X5>%^cwX0WQjo25ey9su +NthKAUJl`ldeMJKX+lkKT6Oc@Ga|wQZ!Iv9;O)^tt3z>he?E5l!#TY{B2I3nk-=mWfeN_O6(trrj?FE +dh{~?3&)}DG7(_YUyi92-eBoa0Q2^A)%RzLaKxceEbw(j-XRh_wX$VKiFK@zVz*p)nlPr4c#w9}eq%8 +#J<{9@x83~u*(!w~oKI1)pCNunejQ!l{iwx2-OHnTpA{*le5rHyhTSzU429|ktmDJMb1>`Ul6|Y~keY#nGtcC>? +NDG9mWz=w@%&5Y>;oxoRksXjEZyRyylwNVtE=kbVlB^ +gqHL(XwXuPeZ^}uT$L2Nnk4gPp%M^f6(${-i+iW{vIM%B=Nya|&b~X;2Z%c>BhpODLi +gmG{aPPB9%(RUQdF&l4+Eu=Nsv`<7WD+L)*}J!E +~N9c=ciH4!?R0t2Bjnoux_9OJ;~OMu3Mzg1zOUXlx!KVCrehX?IbcHkQkBIP3`eecMl)2oRbdr`SQ%} +;6=M?j2^K*s?@&RxgNi9OG!T54vW=ObbwC%+O<`>(2P14`J%ouNaz~y_>91Ckq57@DLlDdXkd!R`Y-ZN{@x$+K!TFLA}aLcYK|J&)-drk{Y-WMtT#oS}RaVN#4ZdXgY}Im}s6eBK`PeEJg?JJ>j8*94p={lvBSuqf-(iqqZRJ!$XeD;FxMwZ+Y>S9y8HCJB%iFe#p6Fm*26Kg?>*I!f`P_RPqQlUDK +!fach!woaTz>-8O>%slOp0ak4NN$~cWa7(R-I@%(3vynIVCI6i#OH$X7}6ekMA&C`e6TP?|A?4?}2ftASlbAAjC*!8cx2Bcq4 +SGZohkvwXj&fReMoLEX1P9-L)I@1r`32&fSZx)Q|LT4xPkg!%_Gzbkv41^MmVmF|WZc>{t4tKU)px^_ +Z!2oG +KXQbj=PY?IHtJyEsqd~2hdxe=nEWd2CfUI~b&V#vdbTEp-c2=un3f-AJcw&qILQV2n7a)k)r^7;91MmD%{+1<2Qt*>0H(-2~U^KFXtuRe(qrdN?{8 +wzLdxi$VEQQj~Vg2pMxGWoMj6(%Yuw20EKTk`{Ms<}?iqqA099Ok@zl!|7x&&UWf>SQUo)sxgM>P3FC +WVAXph?g@byxjY&;@eLbbz6yy@fE}i%hVQT5vl7_FX#Zl?R!&hf9Y7?jZ(I^UvUX9YEw+#rnXIQX45? +GW#iMse$f8J?p{70`jQtbRD*YpWwBgmzSWCKeXbN`BXCZc)V{sW)4K(%FsXi%ue=hk{L>dPbiuoo*>w +}%F82$x+Sd_W-Fk#9+2ODnb%4-e|`PBuf9QxEu)7$rJV(;7bZ&XEsx~xlsE4{@Ome$TyBaQ!$9@Ry!9 +b?Og5gU>W~-uIEzvo9Z;-Vnm4PeU4oW}Wx_-vT%1hNl6!hg$4A-bh)8F2T%zP2#ASfY%7jkJ+twvECCMQ8roq-2v>;jL9hq@xMNyk*M+|r<6keF-J +@p}G)E}q>aRNm17yoYQM4PK*Z0AXX@+}%{=*ZOe8T4zOsf)yQ{M^gUIN1#NTIL~>y2e1iSyq%4z-?~R +a#s8pVCBIjQFI#7Ogz~ +Ba*c+k?D86$2KrCvAYoQa^2Jn;+&>aO3V>6&UlYEtmcUTL(*|0_qau;;N1iczgkW(l$Z0b`i#N%E&Wp +<{z9Z-^nKY(bn7~QfQw(y%6wpCg2pWF~(#sfszRQY*M9W)YB-(M%J@8Uw&>sN%Hws5=39?30arWNLH`Eia%ek7sY1_ZsU94%ksA7>yK0@8K$;tX +XKdt|VD#CM0h&b7gq8y +(q^`4;(nfyiX=5SD?)M&%eCJA_oUM#L@9%NO;Un1 +ydm2z#J?Wuny+Q4M=aA+8>Kd5_v*Tc(LFZ`6|s*-mK)|#hg2s@GBj#?@lC^YHo*jQY~o+%@a>d2o7KW +6@(k1k!71tTvJ#v;GYVxdJbAn9Yn|is|N@Iseysy_0=lRXGWG#p{&)ye6VH$AwWffW?)=I=@86=k^0s +a3$WNYbStha4+HT4ws5lbhAyZHNyLTbP;XP7a6TVMa(9D9mAwAcT!^DciG6r;1-iAqZfbh3zx>=8*$x +9Se*{Md+lR~rU%OlotUjyPpVh0F!cUE*8Z+(;89+{VKpdB~Ep=q*C1^8z6K)w38ZT)oV1(ab?IV`!ft +`6ok4-3YKXgO_fyejHrm-Y&gu#v4b_XDuUuzybd9~#P%B%(7OXxQps0+1nU{0|JqxeG|Ae=Z5&daia# +XXthsGR31Xcpg7Ofh8|pESxPhgW6eu~4TvdInMJsTVb<1T?R%DD17aPrAdJRqz(=Pp@8n_QhvkzW)5P +FSf$huiyo6+JC=#_4@S}uQp$Q{^gs`zWCzJmz(gCR=UtOUKI~xB)JexK%e0BpCxy7_M>sz*IICaD_wI +{u`*F&3gcp$R8twBb)oLMf?VN=i5fHaYDL3hnAglONoI0CsfJ^CHdgu1#B9MwS|&N1YQ7WW#A@ktZfS +CV3Co7jSj$-Q$^e{?t28i^LI#*r@^s33PVktr@V9I@%VV3mS*jB2tnHmgDp}jKsg!8LsXQuO#Me*fZEZ%Aa!QA^2gW(am`DnXdS!*^4-zS`)|h3!SV55j>mY#QmFG&_JWZFjSgWRjh@P}X;(k#Ywm32{{v7<0|XQR000O +8`*~JVr=>FEei;A&*;@br9smFUaA|NaUv_0~WN&gWWNCABY-wUIc4cyNX>V>WaCzN4Yj@kWlHc_!5W6 +`fV~UYvr)jit(lqP#W>4$-#BTTQ%Bn6!LNXhQR7pyj27|%8FyKDxKI +yU`4Wnc}WUFG<{R~Sw`<;FEHeGJAXns{N`>n$Uz5bvJzn-&;E6&a~#Z{WH^K@2R`x$3nrmH0MizrPz= +y}dL`}*D6lhgAPued3gpM>n~x2G5H-+l4j#kcRzu@KsI_V)HV?-t9LFL+W=Zr5QUrIqM!B_1sR*i`BA7(>U)2>0${m5e$x>^M#)jQJ}%}2!T$tAUJV +4pF^Q~&3LCX%hCn&1Ry5M^CE**lVY>vxoog1qF7Zg{VeB`jQ_NXGMZMWGnx2tJeja#HnL9BY~jbz&pi +BIpkcX8EBwYUV49ibc3Aaeea+{7u+ch*g7(Jea-LdqRn*?$yF^m5#OWl<(hNJ!c`*@mx5k|g5{PFo4M +`~vs=uZ(dooG<1)ofuPNx&Zex9=}Q0?#ic*W__84?<>1Q%CP&fuS)uvOJfEtH3gge{i%JwX;JPG9ttX2@a{uhq$*gHz`n^>Q=*RLRKO>&e!+)HwHxF +>X#uRD4HnH9@-)>t?9s7!tGBAqx`1klOyDO;S}+;{WWnQ%Hr(UPVvm_InvT|l|5PqgeH5_<4a7yWiaf +B_v&3AmV*yLP>%%2{>u?ef&OrbZxXIx(7;*8(qx^C!#`Eo?X&BoQWx`FgV;~Wm6ESCUCpGR1*1-6_Q0 +Mo9Dm2MeQ7nf~pN46WdrQ(gUYgCHW_-po4x8>NC?0>2hCJRE(f~kR`T5mkqM;@V1a~*E!b)3P6k}2uU +uIi-?mfoIJ&J;m!VIPJm7#8XnyH6)J#9E!HW);ozlpM&;Wf+X3L5Tbmt%zz9e4jOQ_%a5}b`Jto- +&poRWw%VF;ftar)k2~lC4p~qd4agE+vSi0Q8@adKi`(iWpKwDEbA5BADd%9 +lw1zD97t_#ZYnKHsTMpS8c1=)eG3!-pn^#7gm&dJ*Kw<=lR|Di-%}waX+nXD$?$d0aO*go>{wgk9rkO +Fvuh^?&uLAe+VK_c&Z~mwp{8zPvg;L0JfmoWv8&;&H&E>}h&k{gut?>{UaJk}w$Y&|ANk+z+sp@p|s7 +>t&_HSuwY@MxY9yA$k9g|66I>@DMs;sg}%8ijPc@WJuKmiGbDaER~-piQC4AY@L84Y2SRZSKuIG57!{&91Ma;A# +t;YcT$$KLQ45#mPY8lzrfQ$)ZA3NEOotW)00j!OWr?d#=Pj)b+WS|^pRT+u0f*;u-X+g^owENB5b+nP +!;s0bu^zM>0xPA_-dQt|59C6^L^oMuFTzBH8d?(!cvxOJ991(MX*FW!9|Ob&;dWvQaFNY^=<~?NZ +G`QamQvWmgp0V(~i7UAx^F1#H?8uhz3k_!fdlyTROPvXDg978{!1_(3s|V>NB@9Lz9f;%XS8Gq1Z0Xm +<5wq^T?&^~Uf|S3YP0*8OQUM6F|gT%Uz#`N#V9rT<07!z1ND{#>~I`M=TaC#y!Gj4w6%|9Js{f +4^%A({tFt@AOq4XMet@+BrKpJ38u3zCU??{_fk;$@>$uNjb$)BUsPO`eFUrz6=i@IrPWA^x(Ji#IbGv +c6uA!W`Tp{j`R9X8!NM*x4le370NF79`f~rARO2uzx(r>?*CqfV?;-Lb6x|GPskr8IxP8ef|G;Qu$5G +9+WreDX;8tFDq1v=LT*ua*S7^u$@;HZQA4@gB*=M|u9k?*!V`}Yn`E`9>;{mb_SzxEwno7uT7t0g@?{ +(qU`<<6dV_HZPAAGa1b{J1A>Z(A)a{RPbM-3p_{8h~wG)f|o$#snS|B+7i3B)`V9w}TdS_@@GnFB){3=r-Vj3azk375pwCP^#fUD+0D7EUi_|RYab{Vq`n&tFc_& +Z3IISJkb>pl$;3QDxO7wQZ_l-_OBQo69wr!^OxWUxy)auQCNc`O?NOZL3l4%2<~@K#2c4QR|Sj^fNX| +cMO#C7Q*zk60Vl(r;6;E-R&$mjifa^Jl!wtA=F?)E(6oV4eYtY*TCR-OG`B_A5ZQV@Q&T&EgVK_Gn5d)F~gp{gXk2709*m|u|B0~UBe@kxC`oRDlQ>?!>`gbtmfAD2HN^#;0sh2Mw($w_Mp=1?dfg3p8n?djk-gBV7^al^+E03J1XB +VzWvW{hrq(H_T-DED``M~;p^CHthCP-Q4%dy3szjGNY*J#)4;B3sWwH-*F0tmjDB4CYZNJ}CI`6%D`2 +9pXj$4<@(|}<*Oh7RLZW~tS9^t2Ck&sM9}LuSX5#~8?M~h`;+%4Ur#RHe05GoAtz!*?HiYE<%yT1OH>J+GL1x) +B&?zH{Qaav%6^)}(TzAt%zw+%dfP<###t?-!qlC;-AbE>OQ%70y@7A-dnh5-p4z}p@#*!qh++9mQv(j +3`g;hK8QZ;j<=A{nS1ouj>nj2NedKLf4Y8+arIzO96AXu`ERXART@HbilZ2uocd= +7Br__BD{9*0~hGRh$(eBo+`%@S?YaI3h-)#?~lt7lH7UrGiwWKL9Sf3ddhby91C^eFtQW(P3xUHE)Q= +7Us~nN$rRSaZ*dGGUjHm=PqzManzJv9cwM?p}p3SWKE6et7BGHHs%aPEQmGl$23ap5lYuc=$wcS%#3d +120f7pOzB$=A4Dq^m8k_^zW+A1wJRdFMeZ^60@UvSY_Td{r8!H-E9nJ-$=q8t4DIu) +d$H-rurH`*#iXTZFqjsWWRSmR-lN2HPwU5pC7@_iBut+5#Nbv#;O0J^AM3^aA}sn|j;Y0g5&94!Yw{E +C*ad{MGh@p@~Hb)@2z5Sgw+w$Q!^i>xYpywl6~mL1k9YyX@D2?lbQcV^KZ$<6{au*osZ808V$j6R8R- +pij6PsY~1To;a7zCjzZDq$Dg12q$0#tB76=(At(#>-A;+gieJd#DRp^K&tG~1}?MtdMl_0f`f$||1xl +YC8BxlRKsXRTtpb3&Q?i|?j?G+P0TK*wja;@TYq`wJC{>}>BsO=!o|Q=o-cWfiA+$@4g@2lRDBCi^dO +*h>X=zj@0laCV2=(_=x1RhH)U-+d&&!6uCj&C_gZG$CgRGp(Zfcvn8wxjlFy|wXwY`i;aUCQPk2 +C1U39;so>P9I>bN})M=SeFs{ssz_hJ`W7c1~Di~f}l6^%~I4{Ih_wUNs#29OBrDcS*1%K^)yG)T&sH= +*6H4_!mLc+4f!K!Fc#b!r6J-*s(Il{24u9QVhrNpb_ZD|aF{c_kiRDue1!qzj)CA}knh_vK6>N3E63QIBr>rJBHG1ml8ekVB|YssEA6nL&f_f{(>~}^W2|{;Fbhj) +;4!ji_G6bLvv$^V#i2Hs3TnMNt$^J7s3`ol65TvLBV)&ZGwixrWlCa1`n1C`ya^mIwnz$vJb?O+CaO~ +&uY$uDMFjlO&6l)rQodEY5DCbOaR0yuEacA^UC#>@jXGkcQdw0S1NL8HW6n;5k@AfLqa?9xYrU@M_@9 +H$>Qh(3MK~!PYC{kE}mi*G{)BftvSbVtbUMaW7axiEsp?dr;;S$#F^g=b$NAAN%svY2J6e?Jye4^r9q +aY5w{skf{0nVYzoG`2=3#J)oISNb$NSLj-In%mC5reU=s8xp#E%g_ReEp5ip;HtHlz!5TN1;$zO(dS4 +7UU&0BaG^m;E`b~O0xIl{*@2C<@DHaP4LnEmX~dvW+!9+kBOrBR)s#0R4xnJdqTbR5piUll3z1Z^RiN +Ucqm9UeVDV)kc!uXj{}zs3bxdO^rV+u<``xoDNGp$Qqxj82Q4C +yj;27IvG6}|5RLMp1?3!P(Y%s-wrvTM91j7>LWTk|mg$g?-i`W(tuB9TtJX`LD+Usz%tIifZ##%C2sG +7eM=R+BOK?!)HA=eR9MF;19V6nAf-q<)$)bE_8u0t!uC>yoEG|}-d(zCdE9#dkhZ-(L4oA+%t0!UyVF +m|kDa^XKhRqa)u&;X+2Y~%cT4)bie6j~H@u7zglK;$B08X@m2i2-V00tnjTOmSnb8p%m>>(C=z6TF7i +v@6$;z9x_!wgbLW9t7D$iHF86RvOxUB-(*c8EFV0jMwnfDHZo3daf4q6K$**l +F8h}Qb2cDTx-iFXcKE!Hkp+17;2qv%gPzwH4W7Rck%vCkKY!6OupADPKE>nf?6o$y*Bt#V@L@7!0~pl +z4qbYf_FWj|h1dHilSyiWcpt~F#T1=O@3ov(E7)}pVXpf!Ednn{<@&j{wYHkPH|h_^2At9nwR*4$AO& +S2iZs$iTFWW{D;HK8^mrbHyvt`ZOj5&D#;Zau+XLls==Gj?y%&2DI_eo=D +aVby#peq{rb^m=xm=i^Pf(i24YtvX?ZxvPVmao9uULd!-b@$-k!(K%P%#6gqx`hn|(@F@mf$F96e+9b +MM)wyRbz}#TrS*o4@S8s%<3XWQ$nq^z_tXg+CYflW;4;OB)Tm*EV&yh2NK6h76!H7?##JEFhBLP+g@m +VU6fQ5@xD}JdPGo`y9F=3)aR_ +zcgL6y8weOkjOCP4?v6o7Sc|C%MNtkZgi_cx3U#^_?P)H1<^XtG$;vG9iHQ}u!~;2=?~7DEmAQqbvJ( +T*aOC&BtlyK{7Fb!9VV{0aYxu%DJnZ(L4+ev(A(n}jSYB_Osn%N?HyJX!?cR19tcbl@r5()~(-T0dP| +%0mmlgV6!x}mVZRkLA6Lr|fST;D+2qL986za{qc~q3`b~Ky*oZZz7)PRq}da|KF5X#Jky4+wN`_FnH< +&JhrIkCfU0n=MR;~pDa64gUnh}oJyMP7%nk-&EWE_DpD2Q5S&Ae1r#QOs!&^z|0qpvq8eHULTigmuKP +1zjZdf_9309Q2vp^FG}z_kjWc^H##W?HU3p0KqzVRohrW{Pf}j3c8%_EpPVJ?a3%Jx7EVWJ}7ZtvymE +{Y$%$Y=Df0}?rW)Md7D!W#``{#@TK{gLUa;MymK9O&06Qx4DGyuWA!Kk-t@_}~ +_%cnfk6#Gr*HruWY2XsL=6*C_MEFv5QLGv)=`pFQM`F@=DhSnPl;8})(|Wk$Z!X|J%J2sdQs32~jQB}v;?0V!e_nTdlfC4XOD#>4X=RNdZ5nwl3lqBdP@8w{XZ +V#n+j?7F)CH}je^Exn^eA&(;`}(@{b37XIBPj4k8`V)G)~)u4dya)?e&Dwhz=!bV>HuFb1y5;ed?K}y +Ynk>^9uET-uxIKDe&`?Z$JUH-B=~lh_MGloo@~)780w +l3Hs1b-5f +rK_QMYN&Mv^m`>lIJfu&1%vRwXj}U5rrW*<EpSZ4^BYv|1yleckq`lvAP%7`Q3=hX+70tLx>2L_$E&;-g;`GpsCC{+0cXZ0pb#~+18|GRVK3{#ir@qhx41AYAb1)D`Y4)fN|;wu_ap07CD?;rI%_5~jH#$ewig}6zD0AnqU5rp%?uW|%xp8| +91wyjV$M)sz@CmV}qg{#G4L!ZN7_T$`oi<56bLFHa3B_oLj1eoSeV?$H_M*=T +-QiijjbiZwiN6{KRculiPZVR{rcrJCJ8^mT;Y#qgO7KYdQ|D`a+>3@DOM$VJUgsgr-Ysw=PTXn#b1sG +lwD~imD?^ev*+Kj;d6n@mLyJ)n}7!H+kfz{|K;Y^GS@b +X59HdP)h>@6aWAK2mt$eR#O}p4#qdI0001B0RS5S003}la4%nWWo~3|axY|Qb98KJVlQ_yGA?C!W$e9 +ucoappINbA*q?1h210)!}BLo)>YIF#a9fE_&BtZ!d49t+l1icH{adcfabOWvg5<8=5+EI5e-rsufdao +?Hi@WY!_O5)m3d}H=Nfb38i&;U9ZdPmTtOkk6KtlR=PIb>Dpzhu0eV+Gu|9N>xcURS^Q&p!^f#|QXjuB@)hm7XZp`1%@S`6z-1Ecy@89#l13%*P| +7CrCgYZE9y$|G<-|5c(;g8m>zac9tqd>>;<43vm#h=_WKk@ggJs0NBgZB*&y*@vOzSa5PrSCW9m%)4C +e||Z?h{E1I-%sP;*5QZr_b`30xcB=$#CK(#T~3BsYDi%=f9KL&iMVdYXvj2VFwCnEcvg!myBQu<`0Lc +Ef!{`kNns2$J@IDt8YuDp{RIOgJDMpV!Y1Fz7yW1HXPAW)rGa_Q^$kRQ*1!~{!|MdYO#KJ6-|)p1eFX +Kq+wkr1H}LDX@bG@`PkM#0t>(*N@&UyS4eRdV?*WjxdfRV+ztH7yP_E+!4UFlb6gFr$KmQtB=!S-k4c +~`v+E!>YvjW~D-+-@S{rx`zK;R}y1B%aOxDxKRZ*SrM|L_0Fzg2Rt$G7k^2czaL?Q}45-!6XkVkQt0C +Itrg`MxSh@u;~~khEJ%;3xYIW-^SQ@mZnUea~W6f%P+Lr;-kkR)DmH>S`ljN4#Y$;=9Hm-ttX|H~a&{ +&An!pVRo6B#f$R!DieVRbI3<+5m~ky9H;rQ$@t6f#KILVMlGE+1V3g +y*Due7~3&X+kqG9E2Il7j%S?rzb@%jz|Rf5i8o9gpT0VGo6u+-Q`{7{Uo!ye^!hJ7J`SWb$dnSP^J6^ +6-E(0Y9TJda_XmYgJInAWsFTGcB#6&2Usq@bRO8HA=+hG{8W)FFlV;ITVFGD#nK@8P=y@(X82oF$7=qJ;2G1L!5oQ7)2eYTK&kb5fR +I}6Q_gkBe^$7pA7p|jtg0>#z<@YAE^l6DIcajsn5W2)}9zq$h?7r%v1@98C#HFP8>TJRIL +!meFa%iOD@frhlplM2`3iDEzYDm*McOB8RHOu6LisAkJ|Zj$I^(V*MF5GMpyZE1=^Bet$U#7_hZu7xO +WNCnQ;NwpP=V;`VNlkrmD{xHdgMMeHxF`&*D!vg-{5CFh{~nruB4+pFd$ge+;RknrwWD&$fvO{j->Is +HfTdV9SNG7PlvQaOYey{_nu9T>SIz%K^KdJV%;7XIbN?+UNBY7U5JHr1C2ZE^Y2-D>VUjWQN +-Yk_rxkOD{*=r!L0Xt|3N46ADMV+E}!?uwAQG$*g+v_MYZY6^$r*k4}ffy8-U7x~#TN=AtsRLylkSp +b}eIp(^&USKz`SLLBnYHkjs*`q?KMq=;F*p@5b&>a|-|(;()xXv6-s&JyolQ)ujdJBB2U;vEJ`o#Ck)tRg-ncq5qacH_4^06AtE;Dgbf= +PpI!AJMfEw6)cES?f_E{T{J_C&*UfjSr=J6PUCdJ0>Hx>u<#CK$dYSWv5l37VsAqIA>g<8Rw7OkrgAP +20y*dQZRkZoR|$0qK9t8DNCV-E1x!HPf3xbND8UMD-%GUWd6WVcF=~Vy2%e4L`*m>P(|Yk%MjqrRaR* +W{;h97NRLEFgk}7Y33r&UZ9k(7wt2JYaolbXr +`On8S>@-9c%S4q-uDeYHda_VuT9%IiK(E*y=aZ6Z6E3dhwy@Gx@v(0+5rf6v1MO-U5}wHeG`?{Fu{gm +`5=yw4rY%A~T^F4O;kAc3Pw&m}ctM=yPCE+SoY|I#$g!LpRrp3uf|Wf4-99pDBjSHOpOOfjQBK(ZH#= +s?Gy8Qwo9`jLJAHuI`(PllCE`CX^53EarhZKDUS#P>SK1JS*)x3ouGnG=OhEM2f^HKZOBXNAQ_$c-Kp +o+uF3Ll)MjNxyu#`oI#RwQIgyMaWz2^i?I#V$Zy!YHsp7*%@tS@#6$JGKV<7^?@8T)z6Gxc-K6B*RHuSLL2H8sI~_^vN`246{DTArO?8O +lr3g->ZW&hk|O65s_Fd5D(nH?Gw>D?gDT!!(G*vquC%vs~@llE_j;vuN;rSzm__2D*R_E|2sKzKI)H6 +eq2KinV`{);@*SzJvn$x)3ok>=y$-cW#Z_OPP>VrXdd&K>ux`nny6ui@Fd}*F~<*r3|XY;Y@G>`*QpA +nL?r8x+Yke@1N-+SztUN=_s+iI^#=L@X!pT7 +cKi1M&Xxm4YUb@2MCAl2h>laJ_dEpn4tA00}7%0fbr3oHJ +|WW+==>u=XP?W5l_BG=roE=@g8ADD^}d*gVXNfqi8@j5QAs+AUuO>~h3Z5=pp*Lnq$VP(R1XkXkk=E4 +49NWx>!c!bBYeqiN`bG5L2bkeDl-{R^+t=73T$bHcBGLt2*rR>K+(YbUN5h*C~Up`b$&c_1$ThO{_U$ +TvGO3dbui!Y6KF`>uTAs0zE>gW(yRv*@;KRa>Y!N9b||$5Bem(t&ysU9vF7l>&D5IUA^T<{Lwd+2czX +z$g3mSdyMjLLv+!9k3#pPNn+{Q`HLBbpX9?g87hPVaZu9wU{ +np62XVe8XoJrO53&oZi&AtYX!ws-LCoIk80O&Jkal<-JZj{)y=@~WrvVzH+9^iO_K}8jVre&)v$l?(; +=?cUQ@tdMA2l-|7set9{rNr@vRlzK=qi@{4LZI?TtAGuS4oPMgDI+|3%YE#1wMB;t-oa9EjC&hF7Pb& +WbrA$BNuN#E#b7>CDnEY&hS39xD%owA}%$al4{RNja}jwX85)bHQYf~7cfjo8vl0)EK+a5Y{UssQkwd +Xh7Y3>C`xM23MnPdE<8ksX6;8NuZvpr=?IwVe%AyYD)C6C^Kc+3zx;Miaka>S|)MkvI +fWXLRFc9<~{tc0UxMk7G474$W4iPOrp9M=>;5YLmz%*jci_}U~G9ze0}vEL#3bQ8%hD&SLbR>jCK6EYP5gvr87 +hBql4WT0SDzacwvZZJ8^p+~$m#Zxxa6T?P2QA9JqD7t1qE7CB;cG1l(-uL!PGY?}`3SAo(ZaAY!LQ3` +G73Yw>xBK8N9%7*$(A_+z#`cR2{d!0W7$0BKE* +_*v-G0h!b|~zu~*2n%y{#7D{%a8Z$Fi3%qH6V_TWO+jesG;5jjFY`7&@p3at{VY+z&sYb-d>=AxylsO +KgSsCXdPot6yQRz_z=^=(#7#(sK4Du{eIx*4n$|JN{%dwhXbwdvM{X9Ao11-n7DItE7K@}LePqB~`tj +={cAkNOYNdUOHMs5qP?**GiTZnrI6wSoY>jkd2qNGR#T~n@RaY&=C@&IJR;aXFZ9IqV-DX)){SJbh>K +j_RumKSnH2h!uIw34;Sq5waPCJPwHjaDG8R67i^YO>gv!|*wv=UheZ$W0FR&vAJwMC~wH$OBPg5!+SU +$-%)AXU?Xp#UYiqxcy-B#!EGWaDWew0&L#L{k}10u)<=2E|nVl*|{YqNY*^lc2p!K)B2LGCr0CI^F^EVr3d?2Yy4)$+~c%daI^bn6Ad<8T_lZ%nIu~zg%`&ab=MFW_+?;)#I +^U9T8B2Ep(x#ngt%7CbN{2n@0f)C}8Yy2ItIiUYvez;0`Q;Z1uqYN@$jIcx_!$2 +w2=OBw+@z4*{jy#wDwMFRq@ptFiS!LywmzC8$F|=fz9bnj1~hTC?Ba_K+8}AqXPd9^oDv!d~;jM8rZS +fi0{w$7aKqoF?DN%3Xzjk!t$~j+&q?r^6Rh4d#=qgC!}`fi3`D7(# +my5TFxgWN7mk-=sJ{4FQ9d_krs>lu3#9s`vY1K08rE!_-MW3OJ-AA6qaOmSI=7luto!$T=hQgG@sSSD +DBvWb>j1f@Pmtppg}XEhFnK`G?Rl^~5-Q@fJhv>-z;sZQ2m4>z12thC_vpyI$BR3`j-p*~GL>-XvG%(Sos5Gmj`?N`#>;JNr$};X2fbV8lI%Bkpl4@(o}C+ZK8@mt1d6c +1$NnJ4Ts<1K>yLx($PY>rmc*gWVdi>UQf(>B)g4_U602wljX1J8OcCJyLD=`QMh*<^T^I<&s6M*NTe0 +O0`QXvb%kVp9<4*9{G<^uthr9P6$Muy|_0i2}BaQo1qr!t((%pcA>gkYS4Tu)K3+r2A$oS5OArhSZZz +O*w6DGdt~cvYIV<+3@~0=^HgWIG7cbJZc=gsPWCAKW@368O;03FO(v#*;8Q?tE~)LQm&&(|aLzJ`D0Ss$L6L^2=oAn2Z>HmW>L@RINGop- +tDx3(DLc-!dQc<4?8CFfl$NEFt$|Q43}YwPY=PNeh&fa-LTQ^o`((9xmNVOSg?6{3I~JRFEb%J=b^0w +U{eUnGUQr&DK#bWh0oCjL%L8bFwdsHU?(S&r$tTX=+o(cyStDtHF)9*#5e>&?5reBaXs{dQGYTYVh=kOWgh=ETLM~uRI*r2#qsRL49*?OW-qY74JE~W5W)X|yn8#t?9KyQnVegKhU0_joK +gTZg~kfVJX#nNo5Jqm31YGy(KC4b07#TRI)OmTZ$#Lj3^GAdk+r9`n5sWD0D2|A&zkT>GRf8=-uZNV? +N%T~?2qk}4!RA(j{aV`T_&nO}2#G8Vu{fF}D!cJTglPpGU<&!tvDSVC#xWXw^!?Q5|Ose-PuZ|WWzO6 +z(=8(@blAQEY_{@wdt$7e5oD~o9@Xp@!j;*6)FJW-OJSGaXET%+k}1)$8Z&I^D-E6IXLA0r&@<%$uWc?g?H!?>Ew?X;Wv +;S*kIssuqmIVFngdo2b{+Yd@5*j?TX2tQM26T@nKuYhM1~K&h8SSM@V&%YXF{9!$Y}2cJ|7woGw+Lbu +A}TapVH6*Av-!7Qn^E9tPnp2tOAddiTIoL}r0_iI6o=KT|_ +n7Df`grX_=@Ju%|p=e5hzT`)(Z7I+NS$47X^DKO-xj)A)*Z@F4)&giz4KAVX#9hxGy7>Gg?rE1I0H}B +X!yt?gacdjmz+{iX3)F8bV2$hw&Bv{+oI2ouG^!jasrI(UG`VdzrufY+l8G2mT-EEzMOLJc>Sx7|M5PSw?fvmV+8P +qm&ky*L=4{-L9&IVQ45tRFXlnYk8j?led~hloxUuC+K--B{RMS9PIU7^QJ6jY$x5Q^MQ6Rt?HLn#idn +!iV1h^#6|f$?yZUOd-x(T-oVci`;)!$!^Sv2` +r=f*HiMoQCM&gWLav{%wH{u4SqLt)`F|cXLntE3Ry97fC+XGJx|B$>dj5@ns^(W10q?;e@`UYyt4}E0F}xWNh%kZ1OS5(bQctyneQo;xGW&po7WGkM^J3^V}9i(8aq3E)o>za +hMs3!^Bc$q3U`pMR&?=ZTZdxI>`K~+T;MDV{xLF0r8PymU40*EvbH+8QvB?*WELCd;uFO(pyMT=}=^AD)x0IOVD%Stn(wNu4At%(VWWFkj +L$u=YI;ZU-S8&!Tmtn97=+Ui4Jo3+NQ#LBgxMh&Tanww0-mWa>eB*XZZ1_M3t;RkhT!*%HXhtb+}5)2 +ew(zHoBrAUEp2ui`T_|fyeLd4oUx8!v^?ZoFe)lT8e(^-0?-I_s=3 +up13ZiR4l$M7xo)jRMwq#fe_TUF_N)YQMS6d;5)}=Q&$@v*MuFY&gT5Xp$x8a^9sFfDI(N|Yu_f-$Cw +Y|B9Ec{4YJ_WVu@KrG5dHhE5nzpCqxoq~+_es@aB(0jeLEE4;)vDcBu6@xRFI&ik|439@&r0@;t;249 +6DKx)!ea5l^$q8OReu*RjN6>;&sq%~W`1+05qmDB9NiqVYtW-_R#*(NwGon=PL;0IgK-ng!VniB#zfS +x7NzgQdC12qT>xG#vE*TDAM}w|;Vl)_B{v;>O?C&fPAx{~F}b|JD&1dT4Z>exx7*{T>~fQH*!G%BT2E +9V4)RlHKFJvmI{Vh`#(WXEx^JD7g`YKRaY_1bwaXLu^?4_cE)vTdSzEE0&f%$TPLn2LY(tT=M;m +KkOE+6-%GJ233oEAHK<7spBTDKXdXb(5F_wObqejF&4NNV#rPdRSrs4Tln!ljVSNP?d0FIXt_F$BJqk +^?fl^KTD5#Y_X6cA78-aR^f<-&J{)SY8Ig9+9t^xEaJ!rZwDOn#ET=m+5Y9G8-nvT1@HQ!RnCMOiw~I@;keI$ttexO@l +J+$8T%LmIS^3xu6qLQ9^6F$VnzV1d2q2A3l>=gXju7L^~Y1yALpq*oT-@U&SKyz-g&b?^B4PmrL?4)Qvg($n?FAsuVT|+2ivlV?LEL6G@L68<9WQyM8%n(fWOW +xZbEae(Zf^>CWWR-OhB +BE6%d_cyoB%;P4$h+$E}71o<*4#Qe;)a8Fqhv~plr%4S8f;<}B<(>X>e&g4nbAr1ISB3EgO3;Sv3J7aF`B)cb&5c +LmblCy2F~3#U@-|S2xxm-L+^bZ7E?7_XAf^o!DQ+Tq!uF%ZC*jQmM5{UH5ajiP$_8aq8rzl+wb6W<%r +){QyAuF!XFEN@GX>)LG(fc6{QPix*S})T?vEgFjjFFK9p7J=DXx~2yScE1UIm9s;h=k*B1_hVDx( +d{|f4_jeLDjfY_*7m#jCIe*bsk`tIb@t%cb!im`o7xr<8@mml`s1dmvs*~>o5+j6K +_$)}H08^1LE9F>0|-Mbp8+l6Q^X;&Fh#!eZmIf|RCzbV3yb7C*Gbi#(aLp?p}c^YxpG!JkQ3~9IqNul +m}s!1`fTGQx%w;+RQBE_m5*ap=UL@_g8K$iYN9K1bsRI)&FkFis_q(P5)e~v>_YpWXZ;L0g*sk@rd?j +Gak4W}r&QV5DOIksg$N6@8D)Zlm-YJYS1%8SMuTc`Xu=rHfupWn2~XbI2z6)D`7EO59)uV58}X>c1u2 +;C)r1%N6t%cLiAnBD0)#NwRu|cY;~#C}i7vIcGYP47idmSV?kdpJv!UCLq3xeV5yUp3n>g#+i>&tno- +`>qnea`6;UBHIYXQ()tY;Y%8ryQDZ5!GiX$Rt#wLNkNa6wtL!sh}+{w#7A5|oatO+GcZ9S|fsV-XrR7 +Gg&Lsc{%RbSv)1A-fCxZ{SThAK3}ji9oSX4?tgl?&ToH$dn;X8lWRDdJd_@*g`o@j$zhL +O0H$T93vk2Ae&0*hF48sfd8&XgH(%;kD(8`6#K#IiH}e(@Z3(M%`&qpr7GzjvfvD|?f{f@9OCiV0Cl$ +8j+z#hgNtDU@6rAFI+QkDNXGv&0I78KuqBR?tfBx4fpMTvz>2wA=zT3TPYb=Rg%)a|uoiM?p+9P&axL +_{7OK)h|3?emRf^q&5szR5kn63BdT+?ve7HvsO*o|qVy;=m9^*mk*1R`H>Uy^_e0)UUh +)w-@7hbLyHrlI+MNk+i+C#|0Ft_~9XFl9^;ZMpC8xepRb*${ed{mKUW0E7snNbw?^tF-_nLGv?-IK2u +8nx)VswY}-F*^Idz|*I#P{xF*m0+QRW#791spU$yY#eJiV%!kjKg9#4g-+&r3lFISz_sN`hhD(1-o!$ +Q53syG(=M{$_^_?!>4rPw?}vzMJIdqRaXDUCPT-keBBUU&+gOZXk`ki*tXdZmq +s0?)GSIklut3qvpYgK9R%fX}QmSo_m-&a*Y%Y1uM;B+}t3z$$M4kL=52?d)LOQgAADF@L`lArieY#z| +oUMOTVi-(oYPQSs`{2jh(N@@;8Q6qboS?pgzIU!Ua|?rQ~}T&mWjQ>btvXWt=P?c+k8^z+f}`^T{tu5 +DyB$(fkw`24spqZ#Ae{*Wlqg)OnaMVL+yy2hM!$6lhcW7VGf0Y_ZM+zzBKnR6^Dx>;VA4{r17Gnktm) +o`d;)4(2dEyx-`_x|Yg_gL4pS&i?}C1r_wb94g7NDbl0(4G^L_r{R;eb;)HveOcdtD=AQk#S2a0<^^; +${|nnmeLcS_kK1`vK6;shSyC~nT4-wL4lK@bd$ +Am7}xK*SudXpVHj0QQnSJ^I2M7r=At^fMvhD1Kzl30P +r|3Zan>fX6PSg6&)P1kwIT@VPGojXxbwHLnSH=VfP>jwmde{s9W-b#I`PB`T2g~-ELEsvMXs?--Bh3D +0M*kp*~l5s!vS@ZGG>=`_b^_Ww0z^`-Tzu1GgmeH@~I;g&4KhJ|1r1Fbxp&gdW*2hvHitx#BM)PNCSx?KMp^Z-9oBxe39k!10fD&Ax +ZMGoSL-pIl6^ohrYBL~NssZ%#t`hC<8$Ah_3y)N?XM8wP;XqUs%HnX&im9|;JA6YZQ!EM&x{`R*oqY{ +AUTq>p(zI6mM?#MB;S{-)^gTY6pA?q2a7gt+ +j=oJ9XHj4gEU;p95%H03-5SLVQv1nJ*xM+uzRZH`&`aPb>^OT)~}LnC{PCLEhJn;_N6hh!JB;NH`7Mw +FyryLnbKC1!W_si+ZJ7mf(`H8{#1r)Cg}f*$bK64avflJEP#(XIUvx#Q=kH*+ha~FW*}_fvzwNt +PP6WKECtaj;|9V;J{P0%F$xg1K9-6EQ_@<0K|ipqN0b#B8KZ$7K=WTHlsc}Z-||!V47OxrvZ*upy?k+ +K(&mh&rPHt%|7)c-j7TLtA12^P%IE_bomLPH*4oMcef&6ivIRqk82Aeww +)beulqF-t41VYCdswQZ1F-+KV=I7WwbKgnqc$CkZ|FV(RTaAEt!Ko-wN8h^8uf$PJi(7XnF@KAA#8Hq +YgXKHcJeTo!tu0RD@l(JuB{-Ow}WqfWl#LUH7hFn%mZ4 +}d6V#F1fPJg$Q|%%h3QG0dALJLBTr8!pLhNnfGf{L7ZXEmV|~b($m}kkAK1$d)7IPs2EE#BlQo8fFWX +9BfL*vm`&olAS$bsHhZg2x<7VStXZHhVuZ~tj6fKk8W~B!M5t9l;c`FxDDncz{@RBB5r?30g`p^Xxa*iY`J|JU^7(OpDy#4x&Z+%BAx&r=;U_7eIYFEvdwJM7_21BiZ`%5VWZ% +MdJ^{xV*e(ZsTjCBg-{%uL2X7QiaGS;Zu6WObRPiK>TCR4KnMDaL;00xNMLu3b$|0u3E>AmsS&#(^+B +2C3EEDp~y{_O8@;mf((nyw(SOPJWEr6i(J&X69PF*cJIO=!x}4T+ZqWoZ(t3Ao6A0V}l_f51oHroJ9} +*l9%W)NXglaGc$IOES#k_u0Be}$rZj*ZJfAB>YJ!q*7@9WX**SC2P!rgxxi<*FPVa))4&DZM@uV<6#h +Li1k233b8zA*V|VA#w`KR#$#~icbW|~((rB%hmzG)UC5w}VXoo^*N+~iyW7dwPc~e_kH>6=gOiREK(F +g)74<$@%>lPN%K2hnYO;&pX9nHy#S0BfUPs2F3`fg%G8*ykAL1mw(yXMMluttRZ<{>SYn_Pvt$f{2hi +%5!DT;I*0GsS6$i-@IN=#wRk_g^9&oOq)t7-66@gMK;#tPrFW`mcheCqIf-74EXQFmP~e;H9yF&}bmI +))jTKxTxIxP@D%E^XallPZJ*+21O4joCrs(YBehWf>QvW@)BLhIz8%Zht)vOauE`+YNx1y^#4({Kyk( +UG7@wG8|e|;{l+sD5&lP|3(&32CqIH!gNVA4JmGutBL}t6RS=rsBAZ@^ZrqQ#NM;4Kn;v=TGJsX10L@ +MWQ1?^2wsbW9=GWx7W$d4R@(DgW!U1S^p$dBN5{Sn+@FO0g#NC!rB>Y=_4M27jv-#X~w9Q +hG!Xz3Ot%aoFzW1X-zj{>)-`!R{U+CfhrZ%Yz!yGF!uavD@36Doo09|%;kc9RvL`jw?{T^0FM;8T%VTE;KY)b);UrFln>7UOP*r+(G^7{RZk;KON=Vwoa +;?qLY0MKUaSIIGuFN1|>hZYIud;5M0Hsa4jca8T6AY2*{h!)qX*391ofF#aH28ZL{8ZL7<= +BUGQ>s^b~Z}B(&(P(|fYxVgD{>HyyF84RSIhmRIx|5l>Tl;l?W9NSYKOGwNH?AZXK7zo4nL-Wu0=}dL +ii`RiQ^1`XU|abjl^N@8{F%^B_Gj-{3BoE3zzvC1Em6?ZJBt +=noMS>yr8XcB{QhGeWN}|=m~7uG#*STJk7wTJ3vc8@C$gZNSI45bO8lI`Sw2ELjF#_9rivU3r|@csnP6B5Dxd@=U{~el_f9N( +nObjS$R>g${=OS%km22OQdCH`_Q&&UV1?52dfi@E|hAqJs(O%4u4Rtwul#0eu{Wugr6vf#qJsMiSWgg +aCfT7OpjV9DF@rL7Bd45b3UKrH@e-+NimRT)=@ma3CuhjO5IV-wzA4Zn_B6`(=k|F+?yz_q;Z%}*Xg9 +->4n*77_VKEI8CA-hR-G2K1?$F)$kd+NSkEYBXTWYFi2i5@F~8He5Td_dbC4qM|X3LcIh6nay9zSr9h +>mRCQCHx{39Wb!pU({0FI8mGIma*Me^Nbe;8K)RihM4-3mZ7BzN~>9`6KRLTT=4M1hlMnB;>(9QWE=Ho2Z;#HxZKU%3T?|)mzlZ!hQ(60l7nl0Vz*TcS +<|6s>dol%n>-ZXJ5^pq$Gv}KwdCxTR`mcwOd)rF0L+ok5a$O;b_7;z1hdGEcWi8B?Zqun*@hWp{s%ZN +$U`w>n3-!=Wm_xrU!q1%}Vj!A=|!;>RP>Dl3>rcv;j>v{+#hc|t2mct6PqdGM +*smyezxQnl3{ZZJtsJb*~rXW_DN4P*yNIGvQepI9d$chFB(C&mM2oOBY$&j*)6E>0@Xo`b@AW%gW|Gr +cvi*jyz?_@C@plG1b`gKwax*zZ@Xtxb(1Olzjh1o6|OP~9Ia +Pc;uPOZj%=o`zE|ueFe&?UTV5P=^3@JMW;b^fcNG@MTaTJm~#!1+blxE2n8~cVHupO3 +DrcR9=o0I{UB#ppjgllhGE1aPRPr9hog1TT;tnk5D_n0amTTiE;Dx2~c=L_8r{*Oh?npAqy^=ksTAb{ +jYO}B@6~Sge*edLw>@;yvk1&+uH@RsY`Ll+(7wF(4^UAtt)Rz5#z?K={qLKTm!6kF*#m0CB|3Ch$)%B7IIV +Fyqf*%*)W44^uR-SrIhH@QpPMQWBpsX{31kTY;VQBH`8WN~~f`KL@15JW4&1ahjQjvkOjY@jG%xGZHD +In5fKSM>B*3X|(XK#ZU|KqNtyAg=$= +rHSi*>l!Pr_ampC*}~eVQCU5i?9jn}G+N|%_*72H5fIY}5q(}e0x73)hs%r1Vz+^B(~bx`ty4k{m`%% +cG;_L=#_|C-Q9b&B5hQn!*}&_Xmz(_TLgMt3auqrMZnEs5cPSn`rIN4^ER42)^)Fqxb5+Z_Nu_tJYbZ +U#viIGj5O=?;tFN-dI(mjI2PS>=)V+t^Za0B!2Po|{&n)?;7fF`E=4CjDH`|M(}aPC6vYD+?^>6Banh-)jr)C@)}JLc+x6i>N#`WQ(O+XH)n6*aA=@yz&L{q +PgLw!M}(fnRrX#iPkhRY7k=vVL)yVceAE2zO3+iUHsS-jv4vX^S=ee85-PY2nPkMjlk2Kn8%lTg3g!p +12uk!KflgH%BG=K3u87cu*eS_$nfBkIDze^r%ve?I +=Tue{Kh8#iHP;(pIC_UZqrrTRzQvC_nl1+ydrCcRGL-6p2#?io$u-8M$6A3XCjpmI0Dd<#`Fy#o2F`42K$l6n_k0(sTwb|)ct<)ehF1@SSpoF_++oe9rBogS&X=Pw6oCCr^R&@`nWa?OIlO~utuRwpu +waEH=_1Xw;KG7h!HUkfxl(lw+lRJ3ZaX8NM--H7=nJWWUI}HVHZ3zZDL-zjFgJs4k>Vd%tz-nt=%%a~ +`pTv6F~vnyHyhP#rA06w2#Cd@q~l~@hEMiub>injeUuCYi{dq!YMD4>;vVzi=~vq4YHl2 +NiZp?Oy7NF%UG|W-R%nVUs6v&e#)uQ8+6!^?$^|HiIic4S%_J0OxRQVycP?_vDV$Xx!xXCh=_g(^p%T +Os&iR-0qiR-P2t`@ZsWGDLlXo=X^2R%3McK|PMcEM@?71WuseI?35oK=TS@CUxYG7oHpt^k&^NM%ARZ +zYBPC`)q=Zd7D8gN(~e5$yGW%$W-Za%maIsIjN9RQ5R6fqk_mYSNlCt#q=;_%3QavBU9K1+NEgccb17 +DK91CVq!r`LwMVgdcZ+QL8O3IYRyocbJ{5QCy_f2%LE9b4J3VS%@ +p4Ik?BR;F-ePy6eUG>dhQ?;3&7hX>ZH{rWA*)DN|hch0?kGEk&IW?`OlrTqgxE+TN!59gFca?$Ip3qZ +R}{--shb3}SPkO+O#g-mWneh$kr!>rTh5lcOTZ=c`0w;^!5%@dbrpJ_iC^U_Yd$??dp|0lbMWh@a-zZ +%nriQ-hC{ +An^0pEGBk+0`Kh0@b>F?vngImftZnE?7spdhGU{o1}{?^#PC%^M!k5j~HVtl?Zsb1V}oF|M2DX(V)8pVEyz;TXEug(P6jlT&hec^G)BU5X&@Cy}m!>y+9rwgdM% ++9*VniHn?g>09hR4Ge^HyMh?`f{CT;L-xX2qlVQOZq-N#kmnl2~iBBX|h!yQK#cr9*wRJ8&$3p(Z_U# +7$U)#Z~0k2M`>j3|NG83Un}|lsm+XO%u}ec=-@538Z$)5hN1y`2dB=*ScO_Gb}xWg>o$y%qVe%%w5lB +<5^6M6rb;%t%;dNZUX~qJH~2$4I5;hH#2(4YUv8=^iZBCDRw!BUnXs?~4qc0czK7t<-J;@2p +CkqUNN1IFgmLdWoiK5LzQO(G#1GKGvgSg8K4QnZeqzX=Fuq=8iB@5cBZkpDsK)${(?FCS;*bI>bK3<4 +dD7bdg`;O^2~g1c`6pp7NDQ!+)VP@~lzG$$Cy5e*>zj3gJ~XyaO*#%=x& +?^Xn5MfbmI8E*q8tJ4ZWR-G?KIJA72#*Pmn$i0!2yn8WK*iybM{C$Lj~2Ad)to7((W6tMg>kq!q&$kE +Yx)#9Z(Y)wtQcnB-{10fHF!5E0DBt+A#*9+tH9)RXmYU9-#n>2#W>8COS=g%~xOMyr>axD)HQxfOv|o3lC8}gR9j(;LKJr#I+ +pB=h!PNiy*eN+X7!pi*sg8&s+kf(RU0k49A7tDcAmE65umev12Mg!otwDq^fUS=Bu_st +;Ok_Do?iAqGyVk^BWr9YZosTdupsovo9qytRSfRSSC6`{lWRD2@^`ioGb*7|tm>6n3^D8k+{e26Sv=7=_NO()2*!Wo0g^Lh=JQtTlH$cI-k)C +9K}_?dpAm#Fl2PbBpAS?uw;$ea5UcPcCa1tU0b8{QBoyrxnVVTu%z)$oWN$6JG<&)#&z^(L +p*C;wa{}#Vp+Rt)~Za9GM3L21yq}{6TdtSEkRnt~pWM%>?Z}F1cu)acO)UMd_TYc<#^nVSHSqOVSq^) +or4UJt0Yq@eEY`+fnA4xE>DD=-QgjL^q#X^|HP5!JOC{Z}FWVGOKSojKs5({6?-+I|ST6ABXpT39XC# +#!Sa^yKxZLYTr4&e?)jg%@?uu_G2%rKLg<^}X5`gP(Kt4S!39XX}>DgQc&&n>+gU)iNI@s(D}IG)Dar +Oq8gOn5@t_wi30`dvT?A3P8FUAuOojcd6X^{-`jfozn+-V!U!&LKQaI3{%P$O@Rgk#{O06O8+$x#;aVFWu?JH7+ngKMWs(9!npCM+)(fs=UvrAvXa2kYh8%^D`&d;hRYou-KKofulF2_LK-D{}-Os#&Lr-XVu0>KTqXHtAjKbCa(#% +>1~{R{Iqvn?V124Ludg)@b8TSq|Uc828(VE?;Fce!@uJy`$BI{=zify)HKSM=Or!j&?G{Q9(tk}M}F$ +?}lJajICT$+1l^dO?n*8ff;df?!L8Ul#0eRiP=2jO`DJjETj?*MNO)xFRu4$p +$ukmXJ}Ski*>oP=h1*q%faLrV4gQ7&vzkI-`AwqrdJbJ#V?nut!`V$Bc;h-%Bw6<-2cQ +YIQeuO+)makO@P^;Y0*ppuZDCE|8XX_GgN`@GYi!;n5NhrsjWuPc`Q4obNeH%3i8u3jhqwIG+f*Tjk4 +OZBUN=;S`8IY02J49hQFC!OZr07P(Gj1G?Xs<7hp2C-KH`)!l9X5ON?P?&++Dk=|XCSa(7S1~dZNa3ThC`1mEXaN}LKL +4!JkbJ!N>L4FmjBMViyV_`a9892k2a)(1M0G>r&yiIFl){azYp4K=uZM3CI7Ljxi6C^;=DoaJogLd0V +KCR8jAjL3)HI_}UL1XLIAS?(+)iCCIS=$+pw`7{m+p|yUzm{@%&SX5E)H-eM6x<)Nz$CH@Szan;fFEG +y#{UM{-vcHG3ipl-ZPj)y-r2F;v3+%Vro8VvNfqy0R8t)A +U9;`Jbcj!OvyRl9r8z>WP`L%Qj&DK)V;RzQm@_j79p)SS0j$oY2FXS)8KJjJMA&4oJ(;`%scG +`IK2vQHeZyn?@43X^44Ai}2)cJ9Ut-IVnnY`2=5kNAXPn-EuXry{;S?Sv=3^Jth}{hgab%k8&0`)qyo +K_Tt%oq$=f2F?o>xh-oPD5v3WYi6HBuVxuMEfr%a)Y(y--GuwC`kdgk4WNei^0TYlTn>Wn +>f)?OU**rqf2S$rTvdevFH@vQGQzelkuN(h0dI@8X{qwjYSK!hyy?v`D%mC5c5~qd#C$WXUhApAS7CI +22RyDwA{bw;AN^Z%`K~3Zt(8^J}#mkwh4O6&lx3yLcI?uXD-(v0JtiT7U_;b2Vphr2a!uUa_m2spo_(LaWd2>^>+9W%zVz;R< +@-k!&;m@)*@nu!~oGLz_EIEK)WMZ~T<{n766m;%K!zA_r^-t+nra;|5(j!_1(DrIq?l=^aw5nfr=J>6 +6`Nb{pcOCpqgFmZC9K!P@tNN+@>{X^moxSM4rR2f~^_aH62VHhDNwuIcS^orxnOR5akIZ@LAvJq=yN# +a%QmO!d39=NIy!e%+_yfYi^--thcfArm%yfkoK4<~Al`<&+IIoleia^$*{I%o<`x7m^eSe~bpYOvK(l +H-(YDc5e;DatzIjvD=AFiebqWnZTs~beu;E7q)c`Y4V()6qB_Bi-CxbXLjSW98GdfR${-pYnJ^mCzo#n!LG>D!S5ivUeXUrubt@P^7Yd=v4E*fo_5Emh((I56@_s$zE%N+t+teOOcvao94^NV +0(wc$5jn$j6BHf9-e4+e*#eqM6bnYekTS?uzl`$avjvRam+l@zom1i|afNC}A2vDuE2tA*q-3;B5DtC +(AGe&PUM{m-a9l+cjhSsG<^ViZLIaqEiXv%MDt-jQ>{!&$IwJK~M2sd#pdyIHl$F+8MLMfFJKemU +tz=OIsOq6@%0lKMU+>|ZG&6^6vcnTL-gS$P4og+52iffq;fhsOgiCX~99(4q_wPWM)EQ8VO1ybyuN!L +s>kMhU8G~#D7b?<0zt)`yMdIj-!90 +8Pj5*xVbm2YFQu&=&|I~skM*OF)fBgXxcz+) +6~Nbc*umahJ<{~i_9FJ%Akb;m8je{*AEh;YyglRfrp8N5D=S=0rTG-hg|NwtS}t= +Fn}3-k*+wPFmgKNa$P&K^MG3IZY?Ltccc5}nRDncEI%6__P}OfO2PDIM^51x`d~6r4AkUZI8q*3YK_q +iFsFAT%7gd!WTql3~Js2eZ9A6bq=*Rc!E3tSVbe{f$b;1O)Zf`=$vwP_RTMfv5uu2+P{U_~88mICw;B +sbfAJMLlaPY57jl*LPRv-_`ZRD=F{;L_?V=upvL2gDt3x^pcxR+jo1Lc=A_V9B`8oM>h;vKcLBlPx+w +)plNlS>Qq_q@hlR!FW^#~$aPzaXYvDuEUei;I4_+yMjr@Mc!M-6*hlxbvt{`2-@l0~v+k%Pxe_Zckf< +Lde%D)1SB|OF=oSP29O~<$Z5A)2ryJhHdR+{}c4SH>>QLvqE+itROi!u8y%^yY3Ayu`4gIu5$TDd{4? +(-d%^kD{I#7ngFOdcy}G%TBq{g6}J}5RrypqUodym^}%_YWXm6=r3Dtewr=YmL7ZKs-CI}wN0uyM^pC +YTO&q{#>$gYf7uVTB>tq^ke$b3{5UlcYi@d^8vYZuCXwxF@<*!&_9#=B;4;jn4gO$q6(tVay%U!E$YH +QXUH8W~wgcx6+*ycg+chOc{m1{|5Bw-?vKVcf}$tQ?k4nA+?kn +^=3Cdl-La9edP=u{#$q%wUxMlvGYQW)wd*i5J+0YtKKg7Chy899IDU +go$Fz++YE1&eX0J1l9$iUkieFL-LdG?t$QwJUpI-zhm$>>jW6yX*((mt(Ary6-GeYT4@y9Ok$KGKb}M +$Trvtvp$z!zuu&sI{VECdqy{6lAq+ZhLTc++9F_2z2Y++nuK@n0!e0*jS>Vr%Z76@dM=F1!2i^qU1m3 +;y?uB$Kf4^_b|MNga4PkHvy=sX#dBD9TmNpR&M1@aVb(!TtHAR0xBeiqM)e}%0(drgn +L~|1-+2s6_@vI(bUqk-O3iVO$9ZVveaxb+qCrc5S5gsR_Oen&vVYX91zU<_I|(L-~am_c<#)Z^?9CoX +6BihGc%r;^${Lb9v5|PNDw`Rz(%kTE;Kw&*}rQUFf`g#>NOX_u@*;&9eUVOThHp!*`d{183fdnLDNfT +pRA^Qzh8=cFMLLvUcHp*)yw2pwM+t0CRIzgOb~dPpq~JOVFZf?dvrX!aEOQFN9Nuc=H4rYq=B0jrlzc +Zq>x++3k@Nc8NyMZnlk&*!*K>Eo91H{p?2D9$#{lf6T#~QWd!>O4iS92l*^_a@wy$Et;lReW=k?#lIc +&TKbd}H`jP2NrZ1VEWO~*v8GDF!6_8_bcWx?;Y%faYYtL$8t7TT{@3Z8Qhl$>NIbP_eT>sfY0Izs=!UypW^!%-U-IXT_wm*rkLTe9*M|L@$Sd(;jZ^ +81@9L9ef`k3d~Le_l%oo#2)Af*TZbQKyrO=-LaOPI(547$i%7uXRIoZP;;PMJX}i7nb6Zzl3~F5(|HX +(*ztZ@s5t(iY@(9jVIUjS;%}&E3Qb^6BGI$+u35!u|xAMtdwhrh5{VQ;w(cVkT0#YRa}^+i#ha#c&`Q~Evz+O2^Ru3H0de23_(b8FzF(z>?>;%YmOk_qQRC +!DiwOy==3v^FSk0qSvc$GOr~@p`EmfHlcac+pfno{lFsib;pN)fr=k<3^#LrN4B&ig-I?MBW|boO3bk +_ql@%$oB>EVdXP!P$ujq%&WtGw@umW1I!(JeJk4}C{HcE;>tr_#G`-_(teS$s50b&`j(g&I>BuBOiZl +wmUc7m7`~XC2+FRJq5C=I!BU>u9b0NW>ALZ-q`3BthrU?NZ@CD2sJoWNR3RlT-5OOlK{> +?-mtFQ;Nx3TQo?z*zqH&~&E{=s7Za7qi*;tzTn{Y=$VWvl*^zS^x|!a=Jx%lAWTJHEa%59%6HtQp#q6Qq1NkWe%HT6g!*am8oprqgdHYQzo! +EMH$UzmJ-iqt`fthO&P%EOr;N-vz2Zd3R|FIEA8RB1fxW8U$++ +TYJNL2U|m6wXiiD);?^tzKW!0uDLv_y7-29Su~f0s!LFq*A&fVI=f8o9o8qSnRM9#r}VP4T%yn$*;{$XB2HJ95}mzH5(!!(yRs!OXduLRA-S9NI@<~2%lIqzT>bC}l{&E=%(5)kG!UU +T_SbqNXc%GF%TRG08DFPrA_qUvG^^O~u-tX5s3!@Oo|E{jx`ptDGcOJ+WaDK_S)lSTJIT<|D&_qvD@srZ+x^bX<-J9%kr46ah+Vl +8dtD-6`JQl_sVZbxvE;^yU@?uQpA^_Q5~^2-{UTn#*fvgcze|ET#V;$jtcO^;rH5g(BV$^{%-c}gjDD +Lsc!a8jZfNcDp&mbN4ncvlVkJ#sUCKJ8dW%UdNw|3|IkSq*{)@6%MKrRl)3G?u#;zGS}k%XS}B>+L?A +`_;=)cWeAqUmJ2CM?9v_hF9;E*crN51I!ODe(`JZdxpHnj9{B*|sR61CyoI4-MTfS!b(s)V6dd_U(Y` +)N4y5YDoKht(>iST}#KT_rI5xsXw??2r8HBjIC6+}JxB5eI~zNb7$6-~zYItEI6Gwk~^Ip5n9!TY{=+ +tzB0eU~vGMzU?cu2_KGOvEQAf+(hcx*f(*`1(pumVz1;4&V7y(?VVNSm%YkQczgJ5<`YB +Qo$sEH3@EU#Lb%ol*HWJ`<#RpAY-WR5Y1vi&~Uy=1X5T^QAAF-{#-7^txJ2Dp7LILY+v{#CD@7vewlZJiwlX+lTiF@R())}JD>p4Ls~gM4KBsOh14 +D^xiwzvzXW(&_i^mnGv9WA>osDHfhiV(k_VdOv1h3f6`?X;8ta!y`cdK}cH=nT~%RrZ?2-ec=TsvJwOSdJb%Pgqy +CR)xRKFcp97s}scA%Jv{wSeE^UD9w>Z%W4@`JnyR$;9v2$%Wkg +tC)pQyg!i@ITzJ1*Z+k1pE_W?%p(}025y!L(&(f|7DQuTU>4hA@t`b#RuVN|v%1Dyx1c0rt>D6I-qx!hwLs5V +Noor+!RocDy=5dk$HK0E>ch|@QZecPN{f=>-bZxgd&U-+P^DRodHH0em>s!X!QD;GT7hns` +t5`iF{IRI*Mc%3HAgj~id)4 +jWas0OYKk7NZVcSaCvK3U*1&M!Cl)LCiS +ghukx~8hHWOchTXc>L#%`-Xz8qgExsmoG`89t6 +EFEYZz29(96YcPTHdK30uXS>~Nu6-7NMcHj7<$4!P(|uC8e0UMH4=n*vd~2CP+gJFektxr$fNFZK_?A +ImM3nfB&|zfHDJQk}Szl0(^!ZA38VabmpHY=Cn!yX;Xnoc;be)f$%Pu_eW8FXBbn8_pD0cZqFi90V~EjT_FkfmUodJB@}H8_t+&Y$xO75{(^GCf0EYc$VZ7gr3)GFfDbSHvv74!3d`i)ASKh{Z&1}>_Pz5O(r|&h}?xq*QUhd%49cL^wY9XpS&iY +XJMI69$#GYc@j25)8_+yTRt_eC-Mf1yC(7f5~#>-sw&X=2QtyG4bFSlTm*%01jHl%uGWeegX9JLK*u0 +Zu|F4M`=s#C4aWz0QYIP}eBEOgrDGNV-Jo6CT7sr{_l#Yk?Pj62IfxVp0}#9!N4rV55@XBk+b?ktNr% +iGHIDpgXfDiU1PC-E1=9~6cL*~Un&{Z9Vf;}&G8w8Zj;F&{^nd+~nz&w(s~hc8xk<)&|Kou;qSC$^A6 +ds%qzfhw(>m17&~A@Ljwb!v438~adEU>{H;wYa)fw!!%p+FM|yA2!4^SKrKPA@a97+-nDN!hxFBv4T_ +dvv+b{th~wCyKFMwskGv~V0e?fYwByc8L^z(p!qr1aA3}a+F0(9+WexoE7)k(UA1~r;@gl_KE}J{exmjh!GStDX-m(;ygiP)rh)+Yu}u$!!^e?G(^1D3>IaL9 +ed5#ZLE{K_eCBHSqCV>C2U+-g3m8HRFTH~XYB!7&0z5{6_=i?SH)JFZrAkwb1U^hWyau0vPBDC=kPA>Ee+}F13!l91)oVz=TD$-~7quGMP-FH3yIYj8dj9 +VT%^%qauSU+6bJ5#KPbnB?X@>oep{+!aGWo-y=-r%EMKZ_l9*nD!!GBMigg10-%Q^yFKshLh8AwPE1dGv5- +QZg{E8V(HW>FGG(X4{jXh-P&*Q?#pf_T;DP_M(!QFG@xquWYS+zr>Zpxh3j{ZH#j5Z +5CfRM;}*`2pPU2KD?RhYlzoRwxO8V=4)IPQ;!4>rW(@w6gsC6{tcqv&^i7*zyG2)c0btLD+Nn%1gBzM +;u@;?E>Vx|b9(FBS1u=(wo|u{c&N7(FB%pS;J*LRuO({&D9weX5%gR5tGmss@K-l`7iYZxg~Rwn{3hi +Vl_bTxCi!PeNqM@7(mo#@;%T;-GTrg5MUt|edr1EzWuT9CvFS?a3LfX9eLM?IMT>E?^~gs^k}X-m`$L +CP$7rTpO%IkQI6Y)f*^@YlxpG_$zfDSV*gvDYz^+UDyQCG=i6`rN|!UmW*@2pmIpE}`-c) +AsTNhX8D`#sk<;a} +~<@ndbj`3FA>5gV=0Q3pLEbQKsI`W+l<4kcu-TO~LyinjsL)b@W=e4KhR8zM3@VxK&9DjVIZ<1WA4`_ +LTT|(`VEWs&+ojbMO0+T8WdXLZ;kCuR6O>Grk24A{Vjv%U6w6`$otq?rA7&?0>8VeWymh|7h@E% +<8W?_de|P*;xM@F3giYM49C=EVaVOdg}S?``2E^Q55;)nN$`VkS22gkBHO!xe3WTx3y-fnK2Y&@5dLl +4HUt06rsL8}S~=O-V~^WrB%%JMm-u_^nYRtr%b@J_KTBDL+m)21y49g<2q?RQL#lQCcAX?UYfE_52s4 +oNt43Cs^C1@Yt=GA+)O&mY(weD6HAv1g)S%^+1)on!nlid6jzFoZqzYMDiev-JdGMFuEKf+%&S2!4c@ +*+Vplg7%iYhNg95Jr?5V!paD(dIm2<$K6w>1&z6dbQ#96 +Fr*DHGzKWA7a)E+#6Pn@Jz{GgA>d?00=ZI*Q*R2nTNyEhFG%p8vJl<;r3HqalCP?9l@$xFdSuvQE!Z? +yFkbPxmYtFvy&F0*Ec>G2$gmr0Qf|MX!6U;SSfKBmytn4sYxP2LzR5=!jGGsn@xCRIIMuzWBi7G%ZzP +}VTiKjmJWC^6k{3B)DCUgxb)LCv{N$#Hop*O9gIt1G`cCFwn~MALFnhv_gz;PY{B8FnW!Q_n4dk+|Ly}@4yYYbIAbPJk;JDJ=xoeRnAfO=J-Bg6dxg^E(q +PB;G67n!4bm4{C37vkS{)F62MUQhrdtRtX=*<^u5_+*tLXTaM(0Pkzp=|Lr!O6_F>q95_+x$ar#>FzeVY_ +VVhb+Nm*u9~h9q?2JpB)f7rltL6HITPyOEU^(ym$r|?Y#%OOl$oh09be3->+}_Op^|*$(9gs*B +7^!I#^2#^PGvY$kCM)GtDT#vxIY@bH`Nb@4V(KvR<-X5hEECbnU%773J$D_@pZ;mIZLqUY})D>*$MMk +^?gF1o(hUN&9P%^-zIn9KoUL&Zc=9N>Kw+mRQm;gc;P4KQG5YRW|ec-g*0;C=iCF+<8b8=vX3Vh92-h +u4p$uh{P1^&k2cwFKX>7*vx-8sq!7^o{uln`aX7D87=@eRn`a86u+tk247juPcVMQhFz)vg>4{yg+$yub$1=!TVHX +Mr`@Au!bv&USMNiD@D3Er>;n +}m3vmh2N!~W_MP?JlQKa2Z&8v49$uv=V7ON+^l*~EmkQP&E7&NWk+mI~AAJ{2VVn6QmWEU?!Ikw8=co +-9pgFW5s;~aB+g*}8YRFHV^=TXE7c58bjE4*lgTzKbLLYYzo;G?RfUp5>RliJ8Z}JgQ3UCmpznW7d$0+p!M@XVl7fTvsO9QcL|f2DR4e +d9}{w^L8>#JM(YcWAPLYU%AKP)fijq9c31ZRQYB3X|IwwCsj!}=s3~7{DfX!dmuQ;3g+OzRZpX;pl^U +2SAY$WO_ZPJ1N(H-nX(ayxJki85el3;9p4m`D?YY<+BdlArUEyDNPqm8@e_m}3w{#ta}R!UOG_{rJ=A +`v_(NMi=gxA)(`KslD2?U#rLq1)?dKiem~8EwJA*$j|IXd!Thh8T)-%q2(Q(|v=Do0WIkC&LfU*;O7<@XaB9d5^*Rc&pYfF@2VILRyLN`hf{4`kB|9-%b>&o2SEiQt++Xt(AM` +pkF#!;;Tf>x!NfhKuXo+9DV1D~%RzHD9~%Q7V-I$$Y--(%1D3cK%Lp>ceK@Aefu+rE6Jo*?BRz%z3a_+J=St*cv&?JuQw8e_33oG$@tq`Y$v6jhw9aD +CwA1m$0iad9~^^y~+b{=v~1`^Ia&M*rR)oe@rdIv2Kw_;C7u*Ywb$`_aU@0Pu|4itoWRnxD%vC@}{W<9&?vCmwGBN{^OezxGSJ+>ao2I{xh^C30c +U&jK#tYoD-TWrIX6c--oGH@tzsQn_0}V{e=3sl194Th>=`(zatIL7~ikdkgYf!Cq>vwjCuwPCi_ +RkbgB0+oY-wQ}bD+48F`ngk3f*zJxYB*i?iwMoN3XCHpF?QuuHbeBR0%_DJ=4tLx)B3WKPMY|r+5r +0l9WA4*+uVlqH1=ms8FWQx|nkn$3V+)&mkqv2>noR``LN;C?&$=7>=s$nXDc2b9cFpM +o}NVU@8L1M#dj8Co%;u}|!YHh|y(=QRfJJbvJt=cR}%FIIb0Jf +VPmyb_>FG-n#cjf7sHFvl|-BKQ&i7Kk^C>%T!owIl4H_F^v?%h>)=ibV9H3bc)VuZanz2=5k|9q1YYe +>B^()3cie^RpZCsM#|O+dve9;F@y?uxHe#gST$mSJDz`v;y)$`l@7Cn~4ba{1wKfpISFyH#QmuG4bEr +^vn~d@q8s^)&7@RCsr5$>{GfS{jQhfjFnrI7_tSySNtUS80pHwy`<>fmLmPGcCk@k_EnDXH9&c+*=qV +CuUM3I-YlQ@XfU}VkHs3QJFQvb(2f>49rUKj`k_g_L0l=Zt5{z#QE8c3p~Tl=6O1I(O7U#0#`6!#i1} +QK&|ge(BXy5T(%!TxaEFZ=!rZUEj;?_&`#7Sh+3i_kw +A&Dck&e6Ke)8YyTysejk#lC>r$)hdlhE_F?x>(&~-gLsyo>RCU~0GWr}oFuPSLbtSJnG`*(msnG3J9q +s2Fmnk)zcAm&P+!Z9+r&d;<@r@H)wba80wXeo_WuUaB+UYjdRND_0MFzN+C7ZU)@-%HPQy04rU!3RC|Pw8p3K +mcnAF_;w#Y&tbwYFr$+w6QpWdgs9Q{`PztCc2{rH&Wz49Tr6B<`CG_DkK%1I}UD<_*YuAJ$lapla-C6CF_!g8R6&43 +nGkXy>1UzhSVgK_rt#rtis&co&3;R1;pjrkS$luq|AE%1-CpK+Wp*#cb#-k#?tAYr3Id`_O +tj@&coISd-VnniM3xCb9c&wU2kGFKk!iCyCa-C^#C_4ypaQxE47jsYCk)+0`rA+rW2B>nuVRUHxK)Ld +Q)|xz`djuH3s*qFCL`ta?@Why@k@&rhg>5BcDGKrG}vu+S-t@`fP)#7+;a6J7ZeDf!hqy!^)@k4LfTy +QSwca6O)wJS91kZ4(03pkn#@F(xw6ohYM1y&SAgLFe^{y<0Dh#8cQ~WE7qnA_}=b?-rD?zdK?5#IfP5 +~yd8w$dtBrFMkFRDWxAEdw;YjJGQp206#RL2wykC81V2+T@9T;84SUbFt;DVJ4Ye*gF5YJ<##Qgs_!Q ++!zC`_jd&vYTKdfR>D#qzdt#cLKKY2o;rGBEIW56cr$S&Kx6f3^f#0%=NI?uF!7jAK?#(Gb@?+B{)Ls +@mTeS>) +FzjwW?Yk9u4~b5SDwnOxl-=Y%vx8d~vy46BpYmeVn^G!BTU +(9QB#h8S+$iWKuFpb>|BtPPuNG?n#P!lIxTVTYK_LN>=`1)s=Kl>U#auP5PExzS#%8W3Bll-XERljQ4 +M{-xiN{caGoI$XD;$V#asLog1HA84FmCSR$@R_rG95ixXNA#foDO47gDYt)z-I6$yU_GR7mo9@gKg^l>Xonrv;K*CshXwdI*@xZX_ +C~mh=E8}c{vz?h32oXdEuf``YCGX^WE^?PP8%tW|2)_7JVT>{Ed=`rJ|U7YQ0)6rvqLFM?=-F$6Y(e-J! +Hu$|x&g3AQWeifn(K`(+q1j7l&6J!$D2o@8pCU}M5U4l;tP7+iR{6^sYn-HxCx)Ssz7)&sVApJLd{Y$ +hFY$Di0aGaouz?<+k6Lcl$OE8FFBtaU1jbItU1_Dv7^7BEWX)eLq3s>@bY_LUqIm9BCC0fJ>Z(Bsb{T +7#B;ky>mxVPoXNc7C;a%ntFEW^YTyncq@Yaya?@?vIY*b?$`CRy|Ig_tVx#Uzm<@`RQCx{EZCMSp}pj +Yek@Ork#uCPs@53P%GU`m+*Fxn!CNa_HAioC1#VoZ~REfwO7smd@!;<*-wTntZaQ(r-SeGLd +*bi(JwujXd(r7L(~Mn?iu{>6}7MTJXnyOp^IDVXHK$;i*tdO4NNI6`_Rap~kSE_tL?>!oyc!q41K@XMeeEUs{l)T|!4zJ-p4FFM-@lL_q%`d>=GVuSUoMvv(w$D>^C* +^`)2OfJ=ykCsMLoUVe*ym#PSq-AGUh!riBjKCa(XFViGNQKde!pIq1>z*#%g`e(frX0PG@PXKi%4L1Z +={}p+uol_d~J5@_vPq(HXf~Pqw4_&z(ox*(%Y +P!|MTU8patdBv4^0{{W5JHtfl-X2%x7ETg$WT7lo^C4rPNoogiuqT_Z;+wAPUkXEYmeIFA +l*=x8{H|l6#!0Ki3tfj=cFe-JD~FJYG3)WqEF5A5nV8J5kl7sWM=b)^G^`-A1>DKRX^@9LwTRYa7Lth +@-TldIOXk{7Ec}sC0+~0%oy_)R4*l4|8qV>LEaDb2t3I@l{E-e#<}5Ndk$E4P50VKraxs~hxX2t5#O4Ei0Yl} ++s&Vq7)i7EU{%R%-p3=Arc0pWg7E&)O&H&f4GYtT(8CW|zw>?N6|K4|eaNxe#wz1ICKIYDRM{R`o8_P +&~s4i|KXT?$mS#-6fICT&;iB-6C0P?c9#ziQ_h5mdlQIL(MyvHM=av5&WGbFuEN~{u5bGso{AX_ue%w +D!w|7I$kKC?lJPXJyCHO%-GhvLwi7oe1lsu*OYA5AavJJ6w3G>&+R} +>oJ7XW_@2tNsQKyn=xOWe>FMb4vAeGS)T#aRSvb<@x!oL3j~SL(^oaQQ9%*SkW^pD7RP2!i(bh@UsS~ +YSxWjX%yX<#PvgN>%=^JYMvS?X#II>HlYjH;TC}|Wh8tV8Zqv5iO*gkU-_oIDK&Q@Ky54$Q;O*VI_Xz6QE4X)`zWqW% +`-g=O7#LxR926BDGkD0**tk1}#SgzTVZ_M9QKOTR$Beye-1rH1r`(e|aZ;K!ee#ry%zLw@X6NKi%geX +fr_Y!<>%RME&zV~w{;xmgEB?B={;u@9@^2{hFjxC4s^_nEh6xYhh!>r+bt+-m;Zuko)&0{*Lie?&k2TGF5K&FFW{Bx`ine!o_FYv``)!nbUQ!g=!@Ma2&+SX +i>ixwv%6(q+s4@y`bzdiaqQD<568dd*{xuU+@V`VCJ$_4G5(KKJ~_7hZg6)61{C`r76#TVH?U&9~m(w +tYug`OaOt-`TTw-~M;sJMjJo2M-IMu +_4S#Vn>!;nw^sUC5g1N@H3NJIY9~ep_v)UOMM`uHUq)q2wdUAuk@dJGV{D+#a%9ySKkD<8!^lUSz +4$p)v|QnSo4d3iZ`=8SCfuvB~Yq$yU!o5=JIPR+=&rkQOy=A2w>w%Iz~NJotOm$e{?o1@Jpw{GpA +;oQ*$X$8ij|(Pleju5GK!>nr2SV%b7}{IlAb03&xv~MD0+*bqtw8glT+^%b7^{xWvai&2G(`m61K!oX +!Yc;h%4{*$mzy-4yX|qf=c2mdvlXzs8FVvL|eP*SL= +tME|$;9YgwHtXn*=i#eXV}b>a?-5koOE-pzV_6e=hW2kaSu*2kIadjau01L2~W(-T&j3#YHF!aT2rT{ +l8s6R93r-K=}zU?1-ASt)`_X)n3a)j?Qiasm0xcfGp1zNtogaAldR^{belEL+@*&-Kd;BcjO-rP?CD+ +fh;`(lMplp$1wz)E@m6tVdYDRV{7a|o8#p*WAo@29`sf_C+RUgvf#LL>Uox*d)d19cz;@& +7j@@l#>GHg>I2KlzsJcIMKWoIZerd1155ASrk)cR#it5(=FD;K%Z-)zqZTBAn|?iO-ox?MVUF{f(SLs +Dv`Tn`>yZs|g_Wo6~eK%$6U7o98PG|Y#3h19^U9YFo4vwj@#RXWU%G*f@SwVkD +we_0)WQ+vx*~-;t9@=nzm9Z2r_UiN&-&x!LKpp?&#H+{uJn8EE_MCKe{} +(1-J^!V4)gR9|yQRT&p1SYq`80N1J)h4OUESY*0qH+j2;&LM9#Oy|aJ7BTdkE +2zlD_bZ2}Uj9rL&K;SN)|VHCR(A?CZyV?B#UYEW*ps+ +HmZ(z#6&lIt}`rSVjgCI?dPT8K9M_r|!+pnI^f +-&&jez+48btsB5)RDT>Ja{EW%jS_-3bY7h{2yA5Wvw9x_O`8Dzcc~PWdxV~m;=}xZkIcfGR>tNDlhNn +(N*e5+kK+~?5w#gpx_AFb*pjkHSsGQ`CG;7q9)I9N!Mx!enjZD&zcA_n9DDe-vzPr0N_TzyDX#nop_% +SjqEswDY_ZXIwnx^rMQs@Q_8wgiZ$`gu+)>Lz=miR~!m6emPzugfta(K)zebnR>D6f&ELQH2O6G`z%7 +BOy#wmkZeC3^dF2rBA{T+AYI=&q>g4|}RxH5AoQ!Nz?ZiR%q1Mz~{zOZ>$9DB|tPGU{ +c~qjKX5}L019fC7QXJv%wIxZ2Noq_|>+HGkNTvb5uD#Zj-Vx8y*;5#)w3wNbthtCw6appjBzV`bw?}M +#v~{9=@?>jX0x=%~^`nRZHBosPHezj7B9$WJ66!I`nmXNjh5NVevDxI5nw4>1UC+quw8Y$u?5G?&rTZ +PFYvfid&ylvQ(b=SerisJE$t` +8gnsa$LF!54!RzGJA1qo!bd8cV~xN@Hv)Y&~`-uVliM#Ynu*C8x?gwYi*50t!n^fju%RL5O}R$2()ej%k->#n{_ItDkAf +l7P5&~JQK65)?BevYenFZEIv|e21ysS_G-ORTLR+cM#DRyPazh7ABNV(len$ros&1%iWNhvF6qf`gR| +`UQ;^LddwM!)@>nmYYMGmwXLXfEGEy{fEh#lG1H+VL@gZY2(y9t%q8cvk&UCfp;@oTI#So7AW>so5e3 +R?%-2e0Z|MtQ6Ft|wh<<>i*@m|(BQs}0-(xE;6^~ZI)(z%wu``?A=f1dvn59A43=XBZe&`hi;;M*%)3 +5M;4II$P={XRmS@AuWrMw;1J^J`-8yFrT!)1;;AuesrSuj@2``z@M#AMHI-dymrI6SO!ZHTOi#OxDsL +tL2@gxla{>SI_Z{555u0({T0H!WU{d&(qBLn!iKyFVfOkPJpF^$F%o#8a^Ae{GQg_w`%x(p!t8Q`M2^ +>d7<-6=ef}of6~1n|34ePA^-o$M{194nMGU+{Cgk&1|QYm+~D(XM(n@oX?Rgu^>295^nVubzw`OOE(6 +;0|F|e5c#(eP?&-UMI=Fl0&zgUW=Kj>zs=4k-)tvYEah?&f%q?W|846#%{@8kR1L@AYteO_h+C67AeOvkM+7T{E?$y9sgB`o^#chm~TWMy76m^c!ppD! +CHdV1S<$0Bv?#PL@=8mmmrN`JV64%FoK~3(F7KPaDotm-ULAew-ao}ZGzhg+7b8?ocogUAUH&@kD!d;b%IR +<&k(F7SV6FiU?D*v!Ayc&f_n&t5m*R%YvH>Rv?K5%xcG%dCo6LSa^!K4l-Napn{BX>nnZUwW>R<5H|8(!(T?`pAL?kCCi;Rp6VYl +1Wf+{Se|3a~1#R~EC(@%>x-grZlm6eH4KmAl(zI<6|Rkx4>(+h6Eq=D$t1BHIVw0FjggZz3>o~bVMtD +Z4q_G|<=BM(#`lm{-5_g;GUrI#}lm)>X0z`Jtrz<~q&jwVpxgs3yJI-UJz%&zkNo+8UV+WU-yz84WcJ +-u3eFF^PM2h-CJ?$zF_Reu~`UX6E3znc8D?L7P$|M}I64pbjFk2LUpdF1$53Q(Oz>C?N{+xuJIy86IDA|W08@jd#&ycTEFF)8i}^}STwtNE} +42Pwp2u*;`;|Tbl7-+!ve_t`^a#4k)rb)0O9|^`cVfiRv#=ZWh-Sng2*aQN0nK&*?a-dUN`Wo9Zom7iy2{Evnm>6&&{{j35zcZvYPhw?d&dG!1~87S0$iq5&u%kc +GmHz=Obtz(mkqLeBj)S^pzB@7=q%h>wpKiHV8gzWeU0CF9GMEfdc@_ndg`wbxk2Kl$Vnaq845mz?Kfx +@8l!p_{h)pd{|kyKo%HPxH>_0Vn^ynSw +!+JkYX3D*B)9#dVbBdf>(q+fE(J7)puHUhFG}3!x3DPf+@`no-HVKyE`-Pa>zPh!G=1Qc{u_J9ey?Fkyn2n>JWXo;+D(W@d +`1Q>Ti&ygY6{=FFKR*4ZP(^Y;x9ug(n$1%@x4*v!JJ63D$gR`n%7^vP#7y7oi|ZzU6CvHEWJYSZ_~7ON!rt;s;Uu0Th22#UD%YCsX{Hvbk79@mE +v)7b$)j#s84vpEShpOz|xg|1OGeqxefG{<9Q+C&fQZ@sCpcuPFYv6u*MvpP~5Y4e@()rtvJP>bW#rTx +Jtu(-Im(K1+StE~_$vLVmYg$f}oxJa35KlHzxu_(2qZAjOZT_>@*v2F0I6@fT72H +5C6PieFCgKcx7l4Dm_%NT8~mOUlNwMiig);DgluRFM)R&DRMzkm8T0`1eu#M=1UlivN)zzGx;A#En#E +ZxPExAXVGG;vh+Wm53Fx`CUTx%oTFP142$)E#&f7gnW6QkcYoE#5Ys?z7&5L#h*y=XHxtn6n_K7f0g2 +Ir}%p*KIOUm3yOb=;#axi-%csqK`CTV3JWNOCn<&9l)?#0p{ltQKlha4+z2VoPm|)paw&d&S&H8eOIQ +516u%qAkD&M?DgGpiKb_()r1&c-{!?Pm1~&ouZ-!MGlJO^(8UvwyvE5I&|nf#N`_~C@wZOIyx>YA|f(6rCZl7T{;B}8 +PZWtAv%`4=|7e%(a|Y^w<5rhA!f}#Ec$&y@bm +L)Hw-zzKb{{-zfG+GDPRNqqhjJ?K>l5BDoxy|)j0Qle9wc9YErXR&w=v ++$)M*xj~oPN)+cw%vUOk6w@!8z#sC;U^Qds=`(9Hr4_@Zc6LT2KI&e@b8P6ibg7%3wJ8-#mCQqtMRfA +7%0~g)|F_iHnXWE)Q!pyo!x>%`C*G +g!b@nVMhWl-duVaF@J9O4>`m_df}_G>YkR +BmL+MA8AjZb@>0ig&kUp^{HnM*mZxL7fiLc`=Wm&;RQ(?q$QY6a$9(}H$9;jEBD-;oR^tr3+DY`y4a^ +y(XN9J0GxO9)FXATq#Gh)PwyeRS7{1~z6(Yd1EKGG?uog4b86iMHxI>SsdAAa~@v1-*Sv1ZL0v2NWuv +0=jo))iiT^;Omf-+c2;))hW3ds?g_ePa{p3cGjj79V`@0qX`Qzxi5x^UXKn=8M>gbkINB5LyE?*Np<#sVb?i17G5wTo;Azqf +JsQ<1q#HS81fkyPX)Y&e(o#OYU_!f#ENAbr|{Am<_0mWZK@n50%`zd}+KldL!umTRL{>LSdWxx9<_yBT +%&Ka9h_NH#P3joB}j!+Vtjjw{+^-qewm}dSC&P9FFv8TzJ2@lU@8FHAUii|)QDOZKH-))#YX{AOTnLE=p6sM9uF;DziSK+zM^}C_?s +Vp{PAS!ciuX4=FGo-{PD-r)c1Vz!w)~4BJ=BW=gys>ap}n~zWCzhy?ggM-Q3&)!^6XSXf(Cb(!nPw;K +QHcAIcgX^L3p&ckW0gc-8D0xmCP{P3~z^74W~g9Zf)wVdz?&W%F-LXP+^iR6dM5?8lEEm!E(BIlqG@9Z2V4lVtAPPe1*1k>u`oN> +lmmx8LN?KmUC8n98vO$wU!o*tv5j;}7{bcI+6J!HE+m_#HT+PEZ|Y^T?4S45%9@511%7)E&fm +@4fdJf0QBBe~EIqeEG68KJ@-OxmUxV>Ts_=ga7&S=OuMREdL=PA)O(|0O0@r`|r!Q-g-+?nM=}{xekN +>D3AU7_cH+Zef#!tT{(L6DBD2;+5pHK>IupSxNZ4SO6xHxBR-I__kJmRyo1XzrR-iN<;ZuXeD0K#znn +jRgz8GWKZF1F?c0|J1O)UU9exS5mnaXW0r-OF;4A1sSpav~QTAVb^%Vo`;5qmWd?5#*1$cx1DB~x-mo +oYjDJ>sL8Fo<0eg~us-bXa-mhv{Dq4PE=JG?37k}ssJuC8AFXYi-C-UYy42fZwG_$&1U^#Jmc{FRhLj +!GHzG4UTXP#N@nPs(0IL-waqp0AcN{jiil+g(c&*M$Ft3l|{Ei-UuMJCH0Cfd(pvD`){OkR#w&A06O3 +c#blH+(7=(?$Vfz5xSG`zvFY2hCv@mIgoJePc-x)xye1M;-7Iu%7C}kI&rP|Q=cLA_8;<3{gDLJEfZb +N!B><8Q +*u4ogta(6-b^2V}h=`2zppkA4C1(9Rg2q?1%fLBp3)#u5!NM8l>ZueZPG17L;Kg&ww0cF_2J*4LkE}qQy-6h{)+NI{Y8JEmj&tr-gO#)Kec< +Z?2$D2{H$>K)Qkc038G;Q(SRqF+RyPs!x+M2)Hf;(dVK~BdVNNl)N?QKeV3HCf(GKh`3cg +MfdzOEyYfGRHq0Z@a=(Ko7D9!PNwdu^Q}?O(7%wLc)Fw9Y2eU15X;!3k5)BU!4fBbH +IYa}Vx;n<)N9u2XfIrCs%Rk9=5omeffd}NqjT@PsYoSA@1#y+bugaZ8{bU)@@D|a)^_gfuo21w0wbUl +9&Wn(bkq3+oIVfZxJ}3tcqSrQ>R}T)I>(Pn1 +6@-&q_bcQ``iTUvd7#ZaF&Xf&+SX!r-o(UN;3Wl4rg10L2fsDBVKDy7%?I)J3 +ISb;EMW)Ixu(cT&BS&Uw>34Fj1%KjUYmOj^_gg>r=4l&X$?LtTehs6+D_S}OBb1vk|Ljc@=1w)IX^#N+H5wtc=2Mc +*I$15CAT}!h3caPc%yGXTLoPR`Y6T&cu=1YK4q8HztMPO{d8A-)^&`PT76zdb>WZjfBWsXO{D9L((&) +sty|yp^z`hsw6scUN4XrHc;X3p=bd-T#Kc5dT3RX}e)wVe;DZk`J{aEvU(kTQ0R4nc2gWd<0qrT|5Pc +!?*zJs#A3hEI&&zi=Os~-~rmH#z>NDD;M`y+Z{XZOkg@=ci#K*_agZz-JFQ;*TpUs;$Z=^ab$xqIlIa +5-dka)l2jyqUKc;}sWSRQ~oa77vEa;29C=!5KGoCCcMWr6yDI?eT&%E4HlpRH4$4{UuLdf=*S5uVV{& +<9tpT*>nw$Xlz+vaqla_jt;V9XrZfZ@pELZq2v=57Z4KfCe4v(gJ`?>1m^FeSghF`F62|`e#+gK>z&w +oG7{VpOfY1?`@IifBEGImG?EzKde}>LKYPjon5|sxtu?L{x5gkb(hS_%3{9LxRPZFG@xF9PT&SQ>e7N +a2=v2{d-Tz$E0~YKI1+Bqkx+J^rJje#|Me^I=+DmoATrh6wYj;uU*CWK{g;7fa&j^Q>aImvX-TIC`+c@^X_*wkP`8U)p$Un)_x_R^FeS~@%7Z)dI&z{ZY02 ++Y*ph1J=m@#A69c2I-bN~(dxEEtF=+}^Y;0Y5l1lo+U1)U1vsV(_OKtMp>*Is*VDaH_g1%JpU0J4PnX +1D<_%sJcbcCNosQBl%jvB>V-yK_0fA93o^QJ-w-bwf8HM`#ZqS7?t>cKZAV;e_S3YhAx!{E6>S0CfO$0_ma+CfqM +mIz_EowX#sTKe2oFZbhhTK0eoqKk_x|KRRCl7%u`JDqBgq1->fg`atC^N#0ln&_{vCdK`T|5$z~=iuV +7NS6-1XzW5^dfiJ!E62}9tiBAf(Z4;=EoK3jzB)0`ZUH7=w^&8gTp#Px#r~0@A@2GDufeXqTb(Z9mb& +07{r*hj$b)EY+v^9`#T@FzXP%lv?kQ0;v+GL|X1Uu428KFGTpA)^<5r=lu>l$%);ct{xqYN8m8@Pck$ +Pntnf&~jCwOQOx8z11N1LB~aL0g0e@`EuE0Jwv$#~*(jbBt@<-QE8z?k@aw{h#<<1RC^qu|7J$Z?r+w +ck?&_GKIdjo;iKSA7!KK5LnlN+(VYYbJU>~_?Z9C^Su`SM)^k@QMZq+OAAcQv5y}=UOw{3BP`!WzUzQ ++kR`}E-q8mDcYS??^x8H51>6~bL*AJ-8eA>gqJ$qeJpjgGJ$>%{Ri(@mjghyV +D8zohudB70QD8}4*XFMNFF!*jkq)Z#`#ZbCtUMCpaFd+^cS>$kOTBb=sUqm)~)Nk>qqtkAEv`I~XagX2hU_%{Rcz;_RS%TYb +DkUEzgGiFo^A9*=q#*E`aL{zCJ+5TqJLo?kpQ~XV)Tb=X``4(zs+24VGfo4k8bkj@^&GgYslV-Np%m~ +da&`hbCZZK=_+cAs3@ZkF%9}T?3Rdhwn_Yv$TIA3SHhWtKbqQne1 +?tB&Y*K&FAQ>uw7+_+S!nfbN9 +x1Nuj_>*$NnRzsJCo&rDE@r+Yy)(f%5gY{0#X=8l{bMjbY`=k6Z2B0x$HjNiD_3=H%xyToKG)Oya;kq6G-`0Yd8ZyvxC^j|-mIwqfXB=J1v5^WA};tx`$e5bCPCVr!?`| +95h808V`Ls(}FC-dXlGT9z|3XNY=NzY9(eushbhYk!}(Eq%(aw^Y(VLljh@_PP&yT4P{J+a=#@_1BTY +r`53$>OMVx^y4(%&z>`9PNs3aTi0h{LbrZ#ZWPZYVm=r1h{pV}_KNl6B&iko04v@#BI`SByt(gXE@>pk`j`OE+QtHZ +;E0c_PAaBSyOgva`!<;JC7ceKh<&oS!Tr>RcH!1T7mjQ>>wKmiVtalpohh8yj)~xJ;f`Uxr+ADCtya{ +-L2lMM#YkEOj@54G1=0hQmSYyK4hC^FlnmtK{hv#Q74lIv{YSzB%=3jR$xb7M-%D;R;1h02ttq60Pm> +bvS5%c0$U%}c0uW@Pl&q*81c?~hheZ{0P!}=Yhkg#afeUy5^TYKSyl#l~D$J`su +gz^guB|ViPGEfmYlVf@XcrIg7~5#9-5TW|b?d(S?#rh3KT}^Hz_Vs`zzeiT@WX@k+ZXSvsT1|dqe0f{ZU3QT&z?Oyd%=PQQ^AL&OP6 +xHhjtfzAKHJ&0{8)0MBc_VrysxhNdEB6CsZfu$YWNdYpv{R`J+$4JO}11Bhmcx +QQntUCFTJoD`*HS%byk;heY$0GZB`+rK`UC%!$Dd|K+L!+!-7DX4MDn))x;#EHYp{Ifp&(0>LF92jh|SZ0BaxpU{{Oq@7zZ%|N>y!YOFc^-Jlk|jKM +p^tsQbC}@2{%s@pQMXUVgET4Jha_89)&IJ6>o%I|J&yy?*JE7iyMTDW1-wAJjW!c_?%1(oskpW^3a@s(Fz!I7BPUqjxUKk`KT<8U}Kksjvc(dVPh!P>%v2@`mY8TA5T@qh>V +953``tf@e6CmL4&iM(C;*PX*M&Pkv@%FD~+wgKO&>hp>yFYp?4qfI_?8jGf8?Y0zmO%+tLxyXd-}R0aDa?qjRW&fTeoi2zZ3kcdAsPpmN`(gGid +uz&YL!Eg8cnJyeJXs{K;Q^u-C-@F9PbLWR2Lul$L9yXFS*r2 +epdLw+QWQI9|V_-ju*@x*y*qkeqy$tVBu)KgE@YIinn+<1o4$a(I$=h&UXc&=Z+K6dTewcB5K;RP;Fy +$?elp=XZ@YhI8O3+4)qXYW60}^GzLlhT>TCzg7oWE-*8e}_4o)2nT8Ih>oa0=_JE|~g*ItJeQ^A+9jF9ciAHlZE?SABoSp|>~4wN{MDfji_M`93>0mUW!7GzMFBPE7-2ag4n%_QSXjYNVd5in*0|4>fogY<7u-+Ca1^)2Xo(Dk5eLf6H3W8>-)P6K0(p@av@Z|vByyv~g_41 +74gd!5`;IDp5K(A}Z8W88qT(;Vv%&Li<_DW4&}tXj2-^+9|if^{{NCFG^9E`F$d6YKqRCb@KO5W5-tq +ke(gEv(7m`vJW##q;vqf%3cKN0^@&LltLm{;r#NAbV)*A*U$Ikt0VkPti`JEWg_MgnVWG5dQvi&CEOG +yKg>?_?z|k+qZ9LT}}TU2kka=E|dq_QzI_0qc1ngKf>$fuYZ$-w9ro?fAmKfCm?OK%hdN57~`Xz2Hr> +qyuex!XhNTj`6R@}I2(QU?%lg<#lkSPH=Ll$Z#ZwwUEo +|?oE(7D);t7G(Zw6iTJsb^L2B!Slh(YnkY+w-&07m;=2O;uM0jd`zIE!vtXbxnQ?s)32XwIKW%tjYG{ +rhKHNV@`j7fPp`8nyfZj*AR_D{{9+I@P@4(6$;*%|59d|Q$=FFzwEdq9Vt-Ge#|Y}(jt4!7mm^ZC}5Y +fLRzjYl!_t&{9|8May4JGtapr`ahNYg$5H#`KIV>tt)b%hTu`!iI^`!#!8@y4b +5x@VMaB!5f3$2>v$sOt1{@(>tzrQtzDJvwQ!e_e;I^^!~E9=;PI=OP}yQWBW|+Q{3mDeO~JGMxRgmob +GeEk7wUzecSZCwQu*nmcGOLCiZ>0@0)!q`X=_fyI(=SHT}x^wGQbLGCZUpWMRnCkPRU_LXLzq4(%Q~F +w_ycCA2!!)W1Xj;Qpif&+PwL|J%dj!ls1H4ErVQ>F^iAw}fvC|2llsfbj!z2HZEGV8HSLs|Ktc@b-W` +1HKq=dVt$Nzkw|W1`NDyVAw$0z_|lA419jzI|C04yfE;whz${2BA|+d3*40(5)>aaCTLdA4!uV7O74~ +3t2p?^ey93f>SqtF2rcP9BK*GaxdSc_@E&;Mz>bvGLjwh`RQM7v!-HN6`YcEZstURk)Trn6J=^x|*0W +Df3vq67&xd=S{{PxL->0gMGmc-CT1sq;4#`kQs%0>-hU(d$XV31QT{JYogeJzKMXLdlAeTxEv}&%FMo +g$7mQ1O^N|YvGjNt{gSTQh$60lCfq-d!Uf1pIPj3FuNjX?$par#`+>5Kjco#B}~b6?$i_WOCB=euXmy +|ar};7pu@*WyC_9DW}E8kgZ$@orp$YjHhp!mYRie}ubnFa8XFfp6m&GL_6CiDVve$b;lZB%Q1z>&c6x +e2n}da+I7P=Yjn+ItwN#L!YL*=zgGoicVzH*a;qRXSv3$agVuI+|S+nge$Ve&w=YU@q1A#4v4SBRPVZ +X%bP4y6}I +0-etcRL+2ax_YO(W}lkPm}pNZdya&cI^E80ZRd&b-1{lPovHG1EdT0Saw$^*dkBv9>@*JOfPpiU_c32_zHHTGW +khp>=pOeg(gQkKm&~?h+8Y2kK@HnNJ8=MDoZE@+N5{lju^qn_i;_fyhsIA%B6t%iF|fVw=}1Kan?KJb +U#iodFq*vmN%ljcoV;ufS3#)1kJnO_t_AO=A5tKnYr+4cfJ)jf)`C|@H;_rn1&$5MfligxV>~=e3M{Q& +g(+k&)1Gg)4DxEs#@6Ls>o*j0g{?o5nC=YEwb?7i^LnCMso{H1(GMo*Z_uzf_Z}=?kf!b^Y?R(i_)&& +)Ii`~H=;cI!a_oTPSJL+Bb2E0jfF7(x~EK--$Rh6T+=u&+|$N1V$_kZd?3R()o2~sh>Fm*F(;}*`@zePp!ZNaI*EEv9LAuqlbj(vWCA$(Kj|U%1HJ@Yxl3LF1u<%-TBOp| +8t~ffs!IJ)wW^bDzZ6`n!T!252H<#ZE$l{V1RbQTM;={z2Kbb- +4}1jQ!tx;Q5WM3k54J?FjQHF;g$1UX-NG8eA*ylj!3a*9IWY-?1ds)i0csn6(3`VRkoKigmHf9WTf`78O5%z^%@rg^N`eI4B4D|e=#p +7ILhM)^y*Rql|dWngTh@6aWAK2mt$eR#RFt +TGU4=0RRAs0stQX003}la4%nWWo~3|axY|Qb98KJVlQ_#G%aCrZ7yYaW$e8Pc$3$aKYZU;k}b&#USu2 +0BAJ9#79zb!IS-wlbtm%+g@8ScWblZaN4%WeV*WLOWx-?F?y5Cz__kK>zcH&?P +{Ub~@7mL(ifaA!>pTyyedsf5SA5p9YQ^V1_t1LJ%1?RLZup$1{&SwY@2l~A_Mrzpb +z4S;^JcS*RiC--_k7H@b6s;RQ|vPOdBm-_{XM$4lQ#ify!2sQAJ)Z@w1fnwb+I%B*L+>n-hfth_`X0(c(G +d(&+Fr-zis`eHm=9@3js|5#I{>UrtzTI+txm?Vm&amdYdX!Y{&IW+K6-p{@tdMiESjjfr`^{ov0_L>$ +&Y=l~A_vVKd#$qtY3-tzEZv749WYfE!Vb>+NHu`_zLEA)&+(a3$<)%cyjf<749g-+$=C=d*MX%F>O9T +VxGX2vOT(S^nzKqd)E(vf#b>Jsa2A(^b0J_?`ct*7?O$(I&)QFSDHJ7IED2t-y&@bN0S@)*?E4)3i>! +3$ZNzV)b3D`RC5dpVT_9tkOFpYb>2_ud{XzJ!kss%5W`9EHC-5G9WHkY@(LeRaQ}3@?FzBfI+;(Ir{D!+Hjl!f0b5_)Jjk5zncWxDpg2VDC1K +0U?qy;btMMng?FB3H|}@y-ZRhhFzz4b{Y2iU<39br+IIc{ww9vv?SsUr1^4Khwa=j3 +qAYQI6@DI19m@A)JcII!?0AQtN8p|CsCup~U$F9;d%<}15U*!V{1VSa=XVVfzZUCA{PqC9MM6V;#!_B +y=JouJ!J_l6gRAgd=MhVJeE?VD>lxr{0&orZj}ynlHE?;Hcqjg2d5L4xMn@8%sN^AGr) +OK0Byw2vm5Vr^POj2k%4#kc^<~Qhxu;eydn?YdE`6LLZts^aTYzS9UppB1K;c5e+&A775%}6ev#5yB# +cO}MMN%w*DqPLuFDp^>n)3=>kNL@XmrPEOKw-M#f9g*uCtc>t{U{U`_b1vioW)7^tDf-ul*(Znm}J;8 +P~6yc_tX(@xY0T=JVU@K7cy!LEU!)hJ}Eo68*QL6R?HeiGOsfv2kU(D0v;_8&)xYkJ(qAnPqHW-cx?z +r{JgE>y4#aN5ki)i(95m*F?#;4H5eG8;@;wdn~W)UVm!%+cVKqR|4rJHxVocjgh-Dq5K*6;^=_N)&u9s38e6cw& +Azc>gBw<6e^wW3Yb(?@0Hv7R#~3Ht26hQQwwh-xih@=3R(3-gIqYYhqgtMWQ8;Cl^)#rXst^-{6;Ci+ +1eWR_$0~+LeRR5~xSl9wE93d*#)rY)=RBCc-aaN!=pbJUyb)h)A@S^*o5Qzd_rfj87!e4D?yBIZe@rv +@uls-emjIA>(#G%y!LI?Mk4{?uSnezmL&ng`|teq|5D!F6pBB+mIWCll6Yvs&GC`BeeG!m$9HS8Xcxl +Kj55@nUS96bl6iV9 +YiHxMfFbx2Q{646@2L|Ib_>UwSL>G$VQg=gVUsyH!*o9OPNu&?IoliA&`l{QHx%KP;7^$_$C;J}z$Z1 +Pf7c3?h(^FOq;oF--(H-og$^CPg6XmRX@4)%F+jg?7g{D1`_4D=HXQvp)%TZ6co;9!su=4#m;N6q1-K +$O6eXow61;6Ex)!$7KwVL?!-v(i#IOoIDK9(Y=AvnnC%UrIi*HH#OOg*pLwmYWb +Rfeh5*e&-_*wIec&^Ztu2Rp5-xBgAAY5fE_Th?A{xX}yjwb!+_Luss+SAV!oG5A+P7_&v|H=ic?Dk`I!X3AdyazkHHy`Y9YnB0Ril|+j(evwK{0z-8a5} +ZUP~05&1k1L3!5H-7$8@Iw|Elz!U)zzc4)_?6?r!6GFMh-6>`3PrBOUnPi{GgCf9`r9>%90i@jcU7k< +Rx2K0M>W5$pSZYQ5vuXO=T={&!o?e-%IP(C$CAoc}C6W#7@IJ$XEl_aTRCmxJevpwklHudt~1D^#8ft +6}@$8e+ft?CZFPPELfq7qS8Gq%8MblHWtC^pSmUygoKwn-lwU@aNmQAN$qLAFqG#FBX64%(srbVZQfj +VxK6=8Me1B*8I5ha+-(?J$gX;ftRHXXz99Wv36aC?FY#k`7LZW8~E)t*nnr70w?r9;6yKM!Ek|Svx`8 +ETR<8<8z>xl^kB#3G$V4c=1@nx{2wZxEmuTdE3kC_)=i&H)%BKXZ(fAGc^P$Hv1p-R4cA#9>pY9P&!F +C3*qyJT&TouS=apO|qI|ml)A4TFb>JP^HTzw)Yh?L$O>7gI9y%(-K5v;3VY`G@{%R@k-7%+9+SYbm?0 +*ivCDUpZX4(qa%}CdW?;UgWNS_e<<@;qugf=t3p|4jvpqCxUH~pX`WP7B!+KP8KX<{Pna>@dxONNtrA +xo$+nq+wUINYV+-IfIJwvKrt;bmHFkC`?R-g(3DZW{~Ue&n03{pa8vdh~_PGZVDV*9vs-j77rye_@M6 +EuQ1B`%7Qj;t8Dv%%}1GKG3t5-*G(xSnP|8hzIqt4x|qqls3#yI@EZ_=yIQx$Bb4!Z_TkTE7wHB+&%4uy_Q_xmyqr(-f#8)5=zdaLeO!g`2P +)>|U$bNL8My;-M@^s3Iht~tYX=5>7?b;`CKw1&vnO}e;1x%oKW`)^Tjtj;mQOiNgT5|%s@mfV&rkOzH +#e|ItKV%=WNp0=*idyMT}JVtwSQTJukw`Zj-1iW52y`#NwCF;6_cg-hkp-MB|Uhq$y5T#deKMn1z2QF +DY+gXJ6vA#KDl>hcb(Z;fe+k)~Jvn_glORh~EPeoa*gLRQlwxS-`k|pa>#PKFQe}H31wDYgv8|%9jG( +8P@JM`$`1X(&hKOM9Ms-qJ`gtTz6uiHdhKKN=k=qme3h7qA${mJIpAxj`otqUbrWBtCzDoRR_k2IFN* +=q~+ZV@5!KEf6N?rSWfl=l~rcE$9R&>7Hc0q|nzqWhPquP@VxoB=F}btcva{a?yF%*%Jh +$XjS@W!iAEf2ZsB%Y-=86r+{8j@>9MmHX2? +oAKFtY)0Zll>P0&DMA49|wHAVIKKAd6H3v<(rRs!uEcH^?G~68~O=bT3jfiH-CCVq}ON2LSnQ=Vk&&l +-nD3~?k$Hb9*?=T{-fex0OK=qludZSbTrK2&Ty%Og=-bD&PWR(FeirQZ-Naz +puS461ut{{K!A!q24$v@l;tq-ijuUC%6`vh42>qesBNY+mtDCOiV7^xL)m9<8*G +;ERb#*N;<%XuEg*I!HiLR~=$PM|fIgK*6fjFWbvjYC7SBlVX-DvCq53{Zbs0(tpguJZAJ4`;_lP+<41 +@iJaCv@*^#>OLNp3@e)PeVQ<8ZGsstc#E@eF@*Lrphu){mA1-nmVN6w@d1M31mUzI+RHr^PTG^zcpt_ +8{q(CxP%uv=~t*bh`jEtqM;eMVO{*jXT_tLQa}G3>0)KJ;kVzZ^t`rWLgw-;OepVB2BBcEp4Y_V#%fY#v!xk<_KtiG7mzc06XBWPQk2dj;PtD0>ImUF +;CY?M~>sO~o6Xu;Z$-y=ABOW`+K4C?+$qN7xj8ykDJT4K?Y`fjpGohkjTIygTxG7L=EY2H>~0yjV1#9 +kp)FIk3>w#d*-%x(yD#IpBqdXg6jpcd&czJq}=Zh-O%}IG~Ps-B +^okY8o@@gQ~FAlnf`vu#Pi!uhGqg9{D3%M=oeTGQ`Ve&k6{@>+^HerRn2MwEHGo9W7nTa|j9+y=__)L +6tpilH84{5#!^r--k?cOXxuj-~8K|hPpmGd5~@4_|J=Nx+Agp&Ogk!MmIExjo7^kJ7Q_cE>>dQW3NZA +rA#duz|eKJuWYr}0+Ul;;)g~V6hy9h9{s`XB)C!P3e?gm6yl-UvljArT>4&R%ced$^W}CceqJ;MJfrX;%0l +dq_yFAF8j>>z(ZX0Bilkyw(AJTMGW=eags3={^HL)Ts^!Jlg-#YCMR9B+U@x4}&CjE8~bmbO&uQKymO +xw2`bu@BRqh=l~zt|FS~LYuYvyw25%7)5~A|OhSIt-Tl{Qe)2DL +OQf+-v^{_{xp#`1I{^!9V&56l4h3(6jw18`_=_}OYtqFII>XQ`U5UK3afz=V3i>O#K8`Uk?PtuieSou +Lp-~1}mR1+4G((Rb8ZpK|8dhkuH3HS;no6_6O#7ii(n`_q=cwc9DE<49bUlJRbvkTeOHNn1CiXp9R2E +8uU0XG@{D@x@`|$}THA1NeTS593$zD(l=hMZF7}?-CEk33ahBCnvFEK+8f~e*QN=_v0|k078{Lr&C%j +q0ezlq3>fNoNL9JGWue&857b>uW`5fAN`2}T5MIbXcT66yn8y!a;V8uVS-X2qKQMVYtR66#M=c|8f= +Ps48+PP1ZUntu2#1JE~puuGlIju-opr%H3YXxGwSbo|KJUJlrTn)sMq+qyFdJGS5A?INxnHp=P4!~Dt +d$vMxQy7lWbg_vtzN#j`GRX7OBtLiWB3$ab!UQ0;9;vSBXlHZ%(H#`m>!|TMW(H&c@ffKNWBjDLC^yv +u4;rZ;)n!XR27fHmoEbnoko~_+S%i6S%iLFj()vDi +}!1d_g_SN%S1yb`)Y)JaHKCc0zcG^6;l))H0v-O66GI#)K>6mu|-UT?g|qY>3bF8K3g;t;l6&l5w6n3 +M6YIF;&zAz^r2eln~CL`ea&ujEF@8u(N2KP5@}5_BKytzThXUht^4e0kF9l*Futx0+XL{662|3Uvags +Gf*t)*Y4fa5ZV~!Der_#qfPTae(2n(AG6uV$UyuA%1$43+PYOW)HtBf-H^HBf)a{hPxF%rY7(s=zJ^m{_ky~^hhRbvNs;vo~8A?KfMAvE?wkgKu=`tth+bcS$FTuyC&Y-rB9q+e3 +$)Rd-~Lxju}UG!udW&-)vEK&#!5__;Gb;T6Vv_VBR*{!8@~&_xAVg`(N>}zBy6O){Mn1l;K{~&Gf~1=Y8BbxizDz#Zw`UFJhfar=Iu#?w`xh?@dP= +wxJCUz)wAwL-@bC?sL_soAuv|vfhhdKia}}ECM`>(H6p$ra7PVYEIcEr`e_>CM;`!^Lq1LpV`jk;K`Z +SqP4f8j{vULKt@oGk$cTHEk>KFvjzj2S6K7d~H)qI3Q7=>_;iVpUnF+kChE86tWvr>w+IOn);vbHT(Yz@>ZhXaJ>FWEm(K)~vvuQ?T5d +Cfda@wg)I@X~pUFv8wwwG#?TKeZylvQPm~zk|UXhw5vVf_p?4DGJT=9rPq?yMV<8F%h4Tm +w!!(-fuDt4VTa#RoGB(!HXNjE_BJ6IA(Ynz839c);k@~7omSVvaZ~ +&S*VBa&mfJ(!a0cCuGgm4JBOTi{fV^yTTZ+hLyEg+5|3}3FNt?ef$B29v-*bV}c0G +N~=2TI8&XVr?2jHXM;`AM{DLmXnJUG<-A?Rh|HbtAfwockjf$A>Uxc`Vee~;hys%N%zAYW4@aEZ2eIM +n@g-1lc2;f`!2Gxvbs`qS~8p`JO$vBoB{n_#n@9g5cW1CAyweN8WTnC&>iyc}afSwFEGn<)R&(1sw|M +tbxu7U82Pi!`BppT4lTC3-2kgY9Ek!~x4`*BxulSscEzgV9=L`yB3g`z9kYGTv9s4O9Lm>ZMX>KOL(uuW#BgS>3}jfmS`W7$J{;A;h}qPyQJCPE%c9BJgOiAvAx0q*S{*q;2ZIi#MkTjcl +15$H+>XwXl8c8U?v4i>ivml4TFos3V-l(x4K4r-ox4!keX_!HfxcIM +D74~S5*Ka|e*yW{U+({@XnM7ty87WM(o6TBx|xRUV(SxaBFJC?6)lCt}g^;AB}I#MY6>FgCs&z|7;uW +ZX6ZP*r3cBPfN+}DHjvG;NuDMdWXKK`^-?3d|!t)W;N_>|k3j$^kRuba%Vsw~(ixW59u9ZOGJIhKcW; +?hp$To%V{L?Fk%NrW%s9p`rKG5AK-BW<2MIfs|qbtcO!yII*Jpoh;R>7vF^N9pfGKY%t3d@E_w?1|fB +(x#jw!fvaW&GxNIUmmNAz9k*kSqb)*H#M<=4ZeaWub;`Ot%LVbiauh| +wVfbR!?WLf9+Q%CBm3wE*2BS?#SmK|L^Av_npc?|yX=l_&%N}hNQ`fD!ye#n8gBf9j#!yK;yoH_LUVt +tD=zoLA!F7(&7zDJGkvw!>yc#KSr=XD_;?O5_L)1H@kT)y9%_H?4%%K9(DAMH=b|1DMb_N6F;`9F>Pe +&`pLR|Wmrlp~}L(j*_ce4oJg-=Un(T#x>fKi>$`|8(05*D^Mg`A+wEkCjm-KVh`4*6D+YeY3RGP}xrbffc+QpQba`3&%U+A^UlkG7auzTKhMz-B4!NWei4a25LwXJg(On(5S(Oa;o@i4(|M;u(#ZV^4TvrPr~wFV)<4zRzUtL +!uJEZ{lbH^g<`arlv2Yvh2iHSyVQ*n49nonE-BXp#}x!ZPtLf;yhWyG3|cZgC7$q?oevpe +A_6p0G|2B_-4CI>I_uy+^}$Yz!e&Z`?a+_3vnsqT=WtD<&eBzUStjK2Q +#Ns&<6D*Bi7E{|S1P3c40~PLZdEqCD;5n0ImTrZA=>$N(BSCNz6Fb6U%++?HE9I{{B{ubTOaDELi+{m +bJN$}9!)hu^}uCh&E3HBLg2d+c&~trQgo=AB61x=mTVu&eFJz{4*GgU`Y}6Tg3Jy1(Er*sZQu`Ep4>_ +J;Rl}h1MGs}3}fT>@SX6j$!pnpK+7CpY<*p%=_G6p7xVOQ3GSplz;U^L=E>_>u-MEaT!Ec39@C6J|Ne +C)Kew6JrEV4Ho;)5Oi{-d%(S5a$H`@#2a)sl=tS@;?`Qr6xH|M@Ou18Bamk8RHejy>>MaZMw3q|OYZq +c?E`L=+cAH#RMB~U$xZwDp_^w&Hebu;C_mpy76{W-i}hqSwq=KfO*8gMZn-3+FYgahQnP|#n>Mz;%OEYRf;}XJput>(Z +Nqsi0NWtHOe9w7k0}jq(9?Il8_&l@;*WrCrzM2!f#jW(;X!$(09=;_3w%$pj_3*H4nw?(5@;P>tfjZv +eS`ibshZ(D7!kaZ&)&Iv4_*V5g?i4kB(e`SN0nNod@L5}~SsSF^tufA~BI0PQ-{{eDog9BS3m)iyk7% +U+jou_0dzYyBpTk9Jo~rLAqfsDj;@EMV4kP(A*5=Eg_a*RZ4ch*@N%6KnlhF1=UTt@Zh9lRbwQSo-;O +Y$8#(7gdFA;G%ohVf8K0l7#LH)Pf4&OO7F8>{WqkQu~O8lMM=3c9%C|iPKLg>@Av>|2s$Bb6xE2KgWe +4w=$GSuF@Jw*fxGGWbs{_bb<%PC8(xwCIjw#Y0VyC(8Hq +%X|A84RpB#A96KhxkGavE7x2DQg0z|`yt3t%0lQ9Kl(#`j*4;eYOW=I*h(Y%M9o<9cyXMt%`tk!`T*> +Y(&hxZkS6qBR#%JAqM_x%;>IaCi-zpM4s%W@ksf(Toat!O}F=4>C-{9D~Fd8{u=)?65(2Qdv@HT8}f30724AW9dvZWeB}!x>>rl($eXZv4(A+a?61kE&~=usL5oew^*B)3_Qvscq~lXfKR5iL$;`qO70sag2Yi5uMOSp}vpm5yp7*N3 +EUFH6k26oH7_)m^v7>XpPZUJlE)D(fg35MvuG&JNx2UTW9n+Yxw`_YX1Le*fbx}#5};Uf1Op7a?Zz%K +Fqj{=d{83?T6+Lti=5k!1_^(=yt(BUkAOycp2)ISbD=EY7eaTE;wCN8Ts`z_+V#^GS*#`Z@H=V&xaXMw*Lv9lEQ{u2=yh-W?XVt8TDKw2Z+F9%q}@looez8mwP9 +IZJxPv7Rz~IyHl5`BeUqMbVJrKyUKwf9?E`ox#~de^{`P6u5*%~dpkR{o!U5#J3vGNlMUS-VdStE{Gf +_4z7NhVk)|ZIE5pcdyFb{_=&Br+S9QBs#`yF*! +2s-Qp$C2!_DkFCI9gGP`hmYikkCJ|~h-7G@y5ExCWnZ9d70<7XO&q(h+r%>a0uio++@>GuvmYv4^hJG +gFLZ_XpvX>}Dsr1%7Y$EQZ{}sB!H>q&&S@}$>hZ%KFD7~a$G2Hy9J8p7L|05ANr+$DHVSpB&7YNC1g`%IsIru`Q?>%TZ*7v})HfXfN^m*U!P;yaj^DdC@G;yXPF-+h3)^ +S^*^!kFP04SOQo!txGq1FsTpT-!~&$KWR3mzsDFj)8Z|t&SVueXQ|M+Mis@K>acwaC2=P`}k+kml(^a +)*j_|t+R_Z&Lwedoi@t46p`JeyVeX`S97~JjN|=N>bpzSu)atBGxa6;w@oWWSu^yY_hzHh|97HuvWhv +{X8+3y#=mpE=DfXkLda2Ubk=u?&RouS!aiOxU4-cG+N;3dDI(&y+lbUVMPzXiFmvI@7)1K@p^Jzw8sn%0Qo4X&j9__($EnV7w_9;CJ`c3;U%?iB?yU +}l^{rzOf@u}c}JZ)LIma&?1!v3i}3z)VZeC$KIy~krhnKf`KspVQU8MSr!^UW{2joVK1HUlssC61#?ENL{NXgcnj9BRw*xl22=O^BE4Z@*ZTzf_ +u|pi2>?vD~e%6u4Hl_Q%3tQ(6v~87@tSnr2zu>oaCpLpE(h_T_#dN=T*&$^ +P})&JYz9qEtz=T$~HU+>S9ank!=%ZuBP?RmZ+-P+)i{mpVJ$~}Z91-%@ujW=`E<5LKGVE#o+qgExp +60{%CH3o{UxTuniXb!Jj@C9!6%Bq|>(fM*chPqicpY}R?}H}CS#!Ilm@%D4BGHl+sC(rz=)ccohdz)h ++TN$?$Z2{arP%*OYH^dUl{HmsWhJI<^NJ8{@8uX_r)Xeo&WaN%zWI~zkBaGg=Kx0*=P`%P*zVJuElY| +-XibiYJlMSa`3L8T@K0f{`sWzobC&catMTm6rp>9JBf^YZEM#2g`$Tw>Sw8#fiZkfXkkjLp>qof`t|x +}R@FQ>Wv%$i8T;DL)aazz;OU!;W+Z}u}T0&Yn(AVlyv@+h;UyPQLE~E)@%kr#~KDcSm`Dkr2UH4pw*4 +BZ3iQ^WX&xKz&@$&0^?}mSB6ZmI>$v>OM;Ge)4{Ii_=3EUY=?CGx5xCMVx15m~p^;!#ra&)_+Mf^8P4!#xa^_v}uGmSrO;(Uju(maq*pWCdjmhj$bguT$y<(*o?8!4ieW3}ZoU +>9)gdbTEgu<~VE!)}yWzDH|doRuS;Yo2?`+<4{~q?a~yx`;3y{rxC&5w3SI=7Ij`og&QnV!|8NPnJmO+a#oH|ZS2Z^*za3ZUR^yu&eCtGctc$U3RV%b*QvZN2>{d}*ZxtW&&N#BO!y@J +tYpS1h02a=Tls{{>;kYJ!(emGDvR{|?YYmJuI)ZesNS`K9eUjsgW=!{RTb~`-)~zPJ>Pdq;<1r4gxEU +>Lvqwvab8vuv6Y1BHK)*6g(Qic({elVfyObNJ-#_Lm`rV&IKYOaE-Te;q>m8;a`csU4jyV19GU<0`0{ +z}+(r^AS{fx#q{ZP)h^uxDA`en!IH-oTWZm+J2$$yiERiL4L#?etU{Ev4@!xgOJ5Hn{qh>Iv1JuT~^UH&AdN__w-w+FC5wwsV6El +xlUe|ai2xT3%ly^-OIUp#_DL3=J=70>*zRFCFeoFAD3{io9jj#c=qFa{q)r0K~qosmDRMP(%^6C6=hX +vqVz$OE91T4YjV!j?GR#i*NRR`k@a=h6m1_AVxHfo)-?oe8rPMS*sXFscG*ni{VZ^E4*p9A{g) +N=U!1G=NdH#BeVd8%Okt)09+@7x&Ur87VwWaL`vHqR1+do_FF3G5lWQXn*udWt0@d_I>}i4O{twc=EM +BmNx@;cjS<-y|Y9riko3<7GElSk@9%Kwdjrq;M=M9c|O|ed|zvWuSfi5SmYcD= +St{54(Cr%`Mt;=qwVj2OF#6ZR~wu!X@@*V(Z=LDCnL|uc=VBTaG+JRk-TPJ`5gQ4`Q84mj*)(XdA)Lj +vJ&eTW7IFk$TLx>Ep=UE)Ne_6mt*E<%wufaoBilxZ^(6BU4GbsvA*Pghraa5gucW$%vfJaP3TK)@6ea +h|BQX9#bT~KH2ableQ6E*l128Vuv7J=M!(sY{7HRjO+sJtC-x^Om=xJJ +#dM*o1gr%J*?^oNnul`ca;0^ncs+V~__4deq^oxP$j`-OB;o-it(d6ZgNxaPGcl*0KM +*0cn^~w#(N~}M*FKsmYLUNwOzLZ?QCE;C;7<-<1*k^Ez^cglZFCVbTw<5d-sIg%i9_IIl< +kyWxz-jc`q}lu6)0u7zFwzPJootihj4sgSyjI*WSkD0LY3s>lu&u6`f;yMWLarL+&+-&B1LGN9T$Rg? +D7dZeL0TD8HJtvznE)~(b+brChE?`6I@(&yeE8!z|YrpC)18*Izy>(wK@*H#&UY`NfNtRUJu9!?)_UV +d!_$~&JchEVr@YeJb;l(~mvQFylkWv*K32-(bU{T6%IP1dTG=}7aL=|(5#6)&ZU@TbtGPrpyfw#Xl+p +}(@9LKd-~`thD~*egK4x?7CM>)@?nTuoKDk}(8ko~;RKyODM^u1jEJH^GmKrDNRI>RY%!RiVT`zx!vH +_WskbySTPMSN1+|HovJ%8_v5XGf=$-zLukLN=__qzs1?LhO*w|4X@@r<~MnQYtIv7f~5T}xJUKKxT1I +D5Af_c%Hen752Smy{PC;7|Bvv8GtM8Mc?bUZOY%nySMR_dv;MpI<4dCP&%z%`;{=H^_D6%<`@@Jxxh2 +<#bMLm1bXe8Ad?V@5oPh5b9dzLM0pg)j3;kiDX!Gc%?9;U}uJ1^s_bkwRtn&TMrxpY?QKn>OoJZuoO$ +mH5YJKn+eora#Y=A5s=BcE*66N-KwCk_Ye%2@b4enEgdN09Gpv}Ds?TUU-lo19GVSvA@I|AX_iCl|6I +Bx^mNS;gNXBYgFMEEzLJtNz2XpDLD(fja8T?AOKSTx_q0Ne6gMT2*Os9luBvBR$6vBUL;L@1~m;d!vd +9I)a1XgB%Uf%ev;y~V(7Df+4Oi_LeX&C3_e#WV8~Pt5-jGrz(a@{gv`#ypfWL;BgHbmK;MQhE7WLV08 +8(%hu>s+{M)2DrB(znAta_rnl_KPX0j+p2SIOWrZcKgQ%s-h%v*>m|{rD$t +f7(olYNT#4>@8oVvxXe`WOUh3mh(|MM{MBf=X_n6zoxh%$ytp(4J|8eij8Ic=_HY*)l +dUyHpDnwgRV-x0EddeZ+k(_IGmszJsn%5Z~tFN;vmC0lI^>GA8MF(H(5xa9Y^&GA*u5C*!gEqB~%7_= +4}!4|&({LyslH|8FMz9q$tUF9H5H09*bzZQ#7t0bMlwOzy)5TA|!D*e^lo#;<_y;`0k@Ha2P5YyBy$J +9A<8v}<{5TCPWHzcNmnsrQ`Fr;GL&oo>xBz`YBqRv2aE`zoP$dJ$-EG3UUl7`Kn~0%>@TOkx_AQ{_Nk +LA{F{A|jswlLi@^Ce;47eocL^*Wz(B{{!_mj;P-;vi{-mCdQ?J?k|8A!}fc1MH%;tV*lp+*M}^kb_4o +9W2pDu!gDUy4NdkegH3jD@6oZg#^#@HMc_c +e;azW>m?mOlml1;cBcN5&+K6&IxT_tt%kZEdrPlB4KfKFK4_0Xdfd`g4CL#ydDPR~pCuxxUB)`tCr#a +%%+xd9!!yWDL>YydK^0qON$sq2;wK0`29q!+ds_&nPoE6fHfV7YxwflI?-4lw)qFr&G75)ytTQcOT#M +=W53@SDQ|LI*W-+&k^VH)}@Ha--~C+-7x2X1Mtn8pa+?U^KV5NYFz;3&$NuVF8 +tT@*cut=^AT$ZfAT+q$G6jOjK_uqJf2SG{(?{a*)$~z#Y>eUY7 +wK_4{xm&~$1lGNJjU=Al<|4-HRF9kyd=;Eks2Yoxp(uW>(OqGLl>)cQC%e#(cO<{uh!GZF?kpdA&bVxn`8UzJ23{jqb~8J(7wib# +-kjRC^EBf7KZL1J_^cO1^mux|FTf60ly+dX$t#@O~GVISL-T2hH>iFTVYU+ +-4m5LVJqk`*9{w*!em%mD*JmLCH;UbP+hmZ9dW7Eub5@&4dHwxuG%_V2#nQ#ivEpMvzlC1Y%(&Fu}#q +}a%%Jk*WcLE0PcfxVcx=L~UYPMQm=my*$e|_wA9pn$i=fah^k4kKfF4wuz=D7m-#{J!6>t}gxL*HIyf +1XPlxt1hxzY~^E8!;Mf5B5b*1!y)C6!ijyz;s_>nUO1t$mDD_M^`OkNY>J#d)|ijr_7NC$_fhR8;x3+(+ChlJ^lEy)S? +4ya{=p3fKQ8ls(NXn>_Xrl)X;G^-?eN7xDOQ;BjDr5%GUh?%%mz(Kuh8q3{A=@iGPga=PH)9iiQ@o87 +|Nz`erKrx+*uoRHm(4{qGbInJ%HP3s?AKpWBp|A~Cj3;N6k@34H=oua1a9r8QO{EmeDMP`1kyA9|LiR +bm^xdyFoNB7@|=A+Lw7=NFth$&|VfycMaGh+t9$Nz?37;+;6xO^3OrJg+EjI9YDf6t-kxF?PXY_~gpw +X?}$AJ}T@-`%GCRP&;fy8h|4=#IHV(Gh7ou0=BzT5qSGtnK +XQhyanxSs^fN1e)M_@Yzoq0m-2{}}fQ`NYEc#~fP0lB$aNE%oNwRLH@TTpQM2Sv;^8cFnm*jqkVX&H* +QQ^P|meg&ZqeWUgE3a~>^p{Khf)3TREZ7Ftyt9)0cDTG!h^3!XE{F`*5x?L0P7Vps>~w;l2vvp;6Ee& +Vp_Sbwe&K8G|JX3Ri&pkEKG}o)W2L3w>K0E_{h{~wPy +Pw9$jlag<_Mtld_xBfM>;Vb8OTu0VxIgYp*nh?u-{T;;&k%PX0KTag&Z6zWd6(KBB)@BxS~D- +#EiX)yvb~FI=BMP01@Ewp;-kEJ%B6JGx=hhczFDGKLd&(Z0nX9%$e6Q==iQoXO@J|Ac;6h2(jPj}moB +>VY(`{1t_SkP1^Qxc(`ICBowq_9zoO`M!Q8hb=aYE<2zco8DI(&s8R7QnqM;f5zUZI`&8*PnTueF2D3 +&@nO`c!KeYF-j#l+9&s6D%mfN!~e@u)-X2N!@Xztz0w+OGJ{-aI+{w-xhgr +^CloQjDsY>s?l(?^7eE+nO`vR~`C(2B-2dWpLyn4;C#6IijPP^uOQPmS$w>9=W!=UsdEnTpSxKHv4FR +qso^!n!^gL&4%tJCG#P1V8to(0k;L!=nqQ=o2D1+x`1m8Th05Y=-`ZDx1?%Q;E9 +)<7JG_`->sPuYISu8!|W2h8qc3gw3+N@&O58NUeH0UR$ZF>p0ua{?hG^@1k84AW=3j4ji +%dYj5abM~_yC_L)lNT`TNRx9rp<=+na%Rf7PQ|OiX>yO3Y;HUcBae;$ytIhmrfkUE9vR#a8Na+-Q5HxhB!>IJC1I@1Fs_iXYQ=^- +b_}alE#eXO3rx7M?Hlr3@vDsRObnsQpk9{kIYOkw`y#*!J{oNE0O;DQewrKk$Fp9@i^R*p-hBKi75tR +QS{A3(cEFh-Y!gJyqfF2Jzg_G4l(ljG5mz7yW3R^dEZUp6;#q&hv?)QLXdrD*7_Y2PTZvKW}}xqI0_G +f4^YrBNyzoJ@yf6rBD1@rf8!demh{;gM?Tn7CCX09>-t-_vM)BF4Z*J(gTZUj{e&$=+Gcsfy +tXzoW``V1YG1;>&A?CaYld96#JadI+>fV-Hf@?|Pk`^aw@|ui_q|u`Ik`W!-%y|2M^QuqUlVdHo%Pjb +DxR;-ywScx3H9%c*H8M+8mI10j8*qHQTNVx-KA#TbH}KA9Q&z@^5qJ#(97A-*f^2=&XzB+%dpzCpy?zAb@I95T|7p!QXMU=h*dBw +I+mTPFoCEeB1XO0J5QhSiXm#}wT%~14*?Yp0Cek()K`%;GFZ8@%WAw%I~EIVSXaj;{390wZ@Pu!!$)x +~)C4iisnY%M#vetzV5Sh{(>iU@th=4=USa$hfryE?qP%f#JWo5CH-?E~)Y?-#kw_lugb#@ATheB@nj; +<3WS66oeGJDTi(zl92fvn^HVxO=UxF=j+T!pvK-L +**{3FEQn_M%~tYYE((+s)ZJO~~fUbkLz<9$=fi(Zj;%rTYw@+~}hK-UJ|+VbR1^3mtf_8rhA-R7R~eM +x)5vwof}sYS5Vta@+=`Kn4uc7P>DEpP+{pOWlL7#%-JWR*3hxeBnb3Jry?Y7!ahrX{z7~%3fr +H506T35!X{}R~z(uV2eJ~U3* +wnfsPIJT3%Lr{(X4WFN5>PdNij?zaq8GFfnFd5$>-)SkKhjs9oS>I9MgXL%k(?eTv^;D#Xz5>3M@3ai +{t~WjOWxh*^=aJ_It+bT|orYZd<m +Te#C<|HP4e`+EDgIRcfC)b04JS{mJcF#xnPYthKb|-M1SJr_DZPFDeQNvmXmHPHIO&J=JDCvGO>USe| +9jui191mKsVQ`ao-Dp0T~7@xj4VN8rTKqExw;?M%7<@uVEN9@Aqqcz9le0T>!5tmQdF<)WgYS?KuwS#TgC2bFMb{^S +U9%dX6f3WtK{V6T+zm}$A}--o&xj(R~MMzJoh0DPN#fVI{N5lC(nb7(0AlG=s`SF$L#UQv#}yPr&I2E +3R#=tqO3hUN?uDEG}uB?4w-aNbW?reEc!NipX2_Vn+PHu*KhXyy@36?&3&vr*2%J=1aD=IYljE +pm|5;HHjyUEMWW*g553mQX?;8Al-m}e#?&3|tbAzNrHy~pt(o^ju8>b>S%W% +o@M$}bIo9?#^)&qW)5Kh=@tJY<&Bm{88PamyJUe|-gVsSo^;0l9E%y6FBD@Rx2eBAqhcE&at#n-OV&j +QGf{lnZ!v7?FF;`w!uIO3N77!?UBb{A0O83tPd57aT_5gtxhMr$fvAj?)@AaX;*)1A6YjO`xl8o&`xC +wPd=QyQfb2jZ?I7Pu~qW^!4FuYRZqDnULmXq;aK+?yqL%wh+d*&KG;W +)dEMMA`He4%L7w@>F=OWKgYMmmb~zyf7eoFDsdLfhTVLF{1Z_K}i3rp2JJT&fnsfBSZ#%j(4d0Ixi4b +(8{65o6%XBQufijqOCel@zY0_*eO%Q3SG`lRT^VTCfV`J-}-Y!&lGXIJVQKNi|48oQ5%8Jbp`kz1a5bMpWCL3+wQ*|*W(24YZ)cql(J)ji~@OcUD>$HzuJ$)@&d&QE`bs4`wOPZV? +J7dn}1mwP|d0qE|_AK*=va=$yVzF7=FA2DnabMiy$sv6+sT;B +Y*eSGUXunjMGH!~LadEoTnfR9L+wF4yQYn9_PmjoR81T*`I_-7^TRiUOg?PQGdgqzLBkPrWM7}3(Ke2 +9}!y21|mt{9Qd4}Y9?`1s_+b<*G%xBrgUq$6vwdZN8z7kdIHpeFSz(Rh?1#>QKC#vCf$w4tD%Dx`X>HjCbzgNx-{$+TA?6G01VfD#+c~*^FF&Rflgc1EyeQmORUZe +DYHV>^VI9r&)(%lSatDg!Zufx<6D#4j@m{AM}WPezP+2Lq5MLpI@rG`oYLQ@%d`1dcOX_$a8$Yo*KDP +`)I?7ib)|&SVMD*)P54>!#>G +4jp<3}#wPCbTW*Q#zN`AEeSXVE?(^Hfz?&x)_($*aTXw4@KF`{xkG;=ttp2YQsdHvn*RCRY4)9C%sYb +X;*yr&KygKB$54_YPvgUgMx^r?Dd~Z2aT<0XyOQJ@=&Gek|Na8EO6j_#+-y3F +4Z$)3E)f39|ModEY*acg;NbX-sA&NYXHx;nKbo9el*n>qv8_3HGz$=pwcd@)}u@IjY^TqeB#&izZcHz +wl^xgUURt4r<~xXx_rd%54THgP`VTN-r{87s!69=1lkP~%gOT~Y_kma#qgKAzuEW$q>3XAvcTsmJ@zB +SOpvujPP{3YYX=_KcaZ6@b8#+>gOr~h$Gi8jPr=zkl`{#S(d +zM{+df$+a;qU|9~xV}c2I7a__U?95V>&VZ!)wvd<(VHe@OaRNwondUG&C4@Fxp&0S@1i>lo;`!U)`Wi +cJb2Fk4`Nwov|YyOGz(?VFdy@r+BA7B;b?Zj_c$aP4rk(ePBg6KcpdogVe0lX?bS`XW6ha#eA_&!VQ& +VmU6WLr&6AdWhJM|l$qi@u{oG_T@8o5ScWBx-yO!7V6-`a4Ke1EI?{?=s+<@?zUtL%I~+k8KJ+56@Dxea?$`F^hXe(tjQ@ +_lK;*%ZDnHQ$#mBd<19mNoEPx~A1->V0L|vYGPz9Sy5&e1C`e{*GmgpJ>{AXTx4A@pGq%pF5YirG2zn +;_$GAXZiBpzGgG~E#CWe`x4HLJFclTd+Cc0sWdFZj`xfiv0wiN)2f(I$_mC`>RYOL9>SutibgU$=rzF +frc7YWg+E2b2-bf|TSgs1*->xG4Y{s*vsfnO%x1`_--u=G3tXo`Ir%DhXo|^i+~WhbcB9iFLR|ahH}? +fmXL|*znTP4C9O5|n>zF+6is!MWt)YLr3cOC9cjHP~hIFACd}ltU=N>1o=4_R6@*~4?@>R&m{K@~zpa +09BG5%z}%wuJ$zEisy@R4<7PquGDj3q4*RwNZYSWJ +@@r-^xRfcM+8UdIj#wv==w|G3dD~mV$BhTyj3@=kfHM +^r?Q97{4@nd?hh1{))xoV+_&r@MU42bm#tiMI;wjf6}?6ucEp)9;DyIeI_lRv?-q_Vv;`UCvwXj3;5ozgrQp +GPlwG!4Q+vGG&+E%LN8rTsQOGePv{mi_zZK>9vqi{L?|*)*@xirDdDhN;w#gn+@$mL-a<5Nct|r32j> +>(ieE!6A&1SlQJOeR2)_#qDk&s@P<#MDh*&nG+OlOvxc&=u$UGuhk#GVYk=W<2Zq_wZfDQ0tzi&N?>_ +VDi*qix2y%tE=(SqJA!f9K#mVaku`jGukNXe}7}Kr8(DmlkNLp-Fkh_Q{UG3Hzgu3>M!sS&m_!)5SdE +<8T3ERs!2cd_zC_SwcNMX5A?=COAKKc8$+vu9@Nel>#;Y#&OPWGyMw*WjqokuLQQs^W5A^>ocFN*(uj1xzxHz;#!L&}_=L_eYBSwaW;(w)7DZh~K +F&QTI6m~!e4d4ebnqumtY>U7a8oAFvsCAM)*TX|Tc)ToI(Or(RVtc**k_l81`ax;D|n^ZVTK_Z6ex-{^eFt30c-$r$dV?62 +&n?1$tx@_q45X}j(KzRs92hCK6*{)QJkXxFCAA^my3+RbyFoX)T5dJE@`+V%W_%iw*^?W}}t)rq|2^` +fB%_E86LU0y2WK1KDqD7$R2cX3}(x0Wx@bdkQPSPqzD@YPUSwuttP9c +gT1-#?iB%G5(D6vUbMLm~CUb?2bS+WzBNUzM6Ap=N9&qEiYDcHXYKB7G+8%rS02x_BCFd2lpSBeGiZ~C9bgYMI8Cy>H`5Y^kaSR85qlH(Z@+|7%dH~Op;`aGfnp@7l$urR{@jP| +9Ysrnyqx{d(JAIhm=?V0P?etFcPEquZ(VBGMoJM*ZjimQ!*vTEv5%g{(-R>Dd?|40=chIDFRh-_9WjC +OAWfHyXY>M7qo1%BRq<8uVdh-lCo}8(VbNWT$J$UJ^s#eR7j-8DDjjdz-N;o8DMdETt0> +nd!!3xK)KJg +uGvDxQ$@`z&QTrhUr?-x(t5mW&j$Xsf9&dB`Y+d)FW^2E-lKhtCoK6aW1Eqt6Zz7R2j!Ih3+(UM`akL +j?hEZ_ytbAu*Z!@moYg}AwhFj&IL!4-TvHBuu0cO)cfjvNI>KKMdBb@%*#^+JJwxp+Jn9>0y{=-`F&P +tJNngUf)TYaL(*obkz|}f^;*ztlLtY2Hxjy;~>{Z4Zcz)DgeM_!f&p65kSc`VN#IsjWms{kn5gXk#$5 +0pbeEnsk%m+QsITL$!d*LF@?yJA7Vrj1@6xWb2##&%9 +6%nVt6gT6|M4MZe@s@fs=j;jq}`X3Z1>%l07vCeqTR={OY;-$KJL%sYgBPpKH7cr%=C99lo7e6Y@Hk1 +ePi{D7h>~~zFy$*6!3NbW5!A1Y>HXW%?b77Xu|dRqBzb@q5Wq(qV|Z{Z{A0I0e2IDvqg_K4rTyv&i7> +OT7q_e$2}5fRZFSs~vCA__MA&w=oBa}2YWNC#MfOwn(cR#&s(z(ozffpw@{s4uy@vXbmSrtLJ?2`GW +h`?h`t;lI!}*QRzTYc7F7tEjgZn_~GL}o`kL4xKWPbE{?nyA5|3X53j$@3IU*^5Q^Lp}qf0!)Vc3Vvt +WLq8bY;mV`aGvb*%_*VNlhql_j2C0y=eiHsu7#qEbUVoXm^lBrC!SX7xK$#w8T8@0V;P^Njg}*I!2iV +pBk~u(fB)Xf;u4!ZEgScYVUuM-XO1Xyo!Uz}R%V~u2!mG|oZ0tpI+7}C`=LKwnjcBF10Q?-}jm8YMpk4w3m!B?&;V8+`Iu>q2E%_HGp4KPaoI}+Q>cM>y-{(4jX}cMs +Oc>$nk+QVx(=bILS6}XpWZE+)oSX2cTohk!K5hC>g&*9s|saz*k=A%_`_kzg8giWC`?Sp&HAP@ID6~< +^Jw|_S*!Ux?KvMnb+cde42@8?j6r(ml;>%?pAwky3F-M*WQ+Ar{*v%=aE0AD;ec9=MR}D&Fr7K3FZI% +?U=0k$vf!rONG`LYbd`u(j}2@F`@{KFWKRUvMx*`Ztrn7lAJsgY;b)^W?URXE~+<+pnw>@@K1JlYeNuwxPg;brO86dg>~`OxnB;_8 +8BFzauM9{Wbd9fT?ejfAA<^np@=D1=u1!@Y8aexFFB1J43s7(j5O8QRV~99>taOXFMmDbDecsAcyvtp +#@|Nmj_pGRJECL&h&NMD2rz#+RgW|Ipp0ON1q~cHsp&4d{N2cs=mTGuYn)wSIco!`EKeQo;Sh!O-Ro) +gh`H>y`skXyp5k9ESx0NUQwCo%V=lspo)_o*Iq~%w>k}&GM#G858-=nt{g)u{S^F}Z{f#t8kuIMGrni +YSm$>WCKvEe9;sWsevtFxI?8HVT{5^%#`k4iSQw4gjv3dG6d%nv(mo54V@^<{^!Gu1C8$Mm_88xQ +-iZZ;`}vy5+fXTu*`YFF2qN(ElK3+T@ujx?E?~4ILYHgXaM=_kiOXtsDnv#yqb$V(Ta3=W)yN^v6V}e +WD24KMY>VLjE3Y5V9%aPKakr9*>ay_!YS)$>o|luT6Gmy)~>7dX=BIdj~u^a+_pn-4nX#?u*A!!D#Fy|a$k +d4#zwAX;5m2PhqnBSn)Lt6kD6zI{6=dinjmWD)~Pra_Khg_KY;zje*gcl_wI2~Uf16EexBhn7eHZ#i- +U0hYXW0T8U=(TEe{$~l(ablxwSbFCMN`tJQa%!PrZ=e)oFetbTBW@bP8*_XBVUVH7e*ZQtm>GdVB0X>T}Dl=BkoN?Ud1u;HMA+0_0imhA8V&U#2~_+XxpjAB#M`6prf$mi`aU$`Ly2 +{S=rk%%ULE*o{2N)GI`QWe%~Zpmu`*1y}otkO!|EtWIFnMD2nA=i~Hs%{p>0|n}zmnieR&#OZh27IjJ +*mRQu8@u^Plguq~8ofL~x+N1tflvyy0+TjDnBHZ^97tzW9?$K8~iytJI#6eP_=IRnwHRvwosDq*R_?P +3#V!Vcmys&7H9h3g-Gg6~q|OvLa&o9d|UNcA0lmXqT}S;?(d+%GO;<+%nXbdSqq3x5V07tnic6I+{Or +P#)SbH1E5;^RJ&X*!5EK$n5>cHE$>w5j9$TiDlAdXb)G&%ET9kxzPkP5r5g8+zGL{VL=xsXxQ{AeV0~ +uD=mqOS|gJS!ZQV+%<7M)eL_f@c%-L;A{iOYaP`FUY<17xlJu^!)LxG@Z1VHZsvTcz9&J|D@%n_ox+a +P;(BHz&xDxIuXRVUoH-SM{n*)BIf^!I;CCB-B&BE;+uAfZ2me%t@VCwHIbPw57EXJLZ+r +REj_7I#3H}E&=Xuqpl&Dtxh%q{riYoqa)1NO0F)Yc5*H~j7+a-O$B)u&@$kW?RsG$BI=$$Klj;!fsw^ +l4_n_b=nwEpLivn_)}BAXh!5Y=YjN^BJlW`g`d8pu^8f*YtZV_h!|`8S{LT*DwDezBKHf{~RCQ?pYg; +K3XThOT$^T)+^-5hJbRQGiN$}7Ul9Bd~DXx`a}D)wuRm=Xzg1-vYQ8XoH|_=x8F1~FU}yx=-kpz=Y~J +R#)r!Po?7pcfO@BRHTm_mkoy!r7RU4iagT!i8|xc9^%^SAbxiR>uHrhh^trgxoZV^^GI)_R2Qf@@Av2 +^Vh;ykz-&5#2?YTpJw8kJ`TN1bqp5y@kxH6I@L&y3o*Bw~wZxEdwBnP(3(M}W5!$|bVtgYrc!};kkEd +?Z(?o$1NqSd|y+5nC>4>_i0z`m+SELQFJq$rM83!mhLL{F>%Q)YdFx(i}~f>)yXH^tr4 +q;!M6~Q`aoO!*O)DB>jW=66E$ef^cL8NslVz(INdvhy{{GI#(&R>8NgBWw_yO9#`*0a^Q4%`8bZ-e~^ +^%Y6#nwC`j$T&sFH}sq4wD&!)@TV9h?{T^152~Mt+83KxTUF8GOk!4LnM{&p9wYf>7oBY`RL3)Is0c4Ge8X)w$7WFa* +#aVxcK0R9H2euJ^AS<2IahHJj%^q4$f1}?XlbG+CFHpF1imjgQtblyP*cseYnWg$>UI(2ysKXm@#uz! +5u%L6v4xCG3&icmOSmwY)tyBK_j +aR|7P)PLK@vg-FSTFwDR-&SrHuF?O$M(k0KPi3{VR?lDzeq~fHVN9UIP;o}N-{Nd8KXaToj`QnzQu#g +bBH=SubI~sYUr*m*%RZftS%eGmSYGMl{;l3D8Z-0R$8slcvr(|NV#Q96r!_ag +RA#SNV#eDU=WY&Y)1Y*%zO2W0MVe&$zJZ!rRYlHVJD;mgtb0~+>e{3B3T=#gsnUGk0Dj}PDRH +BCZ4R6DW57ko|dCwS8r=(iu_G=mXrN+BmPwhyjPlf(}q597||H<+ +e1<)yt=u}Vr(SIR)ZuDRHeQv04IlMoG!rw-r{+#}T_%GGDJfzMmPrfSlYxpwJ{Px%w*Zq2M_MtTT^ul +Mfo%@7GWO2G}O?D2vM$bq_0jAWzbs{FHDU-R6!x!pC2K$+J426kK@QP!{X@B1&e5FWM@&YGNYhN+VYw +(=Rc_O_Me$KU0E#5QyNXI|v4DS6Q?TyE`0AFOVUP1 +S?&8YnitylEXDhb_Wc`*_usYeg^IU9``%yZT}|J)Pj7(#8SJdM9CcikqbTiWpWpW^)L#o{-FjLBxF5r +s$cpDOC|?gq|FSjFG?8uIL~CLh$L=t@?wS}5pCnj*2<0a@pO64_ddtQ+Zphe>zjh@ETQ}fh0ACS!-4^ +0qxaUSb*q^*kdCGmhY^hf0;$FNY@Tu_pFnok4?T-oKE^ijm3U<8MXZKJV_BFMfo{j`zOLvjSq_nucDe +T31uBCT)TN@&Ra0U+ZzN$L803N*Ob77wuNMCCI>3X$apyMj_Jje0fs9y@nsOVF@+9xMH-$G{=txu0%5 +-~pUyjSg$L+#Vm%f6-rzGsKz_3e{)j4luueXXhfbX2kB#z3oRN`pZ4jT5T-v$=b~t#-$htc--)(4{>!!3Sw5@)kzyuf-Z!B +DS*WC7{0U{kLj!io0H?Z6c?Ra+yXPz%rTXJC +osXX(VkyYSzBX7eAHGu+ni=*9bHqHnf9F$du3{AaiJ7%Vl3Yl!}2$xUN-zP;>zL<9&NxKU6;&Ctk*E} +d|PU1?lmlaxURmZmeLnx-SK&96rCBUA5T(#oS_fUGuxZW=Xvq1&CvB`Mv1Z>2bPeLIhVtZl-kd+%7}; +SSBd=_vC%Viq71{E%EtLotV2fGQeO|ws*jS)$e&5l`swt%V%q*@_$W-@$KtE#bJ54b*PuvBG665EXd+ +vPJAC;Y%#1#ye{owc?(nx=@#Q|K8=iff=m}a)l+`%abUvt#Vuwzq)|Wg&ZE?Vda1wk7WBL4e^1Lk%^S +(|>=5wf(>gyV0@sK4Ph_OTTsg#7RR>hg4c_f3xSDdlsq3v&S+0dfKtTnqz-|{sro0Hw>q4Uy9kvx80O +;L4fX?4Q$%?26|lmps7Vqi|p0fUs%t=H+hf#qc1>S)F%;{CSmbXY4QS^lD}6V9~IJ43n{mkC&{tLl4- +Vh=RKC(_t%Xwg&K3`D1Ol&4OL8A5*+Ii)kmyDmoL4a|r7VFQJ{w;2{(GI6Y-vGWxL{H~++fKQAivjYo +#JWT6w_`~OPk#>iX#ShUMGg5u!l5%B{6ftz3%Yzb~*L1GAH~o7v&aU-QEFSCaBCWUD$#j-CEAEfKhxi +rDew?1Kq35>UtY#>=wHaeG_+D#sn~Yd!rtTui)Cj+QCYc+z%IlgZrTW(q?(sjJu86fq<$R<|wjb9e*@ +aC7w@v3bP~q^6!`oMD=V;kZ<=0f~Z?zhx(b}T^*j~#T!rlNsU*aG3^SVgRLx9yEan8>#_MR2|g8O)cv +03idMf0=wyubUJa%q0k`f;Q01JrlBWNEBZWB3%QaVslA;#SI$uxB^A6#9It+t-BmhiE_f1Lo{*p;tZyX#0Q3s~=n6{)UmJC$E`4a@72l^(;sS&g=6wsRKIdcABWnx{14zCPooXs4ueG=q-Moc1- +PMYEi8;unv@pII+>NLy4uyuJw76)WyolJy^D!?`DQvBghB5&v~cHPx^&jbbTnt1?>*SIT=UjGg(@S4E +m1SygPJ`Cg)zVocBOHl$VMe2(y5<2tsQmm;Wf#nyNL>y5)>& +&<96ZiSJCz2v)o^_8MD*)?b6Y-L8J}f&9`vFEk4&S!&{n?G8e6!0;Wv^MSY|{Akr*DiumOKtXh;P$qdmZ +(D8uoR>zbwc02I +WMH=*N*VQNp=xG(Vy?nSB3s?EPEbJM$1A>;&|p9q<+=WxvG!ovW;=QYJR=smoW#_c=yEj^6MhlPTOQU +T~fX!5$uqI%78pGqt;gX9qQY^5kDD_BAhQ6;u^27tsaJ-|DcM;>Bk_A=|+8?XNd38dY(>sJ@gLrl`_5 +a&?&zy^wBZa>R)=rdEvq$Dd;Te;C|wg>z-R7XC0{rbUd$%0 +%q~+&(RB?4=Ht+wWcK7J9^Z~BV;+;D}*M|6h9@oQ7Dgxcxsl{GNOY +m}7V7f8k-4F^(NKW)OF)ZL}VvFSSFpF3;M;H>AFinD-hE +Kh`1QKD`uBSA|;FRM=swbpfXib!m4Rr#=*W5PP2A5$q0(T@*#_fzPh+572S^YJMNU)|6CF5oda5Kz*o +--(l(5!tvtGjE+kkYzGGVS@tEv7Ow<)iloT;o{H52B^lmTJ-HSG;?Iqs40e*DJR2 +H>qVf)piUF9XoEzn_fQC{p>2dI6sh<9r`gm7~9nC&VYqg?_1&f+|;XV`#wj|Sw7N!lGgrW9 +xs;0@gQs=%+=-y7H{6N$r{agtWTT|dOoJTkj@7#jJ+C<&5pBXIThMG1HM-1n`^bW +R(kf%_p*-WWo309?q7ht#Q7AZ8-Ag9=^WT +GjcE8$F>{XGfuup!F}VKV6Z;`5h^h;%*st%;D~%2Z2q%(^*h@XnZ41OIo9UyoPPo;{Cu*a~7?`2Qw5m?!d37GnJmkv^;^i5p +SSP;<88$FfDLbKRw=I9m{?R_v!zQyCSt-TcmAiLxiZ)N_9XV2A?GSPC6rEFv6w|zZ1>~8+v@7K;OBI3 +V5ZY+NoHSjajOlO3}2K7VtWH{!XT3aO(S&{P`2nK7-L_kolR!-^SEnnq)wx*Vo|gHPAK%v>o}Kt}m_N +dYUyQ6&b_}6BKt9kGsbfBHj|6E1ZhN?_j+@R`0Xw)lVCfh8`5ULvHJb-+fJj$A{xrTFf<^W3u5VyiLI +!a(1~<@ji3G*HpKS|wm$!G2EiyB;xXi+W#<`*}J?dYRN}#W-Rw#HWY$oeI +iZ8OOXhYr+nO&N{W9P(MOr@!<{^egnXZO(edOL+vaI@yGar=W~A&$@w9$Ie`C^;Kvjk70-e8#fuYyv8m6BI8UR-; +%gyeQNqVUsU2%9Qq=s=b?+RbpW()0i+?QgF&5yf;HjhHBdUIQKNx3BnrKY+oDySl-8f@%^1Ns(c=Xf% +#F!`nW1_V)w0*aS8JqXRjLk*jt-*O7bp1pbuDI +gcefFL??d`I;JM+riO%~!6nb*Ky$Lum(ncG_@B@UfjyRLZ>l^H|-FVS99^&@3?EFF|yn$-AXDq|9lR~ +|dGmE;q|V(I7cXf3XLNzMX2URGmZq*41&KR!Q5lg?x1IgJqF&t;5=|B5u;;lf1q$@(TsPrd$;iMp9ID#`kdtQs%JFZr3d#viO1#e=(1f +tl6C1!U8bGE!=2+jvZXPF>vK|Eh~e=p?VXUDNS5vpXRAph`=szYi8$EcM6n!6XXzF(gp*ub=zTqudN5G_f&pPSi#S-gpQiO&b{u^lS-%z+@IYR~olHR4ZP5Er*;GevmTE_5;QnfsZeLw=J8bNIb^3P +1t|$KBMOnTja=tg((fqsj==>8c?1S{NB8DB0ZL~y{7j?n%Vg}`dJX&J}-oBC#zs9in)O*Kp&v1t6e@) +^##FJOJZKO9+=tpX_G3YZpn)zO^SlIg=TPr&?8$86EwnlG^b<}m%&QU7P@oD=9=ettUa6Ie~3`De{s5 +A9ZU(h=~XXW96@m}7u-0)IUleHQD|;yQm-^mi8R;hyb<`4!x@GSQWVI|gp+An4am^urqGu@^4-at>xF +?FQn_(}^}d*~I4>?vZtcBp1deH!hZvyRqg>slvC6-}MEQAFq{P7_Ht_i?TgS=7>Brrh0Wf(i(9-{NKJ +N?^N)gz*xS57fO0=F?Zr9n=RoSGkUXuH43+YFy4X2Ci+TPky2akf;~?jv63n=0l1`pEx`Pdse+U-AiV` +y)A@^zk#Zm-^^D{5o{Dm_MO$&HOfI7ieF6S+y~H9sYm;ws?h$xsK(X#I0%0EtH-{^2=7*?=px^k$>?u +^$J!k>%W7@v9ifIjPt%z&EW0KN*p!B*`4Q;@ +I-$!ZkN#V2drxPQwSU86GSeVmVo-~Qun(YX*g`06!vq^Z74u-<&%EnYZ1Q6IU8&k3RtB6WJ7v +Ya{j$*Y2c9o%8{&i?+m<-)UuQcU$Mw@(Us0Ihw7nl!13s<#GVNeehBHe0zAv3)qw?y2el~2B%-yX?bjGe+mPfQ+Sdql?FpldUif~rY +Tro+8ZX@NbsH{Z1%7wtPhL2{by%}HMCLwD~cpd9|@*(JD%A*u_Cao{{2Xz{Om4G#`7kqxM@E>PJmaXU +ZjU>BM`Ff7C5>d0A=ADW7pXYl@{?K-oKkz-~gip%}%4d&M^1ThbghKDf+asJl9dk#}Z`eA@X(Hd-siFDw!TA)C?=O_^--vF&p8 +1UGGg4YC<@uD#{e<2*FilxGKypvzoa-BlD<96wTnxYK$ulz-vwY0U0j5`c4&L}T&n73e0J1?L*j33XNcNMK?@9<5l#7FNy4Z2SSqUh@|Nr(p8TqQxRMOGTv2^JNC)6M0f+R!X{f +pu}~GR_Onf2$aJy4@{1)Y)bJ*)5m>Q8gN|)*Aefov(o3ytd +{m4cOCKYx+LMZ+DdH!wlObDR-l+ +mw6{@!jc?l27tLbrHu@GZj}+UrhAhwxBAn8aD9Kl$mV{>^!bZ%3jUGQ7xanNUNv;>-==o$jf|+71Nm^lqrC0wz?&t#-Lqgu9`e70F{Atk=7vK?r +d(SK3z=3eUoB+3h-^ye4W1Py&$#0jy-U#&`FdWQT6j}Pyd|n{jeo-{OISzfAz(Pp-pFpvVk(baqVwUjvJ!5&t{n<#pqZh1xXm4D_sGE{C> +jklIy5?Q+PXT@wP@(-+X5`2p=g>}jn%kUO>ZfH$VEOyPH}=o|DK9`iG>sjT_L{B$$o)P9Tjp%1vXh{M +V8b@U_c4tJl)^AzewWJ&CacSW%d;@!1Fw6-B54d_fR#B!|&is5P+i3wXBX>6>Jl-e%bdr67i1_N`>rf +0xE4n0?DenyxZ|4F5QpegS&`?S1$>2|Q*0`@afEf5!$$%&a0?*s(Sv0ZQ(ypD+{Mp4n4!2%5!IgufQ_3Y{;bBxE@g0GW^Fr +VK0CB6xTrn^hw3-4YLo1x8MBJh+g#~t$E6{`xgU&_xbe+yP%7vG1(ejUviem*iLelbqkqkFZ{+5?!Hu +RVP$_Ho?6zTc=tppwJ-$8Y*oCp$M6_TXxB?B=5bjI^KOn{yZJu+oZ@|j$1q_X_Z;&zr6sTql%c&_s1x +u0GT>e474}Rdo~!CsaW|6&Y>DTDZuJGC3Chi!D9XwX$d^R*5j{X)p8wLkf83{5Q_7KHqMCtTv15K$JfY+fuZh%7YBc@$_h1o_iu^7_@mm +nq{BYC3bHGekn^FIvx$Z))yNQ+kQ>7i2sK2b_bOA0F~E4Yu!P0Yvm=aV(p;!J+!}f$(^;(xd+eXq;3n +*tDfpX%;vo(#hh3n?l4M#(+~Z4gHgx^2i_+7MX;RAXxgWu^o@MpHt3x;9jhtmx2^2gt!oZkm%zHX_pehQ^I}^-)}r6&IuM(M|aXQS|-2tlqQq!4{Ku_PVenIp!(5-#O3W3_BNwpuUE(-cKx7;{n +Swu5Vu~7E&kX^!Pk~Yuny?|2WXBxhdU;zvvyEt5Vn0PeT(RfoCtmpCFuJwz1vwei{lf0{u#b68&0G5A +Ea`&Ql1*3Zy(LiN}7x7uK0KxJ)iM@yWSl`xBoizan;AZrb|~f+-=RiGr^Q-9vF|CW{aWxjZM0pAWS~0r^*uQ!gLxayntEkT&vIGch&aZ;>shg%Iq#8hMyucW@_u%xPw2S +0-Os;9vdw$e*oGln?0q_7xUJ;=yuI?|m9Jz>&UOvFtaJMQ1;d4RY9f$cueF$uZMy(6h1(%E%Y%w_n$@6M2%dnwOi^?q#4XW}d{8V +4$nZhz9^PTThym6|0~cCN>dS#su6;+K-3duW@n7JzxybpiS{TAQ?<_mC|63jGi4pXc67=iup7UXm`_+ +4aP-VQO;+Y@RpLKHB-jtYO%JZI(nCf4?Bs4(_o%^sYgY;s^Fe!sf~eAJ&tU4t=mVFO9}$NlQ1i6cbG< +18l0o%?}N;{%pkXY+ja%-}<16%a3zeE-=)o{A4P>i}+^>l|PV?(qd0kyo(~)PKoAD{y(^lN)2$EU>k~ +frZ}G>o+)J2aM*W6+&@k`+lW0B~j%VUH#_t+(<2YZoxHwH!%y%9G`WW;;h@GH5E +A&6xP5fjtt&tNv7D`e#ut@c>2bvQnnN4UX;t3n6ohG6ma6OA^9~rJpk8R=lO~e9Lf5#HPm_cKO7#gq% ++(mSHh3hd!%e$CwrIEpNZGXUA3&o30=6KP`9Gi2Uj^zT&miXUJV730o-~V@W4*VE&Kt06QHe%i#pt|h +pyvqxichf`XT^PA_1m8o&TAHH1b8SF5kR8%wfB$i=*d__ArLpELVmVY&|LJ^@Tb{`GWJ|PK_Jn}44}P +M>tPuJRJ`d*?xed(0<#qUDWJ(Te(R{r-9RABDJ&(hE+EO^pSstf&9dW{c8GFqv+CRz@C@sOCb_3FqC@ +smKmW4DcrCI%HUqjjyN}J+Ovmq^m(lY#MR-|1^Y1jJG5|NfmX}SKiiAcMd(r)&rnUMBPO8cfiEedIKC +~b~EO+wo3lyPbx +27lW7NSjG%GyQ43VcRoWPq;2lYRo*XY{dQpTZZ#%9nCm{0ef{nm9dQbTM3(y@c5Jg{flHB{Z!5;z;A; +d>ep!4;fCBImhVy3(71h^g2=2-G$zt;`6(O-e>XoK%uws#AsZC2UQzwocZTRutRjl?=Ui@)(D&@`h +*?`=Om_Q&UE@Gx$%&JkKLp!hR7bR5o-Xu<|GgLYBka&6`tH^AH#)0J_2IQ&)vf}@%WmwZadj>uIyjzxp3ChYsB$6nxNWbtaLkisb3_a +v%%QVXF3!N>zS!*AMdb)T0kcbMXF7jov9G)=aEXcn@RE+fpFo$Y`WH{KtLJySsvjsh%VR4p+S5*TYc| +W}bJ(F|8qXs2j(34B^xn*3Uj&m%#LsHpa=Avh?;A1dC1RDalKE5~`Ze4 +Jm3b?hR4dYpaHrKwy+wpXf`Nb# +Ht%(|8-TaR=kxe@6r?R!W@EqTg6h`8IXJ}DQ@WL%b(!B2_1T-xG8VW?}uo7>D{G>_3&8mvlrB7uizP# +^IP@zuj#X5jlfz%xej1*J*{||S9j=_c(xvRS@NM4eE*5~{&T!b?>vXkjf!_BW1G>|WTM?;+ajFHi4VP +tI{+E<6F$}Ta&UW|8G=2d;{6eQI@UZe+yx%EX3=oHnuq3S&T4vwI6j-#a4hMCKf5BZY*!8mUJ|;;Z;` +kzi`%wFoknY)8UaxMj-%%ER3YY+))MD0HiHB?oap(4MlPVBYp6?O6C_?d +XdpKImT)3&&X#3&&X#3&&X#3&&X#3;%~}V&PX<6AQ;%6Tc^!G*s?s|4irxaJS}Au^n)?2K~||k_Sp9w +j2InrDw&xgalldIkM9vFgaa#w)AXxOpt%?AB|^%d!}AmW8l{o27Q}(Nb@a-=RB8j--2Em`{h0#$4SAt +f#fbX?!X-?jtum4(3Nw4A)mnf(DWl({o~CS%Q4VTu)&Ph)xy|5!hyaixSfPR~)F8CH!C9;G +{$#5`j#=&M_ahAstkB()#kIE@SOx$;!HtfrQo_2?EvB5boU24Y}>&XV$dH8l^&smbMQJ#j`(iz3dfH9 +m?yL?z?4JAdYEUul~LDyY6ZjZD9_DK3G0t?-Czr^DnK!+vu=c)L$iLNNzwa9$mc}GgmA4zAFuF!tO%b +|9m9Inp@sY~1IpkwmHSsD!_LmRHNcA?F%sT`y!pE^vcsaAEy#vz=+d-4X!@e@2- +8-NiFIVR4m1KLF=q=(Z^r^8Fa9#wSl?hl)g3iVI>kl-~pD?M>C>amh)pkIJ(aOYm4WE^i-$?H*pm&g8 +kGOa=MkdL0aJu@Qm=j}TCWOvU&`>^eEB}iUFaKELl)k{v#cDlQ@S%SqD}9Enx$jqrO2aX?X4gM +rIQf$wVXVSZ>nU;otjrQa;*S$MM;Kk8RG)%%68*>su1jGq>3Gg90+Z_xMq?_0(@2l4tE)cUsG%+L3z4 +}7st+^4%NLG@YG`pouvsxL~buh0;LJrvBxLh9S6*4OW^5B3Ou{%7iw#;b4i{ga%2%ZRQ_(DoK5>g}X@ +e{)sn;B$iNO&nPlsACJ&9j(=!ZWz&d1=jy%{+X~t74*I(c>Zn`ZBgXWPR1kqu1BnSO4FyFSEA3w*J3ZUt#7$gXrV=kukVc%m?aUhB^<^A7AQiwYy0cSO|p(7AO8cOEC!{{37sjxU?1fw6xv4@FFlcbgHzZ$EHOjs@ebF|U`+jWPcv?p;FXZos(FTsQc2qrfbS +jD!vRnZD>&tEM+Yx{cB`T82AEX?=tr4>0I(XMPWr{}}Ka5QpRejCm3t^G+IT(4NZ8#@QPBmwhC=;S)Z +cpf79@mSwO*6?8^{T)TqGd3U57v3{{HV%<_1hbl5~#vC?GLDHqADwwjv@v3qvSW%;cctmb;?(Sf;Zq&@+=EP?S@9<5dp(+hDwT5n)H-bm{=Mchuu-8?>fO`EV +Iu)j=gyGY}oPI+G$8rds%GZp)PtT=n^+}Don&>QA@?eY3X$R?SQ@By$?gT7f@|7GO&d*Fu$NPaKw_uo +TD(RW~z%qDvC`y1+03grVv3FM)FobokYfZi6dpCpbwn4PI3S)As;L0NzBZW{ZeQS3}Pt?wgx)`5O{pk +K>qDA7+lY;{O1cTbe!-9vm~Zxr)_UO1mvsE?@=75A&j%pDK?Lp-%-B6IWS>RqKFb}qNiG{zem`F&LUT +yGJ5cTHek@ORi9EsA8`1=PpO)CaW7BXgWk-u8E??P!~3SNoro2j%15!n9h)h9n`7r +{h`k>R(OVfR$TFo^izK&n))gFxAhBO+201yKjuD$`yRDDJ2~b}eM$OX1Mly}yoH``uUW(`YM^|}==aZ +Pjy^~KxGU<~R><#x7;w3cz?N;C +{-yR`S%r*xL5@3s$S4R@Foj$`o;oS&(EbpbXDqw)Ct_dC!V@96X2y}$;9$-}ex+S6iasP{TNZJZbyie +yc|`u~rB^6u#q_JhH3wnO8MN4RzoeW0Uww$DHBi~58wZ)iLk+-0tinS0AsU&r_1pR_!AX(91+j1BHKK +Ovb+;El-k<0RL>_7-|u#4GrAzC0ccWig9|L2U*r|U!rrso50%Ep-k3K)_YoZC8 +y+XC^evjW1hPRJGjM;(JoPIne3-G8>7x1V8>3Bh4ve*SZJM#wg~~!(ZTwR3@5~1A9eZV_RLH->4!v2$_M0V{?WeFt%VAruH`* +d&I2O{+ReUnEx6U|mX<8gBgCDIY_Up6XjZ)l3)>Z=hSn#Gv6Ie=Hl$-#c2j~HfY)1*cBTlE0?cv|kW9 +WS&(cZ+eVcQGbaKzy>AeKNPt4TMpGVt#9XqNpT6S1cR9$2!y_ZYt~tSLWO_{3S=CM(tr?7~3{>|@aX+ +G1D@d}wXbq-xl2qJO}*nKPm@Nw%-(nnka=)vVTTZ+Pv;@xzthYpaPCThp2i&R7d@be{_8-s@bn55#Agv{cYgEbvK8=^ +YgAZ+@ISl@Ep6Il7e|F=8vZWYh!`_Bt(0YAk7G^P#{8Js{6SCGnW6#vxN%T;n*{N6evT*p6hYFj%U=tQgT~qewA}y%00Y)dYW4@|J+fs&X|$_d- +DQmBHzCXY7_aqE|4NPjS3`v4dw!!A$SbI`P2u<*q +7W}q+`G4{@?4f`1HN|-^@Zl@-Zdg#+1~vkUfEt*;92dJW$x$oUA4RVRp`4EHM5fHt&CILMbgBXpu1g~GP5cUZ7I(M9*RLqnF% +bE&8&jIXON=#JDXXi`a6=xPT$PZ)ZY>Oc{qH~aC7d4PJsi7dFPkM{c)Wu19&R<=}62e;;G=FhQ-%CQK0PjnRJOU*l%`eMiL_SFZTi-uU|MJ(_$ETr?hM#7pPUK8~jpY46uX*gdZ?=QSjg4$|lO0ve}j +O8XGW)C&v>p!N@ +bOL6bPKIZqy@irbM|~dDsKi(f+Q@70eUN7j +3Qt?(fs7{K~D86Z?Fos;|GD`rkqQzF@!I?}e*Z+w<{$sBDqN{WbmN^ +nUKkDWY=PNS@jPd_ujzDD^Dq%ukf8GqIOozw@VEGZXydJbkYxIR){4voDBt{FvI|P}{LbW;4;2;Uz3T +aLwR3@RjnYE$iu>Vos;(;raY|OiRJXEPs}y&qdjjef$grEck5v-v5!X`+6964NIUSQ-F1x&*$Y@iO)- +crD?YJ({F8l{{9O0y>+9k72H2wZ--r=ewDI`68>-F?(-Fc#GVd(-paIA +?Jm}p*BNX7+@^DN({GgLwLSth0Af;K28bY5uoO7K5~ZCe+~H3lxj(Ed;I#imGYK1xCJA#jfGdx9lkZf +o-tb5xt3;0KtSp>xOecQIb$wu$Bo+5{TkOXEv>dh%HBU|iD@H6Qq{F?EC<;JDMCeT;a{apEr@sTeoFN +i$!8d^>hrm7kucJu@U8pf>Nr#RD|TEcc-c@PUK8ZS=?V7Je8dF?=r^y{4Z0yYTs5`}fiD13S(Oyo4`{ +9|+lPS-|`7H$>b(hZ@5^Fm7Pr{_;PG8@P+s2=@1T0_scndvLtK@$7Q?Uj{~sd$#D6D<~atSvYS=_tTp1O;hRoa$It*dR^d%_@V`d4`k0o*tO +_VT}IgW=u(CTuVZf6MzXuw4&bussU4o*Mzv4>V{6HiBx}G2d68tQ{t04dWB(&retK=ia4hVXsGSx2W; +70DOlYxtI}sBy8-8qiN#-vmnbea?`??%2s`aYzKwO{XLPDkY+_x~$OWO*<6d^xc92Db7HTJykRI)V2RJM-pCu_o^SC?!=p4rJw3!@ry8 +g1X^PY>-TigV#=gkDPOnm*sz(%0ivK?YefzY)9$b0=Y0R-eHyNl-}dVfuD>u&Yb6D5%aM;k(D97H}FL +(WhEc|Njfl)*M*qX`!a{BCyMir@9l_oJCy~w_1^e(qCD(rb47W5YI$__D_gcdw-IL=+;b!5_0YA(mPd +%+Iix8GC~GZz2Pv(J#$*n#qSnez1JS&~R$tAk+Gid*x)`Za^!yX+J;S0e5dZ27+Nxv&pQi +h)fVctVtO_(pNZ%x%dK%Ley8+x<8!eFm28nMHD>8Q6UKVh`=lWm2N)+yq+ZG@jcHtgLQ2?Tx^$rM2go +#cH*(1IvYc$90ivxoLFnL|G1e!nT;k31ueIJULJ60(60$%Ql*$p5NZyK6sttR*3(?C%si-XP)I}T``9 +aV18Ae&3g;+5%y&@4$)kyqIq=%0u2nBUVSVK}XNrIl{lPAjDQJTJhq}hj@|m&% +4LBRSV~z{5yK*#?1Pj1G|O&9(XITLLF*cRv*gBWuoD*|Z?B6BArx)~r{e|Zl +C2N3aq?ZgPPc+Vl>C;zj?d?~TiMhxen!KBdtOG<#5`*;D@un&lF`fW@f +M>d+`!DYb}65xc$^dVT +r6X{P*l(z`PPXs6LF9L*k6>E+RJn^PWV6|vLviGCT;Tt<5*h5CDm`uD1YyI->%XRSe9s?(#hx{!ZP2t +A?GP9=H<((*O<2ur4J9no-vC%PI+KAM{9L_9%`(M5Zy_p~Zo%R;a7E@(-4q{8e*z3*t(HrZ0F(Y;T-W +9IZGT3d*pw91LqYpJeEIk|X%XpZ(BgZ(u>zlE%QmDZs9ohrS%JHFf=HT*q{|9rlSc%^!OY6mV2^c1GQ +3VVO^U;XmX^)v@|MR56RhvrL>q54uJ(^##8ECHE^#w-jntAJavW_XaakTFO4|%y(SX#dB=CNLEusG{5Ug^z5R3*d +S--`gNd5_Uyl+uSxbVyuPL_dX|l{xIa{XXTDuOoV%H10>p#`UUEBd!?fpAhdo;42fh^2_?&8pa;7DUJ +)2|bM6v9$NFH~grvBiZCtf63pmt79Bm6^!k0;5QshFFShAzUdO~=a4U#qOd88XYnHt)?yt4^oAdl)gU +O|%D-{Jz)DYM`UoLgSG}d4Lsd%MrR!%LI}Qu~+|9&hk;2nC~aT&3BrYf@TVjLp+nu2VlKJc1f~Nrt>K +LThKqybUxAJfIOUgj@B1qb@SLqMrubik9Rrp-iE8fuQV|2JxYrVNz3E*u*{2lA?R?FM2?>Z-1#2xoIu +`syjR1aSMBT=*EIz9XBKlK_9Fa#2WgIF8KARc-uYA(*0d*DaaZVA4y|!_J@D`>tOh!vnE~^9+J~?U72 +p34eDdh`gTQefnG=ZN{&lW{v{ecnZU*IXsCuD9b?)B9@1LY%?f#qQx^vI2@p(x&T&w$DgAP_;E_Fe-F +@g`VY-O4hKN638JUoct_-Z43iQ`giHIghnk(~i15qRc+s@HF)&#VTA6|qk%Wme12lH=OtX?Hx5>nx9X +!FW7i*q-Lweiq3(YH4hn>Hjj74;og`I6_|LI^Zbr8}c=eon<5%(>W+Z*W8TQW_;dIo0l6^`JdK +FxsGEEk(`@a-42~|6gxAY){3!ES*T|%CA*{8A$`^Y`=FWY@M=9#^(Dr;l4h;HfvuD|)&Q$@&_1=U(aW@@YTLK};3gLl=}!T8jNyqTj>#{V$12A +^+HcTa_a4J_>G~xD@LI^vnbn#}!-60MU2>@=O*OcLkZIrMQ3dT-lpolcZ|IMy|s1DfJ~MbVgUEg(Vmk +Pnx-|GA%Ftsi@K)N3uhY_g4x&z`jN7rcOFbI8e^#i}HxaB_yi0I5@W=UJ7Wz%i5>fP`pfhfS%*7-oW +!ja6YW%!Tp1lM^W$hzlzz0Uy0h8Nokx$e_=cZjU$T3BZ{Z^#uT|}QZQ2qEmfj*B)6RL5K13bY+lpney7r(>7y)i4p89dfP@-@*8@%L_6S+&Qb7vFQ}JK6-iy +Myta-_<8BDCY7PwY4n1(WYoyjH9On4I`-SXd`0Q?5DQPj}Ub2qPCGN;6<6SROUx?1~|-dWfZS#+L7j +$s0~MBh?ePxo9~qLE*;{~t8Fl;ZHSd(xzEihdS|XWMxPm8YaXmqe&6*+UCU8A&lsp(xWD(TE&qL&q;E +7#)3tm?&pmaBZKEFop4AJhmEPY`zN%GmEhq7;!Qg^z5c*xo42=a+?9f}p7Z=dED2vue9PO8wGZUy!*U +|aMqs|vQ^{Z$Gm7&wyO*9`oQlu${&!b27oZ>pX*ypmDm!XJCl$61wC}{T1luZccJ-|lu{@D4*1$Gjq~B+w=`29|(_f>Q7xP +-Z<18UQd?s_J!`io<$4Yg-N@Z4FTHXts-@3Py&vB>knXb6oSZ6M5wd6Bja~>srhj?rSh-Vc^Yx6A;&x +)@lO79JcXN8#CkfkfG`r}zuNVb{pMF~3z-2KD8fMc%4sy2-AbT$yaHXG!{)12bWm+HFlF!9bcEC+Tb# +Ot~JxLi2R30Z)DPoL&IuYH&N@1@gwN)%v14(NW|Jd4%|`gjL?|0IWXK^99Wlk}!Hh-MQz|IZXX+mZ0d)$(31?Ni=ehOF-roelEJDVY+ +t;Ac-M~ix(hn`O53>7nXYtO`*C5c4qSyQ0LaKjErFK;OsZj5$&HbuLyH?eH!!cWlild~ +je`+Ze=9P|&_w@||ZBpMb%7C1O@;X3+{T@_h+9nA}jca#)eOm+5TroY}CNpp9A%BTPa>wOh_pu0Xn=Y +fxO)9eP~gGGt+i`PkpVk7awMZg%-t^01fWHJ@JDb>U@uy}nG%`xI<*gp_|E|;DieM_pL^;q^hD)Wsfw +y>4>GB8~nw5|<2mZPb94e-#Y-tSPI8Ww1Qn(m=={e6Nb!iGXGMHmlAkw)XR*R7f9|7~4Be0PZQRNZ&| +9_%$JA9Hi4SaHL~(tD@k#(O7eZK7`l_hs%87_PC^uuI4D3q)t^?fm>o-+vBYn^f67Y?Iv|K(>C1==Rq +CPq%P%dztcTblU}6&kt$c(!BF5n-)A@zh`iJmKfY`7{q)%NzXr{au3j4ZByrKzdm@rt|QuEUFnH_pl2 +uz4Me*F$~$;ntMzV`X6vXAT6+*f;Q-OD0I~}1Wf3DXfNl#^`k*};JwcNSm7eH-QLV!)(z5ozj}v8SbU +LBZ33TXsn&eTE36`&pEM2pDweU;dHNhE6GF?RiOW^pF>0MUXgAP*qMcVV`-AWH{n$ +veKUTmv^BvpF?YGj=ShCmh!Xo;n{7g+ghx&H{VyMD|Js2{*hg@)(TokE^T<;ikRT=BhHdCoU;u2Mvcb +HjY|HCI3<70h1I47Xuge}O9n%uVz4y6{gSU~U}&xzv~mE8^c45A%}M4uEZO +}VoPX$9y@i!MPI97w?_pTide=!Bqqh61_vlx}v=|Gs8eR8I-vi9zIw`q&KD9p`b2owH9O|Eq);RQ5gY +?dPT0=-nro0&UA5$IZzg8#eps}*}qF7BEwdtbHU`IZ4ozj~@b3~Tuj6`z-a|Gui%#UBZ1lt~F(P&&l{ +oe7eAKSk=fX=hLK8{7x7t6}ti}m-(-yids{dogtYWnSaHPY>&xrOgTR6qK)2Po#&RW_{wgSMLm5nqMoe*^|bMPk?ukIU3A~0dr;~~EsL +>Y^_b=>!OgMRVXoFtPjG}78OMzL(15wZ5)lLP0q@hi>iEXhBwx9NiCHTDM&_47zZK)J*xzAe@FIC#0S +yxjF8*Vd9k?{S{j0xydp{E6@L!m6-m|oQDQwFfgD1-;sTXFGlW!}xsS4?m0x;}<@#f`)(C +XC`07gSzxr^+jUw~czj@roh(T}iwG=UvdMik{)@c|qkD6I6bYruXxU2I3bO!~LrRc!f58R9_?;)- +ksUC8r+R7pXwG1sKz1S-lJg4K3SE@-#mS%ga`Yp7;$Bm6w6VDHm`~Vf1n`OVI>9Hl^!v)5>-PooiLWr +9=S%+ioFRpu&lj%xlF`<4^eN`Eh;{3u^>~ct2GSa+pXmQ~#y0ma(L4L5O3qs^`s5Cy +TG3wxL}fo8leBoo*y^cd+5vI2%HTuwi4X)hHWHo~b-0<)MN?`w;XESvNVFv!zsizn?>&TWGF)Ml^fb# +8SSa59YIP8ri+!`D~oOkMh`jM&q;NjYDC;IDB}?*MzpjO7$f}I%fZ=Y%V@ZYxvXTR_mwK--=Z)xA^G1 +5JPpZU!^N8qB&=Xy~9);X(+DR$Ks2wVdh3khl~mR@~5oT3K_Fv!HhiEYV9StvY*ykn{4hzdA4cnOcsq +}<~~QWSu%5)^k4Lqd2~uX^v2*HXQNxKp0$dIxwM2i-4P=GgKzEiPUB1C-&=*VS#Rj@HL-)uw?HrR<{N +Lk5gy-+-}~~|b6Wgm*g+sR+qZlDx@vu|=KmC|GY4*ReM0G_?eIZS<1JtJ0@0TEs1_6HGOhccQQ7%wd6 +C?I$2_!#=#$+m^y8@GRFBYye;ROaa$6+ZTo@S`KRSGzVBosrZug>y#fWwCcG8C%mcf2ZXBkN1FiCvcvx`+)m`co9n${>&nttTiZ}EMmms{zE) +V3XV0qlsV~a=0*E>99b$0F>(<%HWcS=JU!l`7hwlF0r+g(AKgoH=Mv4AeblDT9>2Z7lRZ42^LsCjKX) +Iy>g%|V{tNW%40M~B5$q6Xf0@=mN@Y^Z`Kfy6`BSVNdf3^J33b+PgW*Usu*Vmrvol4+M{PRm(7+^C)+ +<|SZKVvI(^-LCUM76KCk5h0a9jiUODx~Nxw%(1^ZdZeAEGfH)TOvufx$!X{#Z`w#@z?;p+oB`2lE`2_ +PqIt40f5jQ*=q)sMGNU-c!6g;5T!nu-CjQsYR=EIkm-KuXUte*yNp{x`3~~NN3@7RY~SbHM!UvPurIWQjCAxqOfIdAUCyv=CU=M6g*wl=9c98hcWs|~jVSy +)lk?QtQ-Sc_es$fhFQGYV8zw@du8@L&~&HNoJkKH-Cy&``f5{qa$m +6sgVE6mvRObA{(`*>)f%Yvg=!dqFyj6CIXqHcHMjGB(8Su`ofOk+PVnPp7{nx{no8AX4c4x3dL-(+D% +75rG?Ge+D`Cj>htaw+_{GRI5aJ2=`B7VPn3;h=gpYsy*7x&>Lvvw?@blh#@j-KQv{{0V!{Cr`>A^%>2 +^-pbb4bVO`NPOfD#=7ew*iPKtIx2_qDrj8Vh#vz-avhBi_wm=#nMkjsIv3#1hMq5=y6doZF8g+)oUPn +{PinN$T4KiflH>4Oq4qp?$S-#;r0)uS|69PmbL|TPgA(Ty*s&N^DVqu!*qJ%RYanYB(44U~2wU+irsP +*5?iS^n0sZ%txxJYNmQY#wNS-Ub%UUEE>{*kQyo$=}^RkE+8`S*MxlGEO$eTs)R8ihcdIxcjaQ|744 +uj%`Ed=d5hcK?y^ojY=Dw~FI-&Y~Qem924O_@*Ar$RP$8zg08m2Bu%DBrktFe7n=#=-w|iTFX6=5l{* +`#e6+Sq=JiS@@pW`Mr@Y%6?H7Z9h4(k0-kRrb8T7bG4byMdmG=tdCPa95jcMV-*_WIVuP-G{`rzG!w22z684_wU@wFC2(qyEJM_Nr_dB +fOgTk+Evx*N28vSAT?Uq&FaJw!E`9Emld +9h!Is7(S)4?)Ml0EeO57hTpM6(>k6>3`@OoK1$e~sGzvvY&-QJwFPkB{1`V;#Y5OANq9^|x;)wQq|mt +8srO$_E)#i}ig$$8t2<6n&!Fup&Ns7;Mycj&NM^62wY&ggmdO=UqpHKiMLhmyqkOb&W66fWDw9@Eii` +!}%Nabx?U9hx+NK`msYW|GnG?in$Qyc9`b;MLy>vXzoY4&e7aIOLPDCZeJ6f8Qh=JJo$v=Q=*mYBZ1} +Q7IB?Z>^wI2cIKRlIC5je!WFXWYhmL`2lD;CkA!Ur)=gisAKxdOT+T7GA_P87^1e8Z7qWaZFhS +_FHlx4{%v~j!->3DfKlN0vRQdQY;#H+dz`z*JJxpg&%!^(+8^d3G25gp9UuXm6_hbug@{P9z?ze&4OETELyu%{rc@6MeD%5FM=Or4h* +Lb*1;KiG%e8@CCB+moiWfT8xam_r!Z51tbYuL^r9go|4TA!|SK1TI*M$O~)tHc9b+F6RnJ(JmP-7SwV-5Z^M@lw*D5^oA3IX;Qs>u-7 +gLZjH0f0dHjhI+(GsFd@qZ*&!Rn%@2LLNGO&3P?WG>B=Q^#IbR%}LX;*zYwE^`R)VRQb{)7*|E&K`pO +>K*)BbnE6;%&{JF#LIPB*wy2Ry$2*n@P^iK2|E?`(batjnd%H*f&+F#d_%TwFbpG(Z<-*CidP6Zugtz +tYhp<)kH@#?~h@Ivw(izK)>;sNn`mf_4|a0t<4Y9Sf&lT3CWGYsiSUV@DfthN)r!i@GbN!VysVm=n$R+B-W`iazT9mc+)B +$I7HJl8CTfgKuv3|L#S2eAW>Q@Me3S|kCx_St4`lS$t_Zw1?Af^V;F^UH;yc*uP^$zHfJCV^yNI5Tt>8T4`7*&O)qRLCM`HT2^H$s&FaFfxz^+fFTpPoO>{v>ud+vJd+>|IG1_tcu +^FeCExJXET9q@;;uE95^V6KKn7@g3FI%5B<_`^;Az+)*Tyhb^}Jh(Km&yQJ*f(wOMAC&tivPo(5Y`;s +H}pme1Eygnfnhfb1?zJV-@Au~1Ya1S1VYAZvC8kwVYK#BXcHhGf*vjc2xcPdyujeSqgV2-fpXlqEXkhn +KyIk>9{!gRpOK!UPhf{8lANr-N-gR`n`Gek>dUn4_vd)a3_{fy5-H-OJiemRaBI!-5>2s6x;FJS(^*t +V}v8c1@&m4tSUMPM0PWS6-R05cZmRlqXh_5FdSqzVIgJ3ggl#3AK-Y!GAa8J1sY_nDQ@?RMy=KZF +kXU4gEiFT{dMoeWn(d*=AF}F3h>hmPqgP-hPkmLIk^ix$*HSO%L5?yJ%lA#k%Nj+r8-Pl7F?8ps&Rb* +fhLdjP>28<2mN;nqirp_Wf&DcKxjdpOoE2f4d8EepZt?yS?q3c)qgxuLZ3O&qPn1|6sx4*Z=VR^Utp< +;NT(b9i8~BFZjm;PxJJ`Z+&pf9LjgkU7{R%*f-~fPYQ0W%{i5S=?C9r5h)yNTz}gw?;86yl}wO~3p}g +hN~5Ku1W%-KDQuu^(!X$D2%A(cu=1oI))>m`OFlENvx0xbr3pLaI7Q53+*A3al%)n*=Yvvm!hmj_6}A +|9=)_U3&S3=)dYbatCB6NGlt^h$b9*n?yw0bzlahtf8m;h^MxFuauvZz0?;7j|DK(*rK8vMjd$|;6NB +c9W{h#Ud_M?(*se$S_C`}ou7dCa>(lsOX-l^96rZkz-e#+}upw`j#f7yE%xTuQ1e|*jYvLGhj@yg;AQ +53IHDlS(60r8Gz!2%1c!m{oz3Z}K%jkFZYip&%*^(8MwrG;gRw?flID-QWyzV|(!na|98=5pqonKNgR$|a5?5^1Y#;}ay>qq}>@wrY1jw2M31MU8gxnd{|OcH%< +2r&+)1p)O-<{ylul*f$s9p5a6*;@|cj(xYu_(!H%G>DIOd>Drdso~|(sbSEhTB=u4?+(yray1tHp%`| +0DXNy;ZO*YeXwj_&zFI!FL6k6JBCmm{W^R11oszp?uT>eH|lSKZXfb?Il=MtImdT +SZyVn_?LW%>s@Rx)KIo}~G}3Umf7E7vkKJ)Ie+PC;^>0XVunoG;rlmF3ti}c +ZS)PW87y}|A2k;jR5_@0t_q5EWSXiu?H_o4ozlX<*U+$Bfq0lzz%XVdQ(N&fJ=1G*FV-5%Xr!S6QAUG +478_HDaUe?zZXb3FXmUQ2T%KeqqN9H|q8RhwZooMNoQ{t0TcoeU(6e&7+rw{y?rq?AE9PG9;fHp+y+7i +1^G3UQqusoQQk$LiP@6A-t*E{R$;(>{=~6>~0QqD6!7``sar?We-R?BcNAK6(ZA;qHd$=!&-^lGmJCe +SOvZ%u_vDc_a4%)<{%t*(yBge6MivmwNCN%DN+#>kb_Z~}FB +ehSPCUchy0)dy@5J*0=lR2%4}a+T9IzLRar=>*G|dNJ3iP%i!x|nVJ1_JiE9+ql$iK-;-^r!# +wn!`c!x;9Vef+rx{Wf-cIIr&4=Fv1XZdB#b_m!`8B|Dp-FS+VZZidrwYX(`Fx{rO!cLaThw=Z?`*WRR +dqr4~O^gUH9R!-`#_HK%Y +VQ)AwV*_VgP{biZ)=9Emp(*8s|aX)y(}`cj4}j?u5Bdk= +=WsyDxL!-c7i7M|VHw-d=X^#@u0CfBi=~4{|g1^K$x~&UXES;93;_1B@32Li;O*c3SSm{r$xJ#eH5){ +k3-X*PZ#R_Hx!2&-Jx*_SfSM`o?p8p3eUK@1QS%>vQWL1bxvks-qU_NEzAz?tOWlz?NlbGw#+M-F%(g +dZ3%1lbb)fX`I{$bE9eShVdTVKR6ZoU;17_D)hf}&%spaf9X5QER4k7;~Glm*@E!CC7!Oo16d%*Vm-* +PK)A=G_tvuif*x(*o@?h7;Kh`8@MAU@5ZsmTRRuEUHvrQUK)|Nj>p8%u(e-$FUE3N0xSvLq_M_xBX+(f{&n0x=(;(iKe;)RuV +<-IujhN!(!B|K@@G`+yIS;G$#i4%)C2Bj;cmPv% +yjQbx(1K!_vl5~zKqb`*#5=LzsKx%ytxfdP#)+p`(1i3O2?>i?0sGOEE)X$mc#VE2;;5fiQ11UROBQ2 +4#33z+M92ypzplHw=SU{uUY{o+{;(3gmXEZC2-37@4EB7J=JQy&vXO)K6;qXkx|`;;dda;mpMQ06xUn +2KTHsg4O#z$*VcQW-du7E2nv?&S=xW0_H#8oC9kDhU@Ykc{Wtvvju(WZ_sHV@zS- +Fw#`F^6g?-EY2+e>UJy6-Z3jy6Kfc@~44PNn{o%D(2`ZfEnMw9M4KwL338ngex>v~_+4?YtkDnrMsOh ++D?z8rVDvN!ob!GZlDyz|fBIckRp`BeP)2F3VwcUQ@BB6{dagv$-i-C?KAC>cw4Cd@XNb4vp8l +wdj(=hyJiV{7wTfG2fN;6tWMzrR-qAGPkozl)YVZs95!)eLQXu_Jzc&V9ycfAmX8V_88U{cbW`kRCc+ +fHuPL59l*>E2z$rJL`NUFwoK_2NhNvYm;%ZD4OJ~NyHA^ZfYL-MZ871>@)(+FvzWxpAI&!O+}1Jd@?K=TZ;R4Dfh +7+>Z>Tyr1|@v6{~&i;zgUp4U>|C3MbvPPf1qiNdPLLTNq+9$y|0KyK2u!(>PfOOw&+K04~$o9a1!>j3 +MsSbcW@ +%y$?+w2`wlf=Dj`>`5N4QM?_4$fhtIdt@o6a?ri1JIx(Zf6$Jr9M1@(&1Id)^|v1*D>b*)RhLI<@2mD8!zmBEb+ +(#*Q{rtHU-t(Y#UOCI}))iCK+&WwA{JetZoBGMHG!) +$cL`6wEs*mM@^btxaiE{;C3my4Ki89Zx4b}FqwgZ8-|iZ7x4eLS#NF}&ZJ-#x3o+ns{=lZOFcvTGq}{ +oN)(`qU0f~Q4AP2vNP4^q~k-sP46nf&kZZ#x3%E(vgG5oaRKLwp?ZMa +0dBA0VDYyn?72z_C4I5aMXW1jJd0vk?~}mLRT0tU%n2cp7nL2*-HD(TE{}59BxvF&A+O;u^$v5Ni;>L +;M}lJs9(Ys6iZrI2thq@gc-{h^r8{BJM^!jCcmI0r3W6+d&-rB8DQyB2Gh0M>HbZ5T8LTL##mDhj;|B +7O@`jPsH|vF`tOBh$)CR#Ags!BbFn+gLnY(B;xmoO^7Y9yt*KY;}(|lJy;IC2fE6OM-Z*##B&C_a&eR +n;_k^PuYQ+f3Jk_5rgf0+7_NK`>RZI+4cqUmUwQb(tC+tE#KJMI;Y^#kJ~eV1a!-*jg}fBGmq-_KZ{( +EYyAw|>RMkyk+(;ltdOT6Z;QMT +c{k*x$h#x2RLE-;@+O7cZyV>mkZX~55%D9}ATLJlkGvdtU*t84@Quh@3wyokYHxSs9>RWay873=>6*T +%H(mRmU&UX45uc@y#=p}& +IjuE=96T>Ik`9Xr> +3UsEc#4gKHwRd`ixvalg^T%%XPS0teL=bWp2$eSR4+isaZOU)tS%JXIS&i!Ugo^3X+T(U)MtvsZLP|5DG3w`wbZav8xq7RfjDg=_VK6=%O-KOz9s>>m +a6a%L_m^+dn{|+0`a3v`*Pj7UDT9OIB)oq1WwIbLnDF{Lnx3G8E*1&{90(ZLpO8lYAA)ls4FiD|!jB- +34}dcm&cSf@q2VCVXi(4>elLXcAvn|KWm$FUx%z&X{?k?d`zj +JVdmD{|ZOZ;~f%W|Il=Cn|M2D7yQERkujm~wR)`aHeSs>#!tL_YO7n!ibk(%&7ER*WlEfDxyrS|mET!0t^cB4WBnJDq~+%s4H{a9ECyOD(i}$OK9%WH${%>p +#YbA6&Zx`QXa1d0mHwJ7XXo-!Sk6V??e@QmZ(4tgjJiB%8U~}mYJi?&f!?gi0&gOpLawvw3k_E1^f~! +*uE%$c?zZ}Rci(riQ{Ow+Tc#)W6(r|RbG4A_mz?xV+q@%l0a`V~!--jZQ9n4A +)*iJs+-|=DobbdB#yj$I-R5$P88vrOQSOX +N`HUn{=diHAA!V33P>*e_2=AJkel1zA*b_FEuH+seOr~g6Ms>`p9St*9?KPcg@TtT_(}zTPQjm7@a-y +BzC*#qeTryj;=U}8SV#uRCuX9DzdR%j_Yc&zo@jG5q8Fk!%33Npy3zZ?ws=za0CXRLJREr>@?;D%3Ed|nPDlMSFz#G*& +m;Vv*hKifr5V#|!?Z3&T!QY8p`OPvJk(RxV%8#GkLlQi@%#(j-^X-)hVoM=pW)8$k^DTlEi&K_svDU~ +rjQu8V+zG`5_||5NfJmj@FWrkxt>6xfc@{Gx$Q4d{<~dv`(G^7xV!y5euqC-AB&y-q<{G59Da$@pXwj +}iLjb~xB36=hi&na#~y#;$)}2+Ub^g=XP2)iS^3=at6q5VrP7yIzq01lwd=~(Z`io$wb%bu{>J7lTer +RWR>j-zyt{qJd+%57{NTe~AMM^#wRhkC0|!6;r25d|Pmdh^>{!k56Q6(a<;hdEU!DH?%s1bjtvmPK`T +7grUu?Ma!{sYK{`7O>)n9(S_S^4&G+qDmuNyaS*@>ibbN6WBsrK@2>C@_-*1m1pwrk&^V<*4PUAlJb- +osxL(6d+XK7IQI_755`FnG}5kRd~d4IeS`-q8E*9|iXeW3{^Uj7)u2c8+28oZLKPzG<%6Vl9|Azp!Y* +1G64{C^fBl`NInrJ@V+kJOBUg`2W-P508k9ijEl<8y7!5Au(w}^2AA#r%atTJ!QsBZvW=x|0C@GKcWY +e9Do1xaOnXh$DisS{yBO;=Y0Q@{3`#{cgS;FrawgH?cqhUsah5`VCSoq4714%Rf>?@Jj<_AM60r)g2C?Cw>wHHOa`G|Gdm#EE`XOo%0}(?IwTQ`xX^4f0C5UB+ +m55b{)rd8SwTN|y4Tz11O^Dn +@Mv|CsBvT6XWV$|+Ovx!AF=hjqtg}*Crml!kIYWAYKMN(`*T{dNdrpB_PzWyzKu>`gSv<7A7=1cP)R{ +@R$qZOT;vxR{f?N__kWD7*O=Lobl_cfQBT@Pcf?*1D<{}!-%znU;NS{0cf(`?{e*ztP=C>f1$w60v1s +{llPOCmIU2n#8g8qEUAP#3|8lTL=6Tv=-+&wXIaAxM0-PptI{cr>cn-LLt>8}2!2IioRSuU4jO*p$9h~d2$Sz!;(Z!9(`ppVg5ud}2$4h;>`ilo< +Wd8Leouxy^?n%Sp4%Ljji239BjAu`6sJwt=Grf9tFFOJ5BWb>pARqInoxwdr)?dK17Rm9Lom|lVjAzj +Ih}ps8H;`O8odqoS#EIuRh3Dxh06SPQzcgJ|_&*Qg(-XN~uyp3jZalq8Un2dk?%{-2mSjYp?nE*eY?= +xG7dfRT6aNOnl|B*wG_p$({y}|$zxyGUJow*2DnppPM1qB}+4TrH3AJWW7zlYYH>LRmW;-Hq^;{2=SlO=bU3uHU?oDNM8 +dhi4PiCt{q6^u0n!3m2TEcU4^D{(#oZMr~dc89rr?*biP&K~oZRX3w?0Xir=OCa5E&Om!_a5#;U-gZey$;-2vUQSAXD__JcCh3l_%=0Ir*6`6{No +96h0T}n-TobCv$NAQv1V?)|i?#1J|+n7u|0=x9r(8YyZ{$8GPMe=8l`gJr10BD(kuH9Y*Cw@i4w$mFQ +Ouj7UEvMfY6Ysc*jt>!>2t3!ZzQp4PkDzy3U!omXu+leg`%nO)HKewNv9L&jmhO+WW}`1n!N&u8cOhy +CzsDxc`7H{RNRbhgi~tfBt*4RTXO&+OB5V(zr~efv*?0ex2tDpyCovw2~cfaTiHmj2rEvjdO!Yk7I${ +#AXp#Ze<@yL@wc%DN+abTiIu*)r=BpD2&tvODfNKIyIZ|LXtjinLXHaa@4U(hoarTK?9wH`a}7G4h@9 +57wogv3YO4)u{byd3lYk%bZ?N>Y1SlLtp%4`eR>4#MkS3zVX!<-_akLf4#8M+F?djtJk|<$^9zG_w(; +*?w{!KfNp}(5cIHLx9^hO2Mqsa@K61cGhUv1==F}5;+F>wQg_NX#&t1vrqQMse_gBTIVm}?_4~j6(r$XY_7P_mJ$T>Amp +jjB>K6JOIobZ*CwI<&ZGrCA`)z0IT7vc3bbmH`|1^#Ns{5CX^m;{YFnzRJKTmV{K;KthZOHCm^y={Hf +M15rZ8tG?_h%a}L{EB@t~T`juyEBMsw!*OXUKz%6$KxE{YLwpUwwDh+*+S7K5%}=y5IBmt&E&_?9dDS +rf&J}WWVpr*S!Ar7*UiW=P48ocp&w{q0k0)iKeDIXcNY$R((f6cKI2Tv`^8~a+f#P^SWYkKwcnDCD8CmpVtTi +UMvRLT9?3Ez#~ay@^3V&CuI@@CCw$kZ)fCsKS~|Fo}e+Eb5po4xniQw8%Hq6QutR~Ek3LwzW$#Rp{#) +yFPQt@`uaJ5kcl6_03oTl$!fFTP$`|E5O=Pi^|B1rr|J^x3fR?c=0?!5GS6&(yJpa0J^+j#Vjt82a^YLv={iRLr{ +yq`Qt{-}JWlPIj3+*0H1V>gF3I+rZe0=)rzCZid5bc6bE~HmB%r36=y-<3z`ry>3eW$)RqvngGI}Ly4WYl7s%=RV5Uyw;dXFJoxQPO+E7-TK#nB!?m4@J~PkzxNzOh3q?t8tLpJN$NEcO%*xG +QRR7AwuSQ*8pPxQ|;_zi3H_RCDZOXT!kEPgSdUjYiy?jQ&#lDlX=Xc6@|NG0S-Z#%1?rRt}qRgZGshv +Z9k8JH9@O9+O+82N8x@AM>u{m@;H1pP?-Gki^jj#Cew{xeH)~~R9?Nz_#rwQ4u)^&K}1W?sntI{4`O6Mp-1xwx!ER$FT#R7vCHQ`GZIV~?g +Xbc)zd305yBqU29vy335V-x1`FqFLRG-MMYbt*;d(@$A?@p`EZTae}FOa>l4~$v5JO0|puO=lnd_DJI +K)35&i=*Rb*KR(wFyWkf&1x$L^oxgtby^Vw3X`j6L*)8||5HbA4m)F`@S+er&19&ikQ7`70BfesMdn;oPLM`+NDGdc*QW)CXU+J?7^3l!bC;XWH%)?i9eioq3`hq;xVg2_7=a2l-fAHgrSx?_-e&%1leLi>0ys}G!+b_yWx +HfFi)yiL&w`$$auqwe)-nws^ww*NEyLj%AfZ*5e{rS-8rQ3&m7!jUu{ba7kvg%67EOrH!XQ!!;!&e-w}^Sy}G- +y-M;dqp$Uh!4O_eGgIinY4Gl;bH1ue@u%%8rY9Dd4%aNF-Z$iI6@sy{b({WXoTUU34y}4 +!Q{`{ZR-FD+^Yn%6J7k@RsY2cVs=jN6UzWAkA#zgZsoy*^fnHhBQvooroTi?Wd_H6HV{kEOx^?v6M-4 +6^n(&GKYm){(;a(?N@_B +D;&XbfW5ZhUFC*RC!V$}x|(+O +)Zvc@oHew#_o>R>Kel*2>#{MfbN<)QK3}lJ`q0SL#)CuNFBy3-BCvXS(VM-$WeeXk!}qQ({_^F4kzaM +WXxlJo%mk5ikT{V11Yo`3gD(rZR_`TDidE3DO1mfx8EvGvFo`zs +@i8DC95^1zM}mu|Jo)lEFmiYGs8YR^41fkvHq`qo)_HU- +M^r6(5S3>iqEZJFH}weO<`qWVykm*G_ax%p@Qg@7$ygJU1ygHdAFTY~R+pkRW?z~g-?((_h-SsD_Wmhj%%Wi#CExXfxgbv +&q;L7FO5AOe;l!!K>HL<-|3fIx!Jm`0F=m>pCFZu+Z;zargA8;~aTv8Nqde7$~e?B1Ap_L;S>uf5Ji? +uY-Sj$s~T&&+|KrY_fYD6y9Dm5V&>wicC=dF;ck&E{-e36UyJp7Q0alZz+c +yA#Pxp+?@1bI8;p~%I1G+N~1y@6Qd9g!y^_d}k7T)gLyhFrYYk%L^kH(^39-jgUqF5a`TA@6~_7`b?F +rUbbLc`5P$3f?Q +tLjyxWD4)XEHO~}P_$wK54klT=puB#aNG~^}7rz0;#J`;Hva`7a%9QmWjpJ~J27nQIBs6e?2c@=Uua;T$nO#1k+(*khTIpq33(eK$N1VJw +;^vQIQnld@{7C!@-pNdh5eBG3Hu@MEbNE8i?A2+uEJi(y9s+;#{NOr3waM=ALRbRKFBq~KF9-veUSGQ +`A6PM*avxUk$>cUME;TY75PWrPvjqYf02LWK_dUi2Z;P5A1LyVJQ%t93ic1k{g4ku9*BH6@=)X>kjEk +)i97}Qy~uNrhaxXTejoB;lMLr67Ir1>%mB_~;uSTv#UW+^&c?0rDd5BL +B!2i~J*(Z~&r3?vC7q?b#D~GRnP>ry=(i{*n72w;}gMUV^+G@-pP@kyjw^h`b7U7vwd_yCZKvJ`i~m@ +=?frf5!TST!TCkc?j}3$YYU9IG{>F?v6YMxhL{MdSuMz1%UMJFn1GGkw +9x-5R#PlHdMec=MgWMZ=2y!3fTI5}jCnFz)JPo;o13VLQcjPt^Kk^b0Kk_mWKk^C@Kk_OOKk^z8Kk^0 + +lPcpP9hiSWqPzhHRee#l254@55Eo4TRMy^zO>@W@kye;i=u2>-|ng@5G5!aq)*lnVdI%Y}dBmBRmU%) +juDyjJ)}-XQ$r0JTZTk*j}2`Dn~Pa&P2;LaxR93pw&w!Er#EB6uw3U+@IXzu-84EJp6W5c7|`3-WU0I +-J8#XR8(nn=7S{HCXO`qI1{92s~`xQ2c<_AF(7jNIq09S`XC^+Ao6tyvr#XducwC* +^%{|zkekrI8QsmOF9+kX;GDjN%tf5X(26T4tmv-*(LgdW|9bQ<^26iF#qhbvvoM|WFn>9?wm}CL0E&6wqpLdo@|WAirk9npM&L)C)-1Z+=$_F(O*7`koTv>bmfOHYD)pP9?#39|jNSb4_D>6ypMYnpr?o5Ni}BlWCzd&!S%_aH%(WEZvE6zN~E +Cc&40>LS`2SU%{4DiSe}k_=E2y(QDQv)D7pUd_!H&w6!AOyL!(Q5)8qBbaeXqoq&FSQEBv$)aZP@H +3q9%gfodoklvPi}yUP;U~-Wh=-3*lvA>-pZkxO^>P1Goa(=Y)&EH@{u7+i!|gWR#ecM19^C(UC%KWuA +M4^jQohb}|KW=Ao+{@@hvncJK3O53s3^w>r~Fx%eWtnaSf_emVYMVqq2F~KENxd)(Efb9D4r7wyNKuV +;<y`THV&ih0 +38l#6;on +qe759MNBra>`}8Hn-*1NjbN59E7=J&=n{mUEDA681p8P1pnZSz!<4&kK7XuM_q_ +{-Ll3^3Q}lke?IwK>oGJKk{!y{#$VVmB>Hxk41dg9>qLrAj-wOTBBkfG!*4xJ#39)UNsiw;{A;##XNZ +m%Efg<%ySMB_C&e-1_bIC^Xi2tUxmCF`D@5ak-v$&9J#oTS0aB8c{TD~$V(LSthFdF7U@^a+cuzF%sY +yCeKGIfgmQ7667#rXURdqP;r&ql7V<#kVq75B4TyCGp(q#EO)*a`HVBMG`E2B6ih1}Hl< +P$NiggM(C>Qg#VqJn*w@`@k7mybt7xyz_-GX>bUW#&Yofh-pVjjI5<>HMTG0!dDiKs+*A#ySAd_VGPl +rKRp)=9|wKPu+YYtjGH$QzK0`vx(uE>?&)p|EcpjLBvHu5UPIt4$JXNmkFUoP}3)*XbRJP&y+ +^2d;idGs;JQ&9dSaxo7-3V9C7A4e|M6^K>%g(!bj=tnL#Xe>oufV>>JxPK7qP{cZwN|e7S?1fy6TWgW +Ogj}p!5$jqSQ2s1(u}(p(t7t;`Bf>t4buel#E*IlSvF=5zi}6Fb821JuFBSPg{*2I%d>!%>jVbv2!k7ouE@i^Vz#e2h+tQT_z-Qsn3*3F1@Hnk`hi|!kc?? +&E)d_QuvH|G_?p2#bOJ&}JZ?1}snk$=THqga&B68XdD_~((QpnRXmAM%fcy^z0-ybSrv$i=!OdH+|Gu +NL+}F7{s(>u81v`=ES*(63mB(}41&$i=!DvCgLnGE9gSG`qi)INi$#7E>#Y1x{t)s&$JqWt{jx7As6e6a2kmeqI|7LAM%Hhi*;6FT~;~Dal2Vki98> +9HFB};uNL`t$QzKCAa6px9=Y0w^LK*e +bM=LY$Hlk?efemS2b_rILy%H<(=kqggLq%U8-esDflA-CMdUG-Vz`o(#XQ+^k){L}w4oSwHhyXQJR-{ +H^i(`Eg9{v}`6Q8%qlJ&WPg=Z_cK~5j%v*mKu<9lv%~TU>ayQ~D{-k?RfTdGa`sbE8~-oSU5 +T$GKj?%?e)R!Y%T3m-7OJe4dj&D@(tF7r5ky&qEpH`1rhGmYiNb-;*WVCky)-y$g5LAHj26c$SlXvrG +NW!hS>8o6mzd(#Pj%9PN_NTRPs;=krg_ax=DDXPzU+&*wRF+ssk3;x8s6kE-pXYGok8 +^`uANYIov*mu9&!1(>_TuwhbL93hN73(yc^~1z-?N`1r=QP{&5`@9Tzn(gD)V`ai~7OmeH`tP&kyFw{ +SlwXa>UP{7Uwx}vqH|>n=8+g>t7zeO)ApE=Y@@O{o(UMj{3v7Q7%s-_8$)a7MK3Og!R=Z^IXcga*N +d1~EV4ET65A8=X=l<`W!zF022!a(trwxL#MC-JP$87QAko?&zPpyZWD}u+Kb&J?69e?chZ&BLl0#-d5 +}n96X0zUvgybdOfk?xWX)Rt4n$EdH(|0KVK(clFNtJj)L399nS8~`Q`Ka^W^mL`FBTp_; +T)A|2cAcIM0*o7v~nae^`L_m@o4Jm-z2=lvk}_k10x*tYesB{TIU!@3$D93V)a3iN^OB<~{cTL(hJ@8 +9s8VilS}O3;P%!ka&QHw;yEq^p;PUeCDJ>j34^-Fynu$ID&dUW9YZ@7{#jI8*7++R`zkm!~36L{JRE* ++Jx6XXYR{%UoiJ=UA|=a(Q$^P`pJ__e(Jtcj5qzj&^By!E#s@keZ}Oxer0I8vgS0?`$yE*j3-`ZSoOz +>GtB+g&~I3{*B0`+hA$ZE6E|4;k_u)Bj +Kr!@5gV3^k`b&$IA>Nerv%o@bb*`if!QithDj2Zq|FO$25Z +CJ2i=dPs>gq?Y9#*4j%=ZDRgK99ZWX$<@Q+RNk44xJO$)a{pR_5E|hW^_FpTm7VOSV;K1?bEI$gq03^ +@5bP=jIeoLbBbU8UWa<-giV^dap{WbbHZ-yd~0s?eY&vB&m_;!zto3C1RQ^7*TuZB%#KN=%T0MaN|VUN#x_ezVmQp0jm-yQS))ugc3=63lj@}-!t7pEs_&vwfVdno<1_vTgFu +q)?pzL*ef3>&`UjZ0r%OAp&U>xl7Cc4pYjD`RT3CS%yhZu*_|CWw(xS7x1UZA3;oU9sr%D2!&dI_ +-|aggBkcLh9X~&}CNC`Fz#E-|re%aBsAGTI_T7}Q1&69u!_8}(uy?|54^7n>v?w +PmX7&xVd>q%U;g!7ec05=zt0GplM>c;>$x8W#!dqG(q0i{(j53`Yr~CBMXNm{+6Cz8RmN4(ZCuH|MbpBs6mi^yVV^eUmJfu-oJoz18X@ +CKhWZWGl14lxxV~K7@oHAP3_$?*Zg`v$=WsRIsKFY(;nF(Xbt@7}@L4@u!;@D4XFw<`aOlPqSW_Nn?WO;OE_6-%P^fXOr}p;uHz7<;e1je(qpDkw4xJops%2?@T?b3eJ8s1s0mLsZ(NtL+@NZ{d)DIUkhQmptVR5 +!|nb9`avFZbMxm@#WeJtl68By=55kU{WE75&;qw;0`>EthcRSn^m!(0QUAY>FCD5HRwfQBC{K%H!#KJIfz8wT!LM7Ai;T_>(m9D)Ae*I-(Yd@$5X1yB6UxL9>Pr +9X4lRGKsXQt-NOyFXkN>Xp{1+YNNq5fIf7dZ-a@1sX`=~Pk=X=Mm`tglVkH_@Spz%lEKWi#nnk}=YK_ +e}im2A$ZU(2z~vKZ*+b5e8hx;ZO+QsT(rvnJ_t^*W1w*8GvfgLUS-L8eUcobL*Bjtw$o!WFzXd;AY&&DMzw3JJ-`f>*5y2!qzF=sAGe|jjls#1UEm28(W&B-ss|w#o_DXmdxVt+bFTQWprcHY=N% ++QmDreS2w0F)WvNI)2DUOH--622Db_@^dK{}9bxU;&rR^&3l=QMOsDsGeqB{upV^wxn`f73T-;_deSy +wyQ@Fp7t|}fcbL0NB;MUj@+zNTTA^lWrULVVlO-ZMKo1eptO;ee`&8e)}R1E!2bwmr{_!) +*D+tB9=&@N^t4m{g>i#&1FoHt{Q2YV)k}iv7jrm&{ +(MJL(fJ(UQ}tCg>8bkjjZZ=SwcG7Z?pkN}7a%F$sUoUY^{hYtBNW9sZI$&9DC-N#JfJ$ATU>v>1<|TK +h?dmXv-KtFbC1=-J-F645Zul!{z9Vh=8$CHS(HCkv!pjTuRZ_rKGWI;6 +NhZ7Uk&BC&|P_T*>N~Q$-Qf%quw=4Ya#Nw|%-w%HGWWRgX2f*(R_0h1^55Zr^#wA66fFlA2~L;t+BRuO}2%Xmn@jG{@_)v@L$E#IO^KvYQa`cD1wq*8qF=pf2>CNy3hrhNoz +lyX}?}ze-^8otsqkbN9_2YAp?gbC+0{t9^bgpu$=K)F^9VR5|Q?Szt?th3vUVIed`3!80@=-L-Wv=Bg +1?29>z?SI80&zyO`oQBHjQz&Fh(U-W5J!Rkkxunx5Y&qx>>E2M(v$Kf*z-$AnYB~MLry|Jbr~b3g7zbM!2PvoU)(+E*sH@&5 +m~tm`!NCv=_bx#PF@n-R1>sa8EE+IcOw#j`kSSlpXvUvT_R{ZO)R@EzLXEH{blg42X$2G>O20W6KYY# +(F&muq~}SbcLX`}!O3-z|r|jzGSuSbcNY=zh@c>D0eXgEaN#e&l|M_IF#`O2ns~B-ihO@Vm~550bA1u +Hnx3!uYa{!(Sl$);jTg5~4YG!dqO8mWe!-LY}(9wW5e5{`77s>Zs|0>_jR44%Q=w*?(XO +IPSnn!uRa7jB0n7=PDC)bnc~b*Y#1h1{h!u#t5la+ihY`;pHXz4-)|8{#vFWr!7s`w)*H)*{ +v;{)yOrFy<367BL0UhWHHPYQ%EHcMuOCo<#f}u?evSmRFY$N1dg|EiC7IupD|1bd_InpRBh=<{K^fx% +x<}IX6~k%*@rB$siAKk8?~&#OJ5;4=a;!_lb1w;*PRf&_<_Al6BUc>EtVE9M#PpWF{Dl`st*zinmgb>;SeTGa{ioZ1NuM?ac-07Hs}}!c^52aq#xAQI~7gzJg^${;50s}EHX +b+FMpqPeDtKG=mat2Hipm=0Mn;4vy&B!Ptxb*&trz0Y|>{;CljQ}Rx|vI10R!P6Q+kJ$Fbm1yo8hWW> +&<;3`j+R(F(Pq%kAYCmq~g!xlUnU5C=zciIr=S#j4MvQ+weS8ZrGv(%l@pwogtlSgf%Sgcxa!n`+EqR +?Unq%+Q-?<$_Q&<{Dm0bY^RTiB`Soq|i|pcu7DmPPk99=1w)vHyA+y8A3HWmE#)8u?ziL$$6*wb&~V` +@~=u_SNdU6G|jhSYk5N6cT3Rg<|((FCnVMFZu`-LkdW$4Y_CxJ;~*t}w`q7{X +cwk@WE23C;d84oejCoNvsXqueUy6Z3VC{q~V!vYyW((8AQnQ@7CFu*9({%ELTbu=~Z^&e|8rmV*PSX}1nG +CnoRQrVJMEZL_OK~!-0_2v5Xv9QEIwdomjHeQ(-sz#{O +E}z^IoV_|vbI9jdQ6e)S{eN<*R}PO$#rcVWz2{lIrlQmMr&QarT{Km+?>8+Fr9w&+3|S-S{n5IJl?Z# +tFDKdTwuweB%&ZIOK-N&7lj>VKB2&BDzH+!CbH6;47q~7g#DhR=gIfww#l53#p`1mUi%zX7azY +Fl6K75-!xLnih@!y`b|4)wQKTOT#&3`!g-<$6L?&JUUdZ9S}e_l-_mAlJuVG~>_(v}o02|)fjZY;4LH +$Ygtu8uxoC)!7B49mg)-i@a>YMRl%_!dVk;*NjlKOFHr@PBMW{e~{&!iI(o4>apv`^b9sYoZ67ryn}4 +A*rWlpT2#0z~awqzpcAY$dE^P$Oh4G&-|~!NAb9VdfjLK-{BxUZ{hXz7G94F=!@dxJ;Zh}|9kwuc0ez +?wX2Hw0agOW0R5LbsRQ%@q>BXG16Bb>1*piv4um8Crgb +D_3ZS|ZA+rGM02}(M*v28(07C%X`>9AWV0%CtU^hTb7eac|H9|e;JMHJC!XCJ;2zjzU-Am=XL^K>L08 +{AtBi0dS&9?-tsdkXDl3Ew&Hc10!9k>O<$B>PQn{G5AjoBqUg+SJ{HV4!}d9(QKmbg +XkUvHj=}zj@Xcw0#;G^VD6qXn63$`BgKF<;2t=HyK4cpV6TcHG~IQ?7rK%J$^qL`I}W4kQFKz}FuM1X +PNFZM#{tF;=lXJn(>;OGRb?Z1di_SyJr~m43rEtur9j`kkZ;gadM|x9vtFVv>{Fz3BkNTLci%DeX +l9z*x~s3V1dnczP(jHfRdu^6xc+{*zwNc9qZ1E&bm5vt|svT5lW@H$efrSItJRk7hPR)zAe2GmRGQV0 +wr_ez)C8lwma1$V!3FxCgG9Y^1(20gKKua$JE25>vL$Hr0p=cFpYTHxAvT0ZGg@^~l*>AX~&K=%@;BN +d5sPpLZ6NH@mmp(2G7pnd?VCt|rH)=tHAP2*TOjqU}alT0aekE2YLW(LO^z!9o+$#)hZ{Q)(pw4Ugsa +=;xD*jq=)PQbtn$d@WZWy*r`g7_*8bUn#=DQz}K^&GI1>N_bgk7H~;-TO=@X-srasSH(viN=$q3bfEC +vKLjR0vdn16gZFW(R5yF0Q?QGt`O`6`pb*xUTB%Bv;{Q0UKRK-$40Yk +;3Xha4XfbmXITWIe;HYP#$H_z9fU2Z9QujQqy{>H%*nif$rr4?Rx`n52YK}H@>BY4ZIzyH*)*rFw{xa +8+m(b+{nu#bQ2+0ARf~uUT+%#eKpWOz<6h|+eNpUazZY(xad~>7UT=un<{v_NO_yCJA-%;%K-yHUiA* +GCmE`ecX_+71`L4mZ`e-X6|9#^b`WwE+MDftNT2GGTiH%1ADXWZp?PzT$!?LekLS +B$KOu!Q{{1xF>5}#U-Sh9f)Cjl++zSs9;sNd7_A&Htz^f0@dV5|~c$lZR7O)1~(>|r)>LlMIg#1YR;i +L3jW}TD+*a-5F&mjNM-)oN1aA_*rFILWt{ +Sx{Uz!JbZK;M&aoubzlz$(C+lXUO5I#uZ@8Xn5$6iru}ss?QX_dM+Rxw@~_RQgA01;m!rH32=xiQx*FI?XT- +d*$=cIKd-V~rtPL)(pI!WiS*q9{>0U5tD)mo<4219@jnFSaIaFSS{sDN(uW(%jEcp%kdqB+}gyd +5GCzKmt8OX~{mkdDS~61zSY_K-*&xHkaOekjc! +(h1kQ;s6?dy3_2Ru3wB@%#q%=2!~&1jMK5L*uWPD*AB$<$Wb+XVBmGqx#NE)qv~ +ibv00e{s!79;9kf_PJapNcfC~GpQao121&%dH{=^I6EJXq#QG-_U?6b1KIU=2ia`>)euoSOdjnU8NaP +A&<4}p50n8a8k*}cN(~gwL)9#mAG~5rP`93OAHj1XBPSTDB{lI<0C1L}NjgUwwU}2<0wgNUq!O)1xV< +Zv_sEwoP)TzqisQu1K4S?0)UNT;SdJWgT1d8V+Z6dWZ^e;&g@qzJ~Z4#sl{An^FU4Yeqw12DvJP74lu +9w(#Ha1Hl1HpgVY>6ZQYUfBK1+WS5ZMYtl=Sn0OctySh*XxUJDJEXu3ITV5o{+f`YlpVERByc$YnI41 +=&zeB67CsoX&<#SH*n=e7Xd`>D|D6#$}a1kv>sJD+w;iYX#^gR~k7p#JhVc+x`#c`)4 +c?3uKqmv8fCVkp6!)oqv4D)VsG-r-oc&fPrHLVJqmAo;K?0|n +{h8~#=X25_sVA6tDAAJZN|Of4({~(YE2S<@1Vh{*qR=PI^g7A^Ca}qPES&*mqTCR;ou>wK;Fl}Yj*Fzy3vb?%YYLs;bDTQ>VzSTek>4g0L~qKpH9YAuEp9d`Rn0=FdOI=rQ}xH*7vP=FeZakb3ype&o +h6`;k9D{0VS1ke?we@cHv8Z#Z`3$Pvcb6oMN5``LaYi^=CNY*b%?!1j-k&p)PaqT#c$ZgBo6^?&48R@ +Sjkkl*0)TyVcZIq1Iua(aNDN4b8^bN^uej~qGnF?aZb=|4;3=N#g<+m{_XLX!q=G<`RYT|W!#cPeM;U +vgvVksC*Tr#dLVHEC8Hc({=Z`hk1wI@G@Znj^;`3t7}Y>e_9K+x^U+;WSZx^NFEt5{?`LAJ0&8sc*co +dPpeuzHlowlp{?yj@hc1|6_Lh&8d5uZZI+R5B9yZrpeC4%>Gn +G0~b^ON9a+@#Y{g-8*VJBF2;k4$qoOiN9){dW1$N8JDqNDN;bkfoMui5{W*sl+syeWoOTeVc@&B$YmeRj=$^}|P!r}n`qOWz@4=5p*IszQZvQK5(V+`dr%v6Il`wn@V +CtOsxfAU69jW$5p6<8jfvKtFQTxU{JEl^7JC{@akJ{}&{_#fzVYL|U86WZYxBGhf+ld>V0$xAQ2L|%URWwK_?8d6qPMqYdEHCDbA6%}O1jvcHo+qG*K`Fwu`S#s4*b~M__!Gj0Mp+kqr(W6Jn@ +#DwI7hilqPM~l$}eI?mpf17+^KQ8)H(jLP1gzzB{ehh?9fbcUQd^UtHw6`H +kA^aK$|0aa5g77CGe7#fnz7Spu;b%g4D}-MT;opGp`yl*r2ww}~zlHGUA^ar>|1*UD-6{OwzHnU$hr0 +d%AthEqDwY#+>M-w+rJ;U_`(3welii(dMJ2pHjH8`+ezd +pUkjq4>eM8$y^{>8zMsHoHd{i%m><1{FbPmGL?9}DWEBg4l=gr^Rq9{Tj@8G!P*L~VF{cw|&0gs15mG +;qLxuvAAP;}he>5!2PLQ|h2WTtn}k0h(UpXn4>cA0Ib1KDB$h&VC>V4}s{RZz_Zz8%_g9#;0~|*RC^{ +vo!YZsewes$@)_}v~SmL{21n80P_%_q2VK=s5G3)+qdgJ9+Q?3L}P&Psbj+vlj7r}<5Rm(1)cpmb?WH +j;U~;Ts)*wGf;VCbYfh5d}4H>zl#Tu_X}eBBNC(HqZ6YNV|sMyfF7v4e_(I|;rb(46vA +q$qp1h9AJac%R3ex-F*-hxWrD>Z><{TrjT)k*Nr(p>U1DO|wQC0+WO?ey7OC37(GWor$a}=Zuq1So<& +mvDT8G{f5*;6v2v&~qXPM|OHaZ=9e|Y#%x7HpZV^b4T6XT*Wm0}-t5*Il5R +qgwY9uw=7*uCdQ-a3v?NRC38ii$Fd7Fs6Ni74v`DGDWs;sZ+&A15&l@#Hal<;*ZM%zMyGF-_5|$X=+W +RF-5~q?weD$VdPC|E<{zHxn;Cp6`D5ey#6YY-aXj{ol_$dk-HvxOV@Te&IoFt_uig7t0U=#E~PSS&%O +8AKbThM6Wh218*MAJ7b2hEZ=>1Ow8r|W5x^_JZivAty}fse`Chr>cfX#UVlss4jDDz#ulyh*dG(k;wo +QzAnC&broW-xO{2!djEo+@vZg57>-$D*P`iF32QwXqYj)%ML&a|XzHhIX0mJL=_Q?-le<%y$@L|3C`| +Z|TpGh-(aDTtu%80sSq~C6$(m6G4bD}FbkSM)2@!XtxX1%dojuPz`&ov&W$awCdhaM8~NOtnQwXsK0) +*vM}b(m5xB}UmeZ)x;PxH6cL=)araz;n+bOhb*O0E^?Sk6gaU*|j-?4K@2p`)ns6%*Ic$jiqr|zNQw_hLLjt{u$# +vA{9TgR@U;Wu0#`e*eEoi4O{cwpckL;0LLZocWp_QAJw55FNW@cK5bTK(~kwr$)0;kx$SZ@(e%dVCQc +)ZtI916p?q$GE*sD_sA-f?NCtf4ZUb^=;aOv3_;z`rj>YgG-&W3It4A2~dfq|Nq|y4<1~~c{J_Vv4g(-_FH0qN(T-cK!J)5A3jW+e-rjI +4#a-;i>j)s?-U>f6tyhv}MZ{V)&?}q(s1d?AS5-=9_P*yu6(D?%gYn!#Nxy;QO +O|&bsyM*U#R(dGl-z*@n%ZKYvt4M#j**yu3J$s{tEcaTa#VEw}Vqv}n=ecinXtXcs(m=+IDH_e$YEef +l(UEVHb0=gz&EhIz-2AE(mN(poxy^wCH3>8GCxIGH~JcKlyiSxKLN{<-)JoaE$~M{HT<&iwe}k2NfJ7 +x!2jl(Z!Tdzc4C=W033Gh+9lwJe0=uVXMzV`ef5?23>cvktixivfB$|lp&Q@_w%{9 +d2j{%^-g^Rm@R0SNz=vOdm3ImH27kW5-(SRj*5SLZ1pag9&JnvIA^(w)k=-E2n1KI-4?du+TelMPoH% +9{It==QAMd{Vu9$$mtgKAvN<~G5_#ZgH20-4RC*Toa+w=ob@@GWR9}?aDE>ZYfL}5FLdX^GBw1>!XjO +gsSbNg9WZfg>94gA};Z(r7>OPAgp!{@QR1V020fEP3eU4ajH0od_Bc>m>>Uy6zUL37X>@InrN3*ZL*! +DG|+L_-e{>Gu&0_=u?Q`$WCU7>C_N-5G~&ZxD5Qoha`R(Z!1w*E9|KW%$|F*G|AMr{sm4SEDD;1IWvx +UlQG0K@{@|(;ql65Bj`EbQj~0evs(gMWXS0i6XYwwkDbd|Ia`F3|U^%t5>g1EDH;O1M}fGxBwQ&5n!w +j56~Sn2ag~(kbl@+&e;S&qZt1CKKJ1;i2;UhX~e%zw9Ck-Ermcvn^iMwQ +JYj%!fSa8Ei{^ctF-0k}uF7`{5UG9_);E#2;lH1rA>j4QCvNF%HEisX60Eo~s&y|Ce8WA@(zMCUB5b; +!z(@>civ0g$uNI%UZI`8bPZjN7IT)dI7}a-w-|WHPM5WL?eO2r>Z`)%tT3je%GhZuu0vv`}DcTHlI$j +#eKga_?gd3?!No(PHfK?0Dj1cKOT^K$hAKn5*NriwhU6ctVzNUvlL5;$Ny`5nQ92`@;`CgMZ;Tt9AkhIsNgdPe&v!7@OzxqYbnAljqq1WY1z8rVpgE)yL`Jc^?jA7>Ci{G7cJj +9--><;Qcu^TCZIa@N;~>q<8P$oj68W0Qg}Sv4zY42gE!Q7kmd_E8{ZkgTF=0fbn` +xKiZVtkJd8|ZpOjJIBZ)viO!t*u35(BJH|7Oc@io0S&cD5w$~3o?1hy7{{8!R125!+z5;H*2G}Jou>G ++2$ZKFX;j7@=Ft*Q&q%DlYYuWv&c-8=NF%JIv{7<$?%hCqZl4picZfXq8pAbWH#to&#(;lZU50(oYv@ +u3cHO6QNezx~Q{u!P;zzw*t1x$b&`T)2kPhjJ<@iF`_aD@E7nLCKyU>r&qhmErt2i67XGjK4eIIL80$ +OjHj521xAjKlb$ls#^kzySj`slO)1=>1mx@(-H`7_I&m;9W*nYp9Of|&vl#~r{}|(M`|GzqfS+YS$Un>V0^su +e^UssZu0&aK+}D(za(mr5Cjt&D@vXT||GN$RtKZPJ=4(e!eLo?c=c@)(B~7>B>J9L-H0Dgc +E|8mx&iUOiF2{RjL~{s#;g&hlJTKATk>R;xJtgX +JjinZdLu)rSLyh%wkd-_LXHx5EFvr~G!@3ZAq7UH0IE4~|GrPu~Mrp^wmk?CflTgI2zp;t96UDgPX+z +HxB3r#8lbKEJ5yv)^bm{sH{peQazj_&|2MUBq69Wfm=3MAN5Fuf?G;JR0K(25i#C>}KdQ<4}*CX=vOE +I_2l*?_}FaAt50&X3Q9}SS$p;oSB(PQ>RX)B}-SDy!F;wLLLA+Uks{;p^L?29qrpPjQ~X!A=G==1kB(Ydo{_cOnnU4JMjD4+!k7MxzTY#Gg)H}CA +@k3UWmCr%V}=e$zL5^#WC08hXMJp6G%4FY}`at|L3T|qqpc_h9=j0D~RmwE=H|I3%4(UsPJa5C$zAtN +K>@N>^S_bcFh^wCGfgicEwx_9qRGiJ;Xe1Ol8wH=8E;064$=8N0_Tj&FH1p45w@9^2$+ynWua)tFb=o +aLkWyv^q?%a=|rz1v;pjoqK2|fS^z&~Wj5K2f$5Z{9bz(G#nAoE`2Vu-IH_ka^y$PjST$`)cO9M87oA +6>e1>9cX;#-+$1t_FU{CMIMF^=5nrxKMMRHf@^F-_UTT(aV7IFl80J(xa +2Jd8jLlKY3ZFc`cz|VLu>C>lAC#K;7=<^$IydiJ^eNZ=K{SY`LB_+}Q_unsM%WO6a_(5yX9RCAu=mG2 +xzDH~d`9Z7=J%A2CCvaWZV21ryzRrT+;9x!T-L!l6?kdIC^J(2&__3}w{*iRWM7{`kn772S1@0;q`oR +1qmNy{-@KK<#JV(|OVMjqz*#8Y1Hju~T5kAoC^@{UAYo=2b+qNg!N6uo{cky=%6<^<@x&0dvzajpC{b +zm5!)NFlwtxlvhR(8_idZ5oElt=~)^*|EU~3@XQVyX9&`aT)2?fW(hy79e_Fty; +B8$g|X8OyDhR^N5@988RdL2W&HoTDENYufSdlzt%5mV;F60(-==M4F-caA9&$-;0AahTdunYLL^-s1Fwe=t10N;uD1@;ee0DlDE2^u5k1nt2GS>FX8 +@EJBAwib4ba|+SdV4SZt>;itsZew!Zki2Vc5%$Zv1?UVMA?qwhzjBOF#k_)FZGVkn$GBPX-GlJ&oLB? +*JzSF?6H*R4mmM=^TvS@quhBDRR4PhzwQtM+UE^&_bsL~=m20#O@VmZd-5hmWdM)6eqi)H!4N$i&)op +8a+g9CnP`AdU#^W +Y*~#*fWkf&$7M!j_uO-Y?nUc_}s*??b93s<2MX=e^e)qH%?7B>g$uC7K%RK0JWC;@oA!uek6+CE!O{` +k@at98(zq3)@b|b@?H~JKUZV^UwNKpo4cRw=IgjHdZy?%qR)l?3Too0#eQ;zsQjm9^?EzNPS2Y+FKPM +me8>{OpUDO$;{NzEbuf6xd|DFfX1pe#fvCpVze!Qq-=BYKnr%n=$IqvJ5K7 +7R2_m%e#Xyp<8A@muec>76Rnd|_c!ue|w$GP#^`!K+N#K3?B{%7mTG*JUXJs354xjyFKj{Ewa=(h=Zt +nl^P(BpwTqDO^ZC;Ehsy(dmBlk3BebH1NGd-m+7wf$}x&ti+%+LIk4YKf@lqK>FtAH7%fk014uN4?sk +L9Y)zCdgyh8$SCW*GK%L?UnlX-vJ9ovD%*~obIc~o;h`#=Kfe02FPQ)pFBpZJ=0#QJo@$N#QK~kC9Pb +!GF}S@)`py8i-CR{YO3fjpeDTO<%}kKhR4qnO<`CD?)CNBpcCkKYS%|xF>~h3^f`0pJgx1$0tVDgKno +1i*U@WotNlLonNSafJfg>h-o|{jzcg#CZ?2e^Dqs-uSbn+p?Z3W%FWA2a4F2z27%lo;=oO)+iQ2f7N7 +Tj9UqNp|^tjaZXOFu_taYzO9tXZ(m;Z>#7A;zov}Vnkc<3VXeZ=R$8?b-|*zPM#6@5eWt58>Us*%M4tfl>0-6MZr11o +`b>+}KG_@<1`HvOyzUb`5B*$M|7Cs#Jw(i}?Xzfmcc4>KJ)`}*yrRc~UflwQAzSS=YQ}HuAJ$*IcyZD +zue=hE-Vow(z#;Q$dEX%504-pTunzVRb*j@NO*nh +|Z=mA;8+S;De4@W+xlSdA)PWZ{=#KE<_vc}ejPeGjnHC@mc`4#J8Z7tou7bbZFUc9v|j;fD-OsBp+a9 +JLwUY5tz*^goQdi_6NzlB^sK0f}d=;&xscSUbHH8qu9c;N++-vK6R3#6?Ce?XhlRiDv^#%E~D{K4X00 +4a~AnfK6!e`e5$qlbtZh<^qR8q`a#*UtnV+1c5Xo_gx3cOoJp=$U7p5q03aygX66khu?NjxFdf?`;G< +{CzS8uF1#kW7%pn{_D}B$0MxwA`gVGM_&fLC62Rb^ytw7?}lp4{=AWXT;5x$;MGw|6F=~0`eSX5KaQi +9h`RCc;loA!6);SmJXz2k_CWSXqzuR!vXlq*6Bj2=oH&7HIg?`~{IDB2e^WVC2hfUXpP!PF@-})rsL? +<2$Rh+^pufg`xwc;bx=T5f_lU?X`>OLy&*%7B>o`9@srY`f(bVW5Yy7wdVmr_qwOq&`YU$`}KtHs71n +>ZM0p|f0&;oWFHWP5}*s)`&(!3sp(ElY%mLwUC#(0@?gU^6r!h{JF78XViJn(>kP1gNne+b7xZlt}ye +Tf@3Y%nUmT%kEb*gx2x`Sa&Ljq9N%51$X4gWkfEPd+Jn%+L!Qivb$Q8ZY8y^i&YHGY)I6VC~xV{cBj- +ngsmOlqpk$ZNR;%vaSezf!4qqHhKU4{njg7Tda?D*wzU9UmqMY2425@J*{24R@91N8&PwTJ;keBTda? +Dr2mC10bdz|L-%A~5->o<(BnY;Y4hgI@;W5J}8z8rY- +Prq@)XL)XdZGs*FR{8sm@@-~XHpv$oj#YCfGu5f +9=X5%kr-OUR3VEMC5|Sj7Fa$JWN&K=w7zkNtwMTjqR*KHF^H_?b}7HChy0A-A2p>e!!k;VZs0KvA0B;@;pYYtZ77E@cfF}pwscS72ynO(l@XWP8C=n4p>x3t+wNj7NiRZ0dryi +*jPg`rPL`7$%P1H?Ko|2h5DScqbojoH$bjj&sCyh%@pD-}wk-y&4BQivnIW;MLT++lz>B$2_W+rEbL= +U?0`lzJL%;dDECeGCH1=2GIhD@81-am6}N^)9KW{eYr|~MuGQ}k?@9Bi3+a+Q&e~@av)o +{q#J)|(0lwo8XN}J0G9s}Q<gSpsTYA!cdnybx!;)jkw-_wNmQqW(rP5MuQLI5$oi)O$x5 +il$tr^xFYrfTBEw+|g%dM5xYO7KdRHQ44DAE_j6(tsB6y+4<7a59*i%N^iizHZF!9}4ATO>mlTf`@o2KYK7#z13`G1#axh8iP`kw(2S));3 +@FeVyPj2Xr(W2LdmSZ%B^DyBeFkSW-tGliNWOpzwNDb^HcN-!mwQcM}9EK`mt*OYH6Fd0lXQ?aSURLX +oQH&tkO6Id9e@+YEDUl^-$DTVoz#hl7zUKKF6Y|O6`=2#i?tb(~##eAz_&IK~>f|+}v%)dzHU@Y@6fw +`E%e9U4_<}xn}%*D16re+z_vw|sF#Wby9ss=J$gPF3SOxsANZmga0E)P%+@g~KdWzV%2*lqR_dzrn$U +S+Sb2Recsp^ivLtRumZ;>dF3Itm;%M~S1%QQ@d^)Hnj2!Ol=;q%+o;;7oC5Idh!_PMfpDS>~*8Ryk{& +fv#Xzs4LPH>q>B?xUyWit^$|MRpKghRk*5LHLgH+ushTp>5g?LxKrF&?p$|)+vYBDm$@t4Rqh&hpeNW +9>WTEkdJ;S-o-9wUr@&+Llz7TK6`m?jjVI6>><#rsdSkr_-V|?^H`iO>wRuatW!?&JmAA&LaA1s=k^~ +xp4WWieL#!dekYdO(6*OQHuXq%k}?Pt?(DYyDDAPF2x<>*103xdUu>V(VgMWap$`Y?qYYTyWCyru68S)Adk +)y;n92IJc*tRPmU+wWAGGvN8;%@dkNy-UzSW8|O{*W_WYF`Cfy!*jwr?_f~qVy)f1-lvrW2gA +6)Dgh6kJGb9=^3^|5;PGgDrOuqD#571FZ>N@VMkW6U=ijK#)MV>#O)K0m01$hk_G^7*h~d +f#<(+3HlWG={Pyrmzh9EC{SdoIQbQn!;4gV!GxsWufWCtm~z$?d7cRmG=Jy-}U?Wm&gAAP)h>@6aWAK +2mt$eR#W>LvixVj0001n0RS5S003}la4%nWWo~3|axY|Qb98KJVlQ_#G%jU$W$e9sd=%x?I6j-*O*S` +Xxh#ZBV1-4|2u9aS;(}(#uFT3tKtNGJz-US- +!1srtY|^#uV6b)vQI|U+P>^`o#B27yi&$yzoaqdO~qN@|d$U_#@}{f8_K|o9X=F6OTSNAu}`06|19di +2UM<%dT#U|E1(@Y#Ao|GmCdm-=-9#?T2{Mh*vn1tASk|0>|N9cW0BurY^oCI$LfB3JmMXcNm&j9<#ADU?|3KeD*g{C@Z-Y=J*kZpn>Gwab&C +P8*@kwX=(TpQ-xwjO8OA1=(2!vR;WH$2&J8*<3&a%#6+1x3P{o#z3YIP#8Q`F5z{&qYv0WnJ)kx$=;5 +w;Xm{IFCuKh9d*>gshdZ4Cxg4)5C(Gj|qgWUtzZJELW~nYZpv{yQRtN8dsAp2qmGFE~n8}iwkZzZiD> +W8@B(~u~=v&8(B&a%El;?AQ76?6-r!9azE +V1pCb&&QVWD#Gi*H-ixhRh&O*y~Di@6n>8aB_)3v6_b40#7F+2u +ZM2xM;{ATM!^;}A?4htgY=_L+v@fXkb{y&`=*bamWe_Gf7}Q>D_;W7E=5vvs!c1-eG-P3|TGIgDE;I^ +=;#?(^@91s!fmpj<*5GIRVcxWNvM~QntQ5xr*2s8}l>!9gy*O`4Mr=9y9h2ru4@>i;x$_#fZ|5*H`#uEJT-ZG9RQUc02u%yLU!)=6jQ^EHJ3<*R +)#7f0F3W76y9M)%lS7LE`W+RA02kGX6%{a@n{X%Ss{ru++|BpQBHWyFv|}T2Z7|3n$lA5efl%sD@Lme +*E?#|rU0(fIsy;Z(jeEhh0OsW(s9L*w2u)6~QMXc~RFC#vCce#eP47A^53iHQY=ym&JOAO{u_pFqfy@ +rdtlMyWeh`YNV+7dKxk!CCnuJ(Knggs=X8WPUIQ3DN6L$EG&==MYt!5YwE4-`GWYPNxZq7M;Z%&8y3U +F_#C&pE_1P8Fp`(zVfYQ^T>V>s48Q#I6Cb<4O0?2jEsnl)PvzwIjP0?;f!QI<;B?wVmSY*)F2POq__rGNQK*L2JlOd$lNC&9#q9b=;~d(WLUjPjxmA-8XJ +@oEf5?E%6X!yqeb*5*TI_a0owC4giXX@%1Sp|;a744ASdiwN~~P=VPvY1)gp(}7NOQQ^^0xb_TWHXN- +yQ!{3If@g~IOc%N#(`m}|XP&7IGu_QIeakbocVj5?_Yk=r$J69{8;~(H{=xwc>~jJzqcYCO5+A~l*Hs +|H!sQA0n=8Q2I1a%334=R1*8aGi;LB$?+}Jc_1r)J_L!_70x%YTO0R+c@%UO$zm*Lpmjx81~fzB=KLT +G7nw`hx_hQYiD;j>(GU>ZZ#2k$aL=2zM|bS^L)-Lcm>>NXha(s&5>$#lb^w$rSyPKA!CWa$?5Vz@P(nebhLI4!JV)ay834mcviP}9{87r}U+V2O_xQ9a7o3Lzn +s?BgrKNv3)+>S&t-ERi)?uzUZsB1$Zqg0bY74Oo1o)O1XS9M`sFbzmYa-+CT%d^Dle%NGxZ0u%ejDQd +-HuEyK@QKq*m|bm5ie=Bh>{i}4O=h2zc=NAThKkkc7=LmA#iz+?LlPv634R$%*h>kDt;8D|gNaBEoaf +>+AgPOR1+)ef1aWB?_F9R0=jhfbC}xu2igCW<7}RJnc(|l;vkCVp) +Egk|rDVvZuN9E8PtH0LoV)?hrd^F|^-8;ex8$2wmo!3$O+lNp0i;Ztz{m%~7o!eGYzTi!;;3>4=P +~s{o@l~wE@zZ@uoDnZCfk*?uV;3TW0`2g**qnWFCI;(Au-9)uEdjRDRls%@(R_5B;q$>DVFQ73IP;;? +I1Ru>?bY71(FR)y1U!5jvW(#cFfr?Kb~G2VS3JUS+^`P>=$Qk7|Cr?(8NAL9#K9SU@GI@1B%C~GXydo +kk0Jn6MiV&nG)~}ca&dzkehDjqy2=39iozDJ;rQ}H#HfG52zQ-dZ87}p0&vLT=}9HuyyItv+_gIpmLR +r;!sj6@8!Z8IeLSY;k{Ag3!*gvZ04LfPR>EGMGcjs#!R1Hcz=k +J0}aDxZxkn2fFY*by!*_}nKYwp9k%=%qSXlp)%0#sbw71|!>g%~+hMNV$M081n3)JjzE90K|A55AD?c +c@@X!0NK-VKWA$9Q5{*x4{U;*6N00)!bG(&Nf|L-%BgLK=(TIzb^0g}QGB?L#AUA{PA-b`noO@vh^Xc +uf?9yw7V?(!k>4tqiPXu@02a5&>;Q}-=+O4NfWOD4LRnfdyt4-2{c6gnQ4)zrP6(NiYFVJ|6zxx#3pp +m|Lk-*Y)Dq8TrN6A!!#*(KKKL3Uf)<(8H@NE>Vr@cW;mo4F2&eDtrZrcP%;oIGR+k;d48?vI;s_BSxctZR_zy8@#k09^5-Y9XX!PZO~V!=KCDpoY ++E{>2>bn|29P_8cxfd70ct*^!r_0#M5kn2UijONVKOlDp$2BYjJ5?^9M*C*3q1rJMr_ +$j_PAz`OQ?8#ook!4JlWmnINQ{9+H`iDGzYt6_4WsRto^C!}h}$75RdZIoqF9z67ZR~mpisdXAOhr(a +xW_Z&sOrXX@;Ki+2Mj#y6L!$cx@k?cpCPoWlR8J61d~`{f~K0RpJD +(Ky;WoP!)xUsv2rg#TIRTE$eI0S7X~_TcjThzR`ExknnbB9cX2>Rst{nE*o$!ireQcwR=>89)Gd2@N| +$K;Jp;ePm>lneKGBN56h+6Bb{7ez~>?|1rOkw3`!K<6ByRYJt!V~Tvg9v1o)^Hw~ +rCq>(??AAGC0K$xdIiceafuPu0~c#gq@)LtAH2h|nIdy~P&{zAfNWrjOr@ml#GpeI+Z@Tqn8e=N4

    oQke5T~y^Zn)SUt?ok{bXbKyr%iAi6X}`<0WX&Tu@tgSg~7Aq?2!;ynyJe1U+{Vu5 +DPO)#{>^C3cUXtSJr_or)j!yBNwf{+bWD;L)R+p1B@f{c5DPiqHok;ApBLjmjCv?U%)2WG@tngygZztE-(msz2mRZ+IgxtJ|gd-5i#+lEPbz +@XWaz7?M~_3dMV|62qUWJwnAfKF)^Z3_2iet;LE9}?SAgiyT=I||r$80k!61mp|A-8Hb6pN_mm$$ONe +DB%IV-mRVOL9SsgkfQwJLBTuaoG!6?#2qE-IjJ-aeji+JDN!4o$cU&>ii%QHos*blV~0{u0Nf*pkGzUg+kZRo9@QcR#Oijf>2{R28k6Fs(y){jOKk_HN33qhsrTlA{04jvtWvJ}dz_OCT+9ZxI7V +`SyIDj~Iu|AeX|o#$&Yl1}kZ*l(Jk0QNm;>b}vGI@W9e;Lg{9G2t@dt7a7K+sw13^ZjQFYU(>Yc#h4k +%I2QruaJ+wAnKo0I%TGxoU!`@BurB35q}t6OrK0dTQO5J9!sNX+80ejlA6;%v41$nuq3by3+Oi@t6E_ +wwsav{Fv(o$FzTj>L}ofT++u(H=t1o@LI3Oek@}wuo%w2n=&OS>x(8OOY&YK##KoV$#?{)!ElYZz|uw7;vJ6f5;WYiA=aCRIv!F_a6ukuGDkZp*JFd@vD2~aZ$P*V_4OAu#}4CxLY0X_=&KEcnn`#q65orTPM-td1eEczPg(kahMozZU2->y3505WQ0k+QAL}!3u-}3>*PGM_9YCfY^5ttxYDNOx-r1}k+%)RFdw0gA1clBhYQE~LxmlFwO9ioU`xiOZT0q*k-3t9kWPg+lp9s5i-o*$e~%TB?zy>iU3?3MU|bwYqUkf5*z-^&^U;%AUQft)E +=G(lklF4SWsA@ez4AJv*GVL9q=?SyH-|v480%WsrTm_+yyE0DKDIO9X%T;Vob-_($n3@$ +U8F6(3#z+zxlLk?0obdHlrC{0;FRGaW<_i+nIGP8f#g__B0~ZT7Kmq^?t +Xs?4@}*%^OVhd1XNJq=$y$9>|IZ=iaF=qGShcl&@`$C~rAeL#2r=CEoYg@;BFhW}qg`6YL>@ +hdx`kBz8mWpV1Q5O7Xaq@(W(4=}B3w$yb24(6&?QWGW@5sTW@vxN!d0Z*cnf4vr)4?ioR4AtL1^td9T +~^t?D}y(Yl;j*PT~^%NFcPp$t5g5J4JZtY=9j_U|E2`@(@A=u1;{Yte!QLv;f=e-lIK)ZC{<0fS`X5f +t`B;ZmuA#=vso!00`C~f1&DryLL}(PeTsfPNI>X#6hkAv8s%;YI=Y@3Z#n!CDIdv@CkdV2(`_dWp$gO +6&OS`5d627K$?yCks3VW7po7Jc$$MK82M@t1wd#ia3$6`gH4dwB34^u{c2LQ1z?93(4m!=)fyq-EBLO>q9Vs%o959&GrXm%JXFA_rA4v^RMoJ-4@u2e2a<=|~-aF>qyy`dNe$n=NO{Gzvk$m^B)p% +{5`#z;<<_Tb>ekiK*PfHoVE2a)J}GZ_L;*^rK`vZ%#2U^ykW9r;=9Ti9%@qq1(r9yT+pYqJfq2h(zv+ +E`;`6iQE?K-l_1u1R2D=R9uj+-z1RA{l|+)JLuA(NTV8sgoaXnbYaD*~%0L@_2e$SzcN&$=E6zb+X4? +dI}KLaJDr9!lqEMXT>S)T}Ql^kp}@3IyZwn^NzrK7))`WjQjxtIZxS(*oh)`+WeHwU*BVu)XzsvWtNV +j$4}0YI=xnCZDo8EN!oZAg{Lt%hS<+tvbj0li6-hqed(#L!Yni%&`5_2Bs*SHQOkdS{5zPZ2#5k8;ev2Qlkb;HIMW-W_9|0PIBqLnZ?gi3=sHPkEm +o{zDtwaR?Rwnc|yJ+6pz<3NC;V-U6$k(P1!V?hSAMniJ>ycROvfZ4LOI|sx`jGnIDG$66|oBGq*Un+F;)pH>Z$386Jy`iH*?N>bsB!209lO!x4g51U3cYOIyP}BgXPCM)iV%=VhGl@6}EAZcd4{V~4vh+My|lj%z +R4dGE)A6qlx+GbvW}TvG63O-_aV>IJrSA94m?;ihfJx8_t-|M5+GY1@5dC3VsdCJ{%K{jDACBTk7GDu +LXgpj*z*-U*RPP@6J~3^;84O@d~giJ0rjRPCmFNo#OFl&!|H(6Onw_3lDFX7`Mk9+OmNZY_dgPt&rY5 +L2Q~d~#Ay08i|~ySq_b24S;#LR(Bt=8v98^*HCwYM{c7k(fs(8)%o6ktZ9;O4lHFp&DcQdvO5fLqlEvCVL#WqDLk~ig;jnE0>0v|BgQRr* +wgpjMezp!z#o|7s-zXPbhi!wNo14n>(0np@yf!@v&*J2);9>V6wFcSte#=txUycfR9@G$e#r03@=E@N +S3!~LZTQg093B<&!A*L417`69?#uX8JG{exc2gkGiCYJRaNSd{1n5aFU$odz|`U$X-Yqg2D(-f!G4#$ +M!I-d6Dt*32Uo)6ph(Ff>#64a(Gm=nuOO3ICpS1Xzwi$W^}?JbBRK3BUog7UeN0*m-d%=cGV*iNr&u} +Q23fl#VNt3|Kq+ZMo+e8f8wzre{xVgV=wi(P}oZigRz7>la!(M$+I8>9?{$nDw<@Vgcl{}mVM{K~iBO +2l#hIPG^2aEp$}y`;n>kbvc?;-_n4Gf~C+^IAHNwf8g8G26; +he#k>%UnT?`bTa@bgUD2iP}gMLlFk*zP~iD4-cJy`t>M`F62tKun3jg$X8mz=v%9(BoPD&{8Mb23~XqP{f|J6|ozcWbv~n^N +Mo1irCQrJ1(=xn9LSVldJ-3N!Ig%4I%>Ue`3|1Hocsj&qVA2Fucpl%?SBTlR2 +x7%A@D1dWp@{QgFe^L_GgO+-XKm??zRHt|)$7SoZp0VQ{-(O+tEoT4{V&?}|`4*F~VGt)+DI}HXR!@T%XpoDW*CVb7S +;s;aO}?K%x!c9ruv3tlH2ybQX0h_7aiH(JvC`C^8*zj=-*Z4wT*jlk_@nMo;_ln@|l`^VZ+!-*s}%Zn^8YTz}Rqvu$ +#HG2KuBEwc$OGzIy{v~+C#E2w))1@WuPLybRm)jr&ntN*Z)$Wxev +299Qcl}w5CNa-EAZBr@~Rd*&~eG+Tq<~w3V1H__8bQ-B^b+g(%^XVs*A4oa7#ERY6R3u%QVzelCsF5t +?8a0xp3{|@=!RLG^U!j!gV{Pg#d*n}8eEruc5yVIZolON|C1M?QS3zVA@;YpLh}xYjRt^JL>}nF~03Y89t@P9LeSiUSe5_eq-`P*k!B +94ZzUrrE$8u&sPEi-d^cow^-FDZt)nLWu4V+OOvEaU6gwhSiz|NFGRJD3}%Ujqrwowm>Kd4Q+* +o9sb5~Wh01Oi4)e`i*epX@=vb&Pw`(Lr_X&X6fiG$Wn +u|y9%-dke-8q@RSQ`)V|yvo@Up|tXysTPa3=90NT{ELEivZlzbiqitMIQh|MhDVjZZ$Z-b=AAxXE&2y +I4F~U%>qgxyNDYf+wwxznx3Azg^D +B`J6ENeKP_$SKx*?H8R5$BNoM)57XyqHFXN;=@n?PR&j8T6?mVhmvg`3p4kkx|eF7|$>=e+;;_p;xw@ +4Y&hMZ|&#EWUj+XOwQY12{DtoBNjXy2QeL3`)&hp^7p3;Xhyn(*l@+YN2+-dTfE7|N!b!P?6b?^B0D! +kMTX9Byz~x^7UUNM3Cao-mV2DcqU=%szD>EEt{((#xiyDq&|KM_jY&hEfL91i0G=drGIRm)pR;^+h))2`SstR(?^V5-m7E}gfpM- +AhR>J+Z!&hdD4^|qt0(`Q={WczX?kx)oB0a$u*hCk(Sqcp +Ef`2^45n!F2p8;cfdY|;4#?uE;dcsEH%jDiX#fdy1E$>W5ICD%r6*FH7gs0nK!936l1+j$5F +YzFPH?CDke{7bpSGETSIfnX96>b7;fyjE=qDDGQZfMIMZ;14HnGlE1LLVS+0}Edl`Y(EzE|IZMvbIOp +4)&Xh2}cIBwJt+^+Q+Ew{3EfER~%F{a}DHb7WvxhpSK7OsJFx)_m04#G`9ZeT;B{IHkcZ8#{x0p^*MH +rcEW(mJZ@TFxhZ?{gx2RvnzP)%1Si37#K?HpRusH1u$DPY=PIY*d=o`o7k=50~@(#z@(>0L>i*KogoO +}d$KieT3ncZ@Bp4Kos-S5A;%X%#eQ|=1z;8}tWkfmb{uBeH8o-HB5FSXf)qd7*XNKVtd$5{=fH~w$H& +mL_!c*?L4vsaBanwD^p@?^GyS2t7X&?7n+r*6(Jtp}EVSou0G#TT7A2u)zCSiNIXvkV#H;2*K&TE2!T +#Er9-_>)u-sq!_z(^(<8ZHj1?Fn;-*7r$&pbp3y{e6CoE3|)TNkuv?&Cr?-*>=U>?ZetX@4W$zRqHbr +D8uaYcUwgT+bgH_~Siza9f(pzhaCrcFkWAL0w{(nP4ps5cyr;?qs{w<4IiK4KK^Mb8ashUT>}q&C4>j_u!!Ns+TNsup@$8RB1%a +kx|TFTY+In%+S(up`43o>fhgCJIC2C8w#{e|tz8scYQ=0x=uNW_MCa>WI_}Sx&D7fS@F0^E94N@|ZzA +c^{OSc$Fa;Rk^K=?;GMFZXi_F@2fX3~WhGGMBYKO&KtDdtd{nc~n3Xl?ul7e=}j%734I0dcRElB@ALJ{rmX|nw)-b-y9@TKX3* +rvTTz3Kw77fR1e@-AW8Mun|=ozj3cuG7{akETz0z+*8r6Ke^HS!iG7sI#Lvwg&91ktxpymViE(p8b_wnNKRaA0NMLG$s7d(@WgZ!!tgIdZ-wvWPAxl;hP1afS$CsuV3nWeRA1^f8w#ue +AGdHrw?P?OKPNJ+Z@@btfEdA@uDbu}%=`4Of}%gfpKurIO3H?qvX9FMK>FW(@RU?l2rTeS=nwH&{BBe +BMO6QyXa=TRAUi7h8-hQfDeX*c?Wn60+q82biMijiYT2P%%C^P!+MT$rUNBL|SEw0txIRG7ijV~%i`oV ++1w_3+DiLlo^D{t+=mc6hXJxUB!};F%AS3teoXt=%aO`Nx!y3#;So_%r565?MmUiaR67lao5E)9>2)< +v972hoV2#?S-5laCKHRzZydLSXj;OG!eRy40yU`q@!M*q=oXxh-u|&m|@3IB&-Fp|6E9T|q{(mj^f+h +%?(r9|q4M))*_$aXyFlYM32&d0Knbp_g2}()6O)oTKgq>Gmr_U6Ki2zcUXwK*B77XkeBRun6~G9fo=)_P{)mNkhL!C1t}S;RSJs*`> +IRY1aANGLauR}d>|NH!f`ehvib_Xe4f)3NUTJF(j6IJNs2G|_^(lR+j-8@~oKlK)sq=$6V0q$M{TrN2 +Wf@s+MGp`GiXhDV?V?kv6b7|ocYSF)qAX^)-)vWOW5-VBZ5tVAa+n}QKZH&Z8uKowFgX`;db-+Qn!{+ +zAy?x>stduP8U({bSC^=4qbP<;E_*tNYVF}%bNnWX@EP+^S9V}_wW+}k)FJL+d3;gRXUNT5>h(rW+H0 +o7g_`Fh_-9u>x@n4|2f*ZBE0bQ2^9K%&#wKC*dVlP*=+O7h)Ge`7Q0nj4bJ&z^=UE&Uv&{(^$4cU^9z +ra9BNnOJVzl?=U}u^q!P9kmneIRXV#0_3)GB>(~oR{}haKIM5avy#MF7|p15@LqxuWe5+p^7+VCC&?c +JgailGB5-Jn0f%$B=8+3AGwGNeS{>m)a?&N2Y{Sw18eyx22FG*P8jg(+GckGTFu=7_4S}rvbER6mN(2 +p~Uw*Bxr*&UG!;i)XXOTu`_geINnwE;%#Ew4F(mYvwr<>PMX~?2CihIsDvOEjgJJbV^Kiw8!O@`xpuX +0&^ygKP?h!&KgNFA-6B-^y2%Ibfb%vh)XHw+~6z-8yim&&Vh_<<2Ji)=IzpWo_5b4abVKSB;*{#AAS1?XAk+m)Qj8~Ze2#tDAL+P0{Ue|q%Ue^*Q8bsf +xUn{P=-{ck7)pD5dmyJ#>NJ~_)Us6j8gJS$y&nXV6nj~!Q(EJ-9jhwG$Sc +~3UX!|$YUfm4kRU2cPF!jqkPhJ>MhPzwO>C3P5^aiTvcZIW4dNlbqiY~owDjO*{RVB5kbcnY@gv3U)> +?5uX?G#RhPy;C-R0l*B>WDPQg0-k}rj2x!En2Q0UI*9N29p^f6hCJEqbI3Ru!F+g!kscfjjH7(C5Bq7 +=vbSR!)<-_aLO-Rl%sPugu$~}e5K!_K-bVu#;K*#@sZQ;^)&y&ku!|txt^j2n)Qj%XyimD1)_0EN%bw +uaChM)|xTs?|{`nh0;AZJF`Tt1Y32 +Grakax}4&(&ruGq>*~C@uB(;td2>w4IB{K`xK1{Cx|ch0c0rxDWkZL`(ky=SUVqN^;2G#xQeIZ@137% +x$XdjKWj34NyoJ)(J(lvw8$bo{n9HIJS0kBrqdxWUZFZn)gmTD-=V}-Cj9=t#Y1P|KFJZx +gG%Q|%>Es6t+JF9>0q^-tM;OfNr+ps{KoWLe`KI%0BFg~@(9WjKn*YUKv*@>PT7;$3Shh9b?x98M49X +wpBybD<;HYa0$J^YtU~A>G}CL(2X+C7Pkn*5ihu=Kw<%yc*_V%14;-BL&S5w2_ck`|!)SK2W#1Ve-C4 +|Ly-~Plr5_I%ySh$iOta_7attc!!WeS=|Xj#QIOJIS# +Hh9l=s?@-cZW=H!y$dM$gE24z^rrUa>>YV5o20&Pjjx8NV8M^j!eaWw&E_7My@S0Nl^vnWbTc57Q!&# +Svb1w?_?cLS!-etskhgs5i_cG(%i^cK&ZHxEMYpSBV2S5_L%qOpW9Hktyt1C01X0!xXxnJuV+&S0 +jKMfNS{V`7X|QKnz&*kiC6(Bs950l@vuC^5`TIgkuSWzc%kBB1e$Wdg%$s^)l_r^UU7WMUrSdj2wsET<+EY|)8rN|=!^@Q +3h5DI1KtF;%7(nVN{?(ORiv8b;*L~mstsI1XD$`JZ`L)^>j4Z1b?D4uXQ^=m8Zo*dVhB5-Gjn~vg|#h +f9=9!)IZ{~VzO{0^_4Ts(@sk}t&XdVL{cw&6Jbg8|kesmG#Hr(g-N-O50T71)Z3r(3$(1LoawadTkIZ +hvvZ-E5GZ+?HgUY@TZCl+2=%0j!x>J<=h+956Qr+}r%Dh2NN3rKB|CA?Q8tS +^?pDVXW1!VWy@;V3T;TL@$OXgrli}v7Qo!b?f^rMdcT)7qdxWVp9#WcRwn#j1XhQLZ9s`P-vmSol`!b +Dwz>4HZ!bNd?{hw{p0Ec(gIs7lVk>eV0{+#N7078T5+{G~Sb&Wfi>B2@o)4A|E|JPV6*`K-zXf`0NR4 +KU4R!<=lw~+Jb&EX#(*Vzl0G^jPLvs9p2#Dc9ttTD8aB7xZ+|gP8OmK*H$6;J%w5?XBW;v0mz8NO1v% +Wyd)J7jpz{96*qqE+m3=KVH>#Vnn6(K^Gg?9C5j~p_Gre?9iEJ#z2e62Pog$lF&N{87uXl^_l00~C+v +;+Pf%R>tw6imlEWgaCB5iv>G@ZMFJg;p(MMHunX60TtFQ<$x9Z-*^D9M4GdO2j5&CNxu*{>eC(_bG+4(9Vf3Ixnb|B{lDwQG@Zs`WOsD?N>_t! +Y*LV_y564dSQ*0W-mWe|a}vOT07}^M_`@PMBebas}{ghrc6G9v&L}b--UV*si}Avj-6y`okZTfFE82U +fS`83c*vupAP(q2HQHyGF(Dr7PrB>;8ite(B^t(;xj?3xc- +T(%<`wn2`e}pTC*t*=vCJga-JlwaM4>I!hVXzG@H1NRC6IwAB1Ns>JhtIXHT7yrGC#A_dvV&ZU#g~c4 +9eZ_HmFW;ygz#u9sMQlkW%~wuR<>hIoCb_LD;(QyQT>ZN{PaW{+$IR?lq!*{XU2-aOXLctM!@axxuA1 +z0o?&V1olhT*R_tm+q|rn2X{oe!G?`SH#L8^OJG1zC?@A +JCboJ#mZaBz-f=N+q-7*jd@ZH3g*6~lTzCSN!+-@TlK>QMvF2C;5eN%bhW){Qr=XL^#s^A`hr;a<=wj +EX_v&w?SL>o8i#a{Y$Jwi%MZqtxY~2{N*6XA0d@p94fh`M={07fh?@D{FVPCIeJZPI$W4`Q2Zruv$W+2`o<;els$aZ}q;4}L^Fe?3K9euJpvzn3%}w=HoKzl(XMhUwp# +o(_ZGmy3CMl-<#@5Jo!hk+aHktz +-ZRrqy*k?kY$Rnlj2;H6~v38sD5M?~SRt$M5+hn2yOoYoj?$&jEmuVvNH!L)$h}5HJyHRiRcz#2s&40k2ROj4 +9yxp(Z|)0oy?{NyV3!W!PqRWGf&QYPgo$C1m!bvUHW8kV}%Jre-}~{nwb|bAqwn8}rq(=HO+qw_{B??fO8NpRR+k87khR{T2CI-)8~#!|77aC%ZK-Yb|1p$OgL +i&`pa0exL7uRKy2(VT;3X)T!isXM#>+ceg=ho(2$+K+Ma2!7%QQb+$l!smW*lEVMFLj{KQy>(}GvR5D +AX{K5YfAQuooKGqA6bX%nw1(=+GLY3s%qS#}%!64*C`0X9O1;C#4AYcvyz)UL1w+B-p*%JA?KqgUzYmImt?@wh)c-+TWl!1V(08Dwts +(N~1)3_hiN3OQ)Q0wNd`dCn*{Kk#Q)zYEKIu16BRAAz*~fZ=E8l +>_ll1A~D+$FT@0~F%4wht0Y!25yC_;9`;tfiuzUZ>5Z^!MU*;2%QD5WEGrU3X}&eUKRKzo`z`FOXiif +ZTM4Q94w-yf>TWgM_q}@LG5FF?-tT0A{#nD_Iyy9Nw;e5)5eX55x4T|RnFsq6Ug#gWzV^#8QS|;o@9)TWM6%`Z*`w@(q229@)P8 +?h-W~CCF)KAuDe$w$z1%qpM^v?G+c6GNW1r9N5RoGPn#cNry3lMN +xw8!9!!+YLfmuyNg$YhL&4dCaMQO=u=#eh!r;zCP8OOB*1u(6DW3WK! +zj`USugUf1aNu2C_V@V)&;tf=M$^#Cngm0?#_R4sa23+<>ixtEib!iuuEkBbz#0zf_XL(|L!>9=W0Hq +E6?g_33cR<(+L|j{Q0vui88xtgOp{4-xbOxrB!B41Q0223j@MUnw&eSWlYALQ6Hs%%y2a)>x3a> +=z5om^vq>K6-$ajr7k*Ck^eAa%0gtULQi*sx>~$H9UXQK3tGx#AA|KwO)}KL2>TNb03s+%JG%|}Q!da +BbAZib_s(8e8b#mx>j~Y!vB<8Ud=xJ)y9JJ!Q4M53bk6A@`uj1X@j9PcECqvJq>~fO@^(yEsG)FSF$m +QoqT76DNH}1@`<5=|J)u%lp +&_NLM)?!-!K{|9 +;Z&nQdNo+499HQ$Y^ugL|JA3ipPHLDVz{w900vxWr%44-)Z^opZeK26cjBWnIaXoS8=^crRlTK-k!9#^+BteZsUj$5 +k8ObsvjU>MY1AAZIbC-OI-KnZ~{eu!xf3S65JXP5t#kbqsJrtMh>f7YWq#hsI7V8{JHS2Z6SO4%KsAa +8T`?M71zoGtlzB^XcvAs%u6vf0-MNv)8V<_e`7LFaEa6d7cbHj_}X2tgek4gtVFY1#1V;3U!4Xnq3Cl +eXXCh6|qxz$JKOn76;A`-QUUo9(9E1g0dL?yjtbkhN0DL2XuhePQDt{wO_COWqX1&-ArkRym|B9M2)r +Y_1O74qV>6HI67Y$5}a0_SUpZ+dwlGyk8KNtX8#ie<$@Al#PD*UPin^&jqG{W~h;0-@~72Y%v2>H +rTFp8!UgSfRs%4vYbU+w_Rw(KPyC$0il>tByh7>4V#R0^^5lzM-c93C@0mZ#wOFj22U5D! +xurm?xWS5DSpS(#R68yFt2cAwZUTXbgA7YRZfmsRb#8diw308gIX1Ke$)y^xaQf20;wHfxeq!&hy#Va +qoXxwVUKZ7#_lleHeC)UvR>_OrB-#I_TP<5PHV(=6o&bwR=A(;K*B1PUQPAv5KL!+5_Op?6fv4z=Cff +E@91|;M((&0LSbtgcWfoVHw +vpjqm)PqSOQSHfJPE;S-H8&>v|L*bFH^(mg})VaajggH5RQ!h2f)kq@ktX8*A;!bS%4Ax0Hl@ZE|>Cn +~z2znMOOCustNNJuTppCCLZ157O|{?W08^1h +%lsn|4RVFrCt|UGAedO9EL5XQ#frDEWH6S5!cFZ89;iG6m4~HwbQz|Uj~r)pNDDR&gU9o?3}Y8CJ$A* +0rXZC@utUyikZE-zZvo{*TX6(|6C(NCN_0mEB7==MG++4pPG7}mPp&Lc>t^81O|H38c*Zw4y$|b;OLP +MWrq7_<-t;d(#$k6PR$Tl2^w^Aw6=g6bc$I=L{Gb!p1B9OxJt>sZgx7%}eOhbgQ^{`yp$ethu +tD{~G+wLO$LcDLCsX(*b5cQo=<@W4(|XVcW(m6NgkRl-fZWwSyk*Qbz$pQ?%h*-f^0XC^9A4ze00Fhg +U8e%9E<8C`jpna5OXfyYF|_I*1PGOfE3pm)5?B$`JVd>fUjY1{C$rg9Z(fdAVMcszv;yRNt8yY|&KX9 +qe~H%<3EnAD-y*+)HS|QKO{k9%r$w!s*@qw3rXiH+}%fBo`$$z*H;A}8d`Uzbw(z +cWg@z#=44ebCL!LV4Se^I8l`eq`$RFLNh+Gt_WToO=DmWzN1@sgF}Ld1#@bX%<#c&-x}ZcxZ~)!Th*v%~;%7nrjGz8tzM9ybjRDtONQj@{ecVOO +*p%V$YY93iQ*ADhb1J>43mxuH27$sEs~5`boP-{ZzO@|j;3bjCo-gPoaVjNYuB{v)t`06o}KAmBi*F&i%sxh09;Y&xBXbu`A`sz&|FbTzt2DXd*gm`lkbFL +gD#T&(P$#1T+>BqfPHnq{LjkYxa2VM0a(?b`u0*dw3A3n2SQyD5gWN+dP!*5F`xtVp_5p{B-S9whSq>wufgE=6Tzo2_(URz*r^7wZ&*IYW9FlEz2xM9driF4Vg(-ZBcY^Y$rZF_ +F!&vS;JjoR;5tK#5ZQ^h;7RI*>w>@u5wfHK)6T>Fi&O-Suvrhp$aJU@&e(+W7+xv25mCV}MoR1~wkAh +fsjFDu^%6&ped+^#4%RO8wm>sz>1xNA2&zo*UZQYD8P)5BdNIC?YvHy^o(AO(H9AJQsWyi%*qGY!{AC +bvzGgNiV?pPXY9-ZcozP-q7qS?B!zonS(z8AN!P#j58H>1SNnWI?zQnkt#+uk+ +{OjD9&1Ci;@gnTS{<}H*Yp^m_@J0Y$DiZnT3(PMN7P~uW(DeTr_bg-$Hi;^tnq|J5!(FSf8oCGRMjq^ +?zI@`!OjCRTT$Z=7~wcJG&%00ab2g`8iM8VPlWi0%#D+X4>&0zh-SPUg_Bs6j@OlKKK=pHw>gdV8_v|EuY^fW1Bj7DhQr4L6mI2f`dDRylrPn1NjL9VCFp?`#R4EhwReHjf-8X +`-K_$^}7Hlu~n1(WFQ;qvfwnIrC%vD+T!lf+J$BWZolH2cbd)A0(;gtbT=InCmycKPk|hc+c3V-dXa+ +xKI}BFa%eR}?O9kKHu2TU+`&ehq)F1gi-(Op$ZWiPe@-m)^RMUzob&%6j^8m9Z1Ar?127>Fbq7x*oVL +r(U1pWjjF9!lodd>8aF9-^yF5?-> +?=QnVlj+Ox1?aj|@+5tOy1)q=Bjfwe~voSUwy|?iv<|B}^2k?buz~#eEyL{M*Tlr(d-Wnfv>j8hV2iho7HzIOjEJxnwABkhw+P9zzssD>AVZUE<0`#?m)2~`bKFtwnf25qW?X`b$2c4xr?lQKlhoD%b>#c$9ESQZHGN +T}!ZUBqJi|p&rS0e(Z>Rk&_!AscNel(htcezzT1jM4szJ>&WBhiV;kB#Om6fJwFw~HXTXx&wSgZP +Bwh639?~1d^@lOdCOTzgX`So2nr`R%gx0W}YYiXsWc#3%rStHL3-+x{hbv=mg(&sQQS#%)96z(17B>d ++NNac-wEY_NbS=hqdh={1B^|#ROLs!+{j?t?>Ia$w3B#t)1)RgRV5|d0i45Ao+Jv5vYrp#H2!Y@Lom5 +7{i%l6JGrwKC@7IP=UYyl+j!g7JX8be^e!(T?FO26Z%v7F77X#*~E3z!Xv8=#339nC49FwlXN54s<@s +Tn~DEPH0n5+$aA7HQ7uQPV1#lOhY&;7lGjrSFO#n?BjTl=3nYF(vHsGIG9P#`HPm!HLESAJQZ|1&|TV;k^xg`n7^;uA0SB?rm!{I!r6k46BA{EVWX$LI_mjb>E +(Ps;OO=o5!$1A0k$ez;HEGZ=SodH&D)#G&nmen)x!FZ#scoof1Z<@x_f>W#xQT77VN{<{zdG!r`^dN+ +qCjlF=sq@a+Dwh)a(Ps52~ehb9NA)1%S-X8ACaV3Ad-fwvs`ICP08GV@}y$uOe_TtaT27AzQ(q_q`x4 +q2cT%KCy5fnFmN~Fv)I+*IN16KaUIu;ezrE4PtL`1mnUxVH@5wgR}8goS*`ujCp%mZHO9wv2?<1>Le@ +$4?@#5>h!9)v_ioIs4P<^%#JAbTOaWB;dP>9+@ApqD(DV&vgD +Yl1v1?IjPT^hulG)Hr!C!)yq_YN#}$niGe*m+(Yp?D=&BZr2D+)(OYHi#%}ZdFiE46aK>~!%x=XT@$> +zA7XV8fq0<*@{NH2)V-jHo$|A&q&|i_=u^NNQj^IPksR8vS8h4SUOlBrhzPD_~` +zHlr`q66xnDJ*SRrer{=$#7IomI86dTwqEi_TEbre7%6gniGW@0ne~)=m8>bk8j*1|6Crp!A0TTgp?N +qE~p46Zff#Vd#qg;p9*fxJ?q6@nx8Obp(N*%Kq^<|#9N+)*t){1Sx?8;81pByeUmF0~N(v5KGICYhs2 +!b8EBYJs58XFdj4O36Y +J5nG4<7f_mZ-0Q?;;9`WmOV1aK!<_P6suo#xgclq|Mu|#qEXxxo`e3dqa5@ZdMSn{UEd%izGVv3yU{u +l^{0m8?MD#avUeOoFrD?OSicifKaN8U%ZSTXMWTKaO#Kvi}J0y1Dn}dWEw?^Z$#jdx +7C-Sx9UlZ&s}ME9V}74Rb1k65rhIOuU^yhAL0aX~$=-y!#w+!QSvxle>jJPDBis;do{tA_WabG6Oewj +w!(zNmK(B$71wZE8vS4C-~wi3u)V@qf6Z3=kc0^)5JRs!74$HPNz`n!uTftjxU4JTF0oRwH2Mo`FEZ- +-SmgUzg>X)e=dv*7{(g +D)(|tMU9NG*nhSUi2LPc5LfLmfYyPee;BDe-adUExq1CZBqLEy~cdeI`O?ti_#(fe$^`>sePuOcOzL2 +=YW*qDf7hY|4J#>dnDuJxt@T(d+EP4)0OqawdCnV2_U~?augnE*`bb_Bze^>kX!T4`v7Qx2(hv9K`pR +Y@F;ktb5*Ffx-jm9wu;3umJ;JB|*Q0lb154_fhNmZg_>=A%vV&ivxxzMa{! +Dc+eglIKitLtoU@kN^yRGJEMAp+g6G);i_1vJ9H$vtut;Fhg@QXN;=V2)G9Ugf~d+=v;cjxa~kqdNvD*!3^Iai@%0Jq9@Cl@O)04yYuGmr?OjsGy=@XfiUR1 +2TzsOItx0ltP$>8O8E~0nqpa^S()9)v|bu5x+$9Z{=d&T=N(3bYWKJM +yL|rp!aL8oKIb{lxu5qu&+)t=bbZhwsn#^$fQby{u6gPKmkDo*lJi$OQdSYJDr5fIdW>oJU&Iw@TjFV +V!@6cK-@EkN>@$0sy*<*-?$f*UNRlDRGbTDTmgWt#c$z)79CNk1*7SG2PHT#C4Wpj{^wWcW3>D|o+?A +HpX3sl@O}o9(CeLASgvs-kH^k&Q>J9L`w}B`!r0k{{+7jC% +WF()4CZ=xA!li>s|+tR^)wk?_#&#})gX`pcgmIFg-;_&JDI{|I7~kfF3uYUbCfFt=3ekn4vwYkIq7&x +Kjq?+8jp`WNlPy(Wo18%9@ey(@5xxw%pS{LtH+kTUr5|r4$_ull2E>2Q%s(>&0<8!na4jkHF6=1Di2M +=dC_|I4X8`4bEorDscDtXuCc(OAw?$6_H7p-Wk`()_e1Kw&YDN}lyBe11vUKSMzG%8N9SfPsWAaJ^3$ +v7baLU+pOKN2ru&2B;=14_IbY>y96yHU#kqtAtMm(hCN9_-)tCml|KL6MAC)KmOst}ltKKc9)K}b{Oy +!5tyrFblDF{Zr)ibNn6RzwXO(QI&)XA5UJerI2EMYB_93^XBw?dEkb9lSNq?DW1?~k{xvosj0Xenl?R +1MXS(YKSz?_6$AxW)4d9fh7uXZ$Vo#HAj}AP~4Fg+jY*AY}5Fs@ghJxyqM-|&4zQ5DG +pkn3Gp6)1X-mH3re=kjlUhD;Snl34y9~Yg{Ga@P{p+D*NpccK8wxg@Qc1LWq3=d +@PajXSeb?1kQUmmdEr2(NG=P4v1u?$d4r1=!=5?02-tId|m$$4jVdiy9=6ZvuyAdtfQ28W%06M!Lb#i +5G^~!of)$hr{v!BqP9Jvod2Z6sXh}V_G|E(QF=**9{01AU=zFQ*W!qe{=Q +BFu5u+1FJSG9|x74ebu+r#E5-`MR@Ph8@rdiTKjX9Qqnq1O78jI_NK5gZT*E!z|m83HpHN_Q2btHGq% +K9%~+{N6H=9;uilx?m*!LZ#@KmHOXCGMUmgZs{){$Bq_U%%)1zUe6Cis*6oMQEGS&v_9URSGUcf|?W435A%VxI +>AqElAaDN(hJA$oIkN?OXw2Dg!S@;%#cI9YnVQFcH3sWKU=ensO%Wiu$`GiYkjq&Lg2FYo_Exk^4XFfqt%!#GkGub!4nvG^Wri*d-#?d2%x<9eysiy3bJ@4DiA3d)}ir<$7yKf2mUGyez)GZ-mO>> +U&gRpJy*Ay-yBEPZ4>$I;v$r!OW0`kyvpx)euvMpmE-5=kRLpf5jhD%87X?U<{7|i&ghz$q&gwHM{;| +fpxwOV^cWP&C?SdCq%RT%wdodB_3h%NEfBgBQcRvYelMnlE;l?5HV@%-gfxqqJt(sy8J^epnK&t&P4CP-TNmOQL*kd=>x05T30LNHwl2VBDDc3cLJ5#sM9$U)x +gv#A5O)b8alyK#VD~g(2*i30{#NeEMFyKmnvk-tM-Aihf=GiWKw18V91Fyw(dQ>mXk@H(A|cH3%nZ|8 +=}SztsWFSh2tDSoW^!%ea)qYmERrcaip2Uhk;YAbd>gH6E|655^ErNU!Vnu2W{5S1rCHb1(Od;&V7k~ +uA$Fv3{zQoMvJ87dvq&&8%aaurMNYA>W<-%&EV&uUZ3sy$l6xb$E95Q+Q|rmKIg3jz{wZUPLMdddY2g +BT6-1qW*@ +wb`W6mp|N$WX|wk=%}vTLb9gB}BzHEsESZWukznn4^EJd|u#9SjaT=P>LV%eO&zhVe+UKD(1w!^07~m +o;azOK5iP#Y9#`RHFwYKtzYr)nK@PCJTi9f3{USvX&ca7B(Wh3S +Hwfd~USFPucWQ;nTG2l%X+C+8>;!UeO>zZt>`#cKfu`nk}b3d573u9R2S@%wBjajGeq2EjV_Xqxak^g +?re=qRg^Zd7EPuh%Gey6sv;0r!p!!$KiR+9((r(?v*rYA_^#X7e4jHmcclc7qeQHuPgkhZyErfR!1hR +XZ6_K=uY3+3cC`UW|M->#D`@7%ycY~~>_)C6DJMo1K!^xrQ0bA0}va{qeG|2M+l=<|Pr`)}3!m)$@W> +KHs;*(N4M{So}##XLC1OD)_~!c!f*8G*cXG49>lgfh|KJH>?(zuYGFdKPA-e&o8DN7=;VMlRXL^Q*>G ++Jq9iU{$d#-M{2 +Xx`5g?H$>&}%WkY{i~bq}vXH1>i?`QwAFB#!nY5kq5^XLm-$dDY#^mT=reHwLFy2X)QB2u(yK(wNoc9 +g>xBINh*4sAqwxI*^p>kstM#u3#GSPI3t*_{Gt;uBu{wXdWoT3##W&yJV_K%l2+0Dh6m-YI3VmzeLr6 +GGoR0fK`zd=L6kWd4fsbq_>|Zcq3qS@5p!#OKZmYF=CAHU +U)>C_Z1#3~g=jNPIs?GD2#nVzf3v-=S*O)zTW_apM>Z@rfDO|S%UG~#V!pE2yChS)0Q}@)&)vXBd95Z +|DWu9}GkGn&x9-Y~f6LjtY>am4nW*prPOeYX3PUwZK;%+r2Hj@tIDDvpaS(h&oGEi|bmC +%|Tx{=`dvKSBATTgsA6YnwI+Fo2d8UThALZNphS~nyVIRxZ6bx{~7NDvQvUp>ldqIcw}u+6LIoSI +93pXgl~Y^^@5d9+G5Iwh^{Uer=-Jvn9TkiLLT%GvcL@Rz!HxXH$N`AZQ9trJGC~(JnqG#REwv{M)FM;s?NGQp}BY* +LFaCEfcH%jTk8#QWOCh>ogA_zS)&l0?P+l&^ZceuuA +m82qXS|Gb2N>ujFA{L7LpuvU5QMcU(gU28L*H~5zqS!1k|gdXE8+KWW1B&~gi)R>)gt9i?16=7|e*uA +weD)+R~f27=VeC3`duJ2KHl+kCYExbHYj+RU(={o|v%*Ygc$KZfOg)*hMVRSKqOwt#o5@@$%q@JZ5@yd}j-o0#!pxP{%@k% +*ai>^?nH1tFiYsZt+( +_nRVOGdIM3|e&+)J2S$Q&iiw3V*}3A3KeEiS=lGM^UaSTZ*XGgrcSRG5d5xn7t_Q?~MwFsG1tn=q3KZ +{<;8wvu_XFq0~8WrHx!C3Cqj=aAVY%!|pqSeQwzu`*wn%gCH9%;jV@3-dZMrwH>#G7lBz&15zT^Fw6r +Cd^yOtQY2OWWHD`%7VcHxSSC#i{Ua`xV$G^9B|1JE(e558C)g`m+ekp2L#8_!toKOuP1`z5aC$k^mR#aj1!J!&Q`w5bK +>BBB+b$Fd}eN5r&A2~wa(1%zt?|ee!zxR!g`jz4r>IgkL3#C3{ChuuZvdRzE@ixyn^Zft3R^;WlcDo< +$0C1RN~g_Ca&)mdfjgKY^z!alyj`->S!76{&ZvOayzr8TWGQ7ndRN?a;wMPmn6Cj!aqXpxP4^>>bP1$yb*x{T-PE$)>MR;XZ4s+-OXWocNW +)r1!I(ZJdCkqjCBvE4LDlhyiWT#+J4ggTKqU|%kK-?QSw&sYi_mI6A^7S)LRV)Fkum?cUV3|v|AKXYa +vix`-c{n;UsmfVnr}VYp6vHPH +B(;}Y4rrG(_7U5!}E1sz2W5p$}`*erww;dXrP}c_)p4$r>&Q=;A!hg7QE`;E(?CWkOfaRt@jfJ@62x; +euX5#yO1P!wD5Uqg7U2p13&yT#K7B?15cv;wl$|4#Qh;l?~r>;RWxAwnhf|uLI(T~clp-QNq1=yHJiCa%|<^N@b^MU{^$+$Y_oP;zPJV9tm_UrapGRd3}GA=WCK&O5?&;7LZUqY +n!F=B&NJvFWb#KOfNF7FKRbuZ`E1Dx)(bMr5KIl<}T}!VR(7(M8oqN^tD*)ULmwzYYnWQWO%+Zv=P3h +Iwgn-A;*Ni%1}9pzS()|Ewp;h6B6i^8>B>fvV`AA_YWo9B4K4G@i{Y|NC5>EE%}DKOX-tmWpRL%R!`B +S)oHJhOREpkq}6*1X?2&2ORB%E6O!uPxukleCaF$BW=#n}Byk>+K}SJpw?`8;A7t1?B73L8OS}AbKk5 +^$he*u4>1?17o158mI*=dU%ibWW#gNZcwdpOA3O@G=m-7xr{4?Php1!8kOP>8Zg?xD(rGyYsddf?v5H +HsfAZh4Ofv)KUy8=^dOxIwGVf$39x@PfvF|0Pv9wlXvCVbul_~vs_V1sG>Y!TO +@?yi5GDFoKAlfmQXv$XW`L-Yp5&@V4G+Ps1vmQS0HHwqTy_ttIz#FF4n^U6cyk;FjeVy3zBhnpisyKDdl={8NF!g(MsQE&sdsoLY-F@4Y=?>6Dx{vaaqNZw>kmxpZiEi4`w^VrzmECoGr +5{?Jnj|FtNmm>^_XQ?3(UlnPC{y{xMecM>RMb0Gl18Z3s3hCHYEY|0_b=&UIw8@G(j;kaub!e(I5a)0 +C(^c(K1+gmhLu1ON1rk(p9xX!hkT;k*?ywjeO +h>p)w^Qx8+3WpB<0C-Lgtw>W2S3SmAWd=>iNcO*xuP%^Q&6^3C6ZnD8W|`Su6GhDChgh*5FR`%-V{%V~%%70%~|Xp87*VKpT8~(mTZDIcvrYBhB02N39@KJ6PZzfb1%2# +zBuC{UxO85^x@IrUK>SbTp}u6e#^ZOfYdC(3NN?rEW$ul(O~t}sY_h +|!K7e;s;pCo=@r)q4+dc%*$zDYey(&$_#+4Z=I}yYHM` +vPaOv<5pY%)B5kPlGGEe5)A7u-l7t~rE=_xgj%r0*!t#K-mhwQ-+M3A*!LZ2zEf-}y?VX$_qQD!+J*+ +h`I!C^dH)G3aoH$#aP+fw{>E3zz?Xd9RfJHaGo(2jKGG=B8;HnmCi_I5nYf(l`RH1)V$QHTa~xfkl({ +jujD&E)#wSpy`!gOmOwb?=V+a{3aQy&T`%Uuj`x62t!cQNny}6y+;NxJ*4Tda0+Ou|3PrAjDF_(OMHK +>Jvq9nr;VK8jkqsH8yB#^Bnt-J5JLenbG8Y*e~O^qpo7dS9C!}9?xnk<<4SK0#TsOwonRV8|Y_2 +Hvx%g=`yHr3L?3JILOp9{S=nkrUw&t@*VdaQfax~%wIp{aBopZVv*iwMi{X3&CncsQ@h$rd_ZR2OTgyn*+0S+q1I!7XI2$Nl_NdOZzEb9n}ci5AaZHRi72f{FAA(DS`1;fFv}9J=Ldu3VbuNJ+o{g*niJseW7r;O8Fj +Ltq9u&PnhkC@dGB$UP;bi)B2$J40Z3gFn9R6tAvg%eh*>Y +x=v(Vb6)MrnDY&5zxgyftTJ+);rq-9wbVyuTWcZ{f6Gg{juZiz>1J_WAXGe7OpYi#+Rac=EQuIl14(> +CAhISjTk|jtw5IMeY>1$_f@kjwoSU3D!&(~Ao4x@*s0<8M$Fxk*ZaELwQMN{sUIG%E3!nma8w#qgGNH +g|Y9`JL)&&$)cjDGH=PywX_jH^?+vcCnA!$mX9NI2*oWpB$SLQI4au}_X`Eu~D;*s*3oMY6H)*K~Xo} +&oVHl23#kzRSe(7NWrR;26P=ij2KH9HdO`Spy{j%OhSPaRkN{M&@xqmGb5uzHPR(YOl}hzl)zHAn6Lt +YC&{ig|BXYc@qvXp3h*7&P=Zf}7ym$wt+>26fxPyk+pjv--5DElh3-x0$E0*OoFZ19I8ejN*G8k{;xkhKKecv$cpb3G}xhvPjja4GQS* +wWxjzwR(>Uuie5wA|wmtAG`7)Zyy&guycH{kv;duj=q>Tz47@# +igH%!Pdm_5~Wa>`V3UbiaAl={_*1X6Q=3{<^m_*E@w6bcm^d)ncX*9M4)n5uWN=B?p6=u(@VgUl(5bS +|TZI}j0u$}#-hzV8mQCR#%)PcGGNGPr_Ln$-vJo$9?}<|t?|)zBI6{qo>bBPYXS4N_xF1B8%ZC|%F+I +KQLmKvOve(aY|TRRU&eRzw7?9N6Y(j+_J~qr;`@>28$TW2 +=qS>tqp*r`m6s#W@vAlMyYJs}DxwK!1XCGIS2$wT%QUQk +nEjX`+Z?eW$sA+~8kB&~$oy+v}YUalzzhWYlBrH^#HFfpJ3>Bm&?QFVUXQ=u!apw*&W1Pq`o#olc9r{ +^orfAH$-Y66Ym}?g6_#sknpqeh3w9q9qX693PH73v~P@_e1`}ciOA*_@m3N&FKysXuuvTB0=w1KfH2_ +SUGA3VcfP#*CNx1RH2T7_Z^%0NliDHE~+8$J8$8iUP&ja;(SOL`OL5-w!?7Q}Dr%T>vLYN(=nB-H&)U +UWm^&VtN(Wg`jC^TUv&{luC__uD2TRz`JDLq+gFn2eZzHjtPFXG~pb&wXh$kxAN@78+KO6M<$?JC|ed +zFqfOFdbX8<{grPR8oQ}!xL1y4>J}WM&)@wP&paQm&~5eDDcj3Vvwnn_pNOP7e1T`)`aG0ROWM=<)bTx#d35WA=X0~?W4b1=vz1O^*DFyQ`8FA?zB +rF^s%qsGf`^oE?*?n%a~V;Ry=X|{M;-Th-oheY9%z2G4=P&sgkxNSv&MSKV6EdqC};=fx*ySKCcnqi{ +a^n3Xru_>jORj@=PS<#;!cjjlGgptVyr~yqU9`4KN|_Cl1i8t#`DiP2bD)NX_BCDnGQP9nw8opw1yXq +T^wNbj4msoh1w4$!>0Y#)X`4Z+$E$H#}wM&Q?T>1(W^%2zi +2g@j8q#90%*r0)eIq((UUpz8@rq_E1UGuG-vWV*~+J^Fkx4e@8S&-uac!j4`pgK(IYDNj?Q3620&O2| +`GpO-IJ#%$w?EFFrySmOaNZr>H!N&agl3$W2$CLdAK18S^#<<6Hl(vo5!dx08{ImKv)AjECtpC^@E5$ +Glv=s^)^3Ryj0kO0XBtEZDK;k4CrDdX@DbODmXmUo7NT3#mB+ej~50~NqKf5q^20EzdYS?0vC$%_q@4 +-@|4v!1&kX|G=dh^kzme9ig5{A{lb04AK#Y%P^iJNAPUBV2NYk2{j!xZB?raYcS{hjJ6$QoPp +9dq?YkNzD{>D2xF%uh89|LQ*O=~mF`Xi7^+LrsIB;$F`5NY@hDHsJaGj`EFM+~IYCl6kF^aG|lR>iqA +Rt$Mh3zJ-?8^!2eyT^?}mqngP9srwBZT$f0awYD{ttkhaV1$}Q2^@jAAhy)CkR@#ToJCs!&Uk~=x>wy +*wZz>`!^_XG1+{C=$VBX7oaaR-3$@8|7`J$+Gpl}xXcZR=>y0b&pnr@wxc{fWRXs?-28v)!zxzS^_TW1^o +kq$+`Ppf#(a&RDljV6$jk!9ON!c-X$=Kj +`5KgH4I^*DtGQnE$edE%QFh;=UE1eUljggNM0xBM-o>gho4Rv3XdCR+f!?>24VuRt!Xue`kW! +!*+vbTh)gV_S%jD`!q8yVOt>?bUNUbe3C>FoOz2h?se>+CYE4}j6e%W=sNfq)P{yl{2EV_#BD~h%ze1 +zH-LK)tpjMzZF``Wojr30N|lGC4$Qb}LZWS!?LrSO#Kf@$@f4YvTgjZ~R&ie{5=8gE6Xo^XujNO#roU +Fc`8qFVrHlcQ!1T64X{vtNyQhwGQ5j@&&$xuwu|8RCLM(zDT_N-Uw>_61s;Akh!`o+lQ3doofnT)$;8 +*!IIY{Zp~FkFahQl&p&LzNZW`vKIG@P9=; +|5ynbZSfvDbZt)+gLAOFgb$GLRIFQa +mw)U;uL&2?jB@bz*szPG63B>}wOt^I6Io*jb9so7BdAEq4#_#=@r4ul7c1{t@J_ODVThz2^?$Cs@1&) +4AhuwF6l#W5GlO6{udWZK%n1SC%en{r&|bze>v+<_Ug&8Tg~~-;dH4kU&0ti4!{xIb4O&*J8WDEw&q| +DFpP32G`c}c7DJWSP`m9)x1k%6#b&sW5AmV_$({m>Y0_s?>m2HlGu4L(S_%RD*8lX*ltPLsWN`I&eBX +8A-j;!S0wK`sGWVb4*kK?1zLxzJOLx!QSNDbOSx|x&*6KXz?4K6j$f^~5u;{7e-IQ{K%ab9wiqhyZT1 +(iI#<_q;(H`aoqiiynBC%ygzT3=cAE@@xooW;t4;bb8J4$6Z@UL& +KZD;Uy?d>@htizyf19+}v7@7ZXLr+m(ns=r_enR7QqS&|_et-LGF0+vM%%L|_!kU|J*#pZPo6H7ewmI>7`E^Ab=>}ImCJR231#lCEXDX3CQ<^gRf +jQ4`D~(ZIyBr`Gh4@%YSt?=XL2S5TRr=|u~+EA?7vVbq`mTfb0lUJjex7b7W!w9?I(xQyy5`u1gDwB_ +nn>5G_Mc-dLeJAR!ui<gGM`gx=n +9?ZCTPpePG->FkTS0Se7x97JBlO$s2C|NRgl7DRYBq<}?;-xq{>Bypzkl$`3@c6JtujFoRhajOD?d!a +DyugNQW>RC@KxZ(n$jQna=F+1ao^LPiPCoYC@8@+nhq#csaz}enm9gX7#A=7Xx={z#qy#foYuHrh!C( +97$F)luPnxyoMa4$ChOT5qp3O*G6rLC>zqqg4tID|>ucWdKSGO!|r=%)YCNbA23_*^HmC1~>$b`wwkF +1*D&u{l>k)b^hp+zQ4QBSv1u41PsrEMCmUcDD~$bG(t@=Z9xPYK6JI8(yK60VT&*Ao6o!W|MeNT^5{HBdmKgeekQC7dl`zJ$dRu99$*gbzvhq=dU +AY>@Cn2|t(cTM5}9SzZ$MmvFd*X%gNbVUC2iOSnnGKS@|C;c*E+myk*R_mr@|gh>*PlQ2udUr1Oi;aU +kllu&z$WckdNux!BP{vAE1uOmfnjK$joROSizezkyYpJy9|`@z)q{o8xogMXM$kcLYe-i9tZ)^I`irI +$D@WreP6XGxyDw3M+Wtd!-k66R!f{0(RMtccN7LrG>GPp?73Jp3Uzo5g0bNo*KPk!fl65o`?0Viq3_v +)BzRgQc^n%*S;*Zib^6-%?jKi70GL_B1pMMz5m%%*)m?wAOi`5Kb+n9-mYA?)m +(3!+W;g4K~A&d!Bxe#&*a{wCgmf+Wo+!CEOo?;eE#HD=hR={yC;b9jeN2M_3;)gJvTe!V=+H1Z-Dx?o +mz1^U>tb&)jx7Rm^Fm8kEON$H{4NUKZ2LX|f@1Ab{L>rp6ZPxLa<}{OJso&v%JOmgd<3UUIQOWn{yXYNTfQav@}beB@vprc7j2pxYqSGIzhVR;cvOJgZoc?&bYcIVq|3rFJYxDof^hW#AbMO))yh{<@iCAuqqrH}-war$ +H8ouOzg8l^@tDTi`$`fVH5;iJ1trV}Me*_!*qvc(K)a;UC(Y{NhKlO@Bd9AcxQJ4JjUtXVxD#Dn@Da` +k)I~|P^ZSh_E6Zn?MUa*k2L?_3d@9QIO?Jyk;|36cIE>9_+^FOyuE;RhD?UZ<3!t2Qp-rp|fV-xiRb| +240E$O(uCi#`$&!?9{h&oDTx`f+Z?b4g%SAIVqe=dF(A%upxw4D;w@VEB&1-y(zYZD`u1NK6W!N^B92 +X}FDyGD(OlYa$|0whekDCT2&X>a^q*h8hgT-vpmNzxuA?U;{od$hDWq`ix@8>PLgwAY>&_E>4pmi8VR +Vbb1HS~s=`dz^+(+Iwl~pA+^z(!NgG`)cW(6?UVRp0xLq_MC5py-eeWv@h4vllI#+`lS7$w6=UB?5ea +MlXe?#`%XSDq4@@t#cV#tX}w-LOTeEIC4KpfG7F}Kh)K0XP!Z-=Zo~N>=KCmX!oG-yTq5Ti0t4Z@mDh +k}_`ROTYnzJ2*UI~G{MnabyUxciEB>y73%Ql@nX`EE<=;>~XA*x#pUZOj$GCeUcTbcqC<8v5XP8mgeE +7_~mlE?%>VdT922i!M9rvkn4yC!y0CdRtiGN5FZ;5U`hS8JB`@iKrJejheWWMkYKHDvlb{j8;JhWx;O +hVX&d=A%IJ^~NTYFb@Iel+?udNn#Vx-@z;Iz)QQeROE)Yw2p~5T0B8fV;-g+!uUoR@x>8?-%EvR*Xv1S9!*V==|F*> +Z{6t~NMs+dCDr2_i*_SM|`zpi@B}*@}Pt9|ckadRL;nF-z?nUlW5S!^P;`lP%#k}B64rd{ifi2gW=PP +IXLZ>^|>0?-CZZWqaT}q4a6K=vu_y`x`FDG1thvHKlHY3;NcIM}DJd>P-(geONb~{UbHt@@yUu^etnC +TWaTS+l@oZ*%**F|_JKEGsgwOhT42)4jZ1BG-BkabUXOPe?OOOgCF5O}1uaPRX*}Fg1JH^cgc}&7L#&#+&BNzxfvn +a&i~u<=YDuEiSy})}kfFC63#ir7ri4cr}9sK-SFpc^Y{DNS(f +>}-2QX(+nVpTF8p#i-&4M3ZAE3(oxfaHz238-=B~Rp-gEDLn|}4{-)#Qv@9y97z=OYk=;25H@aSWYKe +6?XfBN&2fBEa*wmtRqGtWNv{PrC?UwHAQzrVbzwr=;Hy|3)sUw`1$*ABk^#-WD8N8UX8*4ytKd-wQz? +|<;&M~xq!_~hiNPd`(dKKGtJ^Tn6VUw!?}x8I#T*K+>C_ZNS-r2dx&gpND>pEV%-Pv`%CI{kn9fZVbF +{|f%o_sVd;_Rt&lAV2%B+t}%fP`#i1t~U0&+t@d@vES3ies3H5eQoS?g{Z&%xi)tCj81FfTD;U04&9{6YFmby%NuEM4E>=K9D;d2*m0v)pfbM7 +Mh#F8=*a~5XK7OdiE2Y5JE7g_kDX9!<)&@RZ;!d~nyDuS^S{p)C*9@vR>3Wy4_SS-Qxj84M(ivk)`vg +d~A*|kYYbFxD)Tp4+G^*M`k@L)mgD8r*i2o}QhdOd9s=vj!ylTPL3cb2ayM*@L&mfyLyyjyTEQmCq`L +O~}bm1mc)L+BxcQ?lojBR&$z&L-`K#v$eF$`OkpnZ=72>-8aJ4#zTw;|l!r#pMRP^j#(%b~l(hYUJ?z +BDpYY<r`~v+E?38&VrBFgIp}vj+1g-xkW~c(^=v)78V<`a^1ywi|rI|p}?1 +0SY*#Px=M^C4tufDUS`j8yK)y6X)DQD#bhD>0u-n*x7e8LK)`&&p;ZP7dU%^~PJ3>?vA|if1Th4f0sU +yLYyt2a~CEv3+4K9E%Ez?bjIx7L|60 +XW8OHm%Y@Hn`bxX7P#zADZl13X5}j;iAY +8W7uu(5|`bGvTuW?J)*~fx0l!b1$?jH$*yPfS?e>?FBySTtlnX-9Jng>9%$c&pzNAEMZSZdc=g&qVEuW7ukp{lf`K5(R97Xm8MY7+VpI=z&n!jvJ@^DA~LiRHr +!X4dJ&${MEu&&ABtm||`Lu75Z<7A51yPnvGF+1MoMiKrNm{QT$USX_ +PzJc{h?#y~QW1FvP?AFkwHj3k<^tz(I?bn~Nu#Y8<9$~D9E0Xm94LxRtvmVK?J7a2l42vAycdvoPxjM +7%`H`S6f*D4IIXg85H3VRAjm^UtNb$phSQz5K$Ds??v2f&E*DZ{7OE$1>Bcqx-H%2ss)#@D~Ii0ejDX +qOo!v&ZhinR2s$Ml#6jkfXxQq+KcP0|+4^A*DLrG@zcLrO>@V^)p+*f16g+G9a`Ea-@xF7m4j(y<`W6 +5Xv6>z1zvJt0Jo56@plGWOEQRy;pO8Fsq4l%VSc2A``Z}wL)S94@?!yY5dkbh7tM6W2xiQWX2zLVccN^K&R)$u8)F-;s_o{`^@J`-h +*a_Tj`wcMi(s29kO0r@dZZ>8t!35<2YeBypdPmkfDtatui);oC*>#cj4b*_zYgv)Yn43>Ee31A_*`+0 +od8>442F1-06EC%_H85vz0eWjbPj8i8uwjA&C8Xu^fCbs7PJp6u`B+D8wx_cPvAL?LoBuqMWVmlklYcL|Y(S%1w-In%9a +eTJ}Sl|BMBU72>Ut{<*y0|d(DN)EP`WDpVxO;gn`E;GP*)D+H^(gaD)_o=*^3i={SBK +VL>UxE;UXR2ycWsPrFw{mmB67l$!Vo)}@S-1xLq8ffvU{^A4TCTL4J9a7VA-nC)GM6za>b&}bVr-Hiu +KZUVImH2QT+uT)LU8G!g4e`hh11Fxm)q{ND60tlOJb&bz9iwy`3nRh9Ir3pnuJm{p)nlFY5}`wa?e0& +#OQmBXK?j`()U^(rE35{tIoO+ssJxV-c*|z3AVlUC#_dyAI{;TJ+<tJ+en*_lB5So<8zp0B)W>uXB1{=L9XF0sU@|nIWL +16Sa?8(O2UIu2_VP9r@#Z)K#0%jyARSQM&jr7N6XM#edziIkxethHkZ89ML&Z*}UFQ*Xq3(i!>ZhZ9( +6FH$vu9t4A8_(FbYX8{Qb&5KK@8J46A3m`rw>Dr-J7Yn|Dud(23Ecgl@I +z%1JM;*MW-8xtw@Z)tl19g67tzVrU6V3)E-xfGfR}k19K4aXEY*)P>zJ7YvWu^grKxfv)$w!tP +9}lSA%6Uc{<{5FQ-{=!WnL07I;q?+P^4kshr7>L8{gBo<5AUx~-!SI%$oJK^Mva~?4l!nE!2078|Gv9 +3!eZWo^f2FP^ly)mhZ#%8>yY7}rMO2J+o;9wpiO;(FoWOpYg1I;Q714bY3|Y()gW>nmVH^o2t}N}5v;E(n)S_(LS605` +nJwZvwK_{$p*PDvO)RZvq8xh*dX1vtkqZG*cc+m2aKUR`m}C4Tj?xsGh;dZTHV&cZR^*p!{IG5v3(q~+v&toT}w?LJSVr%HL1ioqp)~UkzHb1f{?{_Ct|0U6qlA1+0$LlA`6!Gm^EF+Or@ +oTi;5+NnI)|RuzcMlPL|x+M4F#cnv}itl6<@G1boi`xY|nc-9`3Eg+=xoa+grpCjzI@&S1NFD-5)`i( +G{hm%HpUOXd{j+tU~4I@zye9)01k61tUe&v4~i!9UuY)Cb6YY`#H?(hHFL;imk2C#UB6z^szoe91RpN +F@&YWn5r#veV2gw-D_T`E-_ER8p$hrdX!mV9C;Uem#uJYr1_&$x@!l84i2i9A?qYa5?dxf&5;_L!hV^ +@?vH)tXXqR*%>@~pH>bD?C^h$PIM3(OA3o!py!9Uy$#2oP2pKFnN8^!i?62`Q!O?!N1&`gWnn&VqiDA*F)%CLhGcjhLeBOCP6u6_0JehY7j;swi(Me +B)#qNpYSa3`zfvu*@ +vXyH!%U|H>W^%r56`VR@;`zJxx+m`ZtGU}CjFGm7kX2fJJLGvqOcy)ViDwFqOHXj!7K056~Rvx9tu*h +KvB>yK_m$+}Ay6RL^sf&vs8olr#MbmThiK91WdmLF$k=Q;~%qjU~C&QqUm7nLJdgv*~=AWCOiK1`Y`PDG&JB=^I-MKPOBgC)nDh(x^SesMC1{X%qof<1v+p8ba_=YIM@zd +&+S8>yTgI6#-DgNRN8-Oxrd=f6moPfH#`qa!I%DsYw04r=%O%ZgBwQ=~E2Mv=#6$Y?=tRPU(*Cfd=TV +v76ViRVr0)&s|Bm!0y~PoJJoD#!+j97^=U*NEAC3R-#{ZAT|Nr$NRc8M7?1zP%#=i>wd!MKdAK`E8@c +Fls_TTb!e7I2fcYOHmf0pil<@5i09cc6XAFmS4*UI^RKoB3^9>oMAXYKN}{iOfn9|`#IV*;-EeIpk+O +IuaW;Z}sN`@``+7(2kf=8}ME5%MoFk=+PYcNsfzLUn1 +uBb?vPN!zg5~dOSn!#hlDv2S|v=9FkZr566z&vyd=|;@V{%`6+iy+eS`mP{y)Ww#)%!o4-)Ph^|HLfW +WA8-HBXT3w$qQN|9|EADS4EDHcy~@d0u_?iw{7o%2>pkx-mAikB*rEhs9v +LfO`_){diMg-a>A89SB44i|#-Tb2H$@9_X(ThG1E5^j&Z#xVE)VET1kL2@iqfz4qzGH7RoQ+8oU=_-UxUPUUn6D1sH2V`NB-__ZGq19e +@u`f*;%;0z5fM);++G$*9|K*8>imENGYuxIP1M;r=M#I~jtWlYpU_vfKgd@NPnwy@1v!0w=+uDIz?<- +)Et(qI3cK*@SyM;38>W4CuT8c|jN#pyvjGe*@s&8w5^*7p0lt$f?3y3#iLRKhPb#0<4+NSQgA10pFZ1 +@EiraW`-~i1>847#H|OMIaBb5pm{dJATGg6csJ7xv~w`7;+>B$1cR{zdI|1&z)vx?KL)b`7&TwuPrI4 +1oxc$E_9eis3uIpbSdCYYwy*&(HwW`InDcW;K3=quEWk|*8QXw*Lh$K@qMkep*ga3+p))I^^F&%HfWN +}K8EG{D_Q}Uw5qOM%f3c&T!dwgZrvikB`ANV_i)8<~n6UvRjJe=W_mNI;AYGW#01F%f|6;%|9U^=);O +0i|IO~^!Z*u`Z_@yY*M!+rWL|GGDP>ptqFgbv~sTOH%27I?#;CvtO +TWKb^*CX%{9KS)}Ndt7$h_*~{!rh|ZO9LFUk;WC23*gXuM4wFXBfJsFy8>8nFWMl?ivhjTd>Zh!`-Is +Ac=0~LD}s;Hy=*Yk{cPD=kT%Q&`#*qs33EK)><2_WsRZ2nfS|t-u-n6!%Oeay+rz@$0T}xT#znXjG(L +(rFcW<2QNfd~fXf~e<}$z?kI8-vaP3y)7vU=bd;Ssi8Rgpx@b*6eFU%_dFW^ms`6A%we+Atzp9Y-!H! +*e)T)0iR=L5d7O{CQbxbZ2B9dLgLaM9B!cbLloM?V7^U`_#C^^6>60S`YT>ex}h!e<5FiU3o0p+3QV9 +N^(yqU??WZm-1{3HKd9_{+jv(z-}Lkx{pHl|-4Bo9UKMVMFqrgdWBqRs25q%@U;nGZy-ICK!1_nC +T4tFli>3F3ofn-YLxlYowXrR%s?!C(Q(#q?yi2(-~hf6Qr}AL?=OqbSLPMW;)CHXK5z5SDOFx{Ns<1X +%6Jy2QfV@@67nOwQ~AKxK4ad^9vWk#GVQ(r*+p3K#GTERZjj2AQv}5w?T^*3|Hm!?J!B#7LSfgK3dyx +7kfxoy3pF1X+=2Vn4%~Ni;9lQ>`>_t(m3G`|pQ{D#`g<`(fq5sTFnRMP%vW`-_Sj#eo#<@hgWn>TM}PdxDi+qrWmtF5hN@4WL4yL9Oilb^QAd0<$Y$ri!xK3E>X4EvWYJH +)Mr)GsfVM_gRCY{d!+@TGe2;vx0mIe71fT@TBbhzt9&Wn@H4ME6KEm +(4^<c+tG38(^n{oX*lx^gBunhsu_4C +fzjLI>|^ylOeq;SIzag4ckzuS)eiO4jwCs^`hc$;@W6u^BUFu-k9H{jxf~apOkzr$7CPJ^SplypF&9_ +S@{#sZ+js?x5oVm}5D%Z4aT6xWjRW`h3AIwtCl>y9#bCVXIL@1-n#r^)BxM)P!4CfAQUd=V4g=;Hi14 +`hCI51E*%qnzgGSEBViW3vSK4ZK|q1w?JK0J$To=Sqs=|^$)wAn??9`-bMIVtLm9^=e99ki|@d^t#4m +7Dx|N<0_DqoEL4rB=l2`nioiH#pWo|#N0mXVRcc{gp^G&XzoYVR5t&0m8Bn5*_7idoA!~)O7YG*p|W|Os +VuKiWeZQMY|WZAth&0I-E+@9>^Hyp4g2kHf6KOP*}@)r=ppvlV~_Fj-L`ETd+xdCcwhF?OE0lE_iSTp +zf#$A%_@84l~>q-0|(gaufNU?A3n^E9zDvAA3x4M_}~Nf<*8%r&97DV@y8$YdePL>#J>IVbN0!3l{Gg +vvvcRp@w!4!KqAHmtfxtaRFCyc80t+p8hd9nV58cFjaNsodFnK_T)mBLR_|fYsZX(^>S3)vW!(_JKjJ +4L{&>XCLi`&Me-YxBsnKjb;%`CxzaoAu;vYf$lYa3BA$}U---P%s#J>yi|A_dz5&tmaA4B{P5&sj!Z$ +kVp5&x`T{KP?6UzkwWu{PgGYt_3LJA`#g^By$9BhgGfHiD@iOk?WF+nDIeDpufn8c{YE=;{1@#iD{?TG&y +#D5;~kNU-Domn>PjyBtmZDa{(wqw{K)cj_a!PMxRm^#A2)MC +>n4`N%5!WZ&H5iL&e2x`Z6RuC=gjzgxwwt;d`K{hak3mCZgO<_u+kzy9^F+5Pw5&mMT-0rv325 +3@%feU#4?o_XdOJ|BGHg%|i-;jP*y*!`GqY{Oh(@7}%ajW^!lbAyv7K4K?MoM0zUo@8FHmz_R+ntlD% +mwayU-FM%yAAb0O-E&T5Pr-ipd%u1*u^$FH`oMyAxiJp|-Es_ccVnP?oJFh8vJvXbY?@lnZd2c6_o(l +)r_@szznlHyccx8i#K&N}aVX-CLHsnt&qVxrh<_X6{}S;ZK>Vi>{{Z5*j&uK+r~GH0@_%)nqUmYx*|T +Rqf<3q*_nxr>6B85Zj>w+Io;_oG_PeHUROilpP|tewA2M*@;DL$JQHHA{Vei>*z~I3M8y(d*F(EO5_3 +D4kki_0$iO~qqIWn?OuYLoEB!-6#=_4JC#v#`v>h%#r5a;SHog;h3cE2VuT(1uc4GxaD+FO-9FfOncd=aHL>_|VMSGU0f5)u;=27`X2e@#L{ +d_qFM1fIWMc<4-Jzkb}xRvQ=_N1Jeg^6hAwROc6l!qdV2zG8ofWc_n^5aW;bvp?g}L=Zapy}>fo|BmO +M*(4@<>BxUf-@bi`sq~V{zd@m)q3BuoRg|R8pRNEvPr=0oG>-qPoDgwSWJ9k4~pc7&mTQqRdmQ#6uz!#D{-`e^k~qFn +1X=XwU#a;#KFL88>#`ym=GG;DI^rop;{38|!Fw*REaaXP8ceufD48+__T)ed>-KJ2>5^PMuOe{q$3{p`k%NeE2XAM{zJmAor7q^ZTcsdTQn +N?b}yk%4VsksF><-ILvq5byqg#)kGVKvm|uy-hEVcb@fdnM~)=k#hT6LA%yoIrT?36zELrkxo5zD0i( +ggyFUN?bG5d%*2m{VhYqRlzWXkx6XnBcCv#(Cqx#-^?{PcjNqaHp;ShE1+poU*ss;7#d*D90X4c-edC^|b?0Zq>z +SrNK-hYPvJ$v?SKsokDov5Td?B2bb(@*u|`0?Ys3_kw&V{RuJsZF2_bNJ?)Z}OMg29*avDmQ976z8?q +UgPvr8KV9FzxK{GI;twmjOPJfP_{Up#A_+K0l|gpXYtm;oC2V{xfIJh`J#o|B;cAHzLPO=s$4afNb5mRg~wVF|*NO_8)%i-@o5XXy3PQpV5`7sw +(ps4A=nV4LyNJ(6;G&k)%&VV%`zCW4}oIy&|{n7HPLja +^d`~1|M@?&@QS|@(^Uug~L5B_n0$#M*!4U;U9VgLz`*oPC8C$Q80-aiHVK8FG(a*duzu=>Mh2{f +eQF%KzOT1u%506zO~*fFVM4;eTEgX|wb4=~r7k|ArfG=%{=sM9;7-p>QDUb;%d|&-?fV&cn{QNBmc+q +hR=4q_<+YOEFX&m&S}AbS|lj{$GCiMbyvKnqaWg;t0i)P&m$=J16hGyj)gI?juXa$H;TzVhs_4{#WGT +zll6}MC3j&eC+76%1o5i=lubF#wOjkBcRW1w*_=sEpDj)<7?2O*|l(lteX@it0#7o62-7wF)$L)JzVQik-S=LYP +BEHAE%Gw;okljsApFAcX{Tje9L@_+_jbd==b00^a;|>OFQsjYvf3`O1CdF|5){u6|(68}*LC20A!!<_ +9hkooLM`Q*J#5@)YuicqE=GB)2T&Q#?|q}699#<* +!mX!@}iR{pzm>2f2yuoHcSZfJvciv`<{y(h21ZsM!(ZHyh$BjshqurarbR7~zFWr`tKpPyBmG(R;?3d +VMqS;;+Q#;6`Lbwp2@lQl>Ve^hBOxMPfVPK;3({c7)x{3|_$&<$N2p$WRt2k5pu!N$AeWBe}|k^k3bM +ay=@uthO!n4%a|7tm)glsFg`I~eAHVN7?KIa)D{>?ye;?lKq{*rYD57^CCf(DIMXz=sh>FxRYEBkR_! +6XGH}-8`vJPk#IDH>rF+QFhO`Q?^Z045|w+DF(lz&)B5mtY}%D5iN_-V`M>EtjtY`lR1jv3B@p7F-%b +mjNlmKF9$>0ALv(EF!HZ*oe!2Lo_Ipa%E}C$#^A76IJf$}m*my_J7t$**s2(eJ}U-nlGW!TwMok|Vq{ +@@tURL_3KheXis7#+N7Ise8bYy2ajqESrSC%9f9SXJ-?eMk8=(REX3d&agCQ74eOP|`?Yw-sdx`8V=p +j32M9Nl2pVzzed8LD4se|EZm7~J3aWXqOfPrCR4E4`_buRzL_}{lYmySQebM?RTAAIn^K51!bZ$c~jh +z{iD<{Auc`D%zK9MP%Z94plLXpgTZ#z3E+a`ZW9)EoamKfLeNs~3EbwQJX!*o#`YzbkL=UDdGmIw?Ub8tx=Dr%86vAztrGllW@e^Lm@q*K3JQ!~fByOB#_kXcg~9^ +e_y%khu@Lbn`2Yibes^`2oc~qx4R3ahKHC^$v7^uPR2Leef7`Zg*J-TtXG?$Ewrx9&962&=#E22!sU0 +MJw*=jg{-k>PEUEi6#CD|n=7*~#u +(Xw7z2IACN0Ui5Aqw#zoMd|X5V+;ebbO1mG$|W_jla5abub4u;_b|lanK=6TjA57{HnA+CcL=mR=!^jZ1f*5|cB`uz6h6~uu{8zVfOJ9l2Nc=2LW2VreTmnAPRPvYa_<@)Qd +mz!_CSv0mbv_J>C;htcyGZ+gKnX=c$wjNkMOt$?sR{e7z#=t+Xo6mzsgeav}w~mKu`Ph=_8XTPd0o21N3+A-dz$B63pxH01S45!REc>V#L?TJ#=zJhQQ{QE +n+G@ueRjr)~#E2+OT26Tylsjp&!{~B1_bpc@4U#IcH^M8U5|iqld)C#!9<(?F=7ypL2q7gpw_*8}^7C +VGoci>@mEv^$n$oc3Wfn7lwYtUC^mhr*PfFeDrzy_U#4(`$OGO^}}FDOiYx%eft{OTDfwip`X2G&v^{ +p=mGYJ*NIJ$A7X9v03ARlxGpwWX}_S?$#2=RWvueOWY3;G)t*4lr+H)PXI*#vWA~Lwz6d?aThZ8pyNZ +oID8EJJ&By>giaoaH*m@#%ls(1%uV24jd_JG?fquW=oX1}4K2@u2dsKboWTpL8eQl;E(D!I;|Hi~`#6 +Q@7)yG1fqi-Cc1%9KmDyJrvNKH*OwpDfA_&00~@@?f1JwPwv3337tu*vRth{s$P9>EX%x#Hy)MzEXB8 +cBN%{cc%x%dlIvp$%Nf5V|mP=1ftWW&E^zK%1SMgPp+^F_0f}A||whYsHEc)EJAdx#pV7rM-rJ8~^Km +=Yzr8#ZWldZ)}kIZj&boiRZ|S?H{PkEN$Af=|4hy4gGGv=#F9Bu}ytEVHOn?ne)NL=fMWO$QHH}ES7eSPgh@|yk9~;w5 +Wa=`B(p)Z}YGEu-KZX?cc#0UV{&|CkM~4Mf4KrL4gI@b&V^rGxgH%uK(Cvsy?0&-xL4voW2|r+2Z*6> +#rNT%O0Sw$UF3-2P%&%uOjV+es}#-?La_$RXR2ay1r}OJm +F;WnKGFERL}S}A8UynkhTC+8YrJt{)K`H%8MRRQd|jPd?gu}Ky!(Sl%pSA;_w}s*Uuwg>y5?DTKi%GI +V(aHl%zr`WPik`ys@;5r3)3^D-$Jad=a* +qSe7`2_jt^Br21mNkRA{HPHNipcc{tJV{2%AasbUi(==ZgWAl6RT-GIyW>0L+L)_#Z^vec)6X^HRE8B +1~ROzDDI1wZdW_|a)55f05uqXJh<6nOwr8DA99kbA>0S-GZGUQmGZ`%K8pzmw%A8^Yf{UQ2{QF{Eawo +JCdr)d6~sBvz*`#uc#PYet#_@AwdQ%wzwdN4J4yFT+T#{zv%`fWxYs{*|?dOXM@Jt}&g^a%&PWlr5^* +T;`*zMnQ_%9Jtgez%QhITBm@a(kFsBK2JAi0<|2z0yA(5G0SWPLGCOA3Y}I@z(7D`(W26{&Dw8gZuB$ +!l-ci6W;7VJ@(XzV>0cB+B6`K@j>z!5j9O}<5nK2i_>4BH(`2Q&iYeE+-=sn$0d*5-mcAmVz +Sw@XD2ROwk#f9B;O}K2RF2^2OQt`CY!z?{VM9J>zvy53a7t-PS78rSD2U7vt|z%ZW~SS)-C_&)?<%7m +ZtW9jO`DQ7m(YrC)ghL;#Bp=^5!%BOrL=IbcIu2U+K~b`b=}2KG{@<28NMGU3aj~&wj40|298E4~hBR +eHM4`j(uvVXLRsYzv;2itIJm!a-CkIYg}UgSbxr(If>6d|9m{XA>wi9uz9t;ZxA}z3+xf^G3ej=9=oU +$q2$peYa#7FG4|xilhbC-oSDi#%$+;e*gfnnz7PA4EU+KQB5S*QPTwE>K#m_hq&g8Kk126Ay|Vh&$EQ +%|pr*?nlV7PW*3{C2dtsJ0@M7=$esc1w59Gw(4qcST2^ZyYY3@Ll59$B)`q$X?<*Hvo{=8g5^_TEapJoi1<{{?^Df7aIc;~2d}>c+i$_crxcXc#|!yxDi`f$fo48L%~ED-Y@?&Znf +Lj8a+7)EJ2`c9ZirlvA~0uXOL{jUGMvb$UG1=pTOgVSyL)*VHf9^b6Q`D~I+T5qnf$^{ejZ<9e+Xnx7 +x{1irG-&}e0A{9J?Bj{T;Viwsgrr>}v2xcdn30K33>(869|x3QVfxpU{vxt_-LD2)CW6ci*D7Z=CdoE +ttv!>CcC*kB4gSW|K(RB +}KKNWwQGxs&*S(nS3Di%nbnso17FRgYuZHYfo#T9~yqF_RYJK;X+0b`(-QV4eRgAZmcbj{{O?pQGKcpwMf{Np8V__WSV*e3J{TJ85cDz~ka`Qrky;czE+B7Y=*C08I< +iO^hWvC>7am7E;fk$=`*o7>yOI6r9)w)9ls8sy^SUgUn{edL{yns*Yz5K}=nc8fs{L%jIXv*XP*+~d1 +*ftn6=1ada^4?f|8>^-PkpSa7!Tf}?Bdc>NI^f^HfdfsS8ym(x9NAMWoASDue3I +CmxShO#+-XYEJ!XymUx=*HeObD6sfh=p@+<#VyEHd;a5v!{q%dx{h!No?v*!RDdqf)_WT_?c9>Yr- +j9RbCgy@4*i$zxJjR#1<)6=6{@Z(!xE6kr_3=mK30xbytiFG$`+V#)baNf{f?g4r@Y&RpIF~#d-@Rwg +o?7QJa+{aMwb1El>P$~P(|)G8Gq2x({uht=Oo}QVRG(0OKdB +P;;xxSK-G(JmLNgB~FBRM-cC23SrX3d-Kx9`f)Q~8&YeUq}2Qo>UB+bujXvv*qdxUoqY;bB?Hal`r7g +5APLCZ=R2g-1uVujAy4i?^?P^{Do>XpL%L16rR)wYU2j-7l_RT+g1p@4qJ@!Flle^Q-rq|GQ05`$$Q3 +$;pzlB`v%yy|cXYylcv~l$P06Us-I=akPXUsS%Pd`tPh@~ZOc^0Vdquw$i +OBdjQ*sHk*JX+`Ol(p{zdN-ImNN{^IQmxh%^=-IJ)em}j!P`yUFUL{YjGf%Hnq}Qs@tL@V3Rq7Rw=rv +DrRo}I~7QU9gFkc&AgfG$;>+9v~=S%Pn^^NwW`*M7FzD2$w-x^ksVcEM=J8(ZW!J*IZM>1*SZ^@6aWAK2mt$eR#UY87O)E`006(I000~S003}la4%nWWo~3|axY|Qb98KJVlQ`SWo2w +GaCz;0Yj@i?lIVB;3U+&PNhLC4yVH;L8E4l?oNnLE!^cj#XU6d`lmyv~C6b4vtSGzt-)}tt5FkNHcDm +=zJ$HDzV~GL^K%r2m7Yf_n&chvVoK2GS>cCr6)19C2&DQqTw)Z^CS4DDlU3tOtfw#ZA_ubC^?*6X#Ym +&w*?>zpL7&yU}nABWZF%8S#9_x$afcV{Qh-oJZ$c8)Kh+}5Nm)TRk3Ww^juR +luj*W`I@+1F#k&RuzXvbMZ*w3HA +iW{dAz-VDa4Lr0^YdTNL<|&4AAc*5h~Q&9z822L$N0-6%8LvDR;!_RC8~&CMu?dq%xXM|#Aj@|9MS|% +lEMiJYv!}Knr6jZ^;^!P>iQe_DV`-?#N=20Vd%Y2%SDbq>UYOQkri88NO-W&4iyE40RI)e3!*5E=dff +3TU*psDD);v1$JWF8$*u)n76zZsh0{d5ffNKQzZAes)_(eW$5|&Dqm?TOVVlP4-T+)Uc^`P_`pjuKrH +-D13h_t8l9dUzdZR1wB+Zxza?e>5g>(Xz#&i$U}%|C*Ma{jzKu7B_i5#T=N-Z?<5}Ww3MHXlmEyqh+Z +cY^`t|rMI)DF?U+MJ{(tOj$=r%3_-#|;6O|r@#Ao9|z@@fDFUT3GQR{RI$gVOgN(8x?=UmWaTZk_%W3 +KK$CzW2zexdpxFpNwHx1-6dPp8o>Bn|w&1c^_MEdt;RgfKM5f2oTw)pU& +HbQQfge)j%n-+-y%hH;*YbQ1Uz80$vhemi;hOLY3z*GI2jy?x#fbG|7|=;He~C(qx$IF8Ow{&wsSknW +%KfnFG(3jtea>iK^7DNE9T3J#1JvG6LfJu}~KZ7D_@ogG7kLeRbf{52?i-`@)U{q)c2{OQHtcP<|dws +!FAZTj@XoW;sU>LAuRDBTwc%qv;~bL>kUtSe +)IP1`1#TKvG?Ej9cbX$+jH0!wy4wa_59@Ro8PbVo3woX%ki_LH$S%;9TonE^5OP37k~dyd`K@J!bGW} +FJNCgdF86{_kijI-(LLv+sjAa{`ZITM}xtK@=*{z9Q?_EaQfGGzr1||)kpsJH@vp|#*e>Qzb<{^`dJy +4A}4P8*k`(`vWR3C+*;!KXcBwbftMLe(p^BWZ3a0+fPjsy@hmP&?==XBu+;|H=#v;%17mFi!%M0t3Q9 +4X8jA+MhOjN7><3#Fv<}Vv*ZkbrR4&1)c2czA(60+G=C&E0-16@tm0uJuo482<~Neu`#BY^3=!QXX14_aafu5bPG#m!~i?P-!uq +Iou1%v9f}i`k6vhut|u3Bni#G^Pslv{{I9F{|W2YZPcwYL&xQn*57@th+kP;*PHN^bdAeuF~1Vy2OR5 +dD+utvjN=x`w?Hn3CPhWB<7I-2b?;MM>ByM72>|0Rhny-3-q|$T?WDl2^o_WluE`yJ!SPI>#?|igi(Y +5L;?C$W0LSuJFJ2c7IK?Laba)J#K%miO~!lb?KzO3+RWqe4eDIjC{@5czhl;;>nyv0a*Bi0JmoiBFg8 +c|_S_G>DJw0Yr4Wk>JHQ<}&b- +e%m4}MLb7E2?QN+9xR1;NZDSLxwPP+Z|N08geX{$|jw8U;Ql04f2U0H`NUc@XJXc#9H5e$cXmTlj4<( +2aynAVdw=FitZ3Y5Z`VEoPI5I|C*7gI$L3cuV>QY@OCm`HJ6PkidFr{|G_^NoXmI-EaO@jIZg(I9)Y= +10{-9)*#6m3!#ziVNH-!mwff$}7UsyrY}q56$JO{ +cP?cNDX5L;Ys009@d3aT1u-PPISabfMRm>bF=?#85H!!K5ZSPDF+#Qb+eT#I+mW_()=@ml~4|Zd+2XaN`~yoG(Mj?VT%{8CLcjbA}JI`KC!G@J +~`&ryg|<*t_qn-SPOUHwWH>g0_Xe-brM*``2Dugrs;>;@?32(lMLn6gg*ydhokaZYKqPHN7dW#pOW7z +6|x=*3n(&WfE|*Nd;zbTq7>*8dT*!lwrw`!x*tmdDvv<&-A7y_sY1YeA?b{k%Ru2WQ)>6WditP3Ob?_ +80w!U?rg#_`Th=?A*`}Je9*mND69hpSSPLLqQ)WP*)9U_i~@|;MhSRX7uohCi}H3nG)j1p7ngvib*!( +ZHIww(mXnr_{h-`IYtD}P5A{@BtAVEUp#nbC$mz6qwjaXL5OFi~c&Hr&Bt8T8DVyYj%iCD+HQF@qw3u +#de5ztnVi8hMI(tyXSFQF@rsc_3ax#6hX$A}7K1 +ht~<7yba($(N~(P!@%?iC-=bG*;Qa1Yf2$D_q_}VY|$QPr~<(v}T)3v87WWA`J<=TIA5(izKarWI%*K +w6eTy=r-F*K-6{rET#80oostk(!?*iToMlVMc1m`?(|b!Hu~q5WA4!x8}Pd7n|05vj1zo7`4qJfY^mM +6N4+tZfVm6}{RtFZDbsV~WBJy-tvc +XC9(!#hwz`7B>^c(@GSoeW*bj-2v3PD+u7EB&UUknuk;=%V2P^vv;{ck9B +q5HFHRE_K+S?4XdmYcVv}Ej@jE6^qGQT ++`tgJuGx%5iZ3ARl@Am*@e){y}~B?o+o=G~4KIr%};&;kMzydc+kN(=En^@2vd;EwRRWAJ(Q>hr^;a+ +d7Sgrd5}?k$$0zwIm!YLVFW*hQcRg)R57UdOqbge^hbsjBsrBT?Qm041^$i@$4!qfYr{GQOMDr=B?aI +=61lquS8h|j$#}^6)uWd8Bawt`r(O4agz$T#2_p%>?a2@J@f$%MGpmjT#l2Ztr6?~3|stqiD5TU0rd; +ztm7}G`XHlqutUZa%0lRO1Ox^0>i}5kJiZYWbC$1$xXa8JXzZ-#El2G^x-A2>rxjvGRGG?598c03xbL(?s?CGu|BLVNLWy^#N8%K-i#xN%rYB;sB^1l){BPC;E4{rK3C(uu{3nv +0@*#MKSK2|wx*-IQ*x729VS1Hy6~rq!E2_rXk8pjs2LZnx(x%8C9bs3StuWR +*z4ad@)5FD2Z=^yy+;jDF8>rjSUIHKu(GxGfQHs(dwTaKYx4n!dNHNJ!>iGMUbmmG;Cf@5^jp7su9~( +rtxkKA|Dv!L>8&&3oP!Y^Sq0Uy5zhiEDssC82;_#6!!m@ua4dwzi>)_o014V9PkEgXSBEq{NIYK5}!^sNwB&bg1d{boEL4K5eF;+Q6AYwuDiK@CGn$^`gN_xYfE2d^c)X26WRqVN`$@W{uj)ENO&y1tNqC +o?xTr_teu#pf>b$`07;AH`3?bT#t7;{oA(pGAW3ASBoiZq}K@=lS>`M0X$+y1}N04k>YQFprJtEQzwD +^uSsn$@nbm#!Kf;mVSgBs1GOaR4#tL&@=3a|O>NR(&&VcBH{jM5dB6%A`PipAcC7xcwx}lH2RnU{w7vA +|aa+vevJ`l(r3WaZd4+lD}mETg&CP(SwebIWJpOUMfCUUb#pr3f!9soL1 +Pc$C`XxwibZ07k25Zr-b=k9<{OWDxewpFy~)qfSYZP#=2$HG(|7)7w6^<0GBQCR{>(q +XZY@TNfNpjL(=#O|VmiQ70Vd68y|tLu9GG71lkQd)nqmCDZfLR_b6w+)mw1Q+D2-cyXJaD4WE`P_QHZ>J68|92b1u@gd{OLoDs0d`ltDJV0$+GpGC^2s19gy +fw4+Qk9h#iu5t!6xN}nOoU)Q|AS+1Gx-NWaO=#0auYLO{oc6h*^|6O{@fEhUQ%jz$fQcIAeQLVXxl`| +c&dpD4bZC8LfOanjcU9wMC+?)hLszMOzfFEw4%@iLpzsyd}nx^4`5nN^{#D0Rwnnas(o5StF2XG8**5u%F990H7%C +#9X!&@a4N02r9XUQT~c$m~22GxDU5NNkmW7YG-m|848xNF9FIL6rHfDSa6zXH24w@9*KdXEmhy +e3lQ^%!B97AALFRdlbByXSnN>@e +owPIpi(7W9G6>q{!$FrJve{ddUsrXgHqO}>kbdagt19|c4#_%VjqF=pmMz0RO7o)1)rKOJ$|9u4m9!Q +CfX1l<(+KlI+HS~FL$YkFZCpgwu<|JK8aGLVovTxs9Dtn|z9p3RIDQ>ZdODli_TB;NFuFU&1Ji^+SdF +P)A$*u}riSMFSqc~&l^QRK0=hL@$?ua4xdT%JaY2ha{05{mD>D{V)pAWxZjVdF37Tyi)kT6B3Gpy^jl +FH7z;#vS<-y~}S5R;f9K^1t(6krN^um-=NFWPQJ%zam=;6t1hi9x<9+-He_#Xu_z||>dNru>LsT$o +0*t*eK*tj)L#dQ46KD;78Pkz(CV^^MI|L{i3z^9z8ON2fP;%$An1QBB5cNuxC;&-L7t^5hF=*OEh;)Z +!#3VMAVBjWjm~kQUI32H4F^&X}LJQElSw#axZky`UST2&;elA}XRqJ +-moC?A8_Z&_EvQ}uYNks$$Q&@7GP*daF~y2CKSrv^ypWp=**et!UXsL4uuCyk&5JwsmbTxN{e4}W|#2 +)B$G6KXBpVx$sJ_5mI6Ir!*IIPlRp1(O24Yuf(!F_Satnl*9}AYqSVDTOo}{`e6s1Rp=z47QdYid9rB +P$Zxg8_Msss7T8Ve&%(|Mpb@k2o9=ijdJQWa1TBXRL|1bHLs28eI!tW{Czgme=19_j<;GDw$+_NLeHN +iw66u&QWd1*cWpiG39MV*AavK(NbV53YeuIzhRE@0ONTwrj+k@>KW-YAM$m5SV*h{`VAwqZtLit0E{& +IEAX=ji1kn4SU2SOfYOvL8Sr4x_gVXk~Xh+*7u}-bJ(&=_-_pSE*8%L>Ws3w&dgxncctA +ZXln@Uh3z0MCuQ>wtU+wE4+p%2}~@IjRfiZ+!p$AB*KOD<~H)Asr)_5uIjkoIq}1hw^WG-DP>m2!@)t +iE=|A^)Cg9Sw%LRjpFeQg(AyTGA4}C7%HOFf`_X?tEeK@8@1p;@LpsKJP4hNE7-1X1<0_kvRg4*ePaX +5fD=j!;l(9YL{gG=2!x9YQv)Yhg0W86gA$KbET43Qm|7M-%dEKJm86m}h4Y&Uer`Bf01u+_D%gSr+M! +(r{cXgPbf;osf@l^m(lN{^iJ%ofF1$n8^(#ZipAEsM3n{G +D<9h{`V{o5IQkPJtgOT%f~lEmb3cPqp0VIn@L7laz|=16kEbOx5FkSVVP8@{_Y{cZ6aG6dmB!JUr9rI +G>@eZ37xqPp3PL`uHGsJk*>`gL5-Ma!D;O_Al9SR24yu?ug=(aOdM9-TmnbJERb)c~Vm7E|I{UPOjqP +vwT=%rEeH}J$>X@LKfB!Woach9Jb{K^Rad})sJ-tM(~j~_R(55Yc#AQ(AsflL(h(kbBtcrhPk2<_IIm +mx+IXVn=pLjr0FT^nlr7;P(YKKVlx?3p!U*!0?JdGt_=}-{of7)c;s|B*2$;>b#&ckfNWlf? +y-!lwH}DgFozB4K+JPN1j7#8JgPGniz{-Qs_a3vR_+hs7bGrk&*vVQCQD`wEHq8DVd=_6S3SKjHW|1i +tahz_D;waw!Qb{#{(}%<8(=?(I~0NYK8XNTpmi9ptGha9NEyI@321?@wnnWM+dDcapfAl<0yon`3wC^ +lVk#Ibf0=gnM!D*>~gsbK{Tqe90{ikQ&ByJC6bJ1;_>(2{q)_FC;R(hd0owCJpj)$FAPHvd(uTb14gC +B*B*cAU4isTM#wK#A2y41(JkGNCi-&rhBo#_xk@tL4O(;PU* +VW8V?As;}+Z8)8^qAAyR;Suz~yLeDua)J*T-zb4?-hs)*ZKzV~b}Kq}lWm$(#Cx4k73o;5PH +E`5TgJV)zPtc(_XP;Ljj8tG-?dSVsaI6C6G=j12dCl2H!W`@MeeH*syWxH+Ej`5I +hPC;IR`RD;_b);*EmI%T2k={iKbPgin={snnQTiYHb-|+ZokGnfEo}Q9pamcrDc@4_kB*LtwG^v3MDRxG5s0UVqtA7M6#`ad1sSP=TLPrw9=l&*7o0dS&#WhF+kL2SUDuqVCBjLtm0^7sEot-Oxw$r +T_;6ng)0dc-gFff^QsU+*^`X;@`!ea6`Cg~;afAa+tn-^#WTLuO25P%ny!g*ri)-zH>J=Vj4z--@63y +Dvt+`-)v%#sVDIMxGBe1~mF@lAy(hc2Mo)K%HJE~ZpH?s!QVfGnW7we|{K0yfz(CRNtCVb%t1GeS(yx +H>{pjB%%`kp!tkz=f|8tpP>(G{NL&Wi4-W|Weg!Sj<;kP@al+{_JjI2U;3{`ciJ9oB+Zj|WLx`oA3V% +#<;Ni&=Wy}8KTb(o_e?y!(l-lK{T?zrKl)m-FSE)0vWo@0o;ZfINRt-lStEK$Keh~W?A9E6wROS>+7a=lNgfs6xxnqi$7oJh +P;OdX-B}sx4;WmH9{}n+4|Lfz>JOjH|3VH-%J!-7*l$^&n2WBi1P{sgj9)kCLXBMm>eG6#ee)^n_G8_ +MM393Udh2KxA9*2-I#0`_Ow(o8S(~5N%FWx6r8FrczLYZ=&3e=O*9QlasXp$Na3+7QEe~Vi5tc+tT`i@L+qh& +qQ!^VhmY$FVP~i!L_ar>*3$6v<4k1q+o8-ZC#SwDj3uQIw`_gb)S^ze{~zFY_Nt!4H +JyUy=P?H6;DphICaUIF_eY~$Uc1%Klw$kRpf~Z1PXWsuNxpG$ny%=)fs1F}At$fYgi9sHx!raox>a>S +zz3QRc3r)p>aCmHnu22MNJ`h$ZSrTn>q;e}!VAd+1_C-$&8V*GB5JGzq|Ni_b%@vF)qt{hZ101d%-t|aTXN3R8N2$L47}Xziglhfq +sWnHZ?s;tLUSWtE#aZ9~sMKAKN&TTmq@2g2NOtCnp;;}RXA|`77Hm?$&0pq}UX$tNJpo~s0fbG7u`)y +3#AzS7llryS&vs5S?a<($E|1^>T$(dmwOlT!;)yxk~q^dG +q$zSA!BT}Fw!5en@+;C^x%%~PwWnS7=@G7TV2VU}6s!(@PR`6+Om&P8od1Bqr>3{ybbMofpTgu5OTXT +Wz1t-U&3beCR+r!t_s9}XevOFv0upx+ZHXfB?154WY=ZV)-V3llOU(v{>r`vC-B%aEG?RxU7-GXD!^$ +t}>`Q{$y9mq(ocy&*Q{HnVMGxKN>q`34G4PTu`cDBjKYRRHS6fIyYD39j+7A8m ++t}r9k2WkG?<0Oq-^3Xtycf(%q11|q-zAtm#}D>i0@eK+uqS+f+62>2exluhtXlm+GM(t;ki_6v5PCdg!1#s4rjd0d-26&uqKodD}euyeZ0N}ck8dYTEhX?s*_MTIc6t99Lap*dpDBI9c9q}i>Kj$G +RoqwY*%Z2_p0UrSuAt+w$hkO8T9=$O1dx`DPgRO)w%32tra(8?>Ig0WuPl5P?-h*Ta1PJ<9Lr*|yfsa +K(@CJl|v2*iZEpBKBFbdee{j&z!ul3@}Jn+Fo@7$K={4S_Lj`k(!gG-eq2}Wq#PD%4b%yfvR;|O~Jv_V#5es7F7Cu*L=#WFGP>YbCHL*B7m_`!$eqd^pWm>B=Un?J$85U>@9(YSe;g?KqVG&S;4jzi-LW?LL0>c +IO#D>;|;&)6p(6vAoUWTtNX0QvX3pMIcw1@p1%T(AVB5jl;U=cPNL!Jp4R{Sf`|WJ9>$^#KNe0O+nDv +Ecg7ZVnPH*t0u;gilfAi824b{zWIi`K9AGY3{=_AE!u&uX|BeeP=oPU43+A;NJp +{?{1X-e>W+mh9d#VmR5nbPEZ~w%W_TG7*$I&q?stl&3>U~U*dKb|_lcwTL++-MxUccO7iq^~R~UP<-+ +TN23?1L!-P^rK-+%lk>HCxQeg6ZyQ+LJYZESMiy&Ov8``+CTznKpYZlL%RtGsDOOz1H$%Zb3ziFCZ;e +0=rbjGr{4N`~ge9Q8GW3Juns;u~WRF~Q1u62 ++yhogjF^Bw~3k`U-V!B4qODZ2j6)a1%mYd1vs~U9Qc`fg|ZVXz_a{J~v`_ehqBJ~C1@^WU{)p;Z5*Z4 +(k^~PKH=9*F0L8JPjOIt_$KTt~p1QY-O00;p4c~(;+?`QnO0000I0RR9g0001RX>c!Jc4cm4Z*nhWX> +)XJX<{#5Vqs%zaBp&SFJE72ZfSI1UoLQYC6B>Q12GIl@A-;R&JYB>@drI1@dqfIc)NzIQ^gL|{yi?e& +5S&w=NRKhud|V&^ea=vI{J>!!?rFsK`l$oqoVOL@?g>@tbKsRXh?3DO6by#6vA05|8kw4mX=k0)5}<= +6yq-L26=gU#)A5mzLs2mu6f%9rO0N%LTQ=u)iph_Xe82$iIO9KQH000080Q-4XQ?H6IWzGQr0Luda03`qb0B~ +t=FJE?LZe(wAFJx(RbZlv2FJEF|V{344a&#|kX>(&PaCv=G!EW0y487|shy;b5Eity9h5|XPK?VdE(x +FLD!yw2qooG`eL!!6%?!#Rr5l!+vJwC}(SiD<+w3RZ4J7}q1e2N)1Wm8z$rgQ3WB*<4Yxc%_)7 +WPMkZyg=2ft{`Ck8lWIY-=h(%9w?Y%!c?$&*zO-U_fPwW$6ZW@J~o+5?uGo-SVtae +>p+=G{Z>^gG)OJHN8e-X*2u{1i-2HEogxCPPm%9DW1I`EIfo^D&!mt?FaCftQ(LO)^EG>rsK8JI1lBrJuErzcg|-6C@u{4EQfkOY9= +!XMr1a7ZgEJhGjJh;_YpHzo#qNWDNH)I;)ElW{e04Djf0(O&Q*eqW*IWMEq{*GUZg0mj3;4aU!OnYXR +pk>SR7@$&5WlMNvV(QD|tR~1Gov!Nf$*ExuWk={oeIuo*>BVpTFBVVk{~X0dS$J*50V$?KN +OY%XM$RXAZo)kb>f@aU0dp{x;Konj<`wpOQDAv-sNg*A;a#!6P)h>@6aWAK +2mt$eR#V0~2Gda-003)b001Wd003}la4%nWWo~3|axY|Qb98KJVlQ7}VPk7>Z*p`mb7*yRX>2ZVdDT2 +?ZyU*x-}Ngx6bueG^u*R37I$9g4vyDGj3jHrkL+Fqff|xcYNFu`GY`x3-v0NiXFq0!lGX`uh(6dPr@O +kUy53zqr{?iDkIkxD=jHa9>DtZX|G-~PPEStFi)w$U^X+wOk{2^`_T=fA`EYH`+e3R@mF9i5X>YUInp +agIK45Os +s>#42SA1ef_^ergW>W1F=no4VS;<8+_3*G3*>%Lc#dxJ|js+ST<*TOXd +C82F<(tEwn0h3rygVC?Dq6wkX>igTZF0-}m{iB?-vu42INGw{M%%1+*kd)tJdx_?y`Q?|Ia3KT0{QmHZ*#DSXm +OqOrm08}3j}V2%*(;3Z?e2-2m`P6Kf?Z0w+$hon*IZDH_-ENh|~48-IzeflTA~v=4P{Q+PNugxoxk{p +MLwz(O$Wqr?11eW1;;`mssxYQ +AzrJI?{?)ea>QdcYI$YO{mOXm&KG>li6mb%a=<}iw2fQz`7EMO;%&e~M>V|>FeJ +NG)>!w0cwa;8NHO>W1TCmHL_E1A^T +>D@Gv*z#XH%NIG#C2l@{TQRS`b4Z7TsC0DId0Drv~k-*;OzbCB6WfD^OLGSo;|iHyTam*@1;k?gPUB? +Z@j31Ei{JW#2;!+Wa@rS*>nt+W=^P8b`f-t)lFp*N6XxRqu5!1#s6)JY}?F^Q8-S(Ly+ +(Dro-hz-T+kkjVY7)f|~%tFSBuQ16GeQYJ4J5i~<9yua8KKU`cs)hf32rG>lhbRGEuM)Eh#+u)pbGniGDaTaNi_D!?6*=OCKAEicBoZhOvy>GSQ@yoq+ +Pvg=W0xH;j>&vxC<6l>QEQ(!Fer&F@>(T~M*OYHMW^+bCAbT;73^3}LY?k8!l5>Ms?Y8vyQeNl`s05! +>SJ@j)c0ck$xJ|j!>IuyfFY#56@YBl8~gXpmXPQ}e7`NK<>RJ +36ckO+AeuqzpueR>z~F$o6y2KLZlkQDV5^Tl$4|(TCSW6vS}*8#Of@fk3$-$Sjct<%&MXX^`hIQNV2reiD&w8buBLeRS?K+TMG+f3 +i4(p9-DMSSaj6Y&2aa4-B?t%@$cpjf>ewEXw`Fl&9^4uPf!9HS2Uh2HohoINzv{##SIUG)3i-+@$9lw +C;-6zHa1JaM+Me|qCJPbDM~iHBOwLQm0fqe@2TY%t}km=2e-@IbCKN`-)86-FbF;2y@X72 +6K8bv9Wmso~MKY(XbVP&;NkJjbUhs7U=4=<1a>f^%1_U@21lpy^AC+4iTr$z_ARHJx+jL6+hwh9R3s5 +O_6}xH(#6wkQk_sKwFfSU|U(DNu^ouCO&SkN+#Sb3FWgjvEnLc}H^XZG;HEfK}N$(Y2^9!^K@=GoU1} +CAdX-<0Zcp0)X?P94xYIyK4)ZizO129>?t-xaJy5Gp;zabb2+iQIR&7vdSNLJ$&}dTg`vW?<3{2BpA{E*KL}G519hUG}mNt=>7IANU%G$u@X24i$5-O8Wum` +Dl;Lm%piF(UGh+Kr9Yvh2)Ad}Gn9e23=Jrp;#TTwdNpM)M$(mp@|ORsGvEz)F) +LmDnLpRp!$r2;)GRXfMm3QUOZ#t}~1Vi55k-|aB`i(vXNPy@hT?AKp(Z0kAHsoCur?4f6T@W@EGzHf%Sf^R{G8ro8lqLT7P?AOQiG?`znff~(0iSh5^-`X)hD;>3-K1W{OE-2qR&@bDa; +lR$k-soXut$6yRA8W`t7&>Ib2>1Lo#{6r-dkAq8oAw8s@Q+F{2VxM}^uL|GCItytA=A=Qa*8$Q +F{LKnu)uRo!Sj-z$hjA*Mud5jRJ|Phw|C2RyS!32~}%oQOL@#!XigVWkv1(sDF|nn;i;&*VsG;&mj%`kah8nbADFZa; +m;}gytt+Ai+Ini98~6Lne9Gz^YZIJ@Zy*i9Mx#34vrTP5BRTLbGCbXdZQ9osW=p;66mrh4!9XS}ZnHy +!Z7`h5XANr4ZVxDzt=3q?=%X&7fCe@qI&2*jf$3XKcsWIG>|PH}Y4e74?$9mb5U?B+1_Ub~&{Sn5M_N +U-;(jt9DsQw4qse!9k=2@)^@DjT_P^og-I`Y6TcFMIxd00W&N{xkVva9L5#72)#Pg%S6I?Pag +y9%zb@$uXva~EN|+6$-4bp^*n!jJf0EU4fN=Q|*CW&A*7MRoKtL9Gr;H0iLuunCL3 +B|e)GTI$+q%;nkR0dc~-uS=-&naAyMfWJ8kSh`hia5#;p2KDXO%+upvD;-yjeC|__|i1x5n$r6UT=1v +(iPTTZgE3#qSUBxt-NVE+i=k?-$CSnAqX$MEvlx0XUMOfqX(`R3qAu`%dz%72PXF&@Uu4lXw8mVCzs+ +XvFQMX$buYEc`0CVZ|>gKSX`8(g*-%79zd(pXrSNZVLOJ`VPBF9NF7!X79;08J!VMOtYF3h7VT^JzON +F~N6oY+<9O)qV2xYjfVz0(u^3}4^X=h@+s9%F&yMZifFKEg#CUmALUdur4QOU#iw*sd_WL$MuICE|(e{GRDjbINFt3&e2w)qpU!EJ~v@c4c8J$e!`|1)fsA%t5+#lp$4;!$Ug-A2mtrDU +IO7^2msri?g4-pi~taiwWh=HtZo@ZKq>Y(Zu4~#R6@u#NhR38vpLt^@k?EGdkVqQASH0Er4r5?7u)s2 +d%=d9(gva}=RoHUUgBdNk?cR(Lr=8bBnLC!xD%zZ0;!6t$c?8>#}D#28-o{IOIDvT)yE8wJ<3X`zgrs +IIw+)S46>)C`7end>?6Ae@H=~JR%8lBo>+6fMl>O@R0cQ}Y%VaTb66WWlfr!;(Ewe0eurPDCn!F^qQg +5YZ{XFw98c(??WpoJ$W;aN=A?z`4=ONVg_*wpRs)ch8>p29dF*)e=yblNol1nmKvJ;ds*bvuD>j_ObJ +DVe0(FU+z@y8LqHS~pawT8|N8<69h6r(pd>2>aapJjLyqJ_Bxlna)|H&2;(;4cDOyz#$q7c)$?Vinwb +(=r4*_yO`3RPcH!!d_0@Ff#EvQsFa3D9zuOca+AZ808HD~8AOwGlw~4GU4`K6U~>8Bt^ToA~neGxPJ4UnVKfOB9s$ZE6F)Wc*I%K; +)(cC;svCloH&@3au0B>m$4{5W);Wl +4EC!IA~;ZXKvAC5hVwnGGI2wClA0g#But<0RiooRi?58_%>jo^MPbh81=1xf7LJC;!(% +ARE^n^EfwZ?4! +qf>>60m9vVzCaUJH}7ZL2BJ-Lhb|!pwkGup7=all0bKZX99Xd?HUyuCu?XL#uS+OJ?nXjiQM3^In}lF +HXc|r#DK0}U@npmvD^)Gvmlf#uaIubWO-^Q@Ss{zVz*T^c2d<-W>OIk?qw-jsIgBxbxXaD +HEAE=4C_wo-caJYkhX>lsCV;imQi1?JHM;s7`4Uq1){=F +8^mrfXr;vmY;@ks;!X!#zdUXhO@x0~;oKDa(ZBXmGn!Hi*#COJ_o%{yfNff4z5cnmO9U^tJoLML4u6> +$Uq%rK`zUp6ZAlvEs9n2kk8?{<$(H{cFC*-j5^(Ik~-Kb^jLx5a$ly>$i*VU%vX&bI4}`jPOrRHTaxE +w4#~2e2aQ+LGo_1;>K;pND(kRy>oNNdxEIW=`|5Ano*4`uC*#kq?^MQC}F5CVv-pjIpghdS?&ljY=3@ +r1pIyC)BlUaDG;8?*jk9qSkx8!s>SnnFaGfQFE1Cb-n{$s^AGPaZ{^a$_6sIM&rA~4VCLo`33D~hac7 +rvE?i8p=#bTq$w$oQQuTNkBu-=r6n`9p#gNQBu*iE}(#>aIrEz5k2aMBm2Ta5nfZC@h_o|DZ15bFU-eJBtyPP?V_djd%dhplhdWXGuocanX{DnBnp<*8Yj+ycPHE}C +6w$t!6Am6+shl{RhUb1q>6fH$69$b#Z$4eWK%l+@VkjfMRlZz0e67HB3M{U?%7Ev3U)!>=yMG?;#w9) +xZ*SC5MZVrDq?4^=5#R&URqqot;nk!c@@s4{nhs$}S<|ZNT&q-TncdZ&wQd+yF?Vt;2y17%|_@a87mj +MIfp6MP&JfV*c2KL}N^zqF6=KO5z_;DOfnZ@l-;11x&MJRY$4I0=}97M)+Oc{G7hE)3K`!>Skm9;I7E +G118!5cur3{IuCuBsNp6|n{yy~tOe-3?89knVleAw*zR__Y_?ltMgAyRur#++kWbW^3{=f1aCNUUq1) +4np8#x){EZi%YJo{+*ffa|blGaL){#JZ+3rA|_nKpO8yr6~zpjqKk=Qs;r#$TSOuoL@uYo7}vLuZ2AW +n-UIcfn<1PuJgyns>5=YCScVQc!xtn$V$Q9Yh(`b-qykeQD#q +JQ{uznDOy0d(iKR1W7nCbq0cM^II{NXeEGvk9C4J@utBMY5f}WY9nQ@&(?O?Z6QIs&dM#4z@ox|wXWfVYhMWPS%x0O2l;-_x +E{;7NjMX~iqfWAI&@*~2`vw7gwH(f=2jfr&r?|r!dv+S!6s7+Kk)}Sk%}^K)?x1bJs(;}eta2b<|9YJ +gQK3VyeVOudzOMZjP8-X>T5Ny;rTp73H^aJ#fx1c;Q81giD^#*g{0{%gt0fQY+W!>MLOX%O9B2s= +isK@Jbmi$Y17BIp +iaEX2K-=qt01Fq(BPs~;i=PDip35jpwrZ&513J@LadY|q2lk`J;{3O&4Cv +ln7dUlFsuirSB3$|1y84%`BlKO=Kd+4Nl@^@m#wO8ay!XwHJli1m9qM7IHP}Sk$Snk?GT}8%wpOWs3r +jJMr#LV`w5ut?;S3`zmjjHK^jP2MKrfGp5)#z5E1Ytrjmf$$pBjvuuKA*O?_5p##d%o#W+;ETT%HQ{! +Ye(o_2(kQ>{S~aOyDbXSz|HjzS5yrD3@2k@T*9o{*jX|^&k40$?2COU&gP)oa?J3#h8$r)+}rqI|V(r +$(sSa1DD_zojh{QU;ZGP;&+|Pi;N#O`9T9_pwHCU08cIOGuq#zch(|OKz%bl_zXv!$}2!*6RGRLvK=XgerDKDsdGo`*<_z3?)4%gc$IodmVQt3_?8Q5_M4?I@{ +F!(tDbL@#L3F8I#{T{*f-mK#I!QuiNpL#(=&rf`Pa_4id(G(A>HQJNyY?GTdc1?(LYVrx +ecYl>2ehK!t&Pwr}Y3&u?MT>nr?E-$M!y_3+Z)~XHW5dN=1UC)2&>VnbN8V_PzP_hB|F){>MbAFYayJ +JBeIoYiS5@mlO={rI#}(W|-S-q(YJD%hF`BW-}#U|0 +|Iwd^-X5oI%M}z%9@)~6rLLM!+&ebmEa=|8dlR(g{C`kO0|XQR000O8`*~JVZx#Tp_5lC +@ISK#(D*ylhaA|NaUv_0~WN&gWWNCABY-wUIUt(cnYjAIJbT4yxb7OCAW@%?GV`gXVRm+Z>KoGpoSB# +WvVx-)2$|+I~kv2J578;;|*5YY2J#36D=iAdf)*v2h*khE7<>~I~nkpCgSQ6tUEFGkHIjIl&E7=sY${ +CMjb%G9JPY!_(T0hYlG^N_-z@X#i#NHXqa<8fKeM^?a{SxWN4offCpE=apNF^nw@mv;g2J6vg4MdZCI +Q?QAny3K&s4aQzNmfOmD~6=MNl|OG`sjeEaxsJj#qCA;bWjbcOzAH=03WNwc+(#%b^+%?t_qAsH90Bv +#zS8deqsE@a+Ozxy92dqxC$Bj6CB#F!8JjVk5Sd!;9$)eZc6qgvR1~fkzu$s96?$8ob0u%!xwNY!y)J +7{7sdG@dKbaZ2^g|n<)ZD51&dCKbs7=CEUd}!K00fDuIE!FRbFPCc?BZ8Fykw_XPj5Is@p;}}cXaI7toOi;sp7?%?g8PUAEBMew0gkMaU@yWa*|L4zsV5bKV;3qNvfz(0DT$94{=aq{ogHvM69Tz2%wjKWevZ@9S*SaNc0dW(x2v +5O9_Viy8Apr)Z5yV$)d*Nyxi&38nT_!6d0{`r5Cd){R31}h&Kx6h^>rtH!F7f?$B1QY-O00;p4c~(>Y +dF!L89smI5XaE2z0001RX>c!Jc4cm4Z*nhWX>)XJX<{#5Vqs%zaBp&SFLQZwV{dL|X=g5QdEGs2d)qd +W-~B7FQu2^WBsy_iU(T)D$FZH(*Ch61xoP%nuR=+X#h4-BbLGcE)xQU%fzfJm~4F3X(2u_#%j` +AUL@{%dQ|DogVM(d9Ftf2Gy8oa=AmPOL5RbOnt;78XmUo@Ze`kMbe(3}bx0R^)fvYYaXWXhZ?6GTWlSvrIlL^~lXEe&YZ +FhD{h0O)mN*8rx&mjbG~2`5ia;d@4Jux8OYtNjsQ +P7FUmY&c0X!WhjBDE4#F5grwJ6Yz_SndEuWP|IE}gNI5|E!`mck%)5)9N@d2EOB8vcs0nzsx*x4WkgE +bxt1*zYF&t_r5rkwFrR@^a|KFlw#&t6=xB~Zka^MuWoVRFgm#0iRbBB5ZF3BdpsfMw(nzlQI?Vu%RK^ +7JwfR}2w!E~0@ntO-tKb~E<@VcZCKfMeOt4(laFj}Yi(vuPQ{^Bypl4e)IAdix@X(;J@q1NW}AC!|gM +l17Pd^&Kik14R1lcyImV_&kxb7f~_?`K-H5!`T%+2Vpq73NJaH7KyaUB*;Y;M}_YN-jKZ<08>|^CK3vCmS6lPm_gkYP +{WNzz_N3I|M(ur5h*7F=;74^LIL`tsKyPM-4g^M9I^1K{x=cmH{Cl3#Y7aa6Dxm!I!V}PHW}~zbnyOQ +9N=CpGG7c#%COGN9*Be{_-e+Xg +5jXI){N?*Tw>9Lps7-^EwZN^>)PAa +0o1Fiq3*DGG%I}T;MItMLFz<=IS1aD0ri3bJzJ*JFO7YYx{m4A;4)9k%zrs(?mzVd1S>4s+pQ#j)#D7 +uT&y{|G-i8YAnhay=So}D)p@KH+XkYX`qh@`^M={`O=i_HVL&Qu^6}tge0cN$Hp+V{0{WlFo{|lyEaC +}zDQDZSF3kRu@lQu5r`0I;kPXL>$%muK{*fAa@4%CTw}+n$Q#Jv+y)s^~lTy=Rnfb;R0L1KaB+NstW|*%R^;x6CPlWD`P#- +*snc`pC05TgO6%Vo#s^C+~6V}8$jS7d2$`)Y2pM@H#dNEvZ2w~$u!azt8`q!fo(`072xxLKZxIf_i)$ +gyY}y7>lMEv1yuJ9@R6Zu;HSa*H5|qMwiOGVNx0U)A5c2R#And`0&t=wOx%QKpl~+O +#8Ztx@~-$c&j-L{P2*`7}kh^SeW2%dB93YVBEm(N#h@7J&Jm8`H-{~c_KJ;5Q%1<0VfILr0U6=)MfdW +a#&?Q-7Ld`h10Ywn3swTz9QnyBXD&=^8#OGmt6D0=niyYQ;ljc{j0$U{Lb9j5Q`~Kk9qm!R|7R6TsTXwR0arZ6Iy-3 +$WVIN_g@1kiQ=64tj&{)9Jr}NVHu^5KZFJpe>Y#__0vVFj;Zk3ROQkg0AtJAg6> +|7k$lj~oBT?Le>rS4vM0J-;a99KE1Sy#?O3|CgTxQ439?4QgaAm}6r2SE4l+Et{p2rz* +G>i#2k3JQCzdOofMSgb9XBjxPf?&Zn*wB(@EZf3ZN2o_3BLp*56mFW=P;544J;JV)(d4mhPy%zj2J2aWS*t*Tx|!%mS|JNK+ZMMw*Mr*ZMeui|12E+V|Vo57wX>^qyL;-JRh8kXFKqJKX^7!Bu?Q +G0QAxU4FBC=+{Y;OIkeTklC-EF&=WFTWriY1GoBa(#}k=o+3_ +gR0`iVyEL-gKLaad5`hJN{m<$w>!w!{X@M8GG30NbY!h?wv7J(7&-c^ ++l{fK=2&KKlyeSH>v^q(FUyV+p8L!^;FVLoKe=_mFMa9wxS4YfP+`KKuOSiHFA#0=fmyfoDGB161Buw +iqi`nFOR%weP`DP$y;pNFmsJLNQz%(H4B_IL{I?4<8{Zt>i^>T~PvNUptbeG8rFHSTvFu_WFUvF%oDj +-r3Q>xVio(jzF4wAq_xUOV88Ft;gl?JWFGloI~G=I+@{#I1+`gHc^@PmBRm6^&=JunFr3V$D|pf10`B +a2bgLEQH&xBoHGStiXG-4psz!aQ-~9SLKC7uYeIo4mOyKlOIC+X)$nn~XAx}VEL3ixl!ryiWKd2*sn$ +z|45}-Jst$~Ie$5jFDkAUF=mIiX%5Ab%G+Lp;DNt4#HqIyJj``l++jqM^j`6hK?N?v-j*sPcwS6~B6w +)9_{A%!kea~JPCIxV{wufbuukPt~2P$H3MQc_%SgoXSAb6Mqw@dRE<%cP2?bas0t)v59G=!@DB&Kv|6 +YBz#%Q|A)Y(x(LmpgYtrXJus!DtXKz9McHKWI2=L%P@z3^i23aM&S3Ux@#`1jWp9MA11!BQKL1-Sc*N +^YzrghEXBaFmlK?TZC~;xeH0XG#Jf<`*y*M3S6w+gHMZ2J2rR0eC%lH7v$*y=FU3pK^$`SYWu=DlOve +hSOwMrxS9!lDN>$4;yCeRpHBzWE@5e)RFPLT{l&nwzYojZd)A1?KjP~K5iSZ!M-dO6t>B|g6leTr9r7r_ug32r3$vVdfvasof@{e9MZOJ5oFEuIL)g+ZTy{v%H!AK2HM%Z;t +G|GAZ~L|mj3*c42hqInDW8Fs81A(d;99B4QhmYMYs8J?Aar1Gq4xZ%Tm+|9aJ$GEy)bL9Ufc;tS6Q8& +XaX;%GNXG#((Po^raL9o9AhUVi+7J0C7X5A09mBVcZ(>h*Q4rbo&%9&zQ){JW62=A_J>V)S*H&tJU^7 +!=sTv|1bnZ{p%qDYY0}t@9M%bzV*yuRfQtTxDeVjQ~CGW&D7SP2+C{@z6;4fD~^VZ$_;mxEcvzPeIiH +TL{zYqN&`##r-AlnyVKN-FqFHVL4oU4S=VJK!UE(s7>i(bh0|yehnH5uRmEoJQ9ei@F2NvQqoYPPuwm1f3T2C)0(I>9LTn+-DZkCL125j^f6+V`GsVei1NQxuM#@4RHC +F;;90ltgsnk$JSIWOBr;T8Jm?5XI%IqI2AM$+_wfj%s(!1;O&W|9-)f1k4+^Ph?VLL+5C{c92pBk*Yy +);H^siV(EBYY~j8j7HDj;p@r8jji!S%5TTQ*a1xmOQBsS%IQ(pPGzZQ!uU)%71 +~h&H1cIbxf~Y`cb9>Jjn4+yC(V*d=uQ315(r6}{4t^M;j|V60$ihMD59BOTZMR}w3fo}@Xh|g_> +|CVi!-C+o5K(8z8F_`^gsNt_wJYdgX6z8x<^($k?cD@qE23r-OWAY`N5|H)xdAL*`P!_HFjm}ZQS<{P +dqb;_Sia8s=Ln0X$(8LdweM1w>+~Zk7oCVueId(cL-lUHY`g8H?7ETuP+-IMSrc(@ACn};PZK+i~;bi +_@WJ9{p{HV>rM^W`3T< +$iJ%c_!t|>hOKscgxHP@_&Xu`WS4~idk8q|J_ZXlKrFowLTC3nkn4kCMDyco0p_yg~1*e62yk{i*+Iv ++)u?9K}%(|y7JZ*{SvNP34tgfs!pt2^Lh5zjx3}yJylrB=i;UGbhrnVu~{4!uE(!Ahv->CG|#c%_2`u +Z}%qbHX-+Mc@4Zf|cvPH{Xs@aAs91Ov99z6y}O7|Avcyq>uwx0t;SG2jk`Xkc@DnxivFa-Ai-c)k||T~pEw9>-_**Q&?T*I +wV+^)x(6~s+=vESm8J!=#lXaSx6YC=@lMlnMtw>XE +mIvh}XKDRAEyJc!gQ*N0DY8CbRTUcNbEax31ZgUn`NDlN$~noISi-|QB-3Sh& +A&B3!KxM^R_sYPCd|1i)PwZda57%WI_N|ei((XtDO>238IW>?%TA+yFu_$?79SLq$L?#7hQd6e> +=knWdm0_^dzDXF8gFYKWvG2I!W@rFmEDlpv#h99S +UIlGxlA4p$Tb$OYsTfX*ax`bdprp#Ab7S*DihFb;6{jM1(x13x)fP +J-sQ^hG_X*gp5MXH-k8mCZkEq50rkaBAz|ghEQPXYE8l^fkTu@oY+QlbL7@d6AzFb0Wwt#4s#(J_ewj +q43c%JZBD_9A;l>R!XQC$7c)I-}S%zqth2Cr;nSK*g5H(+2)!v-8w_dD@&XS-C)hVUJG?IH{?QL$v+hrPk9r%^iKv-gG!c8y?4PWc9}=!2)}&{!xd{JIYd|J+ou;1m2&xw>O*{UMt8wB8+ +qK{QTzmxxfh-{Kv=`J^;Y0$cdf(jhz=4&mIAzg(?OnErSj}T?zHwSX(S;08my3C?k*N(s#i1fIB`bu$ +WF_OElyn_rxq)st(yj(I1XZ54I&;%i_8ag=3L}r!j*Qp5C$DETj5F;KENx)89Bnd>`3@oEaD*Q1OPb% +Dd1N&M>(w!%Mup83|>~&MAfFfJ6=?@I3m)AU@k@qcOC5viFlDrpHuyk(98ivd5ZjV0zZbP67gWPSuvV(frmyV-;smqi#kD5CFFUtH>41Uy5dOh%ogQo{`pm0p +q|q#(^*0%jV?+>|nmb7pMq*0$P*su9a~o%l;_UgG*>2A2lnPO43;EpL!2Dz@l=M0)rxEvr!AC%hx@m_ +1Pzflcys*~#D}N1gXd(Y!3q&1-D|r0<`PpATT11kfJ)aNRC`#Qf_1>RkoOw9Mt(D|{GwPpRi0l527s| +1{yIz_!*DytWZ?(BdSaCL>sB~(}9PbR=AC5<8`=5>@;${fmz)WLI|AqUncXwCO^`2$Y{rVsvJMgSd +!DUe&MTfNw>tUyQi%8v9=m_SY))E)J%P5H{h>WHYKq@1#tVWCk-m6$dvqp +p?B1I(aFn8agHnq?v31Hg0ESkv}ip*0=B*D_EUW0|oQ2S(BF@ +sI#5!+ef!)Eh`egOj|AlJb^jxUwDI_8gKM^*oyFWaNl)1QoH$97x4CG0riHzInZ6KMCV(88ZKeiv2_i +6GdXQ5p0b_nQWH2A>^Y_-~Xe-#zSs9te!7O8yaZZrYDkN^AWJE96V*b|V6zmK=2G&Y%=z>$ +d3fb-Jt;rVn)`v;KWBhXPerxk|$apaun4qH23F#2FCMs8E*H;?=8PK9~E5Sxg6DShl(2iFaH8Cn!@D^ +WMLWNowgp*~GRS#fqghdQX?8in7hb`Lj!-80OIX>PaCI}uz9Zc-JCCX9FVVOB7_Up9FNnT(smqw&QCefFvD2CeO9cocDth@s=OsD74p>;! +uxLCwFGzi>yP?tKO&;z?;6~oSAQ!qGvHi^j#nVZ|_DrhSE%0?Tg6@}**k30C05 +A%;r;7=)JjLV#EOs<907S1|Z#9UZMQf6j*!Xz&L=wyoO@d*)r(z_lbbWpB3ISb_(OwN`gH#x+i58knF +b(=W^!TQ?ZE#$ljmrec;Ts%1mTHA#yWop_P%b00j+ +L6|Z&!-I73OcbE37D(Zt!O~#DzB)&vK!;p%QgDOF_V +HWbC#7k2x%@bhvvb_hce?r1~FxAC($b%m1j)Hk@74y+hSmh9$aSdv-3!E|QgK?Z!9ZoxprJnze<#h$N{& +-3ii}PqqGG14MnRd(J_ZtZ0iC>b{n8Q5JA&6d*z$MXK$<_QnCYL4>r-#ymfA7wH7-gom;+49_{E!XtW +z!91N&IT4>X0KtltQ|KZ^A1f5e?^NTZi?T?Y_+XzOIR=-2BEuxSyv!FTKG7e-+MSt{wc{th$>en?05D +VW8n;Evm^Z{jb+;JHzDyuJ7I%^6r*4@mll8@XLQZgh!fz-qSNnOn|6VUBmE)0| +_z|r?$nCg-T6nN{q^(IRPUIp>xBUFj$(bE#_v)hz5Nt}eOLI#V +LhpO2dWDGh@Gc7+57Zk*d$O|Z-p)%CoZ_7m<3a0@=ZLlKXSSm=w_vi<%3BhZ8g&O4nR0yYtsbgVm^#>tioG?*A_v5Z#;YUYU9CUQ|EinW>Er)A_~WQl*+-clPb92B8@& +fjzANJ(sHN0(slF4$9o;Ix_U7F{SN>1C#0kKBiBMtjvA{h7~AUw3^JUL-oEY)Wx-s2=r>ZgvK$96X9rnxF5(K0tOsY6n6+}- +`|cisTvjg*|U=ic+vZexiA1_NL)m>CRaHn+e1v#}XfadDZ>lfmM4WAl%J_&i_S7U}G&in`Bx(eBR9Zg +d;=Ftyx`2Alq^?NW)euR1l^La$|%BV=nr1+dn2OFF4;!RRqr)8PuSrnyZ6kR1nGQN#wM +VwX1v>!#6MUq5Oei==!;$oJ-lPZs*IJ=D&Nm1q*z{tl{oMve@i{dDn0ESpRs;&TDnO{~nagkt+X%v@b +K1pK$98L4d@;b??xJn5IjuSNBt*)SK=Ul3FdeUArP2xFVm?2y!ajxM;H)(a1FDrlnJ*$dzf)M*yb~0a +15i3S5>pnio--%;y +Xv#m?|37})lC7_hkoj(Zi!AeP+Ao2&d9YXFS8gE%Xf%OV2=VBDzO6#gr7fCf1HBbikEl$yHC=kxpqdQ +Es_(-eDK?$KOdK!JFie@>{EOh!?bSI`T_3ulZmV~UV>YF7pAMt*)0Y?T4$cmvqx0y^+3DYoUL3xNItS+wJm~bJcSjdLoxZ(@pvc+5$;B_x>C5Qg +Y&eP%hH)n_E=h5j|boBbo@zLRnespy5{P^vQqmx%r^bG2roL)r7N3V}A0P@9YM9m7kqr-E +60WhQ2hiA`!f+q*hj*gEmet|K4d314tkl`Buj}D^f&B58l(et;*2kG>hNIC%juPEJpbPF| +itqld2#Pc8<4|49T-qv-H&@H;yH>EQU7+l~(2LZ{9EGQfR)`sSCjqgOv&0F0kbk6#?(^Jj;E@WHd=Lv +EWo{QUUf=ygAOaq#-!)gjkC1!#Z@7GXz|q?Bk;q*(<|Gg`YF8=X$)EQzNP6-UavDbgxIA_tBNBwk#{RTLMKtMqeH4zT#f#%S~zmJ(D4u=`Q +xKhBcRDgOKqcqeZn{J^`N!S{om4%CR3z!wUrfKN3a{dKvslRQFR+Vyy8535aqgJC7A_8%|c*B-Fq;c$ +CrcYF77H2iMw(eB>PFnX{93_9w*EYf}i&tE2ED%Oks1EEYlm+6$iAe5u&p0Qm-PoF-1`oseIn1TL1x` +vK#!=S)wnkU!eWib+pMM`tN=^=WjG-vA%WrUMwPO>@6Fq6sR^h +caj7ZK=NI`WrM_sqaZqWV!lLrPoPas8~*PF=R#Rdy#BxRK@My29B*fn-vRSz&F#4nze}@eep4cu0iOaYdNSCFia0HSah>E9K!kxzaK9t48&Y+|Eh6F +UN8N);I6sx?hbs1J^dGIZn83V4=($C%{LO0+>3W0~|NH%I8zs1sKqI^1MiZ#?E +iA$2sgc$AH?KBK-`MGE1;e{EbTcm_Gp2O;q|lHIOIqbz%(T0ybwHI$Co=iE0k+<64=|iL^d+F5==Pya +Xsu#Vi~CAnL>pRJ0GJTqKDQ*k4EEVIKR0Cj3OWb8)mBy1QDK?%R +pFaQj{OIq8Q1$U}H&AJwWV7lDciu&jPZHS4K?EAl^U0@jDddZUW2+1oW`qNB$K?DmK(25-i~1 +JIeJdWB3~(@$iR6MIMOVgM6+y3V}lYBh##;?9=&%f{`zGHJ{_~#A}`Y~X2p%;!;@DRKaEZfUL%&nogJ +@>*YTJ1dUaA%8Q{HfV3rTigH!zY*snlPhsk +xWqZo5H?3Nq|mk6dwhv5=pw%UL4geJ`Pr!ra+igUfgcHJbHPm3qSD-qf&91yDgwJq-%W_ss)R?N@vS_ +S@LM(3cwnp|GOGwBjypd$RiVLFRn9@&Jm)sfI~HQ9O1S{BBoXA^|MWt +4=Sy(JXeADY+aQb#qvT5k`#fwuS0G>kKY9P^SXZB|u0EURV;D=Re13Y2(&Na);eE%fe4Yc*0{?ZJ-FS +O`ad0*QDfjik1=MpFsnPE6`=?K%F0qfE$bQ6=blqVHYc*C<;myJOx`NL&-EMXl@fY3Li^G=(Z;vnP`g +QByzLn56`-f~NAipE5>UIHgM0EucvI1W&7bL~)ZJfV-^9FQ)!xtl*uXB`Zg!A;H1N}|dOMk;3r$|@`C +?vd6`;l6P!QyJQ^&{aW{YW@Nu(-R@`jNYkTC4HW>1_nYLWy1#$x>*F0H$v;)dBKs)bZMe#&5jt;2KDv +J`ZcRQhSC?qyr*r-TIx!B8UL&My2Rc(DWBse~eBJB3XeOL>!4A7sX1-9q^JlIy`E%NqFMaK)FG!0utf#<$Vcw7~tQOe)3>N2Nv)9(TiNx-FYcqZu +REaJIZ2gT&e=D@P8qRq);pYn0v_gv5eNDM-D2{p|3pM(dw6DxH{yXfL{)=W)d&)tLmIT?d}wIPL+a>i +5j~P18URHvavM9O)TokZ1}c`wo)mbv{kFWcj3u=Ywx)>U?zc@|gC;oqV{9Qen-CZ6O3p! +usQqJ9bG+D#C)mT&UpA6T2V_8zKT=L1158@R41x7B8?AFfA_SU|5Si21ww8TzF^~Mk!=tXgngjR7?vkzsX>azIgl +Y?C9c9Az+ncgN#x1@crR)-LzGd5lsGCCTOuS>b$BEfmsM5GAr`s0!r41?CdrmqKejtz&xx#MCzOxi0n +KHj|egVC}|+Fvl~W46*Umq8LmKt>wMeNO|ukjuj%h%QvZ5bps?rV0C^>@^=>Dtka=VNJ(WVrWroLUF^*oDt$EwgWP8Rxss>)^>9Gn5cPr6<+RHR6%WsybYhdoB; +WAq^EipIH(D0a(U4;1&4x5tb;NRNp8I!L!6%3a}Z*+?%4>B}^mc1cHNJb_jg(O>qX?ctu@{;7K(_7)$ +lo(!f*Lw^PTwA+9!Zp6F?F?NgF>IzRM=!K0W9de-9fIIX}j@E@T9fK66S}jr7a-jBspDJrx3N+9IL2; +hn3{(e8G*>|%Cs#$6SYfa3tG4*5589((C+^C8bmDT7rX(!U*;J>8XC<8-c=EKTD~d_(Z@9r4HaYbndt +r2x!3La8eSX3ZWhS3wxj9vM%CmmUNkrfTD&yw;MnFupd2jo;}zH?5ExGLki?L{ +AiJC#`A~+nBhs;3`B|jr+pD__iZgIG||?yswGv=k~`6)u%UhDp;8iyJ#Y%r67S*uP>C}^m$;U@RQ|E!gVxGXWO8dry7eQS)i +;S2eSiMPT`(M-QCAe++M2%vl9XB00l))qx<(GDdP}XNm|9do%?wwx@VT@M_I3@n4>J89*`SyI-k0sH8 +~IZi>a97BFmy5LDzr$q#1{LZPhEU+d;Jm#P1tiB_;u)%3?H&{oAo&ZAS0kzmML5)SO;}_$oB8RRR@W# +h=rBS9;0_EC=rGOkKgc#%3zr-;>o9O=NOoaJmLrQ4h<@>u?=hdkrLvf +h;O^SCI-A4=%CE>VW5@X)b)zRc*D^CWqH(W4RIehNkmKb>XJ7NV~~Fp|o&F- +<{NE@0`(!IEr)q%tdY4h9s;5JUw8A77<2kVBzYs9S?{72PBT8n}_F;f5`3nF%g{vhEB$v01b#C$kyxV +tjz}(sd|y_~_{~pi#i?!ybJ9i0j=c^6>E!Rb=@1$=>7P?#CX}&s|y>uWn@=Zbhin=~UltT}U`z+@jD6 +5>>Y>Ca@Wnl@crW@rcsg{T8?CAc6TZ*+Qgt1htWj)K7q;7yr?vs+X2@?%8M88=Ov{fwUmAVg+CASxI% +h{e6>vasQkm0=g~*6bHi8MX^k5ZU6&@e5jZVP-E&2pWGkr>^`#D0rJcokMAz+x>O&keX^cnUrDDTCg7 +q;zn{H*DMwjDVDb^RNYDAOs}E(bW7sW$-uGc~e7?@7T_xK~RfdZUU|h32u5Pe9r*l@_db|iv*c0S+P= +Y&^Ee5t+v;mTJ7GT2Sns#byr}y1*ZYqB%2I1Fc4*Dn`K@c6oZ;?TX2#&k$f!> +Ioz3iitpm5a=DmYdNR}zCj2rRmTH +NiZzU@bG`f;2;VPJ5n3Gt1C^MvK +fU-SLKP8G(gRPmi0lzhS_NYmbmYWCu@On|*nqZCZP5nr)j(`pV1^FRE6?S?@z?G`k2K?~!$zi?g>U&+B^s{(udAG^MtyxWp6LZjP43)6uh|lY_HgI*ukz=>2P-VRDgL<%V7jF3~kIXYaUf-9uEu +pFDZes|RB;&&z~H!e|02*3SW!QJ&;Y9ymOM?77+m*41U8suZgMum~v->^AFHTv6{&BW9@{eLbhQWJRO +1u96IXpjc1i1)AE%Np+JXnc|>;>c(gGQYjca_du1(O06c)Ll};9lB!CkCk>nRN-SDvZyw#B +`^ZEsI=&P(qGMGrrvn!E`I82Hh(i%g>4w~+j|*6nBtO{!#4 +?}}H6Q{7|U4gKNKdsF0JZoBf>?n{lPs)h%8>S@<7h*GcV$PhpKG_!Ql;YZ`-a1os* +r`@IlML_Wdv)q5XF>th!)SX%1cj)NAmzbUcdj14h~2W7!M7OnFI3B(d_rG56IVU0@aD}a{ +bn+8UMcf6>|f%{_v>*n0J_&wMTQxh9Q&Cy)u()Jk|vS2G9P!`yx?H2C%yG=v$zkhXC<7c?$+R>@rQi9 +drGQKoa1)vQ!-wj~^O*b0eB2bJ>TC{l$>Ec!%mR#7>WPaE&TI-D^OgYlt`wQ9A9xMs=k8Hh8-h(U*<> +Bnudvzh#Hx;n&M{pr>;DkM8$<29-El-Od2my?aci_r?AC`-i>lhqau)8J)d+KK%aiZdAs%B_6$8FUtxgs_5wabo=Sk$K +P)c^(oa$@<2CE#JxV5nDHTMTIK=<9TAFTF^|!khYwZP5XXVo-jREg*w#vQw#9-*m@3s6weN?vU1{}%3G~`T%k^qrUuVxcpUj1r0?4B6_r< +_#+V39K|V)|SPGluC5X2-aWO4d5MY2;o5x6IS-rE~bco@vMNsC1D8jm&FUzZLZ_O6GGqINGX`BytqhD +_U;)kz>XY7+eLLaWkl+3CTM0O72@VMm^u0?p0^N>1~1l2)J_PU5msmlMcV&! +C?N0O7ru0bZT&<8;TW`EmZG<{8)VZPc0P`6umB;W?p`YbLD?T8_wwu_l3h@<@ZMA>n;Af;0#X6CnLLP +eGM0KGFF-kYC1LWd&c|kACC5=r@t*$p6XFz!+9aV_>yOXyIXg{v|2>_CrMBZG3ug{xjP7aw(r)gMhyl +Fyyuh*h~2I96y+q;#*L33#wQmw>iR-QV3ScPTGWM3r+x7wZ6XmG)+tiDkT0mJBA@{8(Vu7e@Zf@4#a8 +j3jQJU6FYSv4aM_H+4?m};XCxicA$SnzMy|g}3gDpz43bX>sIRS-H`2&7MZbOV4vz +v1X<=!3Sa0Wdg1vzS7Tp!93UO$XG>V*f~fb({>E@r#1LbIibsYMNn;Bf*JwUUr4QG(eaT*!A`B^kuWeDGm5`XaBiGGNDeajrARr +7f_N4cQI(r6B~ky@DXXIfb=Pa7XQWZOTKs!XN!&a>Avd9K}{aq2cB>#Wx~ +`sD*F_x$(#8X5Qk-$`xKGZQ*WE1S;JRO{Z6T6FFlqDAC_7Ht*(y1&(1eYC00)AsBks88RT4zPmLs$;d +Rb^5M%^boAzR&1>o^N7#4kE`+njL1;2<tiGkZlMFHW~$1JD&H6AE;Di#u=LR$o_hd;FtMy#HvMbjVn&f0oh85`DDqI#9ec&<@LX-yStH_(; +~yW3nH|ViAK(mhLmdeMKysyB4>2>3Z0NU3`2O*q7HrG|W6#VNwif=r%N<;O@@A1PX*fG;Spa=GE!eVk +P`3ta`LbfS`V!o&iuk5ww^Ec7+^?YOTGs_Y0?~U5_0bf107%22!A^!%Z5xBy-|&qg`hD{bcV|)9)@W@ +$jBtL?AOlk^WNkg8QMc9175aLk!e*X@&_L}q_LIjkKnyZddYYLwaC}c6WkMq6Y|@6tC9I{_u +R&YKi;BdUKAI+7(*f-|Ua%#X#}MY>_oT>WGjz^pq3j5)D_?vMBLrhv2Y?7FcWZ&QJLhM{xahYi5tyS+ +5`pTB=W`*0`lD*7!Eyw6DGNcDT%Y=?LOsParGd3$)g? +xSY%uObUocgi!GzP7hpXUz|-&`F$54*%N1_<*06m#fRrRVv?<2P$Ph=iJX}mJZ3+v7<((|DlVw3Vl2} +LYV2DjQc&|A~_1={l7fA%pITVn94r;N=Eme-k6>8F5+kUg_*b!s4Sk(G?bAzoWJ;i-AC)#!fLlq1NV_ +fw*&ALoujX*pB#FX+_=%Q*rx`yU9>mqg^FNwo0fPeCZUX}ZLWqh!?)<+KrjtxS<+y3^%{6H}8Vu@VgT +vqc=;0MECIlb@KY}OD};c37;-q420b@8Jv%$m~${qtwa!3Rfqg(n-W63xZB_AqYq_y$gIO?LxJqGnVb +3tw<-1q4Q+y)QlVDRg^3_{zfafmg<~yry ++I8xo7eIMG=TiaAtiKa+ja8GR(V@%;q#q?I&KqCnnA89~#_z|tuyVN5Jy;B7WDC!8_qD`wZ6xP%#AbL +1xxb|~>me0=8);k$!6H~_%z!Kc7H)4D>j@T8y5ADU~E@|m3Erkv5?zU}y-OYB*tthUd>%6RjM;b&s&c +T&Avl6TYPjWd109&EwbKc;VS(VQ5yh=RG@D}%1I7@jUQjY$5YB*}a|7Fg_*`Q^^GVFer?hW+Ty|xBt) +DQDki%`tB&5Y23>T333;AREW^KE5M8hQ7sL$+Wg5gC*@!F8;0xOBV8fhExk;Fd?H??k;^mK14-f&fg1 +`a%;^5j}&kAiJ_f_wzJMOxoWb1}GnW`6N~amTw_@p>uOc*dIIFR<2+5Ij-`;B*jdvE>jPt(V=aeEu!f +CHBMOnMO(x?sTiaG$+cprc1%?|)>=lcxQ<*G^z*&*tD<@ +BMuMImy+G*2S_4*^>v=SbbyNr;JhH31)Oad41Y4Av<^_IUA7q13d%aOGKj^OyL2nzT%MxO(r4(}Tp*I +)YPx-FYEL_BjSD_L2XQ;IxBaIsOvCOxrS&K>uMCQ_t{*{_Y2#7BaLVSKX}&?dMwrPgC9uh(4UPtuD29 +PV%zT^re2l1-BRyd0b#9{xN!KfL&M)%(xs7eHlXap4;H28=a^WQgnMI{wn7&hGRK?b+NzgRq~!J@cdv +HWUTehSx5%>8!0ta9Bgo2=yWeZT;RHp1fEwQh>2;hnSMrhJ}5Tzn3+);sByf0z0nI5fs3DwKrtuSnu9 +|89^XOthOTJI2Di5Dk=QCUlg9CA}N3za?zmrIVp65TV)%Q`-2#ozb|_X?v?`7VQHmZo0@d+ID!r$#Al +c8$tS^Rxpf2>3t5I8CZJotKzrZi!>5Ft_#fXa?Ui4zr0-T2#7T9E4zGy&v+q9sJsB^s`9d=gZF46q5m3hw2wyRJeQPPU3I|RLA{v5&RgN{+ +hgS;R1P7BWzh3N1+iC9ognrh0Mq32b^MRKz*6`<&G37mAR892m|eK7&!E$^@x)DFnu;!J9hF +Xj~?mLEDq>|iKSfy2q1D&n{1*!RYd93E;$R8r0C>cQL&)dq0KLq71+TUY0WOY$+;~tf-G(jl$Xt_qr) +if7;63O=PP^2dDX6s!l(FFOmB(L4K6@0us9wp +6)3${w_Vj{UjsAw8N5_@wjrgni(Uw0N!8gQWH0tXS?5AugFi%MV7fSZmu2~6-2U6C#XE<_CH04yj7zsP9knkzJ6PAhSYtN +ovCpHsp&_)r7!?%-v2Op;_=U%DH!f&U<%qcPRN=SYdUBb7|>$ESC$+?{}A|LEU7N>R!k?3HwMG*#zeHydKG#8M}RV%FF^~v{bTZ^WH!)5?JoNI79Et#w0nc=nae%(CTIHd1GLTiHmx!8wiGO&wPEAs7c +!+zpKx6HY^?nwX?=y+u;s44grY$)kY}9ZIr^OC&N9a$1Cdra>;N8N)}69bn|qTr~oEi$c*n&*|>AW8| +q@@mj!u7NJGS88tf}`}QJLw`eGFDCiz@P$=+~4L)o+8_A4Vvu@IVh|t{WG-&giCliCin^PHaAjnCR4b +GWUott&Vk=vc;*gS3;8U4nYHAl|`7)VgpG@4&S6ziiDLWeWxwV+cQ#L=6qkyvUTwhpNn94166U%TGvP +ErM;b4KSXmiDU?cwlHgyh?Br-}dqR49($^>&}T0s)^2o!}rsd3rTTpO8a-`H>!-SQn*^i00Y>Y+tg-s +k(8C%{4OO~Am~|=iG;6Se3>NFSIJYS^YRulKn6I>y={z7nnefa&ySAugw#F5Rye}n75`BY@iU2MZhmU +pkE9$lh`85}x|Siw$V_M~zn*Vo>DD$M_2uC%ftxwDAbbwAAw>G4lmEp?pJY{%yNhqL6iXjcorY_S#u$8{fk7Ir8( +g!WCexyN$g-F;?%t$J=Vr2*b?6*#|3tc!0gcL&^AyJu!t6tw!i3UFhNxWfydZgZ#d7(SY0da9Hq0SYp +;$8Urz=!nMt=b5MF$jWMWy5MNwFSE&ttE724S$8CtfV^l)nfI1~M4ofTv|j3413=7fSVcR0HmXCIKis +fsV0K;0Cwd8)G`3(`M4ZyRD2-=H}Ws^5#`!W1pH~X!dlX_A8@$KavE`>q*FgI4EmwuC^pruYDb{j3NF +P*&)~uCA8yml4=EdGKUTry;*D1#gVphU9TmcNjIUz^+%TTZ}%ya;#133?g_X-BNm?KVi8POhHDPRAxS +KT$mNUTvIqTm25o4NTEjb@#8jA)kjxNw<50Ho0e#7*H` +`fxQ9xD7F=t_H2{4`=Vr_Mu&t}_eWZkcc7t?pb^GCyN0d*tb8qKK{_765$}mEC7YXXa$KSOC*=Xl=i} +)7gqiI+Ew#9-%eL;#@$@TbB9lA!FQ^C=AbUdKiGMX3Drjy_H++Z5Veq=;k(Bxv~;PS59D7+u+M|HRc5 +E<*BN|LVE8AYkrBHA=rJ4mu&@tp(buepJ^b`jheUDHj_CZN=X@X#8&bz^R0$>Zr7s{i?K$BdwDeVxHy +2Y>yqW9aoUa4*-|pzQzlIWxZ_9{o`~IBC{REOUHofTN(DWpp(yNPbSLeNL=GsiHwNFY*Zvaks~LctE`l8_ +32f$z$$a7_8(Bf~_kL(kcOt?&y?3qX|{le4Dh0Kfqj~5%&VA(_7o$xkxbQY3bXY8H($q{$|OM`0CMB{ +s8=ZoXpZJGm^ORpx6cg)h7C!cXo&VRQ;M63g}l3xKx3T$v1nPJvY8kf4R?M9xD>E#jw6vq|ncpgU&gL +m$B!+5uRJ2IJ758GMy(Sg_$nVzRMwXuj)MKyl#LTCs)Si>cir(*te;uW>5ZkrA<&lKi42kOTSg0xv*y +$U1Wp!lPcnmht%6G_}_zQYoGr8HQTa4&@Euk{?m5T0c*^qCA7vY0{1H^zw_+TF&@F+gu`x55H^p3LapduI-Q%-HpyX2er3nvZhh$#RqfOL8=%`N+&kwc3JF-w?$IH +fuweSM8*=7v3Ky?wgI;A=N(8L{TQI(Uq!ZDfWm@qvu~H*VDdE7aPXweC#wevMb2(4T-dXZW&8h->za< +l9rRAqI0S%N8$bGcT-vZ-(t4UIM_5ncmNiEUbb3Wxtl6LqLNG06T7aKN~nk3Y+B1tmrnq2`3F; +$4~NSG9#@}WmQUg)bP>Mz2W00A1xH56#JJnWh%GP2qX>cjN_@+?M)>3_ApR_;UA99@=CC-SjF4f1I7J +go1Tf4#9vkSX9uf2{b(0r57oL1^m+KiDKoRn44>=`cON-r#3@x5+I_S)eCPsF-l)3N!zY}E+bUJ7wpW +&`DZUd=)3Idd9zOo=qxIf!l~aQSW%hQV+^XSPI`wv+Jl@;=es#T>Tkret_8#tjzp|d;fvUy>s_i{|_} +xdppg5iCf)AmMM~{tywSiX9fY*DE8>qzKnx`Oi{p~yQHWGuN7DL#g2pkOLm0LE7OSVN*OxBUiV+^pE& +MEl?u(>PiD59Rb{pdjX38UbxsJqQW%b0j^^0E%jaQ}i{pd*F&wMtL&$z-`mfdf*Ib=m5c+0w|q$G)(W +L|uN8dn=TSm23--6inv1%fh_= +<*5JdZPaI1WIowBHTwy0V^rOJhRAZhi?X9e+4R01wAf9Nr`_Y$_@Ph}JwAom-sD@OvIEMO&I#OHO?;> +nT=XLlV6*8#2KAweTFflVEssz3Rt-uOWf8xil-om0hI +$0FrfQ7zCk&N4!noaYtRh@4raAsmsl2VzckjuQ;UK~c1m2|%v0&>fn>T2S;;VG{nw1weiSz{&FF +`U|;TSL&98|_a*urMC%S|d`5$b7fkX8tf?uiGU&6#R#S=&W|GM3bNjf6WfD&h4$*Mrlr(6 +8Rb}q0G9f1I*p^2jBjiv>XHnX$1J$>f(EY{t8gy;r;o!vLm5N=PWlQ9m^%_$}$yeF9 +G}XTA`+!A=SOpimp;>9411)(D(O$*zGM!hLHNavq^~|gHEmQx7XBsvkDJ)E +(;FMIKviwFK(ULb2LW0HRU&-xFQ*>PwS +kOF+ZgXo5PrV!M5kAFk;HNhsxA9zm3$1Hb>B*H4RDNjkX**TPFL3g5W!q0|29ve=*EWQ3%r7{FV+U=t +_b@#6Fc+xhJ}t8^U&pOs&B+h>S#k=Ci~`@iw#gW-H;x9Os;6BD%BKcFD9wKnTx|C5pXF)C3S)R+mA@r +H0WqIhrTVQzaG8#SoSJ1ir>fY%g~a*qV90+D8J-`4)_?Sq})xT@SK#ti^K;Wm+a%yI%O3#J!11*zbkDvj(_YxQyr-PI?XfDw*azW`5W8BnJ&ByE*260d(80n@vE;_$XADB)F-4FlIui3{3y!Q*e@ro$iM?L=PVA1q&U#t3%Jm +k5|E(AFm=u%_T0EYGp2~!uyzn5)KOsZbYb=MB3Lu~uw?d)>2rmpCP$BYNdou*{bFgF0xp^*<&ppuX$r +q(J$6RjUW26*+e?QIPqQi*oLf1|pcKMzP`=rvIG20;fWo_As@?|*+;(JMbKv>V92Hmfgi@#f1ATzdSA +>ZXS)RqSDb9*`iaF3!QIzQ-U;)#)ZW3jExzz!tm~m3GVqxQoe|ML-s4CcYV8h~z5X96RK%TdltjXTMDfkZ2DlmR*p%@qrSIx(2 +tqez#c2=~+QV_J8#DR86IYwjHg2mL0!Hl*_dq%7=xACb-j%6a|_GC|?mD<&uKsy5R-=Iw0q`BfJ)n1y +&f-$H9D#)XkM~BBR&i!SgsJHdi5wPw9*=kJO$S3WOKy +GIpH1WO*`9*bx973zLINfyN;%?^mwYoN3t82qx*X)A7i;mTJYeYBWRtraVmal`Aj#jc7Xkh`?FZa>U6Oj`; +g>v;Uo4FpyoF|;)WOUDSdN(nVqJ=fnS=~^qIj-LiMp$u&(w%RJDVQDjrhF^z|0?>+)DzqL-H1*1KhZj +z|zo!c^7nYX2uz7ZeO|$#$6FY1;eN-5Kz}|h~Yb9SCzVN}_JwFcUc6hhb@AN*d8UlXuVQ+8uV_(|)Fv +MSNLosY2^fe9<))|vw6S0dSh+|g9Zb?zz_=lVClr9l)`kd$oRpEZL> +jlK%v^;aOt^{4}1L;YX>C9xSchbps=>abNE+%xuVyTH}l{&371%ea*`B=yXfkQ{Y$&-Go8bsY2 +6f0Xu1;NFrmr!=D!X2sQ3rm$Pmf>~j&94Y_JW667H!rxk7bWdWl8j48e- +40Mqoc;~)dyPrh%UA`yp7WOE*Iznh1Yqg2%6FB%jKx#sTie|CG#hP*+8}M^x-4W^M0i4c6YuMPpz)J4 +euO*voDR6sZoUt;8*tzM^!x_tPHe7QC7;~+2O0VpcJxRU{ohVNK1XuHjEnRExoG2I=HM+vAn%rr{ei9 +ubqnX?9)0Gk57JfDo(D}LiPDiE~?}CS}i_1c3Zqm{n*W+nlF!Dp1Q+RJDW6AR(7KL$jh9Tb5lLrRUL+$7!%@1J7>Jag(nT5Z{XleJk-)gJ=*{D!W<*rfw$kwS6)cE_&XdmxUK<{|6#^wsI+zZK!&KNwQPptOhmk4{wSn28dW(YW)9o=Hxu~cJ^(kFF?n)8!x_D?FSMc2 +_-a2tAMadIDl5qv23W%-F1av{^V=iON6{g;6SAFQO8YR|4(8RZIWxnx%ST6&Rnlh4bCQZCRx!=KPK+> +W-mzdx5!=eD`ofI?Ht~@apkv!6Y!hL8>0mCi*NVf$vszB;R6391t3Ys4hN_Rc`TXe~S)Xhj;b})*XusEX=v~ok0CMx7%mfXlYA +SF!SDx|KP%klV`ku~874bY4O=9aM%R`G8#r_@18M%2ec#}qwWlBtz!7{01@u@4e_*MukcO{PhOnP4(> +SFA_RxP=0;n{Kxx`m)fnaKc@Y^H*^;H@!Kba|Jzbz0!@qG05F8qVObBZB^|Q@r|QT`0?(jf(pC>qsMc +}v(s^0(&U~o{;^+-n(_aj|kY?r7LM33iHHp&+gm#A;la||~0Jo!@9RqZ%3{sE{Cy~ +>$qX>6|IlGHzygIbbg1ja^OkCmy%^?WDi84nnR=?eg}Mni$L=%k^KR>&Eh#3S0*6C96AN}R%>995S|a +ItNL$=uA#E#J(e%cBe)-&wvAp{3|;Nn{Kc%VcDr@}^`0o-Oc163*bc@P6^&meI7^Ichv!Iq +=1&u%mND9)8KQ{iMa2b_h#``d*@kJ6Fq$FKw&tKY`9r&9qh&b-D*n?>M8fjns>xxsz!pGp_-cuE#Q>V +3Z~em~W!#}cchx!VMN49K$&yEQbq!L*>ryY{(m7;x5}u7P>Pg)K1G${XGwdtrHUj7hFV2sXB;(A{77+ +W~w884!^x4#zTHVB^O1`n8qB5yx`83fft^|w`Omz}Zu9RDxl+FDl$37-@%EMA$ivV>knbtGs8LYf5t_ +WIHX>f{C@*BFxBrWBbqXNh6_Pq0GcW(`CpRe(9x@a>JRmB+K(9!caxl}=~*qMq|L??NP`n1JvGP>j@5 +@osxlSr%4x8}emL$5U?o7=n>eQ?uP_0f8)WC};{}VfZwpX_HMDTKL^KkoeT3&h5Pvb& +oL^v~UKuEMx-I}jy>i(=t{?C$menUnMGHujh8dPG~SOTigDT69mu_e9)Zuf?DYEvsX1cfjAlZdP@->b +Zz~ummiB^^X4o^m}4=;X_YxJu7c*z1;t9z;1JQ2B}R^yPD$&T(J_-k3Hn67_Tp0qGqscB5BRAsDlR=2J(l +U14W|C4^^%`)7yGPtRU(K;--PKi$N|tPEK-EGveg!1uNKj#IXfcmXPOfw@*0ug$3$HEDjbZHI-@wG~sa*8V0j+`!L*uhQX@t4}(39|GKYdGH +3EEF(AP;NlUT1RC))>_?JT&6bGRuf-rbv_8e%=@Wl3bG{AFUIAat$gR%~gn>D*-jK5QV-HtRvSi2h`5 +FInkiEQfn%3GrqE0AI$=Sx?>SrH2-d!7=hy2K|tz-3&6%VzX4MOhp#Xwc4kUp7zN-VCOOeZV*78ni`l +EP;Jjr)(IrNON~zKDmVH0=Z)*E(XNittuZBu)y`<^io^J8uefGp_mgRzW=jAD{K_^6P?W6`k{S}J=3+Ze)M1Rf_8|5t#g2%}v#Xp{)mk2K^ry +5!OO6KQB$Yvyvu`sFuhR2kI +D#rHK%h{;ohH3}s~i7OwBJRZs4s8R3P1;5o5%%NsrKL# +;_hA02u4AfpzqdnlMJs{=WOB$iLjyr~S5*f-k`uP7c5C4xqlnD_ml*ezlF&w=s^caE?A6x`g=a%GKkq +X{JV4#SZEtUP>+n-yq@wJan4bw`{*Yiwy?dNl?XLEsbx +mwyzDl7c4pVL>TUvooc;@-q3`N~rh-5KdCIU=%g$3*jzE|S=85dG-q{{23Oc4wC`w9g +W&)#dPmBf;VxhqR&bLX(OOR`G7}((>oB3@{oa{=9MnvE3y&Jh5`LalC8j6x76>%3Vopo)U4URF^ +7Rk7~l6nwEISBT7Tl?S;vn59#;t`6j-(NunD}M}Ri!F@}q)G541R)rM_NKAWch4}~aVPZ071a_n=V&t +_C2bz`?~uJU=HbLI^ZX~&u*iY)Z)Qv@$k&VmWkb40UYuaD5gP-RAtL!lW&(+kg!bmxO|YtW*vrfun~3 +F@QOV=3vownN8^U#6_?Y?I0hJHUj*D_o!B2@A^WN9M3d#oEQW%V*?yIk?2+u^0~t83=*mSd)tigtS5Q +OTI+DW^;{cdU4KL8`;Y{rR=P^NogA&}$owJ4UjzQLWjfcP;i#;PyKELsJ%*a`1V! +?x&S64{ALwF8#k&HGqFKNU2zf9r^ZByksFGY&mJkIone$xGORKvwe_{5oUUH^d{c2i#?i=rvt1%h0rv +dG=`?=XsYKhf|*P9eHF +?$OeC-#k2Y=K|r_`PrE@PIL>5G$ji#6~M`uklt{8I@NtN9hOcA)kLXHS<+=7sCfVhn=KD7<792z}0n! +4%y1%svwU~)d5F>z)$#a#;O{q8So%BYY?wf$}~xiS#Elu8R5UpbLFzSrkHOG3fdi6ZuB^?@M>^AIyyf +)eAUI`ZrFj0k6yY440zTzVNR3J!N`c7e>pi`*Yz@rgpYO;eBozCtlL*xmUz)(akVJ^vTwfnKz}|410h +j#Ku&&14fCBMajp$i5T?XdwEx>HAcsxng;wiBKYqt*9%WFrNoFsO&O&77?pf6kY1C*^B_5_Rt3d;keL2n}%*t?PU +T25=RFWx&&9BD8Cc;FS9xrJs%A=J|30=W>bGhPBj1^us~SmA8_@pCHf`2CVRlqNgHNB%y@Q)Z>CRqqU +%!%G>+ypj2U(MaH@U}mqZ%}w)V#bzG?LpMgr(%h%L#Z^c-sIDclvoyWH#>Dc&yUYbg6v(3A=4G@!- +`Ylto~6kxtHqLvE{c4*oHQM(*!BheIH19L6E3|9P*K5wxY$*qYci@HUZ$SzZ$(>yL=aT1bFcjCy^`W) ++v~2z2-jM3Bv_9qW^Q#{8ZCI#hN%I^^XQqCIZ{V}fXyxpcen?gd(-~CX{SNjaz93w)hg(Fq}^*nJuKY +Vn32uQZqY(Ckc|HM+9KLle#@_p|_vE9|@`m56wf{YcQ2(;$hLULVebP +>+*95GMQY~;8ET(;4w}0&Py{%CC+Yjc1YL6)YaLFu}Mti4&y$~@w^Co3s_Z?LD8(EmyRr{n9)rlP$pnq|mQ +Zi+!y%?vCapLkvG;9LPS!_F@yLVAyPu6X$@NxIkxZ`=m`h9N?F3XIZMtl{i!Nh^JXufx6QW-TVcn`ks +)Q@Q&g2-2=cH&!%V!5@dZm>+bgH5E|)dPxXB&&Z)k61wZbIr(`=sWh%c;#`zQ$JFOs8@x>NzPUXN)wx +j0Bx+ESB8?e7$R|y>J`F%p<7A;+JyHkT1$_by9iKof7t_Zf(cxPDmErx6pZtF3ags-C7H5&5ognh3o|#PnbOzeIM%LBq39eRlt5?@ +jGGcTzgOoL0uOdP$J<&TN845kN5%ard +Sebbv(nOEbd#Skrl@E>bPg7F@_Auz=teIHy4Q*9?`O2ly%`EOFymv@}lL?&_~wOIBI4!_MVLn;_k%3C +dq98I(cA30f5v6h^(XSu{`Fd92I%4rt8OS36nQ&skUP{V87S?o|&%lf(r*{j?rFk8i#V@09x6QxCwa{ +ew!Nu?z&?DL&>@S2O@g>Sy~ZBH$JbPQ=W{p6UG(ugk7w*#4|pHA@qRky~QZi*Yc`Q=6vN_R_Icy@m+Y-{PfHdnVaVIxd7zywSjDH^m;+MVmr6=;codP$yM9pX*T7 +)zyAy0E(5=4X}Viu4KhOaivln98iLn+~mb4=cHO|Hxl=zit?KbV}H|Wcsh{`b*WrETwo|Nau6+@)aX- +FHMs&P35UecLQRkv=*{ka+u_=-W;xOW`(jC{c2IbSFjzu1^F$tXVx6V_LFNSGa=aPa +1;(5^5ciJ%U4V;^uj#wjW0*{51JQT(9n&*Ng^$d>x3j4zXh_+m!sm2pH`xmrVEWzyftzuU1RPj5yqay +jg)Mrc$mO=SA$$AaEJsAx@dNpZC-$O8nnfXrWZLKwAx!s#G2_RLvqIh77b7t{^f!nz;&l9|UU0^A_w)A?c@2q8EY#iD#%GmS)FK{Rf7Xy-Scz8_9)EZaF +*v0S)kgdmA;D>^ML*b=qFk4l4a%HgjhQcb84NrGlEE%Pu8KJCxjD`*ObiI2O)K;SrvO$+&b8q5vaADu +Pz!e+snB?gj<~1KzPA_XR@tSv9f-8$x74HML;axzm=PhFpk^8mbc-7_QV}hfVB-aN>Ud3%-nawd*$ZvYs>w}Cnq`bm0)1 +)QZJK}7Tbk)R`mFws4>(Syv%75BCIqIrh)=q(zEL0dAwzkH_ED?9z_)|hB&b9}6stN76xCnNRx(ciqQ7>C*Cw8%3wP&KiOAgc(7jy{_Rvj{X}4fG0VG!oQ9)Wk^! +c+Hk>kNgqLNvsat8f@Rdv*(XoJCj|P!LB=McV()EW-N>8j{KASY>E9TzU1&Ri{GtaX{bo6+GM#dk{Jb +R!fJgxAt*lD=Bo>=pHoaEmPG@Q_&6c1tC_HB2s+4;X@LKN5$T#Qa|34~k8}C-F`kj{gNLdzCHD^d=YOG|_T{Z#}S#Yi +nn*+ca-nAXSi4L>Wn_2NjaKA_EQY{Z4(8s0V*o2V_gKPp1Q?nDz|XCIyL^5Y&ziqVk&jKbIf_%rH$ah +)Qum_f*sg+axX}#2X$4(8&)9(*bCP8X8AkK(j|Xu;wP)re!_GHMWhUt7Dtl&_W@Ld`(TVr&3wDM#2e; +H7XV&22*paxlLY!2Q`2#t3DD0N{!4x>~{e?4JvTi^k9UU`Ai8zE0Aq#$}&-9Ia{gXvdkwbQK`OI5Ez38)&&il00bCb<*Z2_BBo<--LU8A2*Iy@EHTAsNF@>~ptn%(~oES7`_uuM2qbwFD>AqC@Nn# +eM{Yu^`=>Lzc5XgHg#T39wTajB>IME03Xgo>sR;<{h%P%wKo-F))~(&y)&gd=H*gDmi0X&RxN;~;qbg)#wT`QY&I{n7bFlcH}8c*Ph71gsP*N8Mt>%5obVKfCV1c +EUEHxu?Us6Nx;JyAMQ3U0tJdp}9i}*2Z)tfKAwFSk_re1)8zr%vYZ?|Y9>S%Z1!_mI@hx6GsFL +fB(`l8das58mN#=4mJ;^MExh{02los{ +5~feRb^Rv*Vvz=E^Jn{HL=T5t{joWfsjTDVB{-=t{+?CC}D-0rr%WKzX-0?*=Ze^_wKoCPUJBlGHMtZy*AE91S>h^wi+NM}B?=mS&Z9nR(S|Nx*i~Vy;@_WJ-O +4Y>}le8S|0O%h}@i(J*sZKXkeuuuk0RT&+glIqNguhR4)E$*tbPYnt5+{+y|4r0P`{3qx#tnW=;x5?z +3~4lMa}s%2M%zM6#2rSJDuOX7jhaqmakw^xsf*|z5DAyM3N +Q2hFZWX{$WVY1~OABN_hhVR;umP{e;Paj4zqBV{p8<6fWioW0HHw>dIHE$6*?obwX${cTGVN|spOpmI +4J<}Lvx~=&&Hjs74*B+kEpJa){$)jv~t)a!RgbuA22rqWw+rLRWc(vGn3;vwu5 +*P{B&H{bzH1s+t4CrojRLfqp9V(HUjC=Rsj!GAc3Kr>s{M^q4BgHQ746<=CUQAiuiR4rkbOf)g8UUx;n+4wLmqcKsah0sQw8nEdMY*8$79E(4aA6?t`6W$kP_^=!yr +8H)Vz$DjNhr&R@v>MEJz(YSKhGCUtb;bry4E4T79^e>)cf<5xLwKp!xC589jtrWIs0pnZnXzm^7RfSG +%=>M>=6pkZn^yS(byD}|HHmp*Z%nGw7f_$<76}f8iia5;5Wg(tG;S|C=7j=F6c83)o)7(go(TZd`s+0 ++Kmv5qA$m4+2v<_X>&C9nBdy2~N0@D{wWNyZLnGYM96OIr|)pwZfn=5rJM!DWF6%RSrm!Y|}8vjvK!~ +8mMLm{l-*wBp1UMvkRvTxQ1@B)zt3~xT7cnUTf62M4UDDvg(N@7@7X<;XWCZ%tl=byAh<$8vx=?hY5q +tIZ>2a<2u>9avhVV7h(hi@bAp)p{YO&oA@3BTb4bj2drupv&C72fJWp|Y>SG*+B@wIJbntMsmG(k+>C +k!Mpn@fBcF@GByp6N8rs{TQc*j-II?ZuH#i5gjm(M(_Ge^Q*})1CxjeWNrKSu~ +V)a+p=ZOr%jn)n7cB0B3dM_HLlem%{At3NORwE+{95wX$4$(6kw`v*oPbk5zC-^py!ntTKX!Ucu2)VK +wVQJYdy4-_%)z1t7_(wL}h_jp(G-@E@yTR-r+4{C_G-yW)!oQW17_g){>%VNfm=PD}LBlaZv$?jUES2 +f<`$oztN&@g=PUL_gw8be}OPO&@d0PLgpv`DB3BVfPxtfw(ygLJRXCi}VS +JiuFGopf#GV`H~3+1vP=C7O4Xz#rtt+vXR3(y7-O3sJ8RVZN1)$pclSaMEVYH7J;)oqG=aisd(!h+T`$5F#d1*-`R@z=x7A;(?-EDa(eUxQ^0tr0kDI?45cQQPe +l3Zjt8A@wMo>OC4nksPjfo6h*L;Bh@S5#(05_q9aN_jT^gatyq&pS65X`BQsZ)%ZiQ2bQ{L>0F677V> +Z08q1^s4iWK;;><<0yJyMom +1*%0N{qcL;9nCpsFYE8$U=-F#-GP#o6?PAmaM*y+;CqD4>&wUW>sLrfH)1VYSqm?XX779NM^~&?Q!o6 +~<;Hfa-Vf@-rp#eYXCpCMkPaGjveU%s4O9rV8Ajr$6v8BZ&u}N5j~0?jYl3b-&ia#74T!iNMuh`v?_f +fu?i*}nWJGI?1&g)WBANH(g^{8XwnMeQmV&`FXmvF7=FLb{6;#$tBy3&YTdSXj6IV^Z8WJ#) +HP-liV~6YK-MhY<1XsERG-!;Wu{m=Ee3RC`5hE(&~>r-lhhyQF3j|s|Ni=HMHgKsRIP+_*Ra^1AL;Xg +DMqORbt+b|L%*i=r+NJkZW8M44ECCq7bs$vLdoBxcl_KiPUFf_E6TDBK3FWvr4yoZMg@=Myw0GJTj^* +QR`_^=bI{gnzDFR6mC&vWDcP>6TN*H^Kn1d$fbkqrhw0Mm9 +4Bo{EGm(lCce-!%$H>Db8UhMdA@6aWAK2mt$eR#VESE#WN`003A)001EX003}la4%nWW +o~3|axZ9fZEQ7cX<{#5X=q_|Wq56DE^vA6TWfRVwz2)LU%|}AOeQCnFCJYt@Xpjvm|il9=c1SAdxql>DjM) +!h5>+ymVoY=}Fq$l?cCojOMT*IkIa}^pr74*!#Sj)^q=$WU9;ba*&IV_YVKZ(Zfegb#=BsER>*vn5!G +^NHbKlQ=MOFg<@U@V^yLn+mTTo=l*P`X-AIY+O`*LngM_ylgQ#|m7G9s~yGAS9X4%7i47ct#I{JwYBA +nLH*=VmTRctRC_rC^Ms{B+bB?WN6-lifK;h!=5nIzK0!H?fhlcBUS};=rGtI2c84ZzHn +==|B({q|;#aK|Q5_&R1EVFAcBg8V#XwC?+%p#T%VwrDH%?Po~B9{3ParEe!uN}-nOl}sM$<0C<)W%pw +_+&|HIar>Vjj@c;pv-KH8DqrJJ6pzbBRqCDdrL+;Tb7zh$f1WoA#vvY0q#-gCk*2ZkraL9g6 +mn0qiaw;1MxVQvx2DaLXjMtUMdcy3|I36|V0y_{glEi5^~lG|v>DOz$H8#%>BZek-Zbgbqj4O*HFwjc +)!8zcoeSXc)OafPT=Z7 +t_!*;G-iYW^n{_-qmNNps?Zd9x+(H?A=HIf7mTS_QDRC;j^ZT?ed~dGpNps9j5zDPWJz$(HTCsk!; +rF+ThMqa3P(ahns&tHOUp{N-QRg(qoKgQ~eOE8kvx|Ca;f;J0+In>H=>fyHo1IU)r*C)9Ai)6^Y-OB` +a?sT5-60)vp*^lbdN2sHLZ`4NO8oIotl$rcOrHp=LkfrVx)7R@PzHD?K(rMw616Ny*HlWHc#RV6#B7ISXv=NH%A|R`N%gVrUMzETP0Mv +3khVEH`LmIrYj78d*+RshNVzOhHaI=k8XrMQKxD<}?o?;@A|89@ldVZyr+G6q*&6lc{-PLMtazb9;*C +WNL1sB_~sJi)vnw=E6K+6fR{{*5-n2E-btS*<4tg3$nQ&cvUHkb!z3A)Qa*R-=w)EO;Z`ze8tmQS4P5 +i11V_(DKoR)%RHgP#kfP~mi%F(8I@0`uPns6z;vP11s(hqJ@HnFnVl+$h+}fBiXQtbHtw{t6q_bWl2Q +|6RYnT4SPN%k>M*BlN&e_V#VtLvW2dxZSLB5}G1$1PvlM%HyLhu0w%<7U^ft*p6-KmeN0H^wb8BSZDz +J#2e-bMDdw&{>7{;ac0#wp446#E_+#nRh*NUcYv{ +!?9>QC84aR!e`F>KyC-DZ#EC&K%(9V{&&NbgDVGZx80$6-;2!v`^Yn8-N}x~77FW4OJcBXW@mb}-rVu +!f>s@3Y4dJu7Q@44C8froJCtn3!dBp^s?^%AH-l)cz|m+@E!L;eA0==p5&W;$RCEucav_T@O7Cyw`jP +eD1BN|w)Zbf2fpUn47QI|-&AQh1Qg++Z=kK_*copnV5@EbW57e@fTbWgYCl<1k`U2g@#sfh)#dbkrCJ_tK47izs7)vMz}f_=IRs<$`F +wx#1VNNcyiII4fY3&z>%bbWkr$aOJ%v;GQum)x13L-6@!AgvzbUM=X++x@W8){4I2_ku>1LvE&H*V)W +QwUE2=&+jk&mWQYF^5||Sl^*TrnSn=mCYPV|*AwRxnQ+kQsrD8E%xufDa3jwr4HR`;*oN&W-5IvW0XX +*S??toV%iJ!5*Upr3fbSLgtswXC!NN|qs* +Mqa^NqM)@`eIlYP-6UP(#n`WE%#OR_k6SKKC;B!R4!3+F0Z#tM%%;yXBWMQ;|!ZVA)MAKE1q>Y2MlCt +BbQgUR_7@M5T#98C5uFOBINT1{DD{NR|QVZ~mA`BcwsMMI9sAiyGL37jwiVEhN&>M2YLb>%%4Nn7P>d-o7*}Sn+5?3ztcI4FMy@e(t>9;b5 +5tr(nlgqdV+~o>qNPo!f}Smg6l9-(<_=NET@Tc!N&s3@MfKqx%;cUjlh2iz+(#E5vVIH-D>?AtzOs@B +q%nv<8C5tatmFt1j=`3$bPiD0DG2bLx(DR`ZZ5F(_%_5UXJ&0aqiq>ITzRH?X3C%Y#f(hx`uVxtK{P&5-PO$aI4KN>9Kh5%^*NJD@$0HgsR4UlMnM1yc>5C{$V)4-nwjx=zjA +x9c4{D$_^2773O*k}X|h}dYzw-(Ezg>NlfC+9K&}tWNWZ6^Pb +$=6m23@8dSO%6?OP}o|;oh1JDGNgSLu#Y6VDIgM#IwTi~G~Ktq6r01Y8Egg$6Q73js3$seI;GzZ17Q3 +ML1h=igBv_mC^i7{kj(#S9(qjt5JC1q5B^}L}9#A+9n8pEiwPg2<3kaq%I1a%Scq9ZRl*n~6CYk`+w5 +EFn^P>VoI=fO10x}<_CXbGq#KubU^K&t~XUC2QjPz6sHlTZvHb@)lzg2Lsl@j%gl+(iCBwhd5e(b6_l +L5m=<4m~@xcZxMfRLtF$D#8U-D{}75L(rHi478z23)-Qg%#@j*LeqCuC`lTDCSa)?#dm$85m*5#EJQ` +cu%tCr8qf|E4zaEV1q-3-%kgC;z?`s-E1kSnl{V%IflmHie>!uV7tU(^4}P+hHsb}~^SSeW-u&-Nmvt +T-U;WdwVKo0+^5Bn5ZUJ%>PzAfQ1IfD&d*QG=E)kc_d$qA* +=JnpPeZ}v_8xZe|;+0fQ|{@VUG7NN$5TRJ(Z4#$fekg;fRNbFc +qmIj{;`#%{3p%$Pnz+eG~<^g6NhO8Qs}(|LZ=D8B0+fJe7(54Uy6-0+~R*d$c*7H5A~wO<<*f~VRHHX +)uS}&(Jkb^l%M@ydiFQ>j9p!weL9`JJUxGP@%j(H`^{%B{`lLQPv{=6S@OPj{K?liu7cR*T{0X4;EocjWbEyIJ4M1ZdTNzHT`3px3y(drm +YRhRv7#lXCYP@lG0AtRL#7D0qLHukV!QAMPP#(_xe@SM8JXBOKLwb5{w`+PAE))m_@t1AHwfa4G)#VL +b>;Aq^lgwl8NTO6P}|NVpG~Pjn)5wtEm~Z;&TZl0BbnJFa36wk-vFEqf+B2_vHGiMHcz>?3P!*l#&*L +(ZF9h4K$S+0bp)V$%o(-lBdO1Vyl;?&68|_p;+_b?H0Xxific4%NM#>UZ7`d^7a#7sKsz@~C7kCVLmq +`oB1xtHN|*Z0MbauhdO!E#53vj#A-3!{l`Te?9HqAdfHcm9bm5G1A!S(cqD$?gg=NSX~88ZinIi;?=A +7@86%l_s`dxH?QWd>%M|1uYQ3u>dHc0B}fH!q+jOK!41=ReWBbO*5k9*R1GrQpXzdn9d2u)x9|NvUe6 +ftqn=j!a3nyUTw{CS;Uai)b~aHMDP4!R{(AtMg*?LBtXBhf{4w`L$` +*C&5!TAy%2N(2KibBZ>cg1H{$#19gUK;F$@BB`8=R5SWU$mGRv&4fhdPq=$azA8LS)iI(TQl4hJfkCZZWb}V8@lZ_j@K_1 +;px4&moW&LH1Zu?5uChx+w-Qgpl<1fs{ue6NnohiBdc@d_RTsWWf>T>})@1F(MjA_t)7R4hb9v)kVIt +}OK0IMAt#n_r7N^jH$cCc%h9=WJDt5Nsgn&s_vyshn@is-Ley-%X@@vC?Y?fpr3xu?no{8QFZ_`}AXB +gTBEv7dwPCocy$YzyNHq3dU&=tzJ1ZbezR2c7L9apC|4j^91hpDFtAC430ZaSgX{>T!*P9zGYg`r;SQ +NZH4#(X?F7eP)h>@6aWAK2mt$eR#S>L5j_qc003cr001Na003}la4%nWWo~3|axZ9fZEQ7cX<{#5X>M +?JbaQlaWnpbDaCz-LX>S|HlHc_!IurttG08~sEm&Sy2lpB4*g`Dt!$Q^)!(o$}XgD*OL&tmj{`*!{AJ +f-Rv=U<%yN^H|v8TJby1MJG9xlp!#ir9mQ#U1_PFb=l^0H>ptjg1-=2P)~$mYpSQYCpdWKEXD`J7MFq +~>LmR$E&Ogi$2LczVOLd0vjIbjW5X~Hv@uqo3tnQd*!XE`Q4DLXhAgV$B>BK +=IA8{%|7PYNK%*Gzzop0V)=r#AQ!NTrs6EgSbfD=QSu +SNF1H+1i5%>Z!Bl_aS;g!A1#H)9Obh4z9--bF5I}r@5FHSggDrEpkAY~8SKu_g9t}`K0FZ=p^)6ec{0 +Q*A<~4#0663$jnP$DWy&;?R-t+(n9J1^tN|X6io$V~2R!v#v%c$l6uXpja+1=gU8`1w>(Z5IZ?$car?VWCWr`wKn+mUWN)@{eS?L@bo=(bbccBvGPV`NAcWpfsn95=fi&Tfd9@i}a=LeFq;7aEbi)b$yT40c_DL +%|eMeuO?c%@BkLlkdYI;FUFU6Oar+8Sw2sn;n(Cn!6Vo+e!mkeZFf3Vel`IRqE@9@Y0dRuV{!hjt$iv +=(F9BdyRt@`=wHxNq$aq4@$o_LIs1;JL-sR9`m)oh-V@VSa^IGgEiARfWINJ^I9W?Ka1{7OA%30XENU +xAs%=OBz&1X8W4$x^v$vg-A+fyZXD>QiOuGR*S;Lfi>;L?&alybyN}1lS7vBtWV0ed5d+pClyM~9&?R? +h_2yw|;HiQ1pbcGa-|bGhmnuEkh47!ss^+Zk=dBoPHc5RT+B0Y`V`DFEubTZszQS}QR%gH;N8lP{ +^FEFogbxVrN%!#~(i7!v5OS5+=&ZmXTzC~FUqvMHl)pnA+Q&Dx2oeiy=?wv7(WzFUElp-=2?`_ +$Y1gARl}IcEc|E3$XuJj;YXIF>LlqIK}|&oHhU7X)LRm&>0MFpN0C*^&h4Ct&tq +kSFFEBO@F({>%CK*{{4R@~q+mmX~^=+w5Ih_S@a|N=1T+~`l2++@faFk?GdA}Zja!@*EbRCtmGQUaYyfW4$ZFur6R6LK>Iq@4pJ +$TfS|6hs{~54`1GKlh|7^G0u_2NgOaQRnLx&^+mrIHrN1~0}{qB- +=7m5U6jgQ{4k9oNQcz*+LPLi7<)S|cLzhLZnb`Pw+N}r>kXr~gLf!Y~5avTmPP@Fh53On?ZHuag< +V17Ogl^Jd!S;0SGP>lSRVkLovo+CE +8Q;dXIm(y{&l%vsG7e*ax#qcua1t2W9&qg3JGIDGR2u|#_eGL9Vc+nM&@iC6X0akQ{{F2L$5** +BFNWjQsqKPii((!frvW%?XlL6GIidlStdw!zGgc^wyPnTVK|xy2We;AJhx9IKs^FaD7KvC?Ih&_lvwv +9C`rvxhitUx#R&7TGYFFMjU=`eI48RE#=7yE?&N{}j;i$DNh4-^ +lB+$}+o>d}hXWn{=arFyWUfqfKA5#}#~-l44B6s_dFWMcs}l6Mw&Y!^&}3no|)VMH9r~|9OOm?_PTkVJbC{1&=LRk*)Sue$kk-V_ +6L3dMA8x#v1N0c9k4Q*jBZhBJ-;w|IgxrBr1=f@&yx=b*$xN`{DpMTOFl%wY=?(iZ3tU}kL?wk%2FIR +*UWbmv?&{gAuZH-Xc3sJ?*9ZG9t^p*mWS}ILkK*mBWET0XDo`#yz&&hI)c7_v0~F^sk_ +}1lqS1(m@KC#DLs0Fk4}yz6Q32kDWRtGhDdVLbIjy#c^ekAtT(m5WoX?JW9WBtm7q5~efMDE|Hi^i=i +pS7U$Y*V&R}?ev?MWCTU7FAvtV3=;j2!rv`HG`TD-!Y|QK8(HIe0KK=`9^}W5j>t4Z}caOT+7=uwr~_*{i4q +=QYU!Q)RpxR^`-KB+z{(RdamVwo2({{ba(+5Tlfm$rf^GW_-ehX3G`0=9SxRE7 +2Eh=MWSd48A5I4aS>ao!vDjEvg(%CVtxwGhG8*_HP3Bpu!noFvGdrm2TCGiip`aOpYV|N-@M9dq39aM^toC<&u9y>|4gTb_`d@gF(uyT$}fAYDLx>XPDB(|w+NGPlV +g!!ntCP({k$?pmZC8N}#2}}NalYo{CDWJN%cdfpt6waGfm~+t&?S~Qh=TrvF{*B+09QK6V6s*Ttvz$) +-gG3c1Na=QpFzbn2Z65_+jP(|K`>))*-IDUlDm`}5`}+0n$KCxq%pAyJf!Tc11dS)P$dDf(r}@txD}+ +qn{|FV`(yJe<%O@JJshw|}7?j9^qfBwlyouWV6cikX^GTY3V$LOBBsRJ$h3c4k8J79bv#dQ@t-|huK; +u2p@=BCO)#&sM<)LS{QI+i=-^3Bd-)}iu4J>ckiYvdgZ5x+_F_F9o$#u^Q_+h>0r#>@~4A&coJP>23Q +!xwH_a(eqtuQRE@{~*WRj*uHR~-2Ko`@2FA>-797#oRLhDAnHe9Ncb{4*9jsE!&QecS172c|ec+R^&G@*OM*tx<=O&*a!;_M@TbNPzInG<@Bp&g>{Zg*aB +!-pf8BJZ_$(#Q;m_(0F+R&!v+yejD&*4`{t~7LcF3IxuFOdA>mYj3TUYsg+R&EdrI9T^$x1_We%D$ne +#~GZ~~CXk-f#j1~OwYy$zcf-B3(rWgIUlm$Q2&Ei#nVRc^ +)9qK1qMNG2K+)3Bh5N2Hs#O}R!>5)UB5edd0wGRhcV}F+Q=P;A6YE5hM}m$PlTk59J!xU4`sPRtQg{^ghFygvbdGeblL1st=?kqbZ9vDyGEM-2_2vMdS7cmVl!7HwjE4a` +DZIN&$c26a>WDnhmiK_qu>Tv49W$(sPLiv0&ctY?8BSLNh^qenmxM#Cuo|Wsmm67lx|bHhYLK*`J~H1 +duF;6U>GqEiRhA(hec`)nPW#qY3Fb7FLk;O*tV}T +VlsDM#p!Zs9m%rZM`8)DD6h5>!EyvcQtI@mFIUB&Eg@++7Ew|ob0uyLL#84AlBhMT*vc51W2Li&5JGfZ#gE4cQ-_F +4}@`quYrUH)>O4tS?f!)^9ips$NylUbPT_)jhf3AQ@Z?UBa83O0FA*kStI +r}-m${B&>{NOYNGV`y5g)h`i4+WQ8gG!06S$SxRsd%Zpqfm^W=q!)%lNHo@vm3=Zou9prX+mLY=H5pZ +O&nm)f^PeHkEeKp*@6j3dTH?GMRC#n-PDf+*K`WF;u1P)(A-i2TSdPscxb!%Y+{Gp8^#zZoz$MSg_i@ +>k`3QW(-bgARe5lg)Lo4Yk%nam-o)7eoxOJ&fHvRtsg0hA+oC-Yc-F`z;+{qdF~grzN>4#Dr$l)`eB* +`ZU3m2B?6dolgTLQ1%Vmf9(LoRn)DI1+cIw}4G^MaKT1%Mn@B+0ZBbxybTMy{IC2D@7}I;4<*Sn<#wg +6+EsBzlU5#O`>6i1#s{^(}Cv#`Grq|w!>4#I<6Horm(C={6c9`+C(4OwM(4OuGph1VDwgW)Z!2V%bKe +8rR~t0WtQG +7~3IKjVY~5OP_fy>sZibS9S8P?~XpjZG`WVYqI&UGS8&Gsn +aY{&^$>{)(g=o*4}ccGDSkm&(9Q!`;EnNDsnnzgwFjaaPX#14~Ly!j6%P^&qk2G59nfKb) +=+zr*Sz@xvcyL3EqV+I(>Bv^IJy^QLNucs+^v`VH6(acgLVj;ac1ii^|uF2O2~F>hk<3NGa)D(SA6cw +L;RKNIp))?~CdP%4U`u_e7aj!M`vL+W9P;S%PeN*vUPN}0nzf=9Z3uFj0ebb_|l`Ieg~wL*FR$)FzI! +B8(hz}9g9^h4{oC=Lol!*~S|F2}`So1NY7Q&n|ThroUY{I4Si)m^r4L-XJH1xHLLLn5ZipxVL%*NfJ9QDtJ-J{*=~k+p8#IubQi +^Bc4SY_mj~?FfTK3t^bF*1fps!alj~G~E~w^;jHln^+*D)N!_{z*^)x+6)9?J+x1P6`d+?0!Dl*Bjmf +Xg2qgF)6)O9W>uW$8_-@?z4}|AtGpw&OfW*Iy4Z0HbzMoiOKS;EuN@$&OJ9k`npE?X$w5N~OMUOc)C3 +l3X=Eu?nRNJfwA=zYhjr@5y36KR)zYVM-_-L9n2X8*K}xsbfI!h8MFXs);JWfY=k6DDmFDzFgs1U-A5 +fgiRDotVyWynO;ssni_cpA&7HB&Qf%gO&9*NELrc>mb0khs4TwqVhZ$0M&N|`v4T#S9vRE0M5x)K^zUve+~jz8PIK=oG%nD(dH{5)C)`u||}Cjk=Z_Ai@t3s3$Tg$YjI +?ux!D*+J54&I>%GV$Zx=W5I-<1~rzV1QBwsatU^_7$%UO^HNZV?lr$3s*6j2xV^tV5M})lLWXVuFoA; +l&P9a@A|?-Hth$%*Z6pX!8@}w;J*Nzo89=&-9dwiIk}_mhi%kL{9IZcmydtJnJ3PL8B@Tw(A#^Z;@Y)cdWk{8jA +DQlk4lhBWX#kcif4^!oLoiKSs5)?k+?x2EBt{SIBQX|-(@+W@O&;!$1U?2cIia<2kTk8PHm`x`b)j?lcZz9)XZZHv`s?N*zgu&8i9zPuD@ILg +MgmDYr09P0`@i;Ll1Fop5wLe!DVaqz9!9WzlKYS%<+mLXn>M1!xL&3y-rsZqO>srN%gu1b-9HE9xZ(d +0pjwEQWP7iu4 +N|$*FL|=KJiZaJ{R;d*88`?LkR~I>ac2lp)!lGv1gu%~-)`Bovl)=y~iZ%cje1nX+d29IEwn;6E)S4} +n+9W6u6C$)+HeAK4KQss@cR=ip|)~a`ZEU;2uW3k#%($@1 +oZ*8z&Zw#qKOm`&px|oK>NWX99WYz>Z#YP_)nt}Ht*H|#?SgaXxm6|bl~>PYyL)lz>l8 +f0jAKiJ`c>=W#jt5onOt7lI(3SC~Y4)q2I9FO_sTi??XKAuY28k6$i8jy6ecUIhU67w(YcU6{Ks=*dq +k%CWq+Hm#i+eRT9H@m#sX;!^sD~;zqp%(*Fq}GjGH+9-D ++6talk}W(U(9KvQQF65^k@fPe8|;uYvJ=v%`=|66gzetdwpT8j0X>iRh(u9UKVZ|8!5+`S`=DF|M$g1 +(V1$oPm(7>u3uFSEdBD2miq +1GDYx9fJ0C_AQ3D0s&7ZV(i6^h|pHQz*?%vHBKY0Jk!%BiMfBJkZwjoq)Ezr_oga=TfA)#LhqNo22P) +h>@6aWAK2mt$eR#P<&;B8MG008hT0RSQZ003}la4%nWWo~3|axZ9fZEQ7cX<{#5bZ={AZfSaDaxQRr? +Ol6w+cviUKc51nXKJ~TR43i-BU^jNiS21-lQi2nceB^^_|haKv8G6sAnmAazWY4~-~%MVmqfi{aypGH +0SCZ&{LaAv2#h>;O0KU*fggDE`kGkNnd|vPzhSN&`1D%*-6y7XXEDol`Xq2H!!_x(ZTZyGZT9HV2x-i +$+3@;~I;QIlne{Ougrz4Q(HVh%!lH(2+tl!Z8jFgV^!I@F$hA)b;d0gacgQ?IC&X^kx(%1`LW)9~Zj&* +w#@efO3+7xNi~V!l4Ui2p3A;L({4qnkqKr4fdufi1JZXTC@EX;k5-i`TDS;@9u^*KtwbvuIT20zN-_6 +!8Q9B1inoUi96;qes>##eH&oM0(%!$#)0O9uatSA1K~{w!GG7P}@Z5QJYcaud;|AJu+;aG2T%=e(MkF +_1v4^Ks!R(nx>J9i3RGzOZxgPh0nT2i8~T3wEnQ1F()y6#H)No7_~=3Bk(*J1E3ikk3 +A_X*M#MeRAcp-61zxdvs7nrVt44xgq-=^&I54978$B&R&3{=_x$uNz>pa|aEX!{Ly8`0yb^jLSq&d+S +;#XsDEt84=7FM!XOtOgaLawH(X8zV0ElJ`v>lWTtzJdPi?u2h8cKuWs*kZ_I#~VM9c29HfzHt{0JsYo +_Z7>QGO7NT}|SchnDNoCDY|OSdEGKzW&QAtlNTeMZl1eGJsj0Y);vgS7o~H}Fu?85Fs^%8md?!uR|SQ +}^|EMQz0NrOsYJH$$O^svrb!!03Hyrpd^P1jLZFNr3=Rp_kI%KCg8!j0JR%5*iq7D6~++Q(6xH-A_QF +gevJ{%ODdSkjG)er2qe+#B>QThXgFpZBW}#VQ~ol#B+o3gcIOQ0xfk+r0S4+3e@f>sI4zq1wp;@Kztr +AF}J8~N=9-&0R#tu2MkC_4F!M?HZSMUkA8+CjKf1nXFeK{G|V(>d8K~`G(ahFAJDtJI*_%>*8!xGR)^ +$2I1qqU(S&YHs3~j2y=B|cVA5)}2Z{;Z(M?2-3lEw^zF_}t*PV%qAe^b?0G*`IFnpUzE%YR@<$g!w8B +))4J(ezok6`SdnhzkaJ(KYNQhST$bcXe`t3J_CpP@va=y2xFdWQ#}ih7Xnv#9w@F(hcykuNJLI-X!k- +vrYe>Ybq%n1>)^v7hM2wAym59R&_|=ON=6p%(DfBkv!dpP!!+*W+w9BBw=ebih{PA^<1yF{xoq0XCpC +I8Z(M6TF^B8Q7nWEey#^@Xj`K!OMVkdw{{(KQsakDlC-b2S@9 +Or_?P>Q5&qEAwBc{ylY4QW4*Eg{>Gq&89mXd$GNy5$2A~UOK +wLBhCd)r*427VI>hg?%ekn^{#>B2tbrx0k7=3d1p?Wj`O*+=?0nP|!7{m-lt~YRi&7NfpcnCSy`t@jG +yLZqP=-r=MJS_Mt{`~1vq7l>vPWF0-A5XtOJS0CJNnC|L;A?=M0|1ju+r6aNF_e08c#h=`lnmE?4G_P +yhsIcz@gCrYCvJewV@5%%d6m|8}OV@u*!Gm$|^q;RyjXCRVmf^>6yAp%A{eM^#??3)bCyL{yy(x)${YS@ZWR%_X+=^mH_b)6? +o035vixqH{Ud+^-WasZxOA(Mc@3rDXqUpHNTB$eH(rAT~k`$MK!;VXnh}j^FvcwKSVW8B3dWWH_w~Wd +LGq$5z%@PeRJBB)@fApETVN5eRJNF)_JT=hY>M>XjfTC!)IaWs@oF+d0heVa6C(w#oKIh70_K>%c_p-t}&of*eKy7LRwy^Ve^OO8g)QH(uh +P()|oO^FO*B2)X2YeMe-#8gONRr>wEief~vycjWjO;?#o!@;LhCMfA&;0x}L#!Oy7(!1qd=?WG0+LH7W9G4Ds-r+z+tadP(l3}@h%+L|d>5p^DCJW>f1lwL4@Vx(a8oq!r))k +f#q~(J^PJBGSV@NmPXueVkI2;zn!-1ThA}Oogp10yGn`1b%|!TkBSOQG<*_hXo{I^w8+?|S?Rz{eJ|M +p;%@r8hLq#a0Ag1Puq3@9spV(A~PN5Ral`S6j&IKO6ODeHQP6rb>%~9i8BoG8p6NO$;TeDfc&hw7UAEf%MCQa7a~B?KkkBKC= +|wWN#uHy;NF5y$1)}FeauzKYrHNr0h54=rB|}PM+r80k0uhL%-+-oQ(ISeLcKLf5N>Rc_N+fU{cBeCJ +M4-5UbDgl@26%?q8e9UI&KFI|UrN{}6u<$1=Nbb>3G`>_eu^AUFJnt_{zhR;mdLuv3rfR +@&aQ9ioL!^KyoNAgWPMa*SQOOoxdHXKZ3UFl!cUUnAuo7pjA6o=iOda7Z6%RpsPW=ceJ(hM870B_Ofd +)~QEVeCPBsI(nIDNsr0IG4JOeF=p}=(rh;X4RLl0#BC2-i{PTb;iYlEhxrVMFCdv8E1z<5&P342s1n9 +FB~UU^d#6kkzi47@r51a*2zBg)|QUvyp&x~6)^S6H&HK`J4+e5C>=MY+DJLdNJ>Pv{su8i$qI0c1Fwo +^m`KAX1TFDyEhnNtCk8xL1@{x>d0je@g0yUZ-hIjGvCQB9mXhFW^({8?rUj?leU!GL_3j3Rf%W^y8y^ +e^Werc2y@5Sf4&ieqK`Q^?P|RkdzfDLVS4v_fm}W3N7gpj0yNI8Y>AKpoy?U`6*1EVKIxb`|r}FwHhK +!)i0{rK+f?;X=PCi=aaPP7sfy$?tJOQ?95L>73+WDo`-OOf`wBc2#e51i5N(x$#WQdy8&1!Kn(q)pV;zM=6}*djKx +Ee0|88#(45v1);j+7kh!DWXI(nAw(rdqBx6Rhf#raIF$bWF1u;_Cd+gy0+r^zCTQUf2ZeWNm4Mw&;9=OV}6m* +M!lPOiO^@$hSBhM*-BcGb0x#LXK4On(B7UPL(+=CH>i{OZsF|902h89$^*l&%(LfmC5(`-_wg-1Be?b +_mbwwTV3m2`lIKLeY}i2NF03cRg^alJ%wBzw!b({0O47B`d`;+p-CB7vs;2ziiQJm)+DmzUd!0h3mtE +_}-kM8IYyEuh?ACDE*il5p+7gV@oYhyE{iuaP61y8&S^KZ2<+Upfe#7aviE6ccfTnB2pRTukn>iDtgz +h3|OP@mEf6PV%$s(BXn)W>D9s_I$Xo#Imtp!yucp@^Q}S*n3Rl^fCkN^VID@M!$GokOyMGK;dCXdSfN +-HS7}rCqvH7a^gH6ks&7%CN^}&obyP92nL82(xmyWLriJ#N;h6AG7;o-XHI_nVlcqu?O<>WV}5U&ap&}h3}72JC@D~bWWgi0=1n$F-R`y4-2TZAA3l(me8-aO&2itb5$zkqMHbvaJ%&G+W#U +V%sA_zd(~vw5{0t@2#pM}LF?>4~chUT_9h7OdV>n(ShpzHv^1)5u55}%P@Nlcy;0A2MaZ3qCf`NvPCuNZ9{p;ay@to+Y +*obDk&^xgq$MB!*>tStUeNoUHTjo;mRGM*Dbd!!;kH)*F7D9*I7OT3e%3*@PeB<(T4qtC7yax3F +;AxQVAf%tf#|(j8-7F{3S5YFut|t5hPN;^LI<&7TLO5kF>0Q|$UwB=M_R2bsUUJx4^iAosJ@2$8;@xEx%-DLy%`gt7H( +AF>mXz1_1)*c*(8oKV|2f?9|5Kqm;(mB}D_}e0uHVc*jZ=(!(rjKie;ey5UEeT0P?{+dKk#<1T62bOI +C2~J^Ei73~8eL$r6HcWIOuE2iSq^o9$$A2l+CW^g-Dx|5lca;i@k!+-geBeM7u7jPshj)^Yi{zp6Gvq +>+11sVt&gU%UEQ&OYALaFVbxB!#4fDr!m4FC)P+@R39A-`<4i=bTwpb5KfoC*7~h=gY*NdcZh+Hq-#! +O!n>d;+<+k~GakOTm)?{)CV_UN4<&_fuS$ +1Ug?Q%zKb8Y6e%ns&)fz)`FqA29)Oh4O?&h%R`{q0Y$i+)yT*=ytnmP~0w+*2f&{16*ufluAw#-=--q +F+J_H<++*ny;5Tyt8ThrA5^x@pmEt+a>Y4B>u7->XP_tPU0^D&guYSDL3Ua?v{+HLx=~15RF*fIAPg=OSAN83AdZ#(vD!$X8(DbS9vgRLEB2UISBzM8K2Fv_v-^2KTIr8|| +BHy3&7jk1+XHmeTvIFi54!9oS(Z|S6eykn8aVNRzl3!9}xXvB*d~uw}2mtUq-UjIf4&#lY1!X8jhw4RkDw^Xb<>|z>Sh{6d3)1RQ`{E~sy5s#%4K_Hm=-hS}ttB +nb9m9xE16|E{`1YK86JD%80Lb9P!rW`qw>8tAFtt%*{Ox(Og}5gBifo}QMxLaxkRRFUxxhfYS{aZH_z +j~O(;>OQtKKG@w=BlJ3^j%Tz)uY2JV$B8*Sopo4!sZK#~L#quPMW$?DY}FOXkpleRUbibIpjn0!QfYTs2-sqEhEv}WMV=BP1!a?Me@kT70Ioj1^EB40Xy +Bi@4Ay4Mo{MvoTX(VZBK>NaV7ZXhzaRrv06>m%oye&gQJ+!_7WDDjU4`G%4yoI0{oyTn5u+!y50ST?Y;xxtuvlYUzU;p2@hi%T$oZjk-VF}g$JKcil@elDPG3G9K|;b|KElj4E;TT$r +Vlj1Lt?%;jBt5!m-?>ZfS09frlwFmDRhM_BmUg0ahdOa;y7wJVI)oQW?)HUBS$A4a%6f()y(nQ|mz-_ +izAeI}9hASQ2Jqa=jXGuvxF&G)Ut2P+$8*TmhdybESs)hRd$<7Txb2)mNxN6OK3lH!Hoqhyz}%SDgP=)zL@UkuK&#{{T_L(-%_Fzhu#xI6bv^g;2y5Abw`Qi6P +<}23PiVUv8O9`ClaZEUy+JGk1UV*MiL>H>mP0VvH|d<4=0vyHnk_44^DsfV#(<=X=vEe^_Osq +2Er$#$i$XXXw$H4L-0JdFFl`ASLAs)Fves^`?2jHN}p3X*6fLpXM7eEf2H@_jio=ov`pDmsTu=`-x>t +_%;y>e)Nbgk;9{7aP{9o9uCuEP-9meTT-gfRQGO!*(o(_=(%sXPT?tA*dqL3+(HYN`L0~aI2xD5piKG +7Vq7AUM4n>qb&hJPF)QjEtD}&trhQV=OR?67EiQW!n~K%ZcO351L*NrL6XMMM)BVg$42%X$$yE6jw5< +r7IEmiAi!^@TKj;HJ13$kIpMCs%0iVl3ueU$Pg8vnjX88H7_)PKh*?#tgorNT*lp=1sf-ywnQKNIhjP +&L>`N?mku|`qFC6wgUHTbtR{c3ywlUDPBc%sdM8{0Bono#MD>rgy+Y0i8)O}?o~$Z$Qx9H@mVQ%8|aF +}Q|0+{@eHB|qxwk)l1mH_bzwLrLRs%1aJGf9TK!g*vk-S{Z9v9y>MU*LLH?h&(diSz_c1T?79uLV?@i|$lcDm(~a(o!zHJ1LnNA0mGdb8&F8vP` +gYecM+&wxTt2wQ!>u)c+eUbl#<)p@+{?XPx|s#mMdb`8zd{s`t!6ioTxrxLocy9DZhqd=4&cLQ7>aRk +!n?o40U2mu+r})fajk!!UATO>?Qh10OsaQcRa$(}s^gV%v1#qPwR)yG6d1;pA&yq!wI#(M7TO3@s5T` +p0B7s0PT6NVV+uvyhIdt!id_Ao>b9u#E_+aMM;odAF09!><0jp1EUp^fwop`M9(P@b$P^5#@VByaiz@ +iQ@xF#juepP`?Y?%PRK()?Z|HA1Dv7vW#-w&|XeSl3KFVbiLd9?A>{1lfs&yA?bz@zs6Z5XyR5m&1O& +#Yk!&SDo+4nB-DcN<1Pkz_079g$n8gA6pZt(MS@!7`Due7RNzxebt3k$y#6%G9SLR0Pb#iv^LRl}~e! +#B-<;!TVAm9(JwP!dK+bmGhjS)bpLoid}}8|2I*sFgW=htP>` +dypE;Ivo8Uc~pNUM2>!oM;GW3U(@1U`o)c;Jq>C;MpmERwHDsR58ADQHlYGYZ=x8a)$zzAz(QLiwxkP +b@RiUS9#0)yqUcD32EE%S)oOJ%H5K-DQ$W&Mpz6x1hXr9~INU&M2BHRr$CA=W*6QF#W@3i`9yLLaM=pVBmm}}6aJ#UuOjT*!W +YO9F(LFX-TIP`dI+c#ImGH;v7q!B>1C*;F_~hTS$}t +69{;%XM?9#%EIcm!PGsCh(`>?E=#BWhoaQ9;cpPC3DrbZp3QBD+@6^h(_!{}n>(>)8n8ld& +YWcJ7@)opTb`4i#UHW2?`pW>jPsGKRvqs6cV^ +MEz2v3BTIpec&M4OCj9V>U7=kW>=4=y%^8skiMc4Fbp_?|%1wXsOj2(sWtbO7k=Yu`ZRiz!pXomF1~_ +k0BSg!U!c}sHDhL&eK9zu32G%U`ZHuQ*zU=zg2lv@Jl=j0;@OY0pOn_%T!VD%p#IW*dl8gSXl~r9q{{ +%mlj^T(^pz6y_FI%MlKXX$aH}~_UKM=JrM<-8zzp +Pk$*RJp?lRzo;hwLxY9~ncAPTYUGxW!G3W79cMS-!K!aN+@g)xM0xm~IJr7$J4*@|mA6Xtz|BCna{@0 +h@&Ry6*Frhj*-E-=`o64Daq+68vTGOP5aer&Oqq`pZDC3)I{neYl#J%D!wvrDDd2Bfl36JrR0$e|;L= +u`Fbmy?qte9UIE37D0tiX0XkK{8fduPiJ!U{@TE3U!B3Ha3CR60W(V#KbFM;I3u@CO1&lvJs&&B=f`N +?CA7o@~N-}xWvS!W3_Cu9E{a?Vq(-RTOlE`zS*EhWdfIkkN*pf>KNXDArdc^?t&SfgOW~3qCi}NL5$A +M7&Xg@LUZDg3f4SZF)0kzl!9fY=?=KbSjii9|7Mb|xYBzJKfh%~^fdhBJr_Qm031NMDKL2FxiD-|@F~ +$8p)$Eo|D_89mbNsO$&5$oecTnyq)W;J_{c$Wp4WaAb_G=!w$`Izb7)D;z6R-hIzuB<2*nXNq*hT%(m^KLo=Q +}Rq;N|sucsJ-CkJaBaXNDxw3!K~}8H+*BzzYH5Nrb8i&rOe})n@WSN$^x~Zn@q`D#Gzm)hqK0-7kKRv +i~^Qr+_)U4#HZ5208t)d*T&itJ}H$J*Fv(wVUW0^Lfqi-;tTd!&>V+?G>&pjHWC~LgnZzH=ID|wFMNx +_rBc)^7EFheb9{UazfWq8xx2iA=@i_BNBX1bX&xT#RsyRnW0EQt?MD@~!jgBYG^EU8Z7y&~C5_8H7~b +PVBe$k$M70=uqo>iNm(M9h>y<JG=ll%#LNzfhITw=%lpbyl_wU^jKWl5~yWqh=+R +?N+74un^MC8yY^f1}+$eI{)WJ>}SgCeTm(wmbbV)vz%)h8uWSda}<-76-m06#|H=J8Ez)j{tfp3TxBL +HyZ-;|So0;0H*BySnxqa@4-bbX!`UY@-xq5F8jv&$$UWq{9t|Ba*&0YAoxy?vg!7 +dAJ`VNB@;O>`mGpC_^;<8eMw!xRL5^f^2`%Qj0+~R+XM^!KvTnI3PvFXn(;k7f`s%xXF(Dd4|*6`(xLqMzr ++L+8v(cf?-~vN4|aUa&*8P(IW0mh~bcFTIUZBZ|>;Vuy%VBz59g4u0Vq_O=I_w8-?L`meVyV5T;K|T5 +A}>6X?XY5Dyf34YGI)O3UbtNZrPP%gOAo;sC#~#lZ2}|0bI@d=)E507wo_IX7t@!h +lG~G)_HEn7%nK-^l9T-J|Z|{X!HPW0BM?3C8jTr2x~}%Q4C3D=s#=*#%-@fTbBDg8UvCty8u1UBVfHl +cY}Ld_rrVMM++H7ZCm7H+14uGlPf_meD5l)DP6u+;wz7+vp1beXt$y$yP)h>@6aWAK2mt$eR#RIt113O7000O^0RSNY003}la4% +nWWo~3|axZ9fZEQ7cX<{#9Z*FsRVQzGDE^v9xeff7AIg;k@{wp}1owdDVxp;__Se))zl_lHimXERJy1 +Hs>lbJ~}OUXSd +7j2)5&l;He%=eBn<$UccpV!q!$&D9B!GY# +!lSWCwHJ{hWouRY2Kh3hxFXH=yx}5{n_b4cHuIW<^>OKADS&vHe8qQR;g}3b1dEjRUNq8GaNq7 +(@MO55N_8PL@%a6T=tXGrSy}o#b%Hud(dhx++lmzK*elQr0hmOk6DoC%)+bWuJy`)^vm6)G$dDYq?%) +-PEpP6=iFH!ub$xWCQ2dYq*MZTk*cQvNrTZy`igXBrkn}@xouwtM{X@DQ2@KcP;=YRc|r<&#@KTnEh{ +o}i5ogN$FgGHJ>oFyg6EX>u9yyqoG45qSgnV}9Bh6&l+ffi|676*B_R`VfFlN}5MQ6-TuX-4A7?1`y1 +yxS-RP)cXjKUZ}x)8ZgZ7dvWhOTXy7PU{BJMQ;(sq0-2_2)$rmI}fP4r&#GFYC888X_=tL#9OQC9wZJ +;`E|mey0_LOJFhu36?ug+MvCt+KZp`lPjH~7r<@&up0Qd*3s1>PnKjd26mIK#l85o)z>m{hS-68m>@5 +#cQ|*%nR?BttbRFGptD;rc&FE**a#bA6)x^09g=ITyYt^mkBDD~n{Q1fDk?+)gU50s~jQYXa%dQ?$`@ +f_1-qSaiZ!da58s>U9X$s}#rdkg+S?Y&*)mvRCD}CKo{^HqI#vZ^zp02}UrRxmi+-P~H#&)Q5%@3DlJ +2={ucf{E{UnZ$u;d)R0?VCS4bk^0THGADHeTI(E41QPx+E#s43oye^{^e=l72dP_i8Zkm(|XppM#ar) +wfwC@3{{79Go_>gTH@Ivh~8OeW>(E~HN9qBHuf;~WzAjhqLte2`i;NA&Wx-%r=;P;MKk3;)!x +esG|9Z)v;r=Nq+G`y&lh1$Q+peYq=8#ti{~4^4O=%k1;)*&O`&!S%-9$Z<+`z*I@By`bRYl!S*CVHFs*viI`p=|o(pFaOcWMvH>=_)&*7 +%maA6_R3B-yH%w9N_w_SNlC_6NqXxZ@+?b}FfH@#Dr9q`2Vk53q6JYaj~S+X2V+~? +sD!h_!7#$>J^h!bdiFfapZ$?`904LrWgp%~T~%WxqqW+;sRnI@*jamHKediLZ7O)y(>3(MIFyTrjF&w +i0m)7$&yo>TeFA6I}Rzj!W_VpvPqcWpT2Rc +-#Y{dG$>d;qcml_pMIa^+$XR+!@-GR61&7Ky&MuFRU)=?EbbIIUsjoLL@R_T04@!VD3DYsf3^F6~NdR +6^+*GP}TgiTf41@-57XP$cjJ1w_McuYmn@ZE_WGN7H-cxgPCC}L0lSUC6sXX4PU2SQ5eA=dqG(8BtEVhoII;JfT +L$w7=(_TePcBkUFZE<_`wWa&Ip1Ew>gZtwes|WPU9xpFQH&v+JTZV~$e=vA4*i++8l*UR4bEOX9rZ;% +b1GG_#>N4{-tKOgr(y5iAZJ<{-(DUM@Qv71I-oe7~AHu@$?_pu|4`E^S$5}Xharjj%9ByIZ@Zl_Um$& +^Eja2t*?OBq#%=P6e%3FVed%IG#Bxz?N63?wq)AC-0fYk1K3+D~F%g-^#2C8&#qgr-`wzjo*Fi(T~zq +3(Y#rBr#ok+9f)otFSX^^I0;r8nGNMY(`_3>bMr~QcPc)qT*w@w2m8>=V?LcN>Wl*L|YSD6#l!K(Ifd +v)15?nW)UnpvdTdSCUL=0TNpU4{q9&*^ae{qi%4^Keewdh>Vk;fHbjAnk1`SLAMyuov|e+N_&*SOUGT +rxqPur}l1FI@FMb*JYGJPI*u52zrgjxpN2}(EG2Lv%8%At!5)jZ~soKp=#JJ=C5e);qC0))hH|OSB9b +fYun_ikfcyeQw_XoM`$=!m4;;3n$X&&oh-CzT{}3~V@vk+(NtZeSIX=e!8)7b-tOOm?rnVgXv$W1G*v +51vnsp#z0W?^debiJ+w3XvWNi6X=}VqE!uUFl_R45KKh>gKM^|z!%Mj87Eag9_-8S&L$W%4?)}VV=plyLx!(S4`L!vSJ+x#jS+wAO^a4jjb;0_ya7kgXIwJu9mdQVN@* +%{%W$+brwUorAuAq|>Zd)nVMy@-ce>%M{md)hr%Egqo!U%`f)fe##u|CcQ2iThx^#9VLmO%>@T-RU!u +ZCPXw59RMnSmP%CNFBgJ;Bxx-S-L*3HB0}KRtUv;6LF68#>^#R90k~X7mXHoCv-> +SgwMlQ$)T(?W;e^3uQ3cM{aRSm|pM` +b=pN-Gza+*8lbBkrryjQI2ag_3-L{i_N#+d~OXHU ++A{gg(p_NK@Atpa*oN>XNxp-X3jfVxEvMX1H)_r{+Ng!K$kH!iqO91 +{zg4w8g(&}4i}2Uy%01a^Vr3nlJ>Nm&dsQ@dH&OM+PKvdcU6*0Fn<$|aO(lkYqg~8At|!xYEMTMO`7ZxSFxw7<>P17fahveh +`jjgM>`0zFyGVPhnOWV3-@S_a;GDIpQ-TjXrF$})lOwU#eOZBFLRwyxQ8fC{i{0ndN-X8itMJ?qjPGL +Q0z1753>EfQh;zzV}80ve-*(pOUunZs`c92C&FMKJ}i`ryGI3sXitxV=w=VWo*oB#)FoWc!#zD&L}47 +{`)0}_T9(-!by}q9-Z9iReh-D-IX!@6FUzaJ9{g|*ezXUFxCcMpgP-id>zt~+6#E3e+K1okbVXuB{#o +A;2D+?2gR@PmgdpxeZ^r*Vv=p{`h?GDf+g6WM(xv6et>9U3AUp-gb$6i#J+m +YHi2(#ypck!w@+S8r&p4F>5U)e*FM`|l_x0@*5v!Yj?{T_-czwaO56DwJl`zVS%OJU;eF;+?X0CSV1_ +3Yh8)oe2VZcl&Tnz3iG(5WeV4Av(0qGYR!4YGaa^C3OXLx2BT+C;$ixN-M;H$V^ABklJf_HLt;;P$&= +gUO)&qqSlx&cl^w1FUZ!5a3b+8mekHp%YAW_ME2DfL8`URGT +c-(ZH7GL-Vr@{duvkGjTLp|NU0~-QwbCvr+vllc=a-Od9Y(0X|oNmZ}c%)z+=$xdDEvfXsJGt<957tY +J(WM2khKEMl4{cR`1Q4d`VHYAC~C8MeL9e{(O?;DlM?#cD^HmYEv7HZ7+jZjc{a=G_4h~hcZfeIK$gut?Bv^xPqo5W{{qu+Fr=iHN8l9zz@~f +9M%&INF5<~)eu_`SA*BH4`ZZ%$r9+S5(SY|2 +Qdrw6XV${mI6DVeHQat6w9^ZbC2Kth|b%0i09-n@aCSifTa-v=#Vc1vVur{kaLzuNESDrY3FGDfRD<1 +iNowKLNH0y~0n++CtSoUsgW_MNnqIpQ;}QTU#7$iW2>JSYDIW&@(_S`h}{dYz$Kid)2}kTHX6H_A)l1 +EzGu|Ej+y2sEH+M&svzFRUTcS- +XTCwt)ME|_3ezp{4w$N*`TD>as(ER>X{cdUQ^CDcw%7m19|M+bSxo9B`UF8y2uLo4ERX@M4e;SHx8P? +if|M{cD+yi4YMEeJr#rl_=#WzuSJ5Q@R^`GCRcZ{hkYmt}ruLghTuar}#-}uoxg0xbwyO%ZWQwwWo?; +W)FTWG(esScrSylPzZuahMBpnmCdq}pWH=V8*#d0ZXeMSA?otUAg9;Pc0e$ENVB+P!o+N!5Yax1L{=) +y?kS)8+_G`>ze~d5=A@8vX0D=c0V(XK8GgZ-5Ud-z?cwxPm;OWVhLewwbSV3M`kbAP*>6yV#;=4SGn~ ++6m*Ll?%4B)lSRI5&uX8 +VB1R~#vU)C*$qt2nn&i)WajFc_vu}IXx(c0vQUCS4_f*-;MReCts3xi`&x)U||J%~oi%8kB{OL1&$g7 +#8%);(=>hUkeeEed}>@Q`%-+$Tu>KSnMxZ2iKhVWcfk`>yu-mzJ0q6)3$tKtXkk7|$K{#en2WJS}Am- +|t4k=}-xuk!%8_WF5*7Zt^CaZB>7F7kP^*;k}&Ntz-T)t%gYM;kT4vqwR+j0z`Vjd}KH72ch0B~r+ij ++h-*z0^vQX2DZm=|}bNi>hZ&pFOJ-#c$r>bo4@v^gs9BC=u3gyfwq2@6I<@=T&u~atI!+Y8o1`r?t#i +gI4;hq4{fM{yH>&jm=+^$B$Gebf(ZQ1*i={yW6XeV%n|PO_i`+?X=+HBmbd_QMRp_#b|E+(xKh9swxs +`9912}YxmDO*ksx_Ssr!NxAg}E3x$5?q1HfOzuVyM-IL0sK6&1I(uTC2=n~nEB31QIs=e-$=gp65N8J +9pTW{Jw>nMEts|mH@XB||<@1~xL>3OPF7|*$yDr-jkJdV^(Ry#P_lT#wmR>Mz~wKs;a4jals6-fXZJq +lK}`P$onz$=5w3uh9qEYeNv-9LU_@AuWBNcQ(83c{}|(dHOU2qo~jr1tTL+#HNa|XOaoH|_`3qi%emURfXp2B +S%M)xw5OgX@jVLFH!&Yl!~~yN2&#B}D*H8UHV4bwi|F1hQop1zucz?C*fc>NQI#A9OKsc4(g)Qq>NH9 +MnUuu?c}4H)V>oN>h2A>WsSuj1GWyl4u2Go(P`^uDJxi=_qt<5sx5n=OR^`i?w^UZ%O<29^t53CF^v( +-ak-kY@r#1Rs^AfR#wqHDQHPo&i$D_&N^PWEE>)##sd+NW#{>jT;@9gu(_q|W&uisTvlks>odfreC;K +h7?cY5~f{N34UUDt4MZ0Z`;bq!8>s+xDNFV1=w?|(R$_WtAZ`^%Hd^Y`z1Z%;n{wJLYmAL?Rtxsfi{A +52yG3q|*?_v!7)#YOMp?DF#LWA9b(hx5xfy*KaQ|FvmyI5bTjHcg)OdZ+I{f48g27oquZEftV%@y%N{y$?Ml=A6t^KAvtPD28I=)JR0=T-{Y#k!Iv#3)Pevw$Ydbdr}K_(r*R(^5nz0?r786r&_ +cxPd>hX*ETdY4Sm-%G{tb9y}CU3@c#Urs_S*{q=GplJsKT@hnF=E<6(z~^VcVrpFf`U-k)_9?sS{Njg +AMX`>d|}=yXf}ezyB3+IY3C4W^p*cc}iPmDP06=%E@~Eu>apPBk1HnPTmXQ6m0${`$@Cazmp7(@|6IW +z$@vaPDFQQ>>jWr)_gP%b|DeY*Flf_gSf1tCa&(-As^y;_U35()>?vS{gH1r_Br*C^pXCg63!hm7O+o +<5cymwgQ*u7pJv?93LK|u6h;>G)tEsPfpJ&&UqS)P-i<022T6pm;_@;x}5~DX_e$2Gz+F+p`8UIi-jF +?V7g-tkignGaOUh>X9kRpCOuR3Ra5qO%l!GNE`54_a`y2|+ePKCstxp6v$$9;c3jM~gT1W(@z$IA-%X +lVf9RCAS><(yDBpOxCuea~oV(6=8n=)cqNnFM%#_0VutY#^bt4tSoaeN7E6`-8sOO` +Z5r<~P(5IO{_1+X@Z`eFI%&HT1LEoDYI=-)zcGmYG`7n_Bu3I_rRSyu7V`GzH^p&(FX&=#!v<4!yD25 +uT*WGyyeONjD3@<>=U;Hkx|sgpjf4cff>>7%rMBN%k?I_HVhw{o%PE%)We|>$jkR1eyZi_O&K=p0BOo;t(yR`G0g +=-f<}cGpGJ%7GaW^w{x<9$jzOk2x%VVP6G(XJdl_rF4@4M5t%4j$=&TDib6i()40xJENr8biQ|@Ju7A +RoC7cX`7aWkrSb5v9%)1J0v?&fHEk-56Hii~D>pjBMPp{YeNG@VM^+bAh>U?&p$A!g)QBCDfbV9}^fU +A~Q~Bm5#QvnCHDhERq?s>sSLzCW$rrTq_`@NCS;An4Lr6zGlq)CO8O5^z(%J30g+S!QKOV+E`AQV!y9 +K?rA$hL{d!#;M`KLZQG}8k9aN#Trl@B!=03u^CJDGD~wKd5_rZm{9Ol(rg+Ll&9ID5%{TVQk^PA%Dj~ +^b5j_!)4z}vK(|jnF&Gv7%wWU*skdB)M!D4<35X0j?-4j(kuf$ydumPb5C}5HAP*tq{t-dh*n-+NwkQ +#)#a6j2z89a68!?Kx0U4c&|>l9AF&?hFHmem-B)6SAI2d +cGJ-Z8<*{Pc-~v^djW9KfpH2TJ#S>s(t%I(Mrz<~BLWvh>z;1Dan`T!f3lqUd)iKI-fei|nvVb{U2)W +vuba+H@K_S{%Enx(yyJye_GWY%0lItPdR|#QV?|OdfTQ|X9WV=`r6r*vC3Hg&fdK?&_ko+Q&)YtP3S~*9` +6%dw19^oY{+onObMOy|bXR4!Ayg!YT!f*x(I@fNzTXPx`vxCuL+aGs@$|T#%@F=_K<&CN_lwwA@(-jA +=jHu8MkD{!~Z@@S2QjDDW+nr<}u3_HU)!)6_zey$llZ +y)tG*;D!|Bom)&f5KuvlcV$wY? +Hh+qCC0oqBWfk=^s5k?$MN56!YM#1{OGS6B1D3_y%Crr8_XFk`#_CW_}u!$0X{8lb_B*vAC`XVDk^Ny +GRe!nSgZne&Dx2URB*zoA(n-jwUgebWEJ_Jxd=V|-{9)N@{n21bF-|6&57swM2T&)@U>G}HE+7tlaLX +B{xDlc504x=>=ytQzM$yI`%)X^7yQo%0&$OX#cvUb|`#%OiA3!S&0*+5I_;HF&IFM|C*N=NuI932N<7 +=8Vdk3t!l4QGNPb!qr{}R&dy#j5-7&*3zG#2C+IL3~_GTpF_wx0yV!;t}hb6)zcjBMW0~J`XLl +y*U=VU1m{rlgQLQAG*-oahN5hkSp(8Fi}T6Y(55(#4${LNnwWz7A~|HBB3vKaleZcg@quzEs-yct3Hq +AZHaklud$MiV)xj=_7$<*VOX{jB@#<0TWnGDSB<&Q-~W(;w>2XknpbfK}zU(j~K^z +1E;HKsUtc*{$JnJaJALQ<~9PN0u)+uJZ!23u@}jSSve`UJnJI#(0NXV_xC?FiM(thZusjS?jmS;O`_v +6!2lzmuyq2JD?&NfDiRCs$G&_`G9rnF;nTTzdK{ol9)k@w8NztH{5408z4!Mn^h!)8$1NxHrDE&#cL8 +14EkgF1*Fw&fK1OXOFZ|zjx_(s5iI<>G^tQ(xhJOYJR+b?CU$V|aA4wvK0zI4mP1C%)4WMQxE*2 +m36XUojZ;QSTi&-(b^uSSD^f)?l@&faBwT027G0!d;AqgLaK%JAY;4t$ZIm)fGVKyL10~yPvlb^F_AS +wXPED?`K!?cb3c(q-D5N*EvohJT&`>$2LJMP!U*AG^{DEVOz?!iRCb#$oPD*PWSQ&TO63^=@w%e#Euc +brzAq$nYrM!vbz5^@y_qxN;5_Wq#47tLO;f)SJbfz_rS$*KtQIz>M64sQU-(gMATCA}}ssq-a%6X2R& +hWU~Ri?A58#>4~y1$g~(q&LmT2+pi(Bf^V&3iEHP{=BdMO&rs>kNHh2M(+p?zw~IhR`+Kt_m&Hb_ms$ +)A7jBIhTZv+5A}+e_*358+B;oyXKm_l7_}=gPV6QXK17o2SHiN8^m$;JI)&iD5zm(3_KIo@XDeY&7Os)^Ce`Zua*lK1}`vd1Anr?j{p +wU&mOQJNyySbTp`Xw5*X$d#V?9m|&_!a)M(ZUZzH)$r04xmXu5;xeRedwq?1d-Dq82#2)6DsIqhRiIGJ={jO{zHs}k?enT*f8??_#MT2r`R}uJbT@ +2lR^T`IxxA*#f1MTLeF!6Ifby#;35VaMpHk^%`&2HALV9wOeN@i01ZHIVam37WpkUrmHA*3ecm|8rr1 +7xSJO(rZSx00bLt9Qa;Z=7$i%{2&r8j2@u2n +K44SY+p%$n|(+)&Xzd_ymP8CUg-bg9L8~0VJx=P5M<7}f+iz@bVN+Xg1`!wfa8u$!U$#hWeagl1GvtV +T>vExi0|hZWe>ac?>X9J`t*GuDwzs6R&8WUeNQ8a+cDpV8MWFU5 +IKA^3*3n^k8G?tGyXp|=2NI2FQX015uyn#jb*j_gm)c!8w>HhZGa$@&eStA{QJ&GLpC>3#-^bkginvd +vLEzY+z_&1{0Eyc)YM_jsNfGB#?09NXxC?Sh#wP-cM$#>c)*y{>+XX=E2W$+lX`UpQVcihnTPV!r% +It)285p{~Ts%^#=F01dpGED+adsK0NxsdwHA*{oE0Tv5Ov!8xGYx9ybD8FM=2qGH!x05qR(4KO<;$7) +J$g3l}+L9$Xfja(d*lFx0S(k@cJxkUbg?B;R|Xc~%9cvjWaKP$N@%IuVR6HoL=*lYc;9o~lVn&b-r=%zHDxKL1!-%%M8g1|n3^Eln$k}#G5g~CO4 +#A7IwJw!-ZK}{V~Y?NdSP>`>4;(s6!o`<@QEz}&SsfF@^@+=f$S6Js86!Gm_s0C1gg<1j?TBsFJ3qo; +>MZ#|oPnd@m28>Ucg&fK^#C}pE+KcBt4gK;9ZD>X~AM)hmIw;Yx+-QPwfG{*0V@o`2M7)hhn1x9KUK_jpctX^T#;3t4=vh>c$hDPvi6Kv67bf3;{|ox65K-^XD%}O41aUAOH*}Wj$?PEQK99+BllNhH}X7j%s9IHIvfQShD7BPF +1sOTdCmy&u+dl|V2Kzt&rCWb?L9TP5GZtLn;3Y(f;fZgGBsVtax<@9RYK3l65)DoX&&lZ(8l&ovFDkG +ys;wLR}VD7wE(Z|p1^TcU|PGVn&LJLvCl@dVs0;|W-ia8rB5~ihg2E?KXNz@MG*=jVmGf4X+T4j01yrMCYD1rS$gim_4_Cr8io&}S(v)afG)GT*n`}Bj`UeWsJ)UzvO3CdZp>CWTK!v#7vTd1Pd9= +-*;+B~X0A0oU89Q2hv0<&y`L-+A6}GUB>BjKO1YQavbkgX=enUfgz%C0mG?V9>b|yKEtV7 +%+L1#4;YN@PPm%y!On!G^F7#^uz0>-0X}Cq*u-goh7+3$g3saEOnv4*Ao+|LPV(Uf0yNATdKaMKK6e= +W_nCY}!_LjFXxLdh@ek60bn@~t-(#YQ~z77|9-`=Q7DmLw(~hmyp^{FWpp0f(3d%2S2+%3kH}P(ct)g;J(33F7(yVNuN`PGb~m1Y(>7)y;MWd>Z#^n`1O(be0 +)>L|P9Ay63Ng>vsX62_RGEiOltyp~Zks(W1bG!AC$4@e7h#`}8Ho@n)*|Lc&tmvon}^MiZ#{}f2)uh4 +9!cn|3l{HQLK>>e)T%GwIbPzyGUto;Fd;oK1DAz)VW{Q14}qz_ZKkG@fX!UMFbf>7Z2FcLj(Cc{qD={ +nuKwtNnHorht>F3#gH9ZF7&L;|$d8fjr=VtPOyS4#cu?Mi2Gh(KbLD;U5&7_PX`itM^B(&{goT_F@xE-;qIXoE +WtU6-|^t7)B3eku-zxbeWPAZ1B| +i5!A1NS;H|(K1jyK*}<%_0$+>FMJWd3^#O1KO}gL2^UZsv5?Y4B5<=B{sl)F@Xn5cs`ZnXN#a(npA$y +#`pK2CNRGK}9Ikq#lqIjLp0%!)QWK6O0;vJ;4Sjw4B)r~aq3;xCh0ROfEmDOQ;RspK3P^CdEKC~pK9t +az1AODM?UOEnW!kCt+nA&2DUeaA}bHZjNYixa92Kl5P8_%;Ko3(^*!STBNyojV{<3m+}4Ns +L`(5lAq{YpM_6SS#B3v_J3Q>6U{QN0VMDhNr-|Hz`;1SMI~U@2QZ2+Pg4jPHdw}> +X2LMn~jq-i2duRn57NZU)gomr-Zj8P^BZb1k!aAvUzI*!YfP^26fI}-}f*A5^T>)l>RRCp*0f@dZ$F~ +);N8oU4;s#o$%$1^!WxnjGIdTvD+eTryF@P+$@0$-vfuk}X@g9=Y6>y<2|_+$cRK7f_MTr>q5n65HPkO?^*G&PVs6ndXGD6*x!Z=A(J8CmT3nDxfa;Vg`5&{ETOY5 +7%>GT{3s}IHIS5zcl1V-~(#a;fxL}KGChS6)3sg5j40y4K@SwzE4ki~10jC17K%fcRAfqbTgcYFDJRFx3+5h0i>*tb5-r;M^CM({lr9q-r3Z;4!79EvZ`;5!_fo-O4u5|$OY5kxl1(t +5Q%00I#$Tu5A6Z`m7ryA;PPxQe{wfB{++Pu82cNAV;?O_ubrBV2o0k_ltl>uZ;+n#B$)Q3zD-h9V~BA +xOIv%Zi7WuxdmK%+SKnU>y>QyC$Q38cDq2O6tAo}TZZ5!%nC~-teJQt)Knr@A_8nQd*wwLZ&5rT;zei0UU};%MtlWl%PTMAi0 +dI$!-3&L28%1G$BdzSfM}X7mQkE{CBIBzG+-4fABDn?7D9w938#E82m?2&S7GMUjt23bm2Klt*Hs>hh +?79wN)#GOMZ^Z(OxFbhTSL6azDm=p?o-NG2sGaDiUtxWR~@TCW#w%?bfDt`V3@?$U3s9a&vY1tU)_ro +7hK9--EUS%8gw8r(V`%8(4-v(ELIIq0q2w1BEsZ$pgVVrB?~v*m5FU-pwn;!2xI~5N7}@$ +{G$Z2ZjxV9J4%kEq>Ej!b`X4*(L@kbyA-X78AoxBFr~p26q4u&yVWQu~_fa5j`K^#S!Qn|!_*27U`Un +htG()5G_QPGuoK5xV>q*htY?Rd0Jpy=Rj@|lnTusLf_<%tDSQ^xts7XXaMExr`CBIJLutrJBUL`Tgqc +A6^&o2jggb0@uextj1Ra|asso2X_X20hlIH^#a9N^)2HcHvs0>knXDGR`Dn}X)~6DhEx(YA)Gb-Y=I7 +7{w^fIDUh!;!m(Re`Z&;%#;f7ZLa|W3X)F1)&O#*wSLeh#Oba$UH+vZDf5C1`LT$r(pjXA)>Qj*vtn0 +Eh8yohKPRn)iEW$c+O3-We8fB|jAdhxK>aUnAx#@$naEbp@v=rLh-R)~8I4fS`_ +}9=qd)qpM|xOC^0e`o0oLYeukFVuNB7`l=%*plmte?nHdTZUJ8Sh=o%}q +6PB?K9XUF3~q^D)<6VoFN>cF^mWp0V_=&okqkPAOuev%3cyBXiqW4o+1`+G!8r(0XPcWg48Q&0nf&IU +cg{_vOADFl6WqZ(FdGvvK`^YlXz|(X8BJ6tBLFm&)xp&6trQ9y4J~cunVsLYNxINM +6+xl8YUQDn&pBN^GTMK{pp$zU~#<-USTk($(~m;+BxLjfh +d)q&Sae{D0B{_0b_>3w6w9wc;b1RbrzL`Xqq&9f{8i{ea*zy4FJie<21_j@6a_?& +2ztE*eC=#j#Bwq8sql1sf^&QbgP-!6$@MrtWi__(t}j&`G11!FufI9=9EP$`Axtr(_UCN;R +!fnLW{9t}|NXdCAJ;1cY#qn%$?pK}OSZh`n2!d+d^syV=>tz;-!5KNfGXuky?xU^wkeh@tDbTf=F89Fl8>l8Wz*AZ~>=O{GD-(w%RD{egvMV*1p +07A$IFb&LY8jVVAl*xkP%`rnZFdhhjxAR~9Bx?1H76SmvH8?-#|nCfH*64#6hKZL84?%|pd$@lgF`%e +AuzNvn-G+YX9po%V4Q~4NNF_>C6ZiY9AWAkaWDvDTp?Tg{4Hb;-p87EBF65nW%fRr4!yk0!oYeJ$xQO +K?d4!NRuBS0O4Wx=>H$F%wgS$&P4qn-IS1eu@oA+*eB1sP@ku3xyAeoHu=_z)T+&Xqe32E`a}x3WQoCyYK&Hjh1@mNmQ7v)2mXDi1i +S?!@A>xWirnr11F+ +^1n)#D$ZZw~e7E;E)tT8;Nl4PAt9kB!t7)*ZDp@3gI5!)EpqR;s?KNUfrv#l`X#D&yR`XJ8nA%o%e`n +C-a%^VB(t`FQcc=cvcZ@T=C4oq)22&sH=qV?GVh~P%tg0M@qDc?5S$|=N}b;B;&v`qULF7em*0b-;Kz1{xSgEpOZPbDKwK&f#rLJr{RL;F +(kaGdRq3UcxxxMp4$Y%-Yg)3Ar27{;Y~LoJ-UR1)Kos(i2)#Y0~#pbJA&A7%;w>6Qb7@(AK(PCf+JN~ +ViwJ=#$(vb0bJLxxq!{Oz-Y-?(U4$9SsY^mTi;iJ05kf$0(#E3i>M>?UGT-RgwDEPTt*w>DT0(8fU$( +m9AG{;L$s+zcytnszM7ocg@DaEz#E#RBShAOPdX;3*9^miVKWy{`qNwoEk7Pix4lOrIB(7jm<_U-ZE7 +m`jUo9Lf<;Yc_6vfok=E76y>6aM^bE8D7a<YdeS=t5^eEs2XW +AR89Nb5RzuIyYdc*C4o!Cq8G9_JhsS$JyCS(jQRf{5xz4=#$h)++zZO|w>ehL{bN(>nj7&nq0_NfqbE +;VKZCj618Y27@VhTG^hbmjuH#ttWpDQ=NW21jPI?pC8$I1xtxn;nx+rOG5|Oyo!aXB{Y|et7_5Q#2=1 +3iD$jk%0>&gUPrBL49Y7p>HJ|aSNf}yoBd&M}|5WVP`R6ovX0C4TG;yp+tJpF{4B<5E>}oY#s_Yoy`y +f$(gfpPRL?@x3WafTGF&qE2-E!#Rw?Q7MwDe? +nMk4POp588P4}5s3LPS0^V&#HzOFz8`>d*F`=hNV@A-G2??j05y-KzgRl(es}vz4_$r0R2);_;GlH&C +915<)RfSm%?YbxJwaA4DM3 +kQa~mT!jV~=Szo(9A_U*1paM;(7QFKvEVL-@QlQ!(mRBQCvAksw2zcg1a7H7<8zMzvK;}b%#kJNsW8} +3~QHX{dO@}_dLE{m)d4Uv%h?#kTlwel!I?7wg>tNAdOFBD-4^!(KRg==qK7 +bvkDZ?&O-ar`%0@oE3f8ZjaL_6w-CU9LsIqXn$Z;<$c#(5ECDo7j`OvspM3fB!2Y6cI~(XEI~-%ss4w +FNQB{C;#S7P-Y}C8iW&D2SD)KadESs1IZ!o|1>^%G~&X;G7#z^ddJNW!y1zsFP!Z@eZ1W;Us#a^p1x!q(amP-5>xEYWI>{&E~2n;s2Dtiu!J5Cl@mbD63JIa{e}V@8n6Dh +dc|OBlb+JUL?6tP5;DBs2p=zx@I(AjZ&om3c5>RN{o+Av+y@Vs0(eh|lHQ-gMLX`ex84$o9%t@{=r6L +J$~&>EsHG1ZqcUGhelzp9lyv>%gKVJr9R8g$lj^Vki(dP6VIh5maPYdW4{qg%N|V-EexNH)Jq%M5S-F9kvXd1a)CM|jC|Y*a@ca0JzuM#+NG6iFJHk@U>}yR?MG?B#TMOIN7#0b7*4m)J%Un +5&1W!OHuvQXkvv@#tgR!IC0F#&f!GBSdo^-h|b}JC$QphVj@sm98!ha;&+yS4oFR~ot)5 +uUv37qiqEGEn>m2(i^oHT%^bi{0~;}H<^W>&8Olj|1Qs(XWP})b8Z1ueJ7TbyV~2!U(P{jVKNT2mglm +&`Q%R9Fa>lZRc*gn+Cyu!$F=KsFEGsurB6xls*{Ex`l>asurCkL_fm1R(QyGgl@_2)&p#9i^o8SjV^*S!oM>r#aFHSI +Qc2Un2umaI8QF@-IK8s#VX7~^F@duvRW#{BBgKR2pm6ncP!xfJ+irifk53j5s`mo-L=B;wWc&{&i)Z#tAMi(l%KL2;_=8T7S!m>kpRw|P}=+VC4w#%(I{yYJtatGoR3@tHMM0-P#fTi@m&8-Ep{v=+s&Z~CH;D?y +sx1lW=vO|!QlpqR36<3#E-MgO*CP7CH~R*yrjI&`-vcCjAfqzWZgkP)+y(?&2Lc)<@-;OVX++k{^t_! +uKxJ19EafGMHKZb73&hy{VjLOxM}XT6Hs2;rb14poZVbQOAm;~rXQk+^Q6ndwuCwa4gpDNeqPf|Q(h> +`CaX3+7b{RnNuSkRieOX@9(5Qc_0pTmWZXD9IdL%kTY-0T&s`xUX5tb40LcovsnWT3w8Wl$b#E$SjVq%Z_C=bEn) +`nvMb6IKD-Pao)H-G)LlX@zy6kA!m^_d`xi8+!-_2Jf=EA{z+-=%eh3;H##PSq^vL51roud$tW?JV3` +jL2M7t5;>?CrvSZ;ezvb8LpndYSTO7le;#g?RU^@OWi%lsE2+0G9ygtKb9iaW)J7ZLDgw->g+q!yf;JQ?OA`UbX_9&Q;;0DkwR@LnL7- +1iM}y;QWDuAli19%8n9 +RYG4NoJ7^iPaG=BOn)oGJe(}s)o#JxUtvIwrw%lzP@U{o1yz(R0NT#mfqzY3YBFfeu;BmBDdytKtz3m +JL3OQ?nY9c4=HVg5BxIlEq$e6=BKKg*+xZ@8Hj`7y{9UpmM({d-jo@$il&hD_0_8mWVaRi6;j@>MJDD +jTItZ1gzT`Vd&23%BvM(JrJdS~%YJdR=1_l|GBc~Gsp&R9(e#XBB+tnMxq8yj2~xywWsP?P-jt64biI +ztIzivuYSXBBs)Dx4@ildH!us61k;px7NdgI-Il#0*Fq(BQr{xjgL~X|A-h5qtOrTi@X6v7k +$vuC31Zk1?ov}5VdEb$|(ZhSO=OM6hk`@e`*}yL{H3pX!zQ{-&Haq;yU}Rr@;m2HYf8k3Q7C5XLV3GS +G0%%4yUu>zN0p2)6nAWF?2lybzmySHm5Ps>%6Y{t=j* +TL_WsoS&#FxSu3yo=diCnGsYZcV53Ij=4evE4qLi2z{U;CM3 +EJ2(!%#&_8@dsdJ!w6Df_>N0O#yq^(2a@b)3(1Pm$O!z2==O_du3qLegm7L-RYo}! +7*S)e&A&~YGIK5)W{F*PL2G)^R$(p$&6C4R(|FtmV&kNJEbsefgyKFvP5nYal)CpD^@n;Z;GhA1+9>50m|=@4l%ss<>4Sw)13F8B8gSSG^reR +yG;Ui-*Lp4~$fSXMP(FQlrZ&d6u~DA2O`>#x +rqJ$kgs!bNmf3+v|F-c@grX1%NDy_;T?^b&6!=2|HEi>L_K`O|0rWeWW5@v0~`-+uGW?d|Q0+tG_OTY +fW`PLIENtK6pQKW{G{|Ej8etiQzaST*IxUY_?(y~4Xl^ZW<31yuXLr=RmMYyN)rZ7rn7k3V`*9tOSJR +habD?xyFLS*8TpE3z=`MR~8(?+AvfUp-~AvM{f3iY`hvWzh>%6RMJ`vW;rfwEf%1D&r?SyYs`1zNGNf +{MEEwk^f&%O9KQH000080Q-4XQ?aYPpWj3P0K&-u03!eZ0B~t=FJE?LZe(wAFKBdaY&C3YVlQZPZEQ7 +gVRCb2axQRr?R{&L+cvW3cmE1TQ+uu1mE>1;lg#n>9^0|Az8gQvo_$o!am{ihNW&e9)RNT5N-F>Tq8k +t5NrIH-kv&nlJQ4{sfJURy-Dq^ri+mZKoz2(Px*%s~QLjm-qFHj4lu4eAqIH%`^B +FlylZq5^THc!jh*h$hoL!M@mKT#U`6A$KlIr4YNve37#$`E*t`f4jB-`@dJzk*Nt_b~wFeakbS(2^RR +aq4zUYf6}{E}qJPoyv_(bD96ouo5T@IFn{N0aloB%GZ~7DZgqnfj0?SyeZ%jH`=fdP2Tb-ZBU!p8acGR6D9NiYQ?i0?-8;N@@7|2eBYZrZ<wU%6x!y3WC)moQ>r?Legvf{oC4a-a_o~V$c9aL +Q4Xpgbm3>TB6fYIjPli&c)3c+5&QM**|P{222j}_)8u@DU$9momI(fm#o6TDe+mVkJw4B7+h1pXnp_Zi>g{3J$SI$Y$ls0le}0wc=X+O-#z#O<$r`oW#?wzkB%I?EK-gdt%brN;;2@H3l(Rkm`i~e!+eZm3OQID_Q~Yg)~FopZkc-`INz6xQ$j5g^VRtsu +cdZh!;?g6``D`ijxfJoO0d4HIV+Cx3??lOE^79UyhQ2l0Weq@|a?n?jB$E42& +!1It6$#AHW#m{q-@kwP*SD|Uot(Y?`1}X>qOFy0zdnEX-CbNOz6Bw_*$asZ85jBNY(}Oz-P`h_mO6&> +bvA`s?> +Rw*r&6{=0hPM#w%*(g$s|Z6V~}32ql3Sr4po6;5h3kNU&^?Aq%3iW0cRM;VLi7EmuGY|z|*GH@oOsZl4j;Rql;s~3MvyMR+q-Rj(GnC%VL-SA}{hZ1s;k_j +iMPwf+MXiVvwrSG&*<937JhISf{`!wM86FDf2(440=lCMdVZ$MZR8KXfPymYFqwDefRVMF$^6=MVzp$ +h6r(v@!*)Vb5ay^l~CSAvT44iyuJV`&_L~nIZJRjBd+Qsi_cTS-bYKS)X*}?^dd_0MM)7vCqMzEGj#X +_$wW0>YxQ^`jd32jR&#U^EhM?~TA)=$CNzyTp>Y*Pt6c(U4PUwWOy7x4izE^kiqdo +ETAMV~sQC587)mayHVYN*dblPjPnS_%*?E$Pso$2A5=KYF`Oow`NO2uKrz9h?I?@LHq)|I1ZeaT-xesVmneCdms~ywCGqiz!` +O+#e$2nm1;n-B_o~KD7T4GKr$XPGt4J=sX-vAv&~{Zjm8_$c*0seK926MQi>d?a=&kr5`wGIlGtDmA_ +gf==}Me!ff>Wl*;7~Ppl-O44WAX2gls}VRx7|TzI!@2CzQF8Khv#u@Yz#^;*au;3d-=wa;;rlh;OhYU +G+e#Ds!;VVv#PcQ5gJcONF=UXqJ>9UmVk|j?5`F&&F4Ak-(!d$aI0j1Bo<=VQj`Kk4v^y&v1uYGy4KwG +tDDY0cU^d^%vp-@#}1aH)jEhcE!9CZNTN@z>F)+HhU;w1)MWZf|i`rgV7^ed3N~K0oxyM#BNHD>M>PV +P+7oR%tRtyf91P9{@XtP3u*R*230ELm>2^(Uz{xFEe(#tm@ub?$V8RqkY{ODR~$Rvp%dqc?x7+d5~~A +sNX8g^OJta;i_j8Q7dzy#aWBLQ;NUJW}d?AjZ*YwzJxJ5hrwPcg}=|Iz+_#8`75O`DoL+Eo^qA1->OU +)Bzi0b6Qy$po$gizxnh#7V8QBJu%1+EquN`ldr<|oF2h-$tp`~R$vU>VXI&H&SMQkMR?DH3;b(%ZPRE +ZvOEJrOi;K%1iUIsx&5hK@3YVe{sGSLh>Hz46WdTEbih`ii0jCC+EshmFHYvnyd1L#wEICw~07+-?c~ +rLwTIvuVQ=y=wEbZrC4iBABRSI!p$1Sn(0Sr@;4c*gt2m^bl?zSy>mwQVMk8+-PomTbR2577sjZ9N%@ +X)*KFknM5H9Es-ti;b$PX|CUgU`5@=<1yp`A;MREsSoJi*&oXfGuVjr^zB)#>FMwI1if!#%YQ|8X8=4 +=&qvQStRnEcmAS%{_{kMit^C&m-K^0UIfdg$lS~tLS>w-G7ecI;rJ2k4vC_S +2FwL3MaN;-X}msRi7q$)40-}euk|`E~f)n9WT?oL>0oj-hp#_YCWG`X89&1vjv%1Di3_jZF +-PVR(O>svoROI$6zZPQ|%>zLzr=yr^#$h_vogL1wTJCu6Lw$jWNPs4pm$E-{C|A6yKCe?9rU+_~ixFv +y$q58NGV{T2U4_euZ`}UPz;}V*F#mqO4*_p;%!;^(K=%u%)QB +4Zh*B&eXb#%cA{zuREPn?o(sSnpZh;hMaM%t7eopv +FzfRpJv`CJ&;yA4h9<(H#da!{k*JXvLZvs5CV`MW>VTo?&g60H>Nff=AGiS1SLS`dYPd32M0nwl`GUj +m+7qb*5GN02$w2fZ8e+$C{ZM*=>OB%C1mhQdl=><4$BydIt$GC<$kSVYe6N|!}e*`$Dy~=X-NS?wW%z +5 +8QsQx(7zzMX7k~I2WZXV9=G~7nCXXL{3x`%>9DUF;taUn)c}we1!dyDY1Ck%h1CW7I<)fmR#NcjXCUr +Ioy5TV0l{Yg*}A(WWlGOUlw0njSPUh{dHOSvg_u`XEG7DUcz@U(jO20@sEFu*O@rTz!y|W7WNDDl2N$ +9K}5?VsM4lM1QD5WpMQ{6bp +rN-p@D0fGbaT$xnOA}`zXFakZ+4C`sfiEH_N+sHs#-y(koGll1m%ZrP*m+0B-tnSUFZ0NYR2Ba +q`R>7?xNvc!QOq$H`XhVd)V6(K>7>#X@Ms3tgXyE&#RU)(-`+p-cSC+ER&~;B44j~p*&qLvmfw**&wu +{G-$rB`|RpUJ!;omah%FZycWOA7T^$zmf$DX(4H(%b2W4s0!Eo+J}dpv(fHA4oi}B*jXV>^aqGZgr5* +^YFY&x|X4rVOCLexo*7Mxr7<@QUYig{c*9}k(qL&wBdWo2!GT01RdCBCS@*-cSGv@DdftvtdZXN;gAe +x$1JU)2r2Urosx*93OHRAL!;E|lj9_?8pbiFWaA@=%r<8f42u~E#Tm~mu3Scmx3%Cvklz!et9&eU +T{!#kCD25gZ)o?!8mU8#}X%qlG1k3lm|7Mr)CQGu+X^ZIEZ~8TJ)>XeD +cMBz_p%s;Oq=0qo_(SN)89S;{!YjiQsS#p+wK3hxUpSrdlXMIk)vb$>uqXkDY)XDWg7R#>@Ng$4#N0)F3j9DJzq8l7uhxaZFenw3~XosErqS*a%pccH&5x&kJBo3=D9Y3zDwjx&pI4F{2O|JGcu*EO{B#ML1Vby{lDjc6L^#c}2 +;QE;-6Vkknz5;&%xL^gTb5c}clzqZ#*P7(jMk=FqXNNTbS1H@L_GiZj0b(&(J7`YWZB{< +1P!mz5(7L#x9nM~(hnUcC_*CNos+oj}C-np+!X6hn1eR0+jN4c>#u(=^|JNRhxbpMq{o)dbFh3c`+l@ +OZ?1zvm#zuSiiOvl+=IZVYm{LiCOcJ$?tDL+9YcNuGaj8oyMI(bgIHLKRwyy77#=Sh<@}&VE~3QHBNn +B16ra#tqy&UA~4E#2Iqa{8L(DuB}r_kbKH#RNabjwkdPEVB$#@y~x+u>@6~`?-MHGS5tTuLvgGJYoFF +J;1>kk?ePlSG%2R*G%o0~IW3q`4Wi1iZRAEHloZffL6=AwtykDX@|1t@#L +FP^knkwVf4dGO0txm)eE&?!`p)DRaDG{SGQI(Csy4d#AxVE7Jb-09#Pf~;QpCp{{%$Qp-nHrR-5q)N- +0BCRcs&dTN-~MTed(Q@Scn;NE?3ON^u{uiaPPRSFvLso>d$ntQ&Kn@U`Dt3J8qWlWk&Bn +B9-rq&K*X-j~-1Pxh2E{Zdi*iIoBjt2}&E$Do>N?mad&_mM`IM*@tcPloIDRNZ_yEynY`&JBHs*xUNo +4R{3g}!Lo;7Chl<@nkVE^1hOoVZr^L1fH`K1^b6-~v^*v2oq&C$o!I +&HGPeVoFY2dYDDL(5B&YUeZ@xnALLF}b>lXPF^a*8L+SLnBm<$w3aqKL9>TY^%w=cZyo5U<#aTij5To +@v{O*0I~%Oq33~w-R*(fg1@^0N=1%Ure3E{}o1{s&mzvDlEfYMPa;d*H6_6RzHNBR$sNFb6tBz8#h^N~ +zyMOCCf$%G6xcXe$86Sn(WzQv(8p-PQe-|Z)DfRe+N=Ps=hft%mB)%#)%B+iHjv2`-IfLS|j+CaI!5hf3-yELzX*rgYJs`jY!h +;**>q(?(fCXc}#IZpf3>|UpH4K`oh$JLiq`qBrh*L{2%S#}@hjtgVQMWI#iJNB;C+RHBq1-#}=kpBP= +{gMhJG4sm|0Fq3L9N01g1JM(TjEc78%PP$ast5o_=roLAhouNNH{N_>Tp=&^>zVr3t02xGr*gOd)s9dWNCh2a-c}1od +LJr7(qRcJsz+e6KX4Eo`C`IBkgT~QN{0Y%{^=*CgRC12b7I_+4(uYzN4rq;Ba@^L{Wri +>3rdisMYXPoIAi>fMc&p*&X*A9>;arQK+hgrJ$m$jKE#__kH%{6CiHciF-8xWW=2(m54ukdgYmB89D4 +WSw(b$17b|H%$iNa-6>oh|~G(3m7OLZHQPL0YlGd`JS`i!$JMj3sTmeyT^=8m~YXZQYecF +%Cw7)=+D@%g$MFY;;(p|Z+>BkY=n1*eUi#j$Z(xZ7PZI(HOFdR)be5C*E;`8Kb6?{%Q92ZNo5N#)Z!H +%Pa0R`;Rh^L~U@2f$;eFfZg%>2VNqAUyb(Q(~!&*bYeO8@OUn|Cs015PSd=A_Gzk2O=bz46b$P7<)Kh +fZGk9)luYdy4iKQ+5#?G2sdEg*tyd_&d7rKWgu2|;&;+9yQANT>7-!Y)7j4mS{a$uf9}aKloxOC3YNH +l1HX%IhhUO#a27abls{(hLf9dEf>@&a{5=ShZft6NiQ;3o1LCF?EFGC>D-Nc^=fcNy+Sd1tF1x)9#X- +`6zFz9}YlG*Cn@?7FqDI#;7)Cf_H0~L>ivkib+!p$2`+bIc?JP-V?kehV-oyTKTT{p!G{Eb78-mvB?M +4tLT!IkiWAU6$-?#9CA2q#TTlh3BD!AEoxYTsl`wJf>lC|{YVt&M +?@fD=d~%u2~Ns!8U6L-_4r%n;EVxwF5>AWq~1inmu|t$l;8ii@&?o}fltQg6r +Gf3MSo>kOnJy9=E)2pNib)e8l*#q!Gm-{#1a-9h^4rRt5HM;&Yg`}Z)3RY1<^lJ42JLR6~>Lis7++Xk +6P&npMQG!=FPZVuLw+Sw8@Lha^eJmX&8>RoT(Fmz871FxuVgEh%!21O;UZRnvcIVB^B|JH}^E~78Zuk +2{#02WGb)z@u+IL4ql!SQ;w&L|*4 +a+LTeV#{8P;%nnfz+I1c;z}t7$R&xbdrc2=-YD1~s}FiXKDI(AeE^ +il%12Nlq{4OlIRxQBz0#e1!?R>@D!a>D^6)vhUsI}93=9B1y~FAcI3X#$ +0a^dfEAFC0HIX^cS7-`7D2P&}uUF>&bb2x2*Jr79S%mWWILS~U9ZMqI@kq?*QTqu^qh`+7qZzT>rI_# +IR2n4kJt3swa3ij^@7q!mZn@|(-cTD&fDjw7$0>*XfxBEMX{fpJB-7}ztsDCxR>rIXu(w +ZnOW6o(nM+0m-k03vK4)M9ZWp%h>|EVJQSKmm8KyF~mbD=d|pQQBLVTaKiR0-rPTBcAvG +O;Y$-73<|&Usi+XV*n=-Thl?n)@V!`g;47FMFOLx#W^@zOKepnqO|mjE8uP@7gDPTe5d_HA9T)8qk8X +0C7&O-!H&1e0XeI!=BS6j?1JV%lwKA0gT%kZN#44!x7=liJiL`;r=%pryRV(5>bRpz^?9=&Y;I$hO6& +O*J~H!BUJYYtBeeEKC&2cXNy&%q{YP%V~KJJ3_@6<5zlAj63qD8Emxv^u;4Wux}8fx*t_wuY*)LOtN? +^t06U1(G)5P3wOL>)BS>OU!tJOr-8#w#JYC}$jw~M>@OVyR$WBakC>XK=P`Iw7+p+%ViWJ*uTEstXqb +23gP9adnLN;>|kD8;EHV0tFBiEsV2j)tAIFV2q13xkH^6F9$MbKqF+&QVCN;e7ip&ps4}p +o)oAF(lY)NS1*al2qsN5$>6f*q_IqRNz=d?yFdm{qiT`6BX(-rLq=#)Ld0{&JcBDGxZ3kKlG>9+4^ke +8|{t~p~yJZ=nbjWSaS5sG|5_aKKQsL9y+DT6|siVCjEdb#M`*IH1kc1j~$zz4NDyDHqlFlX>si2bvB#>=j$YO2g^XTcSAkRk8(=;i80G}}@rKfO0A +JeWqi_X)0dP$eX#th-Oi8D-aH>0i7r4*wlRmQrZ`yp}XA-_-Dt~-=5v*;+KF2r=5iJoU +0K!%Uq6mqYqsIdkJ~HsC8#>+exr5Wtl@gkx4E~%xI1V`w#0z2QMOJ~@P~uIm-!~MKrk7@!*9Fe#fcMbM5btl$vZ +{5#q)TET#8B*kd`verZ%;D)G-%fQT=jw3df8x@~pld+zb^n89^m8!0l%j>9v^S0{9VIl|cg1^-PGt`1`4%o#JxKT3pzFd6A?u +4+c+7u^&Dx`f!`n@sROOl +X{w*^zeXy;lE2r5sAUm4AZ;&6E4r+;3?@XW$h6d~R0z!2|&%nI&R2YcD@D%Kzf-30 +l+ZUY%+OIerUTL9XC-jE~E^lS9{zTCT6f=8(iqn2myr80cYjw2y?3$_qc*a8 +ng`!^ZnyPIlB%uff^}WI+uz>R58hg6?8Ohr5=&tEwXG32ax)%rm^uu!Z(+YNU-K0(XQgL{8o6ec_YQEh|3!zL(bpY5@-01mKX>^1!r1J(N^-J_loikMUKI(?>a66_+^emWj9E!TUKz>0ohbv$x9~c}1o3>TO*7drioD%-wrK=JufQ8g0kg% +%Px@jlzkBe=JvbOG?}s(_M@-yI#;gf$RU_xAT?QuwT`|VXxVC(z51n)F>8W6h)l+ETVtQliAIP9k&z@ +rvrMO2zmqIKN|3&!ErZW_rZgG2R<;04Tg-d&Xl}n53STGtNpntgBbH9#|+yp%0FPq8!<2ZF)J3$a#Cj +ZtB6G>i9CaCM7mPPZH+deCXNB-;{5%8Tr!V{C +GxIbd$juG0#g9+9n0+I5-YGsd4z?K>0I37}(=~{>215h%s@h&46v-HKbEf>$B-)R=?>%8F*Z@KhmiC> +%7a+)|-o>{XR$=j5IzcjZwUrM*L$wjnm{O%vu(iYaJ$uHndz-A(O;P+S9@zgya#HAO`4Jg`wF>5*Ktg +JjWvnN}`kK6`p&s=g}qEZt`MQQX2MRN%3B@n*5pV6=FSr*(mgg9kgCTGvBX +kU5cw$Hq$uK(MA+351wvYEgcy2iC11&CJA@}Wm;%qHi4(Xb#Nga>-K2cri&e#*f7Mrm!n2iSf4;rmBf +xCqp#><$^*^W^1biK@eDDw4cM@Tp(cG-6)I3M%Pu45u;O83oxW#KiWF%ob|2yp*ghNG$JNK) +9vQ{@-022e*I1B@6m;u*!x#W#2d*=O01gC3SZuM=J*C~f0p7;d#dR_V-sgA*k&>aYw?96Y0$v-RV6|v +NBw1w5)fATsyhVXxt^7qvh4%FV)0cI?m|CM=Byp!$j)77<_z%_#S^tT^whla|l#cu?3O$78dDOV{SNY +iB=Dm_NhbrFyE`IWMBiZXECFGvR0ATj-zxQfh6Uc^@b@a)<5FcG~^c+^kU!$#v^3K5q^`^wR^h0+qXI(b`w_y<1+Eq-@?mBe?3uWu1D_F +`XlW9jbea)zjK%41>?em+w;_C5(u0eM<5#tx7n4=*A=&KahorNNA!U+l)lo>s+;K33m`L^y#~?6(lvn$6?y9gFdv(8#eAV=(pYGe|+TFapZsv=kw<11~v2SZZp)lFs_M${U4 +YL%Yx?S-u*!BCEP*{kH=l`-+_d4K}u?C-2a$?9tD!Tpj^5_c|%Gmiu~e*=OMwy&ezcw@{EYZEu$~Q8E +)?_2>5wG%Su-?J|6-ue=cdv!70SYzFZNin_@(NS2V&p_EO{d?B5|U$cn^FRs@$uiFgi|4gSl_38O$~exno!#lXh$ErptEPi~xPO{s^KDDhs +%`>Evy2AJggs*I1%s7VKsaFiUk6H$67cpVp~bUE+Utq|BW&CpS?Z`7%w{GjiO(n>hk@XQ~YY_Y$Cs<9 +Z7R-!Z%M9Jl@AAc#W{9VF+O8vvvvnP?( +MSnlua@iS#duC915z~3n1UI_l!&I=C{7o7kyIB;(=aFFrIuHj8ZUK2&RL+;9Mb<)WCD8JfdKoQK`qEt +QbH@=eEX+w46xt`_5zd3Mm60;ODfjVE|n1lUckk0%5*39jH;h1vWgyQ5A!tz^kbf{Ugm2qzdUqSd#pW +V^J!In8hv|o&zcAh;feLas{e;$+C5+WubrTO+7QC@8_?${CTKVphrlT8wmnvgpk0~2D&kH2r5~i1|e2HR#gp!Pwh-W~eFk3SM2L#}B +^Nl;LtYRBghxy8{zYVT`WOcXRA4q}c4NSuqOI$T9^fZ>}3UjfrgJQt!%Sr +11ByOJ(7?myR`AHuBKcYLj7OP_u4|kJu{4YzG=Ynmq0jkU)YCx)*MZT*jZP#?o%HM$+?x#(HU|<#AGWd^WqdvPx+`S_O{AQ~*tP45*>u2=n{cVmB{H{C&d +Y=|&1MLIPqb&Vi1U)EYLn>#0m!0FWU#g3@%)H|%cP7Hg3_`~;XcL1sgBQUw>?tzt#n;fr`Z?7P42BuaSsUth!e;;|X*wF@d~BG0s&Pi^(ua +awG^W&f4Wb9#$!Wn>UmOwiAeaOUZqSHfta{IUw!OXd>p}~81|9K{^TSwLx|F8O|o$k{q;Ep_;owW(`uPX +YL3)em>vv(Z{K;=WxdIi2aZYd(kz(@u3=_6MhPc5XlhGX+xO@gU4nV`xgTOxC~%$IIMwlv^d+Qf6?ww +q2wAA4I4NX;3o<>M#K9hwankw`zm2&w%W>jmU*&a>y)lp;27h!-fC%{Vd>Q7G`gb*ZZ-NbUJ0D4a|dT +dnev`LjWe4LGe!X0(-Lz7?KpaKV)M=Q!naSVM2aL`ToDrVzyfSktQ5_UyoN57lc=$Cjo4xs=qKljbYch(Cq3A|%gRc>6>mX*EkAWUzgy@r( +v*oWN7&$y+|Z04ye_lC1WXv1-p1b+l}3RW9vaB_ukpmA1DV@UG878=ddr&#&38!kVgv(&V1@wVU-a$M +xAJI(2$%hoo}X6R!iH2G8G5RN->NO!A6Fk1`tWe;KAEAq#qi!cO}r^_V^<67m!n8b369>Ba>_-{h;ImX(Lz&?^4)3+ +X$~QP&j;yf?8~F@y<;ONG~iXQb=(*MT8+NcuZdv+|{8%CAj#| +Kj6ZKB4#{ehUEhG<%~PD&#^JL0S%fLX6$#SqYivR=O%1kT;TN)%kZA6My5 +T6&{<|^c*))ShX}O+pnYJZ15^jx2J36<&0dY5K@1&cc__BPQPkGuKQ^@Dk}W!Q)8PHswGP{h5PjA8z) +jj7R(J#*RDCsVyNhQ?&-UuV)085o;m(MIp4yWX2l*uJA@C84imF6CI+vz7X +Yivv-<7ewdE28^ba3BkyZ*DkOx~d`S9|aQ8~{;do-bdWX~D`(Vl;kcP}CBG{bndMg|>YW9* +KneZgo@lBsC0T+?|gP<>dZ-Z@}515IQZUTu1war(kWu93*{aHPz#NOELP9}9z~#!a^&vpZGtmX!ioI4 +E0iWhw{-xJ-&+m5!BC&mGGHi6dFCwZLeFVDvVzSg?aE+xs$3Q?>$%wED=ym +aC!M?FAzOU+ZDL9SZ@aSVmqb$s$RiG^=*UA?#Xhep#-R2LG{KStW!rtAz%3^a)a)j)WY7aeNT9EQI1E ++0wldzHJ;y=G|df9W)A&NO5*B8YxqwVlu}fel|?fK-95J~~P|?yo +C#-n%*vkvP;s5EaBO1+f;CLZ7Z2)D?IK-*H{udrPzFjy=0&ul8KTEU2XmMQAi6i3ydB<8_MKj(SDD7C +pG%duPFe52U`SXD<*Yn$$NBcigJ$fd69CwsvZKV +mf$p{>P`fV97Xsc{;z1QXJ=)aSLN9mgaHL5##DT}WYRXZ^47%1BIext*9bMxa3K{WP=oy0>;o+rT(fF +Xf4#PgkYrX37psN+1m{Jo$?qHcgUhrTBY0|C3`IvN@uW!D!3IY<_n`-`_urwUbXd(npND^)G;SF@vvE7UEeRq2)CaNTL*q`C?xO{G&Cod#zHXHRq64-7^xRx|#_ +mY?!+Zd+w!9kLZQ3VO(oCoMXL<&w-rA82F;Qa)CbtgG?^JQMAS+t@C46wN3BaHC_z^9I#XQHnJ=%`w~ +uq@6p2c|qKWU(|{&QbveD{g(2=46302P-~MSZ_z(D;Gh?YB`AhE?gotw?w+kpTbz5MM-rZ{b6O=Z}fy +#r;;;FjLP7c&gM-lFyM?Qm$r8c%;z@A=5yU7~9_-FhkKgB>~pPL++7Fey{fya +Dig7ABA}#$~)nre8HmsIQBBiFj*EQBtFsMZfXmA+))MAm2Q2Z=1B>-ADGG^sIX{Ly=LV0xB +VeCHP{Sg7htW0yYsA(8rfU509b@`K6G=N)zWL#^cwlA#0@p81mnS)jsWj)4@IComWW`%C0|$>-1#xheo*FbE%_eCeb7S9?05)eXJru46ZR_p% +rD;#Y{i@l~GB@;ua0;TFU+lNE$E<`B}D#UV5^sWUdAVAL}FE+@j9jLXlO+%?h|T41}z8B>B-`pg4OA2 +Z@*ck4lzaU=*!-QD{_3v>Jz;Dqu0q$r!H!&8b+RkJ4qCpz8b!QfmT2M95XD#LQ(rQfMT*lFuOS1nmrmJLpYpBP$m$Yd6>pRD*^*E9OzxAprw-+RyUf8kM1A4_^^N;F`;O?)`(C!}1G2!a=`z1CRE;8-+ +aGP(o&aKMn7{sE2G32NR7H)f8bFPnir#DsN-u^;9NtssN;q0!_`q*|dm!TG(Mu8!gJjS_ICM7YA9gRx6=S49-nuSTSmOE=>3P9WSS($=voH +}cUpXmiurmGp2*+REdxMYRfwd2%7)pk9v5iHE!6KLe=h +P?e7c@)==J4U=l!*z6%eWw}d +o!VR&X#Plnp#v3_$v|%OJg5TE`5Las)S>(Kct-~M+Hg;%>wOpFIJlE>K>yV8=55 +@a>28<^=84xR*(o$o@q2hX+j+%FTC1Tq{A8q2?=;%m_brhwZHJngTb*MXSKSS`fPc(4KId=djZV@&+p +Dt7rWAr)TjTJg(?Om;*uH{o+mE_28d;#rdE4I4P63{uM9;G=gfEZFD9@scd;@0Ul9CF~f{nhsiemnM& +DB5jAJ_=~X&%?O@=-NF;hZxBN(&@z5HKP6Tq-*dx~!w1O5Q7QcGH~p7B1SBYg&DAcWkG7IbAV%X#4Ss +j{bI)r9)QN7BAdBBy)YLM>|o3TWkC9L-=$t@do%y|Hf{#I=`_SQQ}&TDE2A@^w7||@WtCZ)L;@zEz&g +j#+v68AglF1BQd>n_35$Yp<7kv8{LZ?>v+dwk}$W}nfH9;nvO;2==N(&DDq7|OF}dR33#1B+MZg}MNb +0ml&_aR)Yh-Xtm(k^Xn81_Oa(g_3IJU__U=6!5Hw7xra-QI_VErwl=&UQezPImqzxfd4R2aSH*r0mcF +`3Fqf;D&{yKP@{r$u|zfeU!x8cB$+nAOQT7AsDcENj>1+z&v@m4zYiFA>=%w{S3x9(}zLlGD+^U^$TeII`pDn!yTGdk-XW$yeGr3Yd-z*rXbp1rAhX7?$?AUp!=7Kn(oVJ5c^ +)9(SUTjKx3Y6cM2gUK0VSffv=>yr|{_-^w~gJ*JN1Y(=~}xcP;LkZ_F?ki1yBh%f1~iW8B^~5Dcbq!) +LmC?urDxFZr}BGg1!DI +ywkUwNBHm%dWquN`ybvA<1jajD#k2!j6;bqylYQUmw9WJt}N>Hj0ziaPAfvOI=k3t6(~hYGbzRh7>%Z +Z_OY6kJIXlW+)GaOO^5T{Z;(?tdHc5*2Skau8FsM`@4+ru(l90Z??qA@S3O|c2^M?CT;ZlkMRyq#?Mu +18G?;=~k?W1~?1S;x#bz5#3$0m9-c(VXmbn~OJ?Puw{Rh#DHSE*X1tdlRD!N-_#RljmtkykfZ|u2`8H +AZvY6}QRro08W5u;Bd>P-CVV$AL{((Ar?{7rae$^@ml9PTP-22m9}wSOK}5qea~rYm#D9=1g!werF8_ +@=k1wAbiV_vR*K>f4hal`6N{r>fnU-YhhwW2X}!*6z)m0hw)eIR)B{DKHlS&hK4 +*X(R%qB!*6WuNG^IB;j=UYGYvrP%YtlK=>_L5YXcbX-X4h6@PbK`#nBCc4UBl;T>QhT9MYG +=v3WtwUwb@lq3{aJ;>s}}~(c0I!E;PijWG%d?-Jp0$WtUmqi2TIWp9I<(q$4v5qGJhV#X|+hVs|!%k= +=UG+dvix+9oo2B?R(Oe;aS#%5Z`K5$Ufd$H%pdrMW%V07g(45eu=-s>MnC;nvoCQ<^ho?MPbYqh4K4q +04MTU3yf)+tSC=JJj3@7vEwMss52*&46+$inUfdzwY3JKhxV*UzOZ_TpSSEmK<(u!&nR)Qv64c-;Ezw +l<1k1ioo59C{tT#@UFGQ&(Fehvg^pvE%;#&m3X;VI@(Tp=J$v}0co+bsl&fVKuLP9(;oG=vQ8OqNm2< +E!5(GTKj{I +nDxU5Ke#BXJxt&zM{%iu6E%Q{TbK;DqV6EpCqmMsi6G2IA2qiITf|)-)(i71el-y3(b4-!F%?g +r8aJF1=|?$`^WZOF-({Tp*CTSHfACOM{N@EnRit|V7tgr^q&0!e3r$%~@V%L@((8z7w=9 +xFsYO3{@;|Hi>YLhuAN#*+GGtq65CL-*nP6Q@Epj;W8dYH`=Tr~#oc4e|r6F^2Nw($i@G$y6RIX)WB9 +)lu@s!Eo$xo1VwP!BRYMDwaNs^5@OBhd?nz)Am9cDpx;?Q;`#!_#8b?j@tuFh58ce0Z21TBi}9I_=G5 +4@p6r91}nT)@`R(6_^G(5X~+@%BgXaOu*9V2OPP$twiwggt48g>q40VN~`imc{-r7>%}q+aT{jJ +8^geJDt0I5>xwutm$P1l_Mp64G&XN5jh-gm9i|oAduvzY)|K|E%JRSwA-i@C+}&z;;@5fyfr>l|Z6oT +s_)goP0|MNJX1M{e^G!A*UnrE+8)oJ(8b^<;dYyQ>Jos$PnB49&AASfJSo`*TKvpcC6y&@ +^I`<1wQ3c_)84%u6Pv5{pNWd!CgF1AiM!bYw;pEuq<2Hx1@XKz()Nq*E=*X0P`kQGUviKZ0-6ao|CAB +TE8Z2@uC%E;yn=gyS}Pkh{05bEVKKBg(bjvW$~%Zo*eXUpSTc*w$uwS5%Exqv;P;x-M-huX +!5n2_p+5yN@L9y=Km#J?a-w1n$~UwE_)cTGHb@b~Hm-CV0SW0&wB~jtdXN1c`Ka%qX$0<~GzG9>&{oD +VOf3t>)i>yeBWyzZIVw0<+`#R%YjIFiaJJ_tru)Y?KOC#Ejlt;LZdazW&`yqIHIl{%kX@$;kiSC_fel +E`%KAT?PHKUN2nLmwN5Fu=HjgZ +OHvXhE%NET!IP(La}*~3}tUxI(C4oTN9#oG>o(D67E<6AWH0;gzgHH=J{w74+QyKyL503i{K_an(IDW +EY@AtENGR=I5$%Y3I=z(-p0yMb=I;EpR(sFvF%aXKq7u}tBUsimCd$Ie%B50w(qYQqul$R*rGaj*Pm{ +TOI{CVZh}$XS+-^?O#5PI*K73K!Lk=@O`GM@HA~v+YG_Zvgbnd~&#{EP+MQflRLv7+n&?qu&~qkI|Z!z&6?$8kIf?AZ!GezvdFu@%--d%e=TC}wertj3#Yp#o)jc$zTT6hc@V9(X_8e(Bm +HK$FpHzo!2v!O-))$9i-uj3MUp;>)a$`^ffEMeP5sL*_>$k7gJS%QqsxjO+s3SBQ*lC_WBc#lAy;Va};@_;7WU9SJ3b1VGYN%J#(7*NOjd +khbxrNBlCR7gr$_U_jV}{;1*Gyu^M +QAJ&vvtvOx!1#{2GrtHk}!;A|`3Xn+t7c)}QYek|mnmTZ7+5z4g8{w(Wa0)pi-fF5 +UttlHPyatqO9KQH000080Q-4XQ;2if;kpn20FONY03rYY0B~t=FJE?LZe(wAFKBdaY&C3YVlQ)La%o{ +~X?kUHE^v9xJ8N^>Hn!jWD^Pk8QCnG#-KLw$mUnU!_iks>O{aElvlDx0h=e566v+~#Y;|Y<`#lE$Nq~ +eX*}F5l@=%t@gY(9D0MI;7S0o7LWl`oV2nb(gXc$$nzneZ@;SP=7qI!5)jC*ktVBK;OyXqa1-qLP6@=3oZa*!16rJN91u8hfA90Y@V|$rXf=ggdRK&( +?k?BDQX1klt-5_TQRsdBC|A&8BN|6Mb2j+2iy`FV0i~PX$W-0HavM4(SkOZ;GGv75dce<`imZ1OPMqF +K;wHrJ0d|@@JcH%WGv_-Uh-}=fQgWzPJ-Xdv|u%tR~ZYDLqVp*^TxlWJn>s>56RcWyYU9o^NTyrH6Di +M48&SxJZ8T4#e)G`$pEffZ`eljfh}mbc^aYvvis%TJidAb*9lywaD4;Uw{X3N>pQsq4A+~R;wJI-#Ws +U0yvgC6!8N~mJOTVCfd2&Wp8)<7z<=_3UwkLP#R+h6@&>N&;i7N>jgt_r2ri&8 +qRagL#^h`w>|ao{$*AfnQC& +F~pjc@Q1^R)) +=ldoW=zVsRhxT%MNM5hyr${HkB(P1n~h-M2~u%9W +~Lh|>p_`$HSsA;}=Tg5F-n#TiN0`02y-nG|2}6_05iZwT7tC0%7_Bl2=lz&k6#v4y8g_RIPfIK2m7h} +fJgi&dq=4)qBXP*^{)qUWRGzt%$S+k^(D$x$nur**CvTeu${EJaFSmxpv}RlPr2|dL5 +0JoEeG_HnBNC!ASyHYbzZH_5aXR~rg~b>A2u$Y11V2HxyKU(d1VPM`&ZKeDbGlWHDv8)54B-%WYr1V> ++MT$#G9AifS)!S&Ad`I2JdZT$ugU4{uq}Y4vpxK^L&?CsO$pa|OVqBXx7k3i4{d~vWydtjKrX+&Gqfx +kW@yg_+@{1($c_h(7p(BZY}nSfgH3R`iUEiOU7?NVtosyj;_PK;81cZ1rSF}24x6+oowX>rRcP^c`1n +GcdE^E0m+TRR`*J$D9S)7T12tem{HpSR!NU>JPNTqVDtf@SQcccphyiwK=X;%a;ElG%h@p-lg5{jX{1 +4{yBrBmF#gc34G5qIYkp}kT$zuAqG+`t1r$5#3d5~s>&x@Vk1R*&k`a48C{VQ0?|S~6 +j0vqIcJek;2lUr6Php7*olO+5EN;{t^tlXG<1y(VM}2`Rgx&k>RT7CCEx_2_J+VGDA5H%XH>}lfTgzA +B->2F7#>XSP~Wd(J{vc9#(%y3=|ioa{0@fFg1|fwBm}t%7HRUz?4psSVJ0F}5Q>TbrWN?*BP5R)yheV +zxVRv)GOihrA2O&RK^wX{mfyC3H~d!6jjpQ^(i(xb##Ev62j__6;(V4yn~U@8;;;Ox^W*H|{8-&MmQM +%68cE}ECb|(xSG)+6qG=oybRniPsH>3`l_jGQB9;I8Ngh3 +Y;fb;v!^mOcRy@BPK-}XG=O`1rI?lme>t)o}k5p6)UJzxnPB<7K{N|@UVo+T`Is<2~B +{ZKbo7$96EeWAqRCl1fsmx*cZP!%}oq_zn463;#merRLCN;Jbl3Y +HDgP<<{*fFcUOh8yE_P<4@;J~kVP2ZWT^q2Pek)bj>Nh{jRRCPexMs6@2ijaq*oOUWw$y|ofGm&FtXr +lC|i^onl>71=1%zTr+9Y}uEaxB#knCYq^y)&!v##o`Np5+erd;b^J7B`F?o7d_>^fgULuCN10bA}W^=(mt{;04piDDD8=ZMk{PnI8xa_XYNZ~A+*q=bc2Cn5qNXtA_W#yP$!;Q~Be +ZX}Jh($aBj5Qv)Xn`vd7Baa~L0+PJz(1O$;tZVx9FWfE5LBue$3Z8o*y-5rsM3y|Ed{L7HI^?cn#k}R +Zy<_6WJSLfwh0;(W8z;UQ@Gp;DirA3Fq4e*38-o1{qm0O7LOszipu3OiycJ|eGRQF98cYsa>FHdxWf2z?0zowOs!!g94LW0>FkoyKhKr{g*_HDr!wPvJQoZxL46kPM@u^Vq?tVB_Nh~5S-gC1A3eBC}PQQOfJWZF- +ELqetcmGu(z>$3z!M{89Q;=&`B5ME11(9Ize!0HO8Wd(xXx^IVzQ49F;<-o*F)D5=4_wGFVuoJVXjae +oO +<>MdZooAV;TfR7vqP8=BSD%fYJW=mx`0b`^&bcU@dKV#C$#vmfWo+xJ3{^F}l*kdMRb%3Q +Nv;(01KOpL`!q?R&IWq2HWjfZqgS9Y@3-K*vxT62cUMB;mg6Vd>$<%7H@6{#mkSw4$fGF1>A@^+a00M +%R7bQ+tGI${dxTppog^ydJ9g;(fsXCk-MwyE?A>I5Rs{dHo5|VbP10iN1Ci$=2SNp9U +zl&iCitowbs$`0Vc$-u$wt-9@Mg`MyVEsjK%>b49h`h&BDI|88y4cw(1lU)dE?oo>t2j62aJ@Tf;+t( +_MJ%+ibB=wq?4K693morVQ{Lahhc=B}=oa)LKgcowJ$f(-{C{2cnt`gi=^@-0{ohB|QKs~8Pyx!y<@rolxlrxHU@KWe* +eqb^#kF&U`{Lm@@74kUSrp8ac<<9tLUi}lBJ~7UtcTfMCM47hXxy7ygsM};2w6}(ZqSt<$S|g92xn$s(bXX7DDB7h>qhna&u@67niT>b8N0XZgcLn!7 +ua_pP^Oe$4xCE;(T8pG^D>tQL=NL&Xd%vUXvT(TM +aa{n(f=ZdYy5%rC<{m##ZBrmspg*g%ZA?g(H|pbTZvGC*^~d)gC*&=ZcZf@s2cpbmip09nIqV)(Qqy- +rtSbn~)qW1A%{w3iNA&0$aqpfS`n-#T@7rJnh2CtPqyBw$cV7Bhc1L?7FpGIpTyyav +%0d-Z9-oAxA*s%~=ZhEGYg+k={DP89A)ehsE=^P_sndD25&?m<0)nB!zH%rTmWOXM7CX4EqFk80Ksm!;Lf!rA!D2)tyJPc?2daPGF@ +OT=e0J%l7hgUlG&3d_R(K?s3;$jD>Mpye7R#!-dm*Ab=ft5Ip<+Of^dN34p7#-F!Q$cA~g^Ts>(H*|G +Rd(h}P6MP<6GaaG1)|$3QbR@)UJlqzre*DcwD#dQ-}jkYiMMM$b+Lms5WC#e+kt*t95##KCyh7HTK~H +V?$*U#>=?zB`6HNSSZo^L=;>LI9B;8(zY4bEbD`_%3Obstl`;DHWfEa2{LD&tYfZbeaCI +L7|Ibve+w1w`7viF+=zG-oE?r6Fj%5-huT}fH^oUP%hjw(-+o3r%pc!a-dPMRBNxwQedLfREE{x@ys# +WXt9^6TIuJ_zC{DGAO{hD}Z&h-|A(Sg4{Yq7sp6o2G}Dsplbri%oR==OGL5>w~cAVv~{8RLg=TNad*- +K+6mV61C}YTMM;!@ZaKyt=cY*}60VgFAL}QF1BGWqsTw(T8I9-y-RrG}p(GxS2lCehEm(uK&Qa?Aqu^Z(t+UmpAjWp!_9Nt7x!fD*#@?874{%of!bASX&YlPEEv?}mJB# +}v9S(<%%||u+)bzs1H}yV${!A`^`Q_s;US|JUKpw~cC=jw1+FZf>*MhDZU0i9#{$P_et9AvsEvASf>oBoUu+!wKTt~p1QY-O0 +0;p4c~(=>c!Jc4cm4Z*nhabZu-kY-wUIUvzS5WiMY}X>MtBUtcb8c{Pkd +3V<*S!0vrT@htwpUw9KKQ#&Zsb#$BH?^VQO0!ef`kSMm=oQY75Y+f;}#k5tXk9*wZlp>aTY)LlTnN%u +!&;k(O$B?f-o?IA!D5yTi5$EnT-2yjIO9KQH000080Q-4XQyCiPfu;cf0QCa^03!eZ0B~t=FJE?LZe( +wAFKBdaY&C3YVlQ8Ga%p8RUt(c%WiD`eg;Psw+%OQn>sJh$i%p${^io*ZLk|sv(vU-LAqd$X?TTt8Gb +1N!O8}?-V=6eOLc{6OhtDtwL@hwVg0+O;UM4(|MA +Re8_8gDH&A!2!{>gK@sOLd)b8-e=0_BqEEbFJ +H3w2nfLbv^MruzGBAR1ejKCH({KkO`Myo662({U-AHBngDV-bW25VecwT4w~ajj_6@6ZLlZH?AQAIL- +nYv8^^Dw3}X5^=d4HA?XTGw`y^*1HXI_>aHY__D*R&5Tn +QpJ8_yAOHXWaA|NaUv_0~WN&gWXmo9CHEd~OFJE+WX=N{P +c`k5yy;aL@+b|Hk>nj#MQK%ra*8n=CMSJYYw;&*BX%#O@lLAR4&fj;bhegRw`an^hEY9w5W;ujgXHOt +y+lStvlt8D>x&Z3nt?mQL@w@)N*PI^8n`#e& +|iUP)7Xgg@J(T@>Lc<2T)f-e!a7SPL@x2M0F7oK%O@%0kZNm!;sIO+#YZJ39dZ;*+>C=*l&2I-Jau0; +#zU8`)yXk2Z}vs-t;Qz|44>Xty!1XW{G&PPv)tAu6tjX +LmXhTDF9(zyI~V*5g`-&C=q6^`7I37PiS6C4$_6^Fij~Q0N>t3Ai-~ec6l#xmf6113o3qQ_^VIM}Rwa +QLrgC#~5m_i-j-)Xe_5(epgPSC*iDF%;nntYJ@>LDNhYCJzpJnoc!Jc4cm4Z*nhabZu-kY-wUIW@&76WpZ;bUtei%X>?y-E^v7R08mQ<1QY-O00;p4c~(<^ +umljo0RRA(0{{Rv0001RX>c!Jc4cm4Z*nhabZu-kY-wUIW@&76WpZ;bVQg?{VPa);X=7n*VRUqIX<~J +BWpgfYd3933irX*{z56Q$U*do)#?t7$nWrc9F$` +c#Kk*dF7_h?_A_E>%a1)bR)-`nYVv`NNhRNGDbYz@mis({N^jf1?sYgt7&9%WfYFTJ$-TfXriqw6!Ef ++=6Q4a>0qj{pa(#iHy0TN|d{veU$0cDJ13}dv&1cHM;#GDqmJ&MSjNK)PUQ<3S9>sEp@UI-984wOc03 +HXc*t?-WoWcf@H4u@~S)8PPG7?N=Pse#{=YYB8CgfNU4l`Y?M5ORxC%IkR}`Ofye9LzKDJW{Gf4?L65 +{DmUtLbom)R6Y$O&(~(q+nBg1*0bk-Tnxyz>^|&esJ_i+Tf>_AxEB*!hbh5V>*PKvb-!8WFIGOf@r*} +e`$r7H7jM)zax{qSw06x`V)Sl}sS>&VT1G?h9G=L7_3)}w^BzIN_6uiMsx#$YxmEd-G4d}wl{J)K)Cr +yc!_-i<2_9a*n`(Q9Qek?-HC)h5q%HJ4P*+`BcgR@o8&Nr=YFpnGct7O9J=~t?e+1q{?o_~Dq-F_sxH +m!sl{s%vvnAIpxZ*ERO9KQH000080Q-4XQzC%-#FYR503HDV03-ka0B~t=FJE?LZe(wAFKBdaY&C3YV +lQTCY;5+cFc +7`-6&gAj!;Ith>?XaWNhg=|(A+W}tiWQ_fJD%e)RW1-S1V);HYQG@lYrf~@9nNuq?IaI6s0xR6+{seM +X4%e{9dcPMu?i{DNDqY&_c;6tEGrl0#PmyDxPc4O9^APalPjnJkN`Bt~I+7xv>i9-K^P%bB~$j`~J23 +3o&Z8T%HwxoW&WhG~oH=&BgDZ*Ehd@ESC)ViV2B|Wxx}rQkJN=ESx;hQ5#jqbSgjr&Fd?UBxh +EAPr9;S1zK9dQkyK(2P57Ui)#*tCe$}Gt9v48L9`6OrvoucC0rc8vD(Y@nA-X*A3h5JE@or5)WHbdOK +@D&s%)2tK8r?fR0X$Mx*iYOH))z-8Md@I!aY76Z1XjhY*_GDA{39f^@O(7F#)~s$5cp+|@HkhZe3&Vr +2d9e0uU1Jp++C8$d|TQf*Np2ZLo6eBti@9lD~aX=L@uLO<_yB1AJr)!mb&D0AZQy-*%Qxepwc-V +2{)t{GgG=k2jMV#r@RHDR*?#AyBzkQMQnwvhZ)kcnJ3_GCMPaQlX38#g1bGf3zME+hmV5wPR4CDLYda +R83zwjFkLcQ+c8`s9Z;>O%y%FBaIb~lRjXYzs?`MHAAZ +fS$iKI@!HJ%*lTTG~&%8P7<3q$srViiY9Xi)SYN#5+=B@&o9`?o>^jHhX6ZX=~zbH`yiCb +UW9yr0(BqXM?mtrO5ruA>6Jw54k*(Jt)d9Abdf_<#vfTkvG&Tdw!GWZq_8d8WO`gM!>*@mF?*`|2TL=NLQeOD5W>yz#8kuh|4>T<1QY-O00;p4c~(=B(PW@(0{{R!4gdfo0001RX>c!Jc4cm +4Z*nhabZu-kY-wUIW@&76WpZ;bY-w(EE^vA6R^4jbFciM`Q=AFP90-1ZKrTj?LSgKt-EK;7Rr|QnYD= +CZx6MZ1eMhn#|HR1>D7#v2B1@mX^M7&2X3(0iV`J_<^ +yP+Yo{(B57=6PM7>j-=A;ZzhEC_-m=f=R5$Y~>VibeH$wg`ZJjIl4m$+8;+tDaDTu^M>+nbLq-D-!6D +*BJ;4nUJXgF1O9uoHjq^IcP2n(mveZe=-KOC4a6Q;{HAgT1oyt#f*X?q_A-u8qBjf7***6=b-UtB`8z +{x5-Ax$Jp{mv%J+6Hrh5km!zOYQlqge*c~poer)I#-lTdxuxx~$6uN+y4bpuLx>j6MyHHJ7tc4N7=xBQXKfvt+tZryss=zhCW=%{yFBmNdXL{5OX&%PeDFz{7Lbb+KQ_obC!x +I3SBWjy;k*#}o^hhQW6Q<+aDZ46Jj}1CO%2iO2?TWnYtJ +v>BBU~$83q^dn^_lGFY;}oLy!^Y7P4_|fWka1=u(y8(G+g3E%%H0SZZFX!tqb!rYra^+3NB?A@B}(N? +rK3w43JKYkD&r@TMNs_fqP>6;FRK8@on5QuJtUInIcNGg0nMu%3(^3(sej_<3Zyb)3@+j|RuYAwiX!kf?0lN7 +%{UO$*lS=D`u>gyaZ|*CFGi!A{W5Y$YNdd!eguDeXe%qIeT}we*yP@s!AS+)=JlGbKndN7DnxKCR;Xm +K{Sc3U$M-16BsrgV>}InW3sQ*M?Gh?WMKH#{m{f*` +4<66!La_a|=6*LR#OeCa<_Vo>&|M6wd0JS4?bAqoL7Qw8h3&xy3 +vG2aU2mG$;OsN(v|QT`=es`9;BD0tZZT!34{Y#$@gp}nJRN@r66_?_Ul|5_`b=jyMR|-8Gs%U`8=C&; +LL1!CRw)OnrDD+6qvJ2l}NSdFw+v!UFFopu^|8L?7gF$rGQZ6^uA$aX7j^nhlN^gfJ3s8T=dmu`1$sj!|nu^A^Le#T{3}v~s`)JsbfevvC7W#mscprgrZQaj$C(ntadbq=$ix?V +!($0n*u~K3maRk1;wj+F=s86 +m4^-1x@1|;L=k2k{02g2QE^ +~=XR}NKpgVtWa5)K^8hB2!L1;^3?}Jf3dlfkEhDUaHM?oCW5w*xEk(MD)V`w3%E{W>{&V?lq%P|H~2VWrZzNK}?4s@$3bv0K@A5 +klT<6dkQhGhn!)Jf%`NWmq$J^*mEEFLR$%sU@B6AAf}W@{q|+VYjip2(#4t957%!5zKqL`}WFHUm7homuU}QVy6=Wi8f}8`OlTbWS6~=Zi<1^Uy9FH< +CgqFrY8K`ZZ6|nkpEF42mt_qcX|7WKx5MXEXPUix<>by{Q_^YYK4woW@qEr^b$Q~N7E|EaER=P~t<~f +`%;Cu<^Z*abX^L0wwcBIST6@9Brir^csrPkx-j5j{>t*mJtRpzBMz{zlhVbbURAr^5-2q +bp(se*qs<=@e6#Q9%S~qRxcF6O~AaD7@JfQ(6`0tvHwgTvZ%vL%T0i${8TPsF?Ali|6&;#yrd|Z}E?| +-BF(p^ +L8&_^0*mDD}9(YsKey5B1~)ou&BiIE*~&@(+Dj!%8LIM**PNhN=Z(<&Y5wvJF~cm)o4P1mG}m-Y`T*J +ch{v0lgwCi+_1Q2}#VUjFL!RH>EvEs`!y^zuFSPquq>!bZaNe=z+=U~19vB!#1OX?qjR*%K1>Ijtcag +_3R_wQThGk63qpPBktq8e1@6NWpXZXd96MDcN@2nzw58q(s4;zBITrM@11Pf&vBGC@0{A +ta+CHXg$8#)648Dcz>=a*_rKo^_6xh1CFETBQh9eU00x8k1hD96H2R+Bl7~$5C|JoX2{t;3{~U{D@b& +!b`O(oO=fTGfmppjB(K=UWr>AS7H^p)uX31%d+&s+kz20Q$sZgrm>h#-h|McSHB3NbeNTl#LVOnHqz? +NB|1EEzARlNiBS@7|@n6q$0_LO9wMI5u!r9pv4kAw=KYL+BCjevNj0tSFQW+4ZVo(Jy$Rq-I^Iw(|dn +`XN}q=DY>fB`863A%-8h!c`9JRVM+=+DhDU7{y +}2;utt^9$bxn5HSpHbaMn?Yi1XTT0{BdD1hIn7L~CI6ibB`pSeVqM~xsp3lvD*C^)Vw{2sXrSQ-U+D* +{b(jVwH9zDGyXqoaCa=J+t_5A7uQz6JbJqCzELOSa?Dg2zVG6mS4I`q*IrNz2l!F%D&Xa}tc%c +zSZEc)64wj8JCjKJg$$k>pBwkRP_EV8*4=FzOE(@)*^e|4y +(k=5a7nBzz#)AZ+!#xCr!Z#$eJU)!ri?Ehik^8Wc1QhQuQp7Y{OYs1#SSanm^0mS}`G#lzYY}JjIDe(nXpVo{12tN9At$e$72{bG!6tf6SIX{f{|x<7>vl5R +XVD%wshnJ}7(`Ug1xQ7+kT>jHk?FV^V&neWuyh2}Ji^0myRw0DqD_zqRKNlS6->q$dI>oGd2@C1m;Cu}DE_n<7ZXzxMsa8`qY +`QvIW5+5n(5j`jyUpY8C4~l1gwTA?cT`N42x@DgJw~$%Tc|*eVrZK?7y467&V3>`=rOarWY^;@Jp%;n +vDmI3(=_-^W*R+ALk^STIGb-B-D71}~L+;3Fx#^&7SgJGHH+JyA_&#uma!BO6NINf%f%&mMNq=`r?+2 +_#2W!AO^>Bu4R2R*UGi=H_SpG>wYvo7s%M3W22S}E|dWa0BSxOI2ZPy1DQJT_JKx3X3N}ShOJfQDA#G +kT38`H!5AX%ygGm#oOj79!#$RagJLJqCI)F=^O(Tdd~DB4!l2s{Q-)Ztz*^_s1Ql*+-HVd(==l{`!y9 +TzmE@jgGy!_6(1hvzL8DQEJpl0||KWSBPUL4<_K+X4A*P`GBe`f(UZC?(SM%s)P`*B&YIBV_S@?GbLo +@s1u@$l?caQVvDBHNw#HLsPCZ+E6wK+ry%xc0z}T88c`e8w8;}`l$y)bEZpE$0a>aPr0H%!#}L-gSgZ-4+ian;FiY~7TlU#1TV-LPoPs +;yz?Lk$#SGKRbefWwrJ>XMavgSTeFKIBG3QbX?)H&M+pTq=bYpE%1cu;1F|cdnFlU)*h^R3B5>`4?6^ +3A^4-}zPQAHaU0#e4)wZkA31PYd|RxX$Xl7bX&czDa5S?a86*RYr=XA-~x5_D1A2cTO!i#rO~fPu}SO +DlLKT#=7NH^Q=tCD{o~tOM=Xcnh|Q9cCanXKSv+XQxvvvwdU>=AA6EhVvCGT)m5U%p6s=m#iC*!jZca +O1558Jc;%xK^;#+z0VzF?rLi%sHkD?+;-xr8YY=L{xwRs+}n$obxEPMtIg$turqlZPATXH(oxbvK|#b +FDce9B4oq@Ka}WkL%b|GF&0{L42wn3l9KM=G+Qi-%stc68U|9jd&QtT%U0|(&e5D7h4K;|)CQl8^(sd +^;ZQz+h8p1-b^F$+Hw_Slku+mHt_&Wkb2yepf2oTy#?M!C^w!+bJi10Rl?&5M?0 +=k@~v18|?E5wrLoaq@c)pdzGTh;Ls7yPLZ(lwcqZygXCl>yz6Ad3`?@LsZr-C#w9nXAC%in2=^2n~Tu +Od-`hp)#bx!Q?wr)Ll-gfkgs&lQV6q$n(z1eFX>KEYgTOGY2%R-S|Uuh&GuUY;;5S8kpq+F&1SWi?rk +~x0A;3r5j4`d}KX`p +yWjy}WzrT_LK%wMSp5sHSw=ep2oF@oI5;Tc3vIT>deFe&?BJXlO2?kM{OW}E_NdgD4K;4~>bzXMK6T4~LPY(Hh^Nw$`&= +`r8BUad4oM0&1x&Lq<=vz3-yhZ{#^X_L?ac5DI+__jKHUuANp*8AfoFnVzj!BHRGh6j64pEGTt&plrY)%m(bJDI4+@nVW+_i!q4(u{ +JJ2ncI4d(qUj^k%8-&4l2z+^__{@=e%5Ea;4Y{_ElTw2z5tm#3O6B0|-{Y}#CvI>6eDzNP_AeKPx$kR +gwWTcj-ZhDJ`g?H)rW`+paO0i|;|FMNkNpECOo%v6LvRojHy%@52z0V^+*z8D?#z@7h*) +{hNpB$7U2g_ur;9ZxKzJhRmKXv;XAy(+T}szsS>?2LR0t@%p_`mqrN3paUR1>ie&G@e_-6oNm_Jxb29 +|1%6Y&PDTfy9oiJ`pyPS02xZro|ojz(x_Prfdo(t1a43&&rXqYMYFM2j&v%hg9i1K=6Z>@uT3<2%dI; +yZ&eiv~_FV67BE-&XM5cWyL^A2cmROibW#O5k+qw0buXV%|BdzM~IUx2XXObuo0f!@`oG%m%h*zbILG +Kr0W)X9o9)>70jLaKXIbAVMcRpb*G>(wXSJGbwvV{y%3~@+ytql}W4FyM~lz0TQ?nYEei*eGgR!Tf{m +xv8G7v5Yn5^L)!`T`t$IT#L!k7xy42A9ds7Rzf|aY^Nd_o+x4R1tT`T_7Eu2HcMm6Mvz$&W!$K-z*Nx +6?2&VSVy*xP1JP@6d5v_8<>(*5^{6aP@;tayg#pW=xSgilrIbl=7dnd@c;MY`aK%W!q7P?2n4zr*Cm= +OMuo`Hc=Ol_1&?dJ>}*s)wJB}tl6VAn&NgDstNEum72_P7NxFJtyuBn9+Xwq)sQ6_C|%bCP0K9+)7D0 +2VVm*5-g6RNT-c3(R&6S<*&`6}&v9P``9a%Bij1A|b04x&~0LJyfqe=38c$ui}j9<^{iivYzYZ0vlwO +2|56?1Ykqz)Zd0xDSrjpblzf)!ZWKEFKZ~qHeE!_j8?eMTD|A2XlKB@%-9NLw*y*o{9wC*nNwNzOBD! +JASuF)38t!dNQ2mI=VexXq@cY%44nrOn}HxzKwrEYq}^_8~2GVV0%zBVM{VJ15*88B{CwVb +XayQPRk4cnqJfT`-Xxy?k#d(&HO^HguU4fw}d>CtPDiO^0QmAmEp9iC7j6|R72xvI}T6|52T7*wx)$? +TWH(yBOWpDn#LirKu$Y}bse`sB4$m5!QX?!ucUBWR|5TNU9(iH5?pY}IBhId6LSL-|F!CE)_UH4R&FU +bUhvT8@I>$=d0|o&BO2LJB6Wd1l*v3ztbNRn@k8tqn1*FxQfd$-E``h0I&6lak-$5B+}iVwkP#a1*eg>MAf@J!g3iHp5u-7 +pKn=_QKoNXj=y|6oxb!F76kYGN(Xy*NSlv>S4x3{Gk3V)nxMHh1Z2Ql^JMl}NK0hlpmcH(kIM)@3d9oerDu{@>bNS4)}>(Nz8J5lW7qpbqueC|5g^Wl7Kmba;K!Ki>bShInLanbVB +z;LD~yenbcJ@uO*T-fy(4f~0(Hm<~2<%dvr=Tz{X<%6~g-Xc1YU!VO4YkY+l-!_jy}SqhXmYyh$#kRM +>$fR=aWsZVIgQbR+BGN{BQo+s<`lrjXJ=39NX8xIV2rwgnJYyrQH_mfdWs@)s@Q~|;@DNV~sfIeeH7GTrwOHx@iv9ylhTi(pMwFmx{Fo_sDAb4&D&otzF%Ctx%lbg?;kEtFdnU1D;)*a#CsjFwk7$ +!`ma8Gc>m*f|M+lmIS(#-F>KWw8US-WVPrZn_c~%1cupP73+Q~#L$MO@y4*J{gQr`(d;j*`#rqFGUR+ +w_*$Dr2@vmRrzW*M{hhA#CUv0O~ZMu*g)M>+5fzJt$;yuFX93xW-@+W9_V}#MA;%`5cn^YE>^3_G359@W)s`8?1zdb-lqZn +X$>|qu{c+Q3p8;$A7wMke?Q3Y*iBpEmr;?;5) +e$*C}+Z!s_Z3Lvg1-J}Tp57h;-=IGwjP}Xql+}EpORe +f@wun1xKG!%j&D}$HG)};?P3{F4;6@+v>SgXrs@=lqTNv@9hGluV~&j5EsZ`5;HfKfGw0~W*WGmLS{T +W7A$$0Ph6)3|!%g5fhKE|=ad*1T&x6rxa}%r{bz9CM|F)|2JFYslOe4xVT3sWJyQttL||1i{~<;JpcmP=}W0OTxa@tbe7aS(NdUkz=c$*-&^wOlid9F7a1Qv +o{WM+sC{_nKZ~lBe5*)PUub0&N-OUHvthL8;u@{C1EY1wVqxOvgz;A`p)h)Gp40&| +CV|xYp5zIRhGAEk8+++oK`F&Pk9tDw}tlXm^y1XaZIAYdbyZbWu7tC1jw!9VDeDVOJPSLIPvh1=C)09 +coqAW11FjM&?$s~V*L4Jx%FzxIFW_3uCODs-OJ=&;TvQ@=$GP?SBefRX~?E3DTr`LD?v4RKo^!i%e9E +06`z|vd&tD7zr>I(nfOs8{{EpT4Mu&QBLmq%L@RZekBZd>9VSLc0RH9r|$gBxB&=u1W}HCpan!8M~Y5 +pR8Q^SpkTbUJa{<9B$6L+~@I(*<5#b*Odp6J#+O1B;su4p>8M0fQm2WQ+z +kJ}Nrh;lDuxX}bS;Or>OjY?0jB|PXbyhsh*;b4;C_8&?({)~r9=H?^T$=D`nJSCadUDx;`8U~H*p)|_ +vLae%$l_tXKxYc|)264VtPp&8M=jzu-pz~M1PH&E1UQZvc_CYWXz66^#6HXk~3Fx|9>1T&jgmv(USHm +l#ImisoBuJYa|JpEO{^Uyrb?8@)^B_StRB+eI%i+2X>zead<_nVpf>E{J#lLY<-%zOujoO{IPS{c{6S +UTvj`^IBlMeg7IFrE8v`QX`?A{`B^!)U(dNB$f52O%n%v)FL1)ODYb1;c%QWr8*y}EHM!6a2xI&czfn +@G1NU@kOIAXpuk&^gQT`i03jc&yGxkJShsN_{ZKQZ<>0Spf!wGr&QV`u|+uDpfa4_m#44r_;leJ$n9F +RgCQFF2)dZJRwlZ(9}*R`gsA>=z+PZXJ)>JFn)xR^E%G(cCLPs*l)KRV4uytz6p+l(dmoP(NV8TDP%~ +>>m~rPu&ijkM;k5f9i1vg-RP1<@1sU1FzT?@;>@B=Z==0qzps_C-S9qzKjW#f@o1c@KJ2`H%?e1>xwd +(U{!P}NSB^%bUnf^*C(j-|o?okG&-f2F_t(4G_3nOrb*-*a2`6ZIz~R88 +9Gew(}6xK;!RVoQeuc8(t`J^VE=V=?8X({Z7jD@98<07hzJ5aJ_A>LT#X1`pN;zq0Cle_kAeRobvgS-?+ZF;l>>WPD8amj=dYoE8+<2R`IQ_K!#)XIy^e}>x1<6MQ-xw3Pha +ERNt^#K3?z61aODgXcgaA|NaUv_0~WN&gWXmo9CHEd~OFJ@_MbY*gLFL!8ZbY*jJVPj=3a +Cw!KO>f&U42JLe6@-eSHsTrEbwCe8;O$y0Jq*YKL$T>qoh%uW+>ibGQMTMhPCH;a7?DUmd`XGvtm&by +yU~vp>l$P~80$eCol&F5dfpe%$_MGB(FKfJHm1c|Nsm@2$5@Q9$XFL}F*tPact`b448W%b2Mdqh34t=)gMB@eUg~t!D0VSQ!(pKdpJs?}`=;p#L)OF@iby0R$L(D`OKE51w#)3D$Q3vTK<(BVJ! +t&2UpiFUl9(MeYILI#g&;{*CxW5&@IQ&d}mK7S5y=YtH?70>_VS}uopnS7gD_u#In``*Q5;Lk(U6pPp +RU!Wp^bg#Jw{hC|SD%1-tvh{NbD~&VxH^6~&vpT~=sXymI@0JF!h18NliFL;j`ZlJo`h$JmaNnFF?`7 +D44nc=AMpPw=cJNz-D`LgN2Hz=W{Evd^FB*du`x7$;Q^mT)8mgfZIS+yG@QBeJr1y}RXbYEXCaCvP}&1wQM5WeRrrs=^IY&`WM>Y*T5XrTw8hf+dzchq3aq)Arv?VD`WTJW@)`Tk~SIOi*m2 +&z>sq7Ps&!ihR)$R5x~SbN$7S%jErA^NU~olhl!vJ|)I8Cx9H-Wi-QCPWgp_*5Hec9RLXQ{0ke44@b} +?Swp_ZOMb)J4ylDxHr#6*Y`N$0*ah|o$;*PpbcByo43@!3RMM}Bu@~x!2Y)sXGOczm>dIYUL=%C4tptCw(P8yw7tW03VLtLam9T}5S8I$a0@W +oSue=`>SX_DuJ|MW5*z#~}tQ4Eg_x(Kg5xbU8Q5jhnCLldx+XMdrbOZTsMap6Pu3s6e~1QY-O00;p4c +~(c!Jc4cm4Z*nhabZu-kY-wUIbaG{7VPs)&bY*gLFK1TScv(qhfIewi)P^O^IfRTKx#m)YPz#>Qj3os(OwmGnUXC{QOy~ojrc$xa$hS2uSqQhrDE%sEv%U +Sk4Ix@DL)b0Im%aqVF^kDNhQi04ncft~*y)G{IH|)28IqYJ0$|ZVxW&*hAq +HzKTJypy?oqR{|MB=lM=Z)Oz`DAgRil&x-O+p}>il(f06}*`2BAC*uVj}Zf+Zi>K#K9TH3N&wVL(LOs +Mb=h@km*m!g^#Ep1b;1st?kDf{DP6cCgbbDZwystOZOOhR95DM`-_-Sp$S#k9@3Z~iH8teD73D~pNal +E(F7*okJZAyseaZ?=9;}~cc3=_QS51paJ}vCeAnuJFAfA?7Wm(Nih*m}90rgz=d%&q#Fok2RdsOEkHz?$LEiYy2#=|ws?bHkhB=z{I>ZgLIcbUCa`&)`X7J-c4UW@Her~iz^XC1t<{wZ?0|XQR000O8`*~JVfN80hL;?T+@CEsJgq+2HyI1op6`yU;^JAuSY^V6^r +)Q6o!6lCxP#|9eNaqqXxv_psGrwVsdn-poi!ZA}A3QFp$xSQH?e)>seX*%{S&EQaq4DtVAj8l6F>Woa +rbl=NtYa*WjhY@n11yByj)n#J +9ZuuCfzDyrf+2c?foKj$ZI^xmGjcA1nkZlZVJEfyKKgPRq4l3l*)=3NNqgUP0JE;o8AEaB@Q^Sjvc4ER +IS`q%J(I>!)nwj60VzXr)DsuEuCO*YH9xo%#Bbe2DByhN@6aWAK2mt$eR#Ro$YfMuG +000OM001oj003}la4%nWWo~3|axZ9fZEQ7cX<{#Qa%E*=b!lv5WpZ;bUtei%X>?y-E^v9BR@-jdMi71 +HR}2&ckqntYZ!ZEIz;%qaK;r_5o0p=HR^&+BX1S~GE+xA~|GhJ_q +N}wX@)MP`k1*7+~i-Y#VJ#ZTC>>-3~q42p_dA1II;=9OYdNKiUH#vh1rIuIbmvxrI?7v`ml6Yu5z96?&Ku!V2EB# +FqStpD*FY)$&UQ0JkYHhx-ZxLfwQ2J%LyuXdBV*R3U^no2?3%52_0Johxy(VX(O}v2xA6qNd6bVCh+a +N*%cueH(+`TNN9R!k$jio>FXxzA2*f!Ym-taif!vzvV6c&1M1j_z)Xv5amY3t<@ta6C7-@uL4VkQvsB +9bd(oTkXqsR8}){s(iFT5q|a2M3Xgv_+9OZ)YwrJWq;C5MX$R(oA`z9u_nsbppB`>Duzh4|M}i`t1r@ +5OyOucU7?_mkXHujzcm%`4(cHxV-*#+DP%(PFZ6dNT2)zMFpxT@DKxg2QyT*q#_e{1++6{4vfP~ptNPfy6fUdXHZlGMr%hK)gD|jr1Ro!Q~|T6w$>z0>M$i +)hU;pQlCo?zn`N2kb-ms-yu0f)Z;JC&y6LI^qEXgYZ5>7xHbks*&|TFyVukPOXDnHc)7g!n5x77_ +e(7-wRRrsWVy-vnYRxrP5Jl}_gZmq+?XbjytcCF@m(#&Nsy2C`D3%-of`$~^2T^k7x^L_qwPtI^u>C< +f5BKX-VLy8@LuRRCi+=l{n0>4B*RNlvYq5&8`OfI==w+kxi? +Iou6T8`6OO#R!2IOx|7SwSj$bOyf+YHGqy#>jR&}PB5>Y41581w;yib +K_PxkuQ;?a{8>gFs91~AzKXBZnoM+?q&osh*BS@pa$DDLC1yTmFmUyJssjE)GaZF&`sZdL@>p}&C}=w +D-#XOasSAD1|74MN`dJ!ZI?_+ZG`>{7a9EQ3`Ee!Rpn=_TAHHy8VN#J-n%?$4@HKerz4LQg+Fz~5!tJ +eQpB}y^&0dBXZ#BF;cRX!I%WyhJCOhvV{{>J>0|XQR000O8`*~JVK0avt@(ut1wGg+JG@{{^K3gJ)t=ty3??t^(O`+un566fP=GN@D;><29ic;0u@Y-m(#SLYy>=>GQ;mGaan88m=}Pke^K*|&|6*#eRlSMU9AP}6>P(sx1!OEH-bspCl+kC7Bz!?q5|!-R(g`(f9+RtS%fm7=QP3$GXc0pA+n*gZqwtp>nGv27!ORl}C5*>Gb30l*{vMwQ@i;rj>(frSJJ0H +o*%q9>p@dqE7aIeV#UkW?kL@m#;)TX_EK8G{eLov|?>`b7YnW}Jv_GG))`Itl>~ga}s8?HLFIo ++v7qwxL4wFApWbS6_c}%XJ=<+#kB^#SctJzvB}I*H5$DZprxA5gVzjfbptBN3>b}Rgn$LKa3Pjp{j!$ +k_I3=cEUlF}Fb>qyMsOnl4q-a$Ud?#3qUAF(M#Jql1~jQsa6ENjuB7p|1#mja5T7gmz1B7piPG{`Dlr;NUo$jB&i@RV(r+_;y=KkQEv~_&$27$=pk|u~r)6v9 +Jbcz5|a79%HSl1^O@I_xxLjdCMiZJLCeqPb0xp0f!@JD63!%c#@A15;SJqS*~TZaKvW#Du8XN*`|fnv +M#F@Smjj}fby=50V3D}{{$$o=8dfOOaZ*c3J7BXc)W-}LjIC)&)M}&mcWhF=%9HmR12(Ed<8OIa|2$w +W_J(?1T3pN2_zV9?FND#=)E?LQ~N;SdvU!n=#ximRQ#-DJ-!}2)bpR<$4{dXduZ)+YENWxf>Xoe3bue +8PuP?kV1ah5$KVCJy>b~-Jd2<<0^)kjMt^>&*+cy;(cKx)G~U_66yVy!hH)8+|zkJNCf1sJ5c+-$8{i^a31QB6;+-F^iY?ucD*!> +8`?_X5sZ%+aE=^y6BjpB#j9$|3PnwpCi9zXvCHN5F7RuhND$WE8|qh?AjXQv?u%8M=h8EV?2Q43c66L +quf+L(4)OGm1Z`cYD>c9i(#oHXC1%t)A@IBjE?VA;r)GKqTa!OGw;45jajhCQEmqRZ~#O<;yQnuc_zH +!EvY90n516g1tt1HTwrzKhcTocK|eb%^qh5>x)&JmN`6jWlvUebo9uw_SrIT#74ar#|`;4t+D7RZ|SZr(f(|(es#z7svuc!A}y8lyxxr$V4F(&s~TG7`j$ApkaFEV%tL1UqOLn$db +^ixOOp`#20E@`G6|u%_dQPPF?xN$!s6&a{k3as%&Y51T7S4e7ZiHDYbXri|0PGD?z7 +jPSiht=gVkETZ1_UP=Kpb{@sfFQzRwrpxnnQGFT2|mjDML=85~(2tCJrnB_i6`{+wQX`C=1Aexc$9LO +Ldri+T;}r#XjA_T!>cJs>@58&?$VbqOQ4qs^EET4s&Ikm+lT4HU(!W<$FNk+7ub!%wvhK%Wf1n4eUpbnxUGltc=LH@Og)}JF_e3+OaVLn`wRQu*8xv-5Y` +?pD0{nVK^u@@TG>C!rj8Ww&+@>+3dKX~pPd5EJ;ltV)5$Avq~Euo@1hx0$KP{p3*kGv0Gm-(q#W}u3; +4HPf%udFo?E{_zk}Cobze!|DPy3%Q`-3p@FBe0?SQ#6I|}P>S1poCA +i8zLO5ynuSH++?_zjTj=%MWx|sKJA$-8JtrEfnXIk3LWz>qvch2w>C|aSjkWY?Cl)qaI`&25G4RpVu +`dqt1dYs}IC1Sb8v=O?s*M^ar9ThJFybreta@_~Mxl3)mrcUt;9A>Xnk%pU$$08q?THU?T2v^_F^|FEF!t1qy1+Av+9Ig_-M(1e;H}h>E+R_N0qQO*GJ)K#0qoiK +tbH3tW8jyH*$f_}HPEE6-;X_n_qcD)l52o#cM3QCMrtWJq2rZAqlJy}k?Kzdy!X`-rkb)NFO8SGeOu(P_2|7G!_TF3nFp22U)68;I{4t@H>a^U#KYbn@;U;QGz$V&U^zDfWB6-1OUF} +ik7;QKRzOuBh +=>leYK(4nl+TFgsyGQh51cX$n&&hpWG`q>&FFMC>q0ftjtu4lIsuxO757?uMmH ++I6578WvGeV&<>hk847vRrP>bE)j_H@k`lgyi+N8Yi~`Y`3kdX_u3JP&Ehr3R&D~%_9ne`6sg)0rAIn +qLw`;g8g6qJ@%ASEU>=4mQ=tX9S$|XWS}A{_X4G3zhZ-I`Gb-BsY|z*)K3x6z{@0*w78ONNlet@(kob +e`3ml-*%_Y#|YwII>@4s#DDp_7G`UU<;ut@JA%h`mB{F6}-&s=vRDC@drg}aybpr;+gQ@D1323$Y2J( +ZB_scUJc_ne>^uGcNK9a)OhqMNH291?oPvv^sN-U=6Cb7$-XL_y$Y5)d70NB*dSE=%uAuw!p;U4pyFd +cVUoH*Av}J?A=TV9<&Jg84XzdDDEqV{%B@!@ih1-umFCc-|utQEG!xbfZQU5PBIm77kqI+p&tI2@wF& +c|d+%B07dQkj9rbk6*TRNu`b-wR6W19Fe~-?8S{2CS(u`*G$Jr@0duSb2ozb3ELz=V7(A@2pJO$aD +D6Rmi4YvMPY#<2I6jzfTnCZ=4?H +tnk6!FlE!j{gRAIe~twY88ZCr=hC0Yr@#@G2X=l4U0kkVDh(dKI=X@nfS^Jpbc`2U<(`U +wEg4)!r*N;4_YUhB6*m$w$y0;c;Ed%P80#O~-UMnc>sP&`bRnly@*Tl}{x4i5wbkz@znx-t+^Bzt~XOW}&Qv(>X9H{9blY%4}r%(D3JiM3=KsBO-eK?p4I@z +K0*4nSL7hJ~8DZv3&6*e+q;fePS20uTrLRT(9|H`GD8eO-oA#wK@;={PZO#SwoBVOr~cpndnns7^O{h{&WNSYBi&m_g-S-rU8E8lezA85cm4M#5VJ;9e-^)OI+ +o3(8&HT360G~ns=z)t|HqTDv-2IHokpV +e$sIrDYm8QR8BH9h{guwj-$SJ`6Xh`hMU8Ur53t$U}EqVv&Crq5aMdKyoBjp6xLot|Q^}7f8nSasq2I +Y=>(dS=<_{q)J +yQp#6#oNIO9KQH000080Q-4XQ&0q_vHu4E0No-004M+e0B~t=FJE?LZe(wAFKBdaY&C3YVlQ-ZWo2S@ +X>4R=a&s?aZ*4AcdCeMKZ`(HT-M@lW5JX-Tq07sFU@4F$=~iq>iq;)Aq=vyrbi!4ZJdsM`VLyIHij+u +-l%2e60vgxkeev#iU-Gi9R%o$UHoU3HVu9#tUDX`nTUHefCyVxbf^zym8Li3$H6_ieoGc2;NsS9OnM` +=SIi4VRmV#PV6$QyS0J9X|W}4(>!|`oFF7bLz%ex7A%E+3d4|4OOuB-YOp*{3*!|$sS9i=~b2?mHsu% +}g#=J2UNRH9=P(?L;j;68x%Mh+68_GGNDX$(i2h7BobOSA?x`Mmms1!)ej&ud2K$$ +a|des@|v%&{j^C=#KF7YW1$6MNaA%{tJ32$cmI4i*rs}b;3G2L4)0i+{_Bh7&_&={*^Kw+Zs^>#3R@( +7N88iSj3DJ%LI{@m;odnv6reXV5|N&aS4Id6(I+|Cg4LQ5&*GDE5#rqy#;S#={K@r@pS4EWrPFTsuNI +XHbD=#Xo$mvE)n>lG(<+?7LGTd9S2SdgefCne%`5%>IY9t*t;6srES5fi^Z_HuB$bv`6e##>Ndv`_ +ZZz{CJt!%U4o(x@yn+V(G=k_A1KOtERC7SsH#CrC2{-~;xQ@nvwKjTHo2`iDZS9g8rSXN1X%F!sYy`c +X+1mlx@4e(8F}K0ZH_rk)bcyBtZH&1#kD2jfUry46NH5oV?lO0@Lz+=(D5wdCTJ|U+cNg2Gb#8TI;bW +=)~g=Hwzo?HS9^IiG*DP%(*zy1Uuut)EtzVvsvgJ|R{`ysEEPS$6rI?1+oB+oTe+jm!eLyQ+f7NQR#^ +uGCyyWHx+DD~fDu~`xR(-H909`qQ0h$Vx<0w2HEWsfeo9Gbn4&vby}si?k_@s=W)SUWZfgZnRaY2{CB +m+HGi7!>l?`Ym$jIgc@96i2ssxWUYb&?~Z7%kt^dYjMzjJOfa0KAZn>pZuLviUwVR-TU)!>3IDal190 +0qaI!fO;ANAOpE|9(ESrhfb(VvL6(8N{*t%CySW=T^LiD680>-0qn8kJn*n5PT +P%XbsY}a($HY>o$GDC#j-z+LD;d|uw&|wm{78D_)f>NjCBgDso%;2^PYt4io`v}ryORTRkC=2+o;@x+ +KH_<&`712EDNj$T@>}Z)Jthz@E?wF}N(A2ErW;L;^IAdWd`|tIpppZIp%0xGL(v%G&`QZbusrYWGEms +x6nIP@cn-n+KJ!~p^-s?-dm5ryWp3vn*EcBY7{_oUhTh+#=aN?&;%i5RTfhgasb$;j7H&VKVS9|s4){ +U$MBRc@Bo7;kBAMzeZuQ-5g!ys~+fO;h|l7p15T2WBKs8$>Vc?>{MsE*EN;*9Mr*1{#7Jf7604QJ&!^ +DZie(?MM)2NEZGwBl(gT12~I)TtXVY$K`5+53-f{5zNTRj4YRSQk;0?TQIm;---9zvZfyvSjK+Q#!q9 +W%6|M{+l{VMI{Dxds~v*;-D<~oKRY=Ts_IEd$*X{OLf-LX-js8$FjzEqnCL*d2R3aiqxQm->DVqi0;_ +uK<43}352JD{;gyRwVvt-4u+qs9MGwdv?a$3{2FjO9fWb+_?r&U(m|Iy(%7|SxbMPin5EM*amSzG7>Ayk1`?@54g +!_wAk{%G;Uwdb?QjN(n}zCM+274BexEI_uFo%IJxhB}NCyIJsn@(XV6J{f=7k6szF{17rDFR8LrUH&= +v;QY|2HlVwKAD^e62xM^6i^S&wU==E$atQGsZME9oxHZotC+{GjE0&#vDe?WbQp_4S#-p +_T}>8YPOjD@$o|HZ)jf=AC4I8q`DSf*BH>9b>+KC-(l}DDia@}C<=Q>U#ht5wqVu|+J~Yj`e&d0_zLt +PG=cURiuPF~|Gb4ww7)~|6Ca=h^qn$8VkymA%Dkj0GDLO+ehWg5iQr)uW)))NVKQ~&P2JV-)$E`b~*XU?!zY=% +)as_sRj;wVdUOPJn!Me#>HwE*(9&idJ39QSco@uzCdShrxwK>t(GD`ofXc^ZJYFEDn7G{>ixXB)xLA{ +SN-rxeQ2nHAAmFS}oR==WnHEDk`aVfunVSPWYh^?PhOeg;WP)h>@6aWAK2mt$eR#Q!_(1%hA001O100 +1fg003}la4%nWWo~3|axZ9fZEQ7cX<{#Qa%E*=b!lv5WpZ;bWpr|7WiD`e?Hk)}+&1=ozJiruM3c(eG +><{YK;zg816#*P9bgN`6=;bwqpd~~M9##s-TeC=-XxNuG#V$}m#vW2vUKh@9v;ferrMw+S#H_3AxVPr +OCreY*zBs;@tN+_MpmYhq +S*Kx8VWmYvY&7U+YMxI#0_BEN!)RTrNjI^rv8jRno$FY9)h$&Hz+Gk0=#z1mYxG&PGqzspg>KE+;4N> +9^p*04PHAu2$dC|V#RN1y5S2$%=vsXW6Glo}5LssOeMhg%f$ZqPqAYt%Fc>mj%hnMp&Vf=C)%xAM%Mw +Vzr*yUYA$lGmRWTXi%3$h_4?zDERZm6_}aK-^>tP20ZW^#CBmXs*Sh>JKJcndJ_hiX +2CoLb!GxNEik1TGUTR?Z@}+L41RHSk|Zy4mLwr1#WE5mj5ss6Al7`@E-$j1px&j?mcis +O6O>#WtK47;CFZx3h^vD&%0Wg=5vc$zU*G1oFcCNn;t(Q7l*ssg3C-5>3y(fn<99K3`1D>1R`MqMaPV +Ta2@3+VK;zdDE5p{S0Nlr(fT3V(TH5H&ilr0<#8lYnix^gzEtLggq8$)$ARysv>$5*i!P2|%O>8jKgw0dRh)j4{g +y;N8E5s?C4?@G$qH`U{go6bK9;D^J~Xzz2X7ns$-W>FY*p7dw#?OP-j$Xlwn!nC+H@e!MzllwTZb87b +@#y?^6dT*j6k?swe;fYSw+f#nf=3?>nRg$l*c-(q5!uTv#*RCCO?#`K~pneIFsWrHC2Agu>UKzP{kv? +zfNN0j_w8AhNG?}!21XZgpfDnh4N0yM`eNNK;BJM!VWhUQxuKviwKpYxeKmRO+x9VyucSSNr5FJ85+2 +FJF=lm2;2z@@~HF9Own*JEWUMd3T=+)q2-8Z$wIYXls?_1JA^Bey#n$Hizz8F$sBtd#2j3mhbtd`L#1!)0U!OM?twd +yw3d@>^;!giM#bsJ9A`#Sph8c)xjOucsidHhuMKJ*ds@6S|XI)zx4!3!0Nx*C(ux8k{{!s%CWSDE`l` +=CTt8oL)67JWxdNLCdb9hV6aG63ukk)C%1C=^M~)?X;ccpTPqO6<1NrTC!R%;A(CU8#Z3_-Kk}faCNN +^pUDExP^vK5*1^?X3M90h*cmy<9iU@6Xwt$R{g!o3Hsdd7O3&A5@W+Et%DHne?6Od2TUjLO@(KBoqkK +g-{X6|?`&PT>TcKlPV^<3f#vmgRl3!)%xSKdHLKn}7I}J|LG{{G*b-i6j;xou9@*HzkKes}e{uKg?d5 +qe)}1Nk24-xEIoDI&_JOoqyt7odeV^Sb^vQk$oG{Q#1JEb%40J52epf=}P`m4~Vqx>94sS`fV{e+pbp +LJZD^obne)Sh+r{k=ltvh55i|RMFhQZslhT(t98d{vl63&|xQxalg*hA2N{Q2_cF8T2O{axVYcGck3p +T+g+!%u(bH<~ji$OvV>kS51P{%_K>+xU)hv-4CssxkDzfWnJJyIHcuEB|%}_T{|m=)ePpSAlQO)0K83 +%$f>l^jafp#<{SHi?M6Ux9AG+v-0bvpAP72j(J4g%pPupmox>Y+I@tc +oD>MtOx19I46vJJ(A(bi9xfthJoE)-Zhv|CJ&Xqn#gWE}_k|*%)fR1a+47&e0rJn-+pU~h1dVyY|PzE +^q9=)DN7HN;IEVZD8JS6H2kKdi0@d!UVJ2%`Ry)o(tf51Y`IFi@L_YqowIZkPW) +C61X^9-tHyufe$eGuJXmah2@u{{8f#UqQ#%;$5BJ2;vV4CFxEAa+D{mo(A@6tj5MRNI<{{@HxpkYz3= +7y6@sSU!TyT}dCt1caQ^Hk2s7lSF}(r9Jdvgz3^Amh3gZN)nUWU!iY@{n0FNp`lwzz=?JNkg;NHsP>Hx7{yauXCo(vc$4W` +2+vT%@u2b_&E2iRq3{nne6UN#>*b>;s#TNaC2sbIjvWs +jvbYlK43HH!<{!HLb5n@##;orDs9fXBd2fW&T5}IMzc5y}!Nm7SA-bt{Ha3VjwQ(Kz7{@kAzb^1?6({ +udy^EdRe|BKIy8X?kncE;9(Y9DUi7~kxT95k;iQA6IiK6*Qv^Whi9vs!2=;_LZAiHQsl_mh+>$>lGuD +38sdJS{wq+lw`gMO{5Mc2%zsi@0BPfJ_C-1=mD^mA^j`*Fsga&qeLfRxn-2B$Vze(a*h$exKUM2Y?)- +4h|ktX;dH9ehuyg!)(#b-pdN;;7UP4DbTz!>BGkuZdSxS +w9ei$1CM_`S@8YwY^J}Z{aO4W1H_x}FB(GUFBImp{{m1;0|XQR000O8`*~JVu^G3384dsdt~mezF8}} +laA|NaUv_0~WN&gWXmo9CHEd~OFLZKcWny({Y-D9}b1!9da%E*-YVR*L_!v0ic|^GJ~hpM?=BuB0D_e4xt24@$;4t`*j?-{z#s_Tm1&bRvM +MXWo9(u&>a4gU^_r3V^Cpj2T`~BXEh}2>B61Dyvy@S?W)=M6q%KLr8KInPnq)mGS7K^G>+GSOhNIh(^ +K6;3IeE?+YM+;wBUY-c}{ +d-S8hKlWX?5CZN8g+#tgo#YN08lM|(oss#F4qVqD@Z9yi~1Sv{_5>6(+u0bYR?9>#X0b&RgNzznvl!I +-unox31Yx)+r|CLq+Kum%lm`qj`=q!#`P2E&1j!Cw`wIp=O%e<*sEdQR9G<(Q+Ru*&86j@THEY7oERaM!6{t<7FYa;mra(Ztm5wU +I0LI=;5)6h3`f$l<)m%4jp7GZq-7QH?6byj_38ULS!M;ePF#bfOhCFO&#w`|f?QV(o7D6!uGk&>yoEO +v0~t44aHBBzJ-qqnAGcr6{s^P5XTfYTQC2As0j!`NoynJBKzSO>BESYTlALl*z&INmy9W5MT>b`XpUI +U-*$NCSE3!I{L(cNmTv+a$Y#T84ep;@nW`E1AYg=WKwKEQHK3=$p#EtCXp8`$fA(12!=}qjto +%^v^L<%3OvT9>mi9gP28Z#R+ewQTj15-n42P8l6fT1XuvR0tE~)4abPr)5oVB1BbOm@;jKN3fpgN+21 +}S{D6#dd99?>){ASQ(hS&Mbn2@(VHvsOx1O%;P6QuLG|r(4Q_2(e{Vy$fxGKxu+xYR2o2tjcJf{hL*x +t*q)nY8QWJ#j512ev{=OD!gu;D7rL9LI~jCFZFo|#=_1iUn@)I3v>%ZK(47?NSd1V97v_`@8Z;uH(L? +hAJ&x9x~@9V030K5^bN3mIJHzREIvo%4fw9uWMJ#ytOrm%MAEFn;3!oV9n5-QKv%CPK%u@O2-U1AXnu +Zu-luEG6}l6ptHoIg+m?5te%y)D1>>g5J(B7tt<#v-xQfA#U!DJacKzwknwMu>3G()A{d4_rX=a{2lAkUOc$=4*5rcAyeFJ^PCj#(cK1_K=fa +0^fz^y86!9@m(XnbQy$eJ?8;h(MTIpuNc26`0XBb{|M`?wlPQF63KYnw;wCtiLe;>`zC)6drpX&I +>_aE%y~FFl%rvG%mtjyCi`s}|IC)YOh}GEEy}yR&wPZ>i-O{T<_$AkK9W;058m_=VEuGP*|S2#>JR;g +nqfyo7Co-D`F65)j)Wi9@i2S%Jk@CRgF3^602>$!%!Y_c9+b6`VY1_SmyP&2Vs)0FO*k}=s`YoR4Iht +*aHyDB8g?iL%%gLcsmP-im>*7uk|~3T$Lh`a{^THS6B^qp6r8(-qY^|BHo7VHHbgN+?}Y!yO-ajpV$0 +iYO~a+ysZzbFM}vP9LG)Xh6=C(zR+yzU8mG2|yuYnGK?F@!NR!oE>8}t~Y+XX)C9z;9O^W0c{zE)Izl +eNNG3SAYfBQ*_e!9|VU|Mh1u*-j$a;`HHU8h(eL;CHC6vGRjow#AvcFc9oX1d*%2v(c^6!Re56Xy{`7W#Ins+qNCX4*M)`Auk81o`IaW^r|Wlbm1ZC_(_Qk#8wxIB#9URN^a~ +aUFBdpNo`Qv9)WD;EbcX6SDn*ar_4So{XxA-h1EKpucJ3shl6tEG0rlOjr!h9X>3MT@4_;63l +O8G0^!lm>gTuhtYTK_F8FQUOgzkroz&ENt-|8B1_3Ht(2VH&oUS>a?#swAYq-j9&njP_PlrlUv#IvI$ +%RIdsr2zr#QvvU*K<`U?Z0;}(XhwasQ$G>V2*ShSNWXa#V#t;V8kG4|mRry{G}^$(WOz(P|I +gQJj&)Fmt1l-osS*jV`>QX7HS`jeH)qMjW>72zu*LhnHxa@}lEmz*Ma+hY^N_Iz6ih+=^8@1aHTakz>d2Fdn~$JKL1)d=!OHAWd +{PcfqEFYb)6BAW}8BK5vo*v;)imT)iFMI1o0>iwg{Mr#}Z0PAV}xx^=k$>Hd|m~=Zl>HIbft`Yg*9+& +$2^=+V6`8u;C3~z!@Gx4=s3?!Qh@Jp~5!jnmg&2_bAr(sG+*fAl3_!Y7u>i2Dj8+!X?A1zRjW4r~cj_ +R>dkjZ3z{r@%2+sZY-6Pyl#b8a{fn5Il&K)+7<$vKVGz)9J==*MhBH-)pEacVYymVLkj|#Q%hYqtpow +%)KyC87UoDoxmA{45?yV*aSs^X($Kd7_R0<8q6OZlt6hclkQz|7RMMA~FkL-myu1W%pagcOiI;JE=gv +A&5Fo04)g@@luyZG6sOV8^J#d4iLx*dt?;$x_fH`2$(*4>|xR@C0P#<{-9q+WQJNo_E;2D +=a`2!SCNdhKscZ(Dk%sKdP*O~T*3=|RC;gvvACbvtHWO1)OfASKaIaa7ozwYO@Lc6TlfM_EQ)-m}8cm +r6v$>ze@o3OcnJ*Vz<+eq#U`SK1VieiPhU{Uj{gkbo_tzIz$rk1)W?g}n=|gl)F+OfG6)0A37%h2G;d#Q0uX}YiO*peN?tSn|rD;RO +pD0Lz)2eILd<9NHHdtHe=X{@Nd+6LfXyAcelQl|Lh +q)Let-1Tf-YGdJ|c)U0I~P3)_6mfp`O(d}!=-GJl-en^CUBFQ*qw9*Z)$cS3e@M^(u)!uCrvVZtlZ6wcT^|g+PJKcXNU9kLn(v +X?&)Kdau2h08#JMN@4xRIrqZ83hdkdo76^tteLpLw2iS15dO!TG3EvMm>T>bL;?d6%g0y!neULxu +V2ZQ%jv&_-~As`k7fqO;8qw<#ffeTwZo~2G0Zl`!>Aby+_tuj24nFcfRazRc-SJN1n22(jOe1pLW4|; +JWbsZ>@vVgP!GA)42nV^!2#y$C0Q$kv}Z&&pSE7*h(ltbh}5$0t2%jxOqi$6^V@2B`=9=R0T+QL*{a1 +aOvYSYpCwVeY@kNGqhL^X1DY@nD1L+!Vm`sr{h9-QL|)&MLdi3Ny6u-2}LQ1@6aWAK2 +mt$eR#V${YK5K#0037O001li003}la4%nWWo~3|axZ9fZEQ7cX<{#Qa%E+AVQgzbYEXC +aCyyG-;dii41V`t!MZrq>5^dBW3Ut`uD4+iYl}7OI$VQ5XDf*sS@JCT(yZwJK2q{;JJ;OyvH*8UB=RF +gex&GxVfYeaEhUew&O2Tm8seNY%YgwQ!;#4Y&z(CA6hDGJWt<%Kc2By%RfM!U5N +RfPO3rn4_1z~tA4KSjM9CFy{o!}ek65dFa#X7c`owI5Vi4w;;kjTEeS8Kf$5SM5+>3Y*mL0gM>@WjHO +MK6Fg}1MgAm_HoF%C?Xal{jN-7;3F??W{Y!Y~YiZLU+;Y_i?$@t +bj9Fa5m#;w(AQ*JCA`)C-wx&?Wlu@9UueHL(=g$vEjF_1$0>Br>;Ac*$Yvm`F%y=r^vs~!Hxa^xM{cs +d^4|j!-b|w^)$|zymd{)PA*Pm&=mhEv5kYwo>;KopyG6z_%6H8l9u+XpT~=aoIOoapnQLUgToaUi +>oap5qVLcDdb<*v0l6mCJl6+rzh<=znsCzd`J}Kk^uRI +en(OL?a0rajdF|Z;Cr#!yPP7X)iLzi{pl3@vD=Ix-PY@ihjUKkY2P2uD*RH<=upLVT_lq!oTZ1%UO)A +apmsc$X~=y~ +7=naKCtX7+fCRI(>=3=yQ*?PjNurxnZkM#nJ$68Df68c99!dezI)l*Ll5RZ +o|FBp1dAuWTWUl4kZjGXe&PR5&qLl5l4r@w?owRKz~?w9n>MjK-P_>*zT_kT$vEAAW}3EG>YvqKX0;x +>F9NYJcFt>g64j_GB2FH5JH^NJfo%#XwtPLo;74%RO89yZx6)Bj)SW?0P2}fJC>mrg +~D?YxPb#FR^iR!pbL27)CbG}XeAWj+fCL@P7$ETsxM3c!VLu#b)WVIz8Lme0SGxVtehdHO$dFIrhHNr +T-Mv2qJ=`mdOP_^zrXh5DTv;2r}AHy>YDwEVB(o-;8ZfCJ{PcefYQB6K|dl^Zkr{avL*qfbdZIujlFV +MTzDa{@|#bMbznawcZFq@54PxpGBoU`>x$NoNhpie&B>$2ibiujjgOlrjM(0Tsq&GqWfw>RW|NSvX6F +G9-USt2&C?ny6D=pN=V8qMJzqh(Ima#GT2#i_MO{KpG*GFBO53jZ>&|KG4SM;nd7x$ZDh0?m0xir^BD +zIm?6C70d@6aWAK2mt +$eR#PD@+Cmox001-{001Ze003}la4%nWWo~3|axZ9fZEQ7cX<{#Qa%E+AVQgzb2T54h~M&@r^$#E2@lhV&0{WDM&sP`R!HOPHXcXts|id2L~V@6VVdNE +CL%YR3Ei@MM?;p6$Zg7_NzAqk_D4jno^EJHL!b_{=W`mzAlEUu^3N_#KfaqxXa0!bc=q}I1c$T9Uvg> +kc4BraFi~;uuu#GX*=KdaXBZH2mm4XoW&7O)u^8 +5h=L?Fwdq^j*-n+f*ng}&z1ztrzNpu$SBVlI7*yvcPh|XG|93wt56wEwFcXK?JDq^C6H)Rg+xIrV~{& +8Hd5*}L~BAIr*gVxHQj)mQcYntfiecEpX33#WErP1|A$Q~9_=ARZz6As$k$^HDpWefZS~u;A8|Gk)#4cQYIN-8e3BV%&u#|kweOXV +e(B{#8EO3D(^zgqAdctjA{E`iBY-U;GomdK(HpA=?H@~O6zL~<@pAf4jgI8hjN#%9P>C>N+Ow;Dv$g> +;Y=yb4S1!}{;�IFd54FBm%`k6dc>|9Rk5+25g0f8J{(zv0LJU^p}y>fumTk|gSMU0=13X#c&-s*Gz +DsMqES3f?P(bPOVGwU{$Pn+cc +}n*#d=)veg%ZsSBj@@;5i04IXNM+Io*hD{9DYI@oirE>U8t+;1CR)5pnWR_=!8K)a +2Hzp0R|aNi$TregNK7y&}E2R2-qe{0WlW=c|N`P@b2Z?^S>waPjAj=lgsxnPbb}S>=DBa>p39<+LratvG&M{I*vCWCM{RxtX;skD{3A)p49d+21vLc>^7@mC&$PLClgPndIaJ!LcG +Wtb7dTEh4lXv{vHnT1W1P?22W~!}I7E4@0=T-{*pD^|V{RGexQM6b@=@hD +&|junXF3fF|l-)N2Qg^Z4V{NC^;T*aBr_1}HOhx*Ea7y=CIwRt90aji@>g1%wN2M(QN9rvX8fTEA~;Q +6x7>dY2eA(umCf(^cgS)V~-M(XbLqUitGD)^=?ec^6jGiCFiIbYJpJVH52%ba>fMZac&9z +xLZ7sdBvfS$!p5@P?^HXa>*fxlJ)nHc~9ZWBQ<+6m994pFsz{sMY((vDfjQd1k8bgE)OrVSQ^}-8Y!z +AGBsaVB;4i&eLu30_2}xC>vHw#*TC&stScm7$fpIjFNCr$E5`9L(Jy*IBZsZ)rm2ZPYlcIY8jCG@_~z +FnwkjWUxQ=t#NtFbf7?lXMoNaK_kBLnZtyf)jWgOLPq~&Ok05(VHPV1VKSrg!1z42c8*k$|IL)rAmix +DVmzx%dp*|bghe$=NyE3KZ-QnpB(-E1yD-^1QY-O00;p4c~(c!Jc4cm4Z*nhabZu-kY-wUIbaG{7cVTR6WpZ;bWN&RQaCwzf$!^;)5WVXwc$5G_A5 +g#tLskl-4HY8%q9M)`_O=6++UDh)N{Lbd@I?752;bO4($qW1J0aN- +MqlvBLwXfZqGwQsbOVYqE`15S#HiL5hcSzl))JaWdTIf!R#r*4NuvFIOwug<@epSu*~DXzSpszawN+@ +a28A6Jt-yV@-GQ@wTex3&k_xJA;SEf{7Xny~e5)Xhzo&L%d{z`~*6BKXjH7g$5bFr8OESTDWd25Z2RF +Cv^Nzp9Htl7`R4F0R3jO{wGnb99E+d>XseH?8EbO4?L58nzl74WZwI|ek7O#7??n8_Y-#CLsh?Z>}w# +KrhIqY}aCGnv4r@uhsGXZaM%89lIzqABvI@%gt@&K1`a|bEue;%CgPqN>iE*pVod0lp8Dtb@8kmQNrxh`*d +G2AgBl`Y!)_kyFd1m%F8Q4n}jk4zu%`uSsydIZl%9;krYUQ-`)v;)@r3s6e~1QY-O00;p4c~(=_&&Na +S1ONcX5dZ)w0001RX>c!Jc4cm4Z*nhabZu-kY-wUIbaG{7cVTR6WpZ;bWpr|7WiD`e-B@jJ+cpsXu3y +2bU!+bE>UN*9dO(*rMSvwKG7sBeWCA0dZ6eeQNXm^d^uOPd@={>HHX(pz^6t6k9^R3Rm#WNJk} +PXeD@YP1@~TvZ@mpQW+Cb8Lk60!)LW{D9SY3#;%pj4%fZ|en-jXoVQc6e-n&=kfN@buRI~A$%+>ioiU +WqDBHc(`xinX|JU9?f6SS4m#K?@wWbGKZB8;XBih<*_%qt-w +@c#-He}27r6MhZiH=!STo|nOrt-#FY3gD+&NaP*NB;-&S8amIb<_o9%!efu&)0;>9QR{u%lQOrjnBlH +l>}CR=>&y>hr0-ehRC28unf%7(9Wo;!;>)tQhO|~%Y~W!oJZ9`QtF}@U<~S3Y-%6O)MXez_+HfW4y9K +4k_x(@kT`;o-R&ixST1&2WGE-0~&}}jinhGm>FT>wxQgg&`H`CftS_t#zrfPbKI5918b +LJsBFx$nSYiQ{U(m0EOUuGwkYleKnZ#_4rU +?776Pw&W+7b$40qt;zN7=BNNOUYNvH*jzK2tT=yK0|9y12Tx3wP|YU9NSB**4TbvDX6k +EZH4UM&;t^;ip2g|fu=U#ff#M6dW!(*&U4Q&UFGCWz^qaAp +6m#Q|5nog|_oQKF@yipT;D+$ob=nM1#V$X;Z#|U)8r#r02Lq-YN>WCHzL1+&fG%{UB+r#NVWb7G@2EAi{fWs2EG>R6n4VN|T)l3R +)8Vv|S+{Spb$dUiZW-u-z!X5^s=W!W3@{6D+AnvVEU(n!P7~Wu5OM4J!T;5&;Qa7>+Jc72NPmc2>|wPppwN8b$+2BQ`CYa~o*@2NUzE!V7d~{0tt8wwJ=9$_LwaLhQt91tZC7CXo|oPXoA)T=62GlPc$Fk+DQ@%_J3SvO$QFmvM4nc~;0+@neId*-2(FX~~#<9mn{*uDAdN6`PfIAG%E>+p=R>W5mtMnyO~=xP6) +M9R<_&VltUzY(rSC@c2s0nTl>E1U`I2z(hqJi=5dv_>l~F%FRpjjtP(hVsfQvp0b&mcSnu}YO2;zLqr +v43~8NBsY*GY&dC(40Zyh7*q92c6!}mTdRDCe0s?Dnv^`09&UKQ^6cd{{5a#TQuVN0c=mnZ)JbW!Pbw**C~5)OF=eGDzkxIO8q#YaWg&e6OYrm=H{=i_#*6V55@o6uYJZi_GA +C}tKAvXtz=i%QKR4F0^(y##PThAIKfag<<`ph|RZvmCMuHu7T_Qx0U>tn3yj7tMl`Fo46+J5A1fW~V^ +1y7oPT$f|>A3_+QpW`8@u@_MVG>~)*nO@drXa2xkP$DCmF&q*7(b5chg1aYqhv`WB6`|OJ#7!nQt{Dl +@DR@-M^q}>-p+UI}er1K2>XX?Cky=v)vkaUY>&lFAkGtOwE-Y3m1jSbvKiIFN@&4_FYX&(vCTeMa1`2 +Y08RdFl6ZqRgtE~3qe;7W&btSKRPT?JK48zsNH*F&jStbM7GEF9S(h#o*a!(bfZ%`bx+E@1oxV2Xi2jv)=j)qp +b^su9$HLAAW6)ng+Ld>b?6sGm1pFonv$0Y5lV1F;BxV_oV%5@DQ~T9ku$3G?@Ct4Y0bSYY?$A_yZiI) +;&C~@h*S?yVXy{k`RV<_@*msj$viqEl=&W*VZwX%I&nlO5Qp<_(~+zg@V=d%F>FAjv7)OPtpVmefRWo +JF~ZyW9<{6!9x`!o&@&~KKXM$n(HZ?3La7{hp(Ey)i*PtGJZMEZ`KOE-jXwPz +X81z9uT6hbrOIh7|;UuHeMJ=N(BvY`7BAU3aN1DohJ@+$-!*@zA=u^1hyq$#E%@?quTtt_fi_3F& +}z8U}-)UH)N!r%MeG{_?;h0di&%2X`}f-Ma3}Xu`*jcYOaGZwqLM23ftMU!^|XoIg9NHnpZ=~QHO+q$ +DPm~|9R%nKf8ts=6?SvidL|lv1Wh~c8-ILzNzQ1Fp5`?7QZiSIynL2G=&&{X?og=M_8SqE5pAgn62lg +)0FMc)d|@P|Il8z~P!q}248GP!w(A(Ca951zDM< +@pKvpRDAM~;~>`qO= +r8%&qw{JH16!87_WMWAJng-@x8fZU|Reig3;l#*SoDBHdKf;O+RDwO!GqSshE;d7AaFe!ge@q;1Plo{ +e^pFz`6BxFPi)fP)h>@6aWAK2mt$eR#OvSZ4k%;000FE001fg003}la4%nWWo~3|axZ9fZEQ7cX<{#Q +a%E+AVQgz92cuor^wb`t-=En7Z;(siG(_DAQ)0#yj{@rYRO+9})0CLBMEuLWsG7J>d&gZKhgE{Up>5w8E4BzQBXfrGYtY{qRtOzN0Rn)eJUg*H~FdB@7h{D6hUBx+;*`P-{o#^69@YmE9Oa%9|{P#u{V17}_x_z@xWg5CvLn; +(O$r8K>F2_1SUD9wOAN3f}@4zz)KDSfX_bMIK+T|U}eUfu(DEvky`==*oTImZL@y?qY(F7U|FFYBkt2 +HA`Z5;$oMTtxcbeY}T@IbCyZ@6aWAK2mt$eR#Sy|x~Loh002<~000 +~S003}la4%nWWo~3|axZCQZecH9UukY>bYEXCaCvo+!A^uQ5QgtOMYAVOHlB<(54w8Ti!Wfzq?7?h%9 +NC{(YLpu6xv~6LagDIPtxQi&aw@Xb#0|XQR000O8`*~JVWffy_D+2%keGLEr82|tPaA|NaUv_0~WN&gWX=H9;FJo_HWn(UIdF@wAZ__{!zW +Y}U>A|+@S}8~zDj5md9tsjjK*gb{qD?$*7H!tn?ph@v{yVde#Ez2`^@4c$U?;oZJocN}&+AInOUUQ7L +g34$Rt8Yc>k>04(Lb4BGZY!L;dyoO_T{BgwTgm)h0XQ)pTelJKFzA(@^0<)W7`Pw^{z3zmP|y^w3XZ% +PRrWpDMc^HlJZzKTwoI4Oxp4IDNfpF^q90&HAZ`XetH|HQ8X7!YdE)Y6CXWyf6}uk0=i19!ZH$#qN24 +h!!kgdwJu_96rY=z&=9U8n=YO~LQ@&gErpX8KIxm;%An4GOLM!y^C~!lCk3qib?)q?7}wa5mBiOlw~Z +wOOK%JdCQD&SnvA}EpN!(Xs@0O2#Jf(@s2@+(#w}wI1x>3Y%toUO#vMKk2M(-Rnt?#+e|9AK8b6k#z{ +oaDj=A5Oq&VKkQJ`R#Bj03Ka;|WR(lBx9*i`F|dqw?-3d>zY;LH*{ojKI>U^iw^aoP~+S;sHGle8TV_ +htsOx)y&F^@H|wN}_4Y4^<%7jo>C!V2w7Es!hX!$R>{aVZE#EpdlMSb#rohyFIzvYn;R4J8LE;w@Q9?Z?g$I`Zz#4nauUlb9Z#uV{f3-3^-V9K +=L$X}%jpV)LtZ7h|iGNWf?w+Q@idgTvdAgX#1)N6vM(u9&?xNmycwJLE_uMSt`k3AhmxD!3soni@^Us +7#{Adct+k_n2ZvEJOgLEhyU9`*@?NN*i{F!0|#2&>sD9!(dVkAzj)Bl?BcWXxbOElv#!ti(@NQO#4k% +Ja|Fbdi7lj4R{z#UPua9qy&Q(^lzWHK;)kE}dk>Zi6%t$^GiP2W)ysZ|nYH-g|Kpnqb%l2sib=XCaFh +R@6aWAK2mt$eR#Ppb#ofy +Q003wK000^Q003}la4%nWWo~3|axZCQZecHDZ*6d4bS`jtUC%MA!Y~jA@V%en;0@M?io8i2grW{zT+~ +V45ZkK+lDlv@hkkq0XhE7S|Kxsut`qjKYFH4g4=f75Mfb^CY$l=h!O~+4E9w_;CCgM4Ep~9>>b$S((w +RHD`L=*`euf#`LK#)&u-w7DSB&{dP@h78G&FsNMkuLY>4eIaw+t<^XGBc@pZQetji(i+I2n&YFqCoXr +hT$;V!}6KY{Ycc+6RXoNwGbOu#~g;(2F(I +(+HSj-ri(T$7UvoUEz^z~S<;bG9#`ytzZsH}_>lbQZgD*Wu_KC{_Y7x-T<~;71|(W#DbGnXPw1THw4< +MAkziq7J{3t6rgLUa;HC-6CCd`b$0SOVeey2L;g`m0${RVN8V}XXHs|N@F@>@igOX5#7VkEVxhl{h%||giYsZE~cEpE6xT0B8y_y4aT2~;qZ`sE{@>mb +Fo+q8GerrCPA;$QSjfqxP11K^OSc#Dp>NNciJKF+1uMAR9-G@x_L>4J0G>+xEK1*N)vd>veb2cX# +PiHY(30x^5WPqb%;(8v=I6jzmV(%~!Ai+I62N162T+0CBEg4HG8mSCNF+!hwncjmYG(IaVb8CbjRm=s(7joc_n+&da4zF@98J(qRYy$H9u$%HZ6 +1-mpgnVt>0eItz +;!3S_X(v+^En=1|^XsmBa3RY$HI@`g=}c_#fI4BdUP^u;qoZ1Znuu347R;pTiWkhH0>Gm2nPlt}kara +0Fhs14MD0t8)|1vfJUPZe6EI;w8#z2V>g_~!c+J9_DaM?VS~U{q{7kNEv@M^+M+Ac$;YAD)UtFg}3}G +aRAIOXmnq@HtE1+1(hiJeSxaf-XmiMOJq7 +>5(TC`V|VFyGh2&8^Cuupy*h$oLIY1-XmzYZApVTY^>8^0Nbaprc`10tFRA9?@9W +AB;C=pk7K2gj=--gzRO?AL?BY{^Sm3%A;n;VrIscm{Cb~z}{<)bUjn&S$1LJ@pAo;K}|MIZ?wk)J`%5ww(mY^l4DcK;SWD% +~H4({1aDo)Vb;^;q65M91H2d~jdJ(#`xZ#&u{;Q&EhYFJb{&MSB#=Yd@n>9YAtnFxS98x#7n>Z+6)X4 +6}&Rl(7)JX9iv)stT3a`hA_i|-GI!|>Ef8DKWlD#KGZba+L&g@E+Q!KBwipe^m8h5+eipZG~{ynPTOW +Q3BF1#lOz^$+%m`g6+lFR8+)_pholHpj(a$#R4GXyQarVAh7Cgc)1~J)a3wRg^fv(S?A)R)V+)AnTX^ +y$E^@QCD*B#ajH#IHNa~qRd}imX5p0^fAb@E?580o!hr8J)!X4pUQ1lu6T65uG5JCHr&u|4D4s14DH`5kVt +_{ii&;Zr*%l$3UTg3g{A)=Q8uU;g?%RJt7`thG`n<|4 +zp}$7e+P4>+TumN=>Nk=Z)+E~PaC{+@Z~DQMw6~qRp@kbQ)AFg>Wx<|wM(b&EymEa{{;1T=kC +~%K65*LsfpMt&53q@cXA{7jIq!6UTWxpOuFAtw_$_U0%KXZZf|3k@0klczGjQvB@B!*2Bd9jbrlsl +G|e>3S`1&~P=flahCph1;!aCd#44p&(s2B<@I!5nbw|rLr=mYvQkej%azHI^>ftt3X{gXpIy-|*CJ)Wm2iPL21r!WSnj^SeEm4l?Zcu?a}N7)92n|cD5DQGq;@^S|#_ZX0)ALvcF +*Rv~e*2lZ;Q|wC_k1ZbkR@@en-7HmeFSpQ1V=C4d)5gubJY&{G@3^0Pd&4f)xrH&TQCyRy8AaO~f;tU +aHJ%3O?YwJu+w?2hEECuz>)LkU<~U7&$C%i-%OD7%;zQMaR+790|(YSH3#+ED_(oU6&t5^DqxiPrOWN@?Y(DxK%j2-R967X`#$=j01ZRPCGkJVC_Y|&I3D43t +B?tB)#aeP0i<{Bx;`C;Tda5 +#n=FXRcZb7lpLOnq=oohC3z=rpnj%!fYYr2LVT)$K#lpYpx2XiwFL#-HZMd;2+%ZkCcxGmL)L~@%(gg +eHXR6Dx&$iAm7}Io%>-VNa5$-PM9U=QUh&;6=>~;;!Zv?nqG--~5mYx6Oxx^jv-YT7TN}C@G8u@36p! +1}#E-VF$7j--?9>|5QwIzpl9EOgCLwRPVzrUntr*opvIMl29fNS6D-9UW?A|mqR?5_K&hAyX^!C5{-B +wAfhv6G@Y7dT1Yb$S3i7htHbZV<3LeI`e)}HJr8J1bEP6X5~uPDq_$lO;hQ9c+!R*!6svg@)OGyHm?x +Xw)NUWV9^_h2mXcOwl)vSZIRcN1aqXmi)o>ArEL?aaEmCW;t`#*-445~P!SDC$HOM^NJo8aLl+o_m#8 +FB26vRZb$Rb4KY#UJcRV-OlO~OS;!iW-c`?Dz10nIY +VTDoG3D$?cAye#2dNHi~pnP_H#o-gF3?m-~We!6OsCHbZ+m7?)FWg~?zr2O3it-P12-BO$JZf^P4T+l +5&{x`0t7}?q9)RxoYckBJB#R)xU{k#3H28Q1C{k*RDk-aXk&EIpo{{m1;0|XQR000O8`*~JVb{HBmZz +BKzZlwSK8~^|SaA|NaUv_0~WN&gWX=H9;FKJ|MVPs)+VJ>iam3>RkB{!1YUcaIMFXRF2aq>+L;Duoue +!}BLV=y}*_{es7WT_#^_B4$C?`B?}`&FGIX)y&92nO%RJQ0ix2AL7$Kl}8D|MB_fPxt5V*QZ_D|NiC2 +|7E{?9`^nBfBy8x^~3tfep&NV|MRC`e*E$Kpa0e8`Gft-X}{Pv-+cGo|5-o%{Q1WpzWeUezy9>C3x9q +6_4VyHKaZb2|LK>XH|hU<^J)M4_CNfz)<1mvH=q9btNQ%A{xOv6^)Hn8FNmB#u1q$N7nAQpt{0JO1UW +G|gIt(gLGDZ*AWtT*LpD!j3x~XzeDGZx&(~x({R_%2JJZuV^z50Q@%t>l@Ash(?jMhJx64d-74*e)kK +af6eZ=ph{669LsS3S;ZbI*%51~)cwg1D{U4&lo+@6@;k9_VS^a1*eOrLMM;F-X4i#(5jb}9Ye=;yw_r +h9kBcbBAU*7HknyJR_E?xnH2thY268qA3XUC^NSkp0yuzWtB(gS=~g9kKbdB) +g-%Fs8--LJ?D!)pb@aoNv#TH@{And*zOBjLdIjelxR>nT5h*RCtUE>tx|~p|DPt%vILO!ed +kzb79Pd$Effa6&|C)3>Idv@;g|0j4F>&zk`j(xA +FKk9^b|`r}6kU9^b~}+nC?R{5Brn#^c*~d>fB%d9XeYet!qQzk~T5{QeH+ckuf=d3-1HJNf;c%>k9jIB~GsLk0T2wv2YR#C$Vr63n#H~5(_7>a1skARj#r!BoFu&|TmJ+$j{1)c7Fu#SBzwr3716kOCE +bKrQb|4EokcAz{GPvi#J+rAVgL`IEUk3NgroOPLFOwxXS#u_9&SXhWmgHnfPS%{snlqW-$^1^{cQU_| +`JK$~VtyC%yO`g_{4VBqF~5uXUCi%dei!q*nBUht__3y|k>;|>OT5ZUyvj?w%1gY;OS~F0_guJI`Nxf +0jaxfc2UjOo7guSFDqHW$*1NLxu57()qPM%U^{$EeP0Vkix4W`Yt(kUCDjU_xMzyk0t!z~5V^qKHiea +!(t(nE1X%eZt^r*b_sJ!&3y!5EN^r*b_sJ!&3y!5EN^r*b_s3ssgR|i+gMXa{Cm34!c9<`I@uibN%`Q +@cY<+VoTwMON&M(sQbyw<3^)~LMJsJzywyw<3^)~JIS +tv&Leev(vKizdFbuaZG^(gfu^(=M4$NP`F-lT3)?@}L9pHg2^zquZS{~-Ja;XerfLHG~Ce-QqI@E?T# +Ap8g6KM4Op_z%K=5dMSkABF!Y{72zG3ja~~kHUWx{-f|8h5so0N8vvT|55ml!haP0qwt@E|0Mh;;Xeu +gN%&8~e-i$a@SlYLB>X4gKMDUy_)o%r68@9$pN0P{{Ab}m3;$X8&%%Ee{5!|5^CY!ha +V2v+!Sp|04Vs;lBv~Mffkme-ZwR@Lz=gda-@3PU>R+Ymj=Bdir|5W!JOR1y5BxG9FdNqsn+x8ILOCQD +r=;j7O94Xfhs6#-qu2G#QU3({Q&ul);uy|#nCYG)sM*NfDv)Z5o}lljSHelnS#Oy(z(`N?E{GMS%D<|mW+$z*;qnV(GN +CzJWfWPUQ4pG@W_lljSHelnS#Oy(z(`N?E{GMS%D<|mW+$z*;qnV(GNCzJWfWPUQ4pG@W_lljSHelnS +#Oy(z(`N?E{GMS%D<|mW+$z*;qnV(GNCzJWfWPUQ4pG@W_lljSHezMp6RPtAKo)0eLg~~&4O7KbuN{C +9Jt@nqJ^|3{NQS}#1e_=}K`X@sP^K0%O!qPr&AjLRZ32!c +^!>e{l<8seUY#hb%caL3M0J{S!sH6zQ5uP^3#nu2(_YtJ7b2{YB7UL?t98WbLCUp(>$iAEv+PN*MYlQ +-87a7b=gY@@Og#Q($IF=t@vvroe0|;bwK1PYl)9x;`&4IrH*L2ug@bNJ_{`C`zbGXi6|8bR`TWOeHKO +$SXG8r396SQ+YU*hf{etm4{P#IF*M}c{r7aQ+YU*hf{etm4{P#IF*N2d3cqF7ZW^X+v&Px-)0k>61)< +`7~HaVvk6HFSqVi6RS8WAri8A9p@gY~r38`b9aSDt5kf>%ONLR3OhLRLaiLRCUjf+?XZVJKlLVJYDjf>U`om4{P#IF*M}c +{r7aQ+YU*hf{etm4{P#IF*M}c{r7aQ+YU*hgW%cm4{b(c$J4&d3cqFS9y4qhgW%cm4{b(c$J4&d3cqF +S9y4qM^JeLl}Au{1eHfnc?6Y5PMnM{wWYt{`WY6 +RPGUtF@$YHxA6{~<;ncP6;jH0W9%z0$j1ewz=uEWhX^h9>R?HIQMZpXMCa7${R18&E-9dJ9wjSLXy7` +Fp%Ne@Jtit~)y1Gi_~9=JW@_Q35Kw+C*|xIJ)l=9u%q?HM-|&9S06B(iNm5;*{OVB7%!0|P{+stb+D1 +~O-4h5_Es3MChLR6B`86m2~qDl}|Vo@awapECPkYHj7 +CP+|{MH7ZtazztxOR6Z+*qmgFCg9GD8`i{09&HBh%(ydfOIBtE?##F|aA!#_1n$hZGjMZ?qRYU|30tl +JcVXNGxH(zcAz9iL#$AA$bEqA1s2y8aSD>+lHMT%w3v2ArwaZn`8>;|V8DIs#$^emZ?J8?5GOk@^Q8m +0IjbD<6z8m-5FglHgp@E&o>=@+Dxtb2y+|HOC!y}C!X<)~g9fQ1$kr))hekUbQ67paQB;mQk6m&KP9n +5!rWXB^1voj!*!7{<5?dI=f9P=h0nz;_LB+Q>jN`9$_r{vjF^6->A +drBUjl4noJ!&CC?DS3EGo;@WGPsy{V?wJ8N}fF +>j~COkr{wXLc**{Tr{vjF^6->AdrBUjl4noJ!&CC?DS3EGo;@WGPsy{V!CaoRBQ +8IgyujsRF4qE?Gq$huFGNo82J?ZL5Ag6p`>pTok;!`cV7_5s#Du(c1c_JOT^fVB^7?E|cR +U~3;>?E@br5^ezGBR2w;wBc?{#v}RQiwL!f?Da-?y^+1%2(LG?*BjyWM)rCmyxz!OZ-mzy+3St)dLw( +i5ngX(uQ$T$jjYHKiX2&yBNREZB1d?=k-gps{f?~Pu>f~r+)(>i7aiF@W{rZf{z^8M~)4mY77vrbL3NEVgqjWsAB_eK3XC+;O5geA{_0=j& +_8j9of;2aI_;k+7YL*L}T0r+{U;KxY_@X@V_Jb-_d}Z=Z4sUyEE<%+?{cE;O>mO19xZKn0I1l+#R^ti +H~sNV`tnQxaG*4;Xw{ejvyx{XOIh%E69z>26AWe0GVwSRvCLrCh~+}d3KExg5?97;)G!NK%$6wC-S@# +G4Dj4cOvGU$n#Fbyc2oeiI{idlCy|_o9CT~c_;F`6EW{Zo_8YVoyhY}#Jm%E-ierZBF{S!^G@V>Ct}` +-JnzICxcTIxi0LTubQG~BiM%F>H*nvK8%veQOO=SpEAmn$VyO~&sS>eNNxW1^SgIsmsw6B`5-(K}mMV +#tDhW%K#7mWgrAp$ZO2Sel@lqvWsgii9lCV@syi`e8sw7^jBrH`DFI5tjDv6gW2}_m4OO=GBO5&wT!c +ryiQYB%jl6a|-uvAIBR7qH>Bwnf{EL9RORT7peiI*w~OO?b+m4u~A;-yN$QYG&omV=6o!2E;I>bcuJW~Z-(j3b29k=%!?$L*O?nKTc3O%a^aV|yubNGVy9sFR(R6IM&(u5C>7X~!uA9 +kl~-l;!O@Uc_p||$|fIPdl1tdJDwH +ruy(bVn*GCk5T!5GgZhMB~!&g3Ilb0jK*SvOBynm>ZIz`qV2hVO@y>!_T +gCVzkzxPDc%#Jd*~j>^k0m@Z@gt$J#tSmT>d07Q4H|2_-Z5yb@xsQSvBnx}&{$)QHE67{TW)a6jWyPw +vBrklps_ai*U(rSj2rgbc(>f3vBnx}&{$*hXmFr9o5v2-?5vs{s@bo}%1=RSO9lp3F4DdH|`s1)mf2y2bmp_j#XV})7!Cp>#WFFUS6FQxx*rNw# +Xfd+*y$^Ltlecb6f}DVAUM%2zIb)4yfi}WgFoj9IR{ud!~bxZA5g!$~K^EBNZlBkV)PU;5AqW2Xt_-4 +q~-@4OX@RWg9$24=CGUWgAeo!3J-@;0?BH1D0*DLJU~8!E^BdKZ6xwKp_Sz#DL>B_!Hikz&&Xi#VY?A +Z14v3WQd+%H9FY6g$h1V<4iuCPgb7^^_i>?6ACd|Atn@J^4|JP$03|-eI~5WH346U0TTPRkoT(GH+=rZ$H6c7{EH`ng(!r@TapV~l8d(_7q%o9`^ +XEQ?H7*@J{?_)-^+#fa`B)o7?j2DCANL8#k*Sz@8#mrLHykI4IGm@$fQ*`AO5;|X8}Wb-MnzW;T7EMu +-=H6xG-cZe(LtgHNzIosNh9hz}IUOA`;Vt(Y9>;Ag6L=gq@0Q>2Z|}}B>0qbxBcaG +QD>7F3PXwk!PrgGi<#_6k@JT*K+u;~(r-7ePPv<2B&L5la?U9X@^KMDOv@R>)Diqr60(%bfM$=iz3%tl;gbbQX&D5o3KmP&ySct33I}{x=W!$4z)-i-#fmKmoE-q +KwXt_N3elwax(u%AM)`Wh%k6rTk;>+hQm(bC_*o}|aLx~l}>SCAt+Ba%_{XL?+lBZ1*FbkODZ$Gvc}c6hGiIdqA%oo6fE&GtL-j1to~&z +CZ{WL|@_yh{mbJYT50H(x&wa6Y*-HXC +ADGUCXOZZ&T_yn$qmZ^zu2YafM4Fn<$z!E8g^YAMeqU_h<#+4}w1ce-!)?_@m&Dz#j#F1b(^ +3YDM6Wf+5~GJmZC{E +|?y3h;}aU$FD52>t^6MerBkmw9br-e2P1Vnbt>g1-X4q+6^C{E~06D)3jqUx8ozja7la3jPZGvaVSb_ +{Gm(4fvbjZ@}LKe*=E;bJp2Y-=#da;kk0v(=t3)(mIymxsum`%^fG3I}W=$yA-(@)VnA6x_Yhu|N;FNr8?0KcqT*8u( +@_y_Qdf4&Cr55Yfxe+d2o{8R8x;Gcqj0>7-Y)&%}3__6+4vi@2V_+=fjCh*I(gbUXaE?KXw3H+J~vw& +YyVHWUfF3bXcO@>*(uh}pQ_%$770l((MEa2CKm<9ZD-QvPJecgip27XyztQ+`cUAb=HzXksd{NjhNw; +wLaH4FPrOR{Sg?i0Mlug2GnUthoZaiN_29_v}zF?BcAN58t3^Y^ab#Jcj9c@|-NyOhi$>@&z7ro$$tl +TFNB$90#b2p!ji%02*gJ$KoKK0xR7??-Nv*u9VIJtf`k<9bioZ~RDxl6B&jYd=Lk*!Q@X{C+_WqW@p6 +{iNr5ch-r!lW*C6WVqG=ujC{5XEsE;S=1fnJ1OL2p7g(7VtF=$ykc3Z7f!xyN7HrQkpMuuD$ +A85R9h>8GL}_FFtYk?t}Jy?|bY-at2@chD8j1bqqpK6Lh5b{+Ve;BUa+1b+kmCiolhH^JY4U+!ZaxQ} +%-!QX(t3H}EBCio5b^$Jo0eiQr#{3iGf_)YK|@SET_;5Wft>5L+}saAA)}X{}lWa_$BpiOyHk_e +**s${1f=6;Ge)h1^)#8DflPw%k8{l0{;^H3;37dU%|#>sm~9&E_mGNNA692A9pe(b90{k +pIug=Ptch!#F6@$<@aZu=p}N%BYk?8O0S^v{DMS?UCQq}e&6NyNO_c~f?db&hx{Jtml8nW5kas^`TaY +0Qqq*MujOT5>#KXHIdAVekwf8E2hN;0wO#UE?!SM9a06Yg@qJ${+$72Kcdz}$bLAReeE1A%cE@uQ>sK68s7Hli*Llp9Fsb{v`Ml@F&5afIkWT1pHa>XW-9*KLdXj{J4%jXThI=KMVd0{ +8{j4;Ln0T1Ah^mXMM3t;j4hJD!;Gbs|sHgd`XCLuHdT*Uln{+;j4nLDtuM&RfVq#z9fb@SKx1gzX87l +H|GZYP4GA1Z-T!8e-r!-_?zHwz~2P_2@SElq<_RPOP*Qv8x4kwTN9Uu&YJvXMz1JVm}M)XA%2ZU_Xo4&(bh1O~$1` +ZYDSlI8AUGaGJ=?AU6~I2K*-Y4fsv)8}M|&)6svI{yX~b(tk()l0Z{Br;d$-M +SXf@)$v5x-=O<9T-d$XVi)3=yHGDR5XAn1#Z0$U)P?urSyq_m8+Hvm*V#{un2yn4N{_E~S6q)HEqO8| +b^_{*frPOQko^ncwd4dndntr;mB1SJ0&&j7#TuNJEC?E`K}WtS|Zf*?-t2%K>@EyOdP4GhS2d#@6ze; +BMnMoLzUIODOP(!`WpL`hDnL`tj(;OFtg{c)^MT|$(t`NGdr@FZ3u#eLqAL%}V$>r +1mcOly5N)oPe;d9wlf@lf8e_i63ZbC+bd||uZClXgZPPkqb4NV*DGz9&N*XrXb^)H~JRSuWkgrlNW;a +0${^1<$^7{My%VO|w_RXH>3Aw)FDGu{UyH>V9A4H6x8H9WjYSi&Z((;)E#7vi}!N&6urG!$((vNETC; +qVs{OSlQ`H0XGo1nj5m+Hk~fQQ<=D8sB>vI5&OIk4b;s`=kv(wS>X888&lswL{98dlwslF8cTDXV49~CSs}>a(&(7?>Dt%rPnpOy4FXG)!$c57(?77a*(P1y%5e!4UK>UdI7Er +9M31;X&cPw!(HPXP0k6L4!U#zW+jm2=`-`iYlQi34cKz(xdbW7w=%mDyxELNTiEH(6hf5O;5<2GK`?xIb;3h;gh)59J>#}m5fhu< +y+7o8-l=2tvy9$aE=@xfJPiQF7S2eM%nMFZVG>Qfh~XyvraBIi3dD9Oy`2>t41juphWf4$#~L{Kb$JWJ +f0VE(I<^;=9BYK7@$|NnU*4xgfi~522q1@s_5&@?)MdOT%l&{3+!Y2y5s{#(WdpY0%UMh<%qu8|rCrO +&VTf^(0AZmgH+&@+p%xWHhkYKW>n4{6XNWghkCJLsB25{cyF<0GlrY +?ghOe^7$}C7lOz0I5A5(Yg{k(IN~zU*;#%<6%(htsqy|38g>}-3~2VmC;AAgLWAm_C`D051wF0SbNj; +gGUOs*jsd{z3|d#4)-U7gRxzf=gtf3>k?46bx}e*--q*k0aaBM6+E4ouXZn#oI`&P=mOO2ML2kc1fNM +A%*M*2)Ix;QwbWi!$ulU;ADhn=Jne0H#=1OL`bDlb`n5J(A6L$5H)S`2bop^x +Yq_HH;y&RA0uo}^u~3}Q9Qi+ +W9zvtd11z!KcBKan)TPxJ{W*IX6>U&Y?5H}WzLN!V*#1eUN`)AcIAOKu@JL(0l33t+zjy$GfErp1;^a +HOWXjz(e$s}+2{hrf(P6t(f}>scjNoz;o3j{1OYP#V_hlq3)Ks0x`jQC20$^kOAV-x?NmxMQx8M(XcNg~l-;5qtd1?oW4a=)z%*NEXBfHL4G7atUtt00cMLn8f+C +JV0&j5yZr{PY-6!6$3ABaBM}{r=?}$c<`xDWdbQu4KGMzH>ehN# +H{PEmBJ$}x;UX|nUaVBFmC2KR{9g9W^ow}KLXsXv;|D%5ti3MXC9=YJf3)6Z`v8?Z77Cjuo&+gM=^5D +5W}a-le+t4)9&2SOZMN&c6gGyz19`gyOfo9$0eUM{8~Zt0sD8juOs)5Ij8eCp&Vxa08mQ<1QY-O00;p +4c~(>DqeJEw0000L0000W0001RX>c!Jc4cm4Z*nhbWNu+EaA9L>VP|DuWMOn+E^v8^k1tCtD$dN$i;q +{ZRZut9Gg9Z`0sv4;0|XQR000O8`*~JVyvGuR9hd+Bc@zTx9RL6TaA|NaUv_0~WN&gWX=H9;FLiWtG& +W>mbYU)Vd9=N0b6m-hCHh^zg55ncDov}qahJ@SsTp<8y?O67YqK}+%jT(|1wm8)0(X2t8Tu6^~LmFr%8WyMc_vtr#_Z>@g!A6C7xcIDr_vU1(Z?|kR +AcivjFV%=}vUbXi16|2^~{nk6{R{S5U-d*?o760;!w^zUV{T2UX?W(ude&;)X{nvl}&-wqEuK4dO-(L +LazpVVJ`>n-?|Hr@lUvc&C-3Pw&o$vhQC%=64op;?6Kl#auAFlXr5dD4I-?x4DJFmR@+KQjN`|9gIX? +y8=KlsiH_rJe+`kSx*V8u({Tk&`QxcG}dTKuISE&l9}R;_g{cz4}9KmJQQyx^Ji$3I^Dl6PKR_ltMdu +K3a7A1}52y6yWbz8ih_d*6TlccA_b)!&i&J63;J)ZbP0ccT8TslV&$?^OMrslOZQ?_B*|sJ~17zJ>tJ +K%glIGzWnuAn1e%UO^AT!3LSqq{k5KawYCb~EN2vJ-H6NkoBh-9 +^nvYQP5o$idr+ob1Qc!4t<|ArTpNIl22(=*6f>;YGT2R%3LXsvZJ`xyp;c|6Ro!E&y2nnHDs(Ks(B+c9hjp2@>rn6HR-fX-~AHOf(;f<|EO3B$|&z^O0yi63s`V%`nksm}oOhv>7J +a3~NvM_*W?iv>?=iNDE>usAxe|3lc4;X+d2JQZ2}|prHl178F`gDnVWIQP+IbH6L}&M_uz#*L>79A9c ++~UGq`beAG1`bGDbNljEzyKdAlxbfZ@DfaQkd!AD{Du>HDDmQ#w^WHhxD6i4?&P%aZ>3bKX*oThqU6f+4+4n9Hi`K97^!@EL;7lO#8MhT!u +w$qk<&_&iH;!)FLS&y(En8G_G?Bqu&)5D_hF5D|n>+W>@Msj +HBI?0WeB53I*$&HpGKVH8-N^=p +?xwlLBNVKjBtPgSxgMheWF$WxUrT_u=?}=~#>Ph=CHVpQ+}Ic!5|ST~&y9_-AszVv`P_=JH +6$ZHAfHB*o9KFQ}+4WCu;Nj}%)5rZoDB%f>Yi9r>7lFv1H#h?m4$>*BFdUrR;y)XTcd}+=2(wZmr?p~66Q@u;>q~@vpa0|%|wK +Z>;ACXI_c_QzU`>A{Lyi3le?)m(1C&|sIRQIFOU7x0r0ZhES0ZiSi?Jjwtx=~vlY9o(P_iDR)j^w^ae +8`j;4^#Jk<1TrFy0`PYLnJqLUWa<9N2gvrACfmV3D_Wop%V`#5gVj1bn?a~AseKgv5(i29EMJerN+=x +1P#>vQsX^R&*6vUdsE|kQ*YuQQ#VUJhabwsQeaZELFSG7<4q*@9DYoZ43e}TUL&~)ypW*%@CM0E;FWm +}Kc)_r!RkgxZmbUJ*AFL1ZWu$h^A&cu$Y8Ff@Eh5}wJi4r{n0j(8-qsj@1yNfo*QK#@%IrCksD8gq~A +y6cXOi#B>X-i|C$>$X=Xws1@CMixwftMqxyKN>$2)x_H)=! +f@y-sC8?_<#cxM;MjoOfVyt9YoMs3JF-q}ZTqc-Fo?;IexQ5$lPcMg%Dbr%7)3MDFptcx#1Hz!8^kwH+)9^jiv{QP$cl~jFViCz#$TNA08xmfv-M8fO#_{@b0#gTn8{A5_li3C%LzakI9uGj +rZ{ul4B-APBJ#uf#Slu{LRKXkj1-8PSRxYLS*soZY8;~4rKA}lJ_!MyfE|D@dk-OEhzzk`p-!0`F)4P +m?Dml+|zq^jO3X1&r49? +PjnyRf%6ON=UKzh8F>Ia26ccntv2Vuj*OS~ggKA?U)88WWx8Ls~xo^Yod)0TKz-ENxrt*2whGnTxR53 +N!$o>6ckmRN~8@azf43pdxX(RXdF}2X-{vzc5{&17z`QneRkO86dfzJAQJR=V)fQgfeNBRM+Rb&`Ya8zlE^e?n{{LHEfml7nqBsyf&n +CAp6lJ{cpq=bWnTBBb>`nIO4m{*y_Pd*(lpbFT==y?f-~QqTWAvc^{$2;kJ@fZEN$#1y*F|# +A{Jn0Hd*<);klZtWub1SW`Fk5l?wP;0iR7O7dz(qFC$=I~o-t +R*LUI2>?HRuda#74Js?S*aru-~I{n@<(BsY6&PfTwB8$OYvx_6%BhEL?E?p-9g;S)Kkd*t~|jw(Wq>K^%glcS1CSSmGlD*#K77TeJ$?lGim^uBi0>_7D6?&*UG-KbPJE{6YHX7@4j6 ++ez;EyuX9wp3nO}GcCiVWLd+dxXnA7`@5)FUU~O-liVxs{vMM1j&Xl4$-N5i?<2YI9{2Z?+$-|_0g`( +~-aklk@7(VnBDwD>_YafYtNDIE$-SEIA0fF{^L^=w!5&P0#(4B-cYojGCVNr%A2{<`^|S_s^1C56m%YdhVYmxgMBf)b!lHNOC=R +#Hi`HKR|LlFvqCrxqpS^dSH$*ufI>8#LVks%9xl81wr3*C-s+o48Smpr*3U$ +*?OqCzd*j+bz$0YdyjoPMuCC2pl0gcynqjyYu>=Q{Bz!=u{rhDVrfKc +L~iZq$Z(_5*T%ridm+5zPZ~f2N2g#ti!bxj$1x6Ju`ufZU(i-56t5{eaw`+1?mqKK+2)pV{FUqpaoux +j$1@6Qiu=0lB}_@QGyo19E?6%VUf@{R47;nc)*T`Um9xGQ%gb^AE`VnLUp&67vtp{h7>sj17+u$o-iu +k1>jF9+3MpMK>{aIzAxxXZAV94V|luvB&Wtg+vXV;*PPu@gW664P6rxV{hX_3WFMYZWp5}=ph9_4L!G +uvDxq;g+2{k6BA>j;X?{~8amSxqsHhVg*&FkC`Lx-A-Ut+*dcN`56KCVv +wnfAf&svB}@W$lp99cWm-EG4eML$sL>gO^p1_L-La*e-k5r^N{?c$=}4t-#jEgY4SHQ@;493Pn!HqjQ +q_*@{=Zi6C;1~ko=^{-^9q@JS0D9@;5Q^HxJ2An*2?S{LMr1lO}%?BY*Rd{G{1-7$bA@ko=^{+{DP-J +S0D9>XTyBCp{!TX|^NAs7!iDe$rGX#mLz_BtL0#HZdxb9+ICll}RzOI1kBBn#!aK@;8q*kX-XwLFVR> +pE}CWq`s`7NyQXERgklJ)J1ZwwhFQ}k9tV1)mA~C=FvuyYqeF7p?S2KvM-PJk=*c!yvw5lBsY8_#wLI!4x#1I8mPbcPZum +rg<F9-St+;S<@EM`uZH_(UG%(Rq>^K9NCrbdltSPvlM>4UpXMiLA+^D>m&#NH%JctZ;~AR +-y%8qA0avTA0;{XA0s*VA168ZpCCE-pCmc>mqTm?dnZ3%Pjb)y=QPk_2j%DOB=`J(-a&HD|L2_~_xyi +Oqak)ve%?)TeL7Ob?3%2gB;&Kw+&Z)Us$$A3DkdvbF$Y0aOqQq8&;>0Ov%R5GnBOviVx`pQLsi?%fog +$1%&8jCgouuVTi(N1tBKd&muDZ#o>fI|@^P<1ynep!QHbwFk2fjAoDrAna+j(pUrXBbp;k#?4tpz82|ow`#HZOvM~;R?+WXtC;Vpn(wKabJJ4unarHvW_B>ujP=*db +h=iU!{f{pv5uY5PfqQ5a%!BgI!z@) +Zt~G}dp4cPFV|ChZk$fe)HCxvnNBp-GjqzFv50zRkEGM-oqEHZh1bxLW4&S8+|ZF=yO7a_*LXUPlJ0Ie|Ehv{j~N8$cTA-jZwTNdl_HZxu;GhQn*UMn+RD>J9zWEDN$W+K+Qbo|LBI8T9-~}%>J6uM%6H6yh3KS9Aq +`awDAg=@d}xlF=jPAK4c~u%gkXJnK}43Gi&mhImb7v8?Bm?d^2;VM3$PKVV1G8)L4f(!y!ved(5f0ne +jkbWpR=C_m#<> +{-b2A3!=8XEhZrW^eX1OW#$uphD%1!Z1o@pD)P3|pknD!W3%^TV$|!rWcCj#4Qka_`ikkT?bN540GY3SQXsR#=nH8zF$ +ikG?6{(J<3R6^9U{Cmyv8_)=i77t6iN@gNJ$AJ)_FWkJE{wk}jK3~SA1F+bR$=^gVdAAC*W*VaGG(I| +pNt+LEGO(ZVdfZa(=STndrQ1?$FB8G-;FF0V!VI@q23q{|HNu +dzn;d#Fxt_2@V(w7|V?AJUSYvgG*+6c$y=EU9?$4c8{0x9@RP}l`qRd?GC}lqWm}m&o!;1%nf{dx^BGgoS>}3R2-=LV2-?iVHfTf1> +h!g|y67cNr{klx}F^!5}mNVGx>`QV^O$C!9!+2~K2AvkNQs +7c2G`O&%r)&7>*_&0S4SWWUQ~R)f%-JrIN@CmMt%DH=RGqD6hmzGG&%56v*^MD`a=owXC$Uo=B)5Sln +K2<=INk(os}QR{TTG9REC1?J77z_!`UuARtOdz9!-9z^!6-pE{C6+~tUI(W9}m_LO|)v3O#jMRY)GLi +xrVk84H%m{jT-!(>ZAbc`?Y0NvezN=~`V@_mG);8(AplS)KmZsY!_7@W~tql@0uMLtw2b)0>+V3*UZ9 +!t{6oRCpL-!!DP9rh#h!fd%C8nz+=7K&avcG7}E-{G|Co%_uCT6KONK9VBiHr#+=6V??GM}nh4b|F=q +1WskD7D39-G640X==@sx;T+-gjH=VF~(F|;*@QZ3l3`bc-@-qxpn*6^}u-Nx@}iI)brAyZpvqz$bQNi +W<55>RJV60)T@Toy16ydiA;y7TfXY4ZI|V%Zr0s`x(z$(HaM!A$ySh>Ix#1*pR!R&YP)D^y?JUjZw9H +km@!E0xSm>{pIVoeriS~}@|D^VJG1tc#fFK@MoXD}$IR+6D~;>O%neLIBQmjOBQj^UKFfgfr?~ixM*r +nKax`dQ?=78v-~b$#aE$?+m%xWf*RWxK0|VIG+|B^DGH+r4d-G2+;3I%<4{T<{IIsrx>rQoSXCwf!gO +L!(PDUaiyBLXq>}I3_WDg@%AbS}}fb3%g;ZMhYMiBmV9AE_DPsc$<5dL%=Vx-jsr-AA0R7XD}1&||*l +tAc|>qaW{bBqz8pW}=O{hVM#=!cCa75X{Fh|tezMudLOFe3DGmJy+!bBqZ6oM%Mn=K>=_KNlGh`nkl2 +(9ZxPLO+)o5&EH1vm2Sv&s9c*eg+v4`eBEX3H=N+BJ{()CKLL(&WO;@4Mv21ZZabDbBht7pAklcenuH +F<8dPs^iMD%=x48)3Hs@1?uMPKHf%PdVIzfxS+x%Aa#xUJFU?ApV3RXT0Z!yKz}AC}HEbo=Si@JPtO)bAF_P +OgRFxbhb}Qw)ymSZjiZ;;%T<#WFNY(T=xjj;)LXq=+Un)3>%-Cx9Wmwra|uR{@boU<3xnwNWtQW%r9B +Mb*5s=U;MOH}0SHWAV>!+v=lfa^mEJwh06G@i9b!_xbt*?l%GXroh)C~VU;qKn^)W^~-yd@aLOgnRfB +}R%H@X<{`uJGAI~?gC15gO_j#!lM#mW(i-et2yBzl+ooH!x=Is?$iEe5cO +B-9|WZVd_>NBlfzeK=8bS5zBKRc;3Z`M?_tF990;EGF=BZR1kVQ;u{;NY=R=HGo&&*iKO>gsK=6 +E&5zBKZc=n#3Lbv5TCDyfIi5tL#KKTwXdmnZY3Vrf5VD?JvA{6@Mo51X?*hMJx$(MoIi?NGP=#%dQGp +9f}k#dJV%1(5k@S}k>GiZ5zBKVc +%ERy@*D}CZ!=!n3#_6IAC_kI1zXr;c>v!lsOT29^rAo +-iuveE;=>B}A7pX8&w&(=HOhGy7+Ip>~l7p4mU!JGF~M@XY?%UaMUsf@ +k*6_GaxO5j?Yhw$t7s5j?YhHfQlVk>HvAvpJ5}i3HE=pY5VXK5=64)F_Vw=FH-tz@f!cqdX3n18STI`W)qP!0cEK +3b7PA%Hx1J*2am1K6xB4`<$If=#$3*dj)ra)yS0}PcnklF6BVY?nfu`um0}44A?gpi;ScE#r6{ILaZ* +1vS+sUXcuBxag;r?y-K?f>xrZ6na!RBClVIMp4prQ>_o!C*fX2sfSpKK7<*=W(RLx$3`f~Bn?r$vLM# +@JvS&8u0y~l5nLV@FFBBAFd2p0Hv)KseM1p7b%;tb#ClWlfXSUaE7h)xFls&UKCfJDt&+M7)_1uM692 +{lOYz_+!o^2F)_Te%1%ni$P13ZthXEqlnIT3gsH?ad(I2NGyCTyt~xIS&+MOz)-__wdz`KNX`xRZ2kgb_MJe>jqJ7IJPw$nkDW;9lg9yjJ9| ++IeeyV94nTGyp-&zM?CtD@C@3G}abSsjx2Pl^<8i?56)8%=Gmiu2Fyx>VS?Tfhj9}e#d;=qB`gl7d2* +1WV7{MEicQS%%m+>w}@FL^gjG)?OoQ93kRxX!zXF@4*#N*o;5pTVN5%JbL84+*2ixKhGyBQI0y@wG~y +jt3V=2B!u$B!{0-rzVR;tftPBHrL6BjOECF(TgJG$TT9XBZKBBllR^YV1-ZB*(8ZB3^Nj5%G#cjEGkp +W<c0;Dd5TlH@YYa85;~g49x +H8@eOh#_u@*dRd+JGXh@h;syL`dV^8be9Rc#paz6mw1e7Op1yxog=W-@P1hsL^>1UMCH=2Zt_n~6ruG)?jM6Og9g{RW +GAZPk?Zasg9(EMw;U|ytrlP9ul|YwofCESm=p(?12<=%`}v0NWh=|tWvVwU>|zQS^E6IGUbk`k7C4^(5h_dj) +(r_X8oTb6t0Gu^wB5pPvLWVv@eFR|QP*7sQMWAn8nueBfpM80+J@YkApo3PDX*BshI;=;Dl__6?l_sV~GnUMg9| +9y(SmiJ6>*8*=Zs%73@es@(NI*N9p%JbKF`5)e5qzdF0jCgyY{kaY>LrH*z&EH*pjS+7xue01+%Ns2B +KJNXq1YqCHDwcbGRa?jCwry20BlZs1@LlbToeX$#zu3iqH$(0XkqHh1tkS-=^4E-DRi`C`tRT=YrvSP7JK38L`A={#`Tz;p*d;&DFSSf#xplS>6j}82*6o3u<8u40N^-Bi4 +%9I>?_|-R60VpI9h=O3u+#9h3uH;@rzgFLr0Z@on4i6XA7Jd_J-`pXw^39$vX`zjzl{S)=+DKYyBWa< +Hq?I<3mfA>KYa?l;jl^n$!oKA_hTi>tE3GFjwVt%ldeTDcNeitfEwpY<-*X~;?|>88xC`^RHscG;al} +rf6VqW}jx7vB#0Jy77u`Pwa|R7=0)-HP!gTLt_m9A+jX{H(LGIaU%n^&0_*7v&Wr|9|2=!+(>w6hVfo +x<%mpq1Xs1Jz^V^cuoMEZ=XFoyRHSnm6K$a3G^BbNKl9<$u{^$N>WwiZs#o^r7^rvlrWmM~_T4W~Z;fG)UmI>W;P-~#4fw_3cLRQNxZRN7e9`Sb%ZLQuW5mxPUkVlC +=a4T&3h`;rmtuwZyyr_5h4{qhOI3yVyw6LCLNbrk6yh^KFVz)-+)vP^paPI)p#-F8sI>u7Jk$a}iipK +z++QHY#G-fo3#6#12{kD$YS2@l$f!Y4fnuWuO@)e$no0S`b+EdUKU@c^KUD}weOV#k^c6-@Q2MGu!02 +lV0imxe1bn`s5YV|wAz<@o3IUmKDg<1vRtT(qjY44cYZU^ke@h{-`nMGV{rp@Z(9b&xfqvdq2=ud#kq +r9zg+id8Un&Ip`ISPTpI<8k`uUAQpr79=#19fHS-#}Kc24dGiI-XK2MHf0Q6#v$w*a``_hZqk+%C-PS +?))o*IDidqBmIX$Dvg$_rnm)u48O-dz0mU5L(T0KL)K~xgUbovV7^ZO5E_lz}qbMdj2`fy`JA;x!3c% +Ecbd|$8yjAFIc|hiFQuz`TrHmJ^#OEx##~kEcg6VVlB>>UT}zW&p+FBzH|aPMwQAdEcg7gz2%;Nwm0m +9V|&Xz|7>sh(%GCiUpku;=Syb|V-&WqyXl>NYU((3)40=C$lrJ)Eu7`rU*HI@Pi*R;+^{s6;K7B5R +;)!VGz?7`+t9nG1HvPTv_2nx~3(x_XRtV`RN7@Rhzzgl@Zi&TpeU21Tw@3swb`vGlJ5ItJfH*0J+WxY +A3GVU19FRzI*<`YQXr#@V7>P07$Z>G)p15Jv%fmQNC9M$5lro`-X;W#O4Ebu8G(KVH!uSI +47M`@{S0<60{skjG6MY!b}<6|40baD{S5Xn0{sm3G6MY!Ze#@d8QjDO^fS1b5$I=d3nS3a;8sSUpTTX +6KtF?hgoHvr+Zhr1*};g=&rU{!es(b;^s}20p&!2)isHZJy$8azK-j@vZkw=!eT)b@*w2Wtg9D5RJ2= +RQu!BR42s=2;h_HixMuZ(4VZ>BdG;9r7!`zS(Hmbr*jx%CXZVe=-?p|a7TM4eSYe9bMt^&;3X`?EXa* +h$RZr-R0ZCqf)1ntgd<+Ldm!y}m&YBXo$hj1L)z&s_a}3(lM{)~$3v7k*vW}R-{KL-4 +!8|uw|~ZR|DwNSxqr_)?DLV{zRNBjWmMBc?DEaYN};%GWqOExy*XJa6xXaw53!RsCmx03>Xqps_U`7y +qfp$sGCjm@-JE#jMCjo|?9a`KM^1#UJ;aXOoOl#Au%l~wh&{MD@hB9Rzf2FYZJHC0LUHTM^bq@ObK+4 +bu6vmt+QW$Dxh{BS@3}-ER~J09+cY+I=^^%)b<1;I@XU^~Zh5W?o{utOc}@k->=n)VN};&DWqR +l&BbMh>@O+vP%X2DtKFf&ZITbvgXT*#Vst;L+ +l{U`AVUy9OiMroa5v~(C09Z1Lhp3u#pR%c^ok3I60Bvna2Thj*}A!o_QQF=Quf$;F-q(bB>b}37&Z +zFy}Zqk>Huf0dtO%6A7Mq9I!cxMlN{faloA86gCRMGmiu29499dJo7kU&T(=gM5MzUkrp;0EifY(zRZ +YOjc=5q8SEy_@k35zGByo!I#8I0CS#M^a>m^5Z^_Nc)#0-Xm-th5rt<8{4*nE^?CI;Ii`*{z=jMu4Co +<~I>te) +=-_9E_&MA+qg{+tfox$U0dkCy8jw*&{7iYaQ?<=|+1aksjATGAGUA=>Y2eX?FaofBd%x)fG#M{B_mOYGAfE;9`3gi|e-VSEBZeXMaiDb@n=n6 +szOG6Maa9%cmk8Q9JU^mCcL3-XJzS6HXWFV0@=V+8uSdV&$?=c@WP=x6XUBhb&_AS2MvO}1;4_|4vAP +l5Wr*_-@r*e5W1bCMD0XJm*G=x2nz7`6<|j<6TQrh(b9Ym7iYW2yzv&-i9WpdUIuFBIpz%}%hkan9Q; +9l;ffbKYhr*mkgkVD@$gBVIpqz1*{K&f8os_eSh4nA^zjgY5-#yRR@30Ab#6H^toE9!4S{+=FpN#oXR +gj8uTIPsB|XbNkr)V?V>(KDIaPXqY>6l@YI>xx>sGPNbVV%p*EZq?@~>THy6FH^6-kC(_LgsP}<>F0- +~{5y!B8u)SjL%5FxWpDWxOv9)4Oc?#^Sn7gXJ4f^4A&Juennj6@|Vn;=@_W~mc5Vl0@rfBw#F;WM@Js +3MFnj6{Pu#cj-k)tQ!-P`laUe#B>_q-kTiGkc#0C<=B_i0c>OeY?`I?e!s8qkN +}9XbMp4S!+|3q`V&3Lnwkj;8H23ag1o}C^n!}1p^S}fn(9c00OR%ugJjgmlRd4f>{0fh`ex`RC +iYZ(}8DzK0`K?B}2FVb6yJsreq&f(npvM!bIJd-+YVuYbOm`vR7y=6l&wU{A>WM($ZyrJCQ!aTWIW&u +?Tqz+%<>fvt={KL>bRz?#+kagJ_r2I2g1ep4)6%^zp3vE6_EH1|fVV9lT5K8G#;^Jm#Yu#h!>j@yP^| +MOg%BTgNh=Xw!w=HUDw&lj+)H9yFVV)y?%FH4Ej2IsGHjD!;a=C8Al$0FDK4YoI&GdMrW`oUV){22FB +LOb4f?sru^ +6hl7cQ!{K|hyx_JPXog-bk}!qV5mCHB3j?q0aW;{sN|76v%;g9`73VQv8y!WKrjH=@dWLCuh{9=0&5z +76^r@Bf!wlKk?OfFK*%mEhA7AD!dpbC6pQngL5uS6{lMRRh!?&L(im4Clv^trq@X}I6l%Sw?rTx^B +{xTwus^%=E^j0@#59U?N8os1ykvbBp5y@(LC#rkj?C$cn}#h|Fo6sSbcj_P@)mznkh3^fFFkRiPe=0x +67XWDyb+BXqs7B`}{Qha`Rrb8w;qGzY@{40y}a%~hu_EX5wO?9qk1SczYZeRq(e%%)s!KuNW?Tp~e;O +|+G_>Dl7o<53RaI;^dR5t}~24KV4@TfquHqF#`Q`oo594>Ea$ +NuEwBszet>RGS$UBSlp1&#XVSDP1|*u5$LDu3M0@@7xzv8iW-V>`l#aBRmI5sr;JXd?8(9aLO*+sR!j5sr;Jj=1%pX9ytqE3%Uhxh9;4;G%)_-nbWpZyaYacFcXx3?Ne^=%t|#eYyB3#* +bnRzEI8e5DabZX|hZ*9!kZuk$#APAf+=IncA>BMAiHkzIc}No1gmkla5toE?b1xBBgmm*TW{%*D?CCI +0B#Kp4uwSm>rz-Oux^D_1lFUls= +ziXED_jdh1CSMMPYS;ZBtk(us(%l0^6>zhQM|xEEm{Lh2i9fZ?9@S;OvJ#w(n;wfd2lt`IN$-zd!bmD +-8PkN$l$e}5XerZDL5-#Zj%0rdCpTa?}d +=P4dr1*@Wzi +-A)Dh&FY-Jv>B1pUnpoKYC`*F2=Z6Z-pRT-gh*0eESCSPdMJ(BD>N^KqMacV+Xj=zrtNCSvGsz6ZLcc +z1bk1aWPML>q=x`%vPxr;iatQ6uAwpoEG_wH$(_|I6yzbY*uGBE#|ax>p&&L3IHGI0`>x00*Q+4B%kb +m;wKKVTA!4=~`vLzg(Cw;3LGa#()nH!#V>#K8)HJ@Zn*k-W&(GsyFw+VWi&N$A*!5a~~Q;{N^I1!HL; +Wq%=6m8j6$#Crm?;(%`gcSQRtpZbrn+nJr6ZHa0TSDkT_JTcrd;k=fwHUnnvgocIexW`h%dp~!4-;x8 +1L4Nm-p$ZWV6juEdO7r_yN%!Z5K81edX(HkRPKQ4A-#OvpeRJP|3uOAn;G2->(qBcgneq7APh}Vyc*c +kEpaq$`>UOz5cW5nyn#cGUr{kTYtkp^0D>)YXD40;v0Ans57>PfGqKOMZvh~C)~g-C3uNoU<6gKhgm;JOt{#K5e%u@w=#mNy^SM`pc;rae>((2>S@*j5)&@ +sVg!kavuw~vOf+}jWCT^?wBx`bSS{aho)IJ_Iu&n7Ol;+jBX(B}vrbVBv~Y|E3{(SsGtSyZHPCGL0Y* +@jJaCc`kvQ7K2zEzt5-3bX|KULf)j+d1&oBc0^f9BT2AbV6#t7D!FYjXn`WaBaBdT82o95un_frZzm- +jjp*8)_94fBAGsv{R`6N0Lv*;9uZ@pce#XojMrh(j~f97PZXWH4n;`GUx_A89kfDAH%W7gZ*zhWoOgYoRV;Rn-oa +x{=6{l0qbZ|_A^C@RKcq)YxDrY))DupvDXF7OVgd+-OI`%LE{d90lgOdtoIyh1;pr4K-j6gpf93_@QK +Ri0jD}{cz&y_+y+~-Q6AMSId&=2>yQs{>xu~O)Vdt)i|!@aQ-`r&C_DfGkrv=sW`ep(9saKu*% +{csO1g?@$@!SdDEso9-mj8U6(aWfq|W;(`|^+#<1edMCErkVER{E?78GCb39P +(&h+$K+OP6QD)f%WUq^v3jmtBfy@-N9f=|`uf1HBi<-IJ%wL +@5*d&AR9@DN1QRL9nO^O2aoWq`p7gU&1$b;3Fgs_Ii6$Dj2jZRkOwKX_#c{RYAcm;AZ9!54pvRU8OIy +z=zb+s7BpO}$`7pBh+f_g4Ohu*kv1PQex*2y+}>x@d`xzIuc>QS;7n@5T3ESjH7JL5^T`LrJ +d2`Vij2j7s$&4-GM#4zrteQ}aHtUz*A{1jzj|z>VPz*a8%`1wL$LKN?gU==vAINKRI<_rgPU5nhj=n_ +`=ww`sMT=v=(r?~SpVHqVMyKtI_$Bj-Vwl?fbR=+Y*!GdIq)!=Jh}sBeq9PGn>@rV^@Z#w(_T0Pj7!X +DDsgp~LijlB_y-&ZwnOpl=^K%vXMIB~H5{331`=7pO`|Tyh#z@%3K@}cHQB8tRJ@HP)>+&a_c1>1Vl+ +yENFpDz6ooU=+To}cHMf^77!YKNm58R4NVHAfKo2YPM4u>>_ahv@9r(e#xj01YukQ;{@SKlve;mC8># +11r$)kpMCR3!e?-1-==5`Iz5+oCuje0b5doiz;=hH&(`Cs!Y-%a5q2BTgB7-iOt@2y-~Dqk_1>;1X^u +&gDmz93f+24yTrg(Be|#{Cq`N#F=LSDdWN-E`S0LKwwT5d>CJ+nTCNi(4@bED@+wX=gq78rfxKet2EuhFLd%O+UTAp1x@ +%~7!OH6!Z?9y;Ywr~Wc-5`^1p{7hKVA6_BVKE7`8*&^S(f)!Bi8_L8gIYME%SEq${Gf0076D^L(`gw5 +pNKy6f;=-7#W`H-OeD^5BCq~TtOmgXV0pYpa{#mbNjc=(P6m`#6s1v3$x>W-;5trD}51Ge3kC8;Ja1< +-Y7m9R|u}HR)DvPHx=NG;?18j;FYyT0bW_JD*%^itz6BBSJs;b^4fa)H3q!4)+(TZw^s3c( +#r3cy{o3h-KaQvtY*R&{$U@4l=NuNMu3_!Whqwg``L;Ptdt0ag5nLZBxFp +!+DG-s%Z>wO=s+uciRu)fD2@vqk~vJ_;yXJRz>}yT>yNEWxhP$Wx{KD~r4~e)@9;&~12t$ExegY++b) +ec4%%^1znHczyj-Pegf;OtNF* +Vyt?`=rU}cdtAEXaSBDzHvADWs6%PnlTwU{)LVWLd`wa&4iPug9$Mv>1Bq|{^Bq|{^Bq|{^Bq|{^Bq|{^Bq|{^Bq|{^Br +72_CMzK{CMzK{CT`z0n7Dx3VB!vLgNbXnHHJ0az_5Z<-N4X-b=|=5f|cFS@Pf77(C~uQ-O%uY_1)0$f +)(D-@Pako(C~s)-q7-b#EIp_>o2stU;=J=K|Q+V1tsa07q7q9d7crs>8*(Ma`8u`<4W7ir_#7)J{6n2 +r)g}4sx&r-)u+|PWd!h}YEjOe=Rv|157?_k98ToF9)V#M-X5j^i<#PVDbJnv(~@>~%-A7I +4tToF7UV#M-X5j^)ZVtK9zo{utOd9Dhck27Ly23$rIp)s0v|1PXWS3*^ymKO=FU4O +5HtKj=2!Ui9FBuH!tg3F;iaJ$#Z8!)1b^hHnp_X2furporn +y!)F;WTqdYn`1bI5Mhuq;>J+{`e322uWrA{qZx0VJVr(`+;la0uuP|b4Hi-mv!;Dz!B0=4CMl5xapzb +Ckmbyq#H^PXeE)vv@F=DBU1a%XPSn47{-EBrJbt01M-@u6J?um%x`a2l0Jc~%Kzl#yevxwySdl<1ii% +712BO{h)5y|y$X2kL=BDwypj98vUB-h`^h~-&Ca&$VaLoClClI!2ah~-&Ca{YT4u{?`Nu74jRmS+*k^ +&eow@+=~`{zHsdo<$_r-_MBUSwwREM;Wm^i%72jI3t#45y|zl%e9eQA|kndcDXi^OGG5s&o0+Sa*2rK +`q|~$NG=hPTtB;98_6XilIv%eYa_Wt?91)}_qZoQPog2zy&|akCSFrz2Y!u{> +ch^Dg(1nhA>%5osP_muMr+L`0fL*d^LXGZDe#5%zL6cuYj_c!a&24IUE_JRV^$XM@KC!Q;0_*vpv%YM +lsnbcDT}xiC3RQb8SiIdfsM6A9|r%b5$4ok-XbYtUSn>_mcR_HyRJWG50lvzId$COeVm`R*A8yfxlE% +z!8Ru3`ixd5HnsFMojnOE{JVKR&{M&!K+IJqgQ#AG7YUD){XHTXH72=g8DtqU=P1dyY)aCCW}DxM%le +E>U(OVdET`noE?ONZ2?>rsfi5ClWT!zS3N7oF)x@e6kb4l8>@SG*=iqkzjK-BR&uPaXSMzY2(H=MiTs +TFC+SN7bn6nbaWFVb{JY*(Bs!cp8VURHyEj!=aB$^pSvKk-yg7oN=9X9|(sziawb*_tF|* +y8xsNohHS`(mPGp9NT5f;Q+<=E4W_>^&7S({-Z3+#6V_Pav!j#;?Gr +%YeZLi>xh=VW5O!x@+i^|s$$y2%KJr;7~u<)Uu~b`nyxzllyg*hp3q6@QQFz-iMsP0Dw&RT8 +T%>I$7{R$n+fFiqbCI^4Vg%Im7{R$n+gL~9MB#0 +h7{N?p+W;dtNp{<1MsT9=Hkt%w;zZ$XR~Zrd8DvE0XNVD@pJ7IXey%Yh^mCmNp`RO!2>r0pi<5r0-C{ +)OXM_=RZnU^wYP25$LC{oe}7#uY(cjr>~O{=%=rX5$ +LC{n-S=zuZI!nhpr)Z2=vppkrC*pZxbWXPv2%npr5`ij6gqq?9niTn(Eue2=voOnYt_$`q|Ei(9aG=g +nronrb0iv7!mr}&4|#?9!7+I_A(;$vyTy>pZ$yo{TyIK=;t6KLO+KX5&AjIh|o_zBSJq%7!mrRxo?&U +{qXpc3H|W+lL`Is_>&3!`04p2|M6%E)#aZ<6I` +}fXBH^*a44onXm&M=Q3djJkDjp4tShv*vO_Zk!og6?sg&*sWwb(oIlHY{40qv`zcc{lRuli@ux~1MU{ +1&+Au%Z%LtBNJ9Lc^T*gS33OWRrF%GM5!==M?A(%tN@m@oW;L>igJBQ%1GP+vJAvnbAwE7)fN!Ftf>| +>#mQyqevcw;pG%#LKtH|eO>rgW{sBU8V!*;J +ep8$hFnfJFBRHh+_C-d}OZIJL1Xq@xKE(+1vw0&UIHa&=KO?xZ^w?2Ga0s62!Px#kcX%HoI7IR~YYto +fo14cO!6Aio%z#612;O8jBRHh6`z9keoNhB~0lWC;cCogxg@5kiE=F+mb1!QSd-j`$&N2f1&=tlG!Ii +T6`F*fXKYxII6*l9~-@L*Iu3S9KJ`sEH7Y5Wb=s!2vQ()hH^TZZLa7f{4_B+^X-@I{*5gZ=a$CilA^$ +S~A3s}nM3n?7}|8{XNBRE9zBwH``(a(;n-@(<~gWPItoS)w?$q4-0#uJRdzn$b>f@S{sVfIMa7r$_bt +rx5PvnRQqVk7+QplSg&QJ!P##pd_9n+F-e_Q`SY{beK>FYir0t_2w9uCa}x)S!8b*}+cw1*&Rw2<%|< +PDbEI+to8T?6rsc9Jb5P_HbW7{Xz4bdIolIgKY<82=jeBW?^Ie!T}zcP>nEuc|9ZE4(2xVxLpx;!1jh +D-TXz>Hg5;t+!|p7e)J0aH)ger#SHSUer{XR=M4u>EPInE;!cD^?c^N4|(hWQ=rN3rXD{sND*$a&6PW* +?87Ctup_5X2!v9K#~#Ik%Z@2RYCA%^Z0n=Q+2HJuGsb%{}TF=x6jYBZxz~Sf|K&&h{`n$a&74U`CPiY +!0(sBj-84kv&o(`VU(?a-Iu4+#3@Shj8CU&U1DL_fzCN`5M``~Kst*UMCZ@p{>;;`{e>f4~7ojeCb&a>IU9tkgl} +XahOVIXb@9A-Jt@RB;VE=urAWKfR*bhJJdL<0|Al=k{`b2RTn2bzPouX1^VLx+#N}1VB>?yDdey*SW0Sad3j`FzO5cv>}L$DEkZj^f?cEUHes}`W2?qM +xpFMRVj=aCv>p2g!FcEc~Icof^=`7#oRV4Uk^-mo8j;Q(7NHpI`a=eA)-{OmP;AMA*qyUCs$JL2cZ6@ +oZX^+xQBpX=gW0Cud;9pfr%?TcvuvLBjB)=(ks?VQfFNTfk3u?ZNZR!h$R14tWwy>|l +7WKJN9s#jIeRg|4Bj`Up+)J=Iy}6UUKendN@8jRW#`J}Q9PeOT`obXF8#bjcjB+LmThix_sAu5cZnDL +fBHzn)fX(R5i|QG#pXMc=wV@QRImGiHY(sBu=2#h<(3|btx3L9%en2sbeD9e0Hq3*!@Jb3cpU?J^7E- +KQ&Gz;%0{=F8h7t6i&AS*u96!955%{;uU5tqJk|T`3za6~Bi12S?jG+G<-@=HQ=Hh3KljGR1P$?D2z)=#n-k0`CWyk3d((I*~}vF|A@5YRd&iVD^VLF#@wcwdA4V@$%lZ< +6403by~d-y4UH`jG%j+>0<=LQ$HIjmb;omlZ?RqO-?YP3sK876zh;X8L^7R0@wV-TZ~x6V*P4iz4|cX +wGM8e*#hcB7&J~^VFZ)cljDqlwNvL9@vJS}VAI4R)ItaA4U13<2PsvNViD?_$;*tuDRrD>1e4b;?%r6 +0n&kz*6pK)^bm5>w(0jJ-UcWr`We(5vqBbZ4`@8O)i{B#p2Lu3dd5hICPr58y1K7wmpYn@!<^jA +1n?v&#)a}aj1Eotrv?!%?leC!RT{EJ%iDQ({?EqhnlyT9V`wtN7U~?KND=DSR9&P&sK%Sq4}NxMvy$E +n++X;C59aoc&1nanm@(*!4lB?8P*S$faWjp`(O!Z{t~wUOF;9(9Hd|gX#N`84wiuCCvPx<)QcKkumrR +~_wYFcq1Dmbj9`>G#(^4^faW$_XT)pa&!;wXcf=adeD8inbfH+8Vu|OQTO7b*iD$N(e>)MQ2zx^;@q9 +DMc8evR*)FyxEb+{Ct7kBZZ04{VOFXliIaC)*JZj_-OFVn_FoIEpQ&Oooa#;D}L@dFv2Nj16pBQEYQ} +@%Ij9}_ +tZQsu9JH;mXh>{E$*%DI>re4p&HA@QW7VkQ*m!?A8Q*+Npsr|GlFPh2Qw;`lJ>AS6iZ2J!XxjkWv`2+ +q`AX~7(q0lVty`2?mXhYiR12V=F`j5*DM`mBSW0TH=lB^*Njhr5Qd08>N6B? +jvd5lJEG2E?`2d!Z^n4LZNzEODj3AoW&9N|+lA61DM8Hx~b3gkxEG0GfA7up5gqpF6rKCfw1uP{sxg0 +meQc|;DeH)^Qe)i;8N@||ugoC)Z_JU#u`cW~ESV|gH>_9))`F*gIq~j(mB{j!6-o#Rpj)AbmGryJlDV +BKV``ALT#53Q|{Rc}t^QXC&V2Nk`3|k16cyx4xC7$^U9A{vON6((($;+en5o +*YX&IzGb^&-`V!DlGBn_)IMEjPv`*B_8(IVu`1lM?xLb{(Ry%s{v~~4&w0)i#&fm*~g)kxK#EsyDO~ne6yYhb*%D +yv!0Cvt2}=`y^-fQSmycj>0zEoV4dgBXH|j{3q1=H9gILrlN=?OoycW|{P1h9G2oebZ50Eam)BM^5aF +xTBQX&5jHl(@bqwfj8fAuJ{m-s_c7vfgSsUz}jTYyFGu40x@7v5Nt(l`nQP;C1rFAqKogzBtT)*T)zA40x8lILCnR1 +YcZW;AtO-o&tZ%a_^_UIRC{4hB5)QGt>a|`M7>ACr~|EfKRQ1Qb05YWXO*?Xq+J^XorTNqMaIol6Gkb +YTB(KC~A*}psKwZvOO31(J%JvPkU$OOj<$E)jRIb`3OVM$Bxf$Gn5N0DyR@FC`j +}h{sg<@GW~x6UGdwy~o^O2PHIKZDb37jM&5bE#hG6w8jxP{fe$57e`0;Bt +8B_y(&FVzS&(}SVIW9rX&({?FXDICXx{Cp?(62W#0J1qYK~2qL^@Jz;%QIh|WvBt@>pct=fFARUqT0n +h#jK&I#X+7o3MQBL<`LI~STsR3yec4O4|Of>b(udDseDa6zR@Ol`0^4%fq(`W5)?h|Vkm0Wh(?=utH+ +xd@&U@1=X5iw0#eOL1az698X(=mx`0#*QvsM4ifK&@}0jU(~A?i`Syr_N)b1?_0---pKehd1#q+S_wu`e&_--5mdbPJ)c0sR#8by+`^3P?2r`ns&Y5c(R{UziI>e +PJOWRwd?VUk*@I}(AQ1<6!djVL(tcV +ZanlgqMw4kM)gzB*QkEV>+A6b{Zt_!^;g$bL3xTj#k<{<7Vn6N!=SIxkL?Qy&Qt%`u +uZ&d}Pe#`6YaR-l*4NTk|#6)4@<{;I=R6vTT4AA3Fh8hA=@0ANky;mV1^5AVU=a4KXCbZ>49{<2b0Bp_+i$1gbJD9Uk=YGEND)xuIh+`>deqpF3 +d+;I@M5V;Q%`J#5mLEJ(V@0g$%)jK9AMmog>#j1sgfK&^iFC8zVg2zFsg{gp43o`+!7Q*}Lup$xBjeZ +K=R|g2F<1qmO3VBR`fJz=So+FQB#&guznDHD*CNrL+%Ek=8$UvFl7j-sf_^pZ2QoolRE!A(q`*thasL +9ciZKEbeOZ|mnv{bfHlcS}2FF9KBd!gXQxN}t8xUq;Ek3^b-xbdjDF~by!ZXCpouZz)AHNGxJOWk +sG1@Dp=yTkhx#e#OV8<1h-2pTsKhaIdSpn=oIVw^bNv+br6=yGoSmzu|Fm=RY2;upf5eAM`4bc)1xxSOx!auJJ&6QzVyUB6SH&u6!fJh?wOpOtEc +4boEzT|vvb8pL(b0mw@{*Err-@RJI7NL_b%@(JFW?0oK#Gp8pzDdQ4VBg<_(brP$PLmWC1iu7$@}{8z +n(zj*XfiGsi|zkQwAr733iHPbdpA6KT{1nTa&&g3Lr3bwOtQM_rJa5@W5!Oo>q$WTwO@4KkqtN`oB4u +Z-mzGw(%dkeT;lZO1_z8suVDslPB2kb19%fYf_IUpi{Qs*r=!d%^GO;kpp34(cgv1~;QC)|1TWTF7~W +dPM8hLJs1{poK#Q2@9IGV>sJm^Pr>hA(@&)WQctzcvK6hfY=!J!3TmCTDP$bV&A@7zF@RFUq53K +Guk~h1j}77u)N^{Qe>qT3>ak7y%bO}16^mmxRWyo?;%)>}Pt^pZo`Tj!^;4;U)KeHOhxJop?Lj|<(ej +#pst}NR3cYNApUTDB-T*(9i?zK=`l(Pr>M8WH%lfHUK$-lbDj@ZgjBeCZH36xoL@#64my5N%Yw +Y@Rv9@=OU0*KN_Efjc#oFFAo`L0UG8$D7^mSc9Vr}n+hM+HH^SM~tQ#PNAwY?GjRQ&xGHlK^Ny;1#C_ +4_SsJ`bc(aHSVPoNK#ny;y$+jW4N*^tbF3liDRGW9L_Hik4rk$IOc{k2sBnLn&7ErQHog|aXDNlLOP*HFm_WS +3HrWe|o0O10?Ul*60$r4=fMP)}9^0{J&lcli`l+=}Iz>#nuxYh@f^o|G1?^;xBWLD5&0Ly*==l_CTEw +P5Im8KcC4$_-;fi5%%QeF-@N{bWoazlvBQ88O{KERu}audNZtuae04*gTmMs2Y?JsJ>RLiCBi@(uZM4 +E`I0+)u>oNg8_luTCoJuY4j-;K{{PuVWXC79p+VNNkhMiT)8m)Q>Y7HxNp_vqP}($b-ApsRV*NTaSZ7p2Wb<+t_nh +a_XuIYvaMOxDmPl*=|id32WT8g=s_r@#Nh*C5{H$S5^j|M4`szH%rw;&}`HAqEp${l6WdFtMr@u;P~R +wls>SQ8D>E-;gBSND%BCGD#QW%Qd0Y`o~n{6!5ClT%BdNdFjpHe_1dfV5zuL6K@TzuAah@SY8kSTOI| +kf|W^Buz^6Z?bn*pxI!Vi~9cSk2`rIMlN!e?H0tVpLM-hEl}jhTRONy7v|(5bGbM!e|d#rNkFGwOUp( +6a?r)Vy`1vRDTmpq^Q}4Zn2&qi)1CUu>+-jiw_MC-MgUhlfB6;9e`>|jUz+*nE;MnWLF81V%BgysH4Ut+m`(eJT5c`pB7EU!J6|8JJp@%pN*DUdykW +I(nv(g3o9ksQb-MhYOC87YC#2Al#F$203UG9s*Q3nMszVEtA`A|TrsiGlPnQZbegd+LA0a!>t_S?+gL +ulRizBl*(CP-)TCDlNNO#dftyi!N31md0JM(xOXM{OfWTM5aVrf5pEr+j7PGJ+|eF_iJp+i%F9uY`G$ +AO#QO3F}7t?-Tn>9Q3?B>SnjJ}|1-;d1?=Cl+}qaQvD|Cnzp~tG;U8Jp?t%|7&^>h1 +d9{*3{z=l{TRujl{BaLNf){s$PqZ(L>ozj1{Du|a_W{P-XPcyp>sEkv!| +Fau&~ivbbKGJqeagJp__$g47d-?+tq*vH0zC~G4?)Ee +c#^nYNvA3y&i%e|-mPb~LQ%)fn?0q><>V!0pOzQ=O!p#L+={owYuEcd?o`z-ec@pmluAH4+)t~kEcd3Fu-u1{HI{pOt+U+o{{xnL{{IWhJ^%leV02{8M;bhCWjHZ!GuxuVlIB|EDbX{J*?p;J3_|Sd}Pxc!k^L_53Q!y`EoVx!3dSEcbeTgXLb& +t61)*r9Wf2cUEt*+)qhYv)uQyH7xi2Y%R;ZKYokkC^UGRd6|1HaXKl>5OeLv$_LK*pv_G6a&j`shs+;=pZ6qm8@X#bPtEjn7)0V*vzK&3?oXpyZbk*)ZDS?=xsf3w`%|NqBw&;RrpiaJXioj=Wo!UWSw>QKLfJW_}H +9poxIXckdof9_1jI3q~gwVz-F`e{GK2=vo_h7ste{Tw6EPx}Q%pr7`Oj0pVVlVCt-#%N`ME}{ui0D +6?8G(K}d=I9;aG9@|DzV>prh`2N@-Z_V+Zcfzbo4O-JLuTSi0G$#7!mz+KO>@_9%4lFQ#v5Ntc!knlo +4SE#~2ZIz+S8_?11}rUDyHl?Ygi7?%Q=?2i&*o!VcKK)rEez*Vl!9xYyT(el9U0^uzw3F7(6xpf2>oy +}mB`=>bMWKc(w5%G4$kkV%{BT+c`dgu5;ZXr{WE7F5qnb+$87!81$?>Sm_8nHChyOm*^rfQp%^PIey1 +^G$W~5RY1!sqU+cWI)*IqDp3}n+IQH2dBDOF{qE3;;V#9Bo3$e7MKzhD^s1UvP|fQona>Q)58e#!}oQ +Rna~e+Hl!%0IyW)`{dBTx&4hl~wPr#;92{jrKkV{QlQPxGE)NAMQ=RPcP>nLxxt$T{r*j7*&`&43$;= +$ETcUtus*4Fj^~h8gdnc5ROm%TogdFWu7yB&~jZAfMyo8F8sV??twp^sd7T2jR4z94nb&Bs9DzU+Js* +Bwp62wzo+|RMSb*hWqA9lA+b#Xt>t@FtRyWI72!R}>71iS1jbHOfm@LaIV9XuE8atF^XySZSO`+P3g< +vyPacDc{zCjVPvKk4t-q>wHD9a|LklKzg_E^uVf@7Y6QBkAwho{)L}9h)FFkxr`#Kw*|NOY9w;-pxY* +68O{I+*z@6bh?{6D>jZ!cXMaOzR~IKU5t3Mo!-l_#{Xa2mvqOCq}T5KD|)Fu_UV%VG6KNjEr0}Yj{ku +-UX;6Rw})~MRqeKWrIDgWilXL`5+_l!L{amwd5gD_wvv}kW-__UpI}fd#SPpqzQtScoT`OzgUCR9_lx +1)Kmc!z{^eq~!>T#;qh7DaJ5_eBv7fqgt+VZ?GlAUOuST`M9diJ7u4S^`U!^4~_f=Nna=c$@=Nf-(w7 +u33lrw?$GJ$egpj;#C7X{is`s!tHIsVpYd+n>_N9B@0xlG1A$^*ZfbF#MAia@!q(nH+z6EFMh +KJ3G?DNdx$VEezS)O^WryqgfK6Dvr~k5@tZx`#bPghv(tom@tZwHm>0j<|687r)t)gn99sJw +=!ozuD7-dGVW_Ax!o|{XoCg5A@^xf=(?h0XEd(B-X%xkZ?YlM02HFupbu +f67O5azYl+)ctfzR%4Q=J9>*7GWOW=WY|`@qKQAFpuwZi-dW6pIai#e~4Sb%^xHj;4LgU)N=LwB#1D_`}t_^&i(6~16c|zm +bz~>2#YXhGrG_DPNp3t~9@Z(40TII)&#mIT +UK`Mbu-Ii5*bFo=3K@Vlw_zEgXxA1G(!TrjnPa#lV^7Wmz)C-0Z_Wcy`3ehFgdS|Q6Nf%^s4ELjq`Ut +leliLXoTT$Amb1==Muf#1#LS}7|6s*gFrbUUhQ0yyeb-5t_<`CWuRSlza~fJ0{K=N*?wgz^P@}y<#K*OW#?M +pcLQ2NSp>>;^S3j<+1n2WMZH0wT?YYvcqc$l&aCYx%o{7$4iM&zm1_qH^Tx`xLxg#AowdV+d2^k$BZP +T#owX^#yt&TWQNp~r&e}9#-dtzx7-8OAXYDv)-dtzx1YzD>XYC|m-dtzx6k*<6XYDj$-dty`yGHU(gl +@4jg!%ECCCrcCS;GAI%@O9u?;K%%{LT~R$L|7Re*7*H=Ev_6VSfBB6XwV73SoZyt`g?Q?;2r#{H_z`$ +L|JVe*C&?Ht&S!7Mmx`kKZlA{P^7_%#Ys!VSfA;3G?H(M3^7HWy1XUtq|tN?+#&p{8kC`<9C-ZKYsTJ +^W%4)Fh70|2=n9jkT5@f-L=%f*A&(s6XwV731NQxo)RXs?VQB2os%SR +zeM&EiS%gOYk}vG{d;>&^v6*mzDB)sP3RNtwZL=qL-&(B2zZzimXd!~&Xd|}@^{m~bEJ{%U(!g%EsbS-Q#rqgQmK1I0sS-D)f}muw=S4n*iTsGVF +w6{J?tQ1iH98`tnXom2{RscgfQ!2Q-q}+c9gKp!=?$#J?t1^g@+v{tn{!Ggbh6GBw>|@og%FEu+xM!9 +yZg(N;JJt*3Jm4wO#^0H~UOq}>or@{BCl;B^^EqK$U__OO>HUrV_VAnZ?DPcD2?dHY$?~L?KP2ycGpX#a;aE1waYcxF5c;hlMA +or35z`J7Gbf6-6kyYum!^U9=1rB@vtSrtcNWVmU`FRl*7nyGvNAHUax`SE*0m +><8lg!%D%N0=YK_k{WJ`_RP#{al@kd5I19EVhyFEZE(XIjCGF`-evA#a6ym+g_97v~A=(+ctrIGcf+9 +f$=R%8&SV3Fm7c7`3^)Dm``SvoM)GT-z}p^jH_k9?`W@ybwC+evA?UmCZ3~=~mw(rfTx| +beBE0eNOA^PqDc|t+|A$m23Y2+7W#T}Y$5bW>lzBpB`hhY}sZ2*3=*-Xd_Ok`;Z_7Ph?KLrnsUlD7xj +ywuw!5eIT%UR!z$^ZH85d9c`S~7!GELw<6gzLh(fNA<9Ebmr05?(iCj#6o;hza`Q-t3T;N}PaLV%kb{3`)&X7FzW +II8|T0gj_T5a7u99|Sl?{+0ko!{2oQybJbw0$e=)K!A(Kj|8}Q{6v6@#~%rB@%T>yTs;0nfQ!d}5#Zw +S-vqdL{Fwk34`L!-^&%$XJuhM+Uh*O);teliB3|zzCgR;LVj^DbA|~RkE@C2H=^`fLeJ)}mUgjbu;!Q +4MB3|PnCgMm%Vj|8|BqrieMPedORU{_jSVdwY&Q&BP;$TH$B2HE$CgNyCVj|8~Bqri;MPedOS0pClct +v6&&Q~NR;($eBB2HK&CgO-iVj|91BqrjJMPed8y+BOFmlueM`0xTT5#L=PCgQUT#6;)@#6*Y&0t# +6-vi#6)NX#6$=M#6+kB#6(C0#6;)=#6*Y##6&0q#6-vf#6)NU#6$=J#6+k8#6(B|#6;)-#6*Yyj*0$E +(eCZX0(L6k;!cc*%s`BX#z2fWV&U0db0H_PLscNMLsB5JLr);GLrfsDLrEaALq;I7Lqj04V*x{C$I6A +sjwK6`9qSY#I~FHIcC1K<>{x~n*>TPzksZf864`OeBat14JQCS)#v_p(M?4bQal#{YyEx#HNQmJTq>PSq)p^n5voasnR#F37~M4aeIOv +HhX#6+CuNKC|Wj>JTq=15G$t2M+#yjMd^#7i~AM7&W$OvLLn#6-MHLrlbrG{i)_MMF%)D>TGJygx%s# +LF|pM7%jeOvGz5#6-L^LrlaAGsHx^EkjJit1`qyyeC6U#7i>7M7$wGOvLLk#6-LsLrlbrF~mf?6+=wK +D>1}GybnW6#LF?EHh$YxgMl8WzGGYlfk`YU=i;P%;Eo8(J>>neRVDlKU1Utv5zru +E|;1>k!-hM$~rvfhIM9ET!&Xsrg1$Bv~P|z#y#0%;TArDe-2u+Yk3?Yz6jMFEmH^k8s)Ena538J_>_^ +I*^n;>@MqzPg-j+vm25NAvf#c{v{Q5>gB5XEt{1W_F4N)W|ys02|QCrS{-ahwEEywY^aO4BJTO{c6ho +wCw&%1YBID@~`YG@Y{2bjnK8DJxBr!cvetCUTGJ_OO{c6iowC++%39MYYfY!D +HJ!57bjn)ODQiuqtTmmo)^y5R(r%dve9(PM$;)9O{Z)$owCt%%0|;E8%?KdG@Y{1bjn84DH~0 +vY&4y+(R9j2(dcKh6T!XxV!+OCD-x%P6p7Qsio|JRDRKS`DAJ>e73tB$iu7n;$^3=@MSnE0qCc8g(H~8$=#M5=^ +hXmb`lE>z{n5mV{%B%Fe>Ab8Kbl0*A5Eg@k0w#{N0TV}qe&F~(IkrgXc9$#G>M`=nnckbO`_6R +n~g8mlOW1||nF5he#QF;#R$1CxW82$O@Dm@2BHNfp)6z~mq%!sH+(rivzMQbm(AsiH}mRM8|2Ob%jVs +tA)NRfI{CDZ-@56k*b2iZE$1DNJ^zgnRrAfVKjP0BSNt05zE+fSODZKusnE(4PTCm^3g`i1C@CP8ygg +#Q02+C{3nFlqORoN|P%RrO6eE(&UOnX>vuPG%!<$@wuW?nq1K-O|IyaCRcPylPfx<$rYW_ +!xuR2=T+t~_uIQAeP;^RDC_1Gn6rIu(icVeI2r70Di(v*r$X-Y+>G^L_bno`jzO{wUVrc`uFQz|;8DHWa4l!{JiN=2tMrJ +_@sQqd_*spynupy-rlpy-rlpy-rlpy-rlpy-rlpy-rlpy-rlpy-rlpy-rlpy-rlpy-rlpy-rlpy-rlp +y-rlpy-rlpy`yArc+j$PFZO>Wu@tqm8Mfxnoe0M`zcMOiItTmR#uu=S!rTrrR=9Pm9n4GRGO4oX;NmT +Ntv}KW!9RMS!+^etx1`+CS}&jeo9kodS)&4%+6eKkG~(#RzTBDYfU$;HQlt1S}rl~biv(`k +-MiVs~P1I~OQM1uR%|;V78%@+~G*PqBM9oGMH5*OTY&21`(L~Kg6Ez!6)NC|Svr$A%+fzhM+fzhM+fz +hM+fzhM+fzhM+fzhM+fzhM+fzhM+fzhM+fzhM+fz1G+Mc3p+Mc3p+Mc3p+Mc3p+Mcqh(ngBBX(L76w2 +>lj+DMT%ZKTMXHc~cK+DOqjZKQ0fw2`u@(niXrN*gKCsErh9)JBRlY9mD&wUHu?+DMT`ZLCP6HdZ!O+ +E~#?ZLH{{Hdgde8!P&#jTL>=#)>{_V?`gev7(RKSkXsqtmvaQR`gLDEBdI76@Apkiau%+MIW_^qL11{ +(MN5f=%Y4K^ii8A`lwA5ebgq3K57$1AGL|1kJ?1hM{T0$qc&0WQJX0Gs7(}o)Fz5PYWs>lYWs>lYWs> +lYWs>lYWs>lYWs>lYWs>lYWs>lYWs>lYWs>lYWs>lYWq?j?aUST_*)EZ1r#0D_7xr0_7xr0_7xr08by +b-M$uudQFK^qlntWRDEg>1iau(MqK{gm=%dys`lvOEK5C7kk6NSXqt+<;s5Oc{YK@|gTBGQr)++j_wT +eD!t)h=wtLUTFD*C9kiau(sqK{gu=%dyu`lz*vK5DI^k6Nqfqt+_=sI`hdYOSJ=TC3=zHdXXdn=1OKO +%;9AriwmlQ$-)OsiKeCRMAIms_3IORrFDtD*C8R6@ApEiau&nMIW`PqL12C(MN5n=%Y4M^ii8B`l!tm +ebi=(K58>XAGMjHkJ?PpM{TC)qc&6YQJX3HsLd38)Mko4YBNP2wV9%i+Dy?$ZKmj>Hdpjfn=AUL%@uv +r=88UQb44GuxuTEST+v5uuIQsSSM*VvEBdI-6@Ap^iau&{MIW`fqL12K(MN5r=%cn!^if+V`lu}webg +3;K5F6T{NE>pxPbh>7lg>2oEDzVN4}41^p8&oajpJwjS$!DADb` +f|tAKj^5of+dEf9;{IfeZ4-*Hkwb<&SR&abf=WmJk=`kM9U^asK$85SMu$KM>+l@6&!l91lJnAS74Y+ +VSAiAwnDvJ{=~+@!-=DLL3i1O%dXF@aZTajt8Hny9hj^Psa#x@%hU%-43=9Cj)#VP6il9oD6V|I2m9a +aWcR=;$(n%#K|yF0|fi%<10dts6j_92p{JuN^fj2|Lt3g?GNn!@@alBV!}h@>gZA0lZA_lHQD! +u}zWrtp7=q$vy_B54W-h^Uyt=^-knuzHA!DZCz{VhXc|sF=d-Au6V@dx(lD{2uPh(^gd{3VXI +1_(FN3f>Zv2j-G!xIznEq=q}v(lL#+bW9^H9n(ln$5>6rSWU-RO~+VG$5>6rSWU-RO~+VG$5>6rSWU- +RO~+V`Ppqb6tj#x4I8L0Xv6`rfny87IsEL}WiJGX1ny87FsC+vFh)4?WiAf6ciAf6giAf6kiAf6oiAf +3ribPGRD +>VIRD>bKRD>fW#tftOV=#p{+;D|BsIZ0jf#3^qlNvP)Mokl=rioG0#HeXv)HE?_niw@rjG87!O%tQ0i +B;3Ys%c`?G_h)$ST#+o8Xv2siB;zT@QOHrteQYpO(3f#kW~}NstIJ(1hQTN(d}RwF-hSXF-c(?F-hSY +F-c(@F-hSZF-c(^F-cQ3KroM(q;QXzq_B^er0|bG($180k3Z7V)&Pl0$pEK{lL1y0Cj-1HPKHbqI8wM +(OnJCcOnKN+OnLZHOnDenOnEp{OnF#SOnG=yV$8_Y{u1^S(+d6+lPA;k*c1+xXi_x{xtfl-nvS`ej=7 +qSxtfl-nvS`ej=7qSxtfl-nvS`ej(H#*e@4_a$<;K;)ilY~_!MfI6zY5ft`#Rxp(aqFCQzX!P@yJJp( +aqFCQ#ue5Z?{~B9eN>7Ln95wuq#ju|*{Hj4dLmXKWEkJ!6YVTB-r^3@0L~XE+f_J;RAe>KRT%QqOQAl +EQFelEQFelEQFel9sCQ;2BOtMbB0uDtfjOQL)tI^3=1Hh&!;An2K9OfR^Zn9+8V1-(pRN-k&(=#}4)q9dtsHd$SVJ)Zu8k7{Tod;Ra4l>IaP4aeaLt<%;955$z%_18K&Cu +7ydr+`LVNO3d-8$y~V`+eoLtcoZ4Fy@_R%@DazG>Z*aBO#DP5RHSyiux +)+J>2esEk{pwyUzW3H%6W^SJEQgP +x8l0xV)-4~I&S3sV)-4~diNVo-EslG)Y(9`Up5fm1+KG7#y5x6Ik~ZTo3O~k76^+yY>}|U! +)SRZ|NXmUi_905hlLFUgv!=Zq&K_er0=2_CL9i{aJ21{f|GVOZWIIG4&u7<5)c?Wj|N-WVyii+3Onp+ +~*ZSTq}RRLx{_f&-V#&8S?oNA>tVC_L|F!FVlp$toZzt5SJ659}wa);`0kaTt0j`PKe8fFNX+mx$xyM +AubcX93jNz!IvpQTo!y@BgEyv=Wd4A=)t}mAOw+FC&WeO%RxdAnJ0v}p6l~7LR@@4-z5a`xkm`%^NMA3rth}CR})oh5>Y>3rtNYrdd)NDx9Y)Arao~U_{sCkg6d61}ikOX3qs2L!SP; +IZN+)w04=Y4^mnb!}rLSN-lKTz+!%B8^C!22FNvnVh^dtZTP{`6J;^i}=@)*#+@-ac5?jKnoj{8G+e~_r-Q*h6%K=QTsy^m?;>wC$xdKwQ5gjRX*B4+gi0Jw1Kv@DtoOuJFqH; +ZJyh+u(k>9~?uc>*Hsy%M1_PD8nxUxm>7RE&{8~LS=EGvb +a!LT&OHAR2CNj78fdu3zfx%%Hl$0aiOxfR9RfAEG|_Rmnw@(mBppX;!#wnDvRZ}) +#|#Ds#4tuwZ93t3{5W<_iK7moo{-%;C9n10_6%ZmNdOms6tI|AZBmvwLrNlP_7D;Ys8z^P4rbuN_JYY +_cw&Z-;jur3d6Jwq-!`~Hz37oL6p#E^^6zJF%O1!&)I7;+KX_b&{&rrY-!jxV=ywFgh}Z8KLcIRK5aRVCLx|T;3?W{BWC-#4Plgb$KQV-O{TD-s*M +BpFc>S3n*E?_QKlkIt{__O8F8bF5xDoUZ1h@h8*WVL@gZ}uG`TVLdGnKCGt&!iV*=K=`npCc+y%za=1iSWgq-eV*SF5I(G@ +1;U5*v_SZ&u{qGT|jM!y3GX!zhFLGDL}GbaghoZdKelpx3I&DZ=_j@QG*D+D=ak5?85O7T~#1Z +DWGMS>i^N0;Uaa)jABe2XB*n9+sH1UbqKkDVgOk$m``AK9^dIK_X};I|%kAz0SK>FWf!cx|m8B*?{U> +)cI(T)f6lT^n{LpnLqOnYId!7sCa9T=3$|D}o#^MsLp%sW;arVQQf^_EFkYuN#5`eiY>puCV)+0;ju%_2uL(lD&P)^J;x&3rZHO7e=o+; +l<_x22d_xznt#i~Gm^TcMJtfG+Yk1)~L5SB8N;_$6H8Do}c3L>PcAqDPF-HBiK+6tUAodHqJ}C<%UV- +7mz#aNtfvtnDWC7z>^4Od#VEsE>y(kN$ejQF+mjyEaC(Z}%Aoe9TG2XcyzNZlcw%Bmy5kXSRh^-vmV= +Fa)B%pch9x>D{w|hztm8*q{-dL%%G`q*0bjziI-`#zYk;-L(a+PekG*G|Y9m?HufgOqMwLrNjP)>TSO +%|9%Wzqv|vOqktEO5U(P%bbF%E~~wQt}`VJa3-L_RGKeD^yOtOVM7FbGpLFxD|otEv$TwA`O&Fg@@Q) +6FYm`Yr=081(9T5}xS!lP+g_8?$D#_9lRI6TG7z^im-j2b>W)-yAls$HPWm1 +Ju3w1=aNTfBfa`t}0$jJ-C%|>Lh5*;iS^`}6niAl;)rDiKkPi`_HQI^_ +!48^-1CDItH?y(ad1$7L3Y`;}QN$_;iMo4@N`6YP%bvgWVE#y|I(KhM725flM>NswdE=Bon)VbkvX;o +F2^)$?wiAV;Ll_YVoOfZh;~`bq4T7*&cm5LB1Jm2i{ +A&kl0G;bXSf&n-EE0s7#E};SVW0K`4fa8WK$OjLdd50$*>@K +bCMv0Z0bHi2-(y+LD*ltdh{k?Sg9W6KSRi-4-w=-wlPOH^jE6HV60S!$EFB^ve22-??4G43N%)b8|Tg +wgovFuN{|I~j35N;1Vsfa$l(d%Hdc_slSc?bz)tcbBVZ?q9Nr4@6xR^(IyFlW;&ti@K}fMvYh4H{$l) +pK6tRLFp87x#UP+k0LrlbCaO=Qhg3$Cc#|c8y&k&`s7@W*MSRf2-KXZ>DwEfHzg3$J7C@j#DhuptGO& +*@1K>~X6aCVv?wEgUzE`(L)@GK1T+(36MPD2*T}4=+=$Ku{iDe%6J&)$26%9Ny}6fd+2u>KxuYO%Q@| +lYa|AnWu3D$3_ir(6EAYqlWX;Sz-g|aGp4ilcR>WDPyq_baHEs`aK*THC*5yL%*`&?K#dh@c$`-?ymOTxP~#5GMsQ-)aODy~pvL +MlLC|`x5_v5z;dibfhwoF527hIE|2;vNYTR1kKSMws(R>1XNQaO3i4c%S)DGB1I($UkOzOqsKHU);Nr +#VUSi|vD!^cD?A?xg1Lm(a#S+JLM;|8r7u$grDgjNPPoND-lVuVel!?pbcAs$bu7s0mD$*mWZ+1OY*T +&H;8=&1J3h)39AI$Y;EB3A1(Bw(lMaQ!Vo=v80bA_$>+K@E(}rW^B*2tw+-peYTun+{)63S+^>d7ruhQ4pAO&9qzn5`CwEVu?PBmahi@p +$u?2N<`y>rB*lIeSCgNkS>F_PJ6}FUaEYP5fJ*AU{<23EUuF}c!QR?KeuXOm1e+>!qj(Cclr5g)0O~M +gU!}t7KX#EeAfjC5J_<`mpI7Mpsf!3lpMrw4BS`oWhHx_9jg6*sui?r^=e%8@JT4duWsmblDjKSDBL~ ++5+*3l8_>9MtSbc9y0*xNcfLh~eSZk2yk0ODtcK|94a8~~!J3%0qAc=?TeuA?dHSg_G`bd=Tx*y%btO +237zuA`%rn%L_)nx?5dHoK0dX=;GouA^hu2|~P%Q(|Jj>*xeU85>?lr??u(u-o@3D%kTnI!QeyHocBc +Q2&NqucK4c1>>V8qtn!3;`pi23-{TOfjXQ4i>&$ug_QN< +kRRA+)xprX2EcMwq?rLNR5(6sbd6?pI6!N3g* +tN_p*24EfFKvJ%^7-R9HTYjl`;;}8eOB#21jX)uF>oahiQ$jQ+J5tv_{uSh`@nblZ6?|a-6BP@ql89Q +?*7nX$Zi%TB943$v9bSbc>>bv$aOI`LCesFU(Q{1&3gbR%j`TW3Wbdsqe)>Sd)b-d`G0%J)#Fr!`gUE>nxmyHM&pCfsZ#?xJ|1%Sb3ud)KoC +@Hl9%1!_FH$pz#-m-smBP1(x3EA(0BE-smCe*o`+My-U+QID3;@&q%j{xi?w3$3KUliATd4Zs92nrZ_ +HZ^pw`JI52Cp#+Aer=Lxqyg7&N{!g#j1g#22(7W@QHy0@R +;*{K6M-^KZJR6nrd)N6vkiqn*m-YX!l?-NwwuZR@z=r&$c4+SG`YnE~xM%>mcaRNr%*6b645dX78KyN +GO9OVeMa&Ek(q=S98b#9R$94NN<_&H$~e?}uf;)UhuS;7#O^N$JgHL{z8AzLpIwc+1Q7T@0@Y~W!O88 +~>8r2~fv!%XJGTY_BFHuqB^!NS|RO2Y~)yvfob8X@4}wb)a_khe>RFB8`HFuo@e_Zs!T*a*6HjanHt- +qv*@5Ny0n?(1RWZIL^b!^Yb@K!XQtyv>8u*Y~~1oViXI^63_hRPgb(Zg9n*&d11V +u-CZFpFvP(@+nhc~ke4hE>2YD8Z9O7J!9d%3bc`Tuz?!DISuY|_sg>ZMZLLv11_y2P_&h<#qBR=4VWe +%X5!qm*Z9U^^pod#}&kv3H$U5HN(H*EU%`!5Cupj>dhMZIk7bG-knV+j>tk9k^{CgET6^ZyO&zL=d8Oh6V%pZR5k#`kvo*mnOBD7qlCc +yP0SC+~B|S7TBlf2=j!;(=_#h<;I>>4$EykO%n!KZsYfEyzNYe_x9sIJ5_M(*qo(K6V~4NI4vgO?2V6 +8yx{DOkJBiO(`AOoY3AX1e&jmj@chQ&ufUDtw1$HnI6gsxuxAG@->2~icHsB~P1oTDj!*J~A)!xEV!{ +m^pQZ&P+`#ck{womUG(VUN*m#DB4L5K++m&Z?xPjwSL~FQ#W7<)d!wnqI&;$r>;CO~|!E*zbdH!E`eJ +~mPIqbm6@)~t3@B=r`@t+}Nb3`5Zf#W%%4*bCJIhu*V51g!s7D3F;)3OJK;AHtNtpec)Zk{LZz!BU$P +wNmkf|KR<^k}dICo6|&mH|(2^E@>cJi+m0S`on$9ABo0!V?@{CeFbV9A72{4xZrn3PXt56?Kgim(F5^E5QT3f#O%wT4AEzC{B%EV}V +6>NjA~O;$Kk@aV=1)H}eV8!ynh4<6m-B^oQ?(Tx{qh69goI;0j$(@VT>%pjNF$yT0x +^3FKlEbOnK16*ijJoZ^Gy#H9H@S0-SPZ9b`!GcfPTlr2Kbni$_6bT)ICa}6E)nEnwtb8O1E+3#hB_BG +b=zl%#4ze6cOFqz!>QZ8Km>tPx4BGrgj2VDftcEOAzKgc|J~cq5be|feeeo(IdBBGuhN1Aj^OY)Epy- +qZeOGB8IIs&m3;uu5?r08l^-m@%~c{B9Kp?1nrp!j+`dVDpJxcJo}nHNhT!I1el(C{^(@Vr;0SKdcga +z}_S>FcB?t^yz3`kcB<;d&f?Q-a@2?Srq+R46Lu8g$2tv-T93%)iyYikO#O2Xxg3!^f-hWIO0(1W?K} +gys`w2qQuDv4&W8)gv17qXb1A<)6Zm&~wc(&i&Lv%;jew%AA34&;I_wW(IFgo6y=38RLaF>>v1&qJ#* +KY{ItB&gz2tv@_ydnr4E$vDzVEk>r4Sy2kk3ap`x8HyF(|5o4Kfm~Cr@P$u75Uob_V)eq>p%W__n%SspKZDw)i0*{#ZX%Uc5)L +fYFQNJ+RKJAkmr(r@s$W9&>r?&uRKGseug}b*`t_-PeX3ud>er|G^{IY_>Sw5ahU#aieuh(n>Sw5ahU +#aieunC2seYE~XQ_Uc>Sw8bmUEZtXQ_Uc>Sw8bDb+8f`lVFAlOVaA|NaUv_0~WN&gWZF6UE +VPk7AUtei%X>?y-E^v9BlTmNeFc8Pz{V7h_OHu_7@q{Y%0YL=_0oB$$Ob9hja_PJ}cI0!{_1ojvPANl +zK^~Gj-}&#~oi7*Am{#iT9QLkz@tSqk^uRX{Xh|9kvLB3fklYl-#|8)IeVXF4Q^ZY6%SX394p_ok_DD +|j17j&=@h{j^!#TrF3(>knVQWCa*IZr}-KBXwuKz5EV~1-zOw@K +1#3gi^I=b`#0U+h|MJ>%m)f2{*w&%TN)d;&~-vGw_1IvnrC!%e8+KBvC&NOvV +9AwlKm-imj^i3$#Ps84DBKhsV~|Igr<5ewtL6*kSlA4+SzxWALse*bFo;=JZGPx+X4R+BIJ0)&!+f^= +j0K{#1UCEanH6Y^uJ@Scpm?1iN}p)Lp6>=bgQjA;FdQ8;*Xa%FJE?La&u{KZZ2?nD@!dZ&dkqKuvO47) +KM_dQ83cv0sv4;0|XQR000O8`*~JVJ3F1ztpNZ4IRpRzApigXaA|NaUv_0~WN&gWZF6UEVPk7AWq4y{ +aCB*JZgVbhd5u%cPQx$|yyq)c<&sLJAArOK5u{!y5`}AJ;x(~w?8=Xp{yl3u4^u!Q2gmWu&W?9Ctzyo +>i$1XSqxo#{;HA;^v|HX(K+j^axML2XO>AK-IMXBGNONGBbjabW +fUn)epp)*xTT@;+}c-AM%5-J@ZX8RlhP7u*v>@4<%C@ePl{<=-qp<==l+h-pm|F5_+1~k&uc~-fCd +puaA3L&RTzEBfpov}z&mMehUf;D+*KD}i{EoRD`f8u~FMg%p)eq%f&Tq>Fr;KUlnW!Dif4~M*%E~C|% +7QA+y@lb18oVUn&l36B!JZVL<@`8H92D2BU+)%X@tLhS6@&|0|X +QR000O8`*~JViT|rS00#g7$QJ+r8UO$QaA|NaUv_0~WN&gWZF6UEVPk7AWq5QhaCxOzdvDt|5dYtw;^ +tz5lscO11Zi!=#fCnH4qK37$=0DL6xyb3ZL;KwR2rx2KKt%Si6Sl8aXTRoo4WV??vCUmm}av$U5ucR^ +W&$Td7iCsDM|QL##t&LUahlSLdsTrCX02#JLSrZNiO4+`7SoXe3W@Hm7PxK^3MT`=}mCUbAj@rIGtzj +!uK#9!=TfN=YS-m4&W1~GLoA$7s%n^mmVZpy71MzsE|`!zQ|JE;fdf)4*aavWWLcaEw;+ifd1U0i~!d +a5x<-AwS*s~%;QNR`O7@d@`w37XJy6{$-L0_(cx*rgn*ZK@(S(p>Y#bua;@8gE=HV_P}cQ*t*qOv-b2 +pUITt*Y01|iTs@*cCvN$t%n+y0}_<+2k)Qi8^2Ajh!*MYbV^v5tN%#m}WGbB*R(~F2iUgl#DY`<^QkG`r_n-=@78gY#$)t!Pti5;Ll7< +S$V(GrO%OFl;iD?u(nfEFsJzK*#v|x#fUL!-ylK7qjvofZWTzW;%Xw7oABKTKgT_|1@s|n8`n;X|K{! +^}iaDDOmE%0?VU4j>eBlQEx;!zUr;I!+4#31{m{f*&{HB!b|gG+hZ4;7#~E%eO=1 +E6ABrLgX=FoHoII4C_q9YAF?gozOkK$>71vi-%fNrLO8Im@nkz`GV(ryPy^0FVJRZW=mtMLQi!^o9i6 +`vXxRz=n3y$gg=?JI`6Hh1H)F +D&T&u?mPNryn3KuLe~aRR6|r$uLz&83#qBrQ52POe~v_D%t6!=j~6lUyA!d5bCDdLZhG_qu#{z>RQiF +^^U3vyRbb>o73l-CNP1+n&wfbKZVx6t9xNPsx5I?y9ZwuBPhBmv13W +uzQSzo$Nv)t{{0_pqP(LTH%4d&RCD*fOaB{lK!v0{iOG0l?R9=5j7F$xQqf_-9Fd^!UD{xG)|7U5UHY +L-XGg+nnWYB}NQ!M8khG)leFcG^3MOk)@w(PWIt9+}SYs*BqB;ZNfiZ(#1icRqyT3s!*Rn~w?m +{njX;N{z^*17^uI>GZBwV>A|yrwwUmRd7hPXrP{s>j*bXEB7-_$CxeD4b#yc^w@P1Ibuxo2-4QHo^X?WA +3p()*#JUnZJydhN;=XMTyPVgILLV_NgJw+PddGsejFwzWtE(7dP?B6gnQ +hGFtZ&%r@a;Rre(9Em${NUsg3oY$&cUOM5SQb0);hxh6xN;7#F_$vlI>*YP&X@6y~z6{z +y-QQ<2sH26*{tr+~0|XQR000O8`*~JVtE}ctBpm<%ij)8V9{>OVaA|NaUv_0~WN&gWZF6UEVPk7AW?^ +h>Vqs%zE^vA6J!^B@Mv~w4D<(|kLMjwPQnD?pLT~9At4@BbEM1by`5=%SN{C2+3xJlCtNZWQJu{d$Kv +0y=&h4Vg76J71boX@k^t>94M&l%$PKV*>mdqC^CGQ{dnn`oV`GvgG +cpcynnyE=ut}P$`!FNH5!_tB&4zfHJ!JA{o-RiD#>U3`-}+>LZ#ti7nn5)|6i<@hyC2D5K>8cegaE|e +27xPz^B{ki(+mnU;igTdNqpVfU@ymXLdMZ352Bo=@Nbqi$1PqCuaxGCG$!MYdJP3P=rp4)a`?@jE#hd +Dj49HWr|ijn8phZ7fc;&$W>G6JWiw2EBF*hKc_s>eOD?ac599`9-RX4LqjNf1F!6MR^M^o}bwdjIL!L(Q_31lckYv;3j +sRirAKpK-jW?4pgO=?O@;bSrfM}1we4mlzE;bMyETZX{rm{?y^QL2#5(JEppsP!-0~W=j4>ZGaadm+r@SH;q>j#!H0`;_;YZ1dUkYt4*JLCjqgX(Fw3O +Kn#u4FI?A;MVr>YbkMbaBW^_6M-5CrU4Gn~uAee>u1TTayXoOk~>YAvzS*ul9s1wfTG#&$-0j5m9c5T +Ad6fE7LwHN%jyl6%PYB-~_Bz^c4(YuzXR&+6)JQ-rMHf=f`TnHAXY4}iriledkEfI;q(KXFKh0_IR%0 +e!z%s|8bT8`AOEYQDXR2f5*7@7=r7AgheH0kyM2&Um|I1Wh`{Ym%8?;kfr|I^`m4yLVaV=9`xA(`v>6=ueKH0))3j&50}7l12=9tzEo-ZH<7_k3^LV!Brt7hIpN5|eczL)_3HUNQ}4cFdWI6zep25J~UAQ1-G9>I{cCxhbU(TIQ(Wl +`*1K^s2ceJ!X8`33~2)fz?_CI5jc&RV?L7=>}1H* +bA;DG-U&Rq1y(j8T)q)?Yh7)|MTPir*zIbRgoC@@-4A$U>E$fBj8lcGR#gT{-(tQ2F{Nggl_L=$bcY* +wWHHbEk7y&N^l8Ab(v%H!gGo4ja~=Pf-Vn4pTMInd{nrboA47Nyy^hzaWigRf$N0voiIeaPC@W#Ai`IZTo%(1K{Qd{1W<8ip2p7Cw!zhzk5vG5W<)N<_?G-GPG3atf05fjXHO8hdC-!@K2V0FZ`SbM +nim0CfX!Fb^2BVteDri(FouZ0!;ni)RIiEw^#zJnvC~040qBcTUxZFz +v;o7$utRbLYg0d${3W3DHAY7j^DpKd~HUoeSk;GNb_wNCs~aW+fbsIf#}Go2-3W~IBdcAO$e%A=xMf? +W0Z$olG9c^A_`{VJh-I~PO~XVf)|LV%%6}X#;8jP^l;3i3 +KRfWG`f2vt0SYCFMET&`ZEsLT`LkiErUS$Z)^dMwY6hIjFA|#x^kXCiCQu}~Vg{z6(Ed=ffgx%N${gE +YctCIA27^T@~VgT(C=b%7Ys%sI3FNE2?~L~727WKsNihylba6T`YjlN>K@2B#U_YPF!RU0#Q2)@3#P_+#H+2}GbzDc8 +b!aLxcb#?ErCkMBeDKteG@v^SyE|dRJpy;m{`eG{5Kq9)V!B)>g&U+8%xpd^t8{vGN=fYpg=PhG;!Q>JZ5gFa +H?9hN2kOpsx#)ghbs)8R0n|2P+l2PZx;)9ftGnFqqMG;*#&C#W2+MLf_pn%0Jj5Q#IF*5`Zdc@lBOs^ +{@28y9dKKQ)21o?JP3`4P_W;rB_J6X7|jwkxzff`EC2^K +ZYhP)Zi-qABK3Q0Zk)+2qZ%qnHwaqx6)k)mgW4c +rFec}V^;#Y9SmQ@S2aYgjG@(*pTl0DUdBSI8pujluXqXOVXofJY(qRQ&)=ONtAe)$_!>QOwL91t0 +nIhf+(Bh{@eNCqz?tE%)8a==Bz}Y4(?>(@l(TY0{oN15Z&Q&=WXa&h+l2O;sskQLV?#MMU_}X9+kva6XHan4HubK1;0(4a^MN!-1BP +eQMHYRc4Fg-t_A63ag;gyTQUU0bjU3^zu{GZUgHd0=f>sT6m}L~V`{`k?d?^<4{pRPt;r2OP3q +lGbjQxRuu#GmWbXh7$)PrE$k}@wstRaQy6hbT&7>Os?FJU;3A%90F)*DT|B3+Y!F`XKb{qK9;f*(Bt3 +BmK$z+1v!pfs}k7FXlY>r&7;2xjRXIeq*7Z2#~$*grZVKMWw=rdj+whKLb&-*W8BPXg=nJ6hU+Zok;xAqrPmET(}r0yp(rNYhCtJItw90;R +FA{a#y!SV%>UA&=BWgezRA2tQ#1v$HkiD6r18fcTYxruLibX;yi7*4o&!R-wchE(o%oK-L4$q{u6uMb +6`YG`j=_LmVPgR_HzJ7F&|)S0#y5reklRTdXp +Z#)xk#hlm=167&DgeIY_fiPeJ*)pSU$LxLrBl^K?dko7gYpyhhE;e*AZ?oJEr0WcB;6I3lxC^28bhOC +nK;Lz$`48xgacb1qP%2->Oznz02r1znL(~%g+EY><+CEa}t;9|KFfb>2hUDK!ci8Ll3#TwFCFKMSwK- +i?s@GGc+!j)oh$i#v;V%DOK0u6HjkpDk +3;dw;SX85KlECFVbKP7-v_R?06Iy^!S;gNox2Sg~#V_ye;HxsHojSq9kh} +@*Qa!Z6sc{RyIa1wr7tNTi+F%u_w0i>UE=SNV07Rto3`hTAKa8qBNgBQkpwUrFpuZG6sx`XRl9fv5JyXtY>W(?J}{Rm56m$inaIradO?RA=g*0*AeXYUs1 +3xrC_)Fj}z>(I)Xi2N3hTSih|wu4Q)z%Cr4_DZ1Pmz(;(}himcrlWR2G&Yu8WKVHH`u@+Md#;4$hys; +a+VLo81gIT&Oeud75))&(16ovf=wfBzgxbktp0iA=JV_^4%6`Bm$vx2|g4d=Aw*>aVL>(dSUDqwRH7> +mQ#*wRTuHIf?FB2vWDBl=G_k()LfJ_XJ&4Z+TZ$J@{$+O`w-ONoZ*Z@0l*V+EGS{@cK_6y#A`f+pZ_P +{yM_j_6zU1F1(#Jg}3tr!rNI@c+ctyZ)Y9hJ@X6ig)Y3>1F{m~?LL9FdnyeTAYtc%& +$I>WFg5&|;mby{}M|hoz!)mvg=@z5D`eNnB({j~3aN*1s<%vdZ!PmsX_zo8>x7RrEzfLsXS>5MNq>z5 +-F%r@MH~+KEU^nsXZ`cQN7HCH6q7-`!)FHFUOUK`m_&Iyw1dYHzb>tKdsH++weWC~od55f8D&eJR46< +7+64Z*(71rvZ-d=X8X}+?jD&C)5Q0@e-<3ezHoM0Bj|Spv9Nb^DVh}cl53}-+xEZNM=zS0)4p2Q>HQc +o6Z4KnsPZgJYwcHmvB6G7U9lSp5U_?dyWWdB(l9+c+88Pkkw9R;TCD4ZL{|x;Tj_}PLSlLFciD*tVf` +5dSbk$WkPlM04y^9pGAq%c4r?K2lbvv@Kzx$Ll!YNOls1^)NePK-3m_?j@X&S;#i@6Vga{YT@}Y>u1I +qGH=jCT)skF!-UfXLPt#*kVJVQK)L%k?J!WLnR*KZMh +iG#b<%%wHvap6ye%BmXkmVEtekppAt`k01R82Q%aDi%&zSImfTlQMR&0j!)4`ary>TfdUf6q!CI&4`? +zGHBP(AjgKwVRkCefRq~ +ZDY7r<1(jWS*GQtV8rXVDG`R~8ODlBY^`Jm0A!}j;_}-8F9QE}Sdt^!Y%KCgu-j0Esh@K6a*Xj+3H=A +BZ+-0gNVG~G|9asb^cM$W*^zE<;xW8(-1#i?l6Ck=Bc8}l)>@$)+R@2@Z_z{JBF9X=D53kNi;%_r9+a +M?058fQ4&!76yxTSK7KzflXnopYV5sL+)oGRDSQ&0r@`+yT|JGaG-OQ}%n2O<90(OyM+J9C?2WIEX9A +G1*rzlqwso-Y%EdrX#|N3poa93Z^*A`PXMg~OAq@O#X5GGb(SvQ^?hCyV0iDGg +YLO+^YG>Ev$+<~fVl#}{}OPDTjEcInq +tW(GjhdT|CN(8)yzp-r1%5rO<0O2mZ9xRq{+X+=hpxu@!|-v!w3{_M^hT6#qm(T*l?39O2QP`uxO|VQ +=pC)BM67#QIVDr(^-;!r_01=(z`K5){wD8YgE3*IVF&;s7G_$G|YKgphYaxsd8=+9yq=W(=n$ +=FsvlCKOVCbMeHm)8=aR|p0R8QnwVKWO`eu$vY3jTJLvLEFA6GLMP_(?wny_R?+2{&aWIr!Wi|7n3bnIkc +RAvuCGQpEbtE97Ofl4j`RQ~p$D>xPyi(3m)L_Gub=_6y}qL}M)`wzU_wG#C$HOm8|K^5-VN$Mm^iF{S +Xy$!b{Qx&Gvl8G5LCW;Ktr88o7ewiXKdfDobmSU#BU6EQ*@F)NW1v#2EuzIiz!jx@;Qc +s`f@D$DK%GFh-sqpq8Mt53ar9Zx9I(74_L)n2?$-+UWTjBhsmXw1DF-*_?4XWs;FoOtvm0A+~XtS4e{H*Eap{iy4rVk)i40pfEc%w8G?4Yq#mpYq)5-uLy&)~ab(RTJ_dD;ls<`|GiVeBl|K +YVLNQjJsFt{30IMsO0kG1ZvWlT$9aL2d_DwN6Yx^YMgGZtu?O>ZaBBSCEU9FJ^|0Coaf`^Ja=8b?X&+ +Y*B)`s`%5`5DT@FBANh5NZt +pT{r@DNsMd;tSSWJ-@%?dA(3;@+9zme-^LuWnh6F%j3^wbvSvs@NzSL@%=5B3a|!nX=X6I&V+TC2?2| +Xoxd+%&@YMN$2%U)XAAIW6-)VL$evXq^PF<4LiW58ndj736|xtV$b5&Os<7>rW2=n(mm8Rf`Cqijna5 +7eIv!t(h`4vlYALgj+HvhmKju2)E7qshD(bfb|3*N~$*2i~RU;U!M=+MpJ$*vmU6cwXR{4AMv8_)@Y? +0n07-Zk(TQcLon7~zdwzJW{o34-p$H;c1u-;faH0g9OXj{ewiPjt+hR+(+!4bT`4xm-!iSDQcr1Gd*< +nl5%L&;r0-e$DoYL|2wQ2Qna*BJuT4*%Og&%`o2znY{Y+y}4ESxX+=8AZ=(J%e_V-ge}7-^)&ll+AJre9pEJ%T&0S*l%Z^-vZ?yVn)ECz}Lr +^vk%BjsazRl+tIa88fA|1s}X>;73qoD*RX$$%;V-bHk#(FvlQqoUS)l&DTl;}R23+Owm!jVey9NEt0U +~2MC>!YxOu?*Isw+gzZy~$f#1FUi)HIg5?pqP415qh1-yec9uThw^b!0gEO>Vh82R>$CFcy}GV>`17I* +bckPkiB$~om$B1teF%xQ(a6;DlOM2YxNpTLS0+k>o4ha?_YK?d3~$p!Rs-k)s`EM4%-!|*FeulKu1R= +de_YN*|+hmIVela%loGFg}nPYg?#*Zg?zG1$Vd30@Z~brBfD6{YVD#^Qg|gtvcGt4P)mC`^-^oN9>&vIeOW4t18vg+6uZ~jrB2@g>D|$nEf +Zeb!|Nlok?k{Ikkyse{&|eNB$K?Rx#CY%HolRD(16xR%yId7mlB;+J>vH(mr3kDG#E!v?}X&(EjFTy9pM@78|HB*-{Gl$Ajrp&He!%f^Zn$$s&OBgRdYwpf3RJvGn(HdV{M;u5>FraUO~0vj +_nBQ=(xSXg*jG{^a0KMeYtqp(oHgPh}CNX1-Sfh`u6AG!^JuLIk-GMJ32ncZDH?!vj=e;t6G-gM|s8S +L1m-+WiIG%ZTv4#O9KQH000080Q-4XQzi+)rey&D0F?p&03rYY0B~t=FJE?LZe(wAFK}UFYhh<;Zf7r +FUtwZzb#z}}E^v8Ol1*#FFbsz8`4u9iw>JG?mkkEn+8zdF3_5lhBe;o@h>!aRr@$xu`${)umYyt0RENPX^U|XygAXFX0o=*E~OaUjv2# +?RZ!^`P#DX1bdTJx0KJ1{tCORUZ4^EKK)rTal5Bh<)(q%E?nEQOS13HR`kfq(L{WMYYBnJ=!uy1o}?O +p!B`UcV>XDH4u5ZBl~0ubG0&a8gw1VKmfs9N}m1cd_Fg=6+aTq9xVlS>h^u*=d8|m?SZwjm^G}-hhHh +gEmN_>k!^C5B6@gYKc2{F8o|p!#IdlQXosZ3X<)nSVwwoEWo{1Le_9%*EAfrMXIK6io370Vnb)=7Z9s +o`VYD~>K<&=6{KV0-*JQpzwzaExs$?K1mDf{qb#?<#J@2`Z|lv?(!XwDZgZH!pULb8P)h>@6aWAK2mt +$eR#O$NlA!be008j;001EX003}la4%nWWo~3|axZXUV{2h&X>MmPUtei%X>?y-E^v8GkIQPrFbqZa{t +7X(X(5C%`!4#JMPcYBLpP-uH&Ggc9R*n~N|o_V)Shf-=doqGDkd$0Dp`m +X;%_Z_Vp8tg4c%5lNWv$IQ&{UpQ6V`r5pc$F0%yWok4 +-q(37Ja(CrAUyV%x-lA!p0Ky8YMG@h%0$W1tYUeQeUy=4dCaSMRwPWO^be<%j6;-qK9`{sX~h-=PwSW +$vRbh!123#gD>=v2QZQbGOIhLUbs<VHVCOQe?>%<6l;M)q*k7$aIX`8ZsX +q#1UY0Dtty39`*VtGTha-m16Sl0Xygc0A7O8(HZ)Gkb$^EQYY}3{DNqfYAr%`~dZq>!9;{1fz1xv+Gu +`?T;r5|}LBWpkRXCBw$q$sjttoNpm<2dd2qZE2j3$^fA$~14dONqnOY2;fg)dgsSH%G6|j)RL2Z(g3f +V=vg`{^{x7{@(H4K|^tTesp!UyMt#_>(;>@ZtXO;emZ)8v~zy-vQMV&y;1iJ-J4+d*#Rzho6D=+=M#F +|K0kW*2G@I&=K7z{4g$5)fgw31lHlm&*_)%QzbS!FPbca#cpm(G@@`)dO!hYuoSt7Ey$@a=z57X%(Ps +~2iY^j!76dYtRS>wPi07VC>mg$7dbX-Wc{65PKd_4|6`f6JQJv;IymjYEbx1+1sAO7oMD(eOm3>34<` +st|w2e=>GZoU;*vezR@85uvS>H+IsXc5K;yx64lW%-MUbZe;!DT51V +c>6eR-1bv5dNu=@p^JIYM+u$eG#aX5hQ#eLr82yb$&Q=IzDo~iTF=1^xdHo+rs}>f?K=k!0beO`G91a +ma-_a+V72cCW1U>-8k6$^hb+o9OyBUD$=2#O%QjIHX;0ni~3O%C8g}m(psch$!GF0gdqD0wmFZiV0+r +p>Es_Q@!a|TwX<1yFJ7>r-k6A?7Uzf8&XAQlI(@lIAtmgORK0f7AuG0Sm)t&DY1Iv>paC)r~;FsCV57(da>zK}=XP;6<{WYdy@+0<=()7tz){&Va9h +On`+`HWLa1hn0vrU`y!E9M4A7hL3@}1Atmqz{jB{JA2!UZO$R@?v9^F^U=BrhZa; +M$Q3;*0Bait88w`8WD27fD*s^Dty<4By=Psv=`a6wc_t5$_3E7Jk2pz0*+p}35bm)I;Ew= +!wS~ec;U}MxjV5=UK{nCyDN(-N@d8<5lb})3-|GNizn9u8O$v=t!0hS}=RPQ&FE_K3PGtj9LdZI_PgM ++yI_%wFbw6vBw)>e{{9$Kf3*q2x>CE(}WtM%f6eo-_azqewM3(K?|f1dgR?nX#Bvrgf#S@u=aw$unjEny$2fA>cLeD;0t>iq2Gaq#Bs;_}_08rJBR4S%R*>y3 ++i_!Ea`5ocB5RYoB85X=cm$T!a^|9P;&SMIcGR7e>)|aFNec2ReGGYU;0=Op*4A)KP_&O-nxM +S*bGj@1e$ONaQ$x|#6M4BGKWX3lJY*}5>->mcvf8S#Er1ymK#jmx5UOAh=G;{b0+^m9|v5??p>Hl(3I9YWkhM@I(+Gtayk-Si($v&u +5SC<7jc#F3To*8QJySI`SEID;(EK}#v)T4MoKK_4WOp{-n@^^bNw^K8Z8tpqeD&mf`gDKmeEMv69sl( +8;yTAy!K;f8f$|>|j;piF3t%7}(@+I7swPb^M@Q(6KYi+Slb?Z6UVNE-yA%#5ifYI~>Tb6MK=cVb)`5 +1L-Y@f5R<4c1judAQ84Pw*fp%6`4JK=G+Qu=MrJ^wzd&4=Dko+T;u^KtLpn`Lu0aTGW_I-btK8KLboa +5opQQrB*$U)tUXF$te7dpoEEl!rNm=Un1>nr$7JvgxkJN;uChc-JLBf4C=Kc-wEid5x1n!T1RiWL~Y_ +;7wcM*2)6Lf|U>sRxEp!qE*Z5KnxCB%(iWP;*ze~|_LwEw$E()GF +CW7zyswopF47#Mq^cHC%%GA|eR@_n+n9GHlKG&7urboRhHnO^C@G|SCccpI=9!6KZW#3k*-GN5M{Ev9r^><*(%q4gwE3}pti<*VCsLBQ=j1}jg{dx4rNkL3ReO +!8#;UY2p(7}62Im=!XBe6AA||l5E_?CHqftM|%c{wa>WuJFl0@0BFsCzoutl1oHLPf`P77Mb?@3d{RW +vDz7mJ!4U#DM4g8()!JS?H8^aEO`_D9(aAOl!Td7dn_5Kx5BFD~HaO6OH05_MHz5z#Qb&}!o8N}a?*U +6C>Q2JKFOqE%QTIMV4|<9}ohY2>6GL_ysAW!SN?iH8s7@Y@k_MF)OxSQSU*tBp&tlRi~l06 +75wh!3~aHst-pz|zptHe`>FM6mCUkOy&FpZT!Eed7_wWdc!lw}F@SMsqkhdnWwg!N)#&$_l)p*D=Do~ +k3fzvrue0E$wLUqr9z_|IhXH_U5#*ym9aOj+WSh8{LInu0+F{%HyUQA6FHy%)o4iZ!Wj6||cmjqoa5if9{=LxIw?Fm2^$E`rskx01Zb +fNmR}&2z;^y{5G7SGFOsGuhgiO!xfh)EWEiN)}_gyeAx$Nll`_o`d?;d-+5{N^v +?fBEVb9jv+KczrJ%_dP%94eZRzX`jAylMJ=@$$hS;S+)zRW@$$S7CbO*x=3H`?m0FOw#ceO_D$)qZ~E +eR7S+2ttz;tpp=IfwjfZJyYwF?uzIVgH+{9lLS)IF%lEWE~y}@k~47cIPe{Emo`*BeRA%}V4;qB&Z>4?d$1{HiGrO&v5LuXoHt{T5>Xrt7+E)q<^XwVZ0a +&9(Fvo-#f?`6)^pb28J?iQbd34Fd#NcHr@Fe(U3bQ|ATac<&P8a=~oK4_rD3awCvLK5iogV4OIqQyfV2_Fi^IkPI{?rHimnp;JZF-;^-RvP*jX?Y34%~lPradhZ +dfS3#^KA{V=)T)j67)iSGvW7v8U?>#*A}aru3e|D&5U;~$c{F@$jSx=Tl4C2IN2-F--QB5MyOwP&FaN +(=%x8~xy0Tb{wSjtJ9`Ja2iCmcrJ7yyc3=&OWl|?L@+qmCRIpNyGMY(fUFCIUTJAJUXO*O`U*HluI(DVgW{{pYWgK>mLZX +-&yJm8*e8vNtyzXXsF_Le%VqKx|m{oV|%RS;&`a{W +W{lNws%jvq@dh@6aWAK2mt$eR#U3$Z`m*h000^h001 +KZ003}la4%nWWo~3|axZXUV{2h&X>MmPUu|`BY;0+6b$Bjtd7W2VbK5o+e%G%!%RDUE$Z+h~PO4e$>~ +`v`HQOf6#6`5Et^bkw$eaV= +WQuKR!u1?p{#+j-pCAW4Y{x)vmgtwTLD?CHSU*t(5+HZZQy!ux3vOkCZ$yOA_uS +Ty_pocuE6daG9WB3K1^X@J^LxV=atBBT6D)0J~S3}=4gytm`o=V_}No}txoF+&c^_1+4znnF +?887Y#$(ceAQQYt$I#@K2$0erNutyV!<=GnwZhO~@0$6+VqWoG2Bg7%Um>4Z028ND +C*s(|w8Tf@F00k3Oa@BKUIdQhtwY^`m~?D%*ijb$5Yx +9yr`b#+|sjmhi$xHvyKfAc*TZ}ab8XKydg-|+MEi!&~Y?6fG>8F%b1e|iU7G+OAMp%#T{L}nPms@)ZJ +t8%9ztM%p>Z9U%bwLE)yoNdpT@WgCutFp7v{T9p!aUZ`*+?UmR?*e*cjyQ(-qc~0>I=T2E@)Tm1iKc@ +T6I$20=MX~pzE;AmVTw4W%h}lq{xSzkagW#0?e0y17DmYo-~}wyXI{!2Dvk*@1yx%zpFPVpnD7t|(bd +%=N~Um<5YtchvVlTk3L~fyvK7y^QVE}aA?ZTsMoxGEB6V1OSL+Isw5T;;x@K^PHio; +oY@9ima?2p&U)q>!jflX?Z4v@QxGtk)13qweO{o`F+q>LpG+GmO>O!#+JHGl-)FK37*)5r%CM9(<2qp +=YLWmY|1Fnb)UBYiZet`jn_Ye*D-|e{@fvKApmOpuRX8Q-53fFrTefLy-Ji>@R;GAfhy)-)+*3AOLnX +jY%siT*4X@f9EVte8gVBJ8~W6BNh>%8{{i`-;)D_B#DLFRZ$hx9R)j`qFb9SI(|y;{*P9u;3KB|hia= +Qv%*E>|Hul0iA2RgbF~q1C*ma{w2E17s`bEkK&%rM9Z55o$Ri>%JE2l&>|8JHvtHI_{r-M)&Fp< +WS{_Q(|%^!$L~A6wNysi&DdJUW?VB{v4rpHa$xeRYZZjv3-w@&BP0iIM-5s{0)EFxS9BiHhsiHtnEw9 +@^JXROn;a7qGC*VW}L(NM(ps@JQ0Hd7jKPrL>MFP7?BwT6I;s6e@N*3H%LC0SVpL^3|#<<=&sqgPc-E +3x;Je+r~Nyib8ywzf5kK`@y>6xt^?&PB6u%dY)_e9=X|hb&g()E4I;f+r%eSrkr<2%cp)?_{U*}Vx?} +=3>$-p6CVJ~jj{XtKL6)J(2o*_x$vl)3*B&mA68c4K?`ytC;sJVC`}xwQg+{s9Mtq>A#uFuwfQTa-78 ++Hsv>~bgVCrd+P0^Fef_ +&R&ON)`xEcZLZ{>@;wSvH1+cj(t$CqsN7D@!|5%)kJj7{Q2p01pG389{Dt~lg5YPogtFqWtrn_Z7ljm +&j)+gi|flNv`SJ$d6_El#iiQ(MzUIRl3RjSYWc>*TxZ)pAG$ljc%uH7P_!&u?A^o +)M2iut8U*N?CbN2YgF46vIWa@cF!Em3r)>u^qQD)z>GWC=XW#|32im^7bMn%=yMDC5;aTpmXETKK-JJ +*F-5tZs8=ep62Ydx_0s5iMY%{$-B;GZ<2c=wS(1Lut0#zxS`3! +94=A?s1e!cErW_W-a14cbPia@{%HW(a(tobVEFvQOMwACBXPNygRLewyjTFthS%W<>Yt{lpGZ@fWAjPV3RO$f$XU^7zNz>O%|Hi!4rQXh+hS%Ha^pQuF`_i; +RvEdk3ta4-g^?H9hZC~^p=PMm#E}msT`?k;t@IfMwwfAEL?kn8YxJ88H^@5L_`_(8s3`D6B~(6-Aj~r +kb>#1HN@B0Y{KrnaWJfES-qWCmZ}V|<6w%dad4$b&5DhQM1{t@?ucD~m08mQ<1QY-O00;p4c~(<#0(1 +;%0RRA91^@sg0001RX>c!Jc4cm4Z*nhiVPk7yXK8L{FJE(Xa&=>Lb#i5ME^v9}lh12|Fcin{{uK{h-O0=n^x*?jYGDVh<%IUerNsdPZ${v!FG5(MEOC`$)FxtR;t6AIqZu +0MB&}Xy{QL1D7-g0CMYb@6aWAK2mt$eR#R>K>OXr5001W;001BW003}la +4%nWWo~3|axZXUV{2h&X>MmPZDDe2WpZ;aaCxm+ZExE+68`RA!Kxo3A*#^s-C_$PF5q^(hhX!zNg5o^ +Fa%bnV{K(oOHoO4Y3{e*3|}NtlG9#s`4CIw%y4En96mF8Nv^kfO*VPK2>h-aT96{oSXr|*Y05PdL~a? +8OzdjHH|k|x(F}f0G+9h;n5c7JPF|9qlGibzY|uckir2 +a1;sNAsxF8wdRRxobteK>FQ74m0zN@$ZmWl}qlStx +ct5y9)N1zyKw8C;-j~m1NCXT^>MKH6vH65E>WieQmwjRf=JDOMGBkY)9O +KPfyC!`ghNE+EEx|b((Pl&-(59Br9q0AHBwiX-mJlG83t8Q&(iOonp#8g63zA4PD=Zx)wV$LDA1htmu9oX=)6*r2ca(LTLvpN`t67wyx3I=Yq +&f*;m6onQXGxJ=J+1$R9~gI88wS`K{b=)Ywkq&aO!yT;A@o&B4a7m1`O^F#O|T~si(S@SbDHtl^sjKKC6|wHQQHxcjg!t+foYIUk~g|$?09sGM +O0MvDmK?w(x!4Tkfw(I3$5*DgXle#(sj>urTm*k4?n9O$oRPPzt(mMv|83>vTEMidIer9C3M2%MDew$ +}oEPR0iHV|u4xRD9UHp1?=Oz6_pB>;|GB7%XR11OB|OU08wwV`ox23P@?zo#YO^v`)^#Ho6h)m^>2X- +ajdNX<1TFs(VFdq|^1-C-r;O-;fG>i`S0UlQI(#NLUGkFedk5pSV#QA>aodcjIF8z&KT +^?y^!=UShNXu?^#}8yry-rkKmJF-AurJI6&+In}v8v;EhjDBS=>%*{l(i#{h5-qZKY3n;YMGA7kAIgT +4B&|O*l;8Y%)uGAN;_)cp;pjxmvne}V9hGpe=0Qv9Lm)8Kp?AN`$Ct&wm@EWFek@b-{kFL3#mhFfd!s +a8rYh?g1sj9km^~7{0Jzm21z09=*$~79SOYQoniKu8GhjnmzMgSH+ozx2Q*Zy6^HIs}3=7P{cz0Dax`< +(pf?q;{&3T64aMfQXt5vC}=gw_S;SZ`v}b!NyTuU?2JOf`?75k6uR$33 +0qjY<$O;mN~mmj#UhkoC4DReq6U%go+w5nLS4)=M%vpjN>{P*Q*{8NF=bpin|*re-{_`(!}Er)u-jKI +|Pu*SkYMhe@Y>N5+6Sy(cUYIuS57cEBl`qIZ$6P;?`vnM7Mr{)wHH|p2uxl7l?P#0xORrwDmncOL;Hs&F8jo(+WZ4z@+4LfsKW(~P;##Q&RwRE(*Hyx84@8#)QH)xAPXZt~bICPW9nHw~ +2rljBB0kAMaZ~KT2y%`#(H$6g!T#t-XceeC9Q$GDj)6-$`yO856?dvQ(-?$PGWCj+1~roOzy9H8!G +1-efQSD{YmoCc_QPCj&ZB++3=$Ki2VwZ7fk3$#jIbN>B})pPktxx?hve-FB=`)`|M(fESJ8P6{E?3`# +s{NcU@SB+d+R}=7G3)}4bcwnc@G<@C<}sx5lE=qYms%yP?~#vDP!B({T7*~Ls<~UpF%~!xdW*)1P2DXFRDp_h+FGTsJK-c!Jc4cm4Z*nhiVPk7yXK8L{FLGsZb!l>CZDnqBb1rasl~ +>zx+cpq==T{)~gUPYNNjm9FMyca4GLveeiz~^wRO4`95we+5qyj*(UC0041AwGJN}jfQ5dkdD?j9_57 +YAf^oeHu{Ge+>AY-mQZbis1L60*q?#)-UUL^8e-q*&@fxTXvI4UAZad}LgtMeZDsUxH^HLi0q6y`7#` +G69(~rY8seUNo9UrNUg#@uH9h8y0Z3E<`E|eh0i|!O5anturPWNtmQ*CIZJvSL=dHh*n=h@-)AyMjM` +G>1D8{Trg+JixrV~Ypj!UeUJ*-A>*}#(QuavGOGq17%CmJX`QZv_y^0Af(Pq6g=S%RgdC8(_)Z;yU$0 +nQra9~A!5{@&26td)YIHq1Q+7qS3#Mny+=N}RTl4v;;EDPAlksBAhXpTz3!Yx3fSIvd`L5t*(zqVwi9 +MW3%4Ns#OHYCo<$vLjD&vRYAJ4|4|BwT7RO=M^5h28Eek|nhirthMO+t~Ovx-&JQn0EIwQY6dl}SV +juQ^feh)|ILvpMc^b8RJ6^k^D5kFGl9F_b837C`u{0!bs!rq5yhHV4Sox6S@HtRKKLU_J2X)Ine7YAq +IBpR6|ueHxw@|#P&@o*TLl}>YI8V&os@nBr;d(~VGOC#Pir=7F&QEzr0&iZ+lH2BA^0GulClA?xr}hH2060A(`)MTVb>@eM%cDp@Z6lP80?BZ&n97G$#Kpneaz@f;B +j~Um0TRTzUSg1W5fAmpsb%kCFC3fFpUTOsp7=IHLNilADN|6;S{1?eb`6!mGmq$<$iVK(2iTisel2_$ +7nJIJu$-W#fxXPrFHlRF;^2Yf~$id4Iuw^j3oC{4f^j#<4M#Dr;%R+4Wjp9?@JBTx?}$tuEraW6xl5^ +zmlf9T6!3czF0@9FOiM3mrP5*?~rdrnosprIz}8L=O^DNGQB*0M>8Z{uorWB6RUIr0^@4)^epN@b4|W +iUZO3$whhOi=Y6hg8C_i_l!y*N9+zDrgW3q>PUH!TvC@)OO7$A}@S@I#uJ8OW)$` +{U&}JptJ<61MXe-i()z)TGxF_cDxKe`XK@A|9g?bLf3})2|rV(fkXJchtoqoziOzlV=*_ +C6rD-Ut$St!^lff#P}h5poX#Mp@g(}=yg!MC$Q6*)S!L()s(b6TaJPZ8odjqT&MKCG*j0G6t +*&TBtR6t*hZ5}yXEY&#F43E8xL(LpnWCKiO?7D}q98X|EJ!K|qgRY)cbH@hXsHkzMh>1odtn=8nig}j +Jok2fp<=cytX4&>tEdhQLhb5_{;2LUuyU26g=th*;}dL^$}T~mQX-VZW^t{07L}$ks;j(41G}cNGf6Q +Z6PgOPuh^3!ht^A)WOq-|zO7iJv|BxjRlL>50h+MWahLRHt~irSpbxla=npw)T;anokA5&mAD9F8CRL +M7?@vlF^Xo6w5nLBH>I}?D$ZIN3$f+LY3@BhhE2CFH6;gMku_7TxPuf7!jj%QYG~KXFw|HvuhiQOM$R +~gkdRc4?Oxbpw>~k;&H!4z0TdeeL`PIV{!%M=JB#u>J#jz(?w(QvM#)iwT;?yTkUz1UhGZP)!Vt%(Bs +2{efs`E&|?E?GtZl)X7R`aR|U@Z+QF!nWp4Jq|o9-0_ssxc%W&b{B`+MEShu|OU(|3~EI#S{MEcF8>p +R^sa6uz~A4^-ysYC?YjE@EX{9TuY=&J%=u%>4K&ti7}5|_G3bc>JP8n=Qs8NZ@V=Rcq8FZkB^C~9y@M +#hXBa8(Korl-{Sb>$?<+O|CMpKoYN^*=K;5AFw0;ng`YqttUYc^*R^!uug?w+KA*pJX!F(}Ow+jZ +(uhnBWSh=7uohn8FVVP|P>XD@SbWn* +b(X=QSAE^vA6J@0edHnQLSS1|I;L~<0qB=5dFzsR{X&E0q=mt>k;GjAN9kEURmHAU)3%8DANfBV}X0J +s1_SxVDudRNUfwMbyGSS)rIyNlI+^!75Vql+w85&VC;O!6qtQdQJy8ZC>ds-osnMUATFbyQx^m#2#)h +5xDW=ryn>XK@HgWVuDSG(g_}TN9Z=UM`3wlm4e&0? +DoV@wB3F%yrm9J@ypH$wviYK{nkYF>&7T*`B5letug!xDFm@hP%KWuTsv;|9_1;BQ&ZB0%fG?4LH{6T +h^DN2pHvHEy0BxVsH$arXy~t|a>eZqFY9u*-d!01UuHY|kly38OxiDbft{3Y2L__&o0bR +#?qdi70UN$gegIt;vWs#*xp8crKt~JrQo&IK8zEHCyUF&tR3p9=vNmZ-8z1R4^seT$wc+?Zy%IST&_q +SrPL;G=5H`VE${-!4fee_?$f%|4Kj?PQqvxzs#XwNLds|Ax8V3_P3z)9M6>liLP<&NE~dK%dW_%e`-|5Q3QXeDA#*&A2Z7y5_M@JFUvxO(Fd*Y%j3R*htd+Zc~%(2L3Pd#QId@1tPZ`E +0C9{nx`oTWRcJY61xzAi5i$dDQqUM^Z|I?6GOG;cQto%*)EWNbAaP@B?nZqF&4(kz7YpJv2VwzoNXz; +bl%28dQq^RdvsWmW=25q4eVhvQeK$?%AxJ{*I>cgS2c239!5JD;|vc`%j#FZEf`VR(MbD6t==1G%Y!f +R4PJUr~lq)IP`2!CW70^i|=qV{cf{8UL+tFE1JF*x?Efy3zTNBr{X?qIa%uvdJ>vmt}dq0G=3i-wP9R +k>UY2Zo@7Q4Ec<#c(w4Z>&wbP+Nh^e)I+as;=C2w58VXNt&ufBkAtVNxXrX$aZ2)QD5{LDA1=vg9~@9 +Cm(4r@#IeRY2fsLwd*1TGs3CaO=&(Vht(82Pf({K1rZG%fBZE32%~)bG&&l@U`_y~hdja=ZHu!r_^U| +fpi^lfd3J>MNX~teZDt1h2*cPx4q9dzS#9F!zcXv#Q@a+t$6TGETG%HKK$wNj2}9B5;i)s1jW(Gc0o! +O0E|LzU9Ska1*{lYg(zjoe;&Rce(a7bC55(>MKJUWSbA4Gh$9UwNgD +Iq@ww21YBnjZ{AmU9S3^lxjE^VX9gy-tNkTl=H{QI5eA(|7V?^sHE^qr~3>OVu~1t~8guzyHx(J-B0> +A1;Zr!kNdz=OuDTsg_ZTnx_8^#SVqj0I(@hfm7I=_e`Zyoq(j786QKRi&<209%#69Y+2s9OvkqwHb4$tjoj59LA46-Kvb(8UZXXXIaW8N +f^#LJ@I%fH;F5w93sS7E!dxqVEVXiSuUhW{+#5sk~DgnHgIx+W7@Q&V=as7tOj&o?WU4~glp%(D`3fU +Tx+ZQ+_{}X;T7&^t=N3KT~`b}ifL7Ah4WW)+O`9)GnG$aKTq^Bndjub^@QEYY1{8=g$LMyXfQZ*`MM( +z%)x^X8*i(n`s8p02!`dC;K(UJhruyVlAgq3f>p +Z-S`ULKu}HX{waPXq1l;8n`^OdRF0gTQaB#I6b*;Mn=os|!vg+HS93+CxzEav_2zre~Buc;lII#>UD- +}wLyFk>M4!s%6#2n~dAt6%S+5i=H)hqyv){e+ZVkhiy5+raZ-6KJUot{K|!9IXtM_>5u1GMCY_Bc6C7 +7JBOyCzdR%jX%H>@0nlbKNBXd|Kg-+6E-NDguz$UEm9Sj-Fj&uUS@LM6F9aq;z;j?ViO=)ZfH4{urN^ +<%<1ezysIH?6fIwH3k2yY30YR80N-$T&Y-L*YUm_n~ex$A$kzam{I9FHkBkG?wdx-g}Y}-jjb|Z&lzy +^rAnp*#qQt@x~j2+A?KjK>@F?6?SD(_072b0m(3@9P7nkH+T6LH6YDNqsZkFm)S7QO3MbpNxk6oF&7tNbqRb}FOn368*KD>c0VoivY?mqq`E@)8=F1jUU*5A=4DMg4a`-rg +J|k{Qnf)_71)a0yS0II7@L0o{JU>ny?Gwp;FsgSynOZD^Jh=rJRkXPWz@p$ppyw`;Q>XE1}&uqcjz(fkh9LK$Mh; +!b$y0LWVgW)HJDWj6+};)#|{jqx8GU6{PfE@xPe(#GxxUyzrRE@Er^b3I)Ccsc|4?GYnGhPdjQy>K3PZhP^g5pF&-6$*)7Z)*;q>TrLp +ZI-w`KI}W}k$f20Lqrv-B($Rf(&HA@svenuKAJ{zJ9sj=sR4;pRJG4H|Z*e0fyv?1+lDGrv4Sk9S0TKgb5Eb)}ks)cRI8xGlVg7lCzT6!iM*c6u(>O#HG2;!l$s&qjp +r6Cr4OZNuS{(1G(U9;m07z0$0s*0i7lCD~wLJE=NN7HG?(dB-ORP;~)d(~_EhNYcibjBFF?JSd|w*5w +jF)R%PN$K}cM?jdNOQ!GiV;!1*MWtD~r96F2495cYe!zT9N5;~2+Aq33J1ZJ8>bCsZC<-=H})4A;QDW +fy1Q#gqyGjZr?ft`ct3wB|?#3Kh14^;V@@=aW7b!Rnv@_2#^hEQH%z5)}7Q?vq0(E%XO0p@c8HYM2ng +xWi8pZmu6IBa$ISi%g8ZQm8A&ro&+^dg%Gf#3?mLK~Fbxw=Q|n=LtUSiqJf&C%f?QCz8~PR*cIn6k<}El)w6?Ag{<^o-ieEn +o40?F-;Qs%d-n%n;A_rBAQl!F|4>yHA?_>URbU>}OO&WskKZ>-6skaY}bN}ppeA>m9U?w-`H|b54-X! +_r627P=xdDLq-*u)|UH8mw5NRr1YK?-_3eTskn~f8Kj`uaiZjj`R%@F(D0NOXzNIzm+x^nFgJNdT)~O|sl`W)}_L8NXn3kJLYU`e1>QD6m`pGvto{`4`E +d0Lmd$N~JZLd!F}bNcLtX&D>)JEgEAbYGijYG2UA5wTqpqJS +O7(MyA+_Dx@89|F*5TT*Jckx6xMW8Y!3UT$wP=4hwT=zvtpAq3vzJ&e))I93c9Kr1kVablzi>SaahV7 +)cCX<6Jg7ACBnihnm+eit5JPI&yDB(txY;$f1UsqDv!+U_wd;caQ=gBh>&|M45prg2; +g+#D?MKgA)5L}Zb(|2-K-qPYgY|33)3Y8gl^c8D449~=a_=hs{VkUW3H$!`cG;TnOr2oAlaKK|MEPLQlgsMl{o +sBa4ZBW0V!u%P3AT6W;B@vZgVD}=uh;v(+qQeB`OVl8oD@bGbHy~bq&4=6bfjXJ7-(GMF>;86^!%p#( +J`|arI3^tJ}_x>XdYuLD|pm#;y1=qD_105EYI^S9eFssL1yCnoS2-9@#%B4_8Og{07x~8BHnCBR&;ke +q3gc@PNd8+yeayRF?8IlFx;2;2Q+;$7kAFW^C2eJsp%os2flxFN*=-QC|1bV2CF2?k#ulw?VYEjlyR< +ZF>Mc?ON7suwg+zm5!|KqY0*@2l}Dq_#4*276}?kuQ=J$fnFEWdVuo&y+VX^tXlW8+C`Z6cKUCs&m`T +J!re=NMfeNmKmx@Sa;3=r7M~V)4P7V?OVaq$CQ=c5KQTZ%M$B=)a&ZeqnSI>_MAldoRS>+t`2>TcRBG71xZVz!ZV|HIO=-XU<}i98{Si9c!WJloWnR8|8!b?5HBFmj;`@j9WzrY +$kK^J1L-8Coj!BbR78$-EcZtbe!8n;`l?He-_9?xAX)yU9u&;KY^^VyHeRQWj|7pZO`nkm4m}n6oHJ; +5k9tk}e?OgjX5&W?YNMa7Nu_sq$bwx@~o?{xm{NJICHp1lO5jE$m@$6EiR|vUQYLg@en*hncH!wODh7 +Cf-s3ItT5Cu=@rUn^Q6ttE32he;ar_?&irzPqy^#M#oU>G#&@m4#Kp_3KsC9ojRhlF+Kn#Nn3-L0LDI +9W27wMj9Cf%W7aGl|NZI9OAVytW3EwUq5))L3A~D{C)VcdSbh*EL}dvlevXE{k>(wm1z{Jd2~p{~bTn +N!tRRz2P3g>v*cJAMoK=XLp-d59rmXLtZ);b@STgj&t6+#0MauJ$eoe%?2Ey?v%{xzU{pi+F8(JlgK? +Q8?vBFT28!oLdg)?h0??3XgUgDU9Pu{4`BWWEw{T%t{o|k<+#;ISS%GTTE$6{8l8@(N` +d;%DUN7R0$ZNUc1OD0g9#8Q7g3^S@j^sp~AxI5x?6Yt>E=0an-*5_2kv_LPw=sg6p85Ye9L*WtP0o;< +|($bAcFg9jo-R(9uD;PsSV{bCS5|Vp}_NMnhb0`!|vAL2I%0Mc@s1F;p^K0N-{sWjJC|a-% +v=p=1*l>P+QH`GPS?{rYJyz?*1M=aXvdm!2TC=zE}L^T52dSs1@?L|1=hr*AUE0UwjwV<`Pp+5=iapqt=ZdQZF9ftlx|tHgpv}~V%ojYYi~Q)%bAW^%PvGhXAzy +CpMFfywlM|e#uVrqbCB%R_ZnfaM*A&TVW2M5Is$lJcxUO3Hn~mF`R4o7jQZ!p931g3(D{vtteu;G5@8 +q`@g>?B?YzkWkDrzr`Y-Y;cb$e+t=w>sUZAY$TIpH@WVHvSrHc-x$---$X}NVOHrd32Ra=1|9ty$tFR +JgbcgAkto(S|%htY?_Cx<38V5Fb@VPKQ-T}QXQrDG%(RzW>!YZO(gUgphV`u}Zm^hMitB)x$A+K|Nw3l6o%G9<`_>n{31vrKUtH`=?$~1jDP +ZA@CPk*s;A{zaRZYab(Ajxs=Yu@Y;iE$lV)&&6D~0G>JYOMjv4NQ6l1SG$sfmy_^E8&jm*yMka0}CsS +`d19)UiSCLB4RJV6$pIH1)L(uhPv?>M+kq!RKs`B#{KWIa5CJLIx@7%#qqGNufWggpa^e@9(pau5o +KSVZ+CbT8?Yj4IgGwg*jCP`*Q(Ey|(ss4;B7VS6pgLipTbXacH#Ctt#rUIQxOLb8BX!pZ6(!d+D5k9d +<@vCpGUu64@1t(Xxzn}Ll_lcbLVXjy6ey_b|qBZS;_Hb_DL;zkPZ>~_t>Jo1p73AW0;wJ>8N4FEdlI7x@&T|aGF +ks=H(?WnoC6V0m>Z7x!mIh(ic>203wuWlweBISsWw;Cv7p81xf9X^kt?n?M5Tksc-m)yQu9eEZ-rI_! +-mU0oX;QAQB*chQ_w=r=a;RGhaJcX6&)UF{O_nb6q|(JTBMj#`Q58H<;r&6pCgDY>q>`Yk8CSKgk>LC +!KwI-KP=)oEDu(+mkeJM5nNeijlzwz2%K19V%kqjFNMA@tB+xIFKVbR72DUrJ`$a_aAt&cNj8-;CelCCP0|iZqzjxyD4}a)akd +7uAMsl7;m*t9B1dv#n>b(HMv^j@1*Z6Ad +dh7i|*c!Jc4cm4Z*nhiVPk7yXK8L{FLYsNb1ras-8*Y<+enh%^(z`Q7NG)(v8 +`+-XGFPm5@*LiCN{>-&h8loLyIlDGZe{TleV>i|NB){KiExDe(cWN;m$}TKB}v$>s{5YEp~b(BwLD%G +x$!MBx9La@LckgHF?S_R$p;e^J*nov80>*GFiaaCj~ZPf8&)DMLybMyV3T9C3#9qKNS8jZxNEUKDi#pR{QFU@V4B{d9KnR~g +G&gG)Wm*R4?tcn#=Pcv~5tzac0=ha$2V+Vgf1^Nz;KF23tK7Ks>`@xACQm;#x%0Pu927e#KqMp#z$Ex +@z&ri6%Jp^_o7a4c%U_t8claLzfsH}l&Nv7^j_-_r*7kbQTQ<~vla)45oiv-7xM~bA#P!q3`e4UBBxt ++juBQv@ljgUVezqw&lbjj;a^el|yJX!HL23*JM`l`sa3<1~>awhWneDqVYXkQUk_>J9>A}y(K +OewneDdSb&!_R{{htm__J}oSvaTi!KF(q2cUnMD(qzFNUIi1zw%A`euwN`9^?uO;EXrVF1_3Y*u}?%* +`> +He26ndi6?Gi(fpsLAESPLclsiu&OCw#xvCN8E#xX4LEQmowyaJ-(6IdO@$%UYAV<&Wja|Y*Xe9D)AL* +$~4<52Q!Nn$oQ_87?%u;?p;hi=Dg`j&kza<2G>e~2M+sQ|K^yC^GeKoiFuc)$;E@VVi=nH7M{We;)-o +EnFv;NpNAAS-^2HZFmaz;vGR6$ldDPXg**pMki~*^S6Db^&j%ldMUAyNrW#V6|9r2XTFs)G$i&8muwP +Bonn{U!zpWZv`wa9Diji4oa9#KotaxUnOhAiok&K3S25^0J>5aDa;{Jb#f*bNuHypNm!X&a-fngBmKuWiFNiW(RLsa-KFN&FP +>_iyKhriYF`5z@(M&q$AGpSn1$jfTEID_1bErGoCP#rU4zTqQSeS%KQ4Z2_ +sDZ7)$`R34@{fF=%X9a%_zMW-Y7$iGXbt7vK1zCR7(ik+rzQlI0K{1?aBwCLxWOppP22V9vCki~0~lC +h9m(M_sByZ%pXQZLX+Mw_{SHD{wT^g{T&dsM-F>{;^U-UM&G%B0&!#4<9EN6Vt3$>#FxSM9(8xq?{;nW+A!iSaA&|6^UxH*8$xl%{=(=Y7U|v +FAVOqGO+)8OvGmKiNEJfh!iaP?sU(lVd4%x4_1!jwA-Ca2zhfLC$#;M$TtgH~De{Fpx{TP@ay^^o;QF +VKhI}0(M+s^(D*x_O^;8Eb%BxMAk&=_Z6jzJg$XQq!4+ +S0Odh*?&Yq-hAu4LvVQ{?%d}s}&d(HDW>WOQRGqq=vCdk>l&63Nbs>Vg4qm@;!1&z?qMbfN;_mN=sEP +NMIQIwc4ooGH3mi`qebwzqpe){Y$16m!${3g=nCWwVek;Y+g)Q@7YabGxU02u0`PBH_a{nZxxMdXmNK}lb5nLn$^#TxWrvxTi +Kh!t8r&{q?2lrln%o@#9Twg#_(Y*<0Ym_alUDj-~NP@95E*+|1))paTNUc9)3r8XB4V0r;1T=Jq6U&VT&Dr5<3Mq)oruF#SWM{Lx{f9EI0=Aynz7?ujc +9^2FvuOPdPC~_+JUF39BKDN`peFB*DSC@8+0jTmH=mq%x-S?CH}zefz)w>#S(;m^Y{5ciBdHEP{F6Iy3m +&@FI>D8`FMWKe8DRii5BtVmK8D0d$8bvkko{KDH|iNtCL@^JR${mGP8Y6YNE7S!Q(IE8!g<~&csWw0o +}YmwY(>!kOP4?$7UvTS=|FWn*EX>E7k&5U>7kpXu$<@5NX2hFdI-=x8F`kYvz=jLKU%$(y#8wM2_UNl +5{wZ}FN8?%Q{J6%6onZDMU=fbfGgu5i~1(7ccOc6%i>I2;vn0jl>y$r*}N?C?3X2nuvDZS_bFE*rQz9-c=PB#H_M|C|v2QwN(q7x;3mw_ +#Wcx^HfEkNY6BSC%($?ihbphKXkDR##|Ez!Dg6dIp_h;iN@?;LyO0lF&5FS{kj+0Hrm#GMKLo9;Zi@a|_FeXF%GeJ~Q6KNFEVmfap$6`CVg49Wj^h;AeTcw9=1i4>cDR--h-1PaM!`uaYJ1d>le&qdWr@^LYqK%n+!I{>y4x +Ti#dZ{;?r`5HxBebx?qPZ!=kQ+33Ew`K@MagYO3WiP&w|^gx|q{<}$wxBJ_~<2>nePdgSc1Xuvk?xv= +_ah>V?g${ydEf(Um{!ZT;yNeB12t8fEx&%`rN!~bOmJ{-zma?O3kxsG#pvwj_-{zs#MwsEKcT`3r^cw +akdzzcLVyqg~au_>1eSSof9iYm1#zQ3&Xp#co%pyD>0z-N>qPAPLVO4CW!>;ZyYNdjKNO0GrG$aZ?#e +uae1%Q$=btrDRPNswY{P_Fxqe1p&K^n2VOh-iqG775+XoiW?33g1hZjcsmB>&3I_P(XY7nnI629fQVp +vw%8dwE(~QZx(!y^zl#d+)r@LQXgp`wL$Nzc5!J_-mFjUsV1hdSXlLhF=|~8yYO=Td_z&@J9o2Lg2{0 +dMW_*99e}Us-F&C*;D&L(e_`^k`boEo3fj8K_uP}kJttV(t3r*OfY+}2(p=P}vjrF5}JvX$P2iO-3ZePEOU%d>x8rSJ@?rcxk%i*nXU +6?wvMbz|5!?OUTnxR~lNe$T5#cVe?*To3+6+JqL4SygNFuA(8hXxRi$BHd>di3FFkA3F2`&qCBdbQ-D +Xlgdwoq|rV46ZBrYctE3ERd)}GZIZvQc*s(TDIfz?dxdCLL@KTFtBbr&+ +5N-|Z#U--z?J=IiG*e>*;QF2&1aO_+aV3!F1x6eO8iNpYTyM>j3TlgrXFQRV@M(yPW>s>kD;zWdrMrQ +hD{od8P{=41_Pg0wS0~v`vXL-`?;C8e*z0vP+$N3!eCt%)PsThFI57@O(jETTU?PK^H?g{sSt5iXz)um32k2)o +O`S6DrkgWs31PFK!e9ks@-|lqr_N9j!s^o3#duF=W!iTH_&trT_m`n$Aj<{%rp-I%Bnj0f-9l==-__B +Ta_8U(^P-jS8(y-j4D<<{b8*1^N?k&+7aw?PBfh;IAiSf&0t=y8*R>&`iSkEo9ctR1VCK#SNZ)R?QbT +m(fhq2?p(TUnSREAE{G={b-D4BB-^KC}P!#Wkjs`197u9C*w7GbL_~$O2xNRUDGTr^(N2^X8uMOH@Mu +LR}(fHuo?V&qPNAr-^q9k(N8uY{QDh^2>+BJ`81=i**t)ZtTSNeo&on5exWKG9QT3h+!J=- +G%@+4tANf5R%LBAbxii}4$B?d|e!i7Wn2PnPWC3~EH+;(w#Ee)|W_&_*{%0c&FCe<+F74|O7ODaLTXc +9Ephwwnfc>P1BFvSy-$kIELRmYHf@AS4t+h1ozehW?iVypER-XQZcuFjw?wEn4=zq{8;ZoErxv6@jAN0z-w2 +fgNDEW>tUJ%xm45MXFQqibJ&DrsHSTsNVs3dx-8py*Y>NTzhI)G0~oC)vk38aK +P3Sh!P{pgjqoPy;2(>^2MIp8XJ4ES(Jz{S!uM8ySOgvb`PJ?K2{^sOVI^EQ=hQMFr%mP@&^*HWakRtm +Ndj;K|*s3P~*%%x7!;v(_Z9JHKyy)ULGM0nl)CR>LuJ$H{s}89Tbc!Jc4cm4Z*nhiVPk7yXK8L{FLiWjY;!Jfd97F5Zrer> +edkw9=>Q^Qif~*91q8T2E)HP8aD&=O9u!re$&s`P#btMwmM`$XcV@Y~DYEOJ6%tEY&h^afnb96xu7rk +#$Pws0tCAdYkz%1SgR00-fmtCKRBH`#VZXd7la$`hYOx4DqtZea-X0vr2N5JiX2srKyl-S;xInarXQ% +H@&M!`!!@`~^DNSvgB2ZXLEsRvRWN#rAq;g&6Xb>_qiO6;Ad175kWk}RpD7gl6TZ&={?(~C_cUWAYiQ +pVJm$xNFcv+E&2tE}QW&~-XjaRG2CRyr6^DeWrdi3AOs7N2|g!zg(pf$6;3>$ +m+$-b{S8H3E5L>|Earl2x7&q6;p#gpVg5;pp(UxV5(x!9^yt`KbsrYy3n6+!)k +n-;*WRmStdBi46N8&C{fig-9)6bA?O1AuJWf+%x1UQ2um0k7xUSNX_JOno!3hTyeResMc3xcwJFw+9X +upjW8=nAQMYrVsCE6xEpp`558vmVGzFzeXdc43m8OX!Qnp>TSgfF4#WD^XzkT^kp7wEI~HIYkE4Bk@N +1p!H3Lk}cO)F^>MHQZ)G6#JjB~j`6@*qzp{Z11`#l&sj%t%Tqjnu1xc75eMVOZ`a4br>?E +!HAWY%QH?JfD-wd>#?{23FJovOu8t4RJvx7F2mGQnaRQ9k>TJ$t?($F0veT%@>$sqDd2%Yb;DWh(RnU +0#Q&k$yAd=eU0j1`DN5?_k~~f)@Ow9=V9_;D}^^HmfQD#Xk-u0aG9jH;FLAvS`fEgArvkA0~J>xr(hp +puCcS*2G)pp?$l^HoX$MgK_{h5R}BEQQjB~CzZ}ECz@E`@QkGa`!NR}qi$46qZCu;?bBM7!J%2l?i%f +8elB%JIk@X~Y1(7c%dfz@ihS%Qo_ODpxX+~%v6ewslWb(4NdUuUyPlM<)U +7>+4u&14ez@n)6#Jrbbar3T+QQU`Z*;h*lFq5<9*+HM$BJ3;80kJ!m5oWOk}_29S098#_Rar<#P86VB +;!z|G&)V)VEZK}dB!-q|NPKMK@t__YNc>TYowNa9;w404`Q8&QTIW>gTMy<WyBa9@4O3%{HMscmxzC7n4#)(t&N?yaig@R*>DSHIES}F^zH +sBxN<}R5>HPHFAMZ~>z5>Jce*(`A!hSddpt_VP+uh574WgE7?r1lMYJgtNwu^s!46nNw>osM7>EUeas +YQWzh^?K*!PA-OW*)ig>;3@E&$QdH<@)Ov;ng{R-~x_107w47N;pi)VoJ+R#|Z&#*+DPpc&TJn2Cu>} +f*+3$d8)9r;m~btyEV%GMmX+%8Mva1o2oeKv&D>{T{*0k$Z8LX(5}KA5e_);-x;vuxm_GbOKiNn>(d- +d(cXpY0pGxwKK0~h4u(g}-ih-0Hm&e%2d;x&8UBQLgqpT~HSA>EI)xEm_7VFg`t`TWJw;u6@C6}JyjK +2Q3B?4H)FgCt&=qsD5_ALHK9|aZeu@_EE=JCbmaw6PiHVQtidl+&G8ros51EhlQ7qe5X)bPyYOMG~7$ +$%|@w0DId={XOVYtOk|B;^h89Z?rLhm0?O9KQH000080Q-4XQwuT+rSA^_07*Fj03ZMW0B~t=FJE?LZ +e(wAFK}UFYhh<;Zf7rcWpZqs$7&8N|Y* +J6e6AR!;4IY`14wW4a9qu7g3t@cEr{g(apg?5hZjmT4MSFLGtSUCTU)l6lda<>^9{s|*jqjJ +=d`EGIG6ats0zvk$;}Q4YkLjOfdFAkLNzO$e%~60hui|_=hxB&TJI&Q;T9mKU6+JgmH9;esb4X)=9WFK`@LRos%C<1rhlbr4QXS2v3qEEZ +Cj81IvQ#&JdgjL5*7ATbL@U13_T)8K7{;Tu3-0xP!XVVA;mC^Wl;=&H57jp>Et&omdT}c0EY()23MD< +WJ<&_O*HG6J1Ur#6m0L}^ypN4_uXDK%yYy{+rA5&HI?~lSShIC-f&^%<@30@2~|d(tnOG^sLnFh1bRJ +QW&^wbUdBsxl;>&gyOMr^g=x@gCD#(sDMch2h-HCyqPR$xF-d$zu_?7nUoX#2I{pc|;xIhERIA}OP3< +ulY0h|jA&ZOgxL>Gv#!3U@X)T*z7$P9jzDC~>r>v&pQmKr$ZVJtgLRliD{u&igQj{_Y)vp3kG`uu8CK +eXa4M#j}iG_eVDGtL1Y?;IgYg?sDgit7+gn(8g26>f2YI3Tz2GYAwaw=xagiTDqE+}-amlv02aRZT(( +hgyCHGz4&W-{CLqM?WhBK3L&ea0L(lJE2L)>klhuLmlhL+;EAxBEA59VXkSNhi~-@fbaJ(T|D0 +NnoT!^pEaYzpsa5{rAiOeEG1V`cz4f?XR|W{79#HVu3MkIH>$oV3?nIt>&)lti{E6T{KlTqjP3y-jPc +u|VtBP{AoFFtkn5j0Q2?S$dz5+SU`L+k&-Th6-W}nvRu5wmZlcF5`rBe(qmY|eqd06!|mS +ba-`uCp?9{wd>PDrjGB9z>{T9g5MjF(p-4byC8V;BRsj(3=PqNAC4MOrMBbaV~d1j_AvW;8yJV +s0aXs`uV3fXGeaUB1|%tAv@jiLS`WPN#~$clprpJ#!t}mRiv(+cf0h23{J+o_EjD_5LDsd`A0l|Hs#E +i>zRrHJe5ViSo>hxyrAmN)G*XRAvp48+e8u+NKv7*wE)9t2Q93|vV*xQ`vf{*gqp850gcI6E|7{;jGM7-;IaIc@w +r4&aymj)WU&@RcF8!JZvmG5$vVqXODTbx4#txD`>^^y0G7EBZ$k;C+`6<~C7xJ`a%pV9_-O(MR0l_Sw +oKP^3!X8DiLQ<1&bG@9EU>gS*s(*yDMiZCfmu;C`J?B6D=j-+2d;j_AGiLqpd~f%M(G&lX|GaCfs*PF +u1x?}fnb96Y05m@YhSoQJ7X_CvV;!y?nu0Dp9_FyR(K0pauC)<9j_=Tl+01Qwd#G75Hd=mk0B#W(dZJ->}l4T`NyBk*UyhVzs~$wndZu +b=bVl%Q)DWdRqK20_E@B5i^3i`xLpPIiw9VFpV})ZnHLOga&(5_w!zeLIxcHG2L)wB9Cdk}&iQG+U0rCC3h?J&Sf}5C<07G4j-NZM +VS=O!hu2WJl1No9(@|LblY3C_Qx3)CWm*Y)9RGRtd*`oB%q+c<={ZJ4FBq1koMhSkPa)!i*KodI&1UE7)?Cy{}9GRwMFcyu84T*u>Ne>3mX +8u;@g*TcNsklr{HV5Y%uX_WM--+7~r`-Y60>6-oW9lEC-sgC6uyGL~lIZd~X)1G+Sydcw<6WhLdQc6_ +rH|xw}=uZ{iXIEyg6spEFBYCw#yPb~0{=H`d@F$DanCo>t2Df&}&ZZ;vedl}fX^rGeXY|)f-PQ#(^sr +XumS#E +#RFt)?N`+Z)uZ*XPuTnsp55IDhYE?nCAUV&}AEFn7+{XaZOBt{a1Ot8g89=MmF(&*l=?)>^wy_xwIb6 +5xUXY<~0vdGWq~zBl@U^NIh5fbt;E^pIA6m$Rm{t}9S|l@o!^VDvRk2)b6Xi7ub}Z9j&p?*#OZG$dA9 +#i|XW^Xz?76KtF+N**-T{^qHk-Z@p6rw^Fw>E@}vt){A(emeQ)KGMqB0 +Jg5|@`o%GJ&<%-Dh5nu=F!{Q{-#M`CbgmQ$EkVw4N+42zIBga=quxEc`j^cUMf?rji6kuH?DPV;GDr4 +a)wp+cs}R7@RNE|>X^fB}@rv__53#8a?AycFUZi6;3k6l+_&_I^x(8{2C9YOIAFjP?*S{w`Yyx@N#!%MBt1Qw=n;2|C6PmK>Uhge{i6_wvDi)x* +8PEHC(0l+lFs9&b-z!s1&;D(kP2Bv*lb-(&aM?lNx!@qtk7n7+JH$x=DaQJ(JHBOZin-i4kQZ7YVrO) +R)*l~F2=}2YxlJPvvv0V!SpmT2cG0GD6?2#&-rMYEH#Au(UsdkZ*72LtvNm4d!vii#5j_DA7Ti9oKBcMjm@S+T)5lhMYsP=3 +SPa~*s;+ieCszN&7VyeMos6hU?ZV+y_<>k&Bb%cGtrH`39Q7o*S~3PLe9$SVzd?n8yuK2Hw0pLJua(Y +&0)zcRt-eeQs`#5wmS}IMrXR1ie`>T>oc;ayIX#p+!DzHro4sUX{{mG#Wo9c6W3@d@6 +aWAK2mt$eR#SqWLh5$_004pj0015U003}la4%nWWo~3|axZXYa5XVEFJE72ZfSI1UoLQYHO##V!Y~wu +;r(32$8ji#gv1{N!BOXIjv;DoLv2n>QpLMh2bXtyUS>XJpHiC(s^C0@`xW+Gdx=Q?GWf17m`aEp?7?D +Vw&+EMLd;C$-17^AMCY(aqm@}I-4NZfj8L!tIE+bh#T=L+%ERw)Tx+xVbwwXgO9KQH000080Q-4XQ~N +##QYZxg0D%nv02=@R0B~t=FJE?LZe(wAFK}gWH8D3YVs&Y3WG--dtyfKN<2Dez>sJsi7LpdWf*y(jMt +#_BlWo!MB8Q@vAP{J2Y;z-#T9UGB1o`hf9FqEQHrXDUizRWs_~y+cvsf&C?L|{F&N{6=LuFZSgxs^+> +s$8Ik3X}6QV*s`7K=q9+D>W9xZZbM8;I{h#ivH?_vjm6m5ER0&|A?Y$xf@56(EmW${ALvfNOaaTFDlq +4Q40JyAHI8X1CB`LBC@??|IXJ4raB`R;gw%v(R?uU4l(z8Elsb1`5k);cHoBD127 +F=dSQeGwiD3Qi1XBwRyEXE%VQc|OuA$##aeB7^#HO?pU7D3m{gW!gVQe?X28apLQ(Fo1!MRgFzVtjVW +`=IlP_b<9$UZiv?z8;x7=ofJB9G8o9MTiWLgfE`S7H=OI$RlasIqWP71HkYf_G*4s2^*wWny@~~VY +K{)0b=#}mA&|TYWd0}`Ldh0ir-UoaB17pd!bpwH8p%@Yw7iZ`N4v{yPbtHnDLgS&GBT=v +$oIU4-0m%zH-*{3hY|f60u1`B0GIl~-95Hup)R@w-0`Kgrh&DpjqQZHq=Pk4PJOUeBHZO684B_ +7jaL?H{#{5q>(^LM9kdIH3stVj0V$b_#LjH@<8@Zm2I22@4wqW_XEF0MK>Rgt=Hh&?*biVH2FY22v0<;+|1zc_CBrSJvyV9IK2Z7Syr0rI{cqnks^X2w^88sSj +DoMoGab`EOB7505{!q{?D5I3q1YBe?o^h3Kq7dKMx!-*<8W~9C>YdVc`Q27l|mYee9ZjPJkB3}4MnX! +V-2DTQER~QtwB}-!4C0gvctiV(J0$`Nmy}?sQ`szH|wjM>#0~vAtPp}Sd&oLdNjizq +ELVVwTTQ6Belz8Ta;6mw~Rdk)$(ivT}>Ih+;mUB19r18_18w!vOk@BeKzLu$;j#S)yQW67u`qE1v=P- +of0hQvl33stxQ*c_x7^zDg^gCP#Xd|KVFPz}CM!;i^%X>$50k2Q!J?}0A;yy08TSS(I&ns7RC~C^U%Z +rewB9eXN;wAHA+Ce_<$Buqi7O-D*lhe4%c&jVk>3#2#-_lB!2a|%=b;-k;haesIAJpvtO}AORb0-g;7 +)qb<856Wk}Q_VYoS-Gt&m)wo&K%1Av~|FP;!$R3=O5#J%ojzq8mJ%416!JubKjz|244c?TWC# +7d1oVH$)q^5ymTxL5XY7$bD0jL!6-Uqtzmo874MiYMeBkxOVtvX-7mY!rj_9rNcJ +RdCJn+{f!EKucQ$*Hs2umszVUAgSDMmUtM9hM4|NlDm3ki!d^wi`~jm|(5;XVw&QqZ25I!@(I-5r@z? +1DI%IzSCnu_*33Yb{x33~>8%NjO2k4pfp>o%AA@6aWAK2mt$eR#S7On+w7P006`n000{R0 +03}la4%nWWo~3|axZXYa5XVEFJowBV{0yOdF@!;Z`(EyfA?R(c_=7juCiDNdFwzZUr35$2rOmQJc +QahfbES#VNW99~{rlJ8ExCmXG=Z917wrxUp?wE+=kT{2@u97`R)3cX$fZyahnSsA^BcUm&HA(7s$WFh +9{wU&x7OG^BRJa$t#S=4;CT7ufSur{)uv!W3EvYxy!Mw^CjXXB@11~;f>pm{io61LYIry}he=~pK;ah +mHw8?Yi$3&oa&$Z)zctE$L!<;u!6)LXDC(!PB0{Mk<;*IanT<^brn$O_GPAQ$SU1~@SMh=U7tBaK#Dp +`z4V!^%>a#^y^=f**bXH+bvKWP%DVBG~C#xM%PwOp;}aZAF&NCX)#lE0Uw{GrwnMxwJ0v&YY7lwX>NK +e^$~6J3E^Ycy1-QXj)up-*ZK!3n&b%tH`zQ9_6 +cl}ll1%Pa13Q4Yny*@1Y0(+PddBnpS<4!YfIAAF2(l~Lp|DC;FZ@etb=2s}K+r34Oe5F;L}sYN;-#s?2id>x`o79z&gMIa)qlg=_uA-)xg2~Oib +m2hv?jeYyI%G5z#{P8e~!<_kvo{c2g?zraT8MA2y%Q*_M@n;PKP>J1aLesph^_n=RO`%ec7F;x_1zbB +a4q*5e1W|+{>j= +7UGp&{Q1Nsly5;Qib&-_sA_w-P&M`22v537j9VV~z+Lx8x&r%*4W_@LK@ok}I#uquwEn_lkZTy9Uaj$MUg}gsF>KWXOU#n`X20rV* +pOtLIR+p!_XNqR-SkUxp)A%1w-IE^6wqq3Bc3H^P%{KU7Qq`1Uq#@MfUx$qxT@>eLs)~tOJ8*it2-HR +MCL0RCp&cUzNkx9iwrA=v{}gb$bh{Z>S?|B5EtoC{eR|78gXDjewfM|nnci$a*5@}E=Kp9mEWns%8<(XOEEPmBW1gG^xmLBNT%ft!>=L{oq<^ +)d(Jt$xFTtjQ(!Vv6s`6e77HqMpy?BoReE>msRC&B$E9?FxbBP#*pc%zv6bV%;fTV@lyRQ1u +-5){(l!9-yqVpIqFhWL5txPTFllqG92k9AWI{0p$rxX-kJkcsvL2zJ4euJX5hQhAsY^Y`q@CAQku|v1FS%fJM{nI6GJC +9Z9tGNb-|nI(`Fjw$JuY&=~~@p;Tv3d7Xr;|3W>s;i?sofu&F=ie6b)@IqEOOQCsUczzQ83$ahJnmV_ +v7ZMZ^#ugF*M?*lbp1|OZZVArbw{9-zIF3=H_i8r=n0or64rbd?0n#JeIZ1NXSO9KQH000080Q-4XQw +_0J(252C00IyI03HAU0B~t=FJE?LZe(wAFK}gWH8D3YV{dG4a%^vBE^v8$S7C44HW2;pUvY3SL|$AaO ++Re#mH}Skw8lIKu#*)i`Ck~<;D9;xMc}{RO;W +(3C>M|EmShaoh>d8C}E}#|8Q-Dl5iPcKtu-JFxBn-23u7@!M2Dbm4(W1gb)TYk#pcr2`?=OiQX1vE|^ +TX55_k5F?{h5pdKv~5|J21hwoY24w!WgudOWy=jWTvCT7mjSn1Vy2-%!Z#@D0ieDn^Tb>>g3IXBcnW0I6aUy2Ck)=Uw=7I +3LgZ@Ogay>F(hku|5xHv*Gl9JetGZ46g5{H{<*9-4x$$VL1H)e~qU%ec(tFZo{8SP5z^kf+)m8g*hjZ +d2@KA73YvCc_K2AU|e!lu@#4v`p&f^5tUpQ!q7n)bT37b6v8q~?}&616zU8&RN9glR|rtQnyu+{tlkb +ffKM1qP?_9x!hw(7;7Dtwy_KJaE0`+DJDpC-GguglTk3_WO0qT!61a|fVFkV5f?Jjw?sv*+81jD23?_ +$;(3{u;_2E>|*!HyNESj0vkJr%j?EJ4E4;>qBtI{Gx;^*XMk7g~Aq`RHQ8<%$HxgpQe4jM615dP7Iv_Az8@kV%(e3bIa=*B~o +7~Oj=pDbw228I2YhLbG4qzlH|4lfiz)ZYvPZ^DggV+O +N9|}xF$|=uoV?4G&vZOEBPVDPH9D6Ge$(kn9hYV7KM){sR?!IV7=R*52$jKQ;yv +=aW&2#H)?#0-7rlBmAgJ{u>Uh@Q8))Es*i>62Wdi}TWlA#LvR@XXc%qNNK@PFjz;?P!`m|YGnTtCG4o{3+<@|S +ndk`?^rH1_*MVf-qEFKv(ViY!D70RRBy1ONaW0001RX>c!Jc4cm4Z*nhiWpFhyH!ov +vZE#_9E^v9RQ(sTpFc5$Dr#P91rYK~rou&#=p0;XCTZ=@eJxo*NHWzB<_%g>O^xI=6A?ZS4NSmL|&fV +{y9nSgH&TdG<=*<|`l;y(8l~mI1IOj}eIT;I*9;@4e^kZG@3dYzGd=ffQ2nIzW^$oZkltx-#f4*MKL2 +CH6oSjTUa5N3uxM~|kjLFmkMR#ZlNjjD~r5~q1;bIEoGX`!QwKLT=!L)+M*lP)BbsK^cy$Rv?_*W^+O +);$J{3@m00OhEIx@AxfLO{JHuXH-t+Vo^H7=kDu?S}mF*io|0@|qysLuaiF@3>as(O;r9ucS3-w?^0O +ibN8mjt*6Uy(mWiBZ}24o%egaSEM*Fs-@%LazG8nES37rMntqEjn7uoF+hx@@IFEZlIRTQQCEnZ1#djm}98I +tvKX>s@cnBIxB{fbtAGX77B)vF0F+tgNr&0^0dJ|=opcU&)(A?@1gG*O`Kqsb-(R*DkzJA$1`qOata4 +W1_=23iyIw2EzT-i_Vx#0ee@Uo5vP)h>@6aWAK2mt$eR#Q9AxJJ(b003zO0015U003}la4%nWWo~3|a +xZXYa5XVEFJx(QbZ>8Lb1ras#Ztj;+b|5h`zy3u;$)t79}r+zkpTq;^w1rKT~Z~c8#cCNPz=)S>qkm< +oV2^o7gOTn<9npA>jSX~vA`T*7^7YA46oeccD!Ne`UYDV&vAcbY{wH2`AhL1%*dknI`P;c?3{PcKt&B +r;;fO%#h1d!H=E7w2pofPDHX`k*$4o393(<7iGjupJXVTQG1q2w5oxX-mZt{37$r3xX^rZ1O{o?~T}%fm^dN%;>vfO$%=r^;-PZ0Z2f_`rr7M)BkgyzZc94Bnua-$ +SaM{JB=#*JpuYM2507i2K*mMAnK(p&oNZd1`LN0xwCnY+`^TRXDs@x&! +x(f395%y-OA3LnmKS_vI__A#qW{!Er?v!a6m1%nG5Ni$ZhLGRAXy&<`|ZpS?;nQ6N$Dn)uEs}Ye`Eh$ +Tj!$6%un--Rd&@=X$P5xqNBbU8PmzNhikDTzIVJ=h*sR1ZsH~-S6Z%-eF^Bx+(6HZ+ZjRo7B5%&F9)V +7iMy>D?b#Ed*k&p(JRi;c^IY7`7ylHT%>8%8SIB`aqlfo7f{VXUO9KQH000080Q-4XQxxznc(?>=4vt+4u^vQ!zo1=ub;TApEADN0bKnm_oZUlAQk*T +WVtN_kExWz%*J~fKZP+cXx1O4S$vc+&r`Om*Y2nai%9LwJkm>9M(gRrQ-VW-YIYLy11zp56+dKQ#-qf +{zKEzOsAzbsY4PVDe_n9@GzTtJ*klQ6P;pt}C=B!HK*Ml67z`4c61_=S96`_DXsk +w(mm;QN!o+wwAn^CX)E?|M;RVe{dZhm2a)Rnje&a0f$l1hd;3lYtHMVFzcN^Isrlngk*zI!St+~b{yK_;!p(}rj@tSv=SPCzAi%dOVmV +&V3M&G&iX*O9djOL>RWFgCAd-L&NglHZP)Wk$5={~*+!ZMSQ?;iCWw-qP1_C>NT*J0V>ek${0{{c-dq4>FNfiOW{Vq_B7E3;9yF15a<3h{K9kC_KnMN2qpLtxa2ayN;E*u*A%RmHO!|Zt4%v7&!Trp +4h`S+c-}}|QcCN{Awt@GdP3~Rr*t>wh3V*f=9*B2CW~&GCi@Un6PsI7#+IKb^;`zkAT`yhd+9vL5zP! +73SD(ohNUywrEM2%s03CRGTLb7iAa`ScZXJKVfWg_7yL5vGo7}j;3XyLBdPdeWKXB)F%b8EscmCSjI6 +&pv$gaGVySnjVN9WdA1ruNZW5oFtUdU!KTQ2ps<_03yxwn4s-Or1FEWG8l1A|wN0b9DWtEE$I3(C%yG +xyde*R$K%XGhC=faU9DhUxvn(L=E74F2bV>#YnJSZwaC0v`r#(B2OkfO~i2*ktCr8#L67@4+T$Y>@CY +6v(ZdE*2g~|I}b1zTa(}me;j2TLR(+A-anvRpi6y3;AHhb6O4P{1!s^P+A9rcDu +)T(BD@^&onffLpC;&RZd#Zf(6K;HOA+pju5!=_hcWyn^rx92Et^fP(mqmpdf7J$|5W!BS690f?QT(V* +-^Mg<^n9_8L|;vEINy!GKA;sg*{3e49K+LSFvfFCey%S<1fR{c_1B2H3_d($W{8R&WIrr5nN9Bm~TpFkTz>uwPqd6OnUuAM`n{Pe5^Lw@@WeQZ>qK!FFX@y386TP5Vm7UlF1)FQ +?M5qv<0Ht}1Ig{m((skAa2G}&kreng|ttum%#-=X%j4dHI>s|V~RP@fou6YNzQ?=@-rO7rj@gV`tAA7 +J^Khswlmnzz3aMi7ggN5A#EAYpF;p4>x+vQx)3o}NOlLi7-m;lp>mgrqjVX4sxlct61w==-74=PmxrQ +Kz(u^NHycGFftX7j$s~;%NhMcNz*R?{ZKacc!Z#_~yR$n7RNtFV>lb4!E({xtfSE5RQ=&uYA7c>&|A_Cd~4kbw +`@p`YFHGxWo>^XmurrMeok%$i9?}D`ZPoW?`K>!w}g(LmX0;ebq^C=0i6;vG%h%K?8(ApWgEbmGu(4C +!XJ0)IGbqXL`U>gpQjYXYtTH@HQ)yyb2NfJKUjDA^{D4TXTi-S)gOc2e9zFIZ@`CJ9pGisOd-d +Vid18kmtQQ9?>IDjmux|GP(*;qB#3!SI*1;?S#oGeeS&Rt3!q_};?k*{5LjV6CzDy~SPcY +qoZT)swj#M4or*|BE>vtq-Mwnq53l1@WskMeSe&-GOcuxH83iR{ +oUv@)Wm?`6<} ++z07~nh!gY?3!G5HFiXNSfQ>I@p+sMHJSfiIP@7Z#0~InV=!wUI}ti$yxA@4PO}dsLKFd+ZgXnc +35`cRl3X&au10edx+A~2|CV|u1 +SEGH_`QNw)smY0LN;W-WQ0>3sz`X|2~~dbo458jkuV>_4zH3)BRkBM_F{LHSHNYg$O-y!w|ToLUTd_M +O4DUJUee!>#+#wv)Wz>YZ?`C$ZHSB$vx@&7#iz6kmBst``J~ztUDZyW0m}%HYlkHCXhdsfp{kHLN^wA +BJjoFA(>;<9g@G_Rz9=83g=Tr7goTB`6+W8j5oNm_D}|sOLX@6aWAK2mt$eR#O&Lc!u +2!004C~0015U003}la4%nWWo~3|axZXYa5XVEFL!cbaByXEb1ras?Hg;4+cxsMe+6&z!SX>7+5l|;13 +p|{MHkn{BH3I%EEWn^qHT6%QWd3jjH3VjX7~^(N_Mho+FtHzU}Kpa4(B~Xal&`CY+BN88%9emHnVjjc +chS_W$)UG-w>Tc>r7EGyrqb)0L +^MY1YYM*7-)?J&DSC`L!e0|wtIoQ2hwyW2?Vh+LXw#1>Cnaw-{JD|x9a*;>d=5{DoMZ@bhNBU>8JO?` +RoUF*zjKF99loq#4l&=N7qr9Rw6`QAnnR9VRD_*+MZ>7Bbo+|~CbwjU(>T2ARl|R<3p)Hrf&6R9TyQ< +B-Sn{S5f;A4{=Z>=z(-*A%>dVKt=`$c)k6+Jbvwud~gKf0fO5P-krC!>Wz@!yjvM@ulr^4dsfJgadh7MsCbnQcxjei}OZV2 +q84Y7C0qV6dB;r_>j9C^vT;iPZX=xDX>BdhUgXn%`lVo6Tp4{920MKdVj(>n3q|5iZ~0M3%XAk1K<%BY0rDZB-3X1qJoQ2dO!xyNAO+bOV6B{!T0sC!bI4u?K9K}_rkcJ**01%eh8*< +cz(fDsb1~(0Q|x?C4*@qf@T22ZnX8hkcBEcW@`AQZ!6=2<7A3B`T7zhUnA~#$D1k1B@)mbi$ogQz1PEoYuV)MfvaT*cLd +gIEq61F*NbmW8zZBQ4;BR5sKN9oq!~ByEW|{1_m`l)D8fNU+dcf +=g=*iIQDYupGm1N*eZa#}rt+7sL+$4)I7aLNT!78bW)6#;}kXf?j~XG|U(_N~*^-0LBQXYF6+yhZ#up +6aXu5t~;jZOq|+LP!Ppyu=nH-I +%#;&F%iGngKbH<0ou$QFdy0m)F)?4$rgUGsNjEgN!i04o>dQTEu@JN$-Mygg*(l_Yy+v54LX3aT_RdM +9~_i4^klP65va9pUGw-C|22IJHni7*sbD!=&D;;R?21K1}qKw#$)_a1{3p*j#wv;w1}B)EPu$Z8RjY&pdHWmdKl*SkZ>T$%P@>NN@-cv)u#gGk}!M0d)ivU@mIO +o`d62a`EDV0AlBl)?JpM2)eTfazL;o82Poc0uuC>7f_hjk|QPkS4a`uiDU5gkDUDn +(DFgj(4Vm7$LV!>z!SIu3?j`j$z+KC#l6z3e(ky*QAT2C0c48$0p-;69%qlQG%v +4OYE~%j}V93>f&8FMI9L8NMd_ykNg51jv^@jGLEa@!UWMm!)!l&tZ<|x@~N(=>K0`{`G#pl3$QQ+2(E +E!r<7_+oV`t8Uw*#Np)ic&d?by5n<+L1C$w?Gt>WUv*e6DhFcC;@T@5lSlw>q3qC4qaVYdLZu?+uqj| +YbS`avV+_+%rb*QueWhm+oi*io`vD~z^kIEz#~ktGnh8^G%+zU+W;m=e_)0N1%`NpSWcjakbJZZoefJ +_&mk;|(jp8-W&40{JfeiZLmCHh;i!E2qk8y>i=-iyDXAA)*)<+3Cyz+@x&FwL)mUBtZUq;xhbP=|oH? +yeK%@>(i1q`+ou}YOV@7%L8v-0ds~%U~<{V>w1V*Ium_XeiNUIHU +2C_?{i1PumV3K-1`fE@SX!d`dvpqGJ4r@8aYxo^HHHcb>?eEp-Ny%OK>PTTjQckTy&TETuLkAmXRJ4o +=Stvl7+Idm53n^V|I>5;bcBR2PB|heqDG{H*m

    IH(f%0Y9v@x=X>vC3*4^t8cch0MmY5^c8D=Yd)hhS-8o +to7SC>r}8P|oO1#IN)+rMY{M&=)Hg9ONBr;q +?`@Q|?Gd5h)t-{I@OsyiH8=hfDQ()&fuG5Zb;=l*$P=)uQ`Y=mhdSvy5e +I?eL^HqDYbKp^BDhcb&XMHAO0LV!PhU~MLEPg0tG4*vY6~agKcKwmE#tDyg&8$OwI9MTWcIQ& +d;gbIA!<0bHx#d&_Ap8=oR@#mLGxjyr%;3{-yrME0E(J^;s7o`ZoWwc&FjZ3@kn#ef@t`Y-lxos!C(5 +dHwvpV0t7@ntqT^9v-zyQ7|eDhei&4!tUk%(egei-bgHgXGb%?!Lvif1cXIxK`FW2(`KUr9XudEivE^ +6#kbjD%Dwr%7QB$)1Lk6fB<4;BMT&PyMY{ki(6?&=zVM33--7u|FHjgWH&OPM)Lna-O)~N(#0D^sky^ +54P(|Jx06P$b(4B52dGhMn`8i3V<~AY)nuI|gAge%btby22@(l-X#hLTMu +5tSjGIu#F=;9ZaYH;bBqDYuI)7)`g#Jq8_gUZTMSD^A +bvGuI)t)K9XZyMS6^I{6@ALmbBpMUo} +fBNLvzh3RJGEeF%>>^%`Budd_f?aO>`M+Os1m3y&Mt#2pA^1!e=qTcL48R3Tc4{+527_12hhJXnR{&}QNx4**MzBv5S3y;<& +;mKyze@`$P$FIa|+e6R(15ir?1QY-O00;p4c~(=R +9NGvZ0RR9q0ssIh0001RX>c!Jc4cm4Z*nhiWpFhyH!o>!UvP47V`X!5FJE72ZfSI1UoLQYjgd`mgfI+ ++_c?{dW0VRn=t-0{L+uOP8E~bytKAy;}r9M}$ +5wdL>><7e$a&+T?VEff#IvHZggWTwIO0WWhHd8EKh%5Opg!0i2U=n?xd^oALkx_eLSNjIO8m5U(&(zf +353wo_98x@?BAhdEqFg)k>#ZB12GW?-6u4xIg>;6^v&8C)l&euftcaT{7TyT(&gyy+CWPuWqKUCi8Uj +Pm+mAg|+oe`P_-D70|?;^KKfn60_T;U0+<&mpNT5OuSgmBpwF$1mkZUt4=*X6+eHKVv;L25%md!t+M) +#7w*!KK|nVFT@%!|CcS1`bexk0?Q~vm@-Gx{6+EsP)h>@6aWAK2mt$eR#UVSBHl;~006-&001li003} +la4%nWWo~3|axZXYa5XVEFKKRHaB^>BWpi^cUukY%aB^>BWpi^baCzNYdvDuD68~SHVxgcQnUk3&#i5 +1oRKQ8(i!X^2xDJXt#m7?O%G#LZ^0=gBHOP1G%)Yp!C|Qo(;BbOriRA3;Jbv@oT@HuC^Cc4m|MF{oMH +BcYF%k8wDEXWUK`Kt>ahj59Ny)5cX+mc4{EFrYS#o|Q!olFABJ`%9^GX~J4#*3hWidQWSx!k@UW!9W8 +0B$BM`X_Ps^n>uuo7q|`86wfp3%G_*Kx_>SxSKkk)QXI_kVr*WAyyP$(xta^P?B9C#NsRKv}NCg0s9j +6fkFpR#8RokIr9>i?~_};tN{DWn6(~D^@KD7omJWivR?0#CgKIL-IKc!!KV +3gEK1XRH^BXX9X_>N1b6;}k~Y;K6V>94tznk;|0N2+ImyR`R#Ht8S#sK2l#%#*;dw1ASA@pnr +4G{E{juM*70A`NYwmf)x0}3@9x_v8*eW+N-tYS(WjeqN4|cM?^wo!BUDiDQn3PFfuP$QH=;X`!_vo&N +G<(H>AsXMSf?+fruC2cfrYu0xOo!(>kGKRkDgksE;2=4ke8fb1fB7tioa)4j#e4S3m>;AQ{Uq={6mEl|1enwEm`JYxBRPrtt)Kaa@|3T}_=7xevltWXgcm=cYvg66&=@mAi5(0tBO(y>=pi-VuM0FAb22H^*Jhe@3kYJ!EsePG3C41?TvO_xYFJL;F3feKnvxhLnY){R*g5TE`{qy#!KA5L5B)aD+t$oPj>zJjOiu}pVoE8-zMo$Gnd=@i-SrU +vwAu;9%a}zU|Ar%vhb|Z8IaAQ>zK_*H;Q5#u~`1mBW56IXJzqWKIIph?U?3T5R^ib|<$Qf12Lw(vlHLIp3`HO-sRp;6Ru +1_^Wq4|85-^H@;d+=yoazpWI3Qo}OaXKW+48mt4tkU`STcwUrr!EXF%kGdBD8IPMwZKOyx29d^`-`M1 +sfwyHh0KvA|LKy8Ik)s4VkAteD4Rp;cjLI%V;sd$`D0nKHONr%pJtC5oPg;Sh&LX^|mGAX*2cA+N+~t +mKz(j8#1m!)*e3<~IE?p>!lvTdlOISc@!KQ~$U&dWNDwWZqCFIoWYjwN}Nec}?q;gsWrET`f=tv8grm +PqSp6?xMeV{pNbO34~Y&8}NhW2<%VuxXfQujKEWOpB@BqU-&ewh`B +e&uUB!*~3*hP`5GC_+NCM2r6$u}%(Xo610chxZ2bEvP$DprjO!V>~jHXd6317tmH)H1ej*c)jgbOefI +NHjdW(CY}JJOY@EtEy~DsbtWO$Jr+nT9DSzJ7Ay%jo~2`PJEpyatw`>2^kq&=3=OPnwnS!WVUuoL#R| +!EU=+797I(@_7p0#&S3!0(={nLBvtS~H0o(B)0?NFo+hk9V0fXC%8g0Tx0@YQ!ZYHH$@Ie5co_?68BI +DO!}b(I$DW2k7#9Vy-5_=CX^6E^!2?lA|64%%-Stmr!opAAzmetgdJ`^p(-HUz*O)d4@A?SZcR*jb|Rjz=qlXn +vIQI-@wdJr6F(7D1&s8#8tcjWXsG_*t#tj;x1DtFVK4NRb)D_4X;>_+%~WB2Go?4;=&#?i(!fxNK(p1 +kfFGY`!Ge@3@VrT&&7Q^ue5dtLl4rT^GWRK7B>YZL*s>xLT$ +lu`u1D5zTe{9{vuxNQ)5Aa=4=%ltazZopsa)sFwVS!5l(Ih!24{Fi<#Nj=kRC(r@Q%|W>a5zkdlV5(3&s((wqM0$;Xcue>u?K9~&W_ +c-9^k+hutvA#Qhh{NX9@QGLPdJW;xU>O+%_Z$V*iRqL%U8-BJvnczZjuD0L +IK(&(7e$We@W0Mon^6((TyR^y7uK5?4)k +_vaU%&TB~QSzKPBBbcc&Xj3UkvoExDI@kU|BdZqPH>~8IDk|6rVPgiXL^pttw80}@g;conf_|Y|IKt$GCstd)Yq)!lLm1%& +Ex{}g2fbiU@Z7OX9LnIz&s{v6vE0*6OfGR6WM~ +{J_%jM*NoR?NW4x_^L}M%st!b*jyv2Uncm!-+yLOyHGx~roGzlR*bRo=eqKRsSBts{3wL+Cy|l*D*;w +uuclRoEqC8pH2*CZZb`+?6?13`s?$wa(Y)3FecbO|`K3=Q|tDv#~s}df@SZ!?Yi$dze?Z#7~NHFqJ%r +$oja)S|zVJ8j7Z1y8*VnCU~*?`xZXT84Oyp!4)5gRy_>5(?OjOOMbN{d$}WO>_GcarS +C;L=LhByuoUhpS*|J<|x4rj9`%c7vE$Z)xe1}~2?_UGCTXq!%b_4&vd$v-&KY24z9qC3d>nvM0ZGO+O +?kUG}%u7HHuc2wC*AQU}%@~AQLKuPOFl_(9s}H$7f_PU@h|DeQk2$WODq5nGYC%<#Zfq(i5~PpWL!P! +biBcg;{@-gIvZKUGCMp$v4OF!M+9_vG@m%bfgC~2u;`Lb*mXRGS1l{Dd&8b_D1_!U+y?Y&<9=)CX0vd +mo?7UrZ=&6F|-PU6mx}ICN?9lHqLzlGw?yksfk8IuQIoN|oUo-@>7+Py>cir3K$#Ps?UI*mw=B*`(X1 +GmnU=1`pr8E&cU%AtVV($A6hU@gH${Y^0`}A3JMrQ}hS6_0{xVBHI5BXKjS1S1&lJCTjeAlpO#L-KOI +2C>{wa+gGjlA;pJh>NQ-vN^A4@Y1Cbn_CQXO5)tBI0uFXRmHx>m91CQG8p-zODy1vBmka8H9$-)TO@A +Z;belA2&iyJ@w*}chQQz&7*W&Ow+y@a&FRroZ-BzX%Cf+Hc3RAl-4t=KKb4{qdBzK?+Wdj){s&xKzeH +PevmWqmK?EF0@pM5RodO&-Zr1~mwqW}*MyoRvY^xxH*o-E%Xw=EM?3G!8Ac=02Uq9KQ7;%lG~xc@_!( +wQhP4b?MiIVp<3$nXgedZqGAcm~{s2%*0|XQR000O8`*~JVz>+DQ#B%@u|E2)|B>(^baA|NaUv_0~WN +&gWaBF8@a%FRGb#h~6b1z?CX>MtBUtcb8dDOk@dK*WQAo`zAQB95y02wSvc6+B+Ub;susk^mfOI%5|r +-$YOMW8@d6Rd(!fGF7GM?2qnr1J_Vu9=ZpSs-Qi>^FyBw?$TEMcyJK;~q!ZteIE!<&$i?Ssef2;PCM9 +;Ix=s7nfzWF570io|W0t)0YRwiJyamr@m3uW<|DaE~{Cw%of$M1jzNKsOoZ_-Bz2cs)qL~20hN2wQqg +UZZGE5x}0sA^)B07mFu!u +?91R(4Us`oc)s76wq&&=$LbdE^HNy;FqMoR@8NiK_=++&1fL9(>)bZ)AbnUArl7^!s*vke8R2{AQdL^ +*r0w_zocW7ciBVn`~CVW-MEQOFOfyZYo-#Vn-bvPL97pK|hk3NItD(2-HrVneF +wcFLIS#M}^)9Gconaqn#F&s^&2RG&Bs+s46TnCFaK$uJx+s$@eP9|A(L!T8FZL{2N$_f8{a3IfNlKN} +oeybY&ZByNp`up1bw%M&p*BIu3(=dNZ7xLh?Sl6&au6?^H+`GlL2CQtB?&a$Ga=WRPZb*a!&Dy;N1X# +ch>EE+vxdc>2B=JqEcC)N5^zZVfsFr|H>$Y@Y%JsVTQ!HhRv+jAZG_B6cLBGJe}%yuY}UIc2O0dxTYPm;ewdZ3P4y+N%s0Jx0kt8t9o@OUUhmf1a?e87aB$bK?Rw_M*=pTf6c@`K0EH8o{pIP4B-}s4p)U{OQ3Dd!xG7 +CL!C&LNU3%Bld4`c@Ce_1G2(S!7ZseMH`^M|2<^x4&{2#pkRia!6R+D$Mmj$nQ4e@tPyhCrV*k6V1^`` +b;he(F#oJ7`Z~lapf(v};2RLtj-0&a}=#9B=7vM2#Z^|dky?W +EnT%i>0#&z5CTZ&wrdKK@_=DB5`z!hlgs;BHwLbYV^C_{U!m9Z11J9!DPIg){*yTkj^T24>zJ3(hJ92 +d_CizMU5(k$rs(b7`4y;5cFBi3`z}#B~7{mlGfs^JV&#GEHum4+yuBwfX(;AAQd(!BDVO{uD$;E(ZVo +7Wui`E1;dkvJwMa!++at$=p(r$#FpBc88VtNF*!E9j+PiT7;ayR6*Cd>*L@6uqq?QU?PBq?_~^*0Mp4 +T;hL(m4iU+}b6-(gp@`jfFYEWkvYJctdjS5ryewur10DzjvQ041f|mQ|Uu5_bIXnD%2(OWH0Q3Fe`c~ +#52WmLVx2rief;aTSfYev>YPQLd&R&N@3%8aC(}U?Ih@y# +glRvm-3{94e+PYD-epK|YPJZuLsAYE))O9OeoAJU3&WG?V4h10ek +A}vLYY7{&RQk;A;6XlXR=c5FQ0j>q!=i)a28@!OL35EK@mb68;ZNBsZWMIcT>QQ4>onZBpmX`GulvN} +5<+5+H5_hOVumAsEQ_0qd67LR)|c%QJ^t7mejqGWIiKj=KJ@4Q4G$ND+16eCgBJM457o`~#&NwM`|3$ +KpihxrA)5_&4FAos9}itbI=HviC3fA?m2p$m1XBR6l#SFOK?BC@2X|~Hb-amR{A|LetgwzLc*x} +x2>0SipaK4?v%Zpt!nk@U%c0(d?b_+zSs5eZ|W>@75FQ$->M+yhUp)H7Bl#!r2|;hyR1R7?zTB5>2Nx$O-qnm6TSvP_$I(4J7UxJO1FtNWez(5{BN^Hy~}dv3BJNY +k^mTj9FE08dXJYi@U2ZN#P=TKL}}`y?9<@)0xo +7tEpSl8k^(jnGkPYR;h&1Ont45n3K=kU%P>47)+8g=1YgWR6coW^AUHP|!>`+V($3b^YJ-2#kzF_MD_ +}MBZwquVW&o%XvAmDAGamkk|FEe?$|1XV|ZHpvxlG6!6EY&EF_I3!HMGI4wI`;XgA75v0ZK^Gmz>^Km5~m1#-*|Ww9m6 +J~WPuxV@6mYG9^_-F(k8c*_%bBb|Ly)Ih4-z@G=3kIkHPg@3-leLQ6ha4YFV6$XKjmjr;$1AZo_`dL# +ifD95O+Yv}9e{MiD)n4jbQltu6`;7PZf&Yxa@ERUzGE*o|T7qh=5J0H;R6L`OD<_%eU_k*q>g$d +iv&X?$5WczkT!Uh5Pf_Utc`?*Vo^^a}S=s`0mBmuTSM~?fLLjDAi#`g69H`CfrKl5fB~my1c}3(xZpV +rn%m(PysDNd-)AW{iw%1HMH-g9trypm2=uy_u~}S!#{(GUS0-&-mEq|3!}sbhRiaQSKH;%wgM>j&*yy +Z?W2FMR;LjVo+c1jmKK@d$)k?I7`$aA_XLFuPqaP&73vC;O50U~+I#-4FLepq=OtnWEq~`g>br9O@B+ +HHE!OjhBSUqEOc1WCo1&sq_w;>HEekk9&qKm`aPH*Xi1rK}ZSu_S(~FjXba)s>g*vEr93?f@&hMsB=ZE=C=J5}yLsF4`ogvIAiTKckOwWX?x!xDj0(M#GOJ^QUOtfO~PiJFf7gE +tkM@DdRR17%g*j$P*$NE8C54BrM`v$G8DAzNzSt&;4f}c5u}3R0U88mbo8{h>yQaP{F@s3H9)2nmk^J +zaanSy8z%BzXZ6y1X?l%^Dl62R^?jM?YIjFW#10q5}!Rf@B19!-}&|9^8?c!v3~EC>r8W`>&0gH-~n0 +OfN>moIJmwOf~Z)KNoGkfPWfPpr{%cUW@`u>+^!>;d+&J^5Z0o^>y&no-Hi-w$7G;6<%R^OtTvqSPntUZ#z3e! +x2{4Gj%~qwVZcqO<`R3{WeEkMR|DS)!fBt3QrWsp#MhD8iLgUX54F3v$e|BIPSor(#LBPSnlSc=Zg@p +&lk3|`mV!(#xcQSg~;qR=Tj=#;OahoaoYFwA@YgYJ74df|rVDN}^U$BPw2deXVuV7&d=M6EP-Zb;=vI +KrczMM>_%rWBWObmrwW}KP`Y35k{M2hSILwk_1_H%4BAY^EmPCI<~bV_p};wxhb=zL!;(R_kz^Qxu00wg8d)7WxQ-Lqu04mq;;!5~dvL!>SC-3X}~ +0t$3rGR70qneiX(nfdHol$E5?`qy$7DYT4j)%Nv~;CX9U8;F*2fd%BtV;B%K)b9nVev)w}AZD%L2n-X +OJ{Hg;f#|gDR+q<$l#{b_S!|)e{Mb)bS*jDK21OqL1Tl0=i$5S$!}X71457m}#q1Py-Il;RP(XDCu^t +l6Gg706Z%HnH_;q!$F4nt;&zg1lwp_ojK$Mbq!Z_sg%^&~+orqsU&Wf)gH5!GTNz1FErBzpl9TEzQ`6 ++=-eB#E*%xk@&HQ{e__oeih{2DqPoRYGBBa;}Qo;d)T?l;~E^TAmRQZ8V^Q`*cNZcC8shARA!as@TKq +Y$mAF);xFwiN5x)lhsXLbJ}#gVW}=T%SH0Jjn*D)eLW5dNP6SM^Cu+O5gl~MiGUAo;VE7ke^r`ewR^3 +^j3&$`t(?gu(N=>YI!~W?ce?_d&V|{<&L2V&2P~n4IJ2Xy4Q|_K7}ZyRcy(q%W~eb%fJ<2Jzq0lSM~M +-T?eY^4ky2V6;NoZ;Ja`NbI$tCaHE +i|~W(r(j*i+3qum$TfM +{QBHEQf1d+u;xMPe(uG^w04ye=vfr;1Tcrnwtb)M?j9~07Yu51B;+Zvz8q2l-hSF7uVd&|NhWFD^Qa& +FCA~8?A3hg?QyU<88KGyT0!{Ne>@Py46{_5^|r*#EvwM(SK-0OI~LEkbmqoLHco(CEOSpqUeU=96?J4 +$jcEW|7`JuxVF$FJY(rWAW6?R`Ajpj88JXajYj<+u-g~?~%F3dJArx&HvrXh^DB4D6za=V(ww^*QBmf +iN?lC>O9nzumCw>q(#5-WVHT&I37Oix +8ZNd*f?|%6CD5*E;3}3h(M{j^O@c7xk2UnM9q0%0pMZpXdwE|qozM|(biri;J36h1>HYTUuY#0(Ub;G +!9GIZBF=yt?v2WR#lBDXg|7~_neke$NcL0>m$n+Y{_Q}@x7&1+lo6Un9q!c@R1p|4w!1knR(fjU;$+< +Y++zc6mYUXgWsHc)fP;9R~A{I9|m_zC=K6|{a@D4*qKzGjGHfP>8UQGBDTOTU_%kWp|D47Y9>duz;-XU?&_a@{uIX2cFDG88&_<19pI*_kOLsdbZS7TJ_OVGR>u`INX38oP0 +_*WHnkq;VndjLMG3ppkQ%vBN$gjYn7hD}y1!M?6*@x8vOwTe!TrAvC$)aKoVx!0sqf8+wn`#W8sA5R3 +0J(ocE)X~4>Pe(W%5EYX^>%Cck8f8w;&S4B%+{uH-m#xCQ3-jsYJ25QkLlLYgtU!5eGvGe)w=t)1w0Gbk}=FsRu^b^@|VXjADx1Z22q +g99?{uRXO+HXXxGxI9#lEY#NcX5^J)Dozr8?f9~!g{kvQXt$HDXh#v=fwjCVq`0x#H*z0WUKALow+Fh +t5!UmC{fLBCWg0H8ZrBm|J>1HV8=&|Ry2u1-| +$z&B2llB5$GR0VkE>0akyuMF-1<0VEsBI!J)6`zAgfy;fdw1AsVqC>b`E3dV~?)A4fgp5PNSZV#2Yq}@GsZA#6AS8y~F>x1F*;+nDdVU6&50pu)RpdfJw}W0i)SG;s +4MiV#qT?7@*AucHYtxhL?7aJCH^DF&ANb;4UeKGwi<#JH1fmt^v@20Ng%X<|hmO^Mi$|LIEezM=hT+% +j9WWP;a7h1EM+MqM4j}i-%{s)J0&Tp**|H56+5LXH#vp#a2)MCb`LsvREly&#w2JMHDqKR?QU37(>=X +5dZ6}jho7@RP)t@6uazKIyM&7sI0{G5WBFcZvv23SFrhnJR(VT#WdX^$~-P;EP?G@N`+6|Q8sg6 +k@2A3WZ=55O%VAwdaaVn%)vj#+1HQccnp4_#w8>{>3jtB-!666f%pI%}g4p6bYE#Put+c9?=POe06BU +Gudi=`Zg74+_WVDV@8PL=fKtUwL^wEpXcz$2sQVtt4Ttn+#OosLoa`Wo%a(?txzDiS6zk_ +>wEN}|`hv@B*n}HRs70(sl|h52m;SmbO9|an{eFC@VEDHS!lYA;qvRmWF|PD|$0#EsOt1WnPMia0HNw +F-yXVx7WDNKm-QY|+NRv0ulWSHtAG>C%xbhriD@$klu$Uq$)fiI%HX({-Mfqez(T(&_ZVUJ*=CjaLfV +UJ;4OEYXYzwI|W3OpGLu>1~6)0F0Wjm33g1<%qmyiWm9~Ed>Nd}f%Q}wM5zo(IG2^%DuT4Bk@8AVAUt +zBBUBsV$rEipXCG#TDILS_w%G-kWXEiMpc*MOc{H!26_qCp*&bVB})JT|;VXjp(9Lt!esvVV;#lr%ob +N{0?0G^>yoH#sKm$1#+cdJ2y?<9b7xq=pHW&{Wuq6`lo?58oiZhfDulo +F)}(!wB>cILHHq5wT9g<{U{&HreU>HsvA)#m4ba|gVbVnd2^tSk$5q;$lMqdDZBRQp-?E>#qlqaD_D2 +SVEkZ`dJiXK8j~W%YGl}iNoC||c9=|P(V*qhR%0JyHRkmeWhvGKhnZOhZ-lo}GVxORq(6xxq=(55`G? +ZdZaRNm~9($aUq|+2RfU4da1FH@TRRi75&Tt^-Nl!`qz>Tm>I2`B_oI%TT@&iw`;GHoS05uOdZo{kyb +92I!4_KLUy*WXi1rHo~o1HM*(_zVlp^>zDj?96y*~}`FCXjjFkl;E#INR3lP9Nj_?*u==31Aj6A^`D~ +`A_cFm~H|*E78imuqbCRY%^#>o_EO1_iaI*3=so9%8hj4I6NZoeW=aYIFJk +pjtgMfKnBD}K +_$7#4Bp^)f0Ik&`cDaEjTHMAa7YZ3dj1~%|5OJ5lB&D8_qqr{ +zAfmM085bJ-?6S_*o@3;LCM;u2knAlEvv9jK9S3vh5RiVxVXVG& +L`4pa3#4pfCfkk;Xk(qeQKd2yjWN>_K~gIODg4jO=R5tz0rbKTz2hK$GUeX^^!0C@+xRG9-7Bv&2B#* +c+42p@eX$^@gr!dO_JjOV-Ef-PDE(d4Kt+0*nm_n1o4HVI0R7tnf{-%P>tt#+I*}9cqBTG&*|x;v_qS +7xeofY%a64oGi)5of8J6|mA5b$uq768hQUD7C?Egd>MlPzA7yz4Y?tGNz +wWwYM;Oi$5$Y*e_&d~8?DHEiJSPXj!mjTqWzqq8SZjvt>#pc-vX-Q-(hqu=*0!M)eY{vgDYz#wL +s`*f%teFq%be#l3dB-ESsO5X+NRA+_*4>gj__f0jw0~}tsi|v{mofC;n%<|p=$l}IFE{>HGQa`kV-nP +=)-XQ&%AQ-a!&|!|xCLG|<+ZCO&+tzT?T}Ils?J_=zXUXq-LpAS*U@pCYCuc}m^;p^P-Q?$_m`*05ps +!8#g7{N&30Zk2O;q|6=lFA!y>3=5C;d3YtvSrvCR-F86f8`Jpe|{VSLlrOW4IW&{pXzz?$XKUL86!d{ +3s-^aQkfAD;IhbFb$0sWC$4x{DHQUh`}I%P_>&x7KPGBe1_S1zPvc|<1hx<8K}!#kQ}T?a~H%ib@X-- +aNN(hzwYMRZHQW*lOYi&DXJb;T&FfwS4aSG|#K~& +8y#zWDQWF-^`PBr-&X|0|C%tmIi(o&A46n{I3G#;wfOz4KX=Um=GvfA(4+RE%a$3|UPr9sugE5w5Tj- +<^B|Jz^k-!_;E&b1{bvFuq6rmz=3j*}hWp&XeJty!7Eh%<5@ZmTdcMKX{0`c9+Y$CM4aDc%x5?06!sw +w6Jo%kr!<=geVpK3wGaj|}TL5I@Dce(0uUzL6zdR#{L_po`rhy9=%kjWUh)NpOss18|RSYcQtXd@?thPWUqKKL(iEnl* +0e4dtoZbX|Le@LuziF6{z%@fvD=jKc(1kuy*|b&`4>bbluM5l-hAg=kPHBu%E(zrqJD0eMJ6Fw}R~qZ +?$*0q7u`Ez_nv=n)#Z1Y>hk+2xJL%OT8_W(wOr3zun&sT5FqXtIf<|okrzFSiCdl_p`|mVI0&1SyGXN +8oeQ>E^vd@k;Tc9UTvf(sXvnd)D@EB2e$aI9eWldU9>#lAiw!xvEC#Gr&Op-vaOFF=asbfIl#=E;|g5 +{o5$5)+^sNGU>c_%Blk8XTDE1M1&Md-!IIuw=Yxc51 +Wvvi&ha=y!U&j{YegWuR7s~(zl@&w9#BQ>T7jfmaFWt*(da)qU}1d=)GO`VyoNiL|i}qgtv6EY=;HWx +3yb}ni`|$8Quh>v%;cM=wpn!JbRPc)kwmNW*^ffnrjcg8crvse8w9r=Fth~tYJ{nkCDvrAdmsW;5BvM +FE-IYy(3s?o+Ce95qswMG2(GJ6WQ>GtEO$M3t%W~a^M>|6Q8-rsUG84To`v6{}fr>>bc(&r_U!YJzg# +L-epeB*b}=jX$`yq(^z)gbjjIyyIc^z8AUvNNrZV_F3a~t9WQ3FwsxY~a!dXN>atA!&&0f_AA|3)lH+ +%$&yS4!L|vnp8m7JWJu*ZWfs%Cww#V1Ei*5p`-ZaA>=%Vn=`hGXT`55YNvq#{xLl^!%zg_u6*cs}T$_QH&P55vX^JN6MU- +UCV3YlM2=N%r<(i|M4Zk9Kt-%3Pg2IUjfYMz|)>I4}*!vo0VvS^(wVhIvuL;QtrWRI_i*E~rD1O*{>!XLBZ5_Jw69vs`0LpLuwAoui*NvEP>Eke5@+cNh-H^ri;j3=tpVIY +IQvG<#NFKJ>x(vA80tBj) +S;Z2xSZzmlPVB5&Mh`M#gd2@wUWdbtIv-b+Kx%nuOiAq2Od{Nq!dTkko|AlfR*I6{6YHq&X*jC9uLnz +M`s%Cz>WW#ICnG;6A^cc-$U=o_6A(oHNPP40`Hgle}l7TSSj6;WtLz7CAB*#S{7cvKB3^Hw#;{HpwcZ +bjhX?$KhNkgzbvBC=&35`*At(6c{HG*q^-8F?Oc-7;0ETAF8{YRW%il%i)SMp-q-~cui?EADDpS2+Ni +{5R5=1V+m_9;LqQhE&0EgB4H?^n>8N-b$N(>^D_7mho!Mc6q2M}vvu +Hr%v^Cg=vDRri1&k-k;g;)cU;~Ll^l&$I8Xs;9!Qrq5C$QWck?i3B)ml^x(i3q4ahdh6Fca^4Y-pNIg +{Z_RP+qZAuv}kyqSa!)t$iqz<)q|gt;aF=Wb{u-<>&oe+>2WVW1YSBndGAw$p{M3YN+cQ)B${AyiUyG +whm@WT|~J1I{7bldtfL)$NYq*b=W! +%(zU|4J2(%+L*r@?V@VS&SHs25d?at5P-Ui?R>#@~eMugCIE!l=A#*=2lVHZcsAnI)fSGl)mU4t@_}+zCxM{}W5^k3E)5wX2ASJS{;^Tf# +9N&brjL^{u7Dy>J%{s?C^swNV?PLH`xSHLp29#(O=fXiVX5IU|r;KQ5%=KM2hu-jmgv!;{xxpr+) +oMWNNmMVOAKmRArfaw}G63Iaz*9%qep5&RYcyTp>LSr}?tb=@y)3IYyg&NcM_a+Kt2N?**?}friBzYH%? +&Y0QE&z5n&nIkJ^uP*PRqd>f)}UEqHZ5^V?35D;g23;S2 +q%o7vS%sbgB4((8AiZECIa7+xpxBJ^jJmdJS{t;)mBLtz!YdPhBeu|xg~@8L)nky#gX1OPqgHHCvYkz +PazF@&2b%lDIu&4WGYhV`>JpwHcImlQX^*q0hMi_o=ZTXkci-PQOU&E2Z_`@;~>T3^G?3S;9UTSg9M&>xC_qi$BPEh=U7za8MqYMz@fwAw> +5A=2R#9D!#q2j+PoNYvxm|FSo>sccU`Jo#(D2kY2uzh&IO|ZR-ly{K!v2sC|$7l^t*#N$pq)vVo2|=Z +{Wj_hjM^2S&rYUe#`j#_y~f=z}}x9gld!IDxNx(wfCx>d3;*{Y2F_Dla$oZb`aFp-K; +M$vo^;b`!Cr4}TE$cp8>#v%8TH{|m*Sm8ad-+Pi{MtFEGcqSd>iaH$BuoD7)8C&# +`9En5Wc0sv1uy~gZ(V`z4CCbR|A^V*NPp98H7W70o-Umq_^k(Ahx~Cz+|YTtjS`&oUn;I +n%hxtA(-`ie{2rk4O>3`k#Oq<}(=6jpvz!CHpNRq*e&ad^qcanE4>N`WT!%{>;^ifeB6MyHd60;A;rMBgrzI(81U3L95|daLU2h!+-=Af3DsEBh_5IHin3)wGn1pKsIUlpqs&8%y*6*gP+ywi-vlmdd%>YA+h#2onxDgcKDU&;V_Rn(V~J +t7+K1&wl}>Y~%Xz5DCyR}-vB{OswozrF~-S?>CMv92^>0cYQ1{+b6?v1liqgq67f5k~ovsyi4ZIczBM +vk~u#xRj_69ekE6%NWHyoRsF*}G90CMp_3!@Swdpb_tWo?pRCC+PwXyxS}V4aCzWqC$is}{K_$nDEX~+f{0pMP3|H(8kF ++v0h!qy{%nr9T(Dy?g&*kNywXC^rQ%~%W^hHr{E;|;7nacg*t7SW{Tx*w<{3^tAb~Eht<4f@#m-49w1=XP4yoc#?N;1Sn&A33~F{Z`xN8clDyM; +p!X!sB|kDR8ORf)#g6}xqzi`=%>Dw23}Y%qcxMgPXkUlXY5s4$qZQV$}sC>2{+gmv{3rq3;Qs8G(c?+ +d9E1>(Aw?9m35UDwU6AGaaE9bdG!rO6ynW=Eg@^A|K$3ZR&m8+5|#^#}m$)^w+XdNc5~gAwIT?cfHJr +PH-~Hn_#$$o1enQt2IGs>AQ$U4!WwR^^(q#ETccoa-f}0m8Aeu?%RBY-a?D(|eaUD;sFb5rZ%~WG?7d +ztHePTtLjc+Qe}9sBzLMSZyq+nNTIWM>kk(mJU6lE@NYkc2;<9*Nxi!Ul@bB$=^<1e*gO2Uo%?s@bO> +XoQ8{Z(_VHQk7!#yq1;e*VwAP^Ax%#LFSbiuLruR#c$gh_L6P57;<-;#l@xjN>=|qpQh)T~MdT6OOMr +6dhzWuiCfd;B)2zkV5)fhzF3Gixa#X%{BGE%eS+=#!@s}Yi@ECnJ*5C7EBfP*hvXOKI!U`dRF&m_d5R +x9?bH#UUmvGQD@Fb1XQ~}0D@2ce>T&iw+1?)?S3O_A9;aGrqQOMNeH_f_O??wTyhG4`rt$Q0}DR;b`&nO3jWN{m#kai-5kRi%c{S}K|boI48@wyR +(1&paa3}UkRO>wP-T%1-COPd9@cl;6JG#n~<(p7OJYLxv)_Ne*di!Vk%g&sBk3_k$`_;7Xw4Tm1{=`- +WL*FD8ftr6eMZzcOYG1<(*6Oy~lb}Q>|#Z5yDFh?z!9eznpyMVhy;5}}t1yziTcoO~aU>kAm=<*$hK3 +6!J*yT8gOe3eh1RFR-2Z-VKC<|D8!8y8ghtp|HzNXV80UI-I#6O#@$vfAfB^s29ZiRTEvt5Dbj*er^Z +0jn;yW8Et(_4PjJ@-<(+^jzud+nv_LMw5}c$(Xl^+l;!vc;Y;IJMTpVG5jSojh7R_1`A;3nAle<Li{1dso|vOW_W-%cxluT`0qmA*OU|sLM$Zg;s-1 +WrJ1+vHtf=4Nabi74_nK$*nM^&J_E`4G3-mINkX?vHlCG`E3moL~-kBrQFIePsvT*nNbH=4`XB|@4T`yS1G3ZIm$^5QHwp8D{?m^qYx*)A-!74 +wz@hBW=K~`fDY`tHK{%fBxL8Ku%q5G4U{+_Y8TWWvTg}x?U*Ob2>~*!z1}Jq6O8)2858oRB~u>jh4dZ +m?<`xa~%$^TQ3d@als%`K1~%0P5k*fB|znM5M8klPa3;q?+m2AX2Q_r6o;Lr%@E*NBULMls0 +iPMyfQ;uMg~-5j!7`1r-;WqX(h!|B9~da;i@fd}yr2cC2^o@Iru+Z=}?%15*_z!<1N%E0t4Uu%tJ(g% +?7Ev%^gjNPGWChLo8LzjodB{8aLCZNEPf@~NB0|NxsTh1bYKtN(~n(h@lU2Uv-!7N)hJgn(R&xd2 +l>PF4nrt;X)k_&i?n)<9{!Xe|mKM-}CcN2V?votaMHDQ@3RKH^PVePgVU=`DFkk&^H~yjN$fPRzLC8( +~Z4hL)%7eMkpa`EYo+`ZcH{ +L8JG?n)~9+WWWkyEmkH@u;e{V{&dcxe|~S|I_7nl3?%e--Wc +HJ1bU9`ELBGz#ha4;EhS)Qot~i6C;I#!-8X;kj|iDJeqJ+?GR0L(K-1J-(y5GzGIW{>5J3si${M5NG1 +dLQaiQTo(N3k`f`zEK67^7f2cIpI2fTl8*g{OtAB90ZH)I>nLBGl(k>qdz425qMjA0yD?ec`7!!9eT&E)^7fW +-$xSSs;tw+?g$$0 +RBGQ-KFgCLQuRxS50S*ri0UHcPqtM9N=X!K!0q`K`X(9Dq%*f&|d%6uAe(u|8)#QKLZOZoL>!BthxNi +mXJ>&3hHy<~7CNjAVi{QQfk3E1%V1Rf03pM{I($k}!o-ml{V#WoDtGbdd=?<6G|kX9n`S^)J+k%U$ajrvls9<$$JL>tw>0E6j{DiY +7iGIpiek9wnKTmvw$v|4<_j1OM%X;{USW^?hXoioPwEQMfPN7M^*&>fHCN5bdTZgQlMNVaqs9h*hR1< +V?|Ni{x(})nT2nZ41qc4r(IdG!C}3g}Y@lq^;~sum{V`ZpRDd0e{SD8DM>j<~c59)jBpnKAo?c%}W(n%6a01iWOi +b)^w5*Ip|%jS=-uFc@&WXZ%%(S?RdgKivJ2DiXFiJD~xHOVYX<~>%YgCWV{!JiWen_<%@(L{ia%k;F6 +b7;5f%)co2N02jStMdmCZz+W2aBe8cu}nL&4*0{71cpQ9fy +s&-xc<__F+VX*MO|ID#sFN=@sLv(%mr+C=00MnMMf^vtj78b<+qPo1=9BE;yIhTIRRsL;ZvlxfMEempsyA^9a}Bz|EhJqJjWL<#*F9t3%)^hvhX@;jdX`TwFVRJgYVo@B=nilIx +(!C$nVI*?O|8rwUKud2pzb7P9=3toVld3}|PV#EWY +LZEl=v(+CIuVO*bR^_`;0jcqs@1f1W2a)+vTN8WJA@uK5^2{2h{FE91sFlgaS68gk@z!lm1YQ5POOXI +7VPD(l`h;Rb`1u!0xJ)$NxAZGc{8m_weDxmk;bWM3glB5@)Pn$@$KZ`}K4IggEi963lI{*FSwI*CO%pGS*G;ogcdfV)7jLjncojO0xj%9_ +^&^o>g_Y!j+RxeX#-X=C^$vEoCQ?yCXN~Et{d%t)b3PPeeiB$P}rP&Yww~W8vFMB!&+ALv7_-$q +^R`J|d@d>E1z8Kt#IFkbbK^Gju4^g$x!%P!qdZh{`79a^7V);>N<#4vNr8$IhvWMPDHh4=z|1<3T*Yp +N;aQqA%E$wp$LWAEBZ7jHOyz*v7NzK*R$?B(NbuqaqFtt^QzOk&yAzDs+U1SDG(R>eloXt21d2||bzX +0Yk!1A^Cy$+-tHD{v()`Y?lv}y##oC8hurs&Yv>+Q^p{a7UXifUyqi<^sik$HT0V)@|x_#!AU7<}a6u +$u3_L?swR1nWcveqEG3Mgj**=9lu{tJOC}T`h1CQY8U?s#bWws6lqIt(IDe%dJ=vZK>su_H~b@<4@Qs +tc})IH`TIOt9k>v7geWoadcmksuSzmuyxH`WK?esqpUXQiVstKiT43B7}oijsv)NWW&)8lqc8^K{udG +^MDHwkGPt0?7MCWMf77=TH*_MNec8i9XBBj<^ +!%0=ByrfuC_;P@L~N&5 +C3E}vD;Ity)g}m?0c+Tw2^nect%Tf0YZ>1OG@Llpf;UM#9o=8>FhjhT+=2arY@zEW0-vz+AlUMGH~k} +Bh0{GTE=Y?u!qg-0*~WNsJqG9fC3OlYG<)v!eh +n4iS$dp=JXdr)-2v?%jC)=IWxGHJKx&~W4qvKp_{)sTPnNh8vC>`EOb9@TUg$-wO9geNoS)=iFey4E# +>*+KE}cJTfLIsYD&i-drS@ccRAWQ*-%Q%g0A}og+kLC1WHjPxdF6l%k8{5UbLYwST5)BlXH>2Vl4T`?uF|!-i&_%Zfpfz?A+GKA?zuL#ga-uS#o!+M2P&4@-mE_u@W}3`)P}4 +)w;tNaTJfM=tJimd*b3Ih#YZDl}9gAdvV#!uA?ZIG5WFt{T3Amh2)bs;~*%$IrjrMzD*k6EY>)1dp>{eC#qyi1X}m#>+$4GtSLq~oaAM1X&9>t$76AG%y0o~R9yYs)_aS1wniPNRn;=hA3pilx# +l*_%Adm6fIuO1zaML_{c}v7vOia9)WMiIy3- +&3$Z--QitDh~`HlK-bIM~eEAnvX6G)7@xVSr3`-_R62e2uSXI6Ir1UL7v&5oo94;jTfCC`Sy$!k8fyq$H1Er_wt}Hy@Gvc=6 +^OeF7U5?amP-vZFPEg;cahNmV7*IzMR~=e97X-JXVPJodC!ph_V`y%()eT3$oHWK(sGi|3X0f0UaYIK +o&yILrg+95MeBVjzS0Cjj0+BvL>#T0Vm^USR@d>y7v|I|pYCImKhhV7>j)k}369@<%_tXx-??3ODN2#0bKeiy~%#0-2Sf;lI4X8n^T$edVkb{-Db@M^u@VwP5s3qYWM-x(0wyUh$SZSbi3yF?t}jG}j}hG2^i@oMA}5}ZBdOZJXA1;BNICAY_ +sc<%#vh|=FlG-K&=L9rIR>9g~tprX9LFURg#XFVhXv^!q_{sNt)m|Z@Yx^5hSS_M+tvTie6c)Uh74K? +zFpdx+5msf;l04EY=I%a0PJvkH;?_r9GGFaN-pZH)>vRmNV1q!Nmhx%1F`GCAIo16Mxlwba54DdL%I0j!v+3-KIjd@rD%}oYZG%9E|{dELP{E!z^`2;d|e{`}6T1LJc-M>-HpG4_bEegZ5zMKP+liDCH^4%~7q${U$*x9pNJV`{kw0GS)*PkF +w|J_1df`RRkJS%}e92XGkXdw!K2n1++e41HZ25R5xLFoLCp6&_*fx9L!XKjI-`Hn4c;3JtWQ%xa(oZ8 +PR<|I(u{;z<|$fi7D!ny0isuPh4;QsGRtdXIAms8h!rCdA#h=Y6|g9uIBN35aU-n{BOCLQS$dYS4v?< +YGL=-_*9PeO+UvVMsE!AU=y(QL}z5m9Ag^3>I$P#l5x7CoZ*g|LM>Q~b?QJ&b)at5$d7iSfBMe&Z1C97LP7%rda%M)$!QvbLf@(%M2N*qEzNv +JcIJ2;1Cb)puJ?<01?XR@xQ(Ba%U`u(^7o~d!Pxmg1c_U~wxQ#lr8jcK^Rk|^>&GC!y;=?euJQJ&1iF +#1xrt8^W*+6e_1{p+amr23IY=Gnt<+YeBx->r%t=VX`XM;ph_#|W%|&T%i*PS0pD`oU;d!CKOpR-*?z +t_^hV)#QXn}yOvdhJ@X4tX^Tu(STEs}n>7j)DOU)-(5H5i2AP!Q4~5aL7&;x&^9%8*&9hOuTmi^$~~sdr}_F`1WL0a^O|ZKFe5`{RGW7(JZ3Mylu(VP9;(v6jcvVvQlE1r|(i;5 +gJ&b=j-w0>7D{*fNQZoRX&_t%fc;Y74=7P=9XL2TNkgt +t%&AG=uDfBxZnfQoVx7;nY?px*(K?@ZTey#Q}O6F<{a>l3NV~-0%IGXXIFv)wk1HAr2;92fg6ux(i6w +Ae9_Ll^n#0U(6l~q>~vPbjIC*;?WD;@$X?dg*`Y +AI@#g7sB~0`z4jnb0zkZv_3x1TnysVowTN6#4-i5F9LDt8ygiDxWs#YlXW%pqD>#Fn)1r>rPT~r+5Sf +YzsP1JQrzvVApy?*!nE1;12i&9i(bJiu?7Q5_8lr3iMq +M+e|JG{l5w;D{F?*R!UI51lax3gCZEjjG3hTV&O9E~gBCTf(}3>0z!O6WRP*OvfonZLYQy$tm%A +}hg1CS_1EUBdv6)1L|6z^PYKW{}{!+<(>SvCsJ8a&=C`9>J!UyPHdflcc4l12i_;bQ+{gHl0Rfu#tgz +5SnY{|BRU?F{sc;v#h8PW7S$CJ5iwCMG=`<2^$&NUKLVTF$_M&9~Z<^nD20DFt(189b94RhHzP4x#iM +*CPh6loKm$*{Ii`Ygq3!Ps2ZY0kc!JSMf#!qS78RYT0jbkkSbDc6~}6t+S%IS_GJ0EO&J1D8~8q&-^6 +n-8Y-qn0IAni{;EOB(hMDlK-nDHl%L&})y2I0u&?U}H*QC{HquNgXb3%qJXZ +bf;6Mqt3aCtEL8QNx0L<<= +rR2GE7JMT%+N!9qJP?0x^zm?L*mKM`O4j#v4Nazk&H8TQN0pKKG{1T{9EwugcMNxN1Jn10s~cmO5+aD +6p%9m0I>tHq|Lvfm+#PfveSWwl)GaPxU-{ueZn5X5i%*KCQPkfG^Pj&!?Pw4c8bHHIIlP=@x)M{9FDV +22kMvEF@|=JM{El9#+M#a$aEt(AU3xG+kBW}FkuL{K!8W<7ra&%?v5==0wnOCBu=^YUze0D0~`5#X?q +5(idHf92s!9LY1Wmx-UujlKeBg$*TK-|9aN#xgj`6X*l;mQ(QOgcN0W$%b+cv-lyBA4CUbGM_9|F!KG +8nmmAZhOC3LW&YPN0^CN~nvRlTqYWiX#6S^lfqO>JKiX;ZL~5=IwGH6_rpx*UmEMNMJb3pyT+nQ5Qf`P&w%W>Q~zApUvbi5eqdml4wxV9u*Z%Oz^gwmX23E*ZB+ua7P*{wEiK~@h{ZmJzTdOEWTE4SDmVp0x1SD_LHDUqnD7*nFMc8Z;5t9G!bhReh;cFQB65*Cp-r9Jt4^f +kzBHGW!>0wq0SgrB@3k78FkQMyl4z`l0lN*Yp^;n30d5N?omYfipMknav%bac2{Clfge?;YrwPE)HMH +(hb^p0oLUL^Ng|YI7MfmR5gx(vB0v!>bT1=`uJ`}pU1sG`fojh=zK~RV?Cd0aQ(1dB^Jk;Yj`?Q%|^D3cE+Bcj@E{$83#0ep3 +Qr@&25w+ns04%?eX^!`Z6aV_h~~0=B35w6PLWM;ghF+wt9AO_tn>PXSO`AH(Qw%z4tn9b_O4wM4>Rz;n_qW-*wUXg_O=cp(Yp!h3SgPPC7LyR(^KoFPVb6sIYyw!FM{2@F0 +n-SWCi&!n$DVH(l0n1)-O2)}-cX?Zp5_M*VG;$`!o(50IjJ7T=Z;B@{v03vzlA)e8HJ4olf$Q}3+n4{ +AefH?lAJEL6JtK%?)Y8mu=h^V{k*d2|5x;IwZSVb%+C2NV)%x8!h2nw&RfnVO79=RV(ny9P%h|HJskk +a`o;^jemirYeJ)vBl5P +=olp={xLF&&*~qB*5Pt*8LBW1(;3Qj!6osM0u|ICnf@Y%tyjYCD$K=7mUy#1r<~}Dsby8M9OI|Y`R?O +j{oRqYD9+9y+yP!fi^zB0#a2vEkvfxUZv^Fi};3$=|gAJ!`f~lY}yFo_@PINxO61RKc9~^x1`uVqCza +T=8Yl*$};SaZ;4u8nuzoQ5LbU4a6RSRJ2&7WSseUbeK{_*lJuU@}-@$Bi_7vlq+p$pwZecLIZ +|Hk3wYc?>gOEEJe1_HS#=9u!dLM0fpB4k%%?qOg2o>h~I&J@p8domnJDSQ4EZMarI5@bR&;+-2B7*{jtL<^EG94MarU5CUjngw@ZkCuf9q8S(>Gr7cB!!Z7{(~f)Xq<%+Vm9BVkDMPP~|D;7ysoEg +e$nG>=FS!E!ME1o04yGO8Jg@k+~F7D(5+{v8YAKbzGEvu?r~I0ZG>Wa=DAEGvUwa^`$2IZttqrL>S;J +HhtEYTadB}Q0IwgR(Ugw_o;(6+?eKQbIa(X2!+DU<&~jyn`EY6;eN7H5vuovYT(Kd@~1b}nG}suGCp} +@0*YGd6RgLJkFXx^IO8RH6s5AXtD(k?Z9UVvl=CfI_To!7HV3(z<2U +3IR9U}2VVxmyAWIMz6O`LhJ;x9zl}nA`+xoh0ZtEBL$xw=CX~!d1BdZ7;q7Hc(0v+pL%8fWZ(6_{+`1 +5NwoQ&XjSSm^vqeBh#tNw&z7*n2oUvf|+)f0j%bjlx2Gt7W<$*Noa0&mW?37|=8IF$bAEesVW-Lgtph +H#69d!TXIYt)w9R#;zk5%_{S=jtr-l!}2!_r`FF<|K@pl640Fsv5*j&2qx+ZzSIp;Q?niMMq!A#Ub%L*>84P}CADFx;1IOd +#pZ;+ay*o*?Fu^KfDVAiSEhBRKXyojxHtIGqN^cRJ;M+H-ip!Xw$)1)I}E9gCSPNrFX8VRm^4tI=^wj +;d5iiAm2MWH;NE(%yiWGN7y_vJA{C>d*!mC%HLHP{gn{BWE@G!-WWbk7jHTGgQtyu%4Nm9Gm7r5d9w8Dn5FHz4s;zHW%@F~go-3ZukS +5GQEL^U_*2id;FTI=nzY{4RywEuMtznmciM3Av58`ZZ3crcS1h7coD00q8f%CD9OSGDSz@~~hi+8`Vq +`xC*k|$vb%rJlj5BUjckdxyM6`qK3V#PFhSsR=>Fi${ZJ<&3DBRg5T2b-)rz8Y)^I4+MNgpGv>72uj$ +@QCtVfr!{U1EnVYEtZ8CmR@Q9|>`isbHlA;v~W7zQrHZuvhiAyvJ^v8>)4b7r6p970ZH#yUpV};eY%5 +{bdq0EyTy+-M)jPOM6Do&+R!763{*U-JEg&z`Ly708=)(nSU`^pos&M$!V~LAc-!xJnhKWQpbk|friH +IdbzAFcu+a~A*WPzn52O~KdbcOgBD5{|)9vY!YUE$$iFb=~8wOgI9#(}z!;DEwJAr>5j#u} +@I4IwEqmzxSh2qfP{U_!9|kp@<2J$qQg$`A&g7AvYkNx&*@*7a$#hbt=i_fCXTlcHNHp}_Vi!>GNH^gHnU_x7unG>j2%LWDC>g8`OhHT96bs^$b_}A +3rjvZ{oCZ5r~mWyo3Sfbk@(ugMxm-11sxAS}kDYwWN%0rcrTSLr3&3H<^FYz=r4>s? +$Z$#(;|0vJ#=9L>EKm=xwF1PX;(#iHM?2$eAy+3d>^|W$B72+Bqm;7m%H$3k{t=o|0vamr!Yv_2B{{B +H&KJ2R3y&6*NT8Ihgv597b*C3=`B}um-i%DmQS<)!L9V~08;sJkWTtRU4Go4zS;xn;|BHBo{M&8pi4w7m~y%#sImS +jX}TPA7^lseSgJ8pd|Bw;Yc^-?5eJ2gvv!GqtW-F{l(P~WmOag2mk)}k337k=VA$m3oArjqp@OREiUf +J%Snd9mA^Lb)n7b{rdmAW~!N@|dAY6Xz#US}RfMxT3$&Y{-2Z+g2`mpGv}JZyR +rZxh)6FWp;@g1WBnuwpIfNmytzMv#Od8CX>)IEUMEOo3yP|s>0;nEtGuj$cNs7jF!*w`riz#RtkRxKi +i}EOE&yjp#L(WsF#Zp2PH}cG_c8zhnIT|laSp_N?LjLw`NQ9jB*1v^Em(p5?WC_@tgvZI4UlzX7-Zvs +#UY?5_8vYTxkc6V8Q_rfrXW?vq$-(94?`d%WYv{*wtyyrBrXK%QbCI<_zkAk0X!Xir+Sv`s-*a2z^;dYWIz8B02{*4mtJED6k8=Z=DH!9`I(Ks3Ac@H*yC4k#V5R8O +jtso?p-}9jdTGOpQnUjDvc0nfhw{x#ZqX0NjdPITvc1qof%aZ3QA#MPXk^5wQBMp +M3qkS~oR$h29kFYf2AM&qddlkvBxW3&6N<$wG;OwQh_6Nbia(42`!E+0sRy$Z8LmUc~ocm?VF2!XHL^ +O0EF&8*;&=_oMXZq-o@#DWsIH(T%gHmhHr~mtSyAy2rX=^Odw7@;}`}$k$|6qUsWh74B)?AzpSk3DW? +tLR9R|t{f@d=#Qa$Y_z8KGgwO#Y-iRF!}g?t9d_8k>G_ReCc^^|(TVv@*UqCxw}CoyXh}XsI{vfkn=5 +0Z6+j2x(7@EC{EkyB+^^sx8DAUrlmeSSKP1bH_S +3+nOWkD%QK3O~ORoaT~3GMD>xr(b(?q>=*MSptY{xuO7{?4HjH=%V1_SsdQy663NgbrhM$*d!%)J>K3 +8f8sw%ZX{Cp9mIT&=_l1p!R{;*3H$r*CYt43cy4BNtSDp?xPgkinTRRy8 +j{M%|nbI>I`KDJD^`xAd8+pZ3-ywQdug`ct+NXD`_lY)crGVc_}J +{&;n|iM5`B1n7_T)hj*Xc~8v90})kJSno(TRCqW$v{m0BB4V6m-HV{}%{61G`QJt0lpwBu)hn#{br +vm=sNS$Xe6k3X^k%Bm}UOlp#Pe5nY{Tc>8kgmcaN-JM_Gbo~Dc=n|(;9X7!jues8fe(V-?`q&p*?oC< +@uxq?dNl54`!(;J9XtRRTCB}g-dd5X{}w2+>C}i%(_*TXn+pAxga1ShFf +;e3zV@^b>$Y7yHPi1^Tg2-ET!6U%QhE@+OX*qM?9U{ZL*5q5Kq`YmnLCkS908R{J)o6$Xo2*;!EiG2Fux4NfLiv +Ew5F4?Q2d#9C#&y(!TOJglRXqB2FUhIK--NXp`Z&}jkOC)%<Xg{P>KyLTv8kW#vG*=H4t~jgFx-!IM+o)0n56{e-_Js`du}?4im7>pccP>XRXPDGN +7?U0rkr^ha{(B8?cX=$7iXsiQ`9vQ>zKUEI6Z1nxiheMiEM_fcX}>4U +K{38g!qbp4f9T9lw&!A|pyaa(E;=Cu&s601PXzRb{}j(uFs$#T$c$@i&yS0$}7HF9;jCzz8L_XF4r%= +Vi!$@J+-@TW^T(u;!vDw^hSdX#|FA*ToYE(^ixM@}s$_G7O2HIzDW(orY +M;1)G|2E_T^>L9QRDT6Jv75yw~+c@0UdoNVmZcDAMkjUx66$i3U(ACdy^fywdn_Lb*X*t{-8abajy2% +o$UF_vXLbNQ}7-kbIg5w-5?g*p7>b@RhR;eC~xP8PbT$ICaYxGSZ_MDE%Ay7Y5fQye?O6*MfdLzv>bZ +7~PA!1vkP@@#Dt6j>Ysoh?*n`*NquTH9GvIB}%)R(XsKq+pU^_+6}#1&UwrHW}|Rd4|ePGs?%% +^;u@stYC15{w_A-1V(>kKnX0q0XJ3(fRO;7=AWT>N6Nede=S&De>PQ(vtSMqZ!Yut{(GjUb2P-|8=BL +eYmhRUI~|x%TjiE`LbR#R?i$Lody0qLTAG2!7i&Z=NOq&I!u6zd1hN+pwU1LjQ(mz(#)({?aXdu5_etRRFgw{-rT}s0fD-~wX%D_^9@$O&c3*2UvTpMXg +4JbVp1aMQZn|0t3!m(u3zYlff4{lVyF*yRQI_jXOs2P-{+o+mz!(5KXlT!OC6UW`1L+mti!#3Tr4&Bv +9aUK7hi>?-YuhGWE(U4&5L(WpFe%~lqt`P|MlX3#R(FJXFm+b=a|f9_`^JZKzVS6XX776=cB{kv#)^! +$Pzg9UOI2ZJR=c2iz9n!&-XfuD)9Km*{4)BwGlgB7W@2%!yeI9@usMjtbkqTG#3omroYj0sxXrmS2X; +-HSBTI`#*6o{0$NE#+x2JFXqk5`=;{LiGP&d$z%vGPgmig}d1bbiZpzhk +K#u|ZK`*~*p_=3<6F8p~ve--JhSg$xH`!fB83WDiKIbao!^*N^Rf|HY9p4T-~IOHOUhS#Mn={D)+O*g +C0lk~D<5xXtnx@>i}7u*07@JJ8?>6YF0RTrU2w*AW2lL&YcwqKH7ae=2LV^hP6(_>lPx$3*LDFo39Zg +!4j#72~*mYnPPNf`R* +-*7SQCn;t~Tlt#XPm+NMcinC{2_#@ap;_n +PP(J|ddjLx_M`_V~N~&usIc8cC&D2h$w02P#6zf>dyRlG>ee`ILc2qzRkXU_5Ag-Z@+o*>fMXy88FFzef|7PqDEa8jnVJ+vaIYPWj^yBQZmHE;9~BrXYP`r%> +a)|&(o5V$3>bHQL0yS=Mj0?)-KzO@>fB^>)zBfu4$Y&rt1mAf!*PYy%FB@73JPTJ}#IbImzV6JqrQBK +h?*EQ^li?j&cw}>B{wa2s}#AO`Nx562`VoykJN-o?&W%RAP-zY7AcE^)m6)4J~}ph@x3=+KlWZN|5m^N|3Q9?L#uVNH+^=t-HF42K2j)J%n$_GP#H|%Mvp&7*;7osidsXde1$6G+hRwNRulnELCTcFhXYEuw3v +^VZWaa_NnGrwm}9gwyE$#RG8x+T)H6G40hKlqZpYbQHU8OvqZs;PHrq-{@uUXP;d!o$FY9V1MV=n_&+ +1}O`2P9iyTm-5P(B?4Djs?P+#km-`$Nc+rBZ@wDm&i33G-w_y7@YZ?8K8uJf5_ei{ucvSvOSp$8kN)a +(+yjpG=^I92nrJ01_*bv`V;}(sNA6Cl0bf?l*a@^9TwKbIBg` +j;Vg`ytk;N3HpPCs|C0N$h1K*5LQ8xqr+&)KqQLl!I(aWry~uaBdLPc58(!o>;LLmWU~GXpX>gn8Xu1C+zivzo)(&ACF0#pqqA80Aesbdw~+)bSiB?93<(3M1d77z1d~*hu~(K4`FzCCQr^K +pDHeE$snaS1*wSz9B7u?U^ztfX6gw!RzQTZ;|6OA(yKi4f)bTMt#=$s${${o3v~_<{R5AWpz)|FGyZN +s*~9S&b5__w+S{eTlPHd(A<@)jFi|KnTd!H{OMcvj`~-<<#;kmJ7Uvb}$a070aT95#!=hq)G@O06a%z4==+`*nT5T +bZrC32hi$rxTDbY9_<@TS^IrA2;h_U14E9A|xOi5e2nsD*-^DCJq!WkN7wP7^GNvYCMc(aUIknH +l|_aiPd(4-f|ZpFs8bciNKVPvbRVUXMApg3^H65kc`7)m$wWR4g%G0|SBvED9A0Hq&G9Soo?>m@LYY-?>6lK(=ra^#+h=Ec}UkWzJHvZOL +Ua4Nj;+u!5@Rm7qZU;z|>)BF%J4Uq3y4X>$V5vvap?xHRHZJX`~Uj7n%#t982lY{&6u6Vo-<6WY9T9(Ea~=goD>S{Qe}H*p8$HPZ27Assl*zome5Lrl +x#8&9+_E$$A+R#(%}FvimLb_;c$6m?h2r~9qU-I7+-d1K@)bUz8p=^Vz=YF*Gf1Qr}_;;zvNgq@oF*jZq^lWj$B585=RLMvuV7_**i+w3#UO=U#&xXS|Bwql@W>lg`O +r>g6T?Z?FQJwij*ZG5@Hwb7|@Qdj<@BA=GvKYT9KHyKn;=vIA5@l^U4{GviKxvJ*Xg$P3gEznwmzaU1 +6-sdgO59DsQY4Wc(e7qNxy8z4=XQqE*!4&>wSUv75SeXha?GNZK+MiH<`;&-EwaAUsGUa4HuC0OA|ou +>U7Bs>p-B)J17@!>5e#NRUr*rHTF|(XB_22=2g>tss<_;oI=&g{b1hZ=1QY-O00;p4c~(q<0RR9p0ssIf0001RX>c!Jc4cm4Z* +nhiYiD0_Wpi(Ja${w4FK~G?F=KCSaA9;VaCucxO>e?5487-9c;Z4O)~@S@5P~C`Y{oGc3`DEw$`8j@6dlw=&Ry1T7f_0aITUKfpTn(OlTAt7v7vKWYSt(_32W72~Xee-5I%oRw)&<;r#j?)XR=OjIX=PmjOU+Yx}BBi-|xshlG*2U{|kQTwyerb4G4%?@z)$ix}=f>( +HXkwIoM-Z@T!CBb4T6Aec6~z`dc+e-3d5w)!LG27mhh;Jtq)RfPg0^N5=T7|*2OSWMsMx6OzCx1JgjD$A1 +ONa#G5`Q10001RX>c!Jc4cm4Z*nhiY+-a}Z*py9X>xNfUtei%X>?y-E^vA6no)1tHV}Z{^(zSV#RgOb +*xrmffSYt%u`MoI2g48qDlO4AF-a6iDyjedjv^^jmhCQSQ*(g)Lh*Du9?9=c@=0MB2Dg&tR8k_)igA< +?Nq9j^TCNeUs+^`+QdYhe6-nuerYNIa#OMpTN=z;;)>LllWt_6&qRO!ZGlkOXbS|xROml&7nFY +1LYZ3<`xIl}Fafx)3)1?(KVUNUC1S`%8RAIRR4Wo-bKv$oT+e-OtwjvR_Euk9(bk$Xy1PFePxrBU?q!gemtmQu_ED+8SdW9;E*9D%SlA)A65FThxVqDSIKeGhaumrQWS +4IJJl})RZBu0=Vk<+2&iX}-91Q}VOL=c(S1x;W@lQhRdjK=8oWizR}y`k>)WMGO+#B12q@?jLtZmvJh +-(M%!v&AeNqv;fd7a5i~R6BA=@#B2Hu!^t;k`$q~t9}K`>Kshno<75>HL!&Ly%{fs!RGsS-Mqib2% +rX&gJCro`NmdAuJ^ywZRhWqePE6(#=?pJvJs~%}Zk$cyg_R#p7t9R}*wqb52T`ZxK!n!5qJ7KA5?K{fKI@fQ@7^OH7g?}X%P?l-I?kyXL9+%P`Tv +8*&AeIA7-MDKpVuqpRp_ev98r;KxkAp@=_XvtU(XBsupwfOSO{7h5>FsNv} +x1+Jqy!;&RS+mo#_H_ThS=73FHrt6e7Htapx;>-0XRTeM#+vCp!u6m;OgVa+`A?kh7!0LH4r%mERb3B~s3q}mMYmz}7zhVyy3dIB~w +e_4sQ|Zgwr@sS}>4|NcJ2IW#DR+PS6Y5F3PTmLq0#Hi>1QY-O00;p4c~(c!Jc4cm4Z*nhiY+-a}Z*py9X>xNfUteuuX>MO%E^v9plwV83Fcih#^C>QQSqtsZ*FgnQd{c+M3L!4 +(7HIyEq{mP(eljAKC`#nc$H#%+q2dZmg-*b}sYHPR`U2d7P__o#%z!v|5@NW)sPv>uP+iv(x+G5Maz +coZHE6C(mP2_1Pu9p)vUBH{;acL7>Er&^Ir<~>HtwwN3wKsKZuXRw(uuy_3~;6V*+Pd2g*P?(0;9B^g~xauHdkhcs9{z +B7F;;njcTN-^`5Mw($(`|3izle*CX^FU5$jfxkCW{1q^5Xz#BccoWV@OjM13hi(XCP~k;jth+;u(Y{% +Kp~x3dO9KQH000080Q-4XQ|<{Z(mn(L0GbZ~03!eZ0B~t=FJE?LZe(wAFK}#ObY^dIZDeV3b1z|TWO8 +q5WG--d#aGXBqc#-2^H;nf7d&+#NoV%pNj#f6PN#?6WH#GFXS)Lm2wNMFs3fv+r+<8(guo`YiPO!tK7 +c^)_xC;V%*skg4MKUWSxMTi)Jl1|6eZ*}Pqh$*0=HAhI!;Ntq+TNsl8Uu^HwDqTkmV(l>f+~_=Xq&Cl +!6PMNx`z<$^K~K0seg7xA!Yi6ymD_y`-?HSw?tDA+b)DR8lxwYF(*G6p_YUs5D9M>0`Pid_luhlo5$e +Pu`sTbUIDq5Z;k{s-RXBL~e{)Ckd%4PD->^xnMF3#v~Cwi7s@K(*)3Aqx?XnVuBx_>?Eg2*yU&!Z!0M +(D)q`fWi&Sd$~YsM#Aqx~w8%&B;}n#ZO?jO9L{eQ#J^>>NC`u6*xdP2-23pvv8B=4R;Ua`2iHu-mUPW +i-%Cc#6R$;}+g4(>IoE20>XBSoV-sYLKNSI&a4oo~@jHRGFGq>2N##oTpWf;T`jyM-ZMrAM>gKsVSqk +SnWrs+4Ntd>M#(swJHuo{ChfD#2suaAsdDldq& +V?Y8d2QKidS22#2#gsFk1%4!w1WhKm&w=T2=Meri)RDEfGmDDoC7f>(xs9(A!7%N%i*vFOP^T|cXjb7 +D!FXU+$O?3doOl9<*&F`;h_o&#*XpG(bczFDbQM&%jn#^1Sr?}8(Q$Oy>JJ}j9sk#Xww*ATm#n#F)|laXi8*gC*^Kx`gEcLZzINa0P|in{?4u5a5UwO +p+uK7uP8yR>;}Fj`REB!Fw@7VuPs2S%V;ec`NDovIx#?W=Z(CAAjK*ot)ZFc6NGv>~%Ub!8LuQc!;rInc)fN$Y5liZt!Et?Yrjr;!cU8*7ODw3>qA81g4dS +xxMwEJTfTfgyHy}Gmcf@T|r!h3nA_qUo`c?$<|aGhkpzECYv-xl+Q6}GiO&O8tFHL4b1g#OWQxPA4X9 +S;-wt`yD}q?)&-KkHQ#3(2I|~|CDG~9(rwh2S(gn%vBXqW!F1ra{yrUl-cq=el-zf3O(*t^O2UwE*SB +*ig$(=|;ih|SxptALshdm9vA>DwF#c~JV0$}ZeYQT^4oC0{lD<8z82<11$}sj#w)Z;b|D*k!KQJk{$! +oVIJaaZ=HaOJ#M(yaE`J-VEDZ2jIFcV(>`e8o%c>Uq``0{%8+q?PukGC&xr(Oi;+#md+xqp0)BRyTDN +*t4-h-0+!sR7=B>W{t8-ak-F0|XQR000O8`*~JVYCo`j`vd?0Iuif@9{>OVaA|NaUv_0~WN&gWaBN|8 +W^ZzBWNC79FJW+LE^v9RSMP7zHW2;pzv7U7u?JUfv7zgPG+5eVz<^>4&X;lJSClG$S{P<~(b7M72Y{_k|Qje*a%?q(^aaTC?3%0 +3*+_b;{U0I7p2PcD4Dbpn%{C>AK`K~lCX;*u_&IR)7h$<(#e ++SG!m}rnNm4Ll;HqFYQ@Z>tw831a_Dt{X!qZi)`M_SXD_@^0-st7Q@}a2lz`#z5`=5s;gr!6rtuG17{)8+do)g_}$t5?x5z6f@sA`)Gvm$ +QU$KM^ZMnoF07z?eGH&beHSJyU+X%o22F>$(&v-r%yM8L`F3B>CYp4}xMG@>y;@&6hgF!Zq64s2trqC +#`_N7r2Mn`)SSO8xh1ouAAdv%h^L=P1mIH>_05;T#N+t@~qOR=-Ud3>ob8r)t(eH&w7+rC+x7Q2ccl+ +_dP{Z>qju8!EEB;Fb7LHigmfCEs(#Exd&4t+s4%gU-2`h?b4#EPi(ot$FX1lrE8AGuUsR +kDd|Ed~F)}WnW+pSiv;8rUFAr-bRIG%q*vv8l7br>ClUP`zx +rHtBs2VN8sev}u;HvxG7XeZYXPdrZ97;QparYYKrj&4~YY9T8oPr{QqKV+ojE+y|KEPJhMd}RC=}dc9 +tMih5Q~Dx0$LhrI)v5qxauu|%ylcZX!S0Xx+4G +L)1fgR*v89?v_N7G~X!$dN7~E7*pT2%PB_L;VCC=8?j8R@C&!>>riB$LMQG?Z0F=2|Y7)PfhrV9>Ou8 +3C-a31%3Aq7N@5{G9<{9fVXesyIHqRZx#a|?Kk$DBhO$u#Laea?IG=BV`uS^g93K;;5P8hDsn#ZO$q7 +wzPZB`{RuO#%kNnJ71y@$vj=Zlt#Z^9OS=Ch-oFdFFAv^c>;uQ}pl_|y-XESA+@+o3sK~JQU`EJzlkkMmzR)&=_}Z=;?G0c+jJ4cI@s^HbY+;% +P084i30T&bX=lmkb1cp +1q(6Z^b*LD(Spgcm6U=wi8^*s;Um{n3J140Kuu0#cdN(QVQTyko1p{hAk`tjo|2!fPTtzkjaFRRhs+w +mj-`w7F)Kl`_kF@>oSilGKO0H#E!+yo#bDr#6=fZk|>6@e$gx%j0$rf673>lDG$N*<-;2$@Km3TtD_M +D6=Ll*ULsiJ}bG$la7yfrXM(R1s${imFXSRa&w&Mmy!v6XUp`H7(GMMKfW$Nz+j7->f{Rr4ogrRb^Rz +vV<`5+P}?TMg2QMN>lM^5))5wL4R81jYbf;7nWFlHLVaHWap8!NIT!jo~5KiP7|PY=4VURsI(IpdLHrEXph8Y;)#kr~O0ZBn1N&Z +C7T>vgUmU`P^)kwFxqPtFAbk{LWl78{HPumO}8xmV6)|q?6t|Yd0#9h@$Xdq(uvD0%Lc7KEjgo)c@m%89$$w3~H{d|>h +YYwn=r@y|3z()Q4i)IIm&H4CCAUhpZFVA4uUOk-eyC!PJ*il`6Ps#Cb`Hy<>UK~kX>u`i-C5*ip*)M? +R*|??=K|^fef7|@wZ{)rJ@4TM1s@6aWAK2mt$eR#V_P9{#%q008+K001BW003}la4%nWWo~3|axZXfVRUA1a&2 +U3a&s?rZfSTfaCyyH+iu%95PkPo5K4f`fFtd`dfQ;rBy9t1;vz|b1-ypDSd`6;ED9u@3ySvJcZSrBzN +B*21)7J%7H5WYIdjBe_@WR}6QO$Cep1h>mrAi9Q<0~9R#2&!B<13%dG^^nvr+}s^NinB0-xclUC_@3& +u7*1QK7lWY1Xrg0WEl~l2M%sxj5reoxDHo^>~U-1V#BgP?}1u9=V?TUdHp~lh+>-azF}6XA3$cxgd9v +=F>SmgU_NM(>a^o4~WRKXQBluGDa06dd|=*W|tuV0zbwbp($9Tr9K1ZhpteQZf8O{<5C;dcA2zl_Fnq{^YO3-nW)L&oh5VxseKu>VP~ll8P +)7j~Iy&i2pwONZvzi376!iU{msTu63b?VW{96Px+K$-c^uF#uBFPwI~81XW^YyIzln$%Mp0Qln&;4ks +ZfTKyD#xxIidwt<1jSfl0{|tS{)XsTPtmMX*GKE@R4?VNY}cwNM07^hl_*C4mIfW +ro7plBoggBl4NziCxXvbDB=KI*n_DR~?RHH^%MKMN@eCiQa-Xvm6;5~^tg#(c%H)L>g_*{bb!o<1a<0~LvwKeH*^?Q25k-q3uXa{gTf&|IXXMJJ&8 +VzF3&e9Y;??I^&K>DPy!=>UiCf4m^T2GeysxZk_&A#fm$Rl{C*R(YtzWEr-J4QM(2BOSS_PAKLj%wMB +qEi6Z{il3=ybDWf(r1>~AZJ;+p;cvT)O3;9o-So=sU4@geHh;*fCF~HDOH%!M}prPBxNT+u5 ++JJx1-?&{nG>(^}!KBzro#`f%}aZ#3I7w_l>QQT-f_-AhsS&9hBzfIxmJo@II(J|!CwwhMQbP(ila4| +%o>z7l(072EFoxUJtQj2Am23zfQdJIZ!w_V1xh`pN01^Zj*uZEerZjzR6c*G)lSeAGJ^#3=1{&_#`|9 +DH+oa=X8xG}eB~Q5OUIIi<QNhk}4bq|}^n{Zishyl#iL^K6z& +>o4ZGK083EVC(h7;@Zn|o0u+f{#83q36)|uV4qduTM+5^0TD(3$jEvKydI?@OAtur-8DM>a(RArdxKo +<0@Sqv?0+S4=4NnpqmAciX9v>;733XTB@P^u?rvLl+|VF(g1i#uttvVeUM#1yw@14cvsCL+$`E8jd=e +h|JRwKRepjBl{&IDDae0n8-3L*%%)Nfxse}1iUCVvj^77up99|DSrYfJE0R@6~2XK3Sa!-T-8R1E%^v +fK0fN0nPiu1s7j5}`5i_Osv${5GDa>?Sjom(e7V=O{r5mm<1h^J9)%(xvmR|=gD#a%-#Ps1xgSK#|K0 +&mXw?V+}d{-9~BX3}+&QL+A(+4X>2tek5xtbxgpO-J?I2FH@XWxcww-!-4t$LaBJYm+Lv>DMZF8rKZXaB`Y!sqWVG5Jb7zj0j?7qtzbZ7OJYxa5VT@E4(W^JlkNlp8HzL)KcpC#J@1xpMSXI3 +7vW3ajcc{5Q?*}VGHqeUeSxGWbtcJv4XjlHmP5SMd4D+9lcg{aO{1(%j>uzYIe2XbOhCm`2U91A*BuP +k0?-}4{iWw(!MiX;-5zcP#7GWxXqanKsm?U|=LWw97zy?W#oSD!3)Eh&dC;#MM?Z##i39{ujWCDtA=+ +@kKppmf$YTm^4vqTfVk9(`^?x>By-lU}MH{P3u)ep!g;M@#{QW$&r1E4~{E6YH4-6E7mG6-|ZeDAP4& +2D`0di@s;O^(;SD56;wMR+%4;f$#92E9q|KTt~p1QY-O00;p4c~(<{`37n|0000`0000Z0001RX>c!J +c4cm4Z*nhiY+-a}Z*py9X>xNfc4cyNX>V>WaCuWwQc?&@Eh^5;&r`_EOUp0HO)LSim6VjYxZ>l>AX4% +13bqPLMtUZC21-bxAPrzC4I>=|6CDKuO)daXO9KQH000080Q-4XQ-&KjFir;m01z1f03!eZ0B~t=FJE +?LZe(wAFK}#ObY^dIZDeV3b1!#kZe(wFb1rasy;*y2+cpsY-=BhTH%u-(HF4Tu^_Fa@ldi>_$KrI|5C +<|NQ#Kb_)JQ6cSM0m*j-+JCZ*RqdppnFT-|vn`mQPY4H3{`JWva&Qn^3h#iV2CbB-BF0inxVXWfTU`84G@(Pd0^B;@3TOLhFFQ>)d&m?}j+@?0Jk|{NkE +s+XlX`02ASD<|8DJsJKJYMrs+~8Z{K%MzwGyq)AR&H^!r^A(zxIMq6n{j#xxBE#7l%GE(Q%E9buT){Gtxp-O7$2d3FYIHpFnc(!5c9hJn|%nL_B2DGa4H+Yix+ +EwgAj#$uLN%)XGEdMy*I*brI>CMrRxI*CfxI#9<(KpmE09MTfY7^;@v)TqJBU-yn`o6fED|7TVK1@GVKB$av1#~cw&$M8Mf?=Rn@JyW?NMEs-jk7Dxs|JrlzEot!XH{PP^Z~lwt(BZN)7wv~pE#S +qiS6Iu~bW7w1Jm?OGcv+8oqb#7A3(!V<6Ta0>Svs>qb#;^28q;%4{IG6 +ptFMQ;(o}EAN|6jh`@aL-5XE8x$`_#nEdRz7dFNDhnfxkz-IkY|8DX6LLieZV=ZBD6B{Et`3kh0+PQsh6@>zr~OhfX!Q_^W0fTKFzXL +o%GwJonaF`EO4S)NPs!~Q8c}y!d!;Fga=?wdla_C^c;o$@pIE-6(x)2%@%K8c;9PxJQ6~Ja0RKSd~@R=bd&sN16*h)6yfX?a8u*HJUnSB +d{>oK?jzm!BYj_+uAPc#e13I7ug0#uVbrnFDqe>E{sRheWMg-qe8FTZohMS?B~A3@qEgIPS$%-l~HwV +{yMegNm{5zPTg&}UNbpHF`eIv=jCPS39cEaN7T^`40IBaG1fv-|?9@2<)E +oeVOX=wIzr*gt(rVKUDX!*T&kX8%6OV}1adKigjIXq2bKkYTqIp^jxasttSL;TR*>yEK<%+zAbGJ<&+Hflx2d^Em;= +MWB-u35n%O{SiZLoaNQBhq^H%JjDo$7obBO9Z^NwhyNh?mPsC6NWB=gFUnl{W}NiK6=3En*)(?snsM# +Ms@PGiNxhvxuyzMS5867!~0-v6f>oh->u)63$rGc(rr5r0EA|F!b>2u3Rl*vA%tCI#!+ctGvvH|xwmh +Jn5U4Xn!Gn;2H2alRi-{GqjD*?hAZFXAWqd~h-&eF65fP9DgGBbG?$6q@?Zkwf&hXUU@N?_-N-dn*f4 +Hu{{T=+0|XQR000O8`*~JV65G%(0tWy9t`qFa%FRKFJE72ZfSI1Uo +LQY)mLqAn@AA;?q4xVR2iqPFR66*;neg>9g-k9CI_Tdb(Ii-wY?V%K6ahh|Nfp`UJTf7b03dLF)TCBy +zI=g!zqok)i&qzg(M>y(EIa?_jJRue9kjLUl`tmPh8N4=i>I$d>Qg&6lKegz0=+)-Lrhjh2U9AGNZL% +Ly7=6q-7TK8GgewrkD$v^T)MhBo_b^*-XBKIip#YLtqP>)jD5gg3$|?3&N`U&DN4;j1e>zEN72fn&)9 +ESv;gwo;~xJ#lQGi;Y>) +0;=Zk%1UD1i@;rWEj>6I2TAN*U#r7PPTPfYvHBXcq#Xu0Opw=EA)Uv2-ETtd{j~0n!e}2s*BjXhl#IZxOSa8nmC?vn;tw1 +CQRQ{%E%ua_J+{2;GV0zHmAy=v2Qz5B@e^CYbr0M*3HDev8H27sXAKTcU%ZQc%{OSOxO05P3OTclH_P +mpiHd-Qzu^K0-k6eEEh?*Gd1pYdm~1@-Yn0S6a=%iT<0tZh%Z-JE>XMBT-A)KIi5t%>yFJDEBC&jAR)=)ymEYEu~-~QesT>5k!k|z_rB=i;3|@E|XD?X4IH&45B@gaG`VM`b&dG#-;at&!lGUowf<;e(lv`-} +IWn#m7a)ZoTIT{3QZDQDn9Z=a%B3JM1WRThC?*6TFZ=x=ot-^_kLIHEQ+o!X5v;KdfX>^?LWa2vq`;d +xi^iZW6F$%P6$7jW>y;{tLcImok^QN_W(9)0Eb7fzhMRS8k~Yz1t~9?fVDnPz9#0z1puFe0Pw(KGM +ZtzttM-C)Uemb`boJ^0muZmnGpschkCCzrv}rlhHNwW09&|i;YJr*@6Ci4gXXOs6x{)-JBN!AG)NupE +*oJaSS)(SZyrE=?Eij4kBRBJX;GqV)#uP$3S%{v-B1xG5*fT=(6*>!iBbD419cK^IF8Ne-98-|9jr2x +7zgUxU^m#&62fP7>zzuvjSF*kmPGNq44QP=3hg(JET|9?&0JeKKbfcOaD8u))QWJl~lG%8W*9CyAEVG +|ER5OA4ugeXv*@lt1%B&*HWksJ$Zv~D-45aMqwpP{lZp%1W-jJQmT5a?Op1KMkn+}JJNPL;6|_;EfL!hkjCdJfR$8mErge +ns4_hVE-Uc<6ZPYx*<&kb|L2mYBwwxvLfpx8`bKS@zOS+iN*#X>w<-K8LcPcla8ui(N*fb_=RAC^uD^ +}dWT)Fhv1uy%5P=7w$=h-~RP-s-&5v9g~G5$=Av4p{k5>NXFb_^sxdCx +HvyQKA7l<5$b;*b!2!H4^I(x`Us#_UzIt7mx%W$P_;=%cw49NvO7GL +cFwzompnBthRt65rG_~gOP#(W(LmRzeRHw&1*z< +v3ytDb0|#$VT}k>rI_?19@1~y`=Kn`l{{m1;0|XQR000O8`*~JV?nCll#smNWehUBq8vpFa%FRKFJfVGE^v9ZRY8y2L=?XJS3GsKO0sf7yIfF3E4+!5wUpSwcDAajRpgDw$$(><8G +Cml^}qqa777TET5&8raOB95|FXY?H#2segcKGbBJIA+%=^Cg&71dL4xoM1hFKcYWd0C}JZgU+b~Nn(x +@opzbiT-;Icvf3{RhV|ASs*El*1Xpli*Xz;loSy^`rSRpmArGE+1$7l6CMrbP}BA%KVlVdP>3a~32y33bR=dPze +PB43c>KLWu;dP$IIw!Ti1=5$7+02wu_rB+8-Mgj&jfgp|rk`msL*YZzI@SMK?#BfcpJQ%yqnbk9UaL@ +2?eGd$~C!LImf9kl9(83@!9- +KZ>j7+zCiW|m>HMIPvGI-zeZPW8QNCCD3=9y{x;GGJZ4P7TD3@zhiXhPp{k;$;1f;Tq1mNC>(v}<;K9 +T}=`i0C@DXMQ|EHuQm>F){{1hAW}vQrzm)@I42kx_CElGW5kA`mQsAq2meJF!4+bVfcmsWByRErz6fp +d9F}x*g{a(w;WseV_@GkgbLdn46T7_cTE9xq`&XD=s598fDVRhc_OAW@l}!>Ns27CsW@fgMJ`Z@)L^= +pgpnb6``ZuE(X@!AOPEfhA}<)3PJu47l;uz)6x)VOW115d#9Y(L1HR%Vs8jilSvqHg^KyHZ#wp{~pak5NrPtynig0yoe%tjqAo?lO`d#ZiVk1HkSd~hIF_H*fe9Lbb{ygZVi17YL +_gtd^L?Imbvg%o7M6_Eu}zQ+i5~Hxf7BIiYyFr(&QwLN}UyygwvQLbE%moxpe8MOB{h*WYq=NIHh9^+ +kmvUIkh!pi3Gu%RNmF{P%hVAcy08MS5QLa<&L&Vb%^GV4?Ffo#A~ZJ| +oljDlG@q5ab%m+lP8ZU36=&m6k?L!@-H$pyudkp|$*@Y}q$z^8*0t7M1USx0WGy>94_MB{Z&b5!MU%!xXJm_j6OPo)0#l?zfs$XA_NJlUi^QDlLr4JneX +q=S%6zNLOJ%-M=4)kMC?kmfp<|PE7%XPhB5J3#r>VDFbElhY%BYY@6wO~_Tl)+40PJFUK^UYFNZbsci +f<>Oc!Jc4cm4Z*nhia&KpHWpi^cV{dG4a&sqRJLxJl4EW8Pc>{hvhpXSNAY*e{6}QmH{OYTEVi@NzVD*aAV +j-cESG_j4gjR&N2Iu@Ff+3vI-=FMvyL6saIWCVyzsnNi~N# +6_V4mkp$!d$pp*gYr+ZSj3Z!$JaxlsCJ4Mzxd42suB?%e69SmAAe^KtD0osyGVemo*$bVMIr1eEe+VQ +gdm%`aZRJ!<(v0W^bk%y->Sn2~Ny33vkd&p$q(t`vY$_bp5f;bEl7V$&{KC +mBL&yAmT@sS(*P89W!0{j4+C&!N=nkbsRz8)O#T>HUuU=)PNWc$w$Jvsx|_PW?~=nUd6GHfEu7zBRg{ +5eB5#K6#=+GEv5s8iRsO4Y=UqX4Uhs4bd#vC=RKgDH4Hq}qmAi!f|M;E_`R~xnsv4I4U*J_&5j=IE&T^U9dwpQZ<8F+zu(wq{!ksEvFv5ngw>EdvHP=!qnEhqr(@ +`GmMY@F&`PR+%A};3ikfOpcu6bL^3&?)Hs~MnR-{NIebK;-RT)Xjw$ +Y3;AuzO1&z$7=$(M|GL>Kbn-e(nV-gFZPxhjc{ty|443XUEA_}kETG{>CVZBc!zHc~sK+r4gdW>0OkY +TiK~=zPA0%Qdb?QPW!|FD%R2rjQcZsGyaQ3HFYwU1XM)-pSYS?Ov6_-~P?b$vWoeuXi_h7jqLXRIaYD +S3r=%OrB8m9_KA1wM2jDm~QogW1qJJg0l;8rkIdrt3EwcpZ-_%U*4%tKUAN-Q=k63`Y${6$o~I@r+dQ +nhp}T(7A~X`uNQivE~)I8m!@@CNoOip`>`J`9+}f898(Kjk4eb=F&&rlp(NekRuaAz23c(QyFRn_m0WaX@QfmeD%cam?D?EJK;wsYV)QXlvIyj@ekEWhh5Hj?rFe8 +QLI^BR1ZN(~9(q-5V5o==-GB&{~7F4DGKcL9P2*Mr*z&VQX);jIA}Fgzn5+jSnsRbc5R2*Qox-us-58 +W`7d7Jq>W+o5SEFcz0YNx;-sUW4EWtFHlPZ1QY-O00;p4c~(0{{R`1^@sb0001RX>c!Jc4cm +4Z*nhia&KpHWpi^cV{dhCbY*fbaCyB{O^@3)5WVYH46-O}2U?@aDF~XPT$#2B$dW6{8w5dMXf4WSBa; +F}?RJm-N&@5%6ljzGmtT@0Wv`nSC~_#OgGU^`_vX!5e?}-Xve;-d`^L<)BGvPC@>DoEWKnCI)QtH6eSiki{_Xh7c6G^Ghc +kl@`Q3eMhQq#pqDhsfzO@HUwmCqh#9$vDNNH0l}Pdo_>xA9#37o_Xq1PjaC}2XlqQ^hzd|*{z=SNw%- +P61^{{kZ%}LHvteRnOd==-ehiR5BAWpfn4J%Hu1j&gbMy%H$ +_l-6dIwkSgh;=QkFh$+=xbbnDsY)u`3SnvV)`+$Zf?!h@ZHou1!;jSrkC4&h0PrbA1zl4XRt#HFSBI> +7_Q)=_0-k`|7$28k`Q;s|mdZf}gYgeqs^RkkECf7bUU4i{DSjNi7~N5P_Qs%xS`8h4^ts7W->Biy|Nm +yw2)s%ZUPxbPmD^(T^6xgAUD1jb3k?S_2x0K?{ZD=Pxevee;nzO=)`wSpc-@CL1FZ4yEvvgSwNSnLK6 +5fIadVH29--&(AjF%+9?%EZaQ~<^8vSPjJ=u9KUwqjtmr@Mn7>MjES0JQ}G03QGV0B~t=FJE?LZe(wAFK}{iXL4n8b1!pnX>M+1axQRrjZ@2x ++cp&4>nkoY2y7P`x6LXDnxY(;wh73RE6NiDlYybJD4VS;3ZyiX0s0L=AB!xD)*sMKA3)Qz|B^51C1ra +i-K1LZh~zovo_j5y!0AV)uu{3K)=!{qiqpsT#Pd!dQ1z{r>rDgw)c_uS^64X(2&LCj88{bslYK1>e0J +TvezD$WvK+3|_H*w9)pMb@(io{KXcV+Y_*kXB^RD+fTtE!+dv@%pkgDmxVnY4&Zmo>Nu$gb42K%>>Mok}%wC0qbkwZ4mbxTE +g@jH>f{GYLXm@8F1>s`EqKVV7**s)e!#I +g&5XRibh6Sfsf~3TXaef$>`>3NBFR`gfWCH~$izCa&!f8Tmiil1^Hla~Ktu%K0G)|DX;Cq1&Bw^gvj2 +CG)q}Z9FOGGOyV-o1cxvq&UgI&4>9z-LaQw-mqpvOS}d0!x3$s@w3WoajaZhl5jrbP#~U85S +yjjiuRPCNm6)$*0t%F~Cmq(kKQq}+P`L0ub-@&&X{BX}F#Bd ++>dG)7LzIxNaUmd)CZ};Y&>s-DR<%ex|e>>@}>vs1`2R}Oa=^qC_cd+X!@7`jOe*sWS0|XQR000O8`* +~JVxf(FQYzF`U`4a#DAOHXWaA|NaUv_0~WN&gWa%FLKWpi|MFJE72ZfSI1UoLQYwODO$+%^*au3s@#K +CJd2%ieX{D;BYVTaIz9PGn{!iultOS{&qx# +EJeMTQETpwzOyS)^o)q|+nQ*uPfarbvbmKwYHxRc>O$59l=adgFhZMim +@UfG(OL8lrbR(NT-;Er*@D +la2&$Z)pOaWMQW;YIgs`mWtY6C(+$5u=F^!%bA3r=iWKDNYCe>mz?m04Tm}zwm2)SLHo}7fe`N(P3=} +(Q43&mC|Xhs#Q7cCSTS@l&`$Qq^?%Xgz%9z|J}5sZ2N?)qk>!GDG8VOdeSCoR}uR8x|v{7rp6-FO-tXi +QS>=&n@{K_IU9Lv9fA#H*--5vrfGAfj-?8;1&)WHFihth`^CME17O^Rx-+5XuurJH^BFNU!Bp1QOWN| +ABmpPPh5$+bs$0%*CSw4BjBqF4%orB5GLy)6!g~;o+?&cr#+9Xt2F}9B81z+zi<-poP3*2TnLFfs(8H +%#F);`XgUkQvu6@(?CmF`6WHW6cxmD754T>p1+_bE#eR`0B_tyIh23Jg515r!%U{`yUVS&2Ji%v^L$@ +lU;+Kch)Don(`go_CMm4sWCrWeuNfty-a$b9!JIQXpdsfqjR03=DC%>uwvs4;#EgKJ +cs$v(u0Nf%yy3a<=RS66`S0qi^tV($ji8lGBusiKb(VNpe5~Y*6w|M|9#&;+03>aD06U7>7Ck& +$#T|u5}HI2<$sOh|JU*{UkD{+sGip_i_{yn$tx{#Yy~c*;37s22c}hr8=OLRMcnGkTv45ON6=4glgRw ++ZQy;buxU}NpLWFunAo!*=hE5x7DZfF83Y~0eCB+Ext_xJkx_jgXxd{7HVtqqH|v?bvKX>Zo@mqwykkUvK@j0|FiGM2VAtHD<$tr+o +}sjHt4b99@EVy_DX&ghuip{%}oy+^Ym@KK<~c-Kt>X;*}USRyo_*L2(&rFW!h`IzI+qx0UMkZ!Agk5x +`cXRn&=VmlpPnCK?|ml*43ydbzK0vM_Xsl9eBbs^f?Ai;mr~qXC)VyJQ~H(TA}Q>4V=}^Slf;c-@g@T +y&ljqK)2w1UR=mv$WYJ;L1KHgoz4vb%~j2+tTndJbbc5bxZ5|E-@m)P4slw`erY$c4Vt(lhF}VE^$g& +2eUB6Qi2Ct(KNooppf}7Xd+nnAfTnK~2g+gsW5%ci+bPAbUtH(7m^h_Z(2I{Z?0b!D@JKsYDKQVKG;0 +ZG3JTdzzmkdi7hV{Pr_4Qj;`=Ih8@{s`+8;XMEj_j<9_geHq_1Ac7gvTy8}J52&j!gRB=&+xlU~XeDR +_=(5NJLNUS@mqNsr_H2(mwV^qEM5q~dp?f(Iodu|U9c43~!j*$p^Dbb7#yFcr6nB+pB(3njYq-2&8d4 +?W*glIE?bN=MIHOp?=f{iGhDl0GOta`^q+RGb8ho{Q#wpxDoOJi+#b~be@H+i_O4sfokZpe3=t;YmM@Q2 +n8YJ(&T}tT&bCIVGIP)h>@6aWAK2 +mt$eR#VF+@nr@9006lG001KZ003}la4%nWWo~3|axZdaadl;LbaO9XUv_13b7^mGUtcb8d1a4JO9L?w +#qawm26~8>O=E;s1VQmq@KU7S3cE2oZ9;dGHGjlPKfFoPTBL->%)I$Ak4%8p^dg}D=bBa%INSgn>Lhx +A<4b*;wyDiE5hT5~&T46?Maj;!s+uO~&|}lUBM^t55qd5s +eS=aO9KQH000080Q-4XQ;4ZKyA%Qd07wJ?04D$d0B~t=FJE?LZe(wAFLGsZb!BsOb1z?MZggdGZeeU+ +b#!TLb1rasWm3Uz+b|5h>nn)t&}2w_=xH#}!?1Nj4+Dy=JG+Wa*V=5!qo}67?Au34w$o-h#1ZxQNWRB ++aCm=!+BL>Ll@Pc+e25XHHk*wi{1ec#FDhdh$?CoeYpdKU>Dk!IGwnfslu`}0z^<~I%`?UaQDK`udqA6Ixw+E5Hs)$qDv%>}z6#ochKvMv{Dn2|f$&LF) +1&v`gm)S-#yF73pyl64=+Ux{!U!UwXe`!JR?}3#LuaI%k6L^9 +_~X;v9R!=2V%LkZQi4v#W3fz=zNQtOjPQANqmq06iy&4b%ztlf% +yOZ8t~8vS)%f@YB;a`rS>6LFRFk-Xc6+1#^-pHUWSFcxFKGk!JH%GSxgvKB>V4eL&mt|iA8xo9~`V2C +NOu$Pxa>?9BC-vxXNp5cfKV4X1fX&uA;#GP!#H9Sh{WA>(`XnqMt=lVBhiMyB@6aWAK2mt$eR#TigE<}tJ001mh001BW0 +03}la4%nWWo~3|axZdaadl;LbaO9ZWMOc0WpZ;aaCz-KYjfK;lHc_!aP|jDb22x{eAq43C{>Q*xpA#y +=WJ)UYO^T@5+Ms~iq!I9MKd+`+poLvAV7kOoMi55=T23LB@$>f`rVBtkJ-t0Cv1@?GP|F$x>}z639k$ +WM@L74Lj1E9WmQHz;hRbn<>0^CpTXd6B}=x>lR6auC#$&3N>;4|s|uc#o4lx)nGTnE#cniIR+j=UG?nx3qfMTtYo +76YQ7}E{EnIq|E=|~`$Up63oF2oJemI4Iy=0{@Kz_QrdwZj=_0Os+nVK0JUX)`0BJS`zxfXd1#4F9$T +V5|dvlgIP6*~js27mNV5T6!eLnJm6&eFUT3DW1hDDvV-Qx(HBs!yJd%LZ@r<7ta4`G4}n%Y4D{!e4k=>)C301cpSUodxvGMZN~ +zH#Y%1snmqcIarda028JMqAFM*qu1v@p1!-hi_cEuSMRP~U!KQrPw#%3F!2dY)S`-imYa=8Vl*q|WWW +NSdr`$AQ&v*hiWSfuEE?Jmv4QQz8E-Fi-{6RJY%^vmTxXR;yaLua#V@54Sn6&hI;tJfvN>F&S^l9{A!W#9Qu +#4=Gr!u)kvT^&9M!&nc+yhXLgh7TpNI0{B7-T^%6pevwg8{5KC1AbciB7AK^9Wr@qZ`*}f4(Jbw5J#- +VzCRlPn%G1fqC80qwjce57P17cMn_qHlErI6)yzIlsHPIKPF9&%BZG-Nl>p>vwl><0=O?JQyreUY6_?h}f +xe+@pN{50C-93Gl{u;NI~*yR$xC4<%P^$P&jglT{p#K&Y1v31B+_)LDSUemRcpgmD7|gghYt6Q<)47+ +n>DuV=LF39FPi&Jg_xdlZFS?q+vIEm~5v5QQ0p>IEdGUk+k`{Vgn4g47DIEh5pe3R47#P=f0M)WsMkb +7NpZOko~h8&szZNi`*kPCvGx{?NMc@*|rI6a&~Zz-)n3o1U^!PCCoeG5hOEK%`Y*5T))?;HB55Xe7%AAjd_=_S%>=y(XUjC_z$kPsY+8ur^y5f}bY^* +g(?u^+F`dbMUsp?HCoIO;#jcB$&>)hQqir*!GU +ZwdACAC@=$K5GlUTB@|NUFE+x1&p?9vFQecU74g8r47(&6U2R4VRXQymlX9LQH&bwsAd!i7_ +(s7O7<9Hx1i&~$_CS`fG5G^4H)4U?0SbzB86CKewr3bq8hhrL9^a_VYnfIjz%CqZY#Ql;@fHg2w~ERr +>GvWN1y4^DYhm4D$rpm=*h~k)lakylkc@Jqg^G9?*D8fSZgGv0%_)3Wdq9;!5_imOvrxn+0aJV2$}}Z +-by@QiSOgqjfNB9T5N_qzqFaR$`$2yJO;a#H>;$Pk0b1lB-=*HQkrmLK*h$cok<04!946*VLnGo$|sMLY6-&iNFhqR68Ltv9OeN ++X}rBZ(MZ~tfNeTrTqAj!oZJ4-jF04_+nvEkw9i(rg+kolz9r&oi?6NVdcu1VIPeoJ@kqR1o2%7QQCB +B-1f_tFAbuG{?45eLL*xW(dq&bN;81F15B|aat)3nl(X;MsU>7QbykgrtE$>e|L}()_|LJiNc1<3K@N +*Dj$x7aLkAEH9m39*@*cm%ZLMrBYULURf@(d2NHzS>6S++~8b51?ngP)6(%tc{n$B7wY9BDda@L7|9j +_smE?hR7u@>m52O2jQc@tEMGI}<^jQ*juCi1Iry(k|y4Kr~J94rcuFtA9h4q)gF@?zci`N +3{AvXA7+(7yz+-DKtZ~&WzSSLnD=<%^W7RP$0~lLfojm{={2jqEvU*z#F@g2|09GflKS=6$M4->=#s+ +DdyVvMT;1l +Q>y9<+ooXeOM>$LRdY(W)U1vnAiMF+}#FaO4@c?1c+>Ma?WT;p8>>WcFNTRSXL*&6NM-*?3#%jBCQHaA>@QO0oYW}$(!ncv5D?=wC5NM_QPIR_3~pkn +f>}$&jI!=LOlnZjt5?m4I)}(5S0}|_;DA6Qv4C(A7546z(O&1%FeN3#va6On->rR7WXwq;&fV(AV$(M +oRC6JGs|j6FkIEfO~7E_7Ac4-?U3$b&C*krkhwnCQXgQXL-yIVIx|;xN5B`FUd}=qGdoaATk1;xQxOM +}N>!+7X;`Dbo*j#UY$hVX>oko9gfvstY8sVv%5%25aW5Z1VhG0XKu>8OQ=7N%R6ape;JtJRmI8JMtAl +bRb)mI^mhGs+RAB+&ypn2j8;d$qn_~wDxhf^KU|0w4iHYx~8o}<4d{4hVI02r49Hsddrw)_K~id2VH +8e+1l)I7sw82;jyh2N&yiHerzBHT$YGBnQ0g_W8pK-LLbZ$NCMXYjg5lZu4VH!e^WDYG`Riej2xil=O +k}Zd&k8z^aR>$MdZ;(B>z&$DV^}6MyF0UIJ6G3N=fl9j1~U@p&#do@yj13V8X}M^u};ggg&^vh}D;ap~sL%a6t}Q%qqKCnxAp19epL)CAFlAzs +z(Hwi~VO$kqKNHBSxI_9i|KJwKh=&n$1{CKBmcS_h9I_c{$An464n3?Upa4X%%*aDNz`Qia_MSVt5$B +;%0(dAMdvo2%B4W#&X1)IThtwQk9b=Y=q&J>l=QMD6qPuNi19O{a)i^v=PAehNGFbX=6O`}#|nI<_2G +6zn>l~~)<(yC>T|+W`-8Dh(I{zV639#uLD)Y5 +!mt9V1Ce~tu_E43-W?sAK$mQkO(>PTb?3PBTj1 +g{N9qAG)WlO_xcW$hd%C@zKDk(ZnwBw^yURJ%~gx>=?I(iFfD_jS}z^*HNNysiR@?>kCVvHb?&jlBje +!BgFngaw|ODj_h(ZcS^?k4xuf~X!H*fq+KEfDFkj>a}MBlx$^d>ylE)C$EJ+twf$<+S8me1wq`Sba2| +(LQ|qI4nz|d(`c86vVxKF3^vD+wS1N&o_6ymL;lsiAut=o$6--jnW5>4b-!`C`4AJJKcao084xHVhY* +FS%d>78ZE6`FKy~sx-y|4;Aq^Sa659!a4n%r4Uo*jp&Xs(^jD-Kb?nnVK~+wyuM$7`KNX@Z4wAvou*_nR~!%OEI +1HPG6Vn^qB5bHB+x}jp2LW2rg7T8w>*v5-E?ElTZ$#2m1YrW2C%eN@AP-2C~d +_^jtDWjuPYcR{Mr@_|vke~g@a) +ITzDOOl+`K?eBJles>plkyYkfqiL#zzRe7*=t2t}5jFd@ktdM^MV=l3bCB($h)K;m>rVqF-Fu3}<3Pt +i+XxtK4Nnpq{06xF3c38+n4?|_wPY@a%w$R+h)-P1x|1+irtM0%A^HvAh96g`dVcSTPzT^;yEGmr0$ +TO<`TkR1GuDelrh7kZ~RSk+j;$v`=%O<9|zPdU;yK8(cdCY%dS#%l?&>U%ADP!5}Q0I&LG9YAlP>`A` +iPOpUw4-;|=9-zlDbuatcuLLjFuAIzv42Z9QE!>OWY2(#K%mwbdGFY4UeR+g2K{F4`4 +FB_U9xX0=#RNac%Ac?KPD_nZY^ILy=m|D~0HV%ZxQtofkrp(I@-M$e>@7VR#6}*T{DfO8KsZ*sq{B#M;q +^lpX?i672BvIDOkylSUrUp2h7nkPqWy_uwgA+)zCaK!_5UmH0GD!Fb5=`2CCN2X#8# +oalB3|Tl8?ahj1FC_HCc3Yr&wyDMf(-Iq+tV4x6h-;m(|#)uq5l83<(6?5;f*o^lS?6C>s_ZA@)hft*IFb5y +nb>Ahz9-l`qUQi7;<&_AeND4;-{_`022=D>o+j{d~Z&lz+U^{&1h3oR63t?nQJaJwQIR3nveGPLU_@U +=71us%*iu=T}FX59ryqyXP@3m^5Qw%%sfOSHFe9KzuN;72|t)5z2UQz9A{|!(}0|XQR000O8`*~JV0; +;kfX$AlQ0vP}R8vpt2ABj_c4*m?H +V4$Ri@0~Q6^B`ufs?XVMNGQq-N1pB2(mbfzjg9sP}^#gb(IieQKV#~Na+fqBooF&aX1b<4;One9g4R`rO ++Y=Z#@dX4k$;Vz|~C?F{s%um{1(z`1|oRjt6vcc0N5mJDvDHj>h|k{_6Z}5*D0a)EE2HgCo8q4{>}rJ +{eEPF@d>;g-#g3#jBIYplEG$--ZZMrKJdptPh7WahG~MfJ{;V)EsO@OdAF;TU)xY%7x2W;*TtxSMsCR +KtUx@HBfHCX!%ct6_4~j@V;Fb<}$ZiWZK2csV&cC1YBOOuiK}();< +Em@fXQt0jsQ^}AxuCXS}lb2#$L8aFFRIrE_LM5#-Ow~ceq4&1YsLf<^W<4kkR??WBO-9qHpjW+CTdq* +aYX&a08_tp}}>mPC4_gP+LR+1|VSopdG^jwK_*pM8@n|)+65e3YUQg+ARqUXIXuW(KV&IuvtbMAb7?~ +(y@6IEC$WpC`d#4c^wC`*%vZ{BPB0FugBmA?sGI|QpGYBo?W-F8dq7?P`wPnyOll`&ax2$+_z0eC|#v +4yJE8(SZZ!h0Uv5_N>fNRkmgFNUb0j1r-w{(5Xv&t1oO8#Z5P1;|KyZlXBqlr +Xd`24QQQRZjh06pFVL+`zY^qwg+lS}j%IwAdoB_Whq=E13vw63q6|pQ+t2Y``sW}~5Cw-w*hN2k4*u` +y>O5@^3jbapf#E6^1i2A|EwStjv0V8f2P#Yy{Lpa2_pe%CHisPxSu;~*H>Ma~Ia^7kOTK#rc`3PB_i? +Za9(lVqC0kgx=p~xueBClRrh&fi+SO!Is8M-sjAcsG4 +p;Y#IybmNE@<4Oad^#6E_s9E>?F5t~BZa}WFg +xqGS#yftHMiu5E*)kI%SYI}_SWJlN6Td%`$BRC5XNH2@3ej}o05Xa%n)Q_#WMDxKDtR?6`YLhdZ7~%C +N@Z^7P^XX$~dUw4lU8Avp(2_ndm!Ci^Lxj3FUnfHVd5C%_dJsDH{~tMO1Dm1x##G=2(n48oHfdVpAX8A@CG2g?u~;ImF^V%JNafp-$gc^Gn +7A`J45l~SICF!Adhk%SOOpIJ2c;L=>E3JPoOTa7HXY1HSkwZ1ApD2p6a%E?o-Ug7r}g1f~oG(%Po-zn +=Ic;Cj4snMJS(>P*?(wkV=XfT6}Q$@c%XVD8`TfG>kqxw_EvNP)h>@6aWAK2mt$eR#UsQi7Rsp007@7 +000~S003}la4%nWWo~3|axZdaadl;LbaO9Zb#!PhaCzk#`)}Je`gi{oJO_oO)>a;CyCSGDWKDK0+NMR +C;d>%HsEalk^@LJ{*#XnDSzNOe#GaKEpS?UccY(N%p +m3N~@GsdeQr5`04d77hI90m{vIh{6bS+D54jPATTR5pHR(3K^C-_=1eLw6OvqbiiUoVFH0dc5z0SF=A +SfQ*3S#77C9f+OAE;;3hZC3s4U>C>h)$)EXkNE_Vh7vi{qz{Spf<%x0+7GvZUHCS28cDRBS+$mIH$e4 +@lfYi!08Q2WK~0(uvO8>r9L63zZHwOT0w3_<1=E!T$5Zk%B9Rk7B38}RxESH~NdF +Z$sQ_z3hzL?v8W6^OJ`{Nb!LwXKyby>LElMJ0grkqCcDRb(D)SxwK*){Rf?jh`NdjM{)pAV6KyJlI!P +wMLmkb2HW)2;QjoE~P5~O6JglMIbL7?TgAR{v6I_*1H_zcOL>WV6A-?;ae3m|Fm77YF+mu3L3jvJlJhG@07<39WYCJ|IS2>>EyXn`0JMHc58yw8%4ehy5UUE3OVeV) +0%tU#>fC`Jp9(2H#Pw_At#eTfEdudW9REU)ToiLAQ4&>^7?M^NpRlCgre$R$?{fxHVE$h-lbMW&@fSA +H#i$*h7nV~9L0hz!|DsTSgLJhjmV?5PSS)>-R0tOb +R4B$1TW%Eg*!=7L@i@$9>+|YD(-ng8<^yPV2U{~XR&p8Q}ur~VP#-UVWtqJh>YC~91YdnI1TDElOQd@ +hF5n3(_*p-;AB~>SUww&&_t4QAbu|ND^H6}&!iMGj!bh|K<9#ge0Sx+9Ftk7nw01;c3b6(8z_){ +yPI*dnT&cH#CD~7^=M1CzAfZ>a7&FqM^j_%hydX4=#2}Lj1F43`?A^cbI9}V$jwt>B=KpG4)=Gn_Cl8tq9u|SO$DUV9|Sp)qS)z9ESfSd2;t+F;4ZgJ= +J_lBM`(suC{|U3k8neo<3>)oV9S+IsuZJJbK75GU!eaC-r%#Mw;+sy6ItIZ!dD9&v!_N)qdsB~*<_ssWzyG-M?#@zDU=Dnt +m)uzW<2FXUqFT`Jry?*vyswU4&|NwXkDff;tS2mdS5<-kHz@0+-Kp=B!=ugm!oqj +e7x;gJzFyj<`n<+NG+PRY8j$I6gA(^2sN)B!PacpMmy)Dav>MVIi#TZmRO1_D->#8+; +H;O1Pvh5p4UliUrS +Y)wdoS{TT?LGb)Oo#5Yi2Qi`2O44E~mhrQ|1rt9G#jSd-S}FhO`0(lHBy`*T+HW^7+Mx)m1FPe}iIH? +;L*ww(&3jea41RrTNd#rYQG=jMk{|?tmKoykI3M;+W`J-{kh!pwsxs#=Rt>syydgL?gL~f#f86j}E1* +sQ7dT$8f#YT*YvCk2bo2a^I|AAkJ$nIU&WDD)(CO}CxohKL{_WOQSJ!Y{!)p!LM7haMnL1~$L|~E=KQ +)JDc#Jh_jz30V$-XN+?%A+%&rnz0Q$+sJHE@I`5{0tBcT_ZIcdS$cZ`W1?+X=dDNh2v%)qz{Cx!A88X +X>paYKWRj{U#)2JJMDv*19zb3mxLGWk`SwYok7f1lo8qdZ2tnHszfb(g61^Ci7vDmY& +8AxMA}(GssMt&@gm>4B+Sj6oWtjV=8)w!Z7Va6~q5YWK*iS;Gg04lIR_Z(zck3iE8PiW}$;jFE-br8? +fj!j*Hd^`53)0JZx1$( +rIQNF$SkLGHG;)ge?5P9arWZm!&$P6pF3q*yK#?!Q>vS-a!t-uQ0Q5#8EwV<^>o0Q}P0BjG2L#s-ZyQp_|XUr +=W|DCvUZIq+>t%MzjG8i4oN$gu5gsxg_obF$f9r`k1Y$F%?y=KAP3Jk2&I%R%XHi?aEYI%${t6_jv#4 +P6C-5r3lKCKz?aOB13X{f;WSZNGHq&COQ#%i7hl?PuNHjBY(TmbQLsmgj-C>}JABO-?lQ&T*xgxpwT9 +{8#qgdxD7$wY_~w0;4A0m7DaP+BWQKznFFNA177G@V6}W-re*8YI@1g{8Gf;lnk)3;C2O(I%L{?$90> +Uu2Iw9GH@L!f@6*r9U|vz73O}Y?w9~`$PtJjyqAsZDL8Ul_hza|z8T3+Rmr!d&z<@6e|CNPFHlPZ1QY +-O00;p4c~(<`vZBu}0RRBe0RR9U0001RX>c!Jc4cm4Z*nhkWpQ<7b98erV`Xx5b1rasRgkex#4r#&uE{9o9CG2JsPmAzwn03k6%PN1E}xy}a0$r28Ywp5zVvkeevx61(di> +gZWcw^Z9R#d2TqNi@vl3rCd}Jazp5q0;!URr{GGPaes#?f&P?nIR_4*^3gaHIPyj_vWZwB3TLQ?*5i3WrCQ@&V5&D<4b +dD46xm$(ZHMcnj7j9ZBBKp|um#jgu42pN|MEeD(#rPmMQM$Z;ValM?k#N33=*OG7^aFOc&gXOy1%%D$ +2KsMQ}5cYV}+jx~k@tQFUhwKyEGwzhLD)~;_2#_5$MZ=@?FBB_bvMm7?!YUTMOxUk_iV-(WDX>`p3f=QJK73-zu6g)Tt;lWs>;(e^8S; +1^9k24zk(|0YXMGr0`pttp^#djSVpxi^{HXgo~L6Xw0O+RoZ8C1u(LIUf6$nx?0;?{n_Eu$aY!{ +nk<%z{ipLI9*}7f^d>$*$I46f+&YT+(64E<=>{9k{l+O=6YqY5S1w0o}GybnlY_dJSNd5SSiK2tAE1U +?!jH4Y0My~R!G0x$l!Wor&)w*m<~IG-g`2e{DWD~H`HZ;5p0m)(A+vu3 +g%5Di??wX|5i6NQJ0?(3Qtw-0nz-ToVp4N(g%B^I8pU;kX3aVhy)Qq33K7&?Q5;G?D2N9Qn>f~|v)UZ +~vpbgR8>thFby4tld28p<^Y?&w*ImC(kZq&*dOXho6x+N3BG5$7p&%%GB*w?iZ=ZqOcp(e0-Wx8z(tA +!qj2WAAwRM`S+Cm8^Z)pP|ei_y3Xe!_hJPJ`r!YjW5#yRz_^r*xhs76#Boja!k2HxQwrh-)8VjoIT9o +^nc3O2}!eNbNT{jf5p}*`vFi(0|XQR000O8`*~JVy(bqsz!Lxf{zm`+9{>OVaA|NaUv_0~WN&gWa%FL +KWpi|MFJo_SYiVV3E^v9>Ty2lz#*zN6U(q*<0jZ6KPMj}y3WPh`NzQxm+KbI>3}Z8pIAo88I~1vOlcN +<3=eMU``jwa-aNj +>}f;4_OcFJ +Js&Qy;O1`G*K>cCGYy0$D3Xmt#`)1U>W7M7cY|32CW<)ewS4}5Xlh^BTN?7%}0iGV^T6YTM +VtNtO6`R@AxHFl+JfmIax%w;yED&6x;70ALT~%N!Fad0R?iyihkih +l50-v6;4vLNGAxa4pc2y(A;a ++y20kAmJwa6RP1TDLT*uq9NFsCGvWxLC(MvL|iA6fMhc~jDF9v^Vjs%l$-2DqLqGyxo-Xs_C)K4#2&U +CG8Y=zZJh3N`$$RY0_^VGM2qlErx%c>tZwfom-uD-h~dc58UJHV@PJhSo$<+AE2Jao|pXny7ZKs=Sjp +^ABxLMB(8rsL@dza##eBV_po!4a(~dgjIrI>h_S?O=ni2O{_uPt8TqcK`rkVmSLkF_`&DdT8Wiy8?*% +Lhdw|46gPLry@bcs7*azmg9r0W7r>kJYr*y70O}Pi0VL6@#uJQ3+_#|B(<7#*ZDd!sMNEWud<`s~qeo +=iob#9jcpq>XsveGgA-)53u_RdkNL6H_#k;rPh>Eome4-NfdD$c#>M%X~x(($0!YGe>00W!0vpbZTUB +CsV364yHRSdacVFa{a(JGFjtV)``>QEcN)yYS&RyG!}QdoDV`hu6owhh|F2Ii=tcwpCuE;Ajh^gXZgW ++7z5!Df|Euu0~Q6zyMmN`->;za6X%B@52*U{?t|OsI!(%<20`P&}#8&jqowun)o8-v>WbzSHg}*oQhU +rbykXe7gfr;o5Ej?Wy#-Zyu$p?kWrlCzS76^?~N!=h~Q^vD_x#$?+h3ioSLrdGK(9rofB`<~5vzd`jN +e-Rj%6nV51)9eLbnrl8HxoCjjb%QBw*K^_<4F|UI;!I!zHgE9ew5VhP^5a7XA63#$pOu=Ud@uDq%hjd +Y)Z}Z{-P3YM60(@jCfSU|`qvI#9VEDIf2eG%7xO=js0GwWMd~?oR-?x2T62K`-&{hH=xct6RvaA#tiR +EQ}G2n5kQz&lWxR)c39EkBf_PYTap`P*fh2iClQycI9|6s(CsyjMz&(Kk#HB{&MDT1Hqbcfwf()|<<9 +w8)l%cmjIeb?;~-h6$1{pPjQ8gR-KO3GkYtLCnSUlu22S$4S3yZ;tn=DGsJS%W5nUur?Y&{-KThjg7% +5x&GmhzlT17d0H5ION9wa|KANg&{!as+l7H; +RfM*V?@1?V%_Z`&PlU<*=oVA{yq$QkBXiV4UqQUnE!EKuO80_c$ogW9$0G!H46g)RkXtY{C77^VUS|H +fl@kxRik|6AH*r!Bz%yNw2|q~N9Vu2ZR{un@`G0`9egMsoxl1N6d8*|_&;&*|q2lIFNKczX%ny3%Y#V +BCAKy--&NY;Z&ep@8CrsgwoFX0|;5KoFrpZh^{EI0!VHu++XsNP+;kbr=_BTlfos}R61aa=T+Iwx9 +W4FhGobfd$@$W=B~FKp0`Ic5WW +~FBU<#@;ObraN!t#zv=Ii!6>r`}Ex~up7pR)wrOiBxL`1C^P*FeL-_SEoG-(B(NRA6{4q$X9yCt~yA1 +hjBoE89XhD@`U;UHczE(C2Bd8gHz$3VtV%EcRdoQNa-kup +1Co-Zbroj6aZyC@9r!6!brNZ&&6lbv>b{hA$DFuxwbfDa|{(z*igNg_7|BFef+1V=$euR( +$!m3C)~Jdi*D;4;ai^S^fYY_u}8-E#zE{hmv8z>7er3eteFh7l`WSc7Ey=nW{O{$a(8%aZjG#MdQSeC +RbX$KD~5GC5(01~kAHsjyC$i7)EfOM^;xCVM%w>VZgM2s4$k+xBBj3(y3Fg2$>PebikJLZZ6bX5(vq; +Pqv;WkuR-zWM%5&_o-s+5EApW$@I<;$2f$gglkp_BHHhpcj7mJM*>omd0UgJ~rv))@!JaUwFz(fUUtqo2Aj*mp?8ld*#p +=rz)*D>23!`RFYB1%_n+i3sC*9yn;r36rUzR_S@A-=eED!dd7j!G{r`G*rbPY1xt74fnS-A3iSQw6?_JL6xG3=0Oor$wT?QnEj_$H%|L1h_3_=7x-Dld`VxlTs;m<&b~YRWqZU$A4i +F7mYY*v%4idE*C{A>s$DSWAhT3|5SUh$Wnb-~ob*PVNvUPwG~TEV28L3H%%>lL}y#=lw~(CwUfNDBTp +hj!j4G47)>CPqS(E3}@xSvfqt)ypM+FQ#E~>w5#GOQD+h(dXYhW4Z?~T#mOp7jV|G7rJMG +d28_I;9+D|jxlcssaj +v;D-Z*NHc~jMOF80Un9)JnsYVtTHeC_i}>1XKDc{xuilaWQBOW3|Pae6|G?>dI$eENVKliZMN0{NykV +3Io&RJaD)>@xo6D}Yb^4Q@f)27e66xN|U<&8(xrqbO%O55z##jZYT>4Lc*V*lW`NA6McVVRZoRsZ4f54QU96i9lo=`JFCwuM{0Z>WQ6qWs=d(`%E~xobl|xpgR*A90Z-rXnV+w$1#h~x#w9#m`jD0fJtj7Du$C2# +V7ykHE1-3p;POPVt3ekZnk|?DT)e2*Y^s-^kA)20Ek={)(k;cn=p?hVz`=2ylPmJi(E^8Bf^%pXtex@ +HGrx!brBOAFxiXG6@Okm;yLAqc_>{yA>HvI{6NTV&@UuntdG`szIya$3{;Z?k=lZ_jTBbajvmSUgsk< +XtfvHLGu<#Fjs#W8`YAP(wd`bYOv2ala-$=B{;`?7H@Hc>d^Vfu(!(4Jhtqq0zEUxdibXb+=XU-9sXy +lZlU1XNZr{)uX6S4dBGFy0I*t3y=a-Q)%6w~@TugO<=;xbaXo9uH-#HH8})mw#xK-JwWb2GWY78x0$WkWnS4- +5)djj}gNBT;1pii5elXcmkV2|1(uFJBvo#2|=%|T%G;8syskyOp*-OoY(|D^i}A)3lnj;Hc1<^;I%>X +ZPh8hHc;HRbP7YKx0cYKr4$ox)Lx{bi>MG3yi4oQ*TuoZJF4+Q#`j6+z!H3hIchdR6~B@rbkJ!y&Kym +O)S|Ze1PKFKP7SS?DD}=4briH!UnsR(`<;Xs +3HHhwuvIQtOl9<5;N+v+Y#iY`&?_jj$kh-oSY6nZ1GdKBa-XW)45b)Ap%b@I4uf4!45`X2^c$IW&$P* +*4lJEX^gv#Us0Z4xBTi%W3OYl&=eg-**PY&G~4*Ea&_pR00?!`0PX?ovPb89xc#h?^9b!DWsn``NmMX +duNe-7x9k~N^ZN>oCner~(-sfmtv^RI+qe5PaTg?O=r;jSvF9KO3wd2|k6)POh*gVgX`XP?2Wz)=#Ax +&Y98yy_3SWk>Z)Ig9tMKg+lJcp#Atg`yBWHir#BH32C%gbxe1Pf3mv-iZPK15w^(vm6F3_3Z^HJvj_K +$XtyTlc@fbb+&{@%_T>gjHiP#DclX7EhcT;^M&WNeye?%kT%VB;|P7#(CAV&vp)N1M&j{l5fhhUwP}@ +x4sIMU^tIF*Ne!{>CpDc4j2);e2$3%p21*O%PlI?z9LJBRk2=ZUr*65!AXe +inpmYc^TOas2_Hg1GDaLA9z*Il>A7vD^mpTa(O#pl2I^?${y{E0XIHZSQ~&59~jMw7%9{bH%lt=XEcU +ZwXjSP7`+6xZbux(B*T+5I+Dwt?B_9Hs32jv73_44J#JXS6&-@s(Y{`(&4YyvnySxj9qY_Y`3I6kS4w{{c +1n6+Icznvcr*e?g<3s^$+g%hEvo0{{%}=$3DJZY2T2@l?!X9k680I%El!yM{D&Uo)ahE_1-p<<;fw)# +VT3^6Ta0>*eL2#s9v(PG)1S?c4Cg^>!B@|ByH!S=^IIMr&Bab9=!=fh0E1pcX`4Wo4;a_j9utc0tFpB +E1~-o%_zludN1-&|q&n(wfC$D2kQ&>l+BZ^SBJ(M05>=;VnTnk)RPwd=>jhbdjsbLE6Fi4hkM;CO3h* +G~RN^2z&Zi4#xY3VxS0PmpuqKCStj@+X!}pX{8Bb){1$%0068pe)EB<0bclh;X9aW3*KiuV1Bs5xZ4u9JZ8hN*WcA7CHtoGGqsHN!Jos7Vp<)I=?aYVeU3OMopicC0&wNvXj&-Vybcm*|R(dy3-JA46oXm#6nQ~1GK8lXH +fMQ2eHw{50vA{>57_5T4-O9KQH000080Q-4XQ)i?&ghvDb0J01K03rYY0B~t=FJE?LZe(wAFLGsZb!B +sOb1!9hV`Xr3X>V?GE^v93R!wi)I1s(-R}9ia?7-2bmqma*v}uZ_Xxbw7?jCJg99!H-q?V-Ocn|&Uou +Ne8vK<-m#S%61-hAIv;!B2=S4O+!*YPh&X1Q`u8d(=eS +lSDv9rzr$ua4lvzwijRi|A%Y{&uzG~4Rxs)yX(qJs!6L-ZFFj0J>^$Qy7fyI@JCq4@Wa +GW+ortfOPh8(6Q&(t5hh?4wuW{OS?XX|nXx-~)>XiIC{qa4F)DhHf#y$XB0ft$PqZC>d!Me#ELear&2 +x9y!ZR~9vi*8y6Tpm%#&OVU3J&EXdE#`MnqDf5rOrEVYdAki+8rEI +L;YS}FO5<68?K9f8C{czRk)tlkIwQ2b5(yGVCsxQP776?~{Jk7UCqr#0L8`dpY3mEU1u47$r9*V0D2( +^SElN}Ca7J)uy#e^^@f!!MXfOsRJM2rgX2G$o2PaU9Ct~Wfx;I?LJWx!JD9Mdh_$_Bbe*UHeF=%$E#G>N*P*sCq9h@~l76M#F#K +=HjX8=9wQCHc^#)76<0bF-m-2eeZRUucj{O7d$Yd2ry+YkV_XP&Q6#mtP;f5w%_-)Pqxqw|T9fP~Pe0 ++H_8P_xA)60#9ewKmfKFu&l#Q;WIIEHb#en@5$&B4lz&qm!n-Ep9LvK*cp-maCc+K8r;5ydyAqZMpu* +Myhb(KR;OCk0)|lw^3qX-t5-qLa?4f$JnicX$^EsmU04qK#xb&t%&67=u;EU%wJKeB&_`1|3Qre{oR`j}5TG%ZVsMkI}XjJ1t%p~ztvF$6t^ +-lffGEd;La|3D6=}U?xoXvbb;7dhHOIEaN9N$^2(Cg-S_zh4?0|XQR000O8`*~JVVHPL3jsySzgbx4! +8~^|SaA|NaUv_0~WN&gWa%FLKWpi|MFKA_Ka4v9pwO8A2<2Dd|*H=tk1hN5F4_g!q1jtJl=^{bbF_PV +VDGEWhXq$;lsw9;}UF6?8yh+r}bUTrQ +H?Y!>YVKSG-BDpd)Rra+HIs1%ebTxDBDIg28<{3!8<7b!Bebd8C512UiiFUl8o5qy44i(7 +UKB++*+Ghr3UVZvB09wLg~h;}xF?tk3^K^+R>2EZ2T%tB>slQ+gRUB6EC&~bBr*tV!JlbPV8fjv%Z|j +z=^suO_--21ZnY8u7m6B0#dHoQ@C(L_yk=T<38?kKX}?R}CqDtzT#EveL(?}H-(qb$zJ%C`#!HAD1HE +b#<2OhA^MOk6DGx7PJW1GKnuVtHHrDmJz68pk%!H!bs>sArTQ3FQWSgQkU^yp}#mc|{9uv5=0Ql`jaA +x6fOXl1m*fScUd+Sqq;8l^MZA5W_SKK1;N*TXxIeG`9BM(_gyfe2P-L7pRwj|8~m5Gn6Pl+&qSB+d!8 +d8JAu->7&f#y}~SDQRTcz;4i%(y3ruhmE~Za_Qx9Q61?Cgv}O3z%aqLNjIElnu&uJUM2MTr3-`GhY)} +l>LWTTfrCY9(NORf)pf^`1VW+_zw5Hqbw@|@0|8foIG;M=D!U{kE<-tV9|m4{d6R6|9z$ad=DSER#OB +%VhfNbEw%^k|FP=p?rFDwVNZ;|u-G0WTbhr*X(PSHE>Ef};JAFd{NV%sTc!;{;s`vY_`Tz6L +ZcOuzibO?Z*5dP4enk%DL^RIQC$tOrJ4m_k?Y7B~qw?b^#T9@9RL6TaA|NaUv_0~WN&gWa%FLKWpi|MFKBOXYjZAed2NwFZ-PJ& +h41?-CVBvka&AaGH1W`+Nl997#=r^#Y{XrP$BrMhH`Lo0e{df57viJ5{&D8Mk}^^ZDV6>%#O7$o0B!q +qw;Hl0c~yN^;%ONh}08f?8loGieTig7&yApbK=>ujKx6{i_EB+Sbs_ZHatH8~SP$Zn(AD?;U$_obsxQ +V{bNocF*0qhuv+j4bGXT+t +hrJ3+28c4!)IBiw)M4c)CU8UhS)1M+q&sgo^`q_Hrjk|Z`!@ReRa&=bj34NRee2EU9GaKy2ur@EsIsr +Dd|)KkS67B-izy^yC^D{MWeGEW}MxqRkmxj`mwGrciT5jQ#b1RLRYHovMSG-T$ROHgDZdqLeEV7%kld +eKyG)d3zgBcH=8q^=Q>yKZ@P=RO4W(h>Z0qm?fl7;a~N@Vmagi}lWn_xVlernXj}O3&+=2)?|RqCllM*C)vLOcuRqqS41v7I0qNOPF +jLe9?#HU#ZSj-N-)(h6?YT{Q0UK+_&-FTk^)Be4G-$hKx9WC{wyj=etv;?=UA0A5T~*R0&!%)&QCx`Krtt +)3%$u^>bSbf$|C>x;m4VJb+M~1b^b<(iaSvMS8zgg)zLoKH~6%IGYZ4WgOd(U$p#*`yA8nMhcd#SPu~ +43o56Ek6acfSvT}h`Vkg|yiz-7bE&$Ik)=cfHf(Wl@7g@V13hD3l;$@x#RksVI+bY{=MjUPcBNM1>rt +mn`9nxXJOKWM)i=0So0h_vo>BD;-6j{dNQr|3K4U4j9VKtVfqCe5CNue4TUW5U^ +$ZUG7F+9TrU3D)OVgiFQ*<{|OAecD*7>V?m6c_7R>E9<%C=iX2|eb`hjRlMYRX+j^P+=MT5v-kXGVT}TDUCaWcakyU+}9v8dNOFh;?~@N`Fv3U#RDr=84R%N>J3oB3YLF^-M38*xNQ*?u#I`q0PUrFAKtt^{_y72>Eh({<>|3H5~L +AOkUm#BpDF)uUp;^NmD6Zl@2Y5{XHKIUWK$Ds^xSFGXfO($Z+<>qy!zqe&wp8*9RJH3c>dK>bpZc+_VwQJ>6;J!yKlaFwzs#pD +zmmVJl`DdpNcOJQb4vO_}T#uC;-8X(CL#4o*ww +H9XOLeOr~S+sHZbXqd_=SYBEXxRs)Xv<^2%}zF2KE2>sk5c_DHY&TqD96BMXEs%gs*&+uLYlM%IITj- +U>vxGg;e)zu@Wr@lLo|W|u&W;Ff7XT?NTObS2LEs=~3I&Y?Z%kzKaF1{l&0<3u*(CzWW?hCUrSeSG0$LXbHDIzU-^kW6-#1crwhq@b&1d%pVL7 +GrMX(58mSZu5fNwZM-W?<_h;4$Nagjbi7rM0&_@4o8ez_nKF$g6S5;F(_qn;B*Lb!1)zCf&Ta*!uI>P +KJ1qBB;D{tq0`2JSrheILf%gP~JX114= +Q3iYup0}@F*VP2{-3(72Ob6yyv1Fp@wsRcd{mpZ*6cn~Oukj!@HEhNhtHlw0&c%{$oaL`iuB-%+dDu9K|zkr~mXK_H; +18gt*3xxL&AtNCy}ac0nB-sb}%+T7xmTMs@gV@S4tj58(N20A{?B7?4mp4Li$8`k_eG40>hY +HQGEida99OCef?#BOQO_SZk(77gta|CdMi8>k?LX7HiK7bUPB-0@rm@}O5`1wXbP*gj8A2jt-^?CCe9 +=eXV1TlDY$#VZ_&8*#E_6WB~WX;j~BG6IR=_b2$JaLH#ss)Owqjl#uA0GK;?HnppEv%>`2r$u6UtiKg +8=5>rP@(zx1NzY-wkIk@jXsCd1gsmKZDbnGl)eH;fC+fwt48n1Rt2@j^a9C-tIrNM +N}*Vk35;5kJI1qtPuCpw*#b9KT05c~l+Ji0b<^v5?l20nH17V8SoaRni=;fEgi$&8g#jDLvHh<-_+&i +Gzq{{}J`|u10ijQG3p8&-eT!w-AB{-!Fk|FN)O#GPxEh7r=DnkZZ0m^g;0Fq@63aR_5%b<1fa$<2C~$ +X>L3}05bPp3iFziPG8g&#dOx@akL8y^q|8ph9%C8UJmH^RkTfjVp)^|iCkwsMC4fW-hjgJPuFFD1X;Y +Xo7C}ItPeAAW4AdlY?jMU3Fx<^wt_AAdAUT48s&Ya$SAW>r)x07dfEi0t+r*h^3jO?(>Xg;@l@V9Ta} +=+GFr8vdP7B%$-V9ye7m1BM(eBv+$KD_^*#RU55=L(}Ez~QRA1lEBXA0Cw%S*ueX+v_9|6rXMy^@ +<+XP5m>XeCKz{$wYwLPQoc|JER>HF`$H=b}4%5&bpBapT>T +534Q~XpR#a)-oIg3&o%KZuh*bE2Rcpf2uRa05&)*yF)AMwY997XR7R<~&*2d@l2o)lV +CCDeEl<+qqL-|a441Ekdv`Br@26tFK%AOdoh*kNFM}a<0=RIQw^NM3}N^iE^4ed@#yFjiB9;FuwqJ@3 +>?0P8>CYu$WW6xNw+?c<4t?7|%Zdlk6ShU!|eD}J@!#y7hwvT-Ey704p7>YyMK{zVu4M8zU+rAfc(8- +HiI@+#pL6LnW4MUB|9%VREIk4EZ;97f +67b&<`><3HWkwDu`0t57Sz7Sl8HVLjM6fDiV*Nqv-%dli*jjBd#Cj}HHyhedy)KDvQg-ofb{$1WA3!6 +lPnn1^<>8>TMs+}c$$+tvdCt*U6yfMY9918W#e!hh?zyfovjQ|$3Jzy?Yq>L|P=eH6)Vne_PD{2-x`K +dhvTBDR;qO#l<5b(4yu+Y((1ju(`oi#wP{O+y; +T4{NFsy!^z7-=J0-uvZ}vs)hh050T6(zIrmfHMJ+GMoRNmEOqk^UCfXmis(8_i$Z1zq|`k@Zw-J+Kp;ld0ajHZ7lnyKMk2)d7w>Z9!r +ePMr2{m)^|xrWn72d)Ea3qNdY%y$C0Qlrg)el_Ag$gU5|;*!mnV88`FTiBkmpr{c1->8lnDCB{nsdCm +LaKxhNshb=``24)MqClH5x_tK(iy@&UUSMMMf@mAe#h^4MUd2c&!AW9bRqM9lmPf!(FOSsv|xbY3&>H +Eh^<(Q{Ro$)+>~3fp+R*9noah2k-*t;-I>{KdrMm+sVs?^zz`&Q6$D&&k}uLPir|KM|$OU^zII?CX@h +7-s`~+#R=>r(@r0AQ@!J`_XWt4z`l7yszZ8|H!GD^gi5C9B#O&CZ&zolx3#(+E(LsjSWLH`u?F!GoMxB|MnWMNs-@ocfgE}j5Jz!&JWl`nj4F{1W2lWZS0j^e&pl{vYS<6l=Qd +x>>Rqk@uX!@wLNvTA;|7k@xYUfXQq#v`vVZT|U=Oszju16Auzc^>Yvu)eXB)i8NR;ttkbe +I^=v-7E_9JpXDYi%C26{98gOXq)Vl{l@(c8gW0FbN!+ICEnzkb%IGzIdj|~rMqBma?JM>CcQ3w$jVv8 +K3$QbpPtd=E3qzY9ISDNf%P5j*N*Y{?N=iajOf+sZbk_9e%Eix#6LFVa0&KzRD+-RaGy}}TbSQrqr6J +U@LN-&(BoOM1dd@gI6I75UJKv!7dDNE^@YY-UVXiT(0l>o&CTkJ<=riDx1D$5gdF#jT$gxh-^LuN8dZ +dy^0)dF*m#6>G{5l7|&3!VP;FPNOy1B@*fUL7A(_IkD=wVG8rV4C%HOQALP^}8wLSPej+S9OC~*E9(S?wJOrUDUfKRc`_JlC9MNH!Ok;a34$VoEBrGhL>WFt80Hy6kB;IT)nYJ_x$A4X;3+vvC01Z( +OQ`=1O54vtsb06h}K0IjvR;+nl+FU%?RiCNK%d_8CxCr0&8+W1h)V*%N|h7VH{-n^}bgj4ips>Q$?MfVyL5jh5^ufFl_rV$1UGl{YggA&~yh3Dr1)XdWhu-2)Y@ZjzyH$`btvjy~~KvYfCWnciMifL;tRRyc6B+JV(laV~owc@d+bVK>K+-HNZ997IIDU +>u>L(rBR}H+taisW=02LYFB3DbE+mi2u;7Tc2BAH|saVAOc0qbV}U@|<|kvazaV@3ho9j~O&sAfn +>eKO%XdzdSXQHY0yvt-aJ*z7tAkRvwBT?>rUGlXPRCgz9&?@H5&6#gInNUbL*N1amfgn`^g5A(sEVpa +<5*`djDAjp#yUqH~CO+#4w>L*P2Jfw_IB3@4WY}0$9H5n$UPeVm^SeAE>6-u62oKoE=My%jB+UbtkFS +JMRQOfH6#pHji-FWFp1d1nTzZ_m)UmsHHr>&vP1Hl`FAsHBx675bvto!O^o|{1rX;8#Wr>-;dshBb!i +uy?nT}TYxl<~ME6BppbFAx|g<=)H(hXW-G5wKtR;B|5Y@=ym2aKPFqVIrn`D*sR#g&Qd&M=j6IEIuB#mqiw#yN#0gy3 +P@b*hpgh5zHkVa~`MaL&`=(Yp;tlc$p*eNtSXsnSOGk0z7*Sw`eKi&;iw=Ww<+peu5U!PfuMp_>&CJ4 +toPQU~7Nw4LYV$^^_1>ljIq#LmHeFwO-OZWn28dm{YmCNbZqPINn*!)=~3#vLibOh|aF7+}0gaGx{!v ++{5ShZ|3LvFedJXfD(Ct{F-WXn+AnC0#I+Ujgb1+mXqT+i?$OMpl%0~COnJh}P5>>b?-ZXl`ogU76LA5nZ!APw* +cz^#7hb&UL*wy9q-{IMt(05jrR*HLg>~YF|9DxI$G=RoNbd2>}-%tI*dhE7sHft^M0s&#d~DG2q0-DCpvFK)LlAz=zo&_@aryb~L} ++t3Xo4_puJMe%#eeC_yOCxU=rIGg(f(W=OKR;a^k~*=!6$}$36&>=5zvCt^HhEP^lPM+XHTC78MO}HV +q;)H;IQZ;0_wGwF>H7LhcK-g`o5)06tWF@?v_h?#Et1>(f`?SUP-%h89%^b2R^xxmP@O3TH7BwTT0?F +Mw7ea;SF!jHv5=qI7q+h(F}tnbfZpwg&aKR{OGBNfR8Ord%0|~Ynr8ax=BZ0F4<8@d2I0ZOm4TUywXw +^+^MNJ1Rxa4!?XZms%S#<1wY7FOv-jisp0*#r#>IZu4DWCw!mC(RFX|J+ui2zqMp{vQAXOfF^_`(O +l|pt&FA+;)Z&#jaQL` +pVS2+KK`$+(BbIfO)%S;oaLK*EnVym?IYh7aPp>B{mC3pu5(17cQXOG=` +Wl#t?*JYPYhlj{qRcK0{Il~9E)^=w!2AUWa8du@8LYg9Ll0qr{OjtQ($7)ry7gX<%HLcB85_YMMy_FR +-cEeuzU6qmMiHNyTEF*fX$XbnnVbwXxHL<;6Ho#{t_02U#1+wORM`-}T0hGNsvNt2A7L&=-;Z{CaF{j +)`(hGbIYR(sdHE~vBP#RWzEEee59#4?Rr(WwXeZ8_epme=JX0OV+)rpsa52`PUNgXM7QgU4_DqhVEUi +N(cox|feVT`vZGhrh#nEi5uMte;$9q9~7&LkXV`5-4Ju0ljKK4~(zZsix8(A>tC#6i +)Etw*sm^jS|2+zx|r3CdsCLH6N)b?ZW7NBXubJ3{k#OGZLzu}!%>)hUdvrxprW8`+lzyn3=jsooqsD| +&;ekXaNC2bc1``9Uyk8uTOPQxx-N&{K>rPIHJQ_)phbU;8lLY<=n0R~C)z$q(q5oD;Gso +p*q((2kcp#x3;$BdnaQ?nFgp{^?y4-1dO7=)ZUYp_!Fj0a*tr(aAAjZyh)OCA1Ov{T40Y#z6;9afPei +K@4q*Ea4sa5bG2uoHx#^#r8adcRUvEF0bf(D +zkv$Y#D8>~YUhcw^|OrPx*oUvH^JKy0XKQth}jRLl2Y@X66h5rm?7~OaJsrU6czB`YDOZ*1I!;2OMKK +g1V-V5_<4wAO#E$g_GkPNwaJuXCH9i{%tzwTmPJ$!?jar8Kgpj{GHNIx^vn#8QYjNoWVQDNv!Z_sV9c +CXn2qo$t)n3!ay(Yh3_ON>la?KWqcOnttiqv+(?3};GN?hccRpb<}grRnz+nxf>blXyJ<)ObY}D)+$_ +v1>h-p8+AKyL**aA9}ExU@N@w>{99m+qyqf#QJ4#pn6vSW8TdnTd +@@WJ`5gf8+wXu6C28u+ay9|=y{-B(%*XI7yH2s-3+OPW9@lA75orW`z32q0cz!lmq?gQnwkK%fW#r)% +?*>}?d>E|(}ErJuP@mFy}j=)%Uns>M0cYnDj1RYQR+DBI<|HRcgaIz1Rajq{gHxIWYJ`djDqkezsTHbzlM>FD(aZ$7JojwCt +!#&17mj;G&5NK4cr+#)Hr73Ol)WweYf@{37Hkd)-`Qul%dQ`hUppq;Y;Y&JTe&4NxIMVK^ +I&~;drz`tJBcE~X^^%&%}dQ1%5-6N-(9K)hJ%$T(zuian`yKqm@+>9s7&Pn5SJPT=b$iI(*!N#}J)Rgy-Jp%AKwvS^w*6qJ4#W=JP_n{ut4{EiXvZ(qTm0rgQe)K1<@!-?4*dUxpNy +{A?(2P`X93C|ncFT)a6qU4|c66s!)Nj-6+R;EO>amtm&YPY~Xu!WfqXByf@4y4yC|i$-KciBFL6xB-@ +)S;I8zN%K?12B(z0y($%8Y(%Er8*drtxoLT#TqwHab$ifE0u=9@0Yi2bgU0`v`yt0H(@Fb~!br95?** +>F}%1^o^I{J*Rg^0u{eR$~5i%P_J}bZwEKH$0j_sulPqag+xvFU{fqyJYKI|&w1uRXX;&yWCm_2OL7Eaa(Iin=;%zE +>Ov^30eI8sm1hOlAq(R=&D9F9T4`z^%^PPa4jTV6vRF#JunDJAAZmmmXtRJ-VRh;w?)Wde;ZHEP_lWu +mZsN3NlXgqQd#5Hh|+DfdJ&GSX|T)4a%bc!fH_;%r5`m#&Y%i!=>swu`$lZX;M>d%n?Qvk|PZB0D(i% +@|DD)vSja&xvc7I*wAxr^KvtExMC#|VFKl2=PYJMzyrG<NBCOJ)qO|IJQ?_Y7|}upvBT=t|iXptj*-q +<^}8r)-ir`-z2Hbt|Nk8hIXGGh=Sk8g9a!cpqSUprpI35}6~G2%EHogUjn6Gof+Zy@+OPeP}LsS@jGw +bJ-`A9XMP}?x+9PgMXC)=IR~Q-)E)G2C>O$RJ}~4ad7+*|Bk$$6KY$|W!2OYOOYKH#T@yT$0fPKT@uy +?h|KuMvkEC2C#SjvZj4Dv6(7#xzc9WT)0@Yw<8wWa%mMkiDJ>t)$N903`^|z!_U7`ySv*Tk$2jkGS)) +A+HgaKYC#fm%Jm>2B@b{ze&Vc?%0W(h(VxJ_~*WW(-Ej(h!>vK-?Xz@JBjm-MX7G4J6KAyfkd@)mpAp +GF;9zKWFye9rDY(rA9!TkQtAe8wtWhXWPH0xf*f?O1_=tnTjeMxIWMP!LT3_)J#VZ=dXX8<@OpDD}HX +*$O3{hx03aT%(nE?*kA-9|l~mX-9^4bXud|4lY`Hs%UFGl^->c%uDB-G_3gf_wU+0QS{i@z?5byD0{? +Qbx*t2YbsUE@yg`Q3@-nsE87xjS+BI$JW*x(1b9ArCuDK75sbAD<@I`vH%J5s;Enf;KhGgZr>&qN+>% +oa#W_z7&Pi+8n0Ar$KL8nbFai#JMe*O|!y1F3GzhoQ(y1d3#`J3X74ci*gZwkgO{?wgD%2yQaI? +hlSck3u7?v#}6km_RbYLp54la%<>4zvhe#DT1veAOwU&G%>w^zKxjPOe_!ozw8{JYdQZ1-RCoWUxZkc +HJh!uo(AahIQ<>u2dZ5Z}(tJcD$B$VRYWFsYI@ePBr_r_l6@E@;RD+;`n}LAu0Z(;{>6?+*(d#+=(5z +tsZ{A3EUhC&oJ`FPkp)Fd3|J_UK6QF$nKkSzXjM*K1%vyp&j1(U|mPx%DnJY?{A^8XuKi)5x}j*D~m$ +tq)(k_~v(qUvc^&yrLr*z%Wb;hDuJfR_{Nk{pa62f8pLFe~JL%+uF)i+O{`mhEBw3|F`)ho{55f$ZeYl3q4Ihv|CQ--wqf>&n+FsE-u=k)FU=5)er_wO_(#ZhKb*HW;vpJl@2-GC^b07Xvx*m2EGZJcI3|4VK%o-n2Ux3-yPp1d&(xNKleX@=*DCeb8Gi?*1uwTU`g<-(gZ +DI3nojG_AM%5;_6^#F%P)h>@6aWAK2mt$eR#Or@-hpcY006fF001BW003}la4%nWWo~3|axZdaadl;L +baO9oVPk7yXJvCPaCv1?J#XVM4Bh=Jh~6N(4YYI%5Zv6}+ASyy#bRP2isU2Zr2h9uIc||$xCs1skL08 +1G$hU-lT_6&_~=QXOCABgNV32_G3;D-yg2qHa~fQx9R^<@B914JvFmj)m*?%sYo0QpRx>V41EV0H( +L>GncSP0HF3rR)``YKy;m5fSQN%eVUxW#IiCPb2)gJ@OqD3*<&qbTfqcT^#mFzgnMep&jt!ibgxnV0d +Mjd%iUs^UZ7m{tjSl{d1tUvEyH{)&1ZRO+EM*H4(DOb5AD53Hj|4{T!KV1qdRXB}@eg#a2KL4)%0Z>Z +=1QY-O00;p4c~(;nv^1zVApii_bpQY$0001RX>c!Jc4cm4Z*nhkWpQ<7b98erb7gaLX>V?GE^vA6J!^ +9t$C2OpD<;+l06uUjS-M0)8LFZvI_8~TM^d>&U%%$L3zBkjuez{Y7O*?hGt +=GEujw8v*G-+&x~{XbsQ3OF|LyIaUuCsgm&-QSYEzXr* +;3akRcG8ql@~whMWdQht{h})C+776Oi?Y1VbUjvS+gyzi@MrisN@!l$ +dQ(=7Dr@tp-rA2%w$^*As$8pO+GzZyyf;goHz~hflz9#_!)2Kk@2aJ)^zv1D&$^y~gT&rD0-vX&<~g{^9V$>+{*^@vD>5K0>ytMp=K|ykU|JYUy-M4>nbE_sT4psMV{t{_rTr``8eJ7>K5LuIjSh1!(k8pnh)aizxXrcR&TgP@p +5esoa5@E2)o!Nm-<_VH2?+gpp*I0x8KviC`6ey4r+S%HKzZnWQUK^|dA7LH>vXJ+uG->SFOLh}564wi +0w#}2n19i{(%#dN;SZR5yJ*_Vk?$z2^+{3dqRw!2#7x3V%X9;nuq}T)KYxG7&-VIO&$GroQ8s$kR@qo +x>Sm^ko2)8}nd}LESm>r=)*P$)s%-P+>|(n~>)JLWVAz!SmzW9|at!cqvjSR|%k7@Gth8xsV4tO17=7 +8>d!$xn1>BWyks4W{m+A_34Fb}iH)So~v6Z?=7uUCGwN$)GvWqOwnr%Nb8df_yKRiFdVSp=Fx&pRTi@ +aQ1j{zD22bpqvl`XE|18i*+Juq0hShN-FcYK?wOTc@9LZQ*?WG`Fc$7GYI4bEImr)mI8e)`>DLO5xv? +L;XUZ`)i!_ovCTf1>C3w=r{=(Hzv2q(}ntYX!(@k+G>Qvz$6?fR!)WMiY||)Z?_uGhM0XDlOa)_YFW_ +_5eMa=eVO83A?>Lpo8pp5vW5Eq~<&D1dC($00jyF0}h#bVsFI5fslQrVQp(5F9_!i@X)5p5Wmu&sA~{ +i8L`o%*Z2EG+=0xXN%0re54fMq;b7bFi6%{);jE0q#B+W_?pt^;1D)pm@{x9T +>_b9JE^Apo%io(G0hZH;~AZryXU)IIuuY!V^~2Y3ryHnG&IG;DRdVAIa_kDl-BAC1HPAE}qLVG&SKHo +?-=P$!p(y3tj(+K$X{*lK8D(?eJzY~5m^haG~8&KV(|cXjHpjp;-kMh`;-Biap4|66oLU>sSS4fn4AR0J^Lxsibg{}p-xZxM+XdXcuZ208Ae2hRb8^DhI?rW? +@SdN5HRKYgKn?f61nR62#0XZ}zM6f`LHe4E%!9qnncS{UC7Fde)|Ku8STxFej$$4@)?o%(dPeP@W^#R +G>3rqv~GN5bDnN!S&8^uZv^m_Ki_&si~K^BhRL$2_(o6QPnevnH)@+O^JCsNn03Cj$VnQN*sE6K?!%1+1|gVIs$2n8ZyjfW`a#x#aSTM<*r@wKw3kZ~`5r(J +{)-fhd)Uj3%5@66y9DW`+D*wSrL3~c3=C3bs6o7I8#lMvLVNI_mrlXpsN0Jgr&KgUJO13?H{S577Ul70kMHoYl|g{5E +Pz38BkhjbUKm3b$fY997AHC8zas^ac(1ERf^(*s +d1Zu90QTNe|UCua-tSjDQX9@)C;oG&4iK&$Vp=@&^)_Fct8NyFzUnk56`6&j70Va8Gt;c7z{~B5{WqC +qO6Fl#2{oTtr(t0zXYahNw8y9=cTI4{DygJtZtDX_PH~W!1mAjCj>mezH3PNi2CMZ!jOjp1~nK3okVu +U&fd`U9;-pyte$(lqz6h0CzQmc!Ok0UU_>O9@Q}QiLf_(tP&|Rl28@AfhA40d{b1|=#Ydh%#ywY&*|0 +QPiJ2ef>Pssek9_zgV8HnELz#tJ1J0(~%*@f>Yz`p!)pb*(Yps4W^#It%lRmtjSFOLH&o;1P*bocwlq +O)w1o!by4VYQ^Fz;4HQUaN@$I(~x0>PRy{x~=0=(1(I!dr+*wCgyjW{qN%fve1_;-a-VYG`8( +brmU6gq0xWD&xv64bbD3!)U7B8HMao0Y6pKhAQKfiX2(Rq5p#O*l_5!;%ve%!zqvyWL)8u>Ig&$Qm07 +m>Oh}G4yk?C^y^TXfJ5`NPQsp)y$t5F>C41RQx?EF-K +ik9vblsqx1rlTYC)FhuA0BrdtJAhXuID +K1jyYB7Z35e5Ib?HkCUaztj_3m-CfCK#$HGXx5WZTcpWSBc87$4f#*V|v!@)7EOQWUs%NLKhg9O8mDS +J~P=+W4k>{F&t#0jU2J|aA2Azh$_htd!BKaoy3KT+939CS~`sB?y8$4lZE=-> +b>$E$;;JV0&PRtr5)7{3HqC`-u#n;aZ0%XOL+Ns}+T?MD&dBd|vE4E$}jL}C1=zkmK)a-23AI=~Chdq +QT5DU3FX;G)nCtgwq<%k{*|hUv%t@eVOhOuSm}f))4NyHiCa+^MsNx;;reRr;dMz#8BP%P@u}!JwGnW +c~v7F9DD31s;taZ0gm4F)0qRx`q!2PaZ#i7Nd|b;vG8?PSIwjW2dCicubU5TPrljd?1BqG3&GdBH1@R +vgg55d;0A6kGq?rrLd~-E1I+eQBM1>b#d8T4Zw{xPtLa+jo;6!w5V6Q+Ba%#(3Nl)@fAN5mKqMzT|_T +gO$=aDZif7Mm)FwiibsC180Tr5Key0b!}T?%DGc}sdSE6IF^CDgqwi*py=&ZH;=b9ym8dj(3f>I`QM^ +#wC}E;rny9iKfSg&kD0e_6v|w+S8+2ODVmelquyiX57F-KDTqJK3guqpLqtX4{(h))nie?!~fc|6o&Z +bFbHmiYbd1I@DqzVr1?}9=5)E@BPNojd-Khz$>gX93EVTj5qKjRSv+cNe9#0^azg)z*H7?H<4a>QhI> +=r>3?s=d9I#jDP!^m%=b5KZl>Qk#Z9d3j7g(vc5F-PW_b7XWlnoEQO!+*3BLV!1E-f9s4H68KK`LH~_ +$o1SFG_qGk3JhIwFe3myn}%r-`vQf7@De5^My;p7&^0tc8E5ImW}Sh7vgrPe9Rt|LcEk;tN3zO8 +iGV$N54Mcj#>z$09n7MqOm?5YE2z9CzsJ3+EnVLSVXRDw%S80yHMA&f4j7)Ah#j@2f%==FhNDz40SPo +{Z#RQU6je2drX#3Zw1zRLhyiyJ{&>JDv42Y6F=w*)XCe^x3_X{K?*brWN1&*P +Svj%8l(0O{F8N%DBkzuysAqt8H(-@xWGNi|ma>aLnAI5yKs%-z3vgb0u@pak{}q;vcsM;~wBrb%_#ik +{2t*|xh~pmaoL(4`Bpz6DT_f9-&Jcknc&6l02^USq4@SDlQITT|rWg9~z=;v02#>voN0?+_PxQi-eez +?V3FVuqb#{5xkbNX+SZF06`?nO3N88KH(v5>rSZX+pGS|o#;WK+cT2r+(CA*Q7RDo_L_gtWO+U)Kiow +b5!EYly)$K%~(td(6G%GfNye)8EH&)d*)01LF{&$NXJoe-hJ0^1U1kkFkx7JMgkm?&nIRDe +3&w$m!1D`7S0v(LN_c-=`?+G3W1dqEU%ftgcsTy#~CX^M +?D;Av9YCd7i#C-J3C1S;2zWkl#iD_aFT!L^?e7r)Z81aNn?AbOdXG|{Fg7Qzy^MR+=yhKrK-j(uoTo&Icdv&?u8t1VZ$5=g1wu11BGKrbU}0roMLrus#N(ZcMmmwh`{ZGHc}Y$c_-t4D +~|~=lWV=vRnT4_4K50CngX6YlSDpFb_cZZCNb^qd~^+&|AYtp7SoH&@Y>pKX{OR +NGYCZ!2=yfNPY4;LVWs64uf5Y_8H0aPJ8~*D3yR&oD6>=)inwbL1Da`SS%rQ +h(%%qMt=nbRrd*#oznV3<-p`Q77ZU$IioYw=c!ClEN_2d;Dnt3gA6*+)y7dQDS2K>lZIxm|}w{wFKYP5>cl!X|s-z-+=Xn*;DxM +MOM&;f)_Tp`@K2kRv*86{P@!g9{pX#Ie2su`MHr=s4AQsF-vz2*wd;B*a!6QubqvMfQOyUFaxlj&G5+ +GQhOkB8H3p3Amc}r!uYQUSNurHrt~bW(lveJV8Ot-?*o)`)L*J!@H2-d@RKzc{4T-SsJ!^(r1@-?6537il$l`sBOk#8Z5JM=m4kdcV!H$4|Z+d>KW{1TwawQz=dN!OM1L +Cs_18#N!(be%ky?NoEg8$l2;){aZ}Ffbn1s)s_$4urx6Z$o3Tsh=G)VsVPKVo6KP>jUfNbhX(Q+Gz78 +`48Df>X4GlPlTx&OvECq)h`niZOvU1mnGX+C5 +s1runArY0TVhJc@J-gd88LJEj1P_975SEtg64|D3!>jg+z70r;k_lUnY>E~?Su2s>lFLV`TYMFcm8p$* +B$$!$zx}zKNY@K0^o88!P*3ewpvQ@Go@%$)cOtKJ{-d?b#0#{Nd{Z+e$H^TWmeHf$B8AzC9_Z!TK4qp5Fm?uCCJBZZ|J +DKsB%CfaWeU*3^7T*=<>CG1*+yG&E=Den6EB1A~^%F7Tj(m=ct-dlPIkCIdkqxYan0GjUtHlOini21p +#^8*kM-0htuoG3sSmf;_0wjguXLS4}BMt~%D97WVAO=-lS+-PcXFc%l(7Lx8JW-GM# +}iu{b2=2G5#t-vFz%7Gn+-~)@QfNpn|dRMFVQ%u?Q8lLHb9TSzQ-@&>!0efu&?n;_*&e@X>W;WV)HD0 +=QF7G?YXleS{-`=I|ksFW8Y_px+7hp&T?0UA#Bt^xN#cte6(uxxu!kk&513FHJ+LWy;Erx9H;wIpQ%m +9vc1UQ$Tv}rKN>RdH?su(#vITs)F8}olc1WTlNP~gMnT0!4>q{3WPtD&N#l@7x#ADvDaz8jU#)c&h>f +44u9BnfVo8e2LNj$B4vP4THY^2-$XGA2a$)RGOi6-P2&~(nV8pNK%tW53VM#`LnvVj9z5 +;KKPIp}#Ns@eDdcBTMZVcuWpHscQ6ZfXQhZW1Nu9p9%(dK_VoOnIz?YN{r^-`5>nR^ +T7vqKn%ve7rHqJ+lA>Bm*D>B0(Ug5m=j+)m~6_z)4bIJDVU36K +Coh=Xq_-tEy1SF5L9}HUOB~~La=z0cXSADJm2O}Vk|mdFvTqAiK3Ha4k-peEJ_|8U<{&)I~Mvi5k?bM +4C(jx2r=T{y)j1IJ`|ib5nPW?R0L`(5&u=2o9lS6rU<_18=aRMibVB=BaI4xRRv6TXPmJcTnwi3&_H9 +yl|79&On52E5$wW-^9fGlv}8_6Ha<+BzjB|S(y?fb$r3r={h*ksRqE|-_B4sp~#Olel|I#<2xtPnW*!QfW1a@3LmXdqDyQw>2YO}j{h|u=!x +1I9y3bxD{IUSTu;VOZ4`5n8RjlaoL`17}amdQ=jJCQ01zP88@au>~fF0%u9)`0Z9E6UX?8tfOry_bE~ +A>^3MkdC7(RW;1_l;RQN)?uHeWBnBrc=!JNy!1NCqI2uUJ|D8mjq_5o ++*pzi3ioA%AjiKC27fKKNpQ6kE^?<_t5dV8Qua}D`q0VO!3gdcqP%m$_yIAW!e){kacKY8${11J!?;| ++6UB8m#hv!E>{)6cMi-?W{c^KK3I@e?>KU9yuI(~h8e*FJLllNkmF>r@oCeI=`pzp=1Ak|)19UTJp)C +oP*NynI&^}m^stFkcQ$sp!@aiySStGInWMlJKEXq1SNw@MZ>eg4OA*rr-uPHFUJKemz#W_l4 +b*2$oA0N-w04WPM|XhpXRN4LdMxX=6ogIpZS*9aSck$DU*x)6!3?zqo^v3Z=*i89cI!(1zq85^9%`fK +`w=6&K{D=%q95!y +R35UyII#r_Sj^>X(RB!qOoF9IRSt`q>*_EI1>^l(g+)nBCOf7n!w|IWm<`$* +-e4;Fd(dqGd^f-yHw8QpiUAPyCQ8r+zHX;tiIJq7`e=*KlOo)i>{5{xC==|BjA0xb9=khR;#(-IJ_Q> +FVD0^Z9|_4mwgL1fkAve2t-P*5iTT-8yIS!D+4KuR6_smc#0`}OEg&5OeDu;4cnH|DVyVNo{SVEhA0x +ZxM0OC@^Sc~?(qh**7=ysED{dcX;u$S3-y$+7WK-kA@(_ +SA(szJgYbj6YH3bd1VuPj3}Z+b8PC0eBBH`N?RzAed& +P$ubNvVY(xNN!s2Q*gs?-j;pE5PZ1_7mnj1fB8-IoF4`JI^y13 +@&8rxvH(eZZ@n0;xZn}jHTx4NyJ_tw?-ROOZHP@AtE5{r|2`@qkhv7rJ61@DStQxLXU+Q|HS>=>iErO +X*+!rrhzVwsH{Iv#4{Ky?XIUmbK81$BGYx=n-`^!t$9W0qkp07vhd-}j2N1nau{q~X%HAHJTFZ}f-V> +4TNpj`(qf}$1JyI7X5yLBt{7!U5T4ts)|96tz^@S|L#$v?LS-|fGqdc5)n*)_&OYZ%q_fG7~o^M? +JyG{{hG1vnI!ca$?r|orf}UB{L{bgQuw&AgeD-B@%vmE&z1@GOA=TI5Dc@BmXnEkk~~fx_g@=?H8B@j +7hVTdXIO?JzbnC=X}e`LM`7q!EBun29luhMsJHr-?o%*#ZQ*sHW+{KQLel(Hs;S?1S)6!F2)nPTt1jc +|b54`_0TTR;3$<$Lr;sqgLJ(o(u>cN`E3|j_7W^krO9KQH000080Q-4XQ$5SydW{AE09+6N03-ka0B~ +t=FJE?LZe(wAFLGsZb!BsOb1!prVRUtKUt@1%WpgfYd3{&Ga@#f#z4I$D$#^Iwwqnb&<9H^ywCS|dPT +Og6i!%^JLK0#UU;xmz+UZyH&`ZCrU(&@cNI-H@9c*IvVX@dNaGPTm1Yu5Yq$yJaPg?;kmN +%4Lw*>VY5>6S}oR-a_$SuNK}OUDFjMc2VRwzj4P8*OVe)1n34+F17e`}e;x=Y{W`bcHv}Z>6$K_pP+5 +5)DJU$4pd}bWW`r$)o~|Wde+QZ`G>zV9TJOR+U#q3%e_nm#v!>#oGtbc8$EgE(!*}H*elB)po{uX65H +zI`2mLAeiBrA4;$QKrqIwtd)grl^+p&e){q&lV)Cu&Un-=&aS>{`o_G7w&tg&Me)wGl6K0r;!d)*Z@d +DpDNwL`U9*+(Th@1-v?n-j%sqTo!bx@~t%Get6xIdACWcp7$wU)i^^AQ70g@Do;~`r!bGuxo(Jw!S5t +uHLUhE*HU<91AR=Uo>!jz;ubLe{5$*c$kQDCZP&fC87Jxm)S{*y7cefKRSsVF#Y_ci;267!V(prIgIf +eCD8C_Fnoy=IfLGTJNM%kp@79OY+0?(UVZwoqKWv&NgsG%kb80|XRUIC_(5&e7;rR=1r|+9%B%=k!)_ +Zwzltn8nJNJiaH7Q|9EMxedS&ukarN#z{h7NxCtn4&OJS0O0?>|NNCK;hrihg_rf{)13AZ>c88U<1Vz +(T=afxtb$aiJINJ>r77dQ--P +SSA;qbPZVF&?*^89+V#LOlRiLu~=IL;SpqMFPU$ONco%2q39>AdZy;D#SIJ((HV8I^^Y>M!_2Ft+mEd +ViuH`m8f|lP5wJf>@U-3)onPWEuUct(N`Cwtfv+ORt{Y +V)0$7}3{5M8tZk}Pt8>u&bn>jYBBj-x6Rx9*=EmSl3p2XwgXXm+>VH5^4o{ +MAH!e(z;Qe@EH5P+gMS{z1okmguHrGZ2L#$3*|P)S`VcIq_!KPgpFqqj@WYy>J~bTv>XVzsa;-ltb8O +JW-f-NN>?T)V?WLiuc*R2P%yCfmWs(I_FY^M`%&=uD!_DY?j48D3VDm&*5Z{1+wFmHv@?+89V8614ph +e*xdAYAPY3P=1N(wu@++bSj<*!DT>%l8$VY(N)Ock50y@OWHv%OQvrQtXM6Bg|xt{l}^!&~n=9(|WBc47T&oj5BV#AeLUi_55DdK)#J?6%gh_C1p(>jJk1oV({Rq +eDL95X5k|y*V5k`d;5PaCx;x4_uI~&MKN6J(@lR89wV%8kC<*uhF6K7!2BD2AtG +{H-lybpW#z(zztYL1rWiLGW_6*8s3JJ_;^IfF`A$5;nbQzbVv{H6}Yp8yVQ0vDXAP^l+)?H<*!+v;md +3?9ah0fVUx9~5SZDa=hRXY8oHW=Aeeno#7>a9 +n%Sm=!#qIR^(gM^XHSl_$gJbGW1{{T=+0|XQR000O8`*~JV2#vMhd;|ahy$b*UA^-pYaA|NaUv_0~WN&gWa%FLKWpi|MFLQKqbz^jO +a%FQaaCwzh+iu%N5Pj!Y3~U6X42nhH3ZpJyI4K$+NsGck9vnklkt1p2C70e^S`~`++dH$%t8BTg9^#O +D=62@HEYD$iv4ldGvff@o>o&_D_)Qd@ot;GnA6vA}X4aXuaIHZb{r&tGMQ?=@FoMACgo8%!(ZKJ$5AQ +MB7+p~~rLb^P*A`^eXyf2lQ=-B0tt?yz$_iaILqX`J9O01XC0XS8QppDdz5Yh|Wsx^{Uo{FVtepj+6jbPTx}^WtCFll?2zBU<2j}@bX +r6{?Sl&Sc22AM&_LY*$i%X +I5q(cl^M2!=5fr~s(ySg}h!p{LyO&`p7I64_X-oai{35Bk +mkaeJcB%iG__=W0zLX!ds#vG_5@2fMh-iae6@qx6Vm`MjOF9TD)P+7v&i;7rGF?aEGQDEcZdqT(5FC` +1#&F{WhUB`2%xVd@v+bu(1!UCMbt!Z(vQPR4E3>S=_1nO}f__H?DOu_nMB6lcg2<%Gr +D-_(c?b%kB!k$r;pDTnDTPCp09Wz_60H)R#I4+pmnBAT@Yf_UH$J1!U8q;x!UR +QUsDfTbHHFp^(N%(XUcbX*49goS7CsC1xu1?ZBhtJeNKefyVE5G!!yCEKw)(>q%46R!i`s9&(kCdkAa` +0D5%s)E>F%n(e$Gt?Y%#6_3c0GV-uk3;7)iOWo?qsfuBzfcl?n!@}}?tlL9#Oe-i2RJHwG=v=ypR^lp +Y%C`eaN}3e?9xz&V;cE}4h@|4YvI>;IizuP7>M8q&4CvLe>X|f1!sQvx<4V9mrDIt1sZHl%bd1yGM3T +jxEQ{aKKPB{12Js;EaF$RnuR%ZTc!UFdTq1vb>TPb@jmtU$dx}I4_ey)$(TUup^I$r`QNLIf9!Jn8o# +k$T8MdaUX3 +U1)PgZ^B^q9ccU3Nx`=pRr^0|XQR000O8`*~JVR?BT%7bgG!qIv)T9RL6TaA|NaUv_0~WN&gWa%FLKW +pi|MFLiWjY;!JfdDT66f7`~f|Lariqv`^5DCn@`x{T@6k>y0KW4k(%*UByo5laac2rvMsBYEj(e{=0( +agcKQuHVzIjY(j4c6N4luALojs;!4xYMRGMb}?4VYPR(hpKNRl27`@4|FqO)RR+r{Nz09YHvTp?PA-# +D&GUGfYPBfx>m=5t!lp@er)JA+S|xc_s_HVTR8(lSEOo4IlIk+a)RBPRuy3QZ%(2OAnW|Y{D1BS$B8y +T$E-RPw1p*5Qt&L>9$cqZG*3+{7mQ~OvP0r1eBsZUO+p5s!Q<+b%bY;I)#d2DIyR#iCJzva{)ONdxiV +SFF-~XKO>sgV{0f#izGHrSPwup71Tpdh~i^K7U7q9jRBqr{ +BY#`H@+8q)xL!-y~Taou~SnJia?9io6(ZY`i%*Ioc0j9lky|RukoWL8hx>>W!2a@+WBX^5E5r)7K~A{ +)_ON(>E_)AB68-oO}n(iT#2bgf)^!_OsrdHnd%uN%O#2e8$F4@oHCc*L{F +u~iQ1pi5m`qpZ=_Nxm#qtkYzk06(g8eHp>CBCzAV*xx^RcM=}F*?;@;@Xfa{@?fL}!KXaQm{5fd$^~$ +?Ukpa?%ZKpR-FbdAMf$?JvTU536iYok8}Q>T^LX%KxB-*gNM&WX3{Yz{V-={L8$ohCCAf!K3;M{sDT-(ap#7O2 +k?Myl~NYtJ6}iY#(w5+{3M|%$Vws+7U-JffO1zJX5bg-s_DuD3hGg)v>NBl((>eJW%jbDBU4i_>PcMIbd-QUoGGGPRfIpM)tr}n!4v+~>)=cNGOI_Uk5T#3faO<~;(4E@ +)KhMF{8=mca=$*@3V8Q}<6E<6v&T|ya=}mM8-;{{+4(hg#U6%h=#0~0Z7(%|yVsDS{V=wa%_&>|52GL +=q>?1SE=P>Ffi3y<0^iof+)HP06kvb!}TwQ8qwxt5L0=9u3sKsTImcGyfhLGgDsR(fR;5q(!%lvqC7E8}E4t`X>LSh0Rf(o-HvJ3~zGg52r3D5)7j0UBj-n;-5zdGxF~Wz3!!AMkh|y)?whj^1!HfS +>WP5_0eN+P+(Za#{6JM5>M(QyP9S8OP*hls$c?=+p2=9xiLW3Z#H%Y@_LiM@O(`1(DxE2-2QV!1rE^_ +YH?l;5yVefWcWz8`mY7x^>etrF$3K;AFBrh6tAsEDlFZ@_Au`~t2nki#QTmGSrpn@XUUdu%sRXQMVM` +n2$l~Gj{ehg+2*gSJkOf`Zr@LC+uV>G%Pd}G0?TrR-8O?Bqy(yz)ZFyrCLMRf_?-@rDf=TlaQ;UY()6 +AY7y+Ua1CqcMn`!_dW}1(S#4&S0xGyTi*hUGo4wuIPylSC)J0EFNw`aQLI|tSzgLBo9u(++45&1Xe{6 +;dGmDaGBRIMH}fdn3Yvj`8?(jEc&oaesIeqIwIUCFs`s38^2F$V?%p~ZtT%Zg+Pl&C+S=T{Z$av>g)aC)U=*kx3^al9B_j`r$z_|3dRP|(x@&a +zpsx6@uX$!=5&<{!i4?rL!69SM4ATfD +XE6w{1WN_IkxQLf_Gm#v`xgA`C{6Fsv_*{r@|UIzM#8{UOx-@GHMUsQm1b)Py?UANe9vbFp5286z?NzoaJQ)OozE@_yh$=ssTI*gwAT4zd +XQ}KJ2EwVQ+(u-Vt71H!jA=aBv~8vIK@_vT0XzUqnTf5Caw{DG==~Vz~VL6Nrj(-5fg03Cz0Foz!P;t +`+mIRsH&v?(Zf>$2_`4iZ|M9b=c7uOklrpTq%r})uq2IQZRC(q|~8CyvqsMuU#VwcbXNL)p7xRgaAmp +MCu~B21x+G*yf}GYJ-Z$qpeSBTP(o^kz67H&K4wWi%8Urd;2{`{(vAzj^^ +ikI;S?(kx~u@SJFzaAjj#)Uz|O8Tv9(Z}6XY80w_u-)N1#h-$O1;l|OREeu;$Pv5qLwoFO{9O&BxY<$ +?>1b<~ZKoK3 +{@Cv$zA5p9Ohwv7MCVQt$2TK6$Ku-vi2Yy8t6$d5!_?7w5QT4}|zSG%!3}THV`Ce2`lf;RgkYhXn`UEmrj<1-06uw$*GqL!C{`9Y3ugW +k$i3UP5fsF{Fu;r!7RJeQJP7)s;4O`%y3)tA(Ja@<|Mt;ZdcWaxc8-{0QfL6UH +3k8ZN8RRT5bCs8rDT`CZf|L^ae;LC=&4tbTP`34?%GIky14-14-W&%p2{2Dg9}QQH(3#Ri-;>jb#Rwa +|vj|l1-BeO~mqwPaa4a@xW!r9CI~QPgDlq^2)=;TrAW2<@e8B|3X;Ih)RS@GsXWH0|H!~0u1}`=LDRDm^M!7)An&}29CjIfIm +uGvf8j6yUuvd!iGOk1#Pn#=p1z8)Lf$=LwZGc)=TiBVtNUL<85VPwrr8j(DM^~fO)3*;?B^ziV7g<2w +7K~;52cfOcC42DN#(g0ZVd#P<#NhK3&b>%#}HfoNEf_T3il +OFpR4hCHt3nLYPxdDnt?6aN;ki!5NRFVWxG7${{1t@Uya;_!TSJt3JE8%Cuvvxhi{dNJ#=aFer8zU@> +iAdBEv`A|EGJED#o2K&GA*&r=0sN8Nb_Os#YzK>ox9#!C;~94~IDV*oY{Wov34){y9%RqMD1cbA1ml0>;GkJzX*Bxt)0 +7COmb+Sw#ALmgPUBKo?dFo;8l7(3&U8rU*aLKMY1B +OnXOy=Xw9vE~Nkxtpg&HZ{B167Mu}v`Q1ByVMvbiKZaNBizVN7mhm@wik;N(M%6i9$z#NuTF>O?qvx3@(Ri}D`yexccnc{aa(?v$CL1tv3%8yRTA2}WA->2=5RlNGB&15I~{dX1D%c~_Eyb)M%b +Wl;@X9YS_T> +^QTqSLuf1cNjKF#!@-n%~w{LWD^|M}HDBtGQIu7wLpv}=quWWA*a>Q +wsxjH%D#k4*Yf8_IuVXODP?mYY$4$>Y&hRH*qf +tr84kSIHca0ZBCli$Iy|Pt2-LN}C30k}gijZ6c1b+XM5qpnp`vYCMIFT8=*=sXvM^!p-0146n+sbT(2 +0p&&sUh%`9kwx2x`O;c{{BeU(H_1Sor3|RWSwL7LS-k_D_L~ti2XIVgo1k +$ITyJPFr)VKw71{za~hJDYk|BFI`(pK&l5}@WlX^e!|7Ew}sj^MBXAK0~E?vvMql%)_0$KXlPIt+>_E +nUpKS)NY@#NxB0yc}SpshAA#;8&-q;a1Xau*`lu#O)9p)5j)9VS|$LK75-tBaH5Q01^3IV0uGP9XKpL +vjd|pWhqbx*fFlcWZ@Gipc9%4=Jfs}>Zk4&Y|IYzv1B1IJII87>3K6Ih4<^{Z$KRBe`W(B0|V?*G+@&_xL2t2T~d3s|+&OjN36IW}gL@{celr;Jh*7s6fLF+L6h(O|eobIoU~!k +8GBNd+~^j$KxqUvXaKHzgh|(WN0t=O8$oEre_%oqBnf(ik0d(Gw5~1PulS7Voz|Q8V*8?KIZAyWDjk7 +H8I-72m;`CAWNL0qx4%UCRr;F<=ws=>#J$V0vzloh~4pN9SOwPy$VkLd&5g +%Q{%P!vnc}70nlE`$!G|^Z5#=8PT|d{qcK{j_*Ng2GwmfF!3v7nSN`R+1ouhq*F?XSl4G1Ci59=nSyu +#Q)DrY1qj=Ky#pmur*HNp#Epk$o#;AaVY0lKd11<@Cd+q_6lE-dM>NbOVKKLeLFc +J=hrxPE24e9Q~tn5QzAuMkRee>W767$?}4G%~9ptB3dvZa5gvlFDg+#-9TtqvflR!6A&(N5~V!acFXe +T>_2Vwtdv9r!6GvBi}k|0hz#2)kRGuWXhn?p+;e>F!&+{AdgczAc&uxMRG}PA2;dUAW7pwobQYabok_ +^3YoiCrQs>cqV(n8E}?9DCKDfdN^7xWT14V^V~!QDq2q8=6JGOZZdIbJcbL@~ew5p}dM533&p^Eb4y< +z*ym^l1X2z*Pu*%?pgM=YZQJZM|bXl5vWqdNexwrS^NuYdGwhkF^|>(V +@e6IuPpHOU~lJX56pi&d;c}O0jTz_Ul+$_WX7+j8{mm3SRj`D)E5m&YNv8vLq@LC$}qkd!19=`sTxC= +|LO9*VJB=$nuWMqS2it&cY<8IbvFP%8oQUjtU=Ftb`pG%6Rzekj_8(94^zU9-V_29SHKiF@gG_jliV4 +P7)VKZtk??-FuXLIEd1ffJOZ|x4QfHp1V}RJ?&e(JiwXa026go;y=b +aY$5ED!qwZxTSa5wC=CcsQ0~pwL2;>BRFd1~RecByBhl8)tXeEOiSZCu_A49b!IlB2h4cyP#1An1_)} +H~%S&1{iDgZ+Y*ml_K%JeO_t1Mly$x_o?*p+`jQ~rN$MDRBDoktvYlRX5L%x~`G?!dNpHdO&=-3g>$J9r0A2i`itHw>QBCrhBIqGnOBBp +cSIW)Fu;cqSg;@Ab5s8~J&IT1)uP;fp<<+WeqF!Uu0+Y^&t1T&e%d{?0lmEsz&q%ky;HA&4ILbJH;L% +z7)SOwzTL&ctCQOY;+H+leBAiPkBTUY{H>C!jl9_XuNrq|1JNV5PJ=^6;cJviw|tVCnwE%K65-eqhTm +{~D&vl`EzL5CW}#w)%jC)sjrMpA+j4)e@?*PbFjK7)2+Z2Lt6Y<<>8?N_+NaY&-~a*Bl%rrn90^3nHG@%Y%B}&!l2n4UiH;>qx*OPSDFs$)iAfFi0yV?aNJ0ax597GwLpz9 +#8#Ip?+Yz4MX1(_k?*F6lULnD2XM~SH=*=c9#@#(DU}e>0jdCsmqn@plGY!{VB@?*|%LD*q;F0avNi9 +C*1?jm~(3+t+NU0&oY+o5xk{hxvLhF$kseUObsGR_{T6me*t${`1TRhBLX6A%XogN*!K`$S5+oL}|eK +OoQeRFhhd~o!`!OQUY-~?z7Y{_x|!|LyP5`4sm4# +bxs8DxGKf;-{jlme)78cRvq6gba3e_Z~g|^2r~b{&6b^|2Z%YhzSzc*!jyMsinkewx!EyL_WYB#+Wfv +z&tWhG&+5aXj*kuC=1Yfl003xF95sa8g(UyJ*nYH!6wreQ7XBc+188W{li1{*X6L@9FMNr>v(`&(q)F +kxEv20&6Xgh1t#D6yCagp)WOXbxMah)D$kM+XFKD)THpeAV~@)rr?MfEmqAiSX||lVuci0irNOHJ)5` ++OX!w^Swfkh)dxyr!v*>`}T!y+DkUx;mtJY?$ya%N>P!H5)>+%~+XNu%#@?3XFLkvV7ILXpxZ~UQ&eQ +#kIr#GyG(|==izx92L*(SNSynw>aD9(wUi=@fNN18JTob-5LS9{iuH>~AkjMDIR*=z{kihOy2pXY+ve{c{ND4255E{~kH4CH{im}J +|7Xw{I@^?q?D{G$~5!JxZ9Z>~CB^G(4gDv?&^_z6~)4tN|$d{m4{ALrVDig7L`FNK&52Q&%#RBc9>Mc; +vTaiA@V942A8gb$Cv?H5{3BRosvdugbYMLA4@jQ^;YDVI8Y3@WM%w;SC==fbf&W2(QsN8F!6M0& +*LRTHWPKx-e^s%fR+Hd`E{WXmQX22>l`l1A_ly8^Ey +q>|vfliqHcP`R4VgdXP=W_wF7QX>d^IBX0`)c#C}f+O`;zc=_af}%nI`;xSuZ^o$~n^U1Cj`Yg%&_1V +$lw72T4A1S`Tg6s0vdiu5ZQ%UB#VvT%0XqUC?mez~(X9U7mtO|v|2ykNt1QXR?M(jEKa{_#o;_2K_SB +Z*d2Xp)ml7`VvMP^!p)k|;`Ju-vZDYF$?5D-`<4`u2czf`yrpJ2LYslx$($(NxNhi&8uTff8&vL5@S! +_v0hSMZ23~*WH7Q#PhD%n8?$-MLBY7cfbi;0(w^N{NWT}MTN^#FqV_S#G}2L>a0nel!>uG?MXRLDNUJ +<)>UTq)V@UR+#dZ1tB3GaUF#?V`4#&(DEp{-s)YtCq9}@trQ>{VUzO+D!}V9%i`b5=lwUAl<45N9#It +(rKK4n6BE@pez$8&vf%}$9tUh#AG^-+&Ykwt3pAMkv&AG%VfD^pElbndVw`yc>mmwfyw!t89Yp&XC&G +dZ_pX`q!yvv-7`^cw|0PG;zmQ~#zz{5CiLE_O<)kJSGjI*W?nWU>kW1y8nKsyLA2C)wHhCJTcfqI?nk +WCUW7iw{$DWK@*=6Q6cHU|P? +l~V|CAg_TrL*eNcsaaUunId%w=Qhn+m4OLT(}vsu;mW^f>Jt3xL2hZ-5UZa|#14EIB7CjppZZq^`zom +H`EY7p7Ye1HuZk9!>0nZgjGkHl(tG856X)(1N)HfwgV8vB_5g)AhB$y3t^$B6e&BQ?`oOw-zMk^6@Op +qcAEkie$c;P2wWaD!h@~*;`8Zo_Q*!6CH)b{bdUhNq&t3yjRb6(%OS;;=D>J^CNXJDWBYQkqaMOk7|2 ++8wV;oRyGUG)|6UKhOzmZi(u<8lJ)MklM5`SLDSDLm2phjKDn0-j3rr{S==aig%{6*KGHVJeNmwUjF8 +X=M|sy7K~%Zd8ydAV0-cQYgz~cXgoX`G**1V$Yto9C&@nCXOR{hg=!%r2ZLOGYD@x%cy$((bn~}N)N_ +)}U@2H$zo9!kO3wBk-qeFKqEa;fZ6hFa3yA&@&fKLS?jPIgL{V3!CvbXGuZgmqLfS5=E8URRmhfaNad +-P4RE8B63;&5ZFj!Ap!Z7n0Ov2m9YEnr_vls9(D<~V&@F|0n0?PZu2b|5t45(nhU8%n^`IJ6q9TD%jM +kDTVp+ZppP`Qp8U6GakiRWZC^yaFJ)%!`EtV5|n?a%DJDaPN{)0}=(UsaA{6QA02{?oGG3SMJYohC3b +EV)+MKlTGdlQyk3z=@_}9j3>?Vq-{~qHg0_zS+`E>MU?2Cf+7~aV{OBF^W%=uTT`fMqdQ3mM6Z4eWI| +;!`pgiMET7LB7K1=(*&dw~`Q)?gpGnIQa%LfEFuituT}IV6o@uG^=L|%#(iqahwct~_S1A==XHvA48l +ybr1AWJ*E=ZvQZ_v@~mx*ip32oR+k +h1~TVvq$e@7I_NPN`U2RJ6BjFGh3JcngrKXc3>sI2j~!p +vX499gL(C^}-`#w-bKp?}%xi{h@UNq}SuvR0GiYAta5nBq-4jUbfI%;!w&G|h@%KZZ9k2`hSElVfdrpKI{@Sc#*h=M +e5=%eh1h$*HSzrG8&1bQ#3VmialnmLobRX6)Y=a~=nE}7FFObvvZfvSIOU!G))A6s|;!lN^JA)wNFhe +I(Is%K0uJ@a=VIijBh0AKT5oQPlFO1yo?08bU&sM@iv(To3KxaOYckA~tfIUo4BuEd{9zS~IJbKiqYi +7pwU4KVZYqI0Cur3Nk7RFSv1&dczzayFfYDKjHQ*G5mW?7nG}*>5*DI5vF +8{^Hw@WeFH(MpW4S53fBDJG}%=+f-_aj!4q;uFtF8Th|MB&uW#*aT(15hqzVH!GT@In9Z^yJl}z3}zH +NOc~5bI`Q|DV6cjo~Ia3Xl$5f-cb_QQ>RXf*(>~~LQ{quCB_ +7q-wtkAJ$(JkV`?>ws&MILoBK$QIejaP~ +X|$~H%tr6CP6lvtLFUhJR9HD+i5mgEBE4+t` +Iai^WPi6^d9?`N6e)%470VHr5dKIxtU6Xxm}@!-fD8cC{v@17wyFJ3l_HMn=$dh0QfE!?Q6i;`^B8ur +A@R(F7P>$17|0kWu)A9QLvRm6&J$|m1UsvgDqkri}lNc5` +q4z=6Iw2oXZE}g(j?y4bA$0ki`5Aq~;bSKdm>^cffw+KwK=7HL1ZY|Ru8iYUdu9})mQ3w{m +gvJs*Z-jUvnmfy=A;bN3X9?6e@T^y8k`utRETr>C-S(@YcO!B-@gfEJ@NR6VDtvJE@2F=}$$b|Gu9CexMHf{knGQVB +YDqLOncak#Ng{(UOi3ax9D_Xx9yDmUthr17E_jp5!qmRsw9rlWh-rsTc2QM;HEPYBpy%F<|CvaO1bMn +tDt>3=QtqkBvGT4O~^!>oaTD(sA7Z@o3fv+tdy}@6aWAK2mt$eR#Tgz-!<0&000>R001HY00 +3}la4%nWWo~3|axZdab8l>RWo&6;FJE72ZfSI1UoLQYZIC|?f-n%p_kN0oNfSaGT^#&3xEbPROjF=Yn +v@>iRX)E}5U|*-@4dU+`zfWZRZ4E;RmkuXrCK01=#)y*PTCgiNtgai*qRC`)^lLA?WpfGLk#bR$D%@j3M`r>@0+DqpYLHu;T&VsYdXJ3XCLEX4~@O9K +QH000080Q-4XQy*o!4>Sh=0Pq$703!eZ0B~t=FJE?LZe(wAFLGsbZ)|pDY-wUIaB^>UX=G(`b1ras)m +Y1p+cp&4=PQWW#4~XG0aFA6qfOdbG$@*&Sz44t*{mf|C8=@z_dS=C_>k=)lU)Y`!5W&v?3;w%SqSX+O)9LObQsZr+65Uk=hR{EtS +|Wc#dtDNStl8+JMfSPt_~Dt&8$G;>g6TeAay5#*!P%nYLPst2yEVV%%>QEyV~!*|W45$uz)&)v{E$)f +(CwbC&>dtrBj7LCxNuf!@Urf7v<(c$>ag?qG8^(HLAK#+iNurOf`(_{pj+JZHeKJ$T07c;6Ji$R(#w{N?}uZWBKpSQz`P +NDD;+8qNb%yJNrkyet4=P_&!HlG7wzu8D4`ca+0i5-`BMl><(N_nda3{J_X!95aKA=VtiA510*yNZ_w +Pv*}(GtiIj3wiz4oEz1C0w|Xa?*teFV)zrtX+Jjk!aXzu0jI9`$czU69-8yichK+kWGUL@+y)CCzP`3 ++S}k+IaKf}32-@zNlP_4)6_-M8t3Egr_b4M*GaN!k*Y?q^UyT!vjQcf;vRBC)u|2*nt4|DJ%-ihN1}49 +25zc#>OouN7>qUC=}8{<=2dTMJ00YN44pYscBe;DV>bu<+Q%!1o*j<8?kYVms%p>!QdlJwGRxz2jI(y +_aAv{(0%tm%+^3#>fJP}CqkP}$rMIi8>((Vnau^5v#XDg#bD;O$>lL-E>TaokWeD-;NAZ#juImR%OK< +0{G9xyV9b&-o07nEqX~iee#VkXTTI=YlekudEz$2QgkX`^g=4<|E>_`HX2ycTyoP@DdBzfFF131VrfC +?t-nMOXAr~7TDAW&>UGa_P3Gr@XY)h)WJbO)5VfC4`;(U{)FPs#Xtm7@S=OG=%AK}nnKYw0>;G>y+2) +M2e=!Hd}ktht3cUbXG_3c!sEUD|csJs-)F=uyl#vVv`4KWBO_`)3t-?>HO{$rq?Ff45f)~V1P~NB(2P+hGMh&(OSKa(@ +P2y$cJl4H9OVQTm2F2PEmI0otnJ(X4=7f^Ae37rSc|^IK5R;T0J|$K-euD(@?__)A;wtDu-@I?0ZXv?2w=%(UIB6Q*L=ae5ew$8QQb7HE*lJ>|L|};alKz~sxSN~E;5!7r!^8IfdiwB<1jtM +EK4B(99f4Y&>ar>4kl%v#}wyVE7qQOrm&hcD7XHQN`!i14Rj!Mhi)8TVW){VUCAuqah|!cVLYCLFoct +h)UTGDz105+hQ>f|2H^VhL-g-f&zlZIDRP^l@d@6aWAK2mt$eR#VFXU)|~f002}4001KZ003}la4%nWWo~3|axZdab8l>RWo&6;FL +GsYZ*p{Ha&s^TXk;cW8M1%EPHLjJK2g2gZd8@H#{2TBLQO7!+5p#otw +ptQ6U6A#BWQEWR&Ofp~CTG7-92y#XA$#_SsQjh)12#EKg--GX+9?)AdPRsAVDF~|r4H4wqQfrb}&7sf +8Wx$b%^eVxBYT%cyO7=Ayar$m9+YC>)ZmiAJjHLcgwwF$fDeEgftYr!HH<^ZMwW_#;qHZ!dt! +$<0GuzcViltDh<0>pC@aY47(iNPbU^ZM-_6`;eDPs60E9#U03!eZ0B~t=FJE?LZe(wAFLGsbZ)|pDY-wUIa%FRGY<6XGb1raswLI +I7+sKvg`idI)!IB|!Gn;*CK*4&)lc15!?!YmS%rqJjC6-#1rbsS}Y*}mg-}jtT_Y2kCvlBiTYO?Cosd +K+nS)97IAK1RFYrY>u+sK(vuiB3H<iop>|cHix!9m53Kx`$!f|dE(VDb~Rtj +7(Da*toe%F&3pF={+@SbkH79`OIU1Qmr^pbg&)7{d57~B_Sf=8!Jp4Ruw5xRQ!`)%R@OD+W}IYwDI~A +h?!tznknGe}W6g^hP5;pi0}_fSZ=8ZBkL_4j1aimOv23au#)u|>#Xd9_0=FA?yBf&HI-ENkIqUTf3?K +k9^t=SZvr@7WW;_&TBi^H#C5C$NyUj2rB{WdR@Xe{g$q6tVv@sCzCi=KD=qF)kwdnA8|O +hIz$$~o^BghR0=u3kRO1Tq5@95imqnTH49@QnsUV(An4T`fsdi +xnxZ{FbUc!50^;}g=nt+x!n7~OjxwLe}KVpCg2k+z$MI7CaFEJu9Z^kYOR1Hy@sD-4T1-sOVH5$WBFk +0z-QFvV_R3KwLRyo9EM)(#(~QlHR2uH3TMBzOrZ8U%ZEU>d0V%IwL~uqy`vw?{gK`%5Z<;?Bh;#BLWW +k`A+zfh13IHo(SRJ!hGRJ}?FWq6u}}+#{=PkRfYMIXVh~(rH-VP!v1!i@^8fwNzCXA9qgzYr#Q{4ovG +rJSM(%|qzCs^@3^hx-Y3W+H*%EXCwu)<7hPgQ)!PSV?Nv}x@;qJypBO4H`_l*mZN^K+AwqeBWV2pCZ( +L#f1=BIABthDoB=Ob@uJ*Q;2iN*;B5ln;AZf2V|uv@kOxj!ETFn^EkN(=<7K9?7%)?I2@ED9t&zj5mN ++4&>*AsR3&sI#Q74uY +Ll~UTbjDew$zCn}WAQ8kC-e2*sXfYZbz4P~gMkr?qJ)v29HY+q`3p9%>_9V(R{#sgV2=iA=MJ~%=3fV +gM(OSLShL(w8cBbxYxR^zD>GqbG&%Cu?h{88Eh@i-!6+>1VmbjN&0lLknLU1P+mD=gFW+F_j!m`M!@t9oU +23q&7X8^Fs@@mC*ZT{Qe&Zp`Bw82bd#Oehv%d~HWXb0RbNY^&c2dcPv9AyfgHy)b<#D43G|$OS;{6HU +VXS>$ug3V^U7$aa;-N92ciZsy?ctz@DkrK!0N(_Gk$n{VN8DyV5HuUstChg-?zxo8JDU)K+hC(Sz}C) +XU%kMxnp6(B$}vgfeQZHD9njSbi$3+pCp|CvL_9*dySrD5|2x0{2^lUH6&eyN`Osf(0;{IA-iLbk{`- +47Mtja%@K0YzaHHwyBiRSzc9a#E6P&qcb~fE8kDG>{H(9KJgg?OmU?B{#@&}9pHbV+~(OkEjccaci<# +D4_?i1NH5g{TE8}O|b6u1_OGL(#axY*y6hOVcHBeh1-*%V9G7PGXs;H6Xuk6rRn +bef{?cL42A8Y62rT}~(;#kmMKAXy_+6?W+U=ik?<;r)i)=9C0|UrsyOlC*j9T8RKYXNOs9TzxZSWjZ}9!MaBws?q11 +1JKL*Pn1p{FpukbAT$&BeO_+ouUy8I|Pkt;9wnwVvjIv>6)a;ke>I(>ZZUIEn;VPErH1RUBwn>DPX3g +tpedLj&V`L&;|JR>p5o1BpZ_F$335#C{!^LrCzy-fATh~u3Tq5Fvf4~XQZ>-m8LG51`XV2rlXSl!r#X +v&mjKp?SAFrHGb2^e_<6l|*@e=9gTIr-bc~CD@2q0>Sua>WAp6rN&?IYO}k90f1L>SbLae@@-5 +*u9X$DbVPL+JA_^(p^RDABc4rG);T%Rk=QIi%gee&sD>3sBMfQ?7G~k?GPABDR~&X-4Y)j5F*nRSKcW +LMK6L|vN13v;%-a>;UT0{brN#|6+3($bKp$7g4A?hiOcUwNaV5{y3@to)&f8~97I|ht+Klm{yvSH_JG +2Fp+KhY_e0jruIC4lVRibgOFYkfkO;C$3_rpkqJ*>gXLS-|mW*LFfbwp}AL4E>ZaD#TJW_!`^#}n`cE +WO08-k_RSTvZ{x`lSJNC?N&9c)93V=S|9ql?~&l^q4+@^Mml3potTt_DDG|_f+s;+(CR{K`^?+WcgpP +U=->7vIynfd;_*7wu1(|+J59E0H4=c5PYKG>fp6Z0FP8=6b185sC+vpqy2q>a)M_5*w$AlHgv-3e%fBIZ-1NKCS>3*sv>$a +ZwD2*P`eHX?|2#NV)eaYlXtcI*hz-in9}qye)7sMg|<6Dw|RLVDckQpGRqKKuUXiT8hx-WTvNUiLNS< ++=t1PKgQd;Wd9Mo54f(`vQ20%$G!5kUXT;FF?B%n)$QzzSJIeZE+mYZgL_GpQ&ip +O>3*t{lAkJbqvR*tK!Jf3|fW9m#-~IfSs%I60b^op)gX-3o9Qh{mPU;U5!+(D3*88`zV7CX|OH~mFtO +kS3`2+HB4c84dqf4xOC_%*7U&=>BFN{Ex%Le^GL?6^FoDDX;1CqP7cY5u36Ii}c8{F5tY>=8@CnjuzU +t>HUMGXSLRYYbgbW8S!HT(TdWP3mHQ>!IVQJG5IV8|XGNaFIV;ZyNCEn_Co(Ijg4WVoHDP%8Fp2Ub11 +zJ27)##Bdx$|KtO(7Nla#yc6|&-Wb!sLg2+*FPdmHYhV21px!o#APoYSE{=5l&RL`V+fe>gxVb7W&JN +=B_p&F+J30P=$*>miw?82yc93ro(Wa8-@LOVykAbl8NPhvgN-<#rISs8V%wfQNeJ!19x4FO?WuYkHll +U4#>O|3lNKwMVQeH|!0F=uWg3%dj-~1;GJ@;VGX+H~P(ffaGd~(1n;qmnW^)}o^O^MP@jg5CYWDtO%w +AJ&_8+gn^q3Y^%=5bHv;VxPcH=+)j{>!Mu6+6xwdmhwH?J;N`_I?M=PTAPAV6v#@d-|WOcimjDc4kCz +<;t;r~p>tl<*);Q@yM`<3D-Tn=WDJY2nPOye3Vqig~5x{~B?;Qt0-dbnBYJpZmD5q3uD~!i`e2agRN7 +piyF>W1n(6s9fti_Md!st8xR35{IC(8~MS5Ss{~yPoe!rhy6Cupp~e7uN)KxwJ!>2-T?HA4!f{$V%(T +P)k_W`<2jd?DDZ49C&(|7+)&A~O%3(QVsi|{Gngwd&R;gvsP<33AXU8BD%+tL!8V^vU%srtpH~;NQZ@ +IOV`2n47J+7L>2K?{5m*xt|Er?IzHNt?Pw~No0GmvQ93Rsq_Yao*cVA&#@DT#}xB-rj?U#Sen6IP;e| +I0%?CoD+#gfWIKFMEDq0HBAa|WLEws@OXH-1g6(zKs42dQ|GHz7VZKL;$t%7HAO%qbcw|1_8{v?>`CJ +p_7mpc1nHuxgYCd*Eb?o>o_WoY=!30#BS#I!UFKcvZ*{1-j>p2Ta$(Xz3@>W4)BkaoXIR@HO?$2fXMI +%U@!pLN?Q>CxM4qh~Z`Pz!zEFauA(Ls8I`=zV<*xCT?t8A> +8Ov_9XqQdzK5i-k$*nPL)R3Q8X3@wj#$J5qQ!nK3ZiNYl8)~bypHid +3vRZdUtc5)hwcr>O&zm5xwoH)_wNmu6ZIYP&D`m-V@6@lFZ@dj0tSP{v@6_8@qI$Mj59f7ksW*@+(`9 +JV9E%tY!T6D`7w(64vA60*Pt9puhjmP%TF@&-eQ$qv2O8xE`^8d=E!>D=oiH>$&IhCvi_{WkdVz6wPTT +xOut2!&H4@D|W&BQeeijnlq><>PE1s**uZ`fl{E;N-(?W?~9vyiP63!)9fA4&%lhU@S$r#6c!-a%5)L +`rZ>tOgoRp_PC9;H{21b$(*0jR+%zZ&q3}F-JDs2vQGJh>RG-q3_FOA(329?P%}e3`dSYa$@Z>P2W`W +@W}@D&uAb%&z9V>r8c9D)0+x6)+IWjzG)&Zl%mlfUqh7OyKKv3Sy%*mx+bDT^uShlITkIwrhnZ_n~mj +4TIsdlMSPXno%zR*L?;n+`AxM#)zN!(Y{Ea+jR8ktPu^pvX=5)41M8A|D6h@3*Wdr{cVTb#Gq#Q5yLH +(2hy4heEjtK*Ko{cs1j?=iXV|%6)l*rI+}s-st~?GV`)b0SYiR7&btY*#89i|GJ4JD7)~TD1-ME8flB +p)4^g4|1*j5X`_J)066=a;aDN`NrYFUNqTaqWC3v;gM8;B=e#Pm-`EzmI0R?S6hZV#fom27KHbZ?m|) +m}Vi8X(gE!8G-3yn9h$@O*5~L(bChPbcsoD +Wlwml=Oz5hetR9^7jh`!?~l-yb+1Ia=gy~D|14nm#D1?xn+X#iw&i5NhDkY4-tv;!SC7A4>kp}$oj|2 +Dj&>TV6I$63GNE?f41_6pI^qCZ7!so1_(*J;&OY{FaQ$)SUFpvC7@r$~ESNHkzX4K9#NDsMI6X@uZ^S +a>K^Vr9=JjZ&SSBZ`ncDb;y6P^AEjTU#CGxU?hXsY8F!=Sgq{RdU{8biD6*DRU&*I-dX>=pROR0~;T| +lkc&A?cZ*rM8}=JQvN#R~vpHbt?s8{p`M-B|SzZ=6+e&geSse2|^UUn5Sy^+TfGHG;^FnSmncyXa2`}rI-g +o4L7X>DY@k&PVKG|$GNfAp4zcN9uG5uY{b?))~C;H`IY=A!7?e=H5)9*nl(Il!72q&g;NUacHdWSM3# +*L9kF@q>#6;n~5FR3gPUsTBrBR)6AZ8NbV_Y=&n-E-hTdNoEnN)H+hkmam)IMtO&a +}o2(QePQ&yWw7X$W6n$&Yw01Mdx$l3*P*V`NA{KwU2h!bKP5uF}pphRIOp;DXl|xm8*2pcqVe}@k3v=?A~m}E~$jLN~b;z*0S4$g?0Uu*f$*=)QNW((p4DJ +yBS#D5#}{1kLwyMso|=NIP`K%Q#>vN#%Vi76;3Tpk*=WP>Sb8}6Wb2y>MVAtWE$QJhH0`4M>a{l8Q2D +D^T}uM6F`!)A{AJjA2F`G144R1m#jZ6!y+|BnEBC*JT=27hlta)^O$NXMWy5mRk_XW4=4B^#1bnm{-S +zQ-M!gKAKLd;_u_~SpzIg|h!d);NSIXBJPLB>1ipjK7 +UGY=P^HgBOU`7|N7gIGGE5ZTj}*a-WAf2fi+s^>PT`<_dTs%XZ_Vg&rKc$?5MTMqNw84e<*kqQFeN@d +aJps8*tNTjyxboQZ{-!?>^EeS)8xdEbwl9Ar1ao +SD0NlOmd3cbBjm2Lg2;|QV<@k4xaD(&#!_drwMrp-l5w#7TGZifIAj#v@djYXgF=@`SsHJBb(Cu;mch +bx73~kzB49t5VvAI*37u)P|JxTA-u}j@nzJXy33xy`mb}MwR$)gH=Kb+ +gcyeHhE`mOF)Q)tlAaqWJ*0QoQa4 +IMrx%3cQUAiStN4hz37@NIrS%n|VBT!j-Mx%Ugk%oEC69z`FE7gPp#FC4vv5c4*;KBQ<<$!SCDt?lA% +Wh$IbS;ySXFE`}X?5hPWTKu8`?6tv$-*sZnk?s*9Ih87qcJh&WqzAg$~GoN+3&V;`@P$tLI#MDMh>ef +#?>7z!8t@#L(Uo?Y*6U{f?vX1(y`1JVfcXOKd34ul{nc126x;n;t?p}>KG9)bzDyCR^axnjQQFygPh2 +^Ehn(^Ui?p;{zpMmp#P)h>@6aWAK2mt$eR#N}~0006200000001cf003}la4%nWWo~3|axZdab8l>RW +o&6;FJo_QaA9;WUtei%X>?y-E^v7R08mQ<1QY-O00;p4c~(;!*Dhyc0001-0000m0001RX>c!Jc4cm4 +Z*nhkWpi(Ac4cg7VlQKFZE#_9FJo_PY-M9~X>V?GUtwZnE^v8^k5A0WiH}#XRftydO)MzL%u83&QBVp +_Ei6sVOHNga<>D$Sss)?-d +#=1#0Q3jD`60`jODge?9g&R&&}aY+pus(4=lObD$i=c^Z%$tS?il{_uk4R?Dn-WL@$5@p9~}BH|H3X( +S&A+D51xI^+oQi@|M~X{CTD`Z6-CM8F2Eo2a#?fs1258i(;of}a`0Pr&A04_JWWJ#a0nnDWGu2$B&^Q +h6|0tlovnEc|LBnk`w6JYJY#Rd6BZ!ANDqz1zXuH4yk;vvn&lO%O93ck$>uT@OgzM5T``%lIA5(($+K +9njjWbLMk5IU#m^c=KC3uDbAVsN)7*t)yds8|RkfbJdbQbXLQaf^d9iqvDxmV!hs*PetDB2sV3xpt%u +-R7tPsD{vVdjIwv4ZVmzd9h!<27WUNF8W1dLTV^13NxC9}nZmHE8d@InBVM3z+{XLaQX%5Vnjbpb$BJ +Y%D?8+Lg!VsFoGE^j7)?d8WSczVyyu6|~Jy1aTfVFJVi +WW>X|K>mSAi6RsU%Iii5kAZov7%JBymU9`yy0S&h7lJMFdr@St9JUt4N|v~hC9szOmQ=1}#VdN#SyXs +%00i@*V)HtSt2|Fj^P=3^pVhJuJOT9fg!_z%7b~4uNa@jOb?vBJ)gic2$9FtN>s +}{Q800y#dT4LtYVg*ss@>;VVU8Y?U*d%yvS?VKS6B6+74i)mui%!DVxoH3!qybZD&LC* +f-^+mcf{wFC?Z}}Vzq8>)&OuKQr_0&E@;Pcx*#2U=z#R;Mh9rK6q%AzRElRMwc)bbM^Xf5eV(kMc!>v +_&r265U(LXCJqHG7LY@J`lDDPQ0iDYSIFOzJl384c6(R(0{S0iDKhIwQ#|sSX1@)4}^vr;>MXj?=)98 +x?MJ%4%!kK_g6Z>qUhSV&mrzp+aGU^2b2e5R-Ilv6r#6G+zKutqgQkRn+pno(UdU?tN`Ab%AXGlIfru +6W5E!HWAU=bt1ERpC%VYiJkUD8TE~4`vF3us!{C;}C;IE0+KxXu2!2U4mamG8Yl*SJZJ{1sPi{yOWXQ{{n;(yFePuXjl6KmP2fzKy*W2FZ82k7cTNB+UMfgk{pmSTEfVQ~vLAk|? +0jDDFv^d%5-l@(^W!F=MR3S%D<|4bqj;*X#+Ojb7fKKBw1gPaAA^Dv6y6hs_F@GIjh5k*fVC$oOL6%# +fZKu?J}xvq7Q&tTFPd@Agbgj{egGg%3E6C@yZO7R8PqC4#;AB15z{`Ya0+PEvHPL>wTdxTU>+2}2-+- +T1Tyu%r&Kx|%U^U`+Z(-Em`+%{^)l9%A3i$K2%7XplJB+JM$6B%wsW18VWhP`n8TmzT4YUX}?qTcl(8 +Jg_@8=VeOSxaE(psnsW06vNKi`^F6vjv2r!G+0A^a{Wl&r~X^?2nE?b&MsBUx8?u6x-KoKcMGU#vP0;wRicMIut!uY{qWz^bh9XlB;nK0Pg%5k+mG+E2c&)kkJQG`YiMN?H6k{EHyzX_GJi5QbQKtGovO-eoG`rg>-KQ(CM{aG`S<6T{3_Fx2vxs+pA52hMo>wX +}Yget67P;f?sE7T``kL)do*yB8>$ieeRhT&wfI3}aBC@StmqB4jm+6tn0S0jyDDa8wn9cNMBJocE*fO +Ab0WA3c(^o=zVZq9S9&=@TWx@jn$$BU&mw;tFvzbl4uZbsI1eOrJY?v`jpWM=s~^W{`1vMA&^W>PXh8 +^f72uQ!B2+BT8~af$eD0NEGEj`{i+>^h*nY=FrBd>c6aid8s$%E){IdO}{KI>R*9vqhp +cmqke_Za}}LHuBJsiGqhPqZAl&`!NdqQslLG6b!m@LWV)8CXC)pz4QflQ51R6=X5Tz82nb9ax#V`mSo +E&Xtzh6s$%O@Ya#E^it2S}b-PENh{)6?;2?QVWy8^sJY8rIpZnMV)@xA&p@1^J9nb)7{AKu6FQ$<{4y(h22*5Z1)k=r4kR-& +izu64jCVG3{Q{=Tcz#sPmIZahL0`y?LXNF`K`Z&bO;XY>bFx9{%! +Nf5c#{wyE7fyUyuK*#Mtk0>(>Z_OZ59O+rwJ}vAl&(k|k!l#$+SU`O)}v*^M`RGm2JkbM%6$!GsuLF{&~ZakFAPw=#_0o68qo^@>{xc9xhse{c4I&9 +o^_V!%M##7e;1+o6SN#Bf*l^BMTrv~L9f_S3R1ZO_v(4hD>mBwhdsbe;9n2Q6PP010?*5-oo#I@~WJ9 +RUN)4sYk?xj0TIaz-pZF6*1;TOCbG*nXB8;e)1YV#6V(K?3rJX#aAxDp``Ib~=kF#4W@(zOH@2|TL64 +^q}n=G~lNcP1>Sr-7L|`g#e!bi}q8Zo52l;?uvaAuPoxZSe=FhNIM@$A21q@Q3svYj)NjRE0vlUw!i; +!>0S#rmfDYQ?nobaFpgCLM#NC%f*o1IOm*W=XSTOa@#jM!1Wfj=jhkXp@8?$&)U0*qxQ0U8)*S;6*IL +xfSK%gf?bo8JFt@)x3FuHb`54$<05uVY8*p%#X~l@3!n86{}^;_UtzGm%xCPDnsy%sIl7T&#q2v2!&T +d{?2X3pE-zi7_A*1HRH*6|TpAYyksEfCsO}D(b5fO&YTQ%`gtWRSS&TI#LCHZTM4}4?&QeqY;Rc(2{f +jp6)Ja&>Iq5h-=ddzn)=F?|?g*zDn)EvAUFfuO58~A0eTemtgMGYrxk78DuZQ!|9|n^m +V*$KkCl8M@QFx~hPmue!kdSshT5Wu4*JJ18vNZ($?14}l{n%JQ4~#C&HiGGa(aQPgXhsi|Qpd+e`<}+ +%j@nZFZe8S%4Od$$V~n_ES;@F;q+EsXWL#kh$#@^nqo|aaah6D`reHlShy1zDu#d>W7&cYS+a$O7(aMsSO!WfHgvgv^ISbh*gjaWfK$RM$ay0{n@mla+=5?8F5vE+yeH`T?!K +`i~D&74yRzr0u=5>mNxK~)V%RtI2uJ2a9imfWqQQA}zyA4B{0jb-uHhgQaQ2!kG*TfM-7@Avz=@g_Q6 +mq)3^d)q3q&Po1LOGGY-Yj|L?YjK~nPagsz^hYo<$!-YVX#~jYk@^!*b=N80$y17(NRAhbqoN(Y6C(C +5q+V8Z7Se+DH-j^#@S%Tok-LBTX3SAFq*&0@8M^en^F*uu?~w;qt%P7tP4SRt^L>l*VZPFYAyP5AWKq +V)n^NNCvK@PZg?TIto8o&NE;TyR0ONc$)R_j?MC><|U>RW>AQQi{!?Yi +i+OFA~KgNZEQ8CFSJXr9Jud1N($$4k=L9&Bo}^G(L?CG1r0+P6YM${wSM#dt>$sJrtXRH|W*Z3Gz#_> +Fs+;MG3rH^_)D7KK<~A%||md4U$!gT|px7tqAL64N+zy^2#$*-qPMO%r|4CWQ@Fw`hZHt7oQ2PXSs_j +iiGC;;F}{7Z9|rQ{CO@TKFFKjXgKM>OMp%hoiOnk`|DjHLxIbqS%_`iqJlMXWKuU8zwVmSO@N+d2rOa +>26LEL|v$jvU)WW1xx@u$P1rb0=tH1+UEk|G<^_<=!x_D>xh*w}W>QHJ#_aDD{|I;hQr(sIf{g +Mw^ed>IaF7PW^)Act7!96)O(shVHf#XG1{!;<26*u#K(aJ~Jy(VJ^`w8Ql#eKU5d3_?G|+MBeqqW}cn +X_#=cu^wiqnr0Cjz@SCghf-%K?yjR&-3_MdQa3#}%;WH&fOnMk4U&`aq*dimo12LT*m-qYoqAWV`lhp +QlJQFHn}H%}uLFA?xZ@kYLFd>JmEY&M(ElzgF`FfR0fhHH{`#pwkx0vyTjnK!>NN0_0RtYd)1yh$*sMp){{V +gxE89%ffg9fG#o~4V0G2lG*JlE*hMKbgK%ddDx1>nY^?CluPKvRZ&y*fxuumY_*n1?(0egd6;$DEb4= +ltNnZ_cvlLXd;7xA;9-=dWj%2C(!PKXvB%Z-r1t*+wPhrg;ZAi;|f|~-bTE|sDHR~R6O_*)sASbls&b +MUhHd$Ss=q97qwTVWr8?xW)?Nx1w?lM$%gF&FrgBRobwEfa8M036w65G21F*8M5Sqr|&0es(Yy{6S+d +cyi1uT>WX^{HrS;UVeF>(dHud^68W?qw*y}K-d)|V$hlzdZ9VyObm~SD6Q^mSk +5@j5eVZ*OgH+4T`lUFzN;%N%Rb(DMu&rorb!eFGY% +CMArcSxQPGM`8?p)5>Pt@B-D=smkQQpwRh6!&6mTSM!v2?BRlu19)R%!_9E)^o9QD6{jxQ%F_trvIyR +g)EAnY+9G-S6KVVyH-GvyXGV4ekK6(*HRu4TAs`X%i0yZRP`fE|Tc2FI~QnVT;Os+IloeZt44Uk>I0j +k@tv9Of$kypJ?p^t0SPp<<0{r+ZapF{3 +jEPjKRJ}{{?|G3oxvIetq}fmyLf-K4>jNHlj0*5q%`Q6;%`2P^9jn=F1ONc?3;+Ni0001RX>c!Jc4cm4Z*nhmWo}_(X>@rnUtx23ZewY0E^v9pR@-jlHV}Q+R}7pNDNvL& +Eeh0yi#nIZdb@Rs*h#Pm0)duCHn%dVC8;=W(GTc%^~XA-F1CEh?xGLXgH3X7XNHGE9>Jx|SBlT(2F}m +E`5ylH+i(;D;R2OriFdTE@UF;60j`+%D2qK}spkcQw@hVnxh?+ognqt*TegC?GMl3Ej!5M_Pf%!_LLq +1g%p9SgvxNRCNeK4@hD!nG(HZzwp;L)E!H?u&B@0-PQy~o$8p#FMkn#)xUlPm>Z~=*2pS)6?a088HVml4^FEl}h^b{owL?IJ!O|uulC>WT-VL{8Vs7X +--zaa>A0Z2_ekb5~Kn)Q%Eu+E!L&thKpGUTBc^n6q1)I*GBP4VOoW%kx{;z3REg4pwl10VAO$&{%9}? +$;ZQB{PyN<3=jR=+x~DoxE{gHEnMCVuLk46&5&kqpg;TozYK;~At05B!r)7;EPevXZ3%H|>or1b0Z3& +DNavXFlqZyzm=$b>Fq6-y1f>Hxss-0}BQ?RBl9!AxxM9Yv>?9R=9tD)jKw5~zNSWz+L9d#jS>@#0OO; +#z(<_m13AI*+n0uut=Xn%AqnJt+85AtxW>xF;jlo53er1C2s?Y0RFpalp)T#;mn)a2f{C|n+$U*+BqK +kgXvTQ`vL(h9q#3-D;5zwm&5{*|m-ZSNQ8d)cViKHs`Dh+FIDHwP$`MYP?c#m~0>I9zWB^e_k^lg>v{ +Qb>h1YHm4n_>!a%mp`b>}$-@u-u9+Y(;*2Of$*MQ&rW@;BO4C8z-K}#wDZe3QJNS|E6fzM=C;YjwW_T!VKYm +`ZrV{4?WX)#m-^{xZGF6*`G#muT}r8|PVKGRPJe6#KiQ@+|Z6~#P4m4gO&|Xsl#zW66)XnREFzQ6|}wEkDTl-mbvO8GZ +dcLqEjmVahbv{SL(02KrG9z-H>`!*Rny#Ppk_UKb)N(2-Tz#X*4m`#y<6~h-O67RQkc5h?&Mi@Y_Dib +o}zK?Fh4dC3D?nPr)`FQ?t+M#)+w+#{yqW%*s0ZyGY9_u1^(3*+#K16^d3UK|Js_x&^ajsM>|&Z>QB} +ItOUnT8v-iazkI+H!1@AK||j^{|dY@6aWAK2mt$eR#R0xYXyA+0 +05W=0015U003}la4%nWWo~3|axZjcZee3-ba^jdb#!TLb1rasomF9P+cpsW?q6|GFu(>J*>Q_~@Rki) +;w|c0dx+g-MNufUbduOmBui3p)S~}=M_F#|*&B-KgJm6`-o1M~p3dM(>BjJ)bTAoTd=D>b!BN6BlkZj +CJ3CrdwyYW4FeB9FpUcnA>E}7zvIcIH$k6o82=bDrC@p4COA3K25hBe}x^*i<_!EugO2Q-@L*D~}ZYd +c2Kn#tlp(0YL9Ml$xf?LSBK)|OIwF538Rh1T;$rNG3UD>ATNJ64`(^06kV}xde*YuR{mf#2i#^$?J3qBQuVjn_{ixwjBA@7EIXKtQgxf~>}r=RBplWKpvMp-_)#B$WdiO~tL>NK5iEViCO +Jj=45+R8-jOQ9@-L*v9Vux<(UKuSYLaG_2T@}#6ZN +pgV7?k0LKX8p1XKl~;r_oFng4@zlpkczEEw;Qqu%^aunRpIe8o0cK@s{3S23>7vuU#3mF%*Z;i({yDr +=gQ$sLW17pB+QZKJ+>Kc=-iSe<%N`*y7RZDTCoVxEK$*9dPO!{NIrUpE}IvPp60npM#FIK$oOUh&4+e +63Hs;r|W6gbm0{(_}+ONhT(7*jAZlfFli73zoGLZq$7g77NR--P&2ZI&JTEE>TVB?bDCOhpl!c3f;K9 +vQ$pR}8`Opb4DRp!0rzwirjygvDwNJr*S>w%Mx1HBI=fq@p`}q~L>wlG7v-}j$zZz~+kq +LmZkEi9QRXYh0pY)r?h}3kslPO{UmD$y@g46`W9&RR(K<)sXyTRhtunQ2=g)S+SY2}Y1l;*4jLg{W=c +038Dv3WIkdUCZsEW2lP+5m54m{vAwT$bJhlg+cKO^>P4#FUOuej{*G7Wlv15ir?1QY-O00;p4c~(=X; +TZ1n0ssKm1pojY0001RX>c!Jc4cm4Z*nhmWo}_(X>@rnVP6*LK{H^q)`+_)?u&Bwf3ERw~m9N{CoF|X&`xs$^*!|v$HdEhYw(&i?xyIis1C*-3RzqW)c;g3-wE +v-odN3wyRRWNSI7lFWYCw{PiAYVhtmm#b~?(hFr=BmBkoJ#U-E>Lcb6Z|1_Nr{6u4=R&W}eK+6IArsc +OkGQ(PzkTa>$1f@l$kQNe|A>c!V1%Xr$>Ac9KP!U2UX;rUjNCKv~X(;rP1fvDx3w}y=aUiG`ydo-k-E +LJ?fvANB+N9lV1G3%A@nATa506=F4Zl(uS_=mMl+v)x88xW~M?vZv{D?gBU}p}_CYVLbQYOMgC|cbMaPyckWd7I594&H9tYcsPgY3@KAxO+068XJgAavq`dOrO5n}A}nj`KBNc2XKEijUajM$e-OOYa^`W}s9J5Zp>rF92sSzgR6nam{ +#sw=Nd1)j$XwIQH&!SkM%7@b2Qiy*v56>Ad^DiKLqLjw#;l9}bXq?rj8H$R5qU^E=Cr>{K^_|srBou) +ObtG&rQLo(}u$g(->MbGnMOh6=rCgfYUVxpFDkkGn*5B&WBzYWKqVWJgoH2A*Xcf$}d0{GwYBuyW_L4hZ +(n-2>(mbJ-nPK&t-H%s=h!H%Pd1TC+7(|=L4!NRdKUT#D#-s6>vtVuD1qpN_zP!B(FOJU*ujhp;=v4Xt7uTFLJoS2Og~vhB_*qWY~>30o(%RYW? +}CYDNMds3SGLRIEVH0>uK$w@ou?vExp*k{x2O~O208r6jC{RL1<0|XQR000O8`*~JV(0%2py#fFLU +}m!JriE&rFTXyfuQ#w}Ei9DCP;g(~70^Gpq#g6}90?!fAK{M^4}mOaAB`Ikc*V +l1!ztP#Yu*ZXo9Z0q;|+EN}^_Dl5U6Oc5GxcilFFNk|sggN52!Mrua-ith5KI$*W~-dS5s#^a`GLe_z +XN*80%i_G{Un$4H-{Fq=n`K1(S3~2mOb4@X~EwGAMQnn>R!5UB+SfP>Dta7Z4=9bGM0Hbo-FpcDrabq +>#)^=}Dk7ShhsenesB>2-9qS%MCX&l7?d0a)C#p-ecSJU-+y4*zb7*=bTt(ND}CR#1&?gLDh*YG)7o( +F(5CfWw?EA99RlDiRN=H?Y6?g8Yg2Qn3=Jm)Fpl10r5ghJh+mJ|mn)Fn6WK^nr#$VKpyTV}h;i%_BG( +S}M}ux&;Co|kJ?LdB~vxkH&L9a@wu<+g=EnADaFLw)(s6K<}*%#+z-KBG(@Cmzr>XoV_@&ehfa>byp) ++ZhugCSLSBFT)(RNU<@YW49xxBkeSaae@i>`yKuWj^D#lN$eqf-#_cRAtedWqa#t#QhsJ3Q)pmt4N1A +b9f2P?c+|AZkRrV33Vz^XB?9kIOz0%pZr5QHj>R3ho15osw?RjC`wwVjZ`>(0szm51x@X3$#j>(Jt#f +g}j)mL*nv1)7+tB6wT^s6sv|z8C`{bnE3)2G=-QmyvB!mUp#1*$J{hmlx;!~I~m!pTn0Q_fm-QNd&T& +_nWs}iL%E}7VK7-A=~F1M&h4`6xO&&}a*n#Uj3Q9l@JueBUzO4w1SuH1)O +?7+=t@98)9g64I3g=+CgoRD1?PVNCdpkuyP<2WO`Fc!BvLP2)7Q~^dyWZZhH#7a-7hd@D;rk`#9h(2w +po^4bRV;reQn?W!jYIvIE)ktLb<$TTISKY=GY=iPnO_AGI)KbJYT`NK3|7q)_q(v;kfkq?prQYh#2Hs +!YJ@!Zq9=vE;%!BUZILDAc1e^1Bb9QgH?GXaUnj4DUvZ=^`PHFVp4a_01B#jOO#vY&o4Q;Cc?@>)FM0 +IlZ3I+j|(zZsF5(c98&5nP?lluZ-7EB84v@=DuDF!~+3^?gZHiGf{|)e5tbLB|@q1&?vG46`GB({va* +M%ZWvbjd0wBm7h$pD54Fuap2mD@}sCQdIOcHSbB#l*M>P%JQJ>k9+=inNK1J|pTm~7e^1i!2!zxaoh#&BtrD-O)wP{Mf6yIQ)ZihgDZEtE%;jZEbf$n^ +$Zs}wEH}xSM+b>XtDVT^+NfakIWaU6YUz%YJijNv$JXP)7%080A~dN02=@R0B~t=FJE?LZ +e(wAFLY&YVPk1@c`t5Za4v9pZB)^2+AtJ-=PRzl3rsY0-Lwa$PNI|zwkk}7uBxU9ndBz8Gj?V>P4fx+ +uKn1qos>aGgm7XXpL@>nO}a2qMXh|`g;pYR>Mq*6m_RdkQ<($G?+puX$tHXVO+3;hIVBD-$)Es5-!4X=s7^pE(QJf#89gec0?+bh0- +8Va03|^2>6s>VS!6X)UFVm$poR|cI(TuBq34Uw-jn)8KD{B3wp{9alot>ytTF%4Tn`#h0F^LmClEv0h +!^=d@_yV=^2Uj@LMU+7|{5uPg92VhiYE2fcHQf{o~o6;Ty^ +{kBi9s(#TCLtKdFpmSc8prc^Kpc1T)ogLQg1hl@IgVEIX$*@cOcv4gd^KN0^z;$N(LH>bN7n;DiiyhL +Q=y%H63JbJn7X`T#3KQjY6O`A6Q1#ee963IIYO=;QA@G|1?n9)?jQ}xONmAB9k~Q8G0n$b@3H4`&}BQWBe;K@j*cc2J +;1nZ>*&CT1Z;!D>T+U{X5G-ZX=pKhg^!%iV_bQ^&OYB;OIOTrbkk86yp>6n0NLa54C{+aT;z^3xq%X+ +3W#IT=9X8X-KA=F$j{tn|rWA{ZT#pI(=SHm#9l*}bb%hAPIbX`98PUH_33gEn;4{9YH_dd*4BdQAmIH +fXRT2=@7gQ#TX5$kJVIL)dU%Hv0})6DGu-)%ha#qvvy76fR^YzWa0fae}-H(PB02ZT-jQf%Qkto9_aB +Mc;x~z!F@6aWA +K2mt$eR#TW#ExWV@008wF0012T003}la4%nWWo~3|axZjcZee3-ba^jwWpr|RE^vA6Slw>hHWa@1Qye +%Kkpf3)(_t4LGR$?|#a-bHak?T13W1g=o2^W$6jfIk2J9X7hI^77N|Z$TCw0=STY>tbRmj8d{CtP!9O +(dtBFQD2FBF_Udi?$0fBtp)==3qX$YMr0JR|(A$T|mWQt(2gi;TcIk+E2vFTx76BP%ac?DMbjDLd0SU^kYC11&l)= +mPyPA4=AjdS`=ywh=&l@213jfL1}{W3H}w?azIpJ@ItAie{!-~tvpf~>IpeNiA$mMXlx< +=ipMlfLDKgblj!Cw2a=#I0hytNFi#8Bd<|fMS?X4gHu%Z9 +f{xbO>pv<29wUWk4iKgVKZsEMfur#pfBBQ<#enRC06&5-OK0)kLOfUWh0$TQWsdv9jTm*Xf`_Ar+8WP +(5NS%#+F!1Vx$1JbG8xzmELj`Dlpt|J?5Y{vs)skg&w_KTsYD=_$%dz*G(f<&r9y4@n$P(GJ?_bb^=^ +WZ|+uBPn%Ixi@$^bW6Z)w>y|&ph=)WZ$l}s-7n67-cxkWXzHPCr#SSJ#vta{lB$IWi}jF3;QIk;_kAa +&anv)4Q*BHix^3&aE*$>|Ga=&A7X3?5d&dI9xYk%g^M@#nGbMsqK-g{rgln1PP;c27uB}1Hy%q3$rvj +d@DKLTe%Y9BUX`BYPiTcC@7>6`7I5UPOTiD(=1jE0}l{sWB+p!r#eq8JK?Nt_J%hkUXeWA+n +rMU3;A;;v&J2x`E1Q_ckN!V$S(^kUfjLC(sFn!0Pic-rusZ!#IRuC1_3NIA(ll&*Bxb?soK$6s$X4v? +WZoLH#bkwj8O&srdPQ9rf_~DIBlHQNL~$u4%g2Q7GLz!I^u~;VGE*OM6fS~jhID6+bCz-=_c&1TSHLH +$gO}?uI}Q_$M{ux?DaO0+ellG6lN6X07;l`-PkBGzPd*&g`}DnyZ*IEY;n+QoM;)9*4)10xOUU>t}6E +^w@&z0xh%odxaAOdo@X6>gXvK3mdm>`Xw7ImxVF-WXNj&1ULA&*EN3AB%q5mGjx6J3;^3&W2H>;~_ceKAU8DCiqQaft8^nq>>BPoS+ +qVDenAD9g~fWD7UoGhvD|KYfx$U8mal%FgP4fLUo!(oAa*ee*F;p=ACRB-(}K>wAMbDQS%J1whp5Y3J +s@xZAW=tHZKZOOy6bg0}vJJT;p4UpSbXx|M>v6XJx*3IxGQq1v)q@Morg=y{-LhcrK^!jHyqcYp*fxYrDuB5i^>5VDhaJJO +IXV2H1Yvd^;uDWp{V0FjLzRvYYxnI68W_Djv-gXMs8!G7mS!{a{YmW;jb49A5>>x1R|COAR=q!bA +TbMX?e|CKT;KXax~r>KD$Xip5B=$?@YoKMTYiVtpU`a!HX;?rpNv!-?+6pYH>$5KPTJqS;`9$tO9KQH +000080Q-4XQ}tHOb*TdY0Okq+02}}S0B~t=FJE?LZe(wAFLY&YVPk1@c`tKxZ*VSfdDT`;bK5o$z4KS +>&=;#C(~jHeK^ll8J{>Hxunnm0Tuu)>DK?yf0$pA1*i{7vD|d(9;$;xV&A@f`xXRs1|w +CkH7^$yuCHGG`1#MjuCK0N!MjFqlyJ-B@2UyTj!^JUnMK3kj%lHm$KmfU=<5fVu{GQ&k)saI2=akvC= +KS&NLm8BKp566qn}Nr55J){TuHc&uHf7O!c8db{X`6F)j&n0QdwvW5(PIM-TlWod>YPX!|8l7j^Ta=qx{$&sVeYV(zMwWZHTP?JzpyqXyJfh5%jx0)+al78)XK3|$#Ds`R5(Yt#2}XSztLh|W2G2=HG<-Aaw +N=6}9HwMB%PhDsv~XvHO92iHAW1T+oh0W5MbW42LTVD@qzoWD)E4&O +;x(j7iB^jH)Kom^kdeVVF69PEEW1`(g7%(B!#xd^ZM1}Kq)X82(kuOq>~ejIU9OMVyN3JdmT>QQ?3|_ +HQ+WGudb2YnbME$$dlDX*;Q8M4Rxp>`?j(QblFVQ~Td#PkB<{63WaM#hO#Iz1dfYxbf{CZ|1_HPMEb=)!*O+y@(#UY +G^mN8SwgsUfQcI!L!D6aEWZH)E!w|+Ec!K4Tqa2I9}SZPFi$So_al@>?u!k6zuc~x^vX>3fniMyDPXh +wrj3`KmV<*zSaN=q1dy2dfdiFfBj#&XV9H~wCgzi<#TJ=9$454_ySN%0|XQR000O8`*~JV9o`~2tOEc +5VF&;KA^-pYaA|NaUv_0~WN&gWbY*T~V`+4GFLZBmZee6^cV%KOaCwzhOK;ma5WeeI43vu$s7f}MMZN +H19mj2rEu129Qe=%lprw(_txT#URY(86Lp^LcanV)>OC*QiV>l#z22)wqisu^xSC@bM3A2K0tl>WE3U}qx&~H~mV~WIVZnAF6|h_(jTvdR?i4p%S7dP|e{B}XDcQ9Ez!oTyyO#nz~A{u-ymDNuoxlJ +LaHeV6qk$_CLf^>-q&v-(gtVrlR_AVq$w>#S+GQth&SpraZSFa(%~fl7i7%E?c}l +Ki)7|e#@I;264;w5%lM_{el5?-ycLNfCdbYfNuDe&pZB`IVtY-R;;e*T3jIoG3J-$c| +ty!jh!bW3R;T~U>Iez>RZ=lO>a64Rm*L-Tf;v41REB9*LY^WeZlLP7}%;LLL42zV}}Lff8hs7$G9sfS +tZZMqu~HuQ)y*tarqA`4*{2EDg?K_$KF8t+k{Vzh*-gYy7}-~9?5Qn>G-JRW$@UnX!cbRxa&Xp?03_q +DjAF~vRYJLC=XB>5VcACRUfyJGVoat4h?|9;&KJz;0MSR1)mI(9rddhy>s|8qP#K4NdmO +bW$ba`jP{ohMsV?2Wd|lC#&`WO}u|{Ok~aeqz^r&0gzlCQRoENPd;6P*%)Xsql)qrC{TNr}(N?2JBzL +Sg92|PDZRp1l?+>+y5&#taZt9Fjd;I(h9JUmd#}*n7B_x;h0n`)p?Ogu2R8P(k=Zl6_W%k-c>C0)NxR +A&=+``$8?N4FYwa2;$%2ntyT&5f+pH5hFKu8!(T7X&M$Ay4ZxQW~f^T{B)lmXuE+Va8 +WX8^#w#V9jZgb!DWJYB69|&)td}0WLFXosrYhZ5XPS4En|dFcnv-m +<&#_4WAj_Tv18U0t)YtIL-cw-;BJczVUgm+#m=E-qgVn1GpZjJPih$qykVnNZBgUN=H)2r$<{AX|u3& +SeU{)S~1I!4~?nFbe9hLYQ1ya-@ZLGjPddE**FN%@;}~oemDv#xXuk>z@W2bzVQYbpghm&fMsn6|zVs +p9R)U;)LR;3x@M*PjdV2pXZaa*XL(Y_U|VhhR>s1u7xw}GoEEP5L$FPoiyXH%!*4lnQ|+ +75HuP8a@G(0y4~)03a9YYFVq7%e%mkisS)?f5-E6ANDL%7t6%nSVbMb2gczID0bbS&_{L2q; +K1u_Vd~>u$={?X$zbv3aJs3$oCs!0DZc8=uV@W}18rHjOTULwH8+Or;XtmKDw}jbia?%%~q9W4_{?$| +-LZLgD@9p@W$CLo31)L;n?XZ8 +@0zo+L8v_7NB=YZOU5rI4}yoE2d60KHV>{dQ@>ghY*E!1M`w8v~(MnR%pySUdfGcBz8ORx}NnBj031eqj)Y!o~XTVt0 +2N}y4%pEPu%Ex!ez`9{ZuO%Hi4mui^v`@L=h&BydU7E5lHy`UXr6$UCI)rf9E^=9z>nsJZ! +<=JtX{|NcjB^439{fr>qyAlF9{rcEEZ&r8?Znp}*zH8lQH!@+>TY4Qdq6JUvyaUC6w{SqClMNm~W>ry +F3$!kq2S?{C#I@7?#J-KWv-zAr!b)Mac5wQaQ}`kIZApQ!D7hF%5@#gdm6Z +OXu-GAzGFw_u~3LN@~(4~35)Rq(kJregX^Q%}OtiinewPNV$KUP&+TX{5lx`v`@Pg7%WMN4zXV?^*u? +d%A)3J3cXs+z)JM$Lq!bCQ0-*127Nf!--FpFrjzTx_43OwVUN}2o$KloU<`uP +C?77_~VPxBGzxMBUiuTCqKPK9n$VDLu5Y_`G#BrZgCYY!)p9(VoG7eZKRBubb$oLIY5_*&B{lghAjO5r0SKHQHa)fxp)%+ +GV{(GE-j7%)=5ffBbYP(nisPo0<5kOv-+Il3#)tDo$Nsf}nEv`-}IBNec1*=im3*TYe?<+0)!28HCe@ +xLc`G`}aD8BR4tAKGB(osirF8@FWTQ&U>l2foEZTM7XG`t};>(Hg +R^^8L75+S(NQEa~mVuDH^Sg$C~BdN?#VVuQIYekY>5e&h8N +Ok$}u*llwjl-UoaOa0;r7{HmP*YR!c#*6U}7;G}EO?Ma`~M{4zuS!f8Z#=?wQ5Y85z>GNp*rpIsP?J;55IAd^=> +=UCxoT*jTU>iG9P*V-c+UUOD#4^UBr-eXCS6J=QpAH8M$#Wv(jP*k!+`C>!0x=cmSH+gOJhUlRow{qX +3gI#F9Zq<6(jFtum%Bnxh^NkovWVn_mD=&P3{iS +%kX;V2(s~k6sIpn{q_aj@!hi*BkK*Q%j$;ej*4JCbv9M5EqqP9LcOU-Nx^o}S5(AQ5JwjXiI6hk8mY1 +*3gJfb?97Kj(@$(?d7F^EkOCzoReiHpo4@b>Ox|0OkMy0384T0B~t=FJE?LZe(wAFLZBhY-ulFUu +kY>bYEXCaCu#hJqyAx6h-&^ipz6QL4qH%L!ku&X*(E)PNCWefz*W8>hITDCzp5Ma1ZwoQHJ2d5~eOSQ +pc!Jc4cm4Z*nhmZ*6R8FJEwBa&u*JE^v9xJZp2?IFjG}D=_p2NfT +M)S9W()=dE%aWmh$}ld_e4oUP(g6l8Nuks6YU?eqG-Uv~op0T9$9ne3ga5=$h|?{0KAKt}ULniVWBvM +9ORnPusm70(Nvvq;>y2o?*t^C?T8o=0#)4S|d0nD!2X&*-0@2L9P!2WP(wFaOe|OQ*@R_;MD+aWILw` +1L*t3-Rqkmi`Yxxhi49YM_^TJ-!cJej6huSn-{)blfL5_dK5 +-wiLj<@fieqrqr+F;Ex&I{1hBe*W?7^xU|6`T64A;7a}ccs3H>=RpzP`$g(cgMt^)oLiUUGz?Y*x*euOh#G-^{L2}32M~9sb`?o +dh;P8(fz`M&Xc~sojI-0k}2o~6AUXS{M$f}$lslU0sk`ir$Sl{5HX+^i(&?A4dtFaX!@t&D5YXF$f%N&g@Z{8uea`3WF?*f;$c#Tr9zD%dxFnkb)e0cqqoApB +aS17&+wanZ_SF9u0flA@4=uupmjKm*zX4(7QgwX;6R|%XoWp1KJyRF%XY;egUUF5wR05cUiC38}EEdb +GRBM1w@w+u;gSs?|GO``5?>Eto^qjUW%_B5uH5GJ53nJD-da-9Go7)Siq)fxST^UU{TJ1F!m*gqbY&w +!4!Z$JU0|d?Vb}NzS4w?0b~~-pMc?dD95xhkANi-R*yLklAMO4BO(VRl002zAs0v=? +@_a(Y{~n|VRqR;k6k}R3o$JQ1*248Xu-{xAV|((s2|<~ZEfJPi&wj4w3MU^u!lX#LAH!J2yXBM@-K)$ +v0y?hKxF_nof`P;F~~%45E^2`EhN^y6{d3(l70DsKmYUvJrHyhW%gYNmk4qSmWKL_qZ~3GScus?{lf7 +I#_S}R3t@C>IKd-8c{F{qX(a0cygoMN3}3d!qR*+P^b7ziSan+YapS`Z +UE`)Wd8&3vTN}|D;>+1vsv`Sr#>uy8Z2*^h}BL7JI@FwM>0y;POg(DZYiL~{9_e?v#l5B9jP*Bf-Y +3b?w3b@~IYJ23sGzjrV;oe4bcff8sKfl%s^&?+o6bol4yVSM8yWRl+4pr^M;7i~leIJ-n|r{WDc1Iql +2$52yz;XVwMY@!YNC9i0%vGxmj>thA+l|oR!w-kb86*c4$XegYp*6cul$#}SgjP?un?K@H~eNgM`b^= +rcm+aT+T}SJUBy`*hrcedJ_f6J47o#uu<@I;wR5u07z%?~f{iD$K*(6QlO7&HhbGpBOw22{LUIh_~BV +_yz#LPvyMZQ57(O{kKBWM19w;iOYWUY}fF#`&rTFsa&C(&I)x3fkY +Q_Pfx;Zjb?m!LuNfs4O+qsHov1xHA=%v7pGX`3W$RCbT2gcU7C7U9$+D9y)Q&~f?hTRj+SHNS%u4;DO +GQJ|dL28^yQa$)c4%8%98AcSlxr`jeL;Sl?5pjk15Seh!9OD*(~qSB-(NiFZ{2K^0zZB>Y}q3SG9G8R +F)1vzvQ@-|NCQfr~(##e&fJE*}WT5V9stOJy&AwbBY?dXBlaSMck0cDXBejfp8#XVdD(Rdx())0K9UC +|pMwbEt`FtA7JCj{AkdDTm0J5e!t0H$tUUdOU~YL?%!i3(*~pMAn|knZ%=?jN--NHuU*ATcUh(FZJxS&}RPXl?mP*9ZbNce(!i2&#EtIG3cjIM_GP~y3wse +V(-w$jzd&QP>e;*I$2>trbaYEKh}o#|A>ly^&{Flh_aZ3zA$#>&GIN*&qCZZC1#32aMU_9a +B=T|ktYT2WM90$>S$D%#lEZmV&fjxo>{S2<1-*eVVs2;}esc}pa_JP0EQu|MIxlrSaBnk`9o3@50XJe +C`*Q2ffy-gupb+wD8Ozo_@P%EpOh_T{JS-e4!OTW&T;ZJs!HG`ogPotWuWXNA*AA>}?SL_Reg{6|;hF +h%Ct9)OYlgu{BueS{2RKvde@HN8Q7TeahyS8Lj7E=A)`Y$BYga3uj(d$SfXsk_w|-7HfIW!zJ}dhf>D +*^F7WwL=Re%8DA=)CDkmlGypB{uqmawBOGk#qQ4?ia4J&N3Q}?{8i|Eb;jF?+4x*PJMSV_O|jQV~oV6?wrFk-j=M4nOULH76irEi +X@K|A&~q(TebV-GjzFsqiW*g$mnq^xP5xn=t;qBJenXz_1vxrdtN{>M%CYpoG`rUiF4Psu=~Mt30%;G +9HU4M@^Q(I1Tp01NA4jxF0D5&0UQFN$Z8uz`ild%a%G$g7^DvdCTB+n+28mDjNa7(1{MKVw3jLv)YfQ +hFavBGkP&)MYFCJGzrAH~+u)CnO1vJ8^BdM0wWXxHKSb82Q>7XR9rvcA{!tD02-+r4$~_Eo|pZWItY0 +Sg&P3YB9~Z$6l+#BaS3&XqpgcoL%K+~k^Fp%L9fB{tmM*0fpMOx>zW7))}%nt;l5>;8=fwlO} +Kz_tk$&ID@HxM8Aw^(^J>vNNc-f+)dLTJnTl*|`Vl75BKG(kiqal*b4z;xK_qY@Wz%HjEQ4cXwq-Dzg-l-Qq+xM2x&N+l!RnbI`riw**akE9%c#2|Jh$44t70bx=+YWW>a^mYfOlCN(?i(<&`eMYZH +>KK;kXLza=K~ZdR)ikS}=w}GFsI{m^-I;db(LsYr&-*#*1?}vb?{2HHMYu&%(zRxR)yP2i+bF)HQH2^nR>ERgg#vuD*#@wlqFPoQ{EkK#jvXsbRLRW +V*zWdz-0-xHT3L*5}I5Klv0)U)bzUz%UcUr3b}0Vm80BMZsUPD%fU-LYit5#{U7UdfHRo~lZ|{dD`b; +s!^VIKs@8!P$B=+x1-h?j*BdXbdgDb%Q6ANVxSClC)26q)R>O2)-p94Zp9FanI-qQJiU~Jlq9E4#T(~ +D8f)nc4Gga@#y0H-$MsKiF=b{TaQ(WNbLse$zl+eQJOj=i^sTx(dq`Jr=zksZv-Qamxu3tj +FSOPSx9$z$ru>!S!fURh)(foleo2SNQC51rglmrXQbJaHETwo +Fs*r7L89$qbu-7$S?zC_P;TxivE)!Ge8<&UpaW?W+ReGf$x6VrSNT=3upXXsvKGx+5c|U3kygExEwvL +(;2m#cG#HF!qS9X`n;H8jIJzfC-!+r5?Zy7!Af{&4jzlry%A4sA8wD#}=?U&JhBzQ@(r`@SCD0ReHgM +;0eX&UwzIi%R^H>O9q+NZ}2r1HFUPuG7%JRQxxbv_QUsUMIp7{|q3?jSK^M4+VIxM8{l$j`IqHJ>A2> +kzHaiQspNta_qU77shMIyJ7hG+Os|pMw`e>huW8?XoGOzvoYcPeXUyX@c^33n+hbv^8{ES3YZbqc9tV +Ref#1Lo7ULtKl6~+~Ia149tHXC3MhRs7gP&^Yz+Z6=8hk;9HdVk4608H< +w4E<5;xvVU>5$zLWBK5y!SnByRmC5)WCF<}E~DOJdo&!GnFvw+V8e1mM=WAOR-604z7t192RvQ=vawF +U4&h5&9AS-Q9v>58ipp~WlfJch<}%XJK+_E5TY6hoalb?xT5`WK3RSivmnbulTgXb==F?^2f9x$#w^J +6cn17%PZb;h}d(Tx!IW#}SqQAh#+lPSio66=z@UwZ**PwOXJF<5Ep4D_$Dlo~d+YZ`=u`wsWL+_@J=!C$>*{(4w2lEyT*w2ZNBA^IQLVZ% +b!mmdvNAp) +2o+-a$=&bpsSZTNzeE_gRCiJrk^q{AeubunDQ5I@%mnEdwV(H$8?e65v{SEl{0ofJ-)krqVu?;YdSrH +1kXh#%weGH|!Ivj^&=gahVPeZb +>x^%L|o=DRvvmUU=!v+Xx`Jk@)KU9AmaEidJh0Eb=q@;rM@trA{m<=;R=>LMZyi%p35cs5#1 +goLB|W(_nN5I_Q@s(Dt713JlkfFX15!VFPq=7G +-!~`W{b_*L#CUsw=3EqaBSXnc+@G5*Qm9c4Dtl(bhn8G~Bav>1s^H&{Bbh+K{keLy&zU=T);o5p9vIoOcv3D*mx*{=WS8v=SEYwMG+bg +d6d(2@pz(5^h#JY5;(A(C8@fk$Gx3O?f!MZ=nMbH!@c-9t3FSrnef5kt0NJlGoOv&x;aAoU3Y?+KS0H +=sC|hZ2N#hlIM#J^S1Bti2&;AP^>6Ih#$J@7uKfk3Xq1tc%cyxSR8*p%Na18%CJO2w%O9KQH000080Q +-4XQ-a(vfX)K|0C@@k02lxO0B~t=FJE?LZe(wAFLZBhY-ulFa%C=Xd97A$Z=6OD{?4yhZN5MZ2<$eFk +*+7_N*b%N9l4h(2}K+|_G~JSMayFKuI_*DzVqfNiq!jX>chPuvOew|L)_6|xEBfJ^Uf?( +I^08D-0X?7V~yw|aARGq(ygJT$o5)%qFl=f>~3LEe14eiPq~!GcI;apI**W)VCkAq(Ba#B3c1zza~;6 +x|~t;3u4rWu(#^VV44oRb^R)@~v93qR^prg5p%{`b{V3?}qhRO{EKw$@|;Y<$GSEaWccQ3Ea<@>v#@R +Ioo2te}4Ga@$5jP{S-3QX*!lw$=Tyf&@O_LYcFJW=fm`%5t21XwKNQHh_=5tPc~=6`9`B +DZp5VG-L6GFr@Y%7%d!fz1A9Is8O50%U|VP+0LAz~2Y5fpsZ071=c`oC9E5O%>qo@KCkLAWksIk6Pz* +NO($|X(z+|G{_;OV5^0GxO_&u*W6cfU5Y}n}oQtk@OUe=g`EhYrDtlkCRA_tFoRTd&L;fC26_c~DVlr +`nQ1IkI(QsNrT9@q3?tSa)uSn+_nsa08b6peK)+D<`Rg4?m~*i~I77&R(csjMg`dno;UbM;~xgTwk*5 +a!<#+%>K3SUP1_rpc>#U??`Owr0X{g=lC{vf@CxVAar3fU*TQY~Ugj4MDUcwP4;(JjL~p*&CCv)<=H9vzuHfcAm +aU=8PE~V(yzPypNLFycY(O&WIKMbVcPzdBWYnbEXj&=Gt1gGa}RyU;uzrAAq&wnx_GRcoezV6Y=d%!o +QVAUH~#@pO9KQH000080Q-4XQz1!h98m-S0Luyh03QGV0B~t=FJE?LZe(wAFLiQkY-wUMFJE72ZfSI1 +UoLQYl~zk{<2De!>sJi42x2c>U3v=;py~FJ0Gq(rc+n$4OCy^NMQZtoSN-)JlAPa>iv +@7VE+7wSxQ2W(`to4L1WEgxiDL8|2MTmbVDCRtjq;NU_&i3bk87DXiw0UFP&IN`0ap +!l+F((VpVsIO7;C-r1{nj<1smX7tEG3y5?vG@;29k>*m5r&NWI&UH`o)FBygvJziQDy`J|7`R(sWyaN +tIfuC0m@do<2umZwlM@+f@rQr;)LA}Lf^gx_oIL++zx#a_|aP>WH4Wd>uT##FCRuB;bo{OKrJlVlZn& +*#j^oCQ4QjU0hP(PPbXY^v4`vW{vcdTtzRwdoq}_^2P;cU!9iByAS%tNnxhFDmA(XGE_I?q?T;XvM9wuRuq@r7V|yw_Q830mjj^G*x`py +iH3kP*6S#hb1&?IaNAefD=l|$KrO+))vXi1lN+cORK5PQ-*iGWidmG$H8QF^h=u=*Hv|LS+=eI?(1{_ +(lHp~%noii`iD-&aJGRd2USx4nIEX?)<^u{!0Q6nthN@V$IWd6Hjl@)U7Gd=Hi=ABLxahF9gzPgI+Kg +S>!j49qx{g)LbFuuUW>@_ZyWLD5VfQtkd5?@i?X3XDy|Mlyi%bVn!Hct4yN7=^>Vc?q=^@|(=OnCPko +*gZ??ta=VZjX?VEBI=$^)eVnoCdc(c_|`Ijqpa%$OCt`B>Ip8F;6H) +BeqP%Mwss@KNG;iIKq<;ffG_-J*q%;IZ=q;n}_RvQgKL%x@sJ^B^p)XuPgN!)cT5xoP5jhvOS(ivFRqO +Pe^Ic7o}!raXl0qTJFAQ8fl1T@~+b?Tv85Uny_`=JLP?3p)+EZDBj)b6ge28*6C?OH4T~%49$_oNi@x +2D=*5zug#*%iz{;|E77qlkSPiJ~>Z0XXiRZ8lG@m^zXPI!#nf;7XC#P542q=PFzdw!jhZfXmqIiYxq6 +1MR!1YZ{vxxE3g_42rq~|wc>~6c{9FuJYUx7>ETgWOQKQZvP3jJp-%tL{{`-u=d^UsFT|qe4tLrNw=< +}4sz#6>@85(NS1`SnyuYR&Z*%i^@)0{li;mLC!7Ph^0Z>Z=1QY-O00;p4c~(;$KNE5S4FCW;DgXc@00 +01RX>c!Jc4cm4Z*nhna%^mAVlyvaV{dG1Wn*+{Z*FrgaCxm-ZExE+68@fFK{zNZcWt5V91B~?E^|>$=4|rg=g%MP& +LrgI8p^DGsk02LiuLbMCz|0aXX-H~QRseWpUGrm{(q>7olKzzcV1j5z7Z_NUW-iL3mDDuVf#Co+_5b_ +&=-o;CDVmra&xog2POXyH#dybB+^U}!(va7#rOP(Pl9EFE4`?kn2Q>6+68NIEb_F^EVglQQSyp!nfto +)@6Y@oxAm6g^>z*UiVd@znaIQz{}trJS0ru7DV3@$lt8}bNyqMoov0wD+zQ5Xa@3Yd#l#M#fS4{>JcG +>Jl{Ys&$H@4123ufx0vC%kX6zDgC_bGT_Yy9=71|NBJ~3}v-(6-3ehz}POA48LN#TsMeEDJ?sJHy3$c +2{^L>;djHF&#s;d7q>X#9_7Jx*PCEbTYG>Eww*4p1R11&av6a?lic?~(tGKdnljP_FBb)tlo!Z>`{S1T@yG9qaL+8)sr2(Gb~SOjzkzPhrp1~ +tH73oc+KGsa3niQr$yVOpX?DkVf_&U6(_?3<3TvtgGgZZSv(4GHxRrTMyj^^BI)yQ?0kb^n=`v%l!@L +R2O|O+P%VYLKERkFhinU(8<(U$*&NSG2n0y=|9dboY$qQ{ga53~iP>6z+LB6FTJCSdvg6o8CHcw6}^q +9m$N#TznPB2Gw&EfK*&60Z5|(d~6dDsX;$cu*c*bR&!He3Um#G6qS0@gYl-$sdFL{5wmO2p +d+-^c+zt5WJwUe)z@fvjs{U4Sy@OzQKrkGiCE77Vj#YAtS^O$^sPgE10Pan$&hhT2T2A7JDK9K75u9_ +4(#Y6NajwAVKO})7K9o8OiVQDw(CP>8ypp4uG0X@L5e#=?kV%el>LepQqkE+k);c(ddX#_V!(1#Ey`s +l0^8P^mIymb%yPa3N^I35uO`J7+o8NAT-lD8E2-rU!4@K%`#rC2eQDBEQn>PLC<(<+X+k%rw*+Pq9oJ +>bb%(*;xbJ~#da#gOs&r9NbtS_wYdPf>zU_2j5Z#bM0V-@Kv>{pOaVB}52;*DB?c>`^_T8&*U&O4-z_ +Nj`6z&@+q%Fgi9fb=b1NI|X1y3xm{;tLvPU>G3sdIDV^=fFYj!^nwQhs0ITV;(&J9FKr!^D@GXQMkGuq9_%QIt+w +^y4u0oJQ?_W17%I3LE&(;x8vA1gq)n)K^mqe6)oH`pd1pn-a#Eem5 +P`efr^Z!|~cYZA6%}mi=(mrQ`mS<8D_v{_T7)g8v-{1NZ0b`Ey$UeBfV~m<2=A%}u?W#VxNMUJuZ{H- +&g~DEWo_dx@w+J2oy|aqrkyn^~blMQz;8edTh*d_@*PUOJ;o +&P#=h*`^F52mYHc<_juRZgmA@HRJS6HW;3>2+Eu}ZCs>$`10G|bbhXOGgEn@;k`b(vtV5hiXU;}S9^w +!Gd}YrKkNOU+`c-E`hr(c#5MIU2lYq2EtRuwDaT?LCFrr~~6`YQBI*kq!|I@&5&fe!}h7N<676&MVX% +mS2Xxe5U$TV&b~R70XKeAB?dH&X3natbN +v=JKGs)o$V&~+l5&+_P+79O3#Qe?x@uu!?3iNY`L7y!L8pAj27H}bVF|?=Y>L220Mk+%{^Hv)Bl6t1K +D2(I?-OgwzFxW4Z82f4H-*5?&rnP7egUU+o2pL&p!~?ECVQ^A)G|5B8Vf&(nnZ<#|4tvZ*;}t<>i)d< +C!sqj;K+NbVX)E&`ojcMZ;B=97WbI*l^YZ6sb4b!1_}C-&#yo=_>KsgFb6IC~8+Jcq)v-F}Sae$snrB +nhL(x1I-hmDUA;z?XSLtY3ALu^L{G{I9Z2;{?Sna{?i`c=KLnql0Wn6QjW?((yvJi@nn_BR0yya%m4V +@zLjOO&CCl|)WW{jcNNfb)xZrUNt9=%1GZ;Qgg%WGvZ +$>;zMuJGUl-deeM+Sb@N3hq+?YWOfTSM;-5uDUFisHGA2^EanXBGxQlbxiYMr)n-iddm76%+hW#YFUh +;jF~MZG4ExpgUM2eXi~rcdKAXgJAINpoN)+Pr?o$JThzO0Wr^OQ6Q#oS21EnF#IypBOq~l^eL_5X-{n +kpa#tl(hQ@r{(DnWP>)Z4n<^|74{ktv0}4W9x``JWoFbCp_`nKzh(yPHjeVo0wioN!#TZ-|9=zX4nHO` +T*#F2aF$7Cwx7HXMw~hN|18EAhRpkel|bSlGK73JM!{=5m;RlaVJ`Z3dha7Lkb|#gibdJ&(}_e?G+zg+3ToZ9?|7vb-wos!;TV@nI +D_54HBr^t3irrSu*#>yrpX@T*IZohvCp#8tPZ^ccw=eMC)csPR9f#eDdbQJ9`yzAS_A +_F~{$2TaTC6Y-A$MwRX%t(;-Uf1a-R@6aWAK2mt$eR#RKC9dbVa002J#0018V003}la4%nWWo~3|axZmqY;0 +*_GcR9uWpZ@6aWAK2mt$eR#Uz!Uy%zJ003}K001EX003}la4%nWWo~3|axZmqY;0*_G +cRLrZf<2`bZKvHE^vA6JpFguHj=;VuR!I?nQCR`^LA71`Q56v()iW2y|&W4y=<1INJwH$kt{)4(RRCk +`_2pi5&$VD>FaxUKU8@yra)jY7|b^YT9)~S1;Mhe>XHWmOEyJbRxDhIJgqAp$nS%JYLCN;SILI!?`gh +TCD}@U&4qp{n=T@c?s%oYZNoBy0b;PkiRC*zDKE>sWT9X;)I7tlef43g}6ABJ5iInUZ+kO3Yz +zHelozWO-8?$LumB|8jp1zN0uB$YxmU+235(STvWfD!;MUHd&GzS0$&=+~e<(yF(3SrIc;g^O6qX~7x +PXRp#a#7RmZ$OJnK{~zVWm~HuC$ypf3z&os3CxTTu{N*eQH(bC*aNrS^0Oi7rEx4isk0pl +f&S^q8Et(b=0F4?Pbe$TiQ2ee#f(`q~LmKPD^)b=DH@v=A +n{zvf}g%hM#PG`}+s7FOkD5{2m)lmkjj%#w`VKO1Ra_q-G+A_`ET8-hUf;2NK1GSA#zr3EA-(~Aqfb# +_a(-_(mAp>dj4NR_uzC8<|CQSl9eYMMtKaR-6igjKW-*14!~n>0QrysS__LM1$RR(iVt%G4?_dF{En0J+-ZA@mh?;X +aVK1MI89fX5^5VtwUj~B%_IAxPl7Ji}g493CNL`>ck|J{-rZq=R9Ri13jz3(Du>C2k ++UFXRw?Cykb9|D|#6js|Jd5(n|vz%C5N$zksY|KH-K*lBnWO9p?_NBe@Z3wpvtN;TGbf3gPbkIG(Hf4zX +D5|oKK$r>S0;B?rdMu7`1!0vefRNRWrQwd3K(=bjVe|476aoN=S;n{UB=wK*rTUqI^20g9l-=>&HZNn +`MPQZ+c#VZX_Zsnr4T0I0$*M+$sSTCD&8B^6aF?oa8lk%27OW(T(mLi!?H#e(TUZR}soUELyWw(w<2< +T2K#-6Owm~c{yHw<~D6ZK3CN8Sy~Ln4G1eS>zk0-Z0=Ui@DszNMI;qK`wQm2BwWNQ|*W`La +H+$dAdWZA_w~HY)MvCjo|SZJ7!9^$9x&qX(j2M=Agl2MjGe#)?T5ndIl~&Y`adPwWhIPaU#M6N|l=7B +3z~X5GF`hUsm?&iA8Y%W+>=jUte+V~4%Ev5O +vRz(-V!le^n{A*w7t~?7%QL%WRtAc6&byWp3CaWix7Ej)7{UR^C +q12JXF~J|TtPXx;$&UmoAxqEsO%I&ew_gI902kZ>fNQ-Nnvf@KPD+yEKE1oj8pX&)q~gHkQCh9(1KP` +kqKBZLjr~nw&|^C2IxN+Y!FmDYpzi&g=uipw^TAcvC`blqXQuBB{kvTQX&2oQs=2#yj?Cv@z(=DBTP&%;cQhHlpYAfXU5Q60_Cf;rYO}f?Oc)a?{>4o1Sf0 +nTo&v=QE1VosoWPr`dv%^Z8AOdDirl%q8*8p7BX}UZ5sSZ;qM{$lmBgJ21@>!~NEyz~x6ntEM#2X-lX)ga1@KL4Csl-<1iH6L-2 +bZmiL9%uJ|HRPsfNnrc?ksT5?0>ICN-!H}==OnFB*AsK0aQ-&p4 +;boqt`Id<~srrd$QwAhGdJMJg3+R#WT&w*a^NQWkgC6Lnf!q(>GO`AAv%Q7hv;GHzCG+Qt#I7pRM#yj2%760%O~(8y1N%s-M@5eV&TP;H)CsZNw1KN +6#12gD<_Xk)#q|7iS4E1T(bJ?~pwH}|$@ZEW}|q)fJ@14B#{7@IhOH4Xu1z7I8}&2WSvh2S3780!R84 +Uke-7!TODc@8lOn-&F|WuWgdT+u2e&OsBP!39w8hAAM#A@023&!SRNI&o-rwcRg5t0;K^j%uqQWS-B! +tl@dZ${lIyENx`(!U3CKo?cGatKd1lN7Y>(6qzn5r5VTSvR8D;Wq^X#ai)kN#C1iuAc)<-4tmgG5skxjwqR?yV1jP#}uPW}q~t +->3PJ|gaJAoF=!ZL!Fj4lUF$`31U?S$~KrRQwEKjh6`Y_A$}y-GfH0$`Kj!dv<(mruoSox~Ibr7dU=; +mh>QMt#VFLH$WWEGl7S>fPg1E`{2vK_|b?^g^4@o44=&dIu}Z(5$58^@e*}`wo|afN8+$|w}*#gb~yB +Z1h3WGgoOvlg88X1e<$d8%j@7c8jZ%i{D2$yz^*Uycz28%Skq3>H#-NS-k?n4OPe10rR6m7RB%`2PVe +(1W}7@tmOFWz$D6^Jb)z#cuyejfceH07_2fr+vGXjE9b&rRD1->O8tU!zoL#?bHynBG4*MvGqu!Oh8@ +@rPQ-X>vR{-`b(3uUKF`yYuwOfM^@42TAq`RBdb+-ZkYqvq;)Y--$TGtsw(7$dwP4S$jpocP0nfF}hd +82%@0UfQTYJkgN3Zo}sMPN9ejBFKBjAv0E%LHmHg&&=B&L|_?i$!N+A-XhqNwx-ed?(1%Ro=T#-_rNS +rrae(5Yo9v&|rDYhO+5MHx1j-G^agY1vEqQUH0}Q|G3q*7FSvO)rZq@U*ou){91RVi4XY@n-+|`P4nKs=F_ZmmfVdg7IHVE2?~Ya$nR +<2K;s;%pB&+$pp$5tX1(Xi1tp`(rM(7;Nzdmgw_l9+~d^sF-2BbVgc_vNp!GmvdhSBKVv&s8TZ}5{To +pVeGgk>4-JOx2dH0sWwM|}muhJP#nmUS(#(S_#!H85n@0G&M5*~)oOmlc;;A0TR!qMJNM4IHN15VYJn +#9T1E$k8a`Mh?1g7sDqwHBE1yeBT&H-?j~MPQ1Uze6u`6v(hepJvm!r!vyYQW!_5H#g@5Pvuz +_J5X|AMrY5HVmDdVS=1g7HzgWIX~ga4pQs6aq}Ehp=p!C#45><^uPum^vbQ;`LbKf{=P;YQr6lItDiu +MJ1^5S*e^R=qUe?i*wX1Djfszi*H(b0l=}i;pJwPDkPIDv?>$GEc+&iota9+v7E2mLsM%1}r@a_5KH` +DXrboOfc=6oK^&%U2szL^_9+8R6;JSFFw3Lob!h8px`G%=F!4<5eAd<>FgtZ|or#tqg+E~+`9X(UHe4 +iAIWqursQA?hss+=ZdZso?0Zj+nM|k>!O=rJ~){J(*Y{CAdA~yg^o0OrhKT$j_tEkY|kc@^WJPJ1EeG=d(I)2{;5c +Cr*vZaYKUkI$#!aG#T-Bq;DW$4eSP~E~rk$pXZzv|7J?XoV9uWm3qhFZ|Li^^mex`TDQ@`w +>7ZMBSw6=@Qbio`}{)zUomFz^!rgS1Z9rRZx6%4F8e@96K;O}Xa@BF3+&8ea#d{-*$K&yC0Z@*tqu-a +gW7lnwliR|`o~h%x?hin(ZjLm^ja8|C +P9-5;U$I2hgW21|+nov0vRF5RkKzsh3>vqtQ{nNizoqGk-XvCx-We0=CRWb6$ehqB~cmNmlyAv|J;mZ +yy3&rg0C`O=c=N{R(+@s4#PG6I)EfRAntmd2QJF0a>!?x>NEmpj7BGnPB*9y#_@##>NK7w#>@Jvvk(i +MKInn5)b&P$$irm?t9;lEGwgfbXSdZC*CzR+i@}HIA`IeYUmBHvPaz9hlvET;2Y%`ko`sHpDvasiOB9 +(5j(_@U2(awbiy^-G6Xv_~_1B+%Bdo(d!;KQ<~d8FL%g!SUk)wZ<;bYwZWKM0cC<=_A0sO#t{}UOkUo +hO9uQ6{P8x);(RNZr_{et_2SR{7hhTVGn?L2$7ot^>#a`Kx|aD9I-!wud-bqRr_VYRbSSJZU4U=`X_| +B+;b`QD9CN5Qq&voQ-7{sWoRou29c(*Bi(xYf_*fK)SZg_`zh` +VKbdSXb@*|@PI$#%5ve0Mqf}*BWtz(bmsHS}LZp +)tZOD?30s^Sl!_TU*%OIi#`e1V@<}k8XZ>?D@sE2*fo+vM3(F?#ICBao=)aF?{|WqSt_R9uydFS +!Bq}W+J*iuL@|FVC1VcQKCQ(wQvSc`09lgZ7#cZCJaZlf5O~_x;5`61f +$x3eHPPq%4vwEI|o@M{ +jIhc7s&qz1qG?r)=l#8lz~$3$pms7y5tTx6o24%Rol25tH?ptZYYQgDm~#oy +U9_t&?2!=6|-u4z@1BQ4B`Qh34ob_{id4N1{sZ>u!98LX0xcz+WyYzQKlS3bz#7|xC!J3hVF)jA5jSS +>2Jl)(73}%`V;(b)MzxgC-!-4|%uMfo0toky+mq;<*?T(;Vv|?E$(_{vq$0*4yu4OYXA$&VGl} +A=nmN23 +j){6(k1fc%tRRHVPdZiA`GLAAUNrpdmQ5j$;&qnSAUc`*F(#GZnD*c88UH4T#rO;*<`~cN{e#}Y#1b|2-Jg2}8*6B=d{EFGjr%wUG4@$9gM>;Koe*;iU0|XQR000O8`*~JVo&cQ-MkoLP(~ +(^baA|NaUv_0~WN&gWb#iQMX<{=kV{dM5Wn*+{Z*FjJZ)`4bdF?%GZyU*x-}NizC>SDr)X1_o*^O}Az +_GQvLj2HKa*SZJAV-`gIj7+aGY?Trko)adkA8PkmXl4eID{}Pa;B%cySlnwT~)Ja>UA=mF8Z!-#B`bz +>rLHsNp{oLW#5S@|2{a7*G1D*wfa%k%Vkk5)z7w`--=HE+O_KIHft(q*B&geGj5g`fOV5(ZE{7I&+%u +hU019{-FK$tHD5U3#_7DSDlzYhx>8RJ4-)wNE^Ecr)f<94<||EoE2_(4Bdm+B`}KPFO2gobKU`m5#;% +>;&&9^Qbmh1EgJ_CHA@b-=9N+Y2H*M!Du@>Wlk(`83fLnJGugba=`DI;~){xKFn{MY_`$1&6XfCs+1$ +^7r>$ZEf%BrQvt*aMRE9S85AH<(~(RQDBnfTtDdY<7({*aZuji|TT{Rh!CJL}Rru{9I4u3t8FSI?dC! +KHiq(GM_QxscXXcfH*E3RRnIew!_YMpRqedv{W}l&blL +e>)}ReKSz^S&%G%Y^?zH~pcdZ5TG34xMJcA6x-GB^x2-$DtZe~X(-qO*bi)MbJ^(~xa0Wl@yJ~mK`)Y +4nE&8TZcRx1eIGKuSUgu)kWzACTYdNjzW}TJAUj!{KG7tC4p637}-5w`66ETHb2M6~J?w@3mXu3-I(! +l{RLbxv3VG2*bfv=(&PNzsu)9L8oVES4tvcBw~U48REux3NJcyO5D29{UagLw&fOw4A&n?t@iI-|`n7 +z{L9OSt(0`Da;Lv;h32F57X^_VX3YnI1T!#%HtCWop>NEXk_eZE)qaNDl}|ngaehamHgo#^f|Z6xJJ2 +4M#~{3&eG2@SVUgH=>z~sypVjOL*;)j~^~t1#r76V9Z6E^sQL*upo*d1 +D6)+2as)Y$h`{J&x?YU=hXs-tel^`rcX-keyTPl)v3&r +Mz@w!J$s)C`_%nA0j(K@ZV)6EKyfOcgMV3S+#>%bug+TD-$D~{ejVwW@OxhHwU$IY1?1}+O)V+MQ-ig +mNsKvwAoDwgINh`l19O7V8apnyv;zJ7i +xZsyNU-V`?{tZ+g5!}9sBo*ztaO1J7Gh9_%`%WXAd#@ab&rZ97zYkdNf_M29%SVOdNo8H#k7CIgsgR^r2h(U +`Cb&lFWxYfy?gB36Nb3ZMv&k67SD5WV_IWkv7B1OZCISjs$5w_3ut>WN83Fr`S{Y^-6S&?vZ5ny19JyOwRuyq*096)ve@&=X?u(jhGs2spe9B&Rx +Y)&kGcXTIQ?9t#IT+l!aCSY)KJl9PR!>MW-5l?kClbC~n$A(6nDC^AbrR7bNPb=seAT1gpT#*Ejv%9P +)Q7Dah@lkp)KpdHd)q|`o0iwmUfi`$njVMJ%%c2BUru4kF0DVv_7VroXz9r(o2AlU~*6dv=AZ-ETL-w +h)&TYo*6QH{*Mfj1}ai$0=B&%WY)TR-4$bVt0);VhQu8<)cr?un^U%)4eYF_p*lPwICt+u(A2^!y8c8R&`(IihzlPa~3a}-lqX(UqTnOK3TKlsqlbg +!-ofD?Q%1?FBsIH{`B-IQpognYmF$+$rc7yujL9aBXtf7!22HeP`e6xv&4fZ;>LT?<~?JXW;4zI_u4l +Dl&Y*!ndjKdaR(sXF>KYF2HI&6nnI3NZH7D( +H*3D~;2lRb(@iSRX5o%g8lal`1k(_fxbjrli8CXGacviC^UHh!oETmNzHNywn!{j>@s3>09Ti0)rwr0I1 +uR#!@V`?}`&|BD9!WX$B$gk226LrTavU~PW>G`|sq4AY&`i=~ckv*8}l|U=CK@r*lM`eu^$Pq_ER;sw +^$>o)7`ep+rZJWB&uKM-V7P6D%S=c~Jr{J#KMsP!ID4Wy_V%=FCCg&9y&+oEjDUML}T-FT;k2OsIp8% +>EBd^ZxYOq+)6%4x6qREzP5c?!bQxD`PvVCy(+*b8gnMT0#N%c*#9CZ>UbmhrK&CY@`uHjZc--u0_vE +JwPQ5I;=1`8zTo`Sw7I~WeQhpFsgFiw8_$yz%9(H{qdrom5YU$`{Ar@16g?F`NDI!m6kXHNisAuhZQBQ0D(r!SKi4d`s} +%`*$GwK@n@q12Jd?&!&_wx=VKs=7D#o)V-sFI76kRPlRv*esAO+2P)Q`qZWlYf9>y^nzgni!RWKuIXZ +|$K^Rp?Unu>HcjT3(4Pv@p(XJI&NAGG(ru#66U$d{f%0y@A;WHO<4|V&^RMg_aJblm=g&?bSU9%gzLhK%o +|O~*`8e}saE^TiPRd8_1dIOas}lTYiCB_8d3-~2%c5)vI8;%!J^{&hQNZK+U-3ci%<5}Nsw=VvNK`Z>YIv_DlXB753>oOj$B@W +?pQ$J`xOYGvK+(zM|fJqW=cl9(IROid0Pp%SQr-$8p$mPDYT8vBjW@{4oV2%3J7`$A}%jldE53{veSk +p6+Pq};w4W*F`Rt>mWb$xo6Gjr5Ri`#tmB1IK$U^lUk(WfGy`IzzJXM-zN@Ergayz}N67?Kl5o`zE%y +a$6S<2)P}j82W8HV`2HHj7*e*|bWDDvYVn`|r+LM(Gfg^y{fO;RAyb)k958UeH!`;!U6Bj_CJqnuJ+d +0O^QG)T|krwavK@I4OHgES<&gL7Jz01>+n+$0MUD)Pu!)Kk?+{L@)DQ8cic8|{cra^+_171L#Jdf6#* ++=~u80{2RZYsvCbr6N$2Q!M~x7Z +Xz&3|5=FszQ47N`tQlDpgGQ!iP196cCOffD5zJoROfzftXt|oCuIzMor$`udDEl`363`~YU4F?txtD! +LoN#x?JA7W}2Ted*)h>-6BrX$;H%*zO@BlVeaD7`hGG<%(nb%2LGa>Ks@$pkdh6w8%XZ!&$308?Irl{ +`)|eT<;uyjtZlf9SANk7@&U6l@Q+q~oJHgoS5P0&L_paSYE2unblYKC%I+pSTLFJMAAtxuJOg&zDCWO +w1@89z2n;=Y|@aI$vrN6)F-qPt+N>QR_>f?4j#{dAxT=LiB;@Z2{ZV-w7Yl;FX7Xeak0M-3j@Fj8h-+ +jARIO51Q`0UT?spzA4J0+l@VV`m_e)Y4W6m~YKw$kyxKNGc`Os7b*qTO{Cy170*r*GrS3hYf2E& +=K1I}S3SAydVvKv?fTrPBi;4hdSpY%JYrOAakVMQUf%B;hyLpqG(K$bZnagL#NsJ5UG=~hj>bq+ZRL(aUz9`&^Dk5g8{2V@I9s9I2=DX;g^NdS2XEO-L?hVnKq=Bkx)?}>(v`Z0G~FZ!e@JhaC_iUBkTcUTdSNp`83_VPbZf_g6m8p!(`V1Wer}_GpfNC@qi$Y_5jiySf=^m-N0$PO +2SU+EH+1j;PZ>`ES#XkQ{wsN&ew}{x3t2DX8|&;=C^w{ctdX$hvL*+-%Sa}QPon|USGOXUM-a)=r-*Z +4`3h*{EWpu`MFTu@ZHdVm-5qo=n6U7$wKJ&zTfshcZ=IMvQloa1a)we-907*g`LTqLbZ$HtDuLuH=Vx +LgC?>)IvM)Y@bW%F=Mx6n}bX7mJxX!6Dek|2O&PK8zeYv4S!61<0LAQ$|!pG653MZPvSFlWE1&1}Nu! +@I0pV~!EZcn3hOX>!KP+k{DZ_y$$@<7_6b5xn(me?mPFgSyuM?5d0b5;O+2^NbY=EQy1$Y>xwmfqT|% +1kwGGkE$Y>2$uTK6K>i*a`9Roq%N@`n6h{*OZ5)fJE{Em*5%SL$g)!baw>E_wMU4t4N``KLRA277)ypZ7X_-uCg2_jvtze-mb1g#+a2EI!mYrX+- +D8qi}&X$IVZB8qy~sQb)C)MU!A-us{Y<`yng-u(>q!UAOE${DaYLW^27V<_t$@fr(wbsy +GiPbyYf20)o($=5lu0_?NV5sRezHf^$F;R%aiBNPQN-iJv}-7%}JJHSWgeAJT9tZeR_GE9qS<9G8z-Cx3l@IlVp}yl(;3vE8@8QGo3@WI@JR-NZ5z{n%D)*b` +NBB4B~Wra8jL6E_q4~N&X_5T2q7c$N=vr$B3nq`K0cWMAQ@hSiK!TU#z=3bmv109Qn>@fo1H8BibWZk +jhdzUy!~7rn;p*a#uG-5mCjSriwCLvLrM$_V!|rdE{=z4Q +nLq_VpEAgpyJ~}{jewPd;gAeOkh(O~@FwU5_3*?J<$4#DNzV9Q1h=B|dF-gdqYot|LHRp3TQ87tW?&! +4J4LA16ELe`w(ooeD!;9P#pjm{IZ6~UPa))HTbISHNUnCkHP+|%MK?Mi1JM(gJa>3NiOUI+#>|~EBGs*lFNru|@~ +*8g(+NbN)1pkuO! +NS`2s`1~d6gG#45KOP$61Ea7Zj5ea+-Or~*wWx_ly0=HcL9CBA%Vp*C&!Tmcb*nIE?jzn0G6ctdc#$7 +cM9?M#GNtTS)$_k9w@nB5S!Z~$;|q%`yq18o`5Ytdts^kqs4nv#5+)>$;+Jr4GZwHy +8rh6c`346Q^jxu&u!OpKF&Yw7M&D*Oh;>GxZYO9F3VSyP!gfsYi{Qo2TC%Sn$3c$iXcVS+`d_I17L+YNSTg&;8V8p)<1GkV16J1cAaQvuGWWVPn~E;aj}wk@xc@CIrcB0-|CpGJtt+>8h4OJM?*J>bm)%i+pg +9v)szhBVw}hE%O(2294HryW8^2tZ_%B_xW|)g;xWt^?94_^!G-&33we?1lwYDAP^Zd3?|m^t0xCZuC(8*4 +kye!*VXSNbr6nVHsu+XEfRDm|2aDB@-@>FzXEOh`ep^5ns>pfQa=;|0d!VlC4&V1Z)>Nv(cuYp5zyv_ +=6Rhm(QIV=k}pgXBeUOYP;!++1+dA@k|0sHXYPl?kqIPlSvzgr^uxJ&xqOx)PBo9ud^@+m@0XIoTs3l2 +N0pX+eQPh!iAzS$i<}5e4j@6b{k5z?%Qws@%cMR>$IS3EtDilWWa8i>zbsi#{W-^zsUBy!huZ&j%k5D +;P1@}m+_LPRKvHZOl3NS)S-?BRr +1RrR*JM?@@FH}n<{G+G%&Ej9K+3{GRSq_Szm0G<$Lw{+-MI#F!Q86V^;g0v0&2C*>(291mM>&2SKM-) +4oi=wVaYAZg+G9BrKn13KOUK-QHriD*P~?OqUPPzuFB=`M_7Af6cvElslB`g@`t0eUsE2T +>SeU}126wjm3slIk?apOe(nvL-LQ?&-i$8GKwe&{M9Cv|nUQodhbVs9qNmWgB9cVe_j#Et7R9C1l4@# +Gh6G*MBgQy4CWZ<)th<`p*??7rBV=h1t`Wc<_1T<@)EAy20_!y-`3et&Po4_xLV7SOWPqx)!lK4A);O ++E*-04olF*$9iq@~rhuN-O5AdS}X3cLPb8dX%W8CvJ~;dC+g-c_#9=H7}UD}#~qF|Z1atXhzGWg8#m6 +lOxhlT1N9;^E!UEec9=dMKYE3d5pDB3{Rz9k?ZY*?~nX6(Dct=p<Xg~IlUN{hE5{SiG(U^N3Ne$Q};{s*KqVpU+F4k8P-ceuh_z +ZW;aTBSX4B@ib2{5#7<&dA{g;Xwli_0XdVM09p#Dp6+Y^BMZQzN;buk;(%-)cORfks<=gnn=!UH9H-GfR~cVTROmo +*s15r(Rtb~fc(0sFzEgK9Sl7tYId>=d#Fmd{+C@cp}Y=dZ3Y*T4cszDQJ=>dlVrC&`AfR+Fi9kznmcO +aLL~!39e?Nq=?gWI0d^41HvSa8XJhCmKXM92{<4Qsp+g)aULoH187=YTgLQiwse>dfIlwu+EE%&>ooS +tq>b81mp%xT~YutST2;moiLjhV?EGp49=<#+4+?fctT2fHyXm!>d!qL7156Clpfy`D>IoI{C>udDr{^gf +pV&Vw{CCI-{X4vnQ5^$8|Kxxs~8C}Xp#F=u-dcYhbT7_^a+ldM+F&lJ2TNb7VPgTRBLK9n`(>AP*I>ZG&0tjimc%zlz+g!hnu37*f8!VM4k?E;fnRLosVTac~~CR44MvGz +%Jp2i@uukhWEGV2c7}iNWCi;j&j-$hm1$#lm)WW$ +_=Je(RWUp@Rc0u6M6lK`d9&>?v2QMK-4FEJBx-Ez3RV`!-`6brm4KS*RZk54nCmj^!Cs@tds{}FX}zzqp6b!O#br2 +n#f55AiX~Ogg}W(e0AD&Dhqm_3)*&XRqy~tLrlX2TiSYJ#P%|>60x6j29&#`zw~P)XF=%;e*j6%y8O( +1{+ZK~{*QP3{b2$3(`A-k%JgW1y1Z{m7@F7=+Hw6hy0uPEd8PMvI8XE=HwpIezr#M>N5GM-ud>(e+_-JFQ#FxI&0}aT%lUS77`uKbgG*gdW1SVOEiLGj9 +qXK(CQtk#@W9I1NIipeT(RwLB~wG!l;nAw9>GbqBBg9Yh6dc1?BQ2`_x=B#H3W~Rdp#(qgV7B-~zpHyChp?Uh>7m0pR5 +2#Q_bUV0<|xYub3E97PQEI(pI^$!a{>6|CxtJiOg?#g-sUQGLG1CU{WO->W3WZbvz ++b$r>An71n6~_(E$%;f4Yp)tA3hCp=7ka6=$|Yx(~8%CwZP@gadCqa66OULKerg$J#NPAGA3JK)t`ng +)i(zIgT`8D1ZwQ*YxMl-ye$e6HaQz2R=Adwd|7M^0pDv^+5Ad{*@7 +j2pOhM}A*ZbCPO;_<*8kv#=TJS8bSR8Niqexu73@~n`*JZY2TZ<&8cw0cYbGxCi9_Rqyd_M2Masn}=P +%H-dC=FlWsY<1~AR`IO2zWAjAR$0pwIAwV?f$`Mk&}KZmkV6)4$Mm|E!N_Aha@R{(dei%fVSaahdH7# +GbK!~n@>$Dc7z(0WbCF-Bs_}0oVX*geFo}S~tkE*2P`OVMmV(eGw5)Lq|GFIb)N8z*NoyntzCGElE>a*imz!qD)Pz{h?tw;`(tIcEnh(Rc;3B*>EKN1- +9+V%Ul0R4>TF%-XZRRL(m26_6sU|Pq~uG|{O}x#o5UW`cRS@2Hwr-OH_HaLCPDN}Jyl;5!DST{owG~) +?0IKQyZ@yz77_gWwJ`2YMO%K3mLjs)b)>ck7)3(=`Qb?s(fJe%IQy@&1tBKlhp&eJA8AX-FP +4>fjV0{;q@A`wF6OJ@C@ynI^o%1FAvqb{dmvQAx&&AGV6wxq&#X|GuoCpfMCD-Tg%=BHb{tJDu(8kXo8I!sEw|5_8ZUd +4HAFd8pM*tPZ2v+|T$9tH;Tv$4JI6k&1iYoaJ+PH=NBs`}!rlhN10&c@HE02WGURc>P}e_+j#0-OROp +Dustm#O^6*UP18VzkELMP)SL*OxT*xK;gm|0hY|D7T?SzkqFbaI-}b~JH3-Cm{%BxPZ?V}ZVVYoyd>h +_W2K-;#$v4xM?>Q(Axdcb9zAJ~(qs^ag^53yt7W1_0#^uZjJjH4)!O{7po8HnAv9jsLr`*#6{@htBOx_lMEHpm)Vioz%oq?d6!#Ou*&DYQ)* +NLez*<8^E!u!9Ra*v|Zu{PlJBOZhGvX{5j| +0Iw^r32J-UfZAT5=f!ue=#&+f%tBj3{D##DB55RAV3NlN%mt`%}f2Q?4&YHDPqqR_NVv)6n~WDXZk-^ +6uA2?O=JSGPsAymGwTGu{^I09>!x63O_e*-owS!!S7}~^CtUCL1|YQgx=yyKWT&hxkt~F1}O6s&_I?{ +o=QL$>V5XsdlgJs3%YnGU(;lY&SYj#3eFGp78{P87dE-|nlQJYmWFe>0E_DFND{_Vc78l1u9GH|j`z? +DXP$ras~5jf1WgRr&hq?GzUqf=h$@$tf3lN5B?aK0A5YJYPdVUHRm?9=9?n48_l9uGm@XYYc40mI6#-eCbxWb4iV +lvCMag496P75a@|9dpr;JjJR<43*~c4p<%EYo}VwsS6z;#3Od%EjMRtg}27PTUHfFm4%6O659gkBmld +*dN>qwtR|3?xa?BG#bSzx0ZdoyZf~?Mw>}BcsU<2_%|MpX^|+Ov%-pmt&hy@q-B{-id4*C)`+I(Agv| +!AKWOZwv+JLk$Uh?Um^3C)~UC&vU$+bjW$f+49j9pDh?7fD8^Y1VrmVE*nn50k}irSR-~ILO_;JwSPL +Eq8t@;E&Tx=j(VS|aGH2G2G`_HNc45ctf=${|FPqwpNQzj5RSku2*jLy_F5|}(k;)5`YYUN3O)vt2Nq +NJfh){~6DVyAAC8iAjURLPcihmWtIuBq4<+A`*vn+UrJGSF{0YPSUEV%qvtqGUO2mlwP{QBs+{e_#YZF;K61;k8ci-+d2c2K{7wx_j`Td8;&(Hw9(eO5dDB4VA0v$o0$r!($ +k410k^=-M^MQP}rFD+dJm?(Ue2?#{c&IvqblC{2x4X9C#+*h-0~959BF9nW*2fU{)s&iKRi-Uyomxt= +oEn>idn#WPq3u4yS}>jKh1DrpON?AE~jz>Wz}7jQUibS+cqjt6=499W;XiP~AY=VDs +}~SMqf6=yBP%>K6G@*BIbY_i~GhP*JBlt~(jFKekym-bUt*u+^xY~;3+}HsA5>L`PB2~< +XTj4D5P4EE)F~`3`ZWY^sf^)M(z~bt+2AB=F?qHA(kj~MJmWmc^h*LAEycV2}BfW#JO7kl0?qnu(;Z_ +dl{VXCuHbB$>L8Wh6KinAB8I=`oLy7jtgVMfn9JH6Qxn%CBs>N%C{rYjSjatsk +Z=d#grzsE5N0pT34;S!08nj26Af{pbXgii}{Ed6=~7+A-|72cp)d +|Ceo+%!q1`axHT<8Z`T)7CU-cq|vN3su?vePI^}(LD?>$o7oH&bs+3w&w=abU6V^R~ +fm_Fu>9|yIIH+<^fm}lYg&RpR?^E~Qs$*+_{g*FZCb4Rk8jj5ZC9_-9Q6TD(o +20ue8hjb)m#tt!XAhUBXtMZw6T4yCS1GBjb;u>?sta9#9J-z6TCmIiPfPgql9X)blERmX=XAB= +#$8XxadUweTGH{4Rf`+@Zf-2RA?pfz0l%SMRC&*J>lTh4K6$eW}3rKva+s1@^0u8iJxB0B*kj@Xk3q( +n_$~0F&D>6@tXth5@Z#1QghbQ1SEeO`lEG;9w|!1f*)*ZN?L-Y|E?8aJ}aPunaj(9T`sp&jr1*yrvnd +3;lrvqFd|=^(c}_s}-KIUIGrQ>Z%N4A@=K$U9?FsL2G0lc;JwoXn5!n;SZf0!Gv%A(sdEK^;GoH9ev^ +gX2A_6mFH*W$|Xnkz4u%g-@&foZOJLZ9Pe#rvloSn7KKE5US6PzkAWb!oxrNV&2ex@-gob*gmGyz5>QAYru+%rZbIm6s>)^wO*c{c_SY +i0A>oYvqXpTHCI)I+3Q6%UZ+CD*%UNz~vA+4MG=|G5*6g)>cdBA3uEjz}|fd3a+XF3{iX)$mE9lYxzWXH}&fbcwScHP|;TF#`AUrlm$CpTK233! +*Qg&sL@=I7icRxPF=KLH!brT)XydqnaB<;;k1MevwS!99!A$ONt37R&ptq?^K)j#TUH&`i +h5TWtpsHVdoZB1q#d&4wq^~OZD#ArvS*}?Kl~~|AH=~Q~EWh+lYd!Ju5#1>W?~onLvCO4l#u7+Nnc62M!7#J1D^3s6e~1Q +Y-O00;p4c~(=$C`&9L3IG6uApig!0001RX>c!Jc4cm4Z*nhna%^mAVlyvhX=Q9=b1ras-C28Y+sG0BU +!P*ZAP||8*0y{>1GOsZbL_hSaT+8!w1sU@D{>_J?T9@}bHUFCptY++=32f394nDz8#3SJ@qAe;zohN~MuUd`t2N2;F0Y7XWX +1JaJg)V|^OxqgVQUpl)&sJa9DJ8GexTa8Eu#k4$G0Re?GYn~&Ms-ktn+fmWVq!l=4Gg~3Cndfo1LEc+ +B=_ETJeqpe&T7tOR}!A2%Uu*Xhwu(G5Mv+B?m{K-BM08Z=!f}3BR;K!PtryxR^=4*`r`d#$%FXo&o#UabH>Dl}9SFf}>VnWj7PD6m8nt}o(5&>M +(G`|;$W~rn&eer_4=UUD0>G$LhTPX`MKRS|mo(h_9xu$u!IoirydW4n(f+s=~Oo8B!%dic`J`fcM0cZ +%nT9*kLP?l&Z{!jovund?P@T2v}vvl9Wpi->OSS<~3F3S8<5(7gf$a1;bOThCI;j)AimloQ9mxkS$a> +^8)GS#j|fvPNt@|YVzd)Q6D(L%B;;=U4Pf*FCXl8lvmh1Z}tK+C$&-!$gP*Y_&9p1$yck>!}>2(i5nP +6n808bpGA9(y-8H|0$>4%}!F7-I`kjh~8z_f&Xe@|3i&UGJ6Uz9DRgDbKVtOhifIHT(HP#0yFMpS2k- +)V7PV%)!QH?Io|*f;R7h3DIy$1@9iMqE$;!>yP$(`?uMgkSFBrUTy)DvqLttQnzgQgy>}(;%(WVaTeI4$p=|8!Ez~IUTvOI~9kvMYR~J6>`bIvCkIZ+@5cLi{%Er$_AKnA~XSKERZmQ$s+02{6&!_M`G4^`Li%=<73PY#}6l$HWi +_AM+&hui-Tp5rdMe4+xY!^lw!3(6RK6?}Z=SY7^AZGC9f|PerxztzvB?chbVa8jj`Y`G2W`D}dE?{C` +!mRgqh2S^kojcPQb@lC5>~)0FU&V>+AB-z`I>sX_tQ#4EmKpAwJ}DrQubsonsY01Un=;Ruk{Xh$gt2a +f6=)8Nte4fm%J-9YSDE1KlGZGd(`6;(m;@rjNCu%B9-La4I;hYq4s0w(IaD1Zs_hd5k8z~rDvlimK)9 +fjR;FaZrD&vqa_(p6y6z66u3U@E$2Wh|zcP++=QC21nT^aF7VvK)+NU>K#)G(Z*k1;RmICOnP(eDx7r ++llTof}pX{gmDgXl$wmJ@_6sW-`97i5Nnpl8qcQWszQF;dbT|ifdp+%xN@XTjxw}%_s1OQIP(I7iKWe +`W{~&|VrV;xVZLz^$*IaJ+KkBG5MZbzSt1jb>I9~2DAnAEhxX41ZC_jt9~J>81{en>kY>S{z*I$~ZgW +(&F*z8IF>+Ay!@g!`X19rU2K>!2fzOu*G2_gWb;S%r)GG{|x+ZwKp43kY#Lbc5wC2e=V(})=oJ~g7!P +Ukri-Tfn8Ygy9A{r`}FO)!?H#Hy@wMgw93jYfwefx!VFZ60(+#aQelK~2Zi*Fu-A-0XKu)DjYJ@6+L! +w`DgJg!}WOYXf)a=qCSP|NoKiDt8Vra-Xp~H{up`6So0n=8J+8!|pj7>U;n4WnyQ+iV$nk +`X%{`bvHO;GW(4j9mOKW=I@6hK3&-`XXK;dWX2VD-RQS7kzyJ7*gvSwk{Y7QV;vSS;5^s(TZoWt+co8!UqfGu)OH`sdN=iku +UJN!HCy*=P-T6{Km(`Rkj_0OQj1Gh3=rwQY=rli%Ae%EckVSIHA{&O>qwzde}nlY#g*sAA3&n9*#Ac^ +EKqmc``p-pZa+e`kJi2#xWp|GR;(_l*o7?JJy!P?I~>gJP8 +Qy$6YuxrZU#SQ@Pksi98R=KBFF~Uw@vN^x2eEbw8TD;o82uF)hcx}^e{WBtm->qu5MBqwBU7$rS0W>v +qPv0CQN|+bgfTeFllD7uiHmAQ@FV7=b8$PX6}469vkQyCI&i+^8vKJQ#*y*)|+XfH&PAKr!d1A-!xq( +bX*}`CgHj_fI)B4`Tvvi{5seFW%F)ZMz=iC;x+%C$=#-yZaCyxvvoo)8Pu8v+J@F +;du^9*gAOhXa#}^GMoCLD1FN3+4hMTHc|g}T!9e=_b@dL4>+01Z$Nyk>K_7@}T|-e(I8f}=()M6iw+V +3r4n3*08i>wPY9O+xyNUzgzJjyG0F=S7keL{%#p)v6xf=jlJrR*y*JI%LRhY(f-$>Piw>!+c_XbVl~B>iGJc26I7uOpgUjRA(%ppP)MavxwkX#Q5%4BG4#d%Qjbu&7jCeZ%-|9KTz*nX#qObuUCJ&ztlXd +x^NygxZe}5rN_3O`J;sm{efdcBA#Hx}OeutUAL?llKPact>vqGuMO&j9;N?(yK#f{XhB_P)h>@6aWAK +2mt$eR#S(ozoUKw004*y0018V003}la4%nWWo~3|axZmqY;0*_GcRUoY-Mn7b963nd6iYcj@vd6z3VF +mDi)9mn?V{NC|v9%Xae|ah9z?5&CGklVQX{;MbQp!Fjy +2Ix?URxys}yj4vXK`mg}RaZwWTN$!C)_WjU6fntv1k!4xQH0vIQhN0i0GTwTU#g!v`taeWooN3|EcrvX`&(JlKmC +lnw>6zyA4cOm2xY1)@r++AIyI>F95e6jZZKtlE=P1l9{({g1V?*{H#y{4LpWhoJ9>%+Z1+VdwMEd#)` +=VM*Z@vfCBh<=_S)#{yLWjUe1rc=bk>tb*&NmPF>J{5BXVMfEDm&yIC3W0)xsqCpV<)^Sy1%yktaQE*)zPn;iwYJfEuPwXyV)e{cp<|A2nFKE`aJyVq)(5s@XtPA1iN|tH2%Tr7>4<#E=CTsUxR;sCf +OreF$N9x25SEvF2&b +T2f}f`dY*RqX{4#VL#%C_cs5z}4c?5^&Oov84#`ODwROw+TrLN60uvv{faCp5tIm=^ON$s^Ow@2x0tx0rDez&aC5a)4jp4c*4~+)#7Wy6d?Um5{>ipN94a$>D=5+`h*}k +PC%{U{6fdBko4ii>#)B(71U{_j>&};k8zdZvBvMBp|(Ty8bvE-7D*nW}uT95n|tE^v9 +ZJpFImMzX)_uh^%eu#~5;O;X(JGoS)_xwH+Mq(PkC;gAccmAJCDrbr!^w5=ZczrXp|Pm;2o-kpKi5_f +lYcD`qJm~B(Khbvc=A5q +6eGYaICZYNM*QJpN)-@uIF;sVmjk`J1|`c=`dLx)ncu`0(z9A9)8O3wn0Ze~7d5^)WY_o!To4Q{8UVA +;Dla5%BZ6-bj4>7g=`dWz*Enf}X#Tx9?Tk9HU3?n)>#5K|p`0uB!T`8hLKI1435?kNxugb#KMoB6N_O +D|x9rI!532MlyJm?dxm!)HP+P*D%r;+c%17*CfL23l>@FgAvWZW1bnZtw0B+q48f=DgJ$0_pWjjZ5c&2%BAM6~ETp_L;6V5OC%>{7L_;iGD1d0xpqNa}ntxwv?p|Mc>2fBpIWpD$o8vvhi;0Fo}%lzA}SC=iIj130kp4`hzd{C +8u@{8}{z2-BOA7rKdO0R>xi+qy@%^+6hQQ#ZvFnLYVJJTFT@%oi|<141^J-5$i>h;}ED3td@=wGu#tY +h9>9v^9*tcNG6U6KNrIYXBNPGYe5SG^48u)s#mdP{d1mbIi0A9yg#MhVl(B(qu20$wm>&zZOBl*Yn|XxHK_S@epjGJ2x*rZ6YO61K!lnd%#N6FnsWz|KwD +XiwgB^q;&q{TzN-+bh=b$i$7R`qHdX4s;z921tj`O)_yF;lW-~`>Iz*7VP7vu+O1p*K9XMv&<>`&gpL +)9E$Y)q423h=)N5oe$&IS0K~0-qOzD^p`c$pMlPTWceNVIbAd5Dwa;O=ns#;4oLPz8hh7WQugPDZ4_^ +YHJ>J1>eA+;B1LjWUni|@AlaQ4f{{k->rUBkqwuER^H+xxv^fxooJ|y-+SWoK^oS!rErc?U+qEWI{k(Cu-<4_ +ugfsRH2FD5v1ot_Ike4+&k^C>jg+^y{qr|>zk;{m5INSmvnJY!$4FZPv6g-GGn_b^$CExzZDjI^w7vH%E9)E3)!qsUS(5qu#Lu?agG7NCp-z!zc*G$jAxWqdTUj7g+s +MiQ8-R}J6Ker+>u;ces%7ti974qb#?7X|zfA`oZ`yM)^B@;`>p(aHArkHkF!TxIyQ*%*+qzO=U3_+$w +G7zJ;(<)JYHw!qm;fU6r-Z>?9$9P1uR2PYQ>2g<8VMMSO#o^F8!(^;OP~60W*rfo|mnD +1i~NjjXbbf0AF%xT$qgtVLe>pT_3YG!~G#!H3ozj(ln593oT4Fs6=P20JG;kCd6J1*&#A5XZcyymklJ +SV@Zbg1(-2%LbNSY@!!I7(=9mE2XssRY=36Y#TFHb1xCwmX250{gSb%278w(d5+RL9c}tdA*O-VTPeX +PD!5o)8nu+)#DvTqWps>U2(Ie+XCluYpwvcVpULMdQS!<$Z@WUgU30lGae1q)lSja4EVUI{XcYw3 +}|&jXK8xi94RNPzI9Wf?&Oro1KW#SF&Tq88UF~Bqb5%hQ#XV_UAyO18@lKvaHu|f}=neO?jNI3JpH8q +o*}Xp$px!N#8dDMgPfm7h|C5d8oc%d7#y@oSg__7S3$X7Xg6zqY!?dOpt`kNo7(59d2Va8^7&H@dkiS +*sDkMo~6Z%Ucd#BY<=AIldJ+RsfiA6)g0oS@37D=96dyEla|Qe(FhRq6-j6{;q?3juRkzP ++I7t87ZodZU0D%~Q!1xHHNV#~c&}JiTs0U9=C9N}LHQ~}23oeK3@z&OTgpGgX2e^xi+{xyEKzT|mL|1 +{GG6H-XAZcNc>|a9ISb1RT_E74LhW*oB|NeK0@XVBK0V>kcnf*>&M!OXsA +=W0(X{rLrQ)&jdSNVbFK^`@?C<&c#6$ehkTyH};pK<3g*ZcLJ^$*f% +euY@+1pIYhZ_p3jUbw~HX| +%O_tiob)Vl$wG|DMdZg)?ag|CEvSGtKF)>!+41!rri)nBmgA-6!2AVj-=W5w671fxl>2o7-s4;xvP}t +(eME#mUr>P<4KsvKp(baI9z{dsL%`&-Vny;s4k&_DLt@`(POEM9kl;RQO2-G%#Un1F|H#GC5r2V3sb0n2uf6++otGa+8lfgq(&RpApat3m~&`skIpf~9|>8=P#Ep;mv;F5IDLANkLxIMnSC9I+W@TK!4 +OQ}!!SKvsq4B^|8%FuLIj&}= +FMM$*YBrc0UlE7n*bsD1wKm8aSlVM^Bt^E)Z+(w_w}C8 +8@PkJbt-hF4opW--o56n8EIB!w#mfKaQNLIi3>TZmp7D((_vQZ3V)YOU$rv+2T*)ei3 +@aczp&F6eQ<#U+fzB(k8l=rP8U`_4liMvW(ADE@sV{f&)gO4ob=Pvr;^(hbbi&768`@(2gsNJq(+U?{ +vA6!=%so4Rv~qEPC!38bn2Af~;6hk6LOHz4e*d7bLia}vHwt~sKcuwvpFL}g<79#{>!$#kOfZoT1Rg> +fcvggTJE|&n#@!0w&*~JC=`HqZmAQ>Q9ccex=z6YTubNB1z}GchFUvCw>^%s=kXNU9@Za7~Y}1ZUrmw_ +_fHtUq5@dD3M~0#Yp=OYnCl%*hw`#(nz|~0$JBljKueYw492c)?1C;RfKz1r#UAqqYKXO6@oN+p?aCC +Pt}bR?Lml-^2HzLLlGUq8PmIdyUGBb1LQK39?TQcbD!)vP&4;o7_&cIEIlxm%hMqLqi)1U;-M@V393U +1glGI-ii?{RwppRYMj^acSx1*vd{YF0+4JaL3hh71Efu3vQD6A`tTZF>4vkB`sj;BXM{|=NPH +*`3ipp0#M7Rj7T-8`$Zk(*XU-gse;!64-vGoX|DI+leF23 +G<}}=8H*2t8yJ=fXmj*jN(cAclsIo|dB&xH?IERFV?=!JdvPkV;pQf@2r$pjX>0y+y9ZHd +FCY-^)`GcGZB^8N;c3G5NbXi=Ofv-*hVsVQ;dq{EdY#3Ft+`1Cq2~mB6%YeMmT@hFgUl*j9gYO$oRS| +mWonw#K*M;65PZm#6aG7Stau-nuWPe^AxsGXrBac#qDPJkrz^LAaOU9U^Hq#G@k4;13UY9Y-pzn<7d4 +D0ri*_L3NS=>=pD~7$xq0s9duaXvn4LXV>~TMBa~vOF`6vgcq5JmH6;xQHTmK23CVv|fdtS+t=>BOaLe~}2-udusIowA6aoWE05Kx9 +LfOxDG65{Yo5}L%@98OE;>431g4n`flwli~#EYNbzJ2-PL$I{e={zo#SW6=B#y!*H1;Bn)I(mw`-_v5 +9TB@+yBKmCaXks#Fy?cPzI027!0kpQvw6;~PsKtlZ7sw +|36UA=84-e|yvE9|xQw#>ll60X}9t&~UJzgrTQ%mmOdpJXEGpoD&_^+kEVjkhtWE@uZ2Nvtw?A_m=rX=-C0Q~d1q#y1G` +hS)^r!KkV*alrDpYjl+0He`2fL{H_p)hhA3_Qu?eh3oE6F0dvNl(^gMSa-nw$OACs&}dL75AHK_OxIE +ZC$x7Uy8Z0vi8FwQmS3&gbw07BYIEIt_?GK}Z3&E(5*w7!qjQYwVB*_7cVceebebh5#ZO4?)Ua#%f1W;jvJe-xJpHHao +9vtTIR5AcTuo>2n&{cr&(ibQOHgnIKfL@fP5e6bgDydqxPj5Vv@)QWHm)orENW%z7IA{d}+kv@8I{e7YvPAuDeeampl57U;x!S1J_9zy=gg%+LL;Hl>dkCYyjVX#TbU( +LvW!1!XD64r4R$>k_6Bn{U +O6fWs9w$*jS&gnO&I`$?Pj@tT$8OjtOm!-FEJUqaKGE2HZr}qaGf}@obn<{J|_SG?NN$usdM1rpzZ4i +6_M}R1Ft+69myl@u=RKi075@H^f>|iOFWoO%mB#GXyN=eJ~#B?m`dU0h+z+SFPdff-6rm3DKQ3#_65T +)SE`XnJqak@WEn%UlVDz;MKH^*uxwd6tW3AqLH%XzvWmCQKWyoVbL%^p~eS2p;@Qn__vwJ9~y+*U`aq +ZezPR7Svq_lgGsbE%u!Vb95n`^luR=c&$*wGKI2F9kgg2OKLZG_T=fegC5=|m@Scde}4b()BD!qpS7;Cy15 ++d-{rNv?%gNp9?$OF!`!_|^Ub}hTQF1*%baR49!2q4ufaHJ8%~nVMXGyf(!-;qwb(yRT8w+k?;49tTW +|c7nOTxS3G>FbQgQ+Mk4p{-kKO*+D4Nck!C@Fc_<~a4yzRSE5aZuF)n+) +H7fFjbRhi>{`jB2k1dEq(=UpijVe2M~z?@385UmW#w;4ZA>?A!Ity1J4r$4$%s$Ceb9IZmQrCE2t!tiYHZ +jKcjH}BtZ1>oxb=r?B<^YL2$!q?jS753~w1#jz8?z@V+=l0DPulr3;-{Y$Y(XCg=cw9yR0t@>p5X= +dxP+ei+tFhCh*HPu{5ckCw5mQRm4;#nifQ_vOwB?eo;hmwO!5aG2Q8zr{r>|{O9KQH000080Q-4XQ~Z +za0;dN60AUvZ03HAU0B~t=FJE?LZe(wAFLiQkY-wUMFLGsZb!BsOE^v9pSW9o?I1;}5R}{1d+Zna`ur +~w2BIwR;0!%u=vuQoTf(e#WoLq^a6r$CR>R +gSDE|Y_{2mG=&_O^dV +IkvlI-IvzA%p2|$-(3Vyic&)Y&L>DsOQs!8PR95?gV<$>lxxm_fA$RFQ$7ekRPsnW?<%G|N?B+i;}rn?AI8R;0+2V;We?LpFyK +YULMHYc0%KiBJ!ZmzFBhV=3J`|I`fGWx~acRD`(vFk6G#Rc1ipBmd#B{OQ_OW=My9^WyF-?gBujoix4 +B0SuGh5`>i^1rL~hnqLVZ$nZI;z0H+XB2_<2L(pB|lr-ha>)}@DAVusJ$$xI@1!k@CeOkp5vRjx1@;B}Xsmf +D{DAl;sG(6ZZ^r#S$Ei?I2q-w8hdWzb}bw7HT+ce5t(zl?7N)S>2{O}heh`;Y2*0=YcJ_oNe9}fvqnu +3Cx&7zcP5;js?euBl8oj$x7H4E+r~z3nc=? +sS#M!i>3ITs$4!!*WIR54zfw)`-1r_l-~h%qMMco3tn6Yh$$O)N)Sv;4`Fru|Cd**%p%2`QPWxV0aup +SBCU4r)`@}n{*m=w9C%A9yi`mP2y!E`jD;_9hwd5dhVO2W6z;Y_UF$Xz>m~EjP-G%ckp%bGZk(+X}N8 +;wKULXCrTt1T_=|9wiX%dh{)JS7k}0VL_hunvU#r-Icgk57_(@~z}U$1+jF8?gm3J*T;Aapu_j@h+64N@o=UNhjy)Ak`q*3ZvO!o?6w)k~vwkpolza&qL9iBl# +Tk5egeqy6!K5xPOr)XJ0PIGhY4XJ91{lUwC+ra0^;d^(|2+iwfYY*rL|V$N}zYukpL=H9rc5v0t?*^;p*y|Y +`;E(28E_xOm0K7KDPzKP_cb63WE1>VXMto6|_Kx?V)fB{mPcJSSG2Vv55QM*@jP?QZrx(%@%Ksu1vTc +4l496X)M`6qHznr(X+eu?Uh6S7>u(%BQ?oNnl@;Q>sNgU0zWREw>IlsY +GEl$+Qslq!_ix>~3y&x98@#1RbR-gYRG7O?2|};oY6N?%ENct^KagY09iCgFN}VCLHAz>5!XnW +#=UvBubr&U3%)EEfVQREpG5pQtEEls*KCM))C#H*gZ?*27pYQXa_b=jk7yO>M_TiZQ-r8zQ3yMp(p-l +)ID?uJQDiyF48{a+6JE+izj({|18pqTLc5%C0%{`*s|hXR?t@H@FKdR_Mz@&7W|xpTSyFqknV2puAMo +TRFlD0W{8HCgjqE5Mf7j3{V#%B$<=B$$Vz^gL*6?pS@T5e`HJQ{Vdmk)F2a&U1j&L)`FCVr>^!8ocfO +mb{nmUr!2_O1TOPQl$HV2lj$ny0E8@j+iVG_o_t^aG*z8o^vhiyzmvbf~mMRmE^8HVRe4La!*X$U7UCi0Ac;0beLH79Z$Q8TVH;~feE8dAv>4gREEy8e +H{WuJEM;!QhX2Um|$exJ)@v)x4s4d<2+!N6=wh$yR1g@548o}t>KBeFBfju?#46B&j`Y!Q5KYV+zTJF +v4o2NLEq-bCuj`m45D6V)YJ|iLM^}g6-+NTLd$~RPBa;Q^ag?xz8o{{g5NQ3`u%QRM&-2q(sEwY`Z52 +P_9ow6Uw^gbz;eL02skfx#ddWD=z3;l?@dgqtx`>WeAJd)UaQ}^?2vJh4&427m#pV}DT0sfsdF}?n|4 +n88YK$Om@j8zsmJB;Tw=ZLdrrM%0x-;z!%)b!tNe{$~L30eD3=YzqUvQ3L{)(1{!rr=i#Zg75ZWY +z{pUusD3T*g1%plN{i)xoYG-@sm9wbyf`_D(mAvR_!9(f(p5{kvM>{{v7<0|XQR000O8`*~JVDK8iZV +;ukhD{cS)9{>OVaA|NaUv_0~WN&gWb#iQMX<{=ka%FRHZ*FsCE^vA6J!^9t$#LKLD<)962%MljlDgbE +v}s$SC?9P}ydrs#Q?d$J00VH%#V))LLhy(D_Vi=kI}1{=9y#hLg}7V?0HN+$C%&* +i+y%A(eAe!RW?=|`C^Wc4PSH>x2JRB$`p>~&hpo11qu!k9W;(8$n4^^y9!0X-y4UYCpAhqC#xX*R&J^ +K!jOn{1ZLF}-~w=jB2&Sb9L9?bpj<4n1<&$ZJ4SKYK{?Y;k_q7WeODv1;zT=S&Sh`U%)j0@V&ggrm6X +J*_u@x&%xKu2f?gnh!>b&P_VMhiNuc*4Y>Jrfsr3sT3gnI;$tk&19aJHGsr#y1}F=CwG9$!2w7bOq7z +hWF?z-@TaVXlSz@TL1sq>2M6;!t!v=cGEW=HOsj_F>@P4&BcAbvT!NfuMb=Cv!&>G`^&CI=HJL24s%| +DANHE%M)ym-YB5hK5GaC$o7cdMw#|4lqBu)pAP{YF^AZAAfywj$s)Zk-*xH~9R5*A<~RI8zZtQuNKb_ +fLs@p1rVOHq^!J+%*=5%9%9*g9Z9w4*`Ep>jxrAH+EI{EUavuR35Y6uAKY?E&xh%9dV7A7p$k-==vjd +)lc+^>PM0fsZT

    ZFh-d%m1ym@u|3Lc)shWOgG+sn% +1@5OV`$4>5BBRSAJq%d)5`rlZ}000Ff1<1Y#<&f&s)l&t%cmQE2^hXbn#qGBp+=uKh&Zf><`?1~5#E`JLAnn=fIZ2Ezdg4OmaQ12<79nBMyoWXMSdpS-cWtcIO +E_AaeMNB8vrY>Qt9Xj7GQgyIpt%-i}dMs*Jz)ILT+$IxNOqo~FNGf?ka@7uhIi_< +_Ei*L~YzJ7cD!^z8IJMQIDfM}v}UZeJsV9PH+3)F!1gA_9<3z2TXD**ebDMboHBS&EUr|3tFQO=yLtX +PoROB>ti&IS7Z=bKyDlnPnlLQKUudSylP;zG4Bpzg>)0PQLB1p~~p8WsbBn{y|jfr56z$7O{*;0x>;^ +E+$v;X>F-TnCI&Vz?6D*T=w#g{(W<`+}K^!te>VU;SXXPU@!0Hp9_~7RDNJAP`9n12)6Kcrf~G2aAe| +H|=_)Pe-54^gnsSMidr^0_+X|LoS9s&_L#t@?G@rmY&UiKkNg+QIU|M#e=Y8BH^g^Mr?Z4SIT;R*U ++@+<4bwn}KFOr7=#Yci?;{ytO9ofKxGR8xnBiwoa!raM-6)Af15cB`lT&5zn)-fHnPMA=mh1dnb8)Os +6=7&1_l74U~R(IQ0)$DaSGf&oo3~fAiRz$+;tQB9unFSh2G=G^|4f4J +0>g;yO{K07>Z7byJM<|5#Gc66iPATo&P1DZ?9Iv{?$irVHCdh`iK;gcxo9i2s{jn>iIze(i8l~W+MaO +;ChQ?{4JIq7VeUY~)0nfgraAYt@u7nELaX0HrB(gG%PL!;CuV{kcKnYlI+?!$#I^!WimJ9Ys(c|Rypl +eF#|Apj_E=;Munus2kOj6z`oMz8TScJ8j}@DowJWh{t4&#heifI{*8)Tnbqm&|U<1;p6Bc=u$=XtPOO +rXF?1qf&i9sk)@If(a59XSHmBe}+(Q!ADD*@7tJpnA4hH2&Jky7BRl1rRbfvpvZvI%d(j;P+;i< +M#sWuK7L2o+wzSR2B?311nEfpy|cB1+>>`x=W-$<6Jn+n;YPXrz}%$7AvG=mdY +f#2-K4k6(U{Y~ew)+-89mIinTr*pB +Yf4_MEc4)zjS2@Z*7YiQqGUgK8W+zq3Lm+BNwO!Pb??eW2Qa^cl@q*4+ot3=t3#=d-YB8Tz< +hXbJmy!ZrXVHiFH2Czzz;h3!1bU(fQBUk{Q&yIf2o86-m1U+*DNUTmj4I=qrebg{KJ3gO +9z^1-h*%$)@5gUX8qUkiG|kj7k+0uanQkj=>%QbbP9u+{E~L6=$qvx{=6gq(5&;U18YJdfC*X^CJ1QW +!!m$}@H6P<$Sg6?eDh?JNI+x85mJ0M7Q;IGLUUQHCe3amL1+n$QPc@mtiWF(3vo@uEBqC_aS}df&N}x +H)UGBYH9#JOPNu_hfBa|S6*x&-*gb2#fz3J1@}h2bIqf50Z58zrW?0)4zx9z+9Xj!5`x3-!*@V;GR1* +NGy==7tG0QR>=n;(-gFDS&l;q`Bx-o-i;!Rl`qM0{uIr6rVASPht0@`ylH<756+(??j2929VR?oqJ#a +EeKPS#&N6GQl-{W~&25K**H31!E_^8%v-#RzhS47kWdScXxy$0Y|1+BLWk+5!MN;xf=?XS8Vx-O8VDmlVu9e|p80>ofSI5^yKeLjHtO8M=6Lmp?shUV_v_tWS_DHRi7f`o4cLupvMusoRMsRw*o3t+bGxu@Ixj!jz$?y1115-3xyr +swPgrEG0=nKcBECrJ&PCuG-wiKg$~cFhM5X3bPTKhJe#);HC6*hbW~$r>SoeFL|~tz>Fmsvm+B)`r?L +W}ii=?tQ21k+E+^_oee5o)jrU0V+p#N19F-^ZbaX2ih8l54kv1~V_lfq^4BcOnmZDwHfUmIIkO2eEcAjH!;Xx&|wxEARU5aH +|CE|U$gVnG^ivXq23Kld>*JWE!_(ty*uG4yo8`0@>s@-e7M!aAFadu38;-_<7aydk)Ka7mX_jOBD~_wN)4MuZ2Y9%NtC<0;>K +ApsT?tnfNH-Jlj3R;-WeP$q0asptX%Q6ukn`4HoDa;fSu8^e8<)%;IP9l}O?VLIC_vBi+UPDF@WOdqFbJA*isTW!AT3gS=1Xj&z9)vt}&qi@)yL)YLvkUyqOd_5}aa|bOk-5fw4$mR4hr*`c!;(GKT ++0YCdx`)@4z*z)BKLTKC;NIj31c)Rm=}6-}*Nl9b8{^AhEkVN5|@rp{tM9>?G@tX648>{nB&*9q$3Lz +PuS+2cCA$ih%8M19i?KLKt-XvwY*{DI8lCT?=8JJ+m$h6>t9{6-vir+F8Wqbw-us1ZLWKmU_8)f&=7QIt)>yh +6>0ll>wCE*3S|E)SqEFvX))umkVLDB~0JREG`?^nKRKBY_-ad24b(TScvx&Y;@G4O0uf6^15S=_Ry7x +WUrSZI(N1*&WD@Bz=l5TB(Ce(YF+EiVfvx-)Kib(m?KIOsD7PK0R*eh*YfnHyBDV6c&?<>+7rQ$@#li +H=O1fUS26*(iZ12k&mR5;sBomL{)A?pvG=RG^6MgQjCKT`! +S0z({@DMC^tE`Z`?pr1I@qlc2Q_($lrbr2oiMZk{|vYH>^ntJiSY4ZGQyCsFpB@7Yq`Vnb!2>iO1z)3 +N{=rQLv>{%q;C;(C#$ML{;uIPS#h_Sk6q1#s3b_sQODFo+kvLAU2YH?L62ZL4$FzYC548P1Vfn9R{O+ +|RjlYy#H#7Ci0;nTrjV!PrwZV}WTQ^o1+=LOY@=!eO91fWei5$v?&@3YWGSh|U2WG~`hS<>DMt)_icxjd%l~ebcc6nD(3gSz5LVri&JsAkfbu +V@(?SG?qygRg1I^sv{{7ip}C@fBXP9XG#C$QwphNY!PdX+fJ9S#bJr8gJ@Wv$atsShUn0mm06YdvVa|_!c7KH59y{%9Z0QF5-7>dT~%%Y;jJ;|(}yx!uxOL|*#tn^ +Li-F^zVp%amc4iUEP7an5Q2dh8M#}WuSC%!IQfpSDkO^pAu3W7)v~PuIBsIZeWUCki&uOaoG9QL;A0o +u#3&q2yF2I*rA}9+ihH$q{x7B^(qc!Y&{+FO6%QHeiY@lx;0`P8K(A)8IL{N(F%G3YMGyNd?oQ)`P## +8B40z1&%9f@D{L&jcMakObGMnpSJg)TUg2VD+*Z|Q0LSAv2l&Z#4aaW=-u?(5S{PgPPEWD;H_pqfeHR +$uhk)tRhw8RpAUUi`jBe#tzNo4VXsdrXa08QB#Pu^qq&orsWzKJy%puTetUfy-&=3~=^@^I31!xdR!- +5EdhQ(3g__k|_0b(W_U5~&h7ef6H4!eJYm>xZ|cNEeF?IhEQP7hA)7ij~S9debR!W;*o{oK79HM-^8B +fj)PBgoV3q=XZ`UHcGA$u>|Eo2b34g0SYD5qQ1{?%ks@tIZs({h(#%DRC;T$&M>T8lX*f2A}y5uF;x< +5Y%kk9_rRIC?a;B$@%#I#g7NcMWw3*Y$MJo5)VT0jv#2B=c{4Tg-snpVN+Z>6-h>L%b_1qljnosyN4C +_L7SEUDJDj0qW(vj=Te_}485QFK!*R9k$`&l7qES`wNmvGt^9BTFMGIbHS>1=ZCO*uEHRUia^ymEebg +ItxId}}OKqpq5#({ySQ;-s5yfQ14TnDZB_J==~8#TGw@UZk?RNgyx-^UQo +Y{E}Gp6@NJ-UH%M?0}Z9K@eKTGb+f*e>oFJri!a`;1WNhQ)KH;VA?OsYW+ef%lWBz-L6(UUGYlU06rG +d-Nf<3=10dJrPC>3({lAoZPY*D*Uth8Z)!D7Xk6W-!ccQOaBvOH^ciTH>v%pNdcQ(?@E +OId{~cvR*IIxS(bMqDHqmU7V5QI;A{Ypu1N&%%Xu8l<$r{Vgl3NZbQ1EKR78Y3=GR5?9e5ofdX+Ham$ +^uQQIzg~uOSm^L68Hs}s*F7eiC0&)|`q_hMV3}v%aUf +2_H{Xck@1s6MkGt?n9~3Jqb9xL@TWl}VhRdnd1e&67sZ(@V?g&O-lUci2+f-Z!W)CUT6so|Trw?Hmg5 +X)fp5v}Oy?=x^fuRV8R3t(%6_dEWQYS#1F2dM)qatwZDxq6$a>sRTD~8Con7813IYD^*pqYcwQru9vg +5SB?28T)PZbpZ)!E4y*8fV1RgH~Wej0NZo`TvlQ5|gl?n$7MQwYo-OqTbM2uS2^Mg^|j4GN-gtcR_=z +Bke)WdQSkHfSOlbOOP)(p-n7W!MQ(Or>()WCm`4j2t^g6kG<6dzoX-jyhx2|z~weko8C}6{O5T+79eB +ad}D6Wy5IKbh2Linc)!3CPK^qE-UbvvHJy(2;z*q(UGadFV$=ZlmX}Vo<7Sz2Z8WuWs2DdamC{vFov= +Wg5B6ftI>q8G->;cYy9fCyj9$c8V$Y>jcvz`(OV(E(kHeLbeMx@$kCT|Yq)u|5BtLXs_MMPRylL+om% +~ro<$3JLT>?6A4NKEIoZ@ksr;F-#6yh(zR7NLH#U1r=I#VKJU2A>LPP?TrR|9?s{^*f=-9e3Kqa1Pej +Y~R0TKRUPK1Hf$#J4s0FlnXAp8S{kTxq2b5&Eyy8PiIgAq)Z2SyTSyzlsQ)-@sd)=1u3)2W-T%@hozM +0a>PM=jNmLee2h#4B)_Y81D#P1C+=T50x*v_1KOQT02e-#P1I420NF4lok3K|CFFa)!eZs*XZIRFwX!@ +ohXN!GeM9cR@shfXK4+X4r&^TM*wa0RBsjwR|SQAA@G#}>(J6l&OC+VqNpa7!;6z +0@Pay1T#^Wbrcx%FcdE8Iu>X!h{z>@R~`6_0&EAwPASNca8<_^$KOk*c4xDqAofUAkfE&Z}$#`@7%XN +wP#Q_31?3+-cCBqt_tikvkmZ+oC#6DWUmznAPC>$P9#64e5(ny63XoLmL!$`)iw3cnb(9O}za@-TV^7 +FhXVN3ojq@XiBBxXaC3n(f{zQW^}@5{+V5TgzZLH-+L5g)zKc|oP9dAAU?xj_(u$gY2^6WaVKS8DH2v +c*SbI}$cuRLpTqW5t01^_;U2ZObxPwSh=0k8Xto2IYC1QvbZ#@GEXc;1T$EGGC`hmG-r3@*lDb_}=1k +X-SLv2d0fB*SgHh@@3Lg^lys-UexniqkGjMA?+YPuga-vV6n{!?MfkV8m5%ax=c-SdhVdM-id$wn=Q% +2gHA$K}+FT8J61q4hn5#J!)J(TDiiSG?2sSJ$%N^Yu?5>QzIeC#Ms)e3*9QsLR)SD+D&N1u*tm2dp~r +*}>ti}n!;bp8Ix75mThfewAl3#XOJUQEstJf#_kd=eQx5>WO|i8z-^D_wyIx>J1zlas5dwvnrjwI-Et +g{Ls~Wtnsb!~aR0F6G1|*&Xi1N*A3W>37_ub6}o~Nd1*H=aA2@-@O$on<@)iV}Y<@yzISlfi`QT^4DQ +YpI$yE5T_S;`pB5!lNUJVe!kF%T|Cb^!2VuD|4E#F5|OZ*P!Jyo{Y?mPKcue~#D6C3#*K85_P2ba-=9 +#T^5kliy%i{Fv9|yvR)6yUl_vj`DDK|+2UGugEw>d`h%}B(NEbgMgEI6__y$9sY+WPEA-Wcg#=Kl*b8 +j=Yo_6dsv@SLc|M`LFxdZEDQ01aN<{a@fI07aPJ6GJC>TZV!W}Q)1FA05*82V3(b~8PF|L8-6D!Ovco +)zn}8p+Cxl6t9+<#C{nqjiO6HuANt5$mu+xcU0jsu6uR(2Os!R_DpB_>xtJ2m0)deQx(h3icxZUc|7c +eP8L`o;uF3eOQmf^j$9LZWt?jx{9IFca{VJ1@1<7An5?hdMy`NI35-SJK=+*FM`mAS>FZjmhzZY9z9O +n*=agXn*Pjy==;taf4}rB#EyA@AAOT*kdlp#NMIhxzyx{Qvv|Qzlyrs4-b@WQ=_G^qsdxN5!viDdDwv +LP^3(38om)-Li9lV%ZH})+0GYEyCd~*S|G^GAIjl%f1EjG*8bVjgAXtv9-x4-r%Y3I +>W@dF8fC7j?g?rJP+e^#}^caObA2#_h_*9VBc~Kxb=|}|4tG4ubgrw3K(DW6oZZe=6L94z*R2QFmhI1v>q{c +TOS3U3dqRdm%TF(kKPR2b+V%v@yzpb^#zl3TD_Cybe@$T4(v3rB;*$+!&}{~^1a%5u6|O +{XSnXy=L4XTB(v2V@)tau)ZUQQQE+VI|SxL+WgXAw|Rty=}k$BF1KOKFBKdEb{Cz9~RAo1z=6gaw1=Y +Q#igjGNUqZfi@p=sDX{2fo@`rZ9r@gCed!H|2g7}9{A`O5G8esMM){1;G50|XQR000O8`*~JV000000 +ssI200000CjbBdaA|NaUv_0~WN&gWb#iQMX<{=kV{dMBa%o~OUtei%X>?y-E^v7R08mQ<1QY-O00;p4 +c~(;@gXtBu0RRBK0{{Rq0001RX>c!Jc4cm4Z*nhna%^mAVlyveZ*FvQX<{#5VQ_F|Zf9w3WnX1(c4=~ +NZZ2?n#gjp6+%OP@@BS5oF5SRlLoR_p4hwNY4&4N2+a5|WiZpAB${Hb!o$kM{WOf^88+xs89*@3xdT% +U*D0aPxFpwTCf)6wqjp-ewi@*dL85INf2pjLAcAaqu=q3}$4d}QmM1mA%@Dvy*7Db_P4<@$Kdz{->7u +N-(Cm@f(A;*|FEsw=F4{X@VOR0;N}K|KX6a(@=Cnr@>i1YqW*xCO?#VlHoEMPS2JK1{aiO+>!y8vyxV +=-G__d6@fsIpx@L;})o{NOw>Y6CpSQ6Ri=8>&rvD)Ae$Hv}>-Z=1QY-O00;p4c~(<6WeLe=3;+NcD*yl}0001RX>c!Jc4cm4Z*nhna%^mAVlyveZ*FvQX<{#7aByXAX +K8L_E^v9ZTI+M$xDo%(zXDYzW9dew^O)%ePjww%;^aJ+T-;chn@)Q}LnI{OOcFeTl&yB!zrDM75g;kW +Zd%PGrohEwAHRJ7&}1@+mRl}KA+k1Sq^iY^XG{`GDj{-G(2{OgO`0ujNGsvRdm%PJcu`g4vfS{Joyh3 ++%jXa8Sd(ta4XbxNW#muWrm5ul*;$&4Hj6iEQk;t7j8+v>^UgAn%Clsl0~;#HjNSec1BhB-N3y(YYk~nb(~41!6Hx*-T;*Mq!bNk9wFHZ6d>tVAjMkLWG~v9fB-p9m7Fo@#Ba7 +WV?@1G#m +O>f9EHMXs5ZR*d1(6j?`FgL=wH7%rL*2os%F?&DAZ)y+MSgp0bCrt9a+mCA&B0VmcHxPi_ZOGRmz&Gv +=F^*>E|)jSkIUuthpQjRIXP+SmYqb=>zG`vk-x&9LL0g5z(wBW?12O0=+P!zn;B{2Yf%=gY>3l4jwVoHB9)7iWZgDk(j>77m#(D9+lD3jdlZ +>*F6>h-HXDpI^YxzAC47~JV;r-Gl$o$JBvWN+wCH7Bi+b+9{-WpVfsgdzGi&O-M{`|^Mx-M5hHh>?c; +A-1U|d`9FTY-oma&Z2uu=|gE@i`LM#S1A29w(fD_YQ#WvWO60Oz{&Nl0X?-(W80GRH +Q^&5}URmqJqnlL4d13}x`-haA#kXzQ@Q#nPzAzOB(i4nIRaUZGkL-aOAqO*s~$a#$||FWy26)@9Z2{G +zGKJl#>AV-AWvBAZj2h82~vhfaVn2ecZ=uXD7`x&^UxVerzpY%86t1yO%FW! +8UZYZV~f|aM)rAC9)&2fJ!W +f?zPv8L%WWku*6|G*7_Yt76GMF_8`)T>a$?fD~0hSU@+_Y16#{td}CAwkMYA<&DM}2pfx?~Z@aclk;jvH!1qvs@2EV(g$ +X%!cvCR5@R|kai^qTj#b<0doY;pKOzICs;f#*ZK0!fHH93BJ`R?M&hov7Y01;Lj;XS;<)V3}a!+`q1( +-bOg((EgypFkBevW*V-&3}CvOg=uT7m6PSw$IDM&mL1`D-`TvH+}+y@(O(n%rrel7Lt;HVHGk($1P_j`N3tKiMMx~ +EMuL7eyCB6Zc7uOdkQc$521;TcrYKtwI{3xv|6zU#EN(v)-RFX43-rM7t#;a?@4LQ_%wNRwKmRo(qe^ +Yqz?2w9*+KzlNY*#cVm!{!9(~dwvkXLnohVy$K69H2gNv#8I~&d(EiVW2ML54pFs9>metBzTx`c6-u} +swwthj8f?D)G18`vJY`pmCNB^u=DLu{ecO&TFcPU{VuU`>K;@s!QZ2kJkq?%}7aeYUA^E6x2H1qEd?& +cL)K(lUSoS7BOu`Jz}US)t+SR|jW{;qYw4qQORPdd!$_#eIokamI21m@ror~ocopLkm{2Lo +1^YI+?H&+h`+&ATj#MMEyVLSe&3&-P1+pYUiJDOn9oy7mzh?!u +zKeQVKonFt$b3o8@TPO)Tq&>D;iI?|vhV$~IDP@gF0@6?>J34nY)zz|LIqXUBjQj{$7+OsW{XY*%agq +zYne7+3K%`jL(%zJ57X%P!gV&)Ko{=|Omfoq|wOh#Y?+5MSisEW7IyDiclj_Nlta5BGF@#Ya9?N^;g| +?Bfr*5H1SdiB*UJvQv2nmKJjn2t6kWUJt2SIm%}?4 +09cOfj!5CR$zFvo1CiO>MqY_*!Bkc4oz+)lsRKn0;p6l+fRdO!v%7&i8HopH`B>yBL{Bpo=bB2JthWc +^AkT7c<@Q%$7=xkA$vazX$$WLHJAuNm3gHu>rKF7f3sq_0pt?W5aN2qjfB)%=- +)X8kS?};fYxwL|6cG%uWv5voQg56djA5N5ScBcSP+ts&ccnd*Xl^519bHi6dHss>Q~q7;Mvtp+2?LlUbPPLE9lisKQ2($*>w_EFWL+Z^OlY +_0JG!Dc7uiq}-O=7re9AFU($hs)K9m$rEXnnR~<%8hqBA;045;x$(Tm)Bf@VjjsAu(bfQxoxWsFw{3a +HG6I`p8vC#qQ2{Jv$a|KuB$#;y^I}=$9F&~*+bKIK)?k=A<{{ky^ABzID>hW~Nv!IoaS{K3_W +)O)rlHn`ZSTmVkekJrbrN_HQrZkSYRiyRSDGHB9!$Ujz1`hc_Z%jn9X9iSqxk<#odH4?gOMlbuTu8t5 +tdHU_8=>`)^K;bk{mby9n^d@FrKU;&felVy?-%yqVhCt^Ck|nKHd=EZ%yoZ+T5-;8yuP#ia5dR0eelN +R)oNCFH?PY>c1Z*)FZbf_H$&*y@8-_bLtj_2BN`+8#MVTTt*1{3BJn%6Y1O@5k|;X-!kultlgy7s0PXCb3Tujs4E?>(kuNC1DV^q4^`$? +9oMoR`cM@kZd?HDRzZ?0|XQR000O8`*~JVxW_~OR0aS5x)A^XCjbBdaA|NaUv_0~WN&gW +b#iQMX<{=kV{dMBa%o~OZggyIaBpvHE^v9xS6y%0HWYpLueehYRPI#8NmpPnU<^U(CBf3PXk7Fm2?Sc +AEjE&QeY3m@)C-?ygWSTe$bxhO|Dj#?}1b*rG;WKQLNZP$$1t)%nK#hx>7WO^&? +Qh?qixzA(bG-WO5;8Md8KKEC5OA98#sq`Qc&A6B=$g-)1z7d458b!eJk_!c7EqR8ANA`O%Y36Gp3Ir+bzKn0T8gjF^rRoxAsF>L0LS=corpL{67`Bt=t +JTNrQ(hD_R*V-P@#d{bN#c95r81Q4TbT0)9O*koamJ9VCmc*BExu3>{0ZQnw@N9zYUnHCLy7vpr`^;PKpF-mSwP}AgDe+0nfT)1vJj!;W(V(az=@yK{tz@3a%+cuZn}LN`sXtCq6Fljk(sI0A^@{LHzmCC2&hEx<%MHBublL94G{9Q(NCL>>L@n&_wV0!q87GTU3ItF{U0{ +x!!$ve{ykkd#hJ9^|0Y0C#rW<)~%xw<*2zxw27I}d&0dJ!DvM>$I|IKlWnG9N7ybey$7@lTRs*oGs9& +;YID(rK{B{}R1$&W+m%q38B@M@d+P;ov~vb>OpuF(v+ZVf7h<~M4R&mu(;Ca8yP*ZLwLr?8&?smf>98 +`f;pqMXK2o8{w&a=^O;K{0l6z`3mNdq4YS;V73_+J7$>V#f9B+9gm98`Qv{ZlvqJTPOnXWs3VruI|z( +g+Xku(X1u!|~>`V%Dlynv(50&_G>=(^ha-IUS$S|J6xa2xC=vEgC+L_9_Lr#?o?|3S$EvRD*G^HPUVpm2 +4w~%LDPP}X(ccJkLAT#};ou7U#GZ{h<`;H5b_Pv5#s& +2U25F*2NLZ|V{L*%f8-Mx)X6pr{BC0Z&Eo{Bp&e^eTqtE*oUfiO2en&zSF=yU{VR+|tM$;{>ybv@bO8l1Da=CwQ$NnQG~K}QX5BO>`1&h`BKS4MCm6PD^m>o!;J7xGV?cDcD +UoQc)G$6xE0QU%vZOgczjaiKohwdBmeFEMM^USlZ^AWtAX#OEw*1{V>2=pu>;Sy!)0NR7sVS%DCzqGYtFtF+%7~slg~iz9a@%@$OV- +Kf)%o&jb#eM+)fz$W8g=0I_n>XhyLZ|H=*OkCh5WxPj{i;3+h2MAZ4sQ_%&`tS;ftx&V}zd;a6Frha@ +euJME+%VG*05(#PgcMCFob1Y>fnSs0GKTO!o}NQqsP6z3H>L?0qpiW$V~pw&l9X+I?(0={c;kdpPW05 +rAm%0ncPgU1WmdPw&FSMgRE3IMVgJ&lvOjr2EQII-d03gU8gGCyxsggS&~bGm9q+M&;s-JC!i}wWk&< +Q8|{#ClxA^`WK#g3-!y+!x)8aUvGPBsq?h^KFV45v1hW@dG76U^`jptBlaB53|EGuE=7ZuiJ-fGFuJl ++ZvFvKO9KQH000080Q-4XQ((E8UV#(<00cq+04M+e0B~t=FJE?LZe(wAFLiQkY-wUMFJo_RbaH88FK~ +HpaAj_Db8IefdF4C(bKAJFzw58SDDNgzPG;=ndUH>$I`uhnqiJIMS$HFS+g +$)8NKvwL@BQ#T+}?yF7K{DH?gHxf`@Oq&Gq#P3HJe>cgOj|>GN}r-l#1Q#inUDHE1zA;B#~QItVJ$Zo +aTip!y=Yx&f+E8$&zhlnMA!Lz89>J>_Mn_xnqbq6sm}qamWh+z^Q}tN)}>>C|M+yyi5v~#0$k$41kgB +y+~#80v=opEL@B5K2BG^r$tjCB3urDcofA*5Kn*mBBs;9N*|D`yILu%oD>Js{4=55P5?;g)0sxj`TT4iZzn3sG0&)Hf8VPx7g@!WSXz?_q8_Lawr +zle2hnGm4ED{;sk49|4^L4Ms;P2mnN3xR>kXD>kIBU*bc8NLACzt_P$3mSq}_=Km^H +0tWZm4`o`Yc;Q#L{W$#vWD&$^Ap-+7*BU|Vu_lb9_Z9*iMUXWv#0ul!KSp>L@B9i3NoJOz1|fBoS +m^LwAB#^XH;>AD^m=RY6Du`=?F9gnMEuv!0y0WY%GJo*4wT{N``1HmT6h%j4M$)I@s~KwjmTIiq61+t +AO*YxsDZ?1kHgvusn{yRhCQOlF21_QO*|$PK7A(65?MEtUXgA&rwlM_+wrUPB*`}oxY!dstfR^c`)!v +O+lW1k*G!ra-epg~p(p9061^lZ?!%5mt`-E#vM7}Z0&9_ck;| +kkM4;b$J@efJWgq_Q?Zr(y`OEK4|9yYdU-Mr#yzmzwD~y!BwQDoWd`Dw5BZ2w!bX-0zZ4m|+bztiCD! +v{&{Oqo;k`oX#7VEUtR2haFLnsQe6&$ +Jc5{1ucYSgFE`UfBT)dl3uI_rhAb>~*UNvT)`W^APKV*IJrBJYy28g%%{<+tChM^Q9;g;`m%CmC6_x5 +}i;MXy`Q>8$d_dL@wGBYt5yRoSgZvPnss|83Y;|k(eA=C-vYOquKy})`I1h<^>%Q5>037vRz`5C!>kN +-WqdL6u<+`YYiGo%?E%s)B@-PN7;nJeUijt7aqi*Zl!)NEXCF1)v0BkoApQ? +FryhgCv^r8HCd2!{RwcktY7^C`|_D>!>o5+H^H>Jx~S_-S%Gz5JO5hE3S(o4e`t)%m+l_?yY(wgrDX`Hv6N+sT`Q@C5#C*XZT>^j$E$1S$w +HKzwMS6+gh5VY1>3(uTtG9)|3GK=t1JkUiiAaZnmQq^q0U8w}9RtwU3)B~whnAcqi84E=?YSBm02fCx +U`PVXkckMFK8{xX~XZ2~KwegFE0?|MC{;#Gj29n?{T9NoM+0D$KLvQLOi76!@k>(8J?2&yn>LaV#95v16tJ +>ngLXAos=8WeUecbsWAH$DR!y6x>B?^W0YCCy9U +}ow<0hgva6$>L0-96P~-ZlG~ikos4LXtsJOrc2p3hvZ$f$>aev7NRJ}ywt)RZq-eA|#^W>pZ2o(i%O7 +~0aNOhpN+uE1ur@*;NqBu>^UW%~sWQ-98wL;wj!pYRJp|hWdECYC$3V`Ty3gy@ECO|x6Rhx;O` +e)zKmdFu9<{R_}ASCMId?zC0}TLFryVPF*GYuR78Mvy5GO(auXHa1BE}X*u*^P*1dc_uy3c19(rikCwuRn6nRemnT0k4;j~D*Wohl2-3SKMDs+_Z7Y*M$T%{)5ZE8Y`HkPnlu{_&~ozwjZ9lzq +Sdxd~|E4$|g>BY{g|uIA~vgMC>1BlV5xpHGQMEifB>?gTd$p`nb(ygtvvIEI4dTjoeotGCxN`!JB{O% +OmRmGGx;uFhKxQ3nxqAxvO@BT=_GHmIgI58vaqjIBmW50ordy7P|sm|j}>Hc2UU1b);@7 +)k9Ju*E{-`(fRolsHTPG~eaE31X1(EhRRdyw^i^>L|%kjIVv!>zBpyQ>wkt@t6v;9-s~SgzwayjKC;xIil>y1||q&InCd>pF?S#_^_vhlMBrA3ML +9=49pAVy?7eE8mI<*h66-auN0&dC`JnRzdqMSleocOtYT!OnWf=zYyUX0xNaT_2EIL^yvvwH)N19*!V +_gYCKdjCjmLglz@zrLoQKuY|Jr5E$0>HgDBukGv4Cc;l%BZdaZ`*(BYHviYi}%-D-;zfasTy1d%Rg=U +1#s2Yq&)-#3FNz$TJThamKMaq&Di$d)`u{Xk?(0YYXdma1<**gq}N#RgGTC$Ij2Y?SgMzdKVH_|yb*4 +@U^P%5tw{aVbkYIIE?9CBaU?s;aey)=5wZVv`j+NGtNfZ#(Cw&+bBX-bG8kDfQu!sMa1<0A0=DVRO0z +@@J6>#S`T3ZkGuc4ejU@=k(VBW-SdnJ-1s6c@GsZOB6tTsQCAi(mp(Su?}dg2UVVz8{wc!C0T_|$iVz +f5?qTWkt=`8RSGI^C89MYm_T79ETNuM!Xd>ya5UQ@RAMO}5w~(u+f<;fajCO*ZL>!*NaqN#g^ugPCwF +~QPLkzgF`(P-NcuNW5H+SdDj9Ux$i9b}u`1KV(?O0q)|YszaSL8gwQ@_N#CS3N66am +I`tyMdyP$M54B#0NWY8e+C?Qj8p_&r2b2#Y)9%JdluaB@lKpL+Ps62i6Q>PNJd!5(=jF>{tmf) +3pU#4!#K$(~dGT;-!Zpsn7`7&bFp-=#sB^W{w!4B!^7C3wYkH8nD?E))Z+BKo@;A+8FT5h3C5?BC6AO +vQ+Y}ZkK1dwmVMS!Qg$#RJGm!WBBPRxdMP>$jW6kn}G6bQM@$Jpg_k`1~vQ%}ZCuXsO!d2fPR8tPACcy{V>S&~R**O)!UtmkFSuHPy}fvGzr`nqz`Z&Qb`g%+Y3kl +5WNkF^ry+2VD|RI-J9!=SN|O>9V7oE>> +sy+N8Q9@bf(_*mP72oXsQ6#bKGUv@Eu^z9NK$>E^Uy2Ekys0?&0tVFI6agi3!Id>|pc +z#Kqynct5c8U|o>!W-PuEltX89-sL&KwcdA+yz7;prVfX}jqYTS8wk_(t{FH_WZ|_n5Ki6!=-wSLs;f +>mAk=Ov_<4#{U&wd-{LPXP?Z_{~M1zV2%Hy+_rMayq1;rNb`0)pKxYbcqc2vwp{>aWSj(eBh(MC|Y~R!ddoXzGG)_zL82OKct9q%fpyB8ms<$ +NR)<+==o&&9L9UguK4 +X{DzrLTKZQ+rzdwWaR8-aP%C4`Syi0WcKZYA)pNme1Xra)(`Ps^D9z|NlF53RDGy4TP!h;E;;>9Y%ZD +8d|a#3Q@@!34jO2DlBjJF!(@EO8O1TZlTjQ70cayuHZzjR{>X1U{719hW!m!n5%WB+kH005DK;7UdcJQ@1OUbx_!n1$A^xu +u$9YoF%u&ZN0?|@W`)92g)8{@`PqtN{WFetaw|CU^3hrK@tNj+nE+hH2VI;a^@EcvGQ6~NtQ^osy&JE +c|@99=#TV%$tH&8=kCvBO>zl!n6*;7Q&lDb1i7t)~)F1n)ugcFxfxd2?<=N)%lTD`J8DyzMcv>cm~lJgEQfysfwq0Vt>tmow7oxB@cz|neW{`n9plEs +}%?zb#s#he-`E5=zYB9WF4TbS^FJNe)jHh+QH8)YY2-kt=pVds12Nh)--9hs=dAu{W_@bI5*lyf6|9m +#jfJapgHFKb}56xyiqnNR41Y#qpszi;ny_RM?(!kAW{qEX&i?L*B3Yg^os+;AG|60sa}sdtPZAuIU@L +z^Rdlod*wsiT7ERVckmMGCz}3w$Sfsjl<1z#ggr37Dzs3LwygK`)?B%BMhj~gXKOG~+f3DO&F~xN^mC +##6rLF$;w^n2jg*?wt(?#RVMkRzRhJCh!|#1^HFSXIdv+m**hKQkazaO)j=6;#!_yes@A!g@Rxg-y(o +2&mJ;iO@x0v82`00m(ED$PO@E9r@ADf4RsP>Y+T0Gs%Ytk&o&FqfoqITy&x}LIx`SFXVaUE|)d`*2?r +gWK-j;*0mE$|QuvRd~jsjF}$A`vJE*E~+G`r(6?#J$)#mVm$K*-90))n79WX^NZBX{@ySdKPIz)fVGn +p_&}j@fkZuJB>*>JKP5UD*WT>@u0ahIY9@aDIlMPm+;LZ;_O8*s_=Aq9kOrkNPy%t-$sOZr?3`5BV_O +-ORFR>Z|#Y0?5Mus;apCajdJ`2oR)!?=@T#fHEa#~xgExvEY@r1UcF>ZA0`{-z`|=VT<5+x>b&V69^& +sFbs16*MKY+`Gy$Z!CJm;v%RzAHtZ8ozyC-Ywq*3?sOWKhKn6%rggrLv3nl+DOYps>`;`CFJ!~;}QJ= +XZLXPM2My*c}EbWF8o3y?j~i4$}|(Zb-Mw^NKlU}tR=f?w5ULTs+8A1{-SG%gMIhnkmYdZfXx?<4iW2 +Jm9Up*AJ4^2E;4<1M9Tv|e!h6_b%q$2kE0Er7%Oy=u34{0yS54A}fqpqcV$2z}dHC>5kq{~G}ZYkL}Q|${u5A30|XQR000O8`*~JV!0)=_!6g6yk%j;OE&u=kaA|NaUv_0~WN&gWb#iQMX<{= +kV{dMBa%o~Ob7f<7a%FUKVQzD9Z*p`laCzl@`*Y(q(%|pq}l>Er<)$N@tm9a^n(P%XKg>JCh?RKUzEHgRFf@G6sg~-bd{ku#vQD +$)*%}>Qc6o|0cZDj6sIbFqno0@|&Qlc0zmq?lbzMeqKlj|>XHQ_N);U?1o!Zgv1C5jR12AKb~#I9bTA*m0dc +M9Cc@0Ik!t1n7X;!-@CqIL(e%(cMaB0#JI8SszAO5U_<@24!4`$wnsA=~bs#1wf5ew8;ryg*re_(n5s +6hUN&EFH#&!!AIobX%g?mHrOEsnatDpK>|3Q6g;{^S}`~#3`{hL3z;c0LWi=5U}De~G23J*jJ4LN21T +c8!2ppS^)lv2GFYfDl3wt_37E1;Jm^>fS(kZ1v&!WEDx*v~ixCvB^N#Mhi8h)BK^&)Bx!_5<9fbs%L4 +Uo|lrjIR^?BV!MjMRc8aM2EPM&fcTj#5PNDLrb&@SbKs#x8B0&RFA;58hkS(_199wQ&qUHhI(jgP ++zQhp)NXWyc+QntGl`>jt9*nM7jpVpBJ6))zSp3v=eJLOc>Bf0rS +QTAZDAIs$&wJ6Jr=2P!Sn%u`#_Q-gXr@lT=oM+Cqi_9KGiJo9)cABdUy3S{r7)A<-|HapxDK#V^q9th +I~tz01HL0$yeBFce)um^KoY}iRiLTa>3OPYoFg6sM|O%|^~dgNiY+W@^`j4a4@o){!TCLn%*fnfK}iO +iZKSJ9dleI5t@panq;0PQGNyx@t;G~-R4$skXYzFz+Yt#lbLkVSz!d9;8v&^#Na5A@eO1(`74X%PooB +#uIvz~bNvzzdD<>l_xr^9bLzB`8heP7R7{D_SG*X6mroQ%S@773RgjP2kZf82gy!sfX+zfc@abjQY0W)A>hEYCyB5uwn4^I2=Y7)qX1x +nhzrX#Y_5Q|tAs<0xPC1+Ww{b6McFz=yOVj2g`8PbrFhATB_OU=E7sG?q%hrbsKmLy?Te-mcO$sV +jt3%?e1iEVBfLX7*U5KtdXs_MZ~p%M=AAP7|1E5#j7|#Q`YLf;n&$&5L;fLE>Zf+-k8()krM0YTSzq@_$Wjy;h`8*TQ +;&yO7`&mpb#o+p9@zePFqAy1O}OuL%kk_QVP8&e#X#H)ZfE1+=c~c3xcPj0GntM6jSB#MJ-)uYg^@;|M%O +bBMuul%^f&lJOg|2;u5dVlH~0)QxkX&XaB}nW?fApTnfN%lx){O3_ane?@cwGV!@&fHSA+4VzPK2C8h +jX0uL(fnd16z>RDAh3!pAu70R9`!#*=HD(r|J;yM;gdFyq@<)&0wOI_itT?RW|(^E7WKFbt9ydQAur^ +t~Q2BqYDRO3(!V{yZJoI9-eeR{(R00L%_vrwhj3G6O#L{bh-^l=heC|3HR@2z+`yQ*C`$xy>We=DF@VOfz|jx+!1*MmASe?CRf!u*Zsh;6MuaF<2 +?FhS#GN^Fh+etnic62YRL%v%Nnrqj=#LaqgGNeUfeyKMec;OshgQ2(eR;Wx>wJ0sRe_i0Tax5f(FCqB +KnIxRH7nr@_Jx;|Rn#LjXMyCF*4%5iwA%{=M9(7og3HYiP+N^m;{b}C3(t3(MY +KxTQVMl%q<)#p0dNabQJ_7ZWv8q9cb8(#}vL4I>mLuao_vY{m0?s{jPOy*0LBH|!i%!P}O2)wd=i--c +iwOe)uM2#i1@N3`tDpbc>AVut>oJI0u +JL0#&jsLu&DvtKGXTJAWq4d*Ys; +bl13E%88X9G35Q1U_l}dIK@FAF$uj3eiEm9D(;FkeE$I-on-)pl;JAnDRyt|8~i;9B)0s~t)EYOTqmI +DzWku>L9xwKIr#i+4k>mOG_i~-VJDRb~sU%{_mDkEK>-32x@8J2A8klaC!5`jhuZo!;K0c(4aCa(+B= +MfDGu#m|4pSG~cLAxp>HO+H&``C^|`jXLpmqJT0qgnrh%14yo=LE=BXpd-q{XfCjVNv9n7oNINnr$c(CR%W+- +_`OImu1dFU-cN%R{WsETK(LCX0)7*HMri(KXEpb6sLA{JF_2K<%Fj6z{Dx#46KOi`uzD)ocdC)RL2@@ +b?w6Tl!xYzN+ipzY^Gdb)+rEqbUVhyKS7PI{AhUbafJV +$4t%tv{K07Izx(cc|HJT;57r?}y#Ji8C+~jHT>;ABuD358^Y*198pwh*4gbA+>)%YSem*&U``z{&i9`+TRr0N{qQBLYIJ=zH>iHk#n$NhOfGur9QSp6Cht9Ue)~E;i(?)i>?JdM;k;fltEaR6)4BZx`| +1UIs+u6Q{NWjGpNae9WPia*J(nT&uPRyj?}J=f6IvlrpjDocCa%0lP^J&>PEiMgF`1ygCJQbQXo93m) +Tekfz#%n(XAh`JtK))8fCVmL1`ZDzjxxp|*B0aKD*hc5Iz;MKu+Wz9-eE{sAJ?GL)u$^bZ@nMB>G)tA +7ipNrKDagOC;{i^E2zl-O~fEYZaTteqGuE69vyrT=t1pN8m@F?|MKst!-abU$mhv;r@rvye;CdPu_X!!YdG`k&KPj4_NtHL%mi2d!DrfuONm +#Ci;@NL!xWl_Y>cj9bWCgIs{Ez9!TW3+6G937(M;r2D18X>E_6g|+gm1dU#=(ML0mm4y5ee|Y%?cw(f +pF3Lg#UdyIe_k$^cz#cRfQNcltb97vm6@{)t_x|!%W8)lpKwrZI@5>RGA~#M<0u4dj&UAT^-={q=wz7 +VXx6AI#O14as1douaB8)S8_nE^gm)(kn*c{uXN-;5{b3$BP7NP_7-2_X$Sebv7$=l>5LfNzK`v`#Z0M +c}*L7;ZL?URw3jjS@mgASVzt&xfXe}{5(0~ooRPTEA1SueH(vC+(-bl*?p}aLfn{*}Gt}qI0H2<-IvE +4z@RO6_KiHB+W(Vt<&W*^lLqg=$~+aLnTkY>cjJ%glu4d|#H2u&}D@`|O106jWq@kWCWgYh+}8-zhom +hMrf0Vb1cb;5hzT%Tiu8jYU3qfis6WSbv6yF>g@2K%4T$RbapGW_Y +fQ3OeFO%T|3Cf<{_BBc^6sQ*bn{xwZ+fq|tvTAW~Woxhp)^7h!8^&oa7au@r$YmL~LYTu4_cibHaY@h +Qgd+?nJ80@XB0WBivt9!3{9eVIiJ#FI;irE%7bb5tC)U25iM95wYc=M11s#tvZ%5P74EK`z@@9N9YA5 +NVwlI0CEzbsz+iN7k{4*=oC4E)9FWKY&w&2_Q^NCIA#-#vn67(Ou(=(em6GxLh`8ukt +7-#1`lsq|TD7NWZff|WgUi2IW78P_T9xhgR{DpjVro5jw4h~a_Mz6bc9W(E(?9AcXN9SJm<5XmE-|QW +N;cBRlzenEe!V7I$K4|92}pqOaT +f-$u>Z+yQ74^8hstZLI(I6q}U~a4+Qs86D3Xx0o@3u6PcRK>Eof#FP6tdXRMdOvl_H-o%ID>jjG@I@? +sKxH4&IxRCN7f&%ktit1=T5a{=FYO~4;1|XQPiO1z@xlS>w6%($-RwYt+o>#8mfAe7BiJxV#>QmR(Ic +HI%89aUu?>e@_tk++MQh+EJ7Py5rmw-agZhh2o^!=XhU$7DPx7%Z!DU1VlC28UFU^h70$ZnZ9e(RCq84S%V>=0Bx>?nZc2^!eV1mU`l`Y5zPVPU()Q{+{q%3NsmQ~qB0s~N%Ly; +Pw{vorpwhI?1#&veUma7^7EzAc)D9a3RTTq6DPBL0a1DG07CHHVTNh|}Ev#;?%G?XQYWeu5TIKL<-P(;Wvq4@rc&n_rz +t-8EnlaZw0vjT$Ii6IE4+bwAH3-pQc)g`*TkB*|p@8t@u)Ja}Ib99&s9`q~xl{qiA{FC5O(>97L2@fu +#CU0D3t{^@^=B0!6NrlaIg7u7<>Ex^7N(g>Hrs%tfE7}yP4ylMmW5;)u?)hZUIOCC02dKY12HWIla02 +4PVffa$PGBTSMQH87R+EWR5ds*y3CFEq2L|vwbFObHn;bm{JM#wFhb~@xD`@7-^_d4MUpfN?RVJ +9h7w-&cR87N+WYSk`117raNd*mc#vEqnP~IQY#DEtWWF#DHgT$WPUVFe;=O#g%Cw*IxRYW8z_iU4z4f +v|7A@gD5_?)|xA7cNh1nZ$BlcFk&Qr$p9@2cuDuH3;@(Mylwua+<5M^m?N2O!vz*+lweBgVyITKg)3N6^fgNTmSL-2^>1$ +Sc8?J_ayTf6H%WCEm@5OK`uhnyHMqLDt~+Q=rI2o?t#WUxPHpym^{8rv$>J0MY0Dw+)hO@x-S;g4PT| +HPK}~dPczbK^rLbhNvbt^kQr4P6&bwt=Ox+~dzNd+;j +1kgHI%XXz`I**K?NAGK~b%<9#M{N(LuD>A +qPFsXwXE&#rv9PZc+A85h|?Hi2Y|>jHd%$S|U^LeJbh|YHCyk3Mm)!zW6(iod>%!0G-C3%c@N5Rz9uu +U>SneM1;)~bNq4BAY?d0Y;TEOJ}0WWKrZ@E?*;4{O0&lSwr38hm|rO)43^+NqPIPZi`>wK1pwHhnrSm +=;A~!gL(}PZI +N^unrn|Cc*Nx71->Q#O?56%{E|2p{UwlDZ^4mCPb^AEL+r1V({sujD5N(P%M +U&xAqnUPA;gOHkVWZnV4dFm?Eh81y%qa(fBtx%@dm4*$aE82J!A1sC7{tyWb6j%f22-*+n}7xD9)U_` +ELo8hGK6iwaS^I)z}hAB70Zn!I#l7A7v<A4+IR!G^IK^+h+EcPZMqZ0LCYS!qzy@NM=bAVHmpLMZqh)@wA8|)(FW8=vthmIbDl`wV6!LA>GupeN#dOz7?ZGGGNhxR +TQ()WnZc5|&f(rJBPH1?nlu(%jrZJjZ?$Q)jnHI*z1A)>UE`F*5D|M_?SpX5#dpK-=Da;REUI+@2I%r +ta4&1qjnskh7v9R^ufPpWg0-w)C_~(qty+LrPcg?*se9vL@U{8Sh-fw*-pgIvGk*iiwpnFCZj5Mqb}H +|3Pl!yPT&r?4mgIP8PDPel7-c2Muo5ixKo_;mYm>tcinG+?;!AY!=T)Se&nPlSmf1G@rE)ctOM)?>i0 +%xSM@7zpurhzZ#sJ-Go};jIH^2p=5`P@o%{jscVpWOfT&WKOZY7(XXlH0=gldsXW2$6E+Xp~4Zse8IF +&Zx7G+MO2ofZIbjXS@^(BJ3Ijwg5&axj$8ekjv-(<}6gXNMP<;vdxwG1Zc!>rJUbnswDJ@PXxbV(l=8 +XzXZtgjdnfJoti{RJ?oh=JZ4yQ{C#aDuxJRoZPNiNs6pwz$2HBPTQgcm<6v9QCYAU)i2?%7?EE_R3AZ +?m*imY8k|5zTU*(sEw9v6&tV1}r~_N{-O0EA@$C<%-+%ibwi!s-1*!u~z|Hn(3c?C_PyP0dI5{wY3-A +~2^{es=JC8C~UHj~q6b$Hj3^HC;@rXbPSWyXBe5LBPd1GQcFikB3+Y!nQtwdKl)v4T(C|6Axqph^}6@ESASVJi%}w;-NWqkz#t_HNa>lDcTfmV2g1-=b^2=pKE-e+gqqOo{kCIa?Xk}@ +9Akr0NVgKK}P#e7b?L7bs4Qo4iAHtq~B2=DQ%g1k20zb=>3g*e2QF#H8k>bxb~^z1z+9qYGwcIU7`|S +V3+79nR(F{ojXQFEk*h44t*6fMBWT?U{D<6;es?MZ9mZLD*{AkLK+&MP0~q<0ZzMJIx=bae*s1F&#)K +C*)1KyR2gy$y86jf&LuldQKC=ha=9l8M=4aM>$}%b+_zLyNw*eFC?NFME^JZCX7~3J4BP{km +r3<=fI)a<=n`ux+buQpvC#ZBp3B>3!#Kb-dnk%ZlT0>_2ngbA8Jm>Kd4s=B{DV9w6Xe)l|D>Jy(*pqW +#=;VeO2|+Y-FtF`Q%R7S+;<{m07tFcELbp{FEZ25|1Mrket_C!@3C3SE_> +ZxHgngH3`j@jB#INqUP-eD;JVUN#K=2cSaE|K33io18nz~ +sN)@wAm^qxk#D&f#lUo=R2{S824w4P0?(ZD?g&tknWnBjtcILfgV-2)(7=>e +bNXnLcdSDi<((w3z#Rm@C%mr`PjVL!-)uO2mVU~x^d|UbCiS0G_7-zjMPw*XI>$JcNrO=0ll~+lb&cO +zm_G$s>Qc%%27TJQ^;86x^wUrjO^{ynGb#Zf)IAAs)_rYO1ybQws>^{CHDM_X!kKKRdbToit ++tH(NOeBhDEe^oFV$}~j5`~~9t*~A^rJEl9+iqc>VE$x^$KNoKc-k%;GhO2Okb#3o*|btYdlF18A8aO +Z>z+il!JDO^Adjk!u3l1z05qrLruK63~x5$QkpIIc|Nmp#zHI46loE}epO>tF$Z~7#>*e;Wps_wq)so +^6-d7rJdv@0*B9BaEe@DL5*vr;n?z*ipyt8l4M*YR(B^UV9;`2VPcK14?riwz-CDgf5;3`CTX +oIDX>vJkBu{Ou&>~4h;ElIIpu$EJ=j5Z%Rf`&qETb(bRwKhHRC`(efc@sLTWR=DXo#(F-&iLM6ojhW| +5~4LH>=oW&%7dsw`VUsjYEbZJLipBK{Ozro(*kU;G=ZIh+^*jaulro=0k932IG16%UZ-^6V5%&#?QEp +kRY#kUW4>r}sVUQqwqVgwkQel-iO9DWBGbXO6{I91b1gZ#8{pBcSYx70Na3NcXEq8!O(AC%bl+)i5`; +CrD#f#0Ap8vzyI5@rfL$Ihsx++6(7SV{##PR(jFkI=8(kla;DVVzV^QJq#nD>8 +Ji8L)OVims_!d9go)VRXXJRdklDgS8SNcjimA&O-;^hatr)GLA=pNpo{bkeyl^R%}{%~ky$`TonSkZY +n5MVH!!W)c$(hs5u$u}|S+a8>v&vSs3uM}o?6P2P$eO%Jvww$ +_uUFzmo0sZbmlr*$&-^*cyu-V_uVDj3cXyF&@x)ol*mTm=t!~(9tb{F4huBOf8nsNKhSfHc0iJ&a6t! +SRUeis*E6i_6s=J?ghSkJcf(oF9=xv(b|3y@B{^T!DpEFAS$7ie5hfer1=ez5zz;7s1^^rQvRXFiZ-Du&n@OamHuFzFCw03_7#k!FE^w_xNgfy{PfclSjY2QlYJZDbH +@5E~Gx!ljp}LPa%P`NCp~hK>Hk1Ip>V*PKRds*e6{Mp?luVy884A)WJEYbG`|(yBz*`bHByk+-eb3Zd +P||y|=Uuq1Ruc;4eEG(5Rxh0;6jhNbE%FA`vjU0AZ=VEBNZ<W|x~yExh}QEk7JV=RI-m|^-R4hEp_$#5yDKET&!pADYIDl3UV5uuiW{?Z2Q-~!As +EF@9=#BaaVcai?~n|ATlEh$_DLXcWrl0!FgR3f0k=|)<3#r##C80OWqUDT_e{pvtVxwyaOiVl@J)qX{ +XQ%H39mW_iA?q#FY$bE0*s<$__vjZ(}>8}`t033F8=yWB+`zjmqa20A#MV6Lx{Lbkp$0LD=WUCR=l1aJ77z$meP +<^;?dZj1f#@6^-y6ivdSW2nB$EiX~B)FIQ7#>E&Z!hg?A!e(Bbsv);w=0QBg6@&Jszl=t(KyUzi3zn7 +>Mxhw%mr!knO>LA8%q3ywU47jst+jPcO%_2;94{pM@CIHU^(TZzS(zF31-)DbUX8JNB{lv_;z$r+doR +kQQ=+H?0RxNQllBb-PyY1HfQX}QQWpTa%E;V@gh9b-O+j+%!XDsZYWqjR*1In{WMkIeZ|B4_CV@ZMtI +GZow%}9K{vDw%vc@yC#$`&a{hR>3>5eFjjvnP*Co-Pl<28&kJFITLJUZJS*Ak1a*WDEOgM*2A%OkbzPW_`MJ1{ymSn +LfV)X^LejTXmly1yN;?fzHcly6RKgRc1EsQ*&)_$5QsQ5#6kXCGrgsT{8|vCJ*K!?&Go}n-k6JW2*Z_ +EH1p{@7ndNL~i{-ZS%wA)}1SMKC-$+-zZ!(F4w8x=$le7ZEK7NYiOd$MRjKifsZWa*>_ip3Xbc~i0J4^pW-6!W +jS3pe}`DE1&L<*PJ_Fva~MLF_E%(lIq2lwH+F`gb45hcZ}=sTzItFu;Rl8JOsBUwBHs~9{%UIfS}!EJ +nJV>qZym6e0;KzQ3Z-0}J0J$3g03-ka0B~t=FJE?LZe(wA +FLiQkY-wUMFJo_RbaH88FLQ5WYjZAed94|1Z`(%lJAcK3K~Tw%=(gt+2O=t3HU?m|baRJ!vtt7bHT +jF2yC_n>%)hj<%1snlwKzuss32)yZ#hRCQK;l|$*&euRYLF3oqZluw2>M8(D8vbkZW%c6I0)9AEpBhg +pyvdMZobmeUGezxGK!+x+hq$l2{!_JW;CtgC={@lZ?{uu&kl2Vg-Frkb7(%+I|+5kj~E5$F;;OYPomD +GjuO>cRFepTC}Jhb6UPB#WGRf08d7mYTW63lC0HX{*J$a`KS;=~Xmj9-ObOSu_OEN{Il90!E +^RQ3S5E9%yY*G}@oYH4`P6uq7Fdm53H9b8mwQct##hiyQ-CE%OY6=Kv+0jS?^b(V57GBdH@E6-G9|Ah +JGcz5nL={z_ZM1Cw18I^VZvAhK=R!70c1-rqgjC@}NL+u&BI@|0tMv~JU+K2u#C&d(i_@8jo=%|Lg`#X2&RWZzJhND@j&lzI71$1o) +3I|x*L)?KrmD$Dfz-XIuwhaJ(&LNC$Nz0n +hzxNsE%!__593Y8qsqBf&V(A-6os7zjGVV_#&M8RScGg(2J@6jVgf$87o`-rjEm?(~-CbF1(j+*KUJq +$x~2c;^B&Tyrw#-onnWFnPOE-syZOQ8>E?4dA$YN$|ssGkImk=1w5 +-Cv5&_EudAl&L@?XW30l^;Pgs)M{S3zldUxdTHf&Y=8sb94QoU0vz#ZMQH+tb{10k*{%QM1U59*$A?5 +R!pteioJzXhwtecMa28aOMXWQ*iaz)|29hw3R8Fyq30Y(#cK(4%qC8QEN0IF?y(Jfi)A1Pg0#S+X!1) +Z+oG@k(%{kQq~9M|RbU!kE6m*5D$KLKE;{yGviqF7>}8Td)Kbzf8SL?_=Vh*vWFev0uh<2z?z3fW=M5k!+TbVHVWJfUKL{MoMNd!xL>a-TxNvkcRSjC)7hVg45Op~4SHYXUo%piU>tS#O@Y59L$$* +E{NMWV(mULP$DwrI>&)sduL8CsTT~$s?)|#$geKvzL0@V~UPCSFo9g6eGAyu*IQXBm6K&fXp*Z!0hW% +HH%HMJ%mmB^VqIJPE&BZ4{*0-bw&caJC9#yrJTm4ohdwfS5(lCl7!!ZPA8%)&e)ZlX~`7XK0S;WoY_p3B~mp+JqSGc;0`VV#Uq7Hectq-MnFgf=V +a<<$`lr0YF*Eh%Q0noKEtK%nRcyvzJQC}^JrJQXE`=rA>kp#$L+xX(VK;@IfjL9&SAFL#He?O@@q#tL +eyz7KTpKJBHoD5TsrX13kCH{*wegtk6-S>Lmos^WDEzD6dCGk50Hacf4o9}TNzK08gPs;^Bgu?PuP2) +`THw{bPVmRsR}(|=>z=>4utz0vdeeny?td_E^!bvK~0SyQnB6pWhY2fo%Q*~%#d=rNFZv1j@3+lyr7; +Rq4_;QLy7cKQP&8A$wXq$O&XhvC0``Ei}0kPF@x>Srr$FcOy=GT$sK{0vg>!I#b}hEIYo~$E}vOB7!~ +ozZ%qdz#+}>%z$P53c>NxC+}JP1OBTCZd{I!RIbb>_yq%!DeLU&mgwmt0D~Fv27T#dkF~<8CS3@a +f|lr`4Fta2V}kNV+!FwY_LTYk(uYwg}^}Uki|V0XXE)?viab?Avi=aMG)b^R4ES9 +X%s=&J~G~ATyxB{{T=+0|XQR000O8`*~JV000000ssI200000H~;_uaA|NaUv_0~WN&gWb#iQMX<{=k +V{dMBa%o~OUvp(+b#i5Na$##jSF +_n{EO&N5_G}Y5ltcjyh!wth@L$Nt+IBa@BriJ#ee1}zc7$!o7Jh^_a|2>Rw*@~!(+vZ%_1V5*P%HB3( +l_0}V}95r%N-?;%yh_(`jpt5uIIC7dqYVGeGYs1=+I~CkimQmouw24N=(3}K|tNmHXY}U<7zQxg+v|v +YyjZf2SE0G8isG=(Pse{;)VH7^+BJYRbOQS*HkH)+=-+ +tVMq-C|qX|I3r5&pT%lqCOrfuH|=S;OzHOT&K&ygBvvcEF7U^5HPA(abOcbkhN^pcIW~?M(lXc2G*we +Ip*26|K4sP1{8Sn4FdcG6+)xseo<67aVt#*bD+@*(T5=8)ij&a0C;_oNR3eytRdc(==j?X1l}50kaJS +--DsT{-m1^JB!e=LFn7ucRB!MInf4XNxB_-ZwoGh8j#=y;{F4%(&6%UFna7Q{!h7zUbJ@XVAmnwK|p> +Th0qvaBOd@E8t8Na107H>E>N*ShuFmuDY^Uda6toyvB`w*?${n-Ii*x9FxfWpU0^M|VZ*?Lob!LE6=D +mL-!cbc4%GzT+1Na8Vh_MrHP_%kTrc4ZE(<|yAqozPmyLGZ73?4&(e;D@DMp4+QBuuEF?d))_!10k9< +Mo-yRjU%Ix%LW<-=g1ld(bOi`lpFP#==!z<}>&hdhjzx3l{t0f@z5y8KRNBQltNCqIv;!w%7ZnJ;w1A +hQK0H@=%s#yYf*r`qIxIG%nbH-I~xEy-kjH(mna<%~0p$&Gab3Et@o?G`=Q>clvbM19v{z+Y`So=s6DZ8lvl;Iji7FP1 +6#!`RR}WUv?;AhJ+%F#|SeV!)blLV!Ef1tm1UI!XY7-}i>D$~4pm6QF4z0U4p&Xu{lsQwCYBwowQvf3 ++g^9z6qmFklWu%}RVX8gaYlm?31xy}*m-skpBta@cS(5#ywd?x))F`&<{F^VxX1)EDBzTCEv#(%)|(O +@3pRT6xKr?S^Nk$%n(-$K-cl|4vt7@)ap?kqr3E>AUNh)|2@NxPkqepu%hs&=sF{8}>G}PB(Z9Lb`9Y$=|L?b7=Yy?9DG+AdW%C99{Q)=C_(N7beraY0V7 +GA3@|{AKf(DjYcx{U1y{L5G3pORFVyE%K`L_xGZOhJ=>8NmQ{10OOXdi2cN7tR?f=&1z@TtrL$ +^?nn46Wgah#TG?jZ?-$}H&jR{hJBXNJ32Qt;jh0AjU(K8F*k0N+B^!{oY#0mGF08*Xp+xkEQ!vVhET&$l)^FB)S)((Jt=2G+_UccHb;#kk}R|p@KJ%JE +@6`MILQvqp`c8ue<@1A2b+H_qdO`3)L#R;dPoNp>Jq{dtRr>F@;v!2EK2;sZDoLXs#Vv(lZn_91fAvF +8oMoT0#acwv2~DMlOEbwt!@ColG1p(bh)EpnkH|ZByM&;n9hP~FcdxRj4d;;@-^F>Vk!VTlQO@61EgqG>W+3|z|>q>gzO$Ql!a^7 +VrtEcZ1U>5Q1>KxGKJu!a;>-!=I)&D?2>b)=>=w6ey;It9mSAB0K#OMJ87QE2XiKVVK81fD>~M#kr!Z3I +hfe042W^=nn2A$Jm51emWqU!Q0*S+tBqU^$#442V253IX-a$;m`b0#X!ai}oPMRu9_?MHBMM&eh+O;w +xO8Qi#jCh7u!cR3*c`#?QN_7+-l&4N$CV41TbGbjqJ`J37Bd3&aOsFQ?s +*IE4oq0_u&-dktJjx&ncMQ60deHn??n|=4FWC*OzE>_tM(Q0tFl2j36H_R&{v=1?<=zYqSgmO^`PPPtz9=Csi0D^8E2{eZV?=rAJs6!fR!)df%Ro+bb$Do=UDxVa*{Spa +!l5<$+W$1o@IaVSA_mI!N!2iG#;SC{j=^Q$-bYlwxh6^k>tF3YDQOLUD+aExE!-O<7WYBOVGsEU2dE`TLhK#8CgARTxf+K_)v{38%8`R#-umq1vVyenSrz*#b4hMFa7*V- +W$*MzX|2E>F7{Qjb<1ouY2De!1WFLfYwRMmAC$v<{WIk-Un}KM&Tn&M29xtv0CuzAx7#^qF;xzj7A1R1u24$PX|NkvMWs5}mDqlHdaOr2sWG9`fs8N>(l}>D=uOda|1V? +SC+#!tlD-D%fUMaBDP@W!B$mFXEOgi@|HWrWyr*VZg6-7D^t%%{_d-p{R*k +24WC85g|YP{E-5$mJM|OeI+hcejBA6LcNP}(ESSfDWfl+2^}>Mn3p +rokBf4!M2@A&5_vUPvP_gGFYB~>$XJxPu+q+1h;T;X<-w-o9=l?GOSMJjTYS +>%b)Q+cPZ7QZ5e;~bCP)T_<8oYH$FsJsXG12U+zl%l)fEOFjal4P}4C6H+dmx9XoHRUSrNa9Wqa??k= +VsEKS_IBk}eQ}%Bc5Qq^WAkTY;z0pk)^Au2EPQ~8hR@$xjKBHGF01qZ2Sfm|_(2dpE;Fq#z$?}A+d7l +qX%0NoqWm?Qsv0MiKCD)N}5}}*WOjH*X#FsnefRn>n~6tAzdwae#7hrvi;M{HK_ +kgeLMjNEqWPjvJr3!&HX~_8MK~?*5$-VTKmv?UTnqYPsL`Wd=)P!4fRnZ*)YhRc!xfI{6GMa^t)$-K2 +sO(*N6BH?*l$hNEGH~u)`uBl(pY0wBMPp*^bdQ`TY5Fro86ZVCneYXQn+2F*TFBxT&qEB{m1<`kg`l8 +&TJyf@9h*Z9I(KXYlO}$1mvy;VjDK>BpAgPd6V;4_{4qFc*Ma_|U?Oe2)e6i+Hs50^X6M1U|O4{e6eL +>}+cn6SMK5CQ!j$<4w0rfFX5pv*4_(?;Zw+TK3lF0-l$FEGU1UiSs;f9htTR0}J36W#A(y(w!XOWf|Z +M$2+<0{z+Jt`o6k-_3<-AoYeJzk4SG|HhYRY&!kR%Qoi}>LjY}~1pA@@FlAE~+S@k)(7!AI4A$tL0{v +1J>=)dj*Ki*z8*>EuB$MDTRO61%Qpea8+fFfCSt}%~zk0S$V~YLrTrWucofi=a&%UhJz005MEEwZw3^ +By11Tkf~PT6j^EC}(E$ekU4&s#eIaxR15DO-Be0|!>XoSx@k?gQ#;CWMRQB)Xi*MtFb(g>bsJqkC{FX +Lt*fv;e%2O_Jp>jqN?WI2V&U?@LhVoBn17kXwhn!~l{nQR^G^VW>yq1q}3Ge!A*iN&QonoS21>;&HD3 +czSVqc_xA2rWx3u&nBY(O!7(S7rszR=QQ13<6Enc`)oWLBo9>VXnQt3x!U0uJ~@qi2xd{}SuuimXJX< +5*f{?~Y6UA-eErd)2jpt@Dn~ahxnAEjH>x)w* +&Zl+FQ+Mlkfer-i3my4L(kkQYbE#*k9JsL%-Fy|2KOm@6aWAK2mt$eR#UDjr@UD +a003e(0021v003}la4%nWWo~3|axZmqY;0*_GcRLrZgg^KVlQ8FWn*=6Wpr|3ZgX&Na&#|jZ+Bm8Wp- +t3E^v9(8*OjfxcR$(1<%Ey^34^}mmP`#*^s8`U9h$(;&jE(D+F4mV_jrXM@n(MVgG&43n`JZ-KO2XEI +=EHA|F2QZ)C>f@#wYQJSx?lD#c2bwK7&LwGquqiJ8hciU#AE +3s^=yeV{LVWn*7OQjpJD2&RRax07~Wuq3N(FA%`+^pQLr|&N8Xx_RtDp%G@v&D%ws=>)?eBNktn+n)= +D~c*F+Xd_{HY;5!F^8>JLN7(>DH)wI?+jOhwGzDJx3RY)7L9z(!!3Q8#TVjZmL!gS-bBz$g$*3UFiOT)aDfBVfH0$VCvIq!Eziq +6B&~c*)MrudZj=)wwtUfN8GRH6WLa1pLg$kMQTk`7al51~aA9&x8>1lb_b((|*kb*B)3iJk +OSu_<;VysTjMCV4b*CTeiqMS+>fO-jSIrVdYv0Z5E!_H1$Lz&Bvop>0*z}26IlueSKa8U=L-{_lQ@G{4lgDTT4`wqqF<~lN?0p7kJ8X!3R!bV6{kJ#VT_6&^Rh0Jg5{a5`R +w;!wv^cd7Tskhl2ejiX;r>x)rQFG8KMAkhc`(Rzcu)+31#(aN6&NEmBf@Ke$=2^ayCaXKlyaw>B9pNz +!EVvIvvpVB}@020#67ZKDJ+5J>=M{t@`2GI+)pO6zK(iRjj=_Wz^BLA;(ipi-O55DeL+oYxz)ieq8#y +o4DMIeWKpWjd3bf!#7FMA{z<8L>X0OkU0V~E-MiwRHJp*Yikq!{VTU{v{fyjRx>O6nmkrUy_VscpbBK +lS_;m=Y0>a=IRHEKC@5za0c18)tJe*L6-DwdS3F%`$r1_t>g-cVXYEK0`jDq +$6weiXi(KXs6cB8gb$zn$cqf-{Zyq4OTmXJ0J!px6`^-)q>nf!pxaElwy!e(Al!Q4_K#@NeD%K&Q;xr +7chQKkC-n>E7Wl)pnbym5_p*0Bgy;-d|E;)caGH6Q$xk+H99+qarE9*r9@{D0yFc{|6=$1t#^ChFA-6{ExOEH(lz6YY|H1eSLy?C=F +mbS*p*ml@t1zv>E2|Ssi$1X&WYP>WAK4Uc9|Nze+m)&1)tu=sFq?dhTCT<6QPTFn~@_0}Q5L_QioK1` +Tmr0&(c1dQF+P%cTlK4LfKjJ^q?JK6>&j&=+OF^V|pf*qiO3&(Nm=!QY3No*+jQdI82}pI^G1Ib~+=o +$H#)v9n0U{AQ3W1+JhI5Tlq9>btDO{bP1^3;7{kC{q1#+bkbFA4e9$GrYw>2!rx0t8_;CoY4PMXa2z5 +EH7co3GOyDp_KyHO+n1tBH9sVv(3ur4aKnz4iW%Up=K@k1M#NOfdNI(VLDGaIT1(4fezqKBOm@~O4bq +YYrYg7&VcPCfHzpY-zcXUjy|G?pg_u9w+*}fJ~A{e!~)tiu;GN0$kVzcob+`tGhr0^+!XbLoG=$ywOS|eFXOWKPzg#OF@wV8)Vsh^8vN#3LV +<@uwA@E2Ds=Y31-EVmr!(j(bI=w@oqVAVCQ&Rn>BWfxGSs4qJ>~;o1?*E|QHJW-7x51*WOl%R<(o!0y +(b2k)W4*lm8Hy8G9UkDeygB7hlgtHWD0dz#-yEykG)r;K1e$M`I$37pf+EnH%7O0@^~_#z(Hfkk*J}N +F0r%GfY%F{OMpLlnN#!1?;F^}o;dne3!^btfvLr^$SK&_TIKMf7M_S+wBMQ~eoKZJWQX6I8BRhL^jLq +H2VYFScGG1P4RV*74Je*HtoBT!Ceg{>?o0<_f9~iOAIcuTfr24UL;u%HzAwnIA(QNEeV>+$M?6{_BXQ +U(y0r&6@~zyMw?(;NqB%e7Bzh2+-PF0$A|~z|Xo)i9E<5gth`K&Yqu2|voy`^K84vWaao)~>GE4{!#> +g~=q$|icH?nFF+(NTg1nzZFROcXedn`0A-T>pKL1`JlvMb#J-w>npkR`{IGJ^&wd(^<`XZd(hSh5V^^QSgJHFOP}Ly^L{JwmlPKreA+n!GXkr-lzymsp1!nxs_WNmp +yvM<~7bO>N^8b(wY@lm{U9MZTrC^y1D7lVmk-V#nx|QOM%!Oag>bFr8mmw(ogAlO+|H4tlC?Z3a*7cX +WwJo(bK%>>8rhvMrhI|7SFDPpacf^*z&kk+lEe4%PiYCEC;?^s9hZdsxJ!&W`tj=$hJ3nFG0YeZL@c3 +_TB8tfOWzZxBEt#o1)4|LVM=)m|KzIq3f+`y6gXDfICNl24D%_B>Vjz2B`wCo1(^ep^ayt!oVuufuL)~eJ<9E +}(i+axrzVHtHdW>qfgHsIEoXFY8HUu_KrY+ud#}suNNNj5`*P?IZm(5wa^(NdW2weV4GbVGWz3iOHTAu{Md0swB511Ie*)H3-j-KTQzhH%{XzjTTViKH?e|rPIshq&s&@*S8CNPF#TXY{^ +KYh$?(ae+PLd5s+y|-XwJxb*PW}FsV0PY;&p-aldG+Dwk=r_rPfe_T}8A<+^GAwBflzL=m4id9F^T9f +POh73h-U%5fSevL1pzoWlQi3TwuxBmGx-N1la{Swu8X(GWdPYAWuq7rpfUB5d5dz|b#^MBrmUspTTW2 +w^!>MZqNf8YKKc9fwEM;LLCVum-?bOhwsk8B`_CzpOt{bmbi819Y(E%DA4m^pLZ}jyvQqICohXfJ`lT +$_IQi-i6Wv+iNh7MGZzlPQ*{5jD6524~rlu*IYr>LF>o3sneB2)K{ku7~F)eNjFeS#%4aAU_W#j)*u_ +=tSb(dP(aE>vbWUeXWi?}6o2lMq=y)=CWj+^8aM2zeWeEM4Xa-=Ok6;@F6cx)#=6G_be +Jb?gBAq>p;D48}hK=C-$%U!0lAfN3cjAbt!!)ELqY*R}Zm^VAOs98vyXo`eRqt^ux=JXpOskuq@?`-4 +7M?9qh`;HJ&SD8t5*Fpgq?s1HQW3u?1U*H|b520$|_{!MGq9Kt7_O#Wi5)l0vyo2+S3Y*gZP1PP!OftHC>{hJ~u|X;Oo7LbA$mPmq1m6 +x{885i_8~Zw@TGwrJ>*frNGnR?~D^-rbm5+0+N%@C~%0 +$H!2CiPU7O1j;Fq`6BVzg%>@FCHk|S%lYP*_0|Oiy-j?YP?gwRbvA>=RfA-Xmn6U0SJ|iU!TyZL?9N# +ljmBrx#?E5$GJ^t?2JEvuULCD^Fo^i!0@bx0Q_swBAPr^tSvTPDX^t>!!8aHUUvg;kkPZ+0$_g`Masy +G_by)(I;E52XiPQR*lJ%=g^><00JD(+Xd4D6eN!OD`L>7~P$uCa9v!c{DEcvevXc$o255iKzfb!u@97w7l=8$i~ +2(y;?<&4V@bbnaui>+<}iVBXGeQZ +SM1GcdZb)a23#}}BLB6zDlv@vJxy0J0xSUP!2(0OkV}PI)h1<%4;`MQT3E@joZBHzKVwWg#@bEg*kb2 +6KFl4VdfO~2~S|gUvIHj$1S>)h=9YWyt8+og`RFkxjGzS~xu126!{NRGr192|#&SmAQL{yU~Gl|*-PV +u@(g9fD*WB?8t-j4g=U{M)aL!<(dn}C>5H}>^Ix`^{Y)TX#Y%wcKFf=j$*2^14*p5jjPq56cO9M9S)Q +=zb#H!V6VS_1X=GAKy9P=>&U(gB25z(uIcSI-IDS0%?z(8zOoBDGkz|0s*GB5zJiSNH=_Ac0ul)}-(^?W2*Y +%$0OcyYGw~ZzM{U9Tb~WB?_IgHbGw{)+23$L|AfW`q>@NfNG6*dCenpmB3aNgh=nPcMAtX_(a))vFh* +t}#zkoE-1Cm2uAS_e4df)&DJw+C+hy!<}Hdz>4@!cQeeXUIHU=MPvKC6Hf7PE?Yliib=E&&SW6B1F%j +j_tR1%>H0Ice@`{?RJyZjKz|kwE7@`hN3Y!7l{Rz{f{@1-kEyj*vorY~CJRe@NiYYI>p$a&>#4qaD|f +__Mc36CxO65x*Vm+8hwLOCw|O(d3;bm*LEhy}T)IFz*bppJHya!8HP +{FgU<*g2eL1H-r7p7Y}hdb*puq98Dr6bIAFci4WE!3bgY)`9T15_t*_Sjs{ad{3ITJ;)b8X@V|WxM#hpgrOnz3YlSMyWhP6kmBC%(AD9fv +uNSxgwE=(%nh4y@v3rlq!}O8ih_lnd6+mUOwfud`4`gF|neox>`^mLN;T1emwuAz+gf&N>kDkz%g|fL +R_|8v{JI#1{L^IabT_$VuCau9{lJQ?&yckahK&J;lO+1jSRF(U~2zc{{nMiYnM$7D?jUWFx__$~97r) +XU$PAWFcX%`P+fKxv@G-2bdHLu+P)h>@6aWAK2mt$eR#U7wjI2-q003+N001Ze003}la4%nWWo~3|ax +ZmqY;0*_GcRyqV{2h&WpgiIUukY>bYEXCaCu8B%Fk7Zk54NtDJ@Ekk5|adEyyn_QAkWG&d(_=NsWi_x +wz6m>hxgJ#l<=Cxrrso8SxqU#U*)(xv5-S@$rc{IY1L^6*QDE<&|_axd2d00|XQR000O8`*~JV-)WED +CLsU-YKH&-BLDyZaA|NaUv_0~WN&gWb#iQMX<{=kaA9L>VP|D?FLP;lE^v9pJ^ORpHj}^WufQqOsgx? +b#7=v6nYvTQaT?v{*U3)PUcL-XiIBybB2_+YtKWS8`|Se&!KY+7cjw7OB(S?!EOr-*2Z3keC|<3T;AW +AD!E7k@UcP$yV(;bNOYv4le|XCv5*IR)Ng7AJXT4|ek}QKX4dO@ysaVKFPS@fl@uE!nBQZ~;6!Bcl7G +82AM8dIFk`#L0$eePDCn0WDRHpz&+Kt;ES<$e(_%e4uRr+J= +4P^=^@KL*iJsei~>hAD_JkHav&#|qBk$Pchy=^JXkE6DYx@tve9akEEp1{^aV*cPjziB(p*@WN0`rIH +*J4RZEMwMvkOGy^>dyily-gbJzZ3n#OL*^(#HaDqhx3nD0w +5-bXIHIp)HkYDXuB010eYR@$%S^6z`4?Pk`DbB6CGGxYO(P`~8nu5Q4^mPRv0m?h|+>J@h4Lf(MqEqg +sGhS%c|CVvqE09}s)Jc>)UWy7N5C6Y07lSfU<*zNT>q#^v&Rucy-Ic{I!7I81dy0Dh{J5Q8dvS)Azib +ZxS-^-89_p6hx!@R2}i2clnddE&>ZO8R+4{5o-R^HU$t?jRpDobg^cuYPb|LJdF~A5%HcL#jN$tfA&! +l1icA!B@=Z;55-p&_bNv#qWDS0yuLt$sn4?pI?3v2M1ygviIAw_c8l*WB!}HFXE>IG1wo8*FzB5Tma~ +so`64eR?&@15&+x*ypM*I6Mz@Lkx7vS2`XzAf>$mw@ImEd@*$%P_bwqzXznV)G<(nET9g9!7K6Ok7EDTP`nVYn8QLWOmv)m3*I>f* +E|g+`uc?jjvOTz&j%y|@h=DZ{bKE=_~>TCqDYdC_-znVcVTgif4-c9^@S3YdH{XaypoN7i3HP(gkrU4 +^0Q=J1_mmF-v+~>Nt6#Wxyr;jXz3jKrsE`ulQPOPkkAW|P$_YvjOub?T0gwriq;SN6!w(i)2{ItTS4#y|2(vFk>7+6Dnd{Q-IDO`;=oU_?^BXSup0=5 +!8cZ@Byt(w86Y1;7ObETb1JqPYSuvm*y*`eC2EFun5hu8!SYssZAp!ql4W4p?-W8t|CH6KXv^+Kgf^j&2Nq4{eCQY|N~f#TWVvA)P^?Zx* +BJuUX^C+3|oqRkkmUWtR!~)pZf{}1{3gzMZxoz3fy-9h)?WaFl56w9Ncj6OVm);)25StsmhJWFjEi*= +Tl-A+(inb%Fq{po+JT9z5!cUjU@8=rEF%#R3B$)s03tf{D3J~4A~y1o7ZMjH0ADWjaYK0MY_uJx~&qS +ny7Q*w2=oOUFUWn%?Cj>FoJ(|U(kT5i>g;clUA@>yO^e0Z?2GmXp?G{-;IvUc%N2RrXiIPXfL$1%-ms +5WIW1c@Nf!X7hbmD_(NKOfR0S)_fRYW5c;HSp@J-oL}C+g1rH9|P)g%Qh^KMhLB$*nQ=)#D%LSxRVi< ++J^2PDR{>wcv4cg(38-K}thgHSVIh4u;z|6>2kDsD +%=%+Y#7DE`Rl^D{X^&a$bB^700yEd0GB5kUAJ}@u4-t}V30z* +BdhM_)ZMCCYLRYxI~A>>VO8|`H@R+zI}ZOIX=F(b+8XdYF@pQLsc9&sNIJz_!zTcfQq5IEU$Ka3CLE**t~*I^8X +V0+aQBju12@RZk?%XN1#l^`Pk8;kHT<_3RFzzKsfnUnT8&~%v~GwoEu}`Ai))tz +!ofu#B^!Wx0&tW4)t8#0Y(n})|#y@wau>GOCY&4&XbuMZBTWPUW$IoOHEo)=~*JZOqOjC+oQ$Nmkbxc +oQh$~2k>y!uB>bUWMRFYYodjN=6FL71FW!wOlq9TWonO9tdIl~$+=)p4^D!@cdc5chu4iy!90p*=_R) +)9@TEhT~pvs$d#DCKf9#)&ha@8T}m%OjQ^XI;iwUR9)wU^g4grm_%h8Q9UKZ}6fo+knO32v3^w<`m(( +biUn-FHE0JsAI*3+z#)CD*DUaQ3wiEB(HE{gUN(~B)8pwfjYr|Q3t8Sqw0Q_ko0oE|P_Yo*gi~l(_FC +M$mr_K1q)3gIW&NC$+2xFHJs}cnWkylAPlWA+^6EC`v8^r%$fcRmP&Tx0CjJJWdnNjVlA?0i8t0;p?A +^c9Q0T-FinrNG~p|%RAi%>qef97#U=&N8QSyY&g51iG4ZChoXU{V`+RKRI)O6nAj>*vem+RYXaO!&48+S1DijgtA11oIzSFl8c5+%K|h1;I^x8^}Mrgk^&-=&|4hvz +;P)1BH~e10O5MX$$Hyba9zDQ@EHw?`sH)Op0SbT8R|O`QhsH8gR0*hw5VpgItE-DkncT5L)KjRTQ+{otarDc$j8IG1MB$Q#Gi98nv`uto7s2e +7qYUeD#ZOHLf95hLnE#@OCXhs~5~cC@*a(xxvs;;^1@H8J+0LkOJEC7a>BJ_4?3RLA1iMm?9bm2>cwt +PA{GEAHd5+5KLdP9;1&R0YRwrY@bdgGD3HCj{R?|!Mu9Z}iE5UUVB)urOV?C+!SAy#zm<1H+UGa}b$J +9=&?3TdQkH&)6E-VNXXm?!74Ox2CG>XK|7LI(Tp&m@@Mx6=-9E5Q)Tg0*Q|Jm&krX1NRnhy)Io8)qd8 +w`sUIHHt#o0K3srF&ae!R+=)N;r=rrEKrblOhne^j;cu3Bx7mOs28Yj#GYr8<(`jg6ghTReQ7Bo%J_Q +Lhwu6u>+!>*PzL9S{rdH5DJ5P`!13ar-M34Y6wk|-`h!c5$nwB;4+D)aaM#il~=`>o!Ug}VHqZ?nHAu +0f^zu$(iP+={It?-{dRgXxi~61E=8#5y1wAnP~R8#LFC8xDb0-q^I#^-B35Om8E*9J1N8o0pXa>lQ%T +?HxuhDGfT!YmS3Rn_eChS<_3j!?wTDS>FeoOD9=~(UW4WdYco&h*vnLm(PtIFNq$qxu! +noHc%w38KC~5ns;N#@vxE#pub;k4-D$ +dL7ihBfOE-=xnIKEuy#pgzIH&mJxwz;<3DJJQp7VzSgjG8r17ty*T3}cfpbdi8;QQi$Ca4E=t3IW_DfDO6_;Ej5}0Ux0DG>u*GZ)CPg +;)kFmVA+5@QQ5oH{G5dLF2L6HU!d|NT1kwDji|tjGm8g!?%0Pj9{ojZZP;FA`mD-mQAWU>g>fnu5CZK=0QcrR1-CtvF3{VI +phZ^OS^=Z)Xa#nyP`S;vU^2HbhGK2*>)C^~-d8q>B4c~2m>?1c4#j?{t(F; +EG_8Y$EtRdHu$#UW_>C&Zs+j=gnFLkLW`N;nh@)zEVk7-b| +lpBU8DY;#YY135)*1q=4G~GXVr5=8LZ{QO4Fs9Zge{>aY-u?Y6{IlShQ7IS+32@M*6xL|i? +-U9fUO=IzXeyHnSAb>>2R}o!Svkts2x|?#J}`*(erlQ?&DWNr36w>IIcPoYO~7lu9i=UfXcqMC}_q^j%yUHsdUF-OrEygs@!L +pdW#>^4Wsuj>I=fUwk8|)}tZH!~THX#V^QQ{9FJJa&qa(b^BHxY8B_<6XtOyL1OrmCzA8ki$P}y{9=l +;;i;VG^Ylo{`TDI4a}&l>FH|7v1tE&h`pQ)Zmh{+E1IWV1whGwqO*JqtZB-~GwA8_V$6ng#gXiDj))5 +!?@8g+W^a=e#)>F-Y2Cb*s-`IT>We{X_^A9oy=?t*p@d4;o@oY9`UKn~)`u<%%cCnyg@MHFR*aPLH!&zKbNdtXW2g2%3u-B@~=G-ZQXljKKb_&19j8BqOi2_f_q>rT4`D1I{CI$pM3)EQ +Zy{YnsBx->=ijNM401)~h(>Si7jiuaGI|f$~F?0cz8*4RYb-ytA^7?!bVm&Im$1=d6%pj~vE2ryI=KF(Zi#%CxS|1OaCSS?$4tTd?KfY +is_z6H&dXou%~8-uuRN1$wgueh_#twQohibi={n=rQBB#&wlMzHXUYZ10_|rO|$}znL9}hfGd4k7mNvmq?;@W%Cyo} +v_CVqUs}oc8vW07(9q?j4sTQ&^l=|j^x~C0Jj)v~I3Ew*Tws&{|8pJGF+W+7L(srUQMQMh<@hvCNOms +iXVCxYZGY(C;8~RwYCL>Jls&+;wFV`xJnc(;o~SP$Xe*u#2QUa~q^be%*=93{nMM5#FoML6ext0nh^l +kci(wb=@zKRo!;`qS+A&)f?!Qm&?PK;7_VS +eQq-l}K;pT$*pol-_AA3(AtZ(FhU2$uR)kKL@t`Yg9kadPV=9N*yC)|nUiUI-DKn1Z^c5V|ckD7ZROc +91%WT!r3D4wC-g|K1-7{DFrd?FJCAG~Mo8X744Pa7H3#H_PX#)#!;zRo9mtSmmZ}fdI&F3Q# +w4mkl&w<9XEx>$OkGtRStnY#O;2J-^T#XQYXxoUfhN`6$mZ1~NmeXMsH0xkl#p4uyYh{0gq8m>|B^dV^e8QD`$%mbj$ +2qh$hfjjy-ckC(~GmaPlOUK0E+t8v(%X2NOxNRfBA$I`)ysrqy%6YV%`IBp>(LdIX?84K!O>r#{)JFg +V0bCK6p29;?o$YGu=g^POorjgj!&qyle{P0PeBP?J6nB0OTKOjoledXOhpF#|sLIDR{>|Dk+$2ip?^eC+he;=x1>*n%)Pm%GK8t4j9~->p@$!1(@CT +)>HWf9eA&j!BBy+i%HRN18GB6F=|bx%9$8|Jm`T%OlwsPr{&8|Nh7!eThLo6b4ig|SxT_IMujPKLbsS +QyIRmg2i)V44#PKHY+}K5Lf$}?OsC~>T;0kjt1L~17vRkdm$bX!xuVZouu&-;YQ2KKFzfU%1kvZIr6E +d5p%{%Y3~)xxrIqXl?*@tF{fjhPhmtXh`5%me%qf_!=@iU8=lhaf%~!xH6LwbdicX@!Wfg3?H1p#;D; +;kK;EkHg&bc@X4cW(QB;>&NGH+P(j_Av*PY)4jB#L_j8cwlC5FSi0$r8XbG3K-Akb6A*4aZP4y1`2b_ +(|&Mj!y>-GN;PapjN2G;vDP%sSj&Fbf#&wE~>0%==sXg%xTA7bF>X2pp~%H(kt!*Kv%RJ5Ss9Izduio+0y%~M$)W^uL)LhbW0kxV-Qn{^a4xn +GV=0tMKnj@!M)Eo-oYB#METI7NV9Bts^^bhV>7J!oTQNef{6MDbFuuK+tw<}o*8<3skh9Zwz>2+ZOOq +VdX5vcx~CepjL8Et10{CgNg)|DrN{@Gb0w?>6|KXwk}C#^e +h3;O80of<5wvRJWhC*m-hBiOicEe6m?_JJlYvT+%t^pTSCsOjI+07Q)^f*spt|HobkTPI3U0fzf1QVU +x~O)?me8;GqeSmRiktR|SeA-fF;Bh&?K;sjAydhRwozSxE1-l7qTsgN_E +458f;b*VI|ld8GHa8wcOsVA%5JdbQqvQ_NT7D<9YK?h|jdlF79qX964|S=>uyPW9%iQ1+cYJ|k#z>dbN~EBZDQg*0gM4#)s+%ZulV8kS@2(n9A1Eb;AY*Viyy4Q}8Ks3 +Y0yYpx)@CsNQAZn$|aQq(j%zG25Py62XKGq4n#5=NB-zevz*92AoNG&#JuIG(tNmzU#T&hVDB%K~0;* +#Q3W;@#oZu>!4}P_B2>>C(!YR~t0rU-gm<&3WaL4E)>TjuiZx#n1pztygQhEg&rwnzY=$Vb@H_rA+Fk +iOf)`@}RM1Bk_-a4u{nSbS!NJnW*Pt;K36j3RvwADeHuFH%FUI)ogsqT$ZE9La8ojjCL2UQbXG$DhRBhb!)0X~7hrjbsuo6GE4#B{aCfCed^FsN +qKro3swr%mgdi(;IAS%qFicTc?`H#EhMokZYE5B)y`NK5Y)OQB;I6)i)b^cn-SP~ORq?gLeOT+{`T>Y +`2BJ4tinDcMK(4yl^^?5K{bl9MC+$z^7^cT +SI`RWT)@-gDy0Q@i1B+&lDHm|L{^*(3Wij6_R$Acw&ZLdvK4QdkM0p&WyS`Ty$6qK8e&f8&sScEp_y} +&y_SgdsH%~@`ZYhL%RyP>gMUCIXw08lASmSZWsga*MUOmc?%C&iCx@Em!%E@jYh_m&DV^tR(@1a2Gr>2 +g2Q!3#1at@6c}gLGu3yAJw!Hh=N&eLW*{pl5U}4UHw<4)FYW +`uw@xrEJ}xsqSrfZ_OTR8N3kJYJ_<$Ui_pc*61pSYbfQqfWkL~;N&X3{Wo<;n-(oxTGaTR=vi61N35v +m$i5ApfrbQ~F`nRzR|4Xlq~Cs%ev2ELdGp_JSZ+j|)ZDEF)8|I^|39r(Ay7 +0F`oCHsMGSKeWg{93!gqoH@!Hz$&~oeTLI1Xrp5btE7P)1YGRrN!&`mpM7wp(VK +H=(txhXKe`6Reldmm0JW}UMk}4@+Mq5^&pn>Q?4A?rFAj)X6|c +K7rIqFY>uHn=T^C-i%$!jK|Lp}-eN1rgnxs_D3-@-Dh4c;-MY6*=9;W|o$S0y;NBvN*d;L@fTq@0Ui!fPHz~l(ygZfmT$GZHVt6ogP&G8Wl^6v7ikL?O743 +Xr(ui0Ml7)XDLT@?sJ-P_!jlRLiPyrV7rb6~0C9gxI=)(1*7|&hJq`JQtPx~}?B&j>r)mV);2Eb5^X9 +;okIG(F}r1;7ybU!P+Tq7X31gM%=$9)jyU$K00arqWAh3)*f#*FzYl6>s8aG(%Jh +|5q;w=<9)JLVYa;mIxLKuDap2MhxnMh`x@LYr%@PN@BoYrjBX}F9KQ%3_>FsH~=Aa+RZPItd;1U#JMYANN!@XzRG&DMhml8m|6dHf1rmc=TIje(b0}Wlo6By41&wLC%XpwBy%mggdytcY$0d2SQ +Cq$DX!KoR5>WX&L@gC-r_OB(K*S=zJ-*0OhvK+k3VpFr2-vNZGT|CARYGp +2T)4`1QY-O00;p4c~(;Z00002000000000o0001RX>c!Jc4cm4Z*nhna%^mAVlyvrVPk7yXJvCQVqs% +zaBp&Sb1z?CX>MtBUtcb8c>@4YO9KQH000080Q-4XQ{K2^vq%B}0Eqblf}u6E?$Ex7o~)jtK{nuv5qk0s~sv +d

    ua{Ci^CingE#+3ywNu^|y6mj>~ua;MYkP<6MXIZCiCIsxGv96}j$Px>x=oGeXRiU!o6knylDWC| +}q@_|)>9`Qs)m)%ok?h>_TwCsc@&sfxYJ_h%S(Sgt|4Zf-2x)9@X5u=_;G%#h-yZGUpDq0ps<}roZk_ +zb>K5m&y9wpu7pr;cPX%WRCQ{S~P*dpa`GD26?6AxzoPozrv&_Rf#f`g$!c_q-e7=v^nz)UdXgYd*-CKieYri1yct{**@d5&J%5G=M17ZzKbM_{Bc=F6W*fJJWRrsM_txWLEcZsT4ALf; +9aWHPX%xd?z)flj-di(BMBGqc3jM5{&~LmbpPdUeR;oF%x~7W%kT43Kv4_{aWsOJ$L;Y!Xhpza3EEgg +8x2jXZ8k;#&5pjIX_aucJi7t=ewL>;2XHvXWh`>Dx@KHWEW+D6c$q>OtjF$klw?I1Nhc6Ja6b;*aLQg +{uB_$-rt|xmq0x>Y^t%Rsn?Ss?zm``k#rAs(43guQB7G$K??cw7=W)shFP5+3V&orCO9KQH000080Q- +4XQw0;FcccLT0G|T@06PEx0B~t=FJE?LZe(wAFLiQkY-wUMFK}UFYhh<)b1!pqY+r3*bYo~=Xm4|LZe +eX@FJE72ZfSI1UoLQYjZ;f+n?Ml0^D91)gKZU|L{TqMn?tKs>ZR?iN3>=KdllG0yMrnJzQf`;ekfA+0 +%CT)*9@-fISAquvrNMDltIrOehmSgk$PY4If^$Op&5KFjy+t2>VsZOl%omW`JHlldu{0q7_5abx=4!>RjOH)2MSS9D(4% +-K9M<{wS6{+7khdJjCLj4GOMOk?4l%Z`aek#Bu5s#y5=3zoZbpA=>gAke5pbIFlF1h(O@Q2_3{Epiga +-Z8J9KP^mQG!%v^_S)QdLz~lRb)D&kQp4^aQu_>gfbApBoDo)l0;;+9tid=k9HN&r<&L!;E62R0^31!?do9$*4@H5Z?XOqec*$$x@6aWAK +2mt$eR#QHNOV+Un001u*002S&003}la4%nWWo~3|axZmqY;0*_GcRyqV{2h&Wpgicb8KI2VRU0?UubW +0bZ%j7WiMZ8ZE$R5ZDnqBVRUJ4ZZ2?ntypbu+c*;b?q4xbP()50rD<-vJ>#-K(5AZqx=FTa_loQG3|g +XMHZmoUbRFNX-)Bg@&`J8Tcb5XGB9b%D%slf#B}wvjA!#XGzL+g)>$F(PbWG(+=T6m{N>eZCa^n_wKF +aWKLeg5Poe~wT7gE#8Dt%2?SFf`qNk*d`IQw_;xtst=0)rN|iUY=itxWK)@lXExIj{7S8#dIycB-x?0 +X@NQ>DsTngnA<`{xA+f>S>W;fd%w*ZVsFf1im5|%n +4SoOO;JP9aciDBVz`Y~YZeTt>-c$U{I?b2kg6$>MWZ9i>?Vz7IM&a4IFb`F31`|~GL5}#{TP$4xcg{8 +VgB+O@|D$O?lREDy#tdXdL1cy9A0?1w;H2_^2alJ&zK^B~lT_LeHEKedY=asSxSwte58ueg9Vp)f(#P +qHt9~T@^OLkGOIU8J?8zMSvq_DMyR1~B|4Y1h|R>8@6kI?p$NYOB94!sO5|j*SP}7f(`+Vx&b=tm=F8FgbS15IKn6*2(TjZ63 +JL&3|i+8VI()q<(5)PXh?@^z;B&b25z%KT5!R?|{h$Nh`UZ{LA>cSmGI$sDQa)(_oxiRoI>K#>LE~`) +-13UN(6LsGc-7jaID+d0f80{r7H~-14YIkp!E4t_z#avX*%Bq|&Av|`kRwMV>BCf=);-WZ+>gm|O +6(rjfcxlMnySK0as!<(NPxd{-44W$w7xV@=I}oE9G)7eM +4i|h%N$jvfsummh-NAS@ISIg+}{)6qWi9ju$moD4!V*yVb}{+FUd;?c6!;Tx@0Rr;DzUPJFogkm^2^m +?;cu4+fbGjdi_V?|rYv4Eb7#T%zRuv6oE9#~M|#&m;6l@2pU8xxjT~u^`yBMzy1@^5D;vb*OQi9(}2v +eghOA=oDiI$~V?Hb1-r-z7tX+8hc(i03<)(=TK_wOg{;4$nhcu>l^2|?+yrhia+G>LeP?LZz0MN~AN;kx5Q*jy=Km0W4=%~8T}y|?Y4|X3_1Vu +kxz{P&%R+RYK0kc!S=xmZ+>wV2K_% +FU{$Xn+-07>OL^_@d6PT#zpU0l9ByEs32&FB1!H>bb7J9~5ba@ZV*)L%^gAB^UomW6iJ;`4-`4{oomx4m_^_x9DIDx6)#0^*yiRzqzw-&q~D2y?9RTX{! +t}61S7KI!0CEvxhReW2(NCcsgH16HKKBaxZ^VWWcu_q2FkV${V++W3wj5JniZiE4Qn=m@`GPnl~xUAP +b-QwT_xIDJ4>4ATI`uI2Pbu_x|S=fKn+bhp=CG%5bQ;6Os-IGEA4s+VV6g +++TH=e;o!hY&e6NUZ8R9F)*6YrqZkq5y#*s53V4!vTE#s^Bm~;ciyDd0EIdtIv9%O&p4m)BF@xa@LgJ +JoPC@B3XR7Lnq<@_Y;5mRw~#R0>pfm#y@;W%xDv|r@3ypW+`aqG +_2^#z>E^a@>1lfurm;shp+}SsUzvEO599JuG%4TCCq3%%IKHOy@Ah^V!-jjmje85~HnvCR4x&!AikX_&0RRAl1ON +ae0001RX>c!Jc4cm4Z*nhna%^mAVlyvwbZKlaUtei%X>?y-E^v8mlfiD=Fbsz8ehMMGY=F@>=poBIG- +!|*c6!)}P;9ypVp|#|C+pi!jzvdl>XZ2W6eazn8`7NsXa+YB0tnR^O-{&z)$QOArZ`EyiQk&UK~|@Wq +}qx~cSbsOP_1$wsW7C^s>ZP03U`!F3>ItQv^bzRBH>fgjE6l{y6>@aO80!4vT%b?lQstHkWKh^KvxT*z-(>E7W#H^tIgBnOS^-;oTdK5+ +je-JTJuQS}bldpzFy><#d4PQnN-Bn?rjWZ{dZM!z2NaZR=<7Ias|2zAmOmEMjLP_Q_jTZeB8p{bqLGP +Nvrp;2@a7p?^FtAKSx9>>sl)r#uqpp=8Dmb3FUZARcyR52Nu}h=zlusB1I2pBTn>9ejY-KF044wc0B~t=FJE?LZe(wAFLiQkY-wUMFLiWjY%gP +PZf<2`bZKvHE^v9BSlezRHxhlhhK2*A-o>MV%Krt}1Qv#z-la-{ +t&E$!jOHvPsluT{2pDV#e}2W3-9a^Sso`8L>i +?wt7CEO=%p1rAF;S}gtvC^h6oK>UIsNyRSwm<_EFUh*1G8(m487)m_02lBinZqmFZ*9#>u@dD7@Mk<8 +`_JDvMhv~2n}&l}f!lEC#i4MB5`n1->_#`Tf+e1j+VUzNc_Fi!8MgKJW`4mhmXcXJTWp-G^HK_~T>i^ +^{`9evz5u=a3;&E#9<<{#K$oG8CR>o=mBVda-}!h>(LA(hFQfz}_pG&Gy#26Pd}7k_#R8o6!X$Uzmm~ +@{XullzW_a16U1|-^FlEsPG*PyI$Dy(LAWQ@p_yB9}m~+NQcM4-#9Z$ilp7?VZL4Opj^sDDHFza3F!W +A9yw~j?UXcZ*&u|xj}18qzMWVR_cz$`3Vpok-iC;72(O0r*brtK4TaRLI8%$5@4z268FyWxhzmNTjbc +ZoVUR|yx#ItCEU$OFup*mB*t4{!1}AxxpXzK-<&Gi;;L1v_^-mAasZMQc9p6HwxmA}A5x(Q$ZgQVV;o9 +7@M2YD@9_-qbIK ++*~ELBrPFu8dZFJF0TiHY=nc@aZ$zxL%j+n-zJ4_wQ4jGSWLEZN&Q2Ia-;>0#JmNT!2 +{eq0O)Jp^u=9M+lbz0F1FEF@ +XaL<*0}6UEYAj_EPZ?uFZA<3Gleo*KRK59yE*HSYg8jvQtTZV|qNmJ4|4P_ttanbcDblTz<_RjipPR% +|O|n@tk?(BQzoc=xEYpk+d>H1#eUMK%I9@U2?OvN#AZw}G-+M;DUZzcDr=kA4Zy2$qcAGp(n +-idoKmtYdoG?PqnuFeI$%P~`ND6*XFwZzQI_)q%eptG?;@MbKij4^LLkh6&3H3TM`B +)6eNlmNnzcZ|vn1BwKO`RAMbf!D$SuR_if*V7SHq +F?rwiQfDc=R1Fo&Hm)1r*J%y4f3^}p~?lFuZ>gz7VF=JrbAd&}%qUP|ColzBiN$lbbgW +UycNnY4*r_TLF4Q_7h$--5F(moe<%f$$+NotlnnFHNIw}8Jml?X8+|E74c`;f1oE)ne($`ny5GQ5Bla +rGm=P?GEWy8$`O+3?k8NnRg;)F(GJwCJ`um(RhI!t;L7(eZuH`@<;+2rizfQ(QiX*w&>R}*cAqv7kJ+ +1dDzjXD-kv7Dr^4huNEKE4izgo9Z!C;==Dv`3ZRS0Emw3XJDxgX$mz&?jzYSNJR0agK?)q2pvGtT;_@ +VV?7BdP2yd&-P)UQJALSAUlmnx1Wlahv%oek71tc8W2qwUV@(vYHs`!gn6pAoEe;{SCN|oW79<9$vp9 +F*StEe?N#1C+|5%^f4O}w?w@|Y>7#V!La=dk0NrpX<#5AnA?n?YXShCW=iY{K=0p;UeO*NdLJ{Rh52l +XEk_udlvpTI_&_oYmr0?cfns=yIlUvaV>jT6}*I=Go5{AM2yU~utQ7cMQ&31`BQm?vue#rLLazEn`O~ +GF@$6y*z6cLh7EDdS+Vo9mL2>p>7I!ipMIL9ZXwX#hO^_g@3+Oo6j>oqpyW{I4!y^jAy=stzD4Y${C- +@m^}c{}|lL5>gke$k@COZ_FAoHWFP@QLJSp2BqjOa*&CFZMc6$AUnUbJ{wRnuCLvQ|jnCnK$>zlW4&)ixzYm3i5ay!> +%9WrwB{Xf2Y_rw3#XoqEZbEa$f-V%p}Hc!Jc4cm4Z*nhna%^mAVlyvwbZ +KlaaB^>Wc`k5yeN;`4n=lZ)^D9R5utY*!dRr-{sNGhova8+nj2sw(C1TUDNjCq!W8;AHwOoMle7yH&# +trE`6vlKV$bGNLLPaPnmIHO2+4p>%5wj}&#W|Ip)AQ`Duk|Z@Yz)=b-%LeB@%ck>fi5Z2T}$$G$6N>DI70x +T;MZpJ2CLWg`p0U+ex!$8>-~N8BJug2dqH9kf8YPiAjgXCac14bK~c<`v#dOa3qo_a_khtlkmm-CYeT +Pfw--w6kc$2kCpDJtNHM3}FqF}D1{Qf2kjVnU$XC8UmC` +gQq_q4$H%8P8cA*`u_rhxXz5NIx!hu*7I52-#F-5}>$(V$#XSC5s2*;X%O~moqT;>37HYh{f0BUb*>n +lxEWHwqYU6?aFyu$CV#JxMdDYKN5_#JBMidE8*_~6IM0+mbIj7i?HJE+PwE8K{ov!fEOCIk> +pX^w?f81-afi>RE_LDpEq_T;ABCu5>iT%?bf!1BSpHuo^1xx90+WzqK{pQ0u`mN)lfHhl&EMRGeiZrR +R4UQ9%E;3R!~#^PQavQENU$|CkTq@5OZ88jZNrHku%iXw_z|17`{;}bb2{{c`-0|XQR000O8`*~JVP6 +PIz`~Uy|@&Nzc!Jc4cm4Z*nhna%^mAVlyvwbZKlaa%FLKWpi{caCxOy>u=LY5dZGKVq}Dph +{S*np;M$HS_nsV6|Ju2zMPP=u_w(^dpGQ^n+C-HelzR0o5DS!s>yoiJ->N)ZFCNCye?c}FpfdyTWuVO +mDO3{FfPwQux7fIu=w&tO|yFW0#%|@tZt(S?-JZPsgTeLy&2rh)RAm|TnMOGl}q}xaZ%jE_|ipDON;5679xg}!ErE^kUgVj00`>U%9sK~%=1bJ +oqHsDFP9`GD_7SmOZ4k7|_V*cVv1RL+iB8Mqal($y7VJLhS}v!=zFfZ7*3oYeDy7p$i)MCwjf +$Q{!R7C#7xDYW@!Q28a0KD&*RMNI=oEU2i{q2CMfLEK=qOTOQCW23zd}(e#Y$;7g>XX(`MY3Mf>ni|C +P8rg=FQ^bDqehebN=@9!*8tb`m5YdQ(R}lVHh~Gp9esnI_gHdOmd9lLXogE&UIR37*`kY2$cRJ=J4*t +v*$sXW;pe{KBJ|uE#)b|%wl;QTJeO;n66A11o6A$)3e3ftHs$F*``;YF>zzEJ0Jrg7dIGJ85ny(H;CI +oQ1 +VEK>E-Kk35543bd|0Xw0{En6fT65lX~+r8UpH7iV($<;4UpG7ajp2jxw +otWv2SS&IH7d&fHBF8zSd#6oWh0Z5^#Jt+bucFM97f78g8a48m;$YT>;QOy8csaBGVMq_^KL#aJ%m|v +!=xk0cVdkF3v?j3f9~UdZ*sg-LhPN@bl;2@VaJa(X8%F@Jn(tq+*}grBQ?AYeelBV +igwT)0E_Nk@;xpGg@s`G&d*$Nj1GiI@grf;wJ@IzS_+JL5=3OWUv?YOM`>iyqG&w)hDf0f&mQb);Kir +Hm6_Wg$FiccJlW+)9~Dq7?npuyE_yuLS2>)x*`_C_GTu53B8^X*C{-Z$**Nwg03qk%{fg3vy+zE$PPy +|YJ_u`(}qH{hMc8_*^$dlvs`x>Ls8EHPK>s!T4@J7_1id*u{ZdDBb_a;2M@Zfpm9H>{6D^rjN@)Xy<#ThqksgB6H+w^E={>GG +8abDGVhlTPI|qT8B8U?g7!Av)5$0!aj>MZ^ft8bC?2y+-e9$=mH@2Y2`m8|Z~E+nOg5*La8ZMg-&2U3 +zsY0KjiY(NdI+p*;6&G1=k!}gtr;Y(?zDwJwowR=J@6aWAK2mt$eR#P@8)w +Ey*006cP001Na003}la4%nWWo~3|axZmqY;0*_GcR>?X>2cYWpi+EZgXWWaCxm(O>g5i5WV|X5Z*&29{kA={vvE(#QdLrWuzjYN7$%3h=BfA1TTl4U2zqCj=AC2~Ia=FJ<)2Ke3u$*BjsL) +Z<6?50W%MJgZ%7to@1=E0udX>}w|6b`C%Gb*IrTMb%!YcK%KP;quN7$=!G+gaEk~nKL8VY`QSz +#BR7}kBujzaw@Qlaf@cXW!{K<)JMZO{{q*$p9X;G@0PHXi<0w%~9ZbbBvje^}AhnEenMt}S~RP@}?8< +BI2tPdOhc)QyhzKLmwN99tY(?@u+4lpV$a_LCi?|fzSg(wU;ed2{9FCOFW$86x~=NobBTub83_uswkz +gt43HRqJ=V}#XM0&(TNRZG-9V7^anEuDKMqt +mrA3ar9n-#3AKG*no9bCyA^qd3~K`1g)kaD ++XIZy#&t&|Ymdl!$J_UKQ4UW^ZG)75$98VL|;f$)1k*`09>me--u)cwg5d?G%;VkXRDBz_o +c!&XsLl?*uz!`m_14jsQHe^+~>+A!fPPDd$7F-OJCVMV1`-4oa^;6Yf*_zZXrQ$7Qs>ka +de-UR5U=U}gpp|vyLCr2ct1HO)Q;f7(ub`jD8Tf=MRfaPa2NlBxZN4DwA?XZK*PAew{$<9b0+s76Hm)i +hpDP?DV{TQ6rel4Fm{11#l`5^=d6X9g~(xf%m9o>{{m1;0|XQR000O8`*~JVmFNkb&=vpyk5d2uApig +XaA|NaUv_0~WN&gWb#iQMX<{=kb#!TLFLGsca(OOrdF4E7bKAyt-}x(c=w<{a6imrU({L`EC=wlOt{r +(K`lcP%1wnEtq6PsL4^fK8{p~&H?0W%FPDb}bZ}~w2f!(uv9(&&4y2!UIiq>^i7d(nsvfbrH#o|Sor* +*|6@x9lR_xUzXQuQUxZ*P4rxD;}@7y!9r&zvfl(V2yfPYhh^?J|PMO?+KtcrS +xjA84%0Q&9*(iW1u~|J7s&$mT?A~+2(f~Hk +p)6pu$rYXDh;GnZ{+w_DQv2E5sGR@fFJjr^8-!!PhaOHDUSUzhK1xml2a9;^i9v5>yX6J{S{9KkbTq2 +h#LF-&TChE~YQf-o2hhZ>FhIkR^+eD^!CDQ+h77V-K9Wg-ALIP3j +yKweNnz99#Bv~P|?oPu|$ljIQ2Y>eZ_0@0F3%SeNL@}h0-@q1dHel7-3 +9+4TPL@5K|2cti>L8&Bdi>YfsxxCI2}lwWx;`XU;#6}7eJ-m5GJPHt%$a?H(+O(6ErZa4`ixx#7OTLn;A)o|Ix*P%m12zmeRKh|lX`s+r$7}~q%y`_hrI%(-B!)}jtxUcf!lQUKImQ3NmQKDiMBY@@ +4(7ZCXm8>yMpr>Xn#lk50TGS}jK^uA^a_HbU#7F8Av?mG-;Z?yvA05jXEC7! +S$O@uW6v>L0gvOaxg@MuR~+sgp8Zq(JM%1KWY1;E9^gU&d+4=kurA1p$;hAcw +AG>a6XHc()U0ZA`K{p8pRxMrpEmVfmz?<66#Hou#}id$tI~g+XVC-~_doamk6W%eW@=G*G>TB2J7fj( +2epgU6hKlbly^YIM;Izp|{}E_fl_L$Pp`vp7xjeaL3uRhJA;IbZQ?$%)l0ht&%>Wp!4Sp`qvv#}$Cuz +vdYb8?S9o4kQ^|)fo4Inra>6UcvWeuyKNDG)5=nkpR1f_yVUP71l{nR?2^E&XAnPFOrtDI{4afX6JLF +RFr3tK2&l-lX&tZ8uGo$_iP(y59$eU08U0q`hp9YAQF&H$*j%IHsGNXd*v{}FW?-U8Npk1!lVa`fW+- +>4%({Z#hnUK@FN5Ryl0TMddt}c6it@HtRlgv0YKwqF$+O~Td@Rbvse1#)JBOR#-cCXBo}I{7FuD{=g@ +@)N;_~!-w` +JUzG0KORg%hOQg?{pLeCSN373lJ^~@urB8Y)Qqpg~(rsq@Wxa{5>cp?vQR9S4)_w!OZ94X#|X1@)QDN +z2&EqCsL4Kgs@P~jq`M6-L_Q^WdiU!>I?>`N|S^L>dsP!ObWT{z{y8F^&O27m}}F5z0+or>izA^z(yq)rL4Q&&Y|DQY6=umIdVl%{Nejj7 +bhm)^OnFb_Z4oxEgE$S_1Bp$uyDfAy&s=mn@j(b^ayABMy%^6A2IDQKEwu5kGGWewXBR3DhCxYZOSBDL)2_33^39u1czaA+f*#RhQtHdqEpUWGCV2KZhd{ +lCFTQT|#`tGqwyuDTD-+2L~5GNQkWneF-5TDicVOVql4TuyGXKMC^O*9Be6jGz7@uiSB%^fgzG&qBKs +nYE{z@DpXr;i6cl*<@J+9DG$`8+B(evVe~lj^*UO|%PKEyg2^XLJRvFCp=Da|YR`G5!p724YbA$M0r( +UT8cwjyfgHBq!pl4*R&GI5ka!650qwD#P20|EP{bO}3LMD?I0Q0s5Q0&robQlRzL0)$H~*P^&4N?*HT +?Z#+((W9fha#_gnZIh +T#WIP`MLWQO4pKNBoq@1|Kcmn)>10h+3(Hg27ga>SPYY0q;mP}H`zLVArPsPCy7VX9TJ%9z;9NNBgj@S)&lxbY +R(Tp=H2IOr{76ips7(wXTW<%hRnHZB0XzbpEB0wp5qA|ehJkJ6R(gw=;LNl+yEKoo+JzaFE9KklC0x? +DxM12uv-e@HVSVPg|)Kd_q3*kz#C8%GpXY-JN +o8Hy#cU#2ts_x2tAyO_S7&Zc;E^>%i7_3I5@&aThSr~MB!dC=IVYu)|5HqW3)pLKBB+3ovpPasJA_Vk +A#`)>5&Lvv}1k?yM965$c+U}o6bN&l<{S{KRhvWjT-0(v&t)?2OkF*Fi9wX6jIJ2@TobQ(X3(4~nY!; +q4vYopG{W=>8<=8j6pqKh%KWGG%WZpkp#y)F3Q-K3z+yXOhlL{&C@P==)*0M~*~EF7Al_N=IR%X7I|_ +o-C24RpJdl@GjABwA2Z_2CQcF%Olhkgl;F>bu|`w1MR^fsyh@tLRTnG73vh@Yr#9ye1<18%7^w`h&&@ +{K%ru0wjPPd21ZClyvmGBOik!5Y}5^4CLMW)thjr+)b;Hyj+LG +yb)2KsR$YinU*l(xpnx`p{Rj-4yc%%)s;ciiD#05|dMfw%1z^cwd%rfA-HciiC{-BYBU^+LGvKAzo{g +oxVp&gPuDu{zS(20eKJ*e1F_qb${}dkx1aSddmtQ!<-yf?i|lEX%W}q;lnp+7ut?lHiF(wcZTw8Y=QO +Z+3CDIB6dEUPH*XvGcfeU4nMk$4$TZo!2`QZ?jUjk2}VK0kRA>EeiLJ2TRa12-8!Vta58!g;vr1PxXA +TVF9m4Mx}y11wt@&YWWQmtf0?Yf1!O__Xskucz%;K58TevV5<{>Scfjd$5`d^Qw8qN7p3{1=xj=ggEn +rAI_ep7QNMhdb5khc3TKz~^wC{uN6J=l|kdizU51RG7;W)McgMq~5C_-%zMNQ}N5yuKYPv%?^uqSh{@ +be{3)}V1msL1zCdVNryNSD3C4{z9V8>Y{%PLQ6$4n+e{0J_`?U?}^Ar6khzgW&70#b|jVex}KHhx8aH}>f4}yo@aNUbGRWJRf@*!tAZW>EBx(SY_e8s2!i#@I06ufMaraMCPGMx!_k+(r8 +%iE@IWi?V^LyNH9HaEA!HCtD)W|n3CrE-(ru}-a>3ZR +t`)d$eI_4c~J_cV#46BtE2GPHno=S(GSviyI95Se$4K*Q=(hLm4G7ofkR+V0^1-DT+Ly=4Qxs5*y~>= +?4(r`M~29XZK7~EY4xUceWha^QC#uX*e034#7amF-Q_G-5>yI!DwYEv9ZTzuB~QS2)R*NhUV?mJ^IAE +6{+wQs2oqj`>sH)8-&EW5d9hx8fBItdtduQFFT&ICTUSqn)Z-8o{r~)r^6U4b;~zeJ0~i0)?^+x`vVH +{gLM3O9)5Dr7S`0%C8;Pv`M7%r_0zqNpL;~^j_jZta%O0Fo9Y-rtR>pbNBk0SoEtzxB24LL3Vt-`u_d +k5l>my^{K7b*}*lA-g&)Avh*LpWQM?hIlNz30+%~GcXxPZwzu~OA$=2a%)o`&BKnN27uZDIqm%T$8}L +4&ZT1zw_ZL2EYh_mW7DQFX&?KjI@?s>1HsvISf$R`=G$23S1hsg<04L>wB#9Rqd0VZmrPfPamrC!_I) +UWbTz!lkhYd?*w=W7P}%rzfvM_*JyTs!g$^`&sx=l~3~#W>NxLtWZ +1WD%w8i3S`}0#O_a3rc_BWmU}FE@uLbw)lYxrL7)6S)9vOTu$;F8l^6984*ny9W2l1 +Hp(5-zWTIjwtaJ3RdRBRCFNQLiv1gdUf?c8x7oGq)tV9!QvwAZ&R6yzMAypIqSq~?a&4(zhMZlZVl=| +C7{BM5~PsvsH5J}-zo=W|P}unerq++Gd`-96FmKvAFUbnh08YCoz3OZ4t0|L4` +1p8xaICCr7vxK7|+j79?Lbeq)@GRP7||z%ppO)at$oJMeIpK|JvWdjllq;y=1iAPKtC25#IWGH5TU!C?>5dWWZ8=o^_!!52ZWFGT1f@MmBt? +E2Ve-G!fLI=6)hG>pbjiBVFF-iN4lvl+FAEQc-6Yc>TyJjPTn-*kq!u* +Nurjro_tRku0v|j4F)PV*xApEAID?xZW`YLPZuc^;|vLCLzRce?vEkr3frv6*Oi=8cUjsd?77{BsSoX +sEcjXFb7}WTK7k@gZ&l=`x_1{B2~L5irN)lo(rcJ=nh*6GMnjz@u5t@&;Wp@#=R{=I6x_Zvm`Q1@zHO +z=SZ+Flyp~hhE-or>Yrl=g_-C#LQq#n;qMwPB4R#&ji3OR3p<;i7R-KcqWy3k1*fyQn(3TBJX*d;)S| +WK;MmDsH=F$~{TA>|rmbG2WBf#`B;JkFEwLJ1=CP9oLSfp+L3#jtt5wRG1}wL4U->#EYV)#nCX`=l5wp%UfW+2)|hbhs)cMkwc{h%Evfx_YOI&xj{ru +^wSphg^Kkv+Hp-jcNaGxgF-vSM)OZh7PeD&h{V>Vg-|1ApSW2XX4a=*R##-A|9op}b;fHcT`i%kA@En +)Q-(R88K*w}lr>XrK=U5<#q(Z{W=n>*ZL9b4y-)ckAogb({1Rl=bp0Nw-9b0NDl~Kj9$Q9zIX`Y*}5) +biJ^@7ti{iaAjBL_n)&sZIr`hSDG +%j#kN1bkWhw8pD3dn3LuIUBqew}>;r#kCJS#D1UGwZC<&R(0HE|Y0~3-(rc*^W1cZXHOIWZ2D7f5IVoD$=OEbkl`KbP>hJTDl6e1jHN$OG5K4%4r8hzxQR?aqFp4_uY!-rL4*nAUdE9>c|TH+0jNnkCi3f#~WPyHs +|PdS1EMZobwTLMh&9@)6&h`EE1&+`ugg+$)Psx`! +6J06C4eCkgbNSa&&{e-Fv=yJ3YUAsWu&o1_0Y%o_Kq0wqHEXQPaZbbo71Wxxwy>ErAM9!S~RLKsvBZF +&wnEH>nD{UK-o%nnrPQpna~pbM8RX+~IyXi&bq6b1l7cs=ut!TISYQG`gV^ulR8wsa)snvVh!Mo7AY% +9Z3mDe(V?jtd@vN%Z(LIE|w1i^tg|{Dvt(Z(G@}9^(F@mw_iW9-?P9Uri&s!=xPlq`=7ACME%s==Rh# +`lkt{54>eyu*$xlf_kIA0IQ&UFKA)}TkWY@s^H~X{yf40d9vFB7b*f-<6Vv=NB#ET1Jv#*&ffB_xswB +!odJoJ(95d7SX4quXlE?Garbta*TR)~^=it&LE*o8J`1wcA_t_Q2E;SE0v<9q_JwaJxj*f5@oE%7289 +PQ%5Icj>$)c{rF**oSLK9%{Hx4nWyiomH%c!Jc4cm4Z*nhna%^mAVlyvwbZKlab8~E8E^v9xJZp2? +$dTXqD<)Lcnao0vC3&43?{rsOie9rKOFB`$Ehm)&hrp1W2m}}a6vb>dzx}#<9+&|^*;|(nr&3vX^z`) +f^t%UM6#1G((PCR|3lT+3t~Ys6F+MNzbX$o?-FG_r`D)E$bDfvws@&Vl9WOGOEz8aV5zl4D%UDW-b|q +d?x!4D6o9Rmhx8mh0m2(5OPQDLVx#H*a@5T4;I|?ie#Y-$UmCUo!!1g)}ekNHdVpzd_%Bw|QtbbBu1o +ZO0$Y1u;cqP{2b5?FQI8G$$-Sl?yNtPwRdPAnun{iR(MbPP+sW&`+;!9yCF6E1UwJlQf@P~)tXnZrCP +NT{Ff4`50*Vhk!j2^}TgWGp-(dhu@QJz)erD9j?HqQh;c`SHV5(53~<8T@QX>j|2r={pjrqTVEyZiAi +0J{&Tt!ang$$*PrLsmPYqiscK +&St4P56{~W%-sHfQc`9gc*kYT-$ac0V@?;wWpJ1>o^#~;Q2x}YSgU$@qo?y1KN#lw@ZIV3Y1m5T+du0;o`4bo|=Woj1gX*!9wI(gd +H0C2un3X)>~NQT;R}ZzY(mginIB?5@krB4+3-n1njZc3MLoCB|DT?@hV_EOBAD74sVN{EJf&1T8MI+; +%^?L2QN4Iy;=8r +izlgXh&*ormPB#Symjncz$t)2s;i_7v-tg5v6HnnA`$51Pxr8v)18=~mNb8P3iF(5;?I +b9bQnvKi2_`U1b+?gMB=(&f=wY7qR?w)*B{4c;}QJZ4_ +rj_h~+>B!wcl^TvV0Dge=Zt;f>(go(;#-(;r8l$QO-P9DXm}DkSFZ=!ek}WH<=$F}2IE4<2@dnUGo3a +}o1xiSa;@z_P?Ck7JPne;}5^A;2E+yz6zIDP)N(ImQpSpKop$--4gaU>itU0`}?pd-iEKDg$;sohpAy +o|K4^I+!!kM2j$#f@|X?xTTOLxxITF4@mOP!^_5wV59)6g_tdY-6`_zaz!x??h$P#>}s*#v7kw_yaIC +uQ4c`R_iUu~tG5@`Z~8i>uJJ;h6!_PO5(P(o2ar}Vht~=<4f5~O00@g +oSJK3e}{|-0^Jnsh0=cA89_`P`RzG;U)290=H!H>*|j}HOeClAwM%UJ!?ee@fSyYD>_2Twp2ynB!ceg +Ef9{}s#aMABd>Kjn#lzy}_-`+GtWr{JTVfxUc=7N!3chkaqGqqsGanXICySBi8IU_@Nyah^tKM?kopI +E|5prHjzOfEYBvJ>V1exwJNyJ@a&n_7>O8QSMWR^utLY!HJ)uRbEyZUkjTG9KgZEka$IFU>E=aLqm$f +Ztrf#El7D3ivl8FotPW|*9Of3cMrrLAEO1g{4rnhp;N{9>TV|CIBjaA*iUl +o2BrO}(DpOz4mT#*trCN@Qm5HETMtmqCQ0^bVPKq)(8I9U4$2s7Xr_}yplNG0nY;@TEzD(9EV3P3)#k +W27`kbh&U#W!G1HiZNqobJgnnA^VbVVRXi}n`OblJWwSd6Fb>;i?-FpVNgkJ>%+Ex2b{<~54`0rB@{BP|a=I_ro*?&Fv{6*2NoLCA*?pl*>s1 +!df4e`ry4=(hHZcdmaJ>4va=~W=1&$s)jRgc>pVy8H6@6(IvY9`}yrJ-B4Kwk0$LLPS{TZtPt=-WHAV +IuMfNftAc64W`B9{Z)}$ruRYM->&N7vpZR6aO+7)qim(jERjU8AFAg)4XWADS}p +52c!EP}`bT-@DbpK=fmn1+XhIU{kd0p;UPb5HMLq^>39q_8p94$Z7^WWRv_nW>s`4(W{Uc`3C}$za&P +Uen^gx7xE^eK)(a8q|sT2?Nd%MQmOF0&}E@W)$wE^3$WV|xkfb}#?-Evjag5i!au@&jFwE)+XDE~;x4 +H*KMy=|&Xvezn_CNDR@kYq5kF|VmS&J^gx7!GdKRFcmILV#2x1?j>`D8kNFZP=EZ!L!1>G6+Bc%`?-q +FpyPHb;jp&aQ2d1p_;Te&Dmc2U6pz4*~}7#$#4ap5GR`*p;=aMAg&M+6KFQW5M~DQQPiv03B3ffo)n$ +yUadrJf|7Z&l6oIh+z7#iyCLpI_;qgz7PTPYOn$7WeS)_Pt3tArG&>kDiz2`k_=Rt$Yz^!Um;*~ewS| +==rH4cu!P6zGy6n3eW4}o_7PZ|fyM=6Mh2vh2>P0G_#GxAfdS)KhZvP+f(h~K5<7W(FT8T(UglJzWX{ +R9$k4|?S1!M$R?F8XPmNYRb-=@1{R;G?5yf(=!by8w#IPErUDHR +avjV9c??LmkTw<485VVnT|uj~ljRCEUKPF!eq*nad@!g}Ei%Cyxd~xovLr7;6UuKZnL3@I$c~6@Clnu +tNA|nlgttY9f4O4+ppkBso}j;23Tw{S%W!zZdgXRrR1;;GXESZ(S;`?WNM# +15b)G+qz8^n2(phT@^&mwF7B#VDAZC&%AP%xNoe=IMh@~k5X$Ocu&7O*~(*&If@cWnu5_K!NTw&y5r= +jxnJg-(37qLZcJcIUT72}(n_i1RJ^55u?JUt60%s9URSk$!*qyG;Y!)=8b_5DudJjDTQM%^7R_&AqwYD!(vx+>d-$H<( +D215a-5SiOMOr5s%2GOrM(DGPEvfTi!C?nMfsQIL-_b15sId7xXqFU>^EvU`b+k{QGcm6k{ipM{ZV>D +UK88H*?Umn@tC!#bwYL6DbfS%gQ^0k`_C2uBEhU*E5m3}9%-a+LjANyaHPoVlSw?{7et_A9xRg0~o;fgQClfqplYvf>RAEOc1s%e+6bhZfM0=RU#eQRtC@HKJrM!|HxGbMApwDA +T+DJ++@kk3*7W53xSbc~^n-pDy1kb=LmE)9pa=d#Idn`!i8&Lokjzzqh3g+{WY$Y6iBN4vO +uaCC}_`S7eVA)MYb-rDe;YQJf&3A&Yp@D*)W4PKZ7h3Xt8R|7gSk7htam9MDPrnP; +yS{3WnjD}Ru0cY88z0$?k8p3gLEZD|xAzu|T(yh_`-kVVlzd^Xpxe!n-v#SA2@kH*%Ule-HJSckO`%6 +zT?zT>A)gCxGAd$$@xU-E-(}tw2Ovywx=m337H*|S8wpWl0QW2!2o0SO;!JbfZRHEYsY@yBgU)97z-SE4)ej$N$6UqtOxmJ5*@CqB1x +k#p{pZsts(&3iBeS%8>F^8a!M!aneH|@k6OE>?bR5C!l}8`aGLzXsiH&{&QqW~!jdC+yrd1-^wHJhDl +Ne;Mes+pY=cgt;O19)^brwtvdCLi;1a1zDK&kR1K5IevrL2sfzZf<9m&8@;L`zhMkqgY%SVlJflq>$Q +D=CD6$QLLxlVHnQ}(y!+|;8R;e=C?(5tHP57GRdlBFEb;h4d!wn;P7aC4dNgfJV_G +1LJ56o45A%vd&i$hD6o+VGfhf{Gw7Nj<0jUCAwDin&i)_cUFH$p}Q1XRUlwT65 +3i8eho5qN+IZt^~-}a`~>bRh_NZyn8U0weM1qOu!yHAO>O)baSB2+JVcn~;2SZ@bFgi7%hw$DfTRq&; +}|rSkRzsvP2z1GhIk576=Dr4nJv9!k+S|o)CpX@%p+A+$Amkz^ty1a|JFTEdsYdPFvmi=x1Lye$I=~| +j_kQT?%-k%JlwUH)%LtMxl`Zmu)8T;^ik}Z9SgtHD)f>>s$6x{*mPp@VW>bv+loD%)9AbM0jotJL@*% +6)|3d+bgXv*!~`OhpJbNgyOQ-x*;GX^SnfZJz78ku*(%l$;%dX+4otkZokXBGu0M#KV(`we)&%_^(0|cob>kJ +xyy3bg??h(@hqc4uL1VE(M2>J^$-6ok}PL1gki~3=937NYvR|s_%(~0Q>a}CKC+dXcACUI^FTT?z$aL +<2eu`;1d#OE!&N-iL!uoQ +2oCxi&xm|oRrG`-iV0E$QJh_x^qRk7MP!yNU&7}mV9>W_HL_gXk@lki#~UpaBv9cJv~+7x&jn7(b+PvDm3^#9jtNdqKC{t< +}fU!c^XKZO?OYnD3rumT{;D_@SD5VBb*JK=0xW|?fB+D@#yBO$2O0AtE+_5$dpk8zrBQkAE-vuNS5*Q +=F}G_%AD)UWgTlC9u{uXl3G)IoBmZIj?UY6Q-6a-=l-63v4+Lwz1L(WOIy~W&alx>b<)Y0%;$G#7~t= +FV?0BhHJ2uoD81LBR3-I4qMM#vmyLwBD(p-H_R-9V9rzaxk%92qnFH@z+oR?@c>SAO(}9#P;r*8j-H) +>8VCwX~dGHRL%JnT9o`PPBR3GldFnu^3O(s+@1j(PLPAu(MerwYbLB|KX^u2r6+q8L}r#<}vcu0r#>b +*j)ANgp!32M&X9gl}1Cm?dRU*fmj82udNg1Ukk8Wz%y<`C;yY!E6GT?(QX&L^e#?J^#H0JpU8qlOc9l +jlzaabM)|=_B=+-v#5_(ZiSf$MLlX4U~25n=Yb5v@IVOC`Q&5fBit0uN7NfQ7f;a^O{OK-({dQwj`Qb +%G_MD$pQ79_HO;I +D@I^Hjdd{nZ!(1-5HntyxKefd^Qv7mko(V>q$H~8Hl-q3aH{k7e{ufY70|XQR000O8`*~JV`M_-R4F~ +`L6B_^kC;$KeaA|NaUv_0~WN&gWb#iQMX<{=kb#!TLFLQHjbaG*Cb8v5RbS`jt%~@@4A{Qg+;;|GhIqN+vBS8}!4q{*blBnTPZKqLsT!8^@#`N +&n62;J!<9w)A4d#6;T@#gN`+s8=x +7XYu;2#EcG@}=7w&2d@!|f4=}H(<#b&KJ>C5jds#xqmIK84LE3szzo|kgPoyZ|La?rzxXP2+F$z5r4s +qmjkbTsu&iXDZY2QDpIejsE}W0bQpdM{V-J3?dyS8p6!3bf9uMFbJZ6p0xcQ)mUoG~yq%G~Q?u?7rpB +8zaIF7g{=agAtT3rGC?bd20kyCFUZhteMh|ZMo&nnb-_7Hg3Sikx2p*#C>-E=XdFI{1`OTfQ=>{YLe$ +m?R;TsL68H9^3e!Lwosx@wT +&BHLlp~oTwAPeLkW}h0;BkuRE6Fl?;IJjKZ>0;$IF|$$uyAdAhVhTzT ++@2G06aYrJ+9(1|sGlhUec=5%j~M8(qSRJQBK);1h0-s82dig+Fk*$MTZN0@X#V!%-48Ritk-K{D4GZ +Ga`Fk9fdGKD;YzTJeFuA8@{OHeM7U5F=^^{ooL!{##MhEP@v;_I#%MDcV?zH^7#&!!3R5G-R-&L|lqE +J=x_Uf)rRZlOLjiC)$nABSF||B8VMBiZaw?E0ksff5mxxB2iI)m{+?lKJ6AtHS`$^-Q=1`Q_!V>lYZu +DBO9m#s0tjG5j20+jxm=Wd({!7^{+#Ff0fD(!o=c0XrV-&P_EN*)#4il7b%D83tYes*wf?GEkUykWHs ++r^CR7k!H&}8F=Chd~YvY;1v8QuquZr9vn&7({O@lQ~HCB7g3HtjHYz;EVkpBnvtWbq`~pmiyLnt!2M +9*lIti`q%m2IQpN4r(gauk*&d($sLx8WSW(fnze$K +{RW*M?2}s6JzmZ_6gErepRsqbp|9s54`r_@M-@!_Y&DC}O;=;ncLJ47LOmIk2|Guqg>Jb;AQx=M+n+a +21@RR^7Y!fzaVJu};7{H-abrh9ry0!=1zAhjDymJ1u2?|AcwW$9`Zr8{z^j=A02QmI2_~ZVN&6drSZ6 +c+S2ZV0?dGJYpuPv!AZ6KYY4fhWudg0*-0=H>=AAMWV$Hi@XuXIdt5$9LSqF~TL2}lX7Do>)fUYDW!^xTjAbFYkj4Pwd=TTbt2U45PDW9mdv=j2Yn?ir$EkHu*qxFTXtmV{Z1^YSJa +5yy+>N)%A?b5A67$2v)W=n%(9>6X-PaYM_Y8z>nsCnsVjuu-L>m(KJS;_>m8N<9wZqSGuy2GvDr>?DC +Wzj}DMd&sZuuBn<2zOU$ME?(#hd>H@~v-UxjkiPKZE3)9Z(lCzKs(^)_n~hD!&G6!Z-ca9scgK(i`j< +Nhm?fKc9N)DsXHcut?3N{Od$zhRS9B?q)b^!QP!1l^z<2c3jhEUCjbB=0001RX>c!Jc4cm +4Z*nhna%^mAVlyvwbZKlabZKp6Z*_DoaCy~QTXWmE6@K@xz}VxlG{eyDrs=lqy0dALO*50-WG2q`QG! +TFLPHTO0a{TX`rCWX!G(ZG#ZKDJw0cM+5;!=Q?_A+^r*>?ySofyy#A3nZu2r33e5F<08?msz&(7R)BX +{EL?1J5G1*^oG_qAbKm7j!trFxUGUW;qplEHtFa#?e&y?8Tw|)XY|^=)2!qTTag_>6%_ ++Hc`)i)b?~cIsxIuVJO^Txc(SU&B1OU4gW0OazJrg~)@@)3j&6Xb+wbz5jCF!nx8LVCbH5(J0gK>FY-Cbqu3Lh5N*WxqZwV=>a?XIupma%soYeo0O?O?P0R ++d|)?nT$hN@y}=EHVB@ndgra`dc{5ysB=!qJDETpP#Ud)QAy6ofZZVyJ5VpEt|aKJ7Gkp5xvXhFRBsC +<>UBn)Z({^qvHw``yl*B#wsErLPWwziOeuvj2GRUnwjCW#)85tvALIw +l5M{Gi_^cG?muo+^JlZUquSw)oxi!v8|2n=X8_o^_5RyZZ2!ZNn56*t5ZE=z3TB#Nye*p7deyMD*`PW +7MxwYD0Z$p*9vJ1RZLGxuEjpy9|szzKw$R&gPRZ5^r}zU#g$1&G1k?W9F_f@~|U_s`fG$GUg6)dvmd_ +#GK*JGBA_INWo1)^k=w}>Ss@%{b{+#136T=l?o9FPA(xlH`0i}1*&jRpjVtu%<1j)pg|FG-)_y7Fnb{yJPzLVM&1twAa7F +;Te;1)YX@-`B7$34Tc0v;>Zg&R6K{T$bpxr?%Kux|O{_@9Ic-7+;gI=7Q4Br~iSoeKyfmt_DT1%pG#@A}?~vhW+L4?tQRY6it4DjV_R9>^-UDO0$>0e89!3M)$201Zdz8svU6%krh=PVp +t%j8f!qSI`$5JbU_e`sj)O9iBH)ihiuBauLc`$9R4I +ROhdo1@#tm2Y&)WW)uFyB_3f3bO#RP!5m}>N=L=sM+`8Yum#m6 +bD_0z-_GYpBuHx~_v2gGjQ<_)w0?MUC%uNP^#ABoZFpxQ_J>ShHG~#$8j6H=E`5HfL;Zf)IUV5_tIEr +6e$Fo0ENmkjK@zg$^2MToguGZ}$$~_oQ?E!BXLQG5)OtTfNoGE@DFq{gUYCz)A8oFpbR3nAOhCS9(wV4aTDSn0Mr +Lo9#>}(`PK9VJ-g5h4XU-2;wPzsD_l2QR@O}`VJES&~wU;?2Tc&9@5A8csDL@q5?B0G+jFcBGVmG7HOLmVOV8nY;1N1)+5HVNQiHj(W? +DY)4c5xJa>iVhLg^UG9lxXbQtN-&7AYkkEA^vqL_6E5m&P>Vm#cIX*Y`n ++X{~|VK^VB;gF>JxWRF4_;Tru&_6H_PTG%XzYw1KlHN%Y-*a;Ti9U-SboECjtF(CuScy +kLoXfZgly=QN(-a+cu;X#O>y#Zkh+UXLO-=0D%N4%=^+Mnd?Su4xENKX0t_4#2aeAHNA5XPQP4#iiN? +jjA@tIqdz^OT0Gr%O@Nygai$;hFaj=FuemkP(L7+*G8d3}0X>E0)P&%PF^f@kkh#880ZlY=%Kx%IXnzX~gBxa-yX6O-O@ +&Y^NkT8y$wB;!IPa($+Yo-AcyKrKi1bRoH!&65u=)q%}$bqL(<+_tLI7E43ln}d{lCC-7(CtMvY3>U28|B$9FuuMF~0y-dP{g>1k>%fOIzA4lgRvil)Mmvzw`hDQYr +0r4er9|BmD6A~DDFCgceIzKUo0P*(9(E%g?!YoWr1xL7Rz`IsoV^xihunx^Vgp7Cq!)zgOhPx{K&1$$ +7}WcEoQ?v-~CF0^Cf0?d$|z;d=UI~nNHK%Jt&=l)~~c17c5Q?59@%}#(Hq@FDMBK7%k7yYK8D~%}#j< +prREKlfsO%f!G#c?h9bA;&aj(?urWJA!gI&>LpvT&Ja5y^QpN~lM%)+_WYOf(rS9g~k`Fy={rDcF-z{ +smA=0|XQR000O8`*~JV=c|?Zr4j%D-!=dM9{>OVaA|NaUv_0~WN&gWb#iQMX<{=kb#!TLFLiQkE^v9R +J9}T-NRt2iQ}l>sEe%E{ye^{~hLGSS>=DQsnB5(4glx63HnODAgWx>wvtLzpx74y^Nbb(R87#Nz)z#J +2_2^8~c+OmRTI5B_U6%#(Bu;bYjk7o`a_)-XosJw&d1s0klQ<0dBoE>!Qz(%)=YC!!A@2wn+vX1wp45 +O&_3}K;*a5pXvdN6kxzS^WSL8GNp2b;?--toqo*4hL;O +PP%rm1%~=TUBqI-NE456Gbkj2=Z#Pfc#0C}U +$*&^X-Um|qg4~DM>XP5490Ai&a0h1u)W@@Zodz;gv&DUF-zl^?Ye>1F(kiP}=gL&ux^yj0^KSuH!ur@ +YFKRaLh7GPctPa0?)1|NZIwjkSpZ8_GVA>jb5-#4#6+}!jJ*Wi`*=K2GTXplFDFt`MD=&YGGj{L>wi7 +{?pot?ZoKOV5J_~ZEe^62!m*MVg?dH?&a`~K(>B}U9^Qo^&|&bJ%e`m*z_`fYUH4*n@Y4IBoIf!^@tF +1$Rw*x6QZfzFp1fA}W;e*F?8_q*RL!=9oM<|YgtNLoFH?+%m{R>#eXF>Zk`20$4OE8&bV+u4Qz#phec +z4y|<6MGK!49#?nJ@VN%+3eXqGaQ3i49O7+O1q9SS(#&f+5fg~;wvkYx?4rjaDdESJ4MH-(cEj&yk4S +)R7Qg4?o(1)hfDN#+vxxNOsAvK-&sjtCwjPw*-k67?G?ijXoT|@boObJ+njH4NhXahSVzAdT6`lTmJN;GU_H}YV2hNkLv*F-kF#LURT>6Tmm#>Zouiv~q`P ++Y;zB@aA|M%g=<<;;1_~*a=@WvCLPw!^IzdwfaC{F%Nv%I+f^sxB6^=x}*cW?hcp8sOIKizEpw}I|wU +-KFU+hE35!~S<1MCSDaTW7)_fa72fa3nEp&yOqw24DEc`PsldIo8OmZ|{9wx6qp*!6o4VY4ml;{58Ld0$%NuNeHaeF_!#9JrQ|W`*m+rp +q=Fz=_cL(RL-1joB@j;5ct0s)FG>w&@`wdW`iboJCT}iZ;!=qP&)4|1s`-23ox1G)Hks_mX+rTtiHGe +ZAO}KB*FD~8p=fg{>ec)uSXJ6?*R3V(bq;}jdbT(RGUgB)l*G=pjrM&W&CM}+u8nw@%F9KKdu_47vWKMycu0U=J#UZ0mR4Z*&$WkO%!fOd>;9 +hl+U3a?HGVP0V5*9XAAUk!%;Dj{DFkKVk)U`!2YbP(+jFgl${=w%RrrlDB{+@=A)foK^nTb3gFDwX&XgSj&nmD%!M({3hytUcO7y@S +qMO7s1MLwHR7z@nGAe)E+)B=Zz)EMz5UdA_r2t>((93orhO(t>bqgW714`07x7^&LjeCWY-voOvxNLa +w8EincL0OJw=&uv{Jd1XH3zDvRQre% +M*=rFn)4h6#3}K`XZC()+ma@rO=JEm76uww>593M8yv}(3DNaAKP^RnJ)Wp69Yo(l9o0b +wA$Lo?xL5y{XC4B=Am{RS%3a4p=Ppu6scZ=-h^Q$$z{>EIQcdkb2+F=zb2o8;j__(%^uBzZyt3! +9KKq+bgC6Z?XE;`;H?7h1pOC2)LfY06o>*0M7DBd;&u37d2dRFH +hl;Q_zseq@O}63`$a9_GV+w!hJtMVEL?l(mzYKw!pI=!sW%QeRxgPl4q%*s +ycoDy^i#wV0!nbMQgR7sv!Rd_jvtAVU?@b?5p(Y4V0XTTA%r=Zk`jqz4z2i-&+H%i>$PN4l@HWV;93oJC5b60?dk{plc$Sbedm2Q(>DM+HzWSAI)s1`-V+ +K<2TKJ=+fHgyocpM94`Jzsuk{SLs-YC(1!J(bH8mft>HVq_evO?eK=;POpL>qy5Un>$D>=|=lq~t9(o +6Dm~>~lAW;LHwucQS(`)Wjeb2uE-dcp(Pk@r`24Th&0l|(9;f23oNsWwO5vW@PD6AqaRo2i^2_ZC8Ask>I$A;7nyGuEL$g3GkQ(gm#stdy(f{gO +fW!c7yoJ&hxW0&A{VJaEtC>(BTZSEBJE8U^IE2b-HWicL^6HrU`5rRByyL8vP4A%f6Hth9`$x~psBrr +v52DqkDAZ-aH4_EQW5`5KRv9n?vRVh^H&BwkcJeniE>^lfJ6LSmVkpvSktEXueaiq>5q<2XrK)+fxA) +hJj1*{g7p2m0JxG{J!#9jCvT=!;8 +F+EhYX*W1oW*^6a=o`U+#?b7NjtLEsMq~)ZPxme1@iA=)9K*{yv+|4+JESZTTa-M7Kf{XSjgpGL|hnX +Gld}_z(U4NF(L)DsG%S3;9bI;zkcgdWRj2C{qiM)xFBF_$F*#wVQs+f?Qpg3Yy=H`_Dh2UWD>{F=1GZ +s0<0|)GSgor0mkO#scAvzV;F`@wi7;vNO$tg)Bw948gOE7aKjglw1j +3ItowF(s&_NpXT}0dvA?pFRU236K|VxQE|Tt|U%Kn9$Rr{WTDV*D6UQoZ#Oo6Ob{>v*u)4nS`jaiP#B +3wKB$(<^aNqd?`+~SW>tlNsU7HXp_LyVcAx|Bhn@YsFF626i?S%BW?dniLn@(=IAmF<2xh=y8?Rf4G- +d^MJ0N^&oH{7dPXJTnu^zPI{=8s@)R;|9O=rmTTKF1QcMBnU{lE&mfdQKD;K?#Rh4`CvSlT}N+(g)gI +!GHOj!k@Y*NyeX<)5L8o;3d(~c%wVL)6l4k9mIxcMUCOJ0=H$Xf7$c^{5i^+KCWb|cnyRijQ54u&AP- +v_d_PL54Sx1?QzpDL6(9sCduP^#KGb_VBXD;-Ho1PYBe_#u11kP%Nc5lLi;7n#qRgz)nj>;hs{CKBmu +!gc_8)M*6=-V&o`*X!GR(y|pj^b9!KE@Yjve7fFoMy=31%=nMcdA5R11u4$K_Ap#R^%`BCb_GfcC+#_ +;laZ>V0}I7+LjZtcTVz{3KaF^!JTEWnG@lNrR1JgF)(^u|22WC{t7xLZ +g%dESCk7&Y}|>ApZzamD`ENn>fP+Vqhpdq-ZhFt0H>d$8DV6i=1CZiLZYW}YtUlcsA?Ns!p?J!l?-P; +s9O*;Z!;T})#q^op*bEwxENUEmfo?jpISx|-M6Oyeq2Q34f5v6oh+V(7WMD1o}6Wmvri(R$mc(qZc~r +DBn$ttnGkOpo0BBtN)J3yweh9XAEO84Xy=iXbv4$7e_XKe{N4GhE89R5`I$v<1gfURIDhQF0f +_Tqbv~WOT5?0SSs#!&e|F4(&LyXsk7nEwz&#DNRa2%Fa<$n3U=k08+74oge;RDh;XvV1aGIGI=8mpE= +a0c5dyfG`!XBhT7~U=zWt~+9HUrZ=TZm{bjo93$dLcQ^Bh12b6 +y>ZbK?@Bs7Y`j>jxK|{c=UeSO1*2SupCvFexjX^0_ZJWW0M+|ai;k@UL~W&>an97Lez1Qi{CBX%6j10vfh>Nv}*0KHTGH-Z8Gm8wIa* +IlPz5vf-nH$^OpJuU3wFjY()Waszry8pkTW-z^fhtP$G}{B9>(&S$3pnyjLe7F3*q89X2HM5hbxt*sT +OX#wqtcu9DQ&&xN{Kw#9qszn<D{82l1Ky-P->EP)h>@6aWAK2mt$eR#T!zzj43^000~n001BW003} +la4%nWWo~3|axZmqY;0*_GcR>?X>2cdVQF+OaCy~OTW=f36@KThm{Jd-T#8m}1OZ&LfeSdOkyJ8l1%3 +z&Vz_&joN{(&Gc&6df&Tb>XD-VnMLF(UOW5LN&iUrtFRSX-STD%h#!OWF;z(2Bhi`i~sjR6*)is4TMm +v#CT4}wKdNT2L|9T?u3Upr8lu2sS7;hR?Oz5$o){FPy_jlGB`wUC?EZvB8vRX;4S1Yj;zfNz>XSu(}A +LV~E_LC`g6yc_GG41i|sxo$ENtQ=H^j3MAewj=rg_h2VXJKaVf}qMYS5d$gVBA9faHEl7Q*Ad?~U;LmYgt^c>CeK6MNH%LN=mvBp^`$P|aq9@mlyT&SX>UfmNH5EY7_2RCPL +nXtg6jt%A!Ua@Co1&aB#a@h<|jRt!o;+7xX}ECP;(3vc08nu$ +rbMhmiOen1RLkrD-nxz;z^H)OrriV6YQYu@Pk9JA_&^|JWW2@ySH5M_g7Q8g+Ih;7qpc>}nB$;MVkWu +|=?yfu-0Q1L`&To~Su+v9OWt2XWW1kfq|VlUn@Js7G@1!EvD8A|s$WsG1EhAtc96VVVa>2QFv?OVo0Q +h=ZY=W+5P0C6#8&Xm2(It?fJn-&Sq5*G*Ti5wE4F6zo}&E39h0u`b3)YZR1)*`ky}1)o>y5qNxCFeGk +H(z33trHYQN0o6XMrZhb$d`J)AG$w=2APfZ8B%K!@_I?Y%(vV{(w$?nTlB2J;vTex%j)hnL3c=R-AjP +El;Y}sc``8UyOsYagajompY-cVHOlX*S5hxHzZL(E8y-F+YQC5_WWlq@4YLo +4kJD}?*SngbNZ9j?Ung&gSFWX{yYLAADKhcJQ}YUMZ;7L$ppxJoZ@rGxw{)l^3OeJN(I=Hk0l#V*9ZV +m*mAp2v8x210I-j8V1)2`vRWr+HD4DEyzWokWlkJkk +k5ahBe|L+j@{eqhThq|tECR_A&LlL7N*n2CQRya8`%5L`JTx@-ETQb?rnF1F!a9h>x?< +Z$1umI&}>3DzY=ejqTO?`dm~;gzRQ8Tpn)WvF2!FL-yb2Rt~yJ2$E;V}M(L?^7vho@(r(xzOq{(7HqYH0&k7zpA-!7|9 +Trp35%nQpV4Cloh=2>?_kG<061+?4VNDfKke`c|TQ3ByUu7nQ0)nbYT%wUzRyx-$G3U%&OF&Zzb99-BljEw@%=Hp1M4g0J2 +J+CxW@W0iy+*=`r%Uqx=N|QUGM)e5o+mnLlJor_2KkC%Uno5-2MFX``c&Y{tWHiFZXdBVcVGXzr}DGX +N04}g{6%`;M-(1#=DxNK)}8erU{nM@hFgn3?GKap}{s7FfbZ9;;3^&qb|)V(4Y3@IM(?yUD=BlOYxVR +n?rF;(J)Ax04GmED60_Fubq`7$UV%PJhByaTi^XuQV$z#|x{nn`msd*ie516C5C`|K +}D;_3S+%S<=(ImsD^|nQBfekxMFmx_LX_p%LEskM4q&u!0&hUm4cSR_kGdJ!V3du=|XJ}D&6vo9o!(b+io +-S>!t2{VU8OnSS9u8pbQ(-S1JyXpvLwUm$g*$&ETC9Wrv4&&MA%^E;}2p=Dp(Q4sid8SC67e +R>=8;6l6>?K)&$nMw>Od;EAPj5qdtP`E|m?4dhIjPM!i9HdYBI&vIXx#t+l4LY^Oj4(?~gK4|H2R$s> +&t%}#2aMWzVg7!T2xgs&HIDZsBr(wqBJ=NDU&1X8Da&?K_n@K~5H&|2;#Uwt@*r*4}&w!%*AA>+H1a~ +N|hv5WOmcf`i?Dru(GEJ`C^w=t!eGWmsrsb%u#0YKKMn8FJE72ZfSI1UoLQY?OOeA+ +eQ-qUr(_Yqo9OJjFVh@E)g5B9i=hqI6>sJxJv?RMXoH?)KcIsE$fN~d5r_^UM^2^Gy6d6^$5JyY{s}=6Y9Zx=cP$57l|P7hmw~YxyU$4maCL48JAQ +h@HLwgxnN}a)3f8tpS~w!BaR43c}(+I33NJTM7c3wrlP5zWK=S$7>G)#O6s;^V&$ +A^7!+9Z#oP{BtFv}$=cmmq8Fij-OX)1a>vw%+{Vfg%%&R8le)A{kq_~JFAe^mRG&PhL0yaU2Otn&|!zckHagoU6hV6#tJ$3$3ll +f>GD*h#v(E{BLv14Tr4I|%Z1L^@tDoPB%RAfrOA)$bF;08qG01FIW?I5N2>10B#*=C*Pu_#rI*^R$`q +?C(buKzF%zuu%NG8H6{2ki}65p^Xja!UL?^%AAo|u +NCMdT$XIv*PK?m=`V0ahL?##@=yE(e?7Ad~O0~b{0tj8PKr4%{qoX5xXux6NozUp*#V|;rpKUk3u!c3W-rMV`N +T+RDht|<}P1itff>kql%6Ni+u$?*7u+)9C!n(vk%$Ld2B*D&9dua)mJbz!EE(8hb4tQ*to(&%yK}<7{fpQt)e@t+_Qv!<{c64XQm?2Hp@bl8F_KAOXJq +dVhcayP)<# +%QDp@KC48u!m7YH&svamo$Xd!NDO$4ZCJ+Tss36JZBfwn3S?pCrD#NLgdmAE2Xh?`#mcxY4vyuf|&gu +eIs9VhCD +#5qpidu`59ZzKZpRqm0m+S&}Zz(JX|0+}{!1%OuoZRlep-UG^tUF|6ftnY%nzI-}5&~;ozMg)e2NGQ8 +1Raq2bDrRGQ-zRABWL8KP1S$lp@_tI8VMrpH>NH{Q9F_VV0-S76iow1LG%r9-_btXs9F{B;;25XLtOV +bC6Sk-Lb_U|>D8v_DWP*9Y0fmM*XIEHs@@WHTDhNTzM22Bsuyn3-Pc6JF?<0Y4GMBPXZK0i8oYU)#a< +P@W}rjaZwo)29+TTi`vY;P0ucD7F_NEIj!3e +B4-j@NLRqS+Ab7S7)s3O-#d*@Kpg7cuvz4ySFm44GF_o5r-ZaAcdmpH8R^1#gD!Q>a=k+FlkH)$KGJJ +k(R<;Z|%BMzpyRuS+tfBCROi`*+F*~N}VZ*Gia3I=Exvn-QWpMUOe|}*;kz$oVQu2xKZDMm;7o^pCI;tge$1r3%?Lz~r+-5lj(dov6fXLN(Ht5qu-%lE_Qx9)2e0 +ZYx1(jtk$}8(2UW +xB_9AYpeYpD5Pt_{KqG+Aah80_{+sHl9Wc3z*DM*snv0b6fU)19mSK|*0yvV*+@qkoWA529;P_wkS-;!mX&@AX!OTGw644b(J$UBvpA9BfIK)V3}8Y0Da7sm~hwl9C?l1^(80~H4yz-33t +VIeWq|%Ev#&z53LEyD&}CQF@GC27;6Pk&GYI7RQK($He;wWc6Cn3nO;%>8!Fcsk8<5ar@Dp8+qJQbHN +|_yIH)Q!~973HDNU}fGE-=^(l#&R9Y?E3n~6gQO$|6Hh( +>t>z=e4ew8`gbWyd|%K=0?wd&`CS{nWYZSWhy!r<_W!@~X$@>x-#M`s{yY1`KYOS<1L +RXfwB1|x&kY-Vsrgv=+4F)*gw_pls6I4SX9fNUi-Q8sYb;f*Sgll4#La7EVH>X+>r#q^j(3Av2*LUqcsCKoDYz^ICn|X>Q+If&>bld)$i6K +ZCH*DlFd8EdNcnIw$3nhcqm`Yj)cMXSCMAmg~n8Arwbt3F;^+pKp7MFwWEs3SWS6$=4q^d;L6KV1n%7auce-0N! +e`4gumjNS(;>ucFI?|{{|it{0|XQR000O8`*~JV$E^s91qJ{B6C(fsA^-pYaA|NaUv_0~WN&gWcV%K_ +Zewp`X>Mn8FKl6AWo&aUaCwcIO>?t05P56$CCXS}KJ1wu`hFZbllGDx-W!9lVk^er0o*w4w7vX%dbKeroxkI(FW2fN+Cs+?5^sS1|GC;5 +EiHc5W|EY$u`ZP?fQpUhbbhiyOEt2*TwdyUU3yV0piWLC-z`||qjiIq~am*2jA|Ks&Ld&>*9lU1zJvR +3`r82&T+@#6>kma{CcnB1xAr&{#qL6z)HrIOXZ<4cvF;z}iPntrmq%w!Q)a@Q<}!;4TWdOFQG +DfUl=PtZ)Q$Z2@;t9B0_rfla_e6!0EQ-SL3CM!*qFfJ4AnWWZM_;49jh6%4poPJ>wC2)MEZe1+mT!^R +Gz1Kxw_YCPa8Bj5-oz#-sC{SZj&gTA?G0KFd6E>jvklo^lq+Jl){S;FJN%y^c}K+v0GV>H*s4x}^FgK +-gWVQ_S?Kf~kll=EO2+H5or`$ptTaw`y^ngO=C)~zK#nVjN!$5<=eN4fUtI1(V&ycq}8B-m7s6HG6SO +&kHybD9F9xn6f5{p2DtvoEAPA}|1%u~yFjrg;^uooP^Ca2iBy69H-xGCToFp(Tz1OkrS2Aq0a-ulvl* +u(1Q_6!u_w$s&1)KsX7o->xCOuR~lE2e~ce2i&ebs9&a>2~lQ3+G_%&w`&Mf=+4lHR~`Yag&U&G%vib +u=`*|s|5D{8PG`h%NFjaVhV|KnBT1fT8k*+&=8XufPohI<_@n`Kvjf*R`X3wFq|&o&34_Ec$4C<-$&` +BAqDkoX4BzSQU7%)LSXyI53R_z)@GF6!TUmiRPhn|<5Giausv!EhHN86S*Mv7sVsMNUwvJ8U25WNlbU +jfagh!U$p*?qxxe5!o)dI>vibL8c? +#gQsFY;5Ijjp@POCARBEoJecvR(dhyM!{T&nYY<2FFMfB*{vZ+;^5U?9@f$b|z=xcu?g)wikg?^9B;V +a!N0WwxAd@KoAB9+5iCz5OxE4G|oyDUESvu7i+)YJ388rVd64Tcn^|jyF4E4&}b;|nL?xeX-d|gjy4l +Jtd>UmswQg{qV3pdIS0i`N70dN;UU~CTRYTfnJF|y1!zZM^zG=SU9{ac~kXjPT&wuqK +1sDffc9$IGG_6+km46c!U8SZGZ<1fFm{vZ9&|qYp72c;L`^9zyLU4aec +x0lXAz?sB8xR5m;2ScHHk}w8-9oVeUyenrGS#BI5rGf{56wrXLEr(HNp!~Q=#KF3|{ENUTtKi{G58(hkSuAIUE)RvaBbR2_g^Vmo=QWuSrNm( +A$TlKEYaJIszk=|dE%IFwp@38$qTr1!}osElTa1gO@_k+R}@4Cl`yK+h+=GxRA(MxobkYh1Gbsc*CQf +Mt9g1T>1{s*XUEzBpo2_5r78gHK@R7OeE*u-_C#-OzoYJD3IK@CNi0?<7wC +!2qM5dIdN=!4k6P+)K36ksW>ZNRSlQp$qwO0z2{d-HSc&#fdO%`PWRd~Ab!^u*0#voXef^OHu~d7kBPEXg=8fk)&tNdfGVtSoYm}9$x!l^K`6<{Mg^f +FZ|20z3cif_6hB*Yc!%L3D0FA2m0*iznS4y7@-|pNP?z<`6e)@b1>l4g*qa@6k<}#rLuTWYX!`=L01Q +roykN4AW^C@hUff^m-!g}gKD1tXy83)f+$;_G4I8tnkYG7}K;A?0B{!eL?86GRt4j$v`)EH&1LJEVw_ +GsPf&@8KQ1I$`9t`zEkhFWKQ)p?>wJCN33GHGW;g60up(Cv+EcUnd-CI&I7Pt;OrW}JlyED0e`sn9F8 +-u5a5d}SKC2xzTwD!9fCNY?xhhu*_tv495!QaQkyyLW!+$BtXraDm{zZ0}UV9Iu!Vr(TXar0%X{p-)* +h`*Js(&AlwYGq1}ko~13yOBH{^pa!*}7d}N)o);nruPND+eAMG1;yam^B7DUB9p{qt4!0F{=vYI5o==*JC#wW(*s&BSy%v5({j4qf96 +gGj2aXh(pD$a1M~AYeeM%BuLpvT?{~kR_0z;rJ3-Qkfpk2a`a88z|9QpOp-%8H34#~+Ma?vJ)6wiuF8 +bP^l7rueA3^ZjK8jO@>!?-L<(zcE7Do6W%B0J~w_~xb)tgsrZ;;>`xuq~9?-kh5$*cj}20Z>Z=1QY-O +00;p4c~(c!Jc4cm4Z*nhpWnyJ+V{c?>ZfA2ZbY*jNb1raswO31T+eQ$ ++>sKri1f&87o52y%s3Y-g|TVOZrPX`y!Vw>tRQ*Oma +EjH;mWu5nw@`~T+Anudr3H^Ap>e@^eTz(k +ms#Zo+iZe5WQQ+2}fx}!35%kU{ZoZrUiTEy&J~y*L2-Mv(xcpHtztfknbSrJzNFOGmG*;zmYatpYte4 +$Q_9|4$q``(y`ZMD8$;A%jt2u)oLxt3Sq`b!o37jMwm-B0aN?DB_G>qjiFB?(hgjF=WO!lH|g|Msz9-29`;Z#EI*i(Itp-{q_y(Ip| +HU%D5@A!V9qvT3qdb@*Jt?yN=90r`?P29YH52NH5fhe%V9xa=NQ1y!kGYXg5)`NEVThU+^8d!3IPbx` +-np_U*?9eha5Z2D#T5;m6;KD7bb{=O53{(fzGBvuuq6q7Q31mUAU(n|Dv6Wqi-NgQIsxwN_FVcaM+X4 +ZDNAU?Ju-+7B{2y*$4-K#Q=<<*MAczTbQK6c}DPO0&gFJL%*FX0 +PsIJjl&0usCpeB5jaaYku8?LS!rL3CjUe)oJ36wDj*j6pMZpoxVK$Bh`SV?LHrcn@TF^zC|a+##03{q +B?EYXF&!4eJWWjj^bq9(zLAqE8b7>3AnYa|W;LpUMm8xEm7tMo%Wj`IYRhXfLU5$>uS?169!J*eHTWT +Q?wfys}TxY~(MBdJX#d$Guy_BN9|vT@{X)^`@0$#k(9kKqf~;D^Nq55ZYSGdk^URHjoq|NOoeuF%=9u +J?u-z57Ay#)N~=*+5~Ttqsu`Tx_>lS}!)oeGx2EX;pps@0UOF5|h1aKgCcHK7=XXbk$rHhuqF$*WF4W +hpWTk4rT$RnE}5(+8p{_6G?p*7YS=j66aUWDd?W^TQvUssnwuhI)N{c_xp>p5#g +zbWD{VG8B4t(Giw(%(m;$el+MW0WPFiOjtf}K+-zs8eoac&^Wy;MZAr-;Hmh_=$~$dh +|^K;+9|nr$O{%EL0*Zu^&)Jl!gbCC~Iv)g0`4r-I=$3+KhiDiSX#p806)%Qk@UdQXUNL3E4@t=pRLbn +L`CYuV$m$9MwMg-atj8LnWk^>OgkGh>G}He}OdHj)&l$VQ6N4^x=5dOyW9GIONuX1#H;6Wd$tI6HvEP +u4C1Ds>;c9tD21iU#&!-7PN%TCLEa$5c5sh?5j+!=lV1%=DD$+U~eLnTg_fddzH5GQe +NT<{3*`6vKGc3!S{GT1JXS-DLc!Jc4cm4Z*nhpWnyJ+V{c?>ZfA2ZcwcpM +WpZC+WoBt^Wn?aJd8L#?GDJy{lHCz?!}ldi_!-v6r?ILt*lTVy +n&yp&}+&&Aw`a!TTQXSLW4FDCphy?WY6%@;Q|y_}XKapXfW=tl#Q+=z>G<`%k;i!`tA&Qe8rK_l__;< +zgcBJt$x=;Z0ei7<03G?g;V3b%CSW$b(=CKIzPmzE}zRef_?ie;X<5@?d8C0UsjjRp^_t}2%{wzjrhF +)k5@=V$JM%4AX0SK9SWy(;O9Y6<67dr`Zf%ydPXFP3FjkhODLj9W!%Q&qOy?Sg-nTunLe*i%z!XL2#& +H8y5<*BzUbGRdeDwsdp<_m;fItu3poxFxsXELXOE!r4hxtM&c3JHlJv*Bf^lWHvr^{ipZr%W9e4Ji#6{j$FL@lcQWg!pi)T+Jn9Q& +PXh5nwRa?YJ+Xt65B#e-MJH~akoAa(O@XZog$Y!5dG`ZgMPo$X>6Vg*Y>*2tykCmerp{@AsmEoH-ryE +xEI3x5FUhZ=)=+nc^FJWsC;O{H433whf%0B3YA8o(kN6Kg-WAPX%s4rLZ#6#gmDOE2t&1z3ZV{Rs5kQ +U3g!pQCUcAVA#se8B87cbGlqCrsZg-zncF-y`23-yPo?-xuE$-;ojXQ|2S)nE4 +s=bLJP!L*`@V5%ZXN!aQYu$^43W#{8Q34f6@}Tjo>dcg%C<_snO^3+8j?56mB#FPJ|ue`fx|eCZ$cD- +K>W-!Lc4Uzz?K`9tJSkUu{D?D&Io$(%A%=5Ng3nHh7&^l#UkxnTam^siUJ^snWT`6u&=dCk0G{>A*8` +4977=G*l8%pWFM0QOoo`mm`F?#OcW*>6T$?2V)Tj8Cq|zbePZ;9(I-Zq +7=2>&iP0xUpBQ~&^oh|YMxPjcB>G77k?14QN1~5JABjE^eI)uw^pWTz(MO_>L?4Mh5`7Z%Nzf-jp9Fm +p^hwYsL7xPD67)&XCqbVCeG>FZ&?iBk1br0xDD+Y2qtHj8k3t`XJ_>yl`Y7~K=%dg_p^rizg+2;>H2P +@t(deVmN28BMAB{d5eKh)L^wH>}(MO|?Mjwqn8hr?T2z>~B2z>~B2z>~B2z>~B2z>~B2z>~B2z>~B2z +?Ct81ymdW6;N-k3k=UJ_daZ`WW;v=wr~wppQWxgFeRjbn1VvPxu2wRR8xjHoxV<*N6YIN|tG++qb_>{ +{v7<0Rj{Q6aWAK2mt$eR#TliG(h+O003nH000jF0000000000005+c00000aA|NaUtei%X>?y-E^v8J +O928D0~7!N00;p4c~(;+8{@xi0ssK61ONaJ00000000000001_fh7R|0B~t=FJE76VQFq(UoLQYP)h* +<6ay3h000O8`*~JV&BwqszyJUM9svLV3;+NC0000000000q=CN!003}la4&FqE_8WtWn@rG0Rj{Q6aW +AK2mt$eR#TR>aG_BF002D#000>P0000000000005+csRRH3aA|NaUukZ1WpZv|Y%gD5X>MtBUtcb8c~ +DCM0u%!j000080Q-4XQ(rU*uN({j0Ny4502%-Q00000000000HlF21^@tXX>c!JX>N37a&BR4FJg6RY +-C?$Zgwtkc~DCM0u%!j000080Q-4XQw7u2l@$vB0O2G602TlM00000000000HlG15&!^jX>c!JX>N37 +a&BR4FJob2Xk{*Nc~DCM0u%!j000080Q-4XQ=-7pTFMUq0AVu#03HAU00000000000HlG=9RL7uX>c! +JX>N37a&BR4FJo_RW@%@2a$$67Z*DGdc~DCM0u%!j000080Q-4XQ{;JOYzzc!JX>N37a&BR4FJ*XRWpH$9Z*FrgaCuNm0Rj{Q6aWAK2mt$eR#PVcqo?Qq002}000 +0#L0000000000005+c89o32aA|NaUukZ1WpZv|Y%gtLX>KlXc~DCM0u%!j000080Q-4XQ#;j?=0FJm0 +52Q>02%-Q00000000000HlF5KL7x5X>c!JX>N37a&BR4FK~Hqa&Ky7V{|TXc~DCM0u%!j000080Q-4X +Q;uiz4&(>`0QndI03-ka00000000000HlGeNB{tEX>c!JX>N37a&BR4FLPyVW?yf0bYx+4Wn^DtXk}w +-E^v8JO928D0~7!N00;p4c~(=hw_o*?3;+PvF8}}@00000000000001_fznX`0B~t=FJEbHbY*gGVQe +pVXk}$=Ut)D>Y-D9}E^v8JO928D0~7!N00;p4c~(=-cj`1~0001l0000T00000000000001_fuddj0B +~t=FJEbHbY*gGVQepBY-ulFUukY>bYEXCaCuNm0Rj{Q6aWAK2mt$eR#VjdaF^N#0093O001KZ000000 +0000005+cMPC2_aA|NaUukZ1WpZv|Y%gPMX)j@QbZ=vCZE$R5bZKvHE^v8JO928D0~7!N00;p4c~(<) +AAbh82><|Y9smF#00000000000001_fna9<0B~t=FJEbHbY*gGVQepBY-ulIVRL0)V{dJ3VQyqDaCuN +m0Rj{Q6aWAK2mt$eR#VV?GE^v8JO928D0~7!N00;p4c~(;(W`@cs0RRB_0ssIc00000000000001_f&GsF0B~t=FJ +EbHbY*gGVQepBY-ulJZ*6U1Ze(9$Z*FvDcyumsc~DCM0u%!j000080Q-4XQ)*dce2@eH0H_H702u%P0 +0000000000HlFvkpKX2X>c!JX>N37a&BR4FJo+JFKuCIZZ2?nP)h*<6ay3h000O8`*~JVm>Usq2Lu2B +HVOa$AOHXW0000000000q=7G%003}la4%nJZggdGZeeUMV{Bc!JX>N37a&BR4FJo+JFLQ8dZf<3Ab1rasP)h*<6ay3h000O8`*~JV{dxd +PSO5S3bN~PVApigX0000000000q=DJX003}la4%nJZggdGZeeUMV{B6he1Pj1ONb-4gdfm00000000000001_fpE+K0B~t=FJEbHbY*gGVQepBZ*6U1Ze +(*WUtei%X>?y-E^v8JO928D0~7!N00;p4c~(<6gYW%*2mkOV00000 +00000q=Dht003}la4%nJZggdGZeeUMV{dJ3VQyq|FJowBV{0yOc~DCM0u%!j000080Q-4XQ|fU;s?`G +k0FDa)03-ka00000000000HlFW+yDS@X>c!JX>N37a&BR4FJo_QZDDR?b1!3WZE$R5bZKvHE^v8JO92 +8D0~7!N00;p4c~(=T(w7#`2><}_A^-p<00000000000001_fo9+U0B~t=FJEbHbY*gGVQepBZ*6U1Ze +(*WV{dL|X=inEVRUJ4ZZ2?nP)h*<6ay3h000O8`*~JVT3!3Cp$Gr~OV0000000000q=9c!JX>N37a&BR4FJo_QZDDR?b1!6XcW!KNVPr0Fc~DCM0u%!j000080Q-4 +XQ~$H#<-r300EY_z03ZMW00000000000HlE_`2YZLX>c!JX>N37a&BR4FJo_QZDDR?b1!CcWo3G0E^v +8JO928D0~7!N00;p4c~(=u$ot^q0ssJ~1^@sa00000000000001_fhhd|0B~t=FJEbHbY*gGVQepBZ* +6U1Ze(*WXkl|`E^v8JO928D0~7!N00;p4c~(=2!<0Pg0RRAO1ONaY00000000000001_fkyxV0B~t=F +JEbHbY*gGVQepBZ*6U1Ze(*WXk~10E^v8JO928D0~7!N00;p4c~(;`3unU81pok=5&!@n0000000000 +0001_fo%c-0B~t=FJEbHbY*gGVQepBZ*6U1Ze(*WX>Md?crI{xP)h*<6ay3h000O8`*~JV9AJnmU>g7 +c%WMDuApigX0000000000q=9@00RV7ma4%nJZggdGZeeUMV{dJ3VQyq|FKKRbbYX04E^v8JO928D0~7 +!N00;p4c~(>7Sb)bq3;+PDF8}}@00000000000001_fg2c!JX>N37a&BR4FJo_QZDDR?b1!pfZ+9+mc~DCM0u%!j000080Q-4XQ!gWy@Rc!JX>N37a&BR4FJo_QZDDR?b1!vnX>N0LVQg$JaCuNm0Rj{Q6aWAK2mt +$eR#OXid4LQD000;m0018V0000000000005+cK1TrnaA|NaUukZ1WpZv|Y%gPPZEaz0WOFZfXk}$=E^ +v8JO928D0~7!N00;p4c~(=?bqy{%0RRA60{{Rg00000000000001_frm~30B~t=FJEbHbY*gGVQepCX +>)XPX<~JBX>V?GFJE72ZfSI1UoLQYP)h*<6ay3h000O8`*~JVXbO;_`~d&}lmq|(BLDyZ0000000000 +q=5%e0RV7ma4%nJZggdGZeeUMWNCABa%p09bZKvHb1!0Hb7d}Yc~DCM0u%!j000080Q-4XQ;*`ja$Nx +c0RI9204M+e00000000000HlFLQUL&PX>c!JX>N37a&BR4FJx(RbaH88b#!TOZgVebZgX^DY;0v@E^v +8JO928D0~7!N00;p4c~(;vfl9b{1^@uw6#xJv00000000000001_f#*{J0B~t=FJEbHbY*gGVQepCX> +)XPX<~JBX>V?GFLPvRb963nc~DCM0u%!j000080Q-4XQvgu2Xxae)09ynA03-ka00000000000HlGSS +^)rXX>c!JX>N37a&BR4FJx(RbaH88b#!TOZgVepXk}$=E^v8JO928D0~7!N00;p4c~(Md?crRmbY;0v?bZ> +GlaCuNm0Rj{Q6aWAK2mt$eR#Ts`S07&@008)n001Qb0000000000005+cA9Dc!aA|NaUukZ1WpZv|Y% +ghUWMz0SaA9L>VP|DuW@&C@WpXZXc~DCM0u%!j000080Q-4XQ`;-Z>hA>r0G$~C03HAU00000000000 +HlGzl>q>7X>c!JX>N37a&BR4FKKRMWq2=hZ*_8GWpgfYc~DCM0u%!j000080Q-4XQ^k>Mqx%p50Bkq_ +03!eZ00000000000HlHJn*jiDX>c!JX>N37a&BR4FKlmPVRUJ4ZgVeRUukY>bYEXCaCuNm0Rj{Q6aWA +K2mt$eR#Wqj+MRm{008e6001Qb0000000000005+cD6IhiaA|NaUukZ1WpZv|Y%gqYV_|e@Z*FrhUu0 +=>baixTY;!Jfc~DCM0u%!j000080Q-4XQ#cE&dK(G=0PY?D03`qb00000000000HlHDwE+NdX>c!JX> +N37a&BR4FKlmPVRUJ4ZgVeRb9r-PZ*FF3XD)DgP)h*<6ay3h000O8`*~JVv-DZ*7XttQD+T}n9{>OV0 +000000000q=7`h0RV7ma4%nJZggdGZeeUMY;R*>bZKvHb1!0Hb7d}Yc~DCM0u%!j000080Q-4XQw(z& +$U*`D0DJ}j03rYY00000000000HlGK!vO$rX>c!JX>N37a&BR4FKuOXVPs)+VJ}}_X>MtBUtcb8c~DC +M0u%!j000080Q-4XQ!azP_Xi9B0ADKr03HAU00000000000HlE$#sL6uX>c!JX>N37a&BR4FKuOXVPs +)+VJ~7~b7d}Yc~DCM0u%!j000080Q-4XQwc9KIy?pd0O1n=04D$d00000000000HlFk(g6T)X>c!JX> +N37a&BR4FKuOXVPs)+VJ~oNXJ2wPD002J#001BW0 +000000000005+c-q-;EaA|NaUukZ1WpZv|Y%gtZWMyn~FJE72ZfSI1UoLQYP)h*<6ay3h000O8`*~JV +6)>Prc>w?b-U9#tApigX0000000000q=8r20RV7ma4%nJZggdGZeeUMZEs{{Y;!MTVQyq;WMOn=E^v8 +JO928D0~7!N00;p4c~(<{l00000000000001_fe+#V0B +~t=FJEbHbY*gGVQepLZ)9a`b1!CZa&2LBUt@1>baHQOE^v8JO928D0~7!N00;p4c~(>3m#NLd0RR971 +ONaX00000000000001_fxG1a0B~t=FJEbHbY*gGVQepLZ)9a`b1!LbWMz0RaCuNm0Rj{Q6aWAK2mt$e +R#RFqbX4mM003Dg000~S0000000000005+cxaR=?aA|NaUukZ1WpZv|Y%gtZWMyn~FKlUUYc6nkP)h* +<6ay3h000O8`*~JV8yxcLYykiO;sO8w9smFU0000000000q=DV^0RV7ma4%nJZggdGZeeUMZEs{{Y;! +MjV`ybc!JX>N37a&BR4FK%UYcW-iQFJE72ZfSI1UoLQYP)h*<6ay3h000O8`*~JVt357KN(}%2o-Y6Z9 +RL6T0000000000q=BRg0swGna4%nJZggdGZeeUMZe?_LZ*prdVRdw9E^v8JO928D0~7!N00;p4c~(z?C000000 +00000001_fzm7j0B~t=FJEbHbY*gGVQepMWpsCMa%(ShWpi_BZ*DGdc~DCM0u%!j000080Q-4XQ`3)D +F}no-0NW1$03HAU00000000000HlGgLIMDAX>c!JX>N37a&BR4FK%UYcW-iQFLiWjY;!Jfc~DCM0u%! +j000080Q-4XQ!TH1U%CPS0RIL603QGV00000000000HlGXNCE(GX>c!JX>N37a&BR4FK%UYcW-iQFL- +Tia&TiVaCuNm0Rj{Q6aWAK2mt$eR#N}~0006200000001Na0000000000005+coJ#@#aA|NaUukZ1Wp +Zv|Y%gzcWpZJ3X>V?GFJE72ZfSI1UoLQYP)h*<6ay3h000O8`*~JV>CLabA_f2e^%DR9ApigX000000 +0000q=Dc|0swGna4%nJZggdGZeeUMZ*XODVRUJ4ZgVeVXk}w-E^v8JO928D0~7!N00;p4c~(=zz6u&A +3IG5qCIA2;00000000000001_fk9FN0B~t=FJEbHbY*gGVQepNaAk5~bZKvHb1!CcWo3G0E^v8JO928 +D0~7!N00;p4c~(>WI%Nj382|ttT>tia +P)h*<6ay3h000O8`*~JVz2<6y83F(RnFIg;GXMYp0000000000q=7Jb0swGna4%nJZggdGZeeUMZ*XO +DVRUJ4ZgVeUb!lv5FKuOXVPs)+VP9orX>?&?Y-KKRc~DCM0u%!j000080Q-4XQ>U8^`|<(+0GS5>05J +dn00000000000HlGMdjbG(X>c!JX>N37a&BR4FK=*Va$$67Z*FrhVs&Y3WG`)HbYWy+bYWj?WoKbyc` +k5yP)h*<6ay3h000O8`*~JV;ye;Y=m7u#Cjc!JX>N37a&BR4FK=*Va$$67Z*FrhVs&Y3WG{DUWo2w%Wn^h|VPb4$E^v8JO928D0~7! +N00;p4c~(<_$cJSK1ONc%3jhEv00000000000001_ftP~<0B~t=FJEbHbY*gGVQepNaAk5~bZKvHb1! +0bX>4RKcW7m0Y+r0;XJKP`E^v8JO928D0~7!N00;p4c~(>5-4uH@0000p0000i00000000000001_f$ +WC@0B~t=FJEbHbY*gGVQepNaAk5~bZKvHb1!Lbb97;BY%gD5X>MtBUtcb8c~DCM0u%!j000080Q-4XQ +{h^v-UR{x01^cN05bpp00000000000HlFyhynm`X>c!JX>N37a&BR4FK=*Va$$67Z*FrhX>N0LVQg$K +Wn^h|VPb4$UuV?GFKKRbbYX04FKlIJVPknNaCuNm0Rj{Q6aWAK2mt$eR#T6qSSr +FG000zg001cf0000000000005+ce2@YFaA|NaUukZ1WpZv|Y%gzcWpZJ3X>V?GFKKRbbYX04FL!8VWo +#~Rc~DCM0u%!j000080Q-4XQwXG`bdLi70O<+<0384T00000000000HlG1u>t^aX>c!JX>N37a&BR4F +LGsZFJE72ZfSI1UoLQYP)h*<6ay3h000O8`*~JVWn*>Aln?*_wL1U+ApigX0000000000q=8Sh0swGn +a4%nJZggdGZeeUMa%FKZV{dMAbaHiLbZ>HVE^v8JO928D0~7!N00;p4c~(;=qt)2!6951WL;wIC0000 +0000000001_fg;8N0B~t=FJEbHbY*gGVQepQWpOWZWpQ6-X>4UKaCuNm0Rj{Q6aWAK2mt$eR#R!EM9= +9W000bx001BW0000000000005+cNZJAbaA|NaUukZ1WpZv|Y%g+UaW8UZabIa}b97;BY%XwlP)h*<6a +y3h000O8`*~JVBLu(1a|i$cpdA1J8~^|S0000000000q=9e!0swGna4%nJZggdGZeeUMa%FKZa%FK}b +7gccaCuNm0Rj{Q6aWAK2mt$eR#O+blrnz>000#b001BW0000000000005+c90mgbaA|NaUukZ1WpZv| +Y%g+UaW8UZabI+DVPk7$axQRrP)h*<6ay3h000O8`*~JVbYEXCaCuNm0Rj{Q6a +WAK2mt$eR#W90%c=hW002h<001BW0000000000005+c@+JcSaA|NaUukZ1WpZv|Y%g+Ub8l>QbZKvHF +JfVHWiD`eP)h*<6ay3h000O8`*~JV000000ssI200000D*ylh0000000000q=7Fe0|0Poa4%nJZggdG +ZeeUMa%FRGY;|;LZ*DJaWoKbyc`sjIX>MtBUtcb8c~DCM0u%!j000080Q-4XQ}{OHU%wOp0EkBb04o3 +h00000000000HlF>C<6d+X>c!JX>N37a&BR4FLGsbZ)|mRX>V>XY-ML*V|g!fWpi(Ac4cxdaCuNm0Rj +{Q6aWAK2mt$eR#N}~0006200000001ul0000000000005+cf;|HOaA|NaUukZ1WpZv|Y%g+Ub8l>QbZ +KvHFLGsbZ)|pDY-wUIUtei%X>?y-E^v8JO928D0~7!N00;p4c~(=y03zQ=1pokK6aWA#00000000000 +001_fzdq!0B~t=FJEbHbY*gGVQepQWpi(Ab#!TOZZC3Wb8l>RWo&6;FJfVHWiD`eP)h*<6ay3h000O8 +`*~JVYKm(DsSp4FB1ZrKF#rGn0000000000q=8~X0|0Poa4%nJZggdGZeeUMa%FRGY;|;LZ*DJgWpi( +Ac4cg7VlQK1Ze(d>VRU74E^v8JO928D0~7!N00;p4c~(dXaE2%00000000000001_fm& +1p0B~t=FJEbHbY*gGVQepQWpi(Ab#!TOZZC3Wb8l>RWo&6;FJ@t5bZ>HbE^v8JO928D0~7!N00;p4c~ +(;?)mp#21^@s_761S@00000000000001_fy{3M0B~t=FJEbHbY*gGVQepQWpi(Ab#!TOZZC3Wb8l>RW +o&6;FJ^CbZe(9$VQyq;WMOn=b1rasP)h*<6ay3h000O8`*~JVRpR%rEerqv^&c!JX>N37a&BR4FLGsbZ)|mRX>V>Xa%F +RGY<6XAX<{#OWpHnDbY*fbaCuNm0Rj{Q6aWAK2mt$eR#RNV>E!MN002)F001)p0000000000005+cw} +t}%aA|NaUukZ1WpZv|Y%g+Ub8l>QbZKvHFLGsbZ)|pDY-wUIa%FLKX>w(4Wo~qHE^v8JO928D0~7!N0 +0;p4c~(;z7guBI3jhFYB>(^~00000000000001_f%c070B~t=FJEbHbY*gGVQepQWpi(Ab#!TOZZC3W +b8l>RWo&6;FLGsbZ)|pDaxQRrP)h*<6ay3h000O8`*~JV000000ssI2000009{>OV0000000000q=7A +%0|0Poa4%nJZggdGZeeUMb#!TLb1z?CX>MtBUtcb8c~DCM0u%!j000080Q-4XQ%ZO_Kh^;N0QUm`02= +@R00000000000HlFzm;(TCX>c!JX>N37a&BR4FLiWjY;!MPY;R{SaCuNm0Rj{Q6aWAK2mt$eR#O9VLc +*5<004mo0015U0000000000005+cdzu3PaA|NaUukZ1WpZv|Y%g_mX>4;ZVQ_F{X>xNeaCuNm0Rj{Q6 +aWAK2mt$eR#W6-jcert003ME0012T0000000000005+cPMre)aA|NaUukZ1WpZv|Y%g_mX>4;ZV{dJ6 +VRSBVc~DCM0u%!j000080Q-4XQ?|5;v2z9h009*M04V?f00000000000HlF#p#uPLX>c!JX>N37a&BR +4FLiWjY;!MTZ*6d4bZKH~Y-x0PUvyz-b1rasP)h*<6ay3h000O8`*~JVY!f}Mm;e9(@&Et;9{>OV000 +0000000q=6`?0|0Poa4%nJZggdGZeeUMb#!TLb1!6JbY*mDZDlTSc~DCM0u%!j000080Q-4XQ_!}>j! +Xpr04ojv03rYY00000000000HlHar~?3SX>c!JX>N37a&BR4FLiWjY;!MUWpHw3V_|e@Z*DGdc~DCM0 +u%!j000080Q-4XQznk}UF`z^0EP?z04V?f00000000000HlG5t^)vYX>c!JX>N37a&BR4FLiWjY;!MU +X>w&_bYFFHY+q<)Y;a|Ab1rasP)h*<6ay3h000O8`*~JVxg+o|3jzQD;RFBxB>(^b0000000000q=CJ +%0|0Poa4%nJZggdGZeeUMb#!TLb1!6Rb98ldX>4;}VRC14E^v8JO928D0~7!N00;p4c~(OV0000000000q=AOG0|0Poa4%nJZggdGZeeUMb#!TLb1!9XV{c?>Z +f7oVc~DCM0u%!j000080Q-4XQ{g5JlS2Xk03QSZ03rYY00000000000HlG@x&r`kX>c!JX>N37a&BR4 +FLiWjY;!MVZgg^aaBpdDbaO6nc~DCM0u%!j000080Q-4XQ^z}7-{%Mb00kES03iSX00000000000HlF +by#oMnX>c!JX>N37a&BR4FLiWjY;!MWX>4V4d2@7SZ7y(mP)h*<6ay3h000O8`*~JVqt#C>SOEY4%mM +%aAOHXW0000000000q=94;ZXKZO=V=i!cP +)h*<6ay3h000O8`*~JV5m0CS*#-ar%Mt(p9RL6T0000000000q=9+O0|0Poa4%nJZggdGZeeUMb#!TL +b1!INb7*CAE^v8JO928D0~7!N00;p4c~(=`e-Wdi0RR9S0{{Rm00000000000001_fsNDy0B~t=FJEb +HbY*gGVQepTbZKmJFKKRSWn*+-b7f<7a%FUKVQzD9Z*p`laCuNm0Rj{Q6aWAK2mt$eR#O=t$&+0T000 +av0015U0000000000005+cde#E~aA|NaUukZ1WpZv|Y%g_mX>4;ZY;R|0X>MmOaCuNm0Rj{Q6aWAK2m +t$eR#V=;&!#;a007WW000{R0000000000005+c6XXK`aA|NaUukZ1WpZv|Y%g_mX>4;ZZE163E^v8JO +928D0~7!N00;p4c~(<9v<$F!0RRB01ONaX00000000000001_fr4;ZaA9L>VP|P>XD)DgP)h*<6ay3h000O8`*~JV$Uak^jsySzd<*~p9{>OV00000000 +00q=EMZ1ORYpa4%nJZggdGZeeUMb#!TLb1!gVa$#(2Wo#~Rc~DCM0u%!j000080Q-4XQc!JX>N37a&BR4FLiWjY;!MgYiD0_Wpi(Ja${w4E^v8JO928D0 +~7!N00;p4c~(<7l-3zA1pok95&!@v00000000000001_ftL&f0B~t=FJEbHbY*gGVQepTbZKmJFLPyd +b#QcVZ)|g4Vs&Y3WG--dP)h*<6ay3h000O8`*~JV$(R6($qWDhN+$pSApigX0000000000q=5_)1ORY +pa4%nJZggdGZeeUMb#!TLb1!psVsLVAV`X!5E^v8JO928D0~7!N00;p4c~(=8MVb@a2><}@9RL6y000 +00000000001_ffOGE0B~t=FJEbHbY*gGVQepTbZKmJFLY&Xa9?C;axQRrP)h*<6ay3h000O8`*~JVJg +Ie^3<>}M$|3*&AOHXW0000000000q=76c1ORYpa4%nJZggdGZeeUMb#!TLb1!vnaA9L>X>MmOaCuNm0 +Rj{Q6aWAK2mt$eR#SeVju$xt007?x000{R0000000000005+cb~6M3aA|NaUukZ1WpZv|Y%g_mX>4;Z +b#iQTE^v8JO928D0~7!N00;p4c~(4;ZcW7m0Y%XwlP)h*<6ay3h000O8`*~JVFRk9iF984mR00419R +L6T0000000000q=9lo1ORYpa4%nJZggdGZeeUMc4KodUtei%X>?y-E^v8JO928D0~7!N00;p4c~($_h7T@?TTj70zd7ytkO0000000000q=ENI1ORYpa4%nJZggdGZeeUMc4KodXK8dUaCuN +m0Rj{Q6aWAK2mt$eR#TU6$*GwI002=F0015U0000000000005+cieCf(aA|NaUukZ1WpZv|Y%g|Wb1! +XWa$|LJX<=+GaCuNm0Rj{Q6aWAK2mt$eR#R%uV*EA^002xa0018V0000000000005+cUu6UUaA|NaUu +kZ1WpZv|Y%g|Wb1!psVs>S6b7^mGE^v8JO928D0~7!N00;p4c~(<%F(Ir$7ytl{R{#Jb00000000000 +001_fzopX0B~t=FJEbHbY*gGVQepUV{MtBUtcb8c~DCM0u%!j000080Q-4 +XQ>83)(Elp{03N*n02KfL00000000000HlGik^}&7X>c!Jc4cm4Z*nhWX>)XPZ!U0oP)h*<6ay3h000 +O8`*~JV1`i|L^ZNh*@+$-Y7ytkO0000000000q=DkT1ORYpa4%nWWo~3|axZXsaA9(DX>MmOaCuNm0R +j{Q6aWAK2mt$eR#VdT6U!aA|NaUv_0~WN&gWV_{=xWn*t{baHQOFJWY1aCBvIE^v8JO928D0~7!N0 +0;p4c~(=#V!TZ<0RR9c0{{Ra00000000000001_fq&)&0B~t=FJE?LZe(wAFJob2Xk}w>Zgg^QY%gPB +V`ybAaCuNm0Rj{Q6aWAK2mt$eR#SzRWD^nr006fF001HY0000000000005+c@aF{paA|NaUv_0~WN&g +WV_{=xWn*t{baHQOFJo_QaA9;VaCuNm0Rj{Q6aWAK2mt$eR#TS#Z*33|002cd001Tc0000000000005 ++cLg@tnaA|NaUv_0~WN&gWV_{=xWn*t{baHQOFJo_RbaHQOY-MsTaCuNm0Rj{Q6aWAK2mt$eR#WrQ{u +=rN0089)001Wd0000000000005+cmiYw$aA|NaUv_0~WN&gWV_{=xWn*t{baHQOFJ@_MWp{F6aByXEE +^v8JO928D0~7!N00;p4c~(=DC2d}>1pol%4*&or00000000000001_fz|y50B~t=FJE?LZe(wAFJob2 +Xk}w>Zgg^QY%geKb#iHQbZKLAE^v8JO928D0~7!N00;p4c~(=m$h}$^2><}I8vp<$00000000000001 +_fye^}0B~t=FJE?LZe(wAFJob2Xk}w>Zgg^QY%g|z0B~t=FJE?LZe(wAFJob2Xk}w>Z +gg^QY%gPBV`yb_FJE72ZfSI1UoLQYP)h*<6ay3h000O8`*~JV{LU}Q2?hWFIS>EZgg^QY%gPBV`y +b_FLGsMX>(s=VPj}zE^v8JO928D0~7!N00;p4c~(=ukX5ii0000!0000V00000000000001_f!P)Y0B +~t=FJE?LZe(wAFJonLbZKU3FJE72ZfSI1UoLQYP)h*<6ay3h000O8`*~JVFCeYqo&W#<{{R309{>OV0 +000000000q=8l!1^{qra4%nWWo~3|axY_La&&2CX)j-2ZDDC{Utcb8c~DCM0u%!j000080Q-4XQx_bf +z0f5B0EzVj03HAU00000000000HlF27zO}vX>c!Jc4cm4Z*nhVWpZ?BW@#^DVPj=-bS`jZZBR=A0u%! +j000080Q-4XQc!Jc4cm4Z*nhVWpZ?BW@#^DZ*p +ZWaCuNm0Rj{Q6aWAK2mt$eR#T(rX47{B0074f0018V0000000000005+c8bb&GaA|NaUv_0~WN&gWV` +yP=WMy?y-E^v8JO928D0~7!N00;p4c~(<7zl0F)IRF3_dH?_)00000000000001_fzC$=0 +B~t=FJE?LZe(wAFJow7a%5$6FJftDHD+>UaV~IqP)h*<6ay3h000O8`*~JV%jaiiJOcm#-39;vApigX +0000000000q=EW@2mo+ta4%nWWo~3|axY_OVRB?;bT49QXEktgZ(?O~E^v8JO928D0~7!N00;p4c~(> +4Stva{2><}YBme*>00000000000001_fpvul0B~t=FJE?LZe(wAFJow7a%5$6FJow7a%5?9baH88b#! +TOZZ2?nP)h*<6ay3h000O8`*~JV#p&<`cLV?c{|*2EDF6Tf0000000000q=EO22mo+ta4%nWWo~3|ax +Y_OVRB?;bT4CQVRCb2bZ2sJb#QQUZ(?O~E^v8JO928D0~7!N00;p4c~(>4mtk7J2LJ%}6951t000000 +00000001_fwhwe0B~t=FJE?LZe(wAFJow7a%5$6FJow7a&u*LaB^>AWpXZXc~DCM0u%!j000080Q-4X +Q(XHuNTdY-00s^K04V?f00000000000HlGon+O1KX>c!Jc4cm4Z*nhVXkl_>WppoNZ)9n1XLEF6bY*Q +}V`yn^WiD`eP)h*<6ay3h000O8`*~JV*bE&BS^@w7umk`A9RL6T0000000000q=BKK2mo+ta4%nWWo~ +3|axY_OVRB?;bT4CXZE#_9E^v8JO928D0~7!N00;p4c~(=sLYgsX0{{R&2LJ#f00000000000001_fi +|QF0B~t=FJE?LZe(wAFJow7a%5$6FJo{yG&yi`Z(?O~E^v8JO928D0~7!N00;p4c~(c!Jc4cm4Z*nhVXkl_>WppoPb7OFFZ(?O~E^v8 +JO928D0~7!N00;p4c~(=w3SgoX1^@sKDF6T*00000000000001_flIIm0B~t=FJE?LZe(wAFJow7a%5 +$6FJ*IMb8Rkgc~DCM0u%!j000080Q-4XQy@3Q&?*H00HqE903rYY00000000000HlGLwg>=lX>c!Jc4 +cm4Z*nhVXkl_>WppoPbz^F9aB^>AWpXZXc~DCM0u%!j000080Q-4XQ}jUtd0`j;0O~XV03ZMW000000 +00000HlEfya)hrX>c!Jc4cm4Z*nhVXkl_>WppoPbz^ICW^!e5E^v8JO928D0~7!N00;p4c~(;<1(*0Z +0{{Tj1^@se00000000000001_fuht10B~t=FJE?LZe(wAFJow7a%5$6FJ*OOYjSXMZ(?O~E^v8JO928 +D0~7!N00;p4c~(=*+ey_kIsgELdjJ3+00000000000001_fg0Ed0B~t=FJE?LZe(wAFJow7a%5$6FJ* +OOba!TQWpOTWc~DCM0u%!j000080Q-4XQ=mbO7&rp}0MiBl03rYY00000000000HlG75(xlsX>c!Jc4 +cm4Z*nhVXkl_>WppoPbz^jQaB^>AWpXZXc~DCM0u%!j000080Q-4XQyE3l*JCFD0P9cy03iSX000000 +00000HlEf76|}wX>c!Jc4cm4Z*nhVXkl_>WppoRVlp!^GG=mRaV~IqP)h*<6ay3h000O8`*~JVeyjeE +HUj_v+6DjsBLDyZ0000000000q=BV92>@_ua4%nWWo~3|axY_OVRB?;bT4OOGBYtUaB^>AWpXZXc~DC +M0u%!j000080Q-4XQw=H8vY8S901h?)03!eZ00000000000HlE&K?wkGX>c!Jc4cm4Z*nhVXkl_>Wpp +oSWnyw=cW`oVVr6nJaCuNm0Rj{Q6aWAK2mt$eR#Uc!Jc4cm4Z*nhVXkl_>WppoWVQyz)b!=y0a%o|1ZEs{{Y%Xw +lP)h*<6ay3h000O8`*~JVq|tlo_ZI*F^MnBaB>(^b0000000000q=C(~2>@_ua4%nWWo~3|axY_OVRB +?;bT4dSZf9q5Wo2t^Z)9a`E^v8JO928D0~7!N00;p4c~(>R2%Q%i7XSd*fdK#}00000000000001_fd +|eB0B~t=FJE?LZe(wAFJow7a%5$6FKl6MXJ}<&a%FdIZ)9a`E^v8JO928D0~7!N00;p4c~(>Ngpv<-8 +vp=ekO2TG00000000000001_fo0_h0B~t=FJE?LZe(wAFJow7a%5$6FKl6MXJ~b9XJK+_VQy`2WMynF +aCuNm0Rj{Q6aWAK2mt$eR#V+O6}2NM003+N0stof0000000000005+cA^{2jaA|NaUv_0~WN&gWV`yP +=WMycaX<=?{Z)9a`E^v8JO928D0~7!N00;p4c~(<35Ym-B8vp>1lK}uE0000000000000 +1_fr=>#0B~t=FJE?LZe(wAFJow7a%5$6FKl6MXLM*`X>D(0Wo#~Rc~DCM0u%!j000080Q-4XQyVYVGZ +-8I0Lpd&04D$d00000000000HlElMG63LX>c!Jc4cm4Z*nhVXkl_>WppoWVQy!1b#iNIb7*aEWMynFa +CuNm0Rj{Q6aWAK2mt$eR#W8c@T8Xp0082daA|NaUv_0~WN&gWV`yP= +WMyJaA|NaUv_0~ +WN&gWV`yP=WMyAWp +XZXc~DCM0u%!j000080Q-4XQ^$+OgLe%80M{@804M+e00000000000HlGBkqQ8CX>c!Jc4cm4Z*nhVX +kl_>WppofZfSO9a&uv9WMy<^V{~tFE^v8JO928D0~7!N00;p4c~(>Pzq~y~1ONce3IG5h0000000000 +0001_flQwY0B~t=FJE?LZe(wAFJow7a%5$6FLiWgIB;@rVr6nJaCuNm0Rj{Q6aWAK2mt$eR#S&;{S~< +Y008m;0015U0000000000005+c(4z_faA|NaUv_0~WN&gWV`yP=WMyV>WaCuNm0Rj{Q6aW +AK2mt$eR#TG(*D?bD00031001KZ0000000000005+c#iR-VaA|NaUv_0~WN&gWV`yP=WMy?y-E^v8JO928D0~7!N00;p4c~(MtBUtcb8c~DCM0u%!j000080Q-4XQ&QQjhKd6K0NM!v02}}S000000 +00000HlE#z6tc!Jc4cm4Z*nhVZ)|UJVQpbAVQzD2E^v8JO928D0~7!N00;p4c~(=)#1YeR3jhEW +DF6T?00000000000001_f!)Ch0B~t=FJE?LZe(wAFJo_PZ*pO6VJ~5Bb7^#McWG`jGA?j=P)h*<6ay3 +h000O8`*~JV8*fE&g8~2mdj|jjA^-pY0000000000q=Apk3IK3va4%nWWo~3|axY_VY;SU5ZDB8IZfS +IBVQgu0WiD`eP)h*<6ay3h000O8`*~JV2pbF$Pz3-092Ecn9RL6T0000000000q=8b<3IK3va4%nWWo +~3|axY_VY;SU5ZDB8WX>KzzE^v8JO928D0~7!N00;p4c~(=RDAwlg1pojh82|tu00000000000001_f +!);#0B~t=FJE?LZe(wAFJo_PZ*pO6VJ~-SZggdGZ7y(mP)h*<6ay3h000O8`*~JVU2hWoT>$_9MFIc- +9{>OV0000000000q=5+B3IK3va4%nWWo~3|axY|Qb98KJVlQ7`X>MtBUtcb8c~DCM0u%!j000080Q-4 +XQ!LZH4qz()02iVF0384T00000000000HlGU-3kD3X>c!Jc4cm4Z*nhWX>)XJX<{#9Z*6d4bS`jtP)h +*<6ay3h000O8`*~JVhCpNd_%8qebH@Mx9{>OV0000000000q=7vN3jlCwa4%nWWo~3|axY|Qb98KJVl +QN2bYWs)b7d}Yc~DCM0u%!j000080Q-4XQ!?pD%+?eD00U6~02}}S00000000000HlF(IST-AX>c!Jc +4cm4Z*nhWX>)XJX<{#FZe(S6E^v8JO928D0~7!N00;p4c~(>1NpBz`GXMbn$^ZZ#00000000000001_ +fr3s80B~t=FJE?LZe(wAFJx(RbZlv2FKlmPVRUbDb1rasP)h*<6ay3h000O8`*~JVc&0UHY!Cnd+c^L +L9{>OV0000000000q=Dgq3jlCwa4%nWWo~3|axY|Qb98KJVlQoBZfRy^b963nc~DCM0u%!j000080Q- +4XQ}nBO_}2yi0DThx03HAU00000000000HlG6k_!NEX>c!Jc4cm4Z*nhWX>)XJX<{#JVRCC_a&sc!Jc4cm4Z*nhWX>)XJX +<{#JWprU=VRT_GaCuNm0Rj{Q6aWAK2mt$eR#Tn5)qt=I002ZP001BW0000000000005+cy}kc!Jc4cm4Z*nhWX>)XJX<{#QHZ(0^a&0bUc +x6ya0Rj{Q6aWAK2mt$eR#SN)bMBJK0001<0RS5S0000000000005+c>eLMYaA|NaUv_0~WN&gWWNCAB +Y-wUIbT%|DWq4&!O928D0~7!N00;p4c~(=jFEei;A&*;@br9smFU0000000000q=B +mE4ghdza4%nWWo~3|axY|Qb98KJVlQ@Oa&u{KZZ2?nP)h*<6ay3h000O8`*~JV92gG9H?RNz0AK+C8v +pt03QGV00000000000HlG`u@3-nX>c!Jc4cm4Z*nhWX>)XJX<{#THZ(0^a&0bUcx6ya0Rj{Q +6aWAK2mt$eR#W>LvixVj0001n0RS5S0000000000005+cSlbW)aA|NaUv_0~WN&gWWNCABY-wUIcQ!O +GWq4&!O928D0~7!N00;p4c~(=j{uZzcDF6V!rvLyP00000000000001_f%uyd0B~t=FJE?LZe(wAFJx +(RbZlv2FL!8VWo#~Rc~DCM0u%!j000080Q-4XQzP$Z{KEhM01^QJ04V?f00000000000HlFE#Ss8-X> +c!Jc4cm4Z*nhWX>)XJX<{#5Vqs%zaBp&SFJE72ZfSI1UoLQYP)h*<6ay3h000O8`*~JVuZk~a&H(@b% +L4!aB>(^b0000000000q=84q5dd&$a4%nWWo~3|axY|Qb98KJVlQ7}VPk7>Z*p`mZE163E^v8JO928D +0~7!N00;p4c~(=#IR?{F8~^}oWB>ps00000000000001_fmp~90B~t=FJE?LZe(wAFJx(RbZlv2FJEF +|V{344a&#|qXmxaHY%XwlP)h*<6ay3h000O8`*~JVZx#Tp_5lC@ISK#(D*ylh0000000000q=D|_5dd +&$a4%nWWo~3|axY|Qb98KJVlQ7}VPk7>Z*p`mb9r-PZ*FF3XD(xAXHZK40u%!j000080Q-4XQ~r7Dqp +2PM0On`_04e|g00000000000HlE}=MeyKX>c!Jc4cm4Z*nhWX>)XJX<{#5Vqs%zaBp&SFLQZwV{dL|X +=g5Qc~DCM0u%!j000080Q-4XQ$#`^Ltc!Jc4cm4Z*nhW +X>)XJX<{#5Vqs%zaBp&SFLYsYW@&6?E^v8JO928D0~7!N00;p4c~(=2mv5*<0ssJr1ONaa000000000 +00001_fyQ4F0B~t=FJE?LZe(wAFKBdaY&C3YVlQ7`X>MtBUtcb8c~DCM0u%!j000080Q-4XQ_80;;Vl +#Z09Zi)03iSX00000000000HlFPViEvwX>c!Jc4cm4Z*nhabZu-kY-wUIUukGzbY*yLY%XwlP)h*<6a +y3h000O8`*~JViZ&5F4j%vjVSWGrBme*a0000000000q=B?{5&&>%a4%nWWo~3|axZ9fZEQ7cX<{#5X +>M?JbaQlaWnpbDaCuNm0Rj{Q6aWAK2mt$eR#P<&;B8MG008hT0RSQZ0000000000005+c1eOv2aA|Na +Uv_0~WN&gWXmo9CHEd~OFJE+TYh`X}dS!AhaCuNm0Rj{Q6aWAK2mt$eR#T4CiAmiC002W10015U0000 +000000005+cld}>4aA|NaUv_0~WN&gWXmo9CHEd~OFJE4;YaCuNm0Rj{Q6aWAK2mt$eR#RIt113 +O7000O^0RSNY0000000000005+cthy2aaA|NaUv_0~WN&gWXmo9CHEd~OFJo_Rb97;DbaO6nc~DCM0u +%!j000080Q-4XQ?aYPpWj3P0K&-u03!eZ00000000000HlE{0}}vnX>c!Jc4cm4Z*nhabZu-kY-wUIX +mo9CHE>~ab7gWaaCuNm0Rj{Q6aWAK2mt$eR#S*`+2Ohn0056Y001HY0000000000005+cOGpy{aA|Na +Uv_0~WN&gWXmo9CHEd~OFLPybX<=+>dS!AhaCuNm0Rj{Q6aWAK2mt$eR#VdDT~t~C003bYEXCaCuNm0Rj{Q6aWAK2mt +$eR#O=o=YggH008v^001KZ0000000000005+c<5?2`aA|NaUv_0~WN&gWXmo9CHEd~OFJE+WX=N{8Vq +tS-E^v8JO928D0~7!N00;p4c~( +UK0RtX>c!Jc4cm4Z*nhabZu-kY-wUIW@&76WpZ;bUtei%X>?y-E^v8JO928D0~7!N00;p4c~(<^umlj +o0RRA(0{{Rv00000000000001_fo)zB0B~t=FJE?LZe(wAFKBdaY&C3YVlQTCY;c!Jc4cm4Z*nhabZu-kY-wUIW@&76WpZ;bX>Mv|V{~6_WprU*V`yP= +b7gccaCuNm0Rj{Q6aWAK2mt$eR#TGEWT0#V0027<001Na0000000000005+c!DJHvaA|NaUv_0~WN&g +WXmo9CHEd~OFJ@_MbY*gLFKlUUbS`jtP)h*<6ay3h000O8`*~JV7j>E{4?5a&s?laCB*JZeeV6VP|tLaCuNm0Rj{Q6aWAK2m +t$eR#Q>FVND4b000qb001cf0000000000005+co^KNXaA|NaUv_0~WN&gWXmo9CHEd~OFJ@_MbY*gLF +LPmTX>@6NWpXZXc~DCM0u%!j000080Q-4XQ($uc5A^{60KNnO04e|g00000000000HlHLhZ6vBX>c!J +c4cm4Z*nhabZu-kY-wUIW@&76WpZ;bcW7yJWpi+0V`VOIc~DCM0u%!j000080Q-4XQ=U0-T4wc!Jc4cm4Z*nhabZu-kY-wUIbaG{7VPs)&bY*gLFJE72ZfSI1UoL +QYP)h*<6ay3h000O8`*~JVQj8HPR0041vjzYFD*ylh0000000000q=DUw698~&a4%nWWo~3|axZ9fZE +Q7cX<{#Qa%E*p0PqF?04M+e000000 +00000HlF>juQZIX>c!Jc4cm4Z*nhabZu-kY-wUIbaG{7VPs)&bY*gLFLPmdE^v8JO928D0~7!N00;p4 +c~(4R +=a&s?VUukY>bYEXCaCuNm0Rj{Q6aWAK2mt$eR#QGcX#DaH008AU001cf0000000000005+cqLvc?aA| +NaUv_0~WN&gWXmo9CHEd~OFLZKcWny({Y-D9}b1!0Hb7d}Yc~DCM0u%!j000080Q-4XQ&0q_vHu4E0N +o-004M+e00000000000HlH2r4s;fX>c!Jc4cm4Z*nhabZu-kY-wUIbaG{7Vs&Y3WMy)5FJy0RE^v8JO +928D0~7!N00;p4c~(4R=a&s?bbaG{7E^v8JO928D0~7!N00;p4c~(=g8MlBL4gdhIIRF4J00000000000001 +_fx5U80B~t=FJE?LZe(wAFKBdaY&C3YVlQ-ZWo2S@X>4R=a&s?bbaG{7Uu<}7Y%XwlP)h*<6ay3h000 +O8`*~JV+jeS&o(2E_R~7&OEC2ui0000000000q=6vE698~&a4%nWWo~3|axZ9fZEQ7cX<{#Qa%E+AVQ +gzbYEXCaCuNm0Rj{Q6aWAK2mt$eR#PD@+Cmox001-{001Ze0000000000005+c2+k7#a +A|NaUv_0~WN&gWXmo9CHEd~OFLZKcWp`n0Yh`kCFJfVHWiD`eP)h*<6ay3h000O8`*~JVWr}a9_W=L^ +g#`crCjbBd0000000000q=9AC698~&a4%nWWo~3|axZ9fZEQ7cX<{#Qa%E+AVQgzc!Jc4cm4Z*nhabZu-kY-w +UIbaG{7cVTR6WpZ;bWpr|7WiD`eP)h*<6ay3h000O8`*~JVFatz%c?JLg)ffN(E&u=k0000000000q= +D1i698~&a4%nWWo~3|axZ9fZEQ7cX<{#Qa%E+AVQgzbYEXCaCuNm0Rj{Q6aWAK2mt$eR#RmaV{t13004ar000>P0000000000005 ++c{^t__aA|NaUv_0~WN&gWX=H9;FJo_HWn(UIc~DCM0u%!j000080Q-4XQ!S&#-OB&~0B8XK02%-Q00 +000000000HlFn>k|NQX>c!Jc4cm4Z*nhbWNu+EV{dJ6VRSBVc~DCM0u%!j000080Q-4XQ&gZqI-Lsu0 +2(p?02lxO00000000000HlFq>=OWRX>c!Jc4cm4Z*nhbWNu+EV{dY0E^v8JO928D0~7!N00;p4c~(<) +7#cEfBLDzyr2qgN00000000000001_fj0OP0B~t=FJE?LZe(wAFKJ|MVJ~T9Zee6$bYU)Vc~DCM0u%! +j000080Q-4XQ!L0`TPFhm0F4I#0384T00000000000HlH68x#O=X>c!Jc4cm4Z*nhbWNu+EX>N3KVQy +z-b1rasP)h*<6ay3h000O8`*~JV>7zsD7XSbN6#xJLAOHXW0000000000q=7*n6aa8(a4%nWWo~3|ax +ZCQZecHQVPk7yXJubxVRT_GaCuNm0Rj{Q6aWAK2mt$eR#Uvk5`-O?004Ou0{|TW0000000000005+cm +LC)VaA|NaUv_0~WN&gWX=H9;FLiWtG&W>mbYU)Vc~DCM0u%!j000080Q-4XQ;jEASl|Hw0A2(D03QGV +00000000000HlHLw-f+yX>c!Jc4cm4Z*nhfb7yd2V{0#8UukY>bYEXCaCuNm0Rj{Q6aWAK2mt$eR#P$ +fug4Yu000yK0018V0000000000005+c3%V2laA|NaUv_0~WN&gWZF6UEVPk7AUv_13b7^mGE^v8JO92 +8D0~7!N00;p4c~(<9JDt<50RR9w1ONab00000000000001_fnK^40B~t=FJE?LZe(wAFKu&YaA9L>FJ +*XRWpH$9Z*FrgaCuNm0Rj{Q6aWAK2mt$eR#S=pt2_V)0077r000^Q0000000000005+cO1u;RaA|NaU +v_0~WN&gWZF6UEVPk7AWq5QhaCuNm0Rj{Q6aWAK2mt$eR#U62=1n9W004@V0018V0000000000005+c +g2NO5aA|NaUv_0~WN&gWZF6UEVPk7AW?^h>Vqs%zE^v8JO928D0~7!N00;p4c~(;<3Bjgi0RRA%0ssI +a00000000000001_f#cv50B~t=FJE?LZe(wAFK}UFYhh<;Zf7rFUtwZzb#z}}E^v8JO928D0~7!N00; +p4c~(;ut&*Vh0002-0RR9Y00000000000001_fr#Q10B~t=FJE?LZe(wAFK}UFYhh<;Zf7rFUukY>bY +EXCaCuNm0Rj{Q6aWAK2mt$eR#O`)I%9|q007`D001KZ0000000000005+cyWVP|P>XD?rEVQzVBX>N6RE^v8JO928D0~7!N00;p4c~(=Z>u=dG2LJ#X5dZ)q00000000000001_ +frRoD0B~t=FJE?LZe(wAFK}UFYhh<;Zf7rFZFO^OY-w(FcrI{xP)h*<6ay3h000O8`*~JVaRPJMmPUvqSFbz^jOa%FQaaCuNm0Rj +{Q6aWAK2mt$eR#R>K>OXr5001W;001BW0000000000005+cp!*a6aA|NaUv_0~WN&gWaA9L>VP|P>XD +@AGa%*LBb1rasP)h*<6ay3h000O8`*~JVZj(V;@&*6^L=pf1B>(^b0000000000q=8um6##H)a4%nWW +o~3|axZXUV{2h&X>MmPa%FLKX>w(4Wo~qHE^v8JO928D0~7!N00;p4c~(<2WSxN$8vp?GcmMz+00000 +000000001_fsPFo0B~t=FJE?LZe(wAFK}UFYhh<;Zf7rZaAjj@W@%+|b1rasP)h*<6ay3h000O8`*~J +V5aMk*l@R~{Vm$x=9RL6T0000000000q=Dfm6##H)a4%nWWo~3|axZXUV{2h&X>MmPbYW+6E^v8JO92 +8D0~7!N00;p4c~(URJD0D=Gj03HAU0000000000 +0HlHFP!#}hX>c!Jc4cm4Z*nhiWpFhyH!ojbX>MtBUtcb8c~DCM0u%!j000080Q-4XQ~N##QYZxg0D%n +v02=@R00000000000HlGNQ567iX>c!Jc4cm4Z*nhiWpFhyH!os!X>4RJaCuNm0Rj{Q6aWAK2mt$eR#S +7On+w7P006`n000{R0000000000005+c{8kkJaA|NaUv_0~WN&gWaAj~cF*h$`Xk}w-E^v8JO928D0~ +7!N00;p4c~(;mu~yKE1^@s85C8xk00000000000001_f%jY$0B~t=FJE?LZe(wAFK}gWH8D3YV{dG4a +%^vBE^v8JO928D0~7!N00;p4c~(=83#T>70RRBy1ONaW00000000000001_fxTlD0B~t=FJE?LZe(wA +FK}gWH8D3YV{dJ6VRSBVc~DCM0u%!j000080Q-4XQ#;SNM$Z8N0BHmO03HAU00000000000HlGyWfcH +$X>c!Jc4cm4Z*nhiWpFhyH!oyqa&&KRY;!Jfc~DCM0u%!j000080Q-4XQxxc!Jc4cm4Z*nhiWpFhyH!o#wc4BpDY-BEQc~DCM0u%!j000080Q-4XQX>c!Jc4cm4Z*nhiWpFhyH!p2vbYU)Vc~DCM0u%!j000080 +Q-4XQx;WthTRMR0Ch9~03HAU00000000000HlE^bQJ(_X>c!Jc4cm4Z*nhiWpFhyH!pW`VQ_F|a&sc!Jc4cm4Z*nhiWpFh +yH!o>!UvP47V`X!5FJE72ZfSI1UoLQYP)h*<6ay3h000O8`*~JVv=buUNDBY}!7Bg&EC2ui00000000 +00q=Br06##H)a4%nWWo~3|axZXYa5XVEFKKRHaB^>BWpi^cUukY%aB^>BWpi^baCuNm0Rj{Q6aWAK2m +t$eR#U)|DV@Y~0094{0RSZc0000000000005+cK8_UtaA|NaUv_0~WN&gWaBF8@a%FRGb#h~6b1z?CX +>MtBUtcb8c~DCM0u%!j000080Q-4XQ(+_n)=L2Z05Spq04D$d00000000000HlFM0u}&pX>c!Jc4cm4 +Z*nhiYiD0_Wpi(Ja${w4FK~G?F=KCSaA9;VaCuNm0Rj{Q6aWAK2mt$eR#WeURQTcq0028O001Na0000 +000000005+c)dLm)aA|NaUv_0~WN&gWaBN|8W^ZzBWNC79FJE72ZfSI1UoLQYP)h*<6ay3h000O8`*~ +JVR)sLnTmb+8bOZnZBme*a0000000000q=ESe765Q*a4%nWWo~3|axZXfVRUA1a&2U3a&s?VUu|J&Ze +L$6aCuNm0Rj{Q6aWAK2mt$eR#WZ?Ez&*&005c~001KZ0000000000005+cmkJgDaA|NaUv_0~WN&gWa +BN|8W^ZzBWNC79FJW$Ea&Kv5E^v8JO928D0~7!N00;p4c~(4IUfGI1^@v08UO$w00000000000001_f%p~{0B~t=FJE?LZe(wAFK}#ObY^dIZDeV3b1!vnX? +QMhc~DCM0u%!j000080Q-4XQ-k>iYCQk|08jt`03!eZ00000000000HlHO9Tos^X>c!Jc4cm4Z*nhiY ++-a}Z*py9X>xNfc4cyNX>V>WaCuNm0Rj{Q6aWAK2mt$eR#S!>I518J000mf001KZ0000000000005+c +Zypu^aA|NaUv_0~WN&gWaBN|8W^ZzBWNC79FL!BfWN&wKE^v8JO928D0~7!N00;p4c~(;r+t4op2LJ% +B6aWAq00000000000001_f&L{H0B~t=FJE?LZe(wAFK}{iXL4n8b1z?CX>MtBUtcb8c~DCM0u%!j000 +080Q-4XQ|?3ZU&aIg0DcPq02=@R00000000000HlFFEfxT9X>c!Jc4cm4Z*nhia&KpHWpi^cVqtPFaC +uNm0Rj{Q6aWAK2mt$eR#TCk?2lvw003VK0015U0000000000005+cJu(&maA|NaUv_0~WN&gWaB^>Fa +%FRKFJo_PZ*p@kaCuNm0Rj{Q6aWAK2mt$eR#Q%e)_ffU002z}0018V0000000000005+c-8L2gaA|Na +Uv_0~WN&gWaB^>Fa%FRKFJo_YZggdGE^v8JO928D0~7!N00;p4c~(>8*Y@fz0{{TE1poja000000000 +00001_fj2r90B~t=FJE?LZe(wAFK}{iXL4n8b1!pnX>M+1axQRrP)h*<6ay3h000O8`*~JVxf(FQYzF +`U`4a#DAOHXW0000000000q=BM6765Q*a4%nWWo~3|axZdaadl;LbaO9XUukY>bYEXCaCuNm0Rj{Q6a +WAK2mt$eR#VF+@nr@9006lG001KZ0000000000005+cOhpy|aA|NaUv_0~WN&gWa%FLKWpi|MFJE7FW +pZEK~phAOHX +W0000000000q=D>6765Q*a4%nWWo~3|axZdaadl;LbaO9ZWMOc0WpZ;aaCuNm0Rj{Q6aWAK2mt$eR#O +71vL9&%0006R000{R0000000000005+cwp|tgaA|NaUv_0~WN&gWa%FLKWpi|MFJW+LE^v8JO928D0~ +7!N00;p4c~(=qw23Qo3jhG$CjbB(00000000000001_fmmb~0B~t=FJE?LZe(wAFLGsZb!BsOb1z|ab +Z9Pcc~DCM0u%!j000080Q-4XQ-ZRh&n^J~0MP*e0384T00000000000HlEha25b?X>c!Jc4cm4Z*nhk +WpQ<7b98erV`Xx5b1rasP)h*<6ay3h000O8`*~JVuV#XDOV000 +0000000q=Alf765Q*a4%nWWo~3|axZdaadl;LbaO9bZ*Oa9WpgfYc~DCM0u%!j000080Q-4XQ)i?&gh +vDb0J01K03rYY00000000000HlG7h!y~FX>c!Jc4cm4Z*nhkWpQ<7b98erWq4y{aCB*JZgVbhc~DCM0 +u%!j000080Q-4XQ(+b-xsC(?0E7c!Jc4cm4Z*nhkWpQ<7b98er +Xk~10E^v8JO928D0~7!N00;p4c~(;!N^{|P0RRB?0ssIV00000000000001_f!dK40B~t=FJE?LZe(w +AFLGsZb!BsOb1!IbZ)?y-E^v8J +O928D0~7!N00;p4c~(;&WxEeF2LJ%@761Sv00000000000001_fx8hG0B~t=FJE?LZe(wAFLGsbZ)|p +DY-wUIaB^>UX=G(`b1rasP)h*<6ay3h000O8`*~JV%K=~A>Hz=%R0RM4BLDyZ0000000000q=7IQ7XW +Z+a4%nWWo~3|axZdab8l>RWo&6;FLGsYZ*p{Ha&ss60E9#U03 +!eZ00000000000HlFi8y5g@X>c!Jc4cm4Z*nhkWpi(Ac4cg7VlQ%Kb8l>RWpZ;aaCuNm0Rj{Q6aWAK2 +mt$eR#Q~m&~Q@)006oY001EX0000000000005+c&Mg-JaA|NaUv_0~WN&gWa%FRGY<6XAX<{#PbaHiL +baO6nc~DCM0u%!j000080Q-4XQvd(}00IC20000004V?f00000000000HlFnGZz4GX>c!Jc4cm4Z*nh +kWpi(Ac4cg7VlQKFZE#_9FJE72ZfSI1UoLQYP)h*<6ay3h000O8`*~JV8`mypV*mgEoB#j-FaQ7m000 +0000000q=Bh37XWZ+a4%nWWo~3|axZdab8l>RWo&6;FJo_QaA9;WV{dG1Wn*+{Z*Fs6VPa!0aCuNm0R +j{Q6aWAK2mt$eR#WC47Qf{a002=(001BW0000000000005+cS~M2`aA|NaUv_0~WN&gWbY*T~V`+4GF +JE72ZfSI1UoLQYP)h*<6ay3h000O8`*~JV8Qg7btpor7@(cg~AOHXW0000000000q=9`%7XWZ+a4%nW +Wo~3|axZjcZee3-ba^jdVRLzIV`*4;YaCuNm0Rj{Q6aWAK2mt$eR#T|q81C`{007 +tp0012T0000000000005+cAyF3qaA|NaUv_0~WN&gWbY*T~V`+4GFJWeMWpXZXc~DCM0u%!j000080Q +-4XQ_y|osl5UK0AK|G03HAU00000000000HlFVR2KknX>c!Jc4cm4Z*nhmWo}_(X>@rnVr6D;a%C=Xc +~DCM0u%!j000080Q-4XQv?g`YhnWc0CWcc03-ka00000000000HlFOR~Gc!Jc4cm4Z*nhmWo}_( +X>@rnVr6D;a%Eq0Y-MF|E^v8JO928D0~7!N00;p4c~(=VY_ikb0ssJK1pojW00000000000001_f$Lf +q0B~t=FJE?LZe(wAFLY&YVPk1@c`t5Za4v9pP)h*<6ay3h000O8`*~JVm{Kjfv;_bF^%(#F9RL6T000 +0000000q=5il7XWZ+a4%nWWo~3|axZjcZee3-ba^jwWpr|RE^v8JO928D0~7!N00;p4c~(>PR?T&(0{ +{T#3IG5c00000000000001_f$w7%0B~t=FJE?LZe(wAFLY&YVPk1@c`tKxZ*VSfc~DCM0u%!j000080 +Q-4XQytzSIjjQ!0AUCK03rYY00000000000HlG^XBPl)X>c!Jc4cm4Z*nhmWo}_(X>@rnbZ>HQVPtQ2 +WnwOHc~DCM0u%!j000080Q-4XQ(9$!S(69=03#Xz02}}S00000000000HlGwYZm};X>c!Jc4cm4Z*nh +mWo}_(X>@rncVTICE^v8JO928D0~7!N00;p4c~(=s(5w5a0002y0000T00000000000001_fs1q(0B~ +t=FJE?LZe(wAFLZBhY-ulFUukY>bYEXCaCuNm0Rj{Q6aWAK2mt$eR#V0R{PsW<0056y000~S0000000 +000005+cadj5}aA|NaUv_0~WN&gWbZ>2JX)j-JVRCb2axQRrP)h*<6ay3h000O8`*~JVg4{8H&I14dc +?tjk7ytkO0000000000q=D;-7XWZ+a4%nWWo~3|axZjmZER^TUvgzGaCuNm0Rj{Q6aWAK2mt$eR#PEK +Z5&Yq007Gh0018V0000000000005+c?~WG$aA|NaUv_0~WN&gWb#iQMX<{=kUtei%X>?y-E^v8JO928 +D0~7!N00;p4c~(;$KNE5S4FCW;DgXc@00000000000001_fqjz~0B~t=FJE?LZe(wAFLiQkY-wUMFJE +JCY;0v?bZKvHb1rasP)h*<6ay3h000O8`*~JVTd^H-KL7v#KL7v#9{>OV0000000000q=CSo7XWZ+a4 +%nWWo~3|axZmqY;0*_GcR9uWpZc!Jc4cm4Z*nhna%^mAVlyveZ*Fd7V{~b6ZZ2?nP)h*<6ay3h000O8`*~JVo&cQ- +MkoLP(~(^b0000000000q=AOG7XWZ+a4%nWWo~3|axZmqY;0*_GcRLrZf<2`bZKvHaBpvHE^v8 +JO928D0~7!N00;p4c~(<{YAOHX%00000000000001_fe+yq0B~t=FJE?LZe(wAFLiQkY-w +UMFJ*XRWpH$9Z*FrgaCuNm0Rj{Q6aWAK2mt$eR#V3)ODrG?004s_0012T0000000000005+cIqMeyaA +|NaUv_0~WN&gWb#iQMX<{=kW@%+?WOFWXc~DCM0u%!j000080Q-4XQ-`a+qkaPb0Eh_y03QGV000000 +00000HlGG^%nqeX>c!Jc4cm4Z*nhna%^mAVlyvhX>4V1Z*z1maCuNm0Rj{Q6aWAK2mt$eR#WJrk&9>+ +001*h001HY0000000000005+cPx%)BaA|NaUv_0~WN&gWb#iQMX<{=kaBpvHZDDR001j)0018V0000000000005+c+7}oAaA|NaUv_0~WN +&gWb#iQMX<{=ka%FRHZ*FsCE^v8JO928D0~7!N00;p4c~(;Z00002000000000d00000000000001_f +qFF<0B~t=FJE?LZe(wAFLiQkY-wUMFJo_RbaH88FJE72ZfSI1UoLQYP)h*<6ay3h000O8`*~JVDud}2 +wE+MCy#oLMF#rGn0000000000q=CUT7yxi-a4%nWWo~3|axZmqY;0*_GcRLrZgg^KVlQ7|aByXAXK8L +_UuAA~X>xCFE^v8JO928D0~7!N00;p4c~(<6WeLe=3;+NcD*yl}00000000000001_fyFl%0B~t=FJE +?LZe(wAFLiQkY-wUMFJo_RbaH88FJW+SWo~C_Ze=cTc~DCM0u%!j000080Q-4XQ@F=N{!|740J;$X04 +D$d00000000000HlF(L>K^YX>c!Jc4cm4Z*nhna%^mAVlyveZ*FvQX<{#KbZl*KZ*OcaaCuNm0Rj{Q6 +aWAK2mt$eR#RZPnqGkv000C+001Ze0000000000005+c3riRPaA|NaUv_0~WN&gWb#iQMX<{=kV{dMB +a%o~OaCvWVWo~nGY%XwlP)h*<6ay3h000O8`*~JV!0)=_!6g6yk%j;OE&u=k0000000000q=C|37yxi +-a4%nWWo~3|axZmqY;0*_GcRLrZgg^KVlQ)LV|8+6baG*Cb8v5RbS`jtP)h*<6ay3h000O8`*~JV%Tm +ZcSqK0Cxf=igBme*a0000000000q=Das7yxi-a4%nWWo~3|axZmqY;0*_GcRLrZgg^KVlQ)VV{3CRaC +uNm0Rj{Q6aWAK2mt$eR#N}~0006200000001}u0000000000005+cdX5+XaA|NaUv_0~WN&gWb#iQMX +<{=kV{dMBa%o~OUvp(+b#i5Na$#Md`ZfA2YaCuNm0Rj{Q6aWAK2mt$eR#UDjr@UDa003e(0021v0000000000005+cAfOlkaA| +NaUv_0~WN&gWb#iQMX<{=kV{dMBa%o~OUvp(+b#i5Na$#;0RtWW>|0BisN04M+e00000000000HlG?u^0ewX>c!Jc4cm4Z*nhna%^mAVlyvrVPk7yX +JvCQUtei%X>?y-E^v8JO928D0~7!N00;p4c~(>3X^-9}ApihshX4R000000000000001_fo8H80B~t= +FJE?LZe(wAFLiQkY-wUMFK}UFYhh<)b1!pgcrI{xP)h*<6ay3h000O8`*~JV000000ssI200000G5`P +o0000000000q=C)T7yxi-a4%nWWo~3|axZmqY;0*_GcRyqV{2h&WpgiLVPk7>Z*p{VFJE72ZfSI1UoL +QYP)h*<6ay3h000O8`*~JV-ne74NCE%=i3I=vG5`Po0000000000q=6sQ7yxi-a4%nWWo~3|axZmqY; +0*_GcRyqV{2h&WpgiLVPk7>Z*p{VFKuCKWoBt?WiD`eP)h*<6ay3h000O8`*~JV1rwupqyYc`p925@I +{*Lx0000000000q=C2A7yxi-a4%nWWo~3|axZmqY;0*_GcRyqV{2h&Wpgicb8KI2VRU0?UubW0bZ%j7 +WiMY}X>MtBUtcb8c~DCM0u%!j000080Q-4XQ$B=C*0Bfx0528*073u&00000000000HlGm*cbqCX>c! +Jc4cm4Z*nhna%^mAVlyvrVPk7yXJvCQb8~E8ZDDj{XkTb=b98QDZDlWCX>D+9Wo>0{bYXO9Z*DGdc~D +CM0u%!j000080Q-4XQ|5}9n%DsV0D}Yo03-ka00000000000HlG%;TQmLX>c!Jc4cm4Z*nhna%^mAVl +yvwbZKlaUtei%X>?y-E^v8JO928D0~7!N00;p4c~(<%5t5pw2LJ##6951v00000000000001_f#2g80 +B~t=FJE?LZe(wAFLiQkY-wUMFLiWjY%gPPZf<2`bZKvHE^v8JO928D0~7!N00;p4c~(;j&s+yy0ssI- +1^@sd00000000000001_fywI_0B~t=FJE?LZe(wAFLiQkY-wUMFLiWjY%g$fZ+LkwaCuNm0Rj{Q6aWA +K2mt$eR#Q#`_MiL!008m<001EX0000000000005+cX6_gOaA|NaUv_0~WN&gWb#iQMX<{=kb#!TLFL8 +Bcb!9Gac~DCM0u%!j000080Q-4XQ<7w_RO$r)02>eh03!eZ00000000000HlGT?-&4ZX>c!Jc4cm4Z* +nhna%^mAVlyvwbZKlaa%FLKWpi{caCuNm0Rj{Q6aWAK2mt$eR#P@8)wEy*006cP001Na00000000000 +05+c%=H)maA|NaUv_0~WN&gWb#iQMX<{=kb#!TLFLGsbaBpsNWiD`eP)h*<6ay3h000O8`*~JVmFNkb +&=vpyk5d2uApigX0000000000q=9bx7yxi-a4%nWWo~3|axZmqY;0*_GcR>?X>2cYWpr|RE^v8JO928 +D0~7!N00;p4c~(=TlKaP}761SlLjV9E00000000000001_fqfDg0B~t=FJE?LZe(wAFLiQkY-wUMFLi +WjY%gc!Jc4 +cm4Z*nhna%^mAVlyvwbZKlab8~ETa$#<2c3jhEUCjbB=0 +0000000000001_fweIi0B~t=FJE?LZe(wAFLiQkY-wUMFLiWjY%g?aZDntDbS`jtP)h*<6ay3h000O8 +`*~JV=c|?Zr4j%D-!=dM9{>OV0000000000q=B|Q831r;a4%nWWo~3|axZmqY;0*_GcR>?X>2cba%?V +ec~DCM0u%!j000080Q-4XQ=&${ali)v02~zn03ZMW00000000000HlGKP#FMlX>c!Jc4cm4Z*nhna%^ +mAVlyvwbZKlacVTICE^v8JO928D0~7!N00;p4c~(<+&>wL!3jhF9DF6T@00000000000001_ftFYq0B +~t=FJE?LZe(wAFLz~PWo~0{WNB_^b1z?CX>MtBUtcb8c~DCM0u%!j000080Q-4XQ^&0civc!Jc4cm4Z*nhpWnyJ+V{c?>ZfA2ZY++($Y;!Jfc~DCM0u%!j00008 +0Q-4XQ&4{~A4CEG02u`U03-ka00000000000HlFWY8e1c!Jc4cm4Z*nhpWnyJ+V{c?>ZfA2ZZEI{ +{Vr6V|E^v8JO928D0~7!N00;p4c~(Mn8FL+;db7gX0WMyV)Ze?UHaCuNm1qJ{B005Z*nE_CM0 +06po82|tP """ + + if __name__ == "__main__": main() diff --git a/news/2317.doc.rst b/news/2317.doc.rst deleted file mode 100644 index ff56fe4d..00000000 --- a/news/2317.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Added documenation about variable expansion in ``Pipfile`` entries. diff --git a/news/2373.bugfix.rst b/news/2373.bugfix.rst deleted file mode 100644 index 9b42add1..00000000 --- a/news/2373.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Raise `PipenvUsageError` when [[source]] does not contain url field. diff --git a/news/2553.behavior.rst b/news/2553.behavior.rst deleted file mode 100644 index d66edfa2..00000000 --- a/news/2553.behavior.rst +++ /dev/null @@ -1 +0,0 @@ -Make conservative checks of known exceptions when subprocess returns output, so user won't see the whole traceback - just the error. \ No newline at end of file diff --git a/news/2722.bugfix.rst b/news/2722.bugfix.rst deleted file mode 100644 index 8c26df8d..00000000 --- a/news/2722.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed a bug which caused editable package resolution to sometimes fail with an unhelpful setuptools-related error message. diff --git a/news/2783.bugfix.rst b/news/2783.bugfix.rst deleted file mode 100644 index 7fa3cfd1..00000000 --- a/news/2783.bugfix.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fixed an issue which caused errors due to reliance on the system utilities ``which`` and ``where`` which may not always exist on some systems. -- Fixed a bug which caused periodic failures in python discovery when executables named ``python`` were not present on the target ``$PATH``. diff --git a/news/3053.bugfix.rst b/news/3053.bugfix.rst deleted file mode 100644 index 21134f59..00000000 --- a/news/3053.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Dependency resolution now writes hashes for local and remote files to the lockfile. diff --git a/news/3071.bugfix.rst b/news/3071.bugfix.rst deleted file mode 100644 index dd4145ea..00000000 --- a/news/3071.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed a bug which prevented ``pipenv graph`` from correctly showing all dependencies when running from within ``pipenv shell``. diff --git a/news/3120.doc.rst b/news/3120.doc.rst deleted file mode 100644 index a2f8ae6c..00000000 --- a/news/3120.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Consolidate all contributing docs in the rst file diff --git a/news/3148.bugfix.rst b/news/3148.bugfix.rst deleted file mode 100644 index 1f0f4a62..00000000 --- a/news/3148.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed resolution of direct-url dependencies in ``setup.py`` files to respect ``PEP-508`` style URL dependencies. diff --git a/news/3148.feature.rst b/news/3148.feature.rst deleted file mode 100644 index e33434db..00000000 --- a/news/3148.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Added support for resolution of direct-url dependencies in ``setup.py`` files to respect ``PEP-508`` style URL dependencies. diff --git a/news/3246.doc.rst b/news/3246.doc.rst deleted file mode 100644 index 284ecd0a..00000000 --- a/news/3246.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Update the out-dated manual page. diff --git a/news/3292.trivial.rst b/news/3292.trivial.rst deleted file mode 100644 index 9cab5de1..00000000 --- a/news/3292.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Update pytest-pypi documentation not to be pytest-httpbin documentation. diff --git a/news/3298.bugfix.rst b/news/3298.bugfix.rst deleted file mode 100644 index aa378723..00000000 --- a/news/3298.bugfix.rst +++ /dev/null @@ -1,3 +0,0 @@ -Fixed a bug which caused failures in warning reporting when running pipenv inside a virtualenv under some circumstances. - -- Fixed a bug with package discovery when running ``pipenv clean``. diff --git a/news/3298.feature.rst b/news/3298.feature.rst deleted file mode 100644 index 65a49424..00000000 --- a/news/3298.feature.rst +++ /dev/null @@ -1,5 +0,0 @@ -Added full support for resolution of all dependency types including direct URLs, zip archives, tarballs, etc. - -- Improved error handling and formatting. - -- Introduced improved cross platform stream wrappers for better ``stdout`` and ``stderr`` consistency. diff --git a/news/3298.vendor.rst b/news/3298.vendor.rst deleted file mode 100644 index cab9a50b..00000000 --- a/news/3298.vendor.rst +++ /dev/null @@ -1,34 +0,0 @@ -Updated vendored dependencies: - - - **attrs**: ``18.2.0`` => ``19.1.0`` - - **certifi**: ``2018.10.15`` => ``2019.3.9`` - - **cached_property**: ``1.4.3`` => ``1.5.1`` - - **cerberus**: ``1.2.0`` => ``1.3.1`` - - **click-completion**: ``0.5.0`` => ``0.5.1`` - - **colorama**: ``0.3.9`` => ``0.4.1`` - - **distlib**: ``0.2.8`` => ``0.2.9`` - - **idna**: ``2.7`` => ``2.8`` - - **jinja2**: ``2.10.0`` => ``2.10.1`` - - **markupsafe**: ``1.0`` => ``1.1.1`` - - **orderedmultidict**: ``(new)`` => ``1.0`` - - **packaging**: ``18.0`` => ``19.0`` - - **parse**: ``1.9.0`` => ``1.12.0`` - - **pathlib2**: ``2.3.2`` => ``2.3.3`` - - **pep517**: ``(new)`` => ``0.5.0`` - - **pexpect**: ``4.6.0`` => ``4.7.0`` - - **pipdeptree**: ``0.13.0`` => ``0.13.2`` - - **pyparsing**: ``2.2.2`` => ``2.3.1`` - - **python-dotenv**: ``0.9.1`` => ``0.10.2`` - - **pythonfinder**: ``1.1.10`` => ``1.2.1`` - - **pytoml**: ``(new)`` => ``0.1.20`` - - **requests**: ``2.20.1`` => ``2.21.0`` - - **requirementslib**: ``1.3.3`` => ``1.5.0`` - - **scandir**: ``1.9.0`` => ``1.10.0`` - - **shellingham**: ``1.2.7`` => ``1.3.1`` - - **six**: ``1.11.0`` => ``1.12.0`` - - **tomlkit**: ``0.5.2`` => ``0.5.3`` - - **urllib3**: ``1.24`` => ``1.25.2`` - - **vistir**: ``0.3.0`` => ``0.4.1`` - - **yaspin**: ``0.14.0`` => ``0.14.3`` - -- Removed vendored dependency **cursor**. diff --git a/news/3307.bugfix.rst b/news/3307.bugfix.rst deleted file mode 100644 index 0f095c1a..00000000 --- a/news/3307.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Quote command arguments with carets (``^``) on Windows to work around unintended shell escapes. diff --git a/news/3313.bugfix.rst b/news/3313.bugfix.rst deleted file mode 100644 index 2f7a6ffc..00000000 --- a/news/3313.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Handle alternate names for UTF-8 encoding. diff --git a/news/3318.bugfix.rst b/news/3318.bugfix.rst deleted file mode 100644 index b56f75dd..00000000 --- a/news/3318.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Abort pipenv before adding the non-exist package to Pipfile. diff --git a/news/3324.bugfix.rst b/news/3324.bugfix.rst deleted file mode 100644 index d13a8d46..00000000 --- a/news/3324.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Don't normalize the package name user passes in. diff --git a/news/3328.feature.rst b/news/3328.feature.rst deleted file mode 100644 index 7e92d39f..00000000 --- a/news/3328.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Pipenv will now successfully recursively lock VCS sub-dependencies. diff --git a/news/3339.bugfix b/news/3339.bugfix deleted file mode 100644 index 8e67e36f..00000000 --- a/news/3339.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix a bug where custom virtualenv can not be activated with pipenv shell diff --git a/news/3346.doc.rst b/news/3346.doc.rst deleted file mode 100644 index c985f001..00000000 --- a/news/3346.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Move CLI docs to its own page. diff --git a/news/3348.feature.rst b/news/3348.feature.rst deleted file mode 100644 index 50547a3d..00000000 --- a/news/3348.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Added support for ``--verbose`` output to ``pipenv run``. \ No newline at end of file diff --git a/news/3351.bugfix.rst b/news/3351.bugfix.rst deleted file mode 100644 index d2d9c675..00000000 --- a/news/3351.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix a bug that ``--site-packages`` flag is not recognized. diff --git a/news/3353.bugfix b/news/3353.bugfix deleted file mode 100644 index 23e2b6af..00000000 --- a/news/3353.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix a bug where pipenv --clear is not working diff --git a/news/3362.trivial.rst b/news/3362.trivial.rst deleted file mode 100644 index 2216b071..00000000 --- a/news/3362.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -The inline tables won't be rewritten now. diff --git a/news/3368.feature.rst b/news/3368.feature.rst deleted file mode 100644 index a998fce1..00000000 --- a/news/3368.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Pipenv will now discover and resolve the intrinsic dependencies of **all** VCS dependencies, whether they are editable or not, to prevent resolution conflicts. diff --git a/news/3384.bugfix.rst b/news/3384.bugfix.rst deleted file mode 100644 index f85cd168..00000000 --- a/news/3384.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix unhashable type error during ``$ pipenv install --selective-upgrade`` diff --git a/news/3386.behavior.rst b/news/3386.behavior.rst deleted file mode 100644 index 8ddc27c6..00000000 --- a/news/3386.behavior.rst +++ /dev/null @@ -1 +0,0 @@ -Do not touch Pipfile early and rely on it so that one can do ``pipenv sync`` without a Pipfile. diff --git a/news/3404.bugfix.rst b/news/3404.bugfix.rst deleted file mode 100644 index fa678d6a..00000000 --- a/news/3404.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed a keyerror which could occur when locking VCS dependencies in some cases. diff --git a/news/3427.bugfix.rst b/news/3427.bugfix.rst deleted file mode 100644 index 76aeb489..00000000 --- a/news/3427.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed a bug that ``ValidationError`` is thrown when some fields are missing in source section. diff --git a/news/3434.trivial.rst b/news/3434.trivial.rst deleted file mode 100644 index 622b52db..00000000 --- a/news/3434.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Improve the error message when one tries to initialize a Pipenv project under ``/``. diff --git a/news/3446.trivial.rst b/news/3446.trivial.rst deleted file mode 100644 index c3f6a000..00000000 --- a/news/3446.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed the wrong order of old and new hashes in message. diff --git a/news/3449.bugfix.rst b/news/3449.bugfix.rst deleted file mode 100644 index 4ed07046..00000000 --- a/news/3449.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Updated the index names in lock file when source name in Pipfile is changed. diff --git a/news/3479.bugfix.rst b/news/3479.bugfix.rst deleted file mode 100644 index 15e8e0f6..00000000 --- a/news/3479.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed an issue which caused ``pipenv install --help`` to show duplicate entries for ``--pre``. diff --git a/news/3499.doc.rst b/news/3499.doc.rst deleted file mode 100644 index d98b0f15..00000000 --- a/news/3499.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Replace (non-existant) video on docs index.rst with equivalent gif. diff --git a/news/3502.bugfix.rst b/news/3502.bugfix.rst deleted file mode 100644 index 2700a740..00000000 --- a/news/3502.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix bug causing ``[SSL: CERTIFICATE_VERIFY_FAILED]`` when Pipfile ``[[source]]`` has verify_ssl=false and url with custom port. diff --git a/news/3522.doc.rst b/news/3522.doc.rst deleted file mode 100644 index 3d71061f..00000000 --- a/news/3522.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Clarify wording in Basic Usage example on using double quotes to escape shell redirection diff --git a/news/3527.doc.rst b/news/3527.doc.rst deleted file mode 100644 index b6043a08..00000000 --- a/news/3527.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Ensure docs show navigation on small-screen devices diff --git a/news/3537.bugfix.rst b/news/3537.bugfix.rst deleted file mode 100644 index 779b9d7c..00000000 --- a/news/3537.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix ``sync --sequential`` ignoring ``pip install`` errors and logs. diff --git a/news/3577.feature.rst b/news/3577.feature.rst deleted file mode 100644 index 7944c098..00000000 --- a/news/3577.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Added a new environment variable, ``PIPENV_RESOLVE_VCS``, to toggle dependency resolution off for non-editable VCS, file, and URL based dependencies. diff --git a/news/3584.bugfix.rst b/news/3584.bugfix.rst deleted file mode 100644 index 09684d1d..00000000 --- a/news/3584.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix the issue that lock file can't be created when ``PIPENV_PIPFILE`` is not under working directory. diff --git a/news/3595.feature.rst b/news/3595.feature.rst deleted file mode 100644 index 30b755b9..00000000 --- a/news/3595.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Added the ability for Windows users to enable emojis by setting ``PIPENV_HIDE_EMOJIS=0``. diff --git a/news/3621.trivial.rst b/news/3621.trivial.rst deleted file mode 100644 index 4d38a31e..00000000 --- a/news/3621.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Removed unused vendored package shutilwhich diff --git a/news/3629.doc.rst b/news/3629.doc.rst deleted file mode 100644 index 4d878c40..00000000 --- a/news/3629.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Added a link to the TOML Spec under General Recommendations & Version Control to clarify how Pipfiles should be written. diff --git a/news/3640.trivial.rst b/news/3640.trivial.rst deleted file mode 100644 index eb9b718d..00000000 --- a/news/3640.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Removed unused vendored package blindspin diff --git a/news/3644.trivial.rst b/news/3644.trivial.rst deleted file mode 100644 index 5a7db2e1..00000000 --- a/news/3644.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Use tablib instead of requests in tests to avoid failures when vendored diff --git a/news/3647.bugfix.rst b/news/3647.bugfix.rst deleted file mode 100644 index cb64edc1..00000000 --- a/news/3647.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Pipenv will no longer inadvertently set ``editable=True`` on all vcs dependencies. diff --git a/news/3652.feature.rst b/news/3652.feature.rst deleted file mode 100644 index 7e5becb9..00000000 --- a/news/3652.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Allow overriding PIPENV_INSTALL_TIMEOUT environment variable (in seconds). diff --git a/news/3656.bugfix.rst b/news/3656.bugfix.rst deleted file mode 100644 index 58df2020..00000000 --- a/news/3656.bugfix.rst +++ /dev/null @@ -1,2 +0,0 @@ -The ``--keep-outdated`` argument to ``pipenv install`` and ``pipenv lock`` will now drop specifier constraints when encountering editable dependencies. -- In addition, ``--keep-outdated`` will retain specifiers that would otherwise be dropped from any entries that have not been updated. diff --git a/news/3669.trivial.rst b/news/3669.trivial.rst deleted file mode 100644 index 86ff9280..00000000 --- a/news/3669.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Allow KeyboardInterrupt to cancel test suite checks for working internet and ssh diff --git a/news/3684.trivial.rst b/news/3684.trivial.rst deleted file mode 100644 index 64561ec7..00000000 --- a/news/3684.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Cleaned up some conditional logic that would always evaluate ``True``. diff --git a/news/3711.trivial.rst b/news/3711.trivial.rst deleted file mode 100644 index 48c531b2..00000000 --- a/news/3711.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Add installation instructions for Debian Buster+ in README diff --git a/news/3718.bugfix.rst b/news/3718.bugfix.rst deleted file mode 100644 index 7a90ea50..00000000 --- a/news/3718.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed a bug which sometimes caused pipenv to fail to respect the ``--site-packages`` flag when passed with ``pipenv install``. diff --git a/news/3724.trivial.rst b/news/3724.trivial.rst deleted file mode 100644 index 63a55013..00000000 --- a/news/3724.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Update pytest configuration to support pytest 4. diff --git a/news/3738.feature.rst b/news/3738.feature.rst deleted file mode 100644 index bb8237e7..00000000 --- a/news/3738.feature.rst +++ /dev/null @@ -1,3 +0,0 @@ -Allow overriding PIP_EXISTS_ACTION evironment variable (value is passed to pip install). -Possible values here: https://pip.pypa.io/en/stable/reference/pip/#exists-action-option -Useful when you need to `PIP_EXISTS_ACTION=i` (ignore existing packages) - great for CI environments, where you need really fast setup. diff --git a/news/3745.bugfix.rst b/news/3745.bugfix.rst deleted file mode 100644 index 229047a4..00000000 --- a/news/3745.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Normalize the package names to lowercase when comparing used and in-Pipfile packages. diff --git a/news/3753.trivial.rst b/news/3753.trivial.rst deleted file mode 100644 index 2ab71d38..00000000 --- a/news/3753.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Improve the error message of ``pipenv --py`` when virtualenv can't be found. diff --git a/news/3759.doc.rst b/news/3759.doc.rst deleted file mode 100644 index 5aebd29e..00000000 --- a/news/3759.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Updated the documentation with the new ``pytest`` entrypoint. diff --git a/news/3763.feature.rst b/news/3763.feature.rst deleted file mode 100644 index 544a1ace..00000000 --- a/news/3763.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Pipenv will no longer forcibly override ``PIP_NO_DEPS`` on all vcs and file dependencies as resolution happens on these in a pre-lock step. diff --git a/news/3766.bugfix.rst b/news/3766.bugfix.rst deleted file mode 100644 index f7f7d304..00000000 --- a/news/3766.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -``pipenv update --outdated`` will now correctly handle comparisons between pre/post-releases and normal releases. diff --git a/news/3766.vendor.rst b/news/3766.vendor.rst deleted file mode 100644 index 16ebbed9..00000000 --- a/news/3766.vendor.rst +++ /dev/null @@ -1 +0,0 @@ -Updated ``pip_shims`` to support ``--outdated`` with new pip versions. diff --git a/news/3768.bugfix.rst b/news/3768.bugfix.rst deleted file mode 100644 index 8efe0197..00000000 --- a/news/3768.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed a ``KeyError`` which could occur when pinning outdated VCS dependencies via ``pipenv lock --keep-outdated``. diff --git a/news/3786.bugfix.rst b/news/3786.bugfix.rst deleted file mode 100644 index 210f7973..00000000 --- a/news/3786.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Resolved an issue which caused resolution to fail when encountering poorly formatted ``python_version`` markers in ``setup.py`` and ``setup.cfg`` files. diff --git a/news/3794.bugfix.rst b/news/3794.bugfix.rst deleted file mode 100644 index a2999fdd..00000000 --- a/news/3794.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix a bug that installation errors are displayed as a list. diff --git a/news/3807.bugfix.rst b/news/3807.bugfix.rst deleted file mode 100644 index 6330c6bc..00000000 --- a/news/3807.bugfix.rst +++ /dev/null @@ -1,2 +0,0 @@ -Update ``pythonfinder`` to fix a problem that ``python.exe`` will be mistakenly chosen for -virtualenv creation under WSL. diff --git a/news/3809.bugfix.rst b/news/3809.bugfix.rst deleted file mode 100644 index bd603aaf..00000000 --- a/news/3809.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed several bugs which could prevent editable VCS dependencies from being installed into target environments, even when reporting successful installation. diff --git a/news/3810.feature.rst b/news/3810.feature.rst deleted file mode 100644 index 33503779..00000000 --- a/news/3810.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Improved verbose logging output during ``pipenv lock`` will now stream output to the console while maintaining a spinner. diff --git a/news/3819.bugfix.rst b/news/3819.bugfix.rst deleted file mode 100644 index a6e05fb5..00000000 --- a/news/3819.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -``pipenv check --system`` should find the correct Python interpreter when ``python`` does not exist on the system. diff --git a/news/3842.bugfix.rst b/news/3842.bugfix.rst deleted file mode 100644 index fb21be89..00000000 --- a/news/3842.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Resolve the symlinks when the path is absolute. diff --git a/news/3844.behavior.rst b/news/3844.behavior.rst deleted file mode 100644 index 2648e839..00000000 --- a/news/3844.behavior.rst +++ /dev/null @@ -1 +0,0 @@ -Re-enable ``--help`` option for ``pipenv run`` command. diff --git a/news/3879.bugfix.rst b/news/3879.bugfix.rst deleted file mode 100644 index 95413ca5..00000000 --- a/news/3879.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Pass ``--pre`` and ``--clear`` options to ``pipenv update --outdated``. diff --git a/news/3885.trivial.rst b/news/3885.trivial.rst deleted file mode 100644 index 7782e0c9..00000000 --- a/news/3885.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Remove a misleading code comment from Specifying Versions documentation. diff --git a/news/3911.doc.rst b/news/3911.doc.rst deleted file mode 100644 index a5ab134f..00000000 --- a/news/3911.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Fix link to GIF in README.md demonstrating Pipenv's usage, and add descriptive alt text. diff --git a/news/3912.doc.rst b/news/3912.doc.rst deleted file mode 100644 index 24598d19..00000000 --- a/news/3912.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Added a line describing potential issues in fancy extension. diff --git a/news/3913.doc.rst b/news/3913.doc.rst deleted file mode 100644 index 54fbbfe8..00000000 --- a/news/3913.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Documental description of how Pipfile works and association with Pipenv. diff --git a/news/3914.doc.rst b/news/3914.doc.rst deleted file mode 100644 index ae37d61d..00000000 --- a/news/3914.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Clarify the proper value of `python_version` and `python_full_version`. diff --git a/news/3915.doc.rst b/news/3915.doc.rst deleted file mode 100644 index 2cc94a20..00000000 --- a/news/3915.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Write description for --deploy extension and few extensions differences. diff --git a/news/4018.feature.rst b/news/4018.feature.rst deleted file mode 100644 index fcd6a2a9..00000000 --- a/news/4018.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Added support for automatic python installs via ``asdf`` and associated ``PIPENV_DONT_USE_ASDF`` environment variable. diff --git a/news/4045.bugfix.rst b/news/4045.bugfix.rst deleted file mode 100644 index 6558018c..00000000 --- a/news/4045.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Honor PIPENV_SPINNER environment variable diff --git a/news/4137.doc b/news/4137.doc deleted file mode 100644 index 45de74d2..00000000 --- a/news/4137.doc +++ /dev/null @@ -1 +0,0 @@ -Updated documentation to point to working links. \ No newline at end of file diff --git a/news/4893.feature.rst b/news/4893.feature.rst new file mode 100644 index 00000000..ea2ab4dd --- /dev/null +++ b/news/4893.feature.rst @@ -0,0 +1 @@ +New CLI command ``verify``, checks the Pipfile.lock is up-to-date \ No newline at end of file diff --git a/news/4935.behavior.rst b/news/4935.behavior.rst new file mode 100644 index 00000000..0e2c3219 --- /dev/null +++ b/news/4935.behavior.rst @@ -0,0 +1 @@ +Pattern expansion for arguments was disabled on Windows. diff --git a/news/4944.bugfix.mdB b/news/4944.bugfix.mdB new file mode 100644 index 00000000..e27a224f --- /dev/null +++ b/news/4944.bugfix.mdB @@ -0,0 +1 @@ +Use `CI` environment value, over mere existence of name diff --git a/news/towncrier_template.rst b/news/towncrier_template.rst index 0fca0c3f..d71025bd 100644 --- a/news/towncrier_template.rst +++ b/news/towncrier_template.rst @@ -1,3 +1,14 @@ +{% if top_line %} +{{ top_line }} +{{ top_underline * ((top_line)|length)}} +{% elif versiondata.name %} +{{ versiondata.name }} {{ versiondata.version }} ({{ versiondata.date }}) +{{ top_underline * ((versiondata.name + versiondata.version + versiondata.date)|length + 4)}} +{% else %} +{{ versiondata.version }} ({{ versiondata.date }}) +{{ top_underline * ((versiondata.version + versiondata.date)|length + 3)}} +{% endif %} + {% for section in sections %} {% set underline = "-" %} {% if section %} diff --git a/peeps/PEEP-003.md b/peeps/PEEP-003.md index d0493eaa..7744e728 100644 --- a/peeps/PEEP-003.md +++ b/peeps/PEEP-003.md @@ -6,4 +6,4 @@ Pipenv will be governed by a board of maintainers (trusted collaborators to the The BDFL retains his title, however, revokes himself of his powers. -PEEP approval will be determined by available members of the board of maintainers, in private or public channels. +PEEP approval will be determined by available members of the board of maintainers, in private or public channels. diff --git a/peeps/PEEP-006.md b/peeps/PEEP-006.md new file mode 100644 index 00000000..5a3739e1 --- /dev/null +++ b/peeps/PEEP-006.md @@ -0,0 +1,62 @@ +# PEEP-006: Include all deps in output of `pipenv lock -r --dev` + +This proposal makes the behavior of `pipenv lock --requirements --dev` +consistent with the behaviour of other commands: converting all dependencies, +not just the development dependencies. + +☤ + +If you type `pipenv lock --help` the help document says: + +```bash +-d, --dev Install both develop and default packages. [env var:PIPENV_DEV] +``` + +That is not accurate and confusing for `pipenv lock -r`, which only produces the develop requirments. + +This PEEP proposes to change the behavior of `pipenv lock -r -d` to produce **all** requirements, both develop +and default. The help string of `-d/--dev` will be changed to **"Generate both develop and default requirements"**. + +As the existing behaviour was intended to support generating traditional `dev-requirements.txt` +files, a new flag, `--dev-only`, will be introduced to restrict output to development requirements only. + +When the new `pipenv lock` specific flag is used, the common `-d/--dev` flag is redundant, but +ignored (i.e. `pipenv lock -r --dev-only` and `pipenv lock -r --dev --dev-only` do the same thing). +If `--dev-only` is specified without `-r/--requirements`, then `PipenvOptionsError` will be thrown. + +As part of this change, `pipenv lock --requirements` will be updated to emit a comment header +indicating that the file was autogenerated, and the options passed to `pipenv lock`. This will use +the following `pip-compile` inspired format: + + # + # These requirements were autogenerated by pipenv + # To regenerate from the project's Pipfile, run: + # + # pipenv lock --requirements + # + +`--dev` or `--dev-only` will be append to the emitted regeneration command if +those options are set. + +To allow this new header to be turned off, `pipenv lock --requirements` will also support the same +`--header/--no-header` options that `pip-compile` offers. + +In the first release including this change, and in releases for at least 6 months from that date, +the emitted header will include the following note when the `--dev` option is set: + + # Note: in pipenv 2020.x, "--dev" changed to emit both default and development + # requirements. To emit only development requirements, pass "--dev-only". + +## Impact + +The users relying on the old behavior will get more requirements listed in the +``dev-requirements.txt`` file, which in most cases is harmless. They can pass +the `--dev-only` flag after updating `pipenv` to achieve the same thing as before. + +## Related issues: + +- #3316 + +## Related pull requests: + +- #4183 diff --git a/peeps/PEEP-044.md b/peeps/PEEP-044.md new file mode 100644 index 00000000..38d60dd3 --- /dev/null +++ b/peeps/PEEP-044.md @@ -0,0 +1,54 @@ +# PEEP-044: safety-db integration, squelch, and output. + +pipenv check needs offline, ci, and other output capabilities. + +☤ + +Not everyone can utilize pipenv check and access the internet. Safety check knew this +and that is why they created safety-db. This repository contains a json database that +is updated monthly. Safety check allows you to pass a --db flag that is a local directory +containing that database. Safety check also allows you to pass --json, --bare, and +--full-report. Pipenv check has their own way of displaying the results that is why I +believe there should be a --output flag that allows users to specify json, bare, +and full-report from safety check and default for the current pipenv check output. +Currently, pipenv check has a lot of stdout messages and makes it harder to pipe +the results into something to be checked (especially for continuous integration +pipelines). That is why adding a --squelch switch is also important. This will be +default False (display all stdout); however, the user has the option to add the +--squelch switch to make the output only come from safety check. + +## Current implementation: +### Example 1 +``` bash +pipenv check +Checking PEP 508 requirements... +Passed! +Checking installed package safety... +25853: insecure-package <0.2.0 resolved (0.1.0 installed)! +This is an insecure package with lots of exploitable security vulnerabilities. +``` +### Example 2 +``` bash +pipenv check | jq length +parse error: Invalid numeric literal at line 1, column 9 +``` + +## Future implementation: +### Example 1 +``` bash +pipenv check --db /Users/macbookpro/workspace/test/safety-db/data/ --output json --squelch +[ + [ + "insecure-package", + "<0.2.0", + "0.1.0", + "This is an insecure package with lots of exploitable security vulnerabilities.", + "25853" + ] +] +``` +### Example 2 +``` bash +pipenv check --db /Users/macbookpro/workspace/test/safety-db/data/ --output json --squelch | jq length +1 +``` diff --git a/pipenv/__init__.py b/pipenv/__init__.py index 624397c5..8cc14037 100644 --- a/pipenv/__init__.py +++ b/pipenv/__init__.py @@ -1,4 +1,3 @@ -# -*- coding=utf-8 -*- # |~~\' |~~ # |__/||~~\|--|/~\\ / # | ||__/|__| |\/ @@ -8,7 +7,7 @@ import os import sys import warnings -from .__version__ import __version__ # noqa +from pipenv.__version__ import __version__ # noqa PIPENV_ROOT = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) @@ -20,13 +19,14 @@ sys.path.insert(0, PIPENV_VENDOR) sys.path.insert(0, PIPENV_PATCHED) from pipenv.vendor.urllib3.exceptions import DependencyWarning -from pipenv.vendor.vistir.compat import ResourceWarning, fs_str +from pipenv.vendor.vistir.compat import fs_str warnings.filterwarnings("ignore", category=DependencyWarning) warnings.filterwarnings("ignore", category=ResourceWarning) warnings.filterwarnings("ignore", category=UserWarning) - +# Load patched pip instead of system pip +os.environ["PIP_SHIMS_BASE_MODULE"] = fs_str("pipenv.patched.notpip") os.environ["PIP_DISABLE_PIP_VERSION_CHECK"] = fs_str("1") # Hack to make things work better. @@ -35,6 +35,8 @@ try: del sys.modules["concurrency"] except Exception: pass +if "urllib3" in sys.modules: + del sys.modules["urllib3"] from pipenv.vendor.vistir.misc import get_text_stream diff --git a/pipenv/__main__.py b/pipenv/__main__.py index 491a4d13..ad23c8e8 100644 --- a/pipenv/__main__.py +++ b/pipenv/__main__.py @@ -1,4 +1,4 @@ -from .cli import cli +from pipenv.cli import cli if __name__ == "__main__": diff --git a/pipenv/__version__.py b/pipenv/__version__.py index a745170a..27f9568a 100644 --- a/pipenv/__version__.py +++ b/pipenv/__version__.py @@ -2,4 +2,4 @@ # // ) ) / / // ) ) //___) ) // ) ) || / / # //___/ / / / //___/ / // // / / || / / # // / / // ((____ // / / ||/ / -__version__ = "2018.11.27.dev0" +__version__ = "2022.1.9.dev0" diff --git a/pipenv/_compat.py b/pipenv/_compat.py index fdcca7f5..4c8b643f 100644 --- a/pipenv/_compat.py +++ b/pipenv/_compat.py @@ -1,4 +1,3 @@ -# -*- coding=utf-8 -*- """A compatibility module for pipenv's backports and manipulations. Exposes a standard API that enables compatibility across python versions, @@ -7,26 +6,13 @@ operating systems, etc. import sys import warnings -import six -import vistir - -from .vendor.vistir.compat import ( - NamedTemporaryFile, Path, ResourceWarning, TemporaryDirectory -) - - -# Backport required for earlier versions of Python. -if sys.version_info < (3, 3): - from .vendor.backports.shutil_get_terminal_size import get_terminal_size -else: - from shutil import get_terminal_size +from pipenv.vendor import vistir warnings.filterwarnings("ignore", category=ResourceWarning) __all__ = [ - "NamedTemporaryFile", "Path", "ResourceWarning", "TemporaryDirectory", - "get_terminal_size", "getpreferredencoding", "DEFAULT_ENCODING", "canonical_encoding_name", + "getpreferredencoding", "DEFAULT_ENCODING", "canonical_encoding_name", "force_encoding", "UNICODE_TO_ASCII_TRANSLATION_MAP", "decode_output", "fix_utf8" ] @@ -35,12 +21,7 @@ def getpreferredencoding(): import locale # Borrowed from Invoke # (see https://github.com/pyinvoke/invoke/blob/93af29d/invoke/runners.py#L881) - _encoding = locale.getpreferredencoding(False) - if six.PY2 and not sys.platform == "win32": - _default_encoding = locale.getdefaultlocale()[1] - if _default_encoding is not None: - _encoding = _default_encoding - return _encoding + return locale.getpreferredencoding(False) DEFAULT_ENCODING = getpreferredencoding() @@ -69,7 +50,7 @@ def force_encoding(): return DEFAULT_ENCODING, DEFAULT_ENCODING stdout_encoding = canonical_encoding_name(sys.stdout.encoding) stderr_encoding = canonical_encoding_name(sys.stderr.encoding) - if sys.platform == "win32" and sys.version_info >= (3, 1): + if sys.platform == "win32": return DEFAULT_ENCODING, DEFAULT_ENCODING if stdout_encoding != "utf-8" or stderr_encoding != "utf-8": @@ -110,10 +91,10 @@ OUT_ENCODING, ERR_ENCODING = force_encoding() UNICODE_TO_ASCII_TRANSLATION_MAP = { - 8230: u"...", - 8211: u"-", - 10004: u"OK", - 10008: u"x", + 8230: "...", + 8211: "-", + 10004: "OK", + 10008: "x", } @@ -124,26 +105,21 @@ def decode_for_output(output, target=sys.stdout): def decode_output(output): - if not isinstance(output, six.string_types): + if not isinstance(output, str): return output try: output = output.encode(DEFAULT_ENCODING) except (AttributeError, UnicodeDecodeError, UnicodeEncodeError): - if six.PY2: - output = unicode.translate(vistir.misc.to_text(output), # noqa - UNICODE_TO_ASCII_TRANSLATION_MAP) - else: - output = output.translate(UNICODE_TO_ASCII_TRANSLATION_MAP) + output = output.translate(UNICODE_TO_ASCII_TRANSLATION_MAP) output = output.encode(DEFAULT_ENCODING, "replace") return vistir.misc.to_text(output, encoding=DEFAULT_ENCODING, errors="replace") def fix_utf8(text): - if not isinstance(text, six.string_types): + if not isinstance(text, str): return text try: text = decode_output(text) except UnicodeDecodeError: - if six.PY2: - text = unicode.translate(vistir.misc.to_text(text), UNICODE_TO_ASCII_TRANSLATION_MAP) # noqa + pass return text diff --git a/pipenv/cli/__init__.py b/pipenv/cli/__init__.py index 0f57e306..9d53abf8 100644 --- a/pipenv/cli/__init__.py +++ b/pipenv/cli/__init__.py @@ -1,4 +1 @@ -# -*- coding=utf-8 -*- -from __future__ import absolute_import - from .command import cli # noqa diff --git a/pipenv/cli/command.py b/pipenv/cli/command.py index 9d3ce9bf..ae99b6ba 100644 --- a/pipenv/cli/command.py +++ b/pipenv/cli/command.py @@ -1,28 +1,24 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - import os import sys -from click import ( - argument, echo, edit, group, option, pass_context, secho, version_option -) - -from ..__version__ import __version__ -from ..patched import crayons -from ..vendor import click_completion, delegator -from .options import ( +from pipenv import environments +from pipenv.__version__ import __version__ +from pipenv._compat import fix_utf8 +from pipenv.cli.options import ( CONTEXT_SETTINGS, PipenvGroup, code_option, common_options, deploy_option, general_options, install_options, lock_options, pass_state, pypi_mirror_option, python_option, site_packages_option, skip_lock_option, - sync_options, system_option, three_option, uninstall_options, - verbose_option + sync_options, system_option, three_option, uninstall_options, verbose_option +) +from pipenv.exceptions import PipenvOptionsError +from pipenv.patched import crayons +from pipenv.utils import subprocess_run +from pipenv.vendor.click import ( + Choice, argument, echo, edit, group, option, pass_context, secho, types, + version_option ) -# Enable shell completion. -click_completion.init() - subcommand_context = CONTEXT_SETTINGS.copy() subcommand_context.update({ "ignore_unknown_options": True, @@ -39,12 +35,6 @@ subcommand_context_no_interspersion["allow_interspersed_args"] = False @option("--envs", is_flag=True, default=False, help="Output Environment Variable options.") @option("--rm", is_flag=True, default=False, help="Remove the virtualenv.") @option("--bare", is_flag=True, default=False, help="Minimal output.") -@option( - "--completion", - is_flag=True, - default=False, - help="Output completion (to be eval'd).", -) @option("--man", is_flag=True, default=False, help="Display manpage.") @option( "--support", @@ -64,39 +54,15 @@ def cli( envs=False, rm=False, bare=False, - completion=False, man=False, support=None, help=False, site_packages=None, **kwargs ): - # Handle this ASAP to make shell startup fast. - if completion: - from .. import shells - - try: - shell = shells.detect_info()[0] - except shells.ShellDetectionFailure: - echo( - "Fail to detect shell. Please provide the {0} environment " - "variable.".format(crayons.normal("PIPENV_SHELL", bold=True)), - err=True, - ) - ctx.abort() - print(click_completion.get_code(shell=shell, prog_name="pipenv")) - return 0 - from ..core import ( - system_which, - do_py, - warn_in_virtualenv, - do_where, - project, - cleanup_virtualenv, - ensure_project, - format_help, - do_clear, + cleanup_virtualenv, do_clear, do_py, do_where, ensure_project, + format_help, system_which, warn_in_virtualenv ) from ..utils import create_spinner @@ -110,58 +76,56 @@ def cli( return 1 if envs: echo("The following environment variables can be set, to do various things:\n") - from .. import environments - for key in environments.__dict__: + for key in state.project.__dict__: if key.startswith("PIPENV"): - echo(" - {0}".format(crayons.normal(key, bold=True))) + echo(f" - {crayons.normal(key, bold=True)}") echo( - "\nYou can learn more at:\n {0}".format( + "\nYou can learn more at:\n {}".format( crayons.green( - "http://docs.pipenv.org/advanced/#configuration-with-environment-variables" + "https://pipenv.pypa.io/en/latest/advanced/#configuration-with-environment-variables" ) ) ) return 0 - warn_in_virtualenv() + warn_in_virtualenv(state.project) if ctx.invoked_subcommand is None: - # --where was passed… + # --where was passed... if where: - do_where(bare=True) + do_where(state.project, bare=True) return 0 elif py: - do_py() + do_py(state.project, ctx=ctx) return 0 - # --support was passed… + # --support was passed... elif support: from ..help import get_pipenv_diagnostics - get_pipenv_diagnostics() + get_pipenv_diagnostics(state.project) return 0 - # --clear was passed… + # --clear was passed... elif state.clear: - do_clear() + do_clear(state.project) return 0 - # --venv was passed… + # --venv was passed... elif venv: # There is no virtualenv yet. - if not project.virtualenv_exists: + if not state.project.virtualenv_exists: echo( "{}({}){}".format( crayons.red("No virtualenv has been created for this project"), - crayons.white(project.project_directory, bold=True), + crayons.normal(state.project.project_directory, bold=True), crayons.red(" yet!") ), err=True, ) ctx.abort() else: - echo(project.virtualenv_location) + echo(state.project.virtualenv_location) return 0 - # --rm was passed… + # --rm was passed... elif rm: # Abort if --system (or running in a virtualenv). - from ..environments import PIPENV_USE_SYSTEM - if PIPENV_USE_SYSTEM: + if state.project.s.PIPENV_USE_SYSTEM or environments.is_in_virtualenv(): echo( crayons.red( "You are attempting to remove a virtualenv that " @@ -169,19 +133,19 @@ def cli( ) ) ctx.abort() - if project.virtualenv_exists: - loc = project.virtualenv_location + if state.project.virtualenv_exists: + loc = state.project.virtualenv_location echo( crayons.normal( - u"{0} ({1})…".format( + "{} ({})...".format( crayons.normal("Removing virtualenv", bold=True), crayons.green(loc), ) ) ) - with create_spinner(text="Running..."): + with create_spinner(text="Running...", setting=state.project.s): # Remove the virtualenv. - cleanup_virtualenv(bare=True) + cleanup_virtualenv(state.project, bare=True) return 0 else: echo( @@ -192,9 +156,10 @@ def cli( err=True, ) ctx.abort() - # --two / --three was passed… + # --two / --three was passed... if (state.python or state.three is not None) or state.site_packages: ensure_project( + state.project, three=state.three, python=state.python, warn=True, @@ -219,16 +184,15 @@ def cli( @skip_lock_option @install_options @pass_state -@pass_context def install( - ctx, state, **kwargs ): """Installs provided packages and adds them to Pipfile, or (if no packages are given), installs all packages from Pipfile.""" from ..core import do_install - retcode = do_install( + do_install( + state.project, dev=state.installstate.dev, three=state.three, python=state.python, @@ -237,7 +201,7 @@ def install( lock=not state.installstate.skip_lock, ignore_pipfile=state.installstate.ignore_pipfile, skip_lock=state.installstate.skip_lock, - requirements=state.installstate.requirementstxt, + requirementstxt=state.installstate.requirementstxt, sequential=state.installstate.sequential, pre=state.installstate.pre, code=state.installstate.code, @@ -250,19 +214,17 @@ def install( editable_packages=state.installstate.editables, site_packages=state.site_packages ) - if retcode: - ctx.abort() @cli.command( - short_help="Un-installs a provided package and removes it from Pipfile.", + short_help="Uninstalls a provided package and removes it from Pipfile.", context_settings=subcommand_context ) @option( "--all-dev", is_flag=True, default=False, - help="Un-install all package from [dev-packages].", + help="Uninstall all package from [dev-packages].", ) @option( "--all", @@ -280,9 +242,10 @@ def uninstall( all=False, **kwargs ): - """Un-installs a provided package and removes it from Pipfile.""" + """Uninstalls a provided package and removes it from Pipfile.""" from ..core import do_uninstall retcode = do_uninstall( + state.project, packages=state.installstate.packages, editable_packages=state.installstate.editables, three=state.three, @@ -299,6 +262,22 @@ def uninstall( sys.exit(retcode) +LOCK_HEADER = """\ +# +# These requirements were autogenerated by pipenv +# To regenerate from the project's Pipfile, run: +# +# pipenv lock {options} +# +""" + + +LOCK_DEV_NOTE = """\ +# Note: in pipenv 2020.x, "--dev" changed to emit both default and development +# requirements. To emit only development requirements, pass "--dev-only". +""" + + @cli.command(short_help="Generates Pipfile.lock.", context_settings=CONTEXT_SETTINGS) @lock_options @pass_state @@ -309,25 +288,52 @@ def lock( **kwargs ): """Generates Pipfile.lock.""" - from ..core import ensure_project, do_init, do_lock + from ..core import do_init, do_lock, ensure_project + # Ensure that virtualenv is available. # Note that we don't pass clear on to ensure_project as it is also # handled in do_lock ensure_project( - three=state.three, python=state.python, pypi_mirror=state.pypi_mirror, + state.project, three=state.three, python=state.python, pypi_mirror=state.pypi_mirror, warn=(not state.quiet), site_packages=state.site_packages, ) - if state.installstate.requirementstxt: + emit_requirements = state.lockoptions.emit_requirements + dev = state.installstate.dev + dev_only = state.lockoptions.dev_only + pre = state.installstate.pre + if emit_requirements: + # Emit requirements file header (unless turned off with --no-header) + if state.lockoptions.emit_requirements_header: + header_options = ["--requirements"] + if dev_only: + header_options.append("--dev-only") + elif dev: + header_options.append("--dev") + echo(LOCK_HEADER.format(options=" ".join(header_options))) + # TODO: Emit pip-compile style header + if dev and not dev_only: + echo(LOCK_DEV_NOTE) + # Setting "emit_requirements=True" means do_init() just emits the + # install requirements file to stdout, it doesn't install anything do_init( - dev=state.installstate.dev, - requirements=state.installstate.requirementstxt, + state.project, + dev=dev, + dev_only=dev_only, + emit_requirements=emit_requirements, pypi_mirror=state.pypi_mirror, - pre=state.installstate.pre, + pre=pre, + ) + elif state.lockoptions.dev_only: + raise PipenvOptionsError( + "--dev-only", + "--dev-only is only permitted in combination with --requirements. " + "Aborting." ) do_lock( + state.project, ctx=ctx, clear=state.clear, - pre=state.installstate.pre, + pre=pre, keep_outdated=state.installstate.keep_outdated, pypi_mirror=state.pypi_mirror, write=not state.quiet, @@ -349,7 +355,7 @@ def lock( "--anyway", is_flag=True, default=False, - help="Always spawn a subshell, even if one is already spawned.", + help="Always spawn a sub-shell, even if one is already spawned.", ) @argument("shell_args", nargs=-1) @pypi_mirror_option @@ -363,7 +369,7 @@ def shell( anyway=False, ): """Spawns a shell within the virtualenv.""" - from ..core import load_dot_env, do_shell + from ..core import do_shell, load_dot_env # Prevent user from activating nested environments. if "PIPENV_ACTIVE" in os.environ: @@ -371,7 +377,7 @@ def shell( venv_name = os.environ.get("VIRTUAL_ENV", "UNKNOWN_VIRTUAL_ENVIRONMENT") if not anyway: echo( - "{0} {1} {2}\nNo action taken to avoid nested environments.".format( + "{} {} {}\nNo action taken to avoid nested environments.".format( crayons.normal("Shell for"), crayons.green(venv_name, bold=True), crayons.normal("already activated.", bold=True), @@ -380,11 +386,12 @@ def shell( ) sys.exit(1) # Load .env file. - load_dot_env() + load_dot_env(state.project) # Use fancy mode for Windows. if os.name == "nt": fancy = True do_shell( + state.project, three=state.three, python=state.python, fancy=fancy, @@ -405,25 +412,51 @@ def run(state, command, args): """Spawns a command installed into the virtualenv.""" from ..core import do_run do_run( - command=command, args=args, three=state.three, python=state.python, pypi_mirror=state.pypi_mirror + state.project, command=command, args=args, three=state.three, python=state.python, pypi_mirror=state.pypi_mirror ) @cli.command( - short_help="Checks for security vulnerabilities and against PEP 508 markers provided in Pipfile.", + short_help="Checks for PyUp Safety security vulnerabilities and against" + " PEP 508 markers provided in Pipfile.", context_settings=subcommand_context ) @option( "--unused", nargs=1, - default=False, + default="", + type=types.STRING, help="Given a code path, show potentially unused dependencies.", ) +@option( + "--db", + nargs=1, + default=lambda: os.environ.get('PIPENV_SAFETY_DB'), + help="Path to a local PyUp Safety vulnerabilities database." + " Default: ENV PIPENV_SAFETY_DB or None.", +) @option( "--ignore", "-i", multiple=True, - help="Ignore specified vulnerability during safety checks.", + help="Ignore specified vulnerability during PyUp Safety checks.", +) +@option( + "--output", + type=Choice(["default", "json", "full-report", "bare"]), + default="default", + help="Translates to --json, --full-report or --bare from PyUp Safety check", +) +@option( + "--key", + help="Safety API key from PyUp.io for scanning dependencies against a live" + " vulnerabilities database. Leave blank for scanning against a" + " database that only updates once a month.", +) +@option( + "--quiet", + is_flag=True, + help="Quiet standard output, except vulnerability report." ) @common_options @system_option @@ -432,20 +465,29 @@ def run(state, command, args): def check( state, unused=False, + db=None, style=False, ignore=None, + output="default", + key=None, + quiet=False, args=None, **kwargs ): - """Checks for security vulnerabilities and against PEP 508 markers provided in Pipfile.""" + """Checks for PyUp Safety security vulnerabilities and against PEP 508 markers provided in Pipfile.""" from ..core import do_check do_check( + state.project, three=state.three, python=state.python, system=state.system, unused=unused, + db=db, ignore=ignore, + output=output, + key=key, + quiet=quiet, args=args, pypi_mirror=state.pypi_mirror, ) @@ -454,9 +496,9 @@ def check( @cli.command(short_help="Runs lock, then sync.", context_settings=CONTEXT_SETTINGS) @option("--bare", is_flag=True, default=False, help="Minimal output.") @option( - "--outdated", is_flag=True, default=False, help=u"List out-of-date dependencies." + "--outdated", is_flag=True, default=False, help="List out-of-date dependencies." ) -@option("--dry-run", is_flag=True, default=None, help=u"List out-of-date dependencies.") +@option("--dry-run", is_flag=True, default=None, help="List out-of-date dependencies.") @install_options @pass_state @pass_context @@ -469,38 +511,32 @@ def update( **kwargs ): """Runs lock, then sync.""" - from ..core import ( - ensure_project, - do_outdated, - do_lock, - do_sync, - project, - ) + from ..core import do_lock, do_outdated, do_sync, ensure_project ensure_project( - three=state.three, python=state.python, pypi_mirror=state.pypi_mirror, + state.project, three=state.three, python=state.python, pypi_mirror=state.pypi_mirror, warn=(not state.quiet), site_packages=state.site_packages, clear=state.clear ) if not outdated: outdated = bool(dry_run) if outdated: - do_outdated(clear=state.clear, pre=state.installstate.pre, pypi_mirror=state.pypi_mirror) + do_outdated(state.project, clear=state.clear, pre=state.installstate.pre, pypi_mirror=state.pypi_mirror) packages = [p for p in state.installstate.packages if p] editable = [p for p in state.installstate.editables if p] if not packages: echo( - "{0} {1} {2} {3}{4}".format( - crayons.white("Running", bold=True), - crayons.red("$ pipenv lock", bold=True), - crayons.white("then", bold=True), - crayons.red("$ pipenv sync", bold=True), - crayons.white(".", bold=True), + "{} {} {} {}{}".format( + crayons.normal("Running", bold=True), + crayons.yellow("$ pipenv lock", bold=True), + crayons.normal("then", bold=True), + crayons.yellow("$ pipenv sync", bold=True), + crayons.normal(".", bold=True), ) ) else: for package in packages + editable: - if package not in project.all_packages: + if package not in state.project.all_packages: echo( - "{0}: {1} was not found in your Pipfile! Aborting." + "{}: {} was not found in your Pipfile! Aborting." "".format( crayons.red("Warning", bold=True), crayons.green(package, bold=True), @@ -509,6 +545,7 @@ def update( ) ctx.abort() do_lock( + state.project, ctx=ctx, clear=state.clear, pre=state.installstate.pre, @@ -517,7 +554,7 @@ def update( write=not state.quiet, ) do_sync( - ctx=ctx, + state.project, dev=state.installstate.dev, three=state.three, python=state.python, @@ -532,18 +569,19 @@ def update( @cli.command( - short_help=u"Displays currently-installed dependency graph information.", + short_help="Displays currently-installed dependency graph information.", context_settings=CONTEXT_SETTINGS ) @option("--bare", is_flag=True, default=False, help="Minimal output.") @option("--json", is_flag=True, default=False, help="Output JSON.") @option("--json-tree", is_flag=True, default=False, help="Output JSON in nested tree.") @option("--reverse", is_flag=True, default=False, help="Reversed dependency graph.") -def graph(bare=False, json=False, json_tree=False, reverse=False): +@pass_state +def graph(state, bare=False, json=False, json_tree=False, reverse=False): """Displays currently-installed dependency graph information.""" from ..core import do_graph - do_graph(bare=bare, json=json, json_tree=json_tree, reverse=reverse) + do_graph(state.project, bare=bare, json=json, json_tree=json_tree, reverse=reverse) @cli.command( @@ -561,27 +599,25 @@ def run_open(state, module, *args, **kwargs): EDITOR=atom pipenv open requests """ - from ..core import which, ensure_project, inline_activate_virtual_environment + from ..core import ensure_project, inline_activate_virtual_environment # Ensure that virtualenv is available. ensure_project( - three=state.three, python=state.python, + state.project, three=state.three, python=state.python, validate=False, pypi_mirror=state.pypi_mirror, ) - c = delegator.run( - '{0} -c "import {1}; print({1}.__file__);"'.format(which("python"), module) + c = subprocess_run( + [state.project._which("python"), "-c", "import {0}; print({0}.__file__)".format(module)] ) - try: - assert c.return_code == 0 - except AssertionError: + if c.returncode: echo(crayons.red("Module not found!")) sys.exit(1) - if "__init__.py" in c.out: - p = os.path.dirname(c.out.strip().rstrip("cdo")) + if "__init__.py" in c.stdout: + p = os.path.dirname(c.stdout.strip().rstrip("cdo")) else: - p = c.out.strip().rstrip("cdo") - echo(crayons.normal("Opening {0!r} in your EDITOR.".format(p), bold=True)) - inline_activate_virtual_environment() + p = c.stdout.strip().rstrip("cdo") + echo(crayons.normal(f"Opening {p!r} in your EDITOR.", bold=True)) + inline_activate_virtual_environment(state.project) edit(filename=p) return 0 @@ -590,6 +626,7 @@ def run_open(state, module, *args, **kwargs): short_help="Installs all packages specified in Pipfile.lock.", context_settings=CONTEXT_SETTINGS ) +@system_option @option("--bare", is_flag=True, default=False, help="Minimal output.") @sync_options @pass_state @@ -606,7 +643,7 @@ def sync( from ..core import do_sync retcode = do_sync( - ctx=ctx, + state.project, dev=state.installstate.dev, three=state.three, python=state.python, @@ -617,6 +654,7 @@ def sync( unused=unused, sequential=state.installstate.sequential, pypi_mirror=state.pypi_mirror, + system=state.system ) if retcode: ctx.abort() @@ -632,13 +670,57 @@ def sync( @three_option @python_option @pass_state -@pass_context -def clean(ctx, state, dry_run=False, bare=False, user=False): +def clean(state, dry_run=False, bare=False, user=False): """Uninstalls all packages not specified in Pipfile.lock.""" from ..core import do_clean - do_clean(ctx=ctx, three=state.three, python=state.python, dry_run=dry_run, + do_clean(state.project, three=state.three, python=state.python, dry_run=dry_run, system=state.system) +@cli.command( + short_help="Lists scripts in current environment config.", + context_settings=subcommand_context_no_interspersion, +) +@common_options +@pass_state +def scripts(state): + """Lists scripts in current environment config.""" + if not state.project.pipfile_exists: + echo("No Pipfile present at project home.", err=True) + sys.exit(1) + scripts = state.project.parsed_pipfile.get('scripts', {}) + first_column_width = max(len(word) for word in ["Command"] + list(scripts)) + second_column_width = max(len(word) for word in ["Script"] + list(scripts.values())) + lines = ["{0:<{width}} Script".format("Command", width=first_column_width)] + lines.append("{} {}".format("-" * first_column_width, "-" * second_column_width)) + lines.extend( + "{0:<{width}} {1}".format(name, script, width=first_column_width) + for name, script in scripts.items() + ) + echo("\n".join(fix_utf8(line) for line in lines)) + + +@cli.command( + short_help="Verify the hash in Pipfile.lock is up-to-date.", + context_settings=CONTEXT_SETTINGS, +) +@pass_state +def verify(state): + """Verify the hash in Pipfile.lock is up-to-date.""" + if not state.project.pipfile_exists: + echo("No Pipfile present at project home.", err=True) + sys.exit(1) + if state.project.get_lockfile_hash() != state.project.calculate_pipfile_hash(): + echo( + 'Pipfile.lock is out-of-date. Run {} to update.'.format( + crayons.yellow("$ pipenv lock", bold=True) + ), + err=True + ) + sys.exit(1) + echo(crayons.green('Pipfile.lock is up-to-date.')) + sys.exit(0) + + if __name__ == "__main__": cli() diff --git a/pipenv/cli/options.py b/pipenv/cli/options.py index fc45256f..a24360ba 100644 --- a/pipenv/cli/options.py +++ b/pipenv/cli/options.py @@ -1,17 +1,13 @@ -# -*- coding=utf-8 -*- -from __future__ import absolute_import - import os -import click.types - -from click import ( - BadParameter, BadArgumentUsage, Group, Option, argument, echo, make_pass_decorator, option +from pipenv.project import Project +from pipenv.utils import is_valid_url +from pipenv.vendor.click import ( + BadArgumentUsage, BadParameter, Group, Option, argument, echo, + make_pass_decorator, option ) -from click_didyoumean import DYMMixin - -from .. import environments -from ..utils import is_valid_url +from pipenv.vendor.click import types as click_types +from pipenv.vendor.click_didyoumean import DYMMixin CONTEXT_SETTINGS = { @@ -50,8 +46,14 @@ class PipenvGroup(DYMMixin, Group): help="Show this message and exit.", ) + def main(self, *args, **kwargs): + """ + to specify the windows_expand_args option to avoid exceptions on Windows + see: https://github.com/pallets/click/issues/1901 + """ + return super().main(*args, **kwargs, windows_expand_args=False) -class State(object): +class State: def __init__(self): self.index = None self.extra_index_urls = [] @@ -64,10 +66,12 @@ class State(object): self.site_packages = None self.clear = False self.system = False + self.project = Project() self.installstate = InstallState() + self.lockoptions = LockOptions() -class InstallState(object): +class InstallState: def __init__(self): self.dev = False self.pre = False @@ -83,6 +87,13 @@ class InstallState(object): self.editables = [] +class LockOptions: + def __init__(self): + self.dev_only = False + self.emit_requirements = False + self.emit_requirements_header = False + + pass_state = make_pass_decorator(State, ensure=True) @@ -102,7 +113,7 @@ def extra_index_option(f): state.extra_index_urls.extend(list(value)) return value return option("--extra-index-url", multiple=True, expose_value=False, - help=u"URLs to the extra PyPI compatible indexes to query for package lookups.", + help="URLs to the extra PyPI compatible indexes to query for package look-ups.", callback=callback, envvar="PIP_EXTRA_INDEX_URL")(f) @@ -112,8 +123,10 @@ def editable_option(f): state.installstate.editables.extend(value) return value return option('-e', '--editable', expose_value=False, multiple=True, - help='An editable python package URL or path, often to a VCS repo.', - callback=callback, type=click.types.STRING)(f) + callback=callback, type=click_types.STRING, help=( + "An editable Python package URL or path, often to a VCS " + "repository." + ))(f) def sequential_option(f): @@ -123,7 +136,7 @@ def sequential_option(f): return value return option("--sequential", is_flag=True, default=False, expose_value=False, help="Install dependencies one-at-a-time, instead of concurrently.", - callback=callback, type=click.types.BOOL, show_envvar=True)(f) + callback=callback, type=click_types.BOOL, show_envvar=True)(f) def skip_lock_option(f): @@ -132,8 +145,8 @@ def skip_lock_option(f): state.installstate.skip_lock = value return value return option("--skip-lock", is_flag=True, default=False, expose_value=False, - help=u"Skip locking mechanisms and use the Pipfile instead during operation.", - envvar="PIPENV_SKIP_LOCK", callback=callback, type=click.types.BOOL, + help="Skip locking mechanisms and use the Pipfile instead during operation.", + envvar="PIPENV_SKIP_LOCK", callback=callback, type=click_types.BOOL, show_envvar=True)(f) @@ -143,8 +156,8 @@ def keep_outdated_option(f): state.installstate.keep_outdated = value return value return option("--keep-outdated", is_flag=True, default=False, expose_value=False, - help=u"Keep out-dated dependencies from being updated in Pipfile.lock.", - callback=callback, type=click.types.BOOL, show_envvar=True)(f) + help="Keep out-dated dependencies from being updated in Pipfile.lock.", + callback=callback, type=click_types.BOOL, show_envvar=True)(f) def selective_upgrade_option(f): @@ -152,7 +165,7 @@ def selective_upgrade_option(f): state = ctx.ensure_object(State) state.installstate.selective_upgrade = value return value - return option("--selective-upgrade", is_flag=True, default=False, type=click.types.BOOL, + return option("--selective-upgrade", is_flag=True, default=False, type=click_types.BOOL, help="Update specified packages.", callback=callback, expose_value=False)(f) @@ -164,26 +177,38 @@ def ignore_pipfile_option(f): return value return option("--ignore-pipfile", is_flag=True, default=False, expose_value=False, help="Ignore Pipfile when installing, using the Pipfile.lock.", - callback=callback, type=click.types.BOOL, show_envvar=True)(f) + callback=callback, type=click_types.BOOL, show_envvar=True)(f) -def dev_option(f): +def _dev_option(f, help_text): def callback(ctx, param, value): state = ctx.ensure_object(State) state.installstate.dev = value return value - return option("--dev", "-d", is_flag=True, default=False, type=click.types.BOOL, - help="Install both develop and default packages.", callback=callback, + return option("--dev", "-d", is_flag=True, default=False, type=click_types.BOOL, + help=help_text, callback=callback, expose_value=False, show_envvar=True)(f) +def install_dev_option(f): + return _dev_option(f, "Install both develop and default packages") + + +def lock_dev_option(f): + return _dev_option(f, "Generate both develop and default requirements") + + +def uninstall_dev_option(f): + return _dev_option(f, "Deprecated (as it has no effect). May be removed in a future release.") + + def pre_option(f): def callback(ctx, param, value): state = ctx.ensure_object(State) state.installstate.pre = value return value - return option("--pre", is_flag=True, default=False, help=u"Allow pre-releases.", - callback=callback, type=click.types.BOOL, expose_value=False)(f) + return option("--pre", is_flag=True, default=False, help="Allow pre-releases.", + callback=callback, type=click_types.BOOL, expose_value=False)(f) def package_arg(f): @@ -192,7 +217,7 @@ def package_arg(f): state.installstate.packages.extend(value) return value return argument('packages', nargs=-1, callback=callback, expose_value=False, - type=click.types.STRING)(f) + type=click_types.STRING)(f) def three_option(f): @@ -213,19 +238,21 @@ def python_option(f): if value is not None: state.python = validate_python_path(ctx, param, value) return value - return option("--python", default=False, nargs=1, callback=callback, + return option("--python", default="", nargs=1, callback=callback, help="Specify which version of Python virtualenv should use.", - expose_value=False, allow_from_autoenv=False)(f) + expose_value=False, allow_from_autoenv=False, + type=click_types.STRING)(f) def pypi_mirror_option(f): def callback(ctx, param, value): state = ctx.ensure_object(State) + value = value or state.project.s.PIPENV_PYPI_MIRROR if value is not None: state.pypi_mirror = validate_pypi_mirror(ctx, param, value) return value - return option("--pypi-mirror", default=environments.PIPENV_PYPI_MIRROR, nargs=1, - callback=callback, help="Specify a PyPI mirror.", expose_value=False)(f) + return option("--pypi-mirror", nargs=1, callback=callback, + help="Specify a PyPI mirror.", expose_value=False)(f) def verbose_option(f): @@ -240,7 +267,7 @@ def verbose_option(f): state.verbose = True setup_verbosity(ctx, param, 1) return option("--verbose", "-v", is_flag=True, expose_value=False, - callback=callback, help="Verbose mode.", type=click.types.BOOL)(f) + callback=callback, help="Verbose mode.", type=click_types.BOOL)(f) def quiet_option(f): @@ -255,7 +282,7 @@ def quiet_option(f): state.quiet = True setup_verbosity(ctx, param, -1) return option("--quiet", "-q", is_flag=True, expose_value=False, - callback=callback, help="Quiet mode.", type=click.types.BOOL)(f) + callback=callback, help="Quiet mode.", type=click_types.BOOL)(f) def site_packages_option(f): @@ -274,8 +301,8 @@ def clear_option(f): state = ctx.ensure_object(State) state.clear = value return value - return option("--clear", is_flag=True, callback=callback, type=click.types.BOOL, - help="Clears caches (pipenv, pip, and pip-tools).", + return option("--clear", is_flag=True, callback=callback, type=click_types.BOOL, + help="Clears caches (pipenv, pip).", expose_value=False, show_envvar=True)(f) @@ -286,7 +313,7 @@ def system_option(f): state.system = value return value return option("--system", is_flag=True, default=False, help="System pip management.", - callback=callback, type=click.types.BOOL, expose_value=False, + callback=callback, type=click_types.BOOL, expose_value=False, show_envvar=True)(f) @@ -296,29 +323,50 @@ def requirementstxt_option(f): if value: state.installstate.requirementstxt = value return value - return option("--requirements", "-r", nargs=1, default=False, expose_value=False, - help="Import a requirements.txt file.", callback=callback)(f) + return option("--requirements", "-r", nargs=1, default="", expose_value=False, + help="Import a requirements.txt file.", callback=callback, + type=click_types.STRING)(f) -def requirements_flag(f): +def emit_requirements_flag(f): def callback(ctx, param, value): state = ctx.ensure_object(State) if value: - state.installstate.requirementstxt = value + state.lockoptions.emit_requirements = value return value return option("--requirements", "-r", default=False, is_flag=True, expose_value=False, help="Generate output in requirements.txt format.", callback=callback)(f) +def emit_requirements_header_flag(f): + def callback(ctx, param, value): + state = ctx.ensure_object(State) + if value: + state.lockoptions.emit_requirements_header = value + return value + return option("--header/--no-header", default=True, is_flag=True, expose_value=False, + help="Add header to generated requirements", callback=callback)(f) + + +def dev_only_flag(f): + def callback(ctx, param, value): + state = ctx.ensure_object(State) + if value: + state.lockoptions.dev_only = value + return value + return option("--dev-only", default=False, is_flag=True, expose_value=False, + help="Emit development dependencies *only* (overrides --dev)", callback=callback)(f) + + def code_option(f): def callback(ctx, param, value): state = ctx.ensure_object(State) if value: state.installstate.code = value return value - return option("--code", "-c", nargs=1, default=False, help="Install packages " + return option("--code", "-c", nargs=1, default="", help="Install packages " "automatically discovered from import statements.", callback=callback, - expose_value=False)(f) + expose_value=False, type=click_types.STRING)(f) def deploy_option(f): @@ -326,8 +374,8 @@ def deploy_option(f): state = ctx.ensure_object(State) state.installstate.deploy = value return value - return option("--deploy", is_flag=True, default=False, type=click.types.BOOL, - help=u"Abort if the Pipfile.lock is out-of-date, or Python version is" + return option("--deploy", is_flag=True, default=False, type=click_types.BOOL, + help="Abort if the Pipfile.lock is out-of-date, or Python version is" " wrong.", callback=callback, expose_value=False)(f) @@ -335,14 +383,14 @@ def setup_verbosity(ctx, param, value): if not value: return import logging - loggers = ("pip", "piptools") + loggers = ("pip",) if value == 1: for logger in loggers: logging.getLogger(logger).setLevel(logging.INFO) elif value == -1: for logger in loggers: logging.getLogger(logger).setLevel(logging.CRITICAL) - environments.PIPENV_VERBOSITY = value + ctx.ensure_object(State).project.s.PIPENV_VERBOSITY = value def validate_python_path(ctx, param, value): @@ -359,7 +407,7 @@ def validate_python_path(ctx, param, value): def validate_bool_or_none(ctx, param, value): if value is not None: - return click.types.BOOL(value) + return click_types.BOOL(value) return False @@ -380,7 +428,6 @@ def common_options(f): def install_base_options(f): f = common_options(f) - f = dev_option(f) f = pre_option(f) f = keep_outdated_option(f) return f @@ -388,6 +435,7 @@ def install_base_options(f): def uninstall_options(f): f = install_base_options(f) + f = uninstall_dev_option(f) f = skip_lock_option(f) f = editable_option(f) f = package_arg(f) @@ -396,12 +444,16 @@ def uninstall_options(f): def lock_options(f): f = install_base_options(f) - f = requirements_flag(f) + f = lock_dev_option(f) + f = emit_requirements_flag(f) + f = emit_requirements_header_flag(f) + f = dev_only_flag(f) return f def sync_options(f): f = install_base_options(f) + f = install_dev_option(f) f = sequential_option(f) return f diff --git a/pipenv/cmdparse.py b/pipenv/cmdparse.py index 2e550e22..760460c2 100644 --- a/pipenv/cmdparse.py +++ b/pipenv/cmdparse.py @@ -2,8 +2,6 @@ import itertools import re import shlex -import six - class ScriptEmptyError(ValueError): pass @@ -28,7 +26,7 @@ class Script(object): @classmethod def parse(cls, value): - if isinstance(value, six.string_types): + if isinstance(value, str): value = shlex.split(value) if not value: raise ScriptEmptyError(value) @@ -45,6 +43,10 @@ class Script(object): def args(self): return self._parts[1:] + @property + def cmd_args(self): + return self._parts + def extend(self, extra_args): self._parts.extend(extra_args) diff --git a/pipenv/core.py b/pipenv/core.py index 74b72359..913c241a 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -1,50 +1,35 @@ -# -*- coding=utf-8 -*- -from __future__ import absolute_import, print_function - -import io import json as simplejson import logging import os +from pathlib import Path +from posixpath import expandvars import sys import time import warnings import click -import six - -import delegator import dotenv import pipfile import vistir -from click_completion import init as init_completion - -from . import environments, exceptions, pep508checker, progress -from ._compat import decode_for_output, fix_utf8 -from .cmdparse import Script -from .environments import ( - PIP_EXISTS_ACTION, PIPENV_CACHE_DIR, PIPENV_COLORBLIND, - PIPENV_DEFAULT_PYTHON_VERSION, PIPENV_DONT_USE_PYENV, PIPENV_DONT_USE_ASDF, - PIPENV_HIDE_EMOJIS, PIPENV_MAX_SUBPROCESS, PIPENV_PYUP_API_KEY, - PIPENV_RESOLVE_VCS, PIPENV_SHELL_FANCY, PIPENV_SKIP_VALIDATION, PIPENV_YES, - SESSION_IS_INTERACTIVE, is_type_checking -) -from .patched import crayons -from .project import Project -from .utils import ( - convert_deps_to_pip, create_spinner, download_file, - escape_grouped_arguments, find_python, find_windows_executable, - get_canonical_names, get_source_list, interrupt_handled_subprocess, - is_pinned, is_python_command, is_required_version, is_star, is_valid_url, +from pipenv import environments, exceptions, pep508checker, progress +from pipenv._compat import decode_for_output, fix_utf8 +from pipenv.patched import crayons +from pipenv.utils import ( + cmd_list_to_shell, convert_deps_to_pip, create_spinner, download_file, + find_python, get_canonical_names, get_host_and_port, get_source_list, is_pinned, + is_python_command, is_required_version, is_star, is_valid_url, parse_indexes, pep423_name, prepare_pip_source_args, proper_case, - python_version, run_command, venv_resolve_deps + python_version, run_command, subprocess_run, venv_resolve_deps ) -if is_type_checking(): - from typing import Dict, List, Optional, Union, Text +if environments.is_type_checking(): + from typing import Dict, List, Optional, Union + + from pipenv.project import Project from pipenv.vendor.requirementslib.models.requirements import Requirement - TSourceDict = Dict[Text, Union[Text, bool]] + TSourceDict = Dict[str, Union[str, bool]] # Packages that should be ignored later. @@ -58,9 +43,8 @@ BAD_PACKAGES = ( ) FIRST_PACKAGES = ("cython",) -# Are we using the default Python? -USING_DEFAULT_PYTHON = True -if not PIPENV_HIDE_EMOJIS: + +if not environments.PIPENV_HIDE_EMOJIS: now = time.localtime() # Halloween easter-egg. if ((now.tm_mon == 10) and (now.tm_mday == 30)) or ( @@ -80,53 +64,27 @@ else: INSTALL_LABEL = " " INSTALL_LABEL2 = " " STARTING_LABEL = " " -# Enable shell completion. -init_completion() + # Disable colors, for the color blind and others who do not prefer colors. -if PIPENV_COLORBLIND: +if environments.PIPENV_COLORBLIND: crayons.disable() -def which(command, location=None, allow_global=False): - if not allow_global and location is None: - if project.virtualenv_exists: - location = project.virtualenv_location - else: - location = os.environ.get("VIRTUAL_ENV", None) - if not (location and os.path.exists(location)) and not allow_global: - raise RuntimeError("location not created nor specified") - - version_str = "python{0}".format(".".join([str(v) for v in sys.version_info[:2]])) - is_python = command in ("python", os.path.basename(sys.executable), version_str) - if not allow_global: - if os.name == "nt": - p = find_windows_executable(os.path.join(location, "Scripts"), command) - else: - p = os.path.join(location, "bin", command) - else: - if is_python: - p = sys.executable - if not os.path.exists(p): - if is_python: - p = sys.executable or system_which("python") - else: - p = system_which(command) - return p - - -project = Project(which=which) - - -def do_clear(): - click.echo(crayons.white(fix_utf8("Clearing caches…"), bold=True)) +def do_clear(project): + click.echo(crayons.normal(fix_utf8("Clearing caches..."), bold=True)) try: from pip._internal import locations except ImportError: # pip 9. from pip import locations try: - vistir.path.rmtree(PIPENV_CACHE_DIR) - vistir.path.rmtree(locations.USER_CACHE_DIR) + vistir.path.rmtree(project.s.PIPENV_CACHE_DIR, onerror=vistir.path.handle_remove_readonly) + # Other processes may be writing into this directory simultaneously. + vistir.path.rmtree( + locations.USER_CACHE_DIR, + ignore_errors=environments.PIPENV_IS_CI, + onerror=vistir.path.handle_remove_readonly + ) except OSError as e: # Ignore FileNotFoundError. This is needed for Python 2.7. import errno @@ -136,41 +94,39 @@ def do_clear(): raise -def load_dot_env(): +def load_dot_env(project, as_dict=False): """Loads .env file into sys.environ.""" - if not environments.PIPENV_DONT_LOAD_ENV: + if not project.s.PIPENV_DONT_LOAD_ENV: # If the project doesn't exist yet, check current directory for a .env file project_directory = project.project_directory or "." - dotenv_file = environments.PIPENV_DOTENV_LOCATION or os.sep.join( + dotenv_file = project.s.PIPENV_DOTENV_LOCATION or os.sep.join( [project_directory, ".env"] ) if os.path.isfile(dotenv_file): click.echo( - crayons.normal(fix_utf8("Loading .env environment variables…"), bold=True), + crayons.normal(fix_utf8("Loading .env environment variables..."), bold=True), err=True, ) else: - if environments.PIPENV_DOTENV_LOCATION: + if project.s.PIPENV_DOTENV_LOCATION: click.echo( - "{0}: file {1}={2} does not exist!!\n{3}".format( + "{}: file {}={} does not exist!!\n{}".format( crayons.red("Warning", bold=True), crayons.normal("PIPENV_DOTENV_LOCATION", bold=True), - crayons.normal(environments.PIPENV_DOTENV_LOCATION, bold=True), + crayons.normal(project.s.PIPENV_DOTENV_LOCATION, bold=True), crayons.red("Not loading environment variables.", bold=True), ), err=True, ) - dotenv.load_dotenv(dotenv_file, override=True) + if as_dict: + return dotenv.dotenv_values(dotenv_file) + else: + dotenv.load_dotenv(dotenv_file, override=True) + project.s.initialize() -def add_to_path(p): - """Adds a given path to the PATH.""" - if p not in os.environ["PATH"]: - os.environ["PATH"] = "{0}{1}{2}".format(p, os.pathsep, os.environ["PATH"]) - - -def cleanup_virtualenv(bare=True): +def cleanup_virtualenv(project, bare=True): """Removes the virtualenv directory from the system.""" if not bare: click.echo(crayons.red("Environment creation aborted.")) @@ -179,18 +135,19 @@ def cleanup_virtualenv(bare=True): vistir.path.rmtree(project.virtualenv_location) except OSError as e: click.echo( - "{0} An error occurred while removing {1}!".format( + "{} An error occurred while removing {}!".format( crayons.red("Error: ", bold=True), crayons.green(project.virtualenv_location), ), err=True, ) - click.echo(crayons.blue(e), err=True) + click.echo(crayons.cyan(e), err=True) -def import_requirements(r=None, dev=False): - from .patched.notpip._vendor import requests as pip_requests - from .vendor.pip_shims.shims import parse_requirements +def import_requirements(project, r=None, dev=False): + from pipenv.patched.notpip._vendor import requests as pip_requests + from pipenv.patched.notpip._internal.req.constructors import install_req_from_parsed_requirement + from pipenv.vendor.pip_shims.shims import parse_requirements # Parse requirements.txt file with Pip's parser. # Pip requires a `PipSession` which is a subclass of requests.Session. @@ -200,23 +157,27 @@ def import_requirements(r=None, dev=False): # Default path, if none is provided. if r is None: r = project.requirements_location - with open(r, "r") as f: + with open(r) as f: contents = f.read() indexes = [] trusted_hosts = [] # Find and add extra indexes. for line in contents.split("\n"): - line_indexes, _trusted_hosts, _ = parse_indexes(line.strip()) - indexes.extend(line_indexes) - trusted_hosts.extend(_trusted_hosts) + index, extra_index, trusted_host, _ = parse_indexes(line.strip(), strict=True) + if index: + indexes = [index] + if extra_index: + indexes.append(extra_index) + if trusted_host: + trusted_hosts.append(get_host_and_port(trusted_host)) indexes = sorted(set(indexes)) trusted_hosts = sorted(set(trusted_hosts)) - reqs = [f for f in parse_requirements(r, session=pip_requests)] + reqs = [install_req_from_parsed_requirement(f) for f in parse_requirements(r, session=pip_requests)] for package in reqs: if package.name not in BAD_PACKAGES: if package.link is not None: package_string = ( - "-e {0}".format(package.link) + f"-e {package.link}" if package.editable else str(package.link) ) @@ -224,18 +185,23 @@ def import_requirements(r=None, dev=False): else: project.add_package_to_pipfile(str(package.req), dev=dev) for index in indexes: - trusted = index in trusted_hosts - project.add_index_to_pipfile(index, verify_ssl=trusted) + # don't require HTTPS for trusted hosts (see: https://pip.pypa.io/en/stable/cli/pip/#cmdoption-trusted-host) + host_and_port = get_host_and_port(index) + require_valid_https = not any((v in trusted_hosts for v in ( + host_and_port, + host_and_port.partition(':')[0], # also check if hostname without port is in trusted_hosts + ))) + project.add_index_to_pipfile(index, verify_ssl=require_valid_https) project.recase_pipfile() def ensure_environment(): - # Skip this on Windows… + # Skip this on Windows... if os.name != "nt": if "LANG" not in os.environ: click.echo( - "{0}: the environment variable {1} is not set!" - "\nWe recommend setting this in {2} (or equivalent) for " + "{}: the environment variable {} is not set!" + "\nWe recommend setting this in {} (or equivalent) for " "proper expected behavior.".format( crayons.red("Warning", bold=True), crayons.normal("LANG", bold=True), @@ -262,41 +228,40 @@ def import_from_code(path="."): return [] -def ensure_pipfile(validate=True, skip_requirements=False, system=False): +def ensure_pipfile(project, validate=True, skip_requirements=False, system=False): """Creates a Pipfile for the project, if it doesn't exist.""" - from .environments import PIPENV_VIRTUALENV # Assert Pipfile exists. - python = which("python") if not (USING_DEFAULT_PYTHON or system) else None + python = project._which("python") if not (project.s.USING_DEFAULT_PYTHON or system) else None if project.pipfile_is_empty: # Show an error message and exit if system is passed and no pipfile exists - if system and not PIPENV_VIRTUALENV: + if system and not project.s.PIPENV_VIRTUALENV: raise exceptions.PipenvOptionsError( "--system", "--system is intended to be used for pre-existing Pipfile " "installation, not installation of specific packages. Aborting." ) - # If there's a requirements file, but no Pipfile… + # If there's a requirements file, but no Pipfile... if project.requirements_exists and not skip_requirements: click.echo( crayons.normal( - fix_utf8("requirements.txt found, instead of Pipfile! Converting…"), + fix_utf8("requirements.txt found, instead of Pipfile! Converting..."), bold=True, ) ) - # Create a Pipfile… + # Create a Pipfile... project.create_pipfile(python=python) - with create_spinner("Importing requirements...") as sp: + with create_spinner("Importing requirements...", project.s) as sp: # Import requirements.txt. try: - import_requirements() + import_requirements(project) except Exception: sp.fail(environments.PIPENV_SPINNER_FAIL_TEXT.format("Failed...")) else: sp.ok(environments.PIPENV_SPINNER_OK_TEXT.format("Success!")) # Warn the user of side-effects. click.echo( - u"{0}: Your {1} now contains pinned versions, if your {2} did. \n" + "{0}: Your {1} now contains pinned versions, if your {2} did. \n" "We recommend updating your {1} to specify the {3} version, instead." "".format( crayons.red("Warning", bold=True), @@ -307,20 +272,20 @@ def ensure_pipfile(validate=True, skip_requirements=False, system=False): ) else: click.echo( - crayons.normal(fix_utf8("Creating a Pipfile for this project…"), bold=True), + crayons.normal(fix_utf8("Creating a Pipfile for this project..."), bold=True), err=True, ) # Create the pipfile if it doesn't exist. project.create_pipfile(python=python) # Validate the Pipfile's contents. - if validate and project.virtualenv_exists and not PIPENV_SKIP_VALIDATION: + if validate and project.virtualenv_exists and not project.s.PIPENV_SKIP_VALIDATION: # Ensure that Pipfile is using proper casing. p = project.parsed_pipfile changed = project.ensure_proper_casing() # Write changes out to disk. if changed: click.echo( - crayons.normal(u"Fixing package names in Pipfile…", bold=True), err=True + crayons.normal("Fixing package names in Pipfile...", bold=True), err=True ) project.write_toml(p) @@ -349,18 +314,17 @@ def find_a_system_python(line): return python_entry -def ensure_python(three=None, python=None): - # Support for the PIPENV_PYTHON environment variable. - from .environments import PIPENV_PYTHON +def ensure_python(project, three=None, python=None): + # Runtime import is necessary due to the possibility that the environments module may have been reloaded. + if project.s.PIPENV_PYTHON and python is False and three is None: + python = project.s.PIPENV_PYTHON - if PIPENV_PYTHON and python is False and three is None: - python = PIPENV_PYTHON - - def abort(): + def abort(msg=''): click.echo( - "You can specify specific versions of Python with:\n {0}".format( - crayons.red( - "$ pipenv --python {0}".format( + "{}\nYou can specify specific versions of Python with:\n{}".format( + crayons.red(msg), + crayons.yellow( + "$ pipenv --python {}".format( os.sep.join(("path", "to", "python")) ) ) @@ -369,87 +333,88 @@ def ensure_python(three=None, python=None): ) sys.exit(1) - global USING_DEFAULT_PYTHON - USING_DEFAULT_PYTHON = three is None and not python + project.s.USING_DEFAULT_PYTHON = three is None and not python # Find out which python is desired. if not python: python = convert_three_to_python(three, python) if not python: python = project.required_python_version if not python: - python = PIPENV_DEFAULT_PYTHON_VERSION + python = project.s.PIPENV_DEFAULT_PYTHON_VERSION path_to_python = find_a_system_python(python) - if environments.is_verbose(): - click.echo(u"Using python: {0}".format(python), err=True) - click.echo(u"Path to python: {0}".format(path_to_python), err=True) + if project.s.is_verbose(): + click.echo(f"Using python: {python}", err=True) + click.echo(f"Path to python: {path_to_python}", err=True) if not path_to_python and python is not None: # We need to install Python. click.echo( - u"{0}: Python {1} {2}".format( + "{}: Python {} {}".format( crayons.red("Warning", bold=True), - crayons.blue(python), - fix_utf8("was not found on your system…"), + crayons.cyan(python), + fix_utf8("was not found on your system..."), ), err=True, ) # check for python installers - from .vendor.pythonfinder.environment import PYENV_INSTALLED, ASDF_INSTALLED - from .installers import Pyenv, Asdf, InstallerError + from .installers import Asdf, InstallerError, InstallerNotFound, Pyenv # prefer pyenv if both pyenv and asdf are installed as it's # dedicated to python installs so probably the preferred # method of the user for new python installs. - if PYENV_INSTALLED and not PIPENV_DONT_USE_PYENV: - installer = Pyenv("pyenv") - elif ASDF_INSTALLED and not PIPENV_DONT_USE_ASDF: - installer = Asdf("asdf") - else: - installer = None + installer = None + if not project.s.PIPENV_DONT_USE_PYENV: + try: + installer = Pyenv(project) + except InstallerNotFound: + pass + if installer is None and not project.s.PIPENV_DONT_USE_ASDF: + try: + installer = Asdf(project) + except InstallerNotFound: + pass if not installer: - abort() + abort("Neither 'pyenv' nor 'asdf' could be found to install Python.") else: - if SESSION_IS_INTERACTIVE or PIPENV_YES: + if environments.SESSION_IS_INTERACTIVE or project.s.PIPENV_YES: try: version = installer.find_version_to_install(python) except ValueError: abort() except InstallerError as e: - click.echo(fix_utf8("Something went wrong…")) - click.echo(crayons.blue(e.err), err=True) - abort() - s = "{0} {1} {2}".format( + abort(f'Something went wrong while installing Python:\n{e.err}') + s = "{} {} {}".format( "Would you like us to install", - crayons.green("CPython {0}".format(version)), - "with {0}?".format(installer), + crayons.green(f"CPython {version}"), + f"with {installer}?", ) - # Prompt the user to continue… - if not (PIPENV_YES or click.confirm(s, default=True)): + # Prompt the user to continue... + if not (project.s.PIPENV_YES or click.confirm(s, default=True)): abort() else: # Tell the user we're installing Python. click.echo( - u"{0} {1} {2} {3}{4}".format( - crayons.normal(u"Installing", bold=True), - crayons.green(u"CPython {0}".format(version), bold=True), - crayons.normal(u"with {0}".format(installer), bold=True), - crayons.normal(u"(this may take a few minutes)"), - crayons.normal(fix_utf8("…"), bold=True), + "{} {} {} {}{}".format( + crayons.normal("Installing", bold=True), + crayons.green(f"CPython {version}", bold=True), + crayons.normal(f"with {installer.cmd}", bold=True), + crayons.normal("(this may take a few minutes)"), + crayons.normal("...", bold=True), ) ) - with create_spinner("Installing python...") as sp: + with create_spinner("Installing python...", project.s) as sp: try: c = installer.install(version) except InstallerError as e: sp.fail(environments.PIPENV_SPINNER_FAIL_TEXT.format( "Failed...") ) - click.echo(fix_utf8("Something went wrong…"), err=True) - click.echo(crayons.blue(e.err), err=True) + click.echo(fix_utf8("Something went wrong..."), err=True) + click.echo(crayons.cyan(e.err), err=True) else: sp.ok(environments.PIPENV_SPINNER_OK_TEXT.format("Success!")) - # Print the results, in a beautiful blue… - click.echo(crayons.blue(c.out), err=True) + # Print the results, in a beautiful blue... + click.echo(crayons.cyan(c.stdout), err=True) # Clear the pythonfinder caches from .vendor.pythonfinder import Finder finder = Finder(system=False, global_search=True) @@ -462,7 +427,7 @@ def ensure_python(three=None, python=None): assert python_version(path_to_python) == version except AssertionError: click.echo( - "{0}: The Python you just installed is not available on your {1}, apparently." + "{}: The Python you just installed is not available on your {}, apparently." "".format( crayons.red("Warning", bold=True), crayons.normal("PATH", bold=True), @@ -473,25 +438,23 @@ def ensure_python(three=None, python=None): return path_to_python -def ensure_virtualenv(three=None, python=None, site_packages=None, pypi_mirror=None): +def ensure_virtualenv(project, three=None, python=None, site_packages=None, pypi_mirror=None): """Creates a virtualenv, if one doesn't exist.""" - from .environments import PIPENV_USE_SYSTEM def abort(): sys.exit(1) - global USING_DEFAULT_PYTHON if not project.virtualenv_exists: try: # Ensure environment variables are set properly. ensure_environment() # Ensure Python is available. - python = ensure_python(three=three, python=python) - if python is not None and not isinstance(python, six.string_types): + python = ensure_python(project, three=three, python=python) + if python is not None and not isinstance(python, str): python = python.path.as_posix() # Create the virtualenv. # Abort if --system (or running in a virtualenv). - if PIPENV_USE_SYSTEM: + if project.s.PIPENV_USE_SYSTEM: click.echo( crayons.red( "You are attempting to re–create a virtualenv that " @@ -500,18 +463,18 @@ def ensure_virtualenv(three=None, python=None, site_packages=None, pypi_mirror=N ) sys.exit(1) do_create_virtualenv( - python=python, site_packages=site_packages, pypi_mirror=pypi_mirror + project, python=python, site_packages=site_packages, pypi_mirror=pypi_mirror ) except KeyboardInterrupt: # If interrupted, cleanup the virtualenv. - cleanup_virtualenv(bare=False) + cleanup_virtualenv(project, bare=False) sys.exit(1) - # If --three, --two, or --python were passed… + # If --three, --two, or --python were passed... elif (python) or (three is not None) or (site_packages is not None): - USING_DEFAULT_PYTHON = False + project.s.USING_DEFAULT_PYTHON = False # Ensure python is installed before deleting existing virtual env - python = ensure_python(three=three, python=python) - if python is not None and not isinstance(python, six.string_types): + python = ensure_python(project, three=three, python=python) + if python is not None and not isinstance(python, str): python = python.path.as_posix() click.echo(crayons.red("Virtualenv already exists!"), err=True) @@ -520,16 +483,17 @@ def ensure_virtualenv(three=None, python=None, site_packages=None, pypi_mirror=N # about, so confirm first. if "VIRTUAL_ENV" in os.environ: if not ( - PIPENV_YES or click.confirm("Remove existing virtualenv?", default=True) + project.s.PIPENV_YES or click.confirm("Use existing virtualenv?", default=True) ): abort() click.echo( - crayons.normal(fix_utf8("Removing existing virtualenv…"), bold=True), err=True + crayons.normal(fix_utf8("Using existing virtualenv..."), bold=True), err=True ) # Remove the virtualenv. - cleanup_virtualenv(bare=True) + cleanup_virtualenv(project, bare=True) # Call this function again. ensure_virtualenv( + project, three=three, python=python, site_packages=site_packages, @@ -538,6 +502,7 @@ def ensure_virtualenv(three=None, python=None, site_packages=None, pypi_mirror=N def ensure_project( + project, three=None, python=None, validate=True, @@ -550,29 +515,18 @@ def ensure_project( clear=False, ): """Ensures both Pipfile and virtualenv exist for the project.""" - from .environments import PIPENV_USE_SYSTEM - - # Clear the caches, if appropriate. - if clear: - print("clearing") - sys.exit(1) # Automatically use an activated virtualenv. - if PIPENV_USE_SYSTEM: - system = True + if project.s.PIPENV_USE_SYSTEM or project.virtualenv_exists: + system_or_exists = True + else: + system_or_exists = system # default to False if not project.pipfile_exists and deploy: raise exceptions.PipfileNotFound - # Fail if working under / - if not project.name: - click.echo( - "{0}: Pipenv is not intended to work under the root directory, " - "please choose another path.".format(crayons.red("ERROR")), - err=True - ) - sys.exit(1) # Skip virtualenv creation when --system was used. - if not system: + if not system_or_exists: ensure_virtualenv( + project, three=three, python=python, site_packages=site_packages, @@ -581,37 +535,37 @@ def ensure_project( if warn: # Warn users if they are using the wrong version of Python. if project.required_python_version: - path_to_python = which("python") or which("py") + path_to_python = project._which("python") or project._which("py") if path_to_python and project.required_python_version not in ( python_version(path_to_python) or "" ): click.echo( - "{0}: Your Pipfile requires {1} {2}, " - "but you are using {3} ({4}).".format( + "{}: Your Pipfile requires {} {}, " + "but you are using {} ({}).".format( crayons.red("Warning", bold=True), crayons.normal("python_version", bold=True), - crayons.blue(project.required_python_version), - crayons.blue(python_version(path_to_python) or "unknown"), + crayons.cyan(project.required_python_version), + crayons.cyan(python_version(path_to_python) or "unknown"), crayons.green(shorten_path(path_to_python)), ), err=True, ) click.echo( - " {0} and rebuilding the virtual environment " + " {} and rebuilding the virtual environment " "may resolve the issue.".format(crayons.green("$ pipenv --rm")), err=True, ) if not deploy: click.echo( - " {0} will surely fail." - "".format(crayons.red("$ pipenv check")), + " {} will surely fail." + "".format(crayons.yellow("$ pipenv check")), err=True, ) else: raise exceptions.DeployException # Ensure the Pipfile exists. ensure_pipfile( - validate=validate, skip_requirements=skip_requirements, system=system + project, validate=validate, skip_requirements=skip_requirements, system=system_or_exists ) @@ -629,13 +583,13 @@ def shorten_path(location, bold=False): # return short -def do_where(virtualenv=False, bare=True): +def do_where(project, virtualenv=False, bare=True): """Executes the where functionality.""" if not virtualenv: if not project.pipfile_exists: click.echo( "No Pipfile present at project home. Consider running " - "{0} first to automatically generate a Pipfile for you." + "{} first to automatically generate a Pipfile for you." "".format(crayons.green("`pipenv install`")), err=True, ) @@ -645,7 +599,7 @@ def do_where(virtualenv=False, bare=True): if not bare: location = shorten_path(location) click.echo( - "Pipfile found at {0}.\n Considering this to be the project home." + "Pipfile found at {}.\n Considering this to be the project home." "".format(crayons.green(location)), err=True, ) @@ -655,62 +609,86 @@ def do_where(virtualenv=False, bare=True): location = project.virtualenv_location if not bare: click.echo( - "Virtualenv location: {0}".format(crayons.green(location)), err=True + f"Virtualenv location: {crayons.green(location)}", err=True ) else: click.echo(location) -def _cleanup_procs(procs, failed_deps_queue, retry=True): +def _cleanup_procs(project, procs, failed_deps_queue, retry=True): while not procs.empty(): c = procs.get() - if not c.blocking: - c.block() - failed = False - if c.return_code != 0: - failed = True - if "Ignoring" in c.out: - click.echo(crayons.yellow(c.out.strip())) - elif environments.is_verbose(): - click.echo(crayons.blue(c.out.strip() or c.err.strip())) - # The Installation failed… + try: + out, err = c.communicate() + except AttributeError: + out, err = c.stdout, c.stderr + failed = c.returncode != 0 + if "Ignoring" in out: + click.echo(crayons.yellow(out.strip())) + elif project.s.is_verbose(): + click.echo(crayons.cyan(out.strip() or err.strip())) + # The Installation failed... if failed: - if not retry: - # The Installation failed… - # We echo both c.out and c.err because pip returns error details on out. - err = c.err.strip().splitlines() if c.err else [] - out = c.out.strip().splitlines() if c.out else [] + # If there is a mismatch in installed locations or the install fails + # due to wrongful disabling of pep517, we should allow for + # additional passes at installation + if "does not match installed location" in err: + project.environment.expand_egg_links() + click.echo("{}".format( + crayons.yellow( + "Failed initial installation: Failed to overwrite existing " + "package, likely due to path aliasing. Expanding and trying " + "again!" + ) + )) + dep = c.dep.copy() + dep.use_pep517 = True + elif "Disabling PEP 517 processing is invalid" in err: + dep = c.dep.copy() + dep.use_pep517 = True + elif not retry: + # The Installation failed... + # We echo both c.stdout and c.stderr because pip returns error details on out. + err = err.strip().splitlines() if err else [] + out = out.strip().splitlines() if out else [] err_lines = [line for message in [out, err] for line in message] # Return the subprocess' return code. raise exceptions.InstallError(c.dep.name, extra=err_lines) + else: + # Alert the user. + dep = c.dep.copy() + dep.use_pep517 = False + click.echo( + "{} {}! Will try again.".format( + crayons.red("An error occurred while installing"), + crayons.green(dep.as_line()), + ), err=True + ) # Save the Failed Dependency for later. - dep = c.dep.copy() failed_deps_queue.put(dep) - # Alert the user. - click.echo( - "{0} {1}! Will try again.".format( - crayons.red("An error occurred while installing"), - crayons.green(dep.as_line()), - ), err=True - ) -def batch_install(deps_list, procs, failed_deps_queue, +def batch_install(project, deps_list, procs, failed_deps_queue, requirements_dir, no_deps=True, ignore_hashes=False, allow_global=False, blocking=False, pypi_mirror=None, retry=True, sequential_deps=None): - from .vendor.requirementslib.models.utils import strip_extras_markers_from_requirement + from .vendor.requirementslib.models.utils import ( + strip_extras_markers_from_requirement + ) if sequential_deps is None: sequential_deps = [] failed = (not retry) install_deps = not no_deps if not failed: - label = INSTALL_LABEL if not PIPENV_HIDE_EMOJIS else "" + label = INSTALL_LABEL if not environments.PIPENV_HIDE_EMOJIS else "" else: label = INSTALL_LABEL2 deps_to_install = deps_list[:] deps_to_install.extend(sequential_deps) + deps_to_install = [ + dep for dep in deps_to_install if not project.environment.is_satisfied(dep) + ] sequential_dep_names = [d.name for d in sequential_deps] deps_list_bar = progress.bar( @@ -734,7 +712,7 @@ def batch_install(deps_list, procs, failed_deps_queue, is_artifact = True elif dep.is_vcs: is_artifact = True - if not PIPENV_RESOLVE_VCS and is_artifact and not dep.editable: + if not project.s.PIPENV_RESOLVE_VCS and is_artifact and not dep.editable: install_deps = True no_deps = False @@ -745,66 +723,69 @@ def batch_install(deps_list, procs, failed_deps_queue, del os.environ["PYTHONHOME"] if "GIT_CONFIG" in os.environ and dep.is_vcs: del os.environ["GIT_CONFIG"] + use_pep517 = True + if failed and not dep.is_vcs: + use_pep517 = getattr(dep, "use_pep517", False) + is_sequential = sequential_deps and dep.name in sequential_dep_names + is_blocking = any([dep.editable, dep.is_vcs, blocking, is_sequential]) c = pip_install( + project, dep, ignore_hashes=any([ignore_hashes, dep.editable, dep.is_vcs]), allow_global=allow_global, no_deps=not install_deps, - block=any([dep.editable, dep.is_vcs, blocking]), + block=is_blocking, index=dep.index, requirements_dir=requirements_dir, pypi_mirror=pypi_mirror, trusted_hosts=trusted_hosts, extra_indexes=extra_indexes, - use_pep517=not failed, + use_pep517=use_pep517, ) c.dep = dep - # if dep.is_vcs or dep.editable: - is_sequential = sequential_deps and dep.name in sequential_dep_names - if is_sequential: - c.block() procs.put(c) if procs.full() or procs.qsize() == len(deps_list) or is_sequential: - _cleanup_procs(procs, failed_deps_queue, retry=retry) + _cleanup_procs(project, procs, failed_deps_queue, retry=retry) def do_install_dependencies( + project, dev=False, - only=False, + dev_only=False, bare=False, - requirements=False, + emit_requirements=False, allow_global=False, ignore_hashes=False, skip_lock=False, concurrent=True, requirements_dir=None, - pypi_mirror=False, + pypi_mirror=None, ): """" Executes the install functionality. - If requirements is True, simply spits out a requirements format to stdout. + If emit_requirements is True, simply spits out a requirements format to stdout. """ - from six.moves import queue - if requirements: + import queue + if emit_requirements: bare = True - # Load the lockfile if it exists, or if only is being used (e.g. lock is being used). - if skip_lock or only or not project.lockfile_exists: + # Load the lockfile if it exists, or if dev_only is being used. + if skip_lock or not project.lockfile_exists: if not bare: click.echo( - crayons.normal(fix_utf8("Installing dependencies from Pipfile…"), bold=True) + crayons.normal(fix_utf8("Installing dependencies from Pipfile..."), bold=True) ) - # skip_lock should completely bypass the lockfile (broken in 4dac1676) - lockfile = project.get_or_create_lockfile(from_pipfile=True) + # skip_lock should completely bypass the lockfile (broken in 4dac1676) + lockfile = project.get_or_create_lockfile(from_pipfile=True) else: lockfile = project.get_or_create_lockfile() if not bare: click.echo( crayons.normal( - fix_utf8("Installing dependencies from Pipfile.lock ({0})…".format( + fix_utf8("Installing dependencies from Pipfile.lock ({})...".format( lockfile["_meta"].get("hash", {}).get("sha256")[-6:] )), bold=True, @@ -812,22 +793,21 @@ def do_install_dependencies( ) # Allow pip to resolve dependencies when in skip-lock mode. no_deps = not skip_lock # skip_lock true, no_deps False, pip resolves deps - deps_list = list(lockfile.get_requirements(dev=dev, only=requirements)) - if requirements: - index_args = prepare_pip_source_args(project.sources) + dev = dev or dev_only + deps_list = list(lockfile.get_requirements(dev=dev, only=dev_only)) + if emit_requirements: + index_args = prepare_pip_source_args( + get_source_list(project, pypi_mirror=pypi_mirror) + ) index_args = " ".join(index_args).replace(" -", "\n-") deps = [ req.as_line(sources=False, include_hashes=False) for req in deps_list ] - # Output only default dependencies click.echo(index_args) - click.echo( - "\n".join(sorted(deps)) - ) + click.echo("\n".join(sorted(deps))) sys.exit(0) - if concurrent: - nprocs = PIPENV_MAX_SUBPROCESS + nprocs = project.s.PIPENV_MAX_SUBPROCESS else: nprocs = 1 procs = queue.Queue(maxsize=nprocs) @@ -843,14 +823,14 @@ def do_install_dependencies( } batch_install( - normal_deps, procs, failed_deps_queue, requirements_dir, **install_kwargs + project, normal_deps, procs, failed_deps_queue, requirements_dir, **install_kwargs ) if not procs.empty(): - _cleanup_procs(procs, failed_deps_queue) + _cleanup_procs(project, procs, failed_deps_queue) # click.echo(crayons.normal( - # decode_for_output("Installing editable and vcs dependencies…"), bold=True + # decode_for_output("Installing editable and vcs dependencies..."), bold=True # )) # install_kwargs.update({"blocking": True}) @@ -861,10 +841,10 @@ def do_install_dependencies( # **install_kwargs # ) - # Iterate over the hopefully-poorly-packaged dependencies… + # Iterate over the hopefully-poorly-packaged dependencies... if not failed_deps_queue.empty(): click.echo( - crayons.normal(fix_utf8("Installing initially failed dependencies…"), bold=True) + crayons.normal(fix_utf8("Installing initially failed dependencies..."), bold=True) ) retry_list = [] while not failed_deps_queue.empty(): @@ -872,10 +852,10 @@ def do_install_dependencies( retry_list.append(failed_dep) install_kwargs.update({"retry": False}) batch_install( - retry_list, procs, failed_deps_queue, requirements_dir, **install_kwargs + project, retry_list, procs, failed_deps_queue, requirements_dir, **install_kwargs ) if not procs.empty(): - _cleanup_procs(procs, failed_deps_queue, retry=False) + _cleanup_procs(project, procs, failed_deps_queue, retry=False) def convert_three_to_python(three, python): @@ -893,45 +873,45 @@ def convert_three_to_python(three, python): return python -def do_create_virtualenv(python=None, site_packages=None, pypi_mirror=None): +def do_create_virtualenv(project, python=None, site_packages=None, pypi_mirror=None): """Creates a virtualenv.""" click.echo( - crayons.normal(fix_utf8("Creating a virtualenv for this project…"), bold=True), err=True + crayons.normal(fix_utf8("Creating a virtualenv for this project..."), bold=True), err=True ) click.echo( - u"Pipfile: {0}".format(crayons.red(project.pipfile_location, bold=True)), + f"Pipfile: {crayons.yellow(project.pipfile_location, bold=True)}", err=True, ) # Default to using sys.executable, if Python wasn't provided. - using_string = u"Using" + using_string = "Using" if not python: python = sys.executable using_string = "Using default python from" click.echo( - u"{0} {1} {3} {2}".format( + "{0} {1} {3} {2}".format( crayons.normal(using_string, bold=True), - crayons.red(python, bold=True), - crayons.normal(fix_utf8("to create virtualenv…"), bold=True), - crayons.green("({0})".format(python_version(python))), + crayons.yellow(python, bold=True), + crayons.normal(fix_utf8("to create virtualenv..."), bold=True), + crayons.green(f"({python_version(python)})"), ), err=True, ) cmd = [ - vistir.compat.Path(sys.executable).absolute().as_posix(), + Path(sys.executable).absolute().as_posix(), "-m", "virtualenv", - "--prompt=({0}) ".format(project.name), - "--python={0}".format(python), + f"--prompt={project.name}", + f"--python={python}", project.get_location_for_virtualenv(), ] - # Pass site-packages flag to virtualenv, if desired… + # Pass site-packages flag to virtualenv, if desired... if site_packages: click.echo( - crayons.normal(fix_utf8("Making site-packages available…"), bold=True), err=True + crayons.normal(fix_utf8("Making site-packages available..."), bold=True), err=True ) cmd.append("--system-site-packages") @@ -942,17 +922,17 @@ def do_create_virtualenv(python=None, site_packages=None, pypi_mirror=None): # Actually create the virtualenv. error = None - with create_spinner(u"Creating virtual environment...") as sp: - with interrupt_handled_subprocess(cmd, combine_stderr=False, env=pip_config) as c: - click.echo(crayons.blue(u"{0}".format(c.out)), err=True) - if c.returncode != 0: - error = c.err if environments.is_verbose() else exceptions.prettify_exc(c.err) - sp.fail(environments.PIPENV_SPINNER_FAIL_TEXT.format(u"Failed creating virtual environment")) - else: - sp.green.ok(environments.PIPENV_SPINNER_OK_TEXT.format(u"Successfully created virtual environment!")) + with create_spinner("Creating virtual environment...", project.s) as sp: + c = subprocess_run(cmd, env=pip_config) + click.echo(crayons.cyan(f"{c.stdout}"), err=True) + if c.returncode != 0: + error = c.stderr if project.s.is_verbose() else exceptions.prettify_exc(c.stderr) + sp.fail(environments.PIPENV_SPINNER_FAIL_TEXT.format("Failed creating virtual environment")) + else: + sp.green.ok(environments.PIPENV_SPINNER_OK_TEXT.format("Successfully created virtual environment!")) if error is not None: raise exceptions.VirtualenvCreationException( - extra=crayons.red("{0}".format(error)) + extra=crayons.red(f"{error}") ) # Associate project directory with the environment. @@ -962,8 +942,10 @@ def do_create_virtualenv(python=None, site_packages=None, pypi_mirror=None): f.write(vistir.misc.fs_str(project.project_directory)) from .environment import Environment sources = project.pipfile_sources + # project.get_location_for_virtualenv is only for if we are creating a new virtualenv + # whereas virtualenv_location is for the current path to the runtime project._environment = Environment( - prefix=project.get_location_for_virtualenv(), + prefix=project.virtualenv_location, is_venv=True, sources=sources, pipfile=project.parsed_pipfile, @@ -971,7 +953,7 @@ def do_create_virtualenv(python=None, site_packages=None, pypi_mirror=None): ) project._environment.add_dist("pipenv") # Say where the virtualenv is. - do_where(virtualenv=True, bare=False) + do_where(project, virtualenv=True, bare=False) def parse_download_fname(fname, name): @@ -988,7 +970,7 @@ def parse_download_fname(fname, name): return version -def get_downloads_info(names_map, section): +def get_downloads_info(project, names_map, section): from .vendor.requirementslib.models.requirements import Requirement info = [] @@ -999,12 +981,13 @@ def get_downloads_info(names_map, section): # Get the version info from the filenames. version = parse_download_fname(fname, name) # Get the hash of each file. - cmd = '{0} hash "{1}"'.format( - escape_grouped_arguments(which_pip()), + cmd = [ + which_pip(project), + "hash", os.sep.join([project.download_location, fname]), - ) - c = delegator.run(cmd) - hash = c.out.split("--hash=")[1].strip() + ] + c = subprocess_run(cmd) + hash = c.stdout.split("--hash=")[1].strip() # Verify we're adding the correct version from Pipfile # and not one from a dependency. specified_version = p[section].get(name, "") @@ -1022,6 +1005,7 @@ def overwrite_dev(prod, dev): def do_lock( + project, ctx=None, system=False, clear=False, @@ -1063,10 +1047,10 @@ def do_lock( if write: # Alert the user of progress. click.echo( - u"{0} {1} {2}".format( - crayons.normal(u"Locking"), - crayons.red(u"[{0}]".format(pipfile_section.replace("_", "-"))), - crayons.normal(fix_utf8("dependencies…")), + "{} {} {}".format( + crayons.normal("Locking"), + crayons.yellow("[{}]".format(pipfile_section.replace("_", "-"))), + crayons.normal(fix_utf8("dependencies...")), ), err=True, ) @@ -1074,7 +1058,7 @@ def do_lock( # Mutates the lockfile venv_resolve_deps( packages, - which=which, + which=project._which, project=project, dev=is_dev, clear=clear, @@ -1086,7 +1070,7 @@ def do_lock( keep_outdated=keep_outdated ) - # Support for --keep-outdated… + # Support for --keep-outdated... if keep_outdated: from pipenv.vendor.packaging.utils import canonicalize_name for section_name, section in ( @@ -1111,9 +1095,9 @@ def do_lock( if write: project.write_lockfile(lockfile) click.echo( - "{0}".format( + "{}".format( crayons.normal( - "Updated Pipfile.lock ({0})!".format( + "Updated Pipfile.lock ({})!".format( lockfile["_meta"].get("hash", {}).get("sha256")[-6:] ), bold=True, @@ -1125,20 +1109,20 @@ def do_lock( return lockfile -def do_purge(bare=False, downloads=False, allow_global=False): +def do_purge(project, bare=False, downloads=False, allow_global=False): """Executes the purge functionality.""" if downloads: if not bare: - click.echo(crayons.normal(fix_utf8("Clearing out downloads directory…"), bold=True)) + click.echo(crayons.normal(fix_utf8("Clearing out downloads directory..."), bold=True)) vistir.path.rmtree(project.download_location) return # Remove comments from the output, if any. - installed = set([ + installed = { pep423_name(pkg.project_name) for pkg in project.environment.get_installed_packages() - ]) - bad_pkgs = set([pep423_name(pkg) for pkg in BAD_PACKAGES]) + } + bad_pkgs = {pep423_name(pkg) for pkg in BAD_PACKAGES} # Remove setuptools, pip, etc from targets for removal to_remove = installed - bad_pkgs @@ -1151,27 +1135,29 @@ def do_purge(bare=False, downloads=False, allow_global=False): if not bare: click.echo( - fix_utf8("Found {0} installed package(s), purging…".format(len(to_remove))) + fix_utf8(f"Found {len(to_remove)} installed package(s), purging...") ) - command = "{0} uninstall {1} -y".format( - escape_grouped_arguments(which_pip(allow_global=allow_global)), - " ".join(to_remove), - ) - if environments.is_verbose(): - click.echo("$ {0}".format(command)) - c = delegator.run(command) - if c.return_code != 0: - raise exceptions.UninstallError(installed, command, c.out + c.err, c.return_code) + command = [ + which_pip(project, allow_global=allow_global), + "uninstall", "-y", + ] + list(to_remove) + if project.s.is_verbose(): + click.echo(f"$ {cmd_list_to_shell(command)}") + c = subprocess_run(command) + if c.returncode != 0: + raise exceptions.UninstallError(installed, cmd_list_to_shell(command), c.stdout + c.stderr, c.returncode) if not bare: - click.echo(crayons.blue(c.out)) + click.echo(crayons.cyan(c.stdout)) click.echo(crayons.green("Environment now purged and fresh!")) return installed def do_init( + project, dev=False, - requirements=False, + dev_only=False, + emit_requirements=False, allow_global=False, ignore_pipfile=False, skip_lock=False, @@ -1184,25 +1170,23 @@ def do_init( pypi_mirror=None, ): """Executes the init functionality.""" - from .environments import ( - PIPENV_VIRTUALENV, PIPENV_DEFAULT_PYTHON_VERSION, PIPENV_PYTHON, PIPENV_USE_SYSTEM - ) - python = None - if PIPENV_PYTHON is not None: - python = PIPENV_PYTHON - elif PIPENV_DEFAULT_PYTHON_VERSION is not None: - python = PIPENV_DEFAULT_PYTHON_VERSION - if not system and not PIPENV_USE_SYSTEM: + python = None + if project.s.PIPENV_PYTHON is not None: + python = project.s.PIPENV_PYTHON + elif project.s.PIPENV_DEFAULT_PYTHON_VERSION is not None: + python = project.s.PIPENV_DEFAULT_PYTHON_VERSION + + if not system and not project.s.PIPENV_USE_SYSTEM: if not project.virtualenv_exists: try: - do_create_virtualenv(python=python, three=None, pypi_mirror=pypi_mirror) + do_create_virtualenv(project, python=python, three=None, pypi_mirror=pypi_mirror) except KeyboardInterrupt: - cleanup_virtualenv(bare=False) + cleanup_virtualenv(project, bare=False) sys.exit(1) # Ensure the Pipfile exists. if not deploy: - ensure_pipfile(system=system) + ensure_pipfile(project, system=system) if not requirements_dir: requirements_dir = vistir.path.create_tracked_tempdir( suffix="-requirements", prefix="pipenv-" @@ -1215,36 +1199,35 @@ def do_init( if deploy: click.echo( crayons.red( - "Your Pipfile.lock ({0}) is out of date. Expected: ({1}).".format( + "Your Pipfile.lock ({}) is out of date. Expected: ({}).".format( old_hash[-6:], new_hash[-6:] ) ) ) raise exceptions.DeployException - sys.exit(1) - elif (system or allow_global) and not (PIPENV_VIRTUALENV): + elif (system or allow_global) and not (project.s.PIPENV_VIRTUALENV): click.echo( - crayons.red(fix_utf8( - "Pipfile.lock ({0}) out of date, but installation " - "uses {1}… re-building lockfile must happen in " + crayons.yellow(fix_utf8( + "Pipfile.lock ({}) out of date, but installation " + "uses {} re-building lockfile must happen in " "isolation. Please rebuild lockfile in a virtualenv. " - "Continuing anyway…".format( - crayons.white(old_hash[-6:]), crayons.white("--system") - )), - bold=True, + "Continuing anyway...".format( + old_hash[-6:], "--system" + )) ), err=True, ) else: if old_hash: - msg = fix_utf8("Pipfile.lock ({0}) out of date, updating to ({1})…") + msg = fix_utf8("Pipfile.lock ({0}) out of date, updating to ({1})...") else: - msg = fix_utf8("Pipfile.lock is corrupted, replaced with ({1})…") + msg = fix_utf8("Pipfile.lock is corrupted, replaced with ({1})...") click.echo( - crayons.red(msg.format(old_hash[-6:], new_hash[-6:]), bold=True), + crayons.yellow(msg.format(old_hash[-6:], new_hash[-6:]), bold=True), err=True, ) do_lock( + project, system=system, pre=pre, keep_outdated=keep_outdated, @@ -1255,7 +1238,7 @@ def do_init( if not project.lockfile_exists and not skip_lock: # Unless we're in a virtualenv not managed by pipenv, abort if we're # using the system's python. - if (system or allow_global) and not (PIPENV_VIRTUALENV): + if (system or allow_global) and not (project.s.PIPENV_VIRTUALENV): raise exceptions.PipenvOptionsError( "--system", "--system is intended to be used for Pipfile installation, " @@ -1264,10 +1247,11 @@ def do_init( ) else: click.echo( - crayons.normal(fix_utf8("Pipfile.lock not found, creating…"), bold=True), + crayons.normal(fix_utf8("Pipfile.lock not found, creating..."), bold=True), err=True, ) do_lock( + project, system=system, pre=pre, keep_outdated=keep_outdated, @@ -1275,8 +1259,10 @@ def do_init( pypi_mirror=pypi_mirror, ) do_install_dependencies( + project, dev=dev, - requirements=requirements, + dev_only=dev_only, + emit_requirements=emit_requirements, allow_global=allow_global, skip_lock=skip_lock, concurrent=concurrent, @@ -1287,22 +1273,23 @@ def do_init( # Hint the user what to do to activate the virtualenv. if not allow_global and not deploy and "PIPENV_ACTIVE" not in os.environ: click.echo( - "To activate this project's virtualenv, run {0}.\n" + "To activate this project's virtualenv, run {}.\n" "Alternatively, run a command " - "inside the virtualenv with {1}.".format( - crayons.red("pipenv shell"), crayons.red("pipenv run") + "inside the virtualenv with {}.".format( + crayons.yellow("pipenv shell"), crayons.yellow("pipenv run") ) ) def get_pip_args( + project, pre=False, # type: bool - verbose=False, # type: bool, - upgrade=False, # type: bool, - require_hashes=False, # type: bool, - no_build_isolation=False, # type: bool, - no_use_pep517=False, # type: bool, - no_deps=False, # type: bool, + verbose=False, # type: bool + upgrade=False, # type: bool + require_hashes=False, # type: bool + no_build_isolation=False, # type: bool + no_use_pep517=False, # type: bool + no_deps=False, # type: bool selective_upgrade=False, # type: bool src_dir=None, # type: Optional[str] ): @@ -1317,7 +1304,8 @@ def get_pip_args( "no_use_pep517": [], "no_deps": ["--no-deps"], "selective_upgrade": [ - "--upgrade-strategy=only-if-needed", "--exists_action={0}".format(PIP_EXISTS_ACTION or "i") + "--upgrade-strategy=only-if-needed", + "--exists-action={}".format(project.s.PIP_EXISTS_ACTION or "i") ], "src_dir": src_dir, } @@ -1329,6 +1317,8 @@ def get_pip_args( for key in arg_map.keys(): if key in locals() and locals().get(key): arg_set.extend(arg_map.get(key)) + elif key == "selective_upgrade" and not locals().get(key): + arg_set.append("--exists-action=i") return list(vistir.misc.dedup(arg_set)) @@ -1348,13 +1338,13 @@ def get_requirement_line( requirement.line_instance.vcsrepo line = requirement.line_instance.line if requirement.line_instance.markers: - line = '{0}; {1}'.format(line, requirement.line_instance.markers) + line = f'{line}; {requirement.line_instance.markers}' if not format_for_file: - line = '"{0}"'.format(line) + line = f'"{line}"' if requirement.editable: if not format_for_file: return ["-e", line] - return '-e {0}'.format(line) + return f'-e {line}' if not format_for_file: return [line] return line @@ -1362,6 +1352,7 @@ def get_requirement_line( def write_requirement_to_file( + project, # type: Project requirement, # type: Requirement requirements_dir=None, # type: Optional[str] src_dir=None, # type: Optional[str] @@ -1379,9 +1370,9 @@ def write_requirement_to_file( prefix="pipenv-", suffix="-requirement.txt", dir=requirements_dir, delete=False ) - if environments.is_verbose(): + if project.s.is_verbose(): click.echo( - "Writing supplied requirement line to temporary file: {0!r}".format(line), + f"Writing supplied requirement line to temporary file: {line!r}", err=True ) f.write(vistir.misc.to_bytes(line)) @@ -1391,6 +1382,7 @@ def write_requirement_to_file( def pip_install( + project, requirement=None, r=None, allow_global=False, @@ -1406,7 +1398,7 @@ def pip_install( trusted_hosts=None, use_pep517=True ): - from pipenv.patched.notpip._internal import logger as piplogger + piplogger = logging.getLogger("pipenv.patched.notpip._internal.commands.install") src_dir = None if not trusted_hosts: trusted_hosts = [] @@ -1439,44 +1431,44 @@ def pip_install( no_deps = True r = write_requirement_to_file( - requirement, requirements_dir=requirements_dir, src_dir=src_dir, + project, requirement, requirements_dir=requirements_dir, src_dir=src_dir, include_hashes=not ignore_hashes ) sources = get_source_list( - index, extra_indexes=extra_indexes, trusted_hosts=trusted_hosts, + project, index, extra_indexes=extra_indexes, trusted_hosts=trusted_hosts, pypi_mirror=pypi_mirror ) if r: - with io.open(r, "r") as fh: + with open(r, "r") as fh: if "--hash" not in fh.read(): ignore_hashes = True - if environments.is_verbose(): + if project.s.is_verbose(): piplogger.setLevel(logging.WARN) if requirement: click.echo( - crayons.normal("Installing {0!r}".format(requirement.name), bold=True), + crayons.normal(f"Installing {requirement.name!r}", bold=True), err=True, ) - pip_command = [which_pip(allow_global=allow_global), "install"] + pip_command = [project._which("python", allow_global=allow_global), "-m", "pip", "install"] pip_args = get_pip_args( - pre=pre, verbose=environments.is_verbose(), upgrade=True, + project, pre=pre, verbose=project.s.is_verbose(), upgrade=True, selective_upgrade=selective_upgrade, no_use_pep517=not use_pep517, - no_deps=no_deps, require_hashes=not ignore_hashes + no_deps=no_deps, require_hashes=not ignore_hashes, ) pip_command.extend(pip_args) if r: - pip_command.extend(["-r", r]) + pip_command.extend(["-r", vistir.path.normalize_path(r)]) elif line: pip_command.extend(line) pip_command.extend(prepare_pip_source_args(sources)) - if environments.is_verbose(): - click.echo("$ {0}".format(pip_command), err=True) - cache_dir = vistir.compat.Path(PIPENV_CACHE_DIR) + if project.s.is_verbose(): + click.echo(f"$ {cmd_list_to_shell(pip_command)}", err=True) + cache_dir = Path(project.s.PIPENV_CACHE_DIR) DEFAULT_EXISTS_ACTION = "w" if selective_upgrade: DEFAULT_EXISTS_ACTION = "i" - exists_action = vistir.misc.fs_str(PIP_EXISTS_ACTION or DEFAULT_EXISTS_ACTION) + exists_action = vistir.misc.fs_str(project.s.PIP_EXISTS_ACTION or DEFAULT_EXISTS_ACTION) pip_config = { "PIP_CACHE_DIR": vistir.misc.fs_str(cache_dir.as_posix()), "PIP_WHEEL_DIR": vistir.misc.fs_str(cache_dir.joinpath("wheels").as_posix()), @@ -1487,21 +1479,18 @@ def pip_install( "PATH": vistir.misc.fs_str(os.environ.get("PATH")), } if src_dir: - if environments.is_verbose(): - click.echo("Using source directory: {0!r}".format(src_dir), err=True) + if project.s.is_verbose(): + click.echo(f"Using source directory: {src_dir!r}", err=True) pip_config.update( {"PIP_SRC": vistir.misc.fs_str(src_dir)} ) - cmd = Script.parse(pip_command) - pip_command = cmd.cmdify() - c = None - c = delegator.run(pip_command, block=block, env=pip_config) + c = subprocess_run(pip_command, block=block, env=pip_config) c.env = pip_config return c -def pip_download(package_name): - cache_dir = vistir.compat.Path(PIPENV_CACHE_DIR) +def pip_download(project, package_name): + cache_dir = Path(project.s.PIPENV_CACHE_DIR) pip_config = { "PIP_CACHE_DIR": vistir.misc.fs_str(cache_dir.as_posix()), "PIP_WHEEL_DIR": vistir.misc.fs_str(cache_dir.joinpath("wheels").as_posix()), @@ -1510,14 +1499,15 @@ def pip_download(package_name): ), } for source in project.sources: - cmd = '{0} download "{1}" -i {2} -d {3}'.format( - escape_grouped_arguments(which_pip()), + cmd = [ + which_pip(project), + "download", package_name, - source["url"], - project.download_location, - ) - c = delegator.run(cmd, env=pip_config) - if c.return_code == 0: + "-i", source["url"], + "-d", project.download_location, + ] + c = subprocess_run(cmd, env=pip_config) + if c.returncode == 0: break return c @@ -1541,8 +1531,8 @@ def fallback_which(command, location=None, allow_global=False, system=False): from .vendor.pythonfinder import Finder if not command: raise ValueError("fallback_which: Must provide a command to search for...") - if not isinstance(command, six.string_types): - raise TypeError("Provided command must be a string, received {0!r}".format(command)) + if not isinstance(command, str): + raise TypeError(f"Provided command must be a string, received {command!r}") global_search = system or allow_global if location is None: global_search = True @@ -1557,7 +1547,7 @@ def fallback_which(command, location=None, allow_global=False, system=False): return "" -def which_pip(allow_global=False): +def which_pip(project, allow_global=False): """Returns the location of virtualenv-installed pip.""" location = None @@ -1565,7 +1555,7 @@ def which_pip(allow_global=False): location = os.environ["VIRTUAL_ENV"] if allow_global: if location: - pip = which("pip", location=location) + pip = project._which("pip", location=location) if pip: return pip @@ -1574,46 +1564,31 @@ def which_pip(allow_global=False): if where: return where - pip = which("pip") + pip = project._which("pip") if not pip: pip = fallback_which("pip", allow_global=allow_global, location=location) return pip -def system_which(command, mult=False): +def system_which(command, path=None): """Emulates the system's which. Returns None if not found.""" - _which = "which -a" if not os.name == "nt" else "where" - os.environ = { - vistir.compat.fs_str(k): vistir.compat.fs_str(val) - for k, val in os.environ.items() - } - result = None - try: - c = delegator.run("{0} {1}".format(_which, command)) - try: - # Which Not found… - if c.return_code == 127: - click.echo( - "{}: the {} system utility is required for Pipenv to find Python installations properly." - "\n Please install it.".format( - crayons.red("Warning", bold=True), crayons.red(_which) - ), - err=True, - ) - assert c.return_code == 0 - except AssertionError: - result = fallback_which(command, allow_global=True) - except TypeError: - if not result: - result = fallback_which(command, allow_global=True) - else: - if not result: - result = next(iter([c.out, c.err]), "").split("\n") - result = next(iter(result)) if not mult else result - return result - if not result: - result = fallback_which(command, allow_global=True) - result = [result] if mult else result + import shutil + + result = shutil.which(command, path=path) + if result is None: + _which = "where" if os.name == "nt" else "which -a" + env = {'PATH': path} if path else None + c = subprocess_run(f"{_which} {command}", shell=True, env=env) + if c.returncode == 127: + click.echo( + "{}: the {} system utility is required for Pipenv to find Python installations properly." + "\n Please install it.".format( + crayons.red("Warning", bold=True), crayons.yellow(_which) + ), + err=True, + ) + if c.returncode == 0: + result = next(iter(c.stdout.splitlines()), None) return result @@ -1621,7 +1596,7 @@ def format_help(help): """Formats the help string.""" help = help.replace("Options:", str(crayons.normal("Options:", bold=True))) help = help.replace( - "Usage: pipenv", str("Usage: {0}".format(crayons.normal("pipenv", bold=True))) + "Usage: pipenv", str("Usage: {}".format(crayons.normal("pipenv", bold=True))) ) help = help.replace(" check", str(crayons.red(" check", bold=True))) help = help.replace(" clean", str(crayons.red(" clean", bold=True))) @@ -1631,46 +1606,45 @@ def format_help(help): help = help.replace(" open", str(crayons.red(" open", bold=True))) help = help.replace(" run", str(crayons.yellow(" run", bold=True))) help = help.replace(" shell", str(crayons.yellow(" shell", bold=True))) + help = help.replace(" scripts", str(crayons.yellow(" scripts", bold=True))) help = help.replace(" sync", str(crayons.green(" sync", bold=True))) help = help.replace(" uninstall", str(crayons.magenta(" uninstall", bold=True))) help = help.replace(" update", str(crayons.green(" update", bold=True))) additional_help = """ Usage Examples: Create a new project using Python 3.7, specifically: - $ {1} + $ {} Remove project virtualenv (inferred from current directory): - $ {9} + $ {} Install all dependencies for a project (including dev): - $ {2} + $ {} Create a lockfile containing pre-releases: - $ {6} + $ {} Show a graph of your installed dependencies: - $ {4} + $ {} Check your installed dependencies for security vulnerabilities: - $ {7} + $ {} Install a local setup.py into your virtual environment/Pipfile: - $ {5} + $ {} Use a lower-level pip command: - $ {8} + $ {} Commands:""".format( - crayons.red("pipenv --three"), - crayons.red("pipenv --python 3.7"), - crayons.red("pipenv install --dev"), - crayons.red("pipenv lock"), - crayons.red("pipenv graph"), - crayons.red("pipenv install -e ."), - crayons.red("pipenv lock --pre"), - crayons.red("pipenv check"), - crayons.red("pipenv run pip freeze"), - crayons.red("pipenv --rm"), + crayons.yellow("pipenv --python 3.7"), + crayons.yellow("pipenv --rm"), + crayons.yellow("pipenv install --dev"), + crayons.yellow("pipenv lock --pre"), + crayons.yellow("pipenv graph"), + crayons.yellow("pipenv check"), + crayons.yellow("pipenv install -e ."), + crayons.yellow("pipenv run pip freeze"), ) help = help.replace("Commands:", additional_help) return help @@ -1705,19 +1679,19 @@ def format_pip_output(out, r=None): else: yield line - out = "\n".join([l for l in gen(out)]) + out = "\n".join([line for line in gen(out)]) return out -def warn_in_virtualenv(): +def warn_in_virtualenv(project): # Only warn if pipenv isn't already active. - if environments.is_in_virtualenv() and not environments.is_quiet(): + if environments.is_in_virtualenv() and not project.s.is_quiet(): click.echo( - "{0}: Pipenv found itself running within a virtual environment, " + "{}: Pipenv found itself running within a virtual environment, " "so it will automatically use that environment, instead of " "creating its own for any project. You can set " - "{1} to force pipenv to ignore that environment and create " - "its own instead. You can set {2} to suppress this " + "{} to force pipenv to ignore that environment and create " + "its own instead. You can set {} to suppress this " "warning.".format( crayons.green("Courtesy Notice"), crayons.normal("PIPENV_IGNORE_VIRTUALENVS=1", bold=True), @@ -1727,7 +1701,7 @@ def warn_in_virtualenv(): ) -def ensure_lockfile(keep_outdated=False, pypi_mirror=None): +def ensure_lockfile(project, keep_outdated=False, pypi_mirror=None): """Ensures that the lockfile is up-to-date.""" if not keep_outdated: keep_outdated = project.settings.get("keep_outdated") @@ -1737,44 +1711,45 @@ def ensure_lockfile(keep_outdated=False, pypi_mirror=None): new_hash = project.calculate_pipfile_hash() if new_hash != old_hash: click.echo( - crayons.red( - fix_utf8("Pipfile.lock ({0}) out of date, updating to ({1})…".format( + crayons.yellow( + fix_utf8("Pipfile.lock ({}) out of date, updating to ({})...".format( old_hash[-6:], new_hash[-6:] )), bold=True, ), err=True, ) - do_lock(keep_outdated=keep_outdated, pypi_mirror=pypi_mirror) + do_lock(project, keep_outdated=keep_outdated, pypi_mirror=pypi_mirror) else: - do_lock(keep_outdated=keep_outdated, pypi_mirror=pypi_mirror) + do_lock(project, keep_outdated=keep_outdated, pypi_mirror=pypi_mirror) -def do_py(system=False): +def do_py(project, ctx=None, system=False): if not project.virtualenv_exists: click.echo( "{}({}){}".format( crayons.red("No virtualenv has been created for this project "), - crayons.white(project.project_directory, bold=True), + crayons.yellow(project.project_directory, bold=True), crayons.red(" yet!") ), err=True, ) - return + ctx.abort() try: - click.echo(which("python", allow_global=system)) + click.echo(project._which("python", allow_global=system)) except AttributeError: click.echo(crayons.red("No project found!")) -def do_outdated(pypi_mirror=None, pre=False, clear=False): +def do_outdated(project, pypi_mirror=None, pre=False, clear=False): # TODO: Allow --skip-lock here? + from collections import namedtuple + + from .vendor.packaging.utils import canonicalize_name from .vendor.requirementslib.models.requirements import Requirement from .vendor.requirementslib.models.utils import get_version - from .vendor.packaging.utils import canonicalize_name from .vendor.vistir.compat import Mapping - from collections import namedtuple packages = {} package_info = namedtuple("PackageInfo", ["name", "installed", "available"]) @@ -1785,12 +1760,15 @@ def do_outdated(pypi_mirror=None, pre=False, clear=False): (pkg.project_name, pkg.parsed_version, pkg.latest_version) for pkg in project.environment.get_outdated_packages() } - reverse_deps = project.environment.reverse_dependencies() + reverse_deps = { + canonicalize_name(name): deps + for name, deps in project.environment.reverse_dependencies().items() + } for result in installed_packages: dep = Requirement.from_line(str(result.as_requirement())) packages.update(dep.as_pipfile()) updated_packages = {} - lockfile = do_lock(clear=clear, pre=pre, write=False, pypi_mirror=pypi_mirror) + lockfile = do_lock(project, clear=clear, pre=pre, write=False, pypi_mirror=pypi_mirror) for section in ("develop", "default"): for package in lockfile[section]: try: @@ -1815,17 +1793,17 @@ def do_outdated(pypi_mirror=None, pre=False, clear=False): version = None if name_in_pipfile: version = get_version(project.packages[name_in_pipfile]) - reverse_deps = reverse_deps.get(name_in_pipfile) - if isinstance(reverse_deps, Mapping) and "required" in reverse_deps: - required = " {0} required".format(reverse_deps["required"]) + rdeps = reverse_deps.get(canonicalize_name(package)) + if isinstance(rdeps, Mapping) and "required" in rdeps: + required = " {} required".format(rdeps["required"]) if version: - pipfile_version_text = " ({0} set in Pipfile)".format(version) + pipfile_version_text = f" ({version} set in Pipfile)" else: pipfile_version_text = " (Unpinned in Pipfile)" click.echo( crayons.yellow( - "Skipped Update of Package {0!s}: {1!s} installed,{2!s}{3!s}, " - "{4!s} available.".format( + "Skipped Update of Package {!s}: {!s} installed,{!s}{!s}, " + "{!s} available.".format( package, old_version, required, pipfile_version_text, new_version ) ), err=True @@ -1835,7 +1813,7 @@ def do_outdated(pypi_mirror=None, pre=False, clear=False): sys.exit(0) for package, new_version, old_version in outdated: click.echo( - "Package {0!r} out-of-date: {1!r} installed, {2!r} available.".format( + "Package {!r} out-of-date: {!r} installed, {!r} available.".format( package, old_version, new_version ) ) @@ -1843,6 +1821,7 @@ def do_outdated(pypi_mirror=None, pre=False, clear=False): def do_install( + project, packages=False, editable_packages=False, index_url=False, @@ -1855,7 +1834,7 @@ def do_install( lock=True, ignore_pipfile=False, skip_lock=False, - requirements=False, + requirementstxt=False, sequential=False, pre=False, code=False, @@ -1864,7 +1843,6 @@ def do_install( selective_upgrade=False, site_packages=None, ): - from .environments import PIPENV_VIRTUALENV, PIPENV_USE_SYSTEM from .vendor.pip_shims.shims import PipError requirements_directory = vistir.path.create_tracked_tempdir( @@ -1878,11 +1856,12 @@ def do_install( package_args = [p for p in packages if p] + [p for p in editable_packages if p] skip_requirements = False # Don't search for requirements.txt files if the user provides one - if requirements or package_args or project.pipfile_exists: + if requirementstxt or package_args or project.pipfile_exists: skip_requirements = True concurrent = not sequential # Ensure that virtualenv is available and pipfile are available ensure_project( + project, three=three, python=python, system=system, @@ -1903,18 +1882,21 @@ def do_install( pre = project.settings.get("allow_prereleases") if not keep_outdated: keep_outdated = project.settings.get("keep_outdated") - remote = requirements and is_valid_url(requirements) + remote = requirementstxt and is_valid_url(requirementstxt) # Warn and exit if --system is used without a pipfile. - if (system and package_args) and not (PIPENV_VIRTUALENV): + if (system and package_args) and not project.s.PIPENV_VIRTUALENV: raise exceptions.SystemUsageError # Automatically use an activated virtualenv. - if PIPENV_USE_SYSTEM: + if project.s.PIPENV_USE_SYSTEM: system = True + if system: + project.s.PIPENV_USE_SYSTEM = True + os.environ["PIPENV_USE_SYSTEM"] = "1" # Check if the file is remote or not if remote: click.echo( crayons.normal( - fix_utf8("Remote requirements file provided! Downloading…"), bold=True + fix_utf8("Remote requirements file provided! Downloading..."), bold=True ), err=True, ) @@ -1922,17 +1904,17 @@ def do_install( prefix="pipenv-", suffix="-requirement.txt", dir=requirements_directory ) temp_reqs = fd.name - requirements_url = requirements + requirements_url = requirementstxt # Download requirements file try: - download_file(requirements, temp_reqs) - except IOError: + download_file(requirements_url, temp_reqs, project.s.PIPENV_MAX_RETRIES) + except OSError: fd.close() os.unlink(temp_reqs) click.echo( crayons.red( - u"Unable to find requirements file at {0}.".format( - crayons.normal(requirements) + "Unable to find requirements file at {}.".format( + crayons.normal(requirements_url) ) ), err=True, @@ -1941,29 +1923,29 @@ def do_install( finally: fd.close() # Replace the url with the temporary requirements file - requirements = temp_reqs + requirementstxt = temp_reqs remote = True - if requirements: + if requirementstxt: error, traceback = None, None click.echo( crayons.normal( - fix_utf8("Requirements file provided! Importing into Pipfile…"), bold=True + fix_utf8("Requirements file provided! Importing into Pipfile..."), bold=True ), err=True, ) try: - import_requirements(r=project.path_to(requirements), dev=dev) + import_requirements(project, r=project.path_to(requirementstxt), dev=dev) except (UnicodeDecodeError, PipError) as e: # Don't print the temp file path if remote since it will be deleted. - req_path = requirements_url if remote else project.path_to(requirements) + req_path = requirements_url if remote else project.path_to(requirementstxt) error = ( - u"Unexpected syntax in {0}. Are you sure this is a " + "Unexpected syntax in {}. Are you sure this is a " "requirements.txt style file?".format(req_path) ) traceback = e except AssertionError as e: error = ( - u"Requirements file doesn't appear to exist. Please ensure the file exists in your " + "Requirements file doesn't appear to exist. Please ensure the file exists in your " "project directory or you provided the correct path." ) traceback = e @@ -1974,18 +1956,18 @@ def do_install( os.remove(temp_reqs) if error and traceback: click.echo(crayons.red(error)) - click.echo(crayons.blue(str(traceback)), err=True) + click.echo(crayons.yellow(str(traceback)), err=True) sys.exit(1) if code: click.echo( - crayons.normal(fix_utf8("Discovering imports from local codebase…"), bold=True) + crayons.normal(fix_utf8("Discovering imports from local codebase..."), bold=True) ) for req in import_from_code(code): - click.echo(" Found {0}!".format(crayons.green(req))) + click.echo(f" Found {crayons.green(req)}!") project.add_package_to_pipfile(req) # Allow more than one package to be provided. package_args = [p for p in packages] + [ - "-e {0}".format(pkg) for pkg in editable_packages + f"-e {pkg}" for pkg in editable_packages ] # Support for --selective-upgrade. # We should do this part first to make sure that we actually do selectively upgrade @@ -2013,6 +1995,7 @@ def do_install( if pre: project.update_settings({"allow_prereleases": pre}) do_init( + project, dev=dev, allow_global=system, ignore_pipfile=ignore_pipfile, @@ -2031,9 +2014,10 @@ def do_install( from .vendor.requirementslib.models.requirements import Requirement # make a tuple of (display_name, entry) - pkg_list = packages + ['-e {0}'.format(pkg) for pkg in editable_packages] + pkg_list = packages + [f'-e {pkg}' for pkg in editable_packages] if not system and not project.virtualenv_exists: do_init( + project, dev=dev, system=system, allow_global=system, @@ -2048,32 +2032,31 @@ def do_install( for pkg_line in pkg_list: click.echo( crayons.normal( - fix_utf8("Installing {0}…".format(crayons.green(pkg_line, bold=True))), + fix_utf8(f"Installing {crayons.green(pkg_line, bold=True)}..."), bold=True, ) ) # pip install: - with vistir.contextmanagers.temp_environ(), create_spinner("Installing...") as sp: + with vistir.contextmanagers.temp_environ(), create_spinner("Installing...", project.s) as sp: if not system: os.environ["PIP_USER"] = vistir.compat.fs_str("0") if "PYTHONHOME" in os.environ: del os.environ["PYTHONHOME"] - sp.text = "Resolving {0}...".format(pkg_line) + sp.text = f"Resolving {pkg_line}..." try: pkg_requirement = Requirement.from_line(pkg_line) except ValueError as e: - sp.write_err(vistir.compat.fs_str("{0}: {1}".format(crayons.red("WARNING"), e))) + sp.write_err(vistir.compat.fs_str("{}: {}".format(crayons.red("WARNING"), e))) sp.red.fail(environments.PIPENV_SPINNER_FAIL_TEXT.format("Installation Failed")) sys.exit(1) - if index_url: - pkg_requirement.index = index_url no_deps = False sp.text = "Installing..." try: - sp.text = "Installing {0}...".format(pkg_requirement.name) - if environments.is_verbose(): - sp.hide_and_write("Installing package: {0}".format(pkg_requirement.as_line(include_hashes=False))) + sp.text = f"Installing {pkg_requirement.name}..." + if project.s.is_verbose(): + sp.hide_and_write(f"Installing package: {pkg_requirement.as_line(include_hashes=False)}") c = pip_install( + project, pkg_requirement, ignore_hashes=True, allow_global=system, @@ -2085,21 +2068,21 @@ def do_install( extra_indexes=extra_index_url, pypi_mirror=pypi_mirror, ) - if not c.ok: + if c.returncode: sp.write_err( - u"{0} An error occurred while installing {1}!".format( - crayons.red(u"Error: ", bold=True), crayons.green(pkg_line) + "{} An error occurred while installing {}!".format( + crayons.red("Error: ", bold=True), crayons.green(pkg_line) ), ) sp.write_err( - vistir.compat.fs_str(u"Error text: {0}".format(c.out)) + vistir.compat.fs_str(f"Error text: {c.stdout}") ) - sp.write_err(crayons.blue(vistir.compat.fs_str(format_pip_error(c.err)))) - if environments.is_verbose(): - sp.write_err(crayons.blue(vistir.compat.fs_str(format_pip_output(c.out)))) - if "setup.py egg_info" in c.err: + sp.write_err(crayons.cyan(vistir.compat.fs_str(format_pip_error(c.stderr)))) + if project.s.is_verbose(): + sp.write_err(crayons.cyan(vistir.compat.fs_str(format_pip_output(c.stdout)))) + if "setup.py egg_info" in c.stderr: sp.write_err(vistir.compat.fs_str( - "This is likely caused by a bug in {0}. " + "This is likely caused by a bug in {}. " "Report this to its maintainers.".format( crayons.green(pkg_requirement.name) ) @@ -2108,39 +2091,46 @@ def do_install( sys.exit(1) except (ValueError, RuntimeError) as e: sp.write_err(vistir.compat.fs_str( - "{0}: {1}".format(crayons.red("WARNING"), e), + "{}: {}".format(crayons.red("WARNING"), e), )) sp.red.fail(environments.PIPENV_SPINNER_FAIL_TEXT.format( "Installation Failed", )) sys.exit(1) # Warn if --editable wasn't passed. - if pkg_requirement.is_vcs and not pkg_requirement.editable and not PIPENV_RESOLVE_VCS: + if pkg_requirement.is_vcs and not pkg_requirement.editable and not project.s.PIPENV_RESOLVE_VCS: sp.write_err( - "{0}: You installed a VCS dependency in non-editable mode. " - "This will work fine, but sub-dependencies will not be resolved by {1}." + "{}: You installed a VCS dependency in non-editable mode. " + "This will work fine, but sub-dependencies will not be resolved by {}." "\n To enable this sub-dependency functionality, specify that this dependency is editable." "".format( crayons.red("Warning", bold=True), - crayons.red("$ pipenv lock"), + crayons.yellow("$ pipenv lock"), ) ) sp.write(vistir.compat.fs_str( - u"{0} {1} {2} {3}{4}".format( - crayons.normal(u"Adding", bold=True), - crayons.green(u"{0}".format(pkg_requirement.name), bold=True), - crayons.normal(u"to Pipfile's", bold=True), - crayons.red(u"[dev-packages]" if dev else u"[packages]", bold=True), - crayons.normal(fix_utf8("…"), bold=True), + "{} {} {} {}{}".format( + crayons.normal("Adding", bold=True), + crayons.green(f"{pkg_requirement.name}", bold=True), + crayons.normal("to Pipfile's", bold=True), + crayons.yellow("[dev-packages]" if dev else "[packages]", bold=True), + crayons.normal(fix_utf8("..."), bold=True), ) )) # Add the package to the Pipfile. + indexes = list(filter(None, [index_url, *extra_index_url])) + for index in indexes: + index_name = project.add_index_to_pipfile( + index, verify_ssl=index.startswith("https:") + ) + if index_url and not extra_index_url: + pkg_requirement.index = index_name try: project.add_package_to_pipfile(pkg_requirement, dev) except ValueError: import traceback sp.write_err( - "{0} {1}".format( + "{} {}".format( crayons.red("Error:", bold=True), traceback.format_exc() ) ) @@ -2154,6 +2144,7 @@ def do_install( if pip_shims_module: os.environ["PIP_SHIMS_BASE_MODULE"] = pip_shims_module do_init( + project, dev=dev, system=system, allow_global=system, @@ -2168,6 +2159,7 @@ def do_install( def do_uninstall( + project, packages=False, editable_packages=False, three=None, @@ -2180,28 +2172,24 @@ def do_uninstall( pypi_mirror=None, ctx=None ): - from .environments import PIPENV_USE_SYSTEM - from .vendor.requirementslib.models.requirements import Requirement from .vendor.packaging.utils import canonicalize_name + from .vendor.requirementslib.models.requirements import Requirement # Automatically use an activated virtualenv. - if PIPENV_USE_SYSTEM: + if project.s.PIPENV_USE_SYSTEM: system = True # Ensure that virtualenv is available. # TODO: We probably shouldn't ensure a project exists if the outcome will be to just # install things in order to remove them... maybe tell the user to install first? - ensure_project(three=three, python=python, pypi_mirror=pypi_mirror) + ensure_project(project, three=three, python=python, pypi_mirror=pypi_mirror) # Un-install all dependencies, if --all was provided. if not any([packages, editable_packages, all_dev, all]): - raise exceptions.MissingParameter( - crayons.red("No package provided!"), - ctx=ctx, param_type="parameter", - ) + raise exceptions.PipenvUsageError("No package provided!", ctx=ctx) editable_pkgs = [ - Requirement.from_line("-e {0}".format(p)).name for p in editable_packages if p + Requirement.from_line(f"-e {p}").name for p in editable_packages if p ] - packages = packages + editable_pkgs - package_names = [p for p in packages if p] + packages += editable_pkgs + package_names = {p for p in packages if p} package_map = { canonicalize_name(p): p for p in packages if p } @@ -2218,45 +2206,40 @@ def do_uninstall( if "dev-packages" not in project.parsed_pipfile and not project_pkg_names["dev"]: click.echo( crayons.normal( - "No {0} to uninstall.".format(crayons.red("[dev-packages]")), + "No {} to uninstall.".format(crayons.yellow("[dev-packages]")), bold=True, ) ) return click.echo( crayons.normal( - fix_utf8("Un-installing {0}…".format(crayons.red("[dev-packages]"))), bold=True + fix_utf8("Un-installing {}...".format(crayons.yellow("[dev-packages]"))), bold=True ) ) - package_names = project_pkg_names["dev"] + package_names = set(project_pkg_names["dev"]) - set(project_pkg_names["default"]) # Remove known "bad packages" from the list. bad_pkgs = get_canonical_names(BAD_PACKAGES) ignored_packages = bad_pkgs & set(list(package_map.keys())) for ignored_pkg in ignored_packages: - if environments.is_verbose(): - click.echo("Ignoring {0}.".format(ignored_pkg), err=True) - pkg_name_index = package_names.index(package_map[ignored_pkg]) - del package_names[pkg_name_index] + if project.s.is_verbose(): + click.echo(f"Ignoring {ignored_pkg}.", err=True) + package_names.discard(package_map[ignored_pkg]) used_packages = project_pkg_names["combined"] & installed_package_names failure = False - packages_to_remove = set() if all: click.echo( crayons.normal( - fix_utf8("Un-installing all {0} and {1}…".format( - crayons.red("[dev-packages]"), - crayons.red("[packages]"), + fix_utf8("Un-installing all {} and {}...".format( + crayons.yellow("[dev-packages]"), + crayons.yellow("[packages]"), )), bold=True ) ) - do_purge(bare=False, allow_global=system) + do_purge(project, bare=False, allow_global=system) sys.exit(0) - if all_dev: - package_names = project_pkg_names["dev"] - else: - package_names = set([pkg_name for pkg_name in package_names]) + selected_pkg_map = { canonicalize_name(p): p for p in package_names } @@ -2267,19 +2250,19 @@ def do_uninstall( pip_path = None for normalized, package_name in selected_pkg_map.items(): click.echo( - crayons.white( - fix_utf8("Uninstalling {0}…".format(package_name)), bold=True + crayons.normal( + fix_utf8(f"Uninstalling {crayons.green(package_name)}..."), bold=True ) ) # Uninstall the package. if package_name in packages_to_remove: with project.environment.activated(): if pip_path is None: - pip_path = which_pip(allow_global=system) + pip_path = which_pip(project, allow_global=system) cmd = [pip_path, "uninstall", package_name, "-y"] - c = run_command(cmd) - click.echo(crayons.blue(c.out)) - if c.return_code != 0: + c = run_command(cmd, is_verbose=project.s.is_verbose()) + click.echo(crayons.cyan(c.stdout)) + if c.returncode != 0: failure = True if not failure and pipfile_remove: in_packages = project.get_package_name_in_pipfile(package_name, dev=False) @@ -2287,11 +2270,11 @@ def do_uninstall( package_name, dev=True ) if normalized in lockfile_packages: - click.echo("{0} {1} {2} {3}".format( - crayons.blue("Removing"), + click.echo("{} {} {} {}".format( + crayons.cyan("Removing"), crayons.green(package_name), - crayons.blue("from"), - crayons.white(fix_utf8("Pipfile.lock…"))) + crayons.cyan("from"), + crayons.white(fix_utf8("Pipfile.lock..."))) ) lockfile = project.get_or_create_lockfile() if normalized in lockfile.default: @@ -2303,14 +2286,14 @@ def do_uninstall( if normalized in lockfile_packages: continue click.echo( - "No package {0} to remove from Pipfile.".format( + "No package {} to remove from Pipfile.".format( crayons.green(package_name) ) ) continue click.echo( - fix_utf8("Removing {0} from Pipfile…".format(crayons.green(package_name))) + fix_utf8(f"Removing {crayons.green(package_name)} from Pipfile...") ) # Remove package from both packages and dev-packages. if in_dev_packages: @@ -2318,24 +2301,24 @@ def do_uninstall( if in_packages: project.remove_package_from_pipfile(package_name, dev=False) if lock: - do_lock(system=system, keep_outdated=keep_outdated, pypi_mirror=pypi_mirror) + do_lock(project, system=system, keep_outdated=keep_outdated, pypi_mirror=pypi_mirror) sys.exit(int(failure)) -def do_shell(three=None, python=False, fancy=False, shell_args=None, pypi_mirror=None): +def do_shell(project, three=None, python=False, fancy=False, shell_args=None, pypi_mirror=None): # Ensure that virtualenv is available. ensure_project( - three=three, python=python, validate=False, pypi_mirror=pypi_mirror, + project, three=three, python=python, validate=False, pypi_mirror=pypi_mirror, ) # Support shell compatibility mode. - if PIPENV_SHELL_FANCY: + if project.s.PIPENV_SHELL_FANCY: fancy = True from .shells import choose_shell - shell = choose_shell() - click.echo(fix_utf8("Launching subshell in virtual environment…"), err=True) + shell = choose_shell(project) + click.echo(fix_utf8("Launching subshell in virtual environment..."), err=True) fork_args = ( project.virtualenv_location, @@ -2359,15 +2342,15 @@ def do_shell(three=None, python=False, fancy=False, shell_args=None, pypi_mirror except (AttributeError, ImportError): click.echo(fix_utf8( "Compatibility mode not supported. " - "Trying to continue as well-configured shell…"), + "Trying to continue as well-configured shell..."), err=True, ) shell.fork(*fork_args) -def _inline_activate_virtualenv(): +def _inline_activate_virtualenv(project): try: - activate_this = which("activate_this.py") + activate_this = project._which("activate_this.py") if not activate_this or not os.path.exists(activate_this): raise exceptions.VirtualenvActivationException() with open(activate_this) as f: @@ -2376,15 +2359,15 @@ def _inline_activate_virtualenv(): # Catch all errors, just in case. except Exception: click.echo( - u"{0}: There was an unexpected error while activating your " - u"virtualenv. Continuing anyway...".format( + "{}: There was an unexpected error while activating your " + "virtualenv. Continuing anyway...".format( crayons.red("Warning", bold=True) ), err=True, ) -def _inline_activate_venv(): +def _inline_activate_venv(project): """Built-in venv doesn't have activate_this.py, but doesn't need it anyway. As long as we find the correct executable, built-in venv sets up the @@ -2402,21 +2385,24 @@ def _inline_activate_venv(): os.environ["PATH"] = os.pathsep.join(components) -def inline_activate_virtual_environment(): +def inline_activate_virtual_environment(project): root = project.virtualenv_location if os.path.exists(os.path.join(root, "pyvenv.cfg")): - _inline_activate_venv() + _inline_activate_venv(project) else: - _inline_activate_virtualenv() + _inline_activate_virtualenv(project) if "VIRTUAL_ENV" not in os.environ: os.environ["VIRTUAL_ENV"] = vistir.misc.fs_str(root) -def _launch_windows_subprocess(script): +def _launch_windows_subprocess(script, env): import subprocess - command = system_which(script.command) - options = {"universal_newlines": True} + path = env.get("PATH", "") + command = system_which(script.command, path=path) + + options = {"universal_newlines": True, "env": env} + script.cmd_args[1:] = [expandvars(arg) for arg in script.args] # Command not found, maybe this is a shell built-in? if not command: @@ -2427,7 +2413,7 @@ def _launch_windows_subprocess(script): # a "command" that is non-executable. See pypa/pipenv#2727. try: return subprocess.Popen([command] + script.args, **options) - except WindowsError as e: + except OSError as e: if e.winerror != 193: raise @@ -2435,21 +2421,22 @@ def _launch_windows_subprocess(script): return subprocess.Popen(script.cmdify(), shell=True, **options) -def do_run_nt(script): - p = _launch_windows_subprocess(script) +def do_run_nt(project, script, env): + p = _launch_windows_subprocess(script, env) p.communicate() sys.exit(p.returncode) -def do_run_posix(script, command): - command_path = system_which(script.command) +def do_run_posix(project, script, command, env): + path = env.get("PATH") + command_path = system_which(script.command, path=path) if not command_path: if project.has_script(command): click.echo( - "{0}: the command {1} (from {2}) could not be found within {3}." + "{}: the command {} (from {}) could not be found within {}." "".format( crayons.red("Error", bold=True), - crayons.red(script.command), + crayons.yellow(script.command), crayons.normal(command, bold=True), crayons.normal("PATH", bold=True), ), @@ -2457,22 +2444,24 @@ def do_run_posix(script, command): ) else: click.echo( - "{0}: the command {1} could not be found within {2} or Pipfile's {3}." + "{}: the command {} could not be found within {} or Pipfile's {}." "".format( crayons.red("Error", bold=True), - crayons.red(command), + crayons.yellow(command), crayons.normal("PATH", bold=True), crayons.normal("[scripts]", bold=True), ), err=True, ) sys.exit(1) - os.execl( - command_path, command_path, *[os.path.expandvars(arg) for arg in script.args] + os.execve( + command_path, + [command_path, *(os.path.expandvars(arg) for arg in script.args)], + env ) -def do_run(command, args, three=None, python=False, pypi_mirror=None): +def do_run(project, command, args, three=None, python=False, pypi_mirror=None): """Attempt to run command either pulling from project or interpreting as executable. Args are appended to the command in [scripts] section of project if found. @@ -2481,64 +2470,68 @@ def do_run(command, args, three=None, python=False, pypi_mirror=None): # Ensure that virtualenv is available. ensure_project( - three=three, python=python, validate=False, pypi_mirror=pypi_mirror, + project, three=three, python=python, validate=False, pypi_mirror=pypi_mirror, ) - load_dot_env() + env = os.environ.copy() + env.update(load_dot_env(project, as_dict=True) or {}) + env.pop("PIP_SHIMS_BASE_MODULE", None) - previous_pip_shims_module = os.environ.pop("PIP_SHIMS_BASE_MODULE", None) - - # Activate virtualenv under the current interpreter's environment - inline_activate_virtual_environment() + path = env.get('PATH', '') + if project.virtualenv_location: + new_path = os.path.join(project.virtualenv_location, 'Scripts' if os.name == 'nt' else 'bin') + paths = path.split(os.pathsep) + paths.insert(0, new_path) + path = os.pathsep.join(paths) + env["VIRTUAL_ENV"] = project.virtualenv_location + env["PATH"] = path # Set an environment variable, so we know we're in the environment. # Only set PIPENV_ACTIVE after finishing reading virtualenv_location # such as in inline_activate_virtual_environment # otherwise its value will be changed - previous_pipenv_active_value = os.environ.get("PIPENV_ACTIVE") - os.environ["PIPENV_ACTIVE"] = vistir.misc.fs_str("1") - - os.environ.pop("PIP_SHIMS_BASE_MODULE", None) + env["PIPENV_ACTIVE"] = vistir.misc.fs_str("1") + env.pop("PIP_SHIMS_BASE_MODULE", None) try: script = project.build_script(command, args) - cmd_string = ' '.join([script.command] + script.args) - if environments.is_verbose(): - click.echo(crayons.normal("$ {0}".format(cmd_string)), err=True) + cmd_string = cmd_list_to_shell([script.command] + script.args) + if project.s.is_verbose(): + click.echo(crayons.normal(f"$ {cmd_string}"), err=True) except ScriptEmptyError: click.echo("Can't run script {0!r}-it's empty?", err=True) - run_args = [script] - run_kwargs = {} - if os.name == "nt": + run_args = [project, script] + run_kwargs = {'env': env} + # We're using `do_run_nt` on CI (even if we're running on a non-nt machine) + # as a workaround for https://github.com/pypa/pipenv/issues/4909. + if os.name == "nt" or environments.PIPENV_IS_CI: run_fn = do_run_nt else: run_fn = do_run_posix - run_kwargs = {"command": command} - try: - run_fn(*run_args, **run_kwargs) - finally: - os.environ.pop("PIPENV_ACTIVE", None) - if previous_pipenv_active_value is not None: - os.environ["PIPENV_ACTIVE"] = previous_pipenv_active_value - if previous_pip_shims_module is not None: - os.environ["PIP_SHIMS_BASE_MODULE"] = previous_pip_shims_module + run_kwargs.update({"command": command}) + run_fn(*run_args, **run_kwargs) def do_check( + project, three=None, python=False, system=False, unused=False, + db=None, ignore=None, + output="default", + key=None, + quiet=False, args=None, - pypi_mirror=None, + pypi_mirror=None ): from pipenv.vendor.vistir.compat import JSONDecodeError - from pipenv.vendor.first import first if not system: # Ensure that virtualenv is available. ensure_project( + project, three=three, python=python, validate=False, @@ -2556,40 +2549,43 @@ def do_check( except ValueError: pass if deps_required: - click.echo( - crayons.normal( - "The following dependencies appear unused, and may be safe for removal:" + if not quiet and not project.s.is_quiet(): + click.echo( + crayons.normal( + "The following dependencies appear unused, and may be safe for removal:" + ) ) - ) - for dep in deps_required: - click.echo(" - {0}".format(crayons.green(dep))) - sys.exit(1) + for dep in deps_required: + click.echo(f" - {crayons.green(dep)}") + sys.exit(1) else: sys.exit(0) - click.echo(crayons.normal(decode_for_output("Checking PEP 508 requirements…"), bold=True)) + if not quiet and not project.s.is_quiet(): + click.echo(crayons.normal(decode_for_output("Checking PEP 508 requirements..."), bold=True)) pep508checker_path = pep508checker.__file__.rstrip("cdo") safety_path = os.path.join( - os.path.dirname(os.path.abspath(__file__)), "patched", "safety.zip" + os.path.dirname(os.path.abspath(__file__)), "patched", "safety" ) if not system: - python = which("python") + python = project._which("python") else: - python = first(system_which(p) for p in ("python", "python3", "python2")) + interpreters = [system_which(p) for p in ("python", "python3", "python2")] + python = interpreters[0] if interpreters else None if not python: click.echo(crayons.red("The Python interpreter can't be found."), err=True) sys.exit(1) - _cmd = [vistir.compat.Path(python).as_posix()] + _cmd = [Path(python).as_posix()] # Run the PEP 508 checker in the virtualenv. - cmd = _cmd + [vistir.compat.Path(pep508checker_path).as_posix()] - c = run_command(cmd) - if c.return_code is not None: + cmd = _cmd + [Path(pep508checker_path).as_posix()] + c = run_command(cmd, is_verbose=project.s.is_verbose()) + if c.returncode is not None: try: - results = simplejson.loads(c.out.strip()) + results = simplejson.loads(c.stdout.strip()) except JSONDecodeError: - click.echo("{0}\n{1}\n{2}".format( + click.echo("{}\n{}\n{}".format( crayons.white(decode_for_output("Failed parsing pep508 results: "), bold=True), - c.out.strip(), - c.err.strip() + c.stdout.strip(), + c.stderr.strip() )) sys.exit(1) # Load the pipfile. @@ -2603,11 +2599,11 @@ def do_check( except AssertionError: failed = True click.echo( - "Specifier {0} does not match {1} ({2})." + "Specifier {} does not match {} ({})." "".format( crayons.green(marker), - crayons.blue(specifier), - crayons.red(results[marker]), + crayons.cyan(specifier), + crayons.yellow(results[marker]), ), err=True, ) @@ -2615,64 +2611,81 @@ def do_check( click.echo(crayons.red("Failed!"), err=True) sys.exit(1) else: - click.echo(crayons.green("Passed!")) - click.echo(crayons.normal( - decode_for_output("Checking installed package safety…"), bold=True) - ) + if not quiet and not project.s.is_quiet(): + click.echo(crayons.green("Passed!")) + if not quiet and not project.s.is_quiet(): + click.echo(crayons.normal( + decode_for_output("Checking installed package safety..."), bold=True) + ) if ignore: if not isinstance(ignore, (tuple, list)): ignore = [ignore] ignored = [["--ignore", cve] for cve in ignore] - click.echo( - crayons.normal( - "Notice: Ignoring CVE(s) {0}".format(crayons.yellow(", ".join(ignore))) - ), - err=True, - ) + if not quiet and not project.s.is_quiet(): + click.echo( + crayons.normal( + "Notice: Ignoring CVE(s) {}".format(crayons.yellow(", ".join(ignore))) + ), + err=True, + ) else: - ignored = "" - key = "--key={0}".format(PIPENV_PYUP_API_KEY) - cmd = _cmd + [safety_path, "check", "--json", key] + ignored = [] + + switch = output + if output == "default": + switch = "json" + + cmd = _cmd + [safety_path, "check", f"--{switch}"] + if db: + if not quiet and not project.s.is_quiet(): + click.echo(crayons.normal(f"Using local database {db}")) + cmd.append(f"--db={db}") + elif key or project.s.PIPENV_PYUP_API_KEY: + cmd = cmd + [f"--key={key or project.s.PIPENV_PYUP_API_KEY}"] if ignored: for cve in ignored: cmd += cve - c = run_command(cmd, catch_exceptions=False) - try: - results = simplejson.loads(c.out) - except (ValueError, JSONDecodeError): - raise exceptions.JSONParseError(c.out, c.err) - except Exception: - raise exceptions.PipenvCmdError(c.cmd, c.out, c.err, c.return_code) - if c.ok: - click.echo(crayons.green("All good!")) - sys.exit(0) - for (package, resolved, installed, description, vuln) in results: - click.echo( - "{0}: {1} {2} resolved ({3} installed)!".format( - crayons.normal(vuln, bold=True), - crayons.green(package), - crayons.red(resolved, bold=False), - crayons.red(installed, bold=True), + c = run_command(cmd, catch_exceptions=False, is_verbose=project.s.is_verbose()) + if output == "default": + try: + results = simplejson.loads(c.stdout) + except (ValueError, JSONDecodeError): + raise exceptions.JSONParseError(c.stdout, c.stderr) + except Exception: + raise exceptions.PipenvCmdError(cmd_list_to_shell(c.args), c.stdout, c.stderr, c.returncode) + for (package, resolved, installed, description, vuln, *_) in results: + click.echo( + "{}: {} {} resolved ({} installed)!".format( + crayons.normal(vuln, bold=True), + crayons.green(package), + crayons.yellow(resolved, bold=False), + crayons.yellow(installed, bold=True), + ) ) - ) - click.echo("{0}".format(description)) - click.echo() + click.echo(f"{description}") + click.echo() + if c.returncode == 0: + click.echo(crayons.green("All good!")) + sys.exit(0) + else: + sys.exit(1) else: - sys.exit(1) + click.echo(c.stdout) + sys.exit(c.returncode) -def do_graph(bare=False, json=False, json_tree=False, reverse=False): +def do_graph(project, bare=False, json=False, json_tree=False, reverse=False): + from pipenv.vendor import pipdeptree from pipenv.vendor.vistir.compat import JSONDecodeError - import pipdeptree pipdeptree_path = pipdeptree.__file__.rstrip("cdo") try: - python_path = which("python") + python_path = project._which("python") except AttributeError: click.echo( - u"{0}: {1}".format( + "{}: {}".format( crayons.red("Warning", bold=True), - u"Unable to display currently-installed dependency graph information here. " - u"Please run within a Pipenv project.", + "Unable to display currently-installed dependency graph information here. " + "Please run within a Pipenv project.", ), err=True, ) @@ -2680,35 +2693,36 @@ def do_graph(bare=False, json=False, json_tree=False, reverse=False): except RuntimeError: pass else: - python_path = vistir.compat.Path(python_path).as_posix() - pipdeptree_path = vistir.compat.Path(pipdeptree_path).as_posix() + if not os.name == 'nt': # bugfix #4388 + python_path = Path(python_path).as_posix() + pipdeptree_path = Path(pipdeptree_path).as_posix() if reverse and json: click.echo( - u"{0}: {1}".format( + "{}: {}".format( crayons.red("Warning", bold=True), - u"Using both --reverse and --json together is not supported. " - u"Please select one of the two options.", + "Using both --reverse and --json together is not supported. " + "Please select one of the two options.", ), err=True, ) sys.exit(1) if reverse and json_tree: click.echo( - u"{0}: {1}".format( + "{}: {}".format( crayons.red("Warning", bold=True), - u"Using both --reverse and --json-tree together is not supported. " - u"Please select one of the two options.", + "Using both --reverse and --json-tree together is not supported. " + "Please select one of the two options.", ), err=True, ) sys.exit(1) if json and json_tree: click.echo( - u"{0}: {1}".format( + "{}: {}".format( crayons.red("Warning", bold=True), - u"Using both --json and --json-tree together is not supported. " - u"Please select one of the two options.", + "Using both --json and --json-tree together is not supported. " + "Please select one of the two options.", ), err=True, ) @@ -2722,9 +2736,9 @@ def do_graph(bare=False, json=False, json_tree=False, reverse=False): flag = "--reverse" if not project.virtualenv_exists: click.echo( - u"{0}: No virtualenv has been created for this project yet! Consider " - u"running {1} first to automatically generate one for you or see " - u"{2} for further instructions.".format( + "{}: No virtualenv has been created for this project yet! Consider " + "running {} first to automatically generate one for you or see " + "{} for further instructions.".format( crayons.red("Warning", bold=True), crayons.green("`pipenv install`"), crayons.green("`pipenv install --help`"), @@ -2732,16 +2746,18 @@ def do_graph(bare=False, json=False, json_tree=False, reverse=False): err=True, ) sys.exit(1) - cmd_args = [python_path, pipdeptree_path, flag, "-l"] - c = run_command(cmd_args) + cmd_args = [python_path, pipdeptree_path, "-l"] + if flag: + cmd_args.append(flag) + c = run_command(cmd_args, is_verbose=project.s.is_verbose()) # Run dep-tree. if not bare: if json: data = [] try: - parsed = simplejson.loads(c.out.strip()) + parsed = simplejson.loads(c.stdout.strip()) except JSONDecodeError: - raise exceptions.JSONParseError(c.out, c.err) + raise exceptions.JSONParseError(c.stdout, c.stderr) else: for d in parsed: if d["package"]["key"] not in BAD_PACKAGES: @@ -2762,15 +2778,15 @@ def do_graph(bare=False, json=False, json_tree=False, reverse=False): return obj try: - parsed = simplejson.loads(c.out.strip()) + parsed = simplejson.loads(c.stdout.strip()) except JSONDecodeError: - raise exceptions.JSONParseError(c.out, c.err) + raise exceptions.JSONParseError(c.stdout, c.stderr) else: data = traverse(parsed) click.echo(simplejson.dumps(data, indent=4)) sys.exit(0) else: - for line in c.out.strip().split("\n"): + for line in c.stdout.strip().split("\n"): # Ignore bad packages as top level. # TODO: This should probably be a "==" in + line.partition if line.split("==")[0] in BAD_PACKAGES and not reverse: @@ -2783,21 +2799,21 @@ def do_graph(bare=False, json=False, json_tree=False, reverse=False): else: click.echo(crayons.normal(line, bold=False)) else: - click.echo(c.out) - if c.return_code != 0: + click.echo(c.stdout) + if c.returncode != 0: click.echo( - "{0} {1}".format( + "{} {}".format( crayons.red("ERROR: ", bold=True), - crayons.white("{0}".format(c.err, bold=True)), + crayons.white(f"{c.stderr}"), ), err=True, ) # Return its return code. - sys.exit(c.return_code) + sys.exit(c.returncode) def do_sync( - ctx, + project, dev=False, three=None, python=None, @@ -2817,19 +2833,27 @@ def do_sync( # Ensure that virtualenv is available if not system. ensure_project( + project, three=three, python=python, validate=False, + system=system, deploy=deploy, pypi_mirror=pypi_mirror, + clear=clear, ) # Install everything. requirements_dir = vistir.path.create_tracked_tempdir( suffix="-requirements", prefix="pipenv-" ) + if system: + project.s.PIPENV_USE_SYSTEM = True + os.environ["PIPENV_USE_SYSTEM"] = "1" do_init( + project, dev=dev, + allow_global=system, concurrent=(not sequential), requirements_dir=requirements_dir, ignore_pipfile=True, # Don't check if Pipfile and lock match. @@ -2842,21 +2866,21 @@ def do_sync( def do_clean( - ctx, three=None, python=None, dry_run=False, bare=False, pypi_mirror=None, + project, three=None, python=None, dry_run=False, bare=False, pypi_mirror=None, system=False ): # Ensure that virtualenv is available. from packaging.utils import canonicalize_name - ensure_project(three=three, python=python, validate=False, pypi_mirror=pypi_mirror) - ensure_lockfile(pypi_mirror=pypi_mirror) + ensure_project(project, three=three, python=python, validate=False, pypi_mirror=pypi_mirror) + ensure_lockfile(project, pypi_mirror=pypi_mirror) # Make sure that the virtualenv's site packages are configured correctly # otherwise we may end up removing from the global site packages directory installed_package_names = project.installed_package_names.copy() # Remove known "bad packages" from the list. for bad_package in BAD_PACKAGES: if canonicalize_name(bad_package) in installed_package_names: - if environments.is_verbose(): - click.echo("Ignoring {0}.".format(bad_package), err=True) + if project.s.is_verbose(): + click.echo(f"Ignoring {bad_package}.", err=True) installed_package_names.remove(canonicalize_name(bad_package)) # Intelligently detect if --dev should be used or not. locked_packages = { @@ -2866,7 +2890,7 @@ def do_clean( if used_package in installed_package_names: installed_package_names.remove(used_package) failure = False - cmd = [which_pip(allow_global=system), "uninstall", "-y", "-qq"] + cmd = [which_pip(project, allow_global=system), "uninstall", "-y", "-qq"] for apparent_bad_package in installed_package_names: if dry_run and not bare: click.echo(apparent_bad_package) @@ -2874,12 +2898,12 @@ def do_clean( if not bare: click.echo( crayons.white( - fix_utf8("Uninstalling {0}…".format(apparent_bad_package)), bold=True + fix_utf8(f"Uninstalling {apparent_bad_package}..."), bold=True ) ) # Uninstall the package. - cmd = [which_pip(), "uninstall", apparent_bad_package, "-y"] - c = run_command(cmd) - if c.return_code != 0: + cmd = [which_pip(project), "uninstall", apparent_bad_package, "-y"] + c = run_command(cmd, is_verbose=project.s.is_verbose()) + if c.returncode != 0: failure = True sys.exit(int(failure)) diff --git a/pipenv/environment.py b/pipenv/environment.py index b57a858a..4eee3ded 100644 --- a/pipenv/environment.py +++ b/pipenv/environment.py @@ -1,39 +1,59 @@ -# -*- coding=utf-8 -*- -from __future__ import absolute_import, print_function - import contextlib import importlib -import io +import itertools import json import operator import os import site import sys - +from pathlib import Path from sysconfig import get_paths, get_python_version -import itertools import pkg_resources -import six import pipenv -from .vendor.cached_property import cached_property -from .vendor import vistir +from pipenv.environments import is_type_checking +from pipenv.utils import make_posix, normalize_path, subprocess_run +from pipenv.vendor import vistir +from pipenv.vendor.cached_property import cached_property +from pipenv.vendor.packaging.utils import canonicalize_name -from .utils import normalize_path, make_posix +if is_type_checking(): + from types import ModuleType + from typing import ( + ContextManager, Dict, Generator, List, Optional, Set, Union + ) + + import pip_shims.shims + import tomlkit + + from pipenv.project import Project, TPipfile, TSource + from pipenv.vendor.packaging.version import Version BASE_WORKING_SET = pkg_resources.WorkingSet(sys.path) +# TODO: Unittests for this class -class Environment(object): - def __init__(self, prefix=None, is_venv=False, base_working_set=None, pipfile=None, - sources=None, project=None): - super(Environment, self).__init__() +class Environment: + def __init__( + self, + prefix=None, # type: Optional[str] + python=None, # type: Optional[str] + is_venv=False, # type: bool + base_working_set=None, # type: pkg_resources.WorkingSet + pipfile=None, # type: Optional[Union[tomlkit.toml_document.TOMLDocument, TPipfile]] + sources=None, # type: Optional[List[TSource]] + project=None # type: Optional[Project] + ): + super().__init__() self._modules = {'pkg_resources': pkg_resources, 'pipenv': pipenv} self.base_working_set = base_working_set if base_working_set else BASE_WORKING_SET prefix = normalize_path(prefix) + self._python = None + if python is not None: + self._python = Path(python).absolute().as_posix() self.is_venv = is_venv or prefix != normalize_path(sys.prefix) if not sources: sources = [] @@ -46,13 +66,14 @@ class Environment(object): self.pipfile = pipfile self.extra_dists = [] prefix = prefix if prefix else sys.prefix - self.prefix = vistir.compat.Path(prefix) + self.prefix = Path(prefix) self._base_paths = {} if self.is_venv: self._base_paths = self.get_paths() self.sys_paths = get_paths() def safe_import(self, name): + # type: (str) -> ModuleType """Helper utility for reimporting previously imported modules while inside the env""" module = None if name not in self._modules: @@ -65,17 +86,11 @@ class Environment(object): if dist: dist.activate() module = importlib.import_module(name) - if name in sys.modules: - try: - six.moves.reload_module(module) - six.moves.reload_module(sys.modules[name]) - except TypeError: - del sys.modules[name] - sys.modules[name] = self._modules[name] return module @classmethod def resolve_dist(cls, dist, working_set): + # type: (pkg_resources.Distribution, pkg_resources.WorkingSet) -> Set[pkg_resources.Distribution] """Given a local distribution and a working set, returns all dependencies from the set. :param dist: A single distribution to find the dependencies of @@ -90,36 +105,47 @@ class Environment(object): deps.add(dist) try: reqs = dist.requires() - except (AttributeError, OSError, IOError): # The METADATA file can't be found + # KeyError = limited metadata can be found + except (KeyError, AttributeError, OSError): # The METADATA file can't be found return deps for req in reqs: - dist = working_set.find(req) + try: + dist = working_set.find(req) + except pkg_resources.VersionConflict: + # https://github.com/pypa/pipenv/issues/4549 + # The requirement is already present with incompatible version. + continue deps |= cls.resolve_dist(dist, working_set) return deps def extend_dists(self, dist): + # type: (pkg_resources.Distribution) -> None extras = self.resolve_dist(dist, self.base_working_set) self.extra_dists.append(dist) if extras: self.extra_dists.extend(extras) def add_dist(self, dist_name): + # type: (str) -> None dist = pkg_resources.get_distribution(pkg_resources.Requirement(dist_name)) self.extend_dists(dist) @cached_property def python_version(self): + # type: () -> str with self.activated(): sysconfig = self.safe_import("sysconfig") py_version = sysconfig.get_python_version() return py_version def find_libdir(self): + # type: () -> Optional[Path] libdir = self.prefix / "lib" return next(iter(list(libdir.iterdir())), None) @property def python_info(self): + # type: () -> Dict[str, str] include_dir = self.prefix / "include" if not os.path.exists(include_dir): include_dirs = self.get_include_path() @@ -127,7 +153,7 @@ class Environment(object): include_path = include_dirs.get("include", include_dirs.get("platinclude")) if not include_path: return {} - include_dir = vistir.compat.Path(include_path) + include_dir = Path(include_path) python_path = next(iter(list(include_dir.iterdir())), None) if python_path and python_path.name.startswith("python"): python_version = python_path.name.replace("python", "") @@ -136,6 +162,7 @@ class Environment(object): return {} def _replace_parent_version(self, path, replace_version): + # type: (str, str) -> str if not os.path.exists(path): base, leaf = os.path.split(path) base, parent = os.path.split(base) @@ -147,6 +174,7 @@ class Environment(object): @cached_property def base_paths(self): + # type: () -> Dict[str, str] """ Returns the context appropriate paths for the environment. @@ -225,6 +253,7 @@ class Environment(object): @cached_property def script_basedir(self): + # type: () -> str """Path to the environment scripts dir""" prefix = make_posix(self.prefix.as_posix()) install_scheme = 'nt' if (os.name == 'nt') else 'posix_prefix' @@ -236,14 +265,22 @@ class Environment(object): @property def python(self): + # type: () -> str """Path to the environment python""" - py = vistir.compat.Path(self.script_basedir).joinpath("python").absolute().as_posix() + if self._python is not None: + return self._python + if os.name == "nt" and not self.is_venv: + py = Path(self.prefix).joinpath("python").absolute().as_posix() + else: + py = Path(self.script_basedir).joinpath("python").absolute().as_posix() if not py: - return vistir.compat.Path(sys.executable).as_posix() + py = Path(sys.executable).as_posix() + self._python = py return py @cached_property def sys_path(self): + # type: () -> List[str] """ The system path inside the environment @@ -252,7 +289,7 @@ class Environment(object): """ from .vendor.vistir.compat import JSONDecodeError - current_executable = vistir.compat.Path(sys.executable).as_posix() + current_executable = Path(sys.executable).as_posix() if not self.python or self.python == current_executable: return sys.path elif any([sys.prefix == self.prefix, not self.is_venv]): @@ -266,6 +303,7 @@ class Environment(object): return path def build_command(self, python_lib=False, python_inc=False, scripts=False, py_version=False): + # type: (bool, bool, bool, bool) -> str """Build the text for running a command in the given environment :param python_lib: Whether to include the python lib dir commands, defaults to False @@ -289,24 +327,25 @@ class Environment(object): sysconfig_line = "sysconfig.get_path('{0}')" if python_lib: for key, var, val in (("pure", "lib", "0"), ("plat", "lib", "1")): - dist_prefix = "{0}lib".format(key) + dist_prefix = f"{key}lib" # XXX: We need to get 'stdlib' or 'platstdlib' - sys_prefix = "{0}stdlib".format("" if key == "pure" else key) - pylib_lines.append("u'%s': u'{{0}}'.format(%s)" % (dist_prefix, distutils_line.format(var, val))) - pylib_lines.append("u'%s': u'{{0}}'.format(%s)" % (sys_prefix, sysconfig_line.format(sys_prefix))) + sys_prefix = "{}stdlib".format("" if key == "pure" else key) + pylib_lines.append(f"u'{dist_prefix}': u'{{{{0}}}}'.format({distutils_line.format(var, val)})") + pylib_lines.append(f"u'{sys_prefix}': u'{{{{0}}}}'.format({sysconfig_line.format(sys_prefix)})") if python_inc: for key, var, val in (("include", "inc", "0"), ("platinclude", "inc", "1")): - pylib_lines.append("u'%s': u'{{0}}'.format(%s)" % (key, distutils_line.format(var, val))) + pylib_lines.append(f"u'{key}': u'{{{{0}}}}'.format({distutils_line.format(var, val)})") lines = pylib_lines + pyinc_lines if scripts: lines.append("u'scripts': u'{{0}}'.format(%s)" % sysconfig_line.format("scripts")) if py_version: lines.append("u'py_version_short': u'{{0}}'.format(distutils.sysconfig.get_python_version()),") - lines_as_str = u",".join(lines) + lines_as_str = ",".join(lines) py_command = py_command % lines_as_str return py_command def get_paths(self): + # type: () -> Optional[Dict[str, str]] """ Get the paths for the environment by running a subcommand @@ -318,12 +357,10 @@ class Environment(object): tmpfile_path = make_posix(tmpfile.name) py_command = self.build_command(python_lib=True, python_inc=True, scripts=True, py_version=True) command = [self.python, "-c", py_command.format(tmpfile_path)] - c = vistir.misc.run( - command, return_object=True, block=True, nospin=True, write_to_stdout=False - ) + c = subprocess_run(command) if c.returncode == 0: paths = {} - with io.open(tmpfile_path, "r", encoding="utf-8") as fh: + with open(tmpfile_path, "r", encoding="utf-8") as fh: paths = json.load(fh) if "purelib" in paths: paths["libdir"] = paths["purelib"] = make_posix(paths["purelib"]) @@ -332,11 +369,12 @@ class Environment(object): paths[key] = make_posix(paths[key]) return paths else: - vistir.misc.echo("Failed to load paths: {0}".format(c.err), fg="yellow") - vistir.misc.echo("Output: {0}".format(c.out), fg="yellow") + vistir.misc.echo(f"Failed to load paths: {c.stderr}", fg="yellow") + vistir.misc.echo(f"Output: {c.stdout}", fg="yellow") return None def get_lib_paths(self): + # type: () -> Dict[str, str] """Get the include path for the environment :return: The python include path for the environment @@ -347,13 +385,11 @@ class Environment(object): tmpfile_path = make_posix(tmpfile.name) py_command = self.build_command(python_lib=True) command = [self.python, "-c", py_command.format(tmpfile_path)] - c = vistir.misc.run( - command, return_object=True, block=True, nospin=True, write_to_stdout=False - ) + c = subprocess_run(command) paths = None if c.returncode == 0: paths = {} - with io.open(tmpfile_path, "r", encoding="utf-8") as fh: + with open(tmpfile_path, "r", encoding="utf-8") as fh: paths = json.load(fh) if "purelib" in paths: paths["libdir"] = paths["purelib"] = make_posix(paths["purelib"]) @@ -362,8 +398,8 @@ class Environment(object): paths[key] = make_posix(paths[key]) return paths else: - vistir.misc.echo("Failed to load paths: {0}".format(c.err), fg="yellow") - vistir.misc.echo("Output: {0}".format(c.out), fg="yellow") + vistir.misc.echo(f"Failed to load paths: {c.stderr}", fg="yellow") + vistir.misc.echo(f"Output: {c.stdout}", fg="yellow") if not paths: if not self.prefix.joinpath("lib").exists(): return {} @@ -384,6 +420,7 @@ class Environment(object): return {} def get_include_path(self): + # type: () -> Optional[Dict[str, str]] """Get the include path for the environment :return: The python include path for the environment @@ -400,24 +437,23 @@ class Environment(object): "fh = io.open('{0}', 'w'); fh.write(value); fh.close()" ) command = [self.python, "-c", py_command.format(tmpfile_path)] - c = vistir.misc.run( - command, return_object=True, block=True, nospin=True, write_to_stdout=False - ) + c = subprocess_run(command) if c.returncode == 0: paths = [] - with io.open(tmpfile_path, "r", encoding="utf-8") as fh: + with open(tmpfile_path, "r", encoding="utf-8") as fh: paths = json.load(fh) for key in ("include", "platinclude"): if key in paths: paths[key] = make_posix(paths[key]) return paths else: - vistir.misc.echo("Failed to load paths: {0}".format(c.err), fg="yellow") - vistir.misc.echo("Output: {0}".format(c.out), fg="yellow") + vistir.misc.echo(f"Failed to load paths: {c.stderr}", fg="yellow") + vistir.misc.echo(f"Output: {c.stdout}", fg="yellow") return None @cached_property def sys_prefix(self): + # type: () -> str """ The prefix run inside the context of the environment @@ -426,12 +462,13 @@ class Environment(object): """ command = [self.python, "-c", "import sys; print(sys.prefix)"] - c = vistir.misc.run(command, return_object=True, block=True, nospin=True, write_to_stdout=False) - sys_prefix = vistir.compat.Path(vistir.misc.to_text(c.out).strip()).as_posix() + c = subprocess_run(command) + sys_prefix = Path(c.stdout.strip()).as_posix() return sys_prefix @cached_property def paths(self): + # type: () -> Dict[str, str] paths = {} with vistir.contextmanagers.temp_environ(), vistir.contextmanagers.temp_path(): os.environ["PYTHONIOENCODING"] = vistir.compat.fs_str("utf-8") @@ -445,10 +482,12 @@ class Environment(object): @property def scripts_dir(self): + # type: () -> str return self.paths["scripts"] @property def libdir(self): + # type: () -> str purelib = self.paths.get("purelib", None) if purelib and os.path.exists(purelib): return "purelib", purelib @@ -456,6 +495,7 @@ class Environment(object): @property def pip_version(self): + # type: () -> Version """ Get the pip version in the environment. Useful for knowing which args we can use when installing. @@ -466,9 +506,33 @@ class Environment(object): ), None) if pip is not None: return parse_version(pip.version) - return parse_version("18.0") + return parse_version("20.2") + + def expand_egg_links(self): + # type: () -> None + """ + Expand paths specified in egg-link files to prevent pip errors during + reinstall + """ + prefixes = [ + Path(prefix) + for prefix in self.base_paths["libdirs"].split(os.pathsep) + if vistir.path.is_in_path(prefix, self.prefix.as_posix()) + ] + for loc in prefixes: + if not loc.exists(): + continue + for pth in loc.iterdir(): + if not pth.suffix == ".egg-link": + continue + contents = [ + vistir.path.normalize_path(line.strip()) + for line in pth.read_text().splitlines() + ] + pth.write_text("\n".join(contents)) def get_distributions(self): + # type: () -> Generator[pkg_resources.Distribution, None, None] """ Retrives the distributions installed on the library path of the environment @@ -476,16 +540,15 @@ class Environment(object): :rtype: iterator """ - pkg_resources = self.safe_import("pkg_resources") libdirs = self.base_paths["libdirs"].split(os.pathsep) dists = (pkg_resources.find_distributions(libdir) for libdir in libdirs) - for dist in itertools.chain.from_iterable(dists): - yield dist + yield from itertools.chain.from_iterable(dists) def find_egg(self, egg_dist): + # type: (pkg_resources.Distribution) -> str """Find an egg by name in the given environment""" site_packages = self.libdir[1] - search_filename = "{0}.egg-link".format(egg_dist.project_name) + search_filename = f"{egg_dist.project_name}.egg-link" try: user_site = site.getusersitepackages() except AttributeError: @@ -497,6 +560,7 @@ class Environment(object): return egg def locate_dist(self, dist): + # type: (pkg_resources.Distribution) -> str """Given a distribution, try to find a corresponding egg link first. If the egg - link doesn 't exist, return the supplied distribution.""" @@ -505,8 +569,9 @@ class Environment(object): return location or dist.location def dist_is_in_project(self, dist): + # type: (pkg_resources.Distribution) -> bool """Determine whether the supplied distribution is in the environment.""" - from .project import _normalized + from .environments import normalize_pipfile_path as _normalized prefixes = [ _normalized(prefix) for prefix in self.base_paths["libdirs"].split(os.pathsep) if _normalized(prefix).startswith(_normalized(self.prefix.as_posix())) @@ -518,6 +583,7 @@ class Environment(object): return any(location.startswith(prefix) for prefix in prefixes) def get_installed_packages(self): + # type: () -> List[pkg_resources.Distribution] """Returns all of the installed packages in a given environment""" workingset = self.get_working_set() packages = [ @@ -528,43 +594,21 @@ class Environment(object): @contextlib.contextmanager def get_finder(self, pre=False): - from .vendor.pip_shims.shims import ( - Command, cmdoptions, index_group, PackageFinder, parse_version, pip_version - ) - from .environments import PIPENV_CACHE_DIR - index_urls = [source.get("url") for source in self.sources] + # type: (bool) -> ContextManager[pip_shims.shims.PackageFinder] + from .vendor.pip_shims.shims import InstallCommand, get_package_finder - class PipCommand(Command): - name = "PipCommand" - - pip_command = PipCommand() - index_opts = cmdoptions.make_option_group( - index_group, pip_command.parser - ) - cmd_opts = pip_command.cmd_opts - pip_command.parser.insert_option_group(0, index_opts) - pip_command.parser.insert_option_group(0, cmd_opts) + pip_command = InstallCommand() pip_args = self._modules["pipenv"].utils.prepare_pip_source_args(self.sources) pip_options, _ = pip_command.parser.parse_args(pip_args) - pip_options.cache_dir = PIPENV_CACHE_DIR + pip_options.cache_dir = self.project.s.PIPENV_CACHE_DIR pip_options.pre = self.pipfile.get("pre", pre) with pip_command._build_session(pip_options) as session: - finder_args = { - "find_links": pip_options.find_links, - "index_urls": index_urls, - "allow_all_prereleases": pip_options.pre, - "trusted_hosts": pip_options.trusted_hosts, - "session": session - } - if parse_version(pip_version) < parse_version("19.0"): - finder_args.update( - {"process_dependency_links": pip_options.process_dependency_links} - ) - finder = PackageFinder(**finder_args) + finder = get_package_finder(install_cmd=pip_command, options=pip_options, session=session) yield finder def get_package_info(self, pre=False): - from .vendor.pip_shims.shims import pip_version, parse_version + # type: (bool) -> Generator[pkg_resources.Distribution, None, None] + from .vendor.pip_shims.shims import parse_version, pip_version dependency_links = [] packages = self.get_installed_packages() # This code is borrowed from pip's current implementation @@ -591,9 +635,10 @@ class Environment(object): if not all_candidates: continue - best_candidate = max(all_candidates, key=finder._candidate_sort_key) - remote_version = best_candidate.version - if best_candidate.location.is_wheel: + candidate_evaluator = finder.make_candidate_evaluator(project_name=dist.key) + best_candidate_result = candidate_evaluator.compute_best_candidate(all_candidates) + remote_version = best_candidate_result.best_candidate.version + if best_candidate_result.best_candidate.link.is_wheel: typ = 'wheel' else: typ = 'sdist' @@ -603,6 +648,7 @@ class Environment(object): yield dist def get_outdated_packages(self, pre=False): + # type: (bool) -> List[pkg_resources.Distribution] return [ pkg for pkg in self.get_package_info(pre=pre) if pkg.latest_version._key > pkg.parsed_version._key @@ -631,18 +677,19 @@ class Environment(object): return d def get_package_requirements(self, pkg=None): - from .vendor.pipdeptree import flatten, sorted_tree, build_dist_index, construct_tree + from .vendor.pipdeptree import PackageDAG, flatten + packages = self.get_installed_packages() if pkg: packages = [p for p in packages if p.key == pkg] - dist_index = build_dist_index(packages) - tree = sorted_tree(construct_tree(dist_index)) - branch_keys = set(r.key for r in flatten(tree.values())) + + tree = PackageDAG.from_pkgs(packages).sort() + branch_keys = {r.key for r in flatten(tree.values())} if pkg is not None: nodes = [p for p in tree.keys() if p.key == pkg] else: nodes = [p for p in tree.keys() if p.key not in branch_keys] - key_tree = dict((k.key, v) for k, v in tree.items()) + key_tree = {k.key: v for k, v in tree.items()} return [self._get_requirements_for_package(p, key_tree) for p in nodes] @@ -661,7 +708,7 @@ class Environment(object): yield new_node def reverse_dependencies(self): - from vistir.misc import unnest, chunked + from vistir.misc import chunked, unnest rdeps = {} for req in self.get_package_requirements(): for d in self.reverse_dependency(req): @@ -684,9 +731,9 @@ class Environment(object): for k in list(rdeps.keys()): entry = rdeps[k] if entry.get("parents"): - rdeps[k]["parents"] = set([ + rdeps[k]["parents"] = { p for p, version in chunked(2, unnest(entry["parents"])) - ]) + } return rdeps def get_working_set(self): @@ -709,6 +756,35 @@ class Environment(object): return any(d for d in self.get_distributions() if d.project_name == pkgname) + def is_satisfied(self, req): + match = next( + iter( + d for d in self.get_distributions() + if canonicalize_name(d.project_name) == req.normalized_name + ), None + ) + if match is not None: + if req.editable and req.line_instance.is_local and self.find_egg(match): + requested_path = req.line_instance.path + return requested_path and vistir.compat.samefile(requested_path, match.location) + elif match.has_metadata("direct_url.json"): + direct_url_metadata = json.loads(match.get_metadata("direct_url.json")) + commit_id = direct_url_metadata.get("vcs_info", {}).get("commit_id", "") + vcs_type = direct_url_metadata.get("vcs_info", {}).get("vcs", "") + _, pipfile_part = req.as_pipfile().popitem() + return ( + vcs_type == req.vcs and commit_id == req.commit_hash + and direct_url_metadata["url"] == pipfile_part[req.vcs] + ) + elif req.is_vcs or req.is_file_or_url: + return False + elif req.line_instance.specifiers is not None: + return req.line_instance.specifiers.contains( + match.version, prereleases=True + ) + return True + return False + def run(self, cmd, cwd=os.curdir): """Run a command with :class:`~subprocess.Popen` in the context of the environment @@ -726,7 +802,7 @@ class Environment(object): return c def run_py(self, cmd, cwd=os.curdir): - """Run a python command in the enviornment context. + """Run a python command in the environment context. :param cmd: A command to run in the environment - runs with `python -c` :type cmd: str or list @@ -736,8 +812,8 @@ class Environment(object): """ c = None - if isinstance(cmd, six.string_types): - script = vistir.cmdparse.Script.parse("{0} -c {1}".format(self.python, cmd)) + if isinstance(cmd, str): + script = vistir.cmdparse.Script.parse(f"{self.python} -c {cmd}") else: script = vistir.cmdparse.Script.parse([self.python, "-c"] + list(cmd)) with self.activated(): @@ -749,8 +825,8 @@ class Environment(object): if self.is_venv: activate_this = os.path.join(self.scripts_dir, "activate_this.py") if not os.path.isfile(activate_this): - raise OSError("No such file: {0!s}".format(activate_this)) - with open(activate_this, "r") as f: + raise OSError(f"No such file: {activate_this!s}") + with open(activate_this) as f: code = compile(f.read(), activate_this, "exec") exec(code, dict(__file__=activate_this)) @@ -779,7 +855,7 @@ class Environment(object): extra_dists = [] original_path = sys.path original_prefix = sys.prefix - parent_path = vistir.compat.Path(__file__).absolute().parent + parent_path = Path(__file__).absolute().parent vendor_dir = parent_path.joinpath("vendor").as_posix() patched_dir = parent_path.joinpath("patched").as_posix() parent_path = parent_path.as_posix() @@ -793,12 +869,11 @@ class Environment(object): ]) os.environ["PYTHONIOENCODING"] = vistir.compat.fs_str("utf-8") os.environ["PYTHONDONTWRITEBYTECODE"] = vistir.compat.fs_str("1") - from .environments import PIPENV_USE_SYSTEM if self.is_venv: os.environ["PYTHONPATH"] = self.base_paths["PYTHONPATH"] os.environ["VIRTUAL_ENV"] = vistir.compat.fs_str(prefix) else: - if not PIPENV_USE_SYSTEM and not os.environ.get("VIRTUAL_ENV"): + if not self.project.s.PIPENV_USE_SYSTEM and not os.environ.get("VIRTUAL_ENV"): os.environ["PYTHONPATH"] = self.base_paths["PYTHONPATH"] os.environ.pop("PYTHONHOME", None) sys.path = self.sys_path @@ -823,7 +898,6 @@ class Environment(object): finally: sys.path = original_path sys.prefix = original_prefix - six.moves.reload_module(pkg_resources) @cached_property def finders(self): @@ -854,11 +928,11 @@ class Environment(object): install_args = [ self.environment.python, "-u", "-c", SETUPTOOLS_SHIM % setup_path, install_arg, "--single-version-externally-managed", "--no-deps", - "--prefix={0}".format(self.base_paths["prefix"]), "--no-warn-script-location" + "--prefix={}".format(self.base_paths["prefix"]), "--no-warn-script-location" ] for key in install_keys: install_args.append( - "--install-{0}={1}".format(key, self.base_paths[key]) + f"--install-{key}={self.base_paths[key]}" ) return install_args @@ -934,7 +1008,7 @@ class Environment(object): return -class PatchedUninstaller(object): +class PatchedUninstaller: def _permitted(self, path): return True diff --git a/pipenv/environments.py b/pipenv/environments.py index 622b76ff..4395e7cd 100644 --- a/pipenv/environments.py +++ b/pipenv/environments.py @@ -1,19 +1,38 @@ -# -*- coding=utf-8 -*- - +import glob import os +import pathlib +import re import sys -from io import UnsupportedOperation - from appdirs import user_cache_dir +from vistir.path import normalize_drive -from ._compat import fix_utf8 -from .vendor.vistir.misc import _isatty, fs_str +from pipenv._compat import fix_utf8 +from pipenv.vendor.vistir.misc import _isatty, fs_str # HACK: avoid resolver.py uses the wrong byte code files. # I hope I can remove this one day. os.environ["PYTHONDONTWRITEBYTECODE"] = fs_str("1") +_false_values = ("0", "false", "no", "off") +_true_values = ("1", "true", "yes", "on") + + +def env_to_bool(val): + """ + Convert **val** to boolean, returning True if truthy or False if falsey + + :param Any val: The value to convert + :return: False if Falsey, True if truthy + :rtype: bool + """ + if isinstance(val, bool): + return val + if val.lower() in _false_values: + return False + if val.lower() in _true_values: + return True + raise ValueError(f"Value is not a valid boolean-like: {val}") def _is_env_truthy(name): @@ -21,24 +40,66 @@ def _is_env_truthy(name): """ if name not in os.environ: return False - return os.environ.get(name).lower() not in ("0", "false", "no", "off") + return os.environ.get(name).lower() not in _false_values -PIPENV_IS_CI = bool("CI" in os.environ or "TF_BUILD" in os.environ) +def get_from_env(arg, prefix="PIPENV", check_for_negation=True): + """ + Check the environment for a variable, returning its truthy or stringified value + + For example, setting ``PIPENV_NO_RESOLVE_VCS=1`` would mean that + ``get_from_env("RESOLVE_VCS", prefix="PIPENV")`` would return ``False``. + + :param str arg: The name of the variable to look for + :param str prefix: The prefix to attach to the variable, defaults to "PIPENV" + :param bool check_for_negation: Whether to check for ``_NO_``, defaults + to True + :return: The value from the environment if available + :rtype: Optional[Union[str, bool]] + """ + negative_lookup = f"NO_{arg}" + positive_lookup = arg + if prefix: + positive_lookup = f"{prefix}_{arg}" + negative_lookup = f"{prefix}_{negative_lookup}" + if positive_lookup in os.environ: + value = os.environ[positive_lookup] + try: + return env_to_bool(value) + except ValueError: + return value + if check_for_negation and negative_lookup in os.environ: + value = os.environ[negative_lookup] + try: + return not env_to_bool(value) + except ValueError: + return value + return None + + +def normalize_pipfile_path(p): + if p is None: + return None + loc = pathlib.Path(p) + try: + loc = loc.resolve() + except OSError: + loc = loc.absolute() + # Recase the path properly on Windows. From https://stackoverflow.com/a/35229734/5043728 + if os.name == 'nt': + matches = glob.glob(re.sub(r'([^:/\\])(?=[/\\]|$)', r'[\1]', str(loc))) + path_str = matches and matches[0] or str(loc) + else: + path_str = str(loc) + return normalize_drive(os.path.abspath(path_str)) + # HACK: Prevent invalid shebangs with Homebrew-installed Python: # https://bugs.python.org/issue22490 os.environ.pop("__PYVENV_LAUNCHER__", None) - -# Load patched pip instead of system pip -os.environ["PIP_SHIMS_BASE_MODULE"] = fs_str("pipenv.patched.notpip") - -PIPENV_CACHE_DIR = os.environ.get("PIPENV_CACHE_DIR", user_cache_dir("pipenv")) -"""Location for Pipenv to store it's package cache. - -Default is to use appdir's user cache directory. -""" - +# Internal, to tell whether the command line session is interactive. +SESSION_IS_INTERACTIVE = _isatty(sys.stdout) +PIPENV_IS_CI = env_to_bool(os.environ.get('CI') or os.environ.get('TF_BUILD') or False) PIPENV_COLORBLIND = bool(os.environ.get("PIPENV_COLORBLIND")) """If set, disable terminal colors. @@ -46,47 +107,6 @@ Some people don't like colors in their terminals, for some reason. Default is to show colors. """ -# Tells Pipenv which Python to default to, when none is provided. -PIPENV_DEFAULT_PYTHON_VERSION = os.environ.get("PIPENV_DEFAULT_PYTHON_VERSION") -"""Use this Python version when creating new virtual environments by default. - -This can be set to a version string, e.g. ``3.6``, or a path. Default is to use -whatever Python Pipenv is installed under (i.e. ``sys.executable``). Command -line flags (e.g. ``--python``, ``--three``, and ``--two``) are prioritized over -this configuration. -""" - -PIPENV_DONT_LOAD_ENV = bool(os.environ.get("PIPENV_DONT_LOAD_ENV")) -"""If set, Pipenv does not load the ``.env`` file. - -Default is to load ``.env`` for ``run`` and ``shell`` commands. -""" - -PIPENV_DONT_USE_PYENV = bool(os.environ.get("PIPENV_DONT_USE_PYENV")) -"""If set, Pipenv does not attempt to install Python with pyenv. - -Default is to install Python automatically via pyenv when needed, if possible. -""" - -PIPENV_DONT_USE_ASDF = bool(os.environ.get("PIPENV_DONT_USE_ASDF")) -"""If set, Pipenv does not attempt to install Python with asdf. - -Default is to install Python automatically via asdf when needed, if possible. -""" - -PIPENV_DOTENV_LOCATION = os.environ.get("PIPENV_DOTENV_LOCATION") -"""If set, Pipenv loads the ``.env`` file at the specified location. - -Default is to load ``.env`` from the project root, if found. -""" - -PIPENV_EMULATOR = os.environ.get("PIPENV_EMULATOR", "") -"""If set, the terminal emulator's name for ``pipenv shell`` to use. - -Default is to detect emulators automatically. This should be set if your -emulator, e.g. Cmder, cannot be detected correctly. -""" - PIPENV_HIDE_EMOJIS = ( os.environ.get("PIPENV_HIDE_EMOJIS") is None and (os.name == "nt" or PIPENV_IS_CI) @@ -97,221 +117,290 @@ PIPENV_HIDE_EMOJIS = ( Default is to show emojis. This is automatically set on Windows. """ -PIPENV_IGNORE_VIRTUALENVS = bool(os.environ.get("PIPENV_IGNORE_VIRTUALENVS")) -"""If set, Pipenv will always assign a virtual environment for this project. -By default, Pipenv tries to detect whether it is run inside a virtual -environment, and reuses it if possible. This is usually the desired behavior, -and enables the user to use any user-built environments with Pipenv. -""" +class Setting: + def __init__(self) -> None: + self.USING_DEFAULT_PYTHON = True + self.initialize() -PIPENV_INSTALL_TIMEOUT = int(os.environ.get("PIPENV_INSTALL_TIMEOUT", 60 * 15)) -"""Max number of seconds to wait for package installation. + def initialize(self): -Defaults to 900 (15 minutes), a very long arbitrary time. -""" + self.PIPENV_CACHE_DIR = os.environ.get("PIPENV_CACHE_DIR", user_cache_dir("pipenv")) + """Location for Pipenv to store it's package cache. -# NOTE: +1 because of a temporary bug in Pipenv. -PIPENV_MAX_DEPTH = int(os.environ.get("PIPENV_MAX_DEPTH", "3")) + 1 -"""Maximum number of directories to recursively search for a Pipfile. + Default is to use appdir's user cache directory. + """ -Default is 3. See also ``PIPENV_NO_INHERIT``. -""" + # Tells Pipenv which Python to default to, when none is provided. + self.PIPENV_DEFAULT_PYTHON_VERSION = os.environ.get("PIPENV_DEFAULT_PYTHON_VERSION") + """Use this Python version when creating new virtual environments by default. -PIPENV_MAX_RETRIES = int( - os.environ.get("PIPENV_MAX_RETRIES", "1" if PIPENV_IS_CI else "0") -) -"""Specify how many retries Pipenv should attempt for network requests. + This can be set to a version string, e.g. ``3.6``, or a path. Default is to use + whatever Python Pipenv is installed under (i.e. ``sys.executable``). Command + line flags (e.g. ``--python``, ``--three``, and ``--two``) are prioritized over + this configuration. + """ -Default is 0. Automatically set to 1 on CI environments for robust testing. -""" + self.PIPENV_DONT_LOAD_ENV = bool(os.environ.get("PIPENV_DONT_LOAD_ENV")) + """If set, Pipenv does not load the ``.env`` file. -PIPENV_MAX_ROUNDS = int(os.environ.get("PIPENV_MAX_ROUNDS", "16")) -"""Tells Pipenv how many rounds of resolving to do for Pip-Tools. + Default is to load ``.env`` for ``run`` and ``shell`` commands. + """ -Default is 16, an arbitrary number that works most of the time. -""" + self.PIPENV_DONT_USE_PYENV = bool(os.environ.get("PIPENV_DONT_USE_PYENV")) + """If set, Pipenv does not attempt to install Python with pyenv. -PIPENV_MAX_SUBPROCESS = int(os.environ.get("PIPENV_MAX_SUBPROCESS", "8")) -"""How many subprocesses should Pipenv use when installing. + Default is to install Python automatically via pyenv when needed, if possible. + """ -Default is 16, an arbitrary number that seems to work. -""" + self.PIPENV_DONT_USE_ASDF = bool(os.environ.get("PIPENV_DONT_USE_ASDF")) + """If set, Pipenv does not attempt to install Python with asdf. -PIPENV_NO_INHERIT = "PIPENV_NO_INHERIT" in os.environ -"""Tell Pipenv not to inherit parent directories. + Default is to install Python automatically via asdf when needed, if possible. + """ -This is useful for deployment to avoid using the wrong current directory. -Overwrites ``PIPENV_MAX_DEPTH``. -""" -if PIPENV_NO_INHERIT: - PIPENV_MAX_DEPTH = 2 + self.PIPENV_DOTENV_LOCATION = os.environ.get("PIPENV_DOTENV_LOCATION") + """If set, Pipenv loads the ``.env`` file at the specified location. -PIPENV_NOSPIN = bool(os.environ.get("PIPENV_NOSPIN")) -"""If set, disable terminal spinner. + Default is to load ``.env`` from the project root, if found. + """ -This can make the logs cleaner. Automatically set on Windows, and in CI -environments. -""" -if PIPENV_IS_CI: - PIPENV_NOSPIN = True + self.PIPENV_EMULATOR = os.environ.get("PIPENV_EMULATOR", "") + """If set, the terminal emulator's name for ``pipenv shell`` to use. -PIPENV_SPINNER = "dots" if not os.name == "nt" else "bouncingBar" -PIPENV_SPINNER = os.environ.get("PIPENV_SPINNER", PIPENV_SPINNER) -"""Sets the default spinner type. + Default is to detect emulators automatically. This should be set if your + emulator, e.g. Cmder, cannot be detected correctly. + """ -Spinners are identitcal to the node.js spinners and can be found at -https://github.com/sindresorhus/cli-spinners -""" + self.PIPENV_IGNORE_VIRTUALENVS = bool(os.environ.get("PIPENV_IGNORE_VIRTUALENVS")) + """If set, Pipenv will always assign a virtual environment for this project. -PIPENV_PIPFILE = os.environ.get("PIPENV_PIPFILE") -"""If set, this specifies a custom Pipfile location. + By default, Pipenv tries to detect whether it is run inside a virtual + environment, and reuses it if possible. This is usually the desired behavior, + and enables the user to use any user-built environments with Pipenv. + """ -When running pipenv from a location other than the same directory where the -Pipfile is located, instruct pipenv to find the Pipfile in the location -specified by this environment variable. + self.PIPENV_INSTALL_TIMEOUT = int(os.environ.get("PIPENV_INSTALL_TIMEOUT", 60 * 15)) + """Max number of seconds to wait for package installation. -Default is to find Pipfile automatically in the current and parent directories. -See also ``PIPENV_MAX_DEPTH``. -""" + Defaults to 900 (15 minutes), a very long arbitrary time. + """ -PIPENV_PYPI_MIRROR = os.environ.get("PIPENV_PYPI_MIRROR") -"""If set, tells pipenv to override PyPI index urls with a mirror. + # NOTE: +1 because of a temporary bug in Pipenv. + self.PIPENV_MAX_DEPTH = int(os.environ.get("PIPENV_MAX_DEPTH", "3")) + 1 + """Maximum number of directories to recursively search for a Pipfile. -Default is to not mirror PyPI, i.e. use the real one, pypi.org. The -``--pypi-mirror`` command line flag overwrites this. -""" + Default is 3. See also ``PIPENV_NO_INHERIT``. + """ -PIPENV_QUIET = bool(os.environ.get("PIPENV_QUIET")) -"""If set, makes Pipenv quieter. + self.PIPENV_MAX_RETRIES = int( + os.environ.get("PIPENV_MAX_RETRIES", "1" if PIPENV_IS_CI else "0") + ) + """Specify how many retries Pipenv should attempt for network requests. -Default is unset, for normal verbosity. ``PIPENV_VERBOSE`` overrides this. -""" + Default is 0. Automatically set to 1 on CI environments for robust testing. + """ -PIPENV_SHELL = os.environ.get("PIPENV_SHELL") -"""An absolute path to the preferred shell for ``pipenv shell``. + self.PIPENV_MAX_ROUNDS = int(os.environ.get("PIPENV_MAX_ROUNDS", "16")) + """Tells Pipenv how many rounds of resolving to do for Pip-Tools. -Default is to detect automatically what shell is currently in use. -""" -# Hack because PIPENV_SHELL is actually something else. Internally this -# variable is called PIPENV_SHELL_EXPLICIT instead. -PIPENV_SHELL_EXPLICIT = PIPENV_SHELL -del PIPENV_SHELL + Default is 16, an arbitrary number that works most of the time. + """ -PIPENV_SHELL_FANCY = bool(os.environ.get("PIPENV_SHELL_FANCY")) -"""If set, always use fancy mode when invoking ``pipenv shell``. + self.PIPENV_MAX_SUBPROCESS = int(os.environ.get("PIPENV_MAX_SUBPROCESS", "8")) + """How many subprocesses should Pipenv use when installing. -Default is to use the compatibility shell if possible. -""" + Default is 16, an arbitrary number that seems to work. + """ -PIPENV_TIMEOUT = int(os.environ.get("PIPENV_TIMEOUT", 120)) -"""Max number of seconds Pipenv will wait for virtualenv creation to complete. + self.PIPENV_NO_INHERIT = "PIPENV_NO_INHERIT" in os.environ + """Tell Pipenv not to inherit parent directories. -Default is 120 seconds, an arbitrary number that seems to work. -""" + This is useful for deployment to avoid using the wrong current directory. + Overwrites ``PIPENV_MAX_DEPTH``. + """ + if self.PIPENV_NO_INHERIT: + self.PIPENV_MAX_DEPTH = 2 -PIPENV_VENV_IN_PROJECT = bool(os.environ.get("PIPENV_VENV_IN_PROJECT")) -"""If set, creates ``.venv`` in your project directory. + self.PIPENV_NOSPIN = bool(os.environ.get("PIPENV_NOSPIN")) + """If set, disable terminal spinner. -Default is to create new virtual environments in a global location. -""" + This can make the logs cleaner. Automatically set on Windows, and in CI + environments. + """ + if PIPENV_IS_CI: + self.PIPENV_NOSPIN = True -PIPENV_VERBOSE = bool(os.environ.get("PIPENV_VERBOSE")) -"""If set, makes Pipenv more wordy. + pipenv_spinner = "dots" if not os.name == "nt" else "bouncingBar" + self.PIPENV_SPINNER = os.environ.get("PIPENV_SPINNER", pipenv_spinner) + """Sets the default spinner type. -Default is unset, for normal verbosity. This takes precedence over -``PIPENV_QUIET``. -""" + Spinners are identical to the ``node.js`` spinners and can be found at + https://github.com/sindresorhus/cli-spinners + """ -PIPENV_YES = bool(os.environ.get("PIPENV_YES")) -"""If set, Pipenv automatically assumes "yes" at all prompts. + pipenv_pipfile = os.environ.get("PIPENV_PIPFILE") + if pipenv_pipfile: + if not os.path.isfile(pipenv_pipfile): + raise RuntimeError("Given PIPENV_PIPFILE is not found!") -Default is to prompt the user for an answer if the current command line session -if interactive. -""" + else: + pipenv_pipfile = normalize_pipfile_path(pipenv_pipfile) + # Overwrite environment variable so that subprocesses can get the correct path. + # See https://github.com/pypa/pipenv/issues/3584 + os.environ['PIPENV_PIPFILE'] = pipenv_pipfile + self.PIPENV_PIPFILE = pipenv_pipfile + """If set, this specifies a custom Pipfile location. -PIPENV_SKIP_LOCK = False -"""If set, Pipenv won't lock dependencies automatically. + When running pipenv from a location other than the same directory where the + Pipfile is located, instruct pipenv to find the Pipfile in the location + specified by this environment variable. -This might be desirable if a project has large number of dependencies, -because locking is an inherently slow operation. + Default is to find Pipfile automatically in the current and parent directories. + See also ``PIPENV_MAX_DEPTH``. + """ -Default is to lock dependencies and update ``Pipfile.lock`` on each run. + self.PIPENV_PYPI_MIRROR = os.environ.get("PIPENV_PYPI_MIRROR") + """If set, tells pipenv to override PyPI index urls with a mirror. -NOTE: This only affects the ``install`` and ``uninstall`` commands. -""" + Default is to not mirror PyPI, i.e. use the real one, pypi.org. The + ``--pypi-mirror`` command line flag overwrites this. + """ -PIP_EXISTS_ACTION = os.environ.get("PIP_EXISTS_ACTION", "w") -"""Specifies the value for pip's --exists-action option + self.PIPENV_QUIET = bool(os.environ.get("PIPENV_QUIET")) + """If set, makes Pipenv quieter. -Defaullts to (w)ipe -""" + Default is unset, for normal verbosity. ``PIPENV_VERBOSE`` overrides this. + """ -PIPENV_RESOLVE_VCS = _is_env_truthy(os.environ.get("PIPENV_RESOLVE_VCS", 'true')) -"""Tells Pipenv whether to resolve all VCS dependencies in full. + self.PIPENV_SHELL_EXPLICIT = os.environ.get("PIPENV_SHELL") + """An absolute path to the preferred shell for ``pipenv shell``. -As of Pipenv 2018.11.26, only editable VCS dependencies were resolved in full. -To retain this behavior and avoid handling any conflicts that arise from the new -approach, you may set this to '0', 'off', or 'false'. -""" + Default is to detect automatically what shell is currently in use. + """ + # Hack because PIPENV_SHELL is actually something else. Internally this + # variable is called PIPENV_SHELL_EXPLICIT instead. -PIPENV_PYUP_API_KEY = os.environ.get( - "PIPENV_PYUP_API_KEY", "1ab8d58f-5122e025-83674263-bc1e79e0" -) + self.PIPENV_SHELL_FANCY = bool(os.environ.get("PIPENV_SHELL_FANCY")) + """If set, always use fancy mode when invoking ``pipenv shell``. -# Internal, support running in a different Python from sys.executable. -PIPENV_PYTHON = os.environ.get("PIPENV_PYTHON") + Default is to use the compatibility shell if possible. + """ -# Internal, overwrite all index funcitonality. -PIPENV_TEST_INDEX = os.environ.get("PIPENV_TEST_INDEX") + self.PIPENV_TIMEOUT = int(os.environ.get("PIPENV_TIMEOUT", 120)) + """Max number of seconds Pipenv will wait for virtualenv creation to complete. -# Internal, tells Pipenv about the surrounding environment. -PIPENV_USE_SYSTEM = False -PIPENV_VIRTUALENV = None -if "PIPENV_ACTIVE" not in os.environ and not PIPENV_IGNORE_VIRTUALENVS: - PIPENV_VIRTUALENV = os.environ.get("VIRTUAL_ENV") - PIPENV_USE_SYSTEM = bool(PIPENV_VIRTUALENV) + Default is 120 seconds, an arbitrary number that seems to work. + """ -# Internal, tells Pipenv to skip case-checking (slow internet connections). -# This is currently always set to True for performance reasons. -PIPENV_SKIP_VALIDATION = True + self.PIPENV_VENV_IN_PROJECT = bool(os.environ.get("PIPENV_VENV_IN_PROJECT")) + """If set, creates ``.venv`` in your project directory. -# Internal, the default shell to use if shell detection fails. -PIPENV_SHELL = ( - os.environ.get("SHELL") - or os.environ.get("PYENV_SHELL") - or os.environ.get("COMSPEC") -) + Default is to create new virtual environments in a global location. + """ -# Internal, to tell whether the command line session is interactive. -try: - SESSION_IS_INTERACTIVE = _isatty(sys.stdout.fileno()) -except UnsupportedOperation: - SESSION_IS_INTERACTIVE = _isatty(sys.stdout) + self.PIPENV_VERBOSE = bool(os.environ.get("PIPENV_VERBOSE")) + """If set, makes Pipenv more wordy. + + Default is unset, for normal verbosity. This takes precedence over + ``PIPENV_QUIET``. + """ + + self.PIPENV_YES = bool(os.environ.get("PIPENV_YES")) + """If set, Pipenv automatically assumes "yes" at all prompts. + + Default is to prompt the user for an answer if the current command line session + if interactive. + """ + + self.PIPENV_SKIP_LOCK = False + """If set, Pipenv won't lock dependencies automatically. + + This might be desirable if a project has large number of dependencies, + because locking is an inherently slow operation. + + Default is to lock dependencies and update ``Pipfile.lock`` on each run. + + NOTE: This only affects the ``install`` and ``uninstall`` commands. + """ + + self.PIP_EXISTS_ACTION = os.environ.get("PIP_EXISTS_ACTION", "w") + """Specifies the value for pip's --exists-action option + + Defaults to ``(w)ipe`` + """ + + self.PIPENV_RESOLVE_VCS = ( + os.environ.get("PIPENV_RESOLVE_VCS") is None + or _is_env_truthy("PIPENV_RESOLVE_VCS") + ) + + """Tells Pipenv whether to resolve all VCS dependencies in full. + + As of Pipenv 2018.11.26, only editable VCS dependencies were resolved in full. + To retain this behavior and avoid handling any conflicts that arise from the new + approach, you may set this to '0', 'off', or 'false'. + """ + + self.PIPENV_PYUP_API_KEY = os.environ.get( + "PIPENV_PYUP_API_KEY", None + ) + + # Internal, support running in a different Python from sys.executable. + self.PIPENV_PYTHON = os.environ.get("PIPENV_PYTHON") + + # Internal, overwrite all index funcitonality. + self.PIPENV_TEST_INDEX = os.environ.get("PIPENV_TEST_INDEX") + + # Internal, tells Pipenv about the surrounding environment. + self.PIPENV_USE_SYSTEM = False + self.PIPENV_VIRTUALENV = None + if "PIPENV_ACTIVE" not in os.environ and not self.PIPENV_IGNORE_VIRTUALENVS: + self.PIPENV_VIRTUALENV = os.environ.get("VIRTUAL_ENV") + + # Internal, tells Pipenv to skip case-checking (slow internet connections). + # This is currently always set to True for performance reasons. + self.PIPENV_SKIP_VALIDATION = True + + # Internal, the default shell to use if shell detection fails. + self.PIPENV_SHELL = ( + os.environ.get("SHELL") + or os.environ.get("PYENV_SHELL") + or os.environ.get("COMSPEC") + ) + + # Internal, consolidated verbosity representation as an integer. The default + # level is 0, increased for wordiness and decreased for terseness. + verbosity = os.environ.get("PIPENV_VERBOSITY", "") + try: + self.PIPENV_VERBOSITY = int(verbosity) + except (ValueError, TypeError): + if self.PIPENV_VERBOSE: + self.PIPENV_VERBOSITY = 1 + elif self.PIPENV_QUIET: + self.PIPENV_VERBOSITY = -1 + else: + self.PIPENV_VERBOSITY = 0 + del self.PIPENV_QUIET + del self.PIPENV_VERBOSE + + def is_verbose(self, threshold=1): + return self.PIPENV_VERBOSITY >= threshold + + def is_quiet(self, threshold=-1): + return self.PIPENV_VERBOSITY <= threshold -# Internal, consolidated verbosity representation as an integer. The default -# level is 0, increased for wordiness and decreased for terseness. -PIPENV_VERBOSITY = os.environ.get("PIPENV_VERBOSITY", "") -try: - PIPENV_VERBOSITY = int(PIPENV_VERBOSITY) -except (ValueError, TypeError): - if PIPENV_VERBOSE: - PIPENV_VERBOSITY = 1 - elif PIPENV_QUIET: - PIPENV_VERBOSITY = -1 +def is_using_venv(): + # type: () -> bool + """Check for venv-based virtual environment which sets sys.base_prefix""" + if getattr(sys, 'real_prefix', None) is not None: + # virtualenv venvs + result = True else: - PIPENV_VERBOSITY = 0 -del PIPENV_QUIET -del PIPENV_VERBOSE - - -def is_verbose(threshold=1): - return PIPENV_VERBOSITY >= threshold - - -def is_quiet(threshold=-1): - return PIPENV_VERBOSITY <= threshold + # PEP 405 venvs + result = sys.prefix != getattr(sys, 'base_prefix', sys.prefix) + return result def is_in_virtualenv(): @@ -323,19 +412,9 @@ def is_in_virtualenv(): """ pipenv_active = os.environ.get("PIPENV_ACTIVE", False) - virtual_env = None - use_system = False + virtual_env = bool(os.environ.get("VIRTUAL_ENV")) ignore_virtualenvs = bool(os.environ.get("PIPENV_IGNORE_VIRTUALENVS", False)) - - if not pipenv_active and not ignore_virtualenvs: - virtual_env = os.environ.get("VIRTUAL_ENV") - use_system = bool(virtual_env) - return (use_system or virtual_env) and not (pipenv_active or ignore_virtualenvs) - - -PIPENV_SPINNER_FAIL_TEXT = fix_utf8(u"✘ {0}") if not PIPENV_HIDE_EMOJIS else ("{0}") - -PIPENV_SPINNER_OK_TEXT = fix_utf8(u"✔ {0}") if not PIPENV_HIDE_EMOJIS else ("{0}") + return virtual_env and not (pipenv_active or ignore_virtualenvs) def is_type_checking(): @@ -347,3 +426,5 @@ def is_type_checking(): MYPY_RUNNING = is_type_checking() +PIPENV_SPINNER_FAIL_TEXT = fix_utf8("✘ {0}") if not PIPENV_HIDE_EMOJIS else "{0}" +PIPENV_SPINNER_OK_TEXT = fix_utf8("✔ {0}") if not PIPENV_HIDE_EMOJIS else "{0}" diff --git a/pipenv/exceptions.py b/pipenv/exceptions.py index 6e0032ad..33b86e5f 100644 --- a/pipenv/exceptions.py +++ b/pipenv/exceptions.py @@ -1,5 +1,3 @@ -# -*- coding=utf-8 -*- - import itertools import re import sys @@ -7,19 +5,17 @@ import sys from collections import namedtuple from traceback import format_tb -import six - -from . import environments -from ._compat import decode_for_output -from .patched import crayons -from .vendor.click.exceptions import ( +from pipenv import environments +from pipenv._compat import decode_for_output +from pipenv.patched import crayons +from pipenv.vendor.click.exceptions import ( ClickException, FileError, UsageError ) -from .vendor.vistir.misc import echo as click_echo -import vistir +from pipenv.vendor.vistir.misc import echo as click_echo +from pipenv.vendor import vistir ANSI_REMOVAL_RE = re.compile(r"\033\[((?:\d|;)*)([a-zA-Z])", re.MULTILINE) -STRING_TYPES = (six.string_types, crayons.ColoredString) +STRING_TYPES = ((str,), crayons.ColoredString) if sys.version_info[:2] >= (3, 7): KnownException = namedtuple( @@ -43,7 +39,7 @@ KNOWN_EXCEPTIONS = [ def handle_exception(exc_type, exception, traceback, hook=sys.excepthook): - if environments.is_verbose() or not issubclass(exc_type, ClickException): + if environments.Setting().is_verbose() or not issubclass(exc_type, ClickException): hook(exc_type, exception, traceback) else: tb = format_tb(traceback, limit=-6) @@ -52,10 +48,10 @@ def handle_exception(exc_type, exception, traceback, hook=sys.excepthook): for line in lines: line = line.strip("'").strip('"').strip("\n").strip() if not line.startswith("File"): - line = " {0}".format(line) + line = f" {line}" else: - line = " {0}".format(line) - line = "[{0!s}]: {1}".format( + line = f" {line}" + line = "[{!s}]: {}".format( exception.__class__.__name__, line ) formatted_lines.append(line) @@ -86,12 +82,12 @@ class PipenvException(ClickException): if isinstance(self.extra, STRING_TYPES): self.extra = [self.extra] for extra in self.extra: - extra = "[pipenv.exceptions.{0!s}]: {1}".format( + extra = "[pipenv.exceptions.{!s}]: {}".format( self.__class__.__name__, extra ) extra = decode_for_output(extra, file) click_echo(extra, file=file) - click_echo(decode_for_output("{0}".format(self.message), file), file=file) + click_echo(decode_for_output(f"{self.message}", file), file=file) class PipenvCmdError(PipenvException): @@ -100,24 +96,24 @@ class PipenvCmdError(PipenvException): self.out = out self.err = err self.exit_code = exit_code - message = "Error running command: {0}".format(cmd) + message = f"Error running command: {cmd}" PipenvException.__init__(self, message) def show(self, file=None): if file is None: file = vistir.misc.get_text_stderr() - click_echo("{0} {1}".format( + click_echo("{} {}".format( crayons.red("Error running command: "), - crayons.white(decode_for_output("$ {0}".format(self.cmd), file), bold=True) + crayons.normal(decode_for_output(f"$ {self.cmd}", file), bold=True) ), err=True) if self.out: - click_echo("{0} {1}".format( - crayons.white("OUTPUT: "), + click_echo("{} {}".format( + crayons.normal("OUTPUT: "), decode_for_output(self.out, file) ), err=True) if self.err: - click_echo("{0} {1}".format( - crayons.white("STDERR: "), + click_echo("{} {}".format( + crayons.normal("STDERR: "), decode_for_output(self.err, file) ), err=True) @@ -130,14 +126,14 @@ class JSONParseError(PipenvException): def show(self, file=None): if file is None: file = vistir.misc.get_text_stderr() - message = "{0}\n{1}".format( - crayons.white("Failed parsing JSON results:", bold=True), + message = "{}\n{}".format( + crayons.normal("Failed parsing JSON results:", bold=True), decode_for_output(self.message.strip(), file) ) click_echo(message, err=True) if self.error_text: - click_echo("{0} {1}".format( - crayons.white("ERROR TEXT:", bold=True), + click_echo("{} {}".format( + crayons.normal("ERROR TEXT:", bold=True), decode_for_output(self.error_text, file) ), err=True) @@ -149,7 +145,7 @@ class PipenvUsageError(UsageError): msg_prefix = crayons.red("ERROR:", bold=True) if not message: message = "Pipenv encountered a problem and had to exit." - message = formatted_message.format(msg_prefix, crayons.white(message, bold=True)) + message = formatted_message.format(msg_prefix, crayons.normal(message, bold=True)) self.message = message extra = kwargs.pop("extra", []) UsageError.__init__(self, decode_for_output(message), ctx) @@ -185,9 +181,9 @@ class PipenvFileError(FileError): def __init__(self, filename, message=None, **kwargs): extra = kwargs.pop("extra", []) if not message: - message = crayons.white("Please ensure that the file exists!", bold=True) + message = crayons.normal("Please ensure that the file exists!", bold=True) message = self.formatted_message.format( - crayons.white("{0} not found!".format(filename), bold=True), + crayons.normal(f"{filename} not found!", bold=True), message ) FileError.__init__(self, filename=filename, hint=decode_for_output(message), **kwargs) @@ -208,32 +204,32 @@ class PipfileNotFound(PipenvFileError): def __init__(self, filename="Pipfile", extra=None, **kwargs): extra = kwargs.pop("extra", []) message = ( - "{0} {1}".format( + "{} {}".format( crayons.red("Aborting!", bold=True), - crayons.white( + crayons.normal( "Please ensure that the file exists and is located in your" " project root directory.", bold=True ) ) ) - super(PipfileNotFound, self).__init__(filename, message=message, extra=extra, **kwargs) + super().__init__(filename, message=message, extra=extra, **kwargs) class LockfileNotFound(PipenvFileError): def __init__(self, filename="Pipfile.lock", extra=None, **kwargs): extra = kwargs.pop("extra", []) - message = "{0} {1} {2}".format( - crayons.white("You need to run", bold=True), + message = "{} {} {}".format( + crayons.normal("You need to run", bold=True), crayons.red("$ pipenv lock", bold=True), - crayons.white("before you can continue.", bold=True) + crayons.normal("before you can continue.", bold=True) ) - super(LockfileNotFound, self).__init__(filename, message=message, extra=extra, **kwargs) + super().__init__(filename, message=message, extra=extra, **kwargs) class DeployException(PipenvUsageError): def __init__(self, message=None, **kwargs): if not message: - message = crayons.normal("Aborting deploy", bold=True) + message = str(crayons.normal("Aborting deploy", bold=True)) extra = kwargs.pop("extra", []) PipenvUsageError.__init__(self, message=message, extra=extra, **kwargs) @@ -250,25 +246,16 @@ class SystemUsageError(PipenvOptionsError): def __init__(self, option_name="system", message=None, ctx=None, **kwargs): extra = kwargs.pop("extra", []) extra += [ - "{0}: --system is intended to be used for Pipfile installation, " + "{}: --system is intended to be used for Pipfile installation, " "not installation of specific packages. Aborting.".format( crayons.red("Warning", bold=True) ), ] if message is None: - message = crayons.blue("See also: {0}".format(crayons.white("--deploy flag."))) - super(SystemUsageError, self).__init__(option_name, message=message, ctx=ctx, extra=extra, **kwargs) - - -class PipfileException(PipenvFileError): - def __init__(self, hint=None, **kwargs): - from .core import project - - if not hint: - hint = "{0} {1}".format(crayons.red("ERROR (PACKAGE NOT INSTALLED):"), hint) - filename = project.pipfile_location - extra = kwargs.pop("extra", []) - PipenvFileError.__init__(self, filename, hint, extra=extra, **kwargs) + message = str( + crayons.cyan("See also: {}".format(crayons.normal("--deploy flag."))) + ) + super().__init__(option_name, message=message, ctx=ctx, extra=extra, **kwargs) class SetupException(PipenvException): @@ -292,7 +279,7 @@ class VirtualenvActivationException(VirtualenvException): if not message: message = ( "activate_this.py not found. Your environment is most certainly " - "not activated. Continuing anyway…" + "not activated. Continuing anyway..." ) self.message = message VirtualenvException.__init__(self, message, **kwargs) @@ -308,9 +295,11 @@ class VirtualenvCreationException(VirtualenvException): # note we need the format interpolation because ``crayons.ColoredString`` # is not an actual string type but is only a preparation for interpolation # so replacement or parsing requires this step - extra = ANSI_REMOVAL_RE.sub("", "{0}".format(extra)) + extra = ANSI_REMOVAL_RE.sub("", f"{extra}") if "KeyboardInterrupt" in extra: - extra = crayons.red("Virtualenv creation interrupted by user", bold=True) + extra = str( + crayons.red("Virtualenv creation interrupted by user", bold=True) + ) self.extra = extra = [extra] VirtualenvException.__init__(self, message, extra=extra) @@ -318,17 +307,17 @@ class VirtualenvCreationException(VirtualenvException): class UninstallError(PipenvException): def __init__(self, package, command, return_values, return_code, **kwargs): extra = [ - "{0} {1}".format( - crayons.blue("Attempted to run command: "), - crayons.yellow("$ {0!r}".format(command), bold=True) + "{} {}".format( + crayons.cyan("Attempted to run command: "), + crayons.yellow(f"$ {command!r}", bold=True) ) ] - extra.extend([crayons.blue(line.strip()) for line in return_values.splitlines()]) + extra.extend([crayons.cyan(line.strip()) for line in return_values.splitlines()]) if isinstance(package, (tuple, list, set)): package = " ".join(package) - message = "{0!s} {1!s}...".format( + message = "{!s} {!s}...".format( crayons.normal("Failed to uninstall package(s)"), - crayons.yellow("{0}!s".format(package), bold=True) + crayons.yellow(f"{package}!s", bold=True) ) self.exit_code = return_code PipenvException.__init__(self, message=message, extra=extra) @@ -339,11 +328,11 @@ class InstallError(PipenvException): def __init__(self, package, **kwargs): package_message = "" if package is not None: - package_message = "Couldn't install package: {0}\n".format( - crayons.white("{0!s}".format(package), bold=True) + package_message = "Couldn't install package: {}\n".format( + crayons.normal(f"{package!s}", bold=True) ) - message = "{0} {1}".format( - "{0}".format(package_message), + message = "{} {}".format( + f"{package_message}", crayons.yellow("Package installation failed...") ) extra = kwargs.pop("extra", []) @@ -352,17 +341,17 @@ class InstallError(PipenvException): class CacheError(PipenvException): def __init__(self, path, **kwargs): - message = "{0} {1}\n{2}".format( - crayons.blue("Corrupt cache file"), - crayons.white("{0!s}".format(path)), - crayons.white('Consider trying "pipenv lock --clear" to clear the cache.') + message = "{} {}\n{}".format( + crayons.cyan("Corrupt cache file"), + crayons.normal(f"{path!s}"), + crayons.normal('Consider trying "pipenv lock --clear" to clear the cache.') ) PipenvException.__init__(self, message=message) class DependencyConflict(PipenvException): def __init__(self, message): - extra = ["{0} {1}".format( + extra = ["{} {}".format( crayons.red("The operation failed...", bold=True), crayons.red("A dependency conflict was detected and could not be resolved."), )] @@ -372,27 +361,25 @@ class DependencyConflict(PipenvException): class ResolutionFailure(PipenvException): def __init__(self, message, no_version_found=False): extra = ( - "{0}: Your dependencies could not be resolved. You likely have a " + "{}: Your dependencies could not be resolved. You likely have a " "mismatch in your sub-dependencies.\n " - "First try clearing your dependency cache with {1}, then try the original command again.\n " - "Alternatively, you can use {2} to bypass this mechanism, then run " - "{3} to inspect the situation.\n " - "Hint: try {4} if it is a pre-release dependency." + "You can use {} to bypass this mechanism, then run " + "{} to inspect the situation.\n " + "Hint: try {} if it is a pre-release dependency." "".format( crayons.red("Warning", bold=True), - crayons.red("$ pipenv lock --clear"), - crayons.red("$ pipenv install --skip-lock"), - crayons.red("$ pipenv graph"), - crayons.red("$ pipenv lock --pre"), + crayons.yellow("$ pipenv install --skip-lock"), + crayons.yellow("$ pipenv graph"), + crayons.yellow("$ pipenv lock --pre"), ), ) if "no version found at all" in message: no_version_found = True - message = crayons.yellow("{0}".format(message)) + message = crayons.yellow(f"{message}") if no_version_found: - message = "{0}\n{1}".format( + message = "{}\n{}".format( message, - crayons.blue( + crayons.cyan( "Please check your version specifier and version number. " "See PEP440 for more information." ) @@ -422,18 +409,18 @@ class RequirementError(PipenvException): if getattr(req, k, None) ] req_value = "\n".join([ - " {0}: {1}".format(k, v) for k, v in slot_vals + f" {k}: {v}" for k, v in slot_vals ]) elif keys_fn: values = [(k, req.get(k)) for k in keys_fn() if req.get(k)] req_value = "\n".join([ - " {0}: {1}".format(k, v) for k, v in values + f" {k}: {v}" for k, v in values ]) else: req_value = getattr(req.line_instance, "line", None) - message = "{0} {1}".format( + message = "{} {}".format( crayons.normal(decode_for_output("Failed creating requirement instance")), - crayons.white(decode_for_output("{0!r}".format(req_value))) + crayons.normal(decode_for_output(f"{req_value!r}")) ) extra = [str(req)] PipenvException.__init__(self, message, extra=extra) @@ -456,8 +443,8 @@ def prettify_exc(error): _, error, info = error.rpartition(exc.prefix) else: _, error, info = error.rpartition(split_string) - errors.append("{0} {1}".format(error, info)) + errors.append(f"{error} {info}") if not errors: - return "{}".format(vistir.misc.decode_for_output(error)) + return f"{vistir.misc.decode_for_output(error)}" return "\n".join(errors) diff --git a/pipenv/help.py b/pipenv/help.py index e8fbf1c8..2882bca1 100644 --- a/pipenv/help.py +++ b/pipenv/help.py @@ -1,13 +1,11 @@ -# coding: utf-8 import os import pprint import sys import pipenv -from .core import project -from .pep508checker import lookup -from .vendor import pythonfinder +from pipenv.pep508checker import lookup +from pipenv.vendor import pythonfinder def print_utf(line): @@ -17,14 +15,14 @@ def print_utf(line): print(line.encode("utf-8")) -def get_pipenv_diagnostics(): +def get_pipenv_diagnostics(project): print("

    $ pipenv --support") print("") - print("Pipenv version: `{0!r}`".format(pipenv.__version__)) + print(f"Pipenv version: `{pipenv.__version__!r}`") print("") - print("Pipenv location: `{0!r}`".format(os.path.dirname(pipenv.__file__))) + print(f"Pipenv location: `{os.path.dirname(pipenv.__file__)!r}`") print("") - print("Python location: `{0!r}`".format(sys.executable)) + print(f"Python location: `{sys.executable!r}`") print("") print("Python installations found:") print("") @@ -32,7 +30,7 @@ def get_pipenv_diagnostics(): finder = pythonfinder.Finder(system=False, global_search=True) python_paths = finder.find_all_python_versions() for python in python_paths: - print(" - `{}`: `{}`".format(python.py_version.version, python.path)) + print(f" - `{python.py_version.version}`: `{python.path}`") print("") print("PEP 508 Information:") @@ -44,43 +42,44 @@ def get_pipenv_diagnostics(): print("System environment variables:") print("") for key in os.environ: - print(" - `{0}`".format(key)) + print(f" - `{key}`") print("") - print_utf(u"Pipenv–specific environment variables:") + print_utf("Pipenv–specific environment variables:") print("") for key in os.environ: if key.startswith("PIPENV"): - print(" - `{0}`: `{1}`".format(key, os.environ[key])) + print(f" - `{key}`: `{os.environ[key]}`") print("") - print_utf(u"Debug–specific environment variables:") + print_utf("Debug–specific environment variables:") print("") for key in ("PATH", "SHELL", "EDITOR", "LANG", "PWD", "VIRTUAL_ENV"): if key in os.environ: - print(" - `{0}`: `{1}`".format(key, os.environ[key])) + print(f" - `{key}`: `{os.environ[key]}`") print("") print("") print("---------------------------") print("") if project.pipfile_exists: - print_utf(u"Contents of `Pipfile` ({0!r}):".format(project.pipfile_location)) + print_utf(f"Contents of `Pipfile` ({project.pipfile_location!r}):") print("") print("```toml") - with open(project.pipfile_location, "r") as f: + with open(project.pipfile_location) as f: print(f.read()) print("```") print("") if project.lockfile_exists: print("") print_utf( - u"Contents of `Pipfile.lock` ({0!r}):".format(project.lockfile_location) + f"Contents of `Pipfile.lock` ({project.lockfile_location!r}):" ) print("") print("```json") - with open(project.lockfile_location, "r") as f: + with open(project.lockfile_location) as f: print(f.read()) print("```") print("
    ") if __name__ == "__main__": - get_pipenv_diagnostics() + from pipenv.project import Project + get_pipenv_diagnostics(Project()) diff --git a/pipenv/installers.py b/pipenv/installers.py index cd8e49a2..5baa1933 100644 --- a/pipenv/installers.py +++ b/pipenv/installers.py @@ -1,12 +1,14 @@ +import os import operator import re +from abc import ABCMeta, abstractmethod -from .environments import PIPENV_INSTALL_TIMEOUT -from .vendor import attr, delegator +from pipenv.vendor import attr +from pipenv.utils import find_windows_executable, subprocess_run @attr.s -class Version(object): +class Version: major = attr.ib() minor = attr.ib() @@ -24,7 +26,7 @@ class Version(object): """ match = re.match(r'^(\d+)\.(\d+)(?:\.(\d+))?$', name) if not match: - raise ValueError('invalid version name {0!r}'.format(name)) + raise ValueError(f'invalid version name {name!r}') major = int(match.group(1)) minor = int(match.group(2)) patch = match.group(3) @@ -48,37 +50,83 @@ class Version(object): return (self.major, self.minor) == (other.major, other.minor) +class InstallerNotFound(RuntimeError): + pass + + class InstallerError(RuntimeError): def __init__(self, desc, c): - super(InstallerError, self).__init__(desc) - self.out = c.out - self.err = c.err + super().__init__(desc) + self.out = c.stdout + self.err = c.stderr -class Installer(object): +class Installer(metaclass=ABCMeta): - def __init__(self, cmd): - self._cmd = cmd + def __init__(self, project): + self.cmd = self._find_installer() + self.project = project def __str__(self): - return self._cmd + return self.__class__.__name__ + + @abstractmethod + def _find_installer(self): + pass + + @staticmethod + def _find_python_installer_by_name_and_env(name, env_var): + """ + Given a python installer (pyenv or asdf), try to locate the binary for that + installer. + + pyenv/asdf are not always present on PATH. Both installers also support a + custom environment variable (PYENV_ROOT or ASDF_DIR) which alows them to + be installed into a non-default location (the default/suggested source + install location is in ~/.pyenv or ~/.asdf). + + For systems without the installers on PATH, and with a custom location + (e.g. /opt/pyenv), Pipenv can use those installers without modifications to + PATH, if an installer's respective environment variable is present in an + environment's .env file. + + This function searches for installer binaries in the following locations, + by precedence: + 1. On PATH, equivalent to which(1). + 2. In the "bin" subdirectory of PYENV_ROOT or ASDF_DIR, depending on the + installer. + 3. In ~/.pyenv/bin or ~/.asdf/bin, depending on the installer. + """ + for candidate in ( + # Look for the Python installer using the equivalent of 'which'. On + # Homebrew-installed systems, the env var may not be set, but this + # strategy will work. + find_windows_executable('', name), + # Check for explicitly set install locations (e.g. PYENV_ROOT, ASDF_DIR). + os.path.join(os.path.expanduser(os.getenv(env_var, '/dev/null')), 'bin', name), + # Check the pyenv/asdf-recommended from-source install locations + os.path.join(os.path.expanduser(f'~/.{name}'), 'bin', name), + ): + if candidate is not None and os.path.isfile(candidate) and os.access(candidate, os.X_OK): + return candidate + raise InstallerNotFound() def _run(self, *args, **kwargs): - timeout = kwargs.pop('timeout', delegator.TIMEOUT) + timeout = kwargs.pop('timeout', 30) if kwargs: k = list(kwargs.keys())[0] - raise TypeError('unexpected keyword argument {0!r}'.format(k)) - args = (self._cmd,) + tuple(args) - c = delegator.run(args, block=False, timeout=timeout) - c.block() - if c.return_code != 0: - raise InstallerError('faild to run {0}'.format(args), c) + raise TypeError(f'unexpected keyword argument {k!r}') + args = (self.cmd,) + tuple(args) + c = subprocess_run(args, timeout=timeout) + if c.returncode != 0: + raise InstallerError(f'failed to run {args}', c) return c + @abstractmethod def iter_installable_versions(self): """Iterate through CPython versions available for Pipenv to install. """ - raise NotImplementedError + pass def find_version_to_install(self, name): """Find a version in the installer from the version supplied. @@ -96,10 +144,11 @@ class Installer(object): ), key=operator.attrgetter('cmpkey')) except ValueError: raise ValueError( - 'no installable version found for {0!r}'.format(name), + f'no installable version found for {name!r}', ) return best_match + @abstractmethod def install(self, version): """Install the given version with runner implementation. @@ -109,15 +158,18 @@ class Installer(object): A ValueError is raised if the given version does not have a match in the runner. A InstallerError is raised if the runner command fails. """ - raise NotImplementedError + pass class Pyenv(Installer): + def _find_installer(self): + return self._find_python_installer_by_name_and_env('pyenv', 'PYENV_ROOT') + def iter_installable_versions(self): """Iterate through CPython versions available for Pipenv to install. """ - for name in self._run('install', '--list').out.splitlines(): + for name in self._run('install', '--list').stdout.splitlines(): try: version = Version.parse(name.strip()) except ValueError: @@ -133,17 +185,20 @@ class Pyenv(Installer): """ c = self._run( 'install', '-s', str(version), - timeout=PIPENV_INSTALL_TIMEOUT, + timeout=self.project.s.PIPENV_INSTALL_TIMEOUT, ) return c class Asdf(Installer): + def _find_installer(self): + return self._find_python_installer_by_name_and_env('asdf', 'ASDF_DIR') + def iter_installable_versions(self): """Iterate through CPython versions available for asdf to install. """ - for name in self._run('list-all', 'python').out.splitlines(): + for name in self._run('list-all', 'python').stdout.splitlines(): try: version = Version.parse(name.strip()) except ValueError: @@ -159,6 +214,6 @@ class Asdf(Installer): """ c = self._run( 'install', 'python', str(version), - timeout=PIPENV_INSTALL_TIMEOUT, + timeout=self.project.s.PIPENV_INSTALL_TIMEOUT, ) return c diff --git a/pipenv/patched/crayons.LICENSE b/pipenv/patched/crayons.LICENSE index 9b00903f..7f0daf5f 100644 --- a/pipenv/patched/crayons.LICENSE +++ b/pipenv/patched/crayons.LICENSE @@ -1,7 +1,22 @@ +The MIT License (MIT) + Copyright 2017 Kenneth Reitz +Copyright 2019 Matthew Peveler -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/pipenv/patched/crayons.py b/pipenv/patched/crayons.py index de735daf..d7644a21 100644 --- a/pipenv/patched/crayons.py +++ b/pipenv/patched/crayons.py @@ -12,8 +12,8 @@ import os import re import sys -import shellingham -import colorama +from pipenv.vendor import shellingham +from pipenv.vendor import colorama PY3 = sys.version_info[0] >= 3 diff --git a/pipenv/patched/notpip/COPYING b/pipenv/patched/notpip/COPYING deleted file mode 100644 index f067af3a..00000000 --- a/pipenv/patched/notpip/COPYING +++ /dev/null @@ -1,14 +0,0 @@ -Copyright (C) 2008-2011 INADA Naoki - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - diff --git a/pipenv/patched/notpip/LICENSE.BSD b/pipenv/patched/notpip/LICENSE.BSD deleted file mode 100644 index 42ce7b75..00000000 --- a/pipenv/patched/notpip/LICENSE.BSD +++ /dev/null @@ -1,23 +0,0 @@ -Copyright (c) Donald Stufft and individual contributors. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/pipenv/patched/notpip/LICENSE.txt b/pipenv/patched/notpip/LICENSE.txt deleted file mode 100644 index d3379fac..00000000 --- a/pipenv/patched/notpip/LICENSE.txt +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2008-2018 The pip developers (see AUTHORS.txt file) - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/pipenv/patched/notpip/__init__.py b/pipenv/patched/notpip/__init__.py index f48c1ca6..9b0b9d29 100644 --- a/pipenv/patched/notpip/__init__.py +++ b/pipenv/patched/notpip/__init__.py @@ -1 +1,13 @@ -__version__ = "19.0.3" +from typing import List, Optional + +__version__ = "21.2.2" + + +def main(args: Optional[List[str]] = None) -> int: + """This is an internal API only meant for use by pip's own console scripts. + + For additional details, see https://github.com/pypa/pip/issues/7498. + """ + from pipenv.patched.notpip._internal.utils.entrypoints import _wrapper + + return _wrapper(args) diff --git a/pipenv/patched/notpip/__main__.py b/pipenv/patched/notpip/__main__.py index a4879980..b5c1fce5 100644 --- a/pipenv/patched/notpip/__main__.py +++ b/pipenv/patched/notpip/__main__.py @@ -1,19 +1,32 @@ -from __future__ import absolute_import - import os import sys +import warnings + +# Remove '' and current working directory from the first entry +# of sys.path, if present to avoid using current directory +# in pip commands check, freeze, install, list and show, +# when invoked as python -m pip +if sys.path[0] in ("", os.getcwd()): + sys.path.pop(0) # If we are running from a wheel, add the wheel to sys.path # This allows the usage python pip-*.whl/pip install pip-*.whl -if __package__ == '': +if __package__ == "": # __file__ is pip-*.whl/pip/__main__.py # first dirname call strips of '/__main__.py', second strips off '/pip' # Resulting path is the name of the wheel itself - # Add that to sys.path so we can import pipenv.patched.notpip + # Add that to sys.path so we can import pip path = os.path.dirname(os.path.dirname(__file__)) sys.path.insert(0, path) -from pipenv.patched.notpip._internal import main as _main # isort:skip # noqa +if __name__ == "__main__": + # Work around the error reported in #9540, pending a proper fix. + # Note: It is essential the warning filter is set *before* importing + # pip, as the deprecation happens at import time, not runtime. + warnings.filterwarnings( + "ignore", category=DeprecationWarning, module=".*packaging\\.version" + ) + sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))) + from pipenv.patched.notpip._internal.cli.main import main as _main -if __name__ == '__main__': sys.exit(_main()) diff --git a/pipenv/patched/notpip/_internal/__init__.py b/pipenv/patched/notpip/_internal/__init__.py index 6d223928..b5c9aff2 100644 --- a/pipenv/patched/notpip/_internal/__init__.py +++ b/pipenv/patched/notpip/_internal/__init__.py @@ -1,78 +1,19 @@ -#!/usr/bin/env python -from __future__ import absolute_import +from typing import List, Optional -import locale -import logging -import os -import warnings +import pipenv.patched.notpip._internal.utils.inject_securetransport # noqa +from pipenv.patched.notpip._internal.utils import _log -import sys - -# 2016-06-17 barry@debian.org: urllib3 1.14 added optional support for socks, -# but if invoked (i.e. imported), it will issue a warning to stderr if socks -# isn't available. requests unconditionally imports urllib3's socks contrib -# module, triggering this warning. The warning breaks DEP-8 tests (because of -# the stderr output) and is just plain annoying in normal usage. I don't want -# to add socks as yet another dependency for pip, nor do I want to allow-stder -# in the DEP-8 tests, so just suppress the warning. pdb tells me this has to -# be done before the import of pip.vcs. -from pipenv.patched.notpip._vendor.urllib3.exceptions import DependencyWarning -warnings.filterwarnings("ignore", category=DependencyWarning) # noqa - -# We want to inject the use of SecureTransport as early as possible so that any -# references or sessions or what have you are ensured to have it, however we -# only want to do this in the case that we're running on macOS and the linked -# OpenSSL is too old to handle TLSv1.2 -try: - import ssl -except ImportError: - pass -else: - # Checks for OpenSSL 1.0.1 on MacOS - if sys.platform == "darwin" and ssl.OPENSSL_VERSION_NUMBER < 0x1000100f: - try: - from pipenv.patched.notpip._vendor.urllib3.contrib import securetransport - except (ImportError, OSError): - pass - else: - securetransport.inject_into_urllib3() - -from pipenv.patched.notpip._internal.cli.autocompletion import autocomplete -from pipenv.patched.notpip._internal.cli.main_parser import parse_command -from pipenv.patched.notpip._internal.commands import commands_dict -from pipenv.patched.notpip._internal.exceptions import PipError -from pipenv.patched.notpip._internal.utils import deprecation -from pipenv.patched.notpip._internal.vcs import git, mercurial, subversion, bazaar # noqa -from pipenv.patched.notpip._vendor.urllib3.exceptions import InsecureRequestWarning - -logger = logging.getLogger(__name__) - -# Hide the InsecureRequestWarning from urllib3 -warnings.filterwarnings("ignore", category=InsecureRequestWarning) +# init_logging() must be called before any call to logging.getLogger() +# which happens at import of most modules. +_log.init_logging() -def main(args=None): - if args is None: - args = sys.argv[1:] +def main(args: (Optional[List[str]]) = None) -> int: + """This is preserved for old console scripts that may still be referencing + it. - # Configure our deprecation warnings to be sent through loggers - deprecation.install_warning_logger() + For additional details, see https://github.com/pypa/pip/issues/7498. + """ + from pipenv.patched.notpip._internal.utils.entrypoints import _wrapper - autocomplete() - - try: - cmd_name, cmd_args = parse_command(args) - except PipError as exc: - sys.stderr.write("ERROR: %s" % exc) - sys.stderr.write(os.linesep) - sys.exit(1) - - # Needed for locale.getpreferredencoding(False) to work - # in pip._internal.utils.encoding.auto_decode - try: - locale.setlocale(locale.LC_ALL, '') - except locale.Error as e: - # setlocale can apparently crash if locale are uninitialized - logger.debug("Ignoring error %s when setting locale", e) - command = commands_dict[cmd_name](isolated=("--isolated" in cmd_args)) - return command.main(cmd_args) + return _wrapper(args) diff --git a/pipenv/patched/notpip/_internal/build_env.py b/pipenv/patched/notpip/_internal/build_env.py index d38adc49..d8c66b3f 100644 --- a/pipenv/patched/notpip/_internal/build_env.py +++ b/pipenv/patched/notpip/_internal/build_env.py @@ -1,25 +1,31 @@ """Build Environment used for isolation during sdist building """ +import contextlib import logging import os +import pathlib import sys import textwrap +import zipfile from collections import OrderedDict -from distutils.sysconfig import get_python_lib from sysconfig import get_paths +from types import TracebackType +from typing import TYPE_CHECKING, Iterable, Iterator, List, Optional, Set, Tuple, Type -from pipenv.patched.notpip._vendor.pkg_resources import Requirement, VersionConflict, WorkingSet +from pipenv.patched.notpip._vendor.certifi import where +from pipenv.patched.notpip._vendor.packaging.requirements import Requirement +from pipenv.patched.notpip._vendor.packaging.version import Version -from pipenv.patched.notpip import __file__ as pip_location -from pipenv.patched.notpip._internal.utils.misc import call_subprocess -from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING -from pipenv.patched.notpip._internal.utils.ui import open_spinner +from pip import __file__ as pip_location +from pipenv.patched.notpip._internal.cli.spinners import open_spinner +from pipenv.patched.notpip._internal.locations import get_platlib, get_prefixed_libs, get_purelib +from pipenv.patched.notpip._internal.metadata import get_environment +from pipenv.patched.notpip._internal.utils.subprocess import call_subprocess +from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory, tempdir_kinds -if MYPY_CHECK_RUNNING: - from typing import Tuple, Set, Iterable, Optional, List # noqa: F401 - from pipenv.patched.notpip._internal.index import PackageFinder # noqa: F401 +if TYPE_CHECKING: + from pipenv.patched.notpip._internal.index.package_finder import PackageFinder logger = logging.getLogger(__name__) @@ -34,29 +40,49 @@ class _Prefix: 'nt' if os.name == 'nt' else 'posix_prefix', vars={'base': path, 'platbase': path} )['scripts'] - # Note: prefer distutils' sysconfig to get the - # library paths so PyPy is correctly supported. - purelib = get_python_lib(plat_specific=False, prefix=path) - platlib = get_python_lib(plat_specific=True, prefix=path) - if purelib == platlib: - self.lib_dirs = [purelib] - else: - self.lib_dirs = [purelib, platlib] + self.lib_dirs = get_prefixed_libs(path) -class BuildEnvironment(object): +@contextlib.contextmanager +def _create_standalone_pip() -> Iterator[str]: + """Create a "standalone pip" zip file. + + The zip file's content is identical to the currently-running pip. + It will be used to install requirements into the build environment. + """ + source = pathlib.Path(pip_location).resolve().parent + + # Return the current instance if `source` is not a directory. We can't build + # a zip from this, and it likely means the instance is already standalone. + if not source.is_dir(): + yield str(source) + return + + with TempDirectory(kind="standalone-pip") as tmp_dir: + pip_zip = os.path.join(tmp_dir.path, "__env_pip__.zip") + kwargs = {} + if sys.version_info >= (3, 8): + kwargs["strict_timestamps"] = False + with zipfile.ZipFile(pip_zip, "w", **kwargs) as zf: + for child in source.rglob("*"): + zf.write(child, child.relative_to(source.parent).as_posix()) + yield os.path.join(pip_zip, "pip") + + +class BuildEnvironment: """Creates and manages an isolated environment to install build deps """ def __init__(self): # type: () -> None - self._temp_dir = TempDirectory(kind="build-env") - self._temp_dir.create() + temp_dir = TempDirectory( + kind=tempdir_kinds.BUILD_ENV, globally_managed=True + ) - self._prefixes = OrderedDict(( - (name, _Prefix(os.path.join(self._temp_dir.path, name))) + self._prefixes = OrderedDict( + (name, _Prefix(os.path.join(temp_dir.path, name))) for name in ('normal', 'overlay') - )) + ) self._bin_dirs = [] # type: List[str] self._lib_dirs = [] # type: List[str] @@ -68,12 +94,9 @@ class BuildEnvironment(object): # - ensure .pth files are honored # - prevent access to system site packages system_sites = { - os.path.normcase(site) for site in ( - get_python_lib(plat_specific=False), - get_python_lib(plat_specific=True), - ) + os.path.normcase(site) for site in (get_purelib(), get_platlib()) } - self._site_dir = os.path.join(self._temp_dir.path, 'site') + self._site_dir = os.path.join(temp_dir.path, 'site') if not os.path.exists(self._site_dir): os.mkdir(self._site_dir) with open(os.path.join(self._site_dir, 'sitecustomize.py'), 'w') as fp: @@ -105,6 +128,7 @@ class BuildEnvironment(object): ).format(system_sites=system_sites, lib_dirs=self._lib_dirs)) def __enter__(self): + # type: () -> None self._save_env = { name: os.environ.get(name, None) for name in ('PATH', 'PYTHONNOUSERSITE', 'PYTHONPATH') @@ -123,17 +147,19 @@ class BuildEnvironment(object): 'PYTHONPATH': os.pathsep.join(pythonpath), }) - def __exit__(self, exc_type, exc_val, exc_tb): + def __exit__( + self, + exc_type, # type: Optional[Type[BaseException]] + exc_val, # type: Optional[BaseException] + exc_tb # type: Optional[TracebackType] + ): + # type: (...) -> None for varname, old_value in self._save_env.items(): if old_value is None: os.environ.pop(varname, None) else: os.environ[varname] = old_value - def cleanup(self): - # type: () -> None - self._temp_dir.cleanup() - def check_requirements(self, reqs): # type: (Iterable[str]) -> Tuple[Set[Tuple[str, str]], Set[str]] """Return 2 sets: @@ -143,14 +169,20 @@ class BuildEnvironment(object): missing = set() conflicting = set() if reqs: - ws = WorkingSet(self._lib_dirs) - for req in reqs: - try: - if ws.find(Requirement.parse(req)) is None: - missing.add(req) - except VersionConflict as e: - conflicting.add((str(e.args[0].as_requirement()), - str(e.args[1]))) + env = get_environment(self._lib_dirs) + for req_str in reqs: + req = Requirement(req_str) + dist = env.get_distribution(req.name) + if not dist: + missing.add(req_str) + continue + if isinstance(dist.version, Version): + installed_req_str = f"{req.name}=={dist.version}" + else: + installed_req_str = f"{req.name}==={dist.version}" + if dist.version not in req.specifier: + conflicting.add((installed_req_str, req_str)) + # FIXME: Consider direct URL? return conflicting, missing def install_requirements( @@ -158,7 +190,7 @@ class BuildEnvironment(object): finder, # type: PackageFinder requirements, # type: Iterable[str] prefix_as_string, # type: str - message # type: Optional[str] + message # type: str ): # type: (...) -> None prefix = self._prefixes[prefix_as_string] @@ -166,8 +198,34 @@ class BuildEnvironment(object): prefix.setup = True if not requirements: return + with contextlib.ExitStack() as ctx: + # TODO: Remove this block when dropping 3.6 support. Python 3.6 + # lacks importlib.resources and pep517 has issues loading files in + # a zip, so we fallback to the "old" method by adding the current + # pip directory to the child process's sys.path. + if sys.version_info < (3, 7): + pip_runnable = os.path.dirname(pip_location) + else: + pip_runnable = ctx.enter_context(_create_standalone_pip()) + self._install_requirements( + pip_runnable, + finder, + requirements, + prefix, + message, + ) + + @staticmethod + def _install_requirements( + pip_runnable: str, + finder: "PackageFinder", + requirements: Iterable[str], + prefix: _Prefix, + message: str, + ) -> None: + sys_executable = os.environ.get('PIP_PYTHON_PATH', sys.executable) args = [ - sys.executable, os.path.dirname(pip_location), 'install', + sys_executable, pip_runnable, 'install', '--ignore-installed', '--no-user', '--prefix', prefix.path, '--no-warn-script-location', ] # type: List[str] @@ -177,22 +235,28 @@ class BuildEnvironment(object): formats = getattr(finder.format_control, format_control) args.extend(('--' + format_control.replace('_', '-'), ','.join(sorted(formats or {':none:'})))) - if finder.index_urls: - args.extend(['-i', finder.index_urls[0]]) - for extra_index in finder.index_urls[1:]: + + index_urls = finder.index_urls + if index_urls: + args.extend(['-i', index_urls[0]]) + for extra_index in index_urls[1:]: args.extend(['--extra-index-url', extra_index]) else: args.append('--no-index') for link in finder.find_links: args.extend(['--find-links', link]) - for _, host, _ in finder.secure_origins: + + for host in finder.trusted_hosts: args.extend(['--trusted-host', host]) if finder.allow_all_prereleases: args.append('--pre') + if finder.prefer_binary: + args.append('--prefer-binary') args.append('--') args.extend(requirements) + extra_environ = {"_PIP_STANDALONE_CERT": where()} with open_spinner(message) as spinner: - call_subprocess(args, show_stdout=False, spinner=spinner) + call_subprocess(args, spinner=spinner, extra_environ=extra_environ) class NoOpBuildEnvironment(BuildEnvironment): @@ -200,16 +264,32 @@ class NoOpBuildEnvironment(BuildEnvironment): """ def __init__(self): + # type: () -> None pass def __enter__(self): + # type: () -> None pass - def __exit__(self, exc_type, exc_val, exc_tb): + def __exit__( + self, + exc_type, # type: Optional[Type[BaseException]] + exc_val, # type: Optional[BaseException] + exc_tb # type: Optional[TracebackType] + ): + # type: (...) -> None pass def cleanup(self): + # type: () -> None pass - def install_requirements(self, finder, requirements, prefix, message): + def install_requirements( + self, + finder, # type: PackageFinder + requirements, # type: Iterable[str] + prefix_as_string, # type: str + message # type: str + ): + # type: (...) -> None raise NotImplementedError() diff --git a/pipenv/patched/notpip/_internal/cache.py b/pipenv/patched/notpip/_internal/cache.py index 9f35e83d..5e53d986 100644 --- a/pipenv/patched/notpip/_internal/cache.py +++ b/pipenv/patched/notpip/_internal/cache.py @@ -1,28 +1,33 @@ """Cache Management """ -import errno import hashlib +import json import logging import os +from typing import Any, Dict, List, Optional, Set +from pipenv.patched.notpip._vendor.packaging.tags import Tag, interpreter_name, interpreter_version from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name -from pipenv.patched.notpip._internal.download import path_to_url +from pipenv.patched.notpip._internal.exceptions import InvalidWheelFilename +from pipenv.patched.notpip._internal.models.format_control import FormatControl from pipenv.patched.notpip._internal.models.link import Link -from pipenv.patched.notpip._internal.utils.compat import expanduser -from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING -from pipenv.patched.notpip._internal.wheel import InvalidWheelFilename, Wheel - -if MYPY_CHECK_RUNNING: - from typing import Optional, Set, List, Any # noqa: F401 - from pipenv.patched.notpip._internal.index import FormatControl # noqa: F401 +from pipenv.patched.notpip._internal.models.wheel import Wheel +from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory, tempdir_kinds +from pipenv.patched.notpip._internal.utils.urls import path_to_url logger = logging.getLogger(__name__) -class Cache(object): +def _hash_dict(d): + # type: (Dict[str, str]) -> str + """Return a stable sha224 of a dictionary.""" + s = json.dumps(d, sort_keys=True, separators=(",", ":"), ensure_ascii=True) + return hashlib.sha224(s.encode("ascii")).hexdigest() + + +class Cache: """An abstract class - provides cache directories for data from links @@ -35,8 +40,9 @@ class Cache(object): def __init__(self, cache_dir, format_control, allowed_formats): # type: (str, FormatControl, Set[str]) -> None - super(Cache, self).__init__() - self.cache_dir = expanduser(cache_dir) if cache_dir else None + super().__init__() + assert not cache_dir or os.path.isabs(cache_dir) + self.cache_dir = cache_dir or None self.format_control = format_control self.allowed_formats = allowed_formats @@ -51,16 +57,25 @@ class Cache(object): # We want to generate an url to use as our cache key, we don't want to # just re-use the URL because it might have other items in the fragment # and we don't care about those. - key_parts = [link.url_without_fragment] + key_parts = {"url": link.url_without_fragment} if link.hash_name is not None and link.hash is not None: - key_parts.append("=".join([link.hash_name, link.hash])) - key_url = "#".join(key_parts) + key_parts[link.hash_name] = link.hash + if link.subdirectory_fragment: + key_parts["subdirectory"] = link.subdirectory_fragment + + # Include interpreter name, major and minor version in cache key + # to cope with ill-behaved sdists that build a different wheel + # depending on the python version their setup.py is being run on, + # and don't encode the difference in compatibility tags. + # https://github.com/pypa/pip/issues/7296 + key_parts["interpreter_name"] = interpreter_name() + key_parts["interpreter_version"] = interpreter_version() # Encode our key url with sha224, we'll use this because it has similar # security properties to sha256, but with a shorter total output (and # thus less secure). However the differences don't make a lot of # difference for our use case here. - hashed = hashlib.sha224(key_url.encode()).hexdigest() + hashed = _hash_dict(key_parts) # We want to nest the directories some to prevent having a ton of top # level directories where we might run out of sub directories on some @@ -69,30 +84,28 @@ class Cache(object): return parts - def _get_candidates(self, link, package_name): - # type: (Link, Optional[str]) -> List[Any] + def _get_candidates(self, link, canonical_package_name): + # type: (Link, str) -> List[Any] can_not_cache = ( not self.cache_dir or - not package_name or + not canonical_package_name or not link ) if can_not_cache: return [] - canonical_name = canonicalize_name(package_name) formats = self.format_control.get_allowed_formats( - canonical_name + canonical_package_name ) if not self.allowed_formats.intersection(formats): return [] - root = self.get_path_for_link(link) - try: - return os.listdir(root) - except OSError as err: - if err.errno in {errno.ENOENT, errno.ENOTDIR}: - return [] - raise + candidates = [] + path = self.get_path_for_link(link) + if os.path.isdir(path): + for candidate in os.listdir(path): + candidates.append((candidate, path)) + return candidates def get_path_for_link(self, link): # type: (Link) -> str @@ -100,24 +113,18 @@ class Cache(object): """ raise NotImplementedError() - def get(self, link, package_name): - # type: (Link, Optional[str]) -> Link + def get( + self, + link, # type: Link + package_name, # type: Optional[str] + supported_tags, # type: List[Tag] + ): + # type: (...) -> Link """Returns a link to a cached item if it exists, otherwise returns the passed link. """ raise NotImplementedError() - def _link_for_candidate(self, link, candidate): - # type: (Link, str) -> Link - root = self.get_path_for_link(link) - path = os.path.join(root, candidate) - - return Link(path_to_url(path)) - - def cleanup(self): - # type: () -> None - pass - class SimpleWheelCache(Cache): """A cache of wheels for future installs. @@ -125,9 +132,7 @@ class SimpleWheelCache(Cache): def __init__(self, cache_dir, format_control): # type: (str, FormatControl) -> None - super(SimpleWheelCache, self).__init__( - cache_dir, format_control, {"binary"} - ) + super().__init__(cache_dir, format_control, {"binary"}) def get_path_for_link(self, link): # type: (Link) -> str @@ -146,28 +151,53 @@ class SimpleWheelCache(Cache): :param link: The link of the sdist for which this will cache wheels. """ parts = self._get_cache_path_parts(link) - + assert self.cache_dir # Store wheels within the root cache_dir return os.path.join(self.cache_dir, "wheels", *parts) - def get(self, link, package_name): - # type: (Link, Optional[str]) -> Link + def get( + self, + link, # type: Link + package_name, # type: Optional[str] + supported_tags, # type: List[Tag] + ): + # type: (...) -> Link candidates = [] - for wheel_name in self._get_candidates(link, package_name): + if not package_name: + return link + + canonical_package_name = canonicalize_name(package_name) + for wheel_name, wheel_dir in self._get_candidates( + link, canonical_package_name + ): try: wheel = Wheel(wheel_name) except InvalidWheelFilename: continue - if not wheel.supported(): + if canonicalize_name(wheel.name) != canonical_package_name: + logger.debug( + "Ignoring cached wheel %s for %s as it " + "does not match the expected distribution name %s.", + wheel_name, link, package_name, + ) + continue + if not wheel.supported(supported_tags): # Built for a different python/arch/etc continue - candidates.append((wheel.support_index_min(), wheel_name)) + candidates.append( + ( + wheel.support_index_min(supported_tags), + wheel_name, + wheel_dir, + ) + ) if not candidates: return link - return self._link_for_candidate(link, min(candidates)[1]) + _, wheel_name, wheel_dir = min(candidates) + return Link(path_to_url(os.path.join(wheel_dir, wheel_name))) class EphemWheelCache(SimpleWheelCache): @@ -176,16 +206,22 @@ class EphemWheelCache(SimpleWheelCache): def __init__(self, format_control): # type: (FormatControl) -> None - self._temp_dir = TempDirectory(kind="ephem-wheel-cache") - self._temp_dir.create() - - super(EphemWheelCache, self).__init__( - self._temp_dir.path, format_control + self._temp_dir = TempDirectory( + kind=tempdir_kinds.EPHEM_WHEEL_CACHE, + globally_managed=True, ) - def cleanup(self): - # type: () -> None - self._temp_dir.cleanup() + super().__init__(self._temp_dir.path, format_control) + + +class CacheEntry: + def __init__( + self, + link, # type: Link + persistent, # type: bool + ): + self.link = link + self.persistent = persistent class WheelCache(Cache): @@ -197,9 +233,7 @@ class WheelCache(Cache): def __init__(self, cache_dir, format_control): # type: (str, FormatControl) -> None - super(WheelCache, self).__init__( - cache_dir, format_control, {'binary'} - ) + super().__init__(cache_dir, format_control, {'binary'}) self._wheel_cache = SimpleWheelCache(cache_dir, format_control) self._ephem_cache = EphemWheelCache(format_control) @@ -211,14 +245,43 @@ class WheelCache(Cache): # type: (Link) -> str return self._ephem_cache.get_path_for_link(link) - def get(self, link, package_name): - # type: (Link, Optional[str]) -> Link - retval = self._wheel_cache.get(link, package_name) - if retval is link: - retval = self._ephem_cache.get(link, package_name) - return retval + def get( + self, + link, # type: Link + package_name, # type: Optional[str] + supported_tags, # type: List[Tag] + ): + # type: (...) -> Link + cache_entry = self.get_cache_entry(link, package_name, supported_tags) + if cache_entry is None: + return link + return cache_entry.link - def cleanup(self): - # type: () -> None - self._wheel_cache.cleanup() - self._ephem_cache.cleanup() + def get_cache_entry( + self, + link, # type: Link + package_name, # type: Optional[str] + supported_tags, # type: List[Tag] + ): + # type: (...) -> Optional[CacheEntry] + """Returns a CacheEntry with a link to a cached item if it exists or + None. The cache entry indicates if the item was found in the persistent + or ephemeral cache. + """ + retval = self._wheel_cache.get( + link=link, + package_name=package_name, + supported_tags=supported_tags, + ) + if retval is not link: + return CacheEntry(retval, persistent=True) + + retval = self._ephem_cache.get( + link=link, + package_name=package_name, + supported_tags=supported_tags, + ) + if retval is not link: + return CacheEntry(retval, persistent=False) + + return None diff --git a/pipenv/patched/notpip/_internal/cli/autocompletion.py b/pipenv/patched/notpip/_internal/cli/autocompletion.py index 15b560a1..2648f29f 100644 --- a/pipenv/patched/notpip/_internal/cli/autocompletion.py +++ b/pipenv/patched/notpip/_internal/cli/autocompletion.py @@ -4,57 +4,62 @@ import optparse import os import sys +from itertools import chain +from typing import Any, Iterable, List, Optional from pipenv.patched.notpip._internal.cli.main_parser import create_main_parser -from pipenv.patched.notpip._internal.commands import commands_dict, get_summaries -from pipenv.patched.notpip._internal.utils.misc import get_installed_distributions +from pipenv.patched.notpip._internal.commands import commands_dict, create_command +from pipenv.patched.notpip._internal.metadata import get_default_environment -def autocomplete(): - """Entry Point for completion of main and subcommand options. - """ +def autocomplete() -> None: + """Entry Point for completion of main and subcommand options.""" # Don't complete if user hasn't sourced bash_completion file. - if 'PIP_AUTO_COMPLETE' not in os.environ: + if "PIP_AUTO_COMPLETE" not in os.environ: return - cwords = os.environ['COMP_WORDS'].split()[1:] - cword = int(os.environ['COMP_CWORD']) + cwords = os.environ["COMP_WORDS"].split()[1:] + cword = int(os.environ["COMP_CWORD"]) try: current = cwords[cword - 1] except IndexError: - current = '' - - subcommands = [cmd for cmd, summary in get_summaries()] - options = [] - # subcommand - try: - subcommand_name = [w for w in cwords if w in subcommands][0] - except IndexError: - subcommand_name = None + current = "" parser = create_main_parser() + subcommands = list(commands_dict) + options = [] + + # subcommand + subcommand_name: Optional[str] = None + for word in cwords: + if word in subcommands: + subcommand_name = word + break # subcommand options - if subcommand_name: + if subcommand_name is not None: # special case: 'help' subcommand has no options - if subcommand_name == 'help': + if subcommand_name == "help": sys.exit(1) # special case: list locally installed dists for show and uninstall - should_list_installed = ( - subcommand_name in ['show', 'uninstall'] and - not current.startswith('-') - ) + should_list_installed = not current.startswith("-") and subcommand_name in [ + "show", + "uninstall", + ] if should_list_installed: - installed = [] + env = get_default_environment() lc = current.lower() - for dist in get_installed_distributions(local_only=True): - if dist.key.startswith(lc) and dist.key not in cwords[1:]: - installed.append(dist.key) + installed = [ + dist.canonical_name + for dist in env.iter_installed_distributions(local_only=True) + if dist.canonical_name.startswith(lc) + and dist.canonical_name not in cwords[1:] + ] # if there are no dists installed, fall back to option completion if installed: for dist in installed: print(dist) sys.exit(1) - subcommand = commands_dict[subcommand_name]() + subcommand = create_command(subcommand_name) for opt in subcommand.parser.option_list_all: if opt.help != optparse.SUPPRESS_HELP: @@ -62,46 +67,50 @@ def autocomplete(): options.append((opt_str, opt.nargs)) # filter out previously specified options from available options - prev_opts = [x.split('=')[0] for x in cwords[1:cword - 1]] + prev_opts = [x.split("=")[0] for x in cwords[1 : cword - 1]] options = [(x, v) for (x, v) in options if x not in prev_opts] # filter options by current input options = [(k, v) for k, v in options if k.startswith(current)] # get completion type given cwords and available subcommand options completion_type = get_path_completion_type( - cwords, cword, subcommand.parser.option_list_all, + cwords, + cword, + subcommand.parser.option_list_all, ) # get completion files and directories if ``completion_type`` is # ````, ```` or ```` if completion_type: - options = auto_complete_paths(current, completion_type) - options = ((opt, 0) for opt in options) + paths = auto_complete_paths(current, completion_type) + options = [(path, 0) for path in paths] for option in options: opt_label = option[0] # append '=' to options which require args if option[1] and option[0][:2] == "--": - opt_label += '=' + opt_label += "=" print(opt_label) else: # show main parser options only when necessary opts = [i.option_list for i in parser.option_groups] opts.append(parser.option_list) - opts = (o for it in opts for o in it) - if current.startswith('-'): - for opt in opts: + flattened_opts = chain.from_iterable(opts) + if current.startswith("-"): + for opt in flattened_opts: if opt.help != optparse.SUPPRESS_HELP: subcommands += opt._long_opts + opt._short_opts else: # get completion type given cwords and all available options - completion_type = get_path_completion_type(cwords, cword, opts) + completion_type = get_path_completion_type(cwords, cword, flattened_opts) if completion_type: - subcommands = auto_complete_paths(current, completion_type) + subcommands = list(auto_complete_paths(current, completion_type)) - print(' '.join([x for x in subcommands if x.startswith(current)])) + print(" ".join([x for x in subcommands if x.startswith(current)])) sys.exit(1) -def get_path_completion_type(cwords, cword, opts): +def get_path_completion_type( + cwords: List[str], cword: int, opts: Iterable[Any] +) -> Optional[str]: """Get the type of path completion (``file``, ``dir``, ``path`` or None) :param cwords: same as the environmental variable ``COMP_WORDS`` @@ -109,20 +118,21 @@ def get_path_completion_type(cwords, cword, opts): :param opts: The available options to check :return: path completion type (``file``, ``dir``, ``path`` or None) """ - if cword < 2 or not cwords[cword - 2].startswith('-'): - return + if cword < 2 or not cwords[cword - 2].startswith("-"): + return None for opt in opts: if opt.help == optparse.SUPPRESS_HELP: continue - for o in str(opt).split('/'): - if cwords[cword - 2].split('=')[0] == o: + for o in str(opt).split("/"): + if cwords[cword - 2].split("=")[0] == o: if not opt.metavar or any( - x in ('path', 'file', 'dir') - for x in opt.metavar.split('/')): + x in ("path", "file", "dir") for x in opt.metavar.split("/") + ): return opt.metavar + return None -def auto_complete_paths(current, completion_type): +def auto_complete_paths(current: str, completion_type: str) -> Iterable[str]: """If ``completion_type`` is ``file`` or ``path``, list all regular files and directories starting with ``current``; otherwise only list directories starting with ``current``. @@ -138,15 +148,16 @@ def auto_complete_paths(current, completion_type): return filename = os.path.normcase(filename) # list all files that start with ``filename`` - file_list = (x for x in os.listdir(current_path) - if os.path.normcase(x).startswith(filename)) + file_list = ( + x for x in os.listdir(current_path) if os.path.normcase(x).startswith(filename) + ) for f in file_list: opt = os.path.join(current_path, f) comp_file = os.path.normcase(os.path.join(directory, f)) # complete regular files when there is not ```` after option # complete directories when there is ````, ```` or # ````after option - if completion_type != 'dir' and os.path.isfile(opt): + if completion_type != "dir" and os.path.isfile(opt): yield comp_file elif os.path.isdir(opt): - yield os.path.join(comp_file, '') + yield os.path.join(comp_file, "") diff --git a/pipenv/patched/notpip/_internal/cli/base_command.py b/pipenv/patched/notpip/_internal/cli/base_command.py index 4aa16da6..b4b03432 100644 --- a/pipenv/patched/notpip/_internal/cli/base_command.py +++ b/pipenv/patched/notpip/_internal/cli/base_command.py @@ -1,74 +1,67 @@ """Base Command class, and related routines""" -from __future__ import absolute_import, print_function import logging import logging.config import optparse import os -import platform import sys import traceback +from optparse import Values +from typing import Any, List, Optional, Tuple from pipenv.patched.notpip._internal.cli import cmdoptions -from pipenv.patched.notpip._internal.cli.parser import ( - ConfigOptionParser, UpdatingDefaultsHelpFormatter, -) +from pipenv.patched.notpip._internal.cli.command_context import CommandContextMixIn +from pipenv.patched.notpip._internal.cli.parser import ConfigOptionParser, UpdatingDefaultsHelpFormatter from pipenv.patched.notpip._internal.cli.status_codes import ( - ERROR, PREVIOUS_BUILD_DIR_ERROR, SUCCESS, UNKNOWN_ERROR, + ERROR, + PREVIOUS_BUILD_DIR_ERROR, + UNKNOWN_ERROR, VIRTUALENV_NOT_FOUND, ) -from pipenv.patched.notpip._internal.download import PipSession from pipenv.patched.notpip._internal.exceptions import ( - BadCommand, CommandError, InstallationError, PreviousBuildDirError, + BadCommand, + CommandError, + InstallationError, + NetworkConnectionError, + PreviousBuildDirError, UninstallationError, ) -from pipenv.patched.notpip._internal.index import PackageFinder -from pipenv.patched.notpip._internal.locations import running_under_virtualenv -from pipenv.patched.notpip._internal.req.constructors import ( - install_req_from_editable, install_req_from_line, -) -from pipenv.patched.notpip._internal.req.req_file import parse_requirements from pipenv.patched.notpip._internal.utils.deprecation import deprecated +from pipenv.patched.notpip._internal.utils.filesystem import check_path_owner from pipenv.patched.notpip._internal.utils.logging import BrokenStdoutLoggingError, setup_logging -from pipenv.patched.notpip._internal.utils.misc import ( - get_prog, normalize_path, redact_password_from_url, -) -from pipenv.patched.notpip._internal.utils.outdated import pip_version_check -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING +from pipenv.patched.notpip._internal.utils.misc import get_prog, normalize_path +from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectoryTypeRegistry as TempDirRegistry +from pipenv.patched.notpip._internal.utils.temp_dir import global_tempdir_manager, tempdir_registry +from pipenv.patched.notpip._internal.utils.virtualenv import running_under_virtualenv -if MYPY_CHECK_RUNNING: - from typing import Optional, List, Tuple, Any # noqa: F401 - from optparse import Values # noqa: F401 - from pipenv.patched.notpip._internal.cache import WheelCache # noqa: F401 - from pipenv.patched.notpip._internal.req.req_set import RequirementSet # noqa: F401 - -__all__ = ['Command'] +__all__ = ["Command"] logger = logging.getLogger(__name__) -class Command(object): - name = None # type: Optional[str] - usage = None # type: Optional[str] - hidden = False # type: bool - ignore_require_venv = False # type: bool +class Command(CommandContextMixIn): + usage: str = "" + ignore_require_venv: bool = False - def __init__(self, isolated=False): - # type: (bool) -> None - parser_kw = { - 'usage': self.usage, - 'prog': '%s %s' % (get_prog(), self.name), - 'formatter': UpdatingDefaultsHelpFormatter(), - 'add_help_option': False, - 'name': self.name, - 'description': self.__doc__, - 'isolated': isolated, - } + def __init__(self, name: str, summary: str, isolated: bool = False) -> None: + super().__init__() - self.parser = ConfigOptionParser(**parser_kw) + self.name = name + self.summary = summary + self.parser = ConfigOptionParser( + usage=self.usage, + prog=f"{get_prog()} {name}", + formatter=UpdatingDefaultsHelpFormatter(), + add_help_option=False, + name=name, + description=self.__doc__, + isolated=isolated, + ) + + self.tempdir_registry: Optional[TempDirRegistry] = None # Commands should add options to this option group - optgroup_name = '%s Options' % self.name.capitalize() + optgroup_name = f"{self.name.capitalize()} Options" self.cmd_opts = optparse.OptionGroup(self.parser, optgroup_name) # Add the general options @@ -78,54 +71,43 @@ class Command(object): ) self.parser.add_option_group(gen_opts) - def run(self, options, args): - # type: (Values, List[Any]) -> Any + self.add_options() + + def add_options(self) -> None: + pass + + def handle_pip_version_check(self, options: Values) -> None: + """ + This is a no-op so that commands by default do not do the pip version + check. + """ + # Make sure we do the pip version check if the index_group options + # are present. + assert not hasattr(options, "no_index") + + def run(self, options: Values, args: List[Any]) -> int: raise NotImplementedError - def _build_session(self, options, retries=None, timeout=None): - # type: (Values, Optional[int], Optional[int]) -> PipSession - session = PipSession( - cache=( - normalize_path(os.path.join(options.cache_dir, "http")) - if options.cache_dir else None - ), - retries=retries if retries is not None else options.retries, - insecure_hosts=options.trusted_hosts, - ) - - # Handle custom ca-bundles from the user - if options.cert: - session.verify = options.cert - - # Handle SSL client certificate - if options.client_cert: - session.cert = options.client_cert - - # Handle timeouts - if options.timeout or timeout: - session.timeout = ( - timeout if timeout is not None else options.timeout - ) - - # Handle configured proxies - if options.proxy: - session.proxies = { - "http": options.proxy, - "https": options.proxy, - } - - # Determine if we can prompt the user for authentication or not - session.auth.prompting = not options.no_input - - return session - - def parse_args(self, args): - # type: (List[str]) -> Tuple + def parse_args(self, args: List[str]) -> Tuple[Any, Any]: # factored out for testability return self.parser.parse_args(args) - def main(self, args): - # type: (List[str]) -> int + def main(self, args: List[str]) -> int: + try: + with self.main_context(): + return self._main(args) + finally: + logging.shutdown() + + def _main(self, args: List[str]) -> int: + # We must initialize this before the tempdir manager, otherwise the + # configuration would not be accessible by the time we clean up the + # tempdir manager. + self.tempdir_registry = self.enter_context(tempdir_registry()) + # Intentionally set as early as possible so globally-managed temporary + # directories are available to the rest of the code. + self.enter_context(global_tempdir_manager()) + options, args = self.parse_args(args) # Set verbosity so that it can be used elsewhere. @@ -137,205 +119,96 @@ class Command(object): user_log_file=options.log, ) - if sys.version_info[:2] == (3, 4): - deprecated( - "Python 3.4 support has been deprecated. pip 19.1 will be the " - "last one supporting it. Please upgrade your Python as Python " - "3.4 won't be maintained after March 2019 (cf PEP 429).", - replacement=None, - gone_in='19.2', - ) - elif sys.version_info[:2] == (2, 7): - message = ( - "A future version of pip will drop support for Python 2.7." - ) - if platform.python_implementation() == "CPython": - message = ( - "Python 2.7 will reach the end of its life on January " - "1st, 2020. Please upgrade your Python as Python 2.7 " - "won't be maintained after that date. " - ) + message - deprecated(message, replacement=None, gone_in=None) - # TODO: Try to get these passing down from the command? # without resorting to os.environ to hold these. # This also affects isolated builds and it should. if options.no_input: - os.environ['PIP_NO_INPUT'] = '1' + os.environ["PIP_NO_INPUT"] = "1" if options.exists_action: - os.environ['PIP_EXISTS_ACTION'] = ' '.join(options.exists_action) + os.environ["PIP_EXISTS_ACTION"] = " ".join(options.exists_action) if options.require_venv and not self.ignore_require_venv: # If a venv is required check if it can really be found if not running_under_virtualenv(): - logger.critical( - 'Could not find an activated virtualenv (required).' - ) + logger.critical("Could not find an activated virtualenv (required).") sys.exit(VIRTUALENV_NOT_FOUND) + if options.cache_dir: + options.cache_dir = normalize_path(options.cache_dir) + if not check_path_owner(options.cache_dir): + logger.warning( + "The directory '%s' or its parent directory is not owned " + "or is not writable by the current user. The cache " + "has been disabled. Check the permissions and owner of " + "that directory. If executing pip with sudo, you should " + "use sudo's -H flag.", + options.cache_dir, + ) + options.cache_dir = None + + if getattr(options, "build_dir", None): + deprecated( + reason=( + "The -b/--build/--build-dir/--build-directory " + "option is deprecated and has no effect anymore." + ), + replacement=( + "use the TMPDIR/TEMP/TMP environment variable, " + "possibly combined with --no-clean" + ), + gone_in="21.3", + issue=8333, + ) + + if "2020-resolver" in options.features_enabled: + logger.warning( + "--use-feature=2020-resolver no longer has any effect, " + "since it is now the default dependency resolver in pip. " + "This will become an error in pip 21.0." + ) + try: status = self.run(options, args) - # FIXME: all commands should return an exit status - # and when it is done, isinstance is not needed anymore - if isinstance(status, int): - return status + assert isinstance(status, int) + return status except PreviousBuildDirError as exc: logger.critical(str(exc)) - logger.debug('Exception information:', exc_info=True) + logger.debug("Exception information:", exc_info=True) return PREVIOUS_BUILD_DIR_ERROR - except (InstallationError, UninstallationError, BadCommand) as exc: + except ( + InstallationError, + UninstallationError, + BadCommand, + NetworkConnectionError, + ) as exc: logger.critical(str(exc)) - logger.debug('Exception information:', exc_info=True) + logger.debug("Exception information:", exc_info=True) return ERROR except CommandError as exc: - logger.critical('ERROR: %s', exc) - logger.debug('Exception information:', exc_info=True) + logger.critical("%s", exc) + logger.debug("Exception information:", exc_info=True) return ERROR except BrokenStdoutLoggingError: # Bypass our logger and write any remaining messages to stderr # because stdout no longer works. - print('ERROR: Pipe to stdout was broken', file=sys.stderr) + print("ERROR: Pipe to stdout was broken", file=sys.stderr) if level_number <= logging.DEBUG: traceback.print_exc(file=sys.stderr) return ERROR except KeyboardInterrupt: - logger.critical('Operation cancelled by user') - logger.debug('Exception information:', exc_info=True) + logger.critical("Operation cancelled by user") + logger.debug("Exception information:", exc_info=True) return ERROR except BaseException: - logger.critical('Exception:', exc_info=True) + logger.critical("Exception:", exc_info=True) return UNKNOWN_ERROR finally: - allow_version_check = ( - # Does this command have the index_group options? - hasattr(options, "no_index") and - # Is this command allowed to perform this check? - not (options.disable_pip_version_check or options.no_index) - ) - # Check if we're using the latest version of pip available - if allow_version_check: - session = self._build_session( - options, - retries=0, - timeout=min(5, options.timeout) - ) - with session: - pip_version_check(session, options) - - # Shutdown the logging module - logging.shutdown() - - return SUCCESS - - -class RequirementCommand(Command): - - @staticmethod - def populate_requirement_set(requirement_set, # type: RequirementSet - args, # type: List[str] - options, # type: Values - finder, # type: PackageFinder - session, # type: PipSession - name, # type: str - wheel_cache # type: Optional[WheelCache] - ): - # type: (...) -> None - """ - Marshal cmd line args into a requirement set. - """ - # NOTE: As a side-effect, options.require_hashes and - # requirement_set.require_hashes may be updated - - for filename in options.constraints: - for req_to_add in parse_requirements( - filename, - constraint=True, finder=finder, options=options, - session=session, wheel_cache=wheel_cache): - req_to_add.is_direct = True - requirement_set.add_requirement(req_to_add) - - for req in args: - req_to_add = install_req_from_line( - req, None, isolated=options.isolated_mode, - use_pep517=options.use_pep517, - wheel_cache=wheel_cache - ) - req_to_add.is_direct = True - requirement_set.add_requirement(req_to_add) - - for req in options.editables: - req_to_add = install_req_from_editable( - req, - isolated=options.isolated_mode, - use_pep517=options.use_pep517, - wheel_cache=wheel_cache - ) - req_to_add.is_direct = True - requirement_set.add_requirement(req_to_add) - - for filename in options.requirements: - for req_to_add in parse_requirements( - filename, - finder=finder, options=options, session=session, - wheel_cache=wheel_cache, - use_pep517=options.use_pep517): - req_to_add.is_direct = True - requirement_set.add_requirement(req_to_add) - # If --require-hashes was a line in a requirements file, tell - # RequirementSet about it: - requirement_set.require_hashes = options.require_hashes - - if not (args or options.editables or options.requirements): - opts = {'name': name} - if options.find_links: - raise CommandError( - 'You must give at least one requirement to %(name)s ' - '(maybe you meant "pip %(name)s %(links)s"?)' % - dict(opts, links=' '.join(options.find_links))) - else: - raise CommandError( - 'You must give at least one requirement to %(name)s ' - '(see "pip help %(name)s")' % opts) - - def _build_package_finder( - self, - options, # type: Values - session, # type: PipSession - platform=None, # type: Optional[str] - python_versions=None, # type: Optional[List[str]] - abi=None, # type: Optional[str] - implementation=None # type: Optional[str] - ): - # type: (...) -> PackageFinder - """ - Create a package finder appropriate to this requirement command. - """ - index_urls = [options.index_url] + options.extra_index_urls - if options.no_index: - logger.debug( - 'Ignoring indexes: %s', - ','.join(redact_password_from_url(url) for url in index_urls), - ) - index_urls = [] - - return PackageFinder( - find_links=options.find_links, - format_control=options.format_control, - index_urls=index_urls, - trusted_hosts=options.trusted_hosts, - allow_all_prereleases=options.pre, - session=session, - platform=platform, - versions=python_versions, - abi=abi, - implementation=implementation, - prefer_binary=options.prefer_binary, - ) + self.handle_pip_version_check(options) diff --git a/pipenv/patched/notpip/_internal/cli/cmdoptions.py b/pipenv/patched/notpip/_internal/cli/cmdoptions.py index 3c5a7084..1b72229c 100644 --- a/pipenv/patched/notpip/_internal/cli/cmdoptions.py +++ b/pipenv/patched/notpip/_internal/cli/cmdoptions.py @@ -5,31 +5,33 @@ The principle here is to define options once, but *not* instantiate them globally. One reason being that options with action='append' can carry state between parses. pip parses general options twice internally, and shouldn't pass on state. To be consistent, all options will follow this design. - """ -from __future__ import absolute_import +# The following comment should be removed at some point in the future. +# mypy: strict-optional=False + +import os import textwrap import warnings -from distutils.util import strtobool from functools import partial -from optparse import SUPPRESS_HELP, Option, OptionGroup +from optparse import SUPPRESS_HELP, Option, OptionGroup, OptionParser, Values +from textwrap import dedent +from typing import Any, Callable, Dict, Optional, Tuple +from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name + +from pipenv.patched.notpip._internal.cli.parser import ConfigOptionParser +from pipenv.patched.notpip._internal.cli.progress_bars import BAR_TYPES from pipenv.patched.notpip._internal.exceptions import CommandError -from pipenv.patched.notpip._internal.locations import USER_CACHE_DIR, src_prefix +from pipenv.patched.notpip._internal.locations import USER_CACHE_DIR, get_src_prefix from pipenv.patched.notpip._internal.models.format_control import FormatControl from pipenv.patched.notpip._internal.models.index import PyPI +from pipenv.patched.notpip._internal.models.target_python import TargetPython from pipenv.patched.notpip._internal.utils.hashes import STRONG_HASHES -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING -from pipenv.patched.notpip._internal.utils.ui import BAR_TYPES - -if MYPY_CHECK_RUNNING: - from typing import Any, Callable, Dict, List, Optional, Union # noqa: F401 - from optparse import OptionParser, Values # noqa: F401 - from pipenv.patched.notpip._internal.cli.parser import ConfigOptionParser # noqa: F401 +from pipenv.patched.notpip._internal.utils.misc import strtobool -def raise_option_error(parser, option, msg): +def raise_option_error(parser: OptionParser, option: Option, msg: str) -> None: """ Raise an option parsing error using parser.error(). @@ -38,26 +40,26 @@ def raise_option_error(parser, option, msg): option: an Option instance. msg: the error text. """ - msg = '{} error: {}'.format(option, msg) - msg = textwrap.fill(' '.join(msg.split())) + msg = f"{option} error: {msg}" + msg = textwrap.fill(" ".join(msg.split())) parser.error(msg) -def make_option_group(group, parser): - # type: (Dict[str, Any], ConfigOptionParser) -> OptionGroup +def make_option_group(group: Dict[str, Any], parser: ConfigOptionParser) -> OptionGroup: """ Return an OptionGroup object group -- assumed to be dict with 'name' and 'options' keys parser -- an optparse Parser """ - option_group = OptionGroup(parser, group['name']) - for option in group['options']: + option_group = OptionGroup(parser, group["name"]) + for option in group["options"]: option_group.add_option(option()) return option_group -def check_install_build_global(options, check_options=None): - # type: (Values, Optional[Values]) -> None +def check_install_build_global( + options: Values, check_options: Optional[Values] = None +) -> None: """Disable wheels if per-setup.py call options are set. :param options: The OptionParser options to update. @@ -67,41 +69,43 @@ def check_install_build_global(options, check_options=None): if check_options is None: check_options = options - def getname(n): + def getname(n: str) -> Optional[Any]: return getattr(check_options, n, None) + names = ["build_options", "global_options", "install_options"] if any(map(getname, names)): control = options.format_control control.disallow_binaries() warnings.warn( - 'Disabling all use of wheels due to the use of --build-options ' - '/ --global-options / --install-options.', stacklevel=2, + "Disabling all use of wheels due to the use of --build-option " + "/ --global-option / --install-option.", + stacklevel=2, ) -def check_dist_restriction(options, check_target=False): - # type: (Values, bool) -> None +def check_dist_restriction(options: Values, check_target: bool = False) -> None: """Function for determining if custom platform options are allowed. :param options: The OptionParser options. :param check_target: Whether or not to check if --target is being used. """ - dist_restriction_set = any([ - options.python_version, - options.platform, - options.abi, - options.implementation, - ]) + dist_restriction_set = any( + [ + options.python_version, + options.platforms, + options.abis, + options.implementation, + ] + ) - binary_only = FormatControl(set(), {':all:'}) + binary_only = FormatControl(set(), {":all:"}) sdist_dependencies_allowed = ( - options.format_control != binary_only and - not options.ignore_dependencies + options.format_control != binary_only and not options.ignore_dependencies ) # Installations or downloads using dist restrictions must not combine # source distributions and dist-specific wheels, as they are not - # gauranteed to be locally compatible. + # guaranteed to be locally compatible. if dist_restriction_set and sdist_dependencies_allowed: raise CommandError( "When restricting platform and interpreter constraints using " @@ -119,19 +123,35 @@ def check_dist_restriction(options, check_target=False): ) +def _path_option_check(option: Option, opt: str, value: str) -> str: + return os.path.expanduser(value) + + +def _package_name_option_check(option: Option, opt: str, value: str) -> str: + return canonicalize_name(value) + + +class PipOption(Option): + TYPES = Option.TYPES + ("path", "package_name") + TYPE_CHECKER = Option.TYPE_CHECKER.copy() + TYPE_CHECKER["package_name"] = _package_name_option_check + TYPE_CHECKER["path"] = _path_option_check + + ########### # options # ########### -help_ = partial( +help_: Callable[..., Option] = partial( Option, - '-h', '--help', - dest='help', - action='help', - help='Show help.', -) # type: Callable[..., Option] + "-h", + "--help", + dest="help", + action="help", + help="Show help.", +) -isolated_mode = partial( +isolated_mode: Callable[..., Option] = partial( Option, "--isolated", dest="isolated_mode", @@ -141,409 +161,519 @@ isolated_mode = partial( "Run pip in an isolated mode, ignoring environment variables and user " "configuration." ), -) # type: Callable[..., Option] +) -require_virtualenv = partial( +require_virtualenv: Callable[..., Option] = partial( Option, # Run only if inside a virtualenv, bail if not. - '--require-virtualenv', '--require-venv', - dest='require_venv', - action='store_true', + "--require-virtualenv", + "--require-venv", + dest="require_venv", + action="store_true", default=False, - help=SUPPRESS_HELP -) # type: Callable[..., Option] + help=SUPPRESS_HELP, +) -verbose = partial( +verbose: Callable[..., Option] = partial( Option, - '-v', '--verbose', - dest='verbose', - action='count', + "-v", + "--verbose", + dest="verbose", + action="count", default=0, - help='Give more output. Option is additive, and can be used up to 3 times.' -) # type: Callable[..., Option] + help="Give more output. Option is additive, and can be used up to 3 times.", +) -no_color = partial( +no_color: Callable[..., Option] = partial( Option, - '--no-color', - dest='no_color', - action='store_true', + "--no-color", + dest="no_color", + action="store_true", default=False, - help="Suppress colored output", -) # type: Callable[..., Option] + help="Suppress colored output.", +) -version = partial( +version: Callable[..., Option] = partial( Option, - '-V', '--version', - dest='version', - action='store_true', - help='Show version and exit.', -) # type: Callable[..., Option] + "-V", + "--version", + dest="version", + action="store_true", + help="Show version and exit.", +) -quiet = partial( +quiet: Callable[..., Option] = partial( Option, - '-q', '--quiet', - dest='quiet', - action='count', + "-q", + "--quiet", + dest="quiet", + action="count", default=0, help=( - 'Give less output. Option is additive, and can be used up to 3' - ' times (corresponding to WARNING, ERROR, and CRITICAL logging' - ' levels).' + "Give less output. Option is additive, and can be used up to 3" + " times (corresponding to WARNING, ERROR, and CRITICAL logging" + " levels)." ), -) # type: Callable[..., Option] +) -progress_bar = partial( +progress_bar: Callable[..., Option] = partial( Option, - '--progress-bar', - dest='progress_bar', - type='choice', + "--progress-bar", + dest="progress_bar", + type="choice", choices=list(BAR_TYPES.keys()), - default='on', + default="on", help=( - 'Specify type of progress to be displayed [' + - '|'.join(BAR_TYPES.keys()) + '] (default: %default)' + "Specify type of progress to be displayed [" + + "|".join(BAR_TYPES.keys()) + + "] (default: %default)" ), -) # type: Callable[..., Option] +) -log = partial( - Option, - "--log", "--log-file", "--local-log", +log: Callable[..., Option] = partial( + PipOption, + "--log", + "--log-file", + "--local-log", dest="log", metavar="path", - help="Path to a verbose appending log." -) # type: Callable[..., Option] + type="path", + help="Path to a verbose appending log.", +) -no_input = partial( +no_input: Callable[..., Option] = partial( Option, # Don't ask for input - '--no-input', - dest='no_input', - action='store_true', + "--no-input", + dest="no_input", + action="store_true", default=False, - help=SUPPRESS_HELP -) # type: Callable[..., Option] + help="Disable prompting for input.", +) -proxy = partial( +proxy: Callable[..., Option] = partial( Option, - '--proxy', - dest='proxy', - type='str', - default='', - help="Specify a proxy in the form [user:passwd@]proxy.server:port." -) # type: Callable[..., Option] + "--proxy", + dest="proxy", + type="str", + default="", + help="Specify a proxy in the form [user:passwd@]proxy.server:port.", +) -retries = partial( +retries: Callable[..., Option] = partial( Option, - '--retries', - dest='retries', - type='int', + "--retries", + dest="retries", + type="int", default=5, help="Maximum number of retries each connection should attempt " - "(default %default times).", -) # type: Callable[..., Option] + "(default %default times).", +) -timeout = partial( +timeout: Callable[..., Option] = partial( Option, - '--timeout', '--default-timeout', - metavar='sec', - dest='timeout', - type='float', + "--timeout", + "--default-timeout", + metavar="sec", + dest="timeout", + type="float", default=15, - help='Set the socket timeout (default %default seconds).', -) # type: Callable[..., Option] - -skip_requirements_regex = partial( - Option, - # A regex to be used to skip requirements - '--skip-requirements-regex', - dest='skip_requirements_regex', - type='str', - default='', - help=SUPPRESS_HELP, -) # type: Callable[..., Option] + help="Set the socket timeout (default %default seconds).", +) -def exists_action(): - # type: () -> Option +def exists_action() -> Option: return Option( # Option when path already exist - '--exists-action', - dest='exists_action', - type='choice', - choices=['s', 'i', 'w', 'b', 'a'], + "--exists-action", + dest="exists_action", + type="choice", + choices=["s", "i", "w", "b", "a"], default=[], - action='append', - metavar='action', + action="append", + metavar="action", help="Default action when a path already exists: " - "(s)witch, (i)gnore, (w)ipe, (b)ackup, (a)bort).", + "(s)witch, (i)gnore, (w)ipe, (b)ackup, (a)bort.", ) -cert = partial( - Option, - '--cert', - dest='cert', - type='str', - metavar='path', - help="Path to alternate CA bundle.", -) # type: Callable[..., Option] +cert: Callable[..., Option] = partial( + PipOption, + "--cert", + dest="cert", + type="path", + metavar="path", + help=( + "Path to PEM-encoded CA certificate bundle. " + "If provided, overrides the default. " + "See 'SSL Certificate Verification' in pip documentation " + "for more information." + ), +) -client_cert = partial( - Option, - '--client-cert', - dest='client_cert', - type='str', +client_cert: Callable[..., Option] = partial( + PipOption, + "--client-cert", + dest="client_cert", + type="path", default=None, - metavar='path', + metavar="path", help="Path to SSL client certificate, a single file containing the " - "private key and the certificate in PEM format.", -) # type: Callable[..., Option] + "private key and the certificate in PEM format.", +) -index_url = partial( +index_url: Callable[..., Option] = partial( Option, - '-i', '--index-url', '--pypi-url', - dest='index_url', - metavar='URL', + "-i", + "--index-url", + "--pypi-url", + dest="index_url", + metavar="URL", default=PyPI.simple_url, - help="Base URL of Python Package Index (default %default). " - "This should point to a repository compliant with PEP 503 " - "(the simple repository API) or a local directory laid out " - "in the same format.", -) # type: Callable[..., Option] + help="Base URL of the Python Package Index (default %default). " + "This should point to a repository compliant with PEP 503 " + "(the simple repository API) or a local directory laid out " + "in the same format.", +) -def extra_index_url(): +def extra_index_url() -> Option: return Option( - '--extra-index-url', - dest='extra_index_urls', - metavar='URL', - action='append', + "--extra-index-url", + dest="extra_index_urls", + metavar="URL", + action="append", default=[], help="Extra URLs of package indexes to use in addition to " - "--index-url. Should follow the same rules as " - "--index-url.", + "--index-url. Should follow the same rules as " + "--index-url.", ) -no_index = partial( +no_index: Callable[..., Option] = partial( Option, - '--no-index', - dest='no_index', - action='store_true', + "--no-index", + dest="no_index", + action="store_true", default=False, - help='Ignore package index (only looking at --find-links URLs instead).', -) # type: Callable[..., Option] + help="Ignore package index (only looking at --find-links URLs instead).", +) -def find_links(): - # type: () -> Option +def find_links() -> Option: return Option( - '-f', '--find-links', - dest='find_links', - action='append', + "-f", + "--find-links", + dest="find_links", + action="append", default=[], - metavar='url', - help="If a url or path to an html file, then parse for links to " - "archives. If a local path or file:// url that's a directory, " - "then look for archives in the directory listing.", + metavar="url", + help="If a URL or path to an html file, then parse for links to " + "archives such as sdist (.tar.gz) or wheel (.whl) files. " + "If a local path or file:// URL that's a directory, " + "then look for archives in the directory listing. " + "Links to VCS project URLs are not supported.", ) -def trusted_host(): - # type: () -> Option +def trusted_host() -> Option: return Option( "--trusted-host", dest="trusted_hosts", action="append", metavar="HOSTNAME", default=[], - help="Mark this host as trusted, even though it does not have valid " - "or any HTTPS.", + help="Mark this host or host:port pair as trusted, even though it " + "does not have valid or any HTTPS.", ) -def constraints(): - # type: () -> Option +def constraints() -> Option: return Option( - '-c', '--constraint', - dest='constraints', - action='append', + "-c", + "--constraint", + dest="constraints", + action="append", default=[], - metavar='file', - help='Constrain versions using the given constraints file. ' - 'This option can be used multiple times.' + metavar="file", + help="Constrain versions using the given constraints file. " + "This option can be used multiple times.", ) -def requirements(): - # type: () -> Option +def requirements() -> Option: return Option( - '-r', '--requirement', - dest='requirements', - action='append', + "-r", + "--requirement", + dest="requirements", + action="append", default=[], - metavar='file', - help='Install from the given requirements file. ' - 'This option can be used multiple times.' + metavar="file", + help="Install from the given requirements file. " + "This option can be used multiple times.", ) -def editable(): - # type: () -> Option +def editable() -> Option: return Option( - '-e', '--editable', - dest='editables', - action='append', + "-e", + "--editable", + dest="editables", + action="append", default=[], - metavar='path/url', - help=('Install a project in editable mode (i.e. setuptools ' - '"develop mode") from a local project path or a VCS url.'), + metavar="path/url", + help=( + "Install a project in editable mode (i.e. setuptools " + '"develop mode") from a local project path or a VCS url.' + ), ) -src = partial( - Option, - '--src', '--source', '--source-dir', '--source-directory', - dest='src_dir', - metavar='dir', - default=src_prefix, - help='Directory to check out editable projects into. ' +def _handle_src(option: Option, opt_str: str, value: str, parser: OptionParser) -> None: + value = os.path.abspath(value) + setattr(parser.values, option.dest, value) + + +src: Callable[..., Option] = partial( + PipOption, + "--src", + "--source", + "--source-dir", + "--source-directory", + dest="src_dir", + type="path", + metavar="dir", + default=get_src_prefix(), + action="callback", + callback=_handle_src, + help="Directory to check out editable projects into. " 'The default in a virtualenv is "/src". ' - 'The default for global installs is "/src".' -) # type: Callable[..., Option] + 'The default for global installs is "/src".', +) -def _get_format_control(values, option): - # type: (Values, Option) -> Any +def _get_format_control(values: Values, option: Option) -> Any: """Get a format_control object.""" return getattr(values, option.dest) -def _handle_no_binary(option, opt_str, value, parser): - # type: (Option, str, str, OptionParser) -> None +def _handle_no_binary( + option: Option, opt_str: str, value: str, parser: OptionParser +) -> None: existing = _get_format_control(parser.values, option) FormatControl.handle_mutual_excludes( - value, existing.no_binary, existing.only_binary, + value, + existing.no_binary, + existing.only_binary, ) -def _handle_only_binary(option, opt_str, value, parser): - # type: (Option, str, str, OptionParser) -> None +def _handle_only_binary( + option: Option, opt_str: str, value: str, parser: OptionParser +) -> None: existing = _get_format_control(parser.values, option) FormatControl.handle_mutual_excludes( - value, existing.only_binary, existing.no_binary, + value, + existing.only_binary, + existing.no_binary, ) -def no_binary(): - # type: () -> Option +def no_binary() -> Option: format_control = FormatControl(set(), set()) return Option( - "--no-binary", dest="format_control", action="callback", - callback=_handle_no_binary, type="str", + "--no-binary", + dest="format_control", + action="callback", + callback=_handle_no_binary, + type="str", default=format_control, help="Do not use binary packages. Can be supplied multiple times, and " - "each time adds to the existing value. Accepts either :all: to " - "disable all binary packages, :none: to empty the set, or one or " - "more package names with commas between them. Note that some " - "packages are tricky to compile and may fail to install when " - "this option is used on them.", + 'each time adds to the existing value. Accepts either ":all:" to ' + 'disable all binary packages, ":none:" to empty the set (notice ' + "the colons), or one or more package names with commas between " + "them (no colons). Note that some packages are tricky to compile " + "and may fail to install when this option is used on them.", ) -def only_binary(): - # type: () -> Option +def only_binary() -> Option: format_control = FormatControl(set(), set()) return Option( - "--only-binary", dest="format_control", action="callback", - callback=_handle_only_binary, type="str", + "--only-binary", + dest="format_control", + action="callback", + callback=_handle_only_binary, + type="str", default=format_control, help="Do not use source packages. Can be supplied multiple times, and " - "each time adds to the existing value. Accepts either :all: to " - "disable all source packages, :none: to empty the set, or one or " - "more package names with commas between them. Packages without " - "binary distributions will fail to install when this option is " - "used on them.", + 'each time adds to the existing value. Accepts either ":all:" to ' + 'disable all source packages, ":none:" to empty the set, or one ' + "or more package names with commas between them. Packages " + "without binary distributions will fail to install when this " + "option is used on them.", ) -platform = partial( +platforms: Callable[..., Option] = partial( Option, - '--platform', - dest='platform', - metavar='platform', + "--platform", + dest="platforms", + metavar="platform", + action="append", default=None, - help=("Only use wheels compatible with . " - "Defaults to the platform of the running system."), -) # type: Callable[..., Option] + help=( + "Only use wheels compatible with . Defaults to the " + "platform of the running system. Use this option multiple times to " + "specify multiple platforms supported by the target interpreter." + ), +) -python_version = partial( +# This was made a separate function for unit-testing purposes. +def _convert_python_version(value: str) -> Tuple[Tuple[int, ...], Optional[str]]: + """ + Convert a version string like "3", "37", or "3.7.3" into a tuple of ints. + + :return: A 2-tuple (version_info, error_msg), where `error_msg` is + non-None if and only if there was a parsing error. + """ + if not value: + # The empty string is the same as not providing a value. + return (None, None) + + parts = value.split(".") + if len(parts) > 3: + return ((), "at most three version parts are allowed") + + if len(parts) == 1: + # Then we are in the case of "3" or "37". + value = parts[0] + if len(value) > 1: + parts = [value[0], value[1:]] + + try: + version_info = tuple(int(part) for part in parts) + except ValueError: + return ((), "each version part must be an integer") + + return (version_info, None) + + +def _handle_python_version( + option: Option, opt_str: str, value: str, parser: OptionParser +) -> None: + """ + Handle a provided --python-version value. + """ + version_info, error_msg = _convert_python_version(value) + if error_msg is not None: + msg = "invalid --python-version value: {!r}: {}".format( + value, + error_msg, + ) + raise_option_error(parser, option=option, msg=msg) + + parser.values.python_version = version_info + + +python_version: Callable[..., Option] = partial( Option, - '--python-version', - dest='python_version', - metavar='python_version', + "--python-version", + dest="python_version", + metavar="python_version", + action="callback", + callback=_handle_python_version, + type="str", default=None, - help=("Only use wheels compatible with Python " - "interpreter version . If not specified, then the " - "current system interpreter minor version is used. A major " - "version (e.g. '2') can be specified to match all " - "minor revs of that major version. A minor version " - "(e.g. '34') can also be specified."), -) # type: Callable[..., Option] + help=dedent( + """\ + The Python interpreter version to use for wheel and "Requires-Python" + compatibility checks. Defaults to a version derived from the running + interpreter. The version can be specified using up to three dot-separated + integers (e.g. "3" for 3.0.0, "3.7" for 3.7.0, or "3.7.3"). A major-minor + version can also be given as a string without dots (e.g. "37" for 3.7.0). + """ + ), +) -implementation = partial( +implementation: Callable[..., Option] = partial( Option, - '--implementation', - dest='implementation', - metavar='implementation', + "--implementation", + dest="implementation", + metavar="implementation", default=None, - help=("Only use wheels compatible with Python " - "implementation , e.g. 'pp', 'jy', 'cp', " - " or 'ip'. If not specified, then the current " - "interpreter implementation is used. Use 'py' to force " - "implementation-agnostic wheels."), -) # type: Callable[..., Option] + help=( + "Only use wheels compatible with Python " + "implementation , e.g. 'pp', 'jy', 'cp', " + " or 'ip'. If not specified, then the current " + "interpreter implementation is used. Use 'py' to force " + "implementation-agnostic wheels." + ), +) -abi = partial( +abis: Callable[..., Option] = partial( Option, - '--abi', - dest='abi', - metavar='abi', + "--abi", + dest="abis", + metavar="abi", + action="append", default=None, - help=("Only use wheels compatible with Python " - "abi , e.g. 'pypy_41'. If not specified, then the " - "current interpreter abi tag is used. Generally " - "you will need to specify --implementation, " - "--platform, and --python-version when using " - "this option."), -) # type: Callable[..., Option] + help=( + "Only use wheels compatible with Python abi , e.g. 'pypy_41'. " + "If not specified, then the current interpreter abi tag is used. " + "Use this option multiple times to specify multiple abis supported " + "by the target interpreter. Generally you will need to specify " + "--implementation, --platform, and --python-version when using this " + "option." + ), +) -def prefer_binary(): - # type: () -> Option +def add_target_python_options(cmd_opts: OptionGroup) -> None: + cmd_opts.add_option(platforms()) + cmd_opts.add_option(python_version()) + cmd_opts.add_option(implementation()) + cmd_opts.add_option(abis()) + + +def make_target_python(options: Values) -> TargetPython: + target_python = TargetPython( + platforms=options.platforms, + py_version_info=options.python_version, + abis=options.abis, + implementation=options.implementation, + ) + + return target_python + + +def prefer_binary() -> Option: return Option( "--prefer-binary", dest="prefer_binary", action="store_true", default=False, - help="Prefer older binary packages over newer source packages." + help="Prefer older binary packages over newer source packages.", ) -cache_dir = partial( - Option, +cache_dir: Callable[..., Option] = partial( + PipOption, "--cache-dir", dest="cache_dir", default=USER_CACHE_DIR, metavar="dir", - help="Store the cache data in ." -) # type: Callable[..., Option] + type="path", + help="Store the cache data in .", +) -def no_cache_dir_callback(option, opt, value, parser): +def _handle_no_cache_dir( + option: Option, opt: str, value: str, parser: OptionParser +) -> None: """ Process a value provided for the --no-cache-dir option. @@ -570,57 +700,60 @@ def no_cache_dir_callback(option, opt, value, parser): parser.values.cache_dir = False -no_cache = partial( +no_cache: Callable[..., Option] = partial( Option, "--no-cache-dir", dest="cache_dir", action="callback", - callback=no_cache_dir_callback, + callback=_handle_no_cache_dir, help="Disable the cache.", -) # type: Callable[..., Option] +) -no_deps = partial( +no_deps: Callable[..., Option] = partial( Option, - '--no-deps', '--no-dependencies', - dest='ignore_dependencies', - action='store_true', + "--no-deps", + "--no-dependencies", + dest="ignore_dependencies", + action="store_true", default=False, help="Don't install package dependencies.", -) # type: Callable[..., Option] +) -build_dir = partial( - Option, - '-b', '--build', '--build-dir', '--build-directory', - dest='build_dir', - metavar='dir', - help='Directory to unpack packages into and build in. Note that ' - 'an initial build still takes place in a temporary directory. ' - 'The location of temporary directories can be controlled by setting ' - 'the TMPDIR environment variable (TEMP on Windows) appropriately. ' - 'When passed, build directories are not cleaned in case of failures.' -) # type: Callable[..., Option] +build_dir: Callable[..., Option] = partial( + PipOption, + "-b", + "--build", + "--build-dir", + "--build-directory", + dest="build_dir", + type="path", + metavar="dir", + help=SUPPRESS_HELP, +) -ignore_requires_python = partial( +ignore_requires_python: Callable[..., Option] = partial( Option, - '--ignore-requires-python', - dest='ignore_requires_python', - action='store_true', - help='Ignore the Requires-Python information.' -) # type: Callable[..., Option] + "--ignore-requires-python", + dest="ignore_requires_python", + action="store_true", + help="Ignore the Requires-Python information.", +) -no_build_isolation = partial( +no_build_isolation: Callable[..., Option] = partial( Option, - '--no-build-isolation', - dest='build_isolation', - action='store_false', + "--no-build-isolation", + dest="build_isolation", + action="store_false", default=True, - help='Disable isolation when building a modern source distribution. ' - 'Build dependencies specified by PEP 518 must be already installed ' - 'if this option is used.' -) # type: Callable[..., Option] + help="Disable isolation when building a modern source distribution. " + "Build dependencies specified by PEP 518 must be already installed " + "if this option is used.", +) -def no_use_pep517_callback(option, opt, value, parser): +def _handle_no_use_pep517( + option: Option, opt: str, value: str, parser: OptionParser +) -> None: """ Process a value provided for the --no-use-pep517 option. @@ -643,138 +776,203 @@ def no_use_pep517_callback(option, opt, value, parser): parser.values.use_pep517 = False -use_pep517 = partial( +use_pep517: Any = partial( Option, - '--use-pep517', - dest='use_pep517', - action='store_true', + "--use-pep517", + dest="use_pep517", + action="store_true", default=None, - help='Use PEP 517 for building source distributions ' - '(use --no-use-pep517 to force legacy behaviour).' -) # type: Any + help="Use PEP 517 for building source distributions " + "(use --no-use-pep517 to force legacy behaviour).", +) -no_use_pep517 = partial( +no_use_pep517: Any = partial( Option, - '--no-use-pep517', - dest='use_pep517', - action='callback', - callback=no_use_pep517_callback, + "--no-use-pep517", + dest="use_pep517", + action="callback", + callback=_handle_no_use_pep517, default=None, - help=SUPPRESS_HELP -) # type: Any + help=SUPPRESS_HELP, +) -install_options = partial( +install_options: Callable[..., Option] = partial( Option, - '--install-option', - dest='install_options', - action='append', - metavar='options', + "--install-option", + dest="install_options", + action="append", + metavar="options", help="Extra arguments to be supplied to the setup.py install " - "command (use like --install-option=\"--install-scripts=/usr/local/" - "bin\"). Use multiple --install-option options to pass multiple " - "options to setup.py install. If you are using an option with a " - "directory path, be sure to use absolute path.", -) # type: Callable[..., Option] + 'command (use like --install-option="--install-scripts=/usr/local/' + 'bin"). Use multiple --install-option options to pass multiple ' + "options to setup.py install. If you are using an option with a " + "directory path, be sure to use absolute path.", +) -global_options = partial( +build_options: Callable[..., Option] = partial( Option, - '--global-option', - dest='global_options', - action='append', - metavar='options', + "--build-option", + dest="build_options", + metavar="options", + action="append", + help="Extra arguments to be supplied to 'setup.py bdist_wheel'.", +) + +global_options: Callable[..., Option] = partial( + Option, + "--global-option", + dest="global_options", + action="append", + metavar="options", help="Extra global options to be supplied to the setup.py " - "call before the install command.", -) # type: Callable[..., Option] + "call before the install or bdist_wheel command.", +) -no_clean = partial( +no_clean: Callable[..., Option] = partial( Option, - '--no-clean', - action='store_true', + "--no-clean", + action="store_true", default=False, - help="Don't clean up build directories." -) # type: Callable[..., Option] + help="Don't clean up build directories.", +) -pre = partial( +pre: Callable[..., Option] = partial( Option, - '--pre', - action='store_true', + "--pre", + action="store_true", default=False, help="Include pre-release and development versions. By default, " - "pip only finds stable versions.", -) # type: Callable[..., Option] + "pip only finds stable versions.", +) -disable_pip_version_check = partial( +disable_pip_version_check: Callable[..., Option] = partial( Option, "--disable-pip-version-check", dest="disable_pip_version_check", action="store_true", default=False, help="Don't periodically check PyPI to determine whether a new version " - "of pip is available for download. Implied with --no-index.", -) # type: Callable[..., Option] + "of pip is available for download. Implied with --no-index.", +) -# Deprecated, Remove later -always_unzip = partial( - Option, - '-Z', '--always-unzip', - dest='always_unzip', - action='store_true', - help=SUPPRESS_HELP, -) # type: Callable[..., Option] - - -def _merge_hash(option, opt_str, value, parser): - # type: (Option, str, str, OptionParser) -> None +def _handle_merge_hash( + option: Option, opt_str: str, value: str, parser: OptionParser +) -> None: """Given a value spelled "algo:digest", append the digest to a list pointed to in a dict by the algo name.""" if not parser.values.hashes: - parser.values.hashes = {} # type: ignore + parser.values.hashes = {} try: - algo, digest = value.split(':', 1) + algo, digest = value.split(":", 1) except ValueError: - parser.error('Arguments to %s must be a hash name ' - 'followed by a value, like --hash=sha256:abcde...' % - opt_str) + parser.error( + "Arguments to {} must be a hash name " # noqa + "followed by a value, like --hash=sha256:" + "abcde...".format(opt_str) + ) if algo not in STRONG_HASHES: - parser.error('Allowed hash algorithms for %s are %s.' % - (opt_str, ', '.join(STRONG_HASHES))) + parser.error( + "Allowed hash algorithms for {} are {}.".format( # noqa + opt_str, ", ".join(STRONG_HASHES) + ) + ) parser.values.hashes.setdefault(algo, []).append(digest) -hash = partial( +hash: Callable[..., Option] = partial( Option, - '--hash', + "--hash", # Hash values eventually end up in InstallRequirement.hashes due to # __dict__ copying in process_line(). - dest='hashes', - action='callback', - callback=_merge_hash, - type='string', + dest="hashes", + action="callback", + callback=_handle_merge_hash, + type="string", help="Verify that the package's archive matches this " - 'hash before installing. Example: --hash=sha256:abcdef...', -) # type: Callable[..., Option] + "hash before installing. Example: --hash=sha256:abcdef...", +) -require_hashes = partial( +require_hashes: Callable[..., Option] = partial( Option, - '--require-hashes', - dest='require_hashes', - action='store_true', + "--require-hashes", + dest="require_hashes", + action="store_true", default=False, - help='Require a hash to check each requirement against, for ' - 'repeatable installs. This option is implied when any package in a ' - 'requirements file has a --hash option.', -) # type: Callable[..., Option] + help="Require a hash to check each requirement against, for " + "repeatable installs. This option is implied when any package in a " + "requirements file has a --hash option.", +) + + +list_path: Callable[..., Option] = partial( + PipOption, + "--path", + dest="path", + type="path", + action="append", + help="Restrict to the specified installation path for listing " + "packages (can be used multiple times).", +) + + +def check_list_path_option(options: Values) -> None: + if options.path and (options.user or options.local): + raise CommandError("Cannot combine '--path' with '--user' or '--local'") + + +list_exclude: Callable[..., Option] = partial( + PipOption, + "--exclude", + dest="excludes", + action="append", + metavar="package", + type="package_name", + help="Exclude specified package from the output", +) + + +no_python_version_warning: Callable[..., Option] = partial( + Option, + "--no-python-version-warning", + dest="no_python_version_warning", + action="store_true", + default=False, + help="Silence deprecation warnings for upcoming unsupported Pythons.", +) + + +use_new_feature: Callable[..., Option] = partial( + Option, + "--use-feature", + dest="features_enabled", + metavar="feature", + action="append", + default=[], + choices=["2020-resolver", "fast-deps", "in-tree-build"], + help="Enable new functionality, that may be backward incompatible.", +) + +use_deprecated_feature: Callable[..., Option] = partial( + Option, + "--use-deprecated", + dest="deprecated_features_enabled", + metavar="feature", + action="append", + default=[], + choices=["legacy-resolver"], + help=("Enable deprecated functionality, that will be removed in the future."), +) ########## # groups # ########## -general_group = { - 'name': 'General Options', - 'options': [ +general_group: Dict[str, Any] = { + "name": "General Options", + "options": [ help_, isolated_mode, require_virtualenv, @@ -786,7 +984,6 @@ general_group = { proxy, retries, timeout, - skip_requirements_regex, exists_action, trusted_host, cert, @@ -795,15 +992,18 @@ general_group = { no_cache, disable_pip_version_check, no_color, - ] -} # type: Dict[str, Any] + no_python_version_warning, + use_new_feature, + use_deprecated_feature, + ], +} -index_group = { - 'name': 'Package Index Options', - 'options': [ +index_group: Dict[str, Any] = { + "name": "Package Index Options", + "options": [ index_url, extra_index_url, no_index, find_links, - ] -} # type: Dict[str, Any] + ], +} diff --git a/pipenv/patched/notpip/_internal/cli/command_context.py b/pipenv/patched/notpip/_internal/cli/command_context.py new file mode 100644 index 00000000..ed683223 --- /dev/null +++ b/pipenv/patched/notpip/_internal/cli/command_context.py @@ -0,0 +1,27 @@ +from contextlib import ExitStack, contextmanager +from typing import ContextManager, Iterator, TypeVar + +_T = TypeVar("_T", covariant=True) + + +class CommandContextMixIn: + def __init__(self) -> None: + super().__init__() + self._in_main_context = False + self._main_context = ExitStack() + + @contextmanager + def main_context(self) -> Iterator[None]: + assert not self._in_main_context + + self._in_main_context = True + try: + with self._main_context: + yield + finally: + self._in_main_context = False + + def enter_context(self, context_provider: ContextManager[_T]) -> _T: + assert self._in_main_context + + return self._main_context.enter_context(context_provider) diff --git a/pipenv/patched/notpip/_internal/cli/main.py b/pipenv/patched/notpip/_internal/cli/main.py new file mode 100644 index 00000000..e1064f8e --- /dev/null +++ b/pipenv/patched/notpip/_internal/cli/main.py @@ -0,0 +1,70 @@ +"""Primary application entrypoint. +""" +import locale +import logging +import os +import sys +from typing import List, Optional + +from pipenv.patched.notpip._internal.cli.autocompletion import autocomplete +from pipenv.patched.notpip._internal.cli.main_parser import parse_command +from pipenv.patched.notpip._internal.commands import create_command +from pipenv.patched.notpip._internal.exceptions import PipError +from pipenv.patched.notpip._internal.utils import deprecation + +logger = logging.getLogger(__name__) + + +# Do not import and use main() directly! Using it directly is actively +# discouraged by pip's maintainers. The name, location and behavior of +# this function is subject to change, so calling it directly is not +# portable across different pip versions. + +# In addition, running pip in-process is unsupported and unsafe. This is +# elaborated in detail at +# https://pip.pypa.io/en/stable/user_guide/#using-pip-from-your-program. +# That document also provides suggestions that should work for nearly +# all users that are considering importing and using main() directly. + +# However, we know that certain users will still want to invoke pip +# in-process. If you understand and accept the implications of using pip +# in an unsupported manner, the best approach is to use runpy to avoid +# depending on the exact location of this entry point. + +# The following example shows how to use runpy to invoke pip in that +# case: +# +# sys.argv = ["pip", your, args, here] +# runpy.run_module("pip", run_name="__main__") +# +# Note that this will exit the process after running, unlike a direct +# call to main. As it is not safe to do any processing after calling +# main, this should not be an issue in practice. + + +def main(args: Optional[List[str]] = None) -> int: + if args is None: + args = sys.argv[1:] + + # Configure our deprecation warnings to be sent through loggers + deprecation.install_warning_logger() + + autocomplete() + + try: + cmd_name, cmd_args = parse_command(args) + except PipError as exc: + sys.stderr.write(f"ERROR: {exc}") + sys.stderr.write(os.linesep) + sys.exit(1) + + # Needed for locale.getpreferredencoding(False) to work + # in pipenv.patched.notpip._internal.utils.encoding.auto_decode + try: + locale.setlocale(locale.LC_ALL, "") + except locale.Error as e: + # setlocale can apparently crash if locale are uninitialized + logger.debug("Ignoring error %s when setting locale", e) + command = create_command(cmd_name, isolated=("--isolated" in cmd_args)) + + return command.main(cmd_args) diff --git a/pipenv/patched/notpip/_internal/cli/main_parser.py b/pipenv/patched/notpip/_internal/cli/main_parser.py index 704bf404..d779017b 100644 --- a/pipenv/patched/notpip/_internal/cli/main_parser.py +++ b/pipenv/patched/notpip/_internal/cli/main_parser.py @@ -3,48 +3,30 @@ import os import sys +from typing import List, Tuple -from pipenv.patched.notpip import __version__ from pipenv.patched.notpip._internal.cli import cmdoptions -from pipenv.patched.notpip._internal.cli.parser import ( - ConfigOptionParser, UpdatingDefaultsHelpFormatter, -) -from pipenv.patched.notpip._internal.commands import ( - commands_dict, get_similar_commands, get_summaries, -) +from pipenv.patched.notpip._internal.cli.parser import ConfigOptionParser, UpdatingDefaultsHelpFormatter +from pipenv.patched.notpip._internal.commands import commands_dict, get_similar_commands from pipenv.patched.notpip._internal.exceptions import CommandError -from pipenv.patched.notpip._internal.utils.misc import get_prog -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING - -if MYPY_CHECK_RUNNING: - from typing import Tuple, List # noqa: F401 - +from pipenv.patched.notpip._internal.utils.misc import get_pip_version, get_prog __all__ = ["create_main_parser", "parse_command"] -def create_main_parser(): - # type: () -> ConfigOptionParser - """Creates and returns the main parser for pip's CLI - """ +def create_main_parser() -> ConfigOptionParser: + """Creates and returns the main parser for pip's CLI""" - parser_kw = { - 'usage': '\n%prog [options]', - 'add_help_option': False, - 'formatter': UpdatingDefaultsHelpFormatter(), - 'name': 'global', - 'prog': get_prog(), - } - - parser = ConfigOptionParser(**parser_kw) + parser = ConfigOptionParser( + usage="\n%prog [options]", + add_help_option=False, + formatter=UpdatingDefaultsHelpFormatter(), + name="global", + prog=get_prog(), + ) parser.disable_interspersed_args() - pip_pkg_dir = os.path.abspath(os.path.join( - os.path.dirname(__file__), "..", "..", - )) - parser.version = 'pip %s from %s (python %s)' % ( - __version__, pip_pkg_dir, sys.version[:3], - ) + parser.version = get_pip_version() # add the general options gen_opts = cmdoptions.make_option_group(cmdoptions.general_group, parser) @@ -54,15 +36,16 @@ def create_main_parser(): parser.main = True # type: ignore # create command listing for description - command_summaries = get_summaries() - description = [''] + ['%-27s %s' % (i, j) for i, j in command_summaries] - parser.description = '\n'.join(description) + description = [""] + [ + f"{name:27} {command_info.summary}" + for name, command_info in commands_dict.items() + ] + parser.description = "\n".join(description) return parser -def parse_command(args): - # type: (List[str]) -> Tuple[str, List[str]] +def parse_command(args: List[str]) -> Tuple[str, List[str]]: parser = create_main_parser() # Note: parser calls disable_interspersed_args(), so the result of this @@ -76,12 +59,12 @@ def parse_command(args): # --version if general_options.version: - sys.stdout.write(parser.version) # type: ignore + sys.stdout.write(parser.version) sys.stdout.write(os.linesep) sys.exit() # pip || pip help -> print_help() - if not args_else or (args_else[0] == 'help' and len(args_else) == 1): + if not args_else or (args_else[0] == "help" and len(args_else) == 1): parser.print_help() sys.exit() @@ -91,11 +74,11 @@ def parse_command(args): if cmd_name not in commands_dict: guess = get_similar_commands(cmd_name) - msg = ['unknown command "%s"' % cmd_name] + msg = [f'unknown command "{cmd_name}"'] if guess: - msg.append('maybe you meant "%s"' % guess) + msg.append(f'maybe you meant "{guess}"') - raise CommandError(' - '.join(msg)) + raise CommandError(" - ".join(msg)) # all the args without the subcommand cmd_args = args[:] diff --git a/pipenv/patched/notpip/_internal/cli/parser.py b/pipenv/patched/notpip/_internal/cli/parser.py index 2d2c8f4d..febf445c 100644 --- a/pipenv/patched/notpip/_internal/cli/parser.py +++ b/pipenv/patched/notpip/_internal/cli/parser.py @@ -1,17 +1,16 @@ """Base option parser setup""" -from __future__ import absolute_import import logging import optparse +import shutil import sys import textwrap -from distutils.util import strtobool - -from pipenv.patched.notpip._vendor.six import string_types +from contextlib import suppress +from typing import Any, Dict, Iterator, List, Tuple from pipenv.patched.notpip._internal.cli.status_codes import UNKNOWN_ERROR from pipenv.patched.notpip._internal.configuration import Configuration, ConfigurationError -from pipenv.patched.notpip._internal.utils.compat import get_terminal_size +from pipenv.patched.notpip._internal.utils.misc import redact_auth_from_url, strtobool logger = logging.getLogger(__name__) @@ -19,22 +18,24 @@ logger = logging.getLogger(__name__) class PrettyHelpFormatter(optparse.IndentedHelpFormatter): """A prettier/less verbose help formatter for optparse.""" - def __init__(self, *args, **kwargs): + def __init__(self, *args: Any, **kwargs: Any) -> None: # help position must be aligned with __init__.parseopts.description - kwargs['max_help_position'] = 30 - kwargs['indent_increment'] = 1 - kwargs['width'] = get_terminal_size()[0] - 2 - optparse.IndentedHelpFormatter.__init__(self, *args, **kwargs) + kwargs["max_help_position"] = 30 + kwargs["indent_increment"] = 1 + kwargs["width"] = shutil.get_terminal_size()[0] - 2 + super().__init__(*args, **kwargs) - def format_option_strings(self, option): - return self._format_option_strings(option, ' <%s>', ', ') + def format_option_strings(self, option: optparse.Option) -> str: + return self._format_option_strings(option) - def _format_option_strings(self, option, mvarfmt=' <%s>', optsep=', '): + def _format_option_strings( + self, option: optparse.Option, mvarfmt: str = " <{}>", optsep: str = ", " + ) -> str: """ Return a comma-separated list of option strings and metavars. :param option: tuple of (short opt, long opt), e.g: ('-f', '--format') - :param mvarfmt: metavar format string - evaluated as mvarfmt % metavar + :param mvarfmt: metavar format string :param optsep: separator """ opts = [] @@ -47,51 +48,52 @@ class PrettyHelpFormatter(optparse.IndentedHelpFormatter): opts.insert(1, optsep) if option.takes_value(): + assert option.dest is not None metavar = option.metavar or option.dest.lower() - opts.append(mvarfmt % metavar.lower()) + opts.append(mvarfmt.format(metavar.lower())) - return ''.join(opts) + return "".join(opts) - def format_heading(self, heading): - if heading == 'Options': - return '' - return heading + ':\n' + def format_heading(self, heading: str) -> str: + if heading == "Options": + return "" + return heading + ":\n" - def format_usage(self, usage): + def format_usage(self, usage: str) -> str: """ Ensure there is only one newline between usage and the first heading if there is no description. """ - msg = '\nUsage: %s\n' % self.indent_lines(textwrap.dedent(usage), " ") + msg = "\nUsage: {}\n".format(self.indent_lines(textwrap.dedent(usage), " ")) return msg - def format_description(self, description): + def format_description(self, description: str) -> str: # leave full control over description to us if description: - if hasattr(self.parser, 'main'): - label = 'Commands' + if hasattr(self.parser, "main"): + label = "Commands" else: - label = 'Description' + label = "Description" # some doc strings have initial newlines, some don't - description = description.lstrip('\n') + description = description.lstrip("\n") # some doc strings have final newlines and spaces, some don't description = description.rstrip() # dedent, then reindent description = self.indent_lines(textwrap.dedent(description), " ") - description = '%s:\n%s\n' % (label, description) + description = f"{label}:\n{description}\n" return description else: - return '' + return "" - def format_epilog(self, epilog): + def format_epilog(self, epilog: str) -> str: # leave full control over epilog to us if epilog: return epilog else: - return '' + return "" - def indent_lines(self, text, indent): - new_lines = [indent + line for line in text.split('\n')] + def indent_lines(self, text: str, indent: str) -> str: + new_lines = [indent + line for line in text.split("\n")] return "\n".join(new_lines) @@ -100,17 +102,37 @@ class UpdatingDefaultsHelpFormatter(PrettyHelpFormatter): This is updates the defaults before expanding them, allowing them to show up correctly in the help listing. + + Also redact auth from url type options """ - def expand_default(self, option): + def expand_default(self, option: optparse.Option) -> str: + default_values = None if self.parser is not None: + assert isinstance(self.parser, ConfigOptionParser) self.parser._update_defaults(self.parser.defaults) - return optparse.IndentedHelpFormatter.expand_default(self, option) + assert option.dest is not None + default_values = self.parser.defaults.get(option.dest) + help_text = super().expand_default(option) + + if default_values and option.metavar == "URL": + if isinstance(default_values, str): + default_values = [default_values] + + # If its not a list, we should abort and just return the help text + if not isinstance(default_values, list): + default_values = [] + + for val in default_values: + help_text = help_text.replace(val, redact_auth_from_url(val)) + + return help_text class CustomOptionParser(optparse.OptionParser): - - def insert_option_group(self, idx, *args, **kwargs): + def insert_option_group( + self, idx: int, *args: Any, **kwargs: Any + ) -> optparse.OptionGroup: """Insert an OptionGroup at a given position.""" group = self.add_option_group(*args, **kwargs) @@ -120,7 +142,7 @@ class CustomOptionParser(optparse.OptionParser): return group @property - def option_list_all(self): + def option_list_all(self) -> List[optparse.Option]: """Get a list of all options, including those in option groups.""" res = self.option_list[:] for i in self.option_groups: @@ -133,34 +155,40 @@ class ConfigOptionParser(CustomOptionParser): """Custom option parser which updates its defaults by checking the configuration files and environmental variables""" - def __init__(self, *args, **kwargs): - self.name = kwargs.pop('name') - - isolated = kwargs.pop("isolated", False) + def __init__( + self, + *args: Any, + name: str, + isolated: bool = False, + **kwargs: Any, + ) -> None: + self.name = name self.config = Configuration(isolated) assert self.name - optparse.OptionParser.__init__(self, *args, **kwargs) + super().__init__(*args, **kwargs) - def check_default(self, option, key, val): + def check_default(self, option: optparse.Option, key: str, val: Any) -> Any: try: return option.check_value(key, val) except optparse.OptionValueError as exc: - print("An error occurred during configuration: %s" % exc) + print(f"An error occurred during configuration: {exc}") sys.exit(3) - def _get_ordered_configuration_items(self): + def _get_ordered_configuration_items(self) -> Iterator[Tuple[str, Any]]: # Configuration gives keys in an unordered manner. Order them. override_order = ["global", self.name, ":env:"] # Pool the options into different groups - section_items = {name: [] for name in override_order} + section_items: Dict[str, List[Tuple[str, Any]]] = { + name: [] for name in override_order + } for section_key, val in self.config.items(): # ignore empty values if not val: logger.debug( "Ignoring configuration key '%s' as it's value is empty.", - section_key + section_key, ) continue @@ -173,7 +201,7 @@ class ConfigOptionParser(CustomOptionParser): for key, val in section_items[section]: yield key, val - def _update_defaults(self, defaults): + def _update_defaults(self, defaults: Dict[str, Any]) -> Dict[str, Any]: """Updates the given defaults with values from the config files and the environ. Does a little special handling for certain types of options (lists).""" @@ -184,7 +212,7 @@ class ConfigOptionParser(CustomOptionParser): # Then set the options with those values for key, val in self._get_ordered_configuration_items(): # '--' because configuration supports only long names - option = self.get_option('--' + key) + option = self.get_option("--" + key) # Ignore options not present in this parser. E.g. non-globals put # in [global] by users that want them to apply to all applicable @@ -192,19 +220,34 @@ class ConfigOptionParser(CustomOptionParser): if option is None: continue - if option.action in ('store_true', 'store_false', 'count'): + assert option.dest is not None + + if option.action in ("store_true", "store_false"): try: val = strtobool(val) except ValueError: - error_msg = invalid_config_error_message( - option.action, key, val + self.error( + "{} is not a valid value for {} option, " # noqa + "please specify a boolean value like yes/no, " + "true/false or 1/0 instead.".format(val, key) ) - self.error(error_msg) - - elif option.action == 'append': + elif option.action == "count": + with suppress(ValueError): + val = strtobool(val) + with suppress(ValueError): + val = int(val) + if not isinstance(val, int) or val < 0: + self.error( + "{} is not a valid value for {} option, " # noqa + "please instead specify either a non-negative integer " + "or a boolean value like yes/no or false/true " + "which is equivalent to 1/0.".format(val, key) + ) + elif option.action == "append": val = val.split() val = [self.check_default(option, key, v) for v in val] - elif option.action == 'callback': + elif option.action == "callback": + assert option.callback is not None late_eval.add(option.dest) opt_str = option.get_opt_string() val = option.convert_value(opt_str, val) @@ -222,7 +265,7 @@ class ConfigOptionParser(CustomOptionParser): self.values = None return defaults - def get_default_values(self): + def get_default_values(self) -> optparse.Values: """Overriding to make updating the defaults after instantiation of the option parser possible, _update_defaults() does the dirty work.""" if not self.process_default_values: @@ -237,25 +280,13 @@ class ConfigOptionParser(CustomOptionParser): defaults = self._update_defaults(self.defaults.copy()) # ours for option in self._get_all_options(): + assert option.dest is not None default = defaults.get(option.dest) - if isinstance(default, string_types): + if isinstance(default, str): opt_str = option.get_opt_string() defaults[option.dest] = option.check_value(opt_str, default) return optparse.Values(defaults) - def error(self, msg): + def error(self, msg: str) -> None: self.print_usage(sys.stderr) - self.exit(UNKNOWN_ERROR, "%s\n" % msg) - - -def invalid_config_error_message(action, key, val): - """Returns a better error message when invalid configuration option - is provided.""" - if action in ('store_true', 'store_false'): - return ("{0} is not a valid value for {1} option, " - "please specify a boolean value like yes/no, " - "true/false or 1/0 instead.").format(val, key) - - return ("{0} is not a valid value for {1} option, " - "please specify a numerical value like 1/0 " - "instead.").format(val, key) + self.exit(UNKNOWN_ERROR, f"{msg}\n") diff --git a/pipenv/patched/notpip/_internal/cli/progress_bars.py b/pipenv/patched/notpip/_internal/cli/progress_bars.py new file mode 100644 index 00000000..582747ed --- /dev/null +++ b/pipenv/patched/notpip/_internal/cli/progress_bars.py @@ -0,0 +1,250 @@ +import itertools +import sys +from signal import SIGINT, default_int_handler, signal +from typing import Any + +from pipenv.patched.notpip._vendor.progress.bar import Bar, FillingCirclesBar, IncrementalBar +from pipenv.patched.notpip._vendor.progress.spinner import Spinner + +from pipenv.patched.notpip._internal.utils.compat import WINDOWS +from pipenv.patched.notpip._internal.utils.logging import get_indentation +from pipenv.patched.notpip._internal.utils.misc import format_size + +try: + from pipenv.patched.notpip._vendor import colorama +# Lots of different errors can come from this, including SystemError and +# ImportError. +except Exception: + colorama = None + + +def _select_progress_class(preferred: Bar, fallback: Bar) -> Bar: + encoding = getattr(preferred.file, "encoding", None) + + # If we don't know what encoding this file is in, then we'll just assume + # that it doesn't support unicode and use the ASCII bar. + if not encoding: + return fallback + + # Collect all of the possible characters we want to use with the preferred + # bar. + characters = [ + getattr(preferred, "empty_fill", ""), + getattr(preferred, "fill", ""), + ] + characters += list(getattr(preferred, "phases", [])) + + # Try to decode the characters we're using for the bar using the encoding + # of the given file, if this works then we'll assume that we can use the + # fancier bar and if not we'll fall back to the plaintext bar. + try: + "".join(characters).encode(encoding) + except UnicodeEncodeError: + return fallback + else: + return preferred + + +_BaseBar: Any = _select_progress_class(IncrementalBar, Bar) + + +class InterruptibleMixin: + """ + Helper to ensure that self.finish() gets called on keyboard interrupt. + + This allows downloads to be interrupted without leaving temporary state + (like hidden cursors) behind. + + This class is similar to the progress library's existing SigIntMixin + helper, but as of version 1.2, that helper has the following problems: + + 1. It calls sys.exit(). + 2. It discards the existing SIGINT handler completely. + 3. It leaves its own handler in place even after an uninterrupted finish, + which will have unexpected delayed effects if the user triggers an + unrelated keyboard interrupt some time after a progress-displaying + download has already completed, for example. + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + """ + Save the original SIGINT handler for later. + """ + # https://github.com/python/mypy/issues/5887 + super().__init__(*args, **kwargs) # type: ignore + + self.original_handler = signal(SIGINT, self.handle_sigint) + + # If signal() returns None, the previous handler was not installed from + # Python, and we cannot restore it. This probably should not happen, + # but if it does, we must restore something sensible instead, at least. + # The least bad option should be Python's default SIGINT handler, which + # just raises KeyboardInterrupt. + if self.original_handler is None: + self.original_handler = default_int_handler + + def finish(self) -> None: + """ + Restore the original SIGINT handler after finishing. + + This should happen regardless of whether the progress display finishes + normally, or gets interrupted. + """ + super().finish() # type: ignore + signal(SIGINT, self.original_handler) + + def handle_sigint(self, signum, frame): # type: ignore + """ + Call self.finish() before delegating to the original SIGINT handler. + + This handler should only be in place while the progress display is + active. + """ + self.finish() + self.original_handler(signum, frame) + + +class SilentBar(Bar): + def update(self) -> None: + pass + + +class BlueEmojiBar(IncrementalBar): + + suffix = "%(percent)d%%" + bar_prefix = " " + bar_suffix = " " + phases = ("\U0001F539", "\U0001F537", "\U0001F535") + + +class DownloadProgressMixin: + def __init__(self, *args: Any, **kwargs: Any) -> None: + # https://github.com/python/mypy/issues/5887 + super().__init__(*args, **kwargs) # type: ignore + self.message: str = (" " * (get_indentation() + 2)) + self.message + + @property + def downloaded(self) -> str: + return format_size(self.index) # type: ignore + + @property + def download_speed(self) -> str: + # Avoid zero division errors... + if self.avg == 0.0: # type: ignore + return "..." + return format_size(1 / self.avg) + "/s" # type: ignore + + @property + def pretty_eta(self) -> str: + if self.eta: # type: ignore + return f"eta {self.eta_td}" # type: ignore + return "" + + def iter(self, it): # type: ignore + for x in it: + yield x + # B305 is incorrectly raised here + # https://github.com/PyCQA/flake8-bugbear/issues/59 + self.next(len(x)) # noqa: B305 + self.finish() + + +class WindowsMixin: + def __init__(self, *args: Any, **kwargs: Any) -> None: + # The Windows terminal does not support the hide/show cursor ANSI codes + # even with colorama. So we'll ensure that hide_cursor is False on + # Windows. + # This call needs to go before the super() call, so that hide_cursor + # is set in time. The base progress bar class writes the "hide cursor" + # code to the terminal in its init, so if we don't set this soon + # enough, we get a "hide" with no corresponding "show"... + if WINDOWS and self.hide_cursor: # type: ignore + self.hide_cursor = False + + # https://github.com/python/mypy/issues/5887 + super().__init__(*args, **kwargs) # type: ignore + + # Check if we are running on Windows and we have the colorama module, + # if we do then wrap our file with it. + if WINDOWS and colorama: + self.file = colorama.AnsiToWin32(self.file) # type: ignore + # The progress code expects to be able to call self.file.isatty() + # but the colorama.AnsiToWin32() object doesn't have that, so we'll + # add it. + self.file.isatty = lambda: self.file.wrapped.isatty() + # The progress code expects to be able to call self.file.flush() + # but the colorama.AnsiToWin32() object doesn't have that, so we'll + # add it. + self.file.flush = lambda: self.file.wrapped.flush() + + +class BaseDownloadProgressBar(WindowsMixin, InterruptibleMixin, DownloadProgressMixin): + + file = sys.stdout + message = "%(percent)d%%" + suffix = "%(downloaded)s %(download_speed)s %(pretty_eta)s" + + +class DefaultDownloadProgressBar(BaseDownloadProgressBar, _BaseBar): + pass + + +class DownloadSilentBar(BaseDownloadProgressBar, SilentBar): + pass + + +class DownloadBar(BaseDownloadProgressBar, Bar): + pass + + +class DownloadFillingCirclesBar(BaseDownloadProgressBar, FillingCirclesBar): + pass + + +class DownloadBlueEmojiProgressBar(BaseDownloadProgressBar, BlueEmojiBar): + pass + + +class DownloadProgressSpinner( + WindowsMixin, InterruptibleMixin, DownloadProgressMixin, Spinner +): + + file = sys.stdout + suffix = "%(downloaded)s %(download_speed)s" + + def next_phase(self) -> str: + if not hasattr(self, "_phaser"): + self._phaser = itertools.cycle(self.phases) + return next(self._phaser) + + def update(self) -> None: + message = self.message % self + phase = self.next_phase() + suffix = self.suffix % self + line = "".join( + [ + message, + " " if message else "", + phase, + " " if suffix else "", + suffix, + ] + ) + + self.writeln(line) + + +BAR_TYPES = { + "off": (DownloadSilentBar, DownloadSilentBar), + "on": (DefaultDownloadProgressBar, DownloadProgressSpinner), + "ascii": (DownloadBar, DownloadProgressSpinner), + "pretty": (DownloadFillingCirclesBar, DownloadProgressSpinner), + "emoji": (DownloadBlueEmojiProgressBar, DownloadProgressSpinner), +} + + +def DownloadProgressProvider(progress_bar, max=None): # type: ignore + if max is None or max == 0: + return BAR_TYPES[progress_bar][1]().iter + else: + return BAR_TYPES[progress_bar][0](max=max).iter diff --git a/pipenv/patched/notpip/_internal/cli/req_command.py b/pipenv/patched/notpip/_internal/cli/req_command.py new file mode 100644 index 00000000..17c2209f --- /dev/null +++ b/pipenv/patched/notpip/_internal/cli/req_command.py @@ -0,0 +1,453 @@ +"""Contains the Command base classes that depend on PipSession. + +The classes in this module are in a separate module so the commands not +needing download / PackageFinder capability don't unnecessarily import the +PackageFinder machinery and all its vendored dependencies, etc. +""" + +import logging +import os +import sys +from functools import partial +from optparse import Values +from typing import Any, List, Optional, Tuple + +from pipenv.patched.notpip._internal.cache import WheelCache +from pipenv.patched.notpip._internal.cli import cmdoptions +from pipenv.patched.notpip._internal.cli.base_command import Command +from pipenv.patched.notpip._internal.cli.command_context import CommandContextMixIn +from pipenv.patched.notpip._internal.exceptions import CommandError, PreviousBuildDirError +from pipenv.patched.notpip._internal.index.collector import LinkCollector +from pipenv.patched.notpip._internal.index.package_finder import PackageFinder +from pipenv.patched.notpip._internal.models.selection_prefs import SelectionPreferences +from pipenv.patched.notpip._internal.models.target_python import TargetPython +from pipenv.patched.notpip._internal.network.session import PipSession +from pipenv.patched.notpip._internal.operations.prepare import RequirementPreparer +from pipenv.patched.notpip._internal.req.constructors import ( + install_req_from_editable, + install_req_from_line, + install_req_from_parsed_requirement, + install_req_from_req_string, +) +from pipenv.patched.notpip._internal.req.req_file import parse_requirements +from pipenv.patched.notpip._internal.req.req_install import InstallRequirement +from pipenv.patched.notpip._internal.req.req_tracker import RequirementTracker +from pipenv.patched.notpip._internal.resolution.base import BaseResolver +from pipenv.patched.notpip._internal.self_outdated_check import pip_self_version_check +from pipenv.patched.notpip._internal.utils.temp_dir import ( + TempDirectory, + TempDirectoryTypeRegistry, + tempdir_kinds, +) +from pipenv.patched.notpip._internal.utils.virtualenv import running_under_virtualenv + +logger = logging.getLogger(__name__) + + +class SessionCommandMixin(CommandContextMixIn): + + """ + A class mixin for command classes needing _build_session(). + """ + + def __init__(self) -> None: + super().__init__() + self._session: Optional[PipSession] = None + + @classmethod + def _get_index_urls(cls, options: Values) -> Optional[List[str]]: + """Return a list of index urls from user-provided options.""" + index_urls = [] + if not getattr(options, "no_index", False): + url = getattr(options, "index_url", None) + if url: + index_urls.append(url) + urls = getattr(options, "extra_index_urls", None) + if urls: + index_urls.extend(urls) + # Return None rather than an empty list + return index_urls or None + + def get_default_session(self, options: Values) -> PipSession: + """Get a default-managed session.""" + if self._session is None: + self._session = self.enter_context(self._build_session(options)) + # there's no type annotation on requests.Session, so it's + # automatically ContextManager[Any] and self._session becomes Any, + # then https://github.com/python/mypy/issues/7696 kicks in + assert self._session is not None + return self._session + + def _build_session( + self, + options: Values, + retries: Optional[int] = None, + timeout: Optional[int] = None, + ) -> PipSession: + assert not options.cache_dir or os.path.isabs(options.cache_dir) + session = PipSession( + cache=( + os.path.join(options.cache_dir, "http") if options.cache_dir else None + ), + retries=retries if retries is not None else options.retries, + trusted_hosts=options.trusted_hosts, + index_urls=self._get_index_urls(options), + ) + + # Handle custom ca-bundles from the user + if options.cert: + session.verify = options.cert + + # Handle SSL client certificate + if options.client_cert: + session.cert = options.client_cert + + # Handle timeouts + if options.timeout or timeout: + session.timeout = timeout if timeout is not None else options.timeout + + # Handle configured proxies + if options.proxy: + session.proxies = { + "http": options.proxy, + "https": options.proxy, + } + + # Determine if we can prompt the user for authentication or not + session.auth.prompting = not options.no_input + + return session + + +class IndexGroupCommand(Command, SessionCommandMixin): + + """ + Abstract base class for commands with the index_group options. + + This also corresponds to the commands that permit the pip version check. + """ + + def handle_pip_version_check(self, options: Values) -> None: + """ + Do the pip version check if not disabled. + + This overrides the default behavior of not doing the check. + """ + # Make sure the index_group options are present. + assert hasattr(options, "no_index") + + if options.disable_pip_version_check or options.no_index: + return + + # Otherwise, check if we're using the latest version of pip available. + session = self._build_session( + options, retries=0, timeout=min(5, options.timeout) + ) + with session: + pip_self_version_check(session, options) + + +KEEPABLE_TEMPDIR_TYPES = [ + tempdir_kinds.BUILD_ENV, + tempdir_kinds.EPHEM_WHEEL_CACHE, + tempdir_kinds.REQ_BUILD, +] + + +def warn_if_run_as_root() -> None: + """Output a warning for sudo users on Unix. + + In a virtual environment, sudo pip still writes to virtualenv. + On Windows, users may run pip as Administrator without issues. + This warning only applies to Unix root users outside of virtualenv. + """ + if running_under_virtualenv(): + return + if not hasattr(os, "getuid"): + return + # On Windows, there are no "system managed" Python packages. Installing as + # Administrator via pip is the correct way of updating system environments. + # + # We choose sys.platform over utils.compat.WINDOWS here to enable Mypy platform + # checks: https://mypy.readthedocs.io/en/stable/common_issues.html + if sys.platform == "win32" or sys.platform == "cygwin": + return + if sys.platform == "darwin" or sys.platform == "linux": + if os.getuid() != 0: + return + logger.warning( + "Running pip as the 'root' user can result in broken permissions and " + "conflicting behaviour with the system package manager. " + "It is recommended to use a virtual environment instead: " + "https://pip.pypa.io/warnings/venv" + ) + + +def with_cleanup(func: Any) -> Any: + """Decorator for common logic related to managing temporary + directories. + """ + + def configure_tempdir_registry(registry: TempDirectoryTypeRegistry) -> None: + for t in KEEPABLE_TEMPDIR_TYPES: + registry.set_delete(t, False) + + def wrapper( + self: RequirementCommand, options: Values, args: List[Any] + ) -> Optional[int]: + assert self.tempdir_registry is not None + if options.no_clean: + configure_tempdir_registry(self.tempdir_registry) + + try: + return func(self, options, args) + except PreviousBuildDirError: + # This kind of conflict can occur when the user passes an explicit + # build directory with a pre-existing folder. In that case we do + # not want to accidentally remove it. + configure_tempdir_registry(self.tempdir_registry) + raise + + return wrapper + + +class RequirementCommand(IndexGroupCommand): + def __init__(self, *args: Any, **kw: Any) -> None: + super().__init__(*args, **kw) + + self.cmd_opts.add_option(cmdoptions.no_clean()) + + @staticmethod + def determine_resolver_variant(options: Values) -> str: + """Determines which resolver should be used, based on the given options.""" + if "legacy-resolver" in options.deprecated_features_enabled: + return "legacy" + + return "2020-resolver" + + @classmethod + def make_requirement_preparer( + cls, + temp_build_dir: TempDirectory, + options: Values, + req_tracker: RequirementTracker, + session: PipSession, + finder: PackageFinder, + use_user_site: bool, + download_dir: Optional[str] = None, + ) -> RequirementPreparer: + """ + Create a RequirementPreparer instance for the given parameters. + """ + temp_build_dir_path = temp_build_dir.path + assert temp_build_dir_path is not None + + resolver_variant = cls.determine_resolver_variant(options) + if resolver_variant == "2020-resolver": + lazy_wheel = "fast-deps" in options.features_enabled + if lazy_wheel: + logger.warning( + "pip is using lazily downloaded wheels using HTTP " + "range requests to obtain dependency information. " + "This experimental feature is enabled through " + "--use-feature=fast-deps and it is not ready for " + "production." + ) + else: + lazy_wheel = False + if "fast-deps" in options.features_enabled: + logger.warning( + "fast-deps has no effect when used with the legacy resolver." + ) + + return RequirementPreparer( + build_dir=temp_build_dir_path, + src_dir=options.src_dir, + download_dir=download_dir, + build_isolation=options.build_isolation, + req_tracker=req_tracker, + session=session, + progress_bar=options.progress_bar, + finder=finder, + require_hashes=options.require_hashes, + use_user_site=use_user_site, + lazy_wheel=lazy_wheel, + in_tree_build="in-tree-build" in options.features_enabled, + ) + + @classmethod + def make_resolver( + cls, + preparer: RequirementPreparer, + finder: PackageFinder, + options: Values, + wheel_cache: Optional[WheelCache] = None, + use_user_site: bool = False, + ignore_installed: bool = True, + ignore_requires_python: bool = False, + force_reinstall: bool = False, + upgrade_strategy: str = "to-satisfy-only", + use_pep517: Optional[bool] = None, + py_version_info: Optional[Tuple[int, ...]] = None, + ) -> BaseResolver: + """ + Create a Resolver instance for the given parameters. + """ + make_install_req = partial( + install_req_from_req_string, + isolated=options.isolated_mode, + use_pep517=use_pep517, + ) + resolver_variant = cls.determine_resolver_variant(options) + # The long import name and duplicated invocation is needed to convince + # Mypy into correctly typechecking. Otherwise it would complain the + # "Resolver" class being redefined. + if resolver_variant == "2020-resolver": + import pipenv.patched.notpip._internal.resolution.resolvelib.resolver + + return pipenv.patched.notpip._internal.resolution.resolvelib.resolver.Resolver( + preparer=preparer, + finder=finder, + wheel_cache=wheel_cache, + make_install_req=make_install_req, + use_user_site=use_user_site, + ignore_dependencies=options.ignore_dependencies, + ignore_installed=ignore_installed, + ignore_requires_python=ignore_requires_python, + force_reinstall=force_reinstall, + upgrade_strategy=upgrade_strategy, + py_version_info=py_version_info, + ) + import pipenv.patched.notpip._internal.resolution.legacy.resolver + + return pipenv.patched.notpip._internal.resolution.legacy.resolver.Resolver( + preparer=preparer, + finder=finder, + wheel_cache=wheel_cache, + make_install_req=make_install_req, + use_user_site=use_user_site, + ignore_dependencies=options.ignore_dependencies, + ignore_installed=ignore_installed, + ignore_requires_python=ignore_requires_python, + force_reinstall=force_reinstall, + upgrade_strategy=upgrade_strategy, + py_version_info=py_version_info, + ) + + def get_requirements( + self, + args: List[str], + options: Values, + finder: PackageFinder, + session: PipSession, + ) -> List[InstallRequirement]: + """ + Parse command-line arguments into the corresponding requirements. + """ + requirements: List[InstallRequirement] = [] + for filename in options.constraints: + for parsed_req in parse_requirements( + filename, + constraint=True, + finder=finder, + options=options, + session=session, + ): + req_to_add = install_req_from_parsed_requirement( + parsed_req, + isolated=options.isolated_mode, + user_supplied=False, + ) + requirements.append(req_to_add) + + for req in args: + req_to_add = install_req_from_line( + req, + None, + isolated=options.isolated_mode, + use_pep517=options.use_pep517, + user_supplied=True, + ) + requirements.append(req_to_add) + + for req in options.editables: + req_to_add = install_req_from_editable( + req, + user_supplied=True, + isolated=options.isolated_mode, + use_pep517=options.use_pep517, + ) + requirements.append(req_to_add) + + # NOTE: options.require_hashes may be set if --require-hashes is True + for filename in options.requirements: + for parsed_req in parse_requirements( + filename, finder=finder, options=options, session=session + ): + req_to_add = install_req_from_parsed_requirement( + parsed_req, + isolated=options.isolated_mode, + use_pep517=options.use_pep517, + user_supplied=True, + ) + requirements.append(req_to_add) + + # If any requirement has hash options, enable hash checking. + if any(req.has_hash_options for req in requirements): + options.require_hashes = True + + if not (args or options.editables or options.requirements): + opts = {"name": self.name} + if options.find_links: + raise CommandError( + "You must give at least one requirement to {name} " + '(maybe you meant "pip {name} {links}"?)'.format( + **dict(opts, links=" ".join(options.find_links)) + ) + ) + else: + raise CommandError( + "You must give at least one requirement to {name} " + '(see "pip help {name}")'.format(**opts) + ) + + return requirements + + @staticmethod + def trace_basic_info(finder: PackageFinder) -> None: + """ + Trace basic information about the provided objects. + """ + # Display where finder is looking for packages + search_scope = finder.search_scope + locations = search_scope.get_formatted_locations() + if locations: + logger.info(locations) + + def _build_package_finder( + self, + options: Values, + session: PipSession, + target_python: Optional[TargetPython] = None, + ignore_requires_python: Optional[bool] = None, + ) -> PackageFinder: + """ + Create a package finder appropriate to this requirement command. + + :param ignore_requires_python: Whether to ignore incompatible + "Requires-Python" values in links. Defaults to False. + """ + link_collector = LinkCollector.create(session, options=options) + selection_prefs = SelectionPreferences( + allow_yanked=True, + format_control=options.format_control, + allow_all_prereleases=options.pre, + prefer_binary=options.prefer_binary, + ignore_requires_python=ignore_requires_python, + ) + + return PackageFinder.create( + link_collector=link_collector, + selection_prefs=selection_prefs, + target_python=target_python, + ) diff --git a/pipenv/patched/notpip/_internal/cli/spinners.py b/pipenv/patched/notpip/_internal/cli/spinners.py new file mode 100644 index 00000000..84f26c5d --- /dev/null +++ b/pipenv/patched/notpip/_internal/cli/spinners.py @@ -0,0 +1,157 @@ +import contextlib +import itertools +import logging +import sys +import time +from typing import IO, Iterator + +from pipenv.patched.notpip._vendor.progress import HIDE_CURSOR, SHOW_CURSOR + +from pipenv.patched.notpip._internal.utils.compat import WINDOWS +from pipenv.patched.notpip._internal.utils.logging import get_indentation + +logger = logging.getLogger(__name__) + + +class SpinnerInterface: + def spin(self) -> None: + raise NotImplementedError() + + def finish(self, final_status: str) -> None: + raise NotImplementedError() + + +class InteractiveSpinner(SpinnerInterface): + def __init__( + self, + message: str, + file: IO[str] = None, + spin_chars: str = "-\\|/", + # Empirically, 8 updates/second looks nice + min_update_interval_seconds: float = 0.125, + ): + self._message = message + if file is None: + file = sys.stdout + self._file = file + self._rate_limiter = RateLimiter(min_update_interval_seconds) + self._finished = False + + self._spin_cycle = itertools.cycle(spin_chars) + + self._file.write(" " * get_indentation() + self._message + " ... ") + self._width = 0 + + def _write(self, status: str) -> None: + assert not self._finished + # Erase what we wrote before by backspacing to the beginning, writing + # spaces to overwrite the old text, and then backspacing again + backup = "\b" * self._width + self._file.write(backup + " " * self._width + backup) + # Now we have a blank slate to add our status + self._file.write(status) + self._width = len(status) + self._file.flush() + self._rate_limiter.reset() + + def spin(self) -> None: + if self._finished: + return + if not self._rate_limiter.ready(): + return + self._write(next(self._spin_cycle)) + + def finish(self, final_status: str) -> None: + if self._finished: + return + self._write(final_status) + self._file.write("\n") + self._file.flush() + self._finished = True + + +# Used for dumb terminals, non-interactive installs (no tty), etc. +# We still print updates occasionally (once every 60 seconds by default) to +# act as a keep-alive for systems like Travis-CI that take lack-of-output as +# an indication that a task has frozen. +class NonInteractiveSpinner(SpinnerInterface): + def __init__(self, message: str, min_update_interval_seconds: float = 60.0) -> None: + self._message = message + self._finished = False + self._rate_limiter = RateLimiter(min_update_interval_seconds) + self._update("started") + + def _update(self, status: str) -> None: + assert not self._finished + self._rate_limiter.reset() + logger.info("%s: %s", self._message, status) + + def spin(self) -> None: + if self._finished: + return + if not self._rate_limiter.ready(): + return + self._update("still running...") + + def finish(self, final_status: str) -> None: + if self._finished: + return + self._update(f"finished with status '{final_status}'") + self._finished = True + + +class RateLimiter: + def __init__(self, min_update_interval_seconds: float) -> None: + self._min_update_interval_seconds = min_update_interval_seconds + self._last_update: float = 0 + + def ready(self) -> bool: + now = time.time() + delta = now - self._last_update + return delta >= self._min_update_interval_seconds + + def reset(self) -> None: + self._last_update = time.time() + + +@contextlib.contextmanager +def open_spinner(message: str) -> Iterator[SpinnerInterface]: + # Interactive spinner goes directly to sys.stdout rather than being routed + # through the logging system, but it acts like it has level INFO, + # i.e. it's only displayed if we're at level INFO or better. + # Non-interactive spinner goes through the logging system, so it is always + # in sync with logging configuration. + if sys.stdout.isatty() and logger.getEffectiveLevel() <= logging.INFO: + spinner: SpinnerInterface = InteractiveSpinner(message) + else: + spinner = NonInteractiveSpinner(message) + try: + with hidden_cursor(sys.stdout): + yield spinner + except KeyboardInterrupt: + spinner.finish("canceled") + raise + except Exception: + spinner.finish("error") + raise + else: + spinner.finish("done") + + +@contextlib.contextmanager +def hidden_cursor(file: IO[str]) -> Iterator[None]: + # The Windows terminal does not support the hide/show cursor ANSI codes, + # even via colorama. So don't even try. + if WINDOWS: + yield + # We don't want to clutter the output with control characters if we're + # writing to a file, or if the user is running with --quiet. + # See https://github.com/pypa/pip/issues/3418 + elif not file.isatty() or logger.getEffectiveLevel() > logging.INFO: + yield + else: + file.write(HIDE_CURSOR) + try: + yield + finally: + file.write(SHOW_CURSOR) diff --git a/pipenv/patched/notpip/_internal/cli/status_codes.py b/pipenv/patched/notpip/_internal/cli/status_codes.py index 275360a3..5e29502c 100644 --- a/pipenv/patched/notpip/_internal/cli/status_codes.py +++ b/pipenv/patched/notpip/_internal/cli/status_codes.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - SUCCESS = 0 ERROR = 1 UNKNOWN_ERROR = 2 diff --git a/pipenv/patched/notpip/_internal/commands/__init__.py b/pipenv/patched/notpip/_internal/commands/__init__.py index a403c6f9..24ad0b49 100644 --- a/pipenv/patched/notpip/_internal/commands/__init__.py +++ b/pipenv/patched/notpip/_internal/commands/__init__.py @@ -1,60 +1,104 @@ """ Package containing all pip commands """ -from __future__ import absolute_import -from pipenv.patched.notpip._internal.commands.completion import CompletionCommand -from pipenv.patched.notpip._internal.commands.configuration import ConfigurationCommand -from pipenv.patched.notpip._internal.commands.download import DownloadCommand -from pipenv.patched.notpip._internal.commands.freeze import FreezeCommand -from pipenv.patched.notpip._internal.commands.hash import HashCommand -from pipenv.patched.notpip._internal.commands.help import HelpCommand -from pipenv.patched.notpip._internal.commands.list import ListCommand -from pipenv.patched.notpip._internal.commands.check import CheckCommand -from pipenv.patched.notpip._internal.commands.search import SearchCommand -from pipenv.patched.notpip._internal.commands.show import ShowCommand -from pipenv.patched.notpip._internal.commands.install import InstallCommand -from pipenv.patched.notpip._internal.commands.uninstall import UninstallCommand -from pipenv.patched.notpip._internal.commands.wheel import WheelCommand +import importlib +from collections import OrderedDict, namedtuple +from typing import Any, Dict, Optional -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING +from pipenv.patched.notpip._internal.cli.base_command import Command -if MYPY_CHECK_RUNNING: - from typing import List, Type # noqa: F401 - from pipenv.patched.notpip._internal.cli.base_command import Command # noqa: F401 +CommandInfo = namedtuple('CommandInfo', 'module_path, class_name, summary') -commands_order = [ - InstallCommand, - DownloadCommand, - UninstallCommand, - FreezeCommand, - ListCommand, - ShowCommand, - CheckCommand, - ConfigurationCommand, - SearchCommand, - WheelCommand, - HashCommand, - CompletionCommand, - HelpCommand, -] # type: List[Type[Command]] - -commands_dict = {c.name: c for c in commands_order} +# The ordering matters for help display. +# Also, even though the module path starts with the same +# "pipenv.patched.notpip._internal.commands" prefix in each case, we include the full path +# because it makes testing easier (specifically when modifying commands_dict +# in test setup / teardown by adding info for a FakeCommand class defined +# in a test-related module). +# Finally, we need to pass an iterable of pairs here rather than a dict +# so that the ordering won't be lost when using Python 2.7. +commands_dict: Dict[str, CommandInfo] = OrderedDict([ + ('install', CommandInfo( + 'pipenv.patched.notpip._internal.commands.install', 'InstallCommand', + 'Install packages.', + )), + ('download', CommandInfo( + 'pipenv.patched.notpip._internal.commands.download', 'DownloadCommand', + 'Download packages.', + )), + ('uninstall', CommandInfo( + 'pipenv.patched.notpip._internal.commands.uninstall', 'UninstallCommand', + 'Uninstall packages.', + )), + ('freeze', CommandInfo( + 'pipenv.patched.notpip._internal.commands.freeze', 'FreezeCommand', + 'Output installed packages in requirements format.', + )), + ('list', CommandInfo( + 'pipenv.patched.notpip._internal.commands.list', 'ListCommand', + 'List installed packages.', + )), + ('show', CommandInfo( + 'pipenv.patched.notpip._internal.commands.show', 'ShowCommand', + 'Show information about installed packages.', + )), + ('check', CommandInfo( + 'pipenv.patched.notpip._internal.commands.check', 'CheckCommand', + 'Verify installed packages have compatible dependencies.', + )), + ('config', CommandInfo( + 'pipenv.patched.notpip._internal.commands.configuration', 'ConfigurationCommand', + 'Manage local and global configuration.', + )), + ('search', CommandInfo( + 'pipenv.patched.notpip._internal.commands.search', 'SearchCommand', + 'Search PyPI for packages.', + )), + ('cache', CommandInfo( + 'pipenv.patched.notpip._internal.commands.cache', 'CacheCommand', + "Inspect and manage pip's wheel cache.", + )), + ('index', CommandInfo( + 'pipenv.patched.notpip._internal.commands.index', 'IndexCommand', + "Inspect information available from package indexes.", + )), + ('wheel', CommandInfo( + 'pipenv.patched.notpip._internal.commands.wheel', 'WheelCommand', + 'Build wheels from your requirements.', + )), + ('hash', CommandInfo( + 'pipenv.patched.notpip._internal.commands.hash', 'HashCommand', + 'Compute hashes of package archives.', + )), + ('completion', CommandInfo( + 'pipenv.patched.notpip._internal.commands.completion', 'CompletionCommand', + 'A helper command used for command completion.', + )), + ('debug', CommandInfo( + 'pipenv.patched.notpip._internal.commands.debug', 'DebugCommand', + 'Show information useful for debugging.', + )), + ('help', CommandInfo( + 'pipenv.patched.notpip._internal.commands.help', 'HelpCommand', + 'Show help for commands.', + )), +]) -def get_summaries(ordered=True): - """Yields sorted (command name, command summary) tuples.""" +def create_command(name: str, **kwargs: Any) -> Command: + """ + Create an instance of the Command class with the given name. + """ + module_path, class_name, summary = commands_dict[name] + module = importlib.import_module(module_path) + command_class = getattr(module, class_name) + command = command_class(name=name, summary=summary, **kwargs) - if ordered: - cmditems = _sort_commands(commands_dict, commands_order) - else: - cmditems = commands_dict.items() - - for name, command_class in cmditems: - yield (name, command_class.summary) + return command -def get_similar_commands(name): +def get_similar_commands(name: str) -> Optional[str]: """Command name auto-correct.""" from difflib import get_close_matches @@ -65,15 +109,4 @@ def get_similar_commands(name): if close_commands: return close_commands[0] else: - return False - - -def _sort_commands(cmddict, order): - def keyfn(key): - try: - return order.index(key[1]) - except ValueError: - # unordered items should come last - return 0xff - - return sorted(cmddict.items(), key=keyfn) + return None diff --git a/pipenv/patched/notpip/_internal/commands/cache.py b/pipenv/patched/notpip/_internal/commands/cache.py new file mode 100644 index 00000000..2668996a --- /dev/null +++ b/pipenv/patched/notpip/_internal/commands/cache.py @@ -0,0 +1,216 @@ +import os +import textwrap +from optparse import Values +from typing import Any, List + +import pipenv.patched.notpip._internal.utils.filesystem as filesystem +from pipenv.patched.notpip._internal.cli.base_command import Command +from pipenv.patched.notpip._internal.cli.status_codes import ERROR, SUCCESS +from pipenv.patched.notpip._internal.exceptions import CommandError, PipError +from pipenv.patched.notpip._internal.utils.logging import getLogger + +logger = getLogger(__name__) + + +class CacheCommand(Command): + """ + Inspect and manage pip's wheel cache. + + Subcommands: + + - dir: Show the cache directory. + - info: Show information about the cache. + - list: List filenames of packages stored in the cache. + - remove: Remove one or more package from the cache. + - purge: Remove all items from the cache. + + ```` can be a glob expression or a package name. + """ + + ignore_require_venv = True + usage = """ + %prog dir + %prog info + %prog list [] [--format=[human, abspath]] + %prog remove + %prog purge + """ + + def add_options(self) -> None: + + self.cmd_opts.add_option( + '--format', + action='store', + dest='list_format', + default="human", + choices=('human', 'abspath'), + help="Select the output format among: human (default) or abspath" + ) + + self.parser.insert_option_group(0, self.cmd_opts) + + def run(self, options: Values, args: List[Any]) -> int: + handlers = { + "dir": self.get_cache_dir, + "info": self.get_cache_info, + "list": self.list_cache_items, + "remove": self.remove_cache_items, + "purge": self.purge_cache, + } + + if not options.cache_dir: + logger.error("pip cache commands can not " + "function since cache is disabled.") + return ERROR + + # Determine action + if not args or args[0] not in handlers: + logger.error( + "Need an action (%s) to perform.", + ", ".join(sorted(handlers)), + ) + return ERROR + + action = args[0] + + # Error handling happens here, not in the action-handlers. + try: + handlers[action](options, args[1:]) + except PipError as e: + logger.error(e.args[0]) + return ERROR + + return SUCCESS + + def get_cache_dir(self, options: Values, args: List[Any]) -> None: + if args: + raise CommandError('Too many arguments') + + logger.info(options.cache_dir) + + def get_cache_info(self, options: Values, args: List[Any]) -> None: + if args: + raise CommandError('Too many arguments') + + num_http_files = len(self._find_http_files(options)) + num_packages = len(self._find_wheels(options, '*')) + + http_cache_location = self._cache_dir(options, 'http') + wheels_cache_location = self._cache_dir(options, 'wheels') + http_cache_size = filesystem.format_directory_size(http_cache_location) + wheels_cache_size = filesystem.format_directory_size( + wheels_cache_location + ) + + message = textwrap.dedent(""" + Package index page cache location: {http_cache_location} + Package index page cache size: {http_cache_size} + Number of HTTP files: {num_http_files} + Wheels location: {wheels_cache_location} + Wheels size: {wheels_cache_size} + Number of wheels: {package_count} + """).format( + http_cache_location=http_cache_location, + http_cache_size=http_cache_size, + num_http_files=num_http_files, + wheels_cache_location=wheels_cache_location, + package_count=num_packages, + wheels_cache_size=wheels_cache_size, + ).strip() + + logger.info(message) + + def list_cache_items(self, options: Values, args: List[Any]) -> None: + if len(args) > 1: + raise CommandError('Too many arguments') + + if args: + pattern = args[0] + else: + pattern = '*' + + files = self._find_wheels(options, pattern) + if options.list_format == 'human': + self.format_for_human(files) + else: + self.format_for_abspath(files) + + def format_for_human(self, files: List[str]) -> None: + if not files: + logger.info('Nothing cached.') + return + + results = [] + for filename in files: + wheel = os.path.basename(filename) + size = filesystem.format_file_size(filename) + results.append(f' - {wheel} ({size})') + logger.info('Cache contents:\n') + logger.info('\n'.join(sorted(results))) + + def format_for_abspath(self, files: List[str]) -> None: + if not files: + return + + results = [] + for filename in files: + results.append(filename) + + logger.info('\n'.join(sorted(results))) + + def remove_cache_items(self, options: Values, args: List[Any]) -> None: + if len(args) > 1: + raise CommandError('Too many arguments') + + if not args: + raise CommandError('Please provide a pattern') + + files = self._find_wheels(options, args[0]) + + # Only fetch http files if no specific pattern given + if args[0] == '*': + files += self._find_http_files(options) + + if not files: + raise CommandError('No matching packages') + + for filename in files: + os.unlink(filename) + logger.verbose("Removed %s", filename) + logger.info("Files removed: %s", len(files)) + + def purge_cache(self, options: Values, args: List[Any]) -> None: + if args: + raise CommandError('Too many arguments') + + return self.remove_cache_items(options, ['*']) + + def _cache_dir(self, options: Values, subdir: str) -> str: + return os.path.join(options.cache_dir, subdir) + + def _find_http_files(self, options: Values) -> List[str]: + http_dir = self._cache_dir(options, 'http') + return filesystem.find_files(http_dir, '*') + + def _find_wheels(self, options: Values, pattern: str) -> List[str]: + wheel_dir = self._cache_dir(options, 'wheels') + + # The wheel filename format, as specified in PEP 427, is: + # {distribution}-{version}(-{build})?-{python}-{abi}-{platform}.whl + # + # Additionally, non-alphanumeric values in the distribution are + # normalized to underscores (_), meaning hyphens can never occur + # before `-{version}`. + # + # Given that information: + # - If the pattern we're given contains a hyphen (-), the user is + # providing at least the version. Thus, we can just append `*.whl` + # to match the rest of it. + # - If the pattern we're given doesn't contain a hyphen (-), the + # user is only providing the name. Thus, we append `-*.whl` to + # match the hyphen before the version, followed by anything else. + # + # PEP 427: https://www.python.org/dev/peps/pep-0427/ + pattern = pattern + ("*.whl" if "-" in pattern else "-*.whl") + + return filesystem.find_files(wheel_dir, pattern) diff --git a/pipenv/patched/notpip/_internal/commands/check.py b/pipenv/patched/notpip/_internal/commands/check.py index cf84f5df..219a02c5 100644 --- a/pipenv/patched/notpip/_internal/commands/check.py +++ b/pipenv/patched/notpip/_internal/commands/check.py @@ -1,28 +1,33 @@ import logging +from optparse import Values +from typing import Any, List from pipenv.patched.notpip._internal.cli.base_command import Command +from pipenv.patched.notpip._internal.cli.status_codes import ERROR, SUCCESS from pipenv.patched.notpip._internal.operations.check import ( - check_package_set, create_package_set_from_installed, + check_package_set, + create_package_set_from_installed, ) +from pipenv.patched.notpip._internal.utils.misc import write_output logger = logging.getLogger(__name__) class CheckCommand(Command): """Verify installed packages have compatible dependencies.""" - name = 'check' + usage = """ %prog [options]""" - summary = 'Verify installed packages have compatible dependencies.' - def run(self, options, args): + def run(self, options: Values, args: List[Any]) -> int: + package_set, parsing_probs = create_package_set_from_installed() missing, conflicting = check_package_set(package_set) for project_name in missing: version = package_set[project_name].version for dependency in missing[project_name]: - logger.info( + write_output( "%s %s requires %s, which is not installed.", project_name, version, dependency[0], ) @@ -30,12 +35,13 @@ class CheckCommand(Command): for project_name in conflicting: version = package_set[project_name].version for dep_name, dep_version, req in conflicting[project_name]: - logger.info( + write_output( "%s %s has requirement %s, but you have %s %s.", project_name, version, req, dep_name, dep_version, ) if missing or conflicting or parsing_probs: - return 1 + return ERROR else: - logger.info("No broken requirements found.") + write_output("No broken requirements found.") + return SUCCESS diff --git a/pipenv/patched/notpip/_internal/commands/completion.py b/pipenv/patched/notpip/_internal/commands/completion.py index cb8a11a7..7a630546 100644 --- a/pipenv/patched/notpip/_internal/commands/completion.py +++ b/pipenv/patched/notpip/_internal/commands/completion.py @@ -1,35 +1,36 @@ -from __future__ import absolute_import - import sys import textwrap +from optparse import Values +from typing import List from pipenv.patched.notpip._internal.cli.base_command import Command +from pipenv.patched.notpip._internal.cli.status_codes import SUCCESS from pipenv.patched.notpip._internal.utils.misc import get_prog BASE_COMPLETION = """ -# pip %(shell)s completion start%(script)s# pip %(shell)s completion end +# pip {shell} completion start{script}# pip {shell} completion end """ COMPLETION_SCRIPTS = { 'bash': """ _pip_completion() - { - COMPREPLY=( $( COMP_WORDS="${COMP_WORDS[*]}" \\ + {{ + COMPREPLY=( $( COMP_WORDS="${{COMP_WORDS[*]}}" \\ COMP_CWORD=$COMP_CWORD \\ - PIP_AUTO_COMPLETE=1 $1 ) ) - } - complete -o default -F _pip_completion %(prog)s + PIP_AUTO_COMPLETE=1 $1 2>/dev/null ) ) + }} + complete -o default -F _pip_completion {prog} """, 'zsh': """ - function _pip_completion { + function _pip_completion {{ local words cword read -Ac words read -cn cword reply=( $( COMP_WORDS="$words[*]" \\ COMP_CWORD=$(( cword-1 )) \\ - PIP_AUTO_COMPLETE=1 $words[1] ) ) - } - compctl -K _pip_completion %(prog)s + PIP_AUTO_COMPLETE=1 $words[1] 2>/dev/null )) + }} + compctl -K _pip_completion {prog} """, 'fish': """ function __fish_complete_pip @@ -40,55 +41,51 @@ COMPLETION_SCRIPTS = { set -lx PIP_AUTO_COMPLETE 1 string split \\ -- (eval $COMP_WORDS[1]) end - complete -fa "(__fish_complete_pip)" -c %(prog)s + complete -fa "(__fish_complete_pip)" -c {prog} """, } class CompletionCommand(Command): """A helper command to be used for command completion.""" - name = 'completion' - summary = 'A helper command used for command completion.' + ignore_require_venv = True - def __init__(self, *args, **kw): - super(CompletionCommand, self).__init__(*args, **kw) - - cmd_opts = self.cmd_opts - - cmd_opts.add_option( + def add_options(self) -> None: + self.cmd_opts.add_option( '--bash', '-b', action='store_const', const='bash', dest='shell', help='Emit completion code for bash') - cmd_opts.add_option( + self.cmd_opts.add_option( '--zsh', '-z', action='store_const', const='zsh', dest='shell', help='Emit completion code for zsh') - cmd_opts.add_option( + self.cmd_opts.add_option( '--fish', '-f', action='store_const', const='fish', dest='shell', help='Emit completion code for fish') - self.parser.insert_option_group(0, cmd_opts) + self.parser.insert_option_group(0, self.cmd_opts) - def run(self, options, args): + def run(self, options: Values, args: List[str]) -> int: """Prints the completion code of the given shell""" shells = COMPLETION_SCRIPTS.keys() shell_options = ['--' + shell for shell in sorted(shells)] if options.shell in shells: script = textwrap.dedent( - COMPLETION_SCRIPTS.get(options.shell, '') % { - 'prog': get_prog(), - } + COMPLETION_SCRIPTS.get(options.shell, '').format( + prog=get_prog()) ) - print(BASE_COMPLETION % {'script': script, 'shell': options.shell}) + print(BASE_COMPLETION.format(script=script, shell=options.shell)) + return SUCCESS else: sys.stderr.write( - 'ERROR: You must pass %s\n' % ' or '.join(shell_options) + 'ERROR: You must pass {}\n' .format(' or '.join(shell_options)) ) + return SUCCESS diff --git a/pipenv/patched/notpip/_internal/commands/configuration.py b/pipenv/patched/notpip/_internal/commands/configuration.py index 6c1dbdfd..658074f2 100644 --- a/pipenv/patched/notpip/_internal/commands/configuration.py +++ b/pipenv/patched/notpip/_internal/commands/configuration.py @@ -1,35 +1,44 @@ import logging import os import subprocess +from optparse import Values +from typing import Any, List, Optional from pipenv.patched.notpip._internal.cli.base_command import Command from pipenv.patched.notpip._internal.cli.status_codes import ERROR, SUCCESS -from pipenv.patched.notpip._internal.configuration import Configuration, kinds +from pipenv.patched.notpip._internal.configuration import ( + Configuration, + Kind, + get_configuration_files, + kinds, +) from pipenv.patched.notpip._internal.exceptions import PipError -from pipenv.patched.notpip._internal.locations import venv_config_file -from pipenv.patched.notpip._internal.utils.misc import get_prog +from pipenv.patched.notpip._internal.utils.logging import indent_log +from pipenv.patched.notpip._internal.utils.misc import get_prog, write_output logger = logging.getLogger(__name__) class ConfigurationCommand(Command): - """Manage local and global configuration. + """ + Manage local and global configuration. - Subcommands: + Subcommands: - list: List the active configuration (or from the file specified) - edit: Edit the configuration file in an editor - get: Get the value associated with name - set: Set the name=value - unset: Unset the value associated with name + - list: List the active configuration (or from the file specified) + - edit: Edit the configuration file in an editor + - get: Get the value associated with name + - set: Set the name=value + - unset: Unset the value associated with name + - debug: List the configuration files and values defined under them - If none of --user, --global and --venv are passed, a virtual - environment configuration file is used if one is active and the file - exists. Otherwise, all modifications happen on the to the user file by - default. + If none of --user, --global and --site are passed, a virtual + environment configuration file is used if one is active and the file + exists. Otherwise, all modifications happen on the to the user file by + default. """ - name = 'config' + ignore_require_venv = True usage = """ %prog [] list %prog [] [--editor ] edit @@ -37,15 +46,10 @@ class ConfigurationCommand(Command): %prog [] get name %prog [] set name value %prog [] unset name + %prog [] debug """ - summary = "Manage local and global configuration." - - def __init__(self, *args, **kwargs): - super(ConfigurationCommand, self).__init__(*args, **kwargs) - - self.configuration = None - + def add_options(self) -> None: self.cmd_opts.add_option( '--editor', dest='editor', @@ -74,28 +78,30 @@ class ConfigurationCommand(Command): ) self.cmd_opts.add_option( - '--venv', - dest='venv_file', + '--site', + dest='site_file', action='store_true', default=False, - help='Use the virtualenv configuration file only' + help='Use the current environment configuration file only' ) self.parser.insert_option_group(0, self.cmd_opts) - def run(self, options, args): + def run(self, options: Values, args: List[str]) -> int: handlers = { "list": self.list_values, "edit": self.open_in_editor, "get": self.get_name, "set": self.set_name_value, - "unset": self.unset_name + "unset": self.unset_name, + "debug": self.list_config_values, } # Determine action if not args or args[0] not in handlers: - logger.error("Need an action ({}) to perform.".format( - ", ".join(sorted(handlers))) + logger.error( + "Need an action (%s) to perform.", + ", ".join(sorted(handlers)), ) return ERROR @@ -126,55 +132,89 @@ class ConfigurationCommand(Command): return SUCCESS - def _determine_file(self, options, need_value): - file_options = { - kinds.USER: options.user_file, - kinds.GLOBAL: options.global_file, - kinds.VENV: options.venv_file - } + def _determine_file(self, options: Values, need_value: bool) -> Optional[Kind]: + file_options = [key for key, value in ( + (kinds.USER, options.user_file), + (kinds.GLOBAL, options.global_file), + (kinds.SITE, options.site_file), + ) if value] - if sum(file_options.values()) == 0: + if not file_options: if not need_value: return None - # Default to user, unless there's a virtualenv file. - elif os.path.exists(venv_config_file): - return kinds.VENV + # Default to user, unless there's a site file. + elif any( + os.path.exists(site_config_file) + for site_config_file in get_configuration_files()[kinds.SITE] + ): + return kinds.SITE else: return kinds.USER - elif sum(file_options.values()) == 1: - # There's probably a better expression for this. - return [key for key in file_options if file_options[key]][0] + elif len(file_options) == 1: + return file_options[0] raise PipError( "Need exactly one file to operate upon " - "(--user, --venv, --global) to perform." + "(--user, --site, --global) to perform." ) - def list_values(self, options, args): + def list_values(self, options: Values, args: List[str]) -> None: self._get_n_args(args, "list", n=0) for key, value in sorted(self.configuration.items()): - logger.info("%s=%r", key, value) + write_output("%s=%r", key, value) - def get_name(self, options, args): + def get_name(self, options: Values, args: List[str]) -> None: key = self._get_n_args(args, "get [name]", n=1) value = self.configuration.get_value(key) - logger.info("%s", value) + write_output("%s", value) - def set_name_value(self, options, args): + def set_name_value(self, options: Values, args: List[str]) -> None: key, value = self._get_n_args(args, "set [name] [value]", n=2) self.configuration.set_value(key, value) self._save_configuration() - def unset_name(self, options, args): + def unset_name(self, options: Values, args: List[str]) -> None: key = self._get_n_args(args, "unset [name]", n=1) self.configuration.unset_value(key) self._save_configuration() - def open_in_editor(self, options, args): + def list_config_values(self, options: Values, args: List[str]) -> None: + """List config key-value pairs across different config files""" + self._get_n_args(args, "debug", n=0) + + self.print_env_var_values() + # Iterate over config files and print if they exist, and the + # key-value pairs present in them if they do + for variant, files in sorted(self.configuration.iter_config_files()): + write_output("%s:", variant) + for fname in files: + with indent_log(): + file_exists = os.path.exists(fname) + write_output("%s, exists: %r", + fname, file_exists) + if file_exists: + self.print_config_file_values(variant) + + def print_config_file_values(self, variant: Kind) -> None: + """Get key-value pairs from the file of a variant""" + for name, value in self.configuration.\ + get_values_in_config(variant).items(): + with indent_log(): + write_output("%s: %s", name, value) + + def print_env_var_values(self) -> None: + """Get key-values pairs present as environment variables""" + write_output("%s:", 'env_var') + with indent_log(): + for key, value in sorted(self.configuration.get_environ_vars()): + env_var = f'PIP_{key.upper()}' + write_output("%s=%r", env_var, value) + + def open_in_editor(self, options: Values, args: List[str]) -> None: editor = self._determine_editor(options) fname = self.configuration.get_file_to_edit() @@ -189,7 +229,7 @@ class ConfigurationCommand(Command): .format(e.returncode) ) - def _get_n_args(self, args, example, n): + def _get_n_args(self, args: List[str], example: str, n: int) -> Any: """Helper to make sure the command got the right number of arguments """ if len(args) != n: @@ -204,19 +244,18 @@ class ConfigurationCommand(Command): else: return args - def _save_configuration(self): + def _save_configuration(self) -> None: # We successfully ran a modifying command. Need to save the # configuration. try: self.configuration.save() except Exception: - logger.error( - "Unable to save configuration. Please report this as a bug.", - exc_info=1 + logger.exception( + "Unable to save configuration. Please report this as a bug." ) raise PipError("Internal Error.") - def _determine_editor(self, options): + def _determine_editor(self, options: Values) -> str: if options.editor is not None: return options.editor elif "VISUAL" in os.environ: diff --git a/pipenv/patched/notpip/_internal/commands/debug.py b/pipenv/patched/notpip/_internal/commands/debug.py new file mode 100644 index 00000000..97035956 --- /dev/null +++ b/pipenv/patched/notpip/_internal/commands/debug.py @@ -0,0 +1,204 @@ +import locale +import logging +import os +import sys +from optparse import Values +from types import ModuleType +from typing import Any, Dict, List, Optional + +import pipenv.patched.notpip._vendor +from pipenv.patched.notpip._vendor.certifi import where +from pipenv.patched.notpip._vendor.packaging.version import parse as parse_version + +from pipenv.patched.notpip import __file__ as pip_location +from pipenv.patched.notpip._internal.cli import cmdoptions +from pipenv.patched.notpip._internal.cli.base_command import Command +from pipenv.patched.notpip._internal.cli.cmdoptions import make_target_python +from pipenv.patched.notpip._internal.cli.status_codes import SUCCESS +from pipenv.patched.notpip._internal.configuration import Configuration +from pipenv.patched.notpip._internal.metadata import get_environment +from pipenv.patched.notpip._internal.utils.logging import indent_log +from pipenv.patched.notpip._internal.utils.misc import get_pip_version + +logger = logging.getLogger(__name__) + + +def show_value(name: str, value: Any) -> None: + logger.info('%s: %s', name, value) + + +def show_sys_implementation() -> None: + logger.info('sys.implementation:') + implementation_name = sys.implementation.name + with indent_log(): + show_value('name', implementation_name) + + +def create_vendor_txt_map() -> Dict[str, str]: + vendor_txt_path = os.path.join( + os.path.dirname(pip_location), + '_vendor', + 'vendor.txt' + ) + + with open(vendor_txt_path) as f: + # Purge non version specifying lines. + # Also, remove any space prefix or suffixes (including comments). + lines = [line.strip().split(' ', 1)[0] + for line in f.readlines() if '==' in line] + + # Transform into "module" -> version dict. + return dict(line.split('==', 1) for line in lines) # type: ignore + + +def get_module_from_module_name(module_name: str) -> ModuleType: + # Module name can be uppercase in vendor.txt for some reason... + module_name = module_name.lower() + # PATCH: setuptools is actually only pkg_resources. + if module_name == 'setuptools': + module_name = 'pkg_resources' + + __import__( + f'pipenv.patched.notpip._vendor.{module_name}', + globals(), + locals(), + level=0 + ) + return getattr(pipenv.patched.notpip._vendor, module_name) + + +def get_vendor_version_from_module(module_name: str) -> Optional[str]: + module = get_module_from_module_name(module_name) + version = getattr(module, '__version__', None) + + if not version: + # Try to find version in debundled module info. + env = get_environment([os.path.dirname(module.__file__)]) + dist = env.get_distribution(module_name) + if dist: + version = str(dist.version) + + return version + + +def show_actual_vendor_versions(vendor_txt_versions: Dict[str, str]) -> None: + """Log the actual version and print extra info if there is + a conflict or if the actual version could not be imported. + """ + for module_name, expected_version in vendor_txt_versions.items(): + extra_message = '' + actual_version = get_vendor_version_from_module(module_name) + if not actual_version: + extra_message = ' (Unable to locate actual module version, using'\ + ' vendor.txt specified version)' + actual_version = expected_version + elif parse_version(actual_version) != parse_version(expected_version): + extra_message = ' (CONFLICT: vendor.txt suggests version should'\ + ' be {})'.format(expected_version) + logger.info('%s==%s%s', module_name, actual_version, extra_message) + + +def show_vendor_versions() -> None: + logger.info('vendored library versions:') + + vendor_txt_versions = create_vendor_txt_map() + with indent_log(): + show_actual_vendor_versions(vendor_txt_versions) + + +def show_tags(options: Values) -> None: + tag_limit = 10 + + target_python = make_target_python(options) + tags = target_python.get_tags() + + # Display the target options that were explicitly provided. + formatted_target = target_python.format_given() + suffix = '' + if formatted_target: + suffix = f' (target: {formatted_target})' + + msg = 'Compatible tags: {}{}'.format(len(tags), suffix) + logger.info(msg) + + if options.verbose < 1 and len(tags) > tag_limit: + tags_limited = True + tags = tags[:tag_limit] + else: + tags_limited = False + + with indent_log(): + for tag in tags: + logger.info(str(tag)) + + if tags_limited: + msg = ( + '...\n' + '[First {tag_limit} tags shown. Pass --verbose to show all.]' + ).format(tag_limit=tag_limit) + logger.info(msg) + + +def ca_bundle_info(config: Configuration) -> str: + levels = set() + for key, _ in config.items(): + levels.add(key.split('.')[0]) + + if not levels: + return "Not specified" + + levels_that_override_global = ['install', 'wheel', 'download'] + global_overriding_level = [ + level for level in levels if level in levels_that_override_global + ] + if not global_overriding_level: + return 'global' + + if 'global' in levels: + levels.remove('global') + return ", ".join(levels) + + +class DebugCommand(Command): + """ + Display debug information. + """ + + usage = """ + %prog """ + ignore_require_venv = True + + def add_options(self) -> None: + cmdoptions.add_target_python_options(self.cmd_opts) + self.parser.insert_option_group(0, self.cmd_opts) + self.parser.config.load() + + def run(self, options: Values, args: List[str]) -> int: + logger.warning( + "This command is only meant for debugging. " + "Do not use this with automation for parsing and getting these " + "details, since the output and options of this command may " + "change without notice." + ) + show_value('pip version', get_pip_version()) + show_value('sys.version', sys.version) + show_value('sys.executable', sys.executable) + show_value('sys.getdefaultencoding', sys.getdefaultencoding()) + show_value('sys.getfilesystemencoding', sys.getfilesystemencoding()) + show_value( + 'locale.getpreferredencoding', locale.getpreferredencoding(), + ) + show_value('sys.platform', sys.platform) + show_sys_implementation() + + show_value("'cert' config value", ca_bundle_info(self.parser.config)) + show_value("REQUESTS_CA_BUNDLE", os.environ.get('REQUESTS_CA_BUNDLE')) + show_value("CURL_CA_BUNDLE", os.environ.get('CURL_CA_BUNDLE')) + show_value("pipenv.patched.notpip._vendor.certifi.where()", where()) + show_value("pipenv.patched.notpip._vendor.DEBUNDLED", pipenv.patched.notpip._vendor.DEBUNDLED) + + show_vendor_versions() + + show_tags(options) + + return SUCCESS diff --git a/pipenv/patched/notpip/_internal/commands/download.py b/pipenv/patched/notpip/_internal/commands/download.py index 133ca135..1b3c18a0 100644 --- a/pipenv/patched/notpip/_internal/commands/download.py +++ b/pipenv/patched/notpip/_internal/commands/download.py @@ -1,16 +1,14 @@ -from __future__ import absolute_import - import logging import os +from optparse import Values +from typing import List from pipenv.patched.notpip._internal.cli import cmdoptions -from pipenv.patched.notpip._internal.cli.base_command import RequirementCommand -from pipenv.patched.notpip._internal.operations.prepare import RequirementPreparer -from pipenv.patched.notpip._internal.req import RequirementSet -from pipenv.patched.notpip._internal.req.req_tracker import RequirementTracker -from pipenv.patched.notpip._internal.resolve import Resolver -from pipenv.patched.notpip._internal.utils.filesystem import check_path_owner -from pipenv.patched.notpip._internal.utils.misc import ensure_dir, normalize_path +from pipenv.patched.notpip._internal.cli.cmdoptions import make_target_python +from pipenv.patched.notpip._internal.cli.req_command import RequirementCommand, with_cleanup +from pipenv.patched.notpip._internal.cli.status_codes import SUCCESS +from pipenv.patched.notpip._internal.req.req_tracker import get_requirement_tracker +from pipenv.patched.notpip._internal.utils.misc import ensure_dir, normalize_path, write_output from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory logger = logging.getLogger(__name__) @@ -28,7 +26,6 @@ class DownloadCommand(RequirementCommand): pip also supports downloading from "requirements files", which provide an easy way to specify a whole environment to be downloaded. """ - name = 'download' usage = """ %prog [options] [package-index-options] ... @@ -37,31 +34,25 @@ class DownloadCommand(RequirementCommand): %prog [options] ... %prog [options] ...""" - summary = 'Download packages.' + def add_options(self) -> None: + self.cmd_opts.add_option(cmdoptions.constraints()) + self.cmd_opts.add_option(cmdoptions.requirements()) + self.cmd_opts.add_option(cmdoptions.build_dir()) + self.cmd_opts.add_option(cmdoptions.no_deps()) + self.cmd_opts.add_option(cmdoptions.global_options()) + self.cmd_opts.add_option(cmdoptions.no_binary()) + self.cmd_opts.add_option(cmdoptions.only_binary()) + self.cmd_opts.add_option(cmdoptions.prefer_binary()) + self.cmd_opts.add_option(cmdoptions.src()) + self.cmd_opts.add_option(cmdoptions.pre()) + self.cmd_opts.add_option(cmdoptions.require_hashes()) + self.cmd_opts.add_option(cmdoptions.progress_bar()) + self.cmd_opts.add_option(cmdoptions.no_build_isolation()) + self.cmd_opts.add_option(cmdoptions.use_pep517()) + self.cmd_opts.add_option(cmdoptions.no_use_pep517()) + self.cmd_opts.add_option(cmdoptions.ignore_requires_python()) - def __init__(self, *args, **kw): - super(DownloadCommand, self).__init__(*args, **kw) - - cmd_opts = self.cmd_opts - - cmd_opts.add_option(cmdoptions.constraints()) - cmd_opts.add_option(cmdoptions.requirements()) - cmd_opts.add_option(cmdoptions.build_dir()) - cmd_opts.add_option(cmdoptions.no_deps()) - cmd_opts.add_option(cmdoptions.global_options()) - cmd_opts.add_option(cmdoptions.no_binary()) - cmd_opts.add_option(cmdoptions.only_binary()) - cmd_opts.add_option(cmdoptions.prefer_binary()) - cmd_opts.add_option(cmdoptions.src()) - cmd_opts.add_option(cmdoptions.pre()) - cmd_opts.add_option(cmdoptions.no_clean()) - cmd_opts.add_option(cmdoptions.require_hashes()) - cmd_opts.add_option(cmdoptions.progress_bar()) - cmd_opts.add_option(cmdoptions.no_build_isolation()) - cmd_opts.add_option(cmdoptions.use_pep517()) - cmd_opts.add_option(cmdoptions.no_use_pep517()) - - cmd_opts.add_option( + self.cmd_opts.add_option( '-d', '--dest', '--destination-dir', '--destination-directory', dest='download_dir', metavar='dir', @@ -69,10 +60,7 @@ class DownloadCommand(RequirementCommand): help=("Download packages into ."), ) - cmd_opts.add_option(cmdoptions.platform()) - cmd_opts.add_option(cmdoptions.python_version()) - cmd_opts.add_option(cmdoptions.implementation()) - cmd_opts.add_option(cmdoptions.abi()) + cmdoptions.add_target_python_options(self.cmd_opts) index_opts = cmdoptions.make_option_group( cmdoptions.index_group, @@ -80,97 +68,72 @@ class DownloadCommand(RequirementCommand): ) self.parser.insert_option_group(0, index_opts) - self.parser.insert_option_group(0, cmd_opts) + self.parser.insert_option_group(0, self.cmd_opts) + + @with_cleanup + def run(self, options: Values, args: List[str]) -> int: - def run(self, options, args): options.ignore_installed = True # editable doesn't really make sense for `pip download`, but the bowels # of the RequirementSet code require that property. options.editables = [] - if options.python_version: - python_versions = [options.python_version] - else: - python_versions = None - cmdoptions.check_dist_restriction(options) - options.src_dir = os.path.abspath(options.src_dir) options.download_dir = normalize_path(options.download_dir) - ensure_dir(options.download_dir) - with self._build_session(options) as session: - finder = self._build_package_finder( - options=options, - session=session, - platform=options.platform, - python_versions=python_versions, - abi=options.abi, - implementation=options.implementation, - ) - build_delete = (not (options.no_clean or options.build_dir)) - if options.cache_dir and not check_path_owner(options.cache_dir): - logger.warning( - "The directory '%s' or its parent directory is not owned " - "by the current user and caching wheels has been " - "disabled. check the permissions and owner of that " - "directory. If executing pip with sudo, you may want " - "sudo's -H flag.", - options.cache_dir, - ) - options.cache_dir = None + session = self.get_default_session(options) - with RequirementTracker() as req_tracker, TempDirectory( - options.build_dir, delete=build_delete, kind="download" - ) as directory: + target_python = make_target_python(options) + finder = self._build_package_finder( + options=options, + session=session, + target_python=target_python, + ignore_requires_python=options.ignore_requires_python, + ) - requirement_set = RequirementSet( - require_hashes=options.require_hashes, - ) - self.populate_requirement_set( - requirement_set, - args, - options, - finder, - session, - self.name, - None - ) + req_tracker = self.enter_context(get_requirement_tracker()) - preparer = RequirementPreparer( - build_dir=directory.path, - src_dir=options.src_dir, - download_dir=options.download_dir, - wheel_download_dir=None, - progress_bar=options.progress_bar, - build_isolation=options.build_isolation, - req_tracker=req_tracker, - ) + directory = TempDirectory( + delete=not options.no_clean, + kind="download", + globally_managed=True, + ) - resolver = Resolver( - preparer=preparer, - finder=finder, - session=session, - wheel_cache=None, - use_user_site=False, - upgrade_strategy="to-satisfy-only", - force_reinstall=False, - ignore_dependencies=options.ignore_dependencies, - ignore_requires_python=False, - ignore_installed=True, - isolated=options.isolated_mode, - ) - resolver.resolve(requirement_set) + reqs = self.get_requirements(args, options, finder, session) - downloaded = ' '.join([ - req.name for req in requirement_set.successfully_downloaded - ]) - if downloaded: - logger.info('Successfully downloaded %s', downloaded) + preparer = self.make_requirement_preparer( + temp_build_dir=directory, + options=options, + req_tracker=req_tracker, + session=session, + finder=finder, + download_dir=options.download_dir, + use_user_site=False, + ) - # Clean up - if not options.no_clean: - requirement_set.cleanup_files() + resolver = self.make_resolver( + preparer=preparer, + finder=finder, + options=options, + ignore_requires_python=options.ignore_requires_python, + py_version_info=options.python_version, + ) - return requirement_set + self.trace_basic_info(finder) + + requirement_set = resolver.resolve( + reqs, check_supported_wheels=True + ) + + downloaded: List[str] = [] + for req in requirement_set.requirements.values(): + if req.satisfied_by is None: + assert req.name is not None + preparer.save_linked_requirement(req) + downloaded.append(req.name) + if downloaded: + write_output('Successfully downloaded %s', ' '.join(downloaded)) + + return SUCCESS diff --git a/pipenv/patched/notpip/_internal/commands/freeze.py b/pipenv/patched/notpip/_internal/commands/freeze.py index 343227ba..2344639f 100644 --- a/pipenv/patched/notpip/_internal/commands/freeze.py +++ b/pipenv/patched/notpip/_internal/commands/freeze.py @@ -1,10 +1,10 @@ -from __future__ import absolute_import - import sys +from optparse import Values +from typing import List -from pipenv.patched.notpip._internal.cache import WheelCache +from pipenv.patched.notpip._internal.cli import cmdoptions from pipenv.patched.notpip._internal.cli.base_command import Command -from pipenv.patched.notpip._internal.models.format_control import FormatControl +from pipenv.patched.notpip._internal.cli.status_codes import SUCCESS from pipenv.patched.notpip._internal.operations.freeze import freeze from pipenv.patched.notpip._internal.utils.compat import stdlib_pkgs @@ -17,15 +17,12 @@ class FreezeCommand(Command): packages are listed in a case-insensitive sorted order. """ - name = 'freeze' + usage = """ %prog [options]""" - summary = 'Output installed packages in requirements format.' log_streams = ("ext://sys.stderr", "ext://sys.stderr") - def __init__(self, *args, **kw): - super(FreezeCommand, self).__init__(*args, **kw) - + def add_options(self) -> None: self.cmd_opts.add_option( '-r', '--requirement', dest='requirements', @@ -35,14 +32,6 @@ class FreezeCommand(Command): help="Use the order in the given requirements file and its " "comments when generating output. This option can be " "used multiple times.") - self.cmd_opts.add_option( - '-f', '--find-links', - dest='find_links', - action='append', - default=[], - metavar='URL', - help='URL for finding packages, which will be added to the ' - 'output.') self.cmd_opts.add_option( '-l', '--local', dest='local', @@ -56,41 +45,40 @@ class FreezeCommand(Command): action='store_true', default=False, help='Only output packages installed in user-site.') + self.cmd_opts.add_option(cmdoptions.list_path()) self.cmd_opts.add_option( '--all', dest='freeze_all', action='store_true', help='Do not skip these packages in the output:' - ' %s' % ', '.join(DEV_PKGS)) + ' {}'.format(', '.join(DEV_PKGS))) self.cmd_opts.add_option( '--exclude-editable', dest='exclude_editable', action='store_true', help='Exclude editable package from output.') + self.cmd_opts.add_option(cmdoptions.list_exclude()) self.parser.insert_option_group(0, self.cmd_opts) - def run(self, options, args): - format_control = FormatControl(set(), set()) - wheel_cache = WheelCache(options.cache_dir, format_control) + def run(self, options: Values, args: List[str]) -> int: skip = set(stdlib_pkgs) if not options.freeze_all: skip.update(DEV_PKGS) - freeze_kwargs = dict( + if options.excludes: + skip.update(options.excludes) + + cmdoptions.check_list_path_option(options) + + for line in freeze( requirement=options.requirements, - find_links=options.find_links, local_only=options.local, user_only=options.user, - skip_regex=options.skip_requirements_regex, + paths=options.path, isolated=options.isolated_mode, - wheel_cache=wheel_cache, skip=skip, exclude_editable=options.exclude_editable, - ) - - try: - for line in freeze(**freeze_kwargs): - sys.stdout.write(line + '\n') - finally: - wheel_cache.cleanup() + ): + sys.stdout.write(line + '\n') + return SUCCESS diff --git a/pipenv/patched/notpip/_internal/commands/hash.py b/pipenv/patched/notpip/_internal/commands/hash.py index 183f11ae..3955eb3e 100644 --- a/pipenv/patched/notpip/_internal/commands/hash.py +++ b/pipenv/patched/notpip/_internal/commands/hash.py @@ -1,13 +1,13 @@ -from __future__ import absolute_import - import hashlib import logging import sys +from optparse import Values +from typing import List from pipenv.patched.notpip._internal.cli.base_command import Command -from pipenv.patched.notpip._internal.cli.status_codes import ERROR +from pipenv.patched.notpip._internal.cli.status_codes import ERROR, SUCCESS from pipenv.patched.notpip._internal.utils.hashes import FAVORITE_HASH, STRONG_HASHES -from pipenv.patched.notpip._internal.utils.misc import read_chunks +from pipenv.patched.notpip._internal.utils.misc import read_chunks, write_output logger = logging.getLogger(__name__) @@ -18,37 +18,35 @@ class HashCommand(Command): These can be used with --hash in a requirements file to do repeatable installs. - """ - name = 'hash' + usage = '%prog [options] ...' - summary = 'Compute hashes of package archives.' ignore_require_venv = True - def __init__(self, *args, **kw): - super(HashCommand, self).__init__(*args, **kw) + def add_options(self) -> None: self.cmd_opts.add_option( '-a', '--algorithm', dest='algorithm', choices=STRONG_HASHES, action='store', default=FAVORITE_HASH, - help='The hash algorithm to use: one of %s' % - ', '.join(STRONG_HASHES)) + help='The hash algorithm to use: one of {}'.format( + ', '.join(STRONG_HASHES))) self.parser.insert_option_group(0, self.cmd_opts) - def run(self, options, args): + def run(self, options: Values, args: List[str]) -> int: if not args: self.parser.print_usage(sys.stderr) return ERROR algorithm = options.algorithm for path in args: - logger.info('%s:\n--hash=%s:%s', - path, algorithm, _hash_of_file(path, algorithm)) + write_output('%s:\n--hash=%s:%s', + path, algorithm, _hash_of_file(path, algorithm)) + return SUCCESS -def _hash_of_file(path, algorithm): +def _hash_of_file(path: str, algorithm: str) -> str: """Return the hash digest of a file.""" with open(path, 'rb') as archive: hash = hashlib.new(algorithm) diff --git a/pipenv/patched/notpip/_internal/commands/help.py b/pipenv/patched/notpip/_internal/commands/help.py index f2c61965..5b56dc9a 100644 --- a/pipenv/patched/notpip/_internal/commands/help.py +++ b/pipenv/patched/notpip/_internal/commands/help.py @@ -1,4 +1,5 @@ -from __future__ import absolute_import +from optparse import Values +from typing import List from pipenv.patched.notpip._internal.cli.base_command import Command from pipenv.patched.notpip._internal.cli.status_codes import SUCCESS @@ -7,14 +8,17 @@ from pipenv.patched.notpip._internal.exceptions import CommandError class HelpCommand(Command): """Show help for commands""" - name = 'help' + usage = """ %prog """ - summary = 'Show help for commands.' ignore_require_venv = True - def run(self, options, args): - from pipenv.patched.notpip._internal.commands import commands_dict, get_similar_commands + def run(self, options: Values, args: List[str]) -> int: + from pipenv.patched.notpip._internal.commands import ( + commands_dict, + create_command, + get_similar_commands, + ) try: # 'pip help' with no args is handled by pip.__init__.parseopt() @@ -25,13 +29,13 @@ class HelpCommand(Command): if cmd_name not in commands_dict: guess = get_similar_commands(cmd_name) - msg = ['unknown command "%s"' % cmd_name] + msg = [f'unknown command "{cmd_name}"'] if guess: - msg.append('maybe you meant "%s"' % guess) + msg.append(f'maybe you meant "{guess}"') raise CommandError(' - '.join(msg)) - command = commands_dict[cmd_name]() + command = create_command(cmd_name) command.parser.print_help() return SUCCESS diff --git a/pipenv/patched/notpip/_internal/commands/index.py b/pipenv/patched/notpip/_internal/commands/index.py new file mode 100644 index 00000000..16741772 --- /dev/null +++ b/pipenv/patched/notpip/_internal/commands/index.py @@ -0,0 +1,139 @@ +import logging +from optparse import Values +from typing import Any, Iterable, List, Optional, Union + +from pipenv.patched.notpip._vendor.packaging.version import LegacyVersion, Version + +from pipenv.patched.notpip._internal.cli import cmdoptions +from pipenv.patched.notpip._internal.cli.req_command import IndexGroupCommand +from pipenv.patched.notpip._internal.cli.status_codes import ERROR, SUCCESS +from pipenv.patched.notpip._internal.commands.search import print_dist_installation_info +from pipenv.patched.notpip._internal.exceptions import CommandError, DistributionNotFound, PipError +from pipenv.patched.notpip._internal.index.collector import LinkCollector +from pipenv.patched.notpip._internal.index.package_finder import PackageFinder +from pipenv.patched.notpip._internal.models.selection_prefs import SelectionPreferences +from pipenv.patched.notpip._internal.models.target_python import TargetPython +from pipenv.patched.notpip._internal.network.session import PipSession +from pipenv.patched.notpip._internal.utils.misc import write_output + +logger = logging.getLogger(__name__) + + +class IndexCommand(IndexGroupCommand): + """ + Inspect information available from package indexes. + """ + + usage = """ + %prog versions + """ + + def add_options(self) -> None: + cmdoptions.add_target_python_options(self.cmd_opts) + + self.cmd_opts.add_option(cmdoptions.ignore_requires_python()) + self.cmd_opts.add_option(cmdoptions.pre()) + self.cmd_opts.add_option(cmdoptions.no_binary()) + self.cmd_opts.add_option(cmdoptions.only_binary()) + + index_opts = cmdoptions.make_option_group( + cmdoptions.index_group, + self.parser, + ) + + self.parser.insert_option_group(0, index_opts) + self.parser.insert_option_group(0, self.cmd_opts) + + def run(self, options: Values, args: List[Any]) -> int: + handlers = { + "versions": self.get_available_package_versions, + } + + logger.warning( + "pip index is currently an experimental command. " + "It may be removed/changed in a future release " + "without prior warning." + ) + + # Determine action + if not args or args[0] not in handlers: + logger.error( + "Need an action (%s) to perform.", + ", ".join(sorted(handlers)), + ) + return ERROR + + action = args[0] + + # Error handling happens here, not in the action-handlers. + try: + handlers[action](options, args[1:]) + except PipError as e: + logger.error(e.args[0]) + return ERROR + + return SUCCESS + + def _build_package_finder( + self, + options: Values, + session: PipSession, + target_python: Optional[TargetPython] = None, + ignore_requires_python: Optional[bool] = None, + ) -> PackageFinder: + """ + Create a package finder appropriate to the index command. + """ + link_collector = LinkCollector.create(session, options=options) + + # Pass allow_yanked=False to ignore yanked versions. + selection_prefs = SelectionPreferences( + allow_yanked=False, + allow_all_prereleases=options.pre, + ignore_requires_python=ignore_requires_python, + ) + + return PackageFinder.create( + link_collector=link_collector, + selection_prefs=selection_prefs, + target_python=target_python, + ) + + def get_available_package_versions(self, options: Values, args: List[Any]) -> None: + if len(args) != 1: + raise CommandError('You need to specify exactly one argument') + + target_python = cmdoptions.make_target_python(options) + query = args[0] + + with self._build_session(options) as session: + finder = self._build_package_finder( + options=options, + session=session, + target_python=target_python, + ignore_requires_python=options.ignore_requires_python, + ) + + versions: Iterable[Union[LegacyVersion, Version]] = ( + candidate.version + for candidate in finder.find_all_candidates(query) + ) + + if not options.pre: + # Remove prereleases + versions = (version for version in versions + if not version.is_prerelease) + versions = set(versions) + + if not versions: + raise DistributionNotFound( + 'No matching distribution found for {}'.format(query)) + + formatted_versions = [str(ver) for ver in sorted( + versions, reverse=True)] + latest = formatted_versions[0] + + write_output('{} ({})'.format(query, latest)) + write_output('Available versions: {}'.format( + ', '.join(formatted_versions))) + print_dist_installation_info(query, latest) diff --git a/pipenv/patched/notpip/_internal/commands/install.py b/pipenv/patched/notpip/_internal/commands/install.py index 68255c85..d4e2296a 100644 --- a/pipenv/patched/notpip/_internal/commands/install.py +++ b/pipenv/patched/notpip/_internal/commands/install.py @@ -1,36 +1,61 @@ -from __future__ import absolute_import - import errno -import logging import operator import os import shutil -from optparse import SUPPRESS_HELP +import site +from optparse import SUPPRESS_HELP, Values +from typing import Iterable, List, Optional -from pipenv.patched.notpip._vendor import pkg_resources +from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name from pipenv.patched.notpip._internal.cache import WheelCache from pipenv.patched.notpip._internal.cli import cmdoptions -from pipenv.patched.notpip._internal.cli.base_command import RequirementCommand -from pipenv.patched.notpip._internal.cli.status_codes import ERROR -from pipenv.patched.notpip._internal.exceptions import ( - CommandError, InstallationError, PreviousBuildDirError, +from pipenv.patched.notpip._internal.cli.cmdoptions import make_target_python +from pipenv.patched.notpip._internal.cli.req_command import ( + RequirementCommand, + warn_if_run_as_root, + with_cleanup, ) -from pipenv.patched.notpip._internal.locations import distutils_scheme, virtualenv_no_global -from pipenv.patched.notpip._internal.operations.check import check_install_conflicts -from pipenv.patched.notpip._internal.operations.prepare import RequirementPreparer -from pipenv.patched.notpip._internal.req import RequirementSet, install_given_reqs -from pipenv.patched.notpip._internal.req.req_tracker import RequirementTracker -from pipenv.patched.notpip._internal.resolve import Resolver -from pipenv.patched.notpip._internal.utils.filesystem import check_path_owner +from pipenv.patched.notpip._internal.cli.status_codes import ERROR, SUCCESS +from pipenv.patched.notpip._internal.exceptions import CommandError, InstallationError +from pipenv.patched.notpip._internal.locations import get_scheme +from pipenv.patched.notpip._internal.metadata import get_environment +from pipenv.patched.notpip._internal.models.format_control import FormatControl +from pipenv.patched.notpip._internal.operations.check import ConflictDetails, check_install_conflicts +from pipenv.patched.notpip._internal.req import install_given_reqs +from pipenv.patched.notpip._internal.req.req_install import InstallRequirement +from pipenv.patched.notpip._internal.req.req_tracker import get_requirement_tracker +from pipenv.patched.notpip._internal.utils.compat import WINDOWS +from pipenv.patched.notpip._internal.utils.distutils_args import parse_distutils_args +from pipenv.patched.notpip._internal.utils.filesystem import test_writable_dir +from pipenv.patched.notpip._internal.utils.logging import getLogger from pipenv.patched.notpip._internal.utils.misc import ( - ensure_dir, get_installed_version, + ensure_dir, + get_pip_version, protect_pip_from_modification_on_windows, + write_output, ) from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory -from pipenv.patched.notpip._internal.wheel import WheelBuilder +from pipenv.patched.notpip._internal.utils.virtualenv import ( + running_under_virtualenv, + virtualenv_no_global, +) +from pipenv.patched.notpip._internal.wheel_builder import ( + BinaryAllowedPredicate, + build, + should_build_for_install_command, +) -logger = logging.getLogger(__name__) +logger = getLogger(__name__) + + +def get_check_binary_allowed(format_control: FormatControl) -> BinaryAllowedPredicate: + def check_binary_allowed(req: InstallRequirement) -> bool: + canonical_name = canonicalize_name(req.name or "") + allowed_formats = format_control.get_allowed_formats(canonical_name) + return "binary" in allowed_formats + + return check_binary_allowed class InstallCommand(RequirementCommand): @@ -45,7 +70,6 @@ class InstallCommand(RequirementCommand): pip also supports installing from "requirements files", which provide an easy way to specify a whole environment to be installed. """ - name = 'install' usage = """ %prog [options] [package-index-options] ... @@ -54,20 +78,14 @@ class InstallCommand(RequirementCommand): %prog [options] [-e] ... %prog [options] ...""" - summary = 'Install packages.' + def add_options(self) -> None: + self.cmd_opts.add_option(cmdoptions.requirements()) + self.cmd_opts.add_option(cmdoptions.constraints()) + self.cmd_opts.add_option(cmdoptions.no_deps()) + self.cmd_opts.add_option(cmdoptions.pre()) - def __init__(self, *args, **kw): - super(InstallCommand, self).__init__(*args, **kw) - - cmd_opts = self.cmd_opts - - cmd_opts.add_option(cmdoptions.requirements()) - cmd_opts.add_option(cmdoptions.constraints()) - cmd_opts.add_option(cmdoptions.no_deps()) - cmd_opts.add_option(cmdoptions.pre()) - - cmd_opts.add_option(cmdoptions.editable()) - cmd_opts.add_option( + self.cmd_opts.add_option(cmdoptions.editable()) + self.cmd_opts.add_option( '-t', '--target', dest='target_dir', metavar='dir', @@ -77,12 +95,9 @@ class InstallCommand(RequirementCommand): '. Use --upgrade to replace existing packages in ' 'with new versions.' ) - cmd_opts.add_option(cmdoptions.platform()) - cmd_opts.add_option(cmdoptions.python_version()) - cmd_opts.add_option(cmdoptions.implementation()) - cmd_opts.add_option(cmdoptions.abi()) + cmdoptions.add_target_python_options(self.cmd_opts) - cmd_opts.add_option( + self.cmd_opts.add_option( '--user', dest='use_user_site', action='store_true', @@ -90,19 +105,19 @@ class InstallCommand(RequirementCommand): "platform. Typically ~/.local/, or %APPDATA%\\Python on " "Windows. (See the Python documentation for site.USER_BASE " "for full details.)") - cmd_opts.add_option( + self.cmd_opts.add_option( '--no-user', dest='use_user_site', action='store_false', help=SUPPRESS_HELP) - cmd_opts.add_option( + self.cmd_opts.add_option( '--root', dest='root_path', metavar='dir', default=None, help="Install everything relative to this alternate root " "directory.") - cmd_opts.add_option( + self.cmd_opts.add_option( '--prefix', dest='prefix_path', metavar='dir', @@ -110,11 +125,11 @@ class InstallCommand(RequirementCommand): help="Installation prefix where lib, bin and other top-level " "folders are placed") - cmd_opts.add_option(cmdoptions.build_dir()) + self.cmd_opts.add_option(cmdoptions.build_dir()) - cmd_opts.add_option(cmdoptions.src()) + self.cmd_opts.add_option(cmdoptions.src()) - cmd_opts.add_option( + self.cmd_opts.add_option( '-U', '--upgrade', dest='upgrade', action='store_true', @@ -123,7 +138,7 @@ class InstallCommand(RequirementCommand): 'upgrade-strategy used.' ) - cmd_opts.add_option( + self.cmd_opts.add_option( '--upgrade-strategy', dest='upgrade_strategy', default='only-if-needed', @@ -137,28 +152,32 @@ class InstallCommand(RequirementCommand): 'satisfy the requirements of the upgraded package(s).' ) - cmd_opts.add_option( + self.cmd_opts.add_option( '--force-reinstall', dest='force_reinstall', action='store_true', help='Reinstall all packages even if they are already ' 'up-to-date.') - cmd_opts.add_option( + self.cmd_opts.add_option( '-I', '--ignore-installed', dest='ignore_installed', action='store_true', - help='Ignore the installed packages (reinstalling instead).') + help='Ignore the installed packages, overwriting them. ' + 'This can break your system if the existing package ' + 'is of a different version or was installed ' + 'with a different package manager!' + ) - cmd_opts.add_option(cmdoptions.ignore_requires_python()) - cmd_opts.add_option(cmdoptions.no_build_isolation()) - cmd_opts.add_option(cmdoptions.use_pep517()) - cmd_opts.add_option(cmdoptions.no_use_pep517()) + self.cmd_opts.add_option(cmdoptions.ignore_requires_python()) + self.cmd_opts.add_option(cmdoptions.no_build_isolation()) + self.cmd_opts.add_option(cmdoptions.use_pep517()) + self.cmd_opts.add_option(cmdoptions.no_use_pep517()) - cmd_opts.add_option(cmdoptions.install_options()) - cmd_opts.add_option(cmdoptions.global_options()) + self.cmd_opts.add_option(cmdoptions.install_options()) + self.cmd_opts.add_option(cmdoptions.global_options()) - cmd_opts.add_option( + self.cmd_opts.add_option( "--compile", action="store_true", dest="compile", @@ -166,21 +185,21 @@ class InstallCommand(RequirementCommand): help="Compile Python source files to bytecode", ) - cmd_opts.add_option( + self.cmd_opts.add_option( "--no-compile", action="store_false", dest="compile", help="Do not compile Python source files to bytecode", ) - cmd_opts.add_option( + self.cmd_opts.add_option( "--no-warn-script-location", action="store_false", dest="warn_script_location", default=True, help="Do not warn when installing scripts outside PATH", ) - cmd_opts.add_option( + self.cmd_opts.add_option( "--no-warn-conflicts", action="store_false", dest="warn_about_conflicts", @@ -188,12 +207,11 @@ class InstallCommand(RequirementCommand): help="Do not warn about broken dependencies", ) - cmd_opts.add_option(cmdoptions.no_binary()) - cmd_opts.add_option(cmdoptions.only_binary()) - cmd_opts.add_option(cmdoptions.prefer_binary()) - cmd_opts.add_option(cmdoptions.no_clean()) - cmd_opts.add_option(cmdoptions.require_hashes()) - cmd_opts.add_option(cmdoptions.progress_bar()) + self.cmd_opts.add_option(cmdoptions.no_binary()) + self.cmd_opts.add_option(cmdoptions.only_binary()) + self.cmd_opts.add_option(cmdoptions.prefer_binary()) + self.cmd_opts.add_option(cmdoptions.require_hashes()) + self.cmd_opts.add_option(cmdoptions.progress_bar()) index_opts = cmdoptions.make_option_group( cmdoptions.index_group, @@ -201,41 +219,33 @@ class InstallCommand(RequirementCommand): ) self.parser.insert_option_group(0, index_opts) - self.parser.insert_option_group(0, cmd_opts) + self.parser.insert_option_group(0, self.cmd_opts) + + @with_cleanup + def run(self, options: Values, args: List[str]) -> int: + if options.use_user_site and options.target_dir is not None: + raise CommandError("Can not combine '--user' and '--target'") - def run(self, options, args): cmdoptions.check_install_build_global(options) upgrade_strategy = "to-satisfy-only" if options.upgrade: upgrade_strategy = options.upgrade_strategy - if options.build_dir: - options.build_dir = os.path.abspath(options.build_dir) - cmdoptions.check_dist_restriction(options, check_target=True) - if options.python_version: - python_versions = [options.python_version] - else: - python_versions = None - - options.src_dir = os.path.abspath(options.src_dir) install_options = options.install_options or [] - if options.use_user_site: - if options.prefix_path: - raise CommandError( - "Can not combine '--user' and '--prefix' as they imply " - "different installation locations" - ) - if virtualenv_no_global(): - raise InstallationError( - "Can not perform a '--user' install. User site-packages " - "are not visible in this virtualenv." - ) - install_options.append('--user') - install_options.append('--prefix=') - target_temp_dir = TempDirectory(kind="target") + logger.verbose("Using %s", get_pip_version()) + options.use_user_site = decide_user_install( + options.use_user_site, + prefix_path=options.prefix_path, + target_dir=options.target_dir, + root_path=options.root_path, + isolated_mode=options.isolated_mode, + ) + + target_temp_dir: Optional[TempDirectory] = None + target_temp_dir_path: Optional[str] = None if options.target_dir: options.ignore_installed = True options.target_dir = os.path.abspath(options.target_dir) @@ -247,298 +257,460 @@ class InstallCommand(RequirementCommand): ) # Create a target directory for using with the target option - target_temp_dir.create() - install_options.append('--home=' + target_temp_dir.path) + target_temp_dir = TempDirectory(kind="target") + target_temp_dir_path = target_temp_dir.path + self.enter_context(target_temp_dir) global_options = options.global_options or [] - with self._build_session(options) as session: - finder = self._build_package_finder( - options=options, - session=session, - platform=options.platform, - python_versions=python_versions, - abi=options.abi, - implementation=options.implementation, + session = self.get_default_session(options) + + target_python = make_target_python(options) + finder = self._build_package_finder( + options=options, + session=session, + target_python=target_python, + ignore_requires_python=options.ignore_requires_python, + ) + wheel_cache = WheelCache(options.cache_dir, options.format_control) + + req_tracker = self.enter_context(get_requirement_tracker()) + + directory = TempDirectory( + delete=not options.no_clean, + kind="install", + globally_managed=True, + ) + + try: + reqs = self.get_requirements(args, options, finder, session) + + reject_location_related_install_options( + reqs, options.install_options ) - build_delete = (not (options.no_clean or options.build_dir)) - wheel_cache = WheelCache(options.cache_dir, options.format_control) - if options.cache_dir and not check_path_owner(options.cache_dir): - logger.warning( - "The directory '%s' or its parent directory is not owned " - "by the current user and caching wheels has been " - "disabled. check the permissions and owner of that " - "directory. If executing pip with sudo, you may want " - "sudo's -H flag.", - options.cache_dir, + preparer = self.make_requirement_preparer( + temp_build_dir=directory, + options=options, + req_tracker=req_tracker, + session=session, + finder=finder, + use_user_site=options.use_user_site, + ) + resolver = self.make_resolver( + preparer=preparer, + finder=finder, + options=options, + wheel_cache=wheel_cache, + use_user_site=options.use_user_site, + ignore_installed=options.ignore_installed, + ignore_requires_python=options.ignore_requires_python, + force_reinstall=options.force_reinstall, + upgrade_strategy=upgrade_strategy, + use_pep517=options.use_pep517, + ) + + self.trace_basic_info(finder) + + requirement_set = resolver.resolve( + reqs, check_supported_wheels=not options.target_dir + ) + + try: + pip_req = requirement_set.get_requirement("pip") + except KeyError: + modifying_pip = False + else: + # If we're not replacing an already installed pip, + # we're not modifying it. + modifying_pip = pip_req.satisfied_by is None + protect_pip_from_modification_on_windows( + modifying_pip=modifying_pip + ) + + check_binary_allowed = get_check_binary_allowed( + finder.format_control + ) + + reqs_to_build = [ + r for r in requirement_set.requirements.values() + if should_build_for_install_command( + r, check_binary_allowed ) - options.cache_dir = None + ] - with RequirementTracker() as req_tracker, TempDirectory( - options.build_dir, delete=build_delete, kind="install" - ) as directory: - requirement_set = RequirementSet( - require_hashes=options.require_hashes, - check_supported_wheels=not options.target_dir, + _, build_failures = build( + reqs_to_build, + wheel_cache=wheel_cache, + verify=True, + build_options=[], + global_options=[], + ) + + # If we're using PEP 517, we cannot do a direct install + # so we fail here. + pep517_build_failure_names: List[str] = [ + r.name # type: ignore + for r in build_failures if r.use_pep517 + ] + if pep517_build_failure_names: + raise InstallationError( + "Could not build wheels for {} which use" + " PEP 517 and cannot be installed directly".format( + ", ".join(pep517_build_failure_names) + ) ) + # For now, we just warn about failures building legacy + # requirements, as we'll fall through to a direct + # install for those. + for r in build_failures: + if not r.use_pep517: + r.legacy_install_reason = 8368 + + to_install = resolver.get_installation_order( + requirement_set + ) + + # Check for conflicts in the package set we're installing. + conflicts: Optional[ConflictDetails] = None + should_warn_about_conflicts = ( + not options.ignore_dependencies and + options.warn_about_conflicts + ) + if should_warn_about_conflicts: + conflicts = self._determine_conflicts(to_install) + + # Don't warn about script install locations if + # --target or --prefix has been specified + warn_script_location = options.warn_script_location + if options.target_dir or options.prefix_path: + warn_script_location = False + + installed = install_given_reqs( + to_install, + install_options, + global_options, + root=options.root_path, + home=target_temp_dir_path, + prefix=options.prefix_path, + warn_script_location=warn_script_location, + use_user_site=options.use_user_site, + pycompile=options.compile, + ) + + lib_locations = get_lib_location_guesses( + user=options.use_user_site, + home=target_temp_dir_path, + root=options.root_path, + prefix=options.prefix_path, + isolated=options.isolated_mode, + ) + env = get_environment(lib_locations) + + installed.sort(key=operator.attrgetter('name')) + items = [] + for result in installed: + item = result.name try: - self.populate_requirement_set( - requirement_set, args, options, finder, session, - self.name, wheel_cache - ) - preparer = RequirementPreparer( - build_dir=directory.path, - src_dir=options.src_dir, - download_dir=None, - wheel_download_dir=None, - progress_bar=options.progress_bar, - build_isolation=options.build_isolation, - req_tracker=req_tracker, - ) + installed_dist = env.get_distribution(item) + if installed_dist is not None: + item = f"{item}-{installed_dist.version}" + except Exception: + pass + items.append(item) - resolver = Resolver( - preparer=preparer, - finder=finder, - session=session, - wheel_cache=wheel_cache, - use_user_site=options.use_user_site, - upgrade_strategy=upgrade_strategy, - force_reinstall=options.force_reinstall, - ignore_dependencies=options.ignore_dependencies, - ignore_requires_python=options.ignore_requires_python, - ignore_installed=options.ignore_installed, - isolated=options.isolated_mode, - use_pep517=options.use_pep517 - ) - resolver.resolve(requirement_set) + if conflicts is not None: + self._warn_about_conflicts( + conflicts, + resolver_variant=self.determine_resolver_variant(options), + ) - protect_pip_from_modification_on_windows( - modifying_pip=requirement_set.has_requirement("pip") - ) + installed_desc = ' '.join(items) + if installed_desc: + write_output( + 'Successfully installed %s', installed_desc, + ) + except OSError as error: + show_traceback = (self.verbosity >= 1) - # Consider legacy and PEP517-using requirements separately - legacy_requirements = [] - pep517_requirements = [] - for req in requirement_set.requirements.values(): - if req.use_pep517: - pep517_requirements.append(req) - else: - legacy_requirements.append(req) + message = create_os_error_message( + error, show_traceback, options.use_user_site, + ) + logger.error(message, exc_info=show_traceback) # noqa - # We don't build wheels for legacy requirements if we - # don't have wheel installed or we don't have a cache dir - try: - import wheel # noqa: F401 - build_legacy = bool(options.cache_dir) - except ImportError: - build_legacy = False - - wb = WheelBuilder( - finder, preparer, wheel_cache, - build_options=[], global_options=[], - ) - - # Always build PEP 517 requirements - build_failures = wb.build( - pep517_requirements, - session=session, autobuilding=True - ) - - if build_legacy: - # We don't care about failures building legacy - # requirements, as we'll fall through to a direct - # install for those. - wb.build( - legacy_requirements, - session=session, autobuilding=True - ) - - # If we're using PEP 517, we cannot do a direct install - # so we fail here. - if build_failures: - raise InstallationError( - "Could not build wheels for {} which use" - " PEP 517 and cannot be installed directly".format( - ", ".join(r.name for r in build_failures))) - - to_install = resolver.get_installation_order( - requirement_set - ) - - # Consistency Checking of the package set we're installing. - should_warn_about_conflicts = ( - not options.ignore_dependencies and - options.warn_about_conflicts - ) - if should_warn_about_conflicts: - self._warn_about_conflicts(to_install) - - # Don't warn about script install locations if - # --target has been specified - warn_script_location = options.warn_script_location - if options.target_dir: - warn_script_location = False - - installed = install_given_reqs( - to_install, - install_options, - global_options, - root=options.root_path, - home=target_temp_dir.path, - prefix=options.prefix_path, - pycompile=options.compile, - warn_script_location=warn_script_location, - use_user_site=options.use_user_site, - ) - - lib_locations = get_lib_location_guesses( - user=options.use_user_site, - home=target_temp_dir.path, - root=options.root_path, - prefix=options.prefix_path, - isolated=options.isolated_mode, - ) - working_set = pkg_resources.WorkingSet(lib_locations) - - reqs = sorted(installed, key=operator.attrgetter('name')) - items = [] - for req in reqs: - item = req.name - try: - installed_version = get_installed_version( - req.name, working_set=working_set - ) - if installed_version: - item += '-' + installed_version - except Exception: - pass - items.append(item) - installed = ' '.join(items) - if installed: - logger.info('Successfully installed %s', installed) - except EnvironmentError as error: - show_traceback = (self.verbosity >= 1) - - message = create_env_error_message( - error, show_traceback, options.use_user_site, - ) - logger.error(message, exc_info=show_traceback) - - return ERROR - except PreviousBuildDirError: - options.no_clean = True - raise - finally: - # Clean up - if not options.no_clean: - requirement_set.cleanup_files() - wheel_cache.cleanup() + return ERROR if options.target_dir: + assert target_temp_dir self._handle_target_dir( options.target_dir, target_temp_dir, options.upgrade ) - return requirement_set - def _handle_target_dir(self, target_dir, target_temp_dir, upgrade): + warn_if_run_as_root() + return SUCCESS + + def _handle_target_dir( + self, target_dir: str, target_temp_dir: TempDirectory, upgrade: bool + ) -> None: ensure_dir(target_dir) # Checking both purelib and platlib directories for installed # packages to be moved to target directory lib_dir_list = [] - with target_temp_dir: - # Checking both purelib and platlib directories for installed - # packages to be moved to target directory - scheme = distutils_scheme('', home=target_temp_dir.path) - purelib_dir = scheme['purelib'] - platlib_dir = scheme['platlib'] - data_dir = scheme['data'] + # Checking both purelib and platlib directories for installed + # packages to be moved to target directory + scheme = get_scheme('', home=target_temp_dir.path) + purelib_dir = scheme.purelib + platlib_dir = scheme.platlib + data_dir = scheme.data - if os.path.exists(purelib_dir): - lib_dir_list.append(purelib_dir) - if os.path.exists(platlib_dir) and platlib_dir != purelib_dir: - lib_dir_list.append(platlib_dir) - if os.path.exists(data_dir): - lib_dir_list.append(data_dir) + if os.path.exists(purelib_dir): + lib_dir_list.append(purelib_dir) + if os.path.exists(platlib_dir) and platlib_dir != purelib_dir: + lib_dir_list.append(platlib_dir) + if os.path.exists(data_dir): + lib_dir_list.append(data_dir) - for lib_dir in lib_dir_list: - for item in os.listdir(lib_dir): - if lib_dir == data_dir: - ddir = os.path.join(data_dir, item) - if any(s.startswith(ddir) for s in lib_dir_list[:-1]): - continue - target_item_dir = os.path.join(target_dir, item) - if os.path.exists(target_item_dir): - if not upgrade: - logger.warning( - 'Target directory %s already exists. Specify ' - '--upgrade to force replacement.', - target_item_dir - ) - continue - if os.path.islink(target_item_dir): - logger.warning( - 'Target directory %s already exists and is ' - 'a link. Pip will not automatically replace ' - 'links, please remove if replacement is ' - 'desired.', - target_item_dir - ) - continue - if os.path.isdir(target_item_dir): - shutil.rmtree(target_item_dir) - else: - os.remove(target_item_dir) + for lib_dir in lib_dir_list: + for item in os.listdir(lib_dir): + if lib_dir == data_dir: + ddir = os.path.join(data_dir, item) + if any(s.startswith(ddir) for s in lib_dir_list[:-1]): + continue + target_item_dir = os.path.join(target_dir, item) + if os.path.exists(target_item_dir): + if not upgrade: + logger.warning( + 'Target directory %s already exists. Specify ' + '--upgrade to force replacement.', + target_item_dir + ) + continue + if os.path.islink(target_item_dir): + logger.warning( + 'Target directory %s already exists and is ' + 'a link. pip will not automatically replace ' + 'links, please remove if replacement is ' + 'desired.', + target_item_dir + ) + continue + if os.path.isdir(target_item_dir): + shutil.rmtree(target_item_dir) + else: + os.remove(target_item_dir) - shutil.move( - os.path.join(lib_dir, item), - target_item_dir - ) + shutil.move( + os.path.join(lib_dir, item), + target_item_dir + ) - def _warn_about_conflicts(self, to_install): + def _determine_conflicts( + self, to_install: List[InstallRequirement] + ) -> Optional[ConflictDetails]: try: - package_set, _dep_info = check_install_conflicts(to_install) + return check_install_conflicts(to_install) except Exception: - logger.error("Error checking for conflicts.", exc_info=True) - return - missing, conflicting = _dep_info + logger.exception( + "Error while checking for conflicts. Please file an issue on " + "pip's issue tracker: https://github.com/pypa/pip/issues/new" + ) + return None - # NOTE: There is some duplication here from pipenv.patched.notpip check + def _warn_about_conflicts( + self, conflict_details: ConflictDetails, resolver_variant: str + ) -> None: + package_set, (missing, conflicting) = conflict_details + if not missing and not conflicting: + return + + parts: List[str] = [] + if resolver_variant == "legacy": + parts.append( + "pip's legacy dependency resolver does not consider dependency " + "conflicts when selecting packages. This behaviour is the " + "source of the following dependency conflicts." + ) + else: + assert resolver_variant == "2020-resolver" + parts.append( + "pip's dependency resolver does not currently take into account " + "all the packages that are installed. This behaviour is the " + "source of the following dependency conflicts." + ) + + # NOTE: There is some duplication here, with commands/check.py for project_name in missing: version = package_set[project_name][0] for dependency in missing[project_name]: - logger.critical( - "%s %s requires %s, which is not installed.", - project_name, version, dependency[1], + message = ( + "{name} {version} requires {requirement}, " + "which is not installed." + ).format( + name=project_name, + version=version, + requirement=dependency[1], ) + parts.append(message) for project_name in conflicting: version = package_set[project_name][0] for dep_name, dep_version, req in conflicting[project_name]: - logger.critical( - "%s %s has requirement %s, but you'll have %s %s which is " - "incompatible.", - project_name, version, req, dep_name, dep_version, + message = ( + "{name} {version} requires {requirement}, but {you} have " + "{dep_name} {dep_version} which is incompatible." + ).format( + name=project_name, + version=version, + requirement=req, + dep_name=dep_name, + dep_version=dep_version, + you=("you" if resolver_variant == "2020-resolver" else "you'll") ) + parts.append(message) + + logger.critical("\n".join(parts)) -def get_lib_location_guesses(*args, **kwargs): - scheme = distutils_scheme('', *args, **kwargs) - return [scheme['purelib'], scheme['platlib']] +def get_lib_location_guesses( + user: bool = False, + home: Optional[str] = None, + root: Optional[str] = None, + isolated: bool = False, + prefix: Optional[str] = None +) -> List[str]: + scheme = get_scheme( + '', + user=user, + home=home, + root=root, + isolated=isolated, + prefix=prefix, + ) + return [scheme.purelib, scheme.platlib] -def create_env_error_message(error, show_traceback, using_user_site): - """Format an error message for an EnvironmentError +def site_packages_writable(root: Optional[str], isolated: bool) -> bool: + return all( + test_writable_dir(d) for d in set( + get_lib_location_guesses(root=root, isolated=isolated)) + ) + + +def decide_user_install( + use_user_site: Optional[bool], + prefix_path: Optional[str] = None, + target_dir: Optional[str] = None, + root_path: Optional[str] = None, + isolated_mode: bool = False, +) -> bool: + """Determine whether to do a user install based on the input options. + + If use_user_site is False, no additional checks are done. + If use_user_site is True, it is checked for compatibility with other + options. + If use_user_site is None, the default behaviour depends on the environment, + which is provided by the other arguments. + """ + # In some cases (config from tox), use_user_site can be set to an integer + # rather than a bool, which 'use_user_site is False' wouldn't catch. + if (use_user_site is not None) and (not use_user_site): + logger.debug("Non-user install by explicit request") + return False + + if use_user_site: + if prefix_path: + raise CommandError( + "Can not combine '--user' and '--prefix' as they imply " + "different installation locations" + ) + if virtualenv_no_global(): + raise InstallationError( + "Can not perform a '--user' install. User site-packages " + "are not visible in this virtualenv." + ) + logger.debug("User install by explicit request") + return True + + # If we are here, user installs have not been explicitly requested/avoided + assert use_user_site is None + + # user install incompatible with --prefix/--target + if prefix_path or target_dir: + logger.debug("Non-user install due to --prefix or --target option") + return False + + # If user installs are not enabled, choose a non-user install + if not site.ENABLE_USER_SITE: + logger.debug("Non-user install because user site-packages disabled") + return False + + # If we have permission for a non-user install, do that, + # otherwise do a user install. + if site_packages_writable(root=root_path, isolated=isolated_mode): + logger.debug("Non-user install because site-packages writeable") + return False + + logger.info("Defaulting to user installation because normal site-packages " + "is not writeable") + return True + + +def reject_location_related_install_options( + requirements: List[InstallRequirement], options: Optional[List[str]] +) -> None: + """If any location-changing --install-option arguments were passed for + requirements or on the command-line, then show a deprecation warning. + """ + def format_options(option_names: Iterable[str]) -> List[str]: + return ["--{}".format(name.replace("_", "-")) for name in option_names] + + offenders = [] + + for requirement in requirements: + install_options = requirement.install_options + location_options = parse_distutils_args(install_options) + if location_options: + offenders.append( + "{!r} from {}".format( + format_options(location_options.keys()), requirement + ) + ) + + if options: + location_options = parse_distutils_args(options) + if location_options: + offenders.append( + "{!r} from command line".format( + format_options(location_options.keys()) + ) + ) + + if not offenders: + return + + raise CommandError( + "Location-changing options found in --install-option: {}." + " This is unsupported, use pip-level options like --user," + " --prefix, --root, and --target instead.".format( + "; ".join(offenders) + ) + ) + + +def create_os_error_message( + error: OSError, show_traceback: bool, using_user_site: bool +) -> str: + """Format an error message for an OSError It may occur anytime during the execution of the install command. """ parts = [] # Mention the error if we are not going to show a traceback - parts.append("Could not install packages due to an EnvironmentError") + parts.append("Could not install packages due to an OSError") if not show_traceback: parts.append(": ") parts.append(str(error)) @@ -554,7 +726,7 @@ def create_env_error_message(error, show_traceback, using_user_site): user_option_part = "Consider using the `--user` option" permissions_part = "Check the permissions" - if not using_user_site: + if not running_under_virtualenv() and not using_user_site: parts.extend([ user_option_part, " or ", permissions_part.lower(), @@ -563,4 +735,16 @@ def create_env_error_message(error, show_traceback, using_user_site): parts.append(permissions_part) parts.append(".\n") + # Suggest the user to enable Long Paths if path length is + # more than 260 + if (WINDOWS and error.errno == errno.ENOENT and error.filename and + len(error.filename) > 260): + parts.append( + "HINT: This error might have occurred since " + "this system does not have Windows Long Path " + "support enabled. You can find information on " + "how to enable this at " + "https://pip.pypa.io/warnings/enable-long-paths\n" + ) + return "".join(parts).strip() + "\n" diff --git a/pipenv/patched/notpip/_internal/commands/list.py b/pipenv/patched/notpip/_internal/commands/list.py index a2bd5be1..61d5ad6d 100644 --- a/pipenv/patched/notpip/_internal/commands/list.py +++ b/pipenv/patched/notpip/_internal/commands/list.py @@ -1,55 +1,68 @@ -from __future__ import absolute_import - import json import logging +from optparse import Values +from typing import TYPE_CHECKING, Iterator, List, Optional, Sequence, Tuple, cast -from pipenv.patched.notpip._vendor import six -from pipenv.patched.notpip._vendor.six.moves import zip_longest +from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name from pipenv.patched.notpip._internal.cli import cmdoptions -from pipenv.patched.notpip._internal.cli.base_command import Command +from pipenv.patched.notpip._internal.cli.req_command import IndexGroupCommand +from pipenv.patched.notpip._internal.cli.status_codes import SUCCESS from pipenv.patched.notpip._internal.exceptions import CommandError -from pipenv.patched.notpip._internal.index import PackageFinder -from pipenv.patched.notpip._internal.utils.misc import ( - dist_is_editable, get_installed_distributions, -) -from pipenv.patched.notpip._internal.utils.packaging import get_installer +from pipenv.patched.notpip._internal.index.collector import LinkCollector +from pipenv.patched.notpip._internal.index.package_finder import PackageFinder +from pipenv.patched.notpip._internal.metadata import BaseDistribution, get_environment +from pipenv.patched.notpip._internal.models.selection_prefs import SelectionPreferences +from pipenv.patched.notpip._internal.network.session import PipSession +from pipenv.patched.notpip._internal.utils.misc import stdlib_pkgs, tabulate, write_output +from pipenv.patched.notpip._internal.utils.parallel import map_multithread + +if TYPE_CHECKING: + from pipenv.patched.notpip._internal.metadata.base import DistributionVersion + + class _DistWithLatestInfo(BaseDistribution): + """Give the distribution object a couple of extra fields. + + These will be populated during ``get_outdated()``. This is dirty but + makes the rest of the code much cleaner. + """ + latest_version: DistributionVersion + latest_filetype: str + + _ProcessedDists = Sequence[_DistWithLatestInfo] + logger = logging.getLogger(__name__) -class ListCommand(Command): +class ListCommand(IndexGroupCommand): """ List installed packages, including editables. Packages are listed in a case-insensitive sorted order. """ - name = 'list' + + ignore_require_venv = True usage = """ %prog [options]""" - summary = 'List installed packages.' - def __init__(self, *args, **kw): - super(ListCommand, self).__init__(*args, **kw) - - cmd_opts = self.cmd_opts - - cmd_opts.add_option( + def add_options(self) -> None: + self.cmd_opts.add_option( '-o', '--outdated', action='store_true', default=False, help='List outdated packages') - cmd_opts.add_option( + self.cmd_opts.add_option( '-u', '--uptodate', action='store_true', default=False, help='List uptodate packages') - cmd_opts.add_option( + self.cmd_opts.add_option( '-e', '--editable', action='store_true', default=False, help='List editable projects.') - cmd_opts.add_option( + self.cmd_opts.add_option( '-l', '--local', action='store_true', default=False, @@ -62,8 +75,8 @@ class ListCommand(Command): action='store_true', default=False, help='Only output packages installed in user-site.') - - cmd_opts.add_option( + self.cmd_opts.add_option(cmdoptions.list_path()) + self.cmd_opts.add_option( '--pre', action='store_true', default=False, @@ -71,7 +84,7 @@ class ListCommand(Command): "pip only finds stable versions."), ) - cmd_opts.add_option( + self.cmd_opts.add_option( '--format', action='store', dest='list_format', @@ -81,7 +94,7 @@ class ListCommand(Command): "or json", ) - cmd_opts.add_option( + self.cmd_opts.add_option( '--not-required', action='store_true', dest='not_required', @@ -89,49 +102,67 @@ class ListCommand(Command): "installed packages.", ) - cmd_opts.add_option( + self.cmd_opts.add_option( '--exclude-editable', action='store_false', dest='include_editable', help='Exclude editable package from output.', ) - cmd_opts.add_option( + self.cmd_opts.add_option( '--include-editable', action='store_true', dest='include_editable', help='Include editable package from output.', default=True, ) + self.cmd_opts.add_option(cmdoptions.list_exclude()) index_opts = cmdoptions.make_option_group( cmdoptions.index_group, self.parser ) self.parser.insert_option_group(0, index_opts) - self.parser.insert_option_group(0, cmd_opts) + self.parser.insert_option_group(0, self.cmd_opts) - def _build_package_finder(self, options, index_urls, session): + def _build_package_finder( + self, options: Values, session: PipSession + ) -> PackageFinder: """ Create a package finder appropriate to this list command. """ - return PackageFinder( - find_links=options.find_links, - index_urls=index_urls, + link_collector = LinkCollector.create(session, options=options) + + # Pass allow_yanked=False to ignore yanked versions. + selection_prefs = SelectionPreferences( + allow_yanked=False, allow_all_prereleases=options.pre, - trusted_hosts=options.trusted_hosts, - session=session, ) - def run(self, options, args): + return PackageFinder.create( + link_collector=link_collector, + selection_prefs=selection_prefs, + ) + + def run(self, options: Values, args: List[str]) -> int: if options.outdated and options.uptodate: raise CommandError( "Options --outdated and --uptodate cannot be combined.") - packages = get_installed_distributions( - local_only=options.local, - user_only=options.user, - editables_only=options.editable, - include_editables=options.include_editable, - ) + cmdoptions.check_list_path_option(options) + + skip = set(stdlib_pkgs) + if options.excludes: + skip.update(canonicalize_name(n) for n in options.excludes) + + packages: "_ProcessedDists" = [ + cast("_DistWithLatestInfo", d) + for d in get_environment(options.path).iter_installed_distributions( + local_only=options.local, + user_only=options.user, + editables_only=options.editable, + include_editables=options.include_editable, + skip=skip, + ) + ] # get_not_required must be called firstly in order to find and # filter out all dependencies correctly. Otherwise a package @@ -146,60 +177,79 @@ class ListCommand(Command): packages = self.get_uptodate(packages, options) self.output_package_listing(packages, options) + return SUCCESS - def get_outdated(self, packages, options): + def get_outdated( + self, packages: "_ProcessedDists", options: Values + ) -> "_ProcessedDists": return [ dist for dist in self.iter_packages_latest_infos(packages, options) - if dist.latest_version > dist.parsed_version + if dist.latest_version > dist.version ] - def get_uptodate(self, packages, options): + def get_uptodate( + self, packages: "_ProcessedDists", options: Values + ) -> "_ProcessedDists": return [ dist for dist in self.iter_packages_latest_infos(packages, options) - if dist.latest_version == dist.parsed_version + if dist.latest_version == dist.version ] - def get_not_required(self, packages, options): - dep_keys = set() - for dist in packages: - dep_keys.update(requirement.key for requirement in dist.requires()) - return {pkg for pkg in packages if pkg.key not in dep_keys} + def get_not_required( + self, packages: "_ProcessedDists", options: Values + ) -> "_ProcessedDists": + dep_keys = { + canonicalize_name(dep.name) + for dist in packages + for dep in (dist.iter_dependencies() or ()) + } - def iter_packages_latest_infos(self, packages, options): - index_urls = [options.index_url] + options.extra_index_urls - if options.no_index: - logger.debug('Ignoring indexes: %s', ','.join(index_urls)) - index_urls = [] + # Create a set to remove duplicate packages, and cast it to a list + # to keep the return type consistent with get_outdated and + # get_uptodate + return list({pkg for pkg in packages if pkg.canonical_name not in dep_keys}) + def iter_packages_latest_infos( + self, packages: "_ProcessedDists", options: Values + ) -> Iterator["_DistWithLatestInfo"]: with self._build_session(options) as session: - finder = self._build_package_finder(options, index_urls, session) + finder = self._build_package_finder(options, session) - for dist in packages: - typ = 'unknown' - all_candidates = finder.find_all_candidates(dist.key) + def latest_info( + dist: "_DistWithLatestInfo" + ) -> Optional["_DistWithLatestInfo"]: + all_candidates = finder.find_all_candidates(dist.canonical_name) if not options.pre: # Remove prereleases all_candidates = [candidate for candidate in all_candidates if not candidate.version.is_prerelease] - if not all_candidates: - continue - best_candidate = max(all_candidates, - key=finder._candidate_sort_key) + evaluator = finder.make_candidate_evaluator( + project_name=dist.canonical_name, + ) + best_candidate = evaluator.sort_best_candidate(all_candidates) + if best_candidate is None: + return None + remote_version = best_candidate.version - if best_candidate.location.is_wheel: + if best_candidate.link.is_wheel: typ = 'wheel' else: typ = 'sdist' - # This is dirty but makes the rest of the code much cleaner dist.latest_version = remote_version dist.latest_filetype = typ - yield dist + return dist - def output_package_listing(self, packages, options): + for dist in map_multithread(latest_info, packages): + if dist is not None: + yield dist + + def output_package_listing( + self, packages: "_ProcessedDists", options: Values + ) -> None: packages = sorted( packages, - key=lambda dist: dist.project_name.lower(), + key=lambda dist: dist.canonical_name, ) if options.list_format == 'columns' and packages: data, header = format_for_columns(packages, options) @@ -207,14 +257,16 @@ class ListCommand(Command): elif options.list_format == 'freeze': for dist in packages: if options.verbose >= 1: - logger.info("%s==%s (%s)", dist.project_name, - dist.version, dist.location) + write_output("%s==%s (%s)", dist.raw_name, + dist.version, dist.location) else: - logger.info("%s==%s", dist.project_name, dist.version) + write_output("%s==%s", dist.raw_name, dist.version) elif options.list_format == 'json': - logger.info(format_for_json(packages, options)) + write_output(format_for_json(packages, options)) - def output_package_listing_columns(self, data, header): + def output_package_listing_columns( + self, data: List[List[str]], header: List[str] + ) -> None: # insert the header first: we need to know the size of column names if len(data) > 0: data.insert(0, header) @@ -226,28 +278,12 @@ class ListCommand(Command): pkg_strings.insert(1, " ".join(map(lambda x: '-' * x, sizes))) for val in pkg_strings: - logger.info(val) + write_output(val) -def tabulate(vals): - # From pfmoore on GitHub: - # https://github.com/pypa/pip/issues/3651#issuecomment-216932564 - assert len(vals) > 0 - - sizes = [0] * max(len(x) for x in vals) - for row in vals: - sizes = [max(s, len(str(c))) for s, c in zip_longest(sizes, row)] - - result = [] - for row in vals: - display = " ".join([str(c).ljust(s) if c is not None else '' - for s, c in zip_longest(sizes, row)]) - result.append(display) - - return result, sizes - - -def format_for_columns(pkgs, options): +def format_for_columns( + pkgs: "_ProcessedDists", options: Values +) -> Tuple[List[List[str]], List[str]]: """ Convert the package data into something usable by output_package_listing_columns. @@ -260,7 +296,7 @@ def format_for_columns(pkgs, options): header = ["Package", "Version"] data = [] - if options.verbose >= 1 or any(dist_is_editable(x) for x in pkgs): + if options.verbose >= 1 or any(x.editable for x in pkgs): header.append("Location") if options.verbose >= 1: header.append("Installer") @@ -268,34 +304,34 @@ def format_for_columns(pkgs, options): for proj in pkgs: # if we're working on the 'outdated' list, separate out the # latest_version and type - row = [proj.project_name, proj.version] + row = [proj.raw_name, str(proj.version)] if running_outdated: - row.append(proj.latest_version) + row.append(str(proj.latest_version)) row.append(proj.latest_filetype) - if options.verbose >= 1 or dist_is_editable(proj): - row.append(proj.location) + if options.verbose >= 1 or proj.editable: + row.append(proj.location or "") if options.verbose >= 1: - row.append(get_installer(proj)) + row.append(proj.installer) data.append(row) return data, header -def format_for_json(packages, options): +def format_for_json(packages: "_ProcessedDists", options: Values) -> str: data = [] for dist in packages: info = { - 'name': dist.project_name, - 'version': six.text_type(dist.version), + 'name': dist.raw_name, + 'version': str(dist.version), } if options.verbose >= 1: - info['location'] = dist.location - info['installer'] = get_installer(dist) + info['location'] = dist.location or "" + info['installer'] = dist.installer if options.outdated: - info['latest_version'] = six.text_type(dist.latest_version) + info['latest_version'] = str(dist.latest_version) info['latest_filetype'] = dist.latest_filetype data.append(info) return json.dumps(data) diff --git a/pipenv/patched/notpip/_internal/commands/search.py b/pipenv/patched/notpip/_internal/commands/search.py index 986208f9..29a0c57e 100644 --- a/pipenv/patched/notpip/_internal/commands/search.py +++ b/pipenv/patched/notpip/_internal/commands/search.py @@ -1,37 +1,43 @@ -from __future__ import absolute_import - import logging +import shutil import sys import textwrap +import xmlrpc.client from collections import OrderedDict +from optparse import Values +from typing import TYPE_CHECKING, Dict, List, Optional -from pipenv.patched.notpip._vendor import pkg_resources from pipenv.patched.notpip._vendor.packaging.version import parse as parse_version -# NOTE: XMLRPC Client is not annotated in typeshed as on 2017-07-17, which is -# why we ignore the type on this import -from pipenv.patched.notpip._vendor.six.moves import xmlrpc_client # type: ignore from pipenv.patched.notpip._internal.cli.base_command import Command +from pipenv.patched.notpip._internal.cli.req_command import SessionCommandMixin from pipenv.patched.notpip._internal.cli.status_codes import NO_MATCHES_FOUND, SUCCESS -from pipenv.patched.notpip._internal.download import PipXmlrpcTransport from pipenv.patched.notpip._internal.exceptions import CommandError +from pipenv.patched.notpip._internal.metadata import get_default_environment from pipenv.patched.notpip._internal.models.index import PyPI -from pipenv.patched.notpip._internal.utils.compat import get_terminal_size +from pipenv.patched.notpip._internal.network.xmlrpc import PipXmlrpcTransport from pipenv.patched.notpip._internal.utils.logging import indent_log +from pipenv.patched.notpip._internal.utils.misc import write_output + +if TYPE_CHECKING: + from typing import TypedDict + + class TransformedHit(TypedDict): + name: str + summary: str + versions: List[str] logger = logging.getLogger(__name__) -class SearchCommand(Command): +class SearchCommand(Command, SessionCommandMixin): """Search for PyPI packages whose name or summary contains .""" - name = 'search' + usage = """ %prog [options] """ - summary = 'Search PyPI for packages.' ignore_require_venv = True - def __init__(self, *args, **kw): - super(SearchCommand, self).__init__(*args, **kw) + def add_options(self) -> None: self.cmd_opts.add_option( '-i', '--index', dest='index', @@ -41,7 +47,7 @@ class SearchCommand(Command): self.parser.insert_option_group(0, self.cmd_opts) - def run(self, options, args): + def run(self, options: Values, args: List[str]) -> int: if not args: raise CommandError('Missing required argument (search query).') query = args @@ -50,29 +56,39 @@ class SearchCommand(Command): terminal_width = None if sys.stdout.isatty(): - terminal_width = get_terminal_size()[0] + terminal_width = shutil.get_terminal_size()[0] print_results(hits, terminal_width=terminal_width) if pypi_hits: return SUCCESS return NO_MATCHES_FOUND - def search(self, query, options): + def search(self, query: List[str], options: Values) -> List[Dict[str, str]]: index_url = options.index - with self._build_session(options) as session: - transport = PipXmlrpcTransport(index_url, session) - pypi = xmlrpc_client.ServerProxy(index_url, transport) + + session = self.get_default_session(options) + + transport = PipXmlrpcTransport(index_url, session) + pypi = xmlrpc.client.ServerProxy(index_url, transport) + try: hits = pypi.search({'name': query, 'summary': query}, 'or') - return hits + except xmlrpc.client.Fault as fault: + message = "XMLRPC request failed [code: {code}]\n{string}".format( + code=fault.faultCode, + string=fault.faultString, + ) + raise CommandError(message) + assert isinstance(hits, list) + return hits -def transform_hits(hits): +def transform_hits(hits: List[Dict[str, str]]) -> List["TransformedHit"]: """ The list from pypi is really a list of versions. We want a list of packages with the list of versions stored inline. This converts the list from pypi into one we can use. """ - packages = OrderedDict() + packages: Dict[str, "TransformedHit"] = OrderedDict() for hit in hits: name = hit['name'] summary = hit['summary'] @@ -94,7 +110,27 @@ def transform_hits(hits): return list(packages.values()) -def print_results(hits, name_column_width=None, terminal_width=None): +def print_dist_installation_info(name: str, latest: str) -> None: + env = get_default_environment() + dist = env.get_distribution(name) + if dist is not None: + with indent_log(): + if dist.version == latest: + write_output('INSTALLED: %s (latest)', dist.version) + else: + write_output('INSTALLED: %s', dist.version) + if parse_version(latest).pre: + write_output('LATEST: %s (pre-release; install' + ' with "pip install --pre")', latest) + else: + write_output('LATEST: %s', latest) + + +def print_results( + hits: List["TransformedHit"], + name_column_width: Optional[int] = None, + terminal_width: Optional[int] = None, +) -> None: if not hits: return if name_column_width is None: @@ -103,7 +139,6 @@ def print_results(hits, name_column_width=None, terminal_width=None): for hit in hits ]) + 4 - installed_packages = [p.project_name for p in pkg_resources.working_set] for hit in hits: name = hit['name'] summary = hit['summary'] or '' @@ -112,24 +147,18 @@ def print_results(hits, name_column_width=None, terminal_width=None): target_width = terminal_width - name_column_width - 5 if target_width > 10: # wrap and indent summary to fit terminal - summary = textwrap.wrap(summary, target_width) - summary = ('\n' + ' ' * (name_column_width + 3)).join(summary) + summary_lines = textwrap.wrap(summary, target_width) + summary = ('\n' + ' ' * (name_column_width + 3)).join( + summary_lines) - line = '%-*s - %s' % (name_column_width, - '%s (%s)' % (name, latest), summary) + name_latest = f'{name} ({latest})' + line = f'{name_latest:{name_column_width}} - {summary}' try: - logger.info(line) - if name in installed_packages: - dist = pkg_resources.get_distribution(name) - with indent_log(): - if dist.version == latest: - logger.info('INSTALLED: %s (latest)', dist.version) - else: - logger.info('INSTALLED: %s', dist.version) - logger.info('LATEST: %s', latest) + write_output(line) + print_dist_installation_info(name, latest) except UnicodeEncodeError: pass -def highest_version(versions): +def highest_version(versions: List[str]) -> str: return max(versions, key=parse_version) diff --git a/pipenv/patched/notpip/_internal/commands/show.py b/pipenv/patched/notpip/_internal/commands/show.py index 3fd24482..e80677ba 100644 --- a/pipenv/patched/notpip/_internal/commands/show.py +++ b/pipenv/patched/notpip/_internal/commands/show.py @@ -1,14 +1,15 @@ -from __future__ import absolute_import - +import csv import logging -import os -from email.parser import FeedParser # type: ignore +import pathlib +from optparse import Values +from typing import Iterator, List, NamedTuple, Optional, Tuple -from pipenv.patched.notpip._vendor import pkg_resources from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name from pipenv.patched.notpip._internal.cli.base_command import Command from pipenv.patched.notpip._internal.cli.status_codes import ERROR, SUCCESS +from pipenv.patched.notpip._internal.metadata import BaseDistribution, get_default_environment +from pipenv.patched.notpip._internal.utils.misc import write_output logger = logging.getLogger(__name__) @@ -19,14 +20,12 @@ class ShowCommand(Command): The output is in RFC-compliant mail header format. """ - name = 'show' + usage = """ %prog [options] ...""" - summary = 'Show information about installed packages.' ignore_require_venv = True - def __init__(self, *args, **kw): - super(ShowCommand, self).__init__(*args, **kw) + def add_options(self) -> None: self.cmd_opts.add_option( '-f', '--files', dest='files', @@ -36,7 +35,7 @@ class ShowCommand(Command): self.parser.insert_option_group(0, self.cmd_opts) - def run(self, options, args): + def run(self, options: Values, args: List[str]) -> int: if not args: logger.warning('ERROR: Please provide a package name or names.') return ERROR @@ -49,120 +48,187 @@ class ShowCommand(Command): return SUCCESS -def search_packages_info(query): +class _PackageInfo(NamedTuple): + name: str + version: str + location: str + requires: List[str] + required_by: List[str] + installer: str + metadata_version: str + classifiers: List[str] + summary: str + homepage: str + author: str + author_email: str + license: str + entry_points: List[str] + files: Optional[List[str]] + + +def _covert_legacy_entry(entry: Tuple[str, ...], info: Tuple[str, ...]) -> str: + """Convert a legacy installed-files.txt path into modern RECORD path. + + The legacy format stores paths relative to the info directory, while the + modern format stores paths relative to the package root, e.g. the + site-packages directory. + + :param entry: Path parts of the installed-files.txt entry. + :param info: Path parts of the egg-info directory relative to package root. + :returns: The converted entry. + + For best compatibility with symlinks, this does not use ``abspath()`` or + ``Path.resolve()``, but tries to work with path parts: + + 1. While ``entry`` starts with ``..``, remove the equal amounts of parts + from ``info``; if ``info`` is empty, start appending ``..`` instead. + 2. Join the two directly. + """ + while entry and entry[0] == "..": + if not info or info[-1] == "..": + info += ("..",) + else: + info = info[:-1] + entry = entry[1:] + return str(pathlib.Path(*info, *entry)) + + +def search_packages_info(query: List[str]) -> Iterator[_PackageInfo]: """ Gather details from installed distributions. Print distribution name, version, location, and installed files. Installed files requires a pip generated 'installed-files.txt' in the distributions '.egg-info' directory. """ - installed = {} - for p in pkg_resources.working_set: - installed[canonicalize_name(p.project_name)] = p + env = get_default_environment() + installed = { + dist.canonical_name: dist + for dist in env.iter_distributions() + } query_names = [canonicalize_name(name) for name in query] + missing = sorted( + [name for name, pkg in zip(query, query_names) if pkg not in installed] + ) + if missing: + logger.warning('Package(s) not found: %s', ', '.join(missing)) - for dist in [installed[pkg] for pkg in query_names if pkg in installed]: - package = { - 'name': dist.project_name, - 'version': dist.version, - 'location': dist.location, - 'requires': [dep.project_name for dep in dist.requires()], - } - file_list = None - metadata = None - if isinstance(dist, pkg_resources.DistInfoDistribution): - # RECORDs should be part of .dist-info metadatas - if dist.has_metadata('RECORD'): - lines = dist.get_metadata_lines('RECORD') - paths = [l.split(',')[0] for l in lines] - paths = [os.path.join(dist.location, p) for p in paths] - file_list = [os.path.relpath(p, dist.location) for p in paths] + def _get_requiring_packages(current_dist: BaseDistribution) -> List[str]: + return [ + dist.metadata["Name"] or "UNKNOWN" + for dist in installed.values() + if current_dist.canonical_name in { + canonicalize_name(d.name) for d in dist.iter_dependencies() + } + ] - if dist.has_metadata('METADATA'): - metadata = dist.get_metadata('METADATA') + def _files_from_record(dist: BaseDistribution) -> Optional[Iterator[str]]: + try: + text = dist.read_text('RECORD') + except FileNotFoundError: + return None + # This extra Path-str cast normalizes entries. + return (str(pathlib.Path(row[0])) for row in csv.reader(text.splitlines())) + + def _files_from_legacy(dist: BaseDistribution) -> Optional[Iterator[str]]: + try: + text = dist.read_text('installed-files.txt') + except FileNotFoundError: + return None + paths = (p for p in text.splitlines(keepends=False) if p) + root = dist.location + info = dist.info_directory + if root is None or info is None: + return paths + try: + info_rel = pathlib.Path(info).relative_to(root) + except ValueError: # info is not relative to root. + return paths + if not info_rel.parts: # info *is* root. + return paths + return ( + _covert_legacy_entry(pathlib.Path(p).parts, info_rel.parts) + for p in paths + ) + + for query_name in query_names: + try: + dist = installed[query_name] + except KeyError: + continue + + try: + entry_points_text = dist.read_text('entry_points.txt') + entry_points = entry_points_text.splitlines(keepends=False) + except FileNotFoundError: + entry_points = [] + + files_iter = _files_from_record(dist) or _files_from_legacy(dist) + if files_iter is None: + files: Optional[List[str]] = None else: - # Otherwise use pip's log for .egg-info's - if dist.has_metadata('installed-files.txt'): - paths = dist.get_metadata_lines('installed-files.txt') - paths = [os.path.join(dist.egg_info, p) for p in paths] - file_list = [os.path.relpath(p, dist.location) for p in paths] + files = sorted(files_iter) - if dist.has_metadata('PKG-INFO'): - metadata = dist.get_metadata('PKG-INFO') + metadata = dist.metadata - if dist.has_metadata('entry_points.txt'): - entry_points = dist.get_metadata_lines('entry_points.txt') - package['entry_points'] = entry_points - - if dist.has_metadata('INSTALLER'): - for line in dist.get_metadata_lines('INSTALLER'): - if line.strip(): - package['installer'] = line.strip() - break - - # @todo: Should pkg_resources.Distribution have a - # `get_pkg_info` method? - feed_parser = FeedParser() - feed_parser.feed(metadata) - pkg_info_dict = feed_parser.close() - for key in ('metadata-version', 'summary', - 'home-page', 'author', 'author-email', 'license'): - package[key] = pkg_info_dict.get(key) - - # It looks like FeedParser cannot deal with repeated headers - classifiers = [] - for line in metadata.splitlines(): - if line.startswith('Classifier: '): - classifiers.append(line[len('Classifier: '):]) - package['classifiers'] = classifiers - - if file_list: - package['files'] = sorted(file_list) - yield package + yield _PackageInfo( + name=dist.raw_name, + version=str(dist.version), + location=dist.location or "", + requires=[req.name for req in dist.iter_dependencies()], + required_by=_get_requiring_packages(dist), + installer=dist.installer, + metadata_version=dist.metadata_version or "", + classifiers=metadata.get_all("Classifier", []), + summary=metadata.get("Summary", ""), + homepage=metadata.get("Home-page", ""), + author=metadata.get("Author", ""), + author_email=metadata.get("Author-email", ""), + license=metadata.get("License", ""), + entry_points=entry_points, + files=files, + ) -def print_results(distributions, list_files=False, verbose=False): +def print_results( + distributions: Iterator[_PackageInfo], + list_files: bool, + verbose: bool, +) -> bool: """ - Print the informations from installed distributions found. + Print the information from installed distributions found. """ results_printed = False for i, dist in enumerate(distributions): results_printed = True if i > 0: - logger.info("---") + write_output("---") - name = dist.get('name', '') - required_by = [ - pkg.project_name for pkg in pkg_resources.working_set - if name in [required.name for required in pkg.requires()] - ] - - logger.info("Name: %s", name) - logger.info("Version: %s", dist.get('version', '')) - logger.info("Summary: %s", dist.get('summary', '')) - logger.info("Home-page: %s", dist.get('home-page', '')) - logger.info("Author: %s", dist.get('author', '')) - logger.info("Author-email: %s", dist.get('author-email', '')) - logger.info("License: %s", dist.get('license', '')) - logger.info("Location: %s", dist.get('location', '')) - logger.info("Requires: %s", ', '.join(dist.get('requires', []))) - logger.info("Required-by: %s", ', '.join(required_by)) + write_output("Name: %s", dist.name) + write_output("Version: %s", dist.version) + write_output("Summary: %s", dist.summary) + write_output("Home-page: %s", dist.homepage) + write_output("Author: %s", dist.author) + write_output("Author-email: %s", dist.author_email) + write_output("License: %s", dist.license) + write_output("Location: %s", dist.location) + write_output("Requires: %s", ', '.join(dist.requires)) + write_output("Required-by: %s", ', '.join(dist.required_by)) if verbose: - logger.info("Metadata-Version: %s", - dist.get('metadata-version', '')) - logger.info("Installer: %s", dist.get('installer', '')) - logger.info("Classifiers:") - for classifier in dist.get('classifiers', []): - logger.info(" %s", classifier) - logger.info("Entry-points:") - for entry in dist.get('entry_points', []): - logger.info(" %s", entry.strip()) + write_output("Metadata-Version: %s", dist.metadata_version) + write_output("Installer: %s", dist.installer) + write_output("Classifiers:") + for classifier in dist.classifiers: + write_output(" %s", classifier) + write_output("Entry-points:") + for entry in dist.entry_points: + write_output(" %s", entry.strip()) if list_files: - logger.info("Files:") - for line in dist.get('files', []): - logger.info(" %s", line.strip()) - if "files" not in dist: - logger.info("Cannot locate installed-files.txt") + write_output("Files:") + if dist.files is None: + write_output("Cannot locate RECORD or installed-files.txt") + else: + for line in dist.files: + write_output(" %s", line.strip()) return results_printed diff --git a/pipenv/patched/notpip/_internal/commands/uninstall.py b/pipenv/patched/notpip/_internal/commands/uninstall.py index cf6a511c..ebc380c3 100644 --- a/pipenv/patched/notpip/_internal/commands/uninstall.py +++ b/pipenv/patched/notpip/_internal/commands/uninstall.py @@ -1,15 +1,24 @@ -from __future__ import absolute_import +import logging +from optparse import Values +from typing import List from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name from pipenv.patched.notpip._internal.cli.base_command import Command +from pipenv.patched.notpip._internal.cli.req_command import SessionCommandMixin, warn_if_run_as_root +from pipenv.patched.notpip._internal.cli.status_codes import SUCCESS from pipenv.patched.notpip._internal.exceptions import InstallationError from pipenv.patched.notpip._internal.req import parse_requirements -from pipenv.patched.notpip._internal.req.constructors import install_req_from_line +from pipenv.patched.notpip._internal.req.constructors import ( + install_req_from_line, + install_req_from_parsed_requirement, +) from pipenv.patched.notpip._internal.utils.misc import protect_pip_from_modification_on_windows +logger = logging.getLogger(__name__) -class UninstallCommand(Command): + +class UninstallCommand(Command, SessionCommandMixin): """ Uninstall packages. @@ -19,14 +28,12 @@ class UninstallCommand(Command): leave behind no metadata to determine what files were installed. - Script wrappers installed by ``python setup.py develop``. """ - name = 'uninstall' + usage = """ %prog [options] ... %prog [options] -r ...""" - summary = 'Uninstall packages.' - def __init__(self, *args, **kw): - super(UninstallCommand, self).__init__(*args, **kw) + def add_options(self) -> None: self.cmd_opts.add_option( '-r', '--requirement', dest='requirements', @@ -44,35 +51,50 @@ class UninstallCommand(Command): self.parser.insert_option_group(0, self.cmd_opts) - def run(self, options, args): - with self._build_session(options) as session: - reqs_to_uninstall = {} - for name in args: - req = install_req_from_line( - name, isolated=options.isolated_mode, + def run(self, options: Values, args: List[str]) -> int: + session = self.get_default_session(options) + + reqs_to_uninstall = {} + for name in args: + req = install_req_from_line( + name, isolated=options.isolated_mode, + ) + if req.name: + reqs_to_uninstall[canonicalize_name(req.name)] = req + else: + logger.warning( + "Invalid requirement: %r ignored -" + " the uninstall command expects named" + " requirements.", + name, + ) + for filename in options.requirements: + for parsed_req in parse_requirements( + filename, + options=options, + session=session): + req = install_req_from_parsed_requirement( + parsed_req, + isolated=options.isolated_mode ) if req.name: reqs_to_uninstall[canonicalize_name(req.name)] = req - for filename in options.requirements: - for req in parse_requirements( - filename, - options=options, - session=session): - if req.name: - reqs_to_uninstall[canonicalize_name(req.name)] = req - if not reqs_to_uninstall: - raise InstallationError( - 'You must give at least one requirement to %(name)s (see ' - '"pip help %(name)s")' % dict(name=self.name) - ) - - protect_pip_from_modification_on_windows( - modifying_pip="pip" in reqs_to_uninstall + if not reqs_to_uninstall: + raise InstallationError( + f'You must give at least one requirement to {self.name} (see ' + f'"pip help {self.name}")' ) - for req in reqs_to_uninstall.values(): - uninstall_pathset = req.uninstall( - auto_confirm=options.yes, verbose=self.verbosity > 0, - ) - if uninstall_pathset: - uninstall_pathset.commit() + protect_pip_from_modification_on_windows( + modifying_pip="pip" in reqs_to_uninstall + ) + + for req in reqs_to_uninstall.values(): + uninstall_pathset = req.uninstall( + auto_confirm=options.yes, verbose=self.verbosity > 0, + ) + if uninstall_pathset: + uninstall_pathset.commit() + + warn_if_run_as_root() + return SUCCESS diff --git a/pipenv/patched/notpip/_internal/commands/wheel.py b/pipenv/patched/notpip/_internal/commands/wheel.py index 801efff8..ffb3d700 100644 --- a/pipenv/patched/notpip/_internal/commands/wheel.py +++ b/pipenv/patched/notpip/_internal/commands/wheel.py @@ -1,19 +1,19 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - import logging import os +import shutil +from optparse import Values +from typing import List from pipenv.patched.notpip._internal.cache import WheelCache from pipenv.patched.notpip._internal.cli import cmdoptions -from pipenv.patched.notpip._internal.cli.base_command import RequirementCommand -from pipenv.patched.notpip._internal.exceptions import CommandError, PreviousBuildDirError -from pipenv.patched.notpip._internal.operations.prepare import RequirementPreparer -from pipenv.patched.notpip._internal.req import RequirementSet -from pipenv.patched.notpip._internal.req.req_tracker import RequirementTracker -from pipenv.patched.notpip._internal.resolve import Resolver +from pipenv.patched.notpip._internal.cli.req_command import RequirementCommand, with_cleanup +from pipenv.patched.notpip._internal.cli.status_codes import SUCCESS +from pipenv.patched.notpip._internal.exceptions import CommandError +from pipenv.patched.notpip._internal.req.req_install import InstallRequirement +from pipenv.patched.notpip._internal.req.req_tracker import get_requirement_tracker +from pipenv.patched.notpip._internal.utils.misc import ensure_dir, normalize_path from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory -from pipenv.patched.notpip._internal.wheel import WheelBuilder +from pipenv.patched.notpip._internal.wheel_builder import build, should_build_for_wheel_command logger = logging.getLogger(__name__) @@ -33,7 +33,6 @@ class WheelCommand(RequirementCommand): """ - name = 'wheel' usage = """ %prog [options] ... %prog [options] -r ... @@ -41,14 +40,9 @@ class WheelCommand(RequirementCommand): %prog [options] [-e] ... %prog [options] ...""" - summary = 'Build wheels from your requirements.' + def add_options(self) -> None: - def __init__(self, *args, **kw): - super(WheelCommand, self).__init__(*args, **kw) - - cmd_opts = self.cmd_opts - - cmd_opts.add_option( + self.cmd_opts.add_option( '-w', '--wheel-dir', dest='wheel_dir', metavar='dir', @@ -56,37 +50,33 @@ class WheelCommand(RequirementCommand): help=("Build wheels into , where the default is the " "current working directory."), ) - cmd_opts.add_option(cmdoptions.no_binary()) - cmd_opts.add_option(cmdoptions.only_binary()) - cmd_opts.add_option(cmdoptions.prefer_binary()) - cmd_opts.add_option( - '--build-option', - dest='build_options', - metavar='options', - action='append', - help="Extra arguments to be supplied to 'setup.py bdist_wheel'.", + self.cmd_opts.add_option(cmdoptions.no_binary()) + self.cmd_opts.add_option(cmdoptions.only_binary()) + self.cmd_opts.add_option(cmdoptions.prefer_binary()) + self.cmd_opts.add_option(cmdoptions.no_build_isolation()) + self.cmd_opts.add_option(cmdoptions.use_pep517()) + self.cmd_opts.add_option(cmdoptions.no_use_pep517()) + self.cmd_opts.add_option(cmdoptions.constraints()) + self.cmd_opts.add_option(cmdoptions.editable()) + self.cmd_opts.add_option(cmdoptions.requirements()) + self.cmd_opts.add_option(cmdoptions.src()) + self.cmd_opts.add_option(cmdoptions.ignore_requires_python()) + self.cmd_opts.add_option(cmdoptions.no_deps()) + self.cmd_opts.add_option(cmdoptions.build_dir()) + self.cmd_opts.add_option(cmdoptions.progress_bar()) + + self.cmd_opts.add_option( + '--no-verify', + dest='no_verify', + action='store_true', + default=False, + help="Don't verify if built wheel is valid.", ) - cmd_opts.add_option(cmdoptions.no_build_isolation()) - cmd_opts.add_option(cmdoptions.use_pep517()) - cmd_opts.add_option(cmdoptions.no_use_pep517()) - cmd_opts.add_option(cmdoptions.constraints()) - cmd_opts.add_option(cmdoptions.editable()) - cmd_opts.add_option(cmdoptions.requirements()) - cmd_opts.add_option(cmdoptions.src()) - cmd_opts.add_option(cmdoptions.ignore_requires_python()) - cmd_opts.add_option(cmdoptions.no_deps()) - cmd_opts.add_option(cmdoptions.build_dir()) - cmd_opts.add_option(cmdoptions.progress_bar()) - cmd_opts.add_option( - '--global-option', - dest='global_options', - action='append', - metavar='options', - help="Extra global options to be supplied to the setup.py " - "call before the 'bdist_wheel' command.") + self.cmd_opts.add_option(cmdoptions.build_options()) + self.cmd_opts.add_option(cmdoptions.global_options()) - cmd_opts.add_option( + self.cmd_opts.add_option( '--pre', action='store_true', default=False, @@ -94,8 +84,7 @@ class WheelCommand(RequirementCommand): "pip only finds stable versions."), ) - cmd_opts.add_option(cmdoptions.no_clean()) - cmd_opts.add_option(cmdoptions.require_hashes()) + self.cmd_opts.add_option(cmdoptions.require_hashes()) index_opts = cmdoptions.make_option_group( cmdoptions.index_group, @@ -103,84 +92,85 @@ class WheelCommand(RequirementCommand): ) self.parser.insert_option_group(0, index_opts) - self.parser.insert_option_group(0, cmd_opts) + self.parser.insert_option_group(0, self.cmd_opts) - def run(self, options, args): + @with_cleanup + def run(self, options: Values, args: List[str]) -> int: cmdoptions.check_install_build_global(options) - index_urls = [options.index_url] + options.extra_index_urls - if options.no_index: - logger.debug('Ignoring indexes: %s', ','.join(index_urls)) - index_urls = [] + session = self.get_default_session(options) - if options.build_dir: - options.build_dir = os.path.abspath(options.build_dir) + finder = self._build_package_finder(options, session) + wheel_cache = WheelCache(options.cache_dir, options.format_control) - options.src_dir = os.path.abspath(options.src_dir) + options.wheel_dir = normalize_path(options.wheel_dir) + ensure_dir(options.wheel_dir) - with self._build_session(options) as session: - finder = self._build_package_finder(options, session) - build_delete = (not (options.no_clean or options.build_dir)) - wheel_cache = WheelCache(options.cache_dir, options.format_control) + req_tracker = self.enter_context(get_requirement_tracker()) - with RequirementTracker() as req_tracker, TempDirectory( - options.build_dir, delete=build_delete, kind="wheel" - ) as directory: + directory = TempDirectory( + delete=not options.no_clean, + kind="wheel", + globally_managed=True, + ) - requirement_set = RequirementSet( - require_hashes=options.require_hashes, + reqs = self.get_requirements(args, options, finder, session) + + preparer = self.make_requirement_preparer( + temp_build_dir=directory, + options=options, + req_tracker=req_tracker, + session=session, + finder=finder, + download_dir=options.wheel_dir, + use_user_site=False, + ) + + resolver = self.make_resolver( + preparer=preparer, + finder=finder, + options=options, + wheel_cache=wheel_cache, + ignore_requires_python=options.ignore_requires_python, + use_pep517=options.use_pep517, + ) + + self.trace_basic_info(finder) + + requirement_set = resolver.resolve( + reqs, check_supported_wheels=True + ) + + reqs_to_build: List[InstallRequirement] = [] + for req in requirement_set.requirements.values(): + if req.is_wheel: + preparer.save_linked_requirement(req) + elif should_build_for_wheel_command(req): + reqs_to_build.append(req) + + # build wheels + build_successes, build_failures = build( + reqs_to_build, + wheel_cache=wheel_cache, + verify=(not options.no_verify), + build_options=options.build_options or [], + global_options=options.global_options or [], + ) + for req in build_successes: + assert req.link and req.link.is_wheel + assert req.local_file_path + # copy from cache to target directory + try: + shutil.copy(req.local_file_path, options.wheel_dir) + except OSError as e: + logger.warning( + "Building wheel for %s failed: %s", + req.name, e, ) + build_failures.append(req) + if len(build_failures) != 0: + raise CommandError( + "Failed to build one or more wheels" + ) - try: - self.populate_requirement_set( - requirement_set, args, options, finder, session, - self.name, wheel_cache - ) - - preparer = RequirementPreparer( - build_dir=directory.path, - src_dir=options.src_dir, - download_dir=None, - wheel_download_dir=options.wheel_dir, - progress_bar=options.progress_bar, - build_isolation=options.build_isolation, - req_tracker=req_tracker, - ) - - resolver = Resolver( - preparer=preparer, - finder=finder, - session=session, - wheel_cache=wheel_cache, - use_user_site=False, - upgrade_strategy="to-satisfy-only", - force_reinstall=False, - ignore_dependencies=options.ignore_dependencies, - ignore_requires_python=options.ignore_requires_python, - ignore_installed=True, - isolated=options.isolated_mode, - use_pep517=options.use_pep517 - ) - resolver.resolve(requirement_set) - - # build wheels - wb = WheelBuilder( - finder, preparer, wheel_cache, - build_options=options.build_options or [], - global_options=options.global_options or [], - no_clean=options.no_clean, - ) - build_failures = wb.build( - requirement_set.requirements.values(), session=session, - ) - if len(build_failures) != 0: - raise CommandError( - "Failed to build one or more wheels" - ) - except PreviousBuildDirError: - options.no_clean = True - raise - finally: - if not options.no_clean: - requirement_set.cleanup_files() - wheel_cache.cleanup() + return SUCCESS diff --git a/pipenv/patched/notpip/_internal/configuration.py b/pipenv/patched/notpip/_internal/configuration.py index 3c02c955..fc6e4db7 100644 --- a/pipenv/patched/notpip/_internal/configuration.py +++ b/pipenv/patched/notpip/_internal/configuration.py @@ -11,30 +11,37 @@ Some terminology: A single word describing where the configuration key-value pair came from """ +import configparser import locale import logging import os - -from pipenv.patched.notpip._vendor import six -from pipenv.patched.notpip._vendor.six.moves import configparser +import sys +from typing import Any, Dict, Iterable, List, NewType, Optional, Tuple from pipenv.patched.notpip._internal.exceptions import ( - ConfigurationError, ConfigurationFileCouldNotBeLoaded, -) -from pipenv.patched.notpip._internal.locations import ( - legacy_config_file, new_config_file, running_under_virtualenv, - site_config_files, venv_config_file, + ConfigurationError, + ConfigurationFileCouldNotBeLoaded, ) +from pipenv.patched.notpip._internal.utils import appdirs +from pipenv.patched.notpip._internal.utils.compat import WINDOWS from pipenv.patched.notpip._internal.utils.misc import ensure_dir, enum -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING -if MYPY_CHECK_RUNNING: - from typing import ( # noqa: F401 - Any, Dict, Iterable, List, NewType, Optional, Tuple - ) +RawConfigParser = configparser.RawConfigParser # Shorthand +Kind = NewType("Kind", str) - RawConfigParser = configparser.RawConfigParser # Shorthand - Kind = NewType("Kind", str) +CONFIG_BASENAME = 'pip.ini' if WINDOWS else 'pip.conf' +ENV_NAMES_IGNORED = "version", "help" + +# The kinds of configurations there are. +kinds = enum( + USER="user", # User Specific + GLOBAL="global", # System Wide + SITE="site", # [Virtual] Environment Specific + ENV="env", # from PIP_CONFIG_FILE + ENV_VAR="env-var", # from Environment Variables +) +OVERRIDE_ORDER = kinds.GLOBAL, kinds.USER, kinds.SITE, kinds.ENV, kinds.ENV_VAR +VALID_LOAD_ONLY = kinds.USER, kinds.GLOBAL, kinds.SITE logger = logging.getLogger(__name__) @@ -52,20 +59,39 @@ def _normalize_name(name): def _disassemble_key(name): # type: (str) -> List[str] + if "." not in name: + error_message = ( + "Key does not contain dot separated section and key. " + "Perhaps you wanted to use 'global.{}' instead?" + ).format(name) + raise ConfigurationError(error_message) return name.split(".", 1) -# The kinds of configurations there are. -kinds = enum( - USER="user", # User Specific - GLOBAL="global", # System Wide - VENV="venv", # Virtual Environment Specific - ENV="env", # from PIP_CONFIG_FILE - ENV_VAR="env-var", # from Environment Variables -) +def get_configuration_files(): + # type: () -> Dict[Kind, List[str]] + global_config_files = [ + os.path.join(path, CONFIG_BASENAME) + for path in appdirs.site_config_dirs('pip') + ] + + site_config_file = os.path.join(sys.prefix, CONFIG_BASENAME) + legacy_config_file = os.path.join( + os.path.expanduser('~'), + 'pip' if WINDOWS else '.pip', + CONFIG_BASENAME, + ) + new_config_file = os.path.join( + appdirs.user_config_dir("pip"), CONFIG_BASENAME + ) + return { + kinds.GLOBAL: global_config_files, + kinds.SITE: [site_config_file], + kinds.USER: [legacy_config_file, new_config_file], + } -class Configuration(object): +class Configuration: """Handles management of configuration. Provides an interface to accessing and managing configuration files. @@ -80,32 +106,24 @@ class Configuration(object): """ def __init__(self, isolated, load_only=None): - # type: (bool, Kind) -> None - super(Configuration, self).__init__() + # type: (bool, Optional[Kind]) -> None + super().__init__() - _valid_load_only = [kinds.USER, kinds.GLOBAL, kinds.VENV, None] - if load_only not in _valid_load_only: + if load_only is not None and load_only not in VALID_LOAD_ONLY: raise ConfigurationError( "Got invalid value for load_only - should be one of {}".format( - ", ".join(map(repr, _valid_load_only[:-1])) + ", ".join(map(repr, VALID_LOAD_ONLY)) ) ) - self.isolated = isolated # type: bool - self.load_only = load_only # type: Optional[Kind] - - # The order here determines the override order. - self._override_order = [ - kinds.GLOBAL, kinds.USER, kinds.VENV, kinds.ENV, kinds.ENV_VAR - ] - - self._ignore_env_names = ["version", "help"] + self.isolated = isolated + self.load_only = load_only # Because we keep track of where we got the data from self._parsers = { - variant: [] for variant in self._override_order + variant: [] for variant in OVERRIDE_ORDER } # type: Dict[Kind, List[Tuple[str, RawConfigParser]]] self._config = { - variant: {} for variant in self._override_order + variant: {} for variant in OVERRIDE_ORDER } # type: Dict[Kind, Dict[str, Any]] self._modified_parsers = [] # type: List[Tuple[str, RawConfigParser]] @@ -143,7 +161,7 @@ class Configuration(object): try: return self._dictionary[key] except KeyError: - raise ConfigurationError("No such key - {}".format(key)) + raise ConfigurationError(f"No such key - {key}") def set_value(self, key, value): # type: (str, Any) -> None @@ -151,6 +169,7 @@ class Configuration(object): """ self._ensure_have_load_only() + assert self.load_only fname, parser = self._get_parser_to_modify() if parser is not None: @@ -166,46 +185,34 @@ class Configuration(object): def unset_value(self, key): # type: (str) -> None - """Unset a value in the configuration. - """ + """Unset a value in the configuration.""" self._ensure_have_load_only() + assert self.load_only if key not in self._config[self.load_only]: - raise ConfigurationError("No such key - {}".format(key)) + raise ConfigurationError(f"No such key - {key}") fname, parser = self._get_parser_to_modify() if parser is not None: section, name = _disassemble_key(key) - - # Remove the key in the parser - modified_something = False - if parser.has_section(section): - # Returns whether the option was removed or not - modified_something = parser.remove_option(section, name) - - if modified_something: - # name removed from parser, section may now be empty - section_iter = iter(parser.items(section)) - try: - val = six.next(section_iter) - except StopIteration: - val = None - - if val is None: - parser.remove_section(section) - - self._mark_as_modified(fname, parser) - else: + if not (parser.has_section(section) + and parser.remove_option(section, name)): + # The option was not removed. raise ConfigurationError( "Fatal Internal error [id=1]. Please report as a bug." ) + # The section may be empty after the option was removed. + if not parser.items(section): + parser.remove_section(section) + self._mark_as_modified(fname, parser) + del self._config[self.load_only][key] def save(self): # type: () -> None - """Save the currentin-memory state. + """Save the current in-memory state. """ self._ensure_have_load_only() @@ -216,7 +223,7 @@ class Configuration(object): ensure_dir(os.path.dirname(fname)) with open(fname, "w") as f: - parser.write(f) # type: ignore + parser.write(f) # # Private routines @@ -237,7 +244,7 @@ class Configuration(object): # are not needed here. retval = {} - for variant in self._override_order: + for variant in OVERRIDE_ORDER: retval.update(self._config[variant]) return retval @@ -246,7 +253,7 @@ class Configuration(object): # type: () -> None """Loads configuration from configuration files """ - config_files = dict(self._iter_config_files()) + config_files = dict(self.iter_config_files()) if config_files[kinds.ENV][0:1] == [os.devnull]: logger.debug( "Skipping loading configuration files due to " @@ -308,7 +315,7 @@ class Configuration(object): """Loads configuration from environment variables """ self._config[kinds.ENV_VAR].update( - self._normalized_keys(":env:", self._get_environ_vars()) + self._normalized_keys(":env:", self.get_environ_vars()) ) def _normalized_keys(self, section, items): @@ -324,19 +331,17 @@ class Configuration(object): normalized[key] = val return normalized - def _get_environ_vars(self): + def get_environ_vars(self): # type: () -> Iterable[Tuple[str, str]] """Returns a generator with all environmental vars with prefix PIP_""" for key, val in os.environ.items(): - should_be_yielded = ( - key.startswith("PIP_") and - key[4:].lower() not in self._ignore_env_names - ) - if should_be_yielded: - yield key[4:].lower(), val + if key.startswith("PIP_"): + name = key[4:].lower() + if name not in ENV_NAMES_IGNORED: + yield name, val # XXX: This is patched in the tests. - def _iter_config_files(self): + def iter_config_files(self): # type: () -> Iterable[Tuple[Kind, List[str]]] """Yields variant and configuration files associated with it. @@ -351,8 +356,10 @@ class Configuration(object): else: yield kinds.ENV, [] + config_files = get_configuration_files() + # at the base we have any global configuration - yield kinds.GLOBAL, list(site_config_files) + yield kinds.GLOBAL, config_files[kinds.GLOBAL] # per-user configuration next should_load_user_config = not self.isolated and not ( @@ -360,15 +367,20 @@ class Configuration(object): ) if should_load_user_config: # The legacy config file is overridden by the new config file - yield kinds.USER, [legacy_config_file, new_config_file] + yield kinds.USER, config_files[kinds.USER] # finally virtualenv configuration first trumping others - if running_under_virtualenv(): - yield kinds.VENV, [venv_config_file] + yield kinds.SITE, config_files[kinds.SITE] + + def get_values_in_config(self, variant): + # type: (Kind) -> Dict[str, Any] + """Get values present in a config file""" + return self._config[variant] def _get_parser_to_modify(self): # type: () -> Tuple[str, RawConfigParser] # Determine which parser to modify + assert self.load_only parsers = self._parsers[self.load_only] if not parsers: # This should not happen if everything works correctly. @@ -385,3 +397,7 @@ class Configuration(object): file_parser_tuple = (fname, parser) if file_parser_tuple not in self._modified_parsers: self._modified_parsers.append(file_parser_tuple) + + def __repr__(self): + # type: () -> str + return f"{self.__class__.__name__}({self._dictionary!r})" diff --git a/pipenv/patched/notpip/_internal/distributions/__init__.py b/pipenv/patched/notpip/_internal/distributions/__init__.py new file mode 100644 index 00000000..79c3b7b5 --- /dev/null +++ b/pipenv/patched/notpip/_internal/distributions/__init__.py @@ -0,0 +1,21 @@ +from pipenv.patched.notpip._internal.distributions.base import AbstractDistribution +from pipenv.patched.notpip._internal.distributions.sdist import SourceDistribution +from pipenv.patched.notpip._internal.distributions.wheel import WheelDistribution +from pipenv.patched.notpip._internal.req.req_install import InstallRequirement + + +def make_distribution_for_install_requirement( + install_req: InstallRequirement, +) -> AbstractDistribution: + """Returns a Distribution for the given InstallRequirement""" + # Editable requirements will always be source distributions. They use the + # legacy logic until we create a modern standard for them. + if install_req.editable: + return SourceDistribution(install_req) + + # If it's a wheel, it's a WheelDistribution + if install_req.is_wheel: + return WheelDistribution(install_req) + + # Otherwise, a SourceDistribution + return SourceDistribution(install_req) diff --git a/pipenv/patched/notpip/_internal/distributions/base.py b/pipenv/patched/notpip/_internal/distributions/base.py new file mode 100644 index 00000000..fabc9b81 --- /dev/null +++ b/pipenv/patched/notpip/_internal/distributions/base.py @@ -0,0 +1,38 @@ +import abc +from typing import Optional + +from pipenv.patched.notpip._vendor.pkg_resources import Distribution + +from pipenv.patched.notpip._internal.index.package_finder import PackageFinder +from pipenv.patched.notpip._internal.req import InstallRequirement + + +class AbstractDistribution(metaclass=abc.ABCMeta): + """A base class for handling installable artifacts. + + The requirements for anything installable are as follows: + + - we must be able to determine the requirement name + (or we can't correctly handle the non-upgrade case). + + - for packages with setup requirements, we must also be able + to determine their requirements without installing additional + packages (for the same reason as run-time dependencies) + + - we must be able to create a Distribution object exposing the + above metadata. + """ + + def __init__(self, req: InstallRequirement) -> None: + super().__init__() + self.req = req + + @abc.abstractmethod + def get_pkg_resources_distribution(self) -> Optional[Distribution]: + raise NotImplementedError() + + @abc.abstractmethod + def prepare_distribution_metadata( + self, finder: PackageFinder, build_isolation: bool + ) -> None: + raise NotImplementedError() diff --git a/pipenv/patched/notpip/_internal/distributions/installed.py b/pipenv/patched/notpip/_internal/distributions/installed.py new file mode 100644 index 00000000..127602b7 --- /dev/null +++ b/pipenv/patched/notpip/_internal/distributions/installed.py @@ -0,0 +1,22 @@ +from typing import Optional + +from pipenv.patched.notpip._vendor.pkg_resources import Distribution + +from pipenv.patched.notpip._internal.distributions.base import AbstractDistribution +from pipenv.patched.notpip._internal.index.package_finder import PackageFinder + + +class InstalledDistribution(AbstractDistribution): + """Represents an installed package. + + This does not need any preparation as the required information has already + been computed. + """ + + def get_pkg_resources_distribution(self) -> Optional[Distribution]: + return self.req.satisfied_by + + def prepare_distribution_metadata( + self, finder: PackageFinder, build_isolation: bool + ) -> None: + pass diff --git a/pipenv/patched/notpip/_internal/distributions/sdist.py b/pipenv/patched/notpip/_internal/distributions/sdist.py new file mode 100644 index 00000000..910fe48c --- /dev/null +++ b/pipenv/patched/notpip/_internal/distributions/sdist.py @@ -0,0 +1,95 @@ +import logging +from typing import Set, Tuple + +from pipenv.patched.notpip._vendor.pkg_resources import Distribution + +from pipenv.patched.notpip._internal.build_env import BuildEnvironment +from pipenv.patched.notpip._internal.distributions.base import AbstractDistribution +from pipenv.patched.notpip._internal.exceptions import InstallationError +from pipenv.patched.notpip._internal.index.package_finder import PackageFinder +from pipenv.patched.notpip._internal.utils.subprocess import runner_with_spinner_message + +logger = logging.getLogger(__name__) + + +class SourceDistribution(AbstractDistribution): + """Represents a source distribution. + + The preparation step for these needs metadata for the packages to be + generated, either using PEP 517 or using the legacy `setup.py egg_info`. + """ + + def get_pkg_resources_distribution(self) -> Distribution: + return self.req.get_dist() + + def prepare_distribution_metadata( + self, finder: PackageFinder, build_isolation: bool + ) -> None: + # Load pyproject.toml, to determine whether PEP 517 is to be used + self.req.load_pyproject_toml() + + # Set up the build isolation, if this requirement should be isolated + should_isolate = self.req.use_pep517 and build_isolation + if should_isolate: + self._setup_isolation(finder) + + self.req.prepare_metadata() + + def _setup_isolation(self, finder: PackageFinder) -> None: + def _raise_conflicts( + conflicting_with: str, conflicting_reqs: Set[Tuple[str, str]] + ) -> None: + format_string = ( + "Some build dependencies for {requirement} " + "conflict with {conflicting_with}: {description}." + ) + error_message = format_string.format( + requirement=self.req, + conflicting_with=conflicting_with, + description=", ".join( + f"{installed} is incompatible with {wanted}" + for installed, wanted in sorted(conflicting) + ), + ) + raise InstallationError(error_message) + + # Isolate in a BuildEnvironment and install the build-time + # requirements. + pyproject_requires = self.req.pyproject_requires + assert pyproject_requires is not None + + self.req.build_env = BuildEnvironment() + self.req.build_env.install_requirements( + finder, pyproject_requires, "overlay", "Installing build dependencies" + ) + conflicting, missing = self.req.build_env.check_requirements( + self.req.requirements_to_check + ) + if conflicting: + _raise_conflicts("PEP 517/518 supported requirements", conflicting) + if missing: + logger.warning( + "Missing build requirements in pyproject.toml for %s.", + self.req, + ) + logger.warning( + "The project does not specify a build backend, and " + "pip cannot fall back to setuptools without %s.", + " and ".join(map(repr, sorted(missing))), + ) + # Install any extra build dependencies that the backend requests. + # This must be done in a second pass, as the pyproject.toml + # dependencies must be installed before we can call the backend. + with self.req.build_env: + runner = runner_with_spinner_message("Getting requirements to build wheel") + backend = self.req.pep517_backend + assert backend is not None + with backend.subprocess_runner(runner): + reqs = backend.get_requires_for_build_wheel() + + conflicting, missing = self.req.build_env.check_requirements(reqs) + if conflicting: + _raise_conflicts("the backend dependencies", conflicting) + self.req.build_env.install_requirements( + finder, missing, "normal", "Installing backend dependencies" + ) diff --git a/pipenv/patched/notpip/_internal/distributions/wheel.py b/pipenv/patched/notpip/_internal/distributions/wheel.py new file mode 100644 index 00000000..91542844 --- /dev/null +++ b/pipenv/patched/notpip/_internal/distributions/wheel.py @@ -0,0 +1,34 @@ +from zipfile import ZipFile + +from pipenv.patched.notpip._vendor.pkg_resources import Distribution + +from pipenv.patched.notpip._internal.distributions.base import AbstractDistribution +from pipenv.patched.notpip._internal.index.package_finder import PackageFinder +from pipenv.patched.notpip._internal.utils.wheel import pkg_resources_distribution_for_wheel + + +class WheelDistribution(AbstractDistribution): + """Represents a wheel distribution. + + This does not need any preparation as wheels can be directly unpacked. + """ + + def get_pkg_resources_distribution(self) -> Distribution: + """Loads the metadata from the wheel file into memory and returns a + Distribution that uses it, not relying on the wheel file or + requirement. + """ + # Set as part of preparation during download. + assert self.req.local_file_path + # Wheels are never unnamed. + assert self.req.name + + with ZipFile(self.req.local_file_path, allowZip64=True) as z: + return pkg_resources_distribution_for_wheel( + z, self.req.name, self.req.local_file_path + ) + + def prepare_distribution_metadata( + self, finder: PackageFinder, build_isolation: bool + ) -> None: + pass diff --git a/pipenv/patched/notpip/_internal/download.py b/pipenv/patched/notpip/_internal/download.py deleted file mode 100644 index f593c2f2..00000000 --- a/pipenv/patched/notpip/_internal/download.py +++ /dev/null @@ -1,971 +0,0 @@ -from __future__ import absolute_import - -import cgi -import email.utils -import getpass -import json -import logging -import mimetypes -import os -import platform -import re -import shutil -import sys - -from pipenv.patched.notpip._vendor import requests, six, urllib3 -from pipenv.patched.notpip._vendor.cachecontrol import CacheControlAdapter -from pipenv.patched.notpip._vendor.cachecontrol.caches import FileCache -from pipenv.patched.notpip._vendor.lockfile import LockError -from pipenv.patched.notpip._vendor.requests.adapters import BaseAdapter, HTTPAdapter -from pipenv.patched.notpip._vendor.requests.auth import AuthBase, HTTPBasicAuth -from pipenv.patched.notpip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response -from pipenv.patched.notpip._vendor.requests.structures import CaseInsensitiveDict -from pipenv.patched.notpip._vendor.requests.utils import get_netrc_auth -# NOTE: XMLRPC Client is not annotated in typeshed as on 2017-07-17, which is -# why we ignore the type on this import -from pipenv.patched.notpip._vendor.six.moves import xmlrpc_client # type: ignore -from pipenv.patched.notpip._vendor.six.moves.urllib import parse as urllib_parse -from pipenv.patched.notpip._vendor.six.moves.urllib import request as urllib_request -from pipenv.patched.notpip._vendor.urllib3.util import IS_PYOPENSSL - -import pipenv.patched.notpip -from pipenv.patched.notpip._internal.exceptions import HashMismatch, InstallationError -from pipenv.patched.notpip._internal.locations import write_delete_marker_file -from pipenv.patched.notpip._internal.models.index import PyPI -from pipenv.patched.notpip._internal.utils.encoding import auto_decode -from pipenv.patched.notpip._internal.utils.filesystem import check_path_owner -from pipenv.patched.notpip._internal.utils.glibc import libc_ver -from pipenv.patched.notpip._internal.utils.logging import indent_log -from pipenv.patched.notpip._internal.utils.misc import ( - ARCHIVE_EXTENSIONS, ask_path_exists, backup_dir, call_subprocess, consume, - display_path, format_size, get_installed_version, rmtree, - split_auth_from_netloc, splitext, unpack_file, -) -from pipenv.patched.notpip._internal.utils.setuptools_build import SETUPTOOLS_SHIM -from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING -from pipenv.patched.notpip._internal.utils.ui import DownloadProgressProvider -from pipenv.patched.notpip._internal.vcs import vcs - -if MYPY_CHECK_RUNNING: - from typing import ( # noqa: F401 - Optional, Tuple, Dict, IO, Text, Union - ) - from pipenv.patched.notpip._internal.models.link import Link # noqa: F401 - from pipenv.patched.notpip._internal.utils.hashes import Hashes # noqa: F401 - from pipenv.patched.notpip._internal.vcs import AuthInfo # noqa: F401 - -try: - import ssl # noqa -except ImportError: - ssl = None - -HAS_TLS = (ssl is not None) or IS_PYOPENSSL - -__all__ = ['get_file_content', - 'is_url', 'url_to_path', 'path_to_url', - 'is_archive_file', 'unpack_vcs_link', - 'unpack_file_url', 'is_vcs_url', 'is_file_url', - 'unpack_http_url', 'unpack_url'] - - -logger = logging.getLogger(__name__) - - -def user_agent(): - """ - Return a string representing the user agent. - """ - data = { - "installer": {"name": "pip", "version": pipenv.patched.notpip.__version__}, - "python": platform.python_version(), - "implementation": { - "name": platform.python_implementation(), - }, - } - - if data["implementation"]["name"] == 'CPython': - data["implementation"]["version"] = platform.python_version() - elif data["implementation"]["name"] == 'PyPy': - if sys.pypy_version_info.releaselevel == 'final': - pypy_version_info = sys.pypy_version_info[:3] - else: - pypy_version_info = sys.pypy_version_info - data["implementation"]["version"] = ".".join( - [str(x) for x in pypy_version_info] - ) - elif data["implementation"]["name"] == 'Jython': - # Complete Guess - data["implementation"]["version"] = platform.python_version() - elif data["implementation"]["name"] == 'IronPython': - # Complete Guess - data["implementation"]["version"] = platform.python_version() - - if sys.platform.startswith("linux"): - from pipenv.patched.notpip._vendor import distro - distro_infos = dict(filter( - lambda x: x[1], - zip(["name", "version", "id"], distro.linux_distribution()), - )) - libc = dict(filter( - lambda x: x[1], - zip(["lib", "version"], libc_ver()), - )) - if libc: - distro_infos["libc"] = libc - if distro_infos: - data["distro"] = distro_infos - - if sys.platform.startswith("darwin") and platform.mac_ver()[0]: - data["distro"] = {"name": "macOS", "version": platform.mac_ver()[0]} - - if platform.system(): - data.setdefault("system", {})["name"] = platform.system() - - if platform.release(): - data.setdefault("system", {})["release"] = platform.release() - - if platform.machine(): - data["cpu"] = platform.machine() - - if HAS_TLS: - data["openssl_version"] = ssl.OPENSSL_VERSION - - setuptools_version = get_installed_version("setuptools") - if setuptools_version is not None: - data["setuptools_version"] = setuptools_version - - return "{data[installer][name]}/{data[installer][version]} {json}".format( - data=data, - json=json.dumps(data, separators=(",", ":"), sort_keys=True), - ) - - -class MultiDomainBasicAuth(AuthBase): - - def __init__(self, prompting=True): - # type: (bool) -> None - self.prompting = prompting - self.passwords = {} # type: Dict[str, AuthInfo] - - def __call__(self, req): - parsed = urllib_parse.urlparse(req.url) - - # Split the credentials from the netloc. - netloc, url_user_password = split_auth_from_netloc(parsed.netloc) - - # Set the url of the request to the url without any credentials - req.url = urllib_parse.urlunparse(parsed[:1] + (netloc,) + parsed[2:]) - - # Use any stored credentials that we have for this netloc - username, password = self.passwords.get(netloc, (None, None)) - - # Use the credentials embedded in the url if we have none stored - if username is None: - username, password = url_user_password - - # Get creds from netrc if we still don't have them - if username is None and password is None: - netrc_auth = get_netrc_auth(req.url) - username, password = netrc_auth if netrc_auth else (None, None) - - if username or password: - # Store the username and password - self.passwords[netloc] = (username, password) - - # Send the basic auth with this request - req = HTTPBasicAuth(username or "", password or "")(req) - - # Attach a hook to handle 401 responses - req.register_hook("response", self.handle_401) - - return req - - def handle_401(self, resp, **kwargs): - # We only care about 401 responses, anything else we want to just - # pass through the actual response - if resp.status_code != 401: - return resp - - # We are not able to prompt the user so simply return the response - if not self.prompting: - return resp - - parsed = urllib_parse.urlparse(resp.url) - - # Prompt the user for a new username and password - username = six.moves.input("User for %s: " % parsed.netloc) - password = getpass.getpass("Password: ") - - # Store the new username and password to use for future requests - if username or password: - self.passwords[parsed.netloc] = (username, password) - - # Consume content and release the original connection to allow our new - # request to reuse the same one. - resp.content - resp.raw.release_conn() - - # Add our new username and password to the request - req = HTTPBasicAuth(username or "", password or "")(resp.request) - req.register_hook("response", self.warn_on_401) - - # Send our new request - new_resp = resp.connection.send(req, **kwargs) - new_resp.history.append(resp) - - return new_resp - - def warn_on_401(self, resp, **kwargs): - # warn user that they provided incorrect credentials - if resp.status_code == 401: - logger.warning('401 Error, Credentials not correct for %s', - resp.request.url) - - -class LocalFSAdapter(BaseAdapter): - - def send(self, request, stream=None, timeout=None, verify=None, cert=None, - proxies=None): - pathname = url_to_path(request.url) - - resp = Response() - resp.status_code = 200 - resp.url = request.url - - try: - stats = os.stat(pathname) - except OSError as exc: - resp.status_code = 404 - resp.raw = exc - else: - modified = email.utils.formatdate(stats.st_mtime, usegmt=True) - content_type = mimetypes.guess_type(pathname)[0] or "text/plain" - resp.headers = CaseInsensitiveDict({ - "Content-Type": content_type, - "Content-Length": stats.st_size, - "Last-Modified": modified, - }) - - resp.raw = open(pathname, "rb") - resp.close = resp.raw.close - - return resp - - def close(self): - pass - - -class SafeFileCache(FileCache): - """ - A file based cache which is safe to use even when the target directory may - not be accessible or writable. - """ - - def __init__(self, *args, **kwargs): - super(SafeFileCache, self).__init__(*args, **kwargs) - - # Check to ensure that the directory containing our cache directory - # is owned by the user current executing pip. If it does not exist - # we will check the parent directory until we find one that does exist. - # If it is not owned by the user executing pip then we will disable - # the cache and log a warning. - if not check_path_owner(self.directory): - logger.warning( - "The directory '%s' or its parent directory is not owned by " - "the current user and the cache has been disabled. Please " - "check the permissions and owner of that directory. If " - "executing pip with sudo, you may want sudo's -H flag.", - self.directory, - ) - - # Set our directory to None to disable the Cache - self.directory = None - - def get(self, *args, **kwargs): - # If we don't have a directory, then the cache should be a no-op. - if self.directory is None: - return - - try: - return super(SafeFileCache, self).get(*args, **kwargs) - except (LockError, OSError, IOError): - # We intentionally silence this error, if we can't access the cache - # then we can just skip caching and process the request as if - # caching wasn't enabled. - pass - - def set(self, *args, **kwargs): - # If we don't have a directory, then the cache should be a no-op. - if self.directory is None: - return - - try: - return super(SafeFileCache, self).set(*args, **kwargs) - except (LockError, OSError, IOError): - # We intentionally silence this error, if we can't access the cache - # then we can just skip caching and process the request as if - # caching wasn't enabled. - pass - - def delete(self, *args, **kwargs): - # If we don't have a directory, then the cache should be a no-op. - if self.directory is None: - return - - try: - return super(SafeFileCache, self).delete(*args, **kwargs) - except (LockError, OSError, IOError): - # We intentionally silence this error, if we can't access the cache - # then we can just skip caching and process the request as if - # caching wasn't enabled. - pass - - -class InsecureHTTPAdapter(HTTPAdapter): - - def cert_verify(self, conn, url, verify, cert): - conn.cert_reqs = 'CERT_NONE' - conn.ca_certs = None - - -class PipSession(requests.Session): - - timeout = None # type: Optional[int] - - def __init__(self, *args, **kwargs): - retries = kwargs.pop("retries", 0) - cache = kwargs.pop("cache", None) - insecure_hosts = kwargs.pop("insecure_hosts", []) - - super(PipSession, self).__init__(*args, **kwargs) - - # Attach our User Agent to the request - self.headers["User-Agent"] = user_agent() - - # Attach our Authentication handler to the session - self.auth = MultiDomainBasicAuth() - - # Create our urllib3.Retry instance which will allow us to customize - # how we handle retries. - retries = urllib3.Retry( - # Set the total number of retries that a particular request can - # have. - total=retries, - - # A 503 error from PyPI typically means that the Fastly -> Origin - # connection got interrupted in some way. A 503 error in general - # is typically considered a transient error so we'll go ahead and - # retry it. - # A 500 may indicate transient error in Amazon S3 - # A 520 or 527 - may indicate transient error in CloudFlare - status_forcelist=[500, 503, 520, 527], - - # Add a small amount of back off between failed requests in - # order to prevent hammering the service. - backoff_factor=0.25, - ) - - # We want to _only_ cache responses on securely fetched origins. We do - # this because we can't validate the response of an insecurely fetched - # origin, and we don't want someone to be able to poison the cache and - # require manual eviction from the cache to fix it. - if cache: - secure_adapter = CacheControlAdapter( - cache=SafeFileCache(cache, use_dir_lock=True), - max_retries=retries, - ) - else: - secure_adapter = HTTPAdapter(max_retries=retries) - - # Our Insecure HTTPAdapter disables HTTPS validation. It does not - # support caching (see above) so we'll use it for all http:// URLs as - # well as any https:// host that we've marked as ignoring TLS errors - # for. - insecure_adapter = InsecureHTTPAdapter(max_retries=retries) - - self.mount("https://", secure_adapter) - self.mount("http://", insecure_adapter) - - # Enable file:// urls - self.mount("file://", LocalFSAdapter()) - - # We want to use a non-validating adapter for any requests which are - # deemed insecure. - for host in insecure_hosts: - self.mount("https://{}/".format(host), insecure_adapter) - - def request(self, method, url, *args, **kwargs): - # Allow setting a default timeout on a session - kwargs.setdefault("timeout", self.timeout) - - # Dispatch the actual request - return super(PipSession, self).request(method, url, *args, **kwargs) - - -def get_file_content(url, comes_from=None, session=None): - # type: (str, Optional[str], Optional[PipSession]) -> Tuple[str, Text] - """Gets the content of a file; it may be a filename, file: URL, or - http: URL. Returns (location, content). Content is unicode. - - :param url: File path or url. - :param comes_from: Origin description of requirements. - :param session: Instance of pip.download.PipSession. - """ - if session is None: - raise TypeError( - "get_file_content() missing 1 required keyword argument: 'session'" - ) - - match = _scheme_re.search(url) - if match: - scheme = match.group(1).lower() - if (scheme == 'file' and comes_from and - comes_from.startswith('http')): - raise InstallationError( - 'Requirements file %s references URL %s, which is local' - % (comes_from, url)) - if scheme == 'file': - path = url.split(':', 1)[1] - path = path.replace('\\', '/') - match = _url_slash_drive_re.match(path) - if match: - path = match.group(1) + ':' + path.split('|', 1)[1] - path = urllib_parse.unquote(path) - if path.startswith('/'): - path = '/' + path.lstrip('/') - url = path - else: - # FIXME: catch some errors - resp = session.get(url) - resp.raise_for_status() - return resp.url, resp.text - try: - with open(url, 'rb') as f: - content = auto_decode(f.read()) - except IOError as exc: - raise InstallationError( - 'Could not open requirements file: %s' % str(exc) - ) - return url, content - - -_scheme_re = re.compile(r'^(http|https|file):', re.I) -_url_slash_drive_re = re.compile(r'/*([a-z])\|', re.I) - - -def is_url(name): - # type: (Union[str, Text]) -> bool - """Returns true if the name looks like a URL""" - if ':' not in name: - return False - scheme = name.split(':', 1)[0].lower() - return scheme in ['http', 'https', 'file', 'ftp'] + vcs.all_schemes - - -def url_to_path(url): - # type: (str) -> str - """ - Convert a file: URL to a path. - """ - assert url.startswith('file:'), ( - "You can only turn file: urls into filenames (not %r)" % url) - - _, netloc, path, _, _ = urllib_parse.urlsplit(url) - - # if we have a UNC path, prepend UNC share notation - if netloc: - netloc = '\\\\' + netloc - - path = urllib_request.url2pathname(netloc + path) - return path - - -def path_to_url(path): - # type: (Union[str, Text]) -> str - """ - Convert a path to a file: URL. The path will be made absolute and have - quoted path parts. - """ - path = os.path.normpath(os.path.abspath(path)) - url = urllib_parse.urljoin('file:', urllib_request.pathname2url(path)) - return url - - -def is_archive_file(name): - # type: (str) -> bool - """Return True if `name` is a considered as an archive file.""" - ext = splitext(name)[1].lower() - if ext in ARCHIVE_EXTENSIONS: - return True - return False - - -def unpack_vcs_link(link, location): - vcs_backend = _get_used_vcs_backend(link) - vcs_backend.unpack(location) - - -def _get_used_vcs_backend(link): - for backend in vcs.backends: - if link.scheme in backend.schemes: - vcs_backend = backend(link.url) - return vcs_backend - - -def is_vcs_url(link): - # type: (Link) -> bool - return bool(_get_used_vcs_backend(link)) - - -def is_file_url(link): - # type: (Link) -> bool - return link.url.lower().startswith('file:') - - -def is_dir_url(link): - # type: (Link) -> bool - """Return whether a file:// Link points to a directory. - - ``link`` must not have any other scheme but file://. Call is_file_url() - first. - - """ - link_path = url_to_path(link.url_without_fragment) - return os.path.isdir(link_path) - - -def _progress_indicator(iterable, *args, **kwargs): - return iterable - - -def _download_url( - resp, # type: Response - link, # type: Link - content_file, # type: IO - hashes, # type: Hashes - progress_bar # type: str -): - # type: (...) -> None - try: - total_length = int(resp.headers['content-length']) - except (ValueError, KeyError, TypeError): - total_length = 0 - - cached_resp = getattr(resp, "from_cache", False) - if logger.getEffectiveLevel() > logging.INFO: - show_progress = False - elif cached_resp: - show_progress = False - elif total_length > (40 * 1000): - show_progress = True - elif not total_length: - show_progress = True - else: - show_progress = False - - show_url = link.show_url - - def resp_read(chunk_size): - try: - # Special case for urllib3. - for chunk in resp.raw.stream( - chunk_size, - # We use decode_content=False here because we don't - # want urllib3 to mess with the raw bytes we get - # from the server. If we decompress inside of - # urllib3 then we cannot verify the checksum - # because the checksum will be of the compressed - # file. This breakage will only occur if the - # server adds a Content-Encoding header, which - # depends on how the server was configured: - # - Some servers will notice that the file isn't a - # compressible file and will leave the file alone - # and with an empty Content-Encoding - # - Some servers will notice that the file is - # already compressed and will leave the file - # alone and will add a Content-Encoding: gzip - # header - # - Some servers won't notice anything at all and - # will take a file that's already been compressed - # and compress it again and set the - # Content-Encoding: gzip header - # - # By setting this not to decode automatically we - # hope to eliminate problems with the second case. - decode_content=False): - yield chunk - except AttributeError: - # Standard file-like object. - while True: - chunk = resp.raw.read(chunk_size) - if not chunk: - break - yield chunk - - def written_chunks(chunks): - for chunk in chunks: - content_file.write(chunk) - yield chunk - - progress_indicator = _progress_indicator - - if link.netloc == PyPI.netloc: - url = show_url - else: - url = link.url_without_fragment - - if show_progress: # We don't show progress on cached responses - progress_indicator = DownloadProgressProvider(progress_bar, - max=total_length) - if total_length: - logger.info("Downloading %s (%s)", url, format_size(total_length)) - else: - logger.info("Downloading %s", url) - elif cached_resp: - logger.info("Using cached %s", url) - else: - logger.info("Downloading %s", url) - - logger.debug('Downloading from URL %s', link) - - downloaded_chunks = written_chunks( - progress_indicator( - resp_read(CONTENT_CHUNK_SIZE), - CONTENT_CHUNK_SIZE - ) - ) - if hashes: - hashes.check_against_chunks(downloaded_chunks) - else: - consume(downloaded_chunks) - - -def _copy_file(filename, location, link): - copy = True - download_location = os.path.join(location, link.filename) - if os.path.exists(download_location): - response = ask_path_exists( - 'The file %s exists. (i)gnore, (w)ipe, (b)ackup, (a)abort' % - display_path(download_location), ('i', 'w', 'b', 'a')) - if response == 'i': - copy = False - elif response == 'w': - logger.warning('Deleting %s', display_path(download_location)) - os.remove(download_location) - elif response == 'b': - dest_file = backup_dir(download_location) - logger.warning( - 'Backing up %s to %s', - display_path(download_location), - display_path(dest_file), - ) - shutil.move(download_location, dest_file) - elif response == 'a': - sys.exit(-1) - if copy: - shutil.copy(filename, download_location) - logger.info('Saved %s', display_path(download_location)) - - -def unpack_http_url( - link, # type: Link - location, # type: str - download_dir=None, # type: Optional[str] - session=None, # type: Optional[PipSession] - hashes=None, # type: Optional[Hashes] - progress_bar="on" # type: str -): - # type: (...) -> None - if session is None: - raise TypeError( - "unpack_http_url() missing 1 required keyword argument: 'session'" - ) - - with TempDirectory(kind="unpack") as temp_dir: - # If a download dir is specified, is the file already downloaded there? - already_downloaded_path = None - if download_dir: - already_downloaded_path = _check_download_dir(link, - download_dir, - hashes) - - if already_downloaded_path: - from_path = already_downloaded_path - content_type = mimetypes.guess_type(from_path)[0] - else: - # let's download to a tmp dir - from_path, content_type = _download_http_url(link, - session, - temp_dir.path, - hashes, - progress_bar) - - # unpack the archive to the build dir location. even when only - # downloading archives, they have to be unpacked to parse dependencies - unpack_file(from_path, location, content_type, link) - - # a download dir is specified; let's copy the archive there - if download_dir and not already_downloaded_path: - _copy_file(from_path, download_dir, link) - - if not already_downloaded_path: - os.unlink(from_path) - - -def unpack_file_url( - link, # type: Link - location, # type: str - download_dir=None, # type: Optional[str] - hashes=None # type: Optional[Hashes] -): - # type: (...) -> None - """Unpack link into location. - - If download_dir is provided and link points to a file, make a copy - of the link file inside download_dir. - """ - link_path = url_to_path(link.url_without_fragment) - - # If it's a url to a local directory - if is_dir_url(link): - if os.path.isdir(location): - rmtree(location) - shutil.copytree(link_path, location, symlinks=True) - if download_dir: - logger.info('Link is a directory, ignoring download_dir') - return - - # If --require-hashes is off, `hashes` is either empty, the - # link's embedded hash, or MissingHashes; it is required to - # match. If --require-hashes is on, we are satisfied by any - # hash in `hashes` matching: a URL-based or an option-based - # one; no internet-sourced hash will be in `hashes`. - if hashes: - hashes.check_against_path(link_path) - - # If a download dir is specified, is the file already there and valid? - already_downloaded_path = None - if download_dir: - already_downloaded_path = _check_download_dir(link, - download_dir, - hashes) - - if already_downloaded_path: - from_path = already_downloaded_path - else: - from_path = link_path - - content_type = mimetypes.guess_type(from_path)[0] - - # unpack the archive to the build dir location. even when only downloading - # archives, they have to be unpacked to parse dependencies - unpack_file(from_path, location, content_type, link) - - # a download dir is specified and not already downloaded - if download_dir and not already_downloaded_path: - _copy_file(from_path, download_dir, link) - - -def _copy_dist_from_dir(link_path, location): - """Copy distribution files in `link_path` to `location`. - - Invoked when user requests to install a local directory. E.g.: - - pip install . - pip install ~/dev/git-repos/python-prompt-toolkit - - """ - - # Note: This is currently VERY SLOW if you have a lot of data in the - # directory, because it copies everything with `shutil.copytree`. - # What it should really do is build an sdist and install that. - # See https://github.com/pypa/pip/issues/2195 - - if os.path.isdir(location): - rmtree(location) - - # build an sdist - setup_py = 'setup.py' - sdist_args = [sys.executable] - sdist_args.append('-c') - sdist_args.append(SETUPTOOLS_SHIM % setup_py) - sdist_args.append('sdist') - sdist_args += ['--dist-dir', location] - logger.info('Running setup.py sdist for %s', link_path) - - with indent_log(): - call_subprocess(sdist_args, cwd=link_path, show_stdout=False) - - # unpack sdist into `location` - sdist = os.path.join(location, os.listdir(location)[0]) - logger.info('Unpacking sdist %s into %s', sdist, location) - unpack_file(sdist, location, content_type=None, link=None) - - -class PipXmlrpcTransport(xmlrpc_client.Transport): - """Provide a `xmlrpclib.Transport` implementation via a `PipSession` - object. - """ - - def __init__(self, index_url, session, use_datetime=False): - xmlrpc_client.Transport.__init__(self, use_datetime) - index_parts = urllib_parse.urlparse(index_url) - self._scheme = index_parts.scheme - self._session = session - - def request(self, host, handler, request_body, verbose=False): - parts = (self._scheme, host, handler, None, None, None) - url = urllib_parse.urlunparse(parts) - try: - headers = {'Content-Type': 'text/xml'} - response = self._session.post(url, data=request_body, - headers=headers, stream=True) - response.raise_for_status() - self.verbose = verbose - return self.parse_response(response.raw) - except requests.HTTPError as exc: - logger.critical( - "HTTP error %s while getting %s", - exc.response.status_code, url, - ) - raise - - -def unpack_url( - link, # type: Optional[Link] - location, # type: Optional[str] - download_dir=None, # type: Optional[str] - only_download=False, # type: bool - session=None, # type: Optional[PipSession] - hashes=None, # type: Optional[Hashes] - progress_bar="on" # type: str -): - # type: (...) -> None - """Unpack link. - If link is a VCS link: - if only_download, export into download_dir and ignore location - else unpack into location - for other types of link: - - unpack into location - - if download_dir, copy the file into download_dir - - if only_download, mark location for deletion - - :param hashes: A Hashes object, one of whose embedded hashes must match, - or HashMismatch will be raised. If the Hashes is empty, no matches are - required, and unhashable types of requirements (like VCS ones, which - would ordinarily raise HashUnsupported) are allowed. - """ - # non-editable vcs urls - if is_vcs_url(link): - unpack_vcs_link(link, location) - - # file urls - elif is_file_url(link): - unpack_file_url(link, location, download_dir, hashes=hashes) - - # http urls - else: - if session is None: - session = PipSession() - - unpack_http_url( - link, - location, - download_dir, - session, - hashes=hashes, - progress_bar=progress_bar - ) - if only_download: - write_delete_marker_file(location) - - -def _download_http_url( - link, # type: Link - session, # type: PipSession - temp_dir, # type: str - hashes, # type: Hashes - progress_bar # type: str -): - # type: (...) -> Tuple[str, str] - """Download link url into temp_dir using provided session""" - target_url = link.url.split('#', 1)[0] - try: - resp = session.get( - target_url, - # We use Accept-Encoding: identity here because requests - # defaults to accepting compressed responses. This breaks in - # a variety of ways depending on how the server is configured. - # - Some servers will notice that the file isn't a compressible - # file and will leave the file alone and with an empty - # Content-Encoding - # - Some servers will notice that the file is already - # compressed and will leave the file alone and will add a - # Content-Encoding: gzip header - # - Some servers won't notice anything at all and will take - # a file that's already been compressed and compress it again - # and set the Content-Encoding: gzip header - # By setting this to request only the identity encoding We're - # hoping to eliminate the third case. Hopefully there does not - # exist a server which when given a file will notice it is - # already compressed and that you're not asking for a - # compressed file and will then decompress it before sending - # because if that's the case I don't think it'll ever be - # possible to make this work. - headers={"Accept-Encoding": "identity"}, - stream=True, - ) - resp.raise_for_status() - except requests.HTTPError as exc: - logger.critical( - "HTTP error %s while getting %s", exc.response.status_code, link, - ) - raise - - content_type = resp.headers.get('content-type', '') - filename = link.filename # fallback - # Have a look at the Content-Disposition header for a better guess - content_disposition = resp.headers.get('content-disposition') - if content_disposition: - type, params = cgi.parse_header(content_disposition) - # We use ``or`` here because we don't want to use an "empty" value - # from the filename param. - filename = params.get('filename') or filename - ext = splitext(filename)[1] - if not ext: - ext = mimetypes.guess_extension(content_type) - if ext: - filename += ext - if not ext and link.url != resp.url: - ext = os.path.splitext(resp.url)[1] - if ext: - filename += ext - file_path = os.path.join(temp_dir, filename) - with open(file_path, 'wb') as content_file: - _download_url(resp, link, content_file, hashes, progress_bar) - return file_path, content_type - - -def _check_download_dir(link, download_dir, hashes): - # type: (Link, str, Hashes) -> Optional[str] - """ Check download_dir for previously downloaded file with correct hash - If a correct file is found return its path else None - """ - download_path = os.path.join(download_dir, link.filename) - if os.path.exists(download_path): - # If already downloaded, does its hash match? - logger.info('File was already downloaded %s', download_path) - if hashes: - try: - hashes.check_against_path(download_path) - except HashMismatch: - logger.warning( - 'Previously-downloaded file %s has bad hash. ' - 'Re-downloading.', - download_path - ) - os.unlink(download_path) - return None - return download_path - return None diff --git a/pipenv/patched/notpip/_internal/exceptions.py b/pipenv/patched/notpip/_internal/exceptions.py index 1342935d..79fc0edb 100644 --- a/pipenv/patched/notpip/_internal/exceptions.py +++ b/pipenv/patched/notpip/_internal/exceptions.py @@ -1,15 +1,16 @@ """Exceptions used throughout package""" -from __future__ import absolute_import +import configparser from itertools import chain, groupby, repeat +from typing import TYPE_CHECKING, Dict, List, Optional -from pipenv.patched.notpip._vendor.six import iteritems +from pipenv.patched.notpip._vendor.pkg_resources import Distribution +from pipenv.patched.notpip._vendor.requests.models import Request, Response -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING +if TYPE_CHECKING: + from hashlib import _Hash -if MYPY_CHECK_RUNNING: - from typing import Optional # noqa: F401 - from pipenv.patched.notpip._internal.req.req_install import InstallRequirement # noqa: F401 + from pipenv.patched.notpip._internal.req.req_install import InstallRequirement class PipError(Exception): @@ -28,6 +29,51 @@ class UninstallationError(PipError): """General exception during uninstallation""" +class NoneMetadataError(PipError): + """ + Raised when accessing "METADATA" or "PKG-INFO" metadata for a + pipenv.patched.notpip._vendor.pkg_resources.Distribution object and + `dist.has_metadata('METADATA')` returns True but + `dist.get_metadata('METADATA')` returns None (and similarly for + "PKG-INFO"). + """ + + def __init__(self, dist, metadata_name): + # type: (Distribution, str) -> None + """ + :param dist: A Distribution object. + :param metadata_name: The name of the metadata being accessed + (can be "METADATA" or "PKG-INFO"). + """ + self.dist = dist + self.metadata_name = metadata_name + + def __str__(self): + # type: () -> str + # Use `dist` in the error message because its stringification + # includes more information, like the version and location. + return ( + 'None {} metadata found for distribution: {}'.format( + self.metadata_name, self.dist, + ) + ) + + +class UserInstallationInvalid(InstallationError): + """A --user install is requested on an environment without user site.""" + + def __str__(self): + # type: () -> str + return "User base directory is not specified" + + +class InvalidSchemeCombination(InstallationError): + def __str__(self): + # type: () -> str + before = ", ".join(str(a) for a in self.args[:-1]) + return f"Cannot set {before} and {self.args[-1]} together" + + class DistributionNotFound(InstallationError): """Raised when a distribution cannot be found to satisfy a requirement""" @@ -53,6 +99,28 @@ class PreviousBuildDirError(PipError): """Raised when there's a previous conflicting build directory""" +class NetworkConnectionError(PipError): + """HTTP connection error""" + + def __init__(self, error_msg, response=None, request=None): + # type: (str, Response, Request) -> None + """ + Initialize NetworkConnectionError with `request` and `response` + objects. + """ + self.response = response + self.request = request + self.error_msg = error_msg + if (self.response is not None and not self.request and + hasattr(response, 'request')): + self.request = self.response.request + super().__init__(error_msg, response, request) + + def __str__(self): + # type: () -> str + return str(self.error_msg) + + class InvalidWheelFilename(InstallationError): """Invalid wheel filename.""" @@ -61,16 +129,57 @@ class UnsupportedWheel(InstallationError): """Unsupported wheel.""" +class MetadataInconsistent(InstallationError): + """Built metadata contains inconsistent information. + + This is raised when the metadata contains values (e.g. name and version) + that do not match the information previously obtained from sdist filename + or user-supplied ``#egg=`` value. + """ + def __init__(self, ireq, field, f_val, m_val): + # type: (InstallRequirement, str, str, str) -> None + self.ireq = ireq + self.field = field + self.f_val = f_val + self.m_val = m_val + + def __str__(self): + # type: () -> str + template = ( + "Requested {} has inconsistent {}: " + "filename has {!r}, but metadata has {!r}" + ) + return template.format(self.ireq, self.field, self.f_val, self.m_val) + + +class InstallationSubprocessError(InstallationError): + """A subprocess call failed during installation.""" + def __init__(self, returncode, description): + # type: (int, str) -> None + self.returncode = returncode + self.description = description + + def __str__(self): + # type: () -> str + return ( + "Command errored out with exit status {}: {} " + "Check the logs for full command output." + ).format(self.returncode, self.description) + + class HashErrors(InstallationError): """Multiple HashError instances rolled into one for reporting""" def __init__(self): - self.errors = [] + # type: () -> None + self.errors = [] # type: List[HashError] def append(self, error): + # type: (HashError) -> None self.errors.append(error) def __str__(self): + # type: () -> str lines = [] self.errors.sort(key=lambda e: e.order) for cls, errors_of_cls in groupby(self.errors, lambda e: e.__class__): @@ -78,11 +187,14 @@ class HashErrors(InstallationError): lines.extend(e.body() for e in errors_of_cls) if lines: return '\n'.join(lines) + return '' def __nonzero__(self): + # type: () -> bool return bool(self.errors) def __bool__(self): + # type: () -> bool return self.__nonzero__() @@ -104,23 +216,27 @@ class HashError(InstallationError): """ req = None # type: Optional[InstallRequirement] head = '' + order = -1 # type: int def body(self): + # type: () -> str """Return a summary of me for display under the heading. This default implementation simply prints a description of the triggering requirement. :param req: The InstallRequirement that provoked this error, with - populate_link() having already been called + its link already populated by the resolver's _populate_link(). """ - return ' %s' % self._requirement_name() + return f' {self._requirement_name()}' def __str__(self): - return '%s\n%s' % (self.head, self.body()) + # type: () -> str + return f'{self.head}\n{self.body()}' def _requirement_name(self): + # type: () -> str """Return a description of the requirement that triggered me. This default implementation returns long description of the req, with @@ -161,6 +277,7 @@ class HashMissing(HashError): 'has a hash.)') def __init__(self, gotten_hash): + # type: (str) -> None """ :param gotten_hash: The hash of the (possibly malicious) archive we just downloaded @@ -168,6 +285,7 @@ class HashMissing(HashError): self.gotten_hash = gotten_hash def body(self): + # type: () -> str # Dodge circular import. from pipenv.patched.notpip._internal.utils.hashes import FAVORITE_HASH @@ -180,9 +298,9 @@ class HashMissing(HashError): # In case someone feeds something downright stupid # to InstallRequirement's constructor. else getattr(self.req, 'req', None)) - return ' %s --hash=%s:%s' % (package or 'unknown package', - FAVORITE_HASH, - self.gotten_hash) + return ' {} --hash={}:{}'.format(package or 'unknown package', + FAVORITE_HASH, + self.gotten_hash) class HashUnpinned(HashError): @@ -210,6 +328,7 @@ class HashMismatch(HashError): 'someone may have tampered with them.') def __init__(self, allowed, gots): + # type: (Dict[str, List[str]], Dict[str, _Hash]) -> None """ :param allowed: A dict of algorithm names pointing to lists of allowed hex digests @@ -220,10 +339,12 @@ class HashMismatch(HashError): self.gots = gots def body(self): - return ' %s:\n%s' % (self._requirement_name(), - self._hash_comparison()) + # type: () -> str + return ' {}:\n{}'.format(self._requirement_name(), + self._hash_comparison()) def _hash_comparison(self): + # type: () -> str """ Return a comparison of actual and expected hash values. @@ -235,18 +356,18 @@ class HashMismatch(HashError): """ def hash_then_or(hash_name): + # type: (str) -> chain[str] # For now, all the decent hashes have 6-char names, so we can get # away with hard-coding space literals. return chain([hash_name], repeat(' or')) - lines = [] - for hash_name, expecteds in iteritems(self.allowed): + lines = [] # type: List[str] + for hash_name, expecteds in self.allowed.items(): prefix = hash_then_or(hash_name) - lines.extend((' Expected %s %s' % (next(prefix), e)) + lines.extend((' Expected {} {}'.format(next(prefix), e)) for e in expecteds) - lines.append(' Got %s\n' % - self.gots[hash_name].hexdigest()) - prefix = ' or' + lines.append(' Got {}\n'.format( + self.gots[hash_name].hexdigest())) return '\n'.join(lines) @@ -260,15 +381,17 @@ class ConfigurationFileCouldNotBeLoaded(ConfigurationError): """ def __init__(self, reason="could not be loaded", fname=None, error=None): - super(ConfigurationFileCouldNotBeLoaded, self).__init__(error) + # type: (str, Optional[str], Optional[configparser.Error]) -> None + super().__init__(error) self.reason = reason self.fname = fname self.error = error def __str__(self): + # type: () -> str if self.fname is not None: - message_part = " in {}.".format(self.fname) + message_part = f" in {self.fname}." else: assert self.error is not None - message_part = ".\n{}\n".format(self.error.message) - return "Configuration file {}{}".format(self.reason, message_part) + message_part = f".\n{self.error}\n" + return f"Configuration file {self.reason}{message_part}" diff --git a/pipenv/patched/notpip/_internal/index.py b/pipenv/patched/notpip/_internal/index.py deleted file mode 100644 index ad145fe4..00000000 --- a/pipenv/patched/notpip/_internal/index.py +++ /dev/null @@ -1,1017 +0,0 @@ -"""Routines related to PyPI, indexes""" -from __future__ import absolute_import - -import cgi -import itertools -import logging -import mimetypes -import os -import posixpath -import re -import sys -from collections import namedtuple - -from pipenv.patched.notpip._vendor import html5lib, requests, six -from pipenv.patched.notpip._vendor.distlib.compat import unescape -from pipenv.patched.notpip._vendor.packaging import specifiers -from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name -from pipenv.patched.notpip._vendor.packaging.version import parse as parse_version -from pipenv.patched.notpip._vendor.requests.exceptions import RetryError, SSLError -from pipenv.patched.notpip._vendor.six.moves.urllib import parse as urllib_parse -from pipenv.patched.notpip._vendor.six.moves.urllib import request as urllib_request - -from pipenv.patched.notpip._internal.download import HAS_TLS, is_url, path_to_url, url_to_path -from pipenv.patched.notpip._internal.exceptions import ( - BestVersionAlreadyInstalled, DistributionNotFound, InvalidWheelFilename, - UnsupportedWheel, -) -from pipenv.patched.notpip._internal.models.candidate import InstallationCandidate -from pipenv.patched.notpip._internal.models.format_control import FormatControl -from pipenv.patched.notpip._internal.models.index import PyPI -from pipenv.patched.notpip._internal.models.link import Link -from pipenv.patched.notpip._internal.pep425tags import get_supported -from pipenv.patched.notpip._internal.utils.compat import ipaddress -from pipenv.patched.notpip._internal.utils.logging import indent_log -from pipenv.patched.notpip._internal.utils.misc import ( - ARCHIVE_EXTENSIONS, SUPPORTED_EXTENSIONS, WHEEL_EXTENSION, normalize_path, - redact_password_from_url, -) -from pipenv.patched.notpip._internal.utils.packaging import check_requires_python -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING -from pipenv.patched.notpip._internal.wheel import Wheel - -if MYPY_CHECK_RUNNING: - from logging import Logger # noqa: F401 - from typing import ( # noqa: F401 - Tuple, Optional, Any, List, Union, Callable, Set, Sequence, - Iterable, MutableMapping - ) - from pipenv.patched.notpip._vendor.packaging.version import _BaseVersion # noqa: F401 - from pipenv.patched.notpip._vendor.requests import Response # noqa: F401 - from pipenv.patched.notpip._internal.req import InstallRequirement # noqa: F401 - from pipenv.patched.notpip._internal.download import PipSession # noqa: F401 - - SecureOrigin = Tuple[str, str, Optional[str]] - BuildTag = Tuple[Any, ...] # either emply tuple or Tuple[int, str] - CandidateSortingKey = Tuple[int, _BaseVersion, BuildTag, Optional[int]] - -__all__ = ['FormatControl', 'PackageFinder'] - - -SECURE_ORIGINS = [ - # protocol, hostname, port - # Taken from Chrome's list of secure origins (See: http://bit.ly/1qrySKC) - ("https", "*", "*"), - ("*", "localhost", "*"), - ("*", "127.0.0.0/8", "*"), - ("*", "::1/128", "*"), - ("file", "*", None), - # ssh is always secure. - ("ssh", "*", "*"), -] # type: List[SecureOrigin] - - -logger = logging.getLogger(__name__) - - -def _match_vcs_scheme(url): - # type: (str) -> Optional[str] - """Look for VCS schemes in the URL. - - Returns the matched VCS scheme, or None if there's no match. - """ - from pipenv.patched.notpip._internal.vcs import VcsSupport - for scheme in VcsSupport.schemes: - if url.lower().startswith(scheme) and url[len(scheme)] in '+:': - return scheme - return None - - -def _is_url_like_archive(url): - # type: (str) -> bool - """Return whether the URL looks like an archive. - """ - filename = Link(url).filename - for bad_ext in ARCHIVE_EXTENSIONS: - if filename.endswith(bad_ext): - return True - return False - - -class _NotHTML(Exception): - def __init__(self, content_type, request_desc): - # type: (str, str) -> None - super(_NotHTML, self).__init__(content_type, request_desc) - self.content_type = content_type - self.request_desc = request_desc - - -def _ensure_html_header(response): - # type: (Response) -> None - """Check the Content-Type header to ensure the response contains HTML. - - Raises `_NotHTML` if the content type is not text/html. - """ - content_type = response.headers.get("Content-Type", "") - if not content_type.lower().startswith("text/html"): - raise _NotHTML(content_type, response.request.method) - - -class _NotHTTP(Exception): - pass - - -def _ensure_html_response(url, session): - # type: (str, PipSession) -> None - """Send a HEAD request to the URL, and ensure the response contains HTML. - - Raises `_NotHTTP` if the URL is not available for a HEAD request, or - `_NotHTML` if the content type is not text/html. - """ - scheme, netloc, path, query, fragment = urllib_parse.urlsplit(url) - if scheme not in {'http', 'https'}: - raise _NotHTTP() - - resp = session.head(url, allow_redirects=True) - resp.raise_for_status() - - _ensure_html_header(resp) - - -def _get_html_response(url, session): - # type: (str, PipSession) -> Response - """Access an HTML page with GET, and return the response. - - This consists of three parts: - - 1. If the URL looks suspiciously like an archive, send a HEAD first to - check the Content-Type is HTML, to avoid downloading a large file. - Raise `_NotHTTP` if the content type cannot be determined, or - `_NotHTML` if it is not HTML. - 2. Actually perform the request. Raise HTTP exceptions on network failures. - 3. Check the Content-Type header to make sure we got HTML, and raise - `_NotHTML` otherwise. - """ - if _is_url_like_archive(url): - _ensure_html_response(url, session=session) - - logger.debug('Getting page %s', url) - - resp = session.get( - url, - headers={ - "Accept": "text/html", - # We don't want to blindly returned cached data for - # /simple/, because authors generally expecting that - # twine upload && pip install will function, but if - # they've done a pip install in the last ~10 minutes - # it won't. Thus by setting this to zero we will not - # blindly use any cached data, however the benefit of - # using max-age=0 instead of no-cache, is that we will - # still support conditional requests, so we will still - # minimize traffic sent in cases where the page hasn't - # changed at all, we will just always incur the round - # trip for the conditional GET now instead of only - # once per 10 minutes. - # For more information, please see pypa/pip#5670. - "Cache-Control": "max-age=0", - }, - ) - resp.raise_for_status() - - # The check for archives above only works if the url ends with - # something that looks like an archive. However that is not a - # requirement of an url. Unless we issue a HEAD request on every - # url we cannot know ahead of time for sure if something is HTML - # or not. However we can check after we've downloaded it. - _ensure_html_header(resp) - - return resp - - -def _handle_get_page_fail( - link, # type: Link - reason, # type: Union[str, Exception] - meth=None # type: Optional[Callable[..., None]] -): - # type: (...) -> None - if meth is None: - meth = logger.debug - meth("Could not fetch URL %s: %s - skipping", link, reason) - - -def _get_html_page(link, session=None): - # type: (Link, Optional[PipSession]) -> Optional[HTMLPage] - if session is None: - raise TypeError( - "_get_html_page() missing 1 required keyword argument: 'session'" - ) - - url = link.url.split('#', 1)[0] - - # Check for VCS schemes that do not support lookup as web pages. - vcs_scheme = _match_vcs_scheme(url) - if vcs_scheme: - logger.debug('Cannot look at %s URL %s', vcs_scheme, link) - return None - - # Tack index.html onto file:// URLs that point to directories - scheme, _, path, _, _, _ = urllib_parse.urlparse(url) - if (scheme == 'file' and os.path.isdir(urllib_request.url2pathname(path))): - # add trailing slash if not present so urljoin doesn't trim - # final segment - if not url.endswith('/'): - url += '/' - url = urllib_parse.urljoin(url, 'index.html') - logger.debug(' file: URL is directory, getting %s', url) - - try: - resp = _get_html_response(url, session=session) - except _NotHTTP as exc: - logger.debug( - 'Skipping page %s because it looks like an archive, and cannot ' - 'be checked by HEAD.', link, - ) - except _NotHTML as exc: - logger.debug( - 'Skipping page %s because the %s request got Content-Type: %s', - link, exc.request_desc, exc.content_type, - ) - except requests.HTTPError as exc: - _handle_get_page_fail(link, exc) - except RetryError as exc: - _handle_get_page_fail(link, exc) - except SSLError as exc: - reason = "There was a problem confirming the ssl certificate: " - reason += str(exc) - _handle_get_page_fail(link, reason, meth=logger.info) - except requests.ConnectionError as exc: - _handle_get_page_fail(link, "connection error: %s" % exc) - except requests.Timeout: - _handle_get_page_fail(link, "timed out") - else: - return HTMLPage(resp.content, resp.url, resp.headers) - return None - - -class PackageFinder(object): - """This finds packages. - - This is meant to match easy_install's technique for looking for - packages, by reading pages and looking for appropriate links. - """ - - def __init__( - self, - find_links, # type: List[str] - index_urls, # type: List[str] - allow_all_prereleases=False, # type: bool - trusted_hosts=None, # type: Optional[Iterable[str]] - session=None, # type: Optional[PipSession] - format_control=None, # type: Optional[FormatControl] - platform=None, # type: Optional[str] - versions=None, # type: Optional[List[str]] - abi=None, # type: Optional[str] - implementation=None, # type: Optional[str] - prefer_binary=False # type: bool - ): - # type: (...) -> None - """Create a PackageFinder. - - :param format_control: A FormatControl object or None. Used to control - the selection of source packages / binary packages when consulting - the index and links. - :param platform: A string or None. If None, searches for packages - that are supported by the current system. Otherwise, will find - packages that can be built on the platform passed in. These - packages will only be downloaded for distribution: they will - not be built locally. - :param versions: A list of strings or None. This is passed directly - to pep425tags.py in the get_supported() method. - :param abi: A string or None. This is passed directly - to pep425tags.py in the get_supported() method. - :param implementation: A string or None. This is passed directly - to pep425tags.py in the get_supported() method. - """ - if session is None: - raise TypeError( - "PackageFinder() missing 1 required keyword argument: " - "'session'" - ) - - # Build find_links. If an argument starts with ~, it may be - # a local file relative to a home directory. So try normalizing - # it and if it exists, use the normalized version. - # This is deliberately conservative - it might be fine just to - # blindly normalize anything starting with a ~... - self.find_links = [] # type: List[str] - for link in find_links: - if link.startswith('~'): - new_link = normalize_path(link) - if os.path.exists(new_link): - link = new_link - self.find_links.append(link) - - self.index_urls = index_urls - - # These are boring links that have already been logged somehow: - self.logged_links = set() # type: Set[Link] - - self.format_control = format_control or FormatControl(set(), set()) - - # Domains that we won't emit warnings for when not using HTTPS - self.secure_origins = [ - ("*", host, "*") - for host in (trusted_hosts if trusted_hosts else []) - ] # type: List[SecureOrigin] - - # Do we want to allow _all_ pre-releases? - self.allow_all_prereleases = allow_all_prereleases - - # The Session we'll use to make requests - self.session = session - - # Kenneth's Hack - self.extra = None - - # The valid tags to check potential found wheel candidates against - self.valid_tags = get_supported( - versions=versions, - platform=platform, - abi=abi, - impl=implementation, - ) - - # Do we prefer old, but valid, binary dist over new source dist - self.prefer_binary = prefer_binary - - # If we don't have TLS enabled, then WARN if anyplace we're looking - # relies on TLS. - if not HAS_TLS: - for link in itertools.chain(self.index_urls, self.find_links): - parsed = urllib_parse.urlparse(link) - if parsed.scheme == "https": - logger.warning( - "pip is configured with locations that require " - "TLS/SSL, however the ssl module in Python is not " - "available." - ) - break - - def get_formatted_locations(self): - # type: () -> str - lines = [] - if self.index_urls and self.index_urls != [PyPI.simple_url]: - lines.append( - "Looking in indexes: {}".format(", ".join( - redact_password_from_url(url) for url in self.index_urls)) - ) - if self.find_links: - lines.append( - "Looking in links: {}".format(", ".join(self.find_links)) - ) - return "\n".join(lines) - - @staticmethod - def get_extras_links(links): - requires = [] - extras = {} - - current_list = requires - - for link in links: - if not link: - current_list = requires - if link.startswith('['): - current_list = [] - extras[link[1:-1]] = current_list - else: - current_list.append(link) - return extras - - @staticmethod - def _sort_locations(locations, expand_dir=False): - # type: (Sequence[str], bool) -> Tuple[List[str], List[str]] - """ - Sort locations into "files" (archives) and "urls", and return - a pair of lists (files,urls) - """ - files = [] - urls = [] - - # puts the url for the given file path into the appropriate list - def sort_path(path): - url = path_to_url(path) - if mimetypes.guess_type(url, strict=False)[0] == 'text/html': - urls.append(url) - else: - files.append(url) - - for url in locations: - - is_local_path = os.path.exists(url) - is_file_url = url.startswith('file:') - - if is_local_path or is_file_url: - if is_local_path: - path = url - else: - path = url_to_path(url) - if os.path.isdir(path): - if expand_dir: - path = os.path.realpath(path) - for item in os.listdir(path): - sort_path(os.path.join(path, item)) - elif is_file_url: - urls.append(url) - else: - logger.warning( - "Path '{0}' is ignored: " - "it is a directory.".format(path), - ) - elif os.path.isfile(path): - sort_path(path) - else: - logger.warning( - "Url '%s' is ignored: it is neither a file " - "nor a directory.", url, - ) - elif is_url(url): - # Only add url with clear scheme - urls.append(url) - else: - logger.warning( - "Url '%s' is ignored. It is either a non-existing " - "path or lacks a specific scheme.", url, - ) - - return files, urls - - def _candidate_sort_key(self, candidate, ignore_compatibility=True): - # type: (InstallationCandidate, bool) -> CandidateSortingKey - """ - Function used to generate link sort key for link tuples. - The greater the return value, the more preferred it is. - If not finding wheels, then sorted by version only. - If finding wheels, then the sort order is by version, then: - 1. existing installs - 2. wheels ordered via Wheel.support_index_min(self.valid_tags) - 3. source archives - If prefer_binary was set, then all wheels are sorted above sources. - Note: it was considered to embed this logic into the Link - comparison operators, but then different sdist links - with the same version, would have to be considered equal - """ - support_num = len(self.valid_tags) - build_tag = tuple() # type: BuildTag - binary_preference = 0 - if candidate.location.is_wheel: - # can raise InvalidWheelFilename - wheel = Wheel(candidate.location.filename) - if not wheel.supported(self.valid_tags) and not ignore_compatibility: - raise UnsupportedWheel( - "%s is not a supported wheel for this platform. It " - "can't be sorted." % wheel.filename - ) - if self.prefer_binary: - binary_preference = 1 - tags = self.valid_tags if not ignore_compatibility else None - try: - pri = -(wheel.support_index_min(tags=tags)) - except TypeError: - pri = -(support_num) - if wheel.build_tag is not None: - match = re.match(r'^(\d+)(.*)$', wheel.build_tag) - build_tag_groups = match.groups() - build_tag = (int(build_tag_groups[0]), build_tag_groups[1]) - else: # sdist - pri = -(support_num) - return (binary_preference, candidate.version, build_tag, pri) - - def _validate_secure_origin(self, logger, location): - # type: (Logger, Link) -> bool - # Determine if this url used a secure transport mechanism - parsed = urllib_parse.urlparse(str(location)) - origin = (parsed.scheme, parsed.hostname, parsed.port) - - # The protocol to use to see if the protocol matches. - # Don't count the repository type as part of the protocol: in - # cases such as "git+ssh", only use "ssh". (I.e., Only verify against - # the last scheme.) - protocol = origin[0].rsplit('+', 1)[-1] - - # Determine if our origin is a secure origin by looking through our - # hardcoded list of secure origins, as well as any additional ones - # configured on this PackageFinder instance. - for secure_origin in (SECURE_ORIGINS + self.secure_origins): - if protocol != secure_origin[0] and secure_origin[0] != "*": - continue - - try: - # We need to do this decode dance to ensure that we have a - # unicode object, even on Python 2.x. - addr = ipaddress.ip_address( - origin[1] - if ( - isinstance(origin[1], six.text_type) or - origin[1] is None - ) - else origin[1].decode("utf8") - ) - network = ipaddress.ip_network( - secure_origin[1] - if isinstance(secure_origin[1], six.text_type) - # setting secure_origin[1] to proper Union[bytes, str] - # creates problems in other places - else secure_origin[1].decode("utf8") # type: ignore - ) - except ValueError: - # We don't have both a valid address or a valid network, so - # we'll check this origin against hostnames. - if (origin[1] and - origin[1].lower() != secure_origin[1].lower() and - secure_origin[1] != "*"): - continue - else: - # We have a valid address and network, so see if the address - # is contained within the network. - if addr not in network: - continue - - # Check to see if the port patches - if (origin[2] != secure_origin[2] and - secure_origin[2] != "*" and - secure_origin[2] is not None): - continue - - # If we've gotten here, then this origin matches the current - # secure origin and we should return True - return True - - # If we've gotten to this point, then the origin isn't secure and we - # will not accept it as a valid location to search. We will however - # log a warning that we are ignoring it. - logger.warning( - "The repository located at %s is not a trusted or secure host and " - "is being ignored. If this repository is available via HTTPS we " - "recommend you use HTTPS instead, otherwise you may silence " - "this warning and allow it anyway with '--trusted-host %s'.", - parsed.hostname, - parsed.hostname, - ) - - return False - - def _get_index_urls_locations(self, project_name): - # type: (str) -> List[str] - """Returns the locations found via self.index_urls - - Checks the url_name on the main (first in the list) index and - use this url_name to produce all locations - """ - - def mkurl_pypi_url(url): - loc = posixpath.join( - url, - urllib_parse.quote(canonicalize_name(project_name))) - # For maximum compatibility with easy_install, ensure the path - # ends in a trailing slash. Although this isn't in the spec - # (and PyPI can handle it without the slash) some other index - # implementations might break if they relied on easy_install's - # behavior. - if not loc.endswith('/'): - loc = loc + '/' - return loc - - return [mkurl_pypi_url(url) for url in self.index_urls] - - def find_all_candidates(self, project_name): - # type: (str) -> List[Optional[InstallationCandidate]] - """Find all available InstallationCandidate for project_name - - This checks index_urls and find_links. - All versions found are returned as an InstallationCandidate list. - - See _link_package_versions for details on which files are accepted - """ - index_locations = self._get_index_urls_locations(project_name) - index_file_loc, index_url_loc = self._sort_locations(index_locations) - fl_file_loc, fl_url_loc = self._sort_locations( - self.find_links, expand_dir=True, - ) - - file_locations = (Link(url) for url in itertools.chain( - index_file_loc, fl_file_loc, - )) - - # We trust every url that the user has given us whether it was given - # via --index-url or --find-links. - # We want to filter out any thing which does not have a secure origin. - url_locations = [ - link for link in itertools.chain( - (Link(url) for url in index_url_loc), - (Link(url) for url in fl_url_loc), - ) - if self._validate_secure_origin(logger, link) - ] - - logger.debug('%d location(s) to search for versions of %s:', - len(url_locations), project_name) - - for location in url_locations: - logger.debug('* %s', location) - - canonical_name = canonicalize_name(project_name) - formats = self.format_control.get_allowed_formats(canonical_name) - search = Search(project_name, canonical_name, formats) - find_links_versions = self._package_versions( - # We trust every directly linked archive in find_links - (Link(url, '-f') for url in self.find_links), - search - ) - - page_versions = [] - for page in self._get_pages(url_locations, project_name): - try: - logger.debug('Analyzing links from page %s', page.url) - except AttributeError: - continue - with indent_log(): - page_versions.extend( - self._package_versions(page.iter_links(), search) - ) - - file_versions = self._package_versions(file_locations, search) - if file_versions: - file_versions.sort(reverse=True) - logger.debug( - 'Local files found: %s', - ', '.join([ - url_to_path(candidate.location.url) - for candidate in file_versions - ]) - ) - - # This is an intentional priority ordering - return file_versions + find_links_versions + page_versions - - def find_requirement(self, req, upgrade, ignore_compatibility=False): - # type: (InstallRequirement, bool, bool) -> Optional[Link] - """Try to find a Link matching req - - Expects req, an InstallRequirement and upgrade, a boolean - Returns a Link if found, - Raises DistributionNotFound or BestVersionAlreadyInstalled otherwise - """ - all_candidates = self.find_all_candidates(req.name) - - # Filter out anything which doesn't match our specifier - compatible_versions = set( - req.specifier.filter( - # We turn the version object into a str here because otherwise - # when we're debundled but setuptools isn't, Python will see - # packaging.version.Version and - # pkg_resources._vendor.packaging.version.Version as different - # types. This way we'll use a str as a common data interchange - # format. If we stop using the pkg_resources provided specifier - # and start using our own, we can drop the cast to str(). - [str(c.version) for c in all_candidates], - prereleases=( - self.allow_all_prereleases - if self.allow_all_prereleases else None - ), - ) - ) - applicable_candidates = [ - # Again, converting to str to deal with debundling. - c for c in all_candidates if str(c.version) in compatible_versions - ] - - if applicable_candidates: - best_candidate = max(applicable_candidates, - key=self._candidate_sort_key) - else: - best_candidate = None - - if req.satisfied_by is not None: - installed_version = parse_version(req.satisfied_by.version) - else: - installed_version = None - - if installed_version is None and best_candidate is None: - logger.critical( - 'Could not find a version that satisfies the requirement %s ' - '(from versions: %s)', - req, - ', '.join( - sorted( - {str(c.version) for c in all_candidates}, - key=parse_version, - ) - ) - ) - - raise DistributionNotFound( - 'No matching distribution found for %s' % req - ) - - best_installed = False - if installed_version and ( - best_candidate is None or - best_candidate.version <= installed_version): - best_installed = True - - if not upgrade and installed_version is not None: - if best_installed: - logger.debug( - 'Existing installed version (%s) is most up-to-date and ' - 'satisfies requirement', - installed_version, - ) - else: - logger.debug( - 'Existing installed version (%s) satisfies requirement ' - '(most up-to-date version is %s)', - installed_version, - best_candidate.version, - ) - return None - - if best_installed: - # We have an existing version, and its the best version - logger.debug( - 'Installed version (%s) is most up-to-date (past versions: ' - '%s)', - installed_version, - ', '.join(sorted(compatible_versions, key=parse_version)) or - "none", - ) - raise BestVersionAlreadyInstalled - - logger.debug( - 'Using version %s (newest of versions: %s)', - best_candidate.version, - ', '.join(sorted(compatible_versions, key=parse_version)) - ) - return best_candidate.location - - def _get_pages(self, locations, project_name): - # type: (Iterable[Link], str) -> Iterable[HTMLPage] - """ - Yields (page, page_url) from the given locations, skipping - locations that have errors. - """ - seen = set() # type: Set[Link] - for location in locations: - if location in seen: - continue - seen.add(location) - - page = _get_html_page(location, session=self.session) - if page is None: - continue - - yield page - - _py_version_re = re.compile(r'-py([123]\.?[0-9]?)$') - - def _sort_links(self, links): - # type: (Iterable[Link]) -> List[Link] - """ - Returns elements of links in order, non-egg links first, egg links - second, while eliminating duplicates - """ - eggs, no_eggs = [], [] - seen = set() # type: Set[Link] - for link in links: - if link not in seen: - seen.add(link) - if link.egg_fragment: - eggs.append(link) - else: - no_eggs.append(link) - return no_eggs + eggs - - def _package_versions( - self, - links, # type: Iterable[Link] - search # type: Search - ): - # type: (...) -> List[Optional[InstallationCandidate]] - result = [] - for link in self._sort_links(links): - v = self._link_package_versions(link, search) - if v is not None: - result.append(v) - return result - - def _log_skipped_link(self, link, reason): - # type: (Link, str) -> None - if link not in self.logged_links: - logger.debug('Skipping link %s; %s', link, reason) - self.logged_links.add(link) - - def _link_package_versions(self, link, search, ignore_compatibility=True): - # type: (Link, Search, bool) -> Optional[InstallationCandidate] - """Return an InstallationCandidate or None""" - version = None - if link.egg_fragment: - egg_info = link.egg_fragment - ext = link.ext - else: - egg_info, ext = link.splitext() - if not ext: - self._log_skipped_link(link, 'not a file') - return None - if ext not in SUPPORTED_EXTENSIONS: - self._log_skipped_link( - link, 'unsupported archive format: %s' % ext, - ) - return None - if "binary" not in search.formats and ext == WHEEL_EXTENSION and not ignore_compatibility: - self._log_skipped_link( - link, 'No binaries permitted for %s' % search.supplied, - ) - return None - if "macosx10" in link.path and ext == '.zip' and not ignore_compatibility: - self._log_skipped_link(link, 'macosx10 one') - return None - if ext == WHEEL_EXTENSION: - try: - wheel = Wheel(link.filename) - except InvalidWheelFilename: - self._log_skipped_link(link, 'invalid wheel filename') - return None - if canonicalize_name(wheel.name) != search.canonical: - self._log_skipped_link( - link, 'wrong project name (not %s)' % search.supplied) - return None - - if not wheel.supported(self.valid_tags) and not ignore_compatibility: - self._log_skipped_link( - link, 'it is not compatible with this Python') - return None - - version = wheel.version - - # This should be up by the search.ok_binary check, but see issue 2700. - if "source" not in search.formats and ext != WHEEL_EXTENSION: - self._log_skipped_link( - link, 'No sources permitted for %s' % search.supplied, - ) - return None - - if not version: - version = _egg_info_matches(egg_info, search.canonical) - if not version: - self._log_skipped_link( - link, 'Missing project version for %s' % search.supplied) - return None - - match = self._py_version_re.search(version) - if match: - version = version[:match.start()] - py_version = match.group(1) - if py_version != sys.version[:3]: - self._log_skipped_link( - link, 'Python version is incorrect') - return None - try: - support_this_python = check_requires_python(link.requires_python) - except specifiers.InvalidSpecifier: - logger.debug("Package %s has an invalid Requires-Python entry: %s", - link.filename, link.requires_python) - support_this_python = True - - if not support_this_python and not ignore_compatibility: - logger.debug("The package %s is incompatible with the python " - "version in use. Acceptable python versions are: %s", - link, link.requires_python) - return None - logger.debug('Found link %s, version: %s', link, version) - - return InstallationCandidate(search.supplied, version, link, link.requires_python) - - -def _find_name_version_sep(egg_info, canonical_name): - # type: (str, str) -> int - """Find the separator's index based on the package's canonical name. - - `egg_info` must be an egg info string for the given package, and - `canonical_name` must be the package's canonical name. - - This function is needed since the canonicalized name does not necessarily - have the same length as the egg info's name part. An example:: - - >>> egg_info = 'foo__bar-1.0' - >>> canonical_name = 'foo-bar' - >>> _find_name_version_sep(egg_info, canonical_name) - 8 - """ - # Project name and version must be separated by one single dash. Find all - # occurrences of dashes; if the string in front of it matches the canonical - # name, this is the one separating the name and version parts. - for i, c in enumerate(egg_info): - if c != "-": - continue - if canonicalize_name(egg_info[:i]) == canonical_name: - return i - raise ValueError("{} does not match {}".format(egg_info, canonical_name)) - - -def _egg_info_matches(egg_info, canonical_name): - # type: (str, str) -> Optional[str] - """Pull the version part out of a string. - - :param egg_info: The string to parse. E.g. foo-2.1 - :param canonical_name: The canonicalized name of the package this - belongs to. - """ - try: - version_start = _find_name_version_sep(egg_info, canonical_name) + 1 - except ValueError: - return None - version = egg_info[version_start:] - if not version: - return None - return version - - -def _determine_base_url(document, page_url): - """Determine the HTML document's base URL. - - This looks for a ```` tag in the HTML document. If present, its href - attribute denotes the base URL of anchor tags in the document. If there is - no such tag (or if it does not have a valid href attribute), the HTML - file's URL is used as the base URL. - - :param document: An HTML document representation. The current - implementation expects the result of ``html5lib.parse()``. - :param page_url: The URL of the HTML document. - """ - for base in document.findall(".//base"): - href = base.get("href") - if href is not None: - return href - return page_url - - -def _get_encoding_from_headers(headers): - """Determine if we have any encoding information in our headers. - """ - if headers and "Content-Type" in headers: - content_type, params = cgi.parse_header(headers["Content-Type"]) - if "charset" in params: - return params['charset'] - return None - - -_CLEAN_LINK_RE = re.compile(r'[^a-z0-9$&+,/:;=?@.#%_\\|-]', re.I) - - -def _clean_link(url): - # type: (str) -> str - """Makes sure a link is fully encoded. That is, if a ' ' shows up in - the link, it will be rewritten to %20 (while not over-quoting - % or other characters).""" - return _CLEAN_LINK_RE.sub(lambda match: '%%%2x' % ord(match.group(0)), url) - - -class HTMLPage(object): - """Represents one page, along with its URL""" - - def __init__(self, content, url, headers=None): - # type: (bytes, str, MutableMapping[str, str]) -> None - self.content = content - self.url = url - self.headers = headers - - def __str__(self): - return redact_password_from_url(self.url) - - def iter_links(self): - # type: () -> Iterable[Link] - """Yields all links in the page""" - document = html5lib.parse( - self.content, - transport_encoding=_get_encoding_from_headers(self.headers), - namespaceHTMLElements=False, - ) - base_url = _determine_base_url(document, self.url) - for anchor in document.findall(".//a"): - if anchor.get("href"): - href = anchor.get("href") - url = _clean_link(urllib_parse.urljoin(base_url, href)) - pyrequire = anchor.get('data-requires-python') - pyrequire = unescape(pyrequire) if pyrequire else None - yield Link(url, self.url, requires_python=pyrequire) - - -Search = namedtuple('Search', 'supplied canonical formats') -"""Capture key aspects of a search. - -:attribute supplied: The user supplied package. -:attribute canonical: The canonical package name. -:attribute formats: The formats allowed for this package. Should be a set - with 'binary' or 'source' or both in it. -""" diff --git a/pipenv/patched/notpip/_internal/index/__init__.py b/pipenv/patched/notpip/_internal/index/__init__.py new file mode 100644 index 00000000..7a17b7b3 --- /dev/null +++ b/pipenv/patched/notpip/_internal/index/__init__.py @@ -0,0 +1,2 @@ +"""Index interaction code +""" diff --git a/pipenv/patched/notpip/_internal/index/collector.py b/pipenv/patched/notpip/_internal/index/collector.py new file mode 100644 index 00000000..98ac9d2e --- /dev/null +++ b/pipenv/patched/notpip/_internal/index/collector.py @@ -0,0 +1,534 @@ +""" +The main purpose of this module is to expose LinkCollector.collect_sources(). +""" + +import cgi +import collections +import functools +import html +import itertools +import logging +import os +import re +import urllib.parse +import urllib.request +import xml.etree.ElementTree +from optparse import Values +from typing import ( + Callable, + Iterable, + List, + MutableMapping, + NamedTuple, + Optional, + Sequence, + Union, +) + +from pipenv.patched.notpip._vendor import html5lib, requests +from pipenv.patched.notpip._vendor.requests import Response +from pipenv.patched.notpip._vendor.requests.exceptions import RetryError, SSLError + +from pipenv.patched.notpip._internal.exceptions import NetworkConnectionError +from pipenv.patched.notpip._internal.models.link import Link +from pipenv.patched.notpip._internal.models.search_scope import SearchScope +from pipenv.patched.notpip._internal.network.session import PipSession +from pipenv.patched.notpip._internal.network.utils import raise_for_status +from pipenv.patched.notpip._internal.utils.filetypes import is_archive_file +from pipenv.patched.notpip._internal.utils.misc import pairwise, redact_auth_from_url +from pipenv.patched.notpip._internal.vcs import vcs + +from .sources import CandidatesFromPage, LinkSource, build_source + +logger = logging.getLogger(__name__) + +HTMLElement = xml.etree.ElementTree.Element +ResponseHeaders = MutableMapping[str, str] + + +def _match_vcs_scheme(url: str) -> Optional[str]: + """Look for VCS schemes in the URL. + + Returns the matched VCS scheme, or None if there's no match. + """ + for scheme in vcs.schemes: + if url.lower().startswith(scheme) and url[len(scheme)] in '+:': + return scheme + return None + + +class _NotHTML(Exception): + def __init__(self, content_type: str, request_desc: str) -> None: + super().__init__(content_type, request_desc) + self.content_type = content_type + self.request_desc = request_desc + + +def _ensure_html_header(response: Response) -> None: + """Check the Content-Type header to ensure the response contains HTML. + + Raises `_NotHTML` if the content type is not text/html. + """ + content_type = response.headers.get("Content-Type", "") + if not content_type.lower().startswith("text/html"): + raise _NotHTML(content_type, response.request.method) + + +class _NotHTTP(Exception): + pass + + +def _ensure_html_response(url: str, session: PipSession) -> None: + """Send a HEAD request to the URL, and ensure the response contains HTML. + + Raises `_NotHTTP` if the URL is not available for a HEAD request, or + `_NotHTML` if the content type is not text/html. + """ + scheme, netloc, path, query, fragment = urllib.parse.urlsplit(url) + if scheme not in {'http', 'https'}: + raise _NotHTTP() + + resp = session.head(url, allow_redirects=True) + raise_for_status(resp) + + _ensure_html_header(resp) + + +def _get_html_response(url: str, session: PipSession) -> Response: + """Access an HTML page with GET, and return the response. + + This consists of three parts: + + 1. If the URL looks suspiciously like an archive, send a HEAD first to + check the Content-Type is HTML, to avoid downloading a large file. + Raise `_NotHTTP` if the content type cannot be determined, or + `_NotHTML` if it is not HTML. + 2. Actually perform the request. Raise HTTP exceptions on network failures. + 3. Check the Content-Type header to make sure we got HTML, and raise + `_NotHTML` otherwise. + """ + if is_archive_file(Link(url).filename): + _ensure_html_response(url, session=session) + + logger.debug('Getting page %s', redact_auth_from_url(url)) + + resp = session.get( + url, + headers={ + "Accept": "text/html", + # We don't want to blindly returned cached data for + # /simple/, because authors generally expecting that + # twine upload && pip install will function, but if + # they've done a pip install in the last ~10 minutes + # it won't. Thus by setting this to zero we will not + # blindly use any cached data, however the benefit of + # using max-age=0 instead of no-cache, is that we will + # still support conditional requests, so we will still + # minimize traffic sent in cases where the page hasn't + # changed at all, we will just always incur the round + # trip for the conditional GET now instead of only + # once per 10 minutes. + # For more information, please see pypa/pip#5670. + "Cache-Control": "max-age=0", + }, + ) + raise_for_status(resp) + + # The check for archives above only works if the url ends with + # something that looks like an archive. However that is not a + # requirement of an url. Unless we issue a HEAD request on every + # url we cannot know ahead of time for sure if something is HTML + # or not. However we can check after we've downloaded it. + _ensure_html_header(resp) + + return resp + + +def _get_encoding_from_headers(headers: ResponseHeaders) -> Optional[str]: + """Determine if we have any encoding information in our headers. + """ + if headers and "Content-Type" in headers: + content_type, params = cgi.parse_header(headers["Content-Type"]) + if "charset" in params: + return params['charset'] + return None + + +def _determine_base_url(document: HTMLElement, page_url: str) -> str: + """Determine the HTML document's base URL. + + This looks for a ```` tag in the HTML document. If present, its href + attribute denotes the base URL of anchor tags in the document. If there is + no such tag (or if it does not have a valid href attribute), the HTML + file's URL is used as the base URL. + + :param document: An HTML document representation. The current + implementation expects the result of ``html5lib.parse()``. + :param page_url: The URL of the HTML document. + """ + for base in document.findall(".//base"): + href = base.get("href") + if href is not None: + return href + return page_url + + +def _clean_url_path_part(part: str) -> str: + """ + Clean a "part" of a URL path (i.e. after splitting on "@" characters). + """ + # We unquote prior to quoting to make sure nothing is double quoted. + return urllib.parse.quote(urllib.parse.unquote(part)) + + +def _clean_file_url_path(part: str) -> str: + """ + Clean the first part of a URL path that corresponds to a local + filesystem path (i.e. the first part after splitting on "@" characters). + """ + # We unquote prior to quoting to make sure nothing is double quoted. + # Also, on Windows the path part might contain a drive letter which + # should not be quoted. On Linux where drive letters do not + # exist, the colon should be quoted. We rely on urllib.request + # to do the right thing here. + return urllib.request.pathname2url(urllib.request.url2pathname(part)) + + +# percent-encoded: / +_reserved_chars_re = re.compile('(@|%2F)', re.IGNORECASE) + + +def _clean_url_path(path: str, is_local_path: bool) -> str: + """ + Clean the path portion of a URL. + """ + if is_local_path: + clean_func = _clean_file_url_path + else: + clean_func = _clean_url_path_part + + # Split on the reserved characters prior to cleaning so that + # revision strings in VCS URLs are properly preserved. + parts = _reserved_chars_re.split(path) + + cleaned_parts = [] + for to_clean, reserved in pairwise(itertools.chain(parts, [''])): + cleaned_parts.append(clean_func(to_clean)) + # Normalize %xx escapes (e.g. %2f -> %2F) + cleaned_parts.append(reserved.upper()) + + return ''.join(cleaned_parts) + + +def _clean_link(url: str) -> str: + """ + Make sure a link is fully quoted. + For example, if ' ' occurs in the URL, it will be replaced with "%20", + and without double-quoting other characters. + """ + # Split the URL into parts according to the general structure + # `scheme://netloc/path;parameters?query#fragment`. + result = urllib.parse.urlparse(url) + # If the netloc is empty, then the URL refers to a local filesystem path. + is_local_path = not result.netloc + path = _clean_url_path(result.path, is_local_path=is_local_path) + return urllib.parse.urlunparse(result._replace(path=path)) + + +def _create_link_from_element( + anchor: HTMLElement, + page_url: str, + base_url: str, +) -> Optional[Link]: + """ + Convert an anchor element in a simple repository page to a Link. + """ + href = anchor.get("href") + if not href: + return None + + url = _clean_link(urllib.parse.urljoin(base_url, href)) + pyrequire = anchor.get('data-requires-python') + pyrequire = html.unescape(pyrequire) if pyrequire else None + + yanked_reason = anchor.get('data-yanked') + if yanked_reason: + yanked_reason = html.unescape(yanked_reason) + + link = Link( + url, + comes_from=page_url, + requires_python=pyrequire, + yanked_reason=yanked_reason, + ) + + return link + + +class CacheablePageContent: + def __init__(self, page: "HTMLPage") -> None: + assert page.cache_link_parsing + self.page = page + + def __eq__(self, other: object) -> bool: + return (isinstance(other, type(self)) and + self.page.url == other.page.url) + + def __hash__(self) -> int: + return hash(self.page.url) + + +def with_cached_html_pages( + fn: Callable[["HTMLPage"], Iterable[Link]], +) -> Callable[["HTMLPage"], List[Link]]: + """ + Given a function that parses an Iterable[Link] from an HTMLPage, cache the + function's result (keyed by CacheablePageContent), unless the HTMLPage + `page` has `page.cache_link_parsing == False`. + """ + + @functools.lru_cache(maxsize=None) + def wrapper(cacheable_page: CacheablePageContent) -> List[Link]: + return list(fn(cacheable_page.page)) + + @functools.wraps(fn) + def wrapper_wrapper(page: "HTMLPage") -> List[Link]: + if page.cache_link_parsing: + return wrapper(CacheablePageContent(page)) + return list(fn(page)) + + return wrapper_wrapper + + +@with_cached_html_pages +def parse_links(page: "HTMLPage") -> Iterable[Link]: + """ + Parse an HTML document, and yield its anchor elements as Link objects. + """ + document = html5lib.parse( + page.content, + transport_encoding=page.encoding, + namespaceHTMLElements=False, + ) + + url = page.url + base_url = _determine_base_url(document, url) + for anchor in document.findall(".//a"): + link = _create_link_from_element( + anchor, + page_url=url, + base_url=base_url, + ) + if link is None: + continue + yield link + + +class HTMLPage: + """Represents one page, along with its URL""" + + def __init__( + self, + content: bytes, + encoding: Optional[str], + url: str, + cache_link_parsing: bool = True, + ) -> None: + """ + :param encoding: the encoding to decode the given content. + :param url: the URL from which the HTML was downloaded. + :param cache_link_parsing: whether links parsed from this page's url + should be cached. PyPI index urls should + have this set to False, for example. + """ + self.content = content + self.encoding = encoding + self.url = url + self.cache_link_parsing = cache_link_parsing + + def __str__(self) -> str: + return redact_auth_from_url(self.url) + + +def _handle_get_page_fail( + link: Link, + reason: Union[str, Exception], + meth: Optional[Callable[..., None]] = None +) -> None: + if meth is None: + meth = logger.debug + meth("Could not fetch URL %s: %s - skipping", link, reason) + + +def _make_html_page(response: Response, cache_link_parsing: bool = True) -> HTMLPage: + encoding = _get_encoding_from_headers(response.headers) + return HTMLPage( + response.content, + encoding=encoding, + url=response.url, + cache_link_parsing=cache_link_parsing) + + +def _get_html_page( + link: Link, session: Optional[PipSession] = None +) -> Optional["HTMLPage"]: + if session is None: + raise TypeError( + "_get_html_page() missing 1 required keyword argument: 'session'" + ) + + url = link.url.split('#', 1)[0] + + # Check for VCS schemes that do not support lookup as web pages. + vcs_scheme = _match_vcs_scheme(url) + if vcs_scheme: + logger.warning('Cannot look at %s URL %s because it does not support ' + 'lookup as web pages.', vcs_scheme, link) + return None + + # Tack index.html onto file:// URLs that point to directories + scheme, _, path, _, _, _ = urllib.parse.urlparse(url) + if (scheme == 'file' and os.path.isdir(urllib.request.url2pathname(path))): + # add trailing slash if not present so urljoin doesn't trim + # final segment + if not url.endswith('/'): + url += '/' + url = urllib.parse.urljoin(url, 'index.html') + logger.debug(' file: URL is directory, getting %s', url) + + try: + resp = _get_html_response(url, session=session) + except _NotHTTP: + logger.warning( + 'Skipping page %s because it looks like an archive, and cannot ' + 'be checked by a HTTP HEAD request.', link, + ) + except _NotHTML as exc: + logger.warning( + 'Skipping page %s because the %s request got Content-Type: %s.' + 'The only supported Content-Type is text/html', + link, exc.request_desc, exc.content_type, + ) + except NetworkConnectionError as exc: + _handle_get_page_fail(link, exc) + except RetryError as exc: + _handle_get_page_fail(link, exc) + except SSLError as exc: + reason = "There was a problem confirming the ssl certificate: " + reason += str(exc) + _handle_get_page_fail(link, reason, meth=logger.info) + except requests.ConnectionError as exc: + _handle_get_page_fail(link, f"connection error: {exc}") + except requests.Timeout: + _handle_get_page_fail(link, "timed out") + else: + return _make_html_page(resp, + cache_link_parsing=link.cache_link_parsing) + return None + + +class CollectedSources(NamedTuple): + find_links: Sequence[Optional[LinkSource]] + index_urls: Sequence[Optional[LinkSource]] + + +class LinkCollector: + + """ + Responsible for collecting Link objects from all configured locations, + making network requests as needed. + + The class's main method is its collect_sources() method. + """ + + def __init__( + self, + session: PipSession, + search_scope: SearchScope, + ) -> None: + self.search_scope = search_scope + self.session = session + + @classmethod + def create( + cls, session: PipSession, + options: Values, + suppress_no_index: bool = False + ) -> "LinkCollector": + """ + :param session: The Session to use to make requests. + :param suppress_no_index: Whether to ignore the --no-index option + when constructing the SearchScope object. + """ + index_urls = [options.index_url] + options.extra_index_urls + if options.no_index and not suppress_no_index: + logger.debug( + 'Ignoring indexes: %s', + ','.join(redact_auth_from_url(url) for url in index_urls), + ) + index_urls = [] + + # Make sure find_links is a list before passing to create(). + find_links = options.find_links or [] + + search_scope = SearchScope.create( + find_links=find_links, index_urls=index_urls, + ) + link_collector = LinkCollector( + session=session, search_scope=search_scope, + ) + return link_collector + + @property + def find_links(self) -> List[str]: + return self.search_scope.find_links + + def fetch_page(self, location: Link) -> Optional[HTMLPage]: + """ + Fetch an HTML page containing package links. + """ + return _get_html_page(location, session=self.session) + + def collect_sources( + self, + project_name: str, + candidates_from_page: CandidatesFromPage, + ) -> CollectedSources: + # The OrderedDict calls deduplicate sources by URL. + index_url_sources = collections.OrderedDict( + build_source( + loc, + candidates_from_page=candidates_from_page, + page_validator=self.session.is_secure_origin, + expand_dir=False, + cache_link_parsing=False, + ) + for loc in self.search_scope.get_index_urls_locations(project_name) + ).values() + find_links_sources = collections.OrderedDict( + build_source( + loc, + candidates_from_page=candidates_from_page, + page_validator=self.session.is_secure_origin, + expand_dir=True, + cache_link_parsing=True, + ) + for loc in self.find_links + ).values() + + if logger.isEnabledFor(logging.DEBUG): + lines = [ + f"* {s.link}" + for s in itertools.chain(find_links_sources, index_url_sources) + if s is not None and s.link is not None + ] + lines = [ + f"{len(lines)} location(s) to search " + f"for versions of {project_name}:" + ] + lines + logger.debug("\n".join(lines)) + + return CollectedSources( + find_links=list(find_links_sources), + index_urls=list(index_url_sources), + ) diff --git a/pipenv/patched/notpip/_internal/index/package_finder.py b/pipenv/patched/notpip/_internal/index/package_finder.py new file mode 100644 index 00000000..ab48628c --- /dev/null +++ b/pipenv/patched/notpip/_internal/index/package_finder.py @@ -0,0 +1,996 @@ +"""Routines related to PyPI, indexes""" + +# The following comment should be removed at some point in the future. +# mypy: strict-optional=False + +import functools +import itertools +import logging +import re +from typing import FrozenSet, Iterable, List, Optional, Set, Tuple, Union + +from pipenv.patched.notpip._vendor.packaging import specifiers +from pipenv.patched.notpip._vendor.packaging.tags import Tag +from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name +from pipenv.patched.notpip._vendor.packaging.version import _BaseVersion +from pipenv.patched.notpip._vendor.packaging.version import parse as parse_version + +from pipenv.patched.notpip._internal.exceptions import ( + BestVersionAlreadyInstalled, + DistributionNotFound, + InvalidWheelFilename, + UnsupportedWheel, +) +from pipenv.patched.notpip._internal.index.collector import LinkCollector, parse_links +from pipenv.patched.notpip._internal.models.candidate import InstallationCandidate +from pipenv.patched.notpip._internal.models.format_control import FormatControl +from pipenv.patched.notpip._internal.models.link import Link +from pipenv.patched.notpip._internal.models.search_scope import SearchScope +from pipenv.patched.notpip._internal.models.selection_prefs import SelectionPreferences +from pipenv.patched.notpip._internal.models.target_python import TargetPython +from pipenv.patched.notpip._internal.models.wheel import Wheel +from pipenv.patched.notpip._internal.req import InstallRequirement +from pipenv.patched.notpip._internal.utils._log import getLogger +from pipenv.patched.notpip._internal.utils.filetypes import WHEEL_EXTENSION +from pipenv.patched.notpip._internal.utils.hashes import Hashes +from pipenv.patched.notpip._internal.utils.logging import indent_log +from pipenv.patched.notpip._internal.utils.misc import build_netloc +from pipenv.patched.notpip._internal.utils.packaging import check_requires_python +from pipenv.patched.notpip._internal.utils.unpacking import SUPPORTED_EXTENSIONS +from pipenv.patched.notpip._internal.utils.urls import url_to_path + +__all__ = ['FormatControl', 'BestCandidateResult', 'PackageFinder'] + + +logger = getLogger(__name__) + +BuildTag = Union[Tuple[()], Tuple[int, str]] +CandidateSortingKey = ( + Tuple[int, int, int, _BaseVersion, Optional[int], BuildTag] +) + + +def _check_link_requires_python( + link: Link, + version_info: Tuple[int, int, int], + ignore_requires_python: bool = False, +) -> bool: + """ + Return whether the given Python version is compatible with a link's + "Requires-Python" value. + + :param version_info: A 3-tuple of ints representing the Python + major-minor-micro version to check. + :param ignore_requires_python: Whether to ignore the "Requires-Python" + value if the given Python version isn't compatible. + """ + try: + is_compatible = check_requires_python( + link.requires_python, version_info=version_info, + ) + except specifiers.InvalidSpecifier: + logger.debug( + "Ignoring invalid Requires-Python (%r) for link: %s", + link.requires_python, link, + ) + else: + if not is_compatible: + version = '.'.join(map(str, version_info)) + if not ignore_requires_python: + logger.verbose( + 'Link requires a different Python (%s not in: %r): %s', + version, link.requires_python, link, + ) + return False + + logger.debug( + 'Ignoring failed Requires-Python check (%s not in: %r) ' + 'for link: %s', + version, link.requires_python, link, + ) + + return True + + +class LinkEvaluator: + + """ + Responsible for evaluating links for a particular project. + """ + + _py_version_re = re.compile(r'-py([123]\.?[0-9]?)$') + + # Don't include an allow_yanked default value to make sure each call + # site considers whether yanked releases are allowed. This also causes + # that decision to be made explicit in the calling code, which helps + # people when reading the code. + def __init__( + self, + project_name: str, + canonical_name: str, + formats: FrozenSet[str], + target_python: TargetPython, + allow_yanked: bool, + ignore_requires_python: Optional[bool] = None, + ignore_compatibility: Optional[bool] = None, + ) -> None: + """ + :param project_name: The user supplied package name. + :param canonical_name: The canonical package name. + :param formats: The formats allowed for this package. Should be a set + with 'binary' or 'source' or both in it. + :param target_python: The target Python interpreter to use when + evaluating link compatibility. This is used, for example, to + check wheel compatibility, as well as when checking the Python + version, e.g. the Python version embedded in a link filename + (or egg fragment) and against an HTML link's optional PEP 503 + "data-requires-python" attribute. + :param allow_yanked: Whether files marked as yanked (in the sense + of PEP 592) are permitted to be candidates for install. + :param ignore_requires_python: Whether to ignore incompatible + PEP 503 "data-requires-python" values in HTML links. Defaults + to False. + :param ignore_compatibility: Whether to ignore + compatibility of python versions and allow all versions of packages. + """ + if ignore_requires_python is None: + ignore_requires_python = False + if ignore_compatibility is None: + ignore_compatibility = True + + self._allow_yanked = allow_yanked + self._canonical_name = canonical_name + self._ignore_requires_python = ignore_requires_python + self._formats = formats + self._target_python = target_python + self._ignore_compatibility = ignore_compatibility + + self.project_name = project_name + + def evaluate_link(self, link: Link) -> Tuple[bool, Optional[str]]: + """ + Determine whether a link is a candidate for installation. + + :return: A tuple (is_candidate, result), where `result` is (1) a + version string if `is_candidate` is True, and (2) if + `is_candidate` is False, an optional string to log the reason + the link fails to qualify. + """ + version = None + if link.is_yanked and not self._allow_yanked: + reason = link.yanked_reason or '' + return (False, f'yanked for reason: {reason}') + + if link.egg_fragment: + egg_info = link.egg_fragment + ext = link.ext + else: + egg_info, ext = link.splitext() + if not ext: + return (False, 'not a file') + if ext not in SUPPORTED_EXTENSIONS: + return (False, f'unsupported archive format: {ext}') + if "binary" not in self._formats and ext == WHEEL_EXTENSION and not self._ignore_compatibility: + reason = 'No binaries permitted for {}'.format( + self.project_name) + return (False, reason) + if "macosx10" in link.path and ext == '.zip' and not self._ignore_compatibility: + return (False, 'macosx10 one') + if ext == WHEEL_EXTENSION: + try: + wheel = Wheel(link.filename) + except InvalidWheelFilename: + return (False, 'invalid wheel filename') + if canonicalize_name(wheel.name) != self._canonical_name: + reason = 'wrong project name (not {})'.format( + self.project_name) + return (False, reason) + + supported_tags = self._target_python.get_tags() + if not wheel.supported(supported_tags) and not self._ignore_compatibility: + # Include the wheel's tags in the reason string to + # simplify troubleshooting compatibility issues. + file_tags = wheel.get_formatted_file_tags() + reason = ( + "none of the wheel's tags ({}) are compatible " + "(run pip debug --verbose to show compatible tags)".format( + ', '.join(file_tags) + ) + ) + return (False, reason) + + version = wheel.version + + # This should be up by the self.ok_binary check, but see issue 2700. + if "source" not in self._formats and ext != WHEEL_EXTENSION: + reason = f'No sources permitted for {self.project_name}' + return (False, reason) + + if not version: + version = _extract_version_from_fragment( + egg_info, self._canonical_name, + ) + if not version: + reason = f'Missing project version for {self.project_name}' + return (False, reason) + + match = self._py_version_re.search(version) + if match: + version = version[:match.start()] + py_version = match.group(1) + if py_version != self._target_python.py_version: + return (False, 'Python version is incorrect') + + supports_python = _check_link_requires_python( + link, version_info=self._target_python.py_version_info, + ignore_requires_python=self._ignore_requires_python, + ) + if not supports_python and not self._ignore_compatibility: + # Return None for the reason text to suppress calling + # _log_skipped_link(). + return (False, None) + + logger.debug('Found link %s, version: %s', link, version) + + return (True, version) + + +def filter_unallowed_hashes( + candidates: List[InstallationCandidate], + hashes: Hashes, + project_name: str, +) -> List[InstallationCandidate]: + """ + Filter out candidates whose hashes aren't allowed, and return a new + list of candidates. + + If at least one candidate has an allowed hash, then all candidates with + either an allowed hash or no hash specified are returned. Otherwise, + the given candidates are returned. + + Including the candidates with no hash specified when there is a match + allows a warning to be logged if there is a more preferred candidate + with no hash specified. Returning all candidates in the case of no + matches lets pip report the hash of the candidate that would otherwise + have been installed (e.g. permitting the user to more easily update + their requirements file with the desired hash). + """ + if not hashes: + logger.debug( + 'Given no hashes to check %s links for project %r: ' + 'discarding no candidates', + len(candidates), + project_name, + ) + # Make sure we're not returning back the given value. + return list(candidates) + + matches_or_no_digest = [] + # Collect the non-matches for logging purposes. + non_matches = [] + match_count = 0 + for candidate in candidates: + link = candidate.link + if not link.has_hash: + pass + elif link.is_hash_allowed(hashes=hashes): + match_count += 1 + else: + non_matches.append(candidate) + continue + + matches_or_no_digest.append(candidate) + + if match_count: + filtered = matches_or_no_digest + else: + # Make sure we're not returning back the given value. + filtered = list(candidates) + + if len(filtered) == len(candidates): + discard_message = 'discarding no candidates' + else: + discard_message = 'discarding {} non-matches:\n {}'.format( + len(non_matches), + '\n '.join(str(candidate.link) for candidate in non_matches) + ) + + logger.debug( + 'Checked %s links for project %r against %s hashes ' + '(%s matches, %s no digest): %s', + len(candidates), + project_name, + hashes.digest_count, + match_count, + len(matches_or_no_digest) - match_count, + discard_message + ) + + return filtered + + +class CandidatePreferences: + + """ + Encapsulates some of the preferences for filtering and sorting + InstallationCandidate objects. + """ + + def __init__( + self, + prefer_binary: bool = False, + allow_all_prereleases: bool = False, + ) -> None: + """ + :param allow_all_prereleases: Whether to allow all pre-releases. + """ + self.allow_all_prereleases = allow_all_prereleases + self.prefer_binary = prefer_binary + + +class BestCandidateResult: + """A collection of candidates, returned by `PackageFinder.find_best_candidate`. + + This class is only intended to be instantiated by CandidateEvaluator's + `compute_best_candidate()` method. + """ + + def __init__( + self, + candidates: List[InstallationCandidate], + applicable_candidates: List[InstallationCandidate], + best_candidate: Optional[InstallationCandidate], + ) -> None: + """ + :param candidates: A sequence of all available candidates found. + :param applicable_candidates: The applicable candidates. + :param best_candidate: The most preferred candidate found, or None + if no applicable candidates were found. + """ + assert set(applicable_candidates) <= set(candidates) + + if best_candidate is None: + assert not applicable_candidates + else: + assert best_candidate in applicable_candidates + + self._applicable_candidates = applicable_candidates + self._candidates = candidates + + self.best_candidate = best_candidate + + def iter_all(self) -> Iterable[InstallationCandidate]: + """Iterate through all candidates. + """ + return iter(self._candidates) + + def iter_applicable(self) -> Iterable[InstallationCandidate]: + """Iterate through the applicable candidates. + """ + return iter(self._applicable_candidates) + + +class CandidateEvaluator: + + """ + Responsible for filtering and sorting candidates for installation based + on what tags are valid. + """ + + @classmethod + def create( + cls, + project_name: str, + target_python: Optional[TargetPython] = None, + prefer_binary: bool = False, + allow_all_prereleases: bool = False, + specifier: Optional[specifiers.BaseSpecifier] = None, + hashes: Optional[Hashes] = None, + ) -> "CandidateEvaluator": + """Create a CandidateEvaluator object. + + :param target_python: The target Python interpreter to use when + checking compatibility. If None (the default), a TargetPython + object will be constructed from the running Python. + :param specifier: An optional object implementing `filter` + (e.g. `packaging.specifiers.SpecifierSet`) to filter applicable + versions. + :param hashes: An optional collection of allowed hashes. + """ + if target_python is None: + target_python = TargetPython() + if specifier is None: + specifier = specifiers.SpecifierSet() + + supported_tags = target_python.get_tags() + + return cls( + project_name=project_name, + supported_tags=supported_tags, + specifier=specifier, + prefer_binary=prefer_binary, + allow_all_prereleases=allow_all_prereleases, + hashes=hashes, + ) + + def __init__( + self, + project_name: str, + supported_tags: List[Tag], + specifier: specifiers.BaseSpecifier, + prefer_binary: bool = False, + allow_all_prereleases: bool = False, + hashes: Optional[Hashes] = None, + ) -> None: + """ + :param supported_tags: The PEP 425 tags supported by the target + Python in order of preference (most preferred first). + """ + self._allow_all_prereleases = allow_all_prereleases + self._hashes = hashes + self._prefer_binary = prefer_binary + self._project_name = project_name + self._specifier = specifier + self._supported_tags = supported_tags + # Since the index of the tag in the _supported_tags list is used + # as a priority, precompute a map from tag to index/priority to be + # used in wheel.find_most_preferred_tag. + self._wheel_tag_preferences = { + tag: idx for idx, tag in enumerate(supported_tags) + } + + def get_applicable_candidates( + self, + candidates: List[InstallationCandidate], + ) -> List[InstallationCandidate]: + """ + Return the applicable candidates from a list of candidates. + """ + # Using None infers from the specifier instead. + allow_prereleases = self._allow_all_prereleases or None + specifier = self._specifier + versions = { + str(v) for v in specifier.filter( + # We turn the version object into a str here because otherwise + # when we're debundled but setuptools isn't, Python will see + # packaging.version.Version and + # pkg_resources._vendor.packaging.version.Version as different + # types. This way we'll use a str as a common data interchange + # format. If we stop using the pkg_resources provided specifier + # and start using our own, we can drop the cast to str(). + (str(c.version) for c in candidates), + prereleases=allow_prereleases, + ) + } + + # Again, converting version to str to deal with debundling. + applicable_candidates = [ + c for c in candidates if str(c.version) in versions + ] + + filtered_applicable_candidates = filter_unallowed_hashes( + candidates=applicable_candidates, + hashes=self._hashes, + project_name=self._project_name, + ) + + return sorted(filtered_applicable_candidates, key=self._sort_key) + + def _sort_key( + self, candidate: InstallationCandidate, + ignore_compatibility: bool = True + ) -> CandidateSortingKey: + """ + Function to pass as the `key` argument to a call to sorted() to sort + InstallationCandidates by preference. + + Returns a tuple such that tuples sorting as greater using Python's + default comparison operator are more preferred. + + The preference is as follows: + + First and foremost, candidates with allowed (matching) hashes are + always preferred over candidates without matching hashes. This is + because e.g. if the only candidate with an allowed hash is yanked, + we still want to use that candidate. + + Second, excepting hash considerations, candidates that have been + yanked (in the sense of PEP 592) are always less preferred than + candidates that haven't been yanked. Then: + + If not finding wheels, they are sorted by version only. + If finding wheels, then the sort order is by version, then: + 1. existing installs + 2. wheels ordered via Wheel.support_index_min(self._supported_tags) + 3. source archives + If prefer_binary was set, then all wheels are sorted above sources. + + Note: it was considered to embed this logic into the Link + comparison operators, but then different sdist links + with the same version, would have to be considered equal + """ + valid_tags = self._supported_tags + support_num = len(valid_tags) + build_tag: BuildTag = () + binary_preference = 0 + link = candidate.link + if link.is_wheel: + # can raise InvalidWheelFilename + wheel = Wheel(link.filename) + try: + pri = -(wheel.find_most_preferred_tag( + valid_tags, self._wheel_tag_preferences + )) + except ValueError: + if not ignore_compatibility: + raise UnsupportedWheel( + "{} is not a supported wheel for this platform. It " + "can't be sorted.".format(wheel.filename) + ) + pri = -(support_num) + if self._prefer_binary: + binary_preference = 1 + if wheel.build_tag is not None: + match = re.match(r'^(\d+)(.*)$', wheel.build_tag) + build_tag_groups = match.groups() + build_tag = (int(build_tag_groups[0]), build_tag_groups[1]) + else: # sdist + pri = -(support_num) + has_allowed_hash = int(link.is_hash_allowed(self._hashes)) + yank_value = -1 * int(link.is_yanked) # -1 for yanked. + return ( + has_allowed_hash, yank_value, binary_preference, candidate.version, + pri, build_tag, + ) + + def sort_best_candidate( + self, + candidates: List[InstallationCandidate], + ) -> Optional[InstallationCandidate]: + """ + Return the best candidate per the instance's sort order, or None if + no candidate is acceptable. + """ + if not candidates: + return None + best_candidate = max(candidates, key=self._sort_key) + return best_candidate + + def compute_best_candidate( + self, + candidates: List[InstallationCandidate], + ) -> BestCandidateResult: + """ + Compute and return a `BestCandidateResult` instance. + """ + applicable_candidates = self.get_applicable_candidates(candidates) + + best_candidate = self.sort_best_candidate(applicable_candidates) + + return BestCandidateResult( + candidates, + applicable_candidates=applicable_candidates, + best_candidate=best_candidate, + ) + + +class PackageFinder: + """This finds packages. + + This is meant to match easy_install's technique for looking for + packages, by reading pages and looking for appropriate links. + """ + + def __init__( + self, + link_collector: LinkCollector, + target_python: TargetPython, + allow_yanked: bool, + format_control: Optional[FormatControl] = None, + candidate_prefs: Optional[CandidatePreferences] = None, + ignore_requires_python: Optional[bool] = None, + ignore_compatibility: Optional[bool] = False + ) -> None: + """ + This constructor is primarily meant to be used by the create() class + method and from tests. + + :param format_control: A FormatControl object, used to control + the selection of source packages / binary packages when consulting + the index and links. + :param candidate_prefs: Options to use when creating a + CandidateEvaluator object. + """ + if candidate_prefs is None: + candidate_prefs = CandidatePreferences() + + format_control = format_control or FormatControl(set(), set()) + + self._allow_yanked = allow_yanked + self._candidate_prefs = candidate_prefs + self._ignore_requires_python = ignore_requires_python + self._link_collector = link_collector + self._target_python = target_python + self._ignore_compatibility = ignore_compatibility + + self.format_control = format_control + + # These are boring links that have already been logged somehow. + self._logged_links: Set[Link] = set() + + # Don't include an allow_yanked default value to make sure each call + # site considers whether yanked releases are allowed. This also causes + # that decision to be made explicit in the calling code, which helps + # people when reading the code. + @classmethod + def create( + cls, + link_collector: LinkCollector, + selection_prefs: SelectionPreferences, + target_python: Optional[TargetPython] = None, + ) -> "PackageFinder": + """Create a PackageFinder. + + :param selection_prefs: The candidate selection preferences, as a + SelectionPreferences object. + :param target_python: The target Python interpreter to use when + checking compatibility. If None (the default), a TargetPython + object will be constructed from the running Python. + """ + if target_python is None: + target_python = TargetPython() + + candidate_prefs = CandidatePreferences( + prefer_binary=selection_prefs.prefer_binary, + allow_all_prereleases=selection_prefs.allow_all_prereleases, + ) + + return cls( + candidate_prefs=candidate_prefs, + link_collector=link_collector, + target_python=target_python, + allow_yanked=selection_prefs.allow_yanked, + format_control=selection_prefs.format_control, + ignore_requires_python=selection_prefs.ignore_requires_python, + ) + + @property + def target_python(self) -> TargetPython: + return self._target_python + + @property + def search_scope(self) -> SearchScope: + return self._link_collector.search_scope + + @search_scope.setter + def search_scope(self, search_scope: SearchScope) -> None: + self._link_collector.search_scope = search_scope + + @property + def find_links(self) -> List[str]: + return self._link_collector.find_links + + @property + def index_urls(self) -> List[str]: + return self.search_scope.index_urls + + @property + def trusted_hosts(self) -> Iterable[str]: + for host_port in self._link_collector.session.pip_trusted_origins: + yield build_netloc(*host_port) + + @property + def allow_all_prereleases(self) -> bool: + return self._candidate_prefs.allow_all_prereleases + + def set_allow_all_prereleases(self) -> None: + self._candidate_prefs.allow_all_prereleases = True + + @property + def prefer_binary(self) -> bool: + return self._candidate_prefs.prefer_binary + + def set_prefer_binary(self) -> None: + self._candidate_prefs.prefer_binary = True + + def make_link_evaluator(self, project_name: str) -> LinkEvaluator: + canonical_name = canonicalize_name(project_name) + formats = self.format_control.get_allowed_formats(canonical_name) + + return LinkEvaluator( + project_name=project_name, + canonical_name=canonical_name, + formats=formats, + target_python=self._target_python, + allow_yanked=self._allow_yanked, + ignore_requires_python=self._ignore_requires_python, + ignore_compatibility=self._ignore_compatibility + ) + + def _sort_links(self, links: Iterable[Link]) -> List[Link]: + """ + Returns elements of links in order, non-egg links first, egg links + second, while eliminating duplicates + """ + eggs, no_eggs = [], [] + seen: Set[Link] = set() + for link in links: + if link not in seen: + seen.add(link) + if link.egg_fragment: + eggs.append(link) + else: + no_eggs.append(link) + return no_eggs + eggs + + def _log_skipped_link(self, link: Link, reason: str) -> None: + if link not in self._logged_links: + # Put the link at the end so the reason is more visible and because + # the link string is usually very long. + logger.debug('Skipping link: %s: %s', reason, link) + self._logged_links.add(link) + + def get_install_candidate( + self, link_evaluator: LinkEvaluator, link: Link + ) -> Optional[InstallationCandidate]: + """ + If the link is a candidate for install, convert it to an + InstallationCandidate and return it. Otherwise, return None. + """ + is_candidate, result = link_evaluator.evaluate_link(link) + if not is_candidate: + if result: + self._log_skipped_link(link, reason=result) + return None + + return InstallationCandidate( + name=link_evaluator.project_name, + link=link, + version=result, + ) + + def evaluate_links( + self, link_evaluator: LinkEvaluator, links: Iterable[Link] + ) -> List[InstallationCandidate]: + """ + Convert links that are candidates to InstallationCandidate objects. + """ + candidates = [] + for link in self._sort_links(links): + candidate = self.get_install_candidate(link_evaluator, link) + if candidate is not None: + candidates.append(candidate) + + return candidates + + def process_project_url( + self, project_url: Link, link_evaluator: LinkEvaluator + ) -> List[InstallationCandidate]: + logger.debug( + 'Fetching project page and analyzing links: %s', project_url, + ) + html_page = self._link_collector.fetch_page(project_url) + if html_page is None: + return [] + + page_links = list(parse_links(html_page)) + + with indent_log(): + package_links = self.evaluate_links( + link_evaluator, + links=page_links, + ) + + return package_links + + @functools.lru_cache(maxsize=None) + def find_all_candidates(self, project_name: str) -> List[InstallationCandidate]: + """Find all available InstallationCandidate for project_name + + This checks index_urls and find_links. + All versions found are returned as an InstallationCandidate list. + + See LinkEvaluator.evaluate_link() for details on which files + are accepted. + """ + link_evaluator = self.make_link_evaluator(project_name) + + collected_sources = self._link_collector.collect_sources( + project_name=project_name, + candidates_from_page=functools.partial( + self.process_project_url, + link_evaluator=link_evaluator, + ), + ) + + page_candidates_it = itertools.chain.from_iterable( + source.page_candidates() + for sources in collected_sources + for source in sources + if source is not None + ) + page_candidates = list(page_candidates_it) + + file_links_it = itertools.chain.from_iterable( + source.file_links() + for sources in collected_sources + for source in sources + if source is not None + ) + file_candidates = self.evaluate_links( + link_evaluator, + sorted(file_links_it, reverse=True), + ) + + if logger.isEnabledFor(logging.DEBUG) and file_candidates: + paths = [url_to_path(c.link.url) for c in file_candidates] + logger.debug("Local files found: %s", ", ".join(paths)) + + # This is an intentional priority ordering + return file_candidates + page_candidates + + def make_candidate_evaluator( + self, + project_name: str, + specifier: Optional[specifiers.BaseSpecifier] = None, + hashes: Optional[Hashes] = None, + ) -> CandidateEvaluator: + """Create a CandidateEvaluator object to use. + """ + candidate_prefs = self._candidate_prefs + return CandidateEvaluator.create( + project_name=project_name, + target_python=self._target_python, + prefer_binary=candidate_prefs.prefer_binary, + allow_all_prereleases=candidate_prefs.allow_all_prereleases, + specifier=specifier, + hashes=hashes, + ) + + @functools.lru_cache(maxsize=None) + def find_best_candidate( + self, + project_name: str, + specifier: Optional[specifiers.BaseSpecifier] = None, + hashes: Optional[Hashes] = None, + ) -> BestCandidateResult: + """Find matches for the given project and specifier. + + :param specifier: An optional object implementing `filter` + (e.g. `packaging.specifiers.SpecifierSet`) to filter applicable + versions. + + :return: A `BestCandidateResult` instance. + """ + candidates = self.find_all_candidates(project_name) + candidate_evaluator = self.make_candidate_evaluator( + project_name=project_name, + specifier=specifier, + hashes=hashes, + ) + return candidate_evaluator.compute_best_candidate(candidates) + + def find_requirement( + self, req: InstallRequirement, upgrade: bool + ) -> Optional[InstallationCandidate]: + """Try to find a Link matching req + + Expects req, an InstallRequirement and upgrade, a boolean + Returns a InstallationCandidate if found, + Raises DistributionNotFound or BestVersionAlreadyInstalled otherwise + """ + hashes = req.hashes(trust_internet=False) + best_candidate_result = self.find_best_candidate( + req.name, specifier=req.specifier, hashes=hashes, + ) + best_candidate = best_candidate_result.best_candidate + + installed_version: Optional[_BaseVersion] = None + if req.satisfied_by is not None: + installed_version = parse_version(req.satisfied_by.version) + + def _format_versions(cand_iter: Iterable[InstallationCandidate]) -> str: + # This repeated parse_version and str() conversion is needed to + # handle different vendoring sources from pip and pkg_resources. + # If we stop using the pkg_resources provided specifier and start + # using our own, we can drop the cast to str(). + return ", ".join(sorted( + {str(c.version) for c in cand_iter}, + key=parse_version, + )) or "none" + + if installed_version is None and best_candidate is None: + logger.critical( + 'Could not find a version that satisfies the requirement %s ' + '(from versions: %s)', + req, + _format_versions(best_candidate_result.iter_all()), + ) + + raise DistributionNotFound( + 'No matching distribution found for {}'.format( + req) + ) + + best_installed = False + if installed_version and ( + best_candidate is None or + best_candidate.version <= installed_version): + best_installed = True + + if not upgrade and installed_version is not None: + if best_installed: + logger.debug( + 'Existing installed version (%s) is most up-to-date and ' + 'satisfies requirement', + installed_version, + ) + else: + logger.debug( + 'Existing installed version (%s) satisfies requirement ' + '(most up-to-date version is %s)', + installed_version, + best_candidate.version, + ) + return None + + if best_installed: + # We have an existing version, and its the best version + logger.debug( + 'Installed version (%s) is most up-to-date (past versions: ' + '%s)', + installed_version, + _format_versions(best_candidate_result.iter_applicable()), + ) + raise BestVersionAlreadyInstalled + + logger.debug( + 'Using version %s (newest of versions: %s)', + best_candidate.version, + _format_versions(best_candidate_result.iter_applicable()), + ) + return best_candidate + + +def _find_name_version_sep(fragment: str, canonical_name: str) -> int: + """Find the separator's index based on the package's canonical name. + + :param fragment: A + filename "fragment" (stem) or + egg fragment. + :param canonical_name: The package's canonical name. + + This function is needed since the canonicalized name does not necessarily + have the same length as the egg info's name part. An example:: + + >>> fragment = 'foo__bar-1.0' + >>> canonical_name = 'foo-bar' + >>> _find_name_version_sep(fragment, canonical_name) + 8 + """ + # Project name and version must be separated by one single dash. Find all + # occurrences of dashes; if the string in front of it matches the canonical + # name, this is the one separating the name and version parts. + for i, c in enumerate(fragment): + if c != "-": + continue + if canonicalize_name(fragment[:i]) == canonical_name: + return i + raise ValueError(f"{fragment} does not match {canonical_name}") + + +def _extract_version_from_fragment(fragment: str, canonical_name: str) -> Optional[str]: + """Parse the version string from a + filename + "fragment" (stem) or egg fragment. + + :param fragment: The string to parse. E.g. foo-2.1 + :param canonical_name: The canonicalized name of the package this + belongs to. + """ + try: + version_start = _find_name_version_sep(fragment, canonical_name) + 1 + except ValueError: + return None + version = fragment[version_start:] + if not version: + return None + return version diff --git a/pipenv/patched/notpip/_internal/index/sources.py b/pipenv/patched/notpip/_internal/index/sources.py new file mode 100644 index 00000000..643cde79 --- /dev/null +++ b/pipenv/patched/notpip/_internal/index/sources.py @@ -0,0 +1,224 @@ +import logging +import mimetypes +import os +import pathlib +from typing import Callable, Iterable, Optional, Tuple + +from pipenv.patched.notpip._internal.models.candidate import InstallationCandidate +from pipenv.patched.notpip._internal.models.link import Link +from pipenv.patched.notpip._internal.utils.urls import path_to_url, url_to_path +from pipenv.patched.notpip._internal.vcs import is_url + +logger = logging.getLogger(__name__) + +FoundCandidates = Iterable[InstallationCandidate] +FoundLinks = Iterable[Link] +CandidatesFromPage = Callable[[Link], Iterable[InstallationCandidate]] +PageValidator = Callable[[Link], bool] + + +class LinkSource: + @property + def link(self) -> Optional[Link]: + """Returns the underlying link, if there's one.""" + raise NotImplementedError() + + def page_candidates(self) -> FoundCandidates: + """Candidates found by parsing an archive listing HTML file.""" + raise NotImplementedError() + + def file_links(self) -> FoundLinks: + """Links found by specifying archives directly.""" + raise NotImplementedError() + + +def _is_html_file(file_url: str) -> bool: + return mimetypes.guess_type(file_url, strict=False)[0] == "text/html" + + +class _FlatDirectorySource(LinkSource): + """Link source specified by ``--find-links=``. + + This looks the content of the directory, and returns: + + * ``page_candidates``: Links listed on each HTML file in the directory. + * ``file_candidates``: Archives in the directory. + """ + + def __init__( + self, + candidates_from_page: CandidatesFromPage, + path: str, + ) -> None: + self._candidates_from_page = candidates_from_page + self._path = pathlib.Path(os.path.realpath(path)) + + @property + def link(self) -> Optional[Link]: + return None + + def page_candidates(self) -> FoundCandidates: + for path in self._path.iterdir(): + url = path_to_url(str(path)) + if not _is_html_file(url): + continue + yield from self._candidates_from_page(Link(url)) + + def file_links(self) -> FoundLinks: + for path in self._path.iterdir(): + url = path_to_url(str(path)) + if _is_html_file(url): + continue + yield Link(url) + + +class _LocalFileSource(LinkSource): + """``--find-links=`` or ``--[extra-]index-url=``. + + If a URL is supplied, it must be a ``file:`` URL. If a path is supplied to + the option, it is converted to a URL first. This returns: + + * ``page_candidates``: Links listed on an HTML file. + * ``file_candidates``: The non-HTML file. + """ + + def __init__( + self, + candidates_from_page: CandidatesFromPage, + link: Link, + ) -> None: + self._candidates_from_page = candidates_from_page + self._link = link + + @property + def link(self) -> Optional[Link]: + return self._link + + def page_candidates(self) -> FoundCandidates: + if not _is_html_file(self._link.url): + return + yield from self._candidates_from_page(self._link) + + def file_links(self) -> FoundLinks: + if _is_html_file(self._link.url): + return + yield self._link + + +class _RemoteFileSource(LinkSource): + """``--find-links=`` or ``--[extra-]index-url=``. + + This returns: + + * ``page_candidates``: Links listed on an HTML file. + * ``file_candidates``: The non-HTML file. + """ + + def __init__( + self, + candidates_from_page: CandidatesFromPage, + page_validator: PageValidator, + link: Link, + ) -> None: + self._candidates_from_page = candidates_from_page + self._page_validator = page_validator + self._link = link + + @property + def link(self) -> Optional[Link]: + return self._link + + def page_candidates(self) -> FoundCandidates: + if not self._page_validator(self._link): + return + yield from self._candidates_from_page(self._link) + + def file_links(self) -> FoundLinks: + yield self._link + + +class _IndexDirectorySource(LinkSource): + """``--[extra-]index-url=``. + + This is treated like a remote URL; ``candidates_from_page`` contains logic + for this by appending ``index.html`` to the link. + """ + + def __init__( + self, + candidates_from_page: CandidatesFromPage, + link: Link, + ) -> None: + self._candidates_from_page = candidates_from_page + self._link = link + + @property + def link(self) -> Optional[Link]: + return self._link + + def page_candidates(self) -> FoundCandidates: + yield from self._candidates_from_page(self._link) + + def file_links(self) -> FoundLinks: + return () + + +def build_source( + location: str, + *, + candidates_from_page: CandidatesFromPage, + page_validator: PageValidator, + expand_dir: bool, + cache_link_parsing: bool, +) -> Tuple[Optional[str], Optional[LinkSource]]: + + path: Optional[str] = None + url: Optional[str] = None + if os.path.exists(location): # Is a local path. + url = path_to_url(location) + path = location + elif location.startswith("file:"): # A file: URL. + url = location + path = url_to_path(location) + elif is_url(location): + url = location + + if url is None: + msg = ( + "Location '%s' is ignored: " + "it is either a non-existing path or lacks a specific scheme." + ) + logger.warning(msg, location) + return (None, None) + + if path is None: + source: LinkSource = _RemoteFileSource( + candidates_from_page=candidates_from_page, + page_validator=page_validator, + link=Link(url, cache_link_parsing=cache_link_parsing), + ) + return (url, source) + + if os.path.isdir(path): + if expand_dir: + source = _FlatDirectorySource( + candidates_from_page=candidates_from_page, + path=path, + ) + else: + source = _IndexDirectorySource( + candidates_from_page=candidates_from_page, + link=Link(url, cache_link_parsing=cache_link_parsing), + ) + return (url, source) + elif os.path.isfile(path): + source = _LocalFileSource( + candidates_from_page=candidates_from_page, + link=Link(url, cache_link_parsing=cache_link_parsing), + ) + return (url, source) + logger.warning( + "Location '%s' is ignored: it is neither a file nor a directory.", + location, + ) + return (url, None) diff --git a/pipenv/patched/notpip/_internal/locations.py b/pipenv/patched/notpip/_internal/locations.py deleted file mode 100644 index e8f5a268..00000000 --- a/pipenv/patched/notpip/_internal/locations.py +++ /dev/null @@ -1,211 +0,0 @@ -"""Locations where we look for configs, install stuff, etc""" -from __future__ import absolute_import - -import os -import os.path -import platform -import site -import sys -import sysconfig -from distutils import sysconfig as distutils_sysconfig -from distutils.command.install import SCHEME_KEYS # type: ignore - -from pipenv.patched.notpip._internal.utils import appdirs -from pipenv.patched.notpip._internal.utils.compat import WINDOWS, expanduser -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING - -if MYPY_CHECK_RUNNING: - from typing import Any, Union, Dict, List, Optional # noqa: F401 - - -# Application Directories -USER_CACHE_DIR = appdirs.user_cache_dir("pip") - - -DELETE_MARKER_MESSAGE = '''\ -This file is placed here by pip to indicate the source was put -here by pip. - -Once this package is successfully installed this source code will be -deleted (unless you remove this file). -''' -PIP_DELETE_MARKER_FILENAME = 'pip-delete-this-directory.txt' - - -def write_delete_marker_file(directory): - # type: (str) -> None - """ - Write the pip delete marker file into this directory. - """ - filepath = os.path.join(directory, PIP_DELETE_MARKER_FILENAME) - with open(filepath, 'w') as marker_fp: - marker_fp.write(DELETE_MARKER_MESSAGE) - - -def running_under_virtualenv(): - # type: () -> bool - """ - Return True if we're running inside a virtualenv, False otherwise. - - """ - if hasattr(sys, 'real_prefix'): - return True - elif sys.prefix != getattr(sys, "base_prefix", sys.prefix): - return True - - return False - - -def virtualenv_no_global(): - # type: () -> bool - """ - Return True if in a venv and no system site packages. - """ - # this mirrors the logic in virtualenv.py for locating the - # no-global-site-packages.txt file - site_mod_dir = os.path.dirname(os.path.abspath(site.__file__)) - no_global_file = os.path.join(site_mod_dir, 'no-global-site-packages.txt') - if running_under_virtualenv() and os.path.isfile(no_global_file): - return True - else: - return False - - -if running_under_virtualenv(): - src_prefix = os.path.join(sys.prefix, 'src') -else: - # FIXME: keep src in cwd for now (it is not a temporary folder) - try: - src_prefix = os.path.join(os.getcwd(), 'src') - except OSError: - # In case the current working directory has been renamed or deleted - sys.exit( - "The folder you are executing pip from can no longer be found." - ) - -# under macOS + virtualenv sys.prefix is not properly resolved -# it is something like /path/to/python/bin/.. -# Note: using realpath due to tmp dirs on OSX being symlinks -src_prefix = os.path.abspath(src_prefix) - -# FIXME doesn't account for venv linked to global site-packages - -site_packages = sysconfig.get_path("purelib") # type: Optional[str] - -# This is because of a bug in PyPy's sysconfig module, see -# https://bitbucket.org/pypy/pypy/issues/2506/sysconfig-returns-incorrect-paths -# for more information. -if platform.python_implementation().lower() == "pypy": - site_packages = distutils_sysconfig.get_python_lib() -try: - # Use getusersitepackages if this is present, as it ensures that the - # value is initialised properly. - user_site = site.getusersitepackages() -except AttributeError: - user_site = site.USER_SITE -user_dir = expanduser('~') -if WINDOWS: - bin_py = os.path.join(sys.prefix, 'Scripts') - bin_user = os.path.join(user_site, 'Scripts') - # buildout uses 'bin' on Windows too? - if not os.path.exists(bin_py): - bin_py = os.path.join(sys.prefix, 'bin') - bin_user = os.path.join(user_site, 'bin') - - config_basename = 'pip.ini' - - legacy_storage_dir = os.path.join(user_dir, 'pip') - legacy_config_file = os.path.join( - legacy_storage_dir, - config_basename, - ) -else: - bin_py = os.path.join(sys.prefix, 'bin') - bin_user = os.path.join(user_site, 'bin') - - config_basename = 'pip.conf' - - legacy_storage_dir = os.path.join(user_dir, '.pip') - legacy_config_file = os.path.join( - legacy_storage_dir, - config_basename, - ) - # Forcing to use /usr/local/bin for standard macOS framework installs - # Also log to ~/Library/Logs/ for use with the Console.app log viewer - if sys.platform[:6] == 'darwin' and sys.prefix[:16] == '/System/Library/': - bin_py = '/usr/local/bin' - -site_config_files = [ - os.path.join(path, config_basename) - for path in appdirs.site_config_dirs('pip') -] - -venv_config_file = os.path.join(sys.prefix, config_basename) -new_config_file = os.path.join(appdirs.user_config_dir("pip"), config_basename) - - -def distutils_scheme(dist_name, user=False, home=None, root=None, - isolated=False, prefix=None): - # type:(str, bool, str, str, bool, str) -> dict - """ - Return a distutils install scheme - """ - from distutils.dist import Distribution - - scheme = {} - - if isolated: - extra_dist_args = {"script_args": ["--no-user-cfg"]} - else: - extra_dist_args = {} - dist_args = {'name': dist_name} # type: Dict[str, Union[str, List[str]]] - dist_args.update(extra_dist_args) - - d = Distribution(dist_args) - # Ignoring, typeshed issue reported python/typeshed/issues/2567 - d.parse_config_files() - # NOTE: Ignoring type since mypy can't find attributes on 'Command' - i = d.get_command_obj('install', create=True) # type: Any - assert i is not None - # NOTE: setting user or home has the side-effect of creating the home dir - # or user base for installations during finalize_options() - # ideally, we'd prefer a scheme class that has no side-effects. - assert not (user and prefix), "user={} prefix={}".format(user, prefix) - i.user = user or i.user - if user: - i.prefix = "" - i.prefix = prefix or i.prefix - i.home = home or i.home - i.root = root or i.root - i.finalize_options() - for key in SCHEME_KEYS: - scheme[key] = getattr(i, 'install_' + key) - - # install_lib specified in setup.cfg should install *everything* - # into there (i.e. it takes precedence over both purelib and - # platlib). Note, i.install_lib is *always* set after - # finalize_options(); we only want to override here if the user - # has explicitly requested it hence going back to the config - - # Ignoring, typeshed issue reported python/typeshed/issues/2567 - if 'install_lib' in d.get_option_dict('install'): # type: ignore - scheme.update(dict(purelib=i.install_lib, platlib=i.install_lib)) - - if running_under_virtualenv(): - scheme['headers'] = os.path.join( - sys.prefix, - 'include', - 'site', - 'python' + sys.version[:3], - dist_name, - ) - - if root is not None: - path_no_drive = os.path.splitdrive( - os.path.abspath(scheme["headers"]))[1] - scheme["headers"] = os.path.join( - root, - path_no_drive[1:], - ) - - return scheme diff --git a/pipenv/patched/notpip/_internal/locations/__init__.py b/pipenv/patched/notpip/_internal/locations/__init__.py new file mode 100644 index 00000000..83c69204 --- /dev/null +++ b/pipenv/patched/notpip/_internal/locations/__init__.py @@ -0,0 +1,320 @@ +import functools +import logging +import os +import pathlib +import sys +import sysconfig +from typing import Dict, Iterator, List, Optional, Tuple + +from pipenv.patched.notpip._internal.models.scheme import SCHEME_KEYS, Scheme +from pipenv.patched.notpip._internal.utils.compat import WINDOWS +from pipenv.patched.notpip._internal.utils.deprecation import deprecated + +from . import _distutils, _sysconfig +from .base import ( + USER_CACHE_DIR, + get_major_minor_version, + get_src_prefix, + is_osx_framework, + site_packages, + user_site, +) + +__all__ = [ + "USER_CACHE_DIR", + "get_bin_prefix", + "get_bin_user", + "get_major_minor_version", + "get_platlib", + "get_prefixed_libs", + "get_purelib", + "get_scheme", + "get_src_prefix", + "site_packages", + "user_site", +] + + +logger = logging.getLogger(__name__) + +if os.environ.get("_PIP_LOCATIONS_NO_WARN_ON_MISMATCH"): + _MISMATCH_LEVEL = logging.DEBUG +else: + _MISMATCH_LEVEL = logging.WARNING + + +def _looks_like_red_hat_patched_platlib_purelib(scheme: Dict[str, str]) -> bool: + platlib = scheme["platlib"] + if "/lib64/" not in platlib: + return False + unpatched = platlib.replace("/lib64/", "/lib/") + return unpatched.replace("$platbase/", "$base/") == scheme["purelib"] + + +@functools.lru_cache(maxsize=None) +def _looks_like_red_hat_patched() -> bool: + """Red Hat patches platlib in unix_prefix and unix_home, but not purelib. + + This is the only way I can see to tell a Red Hat-patched Python. + """ + from distutils.command.install import INSTALL_SCHEMES # type: ignore + + return all( + k in INSTALL_SCHEMES + and _looks_like_red_hat_patched_platlib_purelib(INSTALL_SCHEMES[k]) + for k in ("unix_prefix", "unix_home") + ) + + +@functools.lru_cache(maxsize=None) +def _looks_like_debian_patched() -> bool: + """Debian adds two additional schemes.""" + from distutils.command.install import INSTALL_SCHEMES # type: ignore + + return "deb_system" in INSTALL_SCHEMES and "unix_local" in INSTALL_SCHEMES + + +def _fix_abiflags(parts: Tuple[str]) -> Iterator[str]: + ldversion = sysconfig.get_config_var("LDVERSION") + abiflags: str = getattr(sys, "abiflags", None) + + # LDVERSION does not end with sys.abiflags. Just return the path unchanged. + if not ldversion or not abiflags or not ldversion.endswith(abiflags): + yield from parts + return + + # Strip sys.abiflags from LDVERSION-based path components. + for part in parts: + if part.endswith(ldversion): + part = part[: (0 - len(abiflags))] + yield part + + +def _default_base(*, user: bool) -> str: + if user: + base = sysconfig.get_config_var("userbase") + else: + base = sysconfig.get_config_var("base") + assert base is not None + return base + + +@functools.lru_cache(maxsize=None) +def _warn_mismatched(old: pathlib.Path, new: pathlib.Path, *, key: str) -> None: + issue_url = "https://github.com/pypa/pip/issues/10151" + message = ( + "Value for %s does not match. Please report this to <%s>" + "\ndistutils: %s" + "\nsysconfig: %s" + ) + logger.log(_MISMATCH_LEVEL, message, key, issue_url, old, new) + + +def _warn_if_mismatch(old: pathlib.Path, new: pathlib.Path, *, key: str) -> bool: + if old == new: + return False + _warn_mismatched(old, new, key=key) + return True + + +@functools.lru_cache(maxsize=None) +def _log_context( + *, + user: bool = False, + home: Optional[str] = None, + root: Optional[str] = None, + prefix: Optional[str] = None, +) -> None: + parts = [ + "Additional context:", + "user = %r", + "home = %r", + "root = %r", + "prefix = %r", + ] + + logger.log(_MISMATCH_LEVEL, "\n".join(parts), user, home, root, prefix) + + +def get_scheme( + dist_name: str, + user: bool = False, + home: Optional[str] = None, + root: Optional[str] = None, + isolated: bool = False, + prefix: Optional[str] = None, +) -> Scheme: + old = _distutils.get_scheme( + dist_name, + user=user, + home=home, + root=root, + isolated=isolated, + prefix=prefix, + ) + new = _sysconfig.get_scheme( + dist_name, + user=user, + home=home, + root=root, + isolated=isolated, + prefix=prefix, + ) + + base = prefix or home or _default_base(user=user) + warning_contexts = [] + for k in SCHEME_KEYS: + # Extra join because distutils can return relative paths. + old_v = pathlib.Path(base, getattr(old, k)) + new_v = pathlib.Path(getattr(new, k)) + + if old_v == new_v: + continue + + # distutils incorrectly put PyPy packages under ``site-packages/python`` + # in the ``posix_home`` scheme, but PyPy devs said they expect the + # directory name to be ``pypy`` instead. So we treat this as a bug fix + # and not warn about it. See bpo-43307 and python/cpython#24628. + skip_pypy_special_case = ( + sys.implementation.name == "pypy" + and home is not None + and k in ("platlib", "purelib") + and old_v.parent == new_v.parent + and old_v.name.startswith("python") + and new_v.name.startswith("pypy") + ) + if skip_pypy_special_case: + continue + + # sysconfig's ``osx_framework_user`` does not include ``pythonX.Y`` in + # the ``include`` value, but distutils's ``headers`` does. We'll let + # CPython decide whether this is a bug or feature. See bpo-43948. + skip_osx_framework_user_special_case = ( + user + and is_osx_framework() + and k == "headers" + and old_v.parent.parent == new_v.parent + and old_v.parent.name.startswith("python") + ) + if skip_osx_framework_user_special_case: + continue + + # On Red Hat and derived Linux distributions, distutils is patched to + # use "lib64" instead of "lib" for platlib. + if k == "platlib" and _looks_like_red_hat_patched(): + continue + + # Both Debian and Red Hat patch Python to place the system site under + # /usr/local instead of /usr. Debian also places lib in dist-packages + # instead of site-packages, but the /usr/local check should cover it. + skip_linux_system_special_case = ( + not (user or home or prefix) + and old_v.parts[1:3] == ("usr", "local") + and len(new_v.parts) > 1 + and new_v.parts[1] == "usr" + and (len(new_v.parts) < 3 or new_v.parts[2] != "local") + and (_looks_like_red_hat_patched() or _looks_like_debian_patched()) + ) + if skip_linux_system_special_case: + continue + + # On Python 3.7 and earlier, sysconfig does not include sys.abiflags in + # the "pythonX.Y" part of the path, but distutils does. + skip_sysconfig_abiflag_bug = ( + sys.version_info < (3, 8) + and not WINDOWS + and k in ("headers", "platlib", "purelib") + and tuple(_fix_abiflags(old_v.parts)) == new_v.parts + ) + if skip_sysconfig_abiflag_bug: + continue + + warning_contexts.append((old_v, new_v, f"scheme.{k}")) + + if not warning_contexts: + return old + + # Check if this path mismatch is caused by distutils config files. Those + # files will no longer work once we switch to sysconfig, so this raises a + # deprecation message for them. + default_old = _distutils.distutils_scheme( + dist_name, + user, + home, + root, + isolated, + prefix, + ignore_config_files=True, + ) + if any(default_old[k] != getattr(old, k) for k in SCHEME_KEYS): + deprecated( + "Configuring installation scheme with distutils config files " + "is deprecated and will no longer work in the near future. If you " + "are using a Homebrew or Linuxbrew Python, please see discussion " + "at https://github.com/Homebrew/homebrew-core/issues/76621", + replacement=None, + gone_in=None, + ) + return old + + # Post warnings about this mismatch so user can report them back. + for old_v, new_v, key in warning_contexts: + _warn_mismatched(old_v, new_v, key=key) + _log_context(user=user, home=home, root=root, prefix=prefix) + + return old + + +def get_bin_prefix() -> str: + old = _distutils.get_bin_prefix() + new = _sysconfig.get_bin_prefix() + if _warn_if_mismatch(pathlib.Path(old), pathlib.Path(new), key="bin_prefix"): + _log_context() + return old + + +def get_bin_user() -> str: + return _sysconfig.get_scheme("", user=True).scripts + + +def get_purelib() -> str: + """Return the default pure-Python lib location.""" + old = _distutils.get_purelib() + new = _sysconfig.get_purelib() + if _warn_if_mismatch(pathlib.Path(old), pathlib.Path(new), key="purelib"): + _log_context() + return old + + +def get_platlib() -> str: + """Return the default platform-shared lib location.""" + old = _distutils.get_platlib() + new = _sysconfig.get_platlib() + if _warn_if_mismatch(pathlib.Path(old), pathlib.Path(new), key="platlib"): + _log_context() + return old + + +def get_prefixed_libs(prefix: str) -> List[str]: + """Return the lib locations under ``prefix``.""" + old_pure, old_plat = _distutils.get_prefixed_libs(prefix) + new_pure, new_plat = _sysconfig.get_prefixed_libs(prefix) + + warned = [ + _warn_if_mismatch( + pathlib.Path(old_pure), + pathlib.Path(new_pure), + key="prefixed-purelib", + ), + _warn_if_mismatch( + pathlib.Path(old_plat), + pathlib.Path(new_plat), + key="prefixed-platlib", + ), + ] + if any(warned): + _log_context(prefix=prefix) + + if old_pure == old_plat: + return [old_pure] + return [old_pure, old_plat] diff --git a/pipenv/patched/notpip/_internal/locations/_distutils.py b/pipenv/patched/notpip/_internal/locations/_distutils.py new file mode 100644 index 00000000..6be04094 --- /dev/null +++ b/pipenv/patched/notpip/_internal/locations/_distutils.py @@ -0,0 +1,163 @@ +"""Locations where we look for configs, install stuff, etc""" + +# The following comment should be removed at some point in the future. +# mypy: strict-optional=False + +import logging +import os +import sys +from distutils.cmd import Command as DistutilsCommand +from distutils.command.install import SCHEME_KEYS +from distutils.command.install import install as distutils_install_command +from distutils.sysconfig import get_python_lib +from typing import Dict, List, Optional, Tuple, Union, cast + +from pipenv.patched.notpip._internal.models.scheme import Scheme +from pipenv.patched.notpip._internal.utils.compat import WINDOWS +from pipenv.patched.notpip._internal.utils.virtualenv import running_under_virtualenv + +from .base import get_major_minor_version + +logger = logging.getLogger(__name__) + + +def distutils_scheme( + dist_name: str, + user: bool = False, + home: str = None, + root: str = None, + isolated: bool = False, + prefix: str = None, + *, + ignore_config_files: bool = False, +) -> Dict[str, str]: + """ + Return a distutils install scheme + """ + from distutils.dist import Distribution + + dist_args: Dict[str, Union[str, List[str]]] = {"name": dist_name} + if isolated: + dist_args["script_args"] = ["--no-user-cfg"] + + d = Distribution(dist_args) + if not ignore_config_files: + try: + d.parse_config_files() + except UnicodeDecodeError: + # Typeshed does not include find_config_files() for some reason. + paths = d.find_config_files() # type: ignore + logger.warning( + "Ignore distutils configs in %s due to encoding errors.", + ", ".join(os.path.basename(p) for p in paths), + ) + obj: Optional[DistutilsCommand] = None + obj = d.get_command_obj("install", create=True) + assert obj is not None + i = cast(distutils_install_command, obj) + # NOTE: setting user or home has the side-effect of creating the home dir + # or user base for installations during finalize_options() + # ideally, we'd prefer a scheme class that has no side-effects. + assert not (user and prefix), f"user={user} prefix={prefix}" + assert not (home and prefix), f"home={home} prefix={prefix}" + i.user = user or i.user + if user or home: + i.prefix = "" + i.prefix = prefix or i.prefix + i.home = home or i.home + i.root = root or i.root + i.finalize_options() + + scheme = {} + for key in SCHEME_KEYS: + scheme[key] = getattr(i, "install_" + key) + + # install_lib specified in setup.cfg should install *everything* + # into there (i.e. it takes precedence over both purelib and + # platlib). Note, i.install_lib is *always* set after + # finalize_options(); we only want to override here if the user + # has explicitly requested it hence going back to the config + if "install_lib" in d.get_option_dict("install"): + scheme.update(dict(purelib=i.install_lib, platlib=i.install_lib)) + + if running_under_virtualenv(): + scheme["headers"] = os.path.join( + i.prefix, + "include", + "site", + f"python{get_major_minor_version()}", + dist_name, + ) + + if root is not None: + path_no_drive = os.path.splitdrive(os.path.abspath(scheme["headers"]))[1] + scheme["headers"] = os.path.join( + root, + path_no_drive[1:], + ) + + return scheme + + +def get_scheme( + dist_name: str, + user: bool = False, + home: Optional[str] = None, + root: Optional[str] = None, + isolated: bool = False, + prefix: Optional[str] = None, +) -> Scheme: + """ + Get the "scheme" corresponding to the input parameters. The distutils + documentation provides the context for the available schemes: + https://docs.python.org/3/install/index.html#alternate-installation + + :param dist_name: the name of the package to retrieve the scheme for, used + in the headers scheme path + :param user: indicates to use the "user" scheme + :param home: indicates to use the "home" scheme and provides the base + directory for the same + :param root: root under which other directories are re-based + :param isolated: equivalent to --no-user-cfg, i.e. do not consider + ~/.pydistutils.cfg (posix) or ~/pydistutils.cfg (non-posix) for + scheme paths + :param prefix: indicates to use the "prefix" scheme and provides the + base directory for the same + """ + scheme = distutils_scheme(dist_name, user, home, root, isolated, prefix) + return Scheme( + platlib=scheme["platlib"], + purelib=scheme["purelib"], + headers=scheme["headers"], + scripts=scheme["scripts"], + data=scheme["data"], + ) + + +def get_bin_prefix() -> str: + if WINDOWS: + bin_py = os.path.join(sys.prefix, "Scripts") + # buildout uses 'bin' on Windows too? + if not os.path.exists(bin_py): + bin_py = os.path.join(sys.prefix, "bin") + return bin_py + # Forcing to use /usr/local/bin for standard macOS framework installs + # Also log to ~/Library/Logs/ for use with the Console.app log viewer + if sys.platform[:6] == "darwin" and sys.prefix[:16] == "/System/Library/": + return "/usr/local/bin" + return os.path.join(sys.prefix, "bin") + + +def get_purelib() -> str: + return get_python_lib(plat_specific=False) + + +def get_platlib() -> str: + return get_python_lib(plat_specific=True) + + +def get_prefixed_libs(prefix: str) -> Tuple[str, str]: + return ( + get_python_lib(plat_specific=False, prefix=prefix), + get_python_lib(plat_specific=True, prefix=prefix), + ) diff --git a/pipenv/patched/notpip/_internal/locations/_sysconfig.py b/pipenv/patched/notpip/_internal/locations/_sysconfig.py new file mode 100644 index 00000000..6d82ac3b --- /dev/null +++ b/pipenv/patched/notpip/_internal/locations/_sysconfig.py @@ -0,0 +1,188 @@ +import distutils.util # FIXME: For change_root. +import logging +import os +import sys +import sysconfig +import typing + +from pipenv.patched.notpip._internal.exceptions import InvalidSchemeCombination, UserInstallationInvalid +from pipenv.patched.notpip._internal.models.scheme import SCHEME_KEYS, Scheme +from pipenv.patched.notpip._internal.utils.virtualenv import running_under_virtualenv + +from .base import get_major_minor_version, is_osx_framework + +logger = logging.getLogger(__name__) + + +# Notes on _infer_* functions. +# Unfortunately ``_get_default_scheme()`` is private, so there's no way to +# ask things like "what is the '_prefix' scheme on this platform". These +# functions try to answer that with some heuristics while accounting for ad-hoc +# platforms not covered by CPython's default sysconfig implementation. If the +# ad-hoc implementation does not fully implement sysconfig, we'll fall back to +# a POSIX scheme. + +_AVAILABLE_SCHEMES = set(sysconfig.get_scheme_names()) + +_HAS_PREFERRED_SCHEME_API = sys.version_info >= (3, 10) + + +def _infer_prefix() -> str: + """Try to find a prefix scheme for the current platform. + + This tries: + + * A special ``osx_framework_library`` for Python distributed by Apple's + Command Line Tools, when not running in a virtual environment. + * Implementation + OS, used by PyPy on Windows (``pypy_nt``). + * Implementation without OS, used by PyPy on POSIX (``pypy``). + * OS + "prefix", used by CPython on POSIX (``posix_prefix``). + * Just the OS name, used by CPython on Windows (``nt``). + + If none of the above works, fall back to ``posix_prefix``. + """ + if _HAS_PREFERRED_SCHEME_API: + return sysconfig.get_preferred_scheme("prefix") # type: ignore + os_framework_global = is_osx_framework() and not running_under_virtualenv() + if os_framework_global and "osx_framework_library" in _AVAILABLE_SCHEMES: + return "osx_framework_library" + implementation_suffixed = f"{sys.implementation.name}_{os.name}" + if implementation_suffixed in _AVAILABLE_SCHEMES: + return implementation_suffixed + if sys.implementation.name in _AVAILABLE_SCHEMES: + return sys.implementation.name + suffixed = f"{os.name}_prefix" + if suffixed in _AVAILABLE_SCHEMES: + return suffixed + if os.name in _AVAILABLE_SCHEMES: # On Windows, prefx is just called "nt". + return os.name + return "posix_prefix" + + +def _infer_user() -> str: + """Try to find a user scheme for the current platform.""" + if _HAS_PREFERRED_SCHEME_API: + return sysconfig.get_preferred_scheme("user") # type: ignore + if is_osx_framework() and not running_under_virtualenv(): + suffixed = "osx_framework_user" + else: + suffixed = f"{os.name}_user" + if suffixed in _AVAILABLE_SCHEMES: + return suffixed + if "posix_user" not in _AVAILABLE_SCHEMES: # User scheme unavailable. + raise UserInstallationInvalid() + return "posix_user" + + +def _infer_home() -> str: + """Try to find a home for the current platform.""" + if _HAS_PREFERRED_SCHEME_API: + return sysconfig.get_preferred_scheme("home") # type: ignore + suffixed = f"{os.name}_home" + if suffixed in _AVAILABLE_SCHEMES: + return suffixed + return "posix_home" + + +# Update these keys if the user sets a custom home. +_HOME_KEYS = [ + "installed_base", + "base", + "installed_platbase", + "platbase", + "prefix", + "exec_prefix", +] +if sysconfig.get_config_var("userbase") is not None: + _HOME_KEYS.append("userbase") + + +def get_scheme( + dist_name: str, + user: bool = False, + home: typing.Optional[str] = None, + root: typing.Optional[str] = None, + isolated: bool = False, + prefix: typing.Optional[str] = None, +) -> Scheme: + """ + Get the "scheme" corresponding to the input parameters. + + :param dist_name: the name of the package to retrieve the scheme for, used + in the headers scheme path + :param user: indicates to use the "user" scheme + :param home: indicates to use the "home" scheme + :param root: root under which other directories are re-based + :param isolated: ignored, but kept for distutils compatibility (where + this controls whether the user-site pydistutils.cfg is honored) + :param prefix: indicates to use the "prefix" scheme and provides the + base directory for the same + """ + if user and prefix: + raise InvalidSchemeCombination("--user", "--prefix") + if home and prefix: + raise InvalidSchemeCombination("--home", "--prefix") + + if home is not None: + scheme_name = _infer_home() + elif user: + scheme_name = _infer_user() + else: + scheme_name = _infer_prefix() + + if home is not None: + variables = {k: home for k in _HOME_KEYS} + elif prefix is not None: + variables = {k: prefix for k in _HOME_KEYS} + else: + variables = {} + + paths = sysconfig.get_paths(scheme=scheme_name, vars=variables) + + # Logic here is very arbitrary, we're doing it for compatibility, don't ask. + # 1. Pip historically uses a special header path in virtual environments. + # 2. If the distribution name is not known, distutils uses 'UNKNOWN'. We + # only do the same when not running in a virtual environment because + # pip's historical header path logic (see point 1) did not do this. + if running_under_virtualenv(): + if user: + base = variables.get("userbase", sys.prefix) + else: + base = variables.get("base", sys.prefix) + python_xy = f"python{get_major_minor_version()}" + paths["include"] = os.path.join(base, "include", "site", python_xy) + elif not dist_name: + dist_name = "UNKNOWN" + + scheme = Scheme( + platlib=paths["platlib"], + purelib=paths["purelib"], + headers=os.path.join(paths["include"], dist_name), + scripts=paths["scripts"], + data=paths["data"], + ) + if root is not None: + for key in SCHEME_KEYS: + value = distutils.util.change_root(root, getattr(scheme, key)) + setattr(scheme, key, value) + return scheme + + +def get_bin_prefix() -> str: + # Forcing to use /usr/local/bin for standard macOS framework installs. + if sys.platform[:6] == "darwin" and sys.prefix[:16] == "/System/Library/": + return "/usr/local/bin" + return sysconfig.get_paths()["scripts"] + + +def get_purelib() -> str: + return sysconfig.get_paths()["purelib"] + + +def get_platlib() -> str: + return sysconfig.get_paths()["platlib"] + + +def get_prefixed_libs(prefix: str) -> typing.Tuple[str, str]: + paths = sysconfig.get_paths(vars={"base": prefix, "platbase": prefix}) + return (paths["purelib"], paths["platlib"]) diff --git a/pipenv/patched/notpip/_internal/locations/base.py b/pipenv/patched/notpip/_internal/locations/base.py new file mode 100644 index 00000000..8a248dec --- /dev/null +++ b/pipenv/patched/notpip/_internal/locations/base.py @@ -0,0 +1,52 @@ +import functools +import os +import site +import sys +import sysconfig +import typing + +from pipenv.patched.notpip._internal.utils import appdirs +from pipenv.patched.notpip._internal.utils.virtualenv import running_under_virtualenv + +# Application Directories +USER_CACHE_DIR = appdirs.user_cache_dir("pip") + +# FIXME doesn't account for venv linked to global site-packages +site_packages: typing.Optional[str] = sysconfig.get_path("purelib") + + +def get_major_minor_version() -> str: + """ + Return the major-minor version of the current Python as a string, e.g. + "3.7" or "3.10". + """ + return "{}.{}".format(*sys.version_info) + + +def get_src_prefix() -> str: + if running_under_virtualenv(): + src_prefix = os.path.join(sys.prefix, "src") + else: + # FIXME: keep src in cwd for now (it is not a temporary folder) + try: + src_prefix = os.path.join(os.getcwd(), "src") + except OSError: + # In case the current working directory has been renamed or deleted + sys.exit("The folder you are executing pip from can no longer be found.") + + # under macOS + virtualenv sys.prefix is not properly resolved + # it is something like /path/to/python/bin/.. + return os.path.abspath(src_prefix) + + +try: + # Use getusersitepackages if this is present, as it ensures that the + # value is initialised properly. + user_site: typing.Optional[str] = site.getusersitepackages() +except AttributeError: + user_site = site.USER_SITE + + +@functools.lru_cache(maxsize=None) +def is_osx_framework() -> bool: + return bool(sysconfig.get_config_var("PYTHONFRAMEWORK")) diff --git a/pipenv/patched/notpip/_internal/main.py b/pipenv/patched/notpip/_internal/main.py new file mode 100644 index 00000000..d57e93c9 --- /dev/null +++ b/pipenv/patched/notpip/_internal/main.py @@ -0,0 +1,13 @@ +from typing import List, Optional + + +def main(args=None): + # type: (Optional[List[str]]) -> int + """This is preserved for old console scripts that may still be referencing + it. + + For additional details, see https://github.com/pypa/pip/issues/7498. + """ + from pipenv.patched.notpip._internal.utils.entrypoints import _wrapper + + return _wrapper(args) diff --git a/pipenv/patched/notpip/_internal/metadata/__init__.py b/pipenv/patched/notpip/_internal/metadata/__init__.py new file mode 100644 index 00000000..e3429d25 --- /dev/null +++ b/pipenv/patched/notpip/_internal/metadata/__init__.py @@ -0,0 +1,48 @@ +from typing import List, Optional + +from .base import BaseDistribution, BaseEnvironment + +__all__ = [ + "BaseDistribution", + "BaseEnvironment", + "get_default_environment", + "get_environment", + "get_wheel_distribution", +] + + +def get_default_environment() -> BaseEnvironment: + """Get the default representation for the current environment. + + This returns an Environment instance from the chosen backend. The default + Environment instance should be built from ``sys.path`` and may use caching + to share instance state accorss calls. + """ + from .pkg_resources import Environment + + return Environment.default() + + +def get_environment(paths: Optional[List[str]]) -> BaseEnvironment: + """Get a representation of the environment specified by ``paths``. + + This returns an Environment instance from the chosen backend based on the + given import paths. The backend must build a fresh instance representing + the state of installed distributions when this function is called. + """ + from .pkg_resources import Environment + + return Environment.from_paths(paths) + + +def get_wheel_distribution(wheel_path: str, canonical_name: str) -> BaseDistribution: + """Get the representation of the specified wheel's distribution metadata. + + This returns a Distribution instance from the chosen backend based on + the given wheel's ``.dist-info`` directory. + + :param canonical_name: Normalized project name of the given wheel. + """ + from .pkg_resources import Distribution + + return Distribution.from_wheel(wheel_path, canonical_name) diff --git a/pipenv/patched/notpip/_internal/metadata/base.py b/pipenv/patched/notpip/_internal/metadata/base.py new file mode 100644 index 00000000..af3f811c --- /dev/null +++ b/pipenv/patched/notpip/_internal/metadata/base.py @@ -0,0 +1,242 @@ +import email.message +import json +import logging +import re +from typing import ( + TYPE_CHECKING, + Collection, + Container, + Iterable, + Iterator, + List, + Optional, + Union, +) + +from pipenv.patched.notpip._vendor.packaging.requirements import Requirement +from pipenv.patched.notpip._vendor.packaging.version import LegacyVersion, Version + +from pipenv.patched.notpip._internal.models.direct_url import ( + DIRECT_URL_METADATA_NAME, + DirectUrl, + DirectUrlValidationError, +) +from pipenv.patched.notpip._internal.utils.misc import stdlib_pkgs # TODO: Move definition here. + +if TYPE_CHECKING: + from typing import Protocol + + from pipenv.patched.notpip._vendor.packaging.utils import NormalizedName +else: + Protocol = object + +DistributionVersion = Union[LegacyVersion, Version] + +logger = logging.getLogger(__name__) + + +class BaseEntryPoint(Protocol): + @property + def name(self) -> str: + raise NotImplementedError() + + @property + def value(self) -> str: + raise NotImplementedError() + + @property + def group(self) -> str: + raise NotImplementedError() + + +class BaseDistribution(Protocol): + @property + def location(self) -> Optional[str]: + """Where the distribution is loaded from. + + A string value is not necessarily a filesystem path, since distributions + can be loaded from other sources, e.g. arbitrary zip archives. ``None`` + means the distribution is created in-memory. + + Do not canonicalize this value with e.g. ``pathlib.Path.resolve()``. If + this is a symbolic link, we want to preserve the relative path between + it and files in the distribution. + """ + raise NotImplementedError() + + @property + def info_directory(self) -> Optional[str]: + """Location of the .[egg|dist]-info directory. + + Similarly to ``location``, a string value is not necessarily a + filesystem path. ``None`` means the distribution is created in-memory. + + For a modern .dist-info installation on disk, this should be something + like ``{location}/{raw_name}-{version}.dist-info``. + + Do not canonicalize this value with e.g. ``pathlib.Path.resolve()``. If + this is a symbolic link, we want to preserve the relative path between + it and other files in the distribution. + """ + raise NotImplementedError() + + @property + def canonical_name(self) -> "NormalizedName": + raise NotImplementedError() + + @property + def version(self) -> DistributionVersion: + raise NotImplementedError() + + @property + def direct_url(self) -> Optional[DirectUrl]: + """Obtain a DirectUrl from this distribution. + + Returns None if the distribution has no `direct_url.json` metadata, + or if `direct_url.json` is invalid. + """ + try: + content = self.read_text(DIRECT_URL_METADATA_NAME) + except FileNotFoundError: + return None + try: + return DirectUrl.from_json(content) + except ( + UnicodeDecodeError, + json.JSONDecodeError, + DirectUrlValidationError, + ) as e: + logger.warning( + "Error parsing %s for %s: %s", + DIRECT_URL_METADATA_NAME, + self.canonical_name, + e, + ) + return None + + @property + def installer(self) -> str: + raise NotImplementedError() + + @property + def editable(self) -> bool: + raise NotImplementedError() + + @property + def local(self) -> bool: + raise NotImplementedError() + + @property + def in_usersite(self) -> bool: + raise NotImplementedError() + + @property + def in_site_packages(self) -> bool: + raise NotImplementedError() + + def read_text(self, name: str) -> str: + """Read a file in the .dist-info (or .egg-info) directory. + + Should raise ``FileNotFoundError`` if ``name`` does not exist in the + metadata directory. + """ + raise NotImplementedError() + + def iter_entry_points(self) -> Iterable[BaseEntryPoint]: + raise NotImplementedError() + + @property + def metadata(self) -> email.message.Message: + """Metadata of distribution parsed from e.g. METADATA or PKG-INFO.""" + raise NotImplementedError() + + @property + def metadata_version(self) -> Optional[str]: + """Value of "Metadata-Version:" in distribution metadata, if available.""" + return self.metadata.get("Metadata-Version") + + @property + def raw_name(self) -> str: + """Value of "Name:" in distribution metadata.""" + # The metadata should NEVER be missing the Name: key, but if it somehow + # does not, fall back to the known canonical name. + return self.metadata.get("Name", self.canonical_name) + + def iter_dependencies(self, extras: Collection[str] = ()) -> Iterable[Requirement]: + raise NotImplementedError() + + +class BaseEnvironment: + """An environment containing distributions to introspect.""" + + @classmethod + def default(cls) -> "BaseEnvironment": + raise NotImplementedError() + + @classmethod + def from_paths(cls, paths: Optional[List[str]]) -> "BaseEnvironment": + raise NotImplementedError() + + def get_distribution(self, name: str) -> Optional["BaseDistribution"]: + """Given a requirement name, return the installed distributions.""" + raise NotImplementedError() + + def _iter_distributions(self) -> Iterator["BaseDistribution"]: + """Iterate through installed distributions. + + This function should be implemented by subclass, but never called + directly. Use the public ``iter_distribution()`` instead, which + implements additional logic to make sure the distributions are valid. + """ + raise NotImplementedError() + + def iter_distributions(self) -> Iterator["BaseDistribution"]: + """Iterate through installed distributions.""" + for dist in self._iter_distributions(): + # Make sure the distribution actually comes from a valid Python + # packaging distribution. Pip's AdjacentTempDirectory leaves folders + # e.g. ``~atplotlib.dist-info`` if cleanup was interrupted. The + # valid project name pattern is taken from PEP 508. + project_name_valid = re.match( + r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", + dist.canonical_name, + flags=re.IGNORECASE, + ) + if not project_name_valid: + logger.warning( + "Ignoring invalid distribution %s (%s)", + dist.canonical_name, + dist.location, + ) + continue + yield dist + + def iter_installed_distributions( + self, + local_only: bool = True, + skip: Container[str] = stdlib_pkgs, + include_editables: bool = True, + editables_only: bool = False, + user_only: bool = False, + ) -> Iterator[BaseDistribution]: + """Return a list of installed distributions. + + :param local_only: If True (default), only return installations + local to the current virtualenv, if in a virtualenv. + :param skip: An iterable of canonicalized project names to ignore; + defaults to ``stdlib_pkgs``. + :param include_editables: If False, don't report editables. + :param editables_only: If True, only report editables. + :param user_only: If True, only report installations in the user + site directory. + """ + it = self.iter_distributions() + if local_only: + it = (d for d in it if d.local) + if not include_editables: + it = (d for d in it if not d.editable) + if editables_only: + it = (d for d in it if d.editable) + if user_only: + it = (d for d in it if d.in_usersite) + return (d for d in it if d.canonical_name not in skip) diff --git a/pipenv/patched/notpip/_internal/metadata/pkg_resources.py b/pipenv/patched/notpip/_internal/metadata/pkg_resources.py new file mode 100644 index 00000000..179a0831 --- /dev/null +++ b/pipenv/patched/notpip/_internal/metadata/pkg_resources.py @@ -0,0 +1,153 @@ +import email.message +import logging +import zipfile +from typing import ( + TYPE_CHECKING, + Collection, + Iterable, + Iterator, + List, + NamedTuple, + Optional, +) + +from pipenv.patched.notpip._vendor import pkg_resources +from pipenv.patched.notpip._vendor.packaging.requirements import Requirement +from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name +from pipenv.patched.notpip._vendor.packaging.version import parse as parse_version + +from pipenv.patched.notpip._internal.utils import misc # TODO: Move definition here. +from pipenv.patched.notpip._internal.utils.packaging import get_installer, get_metadata +from pipenv.patched.notpip._internal.utils.wheel import pkg_resources_distribution_for_wheel + +from .base import BaseDistribution, BaseEntryPoint, BaseEnvironment, DistributionVersion + +if TYPE_CHECKING: + from pipenv.patched.notpip._vendor.packaging.utils import NormalizedName + +logger = logging.getLogger(__name__) + + +class EntryPoint(NamedTuple): + name: str + value: str + group: str + + +class Distribution(BaseDistribution): + def __init__(self, dist: pkg_resources.Distribution) -> None: + self._dist = dist + + @classmethod + def from_wheel(cls, path: str, name: str) -> "Distribution": + with zipfile.ZipFile(path, allowZip64=True) as zf: + dist = pkg_resources_distribution_for_wheel(zf, name, path) + return cls(dist) + + @property + def location(self) -> Optional[str]: + return self._dist.location + + @property + def info_directory(self) -> Optional[str]: + return self._dist.egg_info + + @property + def canonical_name(self) -> "NormalizedName": + return canonicalize_name(self._dist.project_name) + + @property + def version(self) -> DistributionVersion: + return parse_version(self._dist.version) + + @property + def installer(self) -> str: + return get_installer(self._dist) + + @property + def editable(self) -> bool: + return misc.dist_is_editable(self._dist) + + @property + def local(self) -> bool: + return misc.dist_is_local(self._dist) + + @property + def in_usersite(self) -> bool: + return misc.dist_in_usersite(self._dist) + + @property + def in_site_packages(self) -> bool: + return misc.dist_in_site_packages(self._dist) + + def read_text(self, name: str) -> str: + if not self._dist.has_metadata(name): + raise FileNotFoundError(name) + return self._dist.get_metadata(name) + + def iter_entry_points(self) -> Iterable[BaseEntryPoint]: + for group, entries in self._dist.get_entry_map().items(): + for name, entry_point in entries.items(): + name, _, value = str(entry_point).partition("=") + yield EntryPoint(name=name.strip(), value=value.strip(), group=group) + + @property + def metadata(self) -> email.message.Message: + return get_metadata(self._dist) + + def iter_dependencies(self, extras: Collection[str] = ()) -> Iterable[Requirement]: + if extras: # pkg_resources raises on invalid extras, so we sanitize. + extras = frozenset(extras).intersection(self._dist.extras) + return self._dist.requires(extras) + + +class Environment(BaseEnvironment): + def __init__(self, ws: pkg_resources.WorkingSet) -> None: + self._ws = ws + + @classmethod + def default(cls) -> BaseEnvironment: + return cls(pkg_resources.working_set) + + @classmethod + def from_paths(cls, paths: Optional[List[str]]) -> BaseEnvironment: + return cls(pkg_resources.WorkingSet(paths)) + + def _search_distribution(self, name: str) -> Optional[BaseDistribution]: + """Find a distribution matching the ``name`` in the environment. + + This searches from *all* distributions available in the environment, to + match the behavior of ``pkg_resources.get_distribution()``. + """ + canonical_name = canonicalize_name(name) + for dist in self.iter_distributions(): + if dist.canonical_name == canonical_name: + return dist + return None + + def get_distribution(self, name: str) -> Optional[BaseDistribution]: + + # Search the distribution by looking through the working set. + dist = self._search_distribution(name) + if dist: + return dist + + # If distribution could not be found, call working_set.require to + # update the working set, and try to find the distribution again. + # This might happen for e.g. when you install a package twice, once + # using setup.py develop and again using setup.py install. Now when + # running pip uninstall twice, the package gets removed from the + # working set in the first uninstall, so we have to populate the + # working set again so that pip knows about it and the packages gets + # picked up and is successfully uninstalled the second time too. + try: + # We didn't pass in any version specifiers, so this can never + # raise pkg_resources.VersionConflict. + self._ws.require(name) + except pkg_resources.DistributionNotFound: + return None + return self._search_distribution(name) + + def _iter_distributions(self) -> Iterator[BaseDistribution]: + for dist in self._ws: + yield Distribution(dist) diff --git a/pipenv/patched/notpip/_internal/models/candidate.py b/pipenv/patched/notpip/_internal/models/candidate.py index adc3550e..ea8227cf 100644 --- a/pipenv/patched/notpip/_internal/models/candidate.py +++ b/pipenv/patched/notpip/_internal/models/candidate.py @@ -1,32 +1,31 @@ from pipenv.patched.notpip._vendor.packaging.version import parse as parse_version +from pipenv.patched.notpip._internal.models.link import Link from pipenv.patched.notpip._internal.utils.models import KeyBasedCompareMixin -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING - -if MYPY_CHECK_RUNNING: - from pipenv.patched.notpip._vendor.packaging.version import _BaseVersion # noqa: F401 - from pipenv.patched.notpip._internal.models.link import Link # noqa: F401 - from typing import Any, Union # noqa: F401 class InstallationCandidate(KeyBasedCompareMixin): """Represents a potential "candidate" for installation. """ - def __init__(self, project, version, location, requires_python=None): - # type: (Any, str, Link, Any) -> None - self.project = project - self.version = parse_version(version) # type: _BaseVersion - self.location = location - self.requires_python = requires_python + __slots__ = ["name", "version", "link"] - super(InstallationCandidate, self).__init__( - key=(self.project, self.version, self.location), + def __init__(self, name: str, version: str, link: Link) -> None: + self.name = name + self.version = parse_version(version) + self.link = link + + super().__init__( + key=(self.name, self.version, self.link), defining_class=InstallationCandidate ) - def __repr__(self): - # type: () -> str + def __repr__(self) -> str: return "".format( - self.project, self.version, self.location, + self.name, self.version, self.link, + ) + + def __str__(self) -> str: + return '{!r} candidate (version {} at {})'.format( + self.name, self.version, self.link, ) diff --git a/pipenv/patched/notpip/_internal/models/direct_url.py b/pipenv/patched/notpip/_internal/models/direct_url.py new file mode 100644 index 00000000..3f9b6993 --- /dev/null +++ b/pipenv/patched/notpip/_internal/models/direct_url.py @@ -0,0 +1,220 @@ +""" PEP 610 """ +import json +import re +import urllib.parse +from typing import Any, Dict, Iterable, Optional, Type, TypeVar, Union + +__all__ = [ + "DirectUrl", + "DirectUrlValidationError", + "DirInfo", + "ArchiveInfo", + "VcsInfo", +] + +T = TypeVar("T") + +DIRECT_URL_METADATA_NAME = "direct_url.json" +ENV_VAR_RE = re.compile(r"^\$\{[A-Za-z0-9-_]+\}(:\$\{[A-Za-z0-9-_]+\})?$") + + +class DirectUrlValidationError(Exception): + pass + + +def _get( + d: Dict[str, Any], expected_type: Type[T], key: str, default: Optional[T] = None +) -> Optional[T]: + """Get value from dictionary and verify expected type.""" + if key not in d: + return default + value = d[key] + if not isinstance(value, expected_type): + raise DirectUrlValidationError( + "{!r} has unexpected type for {} (expected {})".format( + value, key, expected_type + ) + ) + return value + + +def _get_required( + d: Dict[str, Any], expected_type: Type[T], key: str, default: Optional[T] = None +) -> T: + value = _get(d, expected_type, key, default) + if value is None: + raise DirectUrlValidationError(f"{key} must have a value") + return value + + +def _exactly_one_of(infos: Iterable[Optional["InfoType"]]) -> "InfoType": + infos = [info for info in infos if info is not None] + if not infos: + raise DirectUrlValidationError( + "missing one of archive_info, dir_info, vcs_info" + ) + if len(infos) > 1: + raise DirectUrlValidationError( + "more than one of archive_info, dir_info, vcs_info" + ) + assert infos[0] is not None + return infos[0] + + +def _filter_none(**kwargs: Any) -> Dict[str, Any]: + """Make dict excluding None values.""" + return {k: v for k, v in kwargs.items() if v is not None} + + +class VcsInfo: + name = "vcs_info" + + def __init__( + self, + vcs: str, + commit_id: str, + requested_revision: Optional[str] = None, + resolved_revision: Optional[str] = None, + resolved_revision_type: Optional[str] = None, + ) -> None: + self.vcs = vcs + self.requested_revision = requested_revision + self.commit_id = commit_id + self.resolved_revision = resolved_revision + self.resolved_revision_type = resolved_revision_type + + @classmethod + def _from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional["VcsInfo"]: + if d is None: + return None + return cls( + vcs=_get_required(d, str, "vcs"), + commit_id=_get_required(d, str, "commit_id"), + requested_revision=_get(d, str, "requested_revision"), + resolved_revision=_get(d, str, "resolved_revision"), + resolved_revision_type=_get(d, str, "resolved_revision_type"), + ) + + def _to_dict(self) -> Dict[str, Any]: + return _filter_none( + vcs=self.vcs, + requested_revision=self.requested_revision, + commit_id=self.commit_id, + resolved_revision=self.resolved_revision, + resolved_revision_type=self.resolved_revision_type, + ) + + +class ArchiveInfo: + name = "archive_info" + + def __init__( + self, + hash: Optional[str] = None, + ) -> None: + self.hash = hash + + @classmethod + def _from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional["ArchiveInfo"]: + if d is None: + return None + return cls(hash=_get(d, str, "hash")) + + def _to_dict(self) -> Dict[str, Any]: + return _filter_none(hash=self.hash) + + +class DirInfo: + name = "dir_info" + + def __init__( + self, + editable: bool = False, + ) -> None: + self.editable = editable + + @classmethod + def _from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional["DirInfo"]: + if d is None: + return None + return cls( + editable=_get_required(d, bool, "editable", default=False) + ) + + def _to_dict(self) -> Dict[str, Any]: + return _filter_none(editable=self.editable or None) + + +InfoType = Union[ArchiveInfo, DirInfo, VcsInfo] + + +class DirectUrl: + + def __init__( + self, + url: str, + info: InfoType, + subdirectory: Optional[str] = None, + ) -> None: + self.url = url + self.info = info + self.subdirectory = subdirectory + + def _remove_auth_from_netloc(self, netloc: str) -> str: + if "@" not in netloc: + return netloc + user_pass, netloc_no_user_pass = netloc.split("@", 1) + if ( + isinstance(self.info, VcsInfo) and + self.info.vcs == "git" and + user_pass == "git" + ): + return netloc + if ENV_VAR_RE.match(user_pass): + return netloc + return netloc_no_user_pass + + @property + def redacted_url(self) -> str: + """url with user:password part removed unless it is formed with + environment variables as specified in PEP 610, or it is ``git`` + in the case of a git URL. + """ + purl = urllib.parse.urlsplit(self.url) + netloc = self._remove_auth_from_netloc(purl.netloc) + surl = urllib.parse.urlunsplit( + (purl.scheme, netloc, purl.path, purl.query, purl.fragment) + ) + return surl + + def validate(self) -> None: + self.from_dict(self.to_dict()) + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> "DirectUrl": + return DirectUrl( + url=_get_required(d, str, "url"), + subdirectory=_get(d, str, "subdirectory"), + info=_exactly_one_of( + [ + ArchiveInfo._from_dict(_get(d, dict, "archive_info")), + DirInfo._from_dict(_get(d, dict, "dir_info")), + VcsInfo._from_dict(_get(d, dict, "vcs_info")), + ] + ), + ) + + def to_dict(self) -> Dict[str, Any]: + res = _filter_none( + url=self.redacted_url, + subdirectory=self.subdirectory, + ) + res[self.info.name] = self.info._to_dict() + return res + + @classmethod + def from_json(cls, s: str) -> "DirectUrl": + return cls.from_dict(json.loads(s)) + + def to_json(self) -> str: + return json.dumps(self.to_dict(), sort_keys=True) diff --git a/pipenv/patched/notpip/_internal/models/format_control.py b/pipenv/patched/notpip/_internal/models/format_control.py index 7172ad9f..40eae295 100644 --- a/pipenv/patched/notpip/_internal/models/format_control.py +++ b/pipenv/patched/notpip/_internal/models/format_control.py @@ -1,17 +1,21 @@ +from typing import FrozenSet, Optional, Set + from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING - -if MYPY_CHECK_RUNNING: - from typing import Optional, Set, FrozenSet # noqa: F401 +from pipenv.patched.notpip._internal.exceptions import CommandError -class FormatControl(object): +class FormatControl: """Helper for managing formats from which a package can be installed. """ - def __init__(self, no_binary=None, only_binary=None): - # type: (Optional[Set], Optional[Set]) -> None + __slots__ = ["no_binary", "only_binary"] + + def __init__( + self, + no_binary: Optional[Set[str]] = None, + only_binary: Optional[Set[str]] = None + ) -> None: if no_binary is None: no_binary = set() if only_binary is None: @@ -20,13 +24,19 @@ class FormatControl(object): self.no_binary = no_binary self.only_binary = only_binary - def __eq__(self, other): - return self.__dict__ == other.__dict__ + def __eq__(self, other: object) -> bool: + if not isinstance(other, self.__class__): + return NotImplemented - def __ne__(self, other): - return not self.__eq__(other) + if self.__slots__ != other.__slots__: + return False - def __repr__(self): + return all( + getattr(self, k) == getattr(other, k) + for k in self.__slots__ + ) + + def __repr__(self) -> str: return "{}({}, {})".format( self.__class__.__name__, self.no_binary, @@ -34,8 +44,11 @@ class FormatControl(object): ) @staticmethod - def handle_mutual_excludes(value, target, other): - # type: (str, Optional[Set], Optional[Set]) -> None + def handle_mutual_excludes(value: str, target: Set[str], other: Set[str]) -> None: + if value.startswith('-'): + raise CommandError( + "--no-binary / --only-binary option requires 1 argument." + ) new = value.split(',') while ':all:' in new: other.clear() @@ -53,8 +66,7 @@ class FormatControl(object): other.discard(name) target.add(name) - def get_allowed_formats(self, canonical_name): - # type: (str) -> FrozenSet + def get_allowed_formats(self, canonical_name: str) -> FrozenSet[str]: result = {"binary", "source"} if canonical_name in self.only_binary: result.discard('source') @@ -66,8 +78,7 @@ class FormatControl(object): result.discard('binary') return frozenset(result) - def disallow_binaries(self): - # type: () -> None + def disallow_binaries(self) -> None: self.handle_mutual_excludes( ':all:', self.no_binary, self.only_binary, ) diff --git a/pipenv/patched/notpip/_internal/models/index.py b/pipenv/patched/notpip/_internal/models/index.py index b2895dab..1874a5b6 100644 --- a/pipenv/patched/notpip/_internal/models/index.py +++ b/pipenv/patched/notpip/_internal/models/index.py @@ -1,15 +1,17 @@ -from pipenv.patched.notpip._vendor.six.moves.urllib import parse as urllib_parse +import urllib.parse -class PackageIndex(object): +class PackageIndex: """Represents a Package Index and provides easier access to endpoints """ - def __init__(self, url, file_storage_domain): - # type: (str, str) -> None - super(PackageIndex, self).__init__() + __slots__ = ['url', 'netloc', 'simple_url', 'pypi_url', + 'file_storage_domain'] + + def __init__(self, url: str, file_storage_domain: str) -> None: + super().__init__() self.url = url - self.netloc = urllib_parse.urlsplit(url).netloc + self.netloc = urllib.parse.urlsplit(url).netloc self.simple_url = self._url_for_path('simple') self.pypi_url = self._url_for_path('pypi') @@ -18,9 +20,8 @@ class PackageIndex(object): # block such packages themselves self.file_storage_domain = file_storage_domain - def _url_for_path(self, path): - # type: (str) -> str - return urllib_parse.urljoin(self.url, path) + def _url_for_path(self, path: str) -> str: + return urllib.parse.urljoin(self.url, path) PyPI = PackageIndex( diff --git a/pipenv/patched/notpip/_internal/models/link.py b/pipenv/patched/notpip/_internal/models/link.py index ded4de43..8baa9ac0 100644 --- a/pipenv/patched/notpip/_internal/models/link.py +++ b/pipenv/patched/notpip/_internal/models/link.py @@ -1,109 +1,157 @@ +import functools +import logging +import os import posixpath import re +import urllib.parse +from typing import TYPE_CHECKING, Dict, List, NamedTuple, Optional, Tuple, Union -from pipenv.patched.notpip._vendor.six.moves.urllib import parse as urllib_parse - -from pipenv.patched.notpip._internal.download import path_to_url +from pipenv.patched.notpip._internal.utils.filetypes import WHEEL_EXTENSION +from pipenv.patched.notpip._internal.utils.hashes import Hashes from pipenv.patched.notpip._internal.utils.misc import ( - WHEEL_EXTENSION, redact_password_from_url, splitext, + redact_auth_from_url, + split_auth_from_netloc, + splitext, ) from pipenv.patched.notpip._internal.utils.models import KeyBasedCompareMixin -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING +from pipenv.patched.notpip._internal.utils.urls import path_to_url, url_to_path -if MYPY_CHECK_RUNNING: - from typing import Optional, Tuple, Union, Text # noqa: F401 - from pipenv.patched.notpip._internal.index import HTMLPage # noqa: F401 +if TYPE_CHECKING: + from pipenv.patched.notpip._internal.index.collector import HTMLPage + +logger = logging.getLogger(__name__) + + +_SUPPORTED_HASHES = ("sha1", "sha224", "sha384", "sha256", "sha512", "md5") class Link(KeyBasedCompareMixin): """Represents a parsed link from a Package Index's simple URL """ - def __init__(self, url, comes_from=None, requires_python=None): - # type: (str, Optional[Union[str, HTMLPage]], Optional[str]) -> None + __slots__ = [ + "_parsed_url", + "_url", + "comes_from", + "requires_python", + "yanked_reason", + "cache_link_parsing", + ] + + def __init__( + self, + url: str, + comes_from: Optional[Union[str, "HTMLPage"]] = None, + requires_python: Optional[str] = None, + yanked_reason: Optional[str] = None, + cache_link_parsing: bool = True, + ) -> None: """ - url: - url of the resource pointed to (href of the link) - comes_from: - instance of HTMLPage where the link was found, or string. - requires_python: - String containing the `Requires-Python` metadata field, specified - in PEP 345. This may be specified by a data-requires-python - attribute in the HTML link tag, as described in PEP 503. + :param url: url of the resource pointed to (href of the link) + :param comes_from: instance of HTMLPage where the link was found, + or string. + :param requires_python: String containing the `Requires-Python` + metadata field, specified in PEP 345. This may be specified by + a data-requires-python attribute in the HTML link tag, as + described in PEP 503. + :param yanked_reason: the reason the file has been yanked, if the + file has been yanked, or None if the file hasn't been yanked. + This is the value of the "data-yanked" attribute, if present, in + a simple repository HTML link. If the file has been yanked but + no reason was provided, this should be the empty string. See + PEP 592 for more information and the specification. + :param cache_link_parsing: A flag that is used elsewhere to determine + whether resources retrieved from this link + should be cached. PyPI index urls should + generally have this set to False, for + example. """ # url can be a UNC windows share if url.startswith('\\\\'): url = path_to_url(url) - self.url = url + self._parsed_url = urllib.parse.urlsplit(url) + # Store the url as a private attribute to prevent accidentally + # trying to set a new value. + self._url = url + self.comes_from = comes_from self.requires_python = requires_python if requires_python else None + self.yanked_reason = yanked_reason - super(Link, self).__init__( - key=(self.url), - defining_class=Link - ) + super().__init__(key=url, defining_class=Link) - def __str__(self): + self.cache_link_parsing = cache_link_parsing + + def __str__(self) -> str: if self.requires_python: - rp = ' (requires-python:%s)' % self.requires_python + rp = f' (requires-python:{self.requires_python})' else: rp = '' if self.comes_from: - return '%s (from %s)%s' % (redact_password_from_url(self.url), - self.comes_from, rp) + return '{} (from {}){}'.format( + redact_auth_from_url(self._url), self.comes_from, rp) else: - return redact_password_from_url(str(self.url)) + return redact_auth_from_url(str(self._url)) - def __repr__(self): - return '' % self + def __repr__(self) -> str: + return f'' @property - def filename(self): - # type: () -> str - _, netloc, path, _, _ = urllib_parse.urlsplit(self.url) - name = posixpath.basename(path.rstrip('/')) or netloc - name = urllib_parse.unquote(name) - assert name, ('URL %r produced no filename' % self.url) + def url(self) -> str: + return self._url + + @property + def filename(self) -> str: + path = self.path.rstrip('/') + name = posixpath.basename(path) + if not name: + # Make sure we don't leak auth information if the netloc + # includes a username and password. + netloc, user_pass = split_auth_from_netloc(self.netloc) + return netloc + + name = urllib.parse.unquote(name) + assert name, f'URL {self._url!r} produced no filename' return name @property - def scheme(self): - # type: () -> str - return urllib_parse.urlsplit(self.url)[0] + def file_path(self) -> str: + return url_to_path(self.url) @property - def netloc(self): - # type: () -> str - return urllib_parse.urlsplit(self.url)[1] + def scheme(self) -> str: + return self._parsed_url.scheme @property - def path(self): - # type: () -> str - return urllib_parse.unquote(urllib_parse.urlsplit(self.url)[2]) + def netloc(self) -> str: + """ + This can contain auth information. + """ + return self._parsed_url.netloc - def splitext(self): - # type: () -> Tuple[str, str] + @property + def path(self) -> str: + return urllib.parse.unquote(self._parsed_url.path) + + def splitext(self) -> Tuple[str, str]: return splitext(posixpath.basename(self.path.rstrip('/'))) @property - def ext(self): - # type: () -> str + def ext(self) -> str: return self.splitext()[1] @property - def url_without_fragment(self): - # type: () -> str - scheme, netloc, path, query, fragment = urllib_parse.urlsplit(self.url) - return urllib_parse.urlunsplit((scheme, netloc, path, query, None)) + def url_without_fragment(self) -> str: + scheme, netloc, path, query, fragment = self._parsed_url + return urllib.parse.urlunsplit((scheme, netloc, path, query, '')) _egg_fragment_re = re.compile(r'[#&]egg=([^&]*)') @property - def egg_fragment(self): - # type: () -> Optional[str] - match = self._egg_fragment_re.search(self.url) + def egg_fragment(self) -> Optional[str]: + match = self._egg_fragment_re.search(self._url) if not match: return None return match.group(1) @@ -111,53 +159,130 @@ class Link(KeyBasedCompareMixin): _subdirectory_fragment_re = re.compile(r'[#&]subdirectory=([^&]*)') @property - def subdirectory_fragment(self): - # type: () -> Optional[str] - match = self._subdirectory_fragment_re.search(self.url) + def subdirectory_fragment(self) -> Optional[str]: + match = self._subdirectory_fragment_re.search(self._url) if not match: return None return match.group(1) _hash_re = re.compile( - r'(sha1|sha224|sha384|sha256|sha512|md5)=([a-f0-9]+)' + r'({choices})=([a-f0-9]+)'.format(choices="|".join(_SUPPORTED_HASHES)) ) @property - def hash(self): - # type: () -> Optional[str] - match = self._hash_re.search(self.url) + def hash(self) -> Optional[str]: + match = self._hash_re.search(self._url) if match: return match.group(2) return None @property - def hash_name(self): - # type: () -> Optional[str] - match = self._hash_re.search(self.url) + def hash_name(self) -> Optional[str]: + match = self._hash_re.search(self._url) if match: return match.group(1) return None @property - def show_url(self): - # type: () -> Optional[str] - return posixpath.basename(self.url.split('#', 1)[0].split('?', 1)[0]) + def show_url(self) -> str: + return posixpath.basename(self._url.split('#', 1)[0].split('?', 1)[0]) @property - def is_wheel(self): - # type: () -> bool + def is_file(self) -> bool: + return self.scheme == 'file' + + def is_existing_dir(self) -> bool: + return self.is_file and os.path.isdir(self.file_path) + + @property + def is_wheel(self) -> bool: return self.ext == WHEEL_EXTENSION @property - def is_artifact(self): - # type: () -> bool - """ - Determines if this points to an actual artifact (e.g. a tarball) or if - it points to an "abstract" thing like a path or a VCS location. - """ + def is_vcs(self) -> bool: from pipenv.patched.notpip._internal.vcs import vcs - if self.scheme in vcs.all_schemes: - return False + return self.scheme in vcs.all_schemes - return True + @property + def is_yanked(self) -> bool: + return self.yanked_reason is not None + + @property + def has_hash(self) -> bool: + return self.hash_name is not None + + def is_hash_allowed(self, hashes: Optional[Hashes]) -> bool: + """ + Return True if the link has a hash and it is allowed. + """ + if hashes is None or not self.has_hash: + return False + # Assert non-None so mypy knows self.hash_name and self.hash are str. + assert self.hash_name is not None + assert self.hash is not None + + return hashes.is_hash_allowed(self.hash_name, hex_digest=self.hash) + + +class _CleanResult(NamedTuple): + """Convert link for equivalency check. + + This is used in the resolver to check whether two URL-specified requirements + likely point to the same distribution and can be considered equivalent. This + equivalency logic avoids comparing URLs literally, which can be too strict + (e.g. "a=1&b=2" vs "b=2&a=1") and produce conflicts unexpecting to users. + + Currently this does three things: + + 1. Drop the basic auth part. This is technically wrong since a server can + serve different content based on auth, but if it does that, it is even + impossible to guarantee two URLs without auth are equivalent, since + the user can input different auth information when prompted. So the + practical solution is to assume the auth doesn't affect the response. + 2. Parse the query to avoid the ordering issue. Note that ordering under the + same key in the query are NOT cleaned; i.e. "a=1&a=2" and "a=2&a=1" are + still considered different. + 3. Explicitly drop most of the fragment part, except ``subdirectory=`` and + hash values, since it should have no impact the downloaded content. Note + that this drops the "egg=" part historically used to denote the requested + project (and extras), which is wrong in the strictest sense, but too many + people are supplying it inconsistently to cause superfluous resolution + conflicts, so we choose to also ignore them. + """ + + parsed: urllib.parse.SplitResult + query: Dict[str, List[str]] + subdirectory: str + hashes: Dict[str, str] + + @classmethod + def from_link(cls, link: Link) -> "_CleanResult": + parsed = link._parsed_url + netloc = parsed.netloc.rsplit("@", 1)[-1] + # According to RFC 8089, an empty host in file: means localhost. + if parsed.scheme == "file" and not netloc: + netloc = "localhost" + fragment = urllib.parse.parse_qs(parsed.fragment) + if "egg" in fragment: + logger.debug("Ignoring egg= fragment in %s", link) + try: + # If there are multiple subdirectory values, use the first one. + # This matches the behavior of Link.subdirectory_fragment. + subdirectory = fragment["subdirectory"][0] + except (IndexError, KeyError): + subdirectory = "" + # If there are multiple hash values under the same algorithm, use the + # first one. This matches the behavior of Link.hash_value. + hashes = {k: fragment[k][0] for k in _SUPPORTED_HASHES if k in fragment} + return cls( + parsed=parsed._replace(netloc=netloc, query="", fragment=""), + query=urllib.parse.parse_qs(parsed.query), + subdirectory=subdirectory, + hashes=hashes, + ) + + +@functools.lru_cache(maxsize=None) +def links_equivalent(link1: Link, link2: Link) -> bool: + return _CleanResult.from_link(link1) == _CleanResult.from_link(link2) diff --git a/pipenv/patched/notpip/_internal/models/scheme.py b/pipenv/patched/notpip/_internal/models/scheme.py new file mode 100644 index 00000000..9a8dafba --- /dev/null +++ b/pipenv/patched/notpip/_internal/models/scheme.py @@ -0,0 +1,31 @@ +""" +For types associated with installation schemes. + +For a general overview of available schemes and their context, see +https://docs.python.org/3/install/index.html#alternate-installation. +""" + + +SCHEME_KEYS = ['platlib', 'purelib', 'headers', 'scripts', 'data'] + + +class Scheme: + """A Scheme holds paths which are used as the base directories for + artifacts associated with a Python package. + """ + + __slots__ = SCHEME_KEYS + + def __init__( + self, + platlib: str, + purelib: str, + headers: str, + scripts: str, + data: str, + ) -> None: + self.platlib = platlib + self.purelib = purelib + self.headers = headers + self.scripts = scripts + self.data = data diff --git a/pipenv/patched/notpip/_internal/models/search_scope.py b/pipenv/patched/notpip/_internal/models/search_scope.py new file mode 100644 index 00000000..4b700407 --- /dev/null +++ b/pipenv/patched/notpip/_internal/models/search_scope.py @@ -0,0 +1,126 @@ +import itertools +import logging +import os +import posixpath +import urllib.parse +from typing import List + +from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name + +from pipenv.patched.notpip._internal.models.index import PyPI +from pipenv.patched.notpip._internal.utils.compat import has_tls +from pipenv.patched.notpip._internal.utils.misc import normalize_path, redact_auth_from_url + +logger = logging.getLogger(__name__) + + +class SearchScope: + + """ + Encapsulates the locations that pip is configured to search. + """ + + __slots__ = ["find_links", "index_urls"] + + @classmethod + def create( + cls, + find_links: List[str], + index_urls: List[str], + ) -> "SearchScope": + """ + Create a SearchScope object after normalizing the `find_links`. + """ + # Build find_links. If an argument starts with ~, it may be + # a local file relative to a home directory. So try normalizing + # it and if it exists, use the normalized version. + # This is deliberately conservative - it might be fine just to + # blindly normalize anything starting with a ~... + built_find_links: List[str] = [] + for link in find_links: + if link.startswith('~'): + new_link = normalize_path(link) + if os.path.exists(new_link): + link = new_link + built_find_links.append(link) + + # If we don't have TLS enabled, then WARN if anyplace we're looking + # relies on TLS. + if not has_tls(): + for link in itertools.chain(index_urls, built_find_links): + parsed = urllib.parse.urlparse(link) + if parsed.scheme == 'https': + logger.warning( + 'pip is configured with locations that require ' + 'TLS/SSL, however the ssl module in Python is not ' + 'available.' + ) + break + + return cls( + find_links=built_find_links, + index_urls=index_urls, + ) + + def __init__( + self, + find_links: List[str], + index_urls: List[str], + ) -> None: + self.find_links = find_links + self.index_urls = index_urls + + def get_formatted_locations(self) -> str: + lines = [] + redacted_index_urls = [] + if self.index_urls and self.index_urls != [PyPI.simple_url]: + for url in self.index_urls: + + redacted_index_url = redact_auth_from_url(url) + + # Parse the URL + purl = urllib.parse.urlsplit(redacted_index_url) + + # URL is generally invalid if scheme and netloc is missing + # there are issues with Python and URL parsing, so this test + # is a bit crude. See bpo-20271, bpo-23505. Python doesn't + # always parse invalid URLs correctly - it should raise + # exceptions for malformed URLs + if not purl.scheme and not purl.netloc: + logger.warning( + 'The index url "%s" seems invalid, ' + 'please provide a scheme.', redacted_index_url) + + redacted_index_urls.append(redacted_index_url) + + lines.append('Looking in indexes: {}'.format( + ', '.join(redacted_index_urls))) + + if self.find_links: + lines.append( + 'Looking in links: {}'.format(', '.join( + redact_auth_from_url(url) for url in self.find_links)) + ) + return '\n'.join(lines) + + def get_index_urls_locations(self, project_name: str) -> List[str]: + """Returns the locations found via self.index_urls + + Checks the url_name on the main (first in the list) index and + use this url_name to produce all locations + """ + + def mkurl_pypi_url(url: str) -> str: + loc = posixpath.join( + url, + urllib.parse.quote(canonicalize_name(project_name))) + # For maximum compatibility with easy_install, ensure the path + # ends in a trailing slash. Although this isn't in the spec + # (and PyPI can handle it without the slash) some other index + # implementations might break if they relied on easy_install's + # behavior. + if not loc.endswith('/'): + loc = loc + '/' + return loc + + return [mkurl_pypi_url(url) for url in self.index_urls] diff --git a/pipenv/patched/notpip/_internal/models/selection_prefs.py b/pipenv/patched/notpip/_internal/models/selection_prefs.py new file mode 100644 index 00000000..a6b63881 --- /dev/null +++ b/pipenv/patched/notpip/_internal/models/selection_prefs.py @@ -0,0 +1,46 @@ +from typing import Optional + +from pipenv.patched.notpip._internal.models.format_control import FormatControl + + +class SelectionPreferences: + """ + Encapsulates the candidate selection preferences for downloading + and installing files. + """ + + __slots__ = ['allow_yanked', 'allow_all_prereleases', 'format_control', + 'prefer_binary', 'ignore_requires_python'] + + # Don't include an allow_yanked default value to make sure each call + # site considers whether yanked releases are allowed. This also causes + # that decision to be made explicit in the calling code, which helps + # people when reading the code. + def __init__( + self, + allow_yanked: bool, + allow_all_prereleases: bool = False, + format_control: Optional[FormatControl] = None, + prefer_binary: bool = False, + ignore_requires_python: Optional[bool] = None, + ) -> None: + """Create a SelectionPreferences object. + + :param allow_yanked: Whether files marked as yanked (in the sense + of PEP 592) are permitted to be candidates for install. + :param format_control: A FormatControl object or None. Used to control + the selection of source packages / binary packages when consulting + the index and links. + :param prefer_binary: Whether to prefer an old, but valid, binary + dist over a new source dist. + :param ignore_requires_python: Whether to ignore incompatible + "Requires-Python" values in links. Defaults to False. + """ + if ignore_requires_python is None: + ignore_requires_python = False + + self.allow_yanked = allow_yanked + self.allow_all_prereleases = allow_all_prereleases + self.format_control = format_control + self.prefer_binary = prefer_binary + self.ignore_requires_python = ignore_requires_python diff --git a/pipenv/patched/notpip/_internal/models/target_python.py b/pipenv/patched/notpip/_internal/models/target_python.py new file mode 100644 index 00000000..9bd6fba5 --- /dev/null +++ b/pipenv/patched/notpip/_internal/models/target_python.py @@ -0,0 +1,111 @@ +import sys +from typing import List, Optional, Tuple + +from pipenv.patched.notpip._vendor.packaging.tags import Tag + +from pipenv.patched.notpip._internal.utils.compatibility_tags import get_supported, version_info_to_nodot +from pipenv.patched.notpip._internal.utils.misc import normalize_version_info + + +class TargetPython: + + """ + Encapsulates the properties of a Python interpreter one is targeting + for a package install, download, etc. + """ + + __slots__ = [ + "_given_py_version_info", + "abis", + "implementation", + "platforms", + "py_version", + "py_version_info", + "_valid_tags", + ] + + def __init__( + self, + platforms: Optional[List[str]] = None, + py_version_info: Optional[Tuple[int, ...]] = None, + abis: Optional[List[str]] = None, + implementation: Optional[str] = None, + ) -> None: + """ + :param platforms: A list of strings or None. If None, searches for + packages that are supported by the current system. Otherwise, will + find packages that can be built on the platforms passed in. These + packages will only be downloaded for distribution: they will + not be built locally. + :param py_version_info: An optional tuple of ints representing the + Python version information to use (e.g. `sys.version_info[:3]`). + This can have length 1, 2, or 3 when provided. + :param abis: A list of strings or None. This is passed to + compatibility_tags.py's get_supported() function as is. + :param implementation: A string or None. This is passed to + compatibility_tags.py's get_supported() function as is. + """ + # Store the given py_version_info for when we call get_supported(). + self._given_py_version_info = py_version_info + + if py_version_info is None: + py_version_info = sys.version_info[:3] + else: + py_version_info = normalize_version_info(py_version_info) + + py_version = '.'.join(map(str, py_version_info[:2])) + + self.abis = abis + self.implementation = implementation + self.platforms = platforms + self.py_version = py_version + self.py_version_info = py_version_info + + # This is used to cache the return value of get_tags(). + self._valid_tags: Optional[List[Tag]] = None + + def format_given(self) -> str: + """ + Format the given, non-None attributes for display. + """ + display_version = None + if self._given_py_version_info is not None: + display_version = '.'.join( + str(part) for part in self._given_py_version_info + ) + + key_values = [ + ('platforms', self.platforms), + ('version_info', display_version), + ('abis', self.abis), + ('implementation', self.implementation), + ] + return ' '.join( + f'{key}={value!r}' for key, value in key_values + if value is not None + ) + + def get_tags(self) -> List[Tag]: + """ + Return the supported PEP 425 tags to check wheel candidates against. + + The tags are returned in order of preference (most preferred first). + """ + if self._valid_tags is None: + # Pass versions=None if no py_version_info was given since + # versions=None uses special default logic. + py_version_info = self._given_py_version_info + if py_version_info is None: + version = None + else: + version = version_info_to_nodot(py_version_info) + + tags = get_supported( + version=version, + platforms=self.platforms, + abis=self.abis, + impl=self.implementation, + ) + self._valid_tags = tags + + return self._valid_tags diff --git a/pipenv/patched/notpip/_internal/models/wheel.py b/pipenv/patched/notpip/_internal/models/wheel.py new file mode 100644 index 00000000..ed690340 --- /dev/null +++ b/pipenv/patched/notpip/_internal/models/wheel.py @@ -0,0 +1,92 @@ +"""Represents a wheel file and provides access to the various parts of the +name that have meaning. +""" +import re +from typing import Dict, Iterable, List + +from pipenv.patched.notpip._vendor.packaging.tags import Tag + +from pipenv.patched.notpip._internal.exceptions import InvalidWheelFilename + + +class Wheel: + """A wheel file""" + + wheel_file_re = re.compile( + r"""^(?P(?P.+?)-(?P.*?)) + ((-(?P\d[^-]*?))?-(?P.+?)-(?P.+?)-(?P.+?) + \.whl|\.dist-info)$""", + re.VERBOSE + ) + + def __init__(self, filename: str) -> None: + """ + :raises InvalidWheelFilename: when the filename is invalid for a wheel + """ + wheel_info = self.wheel_file_re.match(filename) + if not wheel_info: + raise InvalidWheelFilename( + f"{filename} is not a valid wheel filename." + ) + self.filename = filename + self.name = wheel_info.group('name').replace('_', '-') + # we'll assume "_" means "-" due to wheel naming scheme + # (https://github.com/pypa/pip/issues/1150) + self.version = wheel_info.group('ver').replace('_', '-') + self.build_tag = wheel_info.group('build') + self.pyversions = wheel_info.group('pyver').split('.') + self.abis = wheel_info.group('abi').split('.') + self.plats = wheel_info.group('plat').split('.') + + # All the tag combinations from this file + self.file_tags = { + Tag(x, y, z) for x in self.pyversions + for y in self.abis for z in self.plats + } + + def get_formatted_file_tags(self) -> List[str]: + """Return the wheel's tags as a sorted list of strings.""" + return sorted(str(tag) for tag in self.file_tags) + + def support_index_min(self, tags: List[Tag]) -> int: + """Return the lowest index that one of the wheel's file_tag combinations + achieves in the given list of supported tags. + + For example, if there are 8 supported tags and one of the file tags + is first in the list, then return 0. + + :param tags: the PEP 425 tags to check the wheel against, in order + with most preferred first. + + :raises ValueError: If none of the wheel's file tags match one of + the supported tags. + """ + return min(tags.index(tag) for tag in self.file_tags if tag in tags) + + def find_most_preferred_tag( + self, tags: List[Tag], tag_to_priority: Dict[Tag, int] + ) -> int: + """Return the priority of the most preferred tag that one of the wheel's file + tag combinations achieves in the given list of supported tags using the given + tag_to_priority mapping, where lower priorities are more-preferred. + + This is used in place of support_index_min in some cases in order to avoid + an expensive linear scan of a large list of tags. + + :param tags: the PEP 425 tags to check the wheel against. + :param tag_to_priority: a mapping from tag to priority of that tag, where + lower is more preferred. + + :raises ValueError: If none of the wheel's file tags match one of + the supported tags. + """ + return min( + tag_to_priority[tag] for tag in self.file_tags if tag in tag_to_priority + ) + + def supported(self, tags: Iterable[Tag]) -> bool: + """Return whether the wheel is compatible with one of the given tags. + + :param tags: the PEP 425 tags to check the wheel against. + """ + return not self.file_tags.isdisjoint(tags) diff --git a/pipenv/patched/notpip/_internal/network/__init__.py b/pipenv/patched/notpip/_internal/network/__init__.py new file mode 100644 index 00000000..b51bde91 --- /dev/null +++ b/pipenv/patched/notpip/_internal/network/__init__.py @@ -0,0 +1,2 @@ +"""Contains purely network-related utilities. +""" diff --git a/pipenv/patched/notpip/_internal/network/auth.py b/pipenv/patched/notpip/_internal/network/auth.py new file mode 100644 index 00000000..102ce5be --- /dev/null +++ b/pipenv/patched/notpip/_internal/network/auth.py @@ -0,0 +1,316 @@ +"""Network Authentication Helpers + +Contains interface (MultiDomainBasicAuth) and associated glue code for +providing credentials in the context of network requests. +""" + +import urllib.parse +from typing import Any, Dict, List, Optional, Tuple + +from pipenv.patched.notpip._vendor.requests.auth import AuthBase, HTTPBasicAuth +from pipenv.patched.notpip._vendor.requests.models import Request, Response +from pipenv.patched.notpip._vendor.requests.utils import get_netrc_auth + +from pipenv.patched.notpip._internal.utils.logging import getLogger +from pipenv.patched.notpip._internal.utils.misc import ( + ask, + ask_input, + ask_password, + remove_auth_from_url, + split_auth_netloc_from_url, +) +from pipenv.patched.notpip._internal.vcs.versioncontrol import AuthInfo + +logger = getLogger(__name__) + +Credentials = Tuple[str, str, str] + +try: + import keyring +except ImportError: + keyring = None +except Exception as exc: + logger.warning( + "Keyring is skipped due to an exception: %s", + str(exc), + ) + keyring = None + + +def get_keyring_auth(url: Optional[str], username: Optional[str]) -> Optional[AuthInfo]: + """Return the tuple auth for a given url from keyring.""" + global keyring + if not url or not keyring: + return None + + try: + try: + get_credential = keyring.get_credential + except AttributeError: + pass + else: + logger.debug("Getting credentials from keyring for %s", url) + cred = get_credential(url, username) + if cred is not None: + return cred.username, cred.password + return None + + if username: + logger.debug("Getting password from keyring for %s", url) + password = keyring.get_password(url, username) + if password: + return username, password + + except Exception as exc: + logger.warning( + "Keyring is skipped due to an exception: %s", + str(exc), + ) + keyring = None + return None + + +class MultiDomainBasicAuth(AuthBase): + def __init__( + self, prompting: bool = True, index_urls: Optional[List[str]] = None + ) -> None: + self.prompting = prompting + self.index_urls = index_urls + self.passwords: Dict[str, AuthInfo] = {} + # When the user is prompted to enter credentials and keyring is + # available, we will offer to save them. If the user accepts, + # this value is set to the credentials they entered. After the + # request authenticates, the caller should call + # ``save_credentials`` to save these. + self._credentials_to_save: Optional[Credentials] = None + + def _get_index_url(self, url: str) -> Optional[str]: + """Return the original index URL matching the requested URL. + + Cached or dynamically generated credentials may work against + the original index URL rather than just the netloc. + + The provided url should have had its username and password + removed already. If the original index url had credentials then + they will be included in the return value. + + Returns None if no matching index was found, or if --no-index + was specified by the user. + """ + if not url or not self.index_urls: + return None + + for u in self.index_urls: + prefix = remove_auth_from_url(u).rstrip("/") + "/" + if url.startswith(prefix): + return u + return None + + def _get_new_credentials( + self, + original_url: str, + allow_netrc: bool = True, + allow_keyring: bool = False, + ) -> AuthInfo: + """Find and return credentials for the specified URL.""" + # Split the credentials and netloc from the url. + url, netloc, url_user_password = split_auth_netloc_from_url( + original_url, + ) + + # Start with the credentials embedded in the url + username, password = url_user_password + if username is not None and password is not None: + logger.debug("Found credentials in url for %s", netloc) + return url_user_password + + # Find a matching index url for this request + index_url = self._get_index_url(url) + if index_url: + # Split the credentials from the url. + index_info = split_auth_netloc_from_url(index_url) + if index_info: + index_url, _, index_url_user_password = index_info + logger.debug("Found index url %s", index_url) + + # If an index URL was found, try its embedded credentials + if index_url and index_url_user_password[0] is not None: + username, password = index_url_user_password + if username is not None and password is not None: + logger.debug("Found credentials in index url for %s", netloc) + return index_url_user_password + + # Get creds from netrc if we still don't have them + if allow_netrc: + netrc_auth = get_netrc_auth(original_url) + if netrc_auth: + logger.debug("Found credentials in netrc for %s", netloc) + return netrc_auth + + # If we don't have a password and keyring is available, use it. + if allow_keyring: + # The index url is more specific than the netloc, so try it first + # fmt: off + kr_auth = ( + get_keyring_auth(index_url, username) or + get_keyring_auth(netloc, username) + ) + # fmt: on + if kr_auth: + logger.debug("Found credentials in keyring for %s", netloc) + return kr_auth + + return username, password + + def _get_url_and_credentials( + self, original_url: str + ) -> Tuple[str, Optional[str], Optional[str]]: + """Return the credentials to use for the provided URL. + + If allowed, netrc and keyring may be used to obtain the + correct credentials. + + Returns (url_without_credentials, username, password). Note + that even if the original URL contains credentials, this + function may return a different username and password. + """ + url, netloc, _ = split_auth_netloc_from_url(original_url) + + # Try to get credentials from original url + username, password = self._get_new_credentials(original_url) + + # If credentials not found, use any stored credentials for this netloc + if username is None and password is None: + username, password = self.passwords.get(netloc, (None, None)) + + if username is not None or password is not None: + # Convert the username and password if they're None, so that + # this netloc will show up as "cached" in the conditional above. + # Further, HTTPBasicAuth doesn't accept None, so it makes sense to + # cache the value that is going to be used. + username = username or "" + password = password or "" + + # Store any acquired credentials. + self.passwords[netloc] = (username, password) + + assert ( + # Credentials were found + (username is not None and password is not None) + # Credentials were not found + or (username is None and password is None) + ), f"Could not load credentials from url: {original_url}" + + return url, username, password + + def __call__(self, req: Request) -> Request: + # Get credentials for this request + url, username, password = self._get_url_and_credentials(req.url) + + # Set the url of the request to the url without any credentials + req.url = url + + if username is not None and password is not None: + # Send the basic auth with this request + req = HTTPBasicAuth(username, password)(req) + + # Attach a hook to handle 401 responses + req.register_hook("response", self.handle_401) + + return req + + # Factored out to allow for easy patching in tests + def _prompt_for_password( + self, netloc: str + ) -> Tuple[Optional[str], Optional[str], bool]: + username = ask_input(f"User for {netloc}: ") + if not username: + return None, None, False + auth = get_keyring_auth(netloc, username) + if auth and auth[0] is not None and auth[1] is not None: + return auth[0], auth[1], False + password = ask_password("Password: ") + return username, password, True + + # Factored out to allow for easy patching in tests + def _should_save_password_to_keyring(self) -> bool: + if not keyring: + return False + return ask("Save credentials to keyring [y/N]: ", ["y", "n"]) == "y" + + def handle_401(self, resp: Response, **kwargs: Any) -> Response: + # We only care about 401 responses, anything else we want to just + # pass through the actual response + if resp.status_code != 401: + return resp + + # We are not able to prompt the user so simply return the response + if not self.prompting: + return resp + + parsed = urllib.parse.urlparse(resp.url) + + # Query the keyring for credentials: + username, password = self._get_new_credentials( + resp.url, + allow_netrc=False, + allow_keyring=True, + ) + + # Prompt the user for a new username and password + save = False + if not username and not password: + username, password, save = self._prompt_for_password(parsed.netloc) + + # Store the new username and password to use for future requests + self._credentials_to_save = None + if username is not None and password is not None: + self.passwords[parsed.netloc] = (username, password) + + # Prompt to save the password to keyring + if save and self._should_save_password_to_keyring(): + self._credentials_to_save = (parsed.netloc, username, password) + + # Consume content and release the original connection to allow our new + # request to reuse the same one. + resp.content + resp.raw.release_conn() + + # Add our new username and password to the request + req = HTTPBasicAuth(username or "", password or "")(resp.request) + req.register_hook("response", self.warn_on_401) + + # On successful request, save the credentials that were used to + # keyring. (Note that if the user responded "no" above, this member + # is not set and nothing will be saved.) + if self._credentials_to_save: + req.register_hook("response", self.save_credentials) + + # Send our new request + new_resp = resp.connection.send(req, **kwargs) + new_resp.history.append(resp) + + return new_resp + + def warn_on_401(self, resp: Response, **kwargs: Any) -> None: + """Response callback to warn about incorrect credentials.""" + if resp.status_code == 401: + logger.warning( + "401 Error, Credentials not correct for %s", + resp.request.url, + ) + + def save_credentials(self, resp: Response, **kwargs: Any) -> None: + """Response callback to save credentials on success.""" + assert keyring is not None, "should never reach here without keyring" + if not keyring: + return + + creds = self._credentials_to_save + self._credentials_to_save = None + if creds and resp.status_code < 400: + try: + logger.info("Saving credentials to keyring") + keyring.set_password(*creds) + except Exception: + logger.exception("Failed to save credentials") diff --git a/pipenv/patched/notpip/_internal/network/cache.py b/pipenv/patched/notpip/_internal/network/cache.py new file mode 100644 index 00000000..0ec6f967 --- /dev/null +++ b/pipenv/patched/notpip/_internal/network/cache.py @@ -0,0 +1,69 @@ +"""HTTP cache implementation. +""" + +import os +from contextlib import contextmanager +from typing import Iterator, Optional + +from pipenv.patched.notpip._vendor.cachecontrol.cache import BaseCache +from pipenv.patched.notpip._vendor.cachecontrol.caches import FileCache +from pipenv.patched.notpip._vendor.requests.models import Response + +from pipenv.patched.notpip._internal.utils.filesystem import adjacent_tmp_file, replace +from pipenv.patched.notpip._internal.utils.misc import ensure_dir + + +def is_from_cache(response: Response) -> bool: + return getattr(response, "from_cache", False) + + +@contextmanager +def suppressed_cache_errors() -> Iterator[None]: + """If we can't access the cache then we can just skip caching and process + requests as if caching wasn't enabled. + """ + try: + yield + except OSError: + pass + + +class SafeFileCache(BaseCache): + """ + A file based cache which is safe to use even when the target directory may + not be accessible or writable. + """ + + def __init__(self, directory: str) -> None: + assert directory is not None, "Cache directory must not be None." + super().__init__() + self.directory = directory + + def _get_cache_path(self, name: str) -> str: + # From cachecontrol.caches.file_cache.FileCache._fn, brought into our + # class for backwards-compatibility and to avoid using a non-public + # method. + hashed = FileCache.encode(name) + parts = list(hashed[:5]) + [hashed] + return os.path.join(self.directory, *parts) + + def get(self, key: str) -> Optional[bytes]: + path = self._get_cache_path(key) + with suppressed_cache_errors(): + with open(path, "rb") as f: + return f.read() + + def set(self, key: str, value: bytes) -> None: + path = self._get_cache_path(key) + with suppressed_cache_errors(): + ensure_dir(os.path.dirname(path)) + + with adjacent_tmp_file(path) as f: + f.write(value) + + replace(f.name, path) + + def delete(self, key: str) -> None: + path = self._get_cache_path(key) + with suppressed_cache_errors(): + os.remove(path) diff --git a/pipenv/patched/notpip/_internal/network/download.py b/pipenv/patched/notpip/_internal/network/download.py new file mode 100644 index 00000000..fe229686 --- /dev/null +++ b/pipenv/patched/notpip/_internal/network/download.py @@ -0,0 +1,184 @@ +"""Download files with progress indicators. +""" +import cgi +import logging +import mimetypes +import os +from typing import Iterable, Optional, Tuple + +from pipenv.patched.notpip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response + +from pipenv.patched.notpip._internal.cli.progress_bars import DownloadProgressProvider +from pipenv.patched.notpip._internal.exceptions import NetworkConnectionError +from pipenv.patched.notpip._internal.models.index import PyPI +from pipenv.patched.notpip._internal.models.link import Link +from pipenv.patched.notpip._internal.network.cache import is_from_cache +from pipenv.patched.notpip._internal.network.session import PipSession +from pipenv.patched.notpip._internal.network.utils import HEADERS, raise_for_status, response_chunks +from pipenv.patched.notpip._internal.utils.misc import format_size, redact_auth_from_url, splitext + +logger = logging.getLogger(__name__) + + +def _get_http_response_size(resp: Response) -> Optional[int]: + try: + return int(resp.headers["content-length"]) + except (ValueError, KeyError, TypeError): + return None + + +def _prepare_download( + resp: Response, + link: Link, + progress_bar: str, +) -> Iterable[bytes]: + total_length = _get_http_response_size(resp) + + if link.netloc == PyPI.file_storage_domain: + url = link.show_url + else: + url = link.url_without_fragment + + logged_url = redact_auth_from_url(url) + + if total_length: + logged_url = "{} ({})".format(logged_url, format_size(total_length)) + + if is_from_cache(resp): + logger.info("Using cached %s", logged_url) + else: + logger.info("Downloading %s", logged_url) + + if logger.getEffectiveLevel() > logging.INFO: + show_progress = False + elif is_from_cache(resp): + show_progress = False + elif not total_length: + show_progress = True + elif total_length > (40 * 1000): + show_progress = True + else: + show_progress = False + + chunks = response_chunks(resp, CONTENT_CHUNK_SIZE) + + if not show_progress: + return chunks + + return DownloadProgressProvider(progress_bar, max=total_length)(chunks) + + +def sanitize_content_filename(filename: str) -> str: + """ + Sanitize the "filename" value from a Content-Disposition header. + """ + return os.path.basename(filename) + + +def parse_content_disposition(content_disposition: str, default_filename: str) -> str: + """ + Parse the "filename" value from a Content-Disposition header, and + return the default filename if the result is empty. + """ + _type, params = cgi.parse_header(content_disposition) + filename = params.get("filename") + if filename: + # We need to sanitize the filename to prevent directory traversal + # in case the filename contains ".." path parts. + filename = sanitize_content_filename(filename) + return filename or default_filename + + +def _get_http_response_filename(resp: Response, link: Link) -> str: + """Get an ideal filename from the given HTTP response, falling back to + the link filename if not provided. + """ + filename = link.filename # fallback + # Have a look at the Content-Disposition header for a better guess + content_disposition = resp.headers.get("content-disposition") + if content_disposition: + filename = parse_content_disposition(content_disposition, filename) + ext: Optional[str] = splitext(filename)[1] + if not ext: + ext = mimetypes.guess_extension(resp.headers.get("content-type", "")) + if ext: + filename += ext + if not ext and link.url != resp.url: + ext = os.path.splitext(resp.url)[1] + if ext: + filename += ext + return filename + + +def _http_get_download(session: PipSession, link: Link) -> Response: + target_url = link.url.split("#", 1)[0] + resp = session.get(target_url, headers=HEADERS, stream=True) + raise_for_status(resp) + return resp + + +class Downloader: + def __init__( + self, + session: PipSession, + progress_bar: str, + ) -> None: + self._session = session + self._progress_bar = progress_bar + + def __call__(self, link: Link, location: str) -> Tuple[str, str]: + """Download the file given by link into location.""" + try: + resp = _http_get_download(self._session, link) + except NetworkConnectionError as e: + assert e.response is not None + logger.critical( + "HTTP error %s while getting %s", e.response.status_code, link + ) + raise + + filename = _get_http_response_filename(resp, link) + filepath = os.path.join(location, filename) + + chunks = _prepare_download(resp, link, self._progress_bar) + with open(filepath, "wb") as content_file: + for chunk in chunks: + content_file.write(chunk) + content_type = resp.headers.get("Content-Type", "") + return filepath, content_type + + +class BatchDownloader: + def __init__( + self, + session: PipSession, + progress_bar: str, + ) -> None: + self._session = session + self._progress_bar = progress_bar + + def __call__( + self, links: Iterable[Link], location: str + ) -> Iterable[Tuple[Link, Tuple[str, str]]]: + """Download the files given by links into location.""" + for link in links: + try: + resp = _http_get_download(self._session, link) + except NetworkConnectionError as e: + assert e.response is not None + logger.critical( + "HTTP error %s while getting %s", + e.response.status_code, + link, + ) + raise + + filename = _get_http_response_filename(resp, link) + filepath = os.path.join(location, filename) + + chunks = _prepare_download(resp, link, self._progress_bar) + with open(filepath, "wb") as content_file: + for chunk in chunks: + content_file.write(chunk) + content_type = resp.headers.get("Content-Type", "") + yield link, (filepath, content_type) diff --git a/pipenv/patched/notpip/_internal/network/lazy_wheel.py b/pipenv/patched/notpip/_internal/network/lazy_wheel.py new file mode 100644 index 00000000..17565c7e --- /dev/null +++ b/pipenv/patched/notpip/_internal/network/lazy_wheel.py @@ -0,0 +1,210 @@ +"""Lazy ZIP over HTTP""" + +__all__ = ["HTTPRangeRequestUnsupported", "dist_from_wheel_url"] + +from bisect import bisect_left, bisect_right +from contextlib import contextmanager +from tempfile import NamedTemporaryFile +from typing import Any, Dict, Iterator, List, Optional, Tuple +from zipfile import BadZipfile, ZipFile + +from pipenv.patched.notpip._vendor.pkg_resources import Distribution +from pipenv.patched.notpip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response + +from pipenv.patched.notpip._internal.network.session import PipSession +from pipenv.patched.notpip._internal.network.utils import HEADERS, raise_for_status, response_chunks +from pipenv.patched.notpip._internal.utils.wheel import pkg_resources_distribution_for_wheel + + +class HTTPRangeRequestUnsupported(Exception): + pass + + +def dist_from_wheel_url(name: str, url: str, session: PipSession) -> Distribution: + """Return a pkg_resources.Distribution from the given wheel URL. + + This uses HTTP range requests to only fetch the potion of the wheel + containing metadata, just enough for the object to be constructed. + If such requests are not supported, HTTPRangeRequestUnsupported + is raised. + """ + with LazyZipOverHTTP(url, session) as wheel: + # For read-only ZIP files, ZipFile only needs methods read, + # seek, seekable and tell, not the whole IO protocol. + zip_file = ZipFile(wheel) # type: ignore + # After context manager exit, wheel.name + # is an invalid file by intention. + return pkg_resources_distribution_for_wheel(zip_file, name, wheel.name) + + +class LazyZipOverHTTP: + """File-like object mapped to a ZIP file over HTTP. + + This uses HTTP range requests to lazily fetch the file's content, + which is supposed to be fed to ZipFile. If such requests are not + supported by the server, raise HTTPRangeRequestUnsupported + during initialization. + """ + + def __init__( + self, url: str, session: PipSession, chunk_size: int = CONTENT_CHUNK_SIZE + ) -> None: + head = session.head(url, headers=HEADERS) + raise_for_status(head) + assert head.status_code == 200 + self._session, self._url, self._chunk_size = session, url, chunk_size + self._length = int(head.headers["Content-Length"]) + self._file = NamedTemporaryFile() + self.truncate(self._length) + self._left: List[int] = [] + self._right: List[int] = [] + if "bytes" not in head.headers.get("Accept-Ranges", "none"): + raise HTTPRangeRequestUnsupported("range request is not supported") + self._check_zip() + + @property + def mode(self) -> str: + """Opening mode, which is always rb.""" + return "rb" + + @property + def name(self) -> str: + """Path to the underlying file.""" + return self._file.name + + def seekable(self) -> bool: + """Return whether random access is supported, which is True.""" + return True + + def close(self) -> None: + """Close the file.""" + self._file.close() + + @property + def closed(self) -> bool: + """Whether the file is closed.""" + return self._file.closed + + def read(self, size: int = -1) -> bytes: + """Read up to size bytes from the object and return them. + + As a convenience, if size is unspecified or -1, + all bytes until EOF are returned. Fewer than + size bytes may be returned if EOF is reached. + """ + download_size = max(size, self._chunk_size) + start, length = self.tell(), self._length + stop = length if size < 0 else min(start + download_size, length) + start = max(0, stop - download_size) + self._download(start, stop - 1) + return self._file.read(size) + + def readable(self) -> bool: + """Return whether the file is readable, which is True.""" + return True + + def seek(self, offset: int, whence: int = 0) -> int: + """Change stream position and return the new absolute position. + + Seek to offset relative position indicated by whence: + * 0: Start of stream (the default). pos should be >= 0; + * 1: Current position - pos may be negative; + * 2: End of stream - pos usually negative. + """ + return self._file.seek(offset, whence) + + def tell(self) -> int: + """Return the current position.""" + return self._file.tell() + + def truncate(self, size: Optional[int] = None) -> int: + """Resize the stream to the given size in bytes. + + If size is unspecified resize to the current position. + The current stream position isn't changed. + + Return the new file size. + """ + return self._file.truncate(size) + + def writable(self) -> bool: + """Return False.""" + return False + + def __enter__(self) -> "LazyZipOverHTTP": + self._file.__enter__() + return self + + def __exit__(self, *exc: Any) -> Optional[bool]: + return self._file.__exit__(*exc) + + @contextmanager + def _stay(self) -> Iterator[None]: + """Return a context manager keeping the position. + + At the end of the block, seek back to original position. + """ + pos = self.tell() + try: + yield + finally: + self.seek(pos) + + def _check_zip(self) -> None: + """Check and download until the file is a valid ZIP.""" + end = self._length - 1 + for start in reversed(range(0, end, self._chunk_size)): + self._download(start, end) + with self._stay(): + try: + # For read-only ZIP files, ZipFile only needs + # methods read, seek, seekable and tell. + ZipFile(self) # type: ignore + except BadZipfile: + pass + else: + break + + def _stream_response( + self, start: int, end: int, base_headers: Dict[str, str] = HEADERS + ) -> Response: + """Return HTTP response to a range request from start to end.""" + headers = base_headers.copy() + headers["Range"] = f"bytes={start}-{end}" + # TODO: Get range requests to be correctly cached + headers["Cache-Control"] = "no-cache" + return self._session.get(self._url, headers=headers, stream=True) + + def _merge( + self, start: int, end: int, left: int, right: int + ) -> Iterator[Tuple[int, int]]: + """Return an iterator of intervals to be fetched. + + Args: + start (int): Start of needed interval + end (int): End of needed interval + left (int): Index of first overlapping downloaded data + right (int): Index after last overlapping downloaded data + """ + lslice, rslice = self._left[left:right], self._right[left:right] + i = start = min([start] + lslice[:1]) + end = max([end] + rslice[-1:]) + for j, k in zip(lslice, rslice): + if j > i: + yield i, j - 1 + i = k + 1 + if i <= end: + yield i, end + self._left[left:right], self._right[left:right] = [start], [end] + + def _download(self, start: int, end: int) -> None: + """Download bytes from start to end inclusively.""" + with self._stay(): + left = bisect_left(self._right, start) + right = bisect_right(self._left, end) + for start, end in self._merge(start, end, left, right): + response = self._stream_response(start, end) + response.raise_for_status() + self.seek(start) + for chunk in response_chunks(response, self._chunk_size): + self._file.write(chunk) diff --git a/pipenv/patched/notpip/_internal/network/session.py b/pipenv/patched/notpip/_internal/network/session.py new file mode 100644 index 00000000..cf54d622 --- /dev/null +++ b/pipenv/patched/notpip/_internal/network/session.py @@ -0,0 +1,454 @@ +"""PipSession and supporting code, containing all pip-specific +network request configuration and behavior. +""" + +# When mypy runs on Windows the call to distro.linux_distribution() is skipped +# resulting in the failure: +# +# error: unused 'type: ignore' comment +# +# If the upstream module adds typing, this comment should be removed. See +# https://github.com/nir0s/distro/pull/269 +# +# mypy: warn-unused-ignores=False + +import email.utils +import ipaddress +import json +import logging +import mimetypes +import os +import platform +import shutil +import subprocess +import sys +import urllib.parse +import warnings +from typing import Any, Dict, Iterator, List, Mapping, Optional, Sequence, Tuple, Union + +from pipenv.patched.notpip._vendor import requests, urllib3 +from pipenv.patched.notpip._vendor.cachecontrol import CacheControlAdapter +from pipenv.patched.notpip._vendor.requests.adapters import BaseAdapter, HTTPAdapter +from pipenv.patched.notpip._vendor.requests.models import PreparedRequest, Response +from pipenv.patched.notpip._vendor.requests.structures import CaseInsensitiveDict +from pipenv.patched.notpip._vendor.urllib3.connectionpool import ConnectionPool +from pipenv.patched.notpip._vendor.urllib3.exceptions import InsecureRequestWarning + +from pipenv.patched.notpip import __version__ +from pipenv.patched.notpip._internal.metadata import get_default_environment +from pipenv.patched.notpip._internal.models.link import Link +from pipenv.patched.notpip._internal.network.auth import MultiDomainBasicAuth +from pipenv.patched.notpip._internal.network.cache import SafeFileCache + +# Import ssl from compat so the initial import occurs in only one place. +from pipenv.patched.notpip._internal.utils.compat import has_tls +from pipenv.patched.notpip._internal.utils.glibc import libc_ver +from pipenv.patched.notpip._internal.utils.misc import build_url_from_netloc, parse_netloc +from pipenv.patched.notpip._internal.utils.urls import url_to_path + +logger = logging.getLogger(__name__) + +SecureOrigin = Tuple[str, str, Optional[Union[int, str]]] + + +# Ignore warning raised when using --trusted-host. +warnings.filterwarnings("ignore", category=InsecureRequestWarning) + + +SECURE_ORIGINS: List[SecureOrigin] = [ + # protocol, hostname, port + # Taken from Chrome's list of secure origins (See: http://bit.ly/1qrySKC) + ("https", "*", "*"), + ("*", "localhost", "*"), + ("*", "127.0.0.0/8", "*"), + ("*", "::1/128", "*"), + ("file", "*", None), + # ssh is always secure. + ("ssh", "*", "*"), +] + + +# These are environment variables present when running under various +# CI systems. For each variable, some CI systems that use the variable +# are indicated. The collection was chosen so that for each of a number +# of popular systems, at least one of the environment variables is used. +# This list is used to provide some indication of and lower bound for +# CI traffic to PyPI. Thus, it is okay if the list is not comprehensive. +# For more background, see: https://github.com/pypa/pip/issues/5499 +CI_ENVIRONMENT_VARIABLES = ( + # Azure Pipelines + "BUILD_BUILDID", + # Jenkins + "BUILD_ID", + # AppVeyor, CircleCI, Codeship, Gitlab CI, Shippable, Travis CI + "CI", + # Explicit environment variable. + "PIP_IS_CI", +) + + +def looks_like_ci() -> bool: + """ + Return whether it looks like pip is running under CI. + """ + # We don't use the method of checking for a tty (e.g. using isatty()) + # because some CI systems mimic a tty (e.g. Travis CI). Thus that + # method doesn't provide definitive information in either direction. + return any(name in os.environ for name in CI_ENVIRONMENT_VARIABLES) + + +def user_agent() -> str: + """ + Return a string representing the user agent. + """ + data: Dict[str, Any] = { + "installer": {"name": "pip", "version": __version__}, + "python": platform.python_version(), + "implementation": { + "name": platform.python_implementation(), + }, + } + + if data["implementation"]["name"] == "CPython": + data["implementation"]["version"] = platform.python_version() + elif data["implementation"]["name"] == "PyPy": + pypy_version_info = sys.pypy_version_info # type: ignore + if pypy_version_info.releaselevel == "final": + pypy_version_info = pypy_version_info[:3] + data["implementation"]["version"] = ".".join( + [str(x) for x in pypy_version_info] + ) + elif data["implementation"]["name"] == "Jython": + # Complete Guess + data["implementation"]["version"] = platform.python_version() + elif data["implementation"]["name"] == "IronPython": + # Complete Guess + data["implementation"]["version"] = platform.python_version() + + if sys.platform.startswith("linux"): + from pipenv.patched.notpip._vendor import distro + + # https://github.com/nir0s/distro/pull/269 + linux_distribution = distro.linux_distribution() # type: ignore + distro_infos = dict( + filter( + lambda x: x[1], + zip(["name", "version", "id"], linux_distribution), + ) + ) + libc = dict( + filter( + lambda x: x[1], + zip(["lib", "version"], libc_ver()), + ) + ) + if libc: + distro_infos["libc"] = libc + if distro_infos: + data["distro"] = distro_infos + + if sys.platform.startswith("darwin") and platform.mac_ver()[0]: + data["distro"] = {"name": "macOS", "version": platform.mac_ver()[0]} + + if platform.system(): + data.setdefault("system", {})["name"] = platform.system() + + if platform.release(): + data.setdefault("system", {})["release"] = platform.release() + + if platform.machine(): + data["cpu"] = platform.machine() + + if has_tls(): + import _ssl as ssl + + data["openssl_version"] = ssl.OPENSSL_VERSION + + setuptools_dist = get_default_environment().get_distribution("setuptools") + if setuptools_dist is not None: + data["setuptools_version"] = str(setuptools_dist.version) + + if shutil.which("rustc") is not None: + # If for any reason `rustc --version` fails, silently ignore it + try: + rustc_output = subprocess.check_output( + ["rustc", "--version"], stderr=subprocess.STDOUT, timeout=0.5 + ) + except Exception: + pass + else: + if rustc_output.startswith(b"rustc "): + # The format of `rustc --version` is: + # `b'rustc 1.52.1 (9bc8c42bb 2021-05-09)\n'` + # We extract just the middle (1.52.1) part + data["rustc_version"] = rustc_output.split(b" ")[1].decode() + + # Use None rather than False so as not to give the impression that + # pip knows it is not being run under CI. Rather, it is a null or + # inconclusive result. Also, we include some value rather than no + # value to make it easier to know that the check has been run. + data["ci"] = True if looks_like_ci() else None + + user_data = os.environ.get("PIP_USER_AGENT_USER_DATA") + if user_data is not None: + data["user_data"] = user_data + + return "{data[installer][name]}/{data[installer][version]} {json}".format( + data=data, + json=json.dumps(data, separators=(",", ":"), sort_keys=True), + ) + + +class LocalFSAdapter(BaseAdapter): + def send( + self, + request: PreparedRequest, + stream: bool = False, + timeout: Optional[Union[float, Tuple[float, float]]] = None, + verify: Union[bool, str] = True, + cert: Optional[Union[str, Tuple[str, str]]] = None, + proxies: Optional[Mapping[str, str]] = None, + ) -> Response: + pathname = url_to_path(request.url) + + resp = Response() + resp.status_code = 200 + resp.url = request.url + + try: + stats = os.stat(pathname) + except OSError as exc: + resp.status_code = 404 + resp.raw = exc + else: + modified = email.utils.formatdate(stats.st_mtime, usegmt=True) + content_type = mimetypes.guess_type(pathname)[0] or "text/plain" + resp.headers = CaseInsensitiveDict( + { + "Content-Type": content_type, + "Content-Length": stats.st_size, + "Last-Modified": modified, + } + ) + + resp.raw = open(pathname, "rb") + resp.close = resp.raw.close + + return resp + + def close(self) -> None: + pass + + +class InsecureHTTPAdapter(HTTPAdapter): + def cert_verify( + self, + conn: ConnectionPool, + url: str, + verify: Union[bool, str], + cert: Optional[Union[str, Tuple[str, str]]], + ) -> None: + super().cert_verify(conn=conn, url=url, verify=False, cert=cert) + + +class InsecureCacheControlAdapter(CacheControlAdapter): + def cert_verify( + self, + conn: ConnectionPool, + url: str, + verify: Union[bool, str], + cert: Optional[Union[str, Tuple[str, str]]], + ) -> None: + super().cert_verify(conn=conn, url=url, verify=False, cert=cert) + + +class PipSession(requests.Session): + + timeout: Optional[int] = None + + def __init__( + self, + *args: Any, + retries: int = 0, + cache: Optional[str] = None, + trusted_hosts: Sequence[str] = (), + index_urls: Optional[List[str]] = None, + **kwargs: Any, + ) -> None: + """ + :param trusted_hosts: Domains not to emit warnings for when not using + HTTPS. + """ + super().__init__(*args, **kwargs) + + # Namespace the attribute with "pip_" just in case to prevent + # possible conflicts with the base class. + self.pip_trusted_origins: List[Tuple[str, Optional[int]]] = [] + + # Attach our User Agent to the request + self.headers["User-Agent"] = user_agent() + + # Attach our Authentication handler to the session + self.auth = MultiDomainBasicAuth(index_urls=index_urls) + + # Create our urllib3.Retry instance which will allow us to customize + # how we handle retries. + retries = urllib3.Retry( + # Set the total number of retries that a particular request can + # have. + total=retries, + # A 503 error from PyPI typically means that the Fastly -> Origin + # connection got interrupted in some way. A 503 error in general + # is typically considered a transient error so we'll go ahead and + # retry it. + # A 500 may indicate transient error in Amazon S3 + # A 520 or 527 - may indicate transient error in CloudFlare + status_forcelist=[500, 503, 520, 527], + # Add a small amount of back off between failed requests in + # order to prevent hammering the service. + backoff_factor=0.25, + ) # type: ignore + + # Our Insecure HTTPAdapter disables HTTPS validation. It does not + # support caching so we'll use it for all http:// URLs. + # If caching is disabled, we will also use it for + # https:// hosts that we've marked as ignoring + # TLS errors for (trusted-hosts). + insecure_adapter = InsecureHTTPAdapter(max_retries=retries) + + # We want to _only_ cache responses on securely fetched origins or when + # the host is specified as trusted. We do this because + # we can't validate the response of an insecurely/untrusted fetched + # origin, and we don't want someone to be able to poison the cache and + # require manual eviction from the cache to fix it. + if cache: + secure_adapter = CacheControlAdapter( + cache=SafeFileCache(cache), + max_retries=retries, + ) + self._trusted_host_adapter = InsecureCacheControlAdapter( + cache=SafeFileCache(cache), + max_retries=retries, + ) + else: + secure_adapter = HTTPAdapter(max_retries=retries) + self._trusted_host_adapter = insecure_adapter + + self.mount("https://", secure_adapter) + self.mount("http://", insecure_adapter) + + # Enable file:// urls + self.mount("file://", LocalFSAdapter()) + + for host in trusted_hosts: + self.add_trusted_host(host, suppress_logging=True) + + def update_index_urls(self, new_index_urls: List[str]) -> None: + """ + :param new_index_urls: New index urls to update the authentication + handler with. + """ + self.auth.index_urls = new_index_urls + + def add_trusted_host( + self, host: str, source: Optional[str] = None, suppress_logging: bool = False + ) -> None: + """ + :param host: It is okay to provide a host that has previously been + added. + :param source: An optional source string, for logging where the host + string came from. + """ + if not suppress_logging: + msg = f"adding trusted host: {host!r}" + if source is not None: + msg += f" (from {source})" + logger.info(msg) + + host_port = parse_netloc(host) + if host_port not in self.pip_trusted_origins: + self.pip_trusted_origins.append(host_port) + + self.mount(build_url_from_netloc(host) + "/", self._trusted_host_adapter) + if not host_port[1]: + # Mount wildcard ports for the same host. + self.mount(build_url_from_netloc(host) + ":", self._trusted_host_adapter) + + def iter_secure_origins(self) -> Iterator[SecureOrigin]: + yield from SECURE_ORIGINS + for host, port in self.pip_trusted_origins: + yield ("*", host, "*" if port is None else port) + + def is_secure_origin(self, location: Link) -> bool: + # Determine if this url used a secure transport mechanism + parsed = urllib.parse.urlparse(str(location)) + origin_protocol, origin_host, origin_port = ( + parsed.scheme, + parsed.hostname, + parsed.port, + ) + + # The protocol to use to see if the protocol matches. + # Don't count the repository type as part of the protocol: in + # cases such as "git+ssh", only use "ssh". (I.e., Only verify against + # the last scheme.) + origin_protocol = origin_protocol.rsplit("+", 1)[-1] + + # Determine if our origin is a secure origin by looking through our + # hardcoded list of secure origins, as well as any additional ones + # configured on this PackageFinder instance. + for secure_origin in self.iter_secure_origins(): + secure_protocol, secure_host, secure_port = secure_origin + if origin_protocol != secure_protocol and secure_protocol != "*": + continue + + try: + addr = ipaddress.ip_address(origin_host) + network = ipaddress.ip_network(secure_host) + except ValueError: + # We don't have both a valid address or a valid network, so + # we'll check this origin against hostnames. + if ( + origin_host + and origin_host.lower() != secure_host.lower() + and secure_host != "*" + ): + continue + else: + # We have a valid address and network, so see if the address + # is contained within the network. + if addr not in network: + continue + + # Check to see if the port matches. + if ( + origin_port != secure_port + and secure_port != "*" + and secure_port is not None + ): + continue + + # If we've gotten here, then this origin matches the current + # secure origin and we should return True + return True + + # If we've gotten to this point, then the origin isn't secure and we + # will not accept it as a valid location to search. We will however + # log a warning that we are ignoring it. + logger.warning( + "The repository located at %s is not a trusted or secure host and " + "is being ignored. If this repository is available via HTTPS we " + "recommend you use HTTPS instead, otherwise you may silence " + "this warning and allow it anyway with '--trusted-host %s'.", + origin_host, + origin_host, + ) + + return False + + def request(self, method: str, url: str, *args: Any, **kwargs: Any) -> Response: + # Allow setting a default timeout on a session + kwargs.setdefault("timeout", self.timeout) + + # Dispatch the actual request + return super().request(method, url, *args, **kwargs) diff --git a/pipenv/patched/notpip/_internal/network/utils.py b/pipenv/patched/notpip/_internal/network/utils.py new file mode 100644 index 00000000..3825f9ed --- /dev/null +++ b/pipenv/patched/notpip/_internal/network/utils.py @@ -0,0 +1,96 @@ +from typing import Dict, Iterator + +from pipenv.patched.notpip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response + +from pipenv.patched.notpip._internal.exceptions import NetworkConnectionError + +# The following comments and HTTP headers were originally added by +# Donald Stufft in git commit 22c562429a61bb77172039e480873fb239dd8c03. +# +# We use Accept-Encoding: identity here because requests defaults to +# accepting compressed responses. This breaks in a variety of ways +# depending on how the server is configured. +# - Some servers will notice that the file isn't a compressible file +# and will leave the file alone and with an empty Content-Encoding +# - Some servers will notice that the file is already compressed and +# will leave the file alone, adding a Content-Encoding: gzip header +# - Some servers won't notice anything at all and will take a file +# that's already been compressed and compress it again, and set +# the Content-Encoding: gzip header +# By setting this to request only the identity encoding we're hoping +# to eliminate the third case. Hopefully there does not exist a server +# which when given a file will notice it is already compressed and that +# you're not asking for a compressed file and will then decompress it +# before sending because if that's the case I don't think it'll ever be +# possible to make this work. +HEADERS: Dict[str, str] = {"Accept-Encoding": "identity"} + + +def raise_for_status(resp: Response) -> None: + http_error_msg = "" + if isinstance(resp.reason, bytes): + # We attempt to decode utf-8 first because some servers + # choose to localize their reason strings. If the string + # isn't utf-8, we fall back to iso-8859-1 for all other + # encodings. + try: + reason = resp.reason.decode("utf-8") + except UnicodeDecodeError: + reason = resp.reason.decode("iso-8859-1") + else: + reason = resp.reason + + if 400 <= resp.status_code < 500: + http_error_msg = ( + f"{resp.status_code} Client Error: {reason} for url: {resp.url}" + ) + + elif 500 <= resp.status_code < 600: + http_error_msg = ( + f"{resp.status_code} Server Error: {reason} for url: {resp.url}" + ) + + if http_error_msg: + raise NetworkConnectionError(http_error_msg, response=resp) + + +def response_chunks( + response: Response, chunk_size: int = CONTENT_CHUNK_SIZE +) -> Iterator[bytes]: + """Given a requests Response, provide the data chunks.""" + try: + # Special case for urllib3. + for chunk in response.raw.stream( + chunk_size, + # We use decode_content=False here because we don't + # want urllib3 to mess with the raw bytes we get + # from the server. If we decompress inside of + # urllib3 then we cannot verify the checksum + # because the checksum will be of the compressed + # file. This breakage will only occur if the + # server adds a Content-Encoding header, which + # depends on how the server was configured: + # - Some servers will notice that the file isn't a + # compressible file and will leave the file alone + # and with an empty Content-Encoding + # - Some servers will notice that the file is + # already compressed and will leave the file + # alone and will add a Content-Encoding: gzip + # header + # - Some servers won't notice anything at all and + # will take a file that's already been compressed + # and compress it again and set the + # Content-Encoding: gzip header + # + # By setting this not to decode automatically we + # hope to eliminate problems with the second case. + decode_content=False, + ): + yield chunk + except AttributeError: + # Standard file-like object. + while True: + chunk = response.raw.read(chunk_size) + if not chunk: + break + yield chunk diff --git a/pipenv/patched/notpip/_internal/network/xmlrpc.py b/pipenv/patched/notpip/_internal/network/xmlrpc.py new file mode 100644 index 00000000..bbbb13cf --- /dev/null +++ b/pipenv/patched/notpip/_internal/network/xmlrpc.py @@ -0,0 +1,60 @@ +"""xmlrpclib.Transport implementation +""" + +import logging +import urllib.parse +import xmlrpc.client +from typing import TYPE_CHECKING, Tuple + +from pipenv.patched.notpip._internal.exceptions import NetworkConnectionError +from pipenv.patched.notpip._internal.network.session import PipSession +from pipenv.patched.notpip._internal.network.utils import raise_for_status + +if TYPE_CHECKING: + from xmlrpc.client import _HostType, _Marshallable + +logger = logging.getLogger(__name__) + + +class PipXmlrpcTransport(xmlrpc.client.Transport): + """Provide a `xmlrpclib.Transport` implementation via a `PipSession` + object. + """ + + def __init__( + self, index_url: str, session: PipSession, use_datetime: bool = False + ) -> None: + super().__init__(use_datetime) + index_parts = urllib.parse.urlparse(index_url) + self._scheme = index_parts.scheme + self._session = session + + def request( + self, + host: "_HostType", + handler: str, + request_body: bytes, + verbose: bool = False, + ) -> Tuple["_Marshallable", ...]: + assert isinstance(host, str) + parts = (self._scheme, host, handler, None, None, None) + url = urllib.parse.urlunparse(parts) + try: + headers = {"Content-Type": "text/xml"} + response = self._session.post( + url, + data=request_body, + headers=headers, + stream=True, + ) + raise_for_status(response) + self.verbose = verbose + return self.parse_response(response.raw) + except NetworkConnectionError as exc: + assert exc.response + logger.critical( + "HTTP error %s while getting %s", + exc.response.status_code, + url, + ) + raise diff --git a/pipenv/patched/piptools/__init__.py b/pipenv/patched/notpip/_internal/operations/build/__init__.py similarity index 100% rename from pipenv/patched/piptools/__init__.py rename to pipenv/patched/notpip/_internal/operations/build/__init__.py diff --git a/pipenv/patched/notpip/_internal/operations/build/metadata.py b/pipenv/patched/notpip/_internal/operations/build/metadata.py new file mode 100644 index 00000000..ced231b4 --- /dev/null +++ b/pipenv/patched/notpip/_internal/operations/build/metadata.py @@ -0,0 +1,35 @@ +"""Metadata generation logic for source distributions. +""" + +import os + +from pipenv.patched.notpip._vendor.pep517.wrappers import Pep517HookCaller + +from pipenv.patched.notpip._internal.build_env import BuildEnvironment +from pipenv.patched.notpip._internal.utils.subprocess import runner_with_spinner_message +from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory + + +def generate_metadata(build_env, backend): + # type: (BuildEnvironment, Pep517HookCaller) -> str + """Generate metadata using mechanisms described in PEP 517. + + Returns the generated metadata directory. + """ + metadata_tmpdir = TempDirectory( + kind="modern-metadata", globally_managed=True + ) + + metadata_dir = metadata_tmpdir.path + + with build_env: + # Note that Pep517HookCaller implements a fallback for + # prepare_metadata_for_build_wheel, so we don't have to + # consider the possibility that this hook doesn't exist. + runner = runner_with_spinner_message("Preparing wheel metadata") + with backend.subprocess_runner(runner): + distinfo_dir = backend.prepare_metadata_for_build_wheel( + metadata_dir + ) + + return os.path.join(metadata_dir, distinfo_dir) diff --git a/pipenv/patched/notpip/_internal/operations/build/metadata_legacy.py b/pipenv/patched/notpip/_internal/operations/build/metadata_legacy.py new file mode 100644 index 00000000..5443b866 --- /dev/null +++ b/pipenv/patched/notpip/_internal/operations/build/metadata_legacy.py @@ -0,0 +1,74 @@ +"""Metadata generation logic for legacy source distributions. +""" + +import logging +import os + +from pipenv.patched.notpip._internal.build_env import BuildEnvironment +from pipenv.patched.notpip._internal.exceptions import InstallationError +from pipenv.patched.notpip._internal.utils.setuptools_build import make_setuptools_egg_info_args +from pipenv.patched.notpip._internal.utils.subprocess import call_subprocess +from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory + +logger = logging.getLogger(__name__) + + +def _find_egg_info(directory): + # type: (str) -> str + """Find an .egg-info subdirectory in `directory`. + """ + filenames = [ + f for f in os.listdir(directory) if f.endswith(".egg-info") + ] + + if not filenames: + raise InstallationError( + f"No .egg-info directory found in {directory}" + ) + + if len(filenames) > 1: + raise InstallationError( + "More than one .egg-info directory found in {}".format( + directory + ) + ) + + return os.path.join(directory, filenames[0]) + + +def generate_metadata( + build_env, # type: BuildEnvironment + setup_py_path, # type: str + source_dir, # type: str + isolated, # type: bool + details, # type: str +): + # type: (...) -> str + """Generate metadata using setup.py-based defacto mechanisms. + + Returns the generated metadata directory. + """ + logger.debug( + 'Running setup.py (path:%s) egg_info for package %s', + setup_py_path, details, + ) + + egg_info_dir = TempDirectory( + kind="pip-egg-info", globally_managed=True + ).path + + args = make_setuptools_egg_info_args( + setup_py_path, + egg_info_dir=egg_info_dir, + no_user_config=isolated, + ) + + with build_env: + call_subprocess( + args, + cwd=source_dir, + command_desc='python setup.py egg_info', + ) + + # Return the .egg-info directory. + return _find_egg_info(egg_info_dir) diff --git a/pipenv/patched/notpip/_internal/operations/build/wheel.py b/pipenv/patched/notpip/_internal/operations/build/wheel.py new file mode 100644 index 00000000..d8ea255f --- /dev/null +++ b/pipenv/patched/notpip/_internal/operations/build/wheel.py @@ -0,0 +1,38 @@ +import logging +import os +from typing import Optional + +from pipenv.patched.notpip._vendor.pep517.wrappers import Pep517HookCaller + +from pipenv.patched.notpip._internal.utils.subprocess import runner_with_spinner_message + +logger = logging.getLogger(__name__) + + +def build_wheel_pep517( + name, # type: str + backend, # type: Pep517HookCaller + metadata_directory, # type: str + tempd, # type: str +): + # type: (...) -> Optional[str] + """Build one InstallRequirement using the PEP 517 build process. + + Returns path to wheel if successfully built. Otherwise, returns None. + """ + assert metadata_directory is not None + try: + logger.debug('Destination directory: %s', tempd) + + runner = runner_with_spinner_message( + f'Building wheel for {name} (PEP 517)' + ) + with backend.subprocess_runner(runner): + wheel_name = backend.build_wheel( + tempd, + metadata_directory=metadata_directory, + ) + except Exception: + logger.error('Failed building wheel for %s', name) + return None + return os.path.join(tempd, wheel_name) diff --git a/pipenv/patched/notpip/_internal/operations/build/wheel_legacy.py b/pipenv/patched/notpip/_internal/operations/build/wheel_legacy.py new file mode 100644 index 00000000..f631c78a --- /dev/null +++ b/pipenv/patched/notpip/_internal/operations/build/wheel_legacy.py @@ -0,0 +1,110 @@ +import logging +import os.path +from typing import List, Optional + +from pipenv.patched.notpip._internal.cli.spinners import open_spinner +from pipenv.patched.notpip._internal.utils.setuptools_build import make_setuptools_bdist_wheel_args +from pipenv.patched.notpip._internal.utils.subprocess import ( + LOG_DIVIDER, + call_subprocess, + format_command_args, +) + +logger = logging.getLogger(__name__) + + +def format_command_result( + command_args, # type: List[str] + command_output, # type: str +): + # type: (...) -> str + """Format command information for logging.""" + command_desc = format_command_args(command_args) + text = f'Command arguments: {command_desc}\n' + + if not command_output: + text += 'Command output: None' + elif logger.getEffectiveLevel() > logging.DEBUG: + text += 'Command output: [use --verbose to show]' + else: + if not command_output.endswith('\n'): + command_output += '\n' + text += f'Command output:\n{command_output}{LOG_DIVIDER}' + + return text + + +def get_legacy_build_wheel_path( + names, # type: List[str] + temp_dir, # type: str + name, # type: str + command_args, # type: List[str] + command_output, # type: str +): + # type: (...) -> Optional[str] + """Return the path to the wheel in the temporary build directory.""" + # Sort for determinism. + names = sorted(names) + if not names: + msg = ( + 'Legacy build of wheel for {!r} created no files.\n' + ).format(name) + msg += format_command_result(command_args, command_output) + logger.warning(msg) + return None + + if len(names) > 1: + msg = ( + 'Legacy build of wheel for {!r} created more than one file.\n' + 'Filenames (choosing first): {}\n' + ).format(name, names) + msg += format_command_result(command_args, command_output) + logger.warning(msg) + + return os.path.join(temp_dir, names[0]) + + +def build_wheel_legacy( + name, # type: str + setup_py_path, # type: str + source_dir, # type: str + global_options, # type: List[str] + build_options, # type: List[str] + tempd, # type: str +): + # type: (...) -> Optional[str] + """Build one unpacked package using the "legacy" build process. + + Returns path to wheel if successfully built. Otherwise, returns None. + """ + wheel_args = make_setuptools_bdist_wheel_args( + setup_py_path, + global_options=global_options, + build_options=build_options, + destination_dir=tempd, + ) + + spin_message = f'Building wheel for {name} (setup.py)' + with open_spinner(spin_message) as spinner: + logger.debug('Destination directory: %s', tempd) + + try: + output = call_subprocess( + wheel_args, + cwd=source_dir, + spinner=spinner, + ) + except Exception: + spinner.finish("error") + logger.error('Failed building wheel for %s', name) + return None + + names = os.listdir(tempd) + wheel_path = get_legacy_build_wheel_path( + names=names, + temp_dir=tempd, + name=name, + command_args=wheel_args, + command_output=output, + ) + return wheel_path diff --git a/pipenv/patched/notpip/_internal/operations/check.py b/pipenv/patched/notpip/_internal/operations/check.py index a73611d4..a65cb08f 100644 --- a/pipenv/patched/notpip/_internal/operations/check.py +++ b/pipenv/patched/notpip/_internal/operations/check.py @@ -2,52 +2,51 @@ """ import logging -from collections import namedtuple +from typing import TYPE_CHECKING, Callable, Dict, List, NamedTuple, Optional, Set, Tuple +from pipenv.patched.notpip._vendor.packaging.requirements import Requirement from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name -from pipenv.patched.notpip._vendor.pkg_resources import RequirementParseError -from pipenv.patched.notpip._internal.operations.prepare import make_abstract_dist -from pipenv.patched.notpip._internal.utils.misc import get_installed_distributions -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING +from pipenv.patched.notpip._internal.distributions import make_distribution_for_install_requirement +from pipenv.patched.notpip._internal.metadata import get_default_environment +from pipenv.patched.notpip._internal.metadata.base import DistributionVersion +from pipenv.patched.notpip._internal.req.req_install import InstallRequirement + +if TYPE_CHECKING: + from pipenv.patched.notpip._vendor.packaging.utils import NormalizedName logger = logging.getLogger(__name__) -if MYPY_CHECK_RUNNING: - from pipenv.patched.notpip._internal.req.req_install import InstallRequirement # noqa: F401 - from typing import ( # noqa: F401 - Any, Callable, Dict, Optional, Set, Tuple, List - ) - # Shorthands - PackageSet = Dict[str, 'PackageDetails'] - Missing = Tuple[str, Any] - Conflicting = Tuple[str, str, Any] - - MissingDict = Dict[str, List[Missing]] - ConflictingDict = Dict[str, List[Conflicting]] - CheckResult = Tuple[MissingDict, ConflictingDict] - -PackageDetails = namedtuple('PackageDetails', ['version', 'requires']) +class PackageDetails(NamedTuple): + version: DistributionVersion + dependencies: List[Requirement] -def create_package_set_from_installed(**kwargs): - # type: (**Any) -> Tuple[PackageSet, bool] - """Converts a list of distributions into a PackageSet. - """ - # Default to using all packages installed on the system - if kwargs == {}: - kwargs = {"local_only": False, "skip": ()} +# Shorthands +PackageSet = Dict['NormalizedName', PackageDetails] +Missing = Tuple['NormalizedName', Requirement] +Conflicting = Tuple['NormalizedName', DistributionVersion, Requirement] +MissingDict = Dict['NormalizedName', List[Missing]] +ConflictingDict = Dict['NormalizedName', List[Conflicting]] +CheckResult = Tuple[MissingDict, ConflictingDict] +ConflictDetails = Tuple[PackageSet, CheckResult] + + +def create_package_set_from_installed() -> Tuple[PackageSet, bool]: + """Converts a list of distributions into a PackageSet.""" package_set = {} problems = False - for dist in get_installed_distributions(**kwargs): - name = canonicalize_name(dist.project_name) + env = get_default_environment() + for dist in env.iter_installed_distributions(local_only=False, skip=()): + name = dist.canonical_name try: - package_set[name] = PackageDetails(dist.version, dist.requires()) - except RequirementParseError as e: - # Don't crash on broken metadata - logging.warning("Error parsing requirements for %s: %s", name, e) + dependencies = list(dist.iter_dependencies()) + package_set[name] = PackageDetails(dist.version, dependencies) + except (OSError, ValueError) as e: + # Don't crash on unreadable or broken metadata. + logger.warning("Error parsing requirements for %s: %s", name, e) problems = True return package_set, problems @@ -59,23 +58,20 @@ def check_package_set(package_set, should_ignore=None): If should_ignore is passed, it should be a callable that takes a package name and returns a boolean. """ - if should_ignore is None: - def should_ignore(name): - return False - missing = dict() - conflicting = dict() + missing = {} + conflicting = {} - for package_name in package_set: + for package_name, package_detail in package_set.items(): # Info about dependencies of package_name missing_deps = set() # type: Set[Missing] conflicting_deps = set() # type: Set[Conflicting] - if should_ignore(package_name): + if should_ignore and should_ignore(package_name): continue - for req in package_set[package_name].requires: - name = canonicalize_name(req.project_name) # type: str + for req in package_detail.dependencies: + name = canonicalize_name(req.name) # Check if it's missing if name not in package_set: @@ -87,7 +83,7 @@ def check_package_set(package_set, should_ignore=None): continue # Check if there's a conflict - version = package_set[name].version # type: str + version = package_set[name].version if not req.specifier.contains(version, prereleases=True): conflicting_deps.add((name, version, req)) @@ -100,7 +96,7 @@ def check_package_set(package_set, should_ignore=None): def check_install_conflicts(to_install): - # type: (List[InstallRequirement]) -> Tuple[PackageSet, CheckResult] + # type: (List[InstallRequirement]) -> ConflictDetails """For checking if the dependency graph would be consistent after \ installing given requirements """ @@ -121,18 +117,20 @@ def check_install_conflicts(to_install): def _simulate_installation_of(to_install, package_set): - # type: (List[InstallRequirement], PackageSet) -> Set[str] + # type: (List[InstallRequirement], PackageSet) -> Set[NormalizedName] """Computes the version of packages after installing to_install. """ - # Keep track of packages that were installed installed = set() # Modify it as installing requirement_set would (assuming no errors) for inst_req in to_install: - dist = make_abstract_dist(inst_req).dist() - name = canonicalize_name(dist.key) - package_set[name] = PackageDetails(dist.version, dist.requires()) + abstract_dist = make_distribution_for_install_requirement(inst_req) + dist = abstract_dist.get_pkg_resources_distribution() + + assert dist is not None + name = canonicalize_name(dist.project_name) + package_set[name] = PackageDetails(dist.parsed_version, dist.requires()) installed.add(name) @@ -140,14 +138,14 @@ def _simulate_installation_of(to_install, package_set): def _create_whitelist(would_be_installed, package_set): - # type: (Set[str], PackageSet) -> Set[str] + # type: (Set[NormalizedName], PackageSet) -> Set[NormalizedName] packages_affected = set(would_be_installed) for package_name in package_set: if package_name in packages_affected: continue - for req in package_set[package_name].requires: + for req in package_set[package_name].dependencies: if canonicalize_name(req.name) in packages_affected: packages_affected.add(package_name) break diff --git a/pipenv/patched/notpip/_internal/operations/freeze.py b/pipenv/patched/notpip/_internal/operations/freeze.py index 8fd755e8..10365536 100644 --- a/pipenv/patched/notpip/_internal/operations/freeze.py +++ b/pipenv/patched/notpip/_internal/operations/freeze.py @@ -1,74 +1,62 @@ -from __future__ import absolute_import - import collections import logging import os -import re +from typing import ( + Container, + Dict, + Iterable, + Iterator, + List, + NamedTuple, + Optional, + Set, + Union, +) -from pipenv.patched.notpip._vendor import six +from pipenv.patched.notpip._vendor.packaging.requirements import Requirement from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name -from pipenv.patched.notpip._vendor.pkg_resources import RequirementParseError +from pipenv.patched.notpip._vendor.packaging.version import Version from pipenv.patched.notpip._internal.exceptions import BadCommand, InstallationError +from pipenv.patched.notpip._internal.metadata import BaseDistribution, get_environment from pipenv.patched.notpip._internal.req.constructors import ( - install_req_from_editable, install_req_from_line, + install_req_from_editable, + install_req_from_line, ) from pipenv.patched.notpip._internal.req.req_file import COMMENT_RE -from pipenv.patched.notpip._internal.utils.misc import ( - dist_is_editable, get_installed_distributions, -) -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING - -if MYPY_CHECK_RUNNING: - from typing import ( # noqa: F401 - Iterator, Optional, List, Container, Set, Dict, Tuple, Iterable, Union - ) - from pipenv.patched.notpip._internal.cache import WheelCache # noqa: F401 - from pipenv.patched.notpip._vendor.pkg_resources import ( # noqa: F401 - Distribution, Requirement - ) - - RequirementInfo = Tuple[Optional[Union[str, Requirement]], bool, List[str]] - +from pipenv.patched.notpip._internal.utils.direct_url_helpers import direct_url_as_pep440_direct_reference logger = logging.getLogger(__name__) +class _EditableInfo(NamedTuple): + requirement: Optional[str] + editable: bool + comments: List[str] + + def freeze( requirement=None, # type: Optional[List[str]] - find_links=None, # type: Optional[List[str]] - local_only=None, # type: Optional[bool] - user_only=None, # type: Optional[bool] - skip_regex=None, # type: Optional[str] + local_only=False, # type: bool + user_only=False, # type: bool + paths=None, # type: Optional[List[str]] isolated=False, # type: bool - wheel_cache=None, # type: Optional[WheelCache] exclude_editable=False, # type: bool skip=() # type: Container[str] ): # type: (...) -> Iterator[str] - find_links = find_links or [] - skip_match = None - - if skip_regex: - skip_match = re.compile(skip_regex).search - - for link in find_links: - yield '-f %s' % link installations = {} # type: Dict[str, FrozenRequirement] - for dist in get_installed_distributions(local_only=local_only, - skip=(), - user_only=user_only): - try: - req = FrozenRequirement.from_dist(dist) - except RequirementParseError: - logger.warning( - "Could not parse requirement: %s", - dist.project_name - ) - continue + + dists = get_environment(paths).iter_installed_distributions( + local_only=local_only, + skip=(), + user_only=user_only, + ) + for dist in dists: + req = FrozenRequirement.from_dist(dist) if exclude_editable and req.editable: continue - installations[req.name] = req + installations[req.canonical_name] = req if requirement: # the options that don't get turned into an InstallRequirement @@ -84,16 +72,15 @@ def freeze( for line in req_file: if (not line.strip() or line.strip().startswith('#') or - (skip_match and skip_match(line)) or line.startswith(( '-r', '--requirement', - '-Z', '--always-unzip', '-f', '--find-links', '-i', '--index-url', '--pre', '--trusted-host', '--process-dependency-links', - '--extra-index-url'))): + '--extra-index-url', + '--use-feature'))): line = line.rstrip() if line not in emitted_options: emitted_options.add(line) @@ -108,13 +95,11 @@ def freeze( line_req = install_req_from_editable( line, isolated=isolated, - wheel_cache=wheel_cache, ) else: line_req = install_req_from_line( COMMENT_RE.sub('', line).strip(), isolated=isolated, - wheel_cache=wheel_cache, ) if not line_req.name: @@ -127,26 +112,31 @@ def freeze( " (add #egg=PackageName to the URL to avoid" " this warning)" ) - elif line_req.name not in installations: - # either it's not installed, or it is installed - # but has been processed already - if not req_files[line_req.name]: - logger.warning( - "Requirement file [%s] contains %s, but " - "package %r is not installed", - req_file_path, - COMMENT_RE.sub('', line).strip(), line_req.name - ) - else: - req_files[line_req.name].append(req_file_path) else: - yield str(installations[line_req.name]).rstrip() - del installations[line_req.name] - req_files[line_req.name].append(req_file_path) + line_req_canonical_name = canonicalize_name( + line_req.name) + if line_req_canonical_name not in installations: + # either it's not installed, or it is installed + # but has been processed already + if not req_files[line_req.name]: + logger.warning( + "Requirement file [%s] contains %s, but " + "package %r is not installed", + req_file_path, + COMMENT_RE.sub('', line).strip(), + line_req.name + ) + else: + req_files[line_req.name].append(req_file_path) + else: + yield str(installations[ + line_req_canonical_name]).rstrip() + del installations[line_req_canonical_name] + req_files[line_req.name].append(req_file_path) # Warn about requirements that were included multiple times (in a # single requirements file or in different requirements files). - for name, files in six.iteritems(req_files): + for name, files in req_files.items(): if len(files) > 1: logger.warning("Requirement %s included multiple times [%s]", name, ', '.join(sorted(set(files)))) @@ -157,54 +147,81 @@ def freeze( ) for installation in sorted( installations.values(), key=lambda x: x.name.lower()): - if canonicalize_name(installation.name) not in skip: + if installation.canonical_name not in skip: yield str(installation).rstrip() -def get_requirement_info(dist): - # type: (Distribution) -> RequirementInfo +def _format_as_name_version(dist: BaseDistribution) -> str: + if isinstance(dist.version, Version): + return f"{dist.raw_name}=={dist.version}" + return f"{dist.raw_name}==={dist.version}" + + +def _get_editable_info(dist: BaseDistribution) -> _EditableInfo: """ Compute and return values (req, editable, comments) for use in FrozenRequirement.from_dist(). """ - if not dist_is_editable(dist): - return (None, False, []) + if not dist.editable: + return _EditableInfo(requirement=None, editable=False, comments=[]) + if dist.location is None: + display = _format_as_name_version(dist) + logger.warning("Editable requirement not found on disk: %s", display) + return _EditableInfo( + requirement=None, + editable=True, + comments=[f"# Editable install not found ({display})"], + ) location = os.path.normcase(os.path.abspath(dist.location)) - from pipenv.patched.notpip._internal.vcs import vcs, RemoteNotFoundError - vc_type = vcs.get_backend_type(location) + from pipenv.patched.notpip._internal.vcs import RemoteNotFoundError, RemoteNotValidError, vcs - if not vc_type: - req = dist.as_requirement() + vcs_backend = vcs.get_backend_for_dir(location) + + if vcs_backend is None: + display = _format_as_name_version(dist) logger.debug( - 'No VCS found for editable requirement {!r} in: {!r}', req, + 'No VCS found for editable requirement "%s" in: %r', display, location, ) - comments = [ - '# Editable install with no version control ({})'.format(req) - ] - return (location, True, comments) + return _EditableInfo( + requirement=location, + editable=True, + comments=[f'# Editable install with no version control ({display})'], + ) + + vcs_name = type(vcs_backend).__name__ try: - req = vc_type.get_src_requirement(location, dist.project_name) + req = vcs_backend.get_src_requirement(location, dist.raw_name) except RemoteNotFoundError: - req = dist.as_requirement() - comments = [ - '# Editable {} install with no remote ({})'.format( - vc_type.__name__, req, - ) - ] - return (location, True, comments) + display = _format_as_name_version(dist) + return _EditableInfo( + requirement=location, + editable=True, + comments=[f'# Editable {vcs_name} install with no remote ({display})'], + ) + except RemoteNotValidError as ex: + display = _format_as_name_version(dist) + return _EditableInfo( + requirement=location, + editable=True, + comments=[ + f"# Editable {vcs_name} install ({display}) with either a deleted " + f"local remote or invalid URI:", + f"# '{ex.url}'", + ], + ) except BadCommand: logger.warning( 'cannot determine version of editable source in %s ' '(%s command not found in path)', location, - vc_type.name, + vcs_backend.name, ) - return (None, True, []) + return _EditableInfo(requirement=None, editable=True, comments=[]) except InstallationError as exc: logger.warning( @@ -212,36 +229,49 @@ def get_requirement_info(dist): "falling back to uneditable format", exc ) else: - if req is not None: - return (req, True, []) + return _EditableInfo(requirement=req, editable=True, comments=[]) - logger.warning( - 'Could not determine repository location of %s', location + logger.warning('Could not determine repository location of %s', location) + + return _EditableInfo( + requirement=None, + editable=False, + comments=['## !! Could not determine repository location'], ) - comments = ['## !! Could not determine repository location'] - - return (None, False, comments) -class FrozenRequirement(object): +class FrozenRequirement: def __init__(self, name, req, editable, comments=()): # type: (str, Union[str, Requirement], bool, Iterable[str]) -> None self.name = name + self.canonical_name = canonicalize_name(name) self.req = req self.editable = editable self.comments = comments @classmethod - def from_dist(cls, dist): - # type: (Distribution) -> FrozenRequirement - req, editable, comments = get_requirement_info(dist) + def from_dist(cls, dist: BaseDistribution) -> "FrozenRequirement": + # TODO `get_requirement_info` is taking care of editable requirements. + # TODO This should be refactored when we will add detection of + # editable that provide .dist-info metadata. + req, editable, comments = _get_editable_info(dist) + if req is None and not editable: + # if PEP 610 metadata is present, attempt to use it + direct_url = dist.direct_url + if direct_url: + req = direct_url_as_pep440_direct_reference( + direct_url, dist.raw_name + ) + comments = [] if req is None: - req = dist.as_requirement() + # name==version requirement + req = _format_as_name_version(dist) - return cls(dist.project_name, req, editable, comments=comments) + return cls(dist.raw_name, req, editable, comments=comments) def __str__(self): + # type: () -> str req = self.req if self.editable: - req = '-e %s' % req + req = f'-e {req}' return '\n'.join(list(self.comments) + [str(req)]) + '\n' diff --git a/pipenv/patched/notpip/_internal/operations/install/__init__.py b/pipenv/patched/notpip/_internal/operations/install/__init__.py new file mode 100644 index 00000000..24d6a5dd --- /dev/null +++ b/pipenv/patched/notpip/_internal/operations/install/__init__.py @@ -0,0 +1,2 @@ +"""For modules related to installing packages. +""" diff --git a/pipenv/patched/notpip/_internal/operations/install/editable_legacy.py b/pipenv/patched/notpip/_internal/operations/install/editable_legacy.py new file mode 100644 index 00000000..2c627458 --- /dev/null +++ b/pipenv/patched/notpip/_internal/operations/install/editable_legacy.py @@ -0,0 +1,47 @@ +"""Legacy editable installation process, i.e. `setup.py develop`. +""" +import logging +from typing import List, Optional, Sequence + +from pipenv.patched.notpip._internal.build_env import BuildEnvironment +from pipenv.patched.notpip._internal.utils.logging import indent_log +from pipenv.patched.notpip._internal.utils.setuptools_build import make_setuptools_develop_args +from pipenv.patched.notpip._internal.utils.subprocess import call_subprocess + +logger = logging.getLogger(__name__) + + +def install_editable( + install_options, # type: List[str] + global_options, # type: Sequence[str] + prefix, # type: Optional[str] + home, # type: Optional[str] + use_user_site, # type: bool + name, # type: str + setup_py_path, # type: str + isolated, # type: bool + build_env, # type: BuildEnvironment + unpacked_source_directory, # type: str +): + # type: (...) -> None + """Install a package in editable mode. Most arguments are pass-through + to setuptools. + """ + logger.info('Running setup.py develop for %s', name) + + args = make_setuptools_develop_args( + setup_py_path, + global_options=global_options, + install_options=install_options, + no_user_config=isolated, + prefix=prefix, + home=home, + use_user_site=use_user_site, + ) + + with indent_log(): + with build_env: + call_subprocess( + args, + cwd=unpacked_source_directory, + ) diff --git a/pipenv/patched/notpip/_internal/operations/install/legacy.py b/pipenv/patched/notpip/_internal/operations/install/legacy.py new file mode 100644 index 00000000..e7dcc42f --- /dev/null +++ b/pipenv/patched/notpip/_internal/operations/install/legacy.py @@ -0,0 +1,132 @@ +"""Legacy installation process, i.e. `setup.py install`. +""" + +import logging +import os +import sys +from distutils.util import change_root +from typing import List, Optional, Sequence + +from pipenv.patched.notpip._internal.build_env import BuildEnvironment +from pipenv.patched.notpip._internal.exceptions import InstallationError +from pipenv.patched.notpip._internal.models.scheme import Scheme +from pipenv.patched.notpip._internal.utils.logging import indent_log +from pipenv.patched.notpip._internal.utils.misc import ensure_dir +from pipenv.patched.notpip._internal.utils.setuptools_build import make_setuptools_install_args +from pipenv.patched.notpip._internal.utils.subprocess import runner_with_spinner_message +from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory + +logger = logging.getLogger(__name__) + + +class LegacyInstallFailure(Exception): + def __init__(self): + # type: () -> None + self.parent = sys.exc_info() + + +def write_installed_files_from_setuptools_record( + record_lines: List[str], + root: Optional[str], + req_description: str, +) -> None: + def prepend_root(path): + # type: (str) -> str + if root is None or not os.path.isabs(path): + return path + else: + return change_root(root, path) + + for line in record_lines: + directory = os.path.dirname(line) + if directory.endswith('.egg-info'): + egg_info_dir = prepend_root(directory) + break + else: + message = ( + "{} did not indicate that it installed an " + ".egg-info directory. Only setup.py projects " + "generating .egg-info directories are supported." + ).format(req_description) + raise InstallationError(message) + + new_lines = [] + for line in record_lines: + filename = line.strip() + if os.path.isdir(filename): + filename += os.path.sep + new_lines.append( + os.path.relpath(prepend_root(filename), egg_info_dir) + ) + new_lines.sort() + ensure_dir(egg_info_dir) + inst_files_path = os.path.join(egg_info_dir, 'installed-files.txt') + with open(inst_files_path, 'w') as f: + f.write('\n'.join(new_lines) + '\n') + + +def install( + install_options, # type: List[str] + global_options, # type: Sequence[str] + root, # type: Optional[str] + home, # type: Optional[str] + prefix, # type: Optional[str] + use_user_site, # type: bool + pycompile, # type: bool + scheme, # type: Scheme + setup_py_path, # type: str + isolated, # type: bool + req_name, # type: str + build_env, # type: BuildEnvironment + unpacked_source_directory, # type: str + req_description, # type: str +): + # type: (...) -> bool + + header_dir = scheme.headers + + with TempDirectory(kind="record") as temp_dir: + try: + record_filename = os.path.join(temp_dir.path, 'install-record.txt') + install_args = make_setuptools_install_args( + setup_py_path, + global_options=global_options, + install_options=install_options, + record_filename=record_filename, + root=root, + prefix=prefix, + header_dir=header_dir, + home=home, + use_user_site=use_user_site, + no_user_config=isolated, + pycompile=pycompile, + ) + + runner = runner_with_spinner_message( + f"Running setup.py install for {req_name}" + ) + with indent_log(), build_env: + runner( + cmd=install_args, + cwd=unpacked_source_directory, + ) + + if not os.path.exists(record_filename): + logger.debug('Record file %s not found', record_filename) + # Signal to the caller that we didn't install the new package + return False + + except Exception: + # Signal to the caller that we didn't install the new package + raise LegacyInstallFailure + + # At this point, we have successfully installed the requirement. + + # We intentionally do not use any encoding to read the file because + # setuptools writes the file using distutils.file_util.write_file, + # which does not specify an encoding. + with open(record_filename) as f: + record_lines = f.read().splitlines() + + write_installed_files_from_setuptools_record(record_lines, root, req_description) + return True diff --git a/pipenv/patched/notpip/_internal/operations/install/wheel.py b/pipenv/patched/notpip/_internal/operations/install/wheel.py new file mode 100644 index 00000000..d26ef151 --- /dev/null +++ b/pipenv/patched/notpip/_internal/operations/install/wheel.py @@ -0,0 +1,803 @@ +"""Support for installing and building the "wheel" binary package format. +""" + +import collections +import compileall +import contextlib +import csv +import importlib +import logging +import os.path +import re +import shutil +import sys +import warnings +from base64 import urlsafe_b64encode +from email.message import Message +from itertools import chain, filterfalse, starmap +from typing import ( + IO, + TYPE_CHECKING, + Any, + BinaryIO, + Callable, + Dict, + Iterable, + Iterator, + List, + NewType, + Optional, + Sequence, + Set, + Tuple, + Union, + cast, +) +from zipfile import ZipFile, ZipInfo + +from pipenv.patched.notpip._vendor.distlib.scripts import ScriptMaker +from pipenv.patched.notpip._vendor.distlib.util import get_export_entry +from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name +from pipenv.patched.notpip._vendor.six import ensure_str, ensure_text, reraise + +from pipenv.patched.notpip._internal.exceptions import InstallationError +from pipenv.patched.notpip._internal.locations import get_major_minor_version +from pipenv.patched.notpip._internal.metadata import BaseDistribution, get_wheel_distribution +from pipenv.patched.notpip._internal.models.direct_url import DIRECT_URL_METADATA_NAME, DirectUrl +from pipenv.patched.notpip._internal.models.scheme import SCHEME_KEYS, Scheme +from pipenv.patched.notpip._internal.utils.filesystem import adjacent_tmp_file, replace +from pipenv.patched.notpip._internal.utils.misc import captured_stdout, ensure_dir, hash_file, partition +from pipenv.patched.notpip._internal.utils.unpacking import ( + current_umask, + is_within_directory, + set_extracted_file_to_default_mode_plus_executable, + zip_item_is_executable, +) +from pipenv.patched.notpip._internal.utils.wheel import parse_wheel + +if TYPE_CHECKING: + from typing import Protocol + + class File(Protocol): + src_record_path = None # type: RecordPath + dest_path = None # type: str + changed = None # type: bool + + def save(self): + # type: () -> None + pass + + +logger = logging.getLogger(__name__) + +RecordPath = NewType('RecordPath', str) +InstalledCSVRow = Tuple[RecordPath, str, Union[int, str]] + + +def rehash(path, blocksize=1 << 20): + # type: (str, int) -> Tuple[str, str] + """Return (encoded_digest, length) for path using hashlib.sha256()""" + h, length = hash_file(path, blocksize) + digest = 'sha256=' + urlsafe_b64encode( + h.digest() + ).decode('latin1').rstrip('=') + return (digest, str(length)) + + +def csv_io_kwargs(mode): + # type: (str) -> Dict[str, Any] + """Return keyword arguments to properly open a CSV file + in the given mode. + """ + return {'mode': mode, 'newline': '', 'encoding': 'utf-8'} + + +def fix_script(path): + # type: (str) -> bool + """Replace #!python with #!/path/to/python + Return True if file was changed. + """ + # XXX RECORD hashes will need to be updated + assert os.path.isfile(path) + + with open(path, 'rb') as script: + firstline = script.readline() + if not firstline.startswith(b'#!python'): + return False + exename = sys.executable.encode(sys.getfilesystemencoding()) + firstline = b'#!' + exename + os.linesep.encode("ascii") + rest = script.read() + with open(path, 'wb') as script: + script.write(firstline) + script.write(rest) + return True + + +def wheel_root_is_purelib(metadata): + # type: (Message) -> bool + return metadata.get("Root-Is-Purelib", "").lower() == "true" + + +def get_entrypoints(dist: BaseDistribution) -> Tuple[Dict[str, str], Dict[str, str]]: + console_scripts = {} + gui_scripts = {} + for entry_point in dist.iter_entry_points(): + if entry_point.group == "console_scripts": + console_scripts[entry_point.name] = entry_point.value + elif entry_point.group == "gui_scripts": + gui_scripts[entry_point.name] = entry_point.value + return console_scripts, gui_scripts + + +def message_about_scripts_not_on_PATH(scripts): + # type: (Sequence[str]) -> Optional[str] + """Determine if any scripts are not on PATH and format a warning. + Returns a warning message if one or more scripts are not on PATH, + otherwise None. + """ + if not scripts: + return None + + # Group scripts by the path they were installed in + grouped_by_dir = collections.defaultdict(set) # type: Dict[str, Set[str]] + for destfile in scripts: + parent_dir = os.path.dirname(destfile) + script_name = os.path.basename(destfile) + grouped_by_dir[parent_dir].add(script_name) + + # We don't want to warn for directories that are on PATH. + not_warn_dirs = [ + os.path.normcase(i).rstrip(os.sep) for i in + os.environ.get("PATH", "").split(os.pathsep) + ] + # If an executable sits with sys.executable, we don't warn for it. + # This covers the case of venv invocations without activating the venv. + not_warn_dirs.append(os.path.normcase(os.path.dirname(sys.executable))) + warn_for = { + parent_dir: scripts for parent_dir, scripts in grouped_by_dir.items() + if os.path.normcase(parent_dir) not in not_warn_dirs + } # type: Dict[str, Set[str]] + if not warn_for: + return None + + # Format a message + msg_lines = [] + for parent_dir, dir_scripts in warn_for.items(): + sorted_scripts = sorted(dir_scripts) # type: List[str] + if len(sorted_scripts) == 1: + start_text = "script {} is".format(sorted_scripts[0]) + else: + start_text = "scripts {} are".format( + ", ".join(sorted_scripts[:-1]) + " and " + sorted_scripts[-1] + ) + + msg_lines.append( + "The {} installed in '{}' which is not on PATH." + .format(start_text, parent_dir) + ) + + last_line_fmt = ( + "Consider adding {} to PATH or, if you prefer " + "to suppress this warning, use --no-warn-script-location." + ) + if len(msg_lines) == 1: + msg_lines.append(last_line_fmt.format("this directory")) + else: + msg_lines.append(last_line_fmt.format("these directories")) + + # Add a note if any directory starts with ~ + warn_for_tilde = any( + i[0] == "~" for i in os.environ.get("PATH", "").split(os.pathsep) if i + ) + if warn_for_tilde: + tilde_warning_msg = ( + "NOTE: The current PATH contains path(s) starting with `~`, " + "which may not be expanded by all applications." + ) + msg_lines.append(tilde_warning_msg) + + # Returns the formatted multiline message + return "\n".join(msg_lines) + + +def _normalized_outrows(outrows): + # type: (Iterable[InstalledCSVRow]) -> List[Tuple[str, str, str]] + """Normalize the given rows of a RECORD file. + + Items in each row are converted into str. Rows are then sorted to make + the value more predictable for tests. + + Each row is a 3-tuple (path, hash, size) and corresponds to a record of + a RECORD file (see PEP 376 and PEP 427 for details). For the rows + passed to this function, the size can be an integer as an int or string, + or the empty string. + """ + # Normally, there should only be one row per path, in which case the + # second and third elements don't come into play when sorting. + # However, in cases in the wild where a path might happen to occur twice, + # we don't want the sort operation to trigger an error (but still want + # determinism). Since the third element can be an int or string, we + # coerce each element to a string to avoid a TypeError in this case. + # For additional background, see-- + # https://github.com/pypa/pip/issues/5868 + return sorted( + (ensure_str(record_path, encoding='utf-8'), hash_, str(size)) + for record_path, hash_, size in outrows + ) + + +def _record_to_fs_path(record_path): + # type: (RecordPath) -> str + return record_path + + +def _fs_to_record_path(path, relative_to=None): + # type: (str, Optional[str]) -> RecordPath + if relative_to is not None: + # On Windows, do not handle relative paths if they belong to different + # logical disks + if os.path.splitdrive(path)[0].lower() == \ + os.path.splitdrive(relative_to)[0].lower(): + path = os.path.relpath(path, relative_to) + path = path.replace(os.path.sep, '/') + return cast('RecordPath', path) + + +def _parse_record_path(record_column): + # type: (str) -> RecordPath + p = ensure_text(record_column, encoding='utf-8') + return cast('RecordPath', p) + + +def get_csv_rows_for_installed( + old_csv_rows, # type: List[List[str]] + installed, # type: Dict[RecordPath, RecordPath] + changed, # type: Set[RecordPath] + generated, # type: List[str] + lib_dir, # type: str +): + # type: (...) -> List[InstalledCSVRow] + """ + :param installed: A map from archive RECORD path to installation RECORD + path. + """ + installed_rows = [] # type: List[InstalledCSVRow] + for row in old_csv_rows: + if len(row) > 3: + logger.warning('RECORD line has more than three elements: %s', row) + old_record_path = _parse_record_path(row[0]) + new_record_path = installed.pop(old_record_path, old_record_path) + if new_record_path in changed: + digest, length = rehash(_record_to_fs_path(new_record_path)) + else: + digest = row[1] if len(row) > 1 else '' + length = row[2] if len(row) > 2 else '' + installed_rows.append((new_record_path, digest, length)) + for f in generated: + path = _fs_to_record_path(f, lib_dir) + digest, length = rehash(f) + installed_rows.append((path, digest, length)) + for installed_record_path in installed.values(): + installed_rows.append((installed_record_path, '', '')) + return installed_rows + + +def get_console_script_specs(console): + # type: (Dict[str, str]) -> List[str] + """ + Given the mapping from entrypoint name to callable, return the relevant + console script specs. + """ + # Don't mutate caller's version + console = console.copy() + + scripts_to_generate = [] + + # Special case pip and setuptools to generate versioned wrappers + # + # The issue is that some projects (specifically, pip and setuptools) use + # code in setup.py to create "versioned" entry points - pip2.7 on Python + # 2.7, pip3.3 on Python 3.3, etc. But these entry points are baked into + # the wheel metadata at build time, and so if the wheel is installed with + # a *different* version of Python the entry points will be wrong. The + # correct fix for this is to enhance the metadata to be able to describe + # such versioned entry points, but that won't happen till Metadata 2.0 is + # available. + # In the meantime, projects using versioned entry points will either have + # incorrect versioned entry points, or they will not be able to distribute + # "universal" wheels (i.e., they will need a wheel per Python version). + # + # Because setuptools and pip are bundled with _ensurepip and virtualenv, + # we need to use universal wheels. So, as a stopgap until Metadata 2.0, we + # override the versioned entry points in the wheel and generate the + # correct ones. This code is purely a short-term measure until Metadata 2.0 + # is available. + # + # To add the level of hack in this section of code, in order to support + # ensurepip this code will look for an ``ENSUREPIP_OPTIONS`` environment + # variable which will control which version scripts get installed. + # + # ENSUREPIP_OPTIONS=altinstall + # - Only pipX.Y and easy_install-X.Y will be generated and installed + # ENSUREPIP_OPTIONS=install + # - pipX.Y, pipX, easy_install-X.Y will be generated and installed. Note + # that this option is technically if ENSUREPIP_OPTIONS is set and is + # not altinstall + # DEFAULT + # - The default behavior is to install pip, pipX, pipX.Y, easy_install + # and easy_install-X.Y. + pip_script = console.pop('pip', None) + if pip_script: + if "ENSUREPIP_OPTIONS" not in os.environ: + scripts_to_generate.append('pip = ' + pip_script) + + if os.environ.get("ENSUREPIP_OPTIONS", "") != "altinstall": + scripts_to_generate.append( + 'pip{} = {}'.format(sys.version_info[0], pip_script) + ) + + scripts_to_generate.append( + f'pip{get_major_minor_version()} = {pip_script}' + ) + # Delete any other versioned pip entry points + pip_ep = [k for k in console if re.match(r'pip(\d(\.\d)?)?$', k)] + for k in pip_ep: + del console[k] + easy_install_script = console.pop('easy_install', None) + if easy_install_script: + if "ENSUREPIP_OPTIONS" not in os.environ: + scripts_to_generate.append( + 'easy_install = ' + easy_install_script + ) + + scripts_to_generate.append( + 'easy_install-{} = {}'.format( + get_major_minor_version(), easy_install_script + ) + ) + # Delete any other versioned easy_install entry points + easy_install_ep = [ + k for k in console if re.match(r'easy_install(-\d\.\d)?$', k) + ] + for k in easy_install_ep: + del console[k] + + # Generate the console entry points specified in the wheel + scripts_to_generate.extend(starmap('{} = {}'.format, console.items())) + + return scripts_to_generate + + +class ZipBackedFile: + def __init__(self, src_record_path, dest_path, zip_file): + # type: (RecordPath, str, ZipFile) -> None + self.src_record_path = src_record_path + self.dest_path = dest_path + self._zip_file = zip_file + self.changed = False + + def _getinfo(self): + # type: () -> ZipInfo + return self._zip_file.getinfo(self.src_record_path) + + def save(self): + # type: () -> None + # directory creation is lazy and after file filtering + # to ensure we don't install empty dirs; empty dirs can't be + # uninstalled. + parent_dir = os.path.dirname(self.dest_path) + ensure_dir(parent_dir) + + # When we open the output file below, any existing file is truncated + # before we start writing the new contents. This is fine in most + # cases, but can cause a segfault if pip has loaded a shared + # object (e.g. from pyopenssl through its vendored urllib3) + # Since the shared object is mmap'd an attempt to call a + # symbol in it will then cause a segfault. Unlinking the file + # allows writing of new contents while allowing the process to + # continue to use the old copy. + if os.path.exists(self.dest_path): + os.unlink(self.dest_path) + + zipinfo = self._getinfo() + + with self._zip_file.open(zipinfo) as f: + with open(self.dest_path, "wb") as dest: + shutil.copyfileobj(f, dest) + + if zip_item_is_executable(zipinfo): + set_extracted_file_to_default_mode_plus_executable(self.dest_path) + + +class ScriptFile: + def __init__(self, file): + # type: (File) -> None + self._file = file + self.src_record_path = self._file.src_record_path + self.dest_path = self._file.dest_path + self.changed = False + + def save(self): + # type: () -> None + self._file.save() + self.changed = fix_script(self.dest_path) + + +class MissingCallableSuffix(InstallationError): + def __init__(self, entry_point): + # type: (str) -> None + super().__init__( + "Invalid script entry point: {} - A callable " + "suffix is required. Cf https://packaging.python.org/" + "specifications/entry-points/#use-for-scripts for more " + "information.".format(entry_point) + ) + + +def _raise_for_invalid_entrypoint(specification): + # type: (str) -> None + entry = get_export_entry(specification) + if entry is not None and entry.suffix is None: + raise MissingCallableSuffix(str(entry)) + + +class PipScriptMaker(ScriptMaker): + def make(self, specification, options=None): + # type: (str, Dict[str, Any]) -> List[str] + _raise_for_invalid_entrypoint(specification) + return super().make(specification, options) + + +def _install_wheel( + name, # type: str + wheel_zip, # type: ZipFile + wheel_path, # type: str + scheme, # type: Scheme + pycompile=True, # type: bool + warn_script_location=True, # type: bool + direct_url=None, # type: Optional[DirectUrl] + requested=False, # type: bool +): + # type: (...) -> None + """Install a wheel. + + :param name: Name of the project to install + :param wheel_zip: open ZipFile for wheel being installed + :param scheme: Distutils scheme dictating the install directories + :param req_description: String used in place of the requirement, for + logging + :param pycompile: Whether to byte-compile installed Python files + :param warn_script_location: Whether to check that scripts are installed + into a directory on PATH + :raises UnsupportedWheel: + * when the directory holds an unpacked wheel with incompatible + Wheel-Version + * when the .dist-info dir does not match the wheel + """ + info_dir, metadata = parse_wheel(wheel_zip, name) + + if wheel_root_is_purelib(metadata): + lib_dir = scheme.purelib + else: + lib_dir = scheme.platlib + + # Record details of the files moved + # installed = files copied from the wheel to the destination + # changed = files changed while installing (scripts #! line typically) + # generated = files newly generated during the install (script wrappers) + installed = {} # type: Dict[RecordPath, RecordPath] + changed = set() # type: Set[RecordPath] + generated = [] # type: List[str] + + def record_installed(srcfile, destfile, modified=False): + # type: (RecordPath, str, bool) -> None + """Map archive RECORD paths to installation RECORD paths.""" + newpath = _fs_to_record_path(destfile, lib_dir) + installed[srcfile] = newpath + if modified: + changed.add(_fs_to_record_path(destfile)) + + def all_paths(): + # type: () -> Iterable[RecordPath] + names = wheel_zip.namelist() + # If a flag is set, names may be unicode in Python 2. We convert to + # text explicitly so these are valid for lookup in RECORD. + decoded_names = map(ensure_text, names) + for name in decoded_names: + yield cast("RecordPath", name) + + def is_dir_path(path): + # type: (RecordPath) -> bool + return path.endswith("/") + + def assert_no_path_traversal(dest_dir_path, target_path): + # type: (str, str) -> None + if not is_within_directory(dest_dir_path, target_path): + message = ( + "The wheel {!r} has a file {!r} trying to install" + " outside the target directory {!r}" + ) + raise InstallationError( + message.format(wheel_path, target_path, dest_dir_path) + ) + + def root_scheme_file_maker(zip_file, dest): + # type: (ZipFile, str) -> Callable[[RecordPath], File] + def make_root_scheme_file(record_path): + # type: (RecordPath) -> File + normed_path = os.path.normpath(record_path) + dest_path = os.path.join(dest, normed_path) + assert_no_path_traversal(dest, dest_path) + return ZipBackedFile(record_path, dest_path, zip_file) + + return make_root_scheme_file + + def data_scheme_file_maker(zip_file, scheme): + # type: (ZipFile, Scheme) -> Callable[[RecordPath], File] + scheme_paths = {} + for key in SCHEME_KEYS: + encoded_key = ensure_text(key) + scheme_paths[encoded_key] = ensure_text( + getattr(scheme, key), encoding=sys.getfilesystemencoding() + ) + + def make_data_scheme_file(record_path): + # type: (RecordPath) -> File + normed_path = os.path.normpath(record_path) + try: + _, scheme_key, dest_subpath = normed_path.split(os.path.sep, 2) + except ValueError: + message = ( + "Unexpected file in {}: {!r}. .data directory contents" + " should be named like: '/'." + ).format(wheel_path, record_path) + raise InstallationError(message) + + try: + scheme_path = scheme_paths[scheme_key] + except KeyError: + valid_scheme_keys = ", ".join(sorted(scheme_paths)) + message = ( + "Unknown scheme key used in {}: {} (for file {!r}). .data" + " directory contents should be in subdirectories named" + " with a valid scheme key ({})" + ).format( + wheel_path, scheme_key, record_path, valid_scheme_keys + ) + raise InstallationError(message) + + dest_path = os.path.join(scheme_path, dest_subpath) + assert_no_path_traversal(scheme_path, dest_path) + return ZipBackedFile(record_path, dest_path, zip_file) + + return make_data_scheme_file + + def is_data_scheme_path(path): + # type: (RecordPath) -> bool + return path.split("/", 1)[0].endswith(".data") + + paths = all_paths() + file_paths = filterfalse(is_dir_path, paths) + root_scheme_paths, data_scheme_paths = partition( + is_data_scheme_path, file_paths + ) + + make_root_scheme_file = root_scheme_file_maker( + wheel_zip, + ensure_text(lib_dir, encoding=sys.getfilesystemencoding()), + ) + files = map(make_root_scheme_file, root_scheme_paths) + + def is_script_scheme_path(path): + # type: (RecordPath) -> bool + parts = path.split("/", 2) + return ( + len(parts) > 2 and + parts[0].endswith(".data") and + parts[1] == "scripts" + ) + + other_scheme_paths, script_scheme_paths = partition( + is_script_scheme_path, data_scheme_paths + ) + + make_data_scheme_file = data_scheme_file_maker(wheel_zip, scheme) + other_scheme_files = map(make_data_scheme_file, other_scheme_paths) + files = chain(files, other_scheme_files) + + # Get the defined entry points + distribution = get_wheel_distribution(wheel_path, canonicalize_name(name)) + console, gui = get_entrypoints(distribution) + + def is_entrypoint_wrapper(file): + # type: (File) -> bool + # EP, EP.exe and EP-script.py are scripts generated for + # entry point EP by setuptools + path = file.dest_path + name = os.path.basename(path) + if name.lower().endswith('.exe'): + matchname = name[:-4] + elif name.lower().endswith('-script.py'): + matchname = name[:-10] + elif name.lower().endswith(".pya"): + matchname = name[:-4] + else: + matchname = name + # Ignore setuptools-generated scripts + return (matchname in console or matchname in gui) + + script_scheme_files = map(make_data_scheme_file, script_scheme_paths) + script_scheme_files = filterfalse( + is_entrypoint_wrapper, script_scheme_files + ) + script_scheme_files = map(ScriptFile, script_scheme_files) + files = chain(files, script_scheme_files) + + for file in files: + file.save() + record_installed(file.src_record_path, file.dest_path, file.changed) + + def pyc_source_file_paths(): + # type: () -> Iterator[str] + # We de-duplicate installation paths, since there can be overlap (e.g. + # file in .data maps to same location as file in wheel root). + # Sorting installation paths makes it easier to reproduce and debug + # issues related to permissions on existing files. + for installed_path in sorted(set(installed.values())): + full_installed_path = os.path.join(lib_dir, installed_path) + if not os.path.isfile(full_installed_path): + continue + if not full_installed_path.endswith('.py'): + continue + yield full_installed_path + + def pyc_output_path(path): + # type: (str) -> str + """Return the path the pyc file would have been written to. + """ + return importlib.util.cache_from_source(path) + + # Compile all of the pyc files for the installed files + if pycompile: + with captured_stdout() as stdout: + with warnings.catch_warnings(): + warnings.filterwarnings('ignore') + for path in pyc_source_file_paths(): + # Python 2's `compileall.compile_file` requires a str in + # error cases, so we must convert to the native type. + path_arg = ensure_str( + path, encoding=sys.getfilesystemencoding() + ) + success = compileall.compile_file( + path_arg, force=True, quiet=True + ) + if success: + pyc_path = pyc_output_path(path) + assert os.path.exists(pyc_path) + pyc_record_path = cast( + "RecordPath", pyc_path.replace(os.path.sep, "/") + ) + record_installed(pyc_record_path, pyc_path) + logger.debug(stdout.getvalue()) + + maker = PipScriptMaker(None, scheme.scripts) + + # Ensure old scripts are overwritten. + # See https://github.com/pypa/pip/issues/1800 + maker.clobber = True + + # Ensure we don't generate any variants for scripts because this is almost + # never what somebody wants. + # See https://bitbucket.org/pypa/distlib/issue/35/ + maker.variants = {''} + + # This is required because otherwise distlib creates scripts that are not + # executable. + # See https://bitbucket.org/pypa/distlib/issue/32/ + maker.set_mode = True + + # Generate the console and GUI entry points specified in the wheel + scripts_to_generate = get_console_script_specs(console) + + gui_scripts_to_generate = list(starmap('{} = {}'.format, gui.items())) + + generated_console_scripts = maker.make_multiple(scripts_to_generate) + generated.extend(generated_console_scripts) + + generated.extend( + maker.make_multiple(gui_scripts_to_generate, {'gui': True}) + ) + + if warn_script_location: + msg = message_about_scripts_not_on_PATH(generated_console_scripts) + if msg is not None: + logger.warning(msg) + + generated_file_mode = 0o666 & ~current_umask() + + @contextlib.contextmanager + def _generate_file(path, **kwargs): + # type: (str, **Any) -> Iterator[BinaryIO] + with adjacent_tmp_file(path, **kwargs) as f: + yield f + os.chmod(f.name, generated_file_mode) + replace(f.name, path) + + dest_info_dir = os.path.join(lib_dir, info_dir) + + # Record pip as the installer + installer_path = os.path.join(dest_info_dir, 'INSTALLER') + with _generate_file(installer_path) as installer_file: + installer_file.write(b'pip\n') + generated.append(installer_path) + + # Record the PEP 610 direct URL reference + if direct_url is not None: + direct_url_path = os.path.join(dest_info_dir, DIRECT_URL_METADATA_NAME) + with _generate_file(direct_url_path) as direct_url_file: + direct_url_file.write(direct_url.to_json().encode("utf-8")) + generated.append(direct_url_path) + + # Record the REQUESTED file + if requested: + requested_path = os.path.join(dest_info_dir, 'REQUESTED') + with open(requested_path, "wb"): + pass + generated.append(requested_path) + + record_text = distribution.read_text('RECORD') + record_rows = list(csv.reader(record_text.splitlines())) + + rows = get_csv_rows_for_installed( + record_rows, + installed=installed, + changed=changed, + generated=generated, + lib_dir=lib_dir) + + # Record details of all files installed + record_path = os.path.join(dest_info_dir, 'RECORD') + + with _generate_file(record_path, **csv_io_kwargs('w')) as record_file: + # The type mypy infers for record_file is different for Python 3 + # (typing.IO[Any]) and Python 2 (typing.BinaryIO). We explicitly + # cast to typing.IO[str] as a workaround. + writer = csv.writer(cast('IO[str]', record_file)) + writer.writerows(_normalized_outrows(rows)) + + +@contextlib.contextmanager +def req_error_context(req_description): + # type: (str) -> Iterator[None] + try: + yield + except InstallationError as e: + message = "For req: {}. {}".format(req_description, e.args[0]) + reraise( + InstallationError, InstallationError(message), sys.exc_info()[2] + ) + + +def install_wheel( + name, # type: str + wheel_path, # type: str + scheme, # type: Scheme + req_description, # type: str + pycompile=True, # type: bool + warn_script_location=True, # type: bool + direct_url=None, # type: Optional[DirectUrl] + requested=False, # type: bool +): + # type: (...) -> None + with ZipFile(wheel_path, allowZip64=True) as z: + with req_error_context(req_description): + _install_wheel( + name=name, + wheel_zip=z, + wheel_path=wheel_path, + scheme=scheme, + pycompile=pycompile, + warn_script_location=warn_script_location, + direct_url=direct_url, + requested=requested, + ) diff --git a/pipenv/patched/notpip/_internal/operations/prepare.py b/pipenv/patched/notpip/_internal/operations/prepare.py index 018fca97..0c256d6b 100644 --- a/pipenv/patched/notpip/_internal/operations/prepare.py +++ b/pipenv/patched/notpip/_internal/operations/prepare.py @@ -1,176 +1,283 @@ """Prepares a distribution for installation """ +# The following comment should be removed at some point in the future. +# mypy: strict-optional=False + import logging +import mimetypes import os +import shutil +from typing import Dict, Iterable, List, Optional, Tuple -from pipenv.patched.notpip._vendor import pkg_resources, requests +from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name +from pipenv.patched.notpip._vendor.pkg_resources import Distribution -from pipenv.patched.notpip._internal.build_env import BuildEnvironment -from pipenv.patched.notpip._internal.download import ( - is_dir_url, is_file_url, is_vcs_url, unpack_url, url_to_path, -) +from pipenv.patched.notpip._internal.distributions import make_distribution_for_install_requirement +from pipenv.patched.notpip._internal.distributions.installed import InstalledDistribution from pipenv.patched.notpip._internal.exceptions import ( - DirectoryUrlHashUnsupported, HashUnpinned, InstallationError, - PreviousBuildDirError, VcsHashUnsupported, + DirectoryUrlHashUnsupported, + HashMismatch, + HashUnpinned, + InstallationError, + NetworkConnectionError, + PreviousBuildDirError, + VcsHashUnsupported, ) -from pipenv.patched.notpip._internal.utils.compat import expanduser -from pipenv.patched.notpip._internal.utils.hashes import MissingHashes +from pipenv.patched.notpip._internal.index.package_finder import PackageFinder +from pipenv.patched.notpip._internal.models.link import Link +from pipenv.patched.notpip._internal.models.wheel import Wheel +from pipenv.patched.notpip._internal.network.download import BatchDownloader, Downloader +from pipenv.patched.notpip._internal.network.lazy_wheel import ( + HTTPRangeRequestUnsupported, + dist_from_wheel_url, +) +from pipenv.patched.notpip._internal.network.session import PipSession +from pipenv.patched.notpip._internal.req.req_install import InstallRequirement +from pipenv.patched.notpip._internal.req.req_tracker import RequirementTracker +from pipenv.patched.notpip._internal.utils.deprecation import deprecated +from pipenv.patched.notpip._internal.utils.filesystem import copy2_fixed +from pipenv.patched.notpip._internal.utils.hashes import Hashes, MissingHashes from pipenv.patched.notpip._internal.utils.logging import indent_log -from pipenv.patched.notpip._internal.utils.misc import display_path, normalize_path, rmtree -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING +from pipenv.patched.notpip._internal.utils.misc import display_path, hide_url, is_installable_dir, rmtree +from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory +from pipenv.patched.notpip._internal.utils.unpacking import unpack_file from pipenv.patched.notpip._internal.vcs import vcs -if MYPY_CHECK_RUNNING: - from typing import Any, Optional # noqa: F401 - from pipenv.patched.notpip._internal.req.req_install import InstallRequirement # noqa: F401 - from pipenv.patched.notpip._internal.index import PackageFinder # noqa: F401 - from pipenv.patched.notpip._internal.download import PipSession # noqa: F401 - from pipenv.patched.notpip._internal.req.req_tracker import RequirementTracker # noqa: F401 - logger = logging.getLogger(__name__) -def make_abstract_dist(req): - # type: (InstallRequirement) -> DistAbstraction - """Factory to make an abstract dist object. +def _get_prepared_distribution( + req, # type: InstallRequirement + req_tracker, # type: RequirementTracker + finder, # type: PackageFinder + build_isolation, # type: bool +): + # type: (...) -> Distribution + """Prepare a distribution for installation.""" + abstract_dist = make_distribution_for_install_requirement(req) + with req_tracker.track(req): + abstract_dist.prepare_distribution_metadata(finder, build_isolation) + return abstract_dist.get_pkg_resources_distribution() - Preconditions: Either an editable req with a source_dir, or satisfied_by or - a wheel link, or a non-editable req with a source_dir. - :return: A concrete DistAbstraction. - """ - if req.editable: - return IsSDist(req) - elif req.link and req.link.is_wheel: - return IsWheel(req) +def unpack_vcs_link(link, location): + # type: (Link, str) -> None + vcs_backend = vcs.get_backend_for_scheme(link.scheme) + assert vcs_backend is not None + vcs_backend.unpack(location, url=hide_url(link.url)) + + +class File: + + def __init__(self, path, content_type): + # type: (str, Optional[str]) -> None + self.path = path + if content_type is None: + self.content_type = mimetypes.guess_type(path)[0] + else: + self.content_type = content_type + + +def get_http_url( + link, # type: Link + download, # type: Downloader + download_dir=None, # type: Optional[str] + hashes=None, # type: Optional[Hashes] +): + # type: (...) -> File + temp_dir = TempDirectory(kind="unpack", globally_managed=True) + # If a download dir is specified, is the file already downloaded there? + already_downloaded_path = None + if download_dir: + already_downloaded_path = _check_download_dir( + link, download_dir, hashes + ) + + if already_downloaded_path: + from_path = already_downloaded_path + content_type = None else: - return IsSDist(req) + # let's download to a tmp dir + from_path, content_type = download(link, temp_dir.path) + if hashes: + hashes.check_against_path(from_path) + + return File(from_path, content_type) -class DistAbstraction(object): - """Abstracts out the wheel vs non-wheel Resolver.resolve() logic. - - The requirements for anything installable are as follows: - - we must be able to determine the requirement name - (or we can't correctly handle the non-upgrade case). - - we must be able to generate a list of run-time dependencies - without installing any additional packages (or we would - have to either burn time by doing temporary isolated installs - or alternatively violate pips 'don't start installing unless - all requirements are available' rule - neither of which are - desirable). - - for packages with setup requirements, we must also be able - to determine their requirements without installing additional - packages (for the same reason as run-time dependencies) - - we must be able to create a Distribution object exposing the - above metadata. +def _copy2_ignoring_special_files(src, dest): + # type: (str, str) -> None + """Copying special files is not supported, but as a convenience to users + we skip errors copying them. This supports tools that may create e.g. + socket files in the project source directory. """ - - def __init__(self, req): - # type: (InstallRequirement) -> None - self.req = req # type: InstallRequirement - - def dist(self): - # type: () -> Any - """Return a setuptools Dist object.""" - raise NotImplementedError - - def prep_for_dist(self, finder, build_isolation): - # type: (PackageFinder, bool) -> Any - """Ensure that we can get a Dist for this requirement.""" - raise NotImplementedError + try: + copy2_fixed(src, dest) + except shutil.SpecialFileError as e: + # SpecialFileError may be raised due to either the source or + # destination. If the destination was the cause then we would actually + # care, but since the destination directory is deleted prior to + # copy we ignore all of them assuming it is caused by the source. + logger.warning( + "Ignoring special file error '%s' encountered copying %s to %s.", + str(e), + src, + dest, + ) -class IsWheel(DistAbstraction): +def _copy_source_tree(source, target): + # type: (str, str) -> None + target_abspath = os.path.abspath(target) + target_basename = os.path.basename(target_abspath) + target_dirname = os.path.dirname(target_abspath) - def dist(self): - # type: () -> pkg_resources.Distribution - return list(pkg_resources.find_distributions( - self.req.source_dir))[0] + def ignore(d, names): + # type: (str, List[str]) -> List[str] + skipped = [] # type: List[str] + if d == source: + # Pulling in those directories can potentially be very slow, + # exclude the following directories if they appear in the top + # level dir (and only it). + # See discussion at https://github.com/pypa/pip/pull/6770 + skipped += ['.tox', '.nox'] + if os.path.abspath(d) == target_dirname: + # Prevent an infinite recursion if the target is in source. + # This can happen when TMPDIR is set to ${PWD}/... + # and we copy PWD to TMPDIR. + skipped += [target_basename] + return skipped - def prep_for_dist(self, finder, build_isolation): - # type: (PackageFinder, bool) -> Any - # FIXME:https://github.com/pypa/pip/issues/1112 - pass + shutil.copytree( + source, + target, + ignore=ignore, + symlinks=True, + copy_function=_copy2_ignoring_special_files, + ) -class IsSDist(DistAbstraction): +def get_file_url( + link, # type: Link + download_dir=None, # type: Optional[str] + hashes=None # type: Optional[Hashes] +): + # type: (...) -> File + """Get file and optionally check its hash. + """ + # If a download dir is specified, is the file already there and valid? + already_downloaded_path = None + if download_dir: + already_downloaded_path = _check_download_dir( + link, download_dir, hashes + ) - def dist(self): - return self.req.get_dist() + if already_downloaded_path: + from_path = already_downloaded_path + else: + from_path = link.file_path - def prep_for_dist(self, finder, build_isolation): - # type: (PackageFinder, bool) -> None - # Prepare for building. We need to: - # 1. Load pyproject.toml (if it exists) - # 2. Set up the build environment + # If --require-hashes is off, `hashes` is either empty, the + # link's embedded hash, or MissingHashes; it is required to + # match. If --require-hashes is on, we are satisfied by any + # hash in `hashes` matching: a URL-based or an option-based + # one; no internet-sourced hash will be in `hashes`. + if hashes: + hashes.check_against_path(from_path) + return File(from_path, None) - self.req.load_pyproject_toml() - should_isolate = self.req.use_pep517 and build_isolation - def _raise_conflicts(conflicting_with, conflicting_reqs): - raise InstallationError( - "Some build dependencies for %s conflict with %s: %s." % ( - self.req, conflicting_with, ', '.join( - '%s is incompatible with %s' % (installed, wanted) - for installed, wanted in sorted(conflicting)))) +def unpack_url( + link, # type: Link + location, # type: str + download, # type: Downloader + download_dir=None, # type: Optional[str] + hashes=None, # type: Optional[Hashes] +): + # type: (...) -> Optional[File] + """Unpack link into location, downloading if required. - if should_isolate: - # Isolate in a BuildEnvironment and install the build-time - # requirements. - self.req.build_env = BuildEnvironment() - self.req.build_env.install_requirements( - finder, self.req.pyproject_requires, 'overlay', - "Installing build dependencies" + :param hashes: A Hashes object, one of whose embedded hashes must match, + or HashMismatch will be raised. If the Hashes is empty, no matches are + required, and unhashable types of requirements (like VCS ones, which + would ordinarily raise HashUnsupported) are allowed. + """ + # non-editable vcs urls + if link.is_vcs: + unpack_vcs_link(link, location) + return None + + # Once out-of-tree-builds are no longer supported, could potentially + # replace the below condition with `assert not link.is_existing_dir` + # - unpack_url does not need to be called for in-tree-builds. + # + # As further cleanup, _copy_source_tree and accompanying tests can + # be removed. + if link.is_existing_dir(): + deprecated( + "A future pip version will change local packages to be built " + "in-place without first copying to a temporary directory. " + "We recommend you use --use-feature=in-tree-build to test " + "your packages with this new behavior before it becomes the " + "default.\n", + replacement=None, + gone_in="21.3", + issue=7555 + ) + if os.path.isdir(location): + rmtree(location) + _copy_source_tree(link.file_path, location) + return None + + # file urls + if link.is_file: + file = get_file_url(link, download_dir, hashes=hashes) + + # http urls + else: + file = get_http_url( + link, + download, + download_dir, + hashes=hashes, + ) + + # unpack the archive to the build dir location. even when only downloading + # archives, they have to be unpacked to parse dependencies, except wheels + if not link.is_wheel: + unpack_file(file.path, location, file.content_type) + + return file + + +def _check_download_dir(link, download_dir, hashes): + # type: (Link, str, Optional[Hashes]) -> Optional[str] + """ Check download_dir for previously downloaded file with correct hash + If a correct file is found return its path else None + """ + download_path = os.path.join(download_dir, link.filename) + + if not os.path.exists(download_path): + return None + + # If already downloaded, does its hash match? + logger.info('File was already downloaded %s', download_path) + if hashes: + try: + hashes.check_against_path(download_path) + except HashMismatch: + logger.warning( + 'Previously-downloaded file %s has bad hash. ' + 'Re-downloading.', + download_path ) - conflicting, missing = self.req.build_env.check_requirements( - self.req.requirements_to_check - ) - if conflicting: - _raise_conflicts("PEP 517/518 supported requirements", - conflicting) - if missing: - logger.warning( - "Missing build requirements in pyproject.toml for %s.", - self.req, - ) - logger.warning( - "The project does not specify a build backend, and " - "pip cannot fall back to setuptools without %s.", - " and ".join(map(repr, sorted(missing))) - ) - # Install any extra build dependencies that the backend requests. - # This must be done in a second pass, as the pyproject.toml - # dependencies must be installed before we can call the backend. - with self.req.build_env: - # We need to have the env active when calling the hook. - self.req.spin_message = "Getting requirements to build wheel" - reqs = self.req.pep517_backend.get_requires_for_build_wheel() - conflicting, missing = self.req.build_env.check_requirements(reqs) - if conflicting: - _raise_conflicts("the backend dependencies", conflicting) - self.req.build_env.install_requirements( - finder, missing, 'normal', - "Installing backend dependencies" - ) - - self.req.prepare_metadata() - self.req.assert_source_matches_version() + os.unlink(download_path) + return None + return download_path -class Installed(DistAbstraction): - - def dist(self): - # type: () -> pkg_resources.Distribution - return self.req.satisfied_by - - def prep_for_dist(self, finder, build_isolation): - # type: (PackageFinder, bool) -> Any - pass - - -class RequirementPreparer(object): +class RequirementPreparer: """Prepares a Requirement """ @@ -179,181 +286,323 @@ class RequirementPreparer(object): build_dir, # type: str download_dir, # type: Optional[str] src_dir, # type: str - wheel_download_dir, # type: Optional[str] - progress_bar, # type: str build_isolation, # type: bool - req_tracker # type: RequirementTracker + req_tracker, # type: RequirementTracker + session, # type: PipSession + progress_bar, # type: str + finder, # type: PackageFinder + require_hashes, # type: bool + use_user_site, # type: bool + lazy_wheel, # type: bool + in_tree_build, # type: bool ): # type: (...) -> None - super(RequirementPreparer, self).__init__() + super().__init__() self.src_dir = src_dir self.build_dir = build_dir self.req_tracker = req_tracker + self._session = session + self._download = Downloader(session, progress_bar) + self._batch_download = BatchDownloader(session, progress_bar) + self.finder = finder - # Where still packed archives should be written to. If None, they are + # Where still-packed archives should be written to. If None, they are # not saved, and are deleted immediately after unpacking. self.download_dir = download_dir - # Where still-packed .whl files should be written to. If None, they are - # written to the download_dir parameter. Separate to download_dir to - # permit only keeping wheel archives for pip wheel. - if wheel_download_dir: - wheel_download_dir = normalize_path(wheel_download_dir) - self.wheel_download_dir = wheel_download_dir - - # NOTE - # download_dir and wheel_download_dir overlap semantically and may - # be combined if we're willing to have non-wheel archives present in - # the wheelhouse output by 'pip wheel'. - - self.progress_bar = progress_bar - # Is build isolation allowed? self.build_isolation = build_isolation - @property - def _download_should_save(self): - # type: () -> bool - # TODO: Modify to reduce indentation needed - if self.download_dir: - self.download_dir = expanduser(self.download_dir) - if os.path.exists(self.download_dir): - return True - else: - logger.critical('Could not find download directory') - raise InstallationError( - "Could not find or access download directory '%s'" - % display_path(self.download_dir)) - return False + # Should hash-checking be required? + self.require_hashes = require_hashes - def prepare_linked_requirement( - self, - req, # type: InstallRequirement - session, # type: PipSession - finder, # type: PackageFinder - upgrade_allowed, # type: bool - require_hashes # type: bool - ): - # type: (...) -> DistAbstraction - """Prepare a requirement that would be obtained from req.link - """ - # TODO: Breakup into smaller functions - if req.link and req.link.scheme == 'file': - path = url_to_path(req.link.url) - logger.info('Processing %s', display_path(path)) + # Should install in user site-packages? + self.use_user_site = use_user_site + + # Should wheels be downloaded lazily? + self.use_lazy_wheel = lazy_wheel + + # Should in-tree builds be used for local paths? + self.in_tree_build = in_tree_build + + # Memoized downloaded files, as mapping of url: (path, mime type) + self._downloaded = {} # type: Dict[str, Tuple[str, str]] + + # Previous "header" printed for a link-based InstallRequirement + self._previous_requirement_header = ("", "") + + def _log_preparing_link(self, req): + # type: (InstallRequirement) -> None + """Provide context for the requirement being prepared.""" + if req.link.is_file and not req.original_link_is_in_wheel_cache: + message = "Processing %s" + information = str(display_path(req.link.file_path)) else: - logger.info('Collecting %s', req) + message = "Collecting %s" + information = str(req.req or req) - with indent_log(): - # @@ if filesystem packages are not marked - # editable in a req, a non deterministic error - # occurs when the script attempts to unpack the - # build directory - req.ensure_has_source_dir(self.build_dir) - # If a checkout exists, it's unwise to keep going. version - # inconsistencies are logged later, but do not fail the - # installation. - # FIXME: this won't upgrade when there's an existing - # package unpacked in `req.source_dir` - # package unpacked in `req.source_dir` - if os.path.exists(os.path.join(req.source_dir, 'setup.py')): - rmtree(req.source_dir) - req.populate_link(finder, upgrade_allowed, require_hashes) + if (message, information) != self._previous_requirement_header: + self._previous_requirement_header = (message, information) + logger.info(message, information) - # We can't hit this spot and have populate_link return None. - # req.satisfied_by is None here (because we're - # guarded) and upgrade has no impact except when satisfied_by - # is not None. - # Then inside find_requirement existing_applicable -> False - # If no new versions are found, DistributionNotFound is raised, - # otherwise a result is guaranteed. + if req.original_link_is_in_wheel_cache: + with indent_log(): + logger.info("Using cached %s", req.link.filename) + + def _ensure_link_req_src_dir(self, req, parallel_builds): + # type: (InstallRequirement, bool) -> None + """Ensure source_dir of a linked InstallRequirement.""" + # Since source_dir is only set for editable requirements. + if req.link.is_wheel: + # We don't need to unpack wheels, so no need for a source + # directory. + return + assert req.source_dir is None + if req.link.is_existing_dir() and self.in_tree_build: + # build local directories in-tree + req.source_dir = req.link.file_path + return + + # We always delete unpacked sdists after pip runs. + req.ensure_has_source_dir( + self.build_dir, + autodelete=True, + parallel_builds=parallel_builds, + ) + + # If a checkout exists, it's unwise to keep going. version + # inconsistencies are logged later, but do not fail the + # installation. + # FIXME: this won't upgrade when there's an existing + # package unpacked in `req.source_dir` + if is_installable_dir(req.source_dir): + raise PreviousBuildDirError( + "pip can't proceed with requirements '{}' due to a" + "pre-existing build directory ({}). This is likely " + "due to a previous installation that failed . pip is " + "being responsible and not assuming it can delete this. " + "Please delete it and try again.".format(req, req.source_dir) + ) + + def _get_linked_req_hashes(self, req): + # type: (InstallRequirement) -> Hashes + # By the time this is called, the requirement's link should have + # been checked so we can tell what kind of requirements req is + # and raise some more informative errors than otherwise. + # (For example, we can raise VcsHashUnsupported for a VCS URL + # rather than HashMissing.) + if not self.require_hashes: + return req.hashes(trust_internet=True) + + # We could check these first 2 conditions inside unpack_url + # and save repetition of conditions, but then we would + # report less-useful error messages for unhashable + # requirements, complaining that there's no hash provided. + if req.link.is_vcs: + raise VcsHashUnsupported() + if req.link.is_existing_dir(): + raise DirectoryUrlHashUnsupported() + + # Unpinned packages are asking for trouble when a new version + # is uploaded. This isn't a security check, but it saves users + # a surprising hash mismatch in the future. + # file:/// URLs aren't pinnable, so don't complain about them + # not being pinned. + if req.original_link is None and not req.is_pinned: + raise HashUnpinned() + + # If known-good hashes are missing for this requirement, + # shim it with a facade object that will provoke hash + # computation and then raise a HashMissing exception + # showing the user what the hash should be. + return req.hashes(trust_internet=False) or MissingHashes() + + def _fetch_metadata_using_lazy_wheel(self, link): + # type: (Link) -> Optional[Distribution] + """Fetch metadata using lazy wheel, if possible.""" + if not self.use_lazy_wheel: + return None + if self.require_hashes: + logger.debug('Lazy wheel is not used as hash checking is required') + return None + if link.is_file or not link.is_wheel: + logger.debug( + 'Lazy wheel is not used as ' + '%r does not points to a remote wheel', + link, + ) + return None + + wheel = Wheel(link.filename) + name = canonicalize_name(wheel.name) + logger.info( + 'Obtaining dependency information from %s %s', + name, wheel.version, + ) + url = link.url.split('#', 1)[0] + try: + return dist_from_wheel_url(name, url, self._session) + except HTTPRangeRequestUnsupported: + logger.debug('%s does not support range requests', url) + return None + + def _complete_partial_requirements( + self, + partially_downloaded_reqs, # type: Iterable[InstallRequirement] + parallel_builds=False, # type: bool + ): + # type: (...) -> None + """Download any requirements which were only fetched by metadata.""" + # Download to a temporary directory. These will be copied over as + # needed for downstream 'download', 'wheel', and 'install' commands. + temp_dir = TempDirectory(kind="unpack", globally_managed=True).path + + # Map each link to the requirement that owns it. This allows us to set + # `req.local_file_path` on the appropriate requirement after passing + # all the links at once into BatchDownloader. + links_to_fully_download = {} # type: Dict[Link, InstallRequirement] + for req in partially_downloaded_reqs: assert req.link - link = req.link + links_to_fully_download[req.link] = req - # Now that we have the real link, we can tell what kind of - # requirements we have and raise some more informative errors - # than otherwise. (For example, we can raise VcsHashUnsupported - # for a VCS URL rather than HashMissing.) - if require_hashes: - # We could check these first 2 conditions inside - # unpack_url and save repetition of conditions, but then - # we would report less-useful error messages for - # unhashable requirements, complaining that there's no - # hash provided. - if is_vcs_url(link): - raise VcsHashUnsupported() - elif is_file_url(link) and is_dir_url(link): - raise DirectoryUrlHashUnsupported() - if not req.original_link and not req.is_pinned: - # Unpinned packages are asking for trouble when a new - # version is uploaded. This isn't a security check, but - # it saves users a surprising hash mismatch in the - # future. - # - # file:/// URLs aren't pinnable, so don't complain - # about them not being pinned. - raise HashUnpinned() + batch_download = self._batch_download( + links_to_fully_download.keys(), + temp_dir, + ) + for link, (filepath, _) in batch_download: + logger.debug("Downloading link %s to %s", link, filepath) + req = links_to_fully_download[link] + req.local_file_path = filepath - hashes = req.hashes(trust_internet=not require_hashes) - if require_hashes and not hashes: - # Known-good hashes are missing for this requirement, so - # shim it with a facade object that will provoke hash - # computation and then raise a HashMissing exception - # showing the user what the hash should be. - hashes = MissingHashes() + # This step is necessary to ensure all lazy wheels are processed + # successfully by the 'download', 'wheel', and 'install' commands. + for req in partially_downloaded_reqs: + self._prepare_linked_requirement(req, parallel_builds) + def prepare_linked_requirement(self, req, parallel_builds=False): + # type: (InstallRequirement, bool) -> Distribution + """Prepare a requirement to be obtained from req.link.""" + assert req.link + link = req.link + self._log_preparing_link(req) + with indent_log(): + # Check if the relevant file is already available + # in the download directory + file_path = None + if self.download_dir is not None and link.is_wheel: + hashes = self._get_linked_req_hashes(req) + file_path = _check_download_dir(req.link, self.download_dir, hashes) + + if file_path is not None: + # The file is already available, so mark it as downloaded + self._downloaded[req.link.url] = file_path, None + else: + # The file is not available, attempt to fetch only metadata + wheel_dist = self._fetch_metadata_using_lazy_wheel(link) + if wheel_dist is not None: + req.needs_more_preparation = True + return wheel_dist + + # None of the optimizations worked, fully prepare the requirement + return self._prepare_linked_requirement(req, parallel_builds) + + def prepare_linked_requirements_more(self, reqs, parallel_builds=False): + # type: (Iterable[InstallRequirement], bool) -> None + """Prepare linked requirements more, if needed.""" + reqs = [req for req in reqs if req.needs_more_preparation] + for req in reqs: + # Determine if any of these requirements were already downloaded. + if self.download_dir is not None and req.link.is_wheel: + hashes = self._get_linked_req_hashes(req) + file_path = _check_download_dir(req.link, self.download_dir, hashes) + if file_path is not None: + self._downloaded[req.link.url] = file_path, None + req.needs_more_preparation = False + + # Prepare requirements we found were already downloaded for some + # reason. The other downloads will be completed separately. + partially_downloaded_reqs = [] # type: List[InstallRequirement] + for req in reqs: + if req.needs_more_preparation: + partially_downloaded_reqs.append(req) + else: + self._prepare_linked_requirement(req, parallel_builds) + + # TODO: separate this part out from RequirementPreparer when the v1 + # resolver can be removed! + self._complete_partial_requirements( + partially_downloaded_reqs, parallel_builds=parallel_builds, + ) + + def _prepare_linked_requirement(self, req, parallel_builds): + # type: (InstallRequirement, bool) -> Distribution + assert req.link + link = req.link + + self._ensure_link_req_src_dir(req, parallel_builds) + hashes = self._get_linked_req_hashes(req) + + if link.is_existing_dir() and self.in_tree_build: + local_file = None + elif link.url not in self._downloaded: try: - download_dir = self.download_dir - # We always delete unpacked sdists after pip ran. - autodelete_unpacked = True - if req.link.is_wheel and self.wheel_download_dir: - # when doing 'pip wheel` we download wheels to a - # dedicated dir. - download_dir = self.wheel_download_dir - if req.link.is_wheel: - if download_dir: - # When downloading, we only unpack wheels to get - # metadata. - autodelete_unpacked = True - else: - # When installing a wheel, we use the unpacked - # wheel. - autodelete_unpacked = False - unpack_url( - req.link, req.source_dir, - download_dir, autodelete_unpacked, - session=session, hashes=hashes, - progress_bar=self.progress_bar - ) - except requests.HTTPError as exc: - logger.critical( - 'Could not install requirement %s because of error %s', - req, - exc, + local_file = unpack_url( + link, req.source_dir, self._download, + self.download_dir, hashes ) + except NetworkConnectionError as exc: raise InstallationError( - 'Could not install requirement %s because of HTTP ' - 'error %s for URL %s' % - (req, exc, req.link) + 'Could not install requirement {} because of HTTP ' + 'error {} for URL {}'.format(req, exc, link) ) - abstract_dist = make_abstract_dist(req) - with self.req_tracker.track(req): - abstract_dist.prep_for_dist(finder, self.build_isolation) - if self._download_should_save: - # Make a .zip of the source_dir we already created. - if req.link.scheme in vcs.all_schemes: - req.archive(self.download_dir) - return abstract_dist + else: + file_path, content_type = self._downloaded[link.url] + if hashes: + hashes.check_against_path(file_path) + local_file = File(file_path, content_type) + + # For use in later processing, + # preserve the file path on the requirement. + if local_file: + req.local_file_path = local_file.path + + dist = _get_prepared_distribution( + req, self.req_tracker, self.finder, self.build_isolation, + ) + return dist + + def save_linked_requirement(self, req): + # type: (InstallRequirement) -> None + assert self.download_dir is not None + assert req.link is not None + link = req.link + if link.is_vcs or (link.is_existing_dir() and req.editable): + # Make a .zip of the source_dir we already created. + req.archive(self.download_dir) + return + + if link.is_existing_dir(): + logger.debug( + 'Not copying link to destination directory ' + 'since it is a directory: %s', link, + ) + return + if req.local_file_path is None: + # No distribution was downloaded for this requirement. + return + + download_location = os.path.join(self.download_dir, link.filename) + if not os.path.exists(download_location): + shutil.copy(req.local_file_path, download_location) + download_path = display_path(download_location) + logger.info('Saved %s', download_path) def prepare_editable_requirement( self, req, # type: InstallRequirement - require_hashes, # type: bool - use_user_site, # type: bool - finder # type: PackageFinder ): - # type: (...) -> DistAbstraction + # type: (...) -> Distribution """Prepare an editable requirement """ assert req.editable, "cannot prepare a non-editable req as editable" @@ -361,46 +610,46 @@ class RequirementPreparer(object): logger.info('Obtaining %s', req) with indent_log(): - if require_hashes: + if self.require_hashes: raise InstallationError( - 'The editable requirement %s cannot be installed when ' + 'The editable requirement {} cannot be installed when ' 'requiring hashes, because there is no single file to ' - 'hash.' % req + 'hash.'.format(req) ) req.ensure_has_source_dir(self.src_dir) - req.update_editable(not self._download_should_save) + req.update_editable() - abstract_dist = make_abstract_dist(req) - with self.req_tracker.track(req): - abstract_dist.prep_for_dist(finder, self.build_isolation) + dist = _get_prepared_distribution( + req, self.req_tracker, self.finder, self.build_isolation, + ) - if self._download_should_save: - req.archive(self.download_dir) - req.check_if_exists(use_user_site) + req.check_if_exists(self.use_user_site) - return abstract_dist + return dist - def prepare_installed_requirement(self, req, require_hashes, skip_reason): - # type: (InstallRequirement, bool, Optional[str]) -> DistAbstraction + def prepare_installed_requirement( + self, + req, # type: InstallRequirement + skip_reason # type: str + ): + # type: (...) -> Distribution """Prepare an already-installed requirement """ assert req.satisfied_by, "req should have been satisfied but isn't" assert skip_reason is not None, ( "did not get skip reason skipped but req.satisfied_by " - "is set to %r" % (req.satisfied_by,) + "is set to {}".format(req.satisfied_by) ) logger.info( 'Requirement %s: %s (%s)', skip_reason, req, req.satisfied_by.version ) with indent_log(): - if require_hashes: + if self.require_hashes: logger.debug( 'Since it is already installed, we are trusting this ' 'package without checking its hash. To ensure a ' 'completely repeatable environment, install into an ' 'empty virtualenv.' ) - abstract_dist = Installed(req) - - return abstract_dist + return InstalledDistribution(req).get_pkg_resources_distribution() diff --git a/pipenv/patched/notpip/_internal/pep425tags.py b/pipenv/patched/notpip/_internal/pep425tags.py deleted file mode 100644 index 4b6eb2bc..00000000 --- a/pipenv/patched/notpip/_internal/pep425tags.py +++ /dev/null @@ -1,384 +0,0 @@ -"""Generate and work with PEP 425 Compatibility Tags.""" -from __future__ import absolute_import - -import distutils.util -import logging -import platform -import re -import sys -import sysconfig -import warnings -from collections import OrderedDict - -try: - import pipenv.patched.notpip._internal.utils.glibc -except ImportError: - import pipenv.patched.notpip.utils.glibc -from pipenv.patched.notpip._internal.utils.compat import get_extension_suffixes -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING - -if MYPY_CHECK_RUNNING: - from typing import ( # noqa: F401 - Tuple, Callable, List, Optional, Union, Dict - ) - - Pep425Tag = Tuple[str, str, str] - -logger = logging.getLogger(__name__) - -_osx_arch_pat = re.compile(r'(.+)_(\d+)_(\d+)_(.+)') - - -def get_config_var(var): - # type: (str) -> Optional[str] - try: - return sysconfig.get_config_var(var) - except IOError as e: # Issue #1074 - warnings.warn("{}".format(e), RuntimeWarning) - return None - - -def get_abbr_impl(): - # type: () -> str - """Return abbreviated implementation name.""" - if hasattr(sys, 'pypy_version_info'): - pyimpl = 'pp' - elif sys.platform.startswith('java'): - pyimpl = 'jy' - elif sys.platform == 'cli': - pyimpl = 'ip' - else: - pyimpl = 'cp' - return pyimpl - - -def get_impl_ver(): - # type: () -> str - """Return implementation version.""" - impl_ver = get_config_var("py_version_nodot") - if not impl_ver or get_abbr_impl() == 'pp': - impl_ver = ''.join(map(str, get_impl_version_info())) - return impl_ver - - -def get_impl_version_info(): - # type: () -> Tuple[int, ...] - """Return sys.version_info-like tuple for use in decrementing the minor - version.""" - if get_abbr_impl() == 'pp': - # as per https://github.com/pypa/pip/issues/2882 - # attrs exist only on pypy - return (sys.version_info[0], - sys.pypy_version_info.major, # type: ignore - sys.pypy_version_info.minor) # type: ignore - else: - return sys.version_info[0], sys.version_info[1] - - -def get_impl_tag(): - # type: () -> str - """ - Returns the Tag for this specific implementation. - """ - return "{}{}".format(get_abbr_impl(), get_impl_ver()) - - -def get_flag(var, fallback, expected=True, warn=True): - # type: (str, Callable[..., bool], Union[bool, int], bool) -> bool - """Use a fallback method for determining SOABI flags if the needed config - var is unset or unavailable.""" - val = get_config_var(var) - if val is None: - if warn: - logger.debug("Config variable '%s' is unset, Python ABI tag may " - "be incorrect", var) - return fallback() - return val == expected - - -def get_abi_tag(): - # type: () -> Optional[str] - """Return the ABI tag based on SOABI (if available) or emulate SOABI - (CPython 2, PyPy).""" - soabi = get_config_var('SOABI') - impl = get_abbr_impl() - if not soabi and impl in {'cp', 'pp'} and hasattr(sys, 'maxunicode'): - d = '' - m = '' - u = '' - if get_flag('Py_DEBUG', - lambda: hasattr(sys, 'gettotalrefcount'), - warn=(impl == 'cp')): - d = 'd' - if get_flag('WITH_PYMALLOC', - lambda: impl == 'cp', - warn=(impl == 'cp')): - m = 'm' - if get_flag('Py_UNICODE_SIZE', - lambda: sys.maxunicode == 0x10ffff, - expected=4, - warn=(impl == 'cp' and - sys.version_info < (3, 3))) \ - and sys.version_info < (3, 3): - u = 'u' - abi = '%s%s%s%s%s' % (impl, get_impl_ver(), d, m, u) - elif soabi and soabi.startswith('cpython-'): - abi = 'cp' + soabi.split('-')[1] - elif soabi: - abi = soabi.replace('.', '_').replace('-', '_') - else: - abi = None - return abi - - -def _is_running_32bit(): - # type: () -> bool - return sys.maxsize == 2147483647 - - -def get_platform(): - # type: () -> str - """Return our platform name 'win32', 'linux_x86_64'""" - if sys.platform == 'darwin': - # distutils.util.get_platform() returns the release based on the value - # of MACOSX_DEPLOYMENT_TARGET on which Python was built, which may - # be significantly older than the user's current machine. - release, _, machine = platform.mac_ver() - split_ver = release.split('.') - - if machine == "x86_64" and _is_running_32bit(): - machine = "i386" - elif machine == "ppc64" and _is_running_32bit(): - machine = "ppc" - - return 'macosx_{}_{}_{}'.format(split_ver[0], split_ver[1], machine) - - # XXX remove distutils dependency - result = distutils.util.get_platform().replace('.', '_').replace('-', '_') - if result == "linux_x86_64" and _is_running_32bit(): - # 32 bit Python program (running on a 64 bit Linux): pip should only - # install and run 32 bit compiled extensions in that case. - result = "linux_i686" - - return result - - -def is_manylinux1_compatible(): - # type: () -> bool - # Only Linux, and only x86-64 / i686 - if get_platform() not in {"linux_x86_64", "linux_i686"}: - return False - - # Check for presence of _manylinux module - try: - import _manylinux - return bool(_manylinux.manylinux1_compatible) - except (ImportError, AttributeError): - # Fall through to heuristic check below - pass - - # Check glibc version. CentOS 5 uses glibc 2.5. - return pipenv.patched.notpip._internal.utils.glibc.have_compatible_glibc(2, 5) - - -def is_manylinux2010_compatible(): - # type: () -> bool - # Only Linux, and only x86-64 / i686 - if get_platform() not in {"linux_x86_64", "linux_i686"}: - return False - - # Check for presence of _manylinux module - try: - import _manylinux - return bool(_manylinux.manylinux2010_compatible) - except (ImportError, AttributeError): - # Fall through to heuristic check below - pass - - # Check glibc version. CentOS 6 uses glibc 2.12. - return pipenv.patched.notpip._internal.utils.glibc.have_compatible_glibc(2, 12) - - -def get_darwin_arches(major, minor, machine): - # type: (int, int, str) -> List[str] - """Return a list of supported arches (including group arches) for - the given major, minor and machine architecture of an macOS machine. - """ - arches = [] - - def _supports_arch(major, minor, arch): - # type: (int, int, str) -> bool - # Looking at the application support for macOS versions in the chart - # provided by https://en.wikipedia.org/wiki/OS_X#Versions it appears - # our timeline looks roughly like: - # - # 10.0 - Introduces ppc support. - # 10.4 - Introduces ppc64, i386, and x86_64 support, however the ppc64 - # and x86_64 support is CLI only, and cannot be used for GUI - # applications. - # 10.5 - Extends ppc64 and x86_64 support to cover GUI applications. - # 10.6 - Drops support for ppc64 - # 10.7 - Drops support for ppc - # - # Given that we do not know if we're installing a CLI or a GUI - # application, we must be conservative and assume it might be a GUI - # application and behave as if ppc64 and x86_64 support did not occur - # until 10.5. - # - # Note: The above information is taken from the "Application support" - # column in the chart not the "Processor support" since I believe - # that we care about what instruction sets an application can use - # not which processors the OS supports. - if arch == 'ppc': - return (major, minor) <= (10, 5) - if arch == 'ppc64': - return (major, minor) == (10, 5) - if arch == 'i386': - return (major, minor) >= (10, 4) - if arch == 'x86_64': - return (major, minor) >= (10, 5) - if arch in groups: - for garch in groups[arch]: - if _supports_arch(major, minor, garch): - return True - return False - - groups = OrderedDict([ - ("fat", ("i386", "ppc")), - ("intel", ("x86_64", "i386")), - ("fat64", ("x86_64", "ppc64")), - ("fat32", ("x86_64", "i386", "ppc")), - ]) # type: Dict[str, Tuple[str, ...]] - - if _supports_arch(major, minor, machine): - arches.append(machine) - - for garch in groups: - if machine in groups[garch] and _supports_arch(major, minor, garch): - arches.append(garch) - - arches.append('universal') - - return arches - - -def get_all_minor_versions_as_strings(version_info): - # type: (Tuple[int, ...]) -> List[str] - versions = [] - major = version_info[:-1] - # Support all previous minor Python versions. - for minor in range(version_info[-1], -1, -1): - versions.append(''.join(map(str, major + (minor,)))) - return versions - - -def get_supported( - versions=None, # type: Optional[List[str]] - noarch=False, # type: bool - platform=None, # type: Optional[str] - impl=None, # type: Optional[str] - abi=None # type: Optional[str] -): - # type: (...) -> List[Pep425Tag] - """Return a list of supported tags for each version specified in - `versions`. - - :param versions: a list of string versions, of the form ["33", "32"], - or None. The first version will be assumed to support our ABI. - :param platform: specify the exact platform you want valid - tags for, or None. If None, use the local system platform. - :param impl: specify the exact implementation you want valid - tags for, or None. If None, use the local interpreter impl. - :param abi: specify the exact abi you want valid - tags for, or None. If None, use the local interpreter abi. - """ - supported = [] - - # Versions must be given with respect to the preference - if versions is None: - version_info = get_impl_version_info() - versions = get_all_minor_versions_as_strings(version_info) - - impl = impl or get_abbr_impl() - - abis = [] # type: List[str] - - abi = abi or get_abi_tag() - if abi: - abis[0:0] = [abi] - - abi3s = set() - for suffix in get_extension_suffixes(): - if suffix.startswith('.abi'): - abi3s.add(suffix.split('.', 2)[1]) - - abis.extend(sorted(list(abi3s))) - - abis.append('none') - - if not noarch: - arch = platform or get_platform() - arch_prefix, arch_sep, arch_suffix = arch.partition('_') - if arch.startswith('macosx'): - # support macosx-10.6-intel on macosx-10.9-x86_64 - match = _osx_arch_pat.match(arch) - if match: - name, major, minor, actual_arch = match.groups() - tpl = '{}_{}_%i_%s'.format(name, major) - arches = [] - for m in reversed(range(int(minor) + 1)): - for a in get_darwin_arches(int(major), m, actual_arch): - arches.append(tpl % (m, a)) - else: - # arch pattern didn't match (?!) - arches = [arch] - elif arch_prefix == 'manylinux2010': - # manylinux1 wheels run on most manylinux2010 systems with the - # exception of wheels depending on ncurses. PEP 571 states - # manylinux1 wheels should be considered manylinux2010 wheels: - # https://www.python.org/dev/peps/pep-0571/#backwards-compatibility-with-manylinux1-wheels - arches = [arch, 'manylinux1' + arch_sep + arch_suffix] - elif platform is None: - arches = [] - if is_manylinux2010_compatible(): - arches.append('manylinux2010' + arch_sep + arch_suffix) - if is_manylinux1_compatible(): - arches.append('manylinux1' + arch_sep + arch_suffix) - arches.append(arch) - else: - arches = [arch] - - # Current version, current API (built specifically for our Python): - for abi in abis: - for arch in arches: - supported.append(('%s%s' % (impl, versions[0]), abi, arch)) - - # abi3 modules compatible with older version of Python - for version in versions[1:]: - # abi3 was introduced in Python 3.2 - if version in {'31', '30'}: - break - for abi in abi3s: # empty set if not Python 3 - for arch in arches: - supported.append(("%s%s" % (impl, version), abi, arch)) - - # Has binaries, does not use the Python API: - for arch in arches: - supported.append(('py%s' % (versions[0][0]), 'none', arch)) - - # No abi / arch, but requires our implementation: - supported.append(('%s%s' % (impl, versions[0]), 'none', 'any')) - # Tagged specifically as being cross-version compatible - # (with just the major version specified) - supported.append(('%s%s' % (impl, versions[0][0]), 'none', 'any')) - - # No abi / arch, generic Python - for i, version in enumerate(versions): - supported.append(('py%s' % (version,), 'none', 'any')) - if i == 0: - supported.append(('py%s' % (version[0]), 'none', 'any')) - - return supported - - -implementation_tag = get_impl_tag() diff --git a/pipenv/patched/notpip/_internal/pyproject.py b/pipenv/patched/notpip/_internal/pyproject.py index 8845b2dc..6e4dc3e6 100644 --- a/pipenv/patched/notpip/_internal/pyproject.py +++ b/pipenv/patched/notpip/_internal/pyproject.py @@ -1,35 +1,29 @@ -from __future__ import absolute_import - -import io import os -import sys +from collections import namedtuple +from typing import Any, List, Optional -from pipenv.patched.notpip._vendor import pytoml, six +from pipenv.patched.notpip._vendor import tomli +from pipenv.patched.notpip._vendor.packaging.requirements import InvalidRequirement, Requirement from pipenv.patched.notpip._internal.exceptions import InstallationError -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING - -if MYPY_CHECK_RUNNING: - from typing import Any, Tuple, Optional, List # noqa: F401 def _is_list_of_str(obj): # type: (Any) -> bool return ( isinstance(obj, list) and - all(isinstance(item, six.string_types) for item in obj) + all(isinstance(item, str) for item in obj) ) -def make_pyproject_path(setup_py_dir): +def make_pyproject_path(unpacked_source_directory): # type: (str) -> str - path = os.path.join(setup_py_dir, 'pyproject.toml') + return os.path.join(unpacked_source_directory, 'pyproject.toml') - # Python2 __file__ should not be unicode - if six.PY2 and isinstance(path, six.text_type): - path = path.encode(sys.getfilesystemencoding()) - return path +BuildSystemDetails = namedtuple('BuildSystemDetails', [ + 'requires', 'backend', 'check', 'backend_path' +]) def load_pyproject_toml( @@ -38,7 +32,7 @@ def load_pyproject_toml( setup_py, # type: str req_name # type: str ): - # type: (...) -> Optional[Tuple[List[str], str, List[str]]] + # type: (...) -> Optional[BuildSystemDetails] """Load the pyproject.toml file. Parameters: @@ -56,14 +50,16 @@ def load_pyproject_toml( name of PEP 517 backend, requirements we should check are installed after setting up the build environment + directory paths to import the backend from (backend-path), + relative to the project root. ) """ has_pyproject = os.path.isfile(pyproject_toml) has_setup = os.path.isfile(setup_py) if has_pyproject: - with io.open(pyproject_toml, encoding="utf-8") as f: - pp_toml = pytoml.load(f) + with open(pyproject_toml, encoding="utf-8") as f: + pp_toml = tomli.load(f) build_system = pp_toml.get("build-system") else: build_system = None @@ -150,7 +146,23 @@ def load_pyproject_toml( reason="'build-system.requires' is not a list of strings.", )) + # Each requirement must be valid as per PEP 508 + for requirement in requires: + try: + Requirement(requirement) + except InvalidRequirement: + raise InstallationError( + error_template.format( + package=req_name, + reason=( + "'build-system.requires' contains an invalid " + "requirement: {!r}".format(requirement) + ), + ) + ) + backend = build_system.get("build-backend") + backend_path = build_system.get("backend-path", []) check = [] # type: List[str] if backend is None: # If the user didn't specify a backend, we assume they want to use @@ -168,4 +180,4 @@ def load_pyproject_toml( backend = "setuptools.build_meta:__legacy__" check = ["setuptools>=40.8.0", "wheel"] - return (requires, backend, check) + return BuildSystemDetails(requires, backend, check, backend_path) diff --git a/pipenv/patched/notpip/_internal/req/__init__.py b/pipenv/patched/notpip/_internal/req/__init__.py index 51606fec..dcc9bcaf 100644 --- a/pipenv/patched/notpip/_internal/req/__init__.py +++ b/pipenv/patched/notpip/_internal/req/__init__.py @@ -1,15 +1,12 @@ -from __future__ import absolute_import - +import collections import logging +from typing import Iterator, List, Optional, Sequence, Tuple +from pipenv.patched.notpip._internal.utils.logging import indent_log + +from .req_file import parse_requirements from .req_install import InstallRequirement from .req_set import RequirementSet -from .req_file import parse_requirements -from pipenv.patched.notpip._internal.utils.logging import indent_log -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING - -if MYPY_CHECK_RUNNING: - from typing import List, Sequence # noqa: F401 __all__ = [ "RequirementSet", "InstallRequirement", @@ -19,59 +16,79 @@ __all__ = [ logger = logging.getLogger(__name__) +class InstallationResult: + def __init__(self, name: str) -> None: + self.name = name + + def __repr__(self) -> str: + return f"InstallationResult(name={self.name!r})" + + +def _validate_requirements( + requirements: List[InstallRequirement], +) -> Iterator[Tuple[str, InstallRequirement]]: + for req in requirements: + assert req.name, f"invalid to-be-installed requirement: {req}" + yield req.name, req + + def install_given_reqs( - to_install, # type: List[InstallRequirement] - install_options, # type: List[str] - global_options=(), # type: Sequence[str] - *args, **kwargs -): - # type: (...) -> List[InstallRequirement] + requirements: List[InstallRequirement], + install_options: List[str], + global_options: Sequence[str], + root: Optional[str], + home: Optional[str], + prefix: Optional[str], + warn_script_location: bool, + use_user_site: bool, + pycompile: bool, +) -> List[InstallationResult]: """ Install everything in the given list. (to be called after having downloaded and unpacked the packages) """ + to_install = collections.OrderedDict(_validate_requirements(requirements)) if to_install: logger.info( 'Installing collected packages: %s', - ', '.join([req.name for req in to_install]), + ', '.join(to_install.keys()), ) + installed = [] + with indent_log(): - for requirement in to_install: - if requirement.conflicts_with: - logger.info( - 'Found existing installation: %s', - requirement.conflicts_with, - ) + for req_name, requirement in to_install.items(): + if requirement.should_reinstall: + logger.info('Attempting uninstall: %s', req_name) with indent_log(): uninstalled_pathset = requirement.uninstall( auto_confirm=True ) + else: + uninstalled_pathset = None + try: requirement.install( install_options, global_options, - *args, - **kwargs + root=root, + home=home, + prefix=prefix, + warn_script_location=warn_script_location, + use_user_site=use_user_site, + pycompile=pycompile, ) except Exception: - should_rollback = ( - requirement.conflicts_with and - not requirement.install_succeeded - ) # if install did not succeed, rollback previous uninstall - if should_rollback: + if uninstalled_pathset and not requirement.install_succeeded: uninstalled_pathset.rollback() raise else: - should_commit = ( - requirement.conflicts_with and - requirement.install_succeeded - ) - if should_commit: + if uninstalled_pathset and requirement.install_succeeded: uninstalled_pathset.commit() - requirement.remove_temporary_source() - return to_install + installed.append(InstallationResult(req_name)) + + return installed diff --git a/pipenv/patched/notpip/_internal/req/constructors.py b/pipenv/patched/notpip/_internal/req/constructors.py index a9d8221a..16739d9c 100644 --- a/pipenv/patched/notpip/_internal/req/constructors.py +++ b/pipenv/patched/notpip/_internal/req/constructors.py @@ -11,31 +11,24 @@ InstallRequirement. import logging import os import re +from typing import Any, Dict, Optional, Set, Tuple, Union from pipenv.patched.notpip._vendor.packaging.markers import Marker from pipenv.patched.notpip._vendor.packaging.requirements import InvalidRequirement, Requirement from pipenv.patched.notpip._vendor.packaging.specifiers import Specifier from pipenv.patched.notpip._vendor.pkg_resources import RequirementParseError, parse_requirements -from pipenv.patched.notpip._internal.download import ( - is_archive_file, is_url, path_to_url, url_to_path, -) from pipenv.patched.notpip._internal.exceptions import InstallationError from pipenv.patched.notpip._internal.models.index import PyPI, TestPyPI from pipenv.patched.notpip._internal.models.link import Link +from pipenv.patched.notpip._internal.models.wheel import Wheel from pipenv.patched.notpip._internal.pyproject import make_pyproject_path +from pipenv.patched.notpip._internal.req.req_file import ParsedRequirement from pipenv.patched.notpip._internal.req.req_install import InstallRequirement +from pipenv.patched.notpip._internal.utils.filetypes import is_archive_file from pipenv.patched.notpip._internal.utils.misc import is_installable_dir -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING -from pipenv.patched.notpip._internal.vcs import vcs -from pipenv.patched.notpip._internal.wheel import Wheel - -if MYPY_CHECK_RUNNING: - from typing import ( # noqa: F401 - Optional, Tuple, Set, Any, Union, Text, Dict, - ) - from pipenv.patched.notpip._internal.cache import WheelCache # noqa: F401 - +from pipenv.patched.notpip._internal.utils.urls import path_to_url +from pipenv.patched.notpip._internal.vcs import is_url, vcs __all__ = [ "install_req_from_editable", "install_req_from_line", @@ -46,8 +39,7 @@ logger = logging.getLogger(__name__) operators = Specifier._operators.keys() -def _strip_extras(path): - # type: (str) -> Tuple[str, Optional[str]] +def _strip_extras(path: str) -> Tuple[str, Optional[str]]: m = re.match(r'^(.+)(\[[^\]]+\])$', path) extras = None if m: @@ -59,8 +51,13 @@ def _strip_extras(path): return path_no_extras, extras -def parse_editable(editable_req): - # type: (str) -> Tuple[Optional[str], str, Optional[Set[str]]] +def convert_extras(extras: Optional[str]) -> Set[str]: + if not extras: + return set() + return Requirement("placeholder" + extras.lower()).extras + + +def parse_editable(editable_req: str) -> Tuple[Optional[str], str, Set[str]]: """Parses an editable requirement into: - a requirement name - an URL @@ -77,16 +74,19 @@ def parse_editable(editable_req): url_no_extras, extras = _strip_extras(url) if os.path.isdir(url_no_extras): - if not os.path.exists(os.path.join(url_no_extras, 'setup.py')): + setup_py = os.path.join(url_no_extras, 'setup.py') + setup_cfg = os.path.join(url_no_extras, 'setup.cfg') + if not os.path.exists(setup_py) and not os.path.exists(setup_cfg): msg = ( - 'File "setup.py" not found. Directory cannot be installed ' - 'in editable mode: {}'.format(os.path.abspath(url_no_extras)) + 'File "setup.py" or "setup.cfg" not found. Directory cannot be ' + 'installed in editable mode: {}' + .format(os.path.abspath(url_no_extras)) ) pyproject_path = make_pyproject_path(url_no_extras) if os.path.isfile(pyproject_path): msg += ( '\n(A "pyproject.toml" file was found, but editable ' - 'mode currently requires a setup.py based build.)' + 'mode currently requires a setuptools-based build.)' ) raise InstallationError(msg) @@ -102,39 +102,33 @@ def parse_editable(editable_req): Requirement("placeholder" + extras.lower()).extras, ) else: - return package_name, url_no_extras, None + return package_name, url_no_extras, set() for version_control in vcs: - if url.lower().startswith('%s:' % version_control): - url = '%s+%s' % (version_control, url) + if url.lower().startswith(f'{version_control}:'): + url = f'{version_control}+{url}' break - if '+' not in url: + link = Link(url) + + if not link.is_vcs: + backends = ", ".join(vcs.all_schemes) raise InstallationError( - '%s should either be a path to a local project or a VCS url ' - 'beginning with svn+, git+, hg+, or bzr+' % - editable_req + f'{editable_req} is not a valid editable requirement. ' + f'It should either be a path to a local project or a VCS URL ' + f'(beginning with {backends}).' ) - vc_type = url.split('+', 1)[0].lower() - - if not vcs.get_backend(vc_type): - error_message = 'For --editable=%s only ' % editable_req + \ - ', '.join([backend.name + '+URL' for backend in vcs.backends]) + \ - ' is currently supported' - raise InstallationError(error_message) - - package_name = Link(url).egg_fragment + package_name = link.egg_fragment if not package_name: raise InstallationError( - "Could not detect requirement name for '%s', please specify one " - "with #egg=your_package_name" % editable_req + "Could not detect requirement name for '{}', please specify one " + "with #egg=your_package_name".format(editable_req) ) - return package_name, url, None + return package_name, url, set() -def deduce_helpful_msg(req): - # type: (str) -> str +def deduce_helpful_msg(req: str) -> str: """Returns helpful msg in case requirements file does not exist, or cannot be parsed. @@ -142,77 +136,142 @@ def deduce_helpful_msg(req): """ msg = "" if os.path.exists(req): - msg = " It does exist." + msg = " The path does exist. " # Try to parse and check if it is a requirements file. try: - with open(req, 'r') as fp: + with open(req) as fp: # parse first line only next(parse_requirements(fp.read())) - msg += " The argument you provided " + \ - "(%s) appears to be a" % (req) + \ - " requirements file. If that is the" + \ - " case, use the '-r' flag to install" + \ + msg += ( + "The argument you provided " + "({}) appears to be a" + " requirements file. If that is the" + " case, use the '-r' flag to install" " the packages specified within it." + ).format(req) except RequirementParseError: - logger.debug("Cannot parse '%s' as requirements \ - file" % (req), exc_info=True) + logger.debug( + "Cannot parse '%s' as requirements file", req, exc_info=True + ) else: - msg += " File '%s' does not exist." % (req) + msg += f" File '{req}' does not exist." return msg +class RequirementParts: + def __init__( + self, + requirement: Optional[Requirement], + link: Optional[Link], + markers: Optional[Marker], + extras: Set[str], + ): + self.requirement = requirement + self.link = link + self.markers = markers + self.extras = extras + + +def parse_req_from_editable(editable_req: str) -> RequirementParts: + name, url, extras_override = parse_editable(editable_req) + + if name is not None: + try: + req: Optional[Requirement] = Requirement(name) + except InvalidRequirement: + raise InstallationError(f"Invalid requirement: '{name}'") + else: + req = None + + link = Link(url) + + return RequirementParts(req, link, None, extras_override) + + # ---- The actual constructors follow ---- def install_req_from_editable( - editable_req, # type: str - comes_from=None, # type: Optional[str] - use_pep517=None, # type: Optional[bool] - isolated=False, # type: bool - options=None, # type: Optional[Dict[str, Any]] - wheel_cache=None, # type: Optional[WheelCache] - constraint=False # type: bool -): - # type: (...) -> InstallRequirement - name, url, extras_override = parse_editable(editable_req) - if url.startswith('file:'): - source_dir = url_to_path(url) - else: - source_dir = None + editable_req: str, + comes_from: Optional[Union[InstallRequirement, str]] = None, + use_pep517: Optional[bool] = None, + isolated: bool = False, + options: Optional[Dict[str, Any]] = None, + constraint: bool = False, + user_supplied: bool = False, +) -> InstallRequirement: + + parts = parse_req_from_editable(editable_req) - if name is not None: - try: - req = Requirement(name) - except InvalidRequirement: - raise InstallationError("Invalid requirement: '%s'" % name) - else: - req = None return InstallRequirement( - req, comes_from, source_dir=source_dir, + parts.requirement, + comes_from=comes_from, + user_supplied=user_supplied, editable=True, - link=Link(url), + link=parts.link, constraint=constraint, use_pep517=use_pep517, isolated=isolated, - options=options if options else {}, - wheel_cache=wheel_cache, - extras=extras_override or (), + install_options=options.get("install_options", []) if options else [], + global_options=options.get("global_options", []) if options else [], + hash_options=options.get("hashes", {}) if options else {}, + extras=parts.extras, ) -def install_req_from_line( - name, # type: str - comes_from=None, # type: Optional[Union[str, InstallRequirement]] - use_pep517=None, # type: Optional[bool] - isolated=False, # type: bool - options=None, # type: Optional[Dict[str, Any]] - wheel_cache=None, # type: Optional[WheelCache] - constraint=False # type: bool -): - # type: (...) -> InstallRequirement - """Creates an InstallRequirement from a name, which might be a - requirement, directory containing 'setup.py', filename, or URL. +def _looks_like_path(name: str) -> bool: + """Checks whether the string "looks like" a path on the filesystem. + + This does not check whether the target actually exists, only judge from the + appearance. + + Returns true if any of the following conditions is true: + * a path separator is found (either os.path.sep or os.path.altsep); + * a dot is found (which represents the current directory). """ + if os.path.sep in name: + return True + if os.path.altsep is not None and os.path.altsep in name: + return True + if name.startswith("."): + return True + return False + + +def _get_url_from_path(path: str, name: str) -> Optional[str]: + """ + First, it checks whether a provided path is an installable directory. If it + is, returns the path. + + If false, check if the path is an archive file (such as a .whl). + The function checks if the path is a file. If false, if the path has + an @, it will treat it as a PEP 440 URL requirement and return the path. + """ + if _looks_like_path(name) and os.path.isdir(path): + if is_installable_dir(path): + return path_to_url(path) + raise InstallationError( + f"Directory {name!r} is not installable. Neither 'setup.py' " + "nor 'pyproject.toml' found." + ) + if not is_archive_file(path): + return None + if os.path.isfile(path): + return path_to_url(path) + urlreq_parts = name.split('@', 1) + if len(urlreq_parts) >= 2 and not _looks_like_path(urlreq_parts[0]): + # If the path contains '@' and the part before it does not look + # like a path, try to treat it as a PEP 440 URL req instead. + return None + logger.warning( + 'Requirement %r looks like a filename, but the ' + 'file does not exist', + name + ) + return path_to_url(path) + + +def parse_req_from_line(name: str, line_source: Optional[str]) -> RequirementParts: if is_url(name): marker_sep = '; ' else: @@ -236,26 +295,9 @@ def install_req_from_line( link = Link(name) else: p, extras_as_string = _strip_extras(path) - looks_like_dir = os.path.isdir(p) and ( - os.path.sep in name or - (os.path.altsep is not None and os.path.altsep in name) or - name.startswith('.') - ) - if looks_like_dir: - if not is_installable_dir(p): - raise InstallationError( - "Directory %r is not installable. Neither 'setup.py' " - "nor 'pyproject.toml' found." % name - ) - link = Link(path_to_url(p)) - elif is_archive_file(p): - if not os.path.isfile(p): - logger.warning( - 'Requirement %r looks like a filename, but the ' - 'file does not exist', - name - ) - link = Link(path_to_url(p)) + url = _get_url_from_path(p, name) + if url is not None: + link = Link(url) # it's a local file, dir, or url if link: @@ -266,7 +308,7 @@ def install_req_from_line( # wheel file if link.is_wheel: wheel = Wheel(link.filename) # can raise InvalidWheelFilename - req_as_string = "%s==%s" % (wheel.name, wheel.version) + req_as_string = f"{wheel.name}=={wheel.version}" else: # set the req to the egg fragment. when it's not there, this # will become an 'unnamed' requirement @@ -276,11 +318,14 @@ def install_req_from_line( else: req_as_string = name - if extras_as_string: - extras = Requirement("placeholder" + extras_as_string.lower()).extras - else: - extras = () - if req_as_string is not None: + extras = convert_extras(extras_as_string) + + def with_source(text: str) -> str: + if not line_source: + return text + return f'{text} (from {line_source})' + + def _parse_req_string(req_as_string: str) -> Requirement: try: req = Requirement(req_as_string) except InvalidRequirement: @@ -291,49 +336,139 @@ def install_req_from_line( not any(op in req_as_string for op in operators)): add_msg = "= is not a valid operator. Did you mean == ?" else: - add_msg = "" - raise InstallationError( - "Invalid requirement: '%s'\n%s" % (req_as_string, add_msg) + add_msg = '' + msg = with_source( + f'Invalid requirement: {req_as_string!r}' ) + if add_msg: + msg += f'\nHint: {add_msg}' + raise InstallationError(msg) + else: + # Deprecate extras after specifiers: "name>=1.0[extras]" + # This currently works by accident because _strip_extras() parses + # any extras in the end of the string and those are saved in + # RequirementParts + for spec in req.specifier: + spec_str = str(spec) + if spec_str.endswith(']'): + msg = f"Extras after version '{spec_str}'." + raise InstallationError(msg) + return req + + if req_as_string is not None: + req: Optional[Requirement] = _parse_req_string(req_as_string) else: req = None + return RequirementParts(req, link, markers, extras) + + +def install_req_from_line( + name: str, + comes_from: Optional[Union[str, InstallRequirement]] = None, + use_pep517: Optional[bool] = None, + isolated: bool = False, + options: Optional[Dict[str, Any]] = None, + constraint: bool = False, + line_source: Optional[str] = None, + user_supplied: bool = False, +) -> InstallRequirement: + """Creates an InstallRequirement from a name, which might be a + requirement, directory containing 'setup.py', filename, or URL. + + :param line_source: An optional string describing where the line is from, + for logging purposes in case of an error. + """ + parts = parse_req_from_line(name, line_source) + return InstallRequirement( - req, comes_from, link=link, markers=markers, + parts.requirement, comes_from, link=parts.link, markers=parts.markers, use_pep517=use_pep517, isolated=isolated, - options=options if options else {}, - wheel_cache=wheel_cache, + install_options=options.get("install_options", []) if options else [], + global_options=options.get("global_options", []) if options else [], + hash_options=options.get("hashes", {}) if options else {}, constraint=constraint, - extras=extras, + extras=parts.extras, + user_supplied=user_supplied, ) def install_req_from_req_string( - req_string, # type: str - comes_from=None, # type: Optional[InstallRequirement] - isolated=False, # type: bool - wheel_cache=None, # type: Optional[WheelCache] - use_pep517=None # type: Optional[bool] -): - # type: (...) -> InstallRequirement + req_string: str, + comes_from: Optional[InstallRequirement] = None, + isolated: bool = False, + use_pep517: Optional[bool] = None, + user_supplied: bool = False, +) -> InstallRequirement: try: req = Requirement(req_string) except InvalidRequirement: - raise InstallationError("Invalid requirement: '%s'" % req) + raise InstallationError(f"Invalid requirement: '{req_string}'") domains_not_allowed = [ PyPI.file_storage_domain, TestPyPI.file_storage_domain, ] - if req.url and comes_from.link.netloc in domains_not_allowed: + if (req.url and comes_from and comes_from.link and + comes_from.link.netloc in domains_not_allowed): # Explicitly disallow pypi packages that depend on external urls raise InstallationError( "Packages installed from PyPI cannot depend on packages " "which are not also hosted on PyPI.\n" - "%s depends on %s " % (comes_from.name, req) + "{} depends on {} ".format(comes_from.name, req) ) return InstallRequirement( - req, comes_from, isolated=isolated, wheel_cache=wheel_cache, - use_pep517=use_pep517 + req, + comes_from, + isolated=isolated, + use_pep517=use_pep517, + user_supplied=user_supplied, + ) + + +def install_req_from_parsed_requirement( + parsed_req: ParsedRequirement, + isolated: bool = False, + use_pep517: Optional[bool] = None, + user_supplied: bool = False, +) -> InstallRequirement: + if parsed_req.is_editable: + req = install_req_from_editable( + parsed_req.requirement, + comes_from=parsed_req.comes_from, + use_pep517=use_pep517, + constraint=parsed_req.constraint, + isolated=isolated, + user_supplied=user_supplied, + ) + + else: + req = install_req_from_line( + parsed_req.requirement, + comes_from=parsed_req.comes_from, + use_pep517=use_pep517, + isolated=isolated, + options=parsed_req.options, + constraint=parsed_req.constraint, + line_source=parsed_req.line_source, + user_supplied=user_supplied, + ) + return req + + +def install_req_from_link_and_ireq( + link: Link, ireq: InstallRequirement +) -> InstallRequirement: + return InstallRequirement( + req=ireq.req, + comes_from=ireq.comes_from, + editable=ireq.editable, + link=link, + markers=ireq.markers, + use_pep517=ireq.use_pep517, + isolated=ireq.isolated, + install_options=ireq.install_options, + global_options=ireq.global_options, + hash_options=ireq.hash_options, ) diff --git a/pipenv/patched/notpip/_internal/req/req_file.py b/pipenv/patched/notpip/_internal/req/req_file.py index 30391cb5..2ea94f64 100644 --- a/pipenv/patched/notpip/_internal/req/req_file.py +++ b/pipenv/patched/notpip/_internal/req/req_file.py @@ -2,40 +2,37 @@ Requirements file parsing """ -from __future__ import absolute_import - import optparse import os import re import shlex -import sys - -from pipenv.patched.notpip._vendor.six.moves import filterfalse -from pipenv.patched.notpip._vendor.six.moves.urllib import parse as urllib_parse +import urllib.parse +from optparse import Values +from typing import TYPE_CHECKING, Any, Callable, Dict, Iterator, List, Optional, Tuple from pipenv.patched.notpip._internal.cli import cmdoptions -from pipenv.patched.notpip._internal.download import get_file_content -from pipenv.patched.notpip._internal.exceptions import RequirementsFileParseError -from pipenv.patched.notpip._internal.req.constructors import ( - install_req_from_editable, install_req_from_line, -) -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING +from pipenv.patched.notpip._internal.exceptions import InstallationError, RequirementsFileParseError +from pipenv.patched.notpip._internal.models.search_scope import SearchScope +from pipenv.patched.notpip._internal.network.session import PipSession +from pipenv.patched.notpip._internal.network.utils import raise_for_status +from pipenv.patched.notpip._internal.utils.encoding import auto_decode +from pipenv.patched.notpip._internal.utils.urls import get_url_scheme -if MYPY_CHECK_RUNNING: - from typing import ( # noqa: F401 - Iterator, Tuple, Optional, List, Callable, Text - ) - from pipenv.patched.notpip._internal.req import InstallRequirement # noqa: F401 - from pipenv.patched.notpip._internal.cache import WheelCache # noqa: F401 - from pipenv.patched.notpip._internal.index import PackageFinder # noqa: F401 - from pipenv.patched.notpip._internal.download import PipSession # noqa: F401 +if TYPE_CHECKING: + # NoReturn introduced in 3.6.2; imported only for type checking to maintain + # pip compatibility with older patch versions of Python 3.6 + from typing import NoReturn - ReqFileLines = Iterator[Tuple[int, Text]] + from pipenv.patched.notpip._internal.index.package_finder import PackageFinder __all__ = ['parse_requirements'] +ReqFileLines = Iterator[Tuple[int, str]] + +LineParser = Callable[[str], Tuple[str, Values]] + SCHEME_RE = re.compile(r'^(http|https|file):', re.I) -COMMENT_RE = re.compile(r'(^|\s)+#.*$') +COMMENT_RE = re.compile(r'(^|\s+)#.*$') # Matches environment variable-style values in '${MY_VARIABLE_1}' with the # variable name consisting of only uppercase letters, digits or the '_' @@ -43,106 +40,242 @@ COMMENT_RE = re.compile(r'(^|\s)+#.*$') # 2013 Edition. ENV_VAR_RE = re.compile(r'(?P\$\{(?P[A-Z0-9_]+)\})') -SUPPORTED_OPTIONS = [ - cmdoptions.constraints, - cmdoptions.editable, - cmdoptions.requirements, - cmdoptions.no_index, +SUPPORTED_OPTIONS: List[Callable[..., optparse.Option]] = [ cmdoptions.index_url, - cmdoptions.find_links, cmdoptions.extra_index_url, - cmdoptions.always_unzip, + cmdoptions.no_index, + cmdoptions.constraints, + cmdoptions.requirements, + cmdoptions.editable, + cmdoptions.find_links, cmdoptions.no_binary, cmdoptions.only_binary, + cmdoptions.prefer_binary, + cmdoptions.require_hashes, cmdoptions.pre, cmdoptions.trusted_host, - cmdoptions.require_hashes, -] # type: List[Callable[..., optparse.Option]] + cmdoptions.use_new_feature, +] # options to be passed to requirements -SUPPORTED_OPTIONS_REQ = [ +SUPPORTED_OPTIONS_REQ: List[Callable[..., optparse.Option]] = [ cmdoptions.install_options, cmdoptions.global_options, cmdoptions.hash, -] # type: List[Callable[..., optparse.Option]] +] # the 'dest' string values SUPPORTED_OPTIONS_REQ_DEST = [str(o().dest) for o in SUPPORTED_OPTIONS_REQ] +class ParsedRequirement: + def __init__( + self, + requirement: str, + is_editable: bool, + comes_from: str, + constraint: bool, + options: Optional[Dict[str, Any]] = None, + line_source: Optional[str] = None, + ) -> None: + self.requirement = requirement + self.is_editable = is_editable + self.comes_from = comes_from + self.options = options + self.constraint = constraint + self.line_source = line_source + + +class ParsedLine: + def __init__( + self, + filename: str, + lineno: int, + args: str, + opts: Values, + constraint: bool, + ) -> None: + self.filename = filename + self.lineno = lineno + self.opts = opts + self.constraint = constraint + + if args: + self.is_requirement = True + self.is_editable = False + self.requirement = args + elif opts.editables: + self.is_requirement = True + self.is_editable = True + # We don't support multiple -e on one line + self.requirement = opts.editables[0] + else: + self.is_requirement = False + + def parse_requirements( - filename, # type: str - finder=None, # type: Optional[PackageFinder] - comes_from=None, # type: Optional[str] - options=None, # type: Optional[optparse.Values] - session=None, # type: Optional[PipSession] - constraint=False, # type: bool - wheel_cache=None, # type: Optional[WheelCache] - use_pep517=None # type: Optional[bool] -): - # type: (...) -> Iterator[InstallRequirement] - """Parse a requirements file and yield InstallRequirement instances. + filename: str, + session: PipSession, + finder: Optional["PackageFinder"] = None, + options: Optional[optparse.Values] = None, + constraint: bool = False, +) -> Iterator[ParsedRequirement]: + """Parse a requirements file and yield ParsedRequirement instances. :param filename: Path or url of requirements file. + :param session: PipSession instance. :param finder: Instance of pip.index.PackageFinder. - :param comes_from: Origin description of requirements. :param options: cli options. - :param session: Instance of pip.download.PipSession. :param constraint: If true, parsing a constraint file rather than requirements file. - :param wheel_cache: Instance of pip.wheel.WheelCache - :param use_pep517: Value of the --use-pep517 option. """ - if session is None: - raise TypeError( - "parse_requirements() missing 1 required keyword argument: " - "'session'" + line_parser = get_line_parser(finder) + parser = RequirementsFileParser(session, line_parser) + + for parsed_line in parser.parse(filename, constraint): + parsed_req = handle_line( + parsed_line, + options=options, + finder=finder, + session=session ) - - _, content = get_file_content( - filename, comes_from=comes_from, session=session - ) - - lines_enum = preprocess(content, options) - - for line_number, line in lines_enum: - req_iter = process_line(line, filename, line_number, finder, - comes_from, options, session, wheel_cache, - use_pep517=use_pep517, constraint=constraint) - for req in req_iter: - yield req + if parsed_req is not None: + yield parsed_req -def preprocess(content, options): - # type: (Text, Optional[optparse.Values]) -> ReqFileLines +def preprocess(content: str) -> ReqFileLines: """Split, filter, and join lines, and return a line iterator :param content: the content of the requirements file - :param options: cli options """ - lines_enum = enumerate(content.splitlines(), start=1) # type: ReqFileLines + lines_enum: ReqFileLines = enumerate(content.splitlines(), start=1) lines_enum = join_lines(lines_enum) lines_enum = ignore_comments(lines_enum) - lines_enum = skip_regex(lines_enum, options) lines_enum = expand_env_variables(lines_enum) return lines_enum -def process_line( - line, # type: Text - filename, # type: str - line_number, # type: int - finder=None, # type: Optional[PackageFinder] - comes_from=None, # type: Optional[str] - options=None, # type: Optional[optparse.Values] - session=None, # type: Optional[PipSession] - wheel_cache=None, # type: Optional[WheelCache] - use_pep517=None, # type: Optional[bool] - constraint=False # type: bool -): - # type: (...) -> Iterator[InstallRequirement] - """Process a single requirements line; This can result in creating/yielding - requirements, or updating the finder. +def handle_requirement_line( + line: ParsedLine, + options: Optional[optparse.Values] = None, +) -> ParsedRequirement: + + # preserve for the nested code path + line_comes_from = '{} {} (line {})'.format( + '-c' if line.constraint else '-r', line.filename, line.lineno, + ) + + assert line.is_requirement + + if line.is_editable: + # For editable requirements, we don't support per-requirement + # options, so just return the parsed requirement. + return ParsedRequirement( + requirement=line.requirement, + is_editable=line.is_editable, + comes_from=line_comes_from, + constraint=line.constraint, + ) + else: + if options: + # Disable wheels if the user has specified build options + cmdoptions.check_install_build_global(options, line.opts) + + # get the options that apply to requirements + req_options = {} + for dest in SUPPORTED_OPTIONS_REQ_DEST: + if dest in line.opts.__dict__ and line.opts.__dict__[dest]: + req_options[dest] = line.opts.__dict__[dest] + + line_source = f'line {line.lineno} of {line.filename}' + return ParsedRequirement( + requirement=line.requirement, + is_editable=line.is_editable, + comes_from=line_comes_from, + constraint=line.constraint, + options=req_options, + line_source=line_source, + ) + + +def handle_option_line( + opts: Values, + filename: str, + lineno: int, + finder: Optional["PackageFinder"] = None, + options: Optional[optparse.Values] = None, + session: Optional[PipSession] = None, +) -> None: + + if options: + # percolate options upward + if opts.require_hashes: + options.require_hashes = opts.require_hashes + if opts.features_enabled: + options.features_enabled.extend( + f for f in opts.features_enabled + if f not in options.features_enabled + ) + + # set finder options + if finder: + find_links = finder.find_links + index_urls = finder.index_urls + if opts.index_url: + index_urls = [opts.index_url] + if opts.no_index is True: + index_urls = [] + if opts.extra_index_urls: + index_urls.extend(opts.extra_index_urls) + if opts.find_links: + # FIXME: it would be nice to keep track of the source + # of the find_links: support a find-links local path + # relative to a requirements file. + value = opts.find_links[0] + req_dir = os.path.dirname(os.path.abspath(filename)) + relative_to_reqs_file = os.path.join(req_dir, value) + if os.path.exists(relative_to_reqs_file): + value = relative_to_reqs_file + find_links.append(value) + + if session: + # We need to update the auth urls in session + session.update_index_urls(index_urls) + + search_scope = SearchScope( + find_links=find_links, + index_urls=index_urls, + ) + finder.search_scope = search_scope + + if opts.pre: + finder.set_allow_all_prereleases() + + if opts.prefer_binary: + finder.set_prefer_binary() + + if session: + for host in opts.trusted_hosts or []: + source = f'line {lineno} of {filename}' + session.add_trusted_host(host, source=source) + + +def handle_line( + line: ParsedLine, + options: Optional[optparse.Values] = None, + finder: Optional["PackageFinder"] = None, + session: Optional[PipSession] = None, +) -> Optional[ParsedRequirement]: + """Handle a single parsed requirements line; This can result in + creating/yielding requirements, or updating the finder. + + :param line: The parsed line to be processed. + :param options: CLI options. + :param finder: The finder - updated by non-requirement lines. + :param session: The session - updated by non-requirement lines. + + Returns a ParsedRequirement object if the line is a requirement line, + otherwise returns None. For lines that contain requirements, the only options that have an effect are from SUPPORTED_OPTIONS_REQ, and they are scoped to the @@ -154,109 +287,110 @@ def process_line( be present, but are ignored. These lines may contain multiple options (although our docs imply only one is supported), and all our parsed and affect the finder. - - :param constraint: If True, parsing a constraints file. - :param options: OptionParser options that we may update """ - parser = build_parser(line) - defaults = parser.get_default_values() - defaults.index_url = None - if finder: - defaults.format_control = finder.format_control - args_str, options_str = break_args_options(line) - # Prior to 2.7.3, shlex cannot deal with unicode entries - if sys.version_info < (2, 7, 3): - # https://github.com/python/mypy/issues/1174 - options_str = options_str.encode('utf8') # type: ignore - # https://github.com/python/mypy/issues/1174 - opts, _ = parser.parse_args( - shlex.split(options_str), defaults) # type: ignore - # preserve for the nested code path - line_comes_from = '%s %s (line %s)' % ( - '-c' if constraint else '-r', filename, line_number, - ) - - # yield a line requirement - if args_str: - isolated = options.isolated_mode if options else False - if options: - cmdoptions.check_install_build_global(options, opts) - # get the options that apply to requirements - req_options = {} - for dest in SUPPORTED_OPTIONS_REQ_DEST: - if dest in opts.__dict__ and opts.__dict__[dest]: - req_options[dest] = opts.__dict__[dest] - yield install_req_from_line( - args_str, line_comes_from, constraint=constraint, - use_pep517=use_pep517, - isolated=isolated, options=req_options, wheel_cache=wheel_cache + if line.is_requirement: + parsed_req = handle_requirement_line(line, options) + return parsed_req + else: + handle_option_line( + line.opts, + line.filename, + line.lineno, + finder, + options, + session, ) - - # yield an editable requirement - elif opts.editables: - isolated = options.isolated_mode if options else False - yield install_req_from_editable( - opts.editables[0], comes_from=line_comes_from, - use_pep517=use_pep517, - constraint=constraint, isolated=isolated, wheel_cache=wheel_cache - ) - - # parse a nested requirements file - elif opts.requirements or opts.constraints: - if opts.requirements: - req_path = opts.requirements[0] - nested_constraint = False - else: - req_path = opts.constraints[0] - nested_constraint = True - # original file is over http - if SCHEME_RE.search(filename): - # do a url join so relative paths work - req_path = urllib_parse.urljoin(filename, req_path) - # original file and nested file are paths - elif not SCHEME_RE.search(req_path): - # do a join so relative paths work - req_path = os.path.join(os.path.dirname(filename), req_path) - # TODO: Why not use `comes_from='-r {} (line {})'` here as well? - parsed_reqs = parse_requirements( - req_path, finder, comes_from, options, session, - constraint=nested_constraint, wheel_cache=wheel_cache - ) - for req in parsed_reqs: - yield req - - # percolate hash-checking option upward - elif opts.require_hashes: - options.require_hashes = opts.require_hashes - - # set finder options - elif finder: - if opts.index_url: - finder.index_urls = [opts.index_url] - if opts.no_index is True: - finder.index_urls = [] - if opts.extra_index_urls: - finder.index_urls.extend(opts.extra_index_urls) - if opts.find_links: - # FIXME: it would be nice to keep track of the source - # of the find_links: support a find-links local path - # relative to a requirements file. - value = opts.find_links[0] - req_dir = os.path.dirname(os.path.abspath(filename)) - relative_to_reqs_file = os.path.join(req_dir, value) - if os.path.exists(relative_to_reqs_file): - value = relative_to_reqs_file - finder.find_links.append(value) - if opts.pre: - finder.allow_all_prereleases = True - if opts.trusted_hosts: - finder.secure_origins.extend( - ("*", host, "*") for host in opts.trusted_hosts) + return None -def break_args_options(line): - # type: (Text) -> Tuple[str, Text] +class RequirementsFileParser: + def __init__( + self, + session: PipSession, + line_parser: LineParser, + ) -> None: + self._session = session + self._line_parser = line_parser + + def parse(self, filename: str, constraint: bool) -> Iterator[ParsedLine]: + """Parse a given file, yielding parsed lines. + """ + yield from self._parse_and_recurse(filename, constraint) + + def _parse_and_recurse( + self, filename: str, constraint: bool + ) -> Iterator[ParsedLine]: + for line in self._parse_file(filename, constraint): + if ( + not line.is_requirement and + (line.opts.requirements or line.opts.constraints) + ): + # parse a nested requirements file + if line.opts.requirements: + req_path = line.opts.requirements[0] + nested_constraint = False + else: + req_path = line.opts.constraints[0] + nested_constraint = True + + # original file is over http + if SCHEME_RE.search(filename): + # do a url join so relative paths work + req_path = urllib.parse.urljoin(filename, req_path) + # original file and nested file are paths + elif not SCHEME_RE.search(req_path): + # do a join so relative paths work + req_path = os.path.join( + os.path.dirname(filename), req_path, + ) + + yield from self._parse_and_recurse(req_path, nested_constraint) + else: + yield line + + def _parse_file(self, filename: str, constraint: bool) -> Iterator[ParsedLine]: + _, content = get_file_content(filename, self._session) + + lines_enum = preprocess(content) + + for line_number, line in lines_enum: + try: + args_str, opts = self._line_parser(line) + except OptionParsingError as e: + # add offending line + msg = f'Invalid requirement: {line}\n{e.msg}' + raise RequirementsFileParseError(msg) + + yield ParsedLine( + filename, + line_number, + args_str, + opts, + constraint, + ) + + +def get_line_parser(finder: Optional["PackageFinder"]) -> LineParser: + def parse_line(line: str) -> Tuple[str, Values]: + # Build new parser for each line since it accumulates appendable + # options. + parser = build_parser() + defaults = parser.get_default_values() + defaults.index_url = None + if finder: + defaults.format_control = finder.format_control + + args_str, options_str = break_args_options(line) + + opts, _ = parser.parse_args(shlex.split(options_str), defaults) + + return args_str, opts + + return parse_line + + +def break_args_options(line: str) -> Tuple[str, str]: """Break up the line into an args and options string. We only want to shlex (and then optparse) the options, not the args. args can contain markers which are corrupted by shlex. @@ -270,11 +404,15 @@ def break_args_options(line): else: args.append(token) options.pop(0) - return ' '.join(args), ' '.join(options) # type: ignore + return ' '.join(args), ' '.join(options) -def build_parser(line): - # type: (Text) -> optparse.OptionParser +class OptionParsingError(Exception): + def __init__(self, msg: str) -> None: + self.msg = msg + + +def build_parser() -> optparse.OptionParser: """ Return a parser for parsing requirement lines """ @@ -287,10 +425,8 @@ def build_parser(line): # By default optparse sys.exits on parsing errors. We want to wrap # that in our own exception. - def parser_exit(self, msg): - # add offending line - msg = 'Invalid requirement: %s\n%s' % (line, msg) - raise RequirementsFileParseError(msg) + def parser_exit(self: Any, msg: str) -> "NoReturn": + raise OptionParsingError(msg) # NOTE: mypy disallows assigning to a method # https://github.com/python/mypy/issues/2427 parser.exit = parser_exit # type: ignore @@ -298,13 +434,12 @@ def build_parser(line): return parser -def join_lines(lines_enum): - # type: (ReqFileLines) -> ReqFileLines +def join_lines(lines_enum: ReqFileLines) -> ReqFileLines: """Joins a line ending in '\' with the previous line (except when following comments). The joined line takes on the index of the first line. """ primary_line_number = None - new_line = [] # type: List[Text] + new_line: List[str] = [] for line_number, line in lines_enum: if not line.endswith('\\') or COMMENT_RE.match(line): if COMMENT_RE.match(line): @@ -312,6 +447,7 @@ def join_lines(lines_enum): line = ' ' + line if new_line: new_line.append(line) + assert primary_line_number is not None yield primary_line_number, ''.join(new_line) new_line = [] else: @@ -323,13 +459,13 @@ def join_lines(lines_enum): # last line contains \ if new_line: + assert primary_line_number is not None yield primary_line_number, ''.join(new_line) # TODO: handle space after '\'. -def ignore_comments(lines_enum): - # type: (ReqFileLines) -> ReqFileLines +def ignore_comments(lines_enum: ReqFileLines) -> ReqFileLines: """ Strips comments and filter empty lines. """ @@ -340,22 +476,7 @@ def ignore_comments(lines_enum): yield line_number, line -def skip_regex(lines_enum, options): - # type: (ReqFileLines, Optional[optparse.Values]) -> ReqFileLines - """ - Skip lines that match '--skip-requirements-regex' pattern - - Note: the regex pattern is only built once - """ - skip_regex = options.skip_requirements_regex if options else None - if skip_regex: - pattern = re.compile(skip_regex) - lines_enum = filterfalse(lambda e: pattern.search(e[1]), lines_enum) - return lines_enum - - -def expand_env_variables(lines_enum): - # type: (ReqFileLines) -> ReqFileLines +def expand_env_variables(lines_enum: ReqFileLines) -> ReqFileLines: """Replace all environment variables that can be retrieved via `os.getenv`. The only allowed format for environment variables defined in the @@ -364,7 +485,7 @@ def expand_env_variables(lines_enum): 1. Strings that contain a `$` aren't accidentally (partially) expanded. 2. Ensure consistency across platforms for requirement files. - These points are the result of a discusssion on the `github pull + These points are the result of a discussion on the `github pull request #3514 `_. Valid characters in variable names follow the `POSIX standard @@ -380,3 +501,28 @@ def expand_env_variables(lines_enum): line = line.replace(env_var, value) yield line_number, line + + +def get_file_content(url: str, session: PipSession) -> Tuple[str, str]: + """Gets the content of a file; it may be a filename, file: URL, or + http: URL. Returns (location, content). Content is unicode. + Respects # -*- coding: declarations on the retrieved files. + + :param url: File path or url. + :param session: PipSession instance. + """ + scheme = get_url_scheme(url) + + # Pip has special support for file:// URLs (LocalFSAdapter). + if scheme in ['http', 'https', 'file']: + resp = session.get(url) + raise_for_status(resp) + return resp.url, resp.text + + # Assume this is a bare path. + try: + with open(url, 'rb') as f: + content = auto_decode(f.read()) + except OSError as exc: + raise InstallationError(f'Could not open requirements file: {exc}') + return url, content diff --git a/pipenv/patched/notpip/_internal/req/req_install.py b/pipenv/patched/notpip/_internal/req/req_install.py index fd3cead6..149ecc44 100644 --- a/pipenv/patched/notpip/_internal/req/req_install.py +++ b/pipenv/patched/notpip/_internal/req/req_install.py @@ -1,99 +1,141 @@ -from __future__ import absolute_import +# The following comment should be removed at some point in the future. +# mypy: strict-optional=False import logging import os import shutil import sys -import sysconfig +import uuid import zipfile -from distutils.util import change_root +from typing import Any, Dict, Iterable, List, Optional, Sequence, Union from pipenv.patched.notpip._vendor import pkg_resources, six +from pipenv.patched.notpip._vendor.packaging.markers import Marker from pipenv.patched.notpip._vendor.packaging.requirements import Requirement +from pipenv.patched.notpip._vendor.packaging.specifiers import SpecifierSet from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name from pipenv.patched.notpip._vendor.packaging.version import Version from pipenv.patched.notpip._vendor.packaging.version import parse as parse_version from pipenv.patched.notpip._vendor.pep517.wrappers import Pep517HookCaller +from pipenv.patched.notpip._vendor.pkg_resources import Distribution -from pipenv.patched.notpip._internal import wheel -from pipenv.patched.notpip._internal.build_env import NoOpBuildEnvironment +from pipenv.patched.notpip._internal.build_env import BuildEnvironment, NoOpBuildEnvironment from pipenv.patched.notpip._internal.exceptions import InstallationError -from pipenv.patched.notpip._internal.locations import ( - PIP_DELETE_MARKER_FILENAME, running_under_virtualenv, -) +from pipenv.patched.notpip._internal.locations import get_scheme from pipenv.patched.notpip._internal.models.link import Link +from pipenv.patched.notpip._internal.operations.build.metadata import generate_metadata +from pipenv.patched.notpip._internal.operations.build.metadata_legacy import ( + generate_metadata as generate_metadata_legacy, +) +from pipenv.patched.notpip._internal.operations.install.editable_legacy import ( + install_editable as install_editable_legacy, +) +from pipenv.patched.notpip._internal.operations.install.legacy import LegacyInstallFailure +from pipenv.patched.notpip._internal.operations.install.legacy import install as install_legacy +from pipenv.patched.notpip._internal.operations.install.wheel import install_wheel from pipenv.patched.notpip._internal.pyproject import load_pyproject_toml, make_pyproject_path from pipenv.patched.notpip._internal.req.req_uninstall import UninstallPathSet -from pipenv.patched.notpip._internal.utils.compat import native_str +from pipenv.patched.notpip._internal.utils.deprecation import deprecated +from pipenv.patched.notpip._internal.utils.direct_url_helpers import direct_url_from_link from pipenv.patched.notpip._internal.utils.hashes import Hashes from pipenv.patched.notpip._internal.utils.logging import indent_log from pipenv.patched.notpip._internal.utils.misc import ( - _make_build_dir, ask_path_exists, backup_dir, call_subprocess, - display_path, dist_in_site_packages, dist_in_usersite, ensure_dir, - get_installed_version, redact_password_from_url, rmtree, + ask_path_exists, + backup_dir, + display_path, + dist_in_site_packages, + dist_in_usersite, + get_distribution, + hide_url, + redact_auth_from_url, ) from pipenv.patched.notpip._internal.utils.packaging import get_metadata -from pipenv.patched.notpip._internal.utils.setuptools_build import SETUPTOOLS_SHIM -from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING -from pipenv.patched.notpip._internal.utils.ui import open_spinner +from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory, tempdir_kinds +from pipenv.patched.notpip._internal.utils.virtualenv import running_under_virtualenv from pipenv.patched.notpip._internal.vcs import vcs -from pipenv.patched.notpip._internal.wheel import move_wheel_files - -if MYPY_CHECK_RUNNING: - from typing import ( # noqa: F401 - Optional, Iterable, List, Union, Any, Text, Sequence, Dict - ) - from pipenv.patched.notpip._internal.build_env import BuildEnvironment # noqa: F401 - from pipenv.patched.notpip._internal.cache import WheelCache # noqa: F401 - from pipenv.patched.notpip._internal.index import PackageFinder # noqa: F401 - from pipenv.patched.notpip._vendor.pkg_resources import Distribution # noqa: F401 - from pipenv.patched.notpip._vendor.packaging.specifiers import SpecifierSet # noqa: F401 - from pipenv.patched.notpip._vendor.packaging.markers import Marker # noqa: F401 - logger = logging.getLogger(__name__) -class InstallRequirement(object): +def _get_dist(metadata_directory: str) -> Distribution: + """Return a pkg_resources.Distribution for the provided + metadata directory. + """ + dist_dir = metadata_directory.rstrip(os.sep) + + # Build a PathMetadata object, from path to metadata. :wink: + base_dir, dist_dir_name = os.path.split(dist_dir) + metadata = pkg_resources.PathMetadata(base_dir, dist_dir) + + # Determine the correct Distribution object type. + if dist_dir.endswith(".egg-info"): + dist_cls = pkg_resources.Distribution + dist_name = os.path.splitext(dist_dir_name)[0] + else: + assert dist_dir.endswith(".dist-info") + dist_cls = pkg_resources.DistInfoDistribution + dist_name = os.path.splitext(dist_dir_name)[0].split("-")[0] + + return dist_cls( + base_dir, + project_name=dist_name, + metadata=metadata, + ) + + +class InstallRequirement: """ Represents something that may be installed later on, may have information - about where to fetch the relavant requirement and also contains logic for + about where to fetch the relevant requirement and also contains logic for installing the said requirement. """ def __init__( self, - req, # type: Optional[Requirement] - comes_from, # type: Optional[Union[str, InstallRequirement]] - source_dir=None, # type: Optional[str] - editable=False, # type: bool - link=None, # type: Optional[Link] - update=True, # type: bool - markers=None, # type: Optional[Marker] - use_pep517=None, # type: Optional[bool] - isolated=False, # type: bool - options=None, # type: Optional[Dict[str, Any]] - wheel_cache=None, # type: Optional[WheelCache] - constraint=False, # type: bool - extras=() # type: Iterable[str] - ): - # type: (...) -> None + req: Optional[Requirement], + comes_from: Optional[Union[str, "InstallRequirement"]], + editable: bool = False, + link: Optional[Link] = None, + markers: Optional[Marker] = None, + use_pep517: Optional[bool] = None, + isolated: bool = False, + install_options: Optional[List[str]] = None, + global_options: Optional[List[str]] = None, + hash_options: Optional[Dict[str, List[str]]] = None, + constraint: bool = False, + extras: Iterable[str] = (), + user_supplied: bool = False, + ) -> None: assert req is None or isinstance(req, Requirement), req self.req = req self.comes_from = comes_from self.constraint = constraint - if source_dir is not None: - self.source_dir = os.path.normpath(os.path.abspath(source_dir)) - else: - self.source_dir = None self.editable = editable + self.legacy_install_reason: Optional[int] = None + + # source_dir is the local directory where the linked requirement is + # located, or unpacked. In case unpacking is needed, creating and + # populating source_dir is done by the RequirementPreparer. Note this + # is not necessarily the directory where pyproject.toml or setup.py is + # located - that one is obtained via unpacked_source_directory. + self.source_dir: Optional[str] = None + if self.editable: + assert link + if link.is_file: + self.source_dir = os.path.normpath( + os.path.abspath(link.file_path) + ) - self._wheel_cache = wheel_cache if link is None and req and req.url: # PEP 508 URL requirement link = Link(req.url) self.link = self.original_link = link + self.original_link_is_in_wheel_cache = False + + # Path to any downloaded or already-existing package. + self.local_file_path: Optional[str] = None + if self.link and self.link.is_file: + self.local_file_path = self.link.file_path if extras: self.extras = extras @@ -107,46 +149,44 @@ class InstallRequirement(object): markers = req.marker self.markers = markers - self._egg_info_path = None # type: Optional[str] # This holds the pkg_resources.Distribution object if this requirement # is already available: - self.satisfied_by = None - # This hold the pkg_resources.Distribution object if this requirement - # conflicts with another installed distribution: - self.conflicts_with = None + self.satisfied_by: Optional[Distribution] = None + # Whether the installation process should try to uninstall an existing + # distribution before installing this requirement. + self.should_reinstall = False # Temporary build location - self._temp_build_dir = TempDirectory(kind="req-build") - # Used to store the global directory where the _temp_build_dir should - # have been created. Cf _correct_build_location method. - self._ideal_build_dir = None # type: Optional[str] - # True if the editable should be updated: - self.update = update + self._temp_build_dir: Optional[TempDirectory] = None # Set to True after successful installation - self.install_succeeded = None # type: Optional[bool] - # UninstallPathSet of uninstalled distribution (for possible rollback) - self.uninstalled_pathset = None - self.options = options if options else {} + self.install_succeeded: Optional[bool] = None + # Supplied options + self.install_options = install_options if install_options else [] + self.global_options = global_options if global_options else [] + self.hash_options = hash_options if hash_options else {} # Set to True after successful preparation of this requirement self.prepared = False - self.is_direct = False + # User supplied requirement are explicitly requested for installation + # by the user via CLI arguments or requirements files, as opposed to, + # e.g. dependencies, extras or constraints. + self.user_supplied = user_supplied self.isolated = isolated - self.build_env = NoOpBuildEnvironment() # type: BuildEnvironment + self.build_env: BuildEnvironment = NoOpBuildEnvironment() # For PEP 517, the directory where we request the project metadata # gets stored. We need this to pass to build_wheel, so the backend # can ensure that the wheel matches the metadata (see the PEP for # details). - self.metadata_directory = None # type: Optional[str] + self.metadata_directory: Optional[str] = None # The static build requirements (from pyproject.toml) - self.pyproject_requires = None # type: Optional[List[str]] + self.pyproject_requires: Optional[List[str]] = None # Build requirements that we will check are available - self.requirements_to_check = [] # type: List[str] + self.requirements_to_check: List[str] = [] # The PEP 517 backend we should use to build the project - self.pep517_backend = None # type: Optional[Pep517HookCaller] + self.pep517_backend: Optional[Pep517HookCaller] = None # Are we using PEP 517 for this requirement? # After pyproject.toml has been loaded, the only valid values are True @@ -155,67 +195,60 @@ class InstallRequirement(object): # but after loading this flag should be treated as read only. self.use_pep517 = use_pep517 - def __str__(self): + # This requirement needs more preparation before it can be built + self.needs_more_preparation = False + + def __str__(self) -> str: if self.req: s = str(self.req) if self.link: - s += ' from %s' % redact_password_from_url(self.link.url) + s += ' from {}'.format(redact_auth_from_url(self.link.url)) elif self.link: - s = redact_password_from_url(self.link.url) + s = redact_auth_from_url(self.link.url) else: s = '' if self.satisfied_by is not None: - s += ' in %s' % display_path(self.satisfied_by.location) + s += ' in {}'.format(display_path(self.satisfied_by.location)) if self.comes_from: - if isinstance(self.comes_from, six.string_types): - comes_from = self.comes_from + if isinstance(self.comes_from, str): + comes_from: Optional[str] = self.comes_from else: comes_from = self.comes_from.from_path() if comes_from: - s += ' (from %s)' % comes_from + s += f' (from {comes_from})' return s - def __repr__(self): - return '<%s object: %s editable=%r>' % ( + def __repr__(self) -> str: + return '<{} object: {} editable={!r}>'.format( self.__class__.__name__, str(self), self.editable) - def populate_link(self, finder, upgrade, require_hashes): - # type: (PackageFinder, bool, bool) -> None - """Ensure that if a link can be found for this, that it is found. - - Note that self.link may still be None - if Upgrade is False and the - requirement is already installed. - - If require_hashes is True, don't use the wheel cache, because cached - wheels, always built locally, have different hashes than the files - downloaded from the index server and thus throw false hash mismatches. - Furthermore, cached wheels at present have undeterministic contents due - to file modification times. + def format_debug(self) -> str: + """An un-tested helper for getting state, for debugging. """ - if self.link is None: - self.link = finder.find_requirement(self, upgrade) - if self._wheel_cache is not None and not require_hashes: - old_link = self.link - self.link = self._wheel_cache.get(self.link, self.name) - if old_link != self.link: - logger.debug('Using cached wheel link: %s', self.link) + attributes = vars(self) + names = sorted(attributes) + + state = ( + "{}={!r}".format(attr, attributes[attr]) for attr in sorted(names) + ) + return '<{name} object: {{{state}}}>'.format( + name=self.__class__.__name__, + state=", ".join(state), + ) # Things that are valid for all kinds of requirements? @property - def name(self): - # type: () -> Optional[str] + def name(self) -> Optional[str]: if self.req is None: return None - return native_str(pkg_resources.safe_name(self.req.name)) + return pkg_resources.safe_name(self.req.name) @property - def specifier(self): - # type: () -> SpecifierSet + def specifier(self) -> SpecifierSet: return self.req.specifier @property - def is_pinned(self): - # type: () -> bool + def is_pinned(self) -> bool: """Return whether I am pinned to an exact version. For example, some-package==1.2 is pinned; some-package>1.2 is not. @@ -224,12 +257,7 @@ class InstallRequirement(object): return (len(specifiers) == 1 and next(iter(specifiers)).operator in {'==', '==='}) - @property - def installed_version(self): - return get_installed_version(self.name) - - def match_markers(self, extras_requested=None): - # type: (Optional[Iterable[str]]) -> bool + def match_markers(self, extras_requested: Optional[Iterable[str]] = None) -> bool: if not extras_requested: # Provide an extra to safely evaluate the markers # without matching any extra @@ -242,18 +270,16 @@ class InstallRequirement(object): return True @property - def has_hash_options(self): - # type: () -> bool + def has_hash_options(self) -> bool: """Return whether any known-good hashes are specified as options. These activate --require-hashes mode; hashes specified as part of a URL do not. """ - return bool(self.options.get('hashes', {})) + return bool(self.hash_options) - def hashes(self, trust_internet=True): - # type: (bool) -> Hashes + def hashes(self, trust_internet: bool = True) -> Hashes: """Return a hash-comparer that considers my option- and URL-based hashes to be known-good. @@ -268,21 +294,20 @@ class InstallRequirement(object): downloaded from the internet, as by populate_link() """ - good_hashes = self.options.get('hashes', {}).copy() + good_hashes = self.hash_options.copy() link = self.link if trust_internet else self.original_link if link and link.hash: good_hashes.setdefault(link.hash_name, []).append(link.hash) return Hashes(good_hashes) - def from_path(self): - # type: () -> Optional[str] + def from_path(self) -> Optional[str]: """Format a nice indicator to show where this "comes from" """ if self.req is None: return None s = str(self.req) if self.comes_from: - if isinstance(self.comes_from, six.string_types): + if isinstance(self.comes_from, str): comes_from = self.comes_from else: comes_from = self.comes_from.from_path() @@ -290,191 +315,155 @@ class InstallRequirement(object): s += '->' + comes_from return s - def build_location(self, build_dir): - # type: (str) -> Optional[str] + def ensure_build_location( + self, build_dir: str, autodelete: bool, parallel_builds: bool + ) -> str: assert build_dir is not None - if self._temp_build_dir.path is not None: + if self._temp_build_dir is not None: + assert self._temp_build_dir.path return self._temp_build_dir.path if self.req is None: - # for requirement via a path to a directory: the name of the - # package is not available yet so we create a temp directory - # Once run_egg_info will have run, we'll be able - # to fix it via _correct_build_location # Some systems have /tmp as a symlink which confuses custom # builds (such as numpy). Thus, we ensure that the real path # is returned. - self._temp_build_dir.create() - self._ideal_build_dir = build_dir + self._temp_build_dir = TempDirectory( + kind=tempdir_kinds.REQ_BUILD, globally_managed=True + ) return self._temp_build_dir.path - if self.editable: - name = self.name.lower() - else: - name = self.name + + # This is the only remaining place where we manually determine the path + # for the temporary directory. It is only needed for editables where + # it is the value of the --src option. + + # When parallel builds are enabled, add a UUID to the build directory + # name so multiple builds do not interfere with each other. + dir_name: str = canonicalize_name(self.name) + if parallel_builds: + dir_name = f"{dir_name}_{uuid.uuid4().hex}" + # FIXME: Is there a better place to create the build_dir? (hg and bzr # need this) if not os.path.exists(build_dir): logger.debug('Creating directory %s', build_dir) - _make_build_dir(build_dir) - return os.path.join(build_dir, name) + os.makedirs(build_dir) + actual_build_dir = os.path.join(build_dir, dir_name) + # `None` indicates that we respect the globally-configured deletion + # settings, which is what we actually want when auto-deleting. + delete_arg = None if autodelete else False + return TempDirectory( + path=actual_build_dir, + delete=delete_arg, + kind=tempdir_kinds.REQ_BUILD, + globally_managed=True, + ).path - def _correct_build_location(self): - # type: () -> None - """Move self._temp_build_dir to self._ideal_build_dir/self.req.name - - For some requirements (e.g. a path to a directory), the name of the - package is not available until we run egg_info, so the build_location - will return a temporary directory and store the _ideal_build_dir. - - This is only called by self.run_egg_info to fix the temporary build - directory. + def _set_requirement(self) -> None: + """Set requirement after generating metadata. """ - if self.source_dir is not None: - return - assert self.req is not None - assert self._temp_build_dir.path - assert (self._ideal_build_dir is not None and - self._ideal_build_dir.path) # type: ignore - old_location = self._temp_build_dir.path - self._temp_build_dir.path = None + assert self.req is None + assert self.metadata is not None + assert self.source_dir is not None - new_location = self.build_location(self._ideal_build_dir) - if os.path.exists(new_location): - raise InstallationError( - 'A package already exists in %s; please remove it to continue' - % display_path(new_location)) - logger.debug( - 'Moving package %s from %s to new location %s', - self, display_path(old_location), display_path(new_location), + # Construct a Requirement object from the generated metadata + if isinstance(parse_version(self.metadata["Version"]), Version): + op = "==" + else: + op = "===" + + self.req = Requirement( + "".join([ + self.metadata["Name"], + op, + self.metadata["Version"], + ]) ) - shutil.move(old_location, new_location) - self._temp_build_dir.path = new_location - self._ideal_build_dir = None - self.source_dir = os.path.normpath(os.path.abspath(new_location)) - self._egg_info_path = None - # Correct the metadata directory, if it exists - if self.metadata_directory: - old_meta = self.metadata_directory - rel = os.path.relpath(old_meta, start=old_location) - new_meta = os.path.join(new_location, rel) - new_meta = os.path.normpath(os.path.abspath(new_meta)) - self.metadata_directory = new_meta + def warn_on_mismatching_name(self) -> None: + metadata_name = canonicalize_name(self.metadata["Name"]) + if canonicalize_name(self.req.name) == metadata_name: + # Everything is fine. + return - def remove_temporary_source(self): - # type: () -> None - """Remove the source files from this requirement, if they are marked - for deletion""" - if self.source_dir and os.path.exists( - os.path.join(self.source_dir, PIP_DELETE_MARKER_FILENAME)): - logger.debug('Removing source in %s', self.source_dir) - rmtree(self.source_dir) - self.source_dir = None - self._temp_build_dir.cleanup() - self.build_env.cleanup() + # If we're here, there's a mismatch. Log a warning about it. + logger.warning( + 'Generating metadata for package %s ' + 'produced metadata for project name %s. Fix your ' + '#egg=%s fragments.', + self.name, metadata_name, self.name + ) + self.req = Requirement(metadata_name) - def check_if_exists(self, use_user_site): - # type: (bool) -> bool + def check_if_exists(self, use_user_site: bool) -> None: """Find an installed distribution that satisfies or conflicts with this requirement, and set self.satisfied_by or - self.conflicts_with appropriately. + self.should_reinstall appropriately. """ if self.req is None: - return False - try: - # get_distribution() will resolve the entire list of requirements - # anyway, and we've already determined that we need the requirement - # in question, so strip the marker so that we don't try to - # evaluate it. - no_marker = Requirement(str(self.req)) - no_marker.marker = None - self.satisfied_by = pkg_resources.get_distribution(str(no_marker)) - if self.editable and self.satisfied_by: - self.conflicts_with = self.satisfied_by - # when installing editables, nothing pre-existing should ever - # satisfy - self.satisfied_by = None - return True - except pkg_resources.DistributionNotFound: - return False - except pkg_resources.VersionConflict: - existing_dist = pkg_resources.get_distribution( - self.req.name - ) + return + existing_dist = get_distribution(self.req.name) + if not existing_dist: + return + + # pkg_resouces may contain a different copy of packaging.version from + # pip in if the downstream distributor does a poor job debundling pip. + # We avoid existing_dist.parsed_version and let SpecifierSet.contains + # parses the version instead. + existing_version = existing_dist.version + version_compatible = ( + existing_version is not None and + self.req.specifier.contains(existing_version, prereleases=True) + ) + if not version_compatible: + self.satisfied_by = None if use_user_site: if dist_in_usersite(existing_dist): - self.conflicts_with = existing_dist + self.should_reinstall = True elif (running_under_virtualenv() and dist_in_site_packages(existing_dist)): raise InstallationError( "Will not install to the user site because it will " - "lack sys.path precedence to %s in %s" % - (existing_dist.project_name, existing_dist.location) + "lack sys.path precedence to {} in {}".format( + existing_dist.project_name, existing_dist.location) ) else: - self.conflicts_with = existing_dist - return True + self.should_reinstall = True + else: + if self.editable: + self.should_reinstall = True + # when installing editables, nothing pre-existing should ever + # satisfy + self.satisfied_by = None + else: + self.satisfied_by = existing_dist # Things valid for wheels @property - def is_wheel(self): - # type: () -> bool + def is_wheel(self) -> bool: if not self.link: return False return self.link.is_wheel - def move_wheel_files( - self, - wheeldir, # type: str - root=None, # type: Optional[str] - home=None, # type: Optional[str] - prefix=None, # type: Optional[str] - warn_script_location=True, # type: bool - use_user_site=False, # type: bool - pycompile=True # type: bool - ): - # type: (...) -> None - move_wheel_files( - self.name, self.req, wheeldir, - user=use_user_site, - home=home, - root=root, - prefix=prefix, - pycompile=pycompile, - isolated=self.isolated, - warn_script_location=warn_script_location, - ) - # Things valid for sdists @property - def setup_py_dir(self): - # type: () -> str + def unpacked_source_directory(self) -> str: return os.path.join( self.source_dir, self.link and self.link.subdirectory_fragment or '') @property - def setup_py(self): - # type: () -> str - assert self.source_dir, "No source dir for %s" % self - - setup_py = os.path.join(self.setup_py_dir, 'setup.py') - - # Python2 __file__ should not be unicode - if six.PY2 and isinstance(setup_py, six.text_type): - setup_py = setup_py.encode(sys.getfilesystemencoding()) + def setup_py_path(self) -> str: + assert self.source_dir, f"No source dir for {self}" + setup_py = os.path.join(self.unpacked_source_directory, 'setup.py') return setup_py @property - def pyproject_toml(self): - # type: () -> str - assert self.source_dir, "No source dir for %s" % self + def pyproject_toml_path(self) -> str: + assert self.source_dir, f"No source dir for {self}" + return make_pyproject_path(self.unpacked_source_directory) - return make_pyproject_path(self.setup_py_dir) - - def load_pyproject_toml(self): - # type: () -> None + def load_pyproject_toml(self) -> None: """Load the pyproject.toml file. After calling this routine, all of the attributes related to PEP 517 @@ -482,40 +471,53 @@ class InstallRequirement(object): use_pep517 attribute can be used to determine whether we should follow the PEP 517 or legacy (setup.py) code path. """ - pep517_data = load_pyproject_toml( + pyproject_toml_data = load_pyproject_toml( self.use_pep517, - self.pyproject_toml, - self.setup_py, + self.pyproject_toml_path, + self.setup_py_path, str(self) ) - if pep517_data is None: + if pyproject_toml_data is None: self.use_pep517 = False - else: - self.use_pep517 = True - requires, backend, check = pep517_data - self.requirements_to_check = check - self.pyproject_requires = requires - self.pep517_backend = Pep517HookCaller(self.setup_py_dir, backend) + return - # Use a custom function to call subprocesses - self.spin_message = "" + self.use_pep517 = True + requires, backend, check, backend_path = pyproject_toml_data + self.requirements_to_check = check + self.pyproject_requires = requires + self.pep517_backend = Pep517HookCaller( + self.unpacked_source_directory, backend, backend_path=backend_path, + python_executable=os.getenv('PIP_PYTHON_PATH', sys.executable) + ) - def runner(cmd, cwd=None, extra_environ=None): - with open_spinner(self.spin_message) as spinner: - call_subprocess( - cmd, - cwd=cwd, - extra_environ=extra_environ, - show_stdout=False, - spinner=spinner - ) - self.spin_message = "" + def _generate_metadata(self) -> str: + """Invokes metadata generator functions, with the required arguments. + """ + if not self.use_pep517: + assert self.unpacked_source_directory - self.pep517_backend._subprocess_runner = runner + if not os.path.exists(self.setup_py_path): + raise InstallationError( + f'File "setup.py" not found for legacy project {self}.' + ) - def prepare_metadata(self): - # type: () -> None + return generate_metadata_legacy( + build_env=self.build_env, + setup_py_path=self.setup_py_path, + source_dir=self.unpacked_source_directory, + isolated=self.isolated, + details=self.name or f"from {self.link}" + ) + + assert self.pep517_backend is not None + + return generate_metadata( + build_env=self.build_env, + backend=self.pep517_backend, + ) + + def prepare_metadata(self) -> None: """Ensure that project metadata is available. Under PEP 517, call the backend hook to prepare the metadata. @@ -524,177 +526,27 @@ class InstallRequirement(object): assert self.source_dir with indent_log(): - if self.use_pep517: - self.prepare_pep517_metadata() - else: - self.run_egg_info() + self.metadata_directory = self._generate_metadata() - if not self.req: - if isinstance(parse_version(self.metadata["Version"]), Version): - op = "==" - else: - op = "===" - self.req = Requirement( - "".join([ - self.metadata["Name"], - op, - self.metadata["Version"], - ]) - ) - self._correct_build_location() + # Act on the newly generated metadata, based on the name and version. + if not self.name: + self._set_requirement() else: - metadata_name = canonicalize_name(self.metadata["Name"]) - if canonicalize_name(self.req.name) != metadata_name: - logger.warning( - 'Generating metadata for package %s ' - 'produced metadata for project name %s. Fix your ' - '#egg=%s fragments.', - self.name, metadata_name, self.name - ) - self.req = Requirement(metadata_name) + self.warn_on_mismatching_name() - def prepare_pep517_metadata(self): - # type: () -> None - assert self.pep517_backend is not None - - metadata_dir = os.path.join( - self.setup_py_dir, - 'pip-wheel-metadata' - ) - ensure_dir(metadata_dir) - - with self.build_env: - # Note that Pep517HookCaller implements a fallback for - # prepare_metadata_for_build_wheel, so we don't have to - # consider the possibility that this hook doesn't exist. - backend = self.pep517_backend - self.spin_message = "Preparing wheel metadata" - distinfo_dir = backend.prepare_metadata_for_build_wheel( - metadata_dir - ) - - self.metadata_directory = os.path.join(metadata_dir, distinfo_dir) - - def run_egg_info(self): - # type: () -> None - if self.name: - logger.debug( - 'Running setup.py (path:%s) egg_info for package %s', - self.setup_py, self.name, - ) - else: - logger.debug( - 'Running setup.py (path:%s) egg_info for package from %s', - self.setup_py, self.link, - ) - script = SETUPTOOLS_SHIM % self.setup_py - sys_executable = os.environ.get('PIP_PYTHON_PATH', sys.executable) - base_cmd = [sys_executable, '-c', script] - if self.isolated: - base_cmd += ["--no-user-cfg"] - egg_info_cmd = base_cmd + ['egg_info'] - # We can't put the .egg-info files at the root, because then the - # source code will be mistaken for an installed egg, causing - # problems - if self.editable: - egg_base_option = [] # type: List[str] - else: - egg_info_dir = os.path.join(self.setup_py_dir, 'pip-egg-info') - ensure_dir(egg_info_dir) - egg_base_option = ['--egg-base', 'pip-egg-info'] - with self.build_env: - call_subprocess( - egg_info_cmd + egg_base_option, - cwd=self.setup_py_dir, - show_stdout=False, - command_desc='python setup.py egg_info') + self.assert_source_matches_version() @property - def egg_info_path(self): - # type: () -> str - if self._egg_info_path is None: - if self.editable: - base = self.source_dir - else: - base = os.path.join(self.setup_py_dir, 'pip-egg-info') - filenames = os.listdir(base) - if self.editable: - filenames = [] - for root, dirs, files in os.walk(base): - for dir in vcs.dirnames: - if dir in dirs: - dirs.remove(dir) - # Iterate over a copy of ``dirs``, since mutating - # a list while iterating over it can cause trouble. - # (See https://github.com/pypa/pip/pull/462.) - for dir in list(dirs): - # Don't search in anything that looks like a virtualenv - # environment - if ( - os.path.lexists( - os.path.join(root, dir, 'bin', 'python') - ) or - os.path.exists( - os.path.join( - root, dir, 'Scripts', 'Python.exe' - ) - )): - dirs.remove(dir) - # Also don't search through tests - elif dir == 'test' or dir == 'tests': - dirs.remove(dir) - filenames.extend([os.path.join(root, dir) - for dir in dirs]) - filenames = [f for f in filenames if f.endswith('.egg-info')] - - if not filenames: - raise InstallationError( - "Files/directories not found in %s" % base - ) - # if we have more than one match, we pick the toplevel one. This - # can easily be the case if there is a dist folder which contains - # an extracted tarball for testing purposes. - if len(filenames) > 1: - filenames.sort( - key=lambda x: x.count(os.path.sep) + - (os.path.altsep and x.count(os.path.altsep) or 0) - ) - self._egg_info_path = os.path.join(base, filenames[0]) - return self._egg_info_path - - @property - def metadata(self): + def metadata(self) -> Any: if not hasattr(self, '_metadata'): self._metadata = get_metadata(self.get_dist()) return self._metadata - def get_dist(self): - # type: () -> Distribution - """Return a pkg_resources.Distribution for this requirement""" - if self.metadata_directory: - base_dir, distinfo = os.path.split(self.metadata_directory) - metadata = pkg_resources.PathMetadata( - base_dir, self.metadata_directory - ) - dist_name = os.path.splitext(distinfo)[0] - typ = pkg_resources.DistInfoDistribution - else: - egg_info = self.egg_info_path.rstrip(os.path.sep) - base_dir = os.path.dirname(egg_info) - metadata = pkg_resources.PathMetadata(base_dir, egg_info) - dist_name = os.path.splitext(os.path.basename(egg_info))[0] - # https://github.com/python/mypy/issues/1174 - typ = pkg_resources.Distribution # type: ignore + def get_dist(self) -> Distribution: + return _get_dist(self.metadata_directory) - return typ( - base_dir, - project_name=dist_name, - metadata=metadata, - ) - - def assert_source_matches_version(self): - # type: () -> None + def assert_source_matches_version(self) -> None: assert self.source_dir version = self.metadata['version'] if self.req.specifier and version not in self.req.specifier: @@ -712,8 +564,12 @@ class InstallRequirement(object): ) # For both source distributions and editables - def ensure_has_source_dir(self, parent_dir): - # type: (str) -> str + def ensure_has_source_dir( + self, + parent_dir: str, + autodelete: bool = False, + parallel_builds: bool = False, + ) -> None: """Ensure that a source_dir is set. This will create a temporary build dir if the name of the requirement @@ -724,48 +580,14 @@ class InstallRequirement(object): :return: self.source_dir """ if self.source_dir is None: - self.source_dir = self.build_location(parent_dir) - return self.source_dir + self.source_dir = self.ensure_build_location( + parent_dir, + autodelete=autodelete, + parallel_builds=parallel_builds, + ) # For editable installations - def install_editable( - self, - install_options, # type: List[str] - global_options=(), # type: Sequence[str] - prefix=None # type: Optional[str] - ): - # type: (...) -> None - logger.info('Running setup.py develop for %s', self.name) - - if self.isolated: - global_options = list(global_options) + ["--no-user-cfg"] - - if prefix: - prefix_param = ['--prefix={}'.format(prefix)] - install_options = list(install_options) + prefix_param - - with indent_log(): - # FIXME: should we do --install-headers here too? - with self.build_env: - sys_executable = os.environ.get('PIP_PYTHON_PATH', sys.executable) - call_subprocess( - [ - sys_executable, - '-c', - SETUPTOOLS_SHIM % self.setup_py - ] + - list(global_options) + - ['develop', '--no-deps'] + - list(install_options), - - cwd=self.setup_py_dir, - show_stdout=False, - ) - - self.install_succeeded = True - - def update_editable(self, obtain=True): - # type: (bool) -> None + def update_editable(self) -> None: if not self.link: logger.debug( "Cannot update repository at %s; repository location is " @@ -778,26 +600,17 @@ class InstallRequirement(object): if self.link.scheme == 'file': # Static paths don't get updated return - assert '+' in self.link.url, "bad url: %r" % self.link.url - if not self.update: - return - vc_type, url = self.link.url.split('+', 1) - backend = vcs.get_backend(vc_type) - if backend: - vcs_backend = backend(self.link.url) - if obtain: - vcs_backend.obtain(self.source_dir) - else: - vcs_backend.export(self.source_dir) - else: - assert 0, ( - 'Unexpected version control type (in %s): %s' - % (self.link, vc_type)) + vcs_backend = vcs.get_backend_for_scheme(self.link.scheme) + # Editable requirements are validated in Requirement constructors. + # So here, if it's neither a path nor a valid VCS URL, it's a bug. + assert vcs_backend, f"Unsupported VCS URL {self.link.url}" + hidden_url = hide_url(self.link.url) + vcs_backend.obtain(self.source_dir, url=hidden_url) # Top-level Actions - def uninstall(self, auto_confirm=False, verbose=False, - use_user_site=False): - # type: (bool, bool, bool) -> Optional[UninstallPathSet] + def uninstall( + self, auto_confirm: bool = False, verbose: bool = False + ) -> Optional[UninstallPathSet]: """ Uninstall the distribution currently satisfying this requirement. @@ -810,41 +623,50 @@ class InstallRequirement(object): linked to global site-packages. """ - if not self.check_if_exists(use_user_site): + assert self.req + dist = get_distribution(self.req.name) + if not dist: logger.warning("Skipping %s as it is not installed.", self.name) return None - dist = self.satisfied_by or self.conflicts_with + logger.info('Found existing installation: %s', dist) uninstalled_pathset = UninstallPathSet.from_dist(dist) uninstalled_pathset.remove(auto_confirm, verbose) return uninstalled_pathset - def _clean_zip_name(self, name, prefix): # only used by archive. - assert name.startswith(prefix + os.path.sep), ( - "name %r doesn't start with prefix %r" % (name, prefix) - ) - name = name[len(prefix) + 1:] - name = name.replace(os.path.sep, '/') - return name + def _get_archive_name(self, path: str, parentdir: str, rootdir: str) -> str: + + def _clean_zip_name(name: str, prefix: str) -> str: + assert name.startswith(prefix + os.path.sep), ( + f"name {name!r} doesn't start with prefix {prefix!r}" + ) + name = name[len(prefix) + 1:] + name = name.replace(os.path.sep, '/') + return name - def _get_archive_name(self, path, parentdir, rootdir): - # type: (str, str, str) -> str path = os.path.join(parentdir, path) - name = self._clean_zip_name(path, rootdir) + name = _clean_zip_name(path, rootdir) return self.name + '/' + name - # TODO: Investigate if this should be kept in InstallRequirement - # Seems to be used only when VCS + downloads - def archive(self, build_dir): - # type: (str) -> None + def archive(self, build_dir: Optional[str]) -> None: + """Saves archive to provided build_dir. + + Used for saving downloaded VCS requirements as part of `pip download`. + """ assert self.source_dir + if build_dir is None: + return + create_archive = True - archive_name = '%s-%s.zip' % (self.name, self.metadata["version"]) + archive_name = '{}-{}.zip'.format(self.name, self.metadata["version"]) archive_path = os.path.join(build_dir, archive_name) + if os.path.exists(archive_path): response = ask_path_exists( - 'The file %s exists. (i)gnore, (w)ipe, (b)ackup, (a)bort ' % - display_path(archive_path), ('i', 'w', 'b', 'a')) + 'The file {} exists. (i)gnore, (w)ipe, ' + '(b)ackup, (a)bort '.format( + display_path(archive_path)), + ('i', 'w', 'b', 'a')) if response == 'i': create_archive = False elif response == 'w': @@ -860,165 +682,168 @@ class InstallRequirement(object): shutil.move(archive_path, dest_file) elif response == 'a': sys.exit(-1) - if create_archive: - zip = zipfile.ZipFile( - archive_path, 'w', zipfile.ZIP_DEFLATED, - allowZip64=True + + if not create_archive: + return + + zip_output = zipfile.ZipFile( + archive_path, 'w', zipfile.ZIP_DEFLATED, allowZip64=True, + ) + with zip_output: + dir = os.path.normcase( + os.path.abspath(self.unpacked_source_directory) ) - dir = os.path.normcase(os.path.abspath(self.setup_py_dir)) for dirpath, dirnames, filenames in os.walk(dir): - if 'pip-egg-info' in dirnames: - dirnames.remove('pip-egg-info') for dirname in dirnames: - dir_arcname = self._get_archive_name(dirname, - parentdir=dirpath, - rootdir=dir) + dir_arcname = self._get_archive_name( + dirname, parentdir=dirpath, rootdir=dir, + ) zipdir = zipfile.ZipInfo(dir_arcname + '/') zipdir.external_attr = 0x1ED << 16 # 0o755 - zip.writestr(zipdir, '') + zip_output.writestr(zipdir, '') for filename in filenames: - if filename == PIP_DELETE_MARKER_FILENAME: - continue - file_arcname = self._get_archive_name(filename, - parentdir=dirpath, - rootdir=dir) + file_arcname = self._get_archive_name( + filename, parentdir=dirpath, rootdir=dir, + ) filename = os.path.join(dirpath, filename) - zip.write(filename, file_arcname) - zip.close() - logger.info('Saved %s', display_path(archive_path)) + zip_output.write(filename, file_arcname) + + logger.info('Saved %s', display_path(archive_path)) def install( self, - install_options, # type: List[str] - global_options=None, # type: Optional[Sequence[str]] - root=None, # type: Optional[str] - home=None, # type: Optional[str] - prefix=None, # type: Optional[str] - warn_script_location=True, # type: bool - use_user_site=False, # type: bool - pycompile=True # type: bool - ): - # type: (...) -> None + install_options: List[str], + global_options: Optional[Sequence[str]] = None, + root: Optional[str] = None, + home: Optional[str] = None, + prefix: Optional[str] = None, + warn_script_location: bool = True, + use_user_site: bool = False, + pycompile: bool = True + ) -> None: + scheme = get_scheme( + self.name, + user=use_user_site, + home=home, + root=root, + isolated=self.isolated, + prefix=prefix, + ) + global_options = global_options if global_options is not None else [] if self.editable: - self.install_editable( - install_options, global_options, prefix=prefix, - ) - return - if self.is_wheel: - version = wheel.wheel_version(self.source_dir) - wheel.check_compatibility(version, self.name) - - self.move_wheel_files( - self.source_dir, root=root, prefix=prefix, home=home, - warn_script_location=warn_script_location, - use_user_site=use_user_site, pycompile=pycompile, + install_editable_legacy( + install_options, + global_options, + prefix=prefix, + home=home, + use_user_site=use_user_site, + name=self.name, + setup_py_path=self.setup_py_path, + isolated=self.isolated, + build_env=self.build_env, + unpacked_source_directory=self.unpacked_source_directory, ) self.install_succeeded = True return + if self.is_wheel: + assert self.local_file_path + direct_url = None + if self.original_link: + direct_url = direct_url_from_link( + self.original_link, + self.source_dir, + self.original_link_is_in_wheel_cache, + ) + install_wheel( + self.name, + self.local_file_path, + scheme=scheme, + req_description=str(self.req), + pycompile=pycompile, + warn_script_location=warn_script_location, + direct_url=direct_url, + requested=self.user_supplied, + ) + self.install_succeeded = True + return + + # TODO: Why don't we do this for editable installs? + # Extend the list of global and install options passed on to # the setup.py call with the ones from the requirements file. # Options specified in requirements file override those # specified on the command line, since the last option given # to setup.py is the one that is used. - global_options = list(global_options) + \ - self.options.get('global_options', []) - install_options = list(install_options) + \ - self.options.get('install_options', []) + global_options = list(global_options) + self.global_options + install_options = list(install_options) + self.install_options - if self.isolated: - # https://github.com/python/mypy/issues/1174 - global_options = global_options + ["--no-user-cfg"] # type: ignore - - with TempDirectory(kind="record") as temp_dir: - record_filename = os.path.join(temp_dir.path, 'install-record.txt') - install_args = self.get_install_args( - global_options, record_filename, root, prefix, pycompile, + try: + success = install_legacy( + install_options=install_options, + global_options=global_options, + root=root, + home=home, + prefix=prefix, + use_user_site=use_user_site, + pycompile=pycompile, + scheme=scheme, + setup_py_path=self.setup_py_path, + isolated=self.isolated, + req_name=self.name, + build_env=self.build_env, + unpacked_source_directory=self.unpacked_source_directory, + req_description=str(self.req), ) - msg = 'Running setup.py install for %s' % (self.name,) - with open_spinner(msg) as spinner: - with indent_log(): - with self.build_env: - call_subprocess( - install_args + install_options, - cwd=self.setup_py_dir, - show_stdout=False, - spinner=spinner, - ) - - if not os.path.exists(record_filename): - logger.debug('Record file %s not found', record_filename) - return + except LegacyInstallFailure as exc: + self.install_succeeded = False + six.reraise(*exc.parent) + except Exception: self.install_succeeded = True + raise - def prepend_root(path): - if root is None or not os.path.isabs(path): - return path - else: - return change_root(root, path) + self.install_succeeded = success - with open(record_filename) as f: - for line in f: - directory = os.path.dirname(line) - if directory.endswith('.egg-info'): - egg_info_dir = prepend_root(directory) - break - else: - logger.warning( - 'Could not find .egg-info directory in install record' - ' for %s', - self, - ) - # FIXME: put the record somewhere - # FIXME: should this be an error? - return - new_lines = [] - with open(record_filename) as f: - for line in f: - filename = line.strip() - if os.path.isdir(filename): - filename += os.path.sep - new_lines.append( - os.path.relpath(prepend_root(filename), egg_info_dir) - ) - new_lines.sort() - ensure_dir(egg_info_dir) - inst_files_path = os.path.join(egg_info_dir, 'installed-files.txt') - with open(inst_files_path, 'w') as f: - f.write('\n'.join(new_lines) + '\n') + if success and self.legacy_install_reason == 8368: + deprecated( + reason=( + "{} was installed using the legacy 'setup.py install' " + "method, because a wheel could not be built for it.". + format(self.name) + ), + replacement="to fix the wheel build issue reported above", + gone_in=None, + issue=8368, + ) - def get_install_args( - self, - global_options, # type: Sequence[str] - record_filename, # type: str - root, # type: Optional[str] - prefix, # type: Optional[str] - pycompile # type: bool - ): - # type: (...) -> List[str] - sys_executable = os.environ.get('PIP_PYTHON_PATH', sys.executable) - install_args = [sys_executable, "-u"] - install_args.append('-c') - install_args.append(SETUPTOOLS_SHIM % self.setup_py) - install_args += list(global_options) + \ - ['install', '--record', record_filename] - install_args += ['--single-version-externally-managed'] - if root is not None: - install_args += ['--root', root] - if prefix is not None: - install_args += ['--prefix', prefix] +def check_invalid_constraint_type(req: InstallRequirement) -> str: - if pycompile: - install_args += ["--compile"] - else: - install_args += ["--no-compile"] + # Check for unsupported forms + problem = "" + if not req.name: + problem = "Unnamed requirements are not allowed as constraints" + elif req.editable: + problem = "Editable requirements are not allowed as constraints" + elif req.extras: + problem = "Constraints cannot have extras" - if running_under_virtualenv(): - py_ver_str = 'python' + sysconfig.get_python_version() - install_args += ['--install-headers', - os.path.join(sys.prefix, 'include', 'site', - py_ver_str, self.name)] + if problem: + deprecated( + reason=( + "Constraints are only allowed to take the form of a package " + "name and a version specifier. Other forms were originally " + "permitted as an accident of the implementation, but were " + "undocumented. The new implementation of the resolver no " + "longer supports these forms." + ), + replacement=( + "replacing the constraint with a requirement." + ), + # No plan yet for when the new resolver becomes default + gone_in=None, + issue=8210 + ) - return install_args + return problem diff --git a/pipenv/patched/notpip/_internal/req/req_set.py b/pipenv/patched/notpip/_internal/req/req_set.py index e7da5b71..a9cde800 100644 --- a/pipenv/patched/notpip/_internal/req/req_set.py +++ b/pipenv/patched/notpip/_internal/req/req_set.py @@ -1,61 +1,64 @@ -from __future__ import absolute_import - import logging from collections import OrderedDict +from typing import Dict, Iterable, List, Optional, Tuple + +from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name from pipenv.patched.notpip._internal.exceptions import InstallationError -from pipenv.patched.notpip._internal.utils.logging import indent_log -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING -from pipenv.patched.notpip._internal.wheel import Wheel - -if MYPY_CHECK_RUNNING: - from typing import Optional, List, Tuple, Dict, Iterable # noqa: F401 - from pipenv.patched.notpip._internal.req.req_install import InstallRequirement # noqa: F401 - +from pipenv.patched.notpip._internal.models.wheel import Wheel +from pipenv.patched.notpip._internal.req.req_install import InstallRequirement +from pipenv.patched.notpip._internal.utils import compatibility_tags logger = logging.getLogger(__name__) -class RequirementSet(object): +class RequirementSet: - def __init__(self, require_hashes=False, check_supported_wheels=True, ignore_compatibility=True): - # type: (bool, bool) -> None + def __init__(self, check_supported_wheels: bool = True) -> None: """Create a RequirementSet. """ - self.requirements = OrderedDict() # type: Dict[str, InstallRequirement] # noqa: E501 - self.require_hashes = require_hashes + self.requirements: Dict[str, InstallRequirement] = OrderedDict() self.check_supported_wheels = check_supported_wheels - if ignore_compatibility: - self.check_supported_wheels = False - self.ignore_compatibility = (check_supported_wheels is False or ignore_compatibility is True) - # Mapping of alias: real_name - self.requirement_aliases = {} # type: Dict[str, str] - self.unnamed_requirements = [] # type: List[InstallRequirement] - self.successfully_downloaded = [] # type: List[InstallRequirement] - self.reqs_to_cleanup = [] # type: List[InstallRequirement] + self.unnamed_requirements: List[InstallRequirement] = [] - def __str__(self): - reqs = [req for req in self.requirements.values() - if not req.comes_from] - reqs.sort(key=lambda req: req.name.lower()) - return ' '.join([str(req.req) for req in reqs]) + def __str__(self) -> str: + requirements = sorted( + (req for req in self.requirements.values() if not req.comes_from), + key=lambda req: canonicalize_name(req.name or ""), + ) + return ' '.join(str(req.req) for req in requirements) - def __repr__(self): - reqs = [req for req in self.requirements.values()] - reqs.sort(key=lambda req: req.name.lower()) - reqs_str = ', '.join([str(req.req) for req in reqs]) - return ('<%s object; %d requirement(s): %s>' - % (self.__class__.__name__, len(reqs), reqs_str)) + def __repr__(self) -> str: + requirements = sorted( + self.requirements.values(), + key=lambda req: canonicalize_name(req.name or ""), + ) + + format_string = '<{classname} object; {count} requirement(s): {reqs}>' + return format_string.format( + classname=self.__class__.__name__, + count=len(requirements), + reqs=', '.join(str(req.req) for req in requirements), + ) + + def add_unnamed_requirement(self, install_req: InstallRequirement) -> None: + assert not install_req.name + self.unnamed_requirements.append(install_req) + + def add_named_requirement(self, install_req: InstallRequirement) -> None: + assert install_req.name + + project_name = canonicalize_name(install_req.name) + self.requirements[project_name] = install_req def add_requirement( self, - install_req, # type: InstallRequirement - parent_req_name=None, # type: Optional[str] - extras_requested=None # type: Optional[Iterable[str]] - ): - # type: (...) -> Tuple[List[InstallRequirement], Optional[InstallRequirement]] # noqa: E501 + install_req: InstallRequirement, + parent_req_name: Optional[str] = None, + extras_requested: Optional[Iterable[str]] = None + ) -> Tuple[List[InstallRequirement], Optional[InstallRequirement]]: """Add install_req as a requirement to install. :param parent_req_name: The name of the requirement that needed this @@ -70,13 +73,11 @@ class RequirementSet(object): the requirement is not applicable, or [install_req] if the requirement is applicable and has just been added. """ - name = install_req.name - # If the markers do not match, ignore this requirement. if not install_req.match_markers(extras_requested): logger.info( "Ignoring %s: markers '%s' don't match your environment", - name, install_req.markers, + install_req.name, install_req.markers, ) return [], None @@ -86,27 +87,27 @@ class RequirementSet(object): # single requirements file. if install_req.link and install_req.link.is_wheel: wheel = Wheel(install_req.link.filename) - if self.check_supported_wheels and not wheel.supported(): + tags = compatibility_tags.get_supported() + if (self.check_supported_wheels and not wheel.supported(tags)): raise InstallationError( - "%s is not a supported wheel on this platform." % - wheel.filename + "{} is not a supported wheel on this platform.".format( + wheel.filename) ) # This next bit is really a sanity check. - assert install_req.is_direct == (parent_req_name is None), ( - "a direct req shouldn't have a parent and also, " - "a non direct req should have a parent" + assert not install_req.user_supplied or parent_req_name is None, ( + "a user supplied req shouldn't have a parent" ) # Unnamed requirements are scanned again and the requirement won't be # added as a dependency until after scanning. - if not name: - # url or path requirement w/o an egg fragment - self.unnamed_requirements.append(install_req) + if not install_req.name: + self.add_unnamed_requirement(install_req) return [install_req], None try: - existing_req = self.get_requirement(name) + existing_req: Optional[InstallRequirement] = self.get_requirement( + install_req.name) except KeyError: existing_req = None @@ -115,22 +116,21 @@ class RequirementSet(object): existing_req and not existing_req.constraint and existing_req.extras == install_req.extras and + existing_req.req and + install_req.req and existing_req.req.specifier != install_req.req.specifier ) if has_conflicting_requirement: raise InstallationError( - "Double requirement given: %s (already in %s, name=%r)" - % (install_req, existing_req, name) + "Double requirement given: {} (already in {}, name={!r})" + .format(install_req, existing_req, install_req.name) ) # When no existing requirement exists, add the requirement as a # dependency and it will be scanned again after. if not existing_req: - self.requirements[name] = install_req - # FIXME: what about other normalizations? E.g., _ vs. -? - if name.lower() != name: - self.requirement_aliases[name.lower()] = name - # We'd want to rescan this requirements later + self.add_named_requirement(install_req) + # We'd want to rescan this requirement later return [install_req], install_req # Assume there's no need to scan, and that we've already @@ -146,15 +146,18 @@ class RequirementSet(object): ) ) if does_not_satisfy_constraint: - self.reqs_to_cleanup.append(install_req) raise InstallationError( - "Could not satisfy constraints for '%s': " + "Could not satisfy constraints for '{}': " "installation from path or url cannot be " - "constrained to a version" % name, + "constrained to a version".format(install_req.name) ) # If we're now installing a constraint, mark the existing # object for real installation. existing_req.constraint = False + # If we're now installing a user supplied requirement, + # mark the existing object as such. + if install_req.user_supplied: + existing_req.user_supplied = True existing_req.extras = tuple(sorted( set(existing_req.extras) | set(install_req.extras) )) @@ -166,35 +169,22 @@ class RequirementSet(object): # scanning again. return [existing_req], existing_req - def has_requirement(self, project_name): - # type: (str) -> bool - name = project_name.lower() - if (name in self.requirements and - not self.requirements[name].constraint or - name in self.requirement_aliases and - not self.requirements[self.requirement_aliases[name]].constraint): - return True - return False + def has_requirement(self, name: str) -> bool: + project_name = canonicalize_name(name) + + return ( + project_name in self.requirements and + not self.requirements[project_name].constraint + ) + + def get_requirement(self, name: str) -> InstallRequirement: + project_name = canonicalize_name(name) + + if project_name in self.requirements: + return self.requirements[project_name] + + raise KeyError(f"No project with the name {name!r}") @property - def has_requirements(self): - # type: () -> List[InstallRequirement] - return list(req for req in self.requirements.values() if not - req.constraint) or self.unnamed_requirements - - def get_requirement(self, project_name): - # type: (str) -> InstallRequirement - for name in project_name, project_name.lower(): - if name in self.requirements: - return self.requirements[name] - if name in self.requirement_aliases: - return self.requirements[self.requirement_aliases[name]] - pass - - def cleanup_files(self): - # type: () -> None - """Clean up files, remove builds.""" - logger.debug('Cleaning up...') - with indent_log(): - for req in self.reqs_to_cleanup: - req.remove_temporary_source() + def all_requirements(self) -> List[InstallRequirement]: + return self.unnamed_requirements + list(self.requirements.values()) diff --git a/pipenv/patched/notpip/_internal/req/req_tracker.py b/pipenv/patched/notpip/_internal/req/req_tracker.py index d17a4187..cee440a4 100644 --- a/pipenv/patched/notpip/_internal/req/req_tracker.py +++ b/pipenv/patched/notpip/_internal/req/req_tracker.py @@ -1,88 +1,130 @@ -from __future__ import absolute_import - import contextlib -import errno import hashlib import logging import os +from types import TracebackType +from typing import Dict, Iterator, Optional, Set, Type, Union +from pipenv.patched.notpip._internal.models.link import Link +from pipenv.patched.notpip._internal.req.req_install import InstallRequirement from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING - -if MYPY_CHECK_RUNNING: - from typing import Set, Iterator # noqa: F401 - from pipenv.patched.notpip._internal.req.req_install import InstallRequirement # noqa: F401 - from pipenv.patched.notpip._internal.models.link import Link # noqa: F401 logger = logging.getLogger(__name__) -class RequirementTracker(object): +@contextlib.contextmanager +def update_env_context_manager(**changes: str) -> Iterator[None]: + target = os.environ - def __init__(self): - # type: () -> None - self._root = os.environ.get('PIP_REQ_TRACKER') - if self._root is None: - self._temp_dir = TempDirectory(delete=False, kind='req-tracker') - self._temp_dir.create() - self._root = os.environ['PIP_REQ_TRACKER'] = self._temp_dir.path - logger.debug('Created requirements tracker %r', self._root) - else: - self._temp_dir = None - logger.debug('Re-using requirements tracker %r', self._root) - self._entries = set() # type: Set[InstallRequirement] + # Save values from the target and change them. + non_existent_marker = object() + saved_values: Dict[str, Union[object, str]] = {} + for name, new_value in changes.items(): + try: + saved_values[name] = target[name] + except KeyError: + saved_values[name] = non_existent_marker + target[name] = new_value - def __enter__(self): + try: + yield + finally: + # Restore original values in the target. + for name, original_value in saved_values.items(): + if original_value is non_existent_marker: + del target[name] + else: + assert isinstance(original_value, str) # for mypy + target[name] = original_value + + +@contextlib.contextmanager +def get_requirement_tracker() -> Iterator["RequirementTracker"]: + root = os.environ.get('PIP_REQ_TRACKER') + with contextlib.ExitStack() as ctx: + if root is None: + root = ctx.enter_context( + TempDirectory(kind='req-tracker') + ).path + ctx.enter_context(update_env_context_manager(PIP_REQ_TRACKER=root)) + logger.debug("Initialized build tracking at %s", root) + + with RequirementTracker(root) as tracker: + yield tracker + + +class RequirementTracker: + + def __init__(self, root: str) -> None: + self._root = root + self._entries: Set[InstallRequirement] = set() + logger.debug("Created build tracker: %s", self._root) + + def __enter__(self) -> "RequirementTracker": + logger.debug("Entered build tracker: %s", self._root) return self - def __exit__(self, exc_type, exc_val, exc_tb): + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType] + ) -> None: self.cleanup() - def _entry_path(self, link): - # type: (Link) -> str + def _entry_path(self, link: Link) -> str: hashed = hashlib.sha224(link.url_without_fragment.encode()).hexdigest() return os.path.join(self._root, hashed) - def add(self, req): - # type: (InstallRequirement) -> None - link = req.link - info = str(req) - entry_path = self._entry_path(link) + def add(self, req: InstallRequirement) -> None: + """Add an InstallRequirement to build tracking. + """ + + assert req.link + # Get the file to write information about this requirement. + entry_path = self._entry_path(req.link) + + # Try reading from the file. If it exists and can be read from, a build + # is already in progress, so a LookupError is raised. try: with open(entry_path) as fp: - # Error, these's already a build in progress. - raise LookupError('%s is already being built: %s' - % (link, fp.read())) - except IOError as e: - if e.errno != errno.ENOENT: - raise - assert req not in self._entries - with open(entry_path, 'w') as fp: - fp.write(info) - self._entries.add(req) - logger.debug('Added %s to build tracker %r', req, self._root) + contents = fp.read() + except FileNotFoundError: + pass + else: + message = '{} is already being built: {}'.format( + req.link, contents) + raise LookupError(message) - def remove(self, req): - # type: (InstallRequirement) -> None - link = req.link + # If we're here, req should really not be building already. + assert req not in self._entries + + # Start tracking this requirement. + with open(entry_path, 'w', encoding="utf-8") as fp: + fp.write(str(req)) + self._entries.add(req) + + logger.debug('Added %s to build tracker %r', req, self._root) + + def remove(self, req: InstallRequirement) -> None: + """Remove an InstallRequirement from build tracking. + """ + + assert req.link + # Delete the created file and the corresponding entries. + os.unlink(self._entry_path(req.link)) self._entries.remove(req) - os.unlink(self._entry_path(link)) + logger.debug('Removed %s from build tracker %r', req, self._root) - def cleanup(self): - # type: () -> None + def cleanup(self) -> None: for req in set(self._entries): self.remove(req) - remove = self._temp_dir is not None - if remove: - self._temp_dir.cleanup() - logger.debug('%s build tracker %r', - 'Removed' if remove else 'Cleaned', - self._root) + + logger.debug("Removed build tracker: %r", self._root) @contextlib.contextmanager - def track(self, req): - # type: (InstallRequirement) -> Iterator[None] + def track(self, req: InstallRequirement) -> Iterator[None]: self.add(req) yield self.remove(req) diff --git a/pipenv/patched/notpip/_internal/req/req_uninstall.py b/pipenv/patched/notpip/_internal/req/req_uninstall.py index ce80e6dc..22ce3068 100644 --- a/pipenv/patched/notpip/_internal/req/req_uninstall.py +++ b/pipenv/patched/notpip/_internal/req/req_uninstall.py @@ -1,36 +1,42 @@ -from __future__ import absolute_import - import csv import functools -import logging import os import sys import sysconfig +from importlib.util import cache_from_source +from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Set, Tuple from pipenv.patched.notpip._vendor import pkg_resources +from pipenv.patched.notpip._vendor.pkg_resources import Distribution from pipenv.patched.notpip._internal.exceptions import UninstallationError -from pipenv.patched.notpip._internal.locations import bin_py, bin_user -from pipenv.patched.notpip._internal.utils.compat import WINDOWS, cache_from_source, uses_pycache -from pipenv.patched.notpip._internal.utils.logging import indent_log +from pipenv.patched.notpip._internal.locations import get_bin_prefix, get_bin_user +from pipenv.patched.notpip._internal.utils.compat import WINDOWS +from pipenv.patched.notpip._internal.utils.logging import getLogger, indent_log from pipenv.patched.notpip._internal.utils.misc import ( - FakeFile, ask, dist_in_usersite, dist_is_local, egg_link_path, is_local, - normalize_path, renames, rmtree, + ask, + dist_in_usersite, + dist_is_local, + egg_link_path, + is_local, + normalize_path, + renames, + rmtree, ) from pipenv.patched.notpip._internal.utils.temp_dir import AdjacentTempDirectory, TempDirectory -logger = logging.getLogger(__name__) +logger = getLogger(__name__) -def _script_names(dist, script_name, is_gui): +def _script_names(dist: Distribution, script_name: str, is_gui: bool) -> List[str]: """Create the fully qualified name of the files created by {console,gui}_scripts for the given ``dist``. Returns the list of file names """ if dist_in_usersite(dist): - bin_dir = bin_user + bin_dir = get_bin_user() else: - bin_dir = bin_py + bin_dir = get_bin_prefix() exe_name = os.path.join(bin_dir, script_name) paths_to_remove = [exe_name] if WINDOWS: @@ -43,10 +49,10 @@ def _script_names(dist, script_name, is_gui): return paths_to_remove -def _unique(fn): +def _unique(fn: Callable[..., Iterator[Any]]) -> Callable[..., Iterator[Any]]: @functools.wraps(fn) - def unique(*args, **kw): - seen = set() + def unique(*args: Any, **kw: Any) -> Iterator[Any]: + seen: Set[Any] = set() for item in fn(*args, **kw): if item not in seen: seen.add(item) @@ -55,7 +61,7 @@ def _unique(fn): @_unique -def uninstallation_paths(dist): +def uninstallation_paths(dist: Distribution) -> Iterator[str]: """ Yield all the uninstallation paths for dist based on RECORD-without-.py[co] @@ -63,8 +69,27 @@ def uninstallation_paths(dist): the .pyc and .pyo in the same directory. UninstallPathSet.add() takes care of the __pycache__ .py[co]. + + If RECORD is not found, raises UninstallationError, + with possible information from the INSTALLER file. + + https://packaging.python.org/specifications/recording-installed-packages/ """ - r = csv.reader(FakeFile(dist.get_metadata_lines('RECORD'))) + try: + r = csv.reader(dist.get_metadata_lines('RECORD')) + except FileNotFoundError as missing_record_exception: + msg = 'Cannot uninstall {dist}, RECORD file not found.'.format(dist=dist) + try: + installer = next(dist.get_metadata_lines('INSTALLER')) + if not installer or installer == 'pip': + raise ValueError() + except (OSError, StopIteration, ValueError): + dep = '{}=={}'.format(dist.project_name, dist.version) + msg += (" You might be able to recover from this via: " + "'pip install --force-reinstall --no-deps {}'.".format(dep)) + else: + msg += ' Hint: The package was installed by {}.'.format(installer) + raise UninstallationError(msg) from missing_record_exception for row in r: path = os.path.join(dist.location, row[0]) yield path @@ -77,14 +102,14 @@ def uninstallation_paths(dist): yield path -def compact(paths): +def compact(paths: Iterable[str]) -> Set[str]: """Compact a path set to contain the minimal number of paths necessary to contain all paths in the set. If /a/path/ and /a/path/to/a/file.txt are both in the set, leave only the shorter path.""" sep = os.path.sep - short_paths = set() + short_paths: Set[str] = set() for path in sorted(paths, key=len): should_skip = any( path.startswith(shortpath.rstrip("*")) and @@ -96,19 +121,18 @@ def compact(paths): return short_paths -def compress_for_rename(paths): +def compress_for_rename(paths: Iterable[str]) -> Set[str]: """Returns a set containing the paths that need to be renamed. This set may include directories when the original sequence of paths included every file on disk. """ - case_map = dict((os.path.normcase(p), p) for p in paths) + case_map = {os.path.normcase(p): p for p in paths} remaining = set(case_map) - unchecked = sorted(set(os.path.split(p)[0] - for p in case_map.values()), key=len) - wildcards = set() + unchecked = sorted({os.path.split(p)[0] for p in case_map.values()}, key=len) + wildcards: Set[str] = set() - def norm_join(*a): + def norm_join(*a: str) -> str: return os.path.normcase(os.path.join(*a)) for root in unchecked: @@ -117,8 +141,8 @@ def compress_for_rename(paths): # This directory has already been handled. continue - all_files = set() - all_subdirs = set() + all_files: Set[str] = set() + all_subdirs: Set[str] = set() for dirname, subdirs, files in os.walk(root): all_subdirs.update(norm_join(root, dirname, d) for d in subdirs) @@ -134,7 +158,7 @@ def compress_for_rename(paths): return set(map(case_map.__getitem__, remaining)) | wildcards -def compress_for_output_listing(paths): +def compress_for_output_listing(paths: Iterable[str]) -> Tuple[Set[str], Set[str]]: """Returns a tuple of 2 sets of which paths to display to user The first set contains paths that would be deleted. Files of a package @@ -145,7 +169,7 @@ def compress_for_output_listing(paths): folders. """ - will_remove = list(paths) + will_remove = set(paths) will_skip = set() # Determine folders and files @@ -158,7 +182,8 @@ def compress_for_output_listing(paths): folders.add(os.path.dirname(path)) files.add(path) - _normcased_files = set(map(os.path.normcase, files)) + # probably this one https://github.com/python/mypy/issues/390 + _normcased_files = set(map(os.path.normcase, files)) # type: ignore folders = compact(folders) @@ -183,34 +208,32 @@ def compress_for_output_listing(paths): return will_remove, will_skip -class StashedUninstallPathSet(object): +class StashedUninstallPathSet: """A set of file rename operations to stash files while tentatively uninstalling them.""" - def __init__(self): + def __init__(self) -> None: # Mapping from source file root to [Adjacent]TempDirectory # for files under that directory. - self._save_dirs = {} + self._save_dirs: Dict[str, TempDirectory] = {} # (old path, new path) tuples for each move that may need # to be undone. - self._moves = [] + self._moves: List[Tuple[str, str]] = [] - def _get_directory_stash(self, path): + def _get_directory_stash(self, path: str) -> str: """Stashes a directory. Directories are stashed adjacent to their original location if possible, or else moved/copied into the user's temp dir.""" try: - save_dir = AdjacentTempDirectory(path) - save_dir.create() + save_dir: TempDirectory = AdjacentTempDirectory(path) except OSError: save_dir = TempDirectory(kind="uninstall") - save_dir.create() self._save_dirs[os.path.normcase(path)] = save_dir return save_dir.path - def _get_file_stash(self, path): + def _get_file_stash(self, path: str) -> str: """Stashes a file. If no root has been provided, one will be created for the directory @@ -230,7 +253,6 @@ class StashedUninstallPathSet(object): # Did not find any suitable root head = os.path.dirname(path) save_dir = TempDirectory(kind='uninstall') - save_dir.create() self._save_dirs[head] = save_dir relpath = os.path.relpath(path, head) @@ -238,16 +260,18 @@ class StashedUninstallPathSet(object): return os.path.join(save_dir.path, relpath) return save_dir.path - def stash(self, path): + def stash(self, path: str) -> str: """Stashes the directory or file and returns its new location. + Handle symlinks as files to avoid modifying the symlink targets. """ - if os.path.isdir(path): + path_is_dir = os.path.isdir(path) and not os.path.islink(path) + if path_is_dir: new_path = self._get_directory_stash(path) else: new_path = self._get_file_stash(path) self._moves.append((path, new_path)) - if os.path.isdir(path) and os.path.isdir(new_path): + if (path_is_dir and os.path.isdir(new_path)): # If we're moving a directory, we need to # remove the destination first or else it will be # moved to inside the existing directory. @@ -257,22 +281,22 @@ class StashedUninstallPathSet(object): renames(path, new_path) return new_path - def commit(self): + def commit(self) -> None: """Commits the uninstall by removing stashed files.""" for _, save_dir in self._save_dirs.items(): save_dir.cleanup() self._moves = [] self._save_dirs = {} - def rollback(self): + def rollback(self) -> None: """Undoes the uninstall by moving stashed files back.""" for p in self._moves: - logging.info("Moving to %s\n from %s", *p) + logger.info("Moving to %s\n from %s", *p) for new_path, path in self._moves: try: logger.debug('Replacing %s from %s', new_path, path) - if os.path.isfile(new_path): + if os.path.isfile(new_path) or os.path.islink(new_path): os.unlink(new_path) elif os.path.isdir(new_path): rmtree(new_path) @@ -284,21 +308,21 @@ class StashedUninstallPathSet(object): self.commit() @property - def can_rollback(self): + def can_rollback(self) -> bool: return bool(self._moves) -class UninstallPathSet(object): +class UninstallPathSet: """A set of file paths to be removed in the uninstallation of a requirement.""" - def __init__(self, dist): - self.paths = set() - self._refuse = set() - self.pth = {} + def __init__(self, dist: Distribution) -> None: + self.paths: Set[str] = set() + self._refuse: Set[str] = set() + self.pth: Dict[str, UninstallPthEntries] = {} self.dist = dist self._moved_paths = StashedUninstallPathSet() - def _permitted(self, path): + def _permitted(self, path: str) -> bool: """ Return True if the given path is one we are permitted to remove/modify, False otherwise. @@ -306,7 +330,7 @@ class UninstallPathSet(object): """ return is_local(path) - def add(self, path): + def add(self, path: str) -> None: head, tail = os.path.split(path) # we normalize the head to resolve parent directory symlinks, but not @@ -322,10 +346,10 @@ class UninstallPathSet(object): # __pycache__ files can show up after 'installed-files.txt' is created, # due to imports - if os.path.splitext(path)[1] == '.py' and uses_pycache: + if os.path.splitext(path)[1] == '.py': self.add(cache_from_source(path)) - def add_pth(self, pth_file, entry): + def add_pth(self, pth_file: str, entry: str) -> None: pth_file = normalize_path(pth_file) if self._permitted(pth_file): if pth_file not in self.pth: @@ -334,7 +358,7 @@ class UninstallPathSet(object): else: self._refuse.add(pth_file) - def remove(self, auto_confirm=False, verbose=False): + def remove(self, auto_confirm: bool = False, verbose: bool = False) -> None: """Remove paths in ``self.paths`` with confirmation (unless ``auto_confirm`` is True).""" @@ -358,18 +382,18 @@ class UninstallPathSet(object): for path in sorted(compact(for_rename)): moved.stash(path) - logger.debug('Removing file or directory %s', path) + logger.verbose('Removing file or directory %s', path) for pth in self.pth.values(): pth.remove() logger.info('Successfully uninstalled %s', dist_name_version) - def _allowed_to_proceed(self, verbose): + def _allowed_to_proceed(self, verbose: bool) -> bool: """Display which files would be deleted and prompt for confirmation """ - def _display(msg, paths): + def _display(msg: str, paths: Iterable[str]) -> None: if not paths: return @@ -383,7 +407,7 @@ class UninstallPathSet(object): else: # In verbose mode, display all the files that are going to be # deleted. - will_remove = list(self.paths) + will_remove = set(self.paths) will_skip = set() _display('Would remove:', will_remove) @@ -392,27 +416,27 @@ class UninstallPathSet(object): if verbose: _display('Will actually move:', compress_for_rename(self.paths)) - return ask('Proceed (y/n)? ', ('y', 'n')) == 'y' + return ask('Proceed (Y/n)? ', ('y', 'n', '')) != 'n' - def rollback(self): + def rollback(self) -> None: """Rollback the changes previously made by remove().""" if not self._moved_paths.can_rollback: logger.error( "Can't roll back %s; was not uninstalled", self.dist.project_name, ) - return False + return logger.info('Rolling back uninstall of %s', self.dist.project_name) self._moved_paths.rollback() for pth in self.pth.values(): pth.rollback() - def commit(self): + def commit(self) -> None: """Remove temporary save dir: rollback will no longer be possible.""" self._moved_paths.commit() @classmethod - def from_dist(cls, dist): + def from_dist(cls, dist: Distribution) -> "UninstallPathSet": dist_path = normalize_path(dist.location) if not dist_is_local(dist): logger.info( @@ -498,11 +522,12 @@ class UninstallPathSet(object): elif develop_egg_link: # develop egg - with open(develop_egg_link, 'r') as fh: + with open(develop_egg_link) as fh: link_pointer = os.path.normcase(fh.readline().strip()) assert (link_pointer == dist.location), ( - 'Egg-link %s does not match installed location of %s ' - '(at %s)' % (link_pointer, dist.project_name, dist.location) + 'Egg-link {} does not match installed location of {} ' + '(at {})'.format( + link_pointer, dist.project_name, dist.location) ) paths_to_remove.add(develop_egg_link) easy_install_pth = os.path.join(os.path.dirname(develop_egg_link), @@ -519,9 +544,9 @@ class UninstallPathSet(object): if dist.has_metadata('scripts') and dist.metadata_isdir('scripts'): for script in dist.metadata_listdir('scripts'): if dist_in_usersite(dist): - bin_dir = bin_user + bin_dir = get_bin_user() else: - bin_dir = bin_py + bin_dir = get_bin_prefix() paths_to_remove.add(os.path.join(bin_dir, script)) if WINDOWS: paths_to_remove.add(os.path.join(bin_dir, script) + '.bat') @@ -542,28 +567,36 @@ class UninstallPathSet(object): return paths_to_remove -class UninstallPthEntries(object): - def __init__(self, pth_file): - if not os.path.isfile(pth_file): - raise UninstallationError( - "Cannot remove entries from nonexistent file %s" % pth_file - ) +class UninstallPthEntries: + def __init__(self, pth_file: str) -> None: self.file = pth_file - self.entries = set() - self._saved_lines = None + self.entries: Set[str] = set() + self._saved_lines: Optional[List[bytes]] = None - def add(self, entry): + def add(self, entry: str) -> None: entry = os.path.normcase(entry) # On Windows, os.path.normcase converts the entry to use # backslashes. This is correct for entries that describe absolute # paths outside of site-packages, but all the others use forward # slashes. + # os.path.splitdrive is used instead of os.path.isabs because isabs + # treats non-absolute paths with drive letter markings like c:foo\bar + # as absolute paths. It also does not recognize UNC paths if they don't + # have more than "\\sever\share". Valid examples: "\\server\share\" or + # "\\server\share\folder". if WINDOWS and not os.path.splitdrive(entry)[0]: entry = entry.replace('\\', '/') self.entries.add(entry) - def remove(self): - logger.debug('Removing pth entries from %s:', self.file) + def remove(self) -> None: + logger.verbose('Removing pth entries from %s:', self.file) + + # If the file doesn't exist, log a warning and return + if not os.path.isfile(self.file): + logger.warning( + "Cannot remove entries from nonexistent file %s", self.file + ) + return with open(self.file, 'rb') as fh: # windows uses '\r\n' with py3k, but uses '\n' with py2.x lines = fh.readlines() @@ -577,14 +610,14 @@ class UninstallPthEntries(object): lines[-1] = lines[-1] + endline.encode("utf-8") for entry in self.entries: try: - logger.debug('Removing entry: %s', entry) + logger.verbose('Removing entry: %s', entry) lines.remove((entry + endline).encode("utf-8")) except ValueError: pass with open(self.file, 'wb') as fh: fh.writelines(lines) - def rollback(self): + def rollback(self) -> bool: if self._saved_lines is None: logger.error( 'Cannot roll back changes to %s, none were made', self.file diff --git a/pipenv/patched/piptools/scripts/__init__.py b/pipenv/patched/notpip/_internal/resolution/__init__.py similarity index 100% rename from pipenv/patched/piptools/scripts/__init__.py rename to pipenv/patched/notpip/_internal/resolution/__init__.py diff --git a/pipenv/patched/notpip/_internal/resolution/base.py b/pipenv/patched/notpip/_internal/resolution/base.py new file mode 100644 index 00000000..fa43475a --- /dev/null +++ b/pipenv/patched/notpip/_internal/resolution/base.py @@ -0,0 +1,18 @@ +from typing import Callable, List + +from pipenv.patched.notpip._internal.req.req_install import InstallRequirement +from pipenv.patched.notpip._internal.req.req_set import RequirementSet + +InstallRequirementProvider = Callable[[str, InstallRequirement], InstallRequirement] + + +class BaseResolver: + def resolve( + self, root_reqs: List[InstallRequirement], check_supported_wheels: bool + ) -> RequirementSet: + raise NotImplementedError() + + def get_installation_order( + self, req_set: RequirementSet + ) -> List[InstallRequirement]: + raise NotImplementedError() diff --git a/pipenv/vendor/passa/actions/__init__.py b/pipenv/patched/notpip/_internal/resolution/legacy/__init__.py similarity index 100% rename from pipenv/vendor/passa/actions/__init__.py rename to pipenv/patched/notpip/_internal/resolution/legacy/__init__.py diff --git a/pipenv/patched/notpip/_internal/resolution/legacy/resolver.py b/pipenv/patched/notpip/_internal/resolution/legacy/resolver.py new file mode 100644 index 00000000..8e52bcb0 --- /dev/null +++ b/pipenv/patched/notpip/_internal/resolution/legacy/resolver.py @@ -0,0 +1,453 @@ +"""Dependency Resolution + +The dependency resolution in pip is performed as follows: + +for top-level requirements: + a. only one spec allowed per project, regardless of conflicts or not. + otherwise a "double requirement" exception is raised + b. they override sub-dependency requirements. +for sub-dependencies + a. "first found, wins" (where the order is breadth first) +""" + +# The following comment should be removed at some point in the future. +# mypy: strict-optional=False + +import logging +import sys +from collections import defaultdict +from itertools import chain +from typing import DefaultDict, Iterable, List, Optional, Set, Tuple + +from pipenv.patched.notpip._vendor.packaging import specifiers +from pipenv.patched.notpip._vendor.pkg_resources import Distribution + +from pipenv.patched.notpip._internal.cache import WheelCache +from pipenv.patched.notpip._internal.exceptions import ( + BestVersionAlreadyInstalled, + DistributionNotFound, + HashError, + HashErrors, + UnsupportedPythonVersion, +) +from pipenv.patched.notpip._internal.index.package_finder import PackageFinder +from pipenv.patched.notpip._internal.models.link import Link +from pipenv.patched.notpip._internal.operations.prepare import RequirementPreparer +from pipenv.patched.notpip._internal.req.req_install import ( + InstallRequirement, + check_invalid_constraint_type, +) +from pipenv.patched.notpip._internal.req.req_set import RequirementSet +from pipenv.patched.notpip._internal.resolution.base import BaseResolver, InstallRequirementProvider +from pipenv.patched.notpip._internal.utils.compatibility_tags import get_supported +from pipenv.patched.notpip._internal.utils.logging import indent_log +from pipenv.patched.notpip._internal.utils.misc import dist_in_usersite, normalize_version_info +from pipenv.patched.notpip._internal.utils.packaging import check_requires_python, get_requires_python + +logger = logging.getLogger(__name__) + +DiscoveredDependencies = DefaultDict[str, List[InstallRequirement]] + + +def _check_dist_requires_python( + dist: Distribution, + version_info: Tuple[int, int, int], + ignore_requires_python: bool = False, +) -> None: + """ + Check whether the given Python version is compatible with a distribution's + "Requires-Python" value. + + :param version_info: A 3-tuple of ints representing the Python + major-minor-micro version to check. + :param ignore_requires_python: Whether to ignore the "Requires-Python" + value if the given Python version isn't compatible. + + :raises UnsupportedPythonVersion: When the given Python version isn't + compatible. + """ + requires_python = get_requires_python(dist) + try: + is_compatible = check_requires_python( + requires_python, version_info=version_info + ) + except specifiers.InvalidSpecifier as exc: + logger.warning( + "Package %r has an invalid Requires-Python: %s", dist.project_name, exc + ) + return + + if is_compatible: + return + + version = ".".join(map(str, version_info)) + if ignore_requires_python: + logger.debug( + "Ignoring failed Requires-Python check for package %r: %s not in %r", + dist.project_name, + version, + requires_python, + ) + return + + raise UnsupportedPythonVersion( + "Package {!r} requires a different Python: {} not in {!r}".format( + dist.project_name, version, requires_python + ) + ) + + +class Resolver(BaseResolver): + """Resolves which packages need to be installed/uninstalled to perform \ + the requested operation without breaking the requirements of any package. + """ + + _allowed_strategies = {"eager", "only-if-needed", "to-satisfy-only"} + + def __init__( + self, + preparer: RequirementPreparer, + finder: PackageFinder, + wheel_cache: Optional[WheelCache], + make_install_req: InstallRequirementProvider, + use_user_site: bool, + ignore_dependencies: bool, + ignore_installed: bool, + ignore_requires_python: bool, + force_reinstall: bool, + upgrade_strategy: str, + py_version_info: Optional[Tuple[int, ...]] = None, + ) -> None: + super().__init__() + assert upgrade_strategy in self._allowed_strategies + + if py_version_info is None: + py_version_info = sys.version_info[:3] + else: + py_version_info = normalize_version_info(py_version_info) + + self._py_version_info = py_version_info + + self.preparer = preparer + self.finder = finder + self.wheel_cache = wheel_cache + + self.upgrade_strategy = upgrade_strategy + self.force_reinstall = force_reinstall + self.ignore_dependencies = ignore_dependencies + self.ignore_installed = ignore_installed + self.ignore_requires_python = ignore_requires_python + self.use_user_site = use_user_site + self._make_install_req = make_install_req + + self._discovered_dependencies: DiscoveredDependencies = defaultdict(list) + + def resolve( + self, root_reqs: List[InstallRequirement], check_supported_wheels: bool + ) -> RequirementSet: + """Resolve what operations need to be done + + As a side-effect of this method, the packages (and their dependencies) + are downloaded, unpacked and prepared for installation. This + preparation is done by ``pip.operations.prepare``. + + Once PyPI has static dependency metadata available, it would be + possible to move the preparation to become a step separated from + dependency resolution. + """ + requirement_set = RequirementSet(check_supported_wheels=check_supported_wheels) + for req in root_reqs: + if req.constraint: + check_invalid_constraint_type(req) + requirement_set.add_requirement(req) + + # Actually prepare the files, and collect any exceptions. Most hash + # exceptions cannot be checked ahead of time, because + # _populate_link() needs to be called before we can make decisions + # based on link type. + discovered_reqs: List[InstallRequirement] = [] + hash_errors = HashErrors() + for req in chain(requirement_set.all_requirements, discovered_reqs): + try: + discovered_reqs.extend(self._resolve_one(requirement_set, req)) + except HashError as exc: + exc.req = req + hash_errors.append(exc) + + if hash_errors: + raise hash_errors + + return requirement_set + + def _is_upgrade_allowed(self, req: InstallRequirement) -> bool: + if self.upgrade_strategy == "to-satisfy-only": + return False + elif self.upgrade_strategy == "eager": + return True + else: + assert self.upgrade_strategy == "only-if-needed" + return req.user_supplied or req.constraint + + def _set_req_to_reinstall(self, req: InstallRequirement) -> None: + """ + Set a requirement to be installed. + """ + # Don't uninstall the conflict if doing a user install and the + # conflict is not a user install. + if not self.use_user_site or dist_in_usersite(req.satisfied_by): + req.should_reinstall = True + req.satisfied_by = None + + def _check_skip_installed( + self, req_to_install: InstallRequirement + ) -> Optional[str]: + """Check if req_to_install should be skipped. + + This will check if the req is installed, and whether we should upgrade + or reinstall it, taking into account all the relevant user options. + + After calling this req_to_install will only have satisfied_by set to + None if the req_to_install is to be upgraded/reinstalled etc. Any + other value will be a dist recording the current thing installed that + satisfies the requirement. + + Note that for vcs urls and the like we can't assess skipping in this + routine - we simply identify that we need to pull the thing down, + then later on it is pulled down and introspected to assess upgrade/ + reinstalls etc. + + :return: A text reason for why it was skipped, or None. + """ + if self.ignore_installed: + return None + + req_to_install.check_if_exists(self.use_user_site) + if not req_to_install.satisfied_by: + return None + + if self.force_reinstall: + self._set_req_to_reinstall(req_to_install) + return None + + if not self._is_upgrade_allowed(req_to_install): + if self.upgrade_strategy == "only-if-needed": + return "already satisfied, skipping upgrade" + return "already satisfied" + + # Check for the possibility of an upgrade. For link-based + # requirements we have to pull the tree down and inspect to assess + # the version #, so it's handled way down. + if not req_to_install.link: + try: + self.finder.find_requirement(req_to_install, upgrade=True) + except BestVersionAlreadyInstalled: + # Then the best version is installed. + return "already up-to-date" + except DistributionNotFound: + # No distribution found, so we squash the error. It will + # be raised later when we re-try later to do the install. + # Why don't we just raise here? + pass + + self._set_req_to_reinstall(req_to_install) + return None + + def _find_requirement_link(self, req: InstallRequirement) -> Optional[Link]: + upgrade = self._is_upgrade_allowed(req) + best_candidate = self.finder.find_requirement(req, upgrade) + if not best_candidate: + return None + + # Log a warning per PEP 592 if necessary before returning. + link = best_candidate.link + if link.is_yanked: + reason = link.yanked_reason or "" + msg = ( + # Mark this as a unicode string to prevent + # "UnicodeEncodeError: 'ascii' codec can't encode character" + # in Python 2 when the reason contains non-ascii characters. + "The candidate selected for download or install is a " + "yanked version: {candidate}\n" + "Reason for being yanked: {reason}" + ).format(candidate=best_candidate, reason=reason) + logger.warning(msg) + + return link + + def _populate_link(self, req: InstallRequirement) -> None: + """Ensure that if a link can be found for this, that it is found. + + Note that req.link may still be None - if the requirement is already + installed and not needed to be upgraded based on the return value of + _is_upgrade_allowed(). + + If preparer.require_hashes is True, don't use the wheel cache, because + cached wheels, always built locally, have different hashes than the + files downloaded from the index server and thus throw false hash + mismatches. Furthermore, cached wheels at present have undeterministic + contents due to file modification times. + """ + if req.link is None: + req.link = self._find_requirement_link(req) + + if self.wheel_cache is None or self.preparer.require_hashes: + return + cache_entry = self.wheel_cache.get_cache_entry( + link=req.link, + package_name=req.name, + supported_tags=get_supported(), + ) + if cache_entry is not None: + logger.debug("Using cached wheel link: %s", cache_entry.link) + if req.link is req.original_link and cache_entry.persistent: + req.original_link_is_in_wheel_cache = True + req.link = cache_entry.link + + def _get_dist_for(self, req: InstallRequirement) -> Distribution: + """Takes a InstallRequirement and returns a single AbstractDist \ + representing a prepared variant of the same. + """ + if req.editable: + return self.preparer.prepare_editable_requirement(req) + + # satisfied_by is only evaluated by calling _check_skip_installed, + # so it must be None here. + assert req.satisfied_by is None + skip_reason = self._check_skip_installed(req) + + if req.satisfied_by: + return self.preparer.prepare_installed_requirement(req, skip_reason) + + # We eagerly populate the link, since that's our "legacy" behavior. + self._populate_link(req) + dist = self.preparer.prepare_linked_requirement(req) + + # NOTE + # The following portion is for determining if a certain package is + # going to be re-installed/upgraded or not and reporting to the user. + # This should probably get cleaned up in a future refactor. + + # req.req is only avail after unpack for URL + # pkgs repeat check_if_exists to uninstall-on-upgrade + # (#14) + if not self.ignore_installed: + req.check_if_exists(self.use_user_site) + + if req.satisfied_by: + should_modify = ( + self.upgrade_strategy != "to-satisfy-only" + or self.force_reinstall + or self.ignore_installed + or req.link.scheme == "file" + ) + if should_modify: + self._set_req_to_reinstall(req) + else: + logger.info( + "Requirement already satisfied (use --upgrade to upgrade): %s", + req, + ) + return dist + + def _resolve_one( + self, + requirement_set: RequirementSet, + req_to_install: InstallRequirement, + ) -> List[InstallRequirement]: + """Prepare a single requirements file. + + :return: A list of additional InstallRequirements to also install. + """ + # Tell user what we are doing for this requirement: + # obtain (editable), skipping, processing (local url), collecting + # (remote url or package name) + if req_to_install.constraint or req_to_install.prepared: + return [] + + req_to_install.prepared = True + + # Parse and return dependencies + dist = self._get_dist_for(req_to_install) + # This will raise UnsupportedPythonVersion if the given Python + # version isn't compatible with the distribution's Requires-Python. + _check_dist_requires_python( + dist, + version_info=self._py_version_info, + ignore_requires_python=self.ignore_requires_python, + ) + + more_reqs: List[InstallRequirement] = [] + + def add_req(subreq: Distribution, extras_requested: Iterable[str]) -> None: + sub_install_req = self._make_install_req( + str(subreq), + req_to_install, + ) + parent_req_name = req_to_install.name + to_scan_again, add_to_parent = requirement_set.add_requirement( + sub_install_req, + parent_req_name=parent_req_name, + extras_requested=extras_requested, + ) + if parent_req_name and add_to_parent: + self._discovered_dependencies[parent_req_name].append(add_to_parent) + more_reqs.extend(to_scan_again) + + with indent_log(): + # We add req_to_install before its dependencies, so that we + # can refer to it when adding dependencies. + if not requirement_set.has_requirement(req_to_install.name): + # 'unnamed' requirements will get added here + # 'unnamed' requirements can only come from being directly + # provided by the user. + assert req_to_install.user_supplied + requirement_set.add_requirement(req_to_install, parent_req_name=None) + + if not self.ignore_dependencies: + if req_to_install.extras: + logger.debug( + "Installing extra requirements: %r", + ",".join(req_to_install.extras), + ) + missing_requested = sorted( + set(req_to_install.extras) - set(dist.extras) + ) + for missing in missing_requested: + logger.warning("%s does not provide the extra '%s'", dist, missing) + + available_requested = sorted( + set(dist.extras) & set(req_to_install.extras) + ) + for subreq in dist.requires(available_requested): + add_req(subreq, extras_requested=available_requested) + + return more_reqs + + def get_installation_order( + self, req_set: RequirementSet + ) -> List[InstallRequirement]: + """Create the installation order. + + The installation order is topological - requirements are installed + before the requiring thing. We break cycles at an arbitrary point, + and make no other guarantees. + """ + # The current implementation, which we may change at any point + # installs the user specified things in the order given, except when + # dependencies must come earlier to achieve topological order. + order = [] + ordered_reqs: Set[InstallRequirement] = set() + + def schedule(req: InstallRequirement) -> None: + if req.satisfied_by or req in ordered_reqs: + return + if req.constraint: + return + ordered_reqs.add(req) + for dep in self._discovered_dependencies[req.name]: + schedule(dep) + order.append(req) + + for install_req in req_set.requirements.values(): + schedule(install_req) + return order diff --git a/pipenv/vendor/passa/internals/__init__.py b/pipenv/patched/notpip/_internal/resolution/resolvelib/__init__.py similarity index 100% rename from pipenv/vendor/passa/internals/__init__.py rename to pipenv/patched/notpip/_internal/resolution/resolvelib/__init__.py diff --git a/pipenv/patched/notpip/_internal/resolution/resolvelib/base.py b/pipenv/patched/notpip/_internal/resolution/resolvelib/base.py new file mode 100644 index 00000000..3a2ad094 --- /dev/null +++ b/pipenv/patched/notpip/_internal/resolution/resolvelib/base.py @@ -0,0 +1,144 @@ +from typing import FrozenSet, Iterable, Optional, Tuple, Union + +from pipenv.patched.notpip._vendor.packaging.specifiers import SpecifierSet +from pipenv.patched.notpip._vendor.packaging.utils import NormalizedName, canonicalize_name +from pipenv.patched.notpip._vendor.packaging.version import LegacyVersion, Version + +from pipenv.patched.notpip._internal.models.link import Link, links_equivalent +from pipenv.patched.notpip._internal.req.req_install import InstallRequirement +from pipenv.patched.notpip._internal.utils.hashes import Hashes + +CandidateLookup = Tuple[Optional["Candidate"], Optional[InstallRequirement]] +CandidateVersion = Union[LegacyVersion, Version] + + +def format_name(project: str, extras: FrozenSet[str]) -> str: + if not extras: + return project + canonical_extras = sorted(canonicalize_name(e) for e in extras) + return "{}[{}]".format(project, ",".join(canonical_extras)) + + +class Constraint: + def __init__( + self, specifier: SpecifierSet, hashes: Hashes, links: FrozenSet[Link] + ) -> None: + self.specifier = specifier + self.hashes = hashes + self.links = links + + @classmethod + def empty(cls) -> "Constraint": + return Constraint(SpecifierSet(), Hashes(), frozenset()) + + @classmethod + def from_ireq(cls, ireq: InstallRequirement) -> "Constraint": + links = frozenset([ireq.link]) if ireq.link else frozenset() + return Constraint(ireq.specifier, ireq.hashes(trust_internet=False), links) + + def __nonzero__(self) -> bool: + return bool(self.specifier) or bool(self.hashes) or bool(self.links) + + def __bool__(self) -> bool: + return self.__nonzero__() + + def __and__(self, other: InstallRequirement) -> "Constraint": + if not isinstance(other, InstallRequirement): + return NotImplemented + specifier = self.specifier & other.specifier + hashes = self.hashes & other.hashes(trust_internet=False) + links = self.links + if other.link: + links = links.union([other.link]) + return Constraint(specifier, hashes, links) + + def is_satisfied_by(self, candidate: "Candidate") -> bool: + # Reject if there are any mismatched URL constraints on this package. + if self.links and not all(_match_link(link, candidate) for link in self.links): + return False + # We can safely always allow prereleases here since PackageFinder + # already implements the prerelease logic, and would have filtered out + # prerelease candidates if the user does not expect them. + return self.specifier.contains(candidate.version, prereleases=True) + + +class Requirement: + @property + def project_name(self) -> NormalizedName: + """The "project name" of a requirement. + + This is different from ``name`` if this requirement contains extras, + in which case ``name`` would contain the ``[...]`` part, while this + refers to the name of the project. + """ + raise NotImplementedError("Subclass should override") + + @property + def name(self) -> str: + """The name identifying this requirement in the resolver. + + This is different from ``project_name`` if this requirement contains + extras, where ``project_name`` would not contain the ``[...]`` part. + """ + raise NotImplementedError("Subclass should override") + + def is_satisfied_by(self, candidate: "Candidate") -> bool: + return False + + def get_candidate_lookup(self) -> CandidateLookup: + raise NotImplementedError("Subclass should override") + + def format_for_error(self) -> str: + raise NotImplementedError("Subclass should override") + + +def _match_link(link: Link, candidate: "Candidate") -> bool: + if candidate.source_link: + return links_equivalent(link, candidate.source_link) + return False + + +class Candidate: + @property + def project_name(self) -> NormalizedName: + """The "project name" of the candidate. + + This is different from ``name`` if this candidate contains extras, + in which case ``name`` would contain the ``[...]`` part, while this + refers to the name of the project. + """ + raise NotImplementedError("Override in subclass") + + @property + def name(self) -> str: + """The name identifying this candidate in the resolver. + + This is different from ``project_name`` if this candidate contains + extras, where ``project_name`` would not contain the ``[...]`` part. + """ + raise NotImplementedError("Override in subclass") + + @property + def version(self) -> CandidateVersion: + raise NotImplementedError("Override in subclass") + + @property + def is_installed(self) -> bool: + raise NotImplementedError("Override in subclass") + + @property + def is_editable(self) -> bool: + raise NotImplementedError("Override in subclass") + + @property + def source_link(self) -> Optional[Link]: + raise NotImplementedError("Override in subclass") + + def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]: + raise NotImplementedError("Override in subclass") + + def get_install_requirement(self) -> Optional[InstallRequirement]: + raise NotImplementedError("Override in subclass") + + def format_for_error(self) -> str: + raise NotImplementedError("Subclass should override") diff --git a/pipenv/patched/notpip/_internal/resolution/resolvelib/candidates.py b/pipenv/patched/notpip/_internal/resolution/resolvelib/candidates.py new file mode 100644 index 00000000..6fdb59b7 --- /dev/null +++ b/pipenv/patched/notpip/_internal/resolution/resolvelib/candidates.py @@ -0,0 +1,558 @@ +import logging +import sys +from typing import TYPE_CHECKING, Any, FrozenSet, Iterable, Optional, Tuple, Union, cast + +from pipenv.patched.notpip._vendor.packaging.specifiers import InvalidSpecifier, SpecifierSet +from pipenv.patched.notpip._vendor.packaging.utils import NormalizedName, canonicalize_name +from pipenv.patched.notpip._vendor.packaging.version import Version +from pipenv.patched.notpip._vendor.packaging.version import parse as parse_version +from pipenv.patched.notpip._vendor.pkg_resources import Distribution + +from pipenv.patched.notpip._internal.exceptions import HashError, MetadataInconsistent +from pipenv.patched.notpip._internal.models.link import Link, links_equivalent +from pipenv.patched.notpip._internal.models.wheel import Wheel +from pipenv.patched.notpip._internal.req.constructors import ( + install_req_from_editable, + install_req_from_line, +) +from pipenv.patched.notpip._internal.req.req_install import InstallRequirement +from pipenv.patched.notpip._internal.utils.misc import dist_is_editable, normalize_version_info +from pipenv.patched.notpip._internal.utils.packaging import get_requires_python + +from .base import Candidate, CandidateVersion, Requirement, format_name + +if TYPE_CHECKING: + from .factory import Factory + +logger = logging.getLogger(__name__) + +BaseCandidate = Union[ + "AlreadyInstalledCandidate", + "EditableCandidate", + "LinkCandidate", +] + +# Avoid conflicting with the PyPI package "Python". +REQUIRES_PYTHON_IDENTIFIER = cast(NormalizedName, "") + + +def as_base_candidate(candidate: Candidate) -> Optional[BaseCandidate]: + """The runtime version of BaseCandidate.""" + base_candidate_classes = ( + AlreadyInstalledCandidate, + EditableCandidate, + LinkCandidate, + ) + if isinstance(candidate, base_candidate_classes): + return candidate + return None + + +def make_install_req_from_link( + link: Link, template: InstallRequirement +) -> InstallRequirement: + assert not template.editable, "template is editable" + if template.req: + line = str(template.req) + else: + line = link.url + ireq = install_req_from_line( + line, + user_supplied=template.user_supplied, + comes_from=template.comes_from, + use_pep517=template.use_pep517, + isolated=template.isolated, + constraint=template.constraint, + options=dict( + install_options=template.install_options, + global_options=template.global_options, + hashes=template.hash_options, + ), + ) + ireq.original_link = template.original_link + ireq.link = link + return ireq + + +def make_install_req_from_editable( + link: Link, template: InstallRequirement +) -> InstallRequirement: + assert template.editable, "template not editable" + return install_req_from_editable( + link.url, + user_supplied=template.user_supplied, + comes_from=template.comes_from, + use_pep517=template.use_pep517, + isolated=template.isolated, + constraint=template.constraint, + options=dict( + install_options=template.install_options, + global_options=template.global_options, + hashes=template.hash_options, + ), + ) + + +def make_install_req_from_dist( + dist: Distribution, template: InstallRequirement +) -> InstallRequirement: + project_name = canonicalize_name(dist.project_name) + if template.req: + line = str(template.req) + elif template.link: + line = f"{project_name} @ {template.link.url}" + else: + line = f"{project_name}=={dist.parsed_version}" + ireq = install_req_from_line( + line, + user_supplied=template.user_supplied, + comes_from=template.comes_from, + use_pep517=template.use_pep517, + isolated=template.isolated, + constraint=template.constraint, + options=dict( + install_options=template.install_options, + global_options=template.global_options, + hashes=template.hash_options, + ), + ) + ireq.satisfied_by = dist + return ireq + + +class _InstallRequirementBackedCandidate(Candidate): + """A candidate backed by an ``InstallRequirement``. + + This represents a package request with the target not being already + in the environment, and needs to be fetched and installed. The backing + ``InstallRequirement`` is responsible for most of the leg work; this + class exposes appropriate information to the resolver. + + :param link: The link passed to the ``InstallRequirement``. The backing + ``InstallRequirement`` will use this link to fetch the distribution. + :param source_link: The link this candidate "originates" from. This is + different from ``link`` when the link is found in the wheel cache. + ``link`` would point to the wheel cache, while this points to the + found remote link (e.g. from pypi.org). + """ + + is_installed = False + + def __init__( + self, + link: Link, + source_link: Link, + ireq: InstallRequirement, + factory: "Factory", + name: Optional[NormalizedName] = None, + version: Optional[CandidateVersion] = None, + ) -> None: + self._link = link + self._source_link = source_link + self._factory = factory + self._ireq = ireq + self._name = name + self._version = version + self.dist = self._prepare() + + def __str__(self) -> str: + return f"{self.name} {self.version}" + + def __repr__(self) -> str: + return "{class_name}({link!r})".format( + class_name=self.__class__.__name__, + link=str(self._link), + ) + + def __hash__(self) -> int: + return hash((self.__class__, self._link)) + + def __eq__(self, other: Any) -> bool: + if isinstance(other, self.__class__): + return links_equivalent(self._link, other._link) + return False + + @property + def source_link(self) -> Optional[Link]: + return self._source_link + + @property + def project_name(self) -> NormalizedName: + """The normalised name of the project the candidate refers to""" + if self._name is None: + self._name = canonicalize_name(self.dist.project_name) + return self._name + + @property + def name(self) -> str: + return self.project_name + + @property + def version(self) -> CandidateVersion: + if self._version is None: + self._version = parse_version(self.dist.version) + return self._version + + def format_for_error(self) -> str: + return "{} {} (from {})".format( + self.name, + self.version, + self._link.file_path if self._link.is_file else self._link, + ) + + def _prepare_distribution(self) -> Distribution: + raise NotImplementedError("Override in subclass") + + def _check_metadata_consistency(self, dist: Distribution) -> None: + """Check for consistency of project name and version of dist.""" + canonical_name = canonicalize_name(dist.project_name) + if self._name is not None and self._name != canonical_name: + raise MetadataInconsistent( + self._ireq, + "name", + self._name, + dist.project_name, + ) + parsed_version = parse_version(dist.version) + if self._version is not None and self._version != parsed_version: + raise MetadataInconsistent( + self._ireq, + "version", + str(self._version), + dist.version, + ) + + def _prepare(self) -> Distribution: + try: + dist = self._prepare_distribution() + except HashError as e: + # Provide HashError the underlying ireq that caused it. This + # provides context for the resulting error message to show the + # offending line to the user. + e.req = self._ireq + raise + self._check_metadata_consistency(dist) + return dist + + def _get_requires_python_dependency(self) -> Optional[Requirement]: + requires_python = get_requires_python(self.dist) + if requires_python is None: + return None + try: + spec = SpecifierSet(requires_python) + except InvalidSpecifier as e: + message = "Package %r has an invalid Requires-Python: %s" + logger.warning(message, self.name, e) + return None + return self._factory.make_requires_python_requirement(spec) + + def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]: + requires = self.dist.requires() if with_requires else () + for r in requires: + yield self._factory.make_requirement_from_spec(str(r), self._ireq) + yield self._get_requires_python_dependency() + + def get_install_requirement(self) -> Optional[InstallRequirement]: + ireq = self._ireq + if self._version and ireq.req and not ireq.req.url: + ireq.req.specifier = SpecifierSet(f"=={self._version}") + return ireq + + +class LinkCandidate(_InstallRequirementBackedCandidate): + is_editable = False + + def __init__( + self, + link: Link, + template: InstallRequirement, + factory: "Factory", + name: Optional[NormalizedName] = None, + version: Optional[CandidateVersion] = None, + ) -> None: + source_link = link + cache_entry = factory.get_wheel_cache_entry(link, name) + if cache_entry is not None: + logger.debug("Using cached wheel link: %s", cache_entry.link) + link = cache_entry.link + ireq = make_install_req_from_link(link, template) + assert ireq.link == link + if ireq.link.is_wheel and not ireq.link.is_file: + wheel = Wheel(ireq.link.filename) + wheel_name = canonicalize_name(wheel.name) + assert name == wheel_name, f"{name!r} != {wheel_name!r} for wheel" + # Version may not be present for PEP 508 direct URLs + if version is not None: + wheel_version = Version(wheel.version) + assert version == wheel_version, "{!r} != {!r} for wheel {}".format( + version, wheel_version, name + ) + + if ( + cache_entry is not None + and cache_entry.persistent + and template.link is template.original_link + ): + ireq.original_link_is_in_wheel_cache = True + + super().__init__( + link=link, + source_link=source_link, + ireq=ireq, + factory=factory, + name=name, + version=version, + ) + + def _prepare_distribution(self) -> Distribution: + return self._factory.preparer.prepare_linked_requirement( + self._ireq, parallel_builds=True + ) + + +class EditableCandidate(_InstallRequirementBackedCandidate): + is_editable = True + + def __init__( + self, + link: Link, + template: InstallRequirement, + factory: "Factory", + name: Optional[NormalizedName] = None, + version: Optional[CandidateVersion] = None, + ) -> None: + super().__init__( + link=link, + source_link=link, + ireq=make_install_req_from_editable(link, template), + factory=factory, + name=name, + version=version, + ) + + def _prepare_distribution(self) -> Distribution: + return self._factory.preparer.prepare_editable_requirement(self._ireq) + + +class AlreadyInstalledCandidate(Candidate): + is_installed = True + source_link = None + + def __init__( + self, + dist: Distribution, + template: InstallRequirement, + factory: "Factory", + ) -> None: + self.dist = dist + self._ireq = make_install_req_from_dist(dist, template) + self._factory = factory + + # This is just logging some messages, so we can do it eagerly. + # The returned dist would be exactly the same as self.dist because we + # set satisfied_by in make_install_req_from_dist. + # TODO: Supply reason based on force_reinstall and upgrade_strategy. + skip_reason = "already satisfied" + factory.preparer.prepare_installed_requirement(self._ireq, skip_reason) + + def __str__(self) -> str: + return str(self.dist) + + def __repr__(self) -> str: + return "{class_name}({distribution!r})".format( + class_name=self.__class__.__name__, + distribution=self.dist, + ) + + def __hash__(self) -> int: + return hash((self.__class__, self.name, self.version)) + + def __eq__(self, other: Any) -> bool: + if isinstance(other, self.__class__): + return self.name == other.name and self.version == other.version + return False + + @property + def project_name(self) -> NormalizedName: + return canonicalize_name(self.dist.project_name) + + @property + def name(self) -> str: + return self.project_name + + @property + def version(self) -> CandidateVersion: + return parse_version(self.dist.version) + + @property + def is_editable(self) -> bool: + return dist_is_editable(self.dist) + + def format_for_error(self) -> str: + return f"{self.name} {self.version} (Installed)" + + def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]: + if not with_requires: + return + for r in self.dist.requires(): + yield self._factory.make_requirement_from_spec(str(r), self._ireq) + + def get_install_requirement(self) -> Optional[InstallRequirement]: + return None + + +class ExtrasCandidate(Candidate): + """A candidate that has 'extras', indicating additional dependencies. + + Requirements can be for a project with dependencies, something like + foo[extra]. The extras don't affect the project/version being installed + directly, but indicate that we need additional dependencies. We model that + by having an artificial ExtrasCandidate that wraps the "base" candidate. + + The ExtrasCandidate differs from the base in the following ways: + + 1. It has a unique name, of the form foo[extra]. This causes the resolver + to treat it as a separate node in the dependency graph. + 2. When we're getting the candidate's dependencies, + a) We specify that we want the extra dependencies as well. + b) We add a dependency on the base candidate. + See below for why this is needed. + 3. We return None for the underlying InstallRequirement, as the base + candidate will provide it, and we don't want to end up with duplicates. + + The dependency on the base candidate is needed so that the resolver can't + decide that it should recommend foo[extra1] version 1.0 and foo[extra2] + version 2.0. Having those candidates depend on foo=1.0 and foo=2.0 + respectively forces the resolver to recognise that this is a conflict. + """ + + def __init__( + self, + base: BaseCandidate, + extras: FrozenSet[str], + ) -> None: + self.base = base + self.extras = extras + + def __str__(self) -> str: + name, rest = str(self.base).split(" ", 1) + return "{}[{}] {}".format(name, ",".join(self.extras), rest) + + def __repr__(self) -> str: + return "{class_name}(base={base!r}, extras={extras!r})".format( + class_name=self.__class__.__name__, + base=self.base, + extras=self.extras, + ) + + def __hash__(self) -> int: + return hash((self.base, self.extras)) + + def __eq__(self, other: Any) -> bool: + if isinstance(other, self.__class__): + return self.base == other.base and self.extras == other.extras + return False + + @property + def project_name(self) -> NormalizedName: + return self.base.project_name + + @property + def name(self) -> str: + """The normalised name of the project the candidate refers to""" + return format_name(self.base.project_name, self.extras) + + @property + def version(self) -> CandidateVersion: + return self.base.version + + def format_for_error(self) -> str: + return "{} [{}]".format( + self.base.format_for_error(), ", ".join(sorted(self.extras)) + ) + + @property + def is_installed(self) -> bool: + return self.base.is_installed + + @property + def is_editable(self) -> bool: + return self.base.is_editable + + @property + def source_link(self) -> Optional[Link]: + return self.base.source_link + + def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]: + factory = self.base._factory + + # Add a dependency on the exact base + # (See note 2b in the class docstring) + yield factory.make_requirement_from_candidate(self.base) + if not with_requires: + return + + # The user may have specified extras that the candidate doesn't + # support. We ignore any unsupported extras here. + valid_extras = self.extras.intersection(self.base.dist.extras) + invalid_extras = self.extras.difference(self.base.dist.extras) + for extra in sorted(invalid_extras): + logger.warning( + "%s %s does not provide the extra '%s'", + self.base.name, + self.version, + extra, + ) + + for r in self.base.dist.requires(valid_extras): + requirement = factory.make_requirement_from_spec( + str(r), self.base._ireq, valid_extras + ) + if requirement: + yield requirement + + def get_install_requirement(self) -> Optional[InstallRequirement]: + # We don't return anything here, because we always + # depend on the base candidate, and we'll get the + # install requirement from that. + return None + + +class RequiresPythonCandidate(Candidate): + is_installed = False + source_link = None + + def __init__(self, py_version_info: Optional[Tuple[int, ...]]) -> None: + if py_version_info is not None: + version_info = normalize_version_info(py_version_info) + else: + version_info = sys.version_info[:3] + self._version = Version(".".join(str(c) for c in version_info)) + + # We don't need to implement __eq__() and __ne__() since there is always + # only one RequiresPythonCandidate in a resolution, i.e. the host Python. + # The built-in object.__eq__() and object.__ne__() do exactly what we want. + + def __str__(self) -> str: + return f"Python {self._version}" + + @property + def project_name(self) -> NormalizedName: + return REQUIRES_PYTHON_IDENTIFIER + + @property + def name(self) -> str: + return REQUIRES_PYTHON_IDENTIFIER + + @property + def version(self) -> CandidateVersion: + return self._version + + def format_for_error(self) -> str: + return f"Python {self.version}" + + def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]: + return () + + def get_install_requirement(self) -> Optional[InstallRequirement]: + return None diff --git a/pipenv/patched/notpip/_internal/resolution/resolvelib/factory.py b/pipenv/patched/notpip/_internal/resolution/resolvelib/factory.py new file mode 100644 index 00000000..9b216a1b --- /dev/null +++ b/pipenv/patched/notpip/_internal/resolution/resolvelib/factory.py @@ -0,0 +1,700 @@ +import contextlib +import functools +import logging +from typing import ( + TYPE_CHECKING, + Dict, + FrozenSet, + Iterable, + Iterator, + List, + Mapping, + NamedTuple, + Optional, + Sequence, + Set, + Tuple, + TypeVar, + cast, +) + +from pipenv.patched.notpip._vendor.packaging.requirements import InvalidRequirement +from pipenv.patched.notpip._vendor.packaging.requirements import Requirement as PackagingRequirement +from pipenv.patched.notpip._vendor.packaging.specifiers import SpecifierSet +from pipenv.patched.notpip._vendor.packaging.utils import NormalizedName, canonicalize_name +from pipenv.patched.notpip._vendor.resolvelib import ResolutionImpossible + +from pipenv.patched.notpip._internal.cache import CacheEntry, WheelCache +from pipenv.patched.notpip._internal.exceptions import ( + DistributionNotFound, + InstallationError, + InstallationSubprocessError, + MetadataInconsistent, + UnsupportedPythonVersion, + UnsupportedWheel, +) +from pipenv.patched.notpip._internal.index.package_finder import PackageFinder +from pipenv.patched.notpip._internal.metadata import BaseDistribution, get_default_environment +from pipenv.patched.notpip._internal.models.link import Link +from pipenv.patched.notpip._internal.models.wheel import Wheel +from pipenv.patched.notpip._internal.operations.prepare import RequirementPreparer +from pipenv.patched.notpip._internal.req.constructors import install_req_from_link_and_ireq +from pipenv.patched.notpip._internal.req.req_install import ( + InstallRequirement, + check_invalid_constraint_type, +) +from pipenv.patched.notpip._internal.resolution.base import InstallRequirementProvider +from pipenv.patched.notpip._internal.utils.compatibility_tags import get_supported +from pipenv.patched.notpip._internal.utils.hashes import Hashes +from pipenv.patched.notpip._internal.utils.virtualenv import running_under_virtualenv + +from .base import Candidate, CandidateVersion, Constraint, Requirement +from .candidates import ( + AlreadyInstalledCandidate, + BaseCandidate, + EditableCandidate, + ExtrasCandidate, + LinkCandidate, + RequiresPythonCandidate, + as_base_candidate, +) +from .found_candidates import FoundCandidates, IndexCandidateInfo +from .requirements import ( + ExplicitRequirement, + RequiresPythonRequirement, + SpecifierRequirement, + UnsatisfiableRequirement, +) + +if TYPE_CHECKING: + from typing import Protocol + + class ConflictCause(Protocol): + requirement: RequiresPythonRequirement + parent: Candidate + + +logger = logging.getLogger(__name__) + +C = TypeVar("C") +Cache = Dict[Link, C] + + +class CollectedRootRequirements(NamedTuple): + requirements: List[Requirement] + constraints: Dict[str, Constraint] + user_requested: Dict[str, int] + + +class Factory: + def __init__( + self, + finder: PackageFinder, + preparer: RequirementPreparer, + make_install_req: InstallRequirementProvider, + wheel_cache: Optional[WheelCache], + use_user_site: bool, + force_reinstall: bool, + ignore_installed: bool, + ignore_requires_python: bool, + py_version_info: Optional[Tuple[int, ...]] = None, + ) -> None: + self._finder = finder + self.preparer = preparer + self._wheel_cache = wheel_cache + self._python_candidate = RequiresPythonCandidate(py_version_info) + self._make_install_req_from_spec = make_install_req + self._use_user_site = use_user_site + self._force_reinstall = force_reinstall + self._ignore_requires_python = ignore_requires_python + + self._build_failures: Cache[InstallationError] = {} + self._link_candidate_cache: Cache[LinkCandidate] = {} + self._editable_candidate_cache: Cache[EditableCandidate] = {} + self._installed_candidate_cache: Dict[str, AlreadyInstalledCandidate] = {} + self._extras_candidate_cache: Dict[ + Tuple[int, FrozenSet[str]], ExtrasCandidate + ] = {} + + if not ignore_installed: + env = get_default_environment() + self._installed_dists = { + dist.canonical_name: dist + for dist in env.iter_installed_distributions(local_only=False) + } + else: + self._installed_dists = {} + + @property + def force_reinstall(self) -> bool: + return self._force_reinstall + + def _fail_if_link_is_unsupported_wheel(self, link: Link) -> None: + if not link.is_wheel: + return + wheel = Wheel(link.filename) + if wheel.supported(self._finder.target_python.get_tags()): + return + msg = f"{link.filename} is not a supported wheel on this platform." + raise UnsupportedWheel(msg) + + def _make_extras_candidate( + self, base: BaseCandidate, extras: FrozenSet[str] + ) -> ExtrasCandidate: + cache_key = (id(base), extras) + try: + candidate = self._extras_candidate_cache[cache_key] + except KeyError: + candidate = ExtrasCandidate(base, extras) + self._extras_candidate_cache[cache_key] = candidate + return candidate + + def _make_candidate_from_dist( + self, + dist: BaseDistribution, + extras: FrozenSet[str], + template: InstallRequirement, + ) -> Candidate: + try: + base = self._installed_candidate_cache[dist.canonical_name] + except KeyError: + from pipenv.patched.notpip._internal.metadata.pkg_resources import Distribution as _Dist + + compat_dist = cast(_Dist, dist)._dist + base = AlreadyInstalledCandidate(compat_dist, template, factory=self) + self._installed_candidate_cache[dist.canonical_name] = base + if not extras: + return base + return self._make_extras_candidate(base, extras) + + def _make_candidate_from_link( + self, + link: Link, + extras: FrozenSet[str], + template: InstallRequirement, + name: Optional[NormalizedName], + version: Optional[CandidateVersion], + ) -> Optional[Candidate]: + # TODO: Check already installed candidate, and use it if the link and + # editable flag match. + + if link in self._build_failures: + # We already tried this candidate before, and it does not build. + # Don't bother trying again. + return None + + if template.editable: + if link not in self._editable_candidate_cache: + try: + self._editable_candidate_cache[link] = EditableCandidate( + link, + template, + factory=self, + name=name, + version=version, + ) + except (InstallationSubprocessError, MetadataInconsistent) as e: + logger.warning("Discarding %s. %s", link, e) + self._build_failures[link] = e + return None + base: BaseCandidate = self._editable_candidate_cache[link] + else: + if link not in self._link_candidate_cache: + try: + self._link_candidate_cache[link] = LinkCandidate( + link, + template, + factory=self, + name=name, + version=version, + ) + except (InstallationSubprocessError, MetadataInconsistent) as e: + logger.warning("Discarding %s. %s", link, e) + self._build_failures[link] = e + return None + base = self._link_candidate_cache[link] + + if not extras: + return base + return self._make_extras_candidate(base, extras) + + def _iter_found_candidates( + self, + ireqs: Sequence[InstallRequirement], + specifier: SpecifierSet, + hashes: Hashes, + prefers_installed: bool, + incompatible_ids: Set[int], + ) -> Iterable[Candidate]: + if not ireqs: + return () + + # The InstallRequirement implementation requires us to give it a + # "template". Here we just choose the first requirement to represent + # all of them. + # Hopefully the Project model can correct this mismatch in the future. + template = ireqs[0] + assert template.req, "Candidates found on index must be PEP 508" + name = canonicalize_name(template.req.name) + + extras: FrozenSet[str] = frozenset() + for ireq in ireqs: + assert ireq.req, "Candidates found on index must be PEP 508" + specifier &= ireq.req.specifier + hashes &= ireq.hashes(trust_internet=False) + extras |= frozenset(ireq.extras) + + def _get_installed_candidate() -> Optional[Candidate]: + """Get the candidate for the currently-installed version.""" + # If --force-reinstall is set, we want the version from the index + # instead, so we "pretend" there is nothing installed. + if self._force_reinstall: + return None + try: + installed_dist = self._installed_dists[name] + except KeyError: + return None + # Don't use the installed distribution if its version does not fit + # the current dependency graph. + if not specifier.contains(installed_dist.version, prereleases=True): + return None + candidate = self._make_candidate_from_dist( + dist=installed_dist, + extras=extras, + template=template, + ) + # The candidate is a known incompatiblity. Don't use it. + if id(candidate) in incompatible_ids: + return None + return candidate + + def iter_index_candidate_infos() -> Iterator[IndexCandidateInfo]: + result = self._finder.find_best_candidate( + project_name=name, + specifier=specifier, + hashes=hashes, + ) + icans = list(result.iter_applicable()) + + # PEP 592: Yanked releases must be ignored unless only yanked + # releases can satisfy the version range. So if this is false, + # all yanked icans need to be skipped. + all_yanked = all(ican.link.is_yanked for ican in icans) + + # PackageFinder returns earlier versions first, so we reverse. + for ican in reversed(icans): + if not all_yanked and ican.link.is_yanked: + continue + func = functools.partial( + self._make_candidate_from_link, + link=ican.link, + extras=extras, + template=template, + name=name, + version=ican.version, + ) + yield ican.version, func + + return FoundCandidates( + iter_index_candidate_infos, + _get_installed_candidate(), + prefers_installed, + incompatible_ids, + ) + + def _iter_explicit_candidates_from_base( + self, + base_requirements: Iterable[Requirement], + extras: FrozenSet[str], + ) -> Iterator[Candidate]: + """Produce explicit candidates from the base given an extra-ed package. + + :param base_requirements: Requirements known to the resolver. The + requirements are guaranteed to not have extras. + :param extras: The extras to inject into the explicit requirements' + candidates. + """ + for req in base_requirements: + lookup_cand, _ = req.get_candidate_lookup() + if lookup_cand is None: # Not explicit. + continue + # We've stripped extras from the identifier, and should always + # get a BaseCandidate here, unless there's a bug elsewhere. + base_cand = as_base_candidate(lookup_cand) + assert base_cand is not None, "no extras here" + yield self._make_extras_candidate(base_cand, extras) + + def _iter_candidates_from_constraints( + self, + identifier: str, + constraint: Constraint, + template: InstallRequirement, + ) -> Iterator[Candidate]: + """Produce explicit candidates from constraints. + + This creates "fake" InstallRequirement objects that are basically clones + of what "should" be the template, but with original_link set to link. + """ + for link in constraint.links: + self._fail_if_link_is_unsupported_wheel(link) + candidate = self._make_candidate_from_link( + link, + extras=frozenset(), + template=install_req_from_link_and_ireq(link, template), + name=canonicalize_name(identifier), + version=None, + ) + if candidate: + yield candidate + + def find_candidates( + self, + identifier: str, + requirements: Mapping[str, Iterator[Requirement]], + incompatibilities: Mapping[str, Iterator[Candidate]], + constraint: Constraint, + prefers_installed: bool, + ) -> Iterable[Candidate]: + # Collect basic lookup information from the requirements. + explicit_candidates: Set[Candidate] = set() + ireqs: List[InstallRequirement] = [] + for req in requirements[identifier]: + cand, ireq = req.get_candidate_lookup() + if cand is not None: + explicit_candidates.add(cand) + if ireq is not None: + ireqs.append(ireq) + + # If the current identifier contains extras, add explicit candidates + # from entries from extra-less identifier. + with contextlib.suppress(InvalidRequirement): + parsed_requirement = PackagingRequirement(identifier) + explicit_candidates.update( + self._iter_explicit_candidates_from_base( + requirements.get(parsed_requirement.name, ()), + frozenset(parsed_requirement.extras), + ), + ) + + # Add explicit candidates from constraints. We only do this if there are + # kown ireqs, which represent requirements not already explicit. If + # there are no ireqs, we're constraining already-explicit requirements, + # which is handled later when we return the explicit candidates. + if ireqs: + try: + explicit_candidates.update( + self._iter_candidates_from_constraints( + identifier, + constraint, + template=ireqs[0], + ), + ) + except UnsupportedWheel: + # If we're constrained to install a wheel incompatible with the + # target architecture, no candidates will ever be valid. + return () + + # Since we cache all the candidates, incompatibility identification + # can be made quicker by comparing only the id() values. + incompat_ids = {id(c) for c in incompatibilities.get(identifier, ())} + + # If none of the requirements want an explicit candidate, we can ask + # the finder for candidates. + if not explicit_candidates: + return self._iter_found_candidates( + ireqs, + constraint.specifier, + constraint.hashes, + prefers_installed, + incompat_ids, + ) + + return ( + c + for c in explicit_candidates + if id(c) not in incompat_ids + and constraint.is_satisfied_by(c) + and all(req.is_satisfied_by(c) for req in requirements[identifier]) + ) + + def _make_requirement_from_install_req( + self, ireq: InstallRequirement, requested_extras: Iterable[str] + ) -> Optional[Requirement]: + if not ireq.match_markers(requested_extras): + logger.info( + "Ignoring %s: markers '%s' don't match your environment", + ireq.name, + ireq.markers, + ) + return None + if not ireq.link: + return SpecifierRequirement(ireq) + self._fail_if_link_is_unsupported_wheel(ireq.link) + cand = self._make_candidate_from_link( + ireq.link, + extras=frozenset(ireq.extras), + template=ireq, + name=canonicalize_name(ireq.name) if ireq.name else None, + version=None, + ) + if cand is None: + # There's no way we can satisfy a URL requirement if the underlying + # candidate fails to build. An unnamed URL must be user-supplied, so + # we fail eagerly. If the URL is named, an unsatisfiable requirement + # can make the resolver do the right thing, either backtrack (and + # maybe find some other requirement that's buildable) or raise a + # ResolutionImpossible eventually. + if not ireq.name: + raise self._build_failures[ireq.link] + return UnsatisfiableRequirement(canonicalize_name(ireq.name)) + return self.make_requirement_from_candidate(cand) + + def collect_root_requirements( + self, root_ireqs: List[InstallRequirement] + ) -> CollectedRootRequirements: + collected = CollectedRootRequirements([], {}, {}) + for i, ireq in enumerate(root_ireqs): + if ireq.constraint: + # Ensure we only accept valid constraints + problem = check_invalid_constraint_type(ireq) + if problem: + raise InstallationError(problem) + if not ireq.match_markers(): + continue + assert ireq.name, "Constraint must be named" + name = canonicalize_name(ireq.name) + if name in collected.constraints: + collected.constraints[name] &= ireq + else: + collected.constraints[name] = Constraint.from_ireq(ireq) + else: + req = self._make_requirement_from_install_req( + ireq, + requested_extras=(), + ) + if req is None: + continue + if ireq.user_supplied and req.name not in collected.user_requested: + collected.user_requested[req.name] = i + collected.requirements.append(req) + return collected + + def make_requirement_from_candidate( + self, candidate: Candidate + ) -> ExplicitRequirement: + return ExplicitRequirement(candidate) + + def make_requirement_from_spec( + self, + specifier: str, + comes_from: InstallRequirement, + requested_extras: Iterable[str] = (), + ) -> Optional[Requirement]: + ireq = self._make_install_req_from_spec(specifier, comes_from) + return self._make_requirement_from_install_req(ireq, requested_extras) + + def make_requires_python_requirement( + self, specifier: Optional[SpecifierSet] + ) -> Optional[Requirement]: + if self._ignore_requires_python or specifier is None: + return None + return RequiresPythonRequirement(specifier, self._python_candidate) + + def get_wheel_cache_entry( + self, link: Link, name: Optional[str] + ) -> Optional[CacheEntry]: + """Look up the link in the wheel cache. + + If ``preparer.require_hashes`` is True, don't use the wheel cache, + because cached wheels, always built locally, have different hashes + than the files downloaded from the index server and thus throw false + hash mismatches. Furthermore, cached wheels at present have + nondeterministic contents due to file modification times. + """ + if self._wheel_cache is None or self.preparer.require_hashes: + return None + return self._wheel_cache.get_cache_entry( + link=link, + package_name=name, + supported_tags=get_supported(), + ) + + def get_dist_to_uninstall(self, candidate: Candidate) -> Optional[BaseDistribution]: + # TODO: Are there more cases this needs to return True? Editable? + dist = self._installed_dists.get(candidate.project_name) + if dist is None: # Not installed, no uninstallation required. + return None + + # We're installing into global site. The current installation must + # be uninstalled, no matter it's in global or user site, because the + # user site installation has precedence over global. + if not self._use_user_site: + return dist + + # We're installing into user site. Remove the user site installation. + if dist.in_usersite: + return dist + + # We're installing into user site, but the installed incompatible + # package is in global site. We can't uninstall that, and would let + # the new user installation to "shadow" it. But shadowing won't work + # in virtual environments, so we error out. + if running_under_virtualenv() and dist.in_site_packages: + message = ( + f"Will not install to the user site because it will lack " + f"sys.path precedence to {dist.raw_name} in {dist.location}" + ) + raise InstallationError(message) + return None + + def _report_requires_python_error( + self, causes: Sequence["ConflictCause"] + ) -> UnsupportedPythonVersion: + assert causes, "Requires-Python error reported with no cause" + + version = self._python_candidate.version + + if len(causes) == 1: + specifier = str(causes[0].requirement.specifier) + message = ( + f"Package {causes[0].parent.name!r} requires a different " + f"Python: {version} not in {specifier!r}" + ) + return UnsupportedPythonVersion(message) + + message = f"Packages require a different Python. {version} not in:" + for cause in causes: + package = cause.parent.format_for_error() + specifier = str(cause.requirement.specifier) + message += f"\n{specifier!r} (required by {package})" + return UnsupportedPythonVersion(message) + + def _report_single_requirement_conflict( + self, req: Requirement, parent: Optional[Candidate] + ) -> DistributionNotFound: + if parent is None: + req_disp = str(req) + else: + req_disp = f"{req} (from {parent.name})" + + cands = self._finder.find_all_candidates(req.project_name) + versions = [str(v) for v in sorted({c.version for c in cands})] + + logger.critical( + "Could not find a version that satisfies the requirement %s " + "(from versions: %s)", + req_disp, + ", ".join(versions) or "none", + ) + if str(req) == "requirements.txt": + logger.info( + "HINT: You are attempting to install a package literally " + 'named "requirements.txt" (which cannot exist). Consider ' + "using the '-r' flag to install the packages listed in " + "requirements.txt" + ) + + return DistributionNotFound(f"No matching distribution found for {req}") + + def get_installation_error( + self, + e: "ResolutionImpossible[Requirement, Candidate]", + constraints: Dict[str, Constraint], + ) -> InstallationError: + + assert e.causes, "Installation error reported with no cause" + + # If one of the things we can't solve is "we need Python X.Y", + # that is what we report. + requires_python_causes = [ + cause + for cause in e.causes + if isinstance(cause.requirement, RequiresPythonRequirement) + and not cause.requirement.is_satisfied_by(self._python_candidate) + ] + if requires_python_causes: + # The comprehension above makes sure all Requirement instances are + # RequiresPythonRequirement, so let's cast for convinience. + return self._report_requires_python_error( + cast("Sequence[ConflictCause]", requires_python_causes), + ) + + # Otherwise, we have a set of causes which can't all be satisfied + # at once. + + # The simplest case is when we have *one* cause that can't be + # satisfied. We just report that case. + if len(e.causes) == 1: + req, parent = e.causes[0] + if req.name not in constraints: + return self._report_single_requirement_conflict(req, parent) + + # OK, we now have a list of requirements that can't all be + # satisfied at once. + + # A couple of formatting helpers + def text_join(parts: List[str]) -> str: + if len(parts) == 1: + return parts[0] + + return ", ".join(parts[:-1]) + " and " + parts[-1] + + def describe_trigger(parent: Candidate) -> str: + ireq = parent.get_install_requirement() + if not ireq or not ireq.comes_from: + return f"{parent.name}=={parent.version}" + if isinstance(ireq.comes_from, InstallRequirement): + return str(ireq.comes_from.name) + return str(ireq.comes_from) + + triggers = set() + for req, parent in e.causes: + if parent is None: + # This is a root requirement, so we can report it directly + trigger = req.format_for_error() + else: + trigger = describe_trigger(parent) + triggers.add(trigger) + + if triggers: + info = text_join(sorted(triggers)) + else: + info = "the requested packages" + + msg = ( + "Cannot install {} because these package versions " + "have conflicting dependencies.".format(info) + ) + logger.critical(msg) + msg = "\nThe conflict is caused by:" + + relevant_constraints = set() + for req, parent in e.causes: + if req.name in constraints: + relevant_constraints.add(req.name) + msg = msg + "\n " + if parent: + msg = msg + f"{parent.name} {parent.version} depends on " + else: + msg = msg + "The user requested " + msg = msg + req.format_for_error() + for key in relevant_constraints: + spec = constraints[key].specifier + msg += f"\n The user requested (constraint) {key}{spec}" + + msg = ( + msg + + "\n\n" + + "To fix this you could try to:\n" + + "1. loosen the range of package versions you've specified\n" + + "2. remove package versions to allow pip attempt to solve " + + "the dependency conflict\n" + ) + + logger.info(msg) + + return DistributionNotFound( + "ResolutionImpossible: for help visit " + "https://pip.pypa.io/en/latest/user_guide/" + "#fixing-conflicting-dependencies" + ) diff --git a/pipenv/patched/notpip/_internal/resolution/resolvelib/found_candidates.py b/pipenv/patched/notpip/_internal/resolution/resolvelib/found_candidates.py new file mode 100644 index 00000000..e0514d97 --- /dev/null +++ b/pipenv/patched/notpip/_internal/resolution/resolvelib/found_candidates.py @@ -0,0 +1,142 @@ +"""Utilities to lazily create and visit candidates found. + +Creating and visiting a candidate is a *very* costly operation. It involves +fetching, extracting, potentially building modules from source, and verifying +distribution metadata. It is therefore crucial for performance to keep +everything here lazy all the way down, so we only touch candidates that we +absolutely need, and not "download the world" when we only need one version of +something. +""" + +import functools +from typing import Callable, Iterator, Optional, Set, Tuple + +from pipenv.patched.notpip._vendor.packaging.version import _BaseVersion +from pipenv.patched.notpip._vendor.six.moves import collections_abc # type: ignore + +from .base import Candidate + +IndexCandidateInfo = Tuple[_BaseVersion, Callable[[], Optional[Candidate]]] + + +def _iter_built(infos: Iterator[IndexCandidateInfo]) -> Iterator[Candidate]: + """Iterator for ``FoundCandidates``. + + This iterator is used when the package is not already installed. Candidates + from index come later in their normal ordering. + """ + versions_found: Set[_BaseVersion] = set() + for version, func in infos: + if version in versions_found: + continue + candidate = func() + if candidate is None: + continue + yield candidate + versions_found.add(version) + + +def _iter_built_with_prepended( + installed: Candidate, infos: Iterator[IndexCandidateInfo] +) -> Iterator[Candidate]: + """Iterator for ``FoundCandidates``. + + This iterator is used when the resolver prefers the already-installed + candidate and NOT to upgrade. The installed candidate is therefore + always yielded first, and candidates from index come later in their + normal ordering, except skipped when the version is already installed. + """ + yield installed + versions_found: Set[_BaseVersion] = {installed.version} + for version, func in infos: + if version in versions_found: + continue + candidate = func() + if candidate is None: + continue + yield candidate + versions_found.add(version) + + +def _iter_built_with_inserted( + installed: Candidate, infos: Iterator[IndexCandidateInfo] +) -> Iterator[Candidate]: + """Iterator for ``FoundCandidates``. + + This iterator is used when the resolver prefers to upgrade an + already-installed package. Candidates from index are returned in their + normal ordering, except replaced when the version is already installed. + + The implementation iterates through and yields other candidates, inserting + the installed candidate exactly once before we start yielding older or + equivalent candidates, or after all other candidates if they are all newer. + """ + versions_found: Set[_BaseVersion] = set() + for version, func in infos: + if version in versions_found: + continue + # If the installed candidate is better, yield it first. + if installed.version >= version: + yield installed + versions_found.add(installed.version) + candidate = func() + if candidate is None: + continue + yield candidate + versions_found.add(version) + + # If the installed candidate is older than all other candidates. + if installed.version not in versions_found: + yield installed + + +class FoundCandidates(collections_abc.Sequence): + """A lazy sequence to provide candidates to the resolver. + + The intended usage is to return this from `find_matches()` so the resolver + can iterate through the sequence multiple times, but only access the index + page when remote packages are actually needed. This improve performances + when suitable candidates are already installed on disk. + """ + + def __init__( + self, + get_infos: Callable[[], Iterator[IndexCandidateInfo]], + installed: Optional[Candidate], + prefers_installed: bool, + incompatible_ids: Set[int], + ): + self._get_infos = get_infos + self._installed = installed + self._prefers_installed = prefers_installed + self._incompatible_ids = incompatible_ids + + def __getitem__(self, index: int) -> Candidate: + # Implemented to satisfy the ABC check. This is not needed by the + # resolver, and should not be used by the provider either (for + # performance reasons). + raise NotImplementedError("don't do this") + + def __iter__(self) -> Iterator[Candidate]: + infos = self._get_infos() + if not self._installed: + iterator = _iter_built(infos) + elif self._prefers_installed: + iterator = _iter_built_with_prepended(self._installed, infos) + else: + iterator = _iter_built_with_inserted(self._installed, infos) + return (c for c in iterator if id(c) not in self._incompatible_ids) + + def __len__(self) -> int: + # Implemented to satisfy the ABC check. This is not needed by the + # resolver, and should not be used by the provider either (for + # performance reasons). + raise NotImplementedError("don't do this") + + @functools.lru_cache(maxsize=1) + def __bool__(self) -> bool: + if self._prefers_installed and self._installed: + return True + return any(self) + + __nonzero__ = __bool__ # XXX: Python 2. diff --git a/pipenv/patched/notpip/_internal/resolution/resolvelib/provider.py b/pipenv/patched/notpip/_internal/resolution/resolvelib/provider.py new file mode 100644 index 00000000..daaa437a --- /dev/null +++ b/pipenv/patched/notpip/_internal/resolution/resolvelib/provider.py @@ -0,0 +1,197 @@ +import collections +import math +from typing import TYPE_CHECKING, Dict, Iterable, Iterator, Mapping, Sequence, Union + +from pipenv.patched.notpip._vendor.resolvelib.providers import AbstractProvider + +from .base import Candidate, Constraint, Requirement +from .candidates import REQUIRES_PYTHON_IDENTIFIER +from .factory import Factory + +if TYPE_CHECKING: + from pipenv.patched.notpip._vendor.resolvelib.providers import Preference + from pipenv.patched.notpip._vendor.resolvelib.resolvers import RequirementInformation + + PreferenceInformation = RequirementInformation[Requirement, Candidate] + + _ProviderBase = AbstractProvider[Requirement, Candidate, str] +else: + _ProviderBase = AbstractProvider + +# Notes on the relationship between the provider, the factory, and the +# candidate and requirement classes. +# +# The provider is a direct implementation of the resolvelib class. Its role +# is to deliver the API that resolvelib expects. +# +# Rather than work with completely abstract "requirement" and "candidate" +# concepts as resolvelib does, pip has concrete classes implementing these two +# ideas. The API of Requirement and Candidate objects are defined in the base +# classes, but essentially map fairly directly to the equivalent provider +# methods. In particular, `find_matches` and `is_satisfied_by` are +# requirement methods, and `get_dependencies` is a candidate method. +# +# The factory is the interface to pip's internal mechanisms. It is stateless, +# and is created by the resolver and held as a property of the provider. It is +# responsible for creating Requirement and Candidate objects, and provides +# services to those objects (access to pip's finder and preparer). + + +class PipProvider(_ProviderBase): + """Pip's provider implementation for resolvelib. + + :params constraints: A mapping of constraints specified by the user. Keys + are canonicalized project names. + :params ignore_dependencies: Whether the user specified ``--no-deps``. + :params upgrade_strategy: The user-specified upgrade strategy. + :params user_requested: A set of canonicalized package names that the user + supplied for pip to install/upgrade. + """ + + def __init__( + self, + factory: Factory, + constraints: Dict[str, Constraint], + ignore_dependencies: bool, + upgrade_strategy: str, + user_requested: Dict[str, int], + ) -> None: + self._factory = factory + self._constraints = constraints + self._ignore_dependencies = ignore_dependencies + self._upgrade_strategy = upgrade_strategy + self._user_requested = user_requested + self._known_depths: Dict[str, float] = collections.defaultdict(lambda: math.inf) + + def identify(self, requirement_or_candidate: Union[Requirement, Candidate]) -> str: + return requirement_or_candidate.name + + def get_preference( + self, + identifier: str, + resolutions: Mapping[str, Candidate], + candidates: Mapping[str, Iterator[Candidate]], + information: Mapping[str, Iterator["PreferenceInformation"]], + ) -> "Preference": + """Produce a sort key for given requirement based on preference. + + The lower the return value is, the more preferred this group of + arguments is. + + Currently pip considers the followings in order: + + * Prefer if any of the known requirements is "direct", e.g. points to an + explicit URL. + * If equal, prefer if any requirement is "pinned", i.e. contains + operator ``===`` or ``==``. + * If equal, calculate an approximate "depth" and resolve requirements + closer to the user-specified requirements first. + * Order user-specified requirements by the order they are specified. + * If equal, prefers "non-free" requirements, i.e. contains at least one + operator, such as ``>=`` or ``<``. + * If equal, order alphabetically for consistency (helps debuggability). + """ + lookups = (r.get_candidate_lookup() for r, _ in information[identifier]) + candidate, ireqs = zip(*lookups) + operators = [ + specifier.operator + for specifier_set in (ireq.specifier for ireq in ireqs if ireq) + for specifier in specifier_set + ] + + direct = candidate is not None + pinned = any(op[:2] == "==" for op in operators) + unfree = bool(operators) + + try: + requested_order: Union[int, float] = self._user_requested[identifier] + except KeyError: + requested_order = math.inf + parent_depths = ( + self._known_depths[parent.name] if parent is not None else 0.0 + for _, parent in information[identifier] + ) + inferred_depth = min(d for d in parent_depths) + 1.0 + self._known_depths[identifier] = inferred_depth + else: + inferred_depth = 1.0 + + requested_order = self._user_requested.get(identifier, math.inf) + + # Requires-Python has only one candidate and the check is basically + # free, so we always do it first to avoid needless work if it fails. + requires_python = identifier == REQUIRES_PYTHON_IDENTIFIER + + # HACK: Setuptools have a very long and solid backward compatibility + # track record, and extremely few projects would request a narrow, + # non-recent version range of it since that would break a lot things. + # (Most projects specify it only to request for an installer feature, + # which does not work, but that's another topic.) Intentionally + # delaying Setuptools helps reduce branches the resolver has to check. + # This serves as a temporary fix for issues like "apache-airlfow[all]" + # while we work on "proper" branch pruning techniques. + delay_this = identifier == "setuptools" + + return ( + not requires_python, + delay_this, + not direct, + not pinned, + inferred_depth, + requested_order, + not unfree, + identifier, + ) + + def _get_constraint(self, identifier: str) -> Constraint: + if identifier in self._constraints: + return self._constraints[identifier] + + # HACK: Theoratically we should check whether this identifier is a valid + # "NAME[EXTRAS]" format, and parse out the name part with packaging or + # some regular expression. But since pip's resolver only spits out + # three kinds of identifiers: normalized PEP 503 names, normalized names + # plus extras, and Requires-Python, we can cheat a bit here. + name, open_bracket, _ = identifier.partition("[") + if open_bracket and name in self._constraints: + return self._constraints[name] + + return Constraint.empty() + + def find_matches( + self, + identifier: str, + requirements: Mapping[str, Iterator[Requirement]], + incompatibilities: Mapping[str, Iterator[Candidate]], + ) -> Iterable[Candidate]: + def _eligible_for_upgrade(name: str) -> bool: + """Are upgrades allowed for this project? + + This checks the upgrade strategy, and whether the project was one + that the user specified in the command line, in order to decide + whether we should upgrade if there's a newer version available. + + (Note that we don't need access to the `--upgrade` flag, because + an upgrade strategy of "to-satisfy-only" means that `--upgrade` + was not specified). + """ + if self._upgrade_strategy == "eager": + return True + elif self._upgrade_strategy == "only-if-needed": + return name in self._user_requested + return False + + return self._factory.find_candidates( + identifier=identifier, + requirements=requirements, + constraint=self._get_constraint(identifier), + prefers_installed=(not _eligible_for_upgrade(identifier)), + incompatibilities=incompatibilities, + ) + + def is_satisfied_by(self, requirement: Requirement, candidate: Candidate) -> bool: + return requirement.is_satisfied_by(candidate) + + def get_dependencies(self, candidate: Candidate) -> Sequence[Requirement]: + with_requires = not self._ignore_dependencies + return [r for r in candidate.iter_dependencies(with_requires) if r is not None] diff --git a/pipenv/patched/notpip/_internal/resolution/resolvelib/reporter.py b/pipenv/patched/notpip/_internal/resolution/resolvelib/reporter.py new file mode 100644 index 00000000..e0155df0 --- /dev/null +++ b/pipenv/patched/notpip/_internal/resolution/resolvelib/reporter.py @@ -0,0 +1,69 @@ +from collections import defaultdict +from logging import getLogger +from typing import Any, DefaultDict + +from pipenv.patched.notpip._vendor.resolvelib.reporters import BaseReporter + +from .base import Candidate, Requirement + +logger = getLogger(__name__) + + +class PipReporter(BaseReporter): + def __init__(self) -> None: + self.backtracks_by_package: DefaultDict[str, int] = defaultdict(int) + + self._messages_at_backtrack = { + 1: ( + "pip is looking at multiple versions of {package_name} to " + "determine which version is compatible with other " + "requirements. This could take a while." + ), + 8: ( + "pip is looking at multiple versions of {package_name} to " + "determine which version is compatible with other " + "requirements. This could take a while." + ), + 13: ( + "This is taking longer than usual. You might need to provide " + "the dependency resolver with stricter constraints to reduce " + "runtime. If you want to abort this run, you can press " + "Ctrl + C to do so. To improve how pip performs, tell us what " + "happened here: https://pip.pypa.io/surveys/backtracking" + ), + } + + def backtracking(self, candidate: Candidate) -> None: + self.backtracks_by_package[candidate.name] += 1 + + count = self.backtracks_by_package[candidate.name] + if count not in self._messages_at_backtrack: + return + + message = self._messages_at_backtrack[count] + logger.info("INFO: %s", message.format(package_name=candidate.name)) + + +class PipDebuggingReporter(BaseReporter): + """A reporter that does an info log for every event it sees.""" + + def starting(self) -> None: + logger.info("Reporter.starting()") + + def starting_round(self, index: int) -> None: + logger.info("Reporter.starting_round(%r)", index) + + def ending_round(self, index: int, state: Any) -> None: + logger.info("Reporter.ending_round(%r, state)", index) + + def ending(self, state: Any) -> None: + logger.info("Reporter.ending(%r)", state) + + def adding_requirement(self, requirement: Requirement, parent: Candidate) -> None: + logger.info("Reporter.adding_requirement(%r, %r)", requirement, parent) + + def backtracking(self, candidate: Candidate) -> None: + logger.info("Reporter.backtracking(%r)", candidate) + + def pinning(self, candidate: Candidate) -> None: + logger.info("Reporter.pinning(%r)", candidate) diff --git a/pipenv/patched/notpip/_internal/resolution/resolvelib/requirements.py b/pipenv/patched/notpip/_internal/resolution/resolvelib/requirements.py new file mode 100644 index 00000000..35795706 --- /dev/null +++ b/pipenv/patched/notpip/_internal/resolution/resolvelib/requirements.py @@ -0,0 +1,166 @@ +from pipenv.patched.notpip._vendor.packaging.specifiers import SpecifierSet +from pipenv.patched.notpip._vendor.packaging.utils import NormalizedName, canonicalize_name + +from pipenv.patched.notpip._internal.req.req_install import InstallRequirement + +from .base import Candidate, CandidateLookup, Requirement, format_name + + +class ExplicitRequirement(Requirement): + def __init__(self, candidate: Candidate) -> None: + self.candidate = candidate + + def __str__(self) -> str: + return str(self.candidate) + + def __repr__(self) -> str: + return "{class_name}({candidate!r})".format( + class_name=self.__class__.__name__, + candidate=self.candidate, + ) + + @property + def project_name(self) -> NormalizedName: + # No need to canonicalise - the candidate did this + return self.candidate.project_name + + @property + def name(self) -> str: + # No need to canonicalise - the candidate did this + return self.candidate.name + + def format_for_error(self) -> str: + return self.candidate.format_for_error() + + def get_candidate_lookup(self) -> CandidateLookup: + return self.candidate, None + + def is_satisfied_by(self, candidate: Candidate) -> bool: + return candidate == self.candidate + + +class SpecifierRequirement(Requirement): + def __init__(self, ireq: InstallRequirement) -> None: + assert ireq.link is None, "This is a link, not a specifier" + self._ireq = ireq + self._extras = frozenset(ireq.extras) + + def __str__(self) -> str: + return str(self._ireq.req) + + def __repr__(self) -> str: + return "{class_name}({requirement!r})".format( + class_name=self.__class__.__name__, + requirement=str(self._ireq.req), + ) + + @property + def project_name(self) -> NormalizedName: + assert self._ireq.req, "Specifier-backed ireq is always PEP 508" + return canonicalize_name(self._ireq.req.name) + + @property + def name(self) -> str: + return format_name(self.project_name, self._extras) + + def format_for_error(self) -> str: + + # Convert comma-separated specifiers into "A, B, ..., F and G" + # This makes the specifier a bit more "human readable", without + # risking a change in meaning. (Hopefully! Not all edge cases have + # been checked) + parts = [s.strip() for s in str(self).split(",")] + if len(parts) == 0: + return "" + elif len(parts) == 1: + return parts[0] + + return ", ".join(parts[:-1]) + " and " + parts[-1] + + def get_candidate_lookup(self) -> CandidateLookup: + return None, self._ireq + + def is_satisfied_by(self, candidate: Candidate) -> bool: + assert candidate.name == self.name, ( + f"Internal issue: Candidate is not for this requirement " + f"{candidate.name} vs {self.name}" + ) + # We can safely always allow prereleases here since PackageFinder + # already implements the prerelease logic, and would have filtered out + # prerelease candidates if the user does not expect them. + assert self._ireq.req, "Specifier-backed ireq is always PEP 508" + spec = self._ireq.req.specifier + return spec.contains(candidate.version, prereleases=True) + + +class RequiresPythonRequirement(Requirement): + """A requirement representing Requires-Python metadata.""" + + def __init__(self, specifier: SpecifierSet, match: Candidate) -> None: + self.specifier = specifier + self._candidate = match + + def __str__(self) -> str: + return f"Python {self.specifier}" + + def __repr__(self) -> str: + return "{class_name}({specifier!r})".format( + class_name=self.__class__.__name__, + specifier=str(self.specifier), + ) + + @property + def project_name(self) -> NormalizedName: + return self._candidate.project_name + + @property + def name(self) -> str: + return self._candidate.name + + def format_for_error(self) -> str: + return str(self) + + def get_candidate_lookup(self) -> CandidateLookup: + if self.specifier.contains(self._candidate.version, prereleases=True): + return self._candidate, None + return None, None + + def is_satisfied_by(self, candidate: Candidate) -> bool: + assert candidate.name == self._candidate.name, "Not Python candidate" + # We can safely always allow prereleases here since PackageFinder + # already implements the prerelease logic, and would have filtered out + # prerelease candidates if the user does not expect them. + return self.specifier.contains(candidate.version, prereleases=True) + + +class UnsatisfiableRequirement(Requirement): + """A requirement that cannot be satisfied.""" + + def __init__(self, name: NormalizedName) -> None: + self._name = name + + def __str__(self) -> str: + return f"{self._name} (unavailable)" + + def __repr__(self) -> str: + return "{class_name}({name!r})".format( + class_name=self.__class__.__name__, + name=str(self._name), + ) + + @property + def project_name(self) -> NormalizedName: + return self._name + + @property + def name(self) -> str: + return self._name + + def format_for_error(self) -> str: + return str(self) + + def get_candidate_lookup(self) -> CandidateLookup: + return None, None + + def is_satisfied_by(self, candidate: Candidate) -> bool: + return False diff --git a/pipenv/patched/notpip/_internal/resolution/resolvelib/resolver.py b/pipenv/patched/notpip/_internal/resolution/resolvelib/resolver.py new file mode 100644 index 00000000..99d5984f --- /dev/null +++ b/pipenv/patched/notpip/_internal/resolution/resolvelib/resolver.py @@ -0,0 +1,272 @@ +import functools +import logging +import os +from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, cast + +from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name +from pipenv.patched.notpip._vendor.resolvelib import BaseReporter, ResolutionImpossible +from pipenv.patched.notpip._vendor.resolvelib import Resolver as RLResolver +from pipenv.patched.notpip._vendor.resolvelib.structs import DirectedGraph + +from pipenv.patched.notpip._internal.cache import WheelCache +from pipenv.patched.notpip._internal.index.package_finder import PackageFinder +from pipenv.patched.notpip._internal.operations.prepare import RequirementPreparer +from pipenv.patched.notpip._internal.req.req_install import InstallRequirement +from pipenv.patched.notpip._internal.req.req_set import RequirementSet +from pipenv.patched.notpip._internal.resolution.base import BaseResolver, InstallRequirementProvider +from pipenv.patched.notpip._internal.resolution.resolvelib.provider import PipProvider +from pipenv.patched.notpip._internal.resolution.resolvelib.reporter import ( + PipDebuggingReporter, + PipReporter, +) +from pipenv.patched.notpip._internal.utils.deprecation import deprecated +from pipenv.patched.notpip._internal.utils.filetypes import is_archive_file + +from .base import Candidate, Requirement +from .factory import Factory + +if TYPE_CHECKING: + from pipenv.patched.notpip._vendor.resolvelib.resolvers import Result as RLResult + + Result = RLResult[Requirement, Candidate, str] + + +logger = logging.getLogger(__name__) + + +class Resolver(BaseResolver): + _allowed_strategies = {"eager", "only-if-needed", "to-satisfy-only"} + + def __init__( + self, + preparer: RequirementPreparer, + finder: PackageFinder, + wheel_cache: Optional[WheelCache], + make_install_req: InstallRequirementProvider, + use_user_site: bool, + ignore_dependencies: bool, + ignore_installed: bool, + ignore_requires_python: bool, + force_reinstall: bool, + upgrade_strategy: str, + py_version_info: Optional[Tuple[int, ...]] = None, + ): + super().__init__() + assert upgrade_strategy in self._allowed_strategies + + self.factory = Factory( + finder=finder, + preparer=preparer, + make_install_req=make_install_req, + wheel_cache=wheel_cache, + use_user_site=use_user_site, + force_reinstall=force_reinstall, + ignore_installed=ignore_installed, + ignore_requires_python=ignore_requires_python, + py_version_info=py_version_info, + ) + self.ignore_dependencies = ignore_dependencies + self.upgrade_strategy = upgrade_strategy + self._result: Optional[Result] = None + + def resolve( + self, root_reqs: List[InstallRequirement], check_supported_wheels: bool + ) -> RequirementSet: + collected = self.factory.collect_root_requirements(root_reqs) + provider = PipProvider( + factory=self.factory, + constraints=collected.constraints, + ignore_dependencies=self.ignore_dependencies, + upgrade_strategy=self.upgrade_strategy, + user_requested=collected.user_requested, + ) + if "PIP_RESOLVER_DEBUG" in os.environ: + reporter: BaseReporter = PipDebuggingReporter() + else: + reporter = PipReporter() + resolver: RLResolver[Requirement, Candidate, str] = RLResolver( + provider, + reporter, + ) + + try: + try_to_avoid_resolution_too_deep = 2000000 + result = self._result = resolver.resolve( + collected.requirements, max_rounds=try_to_avoid_resolution_too_deep + ) + + except ResolutionImpossible as e: + error = self.factory.get_installation_error( + cast("ResolutionImpossible[Requirement, Candidate]", e), + collected.constraints, + ) + raise error from e + + req_set = RequirementSet(check_supported_wheels=check_supported_wheels) + for candidate in result.mapping.values(): + ireq = candidate.get_install_requirement() + if ireq is None: + continue + + # Check if there is already an installation under the same name, + # and set a flag for later stages to uninstall it, if needed. + installed_dist = self.factory.get_dist_to_uninstall(candidate) + if installed_dist is None: + # There is no existing installation -- nothing to uninstall. + ireq.should_reinstall = False + elif self.factory.force_reinstall: + # The --force-reinstall flag is set -- reinstall. + ireq.should_reinstall = True + elif installed_dist.version != candidate.version: + # The installation is different in version -- reinstall. + ireq.should_reinstall = True + elif candidate.is_editable or installed_dist.editable: + # The incoming distribution is editable, or different in + # editable-ness to installation -- reinstall. + ireq.should_reinstall = True + elif candidate.source_link and candidate.source_link.is_file: + # The incoming distribution is under file:// + if candidate.source_link.is_wheel: + # is a local wheel -- do nothing. + logger.info( + "%s is already installed with the same version as the " + "provided wheel. Use --force-reinstall to force an " + "installation of the wheel.", + ireq.name, + ) + continue + + looks_like_sdist = ( + is_archive_file(candidate.source_link.file_path) + and candidate.source_link.ext != ".zip" + ) + if looks_like_sdist: + # is a local sdist -- show a deprecation warning! + reason = ( + "Source distribution is being reinstalled despite an " + "installed package having the same name and version as " + "the installed package." + ) + replacement = "use --force-reinstall" + deprecated( + reason=reason, + replacement=replacement, + gone_in="21.3", + issue=8711, + ) + + # is a local sdist or path -- reinstall + ireq.should_reinstall = True + else: + continue + + link = candidate.source_link + if link and link.is_yanked: + # The reason can contain non-ASCII characters, Unicode + # is required for Python 2. + msg = ( + "The candidate selected for download or install is a " + "yanked version: {name!r} candidate (version {version} " + "at {link})\nReason for being yanked: {reason}" + ).format( + name=candidate.name, + version=candidate.version, + link=link, + reason=link.yanked_reason or "", + ) + logger.warning(msg) + + req_set.add_named_requirement(ireq) + + reqs = req_set.all_requirements + self.factory.preparer.prepare_linked_requirements_more(reqs) + return req_set + + def get_installation_order( + self, req_set: RequirementSet + ) -> List[InstallRequirement]: + """Get order for installation of requirements in RequirementSet. + + The returned list contains a requirement before another that depends on + it. This helps ensure that the environment is kept consistent as they + get installed one-by-one. + + The current implementation creates a topological ordering of the + dependency graph, while breaking any cycles in the graph at arbitrary + points. We make no guarantees about where the cycle would be broken, + other than they would be broken. + """ + assert self._result is not None, "must call resolve() first" + + graph = self._result.graph + weights = get_topological_weights( + graph, + expected_node_count=len(self._result.mapping) + 1, + ) + + sorted_items = sorted( + req_set.requirements.items(), + key=functools.partial(_req_set_item_sorter, weights=weights), + reverse=True, + ) + return [ireq for _, ireq in sorted_items] + + +def get_topological_weights( + graph: "DirectedGraph[Optional[str]]", expected_node_count: int +) -> Dict[Optional[str], int]: + """Assign weights to each node based on how "deep" they are. + + This implementation may change at any point in the future without prior + notice. + + We take the length for the longest path to any node from root, ignoring any + paths that contain a single node twice (i.e. cycles). This is done through + a depth-first search through the graph, while keeping track of the path to + the node. + + Cycles in the graph result would result in node being revisited while also + being it's own path. In this case, take no action. This helps ensure we + don't get stuck in a cycle. + + When assigning weight, the longer path (i.e. larger length) is preferred. + """ + path: Set[Optional[str]] = set() + weights: Dict[Optional[str], int] = {} + + def visit(node: Optional[str]) -> None: + if node in path: + # We hit a cycle, so we'll break it here. + return + + # Time to visit the children! + path.add(node) + for child in graph.iter_children(node): + visit(child) + path.remove(node) + + last_known_parent_count = weights.get(node, 0) + weights[node] = max(last_known_parent_count, len(path)) + + # `None` is guaranteed to be the root node by resolvelib. + visit(None) + + # Sanity checks + assert weights[None] == 0 + assert len(weights) == expected_node_count + + return weights + + +def _req_set_item_sorter( + item: Tuple[str, InstallRequirement], + weights: Dict[Optional[str], int], +) -> Tuple[int, str]: + """Key function used to sort install requirements for installation. + + Based on the "weight" mapping calculated in ``get_installation_order()``. + The canonical package name is returned as the second member as a tie- + breaker to ensure the result is predictable, which is useful in tests. + """ + name = canonicalize_name(item[0]) + return weights[name], name diff --git a/pipenv/patched/notpip/_internal/resolve.py b/pipenv/patched/notpip/_internal/resolve.py deleted file mode 100644 index e42dd3d4..00000000 --- a/pipenv/patched/notpip/_internal/resolve.py +++ /dev/null @@ -1,425 +0,0 @@ -"""Dependency Resolution - -The dependency resolution in pip is performed as follows: - -for top-level requirements: - a. only one spec allowed per project, regardless of conflicts or not. - otherwise a "double requirement" exception is raised - b. they override sub-dependency requirements. -for sub-dependencies - a. "first found, wins" (where the order is breadth first) -""" - -import logging -from collections import defaultdict -from itertools import chain - -from pipenv.patched.notpip._internal.exceptions import ( - BestVersionAlreadyInstalled, DistributionNotFound, HashError, HashErrors, - UnsupportedPythonVersion, -) -from pipenv.patched.notpip._internal.req.constructors import install_req_from_req_string -from pipenv.patched.notpip._internal.req.req_install import InstallRequirement -from pipenv.patched.notpip._internal.utils.logging import indent_log -from pipenv.patched.notpip._internal.utils.misc import dist_in_usersite, ensure_dir -from pipenv.patched.notpip._internal.utils.packaging import check_dist_requires_python -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING - -if MYPY_CHECK_RUNNING: - from typing import Optional, DefaultDict, List, Set # noqa: F401 - from pipenv.patched.notpip._internal.download import PipSession # noqa: F401 - from pipenv.patched.notpip._internal.req.req_install import InstallRequirement # noqa: F401 - from pipenv.patched.notpip._internal.index import PackageFinder # noqa: F401 - from pipenv.patched.notpip._internal.req.req_set import RequirementSet # noqa: F401 - from pipenv.patched.notpip._internal.operations.prepare import ( # noqa: F401 - DistAbstraction, RequirementPreparer - ) - from pipenv.patched.notpip._internal.cache import WheelCache # noqa: F401 - -logger = logging.getLogger(__name__) - - -class Resolver(object): - """Resolves which packages need to be installed/uninstalled to perform \ - the requested operation without breaking the requirements of any package. - """ - - _allowed_strategies = {"eager", "only-if-needed", "to-satisfy-only"} - - def __init__( - self, - preparer, # type: RequirementPreparer - session, # type: PipSession - finder, # type: PackageFinder - wheel_cache, # type: Optional[WheelCache] - use_user_site, # type: bool - ignore_dependencies, # type: bool - ignore_installed, # type: bool - ignore_requires_python, # type: bool - force_reinstall, # type: bool - isolated, # type: bool - upgrade_strategy, # type: str - use_pep517=None, # type: Optional[bool] - ignore_compatibility=False, # type: bool - ): - # type: (...) -> None - super(Resolver, self).__init__() - assert upgrade_strategy in self._allowed_strategies - - self.preparer = preparer - self.finder = finder - self.session = session - - # NOTE: This would eventually be replaced with a cache that can give - # information about both sdist and wheels transparently. - self.wheel_cache = wheel_cache - - # This is set in resolve - self.require_hashes = None # type: Optional[bool] - - self.upgrade_strategy = upgrade_strategy - self.force_reinstall = force_reinstall - self.isolated = isolated - self.ignore_dependencies = ignore_dependencies - self.ignore_installed = ignore_installed - self.ignore_requires_python = ignore_requires_python - self.ignore_compatibility = ignore_compatibility - self.use_user_site = use_user_site - self.use_pep517 = use_pep517 - self.requires_python = None - if self.ignore_compatibility: - self.ignore_requires_python = True - - self._discovered_dependencies = \ - defaultdict(list) # type: DefaultDict[str, List] - - def resolve(self, requirement_set): - # type: (RequirementSet) -> None - """Resolve what operations need to be done - - As a side-effect of this method, the packages (and their dependencies) - are downloaded, unpacked and prepared for installation. This - preparation is done by ``pip.operations.prepare``. - - Once PyPI has static dependency metadata available, it would be - possible to move the preparation to become a step separated from - dependency resolution. - """ - # make the wheelhouse - if self.preparer.wheel_download_dir: - ensure_dir(self.preparer.wheel_download_dir) - - # If any top-level requirement has a hash specified, enter - # hash-checking mode, which requires hashes from all. - root_reqs = ( - requirement_set.unnamed_requirements + - list(requirement_set.requirements.values()) - ) - self.require_hashes = ( - requirement_set.require_hashes or - any(req.has_hash_options for req in root_reqs) - ) - - # Display where finder is looking for packages - locations = self.finder.get_formatted_locations() - if locations: - logger.info(locations) - - # Actually prepare the files, and collect any exceptions. Most hash - # exceptions cannot be checked ahead of time, because - # req.populate_link() needs to be called before we can make decisions - # based on link type. - discovered_reqs = [] # type: List[InstallRequirement] - hash_errors = HashErrors() - for req in chain(root_reqs, discovered_reqs): - try: - discovered_reqs.extend( - self._resolve_one(requirement_set, req) - ) - except HashError as exc: - exc.req = req - hash_errors.append(exc) - - if hash_errors: - raise hash_errors - - def _is_upgrade_allowed(self, req): - # type: (InstallRequirement) -> bool - if self.upgrade_strategy == "to-satisfy-only": - return False - elif self.upgrade_strategy == "eager": - return True - else: - assert self.upgrade_strategy == "only-if-needed" - return req.is_direct - - def _set_req_to_reinstall(self, req): - # type: (InstallRequirement) -> None - """ - Set a requirement to be installed. - """ - # Don't uninstall the conflict if doing a user install and the - # conflict is not a user install. - if not self.use_user_site or dist_in_usersite(req.satisfied_by): - req.conflicts_with = req.satisfied_by - req.satisfied_by = None - - # XXX: Stop passing requirement_set for options - def _check_skip_installed(self, req_to_install): - # type: (InstallRequirement) -> Optional[str] - """Check if req_to_install should be skipped. - - This will check if the req is installed, and whether we should upgrade - or reinstall it, taking into account all the relevant user options. - - After calling this req_to_install will only have satisfied_by set to - None if the req_to_install is to be upgraded/reinstalled etc. Any - other value will be a dist recording the current thing installed that - satisfies the requirement. - - Note that for vcs urls and the like we can't assess skipping in this - routine - we simply identify that we need to pull the thing down, - then later on it is pulled down and introspected to assess upgrade/ - reinstalls etc. - - :return: A text reason for why it was skipped, or None. - """ - if self.ignore_installed: - return None - - req_to_install.check_if_exists(self.use_user_site) - if not req_to_install.satisfied_by: - return None - - if self.force_reinstall: - self._set_req_to_reinstall(req_to_install) - return None - - if not self._is_upgrade_allowed(req_to_install): - if self.upgrade_strategy == "only-if-needed": - return 'already satisfied, skipping upgrade' - return 'already satisfied' - - # Check for the possibility of an upgrade. For link-based - # requirements we have to pull the tree down and inspect to assess - # the version #, so it's handled way down. - if not req_to_install.link: - try: - self.finder.find_requirement(req_to_install, upgrade=True) - except BestVersionAlreadyInstalled: - # Then the best version is installed. - return 'already up-to-date' - except DistributionNotFound: - # No distribution found, so we squash the error. It will - # be raised later when we re-try later to do the install. - # Why don't we just raise here? - pass - - self._set_req_to_reinstall(req_to_install) - return None - - def _get_abstract_dist_for(self, req): - # type: (InstallRequirement) -> DistAbstraction - """Takes a InstallRequirement and returns a single AbstractDist \ - representing a prepared variant of the same. - """ - assert self.require_hashes is not None, ( - "require_hashes should have been set in Resolver.resolve()" - ) - - if req.editable: - return self.preparer.prepare_editable_requirement( - req, self.require_hashes, self.use_user_site, self.finder, - ) - - # satisfied_by is only evaluated by calling _check_skip_installed, - # so it must be None here. - assert req.satisfied_by is None - skip_reason = self._check_skip_installed(req) - - if req.satisfied_by: - return self.preparer.prepare_installed_requirement( - req, self.require_hashes, skip_reason - ) - - upgrade_allowed = self._is_upgrade_allowed(req) - abstract_dist = self.preparer.prepare_linked_requirement( - req, self.session, self.finder, upgrade_allowed, - self.require_hashes - ) - - # NOTE - # The following portion is for determining if a certain package is - # going to be re-installed/upgraded or not and reporting to the user. - # This should probably get cleaned up in a future refactor. - - # req.req is only avail after unpack for URL - # pkgs repeat check_if_exists to uninstall-on-upgrade - # (#14) - if not self.ignore_installed: - req.check_if_exists(self.use_user_site) - - if req.satisfied_by: - should_modify = ( - self.upgrade_strategy != "to-satisfy-only" or - self.force_reinstall or - self.ignore_installed or - req.link.scheme == 'file' - ) - if should_modify: - self._set_req_to_reinstall(req) - else: - logger.info( - 'Requirement already satisfied (use --upgrade to upgrade):' - ' %s', req, - ) - - return abstract_dist - - def _resolve_one( - self, - requirement_set, # type: RequirementSet - req_to_install, # type: InstallRequirement - ignore_requires_python=False # type: bool - ): - # type: (...) -> List[InstallRequirement] - """Prepare a single requirements file. - - :return: A list of additional InstallRequirements to also install. - """ - # Tell user what we are doing for this requirement: - # obtain (editable), skipping, processing (local url), collecting - # (remote url or package name) - if req_to_install.constraint or req_to_install.prepared: - return [] - - req_to_install.prepared = True - - # register tmp src for cleanup in case something goes wrong - requirement_set.reqs_to_cleanup.append(req_to_install) - - abstract_dist = self._get_abstract_dist_for(req_to_install) - - # Parse and return dependencies - dist = abstract_dist.dist() - try: - check_dist_requires_python(dist) - except UnsupportedPythonVersion as err: - if self.ignore_requires_python or ignore_requires_python or self.ignore_compatibility: - logger.warning(err.args[0]) - else: - raise - - # A huge hack, by Kenneth Reitz. - try: - self.requires_python = check_dist_requires_python(dist, absorb=False) - except TypeError: - self.requires_python = None - - - more_reqs = [] # type: List[InstallRequirement] - - def add_req(subreq, extras_requested): - sub_install_req = install_req_from_req_string( - str(subreq), - req_to_install, - isolated=self.isolated, - wheel_cache=self.wheel_cache, - use_pep517=self.use_pep517 - ) - parent_req_name = req_to_install.name - to_scan_again, add_to_parent = requirement_set.add_requirement( - sub_install_req, - parent_req_name=parent_req_name, - extras_requested=extras_requested, - ) - if parent_req_name and add_to_parent: - self._discovered_dependencies[parent_req_name].append( - add_to_parent - ) - more_reqs.extend(to_scan_again) - - with indent_log(): - # We add req_to_install before its dependencies, so that we - # can refer to it when adding dependencies. - if not requirement_set.has_requirement(req_to_install.name): - available_requested = sorted( - set(dist.extras) & set(req_to_install.extras) - ) - # 'unnamed' requirements will get added here - req_to_install.is_direct = True - requirement_set.add_requirement( - req_to_install, parent_req_name=None, - extras_requested=available_requested, - ) - - if not self.ignore_dependencies: - if req_to_install.extras: - logger.debug( - "Installing extra requirements: %r", - ','.join(req_to_install.extras), - ) - missing_requested = sorted( - set(req_to_install.extras) - set(dist.extras) - ) - for missing in missing_requested: - logger.warning( - '%s does not provide the extra \'%s\'', - dist, missing - ) - - available_requested = sorted( - set(dist.extras) & set(req_to_install.extras) - ) - for subreq in dist.requires(available_requested): - add_req(subreq, extras_requested=available_requested) - - # Hack for deep-resolving extras. - for available in available_requested: - if hasattr(dist, '_DistInfoDistribution__dep_map'): - for req in dist._DistInfoDistribution__dep_map[available]: - req = InstallRequirement( - req, - req_to_install, - isolated=self.isolated, - wheel_cache=self.wheel_cache, - use_pep517=None - ) - - more_reqs.append(req) - - if not req_to_install.editable and not req_to_install.satisfied_by: - # XXX: --no-install leads this to report 'Successfully - # downloaded' for only non-editable reqs, even though we took - # action on them. - requirement_set.successfully_downloaded.append(req_to_install) - - return more_reqs - - def get_installation_order(self, req_set): - # type: (RequirementSet) -> List[InstallRequirement] - """Create the installation order. - - The installation order is topological - requirements are installed - before the requiring thing. We break cycles at an arbitrary point, - and make no other guarantees. - """ - # The current implementation, which we may change at any point - # installs the user specified things in the order given, except when - # dependencies must come earlier to achieve topological order. - order = [] - ordered_reqs = set() # type: Set[InstallRequirement] - - def schedule(req): - if req.satisfied_by or req in ordered_reqs: - return - if req.constraint: - return - ordered_reqs.add(req) - for dep in self._discovered_dependencies[req.name]: - schedule(dep) - order.append(req) - - for install_req in req_set.requirements.values(): - schedule(install_req) - return order diff --git a/pipenv/patched/notpip/_internal/self_outdated_check.py b/pipenv/patched/notpip/_internal/self_outdated_check.py new file mode 100644 index 00000000..acc8d781 --- /dev/null +++ b/pipenv/patched/notpip/_internal/self_outdated_check.py @@ -0,0 +1,187 @@ +import datetime +import hashlib +import json +import logging +import optparse +import os.path +import sys +from typing import Any, Dict + +from pipenv.patched.notpip._vendor.packaging.version import parse as parse_version + +from pipenv.patched.notpip._internal.index.collector import LinkCollector +from pipenv.patched.notpip._internal.index.package_finder import PackageFinder +from pipenv.patched.notpip._internal.metadata import get_default_environment +from pipenv.patched.notpip._internal.models.selection_prefs import SelectionPreferences +from pipenv.patched.notpip._internal.network.session import PipSession +from pipenv.patched.notpip._internal.utils.filesystem import adjacent_tmp_file, check_path_owner, replace +from pipenv.patched.notpip._internal.utils.misc import ensure_dir + +SELFCHECK_DATE_FMT = "%Y-%m-%dT%H:%M:%SZ" + + +logger = logging.getLogger(__name__) + + +def _get_statefile_name(key): + # type: (str) -> str + key_bytes = key.encode() + name = hashlib.sha224(key_bytes).hexdigest() + return name + + +class SelfCheckState: + def __init__(self, cache_dir): + # type: (str) -> None + self.state = {} # type: Dict[str, Any] + self.statefile_path = None + + # Try to load the existing state + if cache_dir: + self.statefile_path = os.path.join( + cache_dir, "selfcheck", _get_statefile_name(self.key) + ) + try: + with open(self.statefile_path, encoding="utf-8") as statefile: + self.state = json.load(statefile) + except (OSError, ValueError, KeyError): + # Explicitly suppressing exceptions, since we don't want to + # error out if the cache file is invalid. + pass + + @property + def key(self): + # type: () -> str + return sys.prefix + + def save(self, pypi_version, current_time): + # type: (str, datetime.datetime) -> None + # If we do not have a path to cache in, don't bother saving. + if not self.statefile_path: + return + + # Check to make sure that we own the directory + if not check_path_owner(os.path.dirname(self.statefile_path)): + return + + # Now that we've ensured the directory is owned by this user, we'll go + # ahead and make sure that all our directories are created. + ensure_dir(os.path.dirname(self.statefile_path)) + + state = { + # Include the key so it's easy to tell which pip wrote the + # file. + "key": self.key, + "last_check": current_time.strftime(SELFCHECK_DATE_FMT), + "pypi_version": pypi_version, + } + + text = json.dumps(state, sort_keys=True, separators=(",", ":")) + + with adjacent_tmp_file(self.statefile_path) as f: + f.write(text.encode()) + + try: + # Since we have a prefix-specific state file, we can just + # overwrite whatever is there, no need to check. + replace(f.name, self.statefile_path) + except OSError: + # Best effort. + pass + + +def was_installed_by_pip(pkg): + # type: (str) -> bool + """Checks whether pkg was installed by pip + + This is used not to display the upgrade message when pip is in fact + installed by system package manager, such as dnf on Fedora. + """ + dist = get_default_environment().get_distribution(pkg) + return dist is not None and "pip" == dist.installer + + +def pip_self_version_check(session, options): + # type: (PipSession, optparse.Values) -> None + """Check for an update for pip. + + Limit the frequency of checks to once per week. State is stored either in + the active virtualenv or in the user's USER_CACHE_DIR keyed off the prefix + of the pip script path. + """ + installed_dist = get_default_environment().get_distribution("pip") + if not installed_dist: + return + + pip_version = installed_dist.version + pypi_version = None + + try: + state = SelfCheckState(cache_dir=options.cache_dir) + + current_time = datetime.datetime.utcnow() + # Determine if we need to refresh the state + if "last_check" in state.state and "pypi_version" in state.state: + last_check = datetime.datetime.strptime( + state.state["last_check"], + SELFCHECK_DATE_FMT + ) + if (current_time - last_check).total_seconds() < 7 * 24 * 60 * 60: + pypi_version = state.state["pypi_version"] + + # Refresh the version if we need to or just see if we need to warn + if pypi_version is None: + # Lets use PackageFinder to see what the latest pip version is + link_collector = LinkCollector.create( + session, + options=options, + suppress_no_index=True, + ) + + # Pass allow_yanked=False so we don't suggest upgrading to a + # yanked version. + selection_prefs = SelectionPreferences( + allow_yanked=False, + allow_all_prereleases=False, # Explicitly set to False + ) + + finder = PackageFinder.create( + link_collector=link_collector, + selection_prefs=selection_prefs, + ) + best_candidate = finder.find_best_candidate("pip").best_candidate + if best_candidate is None: + return + pypi_version = str(best_candidate.version) + + # save that we've performed a check + state.save(pypi_version, current_time) + + remote_version = parse_version(pypi_version) + + local_version_is_older = ( + pip_version < remote_version and + pip_version.base_version != remote_version.base_version and + was_installed_by_pip('pip') + ) + + # Determine if our pypi_version is older + if not local_version_is_older: + return + + # We cannot tell how the current pip is available in the current + # command context, so be pragmatic here and suggest the command + # that's always available. This does not accommodate spaces in + # `sys.executable`. + pip_cmd = f"{sys.executable} -m pip" + logger.warning( + "You are using pip version %s; however, version %s is " + "available.\nYou should consider upgrading via the " + "'%s install --upgrade pip' command.", + pip_version, pypi_version, pip_cmd + ) + except Exception: + logger.debug( + "There was an error checking the latest version of pip", + exc_info=True, + ) diff --git a/pipenv/patched/notpip/_internal/utils/_log.py b/pipenv/patched/notpip/_internal/utils/_log.py new file mode 100644 index 00000000..2705c259 --- /dev/null +++ b/pipenv/patched/notpip/_internal/utils/_log.py @@ -0,0 +1,38 @@ +"""Customize logging + +Defines custom logger class for the `logger.verbose(...)` method. + +init_logging() must be called before any other modules that call logging.getLogger. +""" + +import logging +from typing import Any, cast + +# custom log level for `--verbose` output +# between DEBUG and INFO +VERBOSE = 15 + + +class VerboseLogger(logging.Logger): + """Custom Logger, defining a verbose log-level + + VERBOSE is between INFO and DEBUG. + """ + + def verbose(self, msg: str, *args: Any, **kwargs: Any) -> None: + return self.log(VERBOSE, msg, *args, **kwargs) + + +def getLogger(name: str) -> VerboseLogger: + """logging.getLogger, but ensures our VerboseLogger class is returned""" + return cast(VerboseLogger, logging.getLogger(name)) + + +def init_logging() -> None: + """Register our VerboseLogger and VERBOSE log level. + + Should be called before any calls to getLogger(), + i.e. in pipenv.patched.notpip._internal.__init__ + """ + logging.setLoggerClass(VerboseLogger) + logging.addLevelName(VERBOSE, "VERBOSE") diff --git a/pipenv/patched/notpip/_internal/utils/appdirs.py b/pipenv/patched/notpip/_internal/utils/appdirs.py index 9ce3a1b3..1e998365 100644 --- a/pipenv/patched/notpip/_internal/utils/appdirs.py +++ b/pipenv/patched/notpip/_internal/utils/appdirs.py @@ -1,270 +1,35 @@ """ -This code was taken from https://github.com/ActiveState/appdirs and modified -to suit our purposes. +This code wraps the vendored appdirs module to so the return values are +compatible for the current pip code base. + +The intention is to rewrite current usages gradually, keeping the tests pass, +and eventually drop this after all usages are changed. """ -from __future__ import absolute_import import os -import sys +from typing import List -from pipenv.patched.notpip._vendor.six import PY2, text_type - -from pipenv.patched.notpip._internal.utils.compat import WINDOWS, expanduser -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING - -if MYPY_CHECK_RUNNING: - from typing import ( # noqa: F401 - List, Union - ) +from pipenv.patched.notpip._vendor import appdirs as _appdirs -def user_cache_dir(appname): - # type: (str) -> str - r""" - Return full path to the user-specific cache dir for this application. +def user_cache_dir(appname: str) -> str: + return _appdirs.user_cache_dir(appname, appauthor=False) - "appname" is the name of application. - - Typical user cache directories are: - macOS: ~/Library/Caches/ - Unix: ~/.cache/ (XDG default) - Windows: C:\Users\\AppData\Local\\Cache - - On Windows the only suggestion in the MSDN docs is that local settings go - in the `CSIDL_LOCAL_APPDATA` directory. This is identical to the - non-roaming app data dir (the default returned by `user_data_dir`). Apps - typically put cache data somewhere *under* the given dir here. Some - examples: - ...\Mozilla\Firefox\Profiles\\Cache - ...\Acme\SuperApp\Cache\1.0 - - OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value. - """ - if WINDOWS: - # Get the base path - path = os.path.normpath(_get_win_folder("CSIDL_LOCAL_APPDATA")) - - # When using Python 2, return paths as bytes on Windows like we do on - # other operating systems. See helper function docs for more details. - if PY2 and isinstance(path, text_type): - path = _win_path_to_bytes(path) - - # Add our app name and Cache directory to it - path = os.path.join(path, appname, "Cache") - elif sys.platform == "darwin": - # Get the base path - path = expanduser("~/Library/Caches") - - # Add our app name to it - path = os.path.join(path, appname) - else: - # Get the base path - path = os.getenv("XDG_CACHE_HOME", expanduser("~/.cache")) - - # Add our app name to it - path = os.path.join(path, appname) +def user_config_dir(appname: str, roaming: bool = True) -> str: + path = _appdirs.user_config_dir(appname, appauthor=False, roaming=roaming) + if _appdirs.system == "darwin" and not os.path.isdir(path): + path = os.path.expanduser("~/.config/") + if appname: + path = os.path.join(path, appname) return path -def user_data_dir(appname, roaming=False): - # type: (str, bool) -> str - r""" - Return full path to the user-specific data dir for this application. - - "appname" is the name of application. - If None, just the system directory is returned. - "roaming" (boolean, default False) can be set True to use the Windows - roaming appdata directory. That means that for users on a Windows - network setup for roaming profiles, this user data will be - sync'd on login. See - - for a discussion of issues. - - Typical user data directories are: - macOS: ~/Library/Application Support/ - if it exists, else ~/.config/ - Unix: ~/.local/share/ # or in - $XDG_DATA_HOME, if defined - Win XP (not roaming): C:\Documents and Settings\\ ... - ...Application Data\ - Win XP (roaming): C:\Documents and Settings\\Local ... - ...Settings\Application Data\ - Win 7 (not roaming): C:\\Users\\AppData\Local\ - Win 7 (roaming): C:\\Users\\AppData\Roaming\ - - For Unix, we follow the XDG spec and support $XDG_DATA_HOME. - That means, by default "~/.local/share/". - """ - if WINDOWS: - const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA" - path = os.path.join(os.path.normpath(_get_win_folder(const)), appname) - elif sys.platform == "darwin": - path = os.path.join( - expanduser('~/Library/Application Support/'), - appname, - ) if os.path.isdir(os.path.join( - expanduser('~/Library/Application Support/'), - appname, - ) - ) else os.path.join( - expanduser('~/.config/'), - appname, - ) - else: - path = os.path.join( - os.getenv('XDG_DATA_HOME', expanduser("~/.local/share")), - appname, - ) - - return path - - -def user_config_dir(appname, roaming=True): - # type: (str, bool) -> str - """Return full path to the user-specific config dir for this application. - - "appname" is the name of application. - If None, just the system directory is returned. - "roaming" (boolean, default True) can be set False to not use the - Windows roaming appdata directory. That means that for users on a - Windows network setup for roaming profiles, this user data will be - sync'd on login. See - - for a discussion of issues. - - Typical user data directories are: - macOS: same as user_data_dir - Unix: ~/.config/ - Win *: same as user_data_dir - - For Unix, we follow the XDG spec and support $XDG_CONFIG_HOME. - That means, by default "~/.config/". - """ - if WINDOWS: - path = user_data_dir(appname, roaming=roaming) - elif sys.platform == "darwin": - path = user_data_dir(appname) - else: - path = os.getenv('XDG_CONFIG_HOME', expanduser("~/.config")) - path = os.path.join(path, appname) - - return path - - -# for the discussion regarding site_config_dirs locations +# for the discussion regarding site_config_dir locations # see -def site_config_dirs(appname): - # type: (str) -> List[str] - r"""Return a list of potential user-shared config dirs for this application. - - "appname" is the name of application. - - Typical user config directories are: - macOS: /Library/Application Support// - Unix: /etc or $XDG_CONFIG_DIRS[i]// for each value in - $XDG_CONFIG_DIRS - Win XP: C:\Documents and Settings\All Users\Application ... - ...Data\\ - Vista: (Fail! "C:\ProgramData" is a hidden *system* directory - on Vista.) - Win 7: Hidden, but writeable on Win 7: - C:\ProgramData\\ - """ - if WINDOWS: - path = os.path.normpath(_get_win_folder("CSIDL_COMMON_APPDATA")) - pathlist = [os.path.join(path, appname)] - elif sys.platform == 'darwin': - pathlist = [os.path.join('/Library/Application Support', appname)] - else: - # try looking in $XDG_CONFIG_DIRS - xdg_config_dirs = os.getenv('XDG_CONFIG_DIRS', '/etc/xdg') - if xdg_config_dirs: - pathlist = [ - os.path.join(expanduser(x), appname) - for x in xdg_config_dirs.split(os.pathsep) - ] - else: - pathlist = [] - +def site_config_dirs(appname: str) -> List[str]: + dirval = _appdirs.site_config_dir(appname, appauthor=False, multipath=True) + if _appdirs.system not in ["win32", "darwin"]: # always look in /etc directly as well - pathlist.append('/etc') - - return pathlist - - -# -- Windows support functions -- - -def _get_win_folder_from_registry(csidl_name): - # type: (str) -> str - """ - This is a fallback technique at best. I'm not sure if using the - registry for this guarantees us the correct answer for all CSIDL_* - names. - """ - import _winreg - - shell_folder_name = { - "CSIDL_APPDATA": "AppData", - "CSIDL_COMMON_APPDATA": "Common AppData", - "CSIDL_LOCAL_APPDATA": "Local AppData", - }[csidl_name] - - key = _winreg.OpenKey( - _winreg.HKEY_CURRENT_USER, - r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" - ) - directory, _type = _winreg.QueryValueEx(key, shell_folder_name) - return directory - - -def _get_win_folder_with_ctypes(csidl_name): - # type: (str) -> str - csidl_const = { - "CSIDL_APPDATA": 26, - "CSIDL_COMMON_APPDATA": 35, - "CSIDL_LOCAL_APPDATA": 28, - }[csidl_name] - - buf = ctypes.create_unicode_buffer(1024) - ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf) - - # Downgrade to short path name if have highbit chars. See - # . - has_high_char = False - for c in buf: - if ord(c) > 255: - has_high_char = True - break - if has_high_char: - buf2 = ctypes.create_unicode_buffer(1024) - if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024): - buf = buf2 - - return buf.value - - -if WINDOWS: - try: - import ctypes - _get_win_folder = _get_win_folder_with_ctypes - except ImportError: - _get_win_folder = _get_win_folder_from_registry - - -def _win_path_to_bytes(path): - """Encode Windows paths to bytes. Only used on Python 2. - - Motivation is to be consistent with other operating systems where paths - are also returned as bytes. This avoids problems mixing bytes and Unicode - elsewhere in the codebase. For more details and discussion see - . - - If encoding using ASCII and MBCS fails, return the original Unicode path. - """ - for encoding in ('ASCII', 'MBCS'): - try: - return path.encode(encoding) - except (UnicodeEncodeError, LookupError): - pass - return path + return dirval.split(os.pathsep) + ["/etc"] + return [dirval] diff --git a/pipenv/patched/notpip/_internal/utils/compat.py b/pipenv/patched/notpip/_internal/utils/compat.py index 1dad56b0..46974024 100644 --- a/pipenv/patched/notpip/_internal/utils/compat.py +++ b/pipenv/patched/notpip/_internal/utils/compat.py @@ -1,150 +1,30 @@ """Stuff that differs in different Python versions and platform distributions.""" -from __future__ import absolute_import, division -import codecs -import locale import logging import os -import shutil import sys -from pipenv.patched.notpip._vendor.six import text_type - -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING - -if MYPY_CHECK_RUNNING: - from typing import Tuple, Text # noqa: F401 - -try: - import ipaddress -except ImportError: - try: - from pipenv.patched.notpip._vendor import ipaddress # type: ignore - except ImportError: - import ipaddr as ipaddress # type: ignore - ipaddress.ip_address = ipaddress.IPAddress # type: ignore - ipaddress.ip_network = ipaddress.IPNetwork # type: ignore - - -__all__ = [ - "ipaddress", "uses_pycache", "console_to_str", "native_str", - "get_path_uid", "stdlib_pkgs", "WINDOWS", "samefile", "get_terminal_size", - "get_extension_suffixes", -] +__all__ = ["get_path_uid", "stdlib_pkgs", "WINDOWS"] logger = logging.getLogger(__name__) -if sys.version_info >= (3, 4): - uses_pycache = True - from importlib.util import cache_from_source -else: - import imp +def has_tls() -> bool: try: - cache_from_source = imp.cache_from_source # type: ignore - except AttributeError: - # does not use __pycache__ - cache_from_source = None + import _ssl # noqa: F401 # ignore unused - uses_pycache = cache_from_source is not None + return True + except ImportError: + pass + + from pipenv.patched.notpip._vendor.urllib3.util import IS_PYOPENSSL + + return IS_PYOPENSSL -if sys.version_info >= (3, 5): - backslashreplace_decode = "backslashreplace" -else: - # In version 3.4 and older, backslashreplace exists - # but does not support use for decoding. - # We implement our own replace handler for this - # situation, so that we can consistently use - # backslash replacement for all versions. - def backslashreplace_decode_fn(err): - raw_bytes = (err.object[i] for i in range(err.start, err.end)) - if sys.version_info[0] == 2: - # Python 2 gave us characters - convert to numeric bytes - raw_bytes = (ord(b) for b in raw_bytes) - return u"".join(u"\\x%x" % c for c in raw_bytes), err.end - codecs.register_error( - "backslashreplace_decode", - backslashreplace_decode_fn, - ) - backslashreplace_decode = "backslashreplace_decode" - - -def console_to_str(data): - # type: (bytes) -> Text - """Return a string, safe for output, of subprocess output. - - We assume the data is in the locale preferred encoding. - If it won't decode properly, we warn the user but decode as - best we can. - - We also ensure that the output can be safely written to - standard output without encoding errors. - """ - - # First, get the encoding we assume. This is the preferred - # encoding for the locale, unless that is not found, or - # it is ASCII, in which case assume UTF-8 - encoding = locale.getpreferredencoding() - if (not encoding) or codecs.lookup(encoding).name == "ascii": - encoding = "utf-8" - - # Now try to decode the data - if we fail, warn the user and - # decode with replacement. - try: - decoded_data = data.decode(encoding) - except UnicodeDecodeError: - logger.warning( - "Subprocess output does not appear to be encoded as %s", - encoding, - ) - decoded_data = data.decode(encoding, errors=backslashreplace_decode) - - # Make sure we can print the output, by encoding it to the output - # encoding with replacement of unencodable characters, and then - # decoding again. - # We use stderr's encoding because it's less likely to be - # redirected and if we don't find an encoding we skip this - # step (on the assumption that output is wrapped by something - # that won't fail). - # The double getattr is to deal with the possibility that we're - # being called in a situation where sys.__stderr__ doesn't exist, - # or doesn't have an encoding attribute. Neither of these cases - # should occur in normal pip use, but there's no harm in checking - # in case people use pip in (unsupported) unusual situations. - output_encoding = getattr(getattr(sys, "__stderr__", None), - "encoding", None) - - if output_encoding: - output_encoded = decoded_data.encode( - output_encoding, - errors="backslashreplace" - ) - decoded_data = output_encoded.decode(output_encoding) - - return decoded_data - - -if sys.version_info >= (3,): - def native_str(s, replace=False): - # type: (str, bool) -> str - if isinstance(s, bytes): - return s.decode('utf-8', 'replace' if replace else 'strict') - return s - -else: - def native_str(s, replace=False): - # type: (str, bool) -> str - # Replace is ignored -- unicode to UTF-8 can't fail - if isinstance(s, text_type): - return s.encode('utf-8') - return s - - -def get_path_uid(path): - # type: (str) -> int +def get_path_uid(path: str) -> int: """ Return path's uid. @@ -156,7 +36,7 @@ def get_path_uid(path): :raises OSError: When path is a symlink or can't be read. """ - if hasattr(os, 'O_NOFOLLOW'): + if hasattr(os, "O_NOFOLLOW"): fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW) file_uid = os.fstat(fd).st_uid os.close(fd) @@ -167,37 +47,10 @@ def get_path_uid(path): file_uid = os.stat(path).st_uid else: # raise OSError for parity with os.O_NOFOLLOW above - raise OSError( - "%s is a symlink; Will not return uid for symlinks" % path - ) + raise OSError(f"{path} is a symlink; Will not return uid for symlinks") return file_uid -if sys.version_info >= (3, 4): - from importlib.machinery import EXTENSION_SUFFIXES - - def get_extension_suffixes(): - return EXTENSION_SUFFIXES -else: - from imp import get_suffixes - - def get_extension_suffixes(): - return [suffix[0] for suffix in get_suffixes()] - - -def expanduser(path): - # type: (str) -> str - """ - Expand ~ and ~user constructions. - - Includes a workaround for https://bugs.python.org/issue14768 - """ - expanded = os.path.expanduser(path) - if path.startswith('~/') and expanded.startswith('//'): - expanded = expanded[1:] - return expanded - - # packages in the stdlib that may have installation metadata, but should not be # considered 'installed'. this theoretically could be determined based on # dist.location (py27:`sysconfig.get_paths()['stdlib']`, @@ -207,58 +60,4 @@ stdlib_pkgs = {"python", "wsgiref", "argparse"} # windows detection, covers cpython and ironpython -WINDOWS = (sys.platform.startswith("win") or - (sys.platform == 'cli' and os.name == 'nt')) - - -def samefile(file1, file2): - # type: (str, str) -> bool - """Provide an alternative for os.path.samefile on Windows/Python2""" - if hasattr(os.path, 'samefile'): - return os.path.samefile(file1, file2) - else: - path1 = os.path.normcase(os.path.abspath(file1)) - path2 = os.path.normcase(os.path.abspath(file2)) - return path1 == path2 - - -if hasattr(shutil, 'get_terminal_size'): - def get_terminal_size(): - # type: () -> Tuple[int, int] - """ - Returns a tuple (x, y) representing the width(x) and the height(y) - in characters of the terminal window. - """ - return tuple(shutil.get_terminal_size()) # type: ignore -else: - def get_terminal_size(): - # type: () -> Tuple[int, int] - """ - Returns a tuple (x, y) representing the width(x) and the height(y) - in characters of the terminal window. - """ - def ioctl_GWINSZ(fd): - try: - import fcntl - import termios - import struct - cr = struct.unpack_from( - 'hh', - fcntl.ioctl(fd, termios.TIOCGWINSZ, '12345678') - ) - except Exception: - return None - if cr == (0, 0): - return None - return cr - cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2) - if not cr: - try: - fd = os.open(os.ctermid(), os.O_RDONLY) - cr = ioctl_GWINSZ(fd) - os.close(fd) - except Exception: - pass - if not cr: - cr = (os.environ.get('LINES', 25), os.environ.get('COLUMNS', 80)) - return int(cr[1]), int(cr[0]) +WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name == "nt") diff --git a/pipenv/patched/notpip/_internal/utils/compatibility_tags.py b/pipenv/patched/notpip/_internal/utils/compatibility_tags.py new file mode 100644 index 00000000..f1badcc0 --- /dev/null +++ b/pipenv/patched/notpip/_internal/utils/compatibility_tags.py @@ -0,0 +1,168 @@ +"""Generate and work with PEP 425 Compatibility Tags. +""" + +import re +from typing import TYPE_CHECKING, List, Optional, Tuple + +from pipenv.patched.notpip._vendor.packaging.tags import ( + Tag, + compatible_tags, + cpython_tags, + generic_tags, + interpreter_name, + interpreter_version, + mac_platforms, +) + +if TYPE_CHECKING: + from pipenv.patched.notpip._vendor.packaging.tags import PythonVersion + + +_osx_arch_pat = re.compile(r"(.+)_(\d+)_(\d+)_(.+)") + + +def version_info_to_nodot(version_info: Tuple[int, ...]) -> str: + # Only use up to the first two numbers. + return "".join(map(str, version_info[:2])) + + +def _mac_platforms(arch: str) -> List[str]: + match = _osx_arch_pat.match(arch) + if match: + name, major, minor, actual_arch = match.groups() + mac_version = (int(major), int(minor)) + arches = [ + # Since we have always only checked that the platform starts + # with "macosx", for backwards-compatibility we extract the + # actual prefix provided by the user in case they provided + # something like "macosxcustom_". It may be good to remove + # this as undocumented or deprecate it in the future. + "{}_{}".format(name, arch[len("macosx_") :]) + for arch in mac_platforms(mac_version, actual_arch) + ] + else: + # arch pattern didn't match (?!) + arches = [arch] + return arches + + +def _custom_manylinux_platforms(arch: str) -> List[str]: + arches = [arch] + arch_prefix, arch_sep, arch_suffix = arch.partition("_") + if arch_prefix == "manylinux2014": + # manylinux1/manylinux2010 wheels run on most manylinux2014 systems + # with the exception of wheels depending on ncurses. PEP 599 states + # manylinux1/manylinux2010 wheels should be considered + # manylinux2014 wheels: + # https://www.python.org/dev/peps/pep-0599/#backwards-compatibility-with-manylinux2010-wheels + if arch_suffix in {"i686", "x86_64"}: + arches.append("manylinux2010" + arch_sep + arch_suffix) + arches.append("manylinux1" + arch_sep + arch_suffix) + elif arch_prefix == "manylinux2010": + # manylinux1 wheels run on most manylinux2010 systems with the + # exception of wheels depending on ncurses. PEP 571 states + # manylinux1 wheels should be considered manylinux2010 wheels: + # https://www.python.org/dev/peps/pep-0571/#backwards-compatibility-with-manylinux1-wheels + arches.append("manylinux1" + arch_sep + arch_suffix) + return arches + + +def _get_custom_platforms(arch: str) -> List[str]: + arch_prefix, arch_sep, arch_suffix = arch.partition("_") + if arch.startswith("macosx"): + arches = _mac_platforms(arch) + elif arch_prefix in ["manylinux2014", "manylinux2010"]: + arches = _custom_manylinux_platforms(arch) + else: + arches = [arch] + return arches + + +def _expand_allowed_platforms(platforms: Optional[List[str]]) -> Optional[List[str]]: + if not platforms: + return None + + seen = set() + result = [] + + for p in platforms: + if p in seen: + continue + additions = [c for c in _get_custom_platforms(p) if c not in seen] + seen.update(additions) + result.extend(additions) + + return result + + +def _get_python_version(version: str) -> "PythonVersion": + if len(version) > 1: + return int(version[0]), int(version[1:]) + else: + return (int(version[0]),) + + +def _get_custom_interpreter( + implementation: Optional[str] = None, version: Optional[str] = None +) -> str: + if implementation is None: + implementation = interpreter_name() + if version is None: + version = interpreter_version() + return f"{implementation}{version}" + + +def get_supported( + version: Optional[str] = None, + platforms: Optional[List[str]] = None, + impl: Optional[str] = None, + abis: Optional[List[str]] = None, +) -> List[Tag]: + """Return a list of supported tags for each version specified in + `versions`. + + :param version: a string version, of the form "33" or "32", + or None. The version will be assumed to support our ABI. + :param platform: specify a list of platforms you want valid + tags for, or None. If None, use the local system platform. + :param impl: specify the exact implementation you want valid + tags for, or None. If None, use the local interpreter impl. + :param abis: specify a list of abis you want valid + tags for, or None. If None, use the local interpreter abi. + """ + supported: List[Tag] = [] + + python_version: Optional["PythonVersion"] = None + if version is not None: + python_version = _get_python_version(version) + + interpreter = _get_custom_interpreter(impl, version) + + platforms = _expand_allowed_platforms(platforms) + + is_cpython = (impl or interpreter_name()) == "cp" + if is_cpython: + supported.extend( + cpython_tags( + python_version=python_version, + abis=abis, + platforms=platforms, + ) + ) + else: + supported.extend( + generic_tags( + interpreter=interpreter, + abis=abis, + platforms=platforms, + ) + ) + supported.extend( + compatible_tags( + python_version=python_version, + interpreter=interpreter, + platforms=platforms, + ) + ) + + return supported diff --git a/pipenv/patched/notpip/_internal/utils/datetime.py b/pipenv/patched/notpip/_internal/utils/datetime.py new file mode 100644 index 00000000..8668b3b0 --- /dev/null +++ b/pipenv/patched/notpip/_internal/utils/datetime.py @@ -0,0 +1,11 @@ +"""For when pip wants to check the date or time. +""" + +import datetime + + +def today_is_later_than(year: int, month: int, day: int) -> bool: + today = datetime.date.today() + given = datetime.date(year, month, day) + + return today > given diff --git a/pipenv/patched/notpip/_internal/utils/deprecation.py b/pipenv/patched/notpip/_internal/utils/deprecation.py index 2e309ec2..c9cd17eb 100644 --- a/pipenv/patched/notpip/_internal/utils/deprecation.py +++ b/pipenv/patched/notpip/_internal/utils/deprecation.py @@ -1,47 +1,47 @@ """ A module that implements tooling to enable easy warnings about deprecations. """ -from __future__ import absolute_import import logging import warnings +from typing import Any, Optional, TextIO, Type, Union from pipenv.patched.notpip._vendor.packaging.version import parse from pipenv.patched.notpip import __version__ as current_version -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING -if MYPY_CHECK_RUNNING: - from typing import Any, Optional # noqa: F401 +DEPRECATION_MSG_PREFIX = "DEPRECATION: " class PipDeprecationWarning(Warning): pass -_original_showwarning = None # type: Any +_original_showwarning: Any = None # Warnings <-> Logging Integration -def _showwarning(message, category, filename, lineno, file=None, line=None): +def _showwarning( + message: Union[Warning, str], + category: Type[Warning], + filename: str, + lineno: int, + file: Optional[TextIO] = None, + line: Optional[str] = None, +) -> None: if file is not None: if _original_showwarning is not None: - _original_showwarning( - message, category, filename, lineno, file, line, - ) + _original_showwarning(message, category, filename, lineno, file, line) elif issubclass(category, PipDeprecationWarning): # We use a specially named logger which will handle all of the # deprecation messages for pip. - logger = logging.getLogger("pip._internal.deprecations") + logger = logging.getLogger("pipenv.patched.notpip._internal.deprecations") logger.warning(message) else: - _original_showwarning( - message, category, filename, lineno, file, line, - ) + _original_showwarning(message, category, filename, lineno, file, line) -def install_warning_logger(): - # type: () -> None +def install_warning_logger() -> None: # Enable our Deprecation Warnings warnings.simplefilter("default", PipDeprecationWarning, append=True) @@ -52,8 +52,12 @@ def install_warning_logger(): warnings.showwarning = _showwarning -def deprecated(reason, replacement, gone_in, issue=None): - # type: (str, Optional[str], Optional[str], Optional[int]) -> None +def deprecated( + reason: str, + replacement: Optional[str], + gone_in: Optional[str], + issue: Optional[int] = None, +) -> None: """Helper to deprecate existing functionality. reason: @@ -75,16 +79,26 @@ def deprecated(reason, replacement, gone_in, issue=None): """ # Construct a nice message. - # This is purposely eagerly formatted as we want it to appear as if someone - # typed this entire message out. - message = "DEPRECATION: " + reason - if replacement is not None: - message += " A possible replacement is {}.".format(replacement) - if issue is not None: - url = "https://github.com/pypa/pip/issues/" + str(issue) - message += " You can find discussion regarding this at {}.".format(url) + # This is eagerly formatted as we want it to get logged as if someone + # typed this entire message out. + sentences = [ + (reason, DEPRECATION_MSG_PREFIX + "{}"), + (gone_in, "pip {} will remove support for this functionality."), + (replacement, "A possible replacement is {}."), + ( + issue, + ( + "You can find discussion regarding this at " + "https://github.com/pypa/pip/issues/{}." + ), + ), + ] + message = " ".join( + template.format(val) for val, template in sentences if val is not None + ) # Raise as an error if it has to be removed. if gone_in is not None and parse(current_version) >= parse(gone_in): raise PipDeprecationWarning(message) + warnings.warn(message, category=PipDeprecationWarning, stacklevel=2) diff --git a/pipenv/patched/notpip/_internal/utils/direct_url_helpers.py b/pipenv/patched/notpip/_internal/utils/direct_url_helpers.py new file mode 100644 index 00000000..8729cc1b --- /dev/null +++ b/pipenv/patched/notpip/_internal/utils/direct_url_helpers.py @@ -0,0 +1,79 @@ +from typing import Optional + +from pipenv.patched.notpip._internal.models.direct_url import ArchiveInfo, DirectUrl, DirInfo, VcsInfo +from pipenv.patched.notpip._internal.models.link import Link +from pipenv.patched.notpip._internal.vcs import vcs + + +def direct_url_as_pep440_direct_reference(direct_url: DirectUrl, name: str) -> str: + """Convert a DirectUrl to a pip requirement string.""" + direct_url.validate() # if invalid, this is a pip bug + requirement = name + " @ " + fragments = [] + if isinstance(direct_url.info, VcsInfo): + requirement += "{}+{}@{}".format( + direct_url.info.vcs, direct_url.url, direct_url.info.commit_id + ) + elif isinstance(direct_url.info, ArchiveInfo): + requirement += direct_url.url + if direct_url.info.hash: + fragments.append(direct_url.info.hash) + else: + assert isinstance(direct_url.info, DirInfo) + requirement += direct_url.url + if direct_url.subdirectory: + fragments.append("subdirectory=" + direct_url.subdirectory) + if fragments: + requirement += "#" + "&".join(fragments) + return requirement + + +def direct_url_from_link( + link: Link, source_dir: Optional[str] = None, link_is_in_wheel_cache: bool = False +) -> DirectUrl: + if link.is_vcs: + vcs_backend = vcs.get_backend_for_scheme(link.scheme) + assert vcs_backend + url, requested_revision, _ = vcs_backend.get_url_rev_and_auth( + link.url_without_fragment + ) + # For VCS links, we need to find out and add commit_id. + if link_is_in_wheel_cache: + # If the requested VCS link corresponds to a cached + # wheel, it means the requested revision was an + # immutable commit hash, otherwise it would not have + # been cached. In that case we don't have a source_dir + # with the VCS checkout. + assert requested_revision + commit_id = requested_revision + else: + # If the wheel was not in cache, it means we have + # had to checkout from VCS to build and we have a source_dir + # which we can inspect to find out the commit id. + assert source_dir + commit_id = vcs_backend.get_revision(source_dir) + return DirectUrl( + url=url, + info=VcsInfo( + vcs=vcs_backend.name, + commit_id=commit_id, + requested_revision=requested_revision, + ), + subdirectory=link.subdirectory_fragment, + ) + elif link.is_existing_dir(): + return DirectUrl( + url=link.url_without_fragment, + info=DirInfo(), + subdirectory=link.subdirectory_fragment, + ) + else: + hash = None + hash_name = link.hash_name + if hash_name: + hash = f"{hash_name}={link.hash}" + return DirectUrl( + url=link.url_without_fragment, + info=ArchiveInfo(hash=hash), + subdirectory=link.subdirectory_fragment, + ) diff --git a/pipenv/patched/notpip/_internal/utils/distutils_args.py b/pipenv/patched/notpip/_internal/utils/distutils_args.py new file mode 100644 index 00000000..e4aa5b82 --- /dev/null +++ b/pipenv/patched/notpip/_internal/utils/distutils_args.py @@ -0,0 +1,42 @@ +from distutils.errors import DistutilsArgError +from distutils.fancy_getopt import FancyGetopt +from typing import Dict, List + +_options = [ + ("exec-prefix=", None, ""), + ("home=", None, ""), + ("install-base=", None, ""), + ("install-data=", None, ""), + ("install-headers=", None, ""), + ("install-lib=", None, ""), + ("install-platlib=", None, ""), + ("install-purelib=", None, ""), + ("install-scripts=", None, ""), + ("prefix=", None, ""), + ("root=", None, ""), + ("user", None, ""), +] + + +# typeshed doesn't permit Tuple[str, None, str], see python/typeshed#3469. +_distutils_getopt = FancyGetopt(_options) # type: ignore + + +def parse_distutils_args(args: List[str]) -> Dict[str, str]: + """Parse provided arguments, returning an object that has the + matched arguments. + + Any unknown arguments are ignored. + """ + result = {} + for arg in args: + try: + _, match = _distutils_getopt.getopt(args=[arg]) + except DistutilsArgError: + # We don't care about any other options, which here may be + # considered unrecognized since our option list is not + # exhaustive. + pass + else: + result.update(match.__dict__) + return result diff --git a/pipenv/patched/notpip/_internal/utils/encoding.py b/pipenv/patched/notpip/_internal/utils/encoding.py index f03fc901..1c73f6c9 100644 --- a/pipenv/patched/notpip/_internal/utils/encoding.py +++ b/pipenv/patched/notpip/_internal/utils/encoding.py @@ -2,37 +2,34 @@ import codecs import locale import re import sys +from typing import List, Tuple -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING +BOMS: List[Tuple[bytes, str]] = [ + (codecs.BOM_UTF8, "utf-8"), + (codecs.BOM_UTF16, "utf-16"), + (codecs.BOM_UTF16_BE, "utf-16-be"), + (codecs.BOM_UTF16_LE, "utf-16-le"), + (codecs.BOM_UTF32, "utf-32"), + (codecs.BOM_UTF32_BE, "utf-32-be"), + (codecs.BOM_UTF32_LE, "utf-32-le"), +] -if MYPY_CHECK_RUNNING: - from typing import List, Tuple, Text # noqa: F401 - -BOMS = [ - (codecs.BOM_UTF8, 'utf8'), - (codecs.BOM_UTF16, 'utf16'), - (codecs.BOM_UTF16_BE, 'utf16-be'), - (codecs.BOM_UTF16_LE, 'utf16-le'), - (codecs.BOM_UTF32, 'utf32'), - (codecs.BOM_UTF32_BE, 'utf32-be'), - (codecs.BOM_UTF32_LE, 'utf32-le'), -] # type: List[Tuple[bytes, Text]] - -ENCODING_RE = re.compile(br'coding[:=]\s*([-\w.]+)') +ENCODING_RE = re.compile(br"coding[:=]\s*([-\w.]+)") -def auto_decode(data): - # type: (bytes) -> Text +def auto_decode(data: bytes) -> str: """Check a bytes string for a BOM to correctly detect the encoding Fallback to locale.getpreferredencoding(False) like open() on Python3""" for bom, encoding in BOMS: if data.startswith(bom): - return data[len(bom):].decode(encoding) + return data[len(bom) :].decode(encoding) # Lets check the first two lines as in PEP263 - for line in data.split(b'\n')[:2]: - if line[0:1] == b'#' and ENCODING_RE.search(line): - encoding = ENCODING_RE.search(line).groups()[0].decode('ascii') + for line in data.split(b"\n")[:2]: + if line[0:1] == b"#" and ENCODING_RE.search(line): + result = ENCODING_RE.search(line) + assert result is not None + encoding = result.groups()[0].decode("ascii") return data.decode(encoding) return data.decode( locale.getpreferredencoding(False) or sys.getdefaultencoding(), diff --git a/pipenv/patched/notpip/_internal/utils/entrypoints.py b/pipenv/patched/notpip/_internal/utils/entrypoints.py new file mode 100644 index 00000000..7adda2fd --- /dev/null +++ b/pipenv/patched/notpip/_internal/utils/entrypoints.py @@ -0,0 +1,27 @@ +import sys +from typing import List, Optional + +from pipenv.patched.notpip._internal.cli.main import main + + +def _wrapper(args: Optional[List[str]] = None) -> int: + """Central wrapper for all old entrypoints. + + Historically pip has had several entrypoints defined. Because of issues + arising from PATH, sys.path, multiple Pythons, their interactions, and most + of them having a pip installed, users suffer every time an entrypoint gets + moved. + + To alleviate this pain, and provide a mechanism for warning users and + directing them to an appropriate place for help, we now define all of + our old entrypoints as wrappers for the current one. + """ + sys.stderr.write( + "WARNING: pip is being invoked by an old script wrapper. This will " + "fail in a future version of pip.\n" + "Please see https://github.com/pypa/pip/issues/5599 for advice on " + "fixing the underlying issue.\n" + "To avoid this problem you can invoke Python with '-m pip' instead of " + "running pip directly.\n" + ) + return main(args) diff --git a/pipenv/patched/notpip/_internal/utils/filesystem.py b/pipenv/patched/notpip/_internal/utils/filesystem.py index d4aae97d..c5c2929d 100644 --- a/pipenv/patched/notpip/_internal/utils/filesystem.py +++ b/pipenv/patched/notpip/_internal/utils/filesystem.py @@ -1,16 +1,28 @@ +import fnmatch import os import os.path +import random +import shutil +import stat +import sys +from contextlib import contextmanager +from tempfile import NamedTemporaryFile +from typing import Any, BinaryIO, Iterator, List, Union, cast + +from pipenv.patched.notpip._vendor.tenacity import retry, stop_after_delay, wait_fixed from pipenv.patched.notpip._internal.utils.compat import get_path_uid +from pipenv.patched.notpip._internal.utils.misc import format_size -def check_path_owner(path): - # type: (str) -> bool +def check_path_owner(path: str) -> bool: # If we don't have a way to check the effective uid of this process, then # we'll just assume that we own the directory. - if not hasattr(os, "geteuid"): + if sys.platform == "win32" or not hasattr(os, "geteuid"): return True + assert os.path.isabs(path) + previous = None while path != previous: if os.path.lexists(path): @@ -28,3 +40,143 @@ def check_path_owner(path): else: previous, path = path, os.path.dirname(path) return False # assume we don't own the path + + +def copy2_fixed(src: str, dest: str) -> None: + """Wrap shutil.copy2() but map errors copying socket files to + SpecialFileError as expected. + + See also https://bugs.python.org/issue37700. + """ + try: + shutil.copy2(src, dest) + except OSError: + for f in [src, dest]: + try: + is_socket_file = is_socket(f) + except OSError: + # An error has already occurred. Another error here is not + # a problem and we can ignore it. + pass + else: + if is_socket_file: + raise shutil.SpecialFileError(f"`{f}` is a socket") + + raise + + +def is_socket(path: str) -> bool: + return stat.S_ISSOCK(os.lstat(path).st_mode) + + +@contextmanager +def adjacent_tmp_file(path: str, **kwargs: Any) -> Iterator[BinaryIO]: + """Return a file-like object pointing to a tmp file next to path. + + The file is created securely and is ensured to be written to disk + after the context reaches its end. + + kwargs will be passed to tempfile.NamedTemporaryFile to control + the way the temporary file will be opened. + """ + with NamedTemporaryFile( + delete=False, + dir=os.path.dirname(path), + prefix=os.path.basename(path), + suffix=".tmp", + **kwargs, + ) as f: + result = cast(BinaryIO, f) + try: + yield result + finally: + result.flush() + os.fsync(result.fileno()) + + +# Tenacity raises RetryError by default, explicitly raise the original exception +_replace_retry = retry(reraise=True, stop=stop_after_delay(1), wait=wait_fixed(0.25)) + +replace = _replace_retry(os.replace) + + +# test_writable_dir and _test_writable_dir_win are copied from Flit, +# with the author's agreement to also place them under pip's license. +def test_writable_dir(path: str) -> bool: + """Check if a directory is writable. + + Uses os.access() on POSIX, tries creating files on Windows. + """ + # If the directory doesn't exist, find the closest parent that does. + while not os.path.isdir(path): + parent = os.path.dirname(path) + if parent == path: + break # Should never get here, but infinite loops are bad + path = parent + + if os.name == "posix": + return os.access(path, os.W_OK) + + return _test_writable_dir_win(path) + + +def _test_writable_dir_win(path: str) -> bool: + # os.access doesn't work on Windows: http://bugs.python.org/issue2528 + # and we can't use tempfile: http://bugs.python.org/issue22107 + basename = "accesstest_deleteme_fishfingers_custard_" + alphabet = "abcdefghijklmnopqrstuvwxyz0123456789" + for _ in range(10): + name = basename + "".join(random.choice(alphabet) for _ in range(6)) + file = os.path.join(path, name) + try: + fd = os.open(file, os.O_RDWR | os.O_CREAT | os.O_EXCL) + except FileExistsError: + pass + except PermissionError: + # This could be because there's a directory with the same name. + # But it's highly unlikely there's a directory called that, + # so we'll assume it's because the parent dir is not writable. + # This could as well be because the parent dir is not readable, + # due to non-privileged user access. + return False + else: + os.close(fd) + os.unlink(file) + return True + + # This should never be reached + raise OSError("Unexpected condition testing for writable directory") + + +def find_files(path: str, pattern: str) -> List[str]: + """Returns a list of absolute paths of files beneath path, recursively, + with filenames which match the UNIX-style shell glob pattern.""" + result: List[str] = [] + for root, _, files in os.walk(path): + matches = fnmatch.filter(files, pattern) + result.extend(os.path.join(root, f) for f in matches) + return result + + +def file_size(path: str) -> Union[int, float]: + # If it's a symlink, return 0. + if os.path.islink(path): + return 0 + return os.path.getsize(path) + + +def format_file_size(path: str) -> str: + return format_size(file_size(path)) + + +def directory_size(path: str) -> Union[int, float]: + size = 0.0 + for root, _dirs, files in os.walk(path): + for filename in files: + file_path = os.path.join(root, filename) + size += file_size(file_path) + return size + + +def format_directory_size(path: str) -> str: + return format_size(directory_size(path)) diff --git a/pipenv/patched/notpip/_internal/utils/filetypes.py b/pipenv/patched/notpip/_internal/utils/filetypes.py new file mode 100644 index 00000000..28747369 --- /dev/null +++ b/pipenv/patched/notpip/_internal/utils/filetypes.py @@ -0,0 +1,28 @@ +"""Filetype information. +""" + +from typing import Tuple + +from pipenv.patched.notpip._internal.utils.misc import splitext + +WHEEL_EXTENSION = ".whl" +BZ2_EXTENSIONS = (".tar.bz2", ".tbz") # type: Tuple[str, ...] +XZ_EXTENSIONS = ( + ".tar.xz", + ".txz", + ".tlz", + ".tar.lz", + ".tar.lzma", +) # type: Tuple[str, ...] +ZIP_EXTENSIONS = (".zip", WHEEL_EXTENSION) # type: Tuple[str, ...] +TAR_EXTENSIONS = (".tar.gz", ".tgz", ".tar") # type: Tuple[str, ...] +ARCHIVE_EXTENSIONS = ZIP_EXTENSIONS + BZ2_EXTENSIONS + TAR_EXTENSIONS + XZ_EXTENSIONS + + +def is_archive_file(name): + # type: (str) -> bool + """Return True if `name` is a considered as an archive file.""" + ext = splitext(name)[1].lower() + if ext in ARCHIVE_EXTENSIONS: + return True + return False diff --git a/pipenv/patched/notpip/_internal/utils/glibc.py b/pipenv/patched/notpip/_internal/utils/glibc.py index e2b6d505..1c9ff354 100644 --- a/pipenv/patched/notpip/_internal/utils/glibc.py +++ b/pipenv/patched/notpip/_internal/utils/glibc.py @@ -1,18 +1,43 @@ -from __future__ import absolute_import +# The following comment should be removed at some point in the future. +# mypy: strict-optional=False -import ctypes -import re -import warnings - -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING - -if MYPY_CHECK_RUNNING: - from typing import Optional, Tuple # noqa: F401 +import os +import sys +from typing import Optional, Tuple def glibc_version_string(): # type: () -> Optional[str] "Returns glibc version string, or None if not using glibc." + return glibc_version_string_confstr() or glibc_version_string_ctypes() + + +def glibc_version_string_confstr(): + # type: () -> Optional[str] + "Primary implementation of glibc_version_string using os.confstr." + # os.confstr is quite a bit faster than ctypes.DLL. It's also less likely + # to be broken or missing. This strategy is used in the standard library + # platform module: + # https://github.com/python/cpython/blob/fcf1d003bf4f0100c9d0921ff3d70e1127ca1b71/Lib/platform.py#L175-L183 + if sys.platform == "win32": + return None + try: + # os.confstr("CS_GNU_LIBC_VERSION") returns a string like "glibc 2.17": + _, version = os.confstr("CS_GNU_LIBC_VERSION").split() + except (AttributeError, OSError, ValueError): + # os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)... + return None + return version + + +def glibc_version_string_ctypes(): + # type: () -> Optional[str] + "Fallback implementation of glibc_version_string using ctypes." + + try: + import ctypes + except ImportError: + return None # ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen # manpage says, "If filename is NULL, then the returned handle is for the @@ -36,32 +61,6 @@ def glibc_version_string(): return version_str -# Separated out from have_compatible_glibc for easier unit testing -def check_glibc_version(version_str, required_major, minimum_minor): - # type: (str, int, int) -> bool - # Parse string and check against requested version. - # - # We use a regexp instead of str.split because we want to discard any - # random junk that might come after the minor version -- this might happen - # in patched/forked versions of glibc (e.g. Linaro's version of glibc - # uses version strings like "2.20-2014.11"). See gh-3588. - m = re.match(r"(?P[0-9]+)\.(?P[0-9]+)", version_str) - if not m: - warnings.warn("Expected glibc version with 2 components major.minor," - " got: %s" % version_str, RuntimeWarning) - return False - return (int(m.group("major")) == required_major and - int(m.group("minor")) >= minimum_minor) - - -def have_compatible_glibc(required_major, minimum_minor): - # type: (int, int) -> bool - version_str = glibc_version_string() # type: Optional[str] - if version_str is None: - return False - return check_glibc_version(version_str, required_major, minimum_minor) - - # platform.libc_ver regularly returns completely nonsensical glibc # versions. E.g. on my computer, platform says: # diff --git a/pipenv/patched/notpip/_internal/utils/hashes.py b/pipenv/patched/notpip/_internal/utils/hashes.py index 55cb8411..9730e556 100644 --- a/pipenv/patched/notpip/_internal/utils/hashes.py +++ b/pipenv/patched/notpip/_internal/utils/hashes.py @@ -1,48 +1,79 @@ -from __future__ import absolute_import - import hashlib +from typing import TYPE_CHECKING, BinaryIO, Dict, Iterator, List -from pipenv.patched.notpip._vendor.six import iteritems, iterkeys, itervalues - -from pipenv.patched.notpip._internal.exceptions import ( - HashMismatch, HashMissing, InstallationError, -) +from pipenv.patched.notpip._internal.exceptions import HashMismatch, HashMissing, InstallationError from pipenv.patched.notpip._internal.utils.misc import read_chunks -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING -if MYPY_CHECK_RUNNING: - from typing import ( # noqa: F401 - Dict, List, BinaryIO, NoReturn, Iterator - ) - from pipenv.patched.notpip._vendor.six import PY3 - if PY3: - from hashlib import _Hash # noqa: F401 - else: - from hashlib import _hash as _Hash # noqa: F401 +if TYPE_CHECKING: + from hashlib import _Hash + + # NoReturn introduced in 3.6.2; imported only for type checking to maintain + # pip compatibility with older patch versions of Python 3.6 + from typing import NoReturn # The recommended hash algo of the moment. Change this whenever the state of # the art changes; it won't hurt backward compatibility. -FAVORITE_HASH = 'sha256' +FAVORITE_HASH = "sha256" # Names of hashlib algorithms allowed by the --hash option and ``pip hash`` # Currently, those are the ones at least as collision-resistant as sha256. -STRONG_HASHES = ['sha256', 'sha384', 'sha512'] +STRONG_HASHES = ["sha256", "sha384", "sha512"] -class Hashes(object): +class Hashes: """A wrapper that builds multiple hashes at once and checks them against known-good values """ + def __init__(self, hashes=None): # type: (Dict[str, List[str]]) -> None """ :param hashes: A dict of algorithm names pointing to lists of allowed hex digests """ - self._allowed = {} if hashes is None else hashes + allowed = {} + if hashes is not None: + for alg, keys in hashes.items(): + # Make sure values are always sorted (to ease equality checks) + allowed[alg] = sorted(keys) + self._allowed = allowed + + def __and__(self, other): + # type: (Hashes) -> Hashes + if not isinstance(other, Hashes): + return NotImplemented + + # If either of the Hashes object is entirely empty (i.e. no hash + # specified at all), all hashes from the other object are allowed. + if not other: + return self + if not self: + return other + + # Otherwise only hashes that present in both objects are allowed. + new = {} + for alg, values in other._allowed.items(): + if alg not in self._allowed: + continue + new[alg] = [v for v in values if v in self._allowed[alg]] + return Hashes(new) + + @property + def digest_count(self): + # type: () -> int + return sum(len(digests) for digests in self._allowed.values()) + + def is_hash_allowed( + self, + hash_name, # type: str + hex_digest, # type: str + ): + # type: (...) -> bool + """Return whether the given hex digest is allowed.""" + return hex_digest in self._allowed.get(hash_name, []) def check_against_chunks(self, chunks): # type: (Iterator[bytes]) -> None @@ -53,17 +84,17 @@ class Hashes(object): """ gots = {} - for hash_name in iterkeys(self._allowed): + for hash_name in self._allowed.keys(): try: gots[hash_name] = hashlib.new(hash_name) except (ValueError, TypeError): - raise InstallationError('Unknown hash name: %s' % hash_name) + raise InstallationError(f"Unknown hash name: {hash_name}") for chunk in chunks: - for hash in itervalues(gots): + for hash in gots.values(): hash.update(chunk) - for hash_name, got in iteritems(gots): + for hash_name, got in gots.items(): if got.hexdigest() in self._allowed[hash_name]: return self._raise(gots) @@ -83,7 +114,7 @@ class Hashes(object): def check_against_path(self, path): # type: (str) -> None - with open(path, 'rb') as file: + with open(path, "rb") as file: return self.check_against_file(file) def __nonzero__(self): @@ -95,6 +126,24 @@ class Hashes(object): # type: () -> bool return self.__nonzero__() + def __eq__(self, other): + # type: (object) -> bool + if not isinstance(other, Hashes): + return NotImplemented + return self._allowed == other._allowed + + def __hash__(self): + # type: () -> int + return hash( + ",".join( + sorted( + ":".join((alg, digest)) + for alg, digest_list in self._allowed.items() + for digest in digest_list + ) + ) + ) + class MissingHashes(Hashes): """A workalike for Hashes used when we're missing a hash for a requirement @@ -103,12 +152,13 @@ class MissingHashes(Hashes): exception showing it to the user. """ + def __init__(self): # type: () -> None """Don't offer the ``hashes`` kwarg.""" # Pass our favorite hash in to generate a "gotten hash". With the # empty list, it will never match, so an error will always raise. - super(MissingHashes, self).__init__(hashes={FAVORITE_HASH: []}) + super().__init__(hashes={FAVORITE_HASH: []}) def _raise(self, gots): # type: (Dict[str, _Hash]) -> NoReturn diff --git a/pipenv/patched/notpip/_internal/utils/inject_securetransport.py b/pipenv/patched/notpip/_internal/utils/inject_securetransport.py new file mode 100644 index 00000000..d1a6256a --- /dev/null +++ b/pipenv/patched/notpip/_internal/utils/inject_securetransport.py @@ -0,0 +1,36 @@ +"""A helper module that injects SecureTransport, on import. + +The import should be done as early as possible, to ensure all requests and +sessions (or whatever) are created after injecting SecureTransport. + +Note that we only do the injection on macOS, when the linked OpenSSL is too +old to handle TLSv1.2. +""" + +import sys + + +def inject_securetransport(): + # type: () -> None + # Only relevant on macOS + if sys.platform != "darwin": + return + + try: + import ssl + except ImportError: + return + + # Checks for OpenSSL 1.0.1 + if ssl.OPENSSL_VERSION_NUMBER >= 0x1000100F: + return + + try: + from pipenv.patched.notpip._vendor.urllib3.contrib import securetransport + except (ImportError, OSError): + return + + securetransport.inject_into_urllib3() + + +inject_securetransport() diff --git a/pipenv/patched/notpip/_internal/utils/logging.py b/pipenv/patched/notpip/_internal/utils/logging.py index 638c5ca9..c8f31e57 100644 --- a/pipenv/patched/notpip/_internal/utils/logging.py +++ b/pipenv/patched/notpip/_internal/utils/logging.py @@ -1,15 +1,15 @@ -from __future__ import absolute_import - import contextlib import errno import logging import logging.handlers import os import sys +from logging import Filter +from typing import IO, Any, Callable, Iterator, Optional, TextIO, Type, cast -from pipenv.patched.notpip._vendor.six import PY2 - +from pipenv.patched.notpip._internal.utils._log import VERBOSE, getLogger from pipenv.patched.notpip._internal.utils.compat import WINDOWS +from pipenv.patched.notpip._internal.utils.deprecation import DEPRECATION_MSG_PREFIX from pipenv.patched.notpip._internal.utils.misc import ensure_dir try: @@ -27,41 +27,34 @@ except Exception: _log_state = threading.local() -_log_state.indentation = 0 +subprocess_logger = getLogger("pip.subprocessor") class BrokenStdoutLoggingError(Exception): """ Raised if BrokenPipeError occurs for the stdout stream while logging. """ + pass -# BrokenPipeError does not exist in Python 2 and, in addition, manifests -# differently in Windows and non-Windows. +# BrokenPipeError manifests differently in Windows and non-Windows. if WINDOWS: # In Windows, a broken pipe can show up as EINVAL rather than EPIPE: # https://bugs.python.org/issue19612 # https://bugs.python.org/issue30418 - if PY2: - def _is_broken_pipe_error(exc_class, exc): - """See the docstring for non-Windows Python 3 below.""" - return (exc_class is IOError and - exc.errno in (errno.EINVAL, errno.EPIPE)) - else: - # In Windows, a broken pipe IOError became OSError in Python 3. - def _is_broken_pipe_error(exc_class, exc): - """See the docstring for non-Windows Python 3 below.""" - return ((exc_class is BrokenPipeError) or # noqa: F821 - (exc_class is OSError and - exc.errno in (errno.EINVAL, errno.EPIPE))) -elif PY2: def _is_broken_pipe_error(exc_class, exc): - """See the docstring for non-Windows Python 3 below.""" - return (exc_class is IOError and exc.errno == errno.EPIPE) + # type: (Type[BaseException], BaseException) -> bool + """See the docstring for non-Windows below.""" + return (exc_class is BrokenPipeError) or ( + isinstance(exc, OSError) and exc.errno in (errno.EINVAL, errno.EPIPE) + ) + + else: - # Then we are in the non-Windows Python 3 case. + # Then we are in the non-Windows case. def _is_broken_pipe_error(exc_class, exc): + # type: (Type[BaseException], BaseException) -> bool """ Return whether an exception is a broken pipe error. @@ -69,15 +62,18 @@ else: exc_class: an exception class. exc: an exception instance. """ - return (exc_class is BrokenPipeError) # noqa: F821 + return exc_class is BrokenPipeError @contextlib.contextmanager def indent_log(num=2): + # type: (int) -> Iterator[None] """ A context manager which will cause the log output to be indented for any log messages emitted inside it. """ + # For thread-safety + _log_state.indentation = get_indentation() _log_state.indentation += num try: yield @@ -86,40 +82,70 @@ def indent_log(num=2): def get_indentation(): - return getattr(_log_state, 'indentation', 0) + # type: () -> int + return getattr(_log_state, "indentation", 0) class IndentingFormatter(logging.Formatter): - def __init__(self, *args, **kwargs): + default_time_format = "%Y-%m-%dT%H:%M:%S" + + def __init__( + self, + *args, # type: Any + add_timestamp=False, # type: bool + **kwargs, # type: Any + ): + # type: (...) -> None """ - A logging.Formatter obeying containing indent_log contexts. + A logging.Formatter that obeys the indent_log() context manager. :param add_timestamp: A bool indicating output lines should be prefixed with their record's timestamp. """ - self.add_timestamp = kwargs.pop("add_timestamp", False) - super(IndentingFormatter, self).__init__(*args, **kwargs) + self.add_timestamp = add_timestamp + super().__init__(*args, **kwargs) + + def get_message_start(self, formatted, levelno): + # type: (str, int) -> str + """ + Return the start of the formatted log message (not counting the + prefix to add to each line). + """ + if levelno < logging.WARNING: + return "" + if formatted.startswith(DEPRECATION_MSG_PREFIX): + # Then the message already has a prefix. We don't want it to + # look like "WARNING: DEPRECATION: ...." + return "" + if levelno < logging.ERROR: + return "WARNING: " + + return "ERROR: " def format(self, record): + # type: (logging.LogRecord) -> str """ - Calls the standard formatter, but will indent all of the log messages - by our current indentation level. + Calls the standard formatter, but will indent all of the log message + lines by our current indentation level. """ - formatted = super(IndentingFormatter, self).format(record) - prefix = '' + formatted = super().format(record) + message_start = self.get_message_start(formatted, record.levelno) + formatted = message_start + formatted + + prefix = "" if self.add_timestamp: - prefix = self.formatTime(record, "%Y-%m-%dT%H:%M:%S ") + prefix = f"{self.formatTime(record)} " prefix += " " * get_indentation() - formatted = "".join([ - prefix + line - for line in formatted.splitlines(True) - ]) + formatted = "".join([prefix + line for line in formatted.splitlines(True)]) return formatted def _color_wrap(*colors): + # type: (*str) -> Callable[[str], str] def wrapped(inp): + # type: (str) -> str return "".join(list(colors) + [inp, colorama.Style.RESET_ALL]) + return wrapped @@ -136,29 +162,34 @@ class ColorizedStreamHandler(logging.StreamHandler): COLORS = [] def __init__(self, stream=None, no_color=None): - logging.StreamHandler.__init__(self, stream) + # type: (Optional[TextIO], bool) -> None + super().__init__(stream) self._no_color = no_color if WINDOWS and colorama: self.stream = colorama.AnsiToWin32(self.stream) def _using_stdout(self): + # type: () -> bool """ Return whether the handler is using sys.stdout. """ if WINDOWS and colorama: # Then self.stream is an AnsiToWin32 object. - return self.stream.wrapped is sys.stdout + stream = cast(colorama.AnsiToWin32, self.stream) + return stream.wrapped is sys.stdout return self.stream is sys.stdout def should_color(self): + # type: () -> bool # Don't colorize things if we do not have colorama or if told not to if not colorama or self._no_color: return False real_stream = ( - self.stream if not isinstance(self.stream, colorama.AnsiToWin32) + self.stream + if not isinstance(self.stream, colorama.AnsiToWin32) else self.stream.wrapped ) @@ -174,7 +205,8 @@ class ColorizedStreamHandler(logging.StreamHandler): return False def format(self, record): - msg = logging.StreamHandler.format(self, record) + # type: (logging.LogRecord) -> str + msg = super().format(record) if self.should_color(): for level, color in self.COLORS: @@ -186,53 +218,75 @@ class ColorizedStreamHandler(logging.StreamHandler): # The logging module says handleError() can be customized. def handleError(self, record): + # type: (logging.LogRecord) -> None exc_class, exc = sys.exc_info()[:2] # If a broken pipe occurred while calling write() or flush() on the # stdout stream in logging's Handler.emit(), then raise our special # exception so we can handle it in main() instead of logging the # broken pipe error and continuing. - if (exc_class and self._using_stdout() and - _is_broken_pipe_error(exc_class, exc)): + if ( + exc_class + and exc + and self._using_stdout() + and _is_broken_pipe_error(exc_class, exc) + ): raise BrokenStdoutLoggingError() - return super(ColorizedStreamHandler, self).handleError(record) + return super().handleError(record) class BetterRotatingFileHandler(logging.handlers.RotatingFileHandler): - def _open(self): + # type: () -> IO[Any] ensure_dir(os.path.dirname(self.baseFilename)) - return logging.handlers.RotatingFileHandler._open(self) + return super()._open() -class MaxLevelFilter(logging.Filter): - +class MaxLevelFilter(Filter): def __init__(self, level): + # type: (int) -> None self.level = level def filter(self, record): + # type: (logging.LogRecord) -> bool return record.levelno < self.level +class ExcludeLoggerFilter(Filter): + + """ + A logging Filter that excludes records from a logger (or its children). + """ + + def filter(self, record): + # type: (logging.LogRecord) -> bool + # The base Filter class allows only records from a logger (or its + # children). + return not super().filter(record) + + def setup_logging(verbosity, no_color, user_log_file): + # type: (int, bool, Optional[str]) -> int """Configures and sets up all of the logging Returns the requested logging level, as its integer value. """ # Determine the level to be logging at. - if verbosity >= 1: - level = "DEBUG" + if verbosity >= 2: + level_number = logging.DEBUG + elif verbosity == 1: + level_number = VERBOSE elif verbosity == -1: - level = "WARNING" + level_number = logging.WARNING elif verbosity == -2: - level = "ERROR" + level_number = logging.ERROR elif verbosity <= -3: - level = "CRITICAL" + level_number = logging.CRITICAL else: - level = "INFO" + level_number = logging.INFO - level_number = getattr(logging, level) + level = logging.getLevelName(level_number) # The "root" logger should match the "console" level *unless* we also need # to log to a user log file. @@ -254,65 +308,84 @@ def setup_logging(verbosity, no_color, user_log_file): "stderr": "ext://sys.stderr", } handler_classes = { - "stream": "pip._internal.utils.logging.ColorizedStreamHandler", - "file": "pip._internal.utils.logging.BetterRotatingFileHandler", + "stream": "pipenv.patched.notpip._internal.utils.logging.ColorizedStreamHandler", + "file": "pipenv.patched.notpip._internal.utils.logging.BetterRotatingFileHandler", } + handlers = ["console", "console_errors", "console_subprocess"] + ( + ["user_log"] if include_user_log else [] + ) - logging.config.dictConfig({ - "version": 1, - "disable_existing_loggers": False, - "filters": { - "exclude_warnings": { - "()": "pip._internal.utils.logging.MaxLevelFilter", - "level": logging.WARNING, + logging.config.dictConfig( + { + "version": 1, + "disable_existing_loggers": False, + "filters": { + "exclude_warnings": { + "()": "pipenv.patched.notpip._internal.utils.logging.MaxLevelFilter", + "level": logging.WARNING, + }, + "restrict_to_subprocess": { + "()": "logging.Filter", + "name": subprocess_logger.name, + }, + "exclude_subprocess": { + "()": "pipenv.patched.notpip._internal.utils.logging.ExcludeLoggerFilter", + "name": subprocess_logger.name, + }, }, - }, - "formatters": { - "indent": { - "()": IndentingFormatter, - "format": "%(message)s", + "formatters": { + "indent": { + "()": IndentingFormatter, + "format": "%(message)s", + }, + "indent_with_timestamp": { + "()": IndentingFormatter, + "format": "%(message)s", + "add_timestamp": True, + }, }, - "indent_with_timestamp": { - "()": IndentingFormatter, - "format": "%(message)s", - "add_timestamp": True, + "handlers": { + "console": { + "level": level, + "class": handler_classes["stream"], + "no_color": no_color, + "stream": log_streams["stdout"], + "filters": ["exclude_subprocess", "exclude_warnings"], + "formatter": "indent", + }, + "console_errors": { + "level": "WARNING", + "class": handler_classes["stream"], + "no_color": no_color, + "stream": log_streams["stderr"], + "filters": ["exclude_subprocess"], + "formatter": "indent", + }, + # A handler responsible for logging to the console messages + # from the "subprocessor" logger. + "console_subprocess": { + "level": level, + "class": handler_classes["stream"], + "no_color": no_color, + "stream": log_streams["stderr"], + "filters": ["restrict_to_subprocess"], + "formatter": "indent", + }, + "user_log": { + "level": "DEBUG", + "class": handler_classes["file"], + "filename": additional_log_file, + "encoding": "utf-8", + "delay": True, + "formatter": "indent_with_timestamp", + }, }, - }, - "handlers": { - "console": { - "level": level, - "class": handler_classes["stream"], - "no_color": no_color, - "stream": log_streams["stdout"], - "filters": ["exclude_warnings"], - "formatter": "indent", + "root": { + "level": root_level, + "handlers": handlers, }, - "console_errors": { - "level": "WARNING", - "class": handler_classes["stream"], - "no_color": no_color, - "stream": log_streams["stderr"], - "formatter": "indent", - }, - "user_log": { - "level": "DEBUG", - "class": handler_classes["file"], - "filename": additional_log_file, - "delay": True, - "formatter": "indent_with_timestamp", - }, - }, - "root": { - "level": root_level, - "handlers": ["console", "console_errors"] + ( - ["user_log"] if include_user_log else [] - ), - }, - "loggers": { - "pip._vendor": { - "level": vendored_log_level - } - }, - }) + "loggers": {"pipenv.patched.notpip._vendor": {"level": vendored_log_level}}, + } + ) return level_number diff --git a/pipenv/patched/notpip/_internal/utils/misc.py b/pipenv/patched/notpip/_internal/utils/misc.py index 0a3237a2..4224909d 100644 --- a/pipenv/patched/notpip/_internal/utils/misc.py +++ b/pipenv/patched/notpip/_internal/utils/misc.py @@ -1,93 +1,106 @@ -from __future__ import absolute_import +# The following comment should be removed at some point in the future. +# mypy: strict-optional=False import contextlib import errno +import getpass +import hashlib import io -import locale -# we have a submodule named 'logging' which would shadow this if we used the -# regular name: -import logging as std_logging +import logging import os import posixpath -import re import shutil import stat -import subprocess import sys -import tarfile -import zipfile -from collections import deque - -from pipenv.patched.notpip._vendor import pkg_resources -# NOTE: retrying is not annotated in typeshed as on 2017-07-17, which is -# why we ignore the type on this import. -from pipenv.patched.notpip._vendor.retrying import retry # type: ignore -from pipenv.patched.notpip._vendor.six import PY2 -from pipenv.patched.notpip._vendor.six.moves import input -from pipenv.patched.notpip._vendor.six.moves.urllib import parse as urllib_parse -from pipenv.patched.notpip._vendor.six.moves.urllib.parse import unquote as urllib_unquote - -from pipenv.patched.notpip._internal.exceptions import CommandError, InstallationError -from pipenv.patched.notpip._internal.locations import ( - running_under_virtualenv, site_packages, user_site, virtualenv_no_global, - write_delete_marker_file, +import urllib.parse +from io import StringIO +from itertools import filterfalse, tee, zip_longest +from types import TracebackType +from typing import ( + Any, + AnyStr, + BinaryIO, + Callable, + Container, + ContextManager, + Iterable, + Iterator, + List, + Optional, + TextIO, + Tuple, + Type, + TypeVar, + cast, ) -from pipenv.patched.notpip._internal.utils.compat import ( - WINDOWS, console_to_str, expanduser, stdlib_pkgs, + +from pipenv.patched.notpip._vendor.pkg_resources import Distribution +from pipenv.patched.notpip._vendor.tenacity import retry, stop_after_delay, wait_fixed + +from pipenv.patched.notpip import __version__ +from pipenv.patched.notpip._internal.exceptions import CommandError +from pipenv.patched.notpip._internal.locations import get_major_minor_version, site_packages, user_site +from pipenv.patched.notpip._internal.utils.compat import WINDOWS, stdlib_pkgs +from pipenv.patched.notpip._internal.utils.virtualenv import ( + running_under_virtualenv, + virtualenv_no_global, ) -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING -if PY2: - from io import BytesIO as StringIO -else: - from io import StringIO +__all__ = [ + "rmtree", + "display_path", + "backup_dir", + "ask", + "splitext", + "format_size", + "is_installable_dir", + "normalize_path", + "renames", + "get_prog", + "captured_stdout", + "ensure_dir", + "remove_auth_from_url", +] -if MYPY_CHECK_RUNNING: - from typing import ( # noqa: F401 - Optional, Tuple, Iterable, List, Match, Union, Any, Mapping, Text, - AnyStr, Container + +logger = logging.getLogger(__name__) + +T = TypeVar("T") +ExcInfo = Tuple[Type[BaseException], BaseException, TracebackType] +VersionInfo = Tuple[int, int, int] +NetlocTuple = Tuple[str, Tuple[Optional[str], Optional[str]]] + + +def get_pip_version(): + # type: () -> str + pip_pkg_dir = os.path.join(os.path.dirname(__file__), "..", "..") + pip_pkg_dir = os.path.abspath(pip_pkg_dir) + + return "pip {} from {} (python {})".format( + __version__, + pip_pkg_dir, + get_major_minor_version(), ) - from pipenv.patched.notpip._vendor.pkg_resources import Distribution # noqa: F401 - from pipenv.patched.notpip._internal.models.link import Link # noqa: F401 - from pipenv.patched.notpip._internal.utils.ui import SpinnerInterface # noqa: F401 -__all__ = ['rmtree', 'display_path', 'backup_dir', - 'ask', 'splitext', - 'format_size', 'is_installable_dir', - 'is_svn_page', 'file_contents', - 'split_leading_dir', 'has_leading_dir', - 'normalize_path', - 'renames', 'get_prog', - 'unzip_file', 'untar_file', 'unpack_file', 'call_subprocess', - 'captured_stdout', 'ensure_dir', - 'ARCHIVE_EXTENSIONS', 'SUPPORTED_EXTENSIONS', 'WHEEL_EXTENSION', - 'get_installed_version', 'remove_auth_from_url'] +def normalize_version_info(py_version_info): + # type: (Tuple[int, ...]) -> Tuple[int, int, int] + """ + Convert a tuple of ints representing a Python version to one of length + three. + :param py_version_info: a tuple of ints representing a Python version, + or None to specify no version. The tuple can have any length. -logger = std_logging.getLogger(__name__) + :return: a tuple of length three if `py_version_info` is non-None. + Otherwise, return `py_version_info` unchanged (i.e. None). + """ + if len(py_version_info) < 3: + py_version_info += (3 - len(py_version_info)) * (0,) + elif len(py_version_info) > 3: + py_version_info = py_version_info[:3] -WHEEL_EXTENSION = '.whl' -BZ2_EXTENSIONS = ('.tar.bz2', '.tbz') -XZ_EXTENSIONS = ('.tar.xz', '.txz', '.tlz', '.tar.lz', '.tar.lzma') -ZIP_EXTENSIONS = ('.zip', WHEEL_EXTENSION) -TAR_EXTENSIONS = ('.tar.gz', '.tgz', '.tar') -ARCHIVE_EXTENSIONS = ( - ZIP_EXTENSIONS + BZ2_EXTENSIONS + TAR_EXTENSIONS + XZ_EXTENSIONS) -SUPPORTED_EXTENSIONS = ZIP_EXTENSIONS + TAR_EXTENSIONS - -try: - import bz2 # noqa - SUPPORTED_EXTENSIONS += BZ2_EXTENSIONS -except ImportError: - logger.debug('bz2 module is not available') - -try: - # Only for Python 3.3+ - import lzma # noqa - SUPPORTED_EXTENSIONS += XZ_EXTENSIONS -except ImportError: - logger.debug('lzma module is not available') + return cast("VersionInfo", py_version_info) def ensure_dir(path): @@ -96,7 +109,8 @@ def ensure_dir(path): try: os.makedirs(path) except OSError as e: - if e.errno != errno.EEXIST: + # Windows can raise spurious ENOTEMPTY errors. See #6426. + if e.errno != errno.EEXIST and e.errno != errno.ENOTEMPTY: raise @@ -104,29 +118,35 @@ def get_prog(): # type: () -> str try: prog = os.path.basename(sys.argv[0]) - if prog in ('__main__.py', '-c'): - return "%s -m pip" % sys.executable + if prog in ("__main__.py", "-c"): + return f"{sys.executable} -m pip" else: return prog except (AttributeError, TypeError, IndexError): pass - return 'pip' + return "pip" # Retry every half second for up to 3 seconds -@retry(stop_max_delay=3000, wait_fixed=500) +# Tenacity raises RetryError by default, explicitly raise the original exception +@retry(reraise=True, stop=stop_after_delay(3), wait=wait_fixed(0.5)) def rmtree(dir, ignore_errors=False): - # type: (str, bool) -> None - from pipenv.vendor.vistir.path import rmtree as vistir_rmtree, handle_remove_readonly - vistir_rmtree(dir, onerror=handle_remove_readonly, ignore_errors=ignore_errors) + # type: (AnyStr, bool) -> None + shutil.rmtree(dir, ignore_errors=ignore_errors, onerror=rmtree_errorhandler) def rmtree_errorhandler(func, path, exc_info): + # type: (Callable[..., Any], str, ExcInfo) -> None """On Windows, the files in .svn are read-only, so when rmtree() tries to remove them, an exception is thrown. We catch that here, remove the read-only attribute, and hopefully continue without problems.""" - # if file type currently read only - if os.stat(path).st_mode & stat.S_IREAD: + try: + has_attr_readonly = not (os.stat(path).st_mode & stat.S_IWRITE) + except OSError: + # it's equivalent to os.path.exists + return + + if has_attr_readonly: # convert to read/write os.chmod(path, stat.S_IWRITE) # use the original function to repeat the operation @@ -137,19 +157,16 @@ def rmtree_errorhandler(func, path, exc_info): def display_path(path): - # type: (Union[str, Text]) -> str + # type: (str) -> str """Gives the display value for a given path, making it relative to cwd if possible.""" path = os.path.normcase(os.path.abspath(path)) - if sys.version_info[0] == 2: - path = path.decode(sys.getfilesystemencoding(), 'replace') - path = path.encode(sys.getdefaultencoding(), 'replace') if path.startswith(os.getcwd() + os.path.sep): - path = '.' + path[len(os.getcwd()):] + path = "." + path[len(os.getcwd()) :] return path -def backup_dir(dir, ext='.bak'): +def backup_dir(dir, ext=".bak"): # type: (str, str) -> str """Figure out the name of a directory to back up the given dir to (adding .bak, .bak2, etc)""" @@ -163,75 +180,114 @@ def backup_dir(dir, ext='.bak'): def ask_path_exists(message, options): # type: (str, Iterable[str]) -> str - for action in os.environ.get('PIP_EXISTS_ACTION', '').split(): + for action in os.environ.get("PIP_EXISTS_ACTION", "").split(): if action in options: return action return ask(message, options) +def _check_no_input(message): + # type: (str) -> None + """Raise an error if no input is allowed.""" + if os.environ.get("PIP_NO_INPUT"): + raise Exception( + f"No input was expected ($PIP_NO_INPUT set); question: {message}" + ) + + def ask(message, options): # type: (str, Iterable[str]) -> str """Ask the message interactively, with the given possible responses""" while 1: - if os.environ.get('PIP_NO_INPUT'): - raise Exception( - 'No input was expected ($PIP_NO_INPUT set); question: %s' % - message - ) + _check_no_input(message) response = input(message) response = response.strip().lower() if response not in options: print( - 'Your response (%r) was not one of the expected responses: ' - '%s' % (response, ', '.join(options)) + "Your response ({!r}) was not one of the expected responses: " + "{}".format(response, ", ".join(options)) ) else: return response +def ask_input(message): + # type: (str) -> str + """Ask for input interactively.""" + _check_no_input(message) + return input(message) + + +def ask_password(message): + # type: (str) -> str + """Ask for a password interactively.""" + _check_no_input(message) + return getpass.getpass(message) + + +def strtobool(val): + # type: (str) -> int + """Convert a string representation of truth to true (1) or false (0). + + True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values + are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if + 'val' is anything else. + """ + val = val.lower() + if val in ("y", "yes", "t", "true", "on", "1"): + return 1 + elif val in ("n", "no", "f", "false", "off", "0"): + return 0 + else: + raise ValueError(f"invalid truth value {val!r}") + + def format_size(bytes): # type: (float) -> str if bytes > 1000 * 1000: - return '%.1fMB' % (bytes / 1000.0 / 1000) + return "{:.1f} MB".format(bytes / 1000.0 / 1000) elif bytes > 10 * 1000: - return '%ikB' % (bytes / 1000) + return "{} kB".format(int(bytes / 1000)) elif bytes > 1000: - return '%.1fkB' % (bytes / 1000.0) + return "{:.1f} kB".format(bytes / 1000.0) else: - return '%ibytes' % bytes + return "{} bytes".format(int(bytes)) -def is_installable_dir(path): - # type: (str) -> bool - """Is path is a directory containing setup.py or pyproject.toml? +def tabulate(rows): + # type: (Iterable[Iterable[Any]]) -> Tuple[List[str], List[int]] + """Return a list of formatted rows and a list of column sizes. + + For example:: + + >>> tabulate([['foobar', 2000], [0xdeadbeef]]) + (['foobar 2000', '3735928559'], [10, 4]) + """ + rows = [tuple(map(str, row)) for row in rows] + sizes = [max(map(len, col)) for col in zip_longest(*rows, fillvalue="")] + table = [" ".join(map(str.ljust, row, sizes)).rstrip() for row in rows] + return table, sizes + + +def is_installable_dir(path: str) -> bool: + """Is path is a directory containing pyproject.toml or setup.py? + + If pyproject.toml exists, this is a PEP 517 project. Otherwise we look for + a legacy setuptools layout by identifying setup.py. We don't check for the + setup.cfg because using it without setup.py is only available for PEP 517 + projects, which are already covered by the pyproject.toml check. """ if not os.path.isdir(path): return False - setup_py = os.path.join(path, 'setup.py') - if os.path.isfile(setup_py): + if os.path.isfile(os.path.join(path, "pyproject.toml")): return True - pyproject_toml = os.path.join(path, 'pyproject.toml') - if os.path.isfile(pyproject_toml): + if os.path.isfile(os.path.join(path, "setup.py")): return True return False -def is_svn_page(html): - # type: (Union[str, Text]) -> Optional[Match[Union[str, Text]]] - """ - Returns true if the page appears to be the index page of an svn repository - """ - return (re.search(r'[^<]*Revision \d+:', html) and - re.search(r'Powered by (?:<a[^>]*?>)?Subversion', html, re.I)) - - -def file_contents(filename): - # type: (str) -> Text - with open(filename, 'rb') as fp: - return fp.read().decode('utf-8') - - def read_chunks(file, size=io.DEFAULT_BUFFER_SIZE): + # type: (BinaryIO, int) -> Iterator[bytes] """Yield pieces of data from a file-like object until EOF.""" while True: chunk = file.read(size) @@ -240,41 +296,13 @@ def read_chunks(file, size=io.DEFAULT_BUFFER_SIZE): yield chunk -def split_leading_dir(path): - # type: (Union[str, Text]) -> List[Union[str, Text]] - path = path.lstrip('/').lstrip('\\') - if '/' in path and (('\\' in path and path.find('/') < path.find('\\')) or - '\\' not in path): - return path.split('/', 1) - elif '\\' in path: - return path.split('\\', 1) - else: - return [path, ''] - - -def has_leading_dir(paths): - # type: (Iterable[Union[str, Text]]) -> bool - """Returns true if all the paths have the same leading path name - (i.e., everything is in one subdirectory in an archive)""" - common_prefix = None - for path in paths: - prefix, rest = split_leading_dir(path) - if not prefix: - return False - elif common_prefix is None: - common_prefix = prefix - elif prefix != common_prefix: - return False - return True - - def normalize_path(path, resolve_symlinks=True): # type: (str, bool) -> str """ Convert a path to its canonical, case-normalized, absolute version. """ - path = expanduser(path) + path = os.path.expanduser(path) if resolve_symlinks: path = os.path.realpath(path) else: @@ -286,7 +314,7 @@ def splitext(path): # type: (str) -> Tuple[str, str] """Like os.path.splitext, but take off .tar too""" base, ext = posixpath.splitext(path) - if base.lower().endswith('.tar'): + if base.lower().endswith(".tar"): ext = base[-4:] + ext base = base[:-4] return base, ext @@ -317,10 +345,12 @@ def is_local(path): If we're not in a virtualenv, all paths are considered "local." + Caution: this function assumes the head of path has been normalized + with normalize_path. """ if not running_under_virtualenv(): return True - return normalize_path(path).startswith(normalize_path(sys.prefix)) + return path.startswith(normalize_path(sys.prefix)) def dist_is_local(dist): @@ -340,8 +370,7 @@ def dist_in_usersite(dist): """ Return True if given Distribution is installed in user site. """ - norm_path = normalize_path(dist_location(dist)) - return norm_path.startswith(normalize_path(user_site)) + return dist_location(dist).startswith(normalize_path(user_site)) def dist_in_site_packages(dist): @@ -350,9 +379,7 @@ def dist_in_site_packages(dist): Return True if given Distribution is installed in sysconfig.get_python_lib(). """ - return normalize_path( - dist_location(dist) - ).startswith(normalize_path(site_packages)) + return dist_location(dist).startswith(normalize_path(site_packages)) def dist_is_editable(dist): @@ -361,69 +388,58 @@ def dist_is_editable(dist): Return True if given Distribution is an editable install. """ for path_item in sys.path: - egg_link = os.path.join(path_item, dist.project_name + '.egg-link') + egg_link = os.path.join(path_item, dist.project_name + ".egg-link") if os.path.isfile(egg_link): return True return False -def get_installed_distributions(local_only=True, - skip=stdlib_pkgs, - include_editables=True, - editables_only=False, - user_only=False): - # type: (bool, Container[str], bool, bool, bool) -> List[Distribution] +def get_installed_distributions( + local_only=True, # type: bool + skip=stdlib_pkgs, # type: Container[str] + include_editables=True, # type: bool + editables_only=False, # type: bool + user_only=False, # type: bool + paths=None, # type: Optional[List[str]] +): + # type: (...) -> List[Distribution] + """Return a list of installed Distribution objects. + + Left for compatibility until direct pkg_resources uses are refactored out. """ - Return a list of installed Distribution objects. + from pipenv.patched.notpip._internal.metadata import get_default_environment, get_environment + from pipenv.patched.notpip._internal.metadata.pkg_resources import Distribution as _Dist - If ``local_only`` is True (default), only return installations - local to the current virtualenv, if in a virtualenv. + if paths is None: + env = get_default_environment() + else: + env = get_environment(paths) + dists = env.iter_installed_distributions( + local_only=local_only, + skip=skip, + include_editables=include_editables, + editables_only=editables_only, + user_only=user_only, + ) + return [cast(_Dist, dist)._dist for dist in dists] - ``skip`` argument is an iterable of lower-case project names to - ignore; defaults to stdlib_pkgs - If ``include_editables`` is False, don't report editables. +def get_distribution(req_name): + # type: (str) -> Optional[Distribution] + """Given a requirement name, return the installed Distribution object. - If ``editables_only`` is True , only report editables. - - If ``user_only`` is True , only report installations in the user - site directory. + This searches from *all* distributions available in the environment, to + match the behavior of ``pkg_resources.get_distribution()``. + Left for compatibility until direct pkg_resources uses are refactored out. """ - if local_only: - local_test = dist_is_local - else: - def local_test(d): - return True + from pipenv.patched.notpip._internal.metadata import get_default_environment + from pipenv.patched.notpip._internal.metadata.pkg_resources import Distribution as _Dist - if include_editables: - def editable_test(d): - return True - else: - def editable_test(d): - return not dist_is_editable(d) - - if editables_only: - def editables_only_test(d): - return dist_is_editable(d) - else: - def editables_only_test(d): - return True - - if user_only: - user_test = dist_in_usersite - else: - def user_test(d): - return True - - # because of pkg_resources vendoring, mypy cannot find stub in typeshed - return [d for d in pkg_resources.working_set # type: ignore - if local_test(d) and - d.key not in skip and - editable_test(d) and - editables_only_test(d) and - user_test(d) - ] + dist = get_default_environment().get_distribution(req_name) + if dist is None: + return None + return cast(_Dist, dist)._dist def egg_link_path(dist): @@ -447,19 +463,16 @@ def egg_link_path(dist): """ sites = [] if running_under_virtualenv(): - if virtualenv_no_global(): - sites.append(site_packages) - else: - sites.append(site_packages) - if user_site: - sites.append(user_site) + sites.append(site_packages) + if not virtualenv_no_global() and user_site: + sites.append(user_site) else: if user_site: sites.append(user_site) sites.append(site_packages) for site in sites: - egglink = os.path.join(site, dist.project_name) + '.egg-link' + egglink = os.path.join(site, dist.project_name) + ".egg-link" if os.path.isfile(egglink): return egglink return None @@ -473,372 +486,38 @@ def dist_location(dist): packages, where dist.location is the source code location, and we want to know where the egg-link file is. + The returned location is normalized (in particular, with symlinks removed). """ egg_link = egg_link_path(dist) if egg_link: - return egg_link - return dist.location + return normalize_path(egg_link) + return normalize_path(dist.location) -def current_umask(): - """Get the current umask which involves having to set it temporarily.""" - mask = os.umask(0) - os.umask(mask) - return mask - - -def unzip_file(filename, location, flatten=True): - # type: (str, str, bool) -> None - """ - Unzip the file (with path `filename`) to the destination `location`. All - files are written based on system defaults and umask (i.e. permissions are - not preserved), except that regular file members with any execute - permissions (user, group, or world) have "chmod +x" applied after being - written. Note that for windows, any execute changes using os.chmod are - no-ops per the python docs. - """ - ensure_dir(location) - zipfp = open(filename, 'rb') - try: - zip = zipfile.ZipFile(zipfp, allowZip64=True) - leading = has_leading_dir(zip.namelist()) and flatten - for info in zip.infolist(): - name = info.filename - fn = name - if leading: - fn = split_leading_dir(name)[1] - fn = os.path.join(location, fn) - dir = os.path.dirname(fn) - if fn.endswith('/') or fn.endswith('\\'): - # A directory - ensure_dir(fn) - else: - ensure_dir(dir) - # Don't use read() to avoid allocating an arbitrarily large - # chunk of memory for the file's content - fp = zip.open(name) - try: - with open(fn, 'wb') as destfp: - shutil.copyfileobj(fp, destfp) - finally: - fp.close() - mode = info.external_attr >> 16 - # if mode and regular file and any execute permissions for - # user/group/world? - if mode and stat.S_ISREG(mode) and mode & 0o111: - # make dest file have execute for user/group/world - # (chmod +x) no-op on windows per python docs - os.chmod(fn, (0o777 - current_umask() | 0o111)) - finally: - zipfp.close() - - -def untar_file(filename, location): - # type: (str, str) -> None - """ - Untar the file (with path `filename`) to the destination `location`. - All files are written based on system defaults and umask (i.e. permissions - are not preserved), except that regular file members with any execute - permissions (user, group, or world) have "chmod +x" applied after being - written. Note that for windows, any execute changes using os.chmod are - no-ops per the python docs. - """ - ensure_dir(location) - if filename.lower().endswith('.gz') or filename.lower().endswith('.tgz'): - mode = 'r:gz' - elif filename.lower().endswith(BZ2_EXTENSIONS): - mode = 'r:bz2' - elif filename.lower().endswith(XZ_EXTENSIONS): - mode = 'r:xz' - elif filename.lower().endswith('.tar'): - mode = 'r' - else: - logger.warning( - 'Cannot determine compression type for file %s', filename, - ) - mode = 'r:*' - tar = tarfile.open(filename, mode) - try: - leading = has_leading_dir([ - member.name for member in tar.getmembers() - ]) - for member in tar.getmembers(): - fn = member.name - if leading: - # https://github.com/python/mypy/issues/1174 - fn = split_leading_dir(fn)[1] # type: ignore - path = os.path.join(location, fn) - if member.isdir(): - ensure_dir(path) - elif member.issym(): - try: - # https://github.com/python/typeshed/issues/2673 - tar._extract_member(member, path) # type: ignore - except Exception as exc: - # Some corrupt tar files seem to produce this - # (specifically bad symlinks) - logger.warning( - 'In the tar file %s the member %s is invalid: %s', - filename, member.name, exc, - ) - continue - else: - try: - fp = tar.extractfile(member) - except (KeyError, AttributeError) as exc: - # Some corrupt tar files seem to produce this - # (specifically bad symlinks) - logger.warning( - 'In the tar file %s the member %s is invalid: %s', - filename, member.name, exc, - ) - continue - ensure_dir(os.path.dirname(path)) - with open(path, 'wb') as destfp: - shutil.copyfileobj(fp, destfp) - fp.close() - # Update the timestamp (useful for cython compiled files) - # https://github.com/python/typeshed/issues/2673 - tar.utime(member, path) # type: ignore - # member have any execute permissions for user/group/world? - if member.mode & 0o111: - # make dest file have execute for user/group/world - # no-op on windows per python docs - os.chmod(path, (0o777 - current_umask() | 0o111)) - finally: - tar.close() - - -def unpack_file( - filename, # type: str - location, # type: str - content_type, # type: Optional[str] - link # type: Optional[Link] -): - # type: (...) -> None - filename = os.path.realpath(filename) - if (content_type == 'application/zip' or - filename.lower().endswith(ZIP_EXTENSIONS) or - zipfile.is_zipfile(filename)): - unzip_file( - filename, - location, - flatten=not filename.endswith('.whl') - ) - elif (content_type == 'application/x-gzip' or - tarfile.is_tarfile(filename) or - filename.lower().endswith( - TAR_EXTENSIONS + BZ2_EXTENSIONS + XZ_EXTENSIONS)): - untar_file(filename, location) - elif (content_type and content_type.startswith('text/html') and - is_svn_page(file_contents(filename))): - # We don't really care about this - from pipenv.patched.notpip._internal.vcs.subversion import Subversion - Subversion('svn+' + link.url).unpack(location) - else: - # FIXME: handle? - # FIXME: magic signatures? - logger.critical( - 'Cannot unpack file %s (downloaded from %s, content-type: %s); ' - 'cannot detect archive format', - filename, location, content_type, - ) - raise InstallationError( - 'Cannot determine archive format of %s' % location - ) - - -def call_subprocess( - cmd, # type: List[str] - show_stdout=True, # type: bool - cwd=None, # type: Optional[str] - on_returncode='raise', # type: str - extra_ok_returncodes=None, # type: Optional[Iterable[int]] - command_desc=None, # type: Optional[str] - extra_environ=None, # type: Optional[Mapping[str, Any]] - unset_environ=None, # type: Optional[Iterable[str]] - spinner=None # type: Optional[SpinnerInterface] -): - # type: (...) -> Optional[Text] - """ - Args: - extra_ok_returncodes: an iterable of integer return codes that are - acceptable, in addition to 0. Defaults to None, which means []. - unset_environ: an iterable of environment variable names to unset - prior to calling subprocess.Popen(). - """ - if extra_ok_returncodes is None: - extra_ok_returncodes = [] - if unset_environ is None: - unset_environ = [] - # This function's handling of subprocess output is confusing and I - # previously broke it terribly, so as penance I will write a long comment - # explaining things. - # - # The obvious thing that affects output is the show_stdout= - # kwarg. show_stdout=True means, let the subprocess write directly to our - # stdout. Even though it is nominally the default, it is almost never used - # inside pip (and should not be used in new code without a very good - # reason); as of 2016-02-22 it is only used in a few places inside the VCS - # wrapper code. Ideally we should get rid of it entirely, because it - # creates a lot of complexity here for a rarely used feature. - # - # Most places in pip set show_stdout=False. What this means is: - # - We connect the child stdout to a pipe, which we read. - # - By default, we hide the output but show a spinner -- unless the - # subprocess exits with an error, in which case we show the output. - # - If the --verbose option was passed (= loglevel is DEBUG), then we show - # the output unconditionally. (But in this case we don't want to show - # the output a second time if it turns out that there was an error.) - # - # stderr is always merged with stdout (even if show_stdout=True). - if show_stdout: - stdout = None - else: - stdout = subprocess.PIPE - if command_desc is None: - cmd_parts = [] - for part in cmd: - if ' ' in part or '\n' in part or '"' in part or "'" in part: - part = '"%s"' % part.replace('"', '\\"') - cmd_parts.append(part) - command_desc = ' '.join(cmd_parts) - logger.debug("Running command %s", command_desc) - env = os.environ.copy() - if extra_environ: - env.update(extra_environ) - for name in unset_environ: - env.pop(name, None) - try: - proc = subprocess.Popen( - cmd, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, - stdout=stdout, cwd=cwd, env=env, - ) - proc.stdin.close() - except Exception as exc: - logger.critical( - "Error %s while executing command %s", exc, command_desc, - ) - raise - all_output = [] - if stdout is not None: - while True: - line = console_to_str(proc.stdout.readline()) - if not line: - break - line = line.rstrip() - all_output.append(line + '\n') - if logger.getEffectiveLevel() <= std_logging.DEBUG: - # Show the line immediately - logger.debug(line) - else: - # Update the spinner - if spinner is not None: - spinner.spin() - try: - proc.wait() - finally: - if proc.stdout: - proc.stdout.close() - if spinner is not None: - if proc.returncode: - spinner.finish("error") - else: - spinner.finish("done") - if proc.returncode and proc.returncode not in extra_ok_returncodes: - if on_returncode == 'raise': - if (logger.getEffectiveLevel() > std_logging.DEBUG and - not show_stdout): - logger.info( - 'Complete output from command %s:', command_desc, - ) - logger.info( - ''.join(all_output) + - '\n----------------------------------------' - ) - raise InstallationError( - 'Command "%s" failed with error code %s in %s' - % (command_desc, proc.returncode, cwd)) - elif on_returncode == 'warn': - logger.warning( - 'Command "%s" had error code %s in %s', - command_desc, proc.returncode, cwd, - ) - elif on_returncode == 'ignore': - pass - else: - raise ValueError('Invalid value: on_returncode=%s' % - repr(on_returncode)) - if not show_stdout: - return ''.join(all_output) - return None - - -def read_text_file(filename): - # type: (str) -> str - """Return the contents of *filename*. - - Try to decode the file contents with utf-8, the preferred system encoding - (e.g., cp1252 on some Windows machines), and latin1, in that order. - Decoding a byte string with latin1 will never raise an error. In the worst - case, the returned string will contain some garbage characters. - - """ - with open(filename, 'rb') as fp: - data = fp.read() - - encodings = ['utf-8', locale.getpreferredencoding(False), 'latin1'] - for enc in encodings: - try: - # https://github.com/python/mypy/issues/1174 - data = data.decode(enc) # type: ignore - except UnicodeDecodeError: - continue - break - - assert not isinstance(data, bytes) # Latin1 should have worked. - return data - - -def _make_build_dir(build_dir): - os.makedirs(build_dir) - write_delete_marker_file(build_dir) - - -class FakeFile(object): - """Wrap a list of lines in an object with readline() to make - ConfigParser happy.""" - def __init__(self, lines): - self._gen = (l for l in lines) - - def readline(self): - try: - try: - return next(self._gen) - except NameError: - return self._gen.next() - except StopIteration: - return '' - - def __iter__(self): - return self._gen +def write_output(msg, *args): + # type: (Any, Any) -> None + logger.info(msg, *args) class StreamWrapper(StringIO): + orig_stream = None # type: TextIO @classmethod def from_stream(cls, orig_stream): + # type: (TextIO) -> StreamWrapper cls.orig_stream = orig_stream return cls() # compileall.compile_dir() needs stdout.encoding to print to stdout + # https://github.com/python/mypy/issues/4125 @property - def encoding(self): + def encoding(self): # type: ignore return self.orig_stream.encoding @contextlib.contextmanager def captured_output(stream_name): + # type: (str) -> Iterator[StreamWrapper] """Return a context manager used by captured_stdout/stdin/stderr that temporarily replaces the sys stream *stream_name* with a StringIO. @@ -853,6 +532,7 @@ def captured_output(stream_name): def captured_stdout(): + # type: () -> ContextManager[StreamWrapper] """Capture the output of sys.stdout: with captured_stdout() as stdout: @@ -861,157 +541,209 @@ def captured_stdout(): Taken from Lib/support/__init__.py in the CPython repo. """ - return captured_output('stdout') + return captured_output("stdout") def captured_stderr(): + # type: () -> ContextManager[StreamWrapper] """ See captured_stdout(). """ - return captured_output('stderr') - - -class cached_property(object): - """A property that is only computed once per instance and then replaces - itself with an ordinary attribute. Deleting the attribute resets the - property. - - Source: https://github.com/bottlepy/bottle/blob/0.11.5/bottle.py#L175 - """ - - def __init__(self, func): - self.__doc__ = getattr(func, '__doc__') - self.func = func - - def __get__(self, obj, cls): - if obj is None: - # We're being accessed from the class itself, not from an object - return self - value = obj.__dict__[self.func.__name__] = self.func(obj) - return value - - -def get_installed_version(dist_name, working_set=None): - """Get the installed version of dist_name avoiding pkg_resources cache""" - # Create a requirement that we'll look for inside of setuptools. - req = pkg_resources.Requirement.parse(dist_name) - - if working_set is None: - # We want to avoid having this cached, so we need to construct a new - # working set each time. - working_set = pkg_resources.WorkingSet() - - # Get the installed distribution from our working set - dist = working_set.find(req) - - # Check to see if we got an installed distribution or not, if we did - # we want to return it's version. - return dist.version if dist else None - - -def consume(iterator): - """Consume an iterable at C speed.""" - deque(iterator, maxlen=0) + return captured_output("stderr") # Simulates an enum def enum(*sequential, **named): + # type: (*Any, **Any) -> Type[Any] enums = dict(zip(sequential, range(len(sequential))), **named) reverse = {value: key for key, value in enums.items()} - enums['reverse_mapping'] = reverse - return type('Enum', (), enums) + enums["reverse_mapping"] = reverse + return type("Enum", (), enums) -def make_vcs_requirement_url(repo_url, rev, project_name, subdir=None): +def build_netloc(host, port): + # type: (str, Optional[int]) -> str """ - Return the URL for a VCS requirement. - - Args: - repo_url: the remote VCS url, with any needed VCS prefix (e.g. "git+"). - project_name: the (unescaped) project name. + Build a netloc from a host-port pair """ - egg_project_name = pkg_resources.to_filename(project_name) - req = '{}@{}#egg={}'.format(repo_url, rev, egg_project_name) - if subdir: - req += '&subdirectory={}'.format(subdir) + if port is None: + return host + if ":" in host: + # Only wrap host with square brackets when it is IPv6 + host = f"[{host}]" + return f"{host}:{port}" - return req + +def build_url_from_netloc(netloc, scheme="https"): + # type: (str, str) -> str + """ + Build a full URL from a netloc. + """ + if netloc.count(":") >= 2 and "@" not in netloc and "[" not in netloc: + # It must be a bare IPv6 address, so wrap it with brackets. + netloc = f"[{netloc}]" + return f"{scheme}://{netloc}" + + +def parse_netloc(netloc): + # type: (str) -> Tuple[str, Optional[int]] + """ + Return the host-port pair from a netloc. + """ + url = build_url_from_netloc(netloc) + parsed = urllib.parse.urlparse(url) + return parsed.hostname, parsed.port def split_auth_from_netloc(netloc): + # type: (str) -> NetlocTuple """ Parse out and remove the auth information from a netloc. Returns: (netloc, (username, password)). """ - if '@' not in netloc: + if "@" not in netloc: return netloc, (None, None) # Split from the right because that's how urllib.parse.urlsplit() # behaves if more than one @ is present (which can be checked using # the password attribute of urlsplit()'s return value). - auth, netloc = netloc.rsplit('@', 1) - if ':' in auth: + auth, netloc = netloc.rsplit("@", 1) + pw = None # type: Optional[str] + if ":" in auth: # Split from the left because that's how urllib.parse.urlsplit() # behaves if more than one : is present (which again can be checked # using the password attribute of the return value) - user_pass = auth.split(':', 1) + user, pw = auth.split(":", 1) else: - user_pass = auth, None + user, pw = auth, None - user_pass = tuple( - None if x is None else urllib_unquote(x) for x in user_pass - ) + user = urllib.parse.unquote(user) + if pw is not None: + pw = urllib.parse.unquote(pw) - return netloc, user_pass + return netloc, (user, pw) def redact_netloc(netloc): # type: (str) -> str """ - Replace the password in a netloc with "****", if it exists. + Replace the sensitive data in a netloc with "****", if it exists. - For example, "user:pass@example.com" returns "user:****@example.com". + For example: + - "user:pass@example.com" returns "user:****@example.com" + - "accesstoken@example.com" returns "****@example.com" """ netloc, (user, password) = split_auth_from_netloc(netloc) if user is None: return netloc - password = '' if password is None else ':****' - return '{user}{password}@{netloc}'.format(user=urllib_parse.quote(user), - password=password, - netloc=netloc) + if password is None: + user = "****" + password = "" + else: + user = urllib.parse.quote(user) + password = ":****" + return "{user}{password}@{netloc}".format( + user=user, password=password, netloc=netloc + ) def _transform_url(url, transform_netloc): - purl = urllib_parse.urlsplit(url) - netloc = transform_netloc(purl.netloc) + # type: (str, Callable[[str], Tuple[Any, ...]]) -> Tuple[str, NetlocTuple] + """Transform and replace netloc in a url. + + transform_netloc is a function taking the netloc and returning a + tuple. The first element of this tuple is the new netloc. The + entire tuple is returned. + + Returns a tuple containing the transformed url as item 0 and the + original tuple returned by transform_netloc as item 1. + """ + purl = urllib.parse.urlsplit(url) + netloc_tuple = transform_netloc(purl.netloc) # stripped url - url_pieces = ( - purl.scheme, netloc, purl.path, purl.query, purl.fragment - ) - surl = urllib_parse.urlunsplit(url_pieces) - return surl + url_pieces = (purl.scheme, netloc_tuple[0], purl.path, purl.query, purl.fragment) + surl = urllib.parse.urlunsplit(url_pieces) + return surl, cast("NetlocTuple", netloc_tuple) def _get_netloc(netloc): - return split_auth_from_netloc(netloc)[0] + # type: (str) -> NetlocTuple + return split_auth_from_netloc(netloc) + + +def _redact_netloc(netloc): + # type: (str) -> Tuple[str,] + return (redact_netloc(netloc),) + + +def split_auth_netloc_from_url(url): + # type: (str) -> Tuple[str, str, Tuple[str, str]] + """ + Parse a url into separate netloc, auth, and url with no auth. + + Returns: (url_without_auth, netloc, (username, password)) + """ + url_without_auth, (netloc, auth) = _transform_url(url, _get_netloc) + return url_without_auth, netloc, auth def remove_auth_from_url(url): # type: (str) -> str - # Return a copy of url with 'username:password@' removed. + """Return a copy of url with 'username:password@' removed.""" # username/pass params are passed to subversion through flags # and are not recognized in the url. - return _transform_url(url, _get_netloc) + return _transform_url(url, _get_netloc)[0] -def redact_password_from_url(url): +def redact_auth_from_url(url): # type: (str) -> str """Replace the password in a given url with ****.""" - return _transform_url(url, redact_netloc) + return _transform_url(url, _redact_netloc)[0] + + +class HiddenText: + def __init__( + self, + secret, # type: str + redacted, # type: str + ): + # type: (...) -> None + self.secret = secret + self.redacted = redacted + + def __repr__(self): + # type: (...) -> str + return "<HiddenText {!r}>".format(str(self)) + + def __str__(self): + # type: (...) -> str + return self.redacted + + # This is useful for testing. + def __eq__(self, other): + # type: (Any) -> bool + if type(self) != type(other): + return False + + # The string being used for redaction doesn't also have to match, + # just the raw, original string. + return self.secret == other.secret + + +def hide_value(value): + # type: (str) -> HiddenText + return HiddenText(value, redacted="****") + + +def hide_url(url): + # type: (str) -> HiddenText + redacted = redact_auth_from_url(url) + return HiddenText(url, redacted=redacted) def protect_pip_from_modification_on_windows(modifying_pip): + # type: (bool) -> None """Protection of pip.exe from modification on Windows On Windows, any operation modifying pip should be run as: @@ -1020,21 +752,77 @@ def protect_pip_from_modification_on_windows(modifying_pip): pip_names = [ "pip.exe", "pip{}.exe".format(sys.version_info[0]), - "pip{}.{}.exe".format(*sys.version_info[:2]) + "pip{}.{}.exe".format(*sys.version_info[:2]), ] # See https://github.com/pypa/pip/issues/1299 for more discussion should_show_use_python_msg = ( - modifying_pip and - WINDOWS and - os.path.basename(sys.argv[0]) in pip_names + modifying_pip and WINDOWS and os.path.basename(sys.argv[0]) in pip_names ) if should_show_use_python_msg: - new_command = [ - sys.executable, "-m", "pip" - ] + sys.argv[1:] + new_command = [sys.executable, "-m", "pip"] + sys.argv[1:] raise CommandError( - 'To modify pip, please run the following command:\n{}' - .format(" ".join(new_command)) + "To modify pip, please run the following command:\n{}".format( + " ".join(new_command) + ) ) + + +def is_console_interactive(): + # type: () -> bool + """Is this console interactive?""" + return sys.stdin is not None and sys.stdin.isatty() + + +def hash_file(path, blocksize=1 << 20): + # type: (str, int) -> Tuple[Any, int] + """Return (hash, length) for path using hashlib.sha256()""" + + h = hashlib.sha256() + length = 0 + with open(path, "rb") as f: + for block in read_chunks(f, size=blocksize): + length += len(block) + h.update(block) + return h, length + + +def is_wheel_installed(): + # type: () -> bool + """ + Return whether the wheel package is installed. + """ + try: + import pipenv.vendor.wheel as wheel # noqa: F401 + except ImportError: + return False + + return True + + +def pairwise(iterable): + # type: (Iterable[Any]) -> Iterator[Tuple[Any, Any]] + """ + Return paired elements. + + For example: + s -> (s0, s1), (s2, s3), (s4, s5), ... + """ + iterable = iter(iterable) + return zip_longest(iterable, iterable) + + +def partition( + pred, # type: Callable[[T], bool] + iterable, # type: Iterable[T] +): + # type: (...) -> Tuple[Iterable[T], Iterable[T]] + """ + Use a predicate to partition entries into false entries and true entries, + like + + partition(is_odd, range(10)) --> 0 2 4 6 8 and 1 3 5 7 9 + """ + t1, t2 = tee(iterable) + return filterfalse(pred, t1), filter(pred, t2) diff --git a/pipenv/patched/notpip/_internal/utils/models.py b/pipenv/patched/notpip/_internal/utils/models.py index d5cb80a7..0e02bc7a 100644 --- a/pipenv/patched/notpip/_internal/utils/models.py +++ b/pipenv/patched/notpip/_internal/utils/models.py @@ -2,38 +2,45 @@ """ import operator +from typing import Any, Callable, Type -class KeyBasedCompareMixin(object): - """Provides comparision capabilities that is based on a key - """ +class KeyBasedCompareMixin: + """Provides comparison capabilities that is based on a key""" + + __slots__ = ["_compare_key", "_defining_class"] def __init__(self, key, defining_class): + # type: (Any, Type[KeyBasedCompareMixin]) -> None self._compare_key = key self._defining_class = defining_class def __hash__(self): + # type: () -> int return hash(self._compare_key) def __lt__(self, other): + # type: (Any) -> bool return self._compare(other, operator.__lt__) def __le__(self, other): + # type: (Any) -> bool return self._compare(other, operator.__le__) def __gt__(self, other): + # type: (Any) -> bool return self._compare(other, operator.__gt__) def __ge__(self, other): + # type: (Any) -> bool return self._compare(other, operator.__ge__) def __eq__(self, other): + # type: (Any) -> bool return self._compare(other, operator.__eq__) - def __ne__(self, other): - return self._compare(other, operator.__ne__) - def _compare(self, other, method): + # type: (Any, Callable[[Any, Any], bool]) -> bool if not isinstance(other, self._defining_class): return NotImplemented diff --git a/pipenv/patched/notpip/_internal/utils/outdated.py b/pipenv/patched/notpip/_internal/utils/outdated.py deleted file mode 100644 index 83dc58cc..00000000 --- a/pipenv/patched/notpip/_internal/utils/outdated.py +++ /dev/null @@ -1,164 +0,0 @@ -from __future__ import absolute_import - -import datetime -import json -import logging -import os.path -import sys - -from pipenv.patched.notpip._vendor import lockfile, pkg_resources -from pipenv.patched.notpip._vendor.packaging import version as packaging_version - -from pipenv.patched.notpip._internal.index import PackageFinder -from pipenv.patched.notpip._internal.utils.compat import WINDOWS -from pipenv.patched.notpip._internal.utils.filesystem import check_path_owner -from pipenv.patched.notpip._internal.utils.misc import ensure_dir, get_installed_version -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING - -if MYPY_CHECK_RUNNING: - import optparse # noqa: F401 - from typing import Any, Dict # noqa: F401 - from pipenv.patched.notpip._internal.download import PipSession # noqa: F401 - - -SELFCHECK_DATE_FMT = "%Y-%m-%dT%H:%M:%SZ" - - -logger = logging.getLogger(__name__) - - -class SelfCheckState(object): - def __init__(self, cache_dir): - # type: (str) -> None - self.state = {} # type: Dict[str, Any] - self.statefile_path = None - - # Try to load the existing state - if cache_dir: - self.statefile_path = os.path.join(cache_dir, "selfcheck.json") - try: - with open(self.statefile_path) as statefile: - self.state = json.load(statefile)[sys.prefix] - except (IOError, ValueError, KeyError): - # Explicitly suppressing exceptions, since we don't want to - # error out if the cache file is invalid. - pass - - def save(self, pypi_version, current_time): - # type: (str, datetime.datetime) -> None - # If we do not have a path to cache in, don't bother saving. - if not self.statefile_path: - return - - # Check to make sure that we own the directory - if not check_path_owner(os.path.dirname(self.statefile_path)): - return - - # Now that we've ensured the directory is owned by this user, we'll go - # ahead and make sure that all our directories are created. - ensure_dir(os.path.dirname(self.statefile_path)) - - # Attempt to write out our version check file - with lockfile.LockFile(self.statefile_path): - if os.path.exists(self.statefile_path): - with open(self.statefile_path) as statefile: - state = json.load(statefile) - else: - state = {} - - state[sys.prefix] = { - "last_check": current_time.strftime(SELFCHECK_DATE_FMT), - "pypi_version": pypi_version, - } - - with open(self.statefile_path, "w") as statefile: - json.dump(state, statefile, sort_keys=True, - separators=(",", ":")) - - -def was_installed_by_pip(pkg): - # type: (str) -> bool - """Checks whether pkg was installed by pip - - This is used not to display the upgrade message when pip is in fact - installed by system package manager, such as dnf on Fedora. - """ - try: - dist = pkg_resources.get_distribution(pkg) - return (dist.has_metadata('INSTALLER') and - 'pip' in dist.get_metadata_lines('INSTALLER')) - except pkg_resources.DistributionNotFound: - return False - - -def pip_version_check(session, options): - # type: (PipSession, optparse.Values) -> None - """Check for an update for pip. - - Limit the frequency of checks to once per week. State is stored either in - the active virtualenv or in the user's USER_CACHE_DIR keyed off the prefix - of the pip script path. - """ - installed_version = get_installed_version("pip") - if not installed_version: - return - - pip_version = packaging_version.parse(installed_version) - pypi_version = None - - try: - state = SelfCheckState(cache_dir=options.cache_dir) - - current_time = datetime.datetime.utcnow() - # Determine if we need to refresh the state - if "last_check" in state.state and "pypi_version" in state.state: - last_check = datetime.datetime.strptime( - state.state["last_check"], - SELFCHECK_DATE_FMT - ) - if (current_time - last_check).total_seconds() < 7 * 24 * 60 * 60: - pypi_version = state.state["pypi_version"] - - # Refresh the version if we need to or just see if we need to warn - if pypi_version is None: - # Lets use PackageFinder to see what the latest pip version is - finder = PackageFinder( - find_links=options.find_links, - index_urls=[options.index_url] + options.extra_index_urls, - allow_all_prereleases=False, # Explicitly set to False - trusted_hosts=options.trusted_hosts, - session=session, - ) - all_candidates = finder.find_all_candidates("pip") - if not all_candidates: - return - pypi_version = str( - max(all_candidates, key=lambda c: c.version).version - ) - - # save that we've performed a check - state.save(pypi_version, current_time) - - remote_version = packaging_version.parse(pypi_version) - - # Determine if our pypi_version is older - if (pip_version < remote_version and - pip_version.base_version != remote_version.base_version and - was_installed_by_pip('pip')): - # Advise "python -m pip" on Windows to avoid issues - # with overwriting pip.exe. - if WINDOWS: - pip_cmd = "python -m pip" - else: - pip_cmd = "pip" - logger.warning( - "You are using pip version %s, however version %s is " - "available.\nYou should consider upgrading via the " - "'%s install --upgrade pip' command.", - pip_version, pypi_version, pip_cmd - ) - except Exception: - logger.debug( - "There was an error checking the latest version of pip", - exc_info=True, - ) diff --git a/pipenv/patched/notpip/_internal/utils/packaging.py b/pipenv/patched/notpip/_internal/utils/packaging.py index dc944529..ea6d29f1 100644 --- a/pipenv/patched/notpip/_internal/utils/packaging.py +++ b/pipenv/patched/notpip/_internal/utils/packaging.py @@ -1,87 +1,89 @@ -from __future__ import absolute_import - import logging -import sys +from email.message import Message from email.parser import FeedParser +from typing import Optional, Tuple from pipenv.patched.notpip._vendor import pkg_resources from pipenv.patched.notpip._vendor.packaging import specifiers, version +from pipenv.patched.notpip._vendor.pkg_resources import Distribution -from pipenv.patched.notpip._internal import exceptions +from pipenv.patched.notpip._internal.exceptions import NoneMetadataError from pipenv.patched.notpip._internal.utils.misc import display_path -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING - -if MYPY_CHECK_RUNNING: - from typing import Optional # noqa: F401 - from email.message import Message # noqa: F401 - from pipenv.patched.notpip._vendor.pkg_resources import Distribution # noqa: F401 - logger = logging.getLogger(__name__) -def check_requires_python(requires_python): - # type: (Optional[str]) -> bool +def check_requires_python(requires_python, version_info): + # type: (Optional[str], Tuple[int, ...]) -> bool """ - Check if the python version in use match the `requires_python` specifier. + Check if the given Python version matches a "Requires-Python" specifier. - Returns `True` if the version of python in use matches the requirement. - Returns `False` if the version of python in use does not matches the - requirement. + :param version_info: A 3-tuple of ints representing a Python + major-minor-micro version to check (e.g. `sys.version_info[:3]`). - Raises an InvalidSpecifier if `requires_python` have an invalid format. + :return: `True` if the given Python version satisfies the requirement. + Otherwise, return `False`. + + :raises InvalidSpecifier: If `requires_python` has an invalid format. """ if requires_python is None: # The package provides no information return True requires_python_specifier = specifiers.SpecifierSet(requires_python) - # We only use major.minor.micro - python_version = version.parse('{0}.{1}.{2}'.format(*sys.version_info[:3])) + python_version = version.parse(".".join(map(str, version_info))) return python_version in requires_python_specifier def get_metadata(dist): # type: (Distribution) -> Message - if (isinstance(dist, pkg_resources.DistInfoDistribution) and - dist.has_metadata('METADATA')): - metadata = dist.get_metadata('METADATA') - elif dist.has_metadata('PKG-INFO'): - metadata = dist.get_metadata('PKG-INFO') + """ + :raises NoneMetadataError: if the distribution reports `has_metadata()` + True but `get_metadata()` returns None. + """ + metadata_name = "METADATA" + if isinstance(dist, pkg_resources.DistInfoDistribution) and dist.has_metadata( + metadata_name + ): + metadata = dist.get_metadata(metadata_name) + elif dist.has_metadata("PKG-INFO"): + metadata_name = "PKG-INFO" + metadata = dist.get_metadata(metadata_name) else: logger.warning("No metadata found in %s", display_path(dist.location)) - metadata = '' + metadata = "" + + if metadata is None: + raise NoneMetadataError(dist, metadata_name) feed_parser = FeedParser() + # The following line errors out if with a "NoneType" TypeError if + # passed metadata=None. feed_parser.feed(metadata) return feed_parser.close() -def check_dist_requires_python(dist, absorb=False): +def get_requires_python(dist): + # type: (pkg_resources.Distribution) -> Optional[str] + """ + Return the "Requires-Python" metadata for a distribution, or None + if not present. + """ pkg_info_dict = get_metadata(dist) - requires_python = pkg_info_dict.get('Requires-Python') - if absorb: - return requires_python - try: - if not check_requires_python(requires_python): - raise exceptions.UnsupportedPythonVersion( - "%s requires Python '%s' but the running Python is %s" % ( - dist.project_name, - requires_python, - '.'.join(map(str, sys.version_info[:3])),) - ) - except specifiers.InvalidSpecifier as e: - logger.warning( - "Package %s has an invalid Requires-Python entry %s - %s", - dist.project_name, requires_python, e, - ) - return + requires_python = pkg_info_dict.get("Requires-Python") + + if requires_python is not None: + # Convert to a str to satisfy the type checker, since requires_python + # can be a Header object. + requires_python = str(requires_python) + + return requires_python def get_installer(dist): # type: (Distribution) -> str - if dist.has_metadata('INSTALLER'): - for line in dist.get_metadata_lines('INSTALLER'): + if dist.has_metadata("INSTALLER"): + for line in dist.get_metadata_lines("INSTALLER"): if line.strip(): return line.strip() - return '' + return "" diff --git a/pipenv/patched/notpip/_internal/utils/parallel.py b/pipenv/patched/notpip/_internal/utils/parallel.py new file mode 100644 index 00000000..31e12c8e --- /dev/null +++ b/pipenv/patched/notpip/_internal/utils/parallel.py @@ -0,0 +1,101 @@ +"""Convenient parallelization of higher order functions. + +This module provides two helper functions, with appropriate fallbacks on +Python 2 and on systems lacking support for synchronization mechanisms: + +- map_multiprocess +- map_multithread + +These helpers work like Python 3's map, with two differences: + +- They don't guarantee the order of processing of + the elements of the iterable. +- The underlying process/thread pools chop the iterable into + a number of chunks, so that for very long iterables using + a large value for chunksize can make the job complete much faster + than using the default value of 1. +""" + +__all__ = ["map_multiprocess", "map_multithread"] + +from contextlib import contextmanager +from multiprocessing import Pool as ProcessPool +from multiprocessing import pool +from multiprocessing.dummy import Pool as ThreadPool +from typing import Callable, Iterable, Iterator, TypeVar, Union + +from pipenv.patched.notpip._vendor.requests.adapters import DEFAULT_POOLSIZE + +Pool = Union[pool.Pool, pool.ThreadPool] +S = TypeVar("S") +T = TypeVar("T") + +# On platforms without sem_open, multiprocessing[.dummy] Pool +# cannot be created. +try: + import multiprocessing.synchronize # noqa +except ImportError: + LACK_SEM_OPEN = True +else: + LACK_SEM_OPEN = False + +# Incredibly large timeout to work around bpo-8296 on Python 2. +TIMEOUT = 2000000 + + +@contextmanager +def closing(pool): + # type: (Pool) -> Iterator[Pool] + """Return a context manager making sure the pool closes properly.""" + try: + yield pool + finally: + # For Pool.imap*, close and join are needed + # for the returned iterator to begin yielding. + pool.close() + pool.join() + pool.terminate() + + +def _map_fallback(func, iterable, chunksize=1): + # type: (Callable[[S], T], Iterable[S], int) -> Iterator[T] + """Make an iterator applying func to each element in iterable. + + This function is the sequential fallback either on Python 2 + where Pool.imap* doesn't react to KeyboardInterrupt + or when sem_open is unavailable. + """ + return map(func, iterable) + + +def _map_multiprocess(func, iterable, chunksize=1): + # type: (Callable[[S], T], Iterable[S], int) -> Iterator[T] + """Chop iterable into chunks and submit them to a process pool. + + For very long iterables using a large value for chunksize can make + the job complete much faster than using the default value of 1. + + Return an unordered iterator of the results. + """ + with closing(ProcessPool()) as pool: + return pool.imap_unordered(func, iterable, chunksize) + + +def _map_multithread(func, iterable, chunksize=1): + # type: (Callable[[S], T], Iterable[S], int) -> Iterator[T] + """Chop iterable into chunks and submit them to a thread pool. + + For very long iterables using a large value for chunksize can make + the job complete much faster than using the default value of 1. + + Return an unordered iterator of the results. + """ + with closing(ThreadPool(DEFAULT_POOLSIZE)) as pool: + return pool.imap_unordered(func, iterable, chunksize) + + +if LACK_SEM_OPEN: + map_multiprocess = map_multithread = _map_fallback +else: + map_multiprocess = _map_multiprocess + map_multithread = _map_multithread diff --git a/pipenv/patched/notpip/_internal/utils/pkg_resources.py b/pipenv/patched/notpip/_internal/utils/pkg_resources.py new file mode 100644 index 00000000..3d60b618 --- /dev/null +++ b/pipenv/patched/notpip/_internal/utils/pkg_resources.py @@ -0,0 +1,40 @@ +from typing import Dict, Iterable, List + +from pipenv.patched.notpip._vendor.pkg_resources import yield_lines + + +class DictMetadata: + """IMetadataProvider that reads metadata files from a dictionary.""" + + def __init__(self, metadata): + # type: (Dict[str, bytes]) -> None + self._metadata = metadata + + def has_metadata(self, name): + # type: (str) -> bool + return name in self._metadata + + def get_metadata(self, name): + # type: (str) -> str + try: + return self._metadata[name].decode() + except UnicodeDecodeError as e: + # Mirrors handling done in pkg_resources.NullProvider. + e.reason += f" in {name} file" + raise + + def get_metadata_lines(self, name): + # type: (str) -> Iterable[str] + return yield_lines(self.get_metadata(name)) + + def metadata_isdir(self, name): + # type: (str) -> bool + return False + + def metadata_listdir(self, name): + # type: (str) -> List[str] + return [] + + def run_script(self, script_name, namespace): + # type: (str, str) -> None + pass diff --git a/pipenv/patched/notpip/_internal/utils/setuptools_build.py b/pipenv/patched/notpip/_internal/utils/setuptools_build.py index 03973e97..4b8e4b35 100644 --- a/pipenv/patched/notpip/_internal/utils/setuptools_build.py +++ b/pipenv/patched/notpip/_internal/utils/setuptools_build.py @@ -1,8 +1,173 @@ +import sys +from typing import List, Optional, Sequence + # Shim to wrap setup.py invocation with setuptools -SETUPTOOLS_SHIM = ( - "import setuptools, tokenize;__file__=%r;" - "f=getattr(tokenize, 'open', open)(__file__);" - "code=f.read().replace('\\r\\n', '\\n');" +# +# We set sys.argv[0] to the path to the underlying setup.py file so +# setuptools / distutils don't take the path to the setup.py to be "-c" when +# invoking via the shim. This avoids e.g. the following manifest_maker +# warning: "warning: manifest_maker: standard file '-c' not found". +_SETUPTOOLS_SHIM = ( + "import io, os, sys, setuptools, tokenize; sys.argv[0] = {0!r}; __file__={0!r};" + "f = getattr(tokenize, 'open', open)(__file__) " + "if os.path.exists(__file__) " + "else io.StringIO('from setuptools import setup; setup()');" + "code = f.read().replace('\\r\\n', '\\n');" "f.close();" "exec(compile(code, __file__, 'exec'))" ) + + +def make_setuptools_shim_args( + setup_py_path, # type: str + global_options=None, # type: Sequence[str] + no_user_config=False, # type: bool + unbuffered_output=False, # type: bool +): + # type: (...) -> List[str] + """ + Get setuptools command arguments with shim wrapped setup file invocation. + + :param setup_py_path: The path to setup.py to be wrapped. + :param global_options: Additional global options. + :param no_user_config: If True, disables personal user configuration. + :param unbuffered_output: If True, adds the unbuffered switch to the + argument list. + """ + args = [sys.executable] + if unbuffered_output: + args += ["-u"] + args += ["-c", _SETUPTOOLS_SHIM.format(setup_py_path)] + if global_options: + args += global_options + if no_user_config: + args += ["--no-user-cfg"] + return args + + +def make_setuptools_bdist_wheel_args( + setup_py_path, # type: str + global_options, # type: Sequence[str] + build_options, # type: Sequence[str] + destination_dir, # type: str +): + # type: (...) -> List[str] + # NOTE: Eventually, we'd want to also -S to the flags here, when we're + # isolating. Currently, it breaks Python in virtualenvs, because it + # relies on site.py to find parts of the standard library outside the + # virtualenv. + args = make_setuptools_shim_args( + setup_py_path, global_options=global_options, unbuffered_output=True + ) + args += ["bdist_wheel", "-d", destination_dir] + args += build_options + return args + + +def make_setuptools_clean_args( + setup_py_path, # type: str + global_options, # type: Sequence[str] +): + # type: (...) -> List[str] + args = make_setuptools_shim_args( + setup_py_path, global_options=global_options, unbuffered_output=True + ) + args += ["clean", "--all"] + return args + + +def make_setuptools_develop_args( + setup_py_path, # type: str + global_options, # type: Sequence[str] + install_options, # type: Sequence[str] + no_user_config, # type: bool + prefix, # type: Optional[str] + home, # type: Optional[str] + use_user_site, # type: bool +): + # type: (...) -> List[str] + assert not (use_user_site and prefix) + + args = make_setuptools_shim_args( + setup_py_path, + global_options=global_options, + no_user_config=no_user_config, + ) + + args += ["develop", "--no-deps"] + + args += install_options + + if prefix: + args += ["--prefix", prefix] + if home is not None: + args += ["--install-dir", home] + + if use_user_site: + args += ["--user", "--prefix="] + + return args + + +def make_setuptools_egg_info_args( + setup_py_path, # type: str + egg_info_dir, # type: Optional[str] + no_user_config, # type: bool +): + # type: (...) -> List[str] + args = make_setuptools_shim_args(setup_py_path, no_user_config=no_user_config) + + args += ["egg_info"] + + if egg_info_dir: + args += ["--egg-base", egg_info_dir] + + return args + + +def make_setuptools_install_args( + setup_py_path, # type: str + global_options, # type: Sequence[str] + install_options, # type: Sequence[str] + record_filename, # type: str + root, # type: Optional[str] + prefix, # type: Optional[str] + header_dir, # type: Optional[str] + home, # type: Optional[str] + use_user_site, # type: bool + no_user_config, # type: bool + pycompile, # type: bool +): + # type: (...) -> List[str] + assert not (use_user_site and prefix) + assert not (use_user_site and root) + + args = make_setuptools_shim_args( + setup_py_path, + global_options=global_options, + no_user_config=no_user_config, + unbuffered_output=True, + ) + args += ["install", "--record", record_filename] + args += ["--single-version-externally-managed"] + + if root is not None: + args += ["--root", root] + if prefix is not None: + args += ["--prefix", prefix] + if home is not None: + args += ["--home", home] + if use_user_site: + args += ["--user", "--prefix="] + + if pycompile: + args += ["--compile"] + else: + args += ["--no-compile"] + + if header_dir: + args += ["--install-headers", header_dir] + + args += install_options + + return args diff --git a/pipenv/patched/notpip/_internal/utils/subprocess.py b/pipenv/patched/notpip/_internal/utils/subprocess.py new file mode 100644 index 00000000..c23aee06 --- /dev/null +++ b/pipenv/patched/notpip/_internal/utils/subprocess.py @@ -0,0 +1,281 @@ +import logging +import os +import shlex +import subprocess +from typing import Any, Callable, Iterable, List, Mapping, Optional, Union + +from pipenv.patched.notpip._internal.cli.spinners import SpinnerInterface, open_spinner +from pipenv.patched.notpip._internal.exceptions import InstallationSubprocessError +from pipenv.patched.notpip._internal.utils.logging import VERBOSE, subprocess_logger +from pipenv.patched.notpip._internal.utils.misc import HiddenText + +CommandArgs = List[Union[str, HiddenText]] + + +LOG_DIVIDER = "----------------------------------------" + + +def make_command(*args): + # type: (Union[str, HiddenText, CommandArgs]) -> CommandArgs + """ + Create a CommandArgs object. + """ + command_args = [] # type: CommandArgs + for arg in args: + # Check for list instead of CommandArgs since CommandArgs is + # only known during type-checking. + if isinstance(arg, list): + command_args.extend(arg) + else: + # Otherwise, arg is str or HiddenText. + command_args.append(arg) + + return command_args + + +def format_command_args(args): + # type: (Union[List[str], CommandArgs]) -> str + """ + Format command arguments for display. + """ + # For HiddenText arguments, display the redacted form by calling str(). + # Also, we don't apply str() to arguments that aren't HiddenText since + # this can trigger a UnicodeDecodeError in Python 2 if the argument + # has type unicode and includes a non-ascii character. (The type + # checker doesn't ensure the annotations are correct in all cases.) + return " ".join( + shlex.quote(str(arg)) if isinstance(arg, HiddenText) else shlex.quote(arg) + for arg in args + ) + + +def reveal_command_args(args): + # type: (Union[List[str], CommandArgs]) -> List[str] + """ + Return the arguments in their raw, unredacted form. + """ + return [arg.secret if isinstance(arg, HiddenText) else arg for arg in args] + + +def make_subprocess_output_error( + cmd_args, # type: Union[List[str], CommandArgs] + cwd, # type: Optional[str] + lines, # type: List[str] + exit_status, # type: int +): + # type: (...) -> str + """ + Create and return the error message to use to log a subprocess error + with command output. + + :param lines: A list of lines, each ending with a newline. + """ + command = format_command_args(cmd_args) + + # We know the joined output value ends in a newline. + output = "".join(lines) + msg = ( + # Use a unicode string to avoid "UnicodeEncodeError: 'ascii' + # codec can't encode character ..." in Python 2 when a format + # argument (e.g. `output`) has a non-ascii character. + "Command errored out with exit status {exit_status}:\n" + " command: {command_display}\n" + " cwd: {cwd_display}\n" + "Complete output ({line_count} lines):\n{output}{divider}" + ).format( + exit_status=exit_status, + command_display=command, + cwd_display=cwd, + line_count=len(lines), + output=output, + divider=LOG_DIVIDER, + ) + return msg + + +def call_subprocess( + cmd, # type: Union[List[str], CommandArgs] + show_stdout=False, # type: bool + cwd=None, # type: Optional[str] + on_returncode="raise", # type: str + extra_ok_returncodes=None, # type: Optional[Iterable[int]] + command_desc=None, # type: Optional[str] + extra_environ=None, # type: Optional[Mapping[str, Any]] + unset_environ=None, # type: Optional[Iterable[str]] + spinner=None, # type: Optional[SpinnerInterface] + log_failed_cmd=True, # type: Optional[bool] + stdout_only=False, # type: Optional[bool] +): + # type: (...) -> str + """ + Args: + show_stdout: if true, use INFO to log the subprocess's stderr and + stdout streams. Otherwise, use DEBUG. Defaults to False. + extra_ok_returncodes: an iterable of integer return codes that are + acceptable, in addition to 0. Defaults to None, which means []. + unset_environ: an iterable of environment variable names to unset + prior to calling subprocess.Popen(). + log_failed_cmd: if false, failed commands are not logged, only raised. + stdout_only: if true, return only stdout, else return both. When true, + logging of both stdout and stderr occurs when the subprocess has + terminated, else logging occurs as subprocess output is produced. + """ + if extra_ok_returncodes is None: + extra_ok_returncodes = [] + if unset_environ is None: + unset_environ = [] + # Most places in pip use show_stdout=False. What this means is-- + # + # - We connect the child's output (combined stderr and stdout) to a + # single pipe, which we read. + # - We log this output to stderr at DEBUG level as it is received. + # - If DEBUG logging isn't enabled (e.g. if --verbose logging wasn't + # requested), then we show a spinner so the user can still see the + # subprocess is in progress. + # - If the subprocess exits with an error, we log the output to stderr + # at ERROR level if it hasn't already been displayed to the console + # (e.g. if --verbose logging wasn't enabled). This way we don't log + # the output to the console twice. + # + # If show_stdout=True, then the above is still done, but with DEBUG + # replaced by INFO. + if show_stdout: + # Then log the subprocess output at INFO level. + log_subprocess = subprocess_logger.info + used_level = logging.INFO + else: + # Then log the subprocess output using VERBOSE. This also ensures + # it will be logged to the log file (aka user_log), if enabled. + log_subprocess = subprocess_logger.verbose + used_level = VERBOSE + + # Whether the subprocess will be visible in the console. + showing_subprocess = subprocess_logger.getEffectiveLevel() <= used_level + + # Only use the spinner if we're not showing the subprocess output + # and we have a spinner. + use_spinner = not showing_subprocess and spinner is not None + + if command_desc is None: + command_desc = format_command_args(cmd) + + log_subprocess("Running command %s", command_desc) + env = os.environ.copy() + if extra_environ: + env.update(extra_environ) + for name in unset_environ: + env.pop(name, None) + try: + proc = subprocess.Popen( + # Convert HiddenText objects to the underlying str. + reveal_command_args(cmd), + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT if not stdout_only else subprocess.PIPE, + cwd=cwd, + env=env, + errors="backslashreplace", + ) + except Exception as exc: + if log_failed_cmd: + subprocess_logger.critical( + "Error %s while executing command %s", + exc, + command_desc, + ) + raise + all_output = [] + if not stdout_only: + assert proc.stdout + assert proc.stdin + proc.stdin.close() + # In this mode, stdout and stderr are in the same pipe. + while True: + line = proc.stdout.readline() # type: str + if not line: + break + line = line.rstrip() + all_output.append(line + "\n") + + # Show the line immediately. + log_subprocess(line) + # Update the spinner. + if use_spinner: + assert spinner + spinner.spin() + try: + proc.wait() + finally: + if proc.stdout: + proc.stdout.close() + output = "".join(all_output) + else: + # In this mode, stdout and stderr are in different pipes. + # We must use communicate() which is the only safe way to read both. + out, err = proc.communicate() + # log line by line to preserve pip log indenting + for out_line in out.splitlines(): + log_subprocess(out_line) + all_output.append(out) + for err_line in err.splitlines(): + log_subprocess(err_line) + all_output.append(err) + output = out + + proc_had_error = proc.returncode and proc.returncode not in extra_ok_returncodes + if use_spinner: + assert spinner + if proc_had_error: + spinner.finish("error") + else: + spinner.finish("done") + if proc_had_error: + if on_returncode == "raise": + if not showing_subprocess and log_failed_cmd: + # Then the subprocess streams haven't been logged to the + # console yet. + msg = make_subprocess_output_error( + cmd_args=cmd, + cwd=cwd, + lines=all_output, + exit_status=proc.returncode, + ) + subprocess_logger.error(msg) + raise InstallationSubprocessError(proc.returncode, command_desc) + elif on_returncode == "warn": + subprocess_logger.warning( + 'Command "%s" had error code %s in %s', + command_desc, + proc.returncode, + cwd, + ) + elif on_returncode == "ignore": + pass + else: + raise ValueError(f"Invalid value: on_returncode={on_returncode!r}") + return output + + +def runner_with_spinner_message(message): + # type: (str) -> Callable[..., None] + """Provide a subprocess_runner that shows a spinner message. + + Intended for use with for pep517's Pep517HookCaller. Thus, the runner has + an API that matches what's expected by Pep517HookCaller.subprocess_runner. + """ + + def runner( + cmd, # type: List[str] + cwd=None, # type: Optional[str] + extra_environ=None, # type: Optional[Mapping[str, Any]] + ): + # type: (...) -> None + with open_spinner(message) as spinner: + call_subprocess( + cmd, + cwd=cwd, + extra_environ=extra_environ, + spinner=spinner, + ) + + return runner diff --git a/pipenv/patched/notpip/_internal/utils/temp_dir.py b/pipenv/patched/notpip/_internal/utils/temp_dir.py index d8121a59..1bc0c14e 100644 --- a/pipenv/patched/notpip/_internal/utils/temp_dir.py +++ b/pipenv/patched/notpip/_internal/utils/temp_dir.py @@ -1,19 +1,90 @@ -from __future__ import absolute_import - import errno import itertools import logging import os.path import tempfile -import warnings +from contextlib import ExitStack, contextmanager +from typing import Any, Dict, Iterator, Optional, TypeVar, Union -from pipenv.patched.notpip._internal.utils.misc import rmtree -from pipenv.vendor.vistir.compat import finalize, ResourceWarning +from pipenv.patched.notpip._internal.utils.misc import enum, rmtree logger = logging.getLogger(__name__) +_T = TypeVar("_T", bound="TempDirectory") -class TempDirectory(object): + +# Kinds of temporary directories. Only needed for ones that are +# globally-managed. +tempdir_kinds = enum( + BUILD_ENV="build-env", + EPHEM_WHEEL_CACHE="ephem-wheel-cache", + REQ_BUILD="req-build", +) + + +_tempdir_manager = None # type: Optional[ExitStack] + + +@contextmanager +def global_tempdir_manager(): + # type: () -> Iterator[None] + global _tempdir_manager + with ExitStack() as stack: + old_tempdir_manager, _tempdir_manager = _tempdir_manager, stack + try: + yield + finally: + _tempdir_manager = old_tempdir_manager + + +class TempDirectoryTypeRegistry: + """Manages temp directory behavior""" + + def __init__(self): + # type: () -> None + self._should_delete = {} # type: Dict[str, bool] + + def set_delete(self, kind, value): + # type: (str, bool) -> None + """Indicate whether a TempDirectory of the given kind should be + auto-deleted. + """ + self._should_delete[kind] = value + + def get_delete(self, kind): + # type: (str) -> bool + """Get configured auto-delete flag for a given TempDirectory type, + default True. + """ + return self._should_delete.get(kind, True) + + +_tempdir_registry = None # type: Optional[TempDirectoryTypeRegistry] + + +@contextmanager +def tempdir_registry(): + # type: () -> Iterator[TempDirectoryTypeRegistry] + """Provides a scoped global tempdir registry that can be used to dictate + whether directories should be deleted. + """ + global _tempdir_registry + old_tempdir_registry = _tempdir_registry + _tempdir_registry = TempDirectoryTypeRegistry() + try: + yield _tempdir_registry + finally: + _tempdir_registry = old_tempdir_registry + + +class _Default: + pass + + +_default = _Default() + + +class TempDirectory: """Helper class that owns and cleans up a temporary directory. This class can be used as a context manager or as an OO representation of a @@ -21,99 +92,96 @@ class TempDirectory(object): Attributes: path - Location to the created temporary directory or None + Location to the created temporary directory delete Whether the directory should be deleted when exiting (when used as a contextmanager) Methods: - create() - Creates a temporary directory and stores its path in the path - attribute. cleanup() - Deletes the temporary directory and sets path attribute to None + Deletes the temporary directory - When used as a context manager, a temporary directory is created on - entering the context and, if the delete attribute is True, on exiting the - context the created directory is deleted. + When used as a context manager, if the delete attribute is True, on + exiting the context the temporary directory is deleted. """ - def __init__(self, path=None, delete=None, kind="temp"): - super(TempDirectory, self).__init__() + def __init__( + self, + path=None, # type: Optional[str] + delete=_default, # type: Union[bool, None, _Default] + kind="temp", # type: str + globally_managed=False, # type: bool + ): + super().__init__() - if path is None and delete is None: - # If we were not given an explicit directory, and we were not given - # an explicit delete option, then we'll default to deleting. - delete = True + if delete is _default: + if path is not None: + # If we were given an explicit directory, resolve delete option + # now. + delete = False + else: + # Otherwise, we wait until cleanup and see what + # tempdir_registry says. + delete = None - self.path = path + # The only time we specify path is in for editables where it + # is the value of the --src option. + if path is None: + path = self._create(kind) + + self._path = path + self._deleted = False self.delete = delete self.kind = kind - self._finalizer = None - if path: - self._register_finalizer() - def _register_finalizer(self): - if self.delete and self.path: - self._finalizer = finalize( - self, - self._cleanup, - self.path, - warn_message = None - ) - else: - self._finalizer = None + if globally_managed: + assert _tempdir_manager is not None + _tempdir_manager.enter_context(self) + + @property + def path(self): + # type: () -> str + assert not self._deleted, f"Attempted to access deleted path: {self._path}" + return self._path def __repr__(self): - return "<{} {!r}>".format(self.__class__.__name__, self.path) + # type: () -> str + return f"<{self.__class__.__name__} {self.path!r}>" def __enter__(self): - self.create() + # type: (_T) -> _T return self def __exit__(self, exc, value, tb): - if self.delete: + # type: (Any, Any, Any) -> None + if self.delete is not None: + delete = self.delete + elif _tempdir_registry: + delete = _tempdir_registry.get_delete(self.kind) + else: + delete = True + + if delete: self.cleanup() - def create(self): - """Create a temporary directory and store its path in self.path - """ - if self.path is not None: - logger.debug( - "Skipped creation of temporary directory: {}".format(self.path) - ) - return + def _create(self, kind): + # type: (str) -> str + """Create a temporary directory and store its path in self.path""" # We realpath here because some systems have their default tmpdir # symlinked to another directory. This tends to confuse build # scripts, so we canonicalize the path by traversing potential # symlinks here. - self.path = os.path.realpath( - tempfile.mkdtemp(prefix="pip-{}-".format(self.kind)) - ) - self._register_finalizer() - logger.debug("Created temporary directory: {}".format(self.path)) - - @classmethod - def _cleanup(cls, name, warn_message=None): - try: - rmtree(name) - except OSError: - pass - else: - if warn_message: - warnings.warn(warn_message, ResourceWarning) + path = os.path.realpath(tempfile.mkdtemp(prefix=f"pip-{kind}-")) + logger.debug("Created temporary directory: %s", path) + return path def cleanup(self): - """Remove the temporary directory created and reset state - """ - if getattr(self._finalizer, "detach", None) and self._finalizer.detach(): - if os.path.exists(self.path): - try: - rmtree(self.path) - except OSError: - pass - else: - self.path = None + # type: () -> None + """Remove the temporary directory created and reset state""" + self._deleted = True + if not os.path.exists(self._path): + return + rmtree(self._path) class AdjacentTempDirectory(TempDirectory): @@ -130,6 +198,7 @@ class AdjacentTempDirectory(TempDirectory): (when used as a contextmanager) """ + # The characters that may be used to name the temp directory # We always prepend a ~ and then rotate through these until # a usable name is found. @@ -138,11 +207,13 @@ class AdjacentTempDirectory(TempDirectory): LEADING_CHARS = "-~.=%0123456789" def __init__(self, original, delete=None): - super(AdjacentTempDirectory, self).__init__(delete=delete) - self.original = original.rstrip('/\\') + # type: (str, Optional[bool]) -> None + self.original = original.rstrip("/\\") + super().__init__(delete=delete) @classmethod def _generate_names(cls, name): + # type: (str) -> Iterator[str] """Generates a series of temporary names. The algorithm replaces the leading characters in the name @@ -152,20 +223,23 @@ class AdjacentTempDirectory(TempDirectory): """ for i in range(1, len(name)): for candidate in itertools.combinations_with_replacement( - cls.LEADING_CHARS, i - 1): - new_name = '~' + ''.join(candidate) + name[i:] + cls.LEADING_CHARS, i - 1 + ): + new_name = "~" + "".join(candidate) + name[i:] if new_name != name: yield new_name # If we make it this far, we will have to make a longer name for i in range(len(cls.LEADING_CHARS)): for candidate in itertools.combinations_with_replacement( - cls.LEADING_CHARS, i): - new_name = '~' + ''.join(candidate) + name + cls.LEADING_CHARS, i + ): + new_name = "~" + "".join(candidate) + name if new_name != name: yield new_name - def create(self): + def _create(self, kind): + # type: (str) -> str root, name = os.path.split(self.original) for candidate in self._generate_names(name): path = os.path.join(root, candidate) @@ -176,13 +250,11 @@ class AdjacentTempDirectory(TempDirectory): if ex.errno != errno.EEXIST: raise else: - self.path = os.path.realpath(path) + path = os.path.realpath(path) break - - if not self.path: + else: # Final fallback on the default behavior. - self.path = os.path.realpath( - tempfile.mkdtemp(prefix="pip-{}-".format(self.kind)) - ) - self._register_finalizer() - logger.debug("Created temporary directory: {}".format(self.path)) + path = os.path.realpath(tempfile.mkdtemp(prefix=f"pip-{kind}-")) + + logger.debug("Created temporary directory: %s", path) + return path diff --git a/pipenv/patched/notpip/_internal/utils/typing.py b/pipenv/patched/notpip/_internal/utils/typing.py deleted file mode 100644 index 56f2fa87..00000000 --- a/pipenv/patched/notpip/_internal/utils/typing.py +++ /dev/null @@ -1,29 +0,0 @@ -"""For neatly implementing static typing in pip. - -`mypy` - the static type analysis tool we use - uses the `typing` module, which -provides core functionality fundamental to mypy's functioning. - -Generally, `typing` would be imported at runtime and used in that fashion - -it acts as a no-op at runtime and does not have any run-time overhead by -design. - -As it turns out, `typing` is not vendorable - it uses separate sources for -Python 2/Python 3. Thus, this codebase can not expect it to be present. -To work around this, mypy allows the typing import to be behind a False-y -optional to prevent it from running at runtime and type-comments can be used -to remove the need for the types to be accessible directly during runtime. - -This module provides the False-y guard in a nicely named fashion so that a -curious maintainer can reach here to read this. - -In pip, all static-typing related imports should be guarded as follows: - - from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING - - if MYPY_CHECK_RUNNING: - from typing import ... # noqa: F401 - -Ref: https://github.com/python/mypy/issues/3216 -""" - -MYPY_CHECK_RUNNING = False diff --git a/pipenv/patched/notpip/_internal/utils/ui.py b/pipenv/patched/notpip/_internal/utils/ui.py deleted file mode 100644 index 18119e0e..00000000 --- a/pipenv/patched/notpip/_internal/utils/ui.py +++ /dev/null @@ -1,441 +0,0 @@ -from __future__ import absolute_import, division - -import contextlib -import itertools -import logging -import sys -import time -from signal import SIGINT, default_int_handler, signal - -from pipenv.patched.notpip._vendor import six -from pipenv.patched.notpip._vendor.progress.bar import ( - Bar, ChargingBar, FillingCirclesBar, FillingSquaresBar, IncrementalBar, - ShadyBar, -) -from pipenv.patched.notpip._vendor.progress.helpers import HIDE_CURSOR, SHOW_CURSOR, WritelnMixin -from pipenv.patched.notpip._vendor.progress.spinner import Spinner - -from pipenv.patched.notpip._internal.utils.compat import WINDOWS -from pipenv.patched.notpip._internal.utils.logging import get_indentation -from pipenv.patched.notpip._internal.utils.misc import format_size -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING - -if MYPY_CHECK_RUNNING: - from typing import Any, Iterator, IO # noqa: F401 - -try: - from pipenv.patched.notpip._vendor import colorama -# Lots of different errors can come from this, including SystemError and -# ImportError. -except Exception: - colorama = None - -logger = logging.getLogger(__name__) - - -def _select_progress_class(preferred, fallback): - encoding = getattr(preferred.file, "encoding", None) - - # If we don't know what encoding this file is in, then we'll just assume - # that it doesn't support unicode and use the ASCII bar. - if not encoding: - return fallback - - # Collect all of the possible characters we want to use with the preferred - # bar. - characters = [ - getattr(preferred, "empty_fill", six.text_type()), - getattr(preferred, "fill", six.text_type()), - ] - characters += list(getattr(preferred, "phases", [])) - - # Try to decode the characters we're using for the bar using the encoding - # of the given file, if this works then we'll assume that we can use the - # fancier bar and if not we'll fall back to the plaintext bar. - try: - six.text_type().join(characters).encode(encoding) - except UnicodeEncodeError: - return fallback - else: - return preferred - - -_BaseBar = _select_progress_class(IncrementalBar, Bar) # type: Any - - -class InterruptibleMixin(object): - """ - Helper to ensure that self.finish() gets called on keyboard interrupt. - - This allows downloads to be interrupted without leaving temporary state - (like hidden cursors) behind. - - This class is similar to the progress library's existing SigIntMixin - helper, but as of version 1.2, that helper has the following problems: - - 1. It calls sys.exit(). - 2. It discards the existing SIGINT handler completely. - 3. It leaves its own handler in place even after an uninterrupted finish, - which will have unexpected delayed effects if the user triggers an - unrelated keyboard interrupt some time after a progress-displaying - download has already completed, for example. - """ - - def __init__(self, *args, **kwargs): - """ - Save the original SIGINT handler for later. - """ - super(InterruptibleMixin, self).__init__(*args, **kwargs) - - self.original_handler = signal(SIGINT, self.handle_sigint) - - # If signal() returns None, the previous handler was not installed from - # Python, and we cannot restore it. This probably should not happen, - # but if it does, we must restore something sensible instead, at least. - # The least bad option should be Python's default SIGINT handler, which - # just raises KeyboardInterrupt. - if self.original_handler is None: - self.original_handler = default_int_handler - - def finish(self): - """ - Restore the original SIGINT handler after finishing. - - This should happen regardless of whether the progress display finishes - normally, or gets interrupted. - """ - super(InterruptibleMixin, self).finish() - signal(SIGINT, self.original_handler) - - def handle_sigint(self, signum, frame): - """ - Call self.finish() before delegating to the original SIGINT handler. - - This handler should only be in place while the progress display is - active. - """ - self.finish() - self.original_handler(signum, frame) - - -class SilentBar(Bar): - - def update(self): - pass - - -class BlueEmojiBar(IncrementalBar): - - suffix = "%(percent)d%%" - bar_prefix = " " - bar_suffix = " " - phases = (u"\U0001F539", u"\U0001F537", u"\U0001F535") # type: Any - - -class DownloadProgressMixin(object): - - def __init__(self, *args, **kwargs): - super(DownloadProgressMixin, self).__init__(*args, **kwargs) - self.message = (" " * (get_indentation() + 2)) + self.message - - @property - def downloaded(self): - return format_size(self.index) - - @property - def download_speed(self): - # Avoid zero division errors... - if self.avg == 0.0: - return "..." - return format_size(1 / self.avg) + "/s" - - @property - def pretty_eta(self): - if self.eta: - return "eta %s" % self.eta_td - return "" - - def iter(self, it, n=1): - for x in it: - yield x - self.next(n) - self.finish() - - -class WindowsMixin(object): - - def __init__(self, *args, **kwargs): - # The Windows terminal does not support the hide/show cursor ANSI codes - # even with colorama. So we'll ensure that hide_cursor is False on - # Windows. - # This call neds to go before the super() call, so that hide_cursor - # is set in time. The base progress bar class writes the "hide cursor" - # code to the terminal in its init, so if we don't set this soon - # enough, we get a "hide" with no corresponding "show"... - if WINDOWS and self.hide_cursor: - self.hide_cursor = False - - super(WindowsMixin, self).__init__(*args, **kwargs) - - # Check if we are running on Windows and we have the colorama module, - # if we do then wrap our file with it. - if WINDOWS and colorama: - self.file = colorama.AnsiToWin32(self.file) - # The progress code expects to be able to call self.file.isatty() - # but the colorama.AnsiToWin32() object doesn't have that, so we'll - # add it. - self.file.isatty = lambda: self.file.wrapped.isatty() - # The progress code expects to be able to call self.file.flush() - # but the colorama.AnsiToWin32() object doesn't have that, so we'll - # add it. - self.file.flush = lambda: self.file.wrapped.flush() - - -class BaseDownloadProgressBar(WindowsMixin, InterruptibleMixin, - DownloadProgressMixin): - - file = sys.stdout - message = "%(percent)d%%" - suffix = "%(downloaded)s %(download_speed)s %(pretty_eta)s" - -# NOTE: The "type: ignore" comments on the following classes are there to -# work around https://github.com/python/typing/issues/241 - - -class DefaultDownloadProgressBar(BaseDownloadProgressBar, - _BaseBar): - pass - - -class DownloadSilentBar(BaseDownloadProgressBar, SilentBar): # type: ignore - pass - - -class DownloadIncrementalBar(BaseDownloadProgressBar, # type: ignore - IncrementalBar): - pass - - -class DownloadChargingBar(BaseDownloadProgressBar, # type: ignore - ChargingBar): - pass - - -class DownloadShadyBar(BaseDownloadProgressBar, ShadyBar): # type: ignore - pass - - -class DownloadFillingSquaresBar(BaseDownloadProgressBar, # type: ignore - FillingSquaresBar): - pass - - -class DownloadFillingCirclesBar(BaseDownloadProgressBar, # type: ignore - FillingCirclesBar): - pass - - -class DownloadBlueEmojiProgressBar(BaseDownloadProgressBar, # type: ignore - BlueEmojiBar): - pass - - -class DownloadProgressSpinner(WindowsMixin, InterruptibleMixin, - DownloadProgressMixin, WritelnMixin, Spinner): - - file = sys.stdout - suffix = "%(downloaded)s %(download_speed)s" - - def next_phase(self): - if not hasattr(self, "_phaser"): - self._phaser = itertools.cycle(self.phases) - return next(self._phaser) - - def update(self): - message = self.message % self - phase = self.next_phase() - suffix = self.suffix % self - line = ''.join([ - message, - " " if message else "", - phase, - " " if suffix else "", - suffix, - ]) - - self.writeln(line) - - -BAR_TYPES = { - "off": (DownloadSilentBar, DownloadSilentBar), - "on": (DefaultDownloadProgressBar, DownloadProgressSpinner), - "ascii": (DownloadIncrementalBar, DownloadProgressSpinner), - "pretty": (DownloadFillingCirclesBar, DownloadProgressSpinner), - "emoji": (DownloadBlueEmojiProgressBar, DownloadProgressSpinner) -} - - -def DownloadProgressProvider(progress_bar, max=None): - if max is None or max == 0: - return BAR_TYPES[progress_bar][1]().iter - else: - return BAR_TYPES[progress_bar][0](max=max).iter - - -################################################################ -# Generic "something is happening" spinners -# -# We don't even try using progress.spinner.Spinner here because it's actually -# simpler to reimplement from scratch than to coerce their code into doing -# what we need. -################################################################ - -@contextlib.contextmanager -def hidden_cursor(file): - # type: (IO) -> Iterator[None] - # The Windows terminal does not support the hide/show cursor ANSI codes, - # even via colorama. So don't even try. - if WINDOWS: - yield - # We don't want to clutter the output with control characters if we're - # writing to a file, or if the user is running with --quiet. - # See https://github.com/pypa/pip/issues/3418 - elif not file.isatty() or logger.getEffectiveLevel() > logging.INFO: - yield - else: - file.write(HIDE_CURSOR) - try: - yield - finally: - file.write(SHOW_CURSOR) - - -class RateLimiter(object): - def __init__(self, min_update_interval_seconds): - # type: (float) -> None - self._min_update_interval_seconds = min_update_interval_seconds - self._last_update = 0 # type: float - - def ready(self): - # type: () -> bool - now = time.time() - delta = now - self._last_update - return delta >= self._min_update_interval_seconds - - def reset(self): - # type: () -> None - self._last_update = time.time() - - -class SpinnerInterface(object): - def spin(self): - # type: () -> None - raise NotImplementedError() - - def finish(self, final_status): - # type: (str) -> None - raise NotImplementedError() - - -class InteractiveSpinner(SpinnerInterface): - def __init__(self, message, file=None, spin_chars="-\\|/", - # Empirically, 8 updates/second looks nice - min_update_interval_seconds=0.125): - self._message = message - if file is None: - file = sys.stdout - self._file = file - self._rate_limiter = RateLimiter(min_update_interval_seconds) - self._finished = False - - self._spin_cycle = itertools.cycle(spin_chars) - - self._file.write(" " * get_indentation() + self._message + " ... ") - self._width = 0 - - def _write(self, status): - assert not self._finished - # Erase what we wrote before by backspacing to the beginning, writing - # spaces to overwrite the old text, and then backspacing again - backup = "\b" * self._width - self._file.write(backup + " " * self._width + backup) - # Now we have a blank slate to add our status - self._file.write(status) - self._width = len(status) - self._file.flush() - self._rate_limiter.reset() - - def spin(self): - # type: () -> None - if self._finished: - return - if not self._rate_limiter.ready(): - return - self._write(next(self._spin_cycle)) - - def finish(self, final_status): - # type: (str) -> None - if self._finished: - return - self._write(final_status) - self._file.write("\n") - self._file.flush() - self._finished = True - - -# Used for dumb terminals, non-interactive installs (no tty), etc. -# We still print updates occasionally (once every 60 seconds by default) to -# act as a keep-alive for systems like Travis-CI that take lack-of-output as -# an indication that a task has frozen. -class NonInteractiveSpinner(SpinnerInterface): - def __init__(self, message, min_update_interval_seconds=60): - # type: (str, float) -> None - self._message = message - self._finished = False - self._rate_limiter = RateLimiter(min_update_interval_seconds) - self._update("started") - - def _update(self, status): - assert not self._finished - self._rate_limiter.reset() - logger.info("%s: %s", self._message, status) - - def spin(self): - # type: () -> None - if self._finished: - return - if not self._rate_limiter.ready(): - return - self._update("still running...") - - def finish(self, final_status): - # type: (str) -> None - if self._finished: - return - self._update("finished with status '%s'" % (final_status,)) - self._finished = True - - -@contextlib.contextmanager -def open_spinner(message): - # type: (str) -> Iterator[SpinnerInterface] - # Interactive spinner goes directly to sys.stdout rather than being routed - # through the logging system, but it acts like it has level INFO, - # i.e. it's only displayed if we're at level INFO or better. - # Non-interactive spinner goes through the logging system, so it is always - # in sync with logging configuration. - if sys.stdout.isatty() and logger.getEffectiveLevel() <= logging.INFO: - spinner = InteractiveSpinner(message) # type: SpinnerInterface - else: - spinner = NonInteractiveSpinner(message) - try: - with hidden_cursor(sys.stdout): - yield spinner - except KeyboardInterrupt: - spinner.finish("canceled") - raise - except Exception: - spinner.finish("error") - raise - else: - spinner.finish("done") diff --git a/pipenv/patched/notpip/_internal/utils/unpacking.py b/pipenv/patched/notpip/_internal/utils/unpacking.py new file mode 100644 index 00000000..aea8900a --- /dev/null +++ b/pipenv/patched/notpip/_internal/utils/unpacking.py @@ -0,0 +1,267 @@ +"""Utilities related archives. +""" + +import logging +import os +import shutil +import stat +import tarfile +import zipfile +from typing import Iterable, List, Optional +from zipfile import ZipInfo + +from pipenv.patched.notpip._internal.exceptions import InstallationError +from pipenv.patched.notpip._internal.utils.filetypes import ( + BZ2_EXTENSIONS, + TAR_EXTENSIONS, + XZ_EXTENSIONS, + ZIP_EXTENSIONS, +) +from pipenv.patched.notpip._internal.utils.misc import ensure_dir + +logger = logging.getLogger(__name__) + + +SUPPORTED_EXTENSIONS = ZIP_EXTENSIONS + TAR_EXTENSIONS + +try: + import bz2 # noqa + + SUPPORTED_EXTENSIONS += BZ2_EXTENSIONS +except ImportError: + logger.debug("bz2 module is not available") + +try: + # Only for Python 3.3+ + import lzma # noqa + + SUPPORTED_EXTENSIONS += XZ_EXTENSIONS +except ImportError: + logger.debug("lzma module is not available") + + +def current_umask(): + # type: () -> int + """Get the current umask which involves having to set it temporarily.""" + mask = os.umask(0) + os.umask(mask) + return mask + + +def split_leading_dir(path): + # type: (str) -> List[str] + path = path.lstrip("/").lstrip("\\") + if "/" in path and ( + ("\\" in path and path.find("/") < path.find("\\")) or "\\" not in path + ): + return path.split("/", 1) + elif "\\" in path: + return path.split("\\", 1) + else: + return [path, ""] + + +def has_leading_dir(paths): + # type: (Iterable[str]) -> bool + """Returns true if all the paths have the same leading path name + (i.e., everything is in one subdirectory in an archive)""" + common_prefix = None + for path in paths: + prefix, rest = split_leading_dir(path) + if not prefix: + return False + elif common_prefix is None: + common_prefix = prefix + elif prefix != common_prefix: + return False + return True + + +def is_within_directory(directory, target): + # type: (str, str) -> bool + """ + Return true if the absolute path of target is within the directory + """ + abs_directory = os.path.abspath(directory) + abs_target = os.path.abspath(target) + + prefix = os.path.commonprefix([abs_directory, abs_target]) + return prefix == abs_directory + + +def set_extracted_file_to_default_mode_plus_executable(path): + # type: (str) -> None + """ + Make file present at path have execute for user/group/world + (chmod +x) is no-op on windows per python docs + """ + os.chmod(path, (0o777 & ~current_umask() | 0o111)) + + +def zip_item_is_executable(info): + # type: (ZipInfo) -> bool + mode = info.external_attr >> 16 + # if mode and regular file and any execute permissions for + # user/group/world? + return bool(mode and stat.S_ISREG(mode) and mode & 0o111) + + +def unzip_file(filename, location, flatten=True): + # type: (str, str, bool) -> None + """ + Unzip the file (with path `filename`) to the destination `location`. All + files are written based on system defaults and umask (i.e. permissions are + not preserved), except that regular file members with any execute + permissions (user, group, or world) have "chmod +x" applied after being + written. Note that for windows, any execute changes using os.chmod are + no-ops per the python docs. + """ + ensure_dir(location) + zipfp = open(filename, "rb") + try: + zip = zipfile.ZipFile(zipfp, allowZip64=True) + leading = has_leading_dir(zip.namelist()) and flatten + for info in zip.infolist(): + name = info.filename + fn = name + if leading: + fn = split_leading_dir(name)[1] + fn = os.path.join(location, fn) + dir = os.path.dirname(fn) + if not is_within_directory(location, fn): + message = ( + "The zip file ({}) has a file ({}) trying to install " + "outside target directory ({})" + ) + raise InstallationError(message.format(filename, fn, location)) + if fn.endswith("/") or fn.endswith("\\"): + # A directory + ensure_dir(fn) + else: + ensure_dir(dir) + # Don't use read() to avoid allocating an arbitrarily large + # chunk of memory for the file's content + fp = zip.open(name) + try: + with open(fn, "wb") as destfp: + shutil.copyfileobj(fp, destfp) + finally: + fp.close() + if zip_item_is_executable(info): + set_extracted_file_to_default_mode_plus_executable(fn) + finally: + zipfp.close() + + +def untar_file(filename, location): + # type: (str, str) -> None + """ + Untar the file (with path `filename`) to the destination `location`. + All files are written based on system defaults and umask (i.e. permissions + are not preserved), except that regular file members with any execute + permissions (user, group, or world) have "chmod +x" applied after being + written. Note that for windows, any execute changes using os.chmod are + no-ops per the python docs. + """ + ensure_dir(location) + if filename.lower().endswith(".gz") or filename.lower().endswith(".tgz"): + mode = "r:gz" + elif filename.lower().endswith(BZ2_EXTENSIONS): + mode = "r:bz2" + elif filename.lower().endswith(XZ_EXTENSIONS): + mode = "r:xz" + elif filename.lower().endswith(".tar"): + mode = "r" + else: + logger.warning( + "Cannot determine compression type for file %s", + filename, + ) + mode = "r:*" + tar = tarfile.open(filename, mode, encoding="utf-8") + try: + leading = has_leading_dir([member.name for member in tar.getmembers()]) + for member in tar.getmembers(): + fn = member.name + if leading: + fn = split_leading_dir(fn)[1] + path = os.path.join(location, fn) + if not is_within_directory(location, path): + message = ( + "The tar file ({}) has a file ({}) trying to install " + "outside target directory ({})" + ) + raise InstallationError(message.format(filename, path, location)) + if member.isdir(): + ensure_dir(path) + elif member.issym(): + try: + # https://github.com/python/typeshed/issues/2673 + tar._extract_member(member, path) # type: ignore + except Exception as exc: + # Some corrupt tar files seem to produce this + # (specifically bad symlinks) + logger.warning( + "In the tar file %s the member %s is invalid: %s", + filename, + member.name, + exc, + ) + continue + else: + try: + fp = tar.extractfile(member) + except (KeyError, AttributeError) as exc: + # Some corrupt tar files seem to produce this + # (specifically bad symlinks) + logger.warning( + "In the tar file %s the member %s is invalid: %s", + filename, + member.name, + exc, + ) + continue + ensure_dir(os.path.dirname(path)) + assert fp is not None + with open(path, "wb") as destfp: + shutil.copyfileobj(fp, destfp) + fp.close() + # Update the timestamp (useful for cython compiled files) + tar.utime(member, path) + # member have any execute permissions for user/group/world? + if member.mode & 0o111: + set_extracted_file_to_default_mode_plus_executable(path) + finally: + tar.close() + + +def unpack_file( + filename, # type: str + location, # type: str + content_type=None, # type: Optional[str] +): + # type: (...) -> None + filename = os.path.realpath(filename) + if ( + content_type == "application/zip" + or filename.lower().endswith(ZIP_EXTENSIONS) + or zipfile.is_zipfile(filename) + ): + unzip_file(filename, location, flatten=not filename.endswith(".whl")) + elif ( + content_type == "application/x-gzip" + or tarfile.is_tarfile(filename) + or filename.lower().endswith(TAR_EXTENSIONS + BZ2_EXTENSIONS + XZ_EXTENSIONS) + ): + untar_file(filename, location) + else: + # FIXME: handle? + # FIXME: magic signatures? + logger.critical( + "Cannot unpack file %s (downloaded from %s, content-type: %s); " + "cannot detect archive format", + filename, + location, + content_type, + ) + raise InstallationError(f"Cannot determine archive format of {location}") diff --git a/pipenv/patched/notpip/_internal/utils/urls.py b/pipenv/patched/notpip/_internal/utils/urls.py new file mode 100644 index 00000000..7b51052c --- /dev/null +++ b/pipenv/patched/notpip/_internal/utils/urls.py @@ -0,0 +1,65 @@ +import os +import string +import urllib.parse +import urllib.request +from typing import Optional + +from .compat import WINDOWS + + +def get_url_scheme(url): + # type: (str) -> Optional[str] + if ":" not in url: + return None + return url.split(":", 1)[0].lower() + + +def path_to_url(path): + # type: (str) -> str + """ + Convert a path to a file: URL. The path will be made absolute and have + quoted path parts. + """ + path = os.path.normpath(os.path.abspath(path)) + url = urllib.parse.urljoin("file:", urllib.request.pathname2url(path)) + return url + + +def url_to_path(url): + # type: (str) -> str + """ + Convert a file: URL to a path. + """ + assert url.startswith( + "file:" + ), f"You can only turn file: urls into filenames (not {url!r})" + + _, netloc, path, _, _ = urllib.parse.urlsplit(url) + + if not netloc or netloc == "localhost": + # According to RFC 8089, same as empty authority. + netloc = "" + elif WINDOWS: + # If we have a UNC path, prepend UNC share notation. + netloc = "\\\\" + netloc + else: + raise ValueError( + f"non-local file URIs are not supported on this platform: {url!r}" + ) + + path = urllib.request.url2pathname(netloc + path) + + # On Windows, urlsplit parses the path as something like "/C:/Users/foo". + # This creates issues for path-related functions like io.open(), so we try + # to detect and strip the leading slash. + if ( + WINDOWS + and not netloc # Not UNC. + and len(path) >= 3 + and path[0] == "/" # Leading slash to strip. + and path[1] in string.ascii_letters # Drive letter. + and path[2:4] in (":", ":/") # Colon + end of string, or colon + absolute path. + ): + path = path[1:] + + return path diff --git a/pipenv/patched/notpip/_internal/utils/virtualenv.py b/pipenv/patched/notpip/_internal/utils/virtualenv.py new file mode 100644 index 00000000..51cacb55 --- /dev/null +++ b/pipenv/patched/notpip/_internal/utils/virtualenv.py @@ -0,0 +1,111 @@ +import logging +import os +import re +import site +import sys +from typing import List, Optional + +logger = logging.getLogger(__name__) +_INCLUDE_SYSTEM_SITE_PACKAGES_REGEX = re.compile( + r"include-system-site-packages\s*=\s*(?P<value>true|false)" +) + + +def _running_under_venv(): + # type: () -> bool + """Checks if sys.base_prefix and sys.prefix match. + + This handles PEP 405 compliant virtual environments. + """ + return sys.prefix != getattr(sys, "base_prefix", sys.prefix) + + +def _running_under_regular_virtualenv(): + # type: () -> bool + """Checks if sys.real_prefix is set. + + This handles virtual environments created with pypa's virtualenv. + """ + # pypa/virtualenv case + return hasattr(sys, "real_prefix") + + +def running_under_virtualenv(): + # type: () -> bool + """Return True if we're running inside a virtualenv, False otherwise.""" + return _running_under_venv() or _running_under_regular_virtualenv() + + +def _get_pyvenv_cfg_lines(): + # type: () -> Optional[List[str]] + """Reads {sys.prefix}/pyvenv.cfg and returns its contents as list of lines + + Returns None, if it could not read/access the file. + """ + pyvenv_cfg_file = os.path.join(sys.prefix, "pyvenv.cfg") + try: + # Although PEP 405 does not specify, the built-in venv module always + # writes with UTF-8. (pypa/pip#8717) + with open(pyvenv_cfg_file, encoding="utf-8") as f: + return f.read().splitlines() # avoids trailing newlines + except OSError: + return None + + +def _no_global_under_venv(): + # type: () -> bool + """Check `{sys.prefix}/pyvenv.cfg` for system site-packages inclusion + + PEP 405 specifies that when system site-packages are not supposed to be + visible from a virtual environment, `pyvenv.cfg` must contain the following + line: + + include-system-site-packages = false + + Additionally, log a warning if accessing the file fails. + """ + cfg_lines = _get_pyvenv_cfg_lines() + if cfg_lines is None: + # We're not in a "sane" venv, so assume there is no system + # site-packages access (since that's PEP 405's default state). + logger.warning( + "Could not access 'pyvenv.cfg' despite a virtual environment " + "being active. Assuming global site-packages is not accessible " + "in this environment." + ) + return True + + for line in cfg_lines: + match = _INCLUDE_SYSTEM_SITE_PACKAGES_REGEX.match(line) + if match is not None and match.group("value") == "false": + return True + return False + + +def _no_global_under_regular_virtualenv(): + # type: () -> bool + """Check if "no-global-site-packages.txt" exists beside site.py + + This mirrors logic in pypa/virtualenv for determining whether system + site-packages are visible in the virtual environment. + """ + site_mod_dir = os.path.dirname(os.path.abspath(site.__file__)) + no_global_site_packages_file = os.path.join( + site_mod_dir, + "no-global-site-packages.txt", + ) + return os.path.exists(no_global_site_packages_file) + + +def virtualenv_no_global(): + # type: () -> bool + """Returns a boolean, whether running in venv with no system site-packages.""" + # PEP 405 compliance needs to be checked first since virtualenv >=20 would + # return True for both checks, but is only able to use the PEP 405 config. + if _running_under_venv(): + return _no_global_under_venv() + + if _running_under_regular_virtualenv(): + return _no_global_under_regular_virtualenv() + + return False diff --git a/pipenv/patched/notpip/_internal/utils/wheel.py b/pipenv/patched/notpip/_internal/utils/wheel.py new file mode 100644 index 00000000..a125b5ff --- /dev/null +++ b/pipenv/patched/notpip/_internal/utils/wheel.py @@ -0,0 +1,189 @@ +"""Support functions for working with wheel files. +""" + +import logging +from email.message import Message +from email.parser import Parser +from typing import Dict, Tuple +from zipfile import BadZipFile, ZipFile + +from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name +from pipenv.patched.notpip._vendor.pkg_resources import DistInfoDistribution, Distribution + +from pipenv.patched.notpip._internal.exceptions import UnsupportedWheel +from pipenv.patched.notpip._internal.utils.pkg_resources import DictMetadata + +VERSION_COMPATIBLE = (1, 0) + + +logger = logging.getLogger(__name__) + + +class WheelMetadata(DictMetadata): + """Metadata provider that maps metadata decoding exceptions to our + internal exception type. + """ + + def __init__(self, metadata, wheel_name): + # type: (Dict[str, bytes], str) -> None + super().__init__(metadata) + self._wheel_name = wheel_name + + def get_metadata(self, name): + # type: (str) -> str + try: + return super().get_metadata(name) + except UnicodeDecodeError as e: + # Augment the default error with the origin of the file. + raise UnsupportedWheel( + f"Error decoding metadata for {self._wheel_name}: {e}" + ) + + +def pkg_resources_distribution_for_wheel(wheel_zip, name, location): + # type: (ZipFile, str, str) -> Distribution + """Get a pkg_resources distribution given a wheel. + + :raises UnsupportedWheel: on any errors + """ + info_dir, _ = parse_wheel(wheel_zip, name) + + metadata_files = [p for p in wheel_zip.namelist() if p.startswith(f"{info_dir}/")] + + metadata_text = {} # type: Dict[str, bytes] + for path in metadata_files: + _, metadata_name = path.split("/", 1) + + try: + metadata_text[metadata_name] = read_wheel_metadata_file(wheel_zip, path) + except UnsupportedWheel as e: + raise UnsupportedWheel("{} has an invalid wheel, {}".format(name, str(e))) + + metadata = WheelMetadata(metadata_text, location) + + return DistInfoDistribution(location=location, metadata=metadata, project_name=name) + + +def parse_wheel(wheel_zip, name): + # type: (ZipFile, str) -> Tuple[str, Message] + """Extract information from the provided wheel, ensuring it meets basic + standards. + + Returns the name of the .dist-info directory and the parsed WHEEL metadata. + """ + try: + info_dir = wheel_dist_info_dir(wheel_zip, name) + metadata = wheel_metadata(wheel_zip, info_dir) + version = wheel_version(metadata) + except UnsupportedWheel as e: + raise UnsupportedWheel("{} has an invalid wheel, {}".format(name, str(e))) + + check_compatibility(version, name) + + return info_dir, metadata + + +def wheel_dist_info_dir(source, name): + # type: (ZipFile, str) -> str + """Returns the name of the contained .dist-info directory. + + Raises AssertionError or UnsupportedWheel if not found, >1 found, or + it doesn't match the provided name. + """ + # Zip file path separators must be / + subdirs = {p.split("/", 1)[0] for p in source.namelist()} + + info_dirs = [s for s in subdirs if s.endswith(".dist-info")] + + if not info_dirs: + raise UnsupportedWheel(".dist-info directory not found") + + if len(info_dirs) > 1: + raise UnsupportedWheel( + "multiple .dist-info directories found: {}".format(", ".join(info_dirs)) + ) + + info_dir = info_dirs[0] + + info_dir_name = canonicalize_name(info_dir) + canonical_name = canonicalize_name(name) + if not info_dir_name.startswith(canonical_name): + raise UnsupportedWheel( + ".dist-info directory {!r} does not start with {!r}".format( + info_dir, canonical_name + ) + ) + + return info_dir + + +def read_wheel_metadata_file(source, path): + # type: (ZipFile, str) -> bytes + try: + return source.read(path) + # BadZipFile for general corruption, KeyError for missing entry, + # and RuntimeError for password-protected files + except (BadZipFile, KeyError, RuntimeError) as e: + raise UnsupportedWheel(f"could not read {path!r} file: {e!r}") + + +def wheel_metadata(source, dist_info_dir): + # type: (ZipFile, str) -> Message + """Return the WHEEL metadata of an extracted wheel, if possible. + Otherwise, raise UnsupportedWheel. + """ + path = f"{dist_info_dir}/WHEEL" + # Zip file path separators must be / + wheel_contents = read_wheel_metadata_file(source, path) + + try: + wheel_text = wheel_contents.decode() + except UnicodeDecodeError as e: + raise UnsupportedWheel(f"error decoding {path!r}: {e!r}") + + # FeedParser (used by Parser) does not raise any exceptions. The returned + # message may have .defects populated, but for backwards-compatibility we + # currently ignore them. + return Parser().parsestr(wheel_text) + + +def wheel_version(wheel_data): + # type: (Message) -> Tuple[int, ...] + """Given WHEEL metadata, return the parsed Wheel-Version. + Otherwise, raise UnsupportedWheel. + """ + version_text = wheel_data["Wheel-Version"] + if version_text is None: + raise UnsupportedWheel("WHEEL is missing Wheel-Version") + + version = version_text.strip() + + try: + return tuple(map(int, version.split("."))) + except ValueError: + raise UnsupportedWheel(f"invalid Wheel-Version: {version!r}") + + +def check_compatibility(version, name): + # type: (Tuple[int, ...], str) -> None + """Raises errors or warns if called with an incompatible Wheel-Version. + + pip should refuse to install a Wheel-Version that's a major series + ahead of what it's compatible with (e.g 2.0 > 1.1); and warn when + installing a version only minor version ahead (e.g 1.2 > 1.1). + + version: a 2-tuple representing a Wheel-Version (Major, Minor) + name: name of wheel or package to raise exception about + + :raises UnsupportedWheel: when an incompatible Wheel-Version is given + """ + if version[0] > VERSION_COMPATIBLE[0]: + raise UnsupportedWheel( + "{}'s Wheel-Version ({}) is not compatible with this version " + "of pip".format(name, ".".join(map(str, version))) + ) + elif version > VERSION_COMPATIBLE: + logger.warning( + "Installing from a newer Wheel-Version (%s)", + ".".join(map(str, version)), + ) diff --git a/pipenv/patched/notpip/_internal/vcs/__init__.py b/pipenv/patched/notpip/_internal/vcs/__init__.py index 06d36148..5ed81940 100644 --- a/pipenv/patched/notpip/_internal/vcs/__init__.py +++ b/pipenv/patched/notpip/_internal/vcs/__init__.py @@ -1,534 +1,15 @@ -"""Handles all VCS (version control) support""" -from __future__ import absolute_import - -import errno -import logging -import os -import shutil -import sys - -from pipenv.patched.notpip._vendor.six.moves.urllib import parse as urllib_parse - -from pipenv.patched.notpip._internal.exceptions import BadCommand -from pipenv.patched.notpip._internal.utils.misc import ( - display_path, backup_dir, call_subprocess, rmtree, ask_path_exists, +# Expose a limited set of classes and functions so callers outside of +# the vcs package don't need to import deeper than `pipenv.patched.notpip._internal.vcs`. +# (The test directory may still need to import from a vcs sub-package.) +# Import all vcs modules to register each VCS in the VcsSupport object. +import pipenv.patched.notpip._internal.vcs.bazaar +import pipenv.patched.notpip._internal.vcs.git +import pipenv.patched.notpip._internal.vcs.mercurial +import pipenv.patched.notpip._internal.vcs.subversion # noqa: F401 +from pipenv.patched.notpip._internal.vcs.versioncontrol import ( # noqa: F401 + RemoteNotFoundError, + RemoteNotValidError, + is_url, + make_vcs_requirement_url, + vcs, ) -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING - -if MYPY_CHECK_RUNNING: - from typing import ( # noqa: F401 - Any, Dict, Iterable, List, Mapping, Optional, Text, Tuple, Type - ) - from pipenv.patched.notpip._internal.utils.ui import SpinnerInterface # noqa: F401 - - AuthInfo = Tuple[Optional[str], Optional[str]] - -__all__ = ['vcs'] - - -logger = logging.getLogger(__name__) - - -class RemoteNotFoundError(Exception): - pass - - -class RevOptions(object): - - """ - Encapsulates a VCS-specific revision to install, along with any VCS - install options. - - Instances of this class should be treated as if immutable. - """ - - def __init__(self, vcs, rev=None, extra_args=None): - # type: (VersionControl, Optional[str], Optional[List[str]]) -> None - """ - Args: - vcs: a VersionControl object. - rev: the name of the revision to install. - extra_args: a list of extra options. - """ - if extra_args is None: - extra_args = [] - - self.extra_args = extra_args - self.rev = rev - self.vcs = vcs - - def __repr__(self): - return '<RevOptions {}: rev={!r}>'.format(self.vcs.name, self.rev) - - @property - def arg_rev(self): - # type: () -> Optional[str] - if self.rev is None: - return self.vcs.default_arg_rev - - return self.rev - - def to_args(self): - # type: () -> List[str] - """ - Return the VCS-specific command arguments. - """ - args = [] # type: List[str] - rev = self.arg_rev - if rev is not None: - args += self.vcs.get_base_rev_args(rev) - args += self.extra_args - - return args - - def to_display(self): - # type: () -> str - if not self.rev: - return '' - - return ' (to revision {})'.format(self.rev) - - def make_new(self, rev): - # type: (str) -> RevOptions - """ - Make a copy of the current instance, but with a new rev. - - Args: - rev: the name of the revision for the new object. - """ - return self.vcs.make_rev_options(rev, extra_args=self.extra_args) - - -class VcsSupport(object): - _registry = {} # type: Dict[str, Type[VersionControl]] - schemes = ['ssh', 'git', 'hg', 'bzr', 'sftp', 'svn'] - - def __init__(self): - # type: () -> None - # Register more schemes with urlparse for various version control - # systems - urllib_parse.uses_netloc.extend(self.schemes) - # Python >= 2.7.4, 3.3 doesn't have uses_fragment - if getattr(urllib_parse, 'uses_fragment', None): - urllib_parse.uses_fragment.extend(self.schemes) - super(VcsSupport, self).__init__() - - def __iter__(self): - return self._registry.__iter__() - - @property - def backends(self): - # type: () -> List[Type[VersionControl]] - return list(self._registry.values()) - - @property - def dirnames(self): - # type: () -> List[str] - return [backend.dirname for backend in self.backends] - - @property - def all_schemes(self): - # type: () -> List[str] - schemes = [] # type: List[str] - for backend in self.backends: - schemes.extend(backend.schemes) - return schemes - - def register(self, cls): - # type: (Type[VersionControl]) -> None - if not hasattr(cls, 'name'): - logger.warning('Cannot register VCS %s', cls.__name__) - return - if cls.name not in self._registry: - self._registry[cls.name] = cls - logger.debug('Registered VCS backend: %s', cls.name) - - def unregister(self, cls=None, name=None): - # type: (Optional[Type[VersionControl]], Optional[str]) -> None - if name in self._registry: - del self._registry[name] - elif cls in self._registry.values(): - del self._registry[cls.name] - else: - logger.warning('Cannot unregister because no class or name given') - - def get_backend_type(self, location): - # type: (str) -> Optional[Type[VersionControl]] - """ - Return the type of the version control backend if found at given - location, e.g. vcs.get_backend_type('/path/to/vcs/checkout') - """ - for vc_type in self._registry.values(): - if vc_type.controls_location(location): - logger.debug('Determine that %s uses VCS: %s', - location, vc_type.name) - return vc_type - return None - - def get_backend(self, name): - # type: (str) -> Optional[Type[VersionControl]] - name = name.lower() - if name in self._registry: - return self._registry[name] - return None - - -vcs = VcsSupport() - - -class VersionControl(object): - name = '' - dirname = '' - repo_name = '' - # List of supported schemes for this Version Control - schemes = () # type: Tuple[str, ...] - # Iterable of environment variable names to pass to call_subprocess(). - unset_environ = () # type: Tuple[str, ...] - default_arg_rev = None # type: Optional[str] - - def __init__(self, url=None, *args, **kwargs): - self.url = url - super(VersionControl, self).__init__(*args, **kwargs) - - def get_base_rev_args(self, rev): - """ - Return the base revision arguments for a vcs command. - - Args: - rev: the name of a revision to install. Cannot be None. - """ - raise NotImplementedError - - def make_rev_options(self, rev=None, extra_args=None): - # type: (Optional[str], Optional[List[str]]) -> RevOptions - """ - Return a RevOptions object. - - Args: - rev: the name of a revision to install. - extra_args: a list of extra options. - """ - return RevOptions(self, rev, extra_args=extra_args) - - @classmethod - def _is_local_repository(cls, repo): - # type: (str) -> bool - """ - posix absolute paths start with os.path.sep, - win32 ones start with drive (like c:\\folder) - """ - drive, tail = os.path.splitdrive(repo) - return repo.startswith(os.path.sep) or bool(drive) - - def export(self, location): - """ - Export the repository at the url to the destination location - i.e. only download the files, without vcs informations - """ - raise NotImplementedError - - def get_netloc_and_auth(self, netloc, scheme): - """ - Parse the repository URL's netloc, and return the new netloc to use - along with auth information. - - Args: - netloc: the original repository URL netloc. - scheme: the repository URL's scheme without the vcs prefix. - - This is mainly for the Subversion class to override, so that auth - information can be provided via the --username and --password options - instead of through the URL. For other subclasses like Git without - such an option, auth information must stay in the URL. - - Returns: (netloc, (username, password)). - """ - return netloc, (None, None) - - def get_url_rev_and_auth(self, url): - # type: (str) -> Tuple[str, Optional[str], AuthInfo] - """ - Parse the repository URL to use, and return the URL, revision, - and auth info to use. - - Returns: (url, rev, (username, password)). - """ - scheme, netloc, path, query, frag = urllib_parse.urlsplit(url) - if '+' not in scheme: - raise ValueError( - "Sorry, {!r} is a malformed VCS url. " - "The format is <vcs>+<protocol>://<url>, " - "e.g. svn+http://myrepo/svn/MyApp#egg=MyApp".format(url) - ) - # Remove the vcs prefix. - scheme = scheme.split('+', 1)[1] - netloc, user_pass = self.get_netloc_and_auth(netloc, scheme) - rev = None - if '@' in path: - path, rev = path.rsplit('@', 1) - url = urllib_parse.urlunsplit((scheme, netloc, path, query, '')) - return url, rev, user_pass - - def make_rev_args(self, username, password): - """ - Return the RevOptions "extra arguments" to use in obtain(). - """ - return [] - - def get_url_rev_options(self, url): - # type: (str) -> Tuple[str, RevOptions] - """ - Return the URL and RevOptions object to use in obtain() and in - some cases export(), as a tuple (url, rev_options). - """ - url, rev, user_pass = self.get_url_rev_and_auth(url) - username, password = user_pass - extra_args = self.make_rev_args(username, password) - rev_options = self.make_rev_options(rev, extra_args=extra_args) - - return url, rev_options - - def normalize_url(self, url): - # type: (str) -> str - """ - Normalize a URL for comparison by unquoting it and removing any - trailing slash. - """ - return urllib_parse.unquote(url).rstrip('/') - - def compare_urls(self, url1, url2): - # type: (str, str) -> bool - """ - Compare two repo URLs for identity, ignoring incidental differences. - """ - return (self.normalize_url(url1) == self.normalize_url(url2)) - - def fetch_new(self, dest, url, rev_options): - """ - Fetch a revision from a repository, in the case that this is the - first fetch from the repository. - - Args: - dest: the directory to fetch the repository to. - rev_options: a RevOptions object. - """ - raise NotImplementedError - - def switch(self, dest, url, rev_options): - """ - Switch the repo at ``dest`` to point to ``URL``. - - Args: - rev_options: a RevOptions object. - """ - raise NotImplementedError - - def update(self, dest, url, rev_options): - """ - Update an already-existing repo to the given ``rev_options``. - - Args: - rev_options: a RevOptions object. - """ - raise NotImplementedError - - def is_commit_id_equal(self, dest, name): - """ - Return whether the id of the current commit equals the given name. - - Args: - dest: the repository directory. - name: a string name. - """ - raise NotImplementedError - - def obtain(self, dest): - # type: (str) -> None - """ - Install or update in editable mode the package represented by this - VersionControl object. - - Args: - dest: the repository directory in which to install or update. - """ - url, rev_options = self.get_url_rev_options(self.url) - - if not os.path.exists(dest): - self.fetch_new(dest, url, rev_options) - return - - rev_display = rev_options.to_display() - if self.is_repository_directory(dest): - existing_url = self.get_remote_url(dest) - if self.compare_urls(existing_url, url): - logger.debug( - '%s in %s exists, and has correct URL (%s)', - self.repo_name.title(), - display_path(dest), - url, - ) - if not self.is_commit_id_equal(dest, rev_options.rev): - logger.info( - 'Updating %s %s%s', - display_path(dest), - self.repo_name, - rev_display, - ) - self.update(dest, url, rev_options) - else: - logger.info('Skipping because already up-to-date.') - return - - logger.warning( - '%s %s in %s exists with URL %s', - self.name, - self.repo_name, - display_path(dest), - existing_url, - ) - prompt = ('(s)witch, (i)gnore, (w)ipe, (b)ackup ', - ('s', 'i', 'w', 'b')) - else: - logger.warning( - 'Directory %s already exists, and is not a %s %s.', - dest, - self.name, - self.repo_name, - ) - # https://github.com/python/mypy/issues/1174 - prompt = ('(i)gnore, (w)ipe, (b)ackup ', # type: ignore - ('i', 'w', 'b')) - - logger.warning( - 'The plan is to install the %s repository %s', - self.name, - url, - ) - response = ask_path_exists('What to do? %s' % prompt[0], prompt[1]) - - if response == 'a': - sys.exit(-1) - - if response == 'w': - logger.warning('Deleting %s', display_path(dest)) - rmtree(dest) - self.fetch_new(dest, url, rev_options) - return - - if response == 'b': - dest_dir = backup_dir(dest) - logger.warning( - 'Backing up %s to %s', display_path(dest), dest_dir, - ) - shutil.move(dest, dest_dir) - self.fetch_new(dest, url, rev_options) - return - - # Do nothing if the response is "i". - if response == 's': - logger.info( - 'Switching %s %s to %s%s', - self.repo_name, - display_path(dest), - url, - rev_display, - ) - self.switch(dest, url, rev_options) - - def unpack(self, location): - # type: (str) -> None - """ - Clean up current location and download the url repository - (and vcs infos) into location - """ - if os.path.exists(location): - rmtree(location) - self.obtain(location) - - @classmethod - def get_src_requirement(cls, location, project_name): - """ - Return a string representing the requirement needed to - redownload the files currently present in location, something - like: - {repository_url}@{revision}#egg={project_name}-{version_identifier} - """ - raise NotImplementedError - - @classmethod - def get_remote_url(cls, location): - """ - Return the url used at location - - Raises RemoteNotFoundError if the repository does not have a remote - url configured. - """ - raise NotImplementedError - - @classmethod - def get_revision(cls, location): - """ - Return the current commit id of the files at the given location. - """ - raise NotImplementedError - - @classmethod - def run_command( - cls, - cmd, # type: List[str] - show_stdout=True, # type: bool - cwd=None, # type: Optional[str] - on_returncode='raise', # type: str - extra_ok_returncodes=None, # type: Optional[Iterable[int]] - command_desc=None, # type: Optional[str] - extra_environ=None, # type: Optional[Mapping[str, Any]] - spinner=None # type: Optional[SpinnerInterface] - ): - # type: (...) -> Optional[Text] - """ - Run a VCS subcommand - This is simply a wrapper around call_subprocess that adds the VCS - command name, and checks that the VCS is available - """ - cmd = [cls.name] + cmd - try: - return call_subprocess(cmd, show_stdout, cwd, - on_returncode=on_returncode, - extra_ok_returncodes=extra_ok_returncodes, - command_desc=command_desc, - extra_environ=extra_environ, - unset_environ=cls.unset_environ, - spinner=spinner) - except OSError as e: - # errno.ENOENT = no such file or directory - # In other words, the VCS executable isn't available - if e.errno == errno.ENOENT: - raise BadCommand( - 'Cannot find command %r - do you have ' - '%r installed and in your ' - 'PATH?' % (cls.name, cls.name)) - else: - raise # re-raise exception if a different error occurred - - @classmethod - def is_repository_directory(cls, path): - # type: (str) -> bool - """ - Return whether a directory path is a repository directory. - """ - logger.debug('Checking in %s for %s (%s)...', - path, cls.dirname, cls.name) - return os.path.exists(os.path.join(path, cls.dirname)) - - @classmethod - def controls_location(cls, location): - # type: (str) -> bool - """ - Check if a location is controlled by the vcs. - It is meant to be overridden to implement smarter detection - mechanisms for specific vcs. - - This can do more than is_repository_directory() alone. For example, - the Git override checks that Git is actually available. - """ - return cls.is_repository_directory(location) diff --git a/pipenv/patched/notpip/_internal/vcs/bazaar.py b/pipenv/patched/notpip/_internal/vcs/bazaar.py index 256eb9e2..0913297c 100644 --- a/pipenv/patched/notpip/_internal/vcs/bazaar.py +++ b/pipenv/patched/notpip/_internal/vcs/bazaar.py @@ -1,16 +1,16 @@ -from __future__ import absolute_import - import logging -import os +from typing import List, Optional, Tuple -from pipenv.patched.notpip._vendor.six.moves.urllib import parse as urllib_parse - -from pipenv.patched.notpip._internal.download import path_to_url -from pipenv.patched.notpip._internal.utils.misc import ( - display_path, make_vcs_requirement_url, rmtree, +from pipenv.patched.notpip._internal.utils.misc import HiddenText, display_path +from pipenv.patched.notpip._internal.utils.subprocess import make_command +from pipenv.patched.notpip._internal.utils.urls import path_to_url +from pipenv.patched.notpip._internal.vcs.versioncontrol import ( + AuthInfo, + RemoteNotFoundError, + RevOptions, + VersionControl, + vcs, ) -from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory -from pipenv.patched.notpip._internal.vcs import VersionControl, vcs logger = logging.getLogger(__name__) @@ -20,37 +20,17 @@ class Bazaar(VersionControl): dirname = '.bzr' repo_name = 'branch' schemes = ( - 'bzr', 'bzr+http', 'bzr+https', 'bzr+ssh', 'bzr+sftp', 'bzr+ftp', - 'bzr+lp', + 'bzr+http', 'bzr+https', 'bzr+ssh', 'bzr+sftp', 'bzr+ftp', + 'bzr+lp', 'bzr+file' ) - def __init__(self, url=None, *args, **kwargs): - super(Bazaar, self).__init__(url, *args, **kwargs) - # This is only needed for python <2.7.5 - # Register lp but do not expose as a scheme to support bzr+lp. - if getattr(urllib_parse, 'uses_fragment', None): - urllib_parse.uses_fragment.extend(['lp']) - - def get_base_rev_args(self, rev): + @staticmethod + def get_base_rev_args(rev): + # type: (str) -> List[str] return ['-r', rev] - def export(self, location): - """ - Export the Bazaar repository at the url to the destination location - """ - # Remove the location to make sure Bazaar can export it correctly - if os.path.exists(location): - rmtree(location) - - with TempDirectory(kind="export") as temp_dir: - self.unpack(temp_dir.path) - - self.run_command( - ['export', location], - cwd=temp_dir.path, show_stdout=False, - ) - def fetch_new(self, dest, url, rev_options): + # type: (str, HiddenText, RevOptions) -> None rev_display = rev_options.to_display() logger.info( 'Checking out %s%s to %s', @@ -58,26 +38,35 @@ class Bazaar(VersionControl): rev_display, display_path(dest), ) - cmd_args = ['branch', '-q'] + rev_options.to_args() + [url, dest] + cmd_args = ( + make_command('branch', '-q', rev_options.to_args(), url, dest) + ) self.run_command(cmd_args) def switch(self, dest, url, rev_options): - self.run_command(['switch', url], cwd=dest) + # type: (str, HiddenText, RevOptions) -> None + self.run_command(make_command('switch', url), cwd=dest) def update(self, dest, url, rev_options): - cmd_args = ['pull', '-q'] + rev_options.to_args() + # type: (str, HiddenText, RevOptions) -> None + cmd_args = make_command('pull', '-q', rev_options.to_args()) self.run_command(cmd_args, cwd=dest) - def get_url_rev_and_auth(self, url): + @classmethod + def get_url_rev_and_auth(cls, url): + # type: (str) -> Tuple[str, Optional[str], AuthInfo] # hotfix the URL scheme after removing bzr+ from bzr+ssh:// readd it - url, rev, user_pass = super(Bazaar, self).get_url_rev_and_auth(url) + url, rev, user_pass = super().get_url_rev_and_auth(url) if url.startswith('ssh://'): url = 'bzr+' + url return url, rev, user_pass @classmethod def get_remote_url(cls, location): - urls = cls.run_command(['info'], show_stdout=False, cwd=location) + # type: (str) -> str + urls = cls.run_command( + ['info'], show_stdout=False, stdout_only=True, cwd=location + ) for line in urls.splitlines(): line = line.strip() for x in ('checkout of branch: ', @@ -87,26 +76,19 @@ class Bazaar(VersionControl): if cls._is_local_repository(repo): return path_to_url(repo) return repo - return None + raise RemoteNotFoundError @classmethod def get_revision(cls, location): + # type: (str) -> str revision = cls.run_command( - ['revno'], show_stdout=False, cwd=location, + ['revno'], show_stdout=False, stdout_only=True, cwd=location, ) return revision.splitlines()[-1] @classmethod - def get_src_requirement(cls, location, project_name): - repo = cls.get_remote_url(location) - if not repo: - return None - if not repo.lower().startswith('bzr:'): - repo = 'bzr+' + repo - current_rev = cls.get_revision(location) - return make_vcs_requirement_url(repo, current_rev, project_name) - - def is_commit_id_equal(self, dest, name): + def is_commit_id_equal(cls, dest, name): + # type: (str, Optional[str]) -> bool """Always assume the versions don't match""" return False diff --git a/pipenv/patched/notpip/_internal/vcs/git.py b/pipenv/patched/notpip/_internal/vcs/git.py index 310eb9fb..37b0e787 100644 --- a/pipenv/patched/notpip/_internal/vcs/git.py +++ b/pipenv/patched/notpip/_internal/vcs/git.py @@ -1,32 +1,56 @@ -from __future__ import absolute_import - import logging import os.path +import pathlib import re +import urllib.parse +import urllib.request +from typing import List, Optional, Tuple -from pipenv.patched.notpip._vendor.packaging.version import parse as parse_version -from pipenv.patched.notpip._vendor.six.moves.urllib import parse as urllib_parse -from pipenv.patched.notpip._vendor.six.moves.urllib import request as urllib_request - -from pipenv.patched.notpip._internal.exceptions import BadCommand -from pipenv.patched.notpip._internal.utils.compat import samefile -from pipenv.patched.notpip._internal.utils.misc import ( - display_path, make_vcs_requirement_url, redact_password_from_url, +from pipenv.patched.notpip._internal.exceptions import BadCommand, InstallationError +from pipenv.patched.notpip._internal.utils.misc import HiddenText, display_path, hide_url +from pipenv.patched.notpip._internal.utils.subprocess import make_command +from pipenv.patched.notpip._internal.vcs.versioncontrol import ( + AuthInfo, + RemoteNotFoundError, + RemoteNotValidError, + RevOptions, + VersionControl, + find_path_to_project_root_from_repo_root, + vcs, ) -from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory -from pipenv.patched.notpip._internal.vcs import RemoteNotFoundError, VersionControl, vcs -urlsplit = urllib_parse.urlsplit -urlunsplit = urllib_parse.urlunsplit +urlsplit = urllib.parse.urlsplit +urlunsplit = urllib.parse.urlunsplit logger = logging.getLogger(__name__) -HASH_REGEX = re.compile('[a-fA-F0-9]{40}') +GIT_VERSION_REGEX = re.compile( + r"^git version " # Prefix. + r"(\d+)" # Major. + r"\.(\d+)" # Dot, minor. + r"(?:\.(\d+))?" # Optional dot, patch. + r".*$" # Suffix, including any pre- and post-release segments we don't care about. +) + +HASH_REGEX = re.compile('^[a-fA-F0-9]{40}$') + +# SCP (Secure copy protocol) shorthand. e.g. 'git@example.com:foo/bar.git' +SCP_REGEX = re.compile(r"""^ + # Optional user, e.g. 'git@' + (\w+@)? + # Server, e.g. 'github.com'. + ([^/:]+): + # The server-side path. e.g. 'user/project.git'. Must start with an + # alphanumeric character so as not to be confusable with a Windows paths + # like 'C:/foo/bar' or 'C:\foo\bar'. + (\w[^:]*) +$""", re.VERBOSE) def looks_like_hash(sha): + # type: (str) -> bool return bool(HASH_REGEX.match(sha)) @@ -35,51 +59,47 @@ class Git(VersionControl): dirname = '.git' repo_name = 'clone' schemes = ( - 'git', 'git+http', 'git+https', 'git+ssh', 'git+git', 'git+file', + 'git+http', 'git+https', 'git+ssh', 'git+git', 'git+file', ) # Prevent the user's environment variables from interfering with pip: # https://github.com/pypa/pip/issues/1130 unset_environ = ('GIT_DIR', 'GIT_WORK_TREE') default_arg_rev = 'HEAD' - def __init__(self, url=None, *args, **kwargs): - - # Works around an apparent Git bug - # (see https://article.gmane.org/gmane.comp.version-control.git/146500) - if url: - scheme, netloc, path, query, fragment = urlsplit(url) - if scheme.endswith('file'): - initial_slashes = path[:-len(path.lstrip('/'))] - newpath = ( - initial_slashes + - urllib_request.url2pathname(path) - .replace('\\', '/').lstrip('/') - ) - url = urlunsplit((scheme, netloc, newpath, query, fragment)) - after_plus = scheme.find('+') + 1 - url = scheme[:after_plus] + urlunsplit( - (scheme[after_plus:], netloc, newpath, query, fragment), - ) - - super(Git, self).__init__(url, *args, **kwargs) - - def get_base_rev_args(self, rev): + @staticmethod + def get_base_rev_args(rev): + # type: (str) -> List[str] return [rev] - def get_git_version(self): - VERSION_PFX = 'git version ' - version = self.run_command(['version'], show_stdout=False) - if version.startswith(VERSION_PFX): - version = version[len(VERSION_PFX):].split()[0] - else: - version = '' - # get first 3 positions of the git version becasue - # on windows it is x.y.z.windows.t, and this parses as - # LegacyVersion which always smaller than a Version. - version = '.'.join(version.split('.')[:3]) - return parse_version(version) + def is_immutable_rev_checkout(self, url, dest): + # type: (str, str) -> bool + _, rev_options = self.get_url_rev_options(hide_url(url)) + if not rev_options.rev: + return False + if not self.is_commit_id_equal(dest, rev_options.rev): + # the current commit is different from rev, + # which means rev was something else than a commit hash + return False + # return False in the rare case rev is both a commit hash + # and a tag or a branch; we don't want to cache in that case + # because that branch/tag could point to something else in the future + is_tag_or_branch = bool( + self.get_revision_sha(dest, rev_options.rev)[0] + ) + return not is_tag_or_branch - def get_current_branch(self, location): + def get_git_version(self) -> Tuple[int, ...]: + version = self.run_command( + ['version'], show_stdout=False, stdout_only=True + ) + match = GIT_VERSION_REGEX.match(version) + if not match: + return () + return tuple(int(c) for c in match.groups()) + + @classmethod + def get_current_branch(cls, location): + # type: (str) -> Optional[str] """ Return the current branch, or None if HEAD isn't at a branch (e.g. detached HEAD). @@ -89,8 +109,12 @@ class Git(VersionControl): # command to exit with status code 1 instead of 128 in this case # and to suppress the message to stderr. args = ['symbolic-ref', '-q', 'HEAD'] - output = self.run_command( - args, extra_ok_returncodes=(1, ), show_stdout=False, cwd=location, + output = cls.run_command( + args, + extra_ok_returncodes=(1, ), + show_stdout=False, + stdout_only=True, + cwd=location, ) ref = output.strip() @@ -99,19 +123,9 @@ class Git(VersionControl): return None - def export(self, location): - """Export the Git repository at the url to the destination location""" - if not location.endswith('/'): - location = location + '/' - - with TempDirectory(kind="export") as temp_dir: - self.unpack(temp_dir.path) - self.run_command( - ['checkout-index', '-a', '-f', '--prefix', location], - show_stdout=False, cwd=temp_dir.path - ) - - def get_revision_sha(self, dest, rev): + @classmethod + def get_revision_sha(cls, dest, rev): + # type: (str, str) -> Tuple[Optional[str], bool] """ Return (sha_or_none, is_branch), where sha_or_none is a commit hash if the revision names a remote branch or tag, otherwise None. @@ -121,21 +135,32 @@ class Git(VersionControl): rev: the revision name. """ # Pass rev to pre-filter the list. - output = self.run_command(['show-ref', rev], cwd=dest, - show_stdout=False, on_returncode='ignore') + output = cls.run_command( + ['show-ref', rev], + cwd=dest, + show_stdout=False, + stdout_only=True, + on_returncode='ignore', + ) refs = {} - for line in output.strip().splitlines(): + # NOTE: We do not use splitlines here since that would split on other + # unicode separators, which can be maliciously used to install a + # different revision. + for line in output.strip().split("\n"): + line = line.rstrip("\r") + if not line: + continue try: - sha, ref = line.split() + ref_sha, ref_name = line.split(" ", maxsplit=2) except ValueError: # Include the offending line to simplify troubleshooting if # this error ever occurs. - raise ValueError('unexpected show-ref line: {!r}'.format(line)) + raise ValueError(f'unexpected show-ref line: {line!r}') - refs[ref] = sha + refs[ref_name] = ref_sha - branch_ref = 'refs/remotes/origin/{}'.format(rev) - tag_ref = 'refs/tags/{}'.format(rev) + branch_ref = f'refs/remotes/origin/{rev}' + tag_ref = f'refs/tags/{rev}' sha = refs.get(branch_ref) if sha is not None: @@ -145,7 +170,33 @@ class Git(VersionControl): return (sha, False) - def resolve_revision(self, dest, url, rev_options): + @classmethod + def _should_fetch(cls, dest, rev): + # type: (str, str) -> bool + """ + Return true if rev is a ref or is a commit that we don't have locally. + + Branches and tags are not considered in this method because they are + assumed to be always available locally (which is a normal outcome of + ``git clone`` and ``git fetch --tags``). + """ + if rev.startswith("refs/"): + # Always fetch remote refs. + return True + + if not looks_like_hash(rev): + # Git fetch would fail with abbreviated commits. + return False + + if cls.has_commit(dest, rev): + # Don't fetch if we have the commit locally. + return False + + return True + + @classmethod + def resolve_revision(cls, dest, url, rev_options): + # type: (str, HiddenText, RevOptions) -> RevOptions """ Resolve a revision to a new RevOptions object with the SHA1 of the branch, tag, or ref if found. @@ -154,7 +205,11 @@ class Git(VersionControl): rev_options: a RevOptions object. """ rev = rev_options.arg_rev - sha, is_branch = self.get_revision_sha(dest, rev) + # The arg_rev property's implementation for Git ensures that the + # rev return value is always non-None. + assert rev is not None + + sha, is_branch = cls.get_revision_sha(dest, rev) if sha is not None: rev_options = rev_options.make_new(sha) @@ -170,21 +225,23 @@ class Git(VersionControl): rev, ) - if not rev.startswith('refs/'): + if not cls._should_fetch(dest, rev): return rev_options - # If it looks like a ref, we have to fetch it explicitly. - self.run_command( - ['fetch', '-q', url] + rev_options.to_args(), + # fetch the requested revision + cls.run_command( + make_command('fetch', '-q', url, rev_options.to_args()), cwd=dest, ) # Change the revision to the SHA of the ref we fetched - sha = self.get_revision(dest, rev='FETCH_HEAD') + sha = cls.get_revision(dest, rev='FETCH_HEAD') rev_options = rev_options.make_new(sha) return rev_options - def is_commit_id_equal(self, dest, name): + @classmethod + def is_commit_id_equal(cls, dest, name): + # type: (str, Optional[str]) -> bool """ Return whether the current commit hash equals the given name. @@ -196,15 +253,13 @@ class Git(VersionControl): # Then avoid an unnecessary subprocess call. return False - return self.get_revision(dest) == name + return cls.get_revision(dest) == name def fetch_new(self, dest, url, rev_options): + # type: (str, HiddenText, RevOptions) -> None rev_display = rev_options.to_display() - logger.info( - 'Cloning %s%s to %s', redact_password_from_url(url), - rev_display, display_path(dest), - ) - self.run_command(['clone', '-q', url, dest]) + logger.info('Cloning %s%s to %s', url, rev_display, display_path(dest)) + self.run_command(make_command('clone', '-q', url, dest)) if rev_options.rev: # Then a specific revision was requested. @@ -214,43 +269,56 @@ class Git(VersionControl): # Only do a checkout if the current commit id doesn't match # the requested revision. if not self.is_commit_id_equal(dest, rev_options.rev): - cmd_args = ['checkout', '-q'] + rev_options.to_args() + cmd_args = make_command( + 'checkout', '-q', rev_options.to_args(), + ) self.run_command(cmd_args, cwd=dest) elif self.get_current_branch(dest) != branch_name: # Then a specific branch was requested, and that branch # is not yet checked out. - track_branch = 'origin/{}'.format(branch_name) + track_branch = f'origin/{branch_name}' cmd_args = [ 'checkout', '-b', branch_name, '--track', track_branch, ] self.run_command(cmd_args, cwd=dest) + else: + sha = self.get_revision(dest) + rev_options = rev_options.make_new(sha) + + logger.info("Resolved %s to commit %s", url, rev_options.rev) #: repo may contain submodules self.update_submodules(dest) def switch(self, dest, url, rev_options): - self.run_command(['config', 'remote.origin.url', url], cwd=dest) - cmd_args = ['checkout', '-q'] + rev_options.to_args() + # type: (str, HiddenText, RevOptions) -> None + self.run_command( + make_command('config', 'remote.origin.url', url), + cwd=dest, + ) + cmd_args = make_command('checkout', '-q', rev_options.to_args()) self.run_command(cmd_args, cwd=dest) self.update_submodules(dest) def update(self, dest, url, rev_options): + # type: (str, HiddenText, RevOptions) -> None # First fetch changes from the default remote - if self.get_git_version() >= parse_version('1.9.0'): + if self.get_git_version() >= (1, 9): # fetch tags in addition to everything else self.run_command(['fetch', '-q', '--tags'], cwd=dest) else: self.run_command(['fetch', '-q'], cwd=dest) # Then reset to wanted revision (maybe even origin/master) rev_options = self.resolve_revision(dest, url, rev_options) - cmd_args = ['reset', '--hard', '-q'] + rev_options.to_args() + cmd_args = make_command('reset', '--hard', '-q', rev_options.to_args()) self.run_command(cmd_args, cwd=dest) #: update submodules self.update_submodules(dest) @classmethod def get_remote_url(cls, location): + # type: (str) -> str """ Return URL of the first remote encountered. @@ -261,7 +329,10 @@ class Git(VersionControl): # exits with return code 1 if there are no matching lines. stdout = cls.run_command( ['config', '--get-regexp', r'remote\..*\.url'], - extra_ok_returncodes=(1, ), show_stdout=False, cwd=location, + extra_ok_returncodes=(1, ), + show_stdout=False, + stdout_only=True, + cwd=location, ) remotes = stdout.splitlines() try: @@ -274,96 +345,162 @@ class Git(VersionControl): found_remote = remote break url = found_remote.split(' ')[1] - return url.strip() + return cls._git_remote_to_pip_url(url.strip()) + + @staticmethod + def _git_remote_to_pip_url(url): + # type: (str) -> str + """ + Convert a remote url from what git uses to what pip accepts. + + There are 3 legal forms **url** may take: + + 1. A fully qualified url: ssh://git@example.com/foo/bar.git + 2. A local project.git folder: /path/to/bare/repository.git + 3. SCP shorthand for form 1: git@example.com:foo/bar.git + + Form 1 is output as-is. Form 2 must be converted to URI and form 3 must + be converted to form 1. + + See the corresponding test test_git_remote_url_to_pip() for examples of + sample inputs/outputs. + """ + if re.match(r"\w+://", url): + # This is already valid. Pass it though as-is. + return url + if os.path.exists(url): + # A local bare remote (git clone --mirror). + # Needs a file:// prefix. + return pathlib.PurePath(url).as_uri() + scp_match = SCP_REGEX.match(url) + if scp_match: + # Add an ssh:// prefix and replace the ':' with a '/'. + return scp_match.expand(r"ssh://\1\2/\3") + # Otherwise, bail out. + raise RemoteNotValidError(url) + + @classmethod + def has_commit(cls, location, rev): + # type: (str, str) -> bool + """ + Check if rev is a commit that is available in the local repository. + """ + try: + cls.run_command( + ['rev-parse', '-q', '--verify', "sha^" + rev], + cwd=location, + log_failed_cmd=False, + ) + except InstallationError: + return False + else: + return True @classmethod def get_revision(cls, location, rev=None): + # type: (str, Optional[str]) -> str if rev is None: rev = 'HEAD' current_rev = cls.run_command( - ['rev-parse', rev], show_stdout=False, cwd=location, + ['rev-parse', rev], + show_stdout=False, + stdout_only=True, + cwd=location, ) return current_rev.strip() @classmethod - def _get_subdirectory(cls, location): - """Return the relative path of setup.py to the git repo root.""" + def get_subdirectory(cls, location): + # type: (str) -> Optional[str] + """ + Return the path to Python project root, relative to the repo root. + Return None if the project root is in the repo root. + """ # find the repo root - git_dir = cls.run_command(['rev-parse', '--git-dir'], - show_stdout=False, cwd=location).strip() + git_dir = cls.run_command( + ['rev-parse', '--git-dir'], + show_stdout=False, + stdout_only=True, + cwd=location, + ).strip() if not os.path.isabs(git_dir): git_dir = os.path.join(location, git_dir) - root_dir = os.path.join(git_dir, '..') - # find setup.py - orig_location = location - while not os.path.exists(os.path.join(location, 'setup.py')): - last_location = location - location = os.path.dirname(location) - if location == last_location: - # We've traversed up to the root of the filesystem without - # finding setup.py - logger.warning( - "Could not find setup.py for directory %s (tried all " - "parent directories)", - orig_location, - ) - return None - # relative path of setup.py to repo root - if samefile(root_dir, location): - return None - return os.path.relpath(location, root_dir) + repo_root = os.path.abspath(os.path.join(git_dir, '..')) + return find_path_to_project_root_from_repo_root(location, repo_root) @classmethod - def get_src_requirement(cls, location, project_name): - repo = cls.get_remote_url(location) - if not repo.lower().startswith('git:'): - repo = 'git+' + repo - current_rev = cls.get_revision(location) - subdir = cls._get_subdirectory(location) - req = make_vcs_requirement_url(repo, current_rev, project_name, - subdir=subdir) - - return req - - def get_url_rev_and_auth(self, url): + def get_url_rev_and_auth(cls, url): + # type: (str) -> Tuple[str, Optional[str], AuthInfo] """ Prefixes stub URLs like 'user@hostname:user/repo.git' with 'ssh://'. That's required because although they use SSH they sometimes don't work with a ssh:// scheme (e.g. GitHub). But we need a scheme for parsing. Hence we remove it again afterwards and return it as a stub. """ + # Works around an apparent Git bug + # (see https://article.gmane.org/gmane.comp.version-control.git/146500) + scheme, netloc, path, query, fragment = urlsplit(url) + if scheme.endswith('file'): + initial_slashes = path[:-len(path.lstrip('/'))] + newpath = ( + initial_slashes + + urllib.request.url2pathname(path) + .replace('\\', '/').lstrip('/') + ) + after_plus = scheme.find('+') + 1 + url = scheme[:after_plus] + urlunsplit( + (scheme[after_plus:], netloc, newpath, query, fragment), + ) + if '://' not in url: assert 'file:' not in url url = url.replace('git+', 'git+ssh://') - url, rev, user_pass = super(Git, self).get_url_rev_and_auth(url) + url, rev, user_pass = super().get_url_rev_and_auth(url) url = url.replace('ssh://', '') else: - url, rev, user_pass = super(Git, self).get_url_rev_and_auth(url) + url, rev, user_pass = super().get_url_rev_and_auth(url) return url, rev, user_pass - def update_submodules(self, location): + @classmethod + def update_submodules(cls, location): + # type: (str) -> None if not os.path.exists(os.path.join(location, '.gitmodules')): return - self.run_command( + cls.run_command( ['submodule', 'update', '--init', '--recursive', '-q'], cwd=location, ) @classmethod - def controls_location(cls, location): - if super(Git, cls).controls_location(location): - return True + def get_repository_root(cls, location): + # type: (str) -> Optional[str] + loc = super().get_repository_root(location) + if loc: + return loc try: - r = cls.run_command(['rev-parse'], - cwd=location, - show_stdout=False, - on_returncode='ignore') - return not r + r = cls.run_command( + ['rev-parse', '--show-toplevel'], + cwd=location, + show_stdout=False, + stdout_only=True, + on_returncode='raise', + log_failed_cmd=False, + ) except BadCommand: logger.debug("could not determine if %s is under git control " "because git is not available", location) - return False + return None + except InstallationError: + return None + return os.path.normpath(r.rstrip('\r\n')) + + @staticmethod + def should_add_vcs_url_prefix(repo_url): + # type: (str) -> bool + """In either https or ssh form, requirements must be prefixed with git+. + """ + return True vcs.register(Git) diff --git a/pipenv/patched/notpip/_internal/vcs/mercurial.py b/pipenv/patched/notpip/_internal/vcs/mercurial.py index 37a3f7c2..f108780b 100644 --- a/pipenv/patched/notpip/_internal/vcs/mercurial.py +++ b/pipenv/patched/notpip/_internal/vcs/mercurial.py @@ -1,14 +1,18 @@ -from __future__ import absolute_import - +import configparser import logging import os +from typing import List, Optional -from pipenv.patched.notpip._vendor.six.moves import configparser - -from pipenv.patched.notpip._internal.download import path_to_url -from pipenv.patched.notpip._internal.utils.misc import display_path, make_vcs_requirement_url -from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory -from pipenv.patched.notpip._internal.vcs import VersionControl, vcs +from pipenv.patched.notpip._internal.exceptions import BadCommand, InstallationError +from pipenv.patched.notpip._internal.utils.misc import HiddenText, display_path +from pipenv.patched.notpip._internal.utils.subprocess import make_command +from pipenv.patched.notpip._internal.utils.urls import path_to_url +from pipenv.patched.notpip._internal.vcs.versioncontrol import ( + RevOptions, + VersionControl, + find_path_to_project_root_from_repo_root, + vcs, +) logger = logging.getLogger(__name__) @@ -17,21 +21,17 @@ class Mercurial(VersionControl): name = 'hg' dirname = '.hg' repo_name = 'clone' - schemes = ('hg', 'hg+http', 'hg+https', 'hg+ssh', 'hg+static-http') + schemes = ( + 'hg+file', 'hg+http', 'hg+https', 'hg+ssh', 'hg+static-http', + ) - def get_base_rev_args(self, rev): + @staticmethod + def get_base_rev_args(rev): + # type: (str) -> List[str] return [rev] - def export(self, location): - """Export the Hg repository at the url to the destination location""" - with TempDirectory(kind="export") as temp_dir: - self.unpack(temp_dir.path) - - self.run_command( - ['archive', location], show_stdout=False, cwd=temp_dir.path - ) - def fetch_new(self, dest, url, rev_options): + # type: (str, HiddenText, RevOptions) -> None rev_display = rev_options.to_display() logger.info( 'Cloning hg %s%s to %s', @@ -39,16 +39,19 @@ class Mercurial(VersionControl): rev_display, display_path(dest), ) - self.run_command(['clone', '--noupdate', '-q', url, dest]) - cmd_args = ['update', '-q'] + rev_options.to_args() - self.run_command(cmd_args, cwd=dest) + self.run_command(make_command('clone', '--noupdate', '-q', url, dest)) + self.run_command( + make_command('update', '-q', rev_options.to_args()), + cwd=dest, + ) def switch(self, dest, url, rev_options): + # type: (str, HiddenText, RevOptions) -> None repo_config = os.path.join(dest, self.dirname, 'hgrc') - config = configparser.SafeConfigParser() + config = configparser.RawConfigParser() try: config.read(repo_config) - config.set('paths', 'default', url) + config.set('paths', 'default', url.secret) with open(repo_config, 'w') as config_file: config.write(config_file) except (OSError, configparser.NoSectionError) as exc: @@ -56,48 +59,100 @@ class Mercurial(VersionControl): 'Could not switch Mercurial repository to %s: %s', url, exc, ) else: - cmd_args = ['update', '-q'] + rev_options.to_args() + cmd_args = make_command('update', '-q', rev_options.to_args()) self.run_command(cmd_args, cwd=dest) def update(self, dest, url, rev_options): + # type: (str, HiddenText, RevOptions) -> None self.run_command(['pull', '-q'], cwd=dest) - cmd_args = ['update', '-q'] + rev_options.to_args() + cmd_args = make_command('update', '-q', rev_options.to_args()) self.run_command(cmd_args, cwd=dest) @classmethod def get_remote_url(cls, location): + # type: (str) -> str url = cls.run_command( ['showconfig', 'paths.default'], - show_stdout=False, cwd=location).strip() + show_stdout=False, + stdout_only=True, + cwd=location, + ).strip() if cls._is_local_repository(url): url = path_to_url(url) return url.strip() @classmethod def get_revision(cls, location): + # type: (str) -> str + """ + Return the repository-local changeset revision number, as an integer. + """ current_revision = cls.run_command( ['parents', '--template={rev}'], - show_stdout=False, cwd=location).strip() + show_stdout=False, + stdout_only=True, + cwd=location, + ).strip() return current_revision @classmethod - def get_revision_hash(cls, location): + def get_requirement_revision(cls, location): + # type: (str) -> str + """ + Return the changeset identification hash, as a 40-character + hexadecimal string + """ current_rev_hash = cls.run_command( ['parents', '--template={node}'], - show_stdout=False, cwd=location).strip() + show_stdout=False, + stdout_only=True, + cwd=location, + ).strip() return current_rev_hash @classmethod - def get_src_requirement(cls, location, project_name): - repo = cls.get_remote_url(location) - if not repo.lower().startswith('hg:'): - repo = 'hg+' + repo - current_rev_hash = cls.get_revision_hash(location) - return make_vcs_requirement_url(repo, current_rev_hash, project_name) - - def is_commit_id_equal(self, dest, name): + def is_commit_id_equal(cls, dest, name): + # type: (str, Optional[str]) -> bool """Always assume the versions don't match""" return False + @classmethod + def get_subdirectory(cls, location): + # type: (str) -> Optional[str] + """ + Return the path to Python project root, relative to the repo root. + Return None if the project root is in the repo root. + """ + # find the repo root + repo_root = cls.run_command( + ['root'], show_stdout=False, stdout_only=True, cwd=location + ).strip() + if not os.path.isabs(repo_root): + repo_root = os.path.abspath(os.path.join(location, repo_root)) + return find_path_to_project_root_from_repo_root(location, repo_root) + + @classmethod + def get_repository_root(cls, location): + # type: (str) -> Optional[str] + loc = super().get_repository_root(location) + if loc: + return loc + try: + r = cls.run_command( + ['root'], + cwd=location, + show_stdout=False, + stdout_only=True, + on_returncode='raise', + log_failed_cmd=False, + ) + except BadCommand: + logger.debug("could not determine if %s is under hg control " + "because hg is not available", location) + return None + except InstallationError: + return None + return os.path.normpath(r.rstrip('\r\n')) + vcs.register(Mercurial) diff --git a/pipenv/patched/notpip/_internal/vcs/subversion.py b/pipenv/patched/notpip/_internal/vcs/subversion.py index e62d3def..11bb41ac 100644 --- a/pipenv/patched/notpip/_internal/vcs/subversion.py +++ b/pipenv/patched/notpip/_internal/vcs/subversion.py @@ -1,14 +1,25 @@ -from __future__ import absolute_import - import logging import os import re +from typing import List, Optional, Tuple -from pipenv.patched.notpip._internal.utils.logging import indent_log from pipenv.patched.notpip._internal.utils.misc import ( - display_path, make_vcs_requirement_url, rmtree, split_auth_from_netloc, + HiddenText, + display_path, + is_console_interactive, + is_installable_dir, + split_auth_from_netloc, ) -from pipenv.patched.notpip._internal.vcs import VersionControl, vcs +from pipenv.patched.notpip._internal.utils.subprocess import CommandArgs, make_command +from pipenv.patched.notpip._internal.vcs.versioncontrol import ( + AuthInfo, + RemoteNotFoundError, + RevOptions, + VersionControl, + vcs, +) + +logger = logging.getLogger(__name__) _svn_xml_url_re = re.compile('url="([^"]+)"') _svn_rev_re = re.compile(r'committed-rev="(\d+)"') @@ -16,59 +27,34 @@ _svn_info_xml_rev_re = re.compile(r'\s*revision="(\d+)"') _svn_info_xml_url_re = re.compile(r'<url>(.*)</url>') -logger = logging.getLogger(__name__) - - class Subversion(VersionControl): name = 'svn' dirname = '.svn' repo_name = 'checkout' - schemes = ('svn', 'svn+ssh', 'svn+http', 'svn+https', 'svn+svn') + schemes = ( + 'svn+ssh', 'svn+http', 'svn+https', 'svn+svn', 'svn+file' + ) - def get_base_rev_args(self, rev): + @classmethod + def should_add_vcs_url_prefix(cls, remote_url): + # type: (str) -> bool + return True + + @staticmethod + def get_base_rev_args(rev): + # type: (str) -> List[str] return ['-r', rev] - def export(self, location): - """Export the svn repository at the url to the destination location""" - url, rev_options = self.get_url_rev_options(self.url) - - logger.info('Exporting svn repository %s to %s', url, location) - with indent_log(): - if os.path.exists(location): - # Subversion doesn't like to check out over an existing - # directory --force fixes this, but was only added in svn 1.5 - rmtree(location) - cmd_args = ['export'] + rev_options.to_args() + [url, location] - self.run_command(cmd_args, show_stdout=False) - - def fetch_new(self, dest, url, rev_options): - rev_display = rev_options.to_display() - logger.info( - 'Checking out %s%s to %s', - url, - rev_display, - display_path(dest), - ) - cmd_args = ['checkout', '-q'] + rev_options.to_args() + [url, dest] - self.run_command(cmd_args) - - def switch(self, dest, url, rev_options): - cmd_args = ['switch'] + rev_options.to_args() + [url, dest] - self.run_command(cmd_args) - - def update(self, dest, url, rev_options): - cmd_args = ['update'] + rev_options.to_args() + [dest] - self.run_command(cmd_args) - @classmethod def get_revision(cls, location): + # type: (str) -> str """ Return the maximum revision for all files under a given location """ # Note: taken from setuptools.command.egg_info revision = 0 - for base, dirs, files in os.walk(location): + for base, dirs, _ in os.walk(location): if cls.dirname not in dirs: dirs[:] = [] continue # no sense walking uncontrolled subdirs @@ -81,14 +67,17 @@ class Subversion(VersionControl): dirurl, localrev = cls._get_svn_url_rev(base) if base == location: + assert dirurl is not None base = dirurl + '/' # save the root url elif not dirurl or not dirurl.startswith(base): dirs[:] = [] continue # not part of the same svn tree, skip it revision = max(revision, localrev) - return revision + return str(revision) - def get_netloc_and_auth(self, netloc, scheme): + @classmethod + def get_netloc_and_auth(cls, netloc, scheme): + # type: (str, str) -> Tuple[str, Tuple[Optional[str], Optional[str]]] """ This override allows the auth information to be passed to svn via the --username and --password options instead of via the URL. @@ -96,20 +85,23 @@ class Subversion(VersionControl): if scheme == 'ssh': # The --username and --password options can't be used for # svn+ssh URLs, so keep the auth information in the URL. - return super(Subversion, self).get_netloc_and_auth( - netloc, scheme) + return super().get_netloc_and_auth(netloc, scheme) return split_auth_from_netloc(netloc) - def get_url_rev_and_auth(self, url): + @classmethod + def get_url_rev_and_auth(cls, url): + # type: (str) -> Tuple[str, Optional[str], AuthInfo] # hotfix the URL scheme after removing svn+ from svn+ssh:// readd it - url, rev, user_pass = super(Subversion, self).get_url_rev_and_auth(url) + url, rev, user_pass = super().get_url_rev_and_auth(url) if url.startswith('ssh://'): url = 'svn+' + url return url, rev, user_pass - def make_rev_args(self, username, password): - extra_args = [] + @staticmethod + def make_rev_args(username, password): + # type: (Optional[str], Optional[HiddenText]) -> CommandArgs + extra_args = [] # type: CommandArgs if username: extra_args += ['--username', username] if password: @@ -119,27 +111,32 @@ class Subversion(VersionControl): @classmethod def get_remote_url(cls, location): - # In cases where the source is in a subdirectory, not alongside - # setup.py we have to look up in the location until we find a real - # setup.py + # type: (str) -> str + # In cases where the source is in a subdirectory, we have to look up in + # the location until we find a valid project root. orig_location = location - while not os.path.exists(os.path.join(location, 'setup.py')): + while not is_installable_dir(location): last_location = location location = os.path.dirname(location) if location == last_location: # We've traversed up to the root of the filesystem without - # finding setup.py + # finding a Python project. logger.warning( - "Could not find setup.py for directory %s (tried all " + "Could not find Python project for directory %s (tried all " "parent directories)", orig_location, ) - return None + raise RemoteNotFoundError - return cls._get_svn_url_rev(location)[0] + url, _rev = cls._get_svn_url_rev(location) + if url is None: + raise RemoteNotFoundError + + return url @classmethod def _get_svn_url_rev(cls, location): + # type: (str) -> Tuple[Optional[str], int] from pipenv.patched.notpip._internal.exceptions import InstallationError entries_path = os.path.join(location, cls.dirname, 'entries') @@ -149,27 +146,36 @@ class Subversion(VersionControl): else: # subversion >= 1.7 does not have the 'entries' file data = '' + url = None if (data.startswith('8') or data.startswith('9') or data.startswith('10')): - data = list(map(str.splitlines, data.split('\n\x0c\n'))) - del data[0][0] # get rid of the '8' - url = data[0][3] - revs = [int(d[9]) for d in data if len(d) > 9 and d[9]] + [0] + entries = list(map(str.splitlines, data.split('\n\x0c\n'))) + del entries[0][0] # get rid of the '8' + url = entries[0][3] + revs = [int(d[9]) for d in entries if len(d) > 9 and d[9]] + [0] elif data.startswith('<?xml'): match = _svn_xml_url_re.search(data) if not match: - raise ValueError('Badly formatted data: %r' % data) + raise ValueError(f'Badly formatted data: {data!r}') url = match.group(1) # get repository URL revs = [int(m.group(1)) for m in _svn_rev_re.finditer(data)] + [0] else: try: # subversion >= 1.7 + # Note that using get_remote_call_options is not necessary here + # because `svn info` is being run against a local directory. + # We don't need to worry about making sure interactive mode + # is being used to prompt for passwords, because passwords + # are only potentially needed for remote server requests. xml = cls.run_command( ['info', '--xml', location], show_stdout=False, + stdout_only=True, ) - url = _svn_info_xml_url_re.search(xml).group(1) + match = _svn_info_xml_url_re.search(xml) + assert match is not None + url = match.group(1) revs = [ int(m.group(1)) for m in _svn_info_xml_rev_re.finditer(xml) ] @@ -184,17 +190,140 @@ class Subversion(VersionControl): return url, rev @classmethod - def get_src_requirement(cls, location, project_name): - repo = cls.get_remote_url(location) - if repo is None: - return None - repo = 'svn+' + repo - rev = cls.get_revision(location) - return make_vcs_requirement_url(repo, rev, project_name) - - def is_commit_id_equal(self, dest, name): + def is_commit_id_equal(cls, dest, name): + # type: (str, Optional[str]) -> bool """Always assume the versions don't match""" return False + def __init__(self, use_interactive=None): + # type: (bool) -> None + if use_interactive is None: + use_interactive = is_console_interactive() + self.use_interactive = use_interactive + + # This member is used to cache the fetched version of the current + # ``svn`` client. + # Special value definitions: + # None: Not evaluated yet. + # Empty tuple: Could not parse version. + self._vcs_version = None # type: Optional[Tuple[int, ...]] + + super().__init__() + + def call_vcs_version(self): + # type: () -> Tuple[int, ...] + """Query the version of the currently installed Subversion client. + + :return: A tuple containing the parts of the version information or + ``()`` if the version returned from ``svn`` could not be parsed. + :raises: BadCommand: If ``svn`` is not installed. + """ + # Example versions: + # svn, version 1.10.3 (r1842928) + # compiled Feb 25 2019, 14:20:39 on x86_64-apple-darwin17.0.0 + # svn, version 1.7.14 (r1542130) + # compiled Mar 28 2018, 08:49:13 on x86_64-pc-linux-gnu + # svn, version 1.12.0-SlikSvn (SlikSvn/1.12.0) + # compiled May 28 2019, 13:44:56 on x86_64-microsoft-windows6.2 + version_prefix = 'svn, version ' + version = self.run_command( + ['--version'], show_stdout=False, stdout_only=True + ) + if not version.startswith(version_prefix): + return () + + version = version[len(version_prefix):].split()[0] + version_list = version.partition('-')[0].split('.') + try: + parsed_version = tuple(map(int, version_list)) + except ValueError: + return () + + return parsed_version + + def get_vcs_version(self): + # type: () -> Tuple[int, ...] + """Return the version of the currently installed Subversion client. + + If the version of the Subversion client has already been queried, + a cached value will be used. + + :return: A tuple containing the parts of the version information or + ``()`` if the version returned from ``svn`` could not be parsed. + :raises: BadCommand: If ``svn`` is not installed. + """ + if self._vcs_version is not None: + # Use cached version, if available. + # If parsing the version failed previously (empty tuple), + # do not attempt to parse it again. + return self._vcs_version + + vcs_version = self.call_vcs_version() + self._vcs_version = vcs_version + return vcs_version + + def get_remote_call_options(self): + # type: () -> CommandArgs + """Return options to be used on calls to Subversion that contact the server. + + These options are applicable for the following ``svn`` subcommands used + in this class. + + - checkout + - switch + - update + + :return: A list of command line arguments to pass to ``svn``. + """ + if not self.use_interactive: + # --non-interactive switch is available since Subversion 0.14.4. + # Subversion < 1.8 runs in interactive mode by default. + return ['--non-interactive'] + + svn_version = self.get_vcs_version() + # By default, Subversion >= 1.8 runs in non-interactive mode if + # stdin is not a TTY. Since that is how pip invokes SVN, in + # call_subprocess(), pip must pass --force-interactive to ensure + # the user can be prompted for a password, if required. + # SVN added the --force-interactive option in SVN 1.8. Since + # e.g. RHEL/CentOS 7, which is supported until 2024, ships with + # SVN 1.7, pip should continue to support SVN 1.7. Therefore, pip + # can't safely add the option if the SVN version is < 1.8 (or unknown). + if svn_version >= (1, 8): + return ['--force-interactive'] + + return [] + + def fetch_new(self, dest, url, rev_options): + # type: (str, HiddenText, RevOptions) -> None + rev_display = rev_options.to_display() + logger.info( + 'Checking out %s%s to %s', + url, + rev_display, + display_path(dest), + ) + cmd_args = make_command( + 'checkout', '-q', self.get_remote_call_options(), + rev_options.to_args(), url, dest, + ) + self.run_command(cmd_args) + + def switch(self, dest, url, rev_options): + # type: (str, HiddenText, RevOptions) -> None + cmd_args = make_command( + 'switch', self.get_remote_call_options(), rev_options.to_args(), + url, dest, + ) + self.run_command(cmd_args) + + def update(self, dest, url, rev_options): + # type: (str, HiddenText, RevOptions) -> None + cmd_args = make_command( + 'update', self.get_remote_call_options(), rev_options.to_args(), + dest, + ) + self.run_command(cmd_args) + vcs.register(Subversion) diff --git a/pipenv/patched/notpip/_internal/vcs/versioncontrol.py b/pipenv/patched/notpip/_internal/vcs/versioncontrol.py new file mode 100644 index 00000000..9355848e --- /dev/null +++ b/pipenv/patched/notpip/_internal/vcs/versioncontrol.py @@ -0,0 +1,722 @@ +"""Handles all VCS (version control) support""" + +import logging +import os +import shutil +import sys +import urllib.parse +from typing import ( + Any, + Dict, + Iterable, + Iterator, + List, + Mapping, + Optional, + Tuple, + Type, + Union, +) + +from pipenv.patched.notpip._internal.cli.spinners import SpinnerInterface +from pipenv.patched.notpip._internal.exceptions import BadCommand, InstallationError +from pipenv.patched.notpip._internal.utils.misc import ( + HiddenText, + ask_path_exists, + backup_dir, + display_path, + hide_url, + hide_value, + is_installable_dir, + rmtree, +) +from pipenv.patched.notpip._internal.utils.subprocess import CommandArgs, call_subprocess, make_command +from pipenv.patched.notpip._internal.utils.urls import get_url_scheme + +__all__ = ['vcs'] + + +logger = logging.getLogger(__name__) + +AuthInfo = Tuple[Optional[str], Optional[str]] + + +def is_url(name): + # type: (str) -> bool + """ + Return true if the name looks like a URL. + """ + scheme = get_url_scheme(name) + if scheme is None: + return False + return scheme in ['http', 'https', 'file', 'ftp'] + vcs.all_schemes + + +def make_vcs_requirement_url(repo_url, rev, project_name, subdir=None): + # type: (str, str, str, Optional[str]) -> str + """ + Return the URL for a VCS requirement. + + Args: + repo_url: the remote VCS url, with any needed VCS prefix (e.g. "git+"). + project_name: the (unescaped) project name. + """ + egg_project_name = project_name.replace("-", "_") + req = f'{repo_url}@{rev}#egg={egg_project_name}' + if subdir: + req += f'&subdirectory={subdir}' + + return req + + +def find_path_to_project_root_from_repo_root(location, repo_root): + # type: (str, str) -> Optional[str] + """ + Find the the Python project's root by searching up the filesystem from + `location`. Return the path to project root relative to `repo_root`. + Return None if the project root is `repo_root`, or cannot be found. + """ + # find project root. + orig_location = location + while not is_installable_dir(location): + last_location = location + location = os.path.dirname(location) + if location == last_location: + # We've traversed up to the root of the filesystem without + # finding a Python project. + logger.warning( + "Could not find a Python project for directory %s (tried all " + "parent directories)", + orig_location, + ) + return None + + if os.path.samefile(repo_root, location): + return None + + return os.path.relpath(location, repo_root) + + +class RemoteNotFoundError(Exception): + pass + + +class RemoteNotValidError(Exception): + def __init__(self, url: str): + super().__init__(url) + self.url = url + + +class RevOptions: + + """ + Encapsulates a VCS-specific revision to install, along with any VCS + install options. + + Instances of this class should be treated as if immutable. + """ + + def __init__( + self, + vc_class, # type: Type[VersionControl] + rev=None, # type: Optional[str] + extra_args=None, # type: Optional[CommandArgs] + ): + # type: (...) -> None + """ + Args: + vc_class: a VersionControl subclass. + rev: the name of the revision to install. + extra_args: a list of extra options. + """ + if extra_args is None: + extra_args = [] + + self.extra_args = extra_args + self.rev = rev + self.vc_class = vc_class + self.branch_name = None # type: Optional[str] + + def __repr__(self): + # type: () -> str + return f'<RevOptions {self.vc_class.name}: rev={self.rev!r}>' + + @property + def arg_rev(self): + # type: () -> Optional[str] + if self.rev is None: + return self.vc_class.default_arg_rev + + return self.rev + + def to_args(self): + # type: () -> CommandArgs + """ + Return the VCS-specific command arguments. + """ + args = [] # type: CommandArgs + rev = self.arg_rev + if rev is not None: + args += self.vc_class.get_base_rev_args(rev) + args += self.extra_args + + return args + + def to_display(self): + # type: () -> str + if not self.rev: + return '' + + return f' (to revision {self.rev})' + + def make_new(self, rev): + # type: (str) -> RevOptions + """ + Make a copy of the current instance, but with a new rev. + + Args: + rev: the name of the revision for the new object. + """ + return self.vc_class.make_rev_options(rev, extra_args=self.extra_args) + + +class VcsSupport: + _registry = {} # type: Dict[str, VersionControl] + schemes = ['ssh', 'git', 'hg', 'bzr', 'sftp', 'svn'] + + def __init__(self): + # type: () -> None + # Register more schemes with urlparse for various version control + # systems + urllib.parse.uses_netloc.extend(self.schemes) + super().__init__() + + def __iter__(self): + # type: () -> Iterator[str] + return self._registry.__iter__() + + @property + def backends(self): + # type: () -> List[VersionControl] + return list(self._registry.values()) + + @property + def dirnames(self): + # type: () -> List[str] + return [backend.dirname for backend in self.backends] + + @property + def all_schemes(self): + # type: () -> List[str] + schemes = [] # type: List[str] + for backend in self.backends: + schemes.extend(backend.schemes) + return schemes + + def register(self, cls): + # type: (Type[VersionControl]) -> None + if not hasattr(cls, 'name'): + logger.warning('Cannot register VCS %s', cls.__name__) + return + if cls.name not in self._registry: + self._registry[cls.name] = cls() + logger.debug('Registered VCS backend: %s', cls.name) + + def unregister(self, name): + # type: (str) -> None + if name in self._registry: + del self._registry[name] + + def get_backend_for_dir(self, location): + # type: (str) -> Optional[VersionControl] + """ + Return a VersionControl object if a repository of that type is found + at the given directory. + """ + vcs_backends = {} + for vcs_backend in self._registry.values(): + repo_path = vcs_backend.get_repository_root(location) + if not repo_path: + continue + logger.debug('Determine that %s uses VCS: %s', + location, vcs_backend.name) + vcs_backends[repo_path] = vcs_backend + + if not vcs_backends: + return None + + # Choose the VCS in the inner-most directory. Since all repository + # roots found here would be either `location` or one of its + # parents, the longest path should have the most path components, + # i.e. the backend representing the inner-most repository. + inner_most_repo_path = max(vcs_backends, key=len) + return vcs_backends[inner_most_repo_path] + + def get_backend_for_scheme(self, scheme): + # type: (str) -> Optional[VersionControl] + """ + Return a VersionControl object or None. + """ + for vcs_backend in self._registry.values(): + if scheme in vcs_backend.schemes: + return vcs_backend + return None + + def get_backend(self, name): + # type: (str) -> Optional[VersionControl] + """ + Return a VersionControl object or None. + """ + name = name.lower() + return self._registry.get(name) + + +vcs = VcsSupport() + + +class VersionControl: + name = '' + dirname = '' + repo_name = '' + # List of supported schemes for this Version Control + schemes = () # type: Tuple[str, ...] + # Iterable of environment variable names to pass to call_subprocess(). + unset_environ = () # type: Tuple[str, ...] + default_arg_rev = None # type: Optional[str] + + @classmethod + def should_add_vcs_url_prefix(cls, remote_url): + # type: (str) -> bool + """ + Return whether the vcs prefix (e.g. "git+") should be added to a + repository's remote url when used in a requirement. + """ + return not remote_url.lower().startswith(f'{cls.name}:') + + @classmethod + def get_subdirectory(cls, location): + # type: (str) -> Optional[str] + """ + Return the path to Python project root, relative to the repo root. + Return None if the project root is in the repo root. + """ + return None + + @classmethod + def get_requirement_revision(cls, repo_dir): + # type: (str) -> str + """ + Return the revision string that should be used in a requirement. + """ + return cls.get_revision(repo_dir) + + @classmethod + def get_src_requirement(cls, repo_dir, project_name): + # type: (str, str) -> str + """ + Return the requirement string to use to redownload the files + currently at the given repository directory. + + Args: + project_name: the (unescaped) project name. + + The return value has a form similar to the following: + + {repository_url}@{revision}#egg={project_name} + """ + repo_url = cls.get_remote_url(repo_dir) + + if cls.should_add_vcs_url_prefix(repo_url): + repo_url = f'{cls.name}+{repo_url}' + + revision = cls.get_requirement_revision(repo_dir) + subdir = cls.get_subdirectory(repo_dir) + req = make_vcs_requirement_url(repo_url, revision, project_name, + subdir=subdir) + + return req + + @staticmethod + def get_base_rev_args(rev): + # type: (str) -> List[str] + """ + Return the base revision arguments for a vcs command. + + Args: + rev: the name of a revision to install. Cannot be None. + """ + raise NotImplementedError + + def is_immutable_rev_checkout(self, url, dest): + # type: (str, str) -> bool + """ + Return true if the commit hash checked out at dest matches + the revision in url. + + Always return False, if the VCS does not support immutable commit + hashes. + + This method does not check if there are local uncommitted changes + in dest after checkout, as pip currently has no use case for that. + """ + return False + + @classmethod + def make_rev_options(cls, rev=None, extra_args=None): + # type: (Optional[str], Optional[CommandArgs]) -> RevOptions + """ + Return a RevOptions object. + + Args: + rev: the name of a revision to install. + extra_args: a list of extra options. + """ + return RevOptions(cls, rev, extra_args=extra_args) + + @classmethod + def _is_local_repository(cls, repo): + # type: (str) -> bool + """ + posix absolute paths start with os.path.sep, + win32 ones start with drive (like c:\\folder) + """ + drive, tail = os.path.splitdrive(repo) + return repo.startswith(os.path.sep) or bool(drive) + + @classmethod + def get_netloc_and_auth(cls, netloc, scheme): + # type: (str, str) -> Tuple[str, Tuple[Optional[str], Optional[str]]] + """ + Parse the repository URL's netloc, and return the new netloc to use + along with auth information. + + Args: + netloc: the original repository URL netloc. + scheme: the repository URL's scheme without the vcs prefix. + + This is mainly for the Subversion class to override, so that auth + information can be provided via the --username and --password options + instead of through the URL. For other subclasses like Git without + such an option, auth information must stay in the URL. + + Returns: (netloc, (username, password)). + """ + return netloc, (None, None) + + @classmethod + def get_url_rev_and_auth(cls, url): + # type: (str) -> Tuple[str, Optional[str], AuthInfo] + """ + Parse the repository URL to use, and return the URL, revision, + and auth info to use. + + Returns: (url, rev, (username, password)). + """ + scheme, netloc, path, query, frag = urllib.parse.urlsplit(url) + if '+' not in scheme: + raise ValueError( + "Sorry, {!r} is a malformed VCS url. " + "The format is <vcs>+<protocol>://<url>, " + "e.g. svn+http://myrepo/svn/MyApp#egg=MyApp".format(url) + ) + # Remove the vcs prefix. + scheme = scheme.split('+', 1)[1] + netloc, user_pass = cls.get_netloc_and_auth(netloc, scheme) + rev = None + if '@' in path: + path, rev = path.rsplit('@', 1) + if not rev: + raise InstallationError( + "The URL {!r} has an empty revision (after @) " + "which is not supported. Include a revision after @ " + "or remove @ from the URL.".format(url) + ) + url = urllib.parse.urlunsplit((scheme, netloc, path, query, '')) + return url, rev, user_pass + + @staticmethod + def make_rev_args(username, password): + # type: (Optional[str], Optional[HiddenText]) -> CommandArgs + """ + Return the RevOptions "extra arguments" to use in obtain(). + """ + return [] + + def get_url_rev_options(self, url): + # type: (HiddenText) -> Tuple[HiddenText, RevOptions] + """ + Return the URL and RevOptions object to use in obtain(), + as a tuple (url, rev_options). + """ + secret_url, rev, user_pass = self.get_url_rev_and_auth(url.secret) + username, secret_password = user_pass + password = None # type: Optional[HiddenText] + if secret_password is not None: + password = hide_value(secret_password) + extra_args = self.make_rev_args(username, password) + rev_options = self.make_rev_options(rev, extra_args=extra_args) + + return hide_url(secret_url), rev_options + + @staticmethod + def normalize_url(url): + # type: (str) -> str + """ + Normalize a URL for comparison by unquoting it and removing any + trailing slash. + """ + return urllib.parse.unquote(url).rstrip('/') + + @classmethod + def compare_urls(cls, url1, url2): + # type: (str, str) -> bool + """ + Compare two repo URLs for identity, ignoring incidental differences. + """ + return (cls.normalize_url(url1) == cls.normalize_url(url2)) + + def fetch_new(self, dest, url, rev_options): + # type: (str, HiddenText, RevOptions) -> None + """ + Fetch a revision from a repository, in the case that this is the + first fetch from the repository. + + Args: + dest: the directory to fetch the repository to. + rev_options: a RevOptions object. + """ + raise NotImplementedError + + def switch(self, dest, url, rev_options): + # type: (str, HiddenText, RevOptions) -> None + """ + Switch the repo at ``dest`` to point to ``URL``. + + Args: + rev_options: a RevOptions object. + """ + raise NotImplementedError + + def update(self, dest, url, rev_options): + # type: (str, HiddenText, RevOptions) -> None + """ + Update an already-existing repo to the given ``rev_options``. + + Args: + rev_options: a RevOptions object. + """ + raise NotImplementedError + + @classmethod + def is_commit_id_equal(cls, dest, name): + # type: (str, Optional[str]) -> bool + """ + Return whether the id of the current commit equals the given name. + + Args: + dest: the repository directory. + name: a string name. + """ + raise NotImplementedError + + def obtain(self, dest, url): + # type: (str, HiddenText) -> None + """ + Install or update in editable mode the package represented by this + VersionControl object. + + :param dest: the repository directory in which to install or update. + :param url: the repository URL starting with a vcs prefix. + """ + url, rev_options = self.get_url_rev_options(url) + + if not os.path.exists(dest): + self.fetch_new(dest, url, rev_options) + return + + rev_display = rev_options.to_display() + if self.is_repository_directory(dest): + existing_url = self.get_remote_url(dest) + if self.compare_urls(existing_url, url.secret): + logger.debug( + '%s in %s exists, and has correct URL (%s)', + self.repo_name.title(), + display_path(dest), + url, + ) + if not self.is_commit_id_equal(dest, rev_options.rev): + logger.info( + 'Updating %s %s%s', + display_path(dest), + self.repo_name, + rev_display, + ) + self.update(dest, url, rev_options) + else: + logger.info('Skipping because already up-to-date.') + return + + logger.warning( + '%s %s in %s exists with URL %s', + self.name, + self.repo_name, + display_path(dest), + existing_url, + ) + prompt = ('(s)witch, (i)gnore, (w)ipe, (b)ackup ', + ('s', 'i', 'w', 'b')) + else: + logger.warning( + 'Directory %s already exists, and is not a %s %s.', + dest, + self.name, + self.repo_name, + ) + # https://github.com/python/mypy/issues/1174 + prompt = ('(i)gnore, (w)ipe, (b)ackup ', # type: ignore + ('i', 'w', 'b')) + + logger.warning( + 'The plan is to install the %s repository %s', + self.name, + url, + ) + response = ask_path_exists('What to do? {}'.format( + prompt[0]), prompt[1]) + + if response == 'a': + sys.exit(-1) + + if response == 'w': + logger.warning('Deleting %s', display_path(dest)) + rmtree(dest) + self.fetch_new(dest, url, rev_options) + return + + if response == 'b': + dest_dir = backup_dir(dest) + logger.warning( + 'Backing up %s to %s', display_path(dest), dest_dir, + ) + shutil.move(dest, dest_dir) + self.fetch_new(dest, url, rev_options) + return + + # Do nothing if the response is "i". + if response == 's': + logger.info( + 'Switching %s %s to %s%s', + self.repo_name, + display_path(dest), + url, + rev_display, + ) + self.switch(dest, url, rev_options) + + def unpack(self, location, url): + # type: (str, HiddenText) -> None + """ + Clean up current location and download the url repository + (and vcs infos) into location + + :param url: the repository URL starting with a vcs prefix. + """ + if os.path.exists(location): + rmtree(location) + self.obtain(location, url=url) + + @classmethod + def get_remote_url(cls, location): + # type: (str) -> str + """ + Return the url used at location + + Raises RemoteNotFoundError if the repository does not have a remote + url configured. + """ + raise NotImplementedError + + @classmethod + def get_revision(cls, location): + # type: (str) -> str + """ + Return the current commit id of the files at the given location. + """ + raise NotImplementedError + + @classmethod + def run_command( + cls, + cmd, # type: Union[List[str], CommandArgs] + show_stdout=True, # type: bool + cwd=None, # type: Optional[str] + on_returncode='raise', # type: str + extra_ok_returncodes=None, # type: Optional[Iterable[int]] + command_desc=None, # type: Optional[str] + extra_environ=None, # type: Optional[Mapping[str, Any]] + spinner=None, # type: Optional[SpinnerInterface] + log_failed_cmd=True, # type: bool + stdout_only=False, # type: bool + ): + # type: (...) -> str + """ + Run a VCS subcommand + This is simply a wrapper around call_subprocess that adds the VCS + command name, and checks that the VCS is available + """ + cmd = make_command(cls.name, *cmd) + try: + return call_subprocess(cmd, show_stdout, cwd, + on_returncode=on_returncode, + extra_ok_returncodes=extra_ok_returncodes, + command_desc=command_desc, + extra_environ=extra_environ, + unset_environ=cls.unset_environ, + spinner=spinner, + log_failed_cmd=log_failed_cmd, + stdout_only=stdout_only) + except FileNotFoundError: + # errno.ENOENT = no such file or directory + # In other words, the VCS executable isn't available + raise BadCommand( + f'Cannot find command {cls.name!r} - do you have ' + f'{cls.name!r} installed and in your PATH?') + except PermissionError: + # errno.EACCES = Permission denied + # This error occurs, for instance, when the command is installed + # only for another user. So, the current user don't have + # permission to call the other user command. + raise BadCommand( + f"No permission to execute {cls.name!r} - install it " + f"locally, globally (ask admin), or check your PATH. " + f"See possible solutions at " + f"https://pip.pypa.io/en/latest/reference/pip_freeze/" + f"#fixing-permission-denied." + ) + + @classmethod + def is_repository_directory(cls, path): + # type: (str) -> bool + """ + Return whether a directory path is a repository directory. + """ + logger.debug('Checking in %s for %s (%s)...', + path, cls.dirname, cls.name) + return os.path.exists(os.path.join(path, cls.dirname)) + + @classmethod + def get_repository_root(cls, location): + # type: (str) -> Optional[str] + """ + Return the "root" (top-level) directory controlled by the vcs, + or `None` if the directory is not in any. + + It is meant to be overridden to implement smarter detection + mechanisms for specific vcs. + + This can do more than is_repository_directory() alone. For + example, the Git override checks that Git is actually available. + """ + if cls.is_repository_directory(location): + return location + return None diff --git a/pipenv/patched/notpip/_internal/wheel.py b/pipenv/patched/notpip/_internal/wheel.py deleted file mode 100644 index 06c880e8..00000000 --- a/pipenv/patched/notpip/_internal/wheel.py +++ /dev/null @@ -1,1097 +0,0 @@ -""" -Support for installing and building the "wheel" binary package format. -""" -from __future__ import absolute_import - -import collections -import compileall -import csv -import hashlib -import logging -import os.path -import re -import shutil -import stat -import sys -import warnings -from base64 import urlsafe_b64encode -from email.parser import Parser - -from pipenv.patched.notpip._vendor import pkg_resources -from pipenv.patched.notpip._vendor.distlib.scripts import ScriptMaker -from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name -from pipenv.patched.notpip._vendor.six import StringIO - -from pipenv.patched.notpip._internal import pep425tags -from pipenv.patched.notpip._internal.download import path_to_url, unpack_url -from pipenv.patched.notpip._internal.exceptions import ( - InstallationError, InvalidWheelFilename, UnsupportedWheel, -) -from pipenv.patched.notpip._internal.locations import ( - PIP_DELETE_MARKER_FILENAME, distutils_scheme, -) -from pipenv.patched.notpip._internal.models.link import Link -from pipenv.patched.notpip._internal.utils.logging import indent_log -from pipenv.patched.notpip._internal.utils.misc import ( - call_subprocess, captured_stdout, ensure_dir, read_chunks, -) -from pipenv.patched.notpip._internal.utils.setuptools_build import SETUPTOOLS_SHIM -from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory -from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING -from pipenv.patched.notpip._internal.utils.ui import open_spinner - -if MYPY_CHECK_RUNNING: - from typing import ( # noqa: F401 - Dict, List, Optional, Sequence, Mapping, Tuple, IO, Text, Any, - Union, Iterable - ) - from pipenv.patched.notpip._vendor.packaging.requirements import Requirement # noqa: F401 - from pipenv.patched.notpip._internal.req.req_install import InstallRequirement # noqa: F401 - from pipenv.patched.notpip._internal.download import PipSession # noqa: F401 - from pipenv.patched.notpip._internal.index import FormatControl, PackageFinder # noqa: F401 - from pipenv.patched.notpip._internal.operations.prepare import ( # noqa: F401 - RequirementPreparer - ) - from pipenv.patched.notpip._internal.cache import WheelCache # noqa: F401 - from pipenv.patched.notpip._internal.pep425tags import Pep425Tag # noqa: F401 - - InstalledCSVRow = Tuple[str, ...] - - -VERSION_COMPATIBLE = (1, 0) - - -logger = logging.getLogger(__name__) - - -def normpath(src, p): - return os.path.relpath(src, p).replace(os.path.sep, '/') - - -def rehash(path, blocksize=1 << 20): - # type: (str, int) -> Tuple[str, str] - """Return (hash, length) for path using hashlib.sha256()""" - h = hashlib.sha256() - length = 0 - with open(path, 'rb') as f: - for block in read_chunks(f, size=blocksize): - length += len(block) - h.update(block) - digest = 'sha256=' + urlsafe_b64encode( - h.digest() - ).decode('latin1').rstrip('=') - # unicode/str python2 issues - return (digest, str(length)) # type: ignore - - -def open_for_csv(name, mode): - # type: (str, Text) -> IO - if sys.version_info[0] < 3: - nl = {} # type: Dict[str, Any] - bin = 'b' - else: - nl = {'newline': ''} # type: Dict[str, Any] - bin = '' - return open(name, mode + bin, **nl) - - -def replace_python_tag(wheelname, new_tag): - # type: (str, str) -> str - """Replace the Python tag in a wheel file name with a new value. - """ - parts = wheelname.split('-') - parts[-3] = new_tag - return '-'.join(parts) - - -def fix_script(path): - # type: (str) -> Optional[bool] - """Replace #!python with #!/path/to/python - Return True if file was changed.""" - # XXX RECORD hashes will need to be updated - if os.path.isfile(path): - with open(path, 'rb') as script: - firstline = script.readline() - if not firstline.startswith(b'#!python'): - return False - exename = os.environ.get('PIP_PYTHON_PATH', sys.executable).encode(sys.getfilesystemencoding()) - firstline = b'#!' + exename + os.linesep.encode("ascii") - rest = script.read() - with open(path, 'wb') as script: - script.write(firstline) - script.write(rest) - return True - return None - - -dist_info_re = re.compile(r"""^(?P<namever>(?P<name>.+?)(-(?P<ver>.+?))?) - \.dist-info$""", re.VERBOSE) - - -def root_is_purelib(name, wheeldir): - # type: (str, str) -> bool - """ - Return True if the extracted wheel in wheeldir should go into purelib. - """ - name_folded = name.replace("-", "_") - for item in os.listdir(wheeldir): - match = dist_info_re.match(item) - if match and match.group('name') == name_folded: - with open(os.path.join(wheeldir, item, 'WHEEL')) as wheel: - for line in wheel: - line = line.lower().rstrip() - if line == "root-is-purelib: true": - return True - return False - - -def get_entrypoints(filename): - # type: (str) -> Tuple[Dict[str, str], Dict[str, str]] - if not os.path.exists(filename): - return {}, {} - - # This is done because you can pass a string to entry_points wrappers which - # means that they may or may not be valid INI files. The attempt here is to - # strip leading and trailing whitespace in order to make them valid INI - # files. - with open(filename) as fp: - data = StringIO() - for line in fp: - data.write(line.strip()) - data.write("\n") - data.seek(0) - - # get the entry points and then the script names - entry_points = pkg_resources.EntryPoint.parse_map(data) - console = entry_points.get('console_scripts', {}) - gui = entry_points.get('gui_scripts', {}) - - def _split_ep(s): - """get the string representation of EntryPoint, remove space and split - on '='""" - return str(s).replace(" ", "").split("=") - - # convert the EntryPoint objects into strings with module:function - console = dict(_split_ep(v) for v in console.values()) - gui = dict(_split_ep(v) for v in gui.values()) - return console, gui - - -def message_about_scripts_not_on_PATH(scripts): - # type: (Sequence[str]) -> Optional[str] - """Determine if any scripts are not on PATH and format a warning. - - Returns a warning message if one or more scripts are not on PATH, - otherwise None. - """ - if not scripts: - return None - - # Group scripts by the path they were installed in - grouped_by_dir = collections.defaultdict(set) # type: Dict[str, set] - for destfile in scripts: - parent_dir = os.path.dirname(destfile) - script_name = os.path.basename(destfile) - grouped_by_dir[parent_dir].add(script_name) - - # We don't want to warn for directories that are on PATH. - not_warn_dirs = [ - os.path.normcase(i).rstrip(os.sep) for i in - os.environ.get("PATH", "").split(os.pathsep) - ] - # If an executable sits with sys.executable, we don't warn for it. - # This covers the case of venv invocations without activating the venv. - executable_loc = os.environ.get("PIP_PYTHON_PATH", sys.executable) - not_warn_dirs.append(os.path.normcase(os.path.dirname(executable_loc))) - warn_for = { - parent_dir: scripts for parent_dir, scripts in grouped_by_dir.items() - if os.path.normcase(parent_dir) not in not_warn_dirs - } - if not warn_for: - return None - - # Format a message - msg_lines = [] - for parent_dir, scripts in warn_for.items(): - scripts = sorted(scripts) - if len(scripts) == 1: - start_text = "script {} is".format(scripts[0]) - else: - start_text = "scripts {} are".format( - ", ".join(scripts[:-1]) + " and " + scripts[-1] - ) - - msg_lines.append( - "The {} installed in '{}' which is not on PATH." - .format(start_text, parent_dir) - ) - - last_line_fmt = ( - "Consider adding {} to PATH or, if you prefer " - "to suppress this warning, use --no-warn-script-location." - ) - if len(msg_lines) == 1: - msg_lines.append(last_line_fmt.format("this directory")) - else: - msg_lines.append(last_line_fmt.format("these directories")) - - # Returns the formatted multiline message - return "\n".join(msg_lines) - - -def sorted_outrows(outrows): - # type: (Iterable[InstalledCSVRow]) -> List[InstalledCSVRow] - """ - Return the given rows of a RECORD file in sorted order. - - Each row is a 3-tuple (path, hash, size) and corresponds to a record of - a RECORD file (see PEP 376 and PEP 427 for details). For the rows - passed to this function, the size can be an integer as an int or string, - or the empty string. - """ - # Normally, there should only be one row per path, in which case the - # second and third elements don't come into play when sorting. - # However, in cases in the wild where a path might happen to occur twice, - # we don't want the sort operation to trigger an error (but still want - # determinism). Since the third element can be an int or string, we - # coerce each element to a string to avoid a TypeError in this case. - # For additional background, see-- - # https://github.com/pypa/pip/issues/5868 - return sorted(outrows, key=lambda row: tuple(str(x) for x in row)) - - -def get_csv_rows_for_installed( - old_csv_rows, # type: Iterable[List[str]] - installed, # type: Dict[str, str] - changed, # type: set - generated, # type: List[str] - lib_dir, # type: str -): - # type: (...) -> List[InstalledCSVRow] - """ - :param installed: A map from archive RECORD path to installation RECORD - path. - """ - installed_rows = [] # type: List[InstalledCSVRow] - for row in old_csv_rows: - if len(row) > 3: - logger.warning( - 'RECORD line has more than three elements: {}'.format(row) - ) - # Make a copy because we are mutating the row. - row = list(row) - old_path = row[0] - new_path = installed.pop(old_path, old_path) - row[0] = new_path - if new_path in changed: - digest, length = rehash(new_path) - row[1] = digest - row[2] = length - installed_rows.append(tuple(row)) - for f in generated: - digest, length = rehash(f) - installed_rows.append((normpath(f, lib_dir), digest, str(length))) - for f in installed: - installed_rows.append((installed[f], '', '')) - return installed_rows - - -def move_wheel_files( - name, # type: str - req, # type: Requirement - wheeldir, # type: str - user=False, # type: bool - home=None, # type: Optional[str] - root=None, # type: Optional[str] - pycompile=True, # type: bool - scheme=None, # type: Optional[Mapping[str, str]] - isolated=False, # type: bool - prefix=None, # type: Optional[str] - warn_script_location=True # type: bool -): - # type: (...) -> None - """Install a wheel""" - # TODO: Investigate and break this up. - # TODO: Look into moving this into a dedicated class for representing an - # installation. - - if not scheme: - scheme = distutils_scheme( - name, user=user, home=home, root=root, isolated=isolated, - prefix=prefix, - ) - - if root_is_purelib(name, wheeldir): - lib_dir = scheme['purelib'] - else: - lib_dir = scheme['platlib'] - - info_dir = [] # type: List[str] - data_dirs = [] - source = wheeldir.rstrip(os.path.sep) + os.path.sep - - # Record details of the files moved - # installed = files copied from the wheel to the destination - # changed = files changed while installing (scripts #! line typically) - # generated = files newly generated during the install (script wrappers) - installed = {} # type: Dict[str, str] - changed = set() - generated = [] # type: List[str] - - # Compile all of the pyc files that we're going to be installing - if pycompile: - with captured_stdout() as stdout: - with warnings.catch_warnings(): - warnings.filterwarnings('ignore') - compileall.compile_dir(source, force=True, quiet=True) - logger.debug(stdout.getvalue()) - - def record_installed(srcfile, destfile, modified=False): - """Map archive RECORD paths to installation RECORD paths.""" - oldpath = normpath(srcfile, wheeldir) - newpath = normpath(destfile, lib_dir) - installed[oldpath] = newpath - if modified: - changed.add(destfile) - - def clobber(source, dest, is_base, fixer=None, filter=None): - ensure_dir(dest) # common for the 'include' path - - for dir, subdirs, files in os.walk(source): - basedir = dir[len(source):].lstrip(os.path.sep) - destdir = os.path.join(dest, basedir) - if is_base and basedir.split(os.path.sep, 1)[0].endswith('.data'): - continue - for s in subdirs: - destsubdir = os.path.join(dest, basedir, s) - if is_base and basedir == '' and destsubdir.endswith('.data'): - data_dirs.append(s) - continue - elif (is_base and - s.endswith('.dist-info') and - canonicalize_name(s).startswith( - canonicalize_name(req.name))): - assert not info_dir, ('Multiple .dist-info directories: ' + - destsubdir + ', ' + - ', '.join(info_dir)) - info_dir.append(destsubdir) - for f in files: - # Skip unwanted files - if filter and filter(f): - continue - srcfile = os.path.join(dir, f) - destfile = os.path.join(dest, basedir, f) - # directory creation is lazy and after the file filtering above - # to ensure we don't install empty dirs; empty dirs can't be - # uninstalled. - ensure_dir(destdir) - - # copyfile (called below) truncates the destination if it - # exists and then writes the new contents. This is fine in most - # cases, but can cause a segfault if pip has loaded a shared - # object (e.g. from pyopenssl through its vendored urllib3) - # Since the shared object is mmap'd an attempt to call a - # symbol in it will then cause a segfault. Unlinking the file - # allows writing of new contents while allowing the process to - # continue to use the old copy. - if os.path.exists(destfile): - os.unlink(destfile) - - # We use copyfile (not move, copy, or copy2) to be extra sure - # that we are not moving directories over (copyfile fails for - # directories) as well as to ensure that we are not copying - # over any metadata because we want more control over what - # metadata we actually copy over. - shutil.copyfile(srcfile, destfile) - - # Copy over the metadata for the file, currently this only - # includes the atime and mtime. - st = os.stat(srcfile) - if hasattr(os, "utime"): - os.utime(destfile, (st.st_atime, st.st_mtime)) - - # If our file is executable, then make our destination file - # executable. - if os.access(srcfile, os.X_OK): - st = os.stat(srcfile) - permissions = ( - st.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH - ) - os.chmod(destfile, permissions) - - changed = False - if fixer: - changed = fixer(destfile) - record_installed(srcfile, destfile, changed) - - clobber(source, lib_dir, True) - - assert info_dir, "%s .dist-info directory not found" % req - - # Get the defined entry points - ep_file = os.path.join(info_dir[0], 'entry_points.txt') - console, gui = get_entrypoints(ep_file) - - def is_entrypoint_wrapper(name): - # EP, EP.exe and EP-script.py are scripts generated for - # entry point EP by setuptools - if name.lower().endswith('.exe'): - matchname = name[:-4] - elif name.lower().endswith('-script.py'): - matchname = name[:-10] - elif name.lower().endswith(".pya"): - matchname = name[:-4] - else: - matchname = name - # Ignore setuptools-generated scripts - return (matchname in console or matchname in gui) - - for datadir in data_dirs: - fixer = None - filter = None - for subdir in os.listdir(os.path.join(wheeldir, datadir)): - fixer = None - if subdir == 'scripts': - fixer = fix_script - filter = is_entrypoint_wrapper - source = os.path.join(wheeldir, datadir, subdir) - dest = scheme[subdir] - clobber(source, dest, False, fixer=fixer, filter=filter) - - maker = ScriptMaker(None, scheme['scripts']) - - # Ensure old scripts are overwritten. - # See https://github.com/pypa/pip/issues/1800 - maker.clobber = True - - # Ensure we don't generate any variants for scripts because this is almost - # never what somebody wants. - # See https://bitbucket.org/pypa/distlib/issue/35/ - maker.variants = {''} - - # This is required because otherwise distlib creates scripts that are not - # executable. - # See https://bitbucket.org/pypa/distlib/issue/32/ - maker.set_mode = True - - # Simplify the script and fix the fact that the default script swallows - # every single stack trace. - # See https://bitbucket.org/pypa/distlib/issue/34/ - # See https://bitbucket.org/pypa/distlib/issue/33/ - def _get_script_text(entry): - if entry.suffix is None: - raise InstallationError( - "Invalid script entry point: %s for req: %s - A callable " - "suffix is required. Cf https://packaging.python.org/en/" - "latest/distributing.html#console-scripts for more " - "information." % (entry, req) - ) - return maker.script_template % { - "module": entry.prefix, - "import_name": entry.suffix.split(".")[0], - "func": entry.suffix, - } - # ignore type, because mypy disallows assigning to a method, - # see https://github.com/python/mypy/issues/2427 - maker._get_script_text = _get_script_text # type: ignore - maker.script_template = r"""# -*- coding: utf-8 -*- -import re -import sys - -from %(module)s import %(import_name)s - -if __name__ == '__main__': - sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) - sys.exit(%(func)s()) -""" - - # Special case pip and setuptools to generate versioned wrappers - # - # The issue is that some projects (specifically, pip and setuptools) use - # code in setup.py to create "versioned" entry points - pip2.7 on Python - # 2.7, pip3.3 on Python 3.3, etc. But these entry points are baked into - # the wheel metadata at build time, and so if the wheel is installed with - # a *different* version of Python the entry points will be wrong. The - # correct fix for this is to enhance the metadata to be able to describe - # such versioned entry points, but that won't happen till Metadata 2.0 is - # available. - # In the meantime, projects using versioned entry points will either have - # incorrect versioned entry points, or they will not be able to distribute - # "universal" wheels (i.e., they will need a wheel per Python version). - # - # Because setuptools and pip are bundled with _ensurepip and virtualenv, - # we need to use universal wheels. So, as a stopgap until Metadata 2.0, we - # override the versioned entry points in the wheel and generate the - # correct ones. This code is purely a short-term measure until Metadata 2.0 - # is available. - # - # To add the level of hack in this section of code, in order to support - # ensurepip this code will look for an ``ENSUREPIP_OPTIONS`` environment - # variable which will control which version scripts get installed. - # - # ENSUREPIP_OPTIONS=altinstall - # - Only pipX.Y and easy_install-X.Y will be generated and installed - # ENSUREPIP_OPTIONS=install - # - pipX.Y, pipX, easy_install-X.Y will be generated and installed. Note - # that this option is technically if ENSUREPIP_OPTIONS is set and is - # not altinstall - # DEFAULT - # - The default behavior is to install pip, pipX, pipX.Y, easy_install - # and easy_install-X.Y. - pip_script = console.pop('pip', None) - if pip_script: - if "ENSUREPIP_OPTIONS" not in os.environ: - spec = 'pip = ' + pip_script - generated.extend(maker.make(spec)) - - if os.environ.get("ENSUREPIP_OPTIONS", "") != "altinstall": - spec = 'pip%s = %s' % (sys.version[:1], pip_script) - generated.extend(maker.make(spec)) - - spec = 'pip%s = %s' % (sys.version[:3], pip_script) - generated.extend(maker.make(spec)) - # Delete any other versioned pip entry points - pip_ep = [k for k in console if re.match(r'pip(\d(\.\d)?)?$', k)] - for k in pip_ep: - del console[k] - easy_install_script = console.pop('easy_install', None) - if easy_install_script: - if "ENSUREPIP_OPTIONS" not in os.environ: - spec = 'easy_install = ' + easy_install_script - generated.extend(maker.make(spec)) - - spec = 'easy_install-%s = %s' % (sys.version[:3], easy_install_script) - generated.extend(maker.make(spec)) - # Delete any other versioned easy_install entry points - easy_install_ep = [ - k for k in console if re.match(r'easy_install(-\d\.\d)?$', k) - ] - for k in easy_install_ep: - del console[k] - - # Generate the console and GUI entry points specified in the wheel - if len(console) > 0: - generated_console_scripts = maker.make_multiple( - ['%s = %s' % kv for kv in console.items()] - ) - generated.extend(generated_console_scripts) - - if warn_script_location: - msg = message_about_scripts_not_on_PATH(generated_console_scripts) - if msg is not None: - logger.warning(msg) - - if len(gui) > 0: - generated.extend( - maker.make_multiple( - ['%s = %s' % kv for kv in gui.items()], - {'gui': True} - ) - ) - - # Record pip as the installer - installer = os.path.join(info_dir[0], 'INSTALLER') - temp_installer = os.path.join(info_dir[0], 'INSTALLER.pip') - with open(temp_installer, 'wb') as installer_file: - installer_file.write(b'pip\n') - shutil.move(temp_installer, installer) - generated.append(installer) - - # Record details of all files installed - record = os.path.join(info_dir[0], 'RECORD') - temp_record = os.path.join(info_dir[0], 'RECORD.pip') - with open_for_csv(record, 'r') as record_in: - with open_for_csv(temp_record, 'w+') as record_out: - reader = csv.reader(record_in) - outrows = get_csv_rows_for_installed( - reader, installed=installed, changed=changed, - generated=generated, lib_dir=lib_dir, - ) - writer = csv.writer(record_out) - # Sort to simplify testing. - for row in sorted_outrows(outrows): - writer.writerow(row) - shutil.move(temp_record, record) - - -def wheel_version(source_dir): - # type: (Optional[str]) -> Optional[Tuple[int, ...]] - """ - Return the Wheel-Version of an extracted wheel, if possible. - - Otherwise, return None if we couldn't parse / extract it. - """ - try: - dist = [d for d in pkg_resources.find_on_path(None, source_dir)][0] - - wheel_data = dist.get_metadata('WHEEL') - wheel_data = Parser().parsestr(wheel_data) - - version = wheel_data['Wheel-Version'].strip() - version = tuple(map(int, version.split('.'))) - return version - except Exception: - return None - - -def check_compatibility(version, name): - # type: (Optional[Tuple[int, ...]], str) -> None - """ - Raises errors or warns if called with an incompatible Wheel-Version. - - Pip should refuse to install a Wheel-Version that's a major series - ahead of what it's compatible with (e.g 2.0 > 1.1); and warn when - installing a version only minor version ahead (e.g 1.2 > 1.1). - - version: a 2-tuple representing a Wheel-Version (Major, Minor) - name: name of wheel or package to raise exception about - - :raises UnsupportedWheel: when an incompatible Wheel-Version is given - """ - if not version: - raise UnsupportedWheel( - "%s is in an unsupported or invalid wheel" % name - ) - if version[0] > VERSION_COMPATIBLE[0]: - raise UnsupportedWheel( - "%s's Wheel-Version (%s) is not compatible with this version " - "of pip" % (name, '.'.join(map(str, version))) - ) - elif version > VERSION_COMPATIBLE: - logger.warning( - 'Installing from a newer Wheel-Version (%s)', - '.'.join(map(str, version)), - ) - - -class Wheel(object): - """A wheel file""" - - # TODO: Maybe move the class into the models sub-package - # TODO: Maybe move the install code into this class - - wheel_file_re = re.compile( - r"""^(?P<namever>(?P<name>.+?)-(?P<ver>.*?)) - ((-(?P<build>\d[^-]*?))?-(?P<pyver>.+?)-(?P<abi>.+?)-(?P<plat>.+?) - \.whl|\.dist-info)$""", - re.VERBOSE - ) - - def __init__(self, filename): - # type: (str) -> None - """ - :raises InvalidWheelFilename: when the filename is invalid for a wheel - """ - wheel_info = self.wheel_file_re.match(filename) - if not wheel_info: - raise InvalidWheelFilename( - "%s is not a valid wheel filename." % filename - ) - self.filename = filename - self.name = wheel_info.group('name').replace('_', '-') - # we'll assume "_" means "-" due to wheel naming scheme - # (https://github.com/pypa/pip/issues/1150) - self.version = wheel_info.group('ver').replace('_', '-') - self.build_tag = wheel_info.group('build') - self.pyversions = wheel_info.group('pyver').split('.') - self.abis = wheel_info.group('abi').split('.') - self.plats = wheel_info.group('plat').split('.') - - # All the tag combinations from this file - self.file_tags = { - (x, y, z) for x in self.pyversions - for y in self.abis for z in self.plats - } - - def support_index_min(self, tags=None): - # type: (Optional[List[Pep425Tag]]) -> Optional[int] - """ - Return the lowest index that one of the wheel's file_tag combinations - achieves in the supported_tags list e.g. if there are 8 supported tags, - and one of the file tags is first in the list, then return 0. Returns - None is the wheel is not supported. - """ - if tags is None: # for mock - tags = pep425tags.get_supported() - indexes = [tags.index(c) for c in self.file_tags if c in tags] - return min(indexes) if indexes else None - - def supported(self, tags=None): - # type: (Optional[List[Pep425Tag]]) -> bool - """Is this wheel supported on this system?""" - if tags is None: # for mock - tags = pep425tags.get_supported() - return bool(set(tags).intersection(self.file_tags)) - - -def _contains_egg_info( - s, _egg_info_re=re.compile(r'([a-z0-9_.]+)-([a-z0-9_.!+-]+)', re.I)): - """Determine whether the string looks like an egg_info. - - :param s: The string to parse. E.g. foo-2.1 - """ - return bool(_egg_info_re.search(s)) - - -def should_use_ephemeral_cache( - req, # type: InstallRequirement - format_control, # type: FormatControl - autobuilding, # type: bool - cache_available # type: bool -): - # type: (...) -> Optional[bool] - """ - Return whether to build an InstallRequirement object using the - ephemeral cache. - - :param cache_available: whether a cache directory is available for the - autobuilding=True case. - - :return: True or False to build the requirement with ephem_cache=True - or False, respectively; or None not to build the requirement. - """ - if req.constraint: - return None - if req.is_wheel: - if not autobuilding: - logger.info( - 'Skipping %s, due to already being wheel.', req.name, - ) - return None - if not autobuilding: - return False - - if req.editable or not req.source_dir: - return None - - if req.link and not req.link.is_artifact: - # VCS checkout. Build wheel just for this run. - return True - - if "binary" not in format_control.get_allowed_formats( - canonicalize_name(req.name)): - logger.info( - "Skipping bdist_wheel for %s, due to binaries " - "being disabled for it.", req.name, - ) - return None - - link = req.link - base, ext = link.splitext() - if cache_available and _contains_egg_info(base): - return False - - # Otherwise, build the wheel just for this run using the ephemeral - # cache since we are either in the case of e.g. a local directory, or - # no cache directory is available to use. - return True - - -def format_command( - command_args, # type: List[str] - command_output, # type: str -): - # type: (...) -> str - """ - Format command information for logging. - """ - text = 'Command arguments: {}\n'.format(command_args) - - if not command_output: - text += 'Command output: None' - elif logger.getEffectiveLevel() > logging.DEBUG: - text += 'Command output: [use --verbose to show]' - else: - if not command_output.endswith('\n'): - command_output += '\n' - text += ( - 'Command output:\n{}' - '-----------------------------------------' - ).format(command_output) - - return text - - -def get_legacy_build_wheel_path( - names, # type: List[str] - temp_dir, # type: str - req, # type: InstallRequirement - command_args, # type: List[str] - command_output, # type: str -): - # type: (...) -> Optional[str] - """ - Return the path to the wheel in the temporary build directory. - """ - # Sort for determinism. - names = sorted(names) - if not names: - msg = ( - 'Legacy build of wheel for {!r} created no files.\n' - ).format(req.name) - msg += format_command(command_args, command_output) - logger.warning(msg) - return None - - if len(names) > 1: - msg = ( - 'Legacy build of wheel for {!r} created more than one file.\n' - 'Filenames (choosing first): {}\n' - ).format(req.name, names) - msg += format_command(command_args, command_output) - logger.warning(msg) - - return os.path.join(temp_dir, names[0]) - - -class WheelBuilder(object): - """Build wheels from a RequirementSet.""" - - def __init__( - self, - finder, # type: PackageFinder - preparer, # type: RequirementPreparer - wheel_cache, # type: WheelCache - build_options=None, # type: Optional[List[str]] - global_options=None, # type: Optional[List[str]] - no_clean=False # type: bool - ): - # type: (...) -> None - self.finder = finder - self.preparer = preparer - self.wheel_cache = wheel_cache - - self._wheel_dir = preparer.wheel_download_dir - - self.build_options = build_options or [] - self.global_options = global_options or [] - self.no_clean = no_clean - - def _build_one(self, req, output_dir, python_tag=None): - """Build one wheel. - - :return: The filename of the built wheel, or None if the build failed. - """ - # Install build deps into temporary directory (PEP 518) - with req.build_env: - return self._build_one_inside_env(req, output_dir, - python_tag=python_tag) - - def _build_one_inside_env(self, req, output_dir, python_tag=None): - with TempDirectory(kind="wheel") as temp_dir: - if req.use_pep517: - builder = self._build_one_pep517 - else: - builder = self._build_one_legacy - wheel_path = builder(req, temp_dir.path, python_tag=python_tag) - if wheel_path is not None: - wheel_name = os.path.basename(wheel_path) - dest_path = os.path.join(output_dir, wheel_name) - try: - shutil.move(wheel_path, dest_path) - logger.info('Stored in directory: %s', output_dir) - return dest_path - except Exception: - pass - # Ignore return, we can't do anything else useful. - self._clean_one(req) - return None - - def _base_setup_args(self, req): - # NOTE: Eventually, we'd want to also -S to the flags here, when we're - # isolating. Currently, it breaks Python in virtualenvs, because it - # relies on site.py to find parts of the standard library outside the - # virtualenv. - executable_loc = os.environ.get('PIP_PYTHON_PATH', sys.executable) - return [ - executable_loc, '-u', '-c', - SETUPTOOLS_SHIM % req.setup_py - ] + list(self.global_options) - - def _build_one_pep517(self, req, tempd, python_tag=None): - """Build one InstallRequirement using the PEP 517 build process. - - Returns path to wheel if successfully built. Otherwise, returns None. - """ - assert req.metadata_directory is not None - try: - req.spin_message = 'Building wheel for %s (PEP 517)' % (req.name,) - logger.debug('Destination directory: %s', tempd) - wheel_name = req.pep517_backend.build_wheel( - tempd, - metadata_directory=req.metadata_directory - ) - if python_tag: - # General PEP 517 backends don't necessarily support - # a "--python-tag" option, so we rename the wheel - # file directly. - new_name = replace_python_tag(wheel_name, python_tag) - os.rename( - os.path.join(tempd, wheel_name), - os.path.join(tempd, new_name) - ) - # Reassign to simplify the return at the end of function - wheel_name = new_name - except Exception: - logger.error('Failed building wheel for %s', req.name) - return None - return os.path.join(tempd, wheel_name) - - def _build_one_legacy(self, req, tempd, python_tag=None): - """Build one InstallRequirement using the "legacy" build process. - - Returns path to wheel if successfully built. Otherwise, returns None. - """ - base_args = self._base_setup_args(req) - - spin_message = 'Building wheel for %s (setup.py)' % (req.name,) - with open_spinner(spin_message) as spinner: - logger.debug('Destination directory: %s', tempd) - wheel_args = base_args + ['bdist_wheel', '-d', tempd] \ - + self.build_options - - if python_tag is not None: - wheel_args += ["--python-tag", python_tag] - - try: - output = call_subprocess(wheel_args, cwd=req.setup_py_dir, - show_stdout=False, spinner=spinner) - except Exception: - spinner.finish("error") - logger.error('Failed building wheel for %s', req.name) - return None - names = os.listdir(tempd) - wheel_path = get_legacy_build_wheel_path( - names=names, - temp_dir=tempd, - req=req, - command_args=wheel_args, - command_output=output, - ) - return wheel_path - - def _clean_one(self, req): - base_args = self._base_setup_args(req) - - logger.info('Running setup.py clean for %s', req.name) - clean_args = base_args + ['clean', '--all'] - try: - call_subprocess(clean_args, cwd=req.source_dir, show_stdout=False) - return True - except Exception: - logger.error('Failed cleaning build dir for %s', req.name) - return False - - def build( - self, - requirements, # type: Iterable[InstallRequirement] - session, # type: PipSession - autobuilding=False # type: bool - ): - # type: (...) -> List[InstallRequirement] - """Build wheels. - - :param unpack: If True, replace the sdist we built from with the - newly built wheel, in preparation for installation. - :return: True if all the wheels built correctly. - """ - buildset = [] - format_control = self.finder.format_control - # Whether a cache directory is available for autobuilding=True. - cache_available = bool(self._wheel_dir or self.wheel_cache.cache_dir) - - for req in requirements: - ephem_cache = should_use_ephemeral_cache( - req, format_control=format_control, autobuilding=autobuilding, - cache_available=cache_available, - ) - if ephem_cache is None: - continue - - buildset.append((req, ephem_cache)) - - if not buildset: - return [] - - # Is any wheel build not using the ephemeral cache? - if any(not ephem_cache for _, ephem_cache in buildset): - have_directory_for_build = self._wheel_dir or ( - autobuilding and self.wheel_cache.cache_dir - ) - assert have_directory_for_build - - # TODO by @pradyunsg - # Should break up this method into 2 separate methods. - - # Build the wheels. - logger.info( - 'Building wheels for collected packages: %s', - ', '.join([req.name for (req, _) in buildset]), - ) - _cache = self.wheel_cache # shorter name - with indent_log(): - build_success, build_failure = [], [] - for req, ephem in buildset: - python_tag = None - if autobuilding: - python_tag = pep425tags.implementation_tag - if ephem: - output_dir = _cache.get_ephem_path_for_link(req.link) - else: - output_dir = _cache.get_path_for_link(req.link) - try: - ensure_dir(output_dir) - except OSError as e: - logger.warning("Building wheel for %s failed: %s", - req.name, e) - build_failure.append(req) - continue - else: - output_dir = self._wheel_dir - wheel_file = self._build_one( - req, output_dir, - python_tag=python_tag, - ) - if wheel_file: - build_success.append(req) - if autobuilding: - # XXX: This is mildly duplicative with prepare_files, - # but not close enough to pull out to a single common - # method. - # The code below assumes temporary source dirs - - # prevent it doing bad things. - if req.source_dir and not os.path.exists(os.path.join( - req.source_dir, PIP_DELETE_MARKER_FILENAME)): - raise AssertionError( - "bad source dir - missing marker") - # Delete the source we built the wheel from - req.remove_temporary_source() - # set the build directory again - name is known from - # the work prepare_files did. - req.source_dir = req.build_location( - self.preparer.build_dir - ) - # Update the link for this. - req.link = Link(path_to_url(wheel_file)) - assert req.link.is_wheel - # extract the wheel into the dir - unpack_url( - req.link, req.source_dir, None, False, - session=session, - ) - else: - build_failure.append(req) - - # notify success/failure - if build_success: - logger.info( - 'Successfully built %s', - ' '.join([req.name for req in build_success]), - ) - if build_failure: - logger.info( - 'Failed to build %s', - ' '.join([req.name for req in build_failure]), - ) - # Return a list of requirements that failed to build - return build_failure diff --git a/pipenv/patched/notpip/_internal/wheel_builder.py b/pipenv/patched/notpip/_internal/wheel_builder.py new file mode 100644 index 00000000..ddb5bf78 --- /dev/null +++ b/pipenv/patched/notpip/_internal/wheel_builder.py @@ -0,0 +1,360 @@ +"""Orchestrator for building wheels from InstallRequirements. +""" + +import logging +import os.path +import re +import shutil +from typing import Any, Callable, Iterable, List, Optional, Tuple + +from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name, canonicalize_version +from pipenv.patched.notpip._vendor.packaging.version import InvalidVersion, Version + +from pipenv.patched.notpip._internal.cache import WheelCache +from pipenv.patched.notpip._internal.exceptions import InvalidWheelFilename, UnsupportedWheel +from pipenv.patched.notpip._internal.metadata import get_wheel_distribution +from pipenv.patched.notpip._internal.models.link import Link +from pipenv.patched.notpip._internal.models.wheel import Wheel +from pipenv.patched.notpip._internal.operations.build.wheel import build_wheel_pep517 +from pipenv.patched.notpip._internal.operations.build.wheel_legacy import build_wheel_legacy +from pipenv.patched.notpip._internal.req.req_install import InstallRequirement +from pipenv.patched.notpip._internal.utils.logging import indent_log +from pipenv.patched.notpip._internal.utils.misc import ensure_dir, hash_file, is_wheel_installed +from pipenv.patched.notpip._internal.utils.setuptools_build import make_setuptools_clean_args +from pipenv.patched.notpip._internal.utils.subprocess import call_subprocess +from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory +from pipenv.patched.notpip._internal.utils.urls import path_to_url +from pipenv.patched.notpip._internal.vcs import vcs + +logger = logging.getLogger(__name__) + +_egg_info_re = re.compile(r'([a-z0-9_.]+)-([a-z0-9_.!+-]+)', re.IGNORECASE) + +BinaryAllowedPredicate = Callable[[InstallRequirement], bool] +BuildResult = Tuple[List[InstallRequirement], List[InstallRequirement]] + + +def _contains_egg_info(s): + # type: (str) -> bool + """Determine whether the string looks like an egg_info. + + :param s: The string to parse. E.g. foo-2.1 + """ + return bool(_egg_info_re.search(s)) + + +def _should_build( + req, # type: InstallRequirement + need_wheel, # type: bool + check_binary_allowed, # type: BinaryAllowedPredicate +): + # type: (...) -> bool + """Return whether an InstallRequirement should be built into a wheel.""" + if req.constraint: + # never build requirements that are merely constraints + return False + if req.is_wheel: + if need_wheel: + logger.info( + 'Skipping %s, due to already being wheel.', req.name, + ) + return False + + if need_wheel: + # i.e. pip wheel, not pip install + return True + + # From this point, this concerns the pip install command only + # (need_wheel=False). + + if req.editable or not req.source_dir: + return False + + if req.use_pep517: + return True + + if not check_binary_allowed(req): + logger.info( + "Skipping wheel build for %s, due to binaries " + "being disabled for it.", req.name, + ) + return False + + if not is_wheel_installed(): + # we don't build legacy requirements if wheel is not installed + logger.info( + "Using legacy 'setup.py install' for %s, " + "since package 'wheel' is not installed.", req.name, + ) + return False + + return True + + +def should_build_for_wheel_command( + req, # type: InstallRequirement +): + # type: (...) -> bool + return _should_build( + req, need_wheel=True, check_binary_allowed=_always_true + ) + + +def should_build_for_install_command( + req, # type: InstallRequirement + check_binary_allowed, # type: BinaryAllowedPredicate +): + # type: (...) -> bool + return _should_build( + req, need_wheel=False, check_binary_allowed=check_binary_allowed + ) + + +def _should_cache( + req, # type: InstallRequirement +): + # type: (...) -> Optional[bool] + """ + Return whether a built InstallRequirement can be stored in the persistent + wheel cache, assuming the wheel cache is available, and _should_build() + has determined a wheel needs to be built. + """ + if req.editable or not req.source_dir: + # never cache editable requirements + return False + + if req.link and req.link.is_vcs: + # VCS checkout. Do not cache + # unless it points to an immutable commit hash. + assert not req.editable + assert req.source_dir + vcs_backend = vcs.get_backend_for_scheme(req.link.scheme) + assert vcs_backend + if vcs_backend.is_immutable_rev_checkout(req.link.url, req.source_dir): + return True + return False + + assert req.link + base, ext = req.link.splitext() + if _contains_egg_info(base): + return True + + # Otherwise, do not cache. + return False + + +def _get_cache_dir( + req, # type: InstallRequirement + wheel_cache, # type: WheelCache +): + # type: (...) -> str + """Return the persistent or temporary cache directory where the built + wheel need to be stored. + """ + cache_available = bool(wheel_cache.cache_dir) + assert req.link + if cache_available and _should_cache(req): + cache_dir = wheel_cache.get_path_for_link(req.link) + else: + cache_dir = wheel_cache.get_ephem_path_for_link(req.link) + return cache_dir + + +def _always_true(_): + # type: (Any) -> bool + return True + + +def _verify_one(req, wheel_path): + # type: (InstallRequirement, str) -> None + canonical_name = canonicalize_name(req.name or "") + w = Wheel(os.path.basename(wheel_path)) + if canonicalize_name(w.name) != canonical_name: + raise InvalidWheelFilename( + "Wheel has unexpected file name: expected {!r}, " + "got {!r}".format(canonical_name, w.name), + ) + dist = get_wheel_distribution(wheel_path, canonical_name) + dist_verstr = str(dist.version) + if canonicalize_version(dist_verstr) != canonicalize_version(w.version): + raise InvalidWheelFilename( + "Wheel has unexpected file name: expected {!r}, " + "got {!r}".format(dist_verstr, w.version), + ) + metadata_version_value = dist.metadata_version + if metadata_version_value is None: + raise UnsupportedWheel("Missing Metadata-Version") + try: + metadata_version = Version(metadata_version_value) + except InvalidVersion: + msg = f"Invalid Metadata-Version: {metadata_version_value}" + raise UnsupportedWheel(msg) + if (metadata_version >= Version("1.2") + and not isinstance(dist.version, Version)): + raise UnsupportedWheel( + "Metadata 1.2 mandates PEP 440 version, " + "but {!r} is not".format(dist_verstr) + ) + + +def _build_one( + req, # type: InstallRequirement + output_dir, # type: str + verify, # type: bool + build_options, # type: List[str] + global_options, # type: List[str] +): + # type: (...) -> Optional[str] + """Build one wheel. + + :return: The filename of the built wheel, or None if the build failed. + """ + try: + ensure_dir(output_dir) + except OSError as e: + logger.warning( + "Building wheel for %s failed: %s", + req.name, e, + ) + return None + + # Install build deps into temporary directory (PEP 518) + with req.build_env: + wheel_path = _build_one_inside_env( + req, output_dir, build_options, global_options + ) + if wheel_path and verify: + try: + _verify_one(req, wheel_path) + except (InvalidWheelFilename, UnsupportedWheel) as e: + logger.warning("Built wheel for %s is invalid: %s", req.name, e) + return None + return wheel_path + + +def _build_one_inside_env( + req, # type: InstallRequirement + output_dir, # type: str + build_options, # type: List[str] + global_options, # type: List[str] +): + # type: (...) -> Optional[str] + with TempDirectory(kind="wheel") as temp_dir: + assert req.name + if req.use_pep517: + assert req.metadata_directory + assert req.pep517_backend + if global_options: + logger.warning( + 'Ignoring --global-option when building %s using PEP 517', req.name + ) + if build_options: + logger.warning( + 'Ignoring --build-option when building %s using PEP 517', req.name + ) + wheel_path = build_wheel_pep517( + name=req.name, + backend=req.pep517_backend, + metadata_directory=req.metadata_directory, + tempd=temp_dir.path, + ) + else: + wheel_path = build_wheel_legacy( + name=req.name, + setup_py_path=req.setup_py_path, + source_dir=req.unpacked_source_directory, + global_options=global_options, + build_options=build_options, + tempd=temp_dir.path, + ) + + if wheel_path is not None: + wheel_name = os.path.basename(wheel_path) + dest_path = os.path.join(output_dir, wheel_name) + try: + wheel_hash, length = hash_file(wheel_path) + shutil.move(wheel_path, dest_path) + logger.info('Created wheel for %s: ' + 'filename=%s size=%d sha256=%s', + req.name, wheel_name, length, + wheel_hash.hexdigest()) + logger.info('Stored in directory: %s', output_dir) + return dest_path + except Exception as e: + logger.warning( + "Building wheel for %s failed: %s", + req.name, e, + ) + # Ignore return, we can't do anything else useful. + if not req.use_pep517: + _clean_one_legacy(req, global_options) + return None + + +def _clean_one_legacy(req, global_options): + # type: (InstallRequirement, List[str]) -> bool + clean_args = make_setuptools_clean_args( + req.setup_py_path, + global_options=global_options, + ) + + logger.info('Running setup.py clean for %s', req.name) + try: + call_subprocess(clean_args, cwd=req.source_dir) + return True + except Exception: + logger.error('Failed cleaning build dir for %s', req.name) + return False + + +def build( + requirements, # type: Iterable[InstallRequirement] + wheel_cache, # type: WheelCache + verify, # type: bool + build_options, # type: List[str] + global_options, # type: List[str] +): + # type: (...) -> BuildResult + """Build wheels. + + :return: The list of InstallRequirement that succeeded to build and + the list of InstallRequirement that failed to build. + """ + if not requirements: + return [], [] + + # Build the wheels. + logger.info( + 'Building wheels for collected packages: %s', + ', '.join(req.name for req in requirements), # type: ignore + ) + + with indent_log(): + build_successes, build_failures = [], [] + for req in requirements: + cache_dir = _get_cache_dir(req, wheel_cache) + wheel_file = _build_one( + req, cache_dir, verify, build_options, global_options + ) + if wheel_file: + # Update the link for this. + req.link = Link(path_to_url(wheel_file)) + req.local_file_path = req.link.file_path + assert req.link.is_wheel + build_successes.append(req) + else: + build_failures.append(req) + + # notify success/failure + if build_successes: + logger.info( + 'Successfully built %s', + ' '.join([req.name for req in build_successes]), # type: ignore + ) + if build_failures: + logger.info( + 'Failed to build %s', + ' '.join([req.name for req in build_failures]), # type: ignore + ) + # Return a list of requirements that failed to build + return build_successes, build_failures diff --git a/pipenv/patched/notpip/_vendor/__init__.py b/pipenv/patched/notpip/_vendor/__init__.py index 1256c039..d6c55e38 100644 --- a/pipenv/patched/notpip/_vendor/__init__.py +++ b/pipenv/patched/notpip/_vendor/__init__.py @@ -1,8 +1,8 @@ """ -pip._vendor is for vendoring dependencies of pip to prevent needing pip to +pipenv.patched.notpip._vendor is for vendoring dependencies of pip to prevent needing pip to depend on something external. -Files inside of pip._vendor should be considered immutable and should only be +Files inside of pipenv.patched.notpip._vendor should be considered immutable and should only be updated to versions from upstream. """ from __future__ import absolute_import @@ -30,24 +30,21 @@ def vendored(modulename): vendored_name = "{0}.{1}".format(__name__, modulename) try: - __import__(vendored_name, globals(), locals(), level=0) + __import__(modulename, globals(), locals(), level=0) except ImportError: - try: - __import__(modulename, globals(), locals(), level=0) - except ImportError: - # We can just silently allow import failures to pass here. If we - # got to this point it means that ``import pipenv.patched.notpip._vendor.whatever`` - # failed and so did ``import whatever``. Since we're importing this - # upfront in an attempt to alias imports, not erroring here will - # just mean we get a regular import error whenever pip *actually* - # tries to import one of these modules to use it, which actually - # gives us a better error message than we would have otherwise - # gotten. - pass - else: - sys.modules[vendored_name] = sys.modules[modulename] - base, head = vendored_name.rsplit(".", 1) - setattr(sys.modules[base], head, sys.modules[modulename]) + # We can just silently allow import failures to pass here. If we + # got to this point it means that ``import pipenv.patched.notpip._vendor.whatever`` + # failed and so did ``import whatever``. Since we're importing this + # upfront in an attempt to alias imports, not erroring here will + # just mean we get a regular import error whenever pip *actually* + # tries to import one of these modules to use it, which actually + # gives us a better error message than we would have otherwise + # gotten. + pass + else: + sys.modules[vendored_name] = sys.modules[modulename] + base, head = vendored_name.rsplit(".", 1) + setattr(sys.modules[base], head, sys.modules[modulename]) # If we're operating in a debundled setup, then we want to go ahead and trigger @@ -61,12 +58,13 @@ if DEBUNDLED: sys.path[:] = glob.glob(os.path.join(WHEEL_DIR, "*.whl")) + sys.path # Actually alias all of our vendored dependencies. + vendored("appdirs") vendored("cachecontrol") + vendored("certifi") vendored("colorama") vendored("distlib") vendored("distro") vendored("html5lib") - vendored("lockfile") vendored("six") vendored("six.moves") vendored("six.moves.urllib") @@ -77,9 +75,8 @@ if DEBUNDLED: vendored("pep517") vendored("pkg_resources") vendored("progress") - vendored("pytoml") - vendored("retrying") vendored("requests") + vendored("requests.exceptions") vendored("requests.packages") vendored("requests.packages.urllib3") vendored("requests.packages.urllib3._collections") @@ -108,4 +105,7 @@ if DEBUNDLED: vendored("requests.packages.urllib3.util.ssl_") vendored("requests.packages.urllib3.util.timeout") vendored("requests.packages.urllib3.util.url") + vendored("resolvelib") + vendored("tenacity") + vendored("tomli") vendored("urllib3") diff --git a/pipenv/patched/notpip/_vendor/appdirs.py b/pipenv/patched/notpip/_vendor/appdirs.py index 2bd39110..65501092 100644 --- a/pipenv/patched/notpip/_vendor/appdirs.py +++ b/pipenv/patched/notpip/_vendor/appdirs.py @@ -13,8 +13,8 @@ See <http://github.com/ActiveState/appdirs> for details and usage. # - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html # - XDG spec for Un*x: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html -__version_info__ = (1, 4, 3) -__version__ = '.'.join(map(str, __version_info__)) +__version__ = "1.4.4" +__version_info__ = tuple(int(segment) for segment in __version__.split(".")) import sys @@ -37,6 +37,10 @@ if sys.platform.startswith('java'): # are actually checked for and the rest of the module expects # *sys.platform* style strings. system = 'linux2' +elif sys.platform == 'cli' and os.name == 'nt': + # Detect Windows in IronPython to match pipenv.patched.notpip._internal.utils.compat.WINDOWS + # Discussion: <https://github.com/pypa/pip/pull/7501> + system = 'win32' else: system = sys.platform @@ -64,7 +68,7 @@ def user_data_dir(appname=None, appauthor=None, version=None, roaming=False): for a discussion of issues. Typical user data directories are: - Mac OS X: ~/Library/Application Support/<AppName> + Mac OS X: ~/Library/Application Support/<AppName> # or ~/.config/<AppName>, if the other does not exist Unix: ~/.local/share/<AppName> # or in $XDG_DATA_HOME, if defined Win XP (not roaming): C:\Documents and Settings\<username>\Application Data\<AppAuthor>\<AppName> Win XP (roaming): C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName> @@ -150,7 +154,7 @@ def site_data_dir(appname=None, appauthor=None, version=None, multipath=False): if appname: if version: appname = os.path.join(appname, version) - pathlist = [os.sep.join([x, appname]) for x in pathlist] + pathlist = [os.path.join(x, appname) for x in pathlist] if multipath: path = os.pathsep.join(pathlist) @@ -203,6 +207,8 @@ def user_config_dir(appname=None, appauthor=None, version=None, roaming=False): return path +# for the discussion regarding site_config_dir locations +# see <https://github.com/pypa/pip/issues/1733> def site_config_dir(appname=None, appauthor=None, version=None, multipath=False): r"""Return full path to the user-shared data dir for this application. @@ -238,14 +244,15 @@ def site_config_dir(appname=None, appauthor=None, version=None, multipath=False) if appname and version: path = os.path.join(path, version) else: - # XDG default for $XDG_CONFIG_DIRS + # XDG default for $XDG_CONFIG_DIRS (missing or empty) + # see <https://github.com/pypa/pip/pull/7501#discussion_r360624829> # only first, if multipath is False - path = os.getenv('XDG_CONFIG_DIRS', '/etc/xdg') - pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)] + path = os.getenv('XDG_CONFIG_DIRS') or '/etc/xdg' + pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep) if x] if appname: if version: appname = os.path.join(appname, version) - pathlist = [os.sep.join([x, appname]) for x in pathlist] + pathlist = [os.path.join(x, appname) for x in pathlist] if multipath: path = os.pathsep.join(pathlist) @@ -291,6 +298,10 @@ def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True): if appauthor is None: appauthor = appname path = os.path.normpath(_get_win_folder("CSIDL_LOCAL_APPDATA")) + # When using Python 2, return paths as bytes on Windows like we do on + # other operating systems. See helper function docs for more details. + if not PY3 and isinstance(path, unicode): + path = _win_path_to_bytes(path) if appname: if appauthor is not False: path = os.path.join(path, appauthor, appname) @@ -567,6 +578,24 @@ if system == "win32": _get_win_folder = _get_win_folder_from_registry +def _win_path_to_bytes(path): + """Encode Windows paths to bytes. Only used on Python 2. + + Motivation is to be consistent with other operating systems where paths + are also returned as bytes. This avoids problems mixing bytes and Unicode + elsewhere in the codebase. For more details and discussion see + <https://github.com/pypa/pip/issues/3463>. + + If encoding using ASCII and MBCS fails, return the original Unicode path. + """ + for encoding in ('ASCII', 'MBCS'): + try: + return path.encode(encoding) + except (UnicodeEncodeError, LookupError): + pass + return path + + #---- self test code if __name__ == "__main__": diff --git a/pipenv/patched/notpip/_vendor/cachecontrol/__init__.py b/pipenv/patched/notpip/_vendor/cachecontrol/__init__.py index 8fdee66f..a1bbbbe3 100644 --- a/pipenv/patched/notpip/_vendor/cachecontrol/__init__.py +++ b/pipenv/patched/notpip/_vendor/cachecontrol/__init__.py @@ -4,7 +4,7 @@ Make it easy to import from cachecontrol without long namespaces. """ __author__ = "Eric Larson" __email__ = "eric@ionrock.org" -__version__ = "0.12.5" +__version__ = "0.12.6" from .wrapper import CacheControl from .adapter import CacheControlAdapter diff --git a/pipenv/patched/notpip/_vendor/cachecontrol/adapter.py b/pipenv/patched/notpip/_vendor/cachecontrol/adapter.py index 2f290998..58efafff 100644 --- a/pipenv/patched/notpip/_vendor/cachecontrol/adapter.py +++ b/pipenv/patched/notpip/_vendor/cachecontrol/adapter.py @@ -24,7 +24,7 @@ class CacheControlAdapter(HTTPAdapter): **kw ): super(CacheControlAdapter, self).__init__(*args, **kw) - self.cache = cache or DictCache() + self.cache = DictCache() if cache is None else cache self.heuristic = heuristic self.cacheable_methods = cacheable_methods or ("GET",) diff --git a/pipenv/patched/notpip/_vendor/cachecontrol/caches/file_cache.py b/pipenv/patched/notpip/_vendor/cachecontrol/caches/file_cache.py index 06f7d09e..607b9452 100644 --- a/pipenv/patched/notpip/_vendor/cachecontrol/caches/file_cache.py +++ b/pipenv/patched/notpip/_vendor/cachecontrol/caches/file_cache.py @@ -69,8 +69,8 @@ class FileCache(BaseCache): raise ValueError("Cannot use use_dir_lock and lock_class together") try: - from pipenv.patched.notpip._vendor.lockfile import LockFile - from pipenv.patched.notpip._vendor.lockfile.mkdirlockfile import MkdirLockFile + from lockfile import LockFile + from lockfile.mkdirlockfile import MkdirLockFile except ImportError: notice = dedent( """ diff --git a/pipenv/patched/notpip/_vendor/cachecontrol/controller.py b/pipenv/patched/notpip/_vendor/cachecontrol/controller.py index 0448910f..80bd030f 100644 --- a/pipenv/patched/notpip/_vendor/cachecontrol/controller.py +++ b/pipenv/patched/notpip/_vendor/cachecontrol/controller.py @@ -34,7 +34,7 @@ class CacheController(object): def __init__( self, cache=None, cache_etags=True, serializer=None, status_codes=None ): - self.cache = cache or DictCache() + self.cache = DictCache() if cache is None else cache self.cache_etags = cache_etags self.serializer = serializer or Serializer() self.cacheable_status_codes = status_codes or (200, 203, 300, 301) @@ -293,6 +293,15 @@ class CacheController(object): if no_store: return + # https://tools.ietf.org/html/rfc7234#section-4.1: + # A Vary header field-value of "*" always fails to match. + # Storing such a response leads to a deserialization warning + # during cache lookup and is not allowed to ever be served, + # so storing it can be avoided. + if "*" in response_headers.get("vary", ""): + logger.debug('Response header has "Vary: *"') + return + # If we've been given an etag, then keep the response if self.cache_etags and "etag" in response_headers: logger.debug("Caching due to etag") diff --git a/pipenv/patched/notpip/_vendor/cachecontrol/serialize.py b/pipenv/patched/notpip/_vendor/cachecontrol/serialize.py index e7106c02..c5da06ad 100644 --- a/pipenv/patched/notpip/_vendor/cachecontrol/serialize.py +++ b/pipenv/patched/notpip/_vendor/cachecontrol/serialize.py @@ -107,6 +107,8 @@ class Serializer(object): """ # Special case the '*' Vary value as it means we cannot actually # determine if the cached response is suitable for this request. + # This case is also handled in the controller code when creating + # a cache entry, but is left here for backwards compatibility. if "*" in cached.get("vary", {}): return @@ -179,7 +181,7 @@ class Serializer(object): def _loads_v4(self, request, data): try: - cached = msgpack.loads(data, encoding="utf-8") + cached = msgpack.loads(data, raw=False) except ValueError: return diff --git a/pipenv/patched/notpip/_vendor/cachecontrol/wrapper.py b/pipenv/patched/notpip/_vendor/cachecontrol/wrapper.py index 265bfc8b..d8e6fc6a 100644 --- a/pipenv/patched/notpip/_vendor/cachecontrol/wrapper.py +++ b/pipenv/patched/notpip/_vendor/cachecontrol/wrapper.py @@ -13,7 +13,7 @@ def CacheControl( cacheable_methods=None, ): - cache = cache or DictCache() + cache = DictCache() if cache is None else cache adapter_class = adapter_class or CacheControlAdapter adapter = adapter_class( cache, diff --git a/pipenv/patched/notpip/_vendor/certifi/LICENSE b/pipenv/patched/notpip/_vendor/certifi/LICENSE index 802b53ff..c2fda9a2 100644 --- a/pipenv/patched/notpip/_vendor/certifi/LICENSE +++ b/pipenv/patched/notpip/_vendor/certifi/LICENSE @@ -1,4 +1,4 @@ -This packge contains a modified version of ca-bundle.crt: +This package contains a modified version of ca-bundle.crt: ca-bundle.crt -- Bundle of CA Root Certificates diff --git a/pipenv/patched/notpip/_vendor/certifi/__init__.py b/pipenv/patched/notpip/_vendor/certifi/__init__.py index ef71f3af..eebdf888 100644 --- a/pipenv/patched/notpip/_vendor/certifi/__init__.py +++ b/pipenv/patched/notpip/_vendor/certifi/__init__.py @@ -1,3 +1,3 @@ -from .core import where +from .core import contents, where -__version__ = "2018.11.29" +__version__ = "2021.05.30" diff --git a/pipenv/patched/notpip/_vendor/certifi/__main__.py b/pipenv/patched/notpip/_vendor/certifi/__main__.py index 983ed0f9..8413cfd6 100644 --- a/pipenv/patched/notpip/_vendor/certifi/__main__.py +++ b/pipenv/patched/notpip/_vendor/certifi/__main__.py @@ -1,2 +1,12 @@ -from pipenv.patched.notpip._vendor.certifi import where -print(where()) +import argparse + +from pipenv.patched.notpip._vendor.certifi import contents, where + +parser = argparse.ArgumentParser() +parser.add_argument("-c", "--contents", action="store_true") +args = parser.parse_args() + +if args.contents: + print(contents()) +else: + print(where()) diff --git a/pipenv/patched/notpip/_vendor/certifi/cacert.pem b/pipenv/patched/notpip/_vendor/certifi/cacert.pem index db68797e..96e2fc65 100644 --- a/pipenv/patched/notpip/_vendor/certifi/cacert.pem +++ b/pipenv/patched/notpip/_vendor/certifi/cacert.pem @@ -58,38 +58,6 @@ AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== -----END CERTIFICATE----- -# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only -# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only -# Label: "Verisign Class 3 Public Primary Certification Authority - G3" -# Serial: 206684696279472310254277870180966723415 -# MD5 Fingerprint: cd:68:b6:a7:c7:c4:ce:75:e0:1d:4f:57:44:61:92:09 -# SHA1 Fingerprint: 13:2d:0d:45:53:4b:69:97:cd:b2:d5:c3:39:e2:55:76:60:9b:5c:c6 -# SHA256 Fingerprint: eb:04:cf:5e:b1:f3:9a:fa:76:2f:2b:b1:20:f2:96:cb:a5:20:c1:b9:7d:b1:58:95:65:b8:1c:b9:a1:7b:72:44 ------BEGIN CERTIFICATE----- -MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw -CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl -cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu -LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT -aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp -dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD -VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT -aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ -bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu -IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg -LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b -N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t -KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu -kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm -CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ -Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu -imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te -2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe -DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC -/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p -F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt -TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== ------END CERTIFICATE----- - # Issuer: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited # Subject: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited # Label: "Entrust.net Premium 2048 Secure Server CA" @@ -152,39 +120,6 @@ ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp -----END CERTIFICATE----- -# Issuer: CN=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network -# Subject: CN=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network -# Label: "AddTrust External Root" -# Serial: 1 -# MD5 Fingerprint: 1d:35:54:04:85:78:b0:3f:42:42:4d:bf:20:73:0a:3f -# SHA1 Fingerprint: 02:fa:f3:e2:91:43:54:68:60:78:57:69:4d:f5:e4:5b:68:85:18:68 -# SHA256 Fingerprint: 68:7f:a4:51:38:22:78:ff:f0:c8:b1:1f:8d:43:d5:76:67:1c:6e:b2:bc:ea:b4:13:fb:83:d9:65:d0:6d:2f:f2 ------BEGIN CERTIFICATE----- -MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU -MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs -IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290 -MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux -FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h -bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v -dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt -H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9 -uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX -mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX -a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN -E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0 -WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD -VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0 -Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU -cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx -IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN -AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH -YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 -6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC -Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX -c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a -mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= ------END CERTIFICATE----- - # Issuer: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc. # Subject: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc. # Label: "Entrust Root Certification Authority" @@ -220,112 +155,6 @@ eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m 0vdXcDazv/wor3ElhVsT/h5/WrQ8 -----END CERTIFICATE----- -# Issuer: CN=GeoTrust Global CA O=GeoTrust Inc. -# Subject: CN=GeoTrust Global CA O=GeoTrust Inc. -# Label: "GeoTrust Global CA" -# Serial: 144470 -# MD5 Fingerprint: f7:75:ab:29:fb:51:4e:b7:77:5e:ff:05:3c:99:8e:f5 -# SHA1 Fingerprint: de:28:f4:a4:ff:e5:b9:2f:a3:c5:03:d1:a3:49:a7:f9:96:2a:82:12 -# SHA256 Fingerprint: ff:85:6a:2d:25:1d:cd:88:d3:66:56:f4:50:12:67:98:cf:ab:aa:de:40:79:9c:72:2d:e4:d2:b5:db:36:a7:3a ------BEGIN CERTIFICATE----- -MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT -MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i -YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG -EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg -R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9 -9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq -fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv -iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU -1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+ -bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW -MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA -ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l -uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn -Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS -tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF -PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un -hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV -5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw== ------END CERTIFICATE----- - -# Issuer: CN=GeoTrust Universal CA O=GeoTrust Inc. -# Subject: CN=GeoTrust Universal CA O=GeoTrust Inc. -# Label: "GeoTrust Universal CA" -# Serial: 1 -# MD5 Fingerprint: 92:65:58:8b:a2:1a:31:72:73:68:5c:b4:a5:7a:07:48 -# SHA1 Fingerprint: e6:21:f3:35:43:79:05:9a:4b:68:30:9d:8a:2f:74:22:15:87:ec:79 -# SHA256 Fingerprint: a0:45:9b:9f:63:b2:25:59:f5:fa:5d:4c:6d:b3:f9:f7:2f:f1:93:42:03:35:78:f0:73:bf:1d:1b:46:cb:b9:12 ------BEGIN CERTIFICATE----- -MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEW -MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVy -c2FsIENBMB4XDTA0MDMwNDA1MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UE -BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xHjAcBgNVBAMTFUdlb1RydXN0 -IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKYV -VaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9tJPi8 -cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTT -QjOgNB0eRXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFh -F7em6fgemdtzbvQKoiFs7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2v -c7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d8Lsrlh/eezJS/R27tQahsiFepdaVaH/w -mZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7VqnJNk22CDtucvc+081xd -VHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3CgaRr0BHdCX -teGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZ -f9hBZ3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfRe -Bi9Fi1jUIxaS5BZuKGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+ -nhutxx9z3SxPGWX9f5NAEC7S8O08ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB -/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0XG0D08DYj3rWMB8GA1UdIwQY -MBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG -9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc -aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fX -IwjhmF7DWgh2qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzyn -ANXH/KttgCJwpQzgXQQpAvvLoJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0z -uzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsKxr2EoyNB3tZ3b4XUhRxQ4K5RirqN -Pnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxFKyDuSN/n3QmOGKja -QI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2DFKW -koRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9 -ER/frslKxfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQt -DF4JbAiXfKM9fJP/P6EUp8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/Sfuvm -bJxPgWp6ZKy7PtXny3YuxadIwVyQD8vIP/rmMuGNG2+k5o7Y+SlIis5z/iw= ------END CERTIFICATE----- - -# Issuer: CN=GeoTrust Universal CA 2 O=GeoTrust Inc. -# Subject: CN=GeoTrust Universal CA 2 O=GeoTrust Inc. -# Label: "GeoTrust Universal CA 2" -# Serial: 1 -# MD5 Fingerprint: 34:fc:b8:d0:36:db:9e:14:b3:c2:f2:db:8f:e4:94:c7 -# SHA1 Fingerprint: 37:9a:19:7b:41:85:45:35:0c:a6:03:69:f3:3c:2e:af:47:4f:20:79 -# SHA256 Fingerprint: a0:23:4f:3b:c8:52:7c:a5:62:8e:ec:81:ad:5d:69:89:5d:a5:68:0d:c9:1d:1c:b8:47:7f:33:f8:78:b9:5b:0b ------BEGIN CERTIFICATE----- -MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEW -MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVy -c2FsIENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYD -VQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1 -c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC -AQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0DE81 -WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUG -FF+3Qs17j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdq -XbboW0W63MOhBW9Wjo8QJqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxL -se4YuU6W3Nx2/zu+z18DwPw76L5GG//aQMJS9/7jOvdqdzXQ2o3rXhhqMcceujwb -KNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2WP0+GfPtDCapkzj4T8Fd -IgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP20gaXT73 -y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRt -hAAnZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgoc -QIgfksILAAX/8sgCSqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4 -Lt1ZrtmhN79UNdxzMk+MBB4zsslG8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAfBgNV -HSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8EBAMCAYYwDQYJ -KoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z -dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQ -L1EuxBRa3ugZ4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgr -Fg5fNuH8KrUwJM/gYwx7WBr+mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSo -ag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpqA1Ihn0CoZ1Dy81of398j9tx4TuaY -T1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpgY+RdM4kX2TGq2tbz -GDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiPpm8m -1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJV -OCiNUW7dFGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH -6aLcr34YEoP9VhdBLtUpgn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwX -QMAJKOSLakhT2+zNVVXxxvjpoixMptEmX36vWkzaH6byHCx+rgIW0lbQL1dTR+iS ------END CERTIFICATE----- - # Issuer: CN=AAA Certificate Services O=Comodo CA Limited # Subject: CN=AAA Certificate Services O=Comodo CA Limited # Label: "Comodo AAA Services root" @@ -359,48 +188,6 @@ l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3 smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== -----END CERTIFICATE----- -# Issuer: CN=QuoVadis Root Certification Authority O=QuoVadis Limited OU=Root Certification Authority -# Subject: CN=QuoVadis Root Certification Authority O=QuoVadis Limited OU=Root Certification Authority -# Label: "QuoVadis Root CA" -# Serial: 985026699 -# MD5 Fingerprint: 27:de:36:fe:72:b7:00:03:00:9d:f4:f0:1e:6c:04:24 -# SHA1 Fingerprint: de:3f:40:bd:50:93:d3:9b:6c:60:f6:da:bc:07:62:01:00:89:76:c9 -# SHA256 Fingerprint: a4:5e:de:3b:bb:f0:9c:8a:e1:5c:72:ef:c0:72:68:d6:93:a2:1c:99:6f:d5:1e:67:ca:07:94:60:fd:6d:88:73 ------BEGIN CERTIFICATE----- -MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJC -TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0 -aWZpY2F0aW9uIEF1dGhvcml0eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0 -aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAzMTkxODMzMzNaFw0yMTAzMTcxODMz -MzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUw -IwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVR -dW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Yp -li4kVEAkOPcahdxYTMukJ0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2D -rOpm2RgbaIr1VxqYuvXtdj182d6UajtLF8HVj71lODqV0D1VNk7feVcxKh7YWWVJ -WCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeLYzcS19Dsw3sgQUSj7cug -F+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWenAScOospU -xbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCC -Ak4wPQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVv -dmFkaXNvZmZzaG9yZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREw -ggENMIIBCQYJKwYBBAG+WAABMIH7MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNl -IG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBh -c3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFy -ZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh -Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYI -KwYBBQUHAgEWFmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3T -KbkGGew5Oanwl4Rqy+/fMIGuBgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rq -y+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1p -dGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYD -VQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6tlCL -MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSk -fnIYj9lofFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf8 -7C9TqnN7Az10buYWnuulLsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1R -cHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2xgI4JVrmcGmD+XcHXetwReNDWXcG31a0y -mQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi5upZIof4l/UO/erMkqQW -xFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi5nrQNiOK -SnQ2+Q== ------END CERTIFICATE----- - # Issuer: CN=QuoVadis Root CA 2 O=QuoVadis Limited # Subject: CN=QuoVadis Root CA 2 O=QuoVadis Limited # Label: "QuoVadis Root CA 2" @@ -516,33 +303,6 @@ JRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ6rBK+1YWc26sTfcioU+tHXot RSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAiFL39vmwLAw== -----END CERTIFICATE----- -# Issuer: CN=Sonera Class2 CA O=Sonera -# Subject: CN=Sonera Class2 CA O=Sonera -# Label: "Sonera Class 2 Root CA" -# Serial: 29 -# MD5 Fingerprint: a3:ec:75:0f:2e:88:df:fa:48:01:4e:0b:5c:48:6f:fb -# SHA1 Fingerprint: 37:f7:6d:e6:07:7c:90:c5:b1:3e:93:1a:b7:41:10:b4:f2:e4:9a:27 -# SHA256 Fingerprint: 79:08:b4:03:14:c1:38:10:0b:51:8d:07:35:80:7f:fb:fc:f8:51:8a:00:95:33:71:05:ba:38:6b:15:3d:d9:27 ------BEGIN CERTIFICATE----- -MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEP -MA0GA1UEChMGU29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAx -MDQwNjA3Mjk0MFoXDTIxMDQwNjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNV -BAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJhIENsYXNzMiBDQTCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3/Ei9vX+ALTU74W+o -Z6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybTdXnt -5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s -3TmVToMGf+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2Ej -vOr7nQKV0ba5cTppCD8PtOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu -8nYybieDwnPz3BjotJPqdURrBGAgcVeHnfO+oJAjPYok4doh28MCAwEAAaMzMDEw -DwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITTXjwwCwYDVR0PBAQDAgEG -MA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt0jSv9zil -zqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/ -3DEIcbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvD -FNr450kkkdAdavphOe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6 -Tk6ezAyNlNzZRZxe7EJQY670XcSxEtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2 -ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLHllpwrN9M ------END CERTIFICATE----- - # Issuer: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com # Subject: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com # Label: "XRamp Global CA Root" @@ -640,46 +400,6 @@ VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q= -----END CERTIFICATE----- -# Issuer: O=Government Root Certification Authority -# Subject: O=Government Root Certification Authority -# Label: "Taiwan GRCA" -# Serial: 42023070807708724159991140556527066870 -# MD5 Fingerprint: 37:85:44:53:32:45:1f:20:f0:f3:95:e1:25:c4:43:4e -# SHA1 Fingerprint: f4:8b:11:bf:de:ab:be:94:54:20:71:e6:41:de:6b:be:88:2b:40:b9 -# SHA256 Fingerprint: 76:00:29:5e:ef:e8:5b:9e:1f:d6:24:db:76:06:2a:aa:ae:59:81:8a:54:d2:77:4c:d4:c0:b2:c0:11:31:e1:b3 ------BEGIN CERTIFICATE----- -MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/ -MQswCQYDVQQGEwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmlj -YXRpb24gQXV0aG9yaXR5MB4XDTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1ow -PzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dvdmVybm1lbnQgUm9vdCBDZXJ0aWZp -Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB -AJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qNw8XR -IePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1q -gQdW8or5BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKy -yhwOeYHWtXBiCAEuTk8O1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAts -F/tnyMKtsc2AtJfcdgEWFelq16TheEfOhtX7MfP6Mb40qij7cEwdScevLJ1tZqa2 -jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wovJ5pGfaENda1UhhXcSTvx -ls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7Q3hub/FC -VGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHK -YS1tB6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoH -EgKXTiCQ8P8NHuJBO9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThN -Xo+EHWbNxWCWtFJaBYmOlXqYwZE8lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1Ud -DgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNVHRMEBTADAQH/MDkGBGcqBwAE -MTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg209yewDL7MTqK -UWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ -TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyf -qzvS/3WXy6TjZwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaK -ZEk9GhiHkASfQlK3T8v+R0F2Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFE -JPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlUD7gsL0u8qV1bYH+Mh6XgUmMqvtg7 -hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6QzDxARvBMB1uUO07+1 -EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+HbkZ6Mm -nD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WX -udpVBrkk7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44Vbnz -ssQwmSNOXfJIoRIM3BKQCZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDe -LMDDav7v3Aun+kbfYNucpllQdSNpc5Oy+fwC00fmcc4QAu4njIT/rEUNE1yDMuAl -pYYsfPQS ------END CERTIFICATE----- - # Issuer: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com # Subject: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com # Label: "DigiCert Assured ID Root CA" @@ -771,36 +491,6 @@ vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep +OkuE6N36B9K -----END CERTIFICATE----- -# Issuer: CN=Class 2 Primary CA O=Certplus -# Subject: CN=Class 2 Primary CA O=Certplus -# Label: "Certplus Class 2 Primary CA" -# Serial: 177770208045934040241468760488327595043 -# MD5 Fingerprint: 88:2c:8c:52:b8:a2:3c:f3:f7:bb:03:ea:ae:ac:42:0b -# SHA1 Fingerprint: 74:20:74:41:72:9c:dd:92:ec:79:31:d8:23:10:8d:c2:81:92:e2:bb -# SHA256 Fingerprint: 0f:99:3c:8a:ef:97:ba:af:56:87:14:0e:d5:9a:d1:82:1b:b4:af:ac:f0:aa:9a:58:b5:d5:7a:33:8a:3a:fb:cb ------BEGIN CERTIFICATE----- -MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAw -PTELMAkGA1UEBhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFz -cyAyIFByaW1hcnkgQ0EwHhcNOTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9 -MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2VydHBsdXMxGzAZBgNVBAMTEkNsYXNz -IDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANxQ -ltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR5aiR -VhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyL -kcAbmXuZVg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCd -EgETjdyAYveVqUSISnFOYFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yas -H7WLO7dDWWuwJKZtkIvEcupdM5i3y95ee++U8Rs+yskhwcWYAqqi9lt3m/V+llU0 -HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRMECDAGAQH/AgEKMAsGA1Ud -DwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJYIZIAYb4 -QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMu -Y29tL0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/ -AN9WM2K191EBkOvDP9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8 -yfFC82x/xXp8HVGIutIKPidd3i1RTtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMR -FcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+7UCmnYR0ObncHoUW2ikbhiMA -ybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW//1IMwrh3KWB -kJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7 -l7+ijrRU ------END CERTIFICATE----- - # Issuer: CN=DST Root CA X3 O=Digital Signature Trust Co. # Subject: CN=DST Root CA X3 O=Digital Signature Trust Co. # Label: "DST Root CA X3" @@ -911,104 +601,6 @@ hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ubDgEj8Z+7fNzcbBGXJbLy tGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u -----END CERTIFICATE----- -# Issuer: CN=GeoTrust Primary Certification Authority O=GeoTrust Inc. -# Subject: CN=GeoTrust Primary Certification Authority O=GeoTrust Inc. -# Label: "GeoTrust Primary Certification Authority" -# Serial: 32798226551256963324313806436981982369 -# MD5 Fingerprint: 02:26:c3:01:5e:08:30:37:43:a9:d0:7d:cf:37:e6:bf -# SHA1 Fingerprint: 32:3c:11:8e:1b:f7:b8:b6:52:54:e2:e2:10:0d:d6:02:90:37:f0:96 -# SHA256 Fingerprint: 37:d5:10:06:c5:12:ea:ab:62:64:21:f1:ec:8c:92:01:3f:c5:f8:2a:e9:8e:e5:33:eb:46:19:b8:de:b4:d0:6c ------BEGIN CERTIFICATE----- -MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBY -MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo -R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx -MjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQK -Ew1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQcmltYXJ5IENlcnRp -ZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC -AQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9 -AWbK7hWNb6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjA -ZIVcFU2Ix7e64HXprQU9nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE0 -7e9GceBrAqg1cmuXm2bgyxx5X9gaBGgeRwLmnWDiNpcB3841kt++Z8dtd1k7j53W -kBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGttm/81w7a4DSwDRp35+MI -mO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G -A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJ -KoZIhvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ1 -6CePbJC/kRYkRj5KTs4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl -4b7UVXGYNTq+k+qurUKykG/g/CFNNWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6K -oKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHaFloxt/m0cYASSJlyc1pZU8Fj -UjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG1riR/aYNKxoU -AT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk= ------END CERTIFICATE----- - -# Issuer: CN=thawte Primary Root CA O=thawte, Inc. OU=Certification Services Division/(c) 2006 thawte, Inc. - For authorized use only -# Subject: CN=thawte Primary Root CA O=thawte, Inc. OU=Certification Services Division/(c) 2006 thawte, Inc. - For authorized use only -# Label: "thawte Primary Root CA" -# Serial: 69529181992039203566298953787712940909 -# MD5 Fingerprint: 8c:ca:dc:0b:22:ce:f5:be:72:ac:41:1a:11:a8:d8:12 -# SHA1 Fingerprint: 91:c6:d6:ee:3e:8a:c8:63:84:e5:48:c2:99:29:5c:75:6c:81:7b:81 -# SHA256 Fingerprint: 8d:72:2f:81:a9:c1:13:c0:79:1d:f1:36:a2:96:6d:b2:6c:95:0a:97:1d:b4:6b:41:99:f4:ea:54:b7:8b:fb:9f ------BEGIN CERTIFICATE----- -MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCB -qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf -Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw -MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV -BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMzYw -NzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j -LjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYG -A1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl -IG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqG -SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsoPD7gFnUnMekz52hWXMJEEUMDSxuaPFs -W0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ1CRfBsDMRJSUjQJib+ta -3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGcq/gcfomk -6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6 -Sk/KaAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94J -NqR32HuHUETVPm4pafs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBA -MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XP -r87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAeRHAS7ORtvzw6WfU -DW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeEuzLlQRHAd9mz -YJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX -xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2 -/qxAeeWsEG89jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/ -LHbTY5xZ3Y+m4Q6gLkH3LpVHz7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7 -jVaMaA== ------END CERTIFICATE----- - -# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G5 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2006 VeriSign, Inc. - For authorized use only -# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G5 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2006 VeriSign, Inc. - For authorized use only -# Label: "VeriSign Class 3 Public Primary Certification Authority - G5" -# Serial: 33037644167568058970164719475676101450 -# MD5 Fingerprint: cb:17:e4:31:67:3e:e2:09:fe:45:57:93:f3:0a:fa:1c -# SHA1 Fingerprint: 4e:b6:d5:78:49:9b:1c:cf:5f:58:1e:ad:56:be:3d:9b:67:44:a5:e5 -# SHA256 Fingerprint: 9a:cf:ab:7e:43:c8:d8:80:d0:6b:26:2a:94:de:ee:e4:b4:65:99:89:c3:d0:ca:f1:9b:af:64:05:e4:1a:b7:df ------BEGIN CERTIFICATE----- -MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB -yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL -ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp -U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW -ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0 -aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL -MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW -ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln -biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp -U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y -aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1 -nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex -t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz -SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG -BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+ -rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/ -NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E -BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH -BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy -aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv -MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE -p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y -5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK -WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ -4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N -hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq ------END CERTIFICATE----- - # Issuer: CN=SecureTrust CA O=SecureTrust Corporation # Subject: CN=SecureTrust CA O=SecureTrust Corporation # Label: "SecureTrust CA" @@ -1157,38 +749,6 @@ fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= -----END CERTIFICATE----- -# Issuer: CN=OISTE WISeKey Global Root GA CA O=WISeKey OU=Copyright (c) 2005/OISTE Foundation Endorsed -# Subject: CN=OISTE WISeKey Global Root GA CA O=WISeKey OU=Copyright (c) 2005/OISTE Foundation Endorsed -# Label: "OISTE WISeKey Global Root GA CA" -# Serial: 86718877871133159090080555911823548314 -# MD5 Fingerprint: bc:6c:51:33:a7:e9:d3:66:63:54:15:72:1b:21:92:93 -# SHA1 Fingerprint: 59:22:a1:e1:5a:ea:16:35:21:f8:98:39:6a:46:46:b0:44:1b:0f:a9 -# SHA256 Fingerprint: 41:c9:23:86:6a:b4:ca:d6:b7:ad:57:80:81:58:2e:02:07:97:a6:cb:df:4f:ff:78:ce:83:96:b3:89:37:d7:f5 ------BEGIN CERTIFICATE----- -MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCB -ijELMAkGA1UEBhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHly -aWdodCAoYykgMjAwNTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl -ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQSBDQTAeFw0w -NTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYDVQQGEwJDSDEQMA4G -A1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIwIAYD -VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBX -SVNlS2V5IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A -MIIBCgKCAQEAy0+zAJs9Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxR -VVuuk+g3/ytr6dTqvirdqFEr12bDYVxgAsj1znJ7O7jyTmUIms2kahnBAbtzptf2 -w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbDd50kc3vkDIzh2TbhmYsF -mQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ/yxViJGg -4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t9 -4B3RLoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYw -DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQw -EAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOx -SPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vImMMkQyh2I+3QZH4VFvbBsUfk2 -ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4+vg1YFkCExh8 -vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa -hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZi -Fj4A4xylNoEYokxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ -/L7fCg0= ------END CERTIFICATE----- - # Issuer: CN=Certigna O=Dhimyotis # Subject: CN=Certigna O=Dhimyotis # Label: "Certigna" @@ -1219,36 +779,6 @@ t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== -----END CERTIFICATE----- -# Issuer: CN=Deutsche Telekom Root CA 2 O=Deutsche Telekom AG OU=T-TeleSec Trust Center -# Subject: CN=Deutsche Telekom Root CA 2 O=Deutsche Telekom AG OU=T-TeleSec Trust Center -# Label: "Deutsche Telekom Root CA 2" -# Serial: 38 -# MD5 Fingerprint: 74:01:4a:91:b1:08:c4:58:ce:47:cd:f0:dd:11:53:08 -# SHA1 Fingerprint: 85:a4:08:c0:9c:19:3e:5d:51:58:7d:cd:d6:13:30:fd:8c:de:37:bf -# SHA256 Fingerprint: b6:19:1a:50:d0:c3:97:7f:7d:a9:9b:cd:aa:c8:6a:22:7d:ae:b9:67:9e:c7:0b:a3:b0:c9:d9:22:71:c1:70:d3 ------BEGIN CERTIFICATE----- -MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEc -MBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2Vj -IFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENB -IDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5MjM1OTAwWjBxMQswCQYDVQQGEwJE -RTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxl -U2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290 -IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEU -ha88EOQ5bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhC -QN/Po7qCWWqSG6wcmtoIKyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1Mjwr -rFDa1sPeg5TKqAyZMg4ISFZbavva4VhYAUlfckE8FQYBjl2tqriTtM2e66foai1S -NNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aKSe5TBY8ZTNXeWHmb0moc -QqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTVjlsB9WoH -txa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAP -BgNVHRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOC -AQEAlGRZrTlk5ynrE/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756Abrsp -tJh6sTtU6zkXR34ajgv8HzFZMQSyzhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpa -IzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8rZ7/gFnkm0W09juwzTkZmDLl -6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4Gdyd1Lx+4ivn+ -xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU -Cm26OWMohpLzGITY+9HPBVZkVw== ------END CERTIFICATE----- - # Issuer: CN=Cybertrust Global Root O=Cybertrust, Inc # Subject: CN=Cybertrust Global Root O=Cybertrust, Inc # Label: "Cybertrust Global Root" @@ -1348,185 +878,6 @@ i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN 9u6wWk5JRFRYX0KD -----END CERTIFICATE----- -# Issuer: CN=GeoTrust Primary Certification Authority - G3 O=GeoTrust Inc. OU=(c) 2008 GeoTrust Inc. - For authorized use only -# Subject: CN=GeoTrust Primary Certification Authority - G3 O=GeoTrust Inc. OU=(c) 2008 GeoTrust Inc. - For authorized use only -# Label: "GeoTrust Primary Certification Authority - G3" -# Serial: 28809105769928564313984085209975885599 -# MD5 Fingerprint: b5:e8:34:36:c9:10:44:58:48:70:6d:2e:83:d4:b8:05 -# SHA1 Fingerprint: 03:9e:ed:b8:0b:e7:a0:3c:69:53:89:3b:20:d2:d9:32:3a:4c:2a:fd -# SHA256 Fingerprint: b4:78:b8:12:25:0d:f8:78:63:5c:2a:a7:ec:7d:15:5e:aa:62:5e:e8:29:16:e2:cd:29:43:61:88:6c:d1:fb:d4 ------BEGIN CERTIFICATE----- -MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCB -mDELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsT -MChjKSAyMDA4IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s -eTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhv -cml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIzNTk1OVowgZgxCzAJ -BgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg -MjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0 -BgNVBAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg -LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz -+uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5jK/BGvESyiaHAKAxJcCGVn2TAppMSAmUm -hsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdEc5IiaacDiGydY8hS2pgn -5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3CIShwiP/W -JmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exAL -DmKudlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZC -huOl1UcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw -HQYDVR0OBBYEFMR5yo6hTgMdHNxr2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IB -AQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9cr5HqQ6XErhK8WTTOd8lNNTB -zU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbEAp7aDHdlDkQN -kv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD -AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUH -SJsMC8tJP33st/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2G -spki4cErx5z481+oghLrGREt ------END CERTIFICATE----- - -# Issuer: CN=thawte Primary Root CA - G2 O=thawte, Inc. OU=(c) 2007 thawte, Inc. - For authorized use only -# Subject: CN=thawte Primary Root CA - G2 O=thawte, Inc. OU=(c) 2007 thawte, Inc. - For authorized use only -# Label: "thawte Primary Root CA - G2" -# Serial: 71758320672825410020661621085256472406 -# MD5 Fingerprint: 74:9d:ea:60:24:c4:fd:22:53:3e:cc:3a:72:d9:29:4f -# SHA1 Fingerprint: aa:db:bc:22:23:8f:c4:01:a1:27:bb:38:dd:f4:1d:db:08:9e:f0:12 -# SHA256 Fingerprint: a4:31:0d:50:af:18:a6:44:71:90:37:2a:86:af:af:8b:95:1f:fb:43:1d:83:7f:1e:56:88:b4:59:71:ed:15:57 ------BEGIN CERTIFICATE----- -MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDEL -MAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMp -IDIwMDcgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAi -BgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMjAeFw0wNzExMDUwMDAw -MDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh -d3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBGb3Ig -YXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9v -dCBDQSAtIEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/ -BebfowJPDQfGAFG6DAJSLSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6 -papu+7qzcMBniKI11KOasf2twu8x+qi58/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8E -BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmtgAMADna3+FGO6Lts6K -DPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUNG4k8VIZ3 -KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41ox -XZ3Krr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg== ------END CERTIFICATE----- - -# Issuer: CN=thawte Primary Root CA - G3 O=thawte, Inc. OU=Certification Services Division/(c) 2008 thawte, Inc. - For authorized use only -# Subject: CN=thawte Primary Root CA - G3 O=thawte, Inc. OU=Certification Services Division/(c) 2008 thawte, Inc. - For authorized use only -# Label: "thawte Primary Root CA - G3" -# Serial: 127614157056681299805556476275995414779 -# MD5 Fingerprint: fb:1b:5d:43:8a:94:cd:44:c6:76:f2:43:4b:47:e7:31 -# SHA1 Fingerprint: f1:8b:53:8d:1b:e9:03:b6:a6:f0:56:43:5b:17:15:89:ca:f3:6b:f2 -# SHA256 Fingerprint: 4b:03:f4:58:07:ad:70:f2:1b:fc:2c:ae:71:c9:fd:e4:60:4c:06:4c:f5:ff:b6:86:ba:e5:db:aa:d7:fd:d3:4c ------BEGIN CERTIFICATE----- -MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCB -rjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf -Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw -MDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNV -BAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0wODA0MDIwMDAwMDBa -Fw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3Rl -LCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9u -MTgwNgYDVQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXpl -ZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEcz -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsr8nLPvb2FvdeHsbnndm -gcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2AtP0LMqmsywCPLLEHd5N/8 -YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC+BsUa0Lf -b1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS9 -9irY7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2S -zhkGcuYMXDhpxwTWvGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUk -OQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNV -HQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJKoZIhvcNAQELBQADggEBABpA -2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweKA3rD6z8KLFIW -oCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu -t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7c -KUGRIjxpp7sC8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fM -m7v/OeZWYdMKp8RcTGB7BXcmer/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZu -MdRAGmI0Nj81Aa6sY6A= ------END CERTIFICATE----- - -# Issuer: CN=GeoTrust Primary Certification Authority - G2 O=GeoTrust Inc. OU=(c) 2007 GeoTrust Inc. - For authorized use only -# Subject: CN=GeoTrust Primary Certification Authority - G2 O=GeoTrust Inc. OU=(c) 2007 GeoTrust Inc. - For authorized use only -# Label: "GeoTrust Primary Certification Authority - G2" -# Serial: 80682863203381065782177908751794619243 -# MD5 Fingerprint: 01:5e:d8:6b:bd:6f:3d:8e:a1:31:f8:12:e0:98:73:6a -# SHA1 Fingerprint: 8d:17:84:d5:37:f3:03:7d:ec:70:fe:57:8b:51:9a:99:e6:10:d7:b0 -# SHA256 Fingerprint: 5e:db:7a:c4:3b:82:a0:6a:87:61:e8:d7:be:49:79:eb:f2:61:1f:7d:d7:9b:f9:1c:1c:6b:56:6a:21:9e:d7:66 ------BEGIN CERTIFICATE----- -MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDEL -MAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChj -KSAyMDA3IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2 -MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 -eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1OVowgZgxCzAJBgNV -BAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykgMjAw -NyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNV -BAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH -MjB2MBAGByqGSM49AgEGBSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcL -So17VDs6bl8VAsBQps8lL33KSLjHUGMcKiEIfJo22Av+0SbFWDEwKCXzXV2juLal -tJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO -BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+EVXVMAoG -CCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGT -qQ7mndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBucz -rD6ogRLQy7rQkgu2npaqBA+K ------END CERTIFICATE----- - -# Issuer: CN=VeriSign Universal Root Certification Authority O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2008 VeriSign, Inc. - For authorized use only -# Subject: CN=VeriSign Universal Root Certification Authority O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2008 VeriSign, Inc. - For authorized use only -# Label: "VeriSign Universal Root Certification Authority" -# Serial: 85209574734084581917763752644031726877 -# MD5 Fingerprint: 8e:ad:b5:01:aa:4d:81:e4:8c:1d:d1:e1:14:00:95:19 -# SHA1 Fingerprint: 36:79:ca:35:66:87:72:30:4d:30:a5:fb:87:3b:0f:a7:7b:b7:0d:54 -# SHA256 Fingerprint: 23:99:56:11:27:a5:71:25:de:8c:ef:ea:61:0d:df:2f:a0:78:b5:c8:06:7f:4e:82:82:90:bf:b8:60:e8:4b:3c ------BEGIN CERTIFICATE----- -MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCB -vTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL -ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJp -U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9W -ZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe -Fw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJVUzEX -MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0 -IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9y -IGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNh -bCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF -AAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj1mCOkdeQmIN65lgZOIzF -9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGPMiJhgsWH -H26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+H -LL729fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN -/BMReYTtXlT2NJ8IAfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPT -rJ9VAMf2CGqUuV/c4DPxhGD5WycRtPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1Ud -EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFsw -WTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgs -exkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud -DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4 -sAPmLGd75JR3Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+ -seQxIcaBlVZaDrHC1LGmWazxY8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz -4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTxP/jgdFcrGJ2BtMQo2pSXpXDrrB2+ -BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+PwGZsY6rp2aQW9IHR -lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3 -7M2CYfE45k+XmCpajQ== ------END CERTIFICATE----- - -# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G4 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2007 VeriSign, Inc. - For authorized use only -# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G4 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2007 VeriSign, Inc. - For authorized use only -# Label: "VeriSign Class 3 Public Primary Certification Authority - G4" -# Serial: 63143484348153506665311985501458640051 -# MD5 Fingerprint: 3a:52:e1:e7:fd:6f:3a:e3:6f:f3:6f:99:1b:f9:22:41 -# SHA1 Fingerprint: 22:d5:d8:df:8f:02:31:d1:8d:f7:9d:b7:cf:8a:2d:64:c9:3f:6c:3a -# SHA256 Fingerprint: 69:dd:d7:ea:90:bb:57:c9:3e:13:5d:c8:5e:a6:fc:d5:48:0b:60:32:39:bd:c4:54:fc:75:8b:2a:26:cf:7f:79 ------BEGIN CERTIFICATE----- -MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjEL -MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW -ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2ln -biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp -U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y -aXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjELMAkG -A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJp -U2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwg -SW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2ln -biBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 -IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8Utpkmw4tXNherJI9/gHm -GUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGzrl0Bp3ve -fLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUw -AwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJ -aW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYj -aHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMW -kf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMDA2gAMGUCMGYhDBgmYFo4e1ZC -4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIxAJw9SDkjOVga -FRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA== ------END CERTIFICATE----- - # Issuer: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services) # Subject: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services) # Label: "NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny" @@ -1559,47 +910,6 @@ uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2 XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= -----END CERTIFICATE----- -# Issuer: CN=Staat der Nederlanden Root CA - G2 O=Staat der Nederlanden -# Subject: CN=Staat der Nederlanden Root CA - G2 O=Staat der Nederlanden -# Label: "Staat der Nederlanden Root CA - G2" -# Serial: 10000012 -# MD5 Fingerprint: 7c:a5:0f:f8:5b:9a:7d:6d:30:ae:54:5a:e3:42:a2:8a -# SHA1 Fingerprint: 59:af:82:79:91:86:c7:b4:75:07:cb:cf:03:57:46:eb:04:dd:b7:16 -# SHA256 Fingerprint: 66:8c:83:94:7d:a6:3b:72:4b:ec:e1:74:3c:31:a0:e6:ae:d0:db:8e:c5:b3:1b:e3:77:bb:78:4f:91:b6:71:6f ------BEGIN CERTIFICATE----- -MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO -TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh -dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oX -DTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl -ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv -b3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ5291 -qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8Sp -uOUfiUtnvWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPU -Z5uW6M7XxgpT0GtJlvOjCwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvE -pMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiile7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp -5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCROME4HYYEhLoaJXhena/M -UGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpICT0ugpTN -GmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy -5V6548r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv -6q012iDTiIJh8BIitrzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEK -eN5KzlW/HdXZt1bv8Hb/C3m1r737qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6 -B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMBAAGjgZcwgZQwDwYDVR0TAQH/ -BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcCARYxaHR0cDov -L3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV -HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqG -SIb3DQEBCwUAA4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLyS -CZa59sCrI2AGeYwRTlHSeYAz+51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen -5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwjf/ST7ZwaUb7dRUG/kSS0H4zpX897 -IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaNkqbG9AclVMwWVxJK -gnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfkCpYL -+63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxL -vJxxcypFURmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkm -bEgeqmiSBeGCc1qb3AdbCG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvk -N1trSt8sV4pAWja63XVECDdCcAz+3F4hoKOKwJCcaNpQ5kUQR3i2TtJlycM33+FC -Y7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoVIPVVYpbtbZNQvOSqeK3Z -ywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm66+KAQ== ------END CERTIFICATE----- - # Issuer: CN=Hongkong Post Root CA 1 O=Hongkong Post # Subject: CN=Hongkong Post Root CA 1 O=Hongkong Post # Label: "Hongkong Post Root CA 1" @@ -1803,105 +1113,6 @@ naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== -----END CERTIFICATE----- -# Issuer: CN=Chambers of Commerce Root - 2008 O=AC Camerfirma S.A. -# Subject: CN=Chambers of Commerce Root - 2008 O=AC Camerfirma S.A. -# Label: "Chambers of Commerce Root - 2008" -# Serial: 11806822484801597146 -# MD5 Fingerprint: 5e:80:9e:84:5a:0e:65:0b:17:02:f3:55:18:2a:3e:d7 -# SHA1 Fingerprint: 78:6a:74:ac:76:ab:14:7f:9c:6a:30:50:ba:9e:a8:7e:fe:9a:ce:3c -# SHA256 Fingerprint: 06:3e:4a:fa:c4:91:df:d3:32:f3:08:9b:85:42:e9:46:17:d8:93:d7:fe:94:4e:10:a7:93:7e:e2:9d:96:93:c0 ------BEGIN CERTIFICATE----- -MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYD -VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0 -IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3 -MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xKTAnBgNVBAMTIENoYW1iZXJz -IG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEyMjk1MFoXDTM4MDcz -MTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBj -dXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIw -EAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEp -MCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0G -CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW9 -28sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKAXuFixrYp4YFs8r/lfTJq -VKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorjh40G072Q -DuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR -5gN/ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfL -ZEFHcpOrUMPrCXZkNNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05a -Sd+pZgvMPMZ4fKecHePOjlO+Bd5gD2vlGts/4+EhySnB8esHnFIbAURRPHsl18Tl -UlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331lubKgdaX8ZSD6e2wsWsSaR6s -+12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ0wlf2eOKNcx5 -Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj -ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAx -hduub+84Mxh2EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNV -HQ4EFgQU+SSsD7K1+HnA+mCIG8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1 -+HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpN -YWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29t -L2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVy -ZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAt -IDIwMDiCCQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRV -HSAAMCowKAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20w -DQYJKoZIhvcNAQEFBQADggIBAJASryI1wqM58C7e6bXpeHxIvj99RZJe6dqxGfwW -PJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH3qLPaYRgM+gQDROpI9CF -5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbURWpGqOt1 -glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaH -FoI6M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2 -pSB7+R5KBWIBpih1YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MD -xvbxrN8y8NmBGuScvfaAFPDRLLmF9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QG -tjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcKzBIKinmwPQN/aUv0NCB9szTq -jktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvGnrDQWzilm1De -fhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg -OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZ -d0jQ ------END CERTIFICATE----- - -# Issuer: CN=Global Chambersign Root - 2008 O=AC Camerfirma S.A. -# Subject: CN=Global Chambersign Root - 2008 O=AC Camerfirma S.A. -# Label: "Global Chambersign Root - 2008" -# Serial: 14541511773111788494 -# MD5 Fingerprint: 9e:80:ff:78:01:0c:2e:c1:36:bd:fe:96:90:6e:08:f3 -# SHA1 Fingerprint: 4a:bd:ee:ec:95:0d:35:9c:89:ae:c7:52:a1:2c:5b:29:f6:d6:aa:0c -# SHA256 Fingerprint: 13:63:35:43:93:34:a7:69:80:16:a0:d3:24:de:72:28:4e:07:9d:7b:52:20:bb:8f:bd:74:78:16:ee:be:ba:ca ------BEGIN CERTIFICATE----- -MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYD -VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0 -IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3 -MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD -aGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMxNDBaFw0zODA3MzEx -MjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3Vy -cmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAG -A1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAl -BgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZI -hvcNAQEBBQADggIPADCCAgoCggIBAMDfVtPkOpt2RbQT2//BthmLN0EYlVJH6xed -KYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXfXjaOcNFccUMd2drvXNL7 -G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0ZJJ0YPP2 -zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4 -ddPB/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyG -HoiMvvKRhI9lNNgATH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2 -Id3UwD2ln58fQ1DJu7xsepeY7s2MH/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3V -yJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfeOx2YItaswTXbo6Al/3K1dh3e -beksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSFHTynyQbehP9r -6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh -wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsog -zCtLkykPAgMBAAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQW -BBS5CcqcHtvTbDprru1U8VuTBjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDpr -ru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UEBhMCRVUxQzBBBgNVBAcTOk1hZHJp -ZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJmaXJtYS5jb20vYWRk -cmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJmaXJt -YSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiC -CQDJzdPp1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCow -KAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZI -hvcNAQEFBQADggIBAICIf3DekijZBZRG/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZ -UohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6ReAJ3spED8IXDneRRXoz -X1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/sdZ7LoR/x -fxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVz -a2Mg9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yyd -Yhz2rXzdpjEetrHHfoUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMd -SqlapskD7+3056huirRXhOukP9DuqqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9O -AP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETrP3iZ8ntxPjzxmKfFGBI/5rso -M0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVqc5iJWzouE4ge -v8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z -09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B ------END CERTIFICATE----- - # Issuer: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. # Subject: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. # Label: "Go Daddy Root Certificate Authority - G2" @@ -2200,6 +1411,45 @@ t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 -----END CERTIFICATE----- +# Issuer: CN=EC-ACC O=Agencia Catalana de Certificacio (NIF Q-0801176-I) OU=Serveis Publics de Certificacio/Vegeu https://www.catcert.net/verarrel (c)03/Jerarquia Entitats de Certificacio Catalanes +# Subject: CN=EC-ACC O=Agencia Catalana de Certificacio (NIF Q-0801176-I) OU=Serveis Publics de Certificacio/Vegeu https://www.catcert.net/verarrel (c)03/Jerarquia Entitats de Certificacio Catalanes +# Label: "EC-ACC" +# Serial: -23701579247955709139626555126524820479 +# MD5 Fingerprint: eb:f5:9d:29:0d:61:f9:42:1f:7c:c2:ba:6d:e3:15:09 +# SHA1 Fingerprint: 28:90:3a:63:5b:52:80:fa:e6:77:4c:0b:6d:a7:d6:ba:a6:4a:f2:e8 +# SHA256 Fingerprint: 88:49:7f:01:60:2f:31:54:24:6a:e2:8c:4d:5a:ef:10:f1:d8:7e:bb:76:62:6f:4a:e0:b7:f9:5b:a7:96:87:99 +-----BEGIN CERTIFICATE----- +MIIFVjCCBD6gAwIBAgIQ7is969Qh3hSoYqwE893EATANBgkqhkiG9w0BAQUFADCB +8zELMAkGA1UEBhMCRVMxOzA5BgNVBAoTMkFnZW5jaWEgQ2F0YWxhbmEgZGUgQ2Vy +dGlmaWNhY2lvIChOSUYgUS0wODAxMTc2LUkpMSgwJgYDVQQLEx9TZXJ2ZWlzIFB1 +YmxpY3MgZGUgQ2VydGlmaWNhY2lvMTUwMwYDVQQLEyxWZWdldSBodHRwczovL3d3 +dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAoYykwMzE1MDMGA1UECxMsSmVyYXJxdWlh +IEVudGl0YXRzIGRlIENlcnRpZmljYWNpbyBDYXRhbGFuZXMxDzANBgNVBAMTBkVD +LUFDQzAeFw0wMzAxMDcyMzAwMDBaFw0zMTAxMDcyMjU5NTlaMIHzMQswCQYDVQQG +EwJFUzE7MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8g +KE5JRiBRLTA4MDExNzYtSSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBD +ZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZlZ2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQu +bmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJhcnF1aWEgRW50aXRhdHMg +ZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUNDMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsyLHT+KXQpWIR4NA9h0X84NzJB5R +85iKw5K4/0CQBXCHYMkAqbWUZRkiFRfCQ2xmRJoNBD45b6VLeqpjt4pEndljkYRm +4CgPukLjbo73FCeTae6RDqNfDrHrZqJyTxIThmV6PttPB/SnCWDaOkKZx7J/sxaV +HMf5NLWUhdWZXqBIoH7nF2W4onW4HvPlQn2v7fOKSGRdghST2MDk/7NQcvJ29rNd +QlB50JQ+awwAvthrDk4q7D7SzIKiGGUzE3eeml0aE9jD2z3Il3rucO2n5nzbcc8t +lGLfbdb1OL4/pYUKGbio2Al1QnDE6u/LDsg0qBIimAy4E5S2S+zw0JDnJwIDAQAB +o4HjMIHgMB0GA1UdEQQWMBSBEmVjX2FjY0BjYXRjZXJ0Lm5ldDAPBgNVHRMBAf8E +BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUoMOLRKo3pUW/l4Ba0fF4 +opvpXY0wfwYDVR0gBHgwdjB0BgsrBgEEAfV4AQMBCjBlMCwGCCsGAQUFBwIBFiBo +dHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbDA1BggrBgEFBQcCAjApGidW +ZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAwDQYJKoZIhvcN +AQEFBQADggEBAKBIW4IB9k1IuDlVNZyAelOZ1Vr/sXE7zDkJlF7W2u++AVtd0x7Y +/X1PzaBB4DSTv8vihpw3kpBWHNzrKQXlxJ7HNd+KDM3FIUPpqojlNcAZQmNaAl6k +SBg6hW/cnbw/nZzBh7h6YQjpdwt/cKt63dmXLGQehb+8dJahw3oS7AwaboMMPOhy +Rp/7SNVel+axofjk70YllJyJ22k4vuxcDlbHZVHlUIiIv0LVKz3l+bqeLrPK9HOS +Agu+TGbrIP65y7WZf+a2E/rKS03Z7lNGBjvGTq2TWoF+bCpLagVFjPIhpDGQh2xl +nJ2lYJU6Un/10asIbvPuW/mIPX64b24D5EI= +-----END CERTIFICATE----- + # Issuer: CN=Hellenic Academic and Research Institutions RootCA 2011 O=Hellenic Academic and Research Institutions Cert. Authority # Subject: CN=Hellenic Academic and Research Institutions RootCA 2011 O=Hellenic Academic and Research Institutions Cert. Authority # Label: "Hellenic Academic and Research Institutions RootCA 2011" @@ -2274,35 +1524,6 @@ LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== -----END CERTIFICATE----- -# Issuer: O=Trustis Limited OU=Trustis FPS Root CA -# Subject: O=Trustis Limited OU=Trustis FPS Root CA -# Label: "Trustis FPS Root CA" -# Serial: 36053640375399034304724988975563710553 -# MD5 Fingerprint: 30:c9:e7:1e:6b:e6:14:eb:65:b2:16:69:20:31:67:4d -# SHA1 Fingerprint: 3b:c0:38:0b:33:c3:f6:a6:0c:86:15:22:93:d9:df:f5:4b:81:c0:04 -# SHA256 Fingerprint: c1:b4:82:99:ab:a5:20:8f:e9:63:0a:ce:55:ca:68:a0:3e:da:5a:51:9c:88:02:a0:d3:a6:73:be:8f:8e:55:7d ------BEGIN CERTIFICATE----- -MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBF -MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQL -ExNUcnVzdGlzIEZQUyBSb290IENBMB4XDTAzMTIyMzEyMTQwNloXDTI0MDEyMTEx -MzY1NFowRTELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1RydXN0aXMgTGltaXRlZDEc -MBoGA1UECxMTVHJ1c3RpcyBGUFMgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBAMVQe547NdDfxIzNjpvto8A2mfRC6qc+gIMPpqdZh8mQRUN+ -AOqGeSoDvT03mYlmt+WKVoaTnGhLaASMk5MCPjDSNzoiYYkchU59j9WvezX2fihH -iTHcDnlkH5nSW7r+f2C/revnPDgpai/lkQtV/+xvWNUtyd5MZnGPDNcE2gfmHhjj -vSkCqPoc4Vu5g6hBSLwacY3nYuUtsuvffM/bq1rKMfFMIvMFE/eC+XN5DL7XSxzA -0RU8k0Fk0ea+IxciAIleH2ulrG6nS4zto3Lmr2NNL4XSFDWaLk6M6jKYKIahkQlB -OrTh4/L68MkKokHdqeMDx4gVOxzUGpTXn2RZEm0CAwEAAaNTMFEwDwYDVR0TAQH/ -BAUwAwEB/zAfBgNVHSMEGDAWgBS6+nEleYtXQSUhhgtx67JkDoshZzAdBgNVHQ4E -FgQUuvpxJXmLV0ElIYYLceuyZA6LIWcwDQYJKoZIhvcNAQEFBQADggEBAH5Y//01 -GX2cGE+esCu8jowU/yyg2kdbw++BLa8F6nRIW/M+TgfHbcWzk88iNVy2P3UnXwmW -zaD+vkAMXBJV+JOCyinpXj9WV4s4NvdFGkwozZ5BuO1WTISkQMi4sKUraXAEasP4 -1BIy+Q7DsdwyhEQsb8tGD+pmQQ9P8Vilpg0ND2HepZ5dfWWhPBfnqFVO76DH7cZE -f1T1o+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHVl/9D7S3B2l0pKoU/rGXuhg8F -jZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYliB6XzCGcKQEN -ZetX2fNXlrtIzYE= ------END CERTIFICATE----- - # Issuer: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 # Subject: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 # Label: "Buypass Class 2 Root CA" @@ -2412,38 +1633,6 @@ e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p TpPDpFQUWw== -----END CERTIFICATE----- -# Issuer: CN=EE Certification Centre Root CA O=AS Sertifitseerimiskeskus -# Subject: CN=EE Certification Centre Root CA O=AS Sertifitseerimiskeskus -# Label: "EE Certification Centre Root CA" -# Serial: 112324828676200291871926431888494945866 -# MD5 Fingerprint: 43:5e:88:d4:7d:1a:4a:7e:fd:84:2e:52:eb:01:d4:6f -# SHA1 Fingerprint: c9:a8:b9:e7:55:80:5e:58:e3:53:77:a7:25:eb:af:c3:7b:27:cc:d7 -# SHA256 Fingerprint: 3e:84:ba:43:42:90:85:16:e7:75:73:c0:99:2f:09:79:ca:08:4e:46:85:68:1f:f1:95:cc:ba:8a:22:9b:8a:76 ------BEGIN CERTIFICATE----- -MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1 -MQswCQYDVQQGEwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1 -czEoMCYGA1UEAwwfRUUgQ2VydGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYG -CSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIwMTAxMDMwMTAxMDMwWhgPMjAzMDEy -MTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNl -ZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBS -b290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEB -AQUAA4IBDwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUy -euuOF0+W2Ap7kaJjbMeMTC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvO -bntl8jixwKIy72KyaOBhU8E2lf/slLo2rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIw -WFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw93X2PaRka9ZP585ArQ/d -MtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtNP2MbRMNE -1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYD -VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/ -zQas8fElyalL1BSZMEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYB -BQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEF -BQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+RjxY6hUFaTlrg4wCQiZrxTFGGV -v9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqMlIpPnTX/dqQG -E5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u -uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIW -iAYLtqZLICjU3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/v -GVCJYMzpJJUPwssd8m92kMfMdcGWxZ0= ------END CERTIFICATE----- - # Issuer: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH # Subject: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH # Label: "D-TRUST Root Class 3 CA 2 2009" @@ -3196,46 +2385,6 @@ KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg xwy8p2Fp8fc74SrL+SvzZpA3 -----END CERTIFICATE----- -# Issuer: CN=Staat der Nederlanden Root CA - G3 O=Staat der Nederlanden -# Subject: CN=Staat der Nederlanden Root CA - G3 O=Staat der Nederlanden -# Label: "Staat der Nederlanden Root CA - G3" -# Serial: 10003001 -# MD5 Fingerprint: 0b:46:67:07:db:10:2f:19:8c:35:50:60:d1:0b:f4:37 -# SHA1 Fingerprint: d8:eb:6b:41:51:92:59:e0:f3:e7:85:00:c0:3d:b6:88:97:c9:ee:fc -# SHA256 Fingerprint: 3c:4f:b0:b9:5a:b8:b3:00:32:f4:32:b8:6f:53:5f:e1:72:c1:85:d0:fd:39:86:58:37:cf:36:18:7f:a6:f4:28 ------BEGIN CERTIFICATE----- -MIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO -TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh -dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloX -DTI4MTExMzIzMDAwMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl -ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv -b3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL4yolQP -cPssXFnrbMSkUeiFKrPMSjTysF/zDsccPVMeiAho2G89rcKezIJnByeHaHE6n3WW -IkYFsO2tx1ueKt6c/DrGlaf1F2cY5y9JCAxcz+bMNO14+1Cx3Gsy8KL+tjzk7FqX -xz8ecAgwoNzFs21v0IJyEavSgWhZghe3eJJg+szeP4TrjTgzkApyI/o1zCZxMdFy -KJLZWyNtZrVtB0LrpjPOktvA9mxjeM3KTj215VKb8b475lRgsGYeCasH/lSJEULR -9yS6YHgamPfJEf0WwTUaVHXvQ9Plrk7O53vDxk5hUUurmkVLoR9BvUhTFXFkC4az -5S6+zqQbwSmEorXLCCN2QyIkHxcE1G6cxvx/K2Ya7Irl1s9N9WMJtxU51nus6+N8 -6U78dULI7ViVDAZCopz35HCz33JvWjdAidiFpNfxC95DGdRKWCyMijmev4SH8RY7 -Ngzp07TKbBlBUgmhHbBqv4LvcFEhMtwFdozL92TkA1CvjJFnq8Xy7ljY3r735zHP -bMk7ccHViLVlvMDoFxcHErVc0qsgk7TmgoNwNsXNo42ti+yjwUOH5kPiNL6VizXt -BznaqB16nzaeErAMZRKQFWDZJkBE41ZgpRDUajz9QdwOWke275dhdU/Z/seyHdTt -XUmzqWrLZoQT1Vyg3N9udwbRcXXIV2+vD3dbAgMBAAGjQjBAMA8GA1UdEwEB/wQF -MAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRUrfrHkleuyjWcLhL75Lpd -INyUVzANBgkqhkiG9w0BAQsFAAOCAgEAMJmdBTLIXg47mAE6iqTnB/d6+Oea31BD -U5cqPco8R5gu4RV78ZLzYdqQJRZlwJ9UXQ4DO1t3ApyEtg2YXzTdO2PCwyiBwpwp -LiniyMMB8jPqKqrMCQj3ZWfGzd/TtiunvczRDnBfuCPRy5FOCvTIeuXZYzbB1N/8 -Ipf3YF3qKS9Ysr1YvY2WTxB1v0h7PVGHoTx0IsL8B3+A3MSs/mrBcDCw6Y5p4ixp -gZQJut3+TcCDjJRYwEYgr5wfAvg1VUkvRtTA8KCWAg8zxXHzniN9lLf9OtMJgwYh -/WA9rjLA0u6NpvDntIJ8CsxwyXmA+P5M9zWEGYox+wrZ13+b8KKaa8MFSu1BYBQw -0aoRQm7TIwIEC8Zl3d1Sd9qBa7Ko+gE4uZbqKmxnl4mUnrzhVNXkanjvSr0rmj1A -fsbAddJu+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgSKL59NVzq -4BZ+Extq1z7XnvwtdbLBFNUjA9tbbws+eC8N3jONFrdI54OagQ97wUNNVQQXOEpR -1VmiiXTTn74eS9fGbbeIJG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0/ -QFH1T/U67cjF68IeHRaVesd+QnGTbksVtzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM -94B7IWcnMFk= ------END CERTIFICATE----- - # Issuer: CN=Staat der Nederlanden EV Root CA O=Staat der Nederlanden # Subject: CN=Staat der Nederlanden EV Root CA O=Staat der Nederlanden # Label: "Staat der Nederlanden EV Root CA" @@ -3453,46 +2602,6 @@ AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ 5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su -----END CERTIFICATE----- -# Issuer: CN=Certinomis - Root CA O=Certinomis OU=0002 433998903 -# Subject: CN=Certinomis - Root CA O=Certinomis OU=0002 433998903 -# Label: "Certinomis - Root CA" -# Serial: 1 -# MD5 Fingerprint: 14:0a:fd:8d:a8:28:b5:38:69:db:56:7e:61:22:03:3f -# SHA1 Fingerprint: 9d:70:bb:01:a5:a4:a0:18:11:2e:f7:1c:01:b9:32:c5:34:e7:88:a8 -# SHA256 Fingerprint: 2a:99:f5:bc:11:74:b7:3c:bb:1d:62:08:84:e0:1c:34:e5:1c:cb:39:78:da:12:5f:0e:33:26:88:83:bf:41:58 ------BEGIN CERTIFICATE----- -MIIFkjCCA3qgAwIBAgIBATANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJGUjET -MBEGA1UEChMKQ2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxHTAb -BgNVBAMTFENlcnRpbm9taXMgLSBSb290IENBMB4XDTEzMTAyMTA5MTcxOFoXDTMz -MTAyMTA5MTcxOFowWjELMAkGA1UEBhMCRlIxEzARBgNVBAoTCkNlcnRpbm9taXMx -FzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMR0wGwYDVQQDExRDZXJ0aW5vbWlzIC0g -Um9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANTMCQosP5L2 -fxSeC5yaah1AMGT9qt8OHgZbn1CF6s2Nq0Nn3rD6foCWnoR4kkjW4znuzuRZWJfl -LieY6pOod5tK8O90gC3rMB+12ceAnGInkYjwSond3IjmFPnVAy//ldu9n+ws+hQV -WZUKxkd8aRi5pwP5ynapz8dvtF4F/u7BUrJ1Mofs7SlmO/NKFoL21prbcpjp3vDF -TKWrteoB4owuZH9kb/2jJZOLyKIOSY008B/sWEUuNKqEUL3nskoTuLAPrjhdsKkb -5nPJWqHZZkCqqU2mNAKthH6yI8H7KsZn9DS2sJVqM09xRLWtwHkziOC/7aOgFLSc -CbAK42C++PhmiM1b8XcF4LVzbsF9Ri6OSyemzTUK/eVNfaoqoynHWmgE6OXWk6Ri -wsXm9E/G+Z8ajYJJGYrKWUM66A0ywfRMEwNvbqY/kXPLynNvEiCL7sCCeN5LLsJJ -wx3tFvYk9CcbXFcx3FXuqB5vbKziRcxXV4p1VxngtViZSTYxPDMBbRZKzbgqg4SG -m/lg0h9tkQPTYKbVPZrdd5A9NaSfD171UkRpucC63M9933zZxKyGIjK8e2uR73r4 -F2iw4lNVYC2vPsKD2NkJK/DAZNuHi5HMkesE/Xa0lZrmFAYb1TQdvtj/dBxThZng -WVJKYe2InmtJiUZ+IFrZ50rlau7SZRFDAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIB -BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTvkUz1pcMw6C8I6tNxIqSSaHh0 -2TAfBgNVHSMEGDAWgBTvkUz1pcMw6C8I6tNxIqSSaHh02TANBgkqhkiG9w0BAQsF -AAOCAgEAfj1U2iJdGlg+O1QnurrMyOMaauo++RLrVl89UM7g6kgmJs95Vn6RHJk/ -0KGRHCwPT5iVWVO90CLYiF2cN/z7ZMF4jIuaYAnq1fohX9B0ZedQxb8uuQsLrbWw -F6YSjNRieOpWauwK0kDDPAUwPk2Ut59KA9N9J0u2/kTO+hkzGm2kQtHdzMjI1xZS -g081lLMSVX3l4kLr5JyTCcBMWwerx20RoFAXlCOotQqSD7J6wWAsOMwaplv/8gzj -qh8c3LigkyfeY+N/IZ865Z764BNqdeuWXGKRlI5nU7aJ+BIJy29SWwNyhlCVCNSN -h4YVH5Uk2KRvms6knZtt0rJ2BobGVgjF6wnaNsIbW0G+YSrjcOa4pvi2WsS9Iff/ -ql+hbHY5ZtbqTFXhADObE5hjyW/QASAJN1LnDE8+zbz1X5YnpyACleAu6AdBBR8V -btaw5BngDwKTACdyxYvRVB9dSsNAl35VpnzBMwQUAR1JIGkLGZOdblgi90AMRgwj -Y/M50n92Uaf0yKHxDHYiI0ZSKS3io0EHVmmY0gUJvGnHWmHNj4FgFU2A3ZDifcRQ -8ow7bkrHxuaAKzyBvBGAFhAn1/DNP3nMcyrDflOR1m749fPH0FFNjkulW+YZFzvW -gQncItzujrnEj1PhZ7szuIgVRs/taTX/dQ1G885x4cVrhkIGuUE= ------END CERTIFICATE----- - # Issuer: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed # Subject: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed # Label: "OISTE WISeKey Global Root GB CA" @@ -3849,47 +2958,6 @@ CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW 1KyLa2tJElMzrdfkviT8tQp21KW8EA== -----END CERTIFICATE----- -# Issuer: CN=LuxTrust Global Root 2 O=LuxTrust S.A. -# Subject: CN=LuxTrust Global Root 2 O=LuxTrust S.A. -# Label: "LuxTrust Global Root 2" -# Serial: 59914338225734147123941058376788110305822489521 -# MD5 Fingerprint: b2:e1:09:00:61:af:f7:f1:91:6f:c4:ad:8d:5e:3b:7c -# SHA1 Fingerprint: 1e:0e:56:19:0a:d1:8b:25:98:b2:04:44:ff:66:8a:04:17:99:5f:3f -# SHA256 Fingerprint: 54:45:5f:71:29:c2:0b:14:47:c4:18:f9:97:16:8f:24:c5:8f:c5:02:3b:f5:da:5b:e2:eb:6e:1d:d8:90:2e:d5 ------BEGIN CERTIFICATE----- -MIIFwzCCA6ugAwIBAgIUCn6m30tEntpqJIWe5rgV0xZ/u7EwDQYJKoZIhvcNAQEL -BQAwRjELMAkGA1UEBhMCTFUxFjAUBgNVBAoMDUx1eFRydXN0IFMuQS4xHzAdBgNV -BAMMFkx1eFRydXN0IEdsb2JhbCBSb290IDIwHhcNMTUwMzA1MTMyMTU3WhcNMzUw -MzA1MTMyMTU3WjBGMQswCQYDVQQGEwJMVTEWMBQGA1UECgwNTHV4VHJ1c3QgUy5B -LjEfMB0GA1UEAwwWTHV4VHJ1c3QgR2xvYmFsIFJvb3QgMjCCAiIwDQYJKoZIhvcN -AQEBBQADggIPADCCAgoCggIBANeFl78RmOnwYoNMPIf5U2o3C/IPPIfOb9wmKb3F -ibrJgz337spbxm1Jc7TJRqMbNBM/wYlFV/TZsfs2ZUv7COJIcRHIbjuend+JZTem -hfY7RBi2xjcwYkSSl2l9QjAk5A0MiWtj3sXh306pFGxT4GHO9hcvHTy95iJMHZP1 -EMShduxq3sVs35a0VkBCwGKSMKEtFZSg0iAGCW5qbeXrt77U8PEVfIvmTroTzEsn -Xpk8F12PgX8zPU/TPxvsXD/wPEx1bvKm1Z3aLQdjAsZy6ZS8TEmVT4hSyNvoaYL4 -zDRbIvCGp4m9SAptZoFtyMhk+wHh9OHe2Z7d21vUKpkmFRseTJIpgp7VkoGSQXAZ -96Tlk0u8d2cx3Rz9MXANF5kM+Qw5GSoXtTBxVdUPrljhPS80m8+f9niFwpN6cj5m -j5wWEWCPnolvZ77gR1o7DJpni89Gxq44o/KnvObWhWszJHAiS8sIm7vI+AIpHb4g -DEa/a4ebsypmQjVGbKq6rfmYe+lQVRQxv7HaLe2ArWgk+2mr2HETMOZns4dA/Yl+ -8kPREd8vZS9kzl8UubG/Mb2HeFpZZYiq/FkySIbWTLkpS5XTdvN3JW1CHDiDTf2j -X5t/Lax5Gw5CMZdjpPuKadUiDTSQMC6otOBttpSsvItO13D8xTiOZCXhTTmQzsmH -hFhxAgMBAAGjgagwgaUwDwYDVR0TAQH/BAUwAwEB/zBCBgNVHSAEOzA5MDcGByuB -KwEBAQowLDAqBggrBgEFBQcCARYeaHR0cHM6Ly9yZXBvc2l0b3J5Lmx1eHRydXN0 -Lmx1MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBT/GCh2+UgFLKGu8SsbK7JT -+Et8szAdBgNVHQ4EFgQU/xgodvlIBSyhrvErGyuyU/hLfLMwDQYJKoZIhvcNAQEL -BQADggIBAGoZFO1uecEsh9QNcH7X9njJCwROxLHOk3D+sFTAMs2ZMGQXvw/l4jP9 -BzZAcg4atmpZ1gDlaCDdLnINH2pkMSCEfUmmWjfrRcmF9dTHF5kH5ptV5AzoqbTO -jFu1EVzPig4N1qx3gf4ynCSecs5U89BvolbW7MM3LGVYvlcAGvI1+ut7MV3CwRI9 -loGIlonBWVx65n9wNOeD4rHh4bhY79SV5GCc8JaXcozrhAIuZY+kt9J/Z93I055c -qqmkoCUUBpvsT34tC38ddfEz2O3OuHVtPlu5mB0xDVbYQw8wkbIEa91WvpWAVWe+ -2M2D2RjuLg+GLZKecBPs3lHJQ3gCpU3I+V/EkVhGFndadKpAvAefMLmx9xIX3eP/ -JEAdemrRTxgKqpAd60Ae36EeRJIQmvKN4dFLRp7oRUKX6kWZ8+xm1QL68qZKJKre -zrnK+T+Tb/mjuuqlPpmt/f97mfVl7vBZKGfXkJWkE4SphMHozs51k2MavDzq1WQf -LSoSOcbDWjLtR5EWDrw4wVDej8oqkDQc7kGUnF4ZLvhFSZl0kbAEb+MEWrGrKqv+ -x9CWttrhSmQGbmBNvUJO/3jaJMobtNeWOWyu8Q6qp31IiyBMz2TWuJdGsE7RKlY6 -oJO9r4Ak4Ap+58rVyuiFVdw2KuGUaJPHZnJED4AhMmwlxyOAgwrr ------END CERTIFICATE----- - # Issuer: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM # Subject: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM # Label: "TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1" @@ -4510,3 +3578,680 @@ Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayh jWZSaX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw 3kAP+HwV96LOPNdeE4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0= -----END CERTIFICATE----- + +# Issuer: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI +# Subject: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI +# Label: "emSign Root CA - G1" +# Serial: 235931866688319308814040 +# MD5 Fingerprint: 9c:42:84:57:dd:cb:0b:a7:2e:95:ad:b6:f3:da:bc:ac +# SHA1 Fingerprint: 8a:c7:ad:8f:73:ac:4e:c1:b5:75:4d:a5:40:f4:fc:cf:7c:b5:8e:8c +# SHA256 Fingerprint: 40:f6:af:03:46:a9:9a:a1:cd:1d:55:5a:4e:9c:ce:62:c7:f9:63:46:03:ee:40:66:15:83:3d:c8:c8:d0:03:67 +-----BEGIN CERTIFICATE----- +MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYD +VQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBU +ZWNobm9sb2dpZXMgTGltaXRlZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBH +MTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgxODMwMDBaMGcxCzAJBgNVBAYTAklO +MRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xv +Z2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQz +f2N4aLTNLnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO +8oG0x5ZOrRkVUkr+PHB1cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aq +d7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHWDV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhM +tTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ6DqS0hdW5TUaQBw+jSzt +Od9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrHhQIDAQAB +o0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQD +AgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31x +PaOfG1vR2vjTnGs2vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjM +wiI/aTvFthUvozXGaCocV685743QNcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6d +GNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q+Mri/Tm3R7nrft8EI6/6nAYH +6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeihU80Bv2noWgby +RQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx +iN66zB+Afko= +-----END CERTIFICATE----- + +# Issuer: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI +# Subject: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI +# Label: "emSign ECC Root CA - G3" +# Serial: 287880440101571086945156 +# MD5 Fingerprint: ce:0b:72:d1:9f:88:8e:d0:50:03:e8:e3:b8:8b:67:40 +# SHA1 Fingerprint: 30:43:fa:4f:f2:57:dc:a0:c3:80:ee:2e:58:ea:78:b2:3f:e6:bb:c1 +# SHA256 Fingerprint: 86:a1:ec:ba:08:9c:4a:8d:3b:be:27:34:c6:12:ba:34:1d:81:3e:04:3c:f9:e8:a8:62:cd:5c:57:a3:6b:be:6b +-----BEGIN CERTIFICATE----- +MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG +EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo +bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g +RzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4MTgzMDAwWjBrMQswCQYDVQQGEwJJ +TjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9s +b2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0 +WXTsuwYc58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xyS +fvalY8L1X44uT6EYGQIrMgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuB +zhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq +hkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB +CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD ++JbNR6iC8hZVdyR+EhCVBCyj +-----END CERTIFICATE----- + +# Issuer: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI +# Subject: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI +# Label: "emSign Root CA - C1" +# Serial: 825510296613316004955058 +# MD5 Fingerprint: d8:e3:5d:01:21:fa:78:5a:b0:df:ba:d2:ee:2a:5f:68 +# SHA1 Fingerprint: e7:2e:f1:df:fc:b2:09:28:cf:5d:d4:d5:67:37:b1:51:cb:86:4f:01 +# SHA256 Fingerprint: 12:56:09:aa:30:1d:a0:a2:49:b9:7a:82:39:cb:6a:34:21:6f:44:dc:ac:9f:39:54:b1:42:92:f2:e8:c8:60:8f +-----BEGIN CERTIFICATE----- +MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkG +A1UEBhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEg +SW5jMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAw +MFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln +biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNpZ24gUm9v +dCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+upufGZ +BczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZ +HdPIWoU/Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH +3DspVpNqs8FqOp099cGXOFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvH +GPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4VI5b2P/AgNBbeCsbEBEV5f6f9vtKppa+c +xSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleoomslMuoaJuvimUnzYnu3Yy1 +aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+XJGFehiq +TbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87 +/kOXSTKZEhVb3xEp/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4 +kqNPEjE2NuLe/gDEo2APJ62gsIq1NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrG +YQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9wC68AivTxEDkigcxHpvOJpkT ++xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQBmIMMMAVSKeo +WXzhriKi4gp6D/piq1JM4fHfyr6DDUI= +-----END CERTIFICATE----- + +# Issuer: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI +# Subject: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI +# Label: "emSign ECC Root CA - C3" +# Serial: 582948710642506000014504 +# MD5 Fingerprint: 3e:53:b3:a3:81:ee:d7:10:f8:d3:b0:1d:17:92:f5:d5 +# SHA1 Fingerprint: b6:af:43:c2:9b:81:53:7d:f6:ef:6b:c3:1f:1f:60:15:0c:ee:48:66 +# SHA256 Fingerprint: bc:4d:80:9b:15:18:9d:78:db:3e:1d:8c:f4:f9:72:6a:79:5d:a1:64:3c:a5:f1:35:8e:1d:db:0e:dc:0d:7e:b3 +-----BEGIN CERTIFICATE----- +MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQG +EwJVUzETMBEGA1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMx +IDAeBgNVBAMTF2VtU2lnbiBFQ0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAw +MFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln +biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQDExdlbVNpZ24gRUND +IFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd6bci +MK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4Ojavti +sIGJAnB9SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0O +BBYEFPtaSNCAIEDyqOkAB2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB +Af8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQC02C8Cif22TGK6Q04ThHK1rt0c +3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwUZOR8loMRnLDRWmFLpg9J +0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ== +-----END CERTIFICATE----- + +# Issuer: CN=Hongkong Post Root CA 3 O=Hongkong Post +# Subject: CN=Hongkong Post Root CA 3 O=Hongkong Post +# Label: "Hongkong Post Root CA 3" +# Serial: 46170865288971385588281144162979347873371282084 +# MD5 Fingerprint: 11:fc:9f:bd:73:30:02:8a:fd:3f:f3:58:b9:cb:20:f0 +# SHA1 Fingerprint: 58:a2:d0:ec:20:52:81:5b:c1:f3:f8:64:02:24:4e:c2:8e:02:4b:02 +# SHA256 Fingerprint: 5a:2f:c0:3f:0c:83:b0:90:bb:fa:40:60:4b:09:88:44:6c:76:36:18:3d:f9:84:6e:17:10:1a:44:7f:b8:ef:d6 +-----BEGIN CERTIFICATE----- +MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQEL +BQAwbzELMAkGA1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJ +SG9uZyBLb25nMRYwFAYDVQQKEw1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25n +a29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2MDMwMjI5NDZaFw00MjA2MDMwMjI5 +NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtvbmcxEjAQBgNVBAcT +CUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMXSG9u +Z2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCziNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFO +dem1p+/l6TWZ5Mwc50tfjTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mI +VoBc+L0sPOFMV4i707mV78vH9toxdCim5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV +9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOesL4jpNrcyCse2m5FHomY +2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj0mRiikKY +vLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+Tt +bNe/JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZb +x39ri1UbSsUgYT2uy1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+ +l2oBlKN8W4UdKjk60FSh0Tlxnf0h+bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YK +TE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsGxVd7GYYKecsAyVKvQv83j+Gj +Hno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwIDAQABo2MwYTAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e +i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEw +DQYJKoZIhvcNAQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG +7BJ8dNVI0lkUmcDrudHr9EgwW62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCk +MpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWldy8joRTnU+kLBEUx3XZL7av9YROXr +gZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov+BS5gLNdTaqX4fnk +GMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDceqFS +3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJm +Ozj/2ZQw9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+ +l6mc1X5VTMbeRRAc6uk7nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6c +JfTzPV4e0hz5sy229zdcxsshTrD3mUcYhcErulWuBurQB7Lcq9CClnXO0lD+mefP +L5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB60PZ2Pierc+xYw5F9KBa +LJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fqdBb9HxEG +mpv0 +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority - G4 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2015 Entrust, Inc. - for authorized use only +# Subject: CN=Entrust Root Certification Authority - G4 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2015 Entrust, Inc. - for authorized use only +# Label: "Entrust Root Certification Authority - G4" +# Serial: 289383649854506086828220374796556676440 +# MD5 Fingerprint: 89:53:f1:83:23:b7:7c:8e:05:f1:8c:71:38:4e:1f:88 +# SHA1 Fingerprint: 14:88:4e:86:26:37:b0:26:af:59:62:5c:40:77:ec:35:29:ba:96:01 +# SHA256 Fingerprint: db:35:17:d1:f6:73:2a:2d:5a:b9:7c:53:3e:c7:07:79:ee:32:70:a6:2f:b4:ac:42:38:37:24:60:e6:f0:1e:88 +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIRANm1Q3+vqTkPAAAAAFVlrVgwDQYJKoZIhvcNAQELBQAw +gb4xCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQL +Ex9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykg +MjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMjAw +BgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEc0 +MB4XDTE1MDUyNzExMTExNloXDTM3MTIyNzExNDExNlowgb4xCzAJBgNVBAYTAlVT +MRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1 +c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJ +bmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3Qg +Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEc0MIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAsewsQu7i0TD/pZJH4i3DumSXbcr3DbVZwbPLqGgZ +2K+EbTBwXX7zLtJTmeH+H17ZSK9dE43b/2MzTdMAArzE+NEGCJR5WIoV3imz/f3E +T+iq4qA7ec2/a0My3dl0ELn39GjUu9CH1apLiipvKgS1sqbHoHrmSKvS0VnM1n4j +5pds8ELl3FFLFUHtSUrJ3hCX1nbB76W1NhSXNdh4IjVS70O92yfbYVaCNNzLiGAM +C1rlLAHGVK/XqsEQe9IFWrhAnoanw5CGAlZSCXqc0ieCU0plUmr1POeo8pyvi73T +DtTUXm6Hnmo9RR3RXRv06QqsYJn7ibT/mCzPfB3pAqoEmh643IhuJbNsZvc8kPNX +wbMv9W3y+8qh+CmdRouzavbmZwe+LGcKKh9asj5XxNMhIWNlUpEbsZmOeX7m640A +2Vqq6nPopIICR5b+W45UYaPrL0swsIsjdXJ8ITzI9vF01Bx7owVV7rtNOzK+mndm +nqxpkCIHH2E6lr7lmk/MBTwoWdPBDFSoWWG9yHJM6Nyfh3+9nEg2XpWjDrk4JFX8 +dWbrAuMINClKxuMrLzOg2qOGpRKX/YAr2hRC45K9PvJdXmd0LhyIRyk0X+IyqJwl +N4y6mACXi0mWHv0liqzc2thddG5msP9E36EYxr5ILzeUePiVSj9/E15dWf10hkNj +c0kCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFJ84xFYjwznooHFs6FRM5Og6sb9nMA0GCSqGSIb3DQEBCwUAA4ICAQAS +5UKme4sPDORGpbZgQIeMJX6tuGguW8ZAdjwD+MlZ9POrYs4QjbRaZIxowLByQzTS +Gwv2LFPSypBLhmb8qoMi9IsabyZIrHZ3CL/FmFz0Jomee8O5ZDIBf9PD3Vht7LGr +hFV0d4QEJ1JrhkzO3bll/9bGXp+aEJlLdWr+aumXIOTkdnrG0CSqkM0gkLpHZPt/ +B7NTeLUKYvJzQ85BK4FqLoUWlFPUa19yIqtRLULVAJyZv967lDtX/Zr1hstWO1uI +AeV8KEsD+UmDfLJ/fOPtjqF/YFOOVZ1QNBIPt5d7bIdKROf1beyAN/BYGW5KaHbw +H5Lk6rWS02FREAutp9lfx1/cH6NcjKF+m7ee01ZvZl4HliDtC3T7Zk6LERXpgUl+ +b7DUUH8i119lAg2m9IUe2K4GS0qn0jFmwvjO5QimpAKWRGhXxNUzzxkvFMSUHHuk +2fCfDrGA4tGeEWSpiBE6doLlYsKA2KSD7ZPvfC+QsDJMlhVoSFLUmQjAJOgc47Ol +IQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk +5F6G+TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuY +n/PIjhs4ViFqUZPTkcpG2om3PVODLAgfi49T3f+sHw== +-----END CERTIFICATE----- + +# Issuer: CN=Microsoft ECC Root Certificate Authority 2017 O=Microsoft Corporation +# Subject: CN=Microsoft ECC Root Certificate Authority 2017 O=Microsoft Corporation +# Label: "Microsoft ECC Root Certificate Authority 2017" +# Serial: 136839042543790627607696632466672567020 +# MD5 Fingerprint: dd:a1:03:e6:4a:93:10:d1:bf:f0:19:42:cb:fe:ed:67 +# SHA1 Fingerprint: 99:9a:64:c3:7f:f4:7d:9f:ab:95:f1:47:69:89:14:60:ee:c4:c3:c5 +# SHA256 Fingerprint: 35:8d:f3:9d:76:4a:f9:e1:b7:66:e9:c9:72:df:35:2e:e1:5c:fa:c2:27:af:6a:d1:d7:0e:8e:4a:6e:dc:ba:02 +-----BEGIN CERTIFICATE----- +MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYD +VQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIw +MTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4MjMxNjA0WjBlMQswCQYDVQQGEwJV +UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNy +b3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZR +ogPZnZH6thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYb +hGBKia/teQ87zvH2RPUBeMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBTIy5lycFIM+Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3 +FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlfXu5gKcs68tvWMoQZP3zV +L8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaReNtUjGUB +iudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M= +-----END CERTIFICATE----- + +# Issuer: CN=Microsoft RSA Root Certificate Authority 2017 O=Microsoft Corporation +# Subject: CN=Microsoft RSA Root Certificate Authority 2017 O=Microsoft Corporation +# Label: "Microsoft RSA Root Certificate Authority 2017" +# Serial: 40975477897264996090493496164228220339 +# MD5 Fingerprint: 10:ff:00:ff:cf:c9:f8:c7:7a:c0:ee:35:8e:c9:0f:47 +# SHA1 Fingerprint: 73:a5:e6:4a:3b:ff:83:16:ff:0e:dc:cc:61:8a:90:6e:4e:ae:4d:74 +# SHA256 Fingerprint: c7:41:f7:0f:4b:2a:8d:88:bf:2e:71:c1:41:22:ef:53:ef:10:eb:a0:cf:a5:e6:4c:fa:20:f4:18:85:30:73:e0 +-----BEGIN CERTIFICATE----- +MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBl +MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw +NAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 +IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIwNzE4MjMwMDIzWjBlMQswCQYDVQQG +EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1N +aWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZ +Nt9GkMml7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0 +ZdDMbRnMlfl7rEqUrQ7eS0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1 +HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw71VdyvD/IybLeS2v4I2wDwAW9lcfNcztm +gGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+dkC0zVJhUXAoP8XFWvLJ +jEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49FyGcohJUc +aDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaG +YaRSMLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6 +W6IYZVcSn2i51BVrlMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4K +UGsTuqwPN1q3ErWQgR5WrlcihtnJ0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH ++FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJClTUFLkqqNfs+avNJVgyeY+Q +W5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZC +LgLNFgVZJ8og6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OC +gMNPOsduET/m4xaRhPtthH80dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6 +tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk+ONVFT24bcMKpBLBaYVu32TxU5nh +SnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex/2kskZGT4d9Mozd2 +TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDyAmH3 +pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGR +xpl/j8nWZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiApp +GWSZI1b7rCoucL5mxAyE7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9 +dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKTc0QWbej09+CVgI+WXTik9KveCjCHk9hN +AHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D5KbvtwEwXlGjefVwaaZB +RA+GsCyRxj3qrg+E +-----END CERTIFICATE----- + +# Issuer: CN=e-Szigno Root CA 2017 O=Microsec Ltd. +# Subject: CN=e-Szigno Root CA 2017 O=Microsec Ltd. +# Label: "e-Szigno Root CA 2017" +# Serial: 411379200276854331539784714 +# MD5 Fingerprint: de:1f:f6:9e:84:ae:a7:b4:21:ce:1e:58:7d:d1:84:98 +# SHA1 Fingerprint: 89:d4:83:03:4f:9e:9a:48:80:5f:72:37:d4:a9:a6:ef:cb:7c:1f:d1 +# SHA256 Fingerprint: be:b0:0b:30:83:9b:9b:c3:2c:32:e4:44:79:05:95:06:41:f2:64:21:b1:5e:d0:89:19:8b:51:8a:e2:ea:1b:99 +-----BEGIN CERTIFICATE----- +MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNV +BAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRk +LjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJv +b3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZaFw00MjA4MjIxMjA3MDZaMHExCzAJ +BgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMg +THRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25v +IFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtv +xie+RJCxs1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+H +Wyx7xf58etqjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBSHERUI0arBeAyxr87GyZDvvzAEwDAfBgNVHSMEGDAWgBSHERUI0arB +eAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEAtVfd14pVCzbhhkT61Nlo +jbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxOsvxyqltZ ++efcMQ== +-----END CERTIFICATE----- + +# Issuer: O=CERTSIGN SA OU=certSIGN ROOT CA G2 +# Subject: O=CERTSIGN SA OU=certSIGN ROOT CA G2 +# Label: "certSIGN Root CA G2" +# Serial: 313609486401300475190 +# MD5 Fingerprint: 8c:f1:75:8a:c6:19:cf:94:b7:f7:65:20:87:c3:97:c7 +# SHA1 Fingerprint: 26:f9:93:b4:ed:3d:28:27:b0:b9:4b:a7:e9:15:1d:a3:8d:92:e5:32 +# SHA256 Fingerprint: 65:7c:fe:2f:a7:3f:aa:38:46:25:71:f3:32:a2:36:3a:46:fc:e7:02:09:51:71:07:02:cd:fb:b6:ee:da:33:05 +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNV +BAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04g +Uk9PVCBDQSBHMjAeFw0xNzAyMDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJ +BgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJ +R04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDF +dRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05N0Iw +vlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZ +uIt4ImfkabBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhp +n+Sc8CnTXPnGFiWeI8MgwT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKs +cpc/I1mbySKEwQdPzH/iV8oScLumZfNpdWO9lfsbl83kqK/20U6o2YpxJM02PbyW +xPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91QqhngLjYl/rNUssuHLoPj1P +rCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732jcZZroiF +DsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fx +DTvf95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgy +LcsUDFDYg2WD7rlcz8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6C +eWRgKRM+o/1Pcmqr4tTluCRVLERLiohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSCIS1mxteg4BXrzkwJ +d8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOBywaK8SJJ6ejq +kX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC +b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQl +qiCA2ClV9+BB/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0 +OJD7uNGzcgbJceaBxXntC6Z58hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+c +NywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5BiKDUyUM/FHE5r7iOZULJK2v0ZXk +ltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklWatKcsWMy5WHgUyIO +pwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tUSxfj +03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZk +PuXaTH4MNMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE +1LlSVHJ7liXMvGnjSG4N0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MX +QRBdJ3NghVdJIgc= +-----END CERTIFICATE----- + +# Issuer: CN=Trustwave Global Certification Authority O=Trustwave Holdings, Inc. +# Subject: CN=Trustwave Global Certification Authority O=Trustwave Holdings, Inc. +# Label: "Trustwave Global Certification Authority" +# Serial: 1846098327275375458322922162 +# MD5 Fingerprint: f8:1c:18:2d:2f:ba:5f:6d:a1:6c:bc:c7:ab:91:c7:0e +# SHA1 Fingerprint: 2f:8f:36:4f:e1:58:97:44:21:59:87:a5:2a:9a:d0:69:95:26:7f:b5 +# SHA256 Fingerprint: 97:55:20:15:f5:dd:fc:3c:87:88:c0:06:94:45:55:40:88:94:45:00:84:f1:00:86:70:86:bc:1a:2b:b5:8d:c8 +-----BEGIN CERTIFICATE----- +MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQsw +CQYDVQQGEwJVUzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28x +ITAfBgNVBAoMGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1 +c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMx +OTM0MTJaFw00MjA4MjMxOTM0MTJaMIGIMQswCQYDVQQGEwJVUzERMA8GA1UECAwI +SWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2ZSBI +b2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +ALldUShLPDeS0YLOvR29zd24q88KPuFd5dyqCblXAj7mY2Hf8g+CY66j96xz0Xzn +swuvCAAJWX/NKSqIk4cXGIDtiLK0thAfLdZfVaITXdHG6wZWiYj+rDKd/VzDBcdu +7oaJuogDnXIhhpCujwOl3J+IKMujkkkP7NAP4m1ET4BqstTnoApTAbqOl5F2brz8 +1Ws25kCI1nsvXwXoLG0R8+eyvpJETNKXpP7ScoFDB5zpET71ixpZfR9oWN0EACyW +80OzfpgZdNmcc9kYvkHHNHnZ9GLCQ7mzJ7Aiy/k9UscwR7PJPrhq4ufogXBeQotP +JqX+OsIgbrv4Fo7NDKm0G2x2EOFYeUY+VM6AqFcJNykbmROPDMjWLBz7BegIlT1l +RtzuzWniTY+HKE40Cz7PFNm73bZQmq131BnW2hqIyE4bJ3XYsgjxroMwuREOzYfw +hI0Vcnyh78zyiGG69Gm7DIwLdVcEuE4qFC49DxweMqZiNu5m4iK4BUBjECLzMx10 +coos9TkpoNPnG4CELcU9402x/RpvumUHO1jsQkUm+9jaJXLE9gCxInm943xZYkqc +BW89zubWR2OZxiRvchLIrH+QtAuRcOi35hYQcRfO3gZPSEF9NUqjifLJS3tBEW1n +twiYTOURGa5CgNz7kAXU+FDKvuStx8KU1xad5hePrzb7AgMBAAGjQjBAMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFJngGWcNYtt2s9o9uFvo/ULSMQ6HMA4GA1Ud +DwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAmHNw4rDT7TnsTGDZqRKGFx6W +0OhUKDtkLSGm+J1WE2pIPU/HPinbbViDVD2HfSMF1OQc3Og4ZYbFdada2zUFvXfe +uyk3QAUHw5RSn8pk3fEbK9xGChACMf1KaA0HZJDmHvUqoai7PF35owgLEQzxPy0Q +lG/+4jSHg9bP5Rs1bdID4bANqKCqRieCNqcVtgimQlRXtpla4gt5kNdXElE1GYhB +aCXUNxeEFfsBctyV3lImIJgm4nb1J2/6ADtKYdkNy1GTKv0WBpanI5ojSP5RvbbE +sLFUzt5sQa0WZ37b/TjNuThOssFgy50X31ieemKyJo90lZvkWx3SD92YHJtZuSPT +MaCm/zjdzyBP6VhWOmfD0faZmZ26NraAL4hHT4a/RDqA5Dccprrql5gR0IRiR2Qe +qu5AvzSxnI9O4fKSTx+O856X3vOmeWqJcU9LJxdI/uz0UA9PSX3MReO9ekDFQdxh +VicGaeVyQYHTtgGJoC86cnn+OjC/QezHYj6RS8fZMXZC+fc8Y+wmjHMMfRod6qh8 +h6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu3R3y4G5OBVixwJAWKqQ9 +EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP29FpHOTK +yeC2nOnOcXHebD8WpHk= +-----END CERTIFICATE----- + +# Issuer: CN=Trustwave Global ECC P256 Certification Authority O=Trustwave Holdings, Inc. +# Subject: CN=Trustwave Global ECC P256 Certification Authority O=Trustwave Holdings, Inc. +# Label: "Trustwave Global ECC P256 Certification Authority" +# Serial: 4151900041497450638097112925 +# MD5 Fingerprint: 5b:44:e3:8d:5d:36:86:26:e8:0d:05:d2:59:a7:83:54 +# SHA1 Fingerprint: b4:90:82:dd:45:0c:be:8b:5b:b1:66:d3:e2:a4:08:26:cd:ed:42:cf +# SHA256 Fingerprint: 94:5b:bc:82:5e:a5:54:f4:89:d1:fd:51:a7:3d:df:2e:a6:24:ac:70:19:a0:52:05:22:5c:22:a7:8c:cf:a8:b4 +-----BEGIN CERTIFICATE----- +MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf +BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3 +YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x +NzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYDVQQGEwJVUzERMA8G +A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0 +d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF +Q0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqG +SM49AwEHA0IABH77bOYj43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoN +FWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqmP62jQzBBMA8GA1UdEwEB/wQFMAMBAf8w +DwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt0UrrdaVKEJmzsaGLSvcw +CgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjzRM4q3wgh +DDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7 +-----END CERTIFICATE----- + +# Issuer: CN=Trustwave Global ECC P384 Certification Authority O=Trustwave Holdings, Inc. +# Subject: CN=Trustwave Global ECC P384 Certification Authority O=Trustwave Holdings, Inc. +# Label: "Trustwave Global ECC P384 Certification Authority" +# Serial: 2704997926503831671788816187 +# MD5 Fingerprint: ea:cf:60:c4:3b:b9:15:29:40:a1:97:ed:78:27:93:d6 +# SHA1 Fingerprint: e7:f3:a3:c8:cf:6f:c3:04:2e:6d:0e:67:32:c5:9e:68:95:0d:5e:d2 +# SHA256 Fingerprint: 55:90:38:59:c8:c0:c3:eb:b8:75:9e:ce:4e:25:57:22:5f:f5:75:8b:bd:38:eb:d4:82:76:60:1e:1b:d5:80:97 +-----BEGIN CERTIFICATE----- +MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf +BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3 +YXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x +NzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYDVQQGEwJVUzERMA8G +A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0 +d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF +Q0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuB +BAAiA2IABGvaDXU1CDFHBa5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJ +j9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr/TklZvFe/oyujUF5nQlgziip04pt89ZF +1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNVHQ8BAf8EBQMDBwYAMB0G +A1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNnADBkAjA3 +AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsC +MGclCrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVu +Sw== +-----END CERTIFICATE----- + +# Issuer: CN=NAVER Global Root Certification Authority O=NAVER BUSINESS PLATFORM Corp. +# Subject: CN=NAVER Global Root Certification Authority O=NAVER BUSINESS PLATFORM Corp. +# Label: "NAVER Global Root Certification Authority" +# Serial: 9013692873798656336226253319739695165984492813 +# MD5 Fingerprint: c8:7e:41:f6:25:3b:f5:09:b3:17:e8:46:3d:bf:d0:9b +# SHA1 Fingerprint: 8f:6b:f2:a9:27:4a:da:14:a0:c4:f4:8e:61:27:f9:c0:1e:78:5d:d1 +# SHA256 Fingerprint: 88:f4:38:dc:f8:ff:d1:fa:8f:42:91:15:ff:e5:f8:2a:e1:e0:6e:0c:70:c3:75:fa:ad:71:7b:34:a4:9e:72:65 +-----BEGIN CERTIFICATE----- +MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEM +BQAwaTELMAkGA1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRG +T1JNIENvcnAuMTIwMAYDVQQDDClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4NDJaFw0zNzA4MTgyMzU5NTlaMGkx +CzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVTUyBQTEFURk9STSBD +b3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVA +iQqrDZBbUGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH +38dq6SZeWYp34+hInDEW+j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lE +HoSTGEq0n+USZGnQJoViAbbJAh2+g1G7XNr4rRVqmfeSVPc0W+m/6imBEtRTkZaz +kVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2aacp+yPOiNgSnABIqKYP +szuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4Yb8Obtoq +vC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHf +nZ3zVHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaG +YQ5fG8Ir4ozVu53BA0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo +0es+nPxdGoMuK8u180SdOqcXYZaicdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3a +CJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejyYhbLgGvtPe31HzClrkvJE+2K +AQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNVHQ4EFgQU0p+I +36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB +Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoN +qo0hV4/GPnrK21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatj +cu3cvuzHV+YwIHHW1xDBE1UBjCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm ++LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bxhYTeodoS76TiEJd6eN4MUZeoIUCL +hr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTgE34h5prCy8VCZLQe +lHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTHD8z7 +p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8 +piKCk5XQA76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLR +LBT/DShycpWbXgnbiUSYqqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX +5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oGI/hGoiLtk/bdmuYqh7GYVPEi92tF4+KO +dh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmgkpzNNIaRkPpkUZ3+/uul +9XXeifdy +-----END CERTIFICATE----- + +# Issuer: CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS O=FNMT-RCM OU=Ceres +# Subject: CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS O=FNMT-RCM OU=Ceres +# Label: "AC RAIZ FNMT-RCM SERVIDORES SEGUROS" +# Serial: 131542671362353147877283741781055151509 +# MD5 Fingerprint: 19:36:9c:52:03:2f:d2:d1:bb:23:cc:dd:1e:12:55:bb +# SHA1 Fingerprint: 62:ff:d9:9e:c0:65:0d:03:ce:75:93:d2:ed:3f:2d:32:c9:e3:e5:4a +# SHA256 Fingerprint: 55:41:53:b1:3d:2c:f9:dd:b7:53:bf:be:1a:4e:0a:e0:8d:0a:a4:18:70:58:fe:60:a2:b8:62:b2:e4:b8:7b:cb +-----BEGIN CERTIFICATE----- +MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQsw +CQYDVQQGEwJFUzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgw +FgYDVQRhDA9WQVRFUy1RMjgyNjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1S +Q00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4MTIyMDA5MzczM1oXDTQzMTIyMDA5 +MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQtUkNNMQ4wDAYDVQQL +DAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNBQyBS +QUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LH +sbI6GA60XYyzZl2hNPk2LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oK +Um8BA06Oi6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqGSM49BAMDA2kAMGYCMQCu +SuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoDzBOQn5IC +MQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJy +v+c= +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign Root R46 O=GlobalSign nv-sa +# Subject: CN=GlobalSign Root R46 O=GlobalSign nv-sa +# Label: "GlobalSign Root R46" +# Serial: 1552617688466950547958867513931858518042577 +# MD5 Fingerprint: c4:14:30:e4:fa:66:43:94:2a:6a:1b:24:5f:19:d0:ef +# SHA1 Fingerprint: 53:a2:b0:4b:ca:6b:d6:45:e6:39:8a:8e:c4:0d:d2:bf:77:c3:a2:90 +# SHA256 Fingerprint: 4f:a3:12:6d:8d:3a:11:d1:c4:85:5a:4f:80:7c:ba:d6:cf:91:9d:3a:5a:88:b0:3b:ea:2c:63:72:d9:3c:40:c9 +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUA +MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYD +VQQDExNHbG9iYWxTaWduIFJvb3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMy +MDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYt +c2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08EsCVeJ +OaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQG +vGIFAha/r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud +316HCkD7rRlr+/fKYIje2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo +0q3v84RLHIf8E6M6cqJaESvWJ3En7YEtbWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSE +y132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvjK8Cd+RTyG/FWaha/LIWF +zXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD412lPFzYE ++cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCN +I/onccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzs +x2sZy/N78CsHpdlseVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqa +ByFrgY/bxFn63iLABJzjqls2k+g9vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC +4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEMBQADggIBAHx4 +7PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg +JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti +2kM3S+LGteWygxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIk +pnnpHs6i58FZFZ8d4kuaPp92CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRF +FRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZmOUdkLG5NrmJ7v2B0GbhWrJKsFjLt +rWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qqJZ4d16GLuc1CLgSk +ZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwyeqiv5 +u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP +4vkYxboznxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6 +N3ec592kD3ZDZopD8p/7DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3 +vouXsXgxT7PntgMTzlSdriVZzH81Xwj3QEUxeCp6 +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign Root E46 O=GlobalSign nv-sa +# Subject: CN=GlobalSign Root E46 O=GlobalSign nv-sa +# Label: "GlobalSign Root E46" +# Serial: 1552617690338932563915843282459653771421763 +# MD5 Fingerprint: b5:b8:66:ed:de:08:83:e3:c9:e2:01:34:06:ac:51:6f +# SHA1 Fingerprint: 39:b4:6c:d5:fe:80:06:eb:e2:2f:4a:bb:08:33:a0:af:db:b9:dd:84 +# SHA256 Fingerprint: cb:b9:c4:4d:84:b8:04:3e:10:50:ea:31:a6:9f:51:49:55:d7:bf:d2:e2:c6:b4:93:01:01:9a:d6:1d:9f:50:58 +-----BEGIN CERTIFICATE----- +MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYx +CzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQD +ExNHbG9iYWxTaWduIFJvb3QgRTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAw +MDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2Ex +HDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkBjtjq +R+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGdd +yXqBPCCjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBQxCpCPtsad0kRLgLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ +7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZkvLtoURMMA/cVi4RguYv/Uo7njLwcAjA8 ++RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+CAezNIm8BZ/3Hobui3A= +-----END CERTIFICATE----- + +# Issuer: CN=GLOBALTRUST 2020 O=e-commerce monitoring GmbH +# Subject: CN=GLOBALTRUST 2020 O=e-commerce monitoring GmbH +# Label: "GLOBALTRUST 2020" +# Serial: 109160994242082918454945253 +# MD5 Fingerprint: 8a:c7:6f:cb:6d:e3:cc:a2:f1:7c:83:fa:0e:78:d7:e8 +# SHA1 Fingerprint: d0:67:c1:13:51:01:0c:aa:d0:c7:6a:65:37:31:16:26:4f:53:71:a2 +# SHA256 Fingerprint: 9a:29:6a:51:82:d1:d4:51:a2:e3:7f:43:9b:74:da:af:a2:67:52:33:29:f9:0f:9a:0d:20:07:c3:34:e2:3c:9a +-----BEGIN CERTIFICATE----- +MIIFgjCCA2qgAwIBAgILWku9WvtPilv6ZeUwDQYJKoZIhvcNAQELBQAwTTELMAkG +A1UEBhMCQVQxIzAhBgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkw +FwYDVQQDExBHTE9CQUxUUlVTVCAyMDIwMB4XDTIwMDIxMDAwMDAwMFoXDTQwMDYx +MDAwMDAwMFowTTELMAkGA1UEBhMCQVQxIzAhBgNVBAoTGmUtY29tbWVyY2UgbW9u +aXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVTVCAyMDIwMIICIjANBgkq +hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAri5WrRsc7/aVj6B3GyvTY4+ETUWiD59b +RatZe1E0+eyLinjF3WuvvcTfk0Uev5E4C64OFudBc/jbu9G4UeDLgztzOG53ig9Z +YybNpyrOVPu44sB8R85gfD+yc/LAGbaKkoc1DZAoouQVBGM+uq/ufF7MpotQsjj3 +QWPKzv9pj2gOlTblzLmMCcpL3TGQlsjMH/1WljTbjhzqLL6FLmPdqqmV0/0plRPw +yJiT2S0WR5ARg6I6IqIoV6Lr/sCMKKCmfecqQjuCgGOlYx8ZzHyyZqjC0203b+J+ +BlHZRYQfEs4kUmSFC0iAToexIiIwquuuvuAC4EDosEKAA1GqtH6qRNdDYfOiaxaJ +SaSjpCuKAsR49GiKweR6NrFvG5Ybd0mN1MkGco/PU+PcF4UgStyYJ9ORJitHHmkH +r96i5OTUawuzXnzUJIBHKWk7buis/UDr2O1xcSvy6Fgd60GXIsUf1DnQJ4+H4xj0 +4KlGDfV0OoIu0G4skaMxXDtG6nsEEFZegB31pWXogvziB4xiRfUg3kZwhqG8k9Me +dKZssCz3AwyIDMvUclOGvGBG85hqwvG/Q/lwIHfKN0F5VVJjjVsSn8VoxIidrPIw +q7ejMZdnrY8XD2zHc+0klGvIg5rQmjdJBKuxFshsSUktq6HQjJLyQUp5ISXbY9e2 +nKd+Qmn7OmMCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFNwuH9FhN3nkq9XVsxJxaD1qaJwiMB8GA1UdIwQYMBaAFNwu +H9FhN3nkq9XVsxJxaD1qaJwiMA0GCSqGSIb3DQEBCwUAA4ICAQCR8EICaEDuw2jA +VC/f7GLDw56KoDEoqoOOpFaWEhCGVrqXctJUMHytGdUdaG/7FELYjQ7ztdGl4wJC +XtzoRlgHNQIw4Lx0SsFDKv/bGtCwr2zD/cuz9X9tAy5ZVp0tLTWMstZDFyySCstd +6IwPS3BD0IL/qMy/pJTAvoe9iuOTe8aPmxadJ2W8esVCgmxcB9CpwYhgROmYhRZf ++I/KARDOJcP5YBugxZfD0yyIMaK9MOzQ0MAS8cE54+X1+NZK3TTN+2/BT+MAi1bi +kvcoskJ3ciNnxz8RFbLEAwW+uxF7Cr+obuf/WEPPm2eggAe2HcqtbepBEX4tdJP7 +wry+UUTF72glJ4DjyKDUEuzZpTcdN3y0kcra1LGWge9oXHYQSa9+pTeAsRxSvTOB +TI/53WXZFM2KJVj04sWDpQmQ1GwUY7VA3+vA/MRYfg0UFodUJ25W5HCEuGwyEn6C +MUO+1918oa2u1qsgEu8KwxCMSZY13At1XrFP1U80DhEgB3VDRemjEdqso5nCtnkn +4rnvyOL2NSl6dPrFf4IFYqYK6miyeUcGbvJXqBUzxvd4Sj1Ce2t+/vdG6tHrju+I +aFvowdlxfv1k7/9nR4hYJS8+hge9+6jlgqispdNpQ80xiEmEU5LAsTkbOYMBMMTy +qfrQA71yN2BWHzZ8vTmR9W0Nv3vXkg== +-----END CERTIFICATE----- + +# Issuer: CN=ANF Secure Server Root CA O=ANF Autoridad de Certificacion OU=ANF CA Raiz +# Subject: CN=ANF Secure Server Root CA O=ANF Autoridad de Certificacion OU=ANF CA Raiz +# Label: "ANF Secure Server Root CA" +# Serial: 996390341000653745 +# MD5 Fingerprint: 26:a6:44:5a:d9:af:4e:2f:b2:1d:b6:65:b0:4e:e8:96 +# SHA1 Fingerprint: 5b:6e:68:d0:cc:15:b6:a0:5f:1e:c1:5f:ae:02:fc:6b:2f:5d:6f:74 +# SHA256 Fingerprint: fb:8f:ec:75:91:69:b9:10:6b:1e:51:16:44:c6:18:c5:13:04:37:3f:6c:06:43:08:8d:8b:ef:fd:1b:99:75:99 +-----BEGIN CERTIFICATE----- +MIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNV +BAUTCUc2MzI4NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlk +YWQgZGUgQ2VydGlmaWNhY2lvbjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNV +BAMTGUFORiBTZWN1cmUgU2VydmVyIFJvb3QgQ0EwHhcNMTkwOTA0MTAwMDM4WhcN +MzkwODMwMTAwMDM4WjCBhDESMBAGA1UEBRMJRzYzMjg3NTEwMQswCQYDVQQGEwJF +UzEnMCUGA1UEChMeQU5GIEF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uMRQwEgYD +VQQLEwtBTkYgQ0EgUmFpejEiMCAGA1UEAxMZQU5GIFNlY3VyZSBTZXJ2ZXIgUm9v +dCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANvrayvmZFSVgpCj +cqQZAZ2cC4Ffc0m6p6zzBE57lgvsEeBbphzOG9INgxwruJ4dfkUyYA8H6XdYfp9q +yGFOtibBTI3/TO80sh9l2Ll49a2pcbnvT1gdpd50IJeh7WhM3pIXS7yr/2WanvtH +2Vdy8wmhrnZEE26cLUQ5vPnHO6RYPUG9tMJJo8gN0pcvB2VSAKduyK9o7PQUlrZX +H1bDOZ8rbeTzPvY1ZNoMHKGESy9LS+IsJJ1tk0DrtSOOMspvRdOoiXsezx76W0OL +zc2oD2rKDF65nkeP8Nm2CgtYZRczuSPkdxl9y0oukntPLxB3sY0vaJxizOBQ+OyR +p1RMVwnVdmPF6GUe7m1qzwmd+nxPrWAI/VaZDxUse6mAq4xhj0oHdkLePfTdsiQz +W7i1o0TJrH93PB0j7IKppuLIBkwC/qxcmZkLLxCKpvR/1Yd0DVlJRfbwcVw5Kda/ +SiOL9V8BY9KHcyi1Swr1+KuCLH5zJTIdC2MKF4EA/7Z2Xue0sUDKIbvVgFHlSFJn +LNJhiQcND85Cd8BEc5xEUKDbEAotlRyBr+Qc5RQe8TZBAQIvfXOn3kLMTOmJDVb3 +n5HUA8ZsyY/b2BzgQJhdZpmYgG4t/wHFzstGH6wCxkPmrqKEPMVOHj1tyRRM4y5B +u8o5vzY8KhmqQYdOpc5LMnndkEl/AgMBAAGjYzBhMB8GA1UdIwQYMBaAFJxf0Gxj +o1+TypOYCK2Mh6UsXME3MB0GA1UdDgQWBBScX9BsY6Nfk8qTmAitjIelLFzBNzAO +BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC +AgEATh65isagmD9uw2nAalxJUqzLK114OMHVVISfk/CHGT0sZonrDUL8zPB1hT+L +9IBdeeUXZ701guLyPI59WzbLWoAAKfLOKyzxj6ptBZNscsdW699QIyjlRRA96Gej +rw5VD5AJYu9LWaL2U/HANeQvwSS9eS9OICI7/RogsKQOLHDtdD+4E5UGUcjohybK +pFtqFiGS3XNgnhAY3jyB6ugYw3yJ8otQPr0R4hUDqDZ9MwFsSBXXiJCZBMXM5gf0 +vPSQ7RPi6ovDj6MzD8EpTBNO2hVWcXNyglD2mjN8orGoGjR0ZVzO0eurU+AagNjq +OknkJjCb5RyKqKkVMoaZkgoQI1YS4PbOTOK7vtuNknMBZi9iPrJyJ0U27U1W45eZ +/zo1PqVUSlJZS2Db7v54EX9K3BR5YLZrZAPbFYPhor72I5dQ8AkzNqdxliXzuUJ9 +2zg/LFis6ELhDtjTO0wugumDLmsx2d1Hhk9tl5EuT+IocTUW0fJz/iUrB0ckYyfI ++PbZa/wSMVYIwFNCr5zQM378BvAxRAMU8Vjq8moNqRGyg77FGr8H6lnco4g175x2 +MjxNBiLOFeXdntiP2t7SxDnlF4HPOEfrf4htWRvfn0IUrn7PqLBmZdo3r5+qPeoo +tt7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw= +-----END CERTIFICATE----- + +# Issuer: CN=Certum EC-384 CA O=Asseco Data Systems S.A. OU=Certum Certification Authority +# Subject: CN=Certum EC-384 CA O=Asseco Data Systems S.A. OU=Certum Certification Authority +# Label: "Certum EC-384 CA" +# Serial: 160250656287871593594747141429395092468 +# MD5 Fingerprint: b6:65:b3:96:60:97:12:a1:ec:4e:e1:3d:a3:c6:c9:f1 +# SHA1 Fingerprint: f3:3e:78:3c:ac:df:f4:a2:cc:ac:67:55:69:56:d7:e5:16:3c:e1:ed +# SHA256 Fingerprint: 6b:32:80:85:62:53:18:aa:50:d1:73:c9:8d:8b:da:09:d5:7e:27:41:3d:11:4c:f7:87:a0:f5:d0:6c:03:0c:f6 +-----BEGIN CERTIFICATE----- +MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQsw +CQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScw +JQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMT +EENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2MDcyNDU0WhcNNDMwMzI2MDcyNDU0 +WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBT +LkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAX +BgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATE +KI6rGFtqvm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7Tm +Fy8as10CW4kjPMIRBSqniBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68Kj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI0GZnQkdjrzife81r1HfS+8 +EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNoADBlAjADVS2m5hjEfO/J +UG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0QoSZ/6vn +nvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k= +-----END CERTIFICATE----- + +# Issuer: CN=Certum Trusted Root CA O=Asseco Data Systems S.A. OU=Certum Certification Authority +# Subject: CN=Certum Trusted Root CA O=Asseco Data Systems S.A. OU=Certum Certification Authority +# Label: "Certum Trusted Root CA" +# Serial: 40870380103424195783807378461123655149 +# MD5 Fingerprint: 51:e1:c2:e7:fe:4c:84:af:59:0e:2f:f4:54:6f:ea:29 +# SHA1 Fingerprint: c8:83:44:c0:18:ae:9f:cc:f1:87:b7:8f:22:d1:c5:d7:45:84:ba:e5 +# SHA256 Fingerprint: fe:76:96:57:38:55:77:3e:37:a9:5e:7a:d4:d9:cc:96:c3:01:57:c1:5d:31:76:5b:a9:b1:57:04:e1:ae:78:fd +-----BEGIN CERTIFICATE----- +MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6 +MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEu +MScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNV +BAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwHhcNMTgwMzE2MTIxMDEzWhcNNDMw +MzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEg +U3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRo +b3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZ +n0EGze2jusDbCSzBfN8pfktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/q +p1x4EaTByIVcJdPTsuclzxFUl6s1wB52HO8AU5853BSlLCIls3Jy/I2z5T4IHhQq +NwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2fJmItdUDmj0VDT06qKhF +8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGtg/BKEiJ3 +HAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGa +mqi4NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi +7VdNIuJGmj8PkTQkfVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSF +ytKAQd8FqKPVhJBPC/PgP5sZ0jeJP/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0P +qafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSYnjYJdmZm/Bo/6khUHL4wvYBQ +v3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHKHRzQ+8S1h9E6 +Tsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1 +vALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQAD +ggIBAEii1QALLtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4 +WxmB82M+w85bj/UvXgF2Ez8sALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvo +zMrnadyHncI013nR03e4qllY/p0m+jiGPp2Kh2RX5Rc64vmNueMzeMGQ2Ljdt4NR +5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8CYyqOhNf6DR5UMEQ +GfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA4kZf +5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq +0Uc9NneoWWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7D +P78v3DSk+yshzWePS/Tj6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTM +qJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmTOPQD8rv7gmsHINFSH5pkAnuYZttcTVoP +0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZckbxJF0WddCajJFdr60qZf +E2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb +-----END CERTIFICATE----- diff --git a/pipenv/patched/notpip/_vendor/certifi/core.py b/pipenv/patched/notpip/_vendor/certifi/core.py index 2d02ea44..5b06ce3f 100644 --- a/pipenv/patched/notpip/_vendor/certifi/core.py +++ b/pipenv/patched/notpip/_vendor/certifi/core.py @@ -1,20 +1,76 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- """ certifi.py ~~~~~~~~~~ -This module returns the installation location of cacert.pem. +This module returns the installation location of cacert.pem or its contents. """ import os -def where(): - f = os.path.dirname(__file__) - - return os.path.join(f, 'cacert.pem') +class _PipPatchedCertificate(Exception): + pass -if __name__ == '__main__': - print(where()) +try: + # Return a certificate file on disk for a standalone pip zipapp running in + # an isolated build environment to use. Passing --cert to the standalone + # pip does not work since requests calls where() unconditionally on import. + _PIP_STANDALONE_CERT = os.environ.get("_PIP_STANDALONE_CERT") + if _PIP_STANDALONE_CERT: + def where(): + return _PIP_STANDALONE_CERT + raise _PipPatchedCertificate() + + from importlib.resources import path as get_path, read_text + + _CACERT_CTX = None + _CACERT_PATH = None + + def where(): + # This is slightly terrible, but we want to delay extracting the file + # in cases where we're inside of a zipimport situation until someone + # actually calls where(), but we don't want to re-extract the file + # on every call of where(), so we'll do it once then store it in a + # global variable. + global _CACERT_CTX + global _CACERT_PATH + if _CACERT_PATH is None: + # This is slightly janky, the importlib.resources API wants you to + # manage the cleanup of this file, so it doesn't actually return a + # path, it returns a context manager that will give you the path + # when you enter it and will do any cleanup when you leave it. In + # the common case of not needing a temporary file, it will just + # return the file system location and the __exit__() is a no-op. + # + # We also have to hold onto the actual context manager, because + # it will do the cleanup whenever it gets garbage collected, so + # we will also store that at the global level as well. + _CACERT_CTX = get_path("pipenv.patched.notpip._vendor.certifi", "cacert.pem") + _CACERT_PATH = str(_CACERT_CTX.__enter__()) + + return _CACERT_PATH + +except _PipPatchedCertificate: + pass + +except ImportError: + # This fallback will work for Python versions prior to 3.7 that lack the + # importlib.resources module but relies on the existing `where` function + # so won't address issues with environments like PyOxidizer that don't set + # __file__ on modules. + def read_text(_module, _path, encoding="ascii"): + with open(where(), "r", encoding=encoding) as data: + return data.read() + + # If we don't have importlib.resources, then we will just do the old logic + # of assuming we're on the filesystem and munge the path directly. + def where(): + f = os.path.dirname(__file__) + + return os.path.join(f, "cacert.pem") + + +def contents(): + return read_text("certifi", "cacert.pem", encoding="ascii") diff --git a/pipenv/patched/notpip/_vendor/chardet/__init__.py b/pipenv/patched/notpip/_vendor/chardet/__init__.py index 0f9f820e..80ad2546 100644 --- a/pipenv/patched/notpip/_vendor/chardet/__init__.py +++ b/pipenv/patched/notpip/_vendor/chardet/__init__.py @@ -16,11 +16,14 @@ ######################### END LICENSE BLOCK ######################### -from .compat import PY2, PY3 from .universaldetector import UniversalDetector +from .enums import InputState from .version import __version__, VERSION +__all__ = ['UniversalDetector', 'detect', 'detect_all', '__version__', 'VERSION'] + + def detect(byte_str): """ Detect the encoding of the given byte string. @@ -31,9 +34,50 @@ def detect(byte_str): if not isinstance(byte_str, bytearray): if not isinstance(byte_str, bytes): raise TypeError('Expected object of type bytes or bytearray, got: ' - '{0}'.format(type(byte_str))) + '{}'.format(type(byte_str))) else: byte_str = bytearray(byte_str) detector = UniversalDetector() detector.feed(byte_str) return detector.close() + + +def detect_all(byte_str): + """ + Detect all the possible encodings of the given byte string. + + :param byte_str: The byte sequence to examine. + :type byte_str: ``bytes`` or ``bytearray`` + """ + if not isinstance(byte_str, bytearray): + if not isinstance(byte_str, bytes): + raise TypeError('Expected object of type bytes or bytearray, got: ' + '{}'.format(type(byte_str))) + else: + byte_str = bytearray(byte_str) + + detector = UniversalDetector() + detector.feed(byte_str) + detector.close() + + if detector._input_state == InputState.HIGH_BYTE: + results = [] + for prober in detector._charset_probers: + if prober.get_confidence() > detector.MINIMUM_THRESHOLD: + charset_name = prober.charset_name + lower_charset_name = prober.charset_name.lower() + # Use Windows encoding name instead of ISO-8859 if we saw any + # extra Windows-specific bytes + if lower_charset_name.startswith('iso-8859'): + if detector._has_win_bytes: + charset_name = detector.ISO_WIN_MAP.get(lower_charset_name, + charset_name) + results.append({ + 'encoding': charset_name, + 'confidence': prober.get_confidence(), + 'language': prober.language, + }) + if len(results) > 0: + return sorted(results, key=lambda result: -result['confidence']) + + return [detector.result] diff --git a/pipenv/patched/notpip/_vendor/chardet/charsetgroupprober.py b/pipenv/patched/notpip/_vendor/chardet/charsetgroupprober.py index 8b3738ef..5812cef0 100644 --- a/pipenv/patched/notpip/_vendor/chardet/charsetgroupprober.py +++ b/pipenv/patched/notpip/_vendor/chardet/charsetgroupprober.py @@ -73,6 +73,7 @@ class CharSetGroupProber(CharSetProber): continue if state == ProbingState.FOUND_IT: self._best_guess_prober = prober + self._state = ProbingState.FOUND_IT return self.state elif state == ProbingState.NOT_ME: prober.active = False diff --git a/pipenv/patched/notpip/_vendor/chardet/cli/chardetect.py b/pipenv/patched/notpip/_vendor/chardet/cli/chardetect.py index 76038aac..a8c127d3 100644 --- a/pipenv/patched/notpip/_vendor/chardet/cli/chardetect.py +++ b/pipenv/patched/notpip/_vendor/chardet/cli/chardetect.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python """ Script which takes one or more file paths and reports on their detected encodings @@ -45,10 +44,10 @@ def description_of(lines, name='stdin'): if PY2: name = name.decode(sys.getfilesystemencoding(), 'ignore') if result['encoding']: - return '{0}: {1} with confidence {2}'.format(name, result['encoding'], + return '{}: {} with confidence {}'.format(name, result['encoding'], result['confidence']) else: - return '{0}: no result'.format(name) + return '{}: no result'.format(name) def main(argv=None): @@ -69,7 +68,7 @@ def main(argv=None): type=argparse.FileType('rb'), nargs='*', default=[sys.stdin if PY2 else sys.stdin.buffer]) parser.add_argument('--version', action='version', - version='%(prog)s {0}'.format(__version__)) + version='%(prog)s {}'.format(__version__)) args = parser.parse_args(argv) for f in args.input: diff --git a/pipenv/patched/notpip/_vendor/chardet/compat.py b/pipenv/patched/notpip/_vendor/chardet/compat.py index ddd74687..8941572b 100644 --- a/pipenv/patched/notpip/_vendor/chardet/compat.py +++ b/pipenv/patched/notpip/_vendor/chardet/compat.py @@ -25,10 +25,12 @@ import sys if sys.version_info < (3, 0): PY2 = True PY3 = False - base_str = (str, unicode) + string_types = (str, unicode) text_type = unicode + iteritems = dict.iteritems else: PY2 = False PY3 = True - base_str = (bytes, str) + string_types = (bytes, str) text_type = str + iteritems = dict.items diff --git a/pipenv/patched/notpip/_vendor/chardet/langbulgarianmodel.py b/pipenv/patched/notpip/_vendor/chardet/langbulgarianmodel.py index 2aa4fb2e..8b76f23b 100644 --- a/pipenv/patched/notpip/_vendor/chardet/langbulgarianmodel.py +++ b/pipenv/patched/notpip/_vendor/chardet/langbulgarianmodel.py @@ -1,228 +1,4650 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### +#!/usr/bin/env python +# -*- coding: utf-8 -*- -# 255: Control characters that usually does not exist in any text +from pipenv.patched.notpip._vendor.chardet.sbcharsetprober import SingleByteCharSetModel + + +# 3: Positive +# 2: Likely +# 1: Unlikely +# 0: Negative + +BULGARIAN_LANG_MODEL = { + 63: { # 'e' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 0, # 'а' + 18: 1, # 'б' + 9: 1, # 'в' + 20: 1, # 'г' + 11: 1, # 'д' + 3: 1, # 'е' + 23: 1, # 'ж' + 15: 1, # 'з' + 2: 0, # 'и' + 26: 1, # 'й' + 12: 1, # 'к' + 10: 1, # 'л' + 14: 1, # 'м' + 6: 1, # 'н' + 4: 1, # 'о' + 13: 1, # 'п' + 7: 1, # 'р' + 8: 1, # 'с' + 5: 1, # 'т' + 19: 0, # 'у' + 29: 1, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 1, # 'ч' + 27: 1, # 'ш' + 24: 1, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 45: { # '\xad' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 0, # 'Г' + 37: 1, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 1, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 0, # 'Л' + 38: 1, # 'М' + 36: 0, # 'Н' + 41: 1, # 'О' + 30: 1, # 'П' + 39: 1, # 'Р' + 28: 1, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 1, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 0, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 0, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 0, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 0, # 'о' + 13: 0, # 'п' + 7: 0, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 0, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 31: { # 'А' + 63: 0, # 'e' + 45: 1, # '\xad' + 31: 1, # 'А' + 32: 1, # 'Б' + 35: 2, # 'В' + 43: 1, # 'Г' + 37: 2, # 'Д' + 44: 2, # 'Е' + 55: 1, # 'Ж' + 47: 2, # 'З' + 40: 1, # 'И' + 59: 1, # 'Й' + 33: 1, # 'К' + 46: 2, # 'Л' + 38: 1, # 'М' + 36: 2, # 'Н' + 41: 1, # 'О' + 30: 2, # 'П' + 39: 2, # 'Р' + 28: 2, # 'С' + 34: 2, # 'Т' + 51: 1, # 'У' + 48: 2, # 'Ф' + 49: 1, # 'Х' + 53: 1, # 'Ц' + 50: 1, # 'Ч' + 54: 1, # 'Ш' + 57: 2, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 1, # 'Я' + 1: 1, # 'а' + 18: 2, # 'б' + 9: 2, # 'в' + 20: 2, # 'г' + 11: 2, # 'д' + 3: 1, # 'е' + 23: 1, # 'ж' + 15: 2, # 'з' + 2: 0, # 'и' + 26: 2, # 'й' + 12: 2, # 'к' + 10: 3, # 'л' + 14: 2, # 'м' + 6: 3, # 'н' + 4: 0, # 'о' + 13: 2, # 'п' + 7: 2, # 'р' + 8: 2, # 'с' + 5: 2, # 'т' + 19: 1, # 'у' + 29: 2, # 'ф' + 25: 1, # 'х' + 22: 1, # 'ц' + 21: 1, # 'ч' + 27: 1, # 'ш' + 24: 0, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 32: { # 'Б' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 2, # 'А' + 32: 2, # 'Б' + 35: 1, # 'В' + 43: 1, # 'Г' + 37: 2, # 'Д' + 44: 1, # 'Е' + 55: 1, # 'Ж' + 47: 2, # 'З' + 40: 1, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 2, # 'Н' + 41: 2, # 'О' + 30: 1, # 'П' + 39: 1, # 'Р' + 28: 2, # 'С' + 34: 2, # 'Т' + 51: 1, # 'У' + 48: 2, # 'Ф' + 49: 1, # 'Х' + 53: 1, # 'Ц' + 50: 1, # 'Ч' + 54: 0, # 'Ш' + 57: 1, # 'Щ' + 61: 2, # 'Ъ' + 60: 1, # 'Ю' + 56: 1, # 'Я' + 1: 3, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 1, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 2, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 2, # 'р' + 8: 1, # 'с' + 5: 0, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 3, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 2, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 35: { # 'В' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 2, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 0, # 'Г' + 37: 1, # 'Д' + 44: 2, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 2, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 1, # 'О' + 30: 1, # 'П' + 39: 2, # 'Р' + 28: 2, # 'С' + 34: 1, # 'Т' + 51: 1, # 'У' + 48: 2, # 'Ф' + 49: 0, # 'Х' + 53: 1, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 1, # 'Ъ' + 60: 1, # 'Ю' + 56: 2, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 1, # 'д' + 3: 3, # 'е' + 23: 1, # 'ж' + 15: 2, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 2, # 'л' + 14: 1, # 'м' + 6: 2, # 'н' + 4: 2, # 'о' + 13: 1, # 'п' + 7: 2, # 'р' + 8: 2, # 'с' + 5: 2, # 'т' + 19: 1, # 'у' + 29: 0, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 2, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 43: { # 'Г' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 2, # 'А' + 32: 1, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 1, # 'Д' + 44: 2, # 'Е' + 55: 0, # 'Ж' + 47: 1, # 'З' + 40: 1, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 0, # 'М' + 36: 1, # 'Н' + 41: 1, # 'О' + 30: 0, # 'П' + 39: 1, # 'Р' + 28: 1, # 'С' + 34: 0, # 'Т' + 51: 1, # 'У' + 48: 1, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 1, # 'Щ' + 61: 1, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 2, # 'а' + 18: 1, # 'б' + 9: 1, # 'в' + 20: 0, # 'г' + 11: 1, # 'д' + 3: 3, # 'е' + 23: 1, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 2, # 'л' + 14: 1, # 'м' + 6: 1, # 'н' + 4: 2, # 'о' + 13: 0, # 'п' + 7: 2, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 1, # 'щ' + 17: 2, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 37: { # 'Д' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 2, # 'А' + 32: 1, # 'Б' + 35: 2, # 'В' + 43: 1, # 'Г' + 37: 2, # 'Д' + 44: 2, # 'Е' + 55: 2, # 'Ж' + 47: 1, # 'З' + 40: 2, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 2, # 'О' + 30: 2, # 'П' + 39: 1, # 'Р' + 28: 2, # 'С' + 34: 1, # 'Т' + 51: 1, # 'У' + 48: 1, # 'Ф' + 49: 0, # 'Х' + 53: 1, # 'Ц' + 50: 1, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 1, # 'Ъ' + 60: 1, # 'Ю' + 56: 1, # 'Я' + 1: 3, # 'а' + 18: 0, # 'б' + 9: 2, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 3, # 'е' + 23: 3, # 'ж' + 15: 1, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 1, # 'л' + 14: 1, # 'м' + 6: 2, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 2, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 1, # 'ь' + 42: 2, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 44: { # 'Е' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 1, # 'А' + 32: 1, # 'Б' + 35: 2, # 'В' + 43: 1, # 'Г' + 37: 1, # 'Д' + 44: 1, # 'Е' + 55: 1, # 'Ж' + 47: 1, # 'З' + 40: 1, # 'И' + 59: 1, # 'Й' + 33: 2, # 'К' + 46: 2, # 'Л' + 38: 1, # 'М' + 36: 2, # 'Н' + 41: 2, # 'О' + 30: 1, # 'П' + 39: 2, # 'Р' + 28: 2, # 'С' + 34: 2, # 'Т' + 51: 1, # 'У' + 48: 2, # 'Ф' + 49: 1, # 'Х' + 53: 2, # 'Ц' + 50: 1, # 'Ч' + 54: 1, # 'Ш' + 57: 1, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 1, # 'Я' + 1: 0, # 'а' + 18: 1, # 'б' + 9: 2, # 'в' + 20: 1, # 'г' + 11: 2, # 'д' + 3: 0, # 'е' + 23: 1, # 'ж' + 15: 1, # 'з' + 2: 0, # 'и' + 26: 1, # 'й' + 12: 2, # 'к' + 10: 2, # 'л' + 14: 2, # 'м' + 6: 2, # 'н' + 4: 0, # 'о' + 13: 1, # 'п' + 7: 2, # 'р' + 8: 2, # 'с' + 5: 1, # 'т' + 19: 1, # 'у' + 29: 1, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 1, # 'ч' + 27: 1, # 'ш' + 24: 1, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 55: { # 'Ж' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 1, # 'А' + 32: 0, # 'Б' + 35: 1, # 'В' + 43: 0, # 'Г' + 37: 1, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 1, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 1, # 'Н' + 41: 1, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 1, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 2, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 1, # 'д' + 3: 2, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 2, # 'о' + 13: 1, # 'п' + 7: 1, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 1, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 47: { # 'З' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 2, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 1, # 'Г' + 37: 1, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 1, # 'З' + 40: 1, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 2, # 'Н' + 41: 1, # 'О' + 30: 1, # 'П' + 39: 1, # 'Р' + 28: 1, # 'С' + 34: 1, # 'Т' + 51: 1, # 'У' + 48: 0, # 'Ф' + 49: 1, # 'Х' + 53: 1, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 1, # 'Ъ' + 60: 0, # 'Ю' + 56: 1, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 2, # 'в' + 20: 1, # 'г' + 11: 2, # 'д' + 3: 2, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 1, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 2, # 'л' + 14: 1, # 'м' + 6: 1, # 'н' + 4: 1, # 'о' + 13: 0, # 'п' + 7: 1, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 1, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 40: { # 'И' + 63: 0, # 'e' + 45: 1, # '\xad' + 31: 1, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 1, # 'Г' + 37: 1, # 'Д' + 44: 2, # 'Е' + 55: 1, # 'Ж' + 47: 2, # 'З' + 40: 1, # 'И' + 59: 1, # 'Й' + 33: 2, # 'К' + 46: 2, # 'Л' + 38: 2, # 'М' + 36: 2, # 'Н' + 41: 1, # 'О' + 30: 1, # 'П' + 39: 2, # 'Р' + 28: 2, # 'С' + 34: 2, # 'Т' + 51: 0, # 'У' + 48: 1, # 'Ф' + 49: 1, # 'Х' + 53: 1, # 'Ц' + 50: 1, # 'Ч' + 54: 1, # 'Ш' + 57: 1, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 2, # 'Я' + 1: 1, # 'а' + 18: 1, # 'б' + 9: 3, # 'в' + 20: 2, # 'г' + 11: 1, # 'д' + 3: 1, # 'е' + 23: 0, # 'ж' + 15: 3, # 'з' + 2: 0, # 'и' + 26: 1, # 'й' + 12: 1, # 'к' + 10: 2, # 'л' + 14: 2, # 'м' + 6: 2, # 'н' + 4: 0, # 'о' + 13: 1, # 'п' + 7: 2, # 'р' + 8: 2, # 'с' + 5: 2, # 'т' + 19: 0, # 'у' + 29: 1, # 'ф' + 25: 1, # 'х' + 22: 1, # 'ц' + 21: 1, # 'ч' + 27: 1, # 'ш' + 24: 1, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 59: { # 'Й' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 1, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 1, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 1, # 'С' + 34: 1, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 1, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 1, # 'Я' + 1: 0, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 1, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 0, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 2, # 'о' + 13: 0, # 'п' + 7: 0, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 0, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 33: { # 'К' + 63: 0, # 'e' + 45: 1, # '\xad' + 31: 2, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 1, # 'Г' + 37: 1, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 1, # 'З' + 40: 2, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 0, # 'М' + 36: 2, # 'Н' + 41: 2, # 'О' + 30: 2, # 'П' + 39: 1, # 'Р' + 28: 2, # 'С' + 34: 1, # 'Т' + 51: 1, # 'У' + 48: 1, # 'Ф' + 49: 1, # 'Х' + 53: 1, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 1, # 'Ъ' + 60: 1, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 0, # 'б' + 9: 1, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 2, # 'е' + 23: 1, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 2, # 'л' + 14: 1, # 'м' + 6: 2, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 3, # 'р' + 8: 1, # 'с' + 5: 0, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 1, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 1, # 'ь' + 42: 2, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 46: { # 'Л' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 2, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 2, # 'Г' + 37: 1, # 'Д' + 44: 2, # 'Е' + 55: 0, # 'Ж' + 47: 1, # 'З' + 40: 2, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 0, # 'М' + 36: 1, # 'Н' + 41: 2, # 'О' + 30: 1, # 'П' + 39: 0, # 'Р' + 28: 1, # 'С' + 34: 1, # 'Т' + 51: 1, # 'У' + 48: 0, # 'Ф' + 49: 1, # 'Х' + 53: 1, # 'Ц' + 50: 1, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 1, # 'Ъ' + 60: 1, # 'Ю' + 56: 1, # 'Я' + 1: 2, # 'а' + 18: 0, # 'б' + 9: 1, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 2, # 'о' + 13: 0, # 'п' + 7: 0, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 1, # 'ь' + 42: 2, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 38: { # 'М' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 2, # 'А' + 32: 1, # 'Б' + 35: 2, # 'В' + 43: 0, # 'Г' + 37: 1, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 1, # 'З' + 40: 2, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 2, # 'О' + 30: 1, # 'П' + 39: 1, # 'Р' + 28: 2, # 'С' + 34: 1, # 'Т' + 51: 1, # 'У' + 48: 1, # 'Ф' + 49: 0, # 'Х' + 53: 1, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 1, # 'Ъ' + 60: 0, # 'Ю' + 56: 1, # 'Я' + 1: 3, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 2, # 'л' + 14: 0, # 'м' + 6: 2, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 1, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 1, # 'ь' + 42: 2, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 36: { # 'Н' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 2, # 'А' + 32: 2, # 'Б' + 35: 1, # 'В' + 43: 1, # 'Г' + 37: 2, # 'Д' + 44: 2, # 'Е' + 55: 1, # 'Ж' + 47: 1, # 'З' + 40: 2, # 'И' + 59: 1, # 'Й' + 33: 2, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 2, # 'О' + 30: 1, # 'П' + 39: 1, # 'Р' + 28: 2, # 'С' + 34: 2, # 'Т' + 51: 1, # 'У' + 48: 1, # 'Ф' + 49: 1, # 'Х' + 53: 1, # 'Ц' + 50: 1, # 'Ч' + 54: 1, # 'Ш' + 57: 0, # 'Щ' + 61: 1, # 'Ъ' + 60: 1, # 'Ю' + 56: 1, # 'Я' + 1: 3, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 1, # 'г' + 11: 0, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 0, # 'р' + 8: 0, # 'с' + 5: 1, # 'т' + 19: 1, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 1, # 'ш' + 24: 0, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 2, # 'ю' + 16: 2, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 41: { # 'О' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 1, # 'А' + 32: 1, # 'Б' + 35: 2, # 'В' + 43: 1, # 'Г' + 37: 2, # 'Д' + 44: 1, # 'Е' + 55: 1, # 'Ж' + 47: 1, # 'З' + 40: 1, # 'И' + 59: 1, # 'Й' + 33: 2, # 'К' + 46: 2, # 'Л' + 38: 2, # 'М' + 36: 2, # 'Н' + 41: 2, # 'О' + 30: 1, # 'П' + 39: 2, # 'Р' + 28: 2, # 'С' + 34: 2, # 'Т' + 51: 1, # 'У' + 48: 1, # 'Ф' + 49: 1, # 'Х' + 53: 0, # 'Ц' + 50: 1, # 'Ч' + 54: 1, # 'Ш' + 57: 1, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 1, # 'Я' + 1: 1, # 'а' + 18: 2, # 'б' + 9: 2, # 'в' + 20: 2, # 'г' + 11: 1, # 'д' + 3: 1, # 'е' + 23: 1, # 'ж' + 15: 1, # 'з' + 2: 0, # 'и' + 26: 1, # 'й' + 12: 2, # 'к' + 10: 2, # 'л' + 14: 1, # 'м' + 6: 1, # 'н' + 4: 0, # 'о' + 13: 2, # 'п' + 7: 2, # 'р' + 8: 2, # 'с' + 5: 3, # 'т' + 19: 1, # 'у' + 29: 1, # 'ф' + 25: 1, # 'х' + 22: 1, # 'ц' + 21: 2, # 'ч' + 27: 0, # 'ш' + 24: 2, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 30: { # 'П' + 63: 0, # 'e' + 45: 1, # '\xad' + 31: 2, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 1, # 'Г' + 37: 1, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 1, # 'З' + 40: 2, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 2, # 'О' + 30: 2, # 'П' + 39: 2, # 'Р' + 28: 2, # 'С' + 34: 1, # 'Т' + 51: 2, # 'У' + 48: 1, # 'Ф' + 49: 0, # 'Х' + 53: 1, # 'Ц' + 50: 1, # 'Ч' + 54: 1, # 'Ш' + 57: 0, # 'Щ' + 61: 1, # 'Ъ' + 60: 1, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 2, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 3, # 'л' + 14: 0, # 'м' + 6: 1, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 3, # 'р' + 8: 1, # 'с' + 5: 1, # 'т' + 19: 2, # 'у' + 29: 1, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 1, # 'ч' + 27: 1, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 39: { # 'Р' + 63: 0, # 'e' + 45: 1, # '\xad' + 31: 2, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 2, # 'Г' + 37: 2, # 'Д' + 44: 2, # 'Е' + 55: 0, # 'Ж' + 47: 1, # 'З' + 40: 2, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 0, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 2, # 'О' + 30: 2, # 'П' + 39: 1, # 'Р' + 28: 1, # 'С' + 34: 1, # 'Т' + 51: 1, # 'У' + 48: 1, # 'Ф' + 49: 1, # 'Х' + 53: 1, # 'Ц' + 50: 1, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 1, # 'Ъ' + 60: 1, # 'Ю' + 56: 1, # 'Я' + 1: 3, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 2, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 1, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 0, # 'р' + 8: 1, # 'с' + 5: 0, # 'т' + 19: 3, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 28: { # 'С' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 3, # 'А' + 32: 2, # 'Б' + 35: 2, # 'В' + 43: 1, # 'Г' + 37: 2, # 'Д' + 44: 2, # 'Е' + 55: 1, # 'Ж' + 47: 1, # 'З' + 40: 2, # 'И' + 59: 0, # 'Й' + 33: 2, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 2, # 'О' + 30: 2, # 'П' + 39: 1, # 'Р' + 28: 2, # 'С' + 34: 2, # 'Т' + 51: 1, # 'У' + 48: 1, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 1, # 'Ъ' + 60: 1, # 'Ю' + 56: 1, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 2, # 'в' + 20: 1, # 'г' + 11: 1, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 2, # 'к' + 10: 3, # 'л' + 14: 2, # 'м' + 6: 1, # 'н' + 4: 3, # 'о' + 13: 3, # 'п' + 7: 2, # 'р' + 8: 0, # 'с' + 5: 3, # 'т' + 19: 2, # 'у' + 29: 2, # 'ф' + 25: 1, # 'х' + 22: 1, # 'ц' + 21: 1, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 3, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 34: { # 'Т' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 2, # 'А' + 32: 2, # 'Б' + 35: 1, # 'В' + 43: 0, # 'Г' + 37: 1, # 'Д' + 44: 2, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 2, # 'И' + 59: 0, # 'Й' + 33: 2, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 2, # 'О' + 30: 1, # 'П' + 39: 2, # 'Р' + 28: 2, # 'С' + 34: 1, # 'Т' + 51: 1, # 'У' + 48: 1, # 'Ф' + 49: 0, # 'Х' + 53: 1, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 1, # 'Ъ' + 60: 0, # 'Ю' + 56: 1, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 1, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 1, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 3, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 2, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 51: { # 'У' + 63: 0, # 'e' + 45: 1, # '\xad' + 31: 1, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 1, # 'Г' + 37: 1, # 'Д' + 44: 2, # 'Е' + 55: 1, # 'Ж' + 47: 1, # 'З' + 40: 1, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 0, # 'О' + 30: 1, # 'П' + 39: 1, # 'Р' + 28: 1, # 'С' + 34: 2, # 'Т' + 51: 0, # 'У' + 48: 1, # 'Ф' + 49: 1, # 'Х' + 53: 1, # 'Ц' + 50: 1, # 'Ч' + 54: 1, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 1, # 'а' + 18: 1, # 'б' + 9: 2, # 'в' + 20: 1, # 'г' + 11: 1, # 'д' + 3: 2, # 'е' + 23: 1, # 'ж' + 15: 1, # 'з' + 2: 2, # 'и' + 26: 1, # 'й' + 12: 2, # 'к' + 10: 1, # 'л' + 14: 1, # 'м' + 6: 2, # 'н' + 4: 2, # 'о' + 13: 1, # 'п' + 7: 1, # 'р' + 8: 2, # 'с' + 5: 1, # 'т' + 19: 1, # 'у' + 29: 0, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 2, # 'ч' + 27: 1, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 48: { # 'Ф' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 2, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 2, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 0, # 'М' + 36: 1, # 'Н' + 41: 1, # 'О' + 30: 2, # 'П' + 39: 1, # 'Р' + 28: 2, # 'С' + 34: 1, # 'Т' + 51: 1, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 2, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 2, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 2, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 2, # 'о' + 13: 0, # 'п' + 7: 2, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 1, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 49: { # 'Х' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 1, # 'А' + 32: 0, # 'Б' + 35: 1, # 'В' + 43: 1, # 'Г' + 37: 1, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 1, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 1, # 'О' + 30: 1, # 'П' + 39: 1, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 1, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 2, # 'а' + 18: 0, # 'б' + 9: 1, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 2, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 1, # 'л' + 14: 1, # 'м' + 6: 0, # 'н' + 4: 2, # 'о' + 13: 0, # 'п' + 7: 2, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 53: { # 'Ц' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 1, # 'А' + 32: 0, # 'Б' + 35: 1, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 2, # 'И' + 59: 0, # 'Й' + 33: 2, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 1, # 'Р' + 28: 2, # 'С' + 34: 0, # 'Т' + 51: 1, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 2, # 'а' + 18: 0, # 'б' + 9: 2, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 2, # 'е' + 23: 0, # 'ж' + 15: 1, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 1, # 'о' + 13: 0, # 'п' + 7: 1, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 1, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 50: { # 'Ч' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 2, # 'А' + 32: 1, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 1, # 'З' + 40: 1, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 0, # 'М' + 36: 1, # 'Н' + 41: 1, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 1, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 2, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 3, # 'е' + 23: 1, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 1, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 2, # 'о' + 13: 0, # 'п' + 7: 1, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 1, # 'ь' + 42: 0, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 54: { # 'Ш' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 1, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 1, # 'З' + 40: 1, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 1, # 'Н' + 41: 1, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 1, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 2, # 'а' + 18: 0, # 'б' + 9: 2, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 2, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 2, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 1, # 'л' + 14: 1, # 'м' + 6: 1, # 'н' + 4: 2, # 'о' + 13: 1, # 'п' + 7: 1, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 1, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 1, # 'ь' + 42: 0, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 57: { # 'Щ' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 1, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 1, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 1, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 2, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 2, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 1, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 1, # 'о' + 13: 0, # 'п' + 7: 1, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 1, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 61: { # 'Ъ' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 0, # 'Г' + 37: 1, # 'Д' + 44: 0, # 'Е' + 55: 1, # 'Ж' + 47: 1, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 2, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 0, # 'О' + 30: 1, # 'П' + 39: 2, # 'Р' + 28: 1, # 'С' + 34: 1, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 1, # 'Х' + 53: 1, # 'Ц' + 50: 1, # 'Ч' + 54: 1, # 'Ш' + 57: 1, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 0, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 0, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 0, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 1, # 'л' + 14: 0, # 'м' + 6: 1, # 'н' + 4: 0, # 'о' + 13: 0, # 'п' + 7: 1, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 0, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 60: { # 'Ю' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 1, # 'А' + 32: 1, # 'Б' + 35: 0, # 'В' + 43: 1, # 'Г' + 37: 1, # 'Д' + 44: 0, # 'Е' + 55: 1, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 0, # 'М' + 36: 1, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 1, # 'Р' + 28: 1, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 0, # 'а' + 18: 1, # 'б' + 9: 1, # 'в' + 20: 2, # 'г' + 11: 1, # 'д' + 3: 0, # 'е' + 23: 2, # 'ж' + 15: 1, # 'з' + 2: 1, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 1, # 'л' + 14: 1, # 'м' + 6: 1, # 'н' + 4: 0, # 'о' + 13: 1, # 'п' + 7: 1, # 'р' + 8: 1, # 'с' + 5: 1, # 'т' + 19: 0, # 'у' + 29: 0, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 56: { # 'Я' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 1, # 'Б' + 35: 1, # 'В' + 43: 1, # 'Г' + 37: 1, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 1, # 'Л' + 38: 1, # 'М' + 36: 1, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 1, # 'С' + 34: 2, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 0, # 'а' + 18: 1, # 'б' + 9: 1, # 'в' + 20: 1, # 'г' + 11: 1, # 'д' + 3: 0, # 'е' + 23: 0, # 'ж' + 15: 1, # 'з' + 2: 1, # 'и' + 26: 1, # 'й' + 12: 1, # 'к' + 10: 1, # 'л' + 14: 2, # 'м' + 6: 2, # 'н' + 4: 0, # 'о' + 13: 2, # 'п' + 7: 1, # 'р' + 8: 1, # 'с' + 5: 1, # 'т' + 19: 0, # 'у' + 29: 0, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 1, # 'ш' + 24: 0, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 1: { # 'а' + 63: 1, # 'e' + 45: 1, # '\xad' + 31: 1, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 1, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 1, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 3, # 'г' + 11: 3, # 'д' + 3: 3, # 'е' + 23: 3, # 'ж' + 15: 3, # 'з' + 2: 3, # 'и' + 26: 3, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 3, # 'м' + 6: 3, # 'н' + 4: 2, # 'о' + 13: 3, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 3, # 'у' + 29: 3, # 'ф' + 25: 3, # 'х' + 22: 3, # 'ц' + 21: 3, # 'ч' + 27: 3, # 'ш' + 24: 3, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 18: { # 'б' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 0, # 'б' + 9: 3, # 'в' + 20: 1, # 'г' + 11: 2, # 'д' + 3: 3, # 'е' + 23: 1, # 'ж' + 15: 1, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 3, # 'л' + 14: 2, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 1, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 0, # 'т' + 19: 3, # 'у' + 29: 0, # 'ф' + 25: 2, # 'х' + 22: 1, # 'ц' + 21: 1, # 'ч' + 27: 1, # 'ш' + 24: 3, # 'щ' + 17: 3, # 'ъ' + 52: 1, # 'ь' + 42: 2, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 9: { # 'в' + 63: 1, # 'e' + 45: 1, # '\xad' + 31: 0, # 'А' + 32: 1, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 1, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 0, # 'в' + 20: 2, # 'г' + 11: 3, # 'д' + 3: 3, # 'е' + 23: 1, # 'ж' + 15: 3, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 2, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 2, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 2, # 'х' + 22: 2, # 'ц' + 21: 3, # 'ч' + 27: 2, # 'ш' + 24: 1, # 'щ' + 17: 3, # 'ъ' + 52: 1, # 'ь' + 42: 2, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 20: { # 'г' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 2, # 'в' + 20: 1, # 'г' + 11: 2, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 1, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 3, # 'л' + 14: 1, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 1, # 'п' + 7: 3, # 'р' + 8: 2, # 'с' + 5: 2, # 'т' + 19: 3, # 'у' + 29: 1, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 1, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 3, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 11: { # 'д' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 2, # 'б' + 9: 3, # 'в' + 20: 2, # 'г' + 11: 2, # 'д' + 3: 3, # 'е' + 23: 3, # 'ж' + 15: 2, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 3, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 3, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 1, # 'т' + 19: 3, # 'у' + 29: 1, # 'ф' + 25: 2, # 'х' + 22: 2, # 'ц' + 21: 2, # 'ч' + 27: 1, # 'ш' + 24: 1, # 'щ' + 17: 3, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 3: { # 'е' + 63: 0, # 'e' + 45: 1, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 2, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 3, # 'г' + 11: 3, # 'д' + 3: 2, # 'е' + 23: 3, # 'ж' + 15: 3, # 'з' + 2: 2, # 'и' + 26: 3, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 3, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 3, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 2, # 'у' + 29: 3, # 'ф' + 25: 3, # 'х' + 22: 3, # 'ц' + 21: 3, # 'ч' + 27: 3, # 'ш' + 24: 3, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 23: { # 'ж' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 3, # 'б' + 9: 2, # 'в' + 20: 1, # 'г' + 11: 3, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 2, # 'к' + 10: 1, # 'л' + 14: 1, # 'м' + 6: 3, # 'н' + 4: 2, # 'о' + 13: 1, # 'п' + 7: 1, # 'р' + 8: 1, # 'с' + 5: 1, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 1, # 'ц' + 21: 1, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 15: { # 'з' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 3, # 'г' + 11: 3, # 'д' + 3: 3, # 'е' + 23: 1, # 'ж' + 15: 1, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 3, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 3, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 3, # 'у' + 29: 1, # 'ф' + 25: 2, # 'х' + 22: 2, # 'ц' + 21: 2, # 'ч' + 27: 2, # 'ш' + 24: 1, # 'щ' + 17: 2, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 2, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 2: { # 'и' + 63: 1, # 'e' + 45: 1, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 1, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 1, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 1, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 1, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 3, # 'г' + 11: 3, # 'д' + 3: 3, # 'е' + 23: 3, # 'ж' + 15: 3, # 'з' + 2: 3, # 'и' + 26: 3, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 3, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 3, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 2, # 'у' + 29: 3, # 'ф' + 25: 3, # 'х' + 22: 3, # 'ц' + 21: 3, # 'ч' + 27: 3, # 'ш' + 24: 3, # 'щ' + 17: 2, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 26: { # 'й' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 1, # 'а' + 18: 2, # 'б' + 9: 2, # 'в' + 20: 1, # 'г' + 11: 2, # 'д' + 3: 2, # 'е' + 23: 0, # 'ж' + 15: 2, # 'з' + 2: 1, # 'и' + 26: 0, # 'й' + 12: 3, # 'к' + 10: 2, # 'л' + 14: 2, # 'м' + 6: 3, # 'н' + 4: 2, # 'о' + 13: 1, # 'п' + 7: 2, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 1, # 'у' + 29: 2, # 'ф' + 25: 1, # 'х' + 22: 2, # 'ц' + 21: 2, # 'ч' + 27: 1, # 'ш' + 24: 1, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 12: { # 'к' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 1, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 1, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 3, # 'в' + 20: 2, # 'г' + 11: 1, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 2, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 3, # 'л' + 14: 2, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 1, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 3, # 'у' + 29: 1, # 'ф' + 25: 1, # 'х' + 22: 3, # 'ц' + 21: 2, # 'ч' + 27: 1, # 'ш' + 24: 0, # 'щ' + 17: 3, # 'ъ' + 52: 1, # 'ь' + 42: 2, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 10: { # 'л' + 63: 1, # 'e' + 45: 1, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 1, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 3, # 'г' + 11: 2, # 'д' + 3: 3, # 'е' + 23: 3, # 'ж' + 15: 2, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 3, # 'к' + 10: 1, # 'л' + 14: 2, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 2, # 'п' + 7: 2, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 3, # 'у' + 29: 2, # 'ф' + 25: 2, # 'х' + 22: 2, # 'ц' + 21: 2, # 'ч' + 27: 2, # 'ш' + 24: 1, # 'щ' + 17: 3, # 'ъ' + 52: 2, # 'ь' + 42: 3, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 14: { # 'м' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 1, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 1, # 'г' + 11: 1, # 'д' + 3: 3, # 'е' + 23: 1, # 'ж' + 15: 1, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 2, # 'к' + 10: 3, # 'л' + 14: 1, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 3, # 'п' + 7: 2, # 'р' + 8: 2, # 'с' + 5: 1, # 'т' + 19: 3, # 'у' + 29: 2, # 'ф' + 25: 1, # 'х' + 22: 2, # 'ц' + 21: 2, # 'ч' + 27: 2, # 'ш' + 24: 1, # 'щ' + 17: 3, # 'ъ' + 52: 1, # 'ь' + 42: 2, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 6: { # 'н' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 1, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 2, # 'б' + 9: 2, # 'в' + 20: 3, # 'г' + 11: 3, # 'д' + 3: 3, # 'е' + 23: 2, # 'ж' + 15: 2, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 3, # 'к' + 10: 2, # 'л' + 14: 1, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 1, # 'п' + 7: 2, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 3, # 'у' + 29: 3, # 'ф' + 25: 2, # 'х' + 22: 3, # 'ц' + 21: 3, # 'ч' + 27: 2, # 'ш' + 24: 1, # 'щ' + 17: 3, # 'ъ' + 52: 2, # 'ь' + 42: 2, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 4: { # 'о' + 63: 0, # 'e' + 45: 1, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 2, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 3, # 'г' + 11: 3, # 'д' + 3: 3, # 'е' + 23: 3, # 'ж' + 15: 3, # 'з' + 2: 3, # 'и' + 26: 3, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 3, # 'м' + 6: 3, # 'н' + 4: 2, # 'о' + 13: 3, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 2, # 'у' + 29: 3, # 'ф' + 25: 3, # 'х' + 22: 3, # 'ц' + 21: 3, # 'ч' + 27: 3, # 'ш' + 24: 3, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 13: { # 'п' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 2, # 'в' + 20: 1, # 'г' + 11: 1, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 1, # 'з' + 2: 3, # 'и' + 26: 1, # 'й' + 12: 2, # 'к' + 10: 3, # 'л' + 14: 1, # 'м' + 6: 2, # 'н' + 4: 3, # 'о' + 13: 1, # 'п' + 7: 3, # 'р' + 8: 2, # 'с' + 5: 2, # 'т' + 19: 3, # 'у' + 29: 1, # 'ф' + 25: 1, # 'х' + 22: 2, # 'ц' + 21: 2, # 'ч' + 27: 1, # 'ш' + 24: 1, # 'щ' + 17: 3, # 'ъ' + 52: 1, # 'ь' + 42: 2, # 'ю' + 16: 2, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 7: { # 'р' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 3, # 'г' + 11: 3, # 'д' + 3: 3, # 'е' + 23: 3, # 'ж' + 15: 2, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 3, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 2, # 'п' + 7: 1, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 3, # 'у' + 29: 2, # 'ф' + 25: 3, # 'х' + 22: 3, # 'ц' + 21: 2, # 'ч' + 27: 3, # 'ш' + 24: 1, # 'щ' + 17: 3, # 'ъ' + 52: 1, # 'ь' + 42: 2, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 8: { # 'с' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 2, # 'б' + 9: 3, # 'в' + 20: 2, # 'г' + 11: 2, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 1, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 3, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 3, # 'п' + 7: 3, # 'р' + 8: 1, # 'с' + 5: 3, # 'т' + 19: 3, # 'у' + 29: 2, # 'ф' + 25: 2, # 'х' + 22: 2, # 'ц' + 21: 2, # 'ч' + 27: 2, # 'ш' + 24: 0, # 'щ' + 17: 3, # 'ъ' + 52: 2, # 'ь' + 42: 2, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 5: { # 'т' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 2, # 'г' + 11: 2, # 'д' + 3: 3, # 'е' + 23: 1, # 'ж' + 15: 1, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 2, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 2, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 3, # 'у' + 29: 1, # 'ф' + 25: 2, # 'х' + 22: 2, # 'ц' + 21: 2, # 'ч' + 27: 1, # 'ш' + 24: 1, # 'щ' + 17: 3, # 'ъ' + 52: 2, # 'ь' + 42: 2, # 'ю' + 16: 3, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 19: { # 'у' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 3, # 'г' + 11: 3, # 'д' + 3: 2, # 'е' + 23: 3, # 'ж' + 15: 3, # 'з' + 2: 2, # 'и' + 26: 2, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 3, # 'м' + 6: 3, # 'н' + 4: 2, # 'о' + 13: 3, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 1, # 'у' + 29: 2, # 'ф' + 25: 2, # 'х' + 22: 2, # 'ц' + 21: 3, # 'ч' + 27: 3, # 'ш' + 24: 2, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 29: { # 'ф' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 1, # 'в' + 20: 1, # 'г' + 11: 0, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 2, # 'к' + 10: 2, # 'л' + 14: 1, # 'м' + 6: 1, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 2, # 'р' + 8: 2, # 'с' + 5: 2, # 'т' + 19: 2, # 'у' + 29: 0, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 1, # 'ч' + 27: 1, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 2, # 'ь' + 42: 1, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 25: { # 'х' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 3, # 'в' + 20: 0, # 'г' + 11: 1, # 'д' + 3: 2, # 'е' + 23: 0, # 'ж' + 15: 1, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 2, # 'л' + 14: 2, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 1, # 'п' + 7: 3, # 'р' + 8: 1, # 'с' + 5: 2, # 'т' + 19: 3, # 'у' + 29: 0, # 'ф' + 25: 1, # 'х' + 22: 0, # 'ц' + 21: 1, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 22: { # 'ц' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 2, # 'в' + 20: 1, # 'г' + 11: 1, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 1, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 2, # 'к' + 10: 1, # 'л' + 14: 1, # 'м' + 6: 1, # 'н' + 4: 2, # 'о' + 13: 1, # 'п' + 7: 1, # 'р' + 8: 1, # 'с' + 5: 1, # 'т' + 19: 2, # 'у' + 29: 1, # 'ф' + 25: 1, # 'х' + 22: 1, # 'ц' + 21: 1, # 'ч' + 27: 1, # 'ш' + 24: 1, # 'щ' + 17: 2, # 'ъ' + 52: 1, # 'ь' + 42: 0, # 'ю' + 16: 2, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 21: { # 'ч' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 1, # 'б' + 9: 3, # 'в' + 20: 1, # 'г' + 11: 0, # 'д' + 3: 3, # 'е' + 23: 1, # 'ж' + 15: 0, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 3, # 'к' + 10: 2, # 'л' + 14: 2, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 2, # 'р' + 8: 0, # 'с' + 5: 2, # 'т' + 19: 3, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 1, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 27: { # 'ш' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 0, # 'б' + 9: 2, # 'в' + 20: 0, # 'г' + 11: 1, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 3, # 'к' + 10: 2, # 'л' + 14: 1, # 'м' + 6: 3, # 'н' + 4: 2, # 'о' + 13: 2, # 'п' + 7: 1, # 'р' + 8: 0, # 'с' + 5: 1, # 'т' + 19: 2, # 'у' + 29: 1, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 1, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 2, # 'ъ' + 52: 1, # 'ь' + 42: 1, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 24: { # 'щ' + 63: 1, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 3, # 'а' + 18: 0, # 'б' + 9: 1, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 3, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 3, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 2, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 1, # 'р' + 8: 0, # 'с' + 5: 2, # 'т' + 19: 3, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 1, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 2, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 17: { # 'ъ' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 1, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 3, # 'г' + 11: 3, # 'д' + 3: 2, # 'е' + 23: 3, # 'ж' + 15: 3, # 'з' + 2: 1, # 'и' + 26: 2, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 3, # 'м' + 6: 3, # 'н' + 4: 3, # 'о' + 13: 3, # 'п' + 7: 3, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 1, # 'у' + 29: 1, # 'ф' + 25: 2, # 'х' + 22: 2, # 'ц' + 21: 3, # 'ч' + 27: 2, # 'ш' + 24: 3, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 2, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 52: { # 'ь' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 0, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 1, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 0, # 'и' + 26: 0, # 'й' + 12: 1, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 1, # 'н' + 4: 3, # 'о' + 13: 0, # 'п' + 7: 0, # 'р' + 8: 0, # 'с' + 5: 1, # 'т' + 19: 0, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 1, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 1, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 42: { # 'ю' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 1, # 'а' + 18: 2, # 'б' + 9: 1, # 'в' + 20: 2, # 'г' + 11: 2, # 'д' + 3: 1, # 'е' + 23: 2, # 'ж' + 15: 2, # 'з' + 2: 1, # 'и' + 26: 1, # 'й' + 12: 2, # 'к' + 10: 2, # 'л' + 14: 2, # 'м' + 6: 2, # 'н' + 4: 1, # 'о' + 13: 1, # 'п' + 7: 2, # 'р' + 8: 2, # 'с' + 5: 2, # 'т' + 19: 1, # 'у' + 29: 1, # 'ф' + 25: 1, # 'х' + 22: 2, # 'ц' + 21: 3, # 'ч' + 27: 1, # 'ш' + 24: 1, # 'щ' + 17: 1, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 16: { # 'я' + 63: 0, # 'e' + 45: 1, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 0, # 'а' + 18: 3, # 'б' + 9: 3, # 'в' + 20: 2, # 'г' + 11: 3, # 'д' + 3: 2, # 'е' + 23: 1, # 'ж' + 15: 2, # 'з' + 2: 1, # 'и' + 26: 2, # 'й' + 12: 3, # 'к' + 10: 3, # 'л' + 14: 3, # 'м' + 6: 3, # 'н' + 4: 1, # 'о' + 13: 2, # 'п' + 7: 2, # 'р' + 8: 3, # 'с' + 5: 3, # 'т' + 19: 1, # 'у' + 29: 1, # 'ф' + 25: 3, # 'х' + 22: 2, # 'ц' + 21: 1, # 'ч' + 27: 1, # 'ш' + 24: 2, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 1, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 58: { # 'є' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 0, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 0, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 0, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 0, # 'о' + 13: 0, # 'п' + 7: 0, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 0, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, + 62: { # '№' + 63: 0, # 'e' + 45: 0, # '\xad' + 31: 0, # 'А' + 32: 0, # 'Б' + 35: 0, # 'В' + 43: 0, # 'Г' + 37: 0, # 'Д' + 44: 0, # 'Е' + 55: 0, # 'Ж' + 47: 0, # 'З' + 40: 0, # 'И' + 59: 0, # 'Й' + 33: 0, # 'К' + 46: 0, # 'Л' + 38: 0, # 'М' + 36: 0, # 'Н' + 41: 0, # 'О' + 30: 0, # 'П' + 39: 0, # 'Р' + 28: 0, # 'С' + 34: 0, # 'Т' + 51: 0, # 'У' + 48: 0, # 'Ф' + 49: 0, # 'Х' + 53: 0, # 'Ц' + 50: 0, # 'Ч' + 54: 0, # 'Ш' + 57: 0, # 'Щ' + 61: 0, # 'Ъ' + 60: 0, # 'Ю' + 56: 0, # 'Я' + 1: 0, # 'а' + 18: 0, # 'б' + 9: 0, # 'в' + 20: 0, # 'г' + 11: 0, # 'д' + 3: 0, # 'е' + 23: 0, # 'ж' + 15: 0, # 'з' + 2: 0, # 'и' + 26: 0, # 'й' + 12: 0, # 'к' + 10: 0, # 'л' + 14: 0, # 'м' + 6: 0, # 'н' + 4: 0, # 'о' + 13: 0, # 'п' + 7: 0, # 'р' + 8: 0, # 'с' + 5: 0, # 'т' + 19: 0, # 'у' + 29: 0, # 'ф' + 25: 0, # 'х' + 22: 0, # 'ц' + 21: 0, # 'ч' + 27: 0, # 'ш' + 24: 0, # 'щ' + 17: 0, # 'ъ' + 52: 0, # 'ь' + 42: 0, # 'ю' + 16: 0, # 'я' + 58: 0, # 'є' + 62: 0, # '№' + }, +} + +# 255: Undefined characters that did not exist in training text # 254: Carriage/Return # 253: symbol (punctuation) that does not belong to word # 252: 0 - 9 +# 251: Control characters -# Character Mapping Table: -# this table is modified base on win1251BulgarianCharToOrderMap, so -# only number <64 is sure valid - -Latin5_BulgarianCharToOrderMap = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253, 77, 90, 99,100, 72,109,107,101, 79,185, 81,102, 76, 94, 82, # 40 -110,186,108, 91, 74,119, 84, 96,111,187,115,253,253,253,253,253, # 50 -253, 65, 69, 70, 66, 63, 68,112,103, 92,194,104, 95, 86, 87, 71, # 60 -116,195, 85, 93, 97,113,196,197,198,199,200,253,253,253,253,253, # 70 -194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209, # 80 -210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225, # 90 - 81,226,227,228,229,230,105,231,232,233,234,235,236, 45,237,238, # a0 - 31, 32, 35, 43, 37, 44, 55, 47, 40, 59, 33, 46, 38, 36, 41, 30, # b0 - 39, 28, 34, 51, 48, 49, 53, 50, 54, 57, 61,239, 67,240, 60, 56, # c0 - 1, 18, 9, 20, 11, 3, 23, 15, 2, 26, 12, 10, 14, 6, 4, 13, # d0 - 7, 8, 5, 19, 29, 25, 22, 21, 27, 24, 17, 75, 52,241, 42, 16, # e0 - 62,242,243,244, 58,245, 98,246,247,248,249,250,251, 91,252,253, # f0 -) - -win1251BulgarianCharToOrderMap = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253, 77, 90, 99,100, 72,109,107,101, 79,185, 81,102, 76, 94, 82, # 40 -110,186,108, 91, 74,119, 84, 96,111,187,115,253,253,253,253,253, # 50 -253, 65, 69, 70, 66, 63, 68,112,103, 92,194,104, 95, 86, 87, 71, # 60 -116,195, 85, 93, 97,113,196,197,198,199,200,253,253,253,253,253, # 70 -206,207,208,209,210,211,212,213,120,214,215,216,217,218,219,220, # 80 -221, 78, 64, 83,121, 98,117,105,222,223,224,225,226,227,228,229, # 90 - 88,230,231,232,233,122, 89,106,234,235,236,237,238, 45,239,240, # a0 - 73, 80,118,114,241,242,243,244,245, 62, 58,246,247,248,249,250, # b0 - 31, 32, 35, 43, 37, 44, 55, 47, 40, 59, 33, 46, 38, 36, 41, 30, # c0 - 39, 28, 34, 51, 48, 49, 53, 50, 54, 57, 61,251, 67,252, 60, 56, # d0 - 1, 18, 9, 20, 11, 3, 23, 15, 2, 26, 12, 10, 14, 6, 4, 13, # e0 - 7, 8, 5, 19, 29, 25, 22, 21, 27, 24, 17, 75, 52,253, 42, 16, # f0 -) - -# Model Table: -# total sequences: 100% -# first 512 sequences: 96.9392% -# first 1024 sequences:3.0618% -# rest sequences: 0.2992% -# negative sequences: 0.0020% -BulgarianLangModel = ( -0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,2,3,3,3,3,3, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,3,3,3,2,2,3,2,2,1,2,2, -3,1,3,3,2,3,3,3,3,3,3,3,3,3,3,3,3,0,3,3,3,3,3,3,3,3,3,3,0,3,0,1, -0,0,0,0,0,0,0,0,0,0,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,2,3,3,3,3,3,3,3,3,0,3,1,0, -0,1,0,0,0,0,0,0,0,0,1,1,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -3,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,1,3,2,3,3,3,3,3,3,3,3,0,3,0,0, -0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,2,3,3,2,3,3,3,3,3,3,3,3,3,3,3,3,1,3,2,3,3,3,3,3,3,3,3,0,3,0,0, -0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,3,2,3,2,2,1,3,3,3,3,2,2,2,1,1,2,0,1,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,3,3,2,3,2,2,3,3,1,1,2,3,3,2,3,3,3,3,2,1,2,0,2,0,3,0,0, -0,0,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,3,3,1,3,3,3,3,3,2,3,2,3,3,3,3,3,2,3,3,1,3,0,3,0,2,0,0, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,3,3,3,1,3,3,2,3,3,3,1,3,3,2,3,2,2,2,0,0,2,0,2,0,2,0,0, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,3,3,3,3,0,3,3,3,2,2,3,3,3,1,2,2,3,2,1,1,2,0,2,0,0,0,0, -1,0,0,0,0,0,0,0,0,0,2,0,0,1,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,3,3,2,3,3,1,2,3,2,2,2,3,3,3,3,3,2,2,3,1,2,0,2,1,2,0,0, -0,0,0,0,0,0,0,0,0,0,3,0,0,1,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,1,3,3,3,3,3,2,3,3,3,2,3,3,2,3,2,2,2,3,1,2,0,1,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,3,3,3,3,3,3,1,1,1,2,2,1,3,1,3,2,2,3,0,0,1,0,1,0,1,0,0, -0,0,0,1,0,0,0,0,1,0,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,2,2,3,2,2,3,1,2,1,1,1,2,3,1,3,1,2,2,0,1,1,1,1,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,1,3,2,2,3,3,1,2,3,1,1,3,3,3,3,1,2,2,1,1,1,0,2,0,2,0,1, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,2,2,3,3,3,2,2,1,1,2,0,2,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, -3,0,1,2,1,3,3,2,3,3,3,3,3,2,3,2,1,0,3,1,2,1,2,1,2,3,2,1,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,1,1,2,3,3,3,3,3,3,3,3,3,3,3,3,0,0,3,1,3,3,2,3,3,2,2,2,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,3,3,3,3,0,3,3,3,3,3,2,1,1,2,1,3,3,0,3,1,1,1,1,3,2,0,1,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, -3,3,2,2,2,3,3,3,3,3,3,3,3,3,3,3,1,1,3,1,3,3,2,3,2,2,2,3,0,2,0,0, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,2,3,3,2,2,3,2,1,1,1,1,1,3,1,3,1,1,0,0,0,1,0,0,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,2,3,2,0,3,2,0,3,0,2,0,0,2,1,3,1,0,0,1,0,0,0,1,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,2,1,1,1,1,2,1,1,2,1,1,1,2,2,1,2,1,1,1,0,1,1,0,1,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,2,1,3,1,1,2,1,3,2,1,1,0,1,2,3,2,1,1,1,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,3,3,3,3,2,2,1,0,1,0,0,1,0,0,0,2,1,0,3,0,0,1,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,2,3,2,3,3,1,3,2,1,1,1,2,1,1,2,1,3,0,1,0,0,0,1,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,1,1,2,2,3,3,2,3,2,2,2,3,1,2,2,1,1,2,1,1,2,2,0,1,1,0,1,0,2,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,2,1,3,1,0,2,2,1,3,2,1,0,0,2,0,2,0,1,0,0,0,0,0,0,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,3,1,2,0,2,3,1,2,3,2,0,1,3,1,2,1,1,1,0,0,1,0,0,2,2,2,3, -2,2,2,2,1,2,1,1,2,2,1,1,2,0,1,1,1,0,0,1,1,0,0,1,1,0,0,0,1,1,0,1, -3,3,3,3,3,2,1,2,2,1,2,0,2,0,1,0,1,2,1,2,1,1,0,0,0,1,0,1,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1, -3,3,2,3,3,1,1,3,1,0,3,2,1,0,0,0,1,2,0,2,0,1,0,0,0,1,0,1,2,1,2,2, -1,1,1,1,1,1,1,2,2,2,1,1,1,1,1,1,1,0,1,2,1,1,1,0,0,0,0,0,1,1,0,0, -3,1,0,1,0,2,3,2,2,2,3,2,2,2,2,2,1,0,2,1,2,1,1,1,0,1,2,1,2,2,2,1, -1,1,2,2,2,2,1,2,1,1,0,1,2,1,2,2,2,1,1,1,0,1,1,1,1,2,0,1,0,0,0,0, -2,3,2,3,3,0,0,2,1,0,2,1,0,0,0,0,2,3,0,2,0,0,0,0,0,1,0,0,2,0,1,2, -2,1,2,1,2,2,1,1,1,2,1,1,1,0,1,2,2,1,1,1,1,1,0,1,1,1,0,0,1,2,0,0, -3,3,2,2,3,0,2,3,1,1,2,0,0,0,1,0,0,2,0,2,0,0,0,1,0,1,0,1,2,0,2,2, -1,1,1,1,2,1,0,1,2,2,2,1,1,1,1,1,1,1,0,1,1,1,0,0,0,0,0,0,1,1,0,0, -2,3,2,3,3,0,0,3,0,1,1,0,1,0,0,0,2,2,1,2,0,0,0,0,0,0,0,0,2,0,1,2, -2,2,1,1,1,1,1,2,2,2,1,0,2,0,1,0,1,0,0,1,0,1,0,0,1,0,0,0,0,1,0,0, -3,3,3,3,2,2,2,2,2,0,2,1,1,1,1,2,1,2,1,1,0,2,0,1,0,1,0,0,2,0,1,2, -1,1,1,1,1,1,1,2,2,1,1,0,2,0,1,0,2,0,0,1,1,1,0,0,2,0,0,0,1,1,0,0, -2,3,3,3,3,1,0,0,0,0,0,0,0,0,0,0,2,0,0,1,1,0,0,0,0,0,0,1,2,0,1,2, -2,2,2,1,1,2,1,1,2,2,2,1,2,0,1,1,1,1,1,1,0,1,1,1,1,0,0,1,1,1,0,0, -2,3,3,3,3,0,2,2,0,2,1,0,0,0,1,1,1,2,0,2,0,0,0,3,0,0,0,0,2,0,2,2, -1,1,1,2,1,2,1,1,2,2,2,1,2,0,1,1,1,0,1,1,1,1,0,2,1,0,0,0,1,1,0,0, -2,3,3,3,3,0,2,1,0,0,2,0,0,0,0,0,1,2,0,2,0,0,0,0,0,0,0,0,2,0,1,2, -1,1,1,2,1,1,1,1,2,2,2,0,1,0,1,1,1,0,0,1,1,1,0,0,1,0,0,0,0,1,0,0, -3,3,2,2,3,0,1,0,1,0,0,0,0,0,0,0,1,1,0,3,0,0,0,0,0,0,0,0,1,0,2,2, -1,1,1,1,1,2,1,1,2,2,1,2,2,1,0,1,1,1,1,1,0,1,0,0,1,0,0,0,1,1,0,0, -3,1,0,1,0,2,2,2,2,3,2,1,1,1,2,3,0,0,1,0,2,1,1,0,1,1,1,1,2,1,1,1, -1,2,2,1,2,1,2,2,1,1,0,1,2,1,2,2,1,1,1,0,0,1,1,1,2,1,0,1,0,0,0,0, -2,1,0,1,0,3,1,2,2,2,2,1,2,2,1,1,1,0,2,1,2,2,1,1,2,1,1,0,2,1,1,1, -1,2,2,2,2,2,2,2,1,2,0,1,1,0,2,1,1,1,1,1,0,0,1,1,1,1,0,1,0,0,0,0, -2,1,1,1,1,2,2,2,2,1,2,2,2,1,2,2,1,1,2,1,2,3,2,2,1,1,1,1,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,2,2,3,2,0,1,2,0,1,2,1,1,0,1,0,1,2,1,2,0,0,0,1,1,0,0,0,1,0,0,2, -1,1,0,0,1,1,0,1,1,1,1,0,2,0,1,1,1,0,0,1,1,0,0,0,0,1,0,0,0,1,0,0, -2,0,0,0,0,1,2,2,2,2,2,2,2,1,2,1,1,1,1,1,1,1,0,1,1,1,1,1,2,1,1,1, -1,2,2,2,2,1,1,2,1,2,1,1,1,0,2,1,2,1,1,1,0,2,1,1,1,1,0,1,0,0,0,0, -3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0, -1,1,0,1,0,1,1,1,1,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,2,2,3,2,0,0,0,0,1,0,0,0,0,0,0,1,1,0,2,0,0,0,0,0,0,0,0,1,0,1,2, -1,1,1,1,1,1,0,0,2,2,2,2,2,0,1,1,0,1,1,1,1,1,0,0,1,0,0,0,1,1,0,1, -2,3,1,2,1,0,1,1,0,2,2,2,0,0,1,0,0,1,1,1,1,0,0,0,0,0,0,0,1,0,1,2, -1,1,1,1,2,1,1,1,1,1,1,1,1,0,1,1,0,1,0,1,0,1,0,0,1,0,0,0,0,1,0,0, -2,2,2,2,2,0,0,2,0,0,2,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,2,0,2,2, -1,1,1,1,1,0,0,1,2,1,1,0,1,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0, -1,2,2,2,2,0,0,2,0,1,1,0,0,0,1,0,0,2,0,2,0,0,0,0,0,0,0,0,0,0,1,1, -0,0,0,1,1,1,1,1,1,1,1,1,1,0,1,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0, -1,2,2,3,2,0,0,1,0,0,1,0,0,0,0,0,0,1,0,2,0,0,0,1,0,0,0,0,0,0,0,2, -1,1,0,0,1,0,0,0,1,1,0,0,1,0,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0, -2,1,2,2,2,1,2,1,2,2,1,1,2,1,1,1,0,1,1,1,1,2,0,1,0,1,1,1,1,0,1,1, -1,1,2,1,1,1,1,1,1,0,0,1,2,1,1,1,1,1,1,0,0,1,1,1,0,0,0,0,0,0,0,0, -1,0,0,1,3,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,2,2,2,1,0,0,1,0,2,0,0,0,0,0,1,1,1,0,1,0,0,0,0,0,0,0,0,2,0,0,1, -0,2,0,1,0,0,1,1,2,0,1,0,1,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0, -1,2,2,2,2,0,1,1,0,2,1,0,1,1,1,0,0,1,0,2,0,1,0,0,0,0,0,0,0,0,0,1, -0,1,0,0,1,0,0,0,1,1,0,0,1,0,0,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0, -2,2,2,2,2,0,0,1,0,0,0,1,0,1,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,1, -0,1,0,1,1,1,0,0,1,1,1,0,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0, -2,0,1,0,0,1,2,1,1,1,1,1,1,2,2,1,0,0,1,0,1,0,0,0,0,1,1,1,1,0,0,0, -1,1,2,1,1,1,1,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,2,1,2,1,0,0,1,0,0,0,0,0,0,0,0,1,1,0,1,0,0,0,0,0,0,0,0,0,0,0,1, -0,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,0,0,1,2,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0, -0,1,1,0,1,1,1,0,0,1,0,0,1,0,1,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0, -1,0,1,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,1,0,2,0,0,2,0,1,0,0,1,0,0,1, -1,1,0,0,1,1,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0, -1,1,1,1,1,1,1,2,0,0,0,0,0,0,2,1,0,1,1,0,0,1,1,1,0,1,0,0,0,0,0,0, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,1,0,1,1,1,1,1,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -) - -Latin5BulgarianModel = { - 'char_to_order_map': Latin5_BulgarianCharToOrderMap, - 'precedence_matrix': BulgarianLangModel, - 'typical_positive_ratio': 0.969392, - 'keep_english_letter': False, - 'charset_name': "ISO-8859-5", - 'language': 'Bulgairan', +# Character Mapping Table(s): +ISO_8859_5_BULGARIAN_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 77, # 'A' + 66: 90, # 'B' + 67: 99, # 'C' + 68: 100, # 'D' + 69: 72, # 'E' + 70: 109, # 'F' + 71: 107, # 'G' + 72: 101, # 'H' + 73: 79, # 'I' + 74: 185, # 'J' + 75: 81, # 'K' + 76: 102, # 'L' + 77: 76, # 'M' + 78: 94, # 'N' + 79: 82, # 'O' + 80: 110, # 'P' + 81: 186, # 'Q' + 82: 108, # 'R' + 83: 91, # 'S' + 84: 74, # 'T' + 85: 119, # 'U' + 86: 84, # 'V' + 87: 96, # 'W' + 88: 111, # 'X' + 89: 187, # 'Y' + 90: 115, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 65, # 'a' + 98: 69, # 'b' + 99: 70, # 'c' + 100: 66, # 'd' + 101: 63, # 'e' + 102: 68, # 'f' + 103: 112, # 'g' + 104: 103, # 'h' + 105: 92, # 'i' + 106: 194, # 'j' + 107: 104, # 'k' + 108: 95, # 'l' + 109: 86, # 'm' + 110: 87, # 'n' + 111: 71, # 'o' + 112: 116, # 'p' + 113: 195, # 'q' + 114: 85, # 'r' + 115: 93, # 's' + 116: 97, # 't' + 117: 113, # 'u' + 118: 196, # 'v' + 119: 197, # 'w' + 120: 198, # 'x' + 121: 199, # 'y' + 122: 200, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 194, # '\x80' + 129: 195, # '\x81' + 130: 196, # '\x82' + 131: 197, # '\x83' + 132: 198, # '\x84' + 133: 199, # '\x85' + 134: 200, # '\x86' + 135: 201, # '\x87' + 136: 202, # '\x88' + 137: 203, # '\x89' + 138: 204, # '\x8a' + 139: 205, # '\x8b' + 140: 206, # '\x8c' + 141: 207, # '\x8d' + 142: 208, # '\x8e' + 143: 209, # '\x8f' + 144: 210, # '\x90' + 145: 211, # '\x91' + 146: 212, # '\x92' + 147: 213, # '\x93' + 148: 214, # '\x94' + 149: 215, # '\x95' + 150: 216, # '\x96' + 151: 217, # '\x97' + 152: 218, # '\x98' + 153: 219, # '\x99' + 154: 220, # '\x9a' + 155: 221, # '\x9b' + 156: 222, # '\x9c' + 157: 223, # '\x9d' + 158: 224, # '\x9e' + 159: 225, # '\x9f' + 160: 81, # '\xa0' + 161: 226, # 'Ё' + 162: 227, # 'Ђ' + 163: 228, # 'Ѓ' + 164: 229, # 'Є' + 165: 230, # 'Ѕ' + 166: 105, # 'І' + 167: 231, # 'Ї' + 168: 232, # 'Ј' + 169: 233, # 'Љ' + 170: 234, # 'Њ' + 171: 235, # 'Ћ' + 172: 236, # 'Ќ' + 173: 45, # '\xad' + 174: 237, # 'Ў' + 175: 238, # 'Џ' + 176: 31, # 'А' + 177: 32, # 'Б' + 178: 35, # 'В' + 179: 43, # 'Г' + 180: 37, # 'Д' + 181: 44, # 'Е' + 182: 55, # 'Ж' + 183: 47, # 'З' + 184: 40, # 'И' + 185: 59, # 'Й' + 186: 33, # 'К' + 187: 46, # 'Л' + 188: 38, # 'М' + 189: 36, # 'Н' + 190: 41, # 'О' + 191: 30, # 'П' + 192: 39, # 'Р' + 193: 28, # 'С' + 194: 34, # 'Т' + 195: 51, # 'У' + 196: 48, # 'Ф' + 197: 49, # 'Х' + 198: 53, # 'Ц' + 199: 50, # 'Ч' + 200: 54, # 'Ш' + 201: 57, # 'Щ' + 202: 61, # 'Ъ' + 203: 239, # 'Ы' + 204: 67, # 'Ь' + 205: 240, # 'Э' + 206: 60, # 'Ю' + 207: 56, # 'Я' + 208: 1, # 'а' + 209: 18, # 'б' + 210: 9, # 'в' + 211: 20, # 'г' + 212: 11, # 'д' + 213: 3, # 'е' + 214: 23, # 'ж' + 215: 15, # 'з' + 216: 2, # 'и' + 217: 26, # 'й' + 218: 12, # 'к' + 219: 10, # 'л' + 220: 14, # 'м' + 221: 6, # 'н' + 222: 4, # 'о' + 223: 13, # 'п' + 224: 7, # 'р' + 225: 8, # 'с' + 226: 5, # 'т' + 227: 19, # 'у' + 228: 29, # 'ф' + 229: 25, # 'х' + 230: 22, # 'ц' + 231: 21, # 'ч' + 232: 27, # 'ш' + 233: 24, # 'щ' + 234: 17, # 'ъ' + 235: 75, # 'ы' + 236: 52, # 'ь' + 237: 241, # 'э' + 238: 42, # 'ю' + 239: 16, # 'я' + 240: 62, # '№' + 241: 242, # 'ё' + 242: 243, # 'ђ' + 243: 244, # 'ѓ' + 244: 58, # 'є' + 245: 245, # 'ѕ' + 246: 98, # 'і' + 247: 246, # 'ї' + 248: 247, # 'ј' + 249: 248, # 'љ' + 250: 249, # 'њ' + 251: 250, # 'ћ' + 252: 251, # 'ќ' + 253: 91, # '§' + 254: 252, # 'ў' + 255: 253, # 'џ' } -Win1251BulgarianModel = { - 'char_to_order_map': win1251BulgarianCharToOrderMap, - 'precedence_matrix': BulgarianLangModel, - 'typical_positive_ratio': 0.969392, - 'keep_english_letter': False, - 'charset_name': "windows-1251", - 'language': 'Bulgarian', +ISO_8859_5_BULGARIAN_MODEL = SingleByteCharSetModel(charset_name='ISO-8859-5', + language='Bulgarian', + char_to_order_map=ISO_8859_5_BULGARIAN_CHAR_TO_ORDER, + language_model=BULGARIAN_LANG_MODEL, + typical_positive_ratio=0.969392, + keep_ascii_letters=False, + alphabet='АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЬЮЯабвгдежзийклмнопрстуфхцчшщъьюя') + +WINDOWS_1251_BULGARIAN_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 77, # 'A' + 66: 90, # 'B' + 67: 99, # 'C' + 68: 100, # 'D' + 69: 72, # 'E' + 70: 109, # 'F' + 71: 107, # 'G' + 72: 101, # 'H' + 73: 79, # 'I' + 74: 185, # 'J' + 75: 81, # 'K' + 76: 102, # 'L' + 77: 76, # 'M' + 78: 94, # 'N' + 79: 82, # 'O' + 80: 110, # 'P' + 81: 186, # 'Q' + 82: 108, # 'R' + 83: 91, # 'S' + 84: 74, # 'T' + 85: 119, # 'U' + 86: 84, # 'V' + 87: 96, # 'W' + 88: 111, # 'X' + 89: 187, # 'Y' + 90: 115, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 65, # 'a' + 98: 69, # 'b' + 99: 70, # 'c' + 100: 66, # 'd' + 101: 63, # 'e' + 102: 68, # 'f' + 103: 112, # 'g' + 104: 103, # 'h' + 105: 92, # 'i' + 106: 194, # 'j' + 107: 104, # 'k' + 108: 95, # 'l' + 109: 86, # 'm' + 110: 87, # 'n' + 111: 71, # 'o' + 112: 116, # 'p' + 113: 195, # 'q' + 114: 85, # 'r' + 115: 93, # 's' + 116: 97, # 't' + 117: 113, # 'u' + 118: 196, # 'v' + 119: 197, # 'w' + 120: 198, # 'x' + 121: 199, # 'y' + 122: 200, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 206, # 'Ђ' + 129: 207, # 'Ѓ' + 130: 208, # '‚' + 131: 209, # 'ѓ' + 132: 210, # '„' + 133: 211, # '…' + 134: 212, # '†' + 135: 213, # '‡' + 136: 120, # '€' + 137: 214, # '‰' + 138: 215, # 'Љ' + 139: 216, # '‹' + 140: 217, # 'Њ' + 141: 218, # 'Ќ' + 142: 219, # 'Ћ' + 143: 220, # 'Џ' + 144: 221, # 'ђ' + 145: 78, # '‘' + 146: 64, # '’' + 147: 83, # '“' + 148: 121, # '”' + 149: 98, # '•' + 150: 117, # '–' + 151: 105, # '—' + 152: 222, # None + 153: 223, # '™' + 154: 224, # 'љ' + 155: 225, # '›' + 156: 226, # 'њ' + 157: 227, # 'ќ' + 158: 228, # 'ћ' + 159: 229, # 'џ' + 160: 88, # '\xa0' + 161: 230, # 'Ў' + 162: 231, # 'ў' + 163: 232, # 'Ј' + 164: 233, # '¤' + 165: 122, # 'Ґ' + 166: 89, # '¦' + 167: 106, # '§' + 168: 234, # 'Ё' + 169: 235, # '©' + 170: 236, # 'Є' + 171: 237, # '«' + 172: 238, # '¬' + 173: 45, # '\xad' + 174: 239, # '®' + 175: 240, # 'Ї' + 176: 73, # '°' + 177: 80, # '±' + 178: 118, # 'І' + 179: 114, # 'і' + 180: 241, # 'ґ' + 181: 242, # 'µ' + 182: 243, # '¶' + 183: 244, # '·' + 184: 245, # 'ё' + 185: 62, # '№' + 186: 58, # 'є' + 187: 246, # '»' + 188: 247, # 'ј' + 189: 248, # 'Ѕ' + 190: 249, # 'ѕ' + 191: 250, # 'ї' + 192: 31, # 'А' + 193: 32, # 'Б' + 194: 35, # 'В' + 195: 43, # 'Г' + 196: 37, # 'Д' + 197: 44, # 'Е' + 198: 55, # 'Ж' + 199: 47, # 'З' + 200: 40, # 'И' + 201: 59, # 'Й' + 202: 33, # 'К' + 203: 46, # 'Л' + 204: 38, # 'М' + 205: 36, # 'Н' + 206: 41, # 'О' + 207: 30, # 'П' + 208: 39, # 'Р' + 209: 28, # 'С' + 210: 34, # 'Т' + 211: 51, # 'У' + 212: 48, # 'Ф' + 213: 49, # 'Х' + 214: 53, # 'Ц' + 215: 50, # 'Ч' + 216: 54, # 'Ш' + 217: 57, # 'Щ' + 218: 61, # 'Ъ' + 219: 251, # 'Ы' + 220: 67, # 'Ь' + 221: 252, # 'Э' + 222: 60, # 'Ю' + 223: 56, # 'Я' + 224: 1, # 'а' + 225: 18, # 'б' + 226: 9, # 'в' + 227: 20, # 'г' + 228: 11, # 'д' + 229: 3, # 'е' + 230: 23, # 'ж' + 231: 15, # 'з' + 232: 2, # 'и' + 233: 26, # 'й' + 234: 12, # 'к' + 235: 10, # 'л' + 236: 14, # 'м' + 237: 6, # 'н' + 238: 4, # 'о' + 239: 13, # 'п' + 240: 7, # 'р' + 241: 8, # 'с' + 242: 5, # 'т' + 243: 19, # 'у' + 244: 29, # 'ф' + 245: 25, # 'х' + 246: 22, # 'ц' + 247: 21, # 'ч' + 248: 27, # 'ш' + 249: 24, # 'щ' + 250: 17, # 'ъ' + 251: 75, # 'ы' + 252: 52, # 'ь' + 253: 253, # 'э' + 254: 42, # 'ю' + 255: 16, # 'я' } + +WINDOWS_1251_BULGARIAN_MODEL = SingleByteCharSetModel(charset_name='windows-1251', + language='Bulgarian', + char_to_order_map=WINDOWS_1251_BULGARIAN_CHAR_TO_ORDER, + language_model=BULGARIAN_LANG_MODEL, + typical_positive_ratio=0.969392, + keep_ascii_letters=False, + alphabet='АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЬЮЯабвгдежзийклмнопрстуфхцчшщъьюя') + diff --git a/pipenv/patched/notpip/_vendor/chardet/langcyrillicmodel.py b/pipenv/patched/notpip/_vendor/chardet/langcyrillicmodel.py deleted file mode 100644 index e5f9a1fd..00000000 --- a/pipenv/patched/notpip/_vendor/chardet/langcyrillicmodel.py +++ /dev/null @@ -1,333 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -# KOI8-R language model -# Character Mapping Table: -KOI8R_char_to_order_map = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 -155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 -253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 - 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 -191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206, # 80 -207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222, # 90 -223,224,225, 68,226,227,228,229,230,231,232,233,234,235,236,237, # a0 -238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253, # b0 - 27, 3, 21, 28, 13, 2, 39, 19, 26, 4, 23, 11, 8, 12, 5, 1, # c0 - 15, 16, 9, 7, 6, 14, 24, 10, 17, 18, 20, 25, 30, 29, 22, 54, # d0 - 59, 37, 44, 58, 41, 48, 53, 46, 55, 42, 60, 36, 49, 38, 31, 34, # e0 - 35, 43, 45, 32, 40, 52, 56, 33, 61, 62, 51, 57, 47, 63, 50, 70, # f0 -) - -win1251_char_to_order_map = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 -155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 -253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 - 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 -191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206, -207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222, -223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238, -239,240,241,242,243,244,245,246, 68,247,248,249,250,251,252,253, - 37, 44, 33, 46, 41, 48, 56, 51, 42, 60, 36, 49, 38, 31, 34, 35, - 45, 32, 40, 52, 53, 55, 58, 50, 57, 63, 70, 62, 61, 47, 59, 43, - 3, 21, 10, 19, 13, 2, 24, 20, 4, 23, 11, 8, 12, 5, 1, 15, - 9, 7, 6, 14, 39, 26, 28, 22, 25, 29, 54, 18, 17, 30, 27, 16, -) - -latin5_char_to_order_map = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 -155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 -253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 - 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 -191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206, -207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222, -223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238, - 37, 44, 33, 46, 41, 48, 56, 51, 42, 60, 36, 49, 38, 31, 34, 35, - 45, 32, 40, 52, 53, 55, 58, 50, 57, 63, 70, 62, 61, 47, 59, 43, - 3, 21, 10, 19, 13, 2, 24, 20, 4, 23, 11, 8, 12, 5, 1, 15, - 9, 7, 6, 14, 39, 26, 28, 22, 25, 29, 54, 18, 17, 30, 27, 16, -239, 68,240,241,242,243,244,245,246,247,248,249,250,251,252,255, -) - -macCyrillic_char_to_order_map = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 -155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 -253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 - 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 - 37, 44, 33, 46, 41, 48, 56, 51, 42, 60, 36, 49, 38, 31, 34, 35, - 45, 32, 40, 52, 53, 55, 58, 50, 57, 63, 70, 62, 61, 47, 59, 43, -191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206, -207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222, -223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238, -239,240,241,242,243,244,245,246,247,248,249,250,251,252, 68, 16, - 3, 21, 10, 19, 13, 2, 24, 20, 4, 23, 11, 8, 12, 5, 1, 15, - 9, 7, 6, 14, 39, 26, 28, 22, 25, 29, 54, 18, 17, 30, 27,255, -) - -IBM855_char_to_order_map = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 -155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 -253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 - 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 -191,192,193,194, 68,195,196,197,198,199,200,201,202,203,204,205, -206,207,208,209,210,211,212,213,214,215,216,217, 27, 59, 54, 70, - 3, 37, 21, 44, 28, 58, 13, 41, 2, 48, 39, 53, 19, 46,218,219, -220,221,222,223,224, 26, 55, 4, 42,225,226,227,228, 23, 60,229, -230,231,232,233,234,235, 11, 36,236,237,238,239,240,241,242,243, - 8, 49, 12, 38, 5, 31, 1, 34, 15,244,245,246,247, 35, 16,248, - 43, 9, 45, 7, 32, 6, 40, 14, 52, 24, 56, 10, 33, 17, 61,249, -250, 18, 62, 20, 51, 25, 57, 30, 47, 29, 63, 22, 50,251,252,255, -) - -IBM866_char_to_order_map = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 -155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 -253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 - 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 - 37, 44, 33, 46, 41, 48, 56, 51, 42, 60, 36, 49, 38, 31, 34, 35, - 45, 32, 40, 52, 53, 55, 58, 50, 57, 63, 70, 62, 61, 47, 59, 43, - 3, 21, 10, 19, 13, 2, 24, 20, 4, 23, 11, 8, 12, 5, 1, 15, -191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206, -207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222, -223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238, - 9, 7, 6, 14, 39, 26, 28, 22, 25, 29, 54, 18, 17, 30, 27, 16, -239, 68,240,241,242,243,244,245,246,247,248,249,250,251,252,255, -) - -# Model Table: -# total sequences: 100% -# first 512 sequences: 97.6601% -# first 1024 sequences: 2.3389% -# rest sequences: 0.1237% -# negative sequences: 0.0009% -RussianLangModel = ( -0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,1,3,3,3,3,1,3,3,3,2,3,2,3,3, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,3,2,2,2,2,2,0,0,2, -3,3,3,2,3,3,3,3,3,3,3,3,3,3,2,3,3,0,0,3,3,3,3,3,3,3,3,3,2,3,2,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,2,2,3,3,3,3,3,3,3,3,3,2,3,3,0,0,3,3,3,3,3,3,3,3,2,3,3,1,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,2,3,2,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,3,3,3,3,3,3,3,3,3,3,3,2,1, -0,0,0,0,0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,0,0,3,3,3,3,3,3,3,3,3,3,3,2,1, -0,0,0,0,0,1,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,2,2,2,3,1,3,3,1,3,3,3,3,2,2,3,0,2,2,2,3,3,2,1,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,2,3,3,3,3,3,2,2,3,2,3,3,3,2,1,2,2,0,1,2,2,2,2,2,2,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,3,0,2,2,3,3,2,1,2,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,2,3,3,1,2,3,2,2,3,2,3,3,3,3,2,2,3,0,3,2,2,3,1,1,1,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,2,2,3,3,3,3,3,2,3,3,3,3,2,2,2,0,3,3,3,2,2,2,2,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,2,3,2,3,3,3,3,3,3,2,3,2,2,0,1,3,2,1,2,2,1,0, -0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,3,2,1,1,3,0,1,1,1,1,2,1,1,0,2,2,2,1,2,0,1,0, -0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,2,3,3,2,2,2,2,1,3,2,3,2,3,2,1,2,2,0,1,1,2,1,2,1,2,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,3,3,2,2,3,2,3,3,3,2,2,2,2,0,2,2,2,2,3,1,1,0, -0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, -3,2,3,2,2,3,3,3,3,3,3,3,3,3,1,3,2,0,0,3,3,3,3,2,3,3,3,3,2,3,2,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,3,3,3,3,3,2,2,3,3,0,2,1,0,3,2,3,2,3,0,0,1,2,0,0,1,0,1,2,1,1,0, -0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,0,3,0,2,3,3,3,3,2,3,3,3,3,1,2,2,0,0,2,3,2,2,2,3,2,3,2,2,3,0,0, -0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,2,3,0,2,3,2,3,0,1,2,3,3,2,0,2,3,0,0,2,3,2,2,0,1,3,1,3,2,2,1,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,1,3,0,2,3,3,3,3,3,3,3,3,2,1,3,2,0,0,2,2,3,3,3,2,3,3,0,2,2,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,2,2,3,3,2,2,2,3,3,0,0,1,1,1,1,1,2,0,0,1,1,1,1,0,1,0, -0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,2,2,3,3,3,3,3,3,3,0,3,2,3,3,2,3,2,0,2,1,0,1,1,0,1,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,2,3,3,3,2,2,2,2,3,1,3,2,3,1,1,2,1,0,2,2,2,2,1,3,1,0, -0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, -2,2,3,3,3,3,3,1,2,2,1,3,1,0,3,0,0,3,0,0,0,1,1,0,1,2,1,0,0,0,0,0, -0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,2,2,1,1,3,3,3,2,2,1,2,2,3,1,1,2,0,0,2,2,1,3,0,0,2,1,1,2,1,1,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,2,3,3,3,3,1,2,2,2,1,2,1,3,3,1,1,2,1,2,1,2,2,0,2,0,0,1,1,0,1,0, -0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,3,3,3,3,3,2,1,3,2,2,3,2,0,3,2,0,3,0,1,0,1,1,0,0,1,1,1,1,0,1,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,2,3,3,3,2,2,2,3,3,1,2,1,2,1,0,1,0,1,1,0,1,0,0,2,1,1,1,0,1,0, -0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0, -3,1,1,2,1,2,3,3,2,2,1,2,2,3,0,2,1,0,0,2,2,3,2,1,2,2,2,2,2,3,1,0, -0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,1,1,0,1,1,2,2,1,1,3,0,0,1,3,1,1,1,0,0,0,1,0,1,1,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,1,3,3,3,2,0,0,0,2,1,0,1,0,2,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,0,1,0,0,2,3,2,2,2,1,2,2,2,1,2,1,0,0,1,1,1,0,2,0,1,1,1,0,0,1,1, -1,0,0,0,0,0,1,2,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0, -2,3,3,3,3,0,0,0,0,1,0,0,0,0,3,0,1,2,1,0,0,0,0,0,0,0,1,1,0,0,1,1, -1,0,1,0,1,2,0,0,1,1,2,1,0,1,1,1,1,0,1,1,1,1,0,1,0,0,1,0,0,1,1,0, -2,2,3,2,2,2,3,1,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,0,1,0,1,1,1,0,2,1, -1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,0,1,0,1,1,0,1,1,1,0,1,1,0, -3,3,3,2,2,2,2,3,2,2,1,1,2,2,2,2,1,1,3,1,2,1,2,0,0,1,1,0,1,0,2,1, -1,1,1,1,1,2,1,0,1,1,1,1,0,1,0,0,1,1,0,0,1,0,1,0,0,1,0,0,0,1,1,0, -2,0,0,1,0,3,2,2,2,2,1,2,1,2,1,2,0,0,0,2,1,2,2,1,1,2,2,0,1,1,0,2, -1,1,1,1,1,0,1,1,1,2,1,1,1,2,1,0,1,2,1,1,1,1,0,1,1,1,0,0,1,0,0,1, -1,3,2,2,2,1,1,1,2,3,0,0,0,0,2,0,2,2,1,0,0,0,0,0,0,1,0,0,0,0,1,1, -1,0,1,1,0,1,0,1,1,0,1,1,0,2,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,1,1,0, -2,3,2,3,2,1,2,2,2,2,1,0,0,0,2,0,0,1,1,0,0,0,0,0,0,0,1,1,0,0,2,1, -1,1,2,1,0,2,0,0,1,0,1,0,0,1,0,0,1,1,0,1,1,0,0,0,0,0,1,0,0,0,0,0, -3,0,0,1,0,2,2,2,3,2,2,2,2,2,2,2,0,0,0,2,1,2,1,1,1,2,2,0,0,0,1,2, -1,1,1,1,1,0,1,2,1,1,1,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1,1,1,1,0,0,1, -2,3,2,3,3,2,0,1,1,1,0,0,1,0,2,0,1,1,3,1,0,0,0,0,0,0,0,1,0,0,2,1, -1,1,1,1,1,1,1,0,1,0,1,1,1,1,0,1,1,1,0,0,1,1,0,1,0,0,0,0,0,0,1,0, -2,3,3,3,3,1,2,2,2,2,0,1,1,0,2,1,1,1,2,1,0,1,1,0,0,1,0,1,0,0,2,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,3,3,3,2,0,0,1,1,2,2,1,0,0,2,0,1,1,3,0,0,1,0,0,0,0,0,1,0,1,2,1, -1,1,2,0,1,1,1,0,1,0,1,1,0,1,0,1,1,1,1,0,1,0,0,0,0,0,0,1,0,1,1,0, -1,3,2,3,2,1,0,0,2,2,2,0,1,0,2,0,1,1,1,0,1,0,0,0,3,0,1,1,0,0,2,1, -1,1,1,0,1,1,0,0,0,0,1,1,0,1,0,0,2,1,1,0,1,0,0,0,1,0,1,0,0,1,1,0, -3,1,2,1,1,2,2,2,2,2,2,1,2,2,1,1,0,0,0,2,2,2,0,0,0,1,2,1,0,1,0,1, -2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,2,1,1,1,0,1,0,1,1,0,1,1,1,0,0,1, -3,0,0,0,0,2,0,1,1,1,1,1,1,1,0,1,0,0,0,1,1,1,0,1,0,1,1,0,0,1,0,1, -1,1,0,0,1,0,0,0,1,0,1,1,0,0,1,0,1,0,1,0,0,0,0,1,0,0,0,1,0,0,0,1, -1,3,3,2,2,0,0,0,2,2,0,0,0,1,2,0,1,1,2,0,0,0,0,0,0,0,0,1,0,0,2,1, -0,1,1,0,0,1,1,0,0,0,1,1,0,1,1,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0, -2,3,2,3,2,0,0,0,0,1,1,0,0,0,2,0,2,0,2,0,0,0,0,0,1,0,0,1,0,0,1,1, -1,1,2,0,1,2,1,0,1,1,2,1,1,1,1,1,2,1,1,0,1,0,0,1,1,1,1,1,0,1,1,0, -1,3,2,2,2,1,0,0,2,2,1,0,1,2,2,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,1, -0,0,1,1,0,1,1,0,0,1,1,0,1,1,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0, -1,0,0,1,0,2,3,1,2,2,2,2,2,2,1,1,0,0,0,1,0,1,0,2,1,1,1,0,0,0,0,1, -1,1,0,1,1,0,1,1,1,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0, -2,0,2,0,0,1,0,3,2,1,2,1,2,2,0,1,0,0,0,2,1,0,0,2,1,1,1,1,0,2,0,2, -2,1,1,1,1,1,1,1,1,1,1,1,1,2,1,0,1,1,1,1,0,0,0,1,1,1,1,0,1,0,0,1, -1,2,2,2,2,1,0,0,1,0,0,0,0,0,2,0,1,1,1,1,0,0,0,0,1,0,1,2,0,0,2,0, -1,0,1,1,1,2,1,0,1,0,1,1,0,0,1,0,1,1,1,0,1,0,0,0,1,0,0,1,0,1,1,0, -2,1,2,2,2,0,3,0,1,1,0,0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -0,0,0,1,1,1,0,0,1,0,1,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0, -1,2,2,3,2,2,0,0,1,1,2,0,1,2,1,0,1,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1, -0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,1,1,0,0,1,0,0,0,0,0,0,0,0,1,1,0, -2,2,1,1,2,1,2,2,2,2,2,1,2,2,0,1,0,0,0,1,2,2,2,1,2,1,1,1,1,1,2,1, -1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,0,1,1,1,0,0,0,0,1,1,1,0,1,1,0,0,1, -1,2,2,2,2,0,1,0,2,2,0,0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,2,0, -0,0,1,0,0,1,0,0,0,0,1,0,1,1,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0, -0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,2,2,2,2,0,0,0,2,2,2,0,1,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1, -0,1,1,0,0,1,1,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,2,2,2,2,0,0,0,0,1,0,0,1,1,2,0,0,0,0,1,0,1,0,0,1,0,0,2,0,0,0,1, -0,0,1,0,0,1,0,0,0,1,1,0,0,0,0,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0, -1,2,2,2,1,1,2,0,2,1,1,1,1,0,2,2,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1, -0,0,1,0,1,1,0,0,0,0,1,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0, -1,0,2,1,2,0,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0, -0,0,1,0,1,1,0,0,0,0,1,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0, -1,0,0,0,0,2,0,1,2,1,0,1,1,1,0,1,0,0,0,1,0,1,0,0,1,0,1,0,0,0,0,1, -0,0,0,0,0,1,0,0,1,1,0,0,1,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1, -2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -1,0,0,0,1,0,0,0,1,1,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,1,0,0,0,0,0, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -1,1,1,0,1,0,1,0,0,1,1,1,1,0,0,0,1,0,0,0,0,1,0,0,0,1,0,1,0,0,0,0, -1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -1,1,0,1,1,0,1,0,1,0,0,0,0,1,1,0,1,1,0,0,0,0,0,1,0,1,1,0,1,0,0,0, -0,1,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0, -) - -Koi8rModel = { - 'char_to_order_map': KOI8R_char_to_order_map, - 'precedence_matrix': RussianLangModel, - 'typical_positive_ratio': 0.976601, - 'keep_english_letter': False, - 'charset_name': "KOI8-R", - 'language': 'Russian', -} - -Win1251CyrillicModel = { - 'char_to_order_map': win1251_char_to_order_map, - 'precedence_matrix': RussianLangModel, - 'typical_positive_ratio': 0.976601, - 'keep_english_letter': False, - 'charset_name': "windows-1251", - 'language': 'Russian', -} - -Latin5CyrillicModel = { - 'char_to_order_map': latin5_char_to_order_map, - 'precedence_matrix': RussianLangModel, - 'typical_positive_ratio': 0.976601, - 'keep_english_letter': False, - 'charset_name': "ISO-8859-5", - 'language': 'Russian', -} - -MacCyrillicModel = { - 'char_to_order_map': macCyrillic_char_to_order_map, - 'precedence_matrix': RussianLangModel, - 'typical_positive_ratio': 0.976601, - 'keep_english_letter': False, - 'charset_name': "MacCyrillic", - 'language': 'Russian', -} - -Ibm866Model = { - 'char_to_order_map': IBM866_char_to_order_map, - 'precedence_matrix': RussianLangModel, - 'typical_positive_ratio': 0.976601, - 'keep_english_letter': False, - 'charset_name': "IBM866", - 'language': 'Russian', -} - -Ibm855Model = { - 'char_to_order_map': IBM855_char_to_order_map, - 'precedence_matrix': RussianLangModel, - 'typical_positive_ratio': 0.976601, - 'keep_english_letter': False, - 'charset_name': "IBM855", - 'language': 'Russian', -} diff --git a/pipenv/patched/notpip/_vendor/chardet/langgreekmodel.py b/pipenv/patched/notpip/_vendor/chardet/langgreekmodel.py index 53322216..42fd8621 100644 --- a/pipenv/patched/notpip/_vendor/chardet/langgreekmodel.py +++ b/pipenv/patched/notpip/_vendor/chardet/langgreekmodel.py @@ -1,225 +1,4398 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### +#!/usr/bin/env python +# -*- coding: utf-8 -*- -# 255: Control characters that usually does not exist in any text +from pipenv.patched.notpip._vendor.chardet.sbcharsetprober import SingleByteCharSetModel + + +# 3: Positive +# 2: Likely +# 1: Unlikely +# 0: Negative + +GREEK_LANG_MODEL = { + 60: { # 'e' + 60: 2, # 'e' + 55: 1, # 'o' + 58: 2, # 't' + 36: 1, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 1, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 55: { # 'o' + 60: 0, # 'e' + 55: 2, # 'o' + 58: 2, # 't' + 36: 1, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 1, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 1, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 58: { # 't' + 60: 2, # 'e' + 55: 1, # 'o' + 58: 1, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 2, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 1, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 36: { # '·' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 61: { # 'Ά' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 1, # 'γ' + 21: 2, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 1, # 'π' + 8: 2, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 46: { # 'Έ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 2, # 'β' + 20: 2, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 2, # 'κ' + 16: 2, # 'λ' + 10: 0, # 'μ' + 6: 3, # 'ν' + 30: 2, # 'ξ' + 4: 0, # 'ο' + 9: 2, # 'π' + 8: 2, # 'ρ' + 14: 0, # 'ς' + 7: 1, # 'σ' + 2: 2, # 'τ' + 12: 0, # 'υ' + 28: 2, # 'φ' + 23: 3, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 54: { # 'Ό' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 2, # 'μ' + 6: 2, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 2, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 2, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 2, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 31: { # 'Α' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 2, # 'Β' + 43: 2, # 'Γ' + 41: 1, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 2, # 'Θ' + 47: 2, # 'Ι' + 44: 2, # 'Κ' + 53: 2, # 'Λ' + 38: 2, # 'Μ' + 49: 2, # 'Ν' + 59: 1, # 'Ξ' + 39: 0, # 'Ο' + 35: 2, # 'Π' + 48: 2, # 'Ρ' + 37: 2, # 'Σ' + 33: 2, # 'Τ' + 45: 2, # 'Υ' + 56: 2, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 2, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 1, # 'θ' + 5: 0, # 'ι' + 11: 2, # 'κ' + 16: 3, # 'λ' + 10: 2, # 'μ' + 6: 3, # 'ν' + 30: 2, # 'ξ' + 4: 0, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 2, # 'ς' + 7: 2, # 'σ' + 2: 0, # 'τ' + 12: 3, # 'υ' + 28: 2, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 2, # 'ύ' + 27: 0, # 'ώ' + }, + 51: { # 'Β' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 1, # 'Ε' + 40: 1, # 'Η' + 52: 0, # 'Θ' + 47: 1, # 'Ι' + 44: 0, # 'Κ' + 53: 1, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 2, # 'ά' + 18: 2, # 'έ' + 22: 2, # 'ή' + 15: 0, # 'ί' + 1: 2, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 2, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 2, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 2, # 'ο' + 9: 0, # 'π' + 8: 2, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 43: { # 'Γ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 1, # 'Α' + 51: 0, # 'Β' + 43: 2, # 'Γ' + 41: 0, # 'Δ' + 34: 2, # 'Ε' + 40: 1, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 1, # 'Κ' + 53: 1, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 1, # 'Ο' + 35: 0, # 'Π' + 48: 2, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 2, # 'Υ' + 56: 0, # 'Φ' + 50: 1, # 'Χ' + 57: 2, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 2, # 'ί' + 1: 2, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 2, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 3, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 0, # 'μ' + 6: 2, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 0, # 'π' + 8: 2, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 41: { # 'Δ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 2, # 'Ε' + 40: 2, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 2, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 2, # 'ή' + 15: 2, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 2, # 'η' + 25: 0, # 'θ' + 5: 3, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 2, # 'ο' + 9: 0, # 'π' + 8: 2, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 2, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 2, # 'ω' + 19: 1, # 'ό' + 26: 2, # 'ύ' + 27: 2, # 'ώ' + }, + 34: { # 'Ε' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 0, # 'Β' + 43: 2, # 'Γ' + 41: 2, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 2, # 'Κ' + 53: 2, # 'Λ' + 38: 2, # 'Μ' + 49: 2, # 'Ν' + 59: 1, # 'Ξ' + 39: 0, # 'Ο' + 35: 2, # 'Π' + 48: 2, # 'Ρ' + 37: 2, # 'Σ' + 33: 2, # 'Τ' + 45: 2, # 'Υ' + 56: 0, # 'Φ' + 50: 2, # 'Χ' + 57: 2, # 'Ω' + 17: 3, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 3, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 3, # 'γ' + 21: 2, # 'δ' + 3: 1, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 1, # 'θ' + 5: 2, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 2, # 'μ' + 6: 3, # 'ν' + 30: 2, # 'ξ' + 4: 0, # 'ο' + 9: 3, # 'π' + 8: 2, # 'ρ' + 14: 0, # 'ς' + 7: 2, # 'σ' + 2: 2, # 'τ' + 12: 2, # 'υ' + 28: 2, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 1, # 'ύ' + 27: 0, # 'ώ' + }, + 40: { # 'Η' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 1, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 2, # 'Θ' + 47: 0, # 'Ι' + 44: 2, # 'Κ' + 53: 0, # 'Λ' + 38: 2, # 'Μ' + 49: 2, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 2, # 'Π' + 48: 2, # 'Ρ' + 37: 2, # 'Σ' + 33: 2, # 'Τ' + 45: 1, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 0, # 'μ' + 6: 1, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 1, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 52: { # 'Θ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 2, # 'Ε' + 40: 2, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 1, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 1, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 2, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 2, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 2, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 2, # 'ύ' + 27: 0, # 'ώ' + }, + 47: { # 'Ι' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 1, # 'Β' + 43: 1, # 'Γ' + 41: 2, # 'Δ' + 34: 2, # 'Ε' + 40: 2, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 2, # 'Κ' + 53: 2, # 'Λ' + 38: 2, # 'Μ' + 49: 2, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 2, # 'Ρ' + 37: 2, # 'Σ' + 33: 2, # 'Τ' + 45: 0, # 'Υ' + 56: 2, # 'Φ' + 50: 0, # 'Χ' + 57: 2, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 2, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 2, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 1, # 'ν' + 30: 0, # 'ξ' + 4: 2, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 2, # 'σ' + 2: 1, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 1, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 44: { # 'Κ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 1, # 'Δ' + 34: 2, # 'Ε' + 40: 2, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 1, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 2, # 'Ρ' + 37: 0, # 'Σ' + 33: 1, # 'Τ' + 45: 2, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 1, # 'Ω' + 17: 3, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 2, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 2, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 2, # 'ο' + 9: 0, # 'π' + 8: 2, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 2, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 2, # 'ό' + 26: 2, # 'ύ' + 27: 2, # 'ώ' + }, + 53: { # 'Λ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 2, # 'Ε' + 40: 2, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 0, # 'Κ' + 53: 2, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 2, # 'Σ' + 33: 0, # 'Τ' + 45: 2, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 2, # 'Ω' + 17: 2, # 'ά' + 18: 2, # 'έ' + 22: 0, # 'ή' + 15: 2, # 'ί' + 1: 2, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 2, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 1, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 2, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 2, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 2, # 'ό' + 26: 2, # 'ύ' + 27: 0, # 'ώ' + }, + 38: { # 'Μ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 2, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 2, # 'Ε' + 40: 2, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 2, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 2, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 2, # 'ά' + 18: 2, # 'έ' + 22: 2, # 'ή' + 15: 2, # 'ί' + 1: 2, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 2, # 'η' + 25: 0, # 'θ' + 5: 3, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 2, # 'ο' + 9: 3, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 2, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 2, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 49: { # 'Ν' + 60: 2, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 2, # 'Ε' + 40: 2, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 2, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 2, # 'Ω' + 17: 0, # 'ά' + 18: 2, # 'έ' + 22: 0, # 'ή' + 15: 2, # 'ί' + 1: 2, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 1, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 2, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 1, # 'ω' + 19: 2, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 59: { # 'Ξ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 1, # 'Ε' + 40: 1, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 1, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 2, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 2, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 2, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 39: { # 'Ο' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 1, # 'Β' + 43: 2, # 'Γ' + 41: 2, # 'Δ' + 34: 2, # 'Ε' + 40: 1, # 'Η' + 52: 2, # 'Θ' + 47: 2, # 'Ι' + 44: 2, # 'Κ' + 53: 2, # 'Λ' + 38: 2, # 'Μ' + 49: 2, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 2, # 'Π' + 48: 2, # 'Ρ' + 37: 2, # 'Σ' + 33: 2, # 'Τ' + 45: 2, # 'Υ' + 56: 2, # 'Φ' + 50: 2, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 2, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 3, # 'ι' + 11: 2, # 'κ' + 16: 2, # 'λ' + 10: 2, # 'μ' + 6: 2, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 2, # 'π' + 8: 2, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 2, # 'τ' + 12: 2, # 'υ' + 28: 1, # 'φ' + 23: 1, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 2, # 'ύ' + 27: 0, # 'ώ' + }, + 35: { # 'Π' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 2, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 0, # 'Κ' + 53: 2, # 'Λ' + 38: 1, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 2, # 'Ρ' + 37: 0, # 'Σ' + 33: 1, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 1, # 'Χ' + 57: 2, # 'Ω' + 17: 2, # 'ά' + 18: 1, # 'έ' + 22: 1, # 'ή' + 15: 2, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 2, # 'η' + 25: 0, # 'θ' + 5: 2, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 0, # 'μ' + 6: 2, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 3, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 2, # 'υ' + 28: 0, # 'φ' + 23: 2, # 'χ' + 42: 0, # 'ψ' + 24: 2, # 'ω' + 19: 2, # 'ό' + 26: 0, # 'ύ' + 27: 3, # 'ώ' + }, + 48: { # 'Ρ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 0, # 'Β' + 43: 1, # 'Γ' + 41: 1, # 'Δ' + 34: 2, # 'Ε' + 40: 2, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 2, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 2, # 'Ρ' + 37: 0, # 'Σ' + 33: 1, # 'Τ' + 45: 1, # 'Υ' + 56: 0, # 'Φ' + 50: 1, # 'Χ' + 57: 1, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 2, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 1, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 3, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 2, # 'ω' + 19: 0, # 'ό' + 26: 2, # 'ύ' + 27: 0, # 'ώ' + }, + 37: { # 'Σ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 1, # 'Δ' + 34: 2, # 'Ε' + 40: 2, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 2, # 'Κ' + 53: 0, # 'Λ' + 38: 2, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 2, # 'Σ' + 33: 2, # 'Τ' + 45: 2, # 'Υ' + 56: 0, # 'Φ' + 50: 2, # 'Χ' + 57: 2, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 2, # 'ή' + 15: 2, # 'ί' + 1: 2, # 'α' + 29: 2, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 0, # 'θ' + 5: 2, # 'ι' + 11: 2, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 2, # 'ο' + 9: 2, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 0, # 'φ' + 23: 2, # 'χ' + 42: 0, # 'ψ' + 24: 2, # 'ω' + 19: 0, # 'ό' + 26: 2, # 'ύ' + 27: 2, # 'ώ' + }, + 33: { # 'Τ' + 60: 0, # 'e' + 55: 1, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 2, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 2, # 'Ε' + 40: 2, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 2, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 2, # 'Ρ' + 37: 0, # 'Σ' + 33: 1, # 'Τ' + 45: 1, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 2, # 'Ω' + 17: 2, # 'ά' + 18: 2, # 'έ' + 22: 0, # 'ή' + 15: 2, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 2, # 'ε' + 32: 0, # 'ζ' + 13: 2, # 'η' + 25: 0, # 'θ' + 5: 2, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 2, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 2, # 'ρ' + 14: 0, # 'ς' + 7: 2, # 'σ' + 2: 0, # 'τ' + 12: 2, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 2, # 'ό' + 26: 2, # 'ύ' + 27: 3, # 'ώ' + }, + 45: { # 'Υ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 2, # 'Γ' + 41: 0, # 'Δ' + 34: 1, # 'Ε' + 40: 2, # 'Η' + 52: 2, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 1, # 'Λ' + 38: 2, # 'Μ' + 49: 2, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 2, # 'Π' + 48: 1, # 'Ρ' + 37: 2, # 'Σ' + 33: 2, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 1, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 3, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 56: { # 'Φ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 1, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 1, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 2, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 2, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 2, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 2, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 2, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 2, # 'τ' + 12: 2, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 1, # 'ύ' + 27: 1, # 'ώ' + }, + 50: { # 'Χ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 1, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 2, # 'Ε' + 40: 2, # 'Η' + 52: 0, # 'Θ' + 47: 2, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 1, # 'Ν' + 59: 0, # 'Ξ' + 39: 1, # 'Ο' + 35: 0, # 'Π' + 48: 2, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 1, # 'Χ' + 57: 1, # 'Ω' + 17: 2, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 2, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 2, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 2, # 'ο' + 9: 0, # 'π' + 8: 3, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 2, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 2, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 57: { # 'Ω' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 1, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 1, # 'Λ' + 38: 0, # 'Μ' + 49: 2, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 2, # 'Ρ' + 37: 2, # 'Σ' + 33: 2, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 0, # 'π' + 8: 2, # 'ρ' + 14: 2, # 'ς' + 7: 2, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 1, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 17: { # 'ά' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 2, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 3, # 'β' + 20: 3, # 'γ' + 21: 3, # 'δ' + 3: 3, # 'ε' + 32: 3, # 'ζ' + 13: 0, # 'η' + 25: 3, # 'θ' + 5: 2, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 3, # 'ξ' + 4: 0, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 3, # 'φ' + 23: 3, # 'χ' + 42: 3, # 'ψ' + 24: 2, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 18: { # 'έ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 3, # 'α' + 29: 2, # 'β' + 20: 3, # 'γ' + 21: 2, # 'δ' + 3: 3, # 'ε' + 32: 2, # 'ζ' + 13: 0, # 'η' + 25: 3, # 'θ' + 5: 0, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 3, # 'ξ' + 4: 3, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 3, # 'φ' + 23: 3, # 'χ' + 42: 3, # 'ψ' + 24: 2, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 22: { # 'ή' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 1, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 3, # 'γ' + 21: 3, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 3, # 'θ' + 5: 0, # 'ι' + 11: 3, # 'κ' + 16: 2, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 2, # 'ξ' + 4: 0, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 2, # 'φ' + 23: 3, # 'χ' + 42: 2, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 15: { # 'ί' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 3, # 'α' + 29: 2, # 'β' + 20: 3, # 'γ' + 21: 3, # 'δ' + 3: 3, # 'ε' + 32: 3, # 'ζ' + 13: 3, # 'η' + 25: 3, # 'θ' + 5: 0, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 3, # 'ξ' + 4: 3, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 1, # 'φ' + 23: 3, # 'χ' + 42: 2, # 'ψ' + 24: 3, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 1: { # 'α' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 2, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 2, # 'έ' + 22: 0, # 'ή' + 15: 3, # 'ί' + 1: 0, # 'α' + 29: 3, # 'β' + 20: 3, # 'γ' + 21: 3, # 'δ' + 3: 2, # 'ε' + 32: 3, # 'ζ' + 13: 1, # 'η' + 25: 3, # 'θ' + 5: 3, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 3, # 'ξ' + 4: 2, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 3, # 'φ' + 23: 3, # 'χ' + 42: 2, # 'ψ' + 24: 0, # 'ω' + 19: 2, # 'ό' + 26: 2, # 'ύ' + 27: 0, # 'ώ' + }, + 29: { # 'β' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 2, # 'έ' + 22: 3, # 'ή' + 15: 2, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 2, # 'γ' + 21: 2, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 2, # 'η' + 25: 0, # 'θ' + 5: 3, # 'ι' + 11: 0, # 'κ' + 16: 3, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 3, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 2, # 'ω' + 19: 2, # 'ό' + 26: 2, # 'ύ' + 27: 2, # 'ώ' + }, + 20: { # 'γ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 3, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 0, # 'θ' + 5: 3, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 3, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 3, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 2, # 'υ' + 28: 0, # 'φ' + 23: 3, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 2, # 'ύ' + 27: 3, # 'ώ' + }, + 21: { # 'δ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 2, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 0, # 'θ' + 5: 3, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 3, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 3, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 3, # 'ύ' + 27: 3, # 'ώ' + }, + 3: { # 'ε' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 2, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 3, # 'ί' + 1: 2, # 'α' + 29: 3, # 'β' + 20: 3, # 'γ' + 21: 3, # 'δ' + 3: 2, # 'ε' + 32: 2, # 'ζ' + 13: 0, # 'η' + 25: 3, # 'θ' + 5: 3, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 3, # 'ξ' + 4: 2, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 3, # 'φ' + 23: 3, # 'χ' + 42: 2, # 'ψ' + 24: 3, # 'ω' + 19: 2, # 'ό' + 26: 3, # 'ύ' + 27: 2, # 'ώ' + }, + 32: { # 'ζ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 2, # 'ά' + 18: 2, # 'έ' + 22: 2, # 'ή' + 15: 2, # 'ί' + 1: 2, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 0, # 'θ' + 5: 2, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 1, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 2, # 'ό' + 26: 0, # 'ύ' + 27: 2, # 'ώ' + }, + 13: { # 'η' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 2, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 3, # 'γ' + 21: 2, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 3, # 'θ' + 5: 0, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 2, # 'ξ' + 4: 0, # 'ο' + 9: 2, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 2, # 'φ' + 23: 3, # 'χ' + 42: 2, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 25: { # 'θ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 2, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 2, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 0, # 'θ' + 5: 3, # 'ι' + 11: 0, # 'κ' + 16: 1, # 'λ' + 10: 3, # 'μ' + 6: 2, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 3, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 3, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 3, # 'ύ' + 27: 3, # 'ώ' + }, + 5: { # 'ι' + 60: 0, # 'e' + 55: 1, # 'o' + 58: 0, # 't' + 36: 2, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 1, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 0, # 'ί' + 1: 3, # 'α' + 29: 3, # 'β' + 20: 3, # 'γ' + 21: 3, # 'δ' + 3: 3, # 'ε' + 32: 2, # 'ζ' + 13: 3, # 'η' + 25: 3, # 'θ' + 5: 0, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 3, # 'ξ' + 4: 3, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 2, # 'φ' + 23: 3, # 'χ' + 42: 2, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 0, # 'ύ' + 27: 3, # 'ώ' + }, + 11: { # 'κ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 3, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 2, # 'θ' + 5: 3, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 2, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 2, # 'π' + 8: 3, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 2, # 'φ' + 23: 2, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 3, # 'ύ' + 27: 3, # 'ώ' + }, + 16: { # 'λ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 1, # 'β' + 20: 2, # 'γ' + 21: 1, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 2, # 'θ' + 5: 3, # 'ι' + 11: 2, # 'κ' + 16: 3, # 'λ' + 10: 2, # 'μ' + 6: 2, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 3, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 2, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 3, # 'ύ' + 27: 3, # 'ώ' + }, + 10: { # 'μ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 1, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 3, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 0, # 'θ' + 5: 3, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 3, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 2, # 'υ' + 28: 3, # 'φ' + 23: 0, # 'χ' + 42: 2, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 2, # 'ύ' + 27: 2, # 'ώ' + }, + 6: { # 'ν' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 2, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 3, # 'δ' + 3: 3, # 'ε' + 32: 2, # 'ζ' + 13: 3, # 'η' + 25: 3, # 'θ' + 5: 3, # 'ι' + 11: 0, # 'κ' + 16: 1, # 'λ' + 10: 0, # 'μ' + 6: 2, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 3, # 'ύ' + 27: 3, # 'ώ' + }, + 30: { # 'ξ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 2, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 2, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 0, # 'θ' + 5: 2, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 3, # 'τ' + 12: 2, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 2, # 'ό' + 26: 3, # 'ύ' + 27: 1, # 'ώ' + }, + 4: { # 'ο' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 2, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 2, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 2, # 'α' + 29: 3, # 'β' + 20: 3, # 'γ' + 21: 3, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 3, # 'θ' + 5: 3, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 2, # 'ξ' + 4: 2, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 3, # 'φ' + 23: 3, # 'χ' + 42: 2, # 'ψ' + 24: 2, # 'ω' + 19: 1, # 'ό' + 26: 3, # 'ύ' + 27: 2, # 'ώ' + }, + 9: { # 'π' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 0, # 'θ' + 5: 3, # 'ι' + 11: 0, # 'κ' + 16: 3, # 'λ' + 10: 0, # 'μ' + 6: 2, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 3, # 'ρ' + 14: 2, # 'ς' + 7: 0, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 0, # 'φ' + 23: 2, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 2, # 'ύ' + 27: 3, # 'ώ' + }, + 8: { # 'ρ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 2, # 'β' + 20: 3, # 'γ' + 21: 2, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 3, # 'θ' + 5: 3, # 'ι' + 11: 3, # 'κ' + 16: 1, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 2, # 'ξ' + 4: 3, # 'ο' + 9: 2, # 'π' + 8: 2, # 'ρ' + 14: 0, # 'ς' + 7: 2, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 3, # 'φ' + 23: 3, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 3, # 'ύ' + 27: 3, # 'ώ' + }, + 14: { # 'ς' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 2, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 0, # 'θ' + 5: 0, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 0, # 'τ' + 12: 0, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 7: { # 'σ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 2, # 'ά' + 18: 2, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 3, # 'β' + 20: 0, # 'γ' + 21: 2, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 3, # 'θ' + 5: 3, # 'ι' + 11: 3, # 'κ' + 16: 2, # 'λ' + 10: 3, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 3, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 3, # 'φ' + 23: 3, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 3, # 'ύ' + 27: 2, # 'ώ' + }, + 2: { # 'τ' + 60: 0, # 'e' + 55: 2, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 2, # 'ζ' + 13: 3, # 'η' + 25: 0, # 'θ' + 5: 3, # 'ι' + 11: 2, # 'κ' + 16: 2, # 'λ' + 10: 3, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 3, # 'ρ' + 14: 0, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 2, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 3, # 'ύ' + 27: 3, # 'ώ' + }, + 12: { # 'υ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 2, # 'ά' + 18: 2, # 'έ' + 22: 3, # 'ή' + 15: 2, # 'ί' + 1: 3, # 'α' + 29: 2, # 'β' + 20: 3, # 'γ' + 21: 2, # 'δ' + 3: 2, # 'ε' + 32: 2, # 'ζ' + 13: 2, # 'η' + 25: 3, # 'θ' + 5: 2, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 3, # 'ξ' + 4: 3, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 2, # 'φ' + 23: 3, # 'χ' + 42: 2, # 'ψ' + 24: 2, # 'ω' + 19: 2, # 'ό' + 26: 0, # 'ύ' + 27: 2, # 'ώ' + }, + 28: { # 'φ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 3, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 2, # 'η' + 25: 2, # 'θ' + 5: 3, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 0, # 'μ' + 6: 1, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 3, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 1, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 2, # 'ύ' + 27: 2, # 'ώ' + }, + 23: { # 'χ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 3, # 'ά' + 18: 2, # 'έ' + 22: 3, # 'ή' + 15: 3, # 'ί' + 1: 3, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 2, # 'η' + 25: 2, # 'θ' + 5: 3, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 2, # 'μ' + 6: 3, # 'ν' + 30: 0, # 'ξ' + 4: 3, # 'ο' + 9: 0, # 'π' + 8: 3, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 3, # 'τ' + 12: 3, # 'υ' + 28: 0, # 'φ' + 23: 2, # 'χ' + 42: 0, # 'ψ' + 24: 3, # 'ω' + 19: 3, # 'ό' + 26: 3, # 'ύ' + 27: 3, # 'ώ' + }, + 42: { # 'ψ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 2, # 'ά' + 18: 2, # 'έ' + 22: 1, # 'ή' + 15: 2, # 'ί' + 1: 2, # 'α' + 29: 0, # 'β' + 20: 0, # 'γ' + 21: 0, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 3, # 'η' + 25: 0, # 'θ' + 5: 2, # 'ι' + 11: 0, # 'κ' + 16: 0, # 'λ' + 10: 0, # 'μ' + 6: 0, # 'ν' + 30: 0, # 'ξ' + 4: 2, # 'ο' + 9: 0, # 'π' + 8: 0, # 'ρ' + 14: 0, # 'ς' + 7: 0, # 'σ' + 2: 2, # 'τ' + 12: 1, # 'υ' + 28: 0, # 'φ' + 23: 0, # 'χ' + 42: 0, # 'ψ' + 24: 2, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 24: { # 'ω' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 1, # 'ά' + 18: 0, # 'έ' + 22: 2, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 2, # 'β' + 20: 3, # 'γ' + 21: 2, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 0, # 'η' + 25: 3, # 'θ' + 5: 2, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 0, # 'ξ' + 4: 0, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 2, # 'φ' + 23: 2, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 19: { # 'ό' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 3, # 'β' + 20: 3, # 'γ' + 21: 3, # 'δ' + 3: 1, # 'ε' + 32: 2, # 'ζ' + 13: 2, # 'η' + 25: 2, # 'θ' + 5: 2, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 1, # 'ξ' + 4: 2, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 2, # 'φ' + 23: 3, # 'χ' + 42: 2, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 26: { # 'ύ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 2, # 'α' + 29: 2, # 'β' + 20: 2, # 'γ' + 21: 1, # 'δ' + 3: 3, # 'ε' + 32: 0, # 'ζ' + 13: 2, # 'η' + 25: 3, # 'θ' + 5: 0, # 'ι' + 11: 3, # 'κ' + 16: 3, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 2, # 'ξ' + 4: 3, # 'ο' + 9: 3, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 2, # 'φ' + 23: 2, # 'χ' + 42: 2, # 'ψ' + 24: 2, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, + 27: { # 'ώ' + 60: 0, # 'e' + 55: 0, # 'o' + 58: 0, # 't' + 36: 0, # '·' + 61: 0, # 'Ά' + 46: 0, # 'Έ' + 54: 0, # 'Ό' + 31: 0, # 'Α' + 51: 0, # 'Β' + 43: 0, # 'Γ' + 41: 0, # 'Δ' + 34: 0, # 'Ε' + 40: 0, # 'Η' + 52: 0, # 'Θ' + 47: 0, # 'Ι' + 44: 0, # 'Κ' + 53: 0, # 'Λ' + 38: 0, # 'Μ' + 49: 0, # 'Ν' + 59: 0, # 'Ξ' + 39: 0, # 'Ο' + 35: 0, # 'Π' + 48: 0, # 'Ρ' + 37: 0, # 'Σ' + 33: 0, # 'Τ' + 45: 0, # 'Υ' + 56: 0, # 'Φ' + 50: 0, # 'Χ' + 57: 0, # 'Ω' + 17: 0, # 'ά' + 18: 0, # 'έ' + 22: 0, # 'ή' + 15: 0, # 'ί' + 1: 0, # 'α' + 29: 1, # 'β' + 20: 0, # 'γ' + 21: 3, # 'δ' + 3: 0, # 'ε' + 32: 0, # 'ζ' + 13: 1, # 'η' + 25: 2, # 'θ' + 5: 2, # 'ι' + 11: 0, # 'κ' + 16: 2, # 'λ' + 10: 3, # 'μ' + 6: 3, # 'ν' + 30: 1, # 'ξ' + 4: 0, # 'ο' + 9: 2, # 'π' + 8: 3, # 'ρ' + 14: 3, # 'ς' + 7: 3, # 'σ' + 2: 3, # 'τ' + 12: 0, # 'υ' + 28: 1, # 'φ' + 23: 1, # 'χ' + 42: 0, # 'ψ' + 24: 0, # 'ω' + 19: 0, # 'ό' + 26: 0, # 'ύ' + 27: 0, # 'ώ' + }, +} + +# 255: Undefined characters that did not exist in training text # 254: Carriage/Return # 253: symbol (punctuation) that does not belong to word # 252: 0 - 9 +# 251: Control characters -# Character Mapping Table: -Latin7_char_to_order_map = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253, 82,100,104, 94, 98,101,116,102,111,187,117, 92, 88,113, 85, # 40 - 79,118,105, 83, 67,114,119, 95, 99,109,188,253,253,253,253,253, # 50 -253, 72, 70, 80, 81, 60, 96, 93, 89, 68,120, 97, 77, 86, 69, 55, # 60 - 78,115, 65, 66, 58, 76,106,103, 87,107,112,253,253,253,253,253, # 70 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 80 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 90 -253,233, 90,253,253,253,253,253,253,253,253,253,253, 74,253,253, # a0 -253,253,253,253,247,248, 61, 36, 46, 71, 73,253, 54,253,108,123, # b0 -110, 31, 51, 43, 41, 34, 91, 40, 52, 47, 44, 53, 38, 49, 59, 39, # c0 - 35, 48,250, 37, 33, 45, 56, 50, 84, 57,120,121, 17, 18, 22, 15, # d0 -124, 1, 29, 20, 21, 3, 32, 13, 25, 5, 11, 16, 10, 6, 30, 4, # e0 - 9, 8, 14, 7, 2, 12, 28, 23, 42, 24, 64, 75, 19, 26, 27,253, # f0 -) - -win1253_char_to_order_map = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253, 82,100,104, 94, 98,101,116,102,111,187,117, 92, 88,113, 85, # 40 - 79,118,105, 83, 67,114,119, 95, 99,109,188,253,253,253,253,253, # 50 -253, 72, 70, 80, 81, 60, 96, 93, 89, 68,120, 97, 77, 86, 69, 55, # 60 - 78,115, 65, 66, 58, 76,106,103, 87,107,112,253,253,253,253,253, # 70 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 80 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 90 -253,233, 61,253,253,253,253,253,253,253,253,253,253, 74,253,253, # a0 -253,253,253,253,247,253,253, 36, 46, 71, 73,253, 54,253,108,123, # b0 -110, 31, 51, 43, 41, 34, 91, 40, 52, 47, 44, 53, 38, 49, 59, 39, # c0 - 35, 48,250, 37, 33, 45, 56, 50, 84, 57,120,121, 17, 18, 22, 15, # d0 -124, 1, 29, 20, 21, 3, 32, 13, 25, 5, 11, 16, 10, 6, 30, 4, # e0 - 9, 8, 14, 7, 2, 12, 28, 23, 42, 24, 64, 75, 19, 26, 27,253, # f0 -) - -# Model Table: -# total sequences: 100% -# first 512 sequences: 98.2851% -# first 1024 sequences:1.7001% -# rest sequences: 0.0359% -# negative sequences: 0.0148% -GreekLangModel = ( -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,3,2,2,3,3,3,3,3,3,3,3,1,3,3,3,0,2,2,3,3,0,3,0,3,2,0,3,3,3,0, -3,0,0,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,3,0,3,3,0,3,2,3,3,0,3,2,3,3,3,0,0,3,0,3,0,3,3,2,0,0,0, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0, -0,2,3,2,2,3,3,3,3,3,3,3,3,0,3,3,3,3,0,2,3,3,0,3,3,3,3,2,3,3,3,0, -2,0,0,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,2,3,3,2,3,3,3,3,3,3,3,3,3,3,3,3,0,2,1,3,3,3,3,2,3,3,2,3,3,2,0, -0,0,0,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,0,3,3,3,3,3,3,0,3,3,0,3,3,3,3,3,3,3,3,3,3,0,3,2,3,3,0, -2,0,1,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, -0,3,3,3,3,3,2,3,0,0,0,0,3,3,0,3,1,3,3,3,0,3,3,0,3,3,3,3,0,0,0,0, -2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,3,0,3,0,3,3,3,3,3,0,3,2,2,2,3,0,2,3,3,3,3,3,2,3,3,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,3,3,2,2,2,3,3,3,3,0,3,1,3,3,3,3,2,3,3,3,3,3,3,3,2,2,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,3,2,0,3,0,0,0,3,3,2,3,3,3,3,3,0,0,3,2,3,0,2,3,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,0,3,3,3,3,0,0,3,3,0,2,3,0,3,0,3,3,3,0,0,3,0,3,0,2,2,3,3,0,0, -0,0,1,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,3,2,0,3,2,3,3,3,3,0,3,3,3,3,3,0,3,3,2,3,2,3,3,2,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,2,3,2,3,3,3,3,3,3,0,2,3,2,3,2,2,2,3,2,3,3,2,3,0,2,2,2,3,0, -2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,3,0,0,0,3,3,3,2,3,3,0,0,3,0,3,0,0,0,3,2,0,3,0,3,0,0,2,0,2,0, -0,0,0,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,0,3,3,3,3,3,3,0,3,3,0,3,0,0,0,3,3,0,3,3,3,0,0,1,2,3,0, -3,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,3,2,0,0,3,2,2,3,3,0,3,3,3,3,3,2,1,3,0,3,2,3,3,2,1,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,3,3,0,2,3,3,3,3,3,3,0,0,3,0,3,0,0,0,3,3,0,3,2,3,0,0,3,3,3,0, -3,0,0,0,2,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,0,3,3,3,3,3,3,0,0,3,0,3,0,0,0,3,2,0,3,2,3,0,0,3,2,3,0, -2,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,3,1,2,2,3,3,3,3,3,3,0,2,3,0,3,0,0,0,3,3,0,3,0,2,0,0,2,3,1,0, -2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,0,3,3,3,3,0,3,0,3,3,2,3,0,3,3,3,3,3,3,0,3,3,3,0,2,3,0,0,3,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,0,3,3,3,0,0,3,0,0,0,3,3,0,3,0,2,3,3,0,0,3,0,3,0,3,3,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,3,0,0,0,3,3,3,3,3,3,0,0,3,0,2,0,0,0,3,3,0,3,0,3,0,0,2,0,2,0, -0,0,0,0,1,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,3,3,0,3,0,2,0,3,2,0,3,2,3,2,3,0,0,3,2,3,2,3,3,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,3,0,0,2,3,3,3,3,3,0,0,0,3,0,2,1,0,0,3,2,2,2,0,3,0,0,2,2,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,0,3,3,3,2,0,3,0,3,0,3,3,0,2,1,2,3,3,0,0,3,0,3,0,3,3,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,2,3,3,3,0,3,3,3,3,3,3,0,2,3,0,3,0,0,0,2,1,0,2,2,3,0,0,2,2,2,0, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,3,0,0,2,3,3,3,2,3,0,0,1,3,0,2,0,0,0,0,3,0,1,0,2,0,0,1,1,1,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,3,1,0,3,0,0,0,3,2,0,3,2,3,3,3,0,0,3,0,3,2,2,2,1,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,0,3,3,3,0,0,3,0,0,0,0,2,0,2,3,3,2,2,2,2,3,0,2,0,2,2,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,2,0,0,0,0,0,0,2,3,0,2,0,2,3,2,0,0,3,0,3,0,3,1,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,3,2,3,3,2,2,3,0,2,0,3,0,0,0,2,0,0,0,0,1,2,0,2,0,2,0, -0,2,0,2,0,2,2,0,0,1,0,2,2,2,0,2,2,2,0,2,2,2,0,0,2,0,0,1,0,0,0,0, -0,2,0,3,3,2,0,0,0,0,0,0,1,3,0,2,0,2,2,2,0,0,2,0,3,0,0,2,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,0,2,3,2,0,2,2,0,2,0,2,2,0,2,0,2,2,2,0,0,0,0,0,0,2,3,0,0,0,2, -0,1,2,0,0,0,0,2,2,0,0,0,2,1,0,2,2,0,0,0,0,0,0,1,0,2,0,0,0,0,0,0, -0,0,2,1,0,2,3,2,2,3,2,3,2,0,0,3,3,3,0,0,3,2,0,0,0,1,1,0,2,0,2,2, -0,2,0,2,0,2,2,0,0,2,0,2,2,2,0,2,2,2,2,0,0,2,0,0,0,2,0,1,0,0,0,0, -0,3,0,3,3,2,2,0,3,0,0,0,2,2,0,2,2,2,1,2,0,0,1,2,2,0,0,3,0,0,0,2, -0,1,2,0,0,0,1,2,0,0,0,0,0,0,0,2,2,0,1,0,0,2,0,0,0,2,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,2,3,3,2,2,0,0,0,2,0,2,3,3,0,2,0,0,0,0,0,0,2,2,2,0,2,2,0,2,0,2, -0,2,2,0,0,2,2,2,2,1,0,0,2,2,0,2,0,0,2,0,0,0,0,0,0,2,0,0,0,0,0,0, -0,2,0,3,2,3,0,0,0,3,0,0,2,2,0,2,0,2,2,2,0,0,2,0,0,0,0,0,0,0,0,2, -0,0,2,2,0,0,2,2,2,0,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,2,0,0,3,2,0,2,2,2,2,2,0,0,0,2,0,0,0,0,2,0,1,0,0,2,0,1,0,0,0, -0,2,2,2,0,2,2,0,1,2,0,2,2,2,0,2,2,2,2,1,2,2,0,0,2,0,0,0,0,0,0,0, -0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, -0,2,0,2,0,2,2,0,0,0,0,1,2,1,0,0,2,2,0,0,2,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,3,2,3,0,0,2,0,0,0,2,2,0,2,0,0,0,1,0,0,2,0,2,0,2,2,0,0,0,0, -0,0,2,0,0,0,0,2,2,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0, -0,2,2,3,2,2,0,0,0,0,0,0,1,3,0,2,0,2,2,0,0,0,1,0,2,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,2,0,2,0,3,2,0,2,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -0,0,2,0,0,0,0,1,1,0,0,2,1,2,0,2,2,0,1,0,0,1,0,0,0,2,0,0,0,0,0,0, -0,3,0,2,2,2,0,0,2,0,0,0,2,0,0,0,2,3,0,2,0,0,0,0,0,0,2,2,0,0,0,2, -0,1,2,0,0,0,1,2,2,1,0,0,0,2,0,0,2,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,2,1,2,0,2,2,0,2,0,0,2,0,0,0,0,1,2,1,0,2,1,0,0,0,0,0,0,0,0,0,0, -0,0,2,0,0,0,3,1,2,2,0,2,0,0,0,0,2,0,0,0,2,0,0,3,0,0,0,0,2,2,2,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,2,1,0,2,0,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,1,0,0,0,0,0,0,2, -0,2,2,0,0,2,2,2,2,2,0,1,2,0,0,0,2,2,0,1,0,2,0,0,2,2,0,0,0,0,0,0, -0,0,0,0,1,0,0,0,0,0,0,0,3,0,0,2,0,0,0,0,0,0,0,0,2,0,2,0,0,0,0,2, -0,1,2,0,0,0,0,2,2,1,0,1,0,1,0,2,2,2,1,0,0,0,0,0,0,1,0,0,0,0,0,0, -0,2,0,1,2,0,0,0,0,0,0,0,0,0,0,2,0,0,2,2,0,0,0,0,1,0,0,0,0,0,0,2, -0,2,2,0,0,0,0,2,2,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,2,0,0,0, -0,2,2,2,2,0,0,0,3,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,2,0,0,0,0,0,0,1, -0,0,2,0,0,0,0,1,2,0,0,0,0,0,0,2,2,1,1,0,0,0,0,0,0,1,0,0,0,0,0,0, -0,2,0,2,2,2,0,0,2,0,0,0,0,0,0,0,2,2,2,0,0,0,2,0,0,0,0,0,0,0,0,2, -0,0,1,0,0,0,0,2,1,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0, -0,3,0,2,0,0,0,0,0,0,0,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,2,0,0,0,0,2, -0,0,2,0,0,0,0,2,2,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,2,0,2,2,1,0,0,0,0,0,0,2,0,0,2,0,2,2,2,0,0,0,0,0,0,2,0,0,0,0,2, -0,0,2,0,0,2,0,2,2,0,0,0,0,2,0,2,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0, -0,0,3,0,0,0,2,2,0,2,2,0,0,0,0,0,2,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,2,0,0,0,0,0, -0,2,2,2,2,2,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1, -0,0,0,0,0,0,0,2,1,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,2,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, -0,2,0,0,0,2,0,0,0,0,0,1,0,0,0,0,2,2,0,0,0,1,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,2,0,0,0, -0,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,2,0,2,0,0,0, -0,0,0,0,0,0,0,0,2,1,0,0,0,0,0,0,2,0,0,0,1,2,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -) - -Latin7GreekModel = { - 'char_to_order_map': Latin7_char_to_order_map, - 'precedence_matrix': GreekLangModel, - 'typical_positive_ratio': 0.982851, - 'keep_english_letter': False, - 'charset_name': "ISO-8859-7", - 'language': 'Greek', +# Character Mapping Table(s): +WINDOWS_1253_GREEK_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 82, # 'A' + 66: 100, # 'B' + 67: 104, # 'C' + 68: 94, # 'D' + 69: 98, # 'E' + 70: 101, # 'F' + 71: 116, # 'G' + 72: 102, # 'H' + 73: 111, # 'I' + 74: 187, # 'J' + 75: 117, # 'K' + 76: 92, # 'L' + 77: 88, # 'M' + 78: 113, # 'N' + 79: 85, # 'O' + 80: 79, # 'P' + 81: 118, # 'Q' + 82: 105, # 'R' + 83: 83, # 'S' + 84: 67, # 'T' + 85: 114, # 'U' + 86: 119, # 'V' + 87: 95, # 'W' + 88: 99, # 'X' + 89: 109, # 'Y' + 90: 188, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 72, # 'a' + 98: 70, # 'b' + 99: 80, # 'c' + 100: 81, # 'd' + 101: 60, # 'e' + 102: 96, # 'f' + 103: 93, # 'g' + 104: 89, # 'h' + 105: 68, # 'i' + 106: 120, # 'j' + 107: 97, # 'k' + 108: 77, # 'l' + 109: 86, # 'm' + 110: 69, # 'n' + 111: 55, # 'o' + 112: 78, # 'p' + 113: 115, # 'q' + 114: 65, # 'r' + 115: 66, # 's' + 116: 58, # 't' + 117: 76, # 'u' + 118: 106, # 'v' + 119: 103, # 'w' + 120: 87, # 'x' + 121: 107, # 'y' + 122: 112, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 255, # '€' + 129: 255, # None + 130: 255, # '‚' + 131: 255, # 'ƒ' + 132: 255, # '„' + 133: 255, # '…' + 134: 255, # '†' + 135: 255, # '‡' + 136: 255, # None + 137: 255, # '‰' + 138: 255, # None + 139: 255, # '‹' + 140: 255, # None + 141: 255, # None + 142: 255, # None + 143: 255, # None + 144: 255, # None + 145: 255, # '‘' + 146: 255, # '’' + 147: 255, # '“' + 148: 255, # '”' + 149: 255, # '•' + 150: 255, # '–' + 151: 255, # '—' + 152: 255, # None + 153: 255, # '™' + 154: 255, # None + 155: 255, # '›' + 156: 255, # None + 157: 255, # None + 158: 255, # None + 159: 255, # None + 160: 253, # '\xa0' + 161: 233, # '΅' + 162: 61, # 'Ά' + 163: 253, # '£' + 164: 253, # '¤' + 165: 253, # '¥' + 166: 253, # '¦' + 167: 253, # '§' + 168: 253, # '¨' + 169: 253, # '©' + 170: 253, # None + 171: 253, # '«' + 172: 253, # '¬' + 173: 74, # '\xad' + 174: 253, # '®' + 175: 253, # '―' + 176: 253, # '°' + 177: 253, # '±' + 178: 253, # '²' + 179: 253, # '³' + 180: 247, # '΄' + 181: 253, # 'µ' + 182: 253, # '¶' + 183: 36, # '·' + 184: 46, # 'Έ' + 185: 71, # 'Ή' + 186: 73, # 'Ί' + 187: 253, # '»' + 188: 54, # 'Ό' + 189: 253, # '½' + 190: 108, # 'Ύ' + 191: 123, # 'Ώ' + 192: 110, # 'ΐ' + 193: 31, # 'Α' + 194: 51, # 'Β' + 195: 43, # 'Γ' + 196: 41, # 'Δ' + 197: 34, # 'Ε' + 198: 91, # 'Ζ' + 199: 40, # 'Η' + 200: 52, # 'Θ' + 201: 47, # 'Ι' + 202: 44, # 'Κ' + 203: 53, # 'Λ' + 204: 38, # 'Μ' + 205: 49, # 'Ν' + 206: 59, # 'Ξ' + 207: 39, # 'Ο' + 208: 35, # 'Π' + 209: 48, # 'Ρ' + 210: 250, # None + 211: 37, # 'Σ' + 212: 33, # 'Τ' + 213: 45, # 'Υ' + 214: 56, # 'Φ' + 215: 50, # 'Χ' + 216: 84, # 'Ψ' + 217: 57, # 'Ω' + 218: 120, # 'Ϊ' + 219: 121, # 'Ϋ' + 220: 17, # 'ά' + 221: 18, # 'έ' + 222: 22, # 'ή' + 223: 15, # 'ί' + 224: 124, # 'ΰ' + 225: 1, # 'α' + 226: 29, # 'β' + 227: 20, # 'γ' + 228: 21, # 'δ' + 229: 3, # 'ε' + 230: 32, # 'ζ' + 231: 13, # 'η' + 232: 25, # 'θ' + 233: 5, # 'ι' + 234: 11, # 'κ' + 235: 16, # 'λ' + 236: 10, # 'μ' + 237: 6, # 'ν' + 238: 30, # 'ξ' + 239: 4, # 'ο' + 240: 9, # 'π' + 241: 8, # 'ρ' + 242: 14, # 'ς' + 243: 7, # 'σ' + 244: 2, # 'τ' + 245: 12, # 'υ' + 246: 28, # 'φ' + 247: 23, # 'χ' + 248: 42, # 'ψ' + 249: 24, # 'ω' + 250: 64, # 'ϊ' + 251: 75, # 'ϋ' + 252: 19, # 'ό' + 253: 26, # 'ύ' + 254: 27, # 'ώ' + 255: 253, # None } -Win1253GreekModel = { - 'char_to_order_map': win1253_char_to_order_map, - 'precedence_matrix': GreekLangModel, - 'typical_positive_ratio': 0.982851, - 'keep_english_letter': False, - 'charset_name': "windows-1253", - 'language': 'Greek', +WINDOWS_1253_GREEK_MODEL = SingleByteCharSetModel(charset_name='windows-1253', + language='Greek', + char_to_order_map=WINDOWS_1253_GREEK_CHAR_TO_ORDER, + language_model=GREEK_LANG_MODEL, + typical_positive_ratio=0.982851, + keep_ascii_letters=False, + alphabet='ΆΈΉΊΌΎΏΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩάέήίαβγδεζηθικλμνξοπρςστυφχψωόύώ') + +ISO_8859_7_GREEK_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 82, # 'A' + 66: 100, # 'B' + 67: 104, # 'C' + 68: 94, # 'D' + 69: 98, # 'E' + 70: 101, # 'F' + 71: 116, # 'G' + 72: 102, # 'H' + 73: 111, # 'I' + 74: 187, # 'J' + 75: 117, # 'K' + 76: 92, # 'L' + 77: 88, # 'M' + 78: 113, # 'N' + 79: 85, # 'O' + 80: 79, # 'P' + 81: 118, # 'Q' + 82: 105, # 'R' + 83: 83, # 'S' + 84: 67, # 'T' + 85: 114, # 'U' + 86: 119, # 'V' + 87: 95, # 'W' + 88: 99, # 'X' + 89: 109, # 'Y' + 90: 188, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 72, # 'a' + 98: 70, # 'b' + 99: 80, # 'c' + 100: 81, # 'd' + 101: 60, # 'e' + 102: 96, # 'f' + 103: 93, # 'g' + 104: 89, # 'h' + 105: 68, # 'i' + 106: 120, # 'j' + 107: 97, # 'k' + 108: 77, # 'l' + 109: 86, # 'm' + 110: 69, # 'n' + 111: 55, # 'o' + 112: 78, # 'p' + 113: 115, # 'q' + 114: 65, # 'r' + 115: 66, # 's' + 116: 58, # 't' + 117: 76, # 'u' + 118: 106, # 'v' + 119: 103, # 'w' + 120: 87, # 'x' + 121: 107, # 'y' + 122: 112, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 255, # '\x80' + 129: 255, # '\x81' + 130: 255, # '\x82' + 131: 255, # '\x83' + 132: 255, # '\x84' + 133: 255, # '\x85' + 134: 255, # '\x86' + 135: 255, # '\x87' + 136: 255, # '\x88' + 137: 255, # '\x89' + 138: 255, # '\x8a' + 139: 255, # '\x8b' + 140: 255, # '\x8c' + 141: 255, # '\x8d' + 142: 255, # '\x8e' + 143: 255, # '\x8f' + 144: 255, # '\x90' + 145: 255, # '\x91' + 146: 255, # '\x92' + 147: 255, # '\x93' + 148: 255, # '\x94' + 149: 255, # '\x95' + 150: 255, # '\x96' + 151: 255, # '\x97' + 152: 255, # '\x98' + 153: 255, # '\x99' + 154: 255, # '\x9a' + 155: 255, # '\x9b' + 156: 255, # '\x9c' + 157: 255, # '\x9d' + 158: 255, # '\x9e' + 159: 255, # '\x9f' + 160: 253, # '\xa0' + 161: 233, # '‘' + 162: 90, # '’' + 163: 253, # '£' + 164: 253, # '€' + 165: 253, # '₯' + 166: 253, # '¦' + 167: 253, # '§' + 168: 253, # '¨' + 169: 253, # '©' + 170: 253, # 'ͺ' + 171: 253, # '«' + 172: 253, # '¬' + 173: 74, # '\xad' + 174: 253, # None + 175: 253, # '―' + 176: 253, # '°' + 177: 253, # '±' + 178: 253, # '²' + 179: 253, # '³' + 180: 247, # '΄' + 181: 248, # '΅' + 182: 61, # 'Ά' + 183: 36, # '·' + 184: 46, # 'Έ' + 185: 71, # 'Ή' + 186: 73, # 'Ί' + 187: 253, # '»' + 188: 54, # 'Ό' + 189: 253, # '½' + 190: 108, # 'Ύ' + 191: 123, # 'Ώ' + 192: 110, # 'ΐ' + 193: 31, # 'Α' + 194: 51, # 'Β' + 195: 43, # 'Γ' + 196: 41, # 'Δ' + 197: 34, # 'Ε' + 198: 91, # 'Ζ' + 199: 40, # 'Η' + 200: 52, # 'Θ' + 201: 47, # 'Ι' + 202: 44, # 'Κ' + 203: 53, # 'Λ' + 204: 38, # 'Μ' + 205: 49, # 'Ν' + 206: 59, # 'Ξ' + 207: 39, # 'Ο' + 208: 35, # 'Π' + 209: 48, # 'Ρ' + 210: 250, # None + 211: 37, # 'Σ' + 212: 33, # 'Τ' + 213: 45, # 'Υ' + 214: 56, # 'Φ' + 215: 50, # 'Χ' + 216: 84, # 'Ψ' + 217: 57, # 'Ω' + 218: 120, # 'Ϊ' + 219: 121, # 'Ϋ' + 220: 17, # 'ά' + 221: 18, # 'έ' + 222: 22, # 'ή' + 223: 15, # 'ί' + 224: 124, # 'ΰ' + 225: 1, # 'α' + 226: 29, # 'β' + 227: 20, # 'γ' + 228: 21, # 'δ' + 229: 3, # 'ε' + 230: 32, # 'ζ' + 231: 13, # 'η' + 232: 25, # 'θ' + 233: 5, # 'ι' + 234: 11, # 'κ' + 235: 16, # 'λ' + 236: 10, # 'μ' + 237: 6, # 'ν' + 238: 30, # 'ξ' + 239: 4, # 'ο' + 240: 9, # 'π' + 241: 8, # 'ρ' + 242: 14, # 'ς' + 243: 7, # 'σ' + 244: 2, # 'τ' + 245: 12, # 'υ' + 246: 28, # 'φ' + 247: 23, # 'χ' + 248: 42, # 'ψ' + 249: 24, # 'ω' + 250: 64, # 'ϊ' + 251: 75, # 'ϋ' + 252: 19, # 'ό' + 253: 26, # 'ύ' + 254: 27, # 'ώ' + 255: 253, # None } + +ISO_8859_7_GREEK_MODEL = SingleByteCharSetModel(charset_name='ISO-8859-7', + language='Greek', + char_to_order_map=ISO_8859_7_GREEK_CHAR_TO_ORDER, + language_model=GREEK_LANG_MODEL, + typical_positive_ratio=0.982851, + keep_ascii_letters=False, + alphabet='ΆΈΉΊΌΎΏΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩάέήίαβγδεζηθικλμνξοπρςστυφχψωόύώ') + diff --git a/pipenv/patched/notpip/_vendor/chardet/langhebrewmodel.py b/pipenv/patched/notpip/_vendor/chardet/langhebrewmodel.py index 58f4c875..739c6cb1 100644 --- a/pipenv/patched/notpip/_vendor/chardet/langhebrewmodel.py +++ b/pipenv/patched/notpip/_vendor/chardet/langhebrewmodel.py @@ -1,200 +1,4383 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Universal charset detector code. -# -# The Initial Developer of the Original Code is -# Simon Montagu -# Portions created by the Initial Developer are Copyright (C) 2005 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# Shy Shalom - original C code -# Shoshannah Forbes - original C code (?) -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### +#!/usr/bin/env python +# -*- coding: utf-8 -*- -# 255: Control characters that usually does not exist in any text +from pipenv.patched.notpip._vendor.chardet.sbcharsetprober import SingleByteCharSetModel + + +# 3: Positive +# 2: Likely +# 1: Unlikely +# 0: Negative + +HEBREW_LANG_MODEL = { + 50: { # 'a' + 50: 0, # 'a' + 60: 1, # 'c' + 61: 1, # 'd' + 42: 1, # 'e' + 53: 1, # 'i' + 56: 2, # 'l' + 54: 2, # 'n' + 49: 0, # 'o' + 51: 2, # 'r' + 43: 1, # 's' + 44: 2, # 't' + 63: 1, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 1, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 1, # 'ק' + 7: 0, # 'ר' + 10: 1, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 60: { # 'c' + 50: 1, # 'a' + 60: 1, # 'c' + 61: 0, # 'd' + 42: 1, # 'e' + 53: 1, # 'i' + 56: 1, # 'l' + 54: 0, # 'n' + 49: 1, # 'o' + 51: 1, # 'r' + 43: 1, # 's' + 44: 2, # 't' + 63: 1, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 1, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 1, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 61: { # 'd' + 50: 1, # 'a' + 60: 0, # 'c' + 61: 1, # 'd' + 42: 1, # 'e' + 53: 1, # 'i' + 56: 1, # 'l' + 54: 1, # 'n' + 49: 2, # 'o' + 51: 1, # 'r' + 43: 1, # 's' + 44: 0, # 't' + 63: 1, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 1, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 1, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 42: { # 'e' + 50: 1, # 'a' + 60: 1, # 'c' + 61: 2, # 'd' + 42: 1, # 'e' + 53: 1, # 'i' + 56: 2, # 'l' + 54: 2, # 'n' + 49: 1, # 'o' + 51: 2, # 'r' + 43: 2, # 's' + 44: 2, # 't' + 63: 1, # 'u' + 34: 1, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 1, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 1, # '–' + 52: 2, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 53: { # 'i' + 50: 1, # 'a' + 60: 2, # 'c' + 61: 1, # 'd' + 42: 1, # 'e' + 53: 0, # 'i' + 56: 1, # 'l' + 54: 2, # 'n' + 49: 2, # 'o' + 51: 1, # 'r' + 43: 2, # 's' + 44: 2, # 't' + 63: 1, # 'u' + 34: 0, # '\xa0' + 55: 1, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 56: { # 'l' + 50: 1, # 'a' + 60: 1, # 'c' + 61: 1, # 'd' + 42: 2, # 'e' + 53: 2, # 'i' + 56: 2, # 'l' + 54: 1, # 'n' + 49: 1, # 'o' + 51: 0, # 'r' + 43: 1, # 's' + 44: 1, # 't' + 63: 1, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 54: { # 'n' + 50: 1, # 'a' + 60: 1, # 'c' + 61: 1, # 'd' + 42: 1, # 'e' + 53: 1, # 'i' + 56: 1, # 'l' + 54: 1, # 'n' + 49: 1, # 'o' + 51: 0, # 'r' + 43: 1, # 's' + 44: 2, # 't' + 63: 1, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 1, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 2, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 49: { # 'o' + 50: 1, # 'a' + 60: 1, # 'c' + 61: 1, # 'd' + 42: 1, # 'e' + 53: 1, # 'i' + 56: 1, # 'l' + 54: 2, # 'n' + 49: 1, # 'o' + 51: 2, # 'r' + 43: 1, # 's' + 44: 1, # 't' + 63: 1, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 51: { # 'r' + 50: 2, # 'a' + 60: 1, # 'c' + 61: 1, # 'd' + 42: 2, # 'e' + 53: 1, # 'i' + 56: 1, # 'l' + 54: 1, # 'n' + 49: 2, # 'o' + 51: 1, # 'r' + 43: 1, # 's' + 44: 1, # 't' + 63: 1, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 2, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 43: { # 's' + 50: 1, # 'a' + 60: 1, # 'c' + 61: 0, # 'd' + 42: 2, # 'e' + 53: 1, # 'i' + 56: 1, # 'l' + 54: 1, # 'n' + 49: 1, # 'o' + 51: 1, # 'r' + 43: 1, # 's' + 44: 2, # 't' + 63: 1, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 2, # '”' + 58: 0, # '†' + 40: 2, # '…' + }, + 44: { # 't' + 50: 1, # 'a' + 60: 1, # 'c' + 61: 0, # 'd' + 42: 2, # 'e' + 53: 2, # 'i' + 56: 1, # 'l' + 54: 0, # 'n' + 49: 1, # 'o' + 51: 1, # 'r' + 43: 1, # 's' + 44: 1, # 't' + 63: 1, # 'u' + 34: 1, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 2, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 63: { # 'u' + 50: 1, # 'a' + 60: 1, # 'c' + 61: 1, # 'd' + 42: 1, # 'e' + 53: 1, # 'i' + 56: 1, # 'l' + 54: 1, # 'n' + 49: 0, # 'o' + 51: 1, # 'r' + 43: 2, # 's' + 44: 1, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 34: { # '\xa0' + 50: 1, # 'a' + 60: 0, # 'c' + 61: 1, # 'd' + 42: 0, # 'e' + 53: 1, # 'i' + 56: 0, # 'l' + 54: 1, # 'n' + 49: 1, # 'o' + 51: 0, # 'r' + 43: 1, # 's' + 44: 1, # 't' + 63: 0, # 'u' + 34: 2, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 1, # 'ב' + 20: 1, # 'ג' + 16: 1, # 'ד' + 3: 1, # 'ה' + 2: 1, # 'ו' + 24: 1, # 'ז' + 14: 1, # 'ח' + 22: 1, # 'ט' + 1: 2, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 1, # 'ל' + 11: 0, # 'ם' + 6: 2, # 'מ' + 23: 0, # 'ן' + 12: 1, # 'נ' + 19: 1, # 'ס' + 13: 1, # 'ע' + 26: 0, # 'ף' + 18: 1, # 'פ' + 27: 0, # 'ץ' + 21: 1, # 'צ' + 17: 1, # 'ק' + 7: 1, # 'ר' + 10: 1, # 'ש' + 5: 1, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 55: { # '´' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 1, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 1, # 'ה' + 2: 1, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 2, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 1, # 'ל' + 11: 0, # 'ם' + 6: 1, # 'מ' + 23: 1, # 'ן' + 12: 1, # 'נ' + 19: 1, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 1, # 'ר' + 10: 1, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 48: { # '¼' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 1, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 1, # 'ל' + 11: 0, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 39: { # '½' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 1, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 1, # 'צ' + 17: 1, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 57: { # '¾' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 30: { # 'ְ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 1, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 1, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 2, # 'ב' + 20: 2, # 'ג' + 16: 2, # 'ד' + 3: 2, # 'ה' + 2: 2, # 'ו' + 24: 2, # 'ז' + 14: 2, # 'ח' + 22: 2, # 'ט' + 1: 2, # 'י' + 25: 2, # 'ך' + 15: 2, # 'כ' + 4: 2, # 'ל' + 11: 1, # 'ם' + 6: 2, # 'מ' + 23: 0, # 'ן' + 12: 2, # 'נ' + 19: 2, # 'ס' + 13: 2, # 'ע' + 26: 0, # 'ף' + 18: 2, # 'פ' + 27: 0, # 'ץ' + 21: 2, # 'צ' + 17: 2, # 'ק' + 7: 2, # 'ר' + 10: 2, # 'ש' + 5: 2, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 59: { # 'ֱ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 1, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 1, # 'ב' + 20: 1, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 1, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 1, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 2, # 'ל' + 11: 0, # 'ם' + 6: 2, # 'מ' + 23: 0, # 'ן' + 12: 1, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 1, # 'ר' + 10: 1, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 41: { # 'ֲ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 2, # 'ב' + 20: 1, # 'ג' + 16: 2, # 'ד' + 3: 1, # 'ה' + 2: 1, # 'ו' + 24: 1, # 'ז' + 14: 1, # 'ח' + 22: 1, # 'ט' + 1: 1, # 'י' + 25: 1, # 'ך' + 15: 1, # 'כ' + 4: 2, # 'ל' + 11: 0, # 'ם' + 6: 2, # 'מ' + 23: 0, # 'ן' + 12: 2, # 'נ' + 19: 1, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 1, # 'פ' + 27: 0, # 'ץ' + 21: 2, # 'צ' + 17: 1, # 'ק' + 7: 2, # 'ר' + 10: 2, # 'ש' + 5: 1, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 33: { # 'ִ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 1, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 1, # 'ִ' + 37: 0, # 'ֵ' + 36: 1, # 'ֶ' + 31: 0, # 'ַ' + 29: 1, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 1, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 2, # 'ב' + 20: 2, # 'ג' + 16: 2, # 'ד' + 3: 1, # 'ה' + 2: 1, # 'ו' + 24: 2, # 'ז' + 14: 1, # 'ח' + 22: 1, # 'ט' + 1: 3, # 'י' + 25: 1, # 'ך' + 15: 2, # 'כ' + 4: 2, # 'ל' + 11: 2, # 'ם' + 6: 2, # 'מ' + 23: 2, # 'ן' + 12: 2, # 'נ' + 19: 2, # 'ס' + 13: 1, # 'ע' + 26: 0, # 'ף' + 18: 2, # 'פ' + 27: 1, # 'ץ' + 21: 2, # 'צ' + 17: 2, # 'ק' + 7: 2, # 'ר' + 10: 2, # 'ש' + 5: 2, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 37: { # 'ֵ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 1, # 'ֶ' + 31: 1, # 'ַ' + 29: 1, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 2, # 'ב' + 20: 1, # 'ג' + 16: 2, # 'ד' + 3: 2, # 'ה' + 2: 1, # 'ו' + 24: 1, # 'ז' + 14: 2, # 'ח' + 22: 1, # 'ט' + 1: 3, # 'י' + 25: 2, # 'ך' + 15: 1, # 'כ' + 4: 2, # 'ל' + 11: 2, # 'ם' + 6: 1, # 'מ' + 23: 2, # 'ן' + 12: 2, # 'נ' + 19: 1, # 'ס' + 13: 2, # 'ע' + 26: 1, # 'ף' + 18: 1, # 'פ' + 27: 1, # 'ץ' + 21: 1, # 'צ' + 17: 1, # 'ק' + 7: 2, # 'ר' + 10: 2, # 'ש' + 5: 2, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 36: { # 'ֶ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 1, # 'ֶ' + 31: 1, # 'ַ' + 29: 1, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 2, # 'ב' + 20: 1, # 'ג' + 16: 2, # 'ד' + 3: 2, # 'ה' + 2: 1, # 'ו' + 24: 1, # 'ז' + 14: 2, # 'ח' + 22: 1, # 'ט' + 1: 2, # 'י' + 25: 2, # 'ך' + 15: 1, # 'כ' + 4: 2, # 'ל' + 11: 2, # 'ם' + 6: 2, # 'מ' + 23: 2, # 'ן' + 12: 2, # 'נ' + 19: 2, # 'ס' + 13: 1, # 'ע' + 26: 1, # 'ף' + 18: 1, # 'פ' + 27: 2, # 'ץ' + 21: 1, # 'צ' + 17: 1, # 'ק' + 7: 2, # 'ר' + 10: 2, # 'ש' + 5: 2, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 31: { # 'ַ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 1, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 1, # 'ֶ' + 31: 0, # 'ַ' + 29: 2, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 2, # 'ב' + 20: 2, # 'ג' + 16: 2, # 'ד' + 3: 2, # 'ה' + 2: 1, # 'ו' + 24: 2, # 'ז' + 14: 2, # 'ח' + 22: 2, # 'ט' + 1: 3, # 'י' + 25: 1, # 'ך' + 15: 2, # 'כ' + 4: 2, # 'ל' + 11: 2, # 'ם' + 6: 2, # 'מ' + 23: 2, # 'ן' + 12: 2, # 'נ' + 19: 2, # 'ס' + 13: 2, # 'ע' + 26: 2, # 'ף' + 18: 2, # 'פ' + 27: 1, # 'ץ' + 21: 2, # 'צ' + 17: 2, # 'ק' + 7: 2, # 'ר' + 10: 2, # 'ש' + 5: 2, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 29: { # 'ָ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 1, # 'ַ' + 29: 2, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 1, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 2, # 'ב' + 20: 2, # 'ג' + 16: 2, # 'ד' + 3: 3, # 'ה' + 2: 2, # 'ו' + 24: 2, # 'ז' + 14: 2, # 'ח' + 22: 1, # 'ט' + 1: 2, # 'י' + 25: 2, # 'ך' + 15: 2, # 'כ' + 4: 2, # 'ל' + 11: 2, # 'ם' + 6: 2, # 'מ' + 23: 2, # 'ן' + 12: 2, # 'נ' + 19: 1, # 'ס' + 13: 2, # 'ע' + 26: 1, # 'ף' + 18: 2, # 'פ' + 27: 1, # 'ץ' + 21: 2, # 'צ' + 17: 2, # 'ק' + 7: 2, # 'ר' + 10: 2, # 'ש' + 5: 2, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 35: { # 'ֹ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 1, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 2, # 'ב' + 20: 1, # 'ג' + 16: 2, # 'ד' + 3: 2, # 'ה' + 2: 1, # 'ו' + 24: 1, # 'ז' + 14: 1, # 'ח' + 22: 1, # 'ט' + 1: 1, # 'י' + 25: 1, # 'ך' + 15: 2, # 'כ' + 4: 2, # 'ל' + 11: 2, # 'ם' + 6: 2, # 'מ' + 23: 2, # 'ן' + 12: 2, # 'נ' + 19: 2, # 'ס' + 13: 2, # 'ע' + 26: 1, # 'ף' + 18: 2, # 'פ' + 27: 1, # 'ץ' + 21: 2, # 'צ' + 17: 2, # 'ק' + 7: 2, # 'ר' + 10: 2, # 'ש' + 5: 2, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 62: { # 'ֻ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 1, # 'ב' + 20: 1, # 'ג' + 16: 1, # 'ד' + 3: 1, # 'ה' + 2: 1, # 'ו' + 24: 1, # 'ז' + 14: 1, # 'ח' + 22: 0, # 'ט' + 1: 1, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 2, # 'ל' + 11: 1, # 'ם' + 6: 1, # 'מ' + 23: 1, # 'ן' + 12: 1, # 'נ' + 19: 1, # 'ס' + 13: 1, # 'ע' + 26: 0, # 'ף' + 18: 1, # 'פ' + 27: 0, # 'ץ' + 21: 1, # 'צ' + 17: 1, # 'ק' + 7: 1, # 'ר' + 10: 1, # 'ש' + 5: 1, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 28: { # 'ּ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 3, # 'ְ' + 59: 0, # 'ֱ' + 41: 1, # 'ֲ' + 33: 3, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 3, # 'ַ' + 29: 3, # 'ָ' + 35: 2, # 'ֹ' + 62: 1, # 'ֻ' + 28: 0, # 'ּ' + 38: 2, # 'ׁ' + 45: 1, # 'ׂ' + 9: 2, # 'א' + 8: 2, # 'ב' + 20: 1, # 'ג' + 16: 2, # 'ד' + 3: 1, # 'ה' + 2: 2, # 'ו' + 24: 1, # 'ז' + 14: 1, # 'ח' + 22: 1, # 'ט' + 1: 2, # 'י' + 25: 2, # 'ך' + 15: 2, # 'כ' + 4: 2, # 'ל' + 11: 1, # 'ם' + 6: 2, # 'מ' + 23: 1, # 'ן' + 12: 2, # 'נ' + 19: 1, # 'ס' + 13: 2, # 'ע' + 26: 1, # 'ף' + 18: 1, # 'פ' + 27: 1, # 'ץ' + 21: 1, # 'צ' + 17: 1, # 'ק' + 7: 2, # 'ר' + 10: 2, # 'ש' + 5: 2, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 38: { # 'ׁ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 1, # 'ֹ' + 62: 1, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 2, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 1, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 1, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 45: { # 'ׂ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 1, # 'ֵ' + 36: 2, # 'ֶ' + 31: 1, # 'ַ' + 29: 2, # 'ָ' + 35: 1, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 0, # 'ב' + 20: 1, # 'ג' + 16: 0, # 'ד' + 3: 1, # 'ה' + 2: 2, # 'ו' + 24: 0, # 'ז' + 14: 1, # 'ח' + 22: 0, # 'ט' + 1: 1, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 1, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 1, # 'נ' + 19: 0, # 'ס' + 13: 1, # 'ע' + 26: 0, # 'ף' + 18: 1, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 1, # 'ר' + 10: 0, # 'ש' + 5: 1, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 9: { # 'א' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 1, # '´' + 48: 1, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 2, # 'ֱ' + 41: 2, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 2, # 'ֹ' + 62: 1, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 3, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 2, # 'ע' + 26: 3, # 'ף' + 18: 3, # 'פ' + 27: 1, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 8: { # 'ב' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 1, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 1, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 2, # 'ֹ' + 62: 1, # 'ֻ' + 28: 3, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 2, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 2, # 'ם' + 6: 3, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 3, # 'ע' + 26: 1, # 'ף' + 18: 3, # 'פ' + 27: 2, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 1, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 20: { # 'ג' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 2, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 1, # 'ִ' + 37: 1, # 'ֵ' + 36: 1, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 1, # 'ֹ' + 62: 0, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 3, # 'ב' + 20: 2, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 2, # 'ח' + 22: 2, # 'ט' + 1: 3, # 'י' + 25: 1, # 'ך' + 15: 1, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 2, # 'ס' + 13: 3, # 'ע' + 26: 2, # 'ף' + 18: 2, # 'פ' + 27: 1, # 'ץ' + 21: 1, # 'צ' + 17: 1, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 16: { # 'ד' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 2, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 1, # 'ז' + 14: 2, # 'ח' + 22: 2, # 'ט' + 1: 3, # 'י' + 25: 2, # 'ך' + 15: 2, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 2, # 'ן' + 12: 3, # 'נ' + 19: 2, # 'ס' + 13: 3, # 'ע' + 26: 2, # 'ף' + 18: 3, # 'פ' + 27: 0, # 'ץ' + 21: 2, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 3: { # 'ה' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 1, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 0, # '´' + 48: 1, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 1, # 'ְ' + 59: 1, # 'ֱ' + 41: 2, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 3, # 'ַ' + 29: 2, # 'ָ' + 35: 1, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 1, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 3, # 'ע' + 26: 0, # 'ף' + 18: 3, # 'פ' + 27: 1, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 1, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 2, # '…' + }, + 2: { # 'ו' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 1, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 1, # '´' + 48: 1, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 1, # 'ֵ' + 36: 1, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 3, # 'ֹ' + 62: 0, # 'ֻ' + 28: 3, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 3, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 3, # 'ע' + 26: 3, # 'ף' + 18: 3, # 'פ' + 27: 3, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 1, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 2, # '…' + }, + 24: { # 'ז' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 1, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 1, # 'ֲ' + 33: 1, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 1, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 2, # 'ב' + 20: 2, # 'ג' + 16: 2, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 2, # 'ז' + 14: 2, # 'ח' + 22: 1, # 'ט' + 1: 3, # 'י' + 25: 1, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 2, # 'ם' + 6: 3, # 'מ' + 23: 2, # 'ן' + 12: 2, # 'נ' + 19: 1, # 'ס' + 13: 2, # 'ע' + 26: 1, # 'ף' + 18: 1, # 'פ' + 27: 0, # 'ץ' + 21: 2, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 1, # 'ש' + 5: 2, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 14: { # 'ח' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 1, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 1, # 'ֱ' + 41: 2, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 2, # 'ֹ' + 62: 1, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 3, # 'ב' + 20: 2, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 2, # 'ח' + 22: 2, # 'ט' + 1: 3, # 'י' + 25: 1, # 'ך' + 15: 2, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 2, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 1, # 'ע' + 26: 2, # 'ף' + 18: 2, # 'פ' + 27: 2, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 22: { # 'ט' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 1, # 'ֵ' + 36: 1, # 'ֶ' + 31: 2, # 'ַ' + 29: 1, # 'ָ' + 35: 1, # 'ֹ' + 62: 1, # 'ֻ' + 28: 1, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 1, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 2, # 'ז' + 14: 3, # 'ח' + 22: 2, # 'ט' + 1: 3, # 'י' + 25: 1, # 'ך' + 15: 2, # 'כ' + 4: 3, # 'ל' + 11: 2, # 'ם' + 6: 2, # 'מ' + 23: 2, # 'ן' + 12: 3, # 'נ' + 19: 2, # 'ס' + 13: 3, # 'ע' + 26: 2, # 'ף' + 18: 3, # 'פ' + 27: 1, # 'ץ' + 21: 2, # 'צ' + 17: 2, # 'ק' + 7: 3, # 'ר' + 10: 2, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 1: { # 'י' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 1, # '´' + 48: 1, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 1, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 2, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 3, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 3, # 'ע' + 26: 3, # 'ף' + 18: 3, # 'פ' + 27: 3, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 1, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 2, # '…' + }, + 25: { # 'ך' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 2, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 1, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 1, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 1, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 1, # 'ל' + 11: 0, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 1, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 15: { # 'כ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 1, # 'ֹ' + 62: 1, # 'ֻ' + 28: 3, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 2, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 3, # 'ח' + 22: 2, # 'ט' + 1: 3, # 'י' + 25: 3, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 2, # 'ע' + 26: 3, # 'ף' + 18: 3, # 'פ' + 27: 1, # 'ץ' + 21: 2, # 'צ' + 17: 2, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 4: { # 'ל' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 1, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 3, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 2, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 3, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 2, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 3, # 'ע' + 26: 2, # 'ף' + 18: 3, # 'פ' + 27: 2, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 1, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 11: { # 'ם' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 1, # 'ב' + 20: 1, # 'ג' + 16: 0, # 'ד' + 3: 1, # 'ה' + 2: 1, # 'ו' + 24: 1, # 'ז' + 14: 1, # 'ח' + 22: 0, # 'ט' + 1: 1, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 1, # 'ל' + 11: 1, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 1, # 'נ' + 19: 0, # 'ס' + 13: 1, # 'ע' + 26: 0, # 'ף' + 18: 1, # 'פ' + 27: 1, # 'ץ' + 21: 1, # 'צ' + 17: 1, # 'ק' + 7: 1, # 'ר' + 10: 1, # 'ש' + 5: 1, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 2, # '…' + }, + 6: { # 'מ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 1, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 2, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 2, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 3, # 'ע' + 26: 0, # 'ף' + 18: 3, # 'פ' + 27: 2, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 23: { # 'ן' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 0, # '´' + 48: 1, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 1, # 'ב' + 20: 1, # 'ג' + 16: 1, # 'ד' + 3: 1, # 'ה' + 2: 1, # 'ו' + 24: 0, # 'ז' + 14: 1, # 'ח' + 22: 1, # 'ט' + 1: 1, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 1, # 'ל' + 11: 1, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 1, # 'נ' + 19: 1, # 'ס' + 13: 1, # 'ע' + 26: 1, # 'ף' + 18: 1, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 1, # 'ק' + 7: 1, # 'ר' + 10: 1, # 'ש' + 5: 1, # 'ת' + 32: 1, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 2, # '…' + }, + 12: { # 'נ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 1, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 2, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 3, # 'ע' + 26: 2, # 'ף' + 18: 3, # 'פ' + 27: 2, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 19: { # 'ס' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 1, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 1, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 1, # 'ָ' + 35: 1, # 'ֹ' + 62: 2, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 1, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 2, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 2, # 'ם' + 6: 3, # 'מ' + 23: 2, # 'ן' + 12: 3, # 'נ' + 19: 2, # 'ס' + 13: 3, # 'ע' + 26: 3, # 'ף' + 18: 3, # 'פ' + 27: 0, # 'ץ' + 21: 2, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 1, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 13: { # 'ע' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 1, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 1, # 'ְ' + 59: 1, # 'ֱ' + 41: 2, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 2, # 'ֹ' + 62: 1, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 1, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 2, # 'ך' + 15: 2, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 2, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 2, # 'ע' + 26: 1, # 'ף' + 18: 2, # 'פ' + 27: 2, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 26: { # 'ף' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 1, # 'ו' + 24: 0, # 'ז' + 14: 1, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 1, # 'ל' + 11: 0, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 1, # 'ס' + 13: 0, # 'ע' + 26: 1, # 'ף' + 18: 1, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 1, # 'ק' + 7: 1, # 'ר' + 10: 1, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 18: { # 'פ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 1, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 1, # 'ֵ' + 36: 2, # 'ֶ' + 31: 1, # 'ַ' + 29: 2, # 'ָ' + 35: 1, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 2, # 'ב' + 20: 3, # 'ג' + 16: 2, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 2, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 2, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 2, # 'ם' + 6: 2, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 3, # 'ע' + 26: 2, # 'ף' + 18: 2, # 'פ' + 27: 2, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 27: { # 'ץ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 1, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 1, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 1, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 1, # 'ר' + 10: 0, # 'ש' + 5: 1, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 21: { # 'צ' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 1, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 1, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 1, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 2, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 1, # 'ז' + 14: 3, # 'ח' + 22: 2, # 'ט' + 1: 3, # 'י' + 25: 1, # 'ך' + 15: 1, # 'כ' + 4: 3, # 'ל' + 11: 2, # 'ם' + 6: 3, # 'מ' + 23: 2, # 'ן' + 12: 3, # 'נ' + 19: 1, # 'ס' + 13: 3, # 'ע' + 26: 2, # 'ף' + 18: 3, # 'פ' + 27: 2, # 'ץ' + 21: 2, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 0, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 17: { # 'ק' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 1, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 1, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 2, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 2, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 2, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 1, # 'ך' + 15: 1, # 'כ' + 4: 3, # 'ל' + 11: 2, # 'ם' + 6: 3, # 'מ' + 23: 2, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 3, # 'ע' + 26: 2, # 'ף' + 18: 3, # 'פ' + 27: 2, # 'ץ' + 21: 3, # 'צ' + 17: 2, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 7: { # 'ר' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 2, # '´' + 48: 1, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 1, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 2, # 'ֹ' + 62: 1, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 3, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 3, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 3, # 'ס' + 13: 3, # 'ע' + 26: 2, # 'ף' + 18: 3, # 'פ' + 27: 3, # 'ץ' + 21: 3, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 2, # '…' + }, + 10: { # 'ש' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 1, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 1, # 'ִ' + 37: 1, # 'ֵ' + 36: 1, # 'ֶ' + 31: 1, # 'ַ' + 29: 1, # 'ָ' + 35: 1, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 3, # 'ׁ' + 45: 2, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 3, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 2, # 'ז' + 14: 3, # 'ח' + 22: 3, # 'ט' + 1: 3, # 'י' + 25: 3, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 2, # 'ן' + 12: 3, # 'נ' + 19: 2, # 'ס' + 13: 3, # 'ע' + 26: 2, # 'ף' + 18: 3, # 'פ' + 27: 1, # 'ץ' + 21: 2, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 1, # '…' + }, + 5: { # 'ת' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 1, # '\xa0' + 55: 0, # '´' + 48: 1, # '¼' + 39: 1, # '½' + 57: 0, # '¾' + 30: 2, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 2, # 'ִ' + 37: 2, # 'ֵ' + 36: 2, # 'ֶ' + 31: 2, # 'ַ' + 29: 2, # 'ָ' + 35: 1, # 'ֹ' + 62: 1, # 'ֻ' + 28: 2, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 3, # 'א' + 8: 3, # 'ב' + 20: 3, # 'ג' + 16: 2, # 'ד' + 3: 3, # 'ה' + 2: 3, # 'ו' + 24: 2, # 'ז' + 14: 3, # 'ח' + 22: 2, # 'ט' + 1: 3, # 'י' + 25: 2, # 'ך' + 15: 3, # 'כ' + 4: 3, # 'ל' + 11: 3, # 'ם' + 6: 3, # 'מ' + 23: 3, # 'ן' + 12: 3, # 'נ' + 19: 2, # 'ס' + 13: 3, # 'ע' + 26: 2, # 'ף' + 18: 3, # 'פ' + 27: 1, # 'ץ' + 21: 2, # 'צ' + 17: 3, # 'ק' + 7: 3, # 'ר' + 10: 3, # 'ש' + 5: 3, # 'ת' + 32: 1, # '–' + 52: 1, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 2, # '…' + }, + 32: { # '–' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 1, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 1, # 'ב' + 20: 1, # 'ג' + 16: 1, # 'ד' + 3: 1, # 'ה' + 2: 1, # 'ו' + 24: 0, # 'ז' + 14: 1, # 'ח' + 22: 0, # 'ט' + 1: 1, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 1, # 'ל' + 11: 0, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 1, # 'ס' + 13: 1, # 'ע' + 26: 0, # 'ף' + 18: 1, # 'פ' + 27: 0, # 'ץ' + 21: 1, # 'צ' + 17: 0, # 'ק' + 7: 1, # 'ר' + 10: 1, # 'ש' + 5: 1, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 52: { # '’' + 50: 1, # 'a' + 60: 0, # 'c' + 61: 1, # 'd' + 42: 1, # 'e' + 53: 1, # 'i' + 56: 1, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 1, # 'r' + 43: 2, # 's' + 44: 2, # 't' + 63: 1, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 1, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 1, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 47: { # '“' + 50: 1, # 'a' + 60: 1, # 'c' + 61: 1, # 'd' + 42: 1, # 'e' + 53: 1, # 'i' + 56: 1, # 'l' + 54: 1, # 'n' + 49: 1, # 'o' + 51: 1, # 'r' + 43: 1, # 's' + 44: 1, # 't' + 63: 1, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 2, # 'א' + 8: 1, # 'ב' + 20: 1, # 'ג' + 16: 1, # 'ד' + 3: 1, # 'ה' + 2: 1, # 'ו' + 24: 1, # 'ז' + 14: 1, # 'ח' + 22: 1, # 'ט' + 1: 1, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 1, # 'ל' + 11: 0, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 1, # 'נ' + 19: 1, # 'ס' + 13: 1, # 'ע' + 26: 0, # 'ף' + 18: 1, # 'פ' + 27: 0, # 'ץ' + 21: 1, # 'צ' + 17: 1, # 'ק' + 7: 1, # 'ר' + 10: 1, # 'ש' + 5: 1, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 46: { # '”' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 1, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 1, # 'ב' + 20: 1, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 1, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 1, # 'ל' + 11: 0, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 1, # 'צ' + 17: 0, # 'ק' + 7: 1, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 0, # '†' + 40: 0, # '…' + }, + 58: { # '†' + 50: 0, # 'a' + 60: 0, # 'c' + 61: 0, # 'd' + 42: 0, # 'e' + 53: 0, # 'i' + 56: 0, # 'l' + 54: 0, # 'n' + 49: 0, # 'o' + 51: 0, # 'r' + 43: 0, # 's' + 44: 0, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 0, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 0, # 'ה' + 2: 0, # 'ו' + 24: 0, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 0, # 'י' + 25: 0, # 'ך' + 15: 0, # 'כ' + 4: 0, # 'ל' + 11: 0, # 'ם' + 6: 0, # 'מ' + 23: 0, # 'ן' + 12: 0, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 0, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 0, # 'ר' + 10: 0, # 'ש' + 5: 0, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 0, # '”' + 58: 2, # '†' + 40: 0, # '…' + }, + 40: { # '…' + 50: 1, # 'a' + 60: 1, # 'c' + 61: 1, # 'd' + 42: 1, # 'e' + 53: 1, # 'i' + 56: 0, # 'l' + 54: 1, # 'n' + 49: 0, # 'o' + 51: 1, # 'r' + 43: 1, # 's' + 44: 1, # 't' + 63: 0, # 'u' + 34: 0, # '\xa0' + 55: 0, # '´' + 48: 0, # '¼' + 39: 0, # '½' + 57: 0, # '¾' + 30: 0, # 'ְ' + 59: 0, # 'ֱ' + 41: 0, # 'ֲ' + 33: 0, # 'ִ' + 37: 0, # 'ֵ' + 36: 0, # 'ֶ' + 31: 0, # 'ַ' + 29: 0, # 'ָ' + 35: 0, # 'ֹ' + 62: 0, # 'ֻ' + 28: 0, # 'ּ' + 38: 0, # 'ׁ' + 45: 0, # 'ׂ' + 9: 1, # 'א' + 8: 0, # 'ב' + 20: 0, # 'ג' + 16: 0, # 'ד' + 3: 1, # 'ה' + 2: 1, # 'ו' + 24: 1, # 'ז' + 14: 0, # 'ח' + 22: 0, # 'ט' + 1: 1, # 'י' + 25: 0, # 'ך' + 15: 1, # 'כ' + 4: 1, # 'ל' + 11: 0, # 'ם' + 6: 1, # 'מ' + 23: 0, # 'ן' + 12: 1, # 'נ' + 19: 0, # 'ס' + 13: 0, # 'ע' + 26: 0, # 'ף' + 18: 1, # 'פ' + 27: 0, # 'ץ' + 21: 0, # 'צ' + 17: 0, # 'ק' + 7: 1, # 'ר' + 10: 1, # 'ש' + 5: 1, # 'ת' + 32: 0, # '–' + 52: 0, # '’' + 47: 0, # '“' + 46: 1, # '”' + 58: 0, # '†' + 40: 2, # '…' + }, +} + +# 255: Undefined characters that did not exist in training text # 254: Carriage/Return # 253: symbol (punctuation) that does not belong to word # 252: 0 - 9 +# 251: Control characters -# Windows-1255 language model -# Character Mapping Table: -WIN1255_CHAR_TO_ORDER_MAP = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253, 69, 91, 79, 80, 92, 89, 97, 90, 68,111,112, 82, 73, 95, 85, # 40 - 78,121, 86, 71, 67,102,107, 84,114,103,115,253,253,253,253,253, # 50 -253, 50, 74, 60, 61, 42, 76, 70, 64, 53,105, 93, 56, 65, 54, 49, # 60 - 66,110, 51, 43, 44, 63, 81, 77, 98, 75,108,253,253,253,253,253, # 70 -124,202,203,204,205, 40, 58,206,207,208,209,210,211,212,213,214, -215, 83, 52, 47, 46, 72, 32, 94,216,113,217,109,218,219,220,221, - 34,116,222,118,100,223,224,117,119,104,125,225,226, 87, 99,227, -106,122,123,228, 55,229,230,101,231,232,120,233, 48, 39, 57,234, - 30, 59, 41, 88, 33, 37, 36, 31, 29, 35,235, 62, 28,236,126,237, -238, 38, 45,239,240,241,242,243,127,244,245,246,247,248,249,250, - 9, 8, 20, 16, 3, 2, 24, 14, 22, 1, 25, 15, 4, 11, 6, 23, - 12, 19, 13, 26, 18, 27, 21, 17, 7, 10, 5,251,252,128, 96,253, -) - -# Model Table: -# total sequences: 100% -# first 512 sequences: 98.4004% -# first 1024 sequences: 1.5981% -# rest sequences: 0.087% -# negative sequences: 0.0015% -HEBREW_LANG_MODEL = ( -0,3,3,3,3,3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,3,3,3,2,3,2,1,2,0,1,0,0, -3,0,3,1,0,0,1,3,2,0,1,1,2,0,2,2,2,1,1,1,1,2,1,1,1,2,0,0,2,2,0,1, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,2, -1,2,1,2,1,2,0,0,2,0,0,0,0,0,1,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2, -1,2,1,3,1,1,0,0,2,0,0,0,1,0,1,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,0,1,2,2,1,3, -1,2,1,1,2,2,0,0,2,2,0,0,0,0,1,0,1,0,0,0,1,0,0,0,0,0,0,1,0,1,1,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,2,2,2,2,3,2, -1,2,1,2,2,2,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,2,3,2,2,3,2,2,2,1,2,2,2,2, -1,2,1,1,2,2,0,1,2,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,0,2,2,2,2,2, -0,2,0,2,2,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,0,2,2,2, -0,2,1,2,2,2,0,0,2,1,0,0,0,0,1,0,1,0,0,0,0,0,0,2,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,3,3,3,3,3,2,1,2,3,2,2,2, -1,2,1,2,2,2,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,1,0, -3,3,3,3,3,3,3,3,3,2,3,3,3,2,3,3,3,3,3,3,3,3,3,3,3,3,3,1,0,2,0,2, -0,2,1,2,2,2,0,0,1,2,0,0,0,0,1,0,1,0,0,0,0,0,0,1,0,0,0,2,0,0,1,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,2,3,2,2,3,2,1,2,1,1,1, -0,1,1,1,1,1,3,0,1,0,0,0,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, -3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,1,1,0,0,1,0,0,1,0,0,0,0, -0,0,1,0,0,0,0,0,2,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,2,2,2,2, -0,2,0,1,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,3,2,3,3,3,2,1,2,3,3,2,3,3,3,3,2,3,2,1,2,0,2,1,2, -0,2,0,2,2,2,0,0,1,2,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0, -3,3,3,3,3,3,3,3,3,2,3,3,3,1,2,2,3,3,2,3,2,3,2,2,3,1,2,2,0,2,2,2, -0,2,1,2,2,2,0,0,1,2,0,0,0,0,1,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,2,3,3,2,2,2,3,3,3,3,1,3,2,2,2, -0,2,0,1,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,3,3,3,2,3,2,2,2,1,2,2,0,2,2,2,2, -0,2,0,2,2,2,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,1,3,2,3,3,2,3,3,2,2,1,2,2,2,2,2,2, -0,2,1,2,1,2,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,2,3,2,3,3,2,3,3,3,3,2,3,2,3,3,3,3,3,2,2,2,2,2,2,2,1, -0,2,0,1,2,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,3,2,1,2,3,3,3,3,3,3,3,2,3,2,3,2,1,2,3,0,2,1,2,2, -0,2,1,1,2,1,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,2,0, -3,3,3,3,3,3,3,3,3,2,3,3,3,3,2,1,3,1,2,2,2,1,2,3,3,1,2,1,2,2,2,2, -0,1,1,1,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,0,2,3,3,3,1,3,3,3,1,2,2,2,2,1,1,2,2,2,2,2,2, -0,2,0,1,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,2,3,3,3,2,2,3,3,3,2,1,2,3,2,3,2,2,2,2,1,2,1,1,1,2,2, -0,2,1,1,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, -3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,1,0,0,0,0,0, -1,0,1,0,0,0,0,0,2,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,2,3,3,2,3,1,2,2,2,2,3,2,3,1,1,2,2,1,2,2,1,1,0,2,2,2,2, -0,1,0,1,2,2,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, -3,0,0,1,1,0,1,0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,2,0, -0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,0,1,0,1,0,1,1,0,1,1,0,0,0,1,1,0,1,1,1,0,0,0,0,0,0,1,0,0,0,0,0, -0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,0,0,0,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, -3,2,2,1,2,2,2,2,2,2,2,1,2,2,1,2,2,1,1,1,1,1,1,1,1,2,1,1,0,3,3,3, -0,3,0,2,2,2,2,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, -2,2,2,3,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,1,2,2,2,1,1,1,2,0,1, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,0,2,2,0,0,0,0,0,0, -0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,3,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,1,0,2,1,0, -0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0, -0,3,1,1,2,2,2,2,2,1,2,2,2,1,1,2,2,2,2,2,2,2,1,2,2,1,0,1,1,1,1,0, -0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,2,1,1,1,1,2,1,1,2,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0, -0,0,2,0,0,0,0,0,0,0,0,1,1,0,0,0,0,1,1,0,0,1,1,0,0,0,0,0,0,1,0,0, -2,1,1,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,1,2,1,2,1,1,1,1,0,0,0,0, -0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,2,1,2,2,2,2,2,2,2,2,2,2,1,2,1,2,1,1,2,1,1,1,2,1,2,1,2,0,1,0,1, -0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,1,2,2,2,1,2,2,2,2,2,2,2,2,1,2,1,1,1,1,1,1,2,1,2,1,1,0,1,0,1, -0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,1,2,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2, -0,2,0,1,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, -3,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,1,1,1,1,1,1,1,0,1,1,0,1,0,0,1,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,2,0,1,1,1,0,1,0,0,0,1,1,0,1,1,0,0,0,0,0,1,1,0,0, -0,1,1,1,2,1,2,2,2,0,2,0,2,0,1,1,2,1,1,1,1,2,1,0,1,1,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,0,1,0,0,0,0,0,1,0,1,2,2,0,1,0,0,1,1,2,2,1,2,0,2,0,0,0,1,2,0,1, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,2,0,2,1,2,0,2,0,0,1,1,1,1,1,1,0,1,0,0,0,1,0,0,1, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,1,0,0,0,0,0,1,0,2,1,1,0,1,0,0,1,1,1,2,2,0,0,1,0,0,0,1,0,0,1, -1,1,2,1,0,1,1,1,0,1,0,1,1,1,1,0,0,0,1,0,1,0,0,0,0,0,0,0,0,2,2,1, -0,2,0,1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,1,0,0,1,0,1,1,1,1,0,0,0,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,1,1,1,1,1,1,1,1,2,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,1,1,1,0,1,1,0,1,0,0,0,1,1,0,1, -2,0,1,0,1,0,1,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,1,0,1,1,1,0,1,0,0,1,1,2,1,1,2,0,1,0,0,0,1,1,0,1, -1,0,0,1,0,0,1,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,1,0,1,1,2,0,1,0,0,0,0,2,1,1,2,0,2,0,0,0,1,1,0,1, -1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,1,0,2,1,1,0,1,0,0,2,2,1,2,1,1,0,1,0,0,0,1,1,0,1, -2,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,1,2,2,0,0,0,0,0,1,1,0,1,0,0,1,0,0,0,0,1,0,1, -1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,1,2,2,0,0,0,0,2,1,1,1,0,2,1,1,0,0,0,2,1,0,1, -1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,1,0,1,1,2,0,1,0,0,1,1,0,2,1,1,0,1,0,0,0,1,1,0,1, -2,2,1,1,1,0,1,1,0,1,1,0,1,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,1,0,2,1,1,0,1,0,0,1,1,0,1,2,1,0,2,0,0,0,1,1,0,1, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0, -0,1,0,0,2,0,2,1,1,0,1,0,1,0,0,1,0,0,0,0,1,0,0,0,1,0,0,0,0,0,1,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,1,0,1,1,2,0,1,0,0,1,1,1,0,1,0,0,1,0,0,0,1,0,0,1, -1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,0,0,0,0,0,0,0,1,0,1,1,0,0,1,0,0,2,1,1,1,1,1,0,1,0,0,0,0,1,0,1, -0,1,1,1,2,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,1,2,1,0,0,0,0,0,1,1,1,1,1,0,1,0,0,0,1,1,0,0, -) - -Win1255HebrewModel = { - 'char_to_order_map': WIN1255_CHAR_TO_ORDER_MAP, - 'precedence_matrix': HEBREW_LANG_MODEL, - 'typical_positive_ratio': 0.984004, - 'keep_english_letter': False, - 'charset_name': "windows-1255", - 'language': 'Hebrew', +# Character Mapping Table(s): +WINDOWS_1255_HEBREW_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 69, # 'A' + 66: 91, # 'B' + 67: 79, # 'C' + 68: 80, # 'D' + 69: 92, # 'E' + 70: 89, # 'F' + 71: 97, # 'G' + 72: 90, # 'H' + 73: 68, # 'I' + 74: 111, # 'J' + 75: 112, # 'K' + 76: 82, # 'L' + 77: 73, # 'M' + 78: 95, # 'N' + 79: 85, # 'O' + 80: 78, # 'P' + 81: 121, # 'Q' + 82: 86, # 'R' + 83: 71, # 'S' + 84: 67, # 'T' + 85: 102, # 'U' + 86: 107, # 'V' + 87: 84, # 'W' + 88: 114, # 'X' + 89: 103, # 'Y' + 90: 115, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 50, # 'a' + 98: 74, # 'b' + 99: 60, # 'c' + 100: 61, # 'd' + 101: 42, # 'e' + 102: 76, # 'f' + 103: 70, # 'g' + 104: 64, # 'h' + 105: 53, # 'i' + 106: 105, # 'j' + 107: 93, # 'k' + 108: 56, # 'l' + 109: 65, # 'm' + 110: 54, # 'n' + 111: 49, # 'o' + 112: 66, # 'p' + 113: 110, # 'q' + 114: 51, # 'r' + 115: 43, # 's' + 116: 44, # 't' + 117: 63, # 'u' + 118: 81, # 'v' + 119: 77, # 'w' + 120: 98, # 'x' + 121: 75, # 'y' + 122: 108, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 124, # '€' + 129: 202, # None + 130: 203, # '‚' + 131: 204, # 'ƒ' + 132: 205, # '„' + 133: 40, # '…' + 134: 58, # '†' + 135: 206, # '‡' + 136: 207, # 'ˆ' + 137: 208, # '‰' + 138: 209, # None + 139: 210, # '‹' + 140: 211, # None + 141: 212, # None + 142: 213, # None + 143: 214, # None + 144: 215, # None + 145: 83, # '‘' + 146: 52, # '’' + 147: 47, # '“' + 148: 46, # '”' + 149: 72, # '•' + 150: 32, # '–' + 151: 94, # '—' + 152: 216, # '˜' + 153: 113, # '™' + 154: 217, # None + 155: 109, # '›' + 156: 218, # None + 157: 219, # None + 158: 220, # None + 159: 221, # None + 160: 34, # '\xa0' + 161: 116, # '¡' + 162: 222, # '¢' + 163: 118, # '£' + 164: 100, # '₪' + 165: 223, # '¥' + 166: 224, # '¦' + 167: 117, # '§' + 168: 119, # '¨' + 169: 104, # '©' + 170: 125, # '×' + 171: 225, # '«' + 172: 226, # '¬' + 173: 87, # '\xad' + 174: 99, # '®' + 175: 227, # '¯' + 176: 106, # '°' + 177: 122, # '±' + 178: 123, # '²' + 179: 228, # '³' + 180: 55, # '´' + 181: 229, # 'µ' + 182: 230, # '¶' + 183: 101, # '·' + 184: 231, # '¸' + 185: 232, # '¹' + 186: 120, # '÷' + 187: 233, # '»' + 188: 48, # '¼' + 189: 39, # '½' + 190: 57, # '¾' + 191: 234, # '¿' + 192: 30, # 'ְ' + 193: 59, # 'ֱ' + 194: 41, # 'ֲ' + 195: 88, # 'ֳ' + 196: 33, # 'ִ' + 197: 37, # 'ֵ' + 198: 36, # 'ֶ' + 199: 31, # 'ַ' + 200: 29, # 'ָ' + 201: 35, # 'ֹ' + 202: 235, # None + 203: 62, # 'ֻ' + 204: 28, # 'ּ' + 205: 236, # 'ֽ' + 206: 126, # '־' + 207: 237, # 'ֿ' + 208: 238, # '׀' + 209: 38, # 'ׁ' + 210: 45, # 'ׂ' + 211: 239, # '׃' + 212: 240, # 'װ' + 213: 241, # 'ױ' + 214: 242, # 'ײ' + 215: 243, # '׳' + 216: 127, # '״' + 217: 244, # None + 218: 245, # None + 219: 246, # None + 220: 247, # None + 221: 248, # None + 222: 249, # None + 223: 250, # None + 224: 9, # 'א' + 225: 8, # 'ב' + 226: 20, # 'ג' + 227: 16, # 'ד' + 228: 3, # 'ה' + 229: 2, # 'ו' + 230: 24, # 'ז' + 231: 14, # 'ח' + 232: 22, # 'ט' + 233: 1, # 'י' + 234: 25, # 'ך' + 235: 15, # 'כ' + 236: 4, # 'ל' + 237: 11, # 'ם' + 238: 6, # 'מ' + 239: 23, # 'ן' + 240: 12, # 'נ' + 241: 19, # 'ס' + 242: 13, # 'ע' + 243: 26, # 'ף' + 244: 18, # 'פ' + 245: 27, # 'ץ' + 246: 21, # 'צ' + 247: 17, # 'ק' + 248: 7, # 'ר' + 249: 10, # 'ש' + 250: 5, # 'ת' + 251: 251, # None + 252: 252, # None + 253: 128, # '\u200e' + 254: 96, # '\u200f' + 255: 253, # None } + +WINDOWS_1255_HEBREW_MODEL = SingleByteCharSetModel(charset_name='windows-1255', + language='Hebrew', + char_to_order_map=WINDOWS_1255_HEBREW_CHAR_TO_ORDER, + language_model=HEBREW_LANG_MODEL, + typical_positive_ratio=0.984004, + keep_ascii_letters=False, + alphabet='אבגדהוזחטיךכלםמןנסעףפץצקרשתװױײ') + diff --git a/pipenv/patched/notpip/_vendor/chardet/langhungarianmodel.py b/pipenv/patched/notpip/_vendor/chardet/langhungarianmodel.py index bb7c095e..58f8efa7 100644 --- a/pipenv/patched/notpip/_vendor/chardet/langhungarianmodel.py +++ b/pipenv/patched/notpip/_vendor/chardet/langhungarianmodel.py @@ -1,225 +1,4650 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### +#!/usr/bin/env python +# -*- coding: utf-8 -*- -# 255: Control characters that usually does not exist in any text +from pipenv.patched.notpip._vendor.chardet.sbcharsetprober import SingleByteCharSetModel + + +# 3: Positive +# 2: Likely +# 1: Unlikely +# 0: Negative + +HUNGARIAN_LANG_MODEL = { + 28: { # 'A' + 28: 0, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 2, # 'D' + 32: 1, # 'E' + 50: 1, # 'F' + 49: 2, # 'G' + 38: 1, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 2, # 'K' + 41: 2, # 'L' + 34: 1, # 'M' + 35: 2, # 'N' + 47: 1, # 'O' + 46: 2, # 'P' + 43: 2, # 'R' + 33: 2, # 'S' + 37: 2, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 2, # 'Z' + 2: 0, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 2, # 'd' + 1: 1, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 1, # 'h' + 9: 1, # 'i' + 22: 1, # 'j' + 7: 2, # 'k' + 6: 2, # 'l' + 13: 2, # 'm' + 4: 2, # 'n' + 8: 0, # 'o' + 23: 2, # 'p' + 10: 2, # 'r' + 5: 1, # 's' + 3: 1, # 't' + 21: 1, # 'u' + 19: 1, # 'v' + 62: 1, # 'x' + 16: 0, # 'y' + 11: 3, # 'z' + 51: 1, # 'Á' + 44: 0, # 'É' + 61: 1, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 40: { # 'B' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 0, # 'M' + 35: 1, # 'N' + 47: 2, # 'O' + 46: 0, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 3, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 2, # 'i' + 22: 1, # 'j' + 7: 0, # 'k' + 6: 1, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 8: 2, # 'o' + 23: 1, # 'p' + 10: 2, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 3, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 0, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 2, # 'á' + 15: 2, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 1, # 'ő' + 56: 1, # 'ű' + }, + 54: { # 'C' + 28: 1, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 1, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 1, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 0, # 'N' + 47: 1, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 2, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 0, # 'V' + 55: 1, # 'Y' + 52: 1, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 1, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 1, # 'h' + 9: 1, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 1, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 8: 2, # 'o' + 23: 0, # 'p' + 10: 1, # 'r' + 5: 3, # 's' + 3: 0, # 't' + 21: 1, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 1, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 1, # 'á' + 15: 1, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 45: { # 'D' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 0, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 0, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 2, # 'O' + 46: 0, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 1, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 3, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 1, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 0, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 8: 1, # 'o' + 23: 0, # 'p' + 10: 2, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 2, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 1, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 1, # 'á' + 15: 1, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 1, # 'ő' + 56: 0, # 'ű' + }, + 32: { # 'E' + 28: 1, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 1, # 'E' + 50: 1, # 'F' + 49: 2, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 2, # 'K' + 41: 2, # 'L' + 34: 2, # 'M' + 35: 2, # 'N' + 47: 1, # 'O' + 46: 1, # 'P' + 43: 2, # 'R' + 33: 2, # 'S' + 37: 2, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 1, # 'Z' + 2: 1, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 2, # 'd' + 1: 1, # 'e' + 27: 1, # 'f' + 12: 3, # 'g' + 20: 1, # 'h' + 9: 1, # 'i' + 22: 1, # 'j' + 7: 1, # 'k' + 6: 2, # 'l' + 13: 2, # 'm' + 4: 2, # 'n' + 8: 0, # 'o' + 23: 1, # 'p' + 10: 2, # 'r' + 5: 2, # 's' + 3: 1, # 't' + 21: 2, # 'u' + 19: 1, # 'v' + 62: 1, # 'x' + 16: 0, # 'y' + 11: 3, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 0, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 0, # 'Ú' + 63: 1, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 1, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 50: { # 'F' + 28: 1, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 1, # 'E' + 50: 1, # 'F' + 49: 0, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 1, # 'O' + 46: 0, # 'P' + 43: 1, # 'R' + 33: 0, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 0, # 'V' + 55: 1, # 'Y' + 52: 0, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 2, # 'e' + 27: 1, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 2, # 'i' + 22: 1, # 'j' + 7: 0, # 'k' + 6: 1, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 8: 2, # 'o' + 23: 0, # 'p' + 10: 2, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 1, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 0, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 0, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 0, # 'Ú' + 63: 1, # 'Ü' + 14: 1, # 'á' + 15: 1, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 2, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 1, # 'ő' + 56: 1, # 'ű' + }, + 49: { # 'G' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 1, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 2, # 'Y' + 52: 1, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 2, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 1, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 1, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 8: 2, # 'o' + 23: 0, # 'p' + 10: 2, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 1, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 2, # 'y' + 11: 0, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 1, # 'á' + 15: 1, # 'é' + 30: 0, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 1, # 'ő' + 56: 0, # 'ű' + }, + 38: { # 'H' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 0, # 'D' + 32: 1, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 1, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 1, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 1, # 'O' + 46: 0, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 0, # 'V' + 55: 1, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 2, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 2, # 'i' + 22: 1, # 'j' + 7: 0, # 'k' + 6: 1, # 'l' + 13: 1, # 'm' + 4: 0, # 'n' + 8: 3, # 'o' + 23: 0, # 'p' + 10: 1, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 2, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 0, # 'z' + 51: 2, # 'Á' + 44: 2, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 2, # 'á' + 15: 1, # 'é' + 30: 2, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 1, # 'ő' + 56: 1, # 'ű' + }, + 39: { # 'I' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 1, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 2, # 'K' + 41: 2, # 'L' + 34: 1, # 'M' + 35: 2, # 'N' + 47: 1, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 2, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 2, # 'Z' + 2: 0, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 2, # 'd' + 1: 0, # 'e' + 27: 1, # 'f' + 12: 2, # 'g' + 20: 1, # 'h' + 9: 0, # 'i' + 22: 1, # 'j' + 7: 1, # 'k' + 6: 2, # 'l' + 13: 2, # 'm' + 4: 1, # 'n' + 8: 0, # 'o' + 23: 1, # 'p' + 10: 2, # 'r' + 5: 2, # 's' + 3: 2, # 't' + 21: 0, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 1, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 0, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 53: { # 'J' + 28: 2, # 'A' + 40: 0, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 1, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 1, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 2, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 1, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 0, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 8: 1, # 'o' + 23: 0, # 'p' + 10: 0, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 2, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 0, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 0, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 2, # 'á' + 15: 1, # 'é' + 30: 0, # 'í' + 25: 2, # 'ó' + 24: 2, # 'ö' + 31: 1, # 'ú' + 29: 0, # 'ü' + 42: 1, # 'ő' + 56: 0, # 'ű' + }, + 36: { # 'K' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 0, # 'G' + 38: 1, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 2, # 'O' + 46: 0, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 0, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 2, # 'e' + 27: 1, # 'f' + 12: 0, # 'g' + 20: 1, # 'h' + 9: 3, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 1, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 8: 2, # 'o' + 23: 0, # 'p' + 10: 2, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 1, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 0, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 2, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 2, # 'á' + 15: 2, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 2, # 'ö' + 31: 1, # 'ú' + 29: 2, # 'ü' + 42: 1, # 'ő' + 56: 0, # 'ű' + }, + 41: { # 'L' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 2, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 2, # 'O' + 46: 0, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 2, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 1, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 3, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 2, # 'i' + 22: 1, # 'j' + 7: 0, # 'k' + 6: 1, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 8: 2, # 'o' + 23: 0, # 'p' + 10: 0, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 2, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 0, # 'z' + 51: 2, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 2, # 'á' + 15: 1, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 0, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 34: { # 'M' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 0, # 'G' + 38: 1, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 1, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 1, # 'Z' + 2: 3, # 'a' + 18: 0, # 'b' + 26: 1, # 'c' + 17: 0, # 'd' + 1: 3, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 3, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 0, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 8: 3, # 'o' + 23: 0, # 'p' + 10: 1, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 2, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 0, # 'z' + 51: 2, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 2, # 'á' + 15: 2, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 1, # 'ű' + }, + 35: { # 'N' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 2, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 1, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 2, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 2, # 'Y' + 52: 1, # 'Z' + 2: 3, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 3, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 2, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 0, # 'l' + 13: 0, # 'm' + 4: 1, # 'n' + 8: 2, # 'o' + 23: 0, # 'p' + 10: 0, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 1, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 2, # 'y' + 11: 0, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 1, # 'á' + 15: 2, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 1, # 'ő' + 56: 0, # 'ű' + }, + 47: { # 'O' + 28: 1, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 1, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 2, # 'K' + 41: 2, # 'L' + 34: 2, # 'M' + 35: 2, # 'N' + 47: 1, # 'O' + 46: 1, # 'P' + 43: 2, # 'R' + 33: 2, # 'S' + 37: 2, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 1, # 'Z' + 2: 0, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 1, # 'd' + 1: 1, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 1, # 'h' + 9: 1, # 'i' + 22: 1, # 'j' + 7: 2, # 'k' + 6: 2, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 8: 1, # 'o' + 23: 1, # 'p' + 10: 2, # 'r' + 5: 1, # 's' + 3: 2, # 't' + 21: 1, # 'u' + 19: 0, # 'v' + 62: 1, # 'x' + 16: 0, # 'y' + 11: 1, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 0, # 'Í' + 58: 1, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 46: { # 'P' + 28: 1, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 1, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 0, # 'M' + 35: 1, # 'N' + 47: 1, # 'O' + 46: 1, # 'P' + 43: 2, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 1, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 2, # 'e' + 27: 1, # 'f' + 12: 0, # 'g' + 20: 1, # 'h' + 9: 2, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 1, # 'l' + 13: 0, # 'm' + 4: 1, # 'n' + 8: 2, # 'o' + 23: 0, # 'p' + 10: 2, # 'r' + 5: 1, # 's' + 3: 0, # 't' + 21: 1, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 0, # 'z' + 51: 2, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 0, # 'Ú' + 63: 1, # 'Ü' + 14: 3, # 'á' + 15: 2, # 'é' + 30: 0, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 0, # 'ú' + 29: 1, # 'ü' + 42: 1, # 'ő' + 56: 0, # 'ű' + }, + 43: { # 'R' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 2, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 2, # 'S' + 37: 2, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 1, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 2, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 1, # 'h' + 9: 2, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 0, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 8: 2, # 'o' + 23: 0, # 'p' + 10: 0, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 1, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 0, # 'z' + 51: 2, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 2, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 2, # 'á' + 15: 2, # 'é' + 30: 1, # 'í' + 25: 2, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 33: { # 'S' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 2, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 2, # 'S' + 37: 2, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 3, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 1, # 'c' + 17: 0, # 'd' + 1: 2, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 1, # 'h' + 9: 2, # 'i' + 22: 0, # 'j' + 7: 1, # 'k' + 6: 1, # 'l' + 13: 1, # 'm' + 4: 0, # 'n' + 8: 2, # 'o' + 23: 1, # 'p' + 10: 0, # 'r' + 5: 0, # 's' + 3: 1, # 't' + 21: 1, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 3, # 'z' + 51: 2, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 2, # 'á' + 15: 1, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 1, # 'ő' + 56: 1, # 'ű' + }, + 37: { # 'T' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 2, # 'O' + 46: 1, # 'P' + 43: 2, # 'R' + 33: 1, # 'S' + 37: 2, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 1, # 'Z' + 2: 2, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 2, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 1, # 'h' + 9: 2, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 0, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 8: 2, # 'o' + 23: 0, # 'p' + 10: 1, # 'r' + 5: 1, # 's' + 3: 0, # 't' + 21: 2, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 1, # 'z' + 51: 2, # 'Á' + 44: 2, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 2, # 'á' + 15: 1, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 2, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 1, # 'ő' + 56: 1, # 'ű' + }, + 57: { # 'U' + 28: 1, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 1, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 1, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 2, # 'S' + 37: 1, # 'T' + 57: 0, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 1, # 'Z' + 2: 0, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 1, # 'd' + 1: 1, # 'e' + 27: 0, # 'f' + 12: 2, # 'g' + 20: 0, # 'h' + 9: 0, # 'i' + 22: 1, # 'j' + 7: 1, # 'k' + 6: 1, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 8: 0, # 'o' + 23: 1, # 'p' + 10: 1, # 'r' + 5: 1, # 's' + 3: 1, # 't' + 21: 0, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 1, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 1, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 48: { # 'V' + 28: 2, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 0, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 1, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 2, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 2, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 1, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 8: 2, # 'o' + 23: 0, # 'p' + 10: 0, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 1, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 0, # 'z' + 51: 2, # 'Á' + 44: 2, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 0, # 'Ú' + 63: 1, # 'Ü' + 14: 2, # 'á' + 15: 2, # 'é' + 30: 1, # 'í' + 25: 0, # 'ó' + 24: 1, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 55: { # 'Y' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 1, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 2, # 'Z' + 2: 1, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 1, # 'd' + 1: 1, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 0, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 0, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 8: 1, # 'o' + 23: 1, # 'p' + 10: 0, # 'r' + 5: 0, # 's' + 3: 0, # 't' + 21: 0, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 0, # 'z' + 51: 1, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 52: { # 'Z' + 28: 2, # 'A' + 40: 1, # 'B' + 54: 0, # 'C' + 45: 1, # 'D' + 32: 2, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 2, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 2, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 2, # 'S' + 37: 1, # 'T' + 57: 1, # 'U' + 48: 1, # 'V' + 55: 1, # 'Y' + 52: 1, # 'Z' + 2: 1, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 1, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 1, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 0, # 'l' + 13: 0, # 'm' + 4: 1, # 'n' + 8: 1, # 'o' + 23: 0, # 'p' + 10: 1, # 'r' + 5: 2, # 's' + 3: 0, # 't' + 21: 1, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 0, # 'z' + 51: 2, # 'Á' + 44: 1, # 'É' + 61: 1, # 'Í' + 58: 1, # 'Ó' + 59: 1, # 'Ö' + 60: 1, # 'Ú' + 63: 1, # 'Ü' + 14: 1, # 'á' + 15: 1, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 2: { # 'a' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 1, # 'a' + 18: 3, # 'b' + 26: 3, # 'c' + 17: 3, # 'd' + 1: 2, # 'e' + 27: 2, # 'f' + 12: 3, # 'g' + 20: 3, # 'h' + 9: 3, # 'i' + 22: 3, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 2, # 'o' + 23: 3, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 3, # 'v' + 62: 1, # 'x' + 16: 2, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 1, # 'á' + 15: 1, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 18: { # 'b' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 3, # 'b' + 26: 1, # 'c' + 17: 1, # 'd' + 1: 3, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 1, # 'h' + 9: 3, # 'i' + 22: 2, # 'j' + 7: 2, # 'k' + 6: 2, # 'l' + 13: 1, # 'm' + 4: 2, # 'n' + 8: 3, # 'o' + 23: 1, # 'p' + 10: 3, # 'r' + 5: 2, # 's' + 3: 1, # 't' + 21: 3, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 1, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 2, # 'í' + 25: 3, # 'ó' + 24: 2, # 'ö' + 31: 2, # 'ú' + 29: 2, # 'ü' + 42: 2, # 'ő' + 56: 1, # 'ű' + }, + 26: { # 'c' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 1, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 1, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 2, # 'a' + 18: 1, # 'b' + 26: 2, # 'c' + 17: 1, # 'd' + 1: 3, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 3, # 'h' + 9: 3, # 'i' + 22: 1, # 'j' + 7: 2, # 'k' + 6: 1, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 8: 3, # 'o' + 23: 1, # 'p' + 10: 2, # 'r' + 5: 3, # 's' + 3: 2, # 't' + 21: 2, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 2, # 'á' + 15: 2, # 'é' + 30: 2, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 17: { # 'd' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 2, # 'b' + 26: 1, # 'c' + 17: 2, # 'd' + 1: 3, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 2, # 'h' + 9: 3, # 'i' + 22: 3, # 'j' + 7: 2, # 'k' + 6: 1, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 8: 3, # 'o' + 23: 1, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 3, # 'v' + 62: 0, # 'x' + 16: 2, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 3, # 'í' + 25: 3, # 'ó' + 24: 3, # 'ö' + 31: 2, # 'ú' + 29: 2, # 'ü' + 42: 2, # 'ő' + 56: 1, # 'ű' + }, + 1: { # 'e' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 2, # 'a' + 18: 3, # 'b' + 26: 3, # 'c' + 17: 3, # 'd' + 1: 2, # 'e' + 27: 3, # 'f' + 12: 3, # 'g' + 20: 3, # 'h' + 9: 3, # 'i' + 22: 3, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 2, # 'o' + 23: 3, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 2, # 'u' + 19: 3, # 'v' + 62: 2, # 'x' + 16: 2, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 1, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 27: { # 'f' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 1, # 'd' + 1: 3, # 'e' + 27: 2, # 'f' + 12: 1, # 'g' + 20: 1, # 'h' + 9: 3, # 'i' + 22: 2, # 'j' + 7: 1, # 'k' + 6: 1, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 8: 3, # 'o' + 23: 0, # 'p' + 10: 3, # 'r' + 5: 1, # 's' + 3: 1, # 't' + 21: 2, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 0, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 3, # 'ö' + 31: 1, # 'ú' + 29: 2, # 'ü' + 42: 1, # 'ő' + 56: 1, # 'ű' + }, + 12: { # 'g' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 3, # 'b' + 26: 2, # 'c' + 17: 2, # 'd' + 1: 3, # 'e' + 27: 2, # 'f' + 12: 3, # 'g' + 20: 3, # 'h' + 9: 3, # 'i' + 22: 3, # 'j' + 7: 2, # 'k' + 6: 3, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 8: 3, # 'o' + 23: 1, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 3, # 'v' + 62: 0, # 'x' + 16: 3, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 2, # 'í' + 25: 3, # 'ó' + 24: 2, # 'ö' + 31: 2, # 'ú' + 29: 2, # 'ü' + 42: 2, # 'ő' + 56: 1, # 'ű' + }, + 20: { # 'h' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 0, # 'd' + 1: 3, # 'e' + 27: 0, # 'f' + 12: 1, # 'g' + 20: 2, # 'h' + 9: 3, # 'i' + 22: 1, # 'j' + 7: 1, # 'k' + 6: 1, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 8: 3, # 'o' + 23: 0, # 'p' + 10: 1, # 'r' + 5: 2, # 's' + 3: 1, # 't' + 21: 3, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 2, # 'y' + 11: 0, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 3, # 'í' + 25: 2, # 'ó' + 24: 2, # 'ö' + 31: 2, # 'ú' + 29: 1, # 'ü' + 42: 1, # 'ő' + 56: 1, # 'ű' + }, + 9: { # 'i' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 3, # 'b' + 26: 3, # 'c' + 17: 3, # 'd' + 1: 3, # 'e' + 27: 3, # 'f' + 12: 3, # 'g' + 20: 3, # 'h' + 9: 2, # 'i' + 22: 2, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 2, # 'o' + 23: 2, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 3, # 'v' + 62: 1, # 'x' + 16: 1, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 2, # 'é' + 30: 1, # 'í' + 25: 3, # 'ó' + 24: 1, # 'ö' + 31: 2, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 1, # 'ű' + }, + 22: { # 'j' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 2, # 'b' + 26: 1, # 'c' + 17: 3, # 'd' + 1: 3, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 2, # 'h' + 9: 1, # 'i' + 22: 2, # 'j' + 7: 2, # 'k' + 6: 2, # 'l' + 13: 1, # 'm' + 4: 2, # 'n' + 8: 3, # 'o' + 23: 1, # 'p' + 10: 2, # 'r' + 5: 2, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 1, # 'í' + 25: 3, # 'ó' + 24: 3, # 'ö' + 31: 3, # 'ú' + 29: 2, # 'ü' + 42: 1, # 'ő' + 56: 1, # 'ű' + }, + 7: { # 'k' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 3, # 'b' + 26: 2, # 'c' + 17: 1, # 'd' + 1: 3, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 2, # 'h' + 9: 3, # 'i' + 22: 2, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 1, # 'm' + 4: 3, # 'n' + 8: 3, # 'o' + 23: 1, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 2, # 'v' + 62: 0, # 'x' + 16: 2, # 'y' + 11: 1, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 3, # 'í' + 25: 2, # 'ó' + 24: 3, # 'ö' + 31: 1, # 'ú' + 29: 3, # 'ü' + 42: 1, # 'ő' + 56: 1, # 'ű' + }, + 6: { # 'l' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 1, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 1, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 2, # 'b' + 26: 3, # 'c' + 17: 3, # 'd' + 1: 3, # 'e' + 27: 3, # 'f' + 12: 3, # 'g' + 20: 3, # 'h' + 9: 3, # 'i' + 22: 3, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 3, # 'o' + 23: 2, # 'p' + 10: 2, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 3, # 'v' + 62: 0, # 'x' + 16: 3, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 3, # 'í' + 25: 3, # 'ó' + 24: 3, # 'ö' + 31: 2, # 'ú' + 29: 2, # 'ü' + 42: 3, # 'ő' + 56: 1, # 'ű' + }, + 13: { # 'm' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 3, # 'b' + 26: 2, # 'c' + 17: 1, # 'd' + 1: 3, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 2, # 'h' + 9: 3, # 'i' + 22: 2, # 'j' + 7: 1, # 'k' + 6: 3, # 'l' + 13: 3, # 'm' + 4: 2, # 'n' + 8: 3, # 'o' + 23: 3, # 'p' + 10: 2, # 'r' + 5: 2, # 's' + 3: 2, # 't' + 21: 3, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 2, # 'í' + 25: 2, # 'ó' + 24: 2, # 'ö' + 31: 2, # 'ú' + 29: 2, # 'ü' + 42: 1, # 'ő' + 56: 2, # 'ű' + }, + 4: { # 'n' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 3, # 'b' + 26: 3, # 'c' + 17: 3, # 'd' + 1: 3, # 'e' + 27: 2, # 'f' + 12: 3, # 'g' + 20: 3, # 'h' + 9: 3, # 'i' + 22: 2, # 'j' + 7: 3, # 'k' + 6: 2, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 8: 3, # 'o' + 23: 2, # 'p' + 10: 2, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 2, # 'v' + 62: 1, # 'x' + 16: 3, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 2, # 'í' + 25: 2, # 'ó' + 24: 3, # 'ö' + 31: 2, # 'ú' + 29: 3, # 'ü' + 42: 2, # 'ő' + 56: 1, # 'ű' + }, + 8: { # 'o' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 1, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 2, # 'a' + 18: 3, # 'b' + 26: 3, # 'c' + 17: 3, # 'd' + 1: 2, # 'e' + 27: 2, # 'f' + 12: 3, # 'g' + 20: 3, # 'h' + 9: 2, # 'i' + 22: 2, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 1, # 'o' + 23: 3, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 2, # 'u' + 19: 3, # 'v' + 62: 1, # 'x' + 16: 1, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 1, # 'á' + 15: 2, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 23: { # 'p' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 1, # 'b' + 26: 2, # 'c' + 17: 1, # 'd' + 1: 3, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 2, # 'h' + 9: 3, # 'i' + 22: 2, # 'j' + 7: 2, # 'k' + 6: 3, # 'l' + 13: 1, # 'm' + 4: 2, # 'n' + 8: 3, # 'o' + 23: 3, # 'p' + 10: 3, # 'r' + 5: 2, # 's' + 3: 2, # 't' + 21: 3, # 'u' + 19: 2, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 2, # 'í' + 25: 2, # 'ó' + 24: 2, # 'ö' + 31: 1, # 'ú' + 29: 2, # 'ü' + 42: 1, # 'ő' + 56: 1, # 'ű' + }, + 10: { # 'r' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 3, # 'b' + 26: 3, # 'c' + 17: 3, # 'd' + 1: 3, # 'e' + 27: 2, # 'f' + 12: 3, # 'g' + 20: 2, # 'h' + 9: 3, # 'i' + 22: 3, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 3, # 'o' + 23: 2, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 3, # 'v' + 62: 1, # 'x' + 16: 2, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 2, # 'í' + 25: 3, # 'ó' + 24: 3, # 'ö' + 31: 3, # 'ú' + 29: 3, # 'ü' + 42: 2, # 'ő' + 56: 2, # 'ű' + }, + 5: { # 's' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 3, # 'b' + 26: 2, # 'c' + 17: 2, # 'd' + 1: 3, # 'e' + 27: 2, # 'f' + 12: 2, # 'g' + 20: 2, # 'h' + 9: 3, # 'i' + 22: 1, # 'j' + 7: 3, # 'k' + 6: 2, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 3, # 'o' + 23: 2, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 2, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 3, # 'í' + 25: 3, # 'ó' + 24: 3, # 'ö' + 31: 3, # 'ú' + 29: 3, # 'ü' + 42: 2, # 'ő' + 56: 1, # 'ű' + }, + 3: { # 't' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 3, # 'b' + 26: 2, # 'c' + 17: 1, # 'd' + 1: 3, # 'e' + 27: 2, # 'f' + 12: 1, # 'g' + 20: 3, # 'h' + 9: 3, # 'i' + 22: 3, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 8: 3, # 'o' + 23: 1, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 3, # 'v' + 62: 0, # 'x' + 16: 3, # 'y' + 11: 1, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 2, # 'í' + 25: 3, # 'ó' + 24: 3, # 'ö' + 31: 3, # 'ú' + 29: 3, # 'ü' + 42: 3, # 'ő' + 56: 2, # 'ű' + }, + 21: { # 'u' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 1, # 'a' + 18: 2, # 'b' + 26: 2, # 'c' + 17: 3, # 'd' + 1: 2, # 'e' + 27: 1, # 'f' + 12: 3, # 'g' + 20: 2, # 'h' + 9: 2, # 'i' + 22: 2, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 1, # 'o' + 23: 2, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 1, # 'u' + 19: 3, # 'v' + 62: 1, # 'x' + 16: 1, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 2, # 'á' + 15: 1, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 0, # 'ö' + 31: 1, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 19: { # 'v' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 2, # 'b' + 26: 1, # 'c' + 17: 1, # 'd' + 1: 3, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 1, # 'h' + 9: 3, # 'i' + 22: 1, # 'j' + 7: 1, # 'k' + 6: 1, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 8: 3, # 'o' + 23: 1, # 'p' + 10: 1, # 'r' + 5: 2, # 's' + 3: 2, # 't' + 21: 2, # 'u' + 19: 2, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 1, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 2, # 'í' + 25: 2, # 'ó' + 24: 2, # 'ö' + 31: 1, # 'ú' + 29: 2, # 'ü' + 42: 1, # 'ő' + 56: 1, # 'ű' + }, + 62: { # 'x' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 1, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 0, # 'd' + 1: 1, # 'e' + 27: 1, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 1, # 'i' + 22: 0, # 'j' + 7: 1, # 'k' + 6: 1, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 8: 1, # 'o' + 23: 1, # 'p' + 10: 1, # 'r' + 5: 1, # 's' + 3: 1, # 't' + 21: 1, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 0, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 1, # 'á' + 15: 1, # 'é' + 30: 1, # 'í' + 25: 1, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 16: { # 'y' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 2, # 'b' + 26: 1, # 'c' + 17: 1, # 'd' + 1: 3, # 'e' + 27: 2, # 'f' + 12: 2, # 'g' + 20: 2, # 'h' + 9: 3, # 'i' + 22: 2, # 'j' + 7: 2, # 'k' + 6: 2, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 8: 3, # 'o' + 23: 2, # 'p' + 10: 2, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 3, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 2, # 'í' + 25: 2, # 'ó' + 24: 3, # 'ö' + 31: 2, # 'ú' + 29: 2, # 'ü' + 42: 1, # 'ő' + 56: 2, # 'ű' + }, + 11: { # 'z' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 3, # 'a' + 18: 2, # 'b' + 26: 1, # 'c' + 17: 3, # 'd' + 1: 3, # 'e' + 27: 1, # 'f' + 12: 2, # 'g' + 20: 2, # 'h' + 9: 3, # 'i' + 22: 1, # 'j' + 7: 3, # 'k' + 6: 2, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 3, # 'o' + 23: 1, # 'p' + 10: 2, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 3, # 'u' + 19: 2, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 3, # 'á' + 15: 3, # 'é' + 30: 3, # 'í' + 25: 3, # 'ó' + 24: 3, # 'ö' + 31: 2, # 'ú' + 29: 3, # 'ü' + 42: 2, # 'ő' + 56: 1, # 'ű' + }, + 51: { # 'Á' + 28: 0, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 0, # 'E' + 50: 1, # 'F' + 49: 2, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 2, # 'L' + 34: 1, # 'M' + 35: 2, # 'N' + 47: 0, # 'O' + 46: 1, # 'P' + 43: 2, # 'R' + 33: 2, # 'S' + 37: 1, # 'T' + 57: 0, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 1, # 'Z' + 2: 0, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 1, # 'd' + 1: 0, # 'e' + 27: 0, # 'f' + 12: 1, # 'g' + 20: 1, # 'h' + 9: 0, # 'i' + 22: 1, # 'j' + 7: 1, # 'k' + 6: 2, # 'l' + 13: 2, # 'm' + 4: 0, # 'n' + 8: 0, # 'o' + 23: 1, # 'p' + 10: 1, # 'r' + 5: 1, # 's' + 3: 1, # 't' + 21: 0, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 1, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 1, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 44: { # 'É' + 28: 0, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 1, # 'E' + 50: 0, # 'F' + 49: 2, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 2, # 'L' + 34: 1, # 'M' + 35: 2, # 'N' + 47: 0, # 'O' + 46: 1, # 'P' + 43: 2, # 'R' + 33: 2, # 'S' + 37: 2, # 'T' + 57: 0, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 1, # 'Z' + 2: 0, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 1, # 'd' + 1: 0, # 'e' + 27: 0, # 'f' + 12: 1, # 'g' + 20: 1, # 'h' + 9: 0, # 'i' + 22: 1, # 'j' + 7: 1, # 'k' + 6: 2, # 'l' + 13: 1, # 'm' + 4: 2, # 'n' + 8: 0, # 'o' + 23: 1, # 'p' + 10: 2, # 'r' + 5: 3, # 's' + 3: 1, # 't' + 21: 0, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 0, # 'z' + 51: 0, # 'Á' + 44: 1, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 61: { # 'Í' + 28: 0, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 0, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 1, # 'J' + 36: 0, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 0, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 0, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 1, # 'Z' + 2: 0, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 0, # 'e' + 27: 0, # 'f' + 12: 2, # 'g' + 20: 0, # 'h' + 9: 0, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 0, # 'l' + 13: 1, # 'm' + 4: 0, # 'n' + 8: 0, # 'o' + 23: 0, # 'p' + 10: 1, # 'r' + 5: 0, # 's' + 3: 1, # 't' + 21: 0, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 1, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 58: { # 'Ó' + 28: 1, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 0, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 1, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 2, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 0, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 0, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 1, # 'Z' + 2: 0, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 1, # 'd' + 1: 0, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 2, # 'h' + 9: 0, # 'i' + 22: 0, # 'j' + 7: 1, # 'k' + 6: 1, # 'l' + 13: 0, # 'm' + 4: 1, # 'n' + 8: 0, # 'o' + 23: 1, # 'p' + 10: 1, # 'r' + 5: 1, # 's' + 3: 0, # 't' + 21: 0, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 1, # 'z' + 51: 0, # 'Á' + 44: 1, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 59: { # 'Ö' + 28: 0, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 0, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 0, # 'O' + 46: 1, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 0, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 1, # 'Z' + 2: 0, # 'a' + 18: 0, # 'b' + 26: 1, # 'c' + 17: 1, # 'd' + 1: 0, # 'e' + 27: 0, # 'f' + 12: 0, # 'g' + 20: 0, # 'h' + 9: 0, # 'i' + 22: 0, # 'j' + 7: 1, # 'k' + 6: 1, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 8: 0, # 'o' + 23: 0, # 'p' + 10: 2, # 'r' + 5: 1, # 's' + 3: 1, # 't' + 21: 0, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 1, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 60: { # 'Ú' + 28: 0, # 'A' + 40: 1, # 'B' + 54: 1, # 'C' + 45: 1, # 'D' + 32: 0, # 'E' + 50: 1, # 'F' + 49: 1, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 0, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 1, # 'Z' + 2: 0, # 'a' + 18: 0, # 'b' + 26: 0, # 'c' + 17: 0, # 'd' + 1: 0, # 'e' + 27: 0, # 'f' + 12: 2, # 'g' + 20: 0, # 'h' + 9: 0, # 'i' + 22: 2, # 'j' + 7: 0, # 'k' + 6: 0, # 'l' + 13: 0, # 'm' + 4: 1, # 'n' + 8: 0, # 'o' + 23: 0, # 'p' + 10: 1, # 'r' + 5: 1, # 's' + 3: 1, # 't' + 21: 0, # 'u' + 19: 0, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 0, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 63: { # 'Ü' + 28: 0, # 'A' + 40: 1, # 'B' + 54: 0, # 'C' + 45: 1, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 1, # 'G' + 38: 1, # 'H' + 39: 0, # 'I' + 53: 1, # 'J' + 36: 1, # 'K' + 41: 1, # 'L' + 34: 1, # 'M' + 35: 1, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 1, # 'R' + 33: 1, # 'S' + 37: 1, # 'T' + 57: 0, # 'U' + 48: 1, # 'V' + 55: 0, # 'Y' + 52: 1, # 'Z' + 2: 0, # 'a' + 18: 1, # 'b' + 26: 0, # 'c' + 17: 1, # 'd' + 1: 0, # 'e' + 27: 0, # 'f' + 12: 1, # 'g' + 20: 0, # 'h' + 9: 0, # 'i' + 22: 0, # 'j' + 7: 0, # 'k' + 6: 1, # 'l' + 13: 0, # 'm' + 4: 1, # 'n' + 8: 0, # 'o' + 23: 0, # 'p' + 10: 1, # 'r' + 5: 1, # 's' + 3: 1, # 't' + 21: 0, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 1, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 14: { # 'á' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 1, # 'a' + 18: 3, # 'b' + 26: 3, # 'c' + 17: 3, # 'd' + 1: 1, # 'e' + 27: 2, # 'f' + 12: 3, # 'g' + 20: 2, # 'h' + 9: 2, # 'i' + 22: 3, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 1, # 'o' + 23: 2, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 2, # 'u' + 19: 3, # 'v' + 62: 0, # 'x' + 16: 1, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 1, # 'á' + 15: 2, # 'é' + 30: 1, # 'í' + 25: 0, # 'ó' + 24: 1, # 'ö' + 31: 0, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 15: { # 'é' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 1, # 'a' + 18: 3, # 'b' + 26: 2, # 'c' + 17: 3, # 'd' + 1: 1, # 'e' + 27: 1, # 'f' + 12: 3, # 'g' + 20: 3, # 'h' + 9: 2, # 'i' + 22: 2, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 1, # 'o' + 23: 3, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 0, # 'u' + 19: 3, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 1, # 'á' + 15: 1, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 30: { # 'í' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 0, # 'a' + 18: 1, # 'b' + 26: 2, # 'c' + 17: 1, # 'd' + 1: 0, # 'e' + 27: 1, # 'f' + 12: 3, # 'g' + 20: 0, # 'h' + 9: 0, # 'i' + 22: 1, # 'j' + 7: 1, # 'k' + 6: 2, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 8: 0, # 'o' + 23: 1, # 'p' + 10: 3, # 'r' + 5: 2, # 's' + 3: 3, # 't' + 21: 0, # 'u' + 19: 3, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 25: { # 'ó' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 2, # 'a' + 18: 3, # 'b' + 26: 2, # 'c' + 17: 3, # 'd' + 1: 1, # 'e' + 27: 2, # 'f' + 12: 2, # 'g' + 20: 2, # 'h' + 9: 2, # 'i' + 22: 2, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 8: 1, # 'o' + 23: 2, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 1, # 'u' + 19: 2, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 1, # 'á' + 15: 1, # 'é' + 30: 1, # 'í' + 25: 0, # 'ó' + 24: 1, # 'ö' + 31: 1, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 24: { # 'ö' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 0, # 'a' + 18: 3, # 'b' + 26: 1, # 'c' + 17: 2, # 'd' + 1: 0, # 'e' + 27: 1, # 'f' + 12: 2, # 'g' + 20: 1, # 'h' + 9: 0, # 'i' + 22: 1, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 8: 0, # 'o' + 23: 2, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 3, # 't' + 21: 0, # 'u' + 19: 3, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 3, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 31: { # 'ú' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 1, # 'a' + 18: 1, # 'b' + 26: 2, # 'c' + 17: 1, # 'd' + 1: 1, # 'e' + 27: 2, # 'f' + 12: 3, # 'g' + 20: 1, # 'h' + 9: 1, # 'i' + 22: 3, # 'j' + 7: 1, # 'k' + 6: 3, # 'l' + 13: 1, # 'm' + 4: 2, # 'n' + 8: 0, # 'o' + 23: 1, # 'p' + 10: 3, # 'r' + 5: 3, # 's' + 3: 2, # 't' + 21: 1, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 1, # 'á' + 15: 1, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 29: { # 'ü' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 1, # 'a' + 18: 1, # 'b' + 26: 1, # 'c' + 17: 2, # 'd' + 1: 1, # 'e' + 27: 1, # 'f' + 12: 3, # 'g' + 20: 2, # 'h' + 9: 1, # 'i' + 22: 1, # 'j' + 7: 3, # 'k' + 6: 3, # 'l' + 13: 1, # 'm' + 4: 3, # 'n' + 8: 0, # 'o' + 23: 1, # 'p' + 10: 2, # 'r' + 5: 2, # 's' + 3: 2, # 't' + 21: 0, # 'u' + 19: 2, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 1, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 42: { # 'ő' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 1, # 'a' + 18: 2, # 'b' + 26: 1, # 'c' + 17: 2, # 'd' + 1: 1, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 1, # 'h' + 9: 1, # 'i' + 22: 1, # 'j' + 7: 2, # 'k' + 6: 3, # 'l' + 13: 1, # 'm' + 4: 2, # 'n' + 8: 1, # 'o' + 23: 1, # 'p' + 10: 2, # 'r' + 5: 2, # 's' + 3: 2, # 't' + 21: 1, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 1, # 'é' + 30: 1, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 1, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, + 56: { # 'ű' + 28: 0, # 'A' + 40: 0, # 'B' + 54: 0, # 'C' + 45: 0, # 'D' + 32: 0, # 'E' + 50: 0, # 'F' + 49: 0, # 'G' + 38: 0, # 'H' + 39: 0, # 'I' + 53: 0, # 'J' + 36: 0, # 'K' + 41: 0, # 'L' + 34: 0, # 'M' + 35: 0, # 'N' + 47: 0, # 'O' + 46: 0, # 'P' + 43: 0, # 'R' + 33: 0, # 'S' + 37: 0, # 'T' + 57: 0, # 'U' + 48: 0, # 'V' + 55: 0, # 'Y' + 52: 0, # 'Z' + 2: 1, # 'a' + 18: 1, # 'b' + 26: 0, # 'c' + 17: 1, # 'd' + 1: 1, # 'e' + 27: 1, # 'f' + 12: 1, # 'g' + 20: 1, # 'h' + 9: 1, # 'i' + 22: 1, # 'j' + 7: 1, # 'k' + 6: 1, # 'l' + 13: 0, # 'm' + 4: 2, # 'n' + 8: 0, # 'o' + 23: 0, # 'p' + 10: 1, # 'r' + 5: 1, # 's' + 3: 1, # 't' + 21: 0, # 'u' + 19: 1, # 'v' + 62: 0, # 'x' + 16: 0, # 'y' + 11: 2, # 'z' + 51: 0, # 'Á' + 44: 0, # 'É' + 61: 0, # 'Í' + 58: 0, # 'Ó' + 59: 0, # 'Ö' + 60: 0, # 'Ú' + 63: 0, # 'Ü' + 14: 0, # 'á' + 15: 0, # 'é' + 30: 0, # 'í' + 25: 0, # 'ó' + 24: 0, # 'ö' + 31: 0, # 'ú' + 29: 0, # 'ü' + 42: 0, # 'ő' + 56: 0, # 'ű' + }, +} + +# 255: Undefined characters that did not exist in training text # 254: Carriage/Return # 253: symbol (punctuation) that does not belong to word # 252: 0 - 9 +# 251: Control characters -# Character Mapping Table: -Latin2_HungarianCharToOrderMap = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253, 28, 40, 54, 45, 32, 50, 49, 38, 39, 53, 36, 41, 34, 35, 47, - 46, 71, 43, 33, 37, 57, 48, 64, 68, 55, 52,253,253,253,253,253, -253, 2, 18, 26, 17, 1, 27, 12, 20, 9, 22, 7, 6, 13, 4, 8, - 23, 67, 10, 5, 3, 21, 19, 65, 62, 16, 11,253,253,253,253,253, -159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174, -175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190, -191,192,193,194,195,196,197, 75,198,199,200,201,202,203,204,205, - 79,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220, -221, 51, 81,222, 78,223,224,225,226, 44,227,228,229, 61,230,231, -232,233,234, 58,235, 66, 59,236,237,238, 60, 69, 63,239,240,241, - 82, 14, 74,242, 70, 80,243, 72,244, 15, 83, 77, 84, 30, 76, 85, -245,246,247, 25, 73, 42, 24,248,249,250, 31, 56, 29,251,252,253, -) - -win1250HungarianCharToOrderMap = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253, 28, 40, 54, 45, 32, 50, 49, 38, 39, 53, 36, 41, 34, 35, 47, - 46, 72, 43, 33, 37, 57, 48, 64, 68, 55, 52,253,253,253,253,253, -253, 2, 18, 26, 17, 1, 27, 12, 20, 9, 22, 7, 6, 13, 4, 8, - 23, 67, 10, 5, 3, 21, 19, 65, 62, 16, 11,253,253,253,253,253, -161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176, -177,178,179,180, 78,181, 69,182,183,184,185,186,187,188,189,190, -191,192,193,194,195,196,197, 76,198,199,200,201,202,203,204,205, - 81,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220, -221, 51, 83,222, 80,223,224,225,226, 44,227,228,229, 61,230,231, -232,233,234, 58,235, 66, 59,236,237,238, 60, 70, 63,239,240,241, - 84, 14, 75,242, 71, 82,243, 73,244, 15, 85, 79, 86, 30, 77, 87, -245,246,247, 25, 74, 42, 24,248,249,250, 31, 56, 29,251,252,253, -) - -# Model Table: -# total sequences: 100% -# first 512 sequences: 94.7368% -# first 1024 sequences:5.2623% -# rest sequences: 0.8894% -# negative sequences: 0.0009% -HungarianLangModel = ( -0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, -3,3,3,3,3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,2,2,3,3,1,1,2,2,2,2,2,1,2, -3,2,2,3,3,3,3,3,2,3,3,3,3,3,3,1,2,3,3,3,3,2,3,3,1,1,3,3,0,1,1,1, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0, -3,2,1,3,3,3,3,3,2,3,3,3,3,3,1,1,2,3,3,3,3,3,3,3,1,1,3,2,0,1,1,1, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,3,3,3,1,1,2,3,3,3,1,3,3,3,3,3,1,3,3,2,2,0,3,2,3, -0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0, -3,3,3,3,3,3,2,3,3,3,2,3,3,2,3,3,3,3,3,2,3,3,2,2,3,2,3,2,0,3,2,2, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0, -3,3,3,3,3,3,2,3,3,3,3,3,2,3,3,3,1,2,3,2,2,3,1,2,3,3,2,2,0,3,3,3, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,2,2,3,3,3,3,3,3,2,3,3,3,3,2,3,3,3,3,0,2,3,2, -0,0,0,1,1,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,3,1,1,1,3,3,2,1,3,2,2,3,2,1,3,2,2,1,0,3,3,1, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -3,2,2,3,3,3,3,3,1,2,3,3,3,3,1,2,1,3,3,3,3,2,2,3,1,1,3,2,0,1,1,1, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,2,2,3,3,3,3,3,2,1,3,3,3,3,3,2,2,1,3,3,3,0,1,1,2, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,2,3,3,2,3,3,3,2,0,3,2,3, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,1,0, -3,3,3,3,3,3,2,3,3,3,2,3,2,3,3,3,1,3,2,2,2,3,1,1,3,3,1,1,0,3,3,2, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,2,3,3,3,2,3,2,3,3,3,2,3,3,3,3,3,1,2,3,2,2,0,2,2,2, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -3,3,3,2,2,2,3,1,3,3,2,2,1,3,3,3,1,1,3,1,2,3,2,3,2,2,2,1,0,2,2,2, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0, -3,1,1,3,3,3,3,3,1,2,3,3,3,3,1,2,1,3,3,3,2,2,3,2,1,0,3,2,0,1,1,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,1,1,3,3,3,3,3,1,2,3,3,3,3,1,1,0,3,3,3,3,0,2,3,0,0,2,1,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,2,2,3,3,2,2,2,2,3,3,0,1,2,3,2,3,2,2,3,2,1,2,0,2,2,2, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0, -3,3,3,3,3,3,1,2,3,3,3,2,1,2,3,3,2,2,2,3,2,3,3,1,3,3,1,1,0,2,3,2, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -3,3,3,1,2,2,2,2,3,3,3,1,1,1,3,3,1,1,3,1,1,3,2,1,2,3,1,1,0,2,2,2, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -3,3,3,2,1,2,1,1,3,3,1,1,1,1,3,3,1,1,2,2,1,2,1,1,2,2,1,1,0,2,2,1, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -3,3,3,1,1,2,1,1,3,3,1,0,1,1,3,3,2,0,1,1,2,3,1,0,2,2,1,0,0,1,3,2, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -3,2,1,3,3,3,3,3,1,2,3,2,3,3,2,1,1,3,2,3,2,1,2,2,0,1,2,1,0,0,1,1, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, -3,3,3,3,2,2,2,2,3,1,2,2,1,1,3,3,0,3,2,1,2,3,2,1,3,3,1,1,0,2,1,3, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -3,3,3,2,2,2,3,2,3,3,3,2,1,1,3,3,1,1,1,2,2,3,2,3,2,2,2,1,0,2,2,1, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -1,0,0,3,3,3,3,3,0,0,3,3,2,3,0,0,0,2,3,3,1,0,1,2,0,0,1,1,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,1,2,3,3,3,3,3,1,2,3,3,2,2,1,1,0,3,3,2,2,1,2,2,1,0,2,2,0,1,1,1, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,2,2,1,3,1,2,3,3,2,2,1,1,2,2,1,1,1,1,3,2,1,1,1,1,2,1,0,1,2,1, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0, -2,3,3,1,1,1,1,1,3,3,3,0,1,1,3,3,1,1,1,1,1,2,2,0,3,1,1,2,0,2,1,1, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -3,1,0,1,2,1,2,2,0,1,2,3,1,2,0,0,0,2,1,1,1,1,1,2,0,0,1,1,0,0,0,0, -1,2,1,2,2,2,1,2,1,2,0,2,0,2,2,1,1,2,1,1,2,1,1,1,0,1,0,0,0,1,1,0, -1,1,1,2,3,2,3,3,0,1,2,2,3,1,0,1,0,2,1,2,2,0,1,1,0,0,1,1,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,0,0,3,3,2,2,1,0,0,3,2,3,2,0,0,0,1,1,3,0,0,1,1,0,0,2,1,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,1,1,2,2,3,3,1,0,1,3,2,3,1,1,1,0,1,1,1,1,1,3,1,0,0,2,2,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,1,1,1,2,2,2,1,0,1,2,3,3,2,0,0,0,2,1,1,1,2,1,1,1,0,1,1,1,0,0,0, -1,2,2,2,2,2,1,1,1,2,0,2,1,1,1,1,1,2,1,1,1,1,1,1,0,1,1,1,0,0,1,1, -3,2,2,1,0,0,1,1,2,2,0,3,0,1,2,1,1,0,0,1,1,1,0,1,1,1,1,0,2,1,1,1, -2,2,1,1,1,2,1,2,1,1,1,1,1,1,1,2,1,1,1,2,3,1,1,1,1,1,1,1,1,1,0,1, -2,3,3,0,1,0,0,0,3,3,1,0,0,1,2,2,1,0,0,0,0,2,0,0,1,1,1,0,2,1,1,1, -2,1,1,1,1,1,1,2,1,1,0,1,1,0,1,1,1,0,1,2,1,1,0,1,1,1,1,1,1,1,0,1, -2,3,3,0,1,0,0,0,2,2,0,0,0,0,1,2,2,0,0,0,0,1,0,0,1,1,0,0,2,0,1,0, -2,1,1,1,1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,2,0,1,1,1,1,1,0,1, -3,2,2,0,1,0,1,0,2,3,2,0,0,1,2,2,1,0,0,1,1,1,0,0,2,1,0,1,2,2,1,1, -2,1,1,1,1,1,1,2,1,1,1,1,1,1,0,2,1,0,1,1,0,1,1,1,0,1,1,2,1,1,0,1, -2,2,2,0,0,1,0,0,2,2,1,1,0,0,2,1,1,0,0,0,1,2,0,0,2,1,0,0,2,1,1,1, -2,1,1,1,1,2,1,2,1,1,1,2,2,1,1,2,1,1,1,2,1,1,1,1,1,1,1,1,1,1,0,1, -1,2,3,0,0,0,1,0,3,2,1,0,0,1,2,1,1,0,0,0,0,2,1,0,1,1,0,0,2,1,2,1, -1,1,0,0,0,1,0,1,1,1,1,1,2,0,0,1,0,0,0,2,0,0,1,1,1,1,1,1,1,1,0,1, -3,0,0,2,1,2,2,1,0,0,2,1,2,2,0,0,0,2,1,1,1,0,1,1,0,0,1,1,2,0,0,0, -1,2,1,2,2,1,1,2,1,2,0,1,1,1,1,1,1,1,1,1,2,1,1,0,0,1,1,1,1,0,0,1, -1,3,2,0,0,0,1,0,2,2,2,0,0,0,2,2,1,0,0,0,0,3,1,1,1,1,0,0,2,1,1,1, -2,1,0,1,1,1,0,1,1,1,1,1,1,1,0,2,1,0,0,1,0,1,1,0,1,1,1,1,1,1,0,1, -2,3,2,0,0,0,1,0,2,2,0,0,0,0,2,1,1,0,0,0,0,2,1,0,1,1,0,0,2,1,1,0, -2,1,1,1,1,2,1,2,1,2,0,1,1,1,0,2,1,1,1,2,1,1,1,1,0,1,1,1,1,1,0,1, -3,1,1,2,2,2,3,2,1,1,2,2,1,1,0,1,0,2,2,1,1,1,1,1,0,0,1,1,0,1,1,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,2,2,0,0,0,0,0,2,2,0,0,0,0,2,2,1,0,0,0,1,1,0,0,1,2,0,0,2,1,1,1, -2,2,1,1,1,2,1,2,1,1,0,1,1,1,1,2,1,1,1,2,1,1,1,1,0,1,2,1,1,1,0,1, -1,0,0,1,2,3,2,1,0,0,2,0,1,1,0,0,0,1,1,1,1,0,1,1,0,0,1,0,0,0,0,0, -1,2,1,2,1,2,1,1,1,2,0,2,1,1,1,0,1,2,0,0,1,1,1,0,0,0,0,0,0,0,0,0, -2,3,2,0,0,0,0,0,1,1,2,1,0,0,1,1,1,0,0,0,0,2,0,0,1,1,0,0,2,1,1,1, -2,1,1,1,1,1,1,2,1,0,1,1,1,1,0,2,1,1,1,1,1,1,0,1,0,1,1,1,1,1,0,1, -1,2,2,0,1,1,1,0,2,2,2,0,0,0,3,2,1,0,0,0,1,1,0,0,1,1,0,1,1,1,0,0, -1,1,0,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,1,1,0,0,1,1,1,0,1,0,1, -2,1,0,2,1,1,2,2,1,1,2,1,1,1,0,0,0,1,1,0,1,1,1,1,0,0,1,1,1,0,0,0, -1,2,2,2,2,2,1,1,1,2,0,2,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,0,0,1,0, -1,2,3,0,0,0,1,0,2,2,0,0,0,0,2,2,0,0,0,0,0,1,0,0,1,0,0,0,2,0,1,0, -2,1,1,1,1,1,0,2,0,0,0,1,2,1,1,1,1,0,1,2,0,1,0,1,0,1,1,1,0,1,0,1, -2,2,2,0,0,0,1,0,2,1,2,0,0,0,1,1,2,0,0,0,0,1,0,0,1,1,0,0,2,1,0,1, -2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,0,1,1,1,1,1,0,1, -1,2,2,0,0,0,1,0,2,2,2,0,0,0,1,1,0,0,0,0,0,1,1,0,2,0,0,1,1,1,0,1, -1,0,1,1,1,1,1,1,0,1,1,1,1,0,0,1,0,0,1,1,0,1,0,1,1,1,1,1,0,0,0,1, -1,0,0,1,0,1,2,1,0,0,1,1,1,2,0,0,0,1,1,0,1,0,1,1,0,0,1,0,0,0,0,0, -0,2,1,2,1,1,1,1,1,2,0,2,0,1,1,0,1,2,1,0,1,1,1,0,0,0,0,0,0,1,0,0, -2,1,1,0,1,2,0,0,1,1,1,0,0,0,1,1,0,0,0,0,0,1,0,0,1,0,0,0,2,1,0,1, -2,2,1,1,1,1,1,2,1,1,0,1,1,1,1,2,1,1,1,2,1,1,0,1,0,1,1,1,1,1,0,1, -1,2,2,0,0,0,0,0,1,1,0,0,0,0,2,1,0,0,0,0,0,2,0,0,2,2,0,0,2,0,0,1, -2,1,1,1,1,1,1,1,0,1,1,0,1,1,0,1,0,0,0,1,1,1,1,0,0,1,1,1,1,0,0,1, -1,1,2,0,0,3,1,0,2,1,1,1,0,0,1,1,1,0,0,0,1,1,0,0,0,1,0,0,1,0,1,0, -1,2,1,0,1,1,1,2,1,1,0,1,1,1,1,1,0,0,0,1,1,1,1,1,0,1,0,0,0,1,0,0, -2,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,1,0,0,0,1,0,0,0,0,2,0,0,0, -2,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,2,1,1,0,0,1,1,1,1,1,0,1, -2,1,1,1,2,1,1,1,0,1,1,2,1,0,0,0,0,1,1,1,1,0,1,0,0,0,0,1,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,1,0,1,1,1,1,1,0,0,1,1,2,1,0,0,0,1,1,0,0,0,1,1,0,0,1,0,1,0,0,0, -1,2,1,1,1,1,1,1,1,1,0,1,0,1,1,1,1,1,1,0,1,1,1,0,0,0,0,0,0,1,0,0, -2,0,0,0,1,1,1,1,0,0,1,1,0,0,0,0,0,1,1,1,2,0,0,1,0,0,1,0,1,0,0,0, -0,1,1,1,1,1,1,1,1,2,0,1,1,1,1,0,1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,0, -1,0,0,1,1,1,1,1,0,0,2,1,0,1,0,0,0,1,0,1,0,0,0,0,0,0,1,0,0,0,0,0, -0,1,1,1,1,1,1,0,1,1,0,1,0,1,1,0,1,1,0,0,1,1,1,0,0,0,0,0,0,0,0,0, -1,0,0,1,1,1,0,0,0,0,1,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, -0,1,1,1,1,1,0,0,1,1,0,1,0,1,0,0,1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,0, -0,0,0,1,0,0,0,0,0,0,1,1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,1,1,1,0,1,0,0,1,1,0,1,0,1,1,0,1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,0, -2,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,0,0,1,0,0,1,0,1,0,1,1,1,0,0,1,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,0,0,1,1,1,1,0,0,0,1,1,1,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0, -0,1,1,1,1,1,1,0,1,1,0,1,0,1,0,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0, -) - -Latin2HungarianModel = { - 'char_to_order_map': Latin2_HungarianCharToOrderMap, - 'precedence_matrix': HungarianLangModel, - 'typical_positive_ratio': 0.947368, - 'keep_english_letter': True, - 'charset_name': "ISO-8859-2", - 'language': 'Hungarian', +# Character Mapping Table(s): +WINDOWS_1250_HUNGARIAN_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 28, # 'A' + 66: 40, # 'B' + 67: 54, # 'C' + 68: 45, # 'D' + 69: 32, # 'E' + 70: 50, # 'F' + 71: 49, # 'G' + 72: 38, # 'H' + 73: 39, # 'I' + 74: 53, # 'J' + 75: 36, # 'K' + 76: 41, # 'L' + 77: 34, # 'M' + 78: 35, # 'N' + 79: 47, # 'O' + 80: 46, # 'P' + 81: 72, # 'Q' + 82: 43, # 'R' + 83: 33, # 'S' + 84: 37, # 'T' + 85: 57, # 'U' + 86: 48, # 'V' + 87: 64, # 'W' + 88: 68, # 'X' + 89: 55, # 'Y' + 90: 52, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 2, # 'a' + 98: 18, # 'b' + 99: 26, # 'c' + 100: 17, # 'd' + 101: 1, # 'e' + 102: 27, # 'f' + 103: 12, # 'g' + 104: 20, # 'h' + 105: 9, # 'i' + 106: 22, # 'j' + 107: 7, # 'k' + 108: 6, # 'l' + 109: 13, # 'm' + 110: 4, # 'n' + 111: 8, # 'o' + 112: 23, # 'p' + 113: 67, # 'q' + 114: 10, # 'r' + 115: 5, # 's' + 116: 3, # 't' + 117: 21, # 'u' + 118: 19, # 'v' + 119: 65, # 'w' + 120: 62, # 'x' + 121: 16, # 'y' + 122: 11, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 161, # '€' + 129: 162, # None + 130: 163, # '‚' + 131: 164, # None + 132: 165, # '„' + 133: 166, # '…' + 134: 167, # '†' + 135: 168, # '‡' + 136: 169, # None + 137: 170, # '‰' + 138: 171, # 'Š' + 139: 172, # '‹' + 140: 173, # 'Ś' + 141: 174, # 'Ť' + 142: 175, # 'Ž' + 143: 176, # 'Ź' + 144: 177, # None + 145: 178, # '‘' + 146: 179, # '’' + 147: 180, # '“' + 148: 78, # '”' + 149: 181, # '•' + 150: 69, # '–' + 151: 182, # '—' + 152: 183, # None + 153: 184, # '™' + 154: 185, # 'š' + 155: 186, # '›' + 156: 187, # 'ś' + 157: 188, # 'ť' + 158: 189, # 'ž' + 159: 190, # 'ź' + 160: 191, # '\xa0' + 161: 192, # 'ˇ' + 162: 193, # '˘' + 163: 194, # 'Ł' + 164: 195, # '¤' + 165: 196, # 'Ą' + 166: 197, # '¦' + 167: 76, # '§' + 168: 198, # '¨' + 169: 199, # '©' + 170: 200, # 'Ş' + 171: 201, # '«' + 172: 202, # '¬' + 173: 203, # '\xad' + 174: 204, # '®' + 175: 205, # 'Ż' + 176: 81, # '°' + 177: 206, # '±' + 178: 207, # '˛' + 179: 208, # 'ł' + 180: 209, # '´' + 181: 210, # 'µ' + 182: 211, # '¶' + 183: 212, # '·' + 184: 213, # '¸' + 185: 214, # 'ą' + 186: 215, # 'ş' + 187: 216, # '»' + 188: 217, # 'Ľ' + 189: 218, # '˝' + 190: 219, # 'ľ' + 191: 220, # 'ż' + 192: 221, # 'Ŕ' + 193: 51, # 'Á' + 194: 83, # 'Â' + 195: 222, # 'Ă' + 196: 80, # 'Ä' + 197: 223, # 'Ĺ' + 198: 224, # 'Ć' + 199: 225, # 'Ç' + 200: 226, # 'Č' + 201: 44, # 'É' + 202: 227, # 'Ę' + 203: 228, # 'Ë' + 204: 229, # 'Ě' + 205: 61, # 'Í' + 206: 230, # 'Î' + 207: 231, # 'Ď' + 208: 232, # 'Đ' + 209: 233, # 'Ń' + 210: 234, # 'Ň' + 211: 58, # 'Ó' + 212: 235, # 'Ô' + 213: 66, # 'Ő' + 214: 59, # 'Ö' + 215: 236, # '×' + 216: 237, # 'Ř' + 217: 238, # 'Ů' + 218: 60, # 'Ú' + 219: 70, # 'Ű' + 220: 63, # 'Ü' + 221: 239, # 'Ý' + 222: 240, # 'Ţ' + 223: 241, # 'ß' + 224: 84, # 'ŕ' + 225: 14, # 'á' + 226: 75, # 'â' + 227: 242, # 'ă' + 228: 71, # 'ä' + 229: 82, # 'ĺ' + 230: 243, # 'ć' + 231: 73, # 'ç' + 232: 244, # 'č' + 233: 15, # 'é' + 234: 85, # 'ę' + 235: 79, # 'ë' + 236: 86, # 'ě' + 237: 30, # 'í' + 238: 77, # 'î' + 239: 87, # 'ď' + 240: 245, # 'đ' + 241: 246, # 'ń' + 242: 247, # 'ň' + 243: 25, # 'ó' + 244: 74, # 'ô' + 245: 42, # 'ő' + 246: 24, # 'ö' + 247: 248, # '÷' + 248: 249, # 'ř' + 249: 250, # 'ů' + 250: 31, # 'ú' + 251: 56, # 'ű' + 252: 29, # 'ü' + 253: 251, # 'ý' + 254: 252, # 'ţ' + 255: 253, # '˙' } -Win1250HungarianModel = { - 'char_to_order_map': win1250HungarianCharToOrderMap, - 'precedence_matrix': HungarianLangModel, - 'typical_positive_ratio': 0.947368, - 'keep_english_letter': True, - 'charset_name': "windows-1250", - 'language': 'Hungarian', +WINDOWS_1250_HUNGARIAN_MODEL = SingleByteCharSetModel(charset_name='windows-1250', + language='Hungarian', + char_to_order_map=WINDOWS_1250_HUNGARIAN_CHAR_TO_ORDER, + language_model=HUNGARIAN_LANG_MODEL, + typical_positive_ratio=0.947368, + keep_ascii_letters=True, + alphabet='ABCDEFGHIJKLMNOPRSTUVZabcdefghijklmnoprstuvzÁÉÍÓÖÚÜáéíóöúüŐőŰű') + +ISO_8859_2_HUNGARIAN_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 28, # 'A' + 66: 40, # 'B' + 67: 54, # 'C' + 68: 45, # 'D' + 69: 32, # 'E' + 70: 50, # 'F' + 71: 49, # 'G' + 72: 38, # 'H' + 73: 39, # 'I' + 74: 53, # 'J' + 75: 36, # 'K' + 76: 41, # 'L' + 77: 34, # 'M' + 78: 35, # 'N' + 79: 47, # 'O' + 80: 46, # 'P' + 81: 71, # 'Q' + 82: 43, # 'R' + 83: 33, # 'S' + 84: 37, # 'T' + 85: 57, # 'U' + 86: 48, # 'V' + 87: 64, # 'W' + 88: 68, # 'X' + 89: 55, # 'Y' + 90: 52, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 2, # 'a' + 98: 18, # 'b' + 99: 26, # 'c' + 100: 17, # 'd' + 101: 1, # 'e' + 102: 27, # 'f' + 103: 12, # 'g' + 104: 20, # 'h' + 105: 9, # 'i' + 106: 22, # 'j' + 107: 7, # 'k' + 108: 6, # 'l' + 109: 13, # 'm' + 110: 4, # 'n' + 111: 8, # 'o' + 112: 23, # 'p' + 113: 67, # 'q' + 114: 10, # 'r' + 115: 5, # 's' + 116: 3, # 't' + 117: 21, # 'u' + 118: 19, # 'v' + 119: 65, # 'w' + 120: 62, # 'x' + 121: 16, # 'y' + 122: 11, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 159, # '\x80' + 129: 160, # '\x81' + 130: 161, # '\x82' + 131: 162, # '\x83' + 132: 163, # '\x84' + 133: 164, # '\x85' + 134: 165, # '\x86' + 135: 166, # '\x87' + 136: 167, # '\x88' + 137: 168, # '\x89' + 138: 169, # '\x8a' + 139: 170, # '\x8b' + 140: 171, # '\x8c' + 141: 172, # '\x8d' + 142: 173, # '\x8e' + 143: 174, # '\x8f' + 144: 175, # '\x90' + 145: 176, # '\x91' + 146: 177, # '\x92' + 147: 178, # '\x93' + 148: 179, # '\x94' + 149: 180, # '\x95' + 150: 181, # '\x96' + 151: 182, # '\x97' + 152: 183, # '\x98' + 153: 184, # '\x99' + 154: 185, # '\x9a' + 155: 186, # '\x9b' + 156: 187, # '\x9c' + 157: 188, # '\x9d' + 158: 189, # '\x9e' + 159: 190, # '\x9f' + 160: 191, # '\xa0' + 161: 192, # 'Ą' + 162: 193, # '˘' + 163: 194, # 'Ł' + 164: 195, # '¤' + 165: 196, # 'Ľ' + 166: 197, # 'Ś' + 167: 75, # '§' + 168: 198, # '¨' + 169: 199, # 'Š' + 170: 200, # 'Ş' + 171: 201, # 'Ť' + 172: 202, # 'Ź' + 173: 203, # '\xad' + 174: 204, # 'Ž' + 175: 205, # 'Ż' + 176: 79, # '°' + 177: 206, # 'ą' + 178: 207, # '˛' + 179: 208, # 'ł' + 180: 209, # '´' + 181: 210, # 'ľ' + 182: 211, # 'ś' + 183: 212, # 'ˇ' + 184: 213, # '¸' + 185: 214, # 'š' + 186: 215, # 'ş' + 187: 216, # 'ť' + 188: 217, # 'ź' + 189: 218, # '˝' + 190: 219, # 'ž' + 191: 220, # 'ż' + 192: 221, # 'Ŕ' + 193: 51, # 'Á' + 194: 81, # 'Â' + 195: 222, # 'Ă' + 196: 78, # 'Ä' + 197: 223, # 'Ĺ' + 198: 224, # 'Ć' + 199: 225, # 'Ç' + 200: 226, # 'Č' + 201: 44, # 'É' + 202: 227, # 'Ę' + 203: 228, # 'Ë' + 204: 229, # 'Ě' + 205: 61, # 'Í' + 206: 230, # 'Î' + 207: 231, # 'Ď' + 208: 232, # 'Đ' + 209: 233, # 'Ń' + 210: 234, # 'Ň' + 211: 58, # 'Ó' + 212: 235, # 'Ô' + 213: 66, # 'Ő' + 214: 59, # 'Ö' + 215: 236, # '×' + 216: 237, # 'Ř' + 217: 238, # 'Ů' + 218: 60, # 'Ú' + 219: 69, # 'Ű' + 220: 63, # 'Ü' + 221: 239, # 'Ý' + 222: 240, # 'Ţ' + 223: 241, # 'ß' + 224: 82, # 'ŕ' + 225: 14, # 'á' + 226: 74, # 'â' + 227: 242, # 'ă' + 228: 70, # 'ä' + 229: 80, # 'ĺ' + 230: 243, # 'ć' + 231: 72, # 'ç' + 232: 244, # 'č' + 233: 15, # 'é' + 234: 83, # 'ę' + 235: 77, # 'ë' + 236: 84, # 'ě' + 237: 30, # 'í' + 238: 76, # 'î' + 239: 85, # 'ď' + 240: 245, # 'đ' + 241: 246, # 'ń' + 242: 247, # 'ň' + 243: 25, # 'ó' + 244: 73, # 'ô' + 245: 42, # 'ő' + 246: 24, # 'ö' + 247: 248, # '÷' + 248: 249, # 'ř' + 249: 250, # 'ů' + 250: 31, # 'ú' + 251: 56, # 'ű' + 252: 29, # 'ü' + 253: 251, # 'ý' + 254: 252, # 'ţ' + 255: 253, # '˙' } + +ISO_8859_2_HUNGARIAN_MODEL = SingleByteCharSetModel(charset_name='ISO-8859-2', + language='Hungarian', + char_to_order_map=ISO_8859_2_HUNGARIAN_CHAR_TO_ORDER, + language_model=HUNGARIAN_LANG_MODEL, + typical_positive_ratio=0.947368, + keep_ascii_letters=True, + alphabet='ABCDEFGHIJKLMNOPRSTUVZabcdefghijklmnoprstuvzÁÉÍÓÖÚÜáéíóöúüŐőŰű') + diff --git a/pipenv/patched/notpip/_vendor/chardet/langrussianmodel.py b/pipenv/patched/notpip/_vendor/chardet/langrussianmodel.py new file mode 100644 index 00000000..ce2c0b6a --- /dev/null +++ b/pipenv/patched/notpip/_vendor/chardet/langrussianmodel.py @@ -0,0 +1,5718 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from pipenv.patched.notpip._vendor.chardet.sbcharsetprober import SingleByteCharSetModel + + +# 3: Positive +# 2: Likely +# 1: Unlikely +# 0: Negative + +RUSSIAN_LANG_MODEL = { + 37: { # 'А' + 37: 0, # 'А' + 44: 1, # 'Б' + 33: 1, # 'В' + 46: 1, # 'Г' + 41: 1, # 'Д' + 48: 1, # 'Е' + 56: 1, # 'Ж' + 51: 1, # 'З' + 42: 1, # 'И' + 60: 1, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 2, # 'Н' + 34: 1, # 'О' + 35: 1, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 1, # 'У' + 53: 1, # 'Ф' + 55: 1, # 'Х' + 58: 1, # 'Ц' + 50: 1, # 'Ч' + 57: 1, # 'Ш' + 63: 1, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 1, # 'Ю' + 43: 1, # 'Я' + 3: 1, # 'а' + 21: 2, # 'б' + 10: 2, # 'в' + 19: 2, # 'г' + 13: 2, # 'д' + 2: 0, # 'е' + 24: 1, # 'ж' + 20: 1, # 'з' + 4: 0, # 'и' + 23: 1, # 'й' + 11: 2, # 'к' + 8: 3, # 'л' + 12: 2, # 'м' + 5: 2, # 'н' + 1: 0, # 'о' + 15: 2, # 'п' + 9: 2, # 'р' + 7: 2, # 'с' + 6: 2, # 'т' + 14: 2, # 'у' + 39: 2, # 'ф' + 26: 2, # 'х' + 28: 0, # 'ц' + 22: 1, # 'ч' + 25: 2, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 1, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 44: { # 'Б' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 1, # 'В' + 46: 1, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 0, # 'П' + 45: 1, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 1, # 'Я' + 3: 2, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 0, # 'г' + 13: 1, # 'д' + 2: 3, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 2, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 2, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 2, # 'ы' + 17: 1, # 'ь' + 30: 2, # 'э' + 27: 1, # 'ю' + 16: 1, # 'я' + }, + 33: { # 'В' + 37: 2, # 'А' + 44: 0, # 'Б' + 33: 1, # 'В' + 46: 0, # 'Г' + 41: 1, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 1, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 1, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 1, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 1, # 'Я' + 3: 2, # 'а' + 21: 1, # 'б' + 10: 1, # 'в' + 19: 1, # 'г' + 13: 2, # 'д' + 2: 3, # 'е' + 24: 0, # 'ж' + 20: 2, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 1, # 'к' + 8: 2, # 'л' + 12: 2, # 'м' + 5: 2, # 'н' + 1: 3, # 'о' + 15: 2, # 'п' + 9: 2, # 'р' + 7: 3, # 'с' + 6: 2, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 1, # 'х' + 28: 1, # 'ц' + 22: 2, # 'ч' + 25: 1, # 'ш' + 29: 0, # 'щ' + 54: 1, # 'ъ' + 18: 3, # 'ы' + 17: 1, # 'ь' + 30: 2, # 'э' + 27: 0, # 'ю' + 16: 1, # 'я' + }, + 46: { # 'Г' + 37: 1, # 'А' + 44: 1, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 1, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 1, # 'П' + 45: 1, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 2, # 'а' + 21: 0, # 'б' + 10: 1, # 'в' + 19: 0, # 'г' + 13: 2, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 2, # 'л' + 12: 1, # 'м' + 5: 1, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 2, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 1, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 0, # 'я' + }, + 41: { # 'Д' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 1, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 2, # 'Е' + 56: 1, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 0, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 0, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 1, # 'Ц' + 50: 1, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 1, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 1, # 'Я' + 3: 3, # 'а' + 21: 0, # 'б' + 10: 2, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 3, # 'ж' + 20: 1, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 2, # 'л' + 12: 1, # 'м' + 5: 1, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 2, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 1, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 1, # 'ы' + 17: 1, # 'ь' + 30: 2, # 'э' + 27: 1, # 'ю' + 16: 1, # 'я' + }, + 48: { # 'Е' + 37: 1, # 'А' + 44: 1, # 'Б' + 33: 1, # 'В' + 46: 1, # 'Г' + 41: 1, # 'Д' + 48: 1, # 'Е' + 56: 1, # 'Ж' + 51: 1, # 'З' + 42: 1, # 'И' + 60: 1, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 2, # 'Н' + 34: 1, # 'О' + 35: 1, # 'П' + 45: 2, # 'Р' + 32: 2, # 'С' + 40: 1, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 1, # 'Х' + 58: 1, # 'Ц' + 50: 1, # 'Ч' + 57: 1, # 'Ш' + 63: 1, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 1, # 'Я' + 3: 0, # 'а' + 21: 0, # 'б' + 10: 2, # 'в' + 19: 2, # 'г' + 13: 2, # 'д' + 2: 2, # 'е' + 24: 1, # 'ж' + 20: 1, # 'з' + 4: 0, # 'и' + 23: 2, # 'й' + 11: 1, # 'к' + 8: 2, # 'л' + 12: 2, # 'м' + 5: 1, # 'н' + 1: 0, # 'о' + 15: 1, # 'п' + 9: 1, # 'р' + 7: 3, # 'с' + 6: 0, # 'т' + 14: 0, # 'у' + 39: 1, # 'ф' + 26: 1, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 1, # 'ш' + 29: 2, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 1, # 'ю' + 16: 0, # 'я' + }, + 56: { # 'Ж' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 1, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 1, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 2, # 'а' + 21: 1, # 'б' + 10: 0, # 'в' + 19: 1, # 'г' + 13: 1, # 'д' + 2: 2, # 'е' + 24: 1, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 0, # 'л' + 12: 1, # 'м' + 5: 0, # 'н' + 1: 2, # 'о' + 15: 0, # 'п' + 9: 1, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 2, # 'ю' + 16: 0, # 'я' + }, + 51: { # 'З' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 1, # 'В' + 46: 1, # 'Г' + 41: 1, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 0, # 'П' + 45: 1, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 1, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 1, # 'б' + 10: 2, # 'в' + 19: 0, # 'г' + 13: 2, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 1, # 'л' + 12: 1, # 'м' + 5: 2, # 'н' + 1: 2, # 'о' + 15: 0, # 'п' + 9: 1, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 1, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 1, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 1, # 'я' + }, + 42: { # 'И' + 37: 1, # 'А' + 44: 1, # 'Б' + 33: 1, # 'В' + 46: 1, # 'Г' + 41: 1, # 'Д' + 48: 2, # 'Е' + 56: 1, # 'Ж' + 51: 1, # 'З' + 42: 1, # 'И' + 60: 1, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 1, # 'П' + 45: 1, # 'Р' + 32: 2, # 'С' + 40: 1, # 'Т' + 52: 0, # 'У' + 53: 1, # 'Ф' + 55: 1, # 'Х' + 58: 1, # 'Ц' + 50: 1, # 'Ч' + 57: 0, # 'Ш' + 63: 1, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 1, # 'Ю' + 43: 1, # 'Я' + 3: 1, # 'а' + 21: 2, # 'б' + 10: 2, # 'в' + 19: 2, # 'г' + 13: 2, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 2, # 'з' + 4: 1, # 'и' + 23: 0, # 'й' + 11: 1, # 'к' + 8: 2, # 'л' + 12: 2, # 'м' + 5: 2, # 'н' + 1: 1, # 'о' + 15: 1, # 'п' + 9: 2, # 'р' + 7: 2, # 'с' + 6: 2, # 'т' + 14: 1, # 'у' + 39: 1, # 'ф' + 26: 2, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 1, # 'ш' + 29: 1, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 1, # 'ю' + 16: 0, # 'я' + }, + 60: { # 'Й' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 1, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 1, # 'Х' + 58: 1, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 0, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 1, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 0, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 0, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 2, # 'о' + 15: 0, # 'п' + 9: 0, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 0, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 36: { # 'К' + 37: 2, # 'А' + 44: 0, # 'Б' + 33: 1, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 1, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 1, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 2, # 'О' + 35: 1, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 1, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 0, # 'б' + 10: 1, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 2, # 'л' + 12: 0, # 'м' + 5: 1, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 2, # 'р' + 7: 2, # 'с' + 6: 2, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 1, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 1, # 'ы' + 17: 1, # 'ь' + 30: 2, # 'э' + 27: 1, # 'ю' + 16: 0, # 'я' + }, + 49: { # 'Л' + 37: 2, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 1, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 1, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 0, # 'Н' + 34: 1, # 'О' + 35: 1, # 'П' + 45: 0, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 1, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 1, # 'Ь' + 47: 0, # 'Э' + 59: 1, # 'Ю' + 43: 1, # 'Я' + 3: 2, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 1, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 1, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 1, # 'л' + 12: 0, # 'м' + 5: 1, # 'н' + 1: 2, # 'о' + 15: 0, # 'п' + 9: 0, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 1, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 1, # 'ы' + 17: 1, # 'ь' + 30: 2, # 'э' + 27: 2, # 'ю' + 16: 1, # 'я' + }, + 38: { # 'М' + 37: 1, # 'А' + 44: 1, # 'Б' + 33: 1, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 1, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 1, # 'У' + 53: 1, # 'Ф' + 55: 1, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 0, # 'Ь' + 47: 1, # 'Э' + 59: 0, # 'Ю' + 43: 1, # 'Я' + 3: 3, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 1, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 1, # 'л' + 12: 1, # 'м' + 5: 2, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 1, # 'р' + 7: 1, # 'с' + 6: 0, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 3, # 'ы' + 17: 1, # 'ь' + 30: 2, # 'э' + 27: 1, # 'ю' + 16: 1, # 'я' + }, + 31: { # 'Н' + 37: 2, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 1, # 'Г' + 41: 1, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 1, # 'З' + 42: 2, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 0, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 1, # 'У' + 53: 1, # 'Ф' + 55: 1, # 'Х' + 58: 1, # 'Ц' + 50: 1, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 1, # 'Ь' + 47: 1, # 'Э' + 59: 0, # 'Ю' + 43: 1, # 'Я' + 3: 3, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 3, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 0, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 1, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 3, # 'у' + 39: 0, # 'ф' + 26: 1, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 1, # 'ы' + 17: 2, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 1, # 'я' + }, + 34: { # 'О' + 37: 0, # 'А' + 44: 1, # 'Б' + 33: 1, # 'В' + 46: 1, # 'Г' + 41: 2, # 'Д' + 48: 1, # 'Е' + 56: 1, # 'Ж' + 51: 1, # 'З' + 42: 1, # 'И' + 60: 1, # 'Й' + 36: 1, # 'К' + 49: 2, # 'Л' + 38: 1, # 'М' + 31: 2, # 'Н' + 34: 1, # 'О' + 35: 1, # 'П' + 45: 2, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 1, # 'У' + 53: 1, # 'Ф' + 55: 1, # 'Х' + 58: 0, # 'Ц' + 50: 1, # 'Ч' + 57: 1, # 'Ш' + 63: 1, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 1, # 'Я' + 3: 1, # 'а' + 21: 2, # 'б' + 10: 1, # 'в' + 19: 2, # 'г' + 13: 2, # 'д' + 2: 0, # 'е' + 24: 1, # 'ж' + 20: 1, # 'з' + 4: 0, # 'и' + 23: 1, # 'й' + 11: 2, # 'к' + 8: 2, # 'л' + 12: 1, # 'м' + 5: 3, # 'н' + 1: 0, # 'о' + 15: 2, # 'п' + 9: 2, # 'р' + 7: 2, # 'с' + 6: 2, # 'т' + 14: 1, # 'у' + 39: 1, # 'ф' + 26: 2, # 'х' + 28: 1, # 'ц' + 22: 2, # 'ч' + 25: 2, # 'ш' + 29: 1, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 35: { # 'П' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 1, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 1, # 'П' + 45: 2, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 1, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 1, # 'Я' + 3: 2, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 2, # 'л' + 12: 0, # 'м' + 5: 1, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 3, # 'р' + 7: 1, # 'с' + 6: 1, # 'т' + 14: 2, # 'у' + 39: 1, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 1, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 1, # 'ы' + 17: 2, # 'ь' + 30: 1, # 'э' + 27: 0, # 'ю' + 16: 2, # 'я' + }, + 45: { # 'Р' + 37: 2, # 'А' + 44: 1, # 'Б' + 33: 1, # 'В' + 46: 1, # 'Г' + 41: 1, # 'Д' + 48: 2, # 'Е' + 56: 1, # 'Ж' + 51: 0, # 'З' + 42: 2, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 2, # 'О' + 35: 0, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 1, # 'Х' + 58: 1, # 'Ц' + 50: 1, # 'Ч' + 57: 1, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 1, # 'Ь' + 47: 1, # 'Э' + 59: 1, # 'Ю' + 43: 1, # 'Я' + 3: 3, # 'а' + 21: 0, # 'б' + 10: 1, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 1, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 0, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 1, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 2, # 'ы' + 17: 0, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 2, # 'я' + }, + 32: { # 'С' + 37: 1, # 'А' + 44: 1, # 'Б' + 33: 1, # 'В' + 46: 1, # 'Г' + 41: 1, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 1, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 2, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 1, # 'Х' + 58: 1, # 'Ц' + 50: 1, # 'Ч' + 57: 1, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 1, # 'Ь' + 47: 1, # 'Э' + 59: 1, # 'Ю' + 43: 1, # 'Я' + 3: 2, # 'а' + 21: 1, # 'б' + 10: 2, # 'в' + 19: 1, # 'г' + 13: 2, # 'д' + 2: 3, # 'е' + 24: 1, # 'ж' + 20: 1, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 2, # 'к' + 8: 2, # 'л' + 12: 2, # 'м' + 5: 2, # 'н' + 1: 2, # 'о' + 15: 2, # 'п' + 9: 2, # 'р' + 7: 1, # 'с' + 6: 3, # 'т' + 14: 2, # 'у' + 39: 1, # 'ф' + 26: 1, # 'х' + 28: 1, # 'ц' + 22: 1, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 1, # 'ъ' + 18: 1, # 'ы' + 17: 1, # 'ь' + 30: 2, # 'э' + 27: 1, # 'ю' + 16: 1, # 'я' + }, + 40: { # 'Т' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 1, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 2, # 'О' + 35: 0, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 1, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 1, # 'Ь' + 47: 1, # 'Э' + 59: 1, # 'Ю' + 43: 1, # 'Я' + 3: 3, # 'а' + 21: 1, # 'б' + 10: 2, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 3, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 1, # 'к' + 8: 1, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 2, # 'р' + 7: 1, # 'с' + 6: 0, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 1, # 'щ' + 54: 0, # 'ъ' + 18: 3, # 'ы' + 17: 1, # 'ь' + 30: 2, # 'э' + 27: 1, # 'ю' + 16: 1, # 'я' + }, + 52: { # 'У' + 37: 1, # 'А' + 44: 1, # 'Б' + 33: 1, # 'В' + 46: 1, # 'Г' + 41: 1, # 'Д' + 48: 1, # 'Е' + 56: 1, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 1, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 1, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 1, # 'Х' + 58: 0, # 'Ц' + 50: 1, # 'Ч' + 57: 1, # 'Ш' + 63: 1, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 1, # 'Ю' + 43: 0, # 'Я' + 3: 1, # 'а' + 21: 2, # 'б' + 10: 2, # 'в' + 19: 1, # 'г' + 13: 2, # 'д' + 2: 1, # 'е' + 24: 2, # 'ж' + 20: 2, # 'з' + 4: 2, # 'и' + 23: 1, # 'й' + 11: 1, # 'к' + 8: 2, # 'л' + 12: 2, # 'м' + 5: 1, # 'н' + 1: 2, # 'о' + 15: 1, # 'п' + 9: 2, # 'р' + 7: 2, # 'с' + 6: 2, # 'т' + 14: 0, # 'у' + 39: 1, # 'ф' + 26: 1, # 'х' + 28: 1, # 'ц' + 22: 2, # 'ч' + 25: 1, # 'ш' + 29: 1, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 2, # 'э' + 27: 1, # 'ю' + 16: 0, # 'я' + }, + 53: { # 'Ф' + 37: 1, # 'А' + 44: 1, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 1, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 1, # 'О' + 35: 0, # 'П' + 45: 1, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 2, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 2, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 2, # 'о' + 15: 0, # 'п' + 9: 2, # 'р' + 7: 0, # 'с' + 6: 1, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 1, # 'ь' + 30: 2, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 55: { # 'Х' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 1, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 2, # 'а' + 21: 0, # 'б' + 10: 2, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 2, # 'л' + 12: 1, # 'м' + 5: 0, # 'н' + 1: 2, # 'о' + 15: 0, # 'п' + 9: 2, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 1, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 1, # 'ь' + 30: 1, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 58: { # 'Ц' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 1, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 1, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 1, # 'а' + 21: 0, # 'б' + 10: 1, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 0, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 0, # 'о' + 15: 0, # 'п' + 9: 0, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 1, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 1, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 1, # 'ю' + 16: 0, # 'я' + }, + 50: { # 'Ч' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 0, # 'О' + 35: 1, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 1, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 1, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 2, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 1, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 1, # 'о' + 15: 0, # 'п' + 9: 1, # 'р' + 7: 0, # 'с' + 6: 3, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 1, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 57: { # 'Ш' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 1, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 2, # 'а' + 21: 0, # 'б' + 10: 1, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 1, # 'и' + 23: 0, # 'й' + 11: 1, # 'к' + 8: 2, # 'л' + 12: 1, # 'м' + 5: 1, # 'н' + 1: 2, # 'о' + 15: 2, # 'п' + 9: 1, # 'р' + 7: 0, # 'с' + 6: 2, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 1, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 1, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 1, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 63: { # 'Щ' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 1, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 1, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 1, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 1, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 0, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 1, # 'о' + 15: 0, # 'п' + 9: 0, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 1, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 62: { # 'Ы' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 1, # 'В' + 46: 1, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 1, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 0, # 'О' + 35: 1, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 1, # 'Х' + 58: 1, # 'Ц' + 50: 0, # 'Ч' + 57: 1, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 0, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 0, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 0, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 0, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 0, # 'о' + 15: 0, # 'п' + 9: 0, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 0, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 61: { # 'Ь' + 37: 0, # 'А' + 44: 1, # 'Б' + 33: 1, # 'В' + 46: 0, # 'Г' + 41: 1, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 0, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 1, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 1, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 1, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 1, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 1, # 'Ю' + 43: 1, # 'Я' + 3: 0, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 0, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 0, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 0, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 0, # 'о' + 15: 0, # 'п' + 9: 0, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 0, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 47: { # 'Э' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 1, # 'В' + 46: 0, # 'Г' + 41: 1, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 1, # 'Й' + 36: 1, # 'К' + 49: 1, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 0, # 'О' + 35: 1, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 1, # 'а' + 21: 1, # 'б' + 10: 2, # 'в' + 19: 1, # 'г' + 13: 2, # 'д' + 2: 0, # 'е' + 24: 1, # 'ж' + 20: 0, # 'з' + 4: 0, # 'и' + 23: 2, # 'й' + 11: 2, # 'к' + 8: 2, # 'л' + 12: 2, # 'м' + 5: 2, # 'н' + 1: 0, # 'о' + 15: 1, # 'п' + 9: 2, # 'р' + 7: 1, # 'с' + 6: 3, # 'т' + 14: 1, # 'у' + 39: 1, # 'ф' + 26: 1, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 1, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 59: { # 'Ю' + 37: 1, # 'А' + 44: 1, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 1, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 1, # 'Р' + 32: 0, # 'С' + 40: 1, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 1, # 'Ч' + 57: 0, # 'Ш' + 63: 1, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 0, # 'а' + 21: 1, # 'б' + 10: 0, # 'в' + 19: 1, # 'г' + 13: 1, # 'д' + 2: 0, # 'е' + 24: 1, # 'ж' + 20: 0, # 'з' + 4: 0, # 'и' + 23: 0, # 'й' + 11: 1, # 'к' + 8: 2, # 'л' + 12: 1, # 'м' + 5: 2, # 'н' + 1: 0, # 'о' + 15: 1, # 'п' + 9: 1, # 'р' + 7: 1, # 'с' + 6: 0, # 'т' + 14: 0, # 'у' + 39: 0, # 'ф' + 26: 1, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 43: { # 'Я' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 1, # 'В' + 46: 1, # 'Г' + 41: 0, # 'Д' + 48: 1, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 1, # 'С' + 40: 1, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 1, # 'Х' + 58: 0, # 'Ц' + 50: 1, # 'Ч' + 57: 0, # 'Ш' + 63: 1, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 1, # 'Ю' + 43: 1, # 'Я' + 3: 0, # 'а' + 21: 1, # 'б' + 10: 1, # 'в' + 19: 1, # 'г' + 13: 1, # 'д' + 2: 0, # 'е' + 24: 0, # 'ж' + 20: 1, # 'з' + 4: 0, # 'и' + 23: 1, # 'й' + 11: 1, # 'к' + 8: 1, # 'л' + 12: 1, # 'м' + 5: 2, # 'н' + 1: 0, # 'о' + 15: 1, # 'п' + 9: 1, # 'р' + 7: 1, # 'с' + 6: 0, # 'т' + 14: 0, # 'у' + 39: 0, # 'ф' + 26: 1, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 1, # 'ш' + 29: 1, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 3: { # 'а' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 1, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 2, # 'а' + 21: 3, # 'б' + 10: 3, # 'в' + 19: 3, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 3, # 'ж' + 20: 3, # 'з' + 4: 3, # 'и' + 23: 3, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 3, # 'м' + 5: 3, # 'н' + 1: 2, # 'о' + 15: 3, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 3, # 'у' + 39: 2, # 'ф' + 26: 3, # 'х' + 28: 3, # 'ц' + 22: 3, # 'ч' + 25: 3, # 'ш' + 29: 3, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 2, # 'э' + 27: 3, # 'ю' + 16: 3, # 'я' + }, + 21: { # 'б' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 1, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 2, # 'б' + 10: 2, # 'в' + 19: 1, # 'г' + 13: 2, # 'д' + 2: 3, # 'е' + 24: 2, # 'ж' + 20: 1, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 2, # 'к' + 8: 3, # 'л' + 12: 2, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 1, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 2, # 'т' + 14: 3, # 'у' + 39: 0, # 'ф' + 26: 2, # 'х' + 28: 1, # 'ц' + 22: 1, # 'ч' + 25: 2, # 'ш' + 29: 3, # 'щ' + 54: 2, # 'ъ' + 18: 3, # 'ы' + 17: 2, # 'ь' + 30: 1, # 'э' + 27: 2, # 'ю' + 16: 3, # 'я' + }, + 10: { # 'в' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 2, # 'б' + 10: 2, # 'в' + 19: 2, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 1, # 'ж' + 20: 3, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 2, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 3, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 3, # 'у' + 39: 1, # 'ф' + 26: 2, # 'х' + 28: 2, # 'ц' + 22: 2, # 'ч' + 25: 3, # 'ш' + 29: 2, # 'щ' + 54: 2, # 'ъ' + 18: 3, # 'ы' + 17: 3, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 3, # 'я' + }, + 19: { # 'г' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 1, # 'б' + 10: 2, # 'в' + 19: 1, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 0, # 'ж' + 20: 1, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 2, # 'к' + 8: 3, # 'л' + 12: 2, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 3, # 'р' + 7: 2, # 'с' + 6: 2, # 'т' + 14: 3, # 'у' + 39: 1, # 'ф' + 26: 1, # 'х' + 28: 1, # 'ц' + 22: 2, # 'ч' + 25: 1, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 1, # 'ы' + 17: 1, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 0, # 'я' + }, + 13: { # 'д' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 2, # 'б' + 10: 3, # 'в' + 19: 2, # 'г' + 13: 2, # 'д' + 2: 3, # 'е' + 24: 2, # 'ж' + 20: 2, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 2, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 2, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 3, # 'у' + 39: 1, # 'ф' + 26: 2, # 'х' + 28: 3, # 'ц' + 22: 2, # 'ч' + 25: 2, # 'ш' + 29: 1, # 'щ' + 54: 2, # 'ъ' + 18: 3, # 'ы' + 17: 3, # 'ь' + 30: 1, # 'э' + 27: 2, # 'ю' + 16: 3, # 'я' + }, + 2: { # 'е' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 2, # 'а' + 21: 3, # 'б' + 10: 3, # 'в' + 19: 3, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 3, # 'ж' + 20: 3, # 'з' + 4: 2, # 'и' + 23: 3, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 3, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 3, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 2, # 'у' + 39: 2, # 'ф' + 26: 3, # 'х' + 28: 3, # 'ц' + 22: 3, # 'ч' + 25: 3, # 'ш' + 29: 3, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 1, # 'э' + 27: 2, # 'ю' + 16: 3, # 'я' + }, + 24: { # 'ж' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 2, # 'б' + 10: 1, # 'в' + 19: 2, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 2, # 'ж' + 20: 1, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 2, # 'к' + 8: 2, # 'л' + 12: 1, # 'м' + 5: 3, # 'н' + 1: 2, # 'о' + 15: 1, # 'п' + 9: 2, # 'р' + 7: 2, # 'с' + 6: 1, # 'т' + 14: 3, # 'у' + 39: 1, # 'ф' + 26: 0, # 'х' + 28: 1, # 'ц' + 22: 2, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 1, # 'ы' + 17: 2, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 1, # 'я' + }, + 20: { # 'з' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 3, # 'б' + 10: 3, # 'в' + 19: 3, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 2, # 'ж' + 20: 2, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 3, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 3, # 'р' + 7: 2, # 'с' + 6: 2, # 'т' + 14: 3, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 1, # 'ц' + 22: 2, # 'ч' + 25: 1, # 'ш' + 29: 0, # 'щ' + 54: 2, # 'ъ' + 18: 3, # 'ы' + 17: 2, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 3, # 'я' + }, + 4: { # 'и' + 37: 1, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 1, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 3, # 'б' + 10: 3, # 'в' + 19: 3, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 3, # 'ж' + 20: 3, # 'з' + 4: 3, # 'и' + 23: 3, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 3, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 3, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 2, # 'у' + 39: 2, # 'ф' + 26: 3, # 'х' + 28: 3, # 'ц' + 22: 3, # 'ч' + 25: 3, # 'ш' + 29: 3, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 2, # 'э' + 27: 3, # 'ю' + 16: 3, # 'я' + }, + 23: { # 'й' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 1, # 'а' + 21: 1, # 'б' + 10: 1, # 'в' + 19: 2, # 'г' + 13: 3, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 2, # 'з' + 4: 1, # 'и' + 23: 0, # 'й' + 11: 2, # 'к' + 8: 2, # 'л' + 12: 2, # 'м' + 5: 3, # 'н' + 1: 2, # 'о' + 15: 1, # 'п' + 9: 2, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 1, # 'у' + 39: 2, # 'ф' + 26: 1, # 'х' + 28: 2, # 'ц' + 22: 3, # 'ч' + 25: 2, # 'ш' + 29: 1, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 2, # 'я' + }, + 11: { # 'к' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 1, # 'б' + 10: 3, # 'в' + 19: 1, # 'г' + 13: 1, # 'д' + 2: 3, # 'е' + 24: 2, # 'ж' + 20: 2, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 2, # 'к' + 8: 3, # 'л' + 12: 1, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 3, # 'у' + 39: 1, # 'ф' + 26: 2, # 'х' + 28: 2, # 'ц' + 22: 1, # 'ч' + 25: 2, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 1, # 'ы' + 17: 1, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 1, # 'я' + }, + 8: { # 'л' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 2, # 'б' + 10: 2, # 'в' + 19: 3, # 'г' + 13: 2, # 'д' + 2: 3, # 'е' + 24: 3, # 'ж' + 20: 2, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 2, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 2, # 'п' + 9: 1, # 'р' + 7: 3, # 'с' + 6: 2, # 'т' + 14: 3, # 'у' + 39: 2, # 'ф' + 26: 2, # 'х' + 28: 1, # 'ц' + 22: 3, # 'ч' + 25: 2, # 'ш' + 29: 1, # 'щ' + 54: 0, # 'ъ' + 18: 3, # 'ы' + 17: 3, # 'ь' + 30: 1, # 'э' + 27: 3, # 'ю' + 16: 3, # 'я' + }, + 12: { # 'м' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 2, # 'б' + 10: 2, # 'в' + 19: 2, # 'г' + 13: 1, # 'д' + 2: 3, # 'е' + 24: 1, # 'ж' + 20: 1, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 2, # 'к' + 8: 3, # 'л' + 12: 2, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 2, # 'п' + 9: 2, # 'р' + 7: 3, # 'с' + 6: 2, # 'т' + 14: 3, # 'у' + 39: 2, # 'ф' + 26: 2, # 'х' + 28: 2, # 'ц' + 22: 2, # 'ч' + 25: 1, # 'ш' + 29: 1, # 'щ' + 54: 0, # 'ъ' + 18: 3, # 'ы' + 17: 2, # 'ь' + 30: 2, # 'э' + 27: 1, # 'ю' + 16: 3, # 'я' + }, + 5: { # 'н' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 2, # 'б' + 10: 2, # 'в' + 19: 3, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 2, # 'ж' + 20: 2, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 3, # 'к' + 8: 2, # 'л' + 12: 1, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 1, # 'п' + 9: 2, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 3, # 'у' + 39: 2, # 'ф' + 26: 2, # 'х' + 28: 3, # 'ц' + 22: 3, # 'ч' + 25: 2, # 'ш' + 29: 2, # 'щ' + 54: 1, # 'ъ' + 18: 3, # 'ы' + 17: 3, # 'ь' + 30: 1, # 'э' + 27: 3, # 'ю' + 16: 3, # 'я' + }, + 1: { # 'о' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 2, # 'а' + 21: 3, # 'б' + 10: 3, # 'в' + 19: 3, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 3, # 'ж' + 20: 3, # 'з' + 4: 3, # 'и' + 23: 3, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 3, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 3, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 2, # 'у' + 39: 2, # 'ф' + 26: 3, # 'х' + 28: 2, # 'ц' + 22: 3, # 'ч' + 25: 3, # 'ш' + 29: 3, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 2, # 'э' + 27: 3, # 'ю' + 16: 3, # 'я' + }, + 15: { # 'п' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 1, # 'б' + 10: 0, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 3, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 2, # 'к' + 8: 3, # 'л' + 12: 1, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 2, # 'п' + 9: 3, # 'р' + 7: 2, # 'с' + 6: 2, # 'т' + 14: 3, # 'у' + 39: 1, # 'ф' + 26: 0, # 'х' + 28: 2, # 'ц' + 22: 2, # 'ч' + 25: 1, # 'ш' + 29: 1, # 'щ' + 54: 0, # 'ъ' + 18: 3, # 'ы' + 17: 2, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 3, # 'я' + }, + 9: { # 'р' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 2, # 'б' + 10: 3, # 'в' + 19: 3, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 3, # 'ж' + 20: 2, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 3, # 'к' + 8: 2, # 'л' + 12: 3, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 2, # 'п' + 9: 2, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 3, # 'у' + 39: 2, # 'ф' + 26: 3, # 'х' + 28: 2, # 'ц' + 22: 2, # 'ч' + 25: 3, # 'ш' + 29: 2, # 'щ' + 54: 0, # 'ъ' + 18: 3, # 'ы' + 17: 3, # 'ь' + 30: 2, # 'э' + 27: 2, # 'ю' + 16: 3, # 'я' + }, + 7: { # 'с' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 1, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 2, # 'б' + 10: 3, # 'в' + 19: 2, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 2, # 'ж' + 20: 2, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 3, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 3, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 3, # 'у' + 39: 2, # 'ф' + 26: 3, # 'х' + 28: 2, # 'ц' + 22: 3, # 'ч' + 25: 2, # 'ш' + 29: 1, # 'щ' + 54: 2, # 'ъ' + 18: 3, # 'ы' + 17: 3, # 'ь' + 30: 2, # 'э' + 27: 3, # 'ю' + 16: 3, # 'я' + }, + 6: { # 'т' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 2, # 'б' + 10: 3, # 'в' + 19: 2, # 'г' + 13: 2, # 'д' + 2: 3, # 'е' + 24: 1, # 'ж' + 20: 1, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 2, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 2, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 2, # 'т' + 14: 3, # 'у' + 39: 2, # 'ф' + 26: 2, # 'х' + 28: 2, # 'ц' + 22: 2, # 'ч' + 25: 2, # 'ш' + 29: 2, # 'щ' + 54: 2, # 'ъ' + 18: 3, # 'ы' + 17: 3, # 'ь' + 30: 2, # 'э' + 27: 2, # 'ю' + 16: 3, # 'я' + }, + 14: { # 'у' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 2, # 'а' + 21: 3, # 'б' + 10: 3, # 'в' + 19: 3, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 3, # 'ж' + 20: 3, # 'з' + 4: 2, # 'и' + 23: 2, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 3, # 'м' + 5: 3, # 'н' + 1: 2, # 'о' + 15: 3, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 1, # 'у' + 39: 2, # 'ф' + 26: 3, # 'х' + 28: 2, # 'ц' + 22: 3, # 'ч' + 25: 3, # 'ш' + 29: 3, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 2, # 'э' + 27: 3, # 'ю' + 16: 2, # 'я' + }, + 39: { # 'ф' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 1, # 'б' + 10: 0, # 'в' + 19: 1, # 'г' + 13: 0, # 'д' + 2: 3, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 1, # 'к' + 8: 2, # 'л' + 12: 1, # 'м' + 5: 1, # 'н' + 1: 3, # 'о' + 15: 1, # 'п' + 9: 2, # 'р' + 7: 2, # 'с' + 6: 2, # 'т' + 14: 2, # 'у' + 39: 2, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 1, # 'ч' + 25: 1, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 2, # 'ы' + 17: 1, # 'ь' + 30: 2, # 'э' + 27: 1, # 'ю' + 16: 1, # 'я' + }, + 26: { # 'х' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 0, # 'б' + 10: 3, # 'в' + 19: 1, # 'г' + 13: 1, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 1, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 1, # 'к' + 8: 2, # 'л' + 12: 2, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 1, # 'п' + 9: 3, # 'р' + 7: 2, # 'с' + 6: 2, # 'т' + 14: 2, # 'у' + 39: 1, # 'ф' + 26: 1, # 'х' + 28: 1, # 'ц' + 22: 1, # 'ч' + 25: 2, # 'ш' + 29: 0, # 'щ' + 54: 1, # 'ъ' + 18: 0, # 'ы' + 17: 1, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 0, # 'я' + }, + 28: { # 'ц' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 1, # 'б' + 10: 2, # 'в' + 19: 1, # 'г' + 13: 1, # 'д' + 2: 3, # 'е' + 24: 0, # 'ж' + 20: 1, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 2, # 'к' + 8: 1, # 'л' + 12: 1, # 'м' + 5: 1, # 'н' + 1: 3, # 'о' + 15: 0, # 'п' + 9: 1, # 'р' + 7: 0, # 'с' + 6: 1, # 'т' + 14: 3, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 1, # 'ц' + 22: 0, # 'ч' + 25: 1, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 3, # 'ы' + 17: 1, # 'ь' + 30: 0, # 'э' + 27: 1, # 'ю' + 16: 0, # 'я' + }, + 22: { # 'ч' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 1, # 'б' + 10: 1, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 3, # 'е' + 24: 1, # 'ж' + 20: 0, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 3, # 'к' + 8: 2, # 'л' + 12: 1, # 'м' + 5: 3, # 'н' + 1: 2, # 'о' + 15: 0, # 'п' + 9: 2, # 'р' + 7: 1, # 'с' + 6: 3, # 'т' + 14: 3, # 'у' + 39: 1, # 'ф' + 26: 1, # 'х' + 28: 0, # 'ц' + 22: 1, # 'ч' + 25: 2, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 3, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 25: { # 'ш' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 1, # 'б' + 10: 2, # 'в' + 19: 1, # 'г' + 13: 0, # 'д' + 2: 3, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 2, # 'м' + 5: 3, # 'н' + 1: 3, # 'о' + 15: 2, # 'п' + 9: 2, # 'р' + 7: 1, # 'с' + 6: 2, # 'т' + 14: 3, # 'у' + 39: 2, # 'ф' + 26: 1, # 'х' + 28: 1, # 'ц' + 22: 1, # 'ч' + 25: 1, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 3, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 0, # 'я' + }, + 29: { # 'щ' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 3, # 'а' + 21: 0, # 'б' + 10: 1, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 3, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 3, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 0, # 'л' + 12: 1, # 'м' + 5: 2, # 'н' + 1: 1, # 'о' + 15: 0, # 'п' + 9: 2, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 2, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 2, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 0, # 'я' + }, + 54: { # 'ъ' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 0, # 'а' + 21: 0, # 'б' + 10: 0, # 'в' + 19: 0, # 'г' + 13: 0, # 'д' + 2: 2, # 'е' + 24: 0, # 'ж' + 20: 0, # 'з' + 4: 0, # 'и' + 23: 0, # 'й' + 11: 0, # 'к' + 8: 0, # 'л' + 12: 0, # 'м' + 5: 0, # 'н' + 1: 0, # 'о' + 15: 0, # 'п' + 9: 0, # 'р' + 7: 0, # 'с' + 6: 0, # 'т' + 14: 0, # 'у' + 39: 0, # 'ф' + 26: 0, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 0, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 1, # 'ю' + 16: 2, # 'я' + }, + 18: { # 'ы' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 0, # 'а' + 21: 3, # 'б' + 10: 3, # 'в' + 19: 2, # 'г' + 13: 2, # 'д' + 2: 3, # 'е' + 24: 2, # 'ж' + 20: 2, # 'з' + 4: 2, # 'и' + 23: 3, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 3, # 'м' + 5: 3, # 'н' + 1: 1, # 'о' + 15: 3, # 'п' + 9: 3, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 1, # 'у' + 39: 0, # 'ф' + 26: 3, # 'х' + 28: 2, # 'ц' + 22: 3, # 'ч' + 25: 3, # 'ш' + 29: 2, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 0, # 'ю' + 16: 2, # 'я' + }, + 17: { # 'ь' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 0, # 'а' + 21: 2, # 'б' + 10: 2, # 'в' + 19: 2, # 'г' + 13: 2, # 'д' + 2: 3, # 'е' + 24: 1, # 'ж' + 20: 3, # 'з' + 4: 2, # 'и' + 23: 0, # 'й' + 11: 3, # 'к' + 8: 0, # 'л' + 12: 3, # 'м' + 5: 3, # 'н' + 1: 2, # 'о' + 15: 2, # 'п' + 9: 1, # 'р' + 7: 3, # 'с' + 6: 2, # 'т' + 14: 0, # 'у' + 39: 2, # 'ф' + 26: 1, # 'х' + 28: 2, # 'ц' + 22: 2, # 'ч' + 25: 3, # 'ш' + 29: 2, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 1, # 'э' + 27: 3, # 'ю' + 16: 3, # 'я' + }, + 30: { # 'э' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 1, # 'М' + 31: 1, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 1, # 'Р' + 32: 1, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 1, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 0, # 'а' + 21: 1, # 'б' + 10: 1, # 'в' + 19: 1, # 'г' + 13: 2, # 'д' + 2: 1, # 'е' + 24: 0, # 'ж' + 20: 1, # 'з' + 4: 0, # 'и' + 23: 2, # 'й' + 11: 2, # 'к' + 8: 2, # 'л' + 12: 2, # 'м' + 5: 2, # 'н' + 1: 0, # 'о' + 15: 2, # 'п' + 9: 2, # 'р' + 7: 2, # 'с' + 6: 3, # 'т' + 14: 1, # 'у' + 39: 2, # 'ф' + 26: 1, # 'х' + 28: 0, # 'ц' + 22: 0, # 'ч' + 25: 1, # 'ш' + 29: 0, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 1, # 'э' + 27: 1, # 'ю' + 16: 1, # 'я' + }, + 27: { # 'ю' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 2, # 'а' + 21: 3, # 'б' + 10: 1, # 'в' + 19: 2, # 'г' + 13: 3, # 'д' + 2: 1, # 'е' + 24: 2, # 'ж' + 20: 2, # 'з' + 4: 1, # 'и' + 23: 1, # 'й' + 11: 2, # 'к' + 8: 2, # 'л' + 12: 2, # 'м' + 5: 2, # 'н' + 1: 1, # 'о' + 15: 2, # 'п' + 9: 2, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 0, # 'у' + 39: 1, # 'ф' + 26: 2, # 'х' + 28: 2, # 'ц' + 22: 2, # 'ч' + 25: 2, # 'ш' + 29: 3, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 1, # 'э' + 27: 2, # 'ю' + 16: 1, # 'я' + }, + 16: { # 'я' + 37: 0, # 'А' + 44: 0, # 'Б' + 33: 0, # 'В' + 46: 0, # 'Г' + 41: 0, # 'Д' + 48: 0, # 'Е' + 56: 0, # 'Ж' + 51: 0, # 'З' + 42: 0, # 'И' + 60: 0, # 'Й' + 36: 0, # 'К' + 49: 0, # 'Л' + 38: 0, # 'М' + 31: 0, # 'Н' + 34: 0, # 'О' + 35: 0, # 'П' + 45: 0, # 'Р' + 32: 0, # 'С' + 40: 0, # 'Т' + 52: 0, # 'У' + 53: 0, # 'Ф' + 55: 0, # 'Х' + 58: 0, # 'Ц' + 50: 0, # 'Ч' + 57: 0, # 'Ш' + 63: 0, # 'Щ' + 62: 0, # 'Ы' + 61: 0, # 'Ь' + 47: 0, # 'Э' + 59: 0, # 'Ю' + 43: 0, # 'Я' + 3: 0, # 'а' + 21: 2, # 'б' + 10: 3, # 'в' + 19: 2, # 'г' + 13: 3, # 'д' + 2: 3, # 'е' + 24: 3, # 'ж' + 20: 3, # 'з' + 4: 2, # 'и' + 23: 2, # 'й' + 11: 3, # 'к' + 8: 3, # 'л' + 12: 3, # 'м' + 5: 3, # 'н' + 1: 0, # 'о' + 15: 2, # 'п' + 9: 2, # 'р' + 7: 3, # 'с' + 6: 3, # 'т' + 14: 1, # 'у' + 39: 1, # 'ф' + 26: 3, # 'х' + 28: 2, # 'ц' + 22: 2, # 'ч' + 25: 2, # 'ш' + 29: 3, # 'щ' + 54: 0, # 'ъ' + 18: 0, # 'ы' + 17: 0, # 'ь' + 30: 0, # 'э' + 27: 2, # 'ю' + 16: 2, # 'я' + }, +} + +# 255: Undefined characters that did not exist in training text +# 254: Carriage/Return +# 253: symbol (punctuation) that does not belong to word +# 252: 0 - 9 +# 251: Control characters + +# Character Mapping Table(s): +IBM866_RUSSIAN_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 142, # 'A' + 66: 143, # 'B' + 67: 144, # 'C' + 68: 145, # 'D' + 69: 146, # 'E' + 70: 147, # 'F' + 71: 148, # 'G' + 72: 149, # 'H' + 73: 150, # 'I' + 74: 151, # 'J' + 75: 152, # 'K' + 76: 74, # 'L' + 77: 153, # 'M' + 78: 75, # 'N' + 79: 154, # 'O' + 80: 155, # 'P' + 81: 156, # 'Q' + 82: 157, # 'R' + 83: 158, # 'S' + 84: 159, # 'T' + 85: 160, # 'U' + 86: 161, # 'V' + 87: 162, # 'W' + 88: 163, # 'X' + 89: 164, # 'Y' + 90: 165, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 71, # 'a' + 98: 172, # 'b' + 99: 66, # 'c' + 100: 173, # 'd' + 101: 65, # 'e' + 102: 174, # 'f' + 103: 76, # 'g' + 104: 175, # 'h' + 105: 64, # 'i' + 106: 176, # 'j' + 107: 177, # 'k' + 108: 77, # 'l' + 109: 72, # 'm' + 110: 178, # 'n' + 111: 69, # 'o' + 112: 67, # 'p' + 113: 179, # 'q' + 114: 78, # 'r' + 115: 73, # 's' + 116: 180, # 't' + 117: 181, # 'u' + 118: 79, # 'v' + 119: 182, # 'w' + 120: 183, # 'x' + 121: 184, # 'y' + 122: 185, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 37, # 'А' + 129: 44, # 'Б' + 130: 33, # 'В' + 131: 46, # 'Г' + 132: 41, # 'Д' + 133: 48, # 'Е' + 134: 56, # 'Ж' + 135: 51, # 'З' + 136: 42, # 'И' + 137: 60, # 'Й' + 138: 36, # 'К' + 139: 49, # 'Л' + 140: 38, # 'М' + 141: 31, # 'Н' + 142: 34, # 'О' + 143: 35, # 'П' + 144: 45, # 'Р' + 145: 32, # 'С' + 146: 40, # 'Т' + 147: 52, # 'У' + 148: 53, # 'Ф' + 149: 55, # 'Х' + 150: 58, # 'Ц' + 151: 50, # 'Ч' + 152: 57, # 'Ш' + 153: 63, # 'Щ' + 154: 70, # 'Ъ' + 155: 62, # 'Ы' + 156: 61, # 'Ь' + 157: 47, # 'Э' + 158: 59, # 'Ю' + 159: 43, # 'Я' + 160: 3, # 'а' + 161: 21, # 'б' + 162: 10, # 'в' + 163: 19, # 'г' + 164: 13, # 'д' + 165: 2, # 'е' + 166: 24, # 'ж' + 167: 20, # 'з' + 168: 4, # 'и' + 169: 23, # 'й' + 170: 11, # 'к' + 171: 8, # 'л' + 172: 12, # 'м' + 173: 5, # 'н' + 174: 1, # 'о' + 175: 15, # 'п' + 176: 191, # '░' + 177: 192, # '▒' + 178: 193, # '▓' + 179: 194, # '│' + 180: 195, # '┤' + 181: 196, # '╡' + 182: 197, # '╢' + 183: 198, # '╖' + 184: 199, # '╕' + 185: 200, # '╣' + 186: 201, # '║' + 187: 202, # '╗' + 188: 203, # '╝' + 189: 204, # '╜' + 190: 205, # '╛' + 191: 206, # '┐' + 192: 207, # '└' + 193: 208, # '┴' + 194: 209, # '┬' + 195: 210, # '├' + 196: 211, # '─' + 197: 212, # '┼' + 198: 213, # '╞' + 199: 214, # '╟' + 200: 215, # '╚' + 201: 216, # '╔' + 202: 217, # '╩' + 203: 218, # '╦' + 204: 219, # '╠' + 205: 220, # '═' + 206: 221, # '╬' + 207: 222, # '╧' + 208: 223, # '╨' + 209: 224, # '╤' + 210: 225, # '╥' + 211: 226, # '╙' + 212: 227, # '╘' + 213: 228, # '╒' + 214: 229, # '╓' + 215: 230, # '╫' + 216: 231, # '╪' + 217: 232, # '┘' + 218: 233, # '┌' + 219: 234, # '█' + 220: 235, # '▄' + 221: 236, # '▌' + 222: 237, # '▐' + 223: 238, # '▀' + 224: 9, # 'р' + 225: 7, # 'с' + 226: 6, # 'т' + 227: 14, # 'у' + 228: 39, # 'ф' + 229: 26, # 'х' + 230: 28, # 'ц' + 231: 22, # 'ч' + 232: 25, # 'ш' + 233: 29, # 'щ' + 234: 54, # 'ъ' + 235: 18, # 'ы' + 236: 17, # 'ь' + 237: 30, # 'э' + 238: 27, # 'ю' + 239: 16, # 'я' + 240: 239, # 'Ё' + 241: 68, # 'ё' + 242: 240, # 'Є' + 243: 241, # 'є' + 244: 242, # 'Ї' + 245: 243, # 'ї' + 246: 244, # 'Ў' + 247: 245, # 'ў' + 248: 246, # '°' + 249: 247, # '∙' + 250: 248, # '·' + 251: 249, # '√' + 252: 250, # '№' + 253: 251, # '¤' + 254: 252, # '■' + 255: 255, # '\xa0' +} + +IBM866_RUSSIAN_MODEL = SingleByteCharSetModel(charset_name='IBM866', + language='Russian', + char_to_order_map=IBM866_RUSSIAN_CHAR_TO_ORDER, + language_model=RUSSIAN_LANG_MODEL, + typical_positive_ratio=0.976601, + keep_ascii_letters=False, + alphabet='ЁАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяё') + +WINDOWS_1251_RUSSIAN_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 142, # 'A' + 66: 143, # 'B' + 67: 144, # 'C' + 68: 145, # 'D' + 69: 146, # 'E' + 70: 147, # 'F' + 71: 148, # 'G' + 72: 149, # 'H' + 73: 150, # 'I' + 74: 151, # 'J' + 75: 152, # 'K' + 76: 74, # 'L' + 77: 153, # 'M' + 78: 75, # 'N' + 79: 154, # 'O' + 80: 155, # 'P' + 81: 156, # 'Q' + 82: 157, # 'R' + 83: 158, # 'S' + 84: 159, # 'T' + 85: 160, # 'U' + 86: 161, # 'V' + 87: 162, # 'W' + 88: 163, # 'X' + 89: 164, # 'Y' + 90: 165, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 71, # 'a' + 98: 172, # 'b' + 99: 66, # 'c' + 100: 173, # 'd' + 101: 65, # 'e' + 102: 174, # 'f' + 103: 76, # 'g' + 104: 175, # 'h' + 105: 64, # 'i' + 106: 176, # 'j' + 107: 177, # 'k' + 108: 77, # 'l' + 109: 72, # 'm' + 110: 178, # 'n' + 111: 69, # 'o' + 112: 67, # 'p' + 113: 179, # 'q' + 114: 78, # 'r' + 115: 73, # 's' + 116: 180, # 't' + 117: 181, # 'u' + 118: 79, # 'v' + 119: 182, # 'w' + 120: 183, # 'x' + 121: 184, # 'y' + 122: 185, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 191, # 'Ђ' + 129: 192, # 'Ѓ' + 130: 193, # '‚' + 131: 194, # 'ѓ' + 132: 195, # '„' + 133: 196, # '…' + 134: 197, # '†' + 135: 198, # '‡' + 136: 199, # '€' + 137: 200, # '‰' + 138: 201, # 'Љ' + 139: 202, # '‹' + 140: 203, # 'Њ' + 141: 204, # 'Ќ' + 142: 205, # 'Ћ' + 143: 206, # 'Џ' + 144: 207, # 'ђ' + 145: 208, # '‘' + 146: 209, # '’' + 147: 210, # '“' + 148: 211, # '”' + 149: 212, # '•' + 150: 213, # '–' + 151: 214, # '—' + 152: 215, # None + 153: 216, # '™' + 154: 217, # 'љ' + 155: 218, # '›' + 156: 219, # 'њ' + 157: 220, # 'ќ' + 158: 221, # 'ћ' + 159: 222, # 'џ' + 160: 223, # '\xa0' + 161: 224, # 'Ў' + 162: 225, # 'ў' + 163: 226, # 'Ј' + 164: 227, # '¤' + 165: 228, # 'Ґ' + 166: 229, # '¦' + 167: 230, # '§' + 168: 231, # 'Ё' + 169: 232, # '©' + 170: 233, # 'Є' + 171: 234, # '«' + 172: 235, # '¬' + 173: 236, # '\xad' + 174: 237, # '®' + 175: 238, # 'Ї' + 176: 239, # '°' + 177: 240, # '±' + 178: 241, # 'І' + 179: 242, # 'і' + 180: 243, # 'ґ' + 181: 244, # 'µ' + 182: 245, # '¶' + 183: 246, # '·' + 184: 68, # 'ё' + 185: 247, # '№' + 186: 248, # 'є' + 187: 249, # '»' + 188: 250, # 'ј' + 189: 251, # 'Ѕ' + 190: 252, # 'ѕ' + 191: 253, # 'ї' + 192: 37, # 'А' + 193: 44, # 'Б' + 194: 33, # 'В' + 195: 46, # 'Г' + 196: 41, # 'Д' + 197: 48, # 'Е' + 198: 56, # 'Ж' + 199: 51, # 'З' + 200: 42, # 'И' + 201: 60, # 'Й' + 202: 36, # 'К' + 203: 49, # 'Л' + 204: 38, # 'М' + 205: 31, # 'Н' + 206: 34, # 'О' + 207: 35, # 'П' + 208: 45, # 'Р' + 209: 32, # 'С' + 210: 40, # 'Т' + 211: 52, # 'У' + 212: 53, # 'Ф' + 213: 55, # 'Х' + 214: 58, # 'Ц' + 215: 50, # 'Ч' + 216: 57, # 'Ш' + 217: 63, # 'Щ' + 218: 70, # 'Ъ' + 219: 62, # 'Ы' + 220: 61, # 'Ь' + 221: 47, # 'Э' + 222: 59, # 'Ю' + 223: 43, # 'Я' + 224: 3, # 'а' + 225: 21, # 'б' + 226: 10, # 'в' + 227: 19, # 'г' + 228: 13, # 'д' + 229: 2, # 'е' + 230: 24, # 'ж' + 231: 20, # 'з' + 232: 4, # 'и' + 233: 23, # 'й' + 234: 11, # 'к' + 235: 8, # 'л' + 236: 12, # 'м' + 237: 5, # 'н' + 238: 1, # 'о' + 239: 15, # 'п' + 240: 9, # 'р' + 241: 7, # 'с' + 242: 6, # 'т' + 243: 14, # 'у' + 244: 39, # 'ф' + 245: 26, # 'х' + 246: 28, # 'ц' + 247: 22, # 'ч' + 248: 25, # 'ш' + 249: 29, # 'щ' + 250: 54, # 'ъ' + 251: 18, # 'ы' + 252: 17, # 'ь' + 253: 30, # 'э' + 254: 27, # 'ю' + 255: 16, # 'я' +} + +WINDOWS_1251_RUSSIAN_MODEL = SingleByteCharSetModel(charset_name='windows-1251', + language='Russian', + char_to_order_map=WINDOWS_1251_RUSSIAN_CHAR_TO_ORDER, + language_model=RUSSIAN_LANG_MODEL, + typical_positive_ratio=0.976601, + keep_ascii_letters=False, + alphabet='ЁАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяё') + +IBM855_RUSSIAN_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 142, # 'A' + 66: 143, # 'B' + 67: 144, # 'C' + 68: 145, # 'D' + 69: 146, # 'E' + 70: 147, # 'F' + 71: 148, # 'G' + 72: 149, # 'H' + 73: 150, # 'I' + 74: 151, # 'J' + 75: 152, # 'K' + 76: 74, # 'L' + 77: 153, # 'M' + 78: 75, # 'N' + 79: 154, # 'O' + 80: 155, # 'P' + 81: 156, # 'Q' + 82: 157, # 'R' + 83: 158, # 'S' + 84: 159, # 'T' + 85: 160, # 'U' + 86: 161, # 'V' + 87: 162, # 'W' + 88: 163, # 'X' + 89: 164, # 'Y' + 90: 165, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 71, # 'a' + 98: 172, # 'b' + 99: 66, # 'c' + 100: 173, # 'd' + 101: 65, # 'e' + 102: 174, # 'f' + 103: 76, # 'g' + 104: 175, # 'h' + 105: 64, # 'i' + 106: 176, # 'j' + 107: 177, # 'k' + 108: 77, # 'l' + 109: 72, # 'm' + 110: 178, # 'n' + 111: 69, # 'o' + 112: 67, # 'p' + 113: 179, # 'q' + 114: 78, # 'r' + 115: 73, # 's' + 116: 180, # 't' + 117: 181, # 'u' + 118: 79, # 'v' + 119: 182, # 'w' + 120: 183, # 'x' + 121: 184, # 'y' + 122: 185, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 191, # 'ђ' + 129: 192, # 'Ђ' + 130: 193, # 'ѓ' + 131: 194, # 'Ѓ' + 132: 68, # 'ё' + 133: 195, # 'Ё' + 134: 196, # 'є' + 135: 197, # 'Є' + 136: 198, # 'ѕ' + 137: 199, # 'Ѕ' + 138: 200, # 'і' + 139: 201, # 'І' + 140: 202, # 'ї' + 141: 203, # 'Ї' + 142: 204, # 'ј' + 143: 205, # 'Ј' + 144: 206, # 'љ' + 145: 207, # 'Љ' + 146: 208, # 'њ' + 147: 209, # 'Њ' + 148: 210, # 'ћ' + 149: 211, # 'Ћ' + 150: 212, # 'ќ' + 151: 213, # 'Ќ' + 152: 214, # 'ў' + 153: 215, # 'Ў' + 154: 216, # 'џ' + 155: 217, # 'Џ' + 156: 27, # 'ю' + 157: 59, # 'Ю' + 158: 54, # 'ъ' + 159: 70, # 'Ъ' + 160: 3, # 'а' + 161: 37, # 'А' + 162: 21, # 'б' + 163: 44, # 'Б' + 164: 28, # 'ц' + 165: 58, # 'Ц' + 166: 13, # 'д' + 167: 41, # 'Д' + 168: 2, # 'е' + 169: 48, # 'Е' + 170: 39, # 'ф' + 171: 53, # 'Ф' + 172: 19, # 'г' + 173: 46, # 'Г' + 174: 218, # '«' + 175: 219, # '»' + 176: 220, # '░' + 177: 221, # '▒' + 178: 222, # '▓' + 179: 223, # '│' + 180: 224, # '┤' + 181: 26, # 'х' + 182: 55, # 'Х' + 183: 4, # 'и' + 184: 42, # 'И' + 185: 225, # '╣' + 186: 226, # '║' + 187: 227, # '╗' + 188: 228, # '╝' + 189: 23, # 'й' + 190: 60, # 'Й' + 191: 229, # '┐' + 192: 230, # '└' + 193: 231, # '┴' + 194: 232, # '┬' + 195: 233, # '├' + 196: 234, # '─' + 197: 235, # '┼' + 198: 11, # 'к' + 199: 36, # 'К' + 200: 236, # '╚' + 201: 237, # '╔' + 202: 238, # '╩' + 203: 239, # '╦' + 204: 240, # '╠' + 205: 241, # '═' + 206: 242, # '╬' + 207: 243, # '¤' + 208: 8, # 'л' + 209: 49, # 'Л' + 210: 12, # 'м' + 211: 38, # 'М' + 212: 5, # 'н' + 213: 31, # 'Н' + 214: 1, # 'о' + 215: 34, # 'О' + 216: 15, # 'п' + 217: 244, # '┘' + 218: 245, # '┌' + 219: 246, # '█' + 220: 247, # '▄' + 221: 35, # 'П' + 222: 16, # 'я' + 223: 248, # '▀' + 224: 43, # 'Я' + 225: 9, # 'р' + 226: 45, # 'Р' + 227: 7, # 'с' + 228: 32, # 'С' + 229: 6, # 'т' + 230: 40, # 'Т' + 231: 14, # 'у' + 232: 52, # 'У' + 233: 24, # 'ж' + 234: 56, # 'Ж' + 235: 10, # 'в' + 236: 33, # 'В' + 237: 17, # 'ь' + 238: 61, # 'Ь' + 239: 249, # '№' + 240: 250, # '\xad' + 241: 18, # 'ы' + 242: 62, # 'Ы' + 243: 20, # 'з' + 244: 51, # 'З' + 245: 25, # 'ш' + 246: 57, # 'Ш' + 247: 30, # 'э' + 248: 47, # 'Э' + 249: 29, # 'щ' + 250: 63, # 'Щ' + 251: 22, # 'ч' + 252: 50, # 'Ч' + 253: 251, # '§' + 254: 252, # '■' + 255: 255, # '\xa0' +} + +IBM855_RUSSIAN_MODEL = SingleByteCharSetModel(charset_name='IBM855', + language='Russian', + char_to_order_map=IBM855_RUSSIAN_CHAR_TO_ORDER, + language_model=RUSSIAN_LANG_MODEL, + typical_positive_ratio=0.976601, + keep_ascii_letters=False, + alphabet='ЁАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяё') + +KOI8_R_RUSSIAN_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 142, # 'A' + 66: 143, # 'B' + 67: 144, # 'C' + 68: 145, # 'D' + 69: 146, # 'E' + 70: 147, # 'F' + 71: 148, # 'G' + 72: 149, # 'H' + 73: 150, # 'I' + 74: 151, # 'J' + 75: 152, # 'K' + 76: 74, # 'L' + 77: 153, # 'M' + 78: 75, # 'N' + 79: 154, # 'O' + 80: 155, # 'P' + 81: 156, # 'Q' + 82: 157, # 'R' + 83: 158, # 'S' + 84: 159, # 'T' + 85: 160, # 'U' + 86: 161, # 'V' + 87: 162, # 'W' + 88: 163, # 'X' + 89: 164, # 'Y' + 90: 165, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 71, # 'a' + 98: 172, # 'b' + 99: 66, # 'c' + 100: 173, # 'd' + 101: 65, # 'e' + 102: 174, # 'f' + 103: 76, # 'g' + 104: 175, # 'h' + 105: 64, # 'i' + 106: 176, # 'j' + 107: 177, # 'k' + 108: 77, # 'l' + 109: 72, # 'm' + 110: 178, # 'n' + 111: 69, # 'o' + 112: 67, # 'p' + 113: 179, # 'q' + 114: 78, # 'r' + 115: 73, # 's' + 116: 180, # 't' + 117: 181, # 'u' + 118: 79, # 'v' + 119: 182, # 'w' + 120: 183, # 'x' + 121: 184, # 'y' + 122: 185, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 191, # '─' + 129: 192, # '│' + 130: 193, # '┌' + 131: 194, # '┐' + 132: 195, # '└' + 133: 196, # '┘' + 134: 197, # '├' + 135: 198, # '┤' + 136: 199, # '┬' + 137: 200, # '┴' + 138: 201, # '┼' + 139: 202, # '▀' + 140: 203, # '▄' + 141: 204, # '█' + 142: 205, # '▌' + 143: 206, # '▐' + 144: 207, # '░' + 145: 208, # '▒' + 146: 209, # '▓' + 147: 210, # '⌠' + 148: 211, # '■' + 149: 212, # '∙' + 150: 213, # '√' + 151: 214, # '≈' + 152: 215, # '≤' + 153: 216, # '≥' + 154: 217, # '\xa0' + 155: 218, # '⌡' + 156: 219, # '°' + 157: 220, # '²' + 158: 221, # '·' + 159: 222, # '÷' + 160: 223, # '═' + 161: 224, # '║' + 162: 225, # '╒' + 163: 68, # 'ё' + 164: 226, # '╓' + 165: 227, # '╔' + 166: 228, # '╕' + 167: 229, # '╖' + 168: 230, # '╗' + 169: 231, # '╘' + 170: 232, # '╙' + 171: 233, # '╚' + 172: 234, # '╛' + 173: 235, # '╜' + 174: 236, # '╝' + 175: 237, # '╞' + 176: 238, # '╟' + 177: 239, # '╠' + 178: 240, # '╡' + 179: 241, # 'Ё' + 180: 242, # '╢' + 181: 243, # '╣' + 182: 244, # '╤' + 183: 245, # '╥' + 184: 246, # '╦' + 185: 247, # '╧' + 186: 248, # '╨' + 187: 249, # '╩' + 188: 250, # '╪' + 189: 251, # '╫' + 190: 252, # '╬' + 191: 253, # '©' + 192: 27, # 'ю' + 193: 3, # 'а' + 194: 21, # 'б' + 195: 28, # 'ц' + 196: 13, # 'д' + 197: 2, # 'е' + 198: 39, # 'ф' + 199: 19, # 'г' + 200: 26, # 'х' + 201: 4, # 'и' + 202: 23, # 'й' + 203: 11, # 'к' + 204: 8, # 'л' + 205: 12, # 'м' + 206: 5, # 'н' + 207: 1, # 'о' + 208: 15, # 'п' + 209: 16, # 'я' + 210: 9, # 'р' + 211: 7, # 'с' + 212: 6, # 'т' + 213: 14, # 'у' + 214: 24, # 'ж' + 215: 10, # 'в' + 216: 17, # 'ь' + 217: 18, # 'ы' + 218: 20, # 'з' + 219: 25, # 'ш' + 220: 30, # 'э' + 221: 29, # 'щ' + 222: 22, # 'ч' + 223: 54, # 'ъ' + 224: 59, # 'Ю' + 225: 37, # 'А' + 226: 44, # 'Б' + 227: 58, # 'Ц' + 228: 41, # 'Д' + 229: 48, # 'Е' + 230: 53, # 'Ф' + 231: 46, # 'Г' + 232: 55, # 'Х' + 233: 42, # 'И' + 234: 60, # 'Й' + 235: 36, # 'К' + 236: 49, # 'Л' + 237: 38, # 'М' + 238: 31, # 'Н' + 239: 34, # 'О' + 240: 35, # 'П' + 241: 43, # 'Я' + 242: 45, # 'Р' + 243: 32, # 'С' + 244: 40, # 'Т' + 245: 52, # 'У' + 246: 56, # 'Ж' + 247: 33, # 'В' + 248: 61, # 'Ь' + 249: 62, # 'Ы' + 250: 51, # 'З' + 251: 57, # 'Ш' + 252: 47, # 'Э' + 253: 63, # 'Щ' + 254: 50, # 'Ч' + 255: 70, # 'Ъ' +} + +KOI8_R_RUSSIAN_MODEL = SingleByteCharSetModel(charset_name='KOI8-R', + language='Russian', + char_to_order_map=KOI8_R_RUSSIAN_CHAR_TO_ORDER, + language_model=RUSSIAN_LANG_MODEL, + typical_positive_ratio=0.976601, + keep_ascii_letters=False, + alphabet='ЁАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяё') + +MACCYRILLIC_RUSSIAN_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 142, # 'A' + 66: 143, # 'B' + 67: 144, # 'C' + 68: 145, # 'D' + 69: 146, # 'E' + 70: 147, # 'F' + 71: 148, # 'G' + 72: 149, # 'H' + 73: 150, # 'I' + 74: 151, # 'J' + 75: 152, # 'K' + 76: 74, # 'L' + 77: 153, # 'M' + 78: 75, # 'N' + 79: 154, # 'O' + 80: 155, # 'P' + 81: 156, # 'Q' + 82: 157, # 'R' + 83: 158, # 'S' + 84: 159, # 'T' + 85: 160, # 'U' + 86: 161, # 'V' + 87: 162, # 'W' + 88: 163, # 'X' + 89: 164, # 'Y' + 90: 165, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 71, # 'a' + 98: 172, # 'b' + 99: 66, # 'c' + 100: 173, # 'd' + 101: 65, # 'e' + 102: 174, # 'f' + 103: 76, # 'g' + 104: 175, # 'h' + 105: 64, # 'i' + 106: 176, # 'j' + 107: 177, # 'k' + 108: 77, # 'l' + 109: 72, # 'm' + 110: 178, # 'n' + 111: 69, # 'o' + 112: 67, # 'p' + 113: 179, # 'q' + 114: 78, # 'r' + 115: 73, # 's' + 116: 180, # 't' + 117: 181, # 'u' + 118: 79, # 'v' + 119: 182, # 'w' + 120: 183, # 'x' + 121: 184, # 'y' + 122: 185, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 37, # 'А' + 129: 44, # 'Б' + 130: 33, # 'В' + 131: 46, # 'Г' + 132: 41, # 'Д' + 133: 48, # 'Е' + 134: 56, # 'Ж' + 135: 51, # 'З' + 136: 42, # 'И' + 137: 60, # 'Й' + 138: 36, # 'К' + 139: 49, # 'Л' + 140: 38, # 'М' + 141: 31, # 'Н' + 142: 34, # 'О' + 143: 35, # 'П' + 144: 45, # 'Р' + 145: 32, # 'С' + 146: 40, # 'Т' + 147: 52, # 'У' + 148: 53, # 'Ф' + 149: 55, # 'Х' + 150: 58, # 'Ц' + 151: 50, # 'Ч' + 152: 57, # 'Ш' + 153: 63, # 'Щ' + 154: 70, # 'Ъ' + 155: 62, # 'Ы' + 156: 61, # 'Ь' + 157: 47, # 'Э' + 158: 59, # 'Ю' + 159: 43, # 'Я' + 160: 191, # '†' + 161: 192, # '°' + 162: 193, # 'Ґ' + 163: 194, # '£' + 164: 195, # '§' + 165: 196, # '•' + 166: 197, # '¶' + 167: 198, # 'І' + 168: 199, # '®' + 169: 200, # '©' + 170: 201, # '™' + 171: 202, # 'Ђ' + 172: 203, # 'ђ' + 173: 204, # '≠' + 174: 205, # 'Ѓ' + 175: 206, # 'ѓ' + 176: 207, # '∞' + 177: 208, # '±' + 178: 209, # '≤' + 179: 210, # '≥' + 180: 211, # 'і' + 181: 212, # 'µ' + 182: 213, # 'ґ' + 183: 214, # 'Ј' + 184: 215, # 'Є' + 185: 216, # 'є' + 186: 217, # 'Ї' + 187: 218, # 'ї' + 188: 219, # 'Љ' + 189: 220, # 'љ' + 190: 221, # 'Њ' + 191: 222, # 'њ' + 192: 223, # 'ј' + 193: 224, # 'Ѕ' + 194: 225, # '¬' + 195: 226, # '√' + 196: 227, # 'ƒ' + 197: 228, # '≈' + 198: 229, # '∆' + 199: 230, # '«' + 200: 231, # '»' + 201: 232, # '…' + 202: 233, # '\xa0' + 203: 234, # 'Ћ' + 204: 235, # 'ћ' + 205: 236, # 'Ќ' + 206: 237, # 'ќ' + 207: 238, # 'ѕ' + 208: 239, # '–' + 209: 240, # '—' + 210: 241, # '“' + 211: 242, # '”' + 212: 243, # '‘' + 213: 244, # '’' + 214: 245, # '÷' + 215: 246, # '„' + 216: 247, # 'Ў' + 217: 248, # 'ў' + 218: 249, # 'Џ' + 219: 250, # 'џ' + 220: 251, # '№' + 221: 252, # 'Ё' + 222: 68, # 'ё' + 223: 16, # 'я' + 224: 3, # 'а' + 225: 21, # 'б' + 226: 10, # 'в' + 227: 19, # 'г' + 228: 13, # 'д' + 229: 2, # 'е' + 230: 24, # 'ж' + 231: 20, # 'з' + 232: 4, # 'и' + 233: 23, # 'й' + 234: 11, # 'к' + 235: 8, # 'л' + 236: 12, # 'м' + 237: 5, # 'н' + 238: 1, # 'о' + 239: 15, # 'п' + 240: 9, # 'р' + 241: 7, # 'с' + 242: 6, # 'т' + 243: 14, # 'у' + 244: 39, # 'ф' + 245: 26, # 'х' + 246: 28, # 'ц' + 247: 22, # 'ч' + 248: 25, # 'ш' + 249: 29, # 'щ' + 250: 54, # 'ъ' + 251: 18, # 'ы' + 252: 17, # 'ь' + 253: 30, # 'э' + 254: 27, # 'ю' + 255: 255, # '€' +} + +MACCYRILLIC_RUSSIAN_MODEL = SingleByteCharSetModel(charset_name='MacCyrillic', + language='Russian', + char_to_order_map=MACCYRILLIC_RUSSIAN_CHAR_TO_ORDER, + language_model=RUSSIAN_LANG_MODEL, + typical_positive_ratio=0.976601, + keep_ascii_letters=False, + alphabet='ЁАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяё') + +ISO_8859_5_RUSSIAN_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 142, # 'A' + 66: 143, # 'B' + 67: 144, # 'C' + 68: 145, # 'D' + 69: 146, # 'E' + 70: 147, # 'F' + 71: 148, # 'G' + 72: 149, # 'H' + 73: 150, # 'I' + 74: 151, # 'J' + 75: 152, # 'K' + 76: 74, # 'L' + 77: 153, # 'M' + 78: 75, # 'N' + 79: 154, # 'O' + 80: 155, # 'P' + 81: 156, # 'Q' + 82: 157, # 'R' + 83: 158, # 'S' + 84: 159, # 'T' + 85: 160, # 'U' + 86: 161, # 'V' + 87: 162, # 'W' + 88: 163, # 'X' + 89: 164, # 'Y' + 90: 165, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 71, # 'a' + 98: 172, # 'b' + 99: 66, # 'c' + 100: 173, # 'd' + 101: 65, # 'e' + 102: 174, # 'f' + 103: 76, # 'g' + 104: 175, # 'h' + 105: 64, # 'i' + 106: 176, # 'j' + 107: 177, # 'k' + 108: 77, # 'l' + 109: 72, # 'm' + 110: 178, # 'n' + 111: 69, # 'o' + 112: 67, # 'p' + 113: 179, # 'q' + 114: 78, # 'r' + 115: 73, # 's' + 116: 180, # 't' + 117: 181, # 'u' + 118: 79, # 'v' + 119: 182, # 'w' + 120: 183, # 'x' + 121: 184, # 'y' + 122: 185, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 191, # '\x80' + 129: 192, # '\x81' + 130: 193, # '\x82' + 131: 194, # '\x83' + 132: 195, # '\x84' + 133: 196, # '\x85' + 134: 197, # '\x86' + 135: 198, # '\x87' + 136: 199, # '\x88' + 137: 200, # '\x89' + 138: 201, # '\x8a' + 139: 202, # '\x8b' + 140: 203, # '\x8c' + 141: 204, # '\x8d' + 142: 205, # '\x8e' + 143: 206, # '\x8f' + 144: 207, # '\x90' + 145: 208, # '\x91' + 146: 209, # '\x92' + 147: 210, # '\x93' + 148: 211, # '\x94' + 149: 212, # '\x95' + 150: 213, # '\x96' + 151: 214, # '\x97' + 152: 215, # '\x98' + 153: 216, # '\x99' + 154: 217, # '\x9a' + 155: 218, # '\x9b' + 156: 219, # '\x9c' + 157: 220, # '\x9d' + 158: 221, # '\x9e' + 159: 222, # '\x9f' + 160: 223, # '\xa0' + 161: 224, # 'Ё' + 162: 225, # 'Ђ' + 163: 226, # 'Ѓ' + 164: 227, # 'Є' + 165: 228, # 'Ѕ' + 166: 229, # 'І' + 167: 230, # 'Ї' + 168: 231, # 'Ј' + 169: 232, # 'Љ' + 170: 233, # 'Њ' + 171: 234, # 'Ћ' + 172: 235, # 'Ќ' + 173: 236, # '\xad' + 174: 237, # 'Ў' + 175: 238, # 'Џ' + 176: 37, # 'А' + 177: 44, # 'Б' + 178: 33, # 'В' + 179: 46, # 'Г' + 180: 41, # 'Д' + 181: 48, # 'Е' + 182: 56, # 'Ж' + 183: 51, # 'З' + 184: 42, # 'И' + 185: 60, # 'Й' + 186: 36, # 'К' + 187: 49, # 'Л' + 188: 38, # 'М' + 189: 31, # 'Н' + 190: 34, # 'О' + 191: 35, # 'П' + 192: 45, # 'Р' + 193: 32, # 'С' + 194: 40, # 'Т' + 195: 52, # 'У' + 196: 53, # 'Ф' + 197: 55, # 'Х' + 198: 58, # 'Ц' + 199: 50, # 'Ч' + 200: 57, # 'Ш' + 201: 63, # 'Щ' + 202: 70, # 'Ъ' + 203: 62, # 'Ы' + 204: 61, # 'Ь' + 205: 47, # 'Э' + 206: 59, # 'Ю' + 207: 43, # 'Я' + 208: 3, # 'а' + 209: 21, # 'б' + 210: 10, # 'в' + 211: 19, # 'г' + 212: 13, # 'д' + 213: 2, # 'е' + 214: 24, # 'ж' + 215: 20, # 'з' + 216: 4, # 'и' + 217: 23, # 'й' + 218: 11, # 'к' + 219: 8, # 'л' + 220: 12, # 'м' + 221: 5, # 'н' + 222: 1, # 'о' + 223: 15, # 'п' + 224: 9, # 'р' + 225: 7, # 'с' + 226: 6, # 'т' + 227: 14, # 'у' + 228: 39, # 'ф' + 229: 26, # 'х' + 230: 28, # 'ц' + 231: 22, # 'ч' + 232: 25, # 'ш' + 233: 29, # 'щ' + 234: 54, # 'ъ' + 235: 18, # 'ы' + 236: 17, # 'ь' + 237: 30, # 'э' + 238: 27, # 'ю' + 239: 16, # 'я' + 240: 239, # '№' + 241: 68, # 'ё' + 242: 240, # 'ђ' + 243: 241, # 'ѓ' + 244: 242, # 'є' + 245: 243, # 'ѕ' + 246: 244, # 'і' + 247: 245, # 'ї' + 248: 246, # 'ј' + 249: 247, # 'љ' + 250: 248, # 'њ' + 251: 249, # 'ћ' + 252: 250, # 'ќ' + 253: 251, # '§' + 254: 252, # 'ў' + 255: 255, # 'џ' +} + +ISO_8859_5_RUSSIAN_MODEL = SingleByteCharSetModel(charset_name='ISO-8859-5', + language='Russian', + char_to_order_map=ISO_8859_5_RUSSIAN_CHAR_TO_ORDER, + language_model=RUSSIAN_LANG_MODEL, + typical_positive_ratio=0.976601, + keep_ascii_letters=False, + alphabet='ЁАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяё') + diff --git a/pipenv/patched/notpip/_vendor/chardet/langthaimodel.py b/pipenv/patched/notpip/_vendor/chardet/langthaimodel.py index 15f94c2d..ef449997 100644 --- a/pipenv/patched/notpip/_vendor/chardet/langthaimodel.py +++ b/pipenv/patched/notpip/_vendor/chardet/langthaimodel.py @@ -1,199 +1,4383 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### +#!/usr/bin/env python +# -*- coding: utf-8 -*- -# 255: Control characters that usually does not exist in any text +from pipenv.patched.notpip._vendor.chardet.sbcharsetprober import SingleByteCharSetModel + + +# 3: Positive +# 2: Likely +# 1: Unlikely +# 0: Negative + +THAI_LANG_MODEL = { + 5: { # 'ก' + 5: 2, # 'ก' + 30: 2, # 'ข' + 24: 2, # 'ค' + 8: 2, # 'ง' + 26: 2, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 3, # 'ฎ' + 57: 2, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 2, # 'ณ' + 20: 2, # 'ด' + 19: 3, # 'ต' + 44: 0, # 'ถ' + 14: 2, # 'ท' + 48: 0, # 'ธ' + 3: 2, # 'น' + 17: 1, # 'บ' + 25: 2, # 'ป' + 39: 1, # 'ผ' + 62: 1, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 1, # 'ภ' + 9: 2, # 'ม' + 16: 1, # 'ย' + 2: 3, # 'ร' + 61: 2, # 'ฤ' + 15: 3, # 'ล' + 12: 3, # 'ว' + 42: 2, # 'ศ' + 46: 3, # 'ษ' + 18: 2, # 'ส' + 21: 2, # 'ห' + 4: 3, # 'อ' + 63: 1, # 'ฯ' + 22: 2, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 3, # 'ำ' + 23: 3, # 'ิ' + 13: 3, # 'ี' + 40: 0, # 'ึ' + 27: 2, # 'ื' + 32: 2, # 'ุ' + 35: 1, # 'ู' + 11: 2, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 1, # 'ใ' + 33: 2, # 'ไ' + 50: 1, # 'ๆ' + 37: 3, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 2, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 30: { # 'ข' + 5: 1, # 'ก' + 30: 0, # 'ข' + 24: 1, # 'ค' + 8: 1, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 2, # 'ณ' + 20: 0, # 'ด' + 19: 2, # 'ต' + 44: 0, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 2, # 'น' + 17: 1, # 'บ' + 25: 1, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 2, # 'ย' + 2: 1, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 2, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 1, # 'ห' + 4: 3, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 2, # 'ี' + 40: 3, # 'ึ' + 27: 1, # 'ื' + 32: 1, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 1, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 1, # '็' + 6: 2, # '่' + 7: 3, # '้' + 38: 1, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 24: { # 'ค' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 2, # 'ค' + 8: 2, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 2, # 'ณ' + 20: 2, # 'ด' + 19: 2, # 'ต' + 44: 0, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 0, # 'บ' + 25: 1, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 2, # 'ม' + 16: 2, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 3, # 'ล' + 12: 3, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 0, # 'ห' + 4: 2, # 'อ' + 63: 0, # 'ฯ' + 22: 2, # 'ะ' + 10: 3, # 'ั' + 1: 2, # 'า' + 36: 3, # 'ำ' + 23: 3, # 'ิ' + 13: 2, # 'ี' + 40: 0, # 'ึ' + 27: 3, # 'ื' + 32: 3, # 'ุ' + 35: 2, # 'ู' + 11: 1, # 'เ' + 28: 0, # 'แ' + 41: 3, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 1, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 3, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 8: { # 'ง' + 5: 3, # 'ก' + 30: 2, # 'ข' + 24: 3, # 'ค' + 8: 2, # 'ง' + 26: 2, # 'จ' + 52: 1, # 'ฉ' + 34: 2, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 2, # 'ด' + 19: 2, # 'ต' + 44: 1, # 'ถ' + 14: 3, # 'ท' + 48: 1, # 'ธ' + 3: 3, # 'น' + 17: 2, # 'บ' + 25: 2, # 'ป' + 39: 2, # 'ผ' + 62: 1, # 'ฝ' + 31: 2, # 'พ' + 54: 0, # 'ฟ' + 45: 1, # 'ภ' + 9: 2, # 'ม' + 16: 1, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 2, # 'ว' + 42: 2, # 'ศ' + 46: 1, # 'ษ' + 18: 3, # 'ส' + 21: 3, # 'ห' + 4: 2, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 1, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 2, # 'ิ' + 13: 1, # 'ี' + 40: 0, # 'ึ' + 27: 1, # 'ื' + 32: 1, # 'ุ' + 35: 0, # 'ู' + 11: 3, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 2, # 'ไ' + 50: 3, # 'ๆ' + 37: 0, # '็' + 6: 2, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 26: { # 'จ' + 5: 2, # 'ก' + 30: 1, # 'ข' + 24: 0, # 'ค' + 8: 2, # 'ง' + 26: 3, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 2, # 'ด' + 19: 1, # 'ต' + 44: 1, # 'ถ' + 14: 2, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 1, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 1, # 'ม' + 16: 1, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 1, # 'ห' + 4: 2, # 'อ' + 63: 0, # 'ฯ' + 22: 3, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 3, # 'ำ' + 23: 2, # 'ิ' + 13: 1, # 'ี' + 40: 3, # 'ึ' + 27: 1, # 'ื' + 32: 3, # 'ุ' + 35: 2, # 'ู' + 11: 1, # 'เ' + 28: 1, # 'แ' + 41: 0, # 'โ' + 29: 1, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 2, # '่' + 7: 2, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 52: { # 'ฉ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 3, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 3, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 1, # 'ม' + 16: 1, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 1, # 'ะ' + 10: 1, # 'ั' + 1: 1, # 'า' + 36: 0, # 'ำ' + 23: 1, # 'ิ' + 13: 1, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 1, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 34: { # 'ช' + 5: 1, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 1, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 1, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 2, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 2, # 'ม' + 16: 1, # 'ย' + 2: 1, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 2, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 2, # 'ั' + 1: 3, # 'า' + 36: 1, # 'ำ' + 23: 3, # 'ิ' + 13: 2, # 'ี' + 40: 0, # 'ึ' + 27: 3, # 'ื' + 32: 3, # 'ุ' + 35: 1, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 1, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 51: { # 'ซ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 1, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 1, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 0, # 'ห' + 4: 2, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 1, # 'ั' + 1: 1, # 'า' + 36: 0, # 'ำ' + 23: 1, # 'ิ' + 13: 2, # 'ี' + 40: 3, # 'ึ' + 27: 2, # 'ื' + 32: 1, # 'ุ' + 35: 1, # 'ู' + 11: 1, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 1, # '็' + 6: 1, # '่' + 7: 2, # '้' + 38: 1, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 47: { # 'ญ' + 5: 1, # 'ก' + 30: 1, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 0, # 'ซ' + 47: 3, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 1, # 'บ' + 25: 1, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 1, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 1, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 2, # 'ห' + 4: 1, # 'อ' + 63: 0, # 'ฯ' + 22: 1, # 'ะ' + 10: 2, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 1, # 'ิ' + 13: 1, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 1, # 'เ' + 28: 1, # 'แ' + 41: 0, # 'โ' + 29: 1, # 'ใ' + 33: 0, # 'ไ' + 50: 1, # 'ๆ' + 37: 0, # '็' + 6: 2, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 58: { # 'ฎ' + 5: 2, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 1, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 1, # 'ิ' + 13: 2, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 57: { # 'ฏ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 3, # 'ิ' + 13: 1, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 49: { # 'ฐ' + 5: 1, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 2, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 2, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 1, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 1, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 1, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 53: { # 'ฑ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 2, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 3, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 55: { # 'ฒ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 1, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 43: { # 'ณ' + 5: 1, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 3, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 3, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 1, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 1, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 3, # 'ะ' + 10: 0, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 1, # 'ิ' + 13: 2, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 1, # 'เ' + 28: 1, # 'แ' + 41: 0, # 'โ' + 29: 1, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 3, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 20: { # 'ด' + 5: 2, # 'ก' + 30: 2, # 'ข' + 24: 2, # 'ค' + 8: 3, # 'ง' + 26: 2, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 1, # 'ด' + 19: 2, # 'ต' + 44: 1, # 'ถ' + 14: 2, # 'ท' + 48: 0, # 'ธ' + 3: 1, # 'น' + 17: 1, # 'บ' + 25: 1, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 1, # 'ภ' + 9: 2, # 'ม' + 16: 3, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 2, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 2, # 'ห' + 4: 1, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 3, # 'ั' + 1: 2, # 'า' + 36: 2, # 'ำ' + 23: 3, # 'ิ' + 13: 3, # 'ี' + 40: 1, # 'ึ' + 27: 2, # 'ื' + 32: 3, # 'ุ' + 35: 2, # 'ู' + 11: 2, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 2, # 'ไ' + 50: 2, # 'ๆ' + 37: 2, # '็' + 6: 1, # '่' + 7: 3, # '้' + 38: 1, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 19: { # 'ต' + 5: 2, # 'ก' + 30: 1, # 'ข' + 24: 1, # 'ค' + 8: 0, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 1, # 'ด' + 19: 1, # 'ต' + 44: 2, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 2, # 'น' + 17: 1, # 'บ' + 25: 1, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 2, # 'ภ' + 9: 1, # 'ม' + 16: 1, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 3, # 'ส' + 21: 0, # 'ห' + 4: 3, # 'อ' + 63: 1, # 'ฯ' + 22: 2, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 2, # 'ำ' + 23: 3, # 'ิ' + 13: 2, # 'ี' + 40: 1, # 'ึ' + 27: 1, # 'ื' + 32: 3, # 'ุ' + 35: 2, # 'ู' + 11: 1, # 'เ' + 28: 1, # 'แ' + 41: 1, # 'โ' + 29: 1, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 2, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 2, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 44: { # 'ถ' + 5: 1, # 'ก' + 30: 0, # 'ข' + 24: 1, # 'ค' + 8: 0, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 1, # 'ต' + 44: 0, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 1, # 'น' + 17: 2, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 1, # 'ร' + 61: 0, # 'ฤ' + 15: 1, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 0, # 'ห' + 4: 1, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 2, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 2, # 'ิ' + 13: 1, # 'ี' + 40: 3, # 'ึ' + 27: 2, # 'ื' + 32: 2, # 'ุ' + 35: 3, # 'ู' + 11: 1, # 'เ' + 28: 1, # 'แ' + 41: 0, # 'โ' + 29: 1, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 2, # '่' + 7: 3, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 14: { # 'ท' + 5: 1, # 'ก' + 30: 1, # 'ข' + 24: 3, # 'ค' + 8: 1, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 2, # 'ด' + 19: 1, # 'ต' + 44: 0, # 'ถ' + 14: 1, # 'ท' + 48: 3, # 'ธ' + 3: 3, # 'น' + 17: 2, # 'บ' + 25: 2, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 2, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 1, # 'ม' + 16: 3, # 'ย' + 2: 3, # 'ร' + 61: 1, # 'ฤ' + 15: 1, # 'ล' + 12: 2, # 'ว' + 42: 3, # 'ศ' + 46: 1, # 'ษ' + 18: 1, # 'ส' + 21: 0, # 'ห' + 4: 2, # 'อ' + 63: 0, # 'ฯ' + 22: 2, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 3, # 'ำ' + 23: 2, # 'ิ' + 13: 3, # 'ี' + 40: 2, # 'ึ' + 27: 1, # 'ื' + 32: 3, # 'ุ' + 35: 1, # 'ู' + 11: 0, # 'เ' + 28: 1, # 'แ' + 41: 0, # 'โ' + 29: 1, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 1, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 2, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 48: { # 'ธ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 1, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 1, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 2, # 'า' + 36: 0, # 'ำ' + 23: 3, # 'ิ' + 13: 3, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 2, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 3, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 3: { # 'น' + 5: 3, # 'ก' + 30: 2, # 'ข' + 24: 3, # 'ค' + 8: 1, # 'ง' + 26: 2, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 1, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 3, # 'ด' + 19: 3, # 'ต' + 44: 2, # 'ถ' + 14: 3, # 'ท' + 48: 3, # 'ธ' + 3: 2, # 'น' + 17: 2, # 'บ' + 25: 2, # 'ป' + 39: 2, # 'ผ' + 62: 0, # 'ฝ' + 31: 2, # 'พ' + 54: 1, # 'ฟ' + 45: 1, # 'ภ' + 9: 2, # 'ม' + 16: 2, # 'ย' + 2: 2, # 'ร' + 61: 1, # 'ฤ' + 15: 2, # 'ล' + 12: 3, # 'ว' + 42: 1, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 2, # 'ห' + 4: 3, # 'อ' + 63: 1, # 'ฯ' + 22: 2, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 3, # 'ำ' + 23: 3, # 'ิ' + 13: 3, # 'ี' + 40: 3, # 'ึ' + 27: 3, # 'ื' + 32: 3, # 'ุ' + 35: 2, # 'ู' + 11: 3, # 'เ' + 28: 2, # 'แ' + 41: 3, # 'โ' + 29: 3, # 'ใ' + 33: 3, # 'ไ' + 50: 2, # 'ๆ' + 37: 1, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 2, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 17: { # 'บ' + 5: 3, # 'ก' + 30: 2, # 'ข' + 24: 2, # 'ค' + 8: 1, # 'ง' + 26: 1, # 'จ' + 52: 1, # 'ฉ' + 34: 1, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 1, # 'ด' + 19: 2, # 'ต' + 44: 1, # 'ถ' + 14: 3, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 3, # 'บ' + 25: 2, # 'ป' + 39: 2, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 1, # 'ฟ' + 45: 1, # 'ภ' + 9: 1, # 'ม' + 16: 0, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 3, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 2, # 'ห' + 4: 2, # 'อ' + 63: 1, # 'ฯ' + 22: 0, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 2, # 'ำ' + 23: 2, # 'ิ' + 13: 2, # 'ี' + 40: 0, # 'ึ' + 27: 2, # 'ื' + 32: 3, # 'ุ' + 35: 2, # 'ู' + 11: 2, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 2, # 'ไ' + 50: 0, # 'ๆ' + 37: 1, # '็' + 6: 2, # '่' + 7: 2, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 25: { # 'ป' + 5: 2, # 'ก' + 30: 0, # 'ข' + 24: 1, # 'ค' + 8: 0, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 1, # 'ฎ' + 57: 3, # 'ฏ' + 49: 1, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 1, # 'ด' + 19: 1, # 'ต' + 44: 1, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 2, # 'น' + 17: 0, # 'บ' + 25: 1, # 'ป' + 39: 1, # 'ผ' + 62: 1, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 1, # 'ม' + 16: 0, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 3, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 1, # 'ษ' + 18: 2, # 'ส' + 21: 1, # 'ห' + 4: 2, # 'อ' + 63: 0, # 'ฯ' + 22: 1, # 'ะ' + 10: 3, # 'ั' + 1: 1, # 'า' + 36: 0, # 'ำ' + 23: 2, # 'ิ' + 13: 3, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 1, # 'ุ' + 35: 0, # 'ู' + 11: 1, # 'เ' + 28: 2, # 'แ' + 41: 0, # 'โ' + 29: 1, # 'ใ' + 33: 2, # 'ไ' + 50: 0, # 'ๆ' + 37: 3, # '็' + 6: 1, # '่' + 7: 2, # '้' + 38: 1, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 39: { # 'ผ' + 5: 1, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 1, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 2, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 1, # 'ม' + 16: 2, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 3, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 1, # 'ะ' + 10: 1, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 2, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 1, # 'ื' + 32: 0, # 'ุ' + 35: 3, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 3, # '่' + 7: 1, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 62: { # 'ฝ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 1, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 1, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 1, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 1, # 'ี' + 40: 2, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 2, # '่' + 7: 1, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 31: { # 'พ' + 5: 1, # 'ก' + 30: 1, # 'ข' + 24: 1, # 'ค' + 8: 1, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 1, # 'ณ' + 20: 1, # 'ด' + 19: 1, # 'ต' + 44: 0, # 'ถ' + 14: 2, # 'ท' + 48: 1, # 'ธ' + 3: 3, # 'น' + 17: 2, # 'บ' + 25: 0, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 1, # 'ม' + 16: 2, # 'ย' + 2: 3, # 'ร' + 61: 2, # 'ฤ' + 15: 2, # 'ล' + 12: 2, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 1, # 'ห' + 4: 2, # 'อ' + 63: 1, # 'ฯ' + 22: 0, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 3, # 'ิ' + 13: 2, # 'ี' + 40: 1, # 'ึ' + 27: 3, # 'ื' + 32: 1, # 'ุ' + 35: 2, # 'ู' + 11: 1, # 'เ' + 28: 1, # 'แ' + 41: 0, # 'โ' + 29: 1, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 1, # '็' + 6: 0, # '่' + 7: 1, # '้' + 38: 3, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 54: { # 'ฟ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 1, # 'ต' + 44: 0, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 2, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 1, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 0, # 'ห' + 4: 1, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 2, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 1, # 'ิ' + 13: 1, # 'ี' + 40: 0, # 'ึ' + 27: 1, # 'ื' + 32: 1, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 1, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 2, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 45: { # 'ภ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 1, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 3, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 1, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 1, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 2, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 1, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 9: { # 'ม' + 5: 2, # 'ก' + 30: 2, # 'ข' + 24: 2, # 'ค' + 8: 2, # 'ง' + 26: 2, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 1, # 'ณ' + 20: 2, # 'ด' + 19: 2, # 'ต' + 44: 1, # 'ถ' + 14: 2, # 'ท' + 48: 1, # 'ธ' + 3: 3, # 'น' + 17: 2, # 'บ' + 25: 2, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 3, # 'พ' + 54: 0, # 'ฟ' + 45: 1, # 'ภ' + 9: 2, # 'ม' + 16: 1, # 'ย' + 2: 2, # 'ร' + 61: 2, # 'ฤ' + 15: 2, # 'ล' + 12: 2, # 'ว' + 42: 1, # 'ศ' + 46: 1, # 'ษ' + 18: 3, # 'ส' + 21: 3, # 'ห' + 4: 3, # 'อ' + 63: 0, # 'ฯ' + 22: 1, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 3, # 'ิ' + 13: 3, # 'ี' + 40: 0, # 'ึ' + 27: 3, # 'ื' + 32: 3, # 'ุ' + 35: 3, # 'ู' + 11: 2, # 'เ' + 28: 2, # 'แ' + 41: 2, # 'โ' + 29: 2, # 'ใ' + 33: 2, # 'ไ' + 50: 1, # 'ๆ' + 37: 1, # '็' + 6: 3, # '่' + 7: 2, # '้' + 38: 1, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 16: { # 'ย' + 5: 3, # 'ก' + 30: 1, # 'ข' + 24: 2, # 'ค' + 8: 3, # 'ง' + 26: 2, # 'จ' + 52: 0, # 'ฉ' + 34: 2, # 'ช' + 51: 0, # 'ซ' + 47: 2, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 2, # 'ด' + 19: 2, # 'ต' + 44: 1, # 'ถ' + 14: 2, # 'ท' + 48: 1, # 'ธ' + 3: 3, # 'น' + 17: 3, # 'บ' + 25: 1, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 1, # 'ภ' + 9: 2, # 'ม' + 16: 0, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 1, # 'ล' + 12: 3, # 'ว' + 42: 1, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 1, # 'ห' + 4: 2, # 'อ' + 63: 0, # 'ฯ' + 22: 2, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 2, # 'ิ' + 13: 3, # 'ี' + 40: 1, # 'ึ' + 27: 2, # 'ื' + 32: 2, # 'ุ' + 35: 3, # 'ู' + 11: 2, # 'เ' + 28: 1, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 2, # 'ไ' + 50: 2, # 'ๆ' + 37: 1, # '็' + 6: 3, # '่' + 7: 2, # '้' + 38: 3, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 2: { # 'ร' + 5: 3, # 'ก' + 30: 2, # 'ข' + 24: 2, # 'ค' + 8: 3, # 'ง' + 26: 2, # 'จ' + 52: 0, # 'ฉ' + 34: 2, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 3, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 3, # 'ณ' + 20: 2, # 'ด' + 19: 2, # 'ต' + 44: 3, # 'ถ' + 14: 3, # 'ท' + 48: 1, # 'ธ' + 3: 2, # 'น' + 17: 2, # 'บ' + 25: 3, # 'ป' + 39: 2, # 'ผ' + 62: 1, # 'ฝ' + 31: 2, # 'พ' + 54: 1, # 'ฟ' + 45: 1, # 'ภ' + 9: 3, # 'ม' + 16: 2, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 3, # 'ว' + 42: 2, # 'ศ' + 46: 2, # 'ษ' + 18: 2, # 'ส' + 21: 2, # 'ห' + 4: 3, # 'อ' + 63: 1, # 'ฯ' + 22: 3, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 3, # 'ิ' + 13: 3, # 'ี' + 40: 2, # 'ึ' + 27: 3, # 'ื' + 32: 3, # 'ุ' + 35: 3, # 'ู' + 11: 3, # 'เ' + 28: 3, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 3, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 3, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 61: { # 'ฤ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 2, # 'ต' + 44: 0, # 'ถ' + 14: 2, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 1, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 2, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 15: { # 'ล' + 5: 2, # 'ก' + 30: 3, # 'ข' + 24: 1, # 'ค' + 8: 3, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 2, # 'ด' + 19: 2, # 'ต' + 44: 1, # 'ถ' + 14: 2, # 'ท' + 48: 0, # 'ธ' + 3: 1, # 'น' + 17: 2, # 'บ' + 25: 2, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 1, # 'ภ' + 9: 1, # 'ม' + 16: 3, # 'ย' + 2: 1, # 'ร' + 61: 0, # 'ฤ' + 15: 1, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 1, # 'ห' + 4: 3, # 'อ' + 63: 2, # 'ฯ' + 22: 3, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 2, # 'ำ' + 23: 3, # 'ิ' + 13: 3, # 'ี' + 40: 2, # 'ึ' + 27: 3, # 'ื' + 32: 2, # 'ุ' + 35: 3, # 'ู' + 11: 2, # 'เ' + 28: 1, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 2, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 2, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 12: { # 'ว' + 5: 3, # 'ก' + 30: 2, # 'ข' + 24: 1, # 'ค' + 8: 3, # 'ง' + 26: 2, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 1, # 'ณ' + 20: 2, # 'ด' + 19: 1, # 'ต' + 44: 1, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 2, # 'บ' + 25: 1, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 1, # 'ฟ' + 45: 0, # 'ภ' + 9: 3, # 'ม' + 16: 3, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 3, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 2, # 'ห' + 4: 2, # 'อ' + 63: 0, # 'ฯ' + 22: 2, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 3, # 'ิ' + 13: 2, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 2, # 'ุ' + 35: 0, # 'ู' + 11: 3, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 1, # 'ใ' + 33: 2, # 'ไ' + 50: 1, # 'ๆ' + 37: 0, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 1, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 42: { # 'ศ' + 5: 1, # 'ก' + 30: 0, # 'ข' + 24: 1, # 'ค' + 8: 0, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 1, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 1, # 'ต' + 44: 0, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 2, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 2, # 'ว' + 42: 1, # 'ศ' + 46: 2, # 'ษ' + 18: 1, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 2, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 2, # 'ิ' + 13: 0, # 'ี' + 40: 3, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 2, # 'ู' + 11: 0, # 'เ' + 28: 1, # 'แ' + 41: 0, # 'โ' + 29: 1, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 1, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 46: { # 'ษ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 2, # 'ฎ' + 57: 1, # 'ฏ' + 49: 2, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 3, # 'ณ' + 20: 0, # 'ด' + 19: 1, # 'ต' + 44: 0, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 1, # 'ภ' + 9: 1, # 'ม' + 16: 2, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 1, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 2, # 'ะ' + 10: 2, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 1, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 1, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 2, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 18: { # 'ส' + 5: 2, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 2, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 3, # 'ด' + 19: 3, # 'ต' + 44: 3, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 2, # 'บ' + 25: 1, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 2, # 'ภ' + 9: 3, # 'ม' + 16: 1, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 1, # 'ล' + 12: 2, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 2, # 'ห' + 4: 3, # 'อ' + 63: 0, # 'ฯ' + 22: 2, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 3, # 'ำ' + 23: 3, # 'ิ' + 13: 3, # 'ี' + 40: 2, # 'ึ' + 27: 3, # 'ื' + 32: 3, # 'ุ' + 35: 3, # 'ู' + 11: 2, # 'เ' + 28: 0, # 'แ' + 41: 1, # 'โ' + 29: 0, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 3, # '่' + 7: 1, # '้' + 38: 2, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 21: { # 'ห' + 5: 3, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 1, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 2, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 1, # 'ด' + 19: 3, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 0, # 'บ' + 25: 1, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 3, # 'ม' + 16: 2, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 3, # 'ล' + 12: 2, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 3, # 'อ' + 63: 0, # 'ฯ' + 22: 1, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 0, # 'ำ' + 23: 1, # 'ิ' + 13: 1, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 1, # 'ุ' + 35: 1, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 3, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 2, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 4: { # 'อ' + 5: 3, # 'ก' + 30: 1, # 'ข' + 24: 2, # 'ค' + 8: 3, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 3, # 'ด' + 19: 2, # 'ต' + 44: 1, # 'ถ' + 14: 2, # 'ท' + 48: 1, # 'ธ' + 3: 3, # 'น' + 17: 3, # 'บ' + 25: 1, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 1, # 'ฟ' + 45: 1, # 'ภ' + 9: 3, # 'ม' + 16: 3, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 2, # 'ว' + 42: 1, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 2, # 'ห' + 4: 3, # 'อ' + 63: 0, # 'ฯ' + 22: 2, # 'ะ' + 10: 3, # 'ั' + 1: 3, # 'า' + 36: 2, # 'ำ' + 23: 2, # 'ิ' + 13: 3, # 'ี' + 40: 0, # 'ึ' + 27: 3, # 'ื' + 32: 3, # 'ุ' + 35: 0, # 'ู' + 11: 3, # 'เ' + 28: 1, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 2, # 'ไ' + 50: 1, # 'ๆ' + 37: 1, # '็' + 6: 2, # '่' + 7: 2, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 63: { # 'ฯ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 22: { # 'ะ' + 5: 3, # 'ก' + 30: 1, # 'ข' + 24: 2, # 'ค' + 8: 1, # 'ง' + 26: 2, # 'จ' + 52: 0, # 'ฉ' + 34: 3, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 3, # 'ด' + 19: 3, # 'ต' + 44: 1, # 'ถ' + 14: 3, # 'ท' + 48: 1, # 'ธ' + 3: 2, # 'น' + 17: 3, # 'บ' + 25: 2, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 2, # 'พ' + 54: 0, # 'ฟ' + 45: 1, # 'ภ' + 9: 3, # 'ม' + 16: 2, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 2, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 3, # 'ส' + 21: 3, # 'ห' + 4: 2, # 'อ' + 63: 1, # 'ฯ' + 22: 1, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 3, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 2, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 10: { # 'ั' + 5: 3, # 'ก' + 30: 0, # 'ข' + 24: 1, # 'ค' + 8: 3, # 'ง' + 26: 3, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 0, # 'ซ' + 47: 3, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 2, # 'ฐ' + 53: 0, # 'ฑ' + 55: 3, # 'ฒ' + 43: 3, # 'ณ' + 20: 3, # 'ด' + 19: 3, # 'ต' + 44: 0, # 'ถ' + 14: 2, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 3, # 'บ' + 25: 1, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 2, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 3, # 'ม' + 16: 3, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 3, # 'ว' + 42: 2, # 'ศ' + 46: 0, # 'ษ' + 18: 3, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 1: { # 'า' + 5: 3, # 'ก' + 30: 2, # 'ข' + 24: 3, # 'ค' + 8: 3, # 'ง' + 26: 3, # 'จ' + 52: 0, # 'ฉ' + 34: 3, # 'ช' + 51: 1, # 'ซ' + 47: 2, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 3, # 'ณ' + 20: 3, # 'ด' + 19: 3, # 'ต' + 44: 1, # 'ถ' + 14: 3, # 'ท' + 48: 2, # 'ธ' + 3: 3, # 'น' + 17: 3, # 'บ' + 25: 2, # 'ป' + 39: 1, # 'ผ' + 62: 1, # 'ฝ' + 31: 3, # 'พ' + 54: 1, # 'ฟ' + 45: 1, # 'ภ' + 9: 3, # 'ม' + 16: 3, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 3, # 'ล' + 12: 3, # 'ว' + 42: 2, # 'ศ' + 46: 3, # 'ษ' + 18: 3, # 'ส' + 21: 3, # 'ห' + 4: 2, # 'อ' + 63: 1, # 'ฯ' + 22: 3, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 3, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 2, # 'ไ' + 50: 1, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 36: { # 'ำ' + 5: 2, # 'ก' + 30: 1, # 'ข' + 24: 3, # 'ค' + 8: 2, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 1, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 1, # 'ด' + 19: 1, # 'ต' + 44: 1, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 1, # 'บ' + 25: 1, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 1, # 'ภ' + 9: 1, # 'ม' + 16: 0, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 3, # 'ห' + 4: 1, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 3, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 2, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 23: { # 'ิ' + 5: 3, # 'ก' + 30: 1, # 'ข' + 24: 2, # 'ค' + 8: 3, # 'ง' + 26: 3, # 'จ' + 52: 0, # 'ฉ' + 34: 3, # 'ช' + 51: 0, # 'ซ' + 47: 2, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 3, # 'ด' + 19: 3, # 'ต' + 44: 1, # 'ถ' + 14: 3, # 'ท' + 48: 3, # 'ธ' + 3: 3, # 'น' + 17: 3, # 'บ' + 25: 2, # 'ป' + 39: 2, # 'ผ' + 62: 0, # 'ฝ' + 31: 3, # 'พ' + 54: 1, # 'ฟ' + 45: 2, # 'ภ' + 9: 3, # 'ม' + 16: 2, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 3, # 'ว' + 42: 3, # 'ศ' + 46: 2, # 'ษ' + 18: 2, # 'ส' + 21: 3, # 'ห' + 4: 1, # 'อ' + 63: 1, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 3, # 'เ' + 28: 1, # 'แ' + 41: 1, # 'โ' + 29: 1, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 3, # '่' + 7: 2, # '้' + 38: 2, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 13: { # 'ี' + 5: 3, # 'ก' + 30: 2, # 'ข' + 24: 2, # 'ค' + 8: 0, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 2, # 'ด' + 19: 1, # 'ต' + 44: 0, # 'ถ' + 14: 2, # 'ท' + 48: 0, # 'ธ' + 3: 1, # 'น' + 17: 2, # 'บ' + 25: 2, # 'ป' + 39: 1, # 'ผ' + 62: 0, # 'ฝ' + 31: 2, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 2, # 'ม' + 16: 3, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 1, # 'ล' + 12: 2, # 'ว' + 42: 1, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 1, # 'ห' + 4: 2, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 2, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 1, # 'ใ' + 33: 1, # 'ไ' + 50: 1, # 'ๆ' + 37: 0, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 40: { # 'ึ' + 5: 3, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 3, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 1, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 1, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 27: { # 'ื' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 1, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 2, # 'น' + 17: 3, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 2, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 3, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 32: { # 'ุ' + 5: 3, # 'ก' + 30: 2, # 'ข' + 24: 3, # 'ค' + 8: 3, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 2, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 1, # 'ฒ' + 43: 3, # 'ณ' + 20: 3, # 'ด' + 19: 3, # 'ต' + 44: 1, # 'ถ' + 14: 2, # 'ท' + 48: 1, # 'ธ' + 3: 2, # 'น' + 17: 2, # 'บ' + 25: 2, # 'ป' + 39: 2, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 1, # 'ภ' + 9: 3, # 'ม' + 16: 1, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 1, # 'ว' + 42: 1, # 'ศ' + 46: 2, # 'ษ' + 18: 1, # 'ส' + 21: 1, # 'ห' + 4: 1, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 1, # 'เ' + 28: 0, # 'แ' + 41: 1, # 'โ' + 29: 0, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 3, # '่' + 7: 2, # '้' + 38: 1, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 35: { # 'ู' + 5: 3, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 2, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 2, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 1, # 'ณ' + 20: 2, # 'ด' + 19: 2, # 'ต' + 44: 0, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 2, # 'น' + 17: 0, # 'บ' + 25: 3, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 2, # 'ม' + 16: 0, # 'ย' + 2: 1, # 'ร' + 61: 0, # 'ฤ' + 15: 3, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 1, # 'เ' + 28: 1, # 'แ' + 41: 1, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 3, # '่' + 7: 3, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 11: { # 'เ' + 5: 3, # 'ก' + 30: 3, # 'ข' + 24: 3, # 'ค' + 8: 2, # 'ง' + 26: 3, # 'จ' + 52: 3, # 'ฉ' + 34: 3, # 'ช' + 51: 2, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 1, # 'ณ' + 20: 3, # 'ด' + 19: 3, # 'ต' + 44: 1, # 'ถ' + 14: 3, # 'ท' + 48: 1, # 'ธ' + 3: 3, # 'น' + 17: 3, # 'บ' + 25: 3, # 'ป' + 39: 2, # 'ผ' + 62: 1, # 'ฝ' + 31: 3, # 'พ' + 54: 1, # 'ฟ' + 45: 3, # 'ภ' + 9: 3, # 'ม' + 16: 2, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 3, # 'ล' + 12: 3, # 'ว' + 42: 2, # 'ศ' + 46: 0, # 'ษ' + 18: 3, # 'ส' + 21: 3, # 'ห' + 4: 3, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 28: { # 'แ' + 5: 3, # 'ก' + 30: 2, # 'ข' + 24: 2, # 'ค' + 8: 1, # 'ง' + 26: 2, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 2, # 'ด' + 19: 3, # 'ต' + 44: 2, # 'ถ' + 14: 3, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 3, # 'บ' + 25: 2, # 'ป' + 39: 3, # 'ผ' + 62: 0, # 'ฝ' + 31: 2, # 'พ' + 54: 2, # 'ฟ' + 45: 0, # 'ภ' + 9: 2, # 'ม' + 16: 2, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 3, # 'ล' + 12: 2, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 3, # 'ส' + 21: 3, # 'ห' + 4: 1, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 41: { # 'โ' + 5: 2, # 'ก' + 30: 1, # 'ข' + 24: 2, # 'ค' + 8: 0, # 'ง' + 26: 1, # 'จ' + 52: 1, # 'ฉ' + 34: 1, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 3, # 'ด' + 19: 2, # 'ต' + 44: 0, # 'ถ' + 14: 2, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 1, # 'บ' + 25: 3, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 1, # 'ฟ' + 45: 1, # 'ภ' + 9: 1, # 'ม' + 16: 2, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 3, # 'ล' + 12: 0, # 'ว' + 42: 1, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 0, # 'ห' + 4: 2, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 29: { # 'ใ' + 5: 2, # 'ก' + 30: 0, # 'ข' + 24: 1, # 'ค' + 8: 0, # 'ง' + 26: 3, # 'จ' + 52: 0, # 'ฉ' + 34: 3, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 3, # 'ด' + 19: 1, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 2, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 1, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 3, # 'ส' + 21: 3, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 33: { # 'ไ' + 5: 1, # 'ก' + 30: 2, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 3, # 'ด' + 19: 1, # 'ต' + 44: 0, # 'ถ' + 14: 3, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 1, # 'บ' + 25: 3, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 2, # 'ฟ' + 45: 0, # 'ภ' + 9: 3, # 'ม' + 16: 0, # 'ย' + 2: 3, # 'ร' + 61: 0, # 'ฤ' + 15: 1, # 'ล' + 12: 3, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 2, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 50: { # 'ๆ' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 37: { # '็' + 5: 2, # 'ก' + 30: 1, # 'ข' + 24: 2, # 'ค' + 8: 2, # 'ง' + 26: 3, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 1, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 1, # 'ด' + 19: 2, # 'ต' + 44: 0, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 3, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 2, # 'ม' + 16: 1, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 2, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 0, # 'ห' + 4: 1, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 1, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 6: { # '่' + 5: 2, # 'ก' + 30: 1, # 'ข' + 24: 2, # 'ค' + 8: 3, # 'ง' + 26: 2, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 1, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 1, # 'ด' + 19: 2, # 'ต' + 44: 1, # 'ถ' + 14: 2, # 'ท' + 48: 1, # 'ธ' + 3: 3, # 'น' + 17: 1, # 'บ' + 25: 2, # 'ป' + 39: 2, # 'ผ' + 62: 1, # 'ฝ' + 31: 1, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 3, # 'ม' + 16: 3, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 2, # 'ล' + 12: 3, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 1, # 'ห' + 4: 3, # 'อ' + 63: 0, # 'ฯ' + 22: 1, # 'ะ' + 10: 0, # 'ั' + 1: 3, # 'า' + 36: 2, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 3, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 2, # 'ไ' + 50: 1, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 7: { # '้' + 5: 2, # 'ก' + 30: 1, # 'ข' + 24: 2, # 'ค' + 8: 3, # 'ง' + 26: 2, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 1, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 1, # 'ด' + 19: 2, # 'ต' + 44: 1, # 'ถ' + 14: 2, # 'ท' + 48: 0, # 'ธ' + 3: 3, # 'น' + 17: 2, # 'บ' + 25: 2, # 'ป' + 39: 2, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 1, # 'ฟ' + 45: 0, # 'ภ' + 9: 3, # 'ม' + 16: 2, # 'ย' + 2: 2, # 'ร' + 61: 0, # 'ฤ' + 15: 1, # 'ล' + 12: 3, # 'ว' + 42: 1, # 'ศ' + 46: 0, # 'ษ' + 18: 2, # 'ส' + 21: 2, # 'ห' + 4: 3, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 3, # 'า' + 36: 2, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 2, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 2, # 'ใ' + 33: 2, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 38: { # '์' + 5: 2, # 'ก' + 30: 1, # 'ข' + 24: 1, # 'ค' + 8: 0, # 'ง' + 26: 1, # 'จ' + 52: 0, # 'ฉ' + 34: 1, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 2, # 'ด' + 19: 1, # 'ต' + 44: 1, # 'ถ' + 14: 1, # 'ท' + 48: 0, # 'ธ' + 3: 1, # 'น' + 17: 1, # 'บ' + 25: 1, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 1, # 'พ' + 54: 1, # 'ฟ' + 45: 0, # 'ภ' + 9: 2, # 'ม' + 16: 0, # 'ย' + 2: 1, # 'ร' + 61: 1, # 'ฤ' + 15: 1, # 'ล' + 12: 1, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 1, # 'ส' + 21: 1, # 'ห' + 4: 2, # 'อ' + 63: 1, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 2, # 'เ' + 28: 2, # 'แ' + 41: 1, # 'โ' + 29: 1, # 'ใ' + 33: 1, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 0, # '๑' + 59: 0, # '๒' + 60: 0, # '๕' + }, + 56: { # '๑' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 2, # '๑' + 59: 1, # '๒' + 60: 1, # '๕' + }, + 59: { # '๒' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 1, # '๑' + 59: 1, # '๒' + 60: 3, # '๕' + }, + 60: { # '๕' + 5: 0, # 'ก' + 30: 0, # 'ข' + 24: 0, # 'ค' + 8: 0, # 'ง' + 26: 0, # 'จ' + 52: 0, # 'ฉ' + 34: 0, # 'ช' + 51: 0, # 'ซ' + 47: 0, # 'ญ' + 58: 0, # 'ฎ' + 57: 0, # 'ฏ' + 49: 0, # 'ฐ' + 53: 0, # 'ฑ' + 55: 0, # 'ฒ' + 43: 0, # 'ณ' + 20: 0, # 'ด' + 19: 0, # 'ต' + 44: 0, # 'ถ' + 14: 0, # 'ท' + 48: 0, # 'ธ' + 3: 0, # 'น' + 17: 0, # 'บ' + 25: 0, # 'ป' + 39: 0, # 'ผ' + 62: 0, # 'ฝ' + 31: 0, # 'พ' + 54: 0, # 'ฟ' + 45: 0, # 'ภ' + 9: 0, # 'ม' + 16: 0, # 'ย' + 2: 0, # 'ร' + 61: 0, # 'ฤ' + 15: 0, # 'ล' + 12: 0, # 'ว' + 42: 0, # 'ศ' + 46: 0, # 'ษ' + 18: 0, # 'ส' + 21: 0, # 'ห' + 4: 0, # 'อ' + 63: 0, # 'ฯ' + 22: 0, # 'ะ' + 10: 0, # 'ั' + 1: 0, # 'า' + 36: 0, # 'ำ' + 23: 0, # 'ิ' + 13: 0, # 'ี' + 40: 0, # 'ึ' + 27: 0, # 'ื' + 32: 0, # 'ุ' + 35: 0, # 'ู' + 11: 0, # 'เ' + 28: 0, # 'แ' + 41: 0, # 'โ' + 29: 0, # 'ใ' + 33: 0, # 'ไ' + 50: 0, # 'ๆ' + 37: 0, # '็' + 6: 0, # '่' + 7: 0, # '้' + 38: 0, # '์' + 56: 2, # '๑' + 59: 1, # '๒' + 60: 0, # '๕' + }, +} + +# 255: Undefined characters that did not exist in training text # 254: Carriage/Return # 253: symbol (punctuation) that does not belong to word # 252: 0 - 9 +# 251: Control characters -# The following result for thai was collected from a limited sample (1M). - -# Character Mapping Table: -TIS620CharToOrderMap = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253,182,106,107,100,183,184,185,101, 94,186,187,108,109,110,111, # 40 -188,189,190, 89, 95,112,113,191,192,193,194,253,253,253,253,253, # 50 -253, 64, 72, 73,114, 74,115,116,102, 81,201,117, 90,103, 78, 82, # 60 - 96,202, 91, 79, 84,104,105, 97, 98, 92,203,253,253,253,253,253, # 70 -209,210,211,212,213, 88,214,215,216,217,218,219,220,118,221,222, -223,224, 99, 85, 83,225,226,227,228,229,230,231,232,233,234,235, -236, 5, 30,237, 24,238, 75, 8, 26, 52, 34, 51,119, 47, 58, 57, - 49, 53, 55, 43, 20, 19, 44, 14, 48, 3, 17, 25, 39, 62, 31, 54, - 45, 9, 16, 2, 61, 15,239, 12, 42, 46, 18, 21, 76, 4, 66, 63, - 22, 10, 1, 36, 23, 13, 40, 27, 32, 35, 86,240,241,242,243,244, - 11, 28, 41, 29, 33,245, 50, 37, 6, 7, 67, 77, 38, 93,246,247, - 68, 56, 59, 65, 69, 60, 70, 80, 71, 87,248,249,250,251,252,253, -) - -# Model Table: -# total sequences: 100% -# first 512 sequences: 92.6386% -# first 1024 sequences:7.3177% -# rest sequences: 1.0230% -# negative sequences: 0.0436% -ThaiLangModel = ( -0,1,3,3,3,3,0,0,3,3,0,3,3,0,3,3,3,3,3,3,3,3,0,0,3,3,3,0,3,3,3,3, -0,3,3,0,0,0,1,3,0,3,3,2,3,3,0,1,2,3,3,3,3,0,2,0,2,0,0,3,2,1,2,2, -3,0,3,3,2,3,0,0,3,3,0,3,3,0,3,3,3,3,3,3,3,3,3,0,3,2,3,0,2,2,2,3, -0,2,3,0,0,0,0,1,0,1,2,3,1,1,3,2,2,0,1,1,0,0,1,0,0,0,0,0,0,0,1,1, -3,3,3,2,3,3,3,3,3,3,3,3,3,3,3,2,2,2,2,2,2,2,3,3,2,3,2,3,3,2,2,2, -3,1,2,3,0,3,3,2,2,1,2,3,3,1,2,0,1,3,0,1,0,0,1,0,0,0,0,0,0,0,1,1, -3,3,2,2,3,3,3,3,1,2,3,3,3,3,3,2,2,2,2,3,3,2,2,3,3,2,2,3,2,3,2,2, -3,3,1,2,3,1,2,2,3,3,1,0,2,1,0,0,3,1,2,1,0,0,1,0,0,0,0,0,0,1,0,1, -3,3,3,3,3,3,2,2,3,3,3,3,2,3,2,2,3,3,2,2,3,2,2,2,2,1,1,3,1,2,1,1, -3,2,1,0,2,1,0,1,0,1,1,0,1,1,0,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0, -3,3,3,2,3,2,3,3,2,2,3,2,3,3,2,3,1,1,2,3,2,2,2,3,2,2,2,2,2,1,2,1, -2,2,1,1,3,3,2,1,0,1,2,2,0,1,3,0,0,0,1,1,0,0,0,0,0,2,3,0,0,2,1,1, -3,3,2,3,3,2,0,0,3,3,0,3,3,0,2,2,3,1,2,2,1,1,1,0,2,2,2,0,2,2,1,1, -0,2,1,0,2,0,0,2,0,1,0,0,1,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,1,0, -3,3,2,3,3,2,0,0,3,3,0,2,3,0,2,1,2,2,2,2,1,2,0,0,2,2,2,0,2,2,1,1, -0,2,1,0,2,0,0,2,0,1,1,0,1,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0, -3,3,2,3,2,3,2,0,2,2,1,3,2,1,3,2,1,2,3,2,2,3,0,2,3,2,2,1,2,2,2,2, -1,2,2,0,0,0,0,2,0,1,2,0,1,1,1,0,1,0,3,1,1,0,0,0,0,0,0,0,0,0,1,0, -3,3,2,3,3,2,3,2,2,2,3,2,2,3,2,2,1,2,3,2,2,3,1,3,2,2,2,3,2,2,2,3, -3,2,1,3,0,1,1,1,0,2,1,1,1,1,1,0,1,0,1,1,0,0,0,0,0,0,0,0,0,2,0,0, -1,0,0,3,0,3,3,3,3,3,0,0,3,0,2,2,3,3,3,3,3,0,0,0,1,1,3,0,0,0,0,2, -0,0,1,0,0,0,0,0,0,0,2,3,0,0,0,3,0,2,0,0,0,0,0,3,0,0,0,0,0,0,0,0, -2,0,3,3,3,3,0,0,2,3,0,0,3,0,3,3,2,3,3,3,3,3,0,0,3,3,3,0,0,0,3,3, -0,0,3,0,0,0,0,2,0,0,2,1,1,3,0,0,1,0,0,2,3,0,1,0,0,0,0,0,0,0,1,0, -3,3,3,3,2,3,3,3,3,3,3,3,1,2,1,3,3,2,2,1,2,2,2,3,1,1,2,0,2,1,2,1, -2,2,1,0,0,0,1,1,0,1,0,1,1,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0, -3,0,2,1,2,3,3,3,0,2,0,2,2,0,2,1,3,2,2,1,2,1,0,0,2,2,1,0,2,1,2,2, -0,1,1,0,0,0,0,1,0,1,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,2,1,3,3,1,1,3,0,2,3,1,1,3,2,1,1,2,0,2,2,3,2,1,1,1,1,1,2, -3,0,0,1,3,1,2,1,2,0,3,0,0,0,1,0,3,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0, -3,3,1,1,3,2,3,3,3,1,3,2,1,3,2,1,3,2,2,2,2,1,3,3,1,2,1,3,1,2,3,0, -2,1,1,3,2,2,2,1,2,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2, -3,3,2,3,2,3,3,2,3,2,3,2,3,3,2,1,0,3,2,2,2,1,2,2,2,1,2,2,1,2,1,1, -2,2,2,3,0,1,3,1,1,1,1,0,1,1,0,2,1,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,2,3,2,2,1,1,3,2,3,2,3,2,0,3,2,2,1,2,0,2,2,2,1,2,2,2,2,1, -3,2,1,2,2,1,0,2,0,1,0,0,1,1,0,0,0,0,0,1,1,0,1,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,2,3,1,2,3,3,2,2,3,0,1,1,2,0,3,3,2,2,3,0,1,1,3,0,0,0,0, -3,1,0,3,3,0,2,0,2,1,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,2,3,2,3,3,0,1,3,1,1,2,1,2,1,1,3,1,1,0,2,3,1,1,1,1,1,1,1,1, -3,1,1,2,2,2,2,1,1,1,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -3,2,2,1,1,2,1,3,3,2,3,2,2,3,2,2,3,1,2,2,1,2,0,3,2,1,2,2,2,2,2,1, -3,2,1,2,2,2,1,1,1,1,0,0,1,1,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,1,3,3,0,2,1,0,3,2,0,0,3,1,0,1,1,0,1,0,0,0,0,0,1, -1,0,0,1,0,3,2,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,0,2,2,2,3,0,0,1,3,0,3,2,0,3,2,2,3,3,3,3,3,1,0,2,2,2,0,2,2,1,2, -0,2,3,0,0,0,0,1,0,1,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -3,0,2,3,1,3,3,2,3,3,0,3,3,0,3,2,2,3,2,3,3,3,0,0,2,2,3,0,1,1,1,3, -0,0,3,0,0,0,2,2,0,1,3,0,1,2,2,2,3,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1, -3,2,3,3,2,0,3,3,2,2,3,1,3,2,1,3,2,0,1,2,2,0,2,3,2,1,0,3,0,0,0,0, -3,0,0,2,3,1,3,0,0,3,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,1,3,2,2,2,1,2,0,1,3,1,1,3,1,3,0,0,2,1,1,1,1,2,1,1,1,0,2,1,0,1, -1,2,0,0,0,3,1,1,0,0,0,0,1,0,1,0,0,1,0,1,0,0,0,0,0,3,1,0,0,0,1,0, -3,3,3,3,2,2,2,2,2,1,3,1,1,1,2,0,1,1,2,1,2,1,3,2,0,0,3,1,1,1,1,1, -3,1,0,2,3,0,0,0,3,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,2,3,0,3,3,0,2,0,0,0,0,0,0,0,3,0,0,1,0,0,0,0,0,0,0,0,0,0,0, -0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,2,3,1,3,0,0,1,2,0,0,2,0,3,3,2,3,3,3,2,3,0,0,2,2,2,0,0,0,2,2, -0,0,1,0,0,0,0,3,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, -0,0,0,3,0,2,0,0,0,0,0,0,0,0,0,0,1,2,3,1,3,3,0,0,1,0,3,0,0,0,0,0, -0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,1,2,3,1,2,3,1,0,3,0,2,2,1,0,2,1,1,2,0,1,0,0,1,1,1,1,0,1,0,0, -1,0,0,0,0,1,1,0,3,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,2,1,0,1,1,1,3,1,2,2,2,2,2,2,1,1,1,1,0,3,1,0,1,3,1,1,1,1, -1,1,0,2,0,1,3,1,1,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,2,0,1, -3,0,2,2,1,3,3,2,3,3,0,1,1,0,2,2,1,2,1,3,3,1,0,0,3,2,0,0,0,0,2,1, -0,1,0,0,0,0,1,2,0,1,1,3,1,1,2,2,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, -0,0,3,0,0,1,0,0,0,3,0,0,3,0,3,1,0,1,1,1,3,2,0,0,0,3,0,0,0,0,2,0, -0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0,0, -3,3,1,3,2,1,3,3,1,2,2,0,1,2,1,0,1,2,0,0,0,0,0,3,0,0,0,3,0,0,0,0, -3,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,0,1,2,0,3,3,3,2,2,0,1,1,0,1,3,0,0,0,2,2,0,0,0,0,3,1,0,1,0,0,0, -0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,0,2,3,1,2,0,0,2,1,0,3,1,0,1,2,0,1,1,1,1,3,0,0,3,1,1,0,2,2,1,1, -0,2,0,0,0,0,0,1,0,1,0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,0,0,3,1,2,0,0,2,2,0,1,2,0,1,0,1,3,1,2,1,0,0,0,2,0,3,0,0,0,1,0, -0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,0,1,1,2,2,0,0,0,2,0,2,1,0,1,1,0,1,1,1,2,1,0,0,1,1,1,0,2,1,1,1, -0,1,1,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,1, -0,0,0,2,0,1,3,1,1,1,1,0,0,0,0,3,2,0,1,0,0,0,1,2,0,0,0,1,0,0,0,0, -0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,3,3,3,3,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,0,2,3,2,2,0,0,0,1,0,0,0,0,2,3,2,1,2,2,3,0,0,0,2,3,1,0,0,0,1,1, -0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,1,1,0,1,0,0,0,0,0,0,0,0,0, -3,3,2,2,0,1,0,0,0,0,2,0,2,0,1,0,0,0,1,1,0,0,0,2,1,0,1,0,1,1,0,0, -0,1,0,2,0,0,1,0,3,0,1,0,0,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,1,0,0,1,0,0,0,0,0,1,1,2,0,0,0,0,1,0,0,1,3,1,0,0,0,0,1,1,0,0, -0,1,0,0,0,0,3,0,0,0,0,0,0,3,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0, -3,3,1,1,1,1,2,3,0,0,2,1,1,1,1,1,0,2,1,1,0,0,0,2,1,0,1,2,1,1,0,1, -2,1,0,3,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,3,1,0,0,0,0,0,0,0,3,0,0,0,3,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1, -0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,2,0,0,0,0,0,0,1,2,1,0,1,1,0,2,0,0,1,0,0,2,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,2,0,0,0,1,3,0,1,0,0,0,2,0,0,0,0,0,0,0,1,2,0,0,0,0,0, -3,3,0,0,1,1,2,0,0,1,2,1,0,1,1,1,0,1,1,0,0,2,1,1,0,1,0,0,1,1,1,0, -0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,2,2,1,0,0,0,0,1,0,0,0,0,3,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0, -2,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,3,0,0,1,1,0,0,0,2,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,1,0,1,2,0,1,2,0,0,1,1,0,2,0,1,0,0,1,0,0,0,0,1,0,0,0,2,0,0,0,0, -1,0,0,1,0,1,1,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,1,0,0,0,0,0,0,0,1,1,0,1,1,0,2,1,3,0,0,0,0,1,1,0,0,0,0,0,0,0,3, -1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,0,1,0,1,0,0,2,0,0,2,0,0,1,1,2,0,0,1,1,0,0,0,1,0,0,0,1,1,0,0,0, -1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, -1,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,1,1,0,0,0, -2,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,0,0,0,0,2,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,3,0,0,0, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,1,0,0,0,0, -1,0,0,0,0,0,0,0,0,1,0,0,0,0,2,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,1,1,0,0,2,1,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -) - -TIS620ThaiModel = { - 'char_to_order_map': TIS620CharToOrderMap, - 'precedence_matrix': ThaiLangModel, - 'typical_positive_ratio': 0.926386, - 'keep_english_letter': False, - 'charset_name': "TIS-620", - 'language': 'Thai', +# Character Mapping Table(s): +TIS_620_THAI_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 254, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 254, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 253, # ' ' + 33: 253, # '!' + 34: 253, # '"' + 35: 253, # '#' + 36: 253, # '$' + 37: 253, # '%' + 38: 253, # '&' + 39: 253, # "'" + 40: 253, # '(' + 41: 253, # ')' + 42: 253, # '*' + 43: 253, # '+' + 44: 253, # ',' + 45: 253, # '-' + 46: 253, # '.' + 47: 253, # '/' + 48: 252, # '0' + 49: 252, # '1' + 50: 252, # '2' + 51: 252, # '3' + 52: 252, # '4' + 53: 252, # '5' + 54: 252, # '6' + 55: 252, # '7' + 56: 252, # '8' + 57: 252, # '9' + 58: 253, # ':' + 59: 253, # ';' + 60: 253, # '<' + 61: 253, # '=' + 62: 253, # '>' + 63: 253, # '?' + 64: 253, # '@' + 65: 182, # 'A' + 66: 106, # 'B' + 67: 107, # 'C' + 68: 100, # 'D' + 69: 183, # 'E' + 70: 184, # 'F' + 71: 185, # 'G' + 72: 101, # 'H' + 73: 94, # 'I' + 74: 186, # 'J' + 75: 187, # 'K' + 76: 108, # 'L' + 77: 109, # 'M' + 78: 110, # 'N' + 79: 111, # 'O' + 80: 188, # 'P' + 81: 189, # 'Q' + 82: 190, # 'R' + 83: 89, # 'S' + 84: 95, # 'T' + 85: 112, # 'U' + 86: 113, # 'V' + 87: 191, # 'W' + 88: 192, # 'X' + 89: 193, # 'Y' + 90: 194, # 'Z' + 91: 253, # '[' + 92: 253, # '\\' + 93: 253, # ']' + 94: 253, # '^' + 95: 253, # '_' + 96: 253, # '`' + 97: 64, # 'a' + 98: 72, # 'b' + 99: 73, # 'c' + 100: 114, # 'd' + 101: 74, # 'e' + 102: 115, # 'f' + 103: 116, # 'g' + 104: 102, # 'h' + 105: 81, # 'i' + 106: 201, # 'j' + 107: 117, # 'k' + 108: 90, # 'l' + 109: 103, # 'm' + 110: 78, # 'n' + 111: 82, # 'o' + 112: 96, # 'p' + 113: 202, # 'q' + 114: 91, # 'r' + 115: 79, # 's' + 116: 84, # 't' + 117: 104, # 'u' + 118: 105, # 'v' + 119: 97, # 'w' + 120: 98, # 'x' + 121: 92, # 'y' + 122: 203, # 'z' + 123: 253, # '{' + 124: 253, # '|' + 125: 253, # '}' + 126: 253, # '~' + 127: 253, # '\x7f' + 128: 209, # '\x80' + 129: 210, # '\x81' + 130: 211, # '\x82' + 131: 212, # '\x83' + 132: 213, # '\x84' + 133: 88, # '\x85' + 134: 214, # '\x86' + 135: 215, # '\x87' + 136: 216, # '\x88' + 137: 217, # '\x89' + 138: 218, # '\x8a' + 139: 219, # '\x8b' + 140: 220, # '\x8c' + 141: 118, # '\x8d' + 142: 221, # '\x8e' + 143: 222, # '\x8f' + 144: 223, # '\x90' + 145: 224, # '\x91' + 146: 99, # '\x92' + 147: 85, # '\x93' + 148: 83, # '\x94' + 149: 225, # '\x95' + 150: 226, # '\x96' + 151: 227, # '\x97' + 152: 228, # '\x98' + 153: 229, # '\x99' + 154: 230, # '\x9a' + 155: 231, # '\x9b' + 156: 232, # '\x9c' + 157: 233, # '\x9d' + 158: 234, # '\x9e' + 159: 235, # '\x9f' + 160: 236, # None + 161: 5, # 'ก' + 162: 30, # 'ข' + 163: 237, # 'ฃ' + 164: 24, # 'ค' + 165: 238, # 'ฅ' + 166: 75, # 'ฆ' + 167: 8, # 'ง' + 168: 26, # 'จ' + 169: 52, # 'ฉ' + 170: 34, # 'ช' + 171: 51, # 'ซ' + 172: 119, # 'ฌ' + 173: 47, # 'ญ' + 174: 58, # 'ฎ' + 175: 57, # 'ฏ' + 176: 49, # 'ฐ' + 177: 53, # 'ฑ' + 178: 55, # 'ฒ' + 179: 43, # 'ณ' + 180: 20, # 'ด' + 181: 19, # 'ต' + 182: 44, # 'ถ' + 183: 14, # 'ท' + 184: 48, # 'ธ' + 185: 3, # 'น' + 186: 17, # 'บ' + 187: 25, # 'ป' + 188: 39, # 'ผ' + 189: 62, # 'ฝ' + 190: 31, # 'พ' + 191: 54, # 'ฟ' + 192: 45, # 'ภ' + 193: 9, # 'ม' + 194: 16, # 'ย' + 195: 2, # 'ร' + 196: 61, # 'ฤ' + 197: 15, # 'ล' + 198: 239, # 'ฦ' + 199: 12, # 'ว' + 200: 42, # 'ศ' + 201: 46, # 'ษ' + 202: 18, # 'ส' + 203: 21, # 'ห' + 204: 76, # 'ฬ' + 205: 4, # 'อ' + 206: 66, # 'ฮ' + 207: 63, # 'ฯ' + 208: 22, # 'ะ' + 209: 10, # 'ั' + 210: 1, # 'า' + 211: 36, # 'ำ' + 212: 23, # 'ิ' + 213: 13, # 'ี' + 214: 40, # 'ึ' + 215: 27, # 'ื' + 216: 32, # 'ุ' + 217: 35, # 'ู' + 218: 86, # 'ฺ' + 219: 240, # None + 220: 241, # None + 221: 242, # None + 222: 243, # None + 223: 244, # '฿' + 224: 11, # 'เ' + 225: 28, # 'แ' + 226: 41, # 'โ' + 227: 29, # 'ใ' + 228: 33, # 'ไ' + 229: 245, # 'ๅ' + 230: 50, # 'ๆ' + 231: 37, # '็' + 232: 6, # '่' + 233: 7, # '้' + 234: 67, # '๊' + 235: 77, # '๋' + 236: 38, # '์' + 237: 93, # 'ํ' + 238: 246, # '๎' + 239: 247, # '๏' + 240: 68, # '๐' + 241: 56, # '๑' + 242: 59, # '๒' + 243: 65, # '๓' + 244: 69, # '๔' + 245: 60, # '๕' + 246: 70, # '๖' + 247: 80, # '๗' + 248: 71, # '๘' + 249: 87, # '๙' + 250: 248, # '๚' + 251: 249, # '๛' + 252: 250, # None + 253: 251, # None + 254: 252, # None + 255: 253, # None } + +TIS_620_THAI_MODEL = SingleByteCharSetModel(charset_name='TIS-620', + language='Thai', + char_to_order_map=TIS_620_THAI_CHAR_TO_ORDER, + language_model=THAI_LANG_MODEL, + typical_positive_ratio=0.926386, + keep_ascii_letters=False, + alphabet='กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำิีึืฺุู฿เแโใไๅๆ็่้๊๋์ํ๎๏๐๑๒๓๔๕๖๗๘๙๚๛') + diff --git a/pipenv/patched/notpip/_vendor/chardet/langturkishmodel.py b/pipenv/patched/notpip/_vendor/chardet/langturkishmodel.py index a427a457..b9fa78be 100644 --- a/pipenv/patched/notpip/_vendor/chardet/langturkishmodel.py +++ b/pipenv/patched/notpip/_vendor/chardet/langturkishmodel.py @@ -1,193 +1,4383 @@ +#!/usr/bin/env python # -*- coding: utf-8 -*- -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# Özgür Baskın - Turkish Language Model -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### -# 255: Control characters that usually does not exist in any text +from pipenv.patched.notpip._vendor.chardet.sbcharsetprober import SingleByteCharSetModel + + +# 3: Positive +# 2: Likely +# 1: Unlikely +# 0: Negative + +TURKISH_LANG_MODEL = { + 23: { # 'A' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 0, # 'c' + 12: 2, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 1, # 'g' + 25: 1, # 'h' + 3: 1, # 'i' + 24: 0, # 'j' + 10: 2, # 'k' + 5: 1, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 1, # 'r' + 8: 1, # 's' + 9: 1, # 't' + 14: 1, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 3, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 0, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 37: { # 'B' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 2, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 2, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 1, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 1, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 0, # 'Z' + 1: 2, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 0, # 'k' + 5: 0, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 2, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 1, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 1, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 0, # 'ı' + 40: 1, # 'Ş' + 19: 1, # 'ş' + }, + 47: { # 'C' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 1, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 1, # 'L' + 20: 0, # 'M' + 46: 1, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 1, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 2, # 'j' + 10: 1, # 'k' + 5: 2, # 'l' + 13: 2, # 'm' + 4: 2, # 'n' + 15: 1, # 'o' + 26: 0, # 'p' + 7: 2, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 1, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 1, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 39: { # 'D' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 1, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 1, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 2, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 2, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 0, # 'k' + 5: 1, # 'l' + 13: 3, # 'm' + 4: 0, # 'n' + 15: 1, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 1, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 1, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 0, # 'İ' + 6: 1, # 'ı' + 40: 1, # 'Ş' + 19: 0, # 'ş' + }, + 29: { # 'E' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 1, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 0, # 'c' + 12: 2, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 1, # 'g' + 25: 0, # 'h' + 3: 1, # 'i' + 24: 1, # 'j' + 10: 0, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 1, # 's' + 9: 1, # 't' + 14: 1, # 'u' + 32: 1, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 2, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 52: { # 'F' + 23: 0, # 'A' + 37: 1, # 'B' + 47: 1, # 'C' + 39: 1, # 'D' + 29: 1, # 'E' + 52: 2, # 'F' + 36: 0, # 'G' + 45: 2, # 'H' + 53: 1, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 1, # 'N' + 42: 1, # 'O' + 48: 2, # 'P' + 44: 1, # 'R' + 35: 1, # 'S' + 31: 1, # 'T' + 51: 1, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 2, # 'Y' + 56: 0, # 'Z' + 1: 0, # 'a' + 21: 1, # 'b' + 28: 1, # 'c' + 12: 1, # 'd' + 2: 0, # 'e' + 18: 1, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 2, # 'i' + 24: 1, # 'j' + 10: 0, # 'k' + 5: 0, # 'l' + 13: 1, # 'm' + 4: 2, # 'n' + 15: 1, # 'o' + 26: 0, # 'p' + 7: 2, # 'r' + 8: 1, # 's' + 9: 1, # 't' + 14: 1, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 1, # 'y' + 22: 1, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 1, # 'Ö' + 55: 2, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 2, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 1, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 2, # 'ş' + }, + 36: { # 'G' + 23: 1, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 1, # 'F' + 36: 2, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 2, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 2, # 'N' + 42: 1, # 'O' + 48: 1, # 'P' + 44: 1, # 'R' + 35: 1, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 2, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 1, # 'c' + 12: 0, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 1, # 'j' + 10: 1, # 'k' + 5: 0, # 'l' + 13: 3, # 'm' + 4: 2, # 'n' + 15: 0, # 'o' + 26: 1, # 'p' + 7: 0, # 'r' + 8: 1, # 's' + 9: 1, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 1, # 'x' + 11: 0, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 2, # 'Ö' + 55: 0, # 'Ü' + 59: 1, # 'â' + 33: 2, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 1, # 'İ' + 6: 2, # 'ı' + 40: 2, # 'Ş' + 19: 1, # 'ş' + }, + 45: { # 'H' + 23: 0, # 'A' + 37: 1, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 2, # 'F' + 36: 2, # 'G' + 45: 1, # 'H' + 53: 1, # 'I' + 60: 0, # 'J' + 16: 2, # 'K' + 49: 1, # 'L' + 20: 0, # 'M' + 46: 1, # 'N' + 42: 1, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 2, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 2, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 2, # 'i' + 24: 0, # 'j' + 10: 1, # 'k' + 5: 0, # 'l' + 13: 2, # 'm' + 4: 0, # 'n' + 15: 1, # 'o' + 26: 1, # 'p' + 7: 1, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 1, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 2, # 'ğ' + 41: 1, # 'İ' + 6: 0, # 'ı' + 40: 2, # 'Ş' + 19: 1, # 'ş' + }, + 53: { # 'I' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 1, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 2, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 2, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 2, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 0, # 'k' + 5: 2, # 'l' + 13: 2, # 'm' + 4: 0, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 2, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 2, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 0, # 'ı' + 40: 1, # 'Ş' + 19: 1, # 'ş' + }, + 60: { # 'J' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 0, # 'a' + 21: 1, # 'b' + 28: 0, # 'c' + 12: 1, # 'd' + 2: 0, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 1, # 'i' + 24: 0, # 'j' + 10: 0, # 'k' + 5: 0, # 'l' + 13: 0, # 'm' + 4: 1, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 1, # 's' + 9: 0, # 't' + 14: 0, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 0, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 16: { # 'K' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 3, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 2, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 2, # 'a' + 21: 3, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 1, # 'e' + 18: 3, # 'f' + 27: 3, # 'g' + 25: 3, # 'h' + 3: 3, # 'i' + 24: 2, # 'j' + 10: 3, # 'k' + 5: 0, # 'l' + 13: 0, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 1, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 0, # 'u' + 32: 3, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 2, # 'y' + 22: 1, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 2, # 'ü' + 30: 0, # 'ğ' + 41: 1, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 49: { # 'L' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 2, # 'E' + 52: 0, # 'F' + 36: 1, # 'G' + 45: 1, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 0, # 'N' + 42: 2, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 0, # 'Z' + 1: 0, # 'a' + 21: 3, # 'b' + 28: 0, # 'c' + 12: 2, # 'd' + 2: 0, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 2, # 'i' + 24: 0, # 'j' + 10: 1, # 'k' + 5: 0, # 'l' + 13: 0, # 'm' + 4: 2, # 'n' + 15: 1, # 'o' + 26: 1, # 'p' + 7: 1, # 'r' + 8: 1, # 's' + 9: 1, # 't' + 14: 0, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 2, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 2, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 1, # 'ü' + 30: 1, # 'ğ' + 41: 0, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 20: { # 'M' + 23: 1, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 1, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 1, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 2, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 1, # 'g' + 25: 1, # 'h' + 3: 2, # 'i' + 24: 2, # 'j' + 10: 2, # 'k' + 5: 2, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 1, # 'p' + 7: 3, # 'r' + 8: 0, # 's' + 9: 2, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 2, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 3, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 46: { # 'N' + 23: 0, # 'A' + 37: 1, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 1, # 'F' + 36: 1, # 'G' + 45: 1, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 2, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 1, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 1, # 'R' + 35: 1, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 2, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 1, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 2, # 'j' + 10: 1, # 'k' + 5: 1, # 'l' + 13: 3, # 'm' + 4: 2, # 'n' + 15: 1, # 'o' + 26: 1, # 'p' + 7: 1, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 1, # 'x' + 11: 1, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 1, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 1, # 'İ' + 6: 2, # 'ı' + 40: 1, # 'Ş' + 19: 1, # 'ş' + }, + 42: { # 'O' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 1, # 'F' + 36: 0, # 'G' + 45: 1, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 2, # 'K' + 49: 1, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 2, # 'P' + 44: 1, # 'R' + 35: 1, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 2, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 0, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 0, # 'n' + 15: 1, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 2, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 2, # 'Ç' + 50: 1, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 2, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 2, # 'İ' + 6: 1, # 'ı' + 40: 1, # 'Ş' + 19: 1, # 'ş' + }, + 48: { # 'P' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 2, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 2, # 'F' + 36: 1, # 'G' + 45: 1, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 2, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 1, # 'N' + 42: 1, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 1, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 2, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 1, # 'k' + 5: 0, # 'l' + 13: 2, # 'm' + 4: 0, # 'n' + 15: 2, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 2, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 2, # 'x' + 11: 0, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 2, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 2, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 1, # 'İ' + 6: 0, # 'ı' + 40: 2, # 'Ş' + 19: 1, # 'ş' + }, + 44: { # 'R' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 1, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 1, # 'b' + 28: 1, # 'c' + 12: 0, # 'd' + 2: 2, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 1, # 'k' + 5: 2, # 'l' + 13: 2, # 'm' + 4: 0, # 'n' + 15: 1, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 2, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 1, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 1, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 1, # 'ü' + 30: 1, # 'ğ' + 41: 0, # 'İ' + 6: 2, # 'ı' + 40: 1, # 'Ş' + 19: 1, # 'ş' + }, + 35: { # 'S' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 1, # 'F' + 36: 1, # 'G' + 45: 1, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 1, # 'L' + 20: 1, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 1, # 'k' + 5: 1, # 'l' + 13: 2, # 'm' + 4: 1, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 1, # 't' + 14: 2, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 1, # 'z' + 63: 0, # '·' + 54: 2, # 'Ç' + 50: 2, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 3, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 2, # 'Ş' + 19: 1, # 'ş' + }, + 31: { # 'T' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 1, # 'J' + 16: 2, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 2, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 2, # 'b' + 28: 0, # 'c' + 12: 1, # 'd' + 2: 3, # 'e' + 18: 2, # 'f' + 27: 2, # 'g' + 25: 0, # 'h' + 3: 1, # 'i' + 24: 1, # 'j' + 10: 2, # 'k' + 5: 2, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 2, # 'p' + 7: 2, # 'r' + 8: 0, # 's' + 9: 2, # 't' + 14: 2, # 'u' + 32: 1, # 'v' + 57: 1, # 'w' + 58: 1, # 'x' + 11: 2, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 1, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 51: { # 'U' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 1, # 'F' + 36: 1, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 1, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 1, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 1, # 'c' + 12: 0, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 2, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 1, # 'k' + 5: 1, # 'l' + 13: 3, # 'm' + 4: 2, # 'n' + 15: 0, # 'o' + 26: 1, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 2, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 1, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 1, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 1, # 'ş' + }, + 38: { # 'V' + 23: 1, # 'A' + 37: 1, # 'B' + 47: 1, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 2, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 3, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 1, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 0, # 'k' + 5: 2, # 'l' + 13: 2, # 'm' + 4: 0, # 'n' + 15: 2, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 1, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 1, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 1, # 'Ö' + 55: 0, # 'Ü' + 59: 1, # 'â' + 33: 2, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 1, # 'İ' + 6: 3, # 'ı' + 40: 2, # 'Ş' + 19: 1, # 'ş' + }, + 62: { # 'W' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 0, # 'a' + 21: 0, # 'b' + 28: 0, # 'c' + 12: 0, # 'd' + 2: 0, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 0, # 'k' + 5: 0, # 'l' + 13: 0, # 'm' + 4: 0, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 0, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 0, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 43: { # 'Y' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 2, # 'F' + 36: 0, # 'G' + 45: 1, # 'H' + 53: 1, # 'I' + 60: 0, # 'J' + 16: 2, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 2, # 'N' + 42: 0, # 'O' + 48: 2, # 'P' + 44: 1, # 'R' + 35: 1, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 2, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 2, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 1, # 'j' + 10: 1, # 'k' + 5: 1, # 'l' + 13: 3, # 'm' + 4: 0, # 'n' + 15: 2, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 1, # 'x' + 11: 0, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 2, # 'Ö' + 55: 1, # 'Ü' + 59: 1, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 1, # 'İ' + 6: 0, # 'ı' + 40: 2, # 'Ş' + 19: 1, # 'ş' + }, + 56: { # 'Z' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 2, # 'Z' + 1: 2, # 'a' + 21: 1, # 'b' + 28: 0, # 'c' + 12: 0, # 'd' + 2: 2, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 2, # 'i' + 24: 1, # 'j' + 10: 0, # 'k' + 5: 0, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 1, # 'r' + 8: 1, # 's' + 9: 0, # 't' + 14: 2, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 1, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 1, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 1: { # 'a' + 23: 3, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 0, # 'D' + 29: 3, # 'E' + 52: 0, # 'F' + 36: 1, # 'G' + 45: 1, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 3, # 'M' + 46: 1, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 3, # 'T' + 51: 0, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 2, # 'Z' + 1: 2, # 'a' + 21: 3, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 2, # 'e' + 18: 3, # 'f' + 27: 3, # 'g' + 25: 3, # 'h' + 3: 3, # 'i' + 24: 3, # 'j' + 10: 3, # 'k' + 5: 0, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 15: 1, # 'o' + 26: 3, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 3, # 'u' + 32: 3, # 'v' + 57: 2, # 'w' + 58: 0, # 'x' + 11: 3, # 'y' + 22: 0, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 1, # 'î' + 34: 1, # 'ö' + 17: 3, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 1, # 'ş' + }, + 21: { # 'b' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 1, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 1, # 'J' + 16: 2, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 1, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 2, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 3, # 'g' + 25: 1, # 'h' + 3: 3, # 'i' + 24: 2, # 'j' + 10: 3, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 3, # 'p' + 7: 1, # 'r' + 8: 2, # 's' + 9: 2, # 't' + 14: 2, # 'u' + 32: 1, # 'v' + 57: 0, # 'w' + 58: 1, # 'x' + 11: 3, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 0, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 28: { # 'c' + 23: 0, # 'A' + 37: 1, # 'B' + 47: 1, # 'C' + 39: 1, # 'D' + 29: 2, # 'E' + 52: 0, # 'F' + 36: 2, # 'G' + 45: 2, # 'H' + 53: 1, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 1, # 'N' + 42: 1, # 'O' + 48: 2, # 'P' + 44: 1, # 'R' + 35: 1, # 'S' + 31: 2, # 'T' + 51: 2, # 'U' + 38: 2, # 'V' + 62: 0, # 'W' + 43: 3, # 'Y' + 56: 0, # 'Z' + 1: 1, # 'a' + 21: 1, # 'b' + 28: 2, # 'c' + 12: 2, # 'd' + 2: 1, # 'e' + 18: 1, # 'f' + 27: 2, # 'g' + 25: 2, # 'h' + 3: 3, # 'i' + 24: 1, # 'j' + 10: 3, # 'k' + 5: 0, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 15: 2, # 'o' + 26: 2, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 1, # 'u' + 32: 0, # 'v' + 57: 1, # 'w' + 58: 0, # 'x' + 11: 2, # 'y' + 22: 1, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 1, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 1, # 'î' + 34: 2, # 'ö' + 17: 2, # 'ü' + 30: 2, # 'ğ' + 41: 1, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 2, # 'ş' + }, + 12: { # 'd' + 23: 1, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 2, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 3, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 1, # 'S' + 31: 1, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 2, # 'b' + 28: 1, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 1, # 'f' + 27: 3, # 'g' + 25: 3, # 'h' + 3: 2, # 'i' + 24: 3, # 'j' + 10: 2, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 1, # 'o' + 26: 2, # 'p' + 7: 3, # 'r' + 8: 2, # 's' + 9: 2, # 't' + 14: 3, # 'u' + 32: 1, # 'v' + 57: 0, # 'w' + 58: 1, # 'x' + 11: 3, # 'y' + 22: 1, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 1, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 2: { # 'e' + 23: 2, # 'A' + 37: 0, # 'B' + 47: 2, # 'C' + 39: 0, # 'D' + 29: 3, # 'E' + 52: 1, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 1, # 'K' + 49: 0, # 'L' + 20: 3, # 'M' + 46: 1, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 1, # 'R' + 35: 0, # 'S' + 31: 3, # 'T' + 51: 0, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 3, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 2, # 'e' + 18: 3, # 'f' + 27: 3, # 'g' + 25: 3, # 'h' + 3: 3, # 'i' + 24: 3, # 'j' + 10: 3, # 'k' + 5: 0, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 15: 1, # 'o' + 26: 3, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 3, # 'u' + 32: 3, # 'v' + 57: 2, # 'w' + 58: 0, # 'x' + 11: 3, # 'y' + 22: 1, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 3, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 18: { # 'f' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 2, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 2, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 1, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 2, # 'f' + 27: 1, # 'g' + 25: 1, # 'h' + 3: 1, # 'i' + 24: 1, # 'j' + 10: 1, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 2, # 'p' + 7: 1, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 1, # 'u' + 32: 2, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 1, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 1, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 1, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 27: { # 'g' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 1, # 'S' + 31: 1, # 'T' + 51: 0, # 'U' + 38: 2, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 1, # 'b' + 28: 0, # 'c' + 12: 1, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 2, # 'g' + 25: 1, # 'h' + 3: 2, # 'i' + 24: 3, # 'j' + 10: 2, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 2, # 'n' + 15: 0, # 'o' + 26: 1, # 'p' + 7: 2, # 'r' + 8: 2, # 's' + 9: 3, # 't' + 14: 3, # 'u' + 32: 1, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 1, # 'y' + 22: 0, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 25: { # 'h' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 2, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 0, # 'c' + 12: 2, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 1, # 'g' + 25: 2, # 'h' + 3: 2, # 'i' + 24: 3, # 'j' + 10: 3, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 1, # 'o' + 26: 1, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 2, # 't' + 14: 3, # 'u' + 32: 2, # 'v' + 57: 1, # 'w' + 58: 0, # 'x' + 11: 1, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 3: { # 'i' + 23: 2, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 1, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 3, # 'M' + 46: 0, # 'N' + 42: 1, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 1, # 'S' + 31: 2, # 'T' + 51: 0, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 2, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 2, # 'f' + 27: 3, # 'g' + 25: 1, # 'h' + 3: 3, # 'i' + 24: 2, # 'j' + 10: 3, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 1, # 'o' + 26: 3, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 3, # 'u' + 32: 2, # 'v' + 57: 1, # 'w' + 58: 1, # 'x' + 11: 3, # 'y' + 22: 1, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 1, # 'Ü' + 59: 0, # 'â' + 33: 2, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 3, # 'ü' + 30: 0, # 'ğ' + 41: 1, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 24: { # 'j' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 1, # 'J' + 16: 2, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 1, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 1, # 'Z' + 1: 3, # 'a' + 21: 1, # 'b' + 28: 1, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 2, # 'f' + 27: 1, # 'g' + 25: 1, # 'h' + 3: 2, # 'i' + 24: 1, # 'j' + 10: 2, # 'k' + 5: 2, # 'l' + 13: 3, # 'm' + 4: 2, # 'n' + 15: 0, # 'o' + 26: 1, # 'p' + 7: 2, # 'r' + 8: 3, # 's' + 9: 2, # 't' + 14: 3, # 'u' + 32: 2, # 'v' + 57: 0, # 'w' + 58: 2, # 'x' + 11: 1, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 1, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 10: { # 'k' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 3, # 'T' + 51: 0, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 1, # 'Z' + 1: 3, # 'a' + 21: 2, # 'b' + 28: 0, # 'c' + 12: 2, # 'd' + 2: 3, # 'e' + 18: 1, # 'f' + 27: 2, # 'g' + 25: 2, # 'h' + 3: 3, # 'i' + 24: 2, # 'j' + 10: 2, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 3, # 'p' + 7: 2, # 'r' + 8: 2, # 's' + 9: 2, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 1, # 'x' + 11: 3, # 'y' + 22: 0, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 3, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 3, # 'ü' + 30: 1, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 1, # 'ş' + }, + 5: { # 'l' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 3, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 1, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 0, # 'a' + 21: 3, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 1, # 'e' + 18: 3, # 'f' + 27: 3, # 'g' + 25: 2, # 'h' + 3: 3, # 'i' + 24: 2, # 'j' + 10: 3, # 'k' + 5: 1, # 'l' + 13: 1, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 2, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 2, # 'u' + 32: 2, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 3, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 2, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 13: { # 'm' + 23: 1, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 3, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 3, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 3, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 0, # 'Z' + 1: 2, # 'a' + 21: 3, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 2, # 'e' + 18: 3, # 'f' + 27: 3, # 'g' + 25: 3, # 'h' + 3: 3, # 'i' + 24: 3, # 'j' + 10: 3, # 'k' + 5: 0, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 15: 1, # 'o' + 26: 2, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 2, # 'u' + 32: 2, # 'v' + 57: 1, # 'w' + 58: 0, # 'x' + 11: 3, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 3, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 1, # 'ş' + }, + 4: { # 'n' + 23: 1, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 1, # 'H' + 53: 0, # 'I' + 60: 2, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 3, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 2, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 2, # 'b' + 28: 1, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 1, # 'f' + 27: 2, # 'g' + 25: 3, # 'h' + 3: 2, # 'i' + 24: 2, # 'j' + 10: 3, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 1, # 'o' + 26: 3, # 'p' + 7: 2, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 3, # 'u' + 32: 2, # 'v' + 57: 0, # 'w' + 58: 2, # 'x' + 11: 3, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 2, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 1, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 15: { # 'o' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 2, # 'F' + 36: 1, # 'G' + 45: 1, # 'H' + 53: 1, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 2, # 'L' + 20: 0, # 'M' + 46: 2, # 'N' + 42: 1, # 'O' + 48: 2, # 'P' + 44: 1, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 1, # 'i' + 24: 2, # 'j' + 10: 1, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 2, # 'n' + 15: 2, # 'o' + 26: 0, # 'p' + 7: 1, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 2, # 'x' + 11: 0, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 2, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 3, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 2, # 'ğ' + 41: 2, # 'İ' + 6: 3, # 'ı' + 40: 2, # 'Ş' + 19: 2, # 'ş' + }, + 26: { # 'p' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 1, # 'b' + 28: 0, # 'c' + 12: 1, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 1, # 'g' + 25: 1, # 'h' + 3: 2, # 'i' + 24: 3, # 'j' + 10: 1, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 2, # 'n' + 15: 0, # 'o' + 26: 2, # 'p' + 7: 2, # 'r' + 8: 1, # 's' + 9: 1, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 1, # 'x' + 11: 1, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 3, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 1, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 7: { # 'r' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 1, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 2, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 2, # 'T' + 51: 1, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 1, # 'Z' + 1: 3, # 'a' + 21: 1, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 2, # 'g' + 25: 3, # 'h' + 3: 2, # 'i' + 24: 2, # 'j' + 10: 3, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 2, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 3, # 'u' + 32: 2, # 'v' + 57: 0, # 'w' + 58: 1, # 'x' + 11: 2, # 'y' + 22: 0, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 2, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 3, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 8: { # 's' + 23: 1, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 1, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 3, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 2, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 1, # 'Z' + 1: 3, # 'a' + 21: 2, # 'b' + 28: 1, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 2, # 'g' + 25: 2, # 'h' + 3: 2, # 'i' + 24: 3, # 'j' + 10: 3, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 3, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 3, # 'u' + 32: 2, # 'v' + 57: 0, # 'w' + 58: 1, # 'x' + 11: 2, # 'y' + 22: 1, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 2, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 2, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 1, # 'ş' + }, + 9: { # 't' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 1, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 2, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 1, # 'Z' + 1: 3, # 'a' + 21: 3, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 2, # 'f' + 27: 2, # 'g' + 25: 2, # 'h' + 3: 2, # 'i' + 24: 2, # 'j' + 10: 3, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 2, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 3, # 'u' + 32: 3, # 'v' + 57: 0, # 'w' + 58: 2, # 'x' + 11: 2, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 3, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 2, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 14: { # 'u' + 23: 3, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 3, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 1, # 'H' + 53: 0, # 'I' + 60: 1, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 3, # 'M' + 46: 2, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 3, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 2, # 'Z' + 1: 2, # 'a' + 21: 3, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 2, # 'e' + 18: 2, # 'f' + 27: 3, # 'g' + 25: 3, # 'h' + 3: 3, # 'i' + 24: 2, # 'j' + 10: 3, # 'k' + 5: 0, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 3, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 3, # 'u' + 32: 2, # 'v' + 57: 2, # 'w' + 58: 0, # 'x' + 11: 3, # 'y' + 22: 0, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 3, # 'ü' + 30: 1, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 32: { # 'v' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 0, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 1, # 'j' + 10: 1, # 'k' + 5: 3, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 1, # 'p' + 7: 1, # 'r' + 8: 2, # 's' + 9: 3, # 't' + 14: 3, # 'u' + 32: 1, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 2, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 1, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 57: { # 'w' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 1, # 'a' + 21: 0, # 'b' + 28: 0, # 'c' + 12: 0, # 'd' + 2: 2, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 1, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 1, # 'k' + 5: 0, # 'l' + 13: 0, # 'm' + 4: 1, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 1, # 's' + 9: 0, # 't' + 14: 1, # 'u' + 32: 0, # 'v' + 57: 2, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 0, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 1, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 0, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 58: { # 'x' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 1, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 1, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 0, # 'a' + 21: 1, # 'b' + 28: 0, # 'c' + 12: 2, # 'd' + 2: 1, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 2, # 'i' + 24: 2, # 'j' + 10: 1, # 'k' + 5: 0, # 'l' + 13: 0, # 'm' + 4: 2, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 1, # 'r' + 8: 2, # 's' + 9: 1, # 't' + 14: 0, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 2, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 1, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 11: { # 'y' + 23: 1, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 1, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 1, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 1, # 'Z' + 1: 3, # 'a' + 21: 1, # 'b' + 28: 0, # 'c' + 12: 2, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 2, # 'g' + 25: 2, # 'h' + 3: 2, # 'i' + 24: 1, # 'j' + 10: 2, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 1, # 'p' + 7: 2, # 'r' + 8: 1, # 's' + 9: 2, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 1, # 'x' + 11: 3, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 3, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 2, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 22: { # 'z' + 23: 2, # 'A' + 37: 2, # 'B' + 47: 1, # 'C' + 39: 2, # 'D' + 29: 3, # 'E' + 52: 1, # 'F' + 36: 2, # 'G' + 45: 2, # 'H' + 53: 1, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 3, # 'M' + 46: 2, # 'N' + 42: 2, # 'O' + 48: 2, # 'P' + 44: 1, # 'R' + 35: 1, # 'S' + 31: 3, # 'T' + 51: 2, # 'U' + 38: 2, # 'V' + 62: 0, # 'W' + 43: 2, # 'Y' + 56: 1, # 'Z' + 1: 1, # 'a' + 21: 2, # 'b' + 28: 1, # 'c' + 12: 2, # 'd' + 2: 2, # 'e' + 18: 3, # 'f' + 27: 2, # 'g' + 25: 2, # 'h' + 3: 3, # 'i' + 24: 2, # 'j' + 10: 3, # 'k' + 5: 0, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 15: 2, # 'o' + 26: 2, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 0, # 'u' + 32: 2, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 3, # 'y' + 22: 2, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 2, # 'Ü' + 59: 1, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 2, # 'ö' + 17: 2, # 'ü' + 30: 2, # 'ğ' + 41: 1, # 'İ' + 6: 3, # 'ı' + 40: 1, # 'Ş' + 19: 2, # 'ş' + }, + 63: { # '·' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 0, # 'a' + 21: 0, # 'b' + 28: 0, # 'c' + 12: 0, # 'd' + 2: 1, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 0, # 'k' + 5: 0, # 'l' + 13: 2, # 'm' + 4: 0, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 2, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 0, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 54: { # 'Ç' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 1, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 1, # 'G' + 45: 1, # 'H' + 53: 1, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 1, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 2, # 'Y' + 56: 0, # 'Z' + 1: 0, # 'a' + 21: 1, # 'b' + 28: 0, # 'c' + 12: 1, # 'd' + 2: 0, # 'e' + 18: 0, # 'f' + 27: 1, # 'g' + 25: 0, # 'h' + 3: 3, # 'i' + 24: 0, # 'j' + 10: 1, # 'k' + 5: 0, # 'l' + 13: 0, # 'm' + 4: 2, # 'n' + 15: 1, # 'o' + 26: 0, # 'p' + 7: 2, # 'r' + 8: 0, # 's' + 9: 1, # 't' + 14: 0, # 'u' + 32: 2, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 2, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 1, # 'ş' + }, + 50: { # 'Ö' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 1, # 'D' + 29: 2, # 'E' + 52: 0, # 'F' + 36: 1, # 'G' + 45: 2, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 1, # 'N' + 42: 2, # 'O' + 48: 2, # 'P' + 44: 1, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 2, # 'Y' + 56: 0, # 'Z' + 1: 0, # 'a' + 21: 2, # 'b' + 28: 1, # 'c' + 12: 2, # 'd' + 2: 0, # 'e' + 18: 1, # 'f' + 27: 1, # 'g' + 25: 1, # 'h' + 3: 2, # 'i' + 24: 0, # 'j' + 10: 2, # 'k' + 5: 0, # 'l' + 13: 0, # 'm' + 4: 3, # 'n' + 15: 2, # 'o' + 26: 2, # 'p' + 7: 3, # 'r' + 8: 1, # 's' + 9: 2, # 't' + 14: 0, # 'u' + 32: 1, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 1, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 2, # 'ö' + 17: 2, # 'ü' + 30: 1, # 'ğ' + 41: 0, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 1, # 'ş' + }, + 55: { # 'Ü' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 2, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 1, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 1, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 2, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 2, # 'e' + 18: 0, # 'f' + 27: 1, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 0, # 'k' + 5: 1, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 1, # 't' + 14: 2, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 1, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 1, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 1, # 'İ' + 6: 0, # 'ı' + 40: 0, # 'Ş' + 19: 1, # 'ş' + }, + 59: { # 'â' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 1, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 1, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 2, # 'a' + 21: 0, # 'b' + 28: 0, # 'c' + 12: 0, # 'd' + 2: 2, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 0, # 'j' + 10: 0, # 'k' + 5: 0, # 'l' + 13: 2, # 'm' + 4: 0, # 'n' + 15: 1, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 2, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 1, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 1, # 'ı' + 40: 1, # 'Ş' + 19: 0, # 'ş' + }, + 33: { # 'ç' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 3, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 2, # 'T' + 51: 0, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 0, # 'Z' + 1: 0, # 'a' + 21: 3, # 'b' + 28: 0, # 'c' + 12: 2, # 'd' + 2: 0, # 'e' + 18: 2, # 'f' + 27: 1, # 'g' + 25: 3, # 'h' + 3: 3, # 'i' + 24: 0, # 'j' + 10: 3, # 'k' + 5: 0, # 'l' + 13: 0, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 1, # 'p' + 7: 3, # 'r' + 8: 2, # 's' + 9: 3, # 't' + 14: 0, # 'u' + 32: 2, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 2, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 1, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 61: { # 'î' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 0, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 0, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 1, # 'Z' + 1: 2, # 'a' + 21: 0, # 'b' + 28: 0, # 'c' + 12: 0, # 'd' + 2: 2, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 1, # 'j' + 10: 0, # 'k' + 5: 0, # 'l' + 13: 1, # 'm' + 4: 1, # 'n' + 15: 0, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 1, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 1, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 1, # 'î' + 34: 0, # 'ö' + 17: 0, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 1, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 34: { # 'ö' + 23: 0, # 'A' + 37: 1, # 'B' + 47: 1, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 2, # 'F' + 36: 1, # 'G' + 45: 1, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 1, # 'L' + 20: 0, # 'M' + 46: 1, # 'N' + 42: 1, # 'O' + 48: 2, # 'P' + 44: 1, # 'R' + 35: 1, # 'S' + 31: 1, # 'T' + 51: 1, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 1, # 'Z' + 1: 3, # 'a' + 21: 1, # 'b' + 28: 2, # 'c' + 12: 1, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 2, # 'g' + 25: 2, # 'h' + 3: 1, # 'i' + 24: 2, # 'j' + 10: 1, # 'k' + 5: 2, # 'l' + 13: 3, # 'm' + 4: 2, # 'n' + 15: 2, # 'o' + 26: 0, # 'p' + 7: 0, # 'r' + 8: 3, # 's' + 9: 1, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 1, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 2, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 2, # 'ç' + 61: 0, # 'î' + 34: 2, # 'ö' + 17: 0, # 'ü' + 30: 2, # 'ğ' + 41: 1, # 'İ' + 6: 1, # 'ı' + 40: 2, # 'Ş' + 19: 1, # 'ş' + }, + 17: { # 'ü' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 0, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 1, # 'J' + 16: 1, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 0, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 1, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 0, # 'Y' + 56: 1, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 0, # 'c' + 12: 1, # 'd' + 2: 3, # 'e' + 18: 1, # 'f' + 27: 2, # 'g' + 25: 0, # 'h' + 3: 1, # 'i' + 24: 1, # 'j' + 10: 2, # 'k' + 5: 3, # 'l' + 13: 2, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 2, # 'p' + 7: 2, # 'r' + 8: 3, # 's' + 9: 2, # 't' + 14: 3, # 'u' + 32: 1, # 'v' + 57: 1, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 2, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 2, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 30: { # 'ğ' + 23: 0, # 'A' + 37: 2, # 'B' + 47: 1, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 2, # 'F' + 36: 1, # 'G' + 45: 0, # 'H' + 53: 1, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 1, # 'M' + 46: 2, # 'N' + 42: 2, # 'O' + 48: 1, # 'P' + 44: 1, # 'R' + 35: 0, # 'S' + 31: 1, # 'T' + 51: 0, # 'U' + 38: 2, # 'V' + 62: 0, # 'W' + 43: 2, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 0, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 2, # 'e' + 18: 0, # 'f' + 27: 0, # 'g' + 25: 0, # 'h' + 3: 0, # 'i' + 24: 3, # 'j' + 10: 1, # 'k' + 5: 2, # 'l' + 13: 3, # 'm' + 4: 0, # 'n' + 15: 1, # 'o' + 26: 0, # 'p' + 7: 1, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 2, # 'Ç' + 50: 2, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 0, # 'î' + 34: 2, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 2, # 'İ' + 6: 2, # 'ı' + 40: 2, # 'Ş' + 19: 1, # 'ş' + }, + 41: { # 'İ' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 1, # 'D' + 29: 1, # 'E' + 52: 0, # 'F' + 36: 2, # 'G' + 45: 2, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 1, # 'N' + 42: 1, # 'O' + 48: 2, # 'P' + 44: 0, # 'R' + 35: 1, # 'S' + 31: 1, # 'T' + 51: 1, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 2, # 'Y' + 56: 0, # 'Z' + 1: 1, # 'a' + 21: 2, # 'b' + 28: 1, # 'c' + 12: 2, # 'd' + 2: 1, # 'e' + 18: 0, # 'f' + 27: 3, # 'g' + 25: 2, # 'h' + 3: 2, # 'i' + 24: 2, # 'j' + 10: 2, # 'k' + 5: 0, # 'l' + 13: 1, # 'm' + 4: 3, # 'n' + 15: 1, # 'o' + 26: 1, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 2, # 't' + 14: 0, # 'u' + 32: 0, # 'v' + 57: 1, # 'w' + 58: 0, # 'x' + 11: 2, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 1, # 'Ü' + 59: 1, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 1, # 'ö' + 17: 1, # 'ü' + 30: 2, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 1, # 'ş' + }, + 6: { # 'ı' + 23: 2, # 'A' + 37: 0, # 'B' + 47: 0, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 0, # 'F' + 36: 1, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 2, # 'J' + 16: 3, # 'K' + 49: 0, # 'L' + 20: 3, # 'M' + 46: 1, # 'N' + 42: 0, # 'O' + 48: 0, # 'P' + 44: 0, # 'R' + 35: 0, # 'S' + 31: 2, # 'T' + 51: 0, # 'U' + 38: 0, # 'V' + 62: 0, # 'W' + 43: 2, # 'Y' + 56: 1, # 'Z' + 1: 3, # 'a' + 21: 2, # 'b' + 28: 1, # 'c' + 12: 3, # 'd' + 2: 3, # 'e' + 18: 3, # 'f' + 27: 3, # 'g' + 25: 2, # 'h' + 3: 3, # 'i' + 24: 3, # 'j' + 10: 3, # 'k' + 5: 3, # 'l' + 13: 3, # 'm' + 4: 3, # 'n' + 15: 0, # 'o' + 26: 3, # 'p' + 7: 3, # 'r' + 8: 3, # 's' + 9: 3, # 't' + 14: 3, # 'u' + 32: 3, # 'v' + 57: 1, # 'w' + 58: 1, # 'x' + 11: 3, # 'y' + 22: 0, # 'z' + 63: 1, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 2, # 'ç' + 61: 0, # 'î' + 34: 0, # 'ö' + 17: 3, # 'ü' + 30: 0, # 'ğ' + 41: 0, # 'İ' + 6: 3, # 'ı' + 40: 0, # 'Ş' + 19: 0, # 'ş' + }, + 40: { # 'Ş' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 1, # 'D' + 29: 1, # 'E' + 52: 0, # 'F' + 36: 1, # 'G' + 45: 2, # 'H' + 53: 1, # 'I' + 60: 0, # 'J' + 16: 0, # 'K' + 49: 0, # 'L' + 20: 2, # 'M' + 46: 1, # 'N' + 42: 1, # 'O' + 48: 2, # 'P' + 44: 2, # 'R' + 35: 1, # 'S' + 31: 1, # 'T' + 51: 0, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 2, # 'Y' + 56: 1, # 'Z' + 1: 0, # 'a' + 21: 2, # 'b' + 28: 0, # 'c' + 12: 2, # 'd' + 2: 0, # 'e' + 18: 3, # 'f' + 27: 0, # 'g' + 25: 2, # 'h' + 3: 3, # 'i' + 24: 2, # 'j' + 10: 1, # 'k' + 5: 0, # 'l' + 13: 1, # 'm' + 4: 3, # 'n' + 15: 2, # 'o' + 26: 0, # 'p' + 7: 3, # 'r' + 8: 2, # 's' + 9: 2, # 't' + 14: 1, # 'u' + 32: 3, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 2, # 'y' + 22: 0, # 'z' + 63: 0, # '·' + 54: 0, # 'Ç' + 50: 0, # 'Ö' + 55: 1, # 'Ü' + 59: 0, # 'â' + 33: 0, # 'ç' + 61: 0, # 'î' + 34: 2, # 'ö' + 17: 1, # 'ü' + 30: 2, # 'ğ' + 41: 0, # 'İ' + 6: 2, # 'ı' + 40: 1, # 'Ş' + 19: 2, # 'ş' + }, + 19: { # 'ş' + 23: 0, # 'A' + 37: 0, # 'B' + 47: 1, # 'C' + 39: 0, # 'D' + 29: 0, # 'E' + 52: 2, # 'F' + 36: 1, # 'G' + 45: 0, # 'H' + 53: 0, # 'I' + 60: 0, # 'J' + 16: 3, # 'K' + 49: 2, # 'L' + 20: 0, # 'M' + 46: 1, # 'N' + 42: 1, # 'O' + 48: 1, # 'P' + 44: 1, # 'R' + 35: 1, # 'S' + 31: 0, # 'T' + 51: 1, # 'U' + 38: 1, # 'V' + 62: 0, # 'W' + 43: 1, # 'Y' + 56: 0, # 'Z' + 1: 3, # 'a' + 21: 1, # 'b' + 28: 2, # 'c' + 12: 0, # 'd' + 2: 3, # 'e' + 18: 0, # 'f' + 27: 2, # 'g' + 25: 1, # 'h' + 3: 1, # 'i' + 24: 0, # 'j' + 10: 2, # 'k' + 5: 2, # 'l' + 13: 3, # 'm' + 4: 0, # 'n' + 15: 0, # 'o' + 26: 1, # 'p' + 7: 3, # 'r' + 8: 0, # 's' + 9: 0, # 't' + 14: 3, # 'u' + 32: 0, # 'v' + 57: 0, # 'w' + 58: 0, # 'x' + 11: 0, # 'y' + 22: 2, # 'z' + 63: 0, # '·' + 54: 1, # 'Ç' + 50: 2, # 'Ö' + 55: 0, # 'Ü' + 59: 0, # 'â' + 33: 1, # 'ç' + 61: 1, # 'î' + 34: 2, # 'ö' + 17: 0, # 'ü' + 30: 1, # 'ğ' + 41: 1, # 'İ' + 6: 1, # 'ı' + 40: 1, # 'Ş' + 19: 1, # 'ş' + }, +} + +# 255: Undefined characters that did not exist in training text # 254: Carriage/Return # 253: symbol (punctuation) that does not belong to word # 252: 0 - 9 +# 251: Control characters -# Character Mapping Table: -Latin5_TurkishCharToOrderMap = ( -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, -255, 23, 37, 47, 39, 29, 52, 36, 45, 53, 60, 16, 49, 20, 46, 42, - 48, 69, 44, 35, 31, 51, 38, 62, 65, 43, 56,255,255,255,255,255, -255, 1, 21, 28, 12, 2, 18, 27, 25, 3, 24, 10, 5, 13, 4, 15, - 26, 64, 7, 8, 9, 14, 32, 57, 58, 11, 22,255,255,255,255,255, -180,179,178,177,176,175,174,173,172,171,170,169,168,167,166,165, -164,163,162,161,160,159,101,158,157,156,155,154,153,152,151,106, -150,149,148,147,146,145,144,100,143,142,141,140,139,138,137,136, - 94, 80, 93,135,105,134,133, 63,132,131,130,129,128,127,126,125, -124,104, 73, 99, 79, 85,123, 54,122, 98, 92,121,120, 91,103,119, - 68,118,117, 97,116,115, 50, 90,114,113,112,111, 55, 41, 40, 86, - 89, 70, 59, 78, 71, 82, 88, 33, 77, 66, 84, 83,110, 75, 61, 96, - 30, 67,109, 74, 87,102, 34, 95, 81,108, 76, 72, 17, 6, 19,107, -) - -TurkishLangModel = ( -3,2,3,3,3,1,3,3,3,3,3,3,3,3,2,1,1,3,3,1,3,3,0,3,3,3,3,3,0,3,1,3, -3,2,1,0,0,1,1,0,0,0,1,0,0,1,1,1,1,0,0,0,0,0,0,0,2,2,0,0,1,0,0,1, -3,2,2,3,3,0,3,3,3,3,3,3,3,2,3,1,0,3,3,1,3,3,0,3,3,3,3,3,0,3,0,3, -3,1,1,0,1,0,1,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,2,2,0,0,0,1,0,1, -3,3,2,3,3,0,3,3,3,3,3,3,3,2,3,1,1,3,3,0,3,3,1,2,3,3,3,3,0,3,0,3, -3,1,1,0,0,0,1,0,0,0,0,1,1,0,1,2,1,0,0,0,1,0,0,0,0,2,0,0,0,0,0,1, -3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,1,3,3,2,0,3,2,1,2,2,1,3,3,0,0,0,2, -2,2,0,1,0,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,0,1,0,0,1, -3,3,3,2,3,3,1,2,3,3,3,3,3,3,3,1,3,2,1,0,3,2,0,1,2,3,3,2,1,0,0,2, -2,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,2,0,2,0,0,0, -1,0,1,3,3,1,3,3,3,3,3,3,3,1,2,0,0,2,3,0,2,3,0,0,2,2,2,3,0,3,0,1, -2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,3,3,3,0,3,2,0,2,3,2,3,3,1,0,0,2, -3,2,0,0,1,0,0,0,0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,1,1,1,0,2,0,0,1, -3,3,3,2,3,3,2,3,3,3,3,2,3,3,3,0,3,3,0,0,2,1,0,0,2,3,2,2,0,0,0,2, -2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,0,1,0,2,0,0,1, -3,3,3,2,3,3,3,3,3,3,3,2,3,3,3,0,3,2,0,1,3,2,1,1,3,2,3,2,1,0,0,2, -2,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0, -3,3,3,2,3,3,3,3,3,3,3,2,3,3,3,0,3,2,2,0,2,3,0,0,2,2,2,2,0,0,0,2, -3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,2,0,1,0,0,0, -3,3,3,3,3,3,3,2,2,2,2,3,2,3,3,0,3,3,1,1,2,2,0,0,2,2,3,2,0,0,1,3, -0,3,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,1, -3,3,3,2,3,3,3,2,1,2,2,3,2,3,3,0,3,2,0,0,1,1,0,1,1,2,1,2,0,0,0,1, -0,3,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,0,0, -3,3,3,2,3,3,2,3,2,2,2,3,3,3,3,1,3,1,1,0,3,2,1,1,3,3,2,3,1,0,0,1, -1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,2,0,0,1, -3,2,2,3,3,0,3,3,3,3,3,3,3,2,2,1,0,3,3,1,3,3,0,1,3,3,2,3,0,3,0,3, -2,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0, -2,2,2,3,3,0,3,3,3,3,3,3,3,3,3,0,0,3,2,0,3,3,0,3,2,3,3,3,0,3,1,3, -2,0,0,0,0,0,0,0,0,0,0,1,0,1,2,0,1,0,0,0,0,0,0,0,2,2,0,0,1,0,0,1, -3,3,3,1,2,3,3,1,0,0,1,0,0,3,3,2,3,0,0,2,0,0,2,0,2,0,0,0,2,0,2,0, -0,3,1,0,1,0,0,0,2,2,1,0,1,1,2,1,2,2,2,0,2,1,1,0,0,0,2,0,0,0,0,0, -1,2,1,3,3,0,3,3,3,3,3,2,3,0,0,0,0,2,3,0,2,3,1,0,2,3,1,3,0,3,0,2, -3,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,1,3,3,2,2,3,2,2,0,1,2,3,0,1,2,1,0,1,0,0,0,1,0,2,2,0,0,0,1, -1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0, -3,3,3,1,3,3,1,1,3,3,1,1,3,3,1,0,2,1,2,0,2,1,0,0,1,1,2,1,0,0,0,2, -2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,1,0,2,1,3,0,0,2,0,0,3,3,0,3,0,0,1,0,1,2,0,0,1,1,2,2,0,1,0, -0,1,2,1,1,0,1,0,1,1,1,1,1,0,1,1,1,2,2,1,2,0,1,0,0,0,0,0,0,1,0,0, -3,3,3,2,3,2,3,3,0,2,2,2,3,3,3,0,3,0,0,0,2,2,0,1,2,1,1,1,0,0,0,1, -0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0, -3,3,3,3,3,3,2,1,2,2,3,3,3,3,2,0,2,0,0,0,2,2,0,0,2,1,3,3,0,0,1,1, -1,1,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0, -1,1,2,3,3,0,3,3,3,3,3,3,2,2,0,2,0,2,3,2,3,2,2,2,2,2,2,2,1,3,2,3, -2,0,2,1,2,2,2,2,1,1,2,2,1,2,2,1,2,0,0,2,1,1,0,2,1,0,0,1,0,0,0,1, -2,3,3,1,1,1,0,1,1,1,2,3,2,1,1,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0, -0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,2,2,2,3,2,3,2,2,1,3,3,3,0,2,1,2,0,2,1,0,0,1,1,1,1,1,0,0,1, -2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,2,0,1,0,0,0, -3,3,3,2,3,3,3,3,3,2,3,1,2,3,3,1,2,0,0,0,0,0,0,0,3,2,1,1,0,0,0,0, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0, -3,3,3,2,2,3,3,2,1,1,1,1,1,3,3,0,3,1,0,0,1,1,0,0,3,1,2,1,0,0,0,0, -0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0, -3,3,3,2,2,3,2,2,2,3,2,1,1,3,3,0,3,0,0,0,0,1,0,0,3,1,1,2,0,0,0,1, -1,0,0,1,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -1,1,1,3,3,0,3,3,3,3,3,2,2,2,1,2,0,2,1,2,2,1,1,0,1,2,2,2,2,2,2,2, -0,0,2,1,2,1,2,1,0,1,1,3,1,2,1,1,2,0,0,2,0,1,0,1,0,1,0,0,0,1,0,1, -3,3,3,1,3,3,3,0,1,1,0,2,2,3,1,0,3,0,0,0,1,0,0,0,1,0,0,1,0,1,0,0, -1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,2,0,0,2,2,1,0,0,1,0,0,3,3,1,3,0,0,1,1,0,2,0,3,0,0,0,2,0,1,1, -0,1,2,0,1,2,2,0,2,2,2,2,1,0,2,1,1,0,2,0,2,1,2,0,0,0,0,0,0,0,0,0, -3,3,3,1,3,2,3,2,0,2,2,2,1,3,2,0,2,1,2,0,1,2,0,0,1,0,2,2,0,0,0,2, -1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,1,0,0,0, -3,3,3,0,3,3,1,1,2,3,1,0,3,2,3,0,3,0,0,0,1,0,0,0,1,0,1,0,0,0,0,0, -1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,3,3,0,3,3,2,3,3,2,2,0,0,0,0,1,2,0,1,3,0,0,0,3,1,1,0,3,0,2, -2,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,1,2,2,1,0,3,1,1,1,1,3,3,2,3,0,0,1,0,1,2,0,2,2,0,2,2,0,2,1, -0,2,2,1,1,1,1,0,2,1,1,0,1,1,1,1,2,1,2,1,2,0,1,0,1,0,0,0,0,0,0,0, -3,3,3,0,1,1,3,0,0,1,1,0,0,2,2,0,3,0,0,1,1,0,1,0,0,0,0,0,2,0,0,0, -0,3,1,0,1,0,1,0,2,0,0,1,0,1,0,1,1,1,2,1,1,0,2,0,0,0,0,0,0,0,0,0, -3,3,3,0,2,0,2,0,1,1,1,0,0,3,3,0,2,0,0,1,0,0,2,1,1,0,1,0,1,0,1,0, -0,2,0,1,2,0,2,0,2,1,1,0,1,0,2,1,1,0,2,1,1,0,1,0,0,0,1,1,0,0,0,0, -3,2,3,0,1,0,0,0,0,0,0,0,0,1,2,0,1,0,0,1,0,0,1,0,0,0,0,0,2,0,0,0, -0,0,1,1,0,0,1,0,1,0,0,1,0,0,0,2,1,0,1,0,2,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,0,0,2,3,0,0,1,0,1,0,2,3,2,3,0,0,1,3,0,2,1,0,0,0,0,2,0,1,0, -0,2,1,0,0,1,1,0,2,1,0,0,1,0,0,1,1,0,1,1,2,0,1,0,0,0,0,1,0,0,0,0, -3,2,2,0,0,1,1,0,0,0,0,0,0,3,1,1,1,0,0,0,0,0,1,0,0,0,0,0,2,0,1,0, -0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0, -0,0,0,3,3,0,2,3,2,2,1,2,2,1,1,2,0,1,3,2,2,2,0,0,2,2,0,0,0,1,2,1, -3,0,2,1,1,0,1,1,1,0,1,2,2,2,1,1,2,0,0,0,0,1,0,1,1,0,0,0,0,0,0,0, -0,1,1,2,3,0,3,3,3,2,2,2,2,1,0,1,0,1,0,1,2,2,0,0,2,2,1,3,1,1,2,1, -0,0,1,1,2,0,1,1,0,0,1,2,0,2,1,1,2,0,0,1,0,0,0,1,0,1,0,1,0,0,0,0, -3,3,2,0,0,3,1,0,0,0,0,0,0,3,2,1,2,0,0,1,0,0,2,0,0,0,0,0,2,0,1,0, -0,2,1,1,0,0,1,0,1,2,0,0,1,1,0,0,2,1,1,1,1,0,2,0,0,0,0,0,0,0,0,0, -3,3,2,0,0,1,0,0,0,0,1,0,0,3,3,2,2,0,0,1,0,0,2,0,1,0,0,0,2,0,1,0, -0,0,1,1,0,0,2,0,2,1,0,0,1,1,2,1,2,0,2,1,2,1,1,1,0,0,1,1,0,0,0,0, -3,3,2,0,0,2,2,0,0,0,1,1,0,2,2,1,3,1,0,1,0,1,2,0,0,0,0,0,1,0,1,0, -0,1,1,0,0,0,0,0,1,0,0,1,0,0,0,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,2,0,0,0,1,0,0,1,0,0,2,3,1,2,0,0,1,0,0,2,0,0,0,1,0,2,0,2,0, -0,1,1,2,2,1,2,0,2,1,1,0,0,1,1,0,1,1,1,1,2,1,1,0,0,0,0,0,0,0,0,0, -3,3,3,0,2,1,2,1,0,0,1,1,0,3,3,1,2,0,0,1,0,0,2,0,2,0,1,1,2,0,0,0, -0,0,1,1,1,1,2,0,1,1,0,1,1,1,1,0,0,0,1,1,1,0,1,0,0,0,1,0,0,0,0,0, -3,3,3,0,2,2,3,2,0,0,1,0,0,2,3,1,0,0,0,0,0,0,2,0,2,0,0,0,2,0,0,0, -0,1,1,0,0,0,1,0,0,1,0,1,1,0,1,0,1,1,1,0,1,0,0,0,0,0,0,0,0,0,0,0, -3,2,3,0,0,0,0,0,0,0,1,0,0,2,2,2,2,0,0,1,0,0,2,0,0,0,0,0,2,0,1,0, -0,0,2,1,1,0,1,0,2,1,1,0,0,1,1,2,1,0,2,0,2,0,1,0,0,0,2,0,0,0,0,0, -0,0,0,2,2,0,2,1,1,1,1,2,2,0,0,1,0,1,0,0,1,3,0,0,0,0,1,0,0,2,1,0, -0,0,1,0,1,0,0,0,0,0,2,1,0,1,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0, -2,0,0,2,3,0,2,3,1,2,2,0,2,0,0,2,0,2,1,1,1,2,1,0,0,1,2,1,1,2,1,0, -1,0,2,0,1,0,1,1,0,0,2,2,1,2,1,1,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,0,2,1,2,0,0,0,1,0,0,3,2,0,1,0,0,1,0,0,2,0,0,0,1,2,1,0,1,0, -0,0,0,0,1,0,1,0,0,1,0,0,0,0,1,0,1,0,1,1,1,0,1,0,0,0,0,0,0,0,0,0, -0,0,0,2,2,0,2,2,1,1,0,1,1,1,1,1,0,0,1,2,1,1,1,0,1,0,0,0,1,1,1,1, -0,0,2,1,0,1,1,1,0,1,1,2,1,2,1,1,2,0,1,1,2,1,0,2,0,0,0,0,0,0,0,0, -3,2,2,0,0,2,0,0,0,0,0,0,0,2,2,0,2,0,0,1,0,0,2,0,0,0,0,0,2,0,0,0, -0,2,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0, -0,0,0,3,2,0,2,2,0,1,1,0,1,0,0,1,0,0,0,1,0,1,0,0,0,0,0,1,0,0,0,0, -2,0,1,0,1,0,1,1,0,0,1,2,0,1,0,1,1,0,0,1,0,1,0,2,0,0,0,0,0,0,0,0, -2,2,2,0,1,1,0,0,0,1,0,0,0,1,2,0,1,0,0,1,0,0,1,0,0,0,0,1,2,0,1,0, -0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,1,0,1,0,2,0,0,0,0,0,0,0,0,0,0,0, -2,2,2,2,1,0,1,1,1,0,0,0,0,1,2,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0, -1,1,2,0,1,0,0,0,1,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,2,0,0,0,0,0,1, -0,0,1,2,2,0,2,1,2,1,1,2,2,0,0,0,0,1,0,0,1,1,0,0,2,0,0,0,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0, -2,2,2,0,0,0,1,0,0,0,0,0,0,2,2,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0, -0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,1,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,2,2,0,1,0,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,1,0,0,0,0,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -) - -Latin5TurkishModel = { - 'char_to_order_map': Latin5_TurkishCharToOrderMap, - 'precedence_matrix': TurkishLangModel, - 'typical_positive_ratio': 0.970290, - 'keep_english_letter': True, - 'charset_name': "ISO-8859-9", - 'language': 'Turkish', +# Character Mapping Table(s): +ISO_8859_9_TURKISH_CHAR_TO_ORDER = { + 0: 255, # '\x00' + 1: 255, # '\x01' + 2: 255, # '\x02' + 3: 255, # '\x03' + 4: 255, # '\x04' + 5: 255, # '\x05' + 6: 255, # '\x06' + 7: 255, # '\x07' + 8: 255, # '\x08' + 9: 255, # '\t' + 10: 255, # '\n' + 11: 255, # '\x0b' + 12: 255, # '\x0c' + 13: 255, # '\r' + 14: 255, # '\x0e' + 15: 255, # '\x0f' + 16: 255, # '\x10' + 17: 255, # '\x11' + 18: 255, # '\x12' + 19: 255, # '\x13' + 20: 255, # '\x14' + 21: 255, # '\x15' + 22: 255, # '\x16' + 23: 255, # '\x17' + 24: 255, # '\x18' + 25: 255, # '\x19' + 26: 255, # '\x1a' + 27: 255, # '\x1b' + 28: 255, # '\x1c' + 29: 255, # '\x1d' + 30: 255, # '\x1e' + 31: 255, # '\x1f' + 32: 255, # ' ' + 33: 255, # '!' + 34: 255, # '"' + 35: 255, # '#' + 36: 255, # '$' + 37: 255, # '%' + 38: 255, # '&' + 39: 255, # "'" + 40: 255, # '(' + 41: 255, # ')' + 42: 255, # '*' + 43: 255, # '+' + 44: 255, # ',' + 45: 255, # '-' + 46: 255, # '.' + 47: 255, # '/' + 48: 255, # '0' + 49: 255, # '1' + 50: 255, # '2' + 51: 255, # '3' + 52: 255, # '4' + 53: 255, # '5' + 54: 255, # '6' + 55: 255, # '7' + 56: 255, # '8' + 57: 255, # '9' + 58: 255, # ':' + 59: 255, # ';' + 60: 255, # '<' + 61: 255, # '=' + 62: 255, # '>' + 63: 255, # '?' + 64: 255, # '@' + 65: 23, # 'A' + 66: 37, # 'B' + 67: 47, # 'C' + 68: 39, # 'D' + 69: 29, # 'E' + 70: 52, # 'F' + 71: 36, # 'G' + 72: 45, # 'H' + 73: 53, # 'I' + 74: 60, # 'J' + 75: 16, # 'K' + 76: 49, # 'L' + 77: 20, # 'M' + 78: 46, # 'N' + 79: 42, # 'O' + 80: 48, # 'P' + 81: 69, # 'Q' + 82: 44, # 'R' + 83: 35, # 'S' + 84: 31, # 'T' + 85: 51, # 'U' + 86: 38, # 'V' + 87: 62, # 'W' + 88: 65, # 'X' + 89: 43, # 'Y' + 90: 56, # 'Z' + 91: 255, # '[' + 92: 255, # '\\' + 93: 255, # ']' + 94: 255, # '^' + 95: 255, # '_' + 96: 255, # '`' + 97: 1, # 'a' + 98: 21, # 'b' + 99: 28, # 'c' + 100: 12, # 'd' + 101: 2, # 'e' + 102: 18, # 'f' + 103: 27, # 'g' + 104: 25, # 'h' + 105: 3, # 'i' + 106: 24, # 'j' + 107: 10, # 'k' + 108: 5, # 'l' + 109: 13, # 'm' + 110: 4, # 'n' + 111: 15, # 'o' + 112: 26, # 'p' + 113: 64, # 'q' + 114: 7, # 'r' + 115: 8, # 's' + 116: 9, # 't' + 117: 14, # 'u' + 118: 32, # 'v' + 119: 57, # 'w' + 120: 58, # 'x' + 121: 11, # 'y' + 122: 22, # 'z' + 123: 255, # '{' + 124: 255, # '|' + 125: 255, # '}' + 126: 255, # '~' + 127: 255, # '\x7f' + 128: 180, # '\x80' + 129: 179, # '\x81' + 130: 178, # '\x82' + 131: 177, # '\x83' + 132: 176, # '\x84' + 133: 175, # '\x85' + 134: 174, # '\x86' + 135: 173, # '\x87' + 136: 172, # '\x88' + 137: 171, # '\x89' + 138: 170, # '\x8a' + 139: 169, # '\x8b' + 140: 168, # '\x8c' + 141: 167, # '\x8d' + 142: 166, # '\x8e' + 143: 165, # '\x8f' + 144: 164, # '\x90' + 145: 163, # '\x91' + 146: 162, # '\x92' + 147: 161, # '\x93' + 148: 160, # '\x94' + 149: 159, # '\x95' + 150: 101, # '\x96' + 151: 158, # '\x97' + 152: 157, # '\x98' + 153: 156, # '\x99' + 154: 155, # '\x9a' + 155: 154, # '\x9b' + 156: 153, # '\x9c' + 157: 152, # '\x9d' + 158: 151, # '\x9e' + 159: 106, # '\x9f' + 160: 150, # '\xa0' + 161: 149, # '¡' + 162: 148, # '¢' + 163: 147, # '£' + 164: 146, # '¤' + 165: 145, # '¥' + 166: 144, # '¦' + 167: 100, # '§' + 168: 143, # '¨' + 169: 142, # '©' + 170: 141, # 'ª' + 171: 140, # '«' + 172: 139, # '¬' + 173: 138, # '\xad' + 174: 137, # '®' + 175: 136, # '¯' + 176: 94, # '°' + 177: 80, # '±' + 178: 93, # '²' + 179: 135, # '³' + 180: 105, # '´' + 181: 134, # 'µ' + 182: 133, # '¶' + 183: 63, # '·' + 184: 132, # '¸' + 185: 131, # '¹' + 186: 130, # 'º' + 187: 129, # '»' + 188: 128, # '¼' + 189: 127, # '½' + 190: 126, # '¾' + 191: 125, # '¿' + 192: 124, # 'À' + 193: 104, # 'Á' + 194: 73, # 'Â' + 195: 99, # 'Ã' + 196: 79, # 'Ä' + 197: 85, # 'Å' + 198: 123, # 'Æ' + 199: 54, # 'Ç' + 200: 122, # 'È' + 201: 98, # 'É' + 202: 92, # 'Ê' + 203: 121, # 'Ë' + 204: 120, # 'Ì' + 205: 91, # 'Í' + 206: 103, # 'Î' + 207: 119, # 'Ï' + 208: 68, # 'Ğ' + 209: 118, # 'Ñ' + 210: 117, # 'Ò' + 211: 97, # 'Ó' + 212: 116, # 'Ô' + 213: 115, # 'Õ' + 214: 50, # 'Ö' + 215: 90, # '×' + 216: 114, # 'Ø' + 217: 113, # 'Ù' + 218: 112, # 'Ú' + 219: 111, # 'Û' + 220: 55, # 'Ü' + 221: 41, # 'İ' + 222: 40, # 'Ş' + 223: 86, # 'ß' + 224: 89, # 'à' + 225: 70, # 'á' + 226: 59, # 'â' + 227: 78, # 'ã' + 228: 71, # 'ä' + 229: 82, # 'å' + 230: 88, # 'æ' + 231: 33, # 'ç' + 232: 77, # 'è' + 233: 66, # 'é' + 234: 84, # 'ê' + 235: 83, # 'ë' + 236: 110, # 'ì' + 237: 75, # 'í' + 238: 61, # 'î' + 239: 96, # 'ï' + 240: 30, # 'ğ' + 241: 67, # 'ñ' + 242: 109, # 'ò' + 243: 74, # 'ó' + 244: 87, # 'ô' + 245: 102, # 'õ' + 246: 34, # 'ö' + 247: 95, # '÷' + 248: 81, # 'ø' + 249: 108, # 'ù' + 250: 76, # 'ú' + 251: 72, # 'û' + 252: 17, # 'ü' + 253: 6, # 'ı' + 254: 19, # 'ş' + 255: 107, # 'ÿ' } + +ISO_8859_9_TURKISH_MODEL = SingleByteCharSetModel(charset_name='ISO-8859-9', + language='Turkish', + char_to_order_map=ISO_8859_9_TURKISH_CHAR_TO_ORDER, + language_model=TURKISH_LANG_MODEL, + typical_positive_ratio=0.97029, + keep_ascii_letters=True, + alphabet='ABCDEFGHIJKLMNOPRSTUVYZabcdefghijklmnoprstuvyzÂÇÎÖÛÜâçîöûüĞğİıŞş') + diff --git a/pipenv/vendor/passa/models/__init__.py b/pipenv/patched/notpip/_vendor/chardet/metadata/__init__.py similarity index 100% rename from pipenv/vendor/passa/models/__init__.py rename to pipenv/patched/notpip/_vendor/chardet/metadata/__init__.py diff --git a/pipenv/patched/notpip/_vendor/chardet/metadata/languages.py b/pipenv/patched/notpip/_vendor/chardet/metadata/languages.py new file mode 100644 index 00000000..3237d5ab --- /dev/null +++ b/pipenv/patched/notpip/_vendor/chardet/metadata/languages.py @@ -0,0 +1,310 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Metadata about languages used by our model training code for our +SingleByteCharSetProbers. Could be used for other things in the future. + +This code is based on the language metadata from the uchardet project. +""" +from __future__ import absolute_import, print_function + +from string import ascii_letters + + +# TODO: Add Ukranian (KOI8-U) + +class Language(object): + """Metadata about a language useful for training models + + :ivar name: The human name for the language, in English. + :type name: str + :ivar iso_code: 2-letter ISO 639-1 if possible, 3-letter ISO code otherwise, + or use another catalog as a last resort. + :type iso_code: str + :ivar use_ascii: Whether or not ASCII letters should be included in trained + models. + :type use_ascii: bool + :ivar charsets: The charsets we want to support and create data for. + :type charsets: list of str + :ivar alphabet: The characters in the language's alphabet. If `use_ascii` is + `True`, you only need to add those not in the ASCII set. + :type alphabet: str + :ivar wiki_start_pages: The Wikipedia pages to start from if we're crawling + Wikipedia for training data. + :type wiki_start_pages: list of str + """ + def __init__(self, name=None, iso_code=None, use_ascii=True, charsets=None, + alphabet=None, wiki_start_pages=None): + super(Language, self).__init__() + self.name = name + self.iso_code = iso_code + self.use_ascii = use_ascii + self.charsets = charsets + if self.use_ascii: + if alphabet: + alphabet += ascii_letters + else: + alphabet = ascii_letters + elif not alphabet: + raise ValueError('Must supply alphabet if use_ascii is False') + self.alphabet = ''.join(sorted(set(alphabet))) if alphabet else None + self.wiki_start_pages = wiki_start_pages + + def __repr__(self): + return '{}({})'.format(self.__class__.__name__, + ', '.join('{}={!r}'.format(k, v) + for k, v in self.__dict__.items() + if not k.startswith('_'))) + + +LANGUAGES = {'Arabic': Language(name='Arabic', + iso_code='ar', + use_ascii=False, + # We only support encodings that use isolated + # forms, because the current recommendation is + # that the rendering system handles presentation + # forms. This means we purposefully skip IBM864. + charsets=['ISO-8859-6', 'WINDOWS-1256', + 'CP720', 'CP864'], + alphabet=u'ءآأؤإئابةتثجحخدذرزسشصضطظعغػؼؽؾؿـفقكلمنهوىيًٌٍَُِّ', + wiki_start_pages=[u'الصفحة_الرئيسية']), + 'Belarusian': Language(name='Belarusian', + iso_code='be', + use_ascii=False, + charsets=['ISO-8859-5', 'WINDOWS-1251', + 'IBM866', 'MacCyrillic'], + alphabet=(u'АБВГДЕЁЖЗІЙКЛМНОПРСТУЎФХЦЧШЫЬЭЮЯ' + u'абвгдеёжзійклмнопрстуўфхцчшыьэюяʼ'), + wiki_start_pages=[u'Галоўная_старонка']), + 'Bulgarian': Language(name='Bulgarian', + iso_code='bg', + use_ascii=False, + charsets=['ISO-8859-5', 'WINDOWS-1251', + 'IBM855'], + alphabet=(u'АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЬЮЯ' + u'абвгдежзийклмнопрстуфхцчшщъьюя'), + wiki_start_pages=[u'Начална_страница']), + 'Czech': Language(name='Czech', + iso_code='cz', + use_ascii=True, + charsets=['ISO-8859-2', 'WINDOWS-1250'], + alphabet=u'áčďéěíňóřšťúůýžÁČĎÉĚÍŇÓŘŠŤÚŮÝŽ', + wiki_start_pages=[u'Hlavní_strana']), + 'Danish': Language(name='Danish', + iso_code='da', + use_ascii=True, + charsets=['ISO-8859-1', 'ISO-8859-15', + 'WINDOWS-1252'], + alphabet=u'æøåÆØÅ', + wiki_start_pages=[u'Forside']), + 'German': Language(name='German', + iso_code='de', + use_ascii=True, + charsets=['ISO-8859-1', 'WINDOWS-1252'], + alphabet=u'äöüßÄÖÜ', + wiki_start_pages=[u'Wikipedia:Hauptseite']), + 'Greek': Language(name='Greek', + iso_code='el', + use_ascii=False, + charsets=['ISO-8859-7', 'WINDOWS-1253'], + alphabet=(u'αβγδεζηθικλμνξοπρσςτυφχψωάέήίόύώ' + u'ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΣΤΥΦΧΨΩΆΈΉΊΌΎΏ'), + wiki_start_pages=[u'Πύλη:Κύρια']), + 'English': Language(name='English', + iso_code='en', + use_ascii=True, + charsets=['ISO-8859-1', 'WINDOWS-1252'], + wiki_start_pages=[u'Main_Page']), + 'Esperanto': Language(name='Esperanto', + iso_code='eo', + # Q, W, X, and Y not used at all + use_ascii=False, + charsets=['ISO-8859-3'], + alphabet=(u'abcĉdefgĝhĥijĵklmnoprsŝtuŭvz' + u'ABCĈDEFGĜHĤIJĴKLMNOPRSŜTUŬVZ'), + wiki_start_pages=[u'Vikipedio:Ĉefpaĝo']), + 'Spanish': Language(name='Spanish', + iso_code='es', + use_ascii=True, + charsets=['ISO-8859-1', 'ISO-8859-15', + 'WINDOWS-1252'], + alphabet=u'ñáéíóúüÑÁÉÍÓÚÜ', + wiki_start_pages=[u'Wikipedia:Portada']), + 'Estonian': Language(name='Estonian', + iso_code='et', + use_ascii=False, + charsets=['ISO-8859-4', 'ISO-8859-13', + 'WINDOWS-1257'], + # C, F, Š, Q, W, X, Y, Z, Ž are only for + # loanwords + alphabet=(u'ABDEGHIJKLMNOPRSTUVÕÄÖÜ' + u'abdeghijklmnoprstuvõäöü'), + wiki_start_pages=[u'Esileht']), + 'Finnish': Language(name='Finnish', + iso_code='fi', + use_ascii=True, + charsets=['ISO-8859-1', 'ISO-8859-15', + 'WINDOWS-1252'], + alphabet=u'ÅÄÖŠŽåäöšž', + wiki_start_pages=[u'Wikipedia:Etusivu']), + 'French': Language(name='French', + iso_code='fr', + use_ascii=True, + charsets=['ISO-8859-1', 'ISO-8859-15', + 'WINDOWS-1252'], + alphabet=u'œàâçèéîïùûêŒÀÂÇÈÉÎÏÙÛÊ', + wiki_start_pages=[u'Wikipédia:Accueil_principal', + u'Bœuf (animal)']), + 'Hebrew': Language(name='Hebrew', + iso_code='he', + use_ascii=False, + charsets=['ISO-8859-8', 'WINDOWS-1255'], + alphabet=u'אבגדהוזחטיךכלםמןנסעףפץצקרשתװױײ', + wiki_start_pages=[u'עמוד_ראשי']), + 'Croatian': Language(name='Croatian', + iso_code='hr', + # Q, W, X, Y are only used for foreign words. + use_ascii=False, + charsets=['ISO-8859-2', 'WINDOWS-1250'], + alphabet=(u'abcčćdđefghijklmnoprsštuvzž' + u'ABCČĆDĐEFGHIJKLMNOPRSŠTUVZŽ'), + wiki_start_pages=[u'Glavna_stranica']), + 'Hungarian': Language(name='Hungarian', + iso_code='hu', + # Q, W, X, Y are only used for foreign words. + use_ascii=False, + charsets=['ISO-8859-2', 'WINDOWS-1250'], + alphabet=(u'abcdefghijklmnoprstuvzáéíóöőúüű' + u'ABCDEFGHIJKLMNOPRSTUVZÁÉÍÓÖŐÚÜŰ'), + wiki_start_pages=[u'Kezdőlap']), + 'Italian': Language(name='Italian', + iso_code='it', + use_ascii=True, + charsets=['ISO-8859-1', 'ISO-8859-15', + 'WINDOWS-1252'], + alphabet=u'ÀÈÉÌÒÓÙàèéìòóù', + wiki_start_pages=[u'Pagina_principale']), + 'Lithuanian': Language(name='Lithuanian', + iso_code='lt', + use_ascii=False, + charsets=['ISO-8859-13', 'WINDOWS-1257', + 'ISO-8859-4'], + # Q, W, and X not used at all + alphabet=(u'AĄBCČDEĘĖFGHIĮYJKLMNOPRSŠTUŲŪVZŽ' + u'aąbcčdeęėfghiįyjklmnoprsštuųūvzž'), + wiki_start_pages=[u'Pagrindinis_puslapis']), + 'Latvian': Language(name='Latvian', + iso_code='lv', + use_ascii=False, + charsets=['ISO-8859-13', 'WINDOWS-1257', + 'ISO-8859-4'], + # Q, W, X, Y are only for loanwords + alphabet=(u'AĀBCČDEĒFGĢHIĪJKĶLĻMNŅOPRSŠTUŪVZŽ' + u'aābcčdeēfgģhiījkķlļmnņoprsštuūvzž'), + wiki_start_pages=[u'Sākumlapa']), + 'Macedonian': Language(name='Macedonian', + iso_code='mk', + use_ascii=False, + charsets=['ISO-8859-5', 'WINDOWS-1251', + 'MacCyrillic', 'IBM855'], + alphabet=(u'АБВГДЃЕЖЗЅИЈКЛЉМНЊОПРСТЌУФХЦЧЏШ' + u'абвгдѓежзѕијклљмнњопрстќуфхцчџш'), + wiki_start_pages=[u'Главна_страница']), + 'Dutch': Language(name='Dutch', + iso_code='nl', + use_ascii=True, + charsets=['ISO-8859-1', 'WINDOWS-1252'], + wiki_start_pages=[u'Hoofdpagina']), + 'Polish': Language(name='Polish', + iso_code='pl', + # Q and X are only used for foreign words. + use_ascii=False, + charsets=['ISO-8859-2', 'WINDOWS-1250'], + alphabet=(u'AĄBCĆDEĘFGHIJKLŁMNŃOÓPRSŚTUWYZŹŻ' + u'aąbcćdeęfghijklłmnńoóprsśtuwyzźż'), + wiki_start_pages=[u'Wikipedia:Strona_główna']), + 'Portuguese': Language(name='Portuguese', + iso_code='pt', + use_ascii=True, + charsets=['ISO-8859-1', 'ISO-8859-15', + 'WINDOWS-1252'], + alphabet=u'ÁÂÃÀÇÉÊÍÓÔÕÚáâãàçéêíóôõú', + wiki_start_pages=[u'Wikipédia:Página_principal']), + 'Romanian': Language(name='Romanian', + iso_code='ro', + use_ascii=True, + charsets=['ISO-8859-2', 'WINDOWS-1250'], + alphabet=u'ăâîșțĂÂÎȘȚ', + wiki_start_pages=[u'Pagina_principală']), + 'Russian': Language(name='Russian', + iso_code='ru', + use_ascii=False, + charsets=['ISO-8859-5', 'WINDOWS-1251', + 'KOI8-R', 'MacCyrillic', 'IBM866', + 'IBM855'], + alphabet=(u'абвгдеёжзийклмнопрстуфхцчшщъыьэюя' + u'АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ'), + wiki_start_pages=[u'Заглавная_страница']), + 'Slovak': Language(name='Slovak', + iso_code='sk', + use_ascii=True, + charsets=['ISO-8859-2', 'WINDOWS-1250'], + alphabet=u'áäčďéíĺľňóôŕšťúýžÁÄČĎÉÍĹĽŇÓÔŔŠŤÚÝŽ', + wiki_start_pages=[u'Hlavná_stránka']), + 'Slovene': Language(name='Slovene', + iso_code='sl', + # Q, W, X, Y are only used for foreign words. + use_ascii=False, + charsets=['ISO-8859-2', 'WINDOWS-1250'], + alphabet=(u'abcčdefghijklmnoprsštuvzž' + u'ABCČDEFGHIJKLMNOPRSŠTUVZŽ'), + wiki_start_pages=[u'Glavna_stran']), + # Serbian can be written in both Latin and Cyrillic, but there's no + # simple way to get the Latin alphabet pages from Wikipedia through + # the API, so for now we just support Cyrillic. + 'Serbian': Language(name='Serbian', + iso_code='sr', + alphabet=(u'АБВГДЂЕЖЗИЈКЛЉМНЊОПРСТЋУФХЦЧЏШ' + u'абвгдђежзијклљмнњопрстћуфхцчџш'), + charsets=['ISO-8859-5', 'WINDOWS-1251', + 'MacCyrillic', 'IBM855'], + wiki_start_pages=[u'Главна_страна']), + 'Thai': Language(name='Thai', + iso_code='th', + use_ascii=False, + charsets=['ISO-8859-11', 'TIS-620', 'CP874'], + alphabet=u'กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำิีึืฺุู฿เแโใไๅๆ็่้๊๋์ํ๎๏๐๑๒๓๔๕๖๗๘๙๚๛', + wiki_start_pages=[u'หน้าหลัก']), + 'Turkish': Language(name='Turkish', + iso_code='tr', + # Q, W, and X are not used by Turkish + use_ascii=False, + charsets=['ISO-8859-3', 'ISO-8859-9', + 'WINDOWS-1254'], + alphabet=(u'abcçdefgğhıijklmnoöprsştuüvyzâîû' + u'ABCÇDEFGĞHIİJKLMNOÖPRSŞTUÜVYZÂÎÛ'), + wiki_start_pages=[u'Ana_Sayfa']), + 'Vietnamese': Language(name='Vietnamese', + iso_code='vi', + use_ascii=False, + # Windows-1258 is the only common 8-bit + # Vietnamese encoding supported by Python. + # From Wikipedia: + # For systems that lack support for Unicode, + # dozens of 8-bit Vietnamese code pages are + # available.[1] The most common are VISCII + # (TCVN 5712:1993), VPS, and Windows-1258.[3] + # Where ASCII is required, such as when + # ensuring readability in plain text e-mail, + # Vietnamese letters are often encoded + # according to Vietnamese Quoted-Readable + # (VIQR) or VSCII Mnemonic (VSCII-MNEM),[4] + # though usage of either variable-width + # scheme has declined dramatically following + # the adoption of Unicode on the World Wide + # Web. + charsets=['WINDOWS-1258'], + alphabet=(u'aăâbcdđeêghiklmnoôơpqrstuưvxy' + u'AĂÂBCDĐEÊGHIKLMNOÔƠPQRSTUƯVXY'), + wiki_start_pages=[u'Chữ_Quốc_ngữ']), + } diff --git a/pipenv/patched/notpip/_vendor/chardet/sbcharsetprober.py b/pipenv/patched/notpip/_vendor/chardet/sbcharsetprober.py index 0adb51de..46ba835c 100644 --- a/pipenv/patched/notpip/_vendor/chardet/sbcharsetprober.py +++ b/pipenv/patched/notpip/_vendor/chardet/sbcharsetprober.py @@ -26,10 +26,22 @@ # 02110-1301 USA ######################### END LICENSE BLOCK ######################### +from collections import namedtuple + from .charsetprober import CharSetProber from .enums import CharacterCategory, ProbingState, SequenceLikelihood +SingleByteCharSetModel = namedtuple('SingleByteCharSetModel', + ['charset_name', + 'language', + 'char_to_order_map', + 'language_model', + 'typical_positive_ratio', + 'keep_ascii_letters', + 'alphabet']) + + class SingleByteCharSetProber(CharSetProber): SAMPLE_SIZE = 64 SB_ENOUGH_REL_THRESHOLD = 1024 # 0.25 * SAMPLE_SIZE^2 @@ -65,25 +77,25 @@ class SingleByteCharSetProber(CharSetProber): if self._name_prober: return self._name_prober.charset_name else: - return self._model['charset_name'] + return self._model.charset_name @property def language(self): if self._name_prober: return self._name_prober.language else: - return self._model.get('language') + return self._model.language def feed(self, byte_str): - if not self._model['keep_english_letter']: + # TODO: Make filter_international_words keep things in self.alphabet + if not self._model.keep_ascii_letters: byte_str = self.filter_international_words(byte_str) if not byte_str: return self.state - char_to_order_map = self._model['char_to_order_map'] - for i, c in enumerate(byte_str): - # XXX: Order is in range 1-64, so one would think we want 0-63 here, - # but that leads to 27 more test failures than before. - order = char_to_order_map[c] + char_to_order_map = self._model.char_to_order_map + language_model = self._model.language_model + for char in byte_str: + order = char_to_order_map.get(char, CharacterCategory.UNDEFINED) # XXX: This was SYMBOL_CAT_ORDER before, with a value of 250, but # CharacterCategory.SYMBOL is actually 253, so we use CONTROL # to make it closer to the original intent. The only difference @@ -91,20 +103,21 @@ class SingleByteCharSetProber(CharSetProber): # _total_char purposes. if order < CharacterCategory.CONTROL: self._total_char += 1 + # TODO: Follow uchardet's lead and discount confidence for frequent + # control characters. + # See https://github.com/BYVoid/uchardet/commit/55b4f23971db61 if order < self.SAMPLE_SIZE: self._freq_char += 1 if self._last_order < self.SAMPLE_SIZE: self._total_seqs += 1 if not self._reversed: - i = (self._last_order * self.SAMPLE_SIZE) + order - model = self._model['precedence_matrix'][i] - else: # reverse the order of the letters in the lookup - i = (order * self.SAMPLE_SIZE) + self._last_order - model = self._model['precedence_matrix'][i] - self._seq_counters[model] += 1 + lm_cat = language_model[self._last_order][order] + else: + lm_cat = language_model[order][self._last_order] + self._seq_counters[lm_cat] += 1 self._last_order = order - charset_name = self._model['charset_name'] + charset_name = self._model.charset_name if self.state == ProbingState.DETECTING: if self._total_seqs > self.SB_ENOUGH_REL_THRESHOLD: confidence = self.get_confidence() @@ -125,7 +138,7 @@ class SingleByteCharSetProber(CharSetProber): r = 0.01 if self._total_seqs > 0: r = ((1.0 * self._seq_counters[SequenceLikelihood.POSITIVE]) / - self._total_seqs / self._model['typical_positive_ratio']) + self._total_seqs / self._model.typical_positive_ratio) r = r * self._freq_char / self._total_char if r >= 1.0: r = 0.99 diff --git a/pipenv/patched/notpip/_vendor/chardet/sbcsgroupprober.py b/pipenv/patched/notpip/_vendor/chardet/sbcsgroupprober.py index 98e95dc1..bdeef4e1 100644 --- a/pipenv/patched/notpip/_vendor/chardet/sbcsgroupprober.py +++ b/pipenv/patched/notpip/_vendor/chardet/sbcsgroupprober.py @@ -27,47 +27,57 @@ ######################### END LICENSE BLOCK ######################### from .charsetgroupprober import CharSetGroupProber -from .sbcharsetprober import SingleByteCharSetProber -from .langcyrillicmodel import (Win1251CyrillicModel, Koi8rModel, - Latin5CyrillicModel, MacCyrillicModel, - Ibm866Model, Ibm855Model) -from .langgreekmodel import Latin7GreekModel, Win1253GreekModel -from .langbulgarianmodel import Latin5BulgarianModel, Win1251BulgarianModel -# from .langhungarianmodel import Latin2HungarianModel, Win1250HungarianModel -from .langthaimodel import TIS620ThaiModel -from .langhebrewmodel import Win1255HebrewModel from .hebrewprober import HebrewProber -from .langturkishmodel import Latin5TurkishModel +from .langbulgarianmodel import (ISO_8859_5_BULGARIAN_MODEL, + WINDOWS_1251_BULGARIAN_MODEL) +from .langgreekmodel import ISO_8859_7_GREEK_MODEL, WINDOWS_1253_GREEK_MODEL +from .langhebrewmodel import WINDOWS_1255_HEBREW_MODEL +# from .langhungarianmodel import (ISO_8859_2_HUNGARIAN_MODEL, +# WINDOWS_1250_HUNGARIAN_MODEL) +from .langrussianmodel import (IBM855_RUSSIAN_MODEL, IBM866_RUSSIAN_MODEL, + ISO_8859_5_RUSSIAN_MODEL, KOI8_R_RUSSIAN_MODEL, + MACCYRILLIC_RUSSIAN_MODEL, + WINDOWS_1251_RUSSIAN_MODEL) +from .langthaimodel import TIS_620_THAI_MODEL +from .langturkishmodel import ISO_8859_9_TURKISH_MODEL +from .sbcharsetprober import SingleByteCharSetProber class SBCSGroupProber(CharSetGroupProber): def __init__(self): super(SBCSGroupProber, self).__init__() + hebrew_prober = HebrewProber() + logical_hebrew_prober = SingleByteCharSetProber(WINDOWS_1255_HEBREW_MODEL, + False, hebrew_prober) + # TODO: See if using ISO-8859-8 Hebrew model works better here, since + # it's actually the visual one + visual_hebrew_prober = SingleByteCharSetProber(WINDOWS_1255_HEBREW_MODEL, + True, hebrew_prober) + hebrew_prober.set_model_probers(logical_hebrew_prober, + visual_hebrew_prober) + # TODO: ORDER MATTERS HERE. I changed the order vs what was in master + # and several tests failed that did not before. Some thought + # should be put into the ordering, and we should consider making + # order not matter here, because that is very counter-intuitive. self.probers = [ - SingleByteCharSetProber(Win1251CyrillicModel), - SingleByteCharSetProber(Koi8rModel), - SingleByteCharSetProber(Latin5CyrillicModel), - SingleByteCharSetProber(MacCyrillicModel), - SingleByteCharSetProber(Ibm866Model), - SingleByteCharSetProber(Ibm855Model), - SingleByteCharSetProber(Latin7GreekModel), - SingleByteCharSetProber(Win1253GreekModel), - SingleByteCharSetProber(Latin5BulgarianModel), - SingleByteCharSetProber(Win1251BulgarianModel), + SingleByteCharSetProber(WINDOWS_1251_RUSSIAN_MODEL), + SingleByteCharSetProber(KOI8_R_RUSSIAN_MODEL), + SingleByteCharSetProber(ISO_8859_5_RUSSIAN_MODEL), + SingleByteCharSetProber(MACCYRILLIC_RUSSIAN_MODEL), + SingleByteCharSetProber(IBM866_RUSSIAN_MODEL), + SingleByteCharSetProber(IBM855_RUSSIAN_MODEL), + SingleByteCharSetProber(ISO_8859_7_GREEK_MODEL), + SingleByteCharSetProber(WINDOWS_1253_GREEK_MODEL), + SingleByteCharSetProber(ISO_8859_5_BULGARIAN_MODEL), + SingleByteCharSetProber(WINDOWS_1251_BULGARIAN_MODEL), # TODO: Restore Hungarian encodings (iso-8859-2 and windows-1250) # after we retrain model. - # SingleByteCharSetProber(Latin2HungarianModel), - # SingleByteCharSetProber(Win1250HungarianModel), - SingleByteCharSetProber(TIS620ThaiModel), - SingleByteCharSetProber(Latin5TurkishModel), + # SingleByteCharSetProber(ISO_8859_2_HUNGARIAN_MODEL), + # SingleByteCharSetProber(WINDOWS_1250_HUNGARIAN_MODEL), + SingleByteCharSetProber(TIS_620_THAI_MODEL), + SingleByteCharSetProber(ISO_8859_9_TURKISH_MODEL), + hebrew_prober, + logical_hebrew_prober, + visual_hebrew_prober, ] - hebrew_prober = HebrewProber() - logical_hebrew_prober = SingleByteCharSetProber(Win1255HebrewModel, - False, hebrew_prober) - visual_hebrew_prober = SingleByteCharSetProber(Win1255HebrewModel, True, - hebrew_prober) - hebrew_prober.set_model_probers(logical_hebrew_prober, visual_hebrew_prober) - self.probers.extend([hebrew_prober, logical_hebrew_prober, - visual_hebrew_prober]) - self.reset() diff --git a/pipenv/patched/notpip/_vendor/chardet/universaldetector.py b/pipenv/patched/notpip/_vendor/chardet/universaldetector.py index 7b4e92d6..055a8ac1 100644 --- a/pipenv/patched/notpip/_vendor/chardet/universaldetector.py +++ b/pipenv/patched/notpip/_vendor/chardet/universaldetector.py @@ -266,7 +266,7 @@ class UniversalDetector(object): 'language': max_prober.language} # Log all prober confidences if none met MINIMUM_THRESHOLD - if self.logger.getEffectiveLevel() == logging.DEBUG: + if self.logger.getEffectiveLevel() <= logging.DEBUG: if self.result['encoding'] is None: self.logger.debug('no probers hit minimum threshold') for group_prober in self._charset_probers: @@ -280,7 +280,7 @@ class UniversalDetector(object): prober.get_confidence()) else: self.logger.debug('%s %s confidence = %s', - prober.charset_name, - prober.language, - prober.get_confidence()) + group_prober.charset_name, + group_prober.language, + group_prober.get_confidence()) return self.result diff --git a/pipenv/patched/notpip/_vendor/chardet/version.py b/pipenv/patched/notpip/_vendor/chardet/version.py index bb2a34a7..70369b9d 100644 --- a/pipenv/patched/notpip/_vendor/chardet/version.py +++ b/pipenv/patched/notpip/_vendor/chardet/version.py @@ -5,5 +5,5 @@ from within setup.py and from chardet subpackages. :author: Dan Blanchard (dan.blanchard@gmail.com) """ -__version__ = "3.0.4" +__version__ = "4.0.0" VERSION = __version__.split('.') diff --git a/pipenv/patched/notpip/_vendor/colorama/__init__.py b/pipenv/patched/notpip/_vendor/colorama/__init__.py index 2a3bf471..b149ed79 100644 --- a/pipenv/patched/notpip/_vendor/colorama/__init__.py +++ b/pipenv/patched/notpip/_vendor/colorama/__init__.py @@ -3,4 +3,4 @@ from .initialise import init, deinit, reinit, colorama_text from .ansi import Fore, Back, Style, Cursor from .ansitowin32 import AnsiToWin32 -__version__ = '0.4.1' +__version__ = '0.4.4' diff --git a/pipenv/patched/notpip/_vendor/colorama/ansi.py b/pipenv/patched/notpip/_vendor/colorama/ansi.py index 78776588..11ec695f 100644 --- a/pipenv/patched/notpip/_vendor/colorama/ansi.py +++ b/pipenv/patched/notpip/_vendor/colorama/ansi.py @@ -6,7 +6,7 @@ See: http://en.wikipedia.org/wiki/ANSI_escape_code CSI = '\033[' OSC = '\033]' -BEL = '\007' +BEL = '\a' def code_to_chars(code): diff --git a/pipenv/patched/notpip/_vendor/colorama/ansitowin32.py b/pipenv/patched/notpip/_vendor/colorama/ansitowin32.py index 359c92be..6039a054 100644 --- a/pipenv/patched/notpip/_vendor/colorama/ansitowin32.py +++ b/pipenv/patched/notpip/_vendor/colorama/ansitowin32.py @@ -3,7 +3,7 @@ import re import sys import os -from .ansi import AnsiFore, AnsiBack, AnsiStyle, Style +from .ansi import AnsiFore, AnsiBack, AnsiStyle, Style, BEL from .winterm import WinTerm, WinColor, WinStyle from .win32 import windll, winapi_test @@ -68,7 +68,7 @@ class AnsiToWin32(object): win32 function calls. ''' ANSI_CSI_RE = re.compile('\001?\033\\[((?:\\d|;)*)([a-zA-Z])\002?') # Control Sequence Introducer - ANSI_OSC_RE = re.compile('\001?\033\\]((?:.|;)*?)(\x07)\002?') # Operating System Command + ANSI_OSC_RE = re.compile('\001?\033\\]([^\a]*)(\a)\002?') # Operating System Command def __init__(self, wrapped, convert=None, strip=None, autoreset=False): # The wrapped stream (normally sys.stdout or sys.stderr) @@ -247,11 +247,12 @@ class AnsiToWin32(object): start, end = match.span() text = text[:start] + text[end:] paramstring, command = match.groups() - if command in '\x07': # \x07 = BEL - params = paramstring.split(";") - # 0 - change title and icon (we will only change title) - # 1 - change icon (we don't support this) - # 2 - change title - if params[0] in '02': - winterm.set_title(params[1]) + if command == BEL: + if paramstring.count(";") == 1: + params = paramstring.split(";") + # 0 - change title and icon (we will only change title) + # 1 - change icon (we don't support this) + # 2 - change title + if params[0] in '02': + winterm.set_title(params[1]) return text diff --git a/pipenv/patched/notpip/_vendor/distlib/__init__.py b/pipenv/patched/notpip/_vendor/distlib/__init__.py index a786b4d3..492c2c70 100644 --- a/pipenv/patched/notpip/_vendor/distlib/__init__.py +++ b/pipenv/patched/notpip/_vendor/distlib/__init__.py @@ -1,12 +1,12 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2012-2017 Vinay Sajip. +# Copyright (C) 2012-2019 Vinay Sajip. # Licensed to the Python Software Foundation under a contributor agreement. # See LICENSE.txt and CONTRIBUTORS.txt. # import logging -__version__ = '0.2.8' +__version__ = '0.3.2' class DistlibException(Exception): pass diff --git a/pipenv/patched/notpip/_vendor/distlib/_backport/shutil.py b/pipenv/patched/notpip/_vendor/distlib/_backport/shutil.py index 159e49ee..10ed3625 100644 --- a/pipenv/patched/notpip/_vendor/distlib/_backport/shutil.py +++ b/pipenv/patched/notpip/_vendor/distlib/_backport/shutil.py @@ -14,7 +14,10 @@ import sys import stat from os.path import abspath import fnmatch -import collections +try: + from collections.abc import Callable +except ImportError: + from collections import Callable import errno from . import tarfile @@ -528,7 +531,7 @@ def register_archive_format(name, function, extra_args=None, description=''): """ if extra_args is None: extra_args = [] - if not isinstance(function, collections.Callable): + if not isinstance(function, Callable): raise TypeError('The %s object is not callable' % function) if not isinstance(extra_args, (tuple, list)): raise TypeError('extra_args needs to be a sequence') @@ -621,7 +624,7 @@ def _check_unpack_options(extensions, function, extra_args): raise RegistryError(msg % (extension, existing_extensions[extension])) - if not isinstance(function, collections.Callable): + if not isinstance(function, Callable): raise TypeError('The registered function must be a callable') diff --git a/pipenv/patched/notpip/_vendor/distlib/_backport/sysconfig.py b/pipenv/patched/notpip/_vendor/distlib/_backport/sysconfig.py index 1df3aba1..b470a373 100644 --- a/pipenv/patched/notpip/_vendor/distlib/_backport/sysconfig.py +++ b/pipenv/patched/notpip/_vendor/distlib/_backport/sysconfig.py @@ -119,11 +119,9 @@ def _expand_globals(config): #_expand_globals(_SCHEMES) - # FIXME don't rely on sys.version here, its format is an implementation detail - # of CPython, use sys.version_info or sys.hexversion -_PY_VERSION = sys.version.split()[0] -_PY_VERSION_SHORT = sys.version[:3] -_PY_VERSION_SHORT_NO_DOT = _PY_VERSION[0] + _PY_VERSION[2] +_PY_VERSION = '%s.%s.%s' % sys.version_info[:3] +_PY_VERSION_SHORT = '%s.%s' % sys.version_info[:2] +_PY_VERSION_SHORT_NO_DOT = '%s%s' % sys.version_info[:2] _PREFIX = os.path.normpath(sys.prefix) _EXEC_PREFIX = os.path.normpath(sys.exec_prefix) _CONFIG_VARS = None diff --git a/pipenv/patched/notpip/_vendor/distlib/compat.py b/pipenv/patched/notpip/_vendor/distlib/compat.py index ff328c8e..c316fd97 100644 --- a/pipenv/patched/notpip/_vendor/distlib/compat.py +++ b/pipenv/patched/notpip/_vendor/distlib/compat.py @@ -319,7 +319,7 @@ except ImportError: # pragma: no cover try: callable = callable except NameError: # pragma: no cover - from collections import Callable + from collections.abc import Callable def callable(obj): return isinstance(obj, Callable) diff --git a/pipenv/patched/notpip/_vendor/distlib/database.py b/pipenv/patched/notpip/_vendor/distlib/database.py index b13cdac9..0a90c300 100644 --- a/pipenv/patched/notpip/_vendor/distlib/database.py +++ b/pipenv/patched/notpip/_vendor/distlib/database.py @@ -550,7 +550,7 @@ class InstalledDistribution(BaseInstalledDistribution): r = finder.find(WHEEL_METADATA_FILENAME) # Temporary - for legacy support if r is None: - r = finder.find('METADATA') + r = finder.find(LEGACY_METADATA_FILENAME) if r is None: raise ValueError('no %s found in %s' % (METADATA_FILENAME, path)) @@ -567,7 +567,7 @@ class InstalledDistribution(BaseInstalledDistribution): p = os.path.join(path, 'top_level.txt') if os.path.exists(p): with open(p, 'rb') as f: - data = f.read() + data = f.read().decode('utf-8') self.modules = data.splitlines() def __repr__(self): diff --git a/pipenv/patched/notpip/_vendor/distlib/index.py b/pipenv/patched/notpip/_vendor/distlib/index.py index 7a87cdcf..b1fbbf8e 100644 --- a/pipenv/patched/notpip/_vendor/distlib/index.py +++ b/pipenv/patched/notpip/_vendor/distlib/index.py @@ -18,7 +18,7 @@ except ImportError: from . import DistlibException from .compat import (HTTPBasicAuthHandler, Request, HTTPPasswordMgr, urlparse, build_opener, string_types) -from .util import cached_property, zip_dir, ServerProxy +from .util import zip_dir, ServerProxy logger = logging.getLogger(__name__) @@ -67,21 +67,17 @@ class PackageIndex(object): Get the distutils command for interacting with PyPI configurations. :return: the command. """ - from distutils.core import Distribution - from distutils.config import PyPIRCCommand - d = Distribution() - return PyPIRCCommand(d) + from .util import _get_pypirc_command as cmd + return cmd() def read_configuration(self): """ - Read the PyPI access configuration as supported by distutils, getting - PyPI to do the actual work. This populates ``username``, ``password``, - ``realm`` and ``url`` attributes from the configuration. + Read the PyPI access configuration as supported by distutils. This populates + ``username``, ``password``, ``realm`` and ``url`` attributes from the + configuration. """ - # get distutils to do the work - c = self._get_pypirc_command() - c.repository = self.url - cfg = c._read_pypirc() + from .util import _load_pypirc + cfg = _load_pypirc(self) self.username = cfg.get('username') self.password = cfg.get('password') self.realm = cfg.get('realm', 'pypi') @@ -91,13 +87,10 @@ class PackageIndex(object): """ Save the PyPI access configuration. You must have set ``username`` and ``password`` attributes before calling this method. - - Again, distutils is used to do the actual work. """ self.check_credentials() - # get distutils to do the work - c = self._get_pypirc_command() - c._store_pypirc(self.username, self.password) + from .util import _store_pypirc + _store_pypirc(self) def check_credentials(self): """ diff --git a/pipenv/patched/notpip/_vendor/distlib/locators.py b/pipenv/patched/notpip/_vendor/distlib/locators.py index a7ed9469..0c7d6391 100644 --- a/pipenv/patched/notpip/_vendor/distlib/locators.py +++ b/pipenv/patched/notpip/_vendor/distlib/locators.py @@ -20,14 +20,14 @@ import zlib from . import DistlibException from .compat import (urljoin, urlparse, urlunparse, url2pathname, pathname2url, - queue, quote, unescape, string_types, build_opener, + queue, quote, unescape, build_opener, HTTPRedirectHandler as BaseRedirectHandler, text_type, Request, HTTPError, URLError) from .database import Distribution, DistributionPath, make_dist from .metadata import Metadata, MetadataInvalidError -from .util import (cached_property, parse_credentials, ensure_slash, - split_filename, get_project_data, parse_requirement, - parse_name_and_version, ServerProxy, normalize_name) +from .util import (cached_property, ensure_slash, split_filename, get_project_data, + parse_requirement, parse_name_and_version, ServerProxy, + normalize_name) from .version import get_scheme, UnsupportedVersionError from .wheel import Wheel, is_compatible @@ -304,18 +304,25 @@ class Locator(object): def _get_digest(self, info): """ - Get a digest from a dictionary by looking at keys of the form - 'algo_digest'. + Get a digest from a dictionary by looking at a "digests" dictionary + or keys of the form 'algo_digest'. Returns a 2-tuple (algo, digest) if found, else None. Currently looks only for SHA256, then MD5. """ result = None - for algo in ('sha256', 'md5'): - key = '%s_digest' % algo - if key in info: - result = (algo, info[key]) - break + if 'digests' in info: + digests = info['digests'] + for algo in ('sha256', 'md5'): + if algo in digests: + result = (algo, digests[algo]) + break + if not result: + for algo in ('sha256', 'md5'): + key = '%s_digest' % algo + if key in info: + result = (algo, info[key]) + break return result def _update_version_data(self, result, info): @@ -371,13 +378,13 @@ class Locator(object): continue try: if not matcher.match(k): - logger.debug('%s did not match %r', matcher, k) + pass # logger.debug('%s did not match %r', matcher, k) else: if prereleases or not vcls(k).is_prerelease: slist.append(k) - else: - logger.debug('skipping pre-release ' - 'version %s of %s', k, matcher.name) + # else: + # logger.debug('skipping pre-release ' + # 'version %s of %s', k, matcher.name) except Exception: # pragma: no cover logger.warning('error matching %s with %r', matcher, k) pass # slist.append(k) @@ -586,7 +593,7 @@ class SimpleScrapingLocator(Locator): # These are used to deal with various Content-Encoding schemes. decoders = { 'deflate': zlib.decompress, - 'gzip': lambda b: gzip.GzipFile(fileobj=BytesIO(d)).read(), + 'gzip': lambda b: gzip.GzipFile(fileobj=BytesIO(b)).read(), 'none': lambda b: b, } @@ -1055,8 +1062,6 @@ default_locator = AggregatingLocator( locate = default_locator.locate -NAME_VERSION_RE = re.compile(r'(?P<name>[\w-]+)\s*' - r'\(\s*(==\s*)?(?P<ver>[^)]+)\)$') class DependencyFinder(object): """ diff --git a/pipenv/patched/notpip/_vendor/distlib/markers.py b/pipenv/patched/notpip/_vendor/distlib/markers.py index ee1f3e23..923a832b 100644 --- a/pipenv/patched/notpip/_vendor/distlib/markers.py +++ b/pipenv/patched/notpip/_vendor/distlib/markers.py @@ -15,9 +15,8 @@ Parser for the environment markers micro-language defined in PEP 508. import os import sys import platform -import re -from .compat import python_implementation, urlparse, string_types +from .compat import string_types from .util import in_venv, parse_marker __all__ = ['interpret'] diff --git a/pipenv/patched/notpip/_vendor/distlib/metadata.py b/pipenv/patched/notpip/_vendor/distlib/metadata.py index 77eed7f9..6a26b0ab 100644 --- a/pipenv/patched/notpip/_vendor/distlib/metadata.py +++ b/pipenv/patched/notpip/_vendor/distlib/metadata.py @@ -5,7 +5,7 @@ # """Implementation of the Metadata for Python packages PEPs. -Supports all metadata formats (1.0, 1.1, 1.2, and 2.0 experimental). +Supports all metadata formats (1.0, 1.1, 1.2, 1.3/2.1 and withdrawn 2.0). """ from __future__ import unicode_literals @@ -91,9 +91,12 @@ _426_FIELDS = ('Metadata-Version', 'Name', 'Version', 'Platform', _426_MARKERS = ('Private-Version', 'Provides-Extra', 'Obsoleted-By', 'Setup-Requires-Dist', 'Extension') -# See issue #106: Sometimes 'Requires' occurs wrongly in the metadata. Include -# it in the tuple literal below to allow it (for now) -_566_FIELDS = _426_FIELDS + ('Description-Content-Type', 'Requires') +# See issue #106: Sometimes 'Requires' and 'Provides' occur wrongly in +# the metadata. Include them in the tuple literal below to allow them +# (for now). +# Ditto for Obsoletes - see issue #140. +_566_FIELDS = _426_FIELDS + ('Description-Content-Type', + 'Requires', 'Provides', 'Obsoletes') _566_MARKERS = ('Description-Content-Type',) @@ -115,7 +118,8 @@ def _version2fieldlist(version): elif version == '1.2': return _345_FIELDS elif version in ('1.3', '2.1'): - return _345_FIELDS + _566_FIELDS + # avoid adding field names if already there + return _345_FIELDS + tuple(f for f in _566_FIELDS if f not in _345_FIELDS) elif version == '2.0': return _426_FIELDS raise MetadataUnrecognizedVersionError(version) @@ -192,38 +196,12 @@ def _best_version(fields): return '2.0' +# This follows the rules about transforming keys as described in +# https://www.python.org/dev/peps/pep-0566/#id17 _ATTR2FIELD = { - 'metadata_version': 'Metadata-Version', - 'name': 'Name', - 'version': 'Version', - 'platform': 'Platform', - 'supported_platform': 'Supported-Platform', - 'summary': 'Summary', - 'description': 'Description', - 'keywords': 'Keywords', - 'home_page': 'Home-page', - 'author': 'Author', - 'author_email': 'Author-email', - 'maintainer': 'Maintainer', - 'maintainer_email': 'Maintainer-email', - 'license': 'License', - 'classifier': 'Classifier', - 'download_url': 'Download-URL', - 'obsoletes_dist': 'Obsoletes-Dist', - 'provides_dist': 'Provides-Dist', - 'requires_dist': 'Requires-Dist', - 'setup_requires_dist': 'Setup-Requires-Dist', - 'requires_python': 'Requires-Python', - 'requires_external': 'Requires-External', - 'requires': 'Requires', - 'provides': 'Provides', - 'obsoletes': 'Obsoletes', - 'project_url': 'Project-URL', - 'private_version': 'Private-Version', - 'obsoleted_by': 'Obsoleted-By', - 'extension': 'Extension', - 'provides_extra': 'Provides-Extra', + name.lower().replace("-", "_"): name for name in _ALL_FIELDS } +_FIELD2ATTR = {field: attr for attr, field in _ATTR2FIELD.items()} _PREDICATE_FIELDS = ('Requires-Dist', 'Obsoletes-Dist', 'Provides-Dist') _VERSIONS_FIELDS = ('Requires-Python',) @@ -260,7 +238,7 @@ def _get_name_and_version(name, version, for_filename=False): class LegacyMetadata(object): """The legacy metadata of a release. - Supports versions 1.0, 1.1 and 1.2 (auto-detected). You can + Supports versions 1.0, 1.1, 1.2, 2.0 and 1.3/2.1 (auto-detected). You can instantiate the class with one of these arguments (or none): - *path*, the path to a metadata file - *fileobj* give a file-like object with metadata as content @@ -379,6 +357,11 @@ class LegacyMetadata(object): value = msg[field] if value is not None and value != 'UNKNOWN': self.set(field, value) + + # PEP 566 specifies that the body be used for the description, if + # available + body = msg.get_payload() + self["Description"] = body if body else self["Description"] # logger.debug('Attempting to set metadata for %s', self) # self.set_metadata_version() @@ -565,57 +548,21 @@ class LegacyMetadata(object): Field names will be converted to use the underscore-lowercase style instead of hyphen-mixed case (i.e. home_page instead of Home-page). + This is as per https://www.python.org/dev/peps/pep-0566/#id17. """ self.set_metadata_version() - mapping_1_0 = ( - ('metadata_version', 'Metadata-Version'), - ('name', 'Name'), - ('version', 'Version'), - ('summary', 'Summary'), - ('home_page', 'Home-page'), - ('author', 'Author'), - ('author_email', 'Author-email'), - ('license', 'License'), - ('description', 'Description'), - ('keywords', 'Keywords'), - ('platform', 'Platform'), - ('classifiers', 'Classifier'), - ('download_url', 'Download-URL'), - ) + fields = _version2fieldlist(self['Metadata-Version']) data = {} - for key, field_name in mapping_1_0: + + for field_name in fields: if not skip_missing or field_name in self._fields: - data[key] = self[field_name] - - if self['Metadata-Version'] == '1.2': - mapping_1_2 = ( - ('requires_dist', 'Requires-Dist'), - ('requires_python', 'Requires-Python'), - ('requires_external', 'Requires-External'), - ('provides_dist', 'Provides-Dist'), - ('obsoletes_dist', 'Obsoletes-Dist'), - ('project_url', 'Project-URL'), - ('maintainer', 'Maintainer'), - ('maintainer_email', 'Maintainer-email'), - ) - for key, field_name in mapping_1_2: - if not skip_missing or field_name in self._fields: - if key != 'project_url': - data[key] = self[field_name] - else: - data[key] = [','.join(u) for u in self[field_name]] - - elif self['Metadata-Version'] == '1.1': - mapping_1_1 = ( - ('provides', 'Provides'), - ('requires', 'Requires'), - ('obsoletes', 'Obsoletes'), - ) - for key, field_name in mapping_1_1: - if not skip_missing or field_name in self._fields: + key = _FIELD2ATTR[field_name] + if key != 'project_url': data[key] = self[field_name] + else: + data[key] = [','.join(u) for u in self[field_name]] return data @@ -1001,10 +948,14 @@ class Metadata(object): LEGACY_MAPPING = { 'name': 'Name', 'version': 'Version', - 'license': 'License', + ('extensions', 'python.details', 'license'): 'License', 'summary': 'Summary', 'description': 'Description', - 'classifiers': 'Classifier', + ('extensions', 'python.project', 'project_urls', 'Home'): 'Home-page', + ('extensions', 'python.project', 'contacts', 0, 'name'): 'Author', + ('extensions', 'python.project', 'contacts', 0, 'email'): 'Author-email', + 'source_url': 'Download-URL', + ('extensions', 'python.details', 'classifiers'): 'Classifier', } def _to_legacy(self): @@ -1032,16 +983,29 @@ class Metadata(object): assert self._data and not self._legacy result = LegacyMetadata() nmd = self._data + # import pdb; pdb.set_trace() for nk, ok in self.LEGACY_MAPPING.items(): - if nk in nmd: - result[ok] = nmd[nk] + if not isinstance(nk, tuple): + if nk in nmd: + result[ok] = nmd[nk] + else: + d = nmd + found = True + for k in nk: + try: + d = d[k] + except (KeyError, IndexError): + found = False + break + if found: + result[ok] = d r1 = process_entries(self.run_requires + self.meta_requires) r2 = process_entries(self.build_requires + self.dev_requires) if self.extras: result['Provides-Extra'] = sorted(self.extras) result['Requires-Dist'] = sorted(r1) result['Setup-Requires-Dist'] = sorted(r2) - # TODO: other fields such as contacts + # TODO: any other fields wanted return result def write(self, path=None, fileobj=None, legacy=False, skip_unknown=True): diff --git a/pipenv/patched/notpip/_vendor/distlib/resources.py b/pipenv/patched/notpip/_vendor/distlib/resources.py index 18840167..fef52aa1 100644 --- a/pipenv/patched/notpip/_vendor/distlib/resources.py +++ b/pipenv/patched/notpip/_vendor/distlib/resources.py @@ -11,13 +11,12 @@ import io import logging import os import pkgutil -import shutil import sys import types import zipimport from . import DistlibException -from .util import cached_property, get_cache_base, path_to_cache_dir, Cache +from .util import cached_property, get_cache_base, Cache logger = logging.getLogger(__name__) @@ -283,6 +282,7 @@ class ZipResourceFinder(ResourceFinder): result = False return result + _finder_registry = { type(None): ResourceFinder, zipimport.zipimporter: ZipResourceFinder @@ -296,6 +296,8 @@ try: import _frozen_importlib as _fi _finder_registry[_fi.SourceFileLoader] = ResourceFinder _finder_registry[_fi.FileFinder] = ResourceFinder + # See issue #146 + _finder_registry[_fi.SourcelessFileLoader] = ResourceFinder del _fi except (ImportError, AttributeError): pass @@ -304,6 +306,7 @@ except (ImportError, AttributeError): def register_finder(loader, finder_maker): _finder_registry[type(loader)] = finder_maker + _finder_cache = {} diff --git a/pipenv/patched/notpip/_vendor/distlib/scripts.py b/pipenv/patched/notpip/_vendor/distlib/scripts.py index 8e22cb91..1ac01dde 100644 --- a/pipenv/patched/notpip/_vendor/distlib/scripts.py +++ b/pipenv/patched/notpip/_vendor/distlib/scripts.py @@ -39,31 +39,16 @@ _DEFAULT_MANIFEST = ''' # check if Python is called on the first line with this expression FIRST_LINE_RE = re.compile(b'^#!.*pythonw?[0-9.]*([ \t].*)?$') SCRIPT_TEMPLATE = r'''# -*- coding: utf-8 -*- +import re +import sys +from %(module)s import %(import_name)s if __name__ == '__main__': - import sys, re - - def _resolve(module, func): - __import__(module) - mod = sys.modules[module] - parts = func.split('.') - result = getattr(mod, parts.pop(0)) - for p in parts: - result = getattr(result, p) - return result - - try: - sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) - - func = _resolve('%(module)s', '%(func)s') - rc = func() # None interpreted as 0 - except Exception as e: # only supporting Python >= 2.6 - sys.stderr.write('%%s\n' %% e) - rc = 1 - sys.exit(rc) + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(%(func)s()) ''' -def _enquote_executable(executable): +def enquote_executable(executable): if ' ' in executable: # make sure we quote only the executable in case of env # for example /usr/bin/env "/dir with spaces/bin/jython" @@ -78,6 +63,8 @@ def _enquote_executable(executable): executable = '"%s"' % executable return executable +# Keep the old name around (for now), as there is at least one project using it! +_enquote_executable = enquote_executable class ScriptMaker(object): """ @@ -103,6 +90,7 @@ class ScriptMaker(object): self._is_nt = os.name == 'nt' or ( os.name == 'java' and os._name == 'nt') + self.version_info = sys.version_info def _get_alternate_executable(self, executable, options): if options.get('gui', False) and self._is_nt: # pragma: no cover @@ -187,12 +175,20 @@ class ScriptMaker(object): if sys.platform.startswith('java'): # pragma: no cover executable = self._fix_jython_executable(executable) - # Normalise case for Windows - executable = os.path.normcase(executable) + + # Normalise case for Windows - COMMENTED OUT + # executable = os.path.normcase(executable) + # N.B. The normalising operation above has been commented out: See + # issue #124. Although paths in Windows are generally case-insensitive, + # they aren't always. For example, a path containing a ẞ (which is a + # LATIN CAPITAL LETTER SHARP S - U+1E9E) is normcased to ß (which is a + # LATIN SMALL LETTER SHARP S' - U+00DF). The two are not considered by + # Windows as equivalent in path names. + # If the user didn't specify an executable, it may be necessary to # cater for executable paths with spaces (not uncommon on Windows) if enquote: - executable = _enquote_executable(executable) + executable = enquote_executable(executable) # Issue #51: don't use fsencode, since we later try to # check that the shebang is decodable using utf-8. executable = executable.encode('utf-8') @@ -225,6 +221,7 @@ class ScriptMaker(object): def _get_script_text(self, entry): return self.script_template % dict(module=entry.prefix, + import_name=entry.suffix.split('.')[0], func=entry.suffix) manifest = _DEFAULT_MANIFEST @@ -285,6 +282,19 @@ class ScriptMaker(object): self._fileop.set_executable_mode([outname]) filenames.append(outname) + variant_separator = '-' + + def get_script_filenames(self, name): + result = set() + if '' in self.variants: + result.add(name) + if 'X' in self.variants: + result.add('%s%s' % (name, self.version_info[0])) + if 'X.Y' in self.variants: + result.add('%s%s%s.%s' % (name, self.variant_separator, + self.version_info[0], self.version_info[1])) + return result + def _make_script(self, entry, filenames, options=None): post_interp = b'' if options: @@ -294,14 +304,7 @@ class ScriptMaker(object): post_interp = args.encode('utf-8') shebang = self._get_shebang('utf-8', post_interp, options=options) script = self._get_script_text(entry).encode('utf-8') - name = entry.name - scriptnames = set() - if '' in self.variants: - scriptnames.add(name) - if 'X' in self.variants: - scriptnames.add('%s%s' % (name, sys.version[0])) - if 'X.Y' in self.variants: - scriptnames.add('%s-%s' % (name, sys.version[:3])) + scriptnames = self.get_script_filenames(entry.name) if options and options.get('gui', False): ext = 'pyw' else: @@ -328,8 +331,7 @@ class ScriptMaker(object): else: first_line = f.readline() if not first_line: # pragma: no cover - logger.warning('%s: %s is an empty file (skipping)', - self.get_command_name(), script) + logger.warning('%s is an empty file (skipping)', script) return match = FIRST_LINE_RE.match(first_line.replace(b'\r\n', b'\n')) @@ -381,8 +383,12 @@ class ScriptMaker(object): # Issue 31: don't hardcode an absolute package name, but # determine it relative to the current package distlib_package = __name__.rsplit('.', 1)[0] - result = finder(distlib_package).find(name).bytes - return result + resource = finder(distlib_package).find(name) + if not resource: + msg = ('Unable to find resource %s in package %s' % (name, + distlib_package)) + raise ValueError(msg) + return resource.bytes # Public API follows diff --git a/pipenv/patched/notpip/_vendor/distlib/t32.exe b/pipenv/patched/notpip/_vendor/distlib/t32.exe index a09d9268..8932a18e 100644 Binary files a/pipenv/patched/notpip/_vendor/distlib/t32.exe and b/pipenv/patched/notpip/_vendor/distlib/t32.exe differ diff --git a/pipenv/patched/notpip/_vendor/distlib/t64.exe b/pipenv/patched/notpip/_vendor/distlib/t64.exe index 9da9b40d..325b8057 100644 Binary files a/pipenv/patched/notpip/_vendor/distlib/t64.exe and b/pipenv/patched/notpip/_vendor/distlib/t64.exe differ diff --git a/pipenv/patched/notpip/_vendor/distlib/util.py b/pipenv/patched/notpip/_vendor/distlib/util.py index 9d4bfd3b..b9e2c695 100644 --- a/pipenv/patched/notpip/_vendor/distlib/util.py +++ b/pipenv/patched/notpip/_vendor/distlib/util.py @@ -1,5 +1,5 @@ # -# Copyright (C) 2012-2017 The Python Software Foundation. +# Copyright (C) 2012-2021 The Python Software Foundation. # See LICENSE.txt and CONTRIBUTORS.txt. # import codecs @@ -309,7 +309,9 @@ def get_executable(): # else: # result = sys.executable # return result - result = os.path.normcase(sys.executable) + # Avoid normcasing: see issue #143 + # result = os.path.normcase(sys.executable) + result = sys.executable if not isinstance(result, text_type): result = fsdecode(result) return result @@ -703,7 +705,7 @@ class ExportEntry(object): ENTRY_RE = re.compile(r'''(?P<name>(\w|[-.+])+) \s*=\s*(?P<callable>(\w+)([:\.]\w+)*) - \s*(\[\s*(?P<flags>\w+(=\w+)?(,\s*\w+(=\w+)?)*)\s*\])? + \s*(\[\s*(?P<flags>[\w-]+(=\w+)?(,\s*\w+(=\w+)?)*)\s*\])? ''', re.VERBOSE) def get_export_entry(specification): @@ -804,11 +806,15 @@ def ensure_slash(s): def parse_credentials(netloc): username = password = None if '@' in netloc: - prefix, netloc = netloc.split('@', 1) + prefix, netloc = netloc.rsplit('@', 1) if ':' not in prefix: username = prefix else: username, password = prefix.split(':', 1) + if username: + username = unquote(username) + if password: + password = unquote(password) return username, password, netloc @@ -1434,7 +1440,8 @@ if ssl: ca_certs=self.ca_certs) else: # pragma: no cover context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) - context.options |= ssl.OP_NO_SSLv2 + if hasattr(ssl, 'OP_NO_SSLv2'): + context.options |= ssl.OP_NO_SSLv2 if self.cert_file: context.load_cert_chain(self.cert_file, self.key_file) kwargs = {} @@ -1565,7 +1572,8 @@ class ServerProxy(xmlrpclib.ServerProxy): # The above classes only come into play if a timeout # is specified if timeout is not None: - scheme, _ = splittype(uri) + # scheme = splittype(uri) # deprecated as of Python 3.8 + scheme = urlparse(uri)[0] use_datetime = kwargs.get('use_datetime', 0) if scheme == 'https': tcls = SafeTransport @@ -1754,3 +1762,204 @@ def normalize_name(name): """Normalize a python package name a la PEP 503""" # https://www.python.org/dev/peps/pep-0503/#normalized-names return re.sub('[-_.]+', '-', name).lower() + +# def _get_pypirc_command(): + # """ + # Get the distutils command for interacting with PyPI configurations. + # :return: the command. + # """ + # from distutils.core import Distribution + # from distutils.config import PyPIRCCommand + # d = Distribution() + # return PyPIRCCommand(d) + +class PyPIRCFile(object): + + DEFAULT_REPOSITORY = 'https://upload.pypi.org/legacy/' + DEFAULT_REALM = 'pypi' + + def __init__(self, fn=None, url=None): + if fn is None: + fn = os.path.join(os.path.expanduser('~'), '.pypirc') + self.filename = fn + self.url = url + + def read(self): + result = {} + + if os.path.exists(self.filename): + repository = self.url or self.DEFAULT_REPOSITORY + + config = configparser.RawConfigParser() + config.read(self.filename) + sections = config.sections() + if 'distutils' in sections: + # let's get the list of servers + index_servers = config.get('distutils', 'index-servers') + _servers = [server.strip() for server in + index_servers.split('\n') + if server.strip() != ''] + if _servers == []: + # nothing set, let's try to get the default pypi + if 'pypi' in sections: + _servers = ['pypi'] + else: + for server in _servers: + result = {'server': server} + result['username'] = config.get(server, 'username') + + # optional params + for key, default in (('repository', self.DEFAULT_REPOSITORY), + ('realm', self.DEFAULT_REALM), + ('password', None)): + if config.has_option(server, key): + result[key] = config.get(server, key) + else: + result[key] = default + + # work around people having "repository" for the "pypi" + # section of their config set to the HTTP (rather than + # HTTPS) URL + if (server == 'pypi' and + repository in (self.DEFAULT_REPOSITORY, 'pypi')): + result['repository'] = self.DEFAULT_REPOSITORY + elif (result['server'] != repository and + result['repository'] != repository): + result = {} + elif 'server-login' in sections: + # old format + server = 'server-login' + if config.has_option(server, 'repository'): + repository = config.get(server, 'repository') + else: + repository = self.DEFAULT_REPOSITORY + result = { + 'username': config.get(server, 'username'), + 'password': config.get(server, 'password'), + 'repository': repository, + 'server': server, + 'realm': self.DEFAULT_REALM + } + return result + + def update(self, username, password): + # import pdb; pdb.set_trace() + config = configparser.RawConfigParser() + fn = self.filename + config.read(fn) + if not config.has_section('pypi'): + config.add_section('pypi') + config.set('pypi', 'username', username) + config.set('pypi', 'password', password) + with open(fn, 'w') as f: + config.write(f) + +def _load_pypirc(index): + """ + Read the PyPI access configuration as supported by distutils. + """ + return PyPIRCFile(url=index.url).read() + +def _store_pypirc(index): + PyPIRCFile().update(index.username, index.password) + +# +# get_platform()/get_host_platform() copied from Python 3.10.a0 source, with some minor +# tweaks +# + +def get_host_platform(): + """Return a string that identifies the current platform. This is used mainly to + distinguish platform-specific build directories and platform-specific built + distributions. Typically includes the OS name and version and the + architecture (as supplied by 'os.uname()'), although the exact information + included depends on the OS; eg. on Linux, the kernel version isn't + particularly important. + + Examples of returned values: + linux-i586 + linux-alpha (?) + solaris-2.6-sun4u + + Windows will return one of: + win-amd64 (64bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc) + win32 (all others - specifically, sys.platform is returned) + + For other non-POSIX platforms, currently just returns 'sys.platform'. + + """ + if os.name == 'nt': + if 'amd64' in sys.version.lower(): + return 'win-amd64' + if '(arm)' in sys.version.lower(): + return 'win-arm32' + if '(arm64)' in sys.version.lower(): + return 'win-arm64' + return sys.platform + + # Set for cross builds explicitly + if "_PYTHON_HOST_PLATFORM" in os.environ: + return os.environ["_PYTHON_HOST_PLATFORM"] + + if os.name != 'posix' or not hasattr(os, 'uname'): + # XXX what about the architecture? NT is Intel or Alpha, + # Mac OS is M68k or PPC, etc. + return sys.platform + + # Try to distinguish various flavours of Unix + + (osname, host, release, version, machine) = os.uname() + + # Convert the OS name to lowercase, remove '/' characters, and translate + # spaces (for "Power Macintosh") + osname = osname.lower().replace('/', '') + machine = machine.replace(' ', '_').replace('/', '-') + + if osname[:5] == 'linux': + # At least on Linux/Intel, 'machine' is the processor -- + # i386, etc. + # XXX what about Alpha, SPARC, etc? + return "%s-%s" % (osname, machine) + + elif osname[:5] == 'sunos': + if release[0] >= '5': # SunOS 5 == Solaris 2 + osname = 'solaris' + release = '%d.%s' % (int(release[0]) - 3, release[2:]) + # We can't use 'platform.architecture()[0]' because a + # bootstrap problem. We use a dict to get an error + # if some suspicious happens. + bitness = {2147483647:'32bit', 9223372036854775807:'64bit'} + machine += '.%s' % bitness[sys.maxsize] + # fall through to standard osname-release-machine representation + elif osname[:3] == 'aix': + from _aix_support import aix_platform + return aix_platform() + elif osname[:6] == 'cygwin': + osname = 'cygwin' + rel_re = re.compile (r'[\d.]+', re.ASCII) + m = rel_re.match(release) + if m: + release = m.group() + elif osname[:6] == 'darwin': + import _osx_support, distutils.sysconfig + osname, release, machine = _osx_support.get_platform_osx( + distutils.sysconfig.get_config_vars(), + osname, release, machine) + + return '%s-%s-%s' % (osname, release, machine) + + +_TARGET_TO_PLAT = { + 'x86' : 'win32', + 'x64' : 'win-amd64', + 'arm' : 'win-arm32', +} + + +def get_platform(): + if os.name != 'nt': + return get_host_platform() + cross_compilation_target = os.environ.get('VSCMD_ARG_TGT_ARCH') + if cross_compilation_target not in _TARGET_TO_PLAT: + return get_host_platform() + return _TARGET_TO_PLAT[cross_compilation_target] diff --git a/pipenv/patched/notpip/_vendor/distlib/version.py b/pipenv/patched/notpip/_vendor/distlib/version.py index 3eebe18e..86c069a7 100644 --- a/pipenv/patched/notpip/_vendor/distlib/version.py +++ b/pipenv/patched/notpip/_vendor/distlib/version.py @@ -710,6 +710,9 @@ class VersionScheme(object): """ Used for processing some metadata fields """ + # See issue #140. Be tolerant of a single trailing comma. + if s.endswith(','): + s = s[:-1] return self.is_valid_matcher('dummy_name (%s)' % s) def suggest(self, s): diff --git a/pipenv/patched/notpip/_vendor/distlib/w32.exe b/pipenv/patched/notpip/_vendor/distlib/w32.exe index 732215a9..e6439e9e 100644 Binary files a/pipenv/patched/notpip/_vendor/distlib/w32.exe and b/pipenv/patched/notpip/_vendor/distlib/w32.exe differ diff --git a/pipenv/patched/notpip/_vendor/distlib/w64.exe b/pipenv/patched/notpip/_vendor/distlib/w64.exe index c41bd0a0..46139dbf 100644 Binary files a/pipenv/patched/notpip/_vendor/distlib/w64.exe and b/pipenv/patched/notpip/_vendor/distlib/w64.exe differ diff --git a/pipenv/patched/notpip/_vendor/distlib/wheel.py b/pipenv/patched/notpip/_vendor/distlib/wheel.py index b04bfaef..5262c832 100644 --- a/pipenv/patched/notpip/_vendor/distlib/wheel.py +++ b/pipenv/patched/notpip/_vendor/distlib/wheel.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2013-2017 Vinay Sajip. +# Copyright (C) 2013-2020 Vinay Sajip. # Licensed to the Python Software Foundation under a contributor agreement. # See LICENSE.txt and CONTRIBUTORS.txt. # @@ -9,7 +9,6 @@ from __future__ import unicode_literals import base64 import codecs import datetime -import distutils.util from email import message_from_file import hashlib import imp @@ -26,9 +25,11 @@ import zipfile from . import __version__, DistlibException from .compat import sysconfig, ZipFile, fsdecode, text_type, filter from .database import InstalledDistribution -from .metadata import Metadata, METADATA_FILENAME, WHEEL_METADATA_FILENAME +from .metadata import (Metadata, METADATA_FILENAME, WHEEL_METADATA_FILENAME, + LEGACY_METADATA_FILENAME) from .util import (FileOperator, convert_path, CSVReader, CSVWriter, Cache, - cached_property, get_cache_base, read_exports, tempdir) + cached_property, get_cache_base, read_exports, tempdir, + get_platform) from .version import NormalizedVersion, UnsupportedVersionError logger = logging.getLogger(__name__) @@ -46,15 +47,18 @@ else: VER_SUFFIX = sysconfig.get_config_var('py_version_nodot') if not VER_SUFFIX: # pragma: no cover - VER_SUFFIX = '%s%s' % sys.version_info[:2] + if sys.version_info[1] >= 10: + VER_SUFFIX = '%s_%s' % sys.version_info[:2] # PEP 641 (draft) + else: + VER_SUFFIX = '%s%s' % sys.version_info[:2] PYVER = 'py' + VER_SUFFIX IMPVER = IMP_PREFIX + VER_SUFFIX -ARCH = distutils.util.get_platform().replace('-', '_').replace('.', '_') +ARCH = get_platform().replace('-', '_').replace('.', '_') ABI = sysconfig.get_config_var('SOABI') if ABI and ABI.startswith('cpython-'): - ABI = ABI.replace('cpython-', 'cp') + ABI = ABI.replace('cpython-', 'cp').split('-')[0] else: def _derive_abi(): parts = ['cp', VER_SUFFIX] @@ -221,10 +225,12 @@ class Wheel(object): wheel_metadata = self.get_wheel_metadata(zf) wv = wheel_metadata['Wheel-Version'].split('.', 1) file_version = tuple([int(i) for i in wv]) - if file_version < (1, 1): - fns = [WHEEL_METADATA_FILENAME, METADATA_FILENAME, 'METADATA'] - else: - fns = [WHEEL_METADATA_FILENAME, METADATA_FILENAME] + # if file_version < (1, 1): + # fns = [WHEEL_METADATA_FILENAME, METADATA_FILENAME, + # LEGACY_METADATA_FILENAME] + # else: + # fns = [WHEEL_METADATA_FILENAME, METADATA_FILENAME] + fns = [WHEEL_METADATA_FILENAME, LEGACY_METADATA_FILENAME] result = None for fn in fns: try: @@ -299,10 +305,9 @@ class Wheel(object): return hash_kind, result def write_record(self, records, record_path, base): - records = list(records) # make a copy for sorting + records = list(records) # make a copy, as mutated p = to_posix(os.path.relpath(record_path, base)) records.append((p, '', '')) - records.sort() with CSVWriter(record_path) as writer: for row in records: writer.writerow(row) @@ -425,6 +430,18 @@ class Wheel(object): ap = to_posix(os.path.join(info_dir, 'WHEEL')) archive_paths.append((ap, p)) + # sort the entries by archive path. Not needed by any spec, but it + # keeps the archive listing and RECORD tidier than they would otherwise + # be. Use the number of path segments to keep directory entries together, + # and keep the dist-info stuff at the end. + def sorter(t): + ap = t[0] + n = ap.count('/') + if '.dist-info' in ap: + n += 10000 + return (n, ap) + archive_paths = sorted(archive_paths, key=sorter) + # Now, at last, RECORD. # Paths in here are archive paths - nothing else makes sense. self.write_records((distinfo, info_dir), libdir, archive_paths) @@ -433,6 +450,22 @@ class Wheel(object): self.build_zip(pathname, archive_paths) return pathname + def skip_entry(self, arcname): + """ + Determine whether an archive entry should be skipped when verifying + or installing. + """ + # The signature file won't be in RECORD, + # and we don't currently don't do anything with it + # We also skip directories, as they won't be in RECORD + # either. See: + # + # https://github.com/pypa/wheel/issues/294 + # https://github.com/pypa/wheel/issues/287 + # https://github.com/pypa/wheel/pull/289 + # + return arcname.endswith(('/', '/RECORD.jws')) + def install(self, paths, maker, **kwargs): """ Install a wheel to the specified paths. If kwarg ``warner`` is @@ -460,7 +493,7 @@ class Wheel(object): data_dir = '%s.data' % name_ver info_dir = '%s.dist-info' % name_ver - metadata_name = posixpath.join(info_dir, METADATA_FILENAME) + metadata_name = posixpath.join(info_dir, LEGACY_METADATA_FILENAME) wheel_metadata_name = posixpath.join(info_dir, 'WHEEL') record_name = posixpath.join(info_dir, 'RECORD') @@ -514,9 +547,7 @@ class Wheel(object): u_arcname = arcname else: u_arcname = arcname.decode('utf-8') - # The signature file won't be in RECORD, - # and we don't currently don't do anything with it - if u_arcname.endswith('/RECORD.jws'): + if self.skip_entry(u_arcname): continue row = records[u_arcname] if row[2] and str(zinfo.file_size) != row[2]: @@ -548,6 +579,13 @@ class Wheel(object): if not is_script: with zf.open(arcname) as bf: fileop.copy_stream(bf, outfile) + # Issue #147: permission bits aren't preserved. Using + # zf.extract(zinfo, libdir) should have worked, but didn't, + # see https://www.thetopsites.net/article/53834422.shtml + # So ... manually preserve permission bits as given in zinfo + if os.name == 'posix': + # just set the normal permission bits + os.chmod(outfile, (zinfo.external_attr >> 16) & 0x1FF) outfiles.append(outfile) # Double check the digest of the written file if not dry_run and row[1]: @@ -605,7 +643,7 @@ class Wheel(object): for v in epdata[k].values(): s = '%s:%s' % (v.prefix, v.suffix) if v.flags: - s += ' %s' % v.flags + s += ' [%s]' % ','.join(v.flags) d[v.name] = s except Exception: logger.warning('Unable to read legacy script ' @@ -670,7 +708,7 @@ class Wheel(object): if cache is None: # Use native string to avoid issues on 2.x: see Python #20140. base = os.path.join(get_cache_base(), str('dylib-cache'), - sys.version[:3]) + '%s.%s' % sys.version_info[:2]) cache = Cache(base) return cache @@ -759,7 +797,7 @@ class Wheel(object): data_dir = '%s.data' % name_ver info_dir = '%s.dist-info' % name_ver - metadata_name = posixpath.join(info_dir, METADATA_FILENAME) + metadata_name = posixpath.join(info_dir, LEGACY_METADATA_FILENAME) wheel_metadata_name = posixpath.join(info_dir, 'WHEEL') record_name = posixpath.join(info_dir, 'RECORD') @@ -786,13 +824,15 @@ class Wheel(object): u_arcname = arcname else: u_arcname = arcname.decode('utf-8') - if '..' in u_arcname: + # See issue #115: some wheels have .. in their entries, but + # in the filename ... e.g. __main__..py ! So the check is + # updated to look for .. in the directory portions + p = u_arcname.split('/') + if '..' in p: raise DistlibException('invalid entry in ' 'wheel: %r' % u_arcname) - # The signature file won't be in RECORD, - # and we don't currently don't do anything with it - if u_arcname.endswith('/RECORD.jws'): + if self.skip_entry(u_arcname): continue row = records[u_arcname] if row[2] and str(zinfo.file_size) != row[2]: @@ -826,7 +866,7 @@ class Wheel(object): def get_version(path_map, info_dir): version = path = None - key = '%s/%s' % (info_dir, METADATA_FILENAME) + key = '%s/%s' % (info_dir, LEGACY_METADATA_FILENAME) if key not in path_map: key = '%s/PKG-INFO' % info_dir if key in path_map: @@ -852,7 +892,7 @@ class Wheel(object): if updated: md = Metadata(path=path) md.version = updated - legacy = not path.endswith(METADATA_FILENAME) + legacy = path.endswith(LEGACY_METADATA_FILENAME) md.write(path=path, legacy=legacy) logger.debug('Version updated from %r to %r', version, updated) @@ -908,6 +948,16 @@ class Wheel(object): shutil.copyfile(newpath, pathname) return modified +def _get_glibc_version(): + import platform + ver = platform.libc_ver() + result = [] + if ver[0] == 'glibc': + for s in ver[1].split('.'): + result.append(int(s) if s.isdigit() else 0) + result = tuple(result) + return result + def compatible_tags(): """ Return (pyver, abi, arch) tuples compatible with this Python. @@ -955,6 +1005,23 @@ def compatible_tags(): for abi in abis: for arch in arches: result.append((''.join((IMP_PREFIX, versions[0])), abi, arch)) + # manylinux + if abi != 'none' and sys.platform.startswith('linux'): + arch = arch.replace('linux_', '') + parts = _get_glibc_version() + if len(parts) == 2: + if parts >= (2, 5): + result.append((''.join((IMP_PREFIX, versions[0])), abi, + 'manylinux1_%s' % arch)) + if parts >= (2, 12): + result.append((''.join((IMP_PREFIX, versions[0])), abi, + 'manylinux2010_%s' % arch)) + if parts >= (2, 17): + result.append((''.join((IMP_PREFIX, versions[0])), abi, + 'manylinux2014_%s' % arch)) + result.append((''.join((IMP_PREFIX, versions[0])), abi, + 'manylinux_%s_%s_%s' % (parts[0], parts[1], + arch))) # where no ABI / arch dependency, but IMP_PREFIX dependency for i, version in enumerate(versions): @@ -967,6 +1034,7 @@ def compatible_tags(): result.append((''.join(('py', version)), 'none', 'any')) if i == 0: result.append((''.join(('py', version[0])), 'none', 'any')) + return set(result) diff --git a/pipenv/patched/notpip/_vendor/distro.py b/pipenv/patched/notpip/_vendor/distro.py index aa4defc3..0611b62a 100644 --- a/pipenv/patched/notpip/_vendor/distro.py +++ b/pipenv/patched/notpip/_vendor/distro.py @@ -17,12 +17,12 @@ The ``distro`` package (``distro`` stands for Linux Distribution) provides information about the Linux distribution it runs on, such as a reliable machine-readable distro ID, or version information. -It is a renewed alternative implementation for Python's original +It is the recommended replacement for Python's original :py:func:`platform.linux_distribution` function, but it provides much more functionality. An alternative implementation became necessary because Python -3.5 deprecated this function, and Python 3.7 is expected to remove it -altogether. Its predecessor function :py:func:`platform.dist` was already -deprecated since Python 2.6 and is also expected to be removed in Python 3.7. +3.5 deprecated this function, and Python 3.8 will remove it altogether. +Its predecessor function :py:func:`platform.dist` was already +deprecated since Python 2.6 and will also be removed in Python 3.8. Still, there are many cases in which access to OS distribution information is needed. See `Python issue 1322 <https://bugs.python.org/issue1322>`_ for more information. @@ -48,7 +48,9 @@ _OS_RELEASE_BASENAME = 'os-release' #: with blanks translated to underscores. #: #: * Value: Normalized value. -NORMALIZED_OS_ID = {} +NORMALIZED_OS_ID = { + 'ol': 'oracle', # Oracle Linux +} #: Translation table for normalizing the "Distributor ID" attribute returned by #: the lsb_release command, for use by the :func:`distro.id` method. @@ -58,9 +60,11 @@ NORMALIZED_OS_ID = {} #: #: * Value: Normalized value. NORMALIZED_LSB_ID = { - 'enterpriseenterprise': 'oracle', # Oracle Enterprise Linux + 'enterpriseenterpriseas': 'oracle', # Oracle Enterprise Linux 4 + 'enterpriseenterpriseserver': 'oracle', # Oracle Linux 5 'redhatenterpriseworkstation': 'rhel', # RHEL 6, 7 Workstation 'redhatenterpriseserver': 'rhel', # RHEL 6, 7 Server + 'redhatenterprisecomputenode': 'rhel', # RHEL 6 ComputeNode } #: Translation table for normalizing the distro ID derived from the file name @@ -88,7 +92,8 @@ _DISTRO_RELEASE_IGNORE_BASENAMES = ( 'lsb-release', 'oem-release', _OS_RELEASE_BASENAME, - 'system-release' + 'system-release', + 'plesk-release', ) @@ -161,6 +166,7 @@ def id(): "openbsd" OpenBSD "netbsd" NetBSD "freebsd" FreeBSD + "midnightbsd" MidnightBSD ============== ========================================= If you have a need to get distros for reliable IDs added into this set, @@ -607,7 +613,7 @@ class LinuxDistribution(object): distro release file can be found, the data source for the distro release file will be empty. - * ``include_name`` (bool): Controls whether uname command output is + * ``include_uname`` (bool): Controls whether uname command output is included as a data source. If the uname command is not available in the program execution path the data source for the uname command will be empty. @@ -755,7 +761,7 @@ class LinuxDistribution(object): version = v break if pretty and version and self.codename(): - version = u'{0} ({1})'.format(version, self.codename()) + version = '{0} ({1})'.format(version, self.codename()) return version def version_parts(self, best=False): @@ -812,10 +818,14 @@ class LinuxDistribution(object): For details, see :func:`distro.codename`. """ - return self.os_release_attr('codename') \ - or self.lsb_release_attr('codename') \ - or self.distro_release_attr('codename') \ - or '' + try: + # Handle os_release specially since distros might purposefully set + # this to empty string to have no codename + return self._os_release_info['codename'] + except KeyError: + return self.lsb_release_attr('codename') \ + or self.distro_release_attr('codename') \ + or '' def info(self, pretty=False, best=False): """ @@ -872,6 +882,7 @@ class LinuxDistribution(object): For details, see :func:`distro.uname_info`. """ + return self._uname_info def os_release_attr(self, attribute): """ @@ -960,26 +971,31 @@ class LinuxDistribution(object): # * commands or their arguments (not allowed in os-release) if '=' in token: k, v = token.split('=', 1) - if isinstance(v, bytes): - v = v.decode('utf-8') props[k.lower()] = v - if k == 'VERSION': - # this handles cases in which the codename is in - # the `(CODENAME)` (rhel, centos, fedora) format - # or in the `, CODENAME` format (Ubuntu). - codename = re.search(r'(\(\D+\))|,(\s+)?\D+', v) - if codename: - codename = codename.group() - codename = codename.strip('()') - codename = codename.strip(',') - codename = codename.strip() - # codename appears within paranthese. - props['codename'] = codename - else: - props['codename'] = '' else: # Ignore any tokens that are not variable assignments pass + + if 'version_codename' in props: + # os-release added a version_codename field. Use that in + # preference to anything else Note that some distros purposefully + # do not have code names. They should be setting + # version_codename="" + props['codename'] = props['version_codename'] + elif 'ubuntu_codename' in props: + # Same as above but a non-standard field name used on older Ubuntus + props['codename'] = props['ubuntu_codename'] + elif 'version' in props: + # If there is no version_codename, parse it from the version + codename = re.search(r'(\(\D+\))|,(\s+)?\D+', props['version']) + if codename: + codename = codename.group() + codename = codename.strip('()') + codename = codename.strip(',') + codename = codename.strip() + # codename appears within paranthese. + props['codename'] = codename + return props @cached_property @@ -998,7 +1014,7 @@ class LinuxDistribution(object): stdout = subprocess.check_output(cmd, stderr=devnull) except OSError: # Command not found return {} - content = stdout.decode(sys.getfilesystemencoding()).splitlines() + content = self._to_str(stdout).splitlines() return self._parse_lsb_release_content(content) @staticmethod @@ -1033,7 +1049,7 @@ class LinuxDistribution(object): stdout = subprocess.check_output(cmd, stderr=devnull) except OSError: return {} - content = stdout.decode(sys.getfilesystemencoding()).splitlines() + content = self._to_str(stdout).splitlines() return self._parse_uname_content(content) @staticmethod @@ -1053,6 +1069,20 @@ class LinuxDistribution(object): props['release'] = version return props + @staticmethod + def _to_str(text): + encoding = sys.getfilesystemencoding() + encoding = 'utf-8' if encoding == 'ascii' else encoding + + if sys.version_info[0] >= 3: + if isinstance(text, bytes): + return text.decode(encoding) + else: + if isinstance(text, unicode): # noqa + return text.encode(encoding) + + return text + @cached_property def _distro_release_info(self): """ @@ -1072,7 +1102,10 @@ class LinuxDistribution(object): # file), because we want to use what was specified as best as # possible. match = _DISTRO_RELEASE_BASENAME_PATTERN.match(basename) - if match: + if 'name' in distro_info \ + and 'cloudlinux' in distro_info['name'].lower(): + distro_info['id'] = 'cloudlinux' + elif match: distro_info['id'] = match.group(1) return distro_info else: @@ -1113,6 +1146,8 @@ class LinuxDistribution(object): # The name is always present if the pattern matches self.distro_release_file = filepath distro_info['id'] = match.group(1) + if 'cloudlinux' in distro_info['name'].lower(): + distro_info['id'] = 'cloudlinux' return distro_info return {} @@ -1150,8 +1185,6 @@ class LinuxDistribution(object): Returns: A dictionary containing all information items. """ - if isinstance(line, bytes): - line = line.decode('utf-8') matches = _DISTRO_RELEASE_CONTENT_REVERSED_PATTERN.match( line.strip()[::-1]) distro_info = {} diff --git a/pipenv/patched/notpip/_vendor/html5lib/__init__.py b/pipenv/patched/notpip/_vendor/html5lib/__init__.py index 17e2e2e0..3cf6cecc 100644 --- a/pipenv/patched/notpip/_vendor/html5lib/__init__.py +++ b/pipenv/patched/notpip/_vendor/html5lib/__init__.py @@ -32,4 +32,4 @@ __all__ = ["HTMLParser", "parse", "parseFragment", "getTreeBuilder", # this has to be at the top level, see how setup.py parses this #: Distribution version number. -__version__ = "1.0.1" +__version__ = "1.1" diff --git a/pipenv/patched/notpip/_vendor/html5lib/_ihatexml.py b/pipenv/patched/notpip/_vendor/html5lib/_ihatexml.py index 4c77717b..3ff803c1 100644 --- a/pipenv/patched/notpip/_vendor/html5lib/_ihatexml.py +++ b/pipenv/patched/notpip/_vendor/html5lib/_ihatexml.py @@ -136,6 +136,7 @@ def normaliseCharList(charList): i += j return rv + # We don't really support characters above the BMP :( max_unicode = int("FFFF", 16) @@ -254,7 +255,7 @@ class InfosetFilter(object): nameRest = name[1:] m = nonXmlNameFirstBMPRegexp.match(nameFirst) if m: - warnings.warn("Coercing non-XML name", DataLossWarning) + warnings.warn("Coercing non-XML name: %s" % name, DataLossWarning) nameFirstOutput = self.getReplacementCharacter(nameFirst) else: nameFirstOutput = nameFirst @@ -262,7 +263,7 @@ class InfosetFilter(object): nameRestOutput = nameRest replaceChars = set(nonXmlNameBMPRegexp.findall(nameRest)) for char in replaceChars: - warnings.warn("Coercing non-XML name", DataLossWarning) + warnings.warn("Coercing non-XML name: %s" % name, DataLossWarning) replacement = self.getReplacementCharacter(char) nameRestOutput = nameRestOutput.replace(char, replacement) return nameFirstOutput + nameRestOutput diff --git a/pipenv/patched/notpip/_vendor/html5lib/_inputstream.py b/pipenv/patched/notpip/_vendor/html5lib/_inputstream.py index 38149dbb..43d272ef 100644 --- a/pipenv/patched/notpip/_vendor/html5lib/_inputstream.py +++ b/pipenv/patched/notpip/_vendor/html5lib/_inputstream.py @@ -1,10 +1,11 @@ from __future__ import absolute_import, division, unicode_literals -from pipenv.patched.notpip._vendor.six import text_type, binary_type +from pipenv.patched.notpip._vendor.six import text_type from pipenv.patched.notpip._vendor.six.moves import http_client, urllib import codecs import re +from io import BytesIO, StringIO from pipenv.patched.notpip._vendor import webencodings @@ -12,13 +13,6 @@ from .constants import EOF, spaceCharacters, asciiLetters, asciiUppercase from .constants import _ReparseException from . import _utils -from io import StringIO - -try: - from io import BytesIO -except ImportError: - BytesIO = StringIO - # Non-unicode versions of constants for use in the pre-parser spaceCharactersBytes = frozenset([item.encode("ascii") for item in spaceCharacters]) asciiLettersBytes = frozenset([item.encode("ascii") for item in asciiLetters]) @@ -40,13 +34,13 @@ if _utils.supports_lone_surrogates: else: invalid_unicode_re = re.compile(invalid_unicode_no_surrogate) -non_bmp_invalid_codepoints = set([0x1FFFE, 0x1FFFF, 0x2FFFE, 0x2FFFF, 0x3FFFE, - 0x3FFFF, 0x4FFFE, 0x4FFFF, 0x5FFFE, 0x5FFFF, - 0x6FFFE, 0x6FFFF, 0x7FFFE, 0x7FFFF, 0x8FFFE, - 0x8FFFF, 0x9FFFE, 0x9FFFF, 0xAFFFE, 0xAFFFF, - 0xBFFFE, 0xBFFFF, 0xCFFFE, 0xCFFFF, 0xDFFFE, - 0xDFFFF, 0xEFFFE, 0xEFFFF, 0xFFFFE, 0xFFFFF, - 0x10FFFE, 0x10FFFF]) +non_bmp_invalid_codepoints = {0x1FFFE, 0x1FFFF, 0x2FFFE, 0x2FFFF, 0x3FFFE, + 0x3FFFF, 0x4FFFE, 0x4FFFF, 0x5FFFE, 0x5FFFF, + 0x6FFFE, 0x6FFFF, 0x7FFFE, 0x7FFFF, 0x8FFFE, + 0x8FFFF, 0x9FFFE, 0x9FFFF, 0xAFFFE, 0xAFFFF, + 0xBFFFE, 0xBFFFF, 0xCFFFE, 0xCFFFF, 0xDFFFE, + 0xDFFFF, 0xEFFFE, 0xEFFFF, 0xFFFFE, 0xFFFFF, + 0x10FFFE, 0x10FFFF} ascii_punctuation_re = re.compile("[\u0009-\u000D\u0020-\u002F\u003A-\u0040\u005C\u005B-\u0060\u007B-\u007E]") @@ -367,7 +361,7 @@ class HTMLUnicodeInputStream(object): def unget(self, char): # Only one character is allowed to be ungotten at once - it must # be consumed again before any further call to unget - if char is not None: + if char is not EOF: if self.chunkOffset == 0: # unget is called quite rarely, so it's a good idea to do # more work here if it saves a bit of work in the frequently @@ -449,7 +443,7 @@ class HTMLBinaryInputStream(HTMLUnicodeInputStream): try: stream.seek(stream.tell()) - except: # pylint:disable=bare-except + except Exception: stream = BufferedStream(stream) return stream @@ -461,7 +455,7 @@ class HTMLBinaryInputStream(HTMLUnicodeInputStream): if charEncoding[0] is not None: return charEncoding - # If we've been overriden, we've been overriden + # If we've been overridden, we've been overridden charEncoding = lookupEncoding(self.override_encoding), "certain" if charEncoding[0] is not None: return charEncoding @@ -664,9 +658,7 @@ class EncodingBytes(bytes): """Look for a sequence of bytes at the start of a string. If the bytes are found return True and advance the position to the byte after the match. Otherwise return False and leave the position alone""" - p = self.position - data = self[p:p + len(bytes)] - rv = data.startswith(bytes) + rv = self.startswith(bytes, self.position) if rv: self.position += len(bytes) return rv @@ -674,15 +666,11 @@ class EncodingBytes(bytes): def jumpTo(self, bytes): """Look for the next sequence of bytes matching a given sequence. If a match is found advance the position to the last byte of the match""" - newPosition = self[self.position:].find(bytes) - if newPosition > -1: - # XXX: This is ugly, but I can't see a nicer way to fix this. - if self._position == -1: - self._position = 0 - self._position += (newPosition + len(bytes) - 1) - return True - else: + try: + self._position = self.index(bytes, self.position) + len(bytes) - 1 + except ValueError: raise StopIteration + return True class EncodingParser(object): @@ -694,6 +682,9 @@ class EncodingParser(object): self.encoding = None def getEncoding(self): + if b"<meta" not in self.data: + return None + methodDispatch = ( (b"<!--", self.handleComment), (b"<meta", self.handleMeta), @@ -703,6 +694,10 @@ class EncodingParser(object): (b"<", self.handlePossibleStartTag)) for _ in self.data: keepParsing = True + try: + self.data.jumpTo(b"<") + except StopIteration: + break for key, method in methodDispatch: if self.data.matchBytes(key): try: @@ -908,7 +903,7 @@ class ContentAttrParser(object): def lookupEncoding(encoding): """Return the python codec name corresponding to an encoding or None if the string doesn't correspond to a valid encoding.""" - if isinstance(encoding, binary_type): + if isinstance(encoding, bytes): try: encoding = encoding.decode("ascii") except UnicodeDecodeError: diff --git a/pipenv/patched/notpip/_vendor/html5lib/_tokenizer.py b/pipenv/patched/notpip/_vendor/html5lib/_tokenizer.py index 150ac96c..8740f4c2 100644 --- a/pipenv/patched/notpip/_vendor/html5lib/_tokenizer.py +++ b/pipenv/patched/notpip/_vendor/html5lib/_tokenizer.py @@ -2,7 +2,8 @@ from __future__ import absolute_import, division, unicode_literals from pipenv.patched.notpip._vendor.six import unichr as chr -from collections import deque +from collections import deque, OrderedDict +from sys import version_info from .constants import spaceCharacters from .constants import entities @@ -17,6 +18,11 @@ from ._trie import Trie entitiesTrie = Trie(entities) +if version_info >= (3, 7): + attributeMap = dict +else: + attributeMap = OrderedDict + class HTMLTokenizer(object): """ This class takes care of tokenizing HTML. @@ -228,6 +234,14 @@ class HTMLTokenizer(object): # Add token to the queue to be yielded if (token["type"] in tagTokenTypes): token["name"] = token["name"].translate(asciiUpper2Lower) + if token["type"] == tokenTypes["StartTag"]: + raw = token["data"] + data = attributeMap(raw) + if len(raw) > len(data): + # we had some duplicated attribute, fix so first wins + data.update(raw[::-1]) + token["data"] = data + if token["type"] == tokenTypes["EndTag"]: if token["data"]: self.tokenQueue.append({"type": tokenTypes["ParseError"], diff --git a/pipenv/patched/notpip/_vendor/html5lib/_trie/__init__.py b/pipenv/patched/notpip/_vendor/html5lib/_trie/__init__.py index a5ba4bf1..07bad5d3 100644 --- a/pipenv/patched/notpip/_vendor/html5lib/_trie/__init__.py +++ b/pipenv/patched/notpip/_vendor/html5lib/_trie/__init__.py @@ -1,14 +1,5 @@ from __future__ import absolute_import, division, unicode_literals -from .py import Trie as PyTrie +from .py import Trie -Trie = PyTrie - -# pylint:disable=wrong-import-position -try: - from .datrie import Trie as DATrie -except ImportError: - pass -else: - Trie = DATrie -# pylint:enable=wrong-import-position +__all__ = ["Trie"] diff --git a/pipenv/patched/notpip/_vendor/html5lib/_trie/_base.py b/pipenv/patched/notpip/_vendor/html5lib/_trie/_base.py index a1158bbb..6b71975f 100644 --- a/pipenv/patched/notpip/_vendor/html5lib/_trie/_base.py +++ b/pipenv/patched/notpip/_vendor/html5lib/_trie/_base.py @@ -1,6 +1,9 @@ from __future__ import absolute_import, division, unicode_literals -from collections import Mapping +try: + from collections.abc import Mapping +except ImportError: # Python 2.7 + from collections import Mapping class Trie(Mapping): diff --git a/pipenv/patched/notpip/_vendor/html5lib/_trie/datrie.py b/pipenv/patched/notpip/_vendor/html5lib/_trie/datrie.py deleted file mode 100644 index 16b48e9a..00000000 --- a/pipenv/patched/notpip/_vendor/html5lib/_trie/datrie.py +++ /dev/null @@ -1,44 +0,0 @@ -from __future__ import absolute_import, division, unicode_literals - -from datrie import Trie as DATrie -from pipenv.patched.notpip._vendor.six import text_type - -from ._base import Trie as ABCTrie - - -class Trie(ABCTrie): - def __init__(self, data): - chars = set() - for key in data.keys(): - if not isinstance(key, text_type): - raise TypeError("All keys must be strings") - for char in key: - chars.add(char) - - self._data = DATrie("".join(chars)) - for key, value in data.items(): - self._data[key] = value - - def __contains__(self, key): - return key in self._data - - def __len__(self): - return len(self._data) - - def __iter__(self): - raise NotImplementedError() - - def __getitem__(self, key): - return self._data[key] - - def keys(self, prefix=None): - return self._data.keys(prefix) - - def has_keys_with_prefix(self, prefix): - return self._data.has_keys_with_prefix(prefix) - - def longest_prefix(self, prefix): - return self._data.longest_prefix(prefix) - - def longest_prefix_item(self, prefix): - return self._data.longest_prefix_item(prefix) diff --git a/pipenv/patched/notpip/_vendor/html5lib/_utils.py b/pipenv/patched/notpip/_vendor/html5lib/_utils.py index 92c69ecd..25042f82 100644 --- a/pipenv/patched/notpip/_vendor/html5lib/_utils.py +++ b/pipenv/patched/notpip/_vendor/html5lib/_utils.py @@ -2,12 +2,20 @@ from __future__ import absolute_import, division, unicode_literals from types import ModuleType -from pipenv.patched.notpip._vendor.six import text_type - try: - import xml.etree.cElementTree as default_etree + from collections.abc import Mapping except ImportError: + from collections import Mapping + +from pipenv.patched.notpip._vendor.six import text_type, PY3 + +if PY3: import xml.etree.ElementTree as default_etree +else: + try: + import xml.etree.cElementTree as default_etree + except ImportError: + import xml.etree.ElementTree as default_etree __all__ = ["default_etree", "MethodDispatcher", "isSurrogatePair", @@ -27,7 +35,7 @@ try: # We need this with u"" because of http://bugs.jython.org/issue2039 _x = eval('u"\\uD800"') # pylint:disable=eval-used assert isinstance(_x, text_type) -except: # pylint:disable=bare-except +except Exception: supports_lone_surrogates = False else: supports_lone_surrogates = True @@ -47,9 +55,6 @@ class MethodDispatcher(dict): """ def __init__(self, items=()): - # Using _dictEntries instead of directly assigning to self is about - # twice as fast. Please do careful performance testing before changing - # anything here. _dictEntries = [] for name, value in items: if isinstance(name, (list, tuple, frozenset, set)): @@ -64,6 +69,36 @@ class MethodDispatcher(dict): def __getitem__(self, key): return dict.get(self, key, self.default) + def __get__(self, instance, owner=None): + return BoundMethodDispatcher(instance, self) + + +class BoundMethodDispatcher(Mapping): + """Wraps a MethodDispatcher, binding its return values to `instance`""" + def __init__(self, instance, dispatcher): + self.instance = instance + self.dispatcher = dispatcher + + def __getitem__(self, key): + # see https://docs.python.org/3/reference/datamodel.html#object.__get__ + # on a function, __get__ is used to bind a function to an instance as a bound method + return self.dispatcher[key].__get__(self.instance) + + def get(self, key, default): + if key in self.dispatcher: + return self[key] + else: + return default + + def __iter__(self): + return iter(self.dispatcher) + + def __len__(self): + return len(self.dispatcher) + + def __contains__(self, key): + return key in self.dispatcher + # Some utility functions to deal with weirdness around UCS2 vs UCS4 # python builds diff --git a/pipenv/patched/notpip/_vendor/html5lib/constants.py b/pipenv/patched/notpip/_vendor/html5lib/constants.py index 1ff80419..fe3e237c 100644 --- a/pipenv/patched/notpip/_vendor/html5lib/constants.py +++ b/pipenv/patched/notpip/_vendor/html5lib/constants.py @@ -519,8 +519,8 @@ adjustForeignAttributes = { "xmlns:xlink": ("xmlns", "xlink", namespaces["xmlns"]) } -unadjustForeignAttributes = dict([((ns, local), qname) for qname, (prefix, local, ns) in - adjustForeignAttributes.items()]) +unadjustForeignAttributes = {(ns, local): qname for qname, (prefix, local, ns) in + adjustForeignAttributes.items()} spaceCharacters = frozenset([ "\t", @@ -544,8 +544,7 @@ asciiLetters = frozenset(string.ascii_letters) digits = frozenset(string.digits) hexDigits = frozenset(string.hexdigits) -asciiUpper2Lower = dict([(ord(c), ord(c.lower())) - for c in string.ascii_uppercase]) +asciiUpper2Lower = {ord(c): ord(c.lower()) for c in string.ascii_uppercase} # Heading elements need to be ordered headingElements = ( @@ -2934,7 +2933,7 @@ tagTokenTypes = frozenset([tokenTypes["StartTag"], tokenTypes["EndTag"], tokenTypes["EmptyTag"]]) -prefixes = dict([(v, k) for k, v in namespaces.items()]) +prefixes = {v: k for k, v in namespaces.items()} prefixes["http://www.w3.org/1998/Math/MathML"] = "math" diff --git a/pipenv/patched/notpip/_vendor/html5lib/filters/sanitizer.py b/pipenv/patched/notpip/_vendor/html5lib/filters/sanitizer.py index b55686cf..788e73d2 100644 --- a/pipenv/patched/notpip/_vendor/html5lib/filters/sanitizer.py +++ b/pipenv/patched/notpip/_vendor/html5lib/filters/sanitizer.py @@ -1,6 +1,15 @@ +"""Deprecated from html5lib 1.1. + +See `here <https://github.com/html5lib/html5lib-python/issues/443>`_ for +information about its deprecation; `Bleach <https://github.com/mozilla/bleach>`_ +is recommended as a replacement. Please let us know in the aforementioned issue +if Bleach is unsuitable for your needs. + +""" from __future__ import absolute_import, division, unicode_literals import re +import warnings from xml.sax.saxutils import escape, unescape from pipenv.patched.notpip._vendor.six.moves import urllib_parse as urlparse @@ -11,6 +20,14 @@ from ..constants import namespaces, prefixes __all__ = ["Filter"] +_deprecation_msg = ( + "html5lib's sanitizer is deprecated; see " + + "https://github.com/html5lib/html5lib-python/issues/443 and please let " + + "us know if Bleach is unsuitable for your needs" +) + +warnings.warn(_deprecation_msg, DeprecationWarning) + allowed_elements = frozenset(( (namespaces['html'], 'a'), (namespaces['html'], 'abbr'), @@ -750,6 +767,9 @@ class Filter(base.Filter): """ super(Filter, self).__init__(source) + + warnings.warn(_deprecation_msg, DeprecationWarning) + self.allowed_elements = allowed_elements self.allowed_attributes = allowed_attributes self.allowed_css_properties = allowed_css_properties diff --git a/pipenv/patched/notpip/_vendor/html5lib/html5parser.py b/pipenv/patched/notpip/_vendor/html5lib/html5parser.py index baeec1ec..9c9a2ed5 100644 --- a/pipenv/patched/notpip/_vendor/html5lib/html5parser.py +++ b/pipenv/patched/notpip/_vendor/html5lib/html5parser.py @@ -2,7 +2,6 @@ from __future__ import absolute_import, division, unicode_literals from pipenv.patched.notpip._vendor.six import with_metaclass, viewkeys import types -from collections import OrderedDict from . import _inputstream from . import _tokenizer @@ -119,8 +118,8 @@ class HTMLParser(object): self.tree = tree(namespaceHTMLElements) self.errors = [] - self.phases = dict([(name, cls(self, self.tree)) for name, cls in - getPhases(debug).items()]) + self.phases = {name: cls(self, self.tree) for name, cls in + getPhases(debug).items()} def _parse(self, stream, innerHTML=False, container="div", scripting=False, **kwargs): @@ -202,7 +201,7 @@ class HTMLParser(object): DoctypeToken = tokenTypes["Doctype"] ParseErrorToken = tokenTypes["ParseError"] - for token in self.normalizedTokens(): + for token in self.tokenizer: prev_token = None new_token = token while new_token is not None: @@ -260,10 +259,6 @@ class HTMLParser(object): if reprocess: assert self.phase not in phases - def normalizedTokens(self): - for token in self.tokenizer: - yield self.normalizeToken(token) - def parse(self, stream, *args, **kwargs): """Parse a HTML document into a well-formed tree @@ -325,17 +320,6 @@ class HTMLParser(object): if self.strict: raise ParseError(E[errorcode] % datavars) - def normalizeToken(self, token): - # HTML5 specific normalizations to the token stream - if token["type"] == tokenTypes["StartTag"]: - raw = token["data"] - token["data"] = OrderedDict(raw) - if len(raw) > len(token["data"]): - # we had some duplicated attribute, fix so first wins - token["data"].update(raw[::-1]) - - return token - def adjustMathMLAttributes(self, token): adjust_attributes(token, adjustMathMLAttributes) @@ -413,16 +397,12 @@ class HTMLParser(object): def getPhases(debug): def log(function): """Logger that records which phase processes each token""" - type_names = dict((value, key) for key, value in - tokenTypes.items()) + type_names = {value: key for key, value in tokenTypes.items()} def wrapped(self, *args, **kwargs): if function.__name__.startswith("process") and len(args) > 0: token = args[0] - try: - info = {"type": type_names[token['type']]} - except: - raise + info = {"type": type_names[token['type']]} if token['type'] in tagTokenTypes: info["name"] = token['name'] @@ -446,10 +426,13 @@ def getPhases(debug): class Phase(with_metaclass(getMetaclass(debug, log))): """Base class for helper object that implements each phase of processing """ + __slots__ = ("parser", "tree", "__startTagCache", "__endTagCache") def __init__(self, parser, tree): self.parser = parser self.tree = tree + self.__startTagCache = {} + self.__endTagCache = {} def processEOF(self): raise NotImplementedError @@ -469,7 +452,21 @@ def getPhases(debug): self.tree.insertText(token["data"]) def processStartTag(self, token): - return self.startTagHandler[token["name"]](token) + # Note the caching is done here rather than BoundMethodDispatcher as doing it there + # requires a circular reference to the Phase, and this ends up with a significant + # (CPython 2.7, 3.8) GC cost when parsing many short inputs + name = token["name"] + # In Py2, using `in` is quicker in general than try/except KeyError + # In Py3, `in` is quicker when there are few cache hits (typically short inputs) + if name in self.__startTagCache: + func = self.__startTagCache[name] + else: + func = self.__startTagCache[name] = self.startTagHandler[name] + # bound the cache size in case we get loads of unknown tags + while len(self.__startTagCache) > len(self.startTagHandler) * 1.1: + # this makes the eviction policy random on Py < 3.7 and FIFO >= 3.7 + self.__startTagCache.pop(next(iter(self.__startTagCache))) + return func(token) def startTagHtml(self, token): if not self.parser.firstStartTag and token["name"] == "html": @@ -482,9 +479,25 @@ def getPhases(debug): self.parser.firstStartTag = False def processEndTag(self, token): - return self.endTagHandler[token["name"]](token) + # Note the caching is done here rather than BoundMethodDispatcher as doing it there + # requires a circular reference to the Phase, and this ends up with a significant + # (CPython 2.7, 3.8) GC cost when parsing many short inputs + name = token["name"] + # In Py2, using `in` is quicker in general than try/except KeyError + # In Py3, `in` is quicker when there are few cache hits (typically short inputs) + if name in self.__endTagCache: + func = self.__endTagCache[name] + else: + func = self.__endTagCache[name] = self.endTagHandler[name] + # bound the cache size in case we get loads of unknown tags + while len(self.__endTagCache) > len(self.endTagHandler) * 1.1: + # this makes the eviction policy random on Py < 3.7 and FIFO >= 3.7 + self.__endTagCache.pop(next(iter(self.__endTagCache))) + return func(token) class InitialPhase(Phase): + __slots__ = tuple() + def processSpaceCharacters(self, token): pass @@ -613,6 +626,8 @@ def getPhases(debug): return True class BeforeHtmlPhase(Phase): + __slots__ = tuple() + # helper methods def insertHtmlElement(self): self.tree.insertRoot(impliedTagToken("html", "StartTag")) @@ -648,19 +663,7 @@ def getPhases(debug): return token class BeforeHeadPhase(Phase): - def __init__(self, parser, tree): - Phase.__init__(self, parser, tree) - - self.startTagHandler = _utils.MethodDispatcher([ - ("html", self.startTagHtml), - ("head", self.startTagHead) - ]) - self.startTagHandler.default = self.startTagOther - - self.endTagHandler = _utils.MethodDispatcher([ - (("head", "body", "html", "br"), self.endTagImplyHead) - ]) - self.endTagHandler.default = self.endTagOther + __slots__ = tuple() def processEOF(self): self.startTagHead(impliedTagToken("head", "StartTag")) @@ -693,28 +696,19 @@ def getPhases(debug): self.parser.parseError("end-tag-after-implied-root", {"name": token["name"]}) + startTagHandler = _utils.MethodDispatcher([ + ("html", startTagHtml), + ("head", startTagHead) + ]) + startTagHandler.default = startTagOther + + endTagHandler = _utils.MethodDispatcher([ + (("head", "body", "html", "br"), endTagImplyHead) + ]) + endTagHandler.default = endTagOther + class InHeadPhase(Phase): - def __init__(self, parser, tree): - Phase.__init__(self, parser, tree) - - self.startTagHandler = _utils.MethodDispatcher([ - ("html", self.startTagHtml), - ("title", self.startTagTitle), - (("noframes", "style"), self.startTagNoFramesStyle), - ("noscript", self.startTagNoscript), - ("script", self.startTagScript), - (("base", "basefont", "bgsound", "command", "link"), - self.startTagBaseLinkCommand), - ("meta", self.startTagMeta), - ("head", self.startTagHead) - ]) - self.startTagHandler.default = self.startTagOther - - self.endTagHandler = _utils.MethodDispatcher([ - ("head", self.endTagHead), - (("br", "html", "body"), self.endTagHtmlBodyBr) - ]) - self.endTagHandler.default = self.endTagOther + __slots__ = tuple() # the real thing def processEOF(self): @@ -796,22 +790,27 @@ def getPhases(debug): def anythingElse(self): self.endTagHead(impliedTagToken("head")) + startTagHandler = _utils.MethodDispatcher([ + ("html", startTagHtml), + ("title", startTagTitle), + (("noframes", "style"), startTagNoFramesStyle), + ("noscript", startTagNoscript), + ("script", startTagScript), + (("base", "basefont", "bgsound", "command", "link"), + startTagBaseLinkCommand), + ("meta", startTagMeta), + ("head", startTagHead) + ]) + startTagHandler.default = startTagOther + + endTagHandler = _utils.MethodDispatcher([ + ("head", endTagHead), + (("br", "html", "body"), endTagHtmlBodyBr) + ]) + endTagHandler.default = endTagOther + class InHeadNoscriptPhase(Phase): - def __init__(self, parser, tree): - Phase.__init__(self, parser, tree) - - self.startTagHandler = _utils.MethodDispatcher([ - ("html", self.startTagHtml), - (("basefont", "bgsound", "link", "meta", "noframes", "style"), self.startTagBaseLinkCommand), - (("head", "noscript"), self.startTagHeadNoscript), - ]) - self.startTagHandler.default = self.startTagOther - - self.endTagHandler = _utils.MethodDispatcher([ - ("noscript", self.endTagNoscript), - ("br", self.endTagBr), - ]) - self.endTagHandler.default = self.endTagOther + __slots__ = tuple() def processEOF(self): self.parser.parseError("eof-in-head-noscript") @@ -860,23 +859,21 @@ def getPhases(debug): # Caller must raise parse error first! self.endTagNoscript(impliedTagToken("noscript")) - class AfterHeadPhase(Phase): - def __init__(self, parser, tree): - Phase.__init__(self, parser, tree) + startTagHandler = _utils.MethodDispatcher([ + ("html", startTagHtml), + (("basefont", "bgsound", "link", "meta", "noframes", "style"), startTagBaseLinkCommand), + (("head", "noscript"), startTagHeadNoscript), + ]) + startTagHandler.default = startTagOther - self.startTagHandler = _utils.MethodDispatcher([ - ("html", self.startTagHtml), - ("body", self.startTagBody), - ("frameset", self.startTagFrameset), - (("base", "basefont", "bgsound", "link", "meta", "noframes", "script", - "style", "title"), - self.startTagFromHead), - ("head", self.startTagHead) - ]) - self.startTagHandler.default = self.startTagOther - self.endTagHandler = _utils.MethodDispatcher([(("body", "html", "br"), - self.endTagHtmlBodyBr)]) - self.endTagHandler.default = self.endTagOther + endTagHandler = _utils.MethodDispatcher([ + ("noscript", endTagNoscript), + ("br", endTagBr), + ]) + endTagHandler.default = endTagOther + + class AfterHeadPhase(Phase): + __slots__ = tuple() def processEOF(self): self.anythingElse() @@ -927,80 +924,30 @@ def getPhases(debug): self.parser.phase = self.parser.phases["inBody"] self.parser.framesetOK = True + startTagHandler = _utils.MethodDispatcher([ + ("html", startTagHtml), + ("body", startTagBody), + ("frameset", startTagFrameset), + (("base", "basefont", "bgsound", "link", "meta", "noframes", "script", + "style", "title"), + startTagFromHead), + ("head", startTagHead) + ]) + startTagHandler.default = startTagOther + endTagHandler = _utils.MethodDispatcher([(("body", "html", "br"), + endTagHtmlBodyBr)]) + endTagHandler.default = endTagOther + class InBodyPhase(Phase): # http://www.whatwg.org/specs/web-apps/current-work/#parsing-main-inbody # the really-really-really-very crazy mode - def __init__(self, parser, tree): - Phase.__init__(self, parser, tree) + __slots__ = ("processSpaceCharacters",) + def __init__(self, *args, **kwargs): + super(InBodyPhase, self).__init__(*args, **kwargs) # Set this to the default handler self.processSpaceCharacters = self.processSpaceCharactersNonPre - self.startTagHandler = _utils.MethodDispatcher([ - ("html", self.startTagHtml), - (("base", "basefont", "bgsound", "command", "link", "meta", - "script", "style", "title"), - self.startTagProcessInHead), - ("body", self.startTagBody), - ("frameset", self.startTagFrameset), - (("address", "article", "aside", "blockquote", "center", "details", - "dir", "div", "dl", "fieldset", "figcaption", "figure", - "footer", "header", "hgroup", "main", "menu", "nav", "ol", "p", - "section", "summary", "ul"), - self.startTagCloseP), - (headingElements, self.startTagHeading), - (("pre", "listing"), self.startTagPreListing), - ("form", self.startTagForm), - (("li", "dd", "dt"), self.startTagListItem), - ("plaintext", self.startTagPlaintext), - ("a", self.startTagA), - (("b", "big", "code", "em", "font", "i", "s", "small", "strike", - "strong", "tt", "u"), self.startTagFormatting), - ("nobr", self.startTagNobr), - ("button", self.startTagButton), - (("applet", "marquee", "object"), self.startTagAppletMarqueeObject), - ("xmp", self.startTagXmp), - ("table", self.startTagTable), - (("area", "br", "embed", "img", "keygen", "wbr"), - self.startTagVoidFormatting), - (("param", "source", "track"), self.startTagParamSource), - ("input", self.startTagInput), - ("hr", self.startTagHr), - ("image", self.startTagImage), - ("isindex", self.startTagIsIndex), - ("textarea", self.startTagTextarea), - ("iframe", self.startTagIFrame), - ("noscript", self.startTagNoscript), - (("noembed", "noframes"), self.startTagRawtext), - ("select", self.startTagSelect), - (("rp", "rt"), self.startTagRpRt), - (("option", "optgroup"), self.startTagOpt), - (("math"), self.startTagMath), - (("svg"), self.startTagSvg), - (("caption", "col", "colgroup", "frame", "head", - "tbody", "td", "tfoot", "th", "thead", - "tr"), self.startTagMisplaced) - ]) - self.startTagHandler.default = self.startTagOther - - self.endTagHandler = _utils.MethodDispatcher([ - ("body", self.endTagBody), - ("html", self.endTagHtml), - (("address", "article", "aside", "blockquote", "button", "center", - "details", "dialog", "dir", "div", "dl", "fieldset", "figcaption", "figure", - "footer", "header", "hgroup", "listing", "main", "menu", "nav", "ol", "pre", - "section", "summary", "ul"), self.endTagBlock), - ("form", self.endTagForm), - ("p", self.endTagP), - (("dd", "dt", "li"), self.endTagListItem), - (headingElements, self.endTagHeading), - (("a", "b", "big", "code", "em", "font", "i", "nobr", "s", "small", - "strike", "strong", "tt", "u"), self.endTagFormatting), - (("applet", "marquee", "object"), self.endTagAppletMarqueeObject), - ("br", self.endTagBr), - ]) - self.endTagHandler.default = self.endTagOther - def isMatchingFormattingElement(self, node1, node2): return (node1.name == node2.name and node1.namespace == node2.namespace and @@ -1650,14 +1597,73 @@ def getPhases(debug): self.parser.parseError("unexpected-end-tag", {"name": token["name"]}) break + startTagHandler = _utils.MethodDispatcher([ + ("html", Phase.startTagHtml), + (("base", "basefont", "bgsound", "command", "link", "meta", + "script", "style", "title"), + startTagProcessInHead), + ("body", startTagBody), + ("frameset", startTagFrameset), + (("address", "article", "aside", "blockquote", "center", "details", + "dir", "div", "dl", "fieldset", "figcaption", "figure", + "footer", "header", "hgroup", "main", "menu", "nav", "ol", "p", + "section", "summary", "ul"), + startTagCloseP), + (headingElements, startTagHeading), + (("pre", "listing"), startTagPreListing), + ("form", startTagForm), + (("li", "dd", "dt"), startTagListItem), + ("plaintext", startTagPlaintext), + ("a", startTagA), + (("b", "big", "code", "em", "font", "i", "s", "small", "strike", + "strong", "tt", "u"), startTagFormatting), + ("nobr", startTagNobr), + ("button", startTagButton), + (("applet", "marquee", "object"), startTagAppletMarqueeObject), + ("xmp", startTagXmp), + ("table", startTagTable), + (("area", "br", "embed", "img", "keygen", "wbr"), + startTagVoidFormatting), + (("param", "source", "track"), startTagParamSource), + ("input", startTagInput), + ("hr", startTagHr), + ("image", startTagImage), + ("isindex", startTagIsIndex), + ("textarea", startTagTextarea), + ("iframe", startTagIFrame), + ("noscript", startTagNoscript), + (("noembed", "noframes"), startTagRawtext), + ("select", startTagSelect), + (("rp", "rt"), startTagRpRt), + (("option", "optgroup"), startTagOpt), + (("math"), startTagMath), + (("svg"), startTagSvg), + (("caption", "col", "colgroup", "frame", "head", + "tbody", "td", "tfoot", "th", "thead", + "tr"), startTagMisplaced) + ]) + startTagHandler.default = startTagOther + + endTagHandler = _utils.MethodDispatcher([ + ("body", endTagBody), + ("html", endTagHtml), + (("address", "article", "aside", "blockquote", "button", "center", + "details", "dialog", "dir", "div", "dl", "fieldset", "figcaption", "figure", + "footer", "header", "hgroup", "listing", "main", "menu", "nav", "ol", "pre", + "section", "summary", "ul"), endTagBlock), + ("form", endTagForm), + ("p", endTagP), + (("dd", "dt", "li"), endTagListItem), + (headingElements, endTagHeading), + (("a", "b", "big", "code", "em", "font", "i", "nobr", "s", "small", + "strike", "strong", "tt", "u"), endTagFormatting), + (("applet", "marquee", "object"), endTagAppletMarqueeObject), + ("br", endTagBr), + ]) + endTagHandler.default = endTagOther + class TextPhase(Phase): - def __init__(self, parser, tree): - Phase.__init__(self, parser, tree) - self.startTagHandler = _utils.MethodDispatcher([]) - self.startTagHandler.default = self.startTagOther - self.endTagHandler = _utils.MethodDispatcher([ - ("script", self.endTagScript)]) - self.endTagHandler.default = self.endTagOther + __slots__ = tuple() def processCharacters(self, token): self.tree.insertText(token["data"]) @@ -1683,30 +1689,15 @@ def getPhases(debug): self.tree.openElements.pop() self.parser.phase = self.parser.originalPhase + startTagHandler = _utils.MethodDispatcher([]) + startTagHandler.default = startTagOther + endTagHandler = _utils.MethodDispatcher([ + ("script", endTagScript)]) + endTagHandler.default = endTagOther + class InTablePhase(Phase): # http://www.whatwg.org/specs/web-apps/current-work/#in-table - def __init__(self, parser, tree): - Phase.__init__(self, parser, tree) - self.startTagHandler = _utils.MethodDispatcher([ - ("html", self.startTagHtml), - ("caption", self.startTagCaption), - ("colgroup", self.startTagColgroup), - ("col", self.startTagCol), - (("tbody", "tfoot", "thead"), self.startTagRowGroup), - (("td", "th", "tr"), self.startTagImplyTbody), - ("table", self.startTagTable), - (("style", "script"), self.startTagStyleScript), - ("input", self.startTagInput), - ("form", self.startTagForm) - ]) - self.startTagHandler.default = self.startTagOther - - self.endTagHandler = _utils.MethodDispatcher([ - ("table", self.endTagTable), - (("body", "caption", "col", "colgroup", "html", "tbody", "td", - "tfoot", "th", "thead", "tr"), self.endTagIgnore) - ]) - self.endTagHandler.default = self.endTagOther + __slots__ = tuple() # helper methods def clearStackToTableContext(self): @@ -1828,9 +1819,32 @@ def getPhases(debug): self.parser.phases["inBody"].processEndTag(token) self.tree.insertFromTable = False + startTagHandler = _utils.MethodDispatcher([ + ("html", Phase.startTagHtml), + ("caption", startTagCaption), + ("colgroup", startTagColgroup), + ("col", startTagCol), + (("tbody", "tfoot", "thead"), startTagRowGroup), + (("td", "th", "tr"), startTagImplyTbody), + ("table", startTagTable), + (("style", "script"), startTagStyleScript), + ("input", startTagInput), + ("form", startTagForm) + ]) + startTagHandler.default = startTagOther + + endTagHandler = _utils.MethodDispatcher([ + ("table", endTagTable), + (("body", "caption", "col", "colgroup", "html", "tbody", "td", + "tfoot", "th", "thead", "tr"), endTagIgnore) + ]) + endTagHandler.default = endTagOther + class InTableTextPhase(Phase): - def __init__(self, parser, tree): - Phase.__init__(self, parser, tree) + __slots__ = ("originalPhase", "characterTokens") + + def __init__(self, *args, **kwargs): + super(InTableTextPhase, self).__init__(*args, **kwargs) self.originalPhase = None self.characterTokens = [] @@ -1875,23 +1889,7 @@ def getPhases(debug): class InCaptionPhase(Phase): # http://www.whatwg.org/specs/web-apps/current-work/#in-caption - def __init__(self, parser, tree): - Phase.__init__(self, parser, tree) - - self.startTagHandler = _utils.MethodDispatcher([ - ("html", self.startTagHtml), - (("caption", "col", "colgroup", "tbody", "td", "tfoot", "th", - "thead", "tr"), self.startTagTableElement) - ]) - self.startTagHandler.default = self.startTagOther - - self.endTagHandler = _utils.MethodDispatcher([ - ("caption", self.endTagCaption), - ("table", self.endTagTable), - (("body", "col", "colgroup", "html", "tbody", "td", "tfoot", "th", - "thead", "tr"), self.endTagIgnore) - ]) - self.endTagHandler.default = self.endTagOther + __slots__ = tuple() def ignoreEndTagCaption(self): return not self.tree.elementInScope("caption", variant="table") @@ -1944,23 +1942,24 @@ def getPhases(debug): def endTagOther(self, token): return self.parser.phases["inBody"].processEndTag(token) + startTagHandler = _utils.MethodDispatcher([ + ("html", Phase.startTagHtml), + (("caption", "col", "colgroup", "tbody", "td", "tfoot", "th", + "thead", "tr"), startTagTableElement) + ]) + startTagHandler.default = startTagOther + + endTagHandler = _utils.MethodDispatcher([ + ("caption", endTagCaption), + ("table", endTagTable), + (("body", "col", "colgroup", "html", "tbody", "td", "tfoot", "th", + "thead", "tr"), endTagIgnore) + ]) + endTagHandler.default = endTagOther + class InColumnGroupPhase(Phase): # http://www.whatwg.org/specs/web-apps/current-work/#in-column - - def __init__(self, parser, tree): - Phase.__init__(self, parser, tree) - - self.startTagHandler = _utils.MethodDispatcher([ - ("html", self.startTagHtml), - ("col", self.startTagCol) - ]) - self.startTagHandler.default = self.startTagOther - - self.endTagHandler = _utils.MethodDispatcher([ - ("colgroup", self.endTagColgroup), - ("col", self.endTagCol) - ]) - self.endTagHandler.default = self.endTagOther + __slots__ = tuple() def ignoreEndTagColgroup(self): return self.tree.openElements[-1].name == "html" @@ -2010,26 +2009,21 @@ def getPhases(debug): if not ignoreEndTag: return token + startTagHandler = _utils.MethodDispatcher([ + ("html", Phase.startTagHtml), + ("col", startTagCol) + ]) + startTagHandler.default = startTagOther + + endTagHandler = _utils.MethodDispatcher([ + ("colgroup", endTagColgroup), + ("col", endTagCol) + ]) + endTagHandler.default = endTagOther + class InTableBodyPhase(Phase): # http://www.whatwg.org/specs/web-apps/current-work/#in-table0 - def __init__(self, parser, tree): - Phase.__init__(self, parser, tree) - self.startTagHandler = _utils.MethodDispatcher([ - ("html", self.startTagHtml), - ("tr", self.startTagTr), - (("td", "th"), self.startTagTableCell), - (("caption", "col", "colgroup", "tbody", "tfoot", "thead"), - self.startTagTableOther) - ]) - self.startTagHandler.default = self.startTagOther - - self.endTagHandler = _utils.MethodDispatcher([ - (("tbody", "tfoot", "thead"), self.endTagTableRowGroup), - ("table", self.endTagTable), - (("body", "caption", "col", "colgroup", "html", "td", "th", - "tr"), self.endTagIgnore) - ]) - self.endTagHandler.default = self.endTagOther + __slots__ = tuple() # helper methods def clearStackToTableBodyContext(self): @@ -2108,26 +2102,26 @@ def getPhases(debug): def endTagOther(self, token): return self.parser.phases["inTable"].processEndTag(token) + startTagHandler = _utils.MethodDispatcher([ + ("html", Phase.startTagHtml), + ("tr", startTagTr), + (("td", "th"), startTagTableCell), + (("caption", "col", "colgroup", "tbody", "tfoot", "thead"), + startTagTableOther) + ]) + startTagHandler.default = startTagOther + + endTagHandler = _utils.MethodDispatcher([ + (("tbody", "tfoot", "thead"), endTagTableRowGroup), + ("table", endTagTable), + (("body", "caption", "col", "colgroup", "html", "td", "th", + "tr"), endTagIgnore) + ]) + endTagHandler.default = endTagOther + class InRowPhase(Phase): # http://www.whatwg.org/specs/web-apps/current-work/#in-row - def __init__(self, parser, tree): - Phase.__init__(self, parser, tree) - self.startTagHandler = _utils.MethodDispatcher([ - ("html", self.startTagHtml), - (("td", "th"), self.startTagTableCell), - (("caption", "col", "colgroup", "tbody", "tfoot", "thead", - "tr"), self.startTagTableOther) - ]) - self.startTagHandler.default = self.startTagOther - - self.endTagHandler = _utils.MethodDispatcher([ - ("tr", self.endTagTr), - ("table", self.endTagTable), - (("tbody", "tfoot", "thead"), self.endTagTableRowGroup), - (("body", "caption", "col", "colgroup", "html", "td", "th"), - self.endTagIgnore) - ]) - self.endTagHandler.default = self.endTagOther + __slots__ = tuple() # helper methods (XXX unify this with other table helper methods) def clearStackToTableRowContext(self): @@ -2197,23 +2191,26 @@ def getPhases(debug): def endTagOther(self, token): return self.parser.phases["inTable"].processEndTag(token) + startTagHandler = _utils.MethodDispatcher([ + ("html", Phase.startTagHtml), + (("td", "th"), startTagTableCell), + (("caption", "col", "colgroup", "tbody", "tfoot", "thead", + "tr"), startTagTableOther) + ]) + startTagHandler.default = startTagOther + + endTagHandler = _utils.MethodDispatcher([ + ("tr", endTagTr), + ("table", endTagTable), + (("tbody", "tfoot", "thead"), endTagTableRowGroup), + (("body", "caption", "col", "colgroup", "html", "td", "th"), + endTagIgnore) + ]) + endTagHandler.default = endTagOther + class InCellPhase(Phase): # http://www.whatwg.org/specs/web-apps/current-work/#in-cell - def __init__(self, parser, tree): - Phase.__init__(self, parser, tree) - self.startTagHandler = _utils.MethodDispatcher([ - ("html", self.startTagHtml), - (("caption", "col", "colgroup", "tbody", "td", "tfoot", "th", - "thead", "tr"), self.startTagTableOther) - ]) - self.startTagHandler.default = self.startTagOther - - self.endTagHandler = _utils.MethodDispatcher([ - (("td", "th"), self.endTagTableCell), - (("body", "caption", "col", "colgroup", "html"), self.endTagIgnore), - (("table", "tbody", "tfoot", "thead", "tr"), self.endTagImply) - ]) - self.endTagHandler.default = self.endTagOther + __slots__ = tuple() # helper def closeCell(self): @@ -2273,26 +2270,22 @@ def getPhases(debug): def endTagOther(self, token): return self.parser.phases["inBody"].processEndTag(token) + startTagHandler = _utils.MethodDispatcher([ + ("html", Phase.startTagHtml), + (("caption", "col", "colgroup", "tbody", "td", "tfoot", "th", + "thead", "tr"), startTagTableOther) + ]) + startTagHandler.default = startTagOther + + endTagHandler = _utils.MethodDispatcher([ + (("td", "th"), endTagTableCell), + (("body", "caption", "col", "colgroup", "html"), endTagIgnore), + (("table", "tbody", "tfoot", "thead", "tr"), endTagImply) + ]) + endTagHandler.default = endTagOther + class InSelectPhase(Phase): - def __init__(self, parser, tree): - Phase.__init__(self, parser, tree) - - self.startTagHandler = _utils.MethodDispatcher([ - ("html", self.startTagHtml), - ("option", self.startTagOption), - ("optgroup", self.startTagOptgroup), - ("select", self.startTagSelect), - (("input", "keygen", "textarea"), self.startTagInput), - ("script", self.startTagScript) - ]) - self.startTagHandler.default = self.startTagOther - - self.endTagHandler = _utils.MethodDispatcher([ - ("option", self.endTagOption), - ("optgroup", self.endTagOptgroup), - ("select", self.endTagSelect) - ]) - self.endTagHandler.default = self.endTagOther + __slots__ = tuple() # http://www.whatwg.org/specs/web-apps/current-work/#in-select def processEOF(self): @@ -2373,21 +2366,25 @@ def getPhases(debug): self.parser.parseError("unexpected-end-tag-in-select", {"name": token["name"]}) + startTagHandler = _utils.MethodDispatcher([ + ("html", Phase.startTagHtml), + ("option", startTagOption), + ("optgroup", startTagOptgroup), + ("select", startTagSelect), + (("input", "keygen", "textarea"), startTagInput), + ("script", startTagScript) + ]) + startTagHandler.default = startTagOther + + endTagHandler = _utils.MethodDispatcher([ + ("option", endTagOption), + ("optgroup", endTagOptgroup), + ("select", endTagSelect) + ]) + endTagHandler.default = endTagOther + class InSelectInTablePhase(Phase): - def __init__(self, parser, tree): - Phase.__init__(self, parser, tree) - - self.startTagHandler = _utils.MethodDispatcher([ - (("caption", "table", "tbody", "tfoot", "thead", "tr", "td", "th"), - self.startTagTable) - ]) - self.startTagHandler.default = self.startTagOther - - self.endTagHandler = _utils.MethodDispatcher([ - (("caption", "table", "tbody", "tfoot", "thead", "tr", "td", "th"), - self.endTagTable) - ]) - self.endTagHandler.default = self.endTagOther + __slots__ = tuple() def processEOF(self): self.parser.phases["inSelect"].processEOF() @@ -2412,7 +2409,21 @@ def getPhases(debug): def endTagOther(self, token): return self.parser.phases["inSelect"].processEndTag(token) + startTagHandler = _utils.MethodDispatcher([ + (("caption", "table", "tbody", "tfoot", "thead", "tr", "td", "th"), + startTagTable) + ]) + startTagHandler.default = startTagOther + + endTagHandler = _utils.MethodDispatcher([ + (("caption", "table", "tbody", "tfoot", "thead", "tr", "td", "th"), + endTagTable) + ]) + endTagHandler.default = endTagOther + class InForeignContentPhase(Phase): + __slots__ = tuple() + breakoutElements = frozenset(["b", "big", "blockquote", "body", "br", "center", "code", "dd", "div", "dl", "dt", "em", "embed", "h1", "h2", "h3", @@ -2422,9 +2433,6 @@ def getPhases(debug): "span", "strong", "strike", "sub", "sup", "table", "tt", "u", "ul", "var"]) - def __init__(self, parser, tree): - Phase.__init__(self, parser, tree) - def adjustSVGTagNames(self, token): replacements = {"altglyph": "altGlyph", "altglyphdef": "altGlyphDef", @@ -2478,7 +2486,7 @@ def getPhases(debug): currentNode = self.tree.openElements[-1] if (token["name"] in self.breakoutElements or (token["name"] == "font" and - set(token["data"].keys()) & set(["color", "face", "size"]))): + set(token["data"].keys()) & {"color", "face", "size"})): self.parser.parseError("unexpected-html-element-in-foreign-content", {"name": token["name"]}) while (self.tree.openElements[-1].namespace != @@ -2528,16 +2536,7 @@ def getPhases(debug): return new_token class AfterBodyPhase(Phase): - def __init__(self, parser, tree): - Phase.__init__(self, parser, tree) - - self.startTagHandler = _utils.MethodDispatcher([ - ("html", self.startTagHtml) - ]) - self.startTagHandler.default = self.startTagOther - - self.endTagHandler = _utils.MethodDispatcher([("html", self.endTagHtml)]) - self.endTagHandler.default = self.endTagOther + __slots__ = tuple() def processEOF(self): # Stop parsing @@ -2574,23 +2573,17 @@ def getPhases(debug): self.parser.phase = self.parser.phases["inBody"] return token + startTagHandler = _utils.MethodDispatcher([ + ("html", startTagHtml) + ]) + startTagHandler.default = startTagOther + + endTagHandler = _utils.MethodDispatcher([("html", endTagHtml)]) + endTagHandler.default = endTagOther + class InFramesetPhase(Phase): # http://www.whatwg.org/specs/web-apps/current-work/#in-frameset - def __init__(self, parser, tree): - Phase.__init__(self, parser, tree) - - self.startTagHandler = _utils.MethodDispatcher([ - ("html", self.startTagHtml), - ("frameset", self.startTagFrameset), - ("frame", self.startTagFrame), - ("noframes", self.startTagNoframes) - ]) - self.startTagHandler.default = self.startTagOther - - self.endTagHandler = _utils.MethodDispatcher([ - ("frameset", self.endTagFrameset) - ]) - self.endTagHandler.default = self.endTagOther + __slots__ = tuple() def processEOF(self): if self.tree.openElements[-1].name != "html": @@ -2631,21 +2624,22 @@ def getPhases(debug): self.parser.parseError("unexpected-end-tag-in-frameset", {"name": token["name"]}) + startTagHandler = _utils.MethodDispatcher([ + ("html", Phase.startTagHtml), + ("frameset", startTagFrameset), + ("frame", startTagFrame), + ("noframes", startTagNoframes) + ]) + startTagHandler.default = startTagOther + + endTagHandler = _utils.MethodDispatcher([ + ("frameset", endTagFrameset) + ]) + endTagHandler.default = endTagOther + class AfterFramesetPhase(Phase): # http://www.whatwg.org/specs/web-apps/current-work/#after3 - def __init__(self, parser, tree): - Phase.__init__(self, parser, tree) - - self.startTagHandler = _utils.MethodDispatcher([ - ("html", self.startTagHtml), - ("noframes", self.startTagNoframes) - ]) - self.startTagHandler.default = self.startTagOther - - self.endTagHandler = _utils.MethodDispatcher([ - ("html", self.endTagHtml) - ]) - self.endTagHandler.default = self.endTagOther + __slots__ = tuple() def processEOF(self): # Stop parsing @@ -2668,14 +2662,19 @@ def getPhases(debug): self.parser.parseError("unexpected-end-tag-after-frameset", {"name": token["name"]}) - class AfterAfterBodyPhase(Phase): - def __init__(self, parser, tree): - Phase.__init__(self, parser, tree) + startTagHandler = _utils.MethodDispatcher([ + ("html", Phase.startTagHtml), + ("noframes", startTagNoframes) + ]) + startTagHandler.default = startTagOther - self.startTagHandler = _utils.MethodDispatcher([ - ("html", self.startTagHtml) - ]) - self.startTagHandler.default = self.startTagOther + endTagHandler = _utils.MethodDispatcher([ + ("html", endTagHtml) + ]) + endTagHandler.default = endTagOther + + class AfterAfterBodyPhase(Phase): + __slots__ = tuple() def processEOF(self): pass @@ -2706,15 +2705,13 @@ def getPhases(debug): self.parser.phase = self.parser.phases["inBody"] return token - class AfterAfterFramesetPhase(Phase): - def __init__(self, parser, tree): - Phase.__init__(self, parser, tree) + startTagHandler = _utils.MethodDispatcher([ + ("html", startTagHtml) + ]) + startTagHandler.default = startTagOther - self.startTagHandler = _utils.MethodDispatcher([ - ("html", self.startTagHtml), - ("noframes", self.startTagNoFrames) - ]) - self.startTagHandler.default = self.startTagOther + class AfterAfterFramesetPhase(Phase): + __slots__ = tuple() def processEOF(self): pass @@ -2741,6 +2738,13 @@ def getPhases(debug): def processEndTag(self, token): self.parser.parseError("expected-eof-but-got-end-tag", {"name": token["name"]}) + + startTagHandler = _utils.MethodDispatcher([ + ("html", startTagHtml), + ("noframes", startTagNoFrames) + ]) + startTagHandler.default = startTagOther + # pylint:enable=unused-argument return { @@ -2774,8 +2778,8 @@ def getPhases(debug): def adjust_attributes(token, replacements): needs_adjustment = viewkeys(token['data']) & viewkeys(replacements) if needs_adjustment: - token['data'] = OrderedDict((replacements.get(k, k), v) - for k, v in token['data'].items()) + token['data'] = type(token['data'])((replacements.get(k, k), v) + for k, v in token['data'].items()) def impliedTagToken(name, type="EndTag", attributes=None, diff --git a/pipenv/patched/notpip/_vendor/html5lib/serializer.py b/pipenv/patched/notpip/_vendor/html5lib/serializer.py index 383f3ee1..940725c9 100644 --- a/pipenv/patched/notpip/_vendor/html5lib/serializer.py +++ b/pipenv/patched/notpip/_vendor/html5lib/serializer.py @@ -274,7 +274,7 @@ class HTMLSerializer(object): if token["systemId"]: if token["systemId"].find('"') >= 0: if token["systemId"].find("'") >= 0: - self.serializeError("System identifer contains both single and double quote characters") + self.serializeError("System identifier contains both single and double quote characters") quote_char = "'" else: quote_char = '"' diff --git a/pipenv/patched/notpip/_vendor/html5lib/treebuilders/base.py b/pipenv/patched/notpip/_vendor/html5lib/treebuilders/base.py index d10a170c..7223b52f 100644 --- a/pipenv/patched/notpip/_vendor/html5lib/treebuilders/base.py +++ b/pipenv/patched/notpip/_vendor/html5lib/treebuilders/base.py @@ -10,9 +10,9 @@ Marker = None listElementsMap = { None: (frozenset(scopingElements), False), - "button": (frozenset(scopingElements | set([(namespaces["html"], "button")])), False), - "list": (frozenset(scopingElements | set([(namespaces["html"], "ol"), - (namespaces["html"], "ul")])), False), + "button": (frozenset(scopingElements | {(namespaces["html"], "button")}), False), + "list": (frozenset(scopingElements | {(namespaces["html"], "ol"), + (namespaces["html"], "ul")}), False), "table": (frozenset([(namespaces["html"], "html"), (namespaces["html"], "table")]), False), "select": (frozenset([(namespaces["html"], "optgroup"), @@ -28,7 +28,7 @@ class Node(object): :arg name: The tag name associated with the node """ - # The tag name assocaited with the node + # The tag name associated with the node self.name = name # The parent of the current node (or None for the document node) self.parent = None diff --git a/pipenv/patched/notpip/_vendor/html5lib/treebuilders/dom.py b/pipenv/patched/notpip/_vendor/html5lib/treebuilders/dom.py index dcfac220..d8b53004 100644 --- a/pipenv/patched/notpip/_vendor/html5lib/treebuilders/dom.py +++ b/pipenv/patched/notpip/_vendor/html5lib/treebuilders/dom.py @@ -1,7 +1,10 @@ from __future__ import absolute_import, division, unicode_literals -from collections import MutableMapping +try: + from collections.abc import MutableMapping +except ImportError: # Python 2.7 + from collections import MutableMapping from xml.dom import minidom, Node import weakref diff --git a/pipenv/patched/notpip/_vendor/html5lib/treebuilders/etree.py b/pipenv/patched/notpip/_vendor/html5lib/treebuilders/etree.py index 6801a492..9c6a9de1 100644 --- a/pipenv/patched/notpip/_vendor/html5lib/treebuilders/etree.py +++ b/pipenv/patched/notpip/_vendor/html5lib/treebuilders/etree.py @@ -5,6 +5,8 @@ from pipenv.patched.notpip._vendor.six import text_type import re +from copy import copy + from . import base from .. import _ihatexml from .. import constants @@ -61,16 +63,17 @@ def getETreeBuilder(ElementTreeImplementation, fullTree=False): return self._element.attrib def _setAttributes(self, attributes): - # Delete existing attributes first - # XXX - there may be a better way to do this... - for key in list(self._element.attrib.keys()): - del self._element.attrib[key] - for key, value in attributes.items(): - if isinstance(key, tuple): - name = "{%s}%s" % (key[2], key[1]) - else: - name = key - self._element.set(name, value) + el_attrib = self._element.attrib + el_attrib.clear() + if attributes: + # calling .items _always_ allocates, and the above truthy check is cheaper than the + # allocation on average + for key, value in attributes.items(): + if isinstance(key, tuple): + name = "{%s}%s" % (key[2], key[1]) + else: + name = key + el_attrib[name] = value attributes = property(_getAttributes, _setAttributes) @@ -129,8 +132,8 @@ def getETreeBuilder(ElementTreeImplementation, fullTree=False): def cloneNode(self): element = type(self)(self.name, self.namespace) - for name, value in self.attributes.items(): - element.attributes[name] = value + if self._element.attrib: + element._element.attrib = copy(self._element.attrib) return element def reparentChildren(self, newParent): diff --git a/pipenv/patched/notpip/_vendor/html5lib/treebuilders/etree_lxml.py b/pipenv/patched/notpip/_vendor/html5lib/treebuilders/etree_lxml.py index ca12a99c..85991d4f 100644 --- a/pipenv/patched/notpip/_vendor/html5lib/treebuilders/etree_lxml.py +++ b/pipenv/patched/notpip/_vendor/html5lib/treebuilders/etree_lxml.py @@ -16,6 +16,11 @@ import warnings import re import sys +try: + from collections.abc import MutableMapping +except ImportError: + from collections import MutableMapping + from . import base from ..constants import DataLossWarning from .. import constants @@ -23,6 +28,7 @@ from . import etree as etree_builders from .. import _ihatexml import lxml.etree as etree +from pipenv.patched.notpip._vendor.six import PY3, binary_type fullTree = True @@ -44,7 +50,11 @@ class Document(object): self._childNodes = [] def appendChild(self, element): - self._elementTree.getroot().addnext(element._element) + last = self._elementTree.getroot() + for last in self._elementTree.getroot().itersiblings(): + pass + + last.addnext(element._element) def _getChildNodes(self): return self._childNodes @@ -185,26 +195,37 @@ class TreeBuilder(base.TreeBuilder): infosetFilter = self.infosetFilter = _ihatexml.InfosetFilter(preventDoubleDashComments=True) self.namespaceHTMLElements = namespaceHTMLElements - class Attributes(dict): - def __init__(self, element, value=None): - if value is None: - value = {} + class Attributes(MutableMapping): + def __init__(self, element): self._element = element - dict.__init__(self, value) # pylint:disable=non-parent-init-called - for key, value in self.items(): - if isinstance(key, tuple): - name = "{%s}%s" % (key[2], infosetFilter.coerceAttribute(key[1])) - else: - name = infosetFilter.coerceAttribute(key) - self._element._element.attrib[name] = value - def __setitem__(self, key, value): - dict.__setitem__(self, key, value) + def _coerceKey(self, key): if isinstance(key, tuple): name = "{%s}%s" % (key[2], infosetFilter.coerceAttribute(key[1])) else: name = infosetFilter.coerceAttribute(key) - self._element._element.attrib[name] = value + return name + + def __getitem__(self, key): + value = self._element._element.attrib[self._coerceKey(key)] + if not PY3 and isinstance(value, binary_type): + value = value.decode("ascii") + return value + + def __setitem__(self, key, value): + self._element._element.attrib[self._coerceKey(key)] = value + + def __delitem__(self, key): + del self._element._element.attrib[self._coerceKey(key)] + + def __iter__(self): + return iter(self._element._element.attrib) + + def __len__(self): + return len(self._element._element.attrib) + + def clear(self): + return self._element._element.attrib.clear() class Element(builder.Element): def __init__(self, name, namespace): @@ -225,8 +246,10 @@ class TreeBuilder(base.TreeBuilder): def _getAttributes(self): return self._attributes - def _setAttributes(self, attributes): - self._attributes = Attributes(self, attributes) + def _setAttributes(self, value): + attributes = self.attributes + attributes.clear() + attributes.update(value) attributes = property(_getAttributes, _setAttributes) @@ -234,8 +257,11 @@ class TreeBuilder(base.TreeBuilder): data = infosetFilter.coerceCharacters(data) builder.Element.insertText(self, data, insertBefore) - def appendChild(self, child): - builder.Element.appendChild(self, child) + def cloneNode(self): + element = type(self)(self.name, self.namespace) + if self._element.attrib: + element._element.attrib.update(self._element.attrib) + return element class Comment(builder.Comment): def __init__(self, data): diff --git a/pipenv/patched/notpip/_vendor/html5lib/treewalkers/__init__.py b/pipenv/patched/notpip/_vendor/html5lib/treewalkers/__init__.py index 9bec2076..b2d3aac3 100644 --- a/pipenv/patched/notpip/_vendor/html5lib/treewalkers/__init__.py +++ b/pipenv/patched/notpip/_vendor/html5lib/treewalkers/__init__.py @@ -2,10 +2,10 @@ tree, generating tokens identical to those produced by the tokenizer module. -To create a tree walker for a new type of tree, you need to do +To create a tree walker for a new type of tree, you need to implement a tree walker object (called TreeWalker by convention) that -implements a 'serialize' method taking a tree as sole argument and -returning an iterator generating tokens. +implements a 'serialize' method which takes a tree as sole argument and +returns an iterator which generates tokens. """ from __future__ import absolute_import, division, unicode_literals diff --git a/pipenv/patched/notpip/_vendor/html5lib/treewalkers/etree.py b/pipenv/patched/notpip/_vendor/html5lib/treewalkers/etree.py index 72ed5aec..29f40906 100644 --- a/pipenv/patched/notpip/_vendor/html5lib/treewalkers/etree.py +++ b/pipenv/patched/notpip/_vendor/html5lib/treewalkers/etree.py @@ -127,4 +127,5 @@ def getETreeBuilder(ElementTreeImplementation): return locals() + getETreeModule = moduleFactoryFactory(getETreeBuilder) diff --git a/pipenv/patched/notpip/_vendor/html5lib/treewalkers/etree_lxml.py b/pipenv/patched/notpip/_vendor/html5lib/treewalkers/etree_lxml.py index 4641bf07..a9e30aed 100644 --- a/pipenv/patched/notpip/_vendor/html5lib/treewalkers/etree_lxml.py +++ b/pipenv/patched/notpip/_vendor/html5lib/treewalkers/etree_lxml.py @@ -1,6 +1,8 @@ from __future__ import absolute_import, division, unicode_literals from pipenv.patched.notpip._vendor.six import text_type +from collections import OrderedDict + from lxml import etree from ..treebuilders.etree import tag_regexp @@ -163,7 +165,7 @@ class TreeWalker(base.NonRecursiveTreeWalker): else: namespace = None tag = ensure_str(node.tag) - attrs = {} + attrs = OrderedDict() for name, value in list(node.attrib.items()): name = ensure_str(name) value = ensure_str(value) diff --git a/pipenv/vendor/scandir.LICENSE.txt b/pipenv/patched/notpip/_vendor/idna/LICENSE.md similarity index 60% rename from pipenv/vendor/scandir.LICENSE.txt rename to pipenv/patched/notpip/_vendor/idna/LICENSE.md index 0759f503..b6f87326 100644 --- a/pipenv/vendor/scandir.LICENSE.txt +++ b/pipenv/patched/notpip/_vendor/idna/LICENSE.md @@ -1,19 +1,21 @@ -Copyright (c) 2012, Ben Hoyt +BSD 3-Clause License + +Copyright (c) 2013-2021, Kim Davies All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -* Redistributions of source code must retain the above copyright notice, this -list of conditions and the following disclaimer. +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation -and/or other materials provided with the distribution. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. -* Neither the name of Ben Hoyt nor the names of its contributors may be used -to endorse or promote products derived from this software without specific -prior written permission. +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE diff --git a/pipenv/patched/notpip/_vendor/idna/LICENSE.rst b/pipenv/patched/notpip/_vendor/idna/LICENSE.rst deleted file mode 100644 index 3ee64fba..00000000 --- a/pipenv/patched/notpip/_vendor/idna/LICENSE.rst +++ /dev/null @@ -1,80 +0,0 @@ -License -------- - -Copyright (c) 2013-2018, Kim Davies. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -#. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -#. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided with - the distribution. - -#. Neither the name of the copyright holder nor the names of the - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -#. THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS "AS IS" AND ANY - EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE - USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH - DAMAGE. - -Portions of the codec implementation and unit tests are derived from the -Python standard library, which carries the `Python Software Foundation -License <https://docs.python.org/2/license.html>`_: - - Copyright (c) 2001-2014 Python Software Foundation; All Rights Reserved - -Portions of the unit tests are derived from the Unicode standard, which -is subject to the Unicode, Inc. License Agreement: - - Copyright (c) 1991-2014 Unicode, Inc. All rights reserved. - Distributed under the Terms of Use in - <http://www.unicode.org/copyright.html>. - - Permission is hereby granted, free of charge, to any person obtaining - a copy of the Unicode data files and any associated documentation - (the "Data Files") or Unicode software and any associated documentation - (the "Software") to deal in the Data Files or Software - without restriction, including without limitation the rights to use, - copy, modify, merge, publish, distribute, and/or sell copies of - the Data Files or Software, and to permit persons to whom the Data Files - or Software are furnished to do so, provided that - - (a) this copyright and permission notice appear with all copies - of the Data Files or Software, - - (b) this copyright and permission notice appear in associated - documentation, and - - (c) there is clear notice in each modified Data File or in the Software - as well as in the documentation associated with the Data File(s) or - Software that the data or software has been modified. - - THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF - ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE - WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT OF THIRD PARTY RIGHTS. - IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS - NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL - DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, - DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER - TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - PERFORMANCE OF THE DATA FILES OR SOFTWARE. - - Except as contained in this notice, the name of a copyright holder - shall not be used in advertising or otherwise to promote the sale, - use or other dealings in these Data Files or Software without prior - written authorization of the copyright holder. diff --git a/pipenv/patched/notpip/_vendor/idna/__init__.py b/pipenv/patched/notpip/_vendor/idna/__init__.py index 847bf935..a40eeafc 100644 --- a/pipenv/patched/notpip/_vendor/idna/__init__.py +++ b/pipenv/patched/notpip/_vendor/idna/__init__.py @@ -1,2 +1,44 @@ from .package_data import __version__ -from .core import * +from .core import ( + IDNABidiError, + IDNAError, + InvalidCodepoint, + InvalidCodepointContext, + alabel, + check_bidi, + check_hyphen_ok, + check_initial_combiner, + check_label, + check_nfc, + decode, + encode, + ulabel, + uts46_remap, + valid_contextj, + valid_contexto, + valid_label_length, + valid_string_length, +) +from .intranges import intranges_contain + +__all__ = [ + "IDNABidiError", + "IDNAError", + "InvalidCodepoint", + "InvalidCodepointContext", + "alabel", + "check_bidi", + "check_hyphen_ok", + "check_initial_combiner", + "check_label", + "check_nfc", + "decode", + "encode", + "intranges_contain", + "ulabel", + "uts46_remap", + "valid_contextj", + "valid_contexto", + "valid_label_length", + "valid_string_length", +] diff --git a/pipenv/patched/notpip/_vendor/idna/codec.py b/pipenv/patched/notpip/_vendor/idna/codec.py index 98c65ead..080f22a3 100644 --- a/pipenv/patched/notpip/_vendor/idna/codec.py +++ b/pipenv/patched/notpip/_vendor/idna/codec.py @@ -1,41 +1,43 @@ from .core import encode, decode, alabel, ulabel, IDNAError import codecs import re +from typing import Tuple, Optional -_unicode_dots_re = re.compile(u'[\u002e\u3002\uff0e\uff61]') +_unicode_dots_re = re.compile('[\u002e\u3002\uff0e\uff61]') class Codec(codecs.Codec): def encode(self, data, errors='strict'): - + # type: (str, str) -> Tuple[bytes, int] if errors != 'strict': - raise IDNAError("Unsupported error handling \"{0}\"".format(errors)) + raise IDNAError('Unsupported error handling \"{}\"'.format(errors)) if not data: - return "", 0 + return b"", 0 return encode(data), len(data) def decode(self, data, errors='strict'): - + # type: (bytes, str) -> Tuple[str, int] if errors != 'strict': - raise IDNAError("Unsupported error handling \"{0}\"".format(errors)) + raise IDNAError('Unsupported error handling \"{}\"'.format(errors)) if not data: - return u"", 0 + return '', 0 return decode(data), len(data) class IncrementalEncoder(codecs.BufferedIncrementalEncoder): - def _buffer_encode(self, data, errors, final): + def _buffer_encode(self, data, errors, final): # type: ignore + # type: (str, str, bool) -> Tuple[str, int] if errors != 'strict': - raise IDNAError("Unsupported error handling \"{0}\"".format(errors)) + raise IDNAError('Unsupported error handling \"{}\"'.format(errors)) if not data: - return ("", 0) + return "", 0 labels = _unicode_dots_re.split(data) - trailing_dot = u'' + trailing_dot = '' if labels: if not labels[-1]: trailing_dot = '.' @@ -55,37 +57,30 @@ class IncrementalEncoder(codecs.BufferedIncrementalEncoder): size += len(label) # Join with U+002E - result = ".".join(result) + trailing_dot + result_str = '.'.join(result) + trailing_dot # type: ignore size += len(trailing_dot) - return (result, size) + return result_str, size class IncrementalDecoder(codecs.BufferedIncrementalDecoder): - def _buffer_decode(self, data, errors, final): + def _buffer_decode(self, data, errors, final): # type: ignore + # type: (str, str, bool) -> Tuple[str, int] if errors != 'strict': - raise IDNAError("Unsupported error handling \"{0}\"".format(errors)) + raise IDNAError('Unsupported error handling \"{}\"'.format(errors)) if not data: - return (u"", 0) + return ('', 0) - # IDNA allows decoding to operate on Unicode strings, too. - if isinstance(data, unicode): - labels = _unicode_dots_re.split(data) - else: - # Must be ASCII string - data = str(data) - unicode(data, "ascii") - labels = data.split(".") - - trailing_dot = u'' + labels = _unicode_dots_re.split(data) + trailing_dot = '' if labels: if not labels[-1]: - trailing_dot = u'.' + trailing_dot = '.' del labels[-1] elif not final: # Keep potentially unfinished label until the next call del labels[-1] if labels: - trailing_dot = u'.' + trailing_dot = '.' result = [] size = 0 @@ -95,22 +90,26 @@ class IncrementalDecoder(codecs.BufferedIncrementalDecoder): size += 1 size += len(label) - result = u".".join(result) + trailing_dot + result_str = '.'.join(result) + trailing_dot size += len(trailing_dot) - return (result, size) + return (result_str, size) class StreamWriter(Codec, codecs.StreamWriter): pass + class StreamReader(Codec, codecs.StreamReader): pass + def getregentry(): + # type: () -> codecs.CodecInfo + # Compatibility as a search_function for codecs.register() return codecs.CodecInfo( name='idna', - encode=Codec().encode, - decode=Codec().decode, + encode=Codec().encode, # type: ignore + decode=Codec().decode, # type: ignore incrementalencoder=IncrementalEncoder, incrementaldecoder=IncrementalDecoder, streamwriter=StreamWriter, diff --git a/pipenv/patched/notpip/_vendor/idna/compat.py b/pipenv/patched/notpip/_vendor/idna/compat.py index 4d47f336..dc896c76 100644 --- a/pipenv/patched/notpip/_vendor/idna/compat.py +++ b/pipenv/patched/notpip/_vendor/idna/compat.py @@ -1,12 +1,16 @@ from .core import * from .codec import * +from typing import Any, Union def ToASCII(label): + # type: (str) -> bytes return encode(label) def ToUnicode(label): + # type: (Union[bytes, bytearray]) -> str return decode(label) def nameprep(s): - raise NotImplementedError("IDNA 2008 does not utilise nameprep protocol") + # type: (Any) -> None + raise NotImplementedError('IDNA 2008 does not utilise nameprep protocol') diff --git a/pipenv/patched/notpip/_vendor/idna/core.py b/pipenv/patched/notpip/_vendor/idna/core.py index 104624ad..d6051297 100644 --- a/pipenv/patched/notpip/_vendor/idna/core.py +++ b/pipenv/patched/notpip/_vendor/idna/core.py @@ -2,16 +2,12 @@ from . import idnadata import bisect import unicodedata import re -import sys +from typing import Union, Optional from .intranges import intranges_contain _virama_combining_class = 9 _alabel_prefix = b'xn--' -_unicode_dots_re = re.compile(u'[\u002e\u3002\uff0e\uff61]') - -if sys.version_info[0] == 3: - unicode = str - unichr = chr +_unicode_dots_re = re.compile('[\u002e\u3002\uff0e\uff61]') class IDNAError(UnicodeError): """ Base exception for all IDNA-encoding related problems """ @@ -34,45 +30,49 @@ class InvalidCodepointContext(IDNAError): def _combining_class(cp): - v = unicodedata.combining(unichr(cp)) + # type: (int) -> int + v = unicodedata.combining(chr(cp)) if v == 0: - if not unicodedata.name(unichr(cp)): - raise ValueError("Unknown character in unicodedata") + if not unicodedata.name(chr(cp)): + raise ValueError('Unknown character in unicodedata') return v def _is_script(cp, script): + # type: (str, str) -> bool return intranges_contain(ord(cp), idnadata.scripts[script]) def _punycode(s): + # type: (str) -> bytes return s.encode('punycode') def _unot(s): - return 'U+{0:04X}'.format(s) + # type: (int) -> str + return 'U+{:04X}'.format(s) def valid_label_length(label): - + # type: (Union[bytes, str]) -> bool if len(label) > 63: return False return True def valid_string_length(label, trailing_dot): - + # type: (Union[bytes, str], bool) -> bool if len(label) > (254 if trailing_dot else 253): return False return True def check_bidi(label, check_ltr=False): - + # type: (str, bool) -> bool # Bidi rules should only be applied if string contains RTL characters bidi_label = False for (idx, cp) in enumerate(label, 1): direction = unicodedata.bidirectional(cp) if direction == '': # String likely comes from a newer version of Unicode - raise IDNABidiError('Unknown directionality in label {0} at position {1}'.format(repr(label), idx)) + raise IDNABidiError('Unknown directionality in label {} at position {}'.format(repr(label), idx)) if direction in ['R', 'AL', 'AN']: bidi_label = True if not bidi_label and not check_ltr: @@ -85,17 +85,17 @@ def check_bidi(label, check_ltr=False): elif direction == 'L': rtl = False else: - raise IDNABidiError('First codepoint in label {0} must be directionality L, R or AL'.format(repr(label))) + raise IDNABidiError('First codepoint in label {} must be directionality L, R or AL'.format(repr(label))) valid_ending = False - number_type = False + number_type = None # type: Optional[str] for (idx, cp) in enumerate(label, 1): direction = unicodedata.bidirectional(cp) if rtl: # Bidi rule 2 if not direction in ['R', 'AL', 'AN', 'EN', 'ES', 'CS', 'ET', 'ON', 'BN', 'NSM']: - raise IDNABidiError('Invalid direction for codepoint at position {0} in a right-to-left label'.format(idx)) + raise IDNABidiError('Invalid direction for codepoint at position {} in a right-to-left label'.format(idx)) # Bidi rule 3 if direction in ['R', 'AL', 'EN', 'AN']: valid_ending = True @@ -111,7 +111,7 @@ def check_bidi(label, check_ltr=False): else: # Bidi rule 5 if not direction in ['L', 'EN', 'ES', 'CS', 'ET', 'ON', 'BN', 'NSM']: - raise IDNABidiError('Invalid direction for codepoint at position {0} in a left-to-right label'.format(idx)) + raise IDNABidiError('Invalid direction for codepoint at position {} in a left-to-right label'.format(idx)) # Bidi rule 6 if direction in ['L', 'EN']: valid_ending = True @@ -125,14 +125,14 @@ def check_bidi(label, check_ltr=False): def check_initial_combiner(label): - + # type: (str) -> bool if unicodedata.category(label[0])[0] == 'M': raise IDNAError('Label begins with an illegal combining character') return True def check_hyphen_ok(label): - + # type: (str) -> bool if label[2:4] == '--': raise IDNAError('Label has disallowed hyphens in 3rd and 4th position') if label[0] == '-' or label[-1] == '-': @@ -141,13 +141,13 @@ def check_hyphen_ok(label): def check_nfc(label): - + # type: (str) -> None if unicodedata.normalize('NFC', label) != label: raise IDNAError('Label must be in Normalization Form C') def valid_contextj(label, pos): - + # type: (str, int) -> bool cp_value = ord(label[pos]) if cp_value == 0x200c: @@ -191,7 +191,7 @@ def valid_contextj(label, pos): def valid_contexto(label, pos, exception=False): - + # type: (str, int, bool) -> bool cp_value = ord(label[pos]) if cp_value == 0x00b7: @@ -212,7 +212,7 @@ def valid_contexto(label, pos, exception=False): elif cp_value == 0x30fb: for cp in label: - if cp == u'\u30fb': + if cp == '\u30fb': continue if _is_script(cp, 'Hiragana') or _is_script(cp, 'Katakana') or _is_script(cp, 'Han'): return True @@ -230,9 +230,11 @@ def valid_contexto(label, pos, exception=False): return False return True + return False + def check_label(label): - + # type: (Union[str, bytes, bytearray]) -> None if isinstance(label, (bytes, bytearray)): label = label.decode('utf-8') if len(label) == 0: @@ -249,98 +251,109 @@ def check_label(label): elif intranges_contain(cp_value, idnadata.codepoint_classes['CONTEXTJ']): try: if not valid_contextj(label, pos): - raise InvalidCodepointContext('Joiner {0} not allowed at position {1} in {2}'.format( + raise InvalidCodepointContext('Joiner {} not allowed at position {} in {}'.format( _unot(cp_value), pos+1, repr(label))) except ValueError: - raise IDNAError('Unknown codepoint adjacent to joiner {0} at position {1} in {2}'.format( + raise IDNAError('Unknown codepoint adjacent to joiner {} at position {} in {}'.format( _unot(cp_value), pos+1, repr(label))) elif intranges_contain(cp_value, idnadata.codepoint_classes['CONTEXTO']): if not valid_contexto(label, pos): - raise InvalidCodepointContext('Codepoint {0} not allowed at position {1} in {2}'.format(_unot(cp_value), pos+1, repr(label))) + raise InvalidCodepointContext('Codepoint {} not allowed at position {} in {}'.format(_unot(cp_value), pos+1, repr(label))) else: - raise InvalidCodepoint('Codepoint {0} at position {1} of {2} not allowed'.format(_unot(cp_value), pos+1, repr(label))) + raise InvalidCodepoint('Codepoint {} at position {} of {} not allowed'.format(_unot(cp_value), pos+1, repr(label))) check_bidi(label) def alabel(label): - + # type: (str) -> bytes try: - label = label.encode('ascii') - ulabel(label) - if not valid_label_length(label): + label_bytes = label.encode('ascii') + ulabel(label_bytes) + if not valid_label_length(label_bytes): raise IDNAError('Label too long') - return label + return label_bytes except UnicodeEncodeError: pass if not label: raise IDNAError('No Input') - label = unicode(label) + label = str(label) check_label(label) - label = _punycode(label) - label = _alabel_prefix + label + label_bytes = _punycode(label) + label_bytes = _alabel_prefix + label_bytes - if not valid_label_length(label): + if not valid_label_length(label_bytes): raise IDNAError('Label too long') - return label + return label_bytes def ulabel(label): - + # type: (Union[str, bytes, bytearray]) -> str if not isinstance(label, (bytes, bytearray)): try: - label = label.encode('ascii') + label_bytes = label.encode('ascii') except UnicodeEncodeError: check_label(label) return label - - label = label.lower() - if label.startswith(_alabel_prefix): - label = label[len(_alabel_prefix):] else: - check_label(label) - return label.decode('ascii') + label_bytes = label - label = label.decode('punycode') + label_bytes = label_bytes.lower() + if label_bytes.startswith(_alabel_prefix): + label_bytes = label_bytes[len(_alabel_prefix):] + if not label_bytes: + raise IDNAError('Malformed A-label, no Punycode eligible content found') + if label_bytes.decode('ascii')[-1] == '-': + raise IDNAError('A-label must not end with a hyphen') + else: + check_label(label_bytes) + return label_bytes.decode('ascii') + + label = label_bytes.decode('punycode') check_label(label) return label def uts46_remap(domain, std3_rules=True, transitional=False): + # type: (str, bool, bool) -> str """Re-map the characters in the string according to UTS46 processing.""" from .uts46data import uts46data - output = u"" - try: - for pos, char in enumerate(domain): - code_point = ord(char) + output = '' + + for pos, char in enumerate(domain): + code_point = ord(char) + try: uts46row = uts46data[code_point if code_point < 256 else - bisect.bisect_left(uts46data, (code_point, "Z")) - 1] + bisect.bisect_left(uts46data, (code_point, 'Z')) - 1] status = uts46row[1] - replacement = uts46row[2] if len(uts46row) == 3 else None - if (status == "V" or - (status == "D" and not transitional) or - (status == "3" and not std3_rules and replacement is None)): + replacement = None # type: Optional[str] + if len(uts46row) == 3: + replacement = uts46row[2] # type: ignore + if (status == 'V' or + (status == 'D' and not transitional) or + (status == '3' and not std3_rules and replacement is None)): output += char - elif replacement is not None and (status == "M" or - (status == "3" and not std3_rules) or - (status == "D" and transitional)): + elif replacement is not None and (status == 'M' or + (status == '3' and not std3_rules) or + (status == 'D' and transitional)): output += replacement - elif status != "I": + elif status != 'I': raise IndexError() - return unicodedata.normalize("NFC", output) - except IndexError: - raise InvalidCodepoint( - "Codepoint {0} not allowed at position {1} in {2}".format( - _unot(code_point), pos + 1, repr(domain))) + except IndexError: + raise InvalidCodepoint( + 'Codepoint {} not allowed at position {} in {}'.format( + _unot(code_point), pos + 1, repr(domain))) + + return unicodedata.normalize('NFC', output) def encode(s, strict=False, uts46=False, std3_rules=False, transitional=False): - + # type: (Union[str, bytes, bytearray], bool, bool, bool, bool) -> bytes if isinstance(s, (bytes, bytearray)): - s = s.decode("ascii") + s = s.decode('ascii') if uts46: s = uts46_remap(s, std3_rules, transitional) trailing_dot = False @@ -369,9 +382,9 @@ def encode(s, strict=False, uts46=False, std3_rules=False, transitional=False): def decode(s, strict=False, uts46=False, std3_rules=False): - + # type: (Union[str, bytes, bytearray], bool, bool, bool) -> str if isinstance(s, (bytes, bytearray)): - s = s.decode("ascii") + s = s.decode('ascii') if uts46: s = uts46_remap(s, std3_rules, False) trailing_dot = False @@ -379,7 +392,7 @@ def decode(s, strict=False, uts46=False, std3_rules=False): if not strict: labels = _unicode_dots_re.split(s) else: - labels = s.split(u'.') + labels = s.split('.') if not labels or labels == ['']: raise IDNAError('Empty domain') if not labels[-1]: @@ -392,5 +405,5 @@ def decode(s, strict=False, uts46=False, std3_rules=False): else: raise IDNAError('Empty label') if trailing_dot: - result.append(u'') - return u'.'.join(result) + result.append('') + return '.'.join(result) diff --git a/pipenv/patched/notpip/_vendor/idna/idnadata.py b/pipenv/patched/notpip/_vendor/idna/idnadata.py index a80c959d..b86a3e06 100644 --- a/pipenv/patched/notpip/_vendor/idna/idnadata.py +++ b/pipenv/patched/notpip/_vendor/idna/idnadata.py @@ -1,6 +1,6 @@ # This file is automatically generated by tools/idna-data -__version__ = "11.0.0" +__version__ = '13.0.0' scripts = { 'Greek': ( 0x37000000374, @@ -48,16 +48,18 @@ scripts = { 0x300700003008, 0x30210000302a, 0x30380000303c, - 0x340000004db6, - 0x4e0000009ff0, + 0x340000004dc0, + 0x4e0000009ffd, 0xf9000000fa6e, 0xfa700000fada, - 0x200000002a6d7, + 0x16ff000016ff2, + 0x200000002a6de, 0x2a7000002b735, 0x2b7400002b81e, 0x2b8200002cea2, 0x2ceb00002ebe1, 0x2f8000002fa1e, + 0x300000003134b, ), 'Hebrew': ( 0x591000005c8, @@ -74,6 +76,7 @@ scripts = { 0x304100003097, 0x309d000030a0, 0x1b0010001b11f, + 0x1b1500001b153, 0x1f2000001f201, ), 'Katakana': ( @@ -85,6 +88,7 @@ scripts = { 0xff660000ff70, 0xff710000ff9e, 0x1b0000001b001, + 0x1b1640001b168, ), } joining_types = { @@ -387,9 +391,9 @@ joining_types = { 0x853: 68, 0x854: 82, 0x855: 68, - 0x856: 85, - 0x857: 85, - 0x858: 85, + 0x856: 82, + 0x857: 82, + 0x858: 82, 0x860: 68, 0x861: 85, 0x862: 68, @@ -430,6 +434,16 @@ joining_types = { 0x8bb: 68, 0x8bc: 68, 0x8bd: 68, + 0x8be: 68, + 0x8bf: 68, + 0x8c0: 68, + 0x8c1: 68, + 0x8c2: 68, + 0x8c3: 68, + 0x8c4: 68, + 0x8c5: 68, + 0x8c6: 68, + 0x8c7: 68, 0x8e2: 85, 0x1806: 85, 0x1807: 68, @@ -754,6 +768,34 @@ joining_types = { 0x10f52: 68, 0x10f53: 68, 0x10f54: 82, + 0x10fb0: 68, + 0x10fb1: 85, + 0x10fb2: 68, + 0x10fb3: 68, + 0x10fb4: 82, + 0x10fb5: 82, + 0x10fb6: 82, + 0x10fb7: 85, + 0x10fb8: 68, + 0x10fb9: 82, + 0x10fba: 82, + 0x10fbb: 68, + 0x10fbc: 68, + 0x10fbd: 82, + 0x10fbe: 68, + 0x10fbf: 68, + 0x10fc0: 85, + 0x10fc1: 68, + 0x10fc2: 82, + 0x10fc3: 82, + 0x10fc4: 68, + 0x10fc5: 85, + 0x10fc6: 85, + 0x10fc7: 85, + 0x10fc8: 85, + 0x10fc9: 82, + 0x10fca: 68, + 0x10fcb: 76, 0x110bd: 85, 0x110cd: 85, 0x1e900: 68, @@ -824,6 +866,7 @@ joining_types = { 0x1e941: 68, 0x1e942: 68, 0x1e943: 68, + 0x1e94b: 84, } codepoint_classes = { 'PVALID': ( @@ -1126,7 +1169,7 @@ codepoint_classes = { 0x8400000085c, 0x8600000086b, 0x8a0000008b5, - 0x8b6000008be, + 0x8b6000008c8, 0x8d3000008e2, 0x8e300000958, 0x96000000964, @@ -1185,7 +1228,7 @@ codepoint_classes = { 0xb3c00000b45, 0xb4700000b49, 0xb4b00000b4e, - 0xb5600000b58, + 0xb5500000b58, 0xb5f00000b64, 0xb6600000b70, 0xb7100000b72, @@ -1230,8 +1273,7 @@ codepoint_classes = { 0xce000000ce4, 0xce600000cf0, 0xcf100000cf3, - 0xd0000000d04, - 0xd0500000d0d, + 0xd0000000d0d, 0xd0e00000d11, 0xd1200000d45, 0xd4600000d49, @@ -1240,7 +1282,7 @@ codepoint_classes = { 0xd5f00000d64, 0xd6600000d70, 0xd7a00000d80, - 0xd8200000d84, + 0xd8100000d84, 0xd8500000d97, 0xd9a00000db2, 0xdb300000dbc, @@ -1258,18 +1300,11 @@ codepoint_classes = { 0xe5000000e5a, 0xe8100000e83, 0xe8400000e85, - 0xe8700000e89, - 0xe8a00000e8b, - 0xe8d00000e8e, - 0xe9400000e98, - 0xe9900000ea0, - 0xea100000ea4, + 0xe8600000e8b, + 0xe8c00000ea4, 0xea500000ea6, - 0xea700000ea8, - 0xeaa00000eac, - 0xead00000eb3, - 0xeb400000eba, - 0xebb00000ebe, + 0xea700000eb3, + 0xeb400000ebe, 0xec000000ec5, 0xec600000ec7, 0xec800000ece, @@ -1362,6 +1397,7 @@ codepoint_classes = { 0x1a9000001a9a, 0x1aa700001aa8, 0x1ab000001abe, + 0x1abf00001ac1, 0x1b0000001b4c, 0x1b5000001b5a, 0x1b6b00001b74, @@ -1370,7 +1406,7 @@ codepoint_classes = { 0x1c4000001c4a, 0x1c4d00001c7e, 0x1cd000001cd3, - 0x1cd400001cfa, + 0x1cd400001cfb, 0x1d0000001d2c, 0x1d2f00001d30, 0x1d3b00001d3c, @@ -1613,10 +1649,10 @@ codepoint_classes = { 0x30a1000030fb, 0x30fc000030ff, 0x310500003130, - 0x31a0000031bb, + 0x31a0000031c0, 0x31f000003200, - 0x340000004db6, - 0x4e0000009ff0, + 0x340000004dc0, + 0x4e0000009ffd, 0xa0000000a48d, 0xa4d00000a4fe, 0xa5000000a60d, @@ -1727,8 +1763,15 @@ codepoint_classes = { 0xa7b50000a7b6, 0xa7b70000a7b8, 0xa7b90000a7ba, - 0xa7f70000a7f8, + 0xa7bb0000a7bc, + 0xa7bd0000a7be, + 0xa7bf0000a7c0, + 0xa7c30000a7c4, + 0xa7c80000a7c9, + 0xa7ca0000a7cb, + 0xa7f60000a7f8, 0xa7fa0000a828, + 0xa82c0000a82d, 0xa8400000a874, 0xa8800000a8c6, 0xa8d00000a8da, @@ -1753,7 +1796,7 @@ codepoint_classes = { 0xab200000ab27, 0xab280000ab2f, 0xab300000ab5b, - 0xab600000ab66, + 0xab600000ab6a, 0xabc00000abeb, 0xabec0000abee, 0xabf00000abfa, @@ -1827,9 +1870,14 @@ codepoint_classes = { 0x10cc000010cf3, 0x10d0000010d28, 0x10d3000010d3a, + 0x10e8000010eaa, + 0x10eab00010ead, + 0x10eb000010eb2, 0x10f0000010f1d, 0x10f2700010f28, 0x10f3000010f51, + 0x10fb000010fc5, + 0x10fe000010ff7, 0x1100000011047, 0x1106600011070, 0x1107f000110bb, @@ -1837,12 +1885,12 @@ codepoint_classes = { 0x110f0000110fa, 0x1110000011135, 0x1113600011140, - 0x1114400011147, + 0x1114400011148, 0x1115000011174, 0x1117600011177, 0x11180000111c5, 0x111c9000111cd, - 0x111d0000111db, + 0x111ce000111db, 0x111dc000111dd, 0x1120000011212, 0x1121300011238, @@ -1871,7 +1919,7 @@ codepoint_classes = { 0x1137000011375, 0x114000001144b, 0x114500001145a, - 0x1145e0001145f, + 0x1145e00011462, 0x11480000114c6, 0x114c7000114c8, 0x114d0000114da, @@ -1881,18 +1929,28 @@ codepoint_classes = { 0x1160000011641, 0x1164400011645, 0x116500001165a, - 0x11680000116b8, + 0x11680000116b9, 0x116c0000116ca, 0x117000001171b, 0x1171d0001172c, 0x117300001173a, 0x118000001183b, 0x118c0000118ea, - 0x118ff00011900, + 0x118ff00011907, + 0x119090001190a, + 0x1190c00011914, + 0x1191500011917, + 0x1191800011936, + 0x1193700011939, + 0x1193b00011944, + 0x119500001195a, + 0x119a0000119a8, + 0x119aa000119d8, + 0x119da000119e2, + 0x119e3000119e5, 0x11a0000011a3f, 0x11a4700011a48, - 0x11a5000011a84, - 0x11a8600011a9a, + 0x11a5000011a9a, 0x11a9d00011a9e, 0x11ac000011af9, 0x11c0000011c09, @@ -1916,6 +1974,7 @@ codepoint_classes = { 0x11d9300011d99, 0x11da000011daa, 0x11ee000011ef7, + 0x11fb000011fb1, 0x120000001239a, 0x1248000012544, 0x130000001342f, @@ -1931,13 +1990,18 @@ codepoint_classes = { 0x16b6300016b78, 0x16b7d00016b90, 0x16e6000016e80, - 0x16f0000016f45, - 0x16f5000016f7f, + 0x16f0000016f4b, + 0x16f4f00016f88, 0x16f8f00016fa0, 0x16fe000016fe2, - 0x17000000187f2, - 0x1880000018af3, + 0x16fe300016fe5, + 0x16ff000016ff2, + 0x17000000187f8, + 0x1880000018cd6, + 0x18d0000018d09, 0x1b0000001b11f, + 0x1b1500001b153, + 0x1b1640001b168, 0x1b1700001b2fc, 0x1bc000001bc6b, 0x1bc700001bc7d, @@ -1955,15 +2019,22 @@ codepoint_classes = { 0x1e01b0001e022, 0x1e0230001e025, 0x1e0260001e02b, + 0x1e1000001e12d, + 0x1e1300001e13e, + 0x1e1400001e14a, + 0x1e14e0001e14f, + 0x1e2c00001e2fa, 0x1e8000001e8c5, 0x1e8d00001e8d7, - 0x1e9220001e94b, + 0x1e9220001e94c, 0x1e9500001e95a, - 0x200000002a6d7, + 0x1fbf00001fbfa, + 0x200000002a6de, 0x2a7000002b735, 0x2b7400002b81e, 0x2b8200002cea2, 0x2ceb00002ebe1, + 0x300000003134b, ), 'CONTEXTJ': ( 0x200c0000200e, diff --git a/pipenv/patched/notpip/_vendor/idna/intranges.py b/pipenv/patched/notpip/_vendor/idna/intranges.py index fa8a7356..ee364a90 100644 --- a/pipenv/patched/notpip/_vendor/idna/intranges.py +++ b/pipenv/patched/notpip/_vendor/idna/intranges.py @@ -6,8 +6,10 @@ in the original list?" in time O(log(# runs)). """ import bisect +from typing import List, Tuple def intranges_from_list(list_): + # type: (List[int]) -> Tuple[int, ...] """Represent a list of integers as a sequence of ranges: ((start_0, end_0), (start_1, end_1), ...), such that the original integers are exactly those x such that start_i <= x < end_i for some i. @@ -29,13 +31,16 @@ def intranges_from_list(list_): return tuple(ranges) def _encode_range(start, end): + # type: (int, int) -> int return (start << 32) | end def _decode_range(r): + # type: (int) -> Tuple[int, int] return (r >> 32), (r & ((1 << 32) - 1)) def intranges_contain(int_, ranges): + # type: (int, Tuple[int, ...]) -> bool """Determine if `int_` falls into one of the ranges in `ranges`.""" tuple_ = _encode_range(int_, 0) pos = bisect.bisect_left(ranges, tuple_) diff --git a/pipenv/patched/notpip/_vendor/idna/package_data.py b/pipenv/patched/notpip/_vendor/idna/package_data.py index 257e8989..e096d1d5 100644 --- a/pipenv/patched/notpip/_vendor/idna/package_data.py +++ b/pipenv/patched/notpip/_vendor/idna/package_data.py @@ -1,2 +1,2 @@ -__version__ = '2.8' +__version__ = '3.2' diff --git a/pipenv/patched/notpip/_vendor/idna/uts46data.py b/pipenv/patched/notpip/_vendor/idna/uts46data.py index a68ed4c0..f382ce38 100644 --- a/pipenv/patched/notpip/_vendor/idna/uts46data.py +++ b/pipenv/patched/notpip/_vendor/idna/uts46data.py @@ -1,11 +1,13 @@ # This file is automatically generated by tools/idna-data -# vim: set fileencoding=utf-8 : + +from typing import List, Tuple, Union """IDNA Mapping Table from UTS46.""" -__version__ = "11.0.0" +__version__ = '13.0.0' def _seg_0(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ (0x0, '3'), (0x1, '3'), @@ -72,32 +74,32 @@ def _seg_0(): (0x3E, '3'), (0x3F, '3'), (0x40, '3'), - (0x41, 'M', u'a'), - (0x42, 'M', u'b'), - (0x43, 'M', u'c'), - (0x44, 'M', u'd'), - (0x45, 'M', u'e'), - (0x46, 'M', u'f'), - (0x47, 'M', u'g'), - (0x48, 'M', u'h'), - (0x49, 'M', u'i'), - (0x4A, 'M', u'j'), - (0x4B, 'M', u'k'), - (0x4C, 'M', u'l'), - (0x4D, 'M', u'm'), - (0x4E, 'M', u'n'), - (0x4F, 'M', u'o'), - (0x50, 'M', u'p'), - (0x51, 'M', u'q'), - (0x52, 'M', u'r'), - (0x53, 'M', u's'), - (0x54, 'M', u't'), - (0x55, 'M', u'u'), - (0x56, 'M', u'v'), - (0x57, 'M', u'w'), - (0x58, 'M', u'x'), - (0x59, 'M', u'y'), - (0x5A, 'M', u'z'), + (0x41, 'M', 'a'), + (0x42, 'M', 'b'), + (0x43, 'M', 'c'), + (0x44, 'M', 'd'), + (0x45, 'M', 'e'), + (0x46, 'M', 'f'), + (0x47, 'M', 'g'), + (0x48, 'M', 'h'), + (0x49, 'M', 'i'), + (0x4A, 'M', 'j'), + (0x4B, 'M', 'k'), + (0x4C, 'M', 'l'), + (0x4D, 'M', 'm'), + (0x4E, 'M', 'n'), + (0x4F, 'M', 'o'), + (0x50, 'M', 'p'), + (0x51, 'M', 'q'), + (0x52, 'M', 'r'), + (0x53, 'M', 's'), + (0x54, 'M', 't'), + (0x55, 'M', 'u'), + (0x56, 'M', 'v'), + (0x57, 'M', 'w'), + (0x58, 'M', 'x'), + (0x59, 'M', 'y'), + (0x5A, 'M', 'z'), (0x5B, '3'), (0x5C, '3'), (0x5D, '3'), @@ -110,6 +112,7 @@ def _seg_0(): ] def _seg_1(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ (0x64, 'V'), (0x65, 'V'), @@ -171,7 +174,7 @@ def _seg_1(): (0x9D, 'X'), (0x9E, 'X'), (0x9F, 'X'), - (0xA0, '3', u' '), + (0xA0, '3', ' '), (0xA1, 'V'), (0xA2, 'V'), (0xA3, 'V'), @@ -179,66 +182,67 @@ def _seg_1(): (0xA5, 'V'), (0xA6, 'V'), (0xA7, 'V'), - (0xA8, '3', u' ̈'), + (0xA8, '3', ' ̈'), (0xA9, 'V'), - (0xAA, 'M', u'a'), + (0xAA, 'M', 'a'), (0xAB, 'V'), (0xAC, 'V'), (0xAD, 'I'), (0xAE, 'V'), - (0xAF, '3', u' ̄'), + (0xAF, '3', ' ̄'), (0xB0, 'V'), (0xB1, 'V'), - (0xB2, 'M', u'2'), - (0xB3, 'M', u'3'), - (0xB4, '3', u' ́'), - (0xB5, 'M', u'μ'), + (0xB2, 'M', '2'), + (0xB3, 'M', '3'), + (0xB4, '3', ' ́'), + (0xB5, 'M', 'μ'), (0xB6, 'V'), (0xB7, 'V'), - (0xB8, '3', u' ̧'), - (0xB9, 'M', u'1'), - (0xBA, 'M', u'o'), + (0xB8, '3', ' ̧'), + (0xB9, 'M', '1'), + (0xBA, 'M', 'o'), (0xBB, 'V'), - (0xBC, 'M', u'1⁄4'), - (0xBD, 'M', u'1⁄2'), - (0xBE, 'M', u'3⁄4'), + (0xBC, 'M', '1⁄4'), + (0xBD, 'M', '1⁄2'), + (0xBE, 'M', '3⁄4'), (0xBF, 'V'), - (0xC0, 'M', u'à'), - (0xC1, 'M', u'á'), - (0xC2, 'M', u'â'), - (0xC3, 'M', u'ã'), - (0xC4, 'M', u'ä'), - (0xC5, 'M', u'å'), - (0xC6, 'M', u'æ'), - (0xC7, 'M', u'ç'), + (0xC0, 'M', 'à'), + (0xC1, 'M', 'á'), + (0xC2, 'M', 'â'), + (0xC3, 'M', 'ã'), + (0xC4, 'M', 'ä'), + (0xC5, 'M', 'å'), + (0xC6, 'M', 'æ'), + (0xC7, 'M', 'ç'), ] def _seg_2(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0xC8, 'M', u'è'), - (0xC9, 'M', u'é'), - (0xCA, 'M', u'ê'), - (0xCB, 'M', u'ë'), - (0xCC, 'M', u'ì'), - (0xCD, 'M', u'í'), - (0xCE, 'M', u'î'), - (0xCF, 'M', u'ï'), - (0xD0, 'M', u'ð'), - (0xD1, 'M', u'ñ'), - (0xD2, 'M', u'ò'), - (0xD3, 'M', u'ó'), - (0xD4, 'M', u'ô'), - (0xD5, 'M', u'õ'), - (0xD6, 'M', u'ö'), + (0xC8, 'M', 'è'), + (0xC9, 'M', 'é'), + (0xCA, 'M', 'ê'), + (0xCB, 'M', 'ë'), + (0xCC, 'M', 'ì'), + (0xCD, 'M', 'í'), + (0xCE, 'M', 'î'), + (0xCF, 'M', 'ï'), + (0xD0, 'M', 'ð'), + (0xD1, 'M', 'ñ'), + (0xD2, 'M', 'ò'), + (0xD3, 'M', 'ó'), + (0xD4, 'M', 'ô'), + (0xD5, 'M', 'õ'), + (0xD6, 'M', 'ö'), (0xD7, 'V'), - (0xD8, 'M', u'ø'), - (0xD9, 'M', u'ù'), - (0xDA, 'M', u'ú'), - (0xDB, 'M', u'û'), - (0xDC, 'M', u'ü'), - (0xDD, 'M', u'ý'), - (0xDE, 'M', u'þ'), - (0xDF, 'D', u'ss'), + (0xD8, 'M', 'ø'), + (0xD9, 'M', 'ù'), + (0xDA, 'M', 'ú'), + (0xDB, 'M', 'û'), + (0xDC, 'M', 'ü'), + (0xDD, 'M', 'ý'), + (0xDE, 'M', 'þ'), + (0xDF, 'D', 'ss'), (0xE0, 'V'), (0xE1, 'V'), (0xE2, 'V'), @@ -271,765 +275,772 @@ def _seg_2(): (0xFD, 'V'), (0xFE, 'V'), (0xFF, 'V'), - (0x100, 'M', u'ā'), + (0x100, 'M', 'ā'), (0x101, 'V'), - (0x102, 'M', u'ă'), + (0x102, 'M', 'ă'), (0x103, 'V'), - (0x104, 'M', u'ą'), + (0x104, 'M', 'ą'), (0x105, 'V'), - (0x106, 'M', u'ć'), + (0x106, 'M', 'ć'), (0x107, 'V'), - (0x108, 'M', u'ĉ'), + (0x108, 'M', 'ĉ'), (0x109, 'V'), - (0x10A, 'M', u'ċ'), + (0x10A, 'M', 'ċ'), (0x10B, 'V'), - (0x10C, 'M', u'č'), + (0x10C, 'M', 'č'), (0x10D, 'V'), - (0x10E, 'M', u'ď'), + (0x10E, 'M', 'ď'), (0x10F, 'V'), - (0x110, 'M', u'đ'), + (0x110, 'M', 'đ'), (0x111, 'V'), - (0x112, 'M', u'ē'), + (0x112, 'M', 'ē'), (0x113, 'V'), - (0x114, 'M', u'ĕ'), + (0x114, 'M', 'ĕ'), (0x115, 'V'), - (0x116, 'M', u'ė'), + (0x116, 'M', 'ė'), (0x117, 'V'), - (0x118, 'M', u'ę'), + (0x118, 'M', 'ę'), (0x119, 'V'), - (0x11A, 'M', u'ě'), + (0x11A, 'M', 'ě'), (0x11B, 'V'), - (0x11C, 'M', u'ĝ'), + (0x11C, 'M', 'ĝ'), (0x11D, 'V'), - (0x11E, 'M', u'ğ'), + (0x11E, 'M', 'ğ'), (0x11F, 'V'), - (0x120, 'M', u'ġ'), + (0x120, 'M', 'ġ'), (0x121, 'V'), - (0x122, 'M', u'ģ'), + (0x122, 'M', 'ģ'), (0x123, 'V'), - (0x124, 'M', u'ĥ'), + (0x124, 'M', 'ĥ'), (0x125, 'V'), - (0x126, 'M', u'ħ'), + (0x126, 'M', 'ħ'), (0x127, 'V'), - (0x128, 'M', u'ĩ'), + (0x128, 'M', 'ĩ'), (0x129, 'V'), - (0x12A, 'M', u'ī'), + (0x12A, 'M', 'ī'), (0x12B, 'V'), ] def _seg_3(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x12C, 'M', u'ĭ'), + (0x12C, 'M', 'ĭ'), (0x12D, 'V'), - (0x12E, 'M', u'į'), + (0x12E, 'M', 'į'), (0x12F, 'V'), - (0x130, 'M', u'i̇'), + (0x130, 'M', 'i̇'), (0x131, 'V'), - (0x132, 'M', u'ij'), - (0x134, 'M', u'ĵ'), + (0x132, 'M', 'ij'), + (0x134, 'M', 'ĵ'), (0x135, 'V'), - (0x136, 'M', u'ķ'), + (0x136, 'M', 'ķ'), (0x137, 'V'), - (0x139, 'M', u'ĺ'), + (0x139, 'M', 'ĺ'), (0x13A, 'V'), - (0x13B, 'M', u'ļ'), + (0x13B, 'M', 'ļ'), (0x13C, 'V'), - (0x13D, 'M', u'ľ'), + (0x13D, 'M', 'ľ'), (0x13E, 'V'), - (0x13F, 'M', u'l·'), - (0x141, 'M', u'ł'), + (0x13F, 'M', 'l·'), + (0x141, 'M', 'ł'), (0x142, 'V'), - (0x143, 'M', u'ń'), + (0x143, 'M', 'ń'), (0x144, 'V'), - (0x145, 'M', u'ņ'), + (0x145, 'M', 'ņ'), (0x146, 'V'), - (0x147, 'M', u'ň'), + (0x147, 'M', 'ň'), (0x148, 'V'), - (0x149, 'M', u'ʼn'), - (0x14A, 'M', u'ŋ'), + (0x149, 'M', 'ʼn'), + (0x14A, 'M', 'ŋ'), (0x14B, 'V'), - (0x14C, 'M', u'ō'), + (0x14C, 'M', 'ō'), (0x14D, 'V'), - (0x14E, 'M', u'ŏ'), + (0x14E, 'M', 'ŏ'), (0x14F, 'V'), - (0x150, 'M', u'ő'), + (0x150, 'M', 'ő'), (0x151, 'V'), - (0x152, 'M', u'œ'), + (0x152, 'M', 'œ'), (0x153, 'V'), - (0x154, 'M', u'ŕ'), + (0x154, 'M', 'ŕ'), (0x155, 'V'), - (0x156, 'M', u'ŗ'), + (0x156, 'M', 'ŗ'), (0x157, 'V'), - (0x158, 'M', u'ř'), + (0x158, 'M', 'ř'), (0x159, 'V'), - (0x15A, 'M', u'ś'), + (0x15A, 'M', 'ś'), (0x15B, 'V'), - (0x15C, 'M', u'ŝ'), + (0x15C, 'M', 'ŝ'), (0x15D, 'V'), - (0x15E, 'M', u'ş'), + (0x15E, 'M', 'ş'), (0x15F, 'V'), - (0x160, 'M', u'š'), + (0x160, 'M', 'š'), (0x161, 'V'), - (0x162, 'M', u'ţ'), + (0x162, 'M', 'ţ'), (0x163, 'V'), - (0x164, 'M', u'ť'), + (0x164, 'M', 'ť'), (0x165, 'V'), - (0x166, 'M', u'ŧ'), + (0x166, 'M', 'ŧ'), (0x167, 'V'), - (0x168, 'M', u'ũ'), + (0x168, 'M', 'ũ'), (0x169, 'V'), - (0x16A, 'M', u'ū'), + (0x16A, 'M', 'ū'), (0x16B, 'V'), - (0x16C, 'M', u'ŭ'), + (0x16C, 'M', 'ŭ'), (0x16D, 'V'), - (0x16E, 'M', u'ů'), + (0x16E, 'M', 'ů'), (0x16F, 'V'), - (0x170, 'M', u'ű'), + (0x170, 'M', 'ű'), (0x171, 'V'), - (0x172, 'M', u'ų'), + (0x172, 'M', 'ų'), (0x173, 'V'), - (0x174, 'M', u'ŵ'), + (0x174, 'M', 'ŵ'), (0x175, 'V'), - (0x176, 'M', u'ŷ'), + (0x176, 'M', 'ŷ'), (0x177, 'V'), - (0x178, 'M', u'ÿ'), - (0x179, 'M', u'ź'), + (0x178, 'M', 'ÿ'), + (0x179, 'M', 'ź'), (0x17A, 'V'), - (0x17B, 'M', u'ż'), + (0x17B, 'M', 'ż'), (0x17C, 'V'), - (0x17D, 'M', u'ž'), + (0x17D, 'M', 'ž'), (0x17E, 'V'), - (0x17F, 'M', u's'), + (0x17F, 'M', 's'), (0x180, 'V'), - (0x181, 'M', u'ɓ'), - (0x182, 'M', u'ƃ'), + (0x181, 'M', 'ɓ'), + (0x182, 'M', 'ƃ'), (0x183, 'V'), - (0x184, 'M', u'ƅ'), + (0x184, 'M', 'ƅ'), (0x185, 'V'), - (0x186, 'M', u'ɔ'), - (0x187, 'M', u'ƈ'), + (0x186, 'M', 'ɔ'), + (0x187, 'M', 'ƈ'), (0x188, 'V'), - (0x189, 'M', u'ɖ'), - (0x18A, 'M', u'ɗ'), - (0x18B, 'M', u'ƌ'), + (0x189, 'M', 'ɖ'), + (0x18A, 'M', 'ɗ'), + (0x18B, 'M', 'ƌ'), (0x18C, 'V'), - (0x18E, 'M', u'ǝ'), - (0x18F, 'M', u'ə'), - (0x190, 'M', u'ɛ'), - (0x191, 'M', u'ƒ'), + (0x18E, 'M', 'ǝ'), + (0x18F, 'M', 'ə'), + (0x190, 'M', 'ɛ'), + (0x191, 'M', 'ƒ'), (0x192, 'V'), - (0x193, 'M', u'ɠ'), + (0x193, 'M', 'ɠ'), ] def _seg_4(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x194, 'M', u'ɣ'), + (0x194, 'M', 'ɣ'), (0x195, 'V'), - (0x196, 'M', u'ɩ'), - (0x197, 'M', u'ɨ'), - (0x198, 'M', u'ƙ'), + (0x196, 'M', 'ɩ'), + (0x197, 'M', 'ɨ'), + (0x198, 'M', 'ƙ'), (0x199, 'V'), - (0x19C, 'M', u'ɯ'), - (0x19D, 'M', u'ɲ'), + (0x19C, 'M', 'ɯ'), + (0x19D, 'M', 'ɲ'), (0x19E, 'V'), - (0x19F, 'M', u'ɵ'), - (0x1A0, 'M', u'ơ'), + (0x19F, 'M', 'ɵ'), + (0x1A0, 'M', 'ơ'), (0x1A1, 'V'), - (0x1A2, 'M', u'ƣ'), + (0x1A2, 'M', 'ƣ'), (0x1A3, 'V'), - (0x1A4, 'M', u'ƥ'), + (0x1A4, 'M', 'ƥ'), (0x1A5, 'V'), - (0x1A6, 'M', u'ʀ'), - (0x1A7, 'M', u'ƨ'), + (0x1A6, 'M', 'ʀ'), + (0x1A7, 'M', 'ƨ'), (0x1A8, 'V'), - (0x1A9, 'M', u'ʃ'), + (0x1A9, 'M', 'ʃ'), (0x1AA, 'V'), - (0x1AC, 'M', u'ƭ'), + (0x1AC, 'M', 'ƭ'), (0x1AD, 'V'), - (0x1AE, 'M', u'ʈ'), - (0x1AF, 'M', u'ư'), + (0x1AE, 'M', 'ʈ'), + (0x1AF, 'M', 'ư'), (0x1B0, 'V'), - (0x1B1, 'M', u'ʊ'), - (0x1B2, 'M', u'ʋ'), - (0x1B3, 'M', u'ƴ'), + (0x1B1, 'M', 'ʊ'), + (0x1B2, 'M', 'ʋ'), + (0x1B3, 'M', 'ƴ'), (0x1B4, 'V'), - (0x1B5, 'M', u'ƶ'), + (0x1B5, 'M', 'ƶ'), (0x1B6, 'V'), - (0x1B7, 'M', u'ʒ'), - (0x1B8, 'M', u'ƹ'), + (0x1B7, 'M', 'ʒ'), + (0x1B8, 'M', 'ƹ'), (0x1B9, 'V'), - (0x1BC, 'M', u'ƽ'), + (0x1BC, 'M', 'ƽ'), (0x1BD, 'V'), - (0x1C4, 'M', u'dž'), - (0x1C7, 'M', u'lj'), - (0x1CA, 'M', u'nj'), - (0x1CD, 'M', u'ǎ'), + (0x1C4, 'M', 'dž'), + (0x1C7, 'M', 'lj'), + (0x1CA, 'M', 'nj'), + (0x1CD, 'M', 'ǎ'), (0x1CE, 'V'), - (0x1CF, 'M', u'ǐ'), + (0x1CF, 'M', 'ǐ'), (0x1D0, 'V'), - (0x1D1, 'M', u'ǒ'), + (0x1D1, 'M', 'ǒ'), (0x1D2, 'V'), - (0x1D3, 'M', u'ǔ'), + (0x1D3, 'M', 'ǔ'), (0x1D4, 'V'), - (0x1D5, 'M', u'ǖ'), + (0x1D5, 'M', 'ǖ'), (0x1D6, 'V'), - (0x1D7, 'M', u'ǘ'), + (0x1D7, 'M', 'ǘ'), (0x1D8, 'V'), - (0x1D9, 'M', u'ǚ'), + (0x1D9, 'M', 'ǚ'), (0x1DA, 'V'), - (0x1DB, 'M', u'ǜ'), + (0x1DB, 'M', 'ǜ'), (0x1DC, 'V'), - (0x1DE, 'M', u'ǟ'), + (0x1DE, 'M', 'ǟ'), (0x1DF, 'V'), - (0x1E0, 'M', u'ǡ'), + (0x1E0, 'M', 'ǡ'), (0x1E1, 'V'), - (0x1E2, 'M', u'ǣ'), + (0x1E2, 'M', 'ǣ'), (0x1E3, 'V'), - (0x1E4, 'M', u'ǥ'), + (0x1E4, 'M', 'ǥ'), (0x1E5, 'V'), - (0x1E6, 'M', u'ǧ'), + (0x1E6, 'M', 'ǧ'), (0x1E7, 'V'), - (0x1E8, 'M', u'ǩ'), + (0x1E8, 'M', 'ǩ'), (0x1E9, 'V'), - (0x1EA, 'M', u'ǫ'), + (0x1EA, 'M', 'ǫ'), (0x1EB, 'V'), - (0x1EC, 'M', u'ǭ'), + (0x1EC, 'M', 'ǭ'), (0x1ED, 'V'), - (0x1EE, 'M', u'ǯ'), + (0x1EE, 'M', 'ǯ'), (0x1EF, 'V'), - (0x1F1, 'M', u'dz'), - (0x1F4, 'M', u'ǵ'), + (0x1F1, 'M', 'dz'), + (0x1F4, 'M', 'ǵ'), (0x1F5, 'V'), - (0x1F6, 'M', u'ƕ'), - (0x1F7, 'M', u'ƿ'), - (0x1F8, 'M', u'ǹ'), + (0x1F6, 'M', 'ƕ'), + (0x1F7, 'M', 'ƿ'), + (0x1F8, 'M', 'ǹ'), (0x1F9, 'V'), - (0x1FA, 'M', u'ǻ'), + (0x1FA, 'M', 'ǻ'), (0x1FB, 'V'), - (0x1FC, 'M', u'ǽ'), + (0x1FC, 'M', 'ǽ'), (0x1FD, 'V'), - (0x1FE, 'M', u'ǿ'), + (0x1FE, 'M', 'ǿ'), (0x1FF, 'V'), - (0x200, 'M', u'ȁ'), + (0x200, 'M', 'ȁ'), (0x201, 'V'), - (0x202, 'M', u'ȃ'), + (0x202, 'M', 'ȃ'), (0x203, 'V'), - (0x204, 'M', u'ȅ'), + (0x204, 'M', 'ȅ'), (0x205, 'V'), - (0x206, 'M', u'ȇ'), + (0x206, 'M', 'ȇ'), (0x207, 'V'), - (0x208, 'M', u'ȉ'), + (0x208, 'M', 'ȉ'), (0x209, 'V'), - (0x20A, 'M', u'ȋ'), + (0x20A, 'M', 'ȋ'), (0x20B, 'V'), - (0x20C, 'M', u'ȍ'), + (0x20C, 'M', 'ȍ'), ] def _seg_5(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ (0x20D, 'V'), - (0x20E, 'M', u'ȏ'), + (0x20E, 'M', 'ȏ'), (0x20F, 'V'), - (0x210, 'M', u'ȑ'), + (0x210, 'M', 'ȑ'), (0x211, 'V'), - (0x212, 'M', u'ȓ'), + (0x212, 'M', 'ȓ'), (0x213, 'V'), - (0x214, 'M', u'ȕ'), + (0x214, 'M', 'ȕ'), (0x215, 'V'), - (0x216, 'M', u'ȗ'), + (0x216, 'M', 'ȗ'), (0x217, 'V'), - (0x218, 'M', u'ș'), + (0x218, 'M', 'ș'), (0x219, 'V'), - (0x21A, 'M', u'ț'), + (0x21A, 'M', 'ț'), (0x21B, 'V'), - (0x21C, 'M', u'ȝ'), + (0x21C, 'M', 'ȝ'), (0x21D, 'V'), - (0x21E, 'M', u'ȟ'), + (0x21E, 'M', 'ȟ'), (0x21F, 'V'), - (0x220, 'M', u'ƞ'), + (0x220, 'M', 'ƞ'), (0x221, 'V'), - (0x222, 'M', u'ȣ'), + (0x222, 'M', 'ȣ'), (0x223, 'V'), - (0x224, 'M', u'ȥ'), + (0x224, 'M', 'ȥ'), (0x225, 'V'), - (0x226, 'M', u'ȧ'), + (0x226, 'M', 'ȧ'), (0x227, 'V'), - (0x228, 'M', u'ȩ'), + (0x228, 'M', 'ȩ'), (0x229, 'V'), - (0x22A, 'M', u'ȫ'), + (0x22A, 'M', 'ȫ'), (0x22B, 'V'), - (0x22C, 'M', u'ȭ'), + (0x22C, 'M', 'ȭ'), (0x22D, 'V'), - (0x22E, 'M', u'ȯ'), + (0x22E, 'M', 'ȯ'), (0x22F, 'V'), - (0x230, 'M', u'ȱ'), + (0x230, 'M', 'ȱ'), (0x231, 'V'), - (0x232, 'M', u'ȳ'), + (0x232, 'M', 'ȳ'), (0x233, 'V'), - (0x23A, 'M', u'ⱥ'), - (0x23B, 'M', u'ȼ'), + (0x23A, 'M', 'ⱥ'), + (0x23B, 'M', 'ȼ'), (0x23C, 'V'), - (0x23D, 'M', u'ƚ'), - (0x23E, 'M', u'ⱦ'), + (0x23D, 'M', 'ƚ'), + (0x23E, 'M', 'ⱦ'), (0x23F, 'V'), - (0x241, 'M', u'ɂ'), + (0x241, 'M', 'ɂ'), (0x242, 'V'), - (0x243, 'M', u'ƀ'), - (0x244, 'M', u'ʉ'), - (0x245, 'M', u'ʌ'), - (0x246, 'M', u'ɇ'), + (0x243, 'M', 'ƀ'), + (0x244, 'M', 'ʉ'), + (0x245, 'M', 'ʌ'), + (0x246, 'M', 'ɇ'), (0x247, 'V'), - (0x248, 'M', u'ɉ'), + (0x248, 'M', 'ɉ'), (0x249, 'V'), - (0x24A, 'M', u'ɋ'), + (0x24A, 'M', 'ɋ'), (0x24B, 'V'), - (0x24C, 'M', u'ɍ'), + (0x24C, 'M', 'ɍ'), (0x24D, 'V'), - (0x24E, 'M', u'ɏ'), + (0x24E, 'M', 'ɏ'), (0x24F, 'V'), - (0x2B0, 'M', u'h'), - (0x2B1, 'M', u'ɦ'), - (0x2B2, 'M', u'j'), - (0x2B3, 'M', u'r'), - (0x2B4, 'M', u'ɹ'), - (0x2B5, 'M', u'ɻ'), - (0x2B6, 'M', u'ʁ'), - (0x2B7, 'M', u'w'), - (0x2B8, 'M', u'y'), + (0x2B0, 'M', 'h'), + (0x2B1, 'M', 'ɦ'), + (0x2B2, 'M', 'j'), + (0x2B3, 'M', 'r'), + (0x2B4, 'M', 'ɹ'), + (0x2B5, 'M', 'ɻ'), + (0x2B6, 'M', 'ʁ'), + (0x2B7, 'M', 'w'), + (0x2B8, 'M', 'y'), (0x2B9, 'V'), - (0x2D8, '3', u' ̆'), - (0x2D9, '3', u' ̇'), - (0x2DA, '3', u' ̊'), - (0x2DB, '3', u' ̨'), - (0x2DC, '3', u' ̃'), - (0x2DD, '3', u' ̋'), + (0x2D8, '3', ' ̆'), + (0x2D9, '3', ' ̇'), + (0x2DA, '3', ' ̊'), + (0x2DB, '3', ' ̨'), + (0x2DC, '3', ' ̃'), + (0x2DD, '3', ' ̋'), (0x2DE, 'V'), - (0x2E0, 'M', u'ɣ'), - (0x2E1, 'M', u'l'), - (0x2E2, 'M', u's'), - (0x2E3, 'M', u'x'), - (0x2E4, 'M', u'ʕ'), + (0x2E0, 'M', 'ɣ'), + (0x2E1, 'M', 'l'), + (0x2E2, 'M', 's'), + (0x2E3, 'M', 'x'), + (0x2E4, 'M', 'ʕ'), (0x2E5, 'V'), - (0x340, 'M', u'̀'), - (0x341, 'M', u'́'), + (0x340, 'M', '̀'), + (0x341, 'M', '́'), (0x342, 'V'), - (0x343, 'M', u'̓'), - (0x344, 'M', u'̈́'), - (0x345, 'M', u'ι'), + (0x343, 'M', '̓'), + (0x344, 'M', '̈́'), + (0x345, 'M', 'ι'), (0x346, 'V'), (0x34F, 'I'), (0x350, 'V'), - (0x370, 'M', u'ͱ'), + (0x370, 'M', 'ͱ'), (0x371, 'V'), - (0x372, 'M', u'ͳ'), + (0x372, 'M', 'ͳ'), (0x373, 'V'), - (0x374, 'M', u'ʹ'), + (0x374, 'M', 'ʹ'), (0x375, 'V'), - (0x376, 'M', u'ͷ'), + (0x376, 'M', 'ͷ'), (0x377, 'V'), ] def _seg_6(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ (0x378, 'X'), - (0x37A, '3', u' ι'), + (0x37A, '3', ' ι'), (0x37B, 'V'), - (0x37E, '3', u';'), - (0x37F, 'M', u'ϳ'), + (0x37E, '3', ';'), + (0x37F, 'M', 'ϳ'), (0x380, 'X'), - (0x384, '3', u' ́'), - (0x385, '3', u' ̈́'), - (0x386, 'M', u'ά'), - (0x387, 'M', u'·'), - (0x388, 'M', u'έ'), - (0x389, 'M', u'ή'), - (0x38A, 'M', u'ί'), + (0x384, '3', ' ́'), + (0x385, '3', ' ̈́'), + (0x386, 'M', 'ά'), + (0x387, 'M', '·'), + (0x388, 'M', 'έ'), + (0x389, 'M', 'ή'), + (0x38A, 'M', 'ί'), (0x38B, 'X'), - (0x38C, 'M', u'ό'), + (0x38C, 'M', 'ό'), (0x38D, 'X'), - (0x38E, 'M', u'ύ'), - (0x38F, 'M', u'ώ'), + (0x38E, 'M', 'ύ'), + (0x38F, 'M', 'ώ'), (0x390, 'V'), - (0x391, 'M', u'α'), - (0x392, 'M', u'β'), - (0x393, 'M', u'γ'), - (0x394, 'M', u'δ'), - (0x395, 'M', u'ε'), - (0x396, 'M', u'ζ'), - (0x397, 'M', u'η'), - (0x398, 'M', u'θ'), - (0x399, 'M', u'ι'), - (0x39A, 'M', u'κ'), - (0x39B, 'M', u'λ'), - (0x39C, 'M', u'μ'), - (0x39D, 'M', u'ν'), - (0x39E, 'M', u'ξ'), - (0x39F, 'M', u'ο'), - (0x3A0, 'M', u'π'), - (0x3A1, 'M', u'ρ'), + (0x391, 'M', 'α'), + (0x392, 'M', 'β'), + (0x393, 'M', 'γ'), + (0x394, 'M', 'δ'), + (0x395, 'M', 'ε'), + (0x396, 'M', 'ζ'), + (0x397, 'M', 'η'), + (0x398, 'M', 'θ'), + (0x399, 'M', 'ι'), + (0x39A, 'M', 'κ'), + (0x39B, 'M', 'λ'), + (0x39C, 'M', 'μ'), + (0x39D, 'M', 'ν'), + (0x39E, 'M', 'ξ'), + (0x39F, 'M', 'ο'), + (0x3A0, 'M', 'π'), + (0x3A1, 'M', 'ρ'), (0x3A2, 'X'), - (0x3A3, 'M', u'σ'), - (0x3A4, 'M', u'τ'), - (0x3A5, 'M', u'υ'), - (0x3A6, 'M', u'φ'), - (0x3A7, 'M', u'χ'), - (0x3A8, 'M', u'ψ'), - (0x3A9, 'M', u'ω'), - (0x3AA, 'M', u'ϊ'), - (0x3AB, 'M', u'ϋ'), + (0x3A3, 'M', 'σ'), + (0x3A4, 'M', 'τ'), + (0x3A5, 'M', 'υ'), + (0x3A6, 'M', 'φ'), + (0x3A7, 'M', 'χ'), + (0x3A8, 'M', 'ψ'), + (0x3A9, 'M', 'ω'), + (0x3AA, 'M', 'ϊ'), + (0x3AB, 'M', 'ϋ'), (0x3AC, 'V'), - (0x3C2, 'D', u'σ'), + (0x3C2, 'D', 'σ'), (0x3C3, 'V'), - (0x3CF, 'M', u'ϗ'), - (0x3D0, 'M', u'β'), - (0x3D1, 'M', u'θ'), - (0x3D2, 'M', u'υ'), - (0x3D3, 'M', u'ύ'), - (0x3D4, 'M', u'ϋ'), - (0x3D5, 'M', u'φ'), - (0x3D6, 'M', u'π'), + (0x3CF, 'M', 'ϗ'), + (0x3D0, 'M', 'β'), + (0x3D1, 'M', 'θ'), + (0x3D2, 'M', 'υ'), + (0x3D3, 'M', 'ύ'), + (0x3D4, 'M', 'ϋ'), + (0x3D5, 'M', 'φ'), + (0x3D6, 'M', 'π'), (0x3D7, 'V'), - (0x3D8, 'M', u'ϙ'), + (0x3D8, 'M', 'ϙ'), (0x3D9, 'V'), - (0x3DA, 'M', u'ϛ'), + (0x3DA, 'M', 'ϛ'), (0x3DB, 'V'), - (0x3DC, 'M', u'ϝ'), + (0x3DC, 'M', 'ϝ'), (0x3DD, 'V'), - (0x3DE, 'M', u'ϟ'), + (0x3DE, 'M', 'ϟ'), (0x3DF, 'V'), - (0x3E0, 'M', u'ϡ'), + (0x3E0, 'M', 'ϡ'), (0x3E1, 'V'), - (0x3E2, 'M', u'ϣ'), + (0x3E2, 'M', 'ϣ'), (0x3E3, 'V'), - (0x3E4, 'M', u'ϥ'), + (0x3E4, 'M', 'ϥ'), (0x3E5, 'V'), - (0x3E6, 'M', u'ϧ'), + (0x3E6, 'M', 'ϧ'), (0x3E7, 'V'), - (0x3E8, 'M', u'ϩ'), + (0x3E8, 'M', 'ϩ'), (0x3E9, 'V'), - (0x3EA, 'M', u'ϫ'), + (0x3EA, 'M', 'ϫ'), (0x3EB, 'V'), - (0x3EC, 'M', u'ϭ'), + (0x3EC, 'M', 'ϭ'), (0x3ED, 'V'), - (0x3EE, 'M', u'ϯ'), + (0x3EE, 'M', 'ϯ'), (0x3EF, 'V'), - (0x3F0, 'M', u'κ'), - (0x3F1, 'M', u'ρ'), - (0x3F2, 'M', u'σ'), + (0x3F0, 'M', 'κ'), + (0x3F1, 'M', 'ρ'), + (0x3F2, 'M', 'σ'), (0x3F3, 'V'), - (0x3F4, 'M', u'θ'), - (0x3F5, 'M', u'ε'), + (0x3F4, 'M', 'θ'), + (0x3F5, 'M', 'ε'), (0x3F6, 'V'), - (0x3F7, 'M', u'ϸ'), + (0x3F7, 'M', 'ϸ'), (0x3F8, 'V'), - (0x3F9, 'M', u'σ'), - (0x3FA, 'M', u'ϻ'), + (0x3F9, 'M', 'σ'), + (0x3FA, 'M', 'ϻ'), (0x3FB, 'V'), - (0x3FD, 'M', u'ͻ'), - (0x3FE, 'M', u'ͼ'), - (0x3FF, 'M', u'ͽ'), - (0x400, 'M', u'ѐ'), - (0x401, 'M', u'ё'), - (0x402, 'M', u'ђ'), + (0x3FD, 'M', 'ͻ'), + (0x3FE, 'M', 'ͼ'), + (0x3FF, 'M', 'ͽ'), + (0x400, 'M', 'ѐ'), + (0x401, 'M', 'ё'), + (0x402, 'M', 'ђ'), ] def _seg_7(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x403, 'M', u'ѓ'), - (0x404, 'M', u'є'), - (0x405, 'M', u'ѕ'), - (0x406, 'M', u'і'), - (0x407, 'M', u'ї'), - (0x408, 'M', u'ј'), - (0x409, 'M', u'љ'), - (0x40A, 'M', u'њ'), - (0x40B, 'M', u'ћ'), - (0x40C, 'M', u'ќ'), - (0x40D, 'M', u'ѝ'), - (0x40E, 'M', u'ў'), - (0x40F, 'M', u'џ'), - (0x410, 'M', u'а'), - (0x411, 'M', u'б'), - (0x412, 'M', u'в'), - (0x413, 'M', u'г'), - (0x414, 'M', u'д'), - (0x415, 'M', u'е'), - (0x416, 'M', u'ж'), - (0x417, 'M', u'з'), - (0x418, 'M', u'и'), - (0x419, 'M', u'й'), - (0x41A, 'M', u'к'), - (0x41B, 'M', u'л'), - (0x41C, 'M', u'м'), - (0x41D, 'M', u'н'), - (0x41E, 'M', u'о'), - (0x41F, 'M', u'п'), - (0x420, 'M', u'р'), - (0x421, 'M', u'с'), - (0x422, 'M', u'т'), - (0x423, 'M', u'у'), - (0x424, 'M', u'ф'), - (0x425, 'M', u'х'), - (0x426, 'M', u'ц'), - (0x427, 'M', u'ч'), - (0x428, 'M', u'ш'), - (0x429, 'M', u'щ'), - (0x42A, 'M', u'ъ'), - (0x42B, 'M', u'ы'), - (0x42C, 'M', u'ь'), - (0x42D, 'M', u'э'), - (0x42E, 'M', u'ю'), - (0x42F, 'M', u'я'), + (0x403, 'M', 'ѓ'), + (0x404, 'M', 'є'), + (0x405, 'M', 'ѕ'), + (0x406, 'M', 'і'), + (0x407, 'M', 'ї'), + (0x408, 'M', 'ј'), + (0x409, 'M', 'љ'), + (0x40A, 'M', 'њ'), + (0x40B, 'M', 'ћ'), + (0x40C, 'M', 'ќ'), + (0x40D, 'M', 'ѝ'), + (0x40E, 'M', 'ў'), + (0x40F, 'M', 'џ'), + (0x410, 'M', 'а'), + (0x411, 'M', 'б'), + (0x412, 'M', 'в'), + (0x413, 'M', 'г'), + (0x414, 'M', 'д'), + (0x415, 'M', 'е'), + (0x416, 'M', 'ж'), + (0x417, 'M', 'з'), + (0x418, 'M', 'и'), + (0x419, 'M', 'й'), + (0x41A, 'M', 'к'), + (0x41B, 'M', 'л'), + (0x41C, 'M', 'м'), + (0x41D, 'M', 'н'), + (0x41E, 'M', 'о'), + (0x41F, 'M', 'п'), + (0x420, 'M', 'р'), + (0x421, 'M', 'с'), + (0x422, 'M', 'т'), + (0x423, 'M', 'у'), + (0x424, 'M', 'ф'), + (0x425, 'M', 'х'), + (0x426, 'M', 'ц'), + (0x427, 'M', 'ч'), + (0x428, 'M', 'ш'), + (0x429, 'M', 'щ'), + (0x42A, 'M', 'ъ'), + (0x42B, 'M', 'ы'), + (0x42C, 'M', 'ь'), + (0x42D, 'M', 'э'), + (0x42E, 'M', 'ю'), + (0x42F, 'M', 'я'), (0x430, 'V'), - (0x460, 'M', u'ѡ'), + (0x460, 'M', 'ѡ'), (0x461, 'V'), - (0x462, 'M', u'ѣ'), + (0x462, 'M', 'ѣ'), (0x463, 'V'), - (0x464, 'M', u'ѥ'), + (0x464, 'M', 'ѥ'), (0x465, 'V'), - (0x466, 'M', u'ѧ'), + (0x466, 'M', 'ѧ'), (0x467, 'V'), - (0x468, 'M', u'ѩ'), + (0x468, 'M', 'ѩ'), (0x469, 'V'), - (0x46A, 'M', u'ѫ'), + (0x46A, 'M', 'ѫ'), (0x46B, 'V'), - (0x46C, 'M', u'ѭ'), + (0x46C, 'M', 'ѭ'), (0x46D, 'V'), - (0x46E, 'M', u'ѯ'), + (0x46E, 'M', 'ѯ'), (0x46F, 'V'), - (0x470, 'M', u'ѱ'), + (0x470, 'M', 'ѱ'), (0x471, 'V'), - (0x472, 'M', u'ѳ'), + (0x472, 'M', 'ѳ'), (0x473, 'V'), - (0x474, 'M', u'ѵ'), + (0x474, 'M', 'ѵ'), (0x475, 'V'), - (0x476, 'M', u'ѷ'), + (0x476, 'M', 'ѷ'), (0x477, 'V'), - (0x478, 'M', u'ѹ'), + (0x478, 'M', 'ѹ'), (0x479, 'V'), - (0x47A, 'M', u'ѻ'), + (0x47A, 'M', 'ѻ'), (0x47B, 'V'), - (0x47C, 'M', u'ѽ'), + (0x47C, 'M', 'ѽ'), (0x47D, 'V'), - (0x47E, 'M', u'ѿ'), + (0x47E, 'M', 'ѿ'), (0x47F, 'V'), - (0x480, 'M', u'ҁ'), + (0x480, 'M', 'ҁ'), (0x481, 'V'), - (0x48A, 'M', u'ҋ'), + (0x48A, 'M', 'ҋ'), (0x48B, 'V'), - (0x48C, 'M', u'ҍ'), + (0x48C, 'M', 'ҍ'), (0x48D, 'V'), - (0x48E, 'M', u'ҏ'), + (0x48E, 'M', 'ҏ'), (0x48F, 'V'), - (0x490, 'M', u'ґ'), + (0x490, 'M', 'ґ'), (0x491, 'V'), - (0x492, 'M', u'ғ'), + (0x492, 'M', 'ғ'), (0x493, 'V'), - (0x494, 'M', u'ҕ'), + (0x494, 'M', 'ҕ'), (0x495, 'V'), - (0x496, 'M', u'җ'), + (0x496, 'M', 'җ'), (0x497, 'V'), - (0x498, 'M', u'ҙ'), + (0x498, 'M', 'ҙ'), (0x499, 'V'), - (0x49A, 'M', u'қ'), + (0x49A, 'M', 'қ'), (0x49B, 'V'), - (0x49C, 'M', u'ҝ'), + (0x49C, 'M', 'ҝ'), (0x49D, 'V'), ] def _seg_8(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x49E, 'M', u'ҟ'), + (0x49E, 'M', 'ҟ'), (0x49F, 'V'), - (0x4A0, 'M', u'ҡ'), + (0x4A0, 'M', 'ҡ'), (0x4A1, 'V'), - (0x4A2, 'M', u'ң'), + (0x4A2, 'M', 'ң'), (0x4A3, 'V'), - (0x4A4, 'M', u'ҥ'), + (0x4A4, 'M', 'ҥ'), (0x4A5, 'V'), - (0x4A6, 'M', u'ҧ'), + (0x4A6, 'M', 'ҧ'), (0x4A7, 'V'), - (0x4A8, 'M', u'ҩ'), + (0x4A8, 'M', 'ҩ'), (0x4A9, 'V'), - (0x4AA, 'M', u'ҫ'), + (0x4AA, 'M', 'ҫ'), (0x4AB, 'V'), - (0x4AC, 'M', u'ҭ'), + (0x4AC, 'M', 'ҭ'), (0x4AD, 'V'), - (0x4AE, 'M', u'ү'), + (0x4AE, 'M', 'ү'), (0x4AF, 'V'), - (0x4B0, 'M', u'ұ'), + (0x4B0, 'M', 'ұ'), (0x4B1, 'V'), - (0x4B2, 'M', u'ҳ'), + (0x4B2, 'M', 'ҳ'), (0x4B3, 'V'), - (0x4B4, 'M', u'ҵ'), + (0x4B4, 'M', 'ҵ'), (0x4B5, 'V'), - (0x4B6, 'M', u'ҷ'), + (0x4B6, 'M', 'ҷ'), (0x4B7, 'V'), - (0x4B8, 'M', u'ҹ'), + (0x4B8, 'M', 'ҹ'), (0x4B9, 'V'), - (0x4BA, 'M', u'һ'), + (0x4BA, 'M', 'һ'), (0x4BB, 'V'), - (0x4BC, 'M', u'ҽ'), + (0x4BC, 'M', 'ҽ'), (0x4BD, 'V'), - (0x4BE, 'M', u'ҿ'), + (0x4BE, 'M', 'ҿ'), (0x4BF, 'V'), (0x4C0, 'X'), - (0x4C1, 'M', u'ӂ'), + (0x4C1, 'M', 'ӂ'), (0x4C2, 'V'), - (0x4C3, 'M', u'ӄ'), + (0x4C3, 'M', 'ӄ'), (0x4C4, 'V'), - (0x4C5, 'M', u'ӆ'), + (0x4C5, 'M', 'ӆ'), (0x4C6, 'V'), - (0x4C7, 'M', u'ӈ'), + (0x4C7, 'M', 'ӈ'), (0x4C8, 'V'), - (0x4C9, 'M', u'ӊ'), + (0x4C9, 'M', 'ӊ'), (0x4CA, 'V'), - (0x4CB, 'M', u'ӌ'), + (0x4CB, 'M', 'ӌ'), (0x4CC, 'V'), - (0x4CD, 'M', u'ӎ'), + (0x4CD, 'M', 'ӎ'), (0x4CE, 'V'), - (0x4D0, 'M', u'ӑ'), + (0x4D0, 'M', 'ӑ'), (0x4D1, 'V'), - (0x4D2, 'M', u'ӓ'), + (0x4D2, 'M', 'ӓ'), (0x4D3, 'V'), - (0x4D4, 'M', u'ӕ'), + (0x4D4, 'M', 'ӕ'), (0x4D5, 'V'), - (0x4D6, 'M', u'ӗ'), + (0x4D6, 'M', 'ӗ'), (0x4D7, 'V'), - (0x4D8, 'M', u'ә'), + (0x4D8, 'M', 'ә'), (0x4D9, 'V'), - (0x4DA, 'M', u'ӛ'), + (0x4DA, 'M', 'ӛ'), (0x4DB, 'V'), - (0x4DC, 'M', u'ӝ'), + (0x4DC, 'M', 'ӝ'), (0x4DD, 'V'), - (0x4DE, 'M', u'ӟ'), + (0x4DE, 'M', 'ӟ'), (0x4DF, 'V'), - (0x4E0, 'M', u'ӡ'), + (0x4E0, 'M', 'ӡ'), (0x4E1, 'V'), - (0x4E2, 'M', u'ӣ'), + (0x4E2, 'M', 'ӣ'), (0x4E3, 'V'), - (0x4E4, 'M', u'ӥ'), + (0x4E4, 'M', 'ӥ'), (0x4E5, 'V'), - (0x4E6, 'M', u'ӧ'), + (0x4E6, 'M', 'ӧ'), (0x4E7, 'V'), - (0x4E8, 'M', u'ө'), + (0x4E8, 'M', 'ө'), (0x4E9, 'V'), - (0x4EA, 'M', u'ӫ'), + (0x4EA, 'M', 'ӫ'), (0x4EB, 'V'), - (0x4EC, 'M', u'ӭ'), + (0x4EC, 'M', 'ӭ'), (0x4ED, 'V'), - (0x4EE, 'M', u'ӯ'), + (0x4EE, 'M', 'ӯ'), (0x4EF, 'V'), - (0x4F0, 'M', u'ӱ'), + (0x4F0, 'M', 'ӱ'), (0x4F1, 'V'), - (0x4F2, 'M', u'ӳ'), + (0x4F2, 'M', 'ӳ'), (0x4F3, 'V'), - (0x4F4, 'M', u'ӵ'), + (0x4F4, 'M', 'ӵ'), (0x4F5, 'V'), - (0x4F6, 'M', u'ӷ'), + (0x4F6, 'M', 'ӷ'), (0x4F7, 'V'), - (0x4F8, 'M', u'ӹ'), + (0x4F8, 'M', 'ӹ'), (0x4F9, 'V'), - (0x4FA, 'M', u'ӻ'), + (0x4FA, 'M', 'ӻ'), (0x4FB, 'V'), - (0x4FC, 'M', u'ӽ'), + (0x4FC, 'M', 'ӽ'), (0x4FD, 'V'), - (0x4FE, 'M', u'ӿ'), + (0x4FE, 'M', 'ӿ'), (0x4FF, 'V'), - (0x500, 'M', u'ԁ'), + (0x500, 'M', 'ԁ'), (0x501, 'V'), - (0x502, 'M', u'ԃ'), + (0x502, 'M', 'ԃ'), ] def _seg_9(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ (0x503, 'V'), - (0x504, 'M', u'ԅ'), + (0x504, 'M', 'ԅ'), (0x505, 'V'), - (0x506, 'M', u'ԇ'), + (0x506, 'M', 'ԇ'), (0x507, 'V'), - (0x508, 'M', u'ԉ'), + (0x508, 'M', 'ԉ'), (0x509, 'V'), - (0x50A, 'M', u'ԋ'), + (0x50A, 'M', 'ԋ'), (0x50B, 'V'), - (0x50C, 'M', u'ԍ'), + (0x50C, 'M', 'ԍ'), (0x50D, 'V'), - (0x50E, 'M', u'ԏ'), + (0x50E, 'M', 'ԏ'), (0x50F, 'V'), - (0x510, 'M', u'ԑ'), + (0x510, 'M', 'ԑ'), (0x511, 'V'), - (0x512, 'M', u'ԓ'), + (0x512, 'M', 'ԓ'), (0x513, 'V'), - (0x514, 'M', u'ԕ'), + (0x514, 'M', 'ԕ'), (0x515, 'V'), - (0x516, 'M', u'ԗ'), + (0x516, 'M', 'ԗ'), (0x517, 'V'), - (0x518, 'M', u'ԙ'), + (0x518, 'M', 'ԙ'), (0x519, 'V'), - (0x51A, 'M', u'ԛ'), + (0x51A, 'M', 'ԛ'), (0x51B, 'V'), - (0x51C, 'M', u'ԝ'), + (0x51C, 'M', 'ԝ'), (0x51D, 'V'), - (0x51E, 'M', u'ԟ'), + (0x51E, 'M', 'ԟ'), (0x51F, 'V'), - (0x520, 'M', u'ԡ'), + (0x520, 'M', 'ԡ'), (0x521, 'V'), - (0x522, 'M', u'ԣ'), + (0x522, 'M', 'ԣ'), (0x523, 'V'), - (0x524, 'M', u'ԥ'), + (0x524, 'M', 'ԥ'), (0x525, 'V'), - (0x526, 'M', u'ԧ'), + (0x526, 'M', 'ԧ'), (0x527, 'V'), - (0x528, 'M', u'ԩ'), + (0x528, 'M', 'ԩ'), (0x529, 'V'), - (0x52A, 'M', u'ԫ'), + (0x52A, 'M', 'ԫ'), (0x52B, 'V'), - (0x52C, 'M', u'ԭ'), + (0x52C, 'M', 'ԭ'), (0x52D, 'V'), - (0x52E, 'M', u'ԯ'), + (0x52E, 'M', 'ԯ'), (0x52F, 'V'), (0x530, 'X'), - (0x531, 'M', u'ա'), - (0x532, 'M', u'բ'), - (0x533, 'M', u'գ'), - (0x534, 'M', u'դ'), - (0x535, 'M', u'ե'), - (0x536, 'M', u'զ'), - (0x537, 'M', u'է'), - (0x538, 'M', u'ը'), - (0x539, 'M', u'թ'), - (0x53A, 'M', u'ժ'), - (0x53B, 'M', u'ի'), - (0x53C, 'M', u'լ'), - (0x53D, 'M', u'խ'), - (0x53E, 'M', u'ծ'), - (0x53F, 'M', u'կ'), - (0x540, 'M', u'հ'), - (0x541, 'M', u'ձ'), - (0x542, 'M', u'ղ'), - (0x543, 'M', u'ճ'), - (0x544, 'M', u'մ'), - (0x545, 'M', u'յ'), - (0x546, 'M', u'ն'), - (0x547, 'M', u'շ'), - (0x548, 'M', u'ո'), - (0x549, 'M', u'չ'), - (0x54A, 'M', u'պ'), - (0x54B, 'M', u'ջ'), - (0x54C, 'M', u'ռ'), - (0x54D, 'M', u'ս'), - (0x54E, 'M', u'վ'), - (0x54F, 'M', u'տ'), - (0x550, 'M', u'ր'), - (0x551, 'M', u'ց'), - (0x552, 'M', u'ւ'), - (0x553, 'M', u'փ'), - (0x554, 'M', u'ք'), - (0x555, 'M', u'օ'), - (0x556, 'M', u'ֆ'), + (0x531, 'M', 'ա'), + (0x532, 'M', 'բ'), + (0x533, 'M', 'գ'), + (0x534, 'M', 'դ'), + (0x535, 'M', 'ե'), + (0x536, 'M', 'զ'), + (0x537, 'M', 'է'), + (0x538, 'M', 'ը'), + (0x539, 'M', 'թ'), + (0x53A, 'M', 'ժ'), + (0x53B, 'M', 'ի'), + (0x53C, 'M', 'լ'), + (0x53D, 'M', 'խ'), + (0x53E, 'M', 'ծ'), + (0x53F, 'M', 'կ'), + (0x540, 'M', 'հ'), + (0x541, 'M', 'ձ'), + (0x542, 'M', 'ղ'), + (0x543, 'M', 'ճ'), + (0x544, 'M', 'մ'), + (0x545, 'M', 'յ'), + (0x546, 'M', 'ն'), + (0x547, 'M', 'շ'), + (0x548, 'M', 'ո'), + (0x549, 'M', 'չ'), + (0x54A, 'M', 'պ'), + (0x54B, 'M', 'ջ'), + (0x54C, 'M', 'ռ'), + (0x54D, 'M', 'ս'), + (0x54E, 'M', 'վ'), + (0x54F, 'M', 'տ'), + (0x550, 'M', 'ր'), + (0x551, 'M', 'ց'), + (0x552, 'M', 'ւ'), + (0x553, 'M', 'փ'), + (0x554, 'M', 'ք'), + (0x555, 'M', 'օ'), + (0x556, 'M', 'ֆ'), (0x557, 'X'), (0x559, 'V'), - (0x587, 'M', u'եւ'), + (0x587, 'M', 'եւ'), (0x588, 'V'), (0x58B, 'X'), (0x58D, 'V'), @@ -1046,11 +1057,12 @@ def _seg_9(): ] def _seg_10(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x675, 'M', u'اٴ'), - (0x676, 'M', u'وٴ'), - (0x677, 'M', u'ۇٴ'), - (0x678, 'M', u'يٴ'), + (0x675, 'M', 'اٴ'), + (0x676, 'M', 'وٴ'), + (0x677, 'M', 'ۇٴ'), + (0x678, 'M', 'يٴ'), (0x679, 'V'), (0x6DD, 'X'), (0x6DE, 'V'), @@ -1074,18 +1086,18 @@ def _seg_10(): (0x8A0, 'V'), (0x8B5, 'X'), (0x8B6, 'V'), - (0x8BE, 'X'), + (0x8C8, 'X'), (0x8D3, 'V'), (0x8E2, 'X'), (0x8E3, 'V'), - (0x958, 'M', u'क़'), - (0x959, 'M', u'ख़'), - (0x95A, 'M', u'ग़'), - (0x95B, 'M', u'ज़'), - (0x95C, 'M', u'ड़'), - (0x95D, 'M', u'ढ़'), - (0x95E, 'M', u'फ़'), - (0x95F, 'M', u'य़'), + (0x958, 'M', 'क़'), + (0x959, 'M', 'ख़'), + (0x95A, 'M', 'ग़'), + (0x95B, 'M', 'ज़'), + (0x95C, 'M', 'ड़'), + (0x95D, 'M', 'ढ़'), + (0x95E, 'M', 'फ़'), + (0x95F, 'M', 'य़'), (0x960, 'V'), (0x984, 'X'), (0x985, 'V'), @@ -1108,10 +1120,10 @@ def _seg_10(): (0x9CF, 'X'), (0x9D7, 'V'), (0x9D8, 'X'), - (0x9DC, 'M', u'ড়'), - (0x9DD, 'M', u'ঢ়'), + (0x9DC, 'M', 'ড়'), + (0x9DD, 'M', 'ঢ়'), (0x9DE, 'X'), - (0x9DF, 'M', u'য়'), + (0x9DF, 'M', 'য়'), (0x9E0, 'V'), (0x9E4, 'X'), (0x9E6, 'V'), @@ -1127,10 +1139,10 @@ def _seg_10(): (0xA2A, 'V'), (0xA31, 'X'), (0xA32, 'V'), - (0xA33, 'M', u'ਲ਼'), + (0xA33, 'M', 'ਲ਼'), (0xA34, 'X'), (0xA35, 'V'), - (0xA36, 'M', u'ਸ਼'), + (0xA36, 'M', 'ਸ਼'), (0xA37, 'X'), (0xA38, 'V'), (0xA3A, 'X'), @@ -1144,16 +1156,17 @@ def _seg_10(): (0xA4E, 'X'), (0xA51, 'V'), (0xA52, 'X'), - (0xA59, 'M', u'ਖ਼'), - (0xA5A, 'M', u'ਗ਼'), - (0xA5B, 'M', u'ਜ਼'), + (0xA59, 'M', 'ਖ਼'), + (0xA5A, 'M', 'ਗ਼'), + (0xA5B, 'M', 'ਜ਼'), ] def _seg_11(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ (0xA5C, 'V'), (0xA5D, 'X'), - (0xA5E, 'M', u'ਫ਼'), + (0xA5E, 'M', 'ਫ਼'), (0xA5F, 'X'), (0xA66, 'V'), (0xA77, 'X'), @@ -1205,10 +1218,10 @@ def _seg_11(): (0xB49, 'X'), (0xB4B, 'V'), (0xB4E, 'X'), - (0xB56, 'V'), + (0xB55, 'V'), (0xB58, 'X'), - (0xB5C, 'M', u'ଡ଼'), - (0xB5D, 'M', u'ଢ଼'), + (0xB5C, 'M', 'ଡ଼'), + (0xB5D, 'M', 'ଢ଼'), (0xB5E, 'X'), (0xB5F, 'V'), (0xB64, 'X'), @@ -1254,6 +1267,7 @@ def _seg_11(): ] def _seg_12(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ (0xC29, 'X'), (0xC2A, 'V'), @@ -1272,7 +1286,7 @@ def _seg_12(): (0xC64, 'X'), (0xC66, 'V'), (0xC70, 'X'), - (0xC78, 'V'), + (0xC77, 'V'), (0xC8D, 'X'), (0xC8E, 'V'), (0xC91, 'X'), @@ -1299,8 +1313,6 @@ def _seg_12(): (0xCF1, 'V'), (0xCF3, 'X'), (0xD00, 'V'), - (0xD04, 'X'), - (0xD05, 'V'), (0xD0D, 'X'), (0xD0E, 'V'), (0xD11, 'X'), @@ -1314,7 +1326,7 @@ def _seg_12(): (0xD64, 'X'), (0xD66, 'V'), (0xD80, 'X'), - (0xD82, 'V'), + (0xD81, 'V'), (0xD84, 'X'), (0xD85, 'V'), (0xD97, 'X'), @@ -1339,7 +1351,7 @@ def _seg_12(): (0xDF2, 'V'), (0xDF5, 'X'), (0xE01, 'V'), - (0xE33, 'M', u'ํา'), + (0xE33, 'M', 'ํา'), (0xE34, 'V'), (0xE3B, 'X'), (0xE3F, 'V'), @@ -1348,33 +1360,20 @@ def _seg_12(): (0xE83, 'X'), (0xE84, 'V'), (0xE85, 'X'), - (0xE87, 'V'), - (0xE89, 'X'), - (0xE8A, 'V'), + (0xE86, 'V'), (0xE8B, 'X'), - (0xE8D, 'V'), - (0xE8E, 'X'), - (0xE94, 'V'), - ] - -def _seg_13(): - return [ - (0xE98, 'X'), - (0xE99, 'V'), - (0xEA0, 'X'), - (0xEA1, 'V'), + (0xE8C, 'V'), (0xEA4, 'X'), (0xEA5, 'V'), (0xEA6, 'X'), (0xEA7, 'V'), - (0xEA8, 'X'), - (0xEAA, 'V'), - (0xEAC, 'X'), - (0xEAD, 'V'), - (0xEB3, 'M', u'ໍາ'), + (0xEB3, 'M', 'ໍາ'), (0xEB4, 'V'), - (0xEBA, 'X'), - (0xEBB, 'V'), + ] + +def _seg_13(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ (0xEBE, 'X'), (0xEC0, 'V'), (0xEC5, 'X'), @@ -1384,52 +1383,52 @@ def _seg_13(): (0xECE, 'X'), (0xED0, 'V'), (0xEDA, 'X'), - (0xEDC, 'M', u'ຫນ'), - (0xEDD, 'M', u'ຫມ'), + (0xEDC, 'M', 'ຫນ'), + (0xEDD, 'M', 'ຫມ'), (0xEDE, 'V'), (0xEE0, 'X'), (0xF00, 'V'), - (0xF0C, 'M', u'་'), + (0xF0C, 'M', '་'), (0xF0D, 'V'), - (0xF43, 'M', u'གྷ'), + (0xF43, 'M', 'གྷ'), (0xF44, 'V'), (0xF48, 'X'), (0xF49, 'V'), - (0xF4D, 'M', u'ཌྷ'), + (0xF4D, 'M', 'ཌྷ'), (0xF4E, 'V'), - (0xF52, 'M', u'དྷ'), + (0xF52, 'M', 'དྷ'), (0xF53, 'V'), - (0xF57, 'M', u'བྷ'), + (0xF57, 'M', 'བྷ'), (0xF58, 'V'), - (0xF5C, 'M', u'ཛྷ'), + (0xF5C, 'M', 'ཛྷ'), (0xF5D, 'V'), - (0xF69, 'M', u'ཀྵ'), + (0xF69, 'M', 'ཀྵ'), (0xF6A, 'V'), (0xF6D, 'X'), (0xF71, 'V'), - (0xF73, 'M', u'ཱི'), + (0xF73, 'M', 'ཱི'), (0xF74, 'V'), - (0xF75, 'M', u'ཱུ'), - (0xF76, 'M', u'ྲྀ'), - (0xF77, 'M', u'ྲཱྀ'), - (0xF78, 'M', u'ླྀ'), - (0xF79, 'M', u'ླཱྀ'), + (0xF75, 'M', 'ཱུ'), + (0xF76, 'M', 'ྲྀ'), + (0xF77, 'M', 'ྲཱྀ'), + (0xF78, 'M', 'ླྀ'), + (0xF79, 'M', 'ླཱྀ'), (0xF7A, 'V'), - (0xF81, 'M', u'ཱྀ'), + (0xF81, 'M', 'ཱྀ'), (0xF82, 'V'), - (0xF93, 'M', u'ྒྷ'), + (0xF93, 'M', 'ྒྷ'), (0xF94, 'V'), (0xF98, 'X'), (0xF99, 'V'), - (0xF9D, 'M', u'ྜྷ'), + (0xF9D, 'M', 'ྜྷ'), (0xF9E, 'V'), - (0xFA2, 'M', u'ྡྷ'), + (0xFA2, 'M', 'ྡྷ'), (0xFA3, 'V'), - (0xFA7, 'M', u'ྦྷ'), + (0xFA7, 'M', 'ྦྷ'), (0xFA8, 'V'), - (0xFAC, 'M', u'ྫྷ'), + (0xFAC, 'M', 'ྫྷ'), (0xFAD, 'V'), - (0xFB9, 'M', u'ྐྵ'), + (0xFB9, 'M', 'ྐྵ'), (0xFBA, 'V'), (0xFBD, 'X'), (0xFBE, 'V'), @@ -1438,12 +1437,12 @@ def _seg_13(): (0xFDB, 'X'), (0x1000, 'V'), (0x10A0, 'X'), - (0x10C7, 'M', u'ⴧ'), + (0x10C7, 'M', 'ⴧ'), (0x10C8, 'X'), - (0x10CD, 'M', u'ⴭ'), + (0x10CD, 'M', 'ⴭ'), (0x10CE, 'X'), (0x10D0, 'V'), - (0x10FC, 'M', u'ნ'), + (0x10FC, 'M', 'ნ'), (0x10FD, 'V'), (0x115F, 'X'), (0x1161, 'V'), @@ -1459,10 +1458,6 @@ def _seg_13(): (0x1260, 'V'), (0x1289, 'X'), (0x128A, 'V'), - ] - -def _seg_14(): - return [ (0x128E, 'X'), (0x1290, 'V'), (0x12B1, 'X'), @@ -1479,6 +1474,11 @@ def _seg_14(): (0x12D8, 'V'), (0x1311, 'X'), (0x1312, 'V'), + ] + +def _seg_14(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ (0x1316, 'X'), (0x1318, 'V'), (0x135B, 'X'), @@ -1488,12 +1488,12 @@ def _seg_14(): (0x139A, 'X'), (0x13A0, 'V'), (0x13F6, 'X'), - (0x13F8, 'M', u'Ᏸ'), - (0x13F9, 'M', u'Ᏹ'), - (0x13FA, 'M', u'Ᏺ'), - (0x13FB, 'M', u'Ᏻ'), - (0x13FC, 'M', u'Ᏼ'), - (0x13FD, 'M', u'Ᏽ'), + (0x13F8, 'M', 'Ᏸ'), + (0x13F9, 'M', 'Ᏹ'), + (0x13FA, 'M', 'Ᏺ'), + (0x13FB, 'M', 'Ᏻ'), + (0x13FC, 'M', 'Ᏼ'), + (0x13FD, 'M', 'Ᏽ'), (0x13FE, 'X'), (0x1400, 'V'), (0x1680, 'X'), @@ -1563,15 +1563,11 @@ def _seg_14(): (0x1A7F, 'V'), (0x1A8A, 'X'), (0x1A90, 'V'), - ] - -def _seg_15(): - return [ (0x1A9A, 'X'), (0x1AA0, 'V'), (0x1AAE, 'X'), (0x1AB0, 'V'), - (0x1ABF, 'X'), + (0x1AC1, 'X'), (0x1B00, 'V'), (0x1B4C, 'X'), (0x1B50, 'V'), @@ -1583,1148 +1579,1208 @@ def _seg_15(): (0x1C3B, 'V'), (0x1C4A, 'X'), (0x1C4D, 'V'), - (0x1C80, 'M', u'в'), - (0x1C81, 'M', u'д'), - (0x1C82, 'M', u'о'), - (0x1C83, 'M', u'с'), - (0x1C84, 'M', u'т'), - (0x1C86, 'M', u'ъ'), - (0x1C87, 'M', u'ѣ'), - (0x1C88, 'M', u'ꙋ'), + ] + +def _seg_15(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ + (0x1C80, 'M', 'в'), + (0x1C81, 'M', 'д'), + (0x1C82, 'M', 'о'), + (0x1C83, 'M', 'с'), + (0x1C84, 'M', 'т'), + (0x1C86, 'M', 'ъ'), + (0x1C87, 'M', 'ѣ'), + (0x1C88, 'M', 'ꙋ'), (0x1C89, 'X'), + (0x1C90, 'M', 'ა'), + (0x1C91, 'M', 'ბ'), + (0x1C92, 'M', 'გ'), + (0x1C93, 'M', 'დ'), + (0x1C94, 'M', 'ე'), + (0x1C95, 'M', 'ვ'), + (0x1C96, 'M', 'ზ'), + (0x1C97, 'M', 'თ'), + (0x1C98, 'M', 'ი'), + (0x1C99, 'M', 'კ'), + (0x1C9A, 'M', 'ლ'), + (0x1C9B, 'M', 'მ'), + (0x1C9C, 'M', 'ნ'), + (0x1C9D, 'M', 'ო'), + (0x1C9E, 'M', 'პ'), + (0x1C9F, 'M', 'ჟ'), + (0x1CA0, 'M', 'რ'), + (0x1CA1, 'M', 'ს'), + (0x1CA2, 'M', 'ტ'), + (0x1CA3, 'M', 'უ'), + (0x1CA4, 'M', 'ფ'), + (0x1CA5, 'M', 'ქ'), + (0x1CA6, 'M', 'ღ'), + (0x1CA7, 'M', 'ყ'), + (0x1CA8, 'M', 'შ'), + (0x1CA9, 'M', 'ჩ'), + (0x1CAA, 'M', 'ც'), + (0x1CAB, 'M', 'ძ'), + (0x1CAC, 'M', 'წ'), + (0x1CAD, 'M', 'ჭ'), + (0x1CAE, 'M', 'ხ'), + (0x1CAF, 'M', 'ჯ'), + (0x1CB0, 'M', 'ჰ'), + (0x1CB1, 'M', 'ჱ'), + (0x1CB2, 'M', 'ჲ'), + (0x1CB3, 'M', 'ჳ'), + (0x1CB4, 'M', 'ჴ'), + (0x1CB5, 'M', 'ჵ'), + (0x1CB6, 'M', 'ჶ'), + (0x1CB7, 'M', 'ჷ'), + (0x1CB8, 'M', 'ჸ'), + (0x1CB9, 'M', 'ჹ'), + (0x1CBA, 'M', 'ჺ'), + (0x1CBB, 'X'), + (0x1CBD, 'M', 'ჽ'), + (0x1CBE, 'M', 'ჾ'), + (0x1CBF, 'M', 'ჿ'), (0x1CC0, 'V'), (0x1CC8, 'X'), (0x1CD0, 'V'), - (0x1CFA, 'X'), + (0x1CFB, 'X'), (0x1D00, 'V'), - (0x1D2C, 'M', u'a'), - (0x1D2D, 'M', u'æ'), - (0x1D2E, 'M', u'b'), + (0x1D2C, 'M', 'a'), + (0x1D2D, 'M', 'æ'), + (0x1D2E, 'M', 'b'), (0x1D2F, 'V'), - (0x1D30, 'M', u'd'), - (0x1D31, 'M', u'e'), - (0x1D32, 'M', u'ǝ'), - (0x1D33, 'M', u'g'), - (0x1D34, 'M', u'h'), - (0x1D35, 'M', u'i'), - (0x1D36, 'M', u'j'), - (0x1D37, 'M', u'k'), - (0x1D38, 'M', u'l'), - (0x1D39, 'M', u'm'), - (0x1D3A, 'M', u'n'), + (0x1D30, 'M', 'd'), + (0x1D31, 'M', 'e'), + (0x1D32, 'M', 'ǝ'), + (0x1D33, 'M', 'g'), + (0x1D34, 'M', 'h'), + (0x1D35, 'M', 'i'), + (0x1D36, 'M', 'j'), + (0x1D37, 'M', 'k'), + (0x1D38, 'M', 'l'), + (0x1D39, 'M', 'm'), + (0x1D3A, 'M', 'n'), (0x1D3B, 'V'), - (0x1D3C, 'M', u'o'), - (0x1D3D, 'M', u'ȣ'), - (0x1D3E, 'M', u'p'), - (0x1D3F, 'M', u'r'), - (0x1D40, 'M', u't'), - (0x1D41, 'M', u'u'), - (0x1D42, 'M', u'w'), - (0x1D43, 'M', u'a'), - (0x1D44, 'M', u'ɐ'), - (0x1D45, 'M', u'ɑ'), - (0x1D46, 'M', u'ᴂ'), - (0x1D47, 'M', u'b'), - (0x1D48, 'M', u'd'), - (0x1D49, 'M', u'e'), - (0x1D4A, 'M', u'ə'), - (0x1D4B, 'M', u'ɛ'), - (0x1D4C, 'M', u'ɜ'), - (0x1D4D, 'M', u'g'), + (0x1D3C, 'M', 'o'), + (0x1D3D, 'M', 'ȣ'), + (0x1D3E, 'M', 'p'), + (0x1D3F, 'M', 'r'), + (0x1D40, 'M', 't'), + (0x1D41, 'M', 'u'), + (0x1D42, 'M', 'w'), + (0x1D43, 'M', 'a'), + (0x1D44, 'M', 'ɐ'), + (0x1D45, 'M', 'ɑ'), + (0x1D46, 'M', 'ᴂ'), + (0x1D47, 'M', 'b'), + (0x1D48, 'M', 'd'), + (0x1D49, 'M', 'e'), + (0x1D4A, 'M', 'ə'), + (0x1D4B, 'M', 'ɛ'), + (0x1D4C, 'M', 'ɜ'), + (0x1D4D, 'M', 'g'), (0x1D4E, 'V'), - (0x1D4F, 'M', u'k'), - (0x1D50, 'M', u'm'), - (0x1D51, 'M', u'ŋ'), - (0x1D52, 'M', u'o'), - (0x1D53, 'M', u'ɔ'), - (0x1D54, 'M', u'ᴖ'), - (0x1D55, 'M', u'ᴗ'), - (0x1D56, 'M', u'p'), - (0x1D57, 'M', u't'), - (0x1D58, 'M', u'u'), - (0x1D59, 'M', u'ᴝ'), - (0x1D5A, 'M', u'ɯ'), - (0x1D5B, 'M', u'v'), - (0x1D5C, 'M', u'ᴥ'), - (0x1D5D, 'M', u'β'), - (0x1D5E, 'M', u'γ'), - (0x1D5F, 'M', u'δ'), - (0x1D60, 'M', u'φ'), - (0x1D61, 'M', u'χ'), - (0x1D62, 'M', u'i'), - (0x1D63, 'M', u'r'), - (0x1D64, 'M', u'u'), - (0x1D65, 'M', u'v'), - (0x1D66, 'M', u'β'), - (0x1D67, 'M', u'γ'), - (0x1D68, 'M', u'ρ'), - (0x1D69, 'M', u'φ'), - (0x1D6A, 'M', u'χ'), - (0x1D6B, 'V'), - (0x1D78, 'M', u'н'), - (0x1D79, 'V'), - (0x1D9B, 'M', u'ɒ'), - (0x1D9C, 'M', u'c'), - (0x1D9D, 'M', u'ɕ'), - (0x1D9E, 'M', u'ð'), + (0x1D4F, 'M', 'k'), + (0x1D50, 'M', 'm'), + (0x1D51, 'M', 'ŋ'), + (0x1D52, 'M', 'o'), ] def _seg_16(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x1D9F, 'M', u'ɜ'), - (0x1DA0, 'M', u'f'), - (0x1DA1, 'M', u'ɟ'), - (0x1DA2, 'M', u'ɡ'), - (0x1DA3, 'M', u'ɥ'), - (0x1DA4, 'M', u'ɨ'), - (0x1DA5, 'M', u'ɩ'), - (0x1DA6, 'M', u'ɪ'), - (0x1DA7, 'M', u'ᵻ'), - (0x1DA8, 'M', u'ʝ'), - (0x1DA9, 'M', u'ɭ'), - (0x1DAA, 'M', u'ᶅ'), - (0x1DAB, 'M', u'ʟ'), - (0x1DAC, 'M', u'ɱ'), - (0x1DAD, 'M', u'ɰ'), - (0x1DAE, 'M', u'ɲ'), - (0x1DAF, 'M', u'ɳ'), - (0x1DB0, 'M', u'ɴ'), - (0x1DB1, 'M', u'ɵ'), - (0x1DB2, 'M', u'ɸ'), - (0x1DB3, 'M', u'ʂ'), - (0x1DB4, 'M', u'ʃ'), - (0x1DB5, 'M', u'ƫ'), - (0x1DB6, 'M', u'ʉ'), - (0x1DB7, 'M', u'ʊ'), - (0x1DB8, 'M', u'ᴜ'), - (0x1DB9, 'M', u'ʋ'), - (0x1DBA, 'M', u'ʌ'), - (0x1DBB, 'M', u'z'), - (0x1DBC, 'M', u'ʐ'), - (0x1DBD, 'M', u'ʑ'), - (0x1DBE, 'M', u'ʒ'), - (0x1DBF, 'M', u'θ'), + (0x1D53, 'M', 'ɔ'), + (0x1D54, 'M', 'ᴖ'), + (0x1D55, 'M', 'ᴗ'), + (0x1D56, 'M', 'p'), + (0x1D57, 'M', 't'), + (0x1D58, 'M', 'u'), + (0x1D59, 'M', 'ᴝ'), + (0x1D5A, 'M', 'ɯ'), + (0x1D5B, 'M', 'v'), + (0x1D5C, 'M', 'ᴥ'), + (0x1D5D, 'M', 'β'), + (0x1D5E, 'M', 'γ'), + (0x1D5F, 'M', 'δ'), + (0x1D60, 'M', 'φ'), + (0x1D61, 'M', 'χ'), + (0x1D62, 'M', 'i'), + (0x1D63, 'M', 'r'), + (0x1D64, 'M', 'u'), + (0x1D65, 'M', 'v'), + (0x1D66, 'M', 'β'), + (0x1D67, 'M', 'γ'), + (0x1D68, 'M', 'ρ'), + (0x1D69, 'M', 'φ'), + (0x1D6A, 'M', 'χ'), + (0x1D6B, 'V'), + (0x1D78, 'M', 'н'), + (0x1D79, 'V'), + (0x1D9B, 'M', 'ɒ'), + (0x1D9C, 'M', 'c'), + (0x1D9D, 'M', 'ɕ'), + (0x1D9E, 'M', 'ð'), + (0x1D9F, 'M', 'ɜ'), + (0x1DA0, 'M', 'f'), + (0x1DA1, 'M', 'ɟ'), + (0x1DA2, 'M', 'ɡ'), + (0x1DA3, 'M', 'ɥ'), + (0x1DA4, 'M', 'ɨ'), + (0x1DA5, 'M', 'ɩ'), + (0x1DA6, 'M', 'ɪ'), + (0x1DA7, 'M', 'ᵻ'), + (0x1DA8, 'M', 'ʝ'), + (0x1DA9, 'M', 'ɭ'), + (0x1DAA, 'M', 'ᶅ'), + (0x1DAB, 'M', 'ʟ'), + (0x1DAC, 'M', 'ɱ'), + (0x1DAD, 'M', 'ɰ'), + (0x1DAE, 'M', 'ɲ'), + (0x1DAF, 'M', 'ɳ'), + (0x1DB0, 'M', 'ɴ'), + (0x1DB1, 'M', 'ɵ'), + (0x1DB2, 'M', 'ɸ'), + (0x1DB3, 'M', 'ʂ'), + (0x1DB4, 'M', 'ʃ'), + (0x1DB5, 'M', 'ƫ'), + (0x1DB6, 'M', 'ʉ'), + (0x1DB7, 'M', 'ʊ'), + (0x1DB8, 'M', 'ᴜ'), + (0x1DB9, 'M', 'ʋ'), + (0x1DBA, 'M', 'ʌ'), + (0x1DBB, 'M', 'z'), + (0x1DBC, 'M', 'ʐ'), + (0x1DBD, 'M', 'ʑ'), + (0x1DBE, 'M', 'ʒ'), + (0x1DBF, 'M', 'θ'), (0x1DC0, 'V'), (0x1DFA, 'X'), (0x1DFB, 'V'), - (0x1E00, 'M', u'ḁ'), + (0x1E00, 'M', 'ḁ'), (0x1E01, 'V'), - (0x1E02, 'M', u'ḃ'), + (0x1E02, 'M', 'ḃ'), (0x1E03, 'V'), - (0x1E04, 'M', u'ḅ'), + (0x1E04, 'M', 'ḅ'), (0x1E05, 'V'), - (0x1E06, 'M', u'ḇ'), + (0x1E06, 'M', 'ḇ'), (0x1E07, 'V'), - (0x1E08, 'M', u'ḉ'), + (0x1E08, 'M', 'ḉ'), (0x1E09, 'V'), - (0x1E0A, 'M', u'ḋ'), + (0x1E0A, 'M', 'ḋ'), (0x1E0B, 'V'), - (0x1E0C, 'M', u'ḍ'), + (0x1E0C, 'M', 'ḍ'), (0x1E0D, 'V'), - (0x1E0E, 'M', u'ḏ'), + (0x1E0E, 'M', 'ḏ'), (0x1E0F, 'V'), - (0x1E10, 'M', u'ḑ'), + (0x1E10, 'M', 'ḑ'), (0x1E11, 'V'), - (0x1E12, 'M', u'ḓ'), + (0x1E12, 'M', 'ḓ'), (0x1E13, 'V'), - (0x1E14, 'M', u'ḕ'), + (0x1E14, 'M', 'ḕ'), (0x1E15, 'V'), - (0x1E16, 'M', u'ḗ'), + (0x1E16, 'M', 'ḗ'), (0x1E17, 'V'), - (0x1E18, 'M', u'ḙ'), + (0x1E18, 'M', 'ḙ'), (0x1E19, 'V'), - (0x1E1A, 'M', u'ḛ'), + (0x1E1A, 'M', 'ḛ'), (0x1E1B, 'V'), - (0x1E1C, 'M', u'ḝ'), + (0x1E1C, 'M', 'ḝ'), (0x1E1D, 'V'), - (0x1E1E, 'M', u'ḟ'), + (0x1E1E, 'M', 'ḟ'), (0x1E1F, 'V'), - (0x1E20, 'M', u'ḡ'), - (0x1E21, 'V'), - (0x1E22, 'M', u'ḣ'), - (0x1E23, 'V'), - (0x1E24, 'M', u'ḥ'), - (0x1E25, 'V'), - (0x1E26, 'M', u'ḧ'), - (0x1E27, 'V'), - (0x1E28, 'M', u'ḩ'), - (0x1E29, 'V'), - (0x1E2A, 'M', u'ḫ'), - (0x1E2B, 'V'), - (0x1E2C, 'M', u'ḭ'), - (0x1E2D, 'V'), - (0x1E2E, 'M', u'ḯ'), - (0x1E2F, 'V'), - (0x1E30, 'M', u'ḱ'), - (0x1E31, 'V'), - (0x1E32, 'M', u'ḳ'), - (0x1E33, 'V'), - (0x1E34, 'M', u'ḵ'), - (0x1E35, 'V'), - (0x1E36, 'M', u'ḷ'), - (0x1E37, 'V'), - (0x1E38, 'M', u'ḹ'), - (0x1E39, 'V'), - (0x1E3A, 'M', u'ḻ'), - (0x1E3B, 'V'), - (0x1E3C, 'M', u'ḽ'), - (0x1E3D, 'V'), - (0x1E3E, 'M', u'ḿ'), - (0x1E3F, 'V'), + (0x1E20, 'M', 'ḡ'), ] def _seg_17(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x1E40, 'M', u'ṁ'), + (0x1E21, 'V'), + (0x1E22, 'M', 'ḣ'), + (0x1E23, 'V'), + (0x1E24, 'M', 'ḥ'), + (0x1E25, 'V'), + (0x1E26, 'M', 'ḧ'), + (0x1E27, 'V'), + (0x1E28, 'M', 'ḩ'), + (0x1E29, 'V'), + (0x1E2A, 'M', 'ḫ'), + (0x1E2B, 'V'), + (0x1E2C, 'M', 'ḭ'), + (0x1E2D, 'V'), + (0x1E2E, 'M', 'ḯ'), + (0x1E2F, 'V'), + (0x1E30, 'M', 'ḱ'), + (0x1E31, 'V'), + (0x1E32, 'M', 'ḳ'), + (0x1E33, 'V'), + (0x1E34, 'M', 'ḵ'), + (0x1E35, 'V'), + (0x1E36, 'M', 'ḷ'), + (0x1E37, 'V'), + (0x1E38, 'M', 'ḹ'), + (0x1E39, 'V'), + (0x1E3A, 'M', 'ḻ'), + (0x1E3B, 'V'), + (0x1E3C, 'M', 'ḽ'), + (0x1E3D, 'V'), + (0x1E3E, 'M', 'ḿ'), + (0x1E3F, 'V'), + (0x1E40, 'M', 'ṁ'), (0x1E41, 'V'), - (0x1E42, 'M', u'ṃ'), + (0x1E42, 'M', 'ṃ'), (0x1E43, 'V'), - (0x1E44, 'M', u'ṅ'), + (0x1E44, 'M', 'ṅ'), (0x1E45, 'V'), - (0x1E46, 'M', u'ṇ'), + (0x1E46, 'M', 'ṇ'), (0x1E47, 'V'), - (0x1E48, 'M', u'ṉ'), + (0x1E48, 'M', 'ṉ'), (0x1E49, 'V'), - (0x1E4A, 'M', u'ṋ'), + (0x1E4A, 'M', 'ṋ'), (0x1E4B, 'V'), - (0x1E4C, 'M', u'ṍ'), + (0x1E4C, 'M', 'ṍ'), (0x1E4D, 'V'), - (0x1E4E, 'M', u'ṏ'), + (0x1E4E, 'M', 'ṏ'), (0x1E4F, 'V'), - (0x1E50, 'M', u'ṑ'), + (0x1E50, 'M', 'ṑ'), (0x1E51, 'V'), - (0x1E52, 'M', u'ṓ'), + (0x1E52, 'M', 'ṓ'), (0x1E53, 'V'), - (0x1E54, 'M', u'ṕ'), + (0x1E54, 'M', 'ṕ'), (0x1E55, 'V'), - (0x1E56, 'M', u'ṗ'), + (0x1E56, 'M', 'ṗ'), (0x1E57, 'V'), - (0x1E58, 'M', u'ṙ'), + (0x1E58, 'M', 'ṙ'), (0x1E59, 'V'), - (0x1E5A, 'M', u'ṛ'), + (0x1E5A, 'M', 'ṛ'), (0x1E5B, 'V'), - (0x1E5C, 'M', u'ṝ'), + (0x1E5C, 'M', 'ṝ'), (0x1E5D, 'V'), - (0x1E5E, 'M', u'ṟ'), + (0x1E5E, 'M', 'ṟ'), (0x1E5F, 'V'), - (0x1E60, 'M', u'ṡ'), + (0x1E60, 'M', 'ṡ'), (0x1E61, 'V'), - (0x1E62, 'M', u'ṣ'), + (0x1E62, 'M', 'ṣ'), (0x1E63, 'V'), - (0x1E64, 'M', u'ṥ'), + (0x1E64, 'M', 'ṥ'), (0x1E65, 'V'), - (0x1E66, 'M', u'ṧ'), + (0x1E66, 'M', 'ṧ'), (0x1E67, 'V'), - (0x1E68, 'M', u'ṩ'), + (0x1E68, 'M', 'ṩ'), (0x1E69, 'V'), - (0x1E6A, 'M', u'ṫ'), + (0x1E6A, 'M', 'ṫ'), (0x1E6B, 'V'), - (0x1E6C, 'M', u'ṭ'), + (0x1E6C, 'M', 'ṭ'), (0x1E6D, 'V'), - (0x1E6E, 'M', u'ṯ'), + (0x1E6E, 'M', 'ṯ'), (0x1E6F, 'V'), - (0x1E70, 'M', u'ṱ'), + (0x1E70, 'M', 'ṱ'), (0x1E71, 'V'), - (0x1E72, 'M', u'ṳ'), + (0x1E72, 'M', 'ṳ'), (0x1E73, 'V'), - (0x1E74, 'M', u'ṵ'), + (0x1E74, 'M', 'ṵ'), (0x1E75, 'V'), - (0x1E76, 'M', u'ṷ'), + (0x1E76, 'M', 'ṷ'), (0x1E77, 'V'), - (0x1E78, 'M', u'ṹ'), + (0x1E78, 'M', 'ṹ'), (0x1E79, 'V'), - (0x1E7A, 'M', u'ṻ'), + (0x1E7A, 'M', 'ṻ'), (0x1E7B, 'V'), - (0x1E7C, 'M', u'ṽ'), + (0x1E7C, 'M', 'ṽ'), (0x1E7D, 'V'), - (0x1E7E, 'M', u'ṿ'), + (0x1E7E, 'M', 'ṿ'), (0x1E7F, 'V'), - (0x1E80, 'M', u'ẁ'), + (0x1E80, 'M', 'ẁ'), (0x1E81, 'V'), - (0x1E82, 'M', u'ẃ'), + (0x1E82, 'M', 'ẃ'), (0x1E83, 'V'), - (0x1E84, 'M', u'ẅ'), - (0x1E85, 'V'), - (0x1E86, 'M', u'ẇ'), - (0x1E87, 'V'), - (0x1E88, 'M', u'ẉ'), - (0x1E89, 'V'), - (0x1E8A, 'M', u'ẋ'), - (0x1E8B, 'V'), - (0x1E8C, 'M', u'ẍ'), - (0x1E8D, 'V'), - (0x1E8E, 'M', u'ẏ'), - (0x1E8F, 'V'), - (0x1E90, 'M', u'ẑ'), - (0x1E91, 'V'), - (0x1E92, 'M', u'ẓ'), - (0x1E93, 'V'), - (0x1E94, 'M', u'ẕ'), - (0x1E95, 'V'), - (0x1E9A, 'M', u'aʾ'), - (0x1E9B, 'M', u'ṡ'), - (0x1E9C, 'V'), - (0x1E9E, 'M', u'ss'), - (0x1E9F, 'V'), - (0x1EA0, 'M', u'ạ'), - (0x1EA1, 'V'), - (0x1EA2, 'M', u'ả'), - (0x1EA3, 'V'), - (0x1EA4, 'M', u'ấ'), - (0x1EA5, 'V'), - (0x1EA6, 'M', u'ầ'), - (0x1EA7, 'V'), - (0x1EA8, 'M', u'ẩ'), + (0x1E84, 'M', 'ẅ'), ] def _seg_18(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ + (0x1E85, 'V'), + (0x1E86, 'M', 'ẇ'), + (0x1E87, 'V'), + (0x1E88, 'M', 'ẉ'), + (0x1E89, 'V'), + (0x1E8A, 'M', 'ẋ'), + (0x1E8B, 'V'), + (0x1E8C, 'M', 'ẍ'), + (0x1E8D, 'V'), + (0x1E8E, 'M', 'ẏ'), + (0x1E8F, 'V'), + (0x1E90, 'M', 'ẑ'), + (0x1E91, 'V'), + (0x1E92, 'M', 'ẓ'), + (0x1E93, 'V'), + (0x1E94, 'M', 'ẕ'), + (0x1E95, 'V'), + (0x1E9A, 'M', 'aʾ'), + (0x1E9B, 'M', 'ṡ'), + (0x1E9C, 'V'), + (0x1E9E, 'M', 'ss'), + (0x1E9F, 'V'), + (0x1EA0, 'M', 'ạ'), + (0x1EA1, 'V'), + (0x1EA2, 'M', 'ả'), + (0x1EA3, 'V'), + (0x1EA4, 'M', 'ấ'), + (0x1EA5, 'V'), + (0x1EA6, 'M', 'ầ'), + (0x1EA7, 'V'), + (0x1EA8, 'M', 'ẩ'), (0x1EA9, 'V'), - (0x1EAA, 'M', u'ẫ'), + (0x1EAA, 'M', 'ẫ'), (0x1EAB, 'V'), - (0x1EAC, 'M', u'ậ'), + (0x1EAC, 'M', 'ậ'), (0x1EAD, 'V'), - (0x1EAE, 'M', u'ắ'), + (0x1EAE, 'M', 'ắ'), (0x1EAF, 'V'), - (0x1EB0, 'M', u'ằ'), + (0x1EB0, 'M', 'ằ'), (0x1EB1, 'V'), - (0x1EB2, 'M', u'ẳ'), + (0x1EB2, 'M', 'ẳ'), (0x1EB3, 'V'), - (0x1EB4, 'M', u'ẵ'), + (0x1EB4, 'M', 'ẵ'), (0x1EB5, 'V'), - (0x1EB6, 'M', u'ặ'), + (0x1EB6, 'M', 'ặ'), (0x1EB7, 'V'), - (0x1EB8, 'M', u'ẹ'), + (0x1EB8, 'M', 'ẹ'), (0x1EB9, 'V'), - (0x1EBA, 'M', u'ẻ'), + (0x1EBA, 'M', 'ẻ'), (0x1EBB, 'V'), - (0x1EBC, 'M', u'ẽ'), + (0x1EBC, 'M', 'ẽ'), (0x1EBD, 'V'), - (0x1EBE, 'M', u'ế'), + (0x1EBE, 'M', 'ế'), (0x1EBF, 'V'), - (0x1EC0, 'M', u'ề'), + (0x1EC0, 'M', 'ề'), (0x1EC1, 'V'), - (0x1EC2, 'M', u'ể'), + (0x1EC2, 'M', 'ể'), (0x1EC3, 'V'), - (0x1EC4, 'M', u'ễ'), + (0x1EC4, 'M', 'ễ'), (0x1EC5, 'V'), - (0x1EC6, 'M', u'ệ'), + (0x1EC6, 'M', 'ệ'), (0x1EC7, 'V'), - (0x1EC8, 'M', u'ỉ'), + (0x1EC8, 'M', 'ỉ'), (0x1EC9, 'V'), - (0x1ECA, 'M', u'ị'), + (0x1ECA, 'M', 'ị'), (0x1ECB, 'V'), - (0x1ECC, 'M', u'ọ'), + (0x1ECC, 'M', 'ọ'), (0x1ECD, 'V'), - (0x1ECE, 'M', u'ỏ'), + (0x1ECE, 'M', 'ỏ'), (0x1ECF, 'V'), - (0x1ED0, 'M', u'ố'), + (0x1ED0, 'M', 'ố'), (0x1ED1, 'V'), - (0x1ED2, 'M', u'ồ'), + (0x1ED2, 'M', 'ồ'), (0x1ED3, 'V'), - (0x1ED4, 'M', u'ổ'), + (0x1ED4, 'M', 'ổ'), (0x1ED5, 'V'), - (0x1ED6, 'M', u'ỗ'), + (0x1ED6, 'M', 'ỗ'), (0x1ED7, 'V'), - (0x1ED8, 'M', u'ộ'), + (0x1ED8, 'M', 'ộ'), (0x1ED9, 'V'), - (0x1EDA, 'M', u'ớ'), + (0x1EDA, 'M', 'ớ'), (0x1EDB, 'V'), - (0x1EDC, 'M', u'ờ'), + (0x1EDC, 'M', 'ờ'), (0x1EDD, 'V'), - (0x1EDE, 'M', u'ở'), + (0x1EDE, 'M', 'ở'), (0x1EDF, 'V'), - (0x1EE0, 'M', u'ỡ'), + (0x1EE0, 'M', 'ỡ'), (0x1EE1, 'V'), - (0x1EE2, 'M', u'ợ'), + (0x1EE2, 'M', 'ợ'), (0x1EE3, 'V'), - (0x1EE4, 'M', u'ụ'), + (0x1EE4, 'M', 'ụ'), (0x1EE5, 'V'), - (0x1EE6, 'M', u'ủ'), + (0x1EE6, 'M', 'ủ'), (0x1EE7, 'V'), - (0x1EE8, 'M', u'ứ'), + (0x1EE8, 'M', 'ứ'), (0x1EE9, 'V'), - (0x1EEA, 'M', u'ừ'), + (0x1EEA, 'M', 'ừ'), (0x1EEB, 'V'), - (0x1EEC, 'M', u'ử'), + (0x1EEC, 'M', 'ử'), (0x1EED, 'V'), - (0x1EEE, 'M', u'ữ'), - (0x1EEF, 'V'), - (0x1EF0, 'M', u'ự'), - (0x1EF1, 'V'), - (0x1EF2, 'M', u'ỳ'), - (0x1EF3, 'V'), - (0x1EF4, 'M', u'ỵ'), - (0x1EF5, 'V'), - (0x1EF6, 'M', u'ỷ'), - (0x1EF7, 'V'), - (0x1EF8, 'M', u'ỹ'), - (0x1EF9, 'V'), - (0x1EFA, 'M', u'ỻ'), - (0x1EFB, 'V'), - (0x1EFC, 'M', u'ỽ'), - (0x1EFD, 'V'), - (0x1EFE, 'M', u'ỿ'), - (0x1EFF, 'V'), - (0x1F08, 'M', u'ἀ'), - (0x1F09, 'M', u'ἁ'), - (0x1F0A, 'M', u'ἂ'), - (0x1F0B, 'M', u'ἃ'), - (0x1F0C, 'M', u'ἄ'), - (0x1F0D, 'M', u'ἅ'), - (0x1F0E, 'M', u'ἆ'), - (0x1F0F, 'M', u'ἇ'), - (0x1F10, 'V'), - (0x1F16, 'X'), - (0x1F18, 'M', u'ἐ'), - (0x1F19, 'M', u'ἑ'), - (0x1F1A, 'M', u'ἒ'), ] def _seg_19(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x1F1B, 'M', u'ἓ'), - (0x1F1C, 'M', u'ἔ'), - (0x1F1D, 'M', u'ἕ'), + (0x1EEE, 'M', 'ữ'), + (0x1EEF, 'V'), + (0x1EF0, 'M', 'ự'), + (0x1EF1, 'V'), + (0x1EF2, 'M', 'ỳ'), + (0x1EF3, 'V'), + (0x1EF4, 'M', 'ỵ'), + (0x1EF5, 'V'), + (0x1EF6, 'M', 'ỷ'), + (0x1EF7, 'V'), + (0x1EF8, 'M', 'ỹ'), + (0x1EF9, 'V'), + (0x1EFA, 'M', 'ỻ'), + (0x1EFB, 'V'), + (0x1EFC, 'M', 'ỽ'), + (0x1EFD, 'V'), + (0x1EFE, 'M', 'ỿ'), + (0x1EFF, 'V'), + (0x1F08, 'M', 'ἀ'), + (0x1F09, 'M', 'ἁ'), + (0x1F0A, 'M', 'ἂ'), + (0x1F0B, 'M', 'ἃ'), + (0x1F0C, 'M', 'ἄ'), + (0x1F0D, 'M', 'ἅ'), + (0x1F0E, 'M', 'ἆ'), + (0x1F0F, 'M', 'ἇ'), + (0x1F10, 'V'), + (0x1F16, 'X'), + (0x1F18, 'M', 'ἐ'), + (0x1F19, 'M', 'ἑ'), + (0x1F1A, 'M', 'ἒ'), + (0x1F1B, 'M', 'ἓ'), + (0x1F1C, 'M', 'ἔ'), + (0x1F1D, 'M', 'ἕ'), (0x1F1E, 'X'), (0x1F20, 'V'), - (0x1F28, 'M', u'ἠ'), - (0x1F29, 'M', u'ἡ'), - (0x1F2A, 'M', u'ἢ'), - (0x1F2B, 'M', u'ἣ'), - (0x1F2C, 'M', u'ἤ'), - (0x1F2D, 'M', u'ἥ'), - (0x1F2E, 'M', u'ἦ'), - (0x1F2F, 'M', u'ἧ'), + (0x1F28, 'M', 'ἠ'), + (0x1F29, 'M', 'ἡ'), + (0x1F2A, 'M', 'ἢ'), + (0x1F2B, 'M', 'ἣ'), + (0x1F2C, 'M', 'ἤ'), + (0x1F2D, 'M', 'ἥ'), + (0x1F2E, 'M', 'ἦ'), + (0x1F2F, 'M', 'ἧ'), (0x1F30, 'V'), - (0x1F38, 'M', u'ἰ'), - (0x1F39, 'M', u'ἱ'), - (0x1F3A, 'M', u'ἲ'), - (0x1F3B, 'M', u'ἳ'), - (0x1F3C, 'M', u'ἴ'), - (0x1F3D, 'M', u'ἵ'), - (0x1F3E, 'M', u'ἶ'), - (0x1F3F, 'M', u'ἷ'), + (0x1F38, 'M', 'ἰ'), + (0x1F39, 'M', 'ἱ'), + (0x1F3A, 'M', 'ἲ'), + (0x1F3B, 'M', 'ἳ'), + (0x1F3C, 'M', 'ἴ'), + (0x1F3D, 'M', 'ἵ'), + (0x1F3E, 'M', 'ἶ'), + (0x1F3F, 'M', 'ἷ'), (0x1F40, 'V'), (0x1F46, 'X'), - (0x1F48, 'M', u'ὀ'), - (0x1F49, 'M', u'ὁ'), - (0x1F4A, 'M', u'ὂ'), - (0x1F4B, 'M', u'ὃ'), - (0x1F4C, 'M', u'ὄ'), - (0x1F4D, 'M', u'ὅ'), + (0x1F48, 'M', 'ὀ'), + (0x1F49, 'M', 'ὁ'), + (0x1F4A, 'M', 'ὂ'), + (0x1F4B, 'M', 'ὃ'), + (0x1F4C, 'M', 'ὄ'), + (0x1F4D, 'M', 'ὅ'), (0x1F4E, 'X'), (0x1F50, 'V'), (0x1F58, 'X'), - (0x1F59, 'M', u'ὑ'), + (0x1F59, 'M', 'ὑ'), (0x1F5A, 'X'), - (0x1F5B, 'M', u'ὓ'), + (0x1F5B, 'M', 'ὓ'), (0x1F5C, 'X'), - (0x1F5D, 'M', u'ὕ'), + (0x1F5D, 'M', 'ὕ'), (0x1F5E, 'X'), - (0x1F5F, 'M', u'ὗ'), + (0x1F5F, 'M', 'ὗ'), (0x1F60, 'V'), - (0x1F68, 'M', u'ὠ'), - (0x1F69, 'M', u'ὡ'), - (0x1F6A, 'M', u'ὢ'), - (0x1F6B, 'M', u'ὣ'), - (0x1F6C, 'M', u'ὤ'), - (0x1F6D, 'M', u'ὥ'), - (0x1F6E, 'M', u'ὦ'), - (0x1F6F, 'M', u'ὧ'), + (0x1F68, 'M', 'ὠ'), + (0x1F69, 'M', 'ὡ'), + (0x1F6A, 'M', 'ὢ'), + (0x1F6B, 'M', 'ὣ'), + (0x1F6C, 'M', 'ὤ'), + (0x1F6D, 'M', 'ὥ'), + (0x1F6E, 'M', 'ὦ'), + (0x1F6F, 'M', 'ὧ'), (0x1F70, 'V'), - (0x1F71, 'M', u'ά'), + (0x1F71, 'M', 'ά'), (0x1F72, 'V'), - (0x1F73, 'M', u'έ'), + (0x1F73, 'M', 'έ'), (0x1F74, 'V'), - (0x1F75, 'M', u'ή'), + (0x1F75, 'M', 'ή'), (0x1F76, 'V'), - (0x1F77, 'M', u'ί'), + (0x1F77, 'M', 'ί'), (0x1F78, 'V'), - (0x1F79, 'M', u'ό'), + (0x1F79, 'M', 'ό'), (0x1F7A, 'V'), - (0x1F7B, 'M', u'ύ'), + (0x1F7B, 'M', 'ύ'), (0x1F7C, 'V'), - (0x1F7D, 'M', u'ώ'), + (0x1F7D, 'M', 'ώ'), (0x1F7E, 'X'), - (0x1F80, 'M', u'ἀι'), - (0x1F81, 'M', u'ἁι'), - (0x1F82, 'M', u'ἂι'), - (0x1F83, 'M', u'ἃι'), - (0x1F84, 'M', u'ἄι'), - (0x1F85, 'M', u'ἅι'), - (0x1F86, 'M', u'ἆι'), - (0x1F87, 'M', u'ἇι'), - (0x1F88, 'M', u'ἀι'), - (0x1F89, 'M', u'ἁι'), - (0x1F8A, 'M', u'ἂι'), - (0x1F8B, 'M', u'ἃι'), - (0x1F8C, 'M', u'ἄι'), - (0x1F8D, 'M', u'ἅι'), - (0x1F8E, 'M', u'ἆι'), - (0x1F8F, 'M', u'ἇι'), - (0x1F90, 'M', u'ἠι'), - (0x1F91, 'M', u'ἡι'), - (0x1F92, 'M', u'ἢι'), - (0x1F93, 'M', u'ἣι'), - (0x1F94, 'M', u'ἤι'), - (0x1F95, 'M', u'ἥι'), - (0x1F96, 'M', u'ἦι'), - (0x1F97, 'M', u'ἧι'), - (0x1F98, 'M', u'ἠι'), - (0x1F99, 'M', u'ἡι'), - (0x1F9A, 'M', u'ἢι'), - (0x1F9B, 'M', u'ἣι'), - (0x1F9C, 'M', u'ἤι'), - (0x1F9D, 'M', u'ἥι'), - (0x1F9E, 'M', u'ἦι'), - (0x1F9F, 'M', u'ἧι'), - (0x1FA0, 'M', u'ὠι'), - (0x1FA1, 'M', u'ὡι'), - (0x1FA2, 'M', u'ὢι'), - (0x1FA3, 'M', u'ὣι'), + (0x1F80, 'M', 'ἀι'), + (0x1F81, 'M', 'ἁι'), + (0x1F82, 'M', 'ἂι'), + (0x1F83, 'M', 'ἃι'), + (0x1F84, 'M', 'ἄι'), ] def _seg_20(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x1FA4, 'M', u'ὤι'), - (0x1FA5, 'M', u'ὥι'), - (0x1FA6, 'M', u'ὦι'), - (0x1FA7, 'M', u'ὧι'), - (0x1FA8, 'M', u'ὠι'), - (0x1FA9, 'M', u'ὡι'), - (0x1FAA, 'M', u'ὢι'), - (0x1FAB, 'M', u'ὣι'), - (0x1FAC, 'M', u'ὤι'), - (0x1FAD, 'M', u'ὥι'), - (0x1FAE, 'M', u'ὦι'), - (0x1FAF, 'M', u'ὧι'), + (0x1F85, 'M', 'ἅι'), + (0x1F86, 'M', 'ἆι'), + (0x1F87, 'M', 'ἇι'), + (0x1F88, 'M', 'ἀι'), + (0x1F89, 'M', 'ἁι'), + (0x1F8A, 'M', 'ἂι'), + (0x1F8B, 'M', 'ἃι'), + (0x1F8C, 'M', 'ἄι'), + (0x1F8D, 'M', 'ἅι'), + (0x1F8E, 'M', 'ἆι'), + (0x1F8F, 'M', 'ἇι'), + (0x1F90, 'M', 'ἠι'), + (0x1F91, 'M', 'ἡι'), + (0x1F92, 'M', 'ἢι'), + (0x1F93, 'M', 'ἣι'), + (0x1F94, 'M', 'ἤι'), + (0x1F95, 'M', 'ἥι'), + (0x1F96, 'M', 'ἦι'), + (0x1F97, 'M', 'ἧι'), + (0x1F98, 'M', 'ἠι'), + (0x1F99, 'M', 'ἡι'), + (0x1F9A, 'M', 'ἢι'), + (0x1F9B, 'M', 'ἣι'), + (0x1F9C, 'M', 'ἤι'), + (0x1F9D, 'M', 'ἥι'), + (0x1F9E, 'M', 'ἦι'), + (0x1F9F, 'M', 'ἧι'), + (0x1FA0, 'M', 'ὠι'), + (0x1FA1, 'M', 'ὡι'), + (0x1FA2, 'M', 'ὢι'), + (0x1FA3, 'M', 'ὣι'), + (0x1FA4, 'M', 'ὤι'), + (0x1FA5, 'M', 'ὥι'), + (0x1FA6, 'M', 'ὦι'), + (0x1FA7, 'M', 'ὧι'), + (0x1FA8, 'M', 'ὠι'), + (0x1FA9, 'M', 'ὡι'), + (0x1FAA, 'M', 'ὢι'), + (0x1FAB, 'M', 'ὣι'), + (0x1FAC, 'M', 'ὤι'), + (0x1FAD, 'M', 'ὥι'), + (0x1FAE, 'M', 'ὦι'), + (0x1FAF, 'M', 'ὧι'), (0x1FB0, 'V'), - (0x1FB2, 'M', u'ὰι'), - (0x1FB3, 'M', u'αι'), - (0x1FB4, 'M', u'άι'), + (0x1FB2, 'M', 'ὰι'), + (0x1FB3, 'M', 'αι'), + (0x1FB4, 'M', 'άι'), (0x1FB5, 'X'), (0x1FB6, 'V'), - (0x1FB7, 'M', u'ᾶι'), - (0x1FB8, 'M', u'ᾰ'), - (0x1FB9, 'M', u'ᾱ'), - (0x1FBA, 'M', u'ὰ'), - (0x1FBB, 'M', u'ά'), - (0x1FBC, 'M', u'αι'), - (0x1FBD, '3', u' ̓'), - (0x1FBE, 'M', u'ι'), - (0x1FBF, '3', u' ̓'), - (0x1FC0, '3', u' ͂'), - (0x1FC1, '3', u' ̈͂'), - (0x1FC2, 'M', u'ὴι'), - (0x1FC3, 'M', u'ηι'), - (0x1FC4, 'M', u'ήι'), + (0x1FB7, 'M', 'ᾶι'), + (0x1FB8, 'M', 'ᾰ'), + (0x1FB9, 'M', 'ᾱ'), + (0x1FBA, 'M', 'ὰ'), + (0x1FBB, 'M', 'ά'), + (0x1FBC, 'M', 'αι'), + (0x1FBD, '3', ' ̓'), + (0x1FBE, 'M', 'ι'), + (0x1FBF, '3', ' ̓'), + (0x1FC0, '3', ' ͂'), + (0x1FC1, '3', ' ̈͂'), + (0x1FC2, 'M', 'ὴι'), + (0x1FC3, 'M', 'ηι'), + (0x1FC4, 'M', 'ήι'), (0x1FC5, 'X'), (0x1FC6, 'V'), - (0x1FC7, 'M', u'ῆι'), - (0x1FC8, 'M', u'ὲ'), - (0x1FC9, 'M', u'έ'), - (0x1FCA, 'M', u'ὴ'), - (0x1FCB, 'M', u'ή'), - (0x1FCC, 'M', u'ηι'), - (0x1FCD, '3', u' ̓̀'), - (0x1FCE, '3', u' ̓́'), - (0x1FCF, '3', u' ̓͂'), + (0x1FC7, 'M', 'ῆι'), + (0x1FC8, 'M', 'ὲ'), + (0x1FC9, 'M', 'έ'), + (0x1FCA, 'M', 'ὴ'), + (0x1FCB, 'M', 'ή'), + (0x1FCC, 'M', 'ηι'), + (0x1FCD, '3', ' ̓̀'), + (0x1FCE, '3', ' ̓́'), + (0x1FCF, '3', ' ̓͂'), (0x1FD0, 'V'), - (0x1FD3, 'M', u'ΐ'), + (0x1FD3, 'M', 'ΐ'), (0x1FD4, 'X'), (0x1FD6, 'V'), - (0x1FD8, 'M', u'ῐ'), - (0x1FD9, 'M', u'ῑ'), - (0x1FDA, 'M', u'ὶ'), - (0x1FDB, 'M', u'ί'), + (0x1FD8, 'M', 'ῐ'), + (0x1FD9, 'M', 'ῑ'), + (0x1FDA, 'M', 'ὶ'), + (0x1FDB, 'M', 'ί'), (0x1FDC, 'X'), - (0x1FDD, '3', u' ̔̀'), - (0x1FDE, '3', u' ̔́'), - (0x1FDF, '3', u' ̔͂'), + (0x1FDD, '3', ' ̔̀'), + (0x1FDE, '3', ' ̔́'), + (0x1FDF, '3', ' ̔͂'), (0x1FE0, 'V'), - (0x1FE3, 'M', u'ΰ'), + (0x1FE3, 'M', 'ΰ'), (0x1FE4, 'V'), - (0x1FE8, 'M', u'ῠ'), - (0x1FE9, 'M', u'ῡ'), - (0x1FEA, 'M', u'ὺ'), - (0x1FEB, 'M', u'ύ'), - (0x1FEC, 'M', u'ῥ'), - (0x1FED, '3', u' ̈̀'), - (0x1FEE, '3', u' ̈́'), - (0x1FEF, '3', u'`'), + (0x1FE8, 'M', 'ῠ'), + (0x1FE9, 'M', 'ῡ'), + (0x1FEA, 'M', 'ὺ'), + (0x1FEB, 'M', 'ύ'), + (0x1FEC, 'M', 'ῥ'), + (0x1FED, '3', ' ̈̀'), + (0x1FEE, '3', ' ̈́'), + (0x1FEF, '3', '`'), (0x1FF0, 'X'), - (0x1FF2, 'M', u'ὼι'), - (0x1FF3, 'M', u'ωι'), - (0x1FF4, 'M', u'ώι'), + (0x1FF2, 'M', 'ὼι'), + (0x1FF3, 'M', 'ωι'), + ] + +def _seg_21(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ + (0x1FF4, 'M', 'ώι'), (0x1FF5, 'X'), (0x1FF6, 'V'), - (0x1FF7, 'M', u'ῶι'), - (0x1FF8, 'M', u'ὸ'), - (0x1FF9, 'M', u'ό'), - (0x1FFA, 'M', u'ὼ'), - (0x1FFB, 'M', u'ώ'), - (0x1FFC, 'M', u'ωι'), - (0x1FFD, '3', u' ́'), - (0x1FFE, '3', u' ̔'), + (0x1FF7, 'M', 'ῶι'), + (0x1FF8, 'M', 'ὸ'), + (0x1FF9, 'M', 'ό'), + (0x1FFA, 'M', 'ὼ'), + (0x1FFB, 'M', 'ώ'), + (0x1FFC, 'M', 'ωι'), + (0x1FFD, '3', ' ́'), + (0x1FFE, '3', ' ̔'), (0x1FFF, 'X'), - (0x2000, '3', u' '), + (0x2000, '3', ' '), (0x200B, 'I'), - (0x200C, 'D', u''), + (0x200C, 'D', ''), (0x200E, 'X'), (0x2010, 'V'), - (0x2011, 'M', u'‐'), + (0x2011, 'M', '‐'), (0x2012, 'V'), - (0x2017, '3', u' ̳'), + (0x2017, '3', ' ̳'), (0x2018, 'V'), (0x2024, 'X'), (0x2027, 'V'), (0x2028, 'X'), - (0x202F, '3', u' '), + (0x202F, '3', ' '), (0x2030, 'V'), - (0x2033, 'M', u'′′'), - (0x2034, 'M', u'′′′'), + (0x2033, 'M', '′′'), + (0x2034, 'M', '′′′'), (0x2035, 'V'), - (0x2036, 'M', u'‵‵'), - (0x2037, 'M', u'‵‵‵'), - ] - -def _seg_21(): - return [ + (0x2036, 'M', '‵‵'), + (0x2037, 'M', '‵‵‵'), (0x2038, 'V'), - (0x203C, '3', u'!!'), + (0x203C, '3', '!!'), (0x203D, 'V'), - (0x203E, '3', u' ̅'), + (0x203E, '3', ' ̅'), (0x203F, 'V'), - (0x2047, '3', u'??'), - (0x2048, '3', u'?!'), - (0x2049, '3', u'!?'), + (0x2047, '3', '??'), + (0x2048, '3', '?!'), + (0x2049, '3', '!?'), (0x204A, 'V'), - (0x2057, 'M', u'′′′′'), + (0x2057, 'M', '′′′′'), (0x2058, 'V'), - (0x205F, '3', u' '), + (0x205F, '3', ' '), (0x2060, 'I'), (0x2061, 'X'), (0x2064, 'I'), (0x2065, 'X'), - (0x2070, 'M', u'0'), - (0x2071, 'M', u'i'), + (0x2070, 'M', '0'), + (0x2071, 'M', 'i'), (0x2072, 'X'), - (0x2074, 'M', u'4'), - (0x2075, 'M', u'5'), - (0x2076, 'M', u'6'), - (0x2077, 'M', u'7'), - (0x2078, 'M', u'8'), - (0x2079, 'M', u'9'), - (0x207A, '3', u'+'), - (0x207B, 'M', u'−'), - (0x207C, '3', u'='), - (0x207D, '3', u'('), - (0x207E, '3', u')'), - (0x207F, 'M', u'n'), - (0x2080, 'M', u'0'), - (0x2081, 'M', u'1'), - (0x2082, 'M', u'2'), - (0x2083, 'M', u'3'), - (0x2084, 'M', u'4'), - (0x2085, 'M', u'5'), - (0x2086, 'M', u'6'), - (0x2087, 'M', u'7'), - (0x2088, 'M', u'8'), - (0x2089, 'M', u'9'), - (0x208A, '3', u'+'), - (0x208B, 'M', u'−'), - (0x208C, '3', u'='), - (0x208D, '3', u'('), - (0x208E, '3', u')'), + (0x2074, 'M', '4'), + (0x2075, 'M', '5'), + (0x2076, 'M', '6'), + (0x2077, 'M', '7'), + (0x2078, 'M', '8'), + (0x2079, 'M', '9'), + (0x207A, '3', '+'), + (0x207B, 'M', '−'), + (0x207C, '3', '='), + (0x207D, '3', '('), + (0x207E, '3', ')'), + (0x207F, 'M', 'n'), + (0x2080, 'M', '0'), + (0x2081, 'M', '1'), + (0x2082, 'M', '2'), + (0x2083, 'M', '3'), + (0x2084, 'M', '4'), + (0x2085, 'M', '5'), + (0x2086, 'M', '6'), + (0x2087, 'M', '7'), + (0x2088, 'M', '8'), + (0x2089, 'M', '9'), + (0x208A, '3', '+'), + (0x208B, 'M', '−'), + (0x208C, '3', '='), + (0x208D, '3', '('), + (0x208E, '3', ')'), (0x208F, 'X'), - (0x2090, 'M', u'a'), - (0x2091, 'M', u'e'), - (0x2092, 'M', u'o'), - (0x2093, 'M', u'x'), - (0x2094, 'M', u'ə'), - (0x2095, 'M', u'h'), - (0x2096, 'M', u'k'), - (0x2097, 'M', u'l'), - (0x2098, 'M', u'm'), - (0x2099, 'M', u'n'), - (0x209A, 'M', u'p'), - (0x209B, 'M', u's'), - (0x209C, 'M', u't'), + (0x2090, 'M', 'a'), + (0x2091, 'M', 'e'), + (0x2092, 'M', 'o'), + (0x2093, 'M', 'x'), + (0x2094, 'M', 'ə'), + (0x2095, 'M', 'h'), + (0x2096, 'M', 'k'), + (0x2097, 'M', 'l'), + (0x2098, 'M', 'm'), + (0x2099, 'M', 'n'), + (0x209A, 'M', 'p'), + (0x209B, 'M', 's'), + (0x209C, 'M', 't'), (0x209D, 'X'), (0x20A0, 'V'), - (0x20A8, 'M', u'rs'), + (0x20A8, 'M', 'rs'), (0x20A9, 'V'), (0x20C0, 'X'), (0x20D0, 'V'), (0x20F1, 'X'), - (0x2100, '3', u'a/c'), - (0x2101, '3', u'a/s'), - (0x2102, 'M', u'c'), - (0x2103, 'M', u'°c'), - (0x2104, 'V'), - (0x2105, '3', u'c/o'), - (0x2106, '3', u'c/u'), - (0x2107, 'M', u'ɛ'), - (0x2108, 'V'), - (0x2109, 'M', u'°f'), - (0x210A, 'M', u'g'), - (0x210B, 'M', u'h'), - (0x210F, 'M', u'ħ'), - (0x2110, 'M', u'i'), - (0x2112, 'M', u'l'), - (0x2114, 'V'), - (0x2115, 'M', u'n'), - (0x2116, 'M', u'no'), - (0x2117, 'V'), - (0x2119, 'M', u'p'), - (0x211A, 'M', u'q'), - (0x211B, 'M', u'r'), - (0x211E, 'V'), - (0x2120, 'M', u'sm'), - (0x2121, 'M', u'tel'), - (0x2122, 'M', u'tm'), - (0x2123, 'V'), - (0x2124, 'M', u'z'), - (0x2125, 'V'), - (0x2126, 'M', u'ω'), - (0x2127, 'V'), - (0x2128, 'M', u'z'), - (0x2129, 'V'), + (0x2100, '3', 'a/c'), + (0x2101, '3', 'a/s'), ] def _seg_22(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x212A, 'M', u'k'), - (0x212B, 'M', u'å'), - (0x212C, 'M', u'b'), - (0x212D, 'M', u'c'), + (0x2102, 'M', 'c'), + (0x2103, 'M', '°c'), + (0x2104, 'V'), + (0x2105, '3', 'c/o'), + (0x2106, '3', 'c/u'), + (0x2107, 'M', 'ɛ'), + (0x2108, 'V'), + (0x2109, 'M', '°f'), + (0x210A, 'M', 'g'), + (0x210B, 'M', 'h'), + (0x210F, 'M', 'ħ'), + (0x2110, 'M', 'i'), + (0x2112, 'M', 'l'), + (0x2114, 'V'), + (0x2115, 'M', 'n'), + (0x2116, 'M', 'no'), + (0x2117, 'V'), + (0x2119, 'M', 'p'), + (0x211A, 'M', 'q'), + (0x211B, 'M', 'r'), + (0x211E, 'V'), + (0x2120, 'M', 'sm'), + (0x2121, 'M', 'tel'), + (0x2122, 'M', 'tm'), + (0x2123, 'V'), + (0x2124, 'M', 'z'), + (0x2125, 'V'), + (0x2126, 'M', 'ω'), + (0x2127, 'V'), + (0x2128, 'M', 'z'), + (0x2129, 'V'), + (0x212A, 'M', 'k'), + (0x212B, 'M', 'å'), + (0x212C, 'M', 'b'), + (0x212D, 'M', 'c'), (0x212E, 'V'), - (0x212F, 'M', u'e'), - (0x2131, 'M', u'f'), + (0x212F, 'M', 'e'), + (0x2131, 'M', 'f'), (0x2132, 'X'), - (0x2133, 'M', u'm'), - (0x2134, 'M', u'o'), - (0x2135, 'M', u'א'), - (0x2136, 'M', u'ב'), - (0x2137, 'M', u'ג'), - (0x2138, 'M', u'ד'), - (0x2139, 'M', u'i'), + (0x2133, 'M', 'm'), + (0x2134, 'M', 'o'), + (0x2135, 'M', 'א'), + (0x2136, 'M', 'ב'), + (0x2137, 'M', 'ג'), + (0x2138, 'M', 'ד'), + (0x2139, 'M', 'i'), (0x213A, 'V'), - (0x213B, 'M', u'fax'), - (0x213C, 'M', u'π'), - (0x213D, 'M', u'γ'), - (0x213F, 'M', u'π'), - (0x2140, 'M', u'∑'), + (0x213B, 'M', 'fax'), + (0x213C, 'M', 'π'), + (0x213D, 'M', 'γ'), + (0x213F, 'M', 'π'), + (0x2140, 'M', '∑'), (0x2141, 'V'), - (0x2145, 'M', u'd'), - (0x2147, 'M', u'e'), - (0x2148, 'M', u'i'), - (0x2149, 'M', u'j'), + (0x2145, 'M', 'd'), + (0x2147, 'M', 'e'), + (0x2148, 'M', 'i'), + (0x2149, 'M', 'j'), (0x214A, 'V'), - (0x2150, 'M', u'1⁄7'), - (0x2151, 'M', u'1⁄9'), - (0x2152, 'M', u'1⁄10'), - (0x2153, 'M', u'1⁄3'), - (0x2154, 'M', u'2⁄3'), - (0x2155, 'M', u'1⁄5'), - (0x2156, 'M', u'2⁄5'), - (0x2157, 'M', u'3⁄5'), - (0x2158, 'M', u'4⁄5'), - (0x2159, 'M', u'1⁄6'), - (0x215A, 'M', u'5⁄6'), - (0x215B, 'M', u'1⁄8'), - (0x215C, 'M', u'3⁄8'), - (0x215D, 'M', u'5⁄8'), - (0x215E, 'M', u'7⁄8'), - (0x215F, 'M', u'1⁄'), - (0x2160, 'M', u'i'), - (0x2161, 'M', u'ii'), - (0x2162, 'M', u'iii'), - (0x2163, 'M', u'iv'), - (0x2164, 'M', u'v'), - (0x2165, 'M', u'vi'), - (0x2166, 'M', u'vii'), - (0x2167, 'M', u'viii'), - (0x2168, 'M', u'ix'), - (0x2169, 'M', u'x'), - (0x216A, 'M', u'xi'), - (0x216B, 'M', u'xii'), - (0x216C, 'M', u'l'), - (0x216D, 'M', u'c'), - (0x216E, 'M', u'd'), - (0x216F, 'M', u'm'), - (0x2170, 'M', u'i'), - (0x2171, 'M', u'ii'), - (0x2172, 'M', u'iii'), - (0x2173, 'M', u'iv'), - (0x2174, 'M', u'v'), - (0x2175, 'M', u'vi'), - (0x2176, 'M', u'vii'), - (0x2177, 'M', u'viii'), - (0x2178, 'M', u'ix'), - (0x2179, 'M', u'x'), - (0x217A, 'M', u'xi'), - (0x217B, 'M', u'xii'), - (0x217C, 'M', u'l'), - (0x217D, 'M', u'c'), - (0x217E, 'M', u'd'), - (0x217F, 'M', u'm'), + (0x2150, 'M', '1⁄7'), + (0x2151, 'M', '1⁄9'), + (0x2152, 'M', '1⁄10'), + (0x2153, 'M', '1⁄3'), + (0x2154, 'M', '2⁄3'), + (0x2155, 'M', '1⁄5'), + (0x2156, 'M', '2⁄5'), + (0x2157, 'M', '3⁄5'), + (0x2158, 'M', '4⁄5'), + (0x2159, 'M', '1⁄6'), + (0x215A, 'M', '5⁄6'), + (0x215B, 'M', '1⁄8'), + (0x215C, 'M', '3⁄8'), + (0x215D, 'M', '5⁄8'), + (0x215E, 'M', '7⁄8'), + (0x215F, 'M', '1⁄'), + (0x2160, 'M', 'i'), + (0x2161, 'M', 'ii'), + (0x2162, 'M', 'iii'), + (0x2163, 'M', 'iv'), + (0x2164, 'M', 'v'), + (0x2165, 'M', 'vi'), + (0x2166, 'M', 'vii'), + (0x2167, 'M', 'viii'), + (0x2168, 'M', 'ix'), + (0x2169, 'M', 'x'), + (0x216A, 'M', 'xi'), + (0x216B, 'M', 'xii'), + (0x216C, 'M', 'l'), + (0x216D, 'M', 'c'), + (0x216E, 'M', 'd'), + (0x216F, 'M', 'm'), + (0x2170, 'M', 'i'), + (0x2171, 'M', 'ii'), + (0x2172, 'M', 'iii'), + (0x2173, 'M', 'iv'), + (0x2174, 'M', 'v'), + (0x2175, 'M', 'vi'), + (0x2176, 'M', 'vii'), + (0x2177, 'M', 'viii'), + (0x2178, 'M', 'ix'), + (0x2179, 'M', 'x'), + ] + +def _seg_23(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ + (0x217A, 'M', 'xi'), + (0x217B, 'M', 'xii'), + (0x217C, 'M', 'l'), + (0x217D, 'M', 'c'), + (0x217E, 'M', 'd'), + (0x217F, 'M', 'm'), (0x2180, 'V'), (0x2183, 'X'), (0x2184, 'V'), - (0x2189, 'M', u'0⁄3'), + (0x2189, 'M', '0⁄3'), (0x218A, 'V'), (0x218C, 'X'), (0x2190, 'V'), - (0x222C, 'M', u'∫∫'), - (0x222D, 'M', u'∫∫∫'), + (0x222C, 'M', '∫∫'), + (0x222D, 'M', '∫∫∫'), (0x222E, 'V'), - (0x222F, 'M', u'∮∮'), - (0x2230, 'M', u'∮∮∮'), + (0x222F, 'M', '∮∮'), + (0x2230, 'M', '∮∮∮'), (0x2231, 'V'), (0x2260, '3'), (0x2261, 'V'), (0x226E, '3'), (0x2270, 'V'), - (0x2329, 'M', u'〈'), - (0x232A, 'M', u'〉'), + (0x2329, 'M', '〈'), + (0x232A, 'M', '〉'), (0x232B, 'V'), (0x2427, 'X'), (0x2440, 'V'), (0x244B, 'X'), - (0x2460, 'M', u'1'), - (0x2461, 'M', u'2'), - ] - -def _seg_23(): - return [ - (0x2462, 'M', u'3'), - (0x2463, 'M', u'4'), - (0x2464, 'M', u'5'), - (0x2465, 'M', u'6'), - (0x2466, 'M', u'7'), - (0x2467, 'M', u'8'), - (0x2468, 'M', u'9'), - (0x2469, 'M', u'10'), - (0x246A, 'M', u'11'), - (0x246B, 'M', u'12'), - (0x246C, 'M', u'13'), - (0x246D, 'M', u'14'), - (0x246E, 'M', u'15'), - (0x246F, 'M', u'16'), - (0x2470, 'M', u'17'), - (0x2471, 'M', u'18'), - (0x2472, 'M', u'19'), - (0x2473, 'M', u'20'), - (0x2474, '3', u'(1)'), - (0x2475, '3', u'(2)'), - (0x2476, '3', u'(3)'), - (0x2477, '3', u'(4)'), - (0x2478, '3', u'(5)'), - (0x2479, '3', u'(6)'), - (0x247A, '3', u'(7)'), - (0x247B, '3', u'(8)'), - (0x247C, '3', u'(9)'), - (0x247D, '3', u'(10)'), - (0x247E, '3', u'(11)'), - (0x247F, '3', u'(12)'), - (0x2480, '3', u'(13)'), - (0x2481, '3', u'(14)'), - (0x2482, '3', u'(15)'), - (0x2483, '3', u'(16)'), - (0x2484, '3', u'(17)'), - (0x2485, '3', u'(18)'), - (0x2486, '3', u'(19)'), - (0x2487, '3', u'(20)'), + (0x2460, 'M', '1'), + (0x2461, 'M', '2'), + (0x2462, 'M', '3'), + (0x2463, 'M', '4'), + (0x2464, 'M', '5'), + (0x2465, 'M', '6'), + (0x2466, 'M', '7'), + (0x2467, 'M', '8'), + (0x2468, 'M', '9'), + (0x2469, 'M', '10'), + (0x246A, 'M', '11'), + (0x246B, 'M', '12'), + (0x246C, 'M', '13'), + (0x246D, 'M', '14'), + (0x246E, 'M', '15'), + (0x246F, 'M', '16'), + (0x2470, 'M', '17'), + (0x2471, 'M', '18'), + (0x2472, 'M', '19'), + (0x2473, 'M', '20'), + (0x2474, '3', '(1)'), + (0x2475, '3', '(2)'), + (0x2476, '3', '(3)'), + (0x2477, '3', '(4)'), + (0x2478, '3', '(5)'), + (0x2479, '3', '(6)'), + (0x247A, '3', '(7)'), + (0x247B, '3', '(8)'), + (0x247C, '3', '(9)'), + (0x247D, '3', '(10)'), + (0x247E, '3', '(11)'), + (0x247F, '3', '(12)'), + (0x2480, '3', '(13)'), + (0x2481, '3', '(14)'), + (0x2482, '3', '(15)'), + (0x2483, '3', '(16)'), + (0x2484, '3', '(17)'), + (0x2485, '3', '(18)'), + (0x2486, '3', '(19)'), + (0x2487, '3', '(20)'), (0x2488, 'X'), - (0x249C, '3', u'(a)'), - (0x249D, '3', u'(b)'), - (0x249E, '3', u'(c)'), - (0x249F, '3', u'(d)'), - (0x24A0, '3', u'(e)'), - (0x24A1, '3', u'(f)'), - (0x24A2, '3', u'(g)'), - (0x24A3, '3', u'(h)'), - (0x24A4, '3', u'(i)'), - (0x24A5, '3', u'(j)'), - (0x24A6, '3', u'(k)'), - (0x24A7, '3', u'(l)'), - (0x24A8, '3', u'(m)'), - (0x24A9, '3', u'(n)'), - (0x24AA, '3', u'(o)'), - (0x24AB, '3', u'(p)'), - (0x24AC, '3', u'(q)'), - (0x24AD, '3', u'(r)'), - (0x24AE, '3', u'(s)'), - (0x24AF, '3', u'(t)'), - (0x24B0, '3', u'(u)'), - (0x24B1, '3', u'(v)'), - (0x24B2, '3', u'(w)'), - (0x24B3, '3', u'(x)'), - (0x24B4, '3', u'(y)'), - (0x24B5, '3', u'(z)'), - (0x24B6, 'M', u'a'), - (0x24B7, 'M', u'b'), - (0x24B8, 'M', u'c'), - (0x24B9, 'M', u'd'), - (0x24BA, 'M', u'e'), - (0x24BB, 'M', u'f'), - (0x24BC, 'M', u'g'), - (0x24BD, 'M', u'h'), - (0x24BE, 'M', u'i'), - (0x24BF, 'M', u'j'), - (0x24C0, 'M', u'k'), - (0x24C1, 'M', u'l'), - (0x24C2, 'M', u'm'), - (0x24C3, 'M', u'n'), - (0x24C4, 'M', u'o'), - (0x24C5, 'M', u'p'), - (0x24C6, 'M', u'q'), - (0x24C7, 'M', u'r'), - (0x24C8, 'M', u's'), - (0x24C9, 'M', u't'), - (0x24CA, 'M', u'u'), - (0x24CB, 'M', u'v'), - (0x24CC, 'M', u'w'), - (0x24CD, 'M', u'x'), - (0x24CE, 'M', u'y'), - (0x24CF, 'M', u'z'), - (0x24D0, 'M', u'a'), - (0x24D1, 'M', u'b'), - (0x24D2, 'M', u'c'), - (0x24D3, 'M', u'd'), - (0x24D4, 'M', u'e'), - (0x24D5, 'M', u'f'), - (0x24D6, 'M', u'g'), - (0x24D7, 'M', u'h'), - (0x24D8, 'M', u'i'), + (0x249C, '3', '(a)'), + (0x249D, '3', '(b)'), + (0x249E, '3', '(c)'), + (0x249F, '3', '(d)'), + (0x24A0, '3', '(e)'), + (0x24A1, '3', '(f)'), + (0x24A2, '3', '(g)'), + (0x24A3, '3', '(h)'), + (0x24A4, '3', '(i)'), + (0x24A5, '3', '(j)'), + (0x24A6, '3', '(k)'), + (0x24A7, '3', '(l)'), + (0x24A8, '3', '(m)'), + (0x24A9, '3', '(n)'), + (0x24AA, '3', '(o)'), + (0x24AB, '3', '(p)'), + (0x24AC, '3', '(q)'), + (0x24AD, '3', '(r)'), + (0x24AE, '3', '(s)'), + (0x24AF, '3', '(t)'), + (0x24B0, '3', '(u)'), + (0x24B1, '3', '(v)'), + (0x24B2, '3', '(w)'), + (0x24B3, '3', '(x)'), + (0x24B4, '3', '(y)'), + (0x24B5, '3', '(z)'), + (0x24B6, 'M', 'a'), + (0x24B7, 'M', 'b'), + (0x24B8, 'M', 'c'), + (0x24B9, 'M', 'd'), ] def _seg_24(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x24D9, 'M', u'j'), - (0x24DA, 'M', u'k'), - (0x24DB, 'M', u'l'), - (0x24DC, 'M', u'm'), - (0x24DD, 'M', u'n'), - (0x24DE, 'M', u'o'), - (0x24DF, 'M', u'p'), - (0x24E0, 'M', u'q'), - (0x24E1, 'M', u'r'), - (0x24E2, 'M', u's'), - (0x24E3, 'M', u't'), - (0x24E4, 'M', u'u'), - (0x24E5, 'M', u'v'), - (0x24E6, 'M', u'w'), - (0x24E7, 'M', u'x'), - (0x24E8, 'M', u'y'), - (0x24E9, 'M', u'z'), - (0x24EA, 'M', u'0'), + (0x24BA, 'M', 'e'), + (0x24BB, 'M', 'f'), + (0x24BC, 'M', 'g'), + (0x24BD, 'M', 'h'), + (0x24BE, 'M', 'i'), + (0x24BF, 'M', 'j'), + (0x24C0, 'M', 'k'), + (0x24C1, 'M', 'l'), + (0x24C2, 'M', 'm'), + (0x24C3, 'M', 'n'), + (0x24C4, 'M', 'o'), + (0x24C5, 'M', 'p'), + (0x24C6, 'M', 'q'), + (0x24C7, 'M', 'r'), + (0x24C8, 'M', 's'), + (0x24C9, 'M', 't'), + (0x24CA, 'M', 'u'), + (0x24CB, 'M', 'v'), + (0x24CC, 'M', 'w'), + (0x24CD, 'M', 'x'), + (0x24CE, 'M', 'y'), + (0x24CF, 'M', 'z'), + (0x24D0, 'M', 'a'), + (0x24D1, 'M', 'b'), + (0x24D2, 'M', 'c'), + (0x24D3, 'M', 'd'), + (0x24D4, 'M', 'e'), + (0x24D5, 'M', 'f'), + (0x24D6, 'M', 'g'), + (0x24D7, 'M', 'h'), + (0x24D8, 'M', 'i'), + (0x24D9, 'M', 'j'), + (0x24DA, 'M', 'k'), + (0x24DB, 'M', 'l'), + (0x24DC, 'M', 'm'), + (0x24DD, 'M', 'n'), + (0x24DE, 'M', 'o'), + (0x24DF, 'M', 'p'), + (0x24E0, 'M', 'q'), + (0x24E1, 'M', 'r'), + (0x24E2, 'M', 's'), + (0x24E3, 'M', 't'), + (0x24E4, 'M', 'u'), + (0x24E5, 'M', 'v'), + (0x24E6, 'M', 'w'), + (0x24E7, 'M', 'x'), + (0x24E8, 'M', 'y'), + (0x24E9, 'M', 'z'), + (0x24EA, 'M', '0'), (0x24EB, 'V'), - (0x2A0C, 'M', u'∫∫∫∫'), + (0x2A0C, 'M', '∫∫∫∫'), (0x2A0D, 'V'), - (0x2A74, '3', u'::='), - (0x2A75, '3', u'=='), - (0x2A76, '3', u'==='), + (0x2A74, '3', '::='), + (0x2A75, '3', '=='), + (0x2A76, '3', '==='), (0x2A77, 'V'), - (0x2ADC, 'M', u'⫝̸'), + (0x2ADC, 'M', '⫝̸'), (0x2ADD, 'V'), (0x2B74, 'X'), (0x2B76, 'V'), (0x2B96, 'X'), - (0x2B98, 'V'), - (0x2BC9, 'X'), - (0x2BCA, 'V'), - (0x2BFF, 'X'), - (0x2C00, 'M', u'ⰰ'), - (0x2C01, 'M', u'ⰱ'), - (0x2C02, 'M', u'ⰲ'), - (0x2C03, 'M', u'ⰳ'), - (0x2C04, 'M', u'ⰴ'), - (0x2C05, 'M', u'ⰵ'), - (0x2C06, 'M', u'ⰶ'), - (0x2C07, 'M', u'ⰷ'), - (0x2C08, 'M', u'ⰸ'), - (0x2C09, 'M', u'ⰹ'), - (0x2C0A, 'M', u'ⰺ'), - (0x2C0B, 'M', u'ⰻ'), - (0x2C0C, 'M', u'ⰼ'), - (0x2C0D, 'M', u'ⰽ'), - (0x2C0E, 'M', u'ⰾ'), - (0x2C0F, 'M', u'ⰿ'), - (0x2C10, 'M', u'ⱀ'), - (0x2C11, 'M', u'ⱁ'), - (0x2C12, 'M', u'ⱂ'), - (0x2C13, 'M', u'ⱃ'), - (0x2C14, 'M', u'ⱄ'), - (0x2C15, 'M', u'ⱅ'), - (0x2C16, 'M', u'ⱆ'), - (0x2C17, 'M', u'ⱇ'), - (0x2C18, 'M', u'ⱈ'), - (0x2C19, 'M', u'ⱉ'), - (0x2C1A, 'M', u'ⱊ'), - (0x2C1B, 'M', u'ⱋ'), - (0x2C1C, 'M', u'ⱌ'), - (0x2C1D, 'M', u'ⱍ'), - (0x2C1E, 'M', u'ⱎ'), - (0x2C1F, 'M', u'ⱏ'), - (0x2C20, 'M', u'ⱐ'), - (0x2C21, 'M', u'ⱑ'), - (0x2C22, 'M', u'ⱒ'), - (0x2C23, 'M', u'ⱓ'), - (0x2C24, 'M', u'ⱔ'), - (0x2C25, 'M', u'ⱕ'), - (0x2C26, 'M', u'ⱖ'), - (0x2C27, 'M', u'ⱗ'), - (0x2C28, 'M', u'ⱘ'), - (0x2C29, 'M', u'ⱙ'), - (0x2C2A, 'M', u'ⱚ'), - (0x2C2B, 'M', u'ⱛ'), - (0x2C2C, 'M', u'ⱜ'), - (0x2C2D, 'M', u'ⱝ'), - (0x2C2E, 'M', u'ⱞ'), - (0x2C2F, 'X'), - (0x2C30, 'V'), - (0x2C5F, 'X'), - (0x2C60, 'M', u'ⱡ'), - (0x2C61, 'V'), - (0x2C62, 'M', u'ɫ'), - (0x2C63, 'M', u'ᵽ'), - (0x2C64, 'M', u'ɽ'), - (0x2C65, 'V'), - (0x2C67, 'M', u'ⱨ'), - (0x2C68, 'V'), - (0x2C69, 'M', u'ⱪ'), - (0x2C6A, 'V'), - (0x2C6B, 'M', u'ⱬ'), - (0x2C6C, 'V'), - (0x2C6D, 'M', u'ɑ'), - (0x2C6E, 'M', u'ɱ'), - (0x2C6F, 'M', u'ɐ'), - (0x2C70, 'M', u'ɒ'), + (0x2B97, 'V'), + (0x2C00, 'M', 'ⰰ'), + (0x2C01, 'M', 'ⰱ'), + (0x2C02, 'M', 'ⰲ'), + (0x2C03, 'M', 'ⰳ'), + (0x2C04, 'M', 'ⰴ'), + (0x2C05, 'M', 'ⰵ'), + (0x2C06, 'M', 'ⰶ'), + (0x2C07, 'M', 'ⰷ'), + (0x2C08, 'M', 'ⰸ'), + (0x2C09, 'M', 'ⰹ'), + (0x2C0A, 'M', 'ⰺ'), + (0x2C0B, 'M', 'ⰻ'), + (0x2C0C, 'M', 'ⰼ'), + (0x2C0D, 'M', 'ⰽ'), + (0x2C0E, 'M', 'ⰾ'), + (0x2C0F, 'M', 'ⰿ'), + (0x2C10, 'M', 'ⱀ'), + (0x2C11, 'M', 'ⱁ'), + (0x2C12, 'M', 'ⱂ'), + (0x2C13, 'M', 'ⱃ'), + (0x2C14, 'M', 'ⱄ'), + (0x2C15, 'M', 'ⱅ'), + (0x2C16, 'M', 'ⱆ'), + (0x2C17, 'M', 'ⱇ'), + (0x2C18, 'M', 'ⱈ'), + (0x2C19, 'M', 'ⱉ'), + (0x2C1A, 'M', 'ⱊ'), + (0x2C1B, 'M', 'ⱋ'), + (0x2C1C, 'M', 'ⱌ'), + (0x2C1D, 'M', 'ⱍ'), + (0x2C1E, 'M', 'ⱎ'), + (0x2C1F, 'M', 'ⱏ'), + (0x2C20, 'M', 'ⱐ'), + (0x2C21, 'M', 'ⱑ'), + (0x2C22, 'M', 'ⱒ'), + (0x2C23, 'M', 'ⱓ'), + (0x2C24, 'M', 'ⱔ'), + (0x2C25, 'M', 'ⱕ'), ] def _seg_25(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ + (0x2C26, 'M', 'ⱖ'), + (0x2C27, 'M', 'ⱗ'), + (0x2C28, 'M', 'ⱘ'), + (0x2C29, 'M', 'ⱙ'), + (0x2C2A, 'M', 'ⱚ'), + (0x2C2B, 'M', 'ⱛ'), + (0x2C2C, 'M', 'ⱜ'), + (0x2C2D, 'M', 'ⱝ'), + (0x2C2E, 'M', 'ⱞ'), + (0x2C2F, 'X'), + (0x2C30, 'V'), + (0x2C5F, 'X'), + (0x2C60, 'M', 'ⱡ'), + (0x2C61, 'V'), + (0x2C62, 'M', 'ɫ'), + (0x2C63, 'M', 'ᵽ'), + (0x2C64, 'M', 'ɽ'), + (0x2C65, 'V'), + (0x2C67, 'M', 'ⱨ'), + (0x2C68, 'V'), + (0x2C69, 'M', 'ⱪ'), + (0x2C6A, 'V'), + (0x2C6B, 'M', 'ⱬ'), + (0x2C6C, 'V'), + (0x2C6D, 'M', 'ɑ'), + (0x2C6E, 'M', 'ɱ'), + (0x2C6F, 'M', 'ɐ'), + (0x2C70, 'M', 'ɒ'), (0x2C71, 'V'), - (0x2C72, 'M', u'ⱳ'), + (0x2C72, 'M', 'ⱳ'), (0x2C73, 'V'), - (0x2C75, 'M', u'ⱶ'), + (0x2C75, 'M', 'ⱶ'), (0x2C76, 'V'), - (0x2C7C, 'M', u'j'), - (0x2C7D, 'M', u'v'), - (0x2C7E, 'M', u'ȿ'), - (0x2C7F, 'M', u'ɀ'), - (0x2C80, 'M', u'ⲁ'), + (0x2C7C, 'M', 'j'), + (0x2C7D, 'M', 'v'), + (0x2C7E, 'M', 'ȿ'), + (0x2C7F, 'M', 'ɀ'), + (0x2C80, 'M', 'ⲁ'), (0x2C81, 'V'), - (0x2C82, 'M', u'ⲃ'), + (0x2C82, 'M', 'ⲃ'), (0x2C83, 'V'), - (0x2C84, 'M', u'ⲅ'), + (0x2C84, 'M', 'ⲅ'), (0x2C85, 'V'), - (0x2C86, 'M', u'ⲇ'), + (0x2C86, 'M', 'ⲇ'), (0x2C87, 'V'), - (0x2C88, 'M', u'ⲉ'), + (0x2C88, 'M', 'ⲉ'), (0x2C89, 'V'), - (0x2C8A, 'M', u'ⲋ'), + (0x2C8A, 'M', 'ⲋ'), (0x2C8B, 'V'), - (0x2C8C, 'M', u'ⲍ'), + (0x2C8C, 'M', 'ⲍ'), (0x2C8D, 'V'), - (0x2C8E, 'M', u'ⲏ'), + (0x2C8E, 'M', 'ⲏ'), (0x2C8F, 'V'), - (0x2C90, 'M', u'ⲑ'), + (0x2C90, 'M', 'ⲑ'), (0x2C91, 'V'), - (0x2C92, 'M', u'ⲓ'), + (0x2C92, 'M', 'ⲓ'), (0x2C93, 'V'), - (0x2C94, 'M', u'ⲕ'), + (0x2C94, 'M', 'ⲕ'), (0x2C95, 'V'), - (0x2C96, 'M', u'ⲗ'), + (0x2C96, 'M', 'ⲗ'), (0x2C97, 'V'), - (0x2C98, 'M', u'ⲙ'), + (0x2C98, 'M', 'ⲙ'), (0x2C99, 'V'), - (0x2C9A, 'M', u'ⲛ'), + (0x2C9A, 'M', 'ⲛ'), (0x2C9B, 'V'), - (0x2C9C, 'M', u'ⲝ'), + (0x2C9C, 'M', 'ⲝ'), (0x2C9D, 'V'), - (0x2C9E, 'M', u'ⲟ'), + (0x2C9E, 'M', 'ⲟ'), (0x2C9F, 'V'), - (0x2CA0, 'M', u'ⲡ'), + (0x2CA0, 'M', 'ⲡ'), (0x2CA1, 'V'), - (0x2CA2, 'M', u'ⲣ'), + (0x2CA2, 'M', 'ⲣ'), (0x2CA3, 'V'), - (0x2CA4, 'M', u'ⲥ'), + (0x2CA4, 'M', 'ⲥ'), (0x2CA5, 'V'), - (0x2CA6, 'M', u'ⲧ'), + (0x2CA6, 'M', 'ⲧ'), (0x2CA7, 'V'), - (0x2CA8, 'M', u'ⲩ'), + (0x2CA8, 'M', 'ⲩ'), (0x2CA9, 'V'), - (0x2CAA, 'M', u'ⲫ'), + (0x2CAA, 'M', 'ⲫ'), (0x2CAB, 'V'), - (0x2CAC, 'M', u'ⲭ'), + (0x2CAC, 'M', 'ⲭ'), (0x2CAD, 'V'), - (0x2CAE, 'M', u'ⲯ'), + (0x2CAE, 'M', 'ⲯ'), (0x2CAF, 'V'), - (0x2CB0, 'M', u'ⲱ'), + (0x2CB0, 'M', 'ⲱ'), (0x2CB1, 'V'), - (0x2CB2, 'M', u'ⲳ'), + (0x2CB2, 'M', 'ⲳ'), (0x2CB3, 'V'), - (0x2CB4, 'M', u'ⲵ'), + (0x2CB4, 'M', 'ⲵ'), (0x2CB5, 'V'), - (0x2CB6, 'M', u'ⲷ'), + (0x2CB6, 'M', 'ⲷ'), (0x2CB7, 'V'), - (0x2CB8, 'M', u'ⲹ'), + (0x2CB8, 'M', 'ⲹ'), (0x2CB9, 'V'), - (0x2CBA, 'M', u'ⲻ'), + (0x2CBA, 'M', 'ⲻ'), (0x2CBB, 'V'), - (0x2CBC, 'M', u'ⲽ'), + (0x2CBC, 'M', 'ⲽ'), (0x2CBD, 'V'), - (0x2CBE, 'M', u'ⲿ'), - (0x2CBF, 'V'), - (0x2CC0, 'M', u'ⳁ'), - (0x2CC1, 'V'), - (0x2CC2, 'M', u'ⳃ'), - (0x2CC3, 'V'), - (0x2CC4, 'M', u'ⳅ'), - (0x2CC5, 'V'), - (0x2CC6, 'M', u'ⳇ'), - (0x2CC7, 'V'), - (0x2CC8, 'M', u'ⳉ'), - (0x2CC9, 'V'), - (0x2CCA, 'M', u'ⳋ'), - (0x2CCB, 'V'), - (0x2CCC, 'M', u'ⳍ'), - (0x2CCD, 'V'), - (0x2CCE, 'M', u'ⳏ'), - (0x2CCF, 'V'), - (0x2CD0, 'M', u'ⳑ'), - (0x2CD1, 'V'), - (0x2CD2, 'M', u'ⳓ'), - (0x2CD3, 'V'), - (0x2CD4, 'M', u'ⳕ'), - (0x2CD5, 'V'), - (0x2CD6, 'M', u'ⳗ'), - (0x2CD7, 'V'), - (0x2CD8, 'M', u'ⳙ'), - (0x2CD9, 'V'), - (0x2CDA, 'M', u'ⳛ'), + (0x2CBE, 'M', 'ⲿ'), ] def _seg_26(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ + (0x2CBF, 'V'), + (0x2CC0, 'M', 'ⳁ'), + (0x2CC1, 'V'), + (0x2CC2, 'M', 'ⳃ'), + (0x2CC3, 'V'), + (0x2CC4, 'M', 'ⳅ'), + (0x2CC5, 'V'), + (0x2CC6, 'M', 'ⳇ'), + (0x2CC7, 'V'), + (0x2CC8, 'M', 'ⳉ'), + (0x2CC9, 'V'), + (0x2CCA, 'M', 'ⳋ'), + (0x2CCB, 'V'), + (0x2CCC, 'M', 'ⳍ'), + (0x2CCD, 'V'), + (0x2CCE, 'M', 'ⳏ'), + (0x2CCF, 'V'), + (0x2CD0, 'M', 'ⳑ'), + (0x2CD1, 'V'), + (0x2CD2, 'M', 'ⳓ'), + (0x2CD3, 'V'), + (0x2CD4, 'M', 'ⳕ'), + (0x2CD5, 'V'), + (0x2CD6, 'M', 'ⳗ'), + (0x2CD7, 'V'), + (0x2CD8, 'M', 'ⳙ'), + (0x2CD9, 'V'), + (0x2CDA, 'M', 'ⳛ'), (0x2CDB, 'V'), - (0x2CDC, 'M', u'ⳝ'), + (0x2CDC, 'M', 'ⳝ'), (0x2CDD, 'V'), - (0x2CDE, 'M', u'ⳟ'), + (0x2CDE, 'M', 'ⳟ'), (0x2CDF, 'V'), - (0x2CE0, 'M', u'ⳡ'), + (0x2CE0, 'M', 'ⳡ'), (0x2CE1, 'V'), - (0x2CE2, 'M', u'ⳣ'), + (0x2CE2, 'M', 'ⳣ'), (0x2CE3, 'V'), - (0x2CEB, 'M', u'ⳬ'), + (0x2CEB, 'M', 'ⳬ'), (0x2CEC, 'V'), - (0x2CED, 'M', u'ⳮ'), + (0x2CED, 'M', 'ⳮ'), (0x2CEE, 'V'), - (0x2CF2, 'M', u'ⳳ'), + (0x2CF2, 'M', 'ⳳ'), (0x2CF3, 'V'), (0x2CF4, 'X'), (0x2CF9, 'V'), @@ -2735,7 +2791,7 @@ def _seg_26(): (0x2D2E, 'X'), (0x2D30, 'V'), (0x2D68, 'X'), - (0x2D6F, 'M', u'ⵡ'), + (0x2D6F, 'M', 'ⵡ'), (0x2D70, 'V'), (0x2D71, 'X'), (0x2D7F, 'V'), @@ -2757,1148 +2813,1172 @@ def _seg_26(): (0x2DD8, 'V'), (0x2DDF, 'X'), (0x2DE0, 'V'), - (0x2E4F, 'X'), + (0x2E53, 'X'), (0x2E80, 'V'), (0x2E9A, 'X'), (0x2E9B, 'V'), - (0x2E9F, 'M', u'母'), + (0x2E9F, 'M', '母'), (0x2EA0, 'V'), - (0x2EF3, 'M', u'龟'), + (0x2EF3, 'M', '龟'), (0x2EF4, 'X'), - (0x2F00, 'M', u'一'), - (0x2F01, 'M', u'丨'), - (0x2F02, 'M', u'丶'), - (0x2F03, 'M', u'丿'), - (0x2F04, 'M', u'乙'), - (0x2F05, 'M', u'亅'), - (0x2F06, 'M', u'二'), - (0x2F07, 'M', u'亠'), - (0x2F08, 'M', u'人'), - (0x2F09, 'M', u'儿'), - (0x2F0A, 'M', u'入'), - (0x2F0B, 'M', u'八'), - (0x2F0C, 'M', u'冂'), - (0x2F0D, 'M', u'冖'), - (0x2F0E, 'M', u'冫'), - (0x2F0F, 'M', u'几'), - (0x2F10, 'M', u'凵'), - (0x2F11, 'M', u'刀'), - (0x2F12, 'M', u'力'), - (0x2F13, 'M', u'勹'), - (0x2F14, 'M', u'匕'), - (0x2F15, 'M', u'匚'), - (0x2F16, 'M', u'匸'), - (0x2F17, 'M', u'十'), - (0x2F18, 'M', u'卜'), - (0x2F19, 'M', u'卩'), - (0x2F1A, 'M', u'厂'), - (0x2F1B, 'M', u'厶'), - (0x2F1C, 'M', u'又'), - (0x2F1D, 'M', u'口'), - (0x2F1E, 'M', u'囗'), - (0x2F1F, 'M', u'土'), - (0x2F20, 'M', u'士'), - (0x2F21, 'M', u'夂'), - (0x2F22, 'M', u'夊'), - (0x2F23, 'M', u'夕'), - (0x2F24, 'M', u'大'), - (0x2F25, 'M', u'女'), - (0x2F26, 'M', u'子'), - (0x2F27, 'M', u'宀'), - (0x2F28, 'M', u'寸'), - (0x2F29, 'M', u'小'), - (0x2F2A, 'M', u'尢'), - (0x2F2B, 'M', u'尸'), - (0x2F2C, 'M', u'屮'), - (0x2F2D, 'M', u'山'), + (0x2F00, 'M', '一'), + (0x2F01, 'M', '丨'), + (0x2F02, 'M', '丶'), + (0x2F03, 'M', '丿'), + (0x2F04, 'M', '乙'), + (0x2F05, 'M', '亅'), + (0x2F06, 'M', '二'), + (0x2F07, 'M', '亠'), + (0x2F08, 'M', '人'), + (0x2F09, 'M', '儿'), + (0x2F0A, 'M', '入'), + (0x2F0B, 'M', '八'), + (0x2F0C, 'M', '冂'), + (0x2F0D, 'M', '冖'), + (0x2F0E, 'M', '冫'), + (0x2F0F, 'M', '几'), + (0x2F10, 'M', '凵'), + (0x2F11, 'M', '刀'), ] def _seg_27(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x2F2E, 'M', u'巛'), - (0x2F2F, 'M', u'工'), - (0x2F30, 'M', u'己'), - (0x2F31, 'M', u'巾'), - (0x2F32, 'M', u'干'), - (0x2F33, 'M', u'幺'), - (0x2F34, 'M', u'广'), - (0x2F35, 'M', u'廴'), - (0x2F36, 'M', u'廾'), - (0x2F37, 'M', u'弋'), - (0x2F38, 'M', u'弓'), - (0x2F39, 'M', u'彐'), - (0x2F3A, 'M', u'彡'), - (0x2F3B, 'M', u'彳'), - (0x2F3C, 'M', u'心'), - (0x2F3D, 'M', u'戈'), - (0x2F3E, 'M', u'戶'), - (0x2F3F, 'M', u'手'), - (0x2F40, 'M', u'支'), - (0x2F41, 'M', u'攴'), - (0x2F42, 'M', u'文'), - (0x2F43, 'M', u'斗'), - (0x2F44, 'M', u'斤'), - (0x2F45, 'M', u'方'), - (0x2F46, 'M', u'无'), - (0x2F47, 'M', u'日'), - (0x2F48, 'M', u'曰'), - (0x2F49, 'M', u'月'), - (0x2F4A, 'M', u'木'), - (0x2F4B, 'M', u'欠'), - (0x2F4C, 'M', u'止'), - (0x2F4D, 'M', u'歹'), - (0x2F4E, 'M', u'殳'), - (0x2F4F, 'M', u'毋'), - (0x2F50, 'M', u'比'), - (0x2F51, 'M', u'毛'), - (0x2F52, 'M', u'氏'), - (0x2F53, 'M', u'气'), - (0x2F54, 'M', u'水'), - (0x2F55, 'M', u'火'), - (0x2F56, 'M', u'爪'), - (0x2F57, 'M', u'父'), - (0x2F58, 'M', u'爻'), - (0x2F59, 'M', u'爿'), - (0x2F5A, 'M', u'片'), - (0x2F5B, 'M', u'牙'), - (0x2F5C, 'M', u'牛'), - (0x2F5D, 'M', u'犬'), - (0x2F5E, 'M', u'玄'), - (0x2F5F, 'M', u'玉'), - (0x2F60, 'M', u'瓜'), - (0x2F61, 'M', u'瓦'), - (0x2F62, 'M', u'甘'), - (0x2F63, 'M', u'生'), - (0x2F64, 'M', u'用'), - (0x2F65, 'M', u'田'), - (0x2F66, 'M', u'疋'), - (0x2F67, 'M', u'疒'), - (0x2F68, 'M', u'癶'), - (0x2F69, 'M', u'白'), - (0x2F6A, 'M', u'皮'), - (0x2F6B, 'M', u'皿'), - (0x2F6C, 'M', u'目'), - (0x2F6D, 'M', u'矛'), - (0x2F6E, 'M', u'矢'), - (0x2F6F, 'M', u'石'), - (0x2F70, 'M', u'示'), - (0x2F71, 'M', u'禸'), - (0x2F72, 'M', u'禾'), - (0x2F73, 'M', u'穴'), - (0x2F74, 'M', u'立'), - (0x2F75, 'M', u'竹'), - (0x2F76, 'M', u'米'), - (0x2F77, 'M', u'糸'), - (0x2F78, 'M', u'缶'), - (0x2F79, 'M', u'网'), - (0x2F7A, 'M', u'羊'), - (0x2F7B, 'M', u'羽'), - (0x2F7C, 'M', u'老'), - (0x2F7D, 'M', u'而'), - (0x2F7E, 'M', u'耒'), - (0x2F7F, 'M', u'耳'), - (0x2F80, 'M', u'聿'), - (0x2F81, 'M', u'肉'), - (0x2F82, 'M', u'臣'), - (0x2F83, 'M', u'自'), - (0x2F84, 'M', u'至'), - (0x2F85, 'M', u'臼'), - (0x2F86, 'M', u'舌'), - (0x2F87, 'M', u'舛'), - (0x2F88, 'M', u'舟'), - (0x2F89, 'M', u'艮'), - (0x2F8A, 'M', u'色'), - (0x2F8B, 'M', u'艸'), - (0x2F8C, 'M', u'虍'), - (0x2F8D, 'M', u'虫'), - (0x2F8E, 'M', u'血'), - (0x2F8F, 'M', u'行'), - (0x2F90, 'M', u'衣'), - (0x2F91, 'M', u'襾'), + (0x2F12, 'M', '力'), + (0x2F13, 'M', '勹'), + (0x2F14, 'M', '匕'), + (0x2F15, 'M', '匚'), + (0x2F16, 'M', '匸'), + (0x2F17, 'M', '十'), + (0x2F18, 'M', '卜'), + (0x2F19, 'M', '卩'), + (0x2F1A, 'M', '厂'), + (0x2F1B, 'M', '厶'), + (0x2F1C, 'M', '又'), + (0x2F1D, 'M', '口'), + (0x2F1E, 'M', '囗'), + (0x2F1F, 'M', '土'), + (0x2F20, 'M', '士'), + (0x2F21, 'M', '夂'), + (0x2F22, 'M', '夊'), + (0x2F23, 'M', '夕'), + (0x2F24, 'M', '大'), + (0x2F25, 'M', '女'), + (0x2F26, 'M', '子'), + (0x2F27, 'M', '宀'), + (0x2F28, 'M', '寸'), + (0x2F29, 'M', '小'), + (0x2F2A, 'M', '尢'), + (0x2F2B, 'M', '尸'), + (0x2F2C, 'M', '屮'), + (0x2F2D, 'M', '山'), + (0x2F2E, 'M', '巛'), + (0x2F2F, 'M', '工'), + (0x2F30, 'M', '己'), + (0x2F31, 'M', '巾'), + (0x2F32, 'M', '干'), + (0x2F33, 'M', '幺'), + (0x2F34, 'M', '广'), + (0x2F35, 'M', '廴'), + (0x2F36, 'M', '廾'), + (0x2F37, 'M', '弋'), + (0x2F38, 'M', '弓'), + (0x2F39, 'M', '彐'), + (0x2F3A, 'M', '彡'), + (0x2F3B, 'M', '彳'), + (0x2F3C, 'M', '心'), + (0x2F3D, 'M', '戈'), + (0x2F3E, 'M', '戶'), + (0x2F3F, 'M', '手'), + (0x2F40, 'M', '支'), + (0x2F41, 'M', '攴'), + (0x2F42, 'M', '文'), + (0x2F43, 'M', '斗'), + (0x2F44, 'M', '斤'), + (0x2F45, 'M', '方'), + (0x2F46, 'M', '无'), + (0x2F47, 'M', '日'), + (0x2F48, 'M', '曰'), + (0x2F49, 'M', '月'), + (0x2F4A, 'M', '木'), + (0x2F4B, 'M', '欠'), + (0x2F4C, 'M', '止'), + (0x2F4D, 'M', '歹'), + (0x2F4E, 'M', '殳'), + (0x2F4F, 'M', '毋'), + (0x2F50, 'M', '比'), + (0x2F51, 'M', '毛'), + (0x2F52, 'M', '氏'), + (0x2F53, 'M', '气'), + (0x2F54, 'M', '水'), + (0x2F55, 'M', '火'), + (0x2F56, 'M', '爪'), + (0x2F57, 'M', '父'), + (0x2F58, 'M', '爻'), + (0x2F59, 'M', '爿'), + (0x2F5A, 'M', '片'), + (0x2F5B, 'M', '牙'), + (0x2F5C, 'M', '牛'), + (0x2F5D, 'M', '犬'), + (0x2F5E, 'M', '玄'), + (0x2F5F, 'M', '玉'), + (0x2F60, 'M', '瓜'), + (0x2F61, 'M', '瓦'), + (0x2F62, 'M', '甘'), + (0x2F63, 'M', '生'), + (0x2F64, 'M', '用'), + (0x2F65, 'M', '田'), + (0x2F66, 'M', '疋'), + (0x2F67, 'M', '疒'), + (0x2F68, 'M', '癶'), + (0x2F69, 'M', '白'), + (0x2F6A, 'M', '皮'), + (0x2F6B, 'M', '皿'), + (0x2F6C, 'M', '目'), + (0x2F6D, 'M', '矛'), + (0x2F6E, 'M', '矢'), + (0x2F6F, 'M', '石'), + (0x2F70, 'M', '示'), + (0x2F71, 'M', '禸'), + (0x2F72, 'M', '禾'), + (0x2F73, 'M', '穴'), + (0x2F74, 'M', '立'), + (0x2F75, 'M', '竹'), ] def _seg_28(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x2F92, 'M', u'見'), - (0x2F93, 'M', u'角'), - (0x2F94, 'M', u'言'), - (0x2F95, 'M', u'谷'), - (0x2F96, 'M', u'豆'), - (0x2F97, 'M', u'豕'), - (0x2F98, 'M', u'豸'), - (0x2F99, 'M', u'貝'), - (0x2F9A, 'M', u'赤'), - (0x2F9B, 'M', u'走'), - (0x2F9C, 'M', u'足'), - (0x2F9D, 'M', u'身'), - (0x2F9E, 'M', u'車'), - (0x2F9F, 'M', u'辛'), - (0x2FA0, 'M', u'辰'), - (0x2FA1, 'M', u'辵'), - (0x2FA2, 'M', u'邑'), - (0x2FA3, 'M', u'酉'), - (0x2FA4, 'M', u'釆'), - (0x2FA5, 'M', u'里'), - (0x2FA6, 'M', u'金'), - (0x2FA7, 'M', u'長'), - (0x2FA8, 'M', u'門'), - (0x2FA9, 'M', u'阜'), - (0x2FAA, 'M', u'隶'), - (0x2FAB, 'M', u'隹'), - (0x2FAC, 'M', u'雨'), - (0x2FAD, 'M', u'靑'), - (0x2FAE, 'M', u'非'), - (0x2FAF, 'M', u'面'), - (0x2FB0, 'M', u'革'), - (0x2FB1, 'M', u'韋'), - (0x2FB2, 'M', u'韭'), - (0x2FB3, 'M', u'音'), - (0x2FB4, 'M', u'頁'), - (0x2FB5, 'M', u'風'), - (0x2FB6, 'M', u'飛'), - (0x2FB7, 'M', u'食'), - (0x2FB8, 'M', u'首'), - (0x2FB9, 'M', u'香'), - (0x2FBA, 'M', u'馬'), - (0x2FBB, 'M', u'骨'), - (0x2FBC, 'M', u'高'), - (0x2FBD, 'M', u'髟'), - (0x2FBE, 'M', u'鬥'), - (0x2FBF, 'M', u'鬯'), - (0x2FC0, 'M', u'鬲'), - (0x2FC1, 'M', u'鬼'), - (0x2FC2, 'M', u'魚'), - (0x2FC3, 'M', u'鳥'), - (0x2FC4, 'M', u'鹵'), - (0x2FC5, 'M', u'鹿'), - (0x2FC6, 'M', u'麥'), - (0x2FC7, 'M', u'麻'), - (0x2FC8, 'M', u'黃'), - (0x2FC9, 'M', u'黍'), - (0x2FCA, 'M', u'黑'), - (0x2FCB, 'M', u'黹'), - (0x2FCC, 'M', u'黽'), - (0x2FCD, 'M', u'鼎'), - (0x2FCE, 'M', u'鼓'), - (0x2FCF, 'M', u'鼠'), - (0x2FD0, 'M', u'鼻'), - (0x2FD1, 'M', u'齊'), - (0x2FD2, 'M', u'齒'), - (0x2FD3, 'M', u'龍'), - (0x2FD4, 'M', u'龜'), - (0x2FD5, 'M', u'龠'), + (0x2F76, 'M', '米'), + (0x2F77, 'M', '糸'), + (0x2F78, 'M', '缶'), + (0x2F79, 'M', '网'), + (0x2F7A, 'M', '羊'), + (0x2F7B, 'M', '羽'), + (0x2F7C, 'M', '老'), + (0x2F7D, 'M', '而'), + (0x2F7E, 'M', '耒'), + (0x2F7F, 'M', '耳'), + (0x2F80, 'M', '聿'), + (0x2F81, 'M', '肉'), + (0x2F82, 'M', '臣'), + (0x2F83, 'M', '自'), + (0x2F84, 'M', '至'), + (0x2F85, 'M', '臼'), + (0x2F86, 'M', '舌'), + (0x2F87, 'M', '舛'), + (0x2F88, 'M', '舟'), + (0x2F89, 'M', '艮'), + (0x2F8A, 'M', '色'), + (0x2F8B, 'M', '艸'), + (0x2F8C, 'M', '虍'), + (0x2F8D, 'M', '虫'), + (0x2F8E, 'M', '血'), + (0x2F8F, 'M', '行'), + (0x2F90, 'M', '衣'), + (0x2F91, 'M', '襾'), + (0x2F92, 'M', '見'), + (0x2F93, 'M', '角'), + (0x2F94, 'M', '言'), + (0x2F95, 'M', '谷'), + (0x2F96, 'M', '豆'), + (0x2F97, 'M', '豕'), + (0x2F98, 'M', '豸'), + (0x2F99, 'M', '貝'), + (0x2F9A, 'M', '赤'), + (0x2F9B, 'M', '走'), + (0x2F9C, 'M', '足'), + (0x2F9D, 'M', '身'), + (0x2F9E, 'M', '車'), + (0x2F9F, 'M', '辛'), + (0x2FA0, 'M', '辰'), + (0x2FA1, 'M', '辵'), + (0x2FA2, 'M', '邑'), + (0x2FA3, 'M', '酉'), + (0x2FA4, 'M', '釆'), + (0x2FA5, 'M', '里'), + (0x2FA6, 'M', '金'), + (0x2FA7, 'M', '長'), + (0x2FA8, 'M', '門'), + (0x2FA9, 'M', '阜'), + (0x2FAA, 'M', '隶'), + (0x2FAB, 'M', '隹'), + (0x2FAC, 'M', '雨'), + (0x2FAD, 'M', '靑'), + (0x2FAE, 'M', '非'), + (0x2FAF, 'M', '面'), + (0x2FB0, 'M', '革'), + (0x2FB1, 'M', '韋'), + (0x2FB2, 'M', '韭'), + (0x2FB3, 'M', '音'), + (0x2FB4, 'M', '頁'), + (0x2FB5, 'M', '風'), + (0x2FB6, 'M', '飛'), + (0x2FB7, 'M', '食'), + (0x2FB8, 'M', '首'), + (0x2FB9, 'M', '香'), + (0x2FBA, 'M', '馬'), + (0x2FBB, 'M', '骨'), + (0x2FBC, 'M', '高'), + (0x2FBD, 'M', '髟'), + (0x2FBE, 'M', '鬥'), + (0x2FBF, 'M', '鬯'), + (0x2FC0, 'M', '鬲'), + (0x2FC1, 'M', '鬼'), + (0x2FC2, 'M', '魚'), + (0x2FC3, 'M', '鳥'), + (0x2FC4, 'M', '鹵'), + (0x2FC5, 'M', '鹿'), + (0x2FC6, 'M', '麥'), + (0x2FC7, 'M', '麻'), + (0x2FC8, 'M', '黃'), + (0x2FC9, 'M', '黍'), + (0x2FCA, 'M', '黑'), + (0x2FCB, 'M', '黹'), + (0x2FCC, 'M', '黽'), + (0x2FCD, 'M', '鼎'), + (0x2FCE, 'M', '鼓'), + (0x2FCF, 'M', '鼠'), + (0x2FD0, 'M', '鼻'), + (0x2FD1, 'M', '齊'), + (0x2FD2, 'M', '齒'), + (0x2FD3, 'M', '龍'), + (0x2FD4, 'M', '龜'), + (0x2FD5, 'M', '龠'), (0x2FD6, 'X'), - (0x3000, '3', u' '), + (0x3000, '3', ' '), (0x3001, 'V'), - (0x3002, 'M', u'.'), + (0x3002, 'M', '.'), + ] + +def _seg_29(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ (0x3003, 'V'), - (0x3036, 'M', u'〒'), + (0x3036, 'M', '〒'), (0x3037, 'V'), - (0x3038, 'M', u'十'), - (0x3039, 'M', u'卄'), - (0x303A, 'M', u'卅'), + (0x3038, 'M', '十'), + (0x3039, 'M', '卄'), + (0x303A, 'M', '卅'), (0x303B, 'V'), (0x3040, 'X'), (0x3041, 'V'), (0x3097, 'X'), (0x3099, 'V'), - (0x309B, '3', u' ゙'), - (0x309C, '3', u' ゚'), + (0x309B, '3', ' ゙'), + (0x309C, '3', ' ゚'), (0x309D, 'V'), - (0x309F, 'M', u'より'), + (0x309F, 'M', 'より'), (0x30A0, 'V'), - (0x30FF, 'M', u'コト'), + (0x30FF, 'M', 'コト'), (0x3100, 'X'), (0x3105, 'V'), (0x3130, 'X'), - (0x3131, 'M', u'ᄀ'), - (0x3132, 'M', u'ᄁ'), - (0x3133, 'M', u'ᆪ'), - (0x3134, 'M', u'ᄂ'), - (0x3135, 'M', u'ᆬ'), - (0x3136, 'M', u'ᆭ'), - (0x3137, 'M', u'ᄃ'), - (0x3138, 'M', u'ᄄ'), - ] - -def _seg_29(): - return [ - (0x3139, 'M', u'ᄅ'), - (0x313A, 'M', u'ᆰ'), - (0x313B, 'M', u'ᆱ'), - (0x313C, 'M', u'ᆲ'), - (0x313D, 'M', u'ᆳ'), - (0x313E, 'M', u'ᆴ'), - (0x313F, 'M', u'ᆵ'), - (0x3140, 'M', u'ᄚ'), - (0x3141, 'M', u'ᄆ'), - (0x3142, 'M', u'ᄇ'), - (0x3143, 'M', u'ᄈ'), - (0x3144, 'M', u'ᄡ'), - (0x3145, 'M', u'ᄉ'), - (0x3146, 'M', u'ᄊ'), - (0x3147, 'M', u'ᄋ'), - (0x3148, 'M', u'ᄌ'), - (0x3149, 'M', u'ᄍ'), - (0x314A, 'M', u'ᄎ'), - (0x314B, 'M', u'ᄏ'), - (0x314C, 'M', u'ᄐ'), - (0x314D, 'M', u'ᄑ'), - (0x314E, 'M', u'ᄒ'), - (0x314F, 'M', u'ᅡ'), - (0x3150, 'M', u'ᅢ'), - (0x3151, 'M', u'ᅣ'), - (0x3152, 'M', u'ᅤ'), - (0x3153, 'M', u'ᅥ'), - (0x3154, 'M', u'ᅦ'), - (0x3155, 'M', u'ᅧ'), - (0x3156, 'M', u'ᅨ'), - (0x3157, 'M', u'ᅩ'), - (0x3158, 'M', u'ᅪ'), - (0x3159, 'M', u'ᅫ'), - (0x315A, 'M', u'ᅬ'), - (0x315B, 'M', u'ᅭ'), - (0x315C, 'M', u'ᅮ'), - (0x315D, 'M', u'ᅯ'), - (0x315E, 'M', u'ᅰ'), - (0x315F, 'M', u'ᅱ'), - (0x3160, 'M', u'ᅲ'), - (0x3161, 'M', u'ᅳ'), - (0x3162, 'M', u'ᅴ'), - (0x3163, 'M', u'ᅵ'), + (0x3131, 'M', 'ᄀ'), + (0x3132, 'M', 'ᄁ'), + (0x3133, 'M', 'ᆪ'), + (0x3134, 'M', 'ᄂ'), + (0x3135, 'M', 'ᆬ'), + (0x3136, 'M', 'ᆭ'), + (0x3137, 'M', 'ᄃ'), + (0x3138, 'M', 'ᄄ'), + (0x3139, 'M', 'ᄅ'), + (0x313A, 'M', 'ᆰ'), + (0x313B, 'M', 'ᆱ'), + (0x313C, 'M', 'ᆲ'), + (0x313D, 'M', 'ᆳ'), + (0x313E, 'M', 'ᆴ'), + (0x313F, 'M', 'ᆵ'), + (0x3140, 'M', 'ᄚ'), + (0x3141, 'M', 'ᄆ'), + (0x3142, 'M', 'ᄇ'), + (0x3143, 'M', 'ᄈ'), + (0x3144, 'M', 'ᄡ'), + (0x3145, 'M', 'ᄉ'), + (0x3146, 'M', 'ᄊ'), + (0x3147, 'M', 'ᄋ'), + (0x3148, 'M', 'ᄌ'), + (0x3149, 'M', 'ᄍ'), + (0x314A, 'M', 'ᄎ'), + (0x314B, 'M', 'ᄏ'), + (0x314C, 'M', 'ᄐ'), + (0x314D, 'M', 'ᄑ'), + (0x314E, 'M', 'ᄒ'), + (0x314F, 'M', 'ᅡ'), + (0x3150, 'M', 'ᅢ'), + (0x3151, 'M', 'ᅣ'), + (0x3152, 'M', 'ᅤ'), + (0x3153, 'M', 'ᅥ'), + (0x3154, 'M', 'ᅦ'), + (0x3155, 'M', 'ᅧ'), + (0x3156, 'M', 'ᅨ'), + (0x3157, 'M', 'ᅩ'), + (0x3158, 'M', 'ᅪ'), + (0x3159, 'M', 'ᅫ'), + (0x315A, 'M', 'ᅬ'), + (0x315B, 'M', 'ᅭ'), + (0x315C, 'M', 'ᅮ'), + (0x315D, 'M', 'ᅯ'), + (0x315E, 'M', 'ᅰ'), + (0x315F, 'M', 'ᅱ'), + (0x3160, 'M', 'ᅲ'), + (0x3161, 'M', 'ᅳ'), + (0x3162, 'M', 'ᅴ'), + (0x3163, 'M', 'ᅵ'), (0x3164, 'X'), - (0x3165, 'M', u'ᄔ'), - (0x3166, 'M', u'ᄕ'), - (0x3167, 'M', u'ᇇ'), - (0x3168, 'M', u'ᇈ'), - (0x3169, 'M', u'ᇌ'), - (0x316A, 'M', u'ᇎ'), - (0x316B, 'M', u'ᇓ'), - (0x316C, 'M', u'ᇗ'), - (0x316D, 'M', u'ᇙ'), - (0x316E, 'M', u'ᄜ'), - (0x316F, 'M', u'ᇝ'), - (0x3170, 'M', u'ᇟ'), - (0x3171, 'M', u'ᄝ'), - (0x3172, 'M', u'ᄞ'), - (0x3173, 'M', u'ᄠ'), - (0x3174, 'M', u'ᄢ'), - (0x3175, 'M', u'ᄣ'), - (0x3176, 'M', u'ᄧ'), - (0x3177, 'M', u'ᄩ'), - (0x3178, 'M', u'ᄫ'), - (0x3179, 'M', u'ᄬ'), - (0x317A, 'M', u'ᄭ'), - (0x317B, 'M', u'ᄮ'), - (0x317C, 'M', u'ᄯ'), - (0x317D, 'M', u'ᄲ'), - (0x317E, 'M', u'ᄶ'), - (0x317F, 'M', u'ᅀ'), - (0x3180, 'M', u'ᅇ'), - (0x3181, 'M', u'ᅌ'), - (0x3182, 'M', u'ᇱ'), - (0x3183, 'M', u'ᇲ'), - (0x3184, 'M', u'ᅗ'), - (0x3185, 'M', u'ᅘ'), - (0x3186, 'M', u'ᅙ'), - (0x3187, 'M', u'ᆄ'), - (0x3188, 'M', u'ᆅ'), - (0x3189, 'M', u'ᆈ'), - (0x318A, 'M', u'ᆑ'), - (0x318B, 'M', u'ᆒ'), - (0x318C, 'M', u'ᆔ'), - (0x318D, 'M', u'ᆞ'), - (0x318E, 'M', u'ᆡ'), - (0x318F, 'X'), - (0x3190, 'V'), - (0x3192, 'M', u'一'), - (0x3193, 'M', u'二'), - (0x3194, 'M', u'三'), - (0x3195, 'M', u'四'), - (0x3196, 'M', u'上'), - (0x3197, 'M', u'中'), - (0x3198, 'M', u'下'), - (0x3199, 'M', u'甲'), - (0x319A, 'M', u'乙'), - (0x319B, 'M', u'丙'), - (0x319C, 'M', u'丁'), - (0x319D, 'M', u'天'), + (0x3165, 'M', 'ᄔ'), + (0x3166, 'M', 'ᄕ'), + (0x3167, 'M', 'ᇇ'), + (0x3168, 'M', 'ᇈ'), + (0x3169, 'M', 'ᇌ'), + (0x316A, 'M', 'ᇎ'), + (0x316B, 'M', 'ᇓ'), + (0x316C, 'M', 'ᇗ'), + (0x316D, 'M', 'ᇙ'), + (0x316E, 'M', 'ᄜ'), + (0x316F, 'M', 'ᇝ'), + (0x3170, 'M', 'ᇟ'), + (0x3171, 'M', 'ᄝ'), + (0x3172, 'M', 'ᄞ'), + (0x3173, 'M', 'ᄠ'), + (0x3174, 'M', 'ᄢ'), + (0x3175, 'M', 'ᄣ'), + (0x3176, 'M', 'ᄧ'), + (0x3177, 'M', 'ᄩ'), + (0x3178, 'M', 'ᄫ'), + (0x3179, 'M', 'ᄬ'), + (0x317A, 'M', 'ᄭ'), + (0x317B, 'M', 'ᄮ'), + (0x317C, 'M', 'ᄯ'), + (0x317D, 'M', 'ᄲ'), + (0x317E, 'M', 'ᄶ'), + (0x317F, 'M', 'ᅀ'), + (0x3180, 'M', 'ᅇ'), ] def _seg_30(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x319E, 'M', u'地'), - (0x319F, 'M', u'人'), + (0x3181, 'M', 'ᅌ'), + (0x3182, 'M', 'ᇱ'), + (0x3183, 'M', 'ᇲ'), + (0x3184, 'M', 'ᅗ'), + (0x3185, 'M', 'ᅘ'), + (0x3186, 'M', 'ᅙ'), + (0x3187, 'M', 'ᆄ'), + (0x3188, 'M', 'ᆅ'), + (0x3189, 'M', 'ᆈ'), + (0x318A, 'M', 'ᆑ'), + (0x318B, 'M', 'ᆒ'), + (0x318C, 'M', 'ᆔ'), + (0x318D, 'M', 'ᆞ'), + (0x318E, 'M', 'ᆡ'), + (0x318F, 'X'), + (0x3190, 'V'), + (0x3192, 'M', '一'), + (0x3193, 'M', '二'), + (0x3194, 'M', '三'), + (0x3195, 'M', '四'), + (0x3196, 'M', '上'), + (0x3197, 'M', '中'), + (0x3198, 'M', '下'), + (0x3199, 'M', '甲'), + (0x319A, 'M', '乙'), + (0x319B, 'M', '丙'), + (0x319C, 'M', '丁'), + (0x319D, 'M', '天'), + (0x319E, 'M', '地'), + (0x319F, 'M', '人'), (0x31A0, 'V'), - (0x31BB, 'X'), - (0x31C0, 'V'), (0x31E4, 'X'), (0x31F0, 'V'), - (0x3200, '3', u'(ᄀ)'), - (0x3201, '3', u'(ᄂ)'), - (0x3202, '3', u'(ᄃ)'), - (0x3203, '3', u'(ᄅ)'), - (0x3204, '3', u'(ᄆ)'), - (0x3205, '3', u'(ᄇ)'), - (0x3206, '3', u'(ᄉ)'), - (0x3207, '3', u'(ᄋ)'), - (0x3208, '3', u'(ᄌ)'), - (0x3209, '3', u'(ᄎ)'), - (0x320A, '3', u'(ᄏ)'), - (0x320B, '3', u'(ᄐ)'), - (0x320C, '3', u'(ᄑ)'), - (0x320D, '3', u'(ᄒ)'), - (0x320E, '3', u'(가)'), - (0x320F, '3', u'(나)'), - (0x3210, '3', u'(다)'), - (0x3211, '3', u'(라)'), - (0x3212, '3', u'(마)'), - (0x3213, '3', u'(바)'), - (0x3214, '3', u'(사)'), - (0x3215, '3', u'(아)'), - (0x3216, '3', u'(자)'), - (0x3217, '3', u'(차)'), - (0x3218, '3', u'(카)'), - (0x3219, '3', u'(타)'), - (0x321A, '3', u'(파)'), - (0x321B, '3', u'(하)'), - (0x321C, '3', u'(주)'), - (0x321D, '3', u'(오전)'), - (0x321E, '3', u'(오후)'), + (0x3200, '3', '(ᄀ)'), + (0x3201, '3', '(ᄂ)'), + (0x3202, '3', '(ᄃ)'), + (0x3203, '3', '(ᄅ)'), + (0x3204, '3', '(ᄆ)'), + (0x3205, '3', '(ᄇ)'), + (0x3206, '3', '(ᄉ)'), + (0x3207, '3', '(ᄋ)'), + (0x3208, '3', '(ᄌ)'), + (0x3209, '3', '(ᄎ)'), + (0x320A, '3', '(ᄏ)'), + (0x320B, '3', '(ᄐ)'), + (0x320C, '3', '(ᄑ)'), + (0x320D, '3', '(ᄒ)'), + (0x320E, '3', '(가)'), + (0x320F, '3', '(나)'), + (0x3210, '3', '(다)'), + (0x3211, '3', '(라)'), + (0x3212, '3', '(마)'), + (0x3213, '3', '(바)'), + (0x3214, '3', '(사)'), + (0x3215, '3', '(아)'), + (0x3216, '3', '(자)'), + (0x3217, '3', '(차)'), + (0x3218, '3', '(카)'), + (0x3219, '3', '(타)'), + (0x321A, '3', '(파)'), + (0x321B, '3', '(하)'), + (0x321C, '3', '(주)'), + (0x321D, '3', '(오전)'), + (0x321E, '3', '(오후)'), (0x321F, 'X'), - (0x3220, '3', u'(一)'), - (0x3221, '3', u'(二)'), - (0x3222, '3', u'(三)'), - (0x3223, '3', u'(四)'), - (0x3224, '3', u'(五)'), - (0x3225, '3', u'(六)'), - (0x3226, '3', u'(七)'), - (0x3227, '3', u'(八)'), - (0x3228, '3', u'(九)'), - (0x3229, '3', u'(十)'), - (0x322A, '3', u'(月)'), - (0x322B, '3', u'(火)'), - (0x322C, '3', u'(水)'), - (0x322D, '3', u'(木)'), - (0x322E, '3', u'(金)'), - (0x322F, '3', u'(土)'), - (0x3230, '3', u'(日)'), - (0x3231, '3', u'(株)'), - (0x3232, '3', u'(有)'), - (0x3233, '3', u'(社)'), - (0x3234, '3', u'(名)'), - (0x3235, '3', u'(特)'), - (0x3236, '3', u'(財)'), - (0x3237, '3', u'(祝)'), - (0x3238, '3', u'(労)'), - (0x3239, '3', u'(代)'), - (0x323A, '3', u'(呼)'), - (0x323B, '3', u'(学)'), - (0x323C, '3', u'(監)'), - (0x323D, '3', u'(企)'), - (0x323E, '3', u'(資)'), - (0x323F, '3', u'(協)'), - (0x3240, '3', u'(祭)'), - (0x3241, '3', u'(休)'), - (0x3242, '3', u'(自)'), - (0x3243, '3', u'(至)'), - (0x3244, 'M', u'問'), - (0x3245, 'M', u'幼'), - (0x3246, 'M', u'文'), - (0x3247, 'M', u'箏'), - (0x3248, 'V'), - (0x3250, 'M', u'pte'), - (0x3251, 'M', u'21'), - (0x3252, 'M', u'22'), - (0x3253, 'M', u'23'), - (0x3254, 'M', u'24'), - (0x3255, 'M', u'25'), - (0x3256, 'M', u'26'), - (0x3257, 'M', u'27'), - (0x3258, 'M', u'28'), - (0x3259, 'M', u'29'), - (0x325A, 'M', u'30'), - (0x325B, 'M', u'31'), - (0x325C, 'M', u'32'), - (0x325D, 'M', u'33'), - (0x325E, 'M', u'34'), - (0x325F, 'M', u'35'), - (0x3260, 'M', u'ᄀ'), - (0x3261, 'M', u'ᄂ'), - (0x3262, 'M', u'ᄃ'), - (0x3263, 'M', u'ᄅ'), + (0x3220, '3', '(一)'), + (0x3221, '3', '(二)'), + (0x3222, '3', '(三)'), + (0x3223, '3', '(四)'), + (0x3224, '3', '(五)'), + (0x3225, '3', '(六)'), + (0x3226, '3', '(七)'), + (0x3227, '3', '(八)'), + (0x3228, '3', '(九)'), + (0x3229, '3', '(十)'), + (0x322A, '3', '(月)'), + (0x322B, '3', '(火)'), + (0x322C, '3', '(水)'), + (0x322D, '3', '(木)'), + (0x322E, '3', '(金)'), + (0x322F, '3', '(土)'), + (0x3230, '3', '(日)'), + (0x3231, '3', '(株)'), + (0x3232, '3', '(有)'), + (0x3233, '3', '(社)'), + (0x3234, '3', '(名)'), + (0x3235, '3', '(特)'), + (0x3236, '3', '(財)'), + (0x3237, '3', '(祝)'), + (0x3238, '3', '(労)'), + (0x3239, '3', '(代)'), + (0x323A, '3', '(呼)'), + (0x323B, '3', '(学)'), + (0x323C, '3', '(監)'), + (0x323D, '3', '(企)'), + (0x323E, '3', '(資)'), + (0x323F, '3', '(協)'), + (0x3240, '3', '(祭)'), + (0x3241, '3', '(休)'), + (0x3242, '3', '(自)'), ] def _seg_31(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x3264, 'M', u'ᄆ'), - (0x3265, 'M', u'ᄇ'), - (0x3266, 'M', u'ᄉ'), - (0x3267, 'M', u'ᄋ'), - (0x3268, 'M', u'ᄌ'), - (0x3269, 'M', u'ᄎ'), - (0x326A, 'M', u'ᄏ'), - (0x326B, 'M', u'ᄐ'), - (0x326C, 'M', u'ᄑ'), - (0x326D, 'M', u'ᄒ'), - (0x326E, 'M', u'가'), - (0x326F, 'M', u'나'), - (0x3270, 'M', u'다'), - (0x3271, 'M', u'라'), - (0x3272, 'M', u'마'), - (0x3273, 'M', u'바'), - (0x3274, 'M', u'사'), - (0x3275, 'M', u'아'), - (0x3276, 'M', u'자'), - (0x3277, 'M', u'차'), - (0x3278, 'M', u'카'), - (0x3279, 'M', u'타'), - (0x327A, 'M', u'파'), - (0x327B, 'M', u'하'), - (0x327C, 'M', u'참고'), - (0x327D, 'M', u'주의'), - (0x327E, 'M', u'우'), + (0x3243, '3', '(至)'), + (0x3244, 'M', '問'), + (0x3245, 'M', '幼'), + (0x3246, 'M', '文'), + (0x3247, 'M', '箏'), + (0x3248, 'V'), + (0x3250, 'M', 'pte'), + (0x3251, 'M', '21'), + (0x3252, 'M', '22'), + (0x3253, 'M', '23'), + (0x3254, 'M', '24'), + (0x3255, 'M', '25'), + (0x3256, 'M', '26'), + (0x3257, 'M', '27'), + (0x3258, 'M', '28'), + (0x3259, 'M', '29'), + (0x325A, 'M', '30'), + (0x325B, 'M', '31'), + (0x325C, 'M', '32'), + (0x325D, 'M', '33'), + (0x325E, 'M', '34'), + (0x325F, 'M', '35'), + (0x3260, 'M', 'ᄀ'), + (0x3261, 'M', 'ᄂ'), + (0x3262, 'M', 'ᄃ'), + (0x3263, 'M', 'ᄅ'), + (0x3264, 'M', 'ᄆ'), + (0x3265, 'M', 'ᄇ'), + (0x3266, 'M', 'ᄉ'), + (0x3267, 'M', 'ᄋ'), + (0x3268, 'M', 'ᄌ'), + (0x3269, 'M', 'ᄎ'), + (0x326A, 'M', 'ᄏ'), + (0x326B, 'M', 'ᄐ'), + (0x326C, 'M', 'ᄑ'), + (0x326D, 'M', 'ᄒ'), + (0x326E, 'M', '가'), + (0x326F, 'M', '나'), + (0x3270, 'M', '다'), + (0x3271, 'M', '라'), + (0x3272, 'M', '마'), + (0x3273, 'M', '바'), + (0x3274, 'M', '사'), + (0x3275, 'M', '아'), + (0x3276, 'M', '자'), + (0x3277, 'M', '차'), + (0x3278, 'M', '카'), + (0x3279, 'M', '타'), + (0x327A, 'M', '파'), + (0x327B, 'M', '하'), + (0x327C, 'M', '참고'), + (0x327D, 'M', '주의'), + (0x327E, 'M', '우'), (0x327F, 'V'), - (0x3280, 'M', u'一'), - (0x3281, 'M', u'二'), - (0x3282, 'M', u'三'), - (0x3283, 'M', u'四'), - (0x3284, 'M', u'五'), - (0x3285, 'M', u'六'), - (0x3286, 'M', u'七'), - (0x3287, 'M', u'八'), - (0x3288, 'M', u'九'), - (0x3289, 'M', u'十'), - (0x328A, 'M', u'月'), - (0x328B, 'M', u'火'), - (0x328C, 'M', u'水'), - (0x328D, 'M', u'木'), - (0x328E, 'M', u'金'), - (0x328F, 'M', u'土'), - (0x3290, 'M', u'日'), - (0x3291, 'M', u'株'), - (0x3292, 'M', u'有'), - (0x3293, 'M', u'社'), - (0x3294, 'M', u'名'), - (0x3295, 'M', u'特'), - (0x3296, 'M', u'財'), - (0x3297, 'M', u'祝'), - (0x3298, 'M', u'労'), - (0x3299, 'M', u'秘'), - (0x329A, 'M', u'男'), - (0x329B, 'M', u'女'), - (0x329C, 'M', u'適'), - (0x329D, 'M', u'優'), - (0x329E, 'M', u'印'), - (0x329F, 'M', u'注'), - (0x32A0, 'M', u'項'), - (0x32A1, 'M', u'休'), - (0x32A2, 'M', u'写'), - (0x32A3, 'M', u'正'), - (0x32A4, 'M', u'上'), - (0x32A5, 'M', u'中'), - (0x32A6, 'M', u'下'), - (0x32A7, 'M', u'左'), - (0x32A8, 'M', u'右'), - (0x32A9, 'M', u'医'), - (0x32AA, 'M', u'宗'), - (0x32AB, 'M', u'学'), - (0x32AC, 'M', u'監'), - (0x32AD, 'M', u'企'), - (0x32AE, 'M', u'資'), - (0x32AF, 'M', u'協'), - (0x32B0, 'M', u'夜'), - (0x32B1, 'M', u'36'), - (0x32B2, 'M', u'37'), - (0x32B3, 'M', u'38'), - (0x32B4, 'M', u'39'), - (0x32B5, 'M', u'40'), - (0x32B6, 'M', u'41'), - (0x32B7, 'M', u'42'), - (0x32B8, 'M', u'43'), - (0x32B9, 'M', u'44'), - (0x32BA, 'M', u'45'), - (0x32BB, 'M', u'46'), - (0x32BC, 'M', u'47'), - (0x32BD, 'M', u'48'), - (0x32BE, 'M', u'49'), - (0x32BF, 'M', u'50'), - (0x32C0, 'M', u'1月'), - (0x32C1, 'M', u'2月'), - (0x32C2, 'M', u'3月'), - (0x32C3, 'M', u'4月'), - (0x32C4, 'M', u'5月'), - (0x32C5, 'M', u'6月'), - (0x32C6, 'M', u'7月'), - (0x32C7, 'M', u'8月'), + (0x3280, 'M', '一'), + (0x3281, 'M', '二'), + (0x3282, 'M', '三'), + (0x3283, 'M', '四'), + (0x3284, 'M', '五'), + (0x3285, 'M', '六'), + (0x3286, 'M', '七'), + (0x3287, 'M', '八'), + (0x3288, 'M', '九'), + (0x3289, 'M', '十'), + (0x328A, 'M', '月'), + (0x328B, 'M', '火'), + (0x328C, 'M', '水'), + (0x328D, 'M', '木'), + (0x328E, 'M', '金'), + (0x328F, 'M', '土'), + (0x3290, 'M', '日'), + (0x3291, 'M', '株'), + (0x3292, 'M', '有'), + (0x3293, 'M', '社'), + (0x3294, 'M', '名'), + (0x3295, 'M', '特'), + (0x3296, 'M', '財'), + (0x3297, 'M', '祝'), + (0x3298, 'M', '労'), + (0x3299, 'M', '秘'), + (0x329A, 'M', '男'), + (0x329B, 'M', '女'), + (0x329C, 'M', '適'), + (0x329D, 'M', '優'), + (0x329E, 'M', '印'), + (0x329F, 'M', '注'), + (0x32A0, 'M', '項'), + (0x32A1, 'M', '休'), + (0x32A2, 'M', '写'), + (0x32A3, 'M', '正'), + (0x32A4, 'M', '上'), + (0x32A5, 'M', '中'), + (0x32A6, 'M', '下'), + (0x32A7, 'M', '左'), + (0x32A8, 'M', '右'), + (0x32A9, 'M', '医'), + (0x32AA, 'M', '宗'), + (0x32AB, 'M', '学'), + (0x32AC, 'M', '監'), + (0x32AD, 'M', '企'), ] def _seg_32(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x32C8, 'M', u'9月'), - (0x32C9, 'M', u'10月'), - (0x32CA, 'M', u'11月'), - (0x32CB, 'M', u'12月'), - (0x32CC, 'M', u'hg'), - (0x32CD, 'M', u'erg'), - (0x32CE, 'M', u'ev'), - (0x32CF, 'M', u'ltd'), - (0x32D0, 'M', u'ア'), - (0x32D1, 'M', u'イ'), - (0x32D2, 'M', u'ウ'), - (0x32D3, 'M', u'エ'), - (0x32D4, 'M', u'オ'), - (0x32D5, 'M', u'カ'), - (0x32D6, 'M', u'キ'), - (0x32D7, 'M', u'ク'), - (0x32D8, 'M', u'ケ'), - (0x32D9, 'M', u'コ'), - (0x32DA, 'M', u'サ'), - (0x32DB, 'M', u'シ'), - (0x32DC, 'M', u'ス'), - (0x32DD, 'M', u'セ'), - (0x32DE, 'M', u'ソ'), - (0x32DF, 'M', u'タ'), - (0x32E0, 'M', u'チ'), - (0x32E1, 'M', u'ツ'), - (0x32E2, 'M', u'テ'), - (0x32E3, 'M', u'ト'), - (0x32E4, 'M', u'ナ'), - (0x32E5, 'M', u'ニ'), - (0x32E6, 'M', u'ヌ'), - (0x32E7, 'M', u'ネ'), - (0x32E8, 'M', u'ノ'), - (0x32E9, 'M', u'ハ'), - (0x32EA, 'M', u'ヒ'), - (0x32EB, 'M', u'フ'), - (0x32EC, 'M', u'ヘ'), - (0x32ED, 'M', u'ホ'), - (0x32EE, 'M', u'マ'), - (0x32EF, 'M', u'ミ'), - (0x32F0, 'M', u'ム'), - (0x32F1, 'M', u'メ'), - (0x32F2, 'M', u'モ'), - (0x32F3, 'M', u'ヤ'), - (0x32F4, 'M', u'ユ'), - (0x32F5, 'M', u'ヨ'), - (0x32F6, 'M', u'ラ'), - (0x32F7, 'M', u'リ'), - (0x32F8, 'M', u'ル'), - (0x32F9, 'M', u'レ'), - (0x32FA, 'M', u'ロ'), - (0x32FB, 'M', u'ワ'), - (0x32FC, 'M', u'ヰ'), - (0x32FD, 'M', u'ヱ'), - (0x32FE, 'M', u'ヲ'), - (0x32FF, 'X'), - (0x3300, 'M', u'アパート'), - (0x3301, 'M', u'アルファ'), - (0x3302, 'M', u'アンペア'), - (0x3303, 'M', u'アール'), - (0x3304, 'M', u'イニング'), - (0x3305, 'M', u'インチ'), - (0x3306, 'M', u'ウォン'), - (0x3307, 'M', u'エスクード'), - (0x3308, 'M', u'エーカー'), - (0x3309, 'M', u'オンス'), - (0x330A, 'M', u'オーム'), - (0x330B, 'M', u'カイリ'), - (0x330C, 'M', u'カラット'), - (0x330D, 'M', u'カロリー'), - (0x330E, 'M', u'ガロン'), - (0x330F, 'M', u'ガンマ'), - (0x3310, 'M', u'ギガ'), - (0x3311, 'M', u'ギニー'), - (0x3312, 'M', u'キュリー'), - (0x3313, 'M', u'ギルダー'), - (0x3314, 'M', u'キロ'), - (0x3315, 'M', u'キログラム'), - (0x3316, 'M', u'キロメートル'), - (0x3317, 'M', u'キロワット'), - (0x3318, 'M', u'グラム'), - (0x3319, 'M', u'グラムトン'), - (0x331A, 'M', u'クルゼイロ'), - (0x331B, 'M', u'クローネ'), - (0x331C, 'M', u'ケース'), - (0x331D, 'M', u'コルナ'), - (0x331E, 'M', u'コーポ'), - (0x331F, 'M', u'サイクル'), - (0x3320, 'M', u'サンチーム'), - (0x3321, 'M', u'シリング'), - (0x3322, 'M', u'センチ'), - (0x3323, 'M', u'セント'), - (0x3324, 'M', u'ダース'), - (0x3325, 'M', u'デシ'), - (0x3326, 'M', u'ドル'), - (0x3327, 'M', u'トン'), - (0x3328, 'M', u'ナノ'), - (0x3329, 'M', u'ノット'), - (0x332A, 'M', u'ハイツ'), - (0x332B, 'M', u'パーセント'), + (0x32AE, 'M', '資'), + (0x32AF, 'M', '協'), + (0x32B0, 'M', '夜'), + (0x32B1, 'M', '36'), + (0x32B2, 'M', '37'), + (0x32B3, 'M', '38'), + (0x32B4, 'M', '39'), + (0x32B5, 'M', '40'), + (0x32B6, 'M', '41'), + (0x32B7, 'M', '42'), + (0x32B8, 'M', '43'), + (0x32B9, 'M', '44'), + (0x32BA, 'M', '45'), + (0x32BB, 'M', '46'), + (0x32BC, 'M', '47'), + (0x32BD, 'M', '48'), + (0x32BE, 'M', '49'), + (0x32BF, 'M', '50'), + (0x32C0, 'M', '1月'), + (0x32C1, 'M', '2月'), + (0x32C2, 'M', '3月'), + (0x32C3, 'M', '4月'), + (0x32C4, 'M', '5月'), + (0x32C5, 'M', '6月'), + (0x32C6, 'M', '7月'), + (0x32C7, 'M', '8月'), + (0x32C8, 'M', '9月'), + (0x32C9, 'M', '10月'), + (0x32CA, 'M', '11月'), + (0x32CB, 'M', '12月'), + (0x32CC, 'M', 'hg'), + (0x32CD, 'M', 'erg'), + (0x32CE, 'M', 'ev'), + (0x32CF, 'M', 'ltd'), + (0x32D0, 'M', 'ア'), + (0x32D1, 'M', 'イ'), + (0x32D2, 'M', 'ウ'), + (0x32D3, 'M', 'エ'), + (0x32D4, 'M', 'オ'), + (0x32D5, 'M', 'カ'), + (0x32D6, 'M', 'キ'), + (0x32D7, 'M', 'ク'), + (0x32D8, 'M', 'ケ'), + (0x32D9, 'M', 'コ'), + (0x32DA, 'M', 'サ'), + (0x32DB, 'M', 'シ'), + (0x32DC, 'M', 'ス'), + (0x32DD, 'M', 'セ'), + (0x32DE, 'M', 'ソ'), + (0x32DF, 'M', 'タ'), + (0x32E0, 'M', 'チ'), + (0x32E1, 'M', 'ツ'), + (0x32E2, 'M', 'テ'), + (0x32E3, 'M', 'ト'), + (0x32E4, 'M', 'ナ'), + (0x32E5, 'M', 'ニ'), + (0x32E6, 'M', 'ヌ'), + (0x32E7, 'M', 'ネ'), + (0x32E8, 'M', 'ノ'), + (0x32E9, 'M', 'ハ'), + (0x32EA, 'M', 'ヒ'), + (0x32EB, 'M', 'フ'), + (0x32EC, 'M', 'ヘ'), + (0x32ED, 'M', 'ホ'), + (0x32EE, 'M', 'マ'), + (0x32EF, 'M', 'ミ'), + (0x32F0, 'M', 'ム'), + (0x32F1, 'M', 'メ'), + (0x32F2, 'M', 'モ'), + (0x32F3, 'M', 'ヤ'), + (0x32F4, 'M', 'ユ'), + (0x32F5, 'M', 'ヨ'), + (0x32F6, 'M', 'ラ'), + (0x32F7, 'M', 'リ'), + (0x32F8, 'M', 'ル'), + (0x32F9, 'M', 'レ'), + (0x32FA, 'M', 'ロ'), + (0x32FB, 'M', 'ワ'), + (0x32FC, 'M', 'ヰ'), + (0x32FD, 'M', 'ヱ'), + (0x32FE, 'M', 'ヲ'), + (0x32FF, 'M', '令和'), + (0x3300, 'M', 'アパート'), + (0x3301, 'M', 'アルファ'), + (0x3302, 'M', 'アンペア'), + (0x3303, 'M', 'アール'), + (0x3304, 'M', 'イニング'), + (0x3305, 'M', 'インチ'), + (0x3306, 'M', 'ウォン'), + (0x3307, 'M', 'エスクード'), + (0x3308, 'M', 'エーカー'), + (0x3309, 'M', 'オンス'), + (0x330A, 'M', 'オーム'), + (0x330B, 'M', 'カイリ'), + (0x330C, 'M', 'カラット'), + (0x330D, 'M', 'カロリー'), + (0x330E, 'M', 'ガロン'), + (0x330F, 'M', 'ガンマ'), + (0x3310, 'M', 'ギガ'), + (0x3311, 'M', 'ギニー'), ] def _seg_33(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x332C, 'M', u'パーツ'), - (0x332D, 'M', u'バーレル'), - (0x332E, 'M', u'ピアストル'), - (0x332F, 'M', u'ピクル'), - (0x3330, 'M', u'ピコ'), - (0x3331, 'M', u'ビル'), - (0x3332, 'M', u'ファラッド'), - (0x3333, 'M', u'フィート'), - (0x3334, 'M', u'ブッシェル'), - (0x3335, 'M', u'フラン'), - (0x3336, 'M', u'ヘクタール'), - (0x3337, 'M', u'ペソ'), - (0x3338, 'M', u'ペニヒ'), - (0x3339, 'M', u'ヘルツ'), - (0x333A, 'M', u'ペンス'), - (0x333B, 'M', u'ページ'), - (0x333C, 'M', u'ベータ'), - (0x333D, 'M', u'ポイント'), - (0x333E, 'M', u'ボルト'), - (0x333F, 'M', u'ホン'), - (0x3340, 'M', u'ポンド'), - (0x3341, 'M', u'ホール'), - (0x3342, 'M', u'ホーン'), - (0x3343, 'M', u'マイクロ'), - (0x3344, 'M', u'マイル'), - (0x3345, 'M', u'マッハ'), - (0x3346, 'M', u'マルク'), - (0x3347, 'M', u'マンション'), - (0x3348, 'M', u'ミクロン'), - (0x3349, 'M', u'ミリ'), - (0x334A, 'M', u'ミリバール'), - (0x334B, 'M', u'メガ'), - (0x334C, 'M', u'メガトン'), - (0x334D, 'M', u'メートル'), - (0x334E, 'M', u'ヤード'), - (0x334F, 'M', u'ヤール'), - (0x3350, 'M', u'ユアン'), - (0x3351, 'M', u'リットル'), - (0x3352, 'M', u'リラ'), - (0x3353, 'M', u'ルピー'), - (0x3354, 'M', u'ルーブル'), - (0x3355, 'M', u'レム'), - (0x3356, 'M', u'レントゲン'), - (0x3357, 'M', u'ワット'), - (0x3358, 'M', u'0点'), - (0x3359, 'M', u'1点'), - (0x335A, 'M', u'2点'), - (0x335B, 'M', u'3点'), - (0x335C, 'M', u'4点'), - (0x335D, 'M', u'5点'), - (0x335E, 'M', u'6点'), - (0x335F, 'M', u'7点'), - (0x3360, 'M', u'8点'), - (0x3361, 'M', u'9点'), - (0x3362, 'M', u'10点'), - (0x3363, 'M', u'11点'), - (0x3364, 'M', u'12点'), - (0x3365, 'M', u'13点'), - (0x3366, 'M', u'14点'), - (0x3367, 'M', u'15点'), - (0x3368, 'M', u'16点'), - (0x3369, 'M', u'17点'), - (0x336A, 'M', u'18点'), - (0x336B, 'M', u'19点'), - (0x336C, 'M', u'20点'), - (0x336D, 'M', u'21点'), - (0x336E, 'M', u'22点'), - (0x336F, 'M', u'23点'), - (0x3370, 'M', u'24点'), - (0x3371, 'M', u'hpa'), - (0x3372, 'M', u'da'), - (0x3373, 'M', u'au'), - (0x3374, 'M', u'bar'), - (0x3375, 'M', u'ov'), - (0x3376, 'M', u'pc'), - (0x3377, 'M', u'dm'), - (0x3378, 'M', u'dm2'), - (0x3379, 'M', u'dm3'), - (0x337A, 'M', u'iu'), - (0x337B, 'M', u'平成'), - (0x337C, 'M', u'昭和'), - (0x337D, 'M', u'大正'), - (0x337E, 'M', u'明治'), - (0x337F, 'M', u'株式会社'), - (0x3380, 'M', u'pa'), - (0x3381, 'M', u'na'), - (0x3382, 'M', u'μa'), - (0x3383, 'M', u'ma'), - (0x3384, 'M', u'ka'), - (0x3385, 'M', u'kb'), - (0x3386, 'M', u'mb'), - (0x3387, 'M', u'gb'), - (0x3388, 'M', u'cal'), - (0x3389, 'M', u'kcal'), - (0x338A, 'M', u'pf'), - (0x338B, 'M', u'nf'), - (0x338C, 'M', u'μf'), - (0x338D, 'M', u'μg'), - (0x338E, 'M', u'mg'), - (0x338F, 'M', u'kg'), + (0x3312, 'M', 'キュリー'), + (0x3313, 'M', 'ギルダー'), + (0x3314, 'M', 'キロ'), + (0x3315, 'M', 'キログラム'), + (0x3316, 'M', 'キロメートル'), + (0x3317, 'M', 'キロワット'), + (0x3318, 'M', 'グラム'), + (0x3319, 'M', 'グラムトン'), + (0x331A, 'M', 'クルゼイロ'), + (0x331B, 'M', 'クローネ'), + (0x331C, 'M', 'ケース'), + (0x331D, 'M', 'コルナ'), + (0x331E, 'M', 'コーポ'), + (0x331F, 'M', 'サイクル'), + (0x3320, 'M', 'サンチーム'), + (0x3321, 'M', 'シリング'), + (0x3322, 'M', 'センチ'), + (0x3323, 'M', 'セント'), + (0x3324, 'M', 'ダース'), + (0x3325, 'M', 'デシ'), + (0x3326, 'M', 'ドル'), + (0x3327, 'M', 'トン'), + (0x3328, 'M', 'ナノ'), + (0x3329, 'M', 'ノット'), + (0x332A, 'M', 'ハイツ'), + (0x332B, 'M', 'パーセント'), + (0x332C, 'M', 'パーツ'), + (0x332D, 'M', 'バーレル'), + (0x332E, 'M', 'ピアストル'), + (0x332F, 'M', 'ピクル'), + (0x3330, 'M', 'ピコ'), + (0x3331, 'M', 'ビル'), + (0x3332, 'M', 'ファラッド'), + (0x3333, 'M', 'フィート'), + (0x3334, 'M', 'ブッシェル'), + (0x3335, 'M', 'フラン'), + (0x3336, 'M', 'ヘクタール'), + (0x3337, 'M', 'ペソ'), + (0x3338, 'M', 'ペニヒ'), + (0x3339, 'M', 'ヘルツ'), + (0x333A, 'M', 'ペンス'), + (0x333B, 'M', 'ページ'), + (0x333C, 'M', 'ベータ'), + (0x333D, 'M', 'ポイント'), + (0x333E, 'M', 'ボルト'), + (0x333F, 'M', 'ホン'), + (0x3340, 'M', 'ポンド'), + (0x3341, 'M', 'ホール'), + (0x3342, 'M', 'ホーン'), + (0x3343, 'M', 'マイクロ'), + (0x3344, 'M', 'マイル'), + (0x3345, 'M', 'マッハ'), + (0x3346, 'M', 'マルク'), + (0x3347, 'M', 'マンション'), + (0x3348, 'M', 'ミクロン'), + (0x3349, 'M', 'ミリ'), + (0x334A, 'M', 'ミリバール'), + (0x334B, 'M', 'メガ'), + (0x334C, 'M', 'メガトン'), + (0x334D, 'M', 'メートル'), + (0x334E, 'M', 'ヤード'), + (0x334F, 'M', 'ヤール'), + (0x3350, 'M', 'ユアン'), + (0x3351, 'M', 'リットル'), + (0x3352, 'M', 'リラ'), + (0x3353, 'M', 'ルピー'), + (0x3354, 'M', 'ルーブル'), + (0x3355, 'M', 'レム'), + (0x3356, 'M', 'レントゲン'), + (0x3357, 'M', 'ワット'), + (0x3358, 'M', '0点'), + (0x3359, 'M', '1点'), + (0x335A, 'M', '2点'), + (0x335B, 'M', '3点'), + (0x335C, 'M', '4点'), + (0x335D, 'M', '5点'), + (0x335E, 'M', '6点'), + (0x335F, 'M', '7点'), + (0x3360, 'M', '8点'), + (0x3361, 'M', '9点'), + (0x3362, 'M', '10点'), + (0x3363, 'M', '11点'), + (0x3364, 'M', '12点'), + (0x3365, 'M', '13点'), + (0x3366, 'M', '14点'), + (0x3367, 'M', '15点'), + (0x3368, 'M', '16点'), + (0x3369, 'M', '17点'), + (0x336A, 'M', '18点'), + (0x336B, 'M', '19点'), + (0x336C, 'M', '20点'), + (0x336D, 'M', '21点'), + (0x336E, 'M', '22点'), + (0x336F, 'M', '23点'), + (0x3370, 'M', '24点'), + (0x3371, 'M', 'hpa'), + (0x3372, 'M', 'da'), + (0x3373, 'M', 'au'), + (0x3374, 'M', 'bar'), + (0x3375, 'M', 'ov'), ] def _seg_34(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x3390, 'M', u'hz'), - (0x3391, 'M', u'khz'), - (0x3392, 'M', u'mhz'), - (0x3393, 'M', u'ghz'), - (0x3394, 'M', u'thz'), - (0x3395, 'M', u'μl'), - (0x3396, 'M', u'ml'), - (0x3397, 'M', u'dl'), - (0x3398, 'M', u'kl'), - (0x3399, 'M', u'fm'), - (0x339A, 'M', u'nm'), - (0x339B, 'M', u'μm'), - (0x339C, 'M', u'mm'), - (0x339D, 'M', u'cm'), - (0x339E, 'M', u'km'), - (0x339F, 'M', u'mm2'), - (0x33A0, 'M', u'cm2'), - (0x33A1, 'M', u'm2'), - (0x33A2, 'M', u'km2'), - (0x33A3, 'M', u'mm3'), - (0x33A4, 'M', u'cm3'), - (0x33A5, 'M', u'm3'), - (0x33A6, 'M', u'km3'), - (0x33A7, 'M', u'm∕s'), - (0x33A8, 'M', u'm∕s2'), - (0x33A9, 'M', u'pa'), - (0x33AA, 'M', u'kpa'), - (0x33AB, 'M', u'mpa'), - (0x33AC, 'M', u'gpa'), - (0x33AD, 'M', u'rad'), - (0x33AE, 'M', u'rad∕s'), - (0x33AF, 'M', u'rad∕s2'), - (0x33B0, 'M', u'ps'), - (0x33B1, 'M', u'ns'), - (0x33B2, 'M', u'μs'), - (0x33B3, 'M', u'ms'), - (0x33B4, 'M', u'pv'), - (0x33B5, 'M', u'nv'), - (0x33B6, 'M', u'μv'), - (0x33B7, 'M', u'mv'), - (0x33B8, 'M', u'kv'), - (0x33B9, 'M', u'mv'), - (0x33BA, 'M', u'pw'), - (0x33BB, 'M', u'nw'), - (0x33BC, 'M', u'μw'), - (0x33BD, 'M', u'mw'), - (0x33BE, 'M', u'kw'), - (0x33BF, 'M', u'mw'), - (0x33C0, 'M', u'kω'), - (0x33C1, 'M', u'mω'), + (0x3376, 'M', 'pc'), + (0x3377, 'M', 'dm'), + (0x3378, 'M', 'dm2'), + (0x3379, 'M', 'dm3'), + (0x337A, 'M', 'iu'), + (0x337B, 'M', '平成'), + (0x337C, 'M', '昭和'), + (0x337D, 'M', '大正'), + (0x337E, 'M', '明治'), + (0x337F, 'M', '株式会社'), + (0x3380, 'M', 'pa'), + (0x3381, 'M', 'na'), + (0x3382, 'M', 'μa'), + (0x3383, 'M', 'ma'), + (0x3384, 'M', 'ka'), + (0x3385, 'M', 'kb'), + (0x3386, 'M', 'mb'), + (0x3387, 'M', 'gb'), + (0x3388, 'M', 'cal'), + (0x3389, 'M', 'kcal'), + (0x338A, 'M', 'pf'), + (0x338B, 'M', 'nf'), + (0x338C, 'M', 'μf'), + (0x338D, 'M', 'μg'), + (0x338E, 'M', 'mg'), + (0x338F, 'M', 'kg'), + (0x3390, 'M', 'hz'), + (0x3391, 'M', 'khz'), + (0x3392, 'M', 'mhz'), + (0x3393, 'M', 'ghz'), + (0x3394, 'M', 'thz'), + (0x3395, 'M', 'μl'), + (0x3396, 'M', 'ml'), + (0x3397, 'M', 'dl'), + (0x3398, 'M', 'kl'), + (0x3399, 'M', 'fm'), + (0x339A, 'M', 'nm'), + (0x339B, 'M', 'μm'), + (0x339C, 'M', 'mm'), + (0x339D, 'M', 'cm'), + (0x339E, 'M', 'km'), + (0x339F, 'M', 'mm2'), + (0x33A0, 'M', 'cm2'), + (0x33A1, 'M', 'm2'), + (0x33A2, 'M', 'km2'), + (0x33A3, 'M', 'mm3'), + (0x33A4, 'M', 'cm3'), + (0x33A5, 'M', 'm3'), + (0x33A6, 'M', 'km3'), + (0x33A7, 'M', 'm∕s'), + (0x33A8, 'M', 'm∕s2'), + (0x33A9, 'M', 'pa'), + (0x33AA, 'M', 'kpa'), + (0x33AB, 'M', 'mpa'), + (0x33AC, 'M', 'gpa'), + (0x33AD, 'M', 'rad'), + (0x33AE, 'M', 'rad∕s'), + (0x33AF, 'M', 'rad∕s2'), + (0x33B0, 'M', 'ps'), + (0x33B1, 'M', 'ns'), + (0x33B2, 'M', 'μs'), + (0x33B3, 'M', 'ms'), + (0x33B4, 'M', 'pv'), + (0x33B5, 'M', 'nv'), + (0x33B6, 'M', 'μv'), + (0x33B7, 'M', 'mv'), + (0x33B8, 'M', 'kv'), + (0x33B9, 'M', 'mv'), + (0x33BA, 'M', 'pw'), + (0x33BB, 'M', 'nw'), + (0x33BC, 'M', 'μw'), + (0x33BD, 'M', 'mw'), + (0x33BE, 'M', 'kw'), + (0x33BF, 'M', 'mw'), + (0x33C0, 'M', 'kω'), + (0x33C1, 'M', 'mω'), (0x33C2, 'X'), - (0x33C3, 'M', u'bq'), - (0x33C4, 'M', u'cc'), - (0x33C5, 'M', u'cd'), - (0x33C6, 'M', u'c∕kg'), + (0x33C3, 'M', 'bq'), + (0x33C4, 'M', 'cc'), + (0x33C5, 'M', 'cd'), + (0x33C6, 'M', 'c∕kg'), (0x33C7, 'X'), - (0x33C8, 'M', u'db'), - (0x33C9, 'M', u'gy'), - (0x33CA, 'M', u'ha'), - (0x33CB, 'M', u'hp'), - (0x33CC, 'M', u'in'), - (0x33CD, 'M', u'kk'), - (0x33CE, 'M', u'km'), - (0x33CF, 'M', u'kt'), - (0x33D0, 'M', u'lm'), - (0x33D1, 'M', u'ln'), - (0x33D2, 'M', u'log'), - (0x33D3, 'M', u'lx'), - (0x33D4, 'M', u'mb'), - (0x33D5, 'M', u'mil'), - (0x33D6, 'M', u'mol'), - (0x33D7, 'M', u'ph'), + (0x33C8, 'M', 'db'), + (0x33C9, 'M', 'gy'), + (0x33CA, 'M', 'ha'), + (0x33CB, 'M', 'hp'), + (0x33CC, 'M', 'in'), + (0x33CD, 'M', 'kk'), + (0x33CE, 'M', 'km'), + (0x33CF, 'M', 'kt'), + (0x33D0, 'M', 'lm'), + (0x33D1, 'M', 'ln'), + (0x33D2, 'M', 'log'), + (0x33D3, 'M', 'lx'), + (0x33D4, 'M', 'mb'), + (0x33D5, 'M', 'mil'), + (0x33D6, 'M', 'mol'), + (0x33D7, 'M', 'ph'), (0x33D8, 'X'), - (0x33D9, 'M', u'ppm'), - (0x33DA, 'M', u'pr'), - (0x33DB, 'M', u'sr'), - (0x33DC, 'M', u'sv'), - (0x33DD, 'M', u'wb'), - (0x33DE, 'M', u'v∕m'), - (0x33DF, 'M', u'a∕m'), - (0x33E0, 'M', u'1日'), - (0x33E1, 'M', u'2日'), - (0x33E2, 'M', u'3日'), - (0x33E3, 'M', u'4日'), - (0x33E4, 'M', u'5日'), - (0x33E5, 'M', u'6日'), - (0x33E6, 'M', u'7日'), - (0x33E7, 'M', u'8日'), - (0x33E8, 'M', u'9日'), - (0x33E9, 'M', u'10日'), - (0x33EA, 'M', u'11日'), - (0x33EB, 'M', u'12日'), - (0x33EC, 'M', u'13日'), - (0x33ED, 'M', u'14日'), - (0x33EE, 'M', u'15日'), - (0x33EF, 'M', u'16日'), - (0x33F0, 'M', u'17日'), - (0x33F1, 'M', u'18日'), - (0x33F2, 'M', u'19日'), - (0x33F3, 'M', u'20日'), + (0x33D9, 'M', 'ppm'), ] def _seg_35(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x33F4, 'M', u'21日'), - (0x33F5, 'M', u'22日'), - (0x33F6, 'M', u'23日'), - (0x33F7, 'M', u'24日'), - (0x33F8, 'M', u'25日'), - (0x33F9, 'M', u'26日'), - (0x33FA, 'M', u'27日'), - (0x33FB, 'M', u'28日'), - (0x33FC, 'M', u'29日'), - (0x33FD, 'M', u'30日'), - (0x33FE, 'M', u'31日'), - (0x33FF, 'M', u'gal'), + (0x33DA, 'M', 'pr'), + (0x33DB, 'M', 'sr'), + (0x33DC, 'M', 'sv'), + (0x33DD, 'M', 'wb'), + (0x33DE, 'M', 'v∕m'), + (0x33DF, 'M', 'a∕m'), + (0x33E0, 'M', '1日'), + (0x33E1, 'M', '2日'), + (0x33E2, 'M', '3日'), + (0x33E3, 'M', '4日'), + (0x33E4, 'M', '5日'), + (0x33E5, 'M', '6日'), + (0x33E6, 'M', '7日'), + (0x33E7, 'M', '8日'), + (0x33E8, 'M', '9日'), + (0x33E9, 'M', '10日'), + (0x33EA, 'M', '11日'), + (0x33EB, 'M', '12日'), + (0x33EC, 'M', '13日'), + (0x33ED, 'M', '14日'), + (0x33EE, 'M', '15日'), + (0x33EF, 'M', '16日'), + (0x33F0, 'M', '17日'), + (0x33F1, 'M', '18日'), + (0x33F2, 'M', '19日'), + (0x33F3, 'M', '20日'), + (0x33F4, 'M', '21日'), + (0x33F5, 'M', '22日'), + (0x33F6, 'M', '23日'), + (0x33F7, 'M', '24日'), + (0x33F8, 'M', '25日'), + (0x33F9, 'M', '26日'), + (0x33FA, 'M', '27日'), + (0x33FB, 'M', '28日'), + (0x33FC, 'M', '29日'), + (0x33FD, 'M', '30日'), + (0x33FE, 'M', '31日'), + (0x33FF, 'M', 'gal'), (0x3400, 'V'), - (0x4DB6, 'X'), - (0x4DC0, 'V'), - (0x9FF0, 'X'), + (0x9FFD, 'X'), (0xA000, 'V'), (0xA48D, 'X'), (0xA490, 'V'), (0xA4C7, 'X'), (0xA4D0, 'V'), (0xA62C, 'X'), - (0xA640, 'M', u'ꙁ'), + (0xA640, 'M', 'ꙁ'), (0xA641, 'V'), - (0xA642, 'M', u'ꙃ'), + (0xA642, 'M', 'ꙃ'), (0xA643, 'V'), - (0xA644, 'M', u'ꙅ'), + (0xA644, 'M', 'ꙅ'), (0xA645, 'V'), - (0xA646, 'M', u'ꙇ'), + (0xA646, 'M', 'ꙇ'), (0xA647, 'V'), - (0xA648, 'M', u'ꙉ'), + (0xA648, 'M', 'ꙉ'), (0xA649, 'V'), - (0xA64A, 'M', u'ꙋ'), + (0xA64A, 'M', 'ꙋ'), (0xA64B, 'V'), - (0xA64C, 'M', u'ꙍ'), + (0xA64C, 'M', 'ꙍ'), (0xA64D, 'V'), - (0xA64E, 'M', u'ꙏ'), + (0xA64E, 'M', 'ꙏ'), (0xA64F, 'V'), - (0xA650, 'M', u'ꙑ'), + (0xA650, 'M', 'ꙑ'), (0xA651, 'V'), - (0xA652, 'M', u'ꙓ'), + (0xA652, 'M', 'ꙓ'), (0xA653, 'V'), - (0xA654, 'M', u'ꙕ'), + (0xA654, 'M', 'ꙕ'), (0xA655, 'V'), - (0xA656, 'M', u'ꙗ'), + (0xA656, 'M', 'ꙗ'), (0xA657, 'V'), - (0xA658, 'M', u'ꙙ'), + (0xA658, 'M', 'ꙙ'), (0xA659, 'V'), - (0xA65A, 'M', u'ꙛ'), + (0xA65A, 'M', 'ꙛ'), (0xA65B, 'V'), - (0xA65C, 'M', u'ꙝ'), + (0xA65C, 'M', 'ꙝ'), (0xA65D, 'V'), - (0xA65E, 'M', u'ꙟ'), + (0xA65E, 'M', 'ꙟ'), (0xA65F, 'V'), - (0xA660, 'M', u'ꙡ'), + (0xA660, 'M', 'ꙡ'), (0xA661, 'V'), - (0xA662, 'M', u'ꙣ'), + (0xA662, 'M', 'ꙣ'), (0xA663, 'V'), - (0xA664, 'M', u'ꙥ'), + (0xA664, 'M', 'ꙥ'), (0xA665, 'V'), - (0xA666, 'M', u'ꙧ'), + (0xA666, 'M', 'ꙧ'), (0xA667, 'V'), - (0xA668, 'M', u'ꙩ'), + (0xA668, 'M', 'ꙩ'), (0xA669, 'V'), - (0xA66A, 'M', u'ꙫ'), + (0xA66A, 'M', 'ꙫ'), (0xA66B, 'V'), - (0xA66C, 'M', u'ꙭ'), + (0xA66C, 'M', 'ꙭ'), (0xA66D, 'V'), - (0xA680, 'M', u'ꚁ'), + (0xA680, 'M', 'ꚁ'), (0xA681, 'V'), - (0xA682, 'M', u'ꚃ'), + (0xA682, 'M', 'ꚃ'), (0xA683, 'V'), - (0xA684, 'M', u'ꚅ'), + (0xA684, 'M', 'ꚅ'), (0xA685, 'V'), - (0xA686, 'M', u'ꚇ'), + (0xA686, 'M', 'ꚇ'), (0xA687, 'V'), - (0xA688, 'M', u'ꚉ'), - (0xA689, 'V'), - (0xA68A, 'M', u'ꚋ'), - (0xA68B, 'V'), - (0xA68C, 'M', u'ꚍ'), - (0xA68D, 'V'), - (0xA68E, 'M', u'ꚏ'), - (0xA68F, 'V'), - (0xA690, 'M', u'ꚑ'), - (0xA691, 'V'), - (0xA692, 'M', u'ꚓ'), - (0xA693, 'V'), - (0xA694, 'M', u'ꚕ'), - (0xA695, 'V'), - (0xA696, 'M', u'ꚗ'), - (0xA697, 'V'), - (0xA698, 'M', u'ꚙ'), - (0xA699, 'V'), - (0xA69A, 'M', u'ꚛ'), - (0xA69B, 'V'), - (0xA69C, 'M', u'ъ'), - (0xA69D, 'M', u'ь'), - (0xA69E, 'V'), - (0xA6F8, 'X'), ] def _seg_36(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ + (0xA688, 'M', 'ꚉ'), + (0xA689, 'V'), + (0xA68A, 'M', 'ꚋ'), + (0xA68B, 'V'), + (0xA68C, 'M', 'ꚍ'), + (0xA68D, 'V'), + (0xA68E, 'M', 'ꚏ'), + (0xA68F, 'V'), + (0xA690, 'M', 'ꚑ'), + (0xA691, 'V'), + (0xA692, 'M', 'ꚓ'), + (0xA693, 'V'), + (0xA694, 'M', 'ꚕ'), + (0xA695, 'V'), + (0xA696, 'M', 'ꚗ'), + (0xA697, 'V'), + (0xA698, 'M', 'ꚙ'), + (0xA699, 'V'), + (0xA69A, 'M', 'ꚛ'), + (0xA69B, 'V'), + (0xA69C, 'M', 'ъ'), + (0xA69D, 'M', 'ь'), + (0xA69E, 'V'), + (0xA6F8, 'X'), (0xA700, 'V'), - (0xA722, 'M', u'ꜣ'), + (0xA722, 'M', 'ꜣ'), (0xA723, 'V'), - (0xA724, 'M', u'ꜥ'), + (0xA724, 'M', 'ꜥ'), (0xA725, 'V'), - (0xA726, 'M', u'ꜧ'), + (0xA726, 'M', 'ꜧ'), (0xA727, 'V'), - (0xA728, 'M', u'ꜩ'), + (0xA728, 'M', 'ꜩ'), (0xA729, 'V'), - (0xA72A, 'M', u'ꜫ'), + (0xA72A, 'M', 'ꜫ'), (0xA72B, 'V'), - (0xA72C, 'M', u'ꜭ'), + (0xA72C, 'M', 'ꜭ'), (0xA72D, 'V'), - (0xA72E, 'M', u'ꜯ'), + (0xA72E, 'M', 'ꜯ'), (0xA72F, 'V'), - (0xA732, 'M', u'ꜳ'), + (0xA732, 'M', 'ꜳ'), (0xA733, 'V'), - (0xA734, 'M', u'ꜵ'), + (0xA734, 'M', 'ꜵ'), (0xA735, 'V'), - (0xA736, 'M', u'ꜷ'), + (0xA736, 'M', 'ꜷ'), (0xA737, 'V'), - (0xA738, 'M', u'ꜹ'), + (0xA738, 'M', 'ꜹ'), (0xA739, 'V'), - (0xA73A, 'M', u'ꜻ'), + (0xA73A, 'M', 'ꜻ'), (0xA73B, 'V'), - (0xA73C, 'M', u'ꜽ'), + (0xA73C, 'M', 'ꜽ'), (0xA73D, 'V'), - (0xA73E, 'M', u'ꜿ'), + (0xA73E, 'M', 'ꜿ'), (0xA73F, 'V'), - (0xA740, 'M', u'ꝁ'), + (0xA740, 'M', 'ꝁ'), (0xA741, 'V'), - (0xA742, 'M', u'ꝃ'), + (0xA742, 'M', 'ꝃ'), (0xA743, 'V'), - (0xA744, 'M', u'ꝅ'), + (0xA744, 'M', 'ꝅ'), (0xA745, 'V'), - (0xA746, 'M', u'ꝇ'), + (0xA746, 'M', 'ꝇ'), (0xA747, 'V'), - (0xA748, 'M', u'ꝉ'), + (0xA748, 'M', 'ꝉ'), (0xA749, 'V'), - (0xA74A, 'M', u'ꝋ'), + (0xA74A, 'M', 'ꝋ'), (0xA74B, 'V'), - (0xA74C, 'M', u'ꝍ'), + (0xA74C, 'M', 'ꝍ'), (0xA74D, 'V'), - (0xA74E, 'M', u'ꝏ'), + (0xA74E, 'M', 'ꝏ'), (0xA74F, 'V'), - (0xA750, 'M', u'ꝑ'), + (0xA750, 'M', 'ꝑ'), (0xA751, 'V'), - (0xA752, 'M', u'ꝓ'), + (0xA752, 'M', 'ꝓ'), (0xA753, 'V'), - (0xA754, 'M', u'ꝕ'), + (0xA754, 'M', 'ꝕ'), (0xA755, 'V'), - (0xA756, 'M', u'ꝗ'), + (0xA756, 'M', 'ꝗ'), (0xA757, 'V'), - (0xA758, 'M', u'ꝙ'), + (0xA758, 'M', 'ꝙ'), (0xA759, 'V'), - (0xA75A, 'M', u'ꝛ'), + (0xA75A, 'M', 'ꝛ'), (0xA75B, 'V'), - (0xA75C, 'M', u'ꝝ'), + (0xA75C, 'M', 'ꝝ'), (0xA75D, 'V'), - (0xA75E, 'M', u'ꝟ'), + (0xA75E, 'M', 'ꝟ'), (0xA75F, 'V'), - (0xA760, 'M', u'ꝡ'), + (0xA760, 'M', 'ꝡ'), (0xA761, 'V'), - (0xA762, 'M', u'ꝣ'), + (0xA762, 'M', 'ꝣ'), (0xA763, 'V'), - (0xA764, 'M', u'ꝥ'), + (0xA764, 'M', 'ꝥ'), (0xA765, 'V'), - (0xA766, 'M', u'ꝧ'), + (0xA766, 'M', 'ꝧ'), (0xA767, 'V'), - (0xA768, 'M', u'ꝩ'), + (0xA768, 'M', 'ꝩ'), (0xA769, 'V'), - (0xA76A, 'M', u'ꝫ'), + (0xA76A, 'M', 'ꝫ'), (0xA76B, 'V'), - (0xA76C, 'M', u'ꝭ'), + (0xA76C, 'M', 'ꝭ'), (0xA76D, 'V'), - (0xA76E, 'M', u'ꝯ'), - (0xA76F, 'V'), - (0xA770, 'M', u'ꝯ'), - (0xA771, 'V'), - (0xA779, 'M', u'ꝺ'), - (0xA77A, 'V'), - (0xA77B, 'M', u'ꝼ'), - (0xA77C, 'V'), - (0xA77D, 'M', u'ᵹ'), - (0xA77E, 'M', u'ꝿ'), - (0xA77F, 'V'), - (0xA780, 'M', u'ꞁ'), - (0xA781, 'V'), - (0xA782, 'M', u'ꞃ'), - (0xA783, 'V'), - (0xA784, 'M', u'ꞅ'), - (0xA785, 'V'), - (0xA786, 'M', u'ꞇ'), - (0xA787, 'V'), - (0xA78B, 'M', u'ꞌ'), - (0xA78C, 'V'), - (0xA78D, 'M', u'ɥ'), - (0xA78E, 'V'), - (0xA790, 'M', u'ꞑ'), - (0xA791, 'V'), + (0xA76E, 'M', 'ꝯ'), ] def _seg_37(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0xA792, 'M', u'ꞓ'), + (0xA76F, 'V'), + (0xA770, 'M', 'ꝯ'), + (0xA771, 'V'), + (0xA779, 'M', 'ꝺ'), + (0xA77A, 'V'), + (0xA77B, 'M', 'ꝼ'), + (0xA77C, 'V'), + (0xA77D, 'M', 'ᵹ'), + (0xA77E, 'M', 'ꝿ'), + (0xA77F, 'V'), + (0xA780, 'M', 'ꞁ'), + (0xA781, 'V'), + (0xA782, 'M', 'ꞃ'), + (0xA783, 'V'), + (0xA784, 'M', 'ꞅ'), + (0xA785, 'V'), + (0xA786, 'M', 'ꞇ'), + (0xA787, 'V'), + (0xA78B, 'M', 'ꞌ'), + (0xA78C, 'V'), + (0xA78D, 'M', 'ɥ'), + (0xA78E, 'V'), + (0xA790, 'M', 'ꞑ'), + (0xA791, 'V'), + (0xA792, 'M', 'ꞓ'), (0xA793, 'V'), - (0xA796, 'M', u'ꞗ'), + (0xA796, 'M', 'ꞗ'), (0xA797, 'V'), - (0xA798, 'M', u'ꞙ'), + (0xA798, 'M', 'ꞙ'), (0xA799, 'V'), - (0xA79A, 'M', u'ꞛ'), + (0xA79A, 'M', 'ꞛ'), (0xA79B, 'V'), - (0xA79C, 'M', u'ꞝ'), + (0xA79C, 'M', 'ꞝ'), (0xA79D, 'V'), - (0xA79E, 'M', u'ꞟ'), + (0xA79E, 'M', 'ꞟ'), (0xA79F, 'V'), - (0xA7A0, 'M', u'ꞡ'), + (0xA7A0, 'M', 'ꞡ'), (0xA7A1, 'V'), - (0xA7A2, 'M', u'ꞣ'), + (0xA7A2, 'M', 'ꞣ'), (0xA7A3, 'V'), - (0xA7A4, 'M', u'ꞥ'), + (0xA7A4, 'M', 'ꞥ'), (0xA7A5, 'V'), - (0xA7A6, 'M', u'ꞧ'), + (0xA7A6, 'M', 'ꞧ'), (0xA7A7, 'V'), - (0xA7A8, 'M', u'ꞩ'), + (0xA7A8, 'M', 'ꞩ'), (0xA7A9, 'V'), - (0xA7AA, 'M', u'ɦ'), - (0xA7AB, 'M', u'ɜ'), - (0xA7AC, 'M', u'ɡ'), - (0xA7AD, 'M', u'ɬ'), - (0xA7AE, 'M', u'ɪ'), + (0xA7AA, 'M', 'ɦ'), + (0xA7AB, 'M', 'ɜ'), + (0xA7AC, 'M', 'ɡ'), + (0xA7AD, 'M', 'ɬ'), + (0xA7AE, 'M', 'ɪ'), (0xA7AF, 'V'), - (0xA7B0, 'M', u'ʞ'), - (0xA7B1, 'M', u'ʇ'), - (0xA7B2, 'M', u'ʝ'), - (0xA7B3, 'M', u'ꭓ'), - (0xA7B4, 'M', u'ꞵ'), + (0xA7B0, 'M', 'ʞ'), + (0xA7B1, 'M', 'ʇ'), + (0xA7B2, 'M', 'ʝ'), + (0xA7B3, 'M', 'ꭓ'), + (0xA7B4, 'M', 'ꞵ'), (0xA7B5, 'V'), - (0xA7B6, 'M', u'ꞷ'), + (0xA7B6, 'M', 'ꞷ'), (0xA7B7, 'V'), - (0xA7B8, 'X'), + (0xA7B8, 'M', 'ꞹ'), (0xA7B9, 'V'), - (0xA7BA, 'X'), - (0xA7F7, 'V'), - (0xA7F8, 'M', u'ħ'), - (0xA7F9, 'M', u'œ'), + (0xA7BA, 'M', 'ꞻ'), + (0xA7BB, 'V'), + (0xA7BC, 'M', 'ꞽ'), + (0xA7BD, 'V'), + (0xA7BE, 'M', 'ꞿ'), + (0xA7BF, 'V'), + (0xA7C0, 'X'), + (0xA7C2, 'M', 'ꟃ'), + (0xA7C3, 'V'), + (0xA7C4, 'M', 'ꞔ'), + (0xA7C5, 'M', 'ʂ'), + (0xA7C6, 'M', 'ᶎ'), + (0xA7C7, 'M', 'ꟈ'), + (0xA7C8, 'V'), + (0xA7C9, 'M', 'ꟊ'), + (0xA7CA, 'V'), + (0xA7CB, 'X'), + (0xA7F5, 'M', 'ꟶ'), + (0xA7F6, 'V'), + (0xA7F8, 'M', 'ħ'), + (0xA7F9, 'M', 'œ'), (0xA7FA, 'V'), - (0xA82C, 'X'), + (0xA82D, 'X'), (0xA830, 'V'), (0xA83A, 'X'), (0xA840, 'V'), @@ -3914,6 +3994,11 @@ def _seg_37(): (0xA980, 'V'), (0xA9CE, 'X'), (0xA9CF, 'V'), + ] + +def _seg_38(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ (0xA9DA, 'X'), (0xA9DE, 'V'), (0xA9FF, 'X'), @@ -3938,96 +4023,99 @@ def _seg_37(): (0xAB28, 'V'), (0xAB2F, 'X'), (0xAB30, 'V'), - (0xAB5C, 'M', u'ꜧ'), - (0xAB5D, 'M', u'ꬷ'), - (0xAB5E, 'M', u'ɫ'), - (0xAB5F, 'M', u'ꭒ'), + (0xAB5C, 'M', 'ꜧ'), + (0xAB5D, 'M', 'ꬷ'), + (0xAB5E, 'M', 'ɫ'), + (0xAB5F, 'M', 'ꭒ'), (0xAB60, 'V'), - (0xAB66, 'X'), - (0xAB70, 'M', u'Ꭰ'), - (0xAB71, 'M', u'Ꭱ'), - (0xAB72, 'M', u'Ꭲ'), - (0xAB73, 'M', u'Ꭳ'), - (0xAB74, 'M', u'Ꭴ'), - (0xAB75, 'M', u'Ꭵ'), - (0xAB76, 'M', u'Ꭶ'), - (0xAB77, 'M', u'Ꭷ'), - (0xAB78, 'M', u'Ꭸ'), - (0xAB79, 'M', u'Ꭹ'), - (0xAB7A, 'M', u'Ꭺ'), + (0xAB69, 'M', 'ʍ'), + (0xAB6A, 'V'), + (0xAB6C, 'X'), + (0xAB70, 'M', 'Ꭰ'), + (0xAB71, 'M', 'Ꭱ'), + (0xAB72, 'M', 'Ꭲ'), + (0xAB73, 'M', 'Ꭳ'), + (0xAB74, 'M', 'Ꭴ'), + (0xAB75, 'M', 'Ꭵ'), + (0xAB76, 'M', 'Ꭶ'), + (0xAB77, 'M', 'Ꭷ'), + (0xAB78, 'M', 'Ꭸ'), + (0xAB79, 'M', 'Ꭹ'), + (0xAB7A, 'M', 'Ꭺ'), + (0xAB7B, 'M', 'Ꭻ'), + (0xAB7C, 'M', 'Ꭼ'), + (0xAB7D, 'M', 'Ꭽ'), + (0xAB7E, 'M', 'Ꭾ'), + (0xAB7F, 'M', 'Ꭿ'), + (0xAB80, 'M', 'Ꮀ'), + (0xAB81, 'M', 'Ꮁ'), + (0xAB82, 'M', 'Ꮂ'), + (0xAB83, 'M', 'Ꮃ'), + (0xAB84, 'M', 'Ꮄ'), + (0xAB85, 'M', 'Ꮅ'), + (0xAB86, 'M', 'Ꮆ'), + (0xAB87, 'M', 'Ꮇ'), + (0xAB88, 'M', 'Ꮈ'), + (0xAB89, 'M', 'Ꮉ'), + (0xAB8A, 'M', 'Ꮊ'), + (0xAB8B, 'M', 'Ꮋ'), + (0xAB8C, 'M', 'Ꮌ'), + (0xAB8D, 'M', 'Ꮍ'), + (0xAB8E, 'M', 'Ꮎ'), + (0xAB8F, 'M', 'Ꮏ'), + (0xAB90, 'M', 'Ꮐ'), + (0xAB91, 'M', 'Ꮑ'), + (0xAB92, 'M', 'Ꮒ'), + (0xAB93, 'M', 'Ꮓ'), + (0xAB94, 'M', 'Ꮔ'), + (0xAB95, 'M', 'Ꮕ'), + (0xAB96, 'M', 'Ꮖ'), + (0xAB97, 'M', 'Ꮗ'), + (0xAB98, 'M', 'Ꮘ'), + (0xAB99, 'M', 'Ꮙ'), + (0xAB9A, 'M', 'Ꮚ'), + (0xAB9B, 'M', 'Ꮛ'), + (0xAB9C, 'M', 'Ꮜ'), + (0xAB9D, 'M', 'Ꮝ'), + (0xAB9E, 'M', 'Ꮞ'), + (0xAB9F, 'M', 'Ꮟ'), + (0xABA0, 'M', 'Ꮠ'), + (0xABA1, 'M', 'Ꮡ'), + (0xABA2, 'M', 'Ꮢ'), + (0xABA3, 'M', 'Ꮣ'), + (0xABA4, 'M', 'Ꮤ'), + (0xABA5, 'M', 'Ꮥ'), + (0xABA6, 'M', 'Ꮦ'), + (0xABA7, 'M', 'Ꮧ'), + (0xABA8, 'M', 'Ꮨ'), + (0xABA9, 'M', 'Ꮩ'), + (0xABAA, 'M', 'Ꮪ'), + (0xABAB, 'M', 'Ꮫ'), + (0xABAC, 'M', 'Ꮬ'), + (0xABAD, 'M', 'Ꮭ'), + (0xABAE, 'M', 'Ꮮ'), + (0xABAF, 'M', 'Ꮯ'), + (0xABB0, 'M', 'Ꮰ'), + (0xABB1, 'M', 'Ꮱ'), + (0xABB2, 'M', 'Ꮲ'), + (0xABB3, 'M', 'Ꮳ'), ] -def _seg_38(): +def _seg_39(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0xAB7B, 'M', u'Ꭻ'), - (0xAB7C, 'M', u'Ꭼ'), - (0xAB7D, 'M', u'Ꭽ'), - (0xAB7E, 'M', u'Ꭾ'), - (0xAB7F, 'M', u'Ꭿ'), - (0xAB80, 'M', u'Ꮀ'), - (0xAB81, 'M', u'Ꮁ'), - (0xAB82, 'M', u'Ꮂ'), - (0xAB83, 'M', u'Ꮃ'), - (0xAB84, 'M', u'Ꮄ'), - (0xAB85, 'M', u'Ꮅ'), - (0xAB86, 'M', u'Ꮆ'), - (0xAB87, 'M', u'Ꮇ'), - (0xAB88, 'M', u'Ꮈ'), - (0xAB89, 'M', u'Ꮉ'), - (0xAB8A, 'M', u'Ꮊ'), - (0xAB8B, 'M', u'Ꮋ'), - (0xAB8C, 'M', u'Ꮌ'), - (0xAB8D, 'M', u'Ꮍ'), - (0xAB8E, 'M', u'Ꮎ'), - (0xAB8F, 'M', u'Ꮏ'), - (0xAB90, 'M', u'Ꮐ'), - (0xAB91, 'M', u'Ꮑ'), - (0xAB92, 'M', u'Ꮒ'), - (0xAB93, 'M', u'Ꮓ'), - (0xAB94, 'M', u'Ꮔ'), - (0xAB95, 'M', u'Ꮕ'), - (0xAB96, 'M', u'Ꮖ'), - (0xAB97, 'M', u'Ꮗ'), - (0xAB98, 'M', u'Ꮘ'), - (0xAB99, 'M', u'Ꮙ'), - (0xAB9A, 'M', u'Ꮚ'), - (0xAB9B, 'M', u'Ꮛ'), - (0xAB9C, 'M', u'Ꮜ'), - (0xAB9D, 'M', u'Ꮝ'), - (0xAB9E, 'M', u'Ꮞ'), - (0xAB9F, 'M', u'Ꮟ'), - (0xABA0, 'M', u'Ꮠ'), - (0xABA1, 'M', u'Ꮡ'), - (0xABA2, 'M', u'Ꮢ'), - (0xABA3, 'M', u'Ꮣ'), - (0xABA4, 'M', u'Ꮤ'), - (0xABA5, 'M', u'Ꮥ'), - (0xABA6, 'M', u'Ꮦ'), - (0xABA7, 'M', u'Ꮧ'), - (0xABA8, 'M', u'Ꮨ'), - (0xABA9, 'M', u'Ꮩ'), - (0xABAA, 'M', u'Ꮪ'), - (0xABAB, 'M', u'Ꮫ'), - (0xABAC, 'M', u'Ꮬ'), - (0xABAD, 'M', u'Ꮭ'), - (0xABAE, 'M', u'Ꮮ'), - (0xABAF, 'M', u'Ꮯ'), - (0xABB0, 'M', u'Ꮰ'), - (0xABB1, 'M', u'Ꮱ'), - (0xABB2, 'M', u'Ꮲ'), - (0xABB3, 'M', u'Ꮳ'), - (0xABB4, 'M', u'Ꮴ'), - (0xABB5, 'M', u'Ꮵ'), - (0xABB6, 'M', u'Ꮶ'), - (0xABB7, 'M', u'Ꮷ'), - (0xABB8, 'M', u'Ꮸ'), - (0xABB9, 'M', u'Ꮹ'), - (0xABBA, 'M', u'Ꮺ'), - (0xABBB, 'M', u'Ꮻ'), - (0xABBC, 'M', u'Ꮼ'), - (0xABBD, 'M', u'Ꮽ'), - (0xABBE, 'M', u'Ꮾ'), - (0xABBF, 'M', u'Ꮿ'), + (0xABB4, 'M', 'Ꮴ'), + (0xABB5, 'M', 'Ꮵ'), + (0xABB6, 'M', 'Ꮶ'), + (0xABB7, 'M', 'Ꮷ'), + (0xABB8, 'M', 'Ꮸ'), + (0xABB9, 'M', 'Ꮹ'), + (0xABBA, 'M', 'Ꮺ'), + (0xABBB, 'M', 'Ꮻ'), + (0xABBC, 'M', 'Ꮼ'), + (0xABBD, 'M', 'Ꮽ'), + (0xABBE, 'M', 'Ꮾ'), + (0xABBF, 'M', 'Ꮿ'), (0xABC0, 'V'), (0xABEE, 'X'), (0xABF0, 'V'), @@ -4038,1440 +4126,1454 @@ def _seg_38(): (0xD7C7, 'X'), (0xD7CB, 'V'), (0xD7FC, 'X'), - (0xF900, 'M', u'豈'), - (0xF901, 'M', u'更'), - (0xF902, 'M', u'車'), - (0xF903, 'M', u'賈'), - (0xF904, 'M', u'滑'), - (0xF905, 'M', u'串'), - (0xF906, 'M', u'句'), - (0xF907, 'M', u'龜'), - (0xF909, 'M', u'契'), - (0xF90A, 'M', u'金'), - (0xF90B, 'M', u'喇'), - (0xF90C, 'M', u'奈'), - (0xF90D, 'M', u'懶'), - (0xF90E, 'M', u'癩'), - (0xF90F, 'M', u'羅'), - (0xF910, 'M', u'蘿'), - (0xF911, 'M', u'螺'), - (0xF912, 'M', u'裸'), - (0xF913, 'M', u'邏'), - (0xF914, 'M', u'樂'), - (0xF915, 'M', u'洛'), - ] - -def _seg_39(): - return [ - (0xF916, 'M', u'烙'), - (0xF917, 'M', u'珞'), - (0xF918, 'M', u'落'), - (0xF919, 'M', u'酪'), - (0xF91A, 'M', u'駱'), - (0xF91B, 'M', u'亂'), - (0xF91C, 'M', u'卵'), - (0xF91D, 'M', u'欄'), - (0xF91E, 'M', u'爛'), - (0xF91F, 'M', u'蘭'), - (0xF920, 'M', u'鸞'), - (0xF921, 'M', u'嵐'), - (0xF922, 'M', u'濫'), - (0xF923, 'M', u'藍'), - (0xF924, 'M', u'襤'), - (0xF925, 'M', u'拉'), - (0xF926, 'M', u'臘'), - (0xF927, 'M', u'蠟'), - (0xF928, 'M', u'廊'), - (0xF929, 'M', u'朗'), - (0xF92A, 'M', u'浪'), - (0xF92B, 'M', u'狼'), - (0xF92C, 'M', u'郎'), - (0xF92D, 'M', u'來'), - (0xF92E, 'M', u'冷'), - (0xF92F, 'M', u'勞'), - (0xF930, 'M', u'擄'), - (0xF931, 'M', u'櫓'), - (0xF932, 'M', u'爐'), - (0xF933, 'M', u'盧'), - (0xF934, 'M', u'老'), - (0xF935, 'M', u'蘆'), - (0xF936, 'M', u'虜'), - (0xF937, 'M', u'路'), - (0xF938, 'M', u'露'), - (0xF939, 'M', u'魯'), - (0xF93A, 'M', u'鷺'), - (0xF93B, 'M', u'碌'), - (0xF93C, 'M', u'祿'), - (0xF93D, 'M', u'綠'), - (0xF93E, 'M', u'菉'), - (0xF93F, 'M', u'錄'), - (0xF940, 'M', u'鹿'), - (0xF941, 'M', u'論'), - (0xF942, 'M', u'壟'), - (0xF943, 'M', u'弄'), - (0xF944, 'M', u'籠'), - (0xF945, 'M', u'聾'), - (0xF946, 'M', u'牢'), - (0xF947, 'M', u'磊'), - (0xF948, 'M', u'賂'), - (0xF949, 'M', u'雷'), - (0xF94A, 'M', u'壘'), - (0xF94B, 'M', u'屢'), - (0xF94C, 'M', u'樓'), - (0xF94D, 'M', u'淚'), - (0xF94E, 'M', u'漏'), - (0xF94F, 'M', u'累'), - (0xF950, 'M', u'縷'), - (0xF951, 'M', u'陋'), - (0xF952, 'M', u'勒'), - (0xF953, 'M', u'肋'), - (0xF954, 'M', u'凜'), - (0xF955, 'M', u'凌'), - (0xF956, 'M', u'稜'), - (0xF957, 'M', u'綾'), - (0xF958, 'M', u'菱'), - (0xF959, 'M', u'陵'), - (0xF95A, 'M', u'讀'), - (0xF95B, 'M', u'拏'), - (0xF95C, 'M', u'樂'), - (0xF95D, 'M', u'諾'), - (0xF95E, 'M', u'丹'), - (0xF95F, 'M', u'寧'), - (0xF960, 'M', u'怒'), - (0xF961, 'M', u'率'), - (0xF962, 'M', u'異'), - (0xF963, 'M', u'北'), - (0xF964, 'M', u'磻'), - (0xF965, 'M', u'便'), - (0xF966, 'M', u'復'), - (0xF967, 'M', u'不'), - (0xF968, 'M', u'泌'), - (0xF969, 'M', u'數'), - (0xF96A, 'M', u'索'), - (0xF96B, 'M', u'參'), - (0xF96C, 'M', u'塞'), - (0xF96D, 'M', u'省'), - (0xF96E, 'M', u'葉'), - (0xF96F, 'M', u'說'), - (0xF970, 'M', u'殺'), - (0xF971, 'M', u'辰'), - (0xF972, 'M', u'沈'), - (0xF973, 'M', u'拾'), - (0xF974, 'M', u'若'), - (0xF975, 'M', u'掠'), - (0xF976, 'M', u'略'), - (0xF977, 'M', u'亮'), - (0xF978, 'M', u'兩'), - (0xF979, 'M', u'凉'), + (0xF900, 'M', '豈'), + (0xF901, 'M', '更'), + (0xF902, 'M', '車'), + (0xF903, 'M', '賈'), + (0xF904, 'M', '滑'), + (0xF905, 'M', '串'), + (0xF906, 'M', '句'), + (0xF907, 'M', '龜'), + (0xF909, 'M', '契'), + (0xF90A, 'M', '金'), + (0xF90B, 'M', '喇'), + (0xF90C, 'M', '奈'), + (0xF90D, 'M', '懶'), + (0xF90E, 'M', '癩'), + (0xF90F, 'M', '羅'), + (0xF910, 'M', '蘿'), + (0xF911, 'M', '螺'), + (0xF912, 'M', '裸'), + (0xF913, 'M', '邏'), + (0xF914, 'M', '樂'), + (0xF915, 'M', '洛'), + (0xF916, 'M', '烙'), + (0xF917, 'M', '珞'), + (0xF918, 'M', '落'), + (0xF919, 'M', '酪'), + (0xF91A, 'M', '駱'), + (0xF91B, 'M', '亂'), + (0xF91C, 'M', '卵'), + (0xF91D, 'M', '欄'), + (0xF91E, 'M', '爛'), + (0xF91F, 'M', '蘭'), + (0xF920, 'M', '鸞'), + (0xF921, 'M', '嵐'), + (0xF922, 'M', '濫'), + (0xF923, 'M', '藍'), + (0xF924, 'M', '襤'), + (0xF925, 'M', '拉'), + (0xF926, 'M', '臘'), + (0xF927, 'M', '蠟'), + (0xF928, 'M', '廊'), + (0xF929, 'M', '朗'), + (0xF92A, 'M', '浪'), + (0xF92B, 'M', '狼'), + (0xF92C, 'M', '郎'), + (0xF92D, 'M', '來'), + (0xF92E, 'M', '冷'), + (0xF92F, 'M', '勞'), + (0xF930, 'M', '擄'), + (0xF931, 'M', '櫓'), + (0xF932, 'M', '爐'), + (0xF933, 'M', '盧'), + (0xF934, 'M', '老'), + (0xF935, 'M', '蘆'), + (0xF936, 'M', '虜'), + (0xF937, 'M', '路'), + (0xF938, 'M', '露'), + (0xF939, 'M', '魯'), + (0xF93A, 'M', '鷺'), + (0xF93B, 'M', '碌'), + (0xF93C, 'M', '祿'), + (0xF93D, 'M', '綠'), + (0xF93E, 'M', '菉'), + (0xF93F, 'M', '錄'), + (0xF940, 'M', '鹿'), + (0xF941, 'M', '論'), + (0xF942, 'M', '壟'), + (0xF943, 'M', '弄'), + (0xF944, 'M', '籠'), + (0xF945, 'M', '聾'), + (0xF946, 'M', '牢'), + (0xF947, 'M', '磊'), + (0xF948, 'M', '賂'), + (0xF949, 'M', '雷'), + (0xF94A, 'M', '壘'), + (0xF94B, 'M', '屢'), + (0xF94C, 'M', '樓'), + (0xF94D, 'M', '淚'), + (0xF94E, 'M', '漏'), ] def _seg_40(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0xF97A, 'M', u'梁'), - (0xF97B, 'M', u'糧'), - (0xF97C, 'M', u'良'), - (0xF97D, 'M', u'諒'), - (0xF97E, 'M', u'量'), - (0xF97F, 'M', u'勵'), - (0xF980, 'M', u'呂'), - (0xF981, 'M', u'女'), - (0xF982, 'M', u'廬'), - (0xF983, 'M', u'旅'), - (0xF984, 'M', u'濾'), - (0xF985, 'M', u'礪'), - (0xF986, 'M', u'閭'), - (0xF987, 'M', u'驪'), - (0xF988, 'M', u'麗'), - (0xF989, 'M', u'黎'), - (0xF98A, 'M', u'力'), - (0xF98B, 'M', u'曆'), - (0xF98C, 'M', u'歷'), - (0xF98D, 'M', u'轢'), - (0xF98E, 'M', u'年'), - (0xF98F, 'M', u'憐'), - (0xF990, 'M', u'戀'), - (0xF991, 'M', u'撚'), - (0xF992, 'M', u'漣'), - (0xF993, 'M', u'煉'), - (0xF994, 'M', u'璉'), - (0xF995, 'M', u'秊'), - (0xF996, 'M', u'練'), - (0xF997, 'M', u'聯'), - (0xF998, 'M', u'輦'), - (0xF999, 'M', u'蓮'), - (0xF99A, 'M', u'連'), - (0xF99B, 'M', u'鍊'), - (0xF99C, 'M', u'列'), - (0xF99D, 'M', u'劣'), - (0xF99E, 'M', u'咽'), - (0xF99F, 'M', u'烈'), - (0xF9A0, 'M', u'裂'), - (0xF9A1, 'M', u'說'), - (0xF9A2, 'M', u'廉'), - (0xF9A3, 'M', u'念'), - (0xF9A4, 'M', u'捻'), - (0xF9A5, 'M', u'殮'), - (0xF9A6, 'M', u'簾'), - (0xF9A7, 'M', u'獵'), - (0xF9A8, 'M', u'令'), - (0xF9A9, 'M', u'囹'), - (0xF9AA, 'M', u'寧'), - (0xF9AB, 'M', u'嶺'), - (0xF9AC, 'M', u'怜'), - (0xF9AD, 'M', u'玲'), - (0xF9AE, 'M', u'瑩'), - (0xF9AF, 'M', u'羚'), - (0xF9B0, 'M', u'聆'), - (0xF9B1, 'M', u'鈴'), - (0xF9B2, 'M', u'零'), - (0xF9B3, 'M', u'靈'), - (0xF9B4, 'M', u'領'), - (0xF9B5, 'M', u'例'), - (0xF9B6, 'M', u'禮'), - (0xF9B7, 'M', u'醴'), - (0xF9B8, 'M', u'隸'), - (0xF9B9, 'M', u'惡'), - (0xF9BA, 'M', u'了'), - (0xF9BB, 'M', u'僚'), - (0xF9BC, 'M', u'寮'), - (0xF9BD, 'M', u'尿'), - (0xF9BE, 'M', u'料'), - (0xF9BF, 'M', u'樂'), - (0xF9C0, 'M', u'燎'), - (0xF9C1, 'M', u'療'), - (0xF9C2, 'M', u'蓼'), - (0xF9C3, 'M', u'遼'), - (0xF9C4, 'M', u'龍'), - (0xF9C5, 'M', u'暈'), - (0xF9C6, 'M', u'阮'), - (0xF9C7, 'M', u'劉'), - (0xF9C8, 'M', u'杻'), - (0xF9C9, 'M', u'柳'), - (0xF9CA, 'M', u'流'), - (0xF9CB, 'M', u'溜'), - (0xF9CC, 'M', u'琉'), - (0xF9CD, 'M', u'留'), - (0xF9CE, 'M', u'硫'), - (0xF9CF, 'M', u'紐'), - (0xF9D0, 'M', u'類'), - (0xF9D1, 'M', u'六'), - (0xF9D2, 'M', u'戮'), - (0xF9D3, 'M', u'陸'), - (0xF9D4, 'M', u'倫'), - (0xF9D5, 'M', u'崙'), - (0xF9D6, 'M', u'淪'), - (0xF9D7, 'M', u'輪'), - (0xF9D8, 'M', u'律'), - (0xF9D9, 'M', u'慄'), - (0xF9DA, 'M', u'栗'), - (0xF9DB, 'M', u'率'), - (0xF9DC, 'M', u'隆'), - (0xF9DD, 'M', u'利'), + (0xF94F, 'M', '累'), + (0xF950, 'M', '縷'), + (0xF951, 'M', '陋'), + (0xF952, 'M', '勒'), + (0xF953, 'M', '肋'), + (0xF954, 'M', '凜'), + (0xF955, 'M', '凌'), + (0xF956, 'M', '稜'), + (0xF957, 'M', '綾'), + (0xF958, 'M', '菱'), + (0xF959, 'M', '陵'), + (0xF95A, 'M', '讀'), + (0xF95B, 'M', '拏'), + (0xF95C, 'M', '樂'), + (0xF95D, 'M', '諾'), + (0xF95E, 'M', '丹'), + (0xF95F, 'M', '寧'), + (0xF960, 'M', '怒'), + (0xF961, 'M', '率'), + (0xF962, 'M', '異'), + (0xF963, 'M', '北'), + (0xF964, 'M', '磻'), + (0xF965, 'M', '便'), + (0xF966, 'M', '復'), + (0xF967, 'M', '不'), + (0xF968, 'M', '泌'), + (0xF969, 'M', '數'), + (0xF96A, 'M', '索'), + (0xF96B, 'M', '參'), + (0xF96C, 'M', '塞'), + (0xF96D, 'M', '省'), + (0xF96E, 'M', '葉'), + (0xF96F, 'M', '說'), + (0xF970, 'M', '殺'), + (0xF971, 'M', '辰'), + (0xF972, 'M', '沈'), + (0xF973, 'M', '拾'), + (0xF974, 'M', '若'), + (0xF975, 'M', '掠'), + (0xF976, 'M', '略'), + (0xF977, 'M', '亮'), + (0xF978, 'M', '兩'), + (0xF979, 'M', '凉'), + (0xF97A, 'M', '梁'), + (0xF97B, 'M', '糧'), + (0xF97C, 'M', '良'), + (0xF97D, 'M', '諒'), + (0xF97E, 'M', '量'), + (0xF97F, 'M', '勵'), + (0xF980, 'M', '呂'), + (0xF981, 'M', '女'), + (0xF982, 'M', '廬'), + (0xF983, 'M', '旅'), + (0xF984, 'M', '濾'), + (0xF985, 'M', '礪'), + (0xF986, 'M', '閭'), + (0xF987, 'M', '驪'), + (0xF988, 'M', '麗'), + (0xF989, 'M', '黎'), + (0xF98A, 'M', '力'), + (0xF98B, 'M', '曆'), + (0xF98C, 'M', '歷'), + (0xF98D, 'M', '轢'), + (0xF98E, 'M', '年'), + (0xF98F, 'M', '憐'), + (0xF990, 'M', '戀'), + (0xF991, 'M', '撚'), + (0xF992, 'M', '漣'), + (0xF993, 'M', '煉'), + (0xF994, 'M', '璉'), + (0xF995, 'M', '秊'), + (0xF996, 'M', '練'), + (0xF997, 'M', '聯'), + (0xF998, 'M', '輦'), + (0xF999, 'M', '蓮'), + (0xF99A, 'M', '連'), + (0xF99B, 'M', '鍊'), + (0xF99C, 'M', '列'), + (0xF99D, 'M', '劣'), + (0xF99E, 'M', '咽'), + (0xF99F, 'M', '烈'), + (0xF9A0, 'M', '裂'), + (0xF9A1, 'M', '說'), + (0xF9A2, 'M', '廉'), + (0xF9A3, 'M', '念'), + (0xF9A4, 'M', '捻'), + (0xF9A5, 'M', '殮'), + (0xF9A6, 'M', '簾'), + (0xF9A7, 'M', '獵'), + (0xF9A8, 'M', '令'), + (0xF9A9, 'M', '囹'), + (0xF9AA, 'M', '寧'), + (0xF9AB, 'M', '嶺'), + (0xF9AC, 'M', '怜'), + (0xF9AD, 'M', '玲'), + (0xF9AE, 'M', '瑩'), + (0xF9AF, 'M', '羚'), + (0xF9B0, 'M', '聆'), + (0xF9B1, 'M', '鈴'), + (0xF9B2, 'M', '零'), ] def _seg_41(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0xF9DE, 'M', u'吏'), - (0xF9DF, 'M', u'履'), - (0xF9E0, 'M', u'易'), - (0xF9E1, 'M', u'李'), - (0xF9E2, 'M', u'梨'), - (0xF9E3, 'M', u'泥'), - (0xF9E4, 'M', u'理'), - (0xF9E5, 'M', u'痢'), - (0xF9E6, 'M', u'罹'), - (0xF9E7, 'M', u'裏'), - (0xF9E8, 'M', u'裡'), - (0xF9E9, 'M', u'里'), - (0xF9EA, 'M', u'離'), - (0xF9EB, 'M', u'匿'), - (0xF9EC, 'M', u'溺'), - (0xF9ED, 'M', u'吝'), - (0xF9EE, 'M', u'燐'), - (0xF9EF, 'M', u'璘'), - (0xF9F0, 'M', u'藺'), - (0xF9F1, 'M', u'隣'), - (0xF9F2, 'M', u'鱗'), - (0xF9F3, 'M', u'麟'), - (0xF9F4, 'M', u'林'), - (0xF9F5, 'M', u'淋'), - (0xF9F6, 'M', u'臨'), - (0xF9F7, 'M', u'立'), - (0xF9F8, 'M', u'笠'), - (0xF9F9, 'M', u'粒'), - (0xF9FA, 'M', u'狀'), - (0xF9FB, 'M', u'炙'), - (0xF9FC, 'M', u'識'), - (0xF9FD, 'M', u'什'), - (0xF9FE, 'M', u'茶'), - (0xF9FF, 'M', u'刺'), - (0xFA00, 'M', u'切'), - (0xFA01, 'M', u'度'), - (0xFA02, 'M', u'拓'), - (0xFA03, 'M', u'糖'), - (0xFA04, 'M', u'宅'), - (0xFA05, 'M', u'洞'), - (0xFA06, 'M', u'暴'), - (0xFA07, 'M', u'輻'), - (0xFA08, 'M', u'行'), - (0xFA09, 'M', u'降'), - (0xFA0A, 'M', u'見'), - (0xFA0B, 'M', u'廓'), - (0xFA0C, 'M', u'兀'), - (0xFA0D, 'M', u'嗀'), + (0xF9B3, 'M', '靈'), + (0xF9B4, 'M', '領'), + (0xF9B5, 'M', '例'), + (0xF9B6, 'M', '禮'), + (0xF9B7, 'M', '醴'), + (0xF9B8, 'M', '隸'), + (0xF9B9, 'M', '惡'), + (0xF9BA, 'M', '了'), + (0xF9BB, 'M', '僚'), + (0xF9BC, 'M', '寮'), + (0xF9BD, 'M', '尿'), + (0xF9BE, 'M', '料'), + (0xF9BF, 'M', '樂'), + (0xF9C0, 'M', '燎'), + (0xF9C1, 'M', '療'), + (0xF9C2, 'M', '蓼'), + (0xF9C3, 'M', '遼'), + (0xF9C4, 'M', '龍'), + (0xF9C5, 'M', '暈'), + (0xF9C6, 'M', '阮'), + (0xF9C7, 'M', '劉'), + (0xF9C8, 'M', '杻'), + (0xF9C9, 'M', '柳'), + (0xF9CA, 'M', '流'), + (0xF9CB, 'M', '溜'), + (0xF9CC, 'M', '琉'), + (0xF9CD, 'M', '留'), + (0xF9CE, 'M', '硫'), + (0xF9CF, 'M', '紐'), + (0xF9D0, 'M', '類'), + (0xF9D1, 'M', '六'), + (0xF9D2, 'M', '戮'), + (0xF9D3, 'M', '陸'), + (0xF9D4, 'M', '倫'), + (0xF9D5, 'M', '崙'), + (0xF9D6, 'M', '淪'), + (0xF9D7, 'M', '輪'), + (0xF9D8, 'M', '律'), + (0xF9D9, 'M', '慄'), + (0xF9DA, 'M', '栗'), + (0xF9DB, 'M', '率'), + (0xF9DC, 'M', '隆'), + (0xF9DD, 'M', '利'), + (0xF9DE, 'M', '吏'), + (0xF9DF, 'M', '履'), + (0xF9E0, 'M', '易'), + (0xF9E1, 'M', '李'), + (0xF9E2, 'M', '梨'), + (0xF9E3, 'M', '泥'), + (0xF9E4, 'M', '理'), + (0xF9E5, 'M', '痢'), + (0xF9E6, 'M', '罹'), + (0xF9E7, 'M', '裏'), + (0xF9E8, 'M', '裡'), + (0xF9E9, 'M', '里'), + (0xF9EA, 'M', '離'), + (0xF9EB, 'M', '匿'), + (0xF9EC, 'M', '溺'), + (0xF9ED, 'M', '吝'), + (0xF9EE, 'M', '燐'), + (0xF9EF, 'M', '璘'), + (0xF9F0, 'M', '藺'), + (0xF9F1, 'M', '隣'), + (0xF9F2, 'M', '鱗'), + (0xF9F3, 'M', '麟'), + (0xF9F4, 'M', '林'), + (0xF9F5, 'M', '淋'), + (0xF9F6, 'M', '臨'), + (0xF9F7, 'M', '立'), + (0xF9F8, 'M', '笠'), + (0xF9F9, 'M', '粒'), + (0xF9FA, 'M', '狀'), + (0xF9FB, 'M', '炙'), + (0xF9FC, 'M', '識'), + (0xF9FD, 'M', '什'), + (0xF9FE, 'M', '茶'), + (0xF9FF, 'M', '刺'), + (0xFA00, 'M', '切'), + (0xFA01, 'M', '度'), + (0xFA02, 'M', '拓'), + (0xFA03, 'M', '糖'), + (0xFA04, 'M', '宅'), + (0xFA05, 'M', '洞'), + (0xFA06, 'M', '暴'), + (0xFA07, 'M', '輻'), + (0xFA08, 'M', '行'), + (0xFA09, 'M', '降'), + (0xFA0A, 'M', '見'), + (0xFA0B, 'M', '廓'), + (0xFA0C, 'M', '兀'), + (0xFA0D, 'M', '嗀'), (0xFA0E, 'V'), - (0xFA10, 'M', u'塚'), + (0xFA10, 'M', '塚'), (0xFA11, 'V'), - (0xFA12, 'M', u'晴'), + (0xFA12, 'M', '晴'), (0xFA13, 'V'), - (0xFA15, 'M', u'凞'), - (0xFA16, 'M', u'猪'), - (0xFA17, 'M', u'益'), - (0xFA18, 'M', u'礼'), - (0xFA19, 'M', u'神'), - (0xFA1A, 'M', u'祥'), - (0xFA1B, 'M', u'福'), - (0xFA1C, 'M', u'靖'), - (0xFA1D, 'M', u'精'), - (0xFA1E, 'M', u'羽'), - (0xFA1F, 'V'), - (0xFA20, 'M', u'蘒'), - (0xFA21, 'V'), - (0xFA22, 'M', u'諸'), - (0xFA23, 'V'), - (0xFA25, 'M', u'逸'), - (0xFA26, 'M', u'都'), - (0xFA27, 'V'), - (0xFA2A, 'M', u'飯'), - (0xFA2B, 'M', u'飼'), - (0xFA2C, 'M', u'館'), - (0xFA2D, 'M', u'鶴'), - (0xFA2E, 'M', u'郞'), - (0xFA2F, 'M', u'隷'), - (0xFA30, 'M', u'侮'), - (0xFA31, 'M', u'僧'), - (0xFA32, 'M', u'免'), - (0xFA33, 'M', u'勉'), - (0xFA34, 'M', u'勤'), - (0xFA35, 'M', u'卑'), - (0xFA36, 'M', u'喝'), - (0xFA37, 'M', u'嘆'), - (0xFA38, 'M', u'器'), - (0xFA39, 'M', u'塀'), - (0xFA3A, 'M', u'墨'), - (0xFA3B, 'M', u'層'), - (0xFA3C, 'M', u'屮'), - (0xFA3D, 'M', u'悔'), - (0xFA3E, 'M', u'慨'), - (0xFA3F, 'M', u'憎'), - (0xFA40, 'M', u'懲'), - (0xFA41, 'M', u'敏'), - (0xFA42, 'M', u'既'), - (0xFA43, 'M', u'暑'), - (0xFA44, 'M', u'梅'), - (0xFA45, 'M', u'海'), - (0xFA46, 'M', u'渚'), + (0xFA15, 'M', '凞'), + (0xFA16, 'M', '猪'), + (0xFA17, 'M', '益'), + (0xFA18, 'M', '礼'), ] def _seg_42(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0xFA47, 'M', u'漢'), - (0xFA48, 'M', u'煮'), - (0xFA49, 'M', u'爫'), - (0xFA4A, 'M', u'琢'), - (0xFA4B, 'M', u'碑'), - (0xFA4C, 'M', u'社'), - (0xFA4D, 'M', u'祉'), - (0xFA4E, 'M', u'祈'), - (0xFA4F, 'M', u'祐'), - (0xFA50, 'M', u'祖'), - (0xFA51, 'M', u'祝'), - (0xFA52, 'M', u'禍'), - (0xFA53, 'M', u'禎'), - (0xFA54, 'M', u'穀'), - (0xFA55, 'M', u'突'), - (0xFA56, 'M', u'節'), - (0xFA57, 'M', u'練'), - (0xFA58, 'M', u'縉'), - (0xFA59, 'M', u'繁'), - (0xFA5A, 'M', u'署'), - (0xFA5B, 'M', u'者'), - (0xFA5C, 'M', u'臭'), - (0xFA5D, 'M', u'艹'), - (0xFA5F, 'M', u'著'), - (0xFA60, 'M', u'褐'), - (0xFA61, 'M', u'視'), - (0xFA62, 'M', u'謁'), - (0xFA63, 'M', u'謹'), - (0xFA64, 'M', u'賓'), - (0xFA65, 'M', u'贈'), - (0xFA66, 'M', u'辶'), - (0xFA67, 'M', u'逸'), - (0xFA68, 'M', u'難'), - (0xFA69, 'M', u'響'), - (0xFA6A, 'M', u'頻'), - (0xFA6B, 'M', u'恵'), - (0xFA6C, 'M', u'𤋮'), - (0xFA6D, 'M', u'舘'), + (0xFA19, 'M', '神'), + (0xFA1A, 'M', '祥'), + (0xFA1B, 'M', '福'), + (0xFA1C, 'M', '靖'), + (0xFA1D, 'M', '精'), + (0xFA1E, 'M', '羽'), + (0xFA1F, 'V'), + (0xFA20, 'M', '蘒'), + (0xFA21, 'V'), + (0xFA22, 'M', '諸'), + (0xFA23, 'V'), + (0xFA25, 'M', '逸'), + (0xFA26, 'M', '都'), + (0xFA27, 'V'), + (0xFA2A, 'M', '飯'), + (0xFA2B, 'M', '飼'), + (0xFA2C, 'M', '館'), + (0xFA2D, 'M', '鶴'), + (0xFA2E, 'M', '郞'), + (0xFA2F, 'M', '隷'), + (0xFA30, 'M', '侮'), + (0xFA31, 'M', '僧'), + (0xFA32, 'M', '免'), + (0xFA33, 'M', '勉'), + (0xFA34, 'M', '勤'), + (0xFA35, 'M', '卑'), + (0xFA36, 'M', '喝'), + (0xFA37, 'M', '嘆'), + (0xFA38, 'M', '器'), + (0xFA39, 'M', '塀'), + (0xFA3A, 'M', '墨'), + (0xFA3B, 'M', '層'), + (0xFA3C, 'M', '屮'), + (0xFA3D, 'M', '悔'), + (0xFA3E, 'M', '慨'), + (0xFA3F, 'M', '憎'), + (0xFA40, 'M', '懲'), + (0xFA41, 'M', '敏'), + (0xFA42, 'M', '既'), + (0xFA43, 'M', '暑'), + (0xFA44, 'M', '梅'), + (0xFA45, 'M', '海'), + (0xFA46, 'M', '渚'), + (0xFA47, 'M', '漢'), + (0xFA48, 'M', '煮'), + (0xFA49, 'M', '爫'), + (0xFA4A, 'M', '琢'), + (0xFA4B, 'M', '碑'), + (0xFA4C, 'M', '社'), + (0xFA4D, 'M', '祉'), + (0xFA4E, 'M', '祈'), + (0xFA4F, 'M', '祐'), + (0xFA50, 'M', '祖'), + (0xFA51, 'M', '祝'), + (0xFA52, 'M', '禍'), + (0xFA53, 'M', '禎'), + (0xFA54, 'M', '穀'), + (0xFA55, 'M', '突'), + (0xFA56, 'M', '節'), + (0xFA57, 'M', '練'), + (0xFA58, 'M', '縉'), + (0xFA59, 'M', '繁'), + (0xFA5A, 'M', '署'), + (0xFA5B, 'M', '者'), + (0xFA5C, 'M', '臭'), + (0xFA5D, 'M', '艹'), + (0xFA5F, 'M', '著'), + (0xFA60, 'M', '褐'), + (0xFA61, 'M', '視'), + (0xFA62, 'M', '謁'), + (0xFA63, 'M', '謹'), + (0xFA64, 'M', '賓'), + (0xFA65, 'M', '贈'), + (0xFA66, 'M', '辶'), + (0xFA67, 'M', '逸'), + (0xFA68, 'M', '難'), + (0xFA69, 'M', '響'), + (0xFA6A, 'M', '頻'), + (0xFA6B, 'M', '恵'), + (0xFA6C, 'M', '𤋮'), + (0xFA6D, 'M', '舘'), (0xFA6E, 'X'), - (0xFA70, 'M', u'並'), - (0xFA71, 'M', u'况'), - (0xFA72, 'M', u'全'), - (0xFA73, 'M', u'侀'), - (0xFA74, 'M', u'充'), - (0xFA75, 'M', u'冀'), - (0xFA76, 'M', u'勇'), - (0xFA77, 'M', u'勺'), - (0xFA78, 'M', u'喝'), - (0xFA79, 'M', u'啕'), - (0xFA7A, 'M', u'喙'), - (0xFA7B, 'M', u'嗢'), - (0xFA7C, 'M', u'塚'), - (0xFA7D, 'M', u'墳'), - (0xFA7E, 'M', u'奄'), - (0xFA7F, 'M', u'奔'), - (0xFA80, 'M', u'婢'), - (0xFA81, 'M', u'嬨'), - (0xFA82, 'M', u'廒'), - (0xFA83, 'M', u'廙'), - (0xFA84, 'M', u'彩'), - (0xFA85, 'M', u'徭'), - (0xFA86, 'M', u'惘'), - (0xFA87, 'M', u'慎'), - (0xFA88, 'M', u'愈'), - (0xFA89, 'M', u'憎'), - (0xFA8A, 'M', u'慠'), - (0xFA8B, 'M', u'懲'), - (0xFA8C, 'M', u'戴'), - (0xFA8D, 'M', u'揄'), - (0xFA8E, 'M', u'搜'), - (0xFA8F, 'M', u'摒'), - (0xFA90, 'M', u'敖'), - (0xFA91, 'M', u'晴'), - (0xFA92, 'M', u'朗'), - (0xFA93, 'M', u'望'), - (0xFA94, 'M', u'杖'), - (0xFA95, 'M', u'歹'), - (0xFA96, 'M', u'殺'), - (0xFA97, 'M', u'流'), - (0xFA98, 'M', u'滛'), - (0xFA99, 'M', u'滋'), - (0xFA9A, 'M', u'漢'), - (0xFA9B, 'M', u'瀞'), - (0xFA9C, 'M', u'煮'), - (0xFA9D, 'M', u'瞧'), - (0xFA9E, 'M', u'爵'), - (0xFA9F, 'M', u'犯'), - (0xFAA0, 'M', u'猪'), - (0xFAA1, 'M', u'瑱'), - (0xFAA2, 'M', u'甆'), - (0xFAA3, 'M', u'画'), - (0xFAA4, 'M', u'瘝'), - (0xFAA5, 'M', u'瘟'), - (0xFAA6, 'M', u'益'), - (0xFAA7, 'M', u'盛'), - (0xFAA8, 'M', u'直'), - (0xFAA9, 'M', u'睊'), - (0xFAAA, 'M', u'着'), - (0xFAAB, 'M', u'磌'), - (0xFAAC, 'M', u'窱'), + (0xFA70, 'M', '並'), + (0xFA71, 'M', '况'), + (0xFA72, 'M', '全'), + (0xFA73, 'M', '侀'), + (0xFA74, 'M', '充'), + (0xFA75, 'M', '冀'), + (0xFA76, 'M', '勇'), + (0xFA77, 'M', '勺'), + (0xFA78, 'M', '喝'), + (0xFA79, 'M', '啕'), + (0xFA7A, 'M', '喙'), + (0xFA7B, 'M', '嗢'), + (0xFA7C, 'M', '塚'), + (0xFA7D, 'M', '墳'), + (0xFA7E, 'M', '奄'), + (0xFA7F, 'M', '奔'), + (0xFA80, 'M', '婢'), + (0xFA81, 'M', '嬨'), ] def _seg_43(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0xFAAD, 'M', u'節'), - (0xFAAE, 'M', u'类'), - (0xFAAF, 'M', u'絛'), - (0xFAB0, 'M', u'練'), - (0xFAB1, 'M', u'缾'), - (0xFAB2, 'M', u'者'), - (0xFAB3, 'M', u'荒'), - (0xFAB4, 'M', u'華'), - (0xFAB5, 'M', u'蝹'), - (0xFAB6, 'M', u'襁'), - (0xFAB7, 'M', u'覆'), - (0xFAB8, 'M', u'視'), - (0xFAB9, 'M', u'調'), - (0xFABA, 'M', u'諸'), - (0xFABB, 'M', u'請'), - (0xFABC, 'M', u'謁'), - (0xFABD, 'M', u'諾'), - (0xFABE, 'M', u'諭'), - (0xFABF, 'M', u'謹'), - (0xFAC0, 'M', u'變'), - (0xFAC1, 'M', u'贈'), - (0xFAC2, 'M', u'輸'), - (0xFAC3, 'M', u'遲'), - (0xFAC4, 'M', u'醙'), - (0xFAC5, 'M', u'鉶'), - (0xFAC6, 'M', u'陼'), - (0xFAC7, 'M', u'難'), - (0xFAC8, 'M', u'靖'), - (0xFAC9, 'M', u'韛'), - (0xFACA, 'M', u'響'), - (0xFACB, 'M', u'頋'), - (0xFACC, 'M', u'頻'), - (0xFACD, 'M', u'鬒'), - (0xFACE, 'M', u'龜'), - (0xFACF, 'M', u'𢡊'), - (0xFAD0, 'M', u'𢡄'), - (0xFAD1, 'M', u'𣏕'), - (0xFAD2, 'M', u'㮝'), - (0xFAD3, 'M', u'䀘'), - (0xFAD4, 'M', u'䀹'), - (0xFAD5, 'M', u'𥉉'), - (0xFAD6, 'M', u'𥳐'), - (0xFAD7, 'M', u'𧻓'), - (0xFAD8, 'M', u'齃'), - (0xFAD9, 'M', u'龎'), + (0xFA82, 'M', '廒'), + (0xFA83, 'M', '廙'), + (0xFA84, 'M', '彩'), + (0xFA85, 'M', '徭'), + (0xFA86, 'M', '惘'), + (0xFA87, 'M', '慎'), + (0xFA88, 'M', '愈'), + (0xFA89, 'M', '憎'), + (0xFA8A, 'M', '慠'), + (0xFA8B, 'M', '懲'), + (0xFA8C, 'M', '戴'), + (0xFA8D, 'M', '揄'), + (0xFA8E, 'M', '搜'), + (0xFA8F, 'M', '摒'), + (0xFA90, 'M', '敖'), + (0xFA91, 'M', '晴'), + (0xFA92, 'M', '朗'), + (0xFA93, 'M', '望'), + (0xFA94, 'M', '杖'), + (0xFA95, 'M', '歹'), + (0xFA96, 'M', '殺'), + (0xFA97, 'M', '流'), + (0xFA98, 'M', '滛'), + (0xFA99, 'M', '滋'), + (0xFA9A, 'M', '漢'), + (0xFA9B, 'M', '瀞'), + (0xFA9C, 'M', '煮'), + (0xFA9D, 'M', '瞧'), + (0xFA9E, 'M', '爵'), + (0xFA9F, 'M', '犯'), + (0xFAA0, 'M', '猪'), + (0xFAA1, 'M', '瑱'), + (0xFAA2, 'M', '甆'), + (0xFAA3, 'M', '画'), + (0xFAA4, 'M', '瘝'), + (0xFAA5, 'M', '瘟'), + (0xFAA6, 'M', '益'), + (0xFAA7, 'M', '盛'), + (0xFAA8, 'M', '直'), + (0xFAA9, 'M', '睊'), + (0xFAAA, 'M', '着'), + (0xFAAB, 'M', '磌'), + (0xFAAC, 'M', '窱'), + (0xFAAD, 'M', '節'), + (0xFAAE, 'M', '类'), + (0xFAAF, 'M', '絛'), + (0xFAB0, 'M', '練'), + (0xFAB1, 'M', '缾'), + (0xFAB2, 'M', '者'), + (0xFAB3, 'M', '荒'), + (0xFAB4, 'M', '華'), + (0xFAB5, 'M', '蝹'), + (0xFAB6, 'M', '襁'), + (0xFAB7, 'M', '覆'), + (0xFAB8, 'M', '視'), + (0xFAB9, 'M', '調'), + (0xFABA, 'M', '諸'), + (0xFABB, 'M', '請'), + (0xFABC, 'M', '謁'), + (0xFABD, 'M', '諾'), + (0xFABE, 'M', '諭'), + (0xFABF, 'M', '謹'), + (0xFAC0, 'M', '變'), + (0xFAC1, 'M', '贈'), + (0xFAC2, 'M', '輸'), + (0xFAC3, 'M', '遲'), + (0xFAC4, 'M', '醙'), + (0xFAC5, 'M', '鉶'), + (0xFAC6, 'M', '陼'), + (0xFAC7, 'M', '難'), + (0xFAC8, 'M', '靖'), + (0xFAC9, 'M', '韛'), + (0xFACA, 'M', '響'), + (0xFACB, 'M', '頋'), + (0xFACC, 'M', '頻'), + (0xFACD, 'M', '鬒'), + (0xFACE, 'M', '龜'), + (0xFACF, 'M', '𢡊'), + (0xFAD0, 'M', '𢡄'), + (0xFAD1, 'M', '𣏕'), + (0xFAD2, 'M', '㮝'), + (0xFAD3, 'M', '䀘'), + (0xFAD4, 'M', '䀹'), + (0xFAD5, 'M', '𥉉'), + (0xFAD6, 'M', '𥳐'), + (0xFAD7, 'M', '𧻓'), + (0xFAD8, 'M', '齃'), + (0xFAD9, 'M', '龎'), (0xFADA, 'X'), - (0xFB00, 'M', u'ff'), - (0xFB01, 'M', u'fi'), - (0xFB02, 'M', u'fl'), - (0xFB03, 'M', u'ffi'), - (0xFB04, 'M', u'ffl'), - (0xFB05, 'M', u'st'), + (0xFB00, 'M', 'ff'), + (0xFB01, 'M', 'fi'), + (0xFB02, 'M', 'fl'), + (0xFB03, 'M', 'ffi'), + (0xFB04, 'M', 'ffl'), + (0xFB05, 'M', 'st'), (0xFB07, 'X'), - (0xFB13, 'M', u'մն'), - (0xFB14, 'M', u'մե'), - (0xFB15, 'M', u'մի'), - (0xFB16, 'M', u'վն'), - (0xFB17, 'M', u'մխ'), - (0xFB18, 'X'), - (0xFB1D, 'M', u'יִ'), - (0xFB1E, 'V'), - (0xFB1F, 'M', u'ײַ'), - (0xFB20, 'M', u'ע'), - (0xFB21, 'M', u'א'), - (0xFB22, 'M', u'ד'), - (0xFB23, 'M', u'ה'), - (0xFB24, 'M', u'כ'), - (0xFB25, 'M', u'ל'), - (0xFB26, 'M', u'ם'), - (0xFB27, 'M', u'ר'), - (0xFB28, 'M', u'ת'), - (0xFB29, '3', u'+'), - (0xFB2A, 'M', u'שׁ'), - (0xFB2B, 'M', u'שׂ'), - (0xFB2C, 'M', u'שּׁ'), - (0xFB2D, 'M', u'שּׂ'), - (0xFB2E, 'M', u'אַ'), - (0xFB2F, 'M', u'אָ'), - (0xFB30, 'M', u'אּ'), - (0xFB31, 'M', u'בּ'), - (0xFB32, 'M', u'גּ'), - (0xFB33, 'M', u'דּ'), - (0xFB34, 'M', u'הּ'), - (0xFB35, 'M', u'וּ'), - (0xFB36, 'M', u'זּ'), - (0xFB37, 'X'), - (0xFB38, 'M', u'טּ'), - (0xFB39, 'M', u'יּ'), - (0xFB3A, 'M', u'ךּ'), - (0xFB3B, 'M', u'כּ'), - (0xFB3C, 'M', u'לּ'), - (0xFB3D, 'X'), - (0xFB3E, 'M', u'מּ'), - (0xFB3F, 'X'), - (0xFB40, 'M', u'נּ'), - (0xFB41, 'M', u'סּ'), - (0xFB42, 'X'), - (0xFB43, 'M', u'ףּ'), - (0xFB44, 'M', u'פּ'), - (0xFB45, 'X'), + (0xFB13, 'M', 'մն'), + (0xFB14, 'M', 'մե'), + (0xFB15, 'M', 'մի'), + (0xFB16, 'M', 'վն'), ] def _seg_44(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0xFB46, 'M', u'צּ'), - (0xFB47, 'M', u'קּ'), - (0xFB48, 'M', u'רּ'), - (0xFB49, 'M', u'שּ'), - (0xFB4A, 'M', u'תּ'), - (0xFB4B, 'M', u'וֹ'), - (0xFB4C, 'M', u'בֿ'), - (0xFB4D, 'M', u'כֿ'), - (0xFB4E, 'M', u'פֿ'), - (0xFB4F, 'M', u'אל'), - (0xFB50, 'M', u'ٱ'), - (0xFB52, 'M', u'ٻ'), - (0xFB56, 'M', u'پ'), - (0xFB5A, 'M', u'ڀ'), - (0xFB5E, 'M', u'ٺ'), - (0xFB62, 'M', u'ٿ'), - (0xFB66, 'M', u'ٹ'), - (0xFB6A, 'M', u'ڤ'), - (0xFB6E, 'M', u'ڦ'), - (0xFB72, 'M', u'ڄ'), - (0xFB76, 'M', u'ڃ'), - (0xFB7A, 'M', u'چ'), - (0xFB7E, 'M', u'ڇ'), - (0xFB82, 'M', u'ڍ'), - (0xFB84, 'M', u'ڌ'), - (0xFB86, 'M', u'ڎ'), - (0xFB88, 'M', u'ڈ'), - (0xFB8A, 'M', u'ژ'), - (0xFB8C, 'M', u'ڑ'), - (0xFB8E, 'M', u'ک'), - (0xFB92, 'M', u'گ'), - (0xFB96, 'M', u'ڳ'), - (0xFB9A, 'M', u'ڱ'), - (0xFB9E, 'M', u'ں'), - (0xFBA0, 'M', u'ڻ'), - (0xFBA4, 'M', u'ۀ'), - (0xFBA6, 'M', u'ہ'), - (0xFBAA, 'M', u'ھ'), - (0xFBAE, 'M', u'ے'), - (0xFBB0, 'M', u'ۓ'), + (0xFB17, 'M', 'մխ'), + (0xFB18, 'X'), + (0xFB1D, 'M', 'יִ'), + (0xFB1E, 'V'), + (0xFB1F, 'M', 'ײַ'), + (0xFB20, 'M', 'ע'), + (0xFB21, 'M', 'א'), + (0xFB22, 'M', 'ד'), + (0xFB23, 'M', 'ה'), + (0xFB24, 'M', 'כ'), + (0xFB25, 'M', 'ל'), + (0xFB26, 'M', 'ם'), + (0xFB27, 'M', 'ר'), + (0xFB28, 'M', 'ת'), + (0xFB29, '3', '+'), + (0xFB2A, 'M', 'שׁ'), + (0xFB2B, 'M', 'שׂ'), + (0xFB2C, 'M', 'שּׁ'), + (0xFB2D, 'M', 'שּׂ'), + (0xFB2E, 'M', 'אַ'), + (0xFB2F, 'M', 'אָ'), + (0xFB30, 'M', 'אּ'), + (0xFB31, 'M', 'בּ'), + (0xFB32, 'M', 'גּ'), + (0xFB33, 'M', 'דּ'), + (0xFB34, 'M', 'הּ'), + (0xFB35, 'M', 'וּ'), + (0xFB36, 'M', 'זּ'), + (0xFB37, 'X'), + (0xFB38, 'M', 'טּ'), + (0xFB39, 'M', 'יּ'), + (0xFB3A, 'M', 'ךּ'), + (0xFB3B, 'M', 'כּ'), + (0xFB3C, 'M', 'לּ'), + (0xFB3D, 'X'), + (0xFB3E, 'M', 'מּ'), + (0xFB3F, 'X'), + (0xFB40, 'M', 'נּ'), + (0xFB41, 'M', 'סּ'), + (0xFB42, 'X'), + (0xFB43, 'M', 'ףּ'), + (0xFB44, 'M', 'פּ'), + (0xFB45, 'X'), + (0xFB46, 'M', 'צּ'), + (0xFB47, 'M', 'קּ'), + (0xFB48, 'M', 'רּ'), + (0xFB49, 'M', 'שּ'), + (0xFB4A, 'M', 'תּ'), + (0xFB4B, 'M', 'וֹ'), + (0xFB4C, 'M', 'בֿ'), + (0xFB4D, 'M', 'כֿ'), + (0xFB4E, 'M', 'פֿ'), + (0xFB4F, 'M', 'אל'), + (0xFB50, 'M', 'ٱ'), + (0xFB52, 'M', 'ٻ'), + (0xFB56, 'M', 'پ'), + (0xFB5A, 'M', 'ڀ'), + (0xFB5E, 'M', 'ٺ'), + (0xFB62, 'M', 'ٿ'), + (0xFB66, 'M', 'ٹ'), + (0xFB6A, 'M', 'ڤ'), + (0xFB6E, 'M', 'ڦ'), + (0xFB72, 'M', 'ڄ'), + (0xFB76, 'M', 'ڃ'), + (0xFB7A, 'M', 'چ'), + (0xFB7E, 'M', 'ڇ'), + (0xFB82, 'M', 'ڍ'), + (0xFB84, 'M', 'ڌ'), + (0xFB86, 'M', 'ڎ'), + (0xFB88, 'M', 'ڈ'), + (0xFB8A, 'M', 'ژ'), + (0xFB8C, 'M', 'ڑ'), + (0xFB8E, 'M', 'ک'), + (0xFB92, 'M', 'گ'), + (0xFB96, 'M', 'ڳ'), + (0xFB9A, 'M', 'ڱ'), + (0xFB9E, 'M', 'ں'), + (0xFBA0, 'M', 'ڻ'), + (0xFBA4, 'M', 'ۀ'), + (0xFBA6, 'M', 'ہ'), + (0xFBAA, 'M', 'ھ'), + (0xFBAE, 'M', 'ے'), + (0xFBB0, 'M', 'ۓ'), (0xFBB2, 'V'), (0xFBC2, 'X'), - (0xFBD3, 'M', u'ڭ'), - (0xFBD7, 'M', u'ۇ'), - (0xFBD9, 'M', u'ۆ'), - (0xFBDB, 'M', u'ۈ'), - (0xFBDD, 'M', u'ۇٴ'), - (0xFBDE, 'M', u'ۋ'), - (0xFBE0, 'M', u'ۅ'), - (0xFBE2, 'M', u'ۉ'), - (0xFBE4, 'M', u'ې'), - (0xFBE8, 'M', u'ى'), - (0xFBEA, 'M', u'ئا'), - (0xFBEC, 'M', u'ئە'), - (0xFBEE, 'M', u'ئو'), - (0xFBF0, 'M', u'ئۇ'), - (0xFBF2, 'M', u'ئۆ'), - (0xFBF4, 'M', u'ئۈ'), - (0xFBF6, 'M', u'ئې'), - (0xFBF9, 'M', u'ئى'), - (0xFBFC, 'M', u'ی'), - (0xFC00, 'M', u'ئج'), - (0xFC01, 'M', u'ئح'), - (0xFC02, 'M', u'ئم'), - (0xFC03, 'M', u'ئى'), - (0xFC04, 'M', u'ئي'), - (0xFC05, 'M', u'بج'), - (0xFC06, 'M', u'بح'), - (0xFC07, 'M', u'بخ'), - (0xFC08, 'M', u'بم'), - (0xFC09, 'M', u'بى'), - (0xFC0A, 'M', u'بي'), - (0xFC0B, 'M', u'تج'), - (0xFC0C, 'M', u'تح'), - (0xFC0D, 'M', u'تخ'), - (0xFC0E, 'M', u'تم'), - (0xFC0F, 'M', u'تى'), - (0xFC10, 'M', u'تي'), - (0xFC11, 'M', u'ثج'), - (0xFC12, 'M', u'ثم'), - (0xFC13, 'M', u'ثى'), - (0xFC14, 'M', u'ثي'), - (0xFC15, 'M', u'جح'), - (0xFC16, 'M', u'جم'), - (0xFC17, 'M', u'حج'), - (0xFC18, 'M', u'حم'), - (0xFC19, 'M', u'خج'), - (0xFC1A, 'M', u'خح'), - (0xFC1B, 'M', u'خم'), - (0xFC1C, 'M', u'سج'), - (0xFC1D, 'M', u'سح'), - (0xFC1E, 'M', u'سخ'), - (0xFC1F, 'M', u'سم'), - (0xFC20, 'M', u'صح'), - (0xFC21, 'M', u'صم'), - (0xFC22, 'M', u'ضج'), - (0xFC23, 'M', u'ضح'), - (0xFC24, 'M', u'ضخ'), - (0xFC25, 'M', u'ضم'), - (0xFC26, 'M', u'طح'), + (0xFBD3, 'M', 'ڭ'), + (0xFBD7, 'M', 'ۇ'), + (0xFBD9, 'M', 'ۆ'), + (0xFBDB, 'M', 'ۈ'), + (0xFBDD, 'M', 'ۇٴ'), + (0xFBDE, 'M', 'ۋ'), + (0xFBE0, 'M', 'ۅ'), + (0xFBE2, 'M', 'ۉ'), + (0xFBE4, 'M', 'ې'), + (0xFBE8, 'M', 'ى'), + (0xFBEA, 'M', 'ئا'), + (0xFBEC, 'M', 'ئە'), + (0xFBEE, 'M', 'ئو'), + (0xFBF0, 'M', 'ئۇ'), + (0xFBF2, 'M', 'ئۆ'), ] def _seg_45(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0xFC27, 'M', u'طم'), - (0xFC28, 'M', u'ظم'), - (0xFC29, 'M', u'عج'), - (0xFC2A, 'M', u'عم'), - (0xFC2B, 'M', u'غج'), - (0xFC2C, 'M', u'غم'), - (0xFC2D, 'M', u'فج'), - (0xFC2E, 'M', u'فح'), - (0xFC2F, 'M', u'فخ'), - (0xFC30, 'M', u'فم'), - (0xFC31, 'M', u'فى'), - (0xFC32, 'M', u'في'), - (0xFC33, 'M', u'قح'), - (0xFC34, 'M', u'قم'), - (0xFC35, 'M', u'قى'), - (0xFC36, 'M', u'قي'), - (0xFC37, 'M', u'كا'), - (0xFC38, 'M', u'كج'), - (0xFC39, 'M', u'كح'), - (0xFC3A, 'M', u'كخ'), - (0xFC3B, 'M', u'كل'), - (0xFC3C, 'M', u'كم'), - (0xFC3D, 'M', u'كى'), - (0xFC3E, 'M', u'كي'), - (0xFC3F, 'M', u'لج'), - (0xFC40, 'M', u'لح'), - (0xFC41, 'M', u'لخ'), - (0xFC42, 'M', u'لم'), - (0xFC43, 'M', u'لى'), - (0xFC44, 'M', u'لي'), - (0xFC45, 'M', u'مج'), - (0xFC46, 'M', u'مح'), - (0xFC47, 'M', u'مخ'), - (0xFC48, 'M', u'مم'), - (0xFC49, 'M', u'مى'), - (0xFC4A, 'M', u'مي'), - (0xFC4B, 'M', u'نج'), - (0xFC4C, 'M', u'نح'), - (0xFC4D, 'M', u'نخ'), - (0xFC4E, 'M', u'نم'), - (0xFC4F, 'M', u'نى'), - (0xFC50, 'M', u'ني'), - (0xFC51, 'M', u'هج'), - (0xFC52, 'M', u'هم'), - (0xFC53, 'M', u'هى'), - (0xFC54, 'M', u'هي'), - (0xFC55, 'M', u'يج'), - (0xFC56, 'M', u'يح'), - (0xFC57, 'M', u'يخ'), - (0xFC58, 'M', u'يم'), - (0xFC59, 'M', u'يى'), - (0xFC5A, 'M', u'يي'), - (0xFC5B, 'M', u'ذٰ'), - (0xFC5C, 'M', u'رٰ'), - (0xFC5D, 'M', u'ىٰ'), - (0xFC5E, '3', u' ٌّ'), - (0xFC5F, '3', u' ٍّ'), - (0xFC60, '3', u' َّ'), - (0xFC61, '3', u' ُّ'), - (0xFC62, '3', u' ِّ'), - (0xFC63, '3', u' ّٰ'), - (0xFC64, 'M', u'ئر'), - (0xFC65, 'M', u'ئز'), - (0xFC66, 'M', u'ئم'), - (0xFC67, 'M', u'ئن'), - (0xFC68, 'M', u'ئى'), - (0xFC69, 'M', u'ئي'), - (0xFC6A, 'M', u'بر'), - (0xFC6B, 'M', u'بز'), - (0xFC6C, 'M', u'بم'), - (0xFC6D, 'M', u'بن'), - (0xFC6E, 'M', u'بى'), - (0xFC6F, 'M', u'بي'), - (0xFC70, 'M', u'تر'), - (0xFC71, 'M', u'تز'), - (0xFC72, 'M', u'تم'), - (0xFC73, 'M', u'تن'), - (0xFC74, 'M', u'تى'), - (0xFC75, 'M', u'تي'), - (0xFC76, 'M', u'ثر'), - (0xFC77, 'M', u'ثز'), - (0xFC78, 'M', u'ثم'), - (0xFC79, 'M', u'ثن'), - (0xFC7A, 'M', u'ثى'), - (0xFC7B, 'M', u'ثي'), - (0xFC7C, 'M', u'فى'), - (0xFC7D, 'M', u'في'), - (0xFC7E, 'M', u'قى'), - (0xFC7F, 'M', u'قي'), - (0xFC80, 'M', u'كا'), - (0xFC81, 'M', u'كل'), - (0xFC82, 'M', u'كم'), - (0xFC83, 'M', u'كى'), - (0xFC84, 'M', u'كي'), - (0xFC85, 'M', u'لم'), - (0xFC86, 'M', u'لى'), - (0xFC87, 'M', u'لي'), - (0xFC88, 'M', u'ما'), - (0xFC89, 'M', u'مم'), - (0xFC8A, 'M', u'نر'), + (0xFBF4, 'M', 'ئۈ'), + (0xFBF6, 'M', 'ئې'), + (0xFBF9, 'M', 'ئى'), + (0xFBFC, 'M', 'ی'), + (0xFC00, 'M', 'ئج'), + (0xFC01, 'M', 'ئح'), + (0xFC02, 'M', 'ئم'), + (0xFC03, 'M', 'ئى'), + (0xFC04, 'M', 'ئي'), + (0xFC05, 'M', 'بج'), + (0xFC06, 'M', 'بح'), + (0xFC07, 'M', 'بخ'), + (0xFC08, 'M', 'بم'), + (0xFC09, 'M', 'بى'), + (0xFC0A, 'M', 'بي'), + (0xFC0B, 'M', 'تج'), + (0xFC0C, 'M', 'تح'), + (0xFC0D, 'M', 'تخ'), + (0xFC0E, 'M', 'تم'), + (0xFC0F, 'M', 'تى'), + (0xFC10, 'M', 'تي'), + (0xFC11, 'M', 'ثج'), + (0xFC12, 'M', 'ثم'), + (0xFC13, 'M', 'ثى'), + (0xFC14, 'M', 'ثي'), + (0xFC15, 'M', 'جح'), + (0xFC16, 'M', 'جم'), + (0xFC17, 'M', 'حج'), + (0xFC18, 'M', 'حم'), + (0xFC19, 'M', 'خج'), + (0xFC1A, 'M', 'خح'), + (0xFC1B, 'M', 'خم'), + (0xFC1C, 'M', 'سج'), + (0xFC1D, 'M', 'سح'), + (0xFC1E, 'M', 'سخ'), + (0xFC1F, 'M', 'سم'), + (0xFC20, 'M', 'صح'), + (0xFC21, 'M', 'صم'), + (0xFC22, 'M', 'ضج'), + (0xFC23, 'M', 'ضح'), + (0xFC24, 'M', 'ضخ'), + (0xFC25, 'M', 'ضم'), + (0xFC26, 'M', 'طح'), + (0xFC27, 'M', 'طم'), + (0xFC28, 'M', 'ظم'), + (0xFC29, 'M', 'عج'), + (0xFC2A, 'M', 'عم'), + (0xFC2B, 'M', 'غج'), + (0xFC2C, 'M', 'غم'), + (0xFC2D, 'M', 'فج'), + (0xFC2E, 'M', 'فح'), + (0xFC2F, 'M', 'فخ'), + (0xFC30, 'M', 'فم'), + (0xFC31, 'M', 'فى'), + (0xFC32, 'M', 'في'), + (0xFC33, 'M', 'قح'), + (0xFC34, 'M', 'قم'), + (0xFC35, 'M', 'قى'), + (0xFC36, 'M', 'قي'), + (0xFC37, 'M', 'كا'), + (0xFC38, 'M', 'كج'), + (0xFC39, 'M', 'كح'), + (0xFC3A, 'M', 'كخ'), + (0xFC3B, 'M', 'كل'), + (0xFC3C, 'M', 'كم'), + (0xFC3D, 'M', 'كى'), + (0xFC3E, 'M', 'كي'), + (0xFC3F, 'M', 'لج'), + (0xFC40, 'M', 'لح'), + (0xFC41, 'M', 'لخ'), + (0xFC42, 'M', 'لم'), + (0xFC43, 'M', 'لى'), + (0xFC44, 'M', 'لي'), + (0xFC45, 'M', 'مج'), + (0xFC46, 'M', 'مح'), + (0xFC47, 'M', 'مخ'), + (0xFC48, 'M', 'مم'), + (0xFC49, 'M', 'مى'), + (0xFC4A, 'M', 'مي'), + (0xFC4B, 'M', 'نج'), + (0xFC4C, 'M', 'نح'), + (0xFC4D, 'M', 'نخ'), + (0xFC4E, 'M', 'نم'), + (0xFC4F, 'M', 'نى'), + (0xFC50, 'M', 'ني'), + (0xFC51, 'M', 'هج'), + (0xFC52, 'M', 'هم'), + (0xFC53, 'M', 'هى'), + (0xFC54, 'M', 'هي'), + (0xFC55, 'M', 'يج'), + (0xFC56, 'M', 'يح'), + (0xFC57, 'M', 'يخ'), + (0xFC58, 'M', 'يم'), + (0xFC59, 'M', 'يى'), + (0xFC5A, 'M', 'يي'), + (0xFC5B, 'M', 'ذٰ'), + (0xFC5C, 'M', 'رٰ'), + (0xFC5D, 'M', 'ىٰ'), + (0xFC5E, '3', ' ٌّ'), + (0xFC5F, '3', ' ٍّ'), ] def _seg_46(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0xFC8B, 'M', u'نز'), - (0xFC8C, 'M', u'نم'), - (0xFC8D, 'M', u'نن'), - (0xFC8E, 'M', u'نى'), - (0xFC8F, 'M', u'ني'), - (0xFC90, 'M', u'ىٰ'), - (0xFC91, 'M', u'ير'), - (0xFC92, 'M', u'يز'), - (0xFC93, 'M', u'يم'), - (0xFC94, 'M', u'ين'), - (0xFC95, 'M', u'يى'), - (0xFC96, 'M', u'يي'), - (0xFC97, 'M', u'ئج'), - (0xFC98, 'M', u'ئح'), - (0xFC99, 'M', u'ئخ'), - (0xFC9A, 'M', u'ئم'), - (0xFC9B, 'M', u'ئه'), - (0xFC9C, 'M', u'بج'), - (0xFC9D, 'M', u'بح'), - (0xFC9E, 'M', u'بخ'), - (0xFC9F, 'M', u'بم'), - (0xFCA0, 'M', u'به'), - (0xFCA1, 'M', u'تج'), - (0xFCA2, 'M', u'تح'), - (0xFCA3, 'M', u'تخ'), - (0xFCA4, 'M', u'تم'), - (0xFCA5, 'M', u'ته'), - (0xFCA6, 'M', u'ثم'), - (0xFCA7, 'M', u'جح'), - (0xFCA8, 'M', u'جم'), - (0xFCA9, 'M', u'حج'), - (0xFCAA, 'M', u'حم'), - (0xFCAB, 'M', u'خج'), - (0xFCAC, 'M', u'خم'), - (0xFCAD, 'M', u'سج'), - (0xFCAE, 'M', u'سح'), - (0xFCAF, 'M', u'سخ'), - (0xFCB0, 'M', u'سم'), - (0xFCB1, 'M', u'صح'), - (0xFCB2, 'M', u'صخ'), - (0xFCB3, 'M', u'صم'), - (0xFCB4, 'M', u'ضج'), - (0xFCB5, 'M', u'ضح'), - (0xFCB6, 'M', u'ضخ'), - (0xFCB7, 'M', u'ضم'), - (0xFCB8, 'M', u'طح'), - (0xFCB9, 'M', u'ظم'), - (0xFCBA, 'M', u'عج'), - (0xFCBB, 'M', u'عم'), - (0xFCBC, 'M', u'غج'), - (0xFCBD, 'M', u'غم'), - (0xFCBE, 'M', u'فج'), - (0xFCBF, 'M', u'فح'), - (0xFCC0, 'M', u'فخ'), - (0xFCC1, 'M', u'فم'), - (0xFCC2, 'M', u'قح'), - (0xFCC3, 'M', u'قم'), - (0xFCC4, 'M', u'كج'), - (0xFCC5, 'M', u'كح'), - (0xFCC6, 'M', u'كخ'), - (0xFCC7, 'M', u'كل'), - (0xFCC8, 'M', u'كم'), - (0xFCC9, 'M', u'لج'), - (0xFCCA, 'M', u'لح'), - (0xFCCB, 'M', u'لخ'), - (0xFCCC, 'M', u'لم'), - (0xFCCD, 'M', u'له'), - (0xFCCE, 'M', u'مج'), - (0xFCCF, 'M', u'مح'), - (0xFCD0, 'M', u'مخ'), - (0xFCD1, 'M', u'مم'), - (0xFCD2, 'M', u'نج'), - (0xFCD3, 'M', u'نح'), - (0xFCD4, 'M', u'نخ'), - (0xFCD5, 'M', u'نم'), - (0xFCD6, 'M', u'نه'), - (0xFCD7, 'M', u'هج'), - (0xFCD8, 'M', u'هم'), - (0xFCD9, 'M', u'هٰ'), - (0xFCDA, 'M', u'يج'), - (0xFCDB, 'M', u'يح'), - (0xFCDC, 'M', u'يخ'), - (0xFCDD, 'M', u'يم'), - (0xFCDE, 'M', u'يه'), - (0xFCDF, 'M', u'ئم'), - (0xFCE0, 'M', u'ئه'), - (0xFCE1, 'M', u'بم'), - (0xFCE2, 'M', u'به'), - (0xFCE3, 'M', u'تم'), - (0xFCE4, 'M', u'ته'), - (0xFCE5, 'M', u'ثم'), - (0xFCE6, 'M', u'ثه'), - (0xFCE7, 'M', u'سم'), - (0xFCE8, 'M', u'سه'), - (0xFCE9, 'M', u'شم'), - (0xFCEA, 'M', u'شه'), - (0xFCEB, 'M', u'كل'), - (0xFCEC, 'M', u'كم'), - (0xFCED, 'M', u'لم'), - (0xFCEE, 'M', u'نم'), + (0xFC60, '3', ' َّ'), + (0xFC61, '3', ' ُّ'), + (0xFC62, '3', ' ِّ'), + (0xFC63, '3', ' ّٰ'), + (0xFC64, 'M', 'ئر'), + (0xFC65, 'M', 'ئز'), + (0xFC66, 'M', 'ئم'), + (0xFC67, 'M', 'ئن'), + (0xFC68, 'M', 'ئى'), + (0xFC69, 'M', 'ئي'), + (0xFC6A, 'M', 'بر'), + (0xFC6B, 'M', 'بز'), + (0xFC6C, 'M', 'بم'), + (0xFC6D, 'M', 'بن'), + (0xFC6E, 'M', 'بى'), + (0xFC6F, 'M', 'بي'), + (0xFC70, 'M', 'تر'), + (0xFC71, 'M', 'تز'), + (0xFC72, 'M', 'تم'), + (0xFC73, 'M', 'تن'), + (0xFC74, 'M', 'تى'), + (0xFC75, 'M', 'تي'), + (0xFC76, 'M', 'ثر'), + (0xFC77, 'M', 'ثز'), + (0xFC78, 'M', 'ثم'), + (0xFC79, 'M', 'ثن'), + (0xFC7A, 'M', 'ثى'), + (0xFC7B, 'M', 'ثي'), + (0xFC7C, 'M', 'فى'), + (0xFC7D, 'M', 'في'), + (0xFC7E, 'M', 'قى'), + (0xFC7F, 'M', 'قي'), + (0xFC80, 'M', 'كا'), + (0xFC81, 'M', 'كل'), + (0xFC82, 'M', 'كم'), + (0xFC83, 'M', 'كى'), + (0xFC84, 'M', 'كي'), + (0xFC85, 'M', 'لم'), + (0xFC86, 'M', 'لى'), + (0xFC87, 'M', 'لي'), + (0xFC88, 'M', 'ما'), + (0xFC89, 'M', 'مم'), + (0xFC8A, 'M', 'نر'), + (0xFC8B, 'M', 'نز'), + (0xFC8C, 'M', 'نم'), + (0xFC8D, 'M', 'نن'), + (0xFC8E, 'M', 'نى'), + (0xFC8F, 'M', 'ني'), + (0xFC90, 'M', 'ىٰ'), + (0xFC91, 'M', 'ير'), + (0xFC92, 'M', 'يز'), + (0xFC93, 'M', 'يم'), + (0xFC94, 'M', 'ين'), + (0xFC95, 'M', 'يى'), + (0xFC96, 'M', 'يي'), + (0xFC97, 'M', 'ئج'), + (0xFC98, 'M', 'ئح'), + (0xFC99, 'M', 'ئخ'), + (0xFC9A, 'M', 'ئم'), + (0xFC9B, 'M', 'ئه'), + (0xFC9C, 'M', 'بج'), + (0xFC9D, 'M', 'بح'), + (0xFC9E, 'M', 'بخ'), + (0xFC9F, 'M', 'بم'), + (0xFCA0, 'M', 'به'), + (0xFCA1, 'M', 'تج'), + (0xFCA2, 'M', 'تح'), + (0xFCA3, 'M', 'تخ'), + (0xFCA4, 'M', 'تم'), + (0xFCA5, 'M', 'ته'), + (0xFCA6, 'M', 'ثم'), + (0xFCA7, 'M', 'جح'), + (0xFCA8, 'M', 'جم'), + (0xFCA9, 'M', 'حج'), + (0xFCAA, 'M', 'حم'), + (0xFCAB, 'M', 'خج'), + (0xFCAC, 'M', 'خم'), + (0xFCAD, 'M', 'سج'), + (0xFCAE, 'M', 'سح'), + (0xFCAF, 'M', 'سخ'), + (0xFCB0, 'M', 'سم'), + (0xFCB1, 'M', 'صح'), + (0xFCB2, 'M', 'صخ'), + (0xFCB3, 'M', 'صم'), + (0xFCB4, 'M', 'ضج'), + (0xFCB5, 'M', 'ضح'), + (0xFCB6, 'M', 'ضخ'), + (0xFCB7, 'M', 'ضم'), + (0xFCB8, 'M', 'طح'), + (0xFCB9, 'M', 'ظم'), + (0xFCBA, 'M', 'عج'), + (0xFCBB, 'M', 'عم'), + (0xFCBC, 'M', 'غج'), + (0xFCBD, 'M', 'غم'), + (0xFCBE, 'M', 'فج'), + (0xFCBF, 'M', 'فح'), + (0xFCC0, 'M', 'فخ'), + (0xFCC1, 'M', 'فم'), + (0xFCC2, 'M', 'قح'), + (0xFCC3, 'M', 'قم'), ] def _seg_47(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0xFCEF, 'M', u'نه'), - (0xFCF0, 'M', u'يم'), - (0xFCF1, 'M', u'يه'), - (0xFCF2, 'M', u'ـَّ'), - (0xFCF3, 'M', u'ـُّ'), - (0xFCF4, 'M', u'ـِّ'), - (0xFCF5, 'M', u'طى'), - (0xFCF6, 'M', u'طي'), - (0xFCF7, 'M', u'عى'), - (0xFCF8, 'M', u'عي'), - (0xFCF9, 'M', u'غى'), - (0xFCFA, 'M', u'غي'), - (0xFCFB, 'M', u'سى'), - (0xFCFC, 'M', u'سي'), - (0xFCFD, 'M', u'شى'), - (0xFCFE, 'M', u'شي'), - (0xFCFF, 'M', u'حى'), - (0xFD00, 'M', u'حي'), - (0xFD01, 'M', u'جى'), - (0xFD02, 'M', u'جي'), - (0xFD03, 'M', u'خى'), - (0xFD04, 'M', u'خي'), - (0xFD05, 'M', u'صى'), - (0xFD06, 'M', u'صي'), - (0xFD07, 'M', u'ضى'), - (0xFD08, 'M', u'ضي'), - (0xFD09, 'M', u'شج'), - (0xFD0A, 'M', u'شح'), - (0xFD0B, 'M', u'شخ'), - (0xFD0C, 'M', u'شم'), - (0xFD0D, 'M', u'شر'), - (0xFD0E, 'M', u'سر'), - (0xFD0F, 'M', u'صر'), - (0xFD10, 'M', u'ضر'), - (0xFD11, 'M', u'طى'), - (0xFD12, 'M', u'طي'), - (0xFD13, 'M', u'عى'), - (0xFD14, 'M', u'عي'), - (0xFD15, 'M', u'غى'), - (0xFD16, 'M', u'غي'), - (0xFD17, 'M', u'سى'), - (0xFD18, 'M', u'سي'), - (0xFD19, 'M', u'شى'), - (0xFD1A, 'M', u'شي'), - (0xFD1B, 'M', u'حى'), - (0xFD1C, 'M', u'حي'), - (0xFD1D, 'M', u'جى'), - (0xFD1E, 'M', u'جي'), - (0xFD1F, 'M', u'خى'), - (0xFD20, 'M', u'خي'), - (0xFD21, 'M', u'صى'), - (0xFD22, 'M', u'صي'), - (0xFD23, 'M', u'ضى'), - (0xFD24, 'M', u'ضي'), - (0xFD25, 'M', u'شج'), - (0xFD26, 'M', u'شح'), - (0xFD27, 'M', u'شخ'), - (0xFD28, 'M', u'شم'), - (0xFD29, 'M', u'شر'), - (0xFD2A, 'M', u'سر'), - (0xFD2B, 'M', u'صر'), - (0xFD2C, 'M', u'ضر'), - (0xFD2D, 'M', u'شج'), - (0xFD2E, 'M', u'شح'), - (0xFD2F, 'M', u'شخ'), - (0xFD30, 'M', u'شم'), - (0xFD31, 'M', u'سه'), - (0xFD32, 'M', u'شه'), - (0xFD33, 'M', u'طم'), - (0xFD34, 'M', u'سج'), - (0xFD35, 'M', u'سح'), - (0xFD36, 'M', u'سخ'), - (0xFD37, 'M', u'شج'), - (0xFD38, 'M', u'شح'), - (0xFD39, 'M', u'شخ'), - (0xFD3A, 'M', u'طم'), - (0xFD3B, 'M', u'ظم'), - (0xFD3C, 'M', u'اً'), - (0xFD3E, 'V'), - (0xFD40, 'X'), - (0xFD50, 'M', u'تجم'), - (0xFD51, 'M', u'تحج'), - (0xFD53, 'M', u'تحم'), - (0xFD54, 'M', u'تخم'), - (0xFD55, 'M', u'تمج'), - (0xFD56, 'M', u'تمح'), - (0xFD57, 'M', u'تمخ'), - (0xFD58, 'M', u'جمح'), - (0xFD5A, 'M', u'حمي'), - (0xFD5B, 'M', u'حمى'), - (0xFD5C, 'M', u'سحج'), - (0xFD5D, 'M', u'سجح'), - (0xFD5E, 'M', u'سجى'), - (0xFD5F, 'M', u'سمح'), - (0xFD61, 'M', u'سمج'), - (0xFD62, 'M', u'سمم'), - (0xFD64, 'M', u'صحح'), - (0xFD66, 'M', u'صمم'), - (0xFD67, 'M', u'شحم'), - (0xFD69, 'M', u'شجي'), + (0xFCC4, 'M', 'كج'), + (0xFCC5, 'M', 'كح'), + (0xFCC6, 'M', 'كخ'), + (0xFCC7, 'M', 'كل'), + (0xFCC8, 'M', 'كم'), + (0xFCC9, 'M', 'لج'), + (0xFCCA, 'M', 'لح'), + (0xFCCB, 'M', 'لخ'), + (0xFCCC, 'M', 'لم'), + (0xFCCD, 'M', 'له'), + (0xFCCE, 'M', 'مج'), + (0xFCCF, 'M', 'مح'), + (0xFCD0, 'M', 'مخ'), + (0xFCD1, 'M', 'مم'), + (0xFCD2, 'M', 'نج'), + (0xFCD3, 'M', 'نح'), + (0xFCD4, 'M', 'نخ'), + (0xFCD5, 'M', 'نم'), + (0xFCD6, 'M', 'نه'), + (0xFCD7, 'M', 'هج'), + (0xFCD8, 'M', 'هم'), + (0xFCD9, 'M', 'هٰ'), + (0xFCDA, 'M', 'يج'), + (0xFCDB, 'M', 'يح'), + (0xFCDC, 'M', 'يخ'), + (0xFCDD, 'M', 'يم'), + (0xFCDE, 'M', 'يه'), + (0xFCDF, 'M', 'ئم'), + (0xFCE0, 'M', 'ئه'), + (0xFCE1, 'M', 'بم'), + (0xFCE2, 'M', 'به'), + (0xFCE3, 'M', 'تم'), + (0xFCE4, 'M', 'ته'), + (0xFCE5, 'M', 'ثم'), + (0xFCE6, 'M', 'ثه'), + (0xFCE7, 'M', 'سم'), + (0xFCE8, 'M', 'سه'), + (0xFCE9, 'M', 'شم'), + (0xFCEA, 'M', 'شه'), + (0xFCEB, 'M', 'كل'), + (0xFCEC, 'M', 'كم'), + (0xFCED, 'M', 'لم'), + (0xFCEE, 'M', 'نم'), + (0xFCEF, 'M', 'نه'), + (0xFCF0, 'M', 'يم'), + (0xFCF1, 'M', 'يه'), + (0xFCF2, 'M', 'ـَّ'), + (0xFCF3, 'M', 'ـُّ'), + (0xFCF4, 'M', 'ـِّ'), + (0xFCF5, 'M', 'طى'), + (0xFCF6, 'M', 'طي'), + (0xFCF7, 'M', 'عى'), + (0xFCF8, 'M', 'عي'), + (0xFCF9, 'M', 'غى'), + (0xFCFA, 'M', 'غي'), + (0xFCFB, 'M', 'سى'), + (0xFCFC, 'M', 'سي'), + (0xFCFD, 'M', 'شى'), + (0xFCFE, 'M', 'شي'), + (0xFCFF, 'M', 'حى'), + (0xFD00, 'M', 'حي'), + (0xFD01, 'M', 'جى'), + (0xFD02, 'M', 'جي'), + (0xFD03, 'M', 'خى'), + (0xFD04, 'M', 'خي'), + (0xFD05, 'M', 'صى'), + (0xFD06, 'M', 'صي'), + (0xFD07, 'M', 'ضى'), + (0xFD08, 'M', 'ضي'), + (0xFD09, 'M', 'شج'), + (0xFD0A, 'M', 'شح'), + (0xFD0B, 'M', 'شخ'), + (0xFD0C, 'M', 'شم'), + (0xFD0D, 'M', 'شر'), + (0xFD0E, 'M', 'سر'), + (0xFD0F, 'M', 'صر'), + (0xFD10, 'M', 'ضر'), + (0xFD11, 'M', 'طى'), + (0xFD12, 'M', 'طي'), + (0xFD13, 'M', 'عى'), + (0xFD14, 'M', 'عي'), + (0xFD15, 'M', 'غى'), + (0xFD16, 'M', 'غي'), + (0xFD17, 'M', 'سى'), + (0xFD18, 'M', 'سي'), + (0xFD19, 'M', 'شى'), + (0xFD1A, 'M', 'شي'), + (0xFD1B, 'M', 'حى'), + (0xFD1C, 'M', 'حي'), + (0xFD1D, 'M', 'جى'), + (0xFD1E, 'M', 'جي'), + (0xFD1F, 'M', 'خى'), + (0xFD20, 'M', 'خي'), + (0xFD21, 'M', 'صى'), + (0xFD22, 'M', 'صي'), + (0xFD23, 'M', 'ضى'), + (0xFD24, 'M', 'ضي'), + (0xFD25, 'M', 'شج'), + (0xFD26, 'M', 'شح'), + (0xFD27, 'M', 'شخ'), ] def _seg_48(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0xFD6A, 'M', u'شمخ'), - (0xFD6C, 'M', u'شمم'), - (0xFD6E, 'M', u'ضحى'), - (0xFD6F, 'M', u'ضخم'), - (0xFD71, 'M', u'طمح'), - (0xFD73, 'M', u'طمم'), - (0xFD74, 'M', u'طمي'), - (0xFD75, 'M', u'عجم'), - (0xFD76, 'M', u'عمم'), - (0xFD78, 'M', u'عمى'), - (0xFD79, 'M', u'غمم'), - (0xFD7A, 'M', u'غمي'), - (0xFD7B, 'M', u'غمى'), - (0xFD7C, 'M', u'فخم'), - (0xFD7E, 'M', u'قمح'), - (0xFD7F, 'M', u'قمم'), - (0xFD80, 'M', u'لحم'), - (0xFD81, 'M', u'لحي'), - (0xFD82, 'M', u'لحى'), - (0xFD83, 'M', u'لجج'), - (0xFD85, 'M', u'لخم'), - (0xFD87, 'M', u'لمح'), - (0xFD89, 'M', u'محج'), - (0xFD8A, 'M', u'محم'), - (0xFD8B, 'M', u'محي'), - (0xFD8C, 'M', u'مجح'), - (0xFD8D, 'M', u'مجم'), - (0xFD8E, 'M', u'مخج'), - (0xFD8F, 'M', u'مخم'), + (0xFD28, 'M', 'شم'), + (0xFD29, 'M', 'شر'), + (0xFD2A, 'M', 'سر'), + (0xFD2B, 'M', 'صر'), + (0xFD2C, 'M', 'ضر'), + (0xFD2D, 'M', 'شج'), + (0xFD2E, 'M', 'شح'), + (0xFD2F, 'M', 'شخ'), + (0xFD30, 'M', 'شم'), + (0xFD31, 'M', 'سه'), + (0xFD32, 'M', 'شه'), + (0xFD33, 'M', 'طم'), + (0xFD34, 'M', 'سج'), + (0xFD35, 'M', 'سح'), + (0xFD36, 'M', 'سخ'), + (0xFD37, 'M', 'شج'), + (0xFD38, 'M', 'شح'), + (0xFD39, 'M', 'شخ'), + (0xFD3A, 'M', 'طم'), + (0xFD3B, 'M', 'ظم'), + (0xFD3C, 'M', 'اً'), + (0xFD3E, 'V'), + (0xFD40, 'X'), + (0xFD50, 'M', 'تجم'), + (0xFD51, 'M', 'تحج'), + (0xFD53, 'M', 'تحم'), + (0xFD54, 'M', 'تخم'), + (0xFD55, 'M', 'تمج'), + (0xFD56, 'M', 'تمح'), + (0xFD57, 'M', 'تمخ'), + (0xFD58, 'M', 'جمح'), + (0xFD5A, 'M', 'حمي'), + (0xFD5B, 'M', 'حمى'), + (0xFD5C, 'M', 'سحج'), + (0xFD5D, 'M', 'سجح'), + (0xFD5E, 'M', 'سجى'), + (0xFD5F, 'M', 'سمح'), + (0xFD61, 'M', 'سمج'), + (0xFD62, 'M', 'سمم'), + (0xFD64, 'M', 'صحح'), + (0xFD66, 'M', 'صمم'), + (0xFD67, 'M', 'شحم'), + (0xFD69, 'M', 'شجي'), + (0xFD6A, 'M', 'شمخ'), + (0xFD6C, 'M', 'شمم'), + (0xFD6E, 'M', 'ضحى'), + (0xFD6F, 'M', 'ضخم'), + (0xFD71, 'M', 'طمح'), + (0xFD73, 'M', 'طمم'), + (0xFD74, 'M', 'طمي'), + (0xFD75, 'M', 'عجم'), + (0xFD76, 'M', 'عمم'), + (0xFD78, 'M', 'عمى'), + (0xFD79, 'M', 'غمم'), + (0xFD7A, 'M', 'غمي'), + (0xFD7B, 'M', 'غمى'), + (0xFD7C, 'M', 'فخم'), + (0xFD7E, 'M', 'قمح'), + (0xFD7F, 'M', 'قمم'), + (0xFD80, 'M', 'لحم'), + (0xFD81, 'M', 'لحي'), + (0xFD82, 'M', 'لحى'), + (0xFD83, 'M', 'لجج'), + (0xFD85, 'M', 'لخم'), + (0xFD87, 'M', 'لمح'), + (0xFD89, 'M', 'محج'), + (0xFD8A, 'M', 'محم'), + (0xFD8B, 'M', 'محي'), + (0xFD8C, 'M', 'مجح'), + (0xFD8D, 'M', 'مجم'), + (0xFD8E, 'M', 'مخج'), + (0xFD8F, 'M', 'مخم'), (0xFD90, 'X'), - (0xFD92, 'M', u'مجخ'), - (0xFD93, 'M', u'همج'), - (0xFD94, 'M', u'همم'), - (0xFD95, 'M', u'نحم'), - (0xFD96, 'M', u'نحى'), - (0xFD97, 'M', u'نجم'), - (0xFD99, 'M', u'نجى'), - (0xFD9A, 'M', u'نمي'), - (0xFD9B, 'M', u'نمى'), - (0xFD9C, 'M', u'يمم'), - (0xFD9E, 'M', u'بخي'), - (0xFD9F, 'M', u'تجي'), - (0xFDA0, 'M', u'تجى'), - (0xFDA1, 'M', u'تخي'), - (0xFDA2, 'M', u'تخى'), - (0xFDA3, 'M', u'تمي'), - (0xFDA4, 'M', u'تمى'), - (0xFDA5, 'M', u'جمي'), - (0xFDA6, 'M', u'جحى'), - (0xFDA7, 'M', u'جمى'), - (0xFDA8, 'M', u'سخى'), - (0xFDA9, 'M', u'صحي'), - (0xFDAA, 'M', u'شحي'), - (0xFDAB, 'M', u'ضحي'), - (0xFDAC, 'M', u'لجي'), - (0xFDAD, 'M', u'لمي'), - (0xFDAE, 'M', u'يحي'), - (0xFDAF, 'M', u'يجي'), - (0xFDB0, 'M', u'يمي'), - (0xFDB1, 'M', u'ممي'), - (0xFDB2, 'M', u'قمي'), - (0xFDB3, 'M', u'نحي'), - (0xFDB4, 'M', u'قمح'), - (0xFDB5, 'M', u'لحم'), - (0xFDB6, 'M', u'عمي'), - (0xFDB7, 'M', u'كمي'), - (0xFDB8, 'M', u'نجح'), - (0xFDB9, 'M', u'مخي'), - (0xFDBA, 'M', u'لجم'), - (0xFDBB, 'M', u'كمم'), - (0xFDBC, 'M', u'لجم'), - (0xFDBD, 'M', u'نجح'), - (0xFDBE, 'M', u'جحي'), - (0xFDBF, 'M', u'حجي'), - (0xFDC0, 'M', u'مجي'), - (0xFDC1, 'M', u'فمي'), - (0xFDC2, 'M', u'بحي'), - (0xFDC3, 'M', u'كمم'), - (0xFDC4, 'M', u'عجم'), - (0xFDC5, 'M', u'صمم'), - (0xFDC6, 'M', u'سخي'), - (0xFDC7, 'M', u'نجي'), - (0xFDC8, 'X'), - (0xFDF0, 'M', u'صلے'), - (0xFDF1, 'M', u'قلے'), - (0xFDF2, 'M', u'الله'), - (0xFDF3, 'M', u'اكبر'), - (0xFDF4, 'M', u'محمد'), - (0xFDF5, 'M', u'صلعم'), - (0xFDF6, 'M', u'رسول'), - (0xFDF7, 'M', u'عليه'), - (0xFDF8, 'M', u'وسلم'), - (0xFDF9, 'M', u'صلى'), - (0xFDFA, '3', u'صلى الله عليه وسلم'), - (0xFDFB, '3', u'جل جلاله'), - (0xFDFC, 'M', u'ریال'), - (0xFDFD, 'V'), - (0xFDFE, 'X'), - (0xFE00, 'I'), - (0xFE10, '3', u','), + (0xFD92, 'M', 'مجخ'), + (0xFD93, 'M', 'همج'), + (0xFD94, 'M', 'همم'), + (0xFD95, 'M', 'نحم'), + (0xFD96, 'M', 'نحى'), + (0xFD97, 'M', 'نجم'), + (0xFD99, 'M', 'نجى'), + (0xFD9A, 'M', 'نمي'), + (0xFD9B, 'M', 'نمى'), + (0xFD9C, 'M', 'يمم'), + (0xFD9E, 'M', 'بخي'), + (0xFD9F, 'M', 'تجي'), + (0xFDA0, 'M', 'تجى'), + (0xFDA1, 'M', 'تخي'), + (0xFDA2, 'M', 'تخى'), + (0xFDA3, 'M', 'تمي'), + (0xFDA4, 'M', 'تمى'), + (0xFDA5, 'M', 'جمي'), + (0xFDA6, 'M', 'جحى'), + (0xFDA7, 'M', 'جمى'), + (0xFDA8, 'M', 'سخى'), + (0xFDA9, 'M', 'صحي'), + (0xFDAA, 'M', 'شحي'), + (0xFDAB, 'M', 'ضحي'), + (0xFDAC, 'M', 'لجي'), + (0xFDAD, 'M', 'لمي'), + (0xFDAE, 'M', 'يحي'), ] def _seg_49(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0xFE11, 'M', u'、'), + (0xFDAF, 'M', 'يجي'), + (0xFDB0, 'M', 'يمي'), + (0xFDB1, 'M', 'ممي'), + (0xFDB2, 'M', 'قمي'), + (0xFDB3, 'M', 'نحي'), + (0xFDB4, 'M', 'قمح'), + (0xFDB5, 'M', 'لحم'), + (0xFDB6, 'M', 'عمي'), + (0xFDB7, 'M', 'كمي'), + (0xFDB8, 'M', 'نجح'), + (0xFDB9, 'M', 'مخي'), + (0xFDBA, 'M', 'لجم'), + (0xFDBB, 'M', 'كمم'), + (0xFDBC, 'M', 'لجم'), + (0xFDBD, 'M', 'نجح'), + (0xFDBE, 'M', 'جحي'), + (0xFDBF, 'M', 'حجي'), + (0xFDC0, 'M', 'مجي'), + (0xFDC1, 'M', 'فمي'), + (0xFDC2, 'M', 'بحي'), + (0xFDC3, 'M', 'كمم'), + (0xFDC4, 'M', 'عجم'), + (0xFDC5, 'M', 'صمم'), + (0xFDC6, 'M', 'سخي'), + (0xFDC7, 'M', 'نجي'), + (0xFDC8, 'X'), + (0xFDF0, 'M', 'صلے'), + (0xFDF1, 'M', 'قلے'), + (0xFDF2, 'M', 'الله'), + (0xFDF3, 'M', 'اكبر'), + (0xFDF4, 'M', 'محمد'), + (0xFDF5, 'M', 'صلعم'), + (0xFDF6, 'M', 'رسول'), + (0xFDF7, 'M', 'عليه'), + (0xFDF8, 'M', 'وسلم'), + (0xFDF9, 'M', 'صلى'), + (0xFDFA, '3', 'صلى الله عليه وسلم'), + (0xFDFB, '3', 'جل جلاله'), + (0xFDFC, 'M', 'ریال'), + (0xFDFD, 'V'), + (0xFDFE, 'X'), + (0xFE00, 'I'), + (0xFE10, '3', ','), + (0xFE11, 'M', '、'), (0xFE12, 'X'), - (0xFE13, '3', u':'), - (0xFE14, '3', u';'), - (0xFE15, '3', u'!'), - (0xFE16, '3', u'?'), - (0xFE17, 'M', u'〖'), - (0xFE18, 'M', u'〗'), + (0xFE13, '3', ':'), + (0xFE14, '3', ';'), + (0xFE15, '3', '!'), + (0xFE16, '3', '?'), + (0xFE17, 'M', '〖'), + (0xFE18, 'M', '〗'), (0xFE19, 'X'), (0xFE20, 'V'), (0xFE30, 'X'), - (0xFE31, 'M', u'—'), - (0xFE32, 'M', u'–'), - (0xFE33, '3', u'_'), - (0xFE35, '3', u'('), - (0xFE36, '3', u')'), - (0xFE37, '3', u'{'), - (0xFE38, '3', u'}'), - (0xFE39, 'M', u'〔'), - (0xFE3A, 'M', u'〕'), - (0xFE3B, 'M', u'【'), - (0xFE3C, 'M', u'】'), - (0xFE3D, 'M', u'《'), - (0xFE3E, 'M', u'》'), - (0xFE3F, 'M', u'〈'), - (0xFE40, 'M', u'〉'), - (0xFE41, 'M', u'「'), - (0xFE42, 'M', u'」'), - (0xFE43, 'M', u'『'), - (0xFE44, 'M', u'』'), + (0xFE31, 'M', '—'), + (0xFE32, 'M', '–'), + (0xFE33, '3', '_'), + (0xFE35, '3', '('), + (0xFE36, '3', ')'), + (0xFE37, '3', '{'), + (0xFE38, '3', '}'), + (0xFE39, 'M', '〔'), + (0xFE3A, 'M', '〕'), + (0xFE3B, 'M', '【'), + (0xFE3C, 'M', '】'), + (0xFE3D, 'M', '《'), + (0xFE3E, 'M', '》'), + (0xFE3F, 'M', '〈'), + (0xFE40, 'M', '〉'), + (0xFE41, 'M', '「'), + (0xFE42, 'M', '」'), + (0xFE43, 'M', '『'), + (0xFE44, 'M', '』'), (0xFE45, 'V'), - (0xFE47, '3', u'['), - (0xFE48, '3', u']'), - (0xFE49, '3', u' ̅'), - (0xFE4D, '3', u'_'), - (0xFE50, '3', u','), - (0xFE51, 'M', u'、'), + (0xFE47, '3', '['), + (0xFE48, '3', ']'), + (0xFE49, '3', ' ̅'), + (0xFE4D, '3', '_'), + (0xFE50, '3', ','), + (0xFE51, 'M', '、'), (0xFE52, 'X'), - (0xFE54, '3', u';'), - (0xFE55, '3', u':'), - (0xFE56, '3', u'?'), - (0xFE57, '3', u'!'), - (0xFE58, 'M', u'—'), - (0xFE59, '3', u'('), - (0xFE5A, '3', u')'), - (0xFE5B, '3', u'{'), - (0xFE5C, '3', u'}'), - (0xFE5D, 'M', u'〔'), - (0xFE5E, 'M', u'〕'), - (0xFE5F, '3', u'#'), - (0xFE60, '3', u'&'), - (0xFE61, '3', u'*'), - (0xFE62, '3', u'+'), - (0xFE63, 'M', u'-'), - (0xFE64, '3', u'<'), - (0xFE65, '3', u'>'), - (0xFE66, '3', u'='), - (0xFE67, 'X'), - (0xFE68, '3', u'\\'), - (0xFE69, '3', u'$'), - (0xFE6A, '3', u'%'), - (0xFE6B, '3', u'@'), - (0xFE6C, 'X'), - (0xFE70, '3', u' ً'), - (0xFE71, 'M', u'ـً'), - (0xFE72, '3', u' ٌ'), - (0xFE73, 'V'), - (0xFE74, '3', u' ٍ'), - (0xFE75, 'X'), - (0xFE76, '3', u' َ'), - (0xFE77, 'M', u'ـَ'), - (0xFE78, '3', u' ُ'), - (0xFE79, 'M', u'ـُ'), - (0xFE7A, '3', u' ِ'), - (0xFE7B, 'M', u'ـِ'), - (0xFE7C, '3', u' ّ'), - (0xFE7D, 'M', u'ـّ'), - (0xFE7E, '3', u' ْ'), - (0xFE7F, 'M', u'ـْ'), - (0xFE80, 'M', u'ء'), - (0xFE81, 'M', u'آ'), - (0xFE83, 'M', u'أ'), - (0xFE85, 'M', u'ؤ'), - (0xFE87, 'M', u'إ'), - (0xFE89, 'M', u'ئ'), - (0xFE8D, 'M', u'ا'), - (0xFE8F, 'M', u'ب'), - (0xFE93, 'M', u'ة'), - (0xFE95, 'M', u'ت'), - (0xFE99, 'M', u'ث'), - (0xFE9D, 'M', u'ج'), - (0xFEA1, 'M', u'ح'), - (0xFEA5, 'M', u'خ'), - (0xFEA9, 'M', u'د'), - (0xFEAB, 'M', u'ذ'), - (0xFEAD, 'M', u'ر'), - (0xFEAF, 'M', u'ز'), - (0xFEB1, 'M', u'س'), - (0xFEB5, 'M', u'ش'), - (0xFEB9, 'M', u'ص'), + (0xFE54, '3', ';'), + (0xFE55, '3', ':'), + (0xFE56, '3', '?'), + (0xFE57, '3', '!'), + (0xFE58, 'M', '—'), + (0xFE59, '3', '('), + (0xFE5A, '3', ')'), + (0xFE5B, '3', '{'), + (0xFE5C, '3', '}'), + (0xFE5D, 'M', '〔'), + (0xFE5E, 'M', '〕'), + (0xFE5F, '3', '#'), + (0xFE60, '3', '&'), + (0xFE61, '3', '*'), + (0xFE62, '3', '+'), + (0xFE63, 'M', '-'), + (0xFE64, '3', '<'), + (0xFE65, '3', '>'), + (0xFE66, '3', '='), ] def _seg_50(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0xFEBD, 'M', u'ض'), - (0xFEC1, 'M', u'ط'), - (0xFEC5, 'M', u'ظ'), - (0xFEC9, 'M', u'ع'), - (0xFECD, 'M', u'غ'), - (0xFED1, 'M', u'ف'), - (0xFED5, 'M', u'ق'), - (0xFED9, 'M', u'ك'), - (0xFEDD, 'M', u'ل'), - (0xFEE1, 'M', u'م'), - (0xFEE5, 'M', u'ن'), - (0xFEE9, 'M', u'ه'), - (0xFEED, 'M', u'و'), - (0xFEEF, 'M', u'ى'), - (0xFEF1, 'M', u'ي'), - (0xFEF5, 'M', u'لآ'), - (0xFEF7, 'M', u'لأ'), - (0xFEF9, 'M', u'لإ'), - (0xFEFB, 'M', u'لا'), + (0xFE67, 'X'), + (0xFE68, '3', '\\'), + (0xFE69, '3', '$'), + (0xFE6A, '3', '%'), + (0xFE6B, '3', '@'), + (0xFE6C, 'X'), + (0xFE70, '3', ' ً'), + (0xFE71, 'M', 'ـً'), + (0xFE72, '3', ' ٌ'), + (0xFE73, 'V'), + (0xFE74, '3', ' ٍ'), + (0xFE75, 'X'), + (0xFE76, '3', ' َ'), + (0xFE77, 'M', 'ـَ'), + (0xFE78, '3', ' ُ'), + (0xFE79, 'M', 'ـُ'), + (0xFE7A, '3', ' ِ'), + (0xFE7B, 'M', 'ـِ'), + (0xFE7C, '3', ' ّ'), + (0xFE7D, 'M', 'ـّ'), + (0xFE7E, '3', ' ْ'), + (0xFE7F, 'M', 'ـْ'), + (0xFE80, 'M', 'ء'), + (0xFE81, 'M', 'آ'), + (0xFE83, 'M', 'أ'), + (0xFE85, 'M', 'ؤ'), + (0xFE87, 'M', 'إ'), + (0xFE89, 'M', 'ئ'), + (0xFE8D, 'M', 'ا'), + (0xFE8F, 'M', 'ب'), + (0xFE93, 'M', 'ة'), + (0xFE95, 'M', 'ت'), + (0xFE99, 'M', 'ث'), + (0xFE9D, 'M', 'ج'), + (0xFEA1, 'M', 'ح'), + (0xFEA5, 'M', 'خ'), + (0xFEA9, 'M', 'د'), + (0xFEAB, 'M', 'ذ'), + (0xFEAD, 'M', 'ر'), + (0xFEAF, 'M', 'ز'), + (0xFEB1, 'M', 'س'), + (0xFEB5, 'M', 'ش'), + (0xFEB9, 'M', 'ص'), + (0xFEBD, 'M', 'ض'), + (0xFEC1, 'M', 'ط'), + (0xFEC5, 'M', 'ظ'), + (0xFEC9, 'M', 'ع'), + (0xFECD, 'M', 'غ'), + (0xFED1, 'M', 'ف'), + (0xFED5, 'M', 'ق'), + (0xFED9, 'M', 'ك'), + (0xFEDD, 'M', 'ل'), + (0xFEE1, 'M', 'م'), + (0xFEE5, 'M', 'ن'), + (0xFEE9, 'M', 'ه'), + (0xFEED, 'M', 'و'), + (0xFEEF, 'M', 'ى'), + (0xFEF1, 'M', 'ي'), + (0xFEF5, 'M', 'لآ'), + (0xFEF7, 'M', 'لأ'), + (0xFEF9, 'M', 'لإ'), + (0xFEFB, 'M', 'لا'), (0xFEFD, 'X'), (0xFEFF, 'I'), (0xFF00, 'X'), - (0xFF01, '3', u'!'), - (0xFF02, '3', u'"'), - (0xFF03, '3', u'#'), - (0xFF04, '3', u'$'), - (0xFF05, '3', u'%'), - (0xFF06, '3', u'&'), - (0xFF07, '3', u'\''), - (0xFF08, '3', u'('), - (0xFF09, '3', u')'), - (0xFF0A, '3', u'*'), - (0xFF0B, '3', u'+'), - (0xFF0C, '3', u','), - (0xFF0D, 'M', u'-'), - (0xFF0E, 'M', u'.'), - (0xFF0F, '3', u'/'), - (0xFF10, 'M', u'0'), - (0xFF11, 'M', u'1'), - (0xFF12, 'M', u'2'), - (0xFF13, 'M', u'3'), - (0xFF14, 'M', u'4'), - (0xFF15, 'M', u'5'), - (0xFF16, 'M', u'6'), - (0xFF17, 'M', u'7'), - (0xFF18, 'M', u'8'), - (0xFF19, 'M', u'9'), - (0xFF1A, '3', u':'), - (0xFF1B, '3', u';'), - (0xFF1C, '3', u'<'), - (0xFF1D, '3', u'='), - (0xFF1E, '3', u'>'), - (0xFF1F, '3', u'?'), - (0xFF20, '3', u'@'), - (0xFF21, 'M', u'a'), - (0xFF22, 'M', u'b'), - (0xFF23, 'M', u'c'), - (0xFF24, 'M', u'd'), - (0xFF25, 'M', u'e'), - (0xFF26, 'M', u'f'), - (0xFF27, 'M', u'g'), - (0xFF28, 'M', u'h'), - (0xFF29, 'M', u'i'), - (0xFF2A, 'M', u'j'), - (0xFF2B, 'M', u'k'), - (0xFF2C, 'M', u'l'), - (0xFF2D, 'M', u'm'), - (0xFF2E, 'M', u'n'), - (0xFF2F, 'M', u'o'), - (0xFF30, 'M', u'p'), - (0xFF31, 'M', u'q'), - (0xFF32, 'M', u'r'), - (0xFF33, 'M', u's'), - (0xFF34, 'M', u't'), - (0xFF35, 'M', u'u'), - (0xFF36, 'M', u'v'), - (0xFF37, 'M', u'w'), - (0xFF38, 'M', u'x'), - (0xFF39, 'M', u'y'), - (0xFF3A, 'M', u'z'), - (0xFF3B, '3', u'['), - (0xFF3C, '3', u'\\'), - (0xFF3D, '3', u']'), - (0xFF3E, '3', u'^'), - (0xFF3F, '3', u'_'), - (0xFF40, '3', u'`'), - (0xFF41, 'M', u'a'), - (0xFF42, 'M', u'b'), - (0xFF43, 'M', u'c'), - (0xFF44, 'M', u'd'), - (0xFF45, 'M', u'e'), - (0xFF46, 'M', u'f'), - (0xFF47, 'M', u'g'), - (0xFF48, 'M', u'h'), - (0xFF49, 'M', u'i'), - (0xFF4A, 'M', u'j'), - (0xFF4B, 'M', u'k'), - (0xFF4C, 'M', u'l'), - (0xFF4D, 'M', u'm'), - (0xFF4E, 'M', u'n'), + (0xFF01, '3', '!'), + (0xFF02, '3', '"'), + (0xFF03, '3', '#'), + (0xFF04, '3', '$'), + (0xFF05, '3', '%'), + (0xFF06, '3', '&'), + (0xFF07, '3', '\''), + (0xFF08, '3', '('), + (0xFF09, '3', ')'), + (0xFF0A, '3', '*'), + (0xFF0B, '3', '+'), + (0xFF0C, '3', ','), + (0xFF0D, 'M', '-'), + (0xFF0E, 'M', '.'), + (0xFF0F, '3', '/'), + (0xFF10, 'M', '0'), + (0xFF11, 'M', '1'), + (0xFF12, 'M', '2'), + (0xFF13, 'M', '3'), + (0xFF14, 'M', '4'), + (0xFF15, 'M', '5'), + (0xFF16, 'M', '6'), + (0xFF17, 'M', '7'), + (0xFF18, 'M', '8'), + (0xFF19, 'M', '9'), + (0xFF1A, '3', ':'), + (0xFF1B, '3', ';'), + (0xFF1C, '3', '<'), + (0xFF1D, '3', '='), + (0xFF1E, '3', '>'), + (0xFF1F, '3', '?'), + (0xFF20, '3', '@'), + (0xFF21, 'M', 'a'), + (0xFF22, 'M', 'b'), + (0xFF23, 'M', 'c'), ] def _seg_51(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0xFF4F, 'M', u'o'), - (0xFF50, 'M', u'p'), - (0xFF51, 'M', u'q'), - (0xFF52, 'M', u'r'), - (0xFF53, 'M', u's'), - (0xFF54, 'M', u't'), - (0xFF55, 'M', u'u'), - (0xFF56, 'M', u'v'), - (0xFF57, 'M', u'w'), - (0xFF58, 'M', u'x'), - (0xFF59, 'M', u'y'), - (0xFF5A, 'M', u'z'), - (0xFF5B, '3', u'{'), - (0xFF5C, '3', u'|'), - (0xFF5D, '3', u'}'), - (0xFF5E, '3', u'~'), - (0xFF5F, 'M', u'⦅'), - (0xFF60, 'M', u'⦆'), - (0xFF61, 'M', u'.'), - (0xFF62, 'M', u'「'), - (0xFF63, 'M', u'」'), - (0xFF64, 'M', u'、'), - (0xFF65, 'M', u'・'), - (0xFF66, 'M', u'ヲ'), - (0xFF67, 'M', u'ァ'), - (0xFF68, 'M', u'ィ'), - (0xFF69, 'M', u'ゥ'), - (0xFF6A, 'M', u'ェ'), - (0xFF6B, 'M', u'ォ'), - (0xFF6C, 'M', u'ャ'), - (0xFF6D, 'M', u'ュ'), - (0xFF6E, 'M', u'ョ'), - (0xFF6F, 'M', u'ッ'), - (0xFF70, 'M', u'ー'), - (0xFF71, 'M', u'ア'), - (0xFF72, 'M', u'イ'), - (0xFF73, 'M', u'ウ'), - (0xFF74, 'M', u'エ'), - (0xFF75, 'M', u'オ'), - (0xFF76, 'M', u'カ'), - (0xFF77, 'M', u'キ'), - (0xFF78, 'M', u'ク'), - (0xFF79, 'M', u'ケ'), - (0xFF7A, 'M', u'コ'), - (0xFF7B, 'M', u'サ'), - (0xFF7C, 'M', u'シ'), - (0xFF7D, 'M', u'ス'), - (0xFF7E, 'M', u'セ'), - (0xFF7F, 'M', u'ソ'), - (0xFF80, 'M', u'タ'), - (0xFF81, 'M', u'チ'), - (0xFF82, 'M', u'ツ'), - (0xFF83, 'M', u'テ'), - (0xFF84, 'M', u'ト'), - (0xFF85, 'M', u'ナ'), - (0xFF86, 'M', u'ニ'), - (0xFF87, 'M', u'ヌ'), - (0xFF88, 'M', u'ネ'), - (0xFF89, 'M', u'ノ'), - (0xFF8A, 'M', u'ハ'), - (0xFF8B, 'M', u'ヒ'), - (0xFF8C, 'M', u'フ'), - (0xFF8D, 'M', u'ヘ'), - (0xFF8E, 'M', u'ホ'), - (0xFF8F, 'M', u'マ'), - (0xFF90, 'M', u'ミ'), - (0xFF91, 'M', u'ム'), - (0xFF92, 'M', u'メ'), - (0xFF93, 'M', u'モ'), - (0xFF94, 'M', u'ヤ'), - (0xFF95, 'M', u'ユ'), - (0xFF96, 'M', u'ヨ'), - (0xFF97, 'M', u'ラ'), - (0xFF98, 'M', u'リ'), - (0xFF99, 'M', u'ル'), - (0xFF9A, 'M', u'レ'), - (0xFF9B, 'M', u'ロ'), - (0xFF9C, 'M', u'ワ'), - (0xFF9D, 'M', u'ン'), - (0xFF9E, 'M', u'゙'), - (0xFF9F, 'M', u'゚'), - (0xFFA0, 'X'), - (0xFFA1, 'M', u'ᄀ'), - (0xFFA2, 'M', u'ᄁ'), - (0xFFA3, 'M', u'ᆪ'), - (0xFFA4, 'M', u'ᄂ'), - (0xFFA5, 'M', u'ᆬ'), - (0xFFA6, 'M', u'ᆭ'), - (0xFFA7, 'M', u'ᄃ'), - (0xFFA8, 'M', u'ᄄ'), - (0xFFA9, 'M', u'ᄅ'), - (0xFFAA, 'M', u'ᆰ'), - (0xFFAB, 'M', u'ᆱ'), - (0xFFAC, 'M', u'ᆲ'), - (0xFFAD, 'M', u'ᆳ'), - (0xFFAE, 'M', u'ᆴ'), - (0xFFAF, 'M', u'ᆵ'), - (0xFFB0, 'M', u'ᄚ'), - (0xFFB1, 'M', u'ᄆ'), - (0xFFB2, 'M', u'ᄇ'), + (0xFF24, 'M', 'd'), + (0xFF25, 'M', 'e'), + (0xFF26, 'M', 'f'), + (0xFF27, 'M', 'g'), + (0xFF28, 'M', 'h'), + (0xFF29, 'M', 'i'), + (0xFF2A, 'M', 'j'), + (0xFF2B, 'M', 'k'), + (0xFF2C, 'M', 'l'), + (0xFF2D, 'M', 'm'), + (0xFF2E, 'M', 'n'), + (0xFF2F, 'M', 'o'), + (0xFF30, 'M', 'p'), + (0xFF31, 'M', 'q'), + (0xFF32, 'M', 'r'), + (0xFF33, 'M', 's'), + (0xFF34, 'M', 't'), + (0xFF35, 'M', 'u'), + (0xFF36, 'M', 'v'), + (0xFF37, 'M', 'w'), + (0xFF38, 'M', 'x'), + (0xFF39, 'M', 'y'), + (0xFF3A, 'M', 'z'), + (0xFF3B, '3', '['), + (0xFF3C, '3', '\\'), + (0xFF3D, '3', ']'), + (0xFF3E, '3', '^'), + (0xFF3F, '3', '_'), + (0xFF40, '3', '`'), + (0xFF41, 'M', 'a'), + (0xFF42, 'M', 'b'), + (0xFF43, 'M', 'c'), + (0xFF44, 'M', 'd'), + (0xFF45, 'M', 'e'), + (0xFF46, 'M', 'f'), + (0xFF47, 'M', 'g'), + (0xFF48, 'M', 'h'), + (0xFF49, 'M', 'i'), + (0xFF4A, 'M', 'j'), + (0xFF4B, 'M', 'k'), + (0xFF4C, 'M', 'l'), + (0xFF4D, 'M', 'm'), + (0xFF4E, 'M', 'n'), + (0xFF4F, 'M', 'o'), + (0xFF50, 'M', 'p'), + (0xFF51, 'M', 'q'), + (0xFF52, 'M', 'r'), + (0xFF53, 'M', 's'), + (0xFF54, 'M', 't'), + (0xFF55, 'M', 'u'), + (0xFF56, 'M', 'v'), + (0xFF57, 'M', 'w'), + (0xFF58, 'M', 'x'), + (0xFF59, 'M', 'y'), + (0xFF5A, 'M', 'z'), + (0xFF5B, '3', '{'), + (0xFF5C, '3', '|'), + (0xFF5D, '3', '}'), + (0xFF5E, '3', '~'), + (0xFF5F, 'M', '⦅'), + (0xFF60, 'M', '⦆'), + (0xFF61, 'M', '.'), + (0xFF62, 'M', '「'), + (0xFF63, 'M', '」'), + (0xFF64, 'M', '、'), + (0xFF65, 'M', '・'), + (0xFF66, 'M', 'ヲ'), + (0xFF67, 'M', 'ァ'), + (0xFF68, 'M', 'ィ'), + (0xFF69, 'M', 'ゥ'), + (0xFF6A, 'M', 'ェ'), + (0xFF6B, 'M', 'ォ'), + (0xFF6C, 'M', 'ャ'), + (0xFF6D, 'M', 'ュ'), + (0xFF6E, 'M', 'ョ'), + (0xFF6F, 'M', 'ッ'), + (0xFF70, 'M', 'ー'), + (0xFF71, 'M', 'ア'), + (0xFF72, 'M', 'イ'), + (0xFF73, 'M', 'ウ'), + (0xFF74, 'M', 'エ'), + (0xFF75, 'M', 'オ'), + (0xFF76, 'M', 'カ'), + (0xFF77, 'M', 'キ'), + (0xFF78, 'M', 'ク'), + (0xFF79, 'M', 'ケ'), + (0xFF7A, 'M', 'コ'), + (0xFF7B, 'M', 'サ'), + (0xFF7C, 'M', 'シ'), + (0xFF7D, 'M', 'ス'), + (0xFF7E, 'M', 'セ'), + (0xFF7F, 'M', 'ソ'), + (0xFF80, 'M', 'タ'), + (0xFF81, 'M', 'チ'), + (0xFF82, 'M', 'ツ'), + (0xFF83, 'M', 'テ'), + (0xFF84, 'M', 'ト'), + (0xFF85, 'M', 'ナ'), + (0xFF86, 'M', 'ニ'), + (0xFF87, 'M', 'ヌ'), ] def _seg_52(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0xFFB3, 'M', u'ᄈ'), - (0xFFB4, 'M', u'ᄡ'), - (0xFFB5, 'M', u'ᄉ'), - (0xFFB6, 'M', u'ᄊ'), - (0xFFB7, 'M', u'ᄋ'), - (0xFFB8, 'M', u'ᄌ'), - (0xFFB9, 'M', u'ᄍ'), - (0xFFBA, 'M', u'ᄎ'), - (0xFFBB, 'M', u'ᄏ'), - (0xFFBC, 'M', u'ᄐ'), - (0xFFBD, 'M', u'ᄑ'), - (0xFFBE, 'M', u'ᄒ'), + (0xFF88, 'M', 'ネ'), + (0xFF89, 'M', 'ノ'), + (0xFF8A, 'M', 'ハ'), + (0xFF8B, 'M', 'ヒ'), + (0xFF8C, 'M', 'フ'), + (0xFF8D, 'M', 'ヘ'), + (0xFF8E, 'M', 'ホ'), + (0xFF8F, 'M', 'マ'), + (0xFF90, 'M', 'ミ'), + (0xFF91, 'M', 'ム'), + (0xFF92, 'M', 'メ'), + (0xFF93, 'M', 'モ'), + (0xFF94, 'M', 'ヤ'), + (0xFF95, 'M', 'ユ'), + (0xFF96, 'M', 'ヨ'), + (0xFF97, 'M', 'ラ'), + (0xFF98, 'M', 'リ'), + (0xFF99, 'M', 'ル'), + (0xFF9A, 'M', 'レ'), + (0xFF9B, 'M', 'ロ'), + (0xFF9C, 'M', 'ワ'), + (0xFF9D, 'M', 'ン'), + (0xFF9E, 'M', '゙'), + (0xFF9F, 'M', '゚'), + (0xFFA0, 'X'), + (0xFFA1, 'M', 'ᄀ'), + (0xFFA2, 'M', 'ᄁ'), + (0xFFA3, 'M', 'ᆪ'), + (0xFFA4, 'M', 'ᄂ'), + (0xFFA5, 'M', 'ᆬ'), + (0xFFA6, 'M', 'ᆭ'), + (0xFFA7, 'M', 'ᄃ'), + (0xFFA8, 'M', 'ᄄ'), + (0xFFA9, 'M', 'ᄅ'), + (0xFFAA, 'M', 'ᆰ'), + (0xFFAB, 'M', 'ᆱ'), + (0xFFAC, 'M', 'ᆲ'), + (0xFFAD, 'M', 'ᆳ'), + (0xFFAE, 'M', 'ᆴ'), + (0xFFAF, 'M', 'ᆵ'), + (0xFFB0, 'M', 'ᄚ'), + (0xFFB1, 'M', 'ᄆ'), + (0xFFB2, 'M', 'ᄇ'), + (0xFFB3, 'M', 'ᄈ'), + (0xFFB4, 'M', 'ᄡ'), + (0xFFB5, 'M', 'ᄉ'), + (0xFFB6, 'M', 'ᄊ'), + (0xFFB7, 'M', 'ᄋ'), + (0xFFB8, 'M', 'ᄌ'), + (0xFFB9, 'M', 'ᄍ'), + (0xFFBA, 'M', 'ᄎ'), + (0xFFBB, 'M', 'ᄏ'), + (0xFFBC, 'M', 'ᄐ'), + (0xFFBD, 'M', 'ᄑ'), + (0xFFBE, 'M', 'ᄒ'), (0xFFBF, 'X'), - (0xFFC2, 'M', u'ᅡ'), - (0xFFC3, 'M', u'ᅢ'), - (0xFFC4, 'M', u'ᅣ'), - (0xFFC5, 'M', u'ᅤ'), - (0xFFC6, 'M', u'ᅥ'), - (0xFFC7, 'M', u'ᅦ'), + (0xFFC2, 'M', 'ᅡ'), + (0xFFC3, 'M', 'ᅢ'), + (0xFFC4, 'M', 'ᅣ'), + (0xFFC5, 'M', 'ᅤ'), + (0xFFC6, 'M', 'ᅥ'), + (0xFFC7, 'M', 'ᅦ'), (0xFFC8, 'X'), - (0xFFCA, 'M', u'ᅧ'), - (0xFFCB, 'M', u'ᅨ'), - (0xFFCC, 'M', u'ᅩ'), - (0xFFCD, 'M', u'ᅪ'), - (0xFFCE, 'M', u'ᅫ'), - (0xFFCF, 'M', u'ᅬ'), + (0xFFCA, 'M', 'ᅧ'), + (0xFFCB, 'M', 'ᅨ'), + (0xFFCC, 'M', 'ᅩ'), + (0xFFCD, 'M', 'ᅪ'), + (0xFFCE, 'M', 'ᅫ'), + (0xFFCF, 'M', 'ᅬ'), (0xFFD0, 'X'), - (0xFFD2, 'M', u'ᅭ'), - (0xFFD3, 'M', u'ᅮ'), - (0xFFD4, 'M', u'ᅯ'), - (0xFFD5, 'M', u'ᅰ'), - (0xFFD6, 'M', u'ᅱ'), - (0xFFD7, 'M', u'ᅲ'), + (0xFFD2, 'M', 'ᅭ'), + (0xFFD3, 'M', 'ᅮ'), + (0xFFD4, 'M', 'ᅯ'), + (0xFFD5, 'M', 'ᅰ'), + (0xFFD6, 'M', 'ᅱ'), + (0xFFD7, 'M', 'ᅲ'), (0xFFD8, 'X'), - (0xFFDA, 'M', u'ᅳ'), - (0xFFDB, 'M', u'ᅴ'), - (0xFFDC, 'M', u'ᅵ'), + (0xFFDA, 'M', 'ᅳ'), + (0xFFDB, 'M', 'ᅴ'), + (0xFFDC, 'M', 'ᅵ'), (0xFFDD, 'X'), - (0xFFE0, 'M', u'¢'), - (0xFFE1, 'M', u'£'), - (0xFFE2, 'M', u'¬'), - (0xFFE3, '3', u' ̄'), - (0xFFE4, 'M', u'¦'), - (0xFFE5, 'M', u'¥'), - (0xFFE6, 'M', u'₩'), + (0xFFE0, 'M', '¢'), + (0xFFE1, 'M', '£'), + (0xFFE2, 'M', '¬'), + (0xFFE3, '3', ' ̄'), + (0xFFE4, 'M', '¦'), + (0xFFE5, 'M', '¥'), + (0xFFE6, 'M', '₩'), (0xFFE7, 'X'), - (0xFFE8, 'M', u'│'), - (0xFFE9, 'M', u'←'), - (0xFFEA, 'M', u'↑'), - (0xFFEB, 'M', u'→'), - (0xFFEC, 'M', u'↓'), - (0xFFED, 'M', u'■'), - (0xFFEE, 'M', u'○'), + (0xFFE8, 'M', '│'), + (0xFFE9, 'M', '←'), + (0xFFEA, 'M', '↑'), + (0xFFEB, 'M', '→'), + (0xFFEC, 'M', '↓'), + (0xFFED, 'M', '■'), + (0xFFEE, 'M', '○'), (0xFFEF, 'X'), (0x10000, 'V'), (0x1000C, 'X'), (0x1000D, 'V'), + ] + +def _seg_53(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ (0x10027, 'X'), (0x10028, 'V'), (0x1003B, 'X'), @@ -5490,7 +5592,7 @@ def _seg_52(): (0x10137, 'V'), (0x1018F, 'X'), (0x10190, 'V'), - (0x1019C, 'X'), + (0x1019D, 'X'), (0x101A0, 'V'), (0x101A1, 'X'), (0x101D0, 'V'), @@ -5513,90 +5615,91 @@ def _seg_52(): (0x103C4, 'X'), (0x103C8, 'V'), (0x103D6, 'X'), - (0x10400, 'M', u'𐐨'), - (0x10401, 'M', u'𐐩'), - ] - -def _seg_53(): - return [ - (0x10402, 'M', u'𐐪'), - (0x10403, 'M', u'𐐫'), - (0x10404, 'M', u'𐐬'), - (0x10405, 'M', u'𐐭'), - (0x10406, 'M', u'𐐮'), - (0x10407, 'M', u'𐐯'), - (0x10408, 'M', u'𐐰'), - (0x10409, 'M', u'𐐱'), - (0x1040A, 'M', u'𐐲'), - (0x1040B, 'M', u'𐐳'), - (0x1040C, 'M', u'𐐴'), - (0x1040D, 'M', u'𐐵'), - (0x1040E, 'M', u'𐐶'), - (0x1040F, 'M', u'𐐷'), - (0x10410, 'M', u'𐐸'), - (0x10411, 'M', u'𐐹'), - (0x10412, 'M', u'𐐺'), - (0x10413, 'M', u'𐐻'), - (0x10414, 'M', u'𐐼'), - (0x10415, 'M', u'𐐽'), - (0x10416, 'M', u'𐐾'), - (0x10417, 'M', u'𐐿'), - (0x10418, 'M', u'𐑀'), - (0x10419, 'M', u'𐑁'), - (0x1041A, 'M', u'𐑂'), - (0x1041B, 'M', u'𐑃'), - (0x1041C, 'M', u'𐑄'), - (0x1041D, 'M', u'𐑅'), - (0x1041E, 'M', u'𐑆'), - (0x1041F, 'M', u'𐑇'), - (0x10420, 'M', u'𐑈'), - (0x10421, 'M', u'𐑉'), - (0x10422, 'M', u'𐑊'), - (0x10423, 'M', u'𐑋'), - (0x10424, 'M', u'𐑌'), - (0x10425, 'M', u'𐑍'), - (0x10426, 'M', u'𐑎'), - (0x10427, 'M', u'𐑏'), + (0x10400, 'M', '𐐨'), + (0x10401, 'M', '𐐩'), + (0x10402, 'M', '𐐪'), + (0x10403, 'M', '𐐫'), + (0x10404, 'M', '𐐬'), + (0x10405, 'M', '𐐭'), + (0x10406, 'M', '𐐮'), + (0x10407, 'M', '𐐯'), + (0x10408, 'M', '𐐰'), + (0x10409, 'M', '𐐱'), + (0x1040A, 'M', '𐐲'), + (0x1040B, 'M', '𐐳'), + (0x1040C, 'M', '𐐴'), + (0x1040D, 'M', '𐐵'), + (0x1040E, 'M', '𐐶'), + (0x1040F, 'M', '𐐷'), + (0x10410, 'M', '𐐸'), + (0x10411, 'M', '𐐹'), + (0x10412, 'M', '𐐺'), + (0x10413, 'M', '𐐻'), + (0x10414, 'M', '𐐼'), + (0x10415, 'M', '𐐽'), + (0x10416, 'M', '𐐾'), + (0x10417, 'M', '𐐿'), + (0x10418, 'M', '𐑀'), + (0x10419, 'M', '𐑁'), + (0x1041A, 'M', '𐑂'), + (0x1041B, 'M', '𐑃'), + (0x1041C, 'M', '𐑄'), + (0x1041D, 'M', '𐑅'), + (0x1041E, 'M', '𐑆'), + (0x1041F, 'M', '𐑇'), + (0x10420, 'M', '𐑈'), + (0x10421, 'M', '𐑉'), + (0x10422, 'M', '𐑊'), + (0x10423, 'M', '𐑋'), + (0x10424, 'M', '𐑌'), + (0x10425, 'M', '𐑍'), + (0x10426, 'M', '𐑎'), + (0x10427, 'M', '𐑏'), (0x10428, 'V'), (0x1049E, 'X'), (0x104A0, 'V'), (0x104AA, 'X'), - (0x104B0, 'M', u'𐓘'), - (0x104B1, 'M', u'𐓙'), - (0x104B2, 'M', u'𐓚'), - (0x104B3, 'M', u'𐓛'), - (0x104B4, 'M', u'𐓜'), - (0x104B5, 'M', u'𐓝'), - (0x104B6, 'M', u'𐓞'), - (0x104B7, 'M', u'𐓟'), - (0x104B8, 'M', u'𐓠'), - (0x104B9, 'M', u'𐓡'), - (0x104BA, 'M', u'𐓢'), - (0x104BB, 'M', u'𐓣'), - (0x104BC, 'M', u'𐓤'), - (0x104BD, 'M', u'𐓥'), - (0x104BE, 'M', u'𐓦'), - (0x104BF, 'M', u'𐓧'), - (0x104C0, 'M', u'𐓨'), - (0x104C1, 'M', u'𐓩'), - (0x104C2, 'M', u'𐓪'), - (0x104C3, 'M', u'𐓫'), - (0x104C4, 'M', u'𐓬'), - (0x104C5, 'M', u'𐓭'), - (0x104C6, 'M', u'𐓮'), - (0x104C7, 'M', u'𐓯'), - (0x104C8, 'M', u'𐓰'), - (0x104C9, 'M', u'𐓱'), - (0x104CA, 'M', u'𐓲'), - (0x104CB, 'M', u'𐓳'), - (0x104CC, 'M', u'𐓴'), - (0x104CD, 'M', u'𐓵'), - (0x104CE, 'M', u'𐓶'), - (0x104CF, 'M', u'𐓷'), - (0x104D0, 'M', u'𐓸'), - (0x104D1, 'M', u'𐓹'), - (0x104D2, 'M', u'𐓺'), - (0x104D3, 'M', u'𐓻'), + (0x104B0, 'M', '𐓘'), + (0x104B1, 'M', '𐓙'), + (0x104B2, 'M', '𐓚'), + (0x104B3, 'M', '𐓛'), + (0x104B4, 'M', '𐓜'), + (0x104B5, 'M', '𐓝'), + (0x104B6, 'M', '𐓞'), + (0x104B7, 'M', '𐓟'), + (0x104B8, 'M', '𐓠'), + (0x104B9, 'M', '𐓡'), + (0x104BA, 'M', '𐓢'), + (0x104BB, 'M', '𐓣'), + (0x104BC, 'M', '𐓤'), + (0x104BD, 'M', '𐓥'), + (0x104BE, 'M', '𐓦'), + ] + +def _seg_54(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ + (0x104BF, 'M', '𐓧'), + (0x104C0, 'M', '𐓨'), + (0x104C1, 'M', '𐓩'), + (0x104C2, 'M', '𐓪'), + (0x104C3, 'M', '𐓫'), + (0x104C4, 'M', '𐓬'), + (0x104C5, 'M', '𐓭'), + (0x104C6, 'M', '𐓮'), + (0x104C7, 'M', '𐓯'), + (0x104C8, 'M', '𐓰'), + (0x104C9, 'M', '𐓱'), + (0x104CA, 'M', '𐓲'), + (0x104CB, 'M', '𐓳'), + (0x104CC, 'M', '𐓴'), + (0x104CD, 'M', '𐓵'), + (0x104CE, 'M', '𐓶'), + (0x104CF, 'M', '𐓷'), + (0x104D0, 'M', '𐓸'), + (0x104D1, 'M', '𐓹'), + (0x104D2, 'M', '𐓺'), + (0x104D3, 'M', '𐓻'), (0x104D4, 'X'), (0x104D8, 'V'), (0x104FC, 'X'), @@ -5619,10 +5722,6 @@ def _seg_53(): (0x1080A, 'V'), (0x10836, 'X'), (0x10837, 'V'), - ] - -def _seg_54(): - return [ (0x10839, 'X'), (0x1083C, 'V'), (0x1083D, 'X'), @@ -5680,63 +5779,64 @@ def _seg_54(): (0x10B9D, 'X'), (0x10BA9, 'V'), (0x10BB0, 'X'), - (0x10C00, 'V'), - (0x10C49, 'X'), - (0x10C80, 'M', u'𐳀'), - (0x10C81, 'M', u'𐳁'), - (0x10C82, 'M', u'𐳂'), - (0x10C83, 'M', u'𐳃'), - (0x10C84, 'M', u'𐳄'), - (0x10C85, 'M', u'𐳅'), - (0x10C86, 'M', u'𐳆'), - (0x10C87, 'M', u'𐳇'), - (0x10C88, 'M', u'𐳈'), - (0x10C89, 'M', u'𐳉'), - (0x10C8A, 'M', u'𐳊'), - (0x10C8B, 'M', u'𐳋'), - (0x10C8C, 'M', u'𐳌'), - (0x10C8D, 'M', u'𐳍'), - (0x10C8E, 'M', u'𐳎'), - (0x10C8F, 'M', u'𐳏'), - (0x10C90, 'M', u'𐳐'), - (0x10C91, 'M', u'𐳑'), - (0x10C92, 'M', u'𐳒'), - (0x10C93, 'M', u'𐳓'), - (0x10C94, 'M', u'𐳔'), - (0x10C95, 'M', u'𐳕'), - (0x10C96, 'M', u'𐳖'), - (0x10C97, 'M', u'𐳗'), - (0x10C98, 'M', u'𐳘'), - (0x10C99, 'M', u'𐳙'), - (0x10C9A, 'M', u'𐳚'), - (0x10C9B, 'M', u'𐳛'), - (0x10C9C, 'M', u'𐳜'), - (0x10C9D, 'M', u'𐳝'), - (0x10C9E, 'M', u'𐳞'), - (0x10C9F, 'M', u'𐳟'), - (0x10CA0, 'M', u'𐳠'), - (0x10CA1, 'M', u'𐳡'), - (0x10CA2, 'M', u'𐳢'), - (0x10CA3, 'M', u'𐳣'), - (0x10CA4, 'M', u'𐳤'), - (0x10CA5, 'M', u'𐳥'), - (0x10CA6, 'M', u'𐳦'), - (0x10CA7, 'M', u'𐳧'), - (0x10CA8, 'M', u'𐳨'), ] def _seg_55(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x10CA9, 'M', u'𐳩'), - (0x10CAA, 'M', u'𐳪'), - (0x10CAB, 'M', u'𐳫'), - (0x10CAC, 'M', u'𐳬'), - (0x10CAD, 'M', u'𐳭'), - (0x10CAE, 'M', u'𐳮'), - (0x10CAF, 'M', u'𐳯'), - (0x10CB0, 'M', u'𐳰'), - (0x10CB1, 'M', u'𐳱'), - (0x10CB2, 'M', u'𐳲'), + (0x10C00, 'V'), + (0x10C49, 'X'), + (0x10C80, 'M', '𐳀'), + (0x10C81, 'M', '𐳁'), + (0x10C82, 'M', '𐳂'), + (0x10C83, 'M', '𐳃'), + (0x10C84, 'M', '𐳄'), + (0x10C85, 'M', '𐳅'), + (0x10C86, 'M', '𐳆'), + (0x10C87, 'M', '𐳇'), + (0x10C88, 'M', '𐳈'), + (0x10C89, 'M', '𐳉'), + (0x10C8A, 'M', '𐳊'), + (0x10C8B, 'M', '𐳋'), + (0x10C8C, 'M', '𐳌'), + (0x10C8D, 'M', '𐳍'), + (0x10C8E, 'M', '𐳎'), + (0x10C8F, 'M', '𐳏'), + (0x10C90, 'M', '𐳐'), + (0x10C91, 'M', '𐳑'), + (0x10C92, 'M', '𐳒'), + (0x10C93, 'M', '𐳓'), + (0x10C94, 'M', '𐳔'), + (0x10C95, 'M', '𐳕'), + (0x10C96, 'M', '𐳖'), + (0x10C97, 'M', '𐳗'), + (0x10C98, 'M', '𐳘'), + (0x10C99, 'M', '𐳙'), + (0x10C9A, 'M', '𐳚'), + (0x10C9B, 'M', '𐳛'), + (0x10C9C, 'M', '𐳜'), + (0x10C9D, 'M', '𐳝'), + (0x10C9E, 'M', '𐳞'), + (0x10C9F, 'M', '𐳟'), + (0x10CA0, 'M', '𐳠'), + (0x10CA1, 'M', '𐳡'), + (0x10CA2, 'M', '𐳢'), + (0x10CA3, 'M', '𐳣'), + (0x10CA4, 'M', '𐳤'), + (0x10CA5, 'M', '𐳥'), + (0x10CA6, 'M', '𐳦'), + (0x10CA7, 'M', '𐳧'), + (0x10CA8, 'M', '𐳨'), + (0x10CA9, 'M', '𐳩'), + (0x10CAA, 'M', '𐳪'), + (0x10CAB, 'M', '𐳫'), + (0x10CAC, 'M', '𐳬'), + (0x10CAD, 'M', '𐳭'), + (0x10CAE, 'M', '𐳮'), + (0x10CAF, 'M', '𐳯'), + (0x10CB0, 'M', '𐳰'), + (0x10CB1, 'M', '𐳱'), + (0x10CB2, 'M', '𐳲'), (0x10CB3, 'X'), (0x10CC0, 'V'), (0x10CF3, 'X'), @@ -5746,10 +5846,20 @@ def _seg_55(): (0x10D3A, 'X'), (0x10E60, 'V'), (0x10E7F, 'X'), + (0x10E80, 'V'), + (0x10EAA, 'X'), + (0x10EAB, 'V'), + (0x10EAE, 'X'), + (0x10EB0, 'V'), + (0x10EB2, 'X'), (0x10F00, 'V'), (0x10F28, 'X'), (0x10F30, 'V'), (0x10F5A, 'X'), + (0x10FB0, 'V'), + (0x10FCC, 'X'), + (0x10FE0, 'V'), + (0x10FF7, 'X'), (0x11000, 'V'), (0x1104E, 'X'), (0x11052, 'V'), @@ -5765,17 +5875,20 @@ def _seg_55(): (0x11100, 'V'), (0x11135, 'X'), (0x11136, 'V'), - (0x11147, 'X'), + (0x11148, 'X'), (0x11150, 'V'), (0x11177, 'X'), (0x11180, 'V'), - (0x111CE, 'X'), - (0x111D0, 'V'), (0x111E0, 'X'), (0x111E1, 'V'), (0x111F5, 'X'), (0x11200, 'V'), (0x11212, 'X'), + ] + +def _seg_56(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ (0x11213, 'V'), (0x1123F, 'X'), (0x11280, 'V'), @@ -5823,15 +5936,9 @@ def _seg_55(): (0x11370, 'V'), (0x11375, 'X'), (0x11400, 'V'), - (0x1145A, 'X'), - (0x1145B, 'V'), (0x1145C, 'X'), (0x1145D, 'V'), - ] - -def _seg_56(): - return [ - (0x1145F, 'X'), + (0x11462, 'X'), (0x11480, 'V'), (0x114C8, 'X'), (0x114D0, 'V'), @@ -5847,7 +5954,7 @@ def _seg_56(): (0x11660, 'V'), (0x1166D, 'X'), (0x11680, 'V'), - (0x116B8, 'X'), + (0x116B9, 'X'), (0x116C0, 'V'), (0x116CA, 'X'), (0x11700, 'V'), @@ -5858,47 +5965,70 @@ def _seg_56(): (0x11740, 'X'), (0x11800, 'V'), (0x1183C, 'X'), - (0x118A0, 'M', u'𑣀'), - (0x118A1, 'M', u'𑣁'), - (0x118A2, 'M', u'𑣂'), - (0x118A3, 'M', u'𑣃'), - (0x118A4, 'M', u'𑣄'), - (0x118A5, 'M', u'𑣅'), - (0x118A6, 'M', u'𑣆'), - (0x118A7, 'M', u'𑣇'), - (0x118A8, 'M', u'𑣈'), - (0x118A9, 'M', u'𑣉'), - (0x118AA, 'M', u'𑣊'), - (0x118AB, 'M', u'𑣋'), - (0x118AC, 'M', u'𑣌'), - (0x118AD, 'M', u'𑣍'), - (0x118AE, 'M', u'𑣎'), - (0x118AF, 'M', u'𑣏'), - (0x118B0, 'M', u'𑣐'), - (0x118B1, 'M', u'𑣑'), - (0x118B2, 'M', u'𑣒'), - (0x118B3, 'M', u'𑣓'), - (0x118B4, 'M', u'𑣔'), - (0x118B5, 'M', u'𑣕'), - (0x118B6, 'M', u'𑣖'), - (0x118B7, 'M', u'𑣗'), - (0x118B8, 'M', u'𑣘'), - (0x118B9, 'M', u'𑣙'), - (0x118BA, 'M', u'𑣚'), - (0x118BB, 'M', u'𑣛'), - (0x118BC, 'M', u'𑣜'), - (0x118BD, 'M', u'𑣝'), - (0x118BE, 'M', u'𑣞'), - (0x118BF, 'M', u'𑣟'), + (0x118A0, 'M', '𑣀'), + (0x118A1, 'M', '𑣁'), + (0x118A2, 'M', '𑣂'), + (0x118A3, 'M', '𑣃'), + (0x118A4, 'M', '𑣄'), + (0x118A5, 'M', '𑣅'), + (0x118A6, 'M', '𑣆'), + (0x118A7, 'M', '𑣇'), + (0x118A8, 'M', '𑣈'), + (0x118A9, 'M', '𑣉'), + (0x118AA, 'M', '𑣊'), + (0x118AB, 'M', '𑣋'), + (0x118AC, 'M', '𑣌'), + (0x118AD, 'M', '𑣍'), + (0x118AE, 'M', '𑣎'), + (0x118AF, 'M', '𑣏'), + (0x118B0, 'M', '𑣐'), + (0x118B1, 'M', '𑣑'), + (0x118B2, 'M', '𑣒'), + (0x118B3, 'M', '𑣓'), + (0x118B4, 'M', '𑣔'), + (0x118B5, 'M', '𑣕'), + (0x118B6, 'M', '𑣖'), + (0x118B7, 'M', '𑣗'), + ] + +def _seg_57(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ + (0x118B8, 'M', '𑣘'), + (0x118B9, 'M', '𑣙'), + (0x118BA, 'M', '𑣚'), + (0x118BB, 'M', '𑣛'), + (0x118BC, 'M', '𑣜'), + (0x118BD, 'M', '𑣝'), + (0x118BE, 'M', '𑣞'), + (0x118BF, 'M', '𑣟'), (0x118C0, 'V'), (0x118F3, 'X'), (0x118FF, 'V'), - (0x11900, 'X'), + (0x11907, 'X'), + (0x11909, 'V'), + (0x1190A, 'X'), + (0x1190C, 'V'), + (0x11914, 'X'), + (0x11915, 'V'), + (0x11917, 'X'), + (0x11918, 'V'), + (0x11936, 'X'), + (0x11937, 'V'), + (0x11939, 'X'), + (0x1193B, 'V'), + (0x11947, 'X'), + (0x11950, 'V'), + (0x1195A, 'X'), + (0x119A0, 'V'), + (0x119A8, 'X'), + (0x119AA, 'V'), + (0x119D8, 'X'), + (0x119DA, 'V'), + (0x119E5, 'X'), (0x11A00, 'V'), (0x11A48, 'X'), (0x11A50, 'V'), - (0x11A84, 'X'), - (0x11A86, 'V'), (0x11AA3, 'X'), (0x11AC0, 'V'), (0x11AF9, 'X'), @@ -5931,10 +6061,6 @@ def _seg_56(): (0x11D50, 'V'), (0x11D5A, 'X'), (0x11D60, 'V'), - ] - -def _seg_57(): - return [ (0x11D66, 'X'), (0x11D67, 'V'), (0x11D69, 'X'), @@ -5948,7 +6074,11 @@ def _seg_57(): (0x11DAA, 'X'), (0x11EE0, 'V'), (0x11EF9, 'X'), - (0x12000, 'V'), + (0x11FB0, 'V'), + (0x11FB1, 'X'), + (0x11FC0, 'V'), + (0x11FF2, 'X'), + (0x11FFF, 'V'), (0x1239A, 'X'), (0x12400, 'V'), (0x1246F, 'X'), @@ -5964,6 +6094,11 @@ def _seg_57(): (0x16A39, 'X'), (0x16A40, 'V'), (0x16A5F, 'X'), + ] + +def _seg_58(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ (0x16A60, 'V'), (0x16A6A, 'X'), (0x16A6E, 'V'), @@ -5982,22 +6117,62 @@ def _seg_57(): (0x16B78, 'X'), (0x16B7D, 'V'), (0x16B90, 'X'), + (0x16E40, 'M', '𖹠'), + (0x16E41, 'M', '𖹡'), + (0x16E42, 'M', '𖹢'), + (0x16E43, 'M', '𖹣'), + (0x16E44, 'M', '𖹤'), + (0x16E45, 'M', '𖹥'), + (0x16E46, 'M', '𖹦'), + (0x16E47, 'M', '𖹧'), + (0x16E48, 'M', '𖹨'), + (0x16E49, 'M', '𖹩'), + (0x16E4A, 'M', '𖹪'), + (0x16E4B, 'M', '𖹫'), + (0x16E4C, 'M', '𖹬'), + (0x16E4D, 'M', '𖹭'), + (0x16E4E, 'M', '𖹮'), + (0x16E4F, 'M', '𖹯'), + (0x16E50, 'M', '𖹰'), + (0x16E51, 'M', '𖹱'), + (0x16E52, 'M', '𖹲'), + (0x16E53, 'M', '𖹳'), + (0x16E54, 'M', '𖹴'), + (0x16E55, 'M', '𖹵'), + (0x16E56, 'M', '𖹶'), + (0x16E57, 'M', '𖹷'), + (0x16E58, 'M', '𖹸'), + (0x16E59, 'M', '𖹹'), + (0x16E5A, 'M', '𖹺'), + (0x16E5B, 'M', '𖹻'), + (0x16E5C, 'M', '𖹼'), + (0x16E5D, 'M', '𖹽'), + (0x16E5E, 'M', '𖹾'), + (0x16E5F, 'M', '𖹿'), (0x16E60, 'V'), (0x16E9B, 'X'), (0x16F00, 'V'), - (0x16F45, 'X'), - (0x16F50, 'V'), - (0x16F7F, 'X'), + (0x16F4B, 'X'), + (0x16F4F, 'V'), + (0x16F88, 'X'), (0x16F8F, 'V'), (0x16FA0, 'X'), (0x16FE0, 'V'), - (0x16FE2, 'X'), + (0x16FE5, 'X'), + (0x16FF0, 'V'), + (0x16FF2, 'X'), (0x17000, 'V'), - (0x187F2, 'X'), + (0x187F8, 'X'), (0x18800, 'V'), - (0x18AF3, 'X'), + (0x18CD6, 'X'), + (0x18D00, 'V'), + (0x18D09, 'X'), (0x1B000, 'V'), (0x1B11F, 'X'), + (0x1B150, 'V'), + (0x1B153, 'X'), + (0x1B164, 'V'), + (0x1B168, 'X'), (0x1B170, 'V'), (0x1B2FC, 'X'), (0x1BC00, 'V'), @@ -6016,29 +6191,30 @@ def _seg_57(): (0x1D100, 'V'), (0x1D127, 'X'), (0x1D129, 'V'), - (0x1D15E, 'M', u'𝅗𝅥'), - (0x1D15F, 'M', u'𝅘𝅥'), - (0x1D160, 'M', u'𝅘𝅥𝅮'), - (0x1D161, 'M', u'𝅘𝅥𝅯'), - (0x1D162, 'M', u'𝅘𝅥𝅰'), - (0x1D163, 'M', u'𝅘𝅥𝅱'), - (0x1D164, 'M', u'𝅘𝅥𝅲'), + (0x1D15E, 'M', '𝅗𝅥'), + (0x1D15F, 'M', '𝅘𝅥'), + (0x1D160, 'M', '𝅘𝅥𝅮'), + (0x1D161, 'M', '𝅘𝅥𝅯'), + (0x1D162, 'M', '𝅘𝅥𝅰'), + (0x1D163, 'M', '𝅘𝅥𝅱'), + (0x1D164, 'M', '𝅘𝅥𝅲'), (0x1D165, 'V'), + ] + +def _seg_59(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ (0x1D173, 'X'), (0x1D17B, 'V'), - (0x1D1BB, 'M', u'𝆹𝅥'), - (0x1D1BC, 'M', u'𝆺𝅥'), - (0x1D1BD, 'M', u'𝆹𝅥𝅮'), - (0x1D1BE, 'M', u'𝆺𝅥𝅮'), - (0x1D1BF, 'M', u'𝆹𝅥𝅯'), - (0x1D1C0, 'M', u'𝆺𝅥𝅯'), + (0x1D1BB, 'M', '𝆹𝅥'), + (0x1D1BC, 'M', '𝆺𝅥'), + (0x1D1BD, 'M', '𝆹𝅥𝅮'), + (0x1D1BE, 'M', '𝆺𝅥𝅮'), + (0x1D1BF, 'M', '𝆹𝅥𝅯'), + (0x1D1C0, 'M', '𝆺𝅥𝅯'), (0x1D1C1, 'V'), (0x1D1E9, 'X'), (0x1D200, 'V'), - ] - -def _seg_58(): - return [ (0x1D246, 'X'), (0x1D2E0, 'V'), (0x1D2F4, 'X'), @@ -6046,1056 +6222,1066 @@ def _seg_58(): (0x1D357, 'X'), (0x1D360, 'V'), (0x1D379, 'X'), - (0x1D400, 'M', u'a'), - (0x1D401, 'M', u'b'), - (0x1D402, 'M', u'c'), - (0x1D403, 'M', u'd'), - (0x1D404, 'M', u'e'), - (0x1D405, 'M', u'f'), - (0x1D406, 'M', u'g'), - (0x1D407, 'M', u'h'), - (0x1D408, 'M', u'i'), - (0x1D409, 'M', u'j'), - (0x1D40A, 'M', u'k'), - (0x1D40B, 'M', u'l'), - (0x1D40C, 'M', u'm'), - (0x1D40D, 'M', u'n'), - (0x1D40E, 'M', u'o'), - (0x1D40F, 'M', u'p'), - (0x1D410, 'M', u'q'), - (0x1D411, 'M', u'r'), - (0x1D412, 'M', u's'), - (0x1D413, 'M', u't'), - (0x1D414, 'M', u'u'), - (0x1D415, 'M', u'v'), - (0x1D416, 'M', u'w'), - (0x1D417, 'M', u'x'), - (0x1D418, 'M', u'y'), - (0x1D419, 'M', u'z'), - (0x1D41A, 'M', u'a'), - (0x1D41B, 'M', u'b'), - (0x1D41C, 'M', u'c'), - (0x1D41D, 'M', u'd'), - (0x1D41E, 'M', u'e'), - (0x1D41F, 'M', u'f'), - (0x1D420, 'M', u'g'), - (0x1D421, 'M', u'h'), - (0x1D422, 'M', u'i'), - (0x1D423, 'M', u'j'), - (0x1D424, 'M', u'k'), - (0x1D425, 'M', u'l'), - (0x1D426, 'M', u'm'), - (0x1D427, 'M', u'n'), - (0x1D428, 'M', u'o'), - (0x1D429, 'M', u'p'), - (0x1D42A, 'M', u'q'), - (0x1D42B, 'M', u'r'), - (0x1D42C, 'M', u's'), - (0x1D42D, 'M', u't'), - (0x1D42E, 'M', u'u'), - (0x1D42F, 'M', u'v'), - (0x1D430, 'M', u'w'), - (0x1D431, 'M', u'x'), - (0x1D432, 'M', u'y'), - (0x1D433, 'M', u'z'), - (0x1D434, 'M', u'a'), - (0x1D435, 'M', u'b'), - (0x1D436, 'M', u'c'), - (0x1D437, 'M', u'd'), - (0x1D438, 'M', u'e'), - (0x1D439, 'M', u'f'), - (0x1D43A, 'M', u'g'), - (0x1D43B, 'M', u'h'), - (0x1D43C, 'M', u'i'), - (0x1D43D, 'M', u'j'), - (0x1D43E, 'M', u'k'), - (0x1D43F, 'M', u'l'), - (0x1D440, 'M', u'm'), - (0x1D441, 'M', u'n'), - (0x1D442, 'M', u'o'), - (0x1D443, 'M', u'p'), - (0x1D444, 'M', u'q'), - (0x1D445, 'M', u'r'), - (0x1D446, 'M', u's'), - (0x1D447, 'M', u't'), - (0x1D448, 'M', u'u'), - (0x1D449, 'M', u'v'), - (0x1D44A, 'M', u'w'), - (0x1D44B, 'M', u'x'), - (0x1D44C, 'M', u'y'), - (0x1D44D, 'M', u'z'), - (0x1D44E, 'M', u'a'), - (0x1D44F, 'M', u'b'), - (0x1D450, 'M', u'c'), - (0x1D451, 'M', u'd'), - (0x1D452, 'M', u'e'), - (0x1D453, 'M', u'f'), - (0x1D454, 'M', u'g'), - (0x1D455, 'X'), - (0x1D456, 'M', u'i'), - (0x1D457, 'M', u'j'), - (0x1D458, 'M', u'k'), - (0x1D459, 'M', u'l'), - (0x1D45A, 'M', u'm'), - (0x1D45B, 'M', u'n'), - (0x1D45C, 'M', u'o'), - ] - -def _seg_59(): - return [ - (0x1D45D, 'M', u'p'), - (0x1D45E, 'M', u'q'), - (0x1D45F, 'M', u'r'), - (0x1D460, 'M', u's'), - (0x1D461, 'M', u't'), - (0x1D462, 'M', u'u'), - (0x1D463, 'M', u'v'), - (0x1D464, 'M', u'w'), - (0x1D465, 'M', u'x'), - (0x1D466, 'M', u'y'), - (0x1D467, 'M', u'z'), - (0x1D468, 'M', u'a'), - (0x1D469, 'M', u'b'), - (0x1D46A, 'M', u'c'), - (0x1D46B, 'M', u'd'), - (0x1D46C, 'M', u'e'), - (0x1D46D, 'M', u'f'), - (0x1D46E, 'M', u'g'), - (0x1D46F, 'M', u'h'), - (0x1D470, 'M', u'i'), - (0x1D471, 'M', u'j'), - (0x1D472, 'M', u'k'), - (0x1D473, 'M', u'l'), - (0x1D474, 'M', u'm'), - (0x1D475, 'M', u'n'), - (0x1D476, 'M', u'o'), - (0x1D477, 'M', u'p'), - (0x1D478, 'M', u'q'), - (0x1D479, 'M', u'r'), - (0x1D47A, 'M', u's'), - (0x1D47B, 'M', u't'), - (0x1D47C, 'M', u'u'), - (0x1D47D, 'M', u'v'), - (0x1D47E, 'M', u'w'), - (0x1D47F, 'M', u'x'), - (0x1D480, 'M', u'y'), - (0x1D481, 'M', u'z'), - (0x1D482, 'M', u'a'), - (0x1D483, 'M', u'b'), - (0x1D484, 'M', u'c'), - (0x1D485, 'M', u'd'), - (0x1D486, 'M', u'e'), - (0x1D487, 'M', u'f'), - (0x1D488, 'M', u'g'), - (0x1D489, 'M', u'h'), - (0x1D48A, 'M', u'i'), - (0x1D48B, 'M', u'j'), - (0x1D48C, 'M', u'k'), - (0x1D48D, 'M', u'l'), - (0x1D48E, 'M', u'm'), - (0x1D48F, 'M', u'n'), - (0x1D490, 'M', u'o'), - (0x1D491, 'M', u'p'), - (0x1D492, 'M', u'q'), - (0x1D493, 'M', u'r'), - (0x1D494, 'M', u's'), - (0x1D495, 'M', u't'), - (0x1D496, 'M', u'u'), - (0x1D497, 'M', u'v'), - (0x1D498, 'M', u'w'), - (0x1D499, 'M', u'x'), - (0x1D49A, 'M', u'y'), - (0x1D49B, 'M', u'z'), - (0x1D49C, 'M', u'a'), - (0x1D49D, 'X'), - (0x1D49E, 'M', u'c'), - (0x1D49F, 'M', u'd'), - (0x1D4A0, 'X'), - (0x1D4A2, 'M', u'g'), - (0x1D4A3, 'X'), - (0x1D4A5, 'M', u'j'), - (0x1D4A6, 'M', u'k'), - (0x1D4A7, 'X'), - (0x1D4A9, 'M', u'n'), - (0x1D4AA, 'M', u'o'), - (0x1D4AB, 'M', u'p'), - (0x1D4AC, 'M', u'q'), - (0x1D4AD, 'X'), - (0x1D4AE, 'M', u's'), - (0x1D4AF, 'M', u't'), - (0x1D4B0, 'M', u'u'), - (0x1D4B1, 'M', u'v'), - (0x1D4B2, 'M', u'w'), - (0x1D4B3, 'M', u'x'), - (0x1D4B4, 'M', u'y'), - (0x1D4B5, 'M', u'z'), - (0x1D4B6, 'M', u'a'), - (0x1D4B7, 'M', u'b'), - (0x1D4B8, 'M', u'c'), - (0x1D4B9, 'M', u'd'), - (0x1D4BA, 'X'), - (0x1D4BB, 'M', u'f'), - (0x1D4BC, 'X'), - (0x1D4BD, 'M', u'h'), - (0x1D4BE, 'M', u'i'), - (0x1D4BF, 'M', u'j'), - (0x1D4C0, 'M', u'k'), - (0x1D4C1, 'M', u'l'), - (0x1D4C2, 'M', u'm'), - (0x1D4C3, 'M', u'n'), + (0x1D400, 'M', 'a'), + (0x1D401, 'M', 'b'), + (0x1D402, 'M', 'c'), + (0x1D403, 'M', 'd'), + (0x1D404, 'M', 'e'), + (0x1D405, 'M', 'f'), + (0x1D406, 'M', 'g'), + (0x1D407, 'M', 'h'), + (0x1D408, 'M', 'i'), + (0x1D409, 'M', 'j'), + (0x1D40A, 'M', 'k'), + (0x1D40B, 'M', 'l'), + (0x1D40C, 'M', 'm'), + (0x1D40D, 'M', 'n'), + (0x1D40E, 'M', 'o'), + (0x1D40F, 'M', 'p'), + (0x1D410, 'M', 'q'), + (0x1D411, 'M', 'r'), + (0x1D412, 'M', 's'), + (0x1D413, 'M', 't'), + (0x1D414, 'M', 'u'), + (0x1D415, 'M', 'v'), + (0x1D416, 'M', 'w'), + (0x1D417, 'M', 'x'), + (0x1D418, 'M', 'y'), + (0x1D419, 'M', 'z'), + (0x1D41A, 'M', 'a'), + (0x1D41B, 'M', 'b'), + (0x1D41C, 'M', 'c'), + (0x1D41D, 'M', 'd'), + (0x1D41E, 'M', 'e'), + (0x1D41F, 'M', 'f'), + (0x1D420, 'M', 'g'), + (0x1D421, 'M', 'h'), + (0x1D422, 'M', 'i'), + (0x1D423, 'M', 'j'), + (0x1D424, 'M', 'k'), + (0x1D425, 'M', 'l'), + (0x1D426, 'M', 'm'), + (0x1D427, 'M', 'n'), + (0x1D428, 'M', 'o'), + (0x1D429, 'M', 'p'), + (0x1D42A, 'M', 'q'), + (0x1D42B, 'M', 'r'), + (0x1D42C, 'M', 's'), + (0x1D42D, 'M', 't'), + (0x1D42E, 'M', 'u'), + (0x1D42F, 'M', 'v'), + (0x1D430, 'M', 'w'), + (0x1D431, 'M', 'x'), + (0x1D432, 'M', 'y'), + (0x1D433, 'M', 'z'), + (0x1D434, 'M', 'a'), + (0x1D435, 'M', 'b'), + (0x1D436, 'M', 'c'), + (0x1D437, 'M', 'd'), + (0x1D438, 'M', 'e'), + (0x1D439, 'M', 'f'), + (0x1D43A, 'M', 'g'), + (0x1D43B, 'M', 'h'), + (0x1D43C, 'M', 'i'), + (0x1D43D, 'M', 'j'), + (0x1D43E, 'M', 'k'), + (0x1D43F, 'M', 'l'), + (0x1D440, 'M', 'm'), + (0x1D441, 'M', 'n'), + (0x1D442, 'M', 'o'), + (0x1D443, 'M', 'p'), + (0x1D444, 'M', 'q'), + (0x1D445, 'M', 'r'), + (0x1D446, 'M', 's'), + (0x1D447, 'M', 't'), + (0x1D448, 'M', 'u'), + (0x1D449, 'M', 'v'), + (0x1D44A, 'M', 'w'), + (0x1D44B, 'M', 'x'), + (0x1D44C, 'M', 'y'), + (0x1D44D, 'M', 'z'), + (0x1D44E, 'M', 'a'), + (0x1D44F, 'M', 'b'), + (0x1D450, 'M', 'c'), + (0x1D451, 'M', 'd'), ] def _seg_60(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x1D4C4, 'X'), - (0x1D4C5, 'M', u'p'), - (0x1D4C6, 'M', u'q'), - (0x1D4C7, 'M', u'r'), - (0x1D4C8, 'M', u's'), - (0x1D4C9, 'M', u't'), - (0x1D4CA, 'M', u'u'), - (0x1D4CB, 'M', u'v'), - (0x1D4CC, 'M', u'w'), - (0x1D4CD, 'M', u'x'), - (0x1D4CE, 'M', u'y'), - (0x1D4CF, 'M', u'z'), - (0x1D4D0, 'M', u'a'), - (0x1D4D1, 'M', u'b'), - (0x1D4D2, 'M', u'c'), - (0x1D4D3, 'M', u'd'), - (0x1D4D4, 'M', u'e'), - (0x1D4D5, 'M', u'f'), - (0x1D4D6, 'M', u'g'), - (0x1D4D7, 'M', u'h'), - (0x1D4D8, 'M', u'i'), - (0x1D4D9, 'M', u'j'), - (0x1D4DA, 'M', u'k'), - (0x1D4DB, 'M', u'l'), - (0x1D4DC, 'M', u'm'), - (0x1D4DD, 'M', u'n'), - (0x1D4DE, 'M', u'o'), - (0x1D4DF, 'M', u'p'), - (0x1D4E0, 'M', u'q'), - (0x1D4E1, 'M', u'r'), - (0x1D4E2, 'M', u's'), - (0x1D4E3, 'M', u't'), - (0x1D4E4, 'M', u'u'), - (0x1D4E5, 'M', u'v'), - (0x1D4E6, 'M', u'w'), - (0x1D4E7, 'M', u'x'), - (0x1D4E8, 'M', u'y'), - (0x1D4E9, 'M', u'z'), - (0x1D4EA, 'M', u'a'), - (0x1D4EB, 'M', u'b'), - (0x1D4EC, 'M', u'c'), - (0x1D4ED, 'M', u'd'), - (0x1D4EE, 'M', u'e'), - (0x1D4EF, 'M', u'f'), - (0x1D4F0, 'M', u'g'), - (0x1D4F1, 'M', u'h'), - (0x1D4F2, 'M', u'i'), - (0x1D4F3, 'M', u'j'), - (0x1D4F4, 'M', u'k'), - (0x1D4F5, 'M', u'l'), - (0x1D4F6, 'M', u'm'), - (0x1D4F7, 'M', u'n'), - (0x1D4F8, 'M', u'o'), - (0x1D4F9, 'M', u'p'), - (0x1D4FA, 'M', u'q'), - (0x1D4FB, 'M', u'r'), - (0x1D4FC, 'M', u's'), - (0x1D4FD, 'M', u't'), - (0x1D4FE, 'M', u'u'), - (0x1D4FF, 'M', u'v'), - (0x1D500, 'M', u'w'), - (0x1D501, 'M', u'x'), - (0x1D502, 'M', u'y'), - (0x1D503, 'M', u'z'), - (0x1D504, 'M', u'a'), - (0x1D505, 'M', u'b'), - (0x1D506, 'X'), - (0x1D507, 'M', u'd'), - (0x1D508, 'M', u'e'), - (0x1D509, 'M', u'f'), - (0x1D50A, 'M', u'g'), - (0x1D50B, 'X'), - (0x1D50D, 'M', u'j'), - (0x1D50E, 'M', u'k'), - (0x1D50F, 'M', u'l'), - (0x1D510, 'M', u'm'), - (0x1D511, 'M', u'n'), - (0x1D512, 'M', u'o'), - (0x1D513, 'M', u'p'), - (0x1D514, 'M', u'q'), - (0x1D515, 'X'), - (0x1D516, 'M', u's'), - (0x1D517, 'M', u't'), - (0x1D518, 'M', u'u'), - (0x1D519, 'M', u'v'), - (0x1D51A, 'M', u'w'), - (0x1D51B, 'M', u'x'), - (0x1D51C, 'M', u'y'), - (0x1D51D, 'X'), - (0x1D51E, 'M', u'a'), - (0x1D51F, 'M', u'b'), - (0x1D520, 'M', u'c'), - (0x1D521, 'M', u'd'), - (0x1D522, 'M', u'e'), - (0x1D523, 'M', u'f'), - (0x1D524, 'M', u'g'), - (0x1D525, 'M', u'h'), - (0x1D526, 'M', u'i'), - (0x1D527, 'M', u'j'), - (0x1D528, 'M', u'k'), + (0x1D452, 'M', 'e'), + (0x1D453, 'M', 'f'), + (0x1D454, 'M', 'g'), + (0x1D455, 'X'), + (0x1D456, 'M', 'i'), + (0x1D457, 'M', 'j'), + (0x1D458, 'M', 'k'), + (0x1D459, 'M', 'l'), + (0x1D45A, 'M', 'm'), + (0x1D45B, 'M', 'n'), + (0x1D45C, 'M', 'o'), + (0x1D45D, 'M', 'p'), + (0x1D45E, 'M', 'q'), + (0x1D45F, 'M', 'r'), + (0x1D460, 'M', 's'), + (0x1D461, 'M', 't'), + (0x1D462, 'M', 'u'), + (0x1D463, 'M', 'v'), + (0x1D464, 'M', 'w'), + (0x1D465, 'M', 'x'), + (0x1D466, 'M', 'y'), + (0x1D467, 'M', 'z'), + (0x1D468, 'M', 'a'), + (0x1D469, 'M', 'b'), + (0x1D46A, 'M', 'c'), + (0x1D46B, 'M', 'd'), + (0x1D46C, 'M', 'e'), + (0x1D46D, 'M', 'f'), + (0x1D46E, 'M', 'g'), + (0x1D46F, 'M', 'h'), + (0x1D470, 'M', 'i'), + (0x1D471, 'M', 'j'), + (0x1D472, 'M', 'k'), + (0x1D473, 'M', 'l'), + (0x1D474, 'M', 'm'), + (0x1D475, 'M', 'n'), + (0x1D476, 'M', 'o'), + (0x1D477, 'M', 'p'), + (0x1D478, 'M', 'q'), + (0x1D479, 'M', 'r'), + (0x1D47A, 'M', 's'), + (0x1D47B, 'M', 't'), + (0x1D47C, 'M', 'u'), + (0x1D47D, 'M', 'v'), + (0x1D47E, 'M', 'w'), + (0x1D47F, 'M', 'x'), + (0x1D480, 'M', 'y'), + (0x1D481, 'M', 'z'), + (0x1D482, 'M', 'a'), + (0x1D483, 'M', 'b'), + (0x1D484, 'M', 'c'), + (0x1D485, 'M', 'd'), + (0x1D486, 'M', 'e'), + (0x1D487, 'M', 'f'), + (0x1D488, 'M', 'g'), + (0x1D489, 'M', 'h'), + (0x1D48A, 'M', 'i'), + (0x1D48B, 'M', 'j'), + (0x1D48C, 'M', 'k'), + (0x1D48D, 'M', 'l'), + (0x1D48E, 'M', 'm'), + (0x1D48F, 'M', 'n'), + (0x1D490, 'M', 'o'), + (0x1D491, 'M', 'p'), + (0x1D492, 'M', 'q'), + (0x1D493, 'M', 'r'), + (0x1D494, 'M', 's'), + (0x1D495, 'M', 't'), + (0x1D496, 'M', 'u'), + (0x1D497, 'M', 'v'), + (0x1D498, 'M', 'w'), + (0x1D499, 'M', 'x'), + (0x1D49A, 'M', 'y'), + (0x1D49B, 'M', 'z'), + (0x1D49C, 'M', 'a'), + (0x1D49D, 'X'), + (0x1D49E, 'M', 'c'), + (0x1D49F, 'M', 'd'), + (0x1D4A0, 'X'), + (0x1D4A2, 'M', 'g'), + (0x1D4A3, 'X'), + (0x1D4A5, 'M', 'j'), + (0x1D4A6, 'M', 'k'), + (0x1D4A7, 'X'), + (0x1D4A9, 'M', 'n'), + (0x1D4AA, 'M', 'o'), + (0x1D4AB, 'M', 'p'), + (0x1D4AC, 'M', 'q'), + (0x1D4AD, 'X'), + (0x1D4AE, 'M', 's'), + (0x1D4AF, 'M', 't'), + (0x1D4B0, 'M', 'u'), + (0x1D4B1, 'M', 'v'), + (0x1D4B2, 'M', 'w'), + (0x1D4B3, 'M', 'x'), + (0x1D4B4, 'M', 'y'), + (0x1D4B5, 'M', 'z'), + (0x1D4B6, 'M', 'a'), + (0x1D4B7, 'M', 'b'), + (0x1D4B8, 'M', 'c'), ] def _seg_61(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x1D529, 'M', u'l'), - (0x1D52A, 'M', u'm'), - (0x1D52B, 'M', u'n'), - (0x1D52C, 'M', u'o'), - (0x1D52D, 'M', u'p'), - (0x1D52E, 'M', u'q'), - (0x1D52F, 'M', u'r'), - (0x1D530, 'M', u's'), - (0x1D531, 'M', u't'), - (0x1D532, 'M', u'u'), - (0x1D533, 'M', u'v'), - (0x1D534, 'M', u'w'), - (0x1D535, 'M', u'x'), - (0x1D536, 'M', u'y'), - (0x1D537, 'M', u'z'), - (0x1D538, 'M', u'a'), - (0x1D539, 'M', u'b'), - (0x1D53A, 'X'), - (0x1D53B, 'M', u'd'), - (0x1D53C, 'M', u'e'), - (0x1D53D, 'M', u'f'), - (0x1D53E, 'M', u'g'), - (0x1D53F, 'X'), - (0x1D540, 'M', u'i'), - (0x1D541, 'M', u'j'), - (0x1D542, 'M', u'k'), - (0x1D543, 'M', u'l'), - (0x1D544, 'M', u'm'), - (0x1D545, 'X'), - (0x1D546, 'M', u'o'), - (0x1D547, 'X'), - (0x1D54A, 'M', u's'), - (0x1D54B, 'M', u't'), - (0x1D54C, 'M', u'u'), - (0x1D54D, 'M', u'v'), - (0x1D54E, 'M', u'w'), - (0x1D54F, 'M', u'x'), - (0x1D550, 'M', u'y'), - (0x1D551, 'X'), - (0x1D552, 'M', u'a'), - (0x1D553, 'M', u'b'), - (0x1D554, 'M', u'c'), - (0x1D555, 'M', u'd'), - (0x1D556, 'M', u'e'), - (0x1D557, 'M', u'f'), - (0x1D558, 'M', u'g'), - (0x1D559, 'M', u'h'), - (0x1D55A, 'M', u'i'), - (0x1D55B, 'M', u'j'), - (0x1D55C, 'M', u'k'), - (0x1D55D, 'M', u'l'), - (0x1D55E, 'M', u'm'), - (0x1D55F, 'M', u'n'), - (0x1D560, 'M', u'o'), - (0x1D561, 'M', u'p'), - (0x1D562, 'M', u'q'), - (0x1D563, 'M', u'r'), - (0x1D564, 'M', u's'), - (0x1D565, 'M', u't'), - (0x1D566, 'M', u'u'), - (0x1D567, 'M', u'v'), - (0x1D568, 'M', u'w'), - (0x1D569, 'M', u'x'), - (0x1D56A, 'M', u'y'), - (0x1D56B, 'M', u'z'), - (0x1D56C, 'M', u'a'), - (0x1D56D, 'M', u'b'), - (0x1D56E, 'M', u'c'), - (0x1D56F, 'M', u'd'), - (0x1D570, 'M', u'e'), - (0x1D571, 'M', u'f'), - (0x1D572, 'M', u'g'), - (0x1D573, 'M', u'h'), - (0x1D574, 'M', u'i'), - (0x1D575, 'M', u'j'), - (0x1D576, 'M', u'k'), - (0x1D577, 'M', u'l'), - (0x1D578, 'M', u'm'), - (0x1D579, 'M', u'n'), - (0x1D57A, 'M', u'o'), - (0x1D57B, 'M', u'p'), - (0x1D57C, 'M', u'q'), - (0x1D57D, 'M', u'r'), - (0x1D57E, 'M', u's'), - (0x1D57F, 'M', u't'), - (0x1D580, 'M', u'u'), - (0x1D581, 'M', u'v'), - (0x1D582, 'M', u'w'), - (0x1D583, 'M', u'x'), - (0x1D584, 'M', u'y'), - (0x1D585, 'M', u'z'), - (0x1D586, 'M', u'a'), - (0x1D587, 'M', u'b'), - (0x1D588, 'M', u'c'), - (0x1D589, 'M', u'd'), - (0x1D58A, 'M', u'e'), - (0x1D58B, 'M', u'f'), - (0x1D58C, 'M', u'g'), - (0x1D58D, 'M', u'h'), - (0x1D58E, 'M', u'i'), + (0x1D4B9, 'M', 'd'), + (0x1D4BA, 'X'), + (0x1D4BB, 'M', 'f'), + (0x1D4BC, 'X'), + (0x1D4BD, 'M', 'h'), + (0x1D4BE, 'M', 'i'), + (0x1D4BF, 'M', 'j'), + (0x1D4C0, 'M', 'k'), + (0x1D4C1, 'M', 'l'), + (0x1D4C2, 'M', 'm'), + (0x1D4C3, 'M', 'n'), + (0x1D4C4, 'X'), + (0x1D4C5, 'M', 'p'), + (0x1D4C6, 'M', 'q'), + (0x1D4C7, 'M', 'r'), + (0x1D4C8, 'M', 's'), + (0x1D4C9, 'M', 't'), + (0x1D4CA, 'M', 'u'), + (0x1D4CB, 'M', 'v'), + (0x1D4CC, 'M', 'w'), + (0x1D4CD, 'M', 'x'), + (0x1D4CE, 'M', 'y'), + (0x1D4CF, 'M', 'z'), + (0x1D4D0, 'M', 'a'), + (0x1D4D1, 'M', 'b'), + (0x1D4D2, 'M', 'c'), + (0x1D4D3, 'M', 'd'), + (0x1D4D4, 'M', 'e'), + (0x1D4D5, 'M', 'f'), + (0x1D4D6, 'M', 'g'), + (0x1D4D7, 'M', 'h'), + (0x1D4D8, 'M', 'i'), + (0x1D4D9, 'M', 'j'), + (0x1D4DA, 'M', 'k'), + (0x1D4DB, 'M', 'l'), + (0x1D4DC, 'M', 'm'), + (0x1D4DD, 'M', 'n'), + (0x1D4DE, 'M', 'o'), + (0x1D4DF, 'M', 'p'), + (0x1D4E0, 'M', 'q'), + (0x1D4E1, 'M', 'r'), + (0x1D4E2, 'M', 's'), + (0x1D4E3, 'M', 't'), + (0x1D4E4, 'M', 'u'), + (0x1D4E5, 'M', 'v'), + (0x1D4E6, 'M', 'w'), + (0x1D4E7, 'M', 'x'), + (0x1D4E8, 'M', 'y'), + (0x1D4E9, 'M', 'z'), + (0x1D4EA, 'M', 'a'), + (0x1D4EB, 'M', 'b'), + (0x1D4EC, 'M', 'c'), + (0x1D4ED, 'M', 'd'), + (0x1D4EE, 'M', 'e'), + (0x1D4EF, 'M', 'f'), + (0x1D4F0, 'M', 'g'), + (0x1D4F1, 'M', 'h'), + (0x1D4F2, 'M', 'i'), + (0x1D4F3, 'M', 'j'), + (0x1D4F4, 'M', 'k'), + (0x1D4F5, 'M', 'l'), + (0x1D4F6, 'M', 'm'), + (0x1D4F7, 'M', 'n'), + (0x1D4F8, 'M', 'o'), + (0x1D4F9, 'M', 'p'), + (0x1D4FA, 'M', 'q'), + (0x1D4FB, 'M', 'r'), + (0x1D4FC, 'M', 's'), + (0x1D4FD, 'M', 't'), + (0x1D4FE, 'M', 'u'), + (0x1D4FF, 'M', 'v'), + (0x1D500, 'M', 'w'), + (0x1D501, 'M', 'x'), + (0x1D502, 'M', 'y'), + (0x1D503, 'M', 'z'), + (0x1D504, 'M', 'a'), + (0x1D505, 'M', 'b'), + (0x1D506, 'X'), + (0x1D507, 'M', 'd'), + (0x1D508, 'M', 'e'), + (0x1D509, 'M', 'f'), + (0x1D50A, 'M', 'g'), + (0x1D50B, 'X'), + (0x1D50D, 'M', 'j'), + (0x1D50E, 'M', 'k'), + (0x1D50F, 'M', 'l'), + (0x1D510, 'M', 'm'), + (0x1D511, 'M', 'n'), + (0x1D512, 'M', 'o'), + (0x1D513, 'M', 'p'), + (0x1D514, 'M', 'q'), + (0x1D515, 'X'), + (0x1D516, 'M', 's'), + (0x1D517, 'M', 't'), + (0x1D518, 'M', 'u'), + (0x1D519, 'M', 'v'), + (0x1D51A, 'M', 'w'), + (0x1D51B, 'M', 'x'), + (0x1D51C, 'M', 'y'), + (0x1D51D, 'X'), ] def _seg_62(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x1D58F, 'M', u'j'), - (0x1D590, 'M', u'k'), - (0x1D591, 'M', u'l'), - (0x1D592, 'M', u'm'), - (0x1D593, 'M', u'n'), - (0x1D594, 'M', u'o'), - (0x1D595, 'M', u'p'), - (0x1D596, 'M', u'q'), - (0x1D597, 'M', u'r'), - (0x1D598, 'M', u's'), - (0x1D599, 'M', u't'), - (0x1D59A, 'M', u'u'), - (0x1D59B, 'M', u'v'), - (0x1D59C, 'M', u'w'), - (0x1D59D, 'M', u'x'), - (0x1D59E, 'M', u'y'), - (0x1D59F, 'M', u'z'), - (0x1D5A0, 'M', u'a'), - (0x1D5A1, 'M', u'b'), - (0x1D5A2, 'M', u'c'), - (0x1D5A3, 'M', u'd'), - (0x1D5A4, 'M', u'e'), - (0x1D5A5, 'M', u'f'), - (0x1D5A6, 'M', u'g'), - (0x1D5A7, 'M', u'h'), - (0x1D5A8, 'M', u'i'), - (0x1D5A9, 'M', u'j'), - (0x1D5AA, 'M', u'k'), - (0x1D5AB, 'M', u'l'), - (0x1D5AC, 'M', u'm'), - (0x1D5AD, 'M', u'n'), - (0x1D5AE, 'M', u'o'), - (0x1D5AF, 'M', u'p'), - (0x1D5B0, 'M', u'q'), - (0x1D5B1, 'M', u'r'), - (0x1D5B2, 'M', u's'), - (0x1D5B3, 'M', u't'), - (0x1D5B4, 'M', u'u'), - (0x1D5B5, 'M', u'v'), - (0x1D5B6, 'M', u'w'), - (0x1D5B7, 'M', u'x'), - (0x1D5B8, 'M', u'y'), - (0x1D5B9, 'M', u'z'), - (0x1D5BA, 'M', u'a'), - (0x1D5BB, 'M', u'b'), - (0x1D5BC, 'M', u'c'), - (0x1D5BD, 'M', u'd'), - (0x1D5BE, 'M', u'e'), - (0x1D5BF, 'M', u'f'), - (0x1D5C0, 'M', u'g'), - (0x1D5C1, 'M', u'h'), - (0x1D5C2, 'M', u'i'), - (0x1D5C3, 'M', u'j'), - (0x1D5C4, 'M', u'k'), - (0x1D5C5, 'M', u'l'), - (0x1D5C6, 'M', u'm'), - (0x1D5C7, 'M', u'n'), - (0x1D5C8, 'M', u'o'), - (0x1D5C9, 'M', u'p'), - (0x1D5CA, 'M', u'q'), - (0x1D5CB, 'M', u'r'), - (0x1D5CC, 'M', u's'), - (0x1D5CD, 'M', u't'), - (0x1D5CE, 'M', u'u'), - (0x1D5CF, 'M', u'v'), - (0x1D5D0, 'M', u'w'), - (0x1D5D1, 'M', u'x'), - (0x1D5D2, 'M', u'y'), - (0x1D5D3, 'M', u'z'), - (0x1D5D4, 'M', u'a'), - (0x1D5D5, 'M', u'b'), - (0x1D5D6, 'M', u'c'), - (0x1D5D7, 'M', u'd'), - (0x1D5D8, 'M', u'e'), - (0x1D5D9, 'M', u'f'), - (0x1D5DA, 'M', u'g'), - (0x1D5DB, 'M', u'h'), - (0x1D5DC, 'M', u'i'), - (0x1D5DD, 'M', u'j'), - (0x1D5DE, 'M', u'k'), - (0x1D5DF, 'M', u'l'), - (0x1D5E0, 'M', u'm'), - (0x1D5E1, 'M', u'n'), - (0x1D5E2, 'M', u'o'), - (0x1D5E3, 'M', u'p'), - (0x1D5E4, 'M', u'q'), - (0x1D5E5, 'M', u'r'), - (0x1D5E6, 'M', u's'), - (0x1D5E7, 'M', u't'), - (0x1D5E8, 'M', u'u'), - (0x1D5E9, 'M', u'v'), - (0x1D5EA, 'M', u'w'), - (0x1D5EB, 'M', u'x'), - (0x1D5EC, 'M', u'y'), - (0x1D5ED, 'M', u'z'), - (0x1D5EE, 'M', u'a'), - (0x1D5EF, 'M', u'b'), - (0x1D5F0, 'M', u'c'), - (0x1D5F1, 'M', u'd'), - (0x1D5F2, 'M', u'e'), + (0x1D51E, 'M', 'a'), + (0x1D51F, 'M', 'b'), + (0x1D520, 'M', 'c'), + (0x1D521, 'M', 'd'), + (0x1D522, 'M', 'e'), + (0x1D523, 'M', 'f'), + (0x1D524, 'M', 'g'), + (0x1D525, 'M', 'h'), + (0x1D526, 'M', 'i'), + (0x1D527, 'M', 'j'), + (0x1D528, 'M', 'k'), + (0x1D529, 'M', 'l'), + (0x1D52A, 'M', 'm'), + (0x1D52B, 'M', 'n'), + (0x1D52C, 'M', 'o'), + (0x1D52D, 'M', 'p'), + (0x1D52E, 'M', 'q'), + (0x1D52F, 'M', 'r'), + (0x1D530, 'M', 's'), + (0x1D531, 'M', 't'), + (0x1D532, 'M', 'u'), + (0x1D533, 'M', 'v'), + (0x1D534, 'M', 'w'), + (0x1D535, 'M', 'x'), + (0x1D536, 'M', 'y'), + (0x1D537, 'M', 'z'), + (0x1D538, 'M', 'a'), + (0x1D539, 'M', 'b'), + (0x1D53A, 'X'), + (0x1D53B, 'M', 'd'), + (0x1D53C, 'M', 'e'), + (0x1D53D, 'M', 'f'), + (0x1D53E, 'M', 'g'), + (0x1D53F, 'X'), + (0x1D540, 'M', 'i'), + (0x1D541, 'M', 'j'), + (0x1D542, 'M', 'k'), + (0x1D543, 'M', 'l'), + (0x1D544, 'M', 'm'), + (0x1D545, 'X'), + (0x1D546, 'M', 'o'), + (0x1D547, 'X'), + (0x1D54A, 'M', 's'), + (0x1D54B, 'M', 't'), + (0x1D54C, 'M', 'u'), + (0x1D54D, 'M', 'v'), + (0x1D54E, 'M', 'w'), + (0x1D54F, 'M', 'x'), + (0x1D550, 'M', 'y'), + (0x1D551, 'X'), + (0x1D552, 'M', 'a'), + (0x1D553, 'M', 'b'), + (0x1D554, 'M', 'c'), + (0x1D555, 'M', 'd'), + (0x1D556, 'M', 'e'), + (0x1D557, 'M', 'f'), + (0x1D558, 'M', 'g'), + (0x1D559, 'M', 'h'), + (0x1D55A, 'M', 'i'), + (0x1D55B, 'M', 'j'), + (0x1D55C, 'M', 'k'), + (0x1D55D, 'M', 'l'), + (0x1D55E, 'M', 'm'), + (0x1D55F, 'M', 'n'), + (0x1D560, 'M', 'o'), + (0x1D561, 'M', 'p'), + (0x1D562, 'M', 'q'), + (0x1D563, 'M', 'r'), + (0x1D564, 'M', 's'), + (0x1D565, 'M', 't'), + (0x1D566, 'M', 'u'), + (0x1D567, 'M', 'v'), + (0x1D568, 'M', 'w'), + (0x1D569, 'M', 'x'), + (0x1D56A, 'M', 'y'), + (0x1D56B, 'M', 'z'), + (0x1D56C, 'M', 'a'), + (0x1D56D, 'M', 'b'), + (0x1D56E, 'M', 'c'), + (0x1D56F, 'M', 'd'), + (0x1D570, 'M', 'e'), + (0x1D571, 'M', 'f'), + (0x1D572, 'M', 'g'), + (0x1D573, 'M', 'h'), + (0x1D574, 'M', 'i'), + (0x1D575, 'M', 'j'), + (0x1D576, 'M', 'k'), + (0x1D577, 'M', 'l'), + (0x1D578, 'M', 'm'), + (0x1D579, 'M', 'n'), + (0x1D57A, 'M', 'o'), + (0x1D57B, 'M', 'p'), + (0x1D57C, 'M', 'q'), + (0x1D57D, 'M', 'r'), + (0x1D57E, 'M', 's'), + (0x1D57F, 'M', 't'), + (0x1D580, 'M', 'u'), + (0x1D581, 'M', 'v'), + (0x1D582, 'M', 'w'), + (0x1D583, 'M', 'x'), ] def _seg_63(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x1D5F3, 'M', u'f'), - (0x1D5F4, 'M', u'g'), - (0x1D5F5, 'M', u'h'), - (0x1D5F6, 'M', u'i'), - (0x1D5F7, 'M', u'j'), - (0x1D5F8, 'M', u'k'), - (0x1D5F9, 'M', u'l'), - (0x1D5FA, 'M', u'm'), - (0x1D5FB, 'M', u'n'), - (0x1D5FC, 'M', u'o'), - (0x1D5FD, 'M', u'p'), - (0x1D5FE, 'M', u'q'), - (0x1D5FF, 'M', u'r'), - (0x1D600, 'M', u's'), - (0x1D601, 'M', u't'), - (0x1D602, 'M', u'u'), - (0x1D603, 'M', u'v'), - (0x1D604, 'M', u'w'), - (0x1D605, 'M', u'x'), - (0x1D606, 'M', u'y'), - (0x1D607, 'M', u'z'), - (0x1D608, 'M', u'a'), - (0x1D609, 'M', u'b'), - (0x1D60A, 'M', u'c'), - (0x1D60B, 'M', u'd'), - (0x1D60C, 'M', u'e'), - (0x1D60D, 'M', u'f'), - (0x1D60E, 'M', u'g'), - (0x1D60F, 'M', u'h'), - (0x1D610, 'M', u'i'), - (0x1D611, 'M', u'j'), - (0x1D612, 'M', u'k'), - (0x1D613, 'M', u'l'), - (0x1D614, 'M', u'm'), - (0x1D615, 'M', u'n'), - (0x1D616, 'M', u'o'), - (0x1D617, 'M', u'p'), - (0x1D618, 'M', u'q'), - (0x1D619, 'M', u'r'), - (0x1D61A, 'M', u's'), - (0x1D61B, 'M', u't'), - (0x1D61C, 'M', u'u'), - (0x1D61D, 'M', u'v'), - (0x1D61E, 'M', u'w'), - (0x1D61F, 'M', u'x'), - (0x1D620, 'M', u'y'), - (0x1D621, 'M', u'z'), - (0x1D622, 'M', u'a'), - (0x1D623, 'M', u'b'), - (0x1D624, 'M', u'c'), - (0x1D625, 'M', u'd'), - (0x1D626, 'M', u'e'), - (0x1D627, 'M', u'f'), - (0x1D628, 'M', u'g'), - (0x1D629, 'M', u'h'), - (0x1D62A, 'M', u'i'), - (0x1D62B, 'M', u'j'), - (0x1D62C, 'M', u'k'), - (0x1D62D, 'M', u'l'), - (0x1D62E, 'M', u'm'), - (0x1D62F, 'M', u'n'), - (0x1D630, 'M', u'o'), - (0x1D631, 'M', u'p'), - (0x1D632, 'M', u'q'), - (0x1D633, 'M', u'r'), - (0x1D634, 'M', u's'), - (0x1D635, 'M', u't'), - (0x1D636, 'M', u'u'), - (0x1D637, 'M', u'v'), - (0x1D638, 'M', u'w'), - (0x1D639, 'M', u'x'), - (0x1D63A, 'M', u'y'), - (0x1D63B, 'M', u'z'), - (0x1D63C, 'M', u'a'), - (0x1D63D, 'M', u'b'), - (0x1D63E, 'M', u'c'), - (0x1D63F, 'M', u'd'), - (0x1D640, 'M', u'e'), - (0x1D641, 'M', u'f'), - (0x1D642, 'M', u'g'), - (0x1D643, 'M', u'h'), - (0x1D644, 'M', u'i'), - (0x1D645, 'M', u'j'), - (0x1D646, 'M', u'k'), - (0x1D647, 'M', u'l'), - (0x1D648, 'M', u'm'), - (0x1D649, 'M', u'n'), - (0x1D64A, 'M', u'o'), - (0x1D64B, 'M', u'p'), - (0x1D64C, 'M', u'q'), - (0x1D64D, 'M', u'r'), - (0x1D64E, 'M', u's'), - (0x1D64F, 'M', u't'), - (0x1D650, 'M', u'u'), - (0x1D651, 'M', u'v'), - (0x1D652, 'M', u'w'), - (0x1D653, 'M', u'x'), - (0x1D654, 'M', u'y'), - (0x1D655, 'M', u'z'), - (0x1D656, 'M', u'a'), + (0x1D584, 'M', 'y'), + (0x1D585, 'M', 'z'), + (0x1D586, 'M', 'a'), + (0x1D587, 'M', 'b'), + (0x1D588, 'M', 'c'), + (0x1D589, 'M', 'd'), + (0x1D58A, 'M', 'e'), + (0x1D58B, 'M', 'f'), + (0x1D58C, 'M', 'g'), + (0x1D58D, 'M', 'h'), + (0x1D58E, 'M', 'i'), + (0x1D58F, 'M', 'j'), + (0x1D590, 'M', 'k'), + (0x1D591, 'M', 'l'), + (0x1D592, 'M', 'm'), + (0x1D593, 'M', 'n'), + (0x1D594, 'M', 'o'), + (0x1D595, 'M', 'p'), + (0x1D596, 'M', 'q'), + (0x1D597, 'M', 'r'), + (0x1D598, 'M', 's'), + (0x1D599, 'M', 't'), + (0x1D59A, 'M', 'u'), + (0x1D59B, 'M', 'v'), + (0x1D59C, 'M', 'w'), + (0x1D59D, 'M', 'x'), + (0x1D59E, 'M', 'y'), + (0x1D59F, 'M', 'z'), + (0x1D5A0, 'M', 'a'), + (0x1D5A1, 'M', 'b'), + (0x1D5A2, 'M', 'c'), + (0x1D5A3, 'M', 'd'), + (0x1D5A4, 'M', 'e'), + (0x1D5A5, 'M', 'f'), + (0x1D5A6, 'M', 'g'), + (0x1D5A7, 'M', 'h'), + (0x1D5A8, 'M', 'i'), + (0x1D5A9, 'M', 'j'), + (0x1D5AA, 'M', 'k'), + (0x1D5AB, 'M', 'l'), + (0x1D5AC, 'M', 'm'), + (0x1D5AD, 'M', 'n'), + (0x1D5AE, 'M', 'o'), + (0x1D5AF, 'M', 'p'), + (0x1D5B0, 'M', 'q'), + (0x1D5B1, 'M', 'r'), + (0x1D5B2, 'M', 's'), + (0x1D5B3, 'M', 't'), + (0x1D5B4, 'M', 'u'), + (0x1D5B5, 'M', 'v'), + (0x1D5B6, 'M', 'w'), + (0x1D5B7, 'M', 'x'), + (0x1D5B8, 'M', 'y'), + (0x1D5B9, 'M', 'z'), + (0x1D5BA, 'M', 'a'), + (0x1D5BB, 'M', 'b'), + (0x1D5BC, 'M', 'c'), + (0x1D5BD, 'M', 'd'), + (0x1D5BE, 'M', 'e'), + (0x1D5BF, 'M', 'f'), + (0x1D5C0, 'M', 'g'), + (0x1D5C1, 'M', 'h'), + (0x1D5C2, 'M', 'i'), + (0x1D5C3, 'M', 'j'), + (0x1D5C4, 'M', 'k'), + (0x1D5C5, 'M', 'l'), + (0x1D5C6, 'M', 'm'), + (0x1D5C7, 'M', 'n'), + (0x1D5C8, 'M', 'o'), + (0x1D5C9, 'M', 'p'), + (0x1D5CA, 'M', 'q'), + (0x1D5CB, 'M', 'r'), + (0x1D5CC, 'M', 's'), + (0x1D5CD, 'M', 't'), + (0x1D5CE, 'M', 'u'), + (0x1D5CF, 'M', 'v'), + (0x1D5D0, 'M', 'w'), + (0x1D5D1, 'M', 'x'), + (0x1D5D2, 'M', 'y'), + (0x1D5D3, 'M', 'z'), + (0x1D5D4, 'M', 'a'), + (0x1D5D5, 'M', 'b'), + (0x1D5D6, 'M', 'c'), + (0x1D5D7, 'M', 'd'), + (0x1D5D8, 'M', 'e'), + (0x1D5D9, 'M', 'f'), + (0x1D5DA, 'M', 'g'), + (0x1D5DB, 'M', 'h'), + (0x1D5DC, 'M', 'i'), + (0x1D5DD, 'M', 'j'), + (0x1D5DE, 'M', 'k'), + (0x1D5DF, 'M', 'l'), + (0x1D5E0, 'M', 'm'), + (0x1D5E1, 'M', 'n'), + (0x1D5E2, 'M', 'o'), + (0x1D5E3, 'M', 'p'), + (0x1D5E4, 'M', 'q'), + (0x1D5E5, 'M', 'r'), + (0x1D5E6, 'M', 's'), + (0x1D5E7, 'M', 't'), ] def _seg_64(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x1D657, 'M', u'b'), - (0x1D658, 'M', u'c'), - (0x1D659, 'M', u'd'), - (0x1D65A, 'M', u'e'), - (0x1D65B, 'M', u'f'), - (0x1D65C, 'M', u'g'), - (0x1D65D, 'M', u'h'), - (0x1D65E, 'M', u'i'), - (0x1D65F, 'M', u'j'), - (0x1D660, 'M', u'k'), - (0x1D661, 'M', u'l'), - (0x1D662, 'M', u'm'), - (0x1D663, 'M', u'n'), - (0x1D664, 'M', u'o'), - (0x1D665, 'M', u'p'), - (0x1D666, 'M', u'q'), - (0x1D667, 'M', u'r'), - (0x1D668, 'M', u's'), - (0x1D669, 'M', u't'), - (0x1D66A, 'M', u'u'), - (0x1D66B, 'M', u'v'), - (0x1D66C, 'M', u'w'), - (0x1D66D, 'M', u'x'), - (0x1D66E, 'M', u'y'), - (0x1D66F, 'M', u'z'), - (0x1D670, 'M', u'a'), - (0x1D671, 'M', u'b'), - (0x1D672, 'M', u'c'), - (0x1D673, 'M', u'd'), - (0x1D674, 'M', u'e'), - (0x1D675, 'M', u'f'), - (0x1D676, 'M', u'g'), - (0x1D677, 'M', u'h'), - (0x1D678, 'M', u'i'), - (0x1D679, 'M', u'j'), - (0x1D67A, 'M', u'k'), - (0x1D67B, 'M', u'l'), - (0x1D67C, 'M', u'm'), - (0x1D67D, 'M', u'n'), - (0x1D67E, 'M', u'o'), - (0x1D67F, 'M', u'p'), - (0x1D680, 'M', u'q'), - (0x1D681, 'M', u'r'), - (0x1D682, 'M', u's'), - (0x1D683, 'M', u't'), - (0x1D684, 'M', u'u'), - (0x1D685, 'M', u'v'), - (0x1D686, 'M', u'w'), - (0x1D687, 'M', u'x'), - (0x1D688, 'M', u'y'), - (0x1D689, 'M', u'z'), - (0x1D68A, 'M', u'a'), - (0x1D68B, 'M', u'b'), - (0x1D68C, 'M', u'c'), - (0x1D68D, 'M', u'd'), - (0x1D68E, 'M', u'e'), - (0x1D68F, 'M', u'f'), - (0x1D690, 'M', u'g'), - (0x1D691, 'M', u'h'), - (0x1D692, 'M', u'i'), - (0x1D693, 'M', u'j'), - (0x1D694, 'M', u'k'), - (0x1D695, 'M', u'l'), - (0x1D696, 'M', u'm'), - (0x1D697, 'M', u'n'), - (0x1D698, 'M', u'o'), - (0x1D699, 'M', u'p'), - (0x1D69A, 'M', u'q'), - (0x1D69B, 'M', u'r'), - (0x1D69C, 'M', u's'), - (0x1D69D, 'M', u't'), - (0x1D69E, 'M', u'u'), - (0x1D69F, 'M', u'v'), - (0x1D6A0, 'M', u'w'), - (0x1D6A1, 'M', u'x'), - (0x1D6A2, 'M', u'y'), - (0x1D6A3, 'M', u'z'), - (0x1D6A4, 'M', u'ı'), - (0x1D6A5, 'M', u'ȷ'), - (0x1D6A6, 'X'), - (0x1D6A8, 'M', u'α'), - (0x1D6A9, 'M', u'β'), - (0x1D6AA, 'M', u'γ'), - (0x1D6AB, 'M', u'δ'), - (0x1D6AC, 'M', u'ε'), - (0x1D6AD, 'M', u'ζ'), - (0x1D6AE, 'M', u'η'), - (0x1D6AF, 'M', u'θ'), - (0x1D6B0, 'M', u'ι'), - (0x1D6B1, 'M', u'κ'), - (0x1D6B2, 'M', u'λ'), - (0x1D6B3, 'M', u'μ'), - (0x1D6B4, 'M', u'ν'), - (0x1D6B5, 'M', u'ξ'), - (0x1D6B6, 'M', u'ο'), - (0x1D6B7, 'M', u'π'), - (0x1D6B8, 'M', u'ρ'), - (0x1D6B9, 'M', u'θ'), - (0x1D6BA, 'M', u'σ'), - (0x1D6BB, 'M', u'τ'), + (0x1D5E8, 'M', 'u'), + (0x1D5E9, 'M', 'v'), + (0x1D5EA, 'M', 'w'), + (0x1D5EB, 'M', 'x'), + (0x1D5EC, 'M', 'y'), + (0x1D5ED, 'M', 'z'), + (0x1D5EE, 'M', 'a'), + (0x1D5EF, 'M', 'b'), + (0x1D5F0, 'M', 'c'), + (0x1D5F1, 'M', 'd'), + (0x1D5F2, 'M', 'e'), + (0x1D5F3, 'M', 'f'), + (0x1D5F4, 'M', 'g'), + (0x1D5F5, 'M', 'h'), + (0x1D5F6, 'M', 'i'), + (0x1D5F7, 'M', 'j'), + (0x1D5F8, 'M', 'k'), + (0x1D5F9, 'M', 'l'), + (0x1D5FA, 'M', 'm'), + (0x1D5FB, 'M', 'n'), + (0x1D5FC, 'M', 'o'), + (0x1D5FD, 'M', 'p'), + (0x1D5FE, 'M', 'q'), + (0x1D5FF, 'M', 'r'), + (0x1D600, 'M', 's'), + (0x1D601, 'M', 't'), + (0x1D602, 'M', 'u'), + (0x1D603, 'M', 'v'), + (0x1D604, 'M', 'w'), + (0x1D605, 'M', 'x'), + (0x1D606, 'M', 'y'), + (0x1D607, 'M', 'z'), + (0x1D608, 'M', 'a'), + (0x1D609, 'M', 'b'), + (0x1D60A, 'M', 'c'), + (0x1D60B, 'M', 'd'), + (0x1D60C, 'M', 'e'), + (0x1D60D, 'M', 'f'), + (0x1D60E, 'M', 'g'), + (0x1D60F, 'M', 'h'), + (0x1D610, 'M', 'i'), + (0x1D611, 'M', 'j'), + (0x1D612, 'M', 'k'), + (0x1D613, 'M', 'l'), + (0x1D614, 'M', 'm'), + (0x1D615, 'M', 'n'), + (0x1D616, 'M', 'o'), + (0x1D617, 'M', 'p'), + (0x1D618, 'M', 'q'), + (0x1D619, 'M', 'r'), + (0x1D61A, 'M', 's'), + (0x1D61B, 'M', 't'), + (0x1D61C, 'M', 'u'), + (0x1D61D, 'M', 'v'), + (0x1D61E, 'M', 'w'), + (0x1D61F, 'M', 'x'), + (0x1D620, 'M', 'y'), + (0x1D621, 'M', 'z'), + (0x1D622, 'M', 'a'), + (0x1D623, 'M', 'b'), + (0x1D624, 'M', 'c'), + (0x1D625, 'M', 'd'), + (0x1D626, 'M', 'e'), + (0x1D627, 'M', 'f'), + (0x1D628, 'M', 'g'), + (0x1D629, 'M', 'h'), + (0x1D62A, 'M', 'i'), + (0x1D62B, 'M', 'j'), + (0x1D62C, 'M', 'k'), + (0x1D62D, 'M', 'l'), + (0x1D62E, 'M', 'm'), + (0x1D62F, 'M', 'n'), + (0x1D630, 'M', 'o'), + (0x1D631, 'M', 'p'), + (0x1D632, 'M', 'q'), + (0x1D633, 'M', 'r'), + (0x1D634, 'M', 's'), + (0x1D635, 'M', 't'), + (0x1D636, 'M', 'u'), + (0x1D637, 'M', 'v'), + (0x1D638, 'M', 'w'), + (0x1D639, 'M', 'x'), + (0x1D63A, 'M', 'y'), + (0x1D63B, 'M', 'z'), + (0x1D63C, 'M', 'a'), + (0x1D63D, 'M', 'b'), + (0x1D63E, 'M', 'c'), + (0x1D63F, 'M', 'd'), + (0x1D640, 'M', 'e'), + (0x1D641, 'M', 'f'), + (0x1D642, 'M', 'g'), + (0x1D643, 'M', 'h'), + (0x1D644, 'M', 'i'), + (0x1D645, 'M', 'j'), + (0x1D646, 'M', 'k'), + (0x1D647, 'M', 'l'), + (0x1D648, 'M', 'm'), + (0x1D649, 'M', 'n'), + (0x1D64A, 'M', 'o'), + (0x1D64B, 'M', 'p'), ] def _seg_65(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x1D6BC, 'M', u'υ'), - (0x1D6BD, 'M', u'φ'), - (0x1D6BE, 'M', u'χ'), - (0x1D6BF, 'M', u'ψ'), - (0x1D6C0, 'M', u'ω'), - (0x1D6C1, 'M', u'∇'), - (0x1D6C2, 'M', u'α'), - (0x1D6C3, 'M', u'β'), - (0x1D6C4, 'M', u'γ'), - (0x1D6C5, 'M', u'δ'), - (0x1D6C6, 'M', u'ε'), - (0x1D6C7, 'M', u'ζ'), - (0x1D6C8, 'M', u'η'), - (0x1D6C9, 'M', u'θ'), - (0x1D6CA, 'M', u'ι'), - (0x1D6CB, 'M', u'κ'), - (0x1D6CC, 'M', u'λ'), - (0x1D6CD, 'M', u'μ'), - (0x1D6CE, 'M', u'ν'), - (0x1D6CF, 'M', u'ξ'), - (0x1D6D0, 'M', u'ο'), - (0x1D6D1, 'M', u'π'), - (0x1D6D2, 'M', u'ρ'), - (0x1D6D3, 'M', u'σ'), - (0x1D6D5, 'M', u'τ'), - (0x1D6D6, 'M', u'υ'), - (0x1D6D7, 'M', u'φ'), - (0x1D6D8, 'M', u'χ'), - (0x1D6D9, 'M', u'ψ'), - (0x1D6DA, 'M', u'ω'), - (0x1D6DB, 'M', u'∂'), - (0x1D6DC, 'M', u'ε'), - (0x1D6DD, 'M', u'θ'), - (0x1D6DE, 'M', u'κ'), - (0x1D6DF, 'M', u'φ'), - (0x1D6E0, 'M', u'ρ'), - (0x1D6E1, 'M', u'π'), - (0x1D6E2, 'M', u'α'), - (0x1D6E3, 'M', u'β'), - (0x1D6E4, 'M', u'γ'), - (0x1D6E5, 'M', u'δ'), - (0x1D6E6, 'M', u'ε'), - (0x1D6E7, 'M', u'ζ'), - (0x1D6E8, 'M', u'η'), - (0x1D6E9, 'M', u'θ'), - (0x1D6EA, 'M', u'ι'), - (0x1D6EB, 'M', u'κ'), - (0x1D6EC, 'M', u'λ'), - (0x1D6ED, 'M', u'μ'), - (0x1D6EE, 'M', u'ν'), - (0x1D6EF, 'M', u'ξ'), - (0x1D6F0, 'M', u'ο'), - (0x1D6F1, 'M', u'π'), - (0x1D6F2, 'M', u'ρ'), - (0x1D6F3, 'M', u'θ'), - (0x1D6F4, 'M', u'σ'), - (0x1D6F5, 'M', u'τ'), - (0x1D6F6, 'M', u'υ'), - (0x1D6F7, 'M', u'φ'), - (0x1D6F8, 'M', u'χ'), - (0x1D6F9, 'M', u'ψ'), - (0x1D6FA, 'M', u'ω'), - (0x1D6FB, 'M', u'∇'), - (0x1D6FC, 'M', u'α'), - (0x1D6FD, 'M', u'β'), - (0x1D6FE, 'M', u'γ'), - (0x1D6FF, 'M', u'δ'), - (0x1D700, 'M', u'ε'), - (0x1D701, 'M', u'ζ'), - (0x1D702, 'M', u'η'), - (0x1D703, 'M', u'θ'), - (0x1D704, 'M', u'ι'), - (0x1D705, 'M', u'κ'), - (0x1D706, 'M', u'λ'), - (0x1D707, 'M', u'μ'), - (0x1D708, 'M', u'ν'), - (0x1D709, 'M', u'ξ'), - (0x1D70A, 'M', u'ο'), - (0x1D70B, 'M', u'π'), - (0x1D70C, 'M', u'ρ'), - (0x1D70D, 'M', u'σ'), - (0x1D70F, 'M', u'τ'), - (0x1D710, 'M', u'υ'), - (0x1D711, 'M', u'φ'), - (0x1D712, 'M', u'χ'), - (0x1D713, 'M', u'ψ'), - (0x1D714, 'M', u'ω'), - (0x1D715, 'M', u'∂'), - (0x1D716, 'M', u'ε'), - (0x1D717, 'M', u'θ'), - (0x1D718, 'M', u'κ'), - (0x1D719, 'M', u'φ'), - (0x1D71A, 'M', u'ρ'), - (0x1D71B, 'M', u'π'), - (0x1D71C, 'M', u'α'), - (0x1D71D, 'M', u'β'), - (0x1D71E, 'M', u'γ'), - (0x1D71F, 'M', u'δ'), - (0x1D720, 'M', u'ε'), - (0x1D721, 'M', u'ζ'), + (0x1D64C, 'M', 'q'), + (0x1D64D, 'M', 'r'), + (0x1D64E, 'M', 's'), + (0x1D64F, 'M', 't'), + (0x1D650, 'M', 'u'), + (0x1D651, 'M', 'v'), + (0x1D652, 'M', 'w'), + (0x1D653, 'M', 'x'), + (0x1D654, 'M', 'y'), + (0x1D655, 'M', 'z'), + (0x1D656, 'M', 'a'), + (0x1D657, 'M', 'b'), + (0x1D658, 'M', 'c'), + (0x1D659, 'M', 'd'), + (0x1D65A, 'M', 'e'), + (0x1D65B, 'M', 'f'), + (0x1D65C, 'M', 'g'), + (0x1D65D, 'M', 'h'), + (0x1D65E, 'M', 'i'), + (0x1D65F, 'M', 'j'), + (0x1D660, 'M', 'k'), + (0x1D661, 'M', 'l'), + (0x1D662, 'M', 'm'), + (0x1D663, 'M', 'n'), + (0x1D664, 'M', 'o'), + (0x1D665, 'M', 'p'), + (0x1D666, 'M', 'q'), + (0x1D667, 'M', 'r'), + (0x1D668, 'M', 's'), + (0x1D669, 'M', 't'), + (0x1D66A, 'M', 'u'), + (0x1D66B, 'M', 'v'), + (0x1D66C, 'M', 'w'), + (0x1D66D, 'M', 'x'), + (0x1D66E, 'M', 'y'), + (0x1D66F, 'M', 'z'), + (0x1D670, 'M', 'a'), + (0x1D671, 'M', 'b'), + (0x1D672, 'M', 'c'), + (0x1D673, 'M', 'd'), + (0x1D674, 'M', 'e'), + (0x1D675, 'M', 'f'), + (0x1D676, 'M', 'g'), + (0x1D677, 'M', 'h'), + (0x1D678, 'M', 'i'), + (0x1D679, 'M', 'j'), + (0x1D67A, 'M', 'k'), + (0x1D67B, 'M', 'l'), + (0x1D67C, 'M', 'm'), + (0x1D67D, 'M', 'n'), + (0x1D67E, 'M', 'o'), + (0x1D67F, 'M', 'p'), + (0x1D680, 'M', 'q'), + (0x1D681, 'M', 'r'), + (0x1D682, 'M', 's'), + (0x1D683, 'M', 't'), + (0x1D684, 'M', 'u'), + (0x1D685, 'M', 'v'), + (0x1D686, 'M', 'w'), + (0x1D687, 'M', 'x'), + (0x1D688, 'M', 'y'), + (0x1D689, 'M', 'z'), + (0x1D68A, 'M', 'a'), + (0x1D68B, 'M', 'b'), + (0x1D68C, 'M', 'c'), + (0x1D68D, 'M', 'd'), + (0x1D68E, 'M', 'e'), + (0x1D68F, 'M', 'f'), + (0x1D690, 'M', 'g'), + (0x1D691, 'M', 'h'), + (0x1D692, 'M', 'i'), + (0x1D693, 'M', 'j'), + (0x1D694, 'M', 'k'), + (0x1D695, 'M', 'l'), + (0x1D696, 'M', 'm'), + (0x1D697, 'M', 'n'), + (0x1D698, 'M', 'o'), + (0x1D699, 'M', 'p'), + (0x1D69A, 'M', 'q'), + (0x1D69B, 'M', 'r'), + (0x1D69C, 'M', 's'), + (0x1D69D, 'M', 't'), + (0x1D69E, 'M', 'u'), + (0x1D69F, 'M', 'v'), + (0x1D6A0, 'M', 'w'), + (0x1D6A1, 'M', 'x'), + (0x1D6A2, 'M', 'y'), + (0x1D6A3, 'M', 'z'), + (0x1D6A4, 'M', 'ı'), + (0x1D6A5, 'M', 'ȷ'), + (0x1D6A6, 'X'), + (0x1D6A8, 'M', 'α'), + (0x1D6A9, 'M', 'β'), + (0x1D6AA, 'M', 'γ'), + (0x1D6AB, 'M', 'δ'), + (0x1D6AC, 'M', 'ε'), + (0x1D6AD, 'M', 'ζ'), + (0x1D6AE, 'M', 'η'), + (0x1D6AF, 'M', 'θ'), + (0x1D6B0, 'M', 'ι'), ] def _seg_66(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x1D722, 'M', u'η'), - (0x1D723, 'M', u'θ'), - (0x1D724, 'M', u'ι'), - (0x1D725, 'M', u'κ'), - (0x1D726, 'M', u'λ'), - (0x1D727, 'M', u'μ'), - (0x1D728, 'M', u'ν'), - (0x1D729, 'M', u'ξ'), - (0x1D72A, 'M', u'ο'), - (0x1D72B, 'M', u'π'), - (0x1D72C, 'M', u'ρ'), - (0x1D72D, 'M', u'θ'), - (0x1D72E, 'M', u'σ'), - (0x1D72F, 'M', u'τ'), - (0x1D730, 'M', u'υ'), - (0x1D731, 'M', u'φ'), - (0x1D732, 'M', u'χ'), - (0x1D733, 'M', u'ψ'), - (0x1D734, 'M', u'ω'), - (0x1D735, 'M', u'∇'), - (0x1D736, 'M', u'α'), - (0x1D737, 'M', u'β'), - (0x1D738, 'M', u'γ'), - (0x1D739, 'M', u'δ'), - (0x1D73A, 'M', u'ε'), - (0x1D73B, 'M', u'ζ'), - (0x1D73C, 'M', u'η'), - (0x1D73D, 'M', u'θ'), - (0x1D73E, 'M', u'ι'), - (0x1D73F, 'M', u'κ'), - (0x1D740, 'M', u'λ'), - (0x1D741, 'M', u'μ'), - (0x1D742, 'M', u'ν'), - (0x1D743, 'M', u'ξ'), - (0x1D744, 'M', u'ο'), - (0x1D745, 'M', u'π'), - (0x1D746, 'M', u'ρ'), - (0x1D747, 'M', u'σ'), - (0x1D749, 'M', u'τ'), - (0x1D74A, 'M', u'υ'), - (0x1D74B, 'M', u'φ'), - (0x1D74C, 'M', u'χ'), - (0x1D74D, 'M', u'ψ'), - (0x1D74E, 'M', u'ω'), - (0x1D74F, 'M', u'∂'), - (0x1D750, 'M', u'ε'), - (0x1D751, 'M', u'θ'), - (0x1D752, 'M', u'κ'), - (0x1D753, 'M', u'φ'), - (0x1D754, 'M', u'ρ'), - (0x1D755, 'M', u'π'), - (0x1D756, 'M', u'α'), - (0x1D757, 'M', u'β'), - (0x1D758, 'M', u'γ'), - (0x1D759, 'M', u'δ'), - (0x1D75A, 'M', u'ε'), - (0x1D75B, 'M', u'ζ'), - (0x1D75C, 'M', u'η'), - (0x1D75D, 'M', u'θ'), - (0x1D75E, 'M', u'ι'), - (0x1D75F, 'M', u'κ'), - (0x1D760, 'M', u'λ'), - (0x1D761, 'M', u'μ'), - (0x1D762, 'M', u'ν'), - (0x1D763, 'M', u'ξ'), - (0x1D764, 'M', u'ο'), - (0x1D765, 'M', u'π'), - (0x1D766, 'M', u'ρ'), - (0x1D767, 'M', u'θ'), - (0x1D768, 'M', u'σ'), - (0x1D769, 'M', u'τ'), - (0x1D76A, 'M', u'υ'), - (0x1D76B, 'M', u'φ'), - (0x1D76C, 'M', u'χ'), - (0x1D76D, 'M', u'ψ'), - (0x1D76E, 'M', u'ω'), - (0x1D76F, 'M', u'∇'), - (0x1D770, 'M', u'α'), - (0x1D771, 'M', u'β'), - (0x1D772, 'M', u'γ'), - (0x1D773, 'M', u'δ'), - (0x1D774, 'M', u'ε'), - (0x1D775, 'M', u'ζ'), - (0x1D776, 'M', u'η'), - (0x1D777, 'M', u'θ'), - (0x1D778, 'M', u'ι'), - (0x1D779, 'M', u'κ'), - (0x1D77A, 'M', u'λ'), - (0x1D77B, 'M', u'μ'), - (0x1D77C, 'M', u'ν'), - (0x1D77D, 'M', u'ξ'), - (0x1D77E, 'M', u'ο'), - (0x1D77F, 'M', u'π'), - (0x1D780, 'M', u'ρ'), - (0x1D781, 'M', u'σ'), - (0x1D783, 'M', u'τ'), - (0x1D784, 'M', u'υ'), - (0x1D785, 'M', u'φ'), - (0x1D786, 'M', u'χ'), - (0x1D787, 'M', u'ψ'), + (0x1D6B1, 'M', 'κ'), + (0x1D6B2, 'M', 'λ'), + (0x1D6B3, 'M', 'μ'), + (0x1D6B4, 'M', 'ν'), + (0x1D6B5, 'M', 'ξ'), + (0x1D6B6, 'M', 'ο'), + (0x1D6B7, 'M', 'π'), + (0x1D6B8, 'M', 'ρ'), + (0x1D6B9, 'M', 'θ'), + (0x1D6BA, 'M', 'σ'), + (0x1D6BB, 'M', 'τ'), + (0x1D6BC, 'M', 'υ'), + (0x1D6BD, 'M', 'φ'), + (0x1D6BE, 'M', 'χ'), + (0x1D6BF, 'M', 'ψ'), + (0x1D6C0, 'M', 'ω'), + (0x1D6C1, 'M', '∇'), + (0x1D6C2, 'M', 'α'), + (0x1D6C3, 'M', 'β'), + (0x1D6C4, 'M', 'γ'), + (0x1D6C5, 'M', 'δ'), + (0x1D6C6, 'M', 'ε'), + (0x1D6C7, 'M', 'ζ'), + (0x1D6C8, 'M', 'η'), + (0x1D6C9, 'M', 'θ'), + (0x1D6CA, 'M', 'ι'), + (0x1D6CB, 'M', 'κ'), + (0x1D6CC, 'M', 'λ'), + (0x1D6CD, 'M', 'μ'), + (0x1D6CE, 'M', 'ν'), + (0x1D6CF, 'M', 'ξ'), + (0x1D6D0, 'M', 'ο'), + (0x1D6D1, 'M', 'π'), + (0x1D6D2, 'M', 'ρ'), + (0x1D6D3, 'M', 'σ'), + (0x1D6D5, 'M', 'τ'), + (0x1D6D6, 'M', 'υ'), + (0x1D6D7, 'M', 'φ'), + (0x1D6D8, 'M', 'χ'), + (0x1D6D9, 'M', 'ψ'), + (0x1D6DA, 'M', 'ω'), + (0x1D6DB, 'M', '∂'), + (0x1D6DC, 'M', 'ε'), + (0x1D6DD, 'M', 'θ'), + (0x1D6DE, 'M', 'κ'), + (0x1D6DF, 'M', 'φ'), + (0x1D6E0, 'M', 'ρ'), + (0x1D6E1, 'M', 'π'), + (0x1D6E2, 'M', 'α'), + (0x1D6E3, 'M', 'β'), + (0x1D6E4, 'M', 'γ'), + (0x1D6E5, 'M', 'δ'), + (0x1D6E6, 'M', 'ε'), + (0x1D6E7, 'M', 'ζ'), + (0x1D6E8, 'M', 'η'), + (0x1D6E9, 'M', 'θ'), + (0x1D6EA, 'M', 'ι'), + (0x1D6EB, 'M', 'κ'), + (0x1D6EC, 'M', 'λ'), + (0x1D6ED, 'M', 'μ'), + (0x1D6EE, 'M', 'ν'), + (0x1D6EF, 'M', 'ξ'), + (0x1D6F0, 'M', 'ο'), + (0x1D6F1, 'M', 'π'), + (0x1D6F2, 'M', 'ρ'), + (0x1D6F3, 'M', 'θ'), + (0x1D6F4, 'M', 'σ'), + (0x1D6F5, 'M', 'τ'), + (0x1D6F6, 'M', 'υ'), + (0x1D6F7, 'M', 'φ'), + (0x1D6F8, 'M', 'χ'), + (0x1D6F9, 'M', 'ψ'), + (0x1D6FA, 'M', 'ω'), + (0x1D6FB, 'M', '∇'), + (0x1D6FC, 'M', 'α'), + (0x1D6FD, 'M', 'β'), + (0x1D6FE, 'M', 'γ'), + (0x1D6FF, 'M', 'δ'), + (0x1D700, 'M', 'ε'), + (0x1D701, 'M', 'ζ'), + (0x1D702, 'M', 'η'), + (0x1D703, 'M', 'θ'), + (0x1D704, 'M', 'ι'), + (0x1D705, 'M', 'κ'), + (0x1D706, 'M', 'λ'), + (0x1D707, 'M', 'μ'), + (0x1D708, 'M', 'ν'), + (0x1D709, 'M', 'ξ'), + (0x1D70A, 'M', 'ο'), + (0x1D70B, 'M', 'π'), + (0x1D70C, 'M', 'ρ'), + (0x1D70D, 'M', 'σ'), + (0x1D70F, 'M', 'τ'), + (0x1D710, 'M', 'υ'), + (0x1D711, 'M', 'φ'), + (0x1D712, 'M', 'χ'), + (0x1D713, 'M', 'ψ'), + (0x1D714, 'M', 'ω'), + (0x1D715, 'M', '∂'), + (0x1D716, 'M', 'ε'), ] def _seg_67(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x1D788, 'M', u'ω'), - (0x1D789, 'M', u'∂'), - (0x1D78A, 'M', u'ε'), - (0x1D78B, 'M', u'θ'), - (0x1D78C, 'M', u'κ'), - (0x1D78D, 'M', u'φ'), - (0x1D78E, 'M', u'ρ'), - (0x1D78F, 'M', u'π'), - (0x1D790, 'M', u'α'), - (0x1D791, 'M', u'β'), - (0x1D792, 'M', u'γ'), - (0x1D793, 'M', u'δ'), - (0x1D794, 'M', u'ε'), - (0x1D795, 'M', u'ζ'), - (0x1D796, 'M', u'η'), - (0x1D797, 'M', u'θ'), - (0x1D798, 'M', u'ι'), - (0x1D799, 'M', u'κ'), - (0x1D79A, 'M', u'λ'), - (0x1D79B, 'M', u'μ'), - (0x1D79C, 'M', u'ν'), - (0x1D79D, 'M', u'ξ'), - (0x1D79E, 'M', u'ο'), - (0x1D79F, 'M', u'π'), - (0x1D7A0, 'M', u'ρ'), - (0x1D7A1, 'M', u'θ'), - (0x1D7A2, 'M', u'σ'), - (0x1D7A3, 'M', u'τ'), - (0x1D7A4, 'M', u'υ'), - (0x1D7A5, 'M', u'φ'), - (0x1D7A6, 'M', u'χ'), - (0x1D7A7, 'M', u'ψ'), - (0x1D7A8, 'M', u'ω'), - (0x1D7A9, 'M', u'∇'), - (0x1D7AA, 'M', u'α'), - (0x1D7AB, 'M', u'β'), - (0x1D7AC, 'M', u'γ'), - (0x1D7AD, 'M', u'δ'), - (0x1D7AE, 'M', u'ε'), - (0x1D7AF, 'M', u'ζ'), - (0x1D7B0, 'M', u'η'), - (0x1D7B1, 'M', u'θ'), - (0x1D7B2, 'M', u'ι'), - (0x1D7B3, 'M', u'κ'), - (0x1D7B4, 'M', u'λ'), - (0x1D7B5, 'M', u'μ'), - (0x1D7B6, 'M', u'ν'), - (0x1D7B7, 'M', u'ξ'), - (0x1D7B8, 'M', u'ο'), - (0x1D7B9, 'M', u'π'), - (0x1D7BA, 'M', u'ρ'), - (0x1D7BB, 'M', u'σ'), - (0x1D7BD, 'M', u'τ'), - (0x1D7BE, 'M', u'υ'), - (0x1D7BF, 'M', u'φ'), - (0x1D7C0, 'M', u'χ'), - (0x1D7C1, 'M', u'ψ'), - (0x1D7C2, 'M', u'ω'), - (0x1D7C3, 'M', u'∂'), - (0x1D7C4, 'M', u'ε'), - (0x1D7C5, 'M', u'θ'), - (0x1D7C6, 'M', u'κ'), - (0x1D7C7, 'M', u'φ'), - (0x1D7C8, 'M', u'ρ'), - (0x1D7C9, 'M', u'π'), - (0x1D7CA, 'M', u'ϝ'), - (0x1D7CC, 'X'), - (0x1D7CE, 'M', u'0'), - (0x1D7CF, 'M', u'1'), - (0x1D7D0, 'M', u'2'), - (0x1D7D1, 'M', u'3'), - (0x1D7D2, 'M', u'4'), - (0x1D7D3, 'M', u'5'), - (0x1D7D4, 'M', u'6'), - (0x1D7D5, 'M', u'7'), - (0x1D7D6, 'M', u'8'), - (0x1D7D7, 'M', u'9'), - (0x1D7D8, 'M', u'0'), - (0x1D7D9, 'M', u'1'), - (0x1D7DA, 'M', u'2'), - (0x1D7DB, 'M', u'3'), - (0x1D7DC, 'M', u'4'), - (0x1D7DD, 'M', u'5'), - (0x1D7DE, 'M', u'6'), - (0x1D7DF, 'M', u'7'), - (0x1D7E0, 'M', u'8'), - (0x1D7E1, 'M', u'9'), - (0x1D7E2, 'M', u'0'), - (0x1D7E3, 'M', u'1'), - (0x1D7E4, 'M', u'2'), - (0x1D7E5, 'M', u'3'), - (0x1D7E6, 'M', u'4'), - (0x1D7E7, 'M', u'5'), - (0x1D7E8, 'M', u'6'), - (0x1D7E9, 'M', u'7'), - (0x1D7EA, 'M', u'8'), - (0x1D7EB, 'M', u'9'), - (0x1D7EC, 'M', u'0'), - (0x1D7ED, 'M', u'1'), - (0x1D7EE, 'M', u'2'), + (0x1D717, 'M', 'θ'), + (0x1D718, 'M', 'κ'), + (0x1D719, 'M', 'φ'), + (0x1D71A, 'M', 'ρ'), + (0x1D71B, 'M', 'π'), + (0x1D71C, 'M', 'α'), + (0x1D71D, 'M', 'β'), + (0x1D71E, 'M', 'γ'), + (0x1D71F, 'M', 'δ'), + (0x1D720, 'M', 'ε'), + (0x1D721, 'M', 'ζ'), + (0x1D722, 'M', 'η'), + (0x1D723, 'M', 'θ'), + (0x1D724, 'M', 'ι'), + (0x1D725, 'M', 'κ'), + (0x1D726, 'M', 'λ'), + (0x1D727, 'M', 'μ'), + (0x1D728, 'M', 'ν'), + (0x1D729, 'M', 'ξ'), + (0x1D72A, 'M', 'ο'), + (0x1D72B, 'M', 'π'), + (0x1D72C, 'M', 'ρ'), + (0x1D72D, 'M', 'θ'), + (0x1D72E, 'M', 'σ'), + (0x1D72F, 'M', 'τ'), + (0x1D730, 'M', 'υ'), + (0x1D731, 'M', 'φ'), + (0x1D732, 'M', 'χ'), + (0x1D733, 'M', 'ψ'), + (0x1D734, 'M', 'ω'), + (0x1D735, 'M', '∇'), + (0x1D736, 'M', 'α'), + (0x1D737, 'M', 'β'), + (0x1D738, 'M', 'γ'), + (0x1D739, 'M', 'δ'), + (0x1D73A, 'M', 'ε'), + (0x1D73B, 'M', 'ζ'), + (0x1D73C, 'M', 'η'), + (0x1D73D, 'M', 'θ'), + (0x1D73E, 'M', 'ι'), + (0x1D73F, 'M', 'κ'), + (0x1D740, 'M', 'λ'), + (0x1D741, 'M', 'μ'), + (0x1D742, 'M', 'ν'), + (0x1D743, 'M', 'ξ'), + (0x1D744, 'M', 'ο'), + (0x1D745, 'M', 'π'), + (0x1D746, 'M', 'ρ'), + (0x1D747, 'M', 'σ'), + (0x1D749, 'M', 'τ'), + (0x1D74A, 'M', 'υ'), + (0x1D74B, 'M', 'φ'), + (0x1D74C, 'M', 'χ'), + (0x1D74D, 'M', 'ψ'), + (0x1D74E, 'M', 'ω'), + (0x1D74F, 'M', '∂'), + (0x1D750, 'M', 'ε'), + (0x1D751, 'M', 'θ'), + (0x1D752, 'M', 'κ'), + (0x1D753, 'M', 'φ'), + (0x1D754, 'M', 'ρ'), + (0x1D755, 'M', 'π'), + (0x1D756, 'M', 'α'), + (0x1D757, 'M', 'β'), + (0x1D758, 'M', 'γ'), + (0x1D759, 'M', 'δ'), + (0x1D75A, 'M', 'ε'), + (0x1D75B, 'M', 'ζ'), + (0x1D75C, 'M', 'η'), + (0x1D75D, 'M', 'θ'), + (0x1D75E, 'M', 'ι'), + (0x1D75F, 'M', 'κ'), + (0x1D760, 'M', 'λ'), + (0x1D761, 'M', 'μ'), + (0x1D762, 'M', 'ν'), + (0x1D763, 'M', 'ξ'), + (0x1D764, 'M', 'ο'), + (0x1D765, 'M', 'π'), + (0x1D766, 'M', 'ρ'), + (0x1D767, 'M', 'θ'), + (0x1D768, 'M', 'σ'), + (0x1D769, 'M', 'τ'), + (0x1D76A, 'M', 'υ'), + (0x1D76B, 'M', 'φ'), + (0x1D76C, 'M', 'χ'), + (0x1D76D, 'M', 'ψ'), + (0x1D76E, 'M', 'ω'), + (0x1D76F, 'M', '∇'), + (0x1D770, 'M', 'α'), + (0x1D771, 'M', 'β'), + (0x1D772, 'M', 'γ'), + (0x1D773, 'M', 'δ'), + (0x1D774, 'M', 'ε'), + (0x1D775, 'M', 'ζ'), + (0x1D776, 'M', 'η'), + (0x1D777, 'M', 'θ'), + (0x1D778, 'M', 'ι'), + (0x1D779, 'M', 'κ'), + (0x1D77A, 'M', 'λ'), + (0x1D77B, 'M', 'μ'), ] def _seg_68(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x1D7EF, 'M', u'3'), - (0x1D7F0, 'M', u'4'), - (0x1D7F1, 'M', u'5'), - (0x1D7F2, 'M', u'6'), - (0x1D7F3, 'M', u'7'), - (0x1D7F4, 'M', u'8'), - (0x1D7F5, 'M', u'9'), - (0x1D7F6, 'M', u'0'), - (0x1D7F7, 'M', u'1'), - (0x1D7F8, 'M', u'2'), - (0x1D7F9, 'M', u'3'), - (0x1D7FA, 'M', u'4'), - (0x1D7FB, 'M', u'5'), - (0x1D7FC, 'M', u'6'), - (0x1D7FD, 'M', u'7'), - (0x1D7FE, 'M', u'8'), - (0x1D7FF, 'M', u'9'), + (0x1D77C, 'M', 'ν'), + (0x1D77D, 'M', 'ξ'), + (0x1D77E, 'M', 'ο'), + (0x1D77F, 'M', 'π'), + (0x1D780, 'M', 'ρ'), + (0x1D781, 'M', 'σ'), + (0x1D783, 'M', 'τ'), + (0x1D784, 'M', 'υ'), + (0x1D785, 'M', 'φ'), + (0x1D786, 'M', 'χ'), + (0x1D787, 'M', 'ψ'), + (0x1D788, 'M', 'ω'), + (0x1D789, 'M', '∂'), + (0x1D78A, 'M', 'ε'), + (0x1D78B, 'M', 'θ'), + (0x1D78C, 'M', 'κ'), + (0x1D78D, 'M', 'φ'), + (0x1D78E, 'M', 'ρ'), + (0x1D78F, 'M', 'π'), + (0x1D790, 'M', 'α'), + (0x1D791, 'M', 'β'), + (0x1D792, 'M', 'γ'), + (0x1D793, 'M', 'δ'), + (0x1D794, 'M', 'ε'), + (0x1D795, 'M', 'ζ'), + (0x1D796, 'M', 'η'), + (0x1D797, 'M', 'θ'), + (0x1D798, 'M', 'ι'), + (0x1D799, 'M', 'κ'), + (0x1D79A, 'M', 'λ'), + (0x1D79B, 'M', 'μ'), + (0x1D79C, 'M', 'ν'), + (0x1D79D, 'M', 'ξ'), + (0x1D79E, 'M', 'ο'), + (0x1D79F, 'M', 'π'), + (0x1D7A0, 'M', 'ρ'), + (0x1D7A1, 'M', 'θ'), + (0x1D7A2, 'M', 'σ'), + (0x1D7A3, 'M', 'τ'), + (0x1D7A4, 'M', 'υ'), + (0x1D7A5, 'M', 'φ'), + (0x1D7A6, 'M', 'χ'), + (0x1D7A7, 'M', 'ψ'), + (0x1D7A8, 'M', 'ω'), + (0x1D7A9, 'M', '∇'), + (0x1D7AA, 'M', 'α'), + (0x1D7AB, 'M', 'β'), + (0x1D7AC, 'M', 'γ'), + (0x1D7AD, 'M', 'δ'), + (0x1D7AE, 'M', 'ε'), + (0x1D7AF, 'M', 'ζ'), + (0x1D7B0, 'M', 'η'), + (0x1D7B1, 'M', 'θ'), + (0x1D7B2, 'M', 'ι'), + (0x1D7B3, 'M', 'κ'), + (0x1D7B4, 'M', 'λ'), + (0x1D7B5, 'M', 'μ'), + (0x1D7B6, 'M', 'ν'), + (0x1D7B7, 'M', 'ξ'), + (0x1D7B8, 'M', 'ο'), + (0x1D7B9, 'M', 'π'), + (0x1D7BA, 'M', 'ρ'), + (0x1D7BB, 'M', 'σ'), + (0x1D7BD, 'M', 'τ'), + (0x1D7BE, 'M', 'υ'), + (0x1D7BF, 'M', 'φ'), + (0x1D7C0, 'M', 'χ'), + (0x1D7C1, 'M', 'ψ'), + (0x1D7C2, 'M', 'ω'), + (0x1D7C3, 'M', '∂'), + (0x1D7C4, 'M', 'ε'), + (0x1D7C5, 'M', 'θ'), + (0x1D7C6, 'M', 'κ'), + (0x1D7C7, 'M', 'φ'), + (0x1D7C8, 'M', 'ρ'), + (0x1D7C9, 'M', 'π'), + (0x1D7CA, 'M', 'ϝ'), + (0x1D7CC, 'X'), + (0x1D7CE, 'M', '0'), + (0x1D7CF, 'M', '1'), + (0x1D7D0, 'M', '2'), + (0x1D7D1, 'M', '3'), + (0x1D7D2, 'M', '4'), + (0x1D7D3, 'M', '5'), + (0x1D7D4, 'M', '6'), + (0x1D7D5, 'M', '7'), + (0x1D7D6, 'M', '8'), + (0x1D7D7, 'M', '9'), + (0x1D7D8, 'M', '0'), + (0x1D7D9, 'M', '1'), + (0x1D7DA, 'M', '2'), + (0x1D7DB, 'M', '3'), + (0x1D7DC, 'M', '4'), + (0x1D7DD, 'M', '5'), + (0x1D7DE, 'M', '6'), + (0x1D7DF, 'M', '7'), + (0x1D7E0, 'M', '8'), + (0x1D7E1, 'M', '9'), + (0x1D7E2, 'M', '0'), + (0x1D7E3, 'M', '1'), + ] + +def _seg_69(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ + (0x1D7E4, 'M', '2'), + (0x1D7E5, 'M', '3'), + (0x1D7E6, 'M', '4'), + (0x1D7E7, 'M', '5'), + (0x1D7E8, 'M', '6'), + (0x1D7E9, 'M', '7'), + (0x1D7EA, 'M', '8'), + (0x1D7EB, 'M', '9'), + (0x1D7EC, 'M', '0'), + (0x1D7ED, 'M', '1'), + (0x1D7EE, 'M', '2'), + (0x1D7EF, 'M', '3'), + (0x1D7F0, 'M', '4'), + (0x1D7F1, 'M', '5'), + (0x1D7F2, 'M', '6'), + (0x1D7F3, 'M', '7'), + (0x1D7F4, 'M', '8'), + (0x1D7F5, 'M', '9'), + (0x1D7F6, 'M', '0'), + (0x1D7F7, 'M', '1'), + (0x1D7F8, 'M', '2'), + (0x1D7F9, 'M', '3'), + (0x1D7FA, 'M', '4'), + (0x1D7FB, 'M', '5'), + (0x1D7FC, 'M', '6'), + (0x1D7FD, 'M', '7'), + (0x1D7FE, 'M', '8'), + (0x1D7FF, 'M', '9'), (0x1D800, 'V'), (0x1DA8C, 'X'), (0x1DA9B, 'V'), @@ -7112,233 +7298,249 @@ def _seg_68(): (0x1E025, 'X'), (0x1E026, 'V'), (0x1E02B, 'X'), + (0x1E100, 'V'), + (0x1E12D, 'X'), + (0x1E130, 'V'), + (0x1E13E, 'X'), + (0x1E140, 'V'), + (0x1E14A, 'X'), + (0x1E14E, 'V'), + (0x1E150, 'X'), + (0x1E2C0, 'V'), + (0x1E2FA, 'X'), + (0x1E2FF, 'V'), + (0x1E300, 'X'), (0x1E800, 'V'), (0x1E8C5, 'X'), (0x1E8C7, 'V'), (0x1E8D7, 'X'), - (0x1E900, 'M', u'𞤢'), - (0x1E901, 'M', u'𞤣'), - (0x1E902, 'M', u'𞤤'), - (0x1E903, 'M', u'𞤥'), - (0x1E904, 'M', u'𞤦'), - (0x1E905, 'M', u'𞤧'), - (0x1E906, 'M', u'𞤨'), - (0x1E907, 'M', u'𞤩'), - (0x1E908, 'M', u'𞤪'), - (0x1E909, 'M', u'𞤫'), - (0x1E90A, 'M', u'𞤬'), - (0x1E90B, 'M', u'𞤭'), - (0x1E90C, 'M', u'𞤮'), - (0x1E90D, 'M', u'𞤯'), - (0x1E90E, 'M', u'𞤰'), - (0x1E90F, 'M', u'𞤱'), - (0x1E910, 'M', u'𞤲'), - (0x1E911, 'M', u'𞤳'), - (0x1E912, 'M', u'𞤴'), - (0x1E913, 'M', u'𞤵'), - (0x1E914, 'M', u'𞤶'), - (0x1E915, 'M', u'𞤷'), - (0x1E916, 'M', u'𞤸'), - (0x1E917, 'M', u'𞤹'), - (0x1E918, 'M', u'𞤺'), - (0x1E919, 'M', u'𞤻'), - (0x1E91A, 'M', u'𞤼'), - (0x1E91B, 'M', u'𞤽'), - (0x1E91C, 'M', u'𞤾'), - (0x1E91D, 'M', u'𞤿'), - (0x1E91E, 'M', u'𞥀'), - (0x1E91F, 'M', u'𞥁'), - (0x1E920, 'M', u'𞥂'), - (0x1E921, 'M', u'𞥃'), + (0x1E900, 'M', '𞤢'), + (0x1E901, 'M', '𞤣'), + (0x1E902, 'M', '𞤤'), + (0x1E903, 'M', '𞤥'), + (0x1E904, 'M', '𞤦'), + (0x1E905, 'M', '𞤧'), + (0x1E906, 'M', '𞤨'), + (0x1E907, 'M', '𞤩'), + (0x1E908, 'M', '𞤪'), + (0x1E909, 'M', '𞤫'), + (0x1E90A, 'M', '𞤬'), + (0x1E90B, 'M', '𞤭'), + (0x1E90C, 'M', '𞤮'), + (0x1E90D, 'M', '𞤯'), + (0x1E90E, 'M', '𞤰'), + (0x1E90F, 'M', '𞤱'), + (0x1E910, 'M', '𞤲'), + (0x1E911, 'M', '𞤳'), + (0x1E912, 'M', '𞤴'), + (0x1E913, 'M', '𞤵'), + (0x1E914, 'M', '𞤶'), + (0x1E915, 'M', '𞤷'), + (0x1E916, 'M', '𞤸'), + (0x1E917, 'M', '𞤹'), + (0x1E918, 'M', '𞤺'), + (0x1E919, 'M', '𞤻'), + (0x1E91A, 'M', '𞤼'), + (0x1E91B, 'M', '𞤽'), + (0x1E91C, 'M', '𞤾'), + (0x1E91D, 'M', '𞤿'), + (0x1E91E, 'M', '𞥀'), + (0x1E91F, 'M', '𞥁'), + (0x1E920, 'M', '𞥂'), + (0x1E921, 'M', '𞥃'), (0x1E922, 'V'), - (0x1E94B, 'X'), + (0x1E94C, 'X'), (0x1E950, 'V'), (0x1E95A, 'X'), (0x1E95E, 'V'), (0x1E960, 'X'), - (0x1EC71, 'V'), - (0x1ECB5, 'X'), - (0x1EE00, 'M', u'ا'), - (0x1EE01, 'M', u'ب'), - (0x1EE02, 'M', u'ج'), - (0x1EE03, 'M', u'د'), - (0x1EE04, 'X'), - (0x1EE05, 'M', u'و'), - (0x1EE06, 'M', u'ز'), - (0x1EE07, 'M', u'ح'), - (0x1EE08, 'M', u'ط'), - (0x1EE09, 'M', u'ي'), - (0x1EE0A, 'M', u'ك'), - (0x1EE0B, 'M', u'ل'), - (0x1EE0C, 'M', u'م'), - (0x1EE0D, 'M', u'ن'), - (0x1EE0E, 'M', u'س'), - (0x1EE0F, 'M', u'ع'), - (0x1EE10, 'M', u'ف'), - (0x1EE11, 'M', u'ص'), - (0x1EE12, 'M', u'ق'), - (0x1EE13, 'M', u'ر'), - (0x1EE14, 'M', u'ش'), - ] - -def _seg_69(): - return [ - (0x1EE15, 'M', u'ت'), - (0x1EE16, 'M', u'ث'), - (0x1EE17, 'M', u'خ'), - (0x1EE18, 'M', u'ذ'), - (0x1EE19, 'M', u'ض'), - (0x1EE1A, 'M', u'ظ'), - (0x1EE1B, 'M', u'غ'), - (0x1EE1C, 'M', u'ٮ'), - (0x1EE1D, 'M', u'ں'), - (0x1EE1E, 'M', u'ڡ'), - (0x1EE1F, 'M', u'ٯ'), - (0x1EE20, 'X'), - (0x1EE21, 'M', u'ب'), - (0x1EE22, 'M', u'ج'), - (0x1EE23, 'X'), - (0x1EE24, 'M', u'ه'), - (0x1EE25, 'X'), - (0x1EE27, 'M', u'ح'), - (0x1EE28, 'X'), - (0x1EE29, 'M', u'ي'), - (0x1EE2A, 'M', u'ك'), - (0x1EE2B, 'M', u'ل'), - (0x1EE2C, 'M', u'م'), - (0x1EE2D, 'M', u'ن'), - (0x1EE2E, 'M', u'س'), - (0x1EE2F, 'M', u'ع'), - (0x1EE30, 'M', u'ف'), - (0x1EE31, 'M', u'ص'), - (0x1EE32, 'M', u'ق'), - (0x1EE33, 'X'), - (0x1EE34, 'M', u'ش'), - (0x1EE35, 'M', u'ت'), - (0x1EE36, 'M', u'ث'), - (0x1EE37, 'M', u'خ'), - (0x1EE38, 'X'), - (0x1EE39, 'M', u'ض'), - (0x1EE3A, 'X'), - (0x1EE3B, 'M', u'غ'), - (0x1EE3C, 'X'), - (0x1EE42, 'M', u'ج'), - (0x1EE43, 'X'), - (0x1EE47, 'M', u'ح'), - (0x1EE48, 'X'), - (0x1EE49, 'M', u'ي'), - (0x1EE4A, 'X'), - (0x1EE4B, 'M', u'ل'), - (0x1EE4C, 'X'), - (0x1EE4D, 'M', u'ن'), - (0x1EE4E, 'M', u'س'), - (0x1EE4F, 'M', u'ع'), - (0x1EE50, 'X'), - (0x1EE51, 'M', u'ص'), - (0x1EE52, 'M', u'ق'), - (0x1EE53, 'X'), - (0x1EE54, 'M', u'ش'), - (0x1EE55, 'X'), - (0x1EE57, 'M', u'خ'), - (0x1EE58, 'X'), - (0x1EE59, 'M', u'ض'), - (0x1EE5A, 'X'), - (0x1EE5B, 'M', u'غ'), - (0x1EE5C, 'X'), - (0x1EE5D, 'M', u'ں'), - (0x1EE5E, 'X'), - (0x1EE5F, 'M', u'ٯ'), - (0x1EE60, 'X'), - (0x1EE61, 'M', u'ب'), - (0x1EE62, 'M', u'ج'), - (0x1EE63, 'X'), - (0x1EE64, 'M', u'ه'), - (0x1EE65, 'X'), - (0x1EE67, 'M', u'ح'), - (0x1EE68, 'M', u'ط'), - (0x1EE69, 'M', u'ي'), - (0x1EE6A, 'M', u'ك'), - (0x1EE6B, 'X'), - (0x1EE6C, 'M', u'م'), - (0x1EE6D, 'M', u'ن'), - (0x1EE6E, 'M', u'س'), - (0x1EE6F, 'M', u'ع'), - (0x1EE70, 'M', u'ف'), - (0x1EE71, 'M', u'ص'), - (0x1EE72, 'M', u'ق'), - (0x1EE73, 'X'), - (0x1EE74, 'M', u'ش'), - (0x1EE75, 'M', u'ت'), - (0x1EE76, 'M', u'ث'), - (0x1EE77, 'M', u'خ'), - (0x1EE78, 'X'), - (0x1EE79, 'M', u'ض'), - (0x1EE7A, 'M', u'ظ'), - (0x1EE7B, 'M', u'غ'), - (0x1EE7C, 'M', u'ٮ'), - (0x1EE7D, 'X'), - (0x1EE7E, 'M', u'ڡ'), - (0x1EE7F, 'X'), - (0x1EE80, 'M', u'ا'), - (0x1EE81, 'M', u'ب'), - (0x1EE82, 'M', u'ج'), - (0x1EE83, 'M', u'د'), ] def _seg_70(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x1EE84, 'M', u'ه'), - (0x1EE85, 'M', u'و'), - (0x1EE86, 'M', u'ز'), - (0x1EE87, 'M', u'ح'), - (0x1EE88, 'M', u'ط'), - (0x1EE89, 'M', u'ي'), + (0x1EC71, 'V'), + (0x1ECB5, 'X'), + (0x1ED01, 'V'), + (0x1ED3E, 'X'), + (0x1EE00, 'M', 'ا'), + (0x1EE01, 'M', 'ب'), + (0x1EE02, 'M', 'ج'), + (0x1EE03, 'M', 'د'), + (0x1EE04, 'X'), + (0x1EE05, 'M', 'و'), + (0x1EE06, 'M', 'ز'), + (0x1EE07, 'M', 'ح'), + (0x1EE08, 'M', 'ط'), + (0x1EE09, 'M', 'ي'), + (0x1EE0A, 'M', 'ك'), + (0x1EE0B, 'M', 'ل'), + (0x1EE0C, 'M', 'م'), + (0x1EE0D, 'M', 'ن'), + (0x1EE0E, 'M', 'س'), + (0x1EE0F, 'M', 'ع'), + (0x1EE10, 'M', 'ف'), + (0x1EE11, 'M', 'ص'), + (0x1EE12, 'M', 'ق'), + (0x1EE13, 'M', 'ر'), + (0x1EE14, 'M', 'ش'), + (0x1EE15, 'M', 'ت'), + (0x1EE16, 'M', 'ث'), + (0x1EE17, 'M', 'خ'), + (0x1EE18, 'M', 'ذ'), + (0x1EE19, 'M', 'ض'), + (0x1EE1A, 'M', 'ظ'), + (0x1EE1B, 'M', 'غ'), + (0x1EE1C, 'M', 'ٮ'), + (0x1EE1D, 'M', 'ں'), + (0x1EE1E, 'M', 'ڡ'), + (0x1EE1F, 'M', 'ٯ'), + (0x1EE20, 'X'), + (0x1EE21, 'M', 'ب'), + (0x1EE22, 'M', 'ج'), + (0x1EE23, 'X'), + (0x1EE24, 'M', 'ه'), + (0x1EE25, 'X'), + (0x1EE27, 'M', 'ح'), + (0x1EE28, 'X'), + (0x1EE29, 'M', 'ي'), + (0x1EE2A, 'M', 'ك'), + (0x1EE2B, 'M', 'ل'), + (0x1EE2C, 'M', 'م'), + (0x1EE2D, 'M', 'ن'), + (0x1EE2E, 'M', 'س'), + (0x1EE2F, 'M', 'ع'), + (0x1EE30, 'M', 'ف'), + (0x1EE31, 'M', 'ص'), + (0x1EE32, 'M', 'ق'), + (0x1EE33, 'X'), + (0x1EE34, 'M', 'ش'), + (0x1EE35, 'M', 'ت'), + (0x1EE36, 'M', 'ث'), + (0x1EE37, 'M', 'خ'), + (0x1EE38, 'X'), + (0x1EE39, 'M', 'ض'), + (0x1EE3A, 'X'), + (0x1EE3B, 'M', 'غ'), + (0x1EE3C, 'X'), + (0x1EE42, 'M', 'ج'), + (0x1EE43, 'X'), + (0x1EE47, 'M', 'ح'), + (0x1EE48, 'X'), + (0x1EE49, 'M', 'ي'), + (0x1EE4A, 'X'), + (0x1EE4B, 'M', 'ل'), + (0x1EE4C, 'X'), + (0x1EE4D, 'M', 'ن'), + (0x1EE4E, 'M', 'س'), + (0x1EE4F, 'M', 'ع'), + (0x1EE50, 'X'), + (0x1EE51, 'M', 'ص'), + (0x1EE52, 'M', 'ق'), + (0x1EE53, 'X'), + (0x1EE54, 'M', 'ش'), + (0x1EE55, 'X'), + (0x1EE57, 'M', 'خ'), + (0x1EE58, 'X'), + (0x1EE59, 'M', 'ض'), + (0x1EE5A, 'X'), + (0x1EE5B, 'M', 'غ'), + (0x1EE5C, 'X'), + (0x1EE5D, 'M', 'ں'), + (0x1EE5E, 'X'), + (0x1EE5F, 'M', 'ٯ'), + (0x1EE60, 'X'), + (0x1EE61, 'M', 'ب'), + (0x1EE62, 'M', 'ج'), + (0x1EE63, 'X'), + (0x1EE64, 'M', 'ه'), + (0x1EE65, 'X'), + (0x1EE67, 'M', 'ح'), + (0x1EE68, 'M', 'ط'), + (0x1EE69, 'M', 'ي'), + (0x1EE6A, 'M', 'ك'), + ] + +def _seg_71(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ + (0x1EE6B, 'X'), + (0x1EE6C, 'M', 'م'), + (0x1EE6D, 'M', 'ن'), + (0x1EE6E, 'M', 'س'), + (0x1EE6F, 'M', 'ع'), + (0x1EE70, 'M', 'ف'), + (0x1EE71, 'M', 'ص'), + (0x1EE72, 'M', 'ق'), + (0x1EE73, 'X'), + (0x1EE74, 'M', 'ش'), + (0x1EE75, 'M', 'ت'), + (0x1EE76, 'M', 'ث'), + (0x1EE77, 'M', 'خ'), + (0x1EE78, 'X'), + (0x1EE79, 'M', 'ض'), + (0x1EE7A, 'M', 'ظ'), + (0x1EE7B, 'M', 'غ'), + (0x1EE7C, 'M', 'ٮ'), + (0x1EE7D, 'X'), + (0x1EE7E, 'M', 'ڡ'), + (0x1EE7F, 'X'), + (0x1EE80, 'M', 'ا'), + (0x1EE81, 'M', 'ب'), + (0x1EE82, 'M', 'ج'), + (0x1EE83, 'M', 'د'), + (0x1EE84, 'M', 'ه'), + (0x1EE85, 'M', 'و'), + (0x1EE86, 'M', 'ز'), + (0x1EE87, 'M', 'ح'), + (0x1EE88, 'M', 'ط'), + (0x1EE89, 'M', 'ي'), (0x1EE8A, 'X'), - (0x1EE8B, 'M', u'ل'), - (0x1EE8C, 'M', u'م'), - (0x1EE8D, 'M', u'ن'), - (0x1EE8E, 'M', u'س'), - (0x1EE8F, 'M', u'ع'), - (0x1EE90, 'M', u'ف'), - (0x1EE91, 'M', u'ص'), - (0x1EE92, 'M', u'ق'), - (0x1EE93, 'M', u'ر'), - (0x1EE94, 'M', u'ش'), - (0x1EE95, 'M', u'ت'), - (0x1EE96, 'M', u'ث'), - (0x1EE97, 'M', u'خ'), - (0x1EE98, 'M', u'ذ'), - (0x1EE99, 'M', u'ض'), - (0x1EE9A, 'M', u'ظ'), - (0x1EE9B, 'M', u'غ'), + (0x1EE8B, 'M', 'ل'), + (0x1EE8C, 'M', 'م'), + (0x1EE8D, 'M', 'ن'), + (0x1EE8E, 'M', 'س'), + (0x1EE8F, 'M', 'ع'), + (0x1EE90, 'M', 'ف'), + (0x1EE91, 'M', 'ص'), + (0x1EE92, 'M', 'ق'), + (0x1EE93, 'M', 'ر'), + (0x1EE94, 'M', 'ش'), + (0x1EE95, 'M', 'ت'), + (0x1EE96, 'M', 'ث'), + (0x1EE97, 'M', 'خ'), + (0x1EE98, 'M', 'ذ'), + (0x1EE99, 'M', 'ض'), + (0x1EE9A, 'M', 'ظ'), + (0x1EE9B, 'M', 'غ'), (0x1EE9C, 'X'), - (0x1EEA1, 'M', u'ب'), - (0x1EEA2, 'M', u'ج'), - (0x1EEA3, 'M', u'د'), + (0x1EEA1, 'M', 'ب'), + (0x1EEA2, 'M', 'ج'), + (0x1EEA3, 'M', 'د'), (0x1EEA4, 'X'), - (0x1EEA5, 'M', u'و'), - (0x1EEA6, 'M', u'ز'), - (0x1EEA7, 'M', u'ح'), - (0x1EEA8, 'M', u'ط'), - (0x1EEA9, 'M', u'ي'), + (0x1EEA5, 'M', 'و'), + (0x1EEA6, 'M', 'ز'), + (0x1EEA7, 'M', 'ح'), + (0x1EEA8, 'M', 'ط'), + (0x1EEA9, 'M', 'ي'), (0x1EEAA, 'X'), - (0x1EEAB, 'M', u'ل'), - (0x1EEAC, 'M', u'م'), - (0x1EEAD, 'M', u'ن'), - (0x1EEAE, 'M', u'س'), - (0x1EEAF, 'M', u'ع'), - (0x1EEB0, 'M', u'ف'), - (0x1EEB1, 'M', u'ص'), - (0x1EEB2, 'M', u'ق'), - (0x1EEB3, 'M', u'ر'), - (0x1EEB4, 'M', u'ش'), - (0x1EEB5, 'M', u'ت'), - (0x1EEB6, 'M', u'ث'), - (0x1EEB7, 'M', u'خ'), - (0x1EEB8, 'M', u'ذ'), - (0x1EEB9, 'M', u'ض'), - (0x1EEBA, 'M', u'ظ'), - (0x1EEBB, 'M', u'غ'), + (0x1EEAB, 'M', 'ل'), + (0x1EEAC, 'M', 'م'), + (0x1EEAD, 'M', 'ن'), + (0x1EEAE, 'M', 'س'), + (0x1EEAF, 'M', 'ع'), + (0x1EEB0, 'M', 'ف'), + (0x1EEB1, 'M', 'ص'), + (0x1EEB2, 'M', 'ق'), + (0x1EEB3, 'M', 'ر'), + (0x1EEB4, 'M', 'ش'), + (0x1EEB5, 'M', 'ت'), + (0x1EEB6, 'M', 'ث'), + (0x1EEB7, 'M', 'خ'), + (0x1EEB8, 'M', 'ذ'), + (0x1EEB9, 'M', 'ض'), + (0x1EEBA, 'M', 'ظ'), + (0x1EEBB, 'M', 'غ'), (0x1EEBC, 'X'), (0x1EEF0, 'V'), (0x1EEF2, 'X'), @@ -7354,173 +7556,176 @@ def _seg_70(): (0x1F0D0, 'X'), (0x1F0D1, 'V'), (0x1F0F6, 'X'), - (0x1F101, '3', u'0,'), - (0x1F102, '3', u'1,'), - (0x1F103, '3', u'2,'), - (0x1F104, '3', u'3,'), - (0x1F105, '3', u'4,'), - (0x1F106, '3', u'5,'), - (0x1F107, '3', u'6,'), - (0x1F108, '3', u'7,'), - (0x1F109, '3', u'8,'), - (0x1F10A, '3', u'9,'), - (0x1F10B, 'V'), - (0x1F10D, 'X'), - (0x1F110, '3', u'(a)'), - (0x1F111, '3', u'(b)'), - (0x1F112, '3', u'(c)'), - (0x1F113, '3', u'(d)'), - (0x1F114, '3', u'(e)'), - (0x1F115, '3', u'(f)'), - (0x1F116, '3', u'(g)'), - (0x1F117, '3', u'(h)'), - (0x1F118, '3', u'(i)'), - (0x1F119, '3', u'(j)'), - (0x1F11A, '3', u'(k)'), - (0x1F11B, '3', u'(l)'), - (0x1F11C, '3', u'(m)'), - (0x1F11D, '3', u'(n)'), - (0x1F11E, '3', u'(o)'), - (0x1F11F, '3', u'(p)'), - (0x1F120, '3', u'(q)'), - (0x1F121, '3', u'(r)'), - (0x1F122, '3', u'(s)'), - (0x1F123, '3', u'(t)'), - (0x1F124, '3', u'(u)'), - ] - -def _seg_71(): - return [ - (0x1F125, '3', u'(v)'), - (0x1F126, '3', u'(w)'), - (0x1F127, '3', u'(x)'), - (0x1F128, '3', u'(y)'), - (0x1F129, '3', u'(z)'), - (0x1F12A, 'M', u'〔s〕'), - (0x1F12B, 'M', u'c'), - (0x1F12C, 'M', u'r'), - (0x1F12D, 'M', u'cd'), - (0x1F12E, 'M', u'wz'), - (0x1F12F, 'V'), - (0x1F130, 'M', u'a'), - (0x1F131, 'M', u'b'), - (0x1F132, 'M', u'c'), - (0x1F133, 'M', u'd'), - (0x1F134, 'M', u'e'), - (0x1F135, 'M', u'f'), - (0x1F136, 'M', u'g'), - (0x1F137, 'M', u'h'), - (0x1F138, 'M', u'i'), - (0x1F139, 'M', u'j'), - (0x1F13A, 'M', u'k'), - (0x1F13B, 'M', u'l'), - (0x1F13C, 'M', u'm'), - (0x1F13D, 'M', u'n'), - (0x1F13E, 'M', u'o'), - (0x1F13F, 'M', u'p'), - (0x1F140, 'M', u'q'), - (0x1F141, 'M', u'r'), - (0x1F142, 'M', u's'), - (0x1F143, 'M', u't'), - (0x1F144, 'M', u'u'), - (0x1F145, 'M', u'v'), - (0x1F146, 'M', u'w'), - (0x1F147, 'M', u'x'), - (0x1F148, 'M', u'y'), - (0x1F149, 'M', u'z'), - (0x1F14A, 'M', u'hv'), - (0x1F14B, 'M', u'mv'), - (0x1F14C, 'M', u'sd'), - (0x1F14D, 'M', u'ss'), - (0x1F14E, 'M', u'ppv'), - (0x1F14F, 'M', u'wc'), - (0x1F150, 'V'), - (0x1F16A, 'M', u'mc'), - (0x1F16B, 'M', u'md'), - (0x1F16C, 'X'), - (0x1F170, 'V'), - (0x1F190, 'M', u'dj'), - (0x1F191, 'V'), - (0x1F1AD, 'X'), - (0x1F1E6, 'V'), - (0x1F200, 'M', u'ほか'), - (0x1F201, 'M', u'ココ'), - (0x1F202, 'M', u'サ'), - (0x1F203, 'X'), - (0x1F210, 'M', u'手'), - (0x1F211, 'M', u'字'), - (0x1F212, 'M', u'双'), - (0x1F213, 'M', u'デ'), - (0x1F214, 'M', u'二'), - (0x1F215, 'M', u'多'), - (0x1F216, 'M', u'解'), - (0x1F217, 'M', u'天'), - (0x1F218, 'M', u'交'), - (0x1F219, 'M', u'映'), - (0x1F21A, 'M', u'無'), - (0x1F21B, 'M', u'料'), - (0x1F21C, 'M', u'前'), - (0x1F21D, 'M', u'後'), - (0x1F21E, 'M', u'再'), - (0x1F21F, 'M', u'新'), - (0x1F220, 'M', u'初'), - (0x1F221, 'M', u'終'), - (0x1F222, 'M', u'生'), - (0x1F223, 'M', u'販'), - (0x1F224, 'M', u'声'), - (0x1F225, 'M', u'吹'), - (0x1F226, 'M', u'演'), - (0x1F227, 'M', u'投'), - (0x1F228, 'M', u'捕'), - (0x1F229, 'M', u'一'), - (0x1F22A, 'M', u'三'), - (0x1F22B, 'M', u'遊'), - (0x1F22C, 'M', u'左'), - (0x1F22D, 'M', u'中'), - (0x1F22E, 'M', u'右'), - (0x1F22F, 'M', u'指'), - (0x1F230, 'M', u'走'), - (0x1F231, 'M', u'打'), - (0x1F232, 'M', u'禁'), - (0x1F233, 'M', u'空'), - (0x1F234, 'M', u'合'), - (0x1F235, 'M', u'満'), - (0x1F236, 'M', u'有'), - (0x1F237, 'M', u'月'), - (0x1F238, 'M', u'申'), - (0x1F239, 'M', u'割'), - (0x1F23A, 'M', u'営'), - (0x1F23B, 'M', u'配'), + (0x1F101, '3', '0,'), + (0x1F102, '3', '1,'), + (0x1F103, '3', '2,'), + (0x1F104, '3', '3,'), + (0x1F105, '3', '4,'), + (0x1F106, '3', '5,'), + (0x1F107, '3', '6,'), + (0x1F108, '3', '7,'), ] def _seg_72(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ + (0x1F109, '3', '8,'), + (0x1F10A, '3', '9,'), + (0x1F10B, 'V'), + (0x1F110, '3', '(a)'), + (0x1F111, '3', '(b)'), + (0x1F112, '3', '(c)'), + (0x1F113, '3', '(d)'), + (0x1F114, '3', '(e)'), + (0x1F115, '3', '(f)'), + (0x1F116, '3', '(g)'), + (0x1F117, '3', '(h)'), + (0x1F118, '3', '(i)'), + (0x1F119, '3', '(j)'), + (0x1F11A, '3', '(k)'), + (0x1F11B, '3', '(l)'), + (0x1F11C, '3', '(m)'), + (0x1F11D, '3', '(n)'), + (0x1F11E, '3', '(o)'), + (0x1F11F, '3', '(p)'), + (0x1F120, '3', '(q)'), + (0x1F121, '3', '(r)'), + (0x1F122, '3', '(s)'), + (0x1F123, '3', '(t)'), + (0x1F124, '3', '(u)'), + (0x1F125, '3', '(v)'), + (0x1F126, '3', '(w)'), + (0x1F127, '3', '(x)'), + (0x1F128, '3', '(y)'), + (0x1F129, '3', '(z)'), + (0x1F12A, 'M', '〔s〕'), + (0x1F12B, 'M', 'c'), + (0x1F12C, 'M', 'r'), + (0x1F12D, 'M', 'cd'), + (0x1F12E, 'M', 'wz'), + (0x1F12F, 'V'), + (0x1F130, 'M', 'a'), + (0x1F131, 'M', 'b'), + (0x1F132, 'M', 'c'), + (0x1F133, 'M', 'd'), + (0x1F134, 'M', 'e'), + (0x1F135, 'M', 'f'), + (0x1F136, 'M', 'g'), + (0x1F137, 'M', 'h'), + (0x1F138, 'M', 'i'), + (0x1F139, 'M', 'j'), + (0x1F13A, 'M', 'k'), + (0x1F13B, 'M', 'l'), + (0x1F13C, 'M', 'm'), + (0x1F13D, 'M', 'n'), + (0x1F13E, 'M', 'o'), + (0x1F13F, 'M', 'p'), + (0x1F140, 'M', 'q'), + (0x1F141, 'M', 'r'), + (0x1F142, 'M', 's'), + (0x1F143, 'M', 't'), + (0x1F144, 'M', 'u'), + (0x1F145, 'M', 'v'), + (0x1F146, 'M', 'w'), + (0x1F147, 'M', 'x'), + (0x1F148, 'M', 'y'), + (0x1F149, 'M', 'z'), + (0x1F14A, 'M', 'hv'), + (0x1F14B, 'M', 'mv'), + (0x1F14C, 'M', 'sd'), + (0x1F14D, 'M', 'ss'), + (0x1F14E, 'M', 'ppv'), + (0x1F14F, 'M', 'wc'), + (0x1F150, 'V'), + (0x1F16A, 'M', 'mc'), + (0x1F16B, 'M', 'md'), + (0x1F16C, 'M', 'mr'), + (0x1F16D, 'V'), + (0x1F190, 'M', 'dj'), + (0x1F191, 'V'), + (0x1F1AE, 'X'), + (0x1F1E6, 'V'), + (0x1F200, 'M', 'ほか'), + (0x1F201, 'M', 'ココ'), + (0x1F202, 'M', 'サ'), + (0x1F203, 'X'), + (0x1F210, 'M', '手'), + (0x1F211, 'M', '字'), + (0x1F212, 'M', '双'), + (0x1F213, 'M', 'デ'), + (0x1F214, 'M', '二'), + (0x1F215, 'M', '多'), + (0x1F216, 'M', '解'), + (0x1F217, 'M', '天'), + (0x1F218, 'M', '交'), + (0x1F219, 'M', '映'), + (0x1F21A, 'M', '無'), + (0x1F21B, 'M', '料'), + (0x1F21C, 'M', '前'), + (0x1F21D, 'M', '後'), + (0x1F21E, 'M', '再'), + (0x1F21F, 'M', '新'), + (0x1F220, 'M', '初'), + (0x1F221, 'M', '終'), + (0x1F222, 'M', '生'), + (0x1F223, 'M', '販'), + ] + +def _seg_73(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ + (0x1F224, 'M', '声'), + (0x1F225, 'M', '吹'), + (0x1F226, 'M', '演'), + (0x1F227, 'M', '投'), + (0x1F228, 'M', '捕'), + (0x1F229, 'M', '一'), + (0x1F22A, 'M', '三'), + (0x1F22B, 'M', '遊'), + (0x1F22C, 'M', '左'), + (0x1F22D, 'M', '中'), + (0x1F22E, 'M', '右'), + (0x1F22F, 'M', '指'), + (0x1F230, 'M', '走'), + (0x1F231, 'M', '打'), + (0x1F232, 'M', '禁'), + (0x1F233, 'M', '空'), + (0x1F234, 'M', '合'), + (0x1F235, 'M', '満'), + (0x1F236, 'M', '有'), + (0x1F237, 'M', '月'), + (0x1F238, 'M', '申'), + (0x1F239, 'M', '割'), + (0x1F23A, 'M', '営'), + (0x1F23B, 'M', '配'), (0x1F23C, 'X'), - (0x1F240, 'M', u'〔本〕'), - (0x1F241, 'M', u'〔三〕'), - (0x1F242, 'M', u'〔二〕'), - (0x1F243, 'M', u'〔安〕'), - (0x1F244, 'M', u'〔点〕'), - (0x1F245, 'M', u'〔打〕'), - (0x1F246, 'M', u'〔盗〕'), - (0x1F247, 'M', u'〔勝〕'), - (0x1F248, 'M', u'〔敗〕'), + (0x1F240, 'M', '〔本〕'), + (0x1F241, 'M', '〔三〕'), + (0x1F242, 'M', '〔二〕'), + (0x1F243, 'M', '〔安〕'), + (0x1F244, 'M', '〔点〕'), + (0x1F245, 'M', '〔打〕'), + (0x1F246, 'M', '〔盗〕'), + (0x1F247, 'M', '〔勝〕'), + (0x1F248, 'M', '〔敗〕'), (0x1F249, 'X'), - (0x1F250, 'M', u'得'), - (0x1F251, 'M', u'可'), + (0x1F250, 'M', '得'), + (0x1F251, 'M', '可'), (0x1F252, 'X'), (0x1F260, 'V'), (0x1F266, 'X'), (0x1F300, 'V'), - (0x1F6D5, 'X'), + (0x1F6D8, 'X'), (0x1F6E0, 'V'), (0x1F6ED, 'X'), (0x1F6F0, 'V'), - (0x1F6FA, 'X'), + (0x1F6FD, 'X'), (0x1F700, 'V'), (0x1F774, 'X'), (0x1F780, 'V'), (0x1F7D9, 'X'), + (0x1F7E0, 'V'), + (0x1F7EC, 'X'), (0x1F800, 'V'), (0x1F80C, 'X'), (0x1F810, 'V'), @@ -7531,28 +7736,52 @@ def _seg_72(): (0x1F888, 'X'), (0x1F890, 'V'), (0x1F8AE, 'X'), + (0x1F8B0, 'V'), + (0x1F8B2, 'X'), (0x1F900, 'V'), - (0x1F90C, 'X'), - (0x1F910, 'V'), - (0x1F93F, 'X'), - (0x1F940, 'V'), - (0x1F971, 'X'), - (0x1F973, 'V'), - (0x1F977, 'X'), + (0x1F979, 'X'), (0x1F97A, 'V'), - (0x1F97B, 'X'), - (0x1F97C, 'V'), - (0x1F9A3, 'X'), - (0x1F9B0, 'V'), - (0x1F9BA, 'X'), - (0x1F9C0, 'V'), - (0x1F9C3, 'X'), - (0x1F9D0, 'V'), - (0x1FA00, 'X'), + (0x1F9CC, 'X'), + (0x1F9CD, 'V'), + (0x1FA54, 'X'), (0x1FA60, 'V'), (0x1FA6E, 'X'), + (0x1FA70, 'V'), + (0x1FA75, 'X'), + (0x1FA78, 'V'), + (0x1FA7B, 'X'), + (0x1FA80, 'V'), + (0x1FA87, 'X'), + (0x1FA90, 'V'), + (0x1FAA9, 'X'), + (0x1FAB0, 'V'), + (0x1FAB7, 'X'), + (0x1FAC0, 'V'), + (0x1FAC3, 'X'), + (0x1FAD0, 'V'), + (0x1FAD7, 'X'), + (0x1FB00, 'V'), + (0x1FB93, 'X'), + (0x1FB94, 'V'), + (0x1FBCB, 'X'), + (0x1FBF0, 'M', '0'), + (0x1FBF1, 'M', '1'), + (0x1FBF2, 'M', '2'), + (0x1FBF3, 'M', '3'), + (0x1FBF4, 'M', '4'), + (0x1FBF5, 'M', '5'), + (0x1FBF6, 'M', '6'), + (0x1FBF7, 'M', '7'), + (0x1FBF8, 'M', '8'), + (0x1FBF9, 'M', '9'), + ] + +def _seg_74(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ + (0x1FBFA, 'X'), (0x20000, 'V'), - (0x2A6D7, 'X'), + (0x2A6DE, 'X'), (0x2A700, 'V'), (0x2B735, 'X'), (0x2B740, 'V'), @@ -7561,564 +7790,567 @@ def _seg_72(): (0x2CEA2, 'X'), (0x2CEB0, 'V'), (0x2EBE1, 'X'), - (0x2F800, 'M', u'丽'), - (0x2F801, 'M', u'丸'), - (0x2F802, 'M', u'乁'), - (0x2F803, 'M', u'𠄢'), - (0x2F804, 'M', u'你'), - (0x2F805, 'M', u'侮'), - (0x2F806, 'M', u'侻'), - (0x2F807, 'M', u'倂'), - (0x2F808, 'M', u'偺'), - (0x2F809, 'M', u'備'), - (0x2F80A, 'M', u'僧'), - (0x2F80B, 'M', u'像'), - (0x2F80C, 'M', u'㒞'), - (0x2F80D, 'M', u'𠘺'), - (0x2F80E, 'M', u'免'), - (0x2F80F, 'M', u'兔'), - (0x2F810, 'M', u'兤'), - (0x2F811, 'M', u'具'), - (0x2F812, 'M', u'𠔜'), - (0x2F813, 'M', u'㒹'), - (0x2F814, 'M', u'內'), - (0x2F815, 'M', u'再'), - (0x2F816, 'M', u'𠕋'), - (0x2F817, 'M', u'冗'), - (0x2F818, 'M', u'冤'), - (0x2F819, 'M', u'仌'), - (0x2F81A, 'M', u'冬'), - (0x2F81B, 'M', u'况'), - (0x2F81C, 'M', u'𩇟'), - (0x2F81D, 'M', u'凵'), - (0x2F81E, 'M', u'刃'), - (0x2F81F, 'M', u'㓟'), - (0x2F820, 'M', u'刻'), - (0x2F821, 'M', u'剆'), - ] - -def _seg_73(): - return [ - (0x2F822, 'M', u'割'), - (0x2F823, 'M', u'剷'), - (0x2F824, 'M', u'㔕'), - (0x2F825, 'M', u'勇'), - (0x2F826, 'M', u'勉'), - (0x2F827, 'M', u'勤'), - (0x2F828, 'M', u'勺'), - (0x2F829, 'M', u'包'), - (0x2F82A, 'M', u'匆'), - (0x2F82B, 'M', u'北'), - (0x2F82C, 'M', u'卉'), - (0x2F82D, 'M', u'卑'), - (0x2F82E, 'M', u'博'), - (0x2F82F, 'M', u'即'), - (0x2F830, 'M', u'卽'), - (0x2F831, 'M', u'卿'), - (0x2F834, 'M', u'𠨬'), - (0x2F835, 'M', u'灰'), - (0x2F836, 'M', u'及'), - (0x2F837, 'M', u'叟'), - (0x2F838, 'M', u'𠭣'), - (0x2F839, 'M', u'叫'), - (0x2F83A, 'M', u'叱'), - (0x2F83B, 'M', u'吆'), - (0x2F83C, 'M', u'咞'), - (0x2F83D, 'M', u'吸'), - (0x2F83E, 'M', u'呈'), - (0x2F83F, 'M', u'周'), - (0x2F840, 'M', u'咢'), - (0x2F841, 'M', u'哶'), - (0x2F842, 'M', u'唐'), - (0x2F843, 'M', u'啓'), - (0x2F844, 'M', u'啣'), - (0x2F845, 'M', u'善'), - (0x2F847, 'M', u'喙'), - (0x2F848, 'M', u'喫'), - (0x2F849, 'M', u'喳'), - (0x2F84A, 'M', u'嗂'), - (0x2F84B, 'M', u'圖'), - (0x2F84C, 'M', u'嘆'), - (0x2F84D, 'M', u'圗'), - (0x2F84E, 'M', u'噑'), - (0x2F84F, 'M', u'噴'), - (0x2F850, 'M', u'切'), - (0x2F851, 'M', u'壮'), - (0x2F852, 'M', u'城'), - (0x2F853, 'M', u'埴'), - (0x2F854, 'M', u'堍'), - (0x2F855, 'M', u'型'), - (0x2F856, 'M', u'堲'), - (0x2F857, 'M', u'報'), - (0x2F858, 'M', u'墬'), - (0x2F859, 'M', u'𡓤'), - (0x2F85A, 'M', u'売'), - (0x2F85B, 'M', u'壷'), - (0x2F85C, 'M', u'夆'), - (0x2F85D, 'M', u'多'), - (0x2F85E, 'M', u'夢'), - (0x2F85F, 'M', u'奢'), - (0x2F860, 'M', u'𡚨'), - (0x2F861, 'M', u'𡛪'), - (0x2F862, 'M', u'姬'), - (0x2F863, 'M', u'娛'), - (0x2F864, 'M', u'娧'), - (0x2F865, 'M', u'姘'), - (0x2F866, 'M', u'婦'), - (0x2F867, 'M', u'㛮'), - (0x2F868, 'X'), - (0x2F869, 'M', u'嬈'), - (0x2F86A, 'M', u'嬾'), - (0x2F86C, 'M', u'𡧈'), - (0x2F86D, 'M', u'寃'), - (0x2F86E, 'M', u'寘'), - (0x2F86F, 'M', u'寧'), - (0x2F870, 'M', u'寳'), - (0x2F871, 'M', u'𡬘'), - (0x2F872, 'M', u'寿'), - (0x2F873, 'M', u'将'), - (0x2F874, 'X'), - (0x2F875, 'M', u'尢'), - (0x2F876, 'M', u'㞁'), - (0x2F877, 'M', u'屠'), - (0x2F878, 'M', u'屮'), - (0x2F879, 'M', u'峀'), - (0x2F87A, 'M', u'岍'), - (0x2F87B, 'M', u'𡷤'), - (0x2F87C, 'M', u'嵃'), - (0x2F87D, 'M', u'𡷦'), - (0x2F87E, 'M', u'嵮'), - (0x2F87F, 'M', u'嵫'), - (0x2F880, 'M', u'嵼'), - (0x2F881, 'M', u'巡'), - (0x2F882, 'M', u'巢'), - (0x2F883, 'M', u'㠯'), - (0x2F884, 'M', u'巽'), - (0x2F885, 'M', u'帨'), - (0x2F886, 'M', u'帽'), - (0x2F887, 'M', u'幩'), - (0x2F888, 'M', u'㡢'), - (0x2F889, 'M', u'𢆃'), - ] - -def _seg_74(): - return [ - (0x2F88A, 'M', u'㡼'), - (0x2F88B, 'M', u'庰'), - (0x2F88C, 'M', u'庳'), - (0x2F88D, 'M', u'庶'), - (0x2F88E, 'M', u'廊'), - (0x2F88F, 'M', u'𪎒'), - (0x2F890, 'M', u'廾'), - (0x2F891, 'M', u'𢌱'), - (0x2F893, 'M', u'舁'), - (0x2F894, 'M', u'弢'), - (0x2F896, 'M', u'㣇'), - (0x2F897, 'M', u'𣊸'), - (0x2F898, 'M', u'𦇚'), - (0x2F899, 'M', u'形'), - (0x2F89A, 'M', u'彫'), - (0x2F89B, 'M', u'㣣'), - (0x2F89C, 'M', u'徚'), - (0x2F89D, 'M', u'忍'), - (0x2F89E, 'M', u'志'), - (0x2F89F, 'M', u'忹'), - (0x2F8A0, 'M', u'悁'), - (0x2F8A1, 'M', u'㤺'), - (0x2F8A2, 'M', u'㤜'), - (0x2F8A3, 'M', u'悔'), - (0x2F8A4, 'M', u'𢛔'), - (0x2F8A5, 'M', u'惇'), - (0x2F8A6, 'M', u'慈'), - (0x2F8A7, 'M', u'慌'), - (0x2F8A8, 'M', u'慎'), - (0x2F8A9, 'M', u'慌'), - (0x2F8AA, 'M', u'慺'), - (0x2F8AB, 'M', u'憎'), - (0x2F8AC, 'M', u'憲'), - (0x2F8AD, 'M', u'憤'), - (0x2F8AE, 'M', u'憯'), - (0x2F8AF, 'M', u'懞'), - (0x2F8B0, 'M', u'懲'), - (0x2F8B1, 'M', u'懶'), - (0x2F8B2, 'M', u'成'), - (0x2F8B3, 'M', u'戛'), - (0x2F8B4, 'M', u'扝'), - (0x2F8B5, 'M', u'抱'), - (0x2F8B6, 'M', u'拔'), - (0x2F8B7, 'M', u'捐'), - (0x2F8B8, 'M', u'𢬌'), - (0x2F8B9, 'M', u'挽'), - (0x2F8BA, 'M', u'拼'), - (0x2F8BB, 'M', u'捨'), - (0x2F8BC, 'M', u'掃'), - (0x2F8BD, 'M', u'揤'), - (0x2F8BE, 'M', u'𢯱'), - (0x2F8BF, 'M', u'搢'), - (0x2F8C0, 'M', u'揅'), - (0x2F8C1, 'M', u'掩'), - (0x2F8C2, 'M', u'㨮'), - (0x2F8C3, 'M', u'摩'), - (0x2F8C4, 'M', u'摾'), - (0x2F8C5, 'M', u'撝'), - (0x2F8C6, 'M', u'摷'), - (0x2F8C7, 'M', u'㩬'), - (0x2F8C8, 'M', u'敏'), - (0x2F8C9, 'M', u'敬'), - (0x2F8CA, 'M', u'𣀊'), - (0x2F8CB, 'M', u'旣'), - (0x2F8CC, 'M', u'書'), - (0x2F8CD, 'M', u'晉'), - (0x2F8CE, 'M', u'㬙'), - (0x2F8CF, 'M', u'暑'), - (0x2F8D0, 'M', u'㬈'), - (0x2F8D1, 'M', u'㫤'), - (0x2F8D2, 'M', u'冒'), - (0x2F8D3, 'M', u'冕'), - (0x2F8D4, 'M', u'最'), - (0x2F8D5, 'M', u'暜'), - (0x2F8D6, 'M', u'肭'), - (0x2F8D7, 'M', u'䏙'), - (0x2F8D8, 'M', u'朗'), - (0x2F8D9, 'M', u'望'), - (0x2F8DA, 'M', u'朡'), - (0x2F8DB, 'M', u'杞'), - (0x2F8DC, 'M', u'杓'), - (0x2F8DD, 'M', u'𣏃'), - (0x2F8DE, 'M', u'㭉'), - (0x2F8DF, 'M', u'柺'), - (0x2F8E0, 'M', u'枅'), - (0x2F8E1, 'M', u'桒'), - (0x2F8E2, 'M', u'梅'), - (0x2F8E3, 'M', u'𣑭'), - (0x2F8E4, 'M', u'梎'), - (0x2F8E5, 'M', u'栟'), - (0x2F8E6, 'M', u'椔'), - (0x2F8E7, 'M', u'㮝'), - (0x2F8E8, 'M', u'楂'), - (0x2F8E9, 'M', u'榣'), - (0x2F8EA, 'M', u'槪'), - (0x2F8EB, 'M', u'檨'), - (0x2F8EC, 'M', u'𣚣'), - (0x2F8ED, 'M', u'櫛'), - (0x2F8EE, 'M', u'㰘'), - (0x2F8EF, 'M', u'次'), + (0x2F800, 'M', '丽'), + (0x2F801, 'M', '丸'), + (0x2F802, 'M', '乁'), + (0x2F803, 'M', '𠄢'), + (0x2F804, 'M', '你'), + (0x2F805, 'M', '侮'), + (0x2F806, 'M', '侻'), + (0x2F807, 'M', '倂'), + (0x2F808, 'M', '偺'), + (0x2F809, 'M', '備'), + (0x2F80A, 'M', '僧'), + (0x2F80B, 'M', '像'), + (0x2F80C, 'M', '㒞'), + (0x2F80D, 'M', '𠘺'), + (0x2F80E, 'M', '免'), + (0x2F80F, 'M', '兔'), + (0x2F810, 'M', '兤'), + (0x2F811, 'M', '具'), + (0x2F812, 'M', '𠔜'), + (0x2F813, 'M', '㒹'), + (0x2F814, 'M', '內'), + (0x2F815, 'M', '再'), + (0x2F816, 'M', '𠕋'), + (0x2F817, 'M', '冗'), + (0x2F818, 'M', '冤'), + (0x2F819, 'M', '仌'), + (0x2F81A, 'M', '冬'), + (0x2F81B, 'M', '况'), + (0x2F81C, 'M', '𩇟'), + (0x2F81D, 'M', '凵'), + (0x2F81E, 'M', '刃'), + (0x2F81F, 'M', '㓟'), + (0x2F820, 'M', '刻'), + (0x2F821, 'M', '剆'), + (0x2F822, 'M', '割'), + (0x2F823, 'M', '剷'), + (0x2F824, 'M', '㔕'), + (0x2F825, 'M', '勇'), + (0x2F826, 'M', '勉'), + (0x2F827, 'M', '勤'), + (0x2F828, 'M', '勺'), + (0x2F829, 'M', '包'), + (0x2F82A, 'M', '匆'), + (0x2F82B, 'M', '北'), + (0x2F82C, 'M', '卉'), + (0x2F82D, 'M', '卑'), + (0x2F82E, 'M', '博'), + (0x2F82F, 'M', '即'), + (0x2F830, 'M', '卽'), + (0x2F831, 'M', '卿'), + (0x2F834, 'M', '𠨬'), + (0x2F835, 'M', '灰'), + (0x2F836, 'M', '及'), + (0x2F837, 'M', '叟'), + (0x2F838, 'M', '𠭣'), + (0x2F839, 'M', '叫'), + (0x2F83A, 'M', '叱'), + (0x2F83B, 'M', '吆'), + (0x2F83C, 'M', '咞'), + (0x2F83D, 'M', '吸'), + (0x2F83E, 'M', '呈'), + (0x2F83F, 'M', '周'), + (0x2F840, 'M', '咢'), + (0x2F841, 'M', '哶'), + (0x2F842, 'M', '唐'), + (0x2F843, 'M', '啓'), + (0x2F844, 'M', '啣'), + (0x2F845, 'M', '善'), + (0x2F847, 'M', '喙'), + (0x2F848, 'M', '喫'), + (0x2F849, 'M', '喳'), + (0x2F84A, 'M', '嗂'), + (0x2F84B, 'M', '圖'), + (0x2F84C, 'M', '嘆'), + (0x2F84D, 'M', '圗'), + (0x2F84E, 'M', '噑'), + (0x2F84F, 'M', '噴'), + (0x2F850, 'M', '切'), + (0x2F851, 'M', '壮'), + (0x2F852, 'M', '城'), + (0x2F853, 'M', '埴'), + (0x2F854, 'M', '堍'), + (0x2F855, 'M', '型'), + (0x2F856, 'M', '堲'), + (0x2F857, 'M', '報'), + (0x2F858, 'M', '墬'), + (0x2F859, 'M', '𡓤'), + (0x2F85A, 'M', '売'), + (0x2F85B, 'M', '壷'), ] def _seg_75(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x2F8F0, 'M', u'𣢧'), - (0x2F8F1, 'M', u'歔'), - (0x2F8F2, 'M', u'㱎'), - (0x2F8F3, 'M', u'歲'), - (0x2F8F4, 'M', u'殟'), - (0x2F8F5, 'M', u'殺'), - (0x2F8F6, 'M', u'殻'), - (0x2F8F7, 'M', u'𣪍'), - (0x2F8F8, 'M', u'𡴋'), - (0x2F8F9, 'M', u'𣫺'), - (0x2F8FA, 'M', u'汎'), - (0x2F8FB, 'M', u'𣲼'), - (0x2F8FC, 'M', u'沿'), - (0x2F8FD, 'M', u'泍'), - (0x2F8FE, 'M', u'汧'), - (0x2F8FF, 'M', u'洖'), - (0x2F900, 'M', u'派'), - (0x2F901, 'M', u'海'), - (0x2F902, 'M', u'流'), - (0x2F903, 'M', u'浩'), - (0x2F904, 'M', u'浸'), - (0x2F905, 'M', u'涅'), - (0x2F906, 'M', u'𣴞'), - (0x2F907, 'M', u'洴'), - (0x2F908, 'M', u'港'), - (0x2F909, 'M', u'湮'), - (0x2F90A, 'M', u'㴳'), - (0x2F90B, 'M', u'滋'), - (0x2F90C, 'M', u'滇'), - (0x2F90D, 'M', u'𣻑'), - (0x2F90E, 'M', u'淹'), - (0x2F90F, 'M', u'潮'), - (0x2F910, 'M', u'𣽞'), - (0x2F911, 'M', u'𣾎'), - (0x2F912, 'M', u'濆'), - (0x2F913, 'M', u'瀹'), - (0x2F914, 'M', u'瀞'), - (0x2F915, 'M', u'瀛'), - (0x2F916, 'M', u'㶖'), - (0x2F917, 'M', u'灊'), - (0x2F918, 'M', u'災'), - (0x2F919, 'M', u'灷'), - (0x2F91A, 'M', u'炭'), - (0x2F91B, 'M', u'𠔥'), - (0x2F91C, 'M', u'煅'), - (0x2F91D, 'M', u'𤉣'), - (0x2F91E, 'M', u'熜'), - (0x2F91F, 'X'), - (0x2F920, 'M', u'爨'), - (0x2F921, 'M', u'爵'), - (0x2F922, 'M', u'牐'), - (0x2F923, 'M', u'𤘈'), - (0x2F924, 'M', u'犀'), - (0x2F925, 'M', u'犕'), - (0x2F926, 'M', u'𤜵'), - (0x2F927, 'M', u'𤠔'), - (0x2F928, 'M', u'獺'), - (0x2F929, 'M', u'王'), - (0x2F92A, 'M', u'㺬'), - (0x2F92B, 'M', u'玥'), - (0x2F92C, 'M', u'㺸'), - (0x2F92E, 'M', u'瑇'), - (0x2F92F, 'M', u'瑜'), - (0x2F930, 'M', u'瑱'), - (0x2F931, 'M', u'璅'), - (0x2F932, 'M', u'瓊'), - (0x2F933, 'M', u'㼛'), - (0x2F934, 'M', u'甤'), - (0x2F935, 'M', u'𤰶'), - (0x2F936, 'M', u'甾'), - (0x2F937, 'M', u'𤲒'), - (0x2F938, 'M', u'異'), - (0x2F939, 'M', u'𢆟'), - (0x2F93A, 'M', u'瘐'), - (0x2F93B, 'M', u'𤾡'), - (0x2F93C, 'M', u'𤾸'), - (0x2F93D, 'M', u'𥁄'), - (0x2F93E, 'M', u'㿼'), - (0x2F93F, 'M', u'䀈'), - (0x2F940, 'M', u'直'), - (0x2F941, 'M', u'𥃳'), - (0x2F942, 'M', u'𥃲'), - (0x2F943, 'M', u'𥄙'), - (0x2F944, 'M', u'𥄳'), - (0x2F945, 'M', u'眞'), - (0x2F946, 'M', u'真'), - (0x2F948, 'M', u'睊'), - (0x2F949, 'M', u'䀹'), - (0x2F94A, 'M', u'瞋'), - (0x2F94B, 'M', u'䁆'), - (0x2F94C, 'M', u'䂖'), - (0x2F94D, 'M', u'𥐝'), - (0x2F94E, 'M', u'硎'), - (0x2F94F, 'M', u'碌'), - (0x2F950, 'M', u'磌'), - (0x2F951, 'M', u'䃣'), - (0x2F952, 'M', u'𥘦'), - (0x2F953, 'M', u'祖'), - (0x2F954, 'M', u'𥚚'), - (0x2F955, 'M', u'𥛅'), + (0x2F85C, 'M', '夆'), + (0x2F85D, 'M', '多'), + (0x2F85E, 'M', '夢'), + (0x2F85F, 'M', '奢'), + (0x2F860, 'M', '𡚨'), + (0x2F861, 'M', '𡛪'), + (0x2F862, 'M', '姬'), + (0x2F863, 'M', '娛'), + (0x2F864, 'M', '娧'), + (0x2F865, 'M', '姘'), + (0x2F866, 'M', '婦'), + (0x2F867, 'M', '㛮'), + (0x2F868, 'X'), + (0x2F869, 'M', '嬈'), + (0x2F86A, 'M', '嬾'), + (0x2F86C, 'M', '𡧈'), + (0x2F86D, 'M', '寃'), + (0x2F86E, 'M', '寘'), + (0x2F86F, 'M', '寧'), + (0x2F870, 'M', '寳'), + (0x2F871, 'M', '𡬘'), + (0x2F872, 'M', '寿'), + (0x2F873, 'M', '将'), + (0x2F874, 'X'), + (0x2F875, 'M', '尢'), + (0x2F876, 'M', '㞁'), + (0x2F877, 'M', '屠'), + (0x2F878, 'M', '屮'), + (0x2F879, 'M', '峀'), + (0x2F87A, 'M', '岍'), + (0x2F87B, 'M', '𡷤'), + (0x2F87C, 'M', '嵃'), + (0x2F87D, 'M', '𡷦'), + (0x2F87E, 'M', '嵮'), + (0x2F87F, 'M', '嵫'), + (0x2F880, 'M', '嵼'), + (0x2F881, 'M', '巡'), + (0x2F882, 'M', '巢'), + (0x2F883, 'M', '㠯'), + (0x2F884, 'M', '巽'), + (0x2F885, 'M', '帨'), + (0x2F886, 'M', '帽'), + (0x2F887, 'M', '幩'), + (0x2F888, 'M', '㡢'), + (0x2F889, 'M', '𢆃'), + (0x2F88A, 'M', '㡼'), + (0x2F88B, 'M', '庰'), + (0x2F88C, 'M', '庳'), + (0x2F88D, 'M', '庶'), + (0x2F88E, 'M', '廊'), + (0x2F88F, 'M', '𪎒'), + (0x2F890, 'M', '廾'), + (0x2F891, 'M', '𢌱'), + (0x2F893, 'M', '舁'), + (0x2F894, 'M', '弢'), + (0x2F896, 'M', '㣇'), + (0x2F897, 'M', '𣊸'), + (0x2F898, 'M', '𦇚'), + (0x2F899, 'M', '形'), + (0x2F89A, 'M', '彫'), + (0x2F89B, 'M', '㣣'), + (0x2F89C, 'M', '徚'), + (0x2F89D, 'M', '忍'), + (0x2F89E, 'M', '志'), + (0x2F89F, 'M', '忹'), + (0x2F8A0, 'M', '悁'), + (0x2F8A1, 'M', '㤺'), + (0x2F8A2, 'M', '㤜'), + (0x2F8A3, 'M', '悔'), + (0x2F8A4, 'M', '𢛔'), + (0x2F8A5, 'M', '惇'), + (0x2F8A6, 'M', '慈'), + (0x2F8A7, 'M', '慌'), + (0x2F8A8, 'M', '慎'), + (0x2F8A9, 'M', '慌'), + (0x2F8AA, 'M', '慺'), + (0x2F8AB, 'M', '憎'), + (0x2F8AC, 'M', '憲'), + (0x2F8AD, 'M', '憤'), + (0x2F8AE, 'M', '憯'), + (0x2F8AF, 'M', '懞'), + (0x2F8B0, 'M', '懲'), + (0x2F8B1, 'M', '懶'), + (0x2F8B2, 'M', '成'), + (0x2F8B3, 'M', '戛'), + (0x2F8B4, 'M', '扝'), + (0x2F8B5, 'M', '抱'), + (0x2F8B6, 'M', '拔'), + (0x2F8B7, 'M', '捐'), + (0x2F8B8, 'M', '𢬌'), + (0x2F8B9, 'M', '挽'), + (0x2F8BA, 'M', '拼'), + (0x2F8BB, 'M', '捨'), + (0x2F8BC, 'M', '掃'), + (0x2F8BD, 'M', '揤'), + (0x2F8BE, 'M', '𢯱'), + (0x2F8BF, 'M', '搢'), + (0x2F8C0, 'M', '揅'), + (0x2F8C1, 'M', '掩'), + (0x2F8C2, 'M', '㨮'), ] def _seg_76(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x2F956, 'M', u'福'), - (0x2F957, 'M', u'秫'), - (0x2F958, 'M', u'䄯'), - (0x2F959, 'M', u'穀'), - (0x2F95A, 'M', u'穊'), - (0x2F95B, 'M', u'穏'), - (0x2F95C, 'M', u'𥥼'), - (0x2F95D, 'M', u'𥪧'), - (0x2F95F, 'X'), - (0x2F960, 'M', u'䈂'), - (0x2F961, 'M', u'𥮫'), - (0x2F962, 'M', u'篆'), - (0x2F963, 'M', u'築'), - (0x2F964, 'M', u'䈧'), - (0x2F965, 'M', u'𥲀'), - (0x2F966, 'M', u'糒'), - (0x2F967, 'M', u'䊠'), - (0x2F968, 'M', u'糨'), - (0x2F969, 'M', u'糣'), - (0x2F96A, 'M', u'紀'), - (0x2F96B, 'M', u'𥾆'), - (0x2F96C, 'M', u'絣'), - (0x2F96D, 'M', u'䌁'), - (0x2F96E, 'M', u'緇'), - (0x2F96F, 'M', u'縂'), - (0x2F970, 'M', u'繅'), - (0x2F971, 'M', u'䌴'), - (0x2F972, 'M', u'𦈨'), - (0x2F973, 'M', u'𦉇'), - (0x2F974, 'M', u'䍙'), - (0x2F975, 'M', u'𦋙'), - (0x2F976, 'M', u'罺'), - (0x2F977, 'M', u'𦌾'), - (0x2F978, 'M', u'羕'), - (0x2F979, 'M', u'翺'), - (0x2F97A, 'M', u'者'), - (0x2F97B, 'M', u'𦓚'), - (0x2F97C, 'M', u'𦔣'), - (0x2F97D, 'M', u'聠'), - (0x2F97E, 'M', u'𦖨'), - (0x2F97F, 'M', u'聰'), - (0x2F980, 'M', u'𣍟'), - (0x2F981, 'M', u'䏕'), - (0x2F982, 'M', u'育'), - (0x2F983, 'M', u'脃'), - (0x2F984, 'M', u'䐋'), - (0x2F985, 'M', u'脾'), - (0x2F986, 'M', u'媵'), - (0x2F987, 'M', u'𦞧'), - (0x2F988, 'M', u'𦞵'), - (0x2F989, 'M', u'𣎓'), - (0x2F98A, 'M', u'𣎜'), - (0x2F98B, 'M', u'舁'), - (0x2F98C, 'M', u'舄'), - (0x2F98D, 'M', u'辞'), - (0x2F98E, 'M', u'䑫'), - (0x2F98F, 'M', u'芑'), - (0x2F990, 'M', u'芋'), - (0x2F991, 'M', u'芝'), - (0x2F992, 'M', u'劳'), - (0x2F993, 'M', u'花'), - (0x2F994, 'M', u'芳'), - (0x2F995, 'M', u'芽'), - (0x2F996, 'M', u'苦'), - (0x2F997, 'M', u'𦬼'), - (0x2F998, 'M', u'若'), - (0x2F999, 'M', u'茝'), - (0x2F99A, 'M', u'荣'), - (0x2F99B, 'M', u'莭'), - (0x2F99C, 'M', u'茣'), - (0x2F99D, 'M', u'莽'), - (0x2F99E, 'M', u'菧'), - (0x2F99F, 'M', u'著'), - (0x2F9A0, 'M', u'荓'), - (0x2F9A1, 'M', u'菊'), - (0x2F9A2, 'M', u'菌'), - (0x2F9A3, 'M', u'菜'), - (0x2F9A4, 'M', u'𦰶'), - (0x2F9A5, 'M', u'𦵫'), - (0x2F9A6, 'M', u'𦳕'), - (0x2F9A7, 'M', u'䔫'), - (0x2F9A8, 'M', u'蓱'), - (0x2F9A9, 'M', u'蓳'), - (0x2F9AA, 'M', u'蔖'), - (0x2F9AB, 'M', u'𧏊'), - (0x2F9AC, 'M', u'蕤'), - (0x2F9AD, 'M', u'𦼬'), - (0x2F9AE, 'M', u'䕝'), - (0x2F9AF, 'M', u'䕡'), - (0x2F9B0, 'M', u'𦾱'), - (0x2F9B1, 'M', u'𧃒'), - (0x2F9B2, 'M', u'䕫'), - (0x2F9B3, 'M', u'虐'), - (0x2F9B4, 'M', u'虜'), - (0x2F9B5, 'M', u'虧'), - (0x2F9B6, 'M', u'虩'), - (0x2F9B7, 'M', u'蚩'), - (0x2F9B8, 'M', u'蚈'), - (0x2F9B9, 'M', u'蜎'), - (0x2F9BA, 'M', u'蛢'), + (0x2F8C3, 'M', '摩'), + (0x2F8C4, 'M', '摾'), + (0x2F8C5, 'M', '撝'), + (0x2F8C6, 'M', '摷'), + (0x2F8C7, 'M', '㩬'), + (0x2F8C8, 'M', '敏'), + (0x2F8C9, 'M', '敬'), + (0x2F8CA, 'M', '𣀊'), + (0x2F8CB, 'M', '旣'), + (0x2F8CC, 'M', '書'), + (0x2F8CD, 'M', '晉'), + (0x2F8CE, 'M', '㬙'), + (0x2F8CF, 'M', '暑'), + (0x2F8D0, 'M', '㬈'), + (0x2F8D1, 'M', '㫤'), + (0x2F8D2, 'M', '冒'), + (0x2F8D3, 'M', '冕'), + (0x2F8D4, 'M', '最'), + (0x2F8D5, 'M', '暜'), + (0x2F8D6, 'M', '肭'), + (0x2F8D7, 'M', '䏙'), + (0x2F8D8, 'M', '朗'), + (0x2F8D9, 'M', '望'), + (0x2F8DA, 'M', '朡'), + (0x2F8DB, 'M', '杞'), + (0x2F8DC, 'M', '杓'), + (0x2F8DD, 'M', '𣏃'), + (0x2F8DE, 'M', '㭉'), + (0x2F8DF, 'M', '柺'), + (0x2F8E0, 'M', '枅'), + (0x2F8E1, 'M', '桒'), + (0x2F8E2, 'M', '梅'), + (0x2F8E3, 'M', '𣑭'), + (0x2F8E4, 'M', '梎'), + (0x2F8E5, 'M', '栟'), + (0x2F8E6, 'M', '椔'), + (0x2F8E7, 'M', '㮝'), + (0x2F8E8, 'M', '楂'), + (0x2F8E9, 'M', '榣'), + (0x2F8EA, 'M', '槪'), + (0x2F8EB, 'M', '檨'), + (0x2F8EC, 'M', '𣚣'), + (0x2F8ED, 'M', '櫛'), + (0x2F8EE, 'M', '㰘'), + (0x2F8EF, 'M', '次'), + (0x2F8F0, 'M', '𣢧'), + (0x2F8F1, 'M', '歔'), + (0x2F8F2, 'M', '㱎'), + (0x2F8F3, 'M', '歲'), + (0x2F8F4, 'M', '殟'), + (0x2F8F5, 'M', '殺'), + (0x2F8F6, 'M', '殻'), + (0x2F8F7, 'M', '𣪍'), + (0x2F8F8, 'M', '𡴋'), + (0x2F8F9, 'M', '𣫺'), + (0x2F8FA, 'M', '汎'), + (0x2F8FB, 'M', '𣲼'), + (0x2F8FC, 'M', '沿'), + (0x2F8FD, 'M', '泍'), + (0x2F8FE, 'M', '汧'), + (0x2F8FF, 'M', '洖'), + (0x2F900, 'M', '派'), + (0x2F901, 'M', '海'), + (0x2F902, 'M', '流'), + (0x2F903, 'M', '浩'), + (0x2F904, 'M', '浸'), + (0x2F905, 'M', '涅'), + (0x2F906, 'M', '𣴞'), + (0x2F907, 'M', '洴'), + (0x2F908, 'M', '港'), + (0x2F909, 'M', '湮'), + (0x2F90A, 'M', '㴳'), + (0x2F90B, 'M', '滋'), + (0x2F90C, 'M', '滇'), + (0x2F90D, 'M', '𣻑'), + (0x2F90E, 'M', '淹'), + (0x2F90F, 'M', '潮'), + (0x2F910, 'M', '𣽞'), + (0x2F911, 'M', '𣾎'), + (0x2F912, 'M', '濆'), + (0x2F913, 'M', '瀹'), + (0x2F914, 'M', '瀞'), + (0x2F915, 'M', '瀛'), + (0x2F916, 'M', '㶖'), + (0x2F917, 'M', '灊'), + (0x2F918, 'M', '災'), + (0x2F919, 'M', '灷'), + (0x2F91A, 'M', '炭'), + (0x2F91B, 'M', '𠔥'), + (0x2F91C, 'M', '煅'), + (0x2F91D, 'M', '𤉣'), + (0x2F91E, 'M', '熜'), + (0x2F91F, 'X'), + (0x2F920, 'M', '爨'), + (0x2F921, 'M', '爵'), + (0x2F922, 'M', '牐'), + (0x2F923, 'M', '𤘈'), + (0x2F924, 'M', '犀'), + (0x2F925, 'M', '犕'), + (0x2F926, 'M', '𤜵'), ] def _seg_77(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x2F9BB, 'M', u'蝹'), - (0x2F9BC, 'M', u'蜨'), - (0x2F9BD, 'M', u'蝫'), - (0x2F9BE, 'M', u'螆'), - (0x2F9BF, 'X'), - (0x2F9C0, 'M', u'蟡'), - (0x2F9C1, 'M', u'蠁'), - (0x2F9C2, 'M', u'䗹'), - (0x2F9C3, 'M', u'衠'), - (0x2F9C4, 'M', u'衣'), - (0x2F9C5, 'M', u'𧙧'), - (0x2F9C6, 'M', u'裗'), - (0x2F9C7, 'M', u'裞'), - (0x2F9C8, 'M', u'䘵'), - (0x2F9C9, 'M', u'裺'), - (0x2F9CA, 'M', u'㒻'), - (0x2F9CB, 'M', u'𧢮'), - (0x2F9CC, 'M', u'𧥦'), - (0x2F9CD, 'M', u'䚾'), - (0x2F9CE, 'M', u'䛇'), - (0x2F9CF, 'M', u'誠'), - (0x2F9D0, 'M', u'諭'), - (0x2F9D1, 'M', u'變'), - (0x2F9D2, 'M', u'豕'), - (0x2F9D3, 'M', u'𧲨'), - (0x2F9D4, 'M', u'貫'), - (0x2F9D5, 'M', u'賁'), - (0x2F9D6, 'M', u'贛'), - (0x2F9D7, 'M', u'起'), - (0x2F9D8, 'M', u'𧼯'), - (0x2F9D9, 'M', u'𠠄'), - (0x2F9DA, 'M', u'跋'), - (0x2F9DB, 'M', u'趼'), - (0x2F9DC, 'M', u'跰'), - (0x2F9DD, 'M', u'𠣞'), - (0x2F9DE, 'M', u'軔'), - (0x2F9DF, 'M', u'輸'), - (0x2F9E0, 'M', u'𨗒'), - (0x2F9E1, 'M', u'𨗭'), - (0x2F9E2, 'M', u'邔'), - (0x2F9E3, 'M', u'郱'), - (0x2F9E4, 'M', u'鄑'), - (0x2F9E5, 'M', u'𨜮'), - (0x2F9E6, 'M', u'鄛'), - (0x2F9E7, 'M', u'鈸'), - (0x2F9E8, 'M', u'鋗'), - (0x2F9E9, 'M', u'鋘'), - (0x2F9EA, 'M', u'鉼'), - (0x2F9EB, 'M', u'鏹'), - (0x2F9EC, 'M', u'鐕'), - (0x2F9ED, 'M', u'𨯺'), - (0x2F9EE, 'M', u'開'), - (0x2F9EF, 'M', u'䦕'), - (0x2F9F0, 'M', u'閷'), - (0x2F9F1, 'M', u'𨵷'), - (0x2F9F2, 'M', u'䧦'), - (0x2F9F3, 'M', u'雃'), - (0x2F9F4, 'M', u'嶲'), - (0x2F9F5, 'M', u'霣'), - (0x2F9F6, 'M', u'𩅅'), - (0x2F9F7, 'M', u'𩈚'), - (0x2F9F8, 'M', u'䩮'), - (0x2F9F9, 'M', u'䩶'), - (0x2F9FA, 'M', u'韠'), - (0x2F9FB, 'M', u'𩐊'), - (0x2F9FC, 'M', u'䪲'), - (0x2F9FD, 'M', u'𩒖'), - (0x2F9FE, 'M', u'頋'), - (0x2FA00, 'M', u'頩'), - (0x2FA01, 'M', u'𩖶'), - (0x2FA02, 'M', u'飢'), - (0x2FA03, 'M', u'䬳'), - (0x2FA04, 'M', u'餩'), - (0x2FA05, 'M', u'馧'), - (0x2FA06, 'M', u'駂'), - (0x2FA07, 'M', u'駾'), - (0x2FA08, 'M', u'䯎'), - (0x2FA09, 'M', u'𩬰'), - (0x2FA0A, 'M', u'鬒'), - (0x2FA0B, 'M', u'鱀'), - (0x2FA0C, 'M', u'鳽'), - (0x2FA0D, 'M', u'䳎'), - (0x2FA0E, 'M', u'䳭'), - (0x2FA0F, 'M', u'鵧'), - (0x2FA10, 'M', u'𪃎'), - (0x2FA11, 'M', u'䳸'), - (0x2FA12, 'M', u'𪄅'), - (0x2FA13, 'M', u'𪈎'), - (0x2FA14, 'M', u'𪊑'), - (0x2FA15, 'M', u'麻'), - (0x2FA16, 'M', u'䵖'), - (0x2FA17, 'M', u'黹'), - (0x2FA18, 'M', u'黾'), - (0x2FA19, 'M', u'鼅'), - (0x2FA1A, 'M', u'鼏'), - (0x2FA1B, 'M', u'鼖'), - (0x2FA1C, 'M', u'鼻'), - (0x2FA1D, 'M', u'𪘀'), - (0x2FA1E, 'X'), - (0xE0100, 'I'), + (0x2F927, 'M', '𤠔'), + (0x2F928, 'M', '獺'), + (0x2F929, 'M', '王'), + (0x2F92A, 'M', '㺬'), + (0x2F92B, 'M', '玥'), + (0x2F92C, 'M', '㺸'), + (0x2F92E, 'M', '瑇'), + (0x2F92F, 'M', '瑜'), + (0x2F930, 'M', '瑱'), + (0x2F931, 'M', '璅'), + (0x2F932, 'M', '瓊'), + (0x2F933, 'M', '㼛'), + (0x2F934, 'M', '甤'), + (0x2F935, 'M', '𤰶'), + (0x2F936, 'M', '甾'), + (0x2F937, 'M', '𤲒'), + (0x2F938, 'M', '異'), + (0x2F939, 'M', '𢆟'), + (0x2F93A, 'M', '瘐'), + (0x2F93B, 'M', '𤾡'), + (0x2F93C, 'M', '𤾸'), + (0x2F93D, 'M', '𥁄'), + (0x2F93E, 'M', '㿼'), + (0x2F93F, 'M', '䀈'), + (0x2F940, 'M', '直'), + (0x2F941, 'M', '𥃳'), + (0x2F942, 'M', '𥃲'), + (0x2F943, 'M', '𥄙'), + (0x2F944, 'M', '𥄳'), + (0x2F945, 'M', '眞'), + (0x2F946, 'M', '真'), + (0x2F948, 'M', '睊'), + (0x2F949, 'M', '䀹'), + (0x2F94A, 'M', '瞋'), + (0x2F94B, 'M', '䁆'), + (0x2F94C, 'M', '䂖'), + (0x2F94D, 'M', '𥐝'), + (0x2F94E, 'M', '硎'), + (0x2F94F, 'M', '碌'), + (0x2F950, 'M', '磌'), + (0x2F951, 'M', '䃣'), + (0x2F952, 'M', '𥘦'), + (0x2F953, 'M', '祖'), + (0x2F954, 'M', '𥚚'), + (0x2F955, 'M', '𥛅'), + (0x2F956, 'M', '福'), + (0x2F957, 'M', '秫'), + (0x2F958, 'M', '䄯'), + (0x2F959, 'M', '穀'), + (0x2F95A, 'M', '穊'), + (0x2F95B, 'M', '穏'), + (0x2F95C, 'M', '𥥼'), + (0x2F95D, 'M', '𥪧'), + (0x2F95F, 'X'), + (0x2F960, 'M', '䈂'), + (0x2F961, 'M', '𥮫'), + (0x2F962, 'M', '篆'), + (0x2F963, 'M', '築'), + (0x2F964, 'M', '䈧'), + (0x2F965, 'M', '𥲀'), + (0x2F966, 'M', '糒'), + (0x2F967, 'M', '䊠'), + (0x2F968, 'M', '糨'), + (0x2F969, 'M', '糣'), + (0x2F96A, 'M', '紀'), + (0x2F96B, 'M', '𥾆'), + (0x2F96C, 'M', '絣'), + (0x2F96D, 'M', '䌁'), + (0x2F96E, 'M', '緇'), + (0x2F96F, 'M', '縂'), + (0x2F970, 'M', '繅'), + (0x2F971, 'M', '䌴'), + (0x2F972, 'M', '𦈨'), + (0x2F973, 'M', '𦉇'), + (0x2F974, 'M', '䍙'), + (0x2F975, 'M', '𦋙'), + (0x2F976, 'M', '罺'), + (0x2F977, 'M', '𦌾'), + (0x2F978, 'M', '羕'), + (0x2F979, 'M', '翺'), + (0x2F97A, 'M', '者'), + (0x2F97B, 'M', '𦓚'), + (0x2F97C, 'M', '𦔣'), + (0x2F97D, 'M', '聠'), + (0x2F97E, 'M', '𦖨'), + (0x2F97F, 'M', '聰'), + (0x2F980, 'M', '𣍟'), + (0x2F981, 'M', '䏕'), + (0x2F982, 'M', '育'), + (0x2F983, 'M', '脃'), + (0x2F984, 'M', '䐋'), + (0x2F985, 'M', '脾'), + (0x2F986, 'M', '媵'), + (0x2F987, 'M', '𦞧'), + (0x2F988, 'M', '𦞵'), + (0x2F989, 'M', '𣎓'), + (0x2F98A, 'M', '𣎜'), + (0x2F98B, 'M', '舁'), + (0x2F98C, 'M', '舄'), + (0x2F98D, 'M', '辞'), ] def _seg_78(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ + (0x2F98E, 'M', '䑫'), + (0x2F98F, 'M', '芑'), + (0x2F990, 'M', '芋'), + (0x2F991, 'M', '芝'), + (0x2F992, 'M', '劳'), + (0x2F993, 'M', '花'), + (0x2F994, 'M', '芳'), + (0x2F995, 'M', '芽'), + (0x2F996, 'M', '苦'), + (0x2F997, 'M', '𦬼'), + (0x2F998, 'M', '若'), + (0x2F999, 'M', '茝'), + (0x2F99A, 'M', '荣'), + (0x2F99B, 'M', '莭'), + (0x2F99C, 'M', '茣'), + (0x2F99D, 'M', '莽'), + (0x2F99E, 'M', '菧'), + (0x2F99F, 'M', '著'), + (0x2F9A0, 'M', '荓'), + (0x2F9A1, 'M', '菊'), + (0x2F9A2, 'M', '菌'), + (0x2F9A3, 'M', '菜'), + (0x2F9A4, 'M', '𦰶'), + (0x2F9A5, 'M', '𦵫'), + (0x2F9A6, 'M', '𦳕'), + (0x2F9A7, 'M', '䔫'), + (0x2F9A8, 'M', '蓱'), + (0x2F9A9, 'M', '蓳'), + (0x2F9AA, 'M', '蔖'), + (0x2F9AB, 'M', '𧏊'), + (0x2F9AC, 'M', '蕤'), + (0x2F9AD, 'M', '𦼬'), + (0x2F9AE, 'M', '䕝'), + (0x2F9AF, 'M', '䕡'), + (0x2F9B0, 'M', '𦾱'), + (0x2F9B1, 'M', '𧃒'), + (0x2F9B2, 'M', '䕫'), + (0x2F9B3, 'M', '虐'), + (0x2F9B4, 'M', '虜'), + (0x2F9B5, 'M', '虧'), + (0x2F9B6, 'M', '虩'), + (0x2F9B7, 'M', '蚩'), + (0x2F9B8, 'M', '蚈'), + (0x2F9B9, 'M', '蜎'), + (0x2F9BA, 'M', '蛢'), + (0x2F9BB, 'M', '蝹'), + (0x2F9BC, 'M', '蜨'), + (0x2F9BD, 'M', '蝫'), + (0x2F9BE, 'M', '螆'), + (0x2F9BF, 'X'), + (0x2F9C0, 'M', '蟡'), + (0x2F9C1, 'M', '蠁'), + (0x2F9C2, 'M', '䗹'), + (0x2F9C3, 'M', '衠'), + (0x2F9C4, 'M', '衣'), + (0x2F9C5, 'M', '𧙧'), + (0x2F9C6, 'M', '裗'), + (0x2F9C7, 'M', '裞'), + (0x2F9C8, 'M', '䘵'), + (0x2F9C9, 'M', '裺'), + (0x2F9CA, 'M', '㒻'), + (0x2F9CB, 'M', '𧢮'), + (0x2F9CC, 'M', '𧥦'), + (0x2F9CD, 'M', '䚾'), + (0x2F9CE, 'M', '䛇'), + (0x2F9CF, 'M', '誠'), + (0x2F9D0, 'M', '諭'), + (0x2F9D1, 'M', '變'), + (0x2F9D2, 'M', '豕'), + (0x2F9D3, 'M', '𧲨'), + (0x2F9D4, 'M', '貫'), + (0x2F9D5, 'M', '賁'), + (0x2F9D6, 'M', '贛'), + (0x2F9D7, 'M', '起'), + (0x2F9D8, 'M', '𧼯'), + (0x2F9D9, 'M', '𠠄'), + (0x2F9DA, 'M', '跋'), + (0x2F9DB, 'M', '趼'), + (0x2F9DC, 'M', '跰'), + (0x2F9DD, 'M', '𠣞'), + (0x2F9DE, 'M', '軔'), + (0x2F9DF, 'M', '輸'), + (0x2F9E0, 'M', '𨗒'), + (0x2F9E1, 'M', '𨗭'), + (0x2F9E2, 'M', '邔'), + (0x2F9E3, 'M', '郱'), + (0x2F9E4, 'M', '鄑'), + (0x2F9E5, 'M', '𨜮'), + (0x2F9E6, 'M', '鄛'), + (0x2F9E7, 'M', '鈸'), + (0x2F9E8, 'M', '鋗'), + (0x2F9E9, 'M', '鋘'), + (0x2F9EA, 'M', '鉼'), + (0x2F9EB, 'M', '鏹'), + (0x2F9EC, 'M', '鐕'), + (0x2F9ED, 'M', '𨯺'), + (0x2F9EE, 'M', '開'), + (0x2F9EF, 'M', '䦕'), + (0x2F9F0, 'M', '閷'), + (0x2F9F1, 'M', '𨵷'), + ] + +def _seg_79(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ + (0x2F9F2, 'M', '䧦'), + (0x2F9F3, 'M', '雃'), + (0x2F9F4, 'M', '嶲'), + (0x2F9F5, 'M', '霣'), + (0x2F9F6, 'M', '𩅅'), + (0x2F9F7, 'M', '𩈚'), + (0x2F9F8, 'M', '䩮'), + (0x2F9F9, 'M', '䩶'), + (0x2F9FA, 'M', '韠'), + (0x2F9FB, 'M', '𩐊'), + (0x2F9FC, 'M', '䪲'), + (0x2F9FD, 'M', '𩒖'), + (0x2F9FE, 'M', '頋'), + (0x2FA00, 'M', '頩'), + (0x2FA01, 'M', '𩖶'), + (0x2FA02, 'M', '飢'), + (0x2FA03, 'M', '䬳'), + (0x2FA04, 'M', '餩'), + (0x2FA05, 'M', '馧'), + (0x2FA06, 'M', '駂'), + (0x2FA07, 'M', '駾'), + (0x2FA08, 'M', '䯎'), + (0x2FA09, 'M', '𩬰'), + (0x2FA0A, 'M', '鬒'), + (0x2FA0B, 'M', '鱀'), + (0x2FA0C, 'M', '鳽'), + (0x2FA0D, 'M', '䳎'), + (0x2FA0E, 'M', '䳭'), + (0x2FA0F, 'M', '鵧'), + (0x2FA10, 'M', '𪃎'), + (0x2FA11, 'M', '䳸'), + (0x2FA12, 'M', '𪄅'), + (0x2FA13, 'M', '𪈎'), + (0x2FA14, 'M', '𪊑'), + (0x2FA15, 'M', '麻'), + (0x2FA16, 'M', '䵖'), + (0x2FA17, 'M', '黹'), + (0x2FA18, 'M', '黾'), + (0x2FA19, 'M', '鼅'), + (0x2FA1A, 'M', '鼏'), + (0x2FA1B, 'M', '鼖'), + (0x2FA1C, 'M', '鼻'), + (0x2FA1D, 'M', '𪘀'), + (0x2FA1E, 'X'), + (0x30000, 'V'), + (0x3134B, 'X'), + (0xE0100, 'I'), (0xE01F0, 'X'), ] @@ -8202,4 +8434,5 @@ uts46data = tuple( + _seg_76() + _seg_77() + _seg_78() -) + + _seg_79() +) # type: Tuple[Union[Tuple[int, str], Tuple[int, str, str]], ...] diff --git a/pipenv/patched/notpip/_vendor/ipaddress.LICENSE b/pipenv/patched/notpip/_vendor/ipaddress.LICENSE deleted file mode 100644 index 41bd16ba..00000000 --- a/pipenv/patched/notpip/_vendor/ipaddress.LICENSE +++ /dev/null @@ -1,50 +0,0 @@ -This package is a modified version of cpython's ipaddress module. -It is therefore distributed under the PSF license, as follows: - -PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 --------------------------------------------- - -1. This LICENSE AGREEMENT is between the Python Software Foundation -("PSF"), and the Individual or Organization ("Licensee") accessing and -otherwise using this software ("Python") in source or binary form and -its associated documentation. - -2. Subject to the terms and conditions of this License Agreement, PSF hereby -grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, -analyze, test, perform and/or display publicly, prepare derivative works, -distribute, and otherwise use Python alone or in any derivative version, -provided, however, that PSF's License Agreement and PSF's notice of copyright, -i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, -2011, 2012, 2013, 2014 Python Software Foundation; All Rights Reserved" are -retained in Python alone or in any derivative version prepared by Licensee. - -3. In the event Licensee prepares a derivative work that is based on -or incorporates Python or any part thereof, and wants to make -the derivative work available to others as provided herein, then -Licensee hereby agrees to include in any such work a brief summary of -the changes made to Python. - -4. PSF is making Python available to Licensee on an "AS IS" -basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON -FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS -A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, -OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -6. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -7. Nothing in this License Agreement shall be deemed to create any -relationship of agency, partnership, or joint venture between PSF and -Licensee. This License Agreement does not grant permission to use PSF -trademarks or trade name in a trademark sense to endorse or promote -products or services of Licensee, or any third party. - -8. By copying, installing or otherwise using Python, Licensee -agrees to be bound by the terms and conditions of this License -Agreement. diff --git a/pipenv/patched/notpip/_vendor/ipaddress.py b/pipenv/patched/notpip/_vendor/ipaddress.py deleted file mode 100644 index f2d07668..00000000 --- a/pipenv/patched/notpip/_vendor/ipaddress.py +++ /dev/null @@ -1,2419 +0,0 @@ -# Copyright 2007 Google Inc. -# Licensed to PSF under a Contributor Agreement. - -"""A fast, lightweight IPv4/IPv6 manipulation library in Python. - -This library is used to create/poke/manipulate IPv4 and IPv6 addresses -and networks. - -""" - -from __future__ import unicode_literals - - -import itertools -import struct - -__version__ = '1.0.22' - -# Compatibility functions -_compat_int_types = (int,) -try: - _compat_int_types = (int, long) -except NameError: - pass -try: - _compat_str = unicode -except NameError: - _compat_str = str - assert bytes != str -if b'\0'[0] == 0: # Python 3 semantics - def _compat_bytes_to_byte_vals(byt): - return byt -else: - def _compat_bytes_to_byte_vals(byt): - return [struct.unpack(b'!B', b)[0] for b in byt] -try: - _compat_int_from_byte_vals = int.from_bytes -except AttributeError: - def _compat_int_from_byte_vals(bytvals, endianess): - assert endianess == 'big' - res = 0 - for bv in bytvals: - assert isinstance(bv, _compat_int_types) - res = (res << 8) + bv - return res - - -def _compat_to_bytes(intval, length, endianess): - assert isinstance(intval, _compat_int_types) - assert endianess == 'big' - if length == 4: - if intval < 0 or intval >= 2 ** 32: - raise struct.error("integer out of range for 'I' format code") - return struct.pack(b'!I', intval) - elif length == 16: - if intval < 0 or intval >= 2 ** 128: - raise struct.error("integer out of range for 'QQ' format code") - return struct.pack(b'!QQ', intval >> 64, intval & 0xffffffffffffffff) - else: - raise NotImplementedError() - - -if hasattr(int, 'bit_length'): - # Not int.bit_length , since that won't work in 2.7 where long exists - def _compat_bit_length(i): - return i.bit_length() -else: - def _compat_bit_length(i): - for res in itertools.count(): - if i >> res == 0: - return res - - -def _compat_range(start, end, step=1): - assert step > 0 - i = start - while i < end: - yield i - i += step - - -class _TotalOrderingMixin(object): - __slots__ = () - - # Helper that derives the other comparison operations from - # __lt__ and __eq__ - # We avoid functools.total_ordering because it doesn't handle - # NotImplemented correctly yet (http://bugs.python.org/issue10042) - def __eq__(self, other): - raise NotImplementedError - - def __ne__(self, other): - equal = self.__eq__(other) - if equal is NotImplemented: - return NotImplemented - return not equal - - def __lt__(self, other): - raise NotImplementedError - - def __le__(self, other): - less = self.__lt__(other) - if less is NotImplemented or not less: - return self.__eq__(other) - return less - - def __gt__(self, other): - less = self.__lt__(other) - if less is NotImplemented: - return NotImplemented - equal = self.__eq__(other) - if equal is NotImplemented: - return NotImplemented - return not (less or equal) - - def __ge__(self, other): - less = self.__lt__(other) - if less is NotImplemented: - return NotImplemented - return not less - - -IPV4LENGTH = 32 -IPV6LENGTH = 128 - - -class AddressValueError(ValueError): - """A Value Error related to the address.""" - - -class NetmaskValueError(ValueError): - """A Value Error related to the netmask.""" - - -def ip_address(address): - """Take an IP string/int and return an object of the correct type. - - Args: - address: A string or integer, the IP address. Either IPv4 or - IPv6 addresses may be supplied; integers less than 2**32 will - be considered to be IPv4 by default. - - Returns: - An IPv4Address or IPv6Address object. - - Raises: - ValueError: if the *address* passed isn't either a v4 or a v6 - address - - """ - try: - return IPv4Address(address) - except (AddressValueError, NetmaskValueError): - pass - - try: - return IPv6Address(address) - except (AddressValueError, NetmaskValueError): - pass - - if isinstance(address, bytes): - raise AddressValueError( - '%r does not appear to be an IPv4 or IPv6 address. ' - 'Did you pass in a bytes (str in Python 2) instead of' - ' a unicode object?' % address) - - raise ValueError('%r does not appear to be an IPv4 or IPv6 address' % - address) - - -def ip_network(address, strict=True): - """Take an IP string/int and return an object of the correct type. - - Args: - address: A string or integer, the IP network. Either IPv4 or - IPv6 networks may be supplied; integers less than 2**32 will - be considered to be IPv4 by default. - - Returns: - An IPv4Network or IPv6Network object. - - Raises: - ValueError: if the string passed isn't either a v4 or a v6 - address. Or if the network has host bits set. - - """ - try: - return IPv4Network(address, strict) - except (AddressValueError, NetmaskValueError): - pass - - try: - return IPv6Network(address, strict) - except (AddressValueError, NetmaskValueError): - pass - - if isinstance(address, bytes): - raise AddressValueError( - '%r does not appear to be an IPv4 or IPv6 network. ' - 'Did you pass in a bytes (str in Python 2) instead of' - ' a unicode object?' % address) - - raise ValueError('%r does not appear to be an IPv4 or IPv6 network' % - address) - - -def ip_interface(address): - """Take an IP string/int and return an object of the correct type. - - Args: - address: A string or integer, the IP address. Either IPv4 or - IPv6 addresses may be supplied; integers less than 2**32 will - be considered to be IPv4 by default. - - Returns: - An IPv4Interface or IPv6Interface object. - - Raises: - ValueError: if the string passed isn't either a v4 or a v6 - address. - - Notes: - The IPv?Interface classes describe an Address on a particular - Network, so they're basically a combination of both the Address - and Network classes. - - """ - try: - return IPv4Interface(address) - except (AddressValueError, NetmaskValueError): - pass - - try: - return IPv6Interface(address) - except (AddressValueError, NetmaskValueError): - pass - - raise ValueError('%r does not appear to be an IPv4 or IPv6 interface' % - address) - - -def v4_int_to_packed(address): - """Represent an address as 4 packed bytes in network (big-endian) order. - - Args: - address: An integer representation of an IPv4 IP address. - - Returns: - The integer address packed as 4 bytes in network (big-endian) order. - - Raises: - ValueError: If the integer is negative or too large to be an - IPv4 IP address. - - """ - try: - return _compat_to_bytes(address, 4, 'big') - except (struct.error, OverflowError): - raise ValueError("Address negative or too large for IPv4") - - -def v6_int_to_packed(address): - """Represent an address as 16 packed bytes in network (big-endian) order. - - Args: - address: An integer representation of an IPv6 IP address. - - Returns: - The integer address packed as 16 bytes in network (big-endian) order. - - """ - try: - return _compat_to_bytes(address, 16, 'big') - except (struct.error, OverflowError): - raise ValueError("Address negative or too large for IPv6") - - -def _split_optional_netmask(address): - """Helper to split the netmask and raise AddressValueError if needed""" - addr = _compat_str(address).split('/') - if len(addr) > 2: - raise AddressValueError("Only one '/' permitted in %r" % address) - return addr - - -def _find_address_range(addresses): - """Find a sequence of sorted deduplicated IPv#Address. - - Args: - addresses: a list of IPv#Address objects. - - Yields: - A tuple containing the first and last IP addresses in the sequence. - - """ - it = iter(addresses) - first = last = next(it) - for ip in it: - if ip._ip != last._ip + 1: - yield first, last - first = ip - last = ip - yield first, last - - -def _count_righthand_zero_bits(number, bits): - """Count the number of zero bits on the right hand side. - - Args: - number: an integer. - bits: maximum number of bits to count. - - Returns: - The number of zero bits on the right hand side of the number. - - """ - if number == 0: - return bits - return min(bits, _compat_bit_length(~number & (number - 1))) - - -def summarize_address_range(first, last): - """Summarize a network range given the first and last IP addresses. - - Example: - >>> list(summarize_address_range(IPv4Address('192.0.2.0'), - ... IPv4Address('192.0.2.130'))) - ... #doctest: +NORMALIZE_WHITESPACE - [IPv4Network('192.0.2.0/25'), IPv4Network('192.0.2.128/31'), - IPv4Network('192.0.2.130/32')] - - Args: - first: the first IPv4Address or IPv6Address in the range. - last: the last IPv4Address or IPv6Address in the range. - - Returns: - An iterator of the summarized IPv(4|6) network objects. - - Raise: - TypeError: - If the first and last objects are not IP addresses. - If the first and last objects are not the same version. - ValueError: - If the last object is not greater than the first. - If the version of the first address is not 4 or 6. - - """ - if (not (isinstance(first, _BaseAddress) and - isinstance(last, _BaseAddress))): - raise TypeError('first and last must be IP addresses, not networks') - if first.version != last.version: - raise TypeError("%s and %s are not of the same version" % ( - first, last)) - if first > last: - raise ValueError('last IP address must be greater than first') - - if first.version == 4: - ip = IPv4Network - elif first.version == 6: - ip = IPv6Network - else: - raise ValueError('unknown IP version') - - ip_bits = first._max_prefixlen - first_int = first._ip - last_int = last._ip - while first_int <= last_int: - nbits = min(_count_righthand_zero_bits(first_int, ip_bits), - _compat_bit_length(last_int - first_int + 1) - 1) - net = ip((first_int, ip_bits - nbits)) - yield net - first_int += 1 << nbits - if first_int - 1 == ip._ALL_ONES: - break - - -def _collapse_addresses_internal(addresses): - """Loops through the addresses, collapsing concurrent netblocks. - - Example: - - ip1 = IPv4Network('192.0.2.0/26') - ip2 = IPv4Network('192.0.2.64/26') - ip3 = IPv4Network('192.0.2.128/26') - ip4 = IPv4Network('192.0.2.192/26') - - _collapse_addresses_internal([ip1, ip2, ip3, ip4]) -> - [IPv4Network('192.0.2.0/24')] - - This shouldn't be called directly; it is called via - collapse_addresses([]). - - Args: - addresses: A list of IPv4Network's or IPv6Network's - - Returns: - A list of IPv4Network's or IPv6Network's depending on what we were - passed. - - """ - # First merge - to_merge = list(addresses) - subnets = {} - while to_merge: - net = to_merge.pop() - supernet = net.supernet() - existing = subnets.get(supernet) - if existing is None: - subnets[supernet] = net - elif existing != net: - # Merge consecutive subnets - del subnets[supernet] - to_merge.append(supernet) - # Then iterate over resulting networks, skipping subsumed subnets - last = None - for net in sorted(subnets.values()): - if last is not None: - # Since they are sorted, - # last.network_address <= net.network_address is a given. - if last.broadcast_address >= net.broadcast_address: - continue - yield net - last = net - - -def collapse_addresses(addresses): - """Collapse a list of IP objects. - - Example: - collapse_addresses([IPv4Network('192.0.2.0/25'), - IPv4Network('192.0.2.128/25')]) -> - [IPv4Network('192.0.2.0/24')] - - Args: - addresses: An iterator of IPv4Network or IPv6Network objects. - - Returns: - An iterator of the collapsed IPv(4|6)Network objects. - - Raises: - TypeError: If passed a list of mixed version objects. - - """ - addrs = [] - ips = [] - nets = [] - - # split IP addresses and networks - for ip in addresses: - if isinstance(ip, _BaseAddress): - if ips and ips[-1]._version != ip._version: - raise TypeError("%s and %s are not of the same version" % ( - ip, ips[-1])) - ips.append(ip) - elif ip._prefixlen == ip._max_prefixlen: - if ips and ips[-1]._version != ip._version: - raise TypeError("%s and %s are not of the same version" % ( - ip, ips[-1])) - try: - ips.append(ip.ip) - except AttributeError: - ips.append(ip.network_address) - else: - if nets and nets[-1]._version != ip._version: - raise TypeError("%s and %s are not of the same version" % ( - ip, nets[-1])) - nets.append(ip) - - # sort and dedup - ips = sorted(set(ips)) - - # find consecutive address ranges in the sorted sequence and summarize them - if ips: - for first, last in _find_address_range(ips): - addrs.extend(summarize_address_range(first, last)) - - return _collapse_addresses_internal(addrs + nets) - - -def get_mixed_type_key(obj): - """Return a key suitable for sorting between networks and addresses. - - Address and Network objects are not sortable by default; they're - fundamentally different so the expression - - IPv4Address('192.0.2.0') <= IPv4Network('192.0.2.0/24') - - doesn't make any sense. There are some times however, where you may wish - to have ipaddress sort these for you anyway. If you need to do this, you - can use this function as the key= argument to sorted(). - - Args: - obj: either a Network or Address object. - Returns: - appropriate key. - - """ - if isinstance(obj, _BaseNetwork): - return obj._get_networks_key() - elif isinstance(obj, _BaseAddress): - return obj._get_address_key() - return NotImplemented - - -class _IPAddressBase(_TotalOrderingMixin): - - """The mother class.""" - - __slots__ = () - - @property - def exploded(self): - """Return the longhand version of the IP address as a string.""" - return self._explode_shorthand_ip_string() - - @property - def compressed(self): - """Return the shorthand version of the IP address as a string.""" - return _compat_str(self) - - @property - def reverse_pointer(self): - """The name of the reverse DNS pointer for the IP address, e.g.: - >>> ipaddress.ip_address("127.0.0.1").reverse_pointer - '1.0.0.127.in-addr.arpa' - >>> ipaddress.ip_address("2001:db8::1").reverse_pointer - '1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa' - - """ - return self._reverse_pointer() - - @property - def version(self): - msg = '%200s has no version specified' % (type(self),) - raise NotImplementedError(msg) - - def _check_int_address(self, address): - if address < 0: - msg = "%d (< 0) is not permitted as an IPv%d address" - raise AddressValueError(msg % (address, self._version)) - if address > self._ALL_ONES: - msg = "%d (>= 2**%d) is not permitted as an IPv%d address" - raise AddressValueError(msg % (address, self._max_prefixlen, - self._version)) - - def _check_packed_address(self, address, expected_len): - address_len = len(address) - if address_len != expected_len: - msg = ( - '%r (len %d != %d) is not permitted as an IPv%d address. ' - 'Did you pass in a bytes (str in Python 2) instead of' - ' a unicode object?') - raise AddressValueError(msg % (address, address_len, - expected_len, self._version)) - - @classmethod - def _ip_int_from_prefix(cls, prefixlen): - """Turn the prefix length into a bitwise netmask - - Args: - prefixlen: An integer, the prefix length. - - Returns: - An integer. - - """ - return cls._ALL_ONES ^ (cls._ALL_ONES >> prefixlen) - - @classmethod - def _prefix_from_ip_int(cls, ip_int): - """Return prefix length from the bitwise netmask. - - Args: - ip_int: An integer, the netmask in expanded bitwise format - - Returns: - An integer, the prefix length. - - Raises: - ValueError: If the input intermingles zeroes & ones - """ - trailing_zeroes = _count_righthand_zero_bits(ip_int, - cls._max_prefixlen) - prefixlen = cls._max_prefixlen - trailing_zeroes - leading_ones = ip_int >> trailing_zeroes - all_ones = (1 << prefixlen) - 1 - if leading_ones != all_ones: - byteslen = cls._max_prefixlen // 8 - details = _compat_to_bytes(ip_int, byteslen, 'big') - msg = 'Netmask pattern %r mixes zeroes & ones' - raise ValueError(msg % details) - return prefixlen - - @classmethod - def _report_invalid_netmask(cls, netmask_str): - msg = '%r is not a valid netmask' % netmask_str - raise NetmaskValueError(msg) - - @classmethod - def _prefix_from_prefix_string(cls, prefixlen_str): - """Return prefix length from a numeric string - - Args: - prefixlen_str: The string to be converted - - Returns: - An integer, the prefix length. - - Raises: - NetmaskValueError: If the input is not a valid netmask - """ - # int allows a leading +/- as well as surrounding whitespace, - # so we ensure that isn't the case - if not _BaseV4._DECIMAL_DIGITS.issuperset(prefixlen_str): - cls._report_invalid_netmask(prefixlen_str) - try: - prefixlen = int(prefixlen_str) - except ValueError: - cls._report_invalid_netmask(prefixlen_str) - if not (0 <= prefixlen <= cls._max_prefixlen): - cls._report_invalid_netmask(prefixlen_str) - return prefixlen - - @classmethod - def _prefix_from_ip_string(cls, ip_str): - """Turn a netmask/hostmask string into a prefix length - - Args: - ip_str: The netmask/hostmask to be converted - - Returns: - An integer, the prefix length. - - Raises: - NetmaskValueError: If the input is not a valid netmask/hostmask - """ - # Parse the netmask/hostmask like an IP address. - try: - ip_int = cls._ip_int_from_string(ip_str) - except AddressValueError: - cls._report_invalid_netmask(ip_str) - - # Try matching a netmask (this would be /1*0*/ as a bitwise regexp). - # Note that the two ambiguous cases (all-ones and all-zeroes) are - # treated as netmasks. - try: - return cls._prefix_from_ip_int(ip_int) - except ValueError: - pass - - # Invert the bits, and try matching a /0+1+/ hostmask instead. - ip_int ^= cls._ALL_ONES - try: - return cls._prefix_from_ip_int(ip_int) - except ValueError: - cls._report_invalid_netmask(ip_str) - - def __reduce__(self): - return self.__class__, (_compat_str(self),) - - -class _BaseAddress(_IPAddressBase): - - """A generic IP object. - - This IP class contains the version independent methods which are - used by single IP addresses. - """ - - __slots__ = () - - def __int__(self): - return self._ip - - def __eq__(self, other): - try: - return (self._ip == other._ip and - self._version == other._version) - except AttributeError: - return NotImplemented - - def __lt__(self, other): - if not isinstance(other, _IPAddressBase): - return NotImplemented - if not isinstance(other, _BaseAddress): - raise TypeError('%s and %s are not of the same type' % ( - self, other)) - if self._version != other._version: - raise TypeError('%s and %s are not of the same version' % ( - self, other)) - if self._ip != other._ip: - return self._ip < other._ip - return False - - # Shorthand for Integer addition and subtraction. This is not - # meant to ever support addition/subtraction of addresses. - def __add__(self, other): - if not isinstance(other, _compat_int_types): - return NotImplemented - return self.__class__(int(self) + other) - - def __sub__(self, other): - if not isinstance(other, _compat_int_types): - return NotImplemented - return self.__class__(int(self) - other) - - def __repr__(self): - return '%s(%r)' % (self.__class__.__name__, _compat_str(self)) - - def __str__(self): - return _compat_str(self._string_from_ip_int(self._ip)) - - def __hash__(self): - return hash(hex(int(self._ip))) - - def _get_address_key(self): - return (self._version, self) - - def __reduce__(self): - return self.__class__, (self._ip,) - - -class _BaseNetwork(_IPAddressBase): - - """A generic IP network object. - - This IP class contains the version independent methods which are - used by networks. - - """ - def __init__(self, address): - self._cache = {} - - def __repr__(self): - return '%s(%r)' % (self.__class__.__name__, _compat_str(self)) - - def __str__(self): - return '%s/%d' % (self.network_address, self.prefixlen) - - def hosts(self): - """Generate Iterator over usable hosts in a network. - - This is like __iter__ except it doesn't return the network - or broadcast addresses. - - """ - network = int(self.network_address) - broadcast = int(self.broadcast_address) - for x in _compat_range(network + 1, broadcast): - yield self._address_class(x) - - def __iter__(self): - network = int(self.network_address) - broadcast = int(self.broadcast_address) - for x in _compat_range(network, broadcast + 1): - yield self._address_class(x) - - def __getitem__(self, n): - network = int(self.network_address) - broadcast = int(self.broadcast_address) - if n >= 0: - if network + n > broadcast: - raise IndexError('address out of range') - return self._address_class(network + n) - else: - n += 1 - if broadcast + n < network: - raise IndexError('address out of range') - return self._address_class(broadcast + n) - - def __lt__(self, other): - if not isinstance(other, _IPAddressBase): - return NotImplemented - if not isinstance(other, _BaseNetwork): - raise TypeError('%s and %s are not of the same type' % ( - self, other)) - if self._version != other._version: - raise TypeError('%s and %s are not of the same version' % ( - self, other)) - if self.network_address != other.network_address: - return self.network_address < other.network_address - if self.netmask != other.netmask: - return self.netmask < other.netmask - return False - - def __eq__(self, other): - try: - return (self._version == other._version and - self.network_address == other.network_address and - int(self.netmask) == int(other.netmask)) - except AttributeError: - return NotImplemented - - def __hash__(self): - return hash(int(self.network_address) ^ int(self.netmask)) - - def __contains__(self, other): - # always false if one is v4 and the other is v6. - if self._version != other._version: - return False - # dealing with another network. - if isinstance(other, _BaseNetwork): - return False - # dealing with another address - else: - # address - return (int(self.network_address) <= int(other._ip) <= - int(self.broadcast_address)) - - def overlaps(self, other): - """Tell if self is partly contained in other.""" - return self.network_address in other or ( - self.broadcast_address in other or ( - other.network_address in self or ( - other.broadcast_address in self))) - - @property - def broadcast_address(self): - x = self._cache.get('broadcast_address') - if x is None: - x = self._address_class(int(self.network_address) | - int(self.hostmask)) - self._cache['broadcast_address'] = x - return x - - @property - def hostmask(self): - x = self._cache.get('hostmask') - if x is None: - x = self._address_class(int(self.netmask) ^ self._ALL_ONES) - self._cache['hostmask'] = x - return x - - @property - def with_prefixlen(self): - return '%s/%d' % (self.network_address, self._prefixlen) - - @property - def with_netmask(self): - return '%s/%s' % (self.network_address, self.netmask) - - @property - def with_hostmask(self): - return '%s/%s' % (self.network_address, self.hostmask) - - @property - def num_addresses(self): - """Number of hosts in the current subnet.""" - return int(self.broadcast_address) - int(self.network_address) + 1 - - @property - def _address_class(self): - # Returning bare address objects (rather than interfaces) allows for - # more consistent behaviour across the network address, broadcast - # address and individual host addresses. - msg = '%200s has no associated address class' % (type(self),) - raise NotImplementedError(msg) - - @property - def prefixlen(self): - return self._prefixlen - - def address_exclude(self, other): - """Remove an address from a larger block. - - For example: - - addr1 = ip_network('192.0.2.0/28') - addr2 = ip_network('192.0.2.1/32') - list(addr1.address_exclude(addr2)) = - [IPv4Network('192.0.2.0/32'), IPv4Network('192.0.2.2/31'), - IPv4Network('192.0.2.4/30'), IPv4Network('192.0.2.8/29')] - - or IPv6: - - addr1 = ip_network('2001:db8::1/32') - addr2 = ip_network('2001:db8::1/128') - list(addr1.address_exclude(addr2)) = - [ip_network('2001:db8::1/128'), - ip_network('2001:db8::2/127'), - ip_network('2001:db8::4/126'), - ip_network('2001:db8::8/125'), - ... - ip_network('2001:db8:8000::/33')] - - Args: - other: An IPv4Network or IPv6Network object of the same type. - - Returns: - An iterator of the IPv(4|6)Network objects which is self - minus other. - - Raises: - TypeError: If self and other are of differing address - versions, or if other is not a network object. - ValueError: If other is not completely contained by self. - - """ - if not self._version == other._version: - raise TypeError("%s and %s are not of the same version" % ( - self, other)) - - if not isinstance(other, _BaseNetwork): - raise TypeError("%s is not a network object" % other) - - if not other.subnet_of(self): - raise ValueError('%s not contained in %s' % (other, self)) - if other == self: - return - - # Make sure we're comparing the network of other. - other = other.__class__('%s/%s' % (other.network_address, - other.prefixlen)) - - s1, s2 = self.subnets() - while s1 != other and s2 != other: - if other.subnet_of(s1): - yield s2 - s1, s2 = s1.subnets() - elif other.subnet_of(s2): - yield s1 - s1, s2 = s2.subnets() - else: - # If we got here, there's a bug somewhere. - raise AssertionError('Error performing exclusion: ' - 's1: %s s2: %s other: %s' % - (s1, s2, other)) - if s1 == other: - yield s2 - elif s2 == other: - yield s1 - else: - # If we got here, there's a bug somewhere. - raise AssertionError('Error performing exclusion: ' - 's1: %s s2: %s other: %s' % - (s1, s2, other)) - - def compare_networks(self, other): - """Compare two IP objects. - - This is only concerned about the comparison of the integer - representation of the network addresses. This means that the - host bits aren't considered at all in this method. If you want - to compare host bits, you can easily enough do a - 'HostA._ip < HostB._ip' - - Args: - other: An IP object. - - Returns: - If the IP versions of self and other are the same, returns: - - -1 if self < other: - eg: IPv4Network('192.0.2.0/25') < IPv4Network('192.0.2.128/25') - IPv6Network('2001:db8::1000/124') < - IPv6Network('2001:db8::2000/124') - 0 if self == other - eg: IPv4Network('192.0.2.0/24') == IPv4Network('192.0.2.0/24') - IPv6Network('2001:db8::1000/124') == - IPv6Network('2001:db8::1000/124') - 1 if self > other - eg: IPv4Network('192.0.2.128/25') > IPv4Network('192.0.2.0/25') - IPv6Network('2001:db8::2000/124') > - IPv6Network('2001:db8::1000/124') - - Raises: - TypeError if the IP versions are different. - - """ - # does this need to raise a ValueError? - if self._version != other._version: - raise TypeError('%s and %s are not of the same type' % ( - self, other)) - # self._version == other._version below here: - if self.network_address < other.network_address: - return -1 - if self.network_address > other.network_address: - return 1 - # self.network_address == other.network_address below here: - if self.netmask < other.netmask: - return -1 - if self.netmask > other.netmask: - return 1 - return 0 - - def _get_networks_key(self): - """Network-only key function. - - Returns an object that identifies this address' network and - netmask. This function is a suitable "key" argument for sorted() - and list.sort(). - - """ - return (self._version, self.network_address, self.netmask) - - def subnets(self, prefixlen_diff=1, new_prefix=None): - """The subnets which join to make the current subnet. - - In the case that self contains only one IP - (self._prefixlen == 32 for IPv4 or self._prefixlen == 128 - for IPv6), yield an iterator with just ourself. - - Args: - prefixlen_diff: An integer, the amount the prefix length - should be increased by. This should not be set if - new_prefix is also set. - new_prefix: The desired new prefix length. This must be a - larger number (smaller prefix) than the existing prefix. - This should not be set if prefixlen_diff is also set. - - Returns: - An iterator of IPv(4|6) objects. - - Raises: - ValueError: The prefixlen_diff is too small or too large. - OR - prefixlen_diff and new_prefix are both set or new_prefix - is a smaller number than the current prefix (smaller - number means a larger network) - - """ - if self._prefixlen == self._max_prefixlen: - yield self - return - - if new_prefix is not None: - if new_prefix < self._prefixlen: - raise ValueError('new prefix must be longer') - if prefixlen_diff != 1: - raise ValueError('cannot set prefixlen_diff and new_prefix') - prefixlen_diff = new_prefix - self._prefixlen - - if prefixlen_diff < 0: - raise ValueError('prefix length diff must be > 0') - new_prefixlen = self._prefixlen + prefixlen_diff - - if new_prefixlen > self._max_prefixlen: - raise ValueError( - 'prefix length diff %d is invalid for netblock %s' % ( - new_prefixlen, self)) - - start = int(self.network_address) - end = int(self.broadcast_address) + 1 - step = (int(self.hostmask) + 1) >> prefixlen_diff - for new_addr in _compat_range(start, end, step): - current = self.__class__((new_addr, new_prefixlen)) - yield current - - def supernet(self, prefixlen_diff=1, new_prefix=None): - """The supernet containing the current network. - - Args: - prefixlen_diff: An integer, the amount the prefix length of - the network should be decreased by. For example, given a - /24 network and a prefixlen_diff of 3, a supernet with a - /21 netmask is returned. - - Returns: - An IPv4 network object. - - Raises: - ValueError: If self.prefixlen - prefixlen_diff < 0. I.e., you have - a negative prefix length. - OR - If prefixlen_diff and new_prefix are both set or new_prefix is a - larger number than the current prefix (larger number means a - smaller network) - - """ - if self._prefixlen == 0: - return self - - if new_prefix is not None: - if new_prefix > self._prefixlen: - raise ValueError('new prefix must be shorter') - if prefixlen_diff != 1: - raise ValueError('cannot set prefixlen_diff and new_prefix') - prefixlen_diff = self._prefixlen - new_prefix - - new_prefixlen = self.prefixlen - prefixlen_diff - if new_prefixlen < 0: - raise ValueError( - 'current prefixlen is %d, cannot have a prefixlen_diff of %d' % - (self.prefixlen, prefixlen_diff)) - return self.__class__(( - int(self.network_address) & (int(self.netmask) << prefixlen_diff), - new_prefixlen)) - - @property - def is_multicast(self): - """Test if the address is reserved for multicast use. - - Returns: - A boolean, True if the address is a multicast address. - See RFC 2373 2.7 for details. - - """ - return (self.network_address.is_multicast and - self.broadcast_address.is_multicast) - - @staticmethod - def _is_subnet_of(a, b): - try: - # Always false if one is v4 and the other is v6. - if a._version != b._version: - raise TypeError("%s and %s are not of the same version" (a, b)) - return (b.network_address <= a.network_address and - b.broadcast_address >= a.broadcast_address) - except AttributeError: - raise TypeError("Unable to test subnet containment " - "between %s and %s" % (a, b)) - - def subnet_of(self, other): - """Return True if this network is a subnet of other.""" - return self._is_subnet_of(self, other) - - def supernet_of(self, other): - """Return True if this network is a supernet of other.""" - return self._is_subnet_of(other, self) - - @property - def is_reserved(self): - """Test if the address is otherwise IETF reserved. - - Returns: - A boolean, True if the address is within one of the - reserved IPv6 Network ranges. - - """ - return (self.network_address.is_reserved and - self.broadcast_address.is_reserved) - - @property - def is_link_local(self): - """Test if the address is reserved for link-local. - - Returns: - A boolean, True if the address is reserved per RFC 4291. - - """ - return (self.network_address.is_link_local and - self.broadcast_address.is_link_local) - - @property - def is_private(self): - """Test if this address is allocated for private networks. - - Returns: - A boolean, True if the address is reserved per - iana-ipv4-special-registry or iana-ipv6-special-registry. - - """ - return (self.network_address.is_private and - self.broadcast_address.is_private) - - @property - def is_global(self): - """Test if this address is allocated for public networks. - - Returns: - A boolean, True if the address is not reserved per - iana-ipv4-special-registry or iana-ipv6-special-registry. - - """ - return not self.is_private - - @property - def is_unspecified(self): - """Test if the address is unspecified. - - Returns: - A boolean, True if this is the unspecified address as defined in - RFC 2373 2.5.2. - - """ - return (self.network_address.is_unspecified and - self.broadcast_address.is_unspecified) - - @property - def is_loopback(self): - """Test if the address is a loopback address. - - Returns: - A boolean, True if the address is a loopback address as defined in - RFC 2373 2.5.3. - - """ - return (self.network_address.is_loopback and - self.broadcast_address.is_loopback) - - -class _BaseV4(object): - - """Base IPv4 object. - - The following methods are used by IPv4 objects in both single IP - addresses and networks. - - """ - - __slots__ = () - _version = 4 - # Equivalent to 255.255.255.255 or 32 bits of 1's. - _ALL_ONES = (2 ** IPV4LENGTH) - 1 - _DECIMAL_DIGITS = frozenset('0123456789') - - # the valid octets for host and netmasks. only useful for IPv4. - _valid_mask_octets = frozenset([255, 254, 252, 248, 240, 224, 192, 128, 0]) - - _max_prefixlen = IPV4LENGTH - # There are only a handful of valid v4 netmasks, so we cache them all - # when constructed (see _make_netmask()). - _netmask_cache = {} - - def _explode_shorthand_ip_string(self): - return _compat_str(self) - - @classmethod - def _make_netmask(cls, arg): - """Make a (netmask, prefix_len) tuple from the given argument. - - Argument can be: - - an integer (the prefix length) - - a string representing the prefix length (e.g. "24") - - a string representing the prefix netmask (e.g. "255.255.255.0") - """ - if arg not in cls._netmask_cache: - if isinstance(arg, _compat_int_types): - prefixlen = arg - else: - try: - # Check for a netmask in prefix length form - prefixlen = cls._prefix_from_prefix_string(arg) - except NetmaskValueError: - # Check for a netmask or hostmask in dotted-quad form. - # This may raise NetmaskValueError. - prefixlen = cls._prefix_from_ip_string(arg) - netmask = IPv4Address(cls._ip_int_from_prefix(prefixlen)) - cls._netmask_cache[arg] = netmask, prefixlen - return cls._netmask_cache[arg] - - @classmethod - def _ip_int_from_string(cls, ip_str): - """Turn the given IP string into an integer for comparison. - - Args: - ip_str: A string, the IP ip_str. - - Returns: - The IP ip_str as an integer. - - Raises: - AddressValueError: if ip_str isn't a valid IPv4 Address. - - """ - if not ip_str: - raise AddressValueError('Address cannot be empty') - - octets = ip_str.split('.') - if len(octets) != 4: - raise AddressValueError("Expected 4 octets in %r" % ip_str) - - try: - return _compat_int_from_byte_vals( - map(cls._parse_octet, octets), 'big') - except ValueError as exc: - raise AddressValueError("%s in %r" % (exc, ip_str)) - - @classmethod - def _parse_octet(cls, octet_str): - """Convert a decimal octet into an integer. - - Args: - octet_str: A string, the number to parse. - - Returns: - The octet as an integer. - - Raises: - ValueError: if the octet isn't strictly a decimal from [0..255]. - - """ - if not octet_str: - raise ValueError("Empty octet not permitted") - # Whitelist the characters, since int() allows a lot of bizarre stuff. - if not cls._DECIMAL_DIGITS.issuperset(octet_str): - msg = "Only decimal digits permitted in %r" - raise ValueError(msg % octet_str) - # We do the length check second, since the invalid character error - # is likely to be more informative for the user - if len(octet_str) > 3: - msg = "At most 3 characters permitted in %r" - raise ValueError(msg % octet_str) - # Convert to integer (we know digits are legal) - octet_int = int(octet_str, 10) - # Any octets that look like they *might* be written in octal, - # and which don't look exactly the same in both octal and - # decimal are rejected as ambiguous - if octet_int > 7 and octet_str[0] == '0': - msg = "Ambiguous (octal/decimal) value in %r not permitted" - raise ValueError(msg % octet_str) - if octet_int > 255: - raise ValueError("Octet %d (> 255) not permitted" % octet_int) - return octet_int - - @classmethod - def _string_from_ip_int(cls, ip_int): - """Turns a 32-bit integer into dotted decimal notation. - - Args: - ip_int: An integer, the IP address. - - Returns: - The IP address as a string in dotted decimal notation. - - """ - return '.'.join(_compat_str(struct.unpack(b'!B', b)[0] - if isinstance(b, bytes) - else b) - for b in _compat_to_bytes(ip_int, 4, 'big')) - - def _is_hostmask(self, ip_str): - """Test if the IP string is a hostmask (rather than a netmask). - - Args: - ip_str: A string, the potential hostmask. - - Returns: - A boolean, True if the IP string is a hostmask. - - """ - bits = ip_str.split('.') - try: - parts = [x for x in map(int, bits) if x in self._valid_mask_octets] - except ValueError: - return False - if len(parts) != len(bits): - return False - if parts[0] < parts[-1]: - return True - return False - - def _reverse_pointer(self): - """Return the reverse DNS pointer name for the IPv4 address. - - This implements the method described in RFC1035 3.5. - - """ - reverse_octets = _compat_str(self).split('.')[::-1] - return '.'.join(reverse_octets) + '.in-addr.arpa' - - @property - def max_prefixlen(self): - return self._max_prefixlen - - @property - def version(self): - return self._version - - -class IPv4Address(_BaseV4, _BaseAddress): - - """Represent and manipulate single IPv4 Addresses.""" - - __slots__ = ('_ip', '__weakref__') - - def __init__(self, address): - - """ - Args: - address: A string or integer representing the IP - - Additionally, an integer can be passed, so - IPv4Address('192.0.2.1') == IPv4Address(3221225985). - or, more generally - IPv4Address(int(IPv4Address('192.0.2.1'))) == - IPv4Address('192.0.2.1') - - Raises: - AddressValueError: If ipaddress isn't a valid IPv4 address. - - """ - # Efficient constructor from integer. - if isinstance(address, _compat_int_types): - self._check_int_address(address) - self._ip = address - return - - # Constructing from a packed address - if isinstance(address, bytes): - self._check_packed_address(address, 4) - bvs = _compat_bytes_to_byte_vals(address) - self._ip = _compat_int_from_byte_vals(bvs, 'big') - return - - # Assume input argument to be string or any object representation - # which converts into a formatted IP string. - addr_str = _compat_str(address) - if '/' in addr_str: - raise AddressValueError("Unexpected '/' in %r" % address) - self._ip = self._ip_int_from_string(addr_str) - - @property - def packed(self): - """The binary representation of this address.""" - return v4_int_to_packed(self._ip) - - @property - def is_reserved(self): - """Test if the address is otherwise IETF reserved. - - Returns: - A boolean, True if the address is within the - reserved IPv4 Network range. - - """ - return self in self._constants._reserved_network - - @property - def is_private(self): - """Test if this address is allocated for private networks. - - Returns: - A boolean, True if the address is reserved per - iana-ipv4-special-registry. - - """ - return any(self in net for net in self._constants._private_networks) - - @property - def is_global(self): - return ( - self not in self._constants._public_network and - not self.is_private) - - @property - def is_multicast(self): - """Test if the address is reserved for multicast use. - - Returns: - A boolean, True if the address is multicast. - See RFC 3171 for details. - - """ - return self in self._constants._multicast_network - - @property - def is_unspecified(self): - """Test if the address is unspecified. - - Returns: - A boolean, True if this is the unspecified address as defined in - RFC 5735 3. - - """ - return self == self._constants._unspecified_address - - @property - def is_loopback(self): - """Test if the address is a loopback address. - - Returns: - A boolean, True if the address is a loopback per RFC 3330. - - """ - return self in self._constants._loopback_network - - @property - def is_link_local(self): - """Test if the address is reserved for link-local. - - Returns: - A boolean, True if the address is link-local per RFC 3927. - - """ - return self in self._constants._linklocal_network - - -class IPv4Interface(IPv4Address): - - def __init__(self, address): - if isinstance(address, (bytes, _compat_int_types)): - IPv4Address.__init__(self, address) - self.network = IPv4Network(self._ip) - self._prefixlen = self._max_prefixlen - return - - if isinstance(address, tuple): - IPv4Address.__init__(self, address[0]) - if len(address) > 1: - self._prefixlen = int(address[1]) - else: - self._prefixlen = self._max_prefixlen - - self.network = IPv4Network(address, strict=False) - self.netmask = self.network.netmask - self.hostmask = self.network.hostmask - return - - addr = _split_optional_netmask(address) - IPv4Address.__init__(self, addr[0]) - - self.network = IPv4Network(address, strict=False) - self._prefixlen = self.network._prefixlen - - self.netmask = self.network.netmask - self.hostmask = self.network.hostmask - - def __str__(self): - return '%s/%d' % (self._string_from_ip_int(self._ip), - self.network.prefixlen) - - def __eq__(self, other): - address_equal = IPv4Address.__eq__(self, other) - if not address_equal or address_equal is NotImplemented: - return address_equal - try: - return self.network == other.network - except AttributeError: - # An interface with an associated network is NOT the - # same as an unassociated address. That's why the hash - # takes the extra info into account. - return False - - def __lt__(self, other): - address_less = IPv4Address.__lt__(self, other) - if address_less is NotImplemented: - return NotImplemented - try: - return (self.network < other.network or - self.network == other.network and address_less) - except AttributeError: - # We *do* allow addresses and interfaces to be sorted. The - # unassociated address is considered less than all interfaces. - return False - - def __hash__(self): - return self._ip ^ self._prefixlen ^ int(self.network.network_address) - - __reduce__ = _IPAddressBase.__reduce__ - - @property - def ip(self): - return IPv4Address(self._ip) - - @property - def with_prefixlen(self): - return '%s/%s' % (self._string_from_ip_int(self._ip), - self._prefixlen) - - @property - def with_netmask(self): - return '%s/%s' % (self._string_from_ip_int(self._ip), - self.netmask) - - @property - def with_hostmask(self): - return '%s/%s' % (self._string_from_ip_int(self._ip), - self.hostmask) - - -class IPv4Network(_BaseV4, _BaseNetwork): - - """This class represents and manipulates 32-bit IPv4 network + addresses.. - - Attributes: [examples for IPv4Network('192.0.2.0/27')] - .network_address: IPv4Address('192.0.2.0') - .hostmask: IPv4Address('0.0.0.31') - .broadcast_address: IPv4Address('192.0.2.32') - .netmask: IPv4Address('255.255.255.224') - .prefixlen: 27 - - """ - # Class to use when creating address objects - _address_class = IPv4Address - - def __init__(self, address, strict=True): - - """Instantiate a new IPv4 network object. - - Args: - address: A string or integer representing the IP [& network]. - '192.0.2.0/24' - '192.0.2.0/255.255.255.0' - '192.0.0.2/0.0.0.255' - are all functionally the same in IPv4. Similarly, - '192.0.2.1' - '192.0.2.1/255.255.255.255' - '192.0.2.1/32' - are also functionally equivalent. That is to say, failing to - provide a subnetmask will create an object with a mask of /32. - - If the mask (portion after the / in the argument) is given in - dotted quad form, it is treated as a netmask if it starts with a - non-zero field (e.g. /255.0.0.0 == /8) and as a hostmask if it - starts with a zero field (e.g. 0.255.255.255 == /8), with the - single exception of an all-zero mask which is treated as a - netmask == /0. If no mask is given, a default of /32 is used. - - Additionally, an integer can be passed, so - IPv4Network('192.0.2.1') == IPv4Network(3221225985) - or, more generally - IPv4Interface(int(IPv4Interface('192.0.2.1'))) == - IPv4Interface('192.0.2.1') - - Raises: - AddressValueError: If ipaddress isn't a valid IPv4 address. - NetmaskValueError: If the netmask isn't valid for - an IPv4 address. - ValueError: If strict is True and a network address is not - supplied. - - """ - _BaseNetwork.__init__(self, address) - - # Constructing from a packed address or integer - if isinstance(address, (_compat_int_types, bytes)): - self.network_address = IPv4Address(address) - self.netmask, self._prefixlen = self._make_netmask( - self._max_prefixlen) - # fixme: address/network test here. - return - - if isinstance(address, tuple): - if len(address) > 1: - arg = address[1] - else: - # We weren't given an address[1] - arg = self._max_prefixlen - self.network_address = IPv4Address(address[0]) - self.netmask, self._prefixlen = self._make_netmask(arg) - packed = int(self.network_address) - if packed & int(self.netmask) != packed: - if strict: - raise ValueError('%s has host bits set' % self) - else: - self.network_address = IPv4Address(packed & - int(self.netmask)) - return - - # Assume input argument to be string or any object representation - # which converts into a formatted IP prefix string. - addr = _split_optional_netmask(address) - self.network_address = IPv4Address(self._ip_int_from_string(addr[0])) - - if len(addr) == 2: - arg = addr[1] - else: - arg = self._max_prefixlen - self.netmask, self._prefixlen = self._make_netmask(arg) - - if strict: - if (IPv4Address(int(self.network_address) & int(self.netmask)) != - self.network_address): - raise ValueError('%s has host bits set' % self) - self.network_address = IPv4Address(int(self.network_address) & - int(self.netmask)) - - if self._prefixlen == (self._max_prefixlen - 1): - self.hosts = self.__iter__ - - @property - def is_global(self): - """Test if this address is allocated for public networks. - - Returns: - A boolean, True if the address is not reserved per - iana-ipv4-special-registry. - - """ - return (not (self.network_address in IPv4Network('100.64.0.0/10') and - self.broadcast_address in IPv4Network('100.64.0.0/10')) and - not self.is_private) - - -class _IPv4Constants(object): - - _linklocal_network = IPv4Network('169.254.0.0/16') - - _loopback_network = IPv4Network('127.0.0.0/8') - - _multicast_network = IPv4Network('224.0.0.0/4') - - _public_network = IPv4Network('100.64.0.0/10') - - _private_networks = [ - IPv4Network('0.0.0.0/8'), - IPv4Network('10.0.0.0/8'), - IPv4Network('127.0.0.0/8'), - IPv4Network('169.254.0.0/16'), - IPv4Network('172.16.0.0/12'), - IPv4Network('192.0.0.0/29'), - IPv4Network('192.0.0.170/31'), - IPv4Network('192.0.2.0/24'), - IPv4Network('192.168.0.0/16'), - IPv4Network('198.18.0.0/15'), - IPv4Network('198.51.100.0/24'), - IPv4Network('203.0.113.0/24'), - IPv4Network('240.0.0.0/4'), - IPv4Network('255.255.255.255/32'), - ] - - _reserved_network = IPv4Network('240.0.0.0/4') - - _unspecified_address = IPv4Address('0.0.0.0') - - -IPv4Address._constants = _IPv4Constants - - -class _BaseV6(object): - - """Base IPv6 object. - - The following methods are used by IPv6 objects in both single IP - addresses and networks. - - """ - - __slots__ = () - _version = 6 - _ALL_ONES = (2 ** IPV6LENGTH) - 1 - _HEXTET_COUNT = 8 - _HEX_DIGITS = frozenset('0123456789ABCDEFabcdef') - _max_prefixlen = IPV6LENGTH - - # There are only a bunch of valid v6 netmasks, so we cache them all - # when constructed (see _make_netmask()). - _netmask_cache = {} - - @classmethod - def _make_netmask(cls, arg): - """Make a (netmask, prefix_len) tuple from the given argument. - - Argument can be: - - an integer (the prefix length) - - a string representing the prefix length (e.g. "24") - - a string representing the prefix netmask (e.g. "255.255.255.0") - """ - if arg not in cls._netmask_cache: - if isinstance(arg, _compat_int_types): - prefixlen = arg - else: - prefixlen = cls._prefix_from_prefix_string(arg) - netmask = IPv6Address(cls._ip_int_from_prefix(prefixlen)) - cls._netmask_cache[arg] = netmask, prefixlen - return cls._netmask_cache[arg] - - @classmethod - def _ip_int_from_string(cls, ip_str): - """Turn an IPv6 ip_str into an integer. - - Args: - ip_str: A string, the IPv6 ip_str. - - Returns: - An int, the IPv6 address - - Raises: - AddressValueError: if ip_str isn't a valid IPv6 Address. - - """ - if not ip_str: - raise AddressValueError('Address cannot be empty') - - parts = ip_str.split(':') - - # An IPv6 address needs at least 2 colons (3 parts). - _min_parts = 3 - if len(parts) < _min_parts: - msg = "At least %d parts expected in %r" % (_min_parts, ip_str) - raise AddressValueError(msg) - - # If the address has an IPv4-style suffix, convert it to hexadecimal. - if '.' in parts[-1]: - try: - ipv4_int = IPv4Address(parts.pop())._ip - except AddressValueError as exc: - raise AddressValueError("%s in %r" % (exc, ip_str)) - parts.append('%x' % ((ipv4_int >> 16) & 0xFFFF)) - parts.append('%x' % (ipv4_int & 0xFFFF)) - - # An IPv6 address can't have more than 8 colons (9 parts). - # The extra colon comes from using the "::" notation for a single - # leading or trailing zero part. - _max_parts = cls._HEXTET_COUNT + 1 - if len(parts) > _max_parts: - msg = "At most %d colons permitted in %r" % ( - _max_parts - 1, ip_str) - raise AddressValueError(msg) - - # Disregarding the endpoints, find '::' with nothing in between. - # This indicates that a run of zeroes has been skipped. - skip_index = None - for i in _compat_range(1, len(parts) - 1): - if not parts[i]: - if skip_index is not None: - # Can't have more than one '::' - msg = "At most one '::' permitted in %r" % ip_str - raise AddressValueError(msg) - skip_index = i - - # parts_hi is the number of parts to copy from above/before the '::' - # parts_lo is the number of parts to copy from below/after the '::' - if skip_index is not None: - # If we found a '::', then check if it also covers the endpoints. - parts_hi = skip_index - parts_lo = len(parts) - skip_index - 1 - if not parts[0]: - parts_hi -= 1 - if parts_hi: - msg = "Leading ':' only permitted as part of '::' in %r" - raise AddressValueError(msg % ip_str) # ^: requires ^:: - if not parts[-1]: - parts_lo -= 1 - if parts_lo: - msg = "Trailing ':' only permitted as part of '::' in %r" - raise AddressValueError(msg % ip_str) # :$ requires ::$ - parts_skipped = cls._HEXTET_COUNT - (parts_hi + parts_lo) - if parts_skipped < 1: - msg = "Expected at most %d other parts with '::' in %r" - raise AddressValueError(msg % (cls._HEXTET_COUNT - 1, ip_str)) - else: - # Otherwise, allocate the entire address to parts_hi. The - # endpoints could still be empty, but _parse_hextet() will check - # for that. - if len(parts) != cls._HEXTET_COUNT: - msg = "Exactly %d parts expected without '::' in %r" - raise AddressValueError(msg % (cls._HEXTET_COUNT, ip_str)) - if not parts[0]: - msg = "Leading ':' only permitted as part of '::' in %r" - raise AddressValueError(msg % ip_str) # ^: requires ^:: - if not parts[-1]: - msg = "Trailing ':' only permitted as part of '::' in %r" - raise AddressValueError(msg % ip_str) # :$ requires ::$ - parts_hi = len(parts) - parts_lo = 0 - parts_skipped = 0 - - try: - # Now, parse the hextets into a 128-bit integer. - ip_int = 0 - for i in range(parts_hi): - ip_int <<= 16 - ip_int |= cls._parse_hextet(parts[i]) - ip_int <<= 16 * parts_skipped - for i in range(-parts_lo, 0): - ip_int <<= 16 - ip_int |= cls._parse_hextet(parts[i]) - return ip_int - except ValueError as exc: - raise AddressValueError("%s in %r" % (exc, ip_str)) - - @classmethod - def _parse_hextet(cls, hextet_str): - """Convert an IPv6 hextet string into an integer. - - Args: - hextet_str: A string, the number to parse. - - Returns: - The hextet as an integer. - - Raises: - ValueError: if the input isn't strictly a hex number from - [0..FFFF]. - - """ - # Whitelist the characters, since int() allows a lot of bizarre stuff. - if not cls._HEX_DIGITS.issuperset(hextet_str): - raise ValueError("Only hex digits permitted in %r" % hextet_str) - # We do the length check second, since the invalid character error - # is likely to be more informative for the user - if len(hextet_str) > 4: - msg = "At most 4 characters permitted in %r" - raise ValueError(msg % hextet_str) - # Length check means we can skip checking the integer value - return int(hextet_str, 16) - - @classmethod - def _compress_hextets(cls, hextets): - """Compresses a list of hextets. - - Compresses a list of strings, replacing the longest continuous - sequence of "0" in the list with "" and adding empty strings at - the beginning or at the end of the string such that subsequently - calling ":".join(hextets) will produce the compressed version of - the IPv6 address. - - Args: - hextets: A list of strings, the hextets to compress. - - Returns: - A list of strings. - - """ - best_doublecolon_start = -1 - best_doublecolon_len = 0 - doublecolon_start = -1 - doublecolon_len = 0 - for index, hextet in enumerate(hextets): - if hextet == '0': - doublecolon_len += 1 - if doublecolon_start == -1: - # Start of a sequence of zeros. - doublecolon_start = index - if doublecolon_len > best_doublecolon_len: - # This is the longest sequence of zeros so far. - best_doublecolon_len = doublecolon_len - best_doublecolon_start = doublecolon_start - else: - doublecolon_len = 0 - doublecolon_start = -1 - - if best_doublecolon_len > 1: - best_doublecolon_end = (best_doublecolon_start + - best_doublecolon_len) - # For zeros at the end of the address. - if best_doublecolon_end == len(hextets): - hextets += [''] - hextets[best_doublecolon_start:best_doublecolon_end] = [''] - # For zeros at the beginning of the address. - if best_doublecolon_start == 0: - hextets = [''] + hextets - - return hextets - - @classmethod - def _string_from_ip_int(cls, ip_int=None): - """Turns a 128-bit integer into hexadecimal notation. - - Args: - ip_int: An integer, the IP address. - - Returns: - A string, the hexadecimal representation of the address. - - Raises: - ValueError: The address is bigger than 128 bits of all ones. - - """ - if ip_int is None: - ip_int = int(cls._ip) - - if ip_int > cls._ALL_ONES: - raise ValueError('IPv6 address is too large') - - hex_str = '%032x' % ip_int - hextets = ['%x' % int(hex_str[x:x + 4], 16) for x in range(0, 32, 4)] - - hextets = cls._compress_hextets(hextets) - return ':'.join(hextets) - - def _explode_shorthand_ip_string(self): - """Expand a shortened IPv6 address. - - Args: - ip_str: A string, the IPv6 address. - - Returns: - A string, the expanded IPv6 address. - - """ - if isinstance(self, IPv6Network): - ip_str = _compat_str(self.network_address) - elif isinstance(self, IPv6Interface): - ip_str = _compat_str(self.ip) - else: - ip_str = _compat_str(self) - - ip_int = self._ip_int_from_string(ip_str) - hex_str = '%032x' % ip_int - parts = [hex_str[x:x + 4] for x in range(0, 32, 4)] - if isinstance(self, (_BaseNetwork, IPv6Interface)): - return '%s/%d' % (':'.join(parts), self._prefixlen) - return ':'.join(parts) - - def _reverse_pointer(self): - """Return the reverse DNS pointer name for the IPv6 address. - - This implements the method described in RFC3596 2.5. - - """ - reverse_chars = self.exploded[::-1].replace(':', '') - return '.'.join(reverse_chars) + '.ip6.arpa' - - @property - def max_prefixlen(self): - return self._max_prefixlen - - @property - def version(self): - return self._version - - -class IPv6Address(_BaseV6, _BaseAddress): - - """Represent and manipulate single IPv6 Addresses.""" - - __slots__ = ('_ip', '__weakref__') - - def __init__(self, address): - """Instantiate a new IPv6 address object. - - Args: - address: A string or integer representing the IP - - Additionally, an integer can be passed, so - IPv6Address('2001:db8::') == - IPv6Address(42540766411282592856903984951653826560) - or, more generally - IPv6Address(int(IPv6Address('2001:db8::'))) == - IPv6Address('2001:db8::') - - Raises: - AddressValueError: If address isn't a valid IPv6 address. - - """ - # Efficient constructor from integer. - if isinstance(address, _compat_int_types): - self._check_int_address(address) - self._ip = address - return - - # Constructing from a packed address - if isinstance(address, bytes): - self._check_packed_address(address, 16) - bvs = _compat_bytes_to_byte_vals(address) - self._ip = _compat_int_from_byte_vals(bvs, 'big') - return - - # Assume input argument to be string or any object representation - # which converts into a formatted IP string. - addr_str = _compat_str(address) - if '/' in addr_str: - raise AddressValueError("Unexpected '/' in %r" % address) - self._ip = self._ip_int_from_string(addr_str) - - @property - def packed(self): - """The binary representation of this address.""" - return v6_int_to_packed(self._ip) - - @property - def is_multicast(self): - """Test if the address is reserved for multicast use. - - Returns: - A boolean, True if the address is a multicast address. - See RFC 2373 2.7 for details. - - """ - return self in self._constants._multicast_network - - @property - def is_reserved(self): - """Test if the address is otherwise IETF reserved. - - Returns: - A boolean, True if the address is within one of the - reserved IPv6 Network ranges. - - """ - return any(self in x for x in self._constants._reserved_networks) - - @property - def is_link_local(self): - """Test if the address is reserved for link-local. - - Returns: - A boolean, True if the address is reserved per RFC 4291. - - """ - return self in self._constants._linklocal_network - - @property - def is_site_local(self): - """Test if the address is reserved for site-local. - - Note that the site-local address space has been deprecated by RFC 3879. - Use is_private to test if this address is in the space of unique local - addresses as defined by RFC 4193. - - Returns: - A boolean, True if the address is reserved per RFC 3513 2.5.6. - - """ - return self in self._constants._sitelocal_network - - @property - def is_private(self): - """Test if this address is allocated for private networks. - - Returns: - A boolean, True if the address is reserved per - iana-ipv6-special-registry. - - """ - return any(self in net for net in self._constants._private_networks) - - @property - def is_global(self): - """Test if this address is allocated for public networks. - - Returns: - A boolean, true if the address is not reserved per - iana-ipv6-special-registry. - - """ - return not self.is_private - - @property - def is_unspecified(self): - """Test if the address is unspecified. - - Returns: - A boolean, True if this is the unspecified address as defined in - RFC 2373 2.5.2. - - """ - return self._ip == 0 - - @property - def is_loopback(self): - """Test if the address is a loopback address. - - Returns: - A boolean, True if the address is a loopback address as defined in - RFC 2373 2.5.3. - - """ - return self._ip == 1 - - @property - def ipv4_mapped(self): - """Return the IPv4 mapped address. - - Returns: - If the IPv6 address is a v4 mapped address, return the - IPv4 mapped address. Return None otherwise. - - """ - if (self._ip >> 32) != 0xFFFF: - return None - return IPv4Address(self._ip & 0xFFFFFFFF) - - @property - def teredo(self): - """Tuple of embedded teredo IPs. - - Returns: - Tuple of the (server, client) IPs or None if the address - doesn't appear to be a teredo address (doesn't start with - 2001::/32) - - """ - if (self._ip >> 96) != 0x20010000: - return None - return (IPv4Address((self._ip >> 64) & 0xFFFFFFFF), - IPv4Address(~self._ip & 0xFFFFFFFF)) - - @property - def sixtofour(self): - """Return the IPv4 6to4 embedded address. - - Returns: - The IPv4 6to4-embedded address if present or None if the - address doesn't appear to contain a 6to4 embedded address. - - """ - if (self._ip >> 112) != 0x2002: - return None - return IPv4Address((self._ip >> 80) & 0xFFFFFFFF) - - -class IPv6Interface(IPv6Address): - - def __init__(self, address): - if isinstance(address, (bytes, _compat_int_types)): - IPv6Address.__init__(self, address) - self.network = IPv6Network(self._ip) - self._prefixlen = self._max_prefixlen - return - if isinstance(address, tuple): - IPv6Address.__init__(self, address[0]) - if len(address) > 1: - self._prefixlen = int(address[1]) - else: - self._prefixlen = self._max_prefixlen - self.network = IPv6Network(address, strict=False) - self.netmask = self.network.netmask - self.hostmask = self.network.hostmask - return - - addr = _split_optional_netmask(address) - IPv6Address.__init__(self, addr[0]) - self.network = IPv6Network(address, strict=False) - self.netmask = self.network.netmask - self._prefixlen = self.network._prefixlen - self.hostmask = self.network.hostmask - - def __str__(self): - return '%s/%d' % (self._string_from_ip_int(self._ip), - self.network.prefixlen) - - def __eq__(self, other): - address_equal = IPv6Address.__eq__(self, other) - if not address_equal or address_equal is NotImplemented: - return address_equal - try: - return self.network == other.network - except AttributeError: - # An interface with an associated network is NOT the - # same as an unassociated address. That's why the hash - # takes the extra info into account. - return False - - def __lt__(self, other): - address_less = IPv6Address.__lt__(self, other) - if address_less is NotImplemented: - return NotImplemented - try: - return (self.network < other.network or - self.network == other.network and address_less) - except AttributeError: - # We *do* allow addresses and interfaces to be sorted. The - # unassociated address is considered less than all interfaces. - return False - - def __hash__(self): - return self._ip ^ self._prefixlen ^ int(self.network.network_address) - - __reduce__ = _IPAddressBase.__reduce__ - - @property - def ip(self): - return IPv6Address(self._ip) - - @property - def with_prefixlen(self): - return '%s/%s' % (self._string_from_ip_int(self._ip), - self._prefixlen) - - @property - def with_netmask(self): - return '%s/%s' % (self._string_from_ip_int(self._ip), - self.netmask) - - @property - def with_hostmask(self): - return '%s/%s' % (self._string_from_ip_int(self._ip), - self.hostmask) - - @property - def is_unspecified(self): - return self._ip == 0 and self.network.is_unspecified - - @property - def is_loopback(self): - return self._ip == 1 and self.network.is_loopback - - -class IPv6Network(_BaseV6, _BaseNetwork): - - """This class represents and manipulates 128-bit IPv6 networks. - - Attributes: [examples for IPv6('2001:db8::1000/124')] - .network_address: IPv6Address('2001:db8::1000') - .hostmask: IPv6Address('::f') - .broadcast_address: IPv6Address('2001:db8::100f') - .netmask: IPv6Address('ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff0') - .prefixlen: 124 - - """ - - # Class to use when creating address objects - _address_class = IPv6Address - - def __init__(self, address, strict=True): - """Instantiate a new IPv6 Network object. - - Args: - address: A string or integer representing the IPv6 network or the - IP and prefix/netmask. - '2001:db8::/128' - '2001:db8:0000:0000:0000:0000:0000:0000/128' - '2001:db8::' - are all functionally the same in IPv6. That is to say, - failing to provide a subnetmask will create an object with - a mask of /128. - - Additionally, an integer can be passed, so - IPv6Network('2001:db8::') == - IPv6Network(42540766411282592856903984951653826560) - or, more generally - IPv6Network(int(IPv6Network('2001:db8::'))) == - IPv6Network('2001:db8::') - - strict: A boolean. If true, ensure that we have been passed - A true network address, eg, 2001:db8::1000/124 and not an - IP address on a network, eg, 2001:db8::1/124. - - Raises: - AddressValueError: If address isn't a valid IPv6 address. - NetmaskValueError: If the netmask isn't valid for - an IPv6 address. - ValueError: If strict was True and a network address was not - supplied. - - """ - _BaseNetwork.__init__(self, address) - - # Efficient constructor from integer or packed address - if isinstance(address, (bytes, _compat_int_types)): - self.network_address = IPv6Address(address) - self.netmask, self._prefixlen = self._make_netmask( - self._max_prefixlen) - return - - if isinstance(address, tuple): - if len(address) > 1: - arg = address[1] - else: - arg = self._max_prefixlen - self.netmask, self._prefixlen = self._make_netmask(arg) - self.network_address = IPv6Address(address[0]) - packed = int(self.network_address) - if packed & int(self.netmask) != packed: - if strict: - raise ValueError('%s has host bits set' % self) - else: - self.network_address = IPv6Address(packed & - int(self.netmask)) - return - - # Assume input argument to be string or any object representation - # which converts into a formatted IP prefix string. - addr = _split_optional_netmask(address) - - self.network_address = IPv6Address(self._ip_int_from_string(addr[0])) - - if len(addr) == 2: - arg = addr[1] - else: - arg = self._max_prefixlen - self.netmask, self._prefixlen = self._make_netmask(arg) - - if strict: - if (IPv6Address(int(self.network_address) & int(self.netmask)) != - self.network_address): - raise ValueError('%s has host bits set' % self) - self.network_address = IPv6Address(int(self.network_address) & - int(self.netmask)) - - if self._prefixlen == (self._max_prefixlen - 1): - self.hosts = self.__iter__ - - def hosts(self): - """Generate Iterator over usable hosts in a network. - - This is like __iter__ except it doesn't return the - Subnet-Router anycast address. - - """ - network = int(self.network_address) - broadcast = int(self.broadcast_address) - for x in _compat_range(network + 1, broadcast + 1): - yield self._address_class(x) - - @property - def is_site_local(self): - """Test if the address is reserved for site-local. - - Note that the site-local address space has been deprecated by RFC 3879. - Use is_private to test if this address is in the space of unique local - addresses as defined by RFC 4193. - - Returns: - A boolean, True if the address is reserved per RFC 3513 2.5.6. - - """ - return (self.network_address.is_site_local and - self.broadcast_address.is_site_local) - - -class _IPv6Constants(object): - - _linklocal_network = IPv6Network('fe80::/10') - - _multicast_network = IPv6Network('ff00::/8') - - _private_networks = [ - IPv6Network('::1/128'), - IPv6Network('::/128'), - IPv6Network('::ffff:0:0/96'), - IPv6Network('100::/64'), - IPv6Network('2001::/23'), - IPv6Network('2001:2::/48'), - IPv6Network('2001:db8::/32'), - IPv6Network('2001:10::/28'), - IPv6Network('fc00::/7'), - IPv6Network('fe80::/10'), - ] - - _reserved_networks = [ - IPv6Network('::/8'), IPv6Network('100::/8'), - IPv6Network('200::/7'), IPv6Network('400::/6'), - IPv6Network('800::/5'), IPv6Network('1000::/4'), - IPv6Network('4000::/3'), IPv6Network('6000::/3'), - IPv6Network('8000::/3'), IPv6Network('A000::/3'), - IPv6Network('C000::/3'), IPv6Network('E000::/4'), - IPv6Network('F000::/5'), IPv6Network('F800::/6'), - IPv6Network('FE00::/9'), - ] - - _sitelocal_network = IPv6Network('fec0::/10') - - -IPv6Address._constants = _IPv6Constants diff --git a/pipenv/patched/notpip/_vendor/lockfile/__init__.py b/pipenv/patched/notpip/_vendor/lockfile/__init__.py deleted file mode 100644 index a6f44a55..00000000 --- a/pipenv/patched/notpip/_vendor/lockfile/__init__.py +++ /dev/null @@ -1,347 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -lockfile.py - Platform-independent advisory file locks. - -Requires Python 2.5 unless you apply 2.4.diff -Locking is done on a per-thread basis instead of a per-process basis. - -Usage: - ->>> lock = LockFile('somefile') ->>> try: -... lock.acquire() -... except AlreadyLocked: -... print 'somefile', 'is locked already.' -... except LockFailed: -... print 'somefile', 'can\\'t be locked.' -... else: -... print 'got lock' -got lock ->>> print lock.is_locked() -True ->>> lock.release() - ->>> lock = LockFile('somefile') ->>> print lock.is_locked() -False ->>> with lock: -... print lock.is_locked() -True ->>> print lock.is_locked() -False - ->>> lock = LockFile('somefile') ->>> # It is okay to lock twice from the same thread... ->>> with lock: -... lock.acquire() -... ->>> # Though no counter is kept, so you can't unlock multiple times... ->>> print lock.is_locked() -False - -Exceptions: - - Error - base class for other exceptions - LockError - base class for all locking exceptions - AlreadyLocked - Another thread or process already holds the lock - LockFailed - Lock failed for some other reason - UnlockError - base class for all unlocking exceptions - AlreadyUnlocked - File was not locked. - NotMyLock - File was locked but not by the current thread/process -""" - -from __future__ import absolute_import - -import functools -import os -import socket -import threading -import warnings - -# Work with PEP8 and non-PEP8 versions of threading module. -if not hasattr(threading, "current_thread"): - threading.current_thread = threading.currentThread -if not hasattr(threading.Thread, "get_name"): - threading.Thread.get_name = threading.Thread.getName - -__all__ = ['Error', 'LockError', 'LockTimeout', 'AlreadyLocked', - 'LockFailed', 'UnlockError', 'NotLocked', 'NotMyLock', - 'LinkFileLock', 'MkdirFileLock', 'SQLiteFileLock', - 'LockBase', 'locked'] - - -class Error(Exception): - """ - Base class for other exceptions. - - >>> try: - ... raise Error - ... except Exception: - ... pass - """ - pass - - -class LockError(Error): - """ - Base class for error arising from attempts to acquire the lock. - - >>> try: - ... raise LockError - ... except Error: - ... pass - """ - pass - - -class LockTimeout(LockError): - """Raised when lock creation fails within a user-defined period of time. - - >>> try: - ... raise LockTimeout - ... except LockError: - ... pass - """ - pass - - -class AlreadyLocked(LockError): - """Some other thread/process is locking the file. - - >>> try: - ... raise AlreadyLocked - ... except LockError: - ... pass - """ - pass - - -class LockFailed(LockError): - """Lock file creation failed for some other reason. - - >>> try: - ... raise LockFailed - ... except LockError: - ... pass - """ - pass - - -class UnlockError(Error): - """ - Base class for errors arising from attempts to release the lock. - - >>> try: - ... raise UnlockError - ... except Error: - ... pass - """ - pass - - -class NotLocked(UnlockError): - """Raised when an attempt is made to unlock an unlocked file. - - >>> try: - ... raise NotLocked - ... except UnlockError: - ... pass - """ - pass - - -class NotMyLock(UnlockError): - """Raised when an attempt is made to unlock a file someone else locked. - - >>> try: - ... raise NotMyLock - ... except UnlockError: - ... pass - """ - pass - - -class _SharedBase(object): - def __init__(self, path): - self.path = path - - def acquire(self, timeout=None): - """ - Acquire the lock. - - * If timeout is omitted (or None), wait forever trying to lock the - file. - - * If timeout > 0, try to acquire the lock for that many seconds. If - the lock period expires and the file is still locked, raise - LockTimeout. - - * If timeout <= 0, raise AlreadyLocked immediately if the file is - already locked. - """ - raise NotImplemented("implement in subclass") - - def release(self): - """ - Release the lock. - - If the file is not locked, raise NotLocked. - """ - raise NotImplemented("implement in subclass") - - def __enter__(self): - """ - Context manager support. - """ - self.acquire() - return self - - def __exit__(self, *_exc): - """ - Context manager support. - """ - self.release() - - def __repr__(self): - return "<%s: %r>" % (self.__class__.__name__, self.path) - - -class LockBase(_SharedBase): - """Base class for platform-specific lock classes.""" - def __init__(self, path, threaded=True, timeout=None): - """ - >>> lock = LockBase('somefile') - >>> lock = LockBase('somefile', threaded=False) - """ - super(LockBase, self).__init__(path) - self.lock_file = os.path.abspath(path) + ".lock" - self.hostname = socket.gethostname() - self.pid = os.getpid() - if threaded: - t = threading.current_thread() - # Thread objects in Python 2.4 and earlier do not have ident - # attrs. Worm around that. - ident = getattr(t, "ident", hash(t)) - self.tname = "-%x" % (ident & 0xffffffff) - else: - self.tname = "" - dirname = os.path.dirname(self.lock_file) - - # unique name is mostly about the current process, but must - # also contain the path -- otherwise, two adjacent locked - # files conflict (one file gets locked, creating lock-file and - # unique file, the other one gets locked, creating lock-file - # and overwriting the already existing lock-file, then one - # gets unlocked, deleting both lock-file and unique file, - # finally the last lock errors out upon releasing. - self.unique_name = os.path.join(dirname, - "%s%s.%s%s" % (self.hostname, - self.tname, - self.pid, - hash(self.path))) - self.timeout = timeout - - def is_locked(self): - """ - Tell whether or not the file is locked. - """ - raise NotImplemented("implement in subclass") - - def i_am_locking(self): - """ - Return True if this object is locking the file. - """ - raise NotImplemented("implement in subclass") - - def break_lock(self): - """ - Remove a lock. Useful if a locking thread failed to unlock. - """ - raise NotImplemented("implement in subclass") - - def __repr__(self): - return "<%s: %r -- %r>" % (self.__class__.__name__, self.unique_name, - self.path) - - -def _fl_helper(cls, mod, *args, **kwds): - warnings.warn("Import from %s module instead of lockfile package" % mod, - DeprecationWarning, stacklevel=2) - # This is a bit funky, but it's only for awhile. The way the unit tests - # are constructed this function winds up as an unbound method, so it - # actually takes three args, not two. We want to toss out self. - if not isinstance(args[0], str): - # We are testing, avoid the first arg - args = args[1:] - if len(args) == 1 and not kwds: - kwds["threaded"] = True - return cls(*args, **kwds) - - -def LinkFileLock(*args, **kwds): - """Factory function provided for backwards compatibility. - - Do not use in new code. Instead, import LinkLockFile from the - lockfile.linklockfile module. - """ - from . import linklockfile - return _fl_helper(linklockfile.LinkLockFile, "lockfile.linklockfile", - *args, **kwds) - - -def MkdirFileLock(*args, **kwds): - """Factory function provided for backwards compatibility. - - Do not use in new code. Instead, import MkdirLockFile from the - lockfile.mkdirlockfile module. - """ - from . import mkdirlockfile - return _fl_helper(mkdirlockfile.MkdirLockFile, "lockfile.mkdirlockfile", - *args, **kwds) - - -def SQLiteFileLock(*args, **kwds): - """Factory function provided for backwards compatibility. - - Do not use in new code. Instead, import SQLiteLockFile from the - lockfile.mkdirlockfile module. - """ - from . import sqlitelockfile - return _fl_helper(sqlitelockfile.SQLiteLockFile, "lockfile.sqlitelockfile", - *args, **kwds) - - -def locked(path, timeout=None): - """Decorator which enables locks for decorated function. - - Arguments: - - path: path for lockfile. - - timeout (optional): Timeout for acquiring lock. - - Usage: - @locked('/var/run/myname', timeout=0) - def myname(...): - ... - """ - def decor(func): - @functools.wraps(func) - def wrapper(*args, **kwargs): - lock = FileLock(path, timeout=timeout) - lock.acquire() - try: - return func(*args, **kwargs) - finally: - lock.release() - return wrapper - return decor - - -if hasattr(os, "link"): - from . import linklockfile as _llf - LockFile = _llf.LinkLockFile -else: - from . import mkdirlockfile as _mlf - LockFile = _mlf.MkdirLockFile - -FileLock = LockFile diff --git a/pipenv/patched/notpip/_vendor/lockfile/linklockfile.py b/pipenv/patched/notpip/_vendor/lockfile/linklockfile.py deleted file mode 100644 index 2ca9be04..00000000 --- a/pipenv/patched/notpip/_vendor/lockfile/linklockfile.py +++ /dev/null @@ -1,73 +0,0 @@ -from __future__ import absolute_import - -import time -import os - -from . import (LockBase, LockFailed, NotLocked, NotMyLock, LockTimeout, - AlreadyLocked) - - -class LinkLockFile(LockBase): - """Lock access to a file using atomic property of link(2). - - >>> lock = LinkLockFile('somefile') - >>> lock = LinkLockFile('somefile', threaded=False) - """ - - def acquire(self, timeout=None): - try: - open(self.unique_name, "wb").close() - except IOError: - raise LockFailed("failed to create %s" % self.unique_name) - - timeout = timeout if timeout is not None else self.timeout - end_time = time.time() - if timeout is not None and timeout > 0: - end_time += timeout - - while True: - # Try and create a hard link to it. - try: - os.link(self.unique_name, self.lock_file) - except OSError: - # Link creation failed. Maybe we've double-locked? - nlinks = os.stat(self.unique_name).st_nlink - if nlinks == 2: - # The original link plus the one I created == 2. We're - # good to go. - return - else: - # Otherwise the lock creation failed. - if timeout is not None and time.time() > end_time: - os.unlink(self.unique_name) - if timeout > 0: - raise LockTimeout("Timeout waiting to acquire" - " lock for %s" % - self.path) - else: - raise AlreadyLocked("%s is already locked" % - self.path) - time.sleep(timeout is not None and timeout / 10 or 0.1) - else: - # Link creation succeeded. We're good to go. - return - - def release(self): - if not self.is_locked(): - raise NotLocked("%s is not locked" % self.path) - elif not os.path.exists(self.unique_name): - raise NotMyLock("%s is locked, but not by me" % self.path) - os.unlink(self.unique_name) - os.unlink(self.lock_file) - - def is_locked(self): - return os.path.exists(self.lock_file) - - def i_am_locking(self): - return (self.is_locked() and - os.path.exists(self.unique_name) and - os.stat(self.unique_name).st_nlink == 2) - - def break_lock(self): - if os.path.exists(self.lock_file): - os.unlink(self.lock_file) diff --git a/pipenv/patched/notpip/_vendor/lockfile/mkdirlockfile.py b/pipenv/patched/notpip/_vendor/lockfile/mkdirlockfile.py deleted file mode 100644 index 05a8c96c..00000000 --- a/pipenv/patched/notpip/_vendor/lockfile/mkdirlockfile.py +++ /dev/null @@ -1,84 +0,0 @@ -from __future__ import absolute_import, division - -import time -import os -import sys -import errno - -from . import (LockBase, LockFailed, NotLocked, NotMyLock, LockTimeout, - AlreadyLocked) - - -class MkdirLockFile(LockBase): - """Lock file by creating a directory.""" - def __init__(self, path, threaded=True, timeout=None): - """ - >>> lock = MkdirLockFile('somefile') - >>> lock = MkdirLockFile('somefile', threaded=False) - """ - LockBase.__init__(self, path, threaded, timeout) - # Lock file itself is a directory. Place the unique file name into - # it. - self.unique_name = os.path.join(self.lock_file, - "%s.%s%s" % (self.hostname, - self.tname, - self.pid)) - - def acquire(self, timeout=None): - timeout = timeout if timeout is not None else self.timeout - end_time = time.time() - if timeout is not None and timeout > 0: - end_time += timeout - - if timeout is None: - wait = 0.1 - else: - wait = max(0, timeout / 10) - - while True: - try: - os.mkdir(self.lock_file) - except OSError: - err = sys.exc_info()[1] - if err.errno == errno.EEXIST: - # Already locked. - if os.path.exists(self.unique_name): - # Already locked by me. - return - if timeout is not None and time.time() > end_time: - if timeout > 0: - raise LockTimeout("Timeout waiting to acquire" - " lock for %s" % - self.path) - else: - # Someone else has the lock. - raise AlreadyLocked("%s is already locked" % - self.path) - time.sleep(wait) - else: - # Couldn't create the lock for some other reason - raise LockFailed("failed to create %s" % self.lock_file) - else: - open(self.unique_name, "wb").close() - return - - def release(self): - if not self.is_locked(): - raise NotLocked("%s is not locked" % self.path) - elif not os.path.exists(self.unique_name): - raise NotMyLock("%s is locked, but not by me" % self.path) - os.unlink(self.unique_name) - os.rmdir(self.lock_file) - - def is_locked(self): - return os.path.exists(self.lock_file) - - def i_am_locking(self): - return (self.is_locked() and - os.path.exists(self.unique_name)) - - def break_lock(self): - if os.path.exists(self.lock_file): - for name in os.listdir(self.lock_file): - os.unlink(os.path.join(self.lock_file, name)) - os.rmdir(self.lock_file) diff --git a/pipenv/patched/notpip/_vendor/lockfile/pidlockfile.py b/pipenv/patched/notpip/_vendor/lockfile/pidlockfile.py deleted file mode 100644 index 069e85b1..00000000 --- a/pipenv/patched/notpip/_vendor/lockfile/pidlockfile.py +++ /dev/null @@ -1,190 +0,0 @@ -# -*- coding: utf-8 -*- - -# pidlockfile.py -# -# Copyright © 2008–2009 Ben Finney <ben+python@benfinney.id.au> -# -# This is free software: you may copy, modify, and/or distribute this work -# under the terms of the Python Software Foundation License, version 2 or -# later as published by the Python Software Foundation. -# No warranty expressed or implied. See the file LICENSE.PSF-2 for details. - -""" Lockfile behaviour implemented via Unix PID files. - """ - -from __future__ import absolute_import - -import errno -import os -import time - -from . import (LockBase, AlreadyLocked, LockFailed, NotLocked, NotMyLock, - LockTimeout) - - -class PIDLockFile(LockBase): - """ Lockfile implemented as a Unix PID file. - - The lock file is a normal file named by the attribute `path`. - A lock's PID file contains a single line of text, containing - the process ID (PID) of the process that acquired the lock. - - >>> lock = PIDLockFile('somefile') - >>> lock = PIDLockFile('somefile') - """ - - def __init__(self, path, threaded=False, timeout=None): - # pid lockfiles don't support threaded operation, so always force - # False as the threaded arg. - LockBase.__init__(self, path, False, timeout) - self.unique_name = self.path - - def read_pid(self): - """ Get the PID from the lock file. - """ - return read_pid_from_pidfile(self.path) - - def is_locked(self): - """ Test if the lock is currently held. - - The lock is held if the PID file for this lock exists. - - """ - return os.path.exists(self.path) - - def i_am_locking(self): - """ Test if the lock is held by the current process. - - Returns ``True`` if the current process ID matches the - number stored in the PID file. - """ - return self.is_locked() and os.getpid() == self.read_pid() - - def acquire(self, timeout=None): - """ Acquire the lock. - - Creates the PID file for this lock, or raises an error if - the lock could not be acquired. - """ - - timeout = timeout if timeout is not None else self.timeout - end_time = time.time() - if timeout is not None and timeout > 0: - end_time += timeout - - while True: - try: - write_pid_to_pidfile(self.path) - except OSError as exc: - if exc.errno == errno.EEXIST: - # The lock creation failed. Maybe sleep a bit. - if time.time() > end_time: - if timeout is not None and timeout > 0: - raise LockTimeout("Timeout waiting to acquire" - " lock for %s" % - self.path) - else: - raise AlreadyLocked("%s is already locked" % - self.path) - time.sleep(timeout is not None and timeout / 10 or 0.1) - else: - raise LockFailed("failed to create %s" % self.path) - else: - return - - def release(self): - """ Release the lock. - - Removes the PID file to release the lock, or raises an - error if the current process does not hold the lock. - - """ - if not self.is_locked(): - raise NotLocked("%s is not locked" % self.path) - if not self.i_am_locking(): - raise NotMyLock("%s is locked, but not by me" % self.path) - remove_existing_pidfile(self.path) - - def break_lock(self): - """ Break an existing lock. - - Removes the PID file if it already exists, otherwise does - nothing. - - """ - remove_existing_pidfile(self.path) - - -def read_pid_from_pidfile(pidfile_path): - """ Read the PID recorded in the named PID file. - - Read and return the numeric PID recorded as text in the named - PID file. If the PID file cannot be read, or if the content is - not a valid PID, return ``None``. - - """ - pid = None - try: - pidfile = open(pidfile_path, 'r') - except IOError: - pass - else: - # According to the FHS 2.3 section on PID files in /var/run: - # - # The file must consist of the process identifier in - # ASCII-encoded decimal, followed by a newline character. - # - # Programs that read PID files should be somewhat flexible - # in what they accept; i.e., they should ignore extra - # whitespace, leading zeroes, absence of the trailing - # newline, or additional lines in the PID file. - - line = pidfile.readline().strip() - try: - pid = int(line) - except ValueError: - pass - pidfile.close() - - return pid - - -def write_pid_to_pidfile(pidfile_path): - """ Write the PID in the named PID file. - - Get the numeric process ID (“PID”) of the current process - and write it to the named file as a line of text. - - """ - open_flags = (os.O_CREAT | os.O_EXCL | os.O_WRONLY) - open_mode = 0o644 - pidfile_fd = os.open(pidfile_path, open_flags, open_mode) - pidfile = os.fdopen(pidfile_fd, 'w') - - # According to the FHS 2.3 section on PID files in /var/run: - # - # The file must consist of the process identifier in - # ASCII-encoded decimal, followed by a newline character. For - # example, if crond was process number 25, /var/run/crond.pid - # would contain three characters: two, five, and newline. - - pid = os.getpid() - pidfile.write("%s\n" % pid) - pidfile.close() - - -def remove_existing_pidfile(pidfile_path): - """ Remove the named PID file if it exists. - - Removing a PID file that doesn't already exist puts us in the - desired state, so we ignore the condition if the file does not - exist. - - """ - try: - os.remove(pidfile_path) - except OSError as exc: - if exc.errno == errno.ENOENT: - pass - else: - raise diff --git a/pipenv/patched/notpip/_vendor/lockfile/sqlitelockfile.py b/pipenv/patched/notpip/_vendor/lockfile/sqlitelockfile.py deleted file mode 100644 index f997e244..00000000 --- a/pipenv/patched/notpip/_vendor/lockfile/sqlitelockfile.py +++ /dev/null @@ -1,156 +0,0 @@ -from __future__ import absolute_import, division - -import time -import os - -try: - unicode -except NameError: - unicode = str - -from . import LockBase, NotLocked, NotMyLock, LockTimeout, AlreadyLocked - - -class SQLiteLockFile(LockBase): - "Demonstrate SQL-based locking." - - testdb = None - - def __init__(self, path, threaded=True, timeout=None): - """ - >>> lock = SQLiteLockFile('somefile') - >>> lock = SQLiteLockFile('somefile', threaded=False) - """ - LockBase.__init__(self, path, threaded, timeout) - self.lock_file = unicode(self.lock_file) - self.unique_name = unicode(self.unique_name) - - if SQLiteLockFile.testdb is None: - import tempfile - _fd, testdb = tempfile.mkstemp() - os.close(_fd) - os.unlink(testdb) - del _fd, tempfile - SQLiteLockFile.testdb = testdb - - import sqlite3 - self.connection = sqlite3.connect(SQLiteLockFile.testdb) - - c = self.connection.cursor() - try: - c.execute("create table locks" - "(" - " lock_file varchar(32)," - " unique_name varchar(32)" - ")") - except sqlite3.OperationalError: - pass - else: - self.connection.commit() - import atexit - atexit.register(os.unlink, SQLiteLockFile.testdb) - - def acquire(self, timeout=None): - timeout = timeout if timeout is not None else self.timeout - end_time = time.time() - if timeout is not None and timeout > 0: - end_time += timeout - - if timeout is None: - wait = 0.1 - elif timeout <= 0: - wait = 0 - else: - wait = timeout / 10 - - cursor = self.connection.cursor() - - while True: - if not self.is_locked(): - # Not locked. Try to lock it. - cursor.execute("insert into locks" - " (lock_file, unique_name)" - " values" - " (?, ?)", - (self.lock_file, self.unique_name)) - self.connection.commit() - - # Check to see if we are the only lock holder. - cursor.execute("select * from locks" - " where unique_name = ?", - (self.unique_name,)) - rows = cursor.fetchall() - if len(rows) > 1: - # Nope. Someone else got there. Remove our lock. - cursor.execute("delete from locks" - " where unique_name = ?", - (self.unique_name,)) - self.connection.commit() - else: - # Yup. We're done, so go home. - return - else: - # Check to see if we are the only lock holder. - cursor.execute("select * from locks" - " where unique_name = ?", - (self.unique_name,)) - rows = cursor.fetchall() - if len(rows) == 1: - # We're the locker, so go home. - return - - # Maybe we should wait a bit longer. - if timeout is not None and time.time() > end_time: - if timeout > 0: - # No more waiting. - raise LockTimeout("Timeout waiting to acquire" - " lock for %s" % - self.path) - else: - # Someone else has the lock and we are impatient.. - raise AlreadyLocked("%s is already locked" % self.path) - - # Well, okay. We'll give it a bit longer. - time.sleep(wait) - - def release(self): - if not self.is_locked(): - raise NotLocked("%s is not locked" % self.path) - if not self.i_am_locking(): - raise NotMyLock("%s is locked, but not by me (by %s)" % - (self.unique_name, self._who_is_locking())) - cursor = self.connection.cursor() - cursor.execute("delete from locks" - " where unique_name = ?", - (self.unique_name,)) - self.connection.commit() - - def _who_is_locking(self): - cursor = self.connection.cursor() - cursor.execute("select unique_name from locks" - " where lock_file = ?", - (self.lock_file,)) - return cursor.fetchone()[0] - - def is_locked(self): - cursor = self.connection.cursor() - cursor.execute("select * from locks" - " where lock_file = ?", - (self.lock_file,)) - rows = cursor.fetchall() - return not not rows - - def i_am_locking(self): - cursor = self.connection.cursor() - cursor.execute("select * from locks" - " where lock_file = ?" - " and unique_name = ?", - (self.lock_file, self.unique_name)) - return not not cursor.fetchall() - - def break_lock(self): - cursor = self.connection.cursor() - cursor.execute("delete from locks" - " where lock_file = ?", - (self.lock_file,)) - self.connection.commit() diff --git a/pipenv/patched/notpip/_vendor/lockfile/symlinklockfile.py b/pipenv/patched/notpip/_vendor/lockfile/symlinklockfile.py deleted file mode 100644 index 23b41f58..00000000 --- a/pipenv/patched/notpip/_vendor/lockfile/symlinklockfile.py +++ /dev/null @@ -1,70 +0,0 @@ -from __future__ import absolute_import - -import os -import time - -from . import (LockBase, NotLocked, NotMyLock, LockTimeout, - AlreadyLocked) - - -class SymlinkLockFile(LockBase): - """Lock access to a file using symlink(2).""" - - def __init__(self, path, threaded=True, timeout=None): - # super(SymlinkLockFile).__init(...) - LockBase.__init__(self, path, threaded, timeout) - # split it back! - self.unique_name = os.path.split(self.unique_name)[1] - - def acquire(self, timeout=None): - # Hopefully unnecessary for symlink. - # try: - # open(self.unique_name, "wb").close() - # except IOError: - # raise LockFailed("failed to create %s" % self.unique_name) - timeout = timeout if timeout is not None else self.timeout - end_time = time.time() - if timeout is not None and timeout > 0: - end_time += timeout - - while True: - # Try and create a symbolic link to it. - try: - os.symlink(self.unique_name, self.lock_file) - except OSError: - # Link creation failed. Maybe we've double-locked? - if self.i_am_locking(): - # Linked to out unique name. Proceed. - return - else: - # Otherwise the lock creation failed. - if timeout is not None and time.time() > end_time: - if timeout > 0: - raise LockTimeout("Timeout waiting to acquire" - " lock for %s" % - self.path) - else: - raise AlreadyLocked("%s is already locked" % - self.path) - time.sleep(timeout / 10 if timeout is not None else 0.1) - else: - # Link creation succeeded. We're good to go. - return - - def release(self): - if not self.is_locked(): - raise NotLocked("%s is not locked" % self.path) - elif not self.i_am_locking(): - raise NotMyLock("%s is locked, but not by me" % self.path) - os.unlink(self.lock_file) - - def is_locked(self): - return os.path.islink(self.lock_file) - - def i_am_locking(self): - return (os.path.islink(self.lock_file) - and os.readlink(self.lock_file) == self.unique_name) - - def break_lock(self): - if os.path.islink(self.lock_file): # exists && link - os.unlink(self.lock_file) diff --git a/pipenv/patched/notpip/_vendor/msgpack/__init__.py b/pipenv/patched/notpip/_vendor/msgpack/__init__.py index a15e5769..d6705e22 100644 --- a/pipenv/patched/notpip/_vendor/msgpack/__init__.py +++ b/pipenv/patched/notpip/_vendor/msgpack/__init__.py @@ -1,31 +1,19 @@ # coding: utf-8 -from pipenv.patched.notpip._vendor.msgpack._version import version -from pipenv.patched.notpip._vendor.msgpack.exceptions import * - -from collections import namedtuple - - -class ExtType(namedtuple('ExtType', 'code data')): - """ExtType represents ext type in msgpack.""" - def __new__(cls, code, data): - if not isinstance(code, int): - raise TypeError("code must be int") - if not isinstance(data, bytes): - raise TypeError("data must be bytes") - if not 0 <= code <= 127: - raise ValueError("code must be 0~127") - return super(ExtType, cls).__new__(cls, code, data) - +from ._version import version +from .exceptions import * +from .ext import ExtType, Timestamp import os -if os.environ.get('MSGPACK_PUREPYTHON'): - from pipenv.patched.notpip._vendor.msgpack.fallback import Packer, unpackb, Unpacker +import sys + + +if os.environ.get("MSGPACK_PUREPYTHON") or sys.version_info[0] == 2: + from .fallback import Packer, unpackb, Unpacker else: try: - from pipenv.patched.notpip._vendor.msgpack._packer import Packer - from pipenv.patched.notpip._vendor.msgpack._unpacker import unpackb, Unpacker + from ._cmsgpack import Packer, unpackb, Unpacker except ImportError: - from pipenv.patched.notpip._vendor.msgpack.fallback import Packer, unpackb, Unpacker + from .fallback import Packer, unpackb, Unpacker def pack(o, stream, **kwargs): diff --git a/pipenv/patched/notpip/_vendor/msgpack/_version.py b/pipenv/patched/notpip/_vendor/msgpack/_version.py index d28f0deb..1c83c8ed 100644 --- a/pipenv/patched/notpip/_vendor/msgpack/_version.py +++ b/pipenv/patched/notpip/_vendor/msgpack/_version.py @@ -1 +1 @@ -version = (0, 5, 6) +version = (1, 0, 2) diff --git a/pipenv/patched/notpip/_vendor/msgpack/exceptions.py b/pipenv/patched/notpip/_vendor/msgpack/exceptions.py index 97668814..d6d2615c 100644 --- a/pipenv/patched/notpip/_vendor/msgpack/exceptions.py +++ b/pipenv/patched/notpip/_vendor/msgpack/exceptions.py @@ -1,5 +1,10 @@ class UnpackException(Exception): - """Deprecated. Use Exception instead to catch all exception during unpacking.""" + """Base class for some exceptions raised while unpacking. + + NOTE: unpack may raise exception other than subclass of + UnpackException. If you want to catch all error, catch + Exception instead. + """ class BufferFull(UnpackException): @@ -10,11 +15,25 @@ class OutOfData(UnpackException): pass -class UnpackValueError(UnpackException, ValueError): - """Deprecated. Use ValueError instead.""" +class FormatError(ValueError, UnpackException): + """Invalid msgpack format""" + + +class StackError(ValueError, UnpackException): + """Too nested""" + + +# Deprecated. Use ValueError instead +UnpackValueError = ValueError class ExtraData(UnpackValueError): + """ExtraData is raised when there is trailing data. + + This exception is raised while only one-shot (not streaming) + unpack. + """ + def __init__(self, unpacked, extra): self.unpacked = unpacked self.extra = extra @@ -23,19 +42,7 @@ class ExtraData(UnpackValueError): return "unpack(b) received extra data." -class PackException(Exception): - """Deprecated. Use Exception instead to catch all exception during packing.""" - - -class PackValueError(PackException, ValueError): - """PackValueError is raised when type of input data is supported but it's value is unsupported. - - Deprecated. Use ValueError instead. - """ - - -class PackOverflowError(PackValueError, OverflowError): - """PackOverflowError is raised when integer value is out of range of msgpack support [-2**31, 2**32). - - Deprecated. Use ValueError instead. - """ +# Deprecated. Use Exception instead to catch all exception during packing. +PackException = Exception +PackValueError = ValueError +PackOverflowError = OverflowError diff --git a/pipenv/patched/notpip/_vendor/msgpack/ext.py b/pipenv/patched/notpip/_vendor/msgpack/ext.py new file mode 100644 index 00000000..4eb9dd65 --- /dev/null +++ b/pipenv/patched/notpip/_vendor/msgpack/ext.py @@ -0,0 +1,193 @@ +# coding: utf-8 +from collections import namedtuple +import datetime +import sys +import struct + + +PY2 = sys.version_info[0] == 2 + +if PY2: + int_types = (int, long) + _utc = None +else: + int_types = int + try: + _utc = datetime.timezone.utc + except AttributeError: + _utc = datetime.timezone(datetime.timedelta(0)) + + +class ExtType(namedtuple("ExtType", "code data")): + """ExtType represents ext type in msgpack.""" + + def __new__(cls, code, data): + if not isinstance(code, int): + raise TypeError("code must be int") + if not isinstance(data, bytes): + raise TypeError("data must be bytes") + if not 0 <= code <= 127: + raise ValueError("code must be 0~127") + return super(ExtType, cls).__new__(cls, code, data) + + +class Timestamp(object): + """Timestamp represents the Timestamp extension type in msgpack. + + When built with Cython, msgpack uses C methods to pack and unpack `Timestamp`. When using pure-Python + msgpack, :func:`to_bytes` and :func:`from_bytes` are used to pack and unpack `Timestamp`. + + This class is immutable: Do not override seconds and nanoseconds. + """ + + __slots__ = ["seconds", "nanoseconds"] + + def __init__(self, seconds, nanoseconds=0): + """Initialize a Timestamp object. + + :param int seconds: + Number of seconds since the UNIX epoch (00:00:00 UTC Jan 1 1970, minus leap seconds). + May be negative. + + :param int nanoseconds: + Number of nanoseconds to add to `seconds` to get fractional time. + Maximum is 999_999_999. Default is 0. + + Note: Negative times (before the UNIX epoch) are represented as negative seconds + positive ns. + """ + if not isinstance(seconds, int_types): + raise TypeError("seconds must be an interger") + if not isinstance(nanoseconds, int_types): + raise TypeError("nanoseconds must be an integer") + if not (0 <= nanoseconds < 10 ** 9): + raise ValueError( + "nanoseconds must be a non-negative integer less than 999999999." + ) + self.seconds = seconds + self.nanoseconds = nanoseconds + + def __repr__(self): + """String representation of Timestamp.""" + return "Timestamp(seconds={0}, nanoseconds={1})".format( + self.seconds, self.nanoseconds + ) + + def __eq__(self, other): + """Check for equality with another Timestamp object""" + if type(other) is self.__class__: + return ( + self.seconds == other.seconds and self.nanoseconds == other.nanoseconds + ) + return False + + def __ne__(self, other): + """not-equals method (see :func:`__eq__()`)""" + return not self.__eq__(other) + + def __hash__(self): + return hash((self.seconds, self.nanoseconds)) + + @staticmethod + def from_bytes(b): + """Unpack bytes into a `Timestamp` object. + + Used for pure-Python msgpack unpacking. + + :param b: Payload from msgpack ext message with code -1 + :type b: bytes + + :returns: Timestamp object unpacked from msgpack ext payload + :rtype: Timestamp + """ + if len(b) == 4: + seconds = struct.unpack("!L", b)[0] + nanoseconds = 0 + elif len(b) == 8: + data64 = struct.unpack("!Q", b)[0] + seconds = data64 & 0x00000003FFFFFFFF + nanoseconds = data64 >> 34 + elif len(b) == 12: + nanoseconds, seconds = struct.unpack("!Iq", b) + else: + raise ValueError( + "Timestamp type can only be created from 32, 64, or 96-bit byte objects" + ) + return Timestamp(seconds, nanoseconds) + + def to_bytes(self): + """Pack this Timestamp object into bytes. + + Used for pure-Python msgpack packing. + + :returns data: Payload for EXT message with code -1 (timestamp type) + :rtype: bytes + """ + if (self.seconds >> 34) == 0: # seconds is non-negative and fits in 34 bits + data64 = self.nanoseconds << 34 | self.seconds + if data64 & 0xFFFFFFFF00000000 == 0: + # nanoseconds is zero and seconds < 2**32, so timestamp 32 + data = struct.pack("!L", data64) + else: + # timestamp 64 + data = struct.pack("!Q", data64) + else: + # timestamp 96 + data = struct.pack("!Iq", self.nanoseconds, self.seconds) + return data + + @staticmethod + def from_unix(unix_sec): + """Create a Timestamp from posix timestamp in seconds. + + :param unix_float: Posix timestamp in seconds. + :type unix_float: int or float. + """ + seconds = int(unix_sec // 1) + nanoseconds = int((unix_sec % 1) * 10 ** 9) + return Timestamp(seconds, nanoseconds) + + def to_unix(self): + """Get the timestamp as a floating-point value. + + :returns: posix timestamp + :rtype: float + """ + return self.seconds + self.nanoseconds / 1e9 + + @staticmethod + def from_unix_nano(unix_ns): + """Create a Timestamp from posix timestamp in nanoseconds. + + :param int unix_ns: Posix timestamp in nanoseconds. + :rtype: Timestamp + """ + return Timestamp(*divmod(unix_ns, 10 ** 9)) + + def to_unix_nano(self): + """Get the timestamp as a unixtime in nanoseconds. + + :returns: posix timestamp in nanoseconds + :rtype: int + """ + return self.seconds * 10 ** 9 + self.nanoseconds + + def to_datetime(self): + """Get the timestamp as a UTC datetime. + + Python 2 is not supported. + + :rtype: datetime. + """ + return datetime.datetime.fromtimestamp(0, _utc) + datetime.timedelta( + seconds=self.to_unix() + ) + + @staticmethod + def from_datetime(dt): + """Create a Timestamp from datetime with tzinfo. + + Python 2 is not supported. + + :rtype: Timestamp + """ + return Timestamp.from_unix(dt.timestamp()) diff --git a/pipenv/patched/notpip/_vendor/msgpack/fallback.py b/pipenv/patched/notpip/_vendor/msgpack/fallback.py index d3a7d558..0bfa94ea 100644 --- a/pipenv/patched/notpip/_vendor/msgpack/fallback.py +++ b/pipenv/patched/notpip/_vendor/msgpack/fallback.py @@ -1,76 +1,98 @@ """Fallback pure Python implementation of msgpack""" +from datetime import datetime as _DateTime import sys import struct -import warnings -if sys.version_info[0] == 3: - PY3 = True - int_types = int - Unicode = str - xrange = range - def dict_iteritems(d): - return d.items() -else: - PY3 = False + +PY2 = sys.version_info[0] == 2 +if PY2: int_types = (int, long) - Unicode = unicode + def dict_iteritems(d): return d.iteritems() -if hasattr(sys, 'pypy_version_info'): - # cStringIO is slow on PyPy, StringIO is faster. However: PyPy's own +else: + int_types = int + unicode = str + xrange = range + + def dict_iteritems(d): + return d.items() + + +if sys.version_info < (3, 5): + # Ugly hack... + RecursionError = RuntimeError + + def _is_recursionerror(e): + return ( + len(e.args) == 1 + and isinstance(e.args[0], str) + and e.args[0].startswith("maximum recursion depth exceeded") + ) + + +else: + + def _is_recursionerror(e): + return True + + +if hasattr(sys, "pypy_version_info"): + # StringIO is slow on PyPy, StringIO is faster. However: PyPy's own # StringBuilder is fastest. from __pypy__ import newlist_hint + try: from __pypy__.builders import BytesBuilder as StringBuilder except ImportError: from __pypy__.builders import StringBuilder USING_STRINGBUILDER = True + class StringIO(object): - def __init__(self, s=b''): + def __init__(self, s=b""): if s: self.builder = StringBuilder(len(s)) self.builder.append(s) else: self.builder = StringBuilder() + def write(self, s): if isinstance(s, memoryview): s = s.tobytes() elif isinstance(s, bytearray): s = bytes(s) self.builder.append(s) + def getvalue(self): return self.builder.build() + + else: USING_STRINGBUILDER = False from io import BytesIO as StringIO + newlist_hint = lambda size: [] -from pipenv.patched.notpip._vendor.msgpack.exceptions import ( - BufferFull, - OutOfData, - UnpackValueError, - PackValueError, - PackOverflowError, - ExtraData) +from .exceptions import BufferFull, OutOfData, ExtraData, FormatError, StackError -from pipenv.patched.notpip._vendor.msgpack import ExtType +from .ext import ExtType, Timestamp -EX_SKIP = 0 -EX_CONSTRUCT = 1 -EX_READ_ARRAY_HEADER = 2 -EX_READ_MAP_HEADER = 3 +EX_SKIP = 0 +EX_CONSTRUCT = 1 +EX_READ_ARRAY_HEADER = 2 +EX_READ_MAP_HEADER = 3 -TYPE_IMMEDIATE = 0 -TYPE_ARRAY = 1 -TYPE_MAP = 2 -TYPE_RAW = 3 -TYPE_BIN = 4 -TYPE_EXT = 5 +TYPE_IMMEDIATE = 0 +TYPE_ARRAY = 1 +TYPE_MAP = 2 +TYPE_RAW = 3 +TYPE_BIN = 4 +TYPE_EXT = 5 DEFAULT_RECURSE_LIMIT = 511 @@ -83,53 +105,54 @@ def _check_type_strict(obj, t, type=type, tuple=tuple): def _get_data_from_buffer(obj): - try: - view = memoryview(obj) - except TypeError: - # try to use legacy buffer protocol if 2.7, otherwise re-raise - if not PY3: - view = memoryview(buffer(obj)) - warnings.warn("using old buffer interface to unpack %s; " - "this leads to unpacking errors if slicing is used and " - "will be removed in a future version" % type(obj), - RuntimeWarning) - else: - raise + view = memoryview(obj) if view.itemsize != 1: raise ValueError("cannot unpack from multi-byte object") return view -def unpack(stream, **kwargs): - warnings.warn( - "Direct calling implementation's unpack() is deprecated, Use msgpack.unpack() or unpackb() instead.", - PendingDeprecationWarning) - data = stream.read() - return unpackb(data, **kwargs) - - def unpackb(packed, **kwargs): """ Unpack an object from `packed`. - Raises `ExtraData` when `packed` contains extra bytes. + Raises ``ExtraData`` when *packed* contains extra bytes. + Raises ``ValueError`` when *packed* is incomplete. + Raises ``FormatError`` when *packed* is not valid msgpack. + Raises ``StackError`` when *packed* contains too nested. + Other exceptions can be raised during unpacking. + See :class:`Unpacker` for options. """ - unpacker = Unpacker(None, **kwargs) + unpacker = Unpacker(None, max_buffer_size=len(packed), **kwargs) unpacker.feed(packed) try: ret = unpacker._unpack() except OutOfData: - raise UnpackValueError("Data is not enough.") + raise ValueError("Unpack failed: incomplete input") + except RecursionError as e: + if _is_recursionerror(e): + raise StackError + raise if unpacker._got_extradata(): raise ExtraData(ret, unpacker._get_extradata()) return ret +if sys.version_info < (2, 7, 6): + + def _unpack_from(f, b, o=0): + """Explicit type cast for legacy struct.unpack_from""" + return struct.unpack_from(f, bytes(b), o) + + +else: + _unpack_from = struct.unpack_from + + class Unpacker(object): """Streaming unpacker. - arguments: + Arguments: :param file_like: File-like object having `.read(n)` method. @@ -143,14 +166,19 @@ class Unpacker(object): Otherwise, unpack to Python tuple. (default: True) :param bool raw: - If true, unpack msgpack raw to Python bytes (default). - Otherwise, unpack to Python str (or unicode on Python 2) by decoding - with UTF-8 encoding (recommended). - Currently, the default is true, but it will be changed to false in - near future. So you must specify it explicitly for keeping backward - compatibility. + If true, unpack msgpack raw to Python bytes. + Otherwise, unpack to Python str by decoding with UTF-8 encoding (default). - *encoding* option which is deprecated overrides this option. + :param int timestamp: + Control how timestamp type is unpacked: + + 0 - Timestamp + 1 - float (Seconds from the EPOCH) + 2 - int (Nanoseconds from the EPOCH) + 3 - datetime.datetime (UTC). Python 2 is not supported. + + :param bool strict_map_key: + If true (default), only str or bytes are accepted for map (dict) keys. :param callable object_hook: When specified, it should be callable. @@ -162,41 +190,46 @@ class Unpacker(object): Unpacker calls it with a list of key-value pairs after unpacking msgpack map. (See also simplejson) - :param str encoding: - Encoding used for decoding msgpack raw. - If it is None (default), msgpack raw is deserialized to Python bytes. - :param str unicode_errors: - (deprecated) Used for decoding msgpack raw with *encoding*. - (default: `'strict'`) + The error handler for decoding unicode. (default: 'strict') + This option should be used only when you have msgpack data which + contains invalid UTF-8 string. :param int max_buffer_size: - Limits size of data waiting unpacked. 0 means system's INT_MAX (default). + Limits size of data waiting unpacked. 0 means 2**32-1. + The default value is 100*1024*1024 (100MiB). Raises `BufferFull` exception when it is insufficient. You should set this parameter when unpacking data from untrusted source. :param int max_str_len: - Limits max length of str. (default: 2**31-1) + Deprecated, use *max_buffer_size* instead. + Limits max length of str. (default: max_buffer_size) :param int max_bin_len: - Limits max length of bin. (default: 2**31-1) + Deprecated, use *max_buffer_size* instead. + Limits max length of bin. (default: max_buffer_size) :param int max_array_len: - Limits max length of array. (default: 2**31-1) + Limits max length of array. + (default: max_buffer_size) :param int max_map_len: - Limits max length of map. (default: 2**31-1) + Limits max length of map. + (default: max_buffer_size//2) + :param int max_ext_len: + Deprecated, use *max_buffer_size* instead. + Limits max size of ext type. (default: max_buffer_size) - example of streaming deserialize from file-like object:: + Example of streaming deserialize from file-like object:: - unpacker = Unpacker(file_like, raw=False) + unpacker = Unpacker(file_like) for o in unpacker: process(o) - example of streaming deserialize from socket:: + Example of streaming deserialize from socket:: - unpacker = Unpacker(raw=False) + unpacker = Unpacker(max_buffer_size) while True: buf = sock.recv(1024**2) if not buf: @@ -204,25 +237,36 @@ class Unpacker(object): unpacker.feed(buf) for o in unpacker: process(o) + + Raises ``ExtraData`` when *packed* contains extra bytes. + Raises ``OutOfData`` when *packed* is incomplete. + Raises ``FormatError`` when *packed* is not valid msgpack. + Raises ``StackError`` when *packed* contains too nested. + Other exceptions can be raised during unpacking. """ - def __init__(self, file_like=None, read_size=0, use_list=True, raw=True, - object_hook=None, object_pairs_hook=None, list_hook=None, - encoding=None, unicode_errors=None, max_buffer_size=0, - ext_hook=ExtType, - max_str_len=2147483647, # 2**32-1 - max_bin_len=2147483647, - max_array_len=2147483647, - max_map_len=2147483647, - max_ext_len=2147483647): - - if encoding is not None: - warnings.warn( - "encoding is deprecated, Use raw=False instead.", - PendingDeprecationWarning) - + def __init__( + self, + file_like=None, + read_size=0, + use_list=True, + raw=False, + timestamp=0, + strict_map_key=True, + object_hook=None, + object_pairs_hook=None, + list_hook=None, + unicode_errors=None, + max_buffer_size=100 * 1024 * 1024, + ext_hook=ExtType, + max_str_len=-1, + max_bin_len=-1, + max_array_len=-1, + max_map_len=-1, + max_ext_len=-1, + ): if unicode_errors is None: - unicode_errors = 'strict' + unicode_errors = "strict" if file_like is None: self._feeding = True @@ -234,12 +278,6 @@ class Unpacker(object): #: array of bytes fed. self._buffer = bytearray() - # Some very old pythons don't support `struct.unpack_from()` with a - # `bytearray`. So we wrap it in a `buffer()` there. - if sys.version_info < (2, 7, 6): - self._buffer_view = buffer(self._buffer) - else: - self._buffer_view = self._buffer #: Which position we currently reads self._buff_i = 0 @@ -252,14 +290,30 @@ class Unpacker(object): # state, which _buf_checkpoint records. self._buf_checkpoint = 0 - self._max_buffer_size = max_buffer_size or 2**31-1 + if not max_buffer_size: + max_buffer_size = 2 ** 31 - 1 + if max_str_len == -1: + max_str_len = max_buffer_size + if max_bin_len == -1: + max_bin_len = max_buffer_size + if max_array_len == -1: + max_array_len = max_buffer_size + if max_map_len == -1: + max_map_len = max_buffer_size // 2 + if max_ext_len == -1: + max_ext_len = max_buffer_size + + self._max_buffer_size = max_buffer_size if read_size > self._max_buffer_size: raise ValueError("read_size must be smaller than max_buffer_size") - self._read_size = read_size or min(self._max_buffer_size, 16*1024) + self._read_size = read_size or min(self._max_buffer_size, 16 * 1024) self._raw = bool(raw) - self._encoding = encoding + self._strict_map_key = bool(strict_map_key) self._unicode_errors = unicode_errors self._use_list = use_list + if not (0 <= timestamp <= 3): + raise ValueError("timestamp must be 0..3") + self._timestamp = timestamp self._list_hook = list_hook self._object_hook = object_hook self._object_pairs_hook = object_pairs_hook @@ -272,30 +326,32 @@ class Unpacker(object): self._stream_offset = 0 if list_hook is not None and not callable(list_hook): - raise TypeError('`list_hook` is not callable') + raise TypeError("`list_hook` is not callable") if object_hook is not None and not callable(object_hook): - raise TypeError('`object_hook` is not callable') + raise TypeError("`object_hook` is not callable") if object_pairs_hook is not None and not callable(object_pairs_hook): - raise TypeError('`object_pairs_hook` is not callable') + raise TypeError("`object_pairs_hook` is not callable") if object_hook is not None and object_pairs_hook is not None: - raise TypeError("object_pairs_hook and object_hook are mutually " - "exclusive") + raise TypeError( + "object_pairs_hook and object_hook are mutually " "exclusive" + ) if not callable(ext_hook): raise TypeError("`ext_hook` is not callable") def feed(self, next_bytes): assert self._feeding view = _get_data_from_buffer(next_bytes) - if (len(self._buffer) - self._buff_i + len(view) > self._max_buffer_size): + if len(self._buffer) - self._buff_i + len(view) > self._max_buffer_size: raise BufferFull # Strip buffer before checkpoint before reading file. if self._buf_checkpoint > 0: - del self._buffer[:self._buf_checkpoint] + del self._buffer[: self._buf_checkpoint] self._buff_i -= self._buf_checkpoint self._buf_checkpoint = 0 - self._buffer += view + # Use extend here: INPLACE_ADD += doesn't reliably typecast memoryview in jython + self._buffer.extend(view) def _consume(self): """ Gets rid of the used parts of the buffer. """ @@ -306,19 +362,22 @@ class Unpacker(object): return self._buff_i < len(self._buffer) def _get_extradata(self): - return self._buffer[self._buff_i:] + return self._buffer[self._buff_i :] def read_bytes(self, n): - return self._read(n) + ret = self._read(n, raise_outofdata=False) + self._consume() + return ret - def _read(self, n): + def _read(self, n, raise_outofdata=True): # (int) -> bytearray - self._reserve(n) + self._reserve(n, raise_outofdata=raise_outofdata) i = self._buff_i - self._buff_i = i+n - return self._buffer[i:i+n] + ret = self._buffer[i : i + n] + self._buff_i = i + len(ret) + return ret - def _reserve(self, n): + def _reserve(self, n, raise_outofdata=True): remain_bytes = len(self._buffer) - self._buff_i - n # Fast path: buffer has n bytes already @@ -331,7 +390,7 @@ class Unpacker(object): # Strip buffer before checkpoint before reading file. if self._buf_checkpoint > 0: - del self._buffer[:self._buf_checkpoint] + del self._buffer[: self._buf_checkpoint] self._buff_i -= self._buf_checkpoint self._buf_checkpoint = 0 @@ -346,7 +405,7 @@ class Unpacker(object): self._buffer += read_data remain_bytes -= len(read_data) - if len(self._buffer) < n + self._buff_i: + if len(self._buffer) < n + self._buff_i and raise_outofdata: self._buff_i = 0 # rollback raise OutOfData @@ -360,206 +419,206 @@ class Unpacker(object): if b & 0b10000000 == 0: obj = b elif b & 0b11100000 == 0b11100000: - obj = -1 - (b ^ 0xff) + obj = -1 - (b ^ 0xFF) elif b & 0b11100000 == 0b10100000: n = b & 0b00011111 typ = TYPE_RAW if n > self._max_str_len: - raise UnpackValueError("%s exceeds max_str_len(%s)", n, self._max_str_len) + raise ValueError("%s exceeds max_str_len(%s)", n, self._max_str_len) obj = self._read(n) elif b & 0b11110000 == 0b10010000: n = b & 0b00001111 typ = TYPE_ARRAY if n > self._max_array_len: - raise UnpackValueError("%s exceeds max_array_len(%s)", n, self._max_array_len) + raise ValueError("%s exceeds max_array_len(%s)", n, self._max_array_len) elif b & 0b11110000 == 0b10000000: n = b & 0b00001111 typ = TYPE_MAP if n > self._max_map_len: - raise UnpackValueError("%s exceeds max_map_len(%s)", n, self._max_map_len) - elif b == 0xc0: + raise ValueError("%s exceeds max_map_len(%s)", n, self._max_map_len) + elif b == 0xC0: obj = None - elif b == 0xc2: + elif b == 0xC2: obj = False - elif b == 0xc3: + elif b == 0xC3: obj = True - elif b == 0xc4: + elif b == 0xC4: typ = TYPE_BIN self._reserve(1) n = self._buffer[self._buff_i] self._buff_i += 1 if n > self._max_bin_len: - raise UnpackValueError("%s exceeds max_bin_len(%s)" % (n, self._max_bin_len)) + raise ValueError("%s exceeds max_bin_len(%s)" % (n, self._max_bin_len)) obj = self._read(n) - elif b == 0xc5: + elif b == 0xC5: typ = TYPE_BIN self._reserve(2) - n = struct.unpack_from(">H", self._buffer_view, self._buff_i)[0] + n = _unpack_from(">H", self._buffer, self._buff_i)[0] self._buff_i += 2 if n > self._max_bin_len: - raise UnpackValueError("%s exceeds max_bin_len(%s)" % (n, self._max_bin_len)) + raise ValueError("%s exceeds max_bin_len(%s)" % (n, self._max_bin_len)) obj = self._read(n) - elif b == 0xc6: + elif b == 0xC6: typ = TYPE_BIN self._reserve(4) - n = struct.unpack_from(">I", self._buffer_view, self._buff_i)[0] + n = _unpack_from(">I", self._buffer, self._buff_i)[0] self._buff_i += 4 if n > self._max_bin_len: - raise UnpackValueError("%s exceeds max_bin_len(%s)" % (n, self._max_bin_len)) + raise ValueError("%s exceeds max_bin_len(%s)" % (n, self._max_bin_len)) obj = self._read(n) - elif b == 0xc7: # ext 8 + elif b == 0xC7: # ext 8 typ = TYPE_EXT self._reserve(2) - L, n = struct.unpack_from('Bb', self._buffer_view, self._buff_i) + L, n = _unpack_from("Bb", self._buffer, self._buff_i) self._buff_i += 2 if L > self._max_ext_len: - raise UnpackValueError("%s exceeds max_ext_len(%s)" % (L, self._max_ext_len)) + raise ValueError("%s exceeds max_ext_len(%s)" % (L, self._max_ext_len)) obj = self._read(L) - elif b == 0xc8: # ext 16 + elif b == 0xC8: # ext 16 typ = TYPE_EXT self._reserve(3) - L, n = struct.unpack_from('>Hb', self._buffer_view, self._buff_i) + L, n = _unpack_from(">Hb", self._buffer, self._buff_i) self._buff_i += 3 if L > self._max_ext_len: - raise UnpackValueError("%s exceeds max_ext_len(%s)" % (L, self._max_ext_len)) + raise ValueError("%s exceeds max_ext_len(%s)" % (L, self._max_ext_len)) obj = self._read(L) - elif b == 0xc9: # ext 32 + elif b == 0xC9: # ext 32 typ = TYPE_EXT self._reserve(5) - L, n = struct.unpack_from('>Ib', self._buffer_view, self._buff_i) + L, n = _unpack_from(">Ib", self._buffer, self._buff_i) self._buff_i += 5 if L > self._max_ext_len: - raise UnpackValueError("%s exceeds max_ext_len(%s)" % (L, self._max_ext_len)) + raise ValueError("%s exceeds max_ext_len(%s)" % (L, self._max_ext_len)) obj = self._read(L) - elif b == 0xca: + elif b == 0xCA: self._reserve(4) - obj = struct.unpack_from(">f", self._buffer_view, self._buff_i)[0] + obj = _unpack_from(">f", self._buffer, self._buff_i)[0] self._buff_i += 4 - elif b == 0xcb: + elif b == 0xCB: self._reserve(8) - obj = struct.unpack_from(">d", self._buffer_view, self._buff_i)[0] + obj = _unpack_from(">d", self._buffer, self._buff_i)[0] self._buff_i += 8 - elif b == 0xcc: + elif b == 0xCC: self._reserve(1) obj = self._buffer[self._buff_i] self._buff_i += 1 - elif b == 0xcd: + elif b == 0xCD: self._reserve(2) - obj = struct.unpack_from(">H", self._buffer_view, self._buff_i)[0] + obj = _unpack_from(">H", self._buffer, self._buff_i)[0] self._buff_i += 2 - elif b == 0xce: + elif b == 0xCE: self._reserve(4) - obj = struct.unpack_from(">I", self._buffer_view, self._buff_i)[0] + obj = _unpack_from(">I", self._buffer, self._buff_i)[0] self._buff_i += 4 - elif b == 0xcf: + elif b == 0xCF: self._reserve(8) - obj = struct.unpack_from(">Q", self._buffer_view, self._buff_i)[0] + obj = _unpack_from(">Q", self._buffer, self._buff_i)[0] self._buff_i += 8 - elif b == 0xd0: + elif b == 0xD0: self._reserve(1) - obj = struct.unpack_from("b", self._buffer_view, self._buff_i)[0] + obj = _unpack_from("b", self._buffer, self._buff_i)[0] self._buff_i += 1 - elif b == 0xd1: + elif b == 0xD1: self._reserve(2) - obj = struct.unpack_from(">h", self._buffer_view, self._buff_i)[0] + obj = _unpack_from(">h", self._buffer, self._buff_i)[0] self._buff_i += 2 - elif b == 0xd2: + elif b == 0xD2: self._reserve(4) - obj = struct.unpack_from(">i", self._buffer_view, self._buff_i)[0] + obj = _unpack_from(">i", self._buffer, self._buff_i)[0] self._buff_i += 4 - elif b == 0xd3: + elif b == 0xD3: self._reserve(8) - obj = struct.unpack_from(">q", self._buffer_view, self._buff_i)[0] + obj = _unpack_from(">q", self._buffer, self._buff_i)[0] self._buff_i += 8 - elif b == 0xd4: # fixext 1 + elif b == 0xD4: # fixext 1 typ = TYPE_EXT if self._max_ext_len < 1: - raise UnpackValueError("%s exceeds max_ext_len(%s)" % (1, self._max_ext_len)) + raise ValueError("%s exceeds max_ext_len(%s)" % (1, self._max_ext_len)) self._reserve(2) - n, obj = struct.unpack_from("b1s", self._buffer_view, self._buff_i) + n, obj = _unpack_from("b1s", self._buffer, self._buff_i) self._buff_i += 2 - elif b == 0xd5: # fixext 2 + elif b == 0xD5: # fixext 2 typ = TYPE_EXT if self._max_ext_len < 2: - raise UnpackValueError("%s exceeds max_ext_len(%s)" % (2, self._max_ext_len)) + raise ValueError("%s exceeds max_ext_len(%s)" % (2, self._max_ext_len)) self._reserve(3) - n, obj = struct.unpack_from("b2s", self._buffer_view, self._buff_i) + n, obj = _unpack_from("b2s", self._buffer, self._buff_i) self._buff_i += 3 - elif b == 0xd6: # fixext 4 + elif b == 0xD6: # fixext 4 typ = TYPE_EXT if self._max_ext_len < 4: - raise UnpackValueError("%s exceeds max_ext_len(%s)" % (4, self._max_ext_len)) + raise ValueError("%s exceeds max_ext_len(%s)" % (4, self._max_ext_len)) self._reserve(5) - n, obj = struct.unpack_from("b4s", self._buffer_view, self._buff_i) + n, obj = _unpack_from("b4s", self._buffer, self._buff_i) self._buff_i += 5 - elif b == 0xd7: # fixext 8 + elif b == 0xD7: # fixext 8 typ = TYPE_EXT if self._max_ext_len < 8: - raise UnpackValueError("%s exceeds max_ext_len(%s)" % (8, self._max_ext_len)) + raise ValueError("%s exceeds max_ext_len(%s)" % (8, self._max_ext_len)) self._reserve(9) - n, obj = struct.unpack_from("b8s", self._buffer_view, self._buff_i) + n, obj = _unpack_from("b8s", self._buffer, self._buff_i) self._buff_i += 9 - elif b == 0xd8: # fixext 16 + elif b == 0xD8: # fixext 16 typ = TYPE_EXT if self._max_ext_len < 16: - raise UnpackValueError("%s exceeds max_ext_len(%s)" % (16, self._max_ext_len)) + raise ValueError("%s exceeds max_ext_len(%s)" % (16, self._max_ext_len)) self._reserve(17) - n, obj = struct.unpack_from("b16s", self._buffer_view, self._buff_i) + n, obj = _unpack_from("b16s", self._buffer, self._buff_i) self._buff_i += 17 - elif b == 0xd9: + elif b == 0xD9: typ = TYPE_RAW self._reserve(1) n = self._buffer[self._buff_i] self._buff_i += 1 if n > self._max_str_len: - raise UnpackValueError("%s exceeds max_str_len(%s)", n, self._max_str_len) + raise ValueError("%s exceeds max_str_len(%s)", n, self._max_str_len) obj = self._read(n) - elif b == 0xda: + elif b == 0xDA: typ = TYPE_RAW self._reserve(2) - n, = struct.unpack_from(">H", self._buffer_view, self._buff_i) + (n,) = _unpack_from(">H", self._buffer, self._buff_i) self._buff_i += 2 if n > self._max_str_len: - raise UnpackValueError("%s exceeds max_str_len(%s)", n, self._max_str_len) + raise ValueError("%s exceeds max_str_len(%s)", n, self._max_str_len) obj = self._read(n) - elif b == 0xdb: + elif b == 0xDB: typ = TYPE_RAW self._reserve(4) - n, = struct.unpack_from(">I", self._buffer_view, self._buff_i) + (n,) = _unpack_from(">I", self._buffer, self._buff_i) self._buff_i += 4 if n > self._max_str_len: - raise UnpackValueError("%s exceeds max_str_len(%s)", n, self._max_str_len) + raise ValueError("%s exceeds max_str_len(%s)", n, self._max_str_len) obj = self._read(n) - elif b == 0xdc: + elif b == 0xDC: typ = TYPE_ARRAY self._reserve(2) - n, = struct.unpack_from(">H", self._buffer_view, self._buff_i) + (n,) = _unpack_from(">H", self._buffer, self._buff_i) self._buff_i += 2 if n > self._max_array_len: - raise UnpackValueError("%s exceeds max_array_len(%s)", n, self._max_array_len) - elif b == 0xdd: + raise ValueError("%s exceeds max_array_len(%s)", n, self._max_array_len) + elif b == 0xDD: typ = TYPE_ARRAY self._reserve(4) - n, = struct.unpack_from(">I", self._buffer_view, self._buff_i) + (n,) = _unpack_from(">I", self._buffer, self._buff_i) self._buff_i += 4 if n > self._max_array_len: - raise UnpackValueError("%s exceeds max_array_len(%s)", n, self._max_array_len) - elif b == 0xde: + raise ValueError("%s exceeds max_array_len(%s)", n, self._max_array_len) + elif b == 0xDE: self._reserve(2) - n, = struct.unpack_from(">H", self._buffer_view, self._buff_i) + (n,) = _unpack_from(">H", self._buffer, self._buff_i) self._buff_i += 2 if n > self._max_map_len: - raise UnpackValueError("%s exceeds max_map_len(%s)", n, self._max_map_len) + raise ValueError("%s exceeds max_map_len(%s)", n, self._max_map_len) typ = TYPE_MAP - elif b == 0xdf: + elif b == 0xDF: self._reserve(4) - n, = struct.unpack_from(">I", self._buffer_view, self._buff_i) + (n,) = _unpack_from(">I", self._buffer, self._buff_i) self._buff_i += 4 if n > self._max_map_len: - raise UnpackValueError("%s exceeds max_map_len(%s)", n, self._max_map_len) + raise ValueError("%s exceeds max_map_len(%s)", n, self._max_map_len) typ = TYPE_MAP else: - raise UnpackValueError("Unknown header: 0x%x" % b) + raise FormatError("Unknown header: 0x%x" % b) return typ, n, obj def _unpack(self, execute=EX_CONSTRUCT): @@ -567,11 +626,11 @@ class Unpacker(object): if execute == EX_READ_ARRAY_HEADER: if typ != TYPE_ARRAY: - raise UnpackValueError("Expected array") + raise ValueError("Expected array") return n if execute == EX_READ_MAP_HEADER: if typ != TYPE_MAP: - raise UnpackValueError("Expected map") + raise ValueError("Expected map") return n # TODO should we eliminate the recursion? if typ == TYPE_ARRAY: @@ -596,13 +655,19 @@ class Unpacker(object): return if self._object_pairs_hook is not None: ret = self._object_pairs_hook( - (self._unpack(EX_CONSTRUCT), - self._unpack(EX_CONSTRUCT)) - for _ in xrange(n)) + (self._unpack(EX_CONSTRUCT), self._unpack(EX_CONSTRUCT)) + for _ in xrange(n) + ) else: ret = {} for _ in xrange(n): key = self._unpack(EX_CONSTRUCT) + if self._strict_map_key and type(key) not in (unicode, bytes): + raise ValueError( + "%s is not allowed for map key" % str(type(key)) + ) + if not PY2 and type(key) is str: + key = sys.intern(key) ret[key] = self._unpack(EX_CONSTRUCT) if self._object_hook is not None: ret = self._object_hook(ret) @@ -610,17 +675,26 @@ class Unpacker(object): if execute == EX_SKIP: return if typ == TYPE_RAW: - if self._encoding is not None: - obj = obj.decode(self._encoding, self._unicode_errors) - elif self._raw: + if self._raw: obj = bytes(obj) else: - obj = obj.decode('utf_8') + obj = obj.decode("utf_8", self._unicode_errors) return obj - if typ == TYPE_EXT: - return self._ext_hook(n, bytes(obj)) if typ == TYPE_BIN: return bytes(obj) + if typ == TYPE_EXT: + if n == -1: # timestamp + ts = Timestamp.from_bytes(bytes(obj)) + if self._timestamp == 1: + return ts.to_unix() + elif self._timestamp == 2: + return ts.to_unix_nano() + elif self._timestamp == 3: + return ts.to_datetime() + else: + return ts + else: + return self._ext_hook(n, bytes(obj)) assert typ == TYPE_IMMEDIATE return obj @@ -635,37 +709,30 @@ class Unpacker(object): except OutOfData: self._consume() raise StopIteration + except RecursionError: + raise StackError next = __next__ - def skip(self, write_bytes=None): + def skip(self): self._unpack(EX_SKIP) - if write_bytes is not None: - warnings.warn("`write_bytes` option is deprecated. Use `.tell()` instead.", DeprecationWarning) - write_bytes(self._buffer[self._buf_checkpoint:self._buff_i]) self._consume() - def unpack(self, write_bytes=None): - ret = self._unpack(EX_CONSTRUCT) - if write_bytes is not None: - warnings.warn("`write_bytes` option is deprecated. Use `.tell()` instead.", DeprecationWarning) - write_bytes(self._buffer[self._buf_checkpoint:self._buff_i]) + def unpack(self): + try: + ret = self._unpack(EX_CONSTRUCT) + except RecursionError: + raise StackError self._consume() return ret - def read_array_header(self, write_bytes=None): + def read_array_header(self): ret = self._unpack(EX_READ_ARRAY_HEADER) - if write_bytes is not None: - warnings.warn("`write_bytes` option is deprecated. Use `.tell()` instead.", DeprecationWarning) - write_bytes(self._buffer[self._buf_checkpoint:self._buff_i]) self._consume() return ret - def read_map_header(self, write_bytes=None): + def read_map_header(self): ret = self._unpack(EX_READ_MAP_HEADER) - if write_bytes is not None: - warnings.warn("`write_bytes` option is deprecated. Use `.tell()` instead.", DeprecationWarning) - write_bytes(self._buffer[self._buf_checkpoint:self._buff_i]) self._consume() return ret @@ -677,7 +744,7 @@ class Packer(object): """ MessagePack Packer - usage: + Usage:: packer = Packer() astream.write(packer.pack(a)) @@ -698,49 +765,81 @@ class Packer(object): :param bool use_bin_type: Use bin type introduced in msgpack spec 2.0 for bytes. - It also enables str8 type for unicode. + It also enables str8 type for unicode. (default: True) :param bool strict_types: If set to true, types will be checked to be exact. Derived classes - from serializeable types will not be serialized and will be + from serializable types will not be serialized and will be treated as unsupported type and forwarded to default. Additionally tuples will not be serialized as lists. This is useful when trying to implement accurate serialization for python types. - :param str encoding: - (deprecated) Convert unicode to bytes with this encoding. (default: 'utf-8') + :param bool datetime: + If set to true, datetime with tzinfo is packed into Timestamp type. + Note that the tzinfo is stripped in the timestamp. + You can get UTC datetime with `timestamp=3` option of the Unpacker. + (Python 2 is not supported). :param str unicode_errors: - Error handler for encoding unicode. (default: 'strict') + The error handler for encoding unicode. (default: 'strict') + DO NOT USE THIS!! This option is kept for very specific usage. + + Example of streaming deserialize from file-like object:: + + unpacker = Unpacker(file_like) + for o in unpacker: + process(o) + + Example of streaming deserialize from socket:: + + unpacker = Unpacker() + while True: + buf = sock.recv(1024**2) + if not buf: + break + unpacker.feed(buf) + for o in unpacker: + process(o) + + Raises ``ExtraData`` when *packed* contains extra bytes. + Raises ``OutOfData`` when *packed* is incomplete. + Raises ``FormatError`` when *packed* is not valid msgpack. + Raises ``StackError`` when *packed* contains too nested. + Other exceptions can be raised during unpacking. """ - def __init__(self, default=None, encoding=None, unicode_errors=None, - use_single_float=False, autoreset=True, use_bin_type=False, - strict_types=False): - if encoding is None: - encoding = 'utf_8' - else: - warnings.warn( - "encoding is deprecated, Use raw=False instead.", - PendingDeprecationWarning) - - if unicode_errors is None: - unicode_errors = 'strict' + def __init__( + self, + default=None, + use_single_float=False, + autoreset=True, + use_bin_type=True, + strict_types=False, + datetime=False, + unicode_errors=None, + ): self._strict_types = strict_types self._use_float = use_single_float self._autoreset = autoreset self._use_bin_type = use_bin_type - self._encoding = encoding - self._unicode_errors = unicode_errors self._buffer = StringIO() + if PY2 and datetime: + raise ValueError("datetime is not supported in Python 2") + self._datetime = bool(datetime) + self._unicode_errors = unicode_errors or "strict" if default is not None: if not callable(default): raise TypeError("default must be callable") self._default = default - def _pack(self, obj, nest_limit=DEFAULT_RECURSE_LIMIT, - check=isinstance, check_type_strict=_check_type_strict): + def _pack( + self, + obj, + nest_limit=DEFAULT_RECURSE_LIMIT, + check=isinstance, + check_type_strict=_check_type_strict, + ): default_used = False if self._strict_types: check = check_type_strict @@ -749,7 +848,7 @@ class Packer(object): list_types = (list, tuple) while True: if nest_limit < 0: - raise PackValueError("recursion limit exceeded") + raise ValueError("recursion limit exceeded") if obj is None: return self._buffer.write(b"\xc0") if check(obj, bool): @@ -761,76 +860,76 @@ class Packer(object): return self._buffer.write(struct.pack("B", obj)) if -0x20 <= obj < 0: return self._buffer.write(struct.pack("b", obj)) - if 0x80 <= obj <= 0xff: - return self._buffer.write(struct.pack("BB", 0xcc, obj)) + if 0x80 <= obj <= 0xFF: + return self._buffer.write(struct.pack("BB", 0xCC, obj)) if -0x80 <= obj < 0: - return self._buffer.write(struct.pack(">Bb", 0xd0, obj)) - if 0xff < obj <= 0xffff: - return self._buffer.write(struct.pack(">BH", 0xcd, obj)) + return self._buffer.write(struct.pack(">Bb", 0xD0, obj)) + if 0xFF < obj <= 0xFFFF: + return self._buffer.write(struct.pack(">BH", 0xCD, obj)) if -0x8000 <= obj < -0x80: - return self._buffer.write(struct.pack(">Bh", 0xd1, obj)) - if 0xffff < obj <= 0xffffffff: - return self._buffer.write(struct.pack(">BI", 0xce, obj)) + return self._buffer.write(struct.pack(">Bh", 0xD1, obj)) + if 0xFFFF < obj <= 0xFFFFFFFF: + return self._buffer.write(struct.pack(">BI", 0xCE, obj)) if -0x80000000 <= obj < -0x8000: - return self._buffer.write(struct.pack(">Bi", 0xd2, obj)) - if 0xffffffff < obj <= 0xffffffffffffffff: - return self._buffer.write(struct.pack(">BQ", 0xcf, obj)) + return self._buffer.write(struct.pack(">Bi", 0xD2, obj)) + if 0xFFFFFFFF < obj <= 0xFFFFFFFFFFFFFFFF: + return self._buffer.write(struct.pack(">BQ", 0xCF, obj)) if -0x8000000000000000 <= obj < -0x80000000: - return self._buffer.write(struct.pack(">Bq", 0xd3, obj)) + return self._buffer.write(struct.pack(">Bq", 0xD3, obj)) if not default_used and self._default is not None: obj = self._default(obj) default_used = True continue - raise PackOverflowError("Integer value out of range") + raise OverflowError("Integer value out of range") if check(obj, (bytes, bytearray)): n = len(obj) - if n >= 2**32: - raise PackValueError("%s is too large" % type(obj).__name__) + if n >= 2 ** 32: + raise ValueError("%s is too large" % type(obj).__name__) self._pack_bin_header(n) return self._buffer.write(obj) - if check(obj, Unicode): - if self._encoding is None: - raise TypeError( - "Can't encode unicode string: " - "no encoding is specified") - obj = obj.encode(self._encoding, self._unicode_errors) + if check(obj, unicode): + obj = obj.encode("utf-8", self._unicode_errors) n = len(obj) - if n >= 2**32: - raise PackValueError("String is too large") + if n >= 2 ** 32: + raise ValueError("String is too large") self._pack_raw_header(n) return self._buffer.write(obj) if check(obj, memoryview): n = len(obj) * obj.itemsize - if n >= 2**32: - raise PackValueError("Memoryview is too large") + if n >= 2 ** 32: + raise ValueError("Memoryview is too large") self._pack_bin_header(n) return self._buffer.write(obj) if check(obj, float): if self._use_float: - return self._buffer.write(struct.pack(">Bf", 0xca, obj)) - return self._buffer.write(struct.pack(">Bd", 0xcb, obj)) - if check(obj, ExtType): - code = obj.code - data = obj.data + return self._buffer.write(struct.pack(">Bf", 0xCA, obj)) + return self._buffer.write(struct.pack(">Bd", 0xCB, obj)) + if check(obj, (ExtType, Timestamp)): + if check(obj, Timestamp): + code = -1 + data = obj.to_bytes() + else: + code = obj.code + data = obj.data assert isinstance(code, int) assert isinstance(data, bytes) L = len(data) if L == 1: - self._buffer.write(b'\xd4') + self._buffer.write(b"\xd4") elif L == 2: - self._buffer.write(b'\xd5') + self._buffer.write(b"\xd5") elif L == 4: - self._buffer.write(b'\xd6') + self._buffer.write(b"\xd6") elif L == 8: - self._buffer.write(b'\xd7') + self._buffer.write(b"\xd7") elif L == 16: - self._buffer.write(b'\xd8') - elif L <= 0xff: - self._buffer.write(struct.pack(">BB", 0xc7, L)) - elif L <= 0xffff: - self._buffer.write(struct.pack(">BH", 0xc8, L)) + self._buffer.write(b"\xd8") + elif L <= 0xFF: + self._buffer.write(struct.pack(">BB", 0xC7, L)) + elif L <= 0xFFFF: + self._buffer.write(struct.pack(">BH", 0xC8, L)) else: - self._buffer.write(struct.pack(">BI", 0xc9, L)) + self._buffer.write(struct.pack(">BI", 0xC9, L)) self._buffer.write(struct.pack("b", code)) self._buffer.write(data) return @@ -841,13 +940,20 @@ class Packer(object): self._pack(obj[i], nest_limit - 1) return if check(obj, dict): - return self._pack_map_pairs(len(obj), dict_iteritems(obj), - nest_limit - 1) + return self._pack_map_pairs( + len(obj), dict_iteritems(obj), nest_limit - 1 + ) + + if self._datetime and check(obj, _DateTime) and obj.tzinfo is not None: + obj = Timestamp.from_datetime(obj) + default_used = 1 + continue + if not default_used and self._default is not None: obj = self._default(obj) default_used = 1 continue - raise TypeError("Cannot serialize %r" % (obj, )) + raise TypeError("Cannot serialize %r" % (obj,)) def pack(self, obj): try: @@ -855,43 +961,35 @@ class Packer(object): except: self._buffer = StringIO() # force reset raise - ret = self._buffer.getvalue() if self._autoreset: + ret = self._buffer.getvalue() self._buffer = StringIO() - elif USING_STRINGBUILDER: - self._buffer = StringIO(ret) - return ret + return ret def pack_map_pairs(self, pairs): self._pack_map_pairs(len(pairs), pairs) - ret = self._buffer.getvalue() if self._autoreset: + ret = self._buffer.getvalue() self._buffer = StringIO() - elif USING_STRINGBUILDER: - self._buffer = StringIO(ret) - return ret + return ret def pack_array_header(self, n): - if n >= 2**32: - raise PackValueError + if n >= 2 ** 32: + raise ValueError self._pack_array_header(n) - ret = self._buffer.getvalue() if self._autoreset: + ret = self._buffer.getvalue() self._buffer = StringIO() - elif USING_STRINGBUILDER: - self._buffer = StringIO(ret) - return ret + return ret def pack_map_header(self, n): - if n >= 2**32: - raise PackValueError + if n >= 2 ** 32: + raise ValueError self._pack_map_header(n) - ret = self._buffer.getvalue() if self._autoreset: + ret = self._buffer.getvalue() self._buffer = StringIO() - elif USING_STRINGBUILDER: - self._buffer = StringIO(ret) - return ret + return ret def pack_ext_type(self, typecode, data): if not isinstance(typecode, int): @@ -901,44 +999,44 @@ class Packer(object): if not isinstance(data, bytes): raise TypeError("data must have bytes type") L = len(data) - if L > 0xffffffff: - raise PackValueError("Too large data") + if L > 0xFFFFFFFF: + raise ValueError("Too large data") if L == 1: - self._buffer.write(b'\xd4') + self._buffer.write(b"\xd4") elif L == 2: - self._buffer.write(b'\xd5') + self._buffer.write(b"\xd5") elif L == 4: - self._buffer.write(b'\xd6') + self._buffer.write(b"\xd6") elif L == 8: - self._buffer.write(b'\xd7') + self._buffer.write(b"\xd7") elif L == 16: - self._buffer.write(b'\xd8') - elif L <= 0xff: - self._buffer.write(b'\xc7' + struct.pack('B', L)) - elif L <= 0xffff: - self._buffer.write(b'\xc8' + struct.pack('>H', L)) + self._buffer.write(b"\xd8") + elif L <= 0xFF: + self._buffer.write(b"\xc7" + struct.pack("B", L)) + elif L <= 0xFFFF: + self._buffer.write(b"\xc8" + struct.pack(">H", L)) else: - self._buffer.write(b'\xc9' + struct.pack('>I', L)) - self._buffer.write(struct.pack('B', typecode)) + self._buffer.write(b"\xc9" + struct.pack(">I", L)) + self._buffer.write(struct.pack("B", typecode)) self._buffer.write(data) def _pack_array_header(self, n): - if n <= 0x0f: - return self._buffer.write(struct.pack('B', 0x90 + n)) - if n <= 0xffff: - return self._buffer.write(struct.pack(">BH", 0xdc, n)) - if n <= 0xffffffff: - return self._buffer.write(struct.pack(">BI", 0xdd, n)) - raise PackValueError("Array is too large") + if n <= 0x0F: + return self._buffer.write(struct.pack("B", 0x90 + n)) + if n <= 0xFFFF: + return self._buffer.write(struct.pack(">BH", 0xDC, n)) + if n <= 0xFFFFFFFF: + return self._buffer.write(struct.pack(">BI", 0xDD, n)) + raise ValueError("Array is too large") def _pack_map_header(self, n): - if n <= 0x0f: - return self._buffer.write(struct.pack('B', 0x80 + n)) - if n <= 0xffff: - return self._buffer.write(struct.pack(">BH", 0xde, n)) - if n <= 0xffffffff: - return self._buffer.write(struct.pack(">BI", 0xdf, n)) - raise PackValueError("Dict is too large") + if n <= 0x0F: + return self._buffer.write(struct.pack("B", 0x80 + n)) + if n <= 0xFFFF: + return self._buffer.write(struct.pack(">BH", 0xDE, n)) + if n <= 0xFFFFFFFF: + return self._buffer.write(struct.pack(">BI", 0xDF, n)) + raise ValueError("Dict is too large") def _pack_map_pairs(self, n, pairs, nest_limit=DEFAULT_RECURSE_LIMIT): self._pack_map_header(n) @@ -947,31 +1045,43 @@ class Packer(object): self._pack(v, nest_limit - 1) def _pack_raw_header(self, n): - if n <= 0x1f: - self._buffer.write(struct.pack('B', 0xa0 + n)) - elif self._use_bin_type and n <= 0xff: - self._buffer.write(struct.pack('>BB', 0xd9, n)) - elif n <= 0xffff: - self._buffer.write(struct.pack(">BH", 0xda, n)) - elif n <= 0xffffffff: - self._buffer.write(struct.pack(">BI", 0xdb, n)) + if n <= 0x1F: + self._buffer.write(struct.pack("B", 0xA0 + n)) + elif self._use_bin_type and n <= 0xFF: + self._buffer.write(struct.pack(">BB", 0xD9, n)) + elif n <= 0xFFFF: + self._buffer.write(struct.pack(">BH", 0xDA, n)) + elif n <= 0xFFFFFFFF: + self._buffer.write(struct.pack(">BI", 0xDB, n)) else: - raise PackValueError('Raw is too large') + raise ValueError("Raw is too large") def _pack_bin_header(self, n): if not self._use_bin_type: return self._pack_raw_header(n) - elif n <= 0xff: - return self._buffer.write(struct.pack('>BB', 0xc4, n)) - elif n <= 0xffff: - return self._buffer.write(struct.pack(">BH", 0xc5, n)) - elif n <= 0xffffffff: - return self._buffer.write(struct.pack(">BI", 0xc6, n)) + elif n <= 0xFF: + return self._buffer.write(struct.pack(">BB", 0xC4, n)) + elif n <= 0xFFFF: + return self._buffer.write(struct.pack(">BH", 0xC5, n)) + elif n <= 0xFFFFFFFF: + return self._buffer.write(struct.pack(">BI", 0xC6, n)) else: - raise PackValueError('Bin is too large') + raise ValueError("Bin is too large") def bytes(self): + """Return internal buffer contents as bytes object""" return self._buffer.getvalue() def reset(self): + """Reset internal buffer. + + This method is useful only when autoreset=False. + """ self._buffer = StringIO() + + def getbuffer(self): + """Return view of internal buffer.""" + if USING_STRINGBUILDER or PY2: + return memoryview(self.bytes()) + else: + return self._buffer.getbuffer() diff --git a/pipenv/patched/notpip/_vendor/packaging/LICENSE.APACHE b/pipenv/patched/notpip/_vendor/packaging/LICENSE.APACHE index 4947287f..f433b1a5 100644 --- a/pipenv/patched/notpip/_vendor/packaging/LICENSE.APACHE +++ b/pipenv/patched/notpip/_vendor/packaging/LICENSE.APACHE @@ -174,4 +174,4 @@ incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. - END OF TERMS AND CONDITIONS \ No newline at end of file + END OF TERMS AND CONDITIONS diff --git a/pipenv/patched/notpip/_vendor/packaging/__about__.py b/pipenv/patched/notpip/_vendor/packaging/__about__.py index 7481c9e2..e70d692c 100644 --- a/pipenv/patched/notpip/_vendor/packaging/__about__.py +++ b/pipenv/patched/notpip/_vendor/packaging/__about__.py @@ -1,7 +1,6 @@ # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function __all__ = [ "__title__", @@ -18,10 +17,10 @@ __title__ = "packaging" __summary__ = "Core utilities for Python packages" __uri__ = "https://github.com/pypa/packaging" -__version__ = "19.0" +__version__ = "21.0" __author__ = "Donald Stufft and individual contributors" __email__ = "donald@stufft.io" -__license__ = "BSD or Apache License, Version 2.0" -__copyright__ = "Copyright 2014-2019 %s" % __author__ +__license__ = "BSD-2-Clause or Apache-2.0" +__copyright__ = "2014-2019 %s" % __author__ diff --git a/pipenv/patched/notpip/_vendor/packaging/__init__.py b/pipenv/patched/notpip/_vendor/packaging/__init__.py index a0cf67df..3c50c5dc 100644 --- a/pipenv/patched/notpip/_vendor/packaging/__init__.py +++ b/pipenv/patched/notpip/_vendor/packaging/__init__.py @@ -1,7 +1,6 @@ # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function from .__about__ import ( __author__, diff --git a/pipenv/patched/notpip/_vendor/packaging/_compat.py b/pipenv/patched/notpip/_vendor/packaging/_compat.py deleted file mode 100644 index 25da473c..00000000 --- a/pipenv/patched/notpip/_vendor/packaging/_compat.py +++ /dev/null @@ -1,31 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. -from __future__ import absolute_import, division, print_function - -import sys - - -PY2 = sys.version_info[0] == 2 -PY3 = sys.version_info[0] == 3 - -# flake8: noqa - -if PY3: - string_types = (str,) -else: - string_types = (basestring,) - - -def with_metaclass(meta, *bases): - """ - Create a base class with a metaclass. - """ - # This requires a bit of explanation: the basic idea is to make a dummy - # metaclass for one level of class instantiation that replaces itself with - # the actual metaclass. - class metaclass(meta): - def __new__(cls, name, this_bases, d): - return meta(name, bases, d) - - return type.__new__(metaclass, "temporary_class", (), {}) diff --git a/pipenv/patched/notpip/_vendor/packaging/_manylinux.py b/pipenv/patched/notpip/_vendor/packaging/_manylinux.py new file mode 100644 index 00000000..4c379aa6 --- /dev/null +++ b/pipenv/patched/notpip/_vendor/packaging/_manylinux.py @@ -0,0 +1,301 @@ +import collections +import functools +import os +import re +import struct +import sys +import warnings +from typing import IO, Dict, Iterator, NamedTuple, Optional, Tuple + + +# Python does not provide platform information at sufficient granularity to +# identify the architecture of the running executable in some cases, so we +# determine it dynamically by reading the information from the running +# process. This only applies on Linux, which uses the ELF format. +class _ELFFileHeader: + # https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header + class _InvalidELFFileHeader(ValueError): + """ + An invalid ELF file header was found. + """ + + ELF_MAGIC_NUMBER = 0x7F454C46 + ELFCLASS32 = 1 + ELFCLASS64 = 2 + ELFDATA2LSB = 1 + ELFDATA2MSB = 2 + EM_386 = 3 + EM_S390 = 22 + EM_ARM = 40 + EM_X86_64 = 62 + EF_ARM_ABIMASK = 0xFF000000 + EF_ARM_ABI_VER5 = 0x05000000 + EF_ARM_ABI_FLOAT_HARD = 0x00000400 + + def __init__(self, file: IO[bytes]) -> None: + def unpack(fmt: str) -> int: + try: + data = file.read(struct.calcsize(fmt)) + result: Tuple[int, ...] = struct.unpack(fmt, data) + except struct.error: + raise _ELFFileHeader._InvalidELFFileHeader() + return result[0] + + self.e_ident_magic = unpack(">I") + if self.e_ident_magic != self.ELF_MAGIC_NUMBER: + raise _ELFFileHeader._InvalidELFFileHeader() + self.e_ident_class = unpack("B") + if self.e_ident_class not in {self.ELFCLASS32, self.ELFCLASS64}: + raise _ELFFileHeader._InvalidELFFileHeader() + self.e_ident_data = unpack("B") + if self.e_ident_data not in {self.ELFDATA2LSB, self.ELFDATA2MSB}: + raise _ELFFileHeader._InvalidELFFileHeader() + self.e_ident_version = unpack("B") + self.e_ident_osabi = unpack("B") + self.e_ident_abiversion = unpack("B") + self.e_ident_pad = file.read(7) + format_h = "<H" if self.e_ident_data == self.ELFDATA2LSB else ">H" + format_i = "<I" if self.e_ident_data == self.ELFDATA2LSB else ">I" + format_q = "<Q" if self.e_ident_data == self.ELFDATA2LSB else ">Q" + format_p = format_i if self.e_ident_class == self.ELFCLASS32 else format_q + self.e_type = unpack(format_h) + self.e_machine = unpack(format_h) + self.e_version = unpack(format_i) + self.e_entry = unpack(format_p) + self.e_phoff = unpack(format_p) + self.e_shoff = unpack(format_p) + self.e_flags = unpack(format_i) + self.e_ehsize = unpack(format_h) + self.e_phentsize = unpack(format_h) + self.e_phnum = unpack(format_h) + self.e_shentsize = unpack(format_h) + self.e_shnum = unpack(format_h) + self.e_shstrndx = unpack(format_h) + + +def _get_elf_header() -> Optional[_ELFFileHeader]: + try: + with open(sys.executable, "rb") as f: + elf_header = _ELFFileHeader(f) + except (OSError, TypeError, _ELFFileHeader._InvalidELFFileHeader): + return None + return elf_header + + +def _is_linux_armhf() -> bool: + # hard-float ABI can be detected from the ELF header of the running + # process + # https://static.docs.arm.com/ihi0044/g/aaelf32.pdf + elf_header = _get_elf_header() + if elf_header is None: + return False + result = elf_header.e_ident_class == elf_header.ELFCLASS32 + result &= elf_header.e_ident_data == elf_header.ELFDATA2LSB + result &= elf_header.e_machine == elf_header.EM_ARM + result &= ( + elf_header.e_flags & elf_header.EF_ARM_ABIMASK + ) == elf_header.EF_ARM_ABI_VER5 + result &= ( + elf_header.e_flags & elf_header.EF_ARM_ABI_FLOAT_HARD + ) == elf_header.EF_ARM_ABI_FLOAT_HARD + return result + + +def _is_linux_i686() -> bool: + elf_header = _get_elf_header() + if elf_header is None: + return False + result = elf_header.e_ident_class == elf_header.ELFCLASS32 + result &= elf_header.e_ident_data == elf_header.ELFDATA2LSB + result &= elf_header.e_machine == elf_header.EM_386 + return result + + +def _have_compatible_abi(arch: str) -> bool: + if arch == "armv7l": + return _is_linux_armhf() + if arch == "i686": + return _is_linux_i686() + return arch in {"x86_64", "aarch64", "ppc64", "ppc64le", "s390x"} + + +# If glibc ever changes its major version, we need to know what the last +# minor version was, so we can build the complete list of all versions. +# For now, guess what the highest minor version might be, assume it will +# be 50 for testing. Once this actually happens, update the dictionary +# with the actual value. +_LAST_GLIBC_MINOR: Dict[int, int] = collections.defaultdict(lambda: 50) + + +class _GLibCVersion(NamedTuple): + major: int + minor: int + + +def _glibc_version_string_confstr() -> Optional[str]: + """ + Primary implementation of glibc_version_string using os.confstr. + """ + # os.confstr is quite a bit faster than ctypes.DLL. It's also less likely + # to be broken or missing. This strategy is used in the standard library + # platform module. + # https://github.com/python/cpython/blob/fcf1d003bf4f0100c/Lib/platform.py#L175-L183 + try: + # os.confstr("CS_GNU_LIBC_VERSION") returns a string like "glibc 2.17". + version_string = os.confstr("CS_GNU_LIBC_VERSION") + assert version_string is not None + _, version = version_string.split() + except (AssertionError, AttributeError, OSError, ValueError): + # os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)... + return None + return version + + +def _glibc_version_string_ctypes() -> Optional[str]: + """ + Fallback implementation of glibc_version_string using ctypes. + """ + try: + import ctypes + except ImportError: + return None + + # ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen + # manpage says, "If filename is NULL, then the returned handle is for the + # main program". This way we can let the linker do the work to figure out + # which libc our process is actually using. + # + # We must also handle the special case where the executable is not a + # dynamically linked executable. This can occur when using musl libc, + # for example. In this situation, dlopen() will error, leading to an + # OSError. Interestingly, at least in the case of musl, there is no + # errno set on the OSError. The single string argument used to construct + # OSError comes from libc itself and is therefore not portable to + # hard code here. In any case, failure to call dlopen() means we + # can proceed, so we bail on our attempt. + try: + process_namespace = ctypes.CDLL(None) + except OSError: + return None + + try: + gnu_get_libc_version = process_namespace.gnu_get_libc_version + except AttributeError: + # Symbol doesn't exist -> therefore, we are not linked to + # glibc. + return None + + # Call gnu_get_libc_version, which returns a string like "2.5" + gnu_get_libc_version.restype = ctypes.c_char_p + version_str: str = gnu_get_libc_version() + # py2 / py3 compatibility: + if not isinstance(version_str, str): + version_str = version_str.decode("ascii") + + return version_str + + +def _glibc_version_string() -> Optional[str]: + """Returns glibc version string, or None if not using glibc.""" + return _glibc_version_string_confstr() or _glibc_version_string_ctypes() + + +def _parse_glibc_version(version_str: str) -> Tuple[int, int]: + """Parse glibc version. + + We use a regexp instead of str.split because we want to discard any + random junk that might come after the minor version -- this might happen + in patched/forked versions of glibc (e.g. Linaro's version of glibc + uses version strings like "2.20-2014.11"). See gh-3588. + """ + m = re.match(r"(?P<major>[0-9]+)\.(?P<minor>[0-9]+)", version_str) + if not m: + warnings.warn( + "Expected glibc version with 2 components major.minor," + " got: %s" % version_str, + RuntimeWarning, + ) + return -1, -1 + return int(m.group("major")), int(m.group("minor")) + + +@functools.lru_cache() +def _get_glibc_version() -> Tuple[int, int]: + version_str = _glibc_version_string() + if version_str is None: + return (-1, -1) + return _parse_glibc_version(version_str) + + +# From PEP 513, PEP 600 +def _is_compatible(name: str, arch: str, version: _GLibCVersion) -> bool: + sys_glibc = _get_glibc_version() + if sys_glibc < version: + return False + # Check for presence of _manylinux module. + try: + import _manylinux # noqa + except ImportError: + return True + if hasattr(_manylinux, "manylinux_compatible"): + result = _manylinux.manylinux_compatible(version[0], version[1], arch) + if result is not None: + return bool(result) + return True + if version == _GLibCVersion(2, 5): + if hasattr(_manylinux, "manylinux1_compatible"): + return bool(_manylinux.manylinux1_compatible) + if version == _GLibCVersion(2, 12): + if hasattr(_manylinux, "manylinux2010_compatible"): + return bool(_manylinux.manylinux2010_compatible) + if version == _GLibCVersion(2, 17): + if hasattr(_manylinux, "manylinux2014_compatible"): + return bool(_manylinux.manylinux2014_compatible) + return True + + +_LEGACY_MANYLINUX_MAP = { + # CentOS 7 w/ glibc 2.17 (PEP 599) + (2, 17): "manylinux2014", + # CentOS 6 w/ glibc 2.12 (PEP 571) + (2, 12): "manylinux2010", + # CentOS 5 w/ glibc 2.5 (PEP 513) + (2, 5): "manylinux1", +} + + +def platform_tags(linux: str, arch: str) -> Iterator[str]: + if not _have_compatible_abi(arch): + return + # Oldest glibc to be supported regardless of architecture is (2, 17). + too_old_glibc2 = _GLibCVersion(2, 16) + if arch in {"x86_64", "i686"}: + # On x86/i686 also oldest glibc to be supported is (2, 5). + too_old_glibc2 = _GLibCVersion(2, 4) + current_glibc = _GLibCVersion(*_get_glibc_version()) + glibc_max_list = [current_glibc] + # We can assume compatibility across glibc major versions. + # https://sourceware.org/bugzilla/show_bug.cgi?id=24636 + # + # Build a list of maximum glibc versions so that we can + # output the canonical list of all glibc from current_glibc + # down to too_old_glibc2, including all intermediary versions. + for glibc_major in range(current_glibc.major - 1, 1, -1): + glibc_minor = _LAST_GLIBC_MINOR[glibc_major] + glibc_max_list.append(_GLibCVersion(glibc_major, glibc_minor)) + for glibc_max in glibc_max_list: + if glibc_max.major == too_old_glibc2.major: + min_minor = too_old_glibc2.minor + else: + # For other glibc major versions oldest supported is (x, 0). + min_minor = -1 + for glibc_minor in range(glibc_max.minor, min_minor, -1): + glibc_version = _GLibCVersion(glibc_max.major, glibc_minor) + tag = "manylinux_{}_{}".format(*glibc_version) + if _is_compatible(tag, arch, glibc_version): + yield linux.replace("linux", tag) + # Handle the legacy manylinux1, manylinux2010, manylinux2014 tags. + if glibc_version in _LEGACY_MANYLINUX_MAP: + legacy_tag = _LEGACY_MANYLINUX_MAP[glibc_version] + if _is_compatible(legacy_tag, arch, glibc_version): + yield linux.replace("linux", legacy_tag) diff --git a/pipenv/patched/notpip/_vendor/packaging/_musllinux.py b/pipenv/patched/notpip/_vendor/packaging/_musllinux.py new file mode 100644 index 00000000..85450faf --- /dev/null +++ b/pipenv/patched/notpip/_vendor/packaging/_musllinux.py @@ -0,0 +1,136 @@ +"""PEP 656 support. + +This module implements logic to detect if the currently running Python is +linked against musl, and what musl version is used. +""" + +import contextlib +import functools +import operator +import os +import re +import struct +import subprocess +import sys +from typing import IO, Iterator, NamedTuple, Optional, Tuple + + +def _read_unpacked(f: IO[bytes], fmt: str) -> Tuple[int, ...]: + return struct.unpack(fmt, f.read(struct.calcsize(fmt))) + + +def _parse_ld_musl_from_elf(f: IO[bytes]) -> Optional[str]: + """Detect musl libc location by parsing the Python executable. + + Based on: https://gist.github.com/lyssdod/f51579ae8d93c8657a5564aefc2ffbca + ELF header: https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html + """ + f.seek(0) + try: + ident = _read_unpacked(f, "16B") + except struct.error: + return None + if ident[:4] != tuple(b"\x7fELF"): # Invalid magic, not ELF. + return None + f.seek(struct.calcsize("HHI"), 1) # Skip file type, machine, and version. + + try: + # e_fmt: Format for program header. + # p_fmt: Format for section header. + # p_idx: Indexes to find p_type, p_offset, and p_filesz. + e_fmt, p_fmt, p_idx = { + 1: ("IIIIHHH", "IIIIIIII", (0, 1, 4)), # 32-bit. + 2: ("QQQIHHH", "IIQQQQQQ", (0, 2, 5)), # 64-bit. + }[ident[4]] + except KeyError: + return None + else: + p_get = operator.itemgetter(*p_idx) + + # Find the interpreter section and return its content. + try: + _, e_phoff, _, _, _, e_phentsize, e_phnum = _read_unpacked(f, e_fmt) + except struct.error: + return None + for i in range(e_phnum + 1): + f.seek(e_phoff + e_phentsize * i) + try: + p_type, p_offset, p_filesz = p_get(_read_unpacked(f, p_fmt)) + except struct.error: + return None + if p_type != 3: # Not PT_INTERP. + continue + f.seek(p_offset) + interpreter = os.fsdecode(f.read(p_filesz)).strip("\0") + if "musl" not in interpreter: + return None + return interpreter + return None + + +class _MuslVersion(NamedTuple): + major: int + minor: int + + +def _parse_musl_version(output: str) -> Optional[_MuslVersion]: + lines = [n for n in (n.strip() for n in output.splitlines()) if n] + if len(lines) < 2 or lines[0][:4] != "musl": + return None + m = re.match(r"Version (\d+)\.(\d+)", lines[1]) + if not m: + return None + return _MuslVersion(major=int(m.group(1)), minor=int(m.group(2))) + + +@functools.lru_cache() +def _get_musl_version(executable: str) -> Optional[_MuslVersion]: + """Detect currently-running musl runtime version. + + This is done by checking the specified executable's dynamic linking + information, and invoking the loader to parse its output for a version + string. If the loader is musl, the output would be something like:: + + musl libc (x86_64) + Version 1.2.2 + Dynamic Program Loader + """ + with contextlib.ExitStack() as stack: + try: + f = stack.enter_context(open(executable, "rb")) + except IOError: + return None + ld = _parse_ld_musl_from_elf(f) + if not ld: + return None + proc = subprocess.run([ld], stderr=subprocess.PIPE, universal_newlines=True) + return _parse_musl_version(proc.stderr) + + +def platform_tags(arch: str) -> Iterator[str]: + """Generate musllinux tags compatible to the current platform. + + :param arch: Should be the part of platform tag after the ``linux_`` + prefix, e.g. ``x86_64``. The ``linux_`` prefix is assumed as a + prerequisite for the current platform to be musllinux-compatible. + + :returns: An iterator of compatible musllinux tags. + """ + sys_musl = _get_musl_version(sys.executable) + if sys_musl is None: # Python not dynamically linked against musl. + return + for minor in range(sys_musl.minor, -1, -1): + yield f"musllinux_{sys_musl.major}_{minor}_{arch}" + + +if __name__ == "__main__": # pragma: no cover + import sysconfig + + plat = sysconfig.get_platform() + assert plat.startswith("linux-"), "not linux" + + print("plat:", plat) + print("musl:", _get_musl_version(sys.executable)) + print("tags:", end=" ") + for t in platform_tags(re.sub(r"[.-]", "_", plat.split("-", 1)[-1])): + print(t, end="\n ") diff --git a/pipenv/patched/notpip/_vendor/packaging/_structures.py b/pipenv/patched/notpip/_vendor/packaging/_structures.py index 68dcca63..95154975 100644 --- a/pipenv/patched/notpip/_vendor/packaging/_structures.py +++ b/pipenv/patched/notpip/_vendor/packaging/_structures.py @@ -1,68 +1,67 @@ # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function -class Infinity(object): - def __repr__(self): +class InfinityType: + def __repr__(self) -> str: return "Infinity" - def __hash__(self): + def __hash__(self) -> int: return hash(repr(self)) - def __lt__(self, other): + def __lt__(self, other: object) -> bool: return False - def __le__(self, other): + def __le__(self, other: object) -> bool: return False - def __eq__(self, other): + def __eq__(self, other: object) -> bool: return isinstance(other, self.__class__) - def __ne__(self, other): + def __ne__(self, other: object) -> bool: return not isinstance(other, self.__class__) - def __gt__(self, other): + def __gt__(self, other: object) -> bool: return True - def __ge__(self, other): + def __ge__(self, other: object) -> bool: return True - def __neg__(self): + def __neg__(self: object) -> "NegativeInfinityType": return NegativeInfinity -Infinity = Infinity() +Infinity = InfinityType() -class NegativeInfinity(object): - def __repr__(self): +class NegativeInfinityType: + def __repr__(self) -> str: return "-Infinity" - def __hash__(self): + def __hash__(self) -> int: return hash(repr(self)) - def __lt__(self, other): + def __lt__(self, other: object) -> bool: return True - def __le__(self, other): + def __le__(self, other: object) -> bool: return True - def __eq__(self, other): + def __eq__(self, other: object) -> bool: return isinstance(other, self.__class__) - def __ne__(self, other): + def __ne__(self, other: object) -> bool: return not isinstance(other, self.__class__) - def __gt__(self, other): + def __gt__(self, other: object) -> bool: return False - def __ge__(self, other): + def __ge__(self, other: object) -> bool: return False - def __neg__(self): + def __neg__(self: object) -> InfinityType: return Infinity -NegativeInfinity = NegativeInfinity() +NegativeInfinity = NegativeInfinityType() diff --git a/pipenv/patched/notpip/_vendor/packaging/markers.py b/pipenv/patched/notpip/_vendor/packaging/markers.py index 50a08091..47951aca 100644 --- a/pipenv/patched/notpip/_vendor/packaging/markers.py +++ b/pipenv/patched/notpip/_vendor/packaging/markers.py @@ -1,20 +1,26 @@ # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import operator import os import platform import sys +from typing import Any, Callable, Dict, List, Optional, Tuple, Union -from pipenv.patched.notpip._vendor.pyparsing import ParseException, ParseResults, stringStart, stringEnd -from pipenv.patched.notpip._vendor.pyparsing import ZeroOrMore, Group, Forward, QuotedString -from pipenv.patched.notpip._vendor.pyparsing import Literal as L # noqa - -from ._compat import string_types -from .specifiers import Specifier, InvalidSpecifier +from pipenv.patched.notpip._vendor.pyparsing import ( # noqa: N817 + Forward, + Group, + Literal as L, + ParseException, + ParseResults, + QuotedString, + ZeroOrMore, + stringEnd, + stringStart, +) +from .specifiers import InvalidSpecifier, Specifier __all__ = [ "InvalidMarker", @@ -24,6 +30,8 @@ __all__ = [ "default_environment", ] +Operator = Callable[[str, str], bool] + class InvalidMarker(ValueError): """ @@ -44,32 +52,32 @@ class UndefinedEnvironmentName(ValueError): """ -class Node(object): - def __init__(self, value): +class Node: + def __init__(self, value: Any) -> None: self.value = value - def __str__(self): + def __str__(self) -> str: return str(self.value) - def __repr__(self): - return "<{0}({1!r})>".format(self.__class__.__name__, str(self)) + def __repr__(self) -> str: + return f"<{self.__class__.__name__}('{self}')>" - def serialize(self): + def serialize(self) -> str: raise NotImplementedError class Variable(Node): - def serialize(self): + def serialize(self) -> str: return str(self) class Value(Node): - def serialize(self): - return '"{0}"'.format(self) + def serialize(self) -> str: + return f'"{self}"' class Op(Node): - def serialize(self): + def serialize(self) -> str: return str(self) @@ -85,13 +93,13 @@ VARIABLE = ( | L("python_version") | L("sys_platform") | L("os_name") - | L("os.name") + | L("os.name") # PEP-345 | L("sys.platform") # PEP-345 | L("platform.version") # PEP-345 | L("platform.machine") # PEP-345 | L("platform.python_implementation") # PEP-345 - | L("python_implementation") # PEP-345 - | L("extra") # undocumented setuptools legacy + | L("python_implementation") # undocumented setuptools legacy + | L("extra") # PEP-508 ) ALIASES = { "os.name": "os_name", @@ -130,15 +138,18 @@ MARKER_EXPR << MARKER_ATOM + ZeroOrMore(BOOLOP + MARKER_EXPR) MARKER = stringStart + MARKER_EXPR + stringEnd -def _coerce_parse_result(results): +def _coerce_parse_result(results: Union[ParseResults, List[Any]]) -> List[Any]: if isinstance(results, ParseResults): return [_coerce_parse_result(i) for i in results] else: return results -def _format_marker(marker, first=True): - assert isinstance(marker, (list, tuple, string_types)) +def _format_marker( + marker: Union[List[str], Tuple[Node, ...], str], first: Optional[bool] = True +) -> str: + + assert isinstance(marker, (list, tuple, str)) # Sometimes we have a structure like [[...]] which is a single item list # where the single item is itself it's own list. In that case we want skip @@ -163,7 +174,7 @@ def _format_marker(marker, first=True): return marker -_operators = { +_operators: Dict[str, Operator] = { "in": lambda lhs, rhs: lhs in rhs, "not in": lambda lhs, rhs: lhs not in rhs, "<": operator.lt, @@ -175,7 +186,7 @@ _operators = { } -def _eval_op(lhs, op, rhs): +def _eval_op(lhs: str, op: Op, rhs: str) -> bool: try: spec = Specifier("".join([op.serialize(), rhs])) except InvalidSpecifier: @@ -183,34 +194,36 @@ def _eval_op(lhs, op, rhs): else: return spec.contains(lhs) - oper = _operators.get(op.serialize()) + oper: Optional[Operator] = _operators.get(op.serialize()) if oper is None: - raise UndefinedComparison( - "Undefined {0!r} on {1!r} and {2!r}.".format(op, lhs, rhs) - ) + raise UndefinedComparison(f"Undefined {op!r} on {lhs!r} and {rhs!r}.") return oper(lhs, rhs) -_undefined = object() +class Undefined: + pass -def _get_env(environment, name): - value = environment.get(name, _undefined) +_undefined = Undefined() - if value is _undefined: + +def _get_env(environment: Dict[str, str], name: str) -> str: + value: Union[str, Undefined] = environment.get(name, _undefined) + + if isinstance(value, Undefined): raise UndefinedEnvironmentName( - "{0!r} does not exist in evaluation environment.".format(name) + f"{name!r} does not exist in evaluation environment." ) return value -def _evaluate_markers(markers, environment): - groups = [[]] +def _evaluate_markers(markers: List[Any], environment: Dict[str, str]) -> bool: + groups: List[List[bool]] = [[]] for marker in markers: - assert isinstance(marker, (list, tuple, string_types)) + assert isinstance(marker, (list, tuple, str)) if isinstance(marker, list): groups[-1].append(_evaluate_markers(marker, environment)) @@ -233,7 +246,7 @@ def _evaluate_markers(markers, environment): return any(all(item) for item in groups) -def format_full_version(info): +def format_full_version(info: "sys._version_info") -> str: version = "{0.major}.{0.minor}.{0.micro}".format(info) kind = info.releaselevel if kind != "final": @@ -241,14 +254,9 @@ def format_full_version(info): return version -def default_environment(): - if hasattr(sys, "implementation"): - iver = format_full_version(sys.implementation.version) - implementation_name = sys.implementation.name - else: - iver = "0" - implementation_name = "" - +def default_environment() -> Dict[str, str]: + iver = format_full_version(sys.implementation.version) + implementation_name = sys.implementation.name return { "implementation_name": implementation_name, "implementation_version": iver, @@ -259,28 +267,28 @@ def default_environment(): "platform_version": platform.version(), "python_full_version": platform.python_version(), "platform_python_implementation": platform.python_implementation(), - "python_version": platform.python_version()[:3], + "python_version": ".".join(platform.python_version_tuple()[:2]), "sys_platform": sys.platform, } -class Marker(object): - def __init__(self, marker): +class Marker: + def __init__(self, marker: str) -> None: try: self._markers = _coerce_parse_result(MARKER.parseString(marker)) except ParseException as e: - err_str = "Invalid marker: {0!r}, parse error at {1!r}".format( - marker, marker[e.loc : e.loc + 8] + raise InvalidMarker( + f"Invalid marker: {marker!r}, parse error at " + f"{marker[e.loc : e.loc + 8]!r}" ) - raise InvalidMarker(err_str) - def __str__(self): + def __str__(self) -> str: return _format_marker(self._markers) - def __repr__(self): - return "<Marker({0!r})>".format(str(self)) + def __repr__(self) -> str: + return f"<Marker('{self}')>" - def evaluate(self, environment=None): + def evaluate(self, environment: Optional[Dict[str, str]] = None) -> bool: """Evaluate a marker. Return the boolean from evaluating the given marker against the diff --git a/pipenv/patched/notpip/_vendor/packaging/requirements.py b/pipenv/patched/notpip/_vendor/packaging/requirements.py index a3de7673..4bc5f947 100644 --- a/pipenv/patched/notpip/_vendor/packaging/requirements.py +++ b/pipenv/patched/notpip/_vendor/packaging/requirements.py @@ -1,15 +1,24 @@ # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function -import string import re +import string +import urllib.parse +from typing import List, Optional as TOptional, Set -from pipenv.patched.notpip._vendor.pyparsing import stringStart, stringEnd, originalTextFor, ParseException -from pipenv.patched.notpip._vendor.pyparsing import ZeroOrMore, Word, Optional, Regex, Combine -from pipenv.patched.notpip._vendor.pyparsing import Literal as L # noqa -from pipenv.patched.notpip._vendor.six.moves.urllib import parse as urlparse +from pipenv.patched.notpip._vendor.pyparsing import ( # noqa + Combine, + Literal as L, + Optional, + ParseException, + Regex, + Word, + ZeroOrMore, + originalTextFor, + stringEnd, + stringStart, +) from .markers import MARKER_EXPR, Marker from .specifiers import LegacySpecifier, Specifier, SpecifierSet @@ -51,7 +60,7 @@ VERSION_ONE = VERSION_PEP440 ^ VERSION_LEGACY VERSION_MANY = Combine( VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE), joinString=",", adjacent=False )("_raw_spec") -_VERSION_SPEC = Optional(((LPAREN + VERSION_MANY + RPAREN) | VERSION_MANY)) +_VERSION_SPEC = Optional((LPAREN + VERSION_MANY + RPAREN) | VERSION_MANY) _VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or "") VERSION_SPEC = originalTextFor(_VERSION_SPEC)("specifier") @@ -75,7 +84,7 @@ REQUIREMENT = stringStart + NAMED_REQUIREMENT + stringEnd REQUIREMENT.parseString("x[]") -class Requirement(object): +class Requirement: """Parse a requirement. Parse a given requirement string into its parts, such as name, specifier, @@ -88,51 +97,50 @@ class Requirement(object): # the thing as well as the version? What about the markers? # TODO: Can we normalize the name and extra name? - def __init__(self, requirement_string): + def __init__(self, requirement_string: str) -> None: try: req = REQUIREMENT.parseString(requirement_string) except ParseException as e: raise InvalidRequirement( - 'Parse error at "{0!r}": {1}'.format( - requirement_string[e.loc : e.loc + 8], e.msg - ) + f'Parse error at "{ requirement_string[e.loc : e.loc + 8]!r}": {e.msg}' ) - self.name = req.name + self.name: str = req.name if req.url: - parsed_url = urlparse.urlparse(req.url) + parsed_url = urllib.parse.urlparse(req.url) if parsed_url.scheme == "file": - if urlparse.urlunparse(parsed_url) != req.url: + if urllib.parse.urlunparse(parsed_url) != req.url: raise InvalidRequirement("Invalid URL given") elif not (parsed_url.scheme and parsed_url.netloc) or ( not parsed_url.scheme and not parsed_url.netloc ): - raise InvalidRequirement("Invalid URL: {0}".format(req.url)) - self.url = req.url + raise InvalidRequirement(f"Invalid URL: {req.url}") + self.url: TOptional[str] = req.url else: self.url = None - self.extras = set(req.extras.asList() if req.extras else []) - self.specifier = SpecifierSet(req.specifier) - self.marker = req.marker if req.marker else None + self.extras: Set[str] = set(req.extras.asList() if req.extras else []) + self.specifier: SpecifierSet = SpecifierSet(req.specifier) + self.marker: TOptional[Marker] = req.marker if req.marker else None - def __str__(self): - parts = [self.name] + def __str__(self) -> str: + parts: List[str] = [self.name] if self.extras: - parts.append("[{0}]".format(",".join(sorted(self.extras)))) + formatted_extras = ",".join(sorted(self.extras)) + parts.append(f"[{formatted_extras}]") if self.specifier: parts.append(str(self.specifier)) if self.url: - parts.append("@ {0}".format(self.url)) + parts.append(f"@ {self.url}") if self.marker: parts.append(" ") if self.marker: - parts.append("; {0}".format(self.marker)) + parts.append(f"; {self.marker}") return "".join(parts) - def __repr__(self): - return "<Requirement({0!r})>".format(str(self)) + def __repr__(self) -> str: + return f"<Requirement('{self}')>" diff --git a/pipenv/patched/notpip/_vendor/packaging/specifiers.py b/pipenv/patched/notpip/_vendor/packaging/specifiers.py index 743576a0..ce66bd4a 100644 --- a/pipenv/patched/notpip/_vendor/packaging/specifiers.py +++ b/pipenv/patched/notpip/_vendor/packaging/specifiers.py @@ -1,15 +1,33 @@ # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import abc import functools import itertools import re +import warnings +from typing import ( + Callable, + Dict, + Iterable, + Iterator, + List, + Optional, + Pattern, + Set, + Tuple, + TypeVar, + Union, +) -from ._compat import string_types, with_metaclass -from .version import Version, LegacyVersion, parse +from .utils import canonicalize_version +from .version import LegacyVersion, Version, parse + +ParsedVersion = Union[Version, LegacyVersion] +UnparsedVersion = Union[Version, LegacyVersion, str] +VersionTypeVar = TypeVar("VersionTypeVar", bound=UnparsedVersion) +CallableOperator = Callable[[ParsedVersion, str], bool] class InvalidSpecifier(ValueError): @@ -18,56 +36,58 @@ class InvalidSpecifier(ValueError): """ -class BaseSpecifier(with_metaclass(abc.ABCMeta, object)): +class BaseSpecifier(metaclass=abc.ABCMeta): @abc.abstractmethod - def __str__(self): + def __str__(self) -> str: """ Returns the str representation of this Specifier like object. This should be representative of the Specifier itself. """ @abc.abstractmethod - def __hash__(self): + def __hash__(self) -> int: """ Returns a hash value for this Specifier like object. """ @abc.abstractmethod - def __eq__(self, other): + def __eq__(self, other: object) -> bool: """ Returns a boolean representing whether or not the two Specifier like objects are equal. """ @abc.abstractmethod - def __ne__(self, other): + def __ne__(self, other: object) -> bool: """ Returns a boolean representing whether or not the two Specifier like objects are not equal. """ @abc.abstractproperty - def prereleases(self): + def prereleases(self) -> Optional[bool]: """ Returns whether or not pre-releases as a whole are allowed by this specifier. """ @prereleases.setter - def prereleases(self, value): + def prereleases(self, value: bool) -> None: """ Sets whether or not pre-releases as a whole are allowed by this specifier. """ @abc.abstractmethod - def contains(self, item, prereleases=None): + def contains(self, item: str, prereleases: Optional[bool] = None) -> bool: """ Determines if the given item is contained within this specifier. """ @abc.abstractmethod - def filter(self, iterable, prereleases=None): + def filter( + self, iterable: Iterable[VersionTypeVar], prereleases: Optional[bool] = None + ) -> Iterable[VersionTypeVar]: """ Takes an iterable of items and filters them so that only items which are contained within this specifier are allowed in it. @@ -76,48 +96,56 @@ class BaseSpecifier(with_metaclass(abc.ABCMeta, object)): class _IndividualSpecifier(BaseSpecifier): - _operators = {} + _operators: Dict[str, str] = {} + _regex: Pattern[str] - def __init__(self, spec="", prereleases=None): + def __init__(self, spec: str = "", prereleases: Optional[bool] = None) -> None: match = self._regex.search(spec) if not match: - raise InvalidSpecifier("Invalid specifier: '{0}'".format(spec)) + raise InvalidSpecifier(f"Invalid specifier: '{spec}'") - self._spec = (match.group("operator").strip(), match.group("version").strip()) + self._spec: Tuple[str, str] = ( + match.group("operator").strip(), + match.group("version").strip(), + ) # Store whether or not this Specifier should accept prereleases self._prereleases = prereleases - def __repr__(self): + def __repr__(self) -> str: pre = ( - ", prereleases={0!r}".format(self.prereleases) + f", prereleases={self.prereleases!r}" if self._prereleases is not None else "" ) - return "<{0}({1!r}{2})>".format(self.__class__.__name__, str(self), pre) + return "<{}({!r}{})>".format(self.__class__.__name__, str(self), pre) - def __str__(self): - return "{0}{1}".format(*self._spec) + def __str__(self) -> str: + return "{}{}".format(*self._spec) - def __hash__(self): - return hash(self._spec) + @property + def _canonical_spec(self) -> Tuple[str, str]: + return self._spec[0], canonicalize_version(self._spec[1]) - def __eq__(self, other): - if isinstance(other, string_types): + def __hash__(self) -> int: + return hash(self._canonical_spec) + + def __eq__(self, other: object) -> bool: + if isinstance(other, str): try: - other = self.__class__(other) + other = self.__class__(str(other)) except InvalidSpecifier: return NotImplemented elif not isinstance(other, self.__class__): return NotImplemented - return self._spec == other._spec + return self._canonical_spec == other._canonical_spec - def __ne__(self, other): - if isinstance(other, string_types): + def __ne__(self, other: object) -> bool: + if isinstance(other, str): try: - other = self.__class__(other) + other = self.__class__(str(other)) except InvalidSpecifier: return NotImplemented elif not isinstance(other, self.__class__): @@ -125,53 +153,63 @@ class _IndividualSpecifier(BaseSpecifier): return self._spec != other._spec - def _get_operator(self, op): - return getattr(self, "_compare_{0}".format(self._operators[op])) + def _get_operator(self, op: str) -> CallableOperator: + operator_callable: CallableOperator = getattr( + self, f"_compare_{self._operators[op]}" + ) + return operator_callable - def _coerce_version(self, version): + def _coerce_version(self, version: UnparsedVersion) -> ParsedVersion: if not isinstance(version, (LegacyVersion, Version)): version = parse(version) return version @property - def operator(self): + def operator(self) -> str: return self._spec[0] @property - def version(self): + def version(self) -> str: return self._spec[1] @property - def prereleases(self): + def prereleases(self) -> Optional[bool]: return self._prereleases @prereleases.setter - def prereleases(self, value): + def prereleases(self, value: bool) -> None: self._prereleases = value - def __contains__(self, item): + def __contains__(self, item: str) -> bool: return self.contains(item) - def contains(self, item, prereleases=None): + def contains( + self, item: UnparsedVersion, prereleases: Optional[bool] = None + ) -> bool: + # Determine if prereleases are to be allowed or not. if prereleases is None: prereleases = self.prereleases # Normalize item to a Version or LegacyVersion, this allows us to have # a shortcut for ``"2.0" in Specifier(">=2") - item = self._coerce_version(item) + normalized_item = self._coerce_version(item) # Determine if we should be supporting prereleases in this specifier # or not, if we do not support prereleases than we can short circuit # logic if this version is a prereleases. - if item.is_prerelease and not prereleases: + if normalized_item.is_prerelease and not prereleases: return False # Actually do the comparison to determine if this item is contained # within this Specifier or not. - return self._get_operator(self.operator)(item, self.version) + operator_callable: CallableOperator = self._get_operator(self.operator) + return operator_callable(normalized_item, self.version) + + def filter( + self, iterable: Iterable[VersionTypeVar], prereleases: Optional[bool] = None + ) -> Iterable[VersionTypeVar]: - def filter(self, iterable, prereleases=None): yielded = False found_prereleases = [] @@ -184,7 +222,7 @@ class _IndividualSpecifier(BaseSpecifier): if self.contains(parsed_version, **kw): # If our version is a prerelease, and we were not set to allow - # prereleases, then we'll store it for later incase nothing + # prereleases, then we'll store it for later in case nothing # else matches this specifier. if parsed_version.is_prerelease and not ( prereleases or self.prereleases @@ -229,33 +267,46 @@ class LegacySpecifier(_IndividualSpecifier): ">": "greater_than", } - def _coerce_version(self, version): + def __init__(self, spec: str = "", prereleases: Optional[bool] = None) -> None: + super().__init__(spec, prereleases) + + warnings.warn( + "Creating a LegacyVersion has been deprecated and will be " + "removed in the next major release", + DeprecationWarning, + ) + + def _coerce_version(self, version: UnparsedVersion) -> LegacyVersion: if not isinstance(version, LegacyVersion): version = LegacyVersion(str(version)) return version - def _compare_equal(self, prospective, spec): + def _compare_equal(self, prospective: LegacyVersion, spec: str) -> bool: return prospective == self._coerce_version(spec) - def _compare_not_equal(self, prospective, spec): + def _compare_not_equal(self, prospective: LegacyVersion, spec: str) -> bool: return prospective != self._coerce_version(spec) - def _compare_less_than_equal(self, prospective, spec): + def _compare_less_than_equal(self, prospective: LegacyVersion, spec: str) -> bool: return prospective <= self._coerce_version(spec) - def _compare_greater_than_equal(self, prospective, spec): + def _compare_greater_than_equal( + self, prospective: LegacyVersion, spec: str + ) -> bool: return prospective >= self._coerce_version(spec) - def _compare_less_than(self, prospective, spec): + def _compare_less_than(self, prospective: LegacyVersion, spec: str) -> bool: return prospective < self._coerce_version(spec) - def _compare_greater_than(self, prospective, spec): + def _compare_greater_than(self, prospective: LegacyVersion, spec: str) -> bool: return prospective > self._coerce_version(spec) -def _require_version_compare(fn): +def _require_version_compare( + fn: Callable[["Specifier", ParsedVersion, str], bool] +) -> Callable[["Specifier", ParsedVersion, str], bool]: @functools.wraps(fn) - def wrapped(self, prospective, spec): + def wrapped(self: "Specifier", prospective: ParsedVersion, spec: str) -> bool: if not isinstance(prospective, Version): return False return fn(self, prospective, spec) @@ -372,7 +423,8 @@ class Specifier(_IndividualSpecifier): } @_require_version_compare - def _compare_compatible(self, prospective, spec): + def _compare_compatible(self, prospective: ParsedVersion, spec: str) -> bool: + # Compatible releases have an equivalent combination of >= and ==. That # is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to # implement this in terms of the other specifiers instead of @@ -380,15 +432,9 @@ class Specifier(_IndividualSpecifier): # the other specifiers. # We want everything but the last item in the version, but we want to - # ignore post and dev releases and we want to treat the pre-release as - # it's own separate segment. + # ignore suffix segments. prefix = ".".join( - list( - itertools.takewhile( - lambda x: (not x.startswith("post") and not x.startswith("dev")), - _version_split(spec), - ) - )[:-1] + list(itertools.takewhile(_is_not_suffix, _version_split(spec)))[:-1] ) # Add the prefix notation to the end of our string @@ -399,57 +445,73 @@ class Specifier(_IndividualSpecifier): ) @_require_version_compare - def _compare_equal(self, prospective, spec): + def _compare_equal(self, prospective: ParsedVersion, spec: str) -> bool: + # We need special logic to handle prefix matching if spec.endswith(".*"): # In the case of prefix matching we want to ignore local segment. prospective = Version(prospective.public) # Split the spec out by dots, and pretend that there is an implicit # dot in between a release segment and a pre-release segment. - spec = _version_split(spec[:-2]) # Remove the trailing .* + split_spec = _version_split(spec[:-2]) # Remove the trailing .* # Split the prospective version out by dots, and pretend that there # is an implicit dot in between a release segment and a pre-release # segment. - prospective = _version_split(str(prospective)) + split_prospective = _version_split(str(prospective)) # Shorten the prospective version to be the same length as the spec # so that we can determine if the specifier is a prefix of the # prospective version or not. - prospective = prospective[: len(spec)] + shortened_prospective = split_prospective[: len(split_spec)] # Pad out our two sides with zeros so that they both equal the same # length. - spec, prospective = _pad_version(spec, prospective) + padded_spec, padded_prospective = _pad_version( + split_spec, shortened_prospective + ) + + return padded_prospective == padded_spec else: # Convert our spec string into a Version - spec = Version(spec) + spec_version = Version(spec) # If the specifier does not have a local segment, then we want to # act as if the prospective version also does not have a local # segment. - if not spec.local: + if not spec_version.local: prospective = Version(prospective.public) - return prospective == spec + return prospective == spec_version @_require_version_compare - def _compare_not_equal(self, prospective, spec): + def _compare_not_equal(self, prospective: ParsedVersion, spec: str) -> bool: return not self._compare_equal(prospective, spec) @_require_version_compare - def _compare_less_than_equal(self, prospective, spec): - return prospective <= Version(spec) + def _compare_less_than_equal(self, prospective: ParsedVersion, spec: str) -> bool: + + # NB: Local version identifiers are NOT permitted in the version + # specifier, so local version labels can be universally removed from + # the prospective version. + return Version(prospective.public) <= Version(spec) @_require_version_compare - def _compare_greater_than_equal(self, prospective, spec): - return prospective >= Version(spec) + def _compare_greater_than_equal( + self, prospective: ParsedVersion, spec: str + ) -> bool: + + # NB: Local version identifiers are NOT permitted in the version + # specifier, so local version labels can be universally removed from + # the prospective version. + return Version(prospective.public) >= Version(spec) @_require_version_compare - def _compare_less_than(self, prospective, spec): + def _compare_less_than(self, prospective: ParsedVersion, spec_str: str) -> bool: + # Convert our spec to a Version instance, since we'll want to work with # it as a version. - spec = Version(spec) + spec = Version(spec_str) # Check to see if the prospective version is less than the spec # version. If it's not we can short circuit and just return False now @@ -471,10 +533,11 @@ class Specifier(_IndividualSpecifier): return True @_require_version_compare - def _compare_greater_than(self, prospective, spec): + def _compare_greater_than(self, prospective: ParsedVersion, spec_str: str) -> bool: + # Convert our spec to a Version instance, since we'll want to work with # it as a version. - spec = Version(spec) + spec = Version(spec_str) # Check to see if the prospective version is greater than the spec # version. If it's not we can short circuit and just return False now @@ -501,11 +564,12 @@ class Specifier(_IndividualSpecifier): # same version in the spec. return True - def _compare_arbitrary(self, prospective, spec): + def _compare_arbitrary(self, prospective: Version, spec: str) -> bool: return str(prospective).lower() == str(spec).lower() @property - def prereleases(self): + def prereleases(self) -> bool: + # If there is an explicit prereleases set for this, then we'll just # blindly use that. if self._prereleases is not None: @@ -529,15 +593,15 @@ class Specifier(_IndividualSpecifier): return False @prereleases.setter - def prereleases(self, value): + def prereleases(self, value: bool) -> None: self._prereleases = value _prefix_regex = re.compile(r"^([0-9]+)((?:a|b|c|rc)[0-9]+)$") -def _version_split(version): - result = [] +def _version_split(version: str) -> List[str]: + result: List[str] = [] for item in version.split("."): match = _prefix_regex.search(item) if match: @@ -547,7 +611,13 @@ def _version_split(version): return result -def _pad_version(left, right): +def _is_not_suffix(segment: str) -> bool: + return not any( + segment.startswith(prefix) for prefix in ("dev", "a", "b", "rc", "post") + ) + + +def _pad_version(left: List[str], right: List[str]) -> Tuple[List[str], List[str]]: left_split, right_split = [], [] # Get the release segment of our versions @@ -566,15 +636,18 @@ def _pad_version(left, right): class SpecifierSet(BaseSpecifier): - def __init__(self, specifiers="", prereleases=None): - # Split on , to break each indidivual specifier into it's own item, and + def __init__( + self, specifiers: str = "", prereleases: Optional[bool] = None + ) -> None: + + # Split on , to break each individual specifier into it's own item, and # strip each item to remove leading/trailing whitespace. - specifiers = [s.strip() for s in specifiers.split(",") if s.strip()] + split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()] # Parsed each individual specifier, attempting first to make it a # Specifier and falling back to a LegacySpecifier. - parsed = set() - for specifier in specifiers: + parsed: Set[_IndividualSpecifier] = set() + for specifier in split_specifiers: try: parsed.add(Specifier(specifier)) except InvalidSpecifier: @@ -587,23 +660,23 @@ class SpecifierSet(BaseSpecifier): # we accept prereleases or not. self._prereleases = prereleases - def __repr__(self): + def __repr__(self) -> str: pre = ( - ", prereleases={0!r}".format(self.prereleases) + f", prereleases={self.prereleases!r}" if self._prereleases is not None else "" ) - return "<SpecifierSet({0!r}{1})>".format(str(self), pre) + return "<SpecifierSet({!r}{})>".format(str(self), pre) - def __str__(self): + def __str__(self) -> str: return ",".join(sorted(str(s) for s in self._specs)) - def __hash__(self): + def __hash__(self) -> int: return hash(self._specs) - def __and__(self, other): - if isinstance(other, string_types): + def __and__(self, other: Union["SpecifierSet", str]) -> "SpecifierSet": + if isinstance(other, str): other = SpecifierSet(other) elif not isinstance(other, SpecifierSet): return NotImplemented @@ -625,34 +698,31 @@ class SpecifierSet(BaseSpecifier): return specifier - def __eq__(self, other): - if isinstance(other, string_types): - other = SpecifierSet(other) - elif isinstance(other, _IndividualSpecifier): + def __eq__(self, other: object) -> bool: + if isinstance(other, (str, _IndividualSpecifier)): other = SpecifierSet(str(other)) elif not isinstance(other, SpecifierSet): return NotImplemented return self._specs == other._specs - def __ne__(self, other): - if isinstance(other, string_types): - other = SpecifierSet(other) - elif isinstance(other, _IndividualSpecifier): + def __ne__(self, other: object) -> bool: + if isinstance(other, (str, _IndividualSpecifier)): other = SpecifierSet(str(other)) elif not isinstance(other, SpecifierSet): return NotImplemented return self._specs != other._specs - def __len__(self): + def __len__(self) -> int: return len(self._specs) - def __iter__(self): + def __iter__(self) -> Iterator[_IndividualSpecifier]: return iter(self._specs) @property - def prereleases(self): + def prereleases(self) -> Optional[bool]: + # If we have been given an explicit prerelease modifier, then we'll # pass that through here. if self._prereleases is not None: @@ -669,13 +739,16 @@ class SpecifierSet(BaseSpecifier): return any(s.prereleases for s in self._specs) @prereleases.setter - def prereleases(self, value): + def prereleases(self, value: bool) -> None: self._prereleases = value - def __contains__(self, item): + def __contains__(self, item: UnparsedVersion) -> bool: return self.contains(item) - def contains(self, item, prereleases=None): + def contains( + self, item: UnparsedVersion, prereleases: Optional[bool] = None + ) -> bool: + # Ensure that our item is a Version or LegacyVersion instance. if not isinstance(item, (LegacyVersion, Version)): item = parse(item) @@ -701,7 +774,10 @@ class SpecifierSet(BaseSpecifier): # will always return True, this is an explicit design decision. return all(s.contains(item, prereleases=prereleases) for s in self._specs) - def filter(self, iterable, prereleases=None): + def filter( + self, iterable: Iterable[VersionTypeVar], prereleases: Optional[bool] = None + ) -> Iterable[VersionTypeVar]: + # Determine if we're forcing a prerelease or not, if we're not forcing # one for this particular filter call, then we'll use whatever the # SpecifierSet thinks for whether or not we should support prereleases. @@ -719,8 +795,11 @@ class SpecifierSet(BaseSpecifier): # which will filter out any pre-releases, unless there are no final # releases, and which will filter out LegacyVersion in general. else: - filtered = [] - found_prereleases = [] + filtered: List[VersionTypeVar] = [] + found_prereleases: List[VersionTypeVar] = [] + + item: UnparsedVersion + parsed_version: Union[Version, LegacyVersion] for item in iterable: # Ensure that we some kind of Version class for this item. diff --git a/pipenv/patched/notpip/_vendor/packaging/tags.py b/pipenv/patched/notpip/_vendor/packaging/tags.py new file mode 100644 index 00000000..82a47cda --- /dev/null +++ b/pipenv/patched/notpip/_vendor/packaging/tags.py @@ -0,0 +1,484 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import logging +import platform +import sys +import sysconfig +from importlib.machinery import EXTENSION_SUFFIXES +from typing import ( + Dict, + FrozenSet, + Iterable, + Iterator, + List, + Optional, + Sequence, + Tuple, + Union, + cast, +) + +from . import _manylinux, _musllinux + +logger = logging.getLogger(__name__) + +PythonVersion = Sequence[int] +MacVersion = Tuple[int, int] + +INTERPRETER_SHORT_NAMES: Dict[str, str] = { + "python": "py", # Generic. + "cpython": "cp", + "pypy": "pp", + "ironpython": "ip", + "jython": "jy", +} + + +_32_BIT_INTERPRETER = sys.maxsize <= 2 ** 32 + + +class Tag: + """ + A representation of the tag triple for a wheel. + + Instances are considered immutable and thus are hashable. Equality checking + is also supported. + """ + + __slots__ = ["_interpreter", "_abi", "_platform", "_hash"] + + def __init__(self, interpreter: str, abi: str, platform: str) -> None: + self._interpreter = interpreter.lower() + self._abi = abi.lower() + self._platform = platform.lower() + # The __hash__ of every single element in a Set[Tag] will be evaluated each time + # that a set calls its `.disjoint()` method, which may be called hundreds of + # times when scanning a page of links for packages with tags matching that + # Set[Tag]. Pre-computing the value here produces significant speedups for + # downstream consumers. + self._hash = hash((self._interpreter, self._abi, self._platform)) + + @property + def interpreter(self) -> str: + return self._interpreter + + @property + def abi(self) -> str: + return self._abi + + @property + def platform(self) -> str: + return self._platform + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Tag): + return NotImplemented + + return ( + (self._hash == other._hash) # Short-circuit ASAP for perf reasons. + and (self._platform == other._platform) + and (self._abi == other._abi) + and (self._interpreter == other._interpreter) + ) + + def __hash__(self) -> int: + return self._hash + + def __str__(self) -> str: + return f"{self._interpreter}-{self._abi}-{self._platform}" + + def __repr__(self) -> str: + return "<{self} @ {self_id}>".format(self=self, self_id=id(self)) + + +def parse_tag(tag: str) -> FrozenSet[Tag]: + """ + Parses the provided tag (e.g. `py3-none-any`) into a frozenset of Tag instances. + + Returning a set is required due to the possibility that the tag is a + compressed tag set. + """ + tags = set() + interpreters, abis, platforms = tag.split("-") + for interpreter in interpreters.split("."): + for abi in abis.split("."): + for platform_ in platforms.split("."): + tags.add(Tag(interpreter, abi, platform_)) + return frozenset(tags) + + +def _get_config_var(name: str, warn: bool = False) -> Union[int, str, None]: + value = sysconfig.get_config_var(name) + if value is None and warn: + logger.debug( + "Config variable '%s' is unset, Python ABI tag may be incorrect", name + ) + return value + + +def _normalize_string(string: str) -> str: + return string.replace(".", "_").replace("-", "_") + + +def _abi3_applies(python_version: PythonVersion) -> bool: + """ + Determine if the Python version supports abi3. + + PEP 384 was first implemented in Python 3.2. + """ + return len(python_version) > 1 and tuple(python_version) >= (3, 2) + + +def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> List[str]: + py_version = tuple(py_version) # To allow for version comparison. + abis = [] + version = _version_nodot(py_version[:2]) + debug = pymalloc = ucs4 = "" + with_debug = _get_config_var("Py_DEBUG", warn) + has_refcount = hasattr(sys, "gettotalrefcount") + # Windows doesn't set Py_DEBUG, so checking for support of debug-compiled + # extension modules is the best option. + # https://github.com/pypa/pip/issues/3383#issuecomment-173267692 + has_ext = "_d.pyd" in EXTENSION_SUFFIXES + if with_debug or (with_debug is None and (has_refcount or has_ext)): + debug = "d" + if py_version < (3, 8): + with_pymalloc = _get_config_var("WITH_PYMALLOC", warn) + if with_pymalloc or with_pymalloc is None: + pymalloc = "m" + if py_version < (3, 3): + unicode_size = _get_config_var("Py_UNICODE_SIZE", warn) + if unicode_size == 4 or ( + unicode_size is None and sys.maxunicode == 0x10FFFF + ): + ucs4 = "u" + elif debug: + # Debug builds can also load "normal" extension modules. + # We can also assume no UCS-4 or pymalloc requirement. + abis.append(f"cp{version}") + abis.insert( + 0, + "cp{version}{debug}{pymalloc}{ucs4}".format( + version=version, debug=debug, pymalloc=pymalloc, ucs4=ucs4 + ), + ) + return abis + + +def cpython_tags( + python_version: Optional[PythonVersion] = None, + abis: Optional[Iterable[str]] = None, + platforms: Optional[Iterable[str]] = None, + *, + warn: bool = False, +) -> Iterator[Tag]: + """ + Yields the tags for a CPython interpreter. + + The tags consist of: + - cp<python_version>-<abi>-<platform> + - cp<python_version>-abi3-<platform> + - cp<python_version>-none-<platform> + - cp<less than python_version>-abi3-<platform> # Older Python versions down to 3.2. + + If python_version only specifies a major version then user-provided ABIs and + the 'none' ABItag will be used. + + If 'abi3' or 'none' are specified in 'abis' then they will be yielded at + their normal position and not at the beginning. + """ + if not python_version: + python_version = sys.version_info[:2] + + interpreter = "cp{}".format(_version_nodot(python_version[:2])) + + if abis is None: + if len(python_version) > 1: + abis = _cpython_abis(python_version, warn) + else: + abis = [] + abis = list(abis) + # 'abi3' and 'none' are explicitly handled later. + for explicit_abi in ("abi3", "none"): + try: + abis.remove(explicit_abi) + except ValueError: + pass + + platforms = list(platforms or _platform_tags()) + for abi in abis: + for platform_ in platforms: + yield Tag(interpreter, abi, platform_) + if _abi3_applies(python_version): + yield from (Tag(interpreter, "abi3", platform_) for platform_ in platforms) + yield from (Tag(interpreter, "none", platform_) for platform_ in platforms) + + if _abi3_applies(python_version): + for minor_version in range(python_version[1] - 1, 1, -1): + for platform_ in platforms: + interpreter = "cp{version}".format( + version=_version_nodot((python_version[0], minor_version)) + ) + yield Tag(interpreter, "abi3", platform_) + + +def _generic_abi() -> Iterator[str]: + abi = sysconfig.get_config_var("SOABI") + if abi: + yield _normalize_string(abi) + + +def generic_tags( + interpreter: Optional[str] = None, + abis: Optional[Iterable[str]] = None, + platforms: Optional[Iterable[str]] = None, + *, + warn: bool = False, +) -> Iterator[Tag]: + """ + Yields the tags for a generic interpreter. + + The tags consist of: + - <interpreter>-<abi>-<platform> + + The "none" ABI will be added if it was not explicitly provided. + """ + if not interpreter: + interp_name = interpreter_name() + interp_version = interpreter_version(warn=warn) + interpreter = "".join([interp_name, interp_version]) + if abis is None: + abis = _generic_abi() + platforms = list(platforms or _platform_tags()) + abis = list(abis) + if "none" not in abis: + abis.append("none") + for abi in abis: + for platform_ in platforms: + yield Tag(interpreter, abi, platform_) + + +def _py_interpreter_range(py_version: PythonVersion) -> Iterator[str]: + """ + Yields Python versions in descending order. + + After the latest version, the major-only version will be yielded, and then + all previous versions of that major version. + """ + if len(py_version) > 1: + yield "py{version}".format(version=_version_nodot(py_version[:2])) + yield "py{major}".format(major=py_version[0]) + if len(py_version) > 1: + for minor in range(py_version[1] - 1, -1, -1): + yield "py{version}".format(version=_version_nodot((py_version[0], minor))) + + +def compatible_tags( + python_version: Optional[PythonVersion] = None, + interpreter: Optional[str] = None, + platforms: Optional[Iterable[str]] = None, +) -> Iterator[Tag]: + """ + Yields the sequence of tags that are compatible with a specific version of Python. + + The tags consist of: + - py*-none-<platform> + - <interpreter>-none-any # ... if `interpreter` is provided. + - py*-none-any + """ + if not python_version: + python_version = sys.version_info[:2] + platforms = list(platforms or _platform_tags()) + for version in _py_interpreter_range(python_version): + for platform_ in platforms: + yield Tag(version, "none", platform_) + if interpreter: + yield Tag(interpreter, "none", "any") + for version in _py_interpreter_range(python_version): + yield Tag(version, "none", "any") + + +def _mac_arch(arch: str, is_32bit: bool = _32_BIT_INTERPRETER) -> str: + if not is_32bit: + return arch + + if arch.startswith("ppc"): + return "ppc" + + return "i386" + + +def _mac_binary_formats(version: MacVersion, cpu_arch: str) -> List[str]: + formats = [cpu_arch] + if cpu_arch == "x86_64": + if version < (10, 4): + return [] + formats.extend(["intel", "fat64", "fat32"]) + + elif cpu_arch == "i386": + if version < (10, 4): + return [] + formats.extend(["intel", "fat32", "fat"]) + + elif cpu_arch == "ppc64": + # TODO: Need to care about 32-bit PPC for ppc64 through 10.2? + if version > (10, 5) or version < (10, 4): + return [] + formats.append("fat64") + + elif cpu_arch == "ppc": + if version > (10, 6): + return [] + formats.extend(["fat32", "fat"]) + + if cpu_arch in {"arm64", "x86_64"}: + formats.append("universal2") + + if cpu_arch in {"x86_64", "i386", "ppc64", "ppc", "intel"}: + formats.append("universal") + + return formats + + +def mac_platforms( + version: Optional[MacVersion] = None, arch: Optional[str] = None +) -> Iterator[str]: + """ + Yields the platform tags for a macOS system. + + The `version` parameter is a two-item tuple specifying the macOS version to + generate platform tags for. The `arch` parameter is the CPU architecture to + generate platform tags for. Both parameters default to the appropriate value + for the current system. + """ + version_str, _, cpu_arch = platform.mac_ver() + if version is None: + version = cast("MacVersion", tuple(map(int, version_str.split(".")[:2]))) + else: + version = version + if arch is None: + arch = _mac_arch(cpu_arch) + else: + arch = arch + + if (10, 0) <= version and version < (11, 0): + # Prior to Mac OS 11, each yearly release of Mac OS bumped the + # "minor" version number. The major version was always 10. + for minor_version in range(version[1], -1, -1): + compat_version = 10, minor_version + binary_formats = _mac_binary_formats(compat_version, arch) + for binary_format in binary_formats: + yield "macosx_{major}_{minor}_{binary_format}".format( + major=10, minor=minor_version, binary_format=binary_format + ) + + if version >= (11, 0): + # Starting with Mac OS 11, each yearly release bumps the major version + # number. The minor versions are now the midyear updates. + for major_version in range(version[0], 10, -1): + compat_version = major_version, 0 + binary_formats = _mac_binary_formats(compat_version, arch) + for binary_format in binary_formats: + yield "macosx_{major}_{minor}_{binary_format}".format( + major=major_version, minor=0, binary_format=binary_format + ) + + if version >= (11, 0): + # Mac OS 11 on x86_64 is compatible with binaries from previous releases. + # Arm64 support was introduced in 11.0, so no Arm binaries from previous + # releases exist. + # + # However, the "universal2" binary format can have a + # macOS version earlier than 11.0 when the x86_64 part of the binary supports + # that version of macOS. + if arch == "x86_64": + for minor_version in range(16, 3, -1): + compat_version = 10, minor_version + binary_formats = _mac_binary_formats(compat_version, arch) + for binary_format in binary_formats: + yield "macosx_{major}_{minor}_{binary_format}".format( + major=compat_version[0], + minor=compat_version[1], + binary_format=binary_format, + ) + else: + for minor_version in range(16, 3, -1): + compat_version = 10, minor_version + binary_format = "universal2" + yield "macosx_{major}_{minor}_{binary_format}".format( + major=compat_version[0], + minor=compat_version[1], + binary_format=binary_format, + ) + + +def _linux_platforms(is_32bit: bool = _32_BIT_INTERPRETER) -> Iterator[str]: + linux = _normalize_string(sysconfig.get_platform()) + if is_32bit: + if linux == "linux_x86_64": + linux = "linux_i686" + elif linux == "linux_aarch64": + linux = "linux_armv7l" + _, arch = linux.split("_", 1) + yield from _manylinux.platform_tags(linux, arch) + yield from _musllinux.platform_tags(arch) + yield linux + + +def _generic_platforms() -> Iterator[str]: + yield _normalize_string(sysconfig.get_platform()) + + +def _platform_tags() -> Iterator[str]: + """ + Provides the platform tags for this installation. + """ + if platform.system() == "Darwin": + return mac_platforms() + elif platform.system() == "Linux": + return _linux_platforms() + else: + return _generic_platforms() + + +def interpreter_name() -> str: + """ + Returns the name of the running interpreter. + """ + name = sys.implementation.name + return INTERPRETER_SHORT_NAMES.get(name) or name + + +def interpreter_version(*, warn: bool = False) -> str: + """ + Returns the version of the running interpreter. + """ + version = _get_config_var("py_version_nodot", warn=warn) + if version: + version = str(version) + else: + version = _version_nodot(sys.version_info[:2]) + return version + + +def _version_nodot(version: PythonVersion) -> str: + return "".join(map(str, version)) + + +def sys_tags(*, warn: bool = False) -> Iterator[Tag]: + """ + Returns the sequence of tag triples for the running interpreter. + + The order of the sequence corresponds to priority order for the + interpreter, from most to least important. + """ + + interp_name = interpreter_name() + if interp_name == "cp": + yield from cpython_tags(warn=warn) + else: + yield from generic_tags() + + yield from compatible_tags() diff --git a/pipenv/patched/notpip/_vendor/packaging/utils.py b/pipenv/patched/notpip/_vendor/packaging/utils.py index 88418786..bab11b80 100644 --- a/pipenv/patched/notpip/_vendor/packaging/utils.py +++ b/pipenv/patched/notpip/_vendor/packaging/utils.py @@ -1,57 +1,136 @@ # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import re +from typing import FrozenSet, NewType, Tuple, Union, cast +from .tags import Tag, parse_tag from .version import InvalidVersion, Version +BuildTag = Union[Tuple[()], Tuple[int, str]] +NormalizedName = NewType("NormalizedName", str) + + +class InvalidWheelFilename(ValueError): + """ + An invalid wheel filename was found, users should refer to PEP 427. + """ + + +class InvalidSdistFilename(ValueError): + """ + An invalid sdist filename was found, users should refer to the packaging user guide. + """ + _canonicalize_regex = re.compile(r"[-_.]+") +# PEP 427: The build number must start with a digit. +_build_tag_regex = re.compile(r"(\d+)(.*)") -def canonicalize_name(name): +def canonicalize_name(name: str) -> NormalizedName: # This is taken from PEP 503. - return _canonicalize_regex.sub("-", name).lower() + value = _canonicalize_regex.sub("-", name).lower() + return cast(NormalizedName, value) -def canonicalize_version(version): +def canonicalize_version(version: Union[Version, str]) -> str: """ - This is very similar to Version.__str__, but has one subtle differences + This is very similar to Version.__str__, but has one subtle difference with the way it handles the release segment. """ - - try: - version = Version(version) - except InvalidVersion: - # Legacy versions cannot be normalized - return version + if isinstance(version, str): + try: + parsed = Version(version) + except InvalidVersion: + # Legacy versions cannot be normalized + return version + else: + parsed = version parts = [] # Epoch - if version.epoch != 0: - parts.append("{0}!".format(version.epoch)) + if parsed.epoch != 0: + parts.append(f"{parsed.epoch}!") # Release segment # NB: This strips trailing '.0's to normalize - parts.append(re.sub(r"(\.0)+$", "", ".".join(str(x) for x in version.release))) + parts.append(re.sub(r"(\.0)+$", "", ".".join(str(x) for x in parsed.release))) # Pre-release - if version.pre is not None: - parts.append("".join(str(x) for x in version.pre)) + if parsed.pre is not None: + parts.append("".join(str(x) for x in parsed.pre)) # Post-release - if version.post is not None: - parts.append(".post{0}".format(version.post)) + if parsed.post is not None: + parts.append(f".post{parsed.post}") # Development release - if version.dev is not None: - parts.append(".dev{0}".format(version.dev)) + if parsed.dev is not None: + parts.append(f".dev{parsed.dev}") # Local version segment - if version.local is not None: - parts.append("+{0}".format(version.local)) + if parsed.local is not None: + parts.append(f"+{parsed.local}") return "".join(parts) + + +def parse_wheel_filename( + filename: str, +) -> Tuple[NormalizedName, Version, BuildTag, FrozenSet[Tag]]: + if not filename.endswith(".whl"): + raise InvalidWheelFilename( + f"Invalid wheel filename (extension must be '.whl'): {filename}" + ) + + filename = filename[:-4] + dashes = filename.count("-") + if dashes not in (4, 5): + raise InvalidWheelFilename( + f"Invalid wheel filename (wrong number of parts): {filename}" + ) + + parts = filename.split("-", dashes - 2) + name_part = parts[0] + # See PEP 427 for the rules on escaping the project name + if "__" in name_part or re.match(r"^[\w\d._]*$", name_part, re.UNICODE) is None: + raise InvalidWheelFilename(f"Invalid project name: {filename}") + name = canonicalize_name(name_part) + version = Version(parts[1]) + if dashes == 5: + build_part = parts[2] + build_match = _build_tag_regex.match(build_part) + if build_match is None: + raise InvalidWheelFilename( + f"Invalid build number: {build_part} in '{filename}'" + ) + build = cast(BuildTag, (int(build_match.group(1)), build_match.group(2))) + else: + build = () + tags = parse_tag(parts[-1]) + return (name, version, build, tags) + + +def parse_sdist_filename(filename: str) -> Tuple[NormalizedName, Version]: + if filename.endswith(".tar.gz"): + file_stem = filename[: -len(".tar.gz")] + elif filename.endswith(".zip"): + file_stem = filename[: -len(".zip")] + else: + raise InvalidSdistFilename( + f"Invalid sdist filename (extension must be '.tar.gz' or '.zip'):" + f" {filename}" + ) + + # We are requiring a PEP 440 version, which cannot contain dashes, + # so we split on the last dash. + name_part, sep, version_part = file_stem.rpartition("-") + if not sep: + raise InvalidSdistFilename(f"Invalid sdist filename: {filename}") + + name = canonicalize_name(name_part) + version = Version(version_part) + return (name, version) diff --git a/pipenv/patched/notpip/_vendor/packaging/version.py b/pipenv/patched/notpip/_vendor/packaging/version.py index 95157a1f..de9a09a4 100644 --- a/pipenv/patched/notpip/_vendor/packaging/version.py +++ b/pipenv/patched/notpip/_vendor/packaging/version.py @@ -1,24 +1,45 @@ # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import collections import itertools import re +import warnings +from typing import Callable, Iterator, List, Optional, SupportsInt, Tuple, Union -from ._structures import Infinity - +from ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType __all__ = ["parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN"] +InfiniteTypes = Union[InfinityType, NegativeInfinityType] +PrePostDevType = Union[InfiniteTypes, Tuple[str, int]] +SubLocalType = Union[InfiniteTypes, int, str] +LocalType = Union[ + NegativeInfinityType, + Tuple[ + Union[ + SubLocalType, + Tuple[SubLocalType, str], + Tuple[NegativeInfinityType, SubLocalType], + ], + ..., + ], +] +CmpKey = Tuple[ + int, Tuple[int, ...], PrePostDevType, PrePostDevType, PrePostDevType, LocalType +] +LegacyCmpKey = Tuple[int, Tuple[str, ...]] +VersionComparisonMethod = Callable[ + [Union[CmpKey, LegacyCmpKey], Union[CmpKey, LegacyCmpKey]], bool +] _Version = collections.namedtuple( "_Version", ["epoch", "release", "dev", "pre", "post", "local"] ) -def parse(version): +def parse(version: str) -> Union["LegacyVersion", "Version"]: """ Parse the given version string and return either a :class:`Version` object or a :class:`LegacyVersion` object depending on if the given version is @@ -36,88 +57,111 @@ class InvalidVersion(ValueError): """ -class _BaseVersion(object): - def __hash__(self): +class _BaseVersion: + _key: Union[CmpKey, LegacyCmpKey] + + def __hash__(self) -> int: return hash(self._key) - def __lt__(self, other): - return self._compare(other, lambda s, o: s < o) - - def __le__(self, other): - return self._compare(other, lambda s, o: s <= o) - - def __eq__(self, other): - return self._compare(other, lambda s, o: s == o) - - def __ge__(self, other): - return self._compare(other, lambda s, o: s >= o) - - def __gt__(self, other): - return self._compare(other, lambda s, o: s > o) - - def __ne__(self, other): - return self._compare(other, lambda s, o: s != o) - - def _compare(self, other, method): + # Please keep the duplicated `isinstance` check + # in the six comparisons hereunder + # unless you find a way to avoid adding overhead function calls. + def __lt__(self, other: "_BaseVersion") -> bool: if not isinstance(other, _BaseVersion): return NotImplemented - return method(self._key, other._key) + return self._key < other._key + + def __le__(self, other: "_BaseVersion") -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key <= other._key + + def __eq__(self, other: object) -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key == other._key + + def __ge__(self, other: "_BaseVersion") -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key >= other._key + + def __gt__(self, other: "_BaseVersion") -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key > other._key + + def __ne__(self, other: object) -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key != other._key class LegacyVersion(_BaseVersion): - def __init__(self, version): + def __init__(self, version: str) -> None: self._version = str(version) self._key = _legacy_cmpkey(self._version) - def __str__(self): + warnings.warn( + "Creating a LegacyVersion has been deprecated and will be " + "removed in the next major release", + DeprecationWarning, + ) + + def __str__(self) -> str: return self._version - def __repr__(self): - return "<LegacyVersion({0})>".format(repr(str(self))) + def __repr__(self) -> str: + return f"<LegacyVersion('{self}')>" @property - def public(self): + def public(self) -> str: return self._version @property - def base_version(self): + def base_version(self) -> str: return self._version @property - def epoch(self): + def epoch(self) -> int: return -1 @property - def release(self): + def release(self) -> None: return None @property - def pre(self): + def pre(self) -> None: return None @property - def post(self): + def post(self) -> None: return None @property - def dev(self): + def dev(self) -> None: return None @property - def local(self): + def local(self) -> None: return None @property - def is_prerelease(self): + def is_prerelease(self) -> bool: return False @property - def is_postrelease(self): + def is_postrelease(self) -> bool: return False @property - def is_devrelease(self): + def is_devrelease(self) -> bool: return False @@ -132,7 +176,7 @@ _legacy_version_replacement_map = { } -def _parse_version_parts(s): +def _parse_version_parts(s: str) -> Iterator[str]: for part in _legacy_version_component_re.split(s): part = _legacy_version_replacement_map.get(part, part) @@ -149,7 +193,8 @@ def _parse_version_parts(s): yield "*final" -def _legacy_cmpkey(version): +def _legacy_cmpkey(version: str) -> LegacyCmpKey: + # We hardcode an epoch of -1 here. A PEP 440 version can only have a epoch # greater than or equal to 0. This will effectively put the LegacyVersion, # which uses the defacto standard originally implemented by setuptools, @@ -158,7 +203,7 @@ def _legacy_cmpkey(version): # This scheme is taken from pkg_resources.parse_version setuptools prior to # it's adoption of the packaging library. - parts = [] + parts: List[str] = [] for part in _parse_version_parts(version.lower()): if part.startswith("*"): # remove "-" before a prerelease tag @@ -171,9 +216,8 @@ def _legacy_cmpkey(version): parts.pop() parts.append(part) - parts = tuple(parts) - return epoch, parts + return epoch, tuple(parts) # Deliberately not anchored to the start and end of the string, to make it @@ -214,11 +258,12 @@ class Version(_BaseVersion): _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE) - def __init__(self, version): + def __init__(self, version: str) -> None: + # Validate the version and parse it into pieces match = self._regex.search(version) if not match: - raise InvalidVersion("Invalid version: '{0}'".format(version)) + raise InvalidVersion(f"Invalid version: '{version}'") # Store the parsed out pieces of the version self._version = _Version( @@ -242,15 +287,15 @@ class Version(_BaseVersion): self._version.local, ) - def __repr__(self): - return "<Version({0})>".format(repr(str(self))) + def __repr__(self) -> str: + return f"<Version('{self}')>" - def __str__(self): + def __str__(self) -> str: parts = [] # Epoch if self.epoch != 0: - parts.append("{0}!".format(self.epoch)) + parts.append(f"{self.epoch}!") # Release segment parts.append(".".join(str(x) for x in self.release)) @@ -261,56 +306,59 @@ class Version(_BaseVersion): # Post-release if self.post is not None: - parts.append(".post{0}".format(self.post)) + parts.append(f".post{self.post}") # Development release if self.dev is not None: - parts.append(".dev{0}".format(self.dev)) + parts.append(f".dev{self.dev}") # Local version segment if self.local is not None: - parts.append("+{0}".format(self.local)) + parts.append(f"+{self.local}") return "".join(parts) @property - def epoch(self): - return self._version.epoch + def epoch(self) -> int: + _epoch: int = self._version.epoch + return _epoch @property - def release(self): - return self._version.release + def release(self) -> Tuple[int, ...]: + _release: Tuple[int, ...] = self._version.release + return _release @property - def pre(self): - return self._version.pre + def pre(self) -> Optional[Tuple[str, int]]: + _pre: Optional[Tuple[str, int]] = self._version.pre + return _pre @property - def post(self): + def post(self) -> Optional[int]: return self._version.post[1] if self._version.post else None @property - def dev(self): + def dev(self) -> Optional[int]: return self._version.dev[1] if self._version.dev else None @property - def local(self): + def local(self) -> Optional[str]: if self._version.local: return ".".join(str(x) for x in self._version.local) else: return None @property - def public(self): + def public(self) -> str: return str(self).split("+", 1)[0] @property - def base_version(self): + def base_version(self) -> str: parts = [] # Epoch if self.epoch != 0: - parts.append("{0}!".format(self.epoch)) + parts.append(f"{self.epoch}!") # Release segment parts.append(".".join(str(x) for x in self.release)) @@ -318,19 +366,34 @@ class Version(_BaseVersion): return "".join(parts) @property - def is_prerelease(self): + def is_prerelease(self) -> bool: return self.dev is not None or self.pre is not None @property - def is_postrelease(self): + def is_postrelease(self) -> bool: return self.post is not None @property - def is_devrelease(self): + def is_devrelease(self) -> bool: return self.dev is not None + @property + def major(self) -> int: + return self.release[0] if len(self.release) >= 1 else 0 + + @property + def minor(self) -> int: + return self.release[1] if len(self.release) >= 2 else 0 + + @property + def micro(self) -> int: + return self.release[2] if len(self.release) >= 3 else 0 + + +def _parse_letter_version( + letter: str, number: Union[str, bytes, SupportsInt] +) -> Optional[Tuple[str, int]]: -def _parse_letter_version(letter, number): if letter: # We consider there to be an implicit 0 in a pre-release if there is # not a numeral associated with it. @@ -360,11 +423,13 @@ def _parse_letter_version(letter, number): return letter, int(number) + return None + _local_version_separators = re.compile(r"[\._-]") -def _parse_local_version(local): +def _parse_local_version(local: str) -> Optional[LocalType]: """ Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve"). """ @@ -373,15 +438,24 @@ def _parse_local_version(local): part.lower() if not part.isdigit() else int(part) for part in _local_version_separators.split(local) ) + return None -def _cmpkey(epoch, release, pre, post, dev, local): +def _cmpkey( + epoch: int, + release: Tuple[int, ...], + pre: Optional[Tuple[str, int]], + post: Optional[Tuple[str, int]], + dev: Optional[Tuple[str, int]], + local: Optional[Tuple[SubLocalType]], +) -> CmpKey: + # When we compare a release version, we want to compare it with all of the # trailing zeros removed. So we'll use a reverse the list, drop all the now # leading zeros until we come to something non zero, then take the rest # re-reverse it back into the correct order and make it a tuple and use # that for our sorting key. - release = tuple( + _release = tuple( reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release)))) ) @@ -390,23 +464,31 @@ def _cmpkey(epoch, release, pre, post, dev, local): # if there is not a pre or a post segment. If we have one of those then # the normal sorting rules will handle this case correctly. if pre is None and post is None and dev is not None: - pre = -Infinity + _pre: PrePostDevType = NegativeInfinity # Versions without a pre-release (except as noted above) should sort after # those with one. elif pre is None: - pre = Infinity + _pre = Infinity + else: + _pre = pre # Versions without a post segment should sort before those with one. if post is None: - post = -Infinity + _post: PrePostDevType = NegativeInfinity + + else: + _post = post # Versions without a development segment should sort after those with one. if dev is None: - dev = Infinity + _dev: PrePostDevType = Infinity + + else: + _dev = dev if local is None: # Versions without a local segment should sort before those with one. - local = -Infinity + _local: LocalType = NegativeInfinity else: # Versions with a local segment need that segment parsed to implement # the sorting rules in PEP440. @@ -415,6 +497,8 @@ def _cmpkey(epoch, release, pre, post, dev, local): # - Numeric segments sort numerically # - Shorter versions sort before longer versions when the prefixes # match exactly - local = tuple((i, "") if isinstance(i, int) else (-Infinity, i) for i in local) + _local = tuple( + (i, "") if isinstance(i, int) else (NegativeInfinity, i) for i in local + ) - return epoch, release, pre, post, dev, local + return epoch, _release, _pre, _post, _dev, _local diff --git a/pipenv/patched/notpip/_vendor/pep517/__init__.py b/pipenv/patched/notpip/_vendor/pep517/__init__.py index 9c1a098f..f064d60c 100644 --- a/pipenv/patched/notpip/_vendor/pep517/__init__.py +++ b/pipenv/patched/notpip/_vendor/pep517/__init__.py @@ -1,4 +1,6 @@ """Wrappers to build Python packages using PEP 517 hooks """ -__version__ = '0.5.0' +__version__ = '0.11.0' + +from .wrappers import * # noqa: F401, F403 diff --git a/pipenv/patched/notpip/_vendor/pep517/_in_process.py b/pipenv/patched/notpip/_vendor/pep517/_in_process.py deleted file mode 100644 index d6524b66..00000000 --- a/pipenv/patched/notpip/_vendor/pep517/_in_process.py +++ /dev/null @@ -1,207 +0,0 @@ -"""This is invoked in a subprocess to call the build backend hooks. - -It expects: -- Command line args: hook_name, control_dir -- Environment variable: PEP517_BUILD_BACKEND=entry.point:spec -- control_dir/input.json: - - {"kwargs": {...}} - -Results: -- control_dir/output.json - - {"return_val": ...} -""" -from glob import glob -from importlib import import_module -import os -from os.path import join as pjoin -import re -import shutil -import sys - -# This is run as a script, not a module, so it can't do a relative import -import compat - - -class BackendUnavailable(Exception): - """Raised if we cannot import the backend""" - - -def _build_backend(): - """Find and load the build backend""" - ep = os.environ['PEP517_BUILD_BACKEND'] - mod_path, _, obj_path = ep.partition(':') - try: - obj = import_module(mod_path) - except ImportError: - raise BackendUnavailable - if obj_path: - for path_part in obj_path.split('.'): - obj = getattr(obj, path_part) - return obj - - -def get_requires_for_build_wheel(config_settings): - """Invoke the optional get_requires_for_build_wheel hook - - Returns [] if the hook is not defined. - """ - backend = _build_backend() - try: - hook = backend.get_requires_for_build_wheel - except AttributeError: - return [] - else: - return hook(config_settings) - - -def prepare_metadata_for_build_wheel(metadata_directory, config_settings): - """Invoke optional prepare_metadata_for_build_wheel - - Implements a fallback by building a wheel if the hook isn't defined. - """ - backend = _build_backend() - try: - hook = backend.prepare_metadata_for_build_wheel - except AttributeError: - return _get_wheel_metadata_from_wheel(backend, metadata_directory, - config_settings) - else: - return hook(metadata_directory, config_settings) - - -WHEEL_BUILT_MARKER = 'PEP517_ALREADY_BUILT_WHEEL' - - -def _dist_info_files(whl_zip): - """Identify the .dist-info folder inside a wheel ZipFile.""" - res = [] - for path in whl_zip.namelist(): - m = re.match(r'[^/\\]+-[^/\\]+\.dist-info/', path) - if m: - res.append(path) - if res: - return res - raise Exception("No .dist-info folder found in wheel") - - -def _get_wheel_metadata_from_wheel( - backend, metadata_directory, config_settings): - """Build a wheel and extract the metadata from it. - - Fallback for when the build backend does not - define the 'get_wheel_metadata' hook. - """ - from zipfile import ZipFile - whl_basename = backend.build_wheel(metadata_directory, config_settings) - with open(os.path.join(metadata_directory, WHEEL_BUILT_MARKER), 'wb'): - pass # Touch marker file - - whl_file = os.path.join(metadata_directory, whl_basename) - with ZipFile(whl_file) as zipf: - dist_info = _dist_info_files(zipf) - zipf.extractall(path=metadata_directory, members=dist_info) - return dist_info[0].split('/')[0] - - -def _find_already_built_wheel(metadata_directory): - """Check for a wheel already built during the get_wheel_metadata hook. - """ - if not metadata_directory: - return None - metadata_parent = os.path.dirname(metadata_directory) - if not os.path.isfile(pjoin(metadata_parent, WHEEL_BUILT_MARKER)): - return None - - whl_files = glob(os.path.join(metadata_parent, '*.whl')) - if not whl_files: - print('Found wheel built marker, but no .whl files') - return None - if len(whl_files) > 1: - print('Found multiple .whl files; unspecified behaviour. ' - 'Will call build_wheel.') - return None - - # Exactly one .whl file - return whl_files[0] - - -def build_wheel(wheel_directory, config_settings, metadata_directory=None): - """Invoke the mandatory build_wheel hook. - - If a wheel was already built in the - prepare_metadata_for_build_wheel fallback, this - will copy it rather than rebuilding the wheel. - """ - prebuilt_whl = _find_already_built_wheel(metadata_directory) - if prebuilt_whl: - shutil.copy2(prebuilt_whl, wheel_directory) - return os.path.basename(prebuilt_whl) - - return _build_backend().build_wheel(wheel_directory, config_settings, - metadata_directory) - - -def get_requires_for_build_sdist(config_settings): - """Invoke the optional get_requires_for_build_wheel hook - - Returns [] if the hook is not defined. - """ - backend = _build_backend() - try: - hook = backend.get_requires_for_build_sdist - except AttributeError: - return [] - else: - return hook(config_settings) - - -class _DummyException(Exception): - """Nothing should ever raise this exception""" - - -class GotUnsupportedOperation(Exception): - """For internal use when backend raises UnsupportedOperation""" - - -def build_sdist(sdist_directory, config_settings): - """Invoke the mandatory build_sdist hook.""" - backend = _build_backend() - try: - return backend.build_sdist(sdist_directory, config_settings) - except getattr(backend, 'UnsupportedOperation', _DummyException): - raise GotUnsupportedOperation - - -HOOK_NAMES = { - 'get_requires_for_build_wheel', - 'prepare_metadata_for_build_wheel', - 'build_wheel', - 'get_requires_for_build_sdist', - 'build_sdist', -} - - -def main(): - if len(sys.argv) < 3: - sys.exit("Needs args: hook_name, control_dir") - hook_name = sys.argv[1] - control_dir = sys.argv[2] - if hook_name not in HOOK_NAMES: - sys.exit("Unknown hook: %s" % hook_name) - hook = globals()[hook_name] - - hook_input = compat.read_json(pjoin(control_dir, 'input.json')) - - json_out = {'unsupported': False, 'return_val': None} - try: - json_out['return_val'] = hook(**hook_input['kwargs']) - except BackendUnavailable: - json_out['no_backend'] = True - except GotUnsupportedOperation: - json_out['unsupported'] = True - - compat.write_json(json_out, pjoin(control_dir, 'output.json'), indent=2) - - -if __name__ == '__main__': - main() diff --git a/pipenv/patched/notpip/_vendor/pep517/build.py b/pipenv/patched/notpip/_vendor/pep517/build.py index db9a0799..3b752145 100644 --- a/pipenv/patched/notpip/_vendor/pep517/build.py +++ b/pipenv/patched/notpip/_vendor/pep517/build.py @@ -1,27 +1,58 @@ """Build a project using PEP 517 hooks. """ import argparse +import io import logging import os -import contextlib -from pipenv.patched.notpip._vendor import pytoml import shutil -import errno -import tempfile from .envbuild import BuildEnvironment from .wrappers import Pep517HookCaller +from .dirtools import tempdir, mkdir_p +from .compat import FileNotFoundError, toml_load log = logging.getLogger(__name__) -@contextlib.contextmanager -def tempdir(): - td = tempfile.mkdtemp() +def validate_system(system): + """ + Ensure build system has the requisite fields. + """ + required = {'requires', 'build-backend'} + if not (required <= set(system)): + message = "Missing required fields: {missing}".format( + missing=required-set(system), + ) + raise ValueError(message) + + +def load_system(source_dir): + """ + Load the build system from a source dir (pyproject.toml). + """ + pyproject = os.path.join(source_dir, 'pyproject.toml') + with io.open(pyproject, encoding="utf-8") as f: + pyproject_data = toml_load(f) + return pyproject_data['build-system'] + + +def compat_system(source_dir): + """ + Given a source dir, attempt to get a build system backend + and requirements from pyproject.toml. Fallback to + setuptools but only if the file was not found or a build + system was not indicated. + """ try: - yield td - finally: - shutil.rmtree(td) + system = load_system(source_dir) + except (FileNotFoundError, KeyError): + system = {} + system.setdefault( + 'build-backend', + 'setuptools.build_meta:__legacy__', + ) + system.setdefault('requires', ['setuptools', 'wheel']) + return system def _do_build(hooks, env, dist, dest): @@ -42,33 +73,18 @@ def _do_build(hooks, env, dist, dest): shutil.move(source, os.path.join(dest, os.path.basename(filename))) -def mkdir_p(*args, **kwargs): - """Like `mkdir`, but does not raise an exception if the - directory already exists. - """ - try: - return os.mkdir(*args, **kwargs) - except OSError as exc: - if exc.errno != errno.EEXIST: - raise - - -def build(source_dir, dist, dest=None): - pyproject = os.path.join(source_dir, 'pyproject.toml') +def build(source_dir, dist, dest=None, system=None): + system = system or load_system(source_dir) dest = os.path.join(source_dir, dest or 'dist') mkdir_p(dest) - with open(pyproject) as f: - pyproject_data = pytoml.load(f) - # Ensure the mandatory data can be loaded - buildsys = pyproject_data['build-system'] - requires = buildsys['requires'] - backend = buildsys['build-backend'] - - hooks = Pep517HookCaller(source_dir, backend) + validate_system(system) + hooks = Pep517HookCaller( + source_dir, system['build-backend'], system.get('backend-path') + ) with BuildEnvironment() as env: - env.pip_install(requires) + env.pip_install(system['requires']) _do_build(hooks, env, dist, dest) @@ -94,6 +110,9 @@ parser.add_argument( def main(args): + log.warning('pep517.build is deprecated. ' + 'Consider switching to https://pypi.org/project/build/') + # determine which dists to build dists = list(filter(None, ( 'sdist' if args.source or not args.binary else None, diff --git a/pipenv/patched/notpip/_vendor/pep517/check.py b/pipenv/patched/notpip/_vendor/pep517/check.py index 9d28ba44..719be040 100644 --- a/pipenv/patched/notpip/_vendor/pep517/check.py +++ b/pipenv/patched/notpip/_vendor/pep517/check.py @@ -1,10 +1,10 @@ """Check a project and backend by attempting to build using PEP 517 hooks. """ import argparse +import io import logging import os from os.path import isfile, join as pjoin -from pipenv.patched.notpip._vendor.pytoml import TomlError, load as toml_load import shutil from subprocess import CalledProcessError import sys @@ -13,6 +13,7 @@ from tempfile import mkdtemp import zipfile from .colorlog import enable_colourful_output +from .compat import TOMLDecodeError, toml_load from .envbuild import BuildEnvironment from .wrappers import Pep517HookCaller @@ -141,18 +142,19 @@ def check(source_dir): return False try: - with open(pyproject) as f: + with io.open(pyproject, encoding="utf-8") as f: pyproject_data = toml_load(f) # Ensure the mandatory data can be loaded buildsys = pyproject_data['build-system'] requires = buildsys['requires'] backend = buildsys['build-backend'] + backend_path = buildsys.get('backend-path') log.info('Loaded pyproject.toml') - except (TomlError, KeyError): + except (TOMLDecodeError, KeyError): log.error("Invalid pyproject.toml", exc_info=True) return False - hooks = Pep517HookCaller(source_dir, backend) + hooks = Pep517HookCaller(source_dir, backend, backend_path) sdist_ok = check_build_sdist(hooks, requires) wheel_ok = check_build_wheel(hooks, requires) @@ -166,6 +168,9 @@ def check(source_dir): def main(argv=None): + log.warning('pep517.check is deprecated. ' + 'Consider switching to https://pypi.org/project/build/') + ap = argparse.ArgumentParser() ap.add_argument( 'source_dir', diff --git a/pipenv/patched/notpip/_vendor/pep517/compat.py b/pipenv/patched/notpip/_vendor/pep517/compat.py index 01c66fc7..c2c4aaa8 100644 --- a/pipenv/patched/notpip/_vendor/pep517/compat.py +++ b/pipenv/patched/notpip/_vendor/pep517/compat.py @@ -1,7 +1,10 @@ -"""Handle reading and writing JSON in UTF-8, on Python 3 and 2.""" +"""Python 2/3 compatibility""" import json import sys + +# Handle reading and writing JSON in UTF-8, on Python 3 and 2. + if sys.version_info[0] >= 3: # Python 3 def write_json(obj, path, **kwargs): @@ -21,3 +24,19 @@ else: def read_json(path): with open(path, 'rb') as f: return json.load(f) + + +# FileNotFoundError + +try: + FileNotFoundError = FileNotFoundError +except NameError: + FileNotFoundError = IOError + + +if sys.version_info < (3, 6): + from pipenv.vendor.toml import load as toml_load # noqa: F401 + from pipenv.vendor.toml import TomlDecodeError as TOMLDecodeError # noqa: F401 +else: + from pipenv.patched.notpip._vendor.tomli import load as toml_load # noqa: F401 + from pipenv.patched.notpip._vendor.tomli import TOMLDecodeError # noqa: F401 diff --git a/pipenv/patched/notpip/_vendor/pep517/dirtools.py b/pipenv/patched/notpip/_vendor/pep517/dirtools.py new file mode 100644 index 00000000..58c6ca0c --- /dev/null +++ b/pipenv/patched/notpip/_vendor/pep517/dirtools.py @@ -0,0 +1,44 @@ +import os +import io +import contextlib +import tempfile +import shutil +import errno +import zipfile + + +@contextlib.contextmanager +def tempdir(): + """Create a temporary directory in a context manager.""" + td = tempfile.mkdtemp() + try: + yield td + finally: + shutil.rmtree(td) + + +def mkdir_p(*args, **kwargs): + """Like `mkdir`, but does not raise an exception if the + directory already exists. + """ + try: + return os.mkdir(*args, **kwargs) + except OSError as exc: + if exc.errno != errno.EEXIST: + raise + + +def dir_to_zipfile(root): + """Construct an in-memory zip file for a directory.""" + buffer = io.BytesIO() + zip_file = zipfile.ZipFile(buffer, 'w') + for root, dirs, files in os.walk(root): + for path in dirs: + fs_path = os.path.join(root, path) + rel_path = os.path.relpath(fs_path, root) + zip_file.writestr(rel_path + '/', '') + for path in files: + fs_path = os.path.join(root, path) + rel_path = os.path.relpath(fs_path, root) + zip_file.write(fs_path, rel_path) + return zip_file diff --git a/pipenv/patched/notpip/_vendor/pep517/envbuild.py b/pipenv/patched/notpip/_vendor/pep517/envbuild.py index 8a5ad4d7..7c2344bf 100644 --- a/pipenv/patched/notpip/_vendor/pep517/envbuild.py +++ b/pipenv/patched/notpip/_vendor/pep517/envbuild.py @@ -1,25 +1,33 @@ """Build wheels/sdists by installing build deps to a temporary environment. """ +import io import os import logging -from pipenv.patched.notpip._vendor import pytoml import shutil from subprocess import check_call import sys from sysconfig import get_paths from tempfile import mkdtemp -from .wrappers import Pep517HookCaller +from .compat import toml_load +from .wrappers import Pep517HookCaller, LoggerWrapper log = logging.getLogger(__name__) def _load_pyproject(source_dir): - with open(os.path.join(source_dir, 'pyproject.toml')) as f: - pyproject_data = pytoml.load(f) + with io.open( + os.path.join(source_dir, 'pyproject.toml'), + encoding="utf-8", + ) as f: + pyproject_data = toml_load(f) buildsys = pyproject_data['build-system'] - return buildsys['requires'], buildsys['build-backend'] + return ( + buildsys['requires'], + buildsys['build-backend'], + buildsys.get('backend-path'), + ) class BuildEnvironment(object): @@ -90,9 +98,14 @@ class BuildEnvironment(object): if not reqs: return log.info('Calling pip to install %s', reqs) - check_call([ + cmd = [ sys.executable, '-m', 'pip', 'install', '--ignore-installed', - '--prefix', self.path] + list(reqs)) + '--prefix', self.path] + list(reqs) + check_call( + cmd, + stdout=LoggerWrapper(log, logging.INFO), + stderr=LoggerWrapper(log, logging.ERROR), + ) def __exit__(self, exc_type, exc_val, exc_tb): needs_cleanup = ( @@ -126,8 +139,8 @@ def build_wheel(source_dir, wheel_dir, config_settings=None): """ if config_settings is None: config_settings = {} - requires, backend = _load_pyproject(source_dir) - hooks = Pep517HookCaller(source_dir, backend) + requires, backend, backend_path = _load_pyproject(source_dir) + hooks = Pep517HookCaller(source_dir, backend, backend_path) with BuildEnvironment() as env: env.pip_install(requires) @@ -148,8 +161,8 @@ def build_sdist(source_dir, sdist_dir, config_settings=None): """ if config_settings is None: config_settings = {} - requires, backend = _load_pyproject(source_dir) - hooks = Pep517HookCaller(source_dir, backend) + requires, backend, backend_path = _load_pyproject(source_dir) + hooks = Pep517HookCaller(source_dir, backend, backend_path) with BuildEnvironment() as env: env.pip_install(requires) diff --git a/pipenv/patched/notpip/_vendor/pep517/in_process/__init__.py b/pipenv/patched/notpip/_vendor/pep517/in_process/__init__.py new file mode 100644 index 00000000..c932313b --- /dev/null +++ b/pipenv/patched/notpip/_vendor/pep517/in_process/__init__.py @@ -0,0 +1,17 @@ +"""This is a subpackage because the directory is on sys.path for _in_process.py + +The subpackage should stay as empty as possible to avoid shadowing modules that +the backend might import. +""" +from os.path import dirname, abspath, join as pjoin +from contextlib import contextmanager + +try: + import importlib.resources as resources + + def _in_proc_script_path(): + return resources.path(__package__, '_in_process.py') +except ImportError: + @contextmanager + def _in_proc_script_path(): + yield pjoin(dirname(abspath(__file__)), '_in_process.py') diff --git a/pipenv/patched/notpip/_vendor/pep517/in_process/_in_process.py b/pipenv/patched/notpip/_vendor/pep517/in_process/_in_process.py new file mode 100644 index 00000000..c7f5f057 --- /dev/null +++ b/pipenv/patched/notpip/_vendor/pep517/in_process/_in_process.py @@ -0,0 +1,349 @@ +"""This is invoked in a subprocess to call the build backend hooks. + +It expects: +- Command line args: hook_name, control_dir +- Environment variables: + PEP517_BUILD_BACKEND=entry.point:spec + PEP517_BACKEND_PATH=paths (separated with os.pathsep) +- control_dir/input.json: + - {"kwargs": {...}} + +Results: +- control_dir/output.json + - {"return_val": ...} +""" +from glob import glob +from importlib import import_module +import json +import os +import os.path +from os.path import join as pjoin +import re +import shutil +import sys +import traceback + +# This file is run as a script, and `import compat` is not zip-safe, so we +# include write_json() and read_json() from compat.py. +# +# Handle reading and writing JSON in UTF-8, on Python 3 and 2. + +if sys.version_info[0] >= 3: + # Python 3 + def write_json(obj, path, **kwargs): + with open(path, 'w', encoding='utf-8') as f: + json.dump(obj, f, **kwargs) + + def read_json(path): + with open(path, 'r', encoding='utf-8') as f: + return json.load(f) + +else: + # Python 2 + def write_json(obj, path, **kwargs): + with open(path, 'wb') as f: + json.dump(obj, f, encoding='utf-8', **kwargs) + + def read_json(path): + with open(path, 'rb') as f: + return json.load(f) + + +class BackendUnavailable(Exception): + """Raised if we cannot import the backend""" + def __init__(self, traceback): + self.traceback = traceback + + +class BackendInvalid(Exception): + """Raised if the backend is invalid""" + def __init__(self, message): + self.message = message + + +class HookMissing(Exception): + """Raised if a hook is missing and we are not executing the fallback""" + def __init__(self, hook_name=None): + super(HookMissing, self).__init__(hook_name) + self.hook_name = hook_name + + +def contained_in(filename, directory): + """Test if a file is located within the given directory.""" + filename = os.path.normcase(os.path.abspath(filename)) + directory = os.path.normcase(os.path.abspath(directory)) + return os.path.commonprefix([filename, directory]) == directory + + +def _build_backend(): + """Find and load the build backend""" + # Add in-tree backend directories to the front of sys.path. + backend_path = os.environ.get('PEP517_BACKEND_PATH') + if backend_path: + extra_pathitems = backend_path.split(os.pathsep) + sys.path[:0] = extra_pathitems + + ep = os.environ['PEP517_BUILD_BACKEND'] + mod_path, _, obj_path = ep.partition(':') + try: + obj = import_module(mod_path) + except ImportError: + raise BackendUnavailable(traceback.format_exc()) + + if backend_path: + if not any( + contained_in(obj.__file__, path) + for path in extra_pathitems + ): + raise BackendInvalid("Backend was not loaded from backend-path") + + if obj_path: + for path_part in obj_path.split('.'): + obj = getattr(obj, path_part) + return obj + + +def get_requires_for_build_wheel(config_settings): + """Invoke the optional get_requires_for_build_wheel hook + + Returns [] if the hook is not defined. + """ + backend = _build_backend() + try: + hook = backend.get_requires_for_build_wheel + except AttributeError: + return [] + else: + return hook(config_settings) + + +def get_requires_for_build_editable(config_settings): + """Invoke the optional get_requires_for_build_editable hook + + Returns [] if the hook is not defined. + """ + backend = _build_backend() + try: + hook = backend.get_requires_for_build_editable + except AttributeError: + return [] + else: + return hook(config_settings) + + +def prepare_metadata_for_build_wheel( + metadata_directory, config_settings, _allow_fallback): + """Invoke optional prepare_metadata_for_build_wheel + + Implements a fallback by building a wheel if the hook isn't defined, + unless _allow_fallback is False in which case HookMissing is raised. + """ + backend = _build_backend() + try: + hook = backend.prepare_metadata_for_build_wheel + except AttributeError: + if not _allow_fallback: + raise HookMissing() + whl_basename = backend.build_wheel(metadata_directory, config_settings) + return _get_wheel_metadata_from_wheel(whl_basename, metadata_directory, + config_settings) + else: + return hook(metadata_directory, config_settings) + + +def prepare_metadata_for_build_editable( + metadata_directory, config_settings, _allow_fallback): + """Invoke optional prepare_metadata_for_build_editable + + Implements a fallback by building an editable wheel if the hook isn't + defined, unless _allow_fallback is False in which case HookMissing is + raised. + """ + backend = _build_backend() + try: + hook = backend.prepare_metadata_for_build_editable + except AttributeError: + if not _allow_fallback: + raise HookMissing() + try: + build_hook = backend.build_editable + except AttributeError: + raise HookMissing(hook_name='build_editable') + else: + whl_basename = build_hook(metadata_directory, config_settings) + return _get_wheel_metadata_from_wheel(whl_basename, + metadata_directory, + config_settings) + else: + return hook(metadata_directory, config_settings) + + +WHEEL_BUILT_MARKER = 'PEP517_ALREADY_BUILT_WHEEL' + + +def _dist_info_files(whl_zip): + """Identify the .dist-info folder inside a wheel ZipFile.""" + res = [] + for path in whl_zip.namelist(): + m = re.match(r'[^/\\]+-[^/\\]+\.dist-info/', path) + if m: + res.append(path) + if res: + return res + raise Exception("No .dist-info folder found in wheel") + + +def _get_wheel_metadata_from_wheel( + whl_basename, metadata_directory, config_settings): + """Extract the metadata from a wheel. + + Fallback for when the build backend does not + define the 'get_wheel_metadata' hook. + """ + from zipfile import ZipFile + with open(os.path.join(metadata_directory, WHEEL_BUILT_MARKER), 'wb'): + pass # Touch marker file + + whl_file = os.path.join(metadata_directory, whl_basename) + with ZipFile(whl_file) as zipf: + dist_info = _dist_info_files(zipf) + zipf.extractall(path=metadata_directory, members=dist_info) + return dist_info[0].split('/')[0] + + +def _find_already_built_wheel(metadata_directory): + """Check for a wheel already built during the get_wheel_metadata hook. + """ + if not metadata_directory: + return None + metadata_parent = os.path.dirname(metadata_directory) + if not os.path.isfile(pjoin(metadata_parent, WHEEL_BUILT_MARKER)): + return None + + whl_files = glob(os.path.join(metadata_parent, '*.whl')) + if not whl_files: + print('Found wheel built marker, but no .whl files') + return None + if len(whl_files) > 1: + print('Found multiple .whl files; unspecified behaviour. ' + 'Will call build_wheel.') + return None + + # Exactly one .whl file + return whl_files[0] + + +def build_wheel(wheel_directory, config_settings, metadata_directory=None): + """Invoke the mandatory build_wheel hook. + + If a wheel was already built in the + prepare_metadata_for_build_wheel fallback, this + will copy it rather than rebuilding the wheel. + """ + prebuilt_whl = _find_already_built_wheel(metadata_directory) + if prebuilt_whl: + shutil.copy2(prebuilt_whl, wheel_directory) + return os.path.basename(prebuilt_whl) + + return _build_backend().build_wheel(wheel_directory, config_settings, + metadata_directory) + + +def build_editable(wheel_directory, config_settings, metadata_directory=None): + """Invoke the optional build_editable hook. + + If a wheel was already built in the + prepare_metadata_for_build_editable fallback, this + will copy it rather than rebuilding the wheel. + """ + backend = _build_backend() + try: + hook = backend.build_editable + except AttributeError: + raise HookMissing() + else: + prebuilt_whl = _find_already_built_wheel(metadata_directory) + if prebuilt_whl: + shutil.copy2(prebuilt_whl, wheel_directory) + return os.path.basename(prebuilt_whl) + + return hook(wheel_directory, config_settings, metadata_directory) + + +def get_requires_for_build_sdist(config_settings): + """Invoke the optional get_requires_for_build_wheel hook + + Returns [] if the hook is not defined. + """ + backend = _build_backend() + try: + hook = backend.get_requires_for_build_sdist + except AttributeError: + return [] + else: + return hook(config_settings) + + +class _DummyException(Exception): + """Nothing should ever raise this exception""" + + +class GotUnsupportedOperation(Exception): + """For internal use when backend raises UnsupportedOperation""" + def __init__(self, traceback): + self.traceback = traceback + + +def build_sdist(sdist_directory, config_settings): + """Invoke the mandatory build_sdist hook.""" + backend = _build_backend() + try: + return backend.build_sdist(sdist_directory, config_settings) + except getattr(backend, 'UnsupportedOperation', _DummyException): + raise GotUnsupportedOperation(traceback.format_exc()) + + +HOOK_NAMES = { + 'get_requires_for_build_wheel', + 'prepare_metadata_for_build_wheel', + 'build_wheel', + 'get_requires_for_build_editable', + 'prepare_metadata_for_build_editable', + 'build_editable', + 'get_requires_for_build_sdist', + 'build_sdist', +} + + +def main(): + if len(sys.argv) < 3: + sys.exit("Needs args: hook_name, control_dir") + hook_name = sys.argv[1] + control_dir = sys.argv[2] + if hook_name not in HOOK_NAMES: + sys.exit("Unknown hook: %s" % hook_name) + hook = globals()[hook_name] + + hook_input = read_json(pjoin(control_dir, 'input.json')) + + json_out = {'unsupported': False, 'return_val': None} + try: + json_out['return_val'] = hook(**hook_input['kwargs']) + except BackendUnavailable as e: + json_out['no_backend'] = True + json_out['traceback'] = e.traceback + except BackendInvalid as e: + json_out['backend_invalid'] = True + json_out['backend_error'] = e.message + except GotUnsupportedOperation as e: + json_out['unsupported'] = True + json_out['traceback'] = e.traceback + except HookMissing as e: + json_out['hook_missing'] = True + json_out['missing_hook_name'] = e.hook_name or hook_name + + write_json(json_out, pjoin(control_dir, 'output.json'), indent=2) + + +if __name__ == '__main__': + main() diff --git a/pipenv/patched/notpip/_vendor/pep517/meta.py b/pipenv/patched/notpip/_vendor/pep517/meta.py new file mode 100644 index 00000000..5f7d9485 --- /dev/null +++ b/pipenv/patched/notpip/_vendor/pep517/meta.py @@ -0,0 +1,92 @@ +"""Build metadata for a project using PEP 517 hooks. +""" +import argparse +import logging +import os +import shutil +import functools + +try: + import importlib.metadata as imp_meta +except ImportError: + import pipenv.vendor.importlib_metadata as imp_meta + +try: + from zipfile import Path +except ImportError: + from pipenv.vendor.zipp import Path + +from .envbuild import BuildEnvironment +from .wrappers import Pep517HookCaller, quiet_subprocess_runner +from .dirtools import tempdir, mkdir_p, dir_to_zipfile +from .build import validate_system, load_system, compat_system + +log = logging.getLogger(__name__) + + +def _prep_meta(hooks, env, dest): + reqs = hooks.get_requires_for_build_wheel({}) + log.info('Got build requires: %s', reqs) + + env.pip_install(reqs) + log.info('Installed dynamic build dependencies') + + with tempdir() as td: + log.info('Trying to build metadata in %s', td) + filename = hooks.prepare_metadata_for_build_wheel(td, {}) + source = os.path.join(td, filename) + shutil.move(source, os.path.join(dest, os.path.basename(filename))) + + +def build(source_dir='.', dest=None, system=None): + system = system or load_system(source_dir) + dest = os.path.join(source_dir, dest or 'dist') + mkdir_p(dest) + validate_system(system) + hooks = Pep517HookCaller( + source_dir, system['build-backend'], system.get('backend-path') + ) + + with hooks.subprocess_runner(quiet_subprocess_runner): + with BuildEnvironment() as env: + env.pip_install(system['requires']) + _prep_meta(hooks, env, dest) + + +def build_as_zip(builder=build): + with tempdir() as out_dir: + builder(dest=out_dir) + return dir_to_zipfile(out_dir) + + +def load(root): + """ + Given a source directory (root) of a package, + return an importlib.metadata.Distribution object + with metadata build from that package. + """ + root = os.path.expanduser(root) + system = compat_system(root) + builder = functools.partial(build, source_dir=root, system=system) + path = Path(build_as_zip(builder)) + return imp_meta.PathDistribution(path) + + +parser = argparse.ArgumentParser() +parser.add_argument( + 'source_dir', + help="A directory containing pyproject.toml", +) +parser.add_argument( + '--out-dir', '-o', + help="Destination in which to save the builds relative to source dir", +) + + +def main(): + args = parser.parse_args() + build(args.source_dir, args.out_dir) + + +if __name__ == '__main__': + main() diff --git a/pipenv/patched/notpip/_vendor/pep517/wrappers.py b/pipenv/patched/notpip/_vendor/pep517/wrappers.py index b14b8991..52da22e8 100644 --- a/pipenv/patched/notpip/_vendor/pep517/wrappers.py +++ b/pipenv/patched/notpip/_vendor/pep517/wrappers.py @@ -1,14 +1,24 @@ +import threading from contextlib import contextmanager import os -from os.path import dirname, abspath, join as pjoin +from os.path import abspath, join as pjoin import shutil -from subprocess import check_call +from subprocess import check_call, check_output, STDOUT import sys from tempfile import mkdtemp from . import compat +from .in_process import _in_proc_script_path -_in_proc_script = pjoin(dirname(abspath(__file__)), '_in_process.py') +__all__ = [ + 'BackendUnavailable', + 'BackendInvalid', + 'HookMissing', + 'UnsupportedOperation', + 'default_subprocess_runner', + 'quiet_subprocess_runner', + 'Pep517HookCaller', +] @contextmanager @@ -22,10 +32,29 @@ def tempdir(): class BackendUnavailable(Exception): """Will be raised if the backend cannot be imported in the hook process.""" + def __init__(self, traceback): + self.traceback = traceback + + +class BackendInvalid(Exception): + """Will be raised if the backend is invalid.""" + def __init__(self, backend_name, backend_path, message): + self.backend_name = backend_name + self.backend_path = backend_path + self.message = message + + +class HookMissing(Exception): + """Will be raised on missing hooks.""" + def __init__(self, hook_name): + super(HookMissing, self).__init__(hook_name) + self.hook_name = hook_name class UnsupportedOperation(Exception): """May be raised by build_sdist if the backend indicates that it can't.""" + def __init__(self, traceback): + self.traceback = traceback def default_subprocess_runner(cmd, cwd=None, extra_environ=None): @@ -37,30 +66,99 @@ def default_subprocess_runner(cmd, cwd=None, extra_environ=None): check_call(cmd, cwd=cwd, env=env) +def quiet_subprocess_runner(cmd, cwd=None, extra_environ=None): + """A method of calling the wrapper subprocess while suppressing output.""" + env = os.environ.copy() + if extra_environ: + env.update(extra_environ) + + check_output(cmd, cwd=cwd, env=env, stderr=STDOUT) + + +def norm_and_check(source_tree, requested): + """Normalise and check a backend path. + + Ensure that the requested backend path is specified as a relative path, + and resolves to a location under the given source tree. + + Return an absolute version of the requested path. + """ + if os.path.isabs(requested): + raise ValueError("paths must be relative") + + abs_source = os.path.abspath(source_tree) + abs_requested = os.path.normpath(os.path.join(abs_source, requested)) + # We have to use commonprefix for Python 2.7 compatibility. So we + # normalise case to avoid problems because commonprefix is a character + # based comparison :-( + norm_source = os.path.normcase(abs_source) + norm_requested = os.path.normcase(abs_requested) + if os.path.commonprefix([norm_source, norm_requested]) != norm_source: + raise ValueError("paths must be inside source tree") + + return abs_requested + + class Pep517HookCaller(object): """A wrapper around a source directory to be built with a PEP 517 backend. - source_dir : The path to the source directory, containing pyproject.toml. - backend : The build backend spec, as per PEP 517, from pyproject.toml. + :param source_dir: The path to the source directory, containing + pyproject.toml. + :param build_backend: The build backend spec, as per PEP 517, from + pyproject.toml. + :param backend_path: The backend path, as per PEP 517, from pyproject.toml. + :param runner: A callable that invokes the wrapper subprocess. + :param python_executable: The Python executable used to invoke the backend + + The 'runner', if provided, must expect the following: + + - cmd: a list of strings representing the command and arguments to + execute, as would be passed to e.g. 'subprocess.check_call'. + - cwd: a string representing the working directory that must be + used for the subprocess. Corresponds to the provided source_dir. + - extra_environ: a dict mapping environment variable names to values + which must be set for the subprocess execution. """ - def __init__(self, source_dir, build_backend): + def __init__( + self, + source_dir, + build_backend, + backend_path=None, + runner=None, + python_executable=None, + ): + if runner is None: + runner = default_subprocess_runner + self.source_dir = abspath(source_dir) self.build_backend = build_backend - self._subprocess_runner = default_subprocess_runner + if backend_path: + backend_path = [ + norm_and_check(self.source_dir, p) for p in backend_path + ] + self.backend_path = backend_path + self._subprocess_runner = runner + if not python_executable: + python_executable = sys.executable + self.python_executable = python_executable - # TODO: Is this over-engineered? Maybe frontends only need to - # set this when creating the wrapper, not on every call. @contextmanager def subprocess_runner(self, runner): + """A context manager for temporarily overriding the default subprocess + runner. + """ prev = self._subprocess_runner self._subprocess_runner = runner - yield - self._subprocess_runner = prev + try: + yield + finally: + self._subprocess_runner = prev def get_requires_for_build_wheel(self, config_settings=None): """Identify packages required for building a wheel - Returns a list of dependency specifications, e.g.: + Returns a list of dependency specifications, e.g.:: + ["wheel >= 0.25", "setuptools"] This does not include requirements specified in pyproject.toml. @@ -72,18 +170,21 @@ class Pep517HookCaller(object): }) def prepare_metadata_for_build_wheel( - self, metadata_directory, config_settings=None): - """Prepare a *.dist-info folder with metadata for this project. + self, metadata_directory, config_settings=None, + _allow_fallback=True): + """Prepare a ``*.dist-info`` folder with metadata for this project. Returns the name of the newly created folder. If the build backend defines a hook with this name, it will be called in a subprocess. If not, the backend will be asked to build a wheel, - and the dist-info extracted from that. + and the dist-info extracted from that (unless _allow_fallback is + False). """ return self._call_hook('prepare_metadata_for_build_wheel', { 'metadata_directory': abspath(metadata_directory), 'config_settings': config_settings, + '_allow_fallback': _allow_fallback, }) def build_wheel( @@ -106,10 +207,64 @@ class Pep517HookCaller(object): 'metadata_directory': metadata_directory, }) + def get_requires_for_build_editable(self, config_settings=None): + """Identify packages required for building an editable wheel + + Returns a list of dependency specifications, e.g.:: + + ["wheel >= 0.25", "setuptools"] + + This does not include requirements specified in pyproject.toml. + It returns the result of calling the equivalently named hook in a + subprocess. + """ + return self._call_hook('get_requires_for_build_editable', { + 'config_settings': config_settings + }) + + def prepare_metadata_for_build_editable( + self, metadata_directory, config_settings=None, + _allow_fallback=True): + """Prepare a ``*.dist-info`` folder with metadata for this project. + + Returns the name of the newly created folder. + + If the build backend defines a hook with this name, it will be called + in a subprocess. If not, the backend will be asked to build an editable + wheel, and the dist-info extracted from that (unless _allow_fallback is + False). + """ + return self._call_hook('prepare_metadata_for_build_editable', { + 'metadata_directory': abspath(metadata_directory), + 'config_settings': config_settings, + '_allow_fallback': _allow_fallback, + }) + + def build_editable( + self, wheel_directory, config_settings=None, + metadata_directory=None): + """Build an editable wheel from this project. + + Returns the name of the newly created file. + + In general, this will call the 'build_editable' hook in the backend. + However, if that was previously called by + 'prepare_metadata_for_build_editable', and the same metadata_directory + is used, the previously built wheel will be copied to wheel_directory. + """ + if metadata_directory is not None: + metadata_directory = abspath(metadata_directory) + return self._call_hook('build_editable', { + 'wheel_directory': abspath(wheel_directory), + 'config_settings': config_settings, + 'metadata_directory': metadata_directory, + }) + def get_requires_for_build_sdist(self, config_settings=None): """Identify packages required for building a wheel - Returns a list of dependency specifications, e.g.: + Returns a list of dependency specifications, e.g.:: + ["setuptools >= 26"] This does not include requirements specified in pyproject.toml. @@ -139,25 +294,78 @@ class Pep517HookCaller(object): # letters, digits and _, . and : characters, and will be used as a # Python identifier, so non-ASCII content is wrong on Python 2 in # any case). + # For backend_path, we use sys.getfilesystemencoding. if sys.version_info[0] == 2: build_backend = self.build_backend.encode('ASCII') else: build_backend = self.build_backend + extra_environ = {'PEP517_BUILD_BACKEND': build_backend} + + if self.backend_path: + backend_path = os.pathsep.join(self.backend_path) + if sys.version_info[0] == 2: + backend_path = backend_path.encode(sys.getfilesystemencoding()) + extra_environ['PEP517_BACKEND_PATH'] = backend_path with tempdir() as td: - compat.write_json({'kwargs': kwargs}, pjoin(td, 'input.json'), + hook_input = {'kwargs': kwargs} + compat.write_json(hook_input, pjoin(td, 'input.json'), indent=2) # Run the hook in a subprocess - self._subprocess_runner( - [sys.executable, _in_proc_script, hook_name, td], - cwd=self.source_dir, - extra_environ={'PEP517_BUILD_BACKEND': build_backend} - ) + with _in_proc_script_path() as script: + python = self.python_executable + self._subprocess_runner( + [python, abspath(str(script)), hook_name, td], + cwd=self.source_dir, + extra_environ=extra_environ + ) data = compat.read_json(pjoin(td, 'output.json')) if data.get('unsupported'): - raise UnsupportedOperation + raise UnsupportedOperation(data.get('traceback', '')) if data.get('no_backend'): - raise BackendUnavailable + raise BackendUnavailable(data.get('traceback', '')) + if data.get('backend_invalid'): + raise BackendInvalid( + backend_name=self.build_backend, + backend_path=self.backend_path, + message=data.get('backend_error', '') + ) + if data.get('hook_missing'): + raise HookMissing(data.get('missing_hook_name') or hook_name) return data['return_val'] + + +class LoggerWrapper(threading.Thread): + """ + Read messages from a pipe and redirect them + to a logger (see python's logging module). + """ + + def __init__(self, logger, level): + threading.Thread.__init__(self) + self.daemon = True + + self.logger = logger + self.level = level + + # create the pipe and reader + self.fd_read, self.fd_write = os.pipe() + self.reader = os.fdopen(self.fd_read) + + self.start() + + def fileno(self): + return self.fd_write + + @staticmethod + def remove_newline(msg): + return msg[:-1] if msg.endswith(os.linesep) else msg + + def run(self): + for line in self.reader: + self._write(self.remove_newline(line)) + + def _write(self, message): + self.logger.log(self.level, message) diff --git a/pipenv/patched/notpip/_vendor/pkg_resources/__init__.py b/pipenv/patched/notpip/_vendor/pkg_resources/__init__.py index 0459a7da..b1cb5de7 100644 --- a/pipenv/patched/notpip/_vendor/pkg_resources/__init__.py +++ b/pipenv/patched/notpip/_vendor/pkg_resources/__init__.py @@ -39,6 +39,8 @@ import tempfile import textwrap import itertools import inspect +import ntpath +import posixpath from pkgutil import get_importer try: @@ -86,8 +88,8 @@ __import__('pipenv.patched.notpip._vendor.packaging.markers') __metaclass__ = type -if (3, 0) < sys.version_info < (3, 4): - raise RuntimeError("Python 3.4 or later is required") +if (3, 0) < sys.version_info < (3, 5): + raise RuntimeError("Python 3.5 or later is required") if six.PY2: # Those builtin exceptions are only defined in Python 3 @@ -331,7 +333,7 @@ class UnknownExtra(ResolutionError): _provider_factories = {} -PY_MAJOR = sys.version[:3] +PY_MAJOR = '{}.{}'.format(*sys.version_info) EGG_DIST = 3 BINARY_DIST = 2 SOURCE_DIST = 1 @@ -1401,14 +1403,30 @@ class NullProvider: def has_resource(self, resource_name): return self._has(self._fn(self.module_path, resource_name)) + def _get_metadata_path(self, name): + return self._fn(self.egg_info, name) + def has_metadata(self, name): - return self.egg_info and self._has(self._fn(self.egg_info, name)) + if not self.egg_info: + return self.egg_info + + path = self._get_metadata_path(name) + return self._has(path) def get_metadata(self, name): if not self.egg_info: return "" - value = self._get(self._fn(self.egg_info, name)) - return value.decode('utf-8') if six.PY3 else value + path = self._get_metadata_path(name) + value = self._get(path) + if six.PY2: + return value + try: + return value.decode('utf-8') + except UnicodeDecodeError as exc: + # Include the path in the error message to simplify + # troubleshooting, and without changing the exception type. + exc.reason += ' in {} file at path: {}'.format(name, path) + raise def get_metadata_lines(self, name): return yield_lines(self.get_metadata(name)) @@ -1466,10 +1484,86 @@ class NullProvider: ) def _fn(self, base, resource_name): + self._validate_resource_path(resource_name) if resource_name: return os.path.join(base, *resource_name.split('/')) return base + @staticmethod + def _validate_resource_path(path): + """ + Validate the resource paths according to the docs. + https://setuptools.readthedocs.io/en/latest/pkg_resources.html#basic-resource-access + + >>> warned = getfixture('recwarn') + >>> warnings.simplefilter('always') + >>> vrp = NullProvider._validate_resource_path + >>> vrp('foo/bar.txt') + >>> bool(warned) + False + >>> vrp('../foo/bar.txt') + >>> bool(warned) + True + >>> warned.clear() + >>> vrp('/foo/bar.txt') + >>> bool(warned) + True + >>> vrp('foo/../../bar.txt') + >>> bool(warned) + True + >>> warned.clear() + >>> vrp('foo/f../bar.txt') + >>> bool(warned) + False + + Windows path separators are straight-up disallowed. + >>> vrp(r'\\foo/bar.txt') + Traceback (most recent call last): + ... + ValueError: Use of .. or absolute path in a resource path \ +is not allowed. + + >>> vrp(r'C:\\foo/bar.txt') + Traceback (most recent call last): + ... + ValueError: Use of .. or absolute path in a resource path \ +is not allowed. + + Blank values are allowed + + >>> vrp('') + >>> bool(warned) + False + + Non-string values are not. + + >>> vrp(None) + Traceback (most recent call last): + ... + AttributeError: ... + """ + invalid = ( + os.path.pardir in path.split(posixpath.sep) or + posixpath.isabs(path) or + ntpath.isabs(path) + ) + if not invalid: + return + + msg = "Use of .. or absolute path in a resource path is not allowed." + + # Aggressively disallow Windows absolute paths + if ntpath.isabs(path) and not posixpath.isabs(path): + raise ValueError(msg) + + # for compatibility, warn; in future + # raise ValueError(msg) + warnings.warn( + msg[:-1] + " and will raise exceptions in a future release.", + DeprecationWarning, + stacklevel=4, + ) + def _get(self, path): if hasattr(self.loader, 'get_data'): return self.loader.get_data(path) @@ -1790,6 +1884,9 @@ class FileMetadata(EmptyProvider): def __init__(self, path): self.path = path + def _get_metadata_path(self, name): + return self.path + def has_metadata(self, name): return name == 'PKG-INFO' and os.path.isfile(self.path) @@ -1888,7 +1985,7 @@ def find_eggs_in_zip(importer, path_item, only=False): if only: # don't yield nested distros return - for subitem in metadata.resource_listdir('/'): + for subitem in metadata.resource_listdir(''): if _is_egg_path(subitem): subpath = os.path.join(path_item, subitem) dists = find_eggs_in_zip(zipimport.zipimporter(subpath), subpath) @@ -2583,10 +2680,14 @@ class Distribution: try: return self._version except AttributeError: - version = _version_from_file(self._get_metadata(self.PKG_INFO)) + version = self._get_version() if version is None: - tmpl = "Missing 'Version:' header and/or %s file" - raise ValueError(tmpl % self.PKG_INFO, self) + path = self._get_metadata_path_for_display(self.PKG_INFO) + msg = ( + "Missing 'Version:' header and/or {} file at path: {}" + ).format(self.PKG_INFO, path) + raise ValueError(msg, self) + return version @property @@ -2644,11 +2745,34 @@ class Distribution: ) return deps + def _get_metadata_path_for_display(self, name): + """ + Return the path to the given metadata file, if available. + """ + try: + # We need to access _get_metadata_path() on the provider object + # directly rather than through this class's __getattr__() + # since _get_metadata_path() is marked private. + path = self._provider._get_metadata_path(name) + + # Handle exceptions e.g. in case the distribution's metadata + # provider doesn't support _get_metadata_path(). + except Exception: + return '[could not detect]' + + return path + def _get_metadata(self, name): if self.has_metadata(name): for line in self.get_metadata_lines(name): yield line + def _get_version(self): + lines = self._get_metadata(self.PKG_INFO) + version = _version_from_file(lines) + + return version + def activate(self, path=None, replace=False): """Ensure distribution is importable on `path` (default=sys.path)""" if path is None: @@ -2867,7 +2991,7 @@ class EggInfoDistribution(Distribution): take an extra step and try to get the version number from the metadata file itself instead of the filename. """ - md_version = _version_from_file(self._get_metadata(self.PKG_INFO)) + md_version = self._get_version() if md_version: self._version = md_version return self @@ -2985,6 +3109,7 @@ class Requirement(packaging.requirements.Requirement): self.extras = tuple(map(safe_extra, self.extras)) self.hashCmp = ( self.key, + self.url, self.specifier, frozenset(self.extras), str(self.marker) if self.marker else None, diff --git a/pipenv/patched/notpip/_vendor/progress/__init__.py b/pipenv/patched/notpip/_vendor/progress/__init__.py index a41f65dc..e434c257 100644 --- a/pipenv/patched/notpip/_vendor/progress/__init__.py +++ b/pipenv/patched/notpip/_vendor/progress/__init__.py @@ -12,31 +12,49 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -from __future__ import division +from __future__ import division, print_function from collections import deque from datetime import timedelta from math import ceil from sys import stderr -from time import time +try: + from time import monotonic +except ImportError: + from time import time as monotonic -__version__ = '1.4' +__version__ = '1.5' + +HIDE_CURSOR = '\x1b[?25l' +SHOW_CURSOR = '\x1b[?25h' class Infinite(object): file = stderr sma_window = 10 # Simple Moving Average window + check_tty = True + hide_cursor = True - def __init__(self, *args, **kwargs): + def __init__(self, message='', **kwargs): self.index = 0 - self.start_ts = time() + self.start_ts = monotonic() self.avg = 0 + self._avg_update_ts = self.start_ts self._ts = self.start_ts self._xput = deque(maxlen=self.sma_window) for key, val in kwargs.items(): setattr(self, key, val) + self._width = 0 + self.message = message + + if self.file and self.is_tty(): + if self.hide_cursor: + print(HIDE_CURSOR, end='', file=self.file) + print(self.message, end='', file=self.file) + self.file.flush() + def __getitem__(self, key): if key.startswith('_'): return None @@ -44,7 +62,7 @@ class Infinite(object): @property def elapsed(self): - return int(time() - self.start_ts) + return int(monotonic() - self.start_ts) @property def elapsed_td(self): @@ -52,8 +70,14 @@ class Infinite(object): def update_avg(self, n, dt): if n > 0: + xput_len = len(self._xput) self._xput.append(dt / n) - self.avg = sum(self._xput) / len(self._xput) + now = monotonic() + # update when we're still filling _xput, then after every second + if (xput_len < self.sma_window or + now - self._avg_update_ts > 1): + self.avg = sum(self._xput) / len(self._xput) + self._avg_update_ts = now def update(self): pass @@ -61,11 +85,34 @@ class Infinite(object): def start(self): pass + def clearln(self): + if self.file and self.is_tty(): + print('\r\x1b[K', end='', file=self.file) + + def write(self, s): + if self.file and self.is_tty(): + line = self.message + s.ljust(self._width) + print('\r' + line, end='', file=self.file) + self._width = max(self._width, len(s)) + self.file.flush() + + def writeln(self, line): + if self.file and self.is_tty(): + self.clearln() + print(line, end='', file=self.file) + self.file.flush() + def finish(self): - pass + if self.file and self.is_tty(): + print(file=self.file) + if self.hide_cursor: + print(SHOW_CURSOR, end='', file=self.file) + + def is_tty(self): + return self.file.isatty() if self.check_tty else True def next(self, n=1): - now = time() + now = monotonic() dt = now - self._ts self.update_avg(n, dt) self._ts = now @@ -73,12 +120,17 @@ class Infinite(object): self.update() def iter(self, it): - try: + with self: for x in it: yield x self.next() - finally: - self.finish() + + def __enter__(self): + self.start() + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.finish() class Progress(Infinite): @@ -119,9 +171,7 @@ class Progress(Infinite): except TypeError: pass - try: + with self: for x in it: yield x self.next() - finally: - self.finish() diff --git a/pipenv/patched/notpip/_vendor/progress/bar.py b/pipenv/patched/notpip/_vendor/progress/bar.py index 025e61c4..8819efda 100644 --- a/pipenv/patched/notpip/_vendor/progress/bar.py +++ b/pipenv/patched/notpip/_vendor/progress/bar.py @@ -19,18 +19,15 @@ from __future__ import unicode_literals import sys from . import Progress -from .helpers import WritelnMixin -class Bar(WritelnMixin, Progress): +class Bar(Progress): width = 32 - message = '' suffix = '%(index)d/%(max)d' bar_prefix = ' |' bar_suffix = '| ' empty_fill = ' ' fill = '#' - hide_cursor = True def update(self): filled_length = int(self.width * self.progress) diff --git a/pipenv/patched/notpip/_vendor/progress/counter.py b/pipenv/patched/notpip/_vendor/progress/counter.py index 6b45a1ec..d955ca47 100644 --- a/pipenv/patched/notpip/_vendor/progress/counter.py +++ b/pipenv/patched/notpip/_vendor/progress/counter.py @@ -16,27 +16,20 @@ from __future__ import unicode_literals from . import Infinite, Progress -from .helpers import WriteMixin -class Counter(WriteMixin, Infinite): - message = '' - hide_cursor = True - +class Counter(Infinite): def update(self): self.write(str(self.index)) -class Countdown(WriteMixin, Progress): - hide_cursor = True - +class Countdown(Progress): def update(self): self.write(str(self.remaining)) -class Stack(WriteMixin, Progress): +class Stack(Progress): phases = (' ', '▁', '▂', '▃', '▄', '▅', '▆', '▇', '█') - hide_cursor = True def update(self): nphases = len(self.phases) diff --git a/pipenv/patched/notpip/_vendor/progress/helpers.py b/pipenv/patched/notpip/_vendor/progress/helpers.py deleted file mode 100644 index 0cde44ec..00000000 --- a/pipenv/patched/notpip/_vendor/progress/helpers.py +++ /dev/null @@ -1,91 +0,0 @@ -# Copyright (c) 2012 Giorgos Verigakis <verigak@gmail.com> -# -# Permission to use, copy, modify, and distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -from __future__ import print_function - - -HIDE_CURSOR = '\x1b[?25l' -SHOW_CURSOR = '\x1b[?25h' - - -class WriteMixin(object): - hide_cursor = False - - def __init__(self, message=None, **kwargs): - super(WriteMixin, self).__init__(**kwargs) - self._width = 0 - if message: - self.message = message - - if self.file and self.file.isatty(): - if self.hide_cursor: - print(HIDE_CURSOR, end='', file=self.file) - print(self.message, end='', file=self.file) - self.file.flush() - - def write(self, s): - if self.file and self.file.isatty(): - b = '\b' * self._width - c = s.ljust(self._width) - print(b + c, end='', file=self.file) - self._width = max(self._width, len(s)) - self.file.flush() - - def finish(self): - if self.file and self.file.isatty() and self.hide_cursor: - print(SHOW_CURSOR, end='', file=self.file) - - -class WritelnMixin(object): - hide_cursor = False - - def __init__(self, message=None, **kwargs): - super(WritelnMixin, self).__init__(**kwargs) - if message: - self.message = message - - if self.file and self.file.isatty() and self.hide_cursor: - print(HIDE_CURSOR, end='', file=self.file) - - def clearln(self): - if self.file and self.file.isatty(): - print('\r\x1b[K', end='', file=self.file) - - def writeln(self, line): - if self.file and self.file.isatty(): - self.clearln() - print(line, end='', file=self.file) - self.file.flush() - - def finish(self): - if self.file and self.file.isatty(): - print(file=self.file) - if self.hide_cursor: - print(SHOW_CURSOR, end='', file=self.file) - - -from signal import signal, SIGINT -from sys import exit - - -class SigIntMixin(object): - """Registers a signal handler that calls finish on SIGINT""" - - def __init__(self, *args, **kwargs): - super(SigIntMixin, self).__init__(*args, **kwargs) - signal(SIGINT, self._sigint_handler) - - def _sigint_handler(self, signum, frame): - self.finish() - exit(0) diff --git a/pipenv/patched/notpip/_vendor/progress/spinner.py b/pipenv/patched/notpip/_vendor/progress/spinner.py index 464c7b27..4e100cab 100644 --- a/pipenv/patched/notpip/_vendor/progress/spinner.py +++ b/pipenv/patched/notpip/_vendor/progress/spinner.py @@ -16,11 +16,9 @@ from __future__ import unicode_literals from . import Infinite -from .helpers import WriteMixin -class Spinner(WriteMixin, Infinite): - message = '' +class Spinner(Infinite): phases = ('-', '\\', '|', '/') hide_cursor = True @@ -40,5 +38,6 @@ class MoonSpinner(Spinner): class LineSpinner(Spinner): phases = ['⎺', '⎻', '⎼', '⎽', '⎼', '⎻'] + class PixelSpinner(Spinner): - phases = ['⣾','⣷', '⣯', '⣟', '⡿', '⢿', '⣻', '⣽'] + phases = ['⣾', '⣷', '⣯', '⣟', '⡿', '⢿', '⣻', '⣽'] diff --git a/pipenv/patched/notpip/_vendor/pyparsing.py b/pipenv/patched/notpip/_vendor/pyparsing.py index 3972b370..7ddd7e78 100644 --- a/pipenv/patched/notpip/_vendor/pyparsing.py +++ b/pipenv/patched/notpip/_vendor/pyparsing.py @@ -1,4 +1,4 @@ -#-*- coding: utf-8 -*- +# -*- coding: utf-8 -*- # module pyparsing.py # # Copyright (c) 2003-2019 Paul T. McGuire @@ -87,14 +87,16 @@ classes inherit from. Use the docstrings for examples of how to: more complex ones - associate names with your parsed results using :class:`ParserElement.setResultsName` + - access the parsed data, which is returned as a :class:`ParseResults` + object - find some helpful expression short-cuts like :class:`delimitedList` and :class:`oneOf` - find more useful common expressions in the :class:`pyparsing_common` namespace class """ -__version__ = "2.3.1" -__versionTime__ = "09 Jan 2019 23:26 UTC" +__version__ = "2.4.7" +__versionTime__ = "30 Mar 2020 00:43 UTC" __author__ = "Paul McGuire <ptmcg@users.sourceforge.net>" import string @@ -109,6 +111,10 @@ import pprint import traceback import types from datetime import datetime +from operator import itemgetter +import itertools +from functools import wraps +from contextlib import contextmanager try: # Python 3 @@ -124,11 +130,11 @@ except ImportError: try: # Python 3 from collections.abc import Iterable - from collections.abc import MutableMapping + from collections.abc import MutableMapping, Mapping except ImportError: # Python 2.7 from collections import Iterable - from collections import MutableMapping + from collections import MutableMapping, Mapping try: from collections import OrderedDict as _OrderedDict @@ -143,29 +149,73 @@ try: except ImportError: class SimpleNamespace: pass +# version compatibility configuration +__compat__ = SimpleNamespace() +__compat__.__doc__ = """ + A cross-version compatibility configuration for pyparsing features that will be + released in a future version. By setting values in this configuration to True, + those features can be enabled in prior versions for compatibility development + and testing. -#~ sys.stderr.write( "testing pyparsing module, version %s, %s\n" % (__version__,__versionTime__ ) ) + - collect_all_And_tokens - flag to enable fix for Issue #63 that fixes erroneous grouping + of results names when an And expression is nested within an Or or MatchFirst; set to + True to enable bugfix released in pyparsing 2.3.0, or False to preserve + pre-2.3.0 handling of named results +""" +__compat__.collect_all_And_tokens = True -__all__ = [ -'And', 'CaselessKeyword', 'CaselessLiteral', 'CharsNotIn', 'Combine', 'Dict', 'Each', 'Empty', -'FollowedBy', 'Forward', 'GoToColumn', 'Group', 'Keyword', 'LineEnd', 'LineStart', 'Literal', -'PrecededBy', 'MatchFirst', 'NoMatch', 'NotAny', 'OneOrMore', 'OnlyOnce', 'Optional', 'Or', -'ParseBaseException', 'ParseElementEnhance', 'ParseException', 'ParseExpression', 'ParseFatalException', -'ParseResults', 'ParseSyntaxException', 'ParserElement', 'QuotedString', 'RecursiveGrammarException', -'Regex', 'SkipTo', 'StringEnd', 'StringStart', 'Suppress', 'Token', 'TokenConverter', -'White', 'Word', 'WordEnd', 'WordStart', 'ZeroOrMore', 'Char', -'alphanums', 'alphas', 'alphas8bit', 'anyCloseTag', 'anyOpenTag', 'cStyleComment', 'col', -'commaSeparatedList', 'commonHTMLEntity', 'countedArray', 'cppStyleComment', 'dblQuotedString', -'dblSlashComment', 'delimitedList', 'dictOf', 'downcaseTokens', 'empty', 'hexnums', -'htmlComment', 'javaStyleComment', 'line', 'lineEnd', 'lineStart', 'lineno', -'makeHTMLTags', 'makeXMLTags', 'matchOnlyAtCol', 'matchPreviousExpr', 'matchPreviousLiteral', -'nestedExpr', 'nullDebugAction', 'nums', 'oneOf', 'opAssoc', 'operatorPrecedence', 'printables', -'punc8bit', 'pythonStyleComment', 'quotedString', 'removeQuotes', 'replaceHTMLEntity', -'replaceWith', 'restOfLine', 'sglQuotedString', 'srange', 'stringEnd', -'stringStart', 'traceParseAction', 'unicodeString', 'upcaseTokens', 'withAttribute', -'indentedBlock', 'originalTextFor', 'ungroup', 'infixNotation','locatedExpr', 'withClass', -'CloseMatch', 'tokenMap', 'pyparsing_common', 'pyparsing_unicode', 'unicode_set', -] +__diag__ = SimpleNamespace() +__diag__.__doc__ = """ +Diagnostic configuration (all default to False) + - warn_multiple_tokens_in_named_alternation - flag to enable warnings when a results + name is defined on a MatchFirst or Or expression with one or more And subexpressions + (only warns if __compat__.collect_all_And_tokens is False) + - warn_ungrouped_named_tokens_in_collection - flag to enable warnings when a results + name is defined on a containing expression with ungrouped subexpressions that also + have results names + - warn_name_set_on_empty_Forward - flag to enable warnings whan a Forward is defined + with a results name, but has no contents defined + - warn_on_multiple_string_args_to_oneof - flag to enable warnings whan oneOf is + incorrectly called with multiple str arguments + - enable_debug_on_named_expressions - flag to auto-enable debug on all subsequent + calls to ParserElement.setName() +""" +__diag__.warn_multiple_tokens_in_named_alternation = False +__diag__.warn_ungrouped_named_tokens_in_collection = False +__diag__.warn_name_set_on_empty_Forward = False +__diag__.warn_on_multiple_string_args_to_oneof = False +__diag__.enable_debug_on_named_expressions = False +__diag__._all_names = [nm for nm in vars(__diag__) if nm.startswith("enable_") or nm.startswith("warn_")] + +def _enable_all_warnings(): + __diag__.warn_multiple_tokens_in_named_alternation = True + __diag__.warn_ungrouped_named_tokens_in_collection = True + __diag__.warn_name_set_on_empty_Forward = True + __diag__.warn_on_multiple_string_args_to_oneof = True +__diag__.enable_all_warnings = _enable_all_warnings + + +__all__ = ['__version__', '__versionTime__', '__author__', '__compat__', '__diag__', + 'And', 'CaselessKeyword', 'CaselessLiteral', 'CharsNotIn', 'Combine', 'Dict', 'Each', 'Empty', + 'FollowedBy', 'Forward', 'GoToColumn', 'Group', 'Keyword', 'LineEnd', 'LineStart', 'Literal', + 'PrecededBy', 'MatchFirst', 'NoMatch', 'NotAny', 'OneOrMore', 'OnlyOnce', 'Optional', 'Or', + 'ParseBaseException', 'ParseElementEnhance', 'ParseException', 'ParseExpression', 'ParseFatalException', + 'ParseResults', 'ParseSyntaxException', 'ParserElement', 'QuotedString', 'RecursiveGrammarException', + 'Regex', 'SkipTo', 'StringEnd', 'StringStart', 'Suppress', 'Token', 'TokenConverter', + 'White', 'Word', 'WordEnd', 'WordStart', 'ZeroOrMore', 'Char', + 'alphanums', 'alphas', 'alphas8bit', 'anyCloseTag', 'anyOpenTag', 'cStyleComment', 'col', + 'commaSeparatedList', 'commonHTMLEntity', 'countedArray', 'cppStyleComment', 'dblQuotedString', + 'dblSlashComment', 'delimitedList', 'dictOf', 'downcaseTokens', 'empty', 'hexnums', + 'htmlComment', 'javaStyleComment', 'line', 'lineEnd', 'lineStart', 'lineno', + 'makeHTMLTags', 'makeXMLTags', 'matchOnlyAtCol', 'matchPreviousExpr', 'matchPreviousLiteral', + 'nestedExpr', 'nullDebugAction', 'nums', 'oneOf', 'opAssoc', 'operatorPrecedence', 'printables', + 'punc8bit', 'pythonStyleComment', 'quotedString', 'removeQuotes', 'replaceHTMLEntity', + 'replaceWith', 'restOfLine', 'sglQuotedString', 'srange', 'stringEnd', + 'stringStart', 'traceParseAction', 'unicodeString', 'upcaseTokens', 'withAttribute', + 'indentedBlock', 'originalTextFor', 'ungroup', 'infixNotation', 'locatedExpr', 'withClass', + 'CloseMatch', 'tokenMap', 'pyparsing_common', 'pyparsing_unicode', 'unicode_set', + 'conditionAsParseAction', 're', + ] system_version = tuple(sys.version_info)[:3] PY_3 = system_version[0] == 3 @@ -190,7 +240,7 @@ else: < returns the unicode object | encodes it with the default encoding | ... >. """ - if isinstance(obj,unicode): + if isinstance(obj, unicode): return obj try: @@ -208,9 +258,10 @@ else: # build list of single arg builtins, tolerant of Python version, that can be used as parse actions singleArgBuiltins = [] import __builtin__ + for fname in "sum len sorted reversed list tuple set any all min max".split(): try: - singleArgBuiltins.append(getattr(__builtin__,fname)) + singleArgBuiltins.append(getattr(__builtin__, fname)) except AttributeError: continue @@ -221,23 +272,36 @@ def _xml_escape(data): # ampersand must be replaced first from_symbols = '&><"\'' - to_symbols = ('&'+s+';' for s in "amp gt lt quot apos".split()) - for from_,to_ in zip(from_symbols, to_symbols): + to_symbols = ('&' + s + ';' for s in "amp gt lt quot apos".split()) + for from_, to_ in zip(from_symbols, to_symbols): data = data.replace(from_, to_) return data -alphas = string.ascii_uppercase + string.ascii_lowercase -nums = "0123456789" -hexnums = nums + "ABCDEFabcdef" -alphanums = alphas + nums -_bslash = chr(92) +alphas = string.ascii_uppercase + string.ascii_lowercase +nums = "0123456789" +hexnums = nums + "ABCDEFabcdef" +alphanums = alphas + nums +_bslash = chr(92) printables = "".join(c for c in string.printable if c not in string.whitespace) + +def conditionAsParseAction(fn, message=None, fatal=False): + msg = message if message is not None else "failed user-defined condition" + exc_type = ParseFatalException if fatal else ParseException + fn = _trim_arity(fn) + + @wraps(fn) + def pa(s, l, t): + if not bool(fn(s, l, t)): + raise exc_type(s, l, msg) + + return pa + class ParseBaseException(Exception): """base exception class for all parsing runtime exceptions""" # Performance tuning: we construct a *lot* of these, so keep this # constructor as small and fast as possible - def __init__( self, pstr, loc=0, msg=None, elem=None ): + def __init__(self, pstr, loc=0, msg=None, elem=None): self.loc = loc if msg is None: self.msg = pstr @@ -256,27 +320,34 @@ class ParseBaseException(Exception): """ return cls(pe.pstr, pe.loc, pe.msg, pe.parserElement) - def __getattr__( self, aname ): + def __getattr__(self, aname): """supported attributes by name are: - lineno - returns the line number of the exception text - col - returns the column number of the exception text - line - returns the line containing the exception text """ - if( aname == "lineno" ): - return lineno( self.loc, self.pstr ) - elif( aname in ("col", "column") ): - return col( self.loc, self.pstr ) - elif( aname == "line" ): - return line( self.loc, self.pstr ) + if aname == "lineno": + return lineno(self.loc, self.pstr) + elif aname in ("col", "column"): + return col(self.loc, self.pstr) + elif aname == "line": + return line(self.loc, self.pstr) else: raise AttributeError(aname) - def __str__( self ): - return "%s (at char %d), (line:%d, col:%d)" % \ - ( self.msg, self.loc, self.lineno, self.column ) - def __repr__( self ): + def __str__(self): + if self.pstr: + if self.loc >= len(self.pstr): + foundstr = ', found end of text' + else: + foundstr = (', found %r' % self.pstr[self.loc:self.loc + 1]).replace(r'\\', '\\') + else: + foundstr = '' + return ("%s%s (at char %d), (line:%d, col:%d)" % + (self.msg, foundstr, self.loc, self.lineno, self.column)) + def __repr__(self): return _ustr(self) - def markInputline( self, markerString = ">!<" ): + def markInputline(self, markerString=">!<"): """Extracts the exception line from the input string, and marks the location of the exception with a special symbol. """ @@ -350,7 +421,7 @@ class ParseException(ParseBaseException): callers = inspect.getinnerframes(exc.__traceback__, context=depth) seen = set() for i, ff in enumerate(callers[-depth:]): - frm = ff.frame + frm = ff[0] f_self = frm.f_locals.get('self', None) if isinstance(f_self, ParserElement): @@ -412,21 +483,21 @@ class RecursiveGrammarException(Exception): """exception thrown by :class:`ParserElement.validate` if the grammar could be improperly recursive """ - def __init__( self, parseElementList ): + def __init__(self, parseElementList): self.parseElementTrace = parseElementList - def __str__( self ): + def __str__(self): return "RecursiveGrammarException: %s" % self.parseElementTrace class _ParseResultsWithOffset(object): - def __init__(self,p1,p2): - self.tup = (p1,p2) - def __getitem__(self,i): + def __init__(self, p1, p2): + self.tup = (p1, p2) + def __getitem__(self, i): return self.tup[i] def __repr__(self): return repr(self.tup[0]) - def setOffset(self,i): - self.tup = (self.tup[0],i) + def setOffset(self, i): + self.tup = (self.tup[0], i) class ParseResults(object): """Structured parse results, to provide multiple means of access to @@ -471,7 +542,7 @@ class ParseResults(object): - month: 12 - year: 1999 """ - def __new__(cls, toklist=None, name=None, asList=True, modal=True ): + def __new__(cls, toklist=None, name=None, asList=True, modal=True): if isinstance(toklist, cls): return toklist retobj = object.__new__(cls) @@ -480,7 +551,7 @@ class ParseResults(object): # Performance tuning: we construct a *lot* of these, so keep this # constructor as small and fast as possible - def __init__( self, toklist=None, name=None, asList=True, modal=True, isinstance=isinstance ): + def __init__(self, toklist=None, name=None, asList=True, modal=True, isinstance=isinstance): if self.__doinit: self.__doinit = False self.__name = None @@ -501,85 +572,93 @@ class ParseResults(object): if name is not None and name: if not modal: self.__accumNames[name] = 0 - if isinstance(name,int): - name = _ustr(name) # will always return a str, but use _ustr for consistency + if isinstance(name, int): + name = _ustr(name) # will always return a str, but use _ustr for consistency self.__name = name - if not (isinstance(toklist, (type(None), basestring, list)) and toklist in (None,'',[])): - if isinstance(toklist,basestring): - toklist = [ toklist ] + if not (isinstance(toklist, (type(None), basestring, list)) and toklist in (None, '', [])): + if isinstance(toklist, basestring): + toklist = [toklist] if asList: - if isinstance(toklist,ParseResults): + if isinstance(toklist, ParseResults): self[name] = _ParseResultsWithOffset(ParseResults(toklist.__toklist), 0) else: - self[name] = _ParseResultsWithOffset(ParseResults(toklist[0]),0) + self[name] = _ParseResultsWithOffset(ParseResults(toklist[0]), 0) self[name].__name = name else: try: self[name] = toklist[0] - except (KeyError,TypeError,IndexError): + except (KeyError, TypeError, IndexError): self[name] = toklist - def __getitem__( self, i ): - if isinstance( i, (int,slice) ): + def __getitem__(self, i): + if isinstance(i, (int, slice)): return self.__toklist[i] else: if i not in self.__accumNames: return self.__tokdict[i][-1][0] else: - return ParseResults([ v[0] for v in self.__tokdict[i] ]) + return ParseResults([v[0] for v in self.__tokdict[i]]) - def __setitem__( self, k, v, isinstance=isinstance ): - if isinstance(v,_ParseResultsWithOffset): - self.__tokdict[k] = self.__tokdict.get(k,list()) + [v] + def __setitem__(self, k, v, isinstance=isinstance): + if isinstance(v, _ParseResultsWithOffset): + self.__tokdict[k] = self.__tokdict.get(k, list()) + [v] sub = v[0] - elif isinstance(k,(int,slice)): + elif isinstance(k, (int, slice)): self.__toklist[k] = v sub = v else: - self.__tokdict[k] = self.__tokdict.get(k,list()) + [_ParseResultsWithOffset(v,0)] + self.__tokdict[k] = self.__tokdict.get(k, list()) + [_ParseResultsWithOffset(v, 0)] sub = v - if isinstance(sub,ParseResults): + if isinstance(sub, ParseResults): sub.__parent = wkref(self) - def __delitem__( self, i ): - if isinstance(i,(int,slice)): - mylen = len( self.__toklist ) + def __delitem__(self, i): + if isinstance(i, (int, slice)): + mylen = len(self.__toklist) del self.__toklist[i] # convert int to slice if isinstance(i, int): if i < 0: i += mylen - i = slice(i, i+1) + i = slice(i, i + 1) # get removed indices removed = list(range(*i.indices(mylen))) removed.reverse() # fixup indices in token dictionary - for name,occurrences in self.__tokdict.items(): + for name, occurrences in self.__tokdict.items(): for j in removed: for k, (value, position) in enumerate(occurrences): occurrences[k] = _ParseResultsWithOffset(value, position - (position > j)) else: del self.__tokdict[i] - def __contains__( self, k ): + def __contains__(self, k): return k in self.__tokdict - def __len__( self ): return len( self.__toklist ) - def __bool__(self): return ( not not self.__toklist ) + def __len__(self): + return len(self.__toklist) + + def __bool__(self): + return (not not self.__toklist) __nonzero__ = __bool__ - def __iter__( self ): return iter( self.__toklist ) - def __reversed__( self ): return iter( self.__toklist[::-1] ) - def _iterkeys( self ): + + def __iter__(self): + return iter(self.__toklist) + + def __reversed__(self): + return iter(self.__toklist[::-1]) + + def _iterkeys(self): if hasattr(self.__tokdict, "iterkeys"): return self.__tokdict.iterkeys() else: return iter(self.__tokdict) - def _itervalues( self ): + def _itervalues(self): return (self[k] for k in self._iterkeys()) - def _iteritems( self ): + def _iteritems(self): return ((k, self[k]) for k in self._iterkeys()) if PY_3: @@ -602,24 +681,24 @@ class ParseResults(object): iteritems = _iteritems """Returns an iterator of all named result key-value tuples (Python 2.x only).""" - def keys( self ): + def keys(self): """Returns all named result keys (as a list in Python 2.x, as an iterator in Python 3.x).""" return list(self.iterkeys()) - def values( self ): + def values(self): """Returns all named result values (as a list in Python 2.x, as an iterator in Python 3.x).""" return list(self.itervalues()) - def items( self ): + def items(self): """Returns all named result key-values (as a list of tuples in Python 2.x, as an iterator in Python 3.x).""" return list(self.iteritems()) - def haskeys( self ): + def haskeys(self): """Since keys() returns an iterator, this method is helpful in bypassing code that looks for the existence of any defined results names.""" return bool(self.__tokdict) - def pop( self, *args, **kwargs): + def pop(self, *args, **kwargs): """ Removes and returns item at specified index (default= ``last``). Supports both ``list`` and ``dict`` semantics for ``pop()``. If @@ -658,14 +737,14 @@ class ParseResults(object): """ if not args: args = [-1] - for k,v in kwargs.items(): + for k, v in kwargs.items(): if k == 'default': args = (args[0], v) else: raise TypeError("pop() got an unexpected keyword argument '%s'" % k) - if (isinstance(args[0], int) or - len(args) == 1 or - args[0] in self): + if (isinstance(args[0], int) + or len(args) == 1 + or args[0] in self): index = args[0] ret = self[index] del self[index] @@ -697,7 +776,7 @@ class ParseResults(object): else: return defaultValue - def insert( self, index, insStr ): + def insert(self, index, insStr): """ Inserts new element at location index in the list of parsed tokens. @@ -714,11 +793,11 @@ class ParseResults(object): """ self.__toklist.insert(index, insStr) # fixup indices in token dictionary - for name,occurrences in self.__tokdict.items(): + for name, occurrences in self.__tokdict.items(): for k, (value, position) in enumerate(occurrences): occurrences[k] = _ParseResultsWithOffset(value, position + (position > index)) - def append( self, item ): + def append(self, item): """ Add single element to end of ParseResults list of elements. @@ -733,7 +812,7 @@ class ParseResults(object): """ self.__toklist.append(item) - def extend( self, itemseq ): + def extend(self, itemseq): """ Add sequence of elements to end of ParseResults list of elements. @@ -748,78 +827,70 @@ class ParseResults(object): print(patt.addParseAction(make_palindrome).parseString("lskdj sdlkjf lksd")) # -> 'lskdjsdlkjflksddsklfjkldsjdksl' """ if isinstance(itemseq, ParseResults): - self += itemseq + self.__iadd__(itemseq) else: self.__toklist.extend(itemseq) - def clear( self ): + def clear(self): """ Clear all elements and results names. """ del self.__toklist[:] self.__tokdict.clear() - def __getattr__( self, name ): + def __getattr__(self, name): try: return self[name] except KeyError: return "" - if name in self.__tokdict: - if name not in self.__accumNames: - return self.__tokdict[name][-1][0] - else: - return ParseResults([ v[0] for v in self.__tokdict[name] ]) - else: - return "" - - def __add__( self, other ): + def __add__(self, other): ret = self.copy() ret += other return ret - def __iadd__( self, other ): + def __iadd__(self, other): if other.__tokdict: offset = len(self.__toklist) - addoffset = lambda a: offset if a<0 else a+offset + addoffset = lambda a: offset if a < 0 else a + offset otheritems = other.__tokdict.items() - otherdictitems = [(k, _ParseResultsWithOffset(v[0],addoffset(v[1])) ) - for (k,vlist) in otheritems for v in vlist] - for k,v in otherdictitems: + otherdictitems = [(k, _ParseResultsWithOffset(v[0], addoffset(v[1]))) + for k, vlist in otheritems for v in vlist] + for k, v in otherdictitems: self[k] = v - if isinstance(v[0],ParseResults): + if isinstance(v[0], ParseResults): v[0].__parent = wkref(self) self.__toklist += other.__toklist - self.__accumNames.update( other.__accumNames ) + self.__accumNames.update(other.__accumNames) return self def __radd__(self, other): - if isinstance(other,int) and other == 0: + if isinstance(other, int) and other == 0: # useful for merging many ParseResults using sum() builtin return self.copy() else: # this may raise a TypeError - so be it return other + self - def __repr__( self ): - return "(%s, %s)" % ( repr( self.__toklist ), repr( self.__tokdict ) ) + def __repr__(self): + return "(%s, %s)" % (repr(self.__toklist), repr(self.__tokdict)) - def __str__( self ): + def __str__(self): return '[' + ', '.join(_ustr(i) if isinstance(i, ParseResults) else repr(i) for i in self.__toklist) + ']' - def _asStringList( self, sep='' ): + def _asStringList(self, sep=''): out = [] for item in self.__toklist: if out and sep: out.append(sep) - if isinstance( item, ParseResults ): + if isinstance(item, ParseResults): out += item._asStringList() else: - out.append( _ustr(item) ) + out.append(_ustr(item)) return out - def asList( self ): + def asList(self): """ Returns the parse results as a nested list of matching tokens, all converted to strings. @@ -834,9 +905,9 @@ class ParseResults(object): result_list = result.asList() print(type(result_list), result_list) # -> <class 'list'> ['sldkj', 'lsdkj', 'sldkj'] """ - return [res.asList() if isinstance(res,ParseResults) else res for res in self.__toklist] + return [res.asList() if isinstance(res, ParseResults) else res for res in self.__toklist] - def asDict( self ): + def asDict(self): """ Returns the named parse results as a nested dictionary. @@ -870,27 +941,27 @@ class ParseResults(object): else: return obj - return dict((k,toItem(v)) for k,v in item_fn()) + return dict((k, toItem(v)) for k, v in item_fn()) - def copy( self ): + def copy(self): """ Returns a new copy of a :class:`ParseResults` object. """ - ret = ParseResults( self.__toklist ) + ret = ParseResults(self.__toklist) ret.__tokdict = dict(self.__tokdict.items()) ret.__parent = self.__parent - ret.__accumNames.update( self.__accumNames ) + ret.__accumNames.update(self.__accumNames) ret.__name = self.__name return ret - def asXML( self, doctag=None, namedItemsOnly=False, indent="", formatted=True ): + def asXML(self, doctag=None, namedItemsOnly=False, indent="", formatted=True): """ (Deprecated) Returns the parse results as XML. Tags are created for tokens and lists that have defined results names. """ nl = "\n" out = [] - namedItems = dict((v[1],k) for (k,vlist) in self.__tokdict.items() - for v in vlist) + namedItems = dict((v[1], k) for (k, vlist) in self.__tokdict.items() + for v in vlist) nextLevelIndent = indent + " " # collapse out indents if formatting is not desired @@ -912,20 +983,20 @@ class ParseResults(object): else: selfTag = "ITEM" - out += [ nl, indent, "<", selfTag, ">" ] + out += [nl, indent, "<", selfTag, ">"] - for i,res in enumerate(self.__toklist): - if isinstance(res,ParseResults): + for i, res in enumerate(self.__toklist): + if isinstance(res, ParseResults): if i in namedItems: - out += [ res.asXML(namedItems[i], - namedItemsOnly and doctag is None, - nextLevelIndent, - formatted)] + out += [res.asXML(namedItems[i], + namedItemsOnly and doctag is None, + nextLevelIndent, + formatted)] else: - out += [ res.asXML(None, - namedItemsOnly and doctag is None, - nextLevelIndent, - formatted)] + out += [res.asXML(None, + namedItemsOnly and doctag is None, + nextLevelIndent, + formatted)] else: # individual token, see if there is a name for it resTag = None @@ -937,16 +1008,16 @@ class ParseResults(object): else: resTag = "ITEM" xmlBodyText = _xml_escape(_ustr(res)) - out += [ nl, nextLevelIndent, "<", resTag, ">", - xmlBodyText, - "</", resTag, ">" ] + out += [nl, nextLevelIndent, "<", resTag, ">", + xmlBodyText, + "</", resTag, ">"] - out += [ nl, indent, "</", selfTag, ">" ] + out += [nl, indent, "</", selfTag, ">"] return "".join(out) - def __lookup(self,sub): - for k,vlist in self.__tokdict.items(): - for v,loc in vlist: + def __lookup(self, sub): + for k, vlist in self.__tokdict.items(): + for v, loc in vlist: if sub is v: return k return None @@ -984,14 +1055,14 @@ class ParseResults(object): return par.__lookup(self) else: return None - elif (len(self) == 1 and - len(self.__tokdict) == 1 and - next(iter(self.__tokdict.values()))[0][1] in (0,-1)): + elif (len(self) == 1 + and len(self.__tokdict) == 1 + and next(iter(self.__tokdict.values()))[0][1] in (0, -1)): return next(iter(self.__tokdict.keys())) else: return None - def dump(self, indent='', depth=0, full=True): + def dump(self, indent='', full=True, include_list=True, _depth=0): """ Diagnostic method for listing out the contents of a :class:`ParseResults`. Accepts an optional ``indent`` argument so @@ -1014,28 +1085,45 @@ class ParseResults(object): """ out = [] NL = '\n' - out.append( indent+_ustr(self.asList()) ) + if include_list: + out.append(indent + _ustr(self.asList())) + else: + out.append('') + if full: if self.haskeys(): - items = sorted((str(k), v) for k,v in self.items()) - for k,v in items: + items = sorted((str(k), v) for k, v in self.items()) + for k, v in items: if out: out.append(NL) - out.append( "%s%s- %s: " % (indent,(' '*depth), k) ) - if isinstance(v,ParseResults): + out.append("%s%s- %s: " % (indent, (' ' * _depth), k)) + if isinstance(v, ParseResults): if v: - out.append( v.dump(indent,depth+1) ) + out.append(v.dump(indent=indent, full=full, include_list=include_list, _depth=_depth + 1)) else: out.append(_ustr(v)) else: out.append(repr(v)) - elif any(isinstance(vv,ParseResults) for vv in self): + elif any(isinstance(vv, ParseResults) for vv in self): v = self - for i,vv in enumerate(v): - if isinstance(vv,ParseResults): - out.append("\n%s%s[%d]:\n%s%s%s" % (indent,(' '*(depth)),i,indent,(' '*(depth+1)),vv.dump(indent,depth+1) )) + for i, vv in enumerate(v): + if isinstance(vv, ParseResults): + out.append("\n%s%s[%d]:\n%s%s%s" % (indent, + (' ' * (_depth)), + i, + indent, + (' ' * (_depth + 1)), + vv.dump(indent=indent, + full=full, + include_list=include_list, + _depth=_depth + 1))) else: - out.append("\n%s%s[%d]:\n%s%s%s" % (indent,(' '*(depth)),i,indent,(' '*(depth+1)),_ustr(vv))) + out.append("\n%s%s[%d]:\n%s%s%s" % (indent, + (' ' * (_depth)), + i, + indent, + (' ' * (_depth + 1)), + _ustr(vv))) return "".join(out) @@ -1068,18 +1156,15 @@ class ParseResults(object): # add support for pickle protocol def __getstate__(self): - return ( self.__toklist, - ( self.__tokdict.copy(), - self.__parent is not None and self.__parent() or None, - self.__accumNames, - self.__name ) ) + return (self.__toklist, + (self.__tokdict.copy(), + self.__parent is not None and self.__parent() or None, + self.__accumNames, + self.__name)) - def __setstate__(self,state): + def __setstate__(self, state): self.__toklist = state[0] - (self.__tokdict, - par, - inAccumNames, - self.__name) = state[1] + self.__tokdict, par, inAccumNames, self.__name = state[1] self.__accumNames = {} self.__accumNames.update(inAccumNames) if par is not None: @@ -1091,11 +1176,39 @@ class ParseResults(object): return self.__toklist, self.__name, self.__asList, self.__modal def __dir__(self): - return (dir(type(self)) + list(self.keys())) + return dir(type(self)) + list(self.keys()) + + @classmethod + def from_dict(cls, other, name=None): + """ + Helper classmethod to construct a ParseResults from a dict, preserving the + name-value relations as results names. If an optional 'name' argument is + given, a nested ParseResults will be returned + """ + def is_iterable(obj): + try: + iter(obj) + except Exception: + return False + else: + if PY_3: + return not isinstance(obj, (str, bytes)) + else: + return not isinstance(obj, basestring) + + ret = cls([]) + for k, v in other.items(): + if isinstance(v, Mapping): + ret += cls.from_dict(v, name=k) + else: + ret += cls([v], name=k, asList=is_iterable(v)) + if name is not None: + ret = cls([ret], name=name) + return ret MutableMapping.register(ParseResults) -def col (loc,strg): +def col (loc, strg): """Returns current column within a string, counting newlines as line separators. The first column is number 1. @@ -1107,9 +1220,9 @@ def col (loc,strg): location, and line and column positions within the parsed string. """ s = strg - return 1 if 0<loc<len(s) and s[loc-1] == '\n' else loc - s.rfind("\n", 0, loc) + return 1 if 0 < loc < len(s) and s[loc-1] == '\n' else loc - s.rfind("\n", 0, loc) -def lineno(loc,strg): +def lineno(loc, strg): """Returns current line number within a string, counting newlines as line separators. The first line is number 1. @@ -1119,26 +1232,26 @@ def lineno(loc,strg): suggested methods to maintain a consistent view of the parsed string, the parse location, and line and column positions within the parsed string. """ - return strg.count("\n",0,loc) + 1 + return strg.count("\n", 0, loc) + 1 -def line( loc, strg ): +def line(loc, strg): """Returns the line of text containing loc within a string, counting newlines as line separators. """ lastCR = strg.rfind("\n", 0, loc) nextCR = strg.find("\n", loc) if nextCR >= 0: - return strg[lastCR+1:nextCR] + return strg[lastCR + 1:nextCR] else: - return strg[lastCR+1:] + return strg[lastCR + 1:] -def _defaultStartDebugAction( instring, loc, expr ): - print (("Match " + _ustr(expr) + " at loc " + _ustr(loc) + "(%d,%d)" % ( lineno(loc,instring), col(loc,instring) ))) +def _defaultStartDebugAction(instring, loc, expr): + print(("Match " + _ustr(expr) + " at loc " + _ustr(loc) + "(%d,%d)" % (lineno(loc, instring), col(loc, instring)))) -def _defaultSuccessDebugAction( instring, startloc, endloc, expr, toks ): - print ("Matched " + _ustr(expr) + " -> " + str(toks.asList())) +def _defaultSuccessDebugAction(instring, startloc, endloc, expr, toks): + print("Matched " + _ustr(expr) + " -> " + str(toks.asList())) -def _defaultExceptionDebugAction( instring, loc, expr, exc ): - print ("Exception raised:" + _ustr(exc)) +def _defaultExceptionDebugAction(instring, loc, expr, exc): + print("Exception raised:" + _ustr(exc)) def nullDebugAction(*args): """'Do-nothing' debug action, to suppress debugging output during parsing.""" @@ -1169,16 +1282,16 @@ def nullDebugAction(*args): 'decorator to trim function calls to match the arity of the target' def _trim_arity(func, maxargs=2): if func in singleArgBuiltins: - return lambda s,l,t: func(t) + return lambda s, l, t: func(t) limit = [0] foundArity = [False] # traceback return data structure changed in Py3.5 - normalize back to plain tuples - if system_version[:2] >= (3,5): + if system_version[:2] >= (3, 5): def extract_stack(limit=0): # special handling for Python 3.5.0 - extra deep call stack by 1 - offset = -3 if system_version == (3,5,0) else -2 - frame_summary = traceback.extract_stack(limit=-offset+limit-1)[offset] + offset = -3 if system_version == (3, 5, 0) else -2 + frame_summary = traceback.extract_stack(limit=-offset + limit - 1)[offset] return [frame_summary[:2]] def extract_tb(tb, limit=0): frames = traceback.extract_tb(tb, limit=limit) @@ -1195,7 +1308,7 @@ def _trim_arity(func, maxargs=2): # IF ANY CODE CHANGES, EVEN JUST COMMENTS OR BLANK LINES, BETWEEN THE NEXT LINE AND # THE CALL TO FUNC INSIDE WRAPPER, LINE_DIFF MUST BE MODIFIED!!!! this_line = extract_stack(limit=2)[-1] - pa_call_line_synth = (this_line[0], this_line[1]+LINE_DIFF) + pa_call_line_synth = (this_line[0], this_line[1] + LINE_DIFF) def wrapper(*args): while 1: @@ -1213,7 +1326,10 @@ def _trim_arity(func, maxargs=2): if not extract_tb(tb, limit=2)[-1][:2] == pa_call_line_synth: raise finally: - del tb + try: + del tb + except NameError: + pass if limit[0] <= maxargs: limit[0] += 1 @@ -1231,13 +1347,14 @@ def _trim_arity(func, maxargs=2): return wrapper + class ParserElement(object): """Abstract base level parser element class.""" DEFAULT_WHITE_CHARS = " \n\t\r" verbose_stacktrace = False @staticmethod - def setDefaultWhitespaceChars( chars ): + def setDefaultWhitespaceChars(chars): r""" Overrides the default whitespace chars @@ -1274,10 +1391,16 @@ class ParserElement(object): """ ParserElement._literalStringClass = cls - def __init__( self, savelist=False ): + @classmethod + def _trim_traceback(cls, tb): + while tb.tb_next: + tb = tb.tb_next + return tb + + def __init__(self, savelist=False): self.parseAction = list() self.failAction = None - #~ self.name = "<unknown>" # don't define self.name, let subclasses try/except upcall + # ~ self.name = "<unknown>" # don't define self.name, let subclasses try/except upcall self.strRepr = None self.resultsName = None self.saveAsList = savelist @@ -1292,12 +1415,12 @@ class ParserElement(object): self.mayIndexError = True # used to optimize exception handling for subclasses that don't advance parse index self.errmsg = "" self.modalResults = True # used to mark results names as modal (report only last) or cumulative (list all) - self.debugActions = ( None, None, None ) #custom debug actions + self.debugActions = (None, None, None) # custom debug actions self.re = None self.callPreparse = True # used to avoid redundant calls to preParse self.callDuringTry = False - def copy( self ): + def copy(self): """ Make a copy of this :class:`ParserElement`. Useful for defining different parse actions for the same parsing pattern, using copies of @@ -1306,8 +1429,8 @@ class ParserElement(object): Example:: integer = Word(nums).setParseAction(lambda toks: int(toks[0])) - integerK = integer.copy().addParseAction(lambda toks: toks[0]*1024) + Suppress("K") - integerM = integer.copy().addParseAction(lambda toks: toks[0]*1024*1024) + Suppress("M") + integerK = integer.copy().addParseAction(lambda toks: toks[0] * 1024) + Suppress("K") + integerM = integer.copy().addParseAction(lambda toks: toks[0] * 1024 * 1024) + Suppress("M") print(OneOrMore(integerK | integerM | integer).parseString("5K 100 640K 256M")) @@ -1317,16 +1440,16 @@ class ParserElement(object): Equivalent form of ``expr.copy()`` is just ``expr()``:: - integerM = integer().addParseAction(lambda toks: toks[0]*1024*1024) + Suppress("M") + integerM = integer().addParseAction(lambda toks: toks[0] * 1024 * 1024) + Suppress("M") """ - cpy = copy.copy( self ) + cpy = copy.copy(self) cpy.parseAction = self.parseAction[:] cpy.ignoreExprs = self.ignoreExprs[:] if self.copyDefaultWhiteChars: cpy.whiteChars = ParserElement.DEFAULT_WHITE_CHARS return cpy - def setName( self, name ): + def setName(self, name): """ Define name for this expression, makes debugging and exception messages clearer. @@ -1337,11 +1460,11 @@ class ParserElement(object): """ self.name = name self.errmsg = "Expected " + self.name - if hasattr(self,"exception"): - self.exception.msg = self.errmsg + if __diag__.enable_debug_on_named_expressions: + self.setDebug() return self - def setResultsName( self, name, listAllMatches=False ): + def setResultsName(self, name, listAllMatches=False): """ Define name for referencing matching tokens as a nested attribute of the returned parse results. @@ -1362,15 +1485,18 @@ class ParserElement(object): # equivalent form: date_str = integer("year") + '/' + integer("month") + '/' + integer("day") """ + return self._setResultsName(name, listAllMatches) + + def _setResultsName(self, name, listAllMatches=False): newself = self.copy() if name.endswith("*"): name = name[:-1] - listAllMatches=True + listAllMatches = True newself.resultsName = name newself.modalResults = not listAllMatches return newself - def setBreak(self,breakFlag = True): + def setBreak(self, breakFlag=True): """Method to invoke the Python pdb debugger when this element is about to be parsed. Set ``breakFlag`` to True to enable, False to disable. @@ -1379,20 +1505,21 @@ class ParserElement(object): _parseMethod = self._parse def breaker(instring, loc, doActions=True, callPreParse=True): import pdb + # this call to pdb.set_trace() is intentional, not a checkin error pdb.set_trace() - return _parseMethod( instring, loc, doActions, callPreParse ) + return _parseMethod(instring, loc, doActions, callPreParse) breaker._originalParseMethod = _parseMethod self._parse = breaker else: - if hasattr(self._parse,"_originalParseMethod"): + if hasattr(self._parse, "_originalParseMethod"): self._parse = self._parse._originalParseMethod return self - def setParseAction( self, *fns, **kwargs ): + def setParseAction(self, *fns, **kwargs): """ Define one or more actions to perform when successfully matching parse element definition. - Parse action fn is a callable method with 0-3 arguments, called as ``fn(s,loc,toks)`` , - ``fn(loc,toks)`` , ``fn(toks)`` , or just ``fn()`` , where: + Parse action fn is a callable method with 0-3 arguments, called as ``fn(s, loc, toks)`` , + ``fn(loc, toks)`` , ``fn(toks)`` , or just ``fn()`` , where: - s = the original string being parsed (see note below) - loc = the location of the matching substring @@ -1402,8 +1529,11 @@ class ParserElement(object): value from fn, and the modified list of tokens will replace the original. Otherwise, fn does not need to return any value. + If None is passed as the parse action, all previously added parse actions for this + expression are cleared. + Optional keyword arguments: - - callDuringTry = (default= ``False`` ) indicate if parse action should be run during lookaheads and alternate testing + - callDuringTry = (default= ``False``) indicate if parse action should be run during lookaheads and alternate testing Note: the default parsing behavior is to expand tabs in the input string before starting the parsing process. See :class:`parseString for more @@ -1425,11 +1555,16 @@ class ParserElement(object): # note that integer fields are now ints, not strings date_str.parseString("1999/12/31") # -> [1999, '/', 12, '/', 31] """ - self.parseAction = list(map(_trim_arity, list(fns))) - self.callDuringTry = kwargs.get("callDuringTry", False) + if list(fns) == [None,]: + self.parseAction = [] + else: + if not all(callable(fn) for fn in fns): + raise TypeError("parse actions must be callable") + self.parseAction = list(map(_trim_arity, list(fns))) + self.callDuringTry = kwargs.get("callDuringTry", False) return self - def addParseAction( self, *fns, **kwargs ): + def addParseAction(self, *fns, **kwargs): """ Add one or more parse actions to expression's list of parse actions. See :class:`setParseAction`. @@ -1457,21 +1592,17 @@ class ParserElement(object): result = date_str.parseString("1999/12/31") # -> Exception: Only support years 2000 and later (at char 0), (line:1, col:1) """ - msg = kwargs.get("message", "failed user-defined condition") - exc_type = ParseFatalException if kwargs.get("fatal", False) else ParseException for fn in fns: - fn = _trim_arity(fn) - def pa(s,l,t): - if not bool(fn(s,l,t)): - raise exc_type(s,l,msg) - self.parseAction.append(pa) + self.parseAction.append(conditionAsParseAction(fn, message=kwargs.get('message'), + fatal=kwargs.get('fatal', False))) + self.callDuringTry = self.callDuringTry or kwargs.get("callDuringTry", False) return self - def setFailAction( self, fn ): + def setFailAction(self, fn): """Define action to perform if parsing fails at this expression. Fail acton fn is a callable function that takes the arguments - ``fn(s,loc,expr,err)`` where: + ``fn(s, loc, expr, err)`` where: - s = string being parsed - loc = location where expression match was attempted and failed - expr = the parse expression that failed @@ -1481,22 +1612,22 @@ class ParserElement(object): self.failAction = fn return self - def _skipIgnorables( self, instring, loc ): + def _skipIgnorables(self, instring, loc): exprsFound = True while exprsFound: exprsFound = False for e in self.ignoreExprs: try: while 1: - loc,dummy = e._parse( instring, loc ) + loc, dummy = e._parse(instring, loc) exprsFound = True except ParseException: pass return loc - def preParse( self, instring, loc ): + def preParse(self, instring, loc): if self.ignoreExprs: - loc = self._skipIgnorables( instring, loc ) + loc = self._skipIgnorables(instring, loc) if self.skipWhitespace: wt = self.whiteChars @@ -1506,101 +1637,105 @@ class ParserElement(object): return loc - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): return loc, [] - def postParse( self, instring, loc, tokenlist ): + def postParse(self, instring, loc, tokenlist): return tokenlist - #~ @profile - def _parseNoCache( self, instring, loc, doActions=True, callPreParse=True ): - debugging = ( self.debug ) #and doActions ) + # ~ @profile + def _parseNoCache(self, instring, loc, doActions=True, callPreParse=True): + TRY, MATCH, FAIL = 0, 1, 2 + debugging = (self.debug) # and doActions) if debugging or self.failAction: - #~ print ("Match",self,"at loc",loc,"(%d,%d)" % ( lineno(loc,instring), col(loc,instring) )) - if (self.debugActions[0] ): - self.debugActions[0]( instring, loc, self ) - if callPreParse and self.callPreparse: - preloc = self.preParse( instring, loc ) - else: - preloc = loc - tokensStart = preloc + # ~ print ("Match", self, "at loc", loc, "(%d, %d)" % (lineno(loc, instring), col(loc, instring))) + if self.debugActions[TRY]: + self.debugActions[TRY](instring, loc, self) try: - try: - loc,tokens = self.parseImpl( instring, preloc, doActions ) - except IndexError: - raise ParseException( instring, len(instring), self.errmsg, self ) - except ParseBaseException as err: - #~ print ("Exception raised:", err) - if self.debugActions[2]: - self.debugActions[2]( instring, tokensStart, self, err ) + if callPreParse and self.callPreparse: + preloc = self.preParse(instring, loc) + else: + preloc = loc + tokensStart = preloc + if self.mayIndexError or preloc >= len(instring): + try: + loc, tokens = self.parseImpl(instring, preloc, doActions) + except IndexError: + raise ParseException(instring, len(instring), self.errmsg, self) + else: + loc, tokens = self.parseImpl(instring, preloc, doActions) + except Exception as err: + # ~ print ("Exception raised:", err) + if self.debugActions[FAIL]: + self.debugActions[FAIL](instring, tokensStart, self, err) if self.failAction: - self.failAction( instring, tokensStart, self, err ) + self.failAction(instring, tokensStart, self, err) raise else: if callPreParse and self.callPreparse: - preloc = self.preParse( instring, loc ) + preloc = self.preParse(instring, loc) else: preloc = loc tokensStart = preloc if self.mayIndexError or preloc >= len(instring): try: - loc,tokens = self.parseImpl( instring, preloc, doActions ) + loc, tokens = self.parseImpl(instring, preloc, doActions) except IndexError: - raise ParseException( instring, len(instring), self.errmsg, self ) + raise ParseException(instring, len(instring), self.errmsg, self) else: - loc,tokens = self.parseImpl( instring, preloc, doActions ) + loc, tokens = self.parseImpl(instring, preloc, doActions) - tokens = self.postParse( instring, loc, tokens ) + tokens = self.postParse(instring, loc, tokens) - retTokens = ParseResults( tokens, self.resultsName, asList=self.saveAsList, modal=self.modalResults ) + retTokens = ParseResults(tokens, self.resultsName, asList=self.saveAsList, modal=self.modalResults) if self.parseAction and (doActions or self.callDuringTry): if debugging: try: for fn in self.parseAction: try: - tokens = fn( instring, tokensStart, retTokens ) + tokens = fn(instring, tokensStart, retTokens) except IndexError as parse_action_exc: exc = ParseException("exception raised in parse action") exc.__cause__ = parse_action_exc raise exc if tokens is not None and tokens is not retTokens: - retTokens = ParseResults( tokens, + retTokens = ParseResults(tokens, self.resultsName, - asList=self.saveAsList and isinstance(tokens,(ParseResults,list)), - modal=self.modalResults ) - except ParseBaseException as err: - #~ print "Exception raised in user parse action:", err - if (self.debugActions[2] ): - self.debugActions[2]( instring, tokensStart, self, err ) + asList=self.saveAsList and isinstance(tokens, (ParseResults, list)), + modal=self.modalResults) + except Exception as err: + # ~ print "Exception raised in user parse action:", err + if self.debugActions[FAIL]: + self.debugActions[FAIL](instring, tokensStart, self, err) raise else: for fn in self.parseAction: try: - tokens = fn( instring, tokensStart, retTokens ) + tokens = fn(instring, tokensStart, retTokens) except IndexError as parse_action_exc: exc = ParseException("exception raised in parse action") exc.__cause__ = parse_action_exc raise exc if tokens is not None and tokens is not retTokens: - retTokens = ParseResults( tokens, + retTokens = ParseResults(tokens, self.resultsName, - asList=self.saveAsList and isinstance(tokens,(ParseResults,list)), - modal=self.modalResults ) + asList=self.saveAsList and isinstance(tokens, (ParseResults, list)), + modal=self.modalResults) if debugging: - #~ print ("Matched",self,"->",retTokens.asList()) - if (self.debugActions[1] ): - self.debugActions[1]( instring, tokensStart, loc, self, retTokens ) + # ~ print ("Matched", self, "->", retTokens.asList()) + if self.debugActions[MATCH]: + self.debugActions[MATCH](instring, tokensStart, loc, self, retTokens) return loc, retTokens - def tryParse( self, instring, loc ): + def tryParse(self, instring, loc): try: - return self._parse( instring, loc, doActions=False )[0] + return self._parse(instring, loc, doActions=False)[0] except ParseFatalException: - raise ParseException( instring, loc, self.errmsg, self) + raise ParseException(instring, loc, self.errmsg, self) def canParseNext(self, instring, loc): try: @@ -1697,7 +1832,7 @@ class ParserElement(object): # this method gets repeatedly called during backtracking with the same arguments - # we can cache these arguments and save ourselves the trouble of re-parsing the contained expression - def _parseCache( self, instring, loc, doActions=True, callPreParse=True ): + def _parseCache(self, instring, loc, doActions=True, callPreParse=True): HIT, MISS = 0, 1 lookup = (self, instring, loc, callPreParse, doActions) with ParserElement.packrat_cache_lock: @@ -1718,7 +1853,7 @@ class ParserElement(object): ParserElement.packrat_cache_stats[HIT] += 1 if isinstance(value, Exception): raise value - return (value[0], value[1].copy()) + return value[0], value[1].copy() _parse = _parseNoCache @@ -1763,12 +1898,16 @@ class ParserElement(object): ParserElement.packrat_cache = ParserElement._FifoCache(cache_size_limit) ParserElement._parse = ParserElement._parseCache - def parseString( self, instring, parseAll=False ): + def parseString(self, instring, parseAll=False): """ Execute the parse expression with the given string. This is the main interface to the client code, once the complete expression has been built. + Returns the parsed data as a :class:`ParseResults` object, which may be + accessed as a list, or as a dict or object with attributes if the given parser + includes results names. + If you want the grammar to require that the entire input string be successfully parsed, then set ``parseAll`` to True (equivalent to ending the grammar with ``StringEnd()``). @@ -1782,7 +1921,7 @@ class ParserElement(object): - calling ``parseWithTabs`` on your grammar before calling ``parseString`` (see :class:`parseWithTabs`) - - define your parse action using the full ``(s,loc,toks)`` signature, and + - define your parse action using the full ``(s, loc, toks)`` signature, and reference the input string using the parse action's ``s`` argument - explictly expand the tabs in your input string before calling ``parseString`` @@ -1795,27 +1934,29 @@ class ParserElement(object): ParserElement.resetCache() if not self.streamlined: self.streamline() - #~ self.saveAsList = True + # ~ self.saveAsList = True for e in self.ignoreExprs: e.streamline() if not self.keepTabs: instring = instring.expandtabs() try: - loc, tokens = self._parse( instring, 0 ) + loc, tokens = self._parse(instring, 0) if parseAll: - loc = self.preParse( instring, loc ) + loc = self.preParse(instring, loc) se = Empty() + StringEnd() - se._parse( instring, loc ) + se._parse(instring, loc) except ParseBaseException as exc: if ParserElement.verbose_stacktrace: raise else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace + # catch and re-raise exception from here, clearing out pyparsing internal stack trace + if getattr(exc, '__traceback__', None) is not None: + exc.__traceback__ = self._trim_traceback(exc.__traceback__) raise exc else: return tokens - def scanString( self, instring, maxMatches=_MAX_INT, overlap=False ): + def scanString(self, instring, maxMatches=_MAX_INT, overlap=False): """ Scan the input string for expression matches. Each match will return the matching tokens, start location, and end location. May be called with optional @@ -1830,7 +1971,7 @@ class ParserElement(object): source = "sldjf123lsdjjkf345sldkjf879lkjsfd987" print(source) - for tokens,start,end in Word(alphas).scanString(source): + for tokens, start, end in Word(alphas).scanString(source): print(' '*start + '^'*(end-start)) print(' '*start + tokens[0]) @@ -1862,16 +2003,16 @@ class ParserElement(object): try: while loc <= instrlen and matches < maxMatches: try: - preloc = preparseFn( instring, loc ) - nextLoc,tokens = parseFn( instring, preloc, callPreParse=False ) + preloc = preparseFn(instring, loc) + nextLoc, tokens = parseFn(instring, preloc, callPreParse=False) except ParseException: - loc = preloc+1 + loc = preloc + 1 else: if nextLoc > loc: matches += 1 yield tokens, preloc, nextLoc if overlap: - nextloc = preparseFn( instring, loc ) + nextloc = preparseFn(instring, loc) if nextloc > loc: loc = nextLoc else: @@ -1879,15 +2020,17 @@ class ParserElement(object): else: loc = nextLoc else: - loc = preloc+1 + loc = preloc + 1 except ParseBaseException as exc: if ParserElement.verbose_stacktrace: raise else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace + # catch and re-raise exception from here, clearing out pyparsing internal stack trace + if getattr(exc, '__traceback__', None) is not None: + exc.__traceback__ = self._trim_traceback(exc.__traceback__) raise exc - def transformString( self, instring ): + def transformString(self, instring): """ Extension to :class:`scanString`, to modify matching text with modified tokens that may be returned from a parse action. To use ``transformString``, define a grammar and @@ -1913,27 +2056,29 @@ class ParserElement(object): # keep string locs straight between transformString and scanString self.keepTabs = True try: - for t,s,e in self.scanString( instring ): - out.append( instring[lastE:s] ) + for t, s, e in self.scanString(instring): + out.append(instring[lastE:s]) if t: - if isinstance(t,ParseResults): + if isinstance(t, ParseResults): out += t.asList() - elif isinstance(t,list): + elif isinstance(t, list): out += t else: out.append(t) lastE = e out.append(instring[lastE:]) out = [o for o in out if o] - return "".join(map(_ustr,_flatten(out))) + return "".join(map(_ustr, _flatten(out))) except ParseBaseException as exc: if ParserElement.verbose_stacktrace: raise else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace + # catch and re-raise exception from here, clearing out pyparsing internal stack trace + if getattr(exc, '__traceback__', None) is not None: + exc.__traceback__ = self._trim_traceback(exc.__traceback__) raise exc - def searchString( self, instring, maxMatches=_MAX_INT ): + def searchString(self, instring, maxMatches=_MAX_INT): """ Another extension to :class:`scanString`, simplifying the access to the tokens found to match the given parse expression. May be called with optional @@ -1955,12 +2100,14 @@ class ParserElement(object): ['More', 'Iron', 'Lead', 'Gold', 'I', 'Electricity'] """ try: - return ParseResults([ t for t,s,e in self.scanString( instring, maxMatches ) ]) + return ParseResults([t for t, s, e in self.scanString(instring, maxMatches)]) except ParseBaseException as exc: if ParserElement.verbose_stacktrace: raise else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace + # catch and re-raise exception from here, clearing out pyparsing internal stack trace + if getattr(exc, '__traceback__', None) is not None: + exc.__traceback__ = self._trim_traceback(exc.__traceback__) raise exc def split(self, instring, maxsplit=_MAX_INT, includeSeparators=False): @@ -1981,14 +2128,14 @@ class ParserElement(object): """ splits = 0 last = 0 - for t,s,e in self.scanString(instring, maxMatches=maxsplit): + for t, s, e in self.scanString(instring, maxMatches=maxsplit): yield instring[last:s] if includeSeparators: yield t[0] last = e yield instring[last:] - def __add__(self, other ): + def __add__(self, other): """ Implementation of + operator - returns :class:`And`. Adding strings to a ParserElement converts them to :class:`Literal`s by default. @@ -2002,24 +2149,42 @@ class ParserElement(object): prints:: Hello, World! -> ['Hello', ',', 'World', '!'] - """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return And( [ self, other ] ) - def __radd__(self, other ): + ``...`` may be used as a parse expression as a short form of :class:`SkipTo`. + + Literal('start') + ... + Literal('end') + + is equivalent to: + + Literal('start') + SkipTo('end')("_skipped*") + Literal('end') + + Note that the skipped text is returned with '_skipped' as a results name, + and to support having multiple skips in the same parser, the value returned is + a list of all skipped text. + """ + if other is Ellipsis: + return _PendingSkip(self) + + if isinstance(other, basestring): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return And([self, other]) + + def __radd__(self, other): """ Implementation of + operator when left operand is not a :class:`ParserElement` """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): + if other is Ellipsis: + return SkipTo(self)("_skipped*") + self + + if isinstance(other, basestring): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) + SyntaxWarning, stacklevel=2) return None return other + self @@ -2027,64 +2192,70 @@ class ParserElement(object): """ Implementation of - operator, returns :class:`And` with error stop """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): + if isinstance(other, basestring): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) + SyntaxWarning, stacklevel=2) return None return self + And._ErrorStop() + other - def __rsub__(self, other ): + def __rsub__(self, other): """ Implementation of - operator when left operand is not a :class:`ParserElement` """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): + if isinstance(other, basestring): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) + SyntaxWarning, stacklevel=2) return None return other - self - def __mul__(self,other): + def __mul__(self, other): """ Implementation of * operator, allows use of ``expr * 3`` in place of ``expr + expr + expr``. Expressions may also me multiplied by a 2-integer - tuple, similar to ``{min,max}`` multipliers in regular expressions. Tuples + tuple, similar to ``{min, max}`` multipliers in regular expressions. Tuples may also include ``None`` as in: - - ``expr*(n,None)`` or ``expr*(n,)`` is equivalent + - ``expr*(n, None)`` or ``expr*(n, )`` is equivalent to ``expr*n + ZeroOrMore(expr)`` (read as "at least n instances of ``expr``") - - ``expr*(None,n)`` is equivalent to ``expr*(0,n)`` + - ``expr*(None, n)`` is equivalent to ``expr*(0, n)`` (read as "0 to n instances of ``expr``") - - ``expr*(None,None)`` is equivalent to ``ZeroOrMore(expr)`` - - ``expr*(1,None)`` is equivalent to ``OneOrMore(expr)`` + - ``expr*(None, None)`` is equivalent to ``ZeroOrMore(expr)`` + - ``expr*(1, None)`` is equivalent to ``OneOrMore(expr)`` - Note that ``expr*(None,n)`` does not raise an exception if + Note that ``expr*(None, n)`` does not raise an exception if more than n exprs exist in the input stream; that is, - ``expr*(None,n)`` does not enforce a maximum number of expr + ``expr*(None, n)`` does not enforce a maximum number of expr occurrences. If this behavior is desired, then write - ``expr*(None,n) + ~expr`` + ``expr*(None, n) + ~expr`` """ - if isinstance(other,int): - minElements, optElements = other,0 - elif isinstance(other,tuple): + if other is Ellipsis: + other = (0, None) + elif isinstance(other, tuple) and other[:1] == (Ellipsis,): + other = ((0, ) + other[1:] + (None,))[:2] + + if isinstance(other, int): + minElements, optElements = other, 0 + elif isinstance(other, tuple): + other = tuple(o if o is not Ellipsis else None for o in other) other = (other + (None, None))[:2] if other[0] is None: other = (0, other[1]) - if isinstance(other[0],int) and other[1] is None: + if isinstance(other[0], int) and other[1] is None: if other[0] == 0: return ZeroOrMore(self) if other[0] == 1: return OneOrMore(self) else: - return self*other[0] + ZeroOrMore(self) - elif isinstance(other[0],int) and isinstance(other[1],int): + return self * other[0] + ZeroOrMore(self) + elif isinstance(other[0], int) and isinstance(other[1], int): minElements, optElements = other optElements -= minElements else: - raise TypeError("cannot multiply 'ParserElement' and ('%s','%s') objects", type(other[0]),type(other[1])) + raise TypeError("cannot multiply 'ParserElement' and ('%s', '%s') objects", type(other[0]), type(other[1])) else: raise TypeError("cannot multiply 'ParserElement' and '%s' objects", type(other)) @@ -2093,108 +2264,152 @@ class ParserElement(object): if optElements < 0: raise ValueError("second tuple value must be greater or equal to first tuple value") if minElements == optElements == 0: - raise ValueError("cannot multiply ParserElement by 0 or (0,0)") + raise ValueError("cannot multiply ParserElement by 0 or (0, 0)") - if (optElements): + if optElements: def makeOptionalList(n): - if n>1: - return Optional(self + makeOptionalList(n-1)) + if n > 1: + return Optional(self + makeOptionalList(n - 1)) else: return Optional(self) if minElements: if minElements == 1: ret = self + makeOptionalList(optElements) else: - ret = And([self]*minElements) + makeOptionalList(optElements) + ret = And([self] * minElements) + makeOptionalList(optElements) else: ret = makeOptionalList(optElements) else: if minElements == 1: ret = self else: - ret = And([self]*minElements) + ret = And([self] * minElements) return ret def __rmul__(self, other): return self.__mul__(other) - def __or__(self, other ): + def __or__(self, other): """ Implementation of | operator - returns :class:`MatchFirst` """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return MatchFirst( [ self, other ] ) + if other is Ellipsis: + return _PendingSkip(self, must_skip=True) - def __ror__(self, other ): + if isinstance(other, basestring): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return MatchFirst([self, other]) + + def __ror__(self, other): """ Implementation of | operator when left operand is not a :class:`ParserElement` """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): + if isinstance(other, basestring): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) + SyntaxWarning, stacklevel=2) return None return other | self - def __xor__(self, other ): + def __xor__(self, other): """ Implementation of ^ operator - returns :class:`Or` """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): + if isinstance(other, basestring): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) + SyntaxWarning, stacklevel=2) return None - return Or( [ self, other ] ) + return Or([self, other]) - def __rxor__(self, other ): + def __rxor__(self, other): """ Implementation of ^ operator when left operand is not a :class:`ParserElement` """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): + if isinstance(other, basestring): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) + SyntaxWarning, stacklevel=2) return None return other ^ self - def __and__(self, other ): + def __and__(self, other): """ Implementation of & operator - returns :class:`Each` """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): + if isinstance(other, basestring): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) + SyntaxWarning, stacklevel=2) return None - return Each( [ self, other ] ) + return Each([self, other]) - def __rand__(self, other ): + def __rand__(self, other): """ Implementation of & operator when left operand is not a :class:`ParserElement` """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): + if isinstance(other, basestring): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) + SyntaxWarning, stacklevel=2) return None return other & self - def __invert__( self ): + def __invert__(self): """ Implementation of ~ operator - returns :class:`NotAny` """ - return NotAny( self ) + return NotAny(self) + + def __iter__(self): + # must implement __iter__ to override legacy use of sequential access to __getitem__ to + # iterate over a sequence + raise TypeError('%r object is not iterable' % self.__class__.__name__) + + def __getitem__(self, key): + """ + use ``[]`` indexing notation as a short form for expression repetition: + - ``expr[n]`` is equivalent to ``expr*n`` + - ``expr[m, n]`` is equivalent to ``expr*(m, n)`` + - ``expr[n, ...]`` or ``expr[n,]`` is equivalent + to ``expr*n + ZeroOrMore(expr)`` + (read as "at least n instances of ``expr``") + - ``expr[..., n]`` is equivalent to ``expr*(0, n)`` + (read as "0 to n instances of ``expr``") + - ``expr[...]`` and ``expr[0, ...]`` are equivalent to ``ZeroOrMore(expr)`` + - ``expr[1, ...]`` is equivalent to ``OneOrMore(expr)`` + ``None`` may be used in place of ``...``. + + Note that ``expr[..., n]`` and ``expr[m, n]``do not raise an exception + if more than ``n`` ``expr``s exist in the input stream. If this behavior is + desired, then write ``expr[..., n] + ~expr``. + """ + + # convert single arg keys to tuples + try: + if isinstance(key, str): + key = (key,) + iter(key) + except TypeError: + key = (key, key) + + if len(key) > 2: + warnings.warn("only 1 or 2 index arguments supported ({0}{1})".format(key[:5], + '... [{0}]'.format(len(key)) + if len(key) > 5 else '')) + + # clip to 2 elements + ret = self * tuple(key[:2]) + return ret def __call__(self, name=None): """ @@ -2208,22 +2423,22 @@ class ParserElement(object): Example:: # these are equivalent - userdata = Word(alphas).setResultsName("name") + Word(nums+"-").setResultsName("socsecno") - userdata = Word(alphas)("name") + Word(nums+"-")("socsecno") + userdata = Word(alphas).setResultsName("name") + Word(nums + "-").setResultsName("socsecno") + userdata = Word(alphas)("name") + Word(nums + "-")("socsecno") """ if name is not None: - return self.setResultsName(name) + return self._setResultsName(name) else: return self.copy() - def suppress( self ): + def suppress(self): """ Suppresses the output of this :class:`ParserElement`; useful to keep punctuation from cluttering up returned output. """ - return Suppress( self ) + return Suppress(self) - def leaveWhitespace( self ): + def leaveWhitespace(self): """ Disables the skipping of whitespace before matching the characters in the :class:`ParserElement`'s defined pattern. This is normally only used internally by @@ -2232,7 +2447,7 @@ class ParserElement(object): self.skipWhitespace = False return self - def setWhitespaceChars( self, chars ): + def setWhitespaceChars(self, chars): """ Overrides the default whitespace chars """ @@ -2241,7 +2456,7 @@ class ParserElement(object): self.copyDefaultWhiteChars = False return self - def parseWithTabs( self ): + def parseWithTabs(self): """ Overrides default behavior to expand ``<TAB>``s to spaces before parsing the input string. Must be called before ``parseString`` when the input grammar contains elements that @@ -2250,7 +2465,7 @@ class ParserElement(object): self.keepTabs = True return self - def ignore( self, other ): + def ignore(self, other): """ Define expression to be ignored (e.g., comments) while doing pattern matching; may be called repeatedly, to define multiple comment or other @@ -2267,14 +2482,14 @@ class ParserElement(object): if isinstance(other, basestring): other = Suppress(other) - if isinstance( other, Suppress ): + if isinstance(other, Suppress): if other not in self.ignoreExprs: self.ignoreExprs.append(other) else: - self.ignoreExprs.append( Suppress( other.copy() ) ) + self.ignoreExprs.append(Suppress(other.copy())) return self - def setDebugActions( self, startAction, successAction, exceptionAction ): + def setDebugActions(self, startAction, successAction, exceptionAction): """ Enable display of debugging messages while doing pattern matching. """ @@ -2284,7 +2499,7 @@ class ParserElement(object): self.debug = True return self - def setDebug( self, flag=True ): + def setDebug(self, flag=True): """ Enable display of debugging messages while doing pattern matching. Set ``flag`` to True to enable, False to disable. @@ -2322,32 +2537,32 @@ class ParserElement(object): name created for the :class:`Word` expression without calling ``setName`` is ``"W:(ABCD...)"``. """ if flag: - self.setDebugActions( _defaultStartDebugAction, _defaultSuccessDebugAction, _defaultExceptionDebugAction ) + self.setDebugActions(_defaultStartDebugAction, _defaultSuccessDebugAction, _defaultExceptionDebugAction) else: self.debug = False return self - def __str__( self ): + def __str__(self): return self.name - def __repr__( self ): + def __repr__(self): return _ustr(self) - def streamline( self ): + def streamline(self): self.streamlined = True self.strRepr = None return self - def checkRecursion( self, parseElementList ): + def checkRecursion(self, parseElementList): pass - def validate( self, validateTrace=[] ): + def validate(self, validateTrace=None): """ Check defined expressions for valid structure, check for infinite recursive definitions. """ - self.checkRecursion( [] ) + self.checkRecursion([]) - def parseFile( self, file_or_filename, parseAll=False ): + def parseFile(self, file_or_filename, parseAll=False): """ Execute the parse expression on the given file or filename. If a filename is specified (instead of a file object), @@ -2364,27 +2579,30 @@ class ParserElement(object): if ParserElement.verbose_stacktrace: raise else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace + # catch and re-raise exception from here, clearing out pyparsing internal stack trace + if getattr(exc, '__traceback__', None) is not None: + exc.__traceback__ = self._trim_traceback(exc.__traceback__) raise exc - def __eq__(self,other): - if isinstance(other, ParserElement): - return self is other or vars(self) == vars(other) + def __eq__(self, other): + if self is other: + return True elif isinstance(other, basestring): return self.matches(other) - else: - return super(ParserElement,self)==other + elif isinstance(other, ParserElement): + return vars(self) == vars(other) + return False - def __ne__(self,other): + def __ne__(self, other): return not (self == other) def __hash__(self): - return hash(id(self)) + return id(self) - def __req__(self,other): + def __req__(self, other): return self == other - def __rne__(self,other): + def __rne__(self, other): return not (self == other) def matches(self, testString, parseAll=True): @@ -2408,7 +2626,8 @@ class ParserElement(object): return False def runTests(self, tests, parseAll=True, comment='#', - fullDump=True, printResults=True, failureTests=False, postParse=None): + fullDump=True, printResults=True, failureTests=False, postParse=None, + file=None): """ Execute the parse expression on a series of test strings, showing each test, the parsed results or where the parse failed. Quick and easy way to @@ -2425,6 +2644,8 @@ class ParserElement(object): - failureTests - (default= ``False``) indicates if these tests are expected to fail parsing - postParse - (default= ``None``) optional callback for successful parse results; called as `fn(test_string, parse_results)` and returns a string to be added to the test output + - file - (default=``None``) optional file-like object to which test output will be written; + if None, will default to ``sys.stdout`` Returns: a (success, results) tuple, where success indicates that all tests succeeded (or failed if ``failureTests`` is True), and the results contain a list of lines of each @@ -2504,37 +2725,34 @@ class ParserElement(object): tests = list(map(str.strip, tests.rstrip().splitlines())) if isinstance(comment, basestring): comment = Literal(comment) + if file is None: + file = sys.stdout + print_ = file.write + allResults = [] comments = [] success = True + NL = Literal(r'\n').addParseAction(replaceWith('\n')).ignore(quotedString) + BOM = u'\ufeff' for t in tests: if comment is not None and comment.matches(t, False) or comments and not t: comments.append(t) continue if not t: continue - out = ['\n'.join(comments), t] + out = ['\n' + '\n'.join(comments) if comments else '', t] comments = [] try: # convert newline marks to actual newlines, and strip leading BOM if present - t = t.replace(r'\n','\n').lstrip('\ufeff') + t = NL.transformString(t.lstrip(BOM)) result = self.parseString(t, parseAll=parseAll) - out.append(result.dump(full=fullDump)) - success = success and not failureTests - if postParse is not None: - try: - pp_value = postParse(t, result) - if pp_value is not None: - out.append(str(pp_value)) - except Exception as e: - out.append("{0} failed: {1}: {2}".format(postParse.__name__, type(e).__name__, e)) except ParseBaseException as pe: fatal = "(FATAL)" if isinstance(pe, ParseFatalException) else "" if '\n' in t: out.append(line(pe.loc, t)) - out.append(' '*(col(pe.loc,t)-1) + '^' + fatal) + out.append(' ' * (col(pe.loc, t) - 1) + '^' + fatal) else: - out.append(' '*pe.loc + '^' + fatal) + out.append(' ' * pe.loc + '^' + fatal) out.append("FAIL: " + str(pe)) success = success and failureTests result = pe @@ -2542,30 +2760,80 @@ class ParserElement(object): out.append("FAIL-EXCEPTION: " + str(exc)) success = success and failureTests result = exc + else: + success = success and not failureTests + if postParse is not None: + try: + pp_value = postParse(t, result) + if pp_value is not None: + if isinstance(pp_value, ParseResults): + out.append(pp_value.dump()) + else: + out.append(str(pp_value)) + else: + out.append(result.dump()) + except Exception as e: + out.append(result.dump(full=fullDump)) + out.append("{0} failed: {1}: {2}".format(postParse.__name__, type(e).__name__, e)) + else: + out.append(result.dump(full=fullDump)) if printResults: if fullDump: out.append('') - print('\n'.join(out)) + print_('\n'.join(out)) allResults.append((t, result)) return success, allResults +class _PendingSkip(ParserElement): + # internal placeholder class to hold a place were '...' is added to a parser element, + # once another ParserElement is added, this placeholder will be replaced with a SkipTo + def __init__(self, expr, must_skip=False): + super(_PendingSkip, self).__init__() + self.strRepr = str(expr + Empty()).replace('Empty', '...') + self.name = self.strRepr + self.anchor = expr + self.must_skip = must_skip + + def __add__(self, other): + skipper = SkipTo(other).setName("...")("_skipped*") + if self.must_skip: + def must_skip(t): + if not t._skipped or t._skipped.asList() == ['']: + del t[0] + t.pop("_skipped", None) + def show_skip(t): + if t._skipped.asList()[-1:] == ['']: + skipped = t.pop('_skipped') + t['_skipped'] = 'missing <' + repr(self.anchor) + '>' + return (self.anchor + skipper().addParseAction(must_skip) + | skipper().addParseAction(show_skip)) + other + + return self.anchor + skipper + other + + def __repr__(self): + return self.strRepr + + def parseImpl(self, *args): + raise Exception("use of `...` expression without following SkipTo target expression") + + class Token(ParserElement): """Abstract :class:`ParserElement` subclass, for defining atomic matching patterns. """ - def __init__( self ): - super(Token,self).__init__( savelist=False ) + def __init__(self): + super(Token, self).__init__(savelist=False) class Empty(Token): """An empty token, will always match. """ - def __init__( self ): - super(Empty,self).__init__() + def __init__(self): + super(Empty, self).__init__() self.name = "Empty" self.mayReturnEmpty = True self.mayIndexError = False @@ -2574,14 +2842,14 @@ class Empty(Token): class NoMatch(Token): """A token that will never match. """ - def __init__( self ): - super(NoMatch,self).__init__() + def __init__(self): + super(NoMatch, self).__init__() self.name = "NoMatch" self.mayReturnEmpty = True self.mayIndexError = False self.errmsg = "Unmatchable token" - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): raise ParseException(instring, loc, self.errmsg, self) @@ -2599,8 +2867,8 @@ class Literal(Token): For keyword matching (force word break before and after the matched string), use :class:`Keyword` or :class:`CaselessKeyword`. """ - def __init__( self, matchString ): - super(Literal,self).__init__() + def __init__(self, matchString): + super(Literal, self).__init__() self.match = matchString self.matchLen = len(matchString) try: @@ -2614,15 +2882,22 @@ class Literal(Token): self.mayReturnEmpty = False self.mayIndexError = False - # Performance tuning: this routine gets called a *lot* - # if this is a single character match string and the first character matches, - # short-circuit as quickly as possible, and avoid calling startswith - #~ @profile - def parseImpl( self, instring, loc, doActions=True ): - if (instring[loc] == self.firstMatchChar and - (self.matchLen==1 or instring.startswith(self.match,loc)) ): - return loc+self.matchLen, self.match + # Performance tuning: modify __class__ to select + # a parseImpl optimized for single-character check + if self.matchLen == 1 and type(self) is Literal: + self.__class__ = _SingleCharLiteral + + def parseImpl(self, instring, loc, doActions=True): + if instring[loc] == self.firstMatchChar and instring.startswith(self.match, loc): + return loc + self.matchLen, self.match raise ParseException(instring, loc, self.errmsg, self) + +class _SingleCharLiteral(Literal): + def parseImpl(self, instring, loc, doActions=True): + if instring[loc] == self.firstMatchChar: + return loc + 1, self.match + raise ParseException(instring, loc, self.errmsg, self) + _L = Literal ParserElement._literalStringClass = Literal @@ -2651,10 +2926,10 @@ class Keyword(Token): For case-insensitive matching, use :class:`CaselessKeyword`. """ - DEFAULT_KEYWORD_CHARS = alphanums+"_$" + DEFAULT_KEYWORD_CHARS = alphanums + "_$" - def __init__( self, matchString, identChars=None, caseless=False ): - super(Keyword,self).__init__() + def __init__(self, matchString, identChars=None, caseless=False): + super(Keyword, self).__init__() if identChars is None: identChars = Keyword.DEFAULT_KEYWORD_CHARS self.match = matchString @@ -2663,7 +2938,7 @@ class Keyword(Token): self.firstMatchChar = matchString[0] except IndexError: warnings.warn("null string passed to Keyword; use Empty() instead", - SyntaxWarning, stacklevel=2) + SyntaxWarning, stacklevel=2) self.name = '"%s"' % self.match self.errmsg = "Expected " + self.name self.mayReturnEmpty = False @@ -2674,27 +2949,32 @@ class Keyword(Token): identChars = identChars.upper() self.identChars = set(identChars) - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): if self.caseless: - if ( (instring[ loc:loc+self.matchLen ].upper() == self.caselessmatch) and - (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen].upper() not in self.identChars) and - (loc == 0 or instring[loc-1].upper() not in self.identChars) ): - return loc+self.matchLen, self.match + if ((instring[loc:loc + self.matchLen].upper() == self.caselessmatch) + and (loc >= len(instring) - self.matchLen + or instring[loc + self.matchLen].upper() not in self.identChars) + and (loc == 0 + or instring[loc - 1].upper() not in self.identChars)): + return loc + self.matchLen, self.match + else: - if (instring[loc] == self.firstMatchChar and - (self.matchLen==1 or instring.startswith(self.match,loc)) and - (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen] not in self.identChars) and - (loc == 0 or instring[loc-1] not in self.identChars) ): - return loc+self.matchLen, self.match + if instring[loc] == self.firstMatchChar: + if ((self.matchLen == 1 or instring.startswith(self.match, loc)) + and (loc >= len(instring) - self.matchLen + or instring[loc + self.matchLen] not in self.identChars) + and (loc == 0 or instring[loc - 1] not in self.identChars)): + return loc + self.matchLen, self.match + raise ParseException(instring, loc, self.errmsg, self) def copy(self): - c = super(Keyword,self).copy() + c = super(Keyword, self).copy() c.identChars = Keyword.DEFAULT_KEYWORD_CHARS return c @staticmethod - def setDefaultKeywordChars( chars ): + def setDefaultKeywordChars(chars): """Overrides the default Keyword chars """ Keyword.DEFAULT_KEYWORD_CHARS = chars @@ -2710,16 +2990,16 @@ class CaselessLiteral(Literal): (Contrast with example for :class:`CaselessKeyword`.) """ - def __init__( self, matchString ): - super(CaselessLiteral,self).__init__( matchString.upper() ) + def __init__(self, matchString): + super(CaselessLiteral, self).__init__(matchString.upper()) # Preserve the defining literal. self.returnString = matchString self.name = "'%s'" % self.returnString self.errmsg = "Expected " + self.name - def parseImpl( self, instring, loc, doActions=True ): - if instring[ loc:loc+self.matchLen ].upper() == self.match: - return loc+self.matchLen, self.returnString + def parseImpl(self, instring, loc, doActions=True): + if instring[loc:loc + self.matchLen].upper() == self.match: + return loc + self.matchLen, self.returnString raise ParseException(instring, loc, self.errmsg, self) class CaselessKeyword(Keyword): @@ -2732,8 +3012,8 @@ class CaselessKeyword(Keyword): (Contrast with example for :class:`CaselessLiteral`.) """ - def __init__( self, matchString, identChars=None ): - super(CaselessKeyword,self).__init__( matchString, identChars, caseless=True ) + def __init__(self, matchString, identChars=None): + super(CaselessKeyword, self).__init__(matchString, identChars, caseless=True) class CloseMatch(Token): """A variation on :class:`Literal` which matches "close" matches, @@ -2769,7 +3049,7 @@ class CloseMatch(Token): patt.parseString("ATCAXCGAAXGGA") # -> (['ATCAXCGAAXGGA'], {'mismatches': [[4, 9]], 'original': ['ATCATCGAATGGA']}) """ def __init__(self, match_string, maxMismatches=1): - super(CloseMatch,self).__init__() + super(CloseMatch, self).__init__() self.name = match_string self.match_string = match_string self.maxMismatches = maxMismatches @@ -2777,7 +3057,7 @@ class CloseMatch(Token): self.mayIndexError = False self.mayReturnEmpty = False - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): start = loc instrlen = len(instring) maxloc = start + len(self.match_string) @@ -2788,8 +3068,8 @@ class CloseMatch(Token): mismatches = [] maxMismatches = self.maxMismatches - for match_stringloc,s_m in enumerate(zip(instring[loc:maxloc], self.match_string)): - src,mat = s_m + for match_stringloc, s_m in enumerate(zip(instring[loc:maxloc], match_string)): + src, mat = s_m if src != mat: mismatches.append(match_stringloc) if len(mismatches) > maxMismatches: @@ -2797,7 +3077,7 @@ class CloseMatch(Token): else: loc = match_stringloc + 1 results = ParseResults([instring[start:loc]]) - results['original'] = self.match_string + results['original'] = match_string results['mismatches'] = mismatches return loc, results @@ -2849,7 +3129,7 @@ class Word(Token): capital_word = Word(alphas.upper(), alphas.lower()) # hostnames are alphanumeric, with leading alpha, and '-' - hostname = Word(alphas, alphanums+'-') + hostname = Word(alphas, alphanums + '-') # roman numeral (not a strict parser, accepts invalid mix of characters) roman = Word("IVXLCDM") @@ -2857,15 +3137,16 @@ class Word(Token): # any string of non-whitespace characters, except for ',' csv_value = Word(printables, excludeChars=",") """ - def __init__( self, initChars, bodyChars=None, min=1, max=0, exact=0, asKeyword=False, excludeChars=None ): - super(Word,self).__init__() + def __init__(self, initChars, bodyChars=None, min=1, max=0, exact=0, asKeyword=False, excludeChars=None): + super(Word, self).__init__() if excludeChars: + excludeChars = set(excludeChars) initChars = ''.join(c for c in initChars if c not in excludeChars) if bodyChars: bodyChars = ''.join(c for c in bodyChars if c not in excludeChars) self.initCharsOrig = initChars self.initChars = set(initChars) - if bodyChars : + if bodyChars: self.bodyCharsOrig = bodyChars self.bodyChars = set(bodyChars) else: @@ -2893,34 +3174,28 @@ class Word(Token): self.mayIndexError = False self.asKeyword = asKeyword - if ' ' not in self.initCharsOrig+self.bodyCharsOrig and (min==1 and max==0 and exact==0): + if ' ' not in self.initCharsOrig + self.bodyCharsOrig and (min == 1 and max == 0 and exact == 0): if self.bodyCharsOrig == self.initCharsOrig: self.reString = "[%s]+" % _escapeRegexRangeChars(self.initCharsOrig) elif len(self.initCharsOrig) == 1: - self.reString = "%s[%s]*" % \ - (re.escape(self.initCharsOrig), - _escapeRegexRangeChars(self.bodyCharsOrig),) + self.reString = "%s[%s]*" % (re.escape(self.initCharsOrig), + _escapeRegexRangeChars(self.bodyCharsOrig),) else: - self.reString = "[%s][%s]*" % \ - (_escapeRegexRangeChars(self.initCharsOrig), - _escapeRegexRangeChars(self.bodyCharsOrig),) + self.reString = "[%s][%s]*" % (_escapeRegexRangeChars(self.initCharsOrig), + _escapeRegexRangeChars(self.bodyCharsOrig),) if self.asKeyword: - self.reString = r"\b"+self.reString+r"\b" + self.reString = r"\b" + self.reString + r"\b" + try: - self.re = re.compile( self.reString ) + self.re = re.compile(self.reString) except Exception: self.re = None + else: + self.re_match = self.re.match + self.__class__ = _WordRegex - def parseImpl( self, instring, loc, doActions=True ): - if self.re: - result = self.re.match(instring,loc) - if not result: - raise ParseException(instring, loc, self.errmsg, self) - - loc = result.end() - return loc, result.group() - - if not(instring[ loc ] in self.initChars): + def parseImpl(self, instring, loc, doActions=True): + if instring[loc] not in self.initChars: raise ParseException(instring, loc, self.errmsg, self) start = loc @@ -2928,17 +3203,18 @@ class Word(Token): instrlen = len(instring) bodychars = self.bodyChars maxloc = start + self.maxLen - maxloc = min( maxloc, instrlen ) + maxloc = min(maxloc, instrlen) while loc < maxloc and instring[loc] in bodychars: loc += 1 throwException = False if loc - start < self.minLen: throwException = True - if self.maxSpecified and loc < instrlen and instring[loc] in bodychars: + elif self.maxSpecified and loc < instrlen and instring[loc] in bodychars: throwException = True - if self.asKeyword: - if (start>0 and instring[start-1] in bodychars) or (loc<instrlen and instring[loc] in bodychars): + elif self.asKeyword: + if (start > 0 and instring[start - 1] in bodychars + or loc < instrlen and instring[loc] in bodychars): throwException = True if throwException: @@ -2946,38 +3222,49 @@ class Word(Token): return loc, instring[start:loc] - def __str__( self ): + def __str__(self): try: - return super(Word,self).__str__() + return super(Word, self).__str__() except Exception: pass - if self.strRepr is None: def charsAsStr(s): - if len(s)>4: - return s[:4]+"..." + if len(s) > 4: + return s[:4] + "..." else: return s - if ( self.initCharsOrig != self.bodyCharsOrig ): - self.strRepr = "W:(%s,%s)" % ( charsAsStr(self.initCharsOrig), charsAsStr(self.bodyCharsOrig) ) + if self.initCharsOrig != self.bodyCharsOrig: + self.strRepr = "W:(%s, %s)" % (charsAsStr(self.initCharsOrig), charsAsStr(self.bodyCharsOrig)) else: self.strRepr = "W:(%s)" % charsAsStr(self.initCharsOrig) return self.strRepr +class _WordRegex(Word): + def parseImpl(self, instring, loc, doActions=True): + result = self.re_match(instring, loc) + if not result: + raise ParseException(instring, loc, self.errmsg, self) -class Char(Word): + loc = result.end() + return loc, result.group() + + +class Char(_WordRegex): """A short-cut class for defining ``Word(characters, exact=1)``, when defining a match of any single character in a string of characters. """ - def __init__(self, charset): - super(Char, self).__init__(charset, exact=1) - self.reString = "[%s]" % _escapeRegexRangeChars(self.initCharsOrig) - self.re = re.compile( self.reString ) + def __init__(self, charset, asKeyword=False, excludeChars=None): + super(Char, self).__init__(charset, exact=1, asKeyword=asKeyword, excludeChars=excludeChars) + self.reString = "[%s]" % _escapeRegexRangeChars(''.join(self.initChars)) + if asKeyword: + self.reString = r"\b%s\b" % self.reString + self.re = re.compile(self.reString) + self.re_match = self.re.match class Regex(Token): @@ -2987,26 +3274,35 @@ class Regex(Token): If the given regex contains named groups (defined using ``(?P<name>...)``), these will be preserved as named parse results. + If instead of the Python stdlib re module you wish to use a different RE module + (such as the `regex` module), you can replace it by either building your + Regex object with a compiled RE that was compiled using regex: + Example:: realnum = Regex(r"[+-]?\d+\.\d*") date = Regex(r'(?P<year>\d{4})-(?P<month>\d\d?)-(?P<day>\d\d?)') # ref: https://stackoverflow.com/questions/267399/how-do-you-match-only-valid-roman-numerals-with-a-regular-expression roman = Regex(r"M{0,4}(CM|CD|D?{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})") + + # use regex module instead of stdlib re module to construct a Regex using + # a compiled regular expression + import regex + parser = pp.Regex(regex.compile(r'[0-9]')) + """ - compiledREtype = type(re.compile("[A-Z]")) - def __init__( self, pattern, flags=0, asGroupList=False, asMatch=False): + def __init__(self, pattern, flags=0, asGroupList=False, asMatch=False): """The parameters ``pattern`` and ``flags`` are passed to the ``re.compile()`` function as-is. See the Python `re module <https://docs.python.org/3/library/re.html>`_ module for an explanation of the acceptable patterns and flags. """ - super(Regex,self).__init__() + super(Regex, self).__init__() if isinstance(pattern, basestring): if not pattern: warnings.warn("null string passed to Regex; use Empty() instead", - SyntaxWarning, stacklevel=2) + SyntaxWarning, stacklevel=2) self.pattern = pattern self.flags = flags @@ -3016,46 +3312,64 @@ class Regex(Token): self.reString = self.pattern except sre_constants.error: warnings.warn("invalid pattern (%s) passed to Regex" % pattern, - SyntaxWarning, stacklevel=2) + SyntaxWarning, stacklevel=2) raise - elif isinstance(pattern, Regex.compiledREtype): + elif hasattr(pattern, 'pattern') and hasattr(pattern, 'match'): self.re = pattern - self.pattern = \ - self.reString = str(pattern) + self.pattern = self.reString = pattern.pattern self.flags = flags else: - raise ValueError("Regex may only be constructed with a string or a compiled RE object") + raise TypeError("Regex may only be constructed with a string or a compiled RE object") + + self.re_match = self.re.match self.name = _ustr(self) self.errmsg = "Expected " + self.name self.mayIndexError = False - self.mayReturnEmpty = True + self.mayReturnEmpty = self.re_match("") is not None self.asGroupList = asGroupList self.asMatch = asMatch + if self.asGroupList: + self.parseImpl = self.parseImplAsGroupList + if self.asMatch: + self.parseImpl = self.parseImplAsMatch - def parseImpl( self, instring, loc, doActions=True ): - result = self.re.match(instring,loc) + def parseImpl(self, instring, loc, doActions=True): + result = self.re_match(instring, loc) if not result: raise ParseException(instring, loc, self.errmsg, self) loc = result.end() - if self.asMatch: - ret = result - elif self.asGroupList: - ret = result.groups() - else: - ret = ParseResults(result.group()) - d = result.groupdict() - if d: - for k, v in d.items(): - ret[k] = v - return loc,ret + ret = ParseResults(result.group()) + d = result.groupdict() + if d: + for k, v in d.items(): + ret[k] = v + return loc, ret - def __str__( self ): + def parseImplAsGroupList(self, instring, loc, doActions=True): + result = self.re_match(instring, loc) + if not result: + raise ParseException(instring, loc, self.errmsg, self) + + loc = result.end() + ret = result.groups() + return loc, ret + + def parseImplAsMatch(self, instring, loc, doActions=True): + result = self.re_match(instring, loc) + if not result: + raise ParseException(instring, loc, self.errmsg, self) + + loc = result.end() + ret = result + return loc, ret + + def __str__(self): try: - return super(Regex,self).__str__() + return super(Regex, self).__str__() except Exception: pass @@ -3065,7 +3379,7 @@ class Regex(Token): return self.strRepr def sub(self, repl): - """ + r""" Return Regex with an attached parse action to transform the parsed result as if called using `re.sub(expr, repl, string) <https://docs.python.org/3/library/re.html#re.sub>`_. @@ -3077,12 +3391,12 @@ class Regex(Token): """ if self.asGroupList: warnings.warn("cannot use sub() with Regex(asGroupList=True)", - SyntaxWarning, stacklevel=2) + SyntaxWarning, stacklevel=2) raise SyntaxError() if self.asMatch and callable(repl): warnings.warn("cannot use sub() with a callable with Regex(asMatch=True)", - SyntaxWarning, stacklevel=2) + SyntaxWarning, stacklevel=2) raise SyntaxError() if self.asMatch: @@ -3102,20 +3416,20 @@ class QuotedString(Token): - quoteChar - string of one or more characters defining the quote delimiting string - escChar - character to escape quotes, typically backslash - (default= ``None`` ) + (default= ``None``) - escQuote - special quote sequence to escape an embedded quote string (such as SQL's ``""`` to escape an embedded ``"``) - (default= ``None`` ) + (default= ``None``) - multiline - boolean indicating whether quotes can span - multiple lines (default= ``False`` ) + multiple lines (default= ``False``) - unquoteResults - boolean indicating whether the matched text - should be unquoted (default= ``True`` ) + should be unquoted (default= ``True``) - endQuoteChar - string of one or more characters defining the end of the quote delimited string (default= ``None`` => same as quoteChar) - convertWhitespaceEscapes - convert escaped whitespace (``'\t'``, ``'\n'``, etc.) to actual whitespace - (default= ``True`` ) + (default= ``True``) Example:: @@ -3132,13 +3446,14 @@ class QuotedString(Token): [['This is the "quote"']] [['This is the quote with "embedded" quotes']] """ - def __init__( self, quoteChar, escChar=None, escQuote=None, multiline=False, unquoteResults=True, endQuoteChar=None, convertWhitespaceEscapes=True): - super(QuotedString,self).__init__() + def __init__(self, quoteChar, escChar=None, escQuote=None, multiline=False, + unquoteResults=True, endQuoteChar=None, convertWhitespaceEscapes=True): + super(QuotedString, self).__init__() # remove white space from quote chars - wont work anyway quoteChar = quoteChar.strip() if not quoteChar: - warnings.warn("quoteChar cannot be the empty string",SyntaxWarning,stacklevel=2) + warnings.warn("quoteChar cannot be the empty string", SyntaxWarning, stacklevel=2) raise SyntaxError() if endQuoteChar is None: @@ -3146,7 +3461,7 @@ class QuotedString(Token): else: endQuoteChar = endQuoteChar.strip() if not endQuoteChar: - warnings.warn("endQuoteChar cannot be the empty string",SyntaxWarning,stacklevel=2) + warnings.warn("endQuoteChar cannot be the empty string", SyntaxWarning, stacklevel=2) raise SyntaxError() self.quoteChar = quoteChar @@ -3161,35 +3476,34 @@ class QuotedString(Token): if multiline: self.flags = re.MULTILINE | re.DOTALL - self.pattern = r'%s(?:[^%s%s]' % \ - ( re.escape(self.quoteChar), - _escapeRegexRangeChars(self.endQuoteChar[0]), - (escChar is not None and _escapeRegexRangeChars(escChar) or '') ) + self.pattern = r'%s(?:[^%s%s]' % (re.escape(self.quoteChar), + _escapeRegexRangeChars(self.endQuoteChar[0]), + (escChar is not None and _escapeRegexRangeChars(escChar) or '')) else: self.flags = 0 - self.pattern = r'%s(?:[^%s\n\r%s]' % \ - ( re.escape(self.quoteChar), - _escapeRegexRangeChars(self.endQuoteChar[0]), - (escChar is not None and _escapeRegexRangeChars(escChar) or '') ) + self.pattern = r'%s(?:[^%s\n\r%s]' % (re.escape(self.quoteChar), + _escapeRegexRangeChars(self.endQuoteChar[0]), + (escChar is not None and _escapeRegexRangeChars(escChar) or '')) if len(self.endQuoteChar) > 1: self.pattern += ( '|(?:' + ')|(?:'.join("%s[^%s]" % (re.escape(self.endQuoteChar[:i]), - _escapeRegexRangeChars(self.endQuoteChar[i])) - for i in range(len(self.endQuoteChar)-1,0,-1)) + ')' - ) + _escapeRegexRangeChars(self.endQuoteChar[i])) + for i in range(len(self.endQuoteChar) - 1, 0, -1)) + ')') + if escQuote: self.pattern += (r'|(?:%s)' % re.escape(escQuote)) if escChar: self.pattern += (r'|(?:%s.)' % re.escape(escChar)) - self.escCharReplacePattern = re.escape(self.escChar)+"(.)" + self.escCharReplacePattern = re.escape(self.escChar) + "(.)" self.pattern += (r')*%s' % re.escape(self.endQuoteChar)) try: self.re = re.compile(self.pattern, self.flags) self.reString = self.pattern + self.re_match = self.re.match except sre_constants.error: warnings.warn("invalid pattern (%s) passed to Regex" % self.pattern, - SyntaxWarning, stacklevel=2) + SyntaxWarning, stacklevel=2) raise self.name = _ustr(self) @@ -3197,8 +3511,8 @@ class QuotedString(Token): self.mayIndexError = False self.mayReturnEmpty = True - def parseImpl( self, instring, loc, doActions=True ): - result = instring[loc] == self.firstQuoteChar and self.re.match(instring,loc) or None + def parseImpl(self, instring, loc, doActions=True): + result = instring[loc] == self.firstQuoteChar and self.re_match(instring, loc) or None if not result: raise ParseException(instring, loc, self.errmsg, self) @@ -3208,18 +3522,18 @@ class QuotedString(Token): if self.unquoteResults: # strip off quotes - ret = ret[self.quoteCharLen:-self.endQuoteCharLen] + ret = ret[self.quoteCharLen: -self.endQuoteCharLen] - if isinstance(ret,basestring): + if isinstance(ret, basestring): # replace escaped whitespace if '\\' in ret and self.convertWhitespaceEscapes: ws_map = { - r'\t' : '\t', - r'\n' : '\n', - r'\f' : '\f', - r'\r' : '\r', + r'\t': '\t', + r'\n': '\n', + r'\f': '\f', + r'\r': '\r', } - for wslit,wschar in ws_map.items(): + for wslit, wschar in ws_map.items(): ret = ret.replace(wslit, wschar) # replace escaped characters @@ -3232,9 +3546,9 @@ class QuotedString(Token): return loc, ret - def __str__( self ): + def __str__(self): try: - return super(QuotedString,self).__str__() + return super(QuotedString, self).__str__() except Exception: pass @@ -3264,15 +3578,14 @@ class CharsNotIn(Token): ['dkls', 'lsdkjf', 's12 34', '@!#', '213'] """ - def __init__( self, notChars, min=1, max=0, exact=0 ): - super(CharsNotIn,self).__init__() + def __init__(self, notChars, min=1, max=0, exact=0): + super(CharsNotIn, self).__init__() self.skipWhitespace = False self.notChars = notChars if min < 1: - raise ValueError( - "cannot specify a minimum length < 1; use " + - "Optional(CharsNotIn()) if zero-length char group is permitted") + raise ValueError("cannot specify a minimum length < 1; use " + "Optional(CharsNotIn()) if zero-length char group is permitted") self.minLen = min @@ -3287,19 +3600,18 @@ class CharsNotIn(Token): self.name = _ustr(self) self.errmsg = "Expected " + self.name - self.mayReturnEmpty = ( self.minLen == 0 ) + self.mayReturnEmpty = (self.minLen == 0) self.mayIndexError = False - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): if instring[loc] in self.notChars: raise ParseException(instring, loc, self.errmsg, self) start = loc loc += 1 notchars = self.notChars - maxlen = min( start+self.maxLen, len(instring) ) - while loc < maxlen and \ - (instring[loc] not in notchars): + maxlen = min(start + self.maxLen, len(instring)) + while loc < maxlen and instring[loc] not in notchars: loc += 1 if loc - start < self.minLen: @@ -3307,7 +3619,7 @@ class CharsNotIn(Token): return loc, instring[start:loc] - def __str__( self ): + def __str__(self): try: return super(CharsNotIn, self).__str__() except Exception: @@ -3336,30 +3648,30 @@ class White(Token): '\n': '<LF>', '\r': '<CR>', '\f': '<FF>', - 'u\00A0': '<NBSP>', - 'u\1680': '<OGHAM_SPACE_MARK>', - 'u\180E': '<MONGOLIAN_VOWEL_SEPARATOR>', - 'u\2000': '<EN_QUAD>', - 'u\2001': '<EM_QUAD>', - 'u\2002': '<EN_SPACE>', - 'u\2003': '<EM_SPACE>', - 'u\2004': '<THREE-PER-EM_SPACE>', - 'u\2005': '<FOUR-PER-EM_SPACE>', - 'u\2006': '<SIX-PER-EM_SPACE>', - 'u\2007': '<FIGURE_SPACE>', - 'u\2008': '<PUNCTUATION_SPACE>', - 'u\2009': '<THIN_SPACE>', - 'u\200A': '<HAIR_SPACE>', - 'u\200B': '<ZERO_WIDTH_SPACE>', - 'u\202F': '<NNBSP>', - 'u\205F': '<MMSP>', - 'u\3000': '<IDEOGRAPHIC_SPACE>', + u'\u00A0': '<NBSP>', + u'\u1680': '<OGHAM_SPACE_MARK>', + u'\u180E': '<MONGOLIAN_VOWEL_SEPARATOR>', + u'\u2000': '<EN_QUAD>', + u'\u2001': '<EM_QUAD>', + u'\u2002': '<EN_SPACE>', + u'\u2003': '<EM_SPACE>', + u'\u2004': '<THREE-PER-EM_SPACE>', + u'\u2005': '<FOUR-PER-EM_SPACE>', + u'\u2006': '<SIX-PER-EM_SPACE>', + u'\u2007': '<FIGURE_SPACE>', + u'\u2008': '<PUNCTUATION_SPACE>', + u'\u2009': '<THIN_SPACE>', + u'\u200A': '<HAIR_SPACE>', + u'\u200B': '<ZERO_WIDTH_SPACE>', + u'\u202F': '<NNBSP>', + u'\u205F': '<MMSP>', + u'\u3000': '<IDEOGRAPHIC_SPACE>', } def __init__(self, ws=" \t\r\n", min=1, max=0, exact=0): - super(White,self).__init__() + super(White, self).__init__() self.matchWhite = ws - self.setWhitespaceChars( "".join(c for c in self.whiteChars if c not in self.matchWhite) ) - #~ self.leaveWhitespace() + self.setWhitespaceChars("".join(c for c in self.whiteChars if c not in self.matchWhite)) + # ~ self.leaveWhitespace() self.name = ("".join(White.whiteStrs[c] for c in self.matchWhite)) self.mayReturnEmpty = True self.errmsg = "Expected " + self.name @@ -3375,13 +3687,13 @@ class White(Token): self.maxLen = exact self.minLen = exact - def parseImpl( self, instring, loc, doActions=True ): - if not(instring[ loc ] in self.matchWhite): + def parseImpl(self, instring, loc, doActions=True): + if instring[loc] not in self.matchWhite: raise ParseException(instring, loc, self.errmsg, self) start = loc loc += 1 maxloc = start + self.maxLen - maxloc = min( maxloc, len(instring) ) + maxloc = min(maxloc, len(instring)) while loc < maxloc and instring[loc] in self.matchWhite: loc += 1 @@ -3392,9 +3704,9 @@ class White(Token): class _PositionToken(Token): - def __init__( self ): - super(_PositionToken,self).__init__() - self.name=self.__class__.__name__ + def __init__(self): + super(_PositionToken, self).__init__() + self.name = self.__class__.__name__ self.mayReturnEmpty = True self.mayIndexError = False @@ -3402,30 +3714,30 @@ class GoToColumn(_PositionToken): """Token to advance to a specific column of input text; useful for tabular report scraping. """ - def __init__( self, colno ): - super(GoToColumn,self).__init__() + def __init__(self, colno): + super(GoToColumn, self).__init__() self.col = colno - def preParse( self, instring, loc ): - if col(loc,instring) != self.col: + def preParse(self, instring, loc): + if col(loc, instring) != self.col: instrlen = len(instring) if self.ignoreExprs: - loc = self._skipIgnorables( instring, loc ) - while loc < instrlen and instring[loc].isspace() and col( loc, instring ) != self.col : + loc = self._skipIgnorables(instring, loc) + while loc < instrlen and instring[loc].isspace() and col(loc, instring) != self.col: loc += 1 return loc - def parseImpl( self, instring, loc, doActions=True ): - thiscol = col( loc, instring ) + def parseImpl(self, instring, loc, doActions=True): + thiscol = col(loc, instring) if thiscol > self.col: - raise ParseException( instring, loc, "Text not in expected column", self ) + raise ParseException(instring, loc, "Text not in expected column", self) newloc = loc + self.col - thiscol - ret = instring[ loc: newloc ] + ret = instring[loc: newloc] return newloc, ret class LineStart(_PositionToken): - """Matches if current position is at the beginning of a line within + r"""Matches if current position is at the beginning of a line within the parse string Example:: @@ -3446,11 +3758,11 @@ class LineStart(_PositionToken): ['AAA', ' and this line'] """ - def __init__( self ): - super(LineStart,self).__init__() + def __init__(self): + super(LineStart, self).__init__() self.errmsg = "Expected start of line" - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): if col(loc, instring) == 1: return loc, [] raise ParseException(instring, loc, self.errmsg, self) @@ -3459,19 +3771,19 @@ class LineEnd(_PositionToken): """Matches if current position is at the end of a line within the parse string """ - def __init__( self ): - super(LineEnd,self).__init__() - self.setWhitespaceChars( ParserElement.DEFAULT_WHITE_CHARS.replace("\n","") ) + def __init__(self): + super(LineEnd, self).__init__() + self.setWhitespaceChars(ParserElement.DEFAULT_WHITE_CHARS.replace("\n", "")) self.errmsg = "Expected end of line" - def parseImpl( self, instring, loc, doActions=True ): - if loc<len(instring): + def parseImpl(self, instring, loc, doActions=True): + if loc < len(instring): if instring[loc] == "\n": - return loc+1, "\n" + return loc + 1, "\n" else: raise ParseException(instring, loc, self.errmsg, self) elif loc == len(instring): - return loc+1, [] + return loc + 1, [] else: raise ParseException(instring, loc, self.errmsg, self) @@ -3479,29 +3791,29 @@ class StringStart(_PositionToken): """Matches if current position is at the beginning of the parse string """ - def __init__( self ): - super(StringStart,self).__init__() + def __init__(self): + super(StringStart, self).__init__() self.errmsg = "Expected start of text" - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): if loc != 0: # see if entire string up to here is just whitespace and ignoreables - if loc != self.preParse( instring, 0 ): + if loc != self.preParse(instring, 0): raise ParseException(instring, loc, self.errmsg, self) return loc, [] class StringEnd(_PositionToken): """Matches if current position is at the end of the parse string """ - def __init__( self ): - super(StringEnd,self).__init__() + def __init__(self): + super(StringEnd, self).__init__() self.errmsg = "Expected end of text" - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): if loc < len(instring): raise ParseException(instring, loc, self.errmsg, self) elif loc == len(instring): - return loc+1, [] + return loc + 1, [] elif loc > len(instring): return loc, [] else: @@ -3516,15 +3828,15 @@ class WordStart(_PositionToken): the beginning of the string being parsed, or at the beginning of a line. """ - def __init__(self, wordChars = printables): - super(WordStart,self).__init__() + def __init__(self, wordChars=printables): + super(WordStart, self).__init__() self.wordChars = set(wordChars) self.errmsg = "Not at the start of a word" - def parseImpl(self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): if loc != 0: - if (instring[loc-1] in self.wordChars or - instring[loc] not in self.wordChars): + if (instring[loc - 1] in self.wordChars + or instring[loc] not in self.wordChars): raise ParseException(instring, loc, self.errmsg, self) return loc, [] @@ -3536,17 +3848,17 @@ class WordEnd(_PositionToken): will also match at the end of the string being parsed, or at the end of a line. """ - def __init__(self, wordChars = printables): - super(WordEnd,self).__init__() + def __init__(self, wordChars=printables): + super(WordEnd, self).__init__() self.wordChars = set(wordChars) self.skipWhitespace = False self.errmsg = "Not at the end of a word" - def parseImpl(self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): instrlen = len(instring) - if instrlen>0 and loc<instrlen: + if instrlen > 0 and loc < instrlen: if (instring[loc] in self.wordChars or - instring[loc-1] not in self.wordChars): + instring[loc - 1] not in self.wordChars): raise ParseException(instring, loc, self.errmsg, self) return loc, [] @@ -3555,90 +3867,89 @@ class ParseExpression(ParserElement): """Abstract subclass of ParserElement, for combining and post-processing parsed tokens. """ - def __init__( self, exprs, savelist = False ): - super(ParseExpression,self).__init__(savelist) - if isinstance( exprs, _generatorType ): + def __init__(self, exprs, savelist=False): + super(ParseExpression, self).__init__(savelist) + if isinstance(exprs, _generatorType): exprs = list(exprs) - if isinstance( exprs, basestring ): - self.exprs = [ ParserElement._literalStringClass( exprs ) ] - elif isinstance( exprs, Iterable ): + if isinstance(exprs, basestring): + self.exprs = [self._literalStringClass(exprs)] + elif isinstance(exprs, ParserElement): + self.exprs = [exprs] + elif isinstance(exprs, Iterable): exprs = list(exprs) # if sequence of strings provided, wrap with Literal - if all(isinstance(expr, basestring) for expr in exprs): - exprs = map(ParserElement._literalStringClass, exprs) + if any(isinstance(expr, basestring) for expr in exprs): + exprs = (self._literalStringClass(e) if isinstance(e, basestring) else e for e in exprs) self.exprs = list(exprs) else: try: - self.exprs = list( exprs ) + self.exprs = list(exprs) except TypeError: - self.exprs = [ exprs ] + self.exprs = [exprs] self.callPreparse = False - def __getitem__( self, i ): - return self.exprs[i] - - def append( self, other ): - self.exprs.append( other ) + def append(self, other): + self.exprs.append(other) self.strRepr = None return self - def leaveWhitespace( self ): + def leaveWhitespace(self): """Extends ``leaveWhitespace`` defined in base class, and also invokes ``leaveWhitespace`` on all contained expressions.""" self.skipWhitespace = False - self.exprs = [ e.copy() for e in self.exprs ] + self.exprs = [e.copy() for e in self.exprs] for e in self.exprs: e.leaveWhitespace() return self - def ignore( self, other ): - if isinstance( other, Suppress ): + def ignore(self, other): + if isinstance(other, Suppress): if other not in self.ignoreExprs: - super( ParseExpression, self).ignore( other ) + super(ParseExpression, self).ignore(other) for e in self.exprs: - e.ignore( self.ignoreExprs[-1] ) + e.ignore(self.ignoreExprs[-1]) else: - super( ParseExpression, self).ignore( other ) + super(ParseExpression, self).ignore(other) for e in self.exprs: - e.ignore( self.ignoreExprs[-1] ) + e.ignore(self.ignoreExprs[-1]) return self - def __str__( self ): + def __str__(self): try: - return super(ParseExpression,self).__str__() + return super(ParseExpression, self).__str__() except Exception: pass if self.strRepr is None: - self.strRepr = "%s:(%s)" % ( self.__class__.__name__, _ustr(self.exprs) ) + self.strRepr = "%s:(%s)" % (self.__class__.__name__, _ustr(self.exprs)) return self.strRepr - def streamline( self ): - super(ParseExpression,self).streamline() + def streamline(self): + super(ParseExpression, self).streamline() for e in self.exprs: e.streamline() - # collapse nested And's of the form And( And( And( a,b), c), d) to And( a,b,c,d ) + # collapse nested And's of the form And(And(And(a, b), c), d) to And(a, b, c, d) # but only if there are no parse actions or resultsNames on the nested And's # (likewise for Or's and MatchFirst's) - if ( len(self.exprs) == 2 ): + if len(self.exprs) == 2: other = self.exprs[0] - if ( isinstance( other, self.__class__ ) and - not(other.parseAction) and - other.resultsName is None and - not other.debug ): - self.exprs = other.exprs[:] + [ self.exprs[1] ] + if (isinstance(other, self.__class__) + and not other.parseAction + and other.resultsName is None + and not other.debug): + self.exprs = other.exprs[:] + [self.exprs[1]] self.strRepr = None self.mayReturnEmpty |= other.mayReturnEmpty self.mayIndexError |= other.mayIndexError other = self.exprs[-1] - if ( isinstance( other, self.__class__ ) and - not(other.parseAction) and - other.resultsName is None and - not other.debug ): + if (isinstance(other, self.__class__) + and not other.parseAction + and other.resultsName is None + and not other.debug): self.exprs = self.exprs[:-1] + other.exprs[:] self.strRepr = None self.mayReturnEmpty |= other.mayReturnEmpty @@ -3648,21 +3959,31 @@ class ParseExpression(ParserElement): return self - def setResultsName( self, name, listAllMatches=False ): - ret = super(ParseExpression,self).setResultsName(name,listAllMatches) - return ret - - def validate( self, validateTrace=[] ): - tmp = validateTrace[:]+[self] + def validate(self, validateTrace=None): + tmp = (validateTrace if validateTrace is not None else [])[:] + [self] for e in self.exprs: e.validate(tmp) - self.checkRecursion( [] ) + self.checkRecursion([]) def copy(self): - ret = super(ParseExpression,self).copy() + ret = super(ParseExpression, self).copy() ret.exprs = [e.copy() for e in self.exprs] return ret + def _setResultsName(self, name, listAllMatches=False): + if __diag__.warn_ungrouped_named_tokens_in_collection: + for e in self.exprs: + if isinstance(e, ParserElement) and e.resultsName: + warnings.warn("{0}: setting results name {1!r} on {2} expression " + "collides with {3!r} on contained expression".format("warn_ungrouped_named_tokens_in_collection", + name, + type(self).__name__, + e.resultsName), + stacklevel=3) + + return super(ParseExpression, self)._setResultsName(name, listAllMatches) + + class And(ParseExpression): """ Requires all given :class:`ParseExpression` s to be found in the given order. @@ -3676,33 +3997,59 @@ class And(ParseExpression): integer = Word(nums) name_expr = OneOrMore(Word(alphas)) - expr = And([integer("id"),name_expr("name"),integer("age")]) + expr = And([integer("id"), name_expr("name"), integer("age")]) # more easily written as: expr = integer("id") + name_expr("name") + integer("age") """ class _ErrorStop(Empty): def __init__(self, *args, **kwargs): - super(And._ErrorStop,self).__init__(*args, **kwargs) + super(And._ErrorStop, self).__init__(*args, **kwargs) self.name = '-' self.leaveWhitespace() - def __init__( self, exprs, savelist = True ): - super(And,self).__init__(exprs, savelist) + def __init__(self, exprs, savelist=True): + exprs = list(exprs) + if exprs and Ellipsis in exprs: + tmp = [] + for i, expr in enumerate(exprs): + if expr is Ellipsis: + if i < len(exprs) - 1: + skipto_arg = (Empty() + exprs[i + 1]).exprs[-1] + tmp.append(SkipTo(skipto_arg)("_skipped*")) + else: + raise Exception("cannot construct And with sequence ending in ...") + else: + tmp.append(expr) + exprs[:] = tmp + super(And, self).__init__(exprs, savelist) self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs) - self.setWhitespaceChars( self.exprs[0].whiteChars ) + self.setWhitespaceChars(self.exprs[0].whiteChars) self.skipWhitespace = self.exprs[0].skipWhitespace self.callPreparse = True def streamline(self): + # collapse any _PendingSkip's + if self.exprs: + if any(isinstance(e, ParseExpression) and e.exprs and isinstance(e.exprs[-1], _PendingSkip) + for e in self.exprs[:-1]): + for i, e in enumerate(self.exprs[:-1]): + if e is None: + continue + if (isinstance(e, ParseExpression) + and e.exprs and isinstance(e.exprs[-1], _PendingSkip)): + e.exprs[-1] = e.exprs[-1] + self.exprs[i + 1] + self.exprs[i + 1] = None + self.exprs = [e for e in self.exprs if e is not None] + super(And, self).streamline() self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs) return self - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): # pass False as last arg to _parse for first element, since we already # pre-parsed the string as part of our And pre-parsing - loc, resultlist = self.exprs[0]._parse( instring, loc, doActions, callPreParse=False ) + loc, resultlist = self.exprs[0]._parse(instring, loc, doActions, callPreParse=False) errorStop = False for e in self.exprs[1:]: if isinstance(e, And._ErrorStop): @@ -3710,7 +4057,7 @@ class And(ParseExpression): continue if errorStop: try: - loc, exprtokens = e._parse( instring, loc, doActions ) + loc, exprtokens = e._parse(instring, loc, doActions) except ParseSyntaxException: raise except ParseBaseException as pe: @@ -3719,25 +4066,25 @@ class And(ParseExpression): except IndexError: raise ParseSyntaxException(instring, len(instring), self.errmsg, self) else: - loc, exprtokens = e._parse( instring, loc, doActions ) + loc, exprtokens = e._parse(instring, loc, doActions) if exprtokens or exprtokens.haskeys(): resultlist += exprtokens return loc, resultlist - def __iadd__(self, other ): - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - return self.append( other ) #And( [ self, other ] ) + def __iadd__(self, other): + if isinstance(other, basestring): + other = self._literalStringClass(other) + return self.append(other) # And([self, other]) - def checkRecursion( self, parseElementList ): - subRecCheckList = parseElementList[:] + [ self ] + def checkRecursion(self, parseElementList): + subRecCheckList = parseElementList[:] + [self] for e in self.exprs: - e.checkRecursion( subRecCheckList ) + e.checkRecursion(subRecCheckList) if not e.mayReturnEmpty: break - def __str__( self ): - if hasattr(self,"name"): + def __str__(self): + if hasattr(self, "name"): return self.name if self.strRepr is None: @@ -3763,8 +4110,8 @@ class Or(ParseExpression): [['123'], ['3.1416'], ['789']] """ - def __init__( self, exprs, savelist = False ): - super(Or,self).__init__(exprs, savelist) + def __init__(self, exprs, savelist=False): + super(Or, self).__init__(exprs, savelist) if self.exprs: self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs) else: @@ -3772,16 +4119,17 @@ class Or(ParseExpression): def streamline(self): super(Or, self).streamline() - self.saveAsList = any(e.saveAsList for e in self.exprs) + if __compat__.collect_all_And_tokens: + self.saveAsList = any(e.saveAsList for e in self.exprs) return self - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): maxExcLoc = -1 maxException = None matches = [] for e in self.exprs: try: - loc2 = e.tryParse( instring, loc ) + loc2 = e.tryParse(instring, loc) except ParseException as err: err.__traceback__ = None if err.loc > maxExcLoc: @@ -3789,22 +4137,45 @@ class Or(ParseExpression): maxExcLoc = err.loc except IndexError: if len(instring) > maxExcLoc: - maxException = ParseException(instring,len(instring),e.errmsg,self) + maxException = ParseException(instring, len(instring), e.errmsg, self) maxExcLoc = len(instring) else: # save match among all matches, to retry longest to shortest matches.append((loc2, e)) if matches: - matches.sort(key=lambda x: -x[0]) - for _,e in matches: + # re-evaluate all matches in descending order of length of match, in case attached actions + # might change whether or how much they match of the input. + matches.sort(key=itemgetter(0), reverse=True) + + if not doActions: + # no further conditions or parse actions to change the selection of + # alternative, so the first match will be the best match + best_expr = matches[0][1] + return best_expr._parse(instring, loc, doActions) + + longest = -1, None + for loc1, expr1 in matches: + if loc1 <= longest[0]: + # already have a longer match than this one will deliver, we are done + return longest + try: - return e._parse( instring, loc, doActions ) + loc2, toks = expr1._parse(instring, loc, doActions) except ParseException as err: err.__traceback__ = None if err.loc > maxExcLoc: maxException = err maxExcLoc = err.loc + else: + if loc2 >= loc1: + return loc2, toks + # didn't match as much as before + elif loc2 > longest[0]: + longest = loc2, toks + + if longest != (-1, None): + return longest if maxException is not None: maxException.msg = self.errmsg @@ -3813,13 +4184,13 @@ class Or(ParseExpression): raise ParseException(instring, loc, "no defined alternatives to match", self) - def __ixor__(self, other ): - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - return self.append( other ) #Or( [ self, other ] ) + def __ixor__(self, other): + if isinstance(other, basestring): + other = self._literalStringClass(other) + return self.append(other) # Or([self, other]) - def __str__( self ): - if hasattr(self,"name"): + def __str__(self): + if hasattr(self, "name"): return self.name if self.strRepr is None: @@ -3827,10 +4198,22 @@ class Or(ParseExpression): return self.strRepr - def checkRecursion( self, parseElementList ): - subRecCheckList = parseElementList[:] + [ self ] + def checkRecursion(self, parseElementList): + subRecCheckList = parseElementList[:] + [self] for e in self.exprs: - e.checkRecursion( subRecCheckList ) + e.checkRecursion(subRecCheckList) + + def _setResultsName(self, name, listAllMatches=False): + if (not __compat__.collect_all_And_tokens + and __diag__.warn_multiple_tokens_in_named_alternation): + if any(isinstance(e, And) for e in self.exprs): + warnings.warn("{0}: setting results name {1!r} on {2} expression " + "may only return a single token for an And alternative, " + "in future will return the full list of tokens".format( + "warn_multiple_tokens_in_named_alternation", name, type(self).__name__), + stacklevel=3) + + return super(Or, self)._setResultsName(name, listAllMatches) class MatchFirst(ParseExpression): @@ -3850,25 +4233,25 @@ class MatchFirst(ParseExpression): number = Combine(Word(nums) + '.' + Word(nums)) | Word(nums) print(number.searchString("123 3.1416 789")) # Better -> [['123'], ['3.1416'], ['789']] """ - def __init__( self, exprs, savelist = False ): - super(MatchFirst,self).__init__(exprs, savelist) + def __init__(self, exprs, savelist=False): + super(MatchFirst, self).__init__(exprs, savelist) if self.exprs: self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs) - # self.saveAsList = any(e.saveAsList for e in self.exprs) else: self.mayReturnEmpty = True def streamline(self): super(MatchFirst, self).streamline() - self.saveAsList = any(e.saveAsList for e in self.exprs) + if __compat__.collect_all_And_tokens: + self.saveAsList = any(e.saveAsList for e in self.exprs) return self - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): maxExcLoc = -1 maxException = None for e in self.exprs: try: - ret = e._parse( instring, loc, doActions ) + ret = e._parse(instring, loc, doActions) return ret except ParseException as err: if err.loc > maxExcLoc: @@ -3876,7 +4259,7 @@ class MatchFirst(ParseExpression): maxExcLoc = err.loc except IndexError: if len(instring) > maxExcLoc: - maxException = ParseException(instring,len(instring),e.errmsg,self) + maxException = ParseException(instring, len(instring), e.errmsg, self) maxExcLoc = len(instring) # only got here if no expression matched, raise exception for match that made it the furthest @@ -3887,13 +4270,13 @@ class MatchFirst(ParseExpression): else: raise ParseException(instring, loc, "no defined alternatives to match", self) - def __ior__(self, other ): - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - return self.append( other ) #MatchFirst( [ self, other ] ) + def __ior__(self, other): + if isinstance(other, basestring): + other = self._literalStringClass(other) + return self.append(other) # MatchFirst([self, other]) - def __str__( self ): - if hasattr(self,"name"): + def __str__(self): + if hasattr(self, "name"): return self.name if self.strRepr is None: @@ -3901,10 +4284,22 @@ class MatchFirst(ParseExpression): return self.strRepr - def checkRecursion( self, parseElementList ): - subRecCheckList = parseElementList[:] + [ self ] + def checkRecursion(self, parseElementList): + subRecCheckList = parseElementList[:] + [self] for e in self.exprs: - e.checkRecursion( subRecCheckList ) + e.checkRecursion(subRecCheckList) + + def _setResultsName(self, name, listAllMatches=False): + if (not __compat__.collect_all_And_tokens + and __diag__.warn_multiple_tokens_in_named_alternation): + if any(isinstance(e, And) for e in self.exprs): + warnings.warn("{0}: setting results name {1!r} on {2} expression " + "may only return a single token for an And alternative, " + "in future will return the full list of tokens".format( + "warn_multiple_tokens_in_named_alternation", name, type(self).__name__), + stacklevel=3) + + return super(MatchFirst, self)._setResultsName(name, listAllMatches) class Each(ParseExpression): @@ -3964,8 +4359,8 @@ class Each(ParseExpression): - shape: TRIANGLE - size: 20 """ - def __init__( self, exprs, savelist = True ): - super(Each,self).__init__(exprs, savelist) + def __init__(self, exprs, savelist=True): + super(Each, self).__init__(exprs, savelist) self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs) self.skipWhitespace = True self.initExprGroups = True @@ -3976,15 +4371,15 @@ class Each(ParseExpression): self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs) return self - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): if self.initExprGroups: - self.opt1map = dict((id(e.expr),e) for e in self.exprs if isinstance(e,Optional)) - opt1 = [ e.expr for e in self.exprs if isinstance(e,Optional) ] - opt2 = [ e for e in self.exprs if e.mayReturnEmpty and not isinstance(e,Optional)] + self.opt1map = dict((id(e.expr), e) for e in self.exprs if isinstance(e, Optional)) + opt1 = [e.expr for e in self.exprs if isinstance(e, Optional)] + opt2 = [e for e in self.exprs if e.mayReturnEmpty and not isinstance(e, (Optional, Regex))] self.optionals = opt1 + opt2 - self.multioptionals = [ e.expr for e in self.exprs if isinstance(e,ZeroOrMore) ] - self.multirequired = [ e.expr for e in self.exprs if isinstance(e,OneOrMore) ] - self.required = [ e for e in self.exprs if not isinstance(e,(Optional,ZeroOrMore,OneOrMore)) ] + self.multioptionals = [e.expr for e in self.exprs if isinstance(e, ZeroOrMore)] + self.multirequired = [e.expr for e in self.exprs if isinstance(e, OneOrMore)] + self.required = [e for e in self.exprs if not isinstance(e, (Optional, ZeroOrMore, OneOrMore))] self.required += self.multirequired self.initExprGroups = False tmpLoc = loc @@ -3998,11 +4393,11 @@ class Each(ParseExpression): failed = [] for e in tmpExprs: try: - tmpLoc = e.tryParse( instring, tmpLoc ) + tmpLoc = e.tryParse(instring, tmpLoc) except ParseException: failed.append(e) else: - matchOrder.append(self.opt1map.get(id(e),e)) + matchOrder.append(self.opt1map.get(id(e), e)) if e in tmpReqd: tmpReqd.remove(e) elif e in tmpOpt: @@ -4012,21 +4407,21 @@ class Each(ParseExpression): if tmpReqd: missing = ", ".join(_ustr(e) for e in tmpReqd) - raise ParseException(instring,loc,"Missing one or more required elements (%s)" % missing ) + raise ParseException(instring, loc, "Missing one or more required elements (%s)" % missing) # add any unmatched Optionals, in case they have default values defined - matchOrder += [e for e in self.exprs if isinstance(e,Optional) and e.expr in tmpOpt] + matchOrder += [e for e in self.exprs if isinstance(e, Optional) and e.expr in tmpOpt] resultlist = [] for e in matchOrder: - loc,results = e._parse(instring,loc,doActions) + loc, results = e._parse(instring, loc, doActions) resultlist.append(results) finalResults = sum(resultlist, ParseResults([])) return loc, finalResults - def __str__( self ): - if hasattr(self,"name"): + def __str__(self): + if hasattr(self, "name"): return self.name if self.strRepr is None: @@ -4034,86 +4429,88 @@ class Each(ParseExpression): return self.strRepr - def checkRecursion( self, parseElementList ): - subRecCheckList = parseElementList[:] + [ self ] + def checkRecursion(self, parseElementList): + subRecCheckList = parseElementList[:] + [self] for e in self.exprs: - e.checkRecursion( subRecCheckList ) + e.checkRecursion(subRecCheckList) class ParseElementEnhance(ParserElement): """Abstract subclass of :class:`ParserElement`, for combining and post-processing parsed tokens. """ - def __init__( self, expr, savelist=False ): - super(ParseElementEnhance,self).__init__(savelist) - if isinstance( expr, basestring ): - if issubclass(ParserElement._literalStringClass, Token): - expr = ParserElement._literalStringClass(expr) + def __init__(self, expr, savelist=False): + super(ParseElementEnhance, self).__init__(savelist) + if isinstance(expr, basestring): + if issubclass(self._literalStringClass, Token): + expr = self._literalStringClass(expr) else: - expr = ParserElement._literalStringClass(Literal(expr)) + expr = self._literalStringClass(Literal(expr)) self.expr = expr self.strRepr = None if expr is not None: self.mayIndexError = expr.mayIndexError self.mayReturnEmpty = expr.mayReturnEmpty - self.setWhitespaceChars( expr.whiteChars ) + self.setWhitespaceChars(expr.whiteChars) self.skipWhitespace = expr.skipWhitespace self.saveAsList = expr.saveAsList self.callPreparse = expr.callPreparse self.ignoreExprs.extend(expr.ignoreExprs) - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): if self.expr is not None: - return self.expr._parse( instring, loc, doActions, callPreParse=False ) + return self.expr._parse(instring, loc, doActions, callPreParse=False) else: - raise ParseException("",loc,self.errmsg,self) + raise ParseException("", loc, self.errmsg, self) - def leaveWhitespace( self ): + def leaveWhitespace(self): self.skipWhitespace = False self.expr = self.expr.copy() if self.expr is not None: self.expr.leaveWhitespace() return self - def ignore( self, other ): - if isinstance( other, Suppress ): + def ignore(self, other): + if isinstance(other, Suppress): if other not in self.ignoreExprs: - super( ParseElementEnhance, self).ignore( other ) + super(ParseElementEnhance, self).ignore(other) if self.expr is not None: - self.expr.ignore( self.ignoreExprs[-1] ) + self.expr.ignore(self.ignoreExprs[-1]) else: - super( ParseElementEnhance, self).ignore( other ) + super(ParseElementEnhance, self).ignore(other) if self.expr is not None: - self.expr.ignore( self.ignoreExprs[-1] ) + self.expr.ignore(self.ignoreExprs[-1]) return self - def streamline( self ): - super(ParseElementEnhance,self).streamline() + def streamline(self): + super(ParseElementEnhance, self).streamline() if self.expr is not None: self.expr.streamline() return self - def checkRecursion( self, parseElementList ): + def checkRecursion(self, parseElementList): if self in parseElementList: - raise RecursiveGrammarException( parseElementList+[self] ) - subRecCheckList = parseElementList[:] + [ self ] + raise RecursiveGrammarException(parseElementList + [self]) + subRecCheckList = parseElementList[:] + [self] if self.expr is not None: - self.expr.checkRecursion( subRecCheckList ) + self.expr.checkRecursion(subRecCheckList) - def validate( self, validateTrace=[] ): - tmp = validateTrace[:]+[self] + def validate(self, validateTrace=None): + if validateTrace is None: + validateTrace = [] + tmp = validateTrace[:] + [self] if self.expr is not None: self.expr.validate(tmp) - self.checkRecursion( [] ) + self.checkRecursion([]) - def __str__( self ): + def __str__(self): try: - return super(ParseElementEnhance,self).__str__() + return super(ParseElementEnhance, self).__str__() except Exception: pass if self.strRepr is None and self.expr is not None: - self.strRepr = "%s:(%s)" % ( self.__class__.__name__, _ustr(self.expr) ) + self.strRepr = "%s:(%s)" % (self.__class__.__name__, _ustr(self.expr)) return self.strRepr @@ -4139,13 +4536,16 @@ class FollowedBy(ParseElementEnhance): [['shape', 'SQUARE'], ['color', 'BLACK'], ['posn', 'upper left']] """ - def __init__( self, expr ): - super(FollowedBy,self).__init__(expr) + def __init__(self, expr): + super(FollowedBy, self).__init__(expr) self.mayReturnEmpty = True - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): + # by using self._expr.parse and deleting the contents of the returned ParseResults list + # we keep any named results that were defined in the FollowedBy expression _, ret = self.expr._parse(instring, loc, doActions=doActions) del ret[:] + return loc, ret @@ -4198,6 +4598,7 @@ class PrecededBy(ParseElementEnhance): self.retreat = retreat self.errmsg = "not preceded by " + str(expr) self.skipWhitespace = False + self.parseAction.append(lambda s, l, t: t.__delitem__(slice(None, None))) def parseImpl(self, instring, loc=0, doActions=True): if self.exact: @@ -4208,19 +4609,18 @@ class PrecededBy(ParseElementEnhance): else: # retreat specified a maximum lookbehind window, iterate test_expr = self.expr + StringEnd() - instring_slice = instring[:loc] + instring_slice = instring[max(0, loc - self.retreat):loc] last_expr = ParseException(instring, loc, self.errmsg) - for offset in range(1, min(loc, self.retreat+1)): + for offset in range(1, min(loc, self.retreat + 1)+1): try: - _, ret = test_expr._parse(instring_slice, loc-offset) + # print('trying', offset, instring_slice, repr(instring_slice[loc - offset:])) + _, ret = test_expr._parse(instring_slice, len(instring_slice) - offset) except ParseBaseException as pbe: last_expr = pbe else: break else: raise last_expr - # return empty list of tokens, but preserve any defined results names - del ret[:] return loc, ret @@ -4247,20 +4647,20 @@ class NotAny(ParseElementEnhance): # integers that are followed by "." are actually floats integer = Word(nums) + ~Char(".") """ - def __init__( self, expr ): - super(NotAny,self).__init__(expr) - #~ self.leaveWhitespace() + def __init__(self, expr): + super(NotAny, self).__init__(expr) + # ~ self.leaveWhitespace() self.skipWhitespace = False # do NOT use self.leaveWhitespace(), don't want to propagate to exprs self.mayReturnEmpty = True - self.errmsg = "Found unwanted token, "+_ustr(self.expr) + self.errmsg = "Found unwanted token, " + _ustr(self.expr) - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): if self.expr.canParseNext(instring, loc): raise ParseException(instring, loc, self.errmsg, self) return loc, [] - def __str__( self ): - if hasattr(self,"name"): + def __str__(self): + if hasattr(self, "name"): return self.name if self.strRepr is None: @@ -4269,15 +4669,21 @@ class NotAny(ParseElementEnhance): return self.strRepr class _MultipleMatch(ParseElementEnhance): - def __init__( self, expr, stopOn=None): + def __init__(self, expr, stopOn=None): super(_MultipleMatch, self).__init__(expr) self.saveAsList = True ender = stopOn if isinstance(ender, basestring): - ender = ParserElement._literalStringClass(ender) - self.not_ender = ~ender if ender is not None else None + ender = self._literalStringClass(ender) + self.stopOn(ender) - def parseImpl( self, instring, loc, doActions=True ): + def stopOn(self, ender): + if isinstance(ender, basestring): + ender = self._literalStringClass(ender) + self.not_ender = ~ender if ender is not None else None + return self + + def parseImpl(self, instring, loc, doActions=True): self_expr_parse = self.expr._parse self_skip_ignorables = self._skipIgnorables check_ender = self.not_ender is not None @@ -4288,24 +4694,38 @@ class _MultipleMatch(ParseElementEnhance): # if so, fail) if check_ender: try_not_ender(instring, loc) - loc, tokens = self_expr_parse( instring, loc, doActions, callPreParse=False ) + loc, tokens = self_expr_parse(instring, loc, doActions, callPreParse=False) try: hasIgnoreExprs = (not not self.ignoreExprs) while 1: if check_ender: try_not_ender(instring, loc) if hasIgnoreExprs: - preloc = self_skip_ignorables( instring, loc ) + preloc = self_skip_ignorables(instring, loc) else: preloc = loc - loc, tmptokens = self_expr_parse( instring, preloc, doActions ) + loc, tmptokens = self_expr_parse(instring, preloc, doActions) if tmptokens or tmptokens.haskeys(): tokens += tmptokens - except (ParseException,IndexError): + except (ParseException, IndexError): pass return loc, tokens + def _setResultsName(self, name, listAllMatches=False): + if __diag__.warn_ungrouped_named_tokens_in_collection: + for e in [self.expr] + getattr(self.expr, 'exprs', []): + if isinstance(e, ParserElement) and e.resultsName: + warnings.warn("{0}: setting results name {1!r} on {2} expression " + "collides with {3!r} on contained expression".format("warn_ungrouped_named_tokens_in_collection", + name, + type(self).__name__, + e.resultsName), + stacklevel=3) + + return super(_MultipleMatch, self)._setResultsName(name, listAllMatches) + + class OneOrMore(_MultipleMatch): """Repetition of one or more of the given expression. @@ -4332,8 +4752,8 @@ class OneOrMore(_MultipleMatch): (attr_expr * (1,)).parseString(text).pprint() """ - def __str__( self ): - if hasattr(self,"name"): + def __str__(self): + if hasattr(self, "name"): return self.name if self.strRepr is None: @@ -4352,18 +4772,18 @@ class ZeroOrMore(_MultipleMatch): Example: similar to :class:`OneOrMore` """ - def __init__( self, expr, stopOn=None): - super(ZeroOrMore,self).__init__(expr, stopOn=stopOn) + def __init__(self, expr, stopOn=None): + super(ZeroOrMore, self).__init__(expr, stopOn=stopOn) self.mayReturnEmpty = True - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): try: return super(ZeroOrMore, self).parseImpl(instring, loc, doActions) - except (ParseException,IndexError): + except (ParseException, IndexError): return loc, [] - def __str__( self ): - if hasattr(self,"name"): + def __str__(self): + if hasattr(self, "name"): return self.name if self.strRepr is None: @@ -4371,6 +4791,7 @@ class ZeroOrMore(_MultipleMatch): return self.strRepr + class _NullToken(object): def __bool__(self): return False @@ -4378,7 +4799,6 @@ class _NullToken(object): def __str__(self): return "" -_optionalNotMatched = _NullToken() class Optional(ParseElementEnhance): """Optional matching of the given expression. @@ -4416,28 +4836,30 @@ class Optional(ParseElementEnhance): ^ FAIL: Expected end of text (at char 5), (line:1, col:6) """ - def __init__( self, expr, default=_optionalNotMatched ): - super(Optional,self).__init__( expr, savelist=False ) + __optionalNotMatched = _NullToken() + + def __init__(self, expr, default=__optionalNotMatched): + super(Optional, self).__init__(expr, savelist=False) self.saveAsList = self.expr.saveAsList self.defaultValue = default self.mayReturnEmpty = True - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): try: - loc, tokens = self.expr._parse( instring, loc, doActions, callPreParse=False ) - except (ParseException,IndexError): - if self.defaultValue is not _optionalNotMatched: + loc, tokens = self.expr._parse(instring, loc, doActions, callPreParse=False) + except (ParseException, IndexError): + if self.defaultValue is not self.__optionalNotMatched: if self.expr.resultsName: - tokens = ParseResults([ self.defaultValue ]) + tokens = ParseResults([self.defaultValue]) tokens[self.expr.resultsName] = self.defaultValue else: - tokens = [ self.defaultValue ] + tokens = [self.defaultValue] else: tokens = [] return loc, tokens - def __str__( self ): - if hasattr(self,"name"): + def __str__(self): + if hasattr(self, "name"): return self.name if self.strRepr is None: @@ -4503,20 +4925,20 @@ class SkipTo(ParseElementEnhance): - issue_num: 79 - sev: Minor """ - def __init__( self, other, include=False, ignore=None, failOn=None ): - super( SkipTo, self ).__init__( other ) + def __init__(self, other, include=False, ignore=None, failOn=None): + super(SkipTo, self).__init__(other) self.ignoreExpr = ignore self.mayReturnEmpty = True self.mayIndexError = False self.includeMatch = include self.saveAsList = False if isinstance(failOn, basestring): - self.failOn = ParserElement._literalStringClass(failOn) + self.failOn = self._literalStringClass(failOn) else: self.failOn = failOn - self.errmsg = "No match found for "+_ustr(self.expr) + self.errmsg = "No match found for " + _ustr(self.expr) - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): startloc = loc instrlen = len(instring) expr = self.expr @@ -4558,7 +4980,7 @@ class SkipTo(ParseElementEnhance): skipresult = ParseResults(skiptext) if self.includeMatch: - loc, mat = expr_parse(instring,loc,doActions,callPreParse=False) + loc, mat = expr_parse(instring, loc, doActions, callPreParse=False) skipresult += mat return loc, skipresult @@ -4590,17 +5012,17 @@ class Forward(ParseElementEnhance): See :class:`ParseResults.pprint` for an example of a recursive parser created using ``Forward``. """ - def __init__( self, other=None ): - super(Forward,self).__init__( other, savelist=False ) + def __init__(self, other=None): + super(Forward, self).__init__(other, savelist=False) - def __lshift__( self, other ): - if isinstance( other, basestring ): - other = ParserElement._literalStringClass(other) + def __lshift__(self, other): + if isinstance(other, basestring): + other = self._literalStringClass(other) self.expr = other self.strRepr = None self.mayIndexError = self.expr.mayIndexError self.mayReturnEmpty = self.expr.mayReturnEmpty - self.setWhitespaceChars( self.expr.whiteChars ) + self.setWhitespaceChars(self.expr.whiteChars) self.skipWhitespace = self.expr.skipWhitespace self.saveAsList = self.expr.saveAsList self.ignoreExprs.extend(self.expr.ignoreExprs) @@ -4609,59 +5031,72 @@ class Forward(ParseElementEnhance): def __ilshift__(self, other): return self << other - def leaveWhitespace( self ): + def leaveWhitespace(self): self.skipWhitespace = False return self - def streamline( self ): + def streamline(self): if not self.streamlined: self.streamlined = True if self.expr is not None: self.expr.streamline() return self - def validate( self, validateTrace=[] ): + def validate(self, validateTrace=None): + if validateTrace is None: + validateTrace = [] + if self not in validateTrace: - tmp = validateTrace[:]+[self] + tmp = validateTrace[:] + [self] if self.expr is not None: self.expr.validate(tmp) self.checkRecursion([]) - def __str__( self ): - if hasattr(self,"name"): + def __str__(self): + if hasattr(self, "name"): return self.name - return self.__class__.__name__ + ": ..." + if self.strRepr is not None: + return self.strRepr - # stubbed out for now - creates awful memory and perf issues - self._revertClass = self.__class__ - self.__class__ = _ForwardNoRecurse + # Avoid infinite recursion by setting a temporary strRepr + self.strRepr = ": ..." + + # Use the string representation of main expression. + retString = '...' try: if self.expr is not None: - retString = _ustr(self.expr) + retString = _ustr(self.expr)[:1000] else: retString = "None" finally: - self.__class__ = self._revertClass - return self.__class__.__name__ + ": " + retString + self.strRepr = self.__class__.__name__ + ": " + retString + return self.strRepr def copy(self): if self.expr is not None: - return super(Forward,self).copy() + return super(Forward, self).copy() else: ret = Forward() ret <<= self return ret -class _ForwardNoRecurse(Forward): - def __str__( self ): - return "..." + def _setResultsName(self, name, listAllMatches=False): + if __diag__.warn_name_set_on_empty_Forward: + if self.expr is None: + warnings.warn("{0}: setting results name {0!r} on {1} expression " + "that has no contained expression".format("warn_name_set_on_empty_Forward", + name, + type(self).__name__), + stacklevel=3) + + return super(Forward, self)._setResultsName(name, listAllMatches) class TokenConverter(ParseElementEnhance): """ Abstract subclass of :class:`ParseExpression`, for converting parsed results. """ - def __init__( self, expr, savelist=False ): - super(TokenConverter,self).__init__( expr )#, savelist ) + def __init__(self, expr, savelist=False): + super(TokenConverter, self).__init__(expr) # , savelist) self.saveAsList = False class Combine(TokenConverter): @@ -4682,8 +5117,8 @@ class Combine(TokenConverter): # no match when there are internal spaces print(real.parseString('3. 1416')) # -> Exception: Expected W:(0123...) """ - def __init__( self, expr, joinString="", adjacent=True ): - super(Combine,self).__init__( expr ) + def __init__(self, expr, joinString="", adjacent=True): + super(Combine, self).__init__(expr) # suppress whitespace-stripping in contained parse expressions, but re-enable it on the Combine itself if adjacent: self.leaveWhitespace() @@ -4692,20 +5127,20 @@ class Combine(TokenConverter): self.joinString = joinString self.callPreparse = True - def ignore( self, other ): + def ignore(self, other): if self.adjacent: ParserElement.ignore(self, other) else: - super( Combine, self).ignore( other ) + super(Combine, self).ignore(other) return self - def postParse( self, instring, loc, tokenlist ): + def postParse(self, instring, loc, tokenlist): retToks = tokenlist.copy() del retToks[:] - retToks += ParseResults([ "".join(tokenlist._asStringList(self.joinString)) ], modal=self.modalResults) + retToks += ParseResults(["".join(tokenlist._asStringList(self.joinString))], modal=self.modalResults) if self.resultsName and retToks.haskeys(): - return [ retToks ] + return [retToks] else: return retToks @@ -4719,17 +5154,17 @@ class Group(TokenConverter): num = Word(nums) term = ident | num func = ident + Optional(delimitedList(term)) - print(func.parseString("fn a,b,100")) # -> ['fn', 'a', 'b', '100'] + print(func.parseString("fn a, b, 100")) # -> ['fn', 'a', 'b', '100'] func = ident + Group(Optional(delimitedList(term))) - print(func.parseString("fn a,b,100")) # -> ['fn', ['a', 'b', '100']] + print(func.parseString("fn a, b, 100")) # -> ['fn', ['a', 'b', '100']] """ - def __init__( self, expr ): - super(Group,self).__init__( expr ) - self.saveAsList = expr.saveAsList + def __init__(self, expr): + super(Group, self).__init__(expr) + self.saveAsList = True - def postParse( self, instring, loc, tokenlist ): - return [ tokenlist ] + def postParse(self, instring, loc, tokenlist): + return [tokenlist] class Dict(TokenConverter): """Converter to return a repetitive expression as a list, but also @@ -4770,31 +5205,31 @@ class Dict(TokenConverter): See more examples at :class:`ParseResults` of accessing fields by results name. """ - def __init__( self, expr ): - super(Dict,self).__init__( expr ) + def __init__(self, expr): + super(Dict, self).__init__(expr) self.saveAsList = True - def postParse( self, instring, loc, tokenlist ): - for i,tok in enumerate(tokenlist): + def postParse(self, instring, loc, tokenlist): + for i, tok in enumerate(tokenlist): if len(tok) == 0: continue ikey = tok[0] - if isinstance(ikey,int): + if isinstance(ikey, int): ikey = _ustr(tok[0]).strip() - if len(tok)==1: - tokenlist[ikey] = _ParseResultsWithOffset("",i) - elif len(tok)==2 and not isinstance(tok[1],ParseResults): - tokenlist[ikey] = _ParseResultsWithOffset(tok[1],i) + if len(tok) == 1: + tokenlist[ikey] = _ParseResultsWithOffset("", i) + elif len(tok) == 2 and not isinstance(tok[1], ParseResults): + tokenlist[ikey] = _ParseResultsWithOffset(tok[1], i) else: - dictvalue = tok.copy() #ParseResults(i) + dictvalue = tok.copy() # ParseResults(i) del dictvalue[0] - if len(dictvalue)!= 1 or (isinstance(dictvalue,ParseResults) and dictvalue.haskeys()): - tokenlist[ikey] = _ParseResultsWithOffset(dictvalue,i) + if len(dictvalue) != 1 or (isinstance(dictvalue, ParseResults) and dictvalue.haskeys()): + tokenlist[ikey] = _ParseResultsWithOffset(dictvalue, i) else: - tokenlist[ikey] = _ParseResultsWithOffset(dictvalue[0],i) + tokenlist[ikey] = _ParseResultsWithOffset(dictvalue[0], i) if self.resultsName: - return [ tokenlist ] + return [tokenlist] else: return tokenlist @@ -4821,10 +5256,10 @@ class Suppress(TokenConverter): (See also :class:`delimitedList`.) """ - def postParse( self, instring, loc, tokenlist ): + def postParse(self, instring, loc, tokenlist): return [] - def suppress( self ): + def suppress(self): return self @@ -4834,12 +5269,12 @@ class OnlyOnce(object): def __init__(self, methodCall): self.callable = _trim_arity(methodCall) self.called = False - def __call__(self,s,l,t): + def __call__(self, s, l, t): if not self.called: - results = self.callable(s,l,t) + results = self.callable(s, l, t) self.called = True return results - raise ParseException(s,l,"") + raise ParseException(s, l, "") def reset(self): self.called = False @@ -4871,16 +5306,16 @@ def traceParseAction(f): f = _trim_arity(f) def z(*paArgs): thisFunc = f.__name__ - s,l,t = paArgs[-3:] - if len(paArgs)>3: + s, l, t = paArgs[-3:] + if len(paArgs) > 3: thisFunc = paArgs[0].__class__.__name__ + '.' + thisFunc - sys.stderr.write( ">>entering %s(line: '%s', %d, %r)\n" % (thisFunc,line(l,s),l,t) ) + sys.stderr.write(">>entering %s(line: '%s', %d, %r)\n" % (thisFunc, line(l, s), l, t)) try: ret = f(*paArgs) except Exception as exc: - sys.stderr.write( "<<leaving %s (exception: %s)\n" % (thisFunc,exc) ) + sys.stderr.write("<<leaving %s (exception: %s)\n" % (thisFunc, exc)) raise - sys.stderr.write( "<<leaving %s (ret: %r)\n" % (thisFunc,ret) ) + sys.stderr.write("<<leaving %s (ret: %r)\n" % (thisFunc, ret)) return ret try: z.__name__ = f.__name__ @@ -4891,7 +5326,7 @@ def traceParseAction(f): # # global helpers # -def delimitedList( expr, delim=",", combine=False ): +def delimitedList(expr, delim=",", combine=False): """Helper to define a delimited list of expressions - the delimiter defaults to ','. By default, the list elements and delimiters can have intervening whitespace, and comments, but this can be @@ -4906,13 +5341,13 @@ def delimitedList( expr, delim=",", combine=False ): delimitedList(Word(alphas)).parseString("aa,bb,cc") # -> ['aa', 'bb', 'cc'] delimitedList(Word(hexnums), delim=':', combine=True).parseString("AA:BB:CC:DD:EE") # -> ['AA:BB:CC:DD:EE'] """ - dlName = _ustr(expr)+" ["+_ustr(delim)+" "+_ustr(expr)+"]..." + dlName = _ustr(expr) + " [" + _ustr(delim) + " " + _ustr(expr) + "]..." if combine: - return Combine( expr + ZeroOrMore( delim + expr ) ).setName(dlName) + return Combine(expr + ZeroOrMore(delim + expr)).setName(dlName) else: - return ( expr + ZeroOrMore( Suppress( delim ) + expr ) ).setName(dlName) + return (expr + ZeroOrMore(Suppress(delim) + expr)).setName(dlName) -def countedArray( expr, intExpr=None ): +def countedArray(expr, intExpr=None): """Helper to define a counted list of expressions. This helper defines a pattern of the form:: @@ -4936,22 +5371,22 @@ def countedArray( expr, intExpr=None ): countedArray(Word(alphas), intExpr=binaryConstant).parseString('10 ab cd ef') # -> ['ab', 'cd'] """ arrayExpr = Forward() - def countFieldParseAction(s,l,t): + def countFieldParseAction(s, l, t): n = t[0] - arrayExpr << (n and Group(And([expr]*n)) or Group(empty)) + arrayExpr << (n and Group(And([expr] * n)) or Group(empty)) return [] if intExpr is None: - intExpr = Word(nums).setParseAction(lambda t:int(t[0])) + intExpr = Word(nums).setParseAction(lambda t: int(t[0])) else: intExpr = intExpr.copy() intExpr.setName("arrayLen") intExpr.addParseAction(countFieldParseAction, callDuringTry=True) - return ( intExpr + arrayExpr ).setName('(len) ' + _ustr(expr) + '...') + return (intExpr + arrayExpr).setName('(len) ' + _ustr(expr) + '...') def _flatten(L): ret = [] for i in L: - if isinstance(i,list): + if isinstance(i, list): ret.extend(_flatten(i)) else: ret.append(i) @@ -4973,7 +5408,7 @@ def matchPreviousLiteral(expr): enabled. """ rep = Forward() - def copyTokenToRepeater(s,l,t): + def copyTokenToRepeater(s, l, t): if t: if len(t) == 1: rep << t[0] @@ -5005,26 +5440,26 @@ def matchPreviousExpr(expr): rep = Forward() e2 = expr.copy() rep <<= e2 - def copyTokenToRepeater(s,l,t): + def copyTokenToRepeater(s, l, t): matchTokens = _flatten(t.asList()) - def mustMatchTheseTokens(s,l,t): + def mustMatchTheseTokens(s, l, t): theseTokens = _flatten(t.asList()) - if theseTokens != matchTokens: - raise ParseException("",0,"") - rep.setParseAction( mustMatchTheseTokens, callDuringTry=True ) + if theseTokens != matchTokens: + raise ParseException('', 0, '') + rep.setParseAction(mustMatchTheseTokens, callDuringTry=True) expr.addParseAction(copyTokenToRepeater, callDuringTry=True) rep.setName('(prev) ' + _ustr(expr)) return rep def _escapeRegexRangeChars(s): - #~ escape these chars: ^-] - for c in r"\^-]": - s = s.replace(c,_bslash+c) - s = s.replace("\n",r"\n") - s = s.replace("\t",r"\t") + # ~ escape these chars: ^-[] + for c in r"\^-[]": + s = s.replace(c, _bslash + c) + s = s.replace("\n", r"\n") + s = s.replace("\t", r"\t") return _ustr(s) -def oneOf( strs, caseless=False, useRegex=True ): +def oneOf(strs, caseless=False, useRegex=True, asKeyword=False): """Helper to quickly define a set of alternative Literals, and makes sure to do longest-first testing when there is a conflict, regardless of the input order, but returns @@ -5038,8 +5473,10 @@ def oneOf( strs, caseless=False, useRegex=True ): caseless - useRegex - (default= ``True``) - as an optimization, will generate a Regex object; otherwise, will generate - a :class:`MatchFirst` object (if ``caseless=True``, or if + a :class:`MatchFirst` object (if ``caseless=True`` or ``asKeyword=True``, or if creating a :class:`Regex` raises an exception) + - asKeyword - (default=``False``) - enforce Keyword-style matching on the + generated expressions Example:: @@ -5054,57 +5491,62 @@ def oneOf( strs, caseless=False, useRegex=True ): [['B', '=', '12'], ['AA', '=', '23'], ['B', '<=', 'AA'], ['AA', '>', '12']] """ + if isinstance(caseless, basestring): + warnings.warn("More than one string argument passed to oneOf, pass " + "choices as a list or space-delimited string", stacklevel=2) + if caseless: - isequal = ( lambda a,b: a.upper() == b.upper() ) - masks = ( lambda a,b: b.upper().startswith(a.upper()) ) - parseElementClass = CaselessLiteral + isequal = (lambda a, b: a.upper() == b.upper()) + masks = (lambda a, b: b.upper().startswith(a.upper())) + parseElementClass = CaselessKeyword if asKeyword else CaselessLiteral else: - isequal = ( lambda a,b: a == b ) - masks = ( lambda a,b: b.startswith(a) ) - parseElementClass = Literal + isequal = (lambda a, b: a == b) + masks = (lambda a, b: b.startswith(a)) + parseElementClass = Keyword if asKeyword else Literal symbols = [] - if isinstance(strs,basestring): + if isinstance(strs, basestring): symbols = strs.split() elif isinstance(strs, Iterable): symbols = list(strs) else: warnings.warn("Invalid argument to oneOf, expected string or iterable", - SyntaxWarning, stacklevel=2) + SyntaxWarning, stacklevel=2) if not symbols: return NoMatch() - i = 0 - while i < len(symbols)-1: - cur = symbols[i] - for j,other in enumerate(symbols[i+1:]): - if ( isequal(other, cur) ): - del symbols[i+j+1] - break - elif ( masks(cur, other) ): - del symbols[i+j+1] - symbols.insert(i,other) - cur = other - break - else: - i += 1 - - if not caseless and useRegex: - #~ print (strs,"->", "|".join( [ _escapeRegexChars(sym) for sym in symbols] )) - try: - if len(symbols)==len("".join(symbols)): - return Regex( "[%s]" % "".join(_escapeRegexRangeChars(sym) for sym in symbols) ).setName(' | '.join(symbols)) + if not asKeyword: + # if not producing keywords, need to reorder to take care to avoid masking + # longer choices with shorter ones + i = 0 + while i < len(symbols) - 1: + cur = symbols[i] + for j, other in enumerate(symbols[i + 1:]): + if isequal(other, cur): + del symbols[i + j + 1] + break + elif masks(cur, other): + del symbols[i + j + 1] + symbols.insert(i, other) + break else: - return Regex( "|".join(re.escape(sym) for sym in symbols) ).setName(' | '.join(symbols)) + i += 1 + + if not (caseless or asKeyword) and useRegex: + # ~ print (strs, "->", "|".join([_escapeRegexChars(sym) for sym in symbols])) + try: + if len(symbols) == len("".join(symbols)): + return Regex("[%s]" % "".join(_escapeRegexRangeChars(sym) for sym in symbols)).setName(' | '.join(symbols)) + else: + return Regex("|".join(re.escape(sym) for sym in symbols)).setName(' | '.join(symbols)) except Exception: warnings.warn("Exception creating Regex for oneOf, building MatchFirst", SyntaxWarning, stacklevel=2) - # last resort, just use MatchFirst return MatchFirst(parseElementClass(sym) for sym in symbols).setName(' | '.join(symbols)) -def dictOf( key, value ): +def dictOf(key, value): """Helper to easily and clearly define a dictionary by specifying the respective patterns for the key and value. Takes care of defining the :class:`Dict`, :class:`ZeroOrMore`, and @@ -5162,8 +5604,8 @@ def originalTextFor(expr, asString=True): Example:: src = "this is test <b> bold <i>text</i> </b> normal text " - for tag in ("b","i"): - opener,closer = makeHTMLTags(tag) + for tag in ("b", "i"): + opener, closer = makeHTMLTags(tag) patt = originalTextFor(opener + SkipTo(closer) + closer) print(patt.searchString(src)[0]) @@ -5172,14 +5614,14 @@ def originalTextFor(expr, asString=True): ['<b> bold <i>text</i> </b>'] ['<i>text</i>'] """ - locMarker = Empty().setParseAction(lambda s,loc,t: loc) + locMarker = Empty().setParseAction(lambda s, loc, t: loc) endlocMarker = locMarker.copy() endlocMarker.callPreparse = False matchExpr = locMarker("_original_start") + expr + endlocMarker("_original_end") if asString: - extractText = lambda s,l,t: s[t._original_start:t._original_end] + extractText = lambda s, l, t: s[t._original_start: t._original_end] else: - def extractText(s,l,t): + def extractText(s, l, t): t[:] = [s[t.pop('_original_start'):t.pop('_original_end')]] matchExpr.setParseAction(extractText) matchExpr.ignoreExprs = expr.ignoreExprs @@ -5189,7 +5631,7 @@ def ungroup(expr): """Helper to undo pyparsing's default grouping of And expressions, even if all but one are non-empty. """ - return TokenConverter(expr).setParseAction(lambda t:t[0]) + return TokenConverter(expr).addParseAction(lambda t: t[0]) def locatedExpr(expr): """Helper to decorate a returned token with its starting and ending @@ -5216,7 +5658,7 @@ def locatedExpr(expr): [[8, 'lksdjjf', 15]] [[18, 'lkkjj', 23]] """ - locator = Empty().setParseAction(lambda s,l,t: l) + locator = Empty().setParseAction(lambda s, l, t: l) return Group(locator("locn_start") + expr("value") + locator.copy().leaveWhitespace()("locn_end")) @@ -5227,12 +5669,12 @@ lineEnd = LineEnd().setName("lineEnd") stringStart = StringStart().setName("stringStart") stringEnd = StringEnd().setName("stringEnd") -_escapedPunc = Word( _bslash, r"\[]-*.$+^?()~ ", exact=2 ).setParseAction(lambda s,l,t:t[0][1]) -_escapedHexChar = Regex(r"\\0?[xX][0-9a-fA-F]+").setParseAction(lambda s,l,t:unichr(int(t[0].lstrip(r'\0x'),16))) -_escapedOctChar = Regex(r"\\0[0-7]+").setParseAction(lambda s,l,t:unichr(int(t[0][1:],8))) +_escapedPunc = Word(_bslash, r"\[]-*.$+^?()~ ", exact=2).setParseAction(lambda s, l, t: t[0][1]) +_escapedHexChar = Regex(r"\\0?[xX][0-9a-fA-F]+").setParseAction(lambda s, l, t: unichr(int(t[0].lstrip(r'\0x'), 16))) +_escapedOctChar = Regex(r"\\0[0-7]+").setParseAction(lambda s, l, t: unichr(int(t[0][1:], 8))) _singleChar = _escapedPunc | _escapedHexChar | _escapedOctChar | CharsNotIn(r'\]', exact=1) _charRange = Group(_singleChar + Suppress("-") + _singleChar) -_reBracketExpr = Literal("[") + Optional("^").setResultsName("negate") + Group( OneOrMore( _charRange | _singleChar ) ).setResultsName("body") + "]" +_reBracketExpr = Literal("[") + Optional("^").setResultsName("negate") + Group(OneOrMore(_charRange | _singleChar)).setResultsName("body") + "]" def srange(s): r"""Helper to easily define string ranges for use in Word @@ -5260,7 +5702,7 @@ def srange(s): - any combination of the above (``'aeiouy'``, ``'a-zA-Z0-9_$'``, etc.) """ - _expanded = lambda p: p if not isinstance(p,ParseResults) else ''.join(unichr(c) for c in range(ord(p[0]),ord(p[1])+1)) + _expanded = lambda p: p if not isinstance(p, ParseResults) else ''.join(unichr(c) for c in range(ord(p[0]), ord(p[1]) + 1)) try: return "".join(_expanded(part) for part in _reBracketExpr.parseString(s).body) except Exception: @@ -5270,9 +5712,9 @@ def matchOnlyAtCol(n): """Helper method for defining parse actions that require matching at a specific column in the input text. """ - def verifyCol(strg,locn,toks): - if col(locn,strg) != n: - raise ParseException(strg,locn,"matched token not at column %d" % n) + def verifyCol(strg, locn, toks): + if col(locn, strg) != n: + raise ParseException(strg, locn, "matched token not at column %d" % n) return verifyCol def replaceWith(replStr): @@ -5288,9 +5730,9 @@ def replaceWith(replStr): OneOrMore(term).parseString("324 234 N/A 234") # -> [324, 234, nan, 234] """ - return lambda s,l,t: [replStr] + return lambda s, l, t: [replStr] -def removeQuotes(s,l,t): +def removeQuotes(s, l, t): """Helper parse action for removing quotation marks from parsed quoted strings. @@ -5341,7 +5783,7 @@ def tokenMap(func, *args): now is the winter of our discontent made glorious summer by this sun of york ['Now Is The Winter Of Our Discontent Made Glorious Summer By This Sun Of York'] """ - def pa(s,l,t): + def pa(s, l, t): return [func(tokn, *args) for tokn in t] try: @@ -5361,33 +5803,41 @@ downcaseTokens = tokenMap(lambda t: _ustr(t).lower()) """(Deprecated) Helper parse action to convert tokens to lower case. Deprecated in favor of :class:`pyparsing_common.downcaseTokens`""" -def _makeTags(tagStr, xml): +def _makeTags(tagStr, xml, + suppress_LT=Suppress("<"), + suppress_GT=Suppress(">")): """Internal helper to construct opening and closing tag expressions, given a tag name""" - if isinstance(tagStr,basestring): + if isinstance(tagStr, basestring): resname = tagStr tagStr = Keyword(tagStr, caseless=not xml) else: resname = tagStr.name - tagAttrName = Word(alphas,alphanums+"_-:") - if (xml): - tagAttrValue = dblQuotedString.copy().setParseAction( removeQuotes ) - openTag = Suppress("<") + tagStr("tag") + \ - Dict(ZeroOrMore(Group( tagAttrName + Suppress("=") + tagAttrValue ))) + \ - Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">") + tagAttrName = Word(alphas, alphanums + "_-:") + if xml: + tagAttrValue = dblQuotedString.copy().setParseAction(removeQuotes) + openTag = (suppress_LT + + tagStr("tag") + + Dict(ZeroOrMore(Group(tagAttrName + Suppress("=") + tagAttrValue))) + + Optional("/", default=[False])("empty").setParseAction(lambda s, l, t: t[0] == '/') + + suppress_GT) else: - printablesLessRAbrack = "".join(c for c in printables if c not in ">") - tagAttrValue = quotedString.copy().setParseAction( removeQuotes ) | Word(printablesLessRAbrack) - openTag = Suppress("<") + tagStr("tag") + \ - Dict(ZeroOrMore(Group( tagAttrName.setParseAction(downcaseTokens) + \ - Optional( Suppress("=") + tagAttrValue ) ))) + \ - Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">") - closeTag = Combine(_L("</") + tagStr + ">") + tagAttrValue = quotedString.copy().setParseAction(removeQuotes) | Word(printables, excludeChars=">") + openTag = (suppress_LT + + tagStr("tag") + + Dict(ZeroOrMore(Group(tagAttrName.setParseAction(downcaseTokens) + + Optional(Suppress("=") + tagAttrValue)))) + + Optional("/", default=[False])("empty").setParseAction(lambda s, l, t: t[0] == '/') + + suppress_GT) + closeTag = Combine(_L("</") + tagStr + ">", adjacent=False) - openTag = openTag.setResultsName("start"+"".join(resname.replace(":"," ").title().split())).setName("<%s>" % resname) - closeTag = closeTag.setResultsName("end"+"".join(resname.replace(":"," ").title().split())).setName("</%s>" % resname) + openTag.setName("<%s>" % resname) + # add start<tagname> results name in parse action now that ungrouped names are not reported at two levels + openTag.addParseAction(lambda t: t.__setitem__("start" + "".join(resname.replace(":", " ").title().split()), t.copy())) + closeTag = closeTag("end" + "".join(resname.replace(":", " ").title().split())).setName("</%s>" % resname) openTag.tag = resname closeTag.tag = resname + openTag.tag_body = SkipTo(closeTag()) return openTag, closeTag def makeHTMLTags(tagStr): @@ -5400,7 +5850,7 @@ def makeHTMLTags(tagStr): text = '<td>More info at the <a href="https://github.com/pyparsing/pyparsing/wiki">pyparsing</a> wiki page</td>' # makeHTMLTags returns pyparsing expressions for the opening and # closing tags as a 2-tuple - a,a_end = makeHTMLTags("A") + a, a_end = makeHTMLTags("A") link_expr = a + SkipTo(a_end)("link_text") + a_end for link in link_expr.searchString(text): @@ -5412,7 +5862,7 @@ def makeHTMLTags(tagStr): pyparsing -> https://github.com/pyparsing/pyparsing/wiki """ - return _makeTags( tagStr, False ) + return _makeTags(tagStr, False) def makeXMLTags(tagStr): """Helper to construct opening and closing tag expressions for XML, @@ -5420,9 +5870,9 @@ def makeXMLTags(tagStr): Example: similar to :class:`makeHTMLTags` """ - return _makeTags( tagStr, True ) + return _makeTags(tagStr, True) -def withAttribute(*args,**attrDict): +def withAttribute(*args, **attrDict): """Helper to create a validating parse action to be used with start tags created with :class:`makeXMLTags` or :class:`makeHTMLTags`. Use ``withAttribute`` to qualify @@ -5435,7 +5885,7 @@ def withAttribute(*args,**attrDict): - keyword arguments, as in ``(align="right")``, or - as an explicit dict with ``**`` operator, when an attribute name is also a Python reserved word, as in ``**{"class":"Customer", "align":"right"}`` - - a list of name-value tuples, as in ``(("ns1:class", "Customer"), ("ns2:align","right"))`` + - a list of name-value tuples, as in ``(("ns1:class", "Customer"), ("ns2:align", "right"))`` For attribute names with a namespace prefix, you must use the second form. Attribute names are matched insensitive to upper/lower case. @@ -5482,13 +5932,13 @@ def withAttribute(*args,**attrDict): attrs = args[:] else: attrs = attrDict.items() - attrs = [(k,v) for k,v in attrs] - def pa(s,l,tokens): - for attrName,attrValue in attrs: + attrs = [(k, v) for k, v in attrs] + def pa(s, l, tokens): + for attrName, attrValue in attrs: if attrName not in tokens: - raise ParseException(s,l,"no matching attribute " + attrName) + raise ParseException(s, l, "no matching attribute " + attrName) if attrValue != withAttribute.ANY_VALUE and tokens[attrName] != attrValue: - raise ParseException(s,l,"attribute '%s' has value '%s', must be '%s'" % + raise ParseException(s, l, "attribute '%s' has value '%s', must be '%s'" % (attrName, tokens[attrName], attrValue)) return pa withAttribute.ANY_VALUE = object() @@ -5529,13 +5979,13 @@ def withClass(classname, namespace=''): 1,3 2,3 1,1 """ classattr = "%s:class" % namespace if namespace else "class" - return withAttribute(**{classattr : classname}) + return withAttribute(**{classattr: classname}) opAssoc = SimpleNamespace() opAssoc.LEFT = object() opAssoc.RIGHT = object() -def infixNotation( baseExpr, opList, lpar=Suppress('('), rpar=Suppress(')') ): +def infixNotation(baseExpr, opList, lpar=Suppress('('), rpar=Suppress(')')): """Helper method for constructing grammars of expressions made up of operators working in a precedence hierarchy. Operators may be unary or binary, left- or right-associative. Parse actions can also be @@ -5613,9 +6063,9 @@ def infixNotation( baseExpr, opList, lpar=Suppress('('), rpar=Suppress(')') ): return loc, [] ret = Forward() - lastExpr = baseExpr | ( lpar + ret + rpar ) - for i,operDef in enumerate(opList): - opExpr,arity,rightLeftAssoc,pa = (operDef + (None,))[:4] + lastExpr = baseExpr | (lpar + ret + rpar) + for i, operDef in enumerate(opList): + opExpr, arity, rightLeftAssoc, pa = (operDef + (None, ))[:4] termName = "%s term" % opExpr if arity < 3 else "%s%s term" % opExpr if arity == 3: if opExpr is None or len(opExpr) != 2: @@ -5625,15 +6075,15 @@ def infixNotation( baseExpr, opList, lpar=Suppress('('), rpar=Suppress(')') ): thisExpr = Forward().setName(termName) if rightLeftAssoc == opAssoc.LEFT: if arity == 1: - matchExpr = _FB(lastExpr + opExpr) + Group( lastExpr + OneOrMore( opExpr ) ) + matchExpr = _FB(lastExpr + opExpr) + Group(lastExpr + OneOrMore(opExpr)) elif arity == 2: if opExpr is not None: - matchExpr = _FB(lastExpr + opExpr + lastExpr) + Group( lastExpr + OneOrMore( opExpr + lastExpr ) ) + matchExpr = _FB(lastExpr + opExpr + lastExpr) + Group(lastExpr + OneOrMore(opExpr + lastExpr)) else: - matchExpr = _FB(lastExpr+lastExpr) + Group( lastExpr + OneOrMore(lastExpr) ) + matchExpr = _FB(lastExpr + lastExpr) + Group(lastExpr + OneOrMore(lastExpr)) elif arity == 3: - matchExpr = _FB(lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr) + \ - Group( lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr ) + matchExpr = (_FB(lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr) + + Group(lastExpr + OneOrMore(opExpr1 + lastExpr + opExpr2 + lastExpr))) else: raise ValueError("operator must be unary (1), binary (2), or ternary (3)") elif rightLeftAssoc == opAssoc.RIGHT: @@ -5641,15 +6091,15 @@ def infixNotation( baseExpr, opList, lpar=Suppress('('), rpar=Suppress(')') ): # try to avoid LR with this extra test if not isinstance(opExpr, Optional): opExpr = Optional(opExpr) - matchExpr = _FB(opExpr.expr + thisExpr) + Group( opExpr + thisExpr ) + matchExpr = _FB(opExpr.expr + thisExpr) + Group(opExpr + thisExpr) elif arity == 2: if opExpr is not None: - matchExpr = _FB(lastExpr + opExpr + thisExpr) + Group( lastExpr + OneOrMore( opExpr + thisExpr ) ) + matchExpr = _FB(lastExpr + opExpr + thisExpr) + Group(lastExpr + OneOrMore(opExpr + thisExpr)) else: - matchExpr = _FB(lastExpr + thisExpr) + Group( lastExpr + OneOrMore( thisExpr ) ) + matchExpr = _FB(lastExpr + thisExpr) + Group(lastExpr + OneOrMore(thisExpr)) elif arity == 3: - matchExpr = _FB(lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr) + \ - Group( lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr ) + matchExpr = (_FB(lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr) + + Group(lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr)) else: raise ValueError("operator must be unary (1), binary (2), or ternary (3)") else: @@ -5659,7 +6109,7 @@ def infixNotation( baseExpr, opList, lpar=Suppress('('), rpar=Suppress(')') ): matchExpr.setParseAction(*pa) else: matchExpr.setParseAction(pa) - thisExpr <<= ( matchExpr.setName(termName) | lastExpr ) + thisExpr <<= (matchExpr.setName(termName) | lastExpr) lastExpr = thisExpr ret <<= lastExpr return ret @@ -5668,10 +6118,10 @@ operatorPrecedence = infixNotation """(Deprecated) Former name of :class:`infixNotation`, will be dropped in a future release.""" -dblQuotedString = Combine(Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*')+'"').setName("string enclosed in double quotes") -sglQuotedString = Combine(Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*")+"'").setName("string enclosed in single quotes") -quotedString = Combine(Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*')+'"'| - Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*")+"'").setName("quotedString using single or double quotes") +dblQuotedString = Combine(Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*') + '"').setName("string enclosed in double quotes") +sglQuotedString = Combine(Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*") + "'").setName("string enclosed in single quotes") +quotedString = Combine(Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*') + '"' + | Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*") + "'").setName("quotedString using single or double quotes") unicodeString = Combine(_L('u') + quotedString.copy()).setName("unicode string literal") def nestedExpr(opener="(", closer=")", content=None, ignoreExpr=quotedString.copy()): @@ -5707,7 +6157,7 @@ def nestedExpr(opener="(", closer=")", content=None, ignoreExpr=quotedString.cop ident = Word(alphas+'_', alphanums+'_') number = pyparsing_common.number arg = Group(decl_data_type + ident) - LPAR,RPAR = map(Suppress, "()") + LPAR, RPAR = map(Suppress, "()") code_body = nestedExpr('{', '}', ignoreExpr=(quotedString | cStyleComment)) @@ -5742,33 +6192,40 @@ def nestedExpr(opener="(", closer=")", content=None, ignoreExpr=quotedString.cop if opener == closer: raise ValueError("opening and closing strings cannot be the same") if content is None: - if isinstance(opener,basestring) and isinstance(closer,basestring): - if len(opener) == 1 and len(closer)==1: + if isinstance(opener, basestring) and isinstance(closer, basestring): + if len(opener) == 1 and len(closer) == 1: if ignoreExpr is not None: - content = (Combine(OneOrMore(~ignoreExpr + - CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS,exact=1)) - ).setParseAction(lambda t:t[0].strip())) + content = (Combine(OneOrMore(~ignoreExpr + + CharsNotIn(opener + + closer + + ParserElement.DEFAULT_WHITE_CHARS, exact=1) + ) + ).setParseAction(lambda t: t[0].strip())) else: - content = (empty.copy()+CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS - ).setParseAction(lambda t:t[0].strip())) + content = (empty.copy() + CharsNotIn(opener + + closer + + ParserElement.DEFAULT_WHITE_CHARS + ).setParseAction(lambda t: t[0].strip())) else: if ignoreExpr is not None: - content = (Combine(OneOrMore(~ignoreExpr + - ~Literal(opener) + ~Literal(closer) + - CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) - ).setParseAction(lambda t:t[0].strip())) + content = (Combine(OneOrMore(~ignoreExpr + + ~Literal(opener) + + ~Literal(closer) + + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS, exact=1)) + ).setParseAction(lambda t: t[0].strip())) else: - content = (Combine(OneOrMore(~Literal(opener) + ~Literal(closer) + - CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) - ).setParseAction(lambda t:t[0].strip())) + content = (Combine(OneOrMore(~Literal(opener) + + ~Literal(closer) + + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS, exact=1)) + ).setParseAction(lambda t: t[0].strip())) else: raise ValueError("opening and closing arguments must be strings if no content expression is given") ret = Forward() if ignoreExpr is not None: - ret <<= Group( Suppress(opener) + ZeroOrMore( ignoreExpr | ret | content ) + Suppress(closer) ) + ret <<= Group(Suppress(opener) + ZeroOrMore(ignoreExpr | ret | content) + Suppress(closer)) else: - ret <<= Group( Suppress(opener) + ZeroOrMore( ret | content ) + Suppress(closer) ) - ret.setName('nested %s%s expression' % (opener,closer)) + ret <<= Group(Suppress(opener) + ZeroOrMore(ret | content) + Suppress(closer)) + ret.setName('nested %s%s expression' % (opener, closer)) return ret def indentedBlock(blockStatementExpr, indentStack, indent=True): @@ -5783,7 +6240,7 @@ def indentedBlock(blockStatementExpr, indentStack, indent=True): (multiple statementWithIndentedBlock expressions within a single grammar should share a common indentStack) - indent - boolean indicating whether block must be indented beyond - the the current level; set to False for block of left-most + the current level; set to False for block of left-most statements (default= ``True``) A valid block must contain at least one ``blockStatement``. @@ -5816,15 +6273,15 @@ def indentedBlock(blockStatementExpr, indentStack, indent=True): stmt = Forward() identifier = Word(alphas, alphanums) - funcDecl = ("def" + identifier + Group( "(" + Optional( delimitedList(identifier) ) + ")" ) + ":") + funcDecl = ("def" + identifier + Group("(" + Optional(delimitedList(identifier)) + ")") + ":") func_body = indentedBlock(stmt, indentStack) - funcDef = Group( funcDecl + func_body ) + funcDef = Group(funcDecl + func_body) rvalue = Forward() funcCall = Group(identifier + "(" + Optional(delimitedList(rvalue)) + ")") rvalue << (funcCall | identifier | Word(nums)) assignment = Group(identifier + "=" + rvalue) - stmt << ( funcDef | assignment | identifier ) + stmt << (funcDef | assignment | identifier) module_body = OneOrMore(stmt) @@ -5852,47 +6309,56 @@ def indentedBlock(blockStatementExpr, indentStack, indent=True): ':', [[['def', 'eggs', ['(', 'z', ')'], ':', [['pass']]]]]]] """ - def checkPeerIndent(s,l,t): + backup_stack = indentStack[:] + + def reset_stack(): + indentStack[:] = backup_stack + + def checkPeerIndent(s, l, t): if l >= len(s): return - curCol = col(l,s) + curCol = col(l, s) if curCol != indentStack[-1]: if curCol > indentStack[-1]: - raise ParseFatalException(s,l,"illegal nesting") - raise ParseException(s,l,"not a peer entry") + raise ParseException(s, l, "illegal nesting") + raise ParseException(s, l, "not a peer entry") - def checkSubIndent(s,l,t): - curCol = col(l,s) + def checkSubIndent(s, l, t): + curCol = col(l, s) if curCol > indentStack[-1]: - indentStack.append( curCol ) + indentStack.append(curCol) else: - raise ParseException(s,l,"not a subentry") + raise ParseException(s, l, "not a subentry") - def checkUnindent(s,l,t): + def checkUnindent(s, l, t): if l >= len(s): return - curCol = col(l,s) - if not(indentStack and curCol < indentStack[-1] and curCol <= indentStack[-2]): - raise ParseException(s,l,"not an unindent") - indentStack.pop() + curCol = col(l, s) + if not(indentStack and curCol in indentStack): + raise ParseException(s, l, "not an unindent") + if curCol < indentStack[-1]: + indentStack.pop() - NL = OneOrMore(LineEnd().setWhitespaceChars("\t ").suppress()) + NL = OneOrMore(LineEnd().setWhitespaceChars("\t ").suppress(), stopOn=StringEnd()) INDENT = (Empty() + Empty().setParseAction(checkSubIndent)).setName('INDENT') PEER = Empty().setParseAction(checkPeerIndent).setName('') UNDENT = Empty().setParseAction(checkUnindent).setName('UNINDENT') if indent: - smExpr = Group( Optional(NL) + - #~ FollowedBy(blockStatementExpr) + - INDENT + (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) + UNDENT) + smExpr = Group(Optional(NL) + + INDENT + + OneOrMore(PEER + Group(blockStatementExpr) + Optional(NL), stopOn=StringEnd()) + + UNDENT) else: - smExpr = Group( Optional(NL) + - (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) ) + smExpr = Group(Optional(NL) + + OneOrMore(PEER + Group(blockStatementExpr) + Optional(NL), stopOn=StringEnd()) + + UNDENT) + smExpr.setFailAction(lambda a, b, c, d: reset_stack()) blockStatementExpr.ignore(_bslash + LineEnd()) return smExpr.setName('indented block') alphas8bit = srange(r"[\0xc0-\0xd6\0xd8-\0xf6\0xf8-\0xff]") punc8bit = srange(r"[\0xa1-\0xbf\0xd7\0xf7]") -anyOpenTag,anyCloseTag = makeHTMLTags(Word(alphas,alphanums+"_:").setName('any tag')) -_htmlEntityMap = dict(zip("gt lt amp nbsp quot apos".split(),'><& "\'')) +anyOpenTag, anyCloseTag = makeHTMLTags(Word(alphas, alphanums + "_:").setName('any tag')) +_htmlEntityMap = dict(zip("gt lt amp nbsp quot apos".split(), '><& "\'')) commonHTMLEntity = Regex('&(?P<entity>' + '|'.join(_htmlEntityMap.keys()) +");").setName("common HTML entity") def replaceHTMLEntity(t): """Helper parser action to replace common HTML entities with their special characters""" @@ -5909,7 +6375,7 @@ restOfLine = Regex(r".*").leaveWhitespace().setName("rest of line") dblSlashComment = Regex(r"//(?:\\\n|[^\n])*").setName("// comment") "Comment of the form ``// ... (to end of line)``" -cppStyleComment = Combine(Regex(r"/\*(?:[^*]|\*(?!/))*") + '*/'| dblSlashComment).setName("C++ style comment") +cppStyleComment = Combine(Regex(r"/\*(?:[^*]|\*(?!/))*") + '*/' | dblSlashComment).setName("C++ style comment") "Comment of either form :class:`cStyleComment` or :class:`dblSlashComment`" javaStyleComment = cppStyleComment @@ -5918,10 +6384,10 @@ javaStyleComment = cppStyleComment pythonStyleComment = Regex(r"#.*").setName("Python style comment") "Comment of the form ``# ... (to end of line)``" -_commasepitem = Combine(OneOrMore(Word(printables, excludeChars=',') + - Optional( Word(" \t") + - ~Literal(",") + ~LineEnd() ) ) ).streamline().setName("commaItem") -commaSeparatedList = delimitedList( Optional( quotedString.copy() | _commasepitem, default="") ).setName("commaSeparatedList") +_commasepitem = Combine(OneOrMore(Word(printables, excludeChars=',') + + Optional(Word(" \t") + + ~Literal(",") + ~LineEnd()))).streamline().setName("commaItem") +commaSeparatedList = delimitedList(Optional(quotedString.copy() | _commasepitem, default="")).setName("commaSeparatedList") """(Deprecated) Predefined expression of 1 or more printable words or quoted strings, separated by commas. @@ -6087,7 +6553,7 @@ class pyparsing_common: integer = Word(nums).setName("integer").setParseAction(convertToInteger) """expression that parses an unsigned integer, returns an int""" - hex_integer = Word(hexnums).setName("hex integer").setParseAction(tokenMap(int,16)) + hex_integer = Word(hexnums).setName("hex integer").setParseAction(tokenMap(int, 16)) """expression that parses a hexadecimal integer, returns an int""" signed_integer = Regex(r'[+-]?\d+').setName("signed integer").setParseAction(convertToInteger) @@ -6101,10 +6567,10 @@ class pyparsing_common: """mixed integer of the form 'integer - fraction', with optional leading integer, returns float""" mixed_integer.addParseAction(sum) - real = Regex(r'[+-]?\d+\.\d*').setName("real number").setParseAction(convertToFloat) + real = Regex(r'[+-]?(?:\d+\.\d*|\.\d+)').setName("real number").setParseAction(convertToFloat) """expression that parses a floating point number and returns a float""" - sci_real = Regex(r'[+-]?\d+([eE][+-]?\d+|\.\d*([eE][+-]?\d+)?)').setName("real number with scientific notation").setParseAction(convertToFloat) + sci_real = Regex(r'[+-]?(?:\d+(?:[eE][+-]?\d+)|(?:\d+\.\d*|\.\d+)(?:[eE][+-]?\d+)?)').setName("real number with scientific notation").setParseAction(convertToFloat) """expression that parses a floating point number with optional scientific notation and returns a float""" @@ -6115,15 +6581,18 @@ class pyparsing_common: fnumber = Regex(r'[+-]?\d+\.?\d*([eE][+-]?\d+)?').setName("fnumber").setParseAction(convertToFloat) """any int or real number, returned as float""" - identifier = Word(alphas+'_', alphanums+'_').setName("identifier") + identifier = Word(alphas + '_', alphanums + '_').setName("identifier") """typical code identifier (leading alpha or '_', followed by 0 or more alphas, nums, or '_')""" ipv4_address = Regex(r'(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})){3}').setName("IPv4 address") "IPv4 address (``0.0.0.0 - 255.255.255.255``)" _ipv6_part = Regex(r'[0-9a-fA-F]{1,4}').setName("hex_integer") - _full_ipv6_address = (_ipv6_part + (':' + _ipv6_part)*7).setName("full IPv6 address") - _short_ipv6_address = (Optional(_ipv6_part + (':' + _ipv6_part)*(0,6)) + "::" + Optional(_ipv6_part + (':' + _ipv6_part)*(0,6))).setName("short IPv6 address") + _full_ipv6_address = (_ipv6_part + (':' + _ipv6_part) * 7).setName("full IPv6 address") + _short_ipv6_address = (Optional(_ipv6_part + (':' + _ipv6_part) * (0, 6)) + + "::" + + Optional(_ipv6_part + (':' + _ipv6_part) * (0, 6)) + ).setName("short IPv6 address") _short_ipv6_address.addCondition(lambda t: sum(1 for tt in t if pyparsing_common._ipv6_part.matches(tt)) < 8) _mixed_ipv6_address = ("::ffff:" + ipv4_address).setName("mixed IPv6 address") ipv6_address = Combine((_full_ipv6_address | _mixed_ipv6_address | _short_ipv6_address).setName("IPv6 address")).setName("IPv6 address") @@ -6150,7 +6619,7 @@ class pyparsing_common: [datetime.date(1999, 12, 31)] """ - def cvt_fn(s,l,t): + def cvt_fn(s, l, t): try: return datetime.strptime(t[0], fmt).date() except ValueError as ve: @@ -6175,7 +6644,7 @@ class pyparsing_common: [datetime.datetime(1999, 12, 31, 23, 59, 59, 999000)] """ - def cvt_fn(s,l,t): + def cvt_fn(s, l, t): try: return datetime.strptime(t[0], fmt) except ValueError as ve: @@ -6200,7 +6669,7 @@ class pyparsing_common: # strip HTML links from normal text text = '<td>More info at the <a href="https://github.com/pyparsing/pyparsing/wiki">pyparsing</a> wiki page</td>' - td,td_end = makeHTMLTags("TD") + td, td_end = makeHTMLTags("TD") table_text = td + SkipTo(td_end).setParseAction(pyparsing_common.stripHTMLTags)("body") + td_end print(table_text.parseString(text).body) @@ -6210,9 +6679,13 @@ class pyparsing_common: """ return pyparsing_common._html_stripper.transformString(tokens[0]) - _commasepitem = Combine(OneOrMore(~Literal(",") + ~LineEnd() + Word(printables, excludeChars=',') - + Optional( White(" \t") ) ) ).streamline().setName("commaItem") - comma_separated_list = delimitedList( Optional( quotedString.copy() | _commasepitem, default="") ).setName("comma separated list") + _commasepitem = Combine(OneOrMore(~Literal(",") + + ~LineEnd() + + Word(printables, excludeChars=',') + + Optional(White(" \t")))).streamline().setName("commaItem") + comma_separated_list = delimitedList(Optional(quotedString.copy() + | _commasepitem, default='') + ).setName("comma separated list") """Predefined expression of 1 or more printable words or quoted strings, separated by commas.""" upcaseTokens = staticmethod(tokenMap(lambda t: _ustr(t).upper())) @@ -6231,7 +6704,8 @@ class _lazyclassproperty(object): def __get__(self, obj, cls): if cls is None: cls = type(obj) - if not hasattr(cls, '_intern') or any(cls._intern is getattr(superclass, '_intern', []) for superclass in cls.__mro__[1:]): + if not hasattr(cls, '_intern') or any(cls._intern is getattr(superclass, '_intern', []) + for superclass in cls.__mro__[1:]): cls._intern = {} attrname = self.fn.__name__ if attrname not in cls._intern: @@ -6262,7 +6736,7 @@ class unicode_set(object): if cc is unicode_set: break for rr in cc._ranges: - ret.extend(range(rr[0], rr[-1]+1)) + ret.extend(range(rr[0], rr[-1] + 1)) return [unichr(c) for c in sorted(set(ret))] @_lazyclassproperty @@ -6318,27 +6792,27 @@ class pyparsing_unicode(unicode_set): class Chinese(unicode_set): "Unicode set for Chinese Unicode Character Range" - _ranges = [(0x4e00, 0x9fff), (0x3000, 0x303f), ] + _ranges = [(0x4e00, 0x9fff), (0x3000, 0x303f),] class Japanese(unicode_set): "Unicode set for Japanese Unicode Character Range, combining Kanji, Hiragana, and Katakana ranges" - _ranges = [ ] + _ranges = [] class Kanji(unicode_set): "Unicode set for Kanji Unicode Character Range" - _ranges = [(0x4E00, 0x9Fbf), (0x3000, 0x303f), ] + _ranges = [(0x4E00, 0x9Fbf), (0x3000, 0x303f),] class Hiragana(unicode_set): "Unicode set for Hiragana Unicode Character Range" - _ranges = [(0x3040, 0x309f), ] + _ranges = [(0x3040, 0x309f),] class Katakana(unicode_set): "Unicode set for Katakana Unicode Character Range" - _ranges = [(0x30a0, 0x30ff), ] + _ranges = [(0x30a0, 0x30ff),] class Korean(unicode_set): "Unicode set for Korean Unicode Character Range" - _ranges = [(0xac00, 0xd7af), (0x1100, 0x11ff), (0x3130, 0x318f), (0xa960, 0xa97f), (0xd7b0, 0xd7ff), (0x3000, 0x303f), ] + _ranges = [(0xac00, 0xd7af), (0x1100, 0x11ff), (0x3130, 0x318f), (0xa960, 0xa97f), (0xd7b0, 0xd7ff), (0x3000, 0x303f),] class CJK(Chinese, Japanese, Korean): "Unicode set for combined Chinese, Japanese, and Korean (CJK) Unicode Character Range" @@ -6346,15 +6820,15 @@ class pyparsing_unicode(unicode_set): class Thai(unicode_set): "Unicode set for Thai Unicode Character Range" - _ranges = [(0x0e01, 0x0e3a), (0x0e3f, 0x0e5b), ] + _ranges = [(0x0e01, 0x0e3a), (0x0e3f, 0x0e5b),] class Arabic(unicode_set): "Unicode set for Arabic Unicode Character Range" - _ranges = [(0x0600, 0x061b), (0x061e, 0x06ff), (0x0700, 0x077f), ] + _ranges = [(0x0600, 0x061b), (0x061e, 0x06ff), (0x0700, 0x077f),] class Hebrew(unicode_set): "Unicode set for Hebrew Unicode Character Range" - _ranges = [(0x0590, 0x05ff), ] + _ranges = [(0x0590, 0x05ff),] class Devanagari(unicode_set): "Unicode set for Devanagari Unicode Character Range" @@ -6366,18 +6840,199 @@ pyparsing_unicode.Japanese._ranges = (pyparsing_unicode.Japanese.Kanji._ranges # define ranges in language character sets if PY_3: - setattr(pyparsing_unicode, "العربية", pyparsing_unicode.Arabic) - setattr(pyparsing_unicode, "中文", pyparsing_unicode.Chinese) - setattr(pyparsing_unicode, "кириллица", pyparsing_unicode.Cyrillic) - setattr(pyparsing_unicode, "Ελληνικά", pyparsing_unicode.Greek) - setattr(pyparsing_unicode, "עִברִית", pyparsing_unicode.Hebrew) - setattr(pyparsing_unicode, "日本語", pyparsing_unicode.Japanese) - setattr(pyparsing_unicode.Japanese, "漢字", pyparsing_unicode.Japanese.Kanji) - setattr(pyparsing_unicode.Japanese, "カタカナ", pyparsing_unicode.Japanese.Katakana) - setattr(pyparsing_unicode.Japanese, "ひらがな", pyparsing_unicode.Japanese.Hiragana) - setattr(pyparsing_unicode, "한국어", pyparsing_unicode.Korean) - setattr(pyparsing_unicode, "ไทย", pyparsing_unicode.Thai) - setattr(pyparsing_unicode, "देवनागरी", pyparsing_unicode.Devanagari) + setattr(pyparsing_unicode, u"العربية", pyparsing_unicode.Arabic) + setattr(pyparsing_unicode, u"中文", pyparsing_unicode.Chinese) + setattr(pyparsing_unicode, u"кириллица", pyparsing_unicode.Cyrillic) + setattr(pyparsing_unicode, u"Ελληνικά", pyparsing_unicode.Greek) + setattr(pyparsing_unicode, u"עִברִית", pyparsing_unicode.Hebrew) + setattr(pyparsing_unicode, u"日本語", pyparsing_unicode.Japanese) + setattr(pyparsing_unicode.Japanese, u"漢字", pyparsing_unicode.Japanese.Kanji) + setattr(pyparsing_unicode.Japanese, u"カタカナ", pyparsing_unicode.Japanese.Katakana) + setattr(pyparsing_unicode.Japanese, u"ひらがな", pyparsing_unicode.Japanese.Hiragana) + setattr(pyparsing_unicode, u"한국어", pyparsing_unicode.Korean) + setattr(pyparsing_unicode, u"ไทย", pyparsing_unicode.Thai) + setattr(pyparsing_unicode, u"देवनागरी", pyparsing_unicode.Devanagari) + + +class pyparsing_test: + """ + namespace class for classes useful in writing unit tests + """ + + class reset_pyparsing_context: + """ + Context manager to be used when writing unit tests that modify pyparsing config values: + - packrat parsing + - default whitespace characters. + - default keyword characters + - literal string auto-conversion class + - __diag__ settings + + Example: + with reset_pyparsing_context(): + # test that literals used to construct a grammar are automatically suppressed + ParserElement.inlineLiteralsUsing(Suppress) + + term = Word(alphas) | Word(nums) + group = Group('(' + term[...] + ')') + + # assert that the '()' characters are not included in the parsed tokens + self.assertParseAndCheckLisst(group, "(abc 123 def)", ['abc', '123', 'def']) + + # after exiting context manager, literals are converted to Literal expressions again + """ + + def __init__(self): + self._save_context = {} + + def save(self): + self._save_context["default_whitespace"] = ParserElement.DEFAULT_WHITE_CHARS + self._save_context["default_keyword_chars"] = Keyword.DEFAULT_KEYWORD_CHARS + self._save_context[ + "literal_string_class" + ] = ParserElement._literalStringClass + self._save_context["packrat_enabled"] = ParserElement._packratEnabled + self._save_context["packrat_parse"] = ParserElement._parse + self._save_context["__diag__"] = { + name: getattr(__diag__, name) for name in __diag__._all_names + } + self._save_context["__compat__"] = { + "collect_all_And_tokens": __compat__.collect_all_And_tokens + } + return self + + def restore(self): + # reset pyparsing global state + if ( + ParserElement.DEFAULT_WHITE_CHARS + != self._save_context["default_whitespace"] + ): + ParserElement.setDefaultWhitespaceChars( + self._save_context["default_whitespace"] + ) + Keyword.DEFAULT_KEYWORD_CHARS = self._save_context["default_keyword_chars"] + ParserElement.inlineLiteralsUsing( + self._save_context["literal_string_class"] + ) + for name, value in self._save_context["__diag__"].items(): + setattr(__diag__, name, value) + ParserElement._packratEnabled = self._save_context["packrat_enabled"] + ParserElement._parse = self._save_context["packrat_parse"] + __compat__.collect_all_And_tokens = self._save_context["__compat__"] + + def __enter__(self): + return self.save() + + def __exit__(self, *args): + return self.restore() + + class TestParseResultsAsserts: + """ + A mixin class to add parse results assertion methods to normal unittest.TestCase classes. + """ + def assertParseResultsEquals( + self, result, expected_list=None, expected_dict=None, msg=None + ): + """ + Unit test assertion to compare a ParseResults object with an optional expected_list, + and compare any defined results names with an optional expected_dict. + """ + if expected_list is not None: + self.assertEqual(expected_list, result.asList(), msg=msg) + if expected_dict is not None: + self.assertEqual(expected_dict, result.asDict(), msg=msg) + + def assertParseAndCheckList( + self, expr, test_string, expected_list, msg=None, verbose=True + ): + """ + Convenience wrapper assert to test a parser element and input string, and assert that + the resulting ParseResults.asList() is equal to the expected_list. + """ + result = expr.parseString(test_string, parseAll=True) + if verbose: + print(result.dump()) + self.assertParseResultsEquals(result, expected_list=expected_list, msg=msg) + + def assertParseAndCheckDict( + self, expr, test_string, expected_dict, msg=None, verbose=True + ): + """ + Convenience wrapper assert to test a parser element and input string, and assert that + the resulting ParseResults.asDict() is equal to the expected_dict. + """ + result = expr.parseString(test_string, parseAll=True) + if verbose: + print(result.dump()) + self.assertParseResultsEquals(result, expected_dict=expected_dict, msg=msg) + + def assertRunTestResults( + self, run_tests_report, expected_parse_results=None, msg=None + ): + """ + Unit test assertion to evaluate output of ParserElement.runTests(). If a list of + list-dict tuples is given as the expected_parse_results argument, then these are zipped + with the report tuples returned by runTests and evaluated using assertParseResultsEquals. + Finally, asserts that the overall runTests() success value is True. + + :param run_tests_report: tuple(bool, [tuple(str, ParseResults or Exception)]) returned from runTests + :param expected_parse_results (optional): [tuple(str, list, dict, Exception)] + """ + run_test_success, run_test_results = run_tests_report + + if expected_parse_results is not None: + merged = [ + (rpt[0], rpt[1], expected) + for rpt, expected in zip(run_test_results, expected_parse_results) + ] + for test_string, result, expected in merged: + # expected should be a tuple containing a list and/or a dict or an exception, + # and optional failure message string + # an empty tuple will skip any result validation + fail_msg = next( + (exp for exp in expected if isinstance(exp, str)), None + ) + expected_exception = next( + ( + exp + for exp in expected + if isinstance(exp, type) and issubclass(exp, Exception) + ), + None, + ) + if expected_exception is not None: + with self.assertRaises( + expected_exception=expected_exception, msg=fail_msg or msg + ): + if isinstance(result, Exception): + raise result + else: + expected_list = next( + (exp for exp in expected if isinstance(exp, list)), None + ) + expected_dict = next( + (exp for exp in expected if isinstance(exp, dict)), None + ) + if (expected_list, expected_dict) != (None, None): + self.assertParseResultsEquals( + result, + expected_list=expected_list, + expected_dict=expected_dict, + msg=fail_msg or msg, + ) + else: + # warning here maybe? + print("no validation for {!r}".format(test_string)) + + # do this last, in case some specific test results can be reported instead + self.assertTrue( + run_test_success, msg=msg if msg is not None else "failed runTests" + ) + + @contextmanager + def assertRaisesParseException(self, exc_type=ParseException, msg=None): + with self.assertRaises(exc_type, msg=msg): + yield if __name__ == "__main__": diff --git a/pipenv/patched/notpip/_vendor/pytoml/LICENSE b/pipenv/patched/notpip/_vendor/pytoml/LICENSE deleted file mode 100644 index 9739fc67..00000000 --- a/pipenv/patched/notpip/_vendor/pytoml/LICENSE +++ /dev/null @@ -1,16 +0,0 @@ -No-notice MIT License - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/pipenv/patched/notpip/_vendor/pytoml/__init__.py b/pipenv/patched/notpip/_vendor/pytoml/__init__.py deleted file mode 100644 index 8ed060ff..00000000 --- a/pipenv/patched/notpip/_vendor/pytoml/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .core import TomlError -from .parser import load, loads -from .test import translate_to_test -from .writer import dump, dumps \ No newline at end of file diff --git a/pipenv/patched/notpip/_vendor/pytoml/core.py b/pipenv/patched/notpip/_vendor/pytoml/core.py deleted file mode 100644 index c182734e..00000000 --- a/pipenv/patched/notpip/_vendor/pytoml/core.py +++ /dev/null @@ -1,13 +0,0 @@ -class TomlError(RuntimeError): - def __init__(self, message, line, col, filename): - RuntimeError.__init__(self, message, line, col, filename) - self.message = message - self.line = line - self.col = col - self.filename = filename - - def __str__(self): - return '{}({}, {}): {}'.format(self.filename, self.line, self.col, self.message) - - def __repr__(self): - return 'TomlError({!r}, {!r}, {!r}, {!r})'.format(self.message, self.line, self.col, self.filename) diff --git a/pipenv/patched/notpip/_vendor/pytoml/parser.py b/pipenv/patched/notpip/_vendor/pytoml/parser.py deleted file mode 100644 index 3493aa64..00000000 --- a/pipenv/patched/notpip/_vendor/pytoml/parser.py +++ /dev/null @@ -1,341 +0,0 @@ -import string, re, sys, datetime -from .core import TomlError -from .utils import rfc3339_re, parse_rfc3339_re - -if sys.version_info[0] == 2: - _chr = unichr -else: - _chr = chr - -def load(fin, translate=lambda t, x, v: v, object_pairs_hook=dict): - return loads(fin.read(), translate=translate, object_pairs_hook=object_pairs_hook, filename=getattr(fin, 'name', repr(fin))) - -def loads(s, filename='<string>', translate=lambda t, x, v: v, object_pairs_hook=dict): - if isinstance(s, bytes): - s = s.decode('utf-8') - - s = s.replace('\r\n', '\n') - - root = object_pairs_hook() - tables = object_pairs_hook() - scope = root - - src = _Source(s, filename=filename) - ast = _p_toml(src, object_pairs_hook=object_pairs_hook) - - def error(msg): - raise TomlError(msg, pos[0], pos[1], filename) - - def process_value(v, object_pairs_hook): - kind, text, value, pos = v - if kind == 'str' and value.startswith('\n'): - value = value[1:] - if kind == 'array': - if value and any(k != value[0][0] for k, t, v, p in value[1:]): - error('array-type-mismatch') - value = [process_value(item, object_pairs_hook=object_pairs_hook) for item in value] - elif kind == 'table': - value = object_pairs_hook([(k, process_value(value[k], object_pairs_hook=object_pairs_hook)) for k in value]) - return translate(kind, text, value) - - for kind, value, pos in ast: - if kind == 'kv': - k, v = value - if k in scope: - error('duplicate_keys. Key "{0}" was used more than once.'.format(k)) - scope[k] = process_value(v, object_pairs_hook=object_pairs_hook) - else: - is_table_array = (kind == 'table_array') - cur = tables - for name in value[:-1]: - if isinstance(cur.get(name), list): - d, cur = cur[name][-1] - else: - d, cur = cur.setdefault(name, (None, object_pairs_hook())) - - scope = object_pairs_hook() - name = value[-1] - if name not in cur: - if is_table_array: - cur[name] = [(scope, object_pairs_hook())] - else: - cur[name] = (scope, object_pairs_hook()) - elif isinstance(cur[name], list): - if not is_table_array: - error('table_type_mismatch') - cur[name].append((scope, object_pairs_hook())) - else: - if is_table_array: - error('table_type_mismatch') - old_scope, next_table = cur[name] - if old_scope is not None: - error('duplicate_tables') - cur[name] = (scope, next_table) - - def merge_tables(scope, tables): - if scope is None: - scope = object_pairs_hook() - for k in tables: - if k in scope: - error('key_table_conflict') - v = tables[k] - if isinstance(v, list): - scope[k] = [merge_tables(sc, tbl) for sc, tbl in v] - else: - scope[k] = merge_tables(v[0], v[1]) - return scope - - return merge_tables(root, tables) - -class _Source: - def __init__(self, s, filename=None): - self.s = s - self._pos = (1, 1) - self._last = None - self._filename = filename - self.backtrack_stack = [] - - def last(self): - return self._last - - def pos(self): - return self._pos - - def fail(self): - return self._expect(None) - - def consume_dot(self): - if self.s: - self._last = self.s[0] - self.s = self[1:] - self._advance(self._last) - return self._last - return None - - def expect_dot(self): - return self._expect(self.consume_dot()) - - def consume_eof(self): - if not self.s: - self._last = '' - return True - return False - - def expect_eof(self): - return self._expect(self.consume_eof()) - - def consume(self, s): - if self.s.startswith(s): - self.s = self.s[len(s):] - self._last = s - self._advance(s) - return True - return False - - def expect(self, s): - return self._expect(self.consume(s)) - - def consume_re(self, re): - m = re.match(self.s) - if m: - self.s = self.s[len(m.group(0)):] - self._last = m - self._advance(m.group(0)) - return m - return None - - def expect_re(self, re): - return self._expect(self.consume_re(re)) - - def __enter__(self): - self.backtrack_stack.append((self.s, self._pos)) - - def __exit__(self, type, value, traceback): - if type is None: - self.backtrack_stack.pop() - else: - self.s, self._pos = self.backtrack_stack.pop() - return type == TomlError - - def commit(self): - self.backtrack_stack[-1] = (self.s, self._pos) - - def _expect(self, r): - if not r: - raise TomlError('msg', self._pos[0], self._pos[1], self._filename) - return r - - def _advance(self, s): - suffix_pos = s.rfind('\n') - if suffix_pos == -1: - self._pos = (self._pos[0], self._pos[1] + len(s)) - else: - self._pos = (self._pos[0] + s.count('\n'), len(s) - suffix_pos) - -_ews_re = re.compile(r'(?:[ \t]|#[^\n]*\n|#[^\n]*\Z|\n)*') -def _p_ews(s): - s.expect_re(_ews_re) - -_ws_re = re.compile(r'[ \t]*') -def _p_ws(s): - s.expect_re(_ws_re) - -_escapes = { 'b': '\b', 'n': '\n', 'r': '\r', 't': '\t', '"': '"', - '\\': '\\', 'f': '\f' } - -_basicstr_re = re.compile(r'[^"\\\000-\037]*') -_short_uni_re = re.compile(r'u([0-9a-fA-F]{4})') -_long_uni_re = re.compile(r'U([0-9a-fA-F]{8})') -_escapes_re = re.compile(r'[btnfr\"\\]') -_newline_esc_re = re.compile('\n[ \t\n]*') -def _p_basicstr_content(s, content=_basicstr_re): - res = [] - while True: - res.append(s.expect_re(content).group(0)) - if not s.consume('\\'): - break - if s.consume_re(_newline_esc_re): - pass - elif s.consume_re(_short_uni_re) or s.consume_re(_long_uni_re): - v = int(s.last().group(1), 16) - if 0xd800 <= v < 0xe000: - s.fail() - res.append(_chr(v)) - else: - s.expect_re(_escapes_re) - res.append(_escapes[s.last().group(0)]) - return ''.join(res) - -_key_re = re.compile(r'[0-9a-zA-Z-_]+') -def _p_key(s): - with s: - s.expect('"') - r = _p_basicstr_content(s, _basicstr_re) - s.expect('"') - return r - if s.consume('\''): - if s.consume('\'\''): - r = s.expect_re(_litstr_ml_re).group(0) - s.expect('\'\'\'') - else: - r = s.expect_re(_litstr_re).group(0) - s.expect('\'') - return r - return s.expect_re(_key_re).group(0) - -_float_re = re.compile(r'[+-]?(?:0|[1-9](?:_?\d)*)(?:\.\d(?:_?\d)*)?(?:[eE][+-]?(?:\d(?:_?\d)*))?') - -_basicstr_ml_re = re.compile(r'(?:""?(?!")|[^"\\\000-\011\013-\037])*') -_litstr_re = re.compile(r"[^'\000\010\012-\037]*") -_litstr_ml_re = re.compile(r"(?:(?:|'|'')(?:[^'\000-\010\013-\037]))*") -def _p_value(s, object_pairs_hook): - pos = s.pos() - - if s.consume('true'): - return 'bool', s.last(), True, pos - if s.consume('false'): - return 'bool', s.last(), False, pos - - if s.consume('"'): - if s.consume('""'): - r = _p_basicstr_content(s, _basicstr_ml_re) - s.expect('"""') - else: - r = _p_basicstr_content(s, _basicstr_re) - s.expect('"') - return 'str', r, r, pos - - if s.consume('\''): - if s.consume('\'\''): - r = s.expect_re(_litstr_ml_re).group(0) - s.expect('\'\'\'') - else: - r = s.expect_re(_litstr_re).group(0) - s.expect('\'') - return 'str', r, r, pos - - if s.consume_re(rfc3339_re): - m = s.last() - return 'datetime', m.group(0), parse_rfc3339_re(m), pos - - if s.consume_re(_float_re): - m = s.last().group(0) - r = m.replace('_','') - if '.' in m or 'e' in m or 'E' in m: - return 'float', m, float(r), pos - else: - return 'int', m, int(r, 10), pos - - if s.consume('['): - items = [] - with s: - while True: - _p_ews(s) - items.append(_p_value(s, object_pairs_hook=object_pairs_hook)) - s.commit() - _p_ews(s) - s.expect(',') - s.commit() - _p_ews(s) - s.expect(']') - return 'array', None, items, pos - - if s.consume('{'): - _p_ws(s) - items = object_pairs_hook() - if not s.consume('}'): - k = _p_key(s) - _p_ws(s) - s.expect('=') - _p_ws(s) - items[k] = _p_value(s, object_pairs_hook=object_pairs_hook) - _p_ws(s) - while s.consume(','): - _p_ws(s) - k = _p_key(s) - _p_ws(s) - s.expect('=') - _p_ws(s) - items[k] = _p_value(s, object_pairs_hook=object_pairs_hook) - _p_ws(s) - s.expect('}') - return 'table', None, items, pos - - s.fail() - -def _p_stmt(s, object_pairs_hook): - pos = s.pos() - if s.consume( '['): - is_array = s.consume('[') - _p_ws(s) - keys = [_p_key(s)] - _p_ws(s) - while s.consume('.'): - _p_ws(s) - keys.append(_p_key(s)) - _p_ws(s) - s.expect(']') - if is_array: - s.expect(']') - return 'table_array' if is_array else 'table', keys, pos - - key = _p_key(s) - _p_ws(s) - s.expect('=') - _p_ws(s) - value = _p_value(s, object_pairs_hook=object_pairs_hook) - return 'kv', (key, value), pos - -_stmtsep_re = re.compile(r'(?:[ \t]*(?:#[^\n]*)?\n)+[ \t]*') -def _p_toml(s, object_pairs_hook): - stmts = [] - _p_ews(s) - with s: - stmts.append(_p_stmt(s, object_pairs_hook=object_pairs_hook)) - while True: - s.commit() - s.expect_re(_stmtsep_re) - stmts.append(_p_stmt(s, object_pairs_hook=object_pairs_hook)) - _p_ews(s) - s.expect_eof() - return stmts diff --git a/pipenv/patched/notpip/_vendor/pytoml/test.py b/pipenv/patched/notpip/_vendor/pytoml/test.py deleted file mode 100644 index ec8abfc6..00000000 --- a/pipenv/patched/notpip/_vendor/pytoml/test.py +++ /dev/null @@ -1,30 +0,0 @@ -import datetime -from .utils import format_rfc3339 - -try: - _string_types = (str, unicode) - _int_types = (int, long) -except NameError: - _string_types = str - _int_types = int - -def translate_to_test(v): - if isinstance(v, dict): - return { k: translate_to_test(v) for k, v in v.items() } - if isinstance(v, list): - a = [translate_to_test(x) for x in v] - if v and isinstance(v[0], dict): - return a - else: - return {'type': 'array', 'value': a} - if isinstance(v, datetime.datetime): - return {'type': 'datetime', 'value': format_rfc3339(v)} - if isinstance(v, bool): - return {'type': 'bool', 'value': 'true' if v else 'false'} - if isinstance(v, _int_types): - return {'type': 'integer', 'value': str(v)} - if isinstance(v, float): - return {'type': 'float', 'value': '{:.17}'.format(v)} - if isinstance(v, _string_types): - return {'type': 'string', 'value': v} - raise RuntimeError('unexpected value: {!r}'.format(v)) diff --git a/pipenv/patched/notpip/_vendor/pytoml/utils.py b/pipenv/patched/notpip/_vendor/pytoml/utils.py deleted file mode 100644 index 636a680b..00000000 --- a/pipenv/patched/notpip/_vendor/pytoml/utils.py +++ /dev/null @@ -1,67 +0,0 @@ -import datetime -import re - -rfc3339_re = re.compile(r'(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.\d+)?(?:Z|([+-]\d{2}):(\d{2}))') - -def parse_rfc3339(v): - m = rfc3339_re.match(v) - if not m or m.group(0) != v: - return None - return parse_rfc3339_re(m) - -def parse_rfc3339_re(m): - r = map(int, m.groups()[:6]) - if m.group(7): - micro = float(m.group(7)) - else: - micro = 0 - - if m.group(8): - g = int(m.group(8), 10) * 60 + int(m.group(9), 10) - tz = _TimeZone(datetime.timedelta(0, g * 60)) - else: - tz = _TimeZone(datetime.timedelta(0, 0)) - - y, m, d, H, M, S = r - return datetime.datetime(y, m, d, H, M, S, int(micro * 1000000), tz) - - -def format_rfc3339(v): - offs = v.utcoffset() - offs = int(offs.total_seconds()) // 60 if offs is not None else 0 - - if offs == 0: - suffix = 'Z' - else: - if offs > 0: - suffix = '+' - else: - suffix = '-' - offs = -offs - suffix = '{0}{1:02}:{2:02}'.format(suffix, offs // 60, offs % 60) - - if v.microsecond: - return v.strftime('%Y-%m-%dT%H:%M:%S.%f') + suffix - else: - return v.strftime('%Y-%m-%dT%H:%M:%S') + suffix - -class _TimeZone(datetime.tzinfo): - def __init__(self, offset): - self._offset = offset - - def utcoffset(self, dt): - return self._offset - - def dst(self, dt): - return None - - def tzname(self, dt): - m = self._offset.total_seconds() // 60 - if m < 0: - res = '-' - m = -m - else: - res = '+' - h = m // 60 - m = m - h * 60 - return '{}{:.02}{:.02}'.format(res, h, m) diff --git a/pipenv/patched/notpip/_vendor/pytoml/writer.py b/pipenv/patched/notpip/_vendor/pytoml/writer.py deleted file mode 100644 index 73b5089c..00000000 --- a/pipenv/patched/notpip/_vendor/pytoml/writer.py +++ /dev/null @@ -1,106 +0,0 @@ -from __future__ import unicode_literals -import io, datetime, math, string, sys - -from .utils import format_rfc3339 - -if sys.version_info[0] == 3: - long = int - unicode = str - - -def dumps(obj, sort_keys=False): - fout = io.StringIO() - dump(obj, fout, sort_keys=sort_keys) - return fout.getvalue() - - -_escapes = {'\n': 'n', '\r': 'r', '\\': '\\', '\t': 't', '\b': 'b', '\f': 'f', '"': '"'} - - -def _escape_string(s): - res = [] - start = 0 - - def flush(): - if start != i: - res.append(s[start:i]) - return i + 1 - - i = 0 - while i < len(s): - c = s[i] - if c in '"\\\n\r\t\b\f': - start = flush() - res.append('\\' + _escapes[c]) - elif ord(c) < 0x20: - start = flush() - res.append('\\u%04x' % ord(c)) - i += 1 - - flush() - return '"' + ''.join(res) + '"' - - -_key_chars = string.digits + string.ascii_letters + '-_' -def _escape_id(s): - if any(c not in _key_chars for c in s): - return _escape_string(s) - return s - - -def _format_value(v): - if isinstance(v, bool): - return 'true' if v else 'false' - if isinstance(v, int) or isinstance(v, long): - return unicode(v) - if isinstance(v, float): - if math.isnan(v) or math.isinf(v): - raise ValueError("{0} is not a valid TOML value".format(v)) - else: - return repr(v) - elif isinstance(v, unicode) or isinstance(v, bytes): - return _escape_string(v) - elif isinstance(v, datetime.datetime): - return format_rfc3339(v) - elif isinstance(v, list): - return '[{0}]'.format(', '.join(_format_value(obj) for obj in v)) - elif isinstance(v, dict): - return '{{{0}}}'.format(', '.join('{} = {}'.format(_escape_id(k), _format_value(obj)) for k, obj in v.items())) - else: - raise RuntimeError(v) - - -def dump(obj, fout, sort_keys=False): - tables = [((), obj, False)] - - while tables: - name, table, is_array = tables.pop() - if name: - section_name = '.'.join(_escape_id(c) for c in name) - if is_array: - fout.write('[[{0}]]\n'.format(section_name)) - else: - fout.write('[{0}]\n'.format(section_name)) - - table_keys = sorted(table.keys()) if sort_keys else table.keys() - new_tables = [] - has_kv = False - for k in table_keys: - v = table[k] - if isinstance(v, dict): - new_tables.append((name + (k,), v, False)) - elif isinstance(v, list) and v and all(isinstance(o, dict) for o in v): - new_tables.extend((name + (k,), d, True) for d in v) - elif v is None: - # based on mojombo's comment: https://github.com/toml-lang/toml/issues/146#issuecomment-25019344 - fout.write( - '#{} = null # To use: uncomment and replace null with value\n'.format(_escape_id(k))) - has_kv = True - else: - fout.write('{0} = {1}\n'.format(_escape_id(k), _format_value(v))) - has_kv = True - - tables.extend(reversed(new_tables)) - - if (name or has_kv) and tables: - fout.write('\n') diff --git a/pipenv/patched/notpip/_vendor/requests/LICENSE b/pipenv/patched/notpip/_vendor/requests/LICENSE index 841c6023..67db8588 100644 --- a/pipenv/patched/notpip/_vendor/requests/LICENSE +++ b/pipenv/patched/notpip/_vendor/requests/LICENSE @@ -1,13 +1,175 @@ -Copyright 2018 Kenneth Reitz - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ - https://www.apache.org/licenses/LICENSE-2.0 + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/pipenv/patched/notpip/_vendor/requests/__init__.py b/pipenv/patched/notpip/_vendor/requests/__init__.py index 1544bc89..1fce9188 100644 --- a/pipenv/patched/notpip/_vendor/requests/__init__.py +++ b/pipenv/patched/notpip/_vendor/requests/__init__.py @@ -9,14 +9,14 @@ Requests HTTP Library ~~~~~~~~~~~~~~~~~~~~~ -Requests is an HTTP library, written in Python, for human beings. Basic GET -usage: +Requests is an HTTP library, written in Python, for human beings. +Basic GET usage: >>> import requests >>> r = requests.get('https://www.python.org') >>> r.status_code 200 - >>> 'Python is a programming language' in r.content + >>> b'Python is a programming language' in r.content True ... or POST: @@ -27,26 +27,31 @@ usage: { ... "form": { - "key2": "value2", - "key1": "value1" + "key1": "value1", + "key2": "value2" }, ... } The other HTTP methods are supported - see `requests.api`. Full documentation -is at <http://python-requests.org>. +is at <https://requests.readthedocs.io>. :copyright: (c) 2017 by Kenneth Reitz. :license: Apache 2.0, see LICENSE for more details. """ from pipenv.patched.notpip._vendor import urllib3 -from pipenv.patched.notpip._vendor import chardet import warnings from .exceptions import RequestsDependencyWarning +charset_normalizer_version = None -def check_compatibility(urllib3_version, chardet_version): +try: + from pipenv.patched.notpip._vendor.chardet import __version__ as chardet_version +except ImportError: + chardet_version = None + +def check_compatibility(urllib3_version, chardet_version, charset_normalizer_version): urllib3_version = urllib3_version.split('.') assert urllib3_version != ['dev'] # Verify urllib3 isn't installed from git. @@ -57,19 +62,24 @@ def check_compatibility(urllib3_version, chardet_version): # Check urllib3 for compatibility. major, minor, patch = urllib3_version # noqa: F811 major, minor, patch = int(major), int(minor), int(patch) - # urllib3 >= 1.21.1, <= 1.24 + # urllib3 >= 1.21.1, <= 1.26 assert major == 1 assert minor >= 21 - assert minor <= 24 - - # Check chardet for compatibility. - major, minor, patch = chardet_version.split('.')[:3] - major, minor, patch = int(major), int(minor), int(patch) - # chardet >= 3.0.2, < 3.1.0 - assert major == 3 - assert minor < 1 - assert patch >= 2 + assert minor <= 26 + # Check charset_normalizer for compatibility. + if chardet_version: + major, minor, patch = chardet_version.split('.')[:3] + major, minor, patch = int(major), int(minor), int(patch) + # chardet_version >= 3.0.2, < 5.0.0 + assert (3, 0, 2) <= (major, minor, patch) < (5, 0, 0) + elif charset_normalizer_version: + major, minor, patch = charset_normalizer_version.split('.')[:3] + major, minor, patch = int(major), int(minor), int(patch) + # charset_normalizer >= 2.0.0 < 3.0.0 + assert (2, 0, 0) <= (major, minor, patch) < (3, 0, 0) + else: + raise Exception("You need either charset_normalizer or chardet installed") def _check_cryptography(cryptography_version): # cryptography < 1.3.4 @@ -84,24 +94,35 @@ def _check_cryptography(cryptography_version): # Check imported dependencies for compatibility. try: - check_compatibility(urllib3.__version__, chardet.__version__) + check_compatibility(urllib3.__version__, chardet_version, charset_normalizer_version) except (AssertionError, ValueError): - warnings.warn("urllib3 ({}) or chardet ({}) doesn't match a supported " - "version!".format(urllib3.__version__, chardet.__version__), + warnings.warn("urllib3 ({}) or chardet ({})/charset_normalizer ({}) doesn't match a supported " + "version!".format(urllib3.__version__, chardet_version, charset_normalizer_version), RequestsDependencyWarning) -# Attempt to enable urllib3's SNI support, if possible -from pipenv.patched.notpip._internal.utils.compat import WINDOWS -if not WINDOWS: +# Attempt to enable urllib3's fallback for SNI support +# if the standard library doesn't support SNI or the +# 'ssl' library isn't available. +try: + # Note: This logic prevents upgrading cryptography on Windows, if imported + # as part of pip. + from pipenv.patched.notpip._internal.utils.compat import WINDOWS + if not WINDOWS: + raise ImportError("pip internals: don't import cryptography on Windows") try: + import ssl + except ImportError: + ssl = None + + if not getattr(ssl, "HAS_SNI", False): from pipenv.patched.notpip._vendor.urllib3.contrib import pyopenssl pyopenssl.inject_into_urllib3() # Check cryptography version from cryptography import __version__ as cryptography_version _check_cryptography(cryptography_version) - except ImportError: - pass +except ImportError: + pass # urllib3's DependencyWarnings should be silenced. from pipenv.patched.notpip._vendor.urllib3.exceptions import DependencyWarning diff --git a/pipenv/patched/notpip/_vendor/requests/__version__.py b/pipenv/patched/notpip/_vendor/requests/__version__.py index f5b5d036..0d7cde1d 100644 --- a/pipenv/patched/notpip/_vendor/requests/__version__.py +++ b/pipenv/patched/notpip/_vendor/requests/__version__.py @@ -4,11 +4,11 @@ __title__ = 'requests' __description__ = 'Python HTTP for Humans.' -__url__ = 'http://python-requests.org' -__version__ = '2.21.0' -__build__ = 0x022100 +__url__ = 'https://requests.readthedocs.io' +__version__ = '2.26.0' +__build__ = 0x022600 __author__ = 'Kenneth Reitz' __author_email__ = 'me@kennethreitz.org' __license__ = 'Apache 2.0' -__copyright__ = 'Copyright 2018 Kenneth Reitz' +__copyright__ = 'Copyright 2020 Kenneth Reitz' __cake__ = u'\u2728 \U0001f370 \u2728' diff --git a/pipenv/patched/notpip/_vendor/requests/api.py b/pipenv/patched/notpip/_vendor/requests/api.py index abada96d..4cba90ee 100644 --- a/pipenv/patched/notpip/_vendor/requests/api.py +++ b/pipenv/patched/notpip/_vendor/requests/api.py @@ -16,10 +16,10 @@ from . import sessions def request(method, url, **kwargs): """Constructs and sends a :class:`Request <Request>`. - :param method: method for the new :class:`Request` object. + :param method: method for the new :class:`Request` object: ``GET``, ``OPTIONS``, ``HEAD``, ``POST``, ``PUT``, ``PATCH``, or ``DELETE``. :param url: URL for the new :class:`Request` object. :param params: (optional) Dictionary, list of tuples or bytes to send - in the body of the :class:`Request`. + in the query string for the :class:`Request`. :param data: (optional) Dictionary, list of tuples, bytes, or file-like object to send in the body of the :class:`Request`. :param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`. @@ -50,6 +50,7 @@ def request(method, url, **kwargs): >>> import requests >>> req = requests.request('GET', 'https://httpbin.org/get') + >>> req <Response [200]> """ @@ -65,13 +66,12 @@ def get(url, params=None, **kwargs): :param url: URL for the new :class:`Request` object. :param params: (optional) Dictionary, list of tuples or bytes to send - in the body of the :class:`Request`. + in the query string for the :class:`Request`. :param \*\*kwargs: Optional arguments that ``request`` takes. :return: :class:`Response <Response>` object :rtype: requests.Response """ - kwargs.setdefault('allow_redirects', True) return request('get', url, params=params, **kwargs) @@ -84,7 +84,6 @@ def options(url, **kwargs): :rtype: requests.Response """ - kwargs.setdefault('allow_redirects', True) return request('options', url, **kwargs) @@ -92,7 +91,9 @@ def head(url, **kwargs): r"""Sends a HEAD request. :param url: URL for the new :class:`Request` object. - :param \*\*kwargs: Optional arguments that ``request`` takes. + :param \*\*kwargs: Optional arguments that ``request`` takes. If + `allow_redirects` is not provided, it will be set to `False` (as + opposed to the default :meth:`request` behavior). :return: :class:`Response <Response>` object :rtype: requests.Response """ diff --git a/pipenv/patched/notpip/_vendor/requests/auth.py b/pipenv/patched/notpip/_vendor/requests/auth.py index bdde51c7..eeface39 100644 --- a/pipenv/patched/notpip/_vendor/requests/auth.py +++ b/pipenv/patched/notpip/_vendor/requests/auth.py @@ -50,7 +50,7 @@ def _basic_auth_str(username, password): "Non-string passwords will no longer be supported in Requests " "3.0.0. Please convert the object you've passed in ({!r}) to " "a string or bytes object in the near future to avoid " - "problems.".format(password), + "problems.".format(type(password)), category=DeprecationWarning, ) password = str(password) @@ -239,7 +239,7 @@ class HTTPDigestAuth(AuthBase): """ # If response is not 4xx, do not auth - # See https://github.com/requests/requests/issues/3772 + # See https://github.com/psf/requests/issues/3772 if not 400 <= r.status_code < 500: self._thread_local.num_401_calls = 1 return r diff --git a/pipenv/patched/notpip/_vendor/requests/compat.py b/pipenv/patched/notpip/_vendor/requests/compat.py index 7c143940..f2df3db3 100644 --- a/pipenv/patched/notpip/_vendor/requests/compat.py +++ b/pipenv/patched/notpip/_vendor/requests/compat.py @@ -47,6 +47,7 @@ if is_py2: import cookielib from Cookie import Morsel from StringIO import StringIO + # Keep OrderedDict for backwards compatibility. from collections import Callable, Mapping, MutableMapping, OrderedDict @@ -63,6 +64,7 @@ elif is_py3: from http import cookiejar as cookielib from http.cookies import Morsel from io import StringIO + # Keep OrderedDict for backwards compatibility. from collections import OrderedDict from collections.abc import Callable, Mapping, MutableMapping diff --git a/pipenv/patched/notpip/_vendor/requests/exceptions.py b/pipenv/patched/notpip/_vendor/requests/exceptions.py index 78b573c7..151ab9c0 100644 --- a/pipenv/patched/notpip/_vendor/requests/exceptions.py +++ b/pipenv/patched/notpip/_vendor/requests/exceptions.py @@ -25,6 +25,10 @@ class RequestException(IOError): super(RequestException, self).__init__(*args, **kwargs) +class InvalidJSONError(RequestException): + """A JSON error occurred.""" + + class HTTPError(RequestException): """An HTTP error occurred.""" @@ -94,11 +98,11 @@ class ChunkedEncodingError(RequestException): class ContentDecodingError(RequestException, BaseHTTPError): - """Failed to decode response content""" + """Failed to decode response content.""" class StreamConsumedError(RequestException, TypeError): - """The content for this response was already consumed""" + """The content for this response was already consumed.""" class RetryError(RequestException): @@ -106,21 +110,18 @@ class RetryError(RequestException): class UnrewindableBodyError(RequestException): - """Requests encountered an error when trying to rewind a body""" + """Requests encountered an error when trying to rewind a body.""" # Warnings class RequestsWarning(Warning): """Base warning for Requests.""" - pass class FileModeWarning(RequestsWarning, DeprecationWarning): """A file was opened in text mode, but Requests determined its binary length.""" - pass class RequestsDependencyWarning(RequestsWarning): """An imported dependency doesn't match the expected version range.""" - pass diff --git a/pipenv/patched/notpip/_vendor/requests/help.py b/pipenv/patched/notpip/_vendor/requests/help.py index 72d72160..44bb453b 100644 --- a/pipenv/patched/notpip/_vendor/requests/help.py +++ b/pipenv/patched/notpip/_vendor/requests/help.py @@ -8,10 +8,16 @@ import ssl from pipenv.patched.notpip._vendor import idna from pipenv.patched.notpip._vendor import urllib3 -from pipenv.patched.notpip._vendor import chardet from . import __version__ as requests_version +charset_normalizer = None + +try: + from pipenv.patched.notpip._vendor import chardet +except ImportError: + chardet = None + try: from pipenv.patched.notpip._vendor.urllib3.contrib import pyopenssl except ImportError: @@ -71,7 +77,12 @@ def info(): implementation_info = _implementation() urllib3_info = {'version': urllib3.__version__} - chardet_info = {'version': chardet.__version__} + charset_normalizer_info = {'version': None} + chardet_info = {'version': None} + if charset_normalizer: + charset_normalizer_info = {'version': charset_normalizer.__version__} + if chardet: + chardet_info = {'version': chardet.__version__} pyopenssl_info = { 'version': None, @@ -99,9 +110,11 @@ def info(): 'implementation': implementation_info, 'system_ssl': system_ssl_info, 'using_pyopenssl': pyopenssl is not None, + 'using_charset_normalizer': chardet is None, 'pyOpenSSL': pyopenssl_info, 'urllib3': urllib3_info, 'chardet': chardet_info, + 'charset_normalizer': charset_normalizer_info, 'cryptography': cryptography_info, 'idna': idna_info, 'requests': { diff --git a/pipenv/patched/notpip/_vendor/requests/models.py b/pipenv/patched/notpip/_vendor/requests/models.py index 5f899c42..02d959c6 100644 --- a/pipenv/patched/notpip/_vendor/requests/models.py +++ b/pipenv/patched/notpip/_vendor/requests/models.py @@ -12,7 +12,7 @@ import sys # Import encoding now, to avoid implicit import later. # Implicit import within threads may cause LookupError when standard library is in a ZIP, -# such as in Embedded Python. See https://github.com/requests/requests/issues/3578. +# such as in Embedded Python. See https://github.com/psf/requests/issues/3578. import encodings.idna from pipenv.patched.notpip._vendor.urllib3.fields import RequestField @@ -29,7 +29,7 @@ from .auth import HTTPBasicAuth from .cookies import cookiejar_from_dict, get_cookie_header, _copy_cookie_jar from .exceptions import ( HTTPError, MissingSchema, InvalidURL, ChunkedEncodingError, - ContentDecodingError, ConnectionError, StreamConsumedError) + ContentDecodingError, ConnectionError, StreamConsumedError, InvalidJSONError) from ._internal_utils import to_native_string, unicode_is_ascii from .utils import ( guess_filename, get_auth_from_url, requote_uri, @@ -273,13 +273,16 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): """The fully mutable :class:`PreparedRequest <PreparedRequest>` object, containing the exact bytes that will be sent to the server. - Generated from either a :class:`Request <Request>` object or manually. + Instances are generated from a :class:`Request <Request>` object, and + should not be instantiated manually; doing so may produce undesirable + effects. Usage:: >>> import requests >>> req = requests.Request('GET', 'https://httpbin.org/get') >>> r = req.prepare() + >>> r <PreparedRequest [GET]> >>> s = requests.Session() @@ -358,7 +361,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): #: We're unable to blindly call unicode/str functions #: as this will include the bytestring indicator (b'') #: on python 3.x. - #: https://github.com/requests/requests/pull/2238 + #: https://github.com/psf/requests/pull/2238 if isinstance(url, bytes): url = url.decode('utf8') else: @@ -463,7 +466,12 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): # urllib3 requires a bytes-like body. Python 2's json.dumps # provides this natively, but Python 3 gives a Unicode string. content_type = 'application/json' - body = complexjson.dumps(json) + + try: + body = complexjson.dumps(json, allow_nan=False) + except ValueError as ve: + raise InvalidJSONError(ve, request=self) + if not isinstance(body, bytes): body = body.encode('utf-8') @@ -472,12 +480,12 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): not isinstance(data, (basestring, list, tuple, Mapping)) ]) - try: - length = super_len(data) - except (TypeError, AttributeError, UnsupportedOperation): - length = None - if is_stream: + try: + length = super_len(data) + except (TypeError, AttributeError, UnsupportedOperation): + length = None + body = data if getattr(body, 'tell', None) is not None: @@ -608,7 +616,7 @@ class Response(object): #: File-like object representation of response (for advanced usage). #: Use of ``raw`` requires that ``stream=True`` be set on the request. - # This requirement does not apply for use internally to Requests. + #: This requirement does not apply for use internally to Requests. self.raw = None #: Final URL location of Response. @@ -723,7 +731,7 @@ class Response(object): @property def apparent_encoding(self): - """The apparent encoding, provided by the chardet library.""" + """The apparent encoding, provided by the charset_normalizer or chardet libraries.""" return chardet.detect(self.content)['encoding'] def iter_content(self, chunk_size=1, decode_unicode=False): @@ -837,7 +845,7 @@ class Response(object): """Content of the response, in unicode. If Response.encoding is None, encoding will be guessed using - ``chardet``. + ``charset_normalizer`` or ``chardet``. The encoding of the response content is determined based solely on HTTP headers, following RFC 2616 to the letter. If you can take advantage of @@ -874,13 +882,18 @@ class Response(object): r"""Returns the json-encoded content of a response, if any. :param \*\*kwargs: Optional arguments that ``json.loads`` takes. - :raises ValueError: If the response body does not contain valid json. + :raises simplejson.JSONDecodeError: If the response body does not + contain valid json and simplejson is installed. + :raises json.JSONDecodeError: If the response body does not contain + valid json and simplejson is not installed on Python 3. + :raises ValueError: If the response body does not contain valid + json and simplejson is not installed on Python 2. """ if not self.encoding and self.content and len(self.content) > 3: # No encoding set. JSON RFC 4627 section 3 states we should expect # UTF-8, -16 or -32. Detect which one to use; If the detection or - # decoding fails, fall back to `self.text` (using chardet to make + # decoding fails, fall back to `self.text` (using charset_normalizer to make # a best guess). encoding = guess_json_utf(self.content) if encoding is not None: @@ -915,7 +928,7 @@ class Response(object): return l def raise_for_status(self): - """Raises stored :class:`HTTPError`, if one occurred.""" + """Raises :class:`HTTPError`, if one occurred.""" http_error_msg = '' if isinstance(self.reason, bytes): diff --git a/pipenv/patched/notpip/_vendor/requests/packages.py b/pipenv/patched/notpip/_vendor/requests/packages.py index 258c89ed..5fb6f07d 100644 --- a/pipenv/patched/notpip/_vendor/requests/packages.py +++ b/pipenv/patched/notpip/_vendor/requests/packages.py @@ -4,13 +4,13 @@ import sys # I don't like it either. Just look the other way. :) for package in ('urllib3', 'idna', 'chardet'): - vendored_package = "notpip._vendor." + package + vendored_package = "pipenv.patched.notpip._vendor." + package locals()[package] = __import__(vendored_package) # This traversal is apparently necessary such that the identities are # preserved (requests.packages.urllib3.* is urllib3.*) for mod in list(sys.modules): if mod == vendored_package or mod.startswith(vendored_package + '.'): - unprefixed_mod = mod[len("notpip._vendor."):] - sys.modules['notpip._vendor.requests.packages.' + unprefixed_mod] = sys.modules[mod] + unprefixed_mod = mod[len("pipenv.patched.notpip._vendor."):] + sys.modules['pipenv.patched.notpip._vendor.requests.packages.' + unprefixed_mod] = sys.modules[mod] # Kinda cool, though, right? diff --git a/pipenv/patched/notpip/_vendor/requests/sessions.py b/pipenv/patched/notpip/_vendor/requests/sessions.py index d73d700f..ae4bcc8e 100644 --- a/pipenv/patched/notpip/_vendor/requests/sessions.py +++ b/pipenv/patched/notpip/_vendor/requests/sessions.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- """ -requests.session -~~~~~~~~~~~~~~~~ +requests.sessions +~~~~~~~~~~~~~~~~~ This module provides a Session object to manage and persist settings across requests (cookies, auth, proxies). @@ -11,9 +11,10 @@ import os import sys import time from datetime import timedelta +from collections import OrderedDict from .auth import _basic_auth_str -from .compat import cookielib, is_py3, OrderedDict, urljoin, urlparse, Mapping +from .compat import cookielib, is_py3, urljoin, urlparse, Mapping from .cookies import ( cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar, merge_cookies) from .models import Request, PreparedRequest, DEFAULT_REDIRECT_LIMIT @@ -162,7 +163,7 @@ class SessionRedirectMixin(object): resp.raw.read(decode_content=False) if len(resp.history) >= self.max_redirects: - raise TooManyRedirects('Exceeded %s redirects.' % self.max_redirects, response=resp) + raise TooManyRedirects('Exceeded {} redirects.'.format(self.max_redirects), response=resp) # Release the connection back into the pool. resp.close() @@ -170,7 +171,7 @@ class SessionRedirectMixin(object): # Handle redirection without scheme (see: RFC 1808 Section 4) if url.startswith('//'): parsed_rurl = urlparse(resp.url) - url = '%s:%s' % (to_native_string(parsed_rurl.scheme), url) + url = ':'.join([to_native_string(parsed_rurl.scheme), url]) # Normalize url case and attach previous fragment if needed (RFC 7231 7.1.2) parsed = urlparse(url) @@ -192,19 +193,16 @@ class SessionRedirectMixin(object): self.rebuild_method(prepared_request, resp) - # https://github.com/requests/requests/issues/1084 + # https://github.com/psf/requests/issues/1084 if resp.status_code not in (codes.temporary_redirect, codes.permanent_redirect): - # https://github.com/requests/requests/issues/3490 + # https://github.com/psf/requests/issues/3490 purged_headers = ('Content-Length', 'Content-Type', 'Transfer-Encoding') for header in purged_headers: prepared_request.headers.pop(header, None) prepared_request.body = None headers = prepared_request.headers - try: - del headers['Cookie'] - except KeyError: - pass + headers.pop('Cookie', None) # Extract any cookies sent on the response to the cookiejar # in the new request. Because we've mutated our copied prepared @@ -271,7 +269,6 @@ class SessionRedirectMixin(object): if new_auth is not None: prepared_request.prepare_auth(new_auth) - return def rebuild_proxies(self, prepared_request, proxies): """This method re-evaluates the proxy configuration by considering the @@ -352,13 +349,13 @@ class Session(SessionRedirectMixin): Or as a context manager:: >>> with requests.Session() as s: - >>> s.get('https://httpbin.org/get') + ... s.get('https://httpbin.org/get') <Response [200]> """ __attrs__ = [ 'headers', 'cookies', 'auth', 'proxies', 'hooks', 'params', 'verify', - 'cert', 'prefetch', 'adapters', 'stream', 'trust_env', + 'cert', 'adapters', 'stream', 'trust_env', 'max_redirects', ] @@ -390,6 +387,13 @@ class Session(SessionRedirectMixin): self.stream = False #: SSL Verification default. + #: Defaults to `True`, requiring requests to verify the TLS certificate at the + #: remote end. + #: If verify is set to `False`, requests will accept any TLS certificate + #: presented by the server, and will ignore hostname mismatches and/or + #: expired certificates, which will make your application vulnerable to + #: man-in-the-middle (MitM) attacks. + #: Only set this to `False` for testing. self.verify = True #: SSL client certificate default, if String, path to ssl client @@ -498,7 +502,12 @@ class Session(SessionRedirectMixin): content. Defaults to ``False``. :param verify: (optional) Either a boolean, in which case it controls whether we verify the server's TLS certificate, or a string, in which case it must be a path - to a CA bundle to use. Defaults to ``True``. + to a CA bundle to use. Defaults to ``True``. When set to + ``False``, requests will accept any TLS certificate presented by + the server, and will ignore hostname mismatches and/or expired + certificates, which will make your application vulnerable to + man-in-the-middle (MitM) attacks. Setting verify to ``False`` + may be useful during local development or testing. :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair. :rtype: requests.Response @@ -624,7 +633,7 @@ class Session(SessionRedirectMixin): kwargs.setdefault('stream', self.stream) kwargs.setdefault('verify', self.verify) kwargs.setdefault('cert', self.cert) - kwargs.setdefault('proxies', self.proxies) + kwargs.setdefault('proxies', self.rebuild_proxies(request, self.proxies)) # It's possible that users might accidentally send a Request object. # Guard against that specific failure case. @@ -661,11 +670,13 @@ class Session(SessionRedirectMixin): extract_cookies_to_jar(self.cookies, request, r.raw) - # Redirect resolving generator. - gen = self.resolve_redirects(r, request, **kwargs) - # Resolve redirects if allowed. - history = [resp for resp in gen] if allow_redirects else [] + if allow_redirects: + # Redirect resolving generator. + gen = self.resolve_redirects(r, request, **kwargs) + history = [resp for resp in gen] + else: + history = [] # Shuffle things around if there's history. if history: @@ -728,7 +739,7 @@ class Session(SessionRedirectMixin): return adapter # Nothing matches :-/ - raise InvalidSchema("No connection adapters were found for '%s'" % url) + raise InvalidSchema("No connection adapters were found for {!r}".format(url)) def close(self): """Closes all adapters and as such the session""" diff --git a/pipenv/patched/notpip/_vendor/requests/status_codes.py b/pipenv/patched/notpip/_vendor/requests/status_codes.py index 813e8c4e..d80a7cd4 100644 --- a/pipenv/patched/notpip/_vendor/requests/status_codes.py +++ b/pipenv/patched/notpip/_vendor/requests/status_codes.py @@ -5,12 +5,15 @@ The ``codes`` object defines a mapping from common names for HTTP statuses to their numerical codes, accessible either as attributes or as dictionary items. ->>> requests.codes['temporary_redirect'] -307 ->>> requests.codes.teapot -418 ->>> requests.codes['\o/'] -200 +Example:: + + >>> import requests + >>> requests.codes['temporary_redirect'] + 307 + >>> requests.codes.teapot + 418 + >>> requests.codes['\o/'] + 200 Some codes have multiple names, and both upper- and lower-case versions of the names are allowed. For example, ``codes.ok``, ``codes.OK``, and diff --git a/pipenv/patched/notpip/_vendor/requests/structures.py b/pipenv/patched/notpip/_vendor/requests/structures.py index da930e28..8ee0ba7a 100644 --- a/pipenv/patched/notpip/_vendor/requests/structures.py +++ b/pipenv/patched/notpip/_vendor/requests/structures.py @@ -7,7 +7,9 @@ requests.structures Data structures that power Requests. """ -from .compat import OrderedDict, Mapping, MutableMapping +from collections import OrderedDict + +from .compat import Mapping, MutableMapping class CaseInsensitiveDict(MutableMapping): diff --git a/pipenv/patched/notpip/_vendor/requests/utils.py b/pipenv/patched/notpip/_vendor/requests/utils.py index 8170a8d2..e7450108 100644 --- a/pipenv/patched/notpip/_vendor/requests/utils.py +++ b/pipenv/patched/notpip/_vendor/requests/utils.py @@ -19,6 +19,8 @@ import sys import tempfile import warnings import zipfile +from collections import OrderedDict +from pipenv.patched.notpip._vendor.urllib3.util import make_headers from .__version__ import __version__ from . import certs @@ -26,7 +28,7 @@ from . import certs from ._internal_utils import to_native_string from .compat import parse_http_list as _parse_list_header from .compat import ( - quote, urlparse, bytes, str, OrderedDict, unquote, getproxies, + quote, urlparse, bytes, str, unquote, getproxies, proxy_bypass, urlunparse, basestring, integer_types, is_py3, proxy_bypass_environment, getproxies_environment, Mapping) from .cookies import cookiejar_from_dict @@ -40,6 +42,11 @@ DEFAULT_CA_BUNDLE_PATH = certs.where() DEFAULT_PORTS = {'http': 80, 'https': 443} +# Ensure that ', ' is used to preserve previous delimiter behavior. +DEFAULT_ACCEPT_ENCODING = ", ".join( + re.split(r",\s*", make_headers(accept_encoding=True)["accept-encoding"]) +) + if sys.platform == 'win32': # provide a proxy_bypass version on Windows without DNS lookups @@ -168,18 +175,24 @@ def super_len(o): def get_netrc_auth(url, raise_errors=False): """Returns the Requests tuple auth for a given url from netrc.""" + netrc_file = os.environ.get('NETRC') + if netrc_file is not None: + netrc_locations = (netrc_file,) + else: + netrc_locations = ('~/{}'.format(f) for f in NETRC_FILES) + try: from netrc import netrc, NetrcParseError netrc_path = None - for f in NETRC_FILES: + for f in netrc_locations: try: - loc = os.path.expanduser('~/{}'.format(f)) + loc = os.path.expanduser(f) except KeyError: # os.path.expanduser can fail when $HOME is undefined and # getpwuid fails. See https://bugs.python.org/issue20164 & - # https://github.com/requests/requests/issues/1846 + # https://github.com/psf/requests/issues/1846 return if os.path.exists(loc): @@ -211,7 +224,7 @@ def get_netrc_auth(url, raise_errors=False): if raise_errors: raise - # AppEngine hackiness. + # App Engine hackiness. except (ImportError, AttributeError): pass @@ -249,13 +262,28 @@ def extract_zipped_paths(path): # we have a valid zip archive and a valid member of that archive tmp = tempfile.gettempdir() - extracted_path = os.path.join(tmp, *member.split('/')) + extracted_path = os.path.join(tmp, member.split('/')[-1]) if not os.path.exists(extracted_path): - extracted_path = zip_file.extract(member, path=tmp) - + # use read + write to avoid the creating nested folders, we only want the file, avoids mkdir racing condition + with atomic_open(extracted_path) as file_handler: + file_handler.write(zip_file.read(member)) return extracted_path +@contextlib.contextmanager +def atomic_open(filename): + """Write a file to the disk in an atomic fashion""" + replacer = os.rename if sys.version_info[0] == 2 else os.replace + tmp_descriptor, tmp_name = tempfile.mkstemp(dir=os.path.dirname(filename)) + try: + with os.fdopen(tmp_descriptor, 'wb') as tmp_handler: + yield tmp_handler + replacer(tmp_name, filename) + except BaseException: + os.remove(tmp_name) + raise + + def from_key_val_list(value): """Take an object and test to see if it can be represented as a dictionary. Unless it can not be represented as such, return an @@ -266,6 +294,8 @@ def from_key_val_list(value): >>> from_key_val_list([('key', 'val')]) OrderedDict([('key', 'val')]) >>> from_key_val_list('string') + Traceback (most recent call last): + ... ValueError: cannot encode objects that are not 2-tuples >>> from_key_val_list({'key': 'val'}) OrderedDict([('key', 'val')]) @@ -292,7 +322,9 @@ def to_key_val_list(value): >>> to_key_val_list({'key': 'val'}) [('key', 'val')] >>> to_key_val_list('string') - ValueError: cannot encode objects that are not 2-tuples. + Traceback (most recent call last): + ... + ValueError: cannot encode objects that are not 2-tuples :rtype: list """ @@ -492,6 +524,10 @@ def get_encoding_from_headers(headers): if 'text' in content_type: return 'ISO-8859-1' + if 'application/json' in content_type: + # Assume UTF-8 based on RFC 4627: https://www.ietf.org/rfc/rfc4627.txt since the charset was unset + return 'utf-8' + def stream_decode_response_unicode(iterator, r): """Stream decodes a iterator.""" @@ -805,7 +841,7 @@ def default_headers(): """ return CaseInsensitiveDict({ 'User-Agent': default_user_agent(), - 'Accept-Encoding': ', '.join(('gzip', 'deflate')), + 'Accept-Encoding': DEFAULT_ACCEPT_ENCODING, 'Accept': '*/*', 'Connection': 'keep-alive', }) diff --git a/pipenv/vendor/resolvelib/LICENSE b/pipenv/patched/notpip/_vendor/resolvelib/LICENSE similarity index 100% rename from pipenv/vendor/resolvelib/LICENSE rename to pipenv/patched/notpip/_vendor/resolvelib/LICENSE diff --git a/pipenv/patched/notpip/_vendor/resolvelib/__init__.py b/pipenv/patched/notpip/_vendor/resolvelib/__init__.py new file mode 100644 index 00000000..1bddc2fd --- /dev/null +++ b/pipenv/patched/notpip/_vendor/resolvelib/__init__.py @@ -0,0 +1,26 @@ +__all__ = [ + "__version__", + "AbstractProvider", + "AbstractResolver", + "BaseReporter", + "InconsistentCandidate", + "Resolver", + "RequirementsConflicted", + "ResolutionError", + "ResolutionImpossible", + "ResolutionTooDeep", +] + +__version__ = "0.7.1" + + +from .providers import AbstractProvider, AbstractResolver +from .reporters import BaseReporter +from .resolvers import ( + InconsistentCandidate, + RequirementsConflicted, + Resolver, + ResolutionError, + ResolutionImpossible, + ResolutionTooDeep, +) diff --git a/pipenv/vendor/passa/operations/__init__.py b/pipenv/patched/notpip/_vendor/resolvelib/compat/__init__.py similarity index 100% rename from pipenv/vendor/passa/operations/__init__.py rename to pipenv/patched/notpip/_vendor/resolvelib/compat/__init__.py diff --git a/pipenv/patched/notpip/_vendor/resolvelib/compat/collections_abc.py b/pipenv/patched/notpip/_vendor/resolvelib/compat/collections_abc.py new file mode 100644 index 00000000..1becc509 --- /dev/null +++ b/pipenv/patched/notpip/_vendor/resolvelib/compat/collections_abc.py @@ -0,0 +1,6 @@ +__all__ = ["Mapping", "Sequence"] + +try: + from collections.abc import Mapping, Sequence +except ImportError: + from collections import Mapping, Sequence diff --git a/pipenv/patched/notpip/_vendor/resolvelib/providers.py b/pipenv/patched/notpip/_vendor/resolvelib/providers.py new file mode 100644 index 00000000..4822d166 --- /dev/null +++ b/pipenv/patched/notpip/_vendor/resolvelib/providers.py @@ -0,0 +1,124 @@ +class AbstractProvider(object): + """Delegate class to provide requirement interface for the resolver.""" + + def identify(self, requirement_or_candidate): + """Given a requirement, return an identifier for it. + + This is used to identify a requirement, e.g. whether two requirements + should have their specifier parts merged. + """ + raise NotImplementedError + + def get_preference(self, identifier, resolutions, candidates, information): + """Produce a sort key for given requirement based on preference. + + The preference is defined as "I think this requirement should be + resolved first". The lower the return value is, the more preferred + this group of arguments is. + + :param identifier: An identifier as returned by ``identify()``. This + identifies the dependency matches of which should be returned. + :param resolutions: Mapping of candidates currently pinned by the + resolver. Each key is an identifier, and the value a candidate. + The candidate may conflict with requirements from ``information``. + :param candidates: Mapping of each dependency's possible candidates. + Each value is an iterator of candidates. + :param information: Mapping of requirement information of each package. + Each value is an iterator of *requirement information*. + + A *requirement information* instance is a named tuple with two members: + + * ``requirement`` specifies a requirement contributing to the current + list of candidates. + * ``parent`` specifies the candidate that provides (dependend on) the + requirement, or ``None`` to indicate a root requirement. + + The preference could depend on a various of issues, including (not + necessarily in this order): + + * Is this package pinned in the current resolution result? + * How relaxed is the requirement? Stricter ones should probably be + worked on first? (I don't know, actually.) + * How many possibilities are there to satisfy this requirement? Those + with few left should likely be worked on first, I guess? + * Are there any known conflicts for this requirement? We should + probably work on those with the most known conflicts. + + A sortable value should be returned (this will be used as the ``key`` + parameter of the built-in sorting function). The smaller the value is, + the more preferred this requirement is (i.e. the sorting function + is called with ``reverse=False``). + """ + raise NotImplementedError + + def find_matches(self, identifier, requirements, incompatibilities): + """Find all possible candidates that satisfy given constraints. + + :param identifier: An identifier as returned by ``identify()``. This + identifies the dependency matches of which should be returned. + :param requirements: A mapping of requirements that all returned + candidates must satisfy. Each key is an identifier, and the value + an iterator of requirements for that dependency. + :param incompatibilities: A mapping of known incompatibilities of + each dependency. Each key is an identifier, and the value an + iterator of incompatibilities known to the resolver. All + incompatibilities *must* be excluded from the return value. + + This should try to get candidates based on the requirements' types. + For VCS, local, and archive requirements, the one-and-only match is + returned, and for a "named" requirement, the index(es) should be + consulted to find concrete candidates for this requirement. + + The return value should produce candidates ordered by preference; the + most preferred candidate should come first. The return type may be one + of the following: + + * A callable that returns an iterator that yields candidates. + * An collection of candidates. + * An iterable of candidates. This will be consumed immediately into a + list of candidates. + """ + raise NotImplementedError + + def is_satisfied_by(self, requirement, candidate): + """Whether the given requirement can be satisfied by a candidate. + + The candidate is guarenteed to have been generated from the + requirement. + + A boolean should be returned to indicate whether ``candidate`` is a + viable solution to the requirement. + """ + raise NotImplementedError + + def get_dependencies(self, candidate): + """Get dependencies of a candidate. + + This should return a collection of requirements that `candidate` + specifies as its dependencies. + """ + raise NotImplementedError + + +class AbstractResolver(object): + """The thing that performs the actual resolution work.""" + + base_exception = Exception + + def __init__(self, provider, reporter): + self.provider = provider + self.reporter = reporter + + def resolve(self, requirements, **kwargs): + """Take a collection of constraints, spit out the resolution result. + + This returns a representation of the final resolution state, with one + guarenteed attribute ``mapping`` that contains resolved candidates as + values. The keys are their respective identifiers. + + :param requirements: A collection of constraints. + :param kwargs: Additional keyword arguments that subclasses may accept. + + :raises: ``self.base_exception`` or its subclass. + """ + raise NotImplementedError diff --git a/pipenv/patched/notpip/_vendor/resolvelib/reporters.py b/pipenv/patched/notpip/_vendor/resolvelib/reporters.py new file mode 100644 index 00000000..563489e1 --- /dev/null +++ b/pipenv/patched/notpip/_vendor/resolvelib/reporters.py @@ -0,0 +1,37 @@ +class BaseReporter(object): + """Delegate class to provider progress reporting for the resolver.""" + + def starting(self): + """Called before the resolution actually starts.""" + + def starting_round(self, index): + """Called before each round of resolution starts. + + The index is zero-based. + """ + + def ending_round(self, index, state): + """Called before each round of resolution ends. + + This is NOT called if the resolution ends at this round. Use `ending` + if you want to report finalization. The index is zero-based. + """ + + def ending(self, state): + """Called before the resolution ends successfully.""" + + def adding_requirement(self, requirement, parent): + """Called when adding a new requirement into the resolve criteria. + + :param requirement: The additional requirement to be applied to filter + the available candidaites. + :param parent: The candidate that requires ``requirement`` as a + dependency, or None if ``requirement`` is one of the root + requirements passed in from ``Resolver.resolve()``. + """ + + def backtracking(self, candidate): + """Called when rejecting a candidate during backtracking.""" + + def pinning(self, candidate): + """Called when adding a candidate to the potential solution.""" diff --git a/pipenv/patched/notpip/_vendor/resolvelib/resolvers.py b/pipenv/patched/notpip/_vendor/resolvelib/resolvers.py new file mode 100644 index 00000000..42484423 --- /dev/null +++ b/pipenv/patched/notpip/_vendor/resolvelib/resolvers.py @@ -0,0 +1,473 @@ +import collections +import operator + +from .providers import AbstractResolver +from .structs import DirectedGraph, IteratorMapping, build_iter_view + + +RequirementInformation = collections.namedtuple( + "RequirementInformation", ["requirement", "parent"] +) + + +class ResolverException(Exception): + """A base class for all exceptions raised by this module. + + Exceptions derived by this class should all be handled in this module. Any + bubbling pass the resolver should be treated as a bug. + """ + + +class RequirementsConflicted(ResolverException): + def __init__(self, criterion): + super(RequirementsConflicted, self).__init__(criterion) + self.criterion = criterion + + def __str__(self): + return "Requirements conflict: {}".format( + ", ".join(repr(r) for r in self.criterion.iter_requirement()), + ) + + +class InconsistentCandidate(ResolverException): + def __init__(self, candidate, criterion): + super(InconsistentCandidate, self).__init__(candidate, criterion) + self.candidate = candidate + self.criterion = criterion + + def __str__(self): + return "Provided candidate {!r} does not satisfy {}".format( + self.candidate, + ", ".join(repr(r) for r in self.criterion.iter_requirement()), + ) + + +class Criterion(object): + """Representation of possible resolution results of a package. + + This holds three attributes: + + * `information` is a collection of `RequirementInformation` pairs. + Each pair is a requirement contributing to this criterion, and the + candidate that provides the requirement. + * `incompatibilities` is a collection of all known not-to-work candidates + to exclude from consideration. + * `candidates` is a collection containing all possible candidates deducted + from the union of contributing requirements and known incompatibilities. + It should never be empty, except when the criterion is an attribute of a + raised `RequirementsConflicted` (in which case it is always empty). + + .. note:: + This class is intended to be externally immutable. **Do not** mutate + any of its attribute containers. + """ + + def __init__(self, candidates, information, incompatibilities): + self.candidates = candidates + self.information = information + self.incompatibilities = incompatibilities + + def __repr__(self): + requirements = ", ".join( + "({!r}, via={!r})".format(req, parent) + for req, parent in self.information + ) + return "Criterion({})".format(requirements) + + def iter_requirement(self): + return (i.requirement for i in self.information) + + def iter_parent(self): + return (i.parent for i in self.information) + + +class ResolutionError(ResolverException): + pass + + +class ResolutionImpossible(ResolutionError): + def __init__(self, causes): + super(ResolutionImpossible, self).__init__(causes) + # causes is a list of RequirementInformation objects + self.causes = causes + + +class ResolutionTooDeep(ResolutionError): + def __init__(self, round_count): + super(ResolutionTooDeep, self).__init__(round_count) + self.round_count = round_count + + +# Resolution state in a round. +State = collections.namedtuple("State", "mapping criteria") + + +class Resolution(object): + """Stateful resolution object. + + This is designed as a one-off object that holds information to kick start + the resolution process, and holds the results afterwards. + """ + + def __init__(self, provider, reporter): + self._p = provider + self._r = reporter + self._states = [] + + @property + def state(self): + try: + return self._states[-1] + except IndexError: + raise AttributeError("state") + + def _push_new_state(self): + """Push a new state into history. + + This new state will be used to hold resolution results of the next + coming round. + """ + base = self._states[-1] + state = State( + mapping=base.mapping.copy(), + criteria=base.criteria.copy(), + ) + self._states.append(state) + + def _add_to_criteria(self, criteria, requirement, parent): + self._r.adding_requirement(requirement=requirement, parent=parent) + + identifier = self._p.identify(requirement_or_candidate=requirement) + criterion = criteria.get(identifier) + if criterion: + incompatibilities = list(criterion.incompatibilities) + else: + incompatibilities = [] + + matches = self._p.find_matches( + identifier=identifier, + requirements=IteratorMapping( + criteria, + operator.methodcaller("iter_requirement"), + {identifier: [requirement]}, + ), + incompatibilities=IteratorMapping( + criteria, + operator.attrgetter("incompatibilities"), + {identifier: incompatibilities}, + ), + ) + + if criterion: + information = list(criterion.information) + information.append(RequirementInformation(requirement, parent)) + else: + information = [RequirementInformation(requirement, parent)] + + criterion = Criterion( + candidates=build_iter_view(matches), + information=information, + incompatibilities=incompatibilities, + ) + if not criterion.candidates: + raise RequirementsConflicted(criterion) + criteria[identifier] = criterion + + def _get_preference(self, name): + return self._p.get_preference( + identifier=name, + resolutions=self.state.mapping, + candidates=IteratorMapping( + self.state.criteria, + operator.attrgetter("candidates"), + ), + information=IteratorMapping( + self.state.criteria, + operator.attrgetter("information"), + ), + ) + + def _is_current_pin_satisfying(self, name, criterion): + try: + current_pin = self.state.mapping[name] + except KeyError: + return False + return all( + self._p.is_satisfied_by(requirement=r, candidate=current_pin) + for r in criterion.iter_requirement() + ) + + def _get_updated_criteria(self, candidate): + criteria = self.state.criteria.copy() + for requirement in self._p.get_dependencies(candidate=candidate): + self._add_to_criteria(criteria, requirement, parent=candidate) + return criteria + + def _attempt_to_pin_criterion(self, name): + criterion = self.state.criteria[name] + + causes = [] + for candidate in criterion.candidates: + try: + criteria = self._get_updated_criteria(candidate) + except RequirementsConflicted as e: + causes.append(e.criterion) + continue + + # Check the newly-pinned candidate actually works. This should + # always pass under normal circumstances, but in the case of a + # faulty provider, we will raise an error to notify the implementer + # to fix find_matches() and/or is_satisfied_by(). + satisfied = all( + self._p.is_satisfied_by(requirement=r, candidate=candidate) + for r in criterion.iter_requirement() + ) + if not satisfied: + raise InconsistentCandidate(candidate, criterion) + + self._r.pinning(candidate=candidate) + self.state.criteria.update(criteria) + + # Put newly-pinned candidate at the end. This is essential because + # backtracking looks at this mapping to get the last pin. + self.state.mapping.pop(name, None) + self.state.mapping[name] = candidate + + return [] + + # All candidates tried, nothing works. This criterion is a dead + # end, signal for backtracking. + return causes + + def _backtrack(self): + """Perform backtracking. + + When we enter here, the stack is like this:: + + [ state Z ] + [ state Y ] + [ state X ] + .... earlier states are irrelevant. + + 1. No pins worked for Z, so it does not have a pin. + 2. We want to reset state Y to unpinned, and pin another candidate. + 3. State X holds what state Y was before the pin, but does not + have the incompatibility information gathered in state Y. + + Each iteration of the loop will: + + 1. Discard Z. + 2. Discard Y but remember its incompatibility information gathered + previously, and the failure we're dealing with right now. + 3. Push a new state Y' based on X, and apply the incompatibility + information from Y to Y'. + 4a. If this causes Y' to conflict, we need to backtrack again. Make Y' + the new Z and go back to step 2. + 4b. If the incompatibilities apply cleanly, end backtracking. + """ + while len(self._states) >= 3: + # Remove the state that triggered backtracking. + del self._states[-1] + + # Retrieve the last candidate pin and known incompatibilities. + broken_state = self._states.pop() + name, candidate = broken_state.mapping.popitem() + incompatibilities_from_broken = [ + (k, list(v.incompatibilities)) + for k, v in broken_state.criteria.items() + ] + + # Also mark the newly known incompatibility. + incompatibilities_from_broken.append((name, [candidate])) + + self._r.backtracking(candidate=candidate) + + # Create a new state from the last known-to-work one, and apply + # the previously gathered incompatibility information. + def _patch_criteria(): + for k, incompatibilities in incompatibilities_from_broken: + if not incompatibilities: + continue + try: + criterion = self.state.criteria[k] + except KeyError: + continue + matches = self._p.find_matches( + identifier=k, + requirements=IteratorMapping( + self.state.criteria, + operator.methodcaller("iter_requirement"), + ), + incompatibilities=IteratorMapping( + self.state.criteria, + operator.attrgetter("incompatibilities"), + {k: incompatibilities}, + ), + ) + candidates = build_iter_view(matches) + if not candidates: + return False + incompatibilities.extend(criterion.incompatibilities) + self.state.criteria[k] = Criterion( + candidates=candidates, + information=list(criterion.information), + incompatibilities=incompatibilities, + ) + return True + + self._push_new_state() + success = _patch_criteria() + + # It works! Let's work on this new state. + if success: + return True + + # State does not work after applying known incompatibilities. + # Try the still previous state. + + # No way to backtrack anymore. + return False + + def resolve(self, requirements, max_rounds): + if self._states: + raise RuntimeError("already resolved") + + self._r.starting() + + # Initialize the root state. + self._states = [State(mapping=collections.OrderedDict(), criteria={})] + for r in requirements: + try: + self._add_to_criteria(self.state.criteria, r, parent=None) + except RequirementsConflicted as e: + raise ResolutionImpossible(e.criterion.information) + + # The root state is saved as a sentinel so the first ever pin can have + # something to backtrack to if it fails. The root state is basically + # pinning the virtual "root" package in the graph. + self._push_new_state() + + for round_index in range(max_rounds): + self._r.starting_round(index=round_index) + + unsatisfied_names = [ + key + for key, criterion in self.state.criteria.items() + if not self._is_current_pin_satisfying(key, criterion) + ] + + # All criteria are accounted for. Nothing more to pin, we are done! + if not unsatisfied_names: + self._r.ending(state=self.state) + return self.state + + # Choose the most preferred unpinned criterion to try. + name = min(unsatisfied_names, key=self._get_preference) + failure_causes = self._attempt_to_pin_criterion(name) + + if failure_causes: + # Backtrack if pinning fails. The backtrack process puts us in + # an unpinned state, so we can work on it in the next round. + success = self._backtrack() + + # Dead ends everywhere. Give up. + if not success: + causes = [i for c in failure_causes for i in c.information] + raise ResolutionImpossible(causes) + else: + # Pinning was successful. Push a new state to do another pin. + self._push_new_state() + + self._r.ending_round(index=round_index, state=self.state) + + raise ResolutionTooDeep(max_rounds) + + +def _has_route_to_root(criteria, key, all_keys, connected): + if key in connected: + return True + if key not in criteria: + return False + for p in criteria[key].iter_parent(): + try: + pkey = all_keys[id(p)] + except KeyError: + continue + if pkey in connected: + connected.add(key) + return True + if _has_route_to_root(criteria, pkey, all_keys, connected): + connected.add(key) + return True + return False + + +Result = collections.namedtuple("Result", "mapping graph criteria") + + +def _build_result(state): + mapping = state.mapping + all_keys = {id(v): k for k, v in mapping.items()} + all_keys[id(None)] = None + + graph = DirectedGraph() + graph.add(None) # Sentinel as root dependencies' parent. + + connected = {None} + for key, criterion in state.criteria.items(): + if not _has_route_to_root(state.criteria, key, all_keys, connected): + continue + if key not in graph: + graph.add(key) + for p in criterion.iter_parent(): + try: + pkey = all_keys[id(p)] + except KeyError: + continue + if pkey not in graph: + graph.add(pkey) + graph.connect(pkey, key) + + return Result( + mapping={k: v for k, v in mapping.items() if k in connected}, + graph=graph, + criteria=state.criteria, + ) + + +class Resolver(AbstractResolver): + """The thing that performs the actual resolution work.""" + + base_exception = ResolverException + + def resolve(self, requirements, max_rounds=100): + """Take a collection of constraints, spit out the resolution result. + + The return value is a representation to the final resolution result. It + is a tuple subclass with three public members: + + * `mapping`: A dict of resolved candidates. Each key is an identifier + of a requirement (as returned by the provider's `identify` method), + and the value is the resolved candidate. + * `graph`: A `DirectedGraph` instance representing the dependency tree. + The vertices are keys of `mapping`, and each edge represents *why* + a particular package is included. A special vertex `None` is + included to represent parents of user-supplied requirements. + * `criteria`: A dict of "criteria" that hold detailed information on + how edges in the graph are derived. Each key is an identifier of a + requirement, and the value is a `Criterion` instance. + + The following exceptions may be raised if a resolution cannot be found: + + * `ResolutionImpossible`: A resolution cannot be found for the given + combination of requirements. The `causes` attribute of the + exception is a list of (requirement, parent), giving the + requirements that could not be satisfied. + * `ResolutionTooDeep`: The dependency tree is too deeply nested and + the resolver gave up. This is usually caused by a circular + dependency, but you can try to resolve this by increasing the + `max_rounds` argument. + """ + resolution = Resolution(self.provider, self.reporter) + state = resolution.resolve(requirements, max_rounds=max_rounds) + return _build_result(state) diff --git a/pipenv/patched/notpip/_vendor/resolvelib/structs.py b/pipenv/patched/notpip/_vendor/resolvelib/structs.py new file mode 100644 index 00000000..93d1568b --- /dev/null +++ b/pipenv/patched/notpip/_vendor/resolvelib/structs.py @@ -0,0 +1,165 @@ +import itertools + +from .compat import collections_abc + + +class DirectedGraph(object): + """A graph structure with directed edges.""" + + def __init__(self): + self._vertices = set() + self._forwards = {} # <key> -> Set[<key>] + self._backwards = {} # <key> -> Set[<key>] + + def __iter__(self): + return iter(self._vertices) + + def __len__(self): + return len(self._vertices) + + def __contains__(self, key): + return key in self._vertices + + def copy(self): + """Return a shallow copy of this graph.""" + other = DirectedGraph() + other._vertices = set(self._vertices) + other._forwards = {k: set(v) for k, v in self._forwards.items()} + other._backwards = {k: set(v) for k, v in self._backwards.items()} + return other + + def add(self, key): + """Add a new vertex to the graph.""" + if key in self._vertices: + raise ValueError("vertex exists") + self._vertices.add(key) + self._forwards[key] = set() + self._backwards[key] = set() + + def remove(self, key): + """Remove a vertex from the graph, disconnecting all edges from/to it.""" + self._vertices.remove(key) + for f in self._forwards.pop(key): + self._backwards[f].remove(key) + for t in self._backwards.pop(key): + self._forwards[t].remove(key) + + def connected(self, f, t): + return f in self._backwards[t] and t in self._forwards[f] + + def connect(self, f, t): + """Connect two existing vertices. + + Nothing happens if the vertices are already connected. + """ + if t not in self._vertices: + raise KeyError(t) + self._forwards[f].add(t) + self._backwards[t].add(f) + + def iter_edges(self): + for f, children in self._forwards.items(): + for t in children: + yield f, t + + def iter_children(self, key): + return iter(self._forwards[key]) + + def iter_parents(self, key): + return iter(self._backwards[key]) + + +class IteratorMapping(collections_abc.Mapping): + def __init__(self, mapping, accessor, appends=None): + self._mapping = mapping + self._accessor = accessor + self._appends = appends or {} + + def __repr__(self): + return "IteratorMapping({!r}, {!r}, {!r})".format( + self._mapping, + self._accessor, + self._appends, + ) + + def __bool__(self): + return bool(self._mapping or self._appends) + + __nonzero__ = __bool__ # XXX: Python 2. + + def __contains__(self, key): + return key in self._mapping or key in self._appends + + def __getitem__(self, k): + try: + v = self._mapping[k] + except KeyError: + return iter(self._appends[k]) + return itertools.chain(self._accessor(v), self._appends.get(k, ())) + + def __iter__(self): + more = (k for k in self._appends if k not in self._mapping) + return itertools.chain(self._mapping, more) + + def __len__(self): + more = sum(1 for k in self._appends if k not in self._mapping) + return len(self._mapping) + more + + +class _FactoryIterableView(object): + """Wrap an iterator factory returned by `find_matches()`. + + Calling `iter()` on this class would invoke the underlying iterator + factory, making it a "collection with ordering" that can be iterated + through multiple times, but lacks random access methods presented in + built-in Python sequence types. + """ + + def __init__(self, factory): + self._factory = factory + + def __repr__(self): + return "{}({})".format(type(self).__name__, list(self._factory())) + + def __bool__(self): + try: + next(self._factory()) + except StopIteration: + return False + return True + + __nonzero__ = __bool__ # XXX: Python 2. + + def __iter__(self): + return self._factory() + + +class _SequenceIterableView(object): + """Wrap an iterable returned by find_matches(). + + This is essentially just a proxy to the underlying sequence that provides + the same interface as `_FactoryIterableView`. + """ + + def __init__(self, sequence): + self._sequence = sequence + + def __repr__(self): + return "{}({})".format(type(self).__name__, self._sequence) + + def __bool__(self): + return bool(self._sequence) + + __nonzero__ = __bool__ # XXX: Python 2. + + def __iter__(self): + return iter(self._sequence) + + +def build_iter_view(matches): + """Build an iterable view from the value returned by `find_matches()`.""" + if callable(matches): + return _FactoryIterableView(matches) + if not isinstance(matches, collections_abc.Sequence): + matches = list(matches) + return _SequenceIterableView(matches) diff --git a/pipenv/patched/notpip/_vendor/retrying.LICENSE b/pipenv/patched/notpip/_vendor/retrying.LICENSE deleted file mode 100644 index 7a4a3ea2..00000000 --- a/pipenv/patched/notpip/_vendor/retrying.LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file diff --git a/pipenv/patched/notpip/_vendor/retrying.py b/pipenv/patched/notpip/_vendor/retrying.py deleted file mode 100644 index 3c12f714..00000000 --- a/pipenv/patched/notpip/_vendor/retrying.py +++ /dev/null @@ -1,267 +0,0 @@ -## Copyright 2013-2014 Ray Holder -## -## Licensed under the Apache License, Version 2.0 (the "License"); -## you may not use this file except in compliance with the License. -## You may obtain a copy of the License at -## -## http://www.apache.org/licenses/LICENSE-2.0 -## -## Unless required by applicable law or agreed to in writing, software -## distributed under the License is distributed on an "AS IS" BASIS, -## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -## See the License for the specific language governing permissions and -## limitations under the License. - -import random -from pipenv.patched.notpip._vendor import six -import sys -import time -import traceback - - -# sys.maxint / 2, since Python 3.2 doesn't have a sys.maxint... -MAX_WAIT = 1073741823 - - -def retry(*dargs, **dkw): - """ - Decorator function that instantiates the Retrying object - @param *dargs: positional arguments passed to Retrying object - @param **dkw: keyword arguments passed to the Retrying object - """ - # support both @retry and @retry() as valid syntax - if len(dargs) == 1 and callable(dargs[0]): - def wrap_simple(f): - - @six.wraps(f) - def wrapped_f(*args, **kw): - return Retrying().call(f, *args, **kw) - - return wrapped_f - - return wrap_simple(dargs[0]) - - else: - def wrap(f): - - @six.wraps(f) - def wrapped_f(*args, **kw): - return Retrying(*dargs, **dkw).call(f, *args, **kw) - - return wrapped_f - - return wrap - - -class Retrying(object): - - def __init__(self, - stop=None, wait=None, - stop_max_attempt_number=None, - stop_max_delay=None, - wait_fixed=None, - wait_random_min=None, wait_random_max=None, - wait_incrementing_start=None, wait_incrementing_increment=None, - wait_exponential_multiplier=None, wait_exponential_max=None, - retry_on_exception=None, - retry_on_result=None, - wrap_exception=False, - stop_func=None, - wait_func=None, - wait_jitter_max=None): - - self._stop_max_attempt_number = 5 if stop_max_attempt_number is None else stop_max_attempt_number - self._stop_max_delay = 100 if stop_max_delay is None else stop_max_delay - self._wait_fixed = 1000 if wait_fixed is None else wait_fixed - self._wait_random_min = 0 if wait_random_min is None else wait_random_min - self._wait_random_max = 1000 if wait_random_max is None else wait_random_max - self._wait_incrementing_start = 0 if wait_incrementing_start is None else wait_incrementing_start - self._wait_incrementing_increment = 100 if wait_incrementing_increment is None else wait_incrementing_increment - self._wait_exponential_multiplier = 1 if wait_exponential_multiplier is None else wait_exponential_multiplier - self._wait_exponential_max = MAX_WAIT if wait_exponential_max is None else wait_exponential_max - self._wait_jitter_max = 0 if wait_jitter_max is None else wait_jitter_max - - # TODO add chaining of stop behaviors - # stop behavior - stop_funcs = [] - if stop_max_attempt_number is not None: - stop_funcs.append(self.stop_after_attempt) - - if stop_max_delay is not None: - stop_funcs.append(self.stop_after_delay) - - if stop_func is not None: - self.stop = stop_func - - elif stop is None: - self.stop = lambda attempts, delay: any(f(attempts, delay) for f in stop_funcs) - - else: - self.stop = getattr(self, stop) - - # TODO add chaining of wait behaviors - # wait behavior - wait_funcs = [lambda *args, **kwargs: 0] - if wait_fixed is not None: - wait_funcs.append(self.fixed_sleep) - - if wait_random_min is not None or wait_random_max is not None: - wait_funcs.append(self.random_sleep) - - if wait_incrementing_start is not None or wait_incrementing_increment is not None: - wait_funcs.append(self.incrementing_sleep) - - if wait_exponential_multiplier is not None or wait_exponential_max is not None: - wait_funcs.append(self.exponential_sleep) - - if wait_func is not None: - self.wait = wait_func - - elif wait is None: - self.wait = lambda attempts, delay: max(f(attempts, delay) for f in wait_funcs) - - else: - self.wait = getattr(self, wait) - - # retry on exception filter - if retry_on_exception is None: - self._retry_on_exception = self.always_reject - else: - self._retry_on_exception = retry_on_exception - - # TODO simplify retrying by Exception types - # retry on result filter - if retry_on_result is None: - self._retry_on_result = self.never_reject - else: - self._retry_on_result = retry_on_result - - self._wrap_exception = wrap_exception - - def stop_after_attempt(self, previous_attempt_number, delay_since_first_attempt_ms): - """Stop after the previous attempt >= stop_max_attempt_number.""" - return previous_attempt_number >= self._stop_max_attempt_number - - def stop_after_delay(self, previous_attempt_number, delay_since_first_attempt_ms): - """Stop after the time from the first attempt >= stop_max_delay.""" - return delay_since_first_attempt_ms >= self._stop_max_delay - - def no_sleep(self, previous_attempt_number, delay_since_first_attempt_ms): - """Don't sleep at all before retrying.""" - return 0 - - def fixed_sleep(self, previous_attempt_number, delay_since_first_attempt_ms): - """Sleep a fixed amount of time between each retry.""" - return self._wait_fixed - - def random_sleep(self, previous_attempt_number, delay_since_first_attempt_ms): - """Sleep a random amount of time between wait_random_min and wait_random_max""" - return random.randint(self._wait_random_min, self._wait_random_max) - - def incrementing_sleep(self, previous_attempt_number, delay_since_first_attempt_ms): - """ - Sleep an incremental amount of time after each attempt, starting at - wait_incrementing_start and incrementing by wait_incrementing_increment - """ - result = self._wait_incrementing_start + (self._wait_incrementing_increment * (previous_attempt_number - 1)) - if result < 0: - result = 0 - return result - - def exponential_sleep(self, previous_attempt_number, delay_since_first_attempt_ms): - exp = 2 ** previous_attempt_number - result = self._wait_exponential_multiplier * exp - if result > self._wait_exponential_max: - result = self._wait_exponential_max - if result < 0: - result = 0 - return result - - def never_reject(self, result): - return False - - def always_reject(self, result): - return True - - def should_reject(self, attempt): - reject = False - if attempt.has_exception: - reject |= self._retry_on_exception(attempt.value[1]) - else: - reject |= self._retry_on_result(attempt.value) - - return reject - - def call(self, fn, *args, **kwargs): - start_time = int(round(time.time() * 1000)) - attempt_number = 1 - while True: - try: - attempt = Attempt(fn(*args, **kwargs), attempt_number, False) - except: - tb = sys.exc_info() - attempt = Attempt(tb, attempt_number, True) - - if not self.should_reject(attempt): - return attempt.get(self._wrap_exception) - - delay_since_first_attempt_ms = int(round(time.time() * 1000)) - start_time - if self.stop(attempt_number, delay_since_first_attempt_ms): - if not self._wrap_exception and attempt.has_exception: - # get() on an attempt with an exception should cause it to be raised, but raise just in case - raise attempt.get() - else: - raise RetryError(attempt) - else: - sleep = self.wait(attempt_number, delay_since_first_attempt_ms) - if self._wait_jitter_max: - jitter = random.random() * self._wait_jitter_max - sleep = sleep + max(0, jitter) - time.sleep(sleep / 1000.0) - - attempt_number += 1 - - -class Attempt(object): - """ - An Attempt encapsulates a call to a target function that may end as a - normal return value from the function or an Exception depending on what - occurred during the execution. - """ - - def __init__(self, value, attempt_number, has_exception): - self.value = value - self.attempt_number = attempt_number - self.has_exception = has_exception - - def get(self, wrap_exception=False): - """ - Return the return value of this Attempt instance or raise an Exception. - If wrap_exception is true, this Attempt is wrapped inside of a - RetryError before being raised. - """ - if self.has_exception: - if wrap_exception: - raise RetryError(self) - else: - six.reraise(self.value[0], self.value[1], self.value[2]) - else: - return self.value - - def __repr__(self): - if self.has_exception: - return "Attempts: {0}, Error:\n{1}".format(self.attempt_number, "".join(traceback.format_tb(self.value[2]))) - else: - return "Attempts: {0}, Value: {1}".format(self.attempt_number, self.value) - - -class RetryError(Exception): - """ - A RetryError encapsulates the last Attempt instance right before giving up. - """ - - def __init__(self, last_attempt): - self.last_attempt = last_attempt - - def __str__(self): - return "RetryError[{0}]".format(self.last_attempt) diff --git a/pipenv/patched/notpip/_vendor/six.LICENSE b/pipenv/patched/notpip/_vendor/six.LICENSE index 365d1074..de663311 100644 --- a/pipenv/patched/notpip/_vendor/six.LICENSE +++ b/pipenv/patched/notpip/_vendor/six.LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2010-2018 Benjamin Peterson +Copyright (c) 2010-2020 Benjamin Peterson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/pipenv/patched/notpip/_vendor/six.py b/pipenv/patched/notpip/_vendor/six.py index 89b2188f..4e15675d 100644 --- a/pipenv/patched/notpip/_vendor/six.py +++ b/pipenv/patched/notpip/_vendor/six.py @@ -1,4 +1,4 @@ -# Copyright (c) 2010-2018 Benjamin Peterson +# Copyright (c) 2010-2020 Benjamin Peterson # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -29,7 +29,7 @@ import sys import types __author__ = "Benjamin Peterson <benjamin@python.org>" -__version__ = "1.12.0" +__version__ = "1.16.0" # Useful for very coarse version differentiation. @@ -71,6 +71,11 @@ else: MAXSIZE = int((1 << 63) - 1) del X +if PY34: + from importlib.util import spec_from_loader +else: + spec_from_loader = None + def _add_doc(func, doc): """Add documentation to a function.""" @@ -186,6 +191,11 @@ class _SixMetaPathImporter(object): return self return None + def find_spec(self, fullname, path, target=None): + if fullname in self.known_modules: + return spec_from_loader(fullname, self) + return None + def __get_module(self, fullname): try: return self.known_modules[fullname] @@ -223,6 +233,12 @@ class _SixMetaPathImporter(object): return None get_source = get_code # same as get_code + def create_module(self, spec): + return self.load_module(spec.name) + + def exec_module(self, module): + pass + _importer = _SixMetaPathImporter(__name__) @@ -255,9 +271,11 @@ _moved_attributes = [ MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"), MovedModule("builtins", "__builtin__"), MovedModule("configparser", "ConfigParser"), + MovedModule("collections_abc", "collections", "collections.abc" if sys.version_info >= (3, 3) else "collections"), MovedModule("copyreg", "copy_reg"), MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), - MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"), + MovedModule("dbm_ndbm", "dbm", "dbm.ndbm"), + MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread" if sys.version_info < (3, 9) else "_thread"), MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), MovedModule("http_cookies", "Cookie", "http.cookies"), MovedModule("html_entities", "htmlentitydefs", "html.entities"), @@ -637,13 +655,16 @@ if PY3: import io StringIO = io.StringIO BytesIO = io.BytesIO + del io _assertCountEqual = "assertCountEqual" if sys.version_info[1] <= 1: _assertRaisesRegex = "assertRaisesRegexp" _assertRegex = "assertRegexpMatches" + _assertNotRegex = "assertNotRegexpMatches" else: _assertRaisesRegex = "assertRaisesRegex" _assertRegex = "assertRegex" + _assertNotRegex = "assertNotRegex" else: def b(s): return s @@ -665,6 +686,7 @@ else: _assertCountEqual = "assertItemsEqual" _assertRaisesRegex = "assertRaisesRegexp" _assertRegex = "assertRegexpMatches" + _assertNotRegex = "assertNotRegexpMatches" _add_doc(b, """Byte literal""") _add_doc(u, """Text literal""") @@ -681,6 +703,10 @@ def assertRegex(self, *args, **kwargs): return getattr(self, _assertRegex)(*args, **kwargs) +def assertNotRegex(self, *args, **kwargs): + return getattr(self, _assertNotRegex)(*args, **kwargs) + + if PY3: exec_ = getattr(moves.builtins, "exec") @@ -716,16 +742,7 @@ else: """) -if sys.version_info[:2] == (3, 2): - exec_("""def raise_from(value, from_value): - try: - if from_value is None: - raise value - raise value from from_value - finally: - value = None -""") -elif sys.version_info[:2] > (3, 2): +if sys.version_info[:2] > (3,): exec_("""def raise_from(value, from_value): try: raise value from from_value @@ -805,13 +822,33 @@ if sys.version_info[:2] < (3, 3): _add_doc(reraise, """Reraise an exception.""") if sys.version_info[0:2] < (3, 4): + # This does exactly the same what the :func:`py3:functools.update_wrapper` + # function does on Python versions after 3.2. It sets the ``__wrapped__`` + # attribute on ``wrapper`` object and it doesn't raise an error if any of + # the attributes mentioned in ``assigned`` and ``updated`` are missing on + # ``wrapped`` object. + def _update_wrapper(wrapper, wrapped, + assigned=functools.WRAPPER_ASSIGNMENTS, + updated=functools.WRAPPER_UPDATES): + for attr in assigned: + try: + value = getattr(wrapped, attr) + except AttributeError: + continue + else: + setattr(wrapper, attr, value) + for attr in updated: + getattr(wrapper, attr).update(getattr(wrapped, attr, {})) + wrapper.__wrapped__ = wrapped + return wrapper + _update_wrapper.__doc__ = functools.update_wrapper.__doc__ + def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, updated=functools.WRAPPER_UPDATES): - def wrapper(f): - f = functools.wraps(wrapped, assigned, updated)(f) - f.__wrapped__ = wrapped - return f - return wrapper + return functools.partial(_update_wrapper, wrapped=wrapped, + assigned=assigned, updated=updated) + wraps.__doc__ = functools.wraps.__doc__ + else: wraps = functools.wraps @@ -824,7 +861,15 @@ def with_metaclass(meta, *bases): class metaclass(type): def __new__(cls, name, this_bases, d): - return meta(name, bases, d) + if sys.version_info[:2] >= (3, 7): + # This version introduced PEP 560 that requires a bit + # of extra care (we mimic what is done by __build_class__). + resolved_bases = types.resolve_bases(bases) + if resolved_bases is not bases: + d['__orig_bases__'] = bases + else: + resolved_bases = bases + return meta(name, resolved_bases, d) @classmethod def __prepare__(cls, name, this_bases): @@ -861,12 +906,11 @@ def ensure_binary(s, encoding='utf-8', errors='strict'): - `str` -> encoded to `bytes` - `bytes` -> `bytes` """ + if isinstance(s, binary_type): + return s if isinstance(s, text_type): return s.encode(encoding, errors) - elif isinstance(s, binary_type): - return s - else: - raise TypeError("not expecting type '%s'" % type(s)) + raise TypeError("not expecting type '%s'" % type(s)) def ensure_str(s, encoding='utf-8', errors='strict'): @@ -880,12 +924,15 @@ def ensure_str(s, encoding='utf-8', errors='strict'): - `str` -> `str` - `bytes` -> decoded to `str` """ - if not isinstance(s, (text_type, binary_type)): - raise TypeError("not expecting type '%s'" % type(s)) + # Optimization: Fast return for the common case. + if type(s) is str: + return s if PY2 and isinstance(s, text_type): - s = s.encode(encoding, errors) + return s.encode(encoding, errors) elif PY3 and isinstance(s, binary_type): - s = s.decode(encoding, errors) + return s.decode(encoding, errors) + elif not isinstance(s, (text_type, binary_type)): + raise TypeError("not expecting type '%s'" % type(s)) return s @@ -908,10 +955,9 @@ def ensure_text(s, encoding='utf-8', errors='strict'): raise TypeError("not expecting type '%s'" % type(s)) - def python_2_unicode_compatible(klass): """ - A decorator that defines __unicode__ and __str__ methods under Python 2. + A class decorator that defines __unicode__ and __str__ methods under Python 2. Under Python 3 it does nothing. To support Python 2 and 3 with a single code base, define a __str__ method diff --git a/pipenv/patched/notpip/_vendor/tenacity/__init__.py b/pipenv/patched/notpip/_vendor/tenacity/__init__.py new file mode 100644 index 00000000..ad33e871 --- /dev/null +++ b/pipenv/patched/notpip/_vendor/tenacity/__init__.py @@ -0,0 +1,517 @@ +# Copyright 2016-2018 Julien Danjou +# Copyright 2017 Elisey Zanko +# Copyright 2016 Étienne Bersac +# Copyright 2016 Joshua Harlow +# Copyright 2013-2014 Ray Holder +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import functools +import sys +import threading +import time +import typing as t +import warnings +from abc import ABC, abstractmethod +from concurrent import futures +from inspect import iscoroutinefunction + +# Import all built-in retry strategies for easier usage. +from .retry import retry_base # noqa +from .retry import retry_all # noqa +from .retry import retry_always # noqa +from .retry import retry_any # noqa +from .retry import retry_if_exception # noqa +from .retry import retry_if_exception_type # noqa +from .retry import retry_if_not_exception_type # noqa +from .retry import retry_if_not_result # noqa +from .retry import retry_if_result # noqa +from .retry import retry_never # noqa +from .retry import retry_unless_exception_type # noqa +from .retry import retry_if_exception_message # noqa +from .retry import retry_if_not_exception_message # noqa + +# Import all nap strategies for easier usage. +from .nap import sleep # noqa +from .nap import sleep_using_event # noqa + +# Import all built-in stop strategies for easier usage. +from .stop import stop_after_attempt # noqa +from .stop import stop_after_delay # noqa +from .stop import stop_all # noqa +from .stop import stop_any # noqa +from .stop import stop_never # noqa +from .stop import stop_when_event_set # noqa + +# Import all built-in wait strategies for easier usage. +from .wait import wait_chain # noqa +from .wait import wait_combine # noqa +from .wait import wait_exponential # noqa +from .wait import wait_fixed # noqa +from .wait import wait_incrementing # noqa +from .wait import wait_none # noqa +from .wait import wait_random # noqa +from .wait import wait_random_exponential # noqa +from .wait import wait_random_exponential as wait_full_jitter # noqa + +# Import all built-in before strategies for easier usage. +from .before import before_log # noqa +from .before import before_nothing # noqa + +# Import all built-in after strategies for easier usage. +from .after import after_log # noqa +from .after import after_nothing # noqa + +# Import all built-in after strategies for easier usage. +from .before_sleep import before_sleep_log # noqa +from .before_sleep import before_sleep_nothing # noqa + +# Replace a conditional import with a hard-coded None so that pip does +# not attempt to use tornado even if it is present in the environment. +# If tornado is non-None, tenacity will attempt to execute some code +# that is sensitive to the version of tornado, which could break pip +# if an old version is found. +tornado = None # type: ignore + +if t.TYPE_CHECKING: + import types + + from .wait import wait_base + from .stop import stop_base + + +WrappedFn = t.TypeVar("WrappedFn", bound=t.Callable) +_RetValT = t.TypeVar("_RetValT") + + +@t.overload +def retry(fn: WrappedFn) -> WrappedFn: + pass + + +@t.overload +def retry(*dargs: t.Any, **dkw: t.Any) -> t.Callable[[WrappedFn], WrappedFn]: # noqa + pass + + +def retry(*dargs: t.Any, **dkw: t.Any) -> t.Union[WrappedFn, t.Callable[[WrappedFn], WrappedFn]]: # noqa + """Wrap a function with a new `Retrying` object. + + :param dargs: positional arguments passed to Retrying object + :param dkw: keyword arguments passed to the Retrying object + """ + # support both @retry and @retry() as valid syntax + if len(dargs) == 1 and callable(dargs[0]): + return retry()(dargs[0]) + else: + + def wrap(f: WrappedFn) -> WrappedFn: + if isinstance(f, retry_base): + warnings.warn( + f"Got retry_base instance ({f.__class__.__name__}) as callable argument, " + f"this will probably hang indefinitely (did you mean retry={f.__class__.__name__}(...)?)" + ) + if iscoroutinefunction(f): + r: "BaseRetrying" = AsyncRetrying(*dargs, **dkw) + elif tornado and hasattr(tornado.gen, "is_coroutine_function") and tornado.gen.is_coroutine_function(f): + r = TornadoRetrying(*dargs, **dkw) + else: + r = Retrying(*dargs, **dkw) + + return r.wraps(f) + + return wrap + + +class TryAgain(Exception): + """Always retry the executed function when raised.""" + + +NO_RESULT = object() + + +class DoAttempt: + pass + + +class DoSleep(float): + pass + + +class BaseAction: + """Base class for representing actions to take by retry object. + + Concrete implementations must define: + - __init__: to initialize all necessary fields + - REPR_FIELDS: class variable specifying attributes to include in repr(self) + - NAME: for identification in retry object methods and callbacks + """ + + REPR_FIELDS: t.Sequence[str] = () + NAME: t.Optional[str] = None + + def __repr__(self) -> str: + state_str = ", ".join(f"{field}={getattr(self, field)!r}" for field in self.REPR_FIELDS) + return f"{self.__class__.__name__}({state_str})" + + def __str__(self) -> str: + return repr(self) + + +class RetryAction(BaseAction): + REPR_FIELDS = ("sleep",) + NAME = "retry" + + def __init__(self, sleep: t.SupportsFloat) -> None: + self.sleep = float(sleep) + + +_unset = object() + + +def _first_set(first: t.Union[t.Any, object], second: t.Any) -> t.Any: + return second if first is _unset else first + + +class RetryError(Exception): + """Encapsulates the last attempt instance right before giving up.""" + + def __init__(self, last_attempt: "Future") -> None: + self.last_attempt = last_attempt + super().__init__(last_attempt) + + def reraise(self) -> "t.NoReturn": + if self.last_attempt.failed: + raise self.last_attempt.result() + raise self + + def __str__(self) -> str: + return f"{self.__class__.__name__}[{self.last_attempt}]" + + +class AttemptManager: + """Manage attempt context.""" + + def __init__(self, retry_state: "RetryCallState"): + self.retry_state = retry_state + + def __enter__(self) -> None: + pass + + def __exit__( + self, + exc_type: t.Optional[t.Type[BaseException]], + exc_value: t.Optional[BaseException], + traceback: t.Optional["types.TracebackType"], + ) -> t.Optional[bool]: + if isinstance(exc_value, BaseException): + self.retry_state.set_exception((exc_type, exc_value, traceback)) + return True # Swallow exception. + else: + # We don't have the result, actually. + self.retry_state.set_result(None) + return None + + +class BaseRetrying(ABC): + def __init__( + self, + sleep: t.Callable[[t.Union[int, float]], None] = sleep, + stop: "stop_base" = stop_never, + wait: "wait_base" = wait_none(), + retry: retry_base = retry_if_exception_type(), + before: t.Callable[["RetryCallState"], None] = before_nothing, + after: t.Callable[["RetryCallState"], None] = after_nothing, + before_sleep: t.Optional[t.Callable[["RetryCallState"], None]] = None, + reraise: bool = False, + retry_error_cls: t.Type[RetryError] = RetryError, + retry_error_callback: t.Optional[t.Callable[["RetryCallState"], t.Any]] = None, + ): + self.sleep = sleep + self.stop = stop + self.wait = wait + self.retry = retry + self.before = before + self.after = after + self.before_sleep = before_sleep + self.reraise = reraise + self._local = threading.local() + self.retry_error_cls = retry_error_cls + self.retry_error_callback = retry_error_callback + + def copy( + self, + sleep: t.Union[t.Callable[[t.Union[int, float]], None], object] = _unset, + stop: t.Union["stop_base", object] = _unset, + wait: t.Union["wait_base", object] = _unset, + retry: t.Union[retry_base, object] = _unset, + before: t.Union[t.Callable[["RetryCallState"], None], object] = _unset, + after: t.Union[t.Callable[["RetryCallState"], None], object] = _unset, + before_sleep: t.Union[t.Optional[t.Callable[["RetryCallState"], None]], object] = _unset, + reraise: t.Union[bool, object] = _unset, + retry_error_cls: t.Union[t.Type[RetryError], object] = _unset, + retry_error_callback: t.Union[t.Optional[t.Callable[["RetryCallState"], t.Any]], object] = _unset, + ) -> "BaseRetrying": + """Copy this object with some parameters changed if needed.""" + return self.__class__( + sleep=_first_set(sleep, self.sleep), + stop=_first_set(stop, self.stop), + wait=_first_set(wait, self.wait), + retry=_first_set(retry, self.retry), + before=_first_set(before, self.before), + after=_first_set(after, self.after), + before_sleep=_first_set(before_sleep, self.before_sleep), + reraise=_first_set(reraise, self.reraise), + retry_error_cls=_first_set(retry_error_cls, self.retry_error_cls), + retry_error_callback=_first_set(retry_error_callback, self.retry_error_callback), + ) + + def __repr__(self) -> str: + return ( + f"<{self.__class__.__name__} object at 0x{id(self):x} (" + f"stop={self.stop}, " + f"wait={self.wait}, " + f"sleep={self.sleep}, " + f"retry={self.retry}, " + f"before={self.before}, " + f"after={self.after})>" + ) + + @property + def statistics(self) -> t.Dict[str, t.Any]: + """Return a dictionary of runtime statistics. + + This dictionary will be empty when the controller has never been + ran. When it is running or has ran previously it should have (but + may not) have useful and/or informational keys and values when + running is underway and/or completed. + + .. warning:: The keys in this dictionary **should** be some what + stable (not changing), but there existence **may** + change between major releases as new statistics are + gathered or removed so before accessing keys ensure that + they actually exist and handle when they do not. + + .. note:: The values in this dictionary are local to the thread + running call (so if multiple threads share the same retrying + object - either directly or indirectly) they will each have + there own view of statistics they have collected (in the + future we may provide a way to aggregate the various + statistics from each thread). + """ + try: + return self._local.statistics + except AttributeError: + self._local.statistics = {} + return self._local.statistics + + def wraps(self, f: WrappedFn) -> WrappedFn: + """Wrap a function for retrying. + + :param f: A function to wraps for retrying. + """ + + @functools.wraps(f) + def wrapped_f(*args: t.Any, **kw: t.Any) -> t.Any: + return self(f, *args, **kw) + + def retry_with(*args: t.Any, **kwargs: t.Any) -> WrappedFn: + return self.copy(*args, **kwargs).wraps(f) + + wrapped_f.retry = self + wrapped_f.retry_with = retry_with + + return wrapped_f + + def begin(self) -> None: + self.statistics.clear() + self.statistics["start_time"] = time.monotonic() + self.statistics["attempt_number"] = 1 + self.statistics["idle_for"] = 0 + + def iter(self, retry_state: "RetryCallState") -> t.Union[DoAttempt, DoSleep, t.Any]: # noqa + fut = retry_state.outcome + if fut is None: + if self.before is not None: + self.before(retry_state) + return DoAttempt() + + is_explicit_retry = retry_state.outcome.failed and isinstance(retry_state.outcome.exception(), TryAgain) + if not (is_explicit_retry or self.retry(retry_state=retry_state)): + return fut.result() + + if self.after is not None: + self.after(retry_state) + + self.statistics["delay_since_first_attempt"] = retry_state.seconds_since_start + if self.stop(retry_state=retry_state): + if self.retry_error_callback: + return self.retry_error_callback(retry_state) + retry_exc = self.retry_error_cls(fut) + if self.reraise: + raise retry_exc.reraise() + raise retry_exc from fut.exception() + + if self.wait: + sleep = self.wait(retry_state=retry_state) + else: + sleep = 0.0 + retry_state.next_action = RetryAction(sleep) + retry_state.idle_for += sleep + self.statistics["idle_for"] += sleep + self.statistics["attempt_number"] += 1 + + if self.before_sleep is not None: + self.before_sleep(retry_state) + + return DoSleep(sleep) + + def __iter__(self) -> t.Generator[AttemptManager, None, None]: + self.begin() + + retry_state = RetryCallState(self, fn=None, args=(), kwargs={}) + while True: + do = self.iter(retry_state=retry_state) + if isinstance(do, DoAttempt): + yield AttemptManager(retry_state=retry_state) + elif isinstance(do, DoSleep): + retry_state.prepare_for_next_attempt() + self.sleep(do) + else: + break + + @abstractmethod + def __call__(self, fn: t.Callable[..., _RetValT], *args: t.Any, **kwargs: t.Any) -> _RetValT: + pass + + +class Retrying(BaseRetrying): + """Retrying controller.""" + + def __call__(self, fn: t.Callable[..., _RetValT], *args: t.Any, **kwargs: t.Any) -> _RetValT: + self.begin() + + retry_state = RetryCallState(retry_object=self, fn=fn, args=args, kwargs=kwargs) + while True: + do = self.iter(retry_state=retry_state) + if isinstance(do, DoAttempt): + try: + result = fn(*args, **kwargs) + except BaseException: # noqa: B902 + retry_state.set_exception(sys.exc_info()) + else: + retry_state.set_result(result) + elif isinstance(do, DoSleep): + retry_state.prepare_for_next_attempt() + self.sleep(do) + else: + return do + + +class Future(futures.Future): + """Encapsulates a (future or past) attempted call to a target function.""" + + def __init__(self, attempt_number: int) -> None: + super().__init__() + self.attempt_number = attempt_number + + @property + def failed(self) -> bool: + """Return whether a exception is being held in this future.""" + return self.exception() is not None + + @classmethod + def construct(cls, attempt_number: int, value: t.Any, has_exception: bool) -> "Future": + """Construct a new Future object.""" + fut = cls(attempt_number) + if has_exception: + fut.set_exception(value) + else: + fut.set_result(value) + return fut + + +class RetryCallState: + """State related to a single call wrapped with Retrying.""" + + def __init__( + self, + retry_object: BaseRetrying, + fn: t.Optional[WrappedFn], + args: t.Any, + kwargs: t.Any, + ) -> None: + #: Retry call start timestamp + self.start_time = time.monotonic() + #: Retry manager object + self.retry_object = retry_object + #: Function wrapped by this retry call + self.fn = fn + #: Arguments of the function wrapped by this retry call + self.args = args + #: Keyword arguments of the function wrapped by this retry call + self.kwargs = kwargs + + #: The number of the current attempt + self.attempt_number: int = 1 + #: Last outcome (result or exception) produced by the function + self.outcome: t.Optional[Future] = None + #: Timestamp of the last outcome + self.outcome_timestamp: t.Optional[float] = None + #: Time spent sleeping in retries + self.idle_for: float = 0.0 + #: Next action as decided by the retry manager + self.next_action: t.Optional[RetryAction] = None + + @property + def seconds_since_start(self) -> t.Optional[float]: + if self.outcome_timestamp is None: + return None + return self.outcome_timestamp - self.start_time + + def prepare_for_next_attempt(self) -> None: + self.outcome = None + self.outcome_timestamp = None + self.attempt_number += 1 + self.next_action = None + + def set_result(self, val: t.Any) -> None: + ts = time.monotonic() + fut = Future(self.attempt_number) + fut.set_result(val) + self.outcome, self.outcome_timestamp = fut, ts + + def set_exception(self, exc_info: t.Tuple[t.Type[BaseException], BaseException, "types.TracebackType"]) -> None: + ts = time.monotonic() + fut = Future(self.attempt_number) + fut.set_exception(exc_info[1]) + self.outcome, self.outcome_timestamp = fut, ts + + def __repr__(self): + if self.outcome is None: + result = "none yet" + elif self.outcome.failed: + exception = self.outcome.exception() + result = f"failed ({exception.__class__.__name__} {exception})" + else: + result = f"returned {self.outcome.result()}" + + slept = float(round(self.idle_for, 2)) + clsname = self.__class__.__name__ + return f"<{clsname} {id(self)}: attempt #{self.attempt_number}; slept for {slept}; last result: {result}>" + + +from pipenv.patched.notpip._vendor.tenacity._asyncio import AsyncRetrying # noqa:E402,I100 + +if tornado: + from pipenv.patched.notpip._vendor.tenacity.tornadoweb import TornadoRetrying diff --git a/pipenv/patched/notpip/_vendor/tenacity/_asyncio.py b/pipenv/patched/notpip/_vendor/tenacity/_asyncio.py new file mode 100644 index 00000000..db078da7 --- /dev/null +++ b/pipenv/patched/notpip/_vendor/tenacity/_asyncio.py @@ -0,0 +1,92 @@ +# Copyright 2016 Étienne Bersac +# Copyright 2016 Julien Danjou +# Copyright 2016 Joshua Harlow +# Copyright 2013-2014 Ray Holder +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import functools +import sys +import typing +from asyncio import sleep + +from pipenv.patched.notpip._vendor.tenacity import AttemptManager +from pipenv.patched.notpip._vendor.tenacity import BaseRetrying +from pipenv.patched.notpip._vendor.tenacity import DoAttempt +from pipenv.patched.notpip._vendor.tenacity import DoSleep +from pipenv.patched.notpip._vendor.tenacity import RetryCallState + +WrappedFn = typing.TypeVar("WrappedFn", bound=typing.Callable) +_RetValT = typing.TypeVar("_RetValT") + + +class AsyncRetrying(BaseRetrying): + def __init__(self, sleep: typing.Callable[[float], typing.Awaitable] = sleep, **kwargs: typing.Any) -> None: + super().__init__(**kwargs) + self.sleep = sleep + + async def __call__( # type: ignore # Change signature from supertype + self, + fn: typing.Callable[..., typing.Awaitable[_RetValT]], + *args: typing.Any, + **kwargs: typing.Any, + ) -> _RetValT: + self.begin() + + retry_state = RetryCallState(retry_object=self, fn=fn, args=args, kwargs=kwargs) + while True: + do = self.iter(retry_state=retry_state) + if isinstance(do, DoAttempt): + try: + result = await fn(*args, **kwargs) + except BaseException: # noqa: B902 + retry_state.set_exception(sys.exc_info()) + else: + retry_state.set_result(result) + elif isinstance(do, DoSleep): + retry_state.prepare_for_next_attempt() + await self.sleep(do) + else: + return do + + def __aiter__(self) -> "AsyncRetrying": + self.begin() + self._retry_state = RetryCallState(self, fn=None, args=(), kwargs={}) + return self + + async def __anext__(self) -> typing.Union[AttemptManager, typing.Any]: + while True: + do = self.iter(retry_state=self._retry_state) + if do is None: + raise StopAsyncIteration + elif isinstance(do, DoAttempt): + return AttemptManager(retry_state=self._retry_state) + elif isinstance(do, DoSleep): + self._retry_state.prepare_for_next_attempt() + await self.sleep(do) + else: + return do + + def wraps(self, fn: WrappedFn) -> WrappedFn: + fn = super().wraps(fn) + # Ensure wrapper is recognized as a coroutine function. + + @functools.wraps(fn) + async def async_wrapped(*args: typing.Any, **kwargs: typing.Any) -> typing.Any: + return await fn(*args, **kwargs) + + # Preserve attributes + async_wrapped.retry = fn.retry + async_wrapped.retry_with = fn.retry_with + + return async_wrapped diff --git a/pipenv/patched/notpip/_vendor/tenacity/_utils.py b/pipenv/patched/notpip/_vendor/tenacity/_utils.py new file mode 100644 index 00000000..d5c4c9de --- /dev/null +++ b/pipenv/patched/notpip/_vendor/tenacity/_utils.py @@ -0,0 +1,68 @@ +# Copyright 2016 Julien Danjou +# Copyright 2016 Joshua Harlow +# Copyright 2013-2014 Ray Holder +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +import typing + + +# sys.maxsize: +# An integer giving the maximum value a variable of type Py_ssize_t can take. +MAX_WAIT = sys.maxsize / 2 + + +def find_ordinal(pos_num: int) -> str: + # See: https://en.wikipedia.org/wiki/English_numerals#Ordinal_numbers + if pos_num == 0: + return "th" + elif pos_num == 1: + return "st" + elif pos_num == 2: + return "nd" + elif pos_num == 3: + return "rd" + elif 4 <= pos_num <= 20: + return "th" + else: + return find_ordinal(pos_num % 10) + + +def to_ordinal(pos_num: int) -> str: + return f"{pos_num}{find_ordinal(pos_num)}" + + +def get_callback_name(cb: typing.Callable[..., typing.Any]) -> str: + """Get a callback fully-qualified name. + + If no name can be produced ``repr(cb)`` is called and returned. + """ + segments = [] + try: + segments.append(cb.__qualname__) + except AttributeError: + try: + segments.append(cb.__name__) + except AttributeError: + pass + if not segments: + return repr(cb) + else: + try: + # When running under sphinx it appears this can be none? + if cb.__module__: + segments.insert(0, cb.__module__) + except AttributeError: + pass + return ".".join(segments) diff --git a/pipenv/patched/notpip/_vendor/tenacity/after.py b/pipenv/patched/notpip/_vendor/tenacity/after.py new file mode 100644 index 00000000..c97a2fda --- /dev/null +++ b/pipenv/patched/notpip/_vendor/tenacity/after.py @@ -0,0 +1,46 @@ +# Copyright 2016 Julien Danjou +# Copyright 2016 Joshua Harlow +# Copyright 2013-2014 Ray Holder +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import typing + +from pipenv.patched.notpip._vendor.tenacity import _utils + +if typing.TYPE_CHECKING: + import logging + + from pipenv.patched.notpip._vendor.tenacity import RetryCallState + + +def after_nothing(retry_state: "RetryCallState") -> None: + """After call strategy that does nothing.""" + + +def after_log( + logger: "logging.Logger", + log_level: int, + sec_format: str = "%0.3f", +) -> typing.Callable[["RetryCallState"], None]: + """After call strategy that logs to some logger the finished attempt.""" + + def log_it(retry_state: "RetryCallState") -> None: + logger.log( + log_level, + f"Finished call to '{_utils.get_callback_name(retry_state.fn)}' " + f"after {sec_format % retry_state.seconds_since_start}(s), " + f"this was the {_utils.to_ordinal(retry_state.attempt_number)} time calling it.", + ) + + return log_it diff --git a/pipenv/patched/notpip/_vendor/tenacity/before.py b/pipenv/patched/notpip/_vendor/tenacity/before.py new file mode 100644 index 00000000..68207957 --- /dev/null +++ b/pipenv/patched/notpip/_vendor/tenacity/before.py @@ -0,0 +1,41 @@ +# Copyright 2016 Julien Danjou +# Copyright 2016 Joshua Harlow +# Copyright 2013-2014 Ray Holder +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import typing + +from pipenv.patched.notpip._vendor.tenacity import _utils + +if typing.TYPE_CHECKING: + import logging + + from pipenv.patched.notpip._vendor.tenacity import RetryCallState + + +def before_nothing(retry_state: "RetryCallState") -> None: + """Before call strategy that does nothing.""" + + +def before_log(logger: "logging.Logger", log_level: int) -> typing.Callable[["RetryCallState"], None]: + """Before call strategy that logs to some logger the attempt.""" + + def log_it(retry_state: "RetryCallState") -> None: + logger.log( + log_level, + f"Starting call to '{_utils.get_callback_name(retry_state.fn)}', " + f"this is the {_utils.to_ordinal(retry_state.attempt_number)} time calling it.", + ) + + return log_it diff --git a/pipenv/patched/notpip/_vendor/tenacity/before_sleep.py b/pipenv/patched/notpip/_vendor/tenacity/before_sleep.py new file mode 100644 index 00000000..0ef30d00 --- /dev/null +++ b/pipenv/patched/notpip/_vendor/tenacity/before_sleep.py @@ -0,0 +1,58 @@ +# Copyright 2016 Julien Danjou +# Copyright 2016 Joshua Harlow +# Copyright 2013-2014 Ray Holder +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import typing + +from pipenv.patched.notpip._vendor.tenacity import _utils + +if typing.TYPE_CHECKING: + import logging + + from pipenv.patched.notpip._vendor.tenacity import RetryCallState + + +def before_sleep_nothing(retry_state: "RetryCallState") -> None: + """Before call strategy that does nothing.""" + + +def before_sleep_log( + logger: "logging.Logger", + log_level: int, + exc_info: bool = False, +) -> typing.Callable[["RetryCallState"], None]: + """Before call strategy that logs to some logger the attempt.""" + + def log_it(retry_state: "RetryCallState") -> None: + if retry_state.outcome.failed: + ex = retry_state.outcome.exception() + verb, value = "raised", f"{ex.__class__.__name__}: {ex}" + + if exc_info: + local_exc_info = retry_state.outcome.exception() + else: + local_exc_info = False + else: + verb, value = "returned", retry_state.outcome.result() + local_exc_info = False # exc_info does not apply when no exception + + logger.log( + log_level, + f"Retrying {_utils.get_callback_name(retry_state.fn)} " + f"in {retry_state.next_action.sleep} seconds as it {verb} {value}.", + exc_info=local_exc_info, + ) + + return log_it diff --git a/pipenv/patched/notpip/_vendor/tenacity/nap.py b/pipenv/patched/notpip/_vendor/tenacity/nap.py new file mode 100644 index 00000000..72aa5bfd --- /dev/null +++ b/pipenv/patched/notpip/_vendor/tenacity/nap.py @@ -0,0 +1,43 @@ +# Copyright 2016 Étienne Bersac +# Copyright 2016 Julien Danjou +# Copyright 2016 Joshua Harlow +# Copyright 2013-2014 Ray Holder +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import time +import typing + +if typing.TYPE_CHECKING: + import threading + + +def sleep(seconds: float) -> None: + """ + Sleep strategy that delays execution for a given number of seconds. + + This is the default strategy, and may be mocked out for unit testing. + """ + time.sleep(seconds) + + +class sleep_using_event: + """Sleep strategy that waits on an event to be set.""" + + def __init__(self, event: "threading.Event") -> None: + self.event = event + + def __call__(self, timeout: typing.Optional[float]) -> None: + # NOTE(harlowja): this may *not* actually wait for timeout + # seconds if the event is set (ie this may eject out early). + self.event.wait(timeout=timeout) diff --git a/pipenv/patched/notpip/_vendor/tenacity/retry.py b/pipenv/patched/notpip/_vendor/tenacity/retry.py new file mode 100644 index 00000000..db3b4471 --- /dev/null +++ b/pipenv/patched/notpip/_vendor/tenacity/retry.py @@ -0,0 +1,213 @@ +# Copyright 2016–2021 Julien Danjou +# Copyright 2016 Joshua Harlow +# Copyright 2013-2014 Ray Holder +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import abc +import re +import typing + +if typing.TYPE_CHECKING: + from pipenv.patched.notpip._vendor.tenacity import RetryCallState + + +class retry_base(abc.ABC): + """Abstract base class for retry strategies.""" + + @abc.abstractmethod + def __call__(self, retry_state: "RetryCallState") -> bool: + pass + + def __and__(self, other: "retry_base") -> "retry_all": + return retry_all(self, other) + + def __or__(self, other: "retry_base") -> "retry_any": + return retry_any(self, other) + + +class _retry_never(retry_base): + """Retry strategy that never rejects any result.""" + + def __call__(self, retry_state: "RetryCallState") -> bool: + return False + + +retry_never = _retry_never() + + +class _retry_always(retry_base): + """Retry strategy that always rejects any result.""" + + def __call__(self, retry_state: "RetryCallState") -> bool: + return True + + +retry_always = _retry_always() + + +class retry_if_exception(retry_base): + """Retry strategy that retries if an exception verifies a predicate.""" + + def __init__(self, predicate: typing.Callable[[BaseException], bool]) -> None: + self.predicate = predicate + + def __call__(self, retry_state: "RetryCallState") -> bool: + if retry_state.outcome.failed: + return self.predicate(retry_state.outcome.exception()) + else: + return False + + +class retry_if_exception_type(retry_if_exception): + """Retries if an exception has been raised of one or more types.""" + + def __init__( + self, + exception_types: typing.Union[ + typing.Type[BaseException], + typing.Tuple[typing.Type[BaseException], ...], + ] = Exception, + ) -> None: + self.exception_types = exception_types + super().__init__(lambda e: isinstance(e, exception_types)) + + +class retry_if_not_exception_type(retry_if_exception): + """Retries except an exception has been raised of one or more types.""" + + def __init__( + self, + exception_types: typing.Union[ + typing.Type[BaseException], + typing.Tuple[typing.Type[BaseException], ...], + ] = Exception, + ) -> None: + self.exception_types = exception_types + super().__init__(lambda e: not isinstance(e, exception_types)) + + +class retry_unless_exception_type(retry_if_exception): + """Retries until an exception is raised of one or more types.""" + + def __init__( + self, + exception_types: typing.Union[ + typing.Type[BaseException], + typing.Tuple[typing.Type[BaseException], ...], + ] = Exception, + ) -> None: + self.exception_types = exception_types + super().__init__(lambda e: not isinstance(e, exception_types)) + + def __call__(self, retry_state: "RetryCallState") -> bool: + # always retry if no exception was raised + if not retry_state.outcome.failed: + return True + return self.predicate(retry_state.outcome.exception()) + + +class retry_if_result(retry_base): + """Retries if the result verifies a predicate.""" + + def __init__(self, predicate: typing.Callable[[typing.Any], bool]) -> None: + self.predicate = predicate + + def __call__(self, retry_state: "RetryCallState") -> bool: + if not retry_state.outcome.failed: + return self.predicate(retry_state.outcome.result()) + else: + return False + + +class retry_if_not_result(retry_base): + """Retries if the result refutes a predicate.""" + + def __init__(self, predicate: typing.Callable[[typing.Any], bool]) -> None: + self.predicate = predicate + + def __call__(self, retry_state: "RetryCallState") -> bool: + if not retry_state.outcome.failed: + return not self.predicate(retry_state.outcome.result()) + else: + return False + + +class retry_if_exception_message(retry_if_exception): + """Retries if an exception message equals or matches.""" + + def __init__( + self, + message: typing.Optional[str] = None, + match: typing.Optional[str] = None, + ) -> None: + if message and match: + raise TypeError(f"{self.__class__.__name__}() takes either 'message' or 'match', not both") + + # set predicate + if message: + + def message_fnc(exception: BaseException) -> bool: + return message == str(exception) + + predicate = message_fnc + elif match: + prog = re.compile(match) + + def match_fnc(exception: BaseException) -> bool: + return bool(prog.match(str(exception))) + + predicate = match_fnc + else: + raise TypeError(f"{self.__class__.__name__}() missing 1 required argument 'message' or 'match'") + + super().__init__(predicate) + + +class retry_if_not_exception_message(retry_if_exception_message): + """Retries until an exception message equals or matches.""" + + def __init__( + self, + message: typing.Optional[str] = None, + match: typing.Optional[str] = None, + ) -> None: + super().__init__(message, match) + # invert predicate + if_predicate = self.predicate + self.predicate = lambda *args_, **kwargs_: not if_predicate(*args_, **kwargs_) + + def __call__(self, retry_state: "RetryCallState") -> bool: + if not retry_state.outcome.failed: + return True + return self.predicate(retry_state.outcome.exception()) + + +class retry_any(retry_base): + """Retries if any of the retries condition is valid.""" + + def __init__(self, *retries: retry_base) -> None: + self.retries = retries + + def __call__(self, retry_state: "RetryCallState") -> bool: + return any(r(retry_state) for r in self.retries) + + +class retry_all(retry_base): + """Retries if all the retries condition are valid.""" + + def __init__(self, *retries: retry_base) -> None: + self.retries = retries + + def __call__(self, retry_state: "RetryCallState") -> bool: + return all(r(retry_state) for r in self.retries) diff --git a/pipenv/patched/notpip/_vendor/tenacity/stop.py b/pipenv/patched/notpip/_vendor/tenacity/stop.py new file mode 100644 index 00000000..b3290509 --- /dev/null +++ b/pipenv/patched/notpip/_vendor/tenacity/stop.py @@ -0,0 +1,96 @@ +# Copyright 2016–2021 Julien Danjou +# Copyright 2016 Joshua Harlow +# Copyright 2013-2014 Ray Holder +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import abc +import typing + +if typing.TYPE_CHECKING: + import threading + + from pipenv.patched.notpip._vendor.tenacity import RetryCallState + + +class stop_base(abc.ABC): + """Abstract base class for stop strategies.""" + + @abc.abstractmethod + def __call__(self, retry_state: "RetryCallState") -> bool: + pass + + def __and__(self, other: "stop_base") -> "stop_all": + return stop_all(self, other) + + def __or__(self, other: "stop_base") -> "stop_any": + return stop_any(self, other) + + +class stop_any(stop_base): + """Stop if any of the stop condition is valid.""" + + def __init__(self, *stops: stop_base) -> None: + self.stops = stops + + def __call__(self, retry_state: "RetryCallState") -> bool: + return any(x(retry_state) for x in self.stops) + + +class stop_all(stop_base): + """Stop if all the stop conditions are valid.""" + + def __init__(self, *stops: stop_base) -> None: + self.stops = stops + + def __call__(self, retry_state: "RetryCallState") -> bool: + return all(x(retry_state) for x in self.stops) + + +class _stop_never(stop_base): + """Never stop.""" + + def __call__(self, retry_state: "RetryCallState") -> bool: + return False + + +stop_never = _stop_never() + + +class stop_when_event_set(stop_base): + """Stop when the given event is set.""" + + def __init__(self, event: "threading.Event") -> None: + self.event = event + + def __call__(self, retry_state: "RetryCallState") -> bool: + return self.event.is_set() + + +class stop_after_attempt(stop_base): + """Stop when the previous attempt >= max_attempt.""" + + def __init__(self, max_attempt_number: int) -> None: + self.max_attempt_number = max_attempt_number + + def __call__(self, retry_state: "RetryCallState") -> bool: + return retry_state.attempt_number >= self.max_attempt_number + + +class stop_after_delay(stop_base): + """Stop when the time from the first attempt >= limit.""" + + def __init__(self, max_delay: float) -> None: + self.max_delay = max_delay + + def __call__(self, retry_state: "RetryCallState") -> bool: + return retry_state.seconds_since_start >= self.max_delay diff --git a/pipenv/patched/notpip/_vendor/tenacity/tornadoweb.py b/pipenv/patched/notpip/_vendor/tenacity/tornadoweb.py new file mode 100644 index 00000000..503ebd90 --- /dev/null +++ b/pipenv/patched/notpip/_vendor/tenacity/tornadoweb.py @@ -0,0 +1,59 @@ +# Copyright 2017 Elisey Zanko +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +import typing + +from pipenv.patched.notpip._vendor.tenacity import BaseRetrying +from pipenv.patched.notpip._vendor.tenacity import DoAttempt +from pipenv.patched.notpip._vendor.tenacity import DoSleep +from pipenv.patched.notpip._vendor.tenacity import RetryCallState + +from tornado import gen + +if typing.TYPE_CHECKING: + from tornado.concurrent import Future + +_RetValT = typing.TypeVar("_RetValT") + + +class TornadoRetrying(BaseRetrying): + def __init__(self, sleep: "typing.Callable[[float], Future[None]]" = gen.sleep, **kwargs: typing.Any) -> None: + super().__init__(**kwargs) + self.sleep = sleep + + @gen.coroutine + def __call__( # type: ignore # Change signature from supertype + self, + fn: "typing.Callable[..., typing.Union[typing.Generator[typing.Any, typing.Any, _RetValT], Future[_RetValT]]]", + *args: typing.Any, + **kwargs: typing.Any, + ) -> "typing.Generator[typing.Any, typing.Any, _RetValT]": + self.begin() + + retry_state = RetryCallState(retry_object=self, fn=fn, args=args, kwargs=kwargs) + while True: + do = self.iter(retry_state=retry_state) + if isinstance(do, DoAttempt): + try: + result = yield fn(*args, **kwargs) + except BaseException: # noqa: B902 + retry_state.set_exception(sys.exc_info()) + else: + retry_state.set_result(result) + elif isinstance(do, DoSleep): + retry_state.prepare_for_next_attempt() + yield self.sleep(do) + else: + raise gen.Return(do) diff --git a/pipenv/patched/notpip/_vendor/tenacity/wait.py b/pipenv/patched/notpip/_vendor/tenacity/wait.py new file mode 100644 index 00000000..3e7756da --- /dev/null +++ b/pipenv/patched/notpip/_vendor/tenacity/wait.py @@ -0,0 +1,191 @@ +# Copyright 2016–2021 Julien Danjou +# Copyright 2016 Joshua Harlow +# Copyright 2013-2014 Ray Holder +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import abc +import random +import typing + +from pipenv.patched.notpip._vendor.tenacity import _utils + +if typing.TYPE_CHECKING: + from pipenv.patched.notpip._vendor.tenacity import RetryCallState + + +class wait_base(abc.ABC): + """Abstract base class for wait strategies.""" + + @abc.abstractmethod + def __call__(self, retry_state: "RetryCallState") -> float: + pass + + def __add__(self, other: "wait_base") -> "wait_combine": + return wait_combine(self, other) + + def __radd__(self, other: "wait_base") -> typing.Union["wait_combine", "wait_base"]: + # make it possible to use multiple waits with the built-in sum function + if other == 0: + return self + return self.__add__(other) + + +class wait_fixed(wait_base): + """Wait strategy that waits a fixed amount of time between each retry.""" + + def __init__(self, wait: float) -> None: + self.wait_fixed = wait + + def __call__(self, retry_state: "RetryCallState") -> float: + return self.wait_fixed + + +class wait_none(wait_fixed): + """Wait strategy that doesn't wait at all before retrying.""" + + def __init__(self) -> None: + super().__init__(0) + + +class wait_random(wait_base): + """Wait strategy that waits a random amount of time between min/max.""" + + def __init__(self, min: typing.Union[int, float] = 0, max: typing.Union[int, float] = 1) -> None: # noqa + self.wait_random_min = min + self.wait_random_max = max + + def __call__(self, retry_state: "RetryCallState") -> float: + return self.wait_random_min + (random.random() * (self.wait_random_max - self.wait_random_min)) + + +class wait_combine(wait_base): + """Combine several waiting strategies.""" + + def __init__(self, *strategies: wait_base) -> None: + self.wait_funcs = strategies + + def __call__(self, retry_state: "RetryCallState") -> float: + return sum(x(retry_state=retry_state) for x in self.wait_funcs) + + +class wait_chain(wait_base): + """Chain two or more waiting strategies. + + If all strategies are exhausted, the very last strategy is used + thereafter. + + For example:: + + @retry(wait=wait_chain(*[wait_fixed(1) for i in range(3)] + + [wait_fixed(2) for j in range(5)] + + [wait_fixed(5) for k in range(4))) + def wait_chained(): + print("Wait 1s for 3 attempts, 2s for 5 attempts and 5s + thereafter.") + """ + + def __init__(self, *strategies: wait_base) -> None: + self.strategies = strategies + + def __call__(self, retry_state: "RetryCallState") -> float: + wait_func_no = min(max(retry_state.attempt_number, 1), len(self.strategies)) + wait_func = self.strategies[wait_func_no - 1] + return wait_func(retry_state=retry_state) + + +class wait_incrementing(wait_base): + """Wait an incremental amount of time after each attempt. + + Starting at a starting value and incrementing by a value for each attempt + (and restricting the upper limit to some maximum value). + """ + + def __init__( + self, + start: typing.Union[int, float] = 0, + increment: typing.Union[int, float] = 100, + max: typing.Union[int, float] = _utils.MAX_WAIT, # noqa + ) -> None: + self.start = start + self.increment = increment + self.max = max + + def __call__(self, retry_state: "RetryCallState") -> float: + result = self.start + (self.increment * (retry_state.attempt_number - 1)) + return max(0, min(result, self.max)) + + +class wait_exponential(wait_base): + """Wait strategy that applies exponential backoff. + + It allows for a customized multiplier and an ability to restrict the + upper and lower limits to some maximum and minimum value. + + The intervals are fixed (i.e. there is no jitter), so this strategy is + suitable for balancing retries against latency when a required resource is + unavailable for an unknown duration, but *not* suitable for resolving + contention between multiple processes for a shared resource. Use + wait_random_exponential for the latter case. + """ + + def __init__( + self, + multiplier: typing.Union[int, float] = 1, + max: typing.Union[int, float] = _utils.MAX_WAIT, # noqa + exp_base: typing.Union[int, float] = 2, + min: typing.Union[int, float] = 0, # noqa + ) -> None: + self.multiplier = multiplier + self.min = min + self.max = max + self.exp_base = exp_base + + def __call__(self, retry_state: "RetryCallState") -> float: + try: + exp = self.exp_base ** (retry_state.attempt_number - 1) + result = self.multiplier * exp + except OverflowError: + return self.max + return max(max(0, self.min), min(result, self.max)) + + +class wait_random_exponential(wait_exponential): + """Random wait with exponentially widening window. + + An exponential backoff strategy used to mediate contention between multiple + uncoordinated processes for a shared resource in distributed systems. This + is the sense in which "exponential backoff" is meant in e.g. Ethernet + networking, and corresponds to the "Full Jitter" algorithm described in + this blog post: + + https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/ + + Each retry occurs at a random time in a geometrically expanding interval. + It allows for a custom multiplier and an ability to restrict the upper + limit of the random interval to some maximum value. + + Example:: + + wait_random_exponential(multiplier=0.5, # initial window 0.5s + max=60) # max 60s timeout + + When waiting for an unavailable resource to become available again, as + opposed to trying to resolve contention for a shared resource, the + wait_exponential strategy (which uses a fixed interval) may be preferable. + + """ + + def __call__(self, retry_state: "RetryCallState") -> float: + high = super().__call__(retry_state=retry_state) + return random.uniform(0, high) diff --git a/pipenv/vendor/pythonfinder/pep514tools.LICENSE b/pipenv/patched/notpip/_vendor/tomli/LICENSE similarity index 96% rename from pipenv/vendor/pythonfinder/pep514tools.LICENSE rename to pipenv/patched/notpip/_vendor/tomli/LICENSE index c7ac395f..e859590f 100644 --- a/pipenv/vendor/pythonfinder/pep514tools.LICENSE +++ b/pipenv/patched/notpip/_vendor/tomli/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2016 Steve Dower +Copyright (c) 2021 Taneli Hukkinen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pipenv/patched/notpip/_vendor/tomli/__init__.py b/pipenv/patched/notpip/_vendor/tomli/__init__.py new file mode 100644 index 00000000..2b642a24 --- /dev/null +++ b/pipenv/patched/notpip/_vendor/tomli/__init__.py @@ -0,0 +1,6 @@ +"""A lil' TOML parser.""" + +__all__ = ("loads", "load", "TOMLDecodeError") +__version__ = "1.0.3" # DO NOT EDIT THIS LINE MANUALLY. LET bump2version UTILITY DO IT + +from pipenv.patched.notpip._vendor.tomli._parser import TOMLDecodeError, load, loads diff --git a/pipenv/patched/notpip/_vendor/tomli/_parser.py b/pipenv/patched/notpip/_vendor/tomli/_parser.py new file mode 100644 index 00000000..ebd80a9e --- /dev/null +++ b/pipenv/patched/notpip/_vendor/tomli/_parser.py @@ -0,0 +1,703 @@ +import string +from types import MappingProxyType +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Dict, + FrozenSet, + Iterable, + Optional, + TextIO, + Tuple, +) + +from pipenv.patched.notpip._vendor.tomli._re import ( + RE_BIN, + RE_DATETIME, + RE_HEX, + RE_LOCALTIME, + RE_NUMBER, + RE_OCT, + match_to_datetime, + match_to_localtime, + match_to_number, +) + +if TYPE_CHECKING: + from re import Pattern + + +ASCII_CTRL = frozenset(chr(i) for i in range(32)) | frozenset(chr(127)) + +# Neither of these sets include quotation mark or backslash. They are +# currently handled as separate cases in the parser functions. +ILLEGAL_BASIC_STR_CHARS = ASCII_CTRL - frozenset("\t") +ILLEGAL_MULTILINE_BASIC_STR_CHARS = ASCII_CTRL - frozenset("\t\n\r") + +ILLEGAL_LITERAL_STR_CHARS = ILLEGAL_BASIC_STR_CHARS +ILLEGAL_MULTILINE_LITERAL_STR_CHARS = ASCII_CTRL - frozenset("\t\n") + +ILLEGAL_COMMENT_CHARS = ILLEGAL_BASIC_STR_CHARS + +TOML_WS = frozenset(" \t") +TOML_WS_AND_NEWLINE = TOML_WS | frozenset("\n") +BARE_KEY_CHARS = frozenset(string.ascii_letters + string.digits + "-_") +KEY_INITIAL_CHARS = BARE_KEY_CHARS | frozenset("\"'") + +BASIC_STR_ESCAPE_REPLACEMENTS = MappingProxyType( + { + "\\b": "\u0008", # backspace + "\\t": "\u0009", # tab + "\\n": "\u000A", # linefeed + "\\f": "\u000C", # form feed + "\\r": "\u000D", # carriage return + '\\"': "\u0022", # quote + "\\\\": "\u005C", # backslash + } +) + +# Type annotations +ParseFloat = Callable[[str], Any] +Key = Tuple[str, ...] +Pos = int + + +class TOMLDecodeError(ValueError): + """An error raised if a document is not valid TOML.""" + + +def load(fp: TextIO, *, parse_float: ParseFloat = float) -> Dict[str, Any]: + """Parse TOML from a file object.""" + s = fp.read() + return loads(s, parse_float=parse_float) + + +def loads(s: str, *, parse_float: ParseFloat = float) -> Dict[str, Any]: # noqa: C901 + """Parse TOML from a string.""" + + # The spec allows converting "\r\n" to "\n", even in string + # literals. Let's do so to simplify parsing. + src = s.replace("\r\n", "\n") + pos = 0 + state = State() + + # Parse one statement at a time + # (typically means one line in TOML source) + while True: + # 1. Skip line leading whitespace + pos = skip_chars(src, pos, TOML_WS) + + # 2. Parse rules. Expect one of the following: + # - end of file + # - end of line + # - comment + # - key/value pair + # - append dict to list (and move to its namespace) + # - create dict (and move to its namespace) + # Skip trailing whitespace when applicable. + try: + char = src[pos] + except IndexError: + break + if char == "\n": + pos += 1 + continue + if char in KEY_INITIAL_CHARS: + pos = key_value_rule(src, pos, state, parse_float) + pos = skip_chars(src, pos, TOML_WS) + elif char == "[": + try: + second_char: Optional[str] = src[pos + 1] + except IndexError: + second_char = None + if second_char == "[": + pos = create_list_rule(src, pos, state) + else: + pos = create_dict_rule(src, pos, state) + pos = skip_chars(src, pos, TOML_WS) + elif char != "#": + raise suffixed_err(src, pos, "Invalid statement") + + # 3. Skip comment + pos = skip_comment(src, pos) + + # 4. Expect end of line or end of file + try: + char = src[pos] + except IndexError: + break + if char != "\n": + raise suffixed_err( + src, pos, "Expected newline or end of document after a statement" + ) + pos += 1 + + return state.out.dict + + +class State: + def __init__(self) -> None: + # Mutable, read-only + self.out = NestedDict() + self.flags = Flags() + + # Immutable, read and write + self.header_namespace: Key = () + + +class Flags: + """Flags that map to parsed keys/namespaces.""" + + # Marks an immutable namespace (inline array or inline table). + FROZEN = 0 + # Marks a nest that has been explicitly created and can no longer + # be opened using the "[table]" syntax. + EXPLICIT_NEST = 1 + + def __init__(self) -> None: + self._flags: Dict[str, dict] = {} + + def unset_all(self, key: Key) -> None: + cont = self._flags + for k in key[:-1]: + if k not in cont: + return + cont = cont[k]["nested"] + cont.pop(key[-1], None) + + def set_for_relative_key(self, head_key: Key, rel_key: Key, flag: int) -> None: + cont = self._flags + for k in head_key: + if k not in cont: + cont[k] = {"flags": set(), "recursive_flags": set(), "nested": {}} + cont = cont[k]["nested"] + for k in rel_key: + if k in cont: + cont[k]["flags"].add(flag) + else: + cont[k] = {"flags": {flag}, "recursive_flags": set(), "nested": {}} + cont = cont[k]["nested"] + + def set(self, key: Key, flag: int, *, recursive: bool) -> None: # noqa: A003 + cont = self._flags + key_parent, key_stem = key[:-1], key[-1] + for k in key_parent: + if k not in cont: + cont[k] = {"flags": set(), "recursive_flags": set(), "nested": {}} + cont = cont[k]["nested"] + if key_stem not in cont: + cont[key_stem] = {"flags": set(), "recursive_flags": set(), "nested": {}} + cont[key_stem]["recursive_flags" if recursive else "flags"].add(flag) + + def is_(self, key: Key, flag: int) -> bool: + if not key: + return False # document root has no flags + cont = self._flags + for k in key[:-1]: + if k not in cont: + return False + inner_cont = cont[k] + if flag in inner_cont["recursive_flags"]: + return True + cont = inner_cont["nested"] + key_stem = key[-1] + if key_stem in cont: + cont = cont[key_stem] + return flag in cont["flags"] or flag in cont["recursive_flags"] + return False + + +class NestedDict: + def __init__(self) -> None: + # The parsed content of the TOML document + self.dict: Dict[str, Any] = {} + + def get_or_create_nest( + self, + key: Key, + *, + access_lists: bool = True, + ) -> dict: + cont: Any = self.dict + for k in key: + if k not in cont: + cont[k] = {} + cont = cont[k] + if access_lists and isinstance(cont, list): + cont = cont[-1] + if not isinstance(cont, dict): + raise KeyError("There is no nest behind this key") + return cont + + def append_nest_to_list(self, key: Key) -> None: + cont = self.get_or_create_nest(key[:-1]) + last_key = key[-1] + if last_key in cont: + list_ = cont[last_key] + if not isinstance(list_, list): + raise KeyError("An object other than list found behind this key") + list_.append({}) + else: + cont[last_key] = [{}] + + +def skip_chars(src: str, pos: Pos, chars: Iterable[str]) -> Pos: + try: + while src[pos] in chars: + pos += 1 + except IndexError: + pass + return pos + + +def skip_until( + src: str, + pos: Pos, + expect: str, + *, + error_on: FrozenSet[str], + error_on_eof: bool, +) -> Pos: + try: + new_pos = src.index(expect, pos) + except ValueError: + new_pos = len(src) + if error_on_eof: + raise suffixed_err(src, new_pos, f'Expected "{expect!r}"') + + bad_chars = error_on.intersection(src[pos:new_pos]) + if bad_chars: + bad_char = next(iter(bad_chars)) + bad_pos = src.index(bad_char, pos) + raise suffixed_err(src, bad_pos, f'Found invalid character "{bad_char!r}"') + return new_pos + + +def skip_comment(src: str, pos: Pos) -> Pos: + try: + char: Optional[str] = src[pos] + except IndexError: + char = None + if char == "#": + return skip_until( + src, pos + 1, "\n", error_on=ILLEGAL_COMMENT_CHARS, error_on_eof=False + ) + return pos + + +def skip_comments_and_array_ws(src: str, pos: Pos) -> Pos: + while True: + pos_before_skip = pos + pos = skip_chars(src, pos, TOML_WS_AND_NEWLINE) + pos = skip_comment(src, pos) + if pos == pos_before_skip: + return pos + + +def create_dict_rule(src: str, pos: Pos, state: State) -> Pos: + pos += 1 # Skip "[" + pos = skip_chars(src, pos, TOML_WS) + pos, key = parse_key(src, pos) + + if state.flags.is_(key, Flags.EXPLICIT_NEST) or state.flags.is_(key, Flags.FROZEN): + raise suffixed_err(src, pos, f"Can not declare {key} twice") + state.flags.set(key, Flags.EXPLICIT_NEST, recursive=False) + try: + state.out.get_or_create_nest(key) + except KeyError: + raise suffixed_err(src, pos, "Can not overwrite a value") + state.header_namespace = key + + if src[pos : pos + 1] != "]": + raise suffixed_err(src, pos, 'Expected "]" at the end of a table declaration') + return pos + 1 + + +def create_list_rule(src: str, pos: Pos, state: State) -> Pos: + pos += 2 # Skip "[[" + pos = skip_chars(src, pos, TOML_WS) + pos, key = parse_key(src, pos) + + if state.flags.is_(key, Flags.FROZEN): + raise suffixed_err(src, pos, f"Can not mutate immutable namespace {key}") + # Free the namespace now that it points to another empty list item... + state.flags.unset_all(key) + # ...but this key precisely is still prohibited from table declaration + state.flags.set(key, Flags.EXPLICIT_NEST, recursive=False) + try: + state.out.append_nest_to_list(key) + except KeyError: + raise suffixed_err(src, pos, "Can not overwrite a value") + state.header_namespace = key + + end_marker = src[pos : pos + 2] + if end_marker != "]]": + raise suffixed_err( + src, + pos, + f'Found "{end_marker!r}" at the end of an array declaration.' + ' Expected "]]"', + ) + return pos + 2 + + +def key_value_rule(src: str, pos: Pos, state: State, parse_float: ParseFloat) -> Pos: + pos, key, value = parse_key_value_pair(src, pos, parse_float) + key_parent, key_stem = key[:-1], key[-1] + abs_key_parent = state.header_namespace + key_parent + + if state.flags.is_(abs_key_parent, Flags.FROZEN): + raise suffixed_err( + src, pos, f"Can not mutate immutable namespace {abs_key_parent}" + ) + # Containers in the relative path can't be opened with the table syntax after this + state.flags.set_for_relative_key(state.header_namespace, key, Flags.EXPLICIT_NEST) + try: + nest = state.out.get_or_create_nest(abs_key_parent) + except KeyError: + raise suffixed_err(src, pos, "Can not overwrite a value") + if key_stem in nest: + raise suffixed_err(src, pos, "Can not overwrite a value") + # Mark inline table and array namespaces recursively immutable + if isinstance(value, (dict, list)): + abs_key = state.header_namespace + key + state.flags.set(abs_key, Flags.FROZEN, recursive=True) + nest[key_stem] = value + return pos + + +def parse_key_value_pair( + src: str, pos: Pos, parse_float: ParseFloat +) -> Tuple[Pos, Key, Any]: + pos, key = parse_key(src, pos) + try: + char: Optional[str] = src[pos] + except IndexError: + char = None + if char != "=": + raise suffixed_err(src, pos, 'Expected "=" after a key in a key/value pair') + pos += 1 + pos = skip_chars(src, pos, TOML_WS) + pos, value = parse_value(src, pos, parse_float) + return pos, key, value + + +def parse_key(src: str, pos: Pos) -> Tuple[Pos, Key]: + pos, key_part = parse_key_part(src, pos) + key = [key_part] + pos = skip_chars(src, pos, TOML_WS) + while True: + try: + char: Optional[str] = src[pos] + except IndexError: + char = None + if char != ".": + return pos, tuple(key) + pos += 1 + pos = skip_chars(src, pos, TOML_WS) + pos, key_part = parse_key_part(src, pos) + key.append(key_part) + pos = skip_chars(src, pos, TOML_WS) + + +def parse_key_part(src: str, pos: Pos) -> Tuple[Pos, str]: + try: + char: Optional[str] = src[pos] + except IndexError: + char = None + if char in BARE_KEY_CHARS: + start_pos = pos + pos = skip_chars(src, pos, BARE_KEY_CHARS) + return pos, src[start_pos:pos] + if char == "'": + return parse_literal_str(src, pos) + if char == '"': + return parse_one_line_basic_str(src, pos) + raise suffixed_err(src, pos, "Invalid initial character for a key part") + + +def parse_one_line_basic_str(src: str, pos: Pos) -> Tuple[Pos, str]: + pos += 1 + return parse_basic_str(src, pos, multiline=False) + + +def parse_array(src: str, pos: Pos, parse_float: ParseFloat) -> Tuple[Pos, list]: + pos += 1 + array: list = [] + + pos = skip_comments_and_array_ws(src, pos) + if src[pos : pos + 1] == "]": + return pos + 1, array + while True: + pos, val = parse_value(src, pos, parse_float) + array.append(val) + pos = skip_comments_and_array_ws(src, pos) + + c = src[pos : pos + 1] + if c == "]": + return pos + 1, array + if c != ",": + raise suffixed_err(src, pos, "Unclosed array") + pos += 1 + + pos = skip_comments_and_array_ws(src, pos) + if src[pos : pos + 1] == "]": + return pos + 1, array + + +def parse_inline_table(src: str, pos: Pos, parse_float: ParseFloat) -> Tuple[Pos, dict]: + pos += 1 + nested_dict = NestedDict() + flags = Flags() + + pos = skip_chars(src, pos, TOML_WS) + if src[pos : pos + 1] == "}": + return pos + 1, nested_dict.dict + while True: + pos, key, value = parse_key_value_pair(src, pos, parse_float) + key_parent, key_stem = key[:-1], key[-1] + if flags.is_(key, Flags.FROZEN): + raise suffixed_err(src, pos, f"Can not mutate immutable namespace {key}") + try: + nest = nested_dict.get_or_create_nest(key_parent, access_lists=False) + except KeyError: + raise suffixed_err(src, pos, "Can not overwrite a value") + if key_stem in nest: + raise suffixed_err(src, pos, f'Duplicate inline table key "{key_stem}"') + nest[key_stem] = value + pos = skip_chars(src, pos, TOML_WS) + c = src[pos : pos + 1] + if c == "}": + return pos + 1, nested_dict.dict + if c != ",": + raise suffixed_err(src, pos, "Unclosed inline table") + if isinstance(value, (dict, list)): + flags.set(key, Flags.FROZEN, recursive=True) + pos += 1 + pos = skip_chars(src, pos, TOML_WS) + + +def parse_basic_str_escape( + src: str, pos: Pos, *, multiline: bool = False +) -> Tuple[Pos, str]: + escape_id = src[pos : pos + 2] + pos += 2 + if multiline and escape_id in {"\\ ", "\\\t", "\\\n"}: + # Skip whitespace until next non-whitespace character or end of + # the doc. Error if non-whitespace is found before newline. + if escape_id != "\\\n": + pos = skip_chars(src, pos, TOML_WS) + char = src[pos : pos + 1] + if not char: + return pos, "" + if char != "\n": + raise suffixed_err(src, pos, 'Unescaped "\\" in a string') + pos += 1 + pos = skip_chars(src, pos, TOML_WS_AND_NEWLINE) + return pos, "" + if escape_id == "\\u": + return parse_hex_char(src, pos, 4) + if escape_id == "\\U": + return parse_hex_char(src, pos, 8) + try: + return pos, BASIC_STR_ESCAPE_REPLACEMENTS[escape_id] + except KeyError: + if len(escape_id) != 2: + raise suffixed_err(src, pos, "Unterminated string") + raise suffixed_err(src, pos, 'Unescaped "\\" in a string') + + +def parse_basic_str_escape_multiline(src: str, pos: Pos) -> Tuple[Pos, str]: + return parse_basic_str_escape(src, pos, multiline=True) + + +def parse_hex_char(src: str, pos: Pos, hex_len: int) -> Tuple[Pos, str]: + hex_str = src[pos : pos + hex_len] + if len(hex_str) != hex_len or any(c not in string.hexdigits for c in hex_str): + raise suffixed_err(src, pos, "Invalid hex value") + pos += hex_len + hex_int = int(hex_str, 16) + if not is_unicode_scalar_value(hex_int): + raise suffixed_err(src, pos, "Escaped character is not a Unicode scalar value") + return pos, chr(hex_int) + + +def parse_literal_str(src: str, pos: Pos) -> Tuple[Pos, str]: + pos += 1 # Skip starting apostrophe + start_pos = pos + pos = skip_until( + src, pos, "'", error_on=ILLEGAL_LITERAL_STR_CHARS, error_on_eof=True + ) + return pos + 1, src[start_pos:pos] # Skip ending apostrophe + + +def parse_multiline_str(src: str, pos: Pos, *, literal: bool) -> Tuple[Pos, str]: + pos += 3 + if src[pos : pos + 1] == "\n": + pos += 1 + + if literal: + delim = "'" + end_pos = skip_until( + src, + pos, + "'''", + error_on=ILLEGAL_MULTILINE_LITERAL_STR_CHARS, + error_on_eof=True, + ) + result = src[pos:end_pos] + pos = end_pos + 3 + else: + delim = '"' + pos, result = parse_basic_str(src, pos, multiline=True) + + # Add at maximum two extra apostrophes/quotes if the end sequence + # is 4 or 5 chars long instead of just 3. + if src[pos : pos + 1] != delim: + return pos, result + pos += 1 + if src[pos : pos + 1] != delim: + return pos, result + delim + pos += 1 + return pos, result + (delim * 2) + + +def parse_basic_str(src: str, pos: Pos, *, multiline: bool) -> Tuple[Pos, str]: + if multiline: + error_on = ILLEGAL_MULTILINE_BASIC_STR_CHARS + parse_escapes = parse_basic_str_escape_multiline + else: + error_on = ILLEGAL_BASIC_STR_CHARS + parse_escapes = parse_basic_str_escape + result = "" + start_pos = pos + while True: + try: + char = src[pos] + except IndexError: + raise suffixed_err(src, pos, "Unterminated string") + if char == '"': + if not multiline: + return pos + 1, result + src[start_pos:pos] + if src[pos + 1 : pos + 3] == '""': + return pos + 3, result + src[start_pos:pos] + pos += 1 + continue + if char == "\\": + result += src[start_pos:pos] + pos, parsed_escape = parse_escapes(src, pos) + result += parsed_escape + start_pos = pos + continue + if char in error_on: + raise suffixed_err(src, pos, f'Illegal character "{char!r}"') + pos += 1 + + +def parse_regex(src: str, pos: Pos, regex: "Pattern") -> Tuple[Pos, str]: + match = regex.match(src, pos) + if not match: + raise suffixed_err(src, pos, "Unexpected sequence") + return match.end(), match.group() + + +def parse_value( # noqa: C901 + src: str, pos: Pos, parse_float: ParseFloat +) -> Tuple[Pos, Any]: + try: + char: Optional[str] = src[pos] + except IndexError: + char = None + + # Basic strings + if char == '"': + if src[pos + 1 : pos + 3] == '""': + return parse_multiline_str(src, pos, literal=False) + return parse_one_line_basic_str(src, pos) + + # Literal strings + if char == "'": + if src[pos + 1 : pos + 3] == "''": + return parse_multiline_str(src, pos, literal=True) + return parse_literal_str(src, pos) + + # Booleans + if char == "t": + if src[pos + 1 : pos + 4] == "rue": + return pos + 4, True + if char == "f": + if src[pos + 1 : pos + 5] == "alse": + return pos + 5, False + + # Dates and times + datetime_match = RE_DATETIME.match(src, pos) + if datetime_match: + try: + datetime_obj = match_to_datetime(datetime_match) + except ValueError: + raise suffixed_err(src, pos, "Invalid date or datetime") + return datetime_match.end(), datetime_obj + localtime_match = RE_LOCALTIME.match(src, pos) + if localtime_match: + return localtime_match.end(), match_to_localtime(localtime_match) + + # Non-decimal integers + if char == "0": + second_char = src[pos + 1 : pos + 2] + if second_char == "x": + pos, hex_str = parse_regex(src, pos + 2, RE_HEX) + return pos, int(hex_str, 16) + if second_char == "o": + pos, oct_str = parse_regex(src, pos + 2, RE_OCT) + return pos, int(oct_str, 8) + if second_char == "b": + pos, bin_str = parse_regex(src, pos + 2, RE_BIN) + return pos, int(bin_str, 2) + + # Decimal integers and "normal" floats. + # The regex will greedily match any type starting with a decimal + # char, so needs to be located after handling of non-decimal ints, + # and dates and times. + number_match = RE_NUMBER.match(src, pos) + if number_match: + return number_match.end(), match_to_number(number_match, parse_float) + + # Arrays + if char == "[": + return parse_array(src, pos, parse_float) + + # Inline tables + if char == "{": + return parse_inline_table(src, pos, parse_float) + + # Special floats + first_three = src[pos : pos + 3] + if first_three in {"inf", "nan"}: + return pos + 3, parse_float(first_three) + first_four = src[pos : pos + 4] + if first_four in {"-inf", "+inf", "-nan", "+nan"}: + return pos + 4, parse_float(first_four) + + raise suffixed_err(src, pos, "Invalid value") + + +def suffixed_err(src: str, pos: Pos, msg: str) -> TOMLDecodeError: + """Return a `TOMLDecodeError` where error message is suffixed with + coordinates in source.""" + + def coord_repr(src: str, pos: Pos) -> str: + if pos >= len(src): + return "end of document" + line = src.count("\n", 0, pos) + 1 + if line == 1: + column = pos + 1 + else: + column = pos - src.rindex("\n", 0, pos) + return f"line {line}, column {column}" + + return TOMLDecodeError(f"{msg} (at {coord_repr(src, pos)})") + + +def is_unicode_scalar_value(codepoint: int) -> bool: + return (0 <= codepoint <= 55295) or (57344 <= codepoint <= 1114111) diff --git a/pipenv/patched/notpip/_vendor/tomli/_re.py b/pipenv/patched/notpip/_vendor/tomli/_re.py new file mode 100644 index 00000000..a2ad4417 --- /dev/null +++ b/pipenv/patched/notpip/_vendor/tomli/_re.py @@ -0,0 +1,83 @@ +from datetime import date, datetime, time, timedelta, timezone, tzinfo +import re +from typing import TYPE_CHECKING, Any, Optional, Union + +if TYPE_CHECKING: + from re import Match + + from pipenv.patched.notpip._vendor.tomli._parser import ParseFloat + +# E.g. +# - 00:32:00.999999 +# - 00:32:00 +_TIME_RE_STR = r"([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])(\.[0-9]+)?" + +RE_HEX = re.compile(r"[0-9A-Fa-f](?:_?[0-9A-Fa-f])*") +RE_BIN = re.compile(r"[01](?:_?[01])*") +RE_OCT = re.compile(r"[0-7](?:_?[0-7])*") +RE_NUMBER = re.compile( + r"[+-]?(?:0|[1-9](?:_?[0-9])*)" # integer + + r"(?:\.[0-9](?:_?[0-9])*)?" # optional fractional part + + r"(?:[eE][+-]?[0-9](?:_?[0-9])*)?" # optional exponent part +) +RE_LOCALTIME = re.compile(_TIME_RE_STR) +RE_DATETIME = re.compile( + r"([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|1[0-9]|2[0-9]|3[01])" # date, e.g. 1988-10-27 + + r"(?:" + + r"[T ]" + + _TIME_RE_STR + + r"(?:(Z)|([+-])([01][0-9]|2[0-3]):([0-5][0-9]))?" # time offset + + r")?" +) + + +def match_to_datetime(match: "Match") -> Union[datetime, date]: + """Convert a `RE_DATETIME` match to `datetime.datetime` or `datetime.date`. + + Raises ValueError if the match does not correspond to a valid date + or datetime. + """ + ( + year_str, + month_str, + day_str, + hour_str, + minute_str, + sec_str, + micros_str, + zulu_time, + offset_dir_str, + offset_hour_str, + offset_minute_str, + ) = match.groups() + year, month, day = int(year_str), int(month_str), int(day_str) + if hour_str is None: + return date(year, month, day) + hour, minute, sec = int(hour_str), int(minute_str), int(sec_str) + micros = int(micros_str[1:].ljust(6, "0")[:6]) if micros_str else 0 + if offset_dir_str: + offset_dir = 1 if offset_dir_str == "+" else -1 + tz: Optional[tzinfo] = timezone( + timedelta( + hours=offset_dir * int(offset_hour_str), + minutes=offset_dir * int(offset_minute_str), + ) + ) + elif zulu_time: + tz = timezone.utc + else: # local date-time + tz = None + return datetime(year, month, day, hour, minute, sec, micros, tzinfo=tz) + + +def match_to_localtime(match: "Match") -> time: + hour_str, minute_str, sec_str, micros_str = match.groups() + micros = int(micros_str[1:].ljust(6, "0")[:6]) if micros_str else 0 + return time(int(hour_str), int(minute_str), int(sec_str), micros) + + +def match_to_number(match: "Match", parse_float: "ParseFloat") -> Any: + match_str = match.group() + if "." in match_str or "e" in match_str or "E" in match_str: + return parse_float(match_str) + return int(match_str) diff --git a/pipenv/patched/notpip/_vendor/urllib3/LICENSE.txt b/pipenv/patched/notpip/_vendor/urllib3/LICENSE.txt index c89cf27b..429a1767 100644 --- a/pipenv/patched/notpip/_vendor/urllib3/LICENSE.txt +++ b/pipenv/patched/notpip/_vendor/urllib3/LICENSE.txt @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2008-2019 Andrey Petrov and contributors (see CONTRIBUTORS.txt) +Copyright (c) 2008-2020 Andrey Petrov and contributors (see CONTRIBUTORS.txt) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pipenv/patched/notpip/_vendor/urllib3/__init__.py b/pipenv/patched/notpip/_vendor/urllib3/__init__.py index 148a9c31..fe86b59d 100644 --- a/pipenv/patched/notpip/_vendor/urllib3/__init__.py +++ b/pipenv/patched/notpip/_vendor/urllib3/__init__.py @@ -1,49 +1,43 @@ """ -urllib3 - Thread-safe connection pooling and re-using. +Python HTTP library with thread-safe connection pooling, file post support, user friendly, and more """ - from __future__ import absolute_import -import warnings -from .connectionpool import ( - HTTPConnectionPool, - HTTPSConnectionPool, - connection_from_url -) +# Set default logging handler to avoid "No handler found" warnings. +import logging +import warnings +from logging import NullHandler from . import exceptions +from ._version import __version__ +from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool, connection_from_url from .filepost import encode_multipart_formdata from .poolmanager import PoolManager, ProxyManager, proxy_from_url from .response import HTTPResponse from .util.request import make_headers -from .util.url import get_host -from .util.timeout import Timeout from .util.retry import Retry +from .util.timeout import Timeout +from .util.url import get_host - -# Set default logging handler to avoid "No handler found" warnings. -import logging -from logging import NullHandler - -__author__ = 'Andrey Petrov (andrey.petrov@shazow.net)' -__license__ = 'MIT' -__version__ = '1.24.1' +__author__ = "Andrey Petrov (andrey.petrov@shazow.net)" +__license__ = "MIT" +__version__ = __version__ __all__ = ( - 'HTTPConnectionPool', - 'HTTPSConnectionPool', - 'PoolManager', - 'ProxyManager', - 'HTTPResponse', - 'Retry', - 'Timeout', - 'add_stderr_logger', - 'connection_from_url', - 'disable_warnings', - 'encode_multipart_formdata', - 'get_host', - 'make_headers', - 'proxy_from_url', + "HTTPConnectionPool", + "HTTPSConnectionPool", + "PoolManager", + "ProxyManager", + "HTTPResponse", + "Retry", + "Timeout", + "add_stderr_logger", + "connection_from_url", + "disable_warnings", + "encode_multipart_formdata", + "get_host", + "make_headers", + "proxy_from_url", ) logging.getLogger(__name__).addHandler(NullHandler()) @@ -60,10 +54,10 @@ def add_stderr_logger(level=logging.DEBUG): # even if urllib3 is vendored within another package. logger = logging.getLogger(__name__) handler = logging.StreamHandler() - handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s %(message)s')) + handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s")) logger.addHandler(handler) logger.setLevel(level) - logger.debug('Added a stderr logging handler to logger: %s', __name__) + logger.debug("Added a stderr logging handler to logger: %s", __name__) return handler @@ -75,18 +69,17 @@ del NullHandler # shouldn't be: otherwise, it's very hard for users to use most Python # mechanisms to silence them. # SecurityWarning's always go off by default. -warnings.simplefilter('always', exceptions.SecurityWarning, append=True) +warnings.simplefilter("always", exceptions.SecurityWarning, append=True) # SubjectAltNameWarning's should go off once per host -warnings.simplefilter('default', exceptions.SubjectAltNameWarning, append=True) +warnings.simplefilter("default", exceptions.SubjectAltNameWarning, append=True) # InsecurePlatformWarning's don't vary between requests, so we keep it default. -warnings.simplefilter('default', exceptions.InsecurePlatformWarning, - append=True) +warnings.simplefilter("default", exceptions.InsecurePlatformWarning, append=True) # SNIMissingWarnings should go off only once. -warnings.simplefilter('default', exceptions.SNIMissingWarning, append=True) +warnings.simplefilter("default", exceptions.SNIMissingWarning, append=True) def disable_warnings(category=exceptions.HTTPWarning): """ Helper for quickly disabling all urllib3 warnings. """ - warnings.simplefilter('ignore', category) + warnings.simplefilter("ignore", category) diff --git a/pipenv/patched/notpip/_vendor/urllib3/_collections.py b/pipenv/patched/notpip/_vendor/urllib3/_collections.py index 34f23811..da9857e9 100644 --- a/pipenv/patched/notpip/_vendor/urllib3/_collections.py +++ b/pipenv/patched/notpip/_vendor/urllib3/_collections.py @@ -1,4 +1,5 @@ from __future__ import absolute_import + try: from collections.abc import Mapping, MutableMapping except ImportError: @@ -6,6 +7,7 @@ except ImportError: try: from threading import RLock except ImportError: # Platform-specific: No threads available + class RLock: def __enter__(self): pass @@ -15,11 +17,12 @@ except ImportError: # Platform-specific: No threads available from collections import OrderedDict + from .exceptions import InvalidHeader -from .packages.six import iterkeys, itervalues, PY3 +from .packages import six +from .packages.six import iterkeys, itervalues - -__all__ = ['RecentlyUsedContainer', 'HTTPHeaderDict'] +__all__ = ["RecentlyUsedContainer", "HTTPHeaderDict"] _Null = object() @@ -82,7 +85,9 @@ class RecentlyUsedContainer(MutableMapping): return len(self._container) def __iter__(self): - raise NotImplementedError('Iteration over this class is unlikely to be threadsafe.') + raise NotImplementedError( + "Iteration over this class is unlikely to be threadsafe." + ) def clear(self): with self.lock: @@ -150,7 +155,7 @@ class HTTPHeaderDict(MutableMapping): def __getitem__(self, key): val = self._container[key.lower()] - return ', '.join(val[1:]) + return ", ".join(val[1:]) def __delitem__(self, key): del self._container[key.lower()] @@ -159,17 +164,18 @@ class HTTPHeaderDict(MutableMapping): return key.lower() in self._container def __eq__(self, other): - if not isinstance(other, Mapping) and not hasattr(other, 'keys'): + if not isinstance(other, Mapping) and not hasattr(other, "keys"): return False if not isinstance(other, type(self)): other = type(self)(other) - return (dict((k.lower(), v) for k, v in self.itermerged()) == - dict((k.lower(), v) for k, v in other.itermerged())) + return dict((k.lower(), v) for k, v in self.itermerged()) == dict( + (k.lower(), v) for k, v in other.itermerged() + ) def __ne__(self, other): return not self.__eq__(other) - if not PY3: # Python 2 + if six.PY2: # Python 2 iterkeys = MutableMapping.iterkeys itervalues = MutableMapping.itervalues @@ -184,9 +190,9 @@ class HTTPHeaderDict(MutableMapping): yield vals[0] def pop(self, key, default=__marker): - '''D.pop(k[,d]) -> v, remove specified key and return the corresponding value. - If key is not found, d is returned if given, otherwise KeyError is raised. - ''' + """D.pop(k[,d]) -> v, remove specified key and return the corresponding value. + If key is not found, d is returned if given, otherwise KeyError is raised. + """ # Using the MutableMapping function directly fails due to the private marker. # Using ordinary dict.pop would expose the internal structures. # So let's reinvent the wheel. @@ -228,8 +234,10 @@ class HTTPHeaderDict(MutableMapping): with self.add instead of self.__setitem__ """ if len(args) > 1: - raise TypeError("extend() takes at most 1 positional " - "arguments ({0} given)".format(len(args))) + raise TypeError( + "extend() takes at most 1 positional " + "arguments ({0} given)".format(len(args)) + ) other = args[0] if len(args) >= 1 else () if isinstance(other, HTTPHeaderDict): @@ -295,7 +303,7 @@ class HTTPHeaderDict(MutableMapping): """Iterate over all headers, merging duplicate ones together.""" for key in self: val = self._container[key.lower()] - yield val[0], ', '.join(val[1:]) + yield val[0], ", ".join(val[1:]) def items(self): return list(self.iteritems()) @@ -306,7 +314,7 @@ class HTTPHeaderDict(MutableMapping): # python2.7 does not expose a proper API for exporting multiheaders # efficiently. This function re-reads raw lines from the message # object and extracts the multiheaders properly. - obs_fold_continued_leaders = (' ', '\t') + obs_fold_continued_leaders = (" ", "\t") headers = [] for line in message.headers: @@ -316,14 +324,14 @@ class HTTPHeaderDict(MutableMapping): # in RFC-7230 S3.2.4. This indicates a multiline header, but # there exists no previous header to which we can attach it. raise InvalidHeader( - 'Header continuation with no previous header: %s' % line + "Header continuation with no previous header: %s" % line ) else: key, value = headers[-1] - headers[-1] = (key, value + ' ' + line.strip()) + headers[-1] = (key, value + " " + line.strip()) continue - key, value = line.split(':', 1) + key, value = line.split(":", 1) headers.append((key, value.strip())) return cls(headers) diff --git a/pipenv/patched/notpip/_vendor/urllib3/_version.py b/pipenv/patched/notpip/_vendor/urllib3/_version.py new file mode 100644 index 00000000..e8ebee95 --- /dev/null +++ b/pipenv/patched/notpip/_vendor/urllib3/_version.py @@ -0,0 +1,2 @@ +# This file is protected via CODEOWNERS +__version__ = "1.26.6" diff --git a/pipenv/patched/notpip/_vendor/urllib3/connection.py b/pipenv/patched/notpip/_vendor/urllib3/connection.py index 02b36654..4c996659 100644 --- a/pipenv/patched/notpip/_vendor/urllib3/connection.py +++ b/pipenv/patched/notpip/_vendor/urllib3/connection.py @@ -1,16 +1,22 @@ from __future__ import absolute_import + import datetime import logging import os +import re import socket -from socket import error as SocketError, timeout as SocketTimeout import warnings +from socket import error as SocketError +from socket import timeout as SocketTimeout + from .packages import six from .packages.six.moves.http_client import HTTPConnection as _HTTPConnection from .packages.six.moves.http_client import HTTPException # noqa: F401 +from .util.proxy import create_proxy_ssl_context try: # Compiled with SSL? import ssl + BaseSSLError = ssl.SSLError except (ImportError, AttributeError): # Platform-specific: No SSL. ssl = None @@ -19,79 +25,80 @@ except (ImportError, AttributeError): # Platform-specific: No SSL. pass -try: # Python 3: - # Not a no-op, we're adding this to the namespace so it can be imported. +try: + # Python 3: not a no-op, we're adding this to the namespace so it can be imported. ConnectionError = ConnectionError -except NameError: # Python 2: +except NameError: + # Python 2 class ConnectionError(Exception): pass +try: # Python 3: + # Not a no-op, we're adding this to the namespace so it can be imported. + BrokenPipeError = BrokenPipeError +except NameError: # Python 2: + + class BrokenPipeError(Exception): + pass + + +from ._collections import HTTPHeaderDict # noqa (historical, removed in v2) +from ._version import __version__ from .exceptions import ( - NewConnectionError, ConnectTimeoutError, + NewConnectionError, SubjectAltNameWarning, SystemTimeWarning, ) -from .packages.ssl_match_hostname import match_hostname, CertificateError - +from .packages.ssl_match_hostname import CertificateError, match_hostname +from .util import SKIP_HEADER, SKIPPABLE_HEADERS, connection from .util.ssl_ import ( - resolve_cert_reqs, - resolve_ssl_version, assert_fingerprint, create_urllib3_context, - ssl_wrap_socket + resolve_cert_reqs, + resolve_ssl_version, + ssl_wrap_socket, ) - -from .util import connection - -from ._collections import HTTPHeaderDict - log = logging.getLogger(__name__) -port_by_scheme = { - 'http': 80, - 'https': 443, -} +port_by_scheme = {"http": 80, "https": 443} -# When updating RECENT_DATE, move it to within two years of the current date, -# and not less than 6 months ago. -# Example: if Today is 2018-01-01, then RECENT_DATE should be any date on or -# after 2016-01-01 (today - 2 years) AND before 2017-07-01 (today - 6 months) -RECENT_DATE = datetime.date(2017, 6, 30) +# When it comes time to update this value as a part of regular maintenance +# (ie test_recent_date is failing) update it to ~6 months before the current date. +RECENT_DATE = datetime.date(2020, 7, 1) - -class DummyConnection(object): - """Used to detect a failed ConnectionCls import.""" - pass +_CONTAINS_CONTROL_CHAR_RE = re.compile(r"[^-!#$%&'*+.^_`|~0-9a-zA-Z]") class HTTPConnection(_HTTPConnection, object): """ - Based on httplib.HTTPConnection but provides an extra constructor + Based on :class:`http.client.HTTPConnection` but provides an extra constructor backwards-compatibility layer between older and newer Pythons. Additional keyword parameters are used to configure attributes of the connection. Accepted parameters include: - - ``strict``: See the documentation on :class:`urllib3.connectionpool.HTTPConnectionPool` - - ``source_address``: Set the source address for the current connection. - - ``socket_options``: Set specific options on the underlying socket. If not specified, then - defaults are loaded from ``HTTPConnection.default_socket_options`` which includes disabling - Nagle's algorithm (sets TCP_NODELAY to 1) unless the connection is behind a proxy. + - ``strict``: See the documentation on :class:`urllib3.connectionpool.HTTPConnectionPool` + - ``source_address``: Set the source address for the current connection. + - ``socket_options``: Set specific options on the underlying socket. If not specified, then + defaults are loaded from ``HTTPConnection.default_socket_options`` which includes disabling + Nagle's algorithm (sets TCP_NODELAY to 1) unless the connection is behind a proxy. - For example, if you wish to enable TCP Keep Alive in addition to the defaults, - you might pass:: + For example, if you wish to enable TCP Keep Alive in addition to the defaults, + you might pass: - HTTPConnection.default_socket_options + [ - (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), - ] + .. code-block:: python - Or you may want to disable the defaults by passing an empty list (e.g., ``[]``). + HTTPConnection.default_socket_options + [ + (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), + ] + + Or you may want to disable the defaults by passing an empty list (e.g., ``[]``). """ - default_port = port_by_scheme['http'] + default_port = port_by_scheme["http"] #: Disable Nagle's algorithm by default. #: ``[(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)]`` @@ -101,15 +108,19 @@ class HTTPConnection(_HTTPConnection, object): is_verified = False def __init__(self, *args, **kw): - if six.PY3: # Python 3 - kw.pop('strict', None) + if not six.PY2: + kw.pop("strict", None) # Pre-set source_address. - self.source_address = kw.get('source_address') + self.source_address = kw.get("source_address") #: The socket options provided by the user. If no options are #: provided, we use the default options. - self.socket_options = kw.pop('socket_options', self.default_socket_options) + self.socket_options = kw.pop("socket_options", self.default_socket_options) + + # Proxy options provided by the user. + self.proxy = kw.pop("proxy", None) + self.proxy_config = kw.pop("proxy_config", None) _HTTPConnection.__init__(self, *args, **kw) @@ -130,7 +141,7 @@ class HTTPConnection(_HTTPConnection, object): those cases where it's appropriate (i.e., when doing DNS lookup to establish the actual TCP connection across which we're going to send HTTP requests). """ - return self._dns_host.rstrip('.') + return self._dns_host.rstrip(".") @host.setter def host(self, value): @@ -143,35 +154,43 @@ class HTTPConnection(_HTTPConnection, object): self._dns_host = value def _new_conn(self): - """ Establish a socket connection and set nodelay settings on it. + """Establish a socket connection and set nodelay settings on it. :return: New socket connection. """ extra_kw = {} if self.source_address: - extra_kw['source_address'] = self.source_address + extra_kw["source_address"] = self.source_address if self.socket_options: - extra_kw['socket_options'] = self.socket_options + extra_kw["socket_options"] = self.socket_options try: conn = connection.create_connection( - (self._dns_host, self.port), self.timeout, **extra_kw) + (self._dns_host, self.port), self.timeout, **extra_kw + ) - except SocketTimeout as e: + except SocketTimeout: raise ConnectTimeoutError( - self, "Connection to %s timed out. (connect timeout=%s)" % - (self.host, self.timeout)) + self, + "Connection to %s timed out. (connect timeout=%s)" + % (self.host, self.timeout), + ) except SocketError as e: raise NewConnectionError( - self, "Failed to establish a new connection: %s" % e) + self, "Failed to establish a new connection: %s" % e + ) return conn + def _is_using_tunnel(self): + # Google App Engine's httplib does not define _tunnel_host + return getattr(self, "_tunnel_host", None) + def _prepare_conn(self, conn): self.sock = conn - if self._tunnel_host: + if self._is_using_tunnel(): # TODO: Fix tunnel so it doesn't depend on self.sock state. self._tunnel() # Mark this connection as not reusable @@ -181,24 +200,57 @@ class HTTPConnection(_HTTPConnection, object): conn = self._new_conn() self._prepare_conn(conn) + def putrequest(self, method, url, *args, **kwargs): + """ """ + # Empty docstring because the indentation of CPython's implementation + # is broken but we don't want this method in our documentation. + match = _CONTAINS_CONTROL_CHAR_RE.search(method) + if match: + raise ValueError( + "Method cannot contain non-token characters %r (found at least %r)" + % (method, match.group()) + ) + + return _HTTPConnection.putrequest(self, method, url, *args, **kwargs) + + def putheader(self, header, *values): + """ """ + if not any(isinstance(v, str) and v == SKIP_HEADER for v in values): + _HTTPConnection.putheader(self, header, *values) + elif six.ensure_str(header.lower()) not in SKIPPABLE_HEADERS: + raise ValueError( + "urllib3.util.SKIP_HEADER only supports '%s'" + % ("', '".join(map(str.title, sorted(SKIPPABLE_HEADERS))),) + ) + + def request(self, method, url, body=None, headers=None): + if headers is None: + headers = {} + else: + # Avoid modifying the headers passed into .request() + headers = headers.copy() + if "user-agent" not in (six.ensure_str(k.lower()) for k in headers): + headers["User-Agent"] = _get_default_user_agent() + super(HTTPConnection, self).request(method, url, body=body, headers=headers) + def request_chunked(self, method, url, body=None, headers=None): """ Alternative to the common request method, which sends the body with chunked encoding and not as one block """ - headers = HTTPHeaderDict(headers if headers is not None else {}) - skip_accept_encoding = 'accept-encoding' in headers - skip_host = 'host' in headers + headers = headers or {} + header_keys = set([six.ensure_str(k.lower()) for k in headers]) + skip_accept_encoding = "accept-encoding" in header_keys + skip_host = "host" in header_keys self.putrequest( - method, - url, - skip_accept_encoding=skip_accept_encoding, - skip_host=skip_host + method, url, skip_accept_encoding=skip_accept_encoding, skip_host=skip_host ) + if "user-agent" not in header_keys: + self.putheader("User-Agent", _get_default_user_agent()) for header, value in headers.items(): self.putheader(header, value) - if 'transfer-encoding' not in headers: - self.putheader('Transfer-Encoding', 'chunked') + if "transfer-encoding" not in header_keys: + self.putheader("Transfer-Encoding", "chunked") self.endheaders() if body is not None: @@ -209,100 +261,106 @@ class HTTPConnection(_HTTPConnection, object): if not chunk: continue if not isinstance(chunk, bytes): - chunk = chunk.encode('utf8') + chunk = chunk.encode("utf8") len_str = hex(len(chunk))[2:] - self.send(len_str.encode('utf-8')) - self.send(b'\r\n') - self.send(chunk) - self.send(b'\r\n') + to_send = bytearray(len_str.encode()) + to_send += b"\r\n" + to_send += chunk + to_send += b"\r\n" + self.send(to_send) # After the if clause, to always have a closed body - self.send(b'0\r\n\r\n') + self.send(b"0\r\n\r\n") class HTTPSConnection(HTTPConnection): - default_port = port_by_scheme['https'] + """ + Many of the parameters to this constructor are passed to the underlying SSL + socket by means of :py:func:`urllib3.util.ssl_wrap_socket`. + """ + default_port = port_by_scheme["https"] + + cert_reqs = None + ca_certs = None + ca_cert_dir = None + ca_cert_data = None ssl_version = None + assert_fingerprint = None + tls_in_tls_required = False - def __init__(self, host, port=None, key_file=None, cert_file=None, - strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, - ssl_context=None, server_hostname=None, **kw): + def __init__( + self, + host, + port=None, + key_file=None, + cert_file=None, + key_password=None, + strict=None, + timeout=socket._GLOBAL_DEFAULT_TIMEOUT, + ssl_context=None, + server_hostname=None, + **kw + ): - HTTPConnection.__init__(self, host, port, strict=strict, - timeout=timeout, **kw) + HTTPConnection.__init__(self, host, port, strict=strict, timeout=timeout, **kw) self.key_file = key_file self.cert_file = cert_file + self.key_password = key_password self.ssl_context = ssl_context self.server_hostname = server_hostname # Required property for Google AppEngine 1.9.0 which otherwise causes # HTTPS requests to go out as HTTP. (See Issue #356) - self._protocol = 'https' + self._protocol = "https" - def connect(self): - conn = self._new_conn() - self._prepare_conn(conn) - - if self.ssl_context is None: - self.ssl_context = create_urllib3_context( - ssl_version=resolve_ssl_version(None), - cert_reqs=resolve_cert_reqs(None), - ) - - self.sock = ssl_wrap_socket( - sock=conn, - keyfile=self.key_file, - certfile=self.cert_file, - ssl_context=self.ssl_context, - server_hostname=self.server_hostname - ) - - -class VerifiedHTTPSConnection(HTTPSConnection): - """ - Based on httplib.HTTPSConnection but wraps the socket with - SSL certification. - """ - cert_reqs = None - ca_certs = None - ca_cert_dir = None - ssl_version = None - assert_fingerprint = None - - def set_cert(self, key_file=None, cert_file=None, - cert_reqs=None, ca_certs=None, - assert_hostname=None, assert_fingerprint=None, - ca_cert_dir=None): + def set_cert( + self, + key_file=None, + cert_file=None, + cert_reqs=None, + key_password=None, + ca_certs=None, + assert_hostname=None, + assert_fingerprint=None, + ca_cert_dir=None, + ca_cert_data=None, + ): """ This method should only be called once, before the connection is used. """ - # If cert_reqs is not provided, we can try to guess. If the user gave - # us a cert database, we assume they want to use it: otherwise, if - # they gave us an SSL Context object we should use whatever is set for - # it. + # If cert_reqs is not provided we'll assume CERT_REQUIRED unless we also + # have an SSLContext object in which case we'll use its verify_mode. if cert_reqs is None: - if ca_certs or ca_cert_dir: - cert_reqs = 'CERT_REQUIRED' - elif self.ssl_context is not None: + if self.ssl_context is not None: cert_reqs = self.ssl_context.verify_mode + else: + cert_reqs = resolve_cert_reqs(None) self.key_file = key_file self.cert_file = cert_file self.cert_reqs = cert_reqs + self.key_password = key_password self.assert_hostname = assert_hostname self.assert_fingerprint = assert_fingerprint self.ca_certs = ca_certs and os.path.expanduser(ca_certs) self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir) + self.ca_cert_data = ca_cert_data def connect(self): # Add certificate verification conn = self._new_conn() hostname = self.host + tls_in_tls = False + + if self._is_using_tunnel(): + if self.tls_in_tls_required: + conn = self._connect_tls_proxy(hostname, conn) + tls_in_tls = True - if self._tunnel_host: self.sock = conn + # Calls self._set_hostport(), so self.host is # self._tunnel_host below. self._tunnel() @@ -318,15 +376,19 @@ class VerifiedHTTPSConnection(HTTPSConnection): is_time_off = datetime.date.today() < RECENT_DATE if is_time_off: - warnings.warn(( - 'System time is way off (before {0}). This will probably ' - 'lead to SSL verification errors').format(RECENT_DATE), - SystemTimeWarning + warnings.warn( + ( + "System time is way off (before {0}). This will probably " + "lead to SSL verification errors" + ).format(RECENT_DATE), + SystemTimeWarning, ) # Wrap socket using verification with the root certs in # trusted_root_certs + default_ssl_context = False if self.ssl_context is None: + default_ssl_context = True self.ssl_context = create_urllib3_context( ssl_version=resolve_ssl_version(self.ssl_version), cert_reqs=resolve_cert_reqs(self.cert_reqs), @@ -334,38 +396,114 @@ class VerifiedHTTPSConnection(HTTPSConnection): context = self.ssl_context context.verify_mode = resolve_cert_reqs(self.cert_reqs) + + # Try to load OS default certs if none are given. + # Works well on Windows (requires Python3.4+) + if ( + not self.ca_certs + and not self.ca_cert_dir + and not self.ca_cert_data + and default_ssl_context + and hasattr(context, "load_default_certs") + ): + context.load_default_certs() + self.sock = ssl_wrap_socket( sock=conn, keyfile=self.key_file, certfile=self.cert_file, + key_password=self.key_password, ca_certs=self.ca_certs, ca_cert_dir=self.ca_cert_dir, + ca_cert_data=self.ca_cert_data, server_hostname=server_hostname, - ssl_context=context) + ssl_context=context, + tls_in_tls=tls_in_tls, + ) + + # If we're using all defaults and the connection + # is TLSv1 or TLSv1.1 we throw a DeprecationWarning + # for the host. + if ( + default_ssl_context + and self.ssl_version is None + and hasattr(self.sock, "version") + and self.sock.version() in {"TLSv1", "TLSv1.1"} + ): + warnings.warn( + "Negotiating TLSv1/TLSv1.1 by default is deprecated " + "and will be disabled in urllib3 v2.0.0. Connecting to " + "'%s' with '%s' can be enabled by explicitly opting-in " + "with 'ssl_version'" % (self.host, self.sock.version()), + DeprecationWarning, + ) if self.assert_fingerprint: - assert_fingerprint(self.sock.getpeercert(binary_form=True), - self.assert_fingerprint) - elif context.verify_mode != ssl.CERT_NONE \ - and not getattr(context, 'check_hostname', False) \ - and self.assert_hostname is not False: + assert_fingerprint( + self.sock.getpeercert(binary_form=True), self.assert_fingerprint + ) + elif ( + context.verify_mode != ssl.CERT_NONE + and not getattr(context, "check_hostname", False) + and self.assert_hostname is not False + ): # While urllib3 attempts to always turn off hostname matching from # the TLS library, this cannot always be done. So we check whether # the TLS Library still thinks it's matching hostnames. cert = self.sock.getpeercert() - if not cert.get('subjectAltName', ()): - warnings.warn(( - 'Certificate for {0} has no `subjectAltName`, falling back to check for a ' - '`commonName` for now. This feature is being removed by major browsers and ' - 'deprecated by RFC 2818. (See https://github.com/shazow/urllib3/issues/497 ' - 'for details.)'.format(hostname)), - SubjectAltNameWarning + if not cert.get("subjectAltName", ()): + warnings.warn( + ( + "Certificate for {0} has no `subjectAltName`, falling back to check for a " + "`commonName` for now. This feature is being removed by major browsers and " + "deprecated by RFC 2818. (See https://github.com/urllib3/urllib3/issues/497 " + "for details.)".format(hostname) + ), + SubjectAltNameWarning, ) _match_hostname(cert, self.assert_hostname or server_hostname) self.is_verified = ( - context.verify_mode == ssl.CERT_REQUIRED or - self.assert_fingerprint is not None + context.verify_mode == ssl.CERT_REQUIRED + or self.assert_fingerprint is not None + ) + + def _connect_tls_proxy(self, hostname, conn): + """ + Establish a TLS connection to the proxy using the provided SSL context. + """ + proxy_config = self.proxy_config + ssl_context = proxy_config.ssl_context + if ssl_context: + # If the user provided a proxy context, we assume CA and client + # certificates have already been set + return ssl_wrap_socket( + sock=conn, + server_hostname=hostname, + ssl_context=ssl_context, + ) + + ssl_context = create_proxy_ssl_context( + self.ssl_version, + self.cert_reqs, + self.ca_certs, + self.ca_cert_dir, + self.ca_cert_data, + ) + # By default urllib3's SSLContext disables `check_hostname` and uses + # a custom check. For proxies we're good with relying on the default + # verification. + ssl_context.check_hostname = True + + # If no cert was provided, use only the default options for server + # certificate validation + return ssl_wrap_socket( + sock=conn, + ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, + ca_cert_data=self.ca_cert_data, + server_hostname=hostname, + ssl_context=ssl_context, ) @@ -373,9 +511,10 @@ def _match_hostname(cert, asserted_hostname): try: match_hostname(cert, asserted_hostname) except CertificateError as e: - log.error( - 'Certificate did not match expected hostname: %s. ' - 'Certificate: %s', asserted_hostname, cert + log.warning( + "Certificate did not match expected hostname: %s. Certificate: %s", + asserted_hostname, + cert, ) # Add cert to exception and reraise so client code can inspect # the cert when catching the exception, if they want to @@ -383,9 +522,18 @@ def _match_hostname(cert, asserted_hostname): raise -if ssl: - # Make a copy for testing. - UnverifiedHTTPSConnection = HTTPSConnection - HTTPSConnection = VerifiedHTTPSConnection -else: - HTTPSConnection = DummyConnection +def _get_default_user_agent(): + return "python-urllib3/%s" % __version__ + + +class DummyConnection(object): + """Used to detect a failed ConnectionCls import.""" + + pass + + +if not ssl: + HTTPSConnection = DummyConnection # noqa: F811 + + +VerifiedHTTPSConnection = HTTPSConnection diff --git a/pipenv/patched/notpip/_vendor/urllib3/connectionpool.py b/pipenv/patched/notpip/_vendor/urllib3/connectionpool.py index f7a8f193..459bbe09 100644 --- a/pipenv/patched/notpip/_vendor/urllib3/connectionpool.py +++ b/pipenv/patched/notpip/_vendor/urllib3/connectionpool.py @@ -1,48 +1,53 @@ from __future__ import absolute_import + import errno import logging +import socket import sys import warnings +from socket import error as SocketError +from socket import timeout as SocketTimeout -from socket import error as SocketError, timeout as SocketTimeout -import socket - - +from .connection import ( + BaseSSLError, + BrokenPipeError, + DummyConnection, + HTTPConnection, + HTTPException, + HTTPSConnection, + VerifiedHTTPSConnection, + port_by_scheme, +) from .exceptions import ( ClosedPoolError, - ProtocolError, EmptyPoolError, HeaderParsingError, HostChangedError, + InsecureRequestWarning, LocationValueError, MaxRetryError, + NewConnectionError, + ProtocolError, ProxyError, ReadTimeoutError, SSLError, TimeoutError, - InsecureRequestWarning, - NewConnectionError, ) -from .packages.ssl_match_hostname import CertificateError from .packages import six from .packages.six.moves import queue -from .connection import ( - port_by_scheme, - DummyConnection, - HTTPConnection, HTTPSConnection, VerifiedHTTPSConnection, - HTTPException, BaseSSLError, -) +from .packages.ssl_match_hostname import CertificateError from .request import RequestMethods from .response import HTTPResponse - from .util.connection import is_connection_dropped +from .util.proxy import connection_requires_http_tunnel +from .util.queue import LifoQueue from .util.request import set_file_position from .util.response import assert_header_parsing from .util.retry import Retry from .util.timeout import Timeout -from .util.url import get_host, Url, NORMALIZABLE_SCHEMES -from .util.queue import LifoQueue - +from .util.url import Url, _encode_target +from .util.url import _normalize_host as normalize_host +from .util.url import get_host, parse_url xrange = six.moves.xrange @@ -56,6 +61,11 @@ class ConnectionPool(object): """ Base class for all connection pools, such as :class:`.HTTPConnectionPool` and :class:`.HTTPSConnectionPool`. + + .. note:: + ConnectionPool.urlopen() does not normalize or percent-encode target URIs + which is useful if your target server doesn't support percent-encoded + target URIs. """ scheme = None @@ -65,13 +75,12 @@ class ConnectionPool(object): if not host: raise LocationValueError("No host specified.") - self.host = _ipv6_host(host, self.scheme) + self.host = _normalize_host(host, scheme=self.scheme) self._proxy_host = host.lower() self.port = port def __str__(self): - return '%s(host=%r, port=%r)' % (type(self).__name__, - self.host, self.port) + return "%s(host=%r, port=%r)" % (type(self).__name__, self.host, self.port) def __enter__(self): return self @@ -98,16 +107,16 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): :param host: Host used for this HTTP Connection (e.g. "localhost"), passed into - :class:`httplib.HTTPConnection`. + :class:`http.client.HTTPConnection`. :param port: Port used for this HTTP Connection (None is equivalent to 80), passed - into :class:`httplib.HTTPConnection`. + into :class:`http.client.HTTPConnection`. :param strict: Causes BadStatusLine to be raised if the status line can't be parsed as a valid HTTP/1.0 or 1.1 status line, passed into - :class:`httplib.HTTPConnection`. + :class:`http.client.HTTPConnection`. .. note:: Only works in Python 2. This parameter is ignored in Python 3. @@ -141,26 +150,36 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): :param _proxy: Parsed proxy URL, should not be used directly, instead, see - :class:`urllib3.connectionpool.ProxyManager`" + :class:`urllib3.ProxyManager` :param _proxy_headers: A dictionary with proxy headers, should not be used directly, - instead, see :class:`urllib3.connectionpool.ProxyManager`" + instead, see :class:`urllib3.ProxyManager` :param \\**conn_kw: Additional parameters are used to create fresh :class:`urllib3.connection.HTTPConnection`, :class:`urllib3.connection.HTTPSConnection` instances. """ - scheme = 'http' + scheme = "http" ConnectionCls = HTTPConnection ResponseCls = HTTPResponse - def __init__(self, host, port=None, strict=False, - timeout=Timeout.DEFAULT_TIMEOUT, maxsize=1, block=False, - headers=None, retries=None, - _proxy=None, _proxy_headers=None, - **conn_kw): + def __init__( + self, + host, + port=None, + strict=False, + timeout=Timeout.DEFAULT_TIMEOUT, + maxsize=1, + block=False, + headers=None, + retries=None, + _proxy=None, + _proxy_headers=None, + _proxy_config=None, + **conn_kw + ): ConnectionPool.__init__(self, host, port) RequestMethods.__init__(self, headers) @@ -180,6 +199,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): self.proxy = _proxy self.proxy_headers = _proxy_headers or {} + self.proxy_config = _proxy_config # Fill the queue up so that doing get() on it will block properly for _ in xrange(maxsize): @@ -194,19 +214,30 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): # Enable Nagle's algorithm for proxies, to avoid packet fragmentation. # We cannot know if the user has added default socket options, so we cannot replace the # list. - self.conn_kw.setdefault('socket_options', []) + self.conn_kw.setdefault("socket_options", []) + + self.conn_kw["proxy"] = self.proxy + self.conn_kw["proxy_config"] = self.proxy_config def _new_conn(self): """ Return a fresh :class:`HTTPConnection`. """ self.num_connections += 1 - log.debug("Starting new HTTP connection (%d): %s:%s", - self.num_connections, self.host, self.port or "80") + log.debug( + "Starting new HTTP connection (%d): %s:%s", + self.num_connections, + self.host, + self.port or "80", + ) - conn = self.ConnectionCls(host=self.host, port=self.port, - timeout=self.timeout.connect_timeout, - strict=self.strict, **self.conn_kw) + conn = self.ConnectionCls( + host=self.host, + port=self.port, + timeout=self.timeout.connect_timeout, + strict=self.strict, + **self.conn_kw + ) return conn def _get_conn(self, timeout=None): @@ -230,18 +261,19 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): except queue.Empty: if self.block: - raise EmptyPoolError(self, - "Pool reached maximum size and no more " - "connections are allowed.") + raise EmptyPoolError( + self, + "Pool reached maximum size and no more connections are allowed.", + ) pass # Oh well, we'll create a new connection then # If this is a persistent connection, check if it got disconnected if conn and is_connection_dropped(conn): log.debug("Resetting dropped connection: %s", self.host) conn.close() - if getattr(conn, 'auto_open', 1) == 0: + if getattr(conn, "auto_open", 1) == 0: # This is a proxied connection that has been mutated by - # httplib._tunnel() and cannot be reused (since it would + # http.client._tunnel() and cannot be reused (since it would # attempt to bypass the proxy) conn = None @@ -269,9 +301,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): pass except queue.Full: # This should never happen if self.block == True - log.warning( - "Connection pool is full, discarding connection: %s", - self.host) + log.warning("Connection pool is full, discarding connection: %s", self.host) # Connection never got put back into the pool, close it. if conn: @@ -288,7 +318,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): pass def _get_timeout(self, timeout): - """ Helper that always returns a :class:`urllib3.util.Timeout` """ + """Helper that always returns a :class:`urllib3.util.Timeout`""" if timeout is _Default: return self.timeout.clone() @@ -303,21 +333,30 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): """Is the error actually a timeout? Will raise a ReadTimeout or pass""" if isinstance(err, SocketTimeout): - raise ReadTimeoutError(self, url, "Read timed out. (read timeout=%s)" % timeout_value) + raise ReadTimeoutError( + self, url, "Read timed out. (read timeout=%s)" % timeout_value + ) # See the above comment about EAGAIN in Python 3. In Python 2 we have # to specifically catch it and throw the timeout error - if hasattr(err, 'errno') and err.errno in _blocking_errnos: - raise ReadTimeoutError(self, url, "Read timed out. (read timeout=%s)" % timeout_value) + if hasattr(err, "errno") and err.errno in _blocking_errnos: + raise ReadTimeoutError( + self, url, "Read timed out. (read timeout=%s)" % timeout_value + ) # Catch possible read timeouts thrown as SSL errors. If not the # case, rethrow the original. We need to do this because of: # http://bugs.python.org/issue10272 - if 'timed out' in str(err) or 'did not complete (read)' in str(err): # Python < 2.7.4 - raise ReadTimeoutError(self, url, "Read timed out. (read timeout=%s)" % timeout_value) + if "timed out" in str(err) or "did not complete (read)" in str( + err + ): # Python < 2.7.4 + raise ReadTimeoutError( + self, url, "Read timed out. (read timeout=%s)" % timeout_value + ) - def _make_request(self, conn, method, url, timeout=_Default, chunked=False, - **httplib_request_kw): + def _make_request( + self, conn, method, url, timeout=_Default, chunked=False, **httplib_request_kw + ): """ Perform a request on a given urllib connection object taken from our pool. @@ -346,18 +385,36 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): self._raise_timeout(err=e, url=url, timeout_value=conn.timeout) raise - # conn.request() calls httplib.*.request, not the method in + # conn.request() calls http.client.*.request, not the method in # urllib3.request. It also calls makefile (recv) on the socket. - if chunked: - conn.request_chunked(method, url, **httplib_request_kw) - else: - conn.request(method, url, **httplib_request_kw) + try: + if chunked: + conn.request_chunked(method, url, **httplib_request_kw) + else: + conn.request(method, url, **httplib_request_kw) + + # We are swallowing BrokenPipeError (errno.EPIPE) since the server is + # legitimately able to close the connection after sending a valid response. + # With this behaviour, the received response is still readable. + except BrokenPipeError: + # Python 3 + pass + except IOError as e: + # Python 2 and macOS/Linux + # EPIPE and ESHUTDOWN are BrokenPipeError on Python 2, and EPROTOTYPE is needed on macOS + # https://erickt.github.io/blog/2014/11/19/adventures-in-debugging-a-potential-osx-kernel-bug/ + if e.errno not in { + errno.EPIPE, + errno.ESHUTDOWN, + errno.EPROTOTYPE, + }: + raise # Reset the timeout for the recv() on the socket read_timeout = timeout_obj.read_timeout # App Engine doesn't have a sock attr - if getattr(conn, 'sock', None): + if getattr(conn, "sock", None): # In Python 3 socket.py will catch EAGAIN and return None when you # try and read into the file pointer created by http.client, which # instead raises a BadStatusLine exception. Instead of catching @@ -365,7 +422,8 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): # timeouts, check for a zero timeout before making the request. if read_timeout == 0: raise ReadTimeoutError( - self, url, "Read timed out. (read timeout=%s)" % read_timeout) + self, url, "Read timed out. (read timeout=%s)" % read_timeout + ) if read_timeout is Timeout.DEFAULT_TIMEOUT: conn.sock.settimeout(socket.getdefaulttimeout()) else: # None or a value @@ -373,31 +431,45 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): # Receive the response from the server try: - try: # Python 2.7, use buffering of HTTP responses + try: + # Python 2.7, use buffering of HTTP responses httplib_response = conn.getresponse(buffering=True) - except TypeError: # Python 3 + except TypeError: + # Python 3 try: httplib_response = conn.getresponse() - except Exception as e: - # Remove the TypeError from the exception chain in Python 3; - # otherwise it looks like a programming error was the cause. + except BaseException as e: + # Remove the TypeError from the exception chain in + # Python 3 (including for exceptions like SystemExit). + # Otherwise it looks like a bug in the code. six.raise_from(e, None) except (SocketTimeout, BaseSSLError, SocketError) as e: self._raise_timeout(err=e, url=url, timeout_value=read_timeout) raise # AppEngine doesn't have a version attr. - http_version = getattr(conn, '_http_vsn_str', 'HTTP/?') - log.debug("%s://%s:%s \"%s %s %s\" %s %s", self.scheme, self.host, self.port, - method, url, http_version, httplib_response.status, - httplib_response.length) + http_version = getattr(conn, "_http_vsn_str", "HTTP/?") + log.debug( + '%s://%s:%s "%s %s %s" %s %s', + self.scheme, + self.host, + self.port, + method, + url, + http_version, + httplib_response.status, + httplib_response.length, + ) try: assert_header_parsing(httplib_response.msg) except (HeaderParsingError, TypeError) as hpe: # Platform-specific: Python 3 log.warning( - 'Failed to parse headers (url=%s): %s', - self._absolute_url(url), hpe, exc_info=True) + "Failed to parse headers (url=%s): %s", + self._absolute_url(url), + hpe, + exc_info=True, + ) return httplib_response @@ -427,13 +499,13 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): Check if the given ``url`` is a member of the same host as this connection pool. """ - if url.startswith('/'): + if url.startswith("/"): return True # TODO: Add optional support for socket.gethostbyname checking. scheme, host, port = get_host(url) - - host = _ipv6_host(host, self.scheme) + if host is not None: + host = _normalize_host(host, scheme=scheme) # Use explicit default port for comparison when none is given if self.port and not port: @@ -443,10 +515,22 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): return (scheme, host, port) == (self.scheme, self.host, self.port) - def urlopen(self, method, url, body=None, headers=None, retries=None, - redirect=True, assert_same_host=True, timeout=_Default, - pool_timeout=None, release_conn=None, chunked=False, - body_pos=None, **response_kw): + def urlopen( + self, + method, + url, + body=None, + headers=None, + retries=None, + redirect=True, + assert_same_host=True, + timeout=_Default, + pool_timeout=None, + release_conn=None, + chunked=False, + body_pos=None, + **response_kw + ): """ Get a connection from the pool and perform an HTTP request. This is the lowest level call for making a request, so you'll need to specify all @@ -467,10 +551,12 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): :param method: HTTP request method (such as GET, POST, PUT, etc.) + :param url: + The URL to perform the request on. + :param body: - Data to send in the request body (useful for creating - POST requests, see HTTPConnectionPool.post_url for - more convenience). + Data to send in the request body, either :class:`str`, :class:`bytes`, + an iterable of :class:`str`/:class:`bytes`, or a file-like object. :param headers: Dictionary of custom headers to send, such as User-Agent, @@ -500,7 +586,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): :param assert_same_host: If ``True``, will make sure that the host of the pool requests is - consistent else will raise HostChangedError. When False, you can + consistent else will raise HostChangedError. When ``False``, you can use the pool on an HTTP proxy and request foreign hosts. :param timeout: @@ -537,6 +623,10 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): Additional parameters are passed to :meth:`urllib3.response.HTTPResponse.from_httplib` """ + + parsed_url = parse_url(url) + destination_scheme = parsed_url.scheme + if headers is None: headers = self.headers @@ -544,12 +634,18 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): retries = Retry.from_int(retries, redirect=redirect, default=self.retries) if release_conn is None: - release_conn = response_kw.get('preload_content', True) + release_conn = response_kw.get("preload_content", True) # Check host if assert_same_host and not self.is_same_host(url): raise HostChangedError(self, url, retries) + # Ensure that the URL we're connecting to is properly encoded + if url.startswith("/"): + url = six.ensure_str(_encode_target(url)) + else: + url = six.ensure_str(parsed_url.url) + conn = None # Track whether `conn` needs to be released before @@ -560,13 +656,17 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): # # See issue #651 [1] for details. # - # [1] <https://github.com/shazow/urllib3/issues/651> + # [1] <https://github.com/urllib3/urllib3/issues/651> release_this_conn = release_conn - # Merge the proxy headers. Only do this in HTTP. We have to copy the - # headers dict so we can safely change it without those changes being - # reflected in anyone else's copy. - if self.scheme == 'http': + http_tunnel_required = connection_requires_http_tunnel( + self.proxy, self.proxy_config, destination_scheme + ) + + # Merge the proxy headers. Only done when not using HTTP CONNECT. We + # have to copy the headers dict so we can safely change it without those + # changes being reflected in anyone else's copy. + if not http_tunnel_required: headers = headers.copy() headers.update(self.proxy_headers) @@ -589,15 +689,22 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): conn.timeout = timeout_obj.connect_timeout - is_new_proxy_conn = self.proxy is not None and not getattr(conn, 'sock', None) - if is_new_proxy_conn: + is_new_proxy_conn = self.proxy is not None and not getattr( + conn, "sock", None + ) + if is_new_proxy_conn and http_tunnel_required: self._prepare_proxy(conn) # Make the request on the httplib connection object. - httplib_response = self._make_request(conn, method, url, - timeout=timeout_obj, - body=body, headers=headers, - chunked=chunked) + httplib_response = self._make_request( + conn, + method, + url, + timeout=timeout_obj, + body=body, + headers=headers, + chunked=chunked, + ) # If we're going to release the connection in ``finally:``, then # the response doesn't need to know about the connection. Otherwise @@ -606,36 +713,48 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): response_conn = conn if not release_conn else None # Pass method to Response for length checking - response_kw['request_method'] = method + response_kw["request_method"] = method # Import httplib's response into our own wrapper object - response = self.ResponseCls.from_httplib(httplib_response, - pool=self, - connection=response_conn, - retries=retries, - **response_kw) + response = self.ResponseCls.from_httplib( + httplib_response, + pool=self, + connection=response_conn, + retries=retries, + **response_kw + ) # Everything went great! clean_exit = True - except queue.Empty: - # Timed out by queue. - raise EmptyPoolError(self, "No pool connections are available.") + except EmptyPoolError: + # Didn't get a connection from the pool, no need to clean up + clean_exit = True + release_this_conn = False + raise - except (TimeoutError, HTTPException, SocketError, ProtocolError, - BaseSSLError, SSLError, CertificateError) as e: + except ( + TimeoutError, + HTTPException, + SocketError, + ProtocolError, + BaseSSLError, + SSLError, + CertificateError, + ) as e: # Discard the connection for these exceptions. It will be # replaced during the next _get_conn() call. clean_exit = False if isinstance(e, (BaseSSLError, CertificateError)): e = SSLError(e) elif isinstance(e, (SocketError, NewConnectionError)) and self.proxy: - e = ProxyError('Cannot connect to proxy.', e) + e = ProxyError("Cannot connect to proxy.", e) elif isinstance(e, (SocketError, HTTPException)): - e = ProtocolError('Connection aborted.', e) + e = ProtocolError("Connection aborted.", e) - retries = retries.increment(method, url, error=e, _pool=self, - _stacktrace=sys.exc_info()[2]) + retries = retries.increment( + method, url, error=e, _pool=self, _stacktrace=sys.exc_info()[2] + ) retries.sleep() # Keep track of the error for the retry warning. @@ -658,77 +777,87 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): if not conn: # Try again - log.warning("Retrying (%r) after connection " - "broken by '%r': %s", retries, err, url) - return self.urlopen(method, url, body, headers, retries, - redirect, assert_same_host, - timeout=timeout, pool_timeout=pool_timeout, - release_conn=release_conn, body_pos=body_pos, - **response_kw) - - def drain_and_release_conn(response): - try: - # discard any remaining response body, the connection will be - # released back to the pool once the entire response is read - response.read() - except (TimeoutError, HTTPException, SocketError, ProtocolError, - BaseSSLError, SSLError) as e: - pass + log.warning( + "Retrying (%r) after connection broken by '%r': %s", retries, err, url + ) + return self.urlopen( + method, + url, + body, + headers, + retries, + redirect, + assert_same_host, + timeout=timeout, + pool_timeout=pool_timeout, + release_conn=release_conn, + chunked=chunked, + body_pos=body_pos, + **response_kw + ) # Handle redirect? redirect_location = redirect and response.get_redirect_location() if redirect_location: if response.status == 303: - method = 'GET' + method = "GET" try: retries = retries.increment(method, url, response=response, _pool=self) except MaxRetryError: if retries.raise_on_redirect: - # Drain and release the connection for this response, since - # we're not returning it to be released manually. - drain_and_release_conn(response) + response.drain_conn() raise return response - # drain and return the connection to the pool before recursing - drain_and_release_conn(response) - + response.drain_conn() retries.sleep_for_retry(response) log.debug("Redirecting %s -> %s", url, redirect_location) return self.urlopen( - method, redirect_location, body, headers, - retries=retries, redirect=redirect, + method, + redirect_location, + body, + headers, + retries=retries, + redirect=redirect, assert_same_host=assert_same_host, - timeout=timeout, pool_timeout=pool_timeout, - release_conn=release_conn, body_pos=body_pos, - **response_kw) + timeout=timeout, + pool_timeout=pool_timeout, + release_conn=release_conn, + chunked=chunked, + body_pos=body_pos, + **response_kw + ) # Check if we should retry the HTTP response. - has_retry_after = bool(response.getheader('Retry-After')) + has_retry_after = bool(response.getheader("Retry-After")) if retries.is_retry(method, response.status, has_retry_after): try: retries = retries.increment(method, url, response=response, _pool=self) except MaxRetryError: if retries.raise_on_status: - # Drain and release the connection for this response, since - # we're not returning it to be released manually. - drain_and_release_conn(response) + response.drain_conn() raise return response - # drain and return the connection to the pool before recursing - drain_and_release_conn(response) - + response.drain_conn() retries.sleep(response) log.debug("Retry: %s", url) return self.urlopen( - method, url, body, headers, - retries=retries, redirect=redirect, + method, + url, + body, + headers, + retries=retries, + redirect=redirect, assert_same_host=assert_same_host, - timeout=timeout, pool_timeout=pool_timeout, + timeout=timeout, + pool_timeout=pool_timeout, release_conn=release_conn, - body_pos=body_pos, **response_kw) + chunked=chunked, + body_pos=body_pos, + **response_kw + ) return response @@ -737,42 +866,62 @@ class HTTPSConnectionPool(HTTPConnectionPool): """ Same as :class:`.HTTPConnectionPool`, but HTTPS. - When Python is compiled with the :mod:`ssl` module, then - :class:`.VerifiedHTTPSConnection` is used, which *can* verify certificates, - instead of :class:`.HTTPSConnection`. - - :class:`.VerifiedHTTPSConnection` uses one of ``assert_fingerprint``, + :class:`.HTTPSConnection` uses one of ``assert_fingerprint``, ``assert_hostname`` and ``host`` in this order to verify connections. If ``assert_hostname`` is False, no verification is done. The ``key_file``, ``cert_file``, ``cert_reqs``, ``ca_certs``, - ``ca_cert_dir``, and ``ssl_version`` are only used if :mod:`ssl` is - available and are fed into :meth:`urllib3.util.ssl_wrap_socket` to upgrade + ``ca_cert_dir``, ``ssl_version``, ``key_password`` are only used if :mod:`ssl` + is available and are fed into :meth:`urllib3.util.ssl_wrap_socket` to upgrade the connection socket into an SSL socket. """ - scheme = 'https' + scheme = "https" ConnectionCls = HTTPSConnection - def __init__(self, host, port=None, - strict=False, timeout=Timeout.DEFAULT_TIMEOUT, maxsize=1, - block=False, headers=None, retries=None, - _proxy=None, _proxy_headers=None, - key_file=None, cert_file=None, cert_reqs=None, - ca_certs=None, ssl_version=None, - assert_hostname=None, assert_fingerprint=None, - ca_cert_dir=None, **conn_kw): + def __init__( + self, + host, + port=None, + strict=False, + timeout=Timeout.DEFAULT_TIMEOUT, + maxsize=1, + block=False, + headers=None, + retries=None, + _proxy=None, + _proxy_headers=None, + key_file=None, + cert_file=None, + cert_reqs=None, + key_password=None, + ca_certs=None, + ssl_version=None, + assert_hostname=None, + assert_fingerprint=None, + ca_cert_dir=None, + **conn_kw + ): - HTTPConnectionPool.__init__(self, host, port, strict, timeout, maxsize, - block, headers, retries, _proxy, _proxy_headers, - **conn_kw) - - if ca_certs and cert_reqs is None: - cert_reqs = 'CERT_REQUIRED' + HTTPConnectionPool.__init__( + self, + host, + port, + strict, + timeout, + maxsize, + block, + headers, + retries, + _proxy, + _proxy_headers, + **conn_kw + ) self.key_file = key_file self.cert_file = cert_file self.cert_reqs = cert_reqs + self.key_password = key_password self.ca_certs = ca_certs self.ca_cert_dir = ca_cert_dir self.ssl_version = ssl_version @@ -786,35 +935,50 @@ class HTTPSConnectionPool(HTTPConnectionPool): """ if isinstance(conn, VerifiedHTTPSConnection): - conn.set_cert(key_file=self.key_file, - cert_file=self.cert_file, - cert_reqs=self.cert_reqs, - ca_certs=self.ca_certs, - ca_cert_dir=self.ca_cert_dir, - assert_hostname=self.assert_hostname, - assert_fingerprint=self.assert_fingerprint) + conn.set_cert( + key_file=self.key_file, + key_password=self.key_password, + cert_file=self.cert_file, + cert_reqs=self.cert_reqs, + ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, + assert_hostname=self.assert_hostname, + assert_fingerprint=self.assert_fingerprint, + ) conn.ssl_version = self.ssl_version return conn def _prepare_proxy(self, conn): """ - Establish tunnel connection early, because otherwise httplib - would improperly set Host: header to proxy's IP:port. + Establishes a tunnel connection through HTTP CONNECT. + + Tunnel connection is established early because otherwise httplib would + improperly set Host: header to proxy's IP:port. """ + conn.set_tunnel(self._proxy_host, self.port, self.proxy_headers) + + if self.proxy.scheme == "https": + conn.tls_in_tls_required = True + conn.connect() def _new_conn(self): """ - Return a fresh :class:`httplib.HTTPSConnection`. + Return a fresh :class:`http.client.HTTPSConnection`. """ self.num_connections += 1 - log.debug("Starting new HTTPS connection (%d): %s:%s", - self.num_connections, self.host, self.port or "443") + log.debug( + "Starting new HTTPS connection (%d): %s:%s", + self.num_connections, + self.host, + self.port or "443", + ) if not self.ConnectionCls or self.ConnectionCls is DummyConnection: - raise SSLError("Can't connect to HTTPS URL because the SSL " - "module is not available.") + raise SSLError( + "Can't connect to HTTPS URL because the SSL module is not available." + ) actual_host = self.host actual_port = self.port @@ -822,9 +986,16 @@ class HTTPSConnectionPool(HTTPConnectionPool): actual_host = self.proxy.host actual_port = self.proxy.port - conn = self.ConnectionCls(host=actual_host, port=actual_port, - timeout=self.timeout.connect_timeout, - strict=self.strict, **self.conn_kw) + conn = self.ConnectionCls( + host=actual_host, + port=actual_port, + timeout=self.timeout.connect_timeout, + strict=self.strict, + cert_file=self.cert_file, + key_file=self.key_file, + key_password=self.key_password, + **self.conn_kw + ) return self._prepare_conn(conn) @@ -835,16 +1006,19 @@ class HTTPSConnectionPool(HTTPConnectionPool): super(HTTPSConnectionPool, self)._validate_conn(conn) # Force connect early to allow us to validate the connection. - if not getattr(conn, 'sock', None): # AppEngine might not have `.sock` + if not getattr(conn, "sock", None): # AppEngine might not have `.sock` conn.connect() if not conn.is_verified: - warnings.warn(( - 'Unverified HTTPS request is being made. ' - 'Adding certificate verification is strongly advised. See: ' - 'https://urllib3.readthedocs.io/en/latest/advanced-usage.html' - '#ssl-warnings'), - InsecureRequestWarning) + warnings.warn( + ( + "Unverified HTTPS request is being made to host '%s'. " + "Adding certificate verification is strongly advised. See: " + "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" + "#ssl-warnings" % conn.host + ), + InsecureRequestWarning, + ) def connection_from_url(url, **kw): @@ -869,28 +1043,25 @@ def connection_from_url(url, **kw): """ scheme, host, port = get_host(url) port = port or port_by_scheme.get(scheme, 80) - if scheme == 'https': + if scheme == "https": return HTTPSConnectionPool(host, port=port, **kw) else: return HTTPConnectionPool(host, port=port, **kw) -def _ipv6_host(host, scheme): +def _normalize_host(host, scheme): """ - Process IPv6 address literals + Normalize hosts for comparisons and use with sockets. """ + host = normalize_host(host, scheme) + # httplib doesn't like it when we include brackets in IPv6 addresses # Specifically, if we include brackets but also pass the port then # httplib crazily doubles up the square brackets on the Host header. # Instead, we need to make sure we never pass ``None`` as the port. # However, for backward compatibility reasons we can't actually # *assert* that. See http://bugs.python.org/issue28539 - # - # Also if an IPv6 address literal has a zone identifier, the - # percent sign might be URIencoded, convert it back into ASCII - if host.startswith('[') and host.endswith(']'): - host = host.replace('%25', '%').strip('[]') - if scheme in NORMALIZABLE_SCHEMES: - host = host.lower() + if host.startswith("[") and host.endswith("]"): + host = host[1:-1] return host diff --git a/pipenv/patched/notpip/_vendor/urllib3/contrib/_appengine_environ.py b/pipenv/patched/notpip/_vendor/urllib3/contrib/_appengine_environ.py index f3e00942..8765b907 100644 --- a/pipenv/patched/notpip/_vendor/urllib3/contrib/_appengine_environ.py +++ b/pipenv/patched/notpip/_vendor/urllib3/contrib/_appengine_environ.py @@ -6,25 +6,31 @@ import os def is_appengine(): - return (is_local_appengine() or - is_prod_appengine() or - is_prod_appengine_mvms()) + return is_local_appengine() or is_prod_appengine() def is_appengine_sandbox(): - return is_appengine() and not is_prod_appengine_mvms() + """Reports if the app is running in the first generation sandbox. + + The second generation runtimes are technically still in a sandbox, but it + is much less restrictive, so generally you shouldn't need to check for it. + see https://cloud.google.com/appengine/docs/standard/runtimes + """ + return is_appengine() and os.environ["APPENGINE_RUNTIME"] == "python27" def is_local_appengine(): - return ('APPENGINE_RUNTIME' in os.environ and - 'Development/' in os.environ['SERVER_SOFTWARE']) + return "APPENGINE_RUNTIME" in os.environ and os.environ.get( + "SERVER_SOFTWARE", "" + ).startswith("Development/") def is_prod_appengine(): - return ('APPENGINE_RUNTIME' in os.environ and - 'Google App Engine/' in os.environ['SERVER_SOFTWARE'] and - not is_prod_appengine_mvms()) + return "APPENGINE_RUNTIME" in os.environ and os.environ.get( + "SERVER_SOFTWARE", "" + ).startswith("Google App Engine/") def is_prod_appengine_mvms(): - return os.environ.get('GAE_VM', False) == 'true' + """Deprecated.""" + return False diff --git a/pipenv/patched/notpip/_vendor/urllib3/contrib/_securetransport/bindings.py b/pipenv/patched/notpip/_vendor/urllib3/contrib/_securetransport/bindings.py index bcf41c02..dd1fc0a2 100644 --- a/pipenv/patched/notpip/_vendor/urllib3/contrib/_securetransport/bindings.py +++ b/pipenv/patched/notpip/_vendor/urllib3/contrib/_securetransport/bindings.py @@ -32,35 +32,60 @@ license and by oscrypto's: from __future__ import absolute_import import platform -from ctypes.util import find_library from ctypes import ( - c_void_p, c_int32, c_char_p, c_size_t, c_byte, c_uint32, c_ulong, c_long, - c_bool + CDLL, + CFUNCTYPE, + POINTER, + c_bool, + c_byte, + c_char_p, + c_int32, + c_long, + c_size_t, + c_uint32, + c_ulong, + c_void_p, ) -from ctypes import CDLL, POINTER, CFUNCTYPE +from ctypes.util import find_library +from pipenv.patched.notpip._vendor.urllib3.packages.six import raise_from -security_path = find_library('Security') -if not security_path: - raise ImportError('The library Security could not be found') - - -core_foundation_path = find_library('CoreFoundation') -if not core_foundation_path: - raise ImportError('The library CoreFoundation could not be found') - +if platform.system() != "Darwin": + raise ImportError("Only macOS is supported") version = platform.mac_ver()[0] -version_info = tuple(map(int, version.split('.'))) +version_info = tuple(map(int, version.split("."))) if version_info < (10, 8): raise OSError( - 'Only OS X 10.8 and newer are supported, not %s.%s' % ( - version_info[0], version_info[1] - ) + "Only OS X 10.8 and newer are supported, not %s.%s" + % (version_info[0], version_info[1]) ) -Security = CDLL(security_path, use_errno=True) -CoreFoundation = CDLL(core_foundation_path, use_errno=True) + +def load_cdll(name, macos10_16_path): + """Loads a CDLL by name, falling back to known path on 10.16+""" + try: + # Big Sur is technically 11 but we use 10.16 due to the Big Sur + # beta being labeled as 10.16. + if version_info >= (10, 16): + path = macos10_16_path + else: + path = find_library(name) + if not path: + raise OSError # Caught and reraised as 'ImportError' + return CDLL(path, use_errno=True) + except OSError: + raise_from(ImportError("The library %s failed to load" % name), None) + + +Security = load_cdll( + "Security", "/System/Library/Frameworks/Security.framework/Security" +) +CoreFoundation = load_cdll( + "CoreFoundation", + "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation", +) + Boolean = c_bool CFIndex = c_long @@ -129,27 +154,19 @@ try: Security.SecKeyGetTypeID.argtypes = [] Security.SecKeyGetTypeID.restype = CFTypeID - Security.SecCertificateCreateWithData.argtypes = [ - CFAllocatorRef, - CFDataRef - ] + Security.SecCertificateCreateWithData.argtypes = [CFAllocatorRef, CFDataRef] Security.SecCertificateCreateWithData.restype = SecCertificateRef - Security.SecCertificateCopyData.argtypes = [ - SecCertificateRef - ] + Security.SecCertificateCopyData.argtypes = [SecCertificateRef] Security.SecCertificateCopyData.restype = CFDataRef - Security.SecCopyErrorMessageString.argtypes = [ - OSStatus, - c_void_p - ] + Security.SecCopyErrorMessageString.argtypes = [OSStatus, c_void_p] Security.SecCopyErrorMessageString.restype = CFStringRef Security.SecIdentityCreateWithCertificate.argtypes = [ CFTypeRef, SecCertificateRef, - POINTER(SecIdentityRef) + POINTER(SecIdentityRef), ] Security.SecIdentityCreateWithCertificate.restype = OSStatus @@ -159,201 +176,133 @@ try: c_void_p, Boolean, c_void_p, - POINTER(SecKeychainRef) + POINTER(SecKeychainRef), ] Security.SecKeychainCreate.restype = OSStatus - Security.SecKeychainDelete.argtypes = [ - SecKeychainRef - ] + Security.SecKeychainDelete.argtypes = [SecKeychainRef] Security.SecKeychainDelete.restype = OSStatus Security.SecPKCS12Import.argtypes = [ CFDataRef, CFDictionaryRef, - POINTER(CFArrayRef) + POINTER(CFArrayRef), ] Security.SecPKCS12Import.restype = OSStatus SSLReadFunc = CFUNCTYPE(OSStatus, SSLConnectionRef, c_void_p, POINTER(c_size_t)) - SSLWriteFunc = CFUNCTYPE(OSStatus, SSLConnectionRef, POINTER(c_byte), POINTER(c_size_t)) + SSLWriteFunc = CFUNCTYPE( + OSStatus, SSLConnectionRef, POINTER(c_byte), POINTER(c_size_t) + ) - Security.SSLSetIOFuncs.argtypes = [ - SSLContextRef, - SSLReadFunc, - SSLWriteFunc - ] + Security.SSLSetIOFuncs.argtypes = [SSLContextRef, SSLReadFunc, SSLWriteFunc] Security.SSLSetIOFuncs.restype = OSStatus - Security.SSLSetPeerID.argtypes = [ - SSLContextRef, - c_char_p, - c_size_t - ] + Security.SSLSetPeerID.argtypes = [SSLContextRef, c_char_p, c_size_t] Security.SSLSetPeerID.restype = OSStatus - Security.SSLSetCertificate.argtypes = [ - SSLContextRef, - CFArrayRef - ] + Security.SSLSetCertificate.argtypes = [SSLContextRef, CFArrayRef] Security.SSLSetCertificate.restype = OSStatus - Security.SSLSetCertificateAuthorities.argtypes = [ - SSLContextRef, - CFTypeRef, - Boolean - ] + Security.SSLSetCertificateAuthorities.argtypes = [SSLContextRef, CFTypeRef, Boolean] Security.SSLSetCertificateAuthorities.restype = OSStatus - Security.SSLSetConnection.argtypes = [ - SSLContextRef, - SSLConnectionRef - ] + Security.SSLSetConnection.argtypes = [SSLContextRef, SSLConnectionRef] Security.SSLSetConnection.restype = OSStatus - Security.SSLSetPeerDomainName.argtypes = [ - SSLContextRef, - c_char_p, - c_size_t - ] + Security.SSLSetPeerDomainName.argtypes = [SSLContextRef, c_char_p, c_size_t] Security.SSLSetPeerDomainName.restype = OSStatus - Security.SSLHandshake.argtypes = [ - SSLContextRef - ] + Security.SSLHandshake.argtypes = [SSLContextRef] Security.SSLHandshake.restype = OSStatus - Security.SSLRead.argtypes = [ - SSLContextRef, - c_char_p, - c_size_t, - POINTER(c_size_t) - ] + Security.SSLRead.argtypes = [SSLContextRef, c_char_p, c_size_t, POINTER(c_size_t)] Security.SSLRead.restype = OSStatus - Security.SSLWrite.argtypes = [ - SSLContextRef, - c_char_p, - c_size_t, - POINTER(c_size_t) - ] + Security.SSLWrite.argtypes = [SSLContextRef, c_char_p, c_size_t, POINTER(c_size_t)] Security.SSLWrite.restype = OSStatus - Security.SSLClose.argtypes = [ - SSLContextRef - ] + Security.SSLClose.argtypes = [SSLContextRef] Security.SSLClose.restype = OSStatus - Security.SSLGetNumberSupportedCiphers.argtypes = [ - SSLContextRef, - POINTER(c_size_t) - ] + Security.SSLGetNumberSupportedCiphers.argtypes = [SSLContextRef, POINTER(c_size_t)] Security.SSLGetNumberSupportedCiphers.restype = OSStatus Security.SSLGetSupportedCiphers.argtypes = [ SSLContextRef, POINTER(SSLCipherSuite), - POINTER(c_size_t) + POINTER(c_size_t), ] Security.SSLGetSupportedCiphers.restype = OSStatus Security.SSLSetEnabledCiphers.argtypes = [ SSLContextRef, POINTER(SSLCipherSuite), - c_size_t + c_size_t, ] Security.SSLSetEnabledCiphers.restype = OSStatus - Security.SSLGetNumberEnabledCiphers.argtype = [ - SSLContextRef, - POINTER(c_size_t) - ] + Security.SSLGetNumberEnabledCiphers.argtype = [SSLContextRef, POINTER(c_size_t)] Security.SSLGetNumberEnabledCiphers.restype = OSStatus Security.SSLGetEnabledCiphers.argtypes = [ SSLContextRef, POINTER(SSLCipherSuite), - POINTER(c_size_t) + POINTER(c_size_t), ] Security.SSLGetEnabledCiphers.restype = OSStatus - Security.SSLGetNegotiatedCipher.argtypes = [ - SSLContextRef, - POINTER(SSLCipherSuite) - ] + Security.SSLGetNegotiatedCipher.argtypes = [SSLContextRef, POINTER(SSLCipherSuite)] Security.SSLGetNegotiatedCipher.restype = OSStatus Security.SSLGetNegotiatedProtocolVersion.argtypes = [ SSLContextRef, - POINTER(SSLProtocol) + POINTER(SSLProtocol), ] Security.SSLGetNegotiatedProtocolVersion.restype = OSStatus - Security.SSLCopyPeerTrust.argtypes = [ - SSLContextRef, - POINTER(SecTrustRef) - ] + Security.SSLCopyPeerTrust.argtypes = [SSLContextRef, POINTER(SecTrustRef)] Security.SSLCopyPeerTrust.restype = OSStatus - Security.SecTrustSetAnchorCertificates.argtypes = [ - SecTrustRef, - CFArrayRef - ] + Security.SecTrustSetAnchorCertificates.argtypes = [SecTrustRef, CFArrayRef] Security.SecTrustSetAnchorCertificates.restype = OSStatus - Security.SecTrustSetAnchorCertificatesOnly.argstypes = [ - SecTrustRef, - Boolean - ] + Security.SecTrustSetAnchorCertificatesOnly.argstypes = [SecTrustRef, Boolean] Security.SecTrustSetAnchorCertificatesOnly.restype = OSStatus - Security.SecTrustEvaluate.argtypes = [ - SecTrustRef, - POINTER(SecTrustResultType) - ] + Security.SecTrustEvaluate.argtypes = [SecTrustRef, POINTER(SecTrustResultType)] Security.SecTrustEvaluate.restype = OSStatus - Security.SecTrustGetCertificateCount.argtypes = [ - SecTrustRef - ] + Security.SecTrustGetCertificateCount.argtypes = [SecTrustRef] Security.SecTrustGetCertificateCount.restype = CFIndex - Security.SecTrustGetCertificateAtIndex.argtypes = [ - SecTrustRef, - CFIndex - ] + Security.SecTrustGetCertificateAtIndex.argtypes = [SecTrustRef, CFIndex] Security.SecTrustGetCertificateAtIndex.restype = SecCertificateRef Security.SSLCreateContext.argtypes = [ CFAllocatorRef, SSLProtocolSide, - SSLConnectionType + SSLConnectionType, ] Security.SSLCreateContext.restype = SSLContextRef - Security.SSLSetSessionOption.argtypes = [ - SSLContextRef, - SSLSessionOption, - Boolean - ] + Security.SSLSetSessionOption.argtypes = [SSLContextRef, SSLSessionOption, Boolean] Security.SSLSetSessionOption.restype = OSStatus - Security.SSLSetProtocolVersionMin.argtypes = [ - SSLContextRef, - SSLProtocol - ] + Security.SSLSetProtocolVersionMin.argtypes = [SSLContextRef, SSLProtocol] Security.SSLSetProtocolVersionMin.restype = OSStatus - Security.SSLSetProtocolVersionMax.argtypes = [ - SSLContextRef, - SSLProtocol - ] + Security.SSLSetProtocolVersionMax.argtypes = [SSLContextRef, SSLProtocol] Security.SSLSetProtocolVersionMax.restype = OSStatus - Security.SecCopyErrorMessageString.argtypes = [ - OSStatus, - c_void_p - ] + try: + Security.SSLSetALPNProtocols.argtypes = [SSLContextRef, CFArrayRef] + Security.SSLSetALPNProtocols.restype = OSStatus + except AttributeError: + # Supported only in 10.12+ + pass + + Security.SecCopyErrorMessageString.argtypes = [OSStatus, c_void_p] Security.SecCopyErrorMessageString.restype = CFStringRef Security.SSLReadFunc = SSLReadFunc @@ -369,64 +318,47 @@ try: Security.OSStatus = OSStatus Security.kSecImportExportPassphrase = CFStringRef.in_dll( - Security, 'kSecImportExportPassphrase' + Security, "kSecImportExportPassphrase" ) Security.kSecImportItemIdentity = CFStringRef.in_dll( - Security, 'kSecImportItemIdentity' + Security, "kSecImportItemIdentity" ) # CoreFoundation time! - CoreFoundation.CFRetain.argtypes = [ - CFTypeRef - ] + CoreFoundation.CFRetain.argtypes = [CFTypeRef] CoreFoundation.CFRetain.restype = CFTypeRef - CoreFoundation.CFRelease.argtypes = [ - CFTypeRef - ] + CoreFoundation.CFRelease.argtypes = [CFTypeRef] CoreFoundation.CFRelease.restype = None - CoreFoundation.CFGetTypeID.argtypes = [ - CFTypeRef - ] + CoreFoundation.CFGetTypeID.argtypes = [CFTypeRef] CoreFoundation.CFGetTypeID.restype = CFTypeID CoreFoundation.CFStringCreateWithCString.argtypes = [ CFAllocatorRef, c_char_p, - CFStringEncoding + CFStringEncoding, ] CoreFoundation.CFStringCreateWithCString.restype = CFStringRef - CoreFoundation.CFStringGetCStringPtr.argtypes = [ - CFStringRef, - CFStringEncoding - ] + CoreFoundation.CFStringGetCStringPtr.argtypes = [CFStringRef, CFStringEncoding] CoreFoundation.CFStringGetCStringPtr.restype = c_char_p CoreFoundation.CFStringGetCString.argtypes = [ CFStringRef, c_char_p, CFIndex, - CFStringEncoding + CFStringEncoding, ] CoreFoundation.CFStringGetCString.restype = c_bool - CoreFoundation.CFDataCreate.argtypes = [ - CFAllocatorRef, - c_char_p, - CFIndex - ] + CoreFoundation.CFDataCreate.argtypes = [CFAllocatorRef, c_char_p, CFIndex] CoreFoundation.CFDataCreate.restype = CFDataRef - CoreFoundation.CFDataGetLength.argtypes = [ - CFDataRef - ] + CoreFoundation.CFDataGetLength.argtypes = [CFDataRef] CoreFoundation.CFDataGetLength.restype = CFIndex - CoreFoundation.CFDataGetBytePtr.argtypes = [ - CFDataRef - ] + CoreFoundation.CFDataGetBytePtr.argtypes = [CFDataRef] CoreFoundation.CFDataGetBytePtr.restype = c_void_p CoreFoundation.CFDictionaryCreate.argtypes = [ @@ -435,14 +367,11 @@ try: POINTER(CFTypeRef), CFIndex, CFDictionaryKeyCallBacks, - CFDictionaryValueCallBacks + CFDictionaryValueCallBacks, ] CoreFoundation.CFDictionaryCreate.restype = CFDictionaryRef - CoreFoundation.CFDictionaryGetValue.argtypes = [ - CFDictionaryRef, - CFTypeRef - ] + CoreFoundation.CFDictionaryGetValue.argtypes = [CFDictionaryRef, CFTypeRef] CoreFoundation.CFDictionaryGetValue.restype = CFTypeRef CoreFoundation.CFArrayCreate.argtypes = [ @@ -456,36 +385,30 @@ try: CoreFoundation.CFArrayCreateMutable.argtypes = [ CFAllocatorRef, CFIndex, - CFArrayCallBacks + CFArrayCallBacks, ] CoreFoundation.CFArrayCreateMutable.restype = CFMutableArrayRef - CoreFoundation.CFArrayAppendValue.argtypes = [ - CFMutableArrayRef, - c_void_p - ] + CoreFoundation.CFArrayAppendValue.argtypes = [CFMutableArrayRef, c_void_p] CoreFoundation.CFArrayAppendValue.restype = None - CoreFoundation.CFArrayGetCount.argtypes = [ - CFArrayRef - ] + CoreFoundation.CFArrayGetCount.argtypes = [CFArrayRef] CoreFoundation.CFArrayGetCount.restype = CFIndex - CoreFoundation.CFArrayGetValueAtIndex.argtypes = [ - CFArrayRef, - CFIndex - ] + CoreFoundation.CFArrayGetValueAtIndex.argtypes = [CFArrayRef, CFIndex] CoreFoundation.CFArrayGetValueAtIndex.restype = c_void_p CoreFoundation.kCFAllocatorDefault = CFAllocatorRef.in_dll( - CoreFoundation, 'kCFAllocatorDefault' + CoreFoundation, "kCFAllocatorDefault" + ) + CoreFoundation.kCFTypeArrayCallBacks = c_void_p.in_dll( + CoreFoundation, "kCFTypeArrayCallBacks" ) - CoreFoundation.kCFTypeArrayCallBacks = c_void_p.in_dll(CoreFoundation, 'kCFTypeArrayCallBacks') CoreFoundation.kCFTypeDictionaryKeyCallBacks = c_void_p.in_dll( - CoreFoundation, 'kCFTypeDictionaryKeyCallBacks' + CoreFoundation, "kCFTypeDictionaryKeyCallBacks" ) CoreFoundation.kCFTypeDictionaryValueCallBacks = c_void_p.in_dll( - CoreFoundation, 'kCFTypeDictionaryValueCallBacks' + CoreFoundation, "kCFTypeDictionaryValueCallBacks" ) CoreFoundation.CFTypeRef = CFTypeRef @@ -494,7 +417,7 @@ try: CoreFoundation.CFDictionaryRef = CFDictionaryRef except (AttributeError): - raise ImportError('Error initializing ctypes') + raise ImportError("Error initializing ctypes") class CFConst(object): @@ -502,6 +425,7 @@ class CFConst(object): A class object that acts as essentially a namespace for CoreFoundation constants. """ + kCFStringEncodingUTF8 = CFStringEncoding(0x08000100) @@ -509,6 +433,7 @@ class SecurityConst(object): """ A class object that acts as essentially a namespace for Security constants. """ + kSSLSessionOptionBreakOnServerAuth = 0 kSSLProtocol2 = 1 @@ -516,6 +441,9 @@ class SecurityConst(object): kTLSProtocol1 = 4 kTLSProtocol11 = 7 kTLSProtocol12 = 8 + # SecureTransport does not support TLS 1.3 even if there's a constant for it + kTLSProtocol13 = 10 + kTLSProtocolMaxSupported = 999 kSSLClientSide = 1 kSSLStreamType = 0 @@ -558,30 +486,27 @@ class SecurityConst(object): errSecInvalidTrustSettings = -25262 # Cipher suites. We only pick the ones our default cipher string allows. + # Source: https://developer.apple.com/documentation/security/1550981-ssl_cipher_suite_values TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02C TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = 0xC030 TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02B TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xC02F - TLS_DHE_DSS_WITH_AES_256_GCM_SHA384 = 0x00A3 + TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCA9 + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCA8 TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 = 0x009F - TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 = 0x00A2 TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 = 0x009E TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC024 TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 = 0xC028 TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = 0xC00A TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xC014 TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 = 0x006B - TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 = 0x006A TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x0039 - TLS_DHE_DSS_WITH_AES_256_CBC_SHA = 0x0038 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC023 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 = 0xC027 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = 0xC009 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = 0xC013 TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 = 0x0067 - TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 = 0x0040 TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033 - TLS_DHE_DSS_WITH_AES_128_CBC_SHA = 0x0032 TLS_RSA_WITH_AES_256_GCM_SHA384 = 0x009D TLS_RSA_WITH_AES_128_GCM_SHA256 = 0x009C TLS_RSA_WITH_AES_256_CBC_SHA256 = 0x003D @@ -590,4 +515,5 @@ class SecurityConst(object): TLS_RSA_WITH_AES_128_CBC_SHA = 0x002F TLS_AES_128_GCM_SHA256 = 0x1301 TLS_AES_256_GCM_SHA384 = 0x1302 - TLS_CHACHA20_POLY1305_SHA256 = 0x1303 + TLS_AES_128_CCM_8_SHA256 = 0x1305 + TLS_AES_128_CCM_SHA256 = 0x1304 diff --git a/pipenv/patched/notpip/_vendor/urllib3/contrib/_securetransport/low_level.py b/pipenv/patched/notpip/_vendor/urllib3/contrib/_securetransport/low_level.py index b13cd9e7..ed812019 100644 --- a/pipenv/patched/notpip/_vendor/urllib3/contrib/_securetransport/low_level.py +++ b/pipenv/patched/notpip/_vendor/urllib3/contrib/_securetransport/low_level.py @@ -10,13 +10,13 @@ appropriate and useful assistance to the higher-level code. import base64 import ctypes import itertools -import re import os +import re import ssl +import struct import tempfile -from .bindings import Security, CoreFoundation, CFConst - +from .bindings import CFConst, CoreFoundation, Security # This regular expression is used to grab PEM data out of a PEM bundle. _PEM_CERTS_RE = re.compile( @@ -56,6 +56,51 @@ def _cf_dictionary_from_tuples(tuples): ) +def _cfstr(py_bstr): + """ + Given a Python binary data, create a CFString. + The string must be CFReleased by the caller. + """ + c_str = ctypes.c_char_p(py_bstr) + cf_str = CoreFoundation.CFStringCreateWithCString( + CoreFoundation.kCFAllocatorDefault, + c_str, + CFConst.kCFStringEncodingUTF8, + ) + return cf_str + + +def _create_cfstring_array(lst): + """ + Given a list of Python binary data, create an associated CFMutableArray. + The array must be CFReleased by the caller. + + Raises an ssl.SSLError on failure. + """ + cf_arr = None + try: + cf_arr = CoreFoundation.CFArrayCreateMutable( + CoreFoundation.kCFAllocatorDefault, + 0, + ctypes.byref(CoreFoundation.kCFTypeArrayCallBacks), + ) + if not cf_arr: + raise MemoryError("Unable to allocate memory!") + for item in lst: + cf_str = _cfstr(item) + if not cf_str: + raise MemoryError("Unable to allocate memory!") + try: + CoreFoundation.CFArrayAppendValue(cf_arr, cf_str) + finally: + CoreFoundation.CFRelease(cf_str) + except BaseException as e: + if cf_arr: + CoreFoundation.CFRelease(cf_arr) + raise ssl.SSLError("Unable to allocate array: %s" % (e,)) + return cf_arr + + def _cf_string_to_unicode(value): """ Creates a Unicode string from a CFString object. Used entirely for error @@ -66,22 +111,18 @@ def _cf_string_to_unicode(value): value_as_void_p = ctypes.cast(value, ctypes.POINTER(ctypes.c_void_p)) string = CoreFoundation.CFStringGetCStringPtr( - value_as_void_p, - CFConst.kCFStringEncodingUTF8 + value_as_void_p, CFConst.kCFStringEncodingUTF8 ) if string is None: buffer = ctypes.create_string_buffer(1024) result = CoreFoundation.CFStringGetCString( - value_as_void_p, - buffer, - 1024, - CFConst.kCFStringEncodingUTF8 + value_as_void_p, buffer, 1024, CFConst.kCFStringEncodingUTF8 ) if not result: - raise OSError('Error copying C string from CFStringRef') + raise OSError("Error copying C string from CFStringRef") string = buffer.value if string is not None: - string = string.decode('utf-8') + string = string.decode("utf-8") return string @@ -97,8 +138,8 @@ def _assert_no_error(error, exception_class=None): output = _cf_string_to_unicode(cf_error_string) CoreFoundation.CFRelease(cf_error_string) - if output is None or output == u'': - output = u'OSStatus %s' % error + if output is None or output == u"": + output = u"OSStatus %s" % error if exception_class is None: exception_class = ssl.SSLError @@ -115,8 +156,7 @@ def _cert_array_from_pem(pem_bundle): pem_bundle = pem_bundle.replace(b"\r\n", b"\n") der_certs = [ - base64.b64decode(match.group(1)) - for match in _PEM_CERTS_RE.finditer(pem_bundle) + base64.b64decode(match.group(1)) for match in _PEM_CERTS_RE.finditer(pem_bundle) ] if not der_certs: raise ssl.SSLError("No root certificates specified") @@ -124,7 +164,7 @@ def _cert_array_from_pem(pem_bundle): cert_array = CoreFoundation.CFArrayCreateMutable( CoreFoundation.kCFAllocatorDefault, 0, - ctypes.byref(CoreFoundation.kCFTypeArrayCallBacks) + ctypes.byref(CoreFoundation.kCFTypeArrayCallBacks), ) if not cert_array: raise ssl.SSLError("Unable to allocate memory!") @@ -186,21 +226,16 @@ def _temporary_keychain(): # some random bytes to password-protect the keychain we're creating, so we # ask for 40 random bytes. random_bytes = os.urandom(40) - filename = base64.b16encode(random_bytes[:8]).decode('utf-8') + filename = base64.b16encode(random_bytes[:8]).decode("utf-8") password = base64.b16encode(random_bytes[8:]) # Must be valid UTF-8 tempdirectory = tempfile.mkdtemp() - keychain_path = os.path.join(tempdirectory, filename).encode('utf-8') + keychain_path = os.path.join(tempdirectory, filename).encode("utf-8") # We now want to create the keychain itself. keychain = Security.SecKeychainRef() status = Security.SecKeychainCreate( - keychain_path, - len(password), - password, - False, - None, - ctypes.byref(keychain) + keychain_path, len(password), password, False, None, ctypes.byref(keychain) ) _assert_no_error(status) @@ -219,14 +254,12 @@ def _load_items_from_file(keychain, path): identities = [] result_array = None - with open(path, 'rb') as f: + with open(path, "rb") as f: raw_filedata = f.read() try: filedata = CoreFoundation.CFDataCreate( - CoreFoundation.kCFAllocatorDefault, - raw_filedata, - len(raw_filedata) + CoreFoundation.kCFAllocatorDefault, raw_filedata, len(raw_filedata) ) result_array = CoreFoundation.CFArrayRef() result = Security.SecItemImport( @@ -237,7 +270,7 @@ def _load_items_from_file(keychain, path): 0, # import flags None, # key params, can include passphrase in the future keychain, # The keychain to insert into - ctypes.byref(result_array) # Results + ctypes.byref(result_array), # Results ) _assert_no_error(result) @@ -247,9 +280,7 @@ def _load_items_from_file(keychain, path): # keychain already has them! result_count = CoreFoundation.CFArrayGetCount(result_array) for index in range(result_count): - item = CoreFoundation.CFArrayGetValueAtIndex( - result_array, index - ) + item = CoreFoundation.CFArrayGetValueAtIndex(result_array, index) item = ctypes.cast(item, CoreFoundation.CFTypeRef) if _is_cert(item): @@ -307,9 +338,7 @@ def _load_client_cert_chain(keychain, *paths): try: for file_path in paths: - new_identities, new_certs = _load_items_from_file( - keychain, file_path - ) + new_identities, new_certs = _load_items_from_file(keychain, file_path) identities.extend(new_identities) certificates.extend(new_certs) @@ -318,9 +347,7 @@ def _load_client_cert_chain(keychain, *paths): if not identities: new_identity = Security.SecIdentityRef() status = Security.SecIdentityCreateWithCertificate( - keychain, - certificates[0], - ctypes.byref(new_identity) + keychain, certificates[0], ctypes.byref(new_identity) ) _assert_no_error(status) identities.append(new_identity) @@ -344,3 +371,26 @@ def _load_client_cert_chain(keychain, *paths): finally: for obj in itertools.chain(identities, certificates): CoreFoundation.CFRelease(obj) + + +TLS_PROTOCOL_VERSIONS = { + "SSLv2": (0, 2), + "SSLv3": (3, 0), + "TLSv1": (3, 1), + "TLSv1.1": (3, 2), + "TLSv1.2": (3, 3), +} + + +def _build_tls_unknown_ca_alert(version): + """ + Builds a TLS alert record for an unknown CA. + """ + ver_maj, ver_min = TLS_PROTOCOL_VERSIONS[version] + severity_fatal = 0x02 + description_unknown_ca = 0x30 + msg = struct.pack(">BB", severity_fatal, description_unknown_ca) + msg_len = len(msg) + record_type_alert = 0x15 + record = struct.pack(">BBBH", record_type_alert, ver_maj, ver_min, msg_len) + msg + return record diff --git a/pipenv/patched/notpip/_vendor/urllib3/contrib/appengine.py b/pipenv/patched/notpip/_vendor/urllib3/contrib/appengine.py index 1c2332cb..d70993be 100644 --- a/pipenv/patched/notpip/_vendor/urllib3/contrib/appengine.py +++ b/pipenv/patched/notpip/_vendor/urllib3/contrib/appengine.py @@ -39,24 +39,24 @@ urllib3 on Google App Engine: """ from __future__ import absolute_import + import io import logging import warnings -from ..packages.six.moves.urllib.parse import urljoin from ..exceptions import ( HTTPError, HTTPWarning, MaxRetryError, ProtocolError, + SSLError, TimeoutError, - SSLError ) - +from ..packages.six.moves.urllib.parse import urljoin from ..request import RequestMethods from ..response import HTTPResponse -from ..util.timeout import Timeout from ..util.retry import Retry +from ..util.timeout import Timeout from . import _appengine_environ try: @@ -90,29 +90,30 @@ class AppEngineManager(RequestMethods): * If you attempt to use this on App Engine Flexible, as full socket support is available. * If a request size is more than 10 megabytes. - * If a response size is more than 32 megabtyes. + * If a response size is more than 32 megabytes. * If you use an unsupported request method such as OPTIONS. Beyond those cases, it will raise normal urllib3 errors. """ - def __init__(self, headers=None, retries=None, validate_certificate=True, - urlfetch_retries=True): + def __init__( + self, + headers=None, + retries=None, + validate_certificate=True, + urlfetch_retries=True, + ): if not urlfetch: raise AppEnginePlatformError( - "URLFetch is not available in this environment.") - - if is_prod_appengine_mvms(): - raise AppEnginePlatformError( - "Use normal urllib3.PoolManager instead of AppEngineManager" - "on Managed VMs, as using URLFetch is not necessary in " - "this environment.") + "URLFetch is not available in this environment." + ) warnings.warn( "urllib3 is using URLFetch on Google App Engine sandbox instead " "of sockets. To use sockets directly instead of URLFetch see " - "https://urllib3.readthedocs.io/en/latest/reference/urllib3.contrib.html.", - AppEnginePlatformWarning) + "https://urllib3.readthedocs.io/en/1.26.x/reference/urllib3.contrib.html.", + AppEnginePlatformWarning, + ) RequestMethods.__init__(self, headers) self.validate_certificate = validate_certificate @@ -127,17 +128,22 @@ class AppEngineManager(RequestMethods): # Return False to re-raise any potential exceptions return False - def urlopen(self, method, url, body=None, headers=None, - retries=None, redirect=True, timeout=Timeout.DEFAULT_TIMEOUT, - **response_kw): + def urlopen( + self, + method, + url, + body=None, + headers=None, + retries=None, + redirect=True, + timeout=Timeout.DEFAULT_TIMEOUT, + **response_kw + ): retries = self._get_retries(retries, redirect) try: - follow_redirects = ( - redirect and - retries.redirect != 0 and - retries.total) + follow_redirects = redirect and retries.redirect != 0 and retries.total response = urlfetch.fetch( url, payload=body, @@ -152,44 +158,52 @@ class AppEngineManager(RequestMethods): raise TimeoutError(self, e) except urlfetch.InvalidURLError as e: - if 'too large' in str(e): + if "too large" in str(e): raise AppEnginePlatformError( "URLFetch request too large, URLFetch only " - "supports requests up to 10mb in size.", e) + "supports requests up to 10mb in size.", + e, + ) raise ProtocolError(e) except urlfetch.DownloadError as e: - if 'Too many redirects' in str(e): + if "Too many redirects" in str(e): raise MaxRetryError(self, url, reason=e) raise ProtocolError(e) except urlfetch.ResponseTooLargeError as e: raise AppEnginePlatformError( "URLFetch response too large, URLFetch only supports" - "responses up to 32mb in size.", e) + "responses up to 32mb in size.", + e, + ) except urlfetch.SSLCertificateError as e: raise SSLError(e) except urlfetch.InvalidMethodError as e: raise AppEnginePlatformError( - "URLFetch does not support method: %s" % method, e) + "URLFetch does not support method: %s" % method, e + ) http_response = self._urlfetch_response_to_http_response( - response, retries=retries, **response_kw) + response, retries=retries, **response_kw + ) # Handle redirect? redirect_location = redirect and http_response.get_redirect_location() if redirect_location: # Check for redirect response - if (self.urlfetch_retries and retries.raise_on_redirect): + if self.urlfetch_retries and retries.raise_on_redirect: raise MaxRetryError(self, url, "too many redirects") else: if http_response.status == 303: - method = 'GET' + method = "GET" try: - retries = retries.increment(method, url, response=http_response, _pool=self) + retries = retries.increment( + method, url, response=http_response, _pool=self + ) except MaxRetryError: if retries.raise_on_redirect: raise MaxRetryError(self, url, "too many redirects") @@ -199,22 +213,32 @@ class AppEngineManager(RequestMethods): log.debug("Redirecting %s -> %s", url, redirect_location) redirect_url = urljoin(url, redirect_location) return self.urlopen( - method, redirect_url, body, headers, - retries=retries, redirect=redirect, - timeout=timeout, **response_kw) + method, + redirect_url, + body, + headers, + retries=retries, + redirect=redirect, + timeout=timeout, + **response_kw + ) # Check if we should retry the HTTP response. - has_retry_after = bool(http_response.getheader('Retry-After')) + has_retry_after = bool(http_response.getheader("Retry-After")) if retries.is_retry(method, http_response.status, has_retry_after): - retries = retries.increment( - method, url, response=http_response, _pool=self) + retries = retries.increment(method, url, response=http_response, _pool=self) log.debug("Retry: %s", url) retries.sleep(http_response) return self.urlopen( - method, url, - body=body, headers=headers, - retries=retries, redirect=redirect, - timeout=timeout, **response_kw) + method, + url, + body=body, + headers=headers, + retries=retries, + redirect=redirect, + timeout=timeout, + **response_kw + ) return http_response @@ -223,18 +247,18 @@ class AppEngineManager(RequestMethods): if is_prod_appengine(): # Production GAE handles deflate encoding automatically, but does # not remove the encoding header. - content_encoding = urlfetch_resp.headers.get('content-encoding') + content_encoding = urlfetch_resp.headers.get("content-encoding") - if content_encoding == 'deflate': - del urlfetch_resp.headers['content-encoding'] + if content_encoding == "deflate": + del urlfetch_resp.headers["content-encoding"] - transfer_encoding = urlfetch_resp.headers.get('transfer-encoding') + transfer_encoding = urlfetch_resp.headers.get("transfer-encoding") # We have a full response's content, # so let's make sure we don't report ourselves as chunked data. - if transfer_encoding == 'chunked': + if transfer_encoding == "chunked": encodings = transfer_encoding.split(",") - encodings.remove('chunked') - urlfetch_resp.headers['transfer-encoding'] = ','.join(encodings) + encodings.remove("chunked") + urlfetch_resp.headers["transfer-encoding"] = ",".join(encodings) original_response = HTTPResponse( # In order for decoding to work, we must present the content as @@ -262,20 +286,21 @@ class AppEngineManager(RequestMethods): warnings.warn( "URLFetch does not support granular timeout settings, " "reverting to total or default URLFetch timeout.", - AppEnginePlatformWarning) + AppEnginePlatformWarning, + ) return timeout.total return timeout def _get_retries(self, retries, redirect): if not isinstance(retries, Retry): - retries = Retry.from_int( - retries, redirect=redirect, default=self.retries) + retries = Retry.from_int(retries, redirect=redirect, default=self.retries) if retries.connect or retries.read or retries.redirect: warnings.warn( "URLFetch only supports total retries and does not " "recognize connect, read, or redirect retry parameters.", - AppEnginePlatformWarning) + AppEnginePlatformWarning, + ) return retries diff --git a/pipenv/patched/notpip/_vendor/urllib3/contrib/ntlmpool.py b/pipenv/patched/notpip/_vendor/urllib3/contrib/ntlmpool.py index 8ea127c5..41a8fd17 100644 --- a/pipenv/patched/notpip/_vendor/urllib3/contrib/ntlmpool.py +++ b/pipenv/patched/notpip/_vendor/urllib3/contrib/ntlmpool.py @@ -5,12 +5,21 @@ Issue #10, see: http://code.google.com/p/urllib3/issues/detail?id=10 """ from __future__ import absolute_import +import warnings from logging import getLogger + from ntlm import ntlm from .. import HTTPSConnectionPool from ..packages.six.moves.http_client import HTTPSConnection +warnings.warn( + "The 'urllib3.contrib.ntlmpool' module is deprecated and will be removed " + "in urllib3 v2.0 release, urllib3 is not able to support it properly due " + "to reasons listed in issue: https://github.com/urllib3/urllib3/issues/2282. " + "If you are a user of this module please comment in the mentioned issue.", + DeprecationWarning, +) log = getLogger(__name__) @@ -20,7 +29,7 @@ class NTLMConnectionPool(HTTPSConnectionPool): Implements an NTLM authentication version of an urllib3 connection pool """ - scheme = 'https' + scheme = "https" def __init__(self, user, pw, authurl, *args, **kwargs): """ @@ -31,7 +40,7 @@ class NTLMConnectionPool(HTTPSConnectionPool): super(NTLMConnectionPool, self).__init__(*args, **kwargs) self.authurl = authurl self.rawuser = user - user_parts = user.split('\\', 1) + user_parts = user.split("\\", 1) self.domain = user_parts[0].upper() self.user = user_parts[1] self.pw = pw @@ -40,72 +49,82 @@ class NTLMConnectionPool(HTTPSConnectionPool): # Performs the NTLM handshake that secures the connection. The socket # must be kept open while requests are performed. self.num_connections += 1 - log.debug('Starting NTLM HTTPS connection no. %d: https://%s%s', - self.num_connections, self.host, self.authurl) + log.debug( + "Starting NTLM HTTPS connection no. %d: https://%s%s", + self.num_connections, + self.host, + self.authurl, + ) - headers = {'Connection': 'Keep-Alive'} - req_header = 'Authorization' - resp_header = 'www-authenticate' + headers = {"Connection": "Keep-Alive"} + req_header = "Authorization" + resp_header = "www-authenticate" conn = HTTPSConnection(host=self.host, port=self.port) # Send negotiation message - headers[req_header] = ( - 'NTLM %s' % ntlm.create_NTLM_NEGOTIATE_MESSAGE(self.rawuser)) - log.debug('Request headers: %s', headers) - conn.request('GET', self.authurl, None, headers) + headers[req_header] = "NTLM %s" % ntlm.create_NTLM_NEGOTIATE_MESSAGE( + self.rawuser + ) + log.debug("Request headers: %s", headers) + conn.request("GET", self.authurl, None, headers) res = conn.getresponse() reshdr = dict(res.getheaders()) - log.debug('Response status: %s %s', res.status, res.reason) - log.debug('Response headers: %s', reshdr) - log.debug('Response data: %s [...]', res.read(100)) + log.debug("Response status: %s %s", res.status, res.reason) + log.debug("Response headers: %s", reshdr) + log.debug("Response data: %s [...]", res.read(100)) # Remove the reference to the socket, so that it can not be closed by # the response object (we want to keep the socket open) res.fp = None # Server should respond with a challenge message - auth_header_values = reshdr[resp_header].split(', ') + auth_header_values = reshdr[resp_header].split(", ") auth_header_value = None for s in auth_header_values: - if s[:5] == 'NTLM ': + if s[:5] == "NTLM ": auth_header_value = s[5:] if auth_header_value is None: - raise Exception('Unexpected %s response header: %s' % - (resp_header, reshdr[resp_header])) + raise Exception( + "Unexpected %s response header: %s" % (resp_header, reshdr[resp_header]) + ) # Send authentication message - ServerChallenge, NegotiateFlags = \ - ntlm.parse_NTLM_CHALLENGE_MESSAGE(auth_header_value) - auth_msg = ntlm.create_NTLM_AUTHENTICATE_MESSAGE(ServerChallenge, - self.user, - self.domain, - self.pw, - NegotiateFlags) - headers[req_header] = 'NTLM %s' % auth_msg - log.debug('Request headers: %s', headers) - conn.request('GET', self.authurl, None, headers) + ServerChallenge, NegotiateFlags = ntlm.parse_NTLM_CHALLENGE_MESSAGE( + auth_header_value + ) + auth_msg = ntlm.create_NTLM_AUTHENTICATE_MESSAGE( + ServerChallenge, self.user, self.domain, self.pw, NegotiateFlags + ) + headers[req_header] = "NTLM %s" % auth_msg + log.debug("Request headers: %s", headers) + conn.request("GET", self.authurl, None, headers) res = conn.getresponse() - log.debug('Response status: %s %s', res.status, res.reason) - log.debug('Response headers: %s', dict(res.getheaders())) - log.debug('Response data: %s [...]', res.read()[:100]) + log.debug("Response status: %s %s", res.status, res.reason) + log.debug("Response headers: %s", dict(res.getheaders())) + log.debug("Response data: %s [...]", res.read()[:100]) if res.status != 200: if res.status == 401: - raise Exception('Server rejected request: wrong ' - 'username or password') - raise Exception('Wrong server response: %s %s' % - (res.status, res.reason)) + raise Exception("Server rejected request: wrong username or password") + raise Exception("Wrong server response: %s %s" % (res.status, res.reason)) res.fp = None - log.debug('Connection established') + log.debug("Connection established") return conn - def urlopen(self, method, url, body=None, headers=None, retries=3, - redirect=True, assert_same_host=True): + def urlopen( + self, + method, + url, + body=None, + headers=None, + retries=3, + redirect=True, + assert_same_host=True, + ): if headers is None: headers = {} - headers['Connection'] = 'Keep-Alive' - return super(NTLMConnectionPool, self).urlopen(method, url, body, - headers, retries, - redirect, - assert_same_host) + headers["Connection"] = "Keep-Alive" + return super(NTLMConnectionPool, self).urlopen( + method, url, body, headers, retries, redirect, assert_same_host + ) diff --git a/pipenv/patched/notpip/_vendor/urllib3/contrib/pyopenssl.py b/pipenv/patched/notpip/_vendor/urllib3/contrib/pyopenssl.py index f5bc7d83..bebf30d6 100644 --- a/pipenv/patched/notpip/_vendor/urllib3/contrib/pyopenssl.py +++ b/pipenv/patched/notpip/_vendor/urllib3/contrib/pyopenssl.py @@ -1,27 +1,31 @@ """ -SSL with SNI_-support for Python 2. Follow these instructions if you would -like to verify SSL certificates in Python 2. Note, the default libraries do +TLS with SNI_-support for Python 2. Follow these instructions if you would +like to verify TLS certificates in Python 2. Note, the default libraries do *not* do certificate checking; you need to do additional work to validate certificates yourself. This needs the following packages installed: -* pyOpenSSL (tested with 16.0.0) -* cryptography (minimum 1.3.4, from pyopenssl) -* idna (minimum 2.0, from cryptography) +* `pyOpenSSL`_ (tested with 16.0.0) +* `cryptography`_ (minimum 1.3.4, from pyopenssl) +* `idna`_ (minimum 2.0, from cryptography) However, pyopenssl depends on cryptography, which depends on idna, so while we use all three directly here we end up having relatively few packages required. You can install them with the following command: - pip install pyopenssl cryptography idna +.. code-block:: bash + + $ python -m pip install pyopenssl cryptography idna To activate certificate checking, call :func:`~urllib3.contrib.pyopenssl.inject_into_urllib3` from your Python code before you begin making HTTP requests. This can be done in a ``sitecustomize`` module, or at any other time before your application begins using ``urllib3``, -like this:: +like this: + +.. code-block:: python try: import urllib3.contrib.pyopenssl @@ -35,11 +39,11 @@ when the required modules are installed. Activating this module also has the positive side effect of disabling SSL/TLS compression in Python 2 (see `CRIME attack`_). -If you want to configure the default list of supported cipher suites, you can -set the ``urllib3.contrib.pyopenssl.DEFAULT_SSL_CIPHER_LIST`` variable. - .. _sni: https://en.wikipedia.org/wiki/Server_Name_Indication .. _crime attack: https://en.wikipedia.org/wiki/CRIME_(security_exploit) +.. _pyopenssl: https://www.pyopenssl.org +.. _cryptography: https://cryptography.io +.. _idna: https://github.com/kjd/idna """ from __future__ import absolute_import @@ -47,6 +51,7 @@ import OpenSSL.SSL from cryptography import x509 from cryptography.hazmat.backends.openssl import backend as openssl_backend from cryptography.hazmat.backends.openssl.x509 import _Certificate + try: from cryptography.x509 import UnsupportedExtension except ImportError: @@ -54,8 +59,10 @@ except ImportError: class UnsupportedExtension(Exception): pass -from socket import timeout, error as SocketError + from io import BytesIO +from socket import error as SocketError +from socket import timeout try: # Platform-specific: Python 2 from socket import _fileobject @@ -65,42 +72,41 @@ except ImportError: # Platform-specific: Python 3 import logging import ssl -from ..packages import six import sys from .. import util +from ..packages import six +from ..util.ssl_ import PROTOCOL_TLS_CLIENT -__all__ = ['inject_into_urllib3', 'extract_from_urllib3'] +__all__ = ["inject_into_urllib3", "extract_from_urllib3"] # SNI always works. HAS_SNI = True # Map from urllib3 to PyOpenSSL compatible parameter-values. _openssl_versions = { - ssl.PROTOCOL_SSLv23: OpenSSL.SSL.SSLv23_METHOD, + util.PROTOCOL_TLS: OpenSSL.SSL.SSLv23_METHOD, + PROTOCOL_TLS_CLIENT: OpenSSL.SSL.SSLv23_METHOD, ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD, } -if hasattr(ssl, 'PROTOCOL_TLSv1_1') and hasattr(OpenSSL.SSL, 'TLSv1_1_METHOD'): +if hasattr(ssl, "PROTOCOL_SSLv3") and hasattr(OpenSSL.SSL, "SSLv3_METHOD"): + _openssl_versions[ssl.PROTOCOL_SSLv3] = OpenSSL.SSL.SSLv3_METHOD + +if hasattr(ssl, "PROTOCOL_TLSv1_1") and hasattr(OpenSSL.SSL, "TLSv1_1_METHOD"): _openssl_versions[ssl.PROTOCOL_TLSv1_1] = OpenSSL.SSL.TLSv1_1_METHOD -if hasattr(ssl, 'PROTOCOL_TLSv1_2') and hasattr(OpenSSL.SSL, 'TLSv1_2_METHOD'): +if hasattr(ssl, "PROTOCOL_TLSv1_2") and hasattr(OpenSSL.SSL, "TLSv1_2_METHOD"): _openssl_versions[ssl.PROTOCOL_TLSv1_2] = OpenSSL.SSL.TLSv1_2_METHOD -try: - _openssl_versions.update({ssl.PROTOCOL_SSLv3: OpenSSL.SSL.SSLv3_METHOD}) -except AttributeError: - pass _stdlib_to_openssl_verify = { ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE, ssl.CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER, - ssl.CERT_REQUIRED: - OpenSSL.SSL.VERIFY_PEER + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT, + ssl.CERT_REQUIRED: OpenSSL.SSL.VERIFY_PEER + + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT, } -_openssl_to_stdlib_verify = dict( - (v, k) for k, v in _stdlib_to_openssl_verify.items() -) +_openssl_to_stdlib_verify = dict((v, k) for k, v in _stdlib_to_openssl_verify.items()) # OpenSSL will only write 16K at a time SSL_WRITE_BLOCKSIZE = 16384 @@ -113,10 +119,11 @@ log = logging.getLogger(__name__) def inject_into_urllib3(): - 'Monkey-patch urllib3 with PyOpenSSL-backed SSL-support.' + "Monkey-patch urllib3 with PyOpenSSL-backed SSL-support." _validate_dependencies_met() + util.SSLContext = PyOpenSSLContext util.ssl_.SSLContext = PyOpenSSLContext util.HAS_SNI = HAS_SNI util.ssl_.HAS_SNI = HAS_SNI @@ -125,8 +132,9 @@ def inject_into_urllib3(): def extract_from_urllib3(): - 'Undo monkey-patching by :func:`inject_into_urllib3`.' + "Undo monkey-patching by :func:`inject_into_urllib3`." + util.SSLContext = orig_util_SSLContext util.ssl_.SSLContext = orig_util_SSLContext util.HAS_SNI = orig_util_HAS_SNI util.ssl_.HAS_SNI = orig_util_HAS_SNI @@ -141,17 +149,23 @@ def _validate_dependencies_met(): """ # Method added in `cryptography==1.1`; not available in older versions from cryptography.x509.extensions import Extensions + if getattr(Extensions, "get_extension_for_class", None) is None: - raise ImportError("'cryptography' module missing required functionality. " - "Try upgrading to v1.3.4 or newer.") + raise ImportError( + "'cryptography' module missing required functionality. " + "Try upgrading to v1.3.4 or newer." + ) # pyOpenSSL 0.14 and above use cryptography for OpenSSL bindings. The _x509 # attribute is only present on those versions. from OpenSSL.crypto import X509 + x509 = X509() if getattr(x509, "_x509", None) is None: - raise ImportError("'pyOpenSSL' module missing required functionality. " - "Try upgrading to v0.14 or newer.") + raise ImportError( + "'pyOpenSSL' module missing required functionality. " + "Try upgrading to v0.14 or newer." + ) def _dnsname_to_stdlib(name): @@ -167,6 +181,7 @@ def _dnsname_to_stdlib(name): If the name cannot be idna-encoded then we return None signalling that the name given should be skipped. """ + def idna_encode(name): """ Borrowed wholesale from the Python Cryptography Project. It turns out @@ -176,19 +191,23 @@ def _dnsname_to_stdlib(name): from pipenv.patched.notpip._vendor import idna try: - for prefix in [u'*.', u'.']: + for prefix in [u"*.", u"."]: if name.startswith(prefix): - name = name[len(prefix):] - return prefix.encode('ascii') + idna.encode(name) + name = name[len(prefix) :] + return prefix.encode("ascii") + idna.encode(name) return idna.encode(name) except idna.core.IDNAError: return None + # Don't send IPv6 addresses through the IDNA encoder. + if ":" in name: + return name + name = idna_encode(name) if name is None: return None elif sys.version_info >= (3, 0): - name = name.decode('utf-8') + name = name.decode("utf-8") return name @@ -207,14 +226,16 @@ def get_subj_alt_name(peer_cert): # We want to find the SAN extension. Ask Cryptography to locate it (it's # faster than looping in Python) try: - ext = cert.extensions.get_extension_for_class( - x509.SubjectAlternativeName - ).value + ext = cert.extensions.get_extension_for_class(x509.SubjectAlternativeName).value except x509.ExtensionNotFound: # No such extension, return the empty list. return [] - except (x509.DuplicateExtension, UnsupportedExtension, - x509.UnsupportedGeneralNameType, UnicodeError) as e: + except ( + x509.DuplicateExtension, + UnsupportedExtension, + x509.UnsupportedGeneralNameType, + UnicodeError, + ) as e: # A problem has been found with the quality of the certificate. Assume # no SAN field is present. log.warning( @@ -233,23 +254,23 @@ def get_subj_alt_name(peer_cert): # does with certificates, and so we need to attempt to do the same. # We also want to skip over names which cannot be idna encoded. names = [ - ('DNS', name) for name in map(_dnsname_to_stdlib, ext.get_values_for_type(x509.DNSName)) + ("DNS", name) + for name in map(_dnsname_to_stdlib, ext.get_values_for_type(x509.DNSName)) if name is not None ] names.extend( - ('IP Address', str(name)) - for name in ext.get_values_for_type(x509.IPAddress) + ("IP Address", str(name)) for name in ext.get_values_for_type(x509.IPAddress) ) return names class WrappedSocket(object): - '''API-compatibility wrapper for Python OpenSSL's Connection-class. + """API-compatibility wrapper for Python OpenSSL's Connection-class. Note: _makefile_refs, _drop() and _reuse() are needed for the garbage collector of pypy. - ''' + """ def __init__(self, connection, socket, suppress_ragged_eofs=True): self.connection = connection @@ -272,20 +293,24 @@ class WrappedSocket(object): try: data = self.connection.recv(*args, **kwargs) except OpenSSL.SSL.SysCallError as e: - if self.suppress_ragged_eofs and e.args == (-1, 'Unexpected EOF'): - return b'' + if self.suppress_ragged_eofs and e.args == (-1, "Unexpected EOF"): + return b"" else: raise SocketError(str(e)) - except OpenSSL.SSL.ZeroReturnError as e: + except OpenSSL.SSL.ZeroReturnError: if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: - return b'' + return b"" else: raise except OpenSSL.SSL.WantReadError: if not util.wait_for_read(self.socket, self.socket.gettimeout()): - raise timeout('The read operation timed out') + raise timeout("The read operation timed out") else: return self.recv(*args, **kwargs) + + # TLS 1.3 post-handshake authentication + except OpenSSL.SSL.Error as e: + raise ssl.SSLError("read error: %r" % e) else: return data @@ -293,21 +318,25 @@ class WrappedSocket(object): try: return self.connection.recv_into(*args, **kwargs) except OpenSSL.SSL.SysCallError as e: - if self.suppress_ragged_eofs and e.args == (-1, 'Unexpected EOF'): + if self.suppress_ragged_eofs and e.args == (-1, "Unexpected EOF"): return 0 else: raise SocketError(str(e)) - except OpenSSL.SSL.ZeroReturnError as e: + except OpenSSL.SSL.ZeroReturnError: if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: return 0 else: raise except OpenSSL.SSL.WantReadError: if not util.wait_for_read(self.socket, self.socket.gettimeout()): - raise timeout('The read operation timed out') + raise timeout("The read operation timed out") else: return self.recv_into(*args, **kwargs) + # TLS 1.3 post-handshake authentication + except OpenSSL.SSL.Error as e: + raise ssl.SSLError("read error: %r" % e) + def settimeout(self, timeout): return self.socket.settimeout(timeout) @@ -325,7 +354,9 @@ class WrappedSocket(object): def sendall(self, data): total_sent = 0 while total_sent < len(data): - sent = self._send_until_done(data[total_sent:total_sent + SSL_WRITE_BLOCKSIZE]) + sent = self._send_until_done( + data[total_sent : total_sent + SSL_WRITE_BLOCKSIZE] + ) total_sent += sent def shutdown(self): @@ -349,17 +380,16 @@ class WrappedSocket(object): return x509 if binary_form: - return OpenSSL.crypto.dump_certificate( - OpenSSL.crypto.FILETYPE_ASN1, - x509) + return OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_ASN1, x509) return { - 'subject': ( - (('commonName', x509.get_subject().CN),), - ), - 'subjectAltName': get_subj_alt_name(x509) + "subject": ((("commonName", x509.get_subject().CN),),), + "subjectAltName": get_subj_alt_name(x509), } + def version(self): + return self.connection.get_protocol_version_name() + def _reuse(self): self._makefile_refs += 1 @@ -371,9 +401,12 @@ class WrappedSocket(object): if _fileobject: # Platform-specific: Python 2 + def makefile(self, mode, bufsize=-1): self._makefile_refs += 1 return _fileobject(self, mode, bufsize, close=True) + + else: # Platform-specific: Python 3 makefile = backport_makefile @@ -386,6 +419,7 @@ class PyOpenSSLContext(object): for translating the interface of the standard library ``SSLContext`` object to calls into PyOpenSSL. """ + def __init__(self, protocol): self.protocol = _openssl_versions[protocol] self._ctx = OpenSSL.SSL.Context(self.protocol) @@ -407,41 +441,52 @@ class PyOpenSSLContext(object): @verify_mode.setter def verify_mode(self, value): - self._ctx.set_verify( - _stdlib_to_openssl_verify[value], - _verify_callback - ) + self._ctx.set_verify(_stdlib_to_openssl_verify[value], _verify_callback) def set_default_verify_paths(self): self._ctx.set_default_verify_paths() def set_ciphers(self, ciphers): if isinstance(ciphers, six.text_type): - ciphers = ciphers.encode('utf-8') + ciphers = ciphers.encode("utf-8") self._ctx.set_cipher_list(ciphers) def load_verify_locations(self, cafile=None, capath=None, cadata=None): if cafile is not None: - cafile = cafile.encode('utf-8') + cafile = cafile.encode("utf-8") if capath is not None: - capath = capath.encode('utf-8') - self._ctx.load_verify_locations(cafile, capath) - if cadata is not None: - self._ctx.load_verify_locations(BytesIO(cadata)) + capath = capath.encode("utf-8") + try: + self._ctx.load_verify_locations(cafile, capath) + if cadata is not None: + self._ctx.load_verify_locations(BytesIO(cadata)) + except OpenSSL.SSL.Error as e: + raise ssl.SSLError("unable to load trusted certificates: %r" % e) def load_cert_chain(self, certfile, keyfile=None, password=None): self._ctx.use_certificate_chain_file(certfile) if password is not None: - self._ctx.set_passwd_cb(lambda max_length, prompt_twice, userdata: password) + if not isinstance(password, six.binary_type): + password = password.encode("utf-8") + self._ctx.set_passwd_cb(lambda *_: password) self._ctx.use_privatekey_file(keyfile or certfile) - def wrap_socket(self, sock, server_side=False, - do_handshake_on_connect=True, suppress_ragged_eofs=True, - server_hostname=None): + def set_alpn_protocols(self, protocols): + protocols = [six.ensure_binary(p) for p in protocols] + return self._ctx.set_alpn_protos(protocols) + + def wrap_socket( + self, + sock, + server_side=False, + do_handshake_on_connect=True, + suppress_ragged_eofs=True, + server_hostname=None, + ): cnx = OpenSSL.SSL.Connection(self._ctx, sock) if isinstance(server_hostname, six.text_type): # Platform-specific: Python 3 - server_hostname = server_hostname.encode('utf-8') + server_hostname = server_hostname.encode("utf-8") if server_hostname is not None: cnx.set_tlsext_host_name(server_hostname) @@ -453,10 +498,10 @@ class PyOpenSSLContext(object): cnx.do_handshake() except OpenSSL.SSL.WantReadError: if not util.wait_for_read(sock, sock.gettimeout()): - raise timeout('select timed out') + raise timeout("select timed out") continue except OpenSSL.SSL.Error as e: - raise ssl.SSLError('bad handshake: %r' % e) + raise ssl.SSLError("bad handshake: %r" % e) break return WrappedSocket(cnx, sock) diff --git a/pipenv/patched/notpip/_vendor/urllib3/contrib/securetransport.py b/pipenv/patched/notpip/_vendor/urllib3/contrib/securetransport.py index 77cb59ed..4b1bca3d 100644 --- a/pipenv/patched/notpip/_vendor/urllib3/contrib/securetransport.py +++ b/pipenv/patched/notpip/_vendor/urllib3/contrib/securetransport.py @@ -23,6 +23,33 @@ To use this module, simply import and inject it:: urllib3.contrib.securetransport.inject_into_urllib3() Happy TLSing! + +This code is a bastardised version of the code found in Will Bond's oscrypto +library. An enormous debt is owed to him for blazing this trail for us. For +that reason, this code should be considered to be covered both by urllib3's +license and by oscrypto's: + +.. code-block:: + + Copyright (c) 2015-2016 Will Bond <will@wbond.net> + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. """ from __future__ import absolute_import @@ -33,16 +60,22 @@ import os.path import shutil import socket import ssl +import struct import threading import weakref +from pipenv.patched.notpip._vendor import six + from .. import util -from ._securetransport.bindings import ( - Security, SecurityConst, CoreFoundation -) +from ..util.ssl_ import PROTOCOL_TLS_CLIENT +from ._securetransport.bindings import CoreFoundation, Security, SecurityConst from ._securetransport.low_level import ( - _assert_no_error, _cert_array_from_pem, _temporary_keychain, - _load_client_cert_chain + _assert_no_error, + _build_tls_unknown_ca_alert, + _cert_array_from_pem, + _create_cfstring_array, + _load_client_cert_chain, + _temporary_keychain, ) try: # Platform-specific: Python 2 @@ -51,7 +84,7 @@ except ImportError: # Platform-specific: Python 3 _fileobject = None from ..packages.backports.makefile import backport_makefile -__all__ = ['inject_into_urllib3', 'extract_from_urllib3'] +__all__ = ["inject_into_urllib3", "extract_from_urllib3"] # SNI always works HAS_SNI = True @@ -86,35 +119,32 @@ SSL_WRITE_BLOCKSIZE = 16384 # individual cipher suites. We need to do this because this is how # SecureTransport wants them. CIPHER_SUITES = [ - SecurityConst.TLS_AES_256_GCM_SHA384, - SecurityConst.TLS_CHACHA20_POLY1305_SHA256, - SecurityConst.TLS_AES_128_GCM_SHA256, SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - SecurityConst.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, + SecurityConst.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + SecurityConst.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, SecurityConst.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, - SecurityConst.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, SecurityConst.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, - SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, - SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, - SecurityConst.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, - SecurityConst.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, - SecurityConst.TLS_DHE_RSA_WITH_AES_256_CBC_SHA, - SecurityConst.TLS_DHE_DSS_WITH_AES_256_CBC_SHA, SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, - SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + SecurityConst.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, + SecurityConst.TLS_DHE_RSA_WITH_AES_256_CBC_SHA, SecurityConst.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, - SecurityConst.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, SecurityConst.TLS_DHE_RSA_WITH_AES_128_CBC_SHA, - SecurityConst.TLS_DHE_DSS_WITH_AES_128_CBC_SHA, + SecurityConst.TLS_AES_256_GCM_SHA384, + SecurityConst.TLS_AES_128_GCM_SHA256, SecurityConst.TLS_RSA_WITH_AES_256_GCM_SHA384, SecurityConst.TLS_RSA_WITH_AES_128_GCM_SHA256, + SecurityConst.TLS_AES_128_CCM_8_SHA256, + SecurityConst.TLS_AES_128_CCM_SHA256, SecurityConst.TLS_RSA_WITH_AES_256_CBC_SHA256, SecurityConst.TLS_RSA_WITH_AES_128_CBC_SHA256, SecurityConst.TLS_RSA_WITH_AES_256_CBC_SHA, @@ -123,38 +153,44 @@ CIPHER_SUITES = [ # Basically this is simple: for PROTOCOL_SSLv23 we turn it into a low of # TLSv1 and a high of TLSv1.2. For everything else, we pin to that version. +# TLSv1 to 1.2 are supported on macOS 10.8+ _protocol_to_min_max = { - ssl.PROTOCOL_SSLv23: (SecurityConst.kTLSProtocol1, SecurityConst.kTLSProtocol12), + util.PROTOCOL_TLS: (SecurityConst.kTLSProtocol1, SecurityConst.kTLSProtocol12), + PROTOCOL_TLS_CLIENT: (SecurityConst.kTLSProtocol1, SecurityConst.kTLSProtocol12), } if hasattr(ssl, "PROTOCOL_SSLv2"): _protocol_to_min_max[ssl.PROTOCOL_SSLv2] = ( - SecurityConst.kSSLProtocol2, SecurityConst.kSSLProtocol2 + SecurityConst.kSSLProtocol2, + SecurityConst.kSSLProtocol2, ) if hasattr(ssl, "PROTOCOL_SSLv3"): _protocol_to_min_max[ssl.PROTOCOL_SSLv3] = ( - SecurityConst.kSSLProtocol3, SecurityConst.kSSLProtocol3 + SecurityConst.kSSLProtocol3, + SecurityConst.kSSLProtocol3, ) if hasattr(ssl, "PROTOCOL_TLSv1"): _protocol_to_min_max[ssl.PROTOCOL_TLSv1] = ( - SecurityConst.kTLSProtocol1, SecurityConst.kTLSProtocol1 + SecurityConst.kTLSProtocol1, + SecurityConst.kTLSProtocol1, ) if hasattr(ssl, "PROTOCOL_TLSv1_1"): _protocol_to_min_max[ssl.PROTOCOL_TLSv1_1] = ( - SecurityConst.kTLSProtocol11, SecurityConst.kTLSProtocol11 + SecurityConst.kTLSProtocol11, + SecurityConst.kTLSProtocol11, ) if hasattr(ssl, "PROTOCOL_TLSv1_2"): _protocol_to_min_max[ssl.PROTOCOL_TLSv1_2] = ( - SecurityConst.kTLSProtocol12, SecurityConst.kTLSProtocol12 + SecurityConst.kTLSProtocol12, + SecurityConst.kTLSProtocol12, ) -if hasattr(ssl, "PROTOCOL_TLS"): - _protocol_to_min_max[ssl.PROTOCOL_TLS] = _protocol_to_min_max[ssl.PROTOCOL_SSLv23] def inject_into_urllib3(): """ Monkey-patch urllib3 with SecureTransport-backed SSL-support. """ + util.SSLContext = SecureTransportContext util.ssl_.SSLContext = SecureTransportContext util.HAS_SNI = HAS_SNI util.ssl_.HAS_SNI = HAS_SNI @@ -166,6 +202,7 @@ def extract_from_urllib3(): """ Undo monkey-patching by :func:`inject_into_urllib3`. """ + util.SSLContext = orig_util_SSLContext util.ssl_.SSLContext = orig_util_SSLContext util.HAS_SNI = orig_util_HAS_SNI util.ssl_.HAS_SNI = orig_util_HAS_SNI @@ -195,7 +232,7 @@ def _read_callback(connection_id, data_buffer, data_length_pointer): while read_count < requested_length: if timeout is None or timeout >= 0: if not util.wait_for_read(base_socket, timeout): - raise socket.error(errno.EAGAIN, 'timed out') + raise socket.error(errno.EAGAIN, "timed out") remaining = requested_length - read_count buffer = (ctypes.c_char * remaining).from_address( @@ -251,7 +288,7 @@ def _write_callback(connection_id, data_buffer, data_length_pointer): while sent < bytes_to_write: if timeout is None or timeout >= 0: if not util.wait_for_write(base_socket, timeout): - raise socket.error(errno.EAGAIN, 'timed out') + raise socket.error(errno.EAGAIN, "timed out") chunk_sent = base_socket.send(data) sent += chunk_sent @@ -293,6 +330,7 @@ class WrappedSocket(object): Note: _makefile_refs, _drop(), and _reuse() are needed for the garbage collector of PyPy. """ + def __init__(self, socket): self.socket = socket self.context = None @@ -345,19 +383,58 @@ class WrappedSocket(object): ) _assert_no_error(result) + def _set_alpn_protocols(self, protocols): + """ + Sets up the ALPN protocols on the context. + """ + if not protocols: + return + protocols_arr = _create_cfstring_array(protocols) + try: + result = Security.SSLSetALPNProtocols(self.context, protocols_arr) + _assert_no_error(result) + finally: + CoreFoundation.CFRelease(protocols_arr) + def _custom_validate(self, verify, trust_bundle): """ Called when we have set custom validation. We do this in two cases: first, when cert validation is entirely disabled; and second, when using a custom trust DB. + Raises an SSLError if the connection is not trusted. """ # If we disabled cert validation, just say: cool. if not verify: return + successes = ( + SecurityConst.kSecTrustResultUnspecified, + SecurityConst.kSecTrustResultProceed, + ) + try: + trust_result = self._evaluate_trust(trust_bundle) + if trust_result in successes: + return + reason = "error code: %d" % (trust_result,) + except Exception as e: + # Do not trust on error + reason = "exception: %r" % (e,) + + # SecureTransport does not send an alert nor shuts down the connection. + rec = _build_tls_unknown_ca_alert(self.version()) + self.socket.sendall(rec) + # close the connection immediately + # l_onoff = 1, activate linger + # l_linger = 0, linger for 0 seoncds + opts = struct.pack("ii", 1, 0) + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, opts) + self.close() + raise ssl.SSLError("certificate verify failed, %s" % reason) + + def _evaluate_trust(self, trust_bundle): # We want data in memory, so load it up. if os.path.isfile(trust_bundle): - with open(trust_bundle, 'rb') as f: + with open(trust_bundle, "rb") as f: trust_bundle = f.read() cert_array = None @@ -371,9 +448,7 @@ class WrappedSocket(object): # created for this connection, shove our CAs into it, tell ST to # ignore everything else it knows, and then ask if it can build a # chain. This is a buuuunch of code. - result = Security.SSLCopyPeerTrust( - self.context, ctypes.byref(trust) - ) + result = Security.SSLCopyPeerTrust(self.context, ctypes.byref(trust)) _assert_no_error(result) if not trust: raise ssl.SSLError("Failed to copy trust reference") @@ -385,9 +460,7 @@ class WrappedSocket(object): _assert_no_error(result) trust_result = Security.SecTrustResultType() - result = Security.SecTrustEvaluate( - trust, ctypes.byref(trust_result) - ) + result = Security.SecTrustEvaluate(trust, ctypes.byref(trust_result)) _assert_no_error(result) finally: if trust: @@ -396,26 +469,20 @@ class WrappedSocket(object): if cert_array is not None: CoreFoundation.CFRelease(cert_array) - # Ok, now we can look at what the result was. - successes = ( - SecurityConst.kSecTrustResultUnspecified, - SecurityConst.kSecTrustResultProceed - ) - if trust_result.value not in successes: - raise ssl.SSLError( - "certificate verify failed, error code: %d" % - trust_result.value - ) + return trust_result.value - def handshake(self, - server_hostname, - verify, - trust_bundle, - min_version, - max_version, - client_cert, - client_key, - client_key_passphrase): + def handshake( + self, + server_hostname, + verify, + trust_bundle, + min_version, + max_version, + client_cert, + client_key, + client_key_passphrase, + alpn_protocols, + ): """ Actually performs the TLS handshake. This is run automatically by wrapped socket, and shouldn't be needed in user code. @@ -445,7 +512,7 @@ class WrappedSocket(object): # If we have a server hostname, we should set that too. if server_hostname: if not isinstance(server_hostname, bytes): - server_hostname = server_hostname.encode('utf-8') + server_hostname = server_hostname.encode("utf-8") result = Security.SSLSetPeerDomainName( self.context, server_hostname, len(server_hostname) @@ -455,9 +522,13 @@ class WrappedSocket(object): # Setup the ciphers. self._set_ciphers() + # Setup the ALPN protocols. + self._set_alpn_protocols(alpn_protocols) + # Set the minimum and maximum TLS versions. result = Security.SSLSetProtocolVersionMin(self.context, min_version) _assert_no_error(result) + result = Security.SSLSetProtocolVersionMax(self.context, max_version) _assert_no_error(result) @@ -467,9 +538,7 @@ class WrappedSocket(object): # authing in that case. if not verify or trust_bundle is not None: result = Security.SSLSetSessionOption( - self.context, - SecurityConst.kSSLSessionOptionBreakOnServerAuth, - True + self.context, SecurityConst.kSSLSessionOptionBreakOnServerAuth, True ) _assert_no_error(result) @@ -479,9 +548,7 @@ class WrappedSocket(object): self._client_cert_chain = _load_client_cert_chain( self._keychain, client_cert, client_key ) - result = Security.SSLSetCertificate( - self.context, self._client_cert_chain - ) + result = Security.SSLSetCertificate(self.context, self._client_cert_chain) _assert_no_error(result) while True: @@ -532,7 +599,7 @@ class WrappedSocket(object): # There are some result codes that we want to treat as "not always # errors". Specifically, those are errSSLWouldBlock, # errSSLClosedGraceful, and errSSLClosedNoNotify. - if (result == SecurityConst.errSSLWouldBlock): + if result == SecurityConst.errSSLWouldBlock: # If we didn't process any bytes, then this was just a time out. # However, we can get errSSLWouldBlock in situations when we *did* # read some data, and in those cases we should just read "short" @@ -540,7 +607,10 @@ class WrappedSocket(object): if processed_bytes.value == 0: # Timed out, no data read. raise socket.timeout("recv timed out") - elif result in (SecurityConst.errSSLClosedGraceful, SecurityConst.errSSLClosedNoNotify): + elif result in ( + SecurityConst.errSSLClosedGraceful, + SecurityConst.errSSLClosedNoNotify, + ): # The remote peer has closed this connection. We should do so as # well. Note that we don't actually return here because in # principle this could actually be fired along with return data. @@ -579,7 +649,7 @@ class WrappedSocket(object): def sendall(self, data): total_sent = 0 while total_sent < len(data): - sent = self.send(data[total_sent:total_sent + SSL_WRITE_BLOCKSIZE]) + sent = self.send(data[total_sent : total_sent + SSL_WRITE_BLOCKSIZE]) total_sent += sent def shutdown(self): @@ -626,18 +696,14 @@ class WrappedSocket(object): # instead to just flag to urllib3 that it shouldn't do its own hostname # validation when using SecureTransport. if not binary_form: - raise ValueError( - "SecureTransport only supports dumping binary certs" - ) + raise ValueError("SecureTransport only supports dumping binary certs") trust = Security.SecTrustRef() certdata = None der_bytes = None try: # Grab the trust store. - result = Security.SSLCopyPeerTrust( - self.context, ctypes.byref(trust) - ) + result = Security.SSLCopyPeerTrust(self.context, ctypes.byref(trust)) _assert_no_error(result) if not trust: # Probably we haven't done the handshake yet. No biggie. @@ -667,6 +733,27 @@ class WrappedSocket(object): return der_bytes + def version(self): + protocol = Security.SSLProtocol() + result = Security.SSLGetNegotiatedProtocolVersion( + self.context, ctypes.byref(protocol) + ) + _assert_no_error(result) + if protocol.value == SecurityConst.kTLSProtocol13: + raise ssl.SSLError("SecureTransport does not support TLS 1.3") + elif protocol.value == SecurityConst.kTLSProtocol12: + return "TLSv1.2" + elif protocol.value == SecurityConst.kTLSProtocol11: + return "TLSv1.1" + elif protocol.value == SecurityConst.kTLSProtocol1: + return "TLSv1" + elif protocol.value == SecurityConst.kSSLProtocol3: + return "SSLv3" + elif protocol.value == SecurityConst.kSSLProtocol2: + return "SSLv2" + else: + raise ssl.SSLError("Unknown TLS version: %r" % protocol) + def _reuse(self): self._makefile_refs += 1 @@ -678,16 +765,21 @@ class WrappedSocket(object): if _fileobject: # Platform-specific: Python 2 + def makefile(self, mode, bufsize=-1): self._makefile_refs += 1 return _fileobject(self, mode, bufsize, close=True) + + else: # Platform-specific: Python 3 + def makefile(self, mode="r", buffering=None, *args, **kwargs): # We disable buffering with SecureTransport because it conflicts with # the buffering that ST does internally (see issue #1153 for more). buffering = 0 return backport_makefile(self, mode, buffering, *args, **kwargs) + WrappedSocket.makefile = makefile @@ -697,6 +789,7 @@ class SecureTransportContext(object): interface of the standard library ``SSLContext`` object to calls into SecureTransport. """ + def __init__(self, protocol): self._min_version, self._max_version = _protocol_to_min_max[protocol] self._options = 0 @@ -705,6 +798,7 @@ class SecureTransportContext(object): self._client_cert = None self._client_key = None self._client_key_passphrase = None + self._alpn_protocols = None @property def check_hostname(self): @@ -763,16 +857,17 @@ class SecureTransportContext(object): def set_ciphers(self, ciphers): # For now, we just require the default cipher string. if ciphers != util.ssl_.DEFAULT_CIPHERS: - raise ValueError( - "SecureTransport doesn't support custom cipher strings" - ) + raise ValueError("SecureTransport doesn't support custom cipher strings") def load_verify_locations(self, cafile=None, capath=None, cadata=None): # OK, we only really support cadata and cafile. if capath is not None: - raise ValueError( - "SecureTransport does not support cert directories" - ) + raise ValueError("SecureTransport does not support cert directories") + + # Raise if cafile does not exist. + if cafile is not None: + with open(cafile): + pass self._trust_bundle = cafile or cadata @@ -781,9 +876,26 @@ class SecureTransportContext(object): self._client_key = keyfile self._client_cert_passphrase = password - def wrap_socket(self, sock, server_side=False, - do_handshake_on_connect=True, suppress_ragged_eofs=True, - server_hostname=None): + def set_alpn_protocols(self, protocols): + """ + Sets the ALPN protocols that will later be set on the context. + + Raises a NotImplementedError if ALPN is not supported. + """ + if not hasattr(Security, "SSLSetALPNProtocols"): + raise NotImplementedError( + "SecureTransport supports ALPN only in macOS 10.12+" + ) + self._alpn_protocols = [six.ensure_binary(p) for p in protocols] + + def wrap_socket( + self, + sock, + server_side=False, + do_handshake_on_connect=True, + suppress_ragged_eofs=True, + server_hostname=None, + ): # So, what do we do here? Firstly, we assert some properties. This is a # stripped down shim, so there is some functionality we don't support. # See PEP 543 for the real deal. @@ -797,8 +909,14 @@ class SecureTransportContext(object): # Now we can handshake wrapped_socket.handshake( - server_hostname, self._verify, self._trust_bundle, - self._min_version, self._max_version, self._client_cert, - self._client_key, self._client_key_passphrase + server_hostname, + self._verify, + self._trust_bundle, + self._min_version, + self._max_version, + self._client_cert, + self._client_key, + self._client_key_passphrase, + self._alpn_protocols, ) return wrapped_socket diff --git a/pipenv/patched/notpip/_vendor/urllib3/contrib/socks.py b/pipenv/patched/notpip/_vendor/urllib3/contrib/socks.py index 811e312e..c326e80d 100644 --- a/pipenv/patched/notpip/_vendor/urllib3/contrib/socks.py +++ b/pipenv/patched/notpip/_vendor/urllib3/contrib/socks.py @@ -1,25 +1,42 @@ # -*- coding: utf-8 -*- """ This module contains provisional support for SOCKS proxies from within -urllib3. This module supports SOCKS4 (specifically the SOCKS4A variant) and +urllib3. This module supports SOCKS4, SOCKS4A (an extension of SOCKS4), and SOCKS5. To enable its functionality, either install PySocks or install this module with the ``socks`` extra. The SOCKS implementation supports the full range of urllib3 features. It also supports the following SOCKS features: -- SOCKS4 -- SOCKS4a -- SOCKS5 +- SOCKS4A (``proxy_url='socks4a://...``) +- SOCKS4 (``proxy_url='socks4://...``) +- SOCKS5 with remote DNS (``proxy_url='socks5h://...``) +- SOCKS5 with local DNS (``proxy_url='socks5://...``) - Usernames and passwords for the SOCKS proxy -Known Limitations: +.. note:: + It is recommended to use ``socks5h://`` or ``socks4a://`` schemes in + your ``proxy_url`` to ensure that DNS resolution is done from the remote + server instead of client-side when connecting to a domain name. + +SOCKS4 supports IPv4 and domain names with the SOCKS4A extension. SOCKS5 +supports IPv4, IPv6, and domain names. + +When connecting to a SOCKS4 proxy the ``username`` portion of the ``proxy_url`` +will be sent as the ``userid`` section of the SOCKS request: + +.. code-block:: python + + proxy_url="socks4a://<userid>@proxy-host" + +When connecting to a SOCKS5 proxy the ``username`` and ``password`` portion +of the ``proxy_url`` will be sent as the username/password to authenticate +with the proxy: + +.. code-block:: python + + proxy_url="socks5h://<username>:<password>@proxy-host" -- Currently PySocks does not support contacting remote websites via literal - IPv6 addresses. Any such connection attempt will fail. You must use a domain - name. -- Currently PySocks does not support IPv6 connections to the SOCKS proxy. Any - such connection attempt will fail. """ from __future__ import absolute_import @@ -27,25 +44,24 @@ try: import socks except ImportError: import warnings + from ..exceptions import DependencyWarning - warnings.warn(( - 'SOCKS support in urllib3 requires the installation of optional ' - 'dependencies: specifically, PySocks. For more information, see ' - 'https://urllib3.readthedocs.io/en/latest/contrib.html#socks-proxies' + warnings.warn( + ( + "SOCKS support in urllib3 requires the installation of optional " + "dependencies: specifically, PySocks. For more information, see " + "https://urllib3.readthedocs.io/en/1.26.x/contrib.html#socks-proxies" ), - DependencyWarning + DependencyWarning, ) raise -from socket import error as SocketError, timeout as SocketTimeout +from socket import error as SocketError +from socket import timeout as SocketTimeout -from ..connection import ( - HTTPConnection, HTTPSConnection -) -from ..connectionpool import ( - HTTPConnectionPool, HTTPSConnectionPool -) +from ..connection import HTTPConnection, HTTPSConnection +from ..connectionpool import HTTPConnectionPool, HTTPSConnectionPool from ..exceptions import ConnectTimeoutError, NewConnectionError from ..poolmanager import PoolManager from ..util.url import parse_url @@ -60,8 +76,9 @@ class SOCKSConnection(HTTPConnection): """ A plain-text HTTP connection that connects via a SOCKS proxy. """ + def __init__(self, *args, **kwargs): - self._socks_options = kwargs.pop('_socks_options') + self._socks_options = kwargs.pop("_socks_options") super(SOCKSConnection, self).__init__(*args, **kwargs) def _new_conn(self): @@ -70,28 +87,30 @@ class SOCKSConnection(HTTPConnection): """ extra_kw = {} if self.source_address: - extra_kw['source_address'] = self.source_address + extra_kw["source_address"] = self.source_address if self.socket_options: - extra_kw['socket_options'] = self.socket_options + extra_kw["socket_options"] = self.socket_options try: conn = socks.create_connection( (self.host, self.port), - proxy_type=self._socks_options['socks_version'], - proxy_addr=self._socks_options['proxy_host'], - proxy_port=self._socks_options['proxy_port'], - proxy_username=self._socks_options['username'], - proxy_password=self._socks_options['password'], - proxy_rdns=self._socks_options['rdns'], + proxy_type=self._socks_options["socks_version"], + proxy_addr=self._socks_options["proxy_host"], + proxy_port=self._socks_options["proxy_port"], + proxy_username=self._socks_options["username"], + proxy_password=self._socks_options["password"], + proxy_rdns=self._socks_options["rdns"], timeout=self.timeout, **extra_kw ) - except SocketTimeout as e: + except SocketTimeout: raise ConnectTimeoutError( - self, "Connection to %s timed out. (connect timeout=%s)" % - (self.host, self.timeout)) + self, + "Connection to %s timed out. (connect timeout=%s)" + % (self.host, self.timeout), + ) except socks.ProxyError as e: # This is fragile as hell, but it seems to be the only way to raise @@ -101,23 +120,22 @@ class SOCKSConnection(HTTPConnection): if isinstance(error, SocketTimeout): raise ConnectTimeoutError( self, - "Connection to %s timed out. (connect timeout=%s)" % - (self.host, self.timeout) + "Connection to %s timed out. (connect timeout=%s)" + % (self.host, self.timeout), ) else: raise NewConnectionError( - self, - "Failed to establish a new connection: %s" % error + self, "Failed to establish a new connection: %s" % error ) else: raise NewConnectionError( - self, - "Failed to establish a new connection: %s" % e + self, "Failed to establish a new connection: %s" % e ) except SocketError as e: # Defensive: PySocks should catch all these. raise NewConnectionError( - self, "Failed to establish a new connection: %s" % e) + self, "Failed to establish a new connection: %s" % e + ) return conn @@ -143,47 +161,53 @@ class SOCKSProxyManager(PoolManager): A version of the urllib3 ProxyManager that routes connections via the defined SOCKS proxy. """ + pool_classes_by_scheme = { - 'http': SOCKSHTTPConnectionPool, - 'https': SOCKSHTTPSConnectionPool, + "http": SOCKSHTTPConnectionPool, + "https": SOCKSHTTPSConnectionPool, } - def __init__(self, proxy_url, username=None, password=None, - num_pools=10, headers=None, **connection_pool_kw): + def __init__( + self, + proxy_url, + username=None, + password=None, + num_pools=10, + headers=None, + **connection_pool_kw + ): parsed = parse_url(proxy_url) if username is None and password is None and parsed.auth is not None: - split = parsed.auth.split(':') + split = parsed.auth.split(":") if len(split) == 2: username, password = split - if parsed.scheme == 'socks5': + if parsed.scheme == "socks5": socks_version = socks.PROXY_TYPE_SOCKS5 rdns = False - elif parsed.scheme == 'socks5h': + elif parsed.scheme == "socks5h": socks_version = socks.PROXY_TYPE_SOCKS5 rdns = True - elif parsed.scheme == 'socks4': + elif parsed.scheme == "socks4": socks_version = socks.PROXY_TYPE_SOCKS4 rdns = False - elif parsed.scheme == 'socks4a': + elif parsed.scheme == "socks4a": socks_version = socks.PROXY_TYPE_SOCKS4 rdns = True else: - raise ValueError( - "Unable to determine SOCKS version from %s" % proxy_url - ) + raise ValueError("Unable to determine SOCKS version from %s" % proxy_url) self.proxy_url = proxy_url socks_options = { - 'socks_version': socks_version, - 'proxy_host': parsed.host, - 'proxy_port': parsed.port, - 'username': username, - 'password': password, - 'rdns': rdns + "socks_version": socks_version, + "proxy_host": parsed.host, + "proxy_port": parsed.port, + "username": username, + "password": password, + "rdns": rdns, } - connection_pool_kw['_socks_options'] = socks_options + connection_pool_kw["_socks_options"] = socks_options super(SOCKSProxyManager, self).__init__( num_pools, headers, **connection_pool_kw diff --git a/pipenv/patched/notpip/_vendor/urllib3/exceptions.py b/pipenv/patched/notpip/_vendor/urllib3/exceptions.py index 7bbaa987..cba6f3f5 100644 --- a/pipenv/patched/notpip/_vendor/urllib3/exceptions.py +++ b/pipenv/patched/notpip/_vendor/urllib3/exceptions.py @@ -1,22 +1,25 @@ from __future__ import absolute_import -from .packages.six.moves.http_client import ( - IncompleteRead as httplib_IncompleteRead -) + +from .packages.six.moves.http_client import IncompleteRead as httplib_IncompleteRead + # Base Exceptions class HTTPError(Exception): - "Base exception used by this module." + """Base exception used by this module.""" + pass class HTTPWarning(Warning): - "Base warning used by this module." + """Base warning used by this module.""" + pass class PoolError(HTTPError): - "Base exception for errors caused within a pool." + """Base exception for errors caused within a pool.""" + def __init__(self, pool, message): self.pool = pool HTTPError.__init__(self, "%s: %s" % (pool, message)) @@ -27,7 +30,8 @@ class PoolError(HTTPError): class RequestError(PoolError): - "Base exception for PoolErrors that have associated URLs." + """Base exception for PoolErrors that have associated URLs.""" + def __init__(self, pool, url, message): self.url = url PoolError.__init__(self, pool, message) @@ -38,22 +42,28 @@ class RequestError(PoolError): class SSLError(HTTPError): - "Raised when SSL certificate fails in an HTTPS connection." + """Raised when SSL certificate fails in an HTTPS connection.""" + pass class ProxyError(HTTPError): - "Raised when the connection to a proxy fails." - pass + """Raised when the connection to a proxy fails.""" + + def __init__(self, message, error, *args): + super(ProxyError, self).__init__(message, error, *args) + self.original_error = error class DecodeError(HTTPError): - "Raised when automatic decoding based on Content-Type fails." + """Raised when automatic decoding based on Content-Type fails.""" + pass class ProtocolError(HTTPError): - "Raised when something unexpected happens mid-request/response." + """Raised when something unexpected happens mid-request/response.""" + pass @@ -63,6 +73,7 @@ ConnectionError = ProtocolError # Leaf Exceptions + class MaxRetryError(RequestError): """Raised when the maximum number of retries is exceeded. @@ -76,14 +87,13 @@ class MaxRetryError(RequestError): def __init__(self, pool, url, reason=None): self.reason = reason - message = "Max retries exceeded with url: %s (Caused by %r)" % ( - url, reason) + message = "Max retries exceeded with url: %s (Caused by %r)" % (url, reason) RequestError.__init__(self, pool, url, message) class HostChangedError(RequestError): - "Raised when an existing pool gets a request for a foreign host." + """Raised when an existing pool gets a request for a foreign host.""" def __init__(self, pool, url, retries=3): message = "Tried to open a foreign host with url: %s" % url @@ -92,53 +102,61 @@ class HostChangedError(RequestError): class TimeoutStateError(HTTPError): - """ Raised when passing an invalid state to a timeout """ + """Raised when passing an invalid state to a timeout""" + pass class TimeoutError(HTTPError): - """ Raised when a socket timeout error occurs. + """Raised when a socket timeout error occurs. Catching this error will catch both :exc:`ReadTimeoutErrors <ReadTimeoutError>` and :exc:`ConnectTimeoutErrors <ConnectTimeoutError>`. """ + pass class ReadTimeoutError(TimeoutError, RequestError): - "Raised when a socket timeout occurs while receiving data from a server" + """Raised when a socket timeout occurs while receiving data from a server""" + pass # This timeout error does not have a URL attached and needs to inherit from the # base HTTPError class ConnectTimeoutError(TimeoutError): - "Raised when a socket timeout occurs while connecting to a server" + """Raised when a socket timeout occurs while connecting to a server""" + pass class NewConnectionError(ConnectTimeoutError, PoolError): - "Raised when we fail to establish a new connection. Usually ECONNREFUSED." + """Raised when we fail to establish a new connection. Usually ECONNREFUSED.""" + pass class EmptyPoolError(PoolError): - "Raised when a pool runs out of connections and no more are allowed." + """Raised when a pool runs out of connections and no more are allowed.""" + pass class ClosedPoolError(PoolError): - "Raised when a request enters a pool after the pool has been closed." + """Raised when a request enters a pool after the pool has been closed.""" + pass class LocationValueError(ValueError, HTTPError): - "Raised when there is something wrong with a given URL input." + """Raised when there is something wrong with a given URL input.""" + pass class LocationParseError(LocationValueError): - "Raised when get_host or similar fails to parse the URL input." + """Raised when get_host or similar fails to parse the URL input.""" def __init__(self, location): message = "Failed to parse: %s" % location @@ -147,39 +165,56 @@ class LocationParseError(LocationValueError): self.location = location +class URLSchemeUnknown(LocationValueError): + """Raised when a URL input has an unsupported scheme.""" + + def __init__(self, scheme): + message = "Not supported URL scheme %s" % scheme + super(URLSchemeUnknown, self).__init__(message) + + self.scheme = scheme + + class ResponseError(HTTPError): - "Used as a container for an error reason supplied in a MaxRetryError." - GENERIC_ERROR = 'too many error responses' - SPECIFIC_ERROR = 'too many {status_code} error responses' + """Used as a container for an error reason supplied in a MaxRetryError.""" + + GENERIC_ERROR = "too many error responses" + SPECIFIC_ERROR = "too many {status_code} error responses" class SecurityWarning(HTTPWarning): - "Warned when performing security reducing actions" + """Warned when performing security reducing actions""" + pass class SubjectAltNameWarning(SecurityWarning): - "Warned when connecting to a host with a certificate missing a SAN." + """Warned when connecting to a host with a certificate missing a SAN.""" + pass class InsecureRequestWarning(SecurityWarning): - "Warned when making an unverified HTTPS request." + """Warned when making an unverified HTTPS request.""" + pass class SystemTimeWarning(SecurityWarning): - "Warned when system time is suspected to be wrong" + """Warned when system time is suspected to be wrong""" + pass class InsecurePlatformWarning(SecurityWarning): - "Warned when certain SSL configuration is not available on a platform." + """Warned when certain TLS/SSL configuration is not available on a platform.""" + pass class SNIMissingWarning(HTTPWarning): - "Warned when making a HTTPS request without SNI available." + """Warned when making a HTTPS request without SNI available.""" + pass @@ -188,19 +223,22 @@ class DependencyWarning(HTTPWarning): Warned when an attempt is made to import a module with missing optional dependencies. """ + pass class ResponseNotChunked(ProtocolError, ValueError): - "Response needs to be chunked in order to read it as chunks." + """Response needs to be chunked in order to read it as chunks.""" + pass class BodyNotHttplibCompatible(HTTPError): """ - Body should be httplib.HTTPResponse like (have an fp attribute which - returns raw chunks) for read_chunked(). + Body should be :class:`http.client.HTTPResponse` like + (have an fp attribute which returns raw chunks) for read_chunked(). """ + pass @@ -208,39 +246,78 @@ class IncompleteRead(HTTPError, httplib_IncompleteRead): """ Response length doesn't match expected Content-Length - Subclass of http_client.IncompleteRead to allow int value - for `partial` to avoid creating large objects on streamed - reads. + Subclass of :class:`http.client.IncompleteRead` to allow int value + for ``partial`` to avoid creating large objects on streamed reads. """ + def __init__(self, partial, expected): super(IncompleteRead, self).__init__(partial, expected) def __repr__(self): - return ('IncompleteRead(%i bytes read, ' - '%i more expected)' % (self.partial, self.expected)) + return "IncompleteRead(%i bytes read, %i more expected)" % ( + self.partial, + self.expected, + ) + + +class InvalidChunkLength(HTTPError, httplib_IncompleteRead): + """Invalid chunk length in a chunked response.""" + + def __init__(self, response, length): + super(InvalidChunkLength, self).__init__( + response.tell(), response.length_remaining + ) + self.response = response + self.length = length + + def __repr__(self): + return "InvalidChunkLength(got length %r, %i bytes read)" % ( + self.length, + self.partial, + ) class InvalidHeader(HTTPError): - "The header provided was somehow invalid." + """The header provided was somehow invalid.""" + pass -class ProxySchemeUnknown(AssertionError, ValueError): - "ProxyManager does not support the supplied scheme" +class ProxySchemeUnknown(AssertionError, URLSchemeUnknown): + """ProxyManager does not support the supplied scheme""" + # TODO(t-8ch): Stop inheriting from AssertionError in v2.0. def __init__(self, scheme): - message = "Not supported proxy scheme %s" % scheme + # 'localhost' is here because our URL parser parses + # localhost:8080 -> scheme=localhost, remove if we fix this. + if scheme == "localhost": + scheme = None + if scheme is None: + message = "Proxy URL had no scheme, should start with http:// or https://" + else: + message = ( + "Proxy URL had unsupported scheme %s, should use http:// or https://" + % scheme + ) super(ProxySchemeUnknown, self).__init__(message) +class ProxySchemeUnsupported(ValueError): + """Fetching HTTPS resources through HTTPS proxies is unsupported""" + + pass + + class HeaderParsingError(HTTPError): - "Raised by assert_header_parsing, but we convert it to a log.warning statement." + """Raised by assert_header_parsing, but we convert it to a log.warning statement.""" + def __init__(self, defects, unparsed_data): - message = '%s, unparsed data: %r' % (defects or 'Unknown', unparsed_data) + message = "%s, unparsed data: %r" % (defects or "Unknown", unparsed_data) super(HeaderParsingError, self).__init__(message) class UnrewindableBodyError(HTTPError): - "urllib3 encountered an error when trying to rewind a body" + """urllib3 encountered an error when trying to rewind a body""" + pass diff --git a/pipenv/patched/notpip/_vendor/urllib3/fields.py b/pipenv/patched/notpip/_vendor/urllib3/fields.py index 37fe64a3..9d630f49 100644 --- a/pipenv/patched/notpip/_vendor/urllib3/fields.py +++ b/pipenv/patched/notpip/_vendor/urllib3/fields.py @@ -1,11 +1,13 @@ from __future__ import absolute_import + import email.utils import mimetypes +import re from .packages import six -def guess_content_type(filename, default='application/octet-stream'): +def guess_content_type(filename, default="application/octet-stream"): """ Guess the "Content-Type" of a file. @@ -19,57 +21,143 @@ def guess_content_type(filename, default='application/octet-stream'): return default -def format_header_param(name, value): +def format_header_param_rfc2231(name, value): """ - Helper function to format and quote a single header parameter. + Helper function to format and quote a single header parameter using the + strategy defined in RFC 2231. Particularly useful for header parameters which might contain - non-ASCII values, like file names. This follows RFC 2231, as - suggested by RFC 2388 Section 4.4. + non-ASCII values, like file names. This follows + `RFC 2388 Section 4.4 <https://tools.ietf.org/html/rfc2388#section-4.4>`_. :param name: The name of the parameter, a string expected to be ASCII only. :param value: - The value of the parameter, provided as a unicode string. + The value of the parameter, provided as ``bytes`` or `str``. + :ret: + An RFC-2231-formatted unicode string. """ + if isinstance(value, six.binary_type): + value = value.decode("utf-8") + if not any(ch in value for ch in '"\\\r\n'): - result = '%s="%s"' % (name, value) + result = u'%s="%s"' % (name, value) try: - result.encode('ascii') + result.encode("ascii") except (UnicodeEncodeError, UnicodeDecodeError): pass else: return result - if not six.PY3 and isinstance(value, six.text_type): # Python 2: - value = value.encode('utf-8') - value = email.utils.encode_rfc2231(value, 'utf-8') - value = '%s*=%s' % (name, value) + + if six.PY2: # Python 2: + value = value.encode("utf-8") + + # encode_rfc2231 accepts an encoded string and returns an ascii-encoded + # string in Python 2 but accepts and returns unicode strings in Python 3 + value = email.utils.encode_rfc2231(value, "utf-8") + value = "%s*=%s" % (name, value) + + if six.PY2: # Python 2: + value = value.decode("utf-8") + return value +_HTML5_REPLACEMENTS = { + u"\u0022": u"%22", + # Replace "\" with "\\". + u"\u005C": u"\u005C\u005C", +} + +# All control characters from 0x00 to 0x1F *except* 0x1B. +_HTML5_REPLACEMENTS.update( + { + six.unichr(cc): u"%{:02X}".format(cc) + for cc in range(0x00, 0x1F + 1) + if cc not in (0x1B,) + } +) + + +def _replace_multiple(value, needles_and_replacements): + def replacer(match): + return needles_and_replacements[match.group(0)] + + pattern = re.compile( + r"|".join([re.escape(needle) for needle in needles_and_replacements.keys()]) + ) + + result = pattern.sub(replacer, value) + + return result + + +def format_header_param_html5(name, value): + """ + Helper function to format and quote a single header parameter using the + HTML5 strategy. + + Particularly useful for header parameters which might contain + non-ASCII values, like file names. This follows the `HTML5 Working Draft + Section 4.10.22.7`_ and matches the behavior of curl and modern browsers. + + .. _HTML5 Working Draft Section 4.10.22.7: + https://w3c.github.io/html/sec-forms.html#multipart-form-data + + :param name: + The name of the parameter, a string expected to be ASCII only. + :param value: + The value of the parameter, provided as ``bytes`` or `str``. + :ret: + A unicode string, stripped of troublesome characters. + """ + if isinstance(value, six.binary_type): + value = value.decode("utf-8") + + value = _replace_multiple(value, _HTML5_REPLACEMENTS) + + return u'%s="%s"' % (name, value) + + +# For backwards-compatibility. +format_header_param = format_header_param_html5 + + class RequestField(object): """ A data container for request body parameters. :param name: - The name of this request field. + The name of this request field. Must be unicode. :param data: The data/value body. :param filename: - An optional filename of the request field. + An optional filename of the request field. Must be unicode. :param headers: An optional dict-like object of headers to initially use for the field. + :param header_formatter: + An optional callable that is used to encode and format the headers. By + default, this is :func:`format_header_param_html5`. """ - def __init__(self, name, data, filename=None, headers=None): + + def __init__( + self, + name, + data, + filename=None, + headers=None, + header_formatter=format_header_param_html5, + ): self._name = name self._filename = filename self.data = data self.headers = {} if headers: self.headers = dict(headers) + self.header_formatter = header_formatter @classmethod - def from_tuples(cls, fieldname, value): + def from_tuples(cls, fieldname, value, header_formatter=format_header_param_html5): """ A :class:`~urllib3.fields.RequestField` factory from old-style tuple parameters. @@ -97,21 +185,25 @@ class RequestField(object): content_type = None data = value - request_param = cls(fieldname, data, filename=filename) + request_param = cls( + fieldname, data, filename=filename, header_formatter=header_formatter + ) request_param.make_multipart(content_type=content_type) return request_param def _render_part(self, name, value): """ - Overridable helper function to format a single header parameter. + Overridable helper function to format a single header parameter. By + default, this calls ``self.header_formatter``. :param name: The name of the parameter, a string expected to be ASCII only. :param value: The value of the parameter, provided as a unicode string. """ - return format_header_param(name, value) + + return self.header_formatter(name, value) def _render_parts(self, header_parts): """ @@ -133,7 +225,7 @@ class RequestField(object): if value is not None: parts.append(self._render_part(name, value)) - return '; '.join(parts) + return u"; ".join(parts) def render_headers(self): """ @@ -141,21 +233,22 @@ class RequestField(object): """ lines = [] - sort_keys = ['Content-Disposition', 'Content-Type', 'Content-Location'] + sort_keys = ["Content-Disposition", "Content-Type", "Content-Location"] for sort_key in sort_keys: if self.headers.get(sort_key, False): - lines.append('%s: %s' % (sort_key, self.headers[sort_key])) + lines.append(u"%s: %s" % (sort_key, self.headers[sort_key])) for header_name, header_value in self.headers.items(): if header_name not in sort_keys: if header_value: - lines.append('%s: %s' % (header_name, header_value)) + lines.append(u"%s: %s" % (header_name, header_value)) - lines.append('\r\n') - return '\r\n'.join(lines) + lines.append(u"\r\n") + return u"\r\n".join(lines) - def make_multipart(self, content_disposition=None, content_type=None, - content_location=None): + def make_multipart( + self, content_disposition=None, content_type=None, content_location=None + ): """ Makes this request field into a multipart request field. @@ -168,11 +261,14 @@ class RequestField(object): The 'Content-Location' of the request body. """ - self.headers['Content-Disposition'] = content_disposition or 'form-data' - self.headers['Content-Disposition'] += '; '.join([ - '', self._render_parts( - (('name', self._name), ('filename', self._filename)) - ) - ]) - self.headers['Content-Type'] = content_type - self.headers['Content-Location'] = content_location + self.headers["Content-Disposition"] = content_disposition or u"form-data" + self.headers["Content-Disposition"] += u"; ".join( + [ + u"", + self._render_parts( + ((u"name", self._name), (u"filename", self._filename)) + ), + ] + ) + self.headers["Content-Type"] = content_type + self.headers["Content-Location"] = content_location diff --git a/pipenv/patched/notpip/_vendor/urllib3/filepost.py b/pipenv/patched/notpip/_vendor/urllib3/filepost.py index 78f1e19b..36c9252c 100644 --- a/pipenv/patched/notpip/_vendor/urllib3/filepost.py +++ b/pipenv/patched/notpip/_vendor/urllib3/filepost.py @@ -1,15 +1,15 @@ from __future__ import absolute_import + import binascii import codecs import os - from io import BytesIO +from .fields import RequestField from .packages import six from .packages.six import b -from .fields import RequestField -writer = codecs.lookup('utf-8')[3] +writer = codecs.lookup("utf-8")[3] def choose_boundary(): @@ -17,8 +17,8 @@ def choose_boundary(): Our embarrassingly-simple replacement for mimetools.choose_boundary. """ boundary = binascii.hexlify(os.urandom(16)) - if six.PY3: - boundary = boundary.decode('ascii') + if not six.PY2: + boundary = boundary.decode("ascii") return boundary @@ -76,7 +76,7 @@ def encode_multipart_formdata(fields, boundary=None): boundary = choose_boundary() for field in iter_field_objects(fields): - body.write(b('--%s\r\n' % (boundary))) + body.write(b("--%s\r\n" % (boundary))) writer(body).write(field.render_headers()) data = field.data @@ -89,10 +89,10 @@ def encode_multipart_formdata(fields, boundary=None): else: body.write(data) - body.write(b'\r\n') + body.write(b"\r\n") - body.write(b('--%s--\r\n' % (boundary))) + body.write(b("--%s--\r\n" % (boundary))) - content_type = str('multipart/form-data; boundary=%s' % boundary) + content_type = str("multipart/form-data; boundary=%s" % boundary) return body.getvalue(), content_type diff --git a/pipenv/patched/notpip/_vendor/urllib3/packages/__init__.py b/pipenv/patched/notpip/_vendor/urllib3/packages/__init__.py index 170e974c..fce4caa6 100644 --- a/pipenv/patched/notpip/_vendor/urllib3/packages/__init__.py +++ b/pipenv/patched/notpip/_vendor/urllib3/packages/__init__.py @@ -2,4 +2,4 @@ from __future__ import absolute_import from . import ssl_match_hostname -__all__ = ('ssl_match_hostname', ) +__all__ = ("ssl_match_hostname",) diff --git a/pipenv/patched/notpip/_vendor/urllib3/packages/backports/makefile.py b/pipenv/patched/notpip/_vendor/urllib3/packages/backports/makefile.py index 740db377..b8fb2154 100644 --- a/pipenv/patched/notpip/_vendor/urllib3/packages/backports/makefile.py +++ b/pipenv/patched/notpip/_vendor/urllib3/packages/backports/makefile.py @@ -7,19 +7,17 @@ Backports the Python 3 ``socket.makefile`` method for use with anything that wants to create a "fake" socket object. """ import io - from socket import SocketIO -def backport_makefile(self, mode="r", buffering=None, encoding=None, - errors=None, newline=None): +def backport_makefile( + self, mode="r", buffering=None, encoding=None, errors=None, newline=None +): """ Backport of ``socket.makefile`` from Python 3.5. """ if not set(mode) <= {"r", "w", "b"}: - raise ValueError( - "invalid mode %r (only r, w, b allowed)" % (mode,) - ) + raise ValueError("invalid mode %r (only r, w, b allowed)" % (mode,)) writing = "w" in mode reading = "r" in mode or not writing assert reading or writing diff --git a/pipenv/patched/notpip/_vendor/urllib3/packages/six.py b/pipenv/patched/notpip/_vendor/urllib3/packages/six.py index 190c0239..ba50acb0 100644 --- a/pipenv/patched/notpip/_vendor/urllib3/packages/six.py +++ b/pipenv/patched/notpip/_vendor/urllib3/packages/six.py @@ -1,6 +1,4 @@ -"""Utilities for writing code that runs on Python 2 and 3""" - -# Copyright (c) 2010-2015 Benjamin Peterson +# Copyright (c) 2010-2020 Benjamin Peterson # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -20,6 +18,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +"""Utilities for writing code that runs on Python 2 and 3""" + from __future__ import absolute_import import functools @@ -29,7 +29,7 @@ import sys import types __author__ = "Benjamin Peterson <benjamin@python.org>" -__version__ = "1.10.0" +__version__ = "1.16.0" # Useful for very coarse version differentiation. @@ -38,15 +38,15 @@ PY3 = sys.version_info[0] == 3 PY34 = sys.version_info[0:2] >= (3, 4) if PY3: - string_types = str, - integer_types = int, - class_types = type, + string_types = (str,) + integer_types = (int,) + class_types = (type,) text_type = str binary_type = bytes MAXSIZE = sys.maxsize else: - string_types = basestring, + string_types = (basestring,) integer_types = (int, long) class_types = (type, types.ClassType) text_type = unicode @@ -58,9 +58,9 @@ else: else: # It's possible to have sizeof(long) != sizeof(Py_ssize_t). class X(object): - def __len__(self): return 1 << 31 + try: len(X()) except OverflowError: @@ -71,6 +71,11 @@ else: MAXSIZE = int((1 << 63) - 1) del X +if PY34: + from importlib.util import spec_from_loader +else: + spec_from_loader = None + def _add_doc(func, doc): """Add documentation to a function.""" @@ -84,7 +89,6 @@ def _import_module(name): class _LazyDescr(object): - def __init__(self, name): self.name = name @@ -101,7 +105,6 @@ class _LazyDescr(object): class MovedModule(_LazyDescr): - def __init__(self, name, old, new=None): super(MovedModule, self).__init__(name) if PY3: @@ -122,7 +125,6 @@ class MovedModule(_LazyDescr): class _LazyModule(types.ModuleType): - def __init__(self, name): super(_LazyModule, self).__init__(name) self.__doc__ = self.__class__.__doc__ @@ -137,7 +139,6 @@ class _LazyModule(types.ModuleType): class MovedAttribute(_LazyDescr): - def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): super(MovedAttribute, self).__init__(name) if PY3: @@ -186,6 +187,11 @@ class _SixMetaPathImporter(object): return self return None + def find_spec(self, fullname, path, target=None): + if fullname in self.known_modules: + return spec_from_loader(fullname, self) + return None + def __get_module(self, fullname): try: return self.known_modules[fullname] @@ -221,28 +227,42 @@ class _SixMetaPathImporter(object): Required, if is_package is implemented""" self.__get_module(fullname) # eventually raises ImportError return None + get_source = get_code # same as get_code + def create_module(self, spec): + return self.load_module(spec.name) + + def exec_module(self, module): + pass + + _importer = _SixMetaPathImporter(__name__) class _MovedItems(_LazyModule): """Lazy loading of moved objects""" + __path__ = [] # mark as package _moved_attributes = [ MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), - MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"), + MovedAttribute( + "filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse" + ), MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), MovedAttribute("intern", "__builtin__", "sys"), MovedAttribute("map", "itertools", "builtins", "imap", "map"), MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"), MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"), + MovedAttribute("getoutput", "commands", "subprocess"), MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), - MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"), + MovedAttribute( + "reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload" + ), MovedAttribute("reduce", "__builtin__", "functools"), MovedAttribute("shlex_quote", "pipes", "shlex", "quote"), MovedAttribute("StringIO", "StringIO", "io"), @@ -251,21 +271,36 @@ _moved_attributes = [ MovedAttribute("UserString", "UserString", "collections"), MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), - MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"), + MovedAttribute( + "zip_longest", "itertools", "itertools", "izip_longest", "zip_longest" + ), MovedModule("builtins", "__builtin__"), MovedModule("configparser", "ConfigParser"), + MovedModule( + "collections_abc", + "collections", + "collections.abc" if sys.version_info >= (3, 3) else "collections", + ), MovedModule("copyreg", "copy_reg"), MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), - MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"), + MovedModule("dbm_ndbm", "dbm", "dbm.ndbm"), + MovedModule( + "_dummy_thread", + "dummy_thread", + "_dummy_thread" if sys.version_info < (3, 9) else "_thread", + ), MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), MovedModule("http_cookies", "Cookie", "http.cookies"), MovedModule("html_entities", "htmlentitydefs", "html.entities"), MovedModule("html_parser", "HTMLParser", "html.parser"), MovedModule("http_client", "httplib", "http.client"), - MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), - MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"), - MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), + MovedModule("email_mime_image", "email.MIMEImage", "email.mime.image"), + MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), + MovedModule( + "email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart" + ), + MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), @@ -283,15 +318,12 @@ _moved_attributes = [ MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"), MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), - MovedModule("tkinter_colorchooser", "tkColorChooser", - "tkinter.colorchooser"), - MovedModule("tkinter_commondialog", "tkCommonDialog", - "tkinter.commondialog"), + MovedModule("tkinter_colorchooser", "tkColorChooser", "tkinter.colorchooser"), + MovedModule("tkinter_commondialog", "tkCommonDialog", "tkinter.commondialog"), MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), MovedModule("tkinter_font", "tkFont", "tkinter.font"), MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), - MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", - "tkinter.simpledialog"), + MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", "tkinter.simpledialog"), MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), @@ -337,10 +369,14 @@ _urllib_parse_moved_attributes = [ MovedAttribute("quote_plus", "urllib", "urllib.parse"), MovedAttribute("unquote", "urllib", "urllib.parse"), MovedAttribute("unquote_plus", "urllib", "urllib.parse"), + MovedAttribute( + "unquote_to_bytes", "urllib", "urllib.parse", "unquote", "unquote_to_bytes" + ), MovedAttribute("urlencode", "urllib", "urllib.parse"), MovedAttribute("splitquery", "urllib", "urllib.parse"), MovedAttribute("splittag", "urllib", "urllib.parse"), MovedAttribute("splituser", "urllib", "urllib.parse"), + MovedAttribute("splitvalue", "urllib", "urllib.parse"), MovedAttribute("uses_fragment", "urlparse", "urllib.parse"), MovedAttribute("uses_netloc", "urlparse", "urllib.parse"), MovedAttribute("uses_params", "urlparse", "urllib.parse"), @@ -353,8 +389,11 @@ del attr Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes -_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), - "moves.urllib_parse", "moves.urllib.parse") +_importer._add_module( + Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), + "moves.urllib_parse", + "moves.urllib.parse", +) class Module_six_moves_urllib_error(_LazyModule): @@ -373,8 +412,11 @@ del attr Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes -_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), - "moves.urllib_error", "moves.urllib.error") +_importer._add_module( + Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), + "moves.urllib_error", + "moves.urllib.error", +) class Module_six_moves_urllib_request(_LazyModule): @@ -416,6 +458,8 @@ _urllib_request_moved_attributes = [ MovedAttribute("URLopener", "urllib", "urllib.request"), MovedAttribute("FancyURLopener", "urllib", "urllib.request"), MovedAttribute("proxy_bypass", "urllib", "urllib.request"), + MovedAttribute("parse_http_list", "urllib2", "urllib.request"), + MovedAttribute("parse_keqv_list", "urllib2", "urllib.request"), ] for attr in _urllib_request_moved_attributes: setattr(Module_six_moves_urllib_request, attr.name, attr) @@ -423,8 +467,11 @@ del attr Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes -_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), - "moves.urllib_request", "moves.urllib.request") +_importer._add_module( + Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), + "moves.urllib_request", + "moves.urllib.request", +) class Module_six_moves_urllib_response(_LazyModule): @@ -444,8 +491,11 @@ del attr Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes -_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), - "moves.urllib_response", "moves.urllib.response") +_importer._add_module( + Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), + "moves.urllib_response", + "moves.urllib.response", +) class Module_six_moves_urllib_robotparser(_LazyModule): @@ -460,15 +510,21 @@ for attr in _urllib_robotparser_moved_attributes: setattr(Module_six_moves_urllib_robotparser, attr.name, attr) del attr -Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes +Module_six_moves_urllib_robotparser._moved_attributes = ( + _urllib_robotparser_moved_attributes +) -_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), - "moves.urllib_robotparser", "moves.urllib.robotparser") +_importer._add_module( + Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), + "moves.urllib_robotparser", + "moves.urllib.robotparser", +) class Module_six_moves_urllib(types.ModuleType): """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" + __path__ = [] # mark as package parse = _importer._get_module("moves.urllib_parse") error = _importer._get_module("moves.urllib_error") @@ -477,10 +533,12 @@ class Module_six_moves_urllib(types.ModuleType): robotparser = _importer._get_module("moves.urllib_robotparser") def __dir__(self): - return ['parse', 'error', 'request', 'response', 'robotparser'] + return ["parse", "error", "request", "response", "robotparser"] -_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"), - "moves.urllib") + +_importer._add_module( + Module_six_moves_urllib(__name__ + ".moves.urllib"), "moves.urllib" +) def add_move(move): @@ -520,19 +578,24 @@ else: try: advance_iterator = next except NameError: + def advance_iterator(it): return it.next() + + next = advance_iterator try: callable = callable except NameError: + def callable(obj): return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) if PY3: + def get_unbound_function(unbound): return unbound @@ -543,6 +606,7 @@ if PY3: Iterator = object else: + def get_unbound_function(unbound): return unbound.im_func @@ -553,13 +617,13 @@ else: return types.MethodType(func, None, cls) class Iterator(object): - def next(self): return type(self).__next__(self) callable = callable -_add_doc(get_unbound_function, - """Get the function out of a possibly unbound function""") +_add_doc( + get_unbound_function, """Get the function out of a possibly unbound function""" +) get_method_function = operator.attrgetter(_meth_func) @@ -571,6 +635,7 @@ get_function_globals = operator.attrgetter(_func_globals) if PY3: + def iterkeys(d, **kw): return iter(d.keys(**kw)) @@ -589,6 +654,7 @@ if PY3: viewitems = operator.methodcaller("items") else: + def iterkeys(d, **kw): return d.iterkeys(**kw) @@ -609,42 +675,52 @@ else: _add_doc(iterkeys, "Return an iterator over the keys of a dictionary.") _add_doc(itervalues, "Return an iterator over the values of a dictionary.") -_add_doc(iteritems, - "Return an iterator over the (key, value) pairs of a dictionary.") -_add_doc(iterlists, - "Return an iterator over the (key, [values]) pairs of a dictionary.") +_add_doc(iteritems, "Return an iterator over the (key, value) pairs of a dictionary.") +_add_doc( + iterlists, "Return an iterator over the (key, [values]) pairs of a dictionary." +) if PY3: + def b(s): return s.encode("latin-1") def u(s): return s + unichr = chr import struct + int2byte = struct.Struct(">B").pack del struct byte2int = operator.itemgetter(0) indexbytes = operator.getitem iterbytes = iter import io + StringIO = io.StringIO BytesIO = io.BytesIO + del io _assertCountEqual = "assertCountEqual" if sys.version_info[1] <= 1: _assertRaisesRegex = "assertRaisesRegexp" _assertRegex = "assertRegexpMatches" + _assertNotRegex = "assertNotRegexpMatches" else: _assertRaisesRegex = "assertRaisesRegex" _assertRegex = "assertRegex" + _assertNotRegex = "assertNotRegex" else: + def b(s): return s + # Workaround for standalone backslash def u(s): - return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape") + return unicode(s.replace(r"\\", r"\\\\"), "unicode_escape") + unichr = unichr int2byte = chr @@ -653,12 +729,15 @@ else: def indexbytes(buf, i): return ord(buf[i]) + iterbytes = functools.partial(itertools.imap, ord) import StringIO + StringIO = BytesIO = StringIO.StringIO _assertCountEqual = "assertItemsEqual" _assertRaisesRegex = "assertRaisesRegexp" _assertRegex = "assertRegexpMatches" + _assertNotRegex = "assertNotRegexpMatches" _add_doc(b, """Byte literal""") _add_doc(u, """Text literal""") @@ -675,17 +754,27 @@ def assertRegex(self, *args, **kwargs): return getattr(self, _assertRegex)(*args, **kwargs) +def assertNotRegex(self, *args, **kwargs): + return getattr(self, _assertNotRegex)(*args, **kwargs) + + if PY3: exec_ = getattr(moves.builtins, "exec") def reraise(tp, value, tb=None): - if value is None: - value = tp() - if value.__traceback__ is not tb: - raise value.with_traceback(tb) - raise value + try: + if value is None: + value = tp() + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + finally: + value = None + tb = None + else: + def exec_(_code_, _globs_=None, _locs_=None): """Execute code in a namespace.""" if _globs_ is None: @@ -696,30 +785,36 @@ else: del frame elif _locs_ is None: _locs_ = _globs_ - exec("""exec _code_ in _globs_, _locs_""") + exec ("""exec _code_ in _globs_, _locs_""") - exec_("""def reraise(tp, value, tb=None): - raise tp, value, tb -""") + exec_( + """def reraise(tp, value, tb=None): + try: + raise tp, value, tb + finally: + tb = None +""" + ) -if sys.version_info[:2] == (3, 2): - exec_("""def raise_from(value, from_value): - if from_value is None: - raise value - raise value from from_value -""") -elif sys.version_info[:2] > (3, 2): - exec_("""def raise_from(value, from_value): - raise value from from_value -""") +if sys.version_info[:2] > (3,): + exec_( + """def raise_from(value, from_value): + try: + raise value from from_value + finally: + value = None +""" + ) else: + def raise_from(value, from_value): raise value print_ = getattr(moves.builtins, "print", None) if print_ is None: + def print_(*args, **kwargs): """The new-style print function for Python 2.4 and 2.5.""" fp = kwargs.pop("file", sys.stdout) @@ -730,14 +825,17 @@ if print_ is None: if not isinstance(data, basestring): data = str(data) # If the file has an encoding, encode unicode with it. - if (isinstance(fp, file) and - isinstance(data, unicode) and - fp.encoding is not None): + if ( + isinstance(fp, file) + and isinstance(data, unicode) + and fp.encoding is not None + ): errors = getattr(fp, "errors", None) if errors is None: errors = "strict" data = data.encode(fp.encoding, errors) fp.write(data) + want_unicode = False sep = kwargs.pop("sep", None) if sep is not None: @@ -773,6 +871,8 @@ if print_ is None: write(sep) write(arg) write(end) + + if sys.version_info[:2] < (3, 3): _print = print_ @@ -783,16 +883,46 @@ if sys.version_info[:2] < (3, 3): if flush and fp is not None: fp.flush() + _add_doc(reraise, """Reraise an exception.""") if sys.version_info[0:2] < (3, 4): - def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, - updated=functools.WRAPPER_UPDATES): - def wrapper(f): - f = functools.wraps(wrapped, assigned, updated)(f) - f.__wrapped__ = wrapped - return f + # This does exactly the same what the :func:`py3:functools.update_wrapper` + # function does on Python versions after 3.2. It sets the ``__wrapped__`` + # attribute on ``wrapper`` object and it doesn't raise an error if any of + # the attributes mentioned in ``assigned`` and ``updated`` are missing on + # ``wrapped`` object. + def _update_wrapper( + wrapper, + wrapped, + assigned=functools.WRAPPER_ASSIGNMENTS, + updated=functools.WRAPPER_UPDATES, + ): + for attr in assigned: + try: + value = getattr(wrapped, attr) + except AttributeError: + continue + else: + setattr(wrapper, attr, value) + for attr in updated: + getattr(wrapper, attr).update(getattr(wrapped, attr, {})) + wrapper.__wrapped__ = wrapped return wrapper + + _update_wrapper.__doc__ = functools.update_wrapper.__doc__ + + def wraps( + wrapped, + assigned=functools.WRAPPER_ASSIGNMENTS, + updated=functools.WRAPPER_UPDATES, + ): + return functools.partial( + _update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated + ) + + wraps.__doc__ = functools.wraps.__doc__ + else: wraps = functools.wraps @@ -802,44 +932,121 @@ def with_metaclass(meta, *bases): # This requires a bit of explanation: the basic idea is to make a dummy # metaclass for one level of class instantiation that replaces itself with # the actual metaclass. - class metaclass(meta): - + class metaclass(type): def __new__(cls, name, this_bases, d): - return meta(name, bases, d) - return type.__new__(metaclass, 'temporary_class', (), {}) + if sys.version_info[:2] >= (3, 7): + # This version introduced PEP 560 that requires a bit + # of extra care (we mimic what is done by __build_class__). + resolved_bases = types.resolve_bases(bases) + if resolved_bases is not bases: + d["__orig_bases__"] = bases + else: + resolved_bases = bases + return meta(name, resolved_bases, d) + + @classmethod + def __prepare__(cls, name, this_bases): + return meta.__prepare__(name, bases) + + return type.__new__(metaclass, "temporary_class", (), {}) def add_metaclass(metaclass): """Class decorator for creating a class with a metaclass.""" + def wrapper(cls): orig_vars = cls.__dict__.copy() - slots = orig_vars.get('__slots__') + slots = orig_vars.get("__slots__") if slots is not None: if isinstance(slots, str): slots = [slots] for slots_var in slots: orig_vars.pop(slots_var) - orig_vars.pop('__dict__', None) - orig_vars.pop('__weakref__', None) + orig_vars.pop("__dict__", None) + orig_vars.pop("__weakref__", None) + if hasattr(cls, "__qualname__"): + orig_vars["__qualname__"] = cls.__qualname__ return metaclass(cls.__name__, cls.__bases__, orig_vars) + return wrapper +def ensure_binary(s, encoding="utf-8", errors="strict"): + """Coerce **s** to six.binary_type. + + For Python 2: + - `unicode` -> encoded to `str` + - `str` -> `str` + + For Python 3: + - `str` -> encoded to `bytes` + - `bytes` -> `bytes` + """ + if isinstance(s, binary_type): + return s + if isinstance(s, text_type): + return s.encode(encoding, errors) + raise TypeError("not expecting type '%s'" % type(s)) + + +def ensure_str(s, encoding="utf-8", errors="strict"): + """Coerce *s* to `str`. + + For Python 2: + - `unicode` -> encoded to `str` + - `str` -> `str` + + For Python 3: + - `str` -> `str` + - `bytes` -> decoded to `str` + """ + # Optimization: Fast return for the common case. + if type(s) is str: + return s + if PY2 and isinstance(s, text_type): + return s.encode(encoding, errors) + elif PY3 and isinstance(s, binary_type): + return s.decode(encoding, errors) + elif not isinstance(s, (text_type, binary_type)): + raise TypeError("not expecting type '%s'" % type(s)) + return s + + +def ensure_text(s, encoding="utf-8", errors="strict"): + """Coerce *s* to six.text_type. + + For Python 2: + - `unicode` -> `unicode` + - `str` -> `unicode` + + For Python 3: + - `str` -> `str` + - `bytes` -> decoded to `str` + """ + if isinstance(s, binary_type): + return s.decode(encoding, errors) + elif isinstance(s, text_type): + return s + else: + raise TypeError("not expecting type '%s'" % type(s)) + + def python_2_unicode_compatible(klass): """ - A decorator that defines __unicode__ and __str__ methods under Python 2. + A class decorator that defines __unicode__ and __str__ methods under Python 2. Under Python 3 it does nothing. To support Python 2 and 3 with a single code base, define a __str__ method returning text and apply this decorator to the class. """ if PY2: - if '__str__' not in klass.__dict__: - raise ValueError("@python_2_unicode_compatible cannot be applied " - "to %s because it doesn't define __str__()." % - klass.__name__) + if "__str__" not in klass.__dict__: + raise ValueError( + "@python_2_unicode_compatible cannot be applied " + "to %s because it doesn't define __str__()." % klass.__name__ + ) klass.__unicode__ = klass.__str__ - klass.__str__ = lambda self: self.__unicode__().encode('utf-8') + klass.__str__ = lambda self: self.__unicode__().encode("utf-8") return klass @@ -859,8 +1066,10 @@ if sys.meta_path: # be floating around. Therefore, we can't use isinstance() to check for # the six meta path importer, since the other six instance will have # inserted an importer with different class. - if (type(importer).__name__ == "_SixMetaPathImporter" and - importer.name == __name__): + if ( + type(importer).__name__ == "_SixMetaPathImporter" + and importer.name == __name__ + ): del sys.meta_path[i] break del i, importer diff --git a/pipenv/patched/notpip/_vendor/urllib3/packages/ssl_match_hostname/__init__.py b/pipenv/patched/notpip/_vendor/urllib3/packages/ssl_match_hostname/__init__.py index d6594eb2..ef3fde52 100644 --- a/pipenv/patched/notpip/_vendor/urllib3/packages/ssl_match_hostname/__init__.py +++ b/pipenv/patched/notpip/_vendor/urllib3/packages/ssl_match_hostname/__init__.py @@ -1,19 +1,24 @@ import sys try: - # Our match_hostname function is the same as 3.5's, so we only want to + # Our match_hostname function is the same as 3.10's, so we only want to # import the match_hostname function if it's at least that good. - if sys.version_info < (3, 5): + # We also fallback on Python 3.10+ because our code doesn't emit + # deprecation warnings and is the same as Python 3.10 otherwise. + if sys.version_info < (3, 5) or sys.version_info >= (3, 10): raise ImportError("Fallback to vendored code") from ssl import CertificateError, match_hostname except ImportError: try: # Backport of the function from a pypi module - from backports.ssl_match_hostname import CertificateError, match_hostname + from backports.ssl_match_hostname import ( # type: ignore + CertificateError, + match_hostname, + ) except ImportError: # Our vendored copy - from ._implementation import CertificateError, match_hostname + from ._implementation import CertificateError, match_hostname # type: ignore # Not needed, but documenting what we provide. -__all__ = ('CertificateError', 'match_hostname') +__all__ = ("CertificateError", "match_hostname") diff --git a/pipenv/patched/notpip/_vendor/urllib3/packages/ssl_match_hostname/_implementation.py b/pipenv/patched/notpip/_vendor/urllib3/packages/ssl_match_hostname/_implementation.py index b48752f8..689208d3 100644 --- a/pipenv/patched/notpip/_vendor/urllib3/packages/ssl_match_hostname/_implementation.py +++ b/pipenv/patched/notpip/_vendor/urllib3/packages/ssl_match_hostname/_implementation.py @@ -11,11 +11,11 @@ import sys # python-3.5) otherwise only do DNS matching. This allows # backports.ssl_match_hostname to continue to be used in Python 2.7. try: - from pipenv.patched.notpip._vendor import ipaddress + import ipaddress except ImportError: ipaddress = None -__version__ = '3.5.0.1' +__version__ = "3.5.0.1" class CertificateError(ValueError): @@ -33,18 +33,19 @@ def _dnsname_match(dn, hostname, max_wildcards=1): # Ported from python3-syntax: # leftmost, *remainder = dn.split(r'.') - parts = dn.split(r'.') + parts = dn.split(r".") leftmost = parts[0] remainder = parts[1:] - wildcards = leftmost.count('*') + wildcards = leftmost.count("*") if wildcards > max_wildcards: # Issue #17980: avoid denials of service by refusing more # than one wildcard per fragment. A survey of established # policy among SSL implementations showed it to be a # reasonable choice. raise CertificateError( - "too many wildcards in certificate DNS name: " + repr(dn)) + "too many wildcards in certificate DNS name: " + repr(dn) + ) # speed up common case w/o wildcards if not wildcards: @@ -53,11 +54,11 @@ def _dnsname_match(dn, hostname, max_wildcards=1): # RFC 6125, section 6.4.3, subitem 1. # The client SHOULD NOT attempt to match a presented identifier in which # the wildcard character comprises a label other than the left-most label. - if leftmost == '*': + if leftmost == "*": # When '*' is a fragment by itself, it matches a non-empty dotless # fragment. - pats.append('[^.]+') - elif leftmost.startswith('xn--') or hostname.startswith('xn--'): + pats.append("[^.]+") + elif leftmost.startswith("xn--") or hostname.startswith("xn--"): # RFC 6125, section 6.4.3, subitem 3. # The client SHOULD NOT attempt to match a presented identifier # where the wildcard character is embedded within an A-label or @@ -65,21 +66,22 @@ def _dnsname_match(dn, hostname, max_wildcards=1): pats.append(re.escape(leftmost)) else: # Otherwise, '*' matches any dotless string, e.g. www* - pats.append(re.escape(leftmost).replace(r'\*', '[^.]*')) + pats.append(re.escape(leftmost).replace(r"\*", "[^.]*")) # add the remaining fragments, ignore any wildcards for frag in remainder: pats.append(re.escape(frag)) - pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE) + pat = re.compile(r"\A" + r"\.".join(pats) + r"\Z", re.IGNORECASE) return pat.match(hostname) def _to_unicode(obj): if isinstance(obj, str) and sys.version_info < (3,): - obj = unicode(obj, encoding='ascii', errors='strict') + obj = unicode(obj, encoding="ascii", errors="strict") return obj + def _ipaddress_match(ipname, host_ip): """Exact matching of IP addresses. @@ -101,9 +103,11 @@ def match_hostname(cert, hostname): returns nothing. """ if not cert: - raise ValueError("empty or no certificate, match_hostname needs a " - "SSL socket or SSL context with either " - "CERT_OPTIONAL or CERT_REQUIRED") + raise ValueError( + "empty or no certificate, match_hostname needs a " + "SSL socket or SSL context with either " + "CERT_OPTIONAL or CERT_REQUIRED" + ) try: # Divergence from upstream: ipaddress can't handle byte str host_ip = ipaddress.ip_address(_to_unicode(hostname)) @@ -122,35 +126,35 @@ def match_hostname(cert, hostname): else: raise dnsnames = [] - san = cert.get('subjectAltName', ()) + san = cert.get("subjectAltName", ()) for key, value in san: - if key == 'DNS': + if key == "DNS": if host_ip is None and _dnsname_match(value, hostname): return dnsnames.append(value) - elif key == 'IP Address': + elif key == "IP Address": if host_ip is not None and _ipaddress_match(value, host_ip): return dnsnames.append(value) if not dnsnames: # The subject is only checked when there is no dNSName entry # in subjectAltName - for sub in cert.get('subject', ()): + for sub in cert.get("subject", ()): for key, value in sub: # XXX according to RFC 2818, the most specific Common Name # must be used. - if key == 'commonName': + if key == "commonName": if _dnsname_match(value, hostname): return dnsnames.append(value) if len(dnsnames) > 1: - raise CertificateError("hostname %r " - "doesn't match either of %s" - % (hostname, ', '.join(map(repr, dnsnames)))) + raise CertificateError( + "hostname %r " + "doesn't match either of %s" % (hostname, ", ".join(map(repr, dnsnames))) + ) elif len(dnsnames) == 1: - raise CertificateError("hostname %r " - "doesn't match %r" - % (hostname, dnsnames[0])) + raise CertificateError("hostname %r doesn't match %r" % (hostname, dnsnames[0])) else: - raise CertificateError("no appropriate commonName or " - "subjectAltName fields were found") + raise CertificateError( + "no appropriate commonName or subjectAltName fields were found" + ) diff --git a/pipenv/patched/notpip/_vendor/urllib3/poolmanager.py b/pipenv/patched/notpip/_vendor/urllib3/poolmanager.py index fe5491cf..3a31a285 100644 --- a/pipenv/patched/notpip/_vendor/urllib3/poolmanager.py +++ b/pipenv/patched/notpip/_vendor/urllib3/poolmanager.py @@ -1,58 +1,78 @@ from __future__ import absolute_import + import collections import functools import logging from ._collections import RecentlyUsedContainer -from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool -from .connectionpool import port_by_scheme -from .exceptions import LocationValueError, MaxRetryError, ProxySchemeUnknown +from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool, port_by_scheme +from .exceptions import ( + LocationValueError, + MaxRetryError, + ProxySchemeUnknown, + ProxySchemeUnsupported, + URLSchemeUnknown, +) +from .packages import six from .packages.six.moves.urllib.parse import urljoin from .request import RequestMethods -from .util.url import parse_url +from .util.proxy import connection_requires_http_tunnel from .util.retry import Retry +from .util.url import parse_url - -__all__ = ['PoolManager', 'ProxyManager', 'proxy_from_url'] +__all__ = ["PoolManager", "ProxyManager", "proxy_from_url"] log = logging.getLogger(__name__) -SSL_KEYWORDS = ('key_file', 'cert_file', 'cert_reqs', 'ca_certs', - 'ssl_version', 'ca_cert_dir', 'ssl_context') +SSL_KEYWORDS = ( + "key_file", + "cert_file", + "cert_reqs", + "ca_certs", + "ssl_version", + "ca_cert_dir", + "ssl_context", + "key_password", +) # All known keyword arguments that could be provided to the pool manager, its # pools, or the underlying connections. This is used to construct a pool key. _key_fields = ( - 'key_scheme', # str - 'key_host', # str - 'key_port', # int - 'key_timeout', # int or float or Timeout - 'key_retries', # int or Retry - 'key_strict', # bool - 'key_block', # bool - 'key_source_address', # str - 'key_key_file', # str - 'key_cert_file', # str - 'key_cert_reqs', # str - 'key_ca_certs', # str - 'key_ssl_version', # str - 'key_ca_cert_dir', # str - 'key_ssl_context', # instance of ssl.SSLContext or urllib3.util.ssl_.SSLContext - 'key_maxsize', # int - 'key_headers', # dict - 'key__proxy', # parsed proxy url - 'key__proxy_headers', # dict - 'key_socket_options', # list of (level (int), optname (int), value (int or str)) tuples - 'key__socks_options', # dict - 'key_assert_hostname', # bool or string - 'key_assert_fingerprint', # str - 'key_server_hostname', #str + "key_scheme", # str + "key_host", # str + "key_port", # int + "key_timeout", # int or float or Timeout + "key_retries", # int or Retry + "key_strict", # bool + "key_block", # bool + "key_source_address", # str + "key_key_file", # str + "key_key_password", # str + "key_cert_file", # str + "key_cert_reqs", # str + "key_ca_certs", # str + "key_ssl_version", # str + "key_ca_cert_dir", # str + "key_ssl_context", # instance of ssl.SSLContext or urllib3.util.ssl_.SSLContext + "key_maxsize", # int + "key_headers", # dict + "key__proxy", # parsed proxy url + "key__proxy_headers", # dict + "key__proxy_config", # class + "key_socket_options", # list of (level (int), optname (int), value (int or str)) tuples + "key__socks_options", # dict + "key_assert_hostname", # bool or string + "key_assert_fingerprint", # str + "key_server_hostname", # str ) #: The namedtuple class used to construct keys for the connection pool. #: All custom key schemes should include the fields in this key at a minimum. -PoolKey = collections.namedtuple('PoolKey', _key_fields) +PoolKey = collections.namedtuple("PoolKey", _key_fields) + +_proxy_config_fields = ("ssl_context", "use_forwarding_for_https") +ProxyConfig = collections.namedtuple("ProxyConfig", _proxy_config_fields) def _default_key_normalizer(key_class, request_context): @@ -77,24 +97,24 @@ def _default_key_normalizer(key_class, request_context): """ # Since we mutate the dictionary, make a copy first context = request_context.copy() - context['scheme'] = context['scheme'].lower() - context['host'] = context['host'].lower() + context["scheme"] = context["scheme"].lower() + context["host"] = context["host"].lower() # These are both dictionaries and need to be transformed into frozensets - for key in ('headers', '_proxy_headers', '_socks_options'): + for key in ("headers", "_proxy_headers", "_socks_options"): if key in context and context[key] is not None: context[key] = frozenset(context[key].items()) # The socket_options key may be a list and needs to be transformed into a # tuple. - socket_opts = context.get('socket_options') + socket_opts = context.get("socket_options") if socket_opts is not None: - context['socket_options'] = tuple(socket_opts) + context["socket_options"] = tuple(socket_opts) # Map the kwargs to the names in the namedtuple - this is necessary since # namedtuples can't have fields starting with '_'. for key in list(context.keys()): - context['key_' + key] = context.pop(key) + context["key_" + key] = context.pop(key) # Default to ``None`` for keys missing from the context for field in key_class._fields: @@ -109,14 +129,11 @@ def _default_key_normalizer(key_class, request_context): #: Each PoolManager makes a copy of this dictionary so they can be configured #: globally here, or individually on the instance. key_fn_by_scheme = { - 'http': functools.partial(_default_key_normalizer, PoolKey), - 'https': functools.partial(_default_key_normalizer, PoolKey), + "http": functools.partial(_default_key_normalizer, PoolKey), + "https": functools.partial(_default_key_normalizer, PoolKey), } -pool_classes_by_scheme = { - 'http': HTTPConnectionPool, - 'https': HTTPSConnectionPool, -} +pool_classes_by_scheme = {"http": HTTPConnectionPool, "https": HTTPSConnectionPool} class PoolManager(RequestMethods): @@ -148,12 +165,12 @@ class PoolManager(RequestMethods): """ proxy = None + proxy_config = None def __init__(self, num_pools=10, headers=None, **connection_pool_kw): RequestMethods.__init__(self, headers) self.connection_pool_kw = connection_pool_kw - self.pools = RecentlyUsedContainer(num_pools, - dispose_func=lambda p: p.close()) + self.pools = RecentlyUsedContainer(num_pools, dispose_func=lambda p: p.close()) # Locally set the pool classes and keys so other PoolManagers can # override them. @@ -170,7 +187,7 @@ class PoolManager(RequestMethods): def _new_pool(self, scheme, host, port, request_context=None): """ - Create a new :class:`ConnectionPool` based on host, port, scheme, and + Create a new :class:`urllib3.connectionpool.ConnectionPool` based on host, port, scheme, and any additional pool keyword arguments. If ``request_context`` is provided, it is provided as keyword arguments @@ -186,10 +203,10 @@ class PoolManager(RequestMethods): # this function has historically only used the scheme, host, and port # in the positional args. When an API change is acceptable these can # be removed. - for key in ('scheme', 'host', 'port'): + for key in ("scheme", "host", "port"): request_context.pop(key, None) - if scheme == 'http': + if scheme == "http": for kw in SSL_KEYWORDS: request_context.pop(kw, None) @@ -204,9 +221,9 @@ class PoolManager(RequestMethods): """ self.pools.clear() - def connection_from_host(self, host, port=None, scheme='http', pool_kwargs=None): + def connection_from_host(self, host, port=None, scheme="http", pool_kwargs=None): """ - Get a :class:`ConnectionPool` based on the host, port, and scheme. + Get a :class:`urllib3.connectionpool.ConnectionPool` based on the host, port, and scheme. If ``port`` isn't given, it will be derived from the ``scheme`` using ``urllib3.connectionpool.port_by_scheme``. If ``pool_kwargs`` is @@ -219,30 +236,32 @@ class PoolManager(RequestMethods): raise LocationValueError("No host specified.") request_context = self._merge_pool_kwargs(pool_kwargs) - request_context['scheme'] = scheme or 'http' + request_context["scheme"] = scheme or "http" if not port: - port = port_by_scheme.get(request_context['scheme'].lower(), 80) - request_context['port'] = port - request_context['host'] = host + port = port_by_scheme.get(request_context["scheme"].lower(), 80) + request_context["port"] = port + request_context["host"] = host return self.connection_from_context(request_context) def connection_from_context(self, request_context): """ - Get a :class:`ConnectionPool` based on the request context. + Get a :class:`urllib3.connectionpool.ConnectionPool` based on the request context. ``request_context`` must at least contain the ``scheme`` key and its value must be a key in ``key_fn_by_scheme`` instance variable. """ - scheme = request_context['scheme'].lower() - pool_key_constructor = self.key_fn_by_scheme[scheme] + scheme = request_context["scheme"].lower() + pool_key_constructor = self.key_fn_by_scheme.get(scheme) + if not pool_key_constructor: + raise URLSchemeUnknown(scheme) pool_key = pool_key_constructor(request_context) return self.connection_from_pool_key(pool_key, request_context=request_context) def connection_from_pool_key(self, pool_key, request_context=None): """ - Get a :class:`ConnectionPool` based on the provided pool key. + Get a :class:`urllib3.connectionpool.ConnectionPool` based on the provided pool key. ``pool_key`` should be a namedtuple that only contains immutable objects. At a minimum it must have the ``scheme``, ``host``, and @@ -256,9 +275,9 @@ class PoolManager(RequestMethods): return pool # Make a fresh ConnectionPool of the desired type - scheme = request_context['scheme'] - host = request_context['host'] - port = request_context['port'] + scheme = request_context["scheme"] + host = request_context["host"] + port = request_context["port"] pool = self._new_pool(scheme, host, port, request_context=request_context) self.pools[pool_key] = pool @@ -276,8 +295,9 @@ class PoolManager(RequestMethods): not used. """ u = parse_url(url) - return self.connection_from_host(u.host, port=u.port, scheme=u.scheme, - pool_kwargs=pool_kwargs) + return self.connection_from_host( + u.host, port=u.port, scheme=u.scheme, pool_kwargs=pool_kwargs + ) def _merge_pool_kwargs(self, override): """ @@ -299,9 +319,39 @@ class PoolManager(RequestMethods): base_pool_kwargs[key] = value return base_pool_kwargs + def _proxy_requires_url_absolute_form(self, parsed_url): + """ + Indicates if the proxy requires the complete destination URL in the + request. Normally this is only needed when not using an HTTP CONNECT + tunnel. + """ + if self.proxy is None: + return False + + return not connection_requires_http_tunnel( + self.proxy, self.proxy_config, parsed_url.scheme + ) + + def _validate_proxy_scheme_url_selection(self, url_scheme): + """ + Validates that were not attempting to do TLS in TLS connections on + Python2 or with unsupported SSL implementations. + """ + if self.proxy is None or url_scheme != "https": + return + + if self.proxy.scheme != "https": + return + + if six.PY2 and not self.proxy_config.use_forwarding_for_https: + raise ProxySchemeUnsupported( + "Contacting HTTPS destinations through HTTPS proxies " + "'via CONNECT tunnels' is not supported in Python 2" + ) + def urlopen(self, method, url, redirect=True, **kw): """ - Same as :meth:`urllib3.connectionpool.HTTPConnectionPool.urlopen` + Same as :meth:`urllib3.HTTPConnectionPool.urlopen` with custom cross-host redirect logic and only sends the request-uri portion of the ``url``. @@ -309,15 +359,17 @@ class PoolManager(RequestMethods): :class:`urllib3.connectionpool.ConnectionPool` can be chosen for it. """ u = parse_url(url) + self._validate_proxy_scheme_url_selection(u.scheme) + conn = self.connection_from_host(u.host, port=u.port, scheme=u.scheme) - kw['assert_same_host'] = False - kw['redirect'] = False + kw["assert_same_host"] = False + kw["redirect"] = False - if 'headers' not in kw: - kw['headers'] = self.headers.copy() + if "headers" not in kw: + kw["headers"] = self.headers.copy() - if self.proxy is not None and u.scheme == "http": + if self._proxy_requires_url_absolute_form(u): response = conn.urlopen(method, url, **kw) else: response = conn.urlopen(method, u.request_uri, **kw) @@ -331,31 +383,37 @@ class PoolManager(RequestMethods): # RFC 7231, Section 6.4.4 if response.status == 303: - method = 'GET' + method = "GET" - retries = kw.get('retries') + retries = kw.get("retries") if not isinstance(retries, Retry): retries = Retry.from_int(retries, redirect=redirect) # Strip headers marked as unsafe to forward to the redirected location. # Check remove_headers_on_redirect to avoid a potential network call within # conn.is_same_host() which may use socket.gethostbyname() in the future. - if (retries.remove_headers_on_redirect - and not conn.is_same_host(redirect_location)): - for header in retries.remove_headers_on_redirect: - kw['headers'].pop(header, None) + if retries.remove_headers_on_redirect and not conn.is_same_host( + redirect_location + ): + headers = list(six.iterkeys(kw["headers"])) + for header in headers: + if header.lower() in retries.remove_headers_on_redirect: + kw["headers"].pop(header, None) try: retries = retries.increment(method, url, response=response, _pool=conn) except MaxRetryError: if retries.raise_on_redirect: + response.drain_conn() raise return response - kw['retries'] = retries - kw['redirect'] = redirect + kw["retries"] = retries + kw["redirect"] = redirect log.info("Redirecting %s -> %s", url, redirect_location) + + response.drain_conn() return self.urlopen(method, redirect_location, **kw) @@ -373,6 +431,19 @@ class ProxyManager(PoolManager): HTTPS/CONNECT case they are sent only once. Could be used for proxy authentication. + :param proxy_ssl_context: + The proxy SSL context is used to establish the TLS connection to the + proxy when using HTTPS proxies. + + :param use_forwarding_for_https: + (Defaults to False) If set to True will forward requests to the HTTPS + proxy to be made on behalf of the client instead of creating a TLS + tunnel via the CONNECT method. **Enabling this flag means that request + and response headers and content will be visible from the HTTPS proxy** + whereas tunneling keeps request and response headers and content + private. IP address, target hostname, SNI, and port are always visible + to an HTTPS proxy even when this flag is disabled. + Example: >>> proxy = urllib3.ProxyManager('http://localhost:3128/') >>> r1 = proxy.request('GET', 'http://google.com/') @@ -386,47 +457,63 @@ class ProxyManager(PoolManager): """ - def __init__(self, proxy_url, num_pools=10, headers=None, - proxy_headers=None, **connection_pool_kw): + def __init__( + self, + proxy_url, + num_pools=10, + headers=None, + proxy_headers=None, + proxy_ssl_context=None, + use_forwarding_for_https=False, + **connection_pool_kw + ): if isinstance(proxy_url, HTTPConnectionPool): - proxy_url = '%s://%s:%i' % (proxy_url.scheme, proxy_url.host, - proxy_url.port) + proxy_url = "%s://%s:%i" % ( + proxy_url.scheme, + proxy_url.host, + proxy_url.port, + ) proxy = parse_url(proxy_url) - if not proxy.port: - port = port_by_scheme.get(proxy.scheme, 80) - proxy = proxy._replace(port=port) if proxy.scheme not in ("http", "https"): raise ProxySchemeUnknown(proxy.scheme) + if not proxy.port: + port = port_by_scheme.get(proxy.scheme, 80) + proxy = proxy._replace(port=port) + self.proxy = proxy self.proxy_headers = proxy_headers or {} + self.proxy_ssl_context = proxy_ssl_context + self.proxy_config = ProxyConfig(proxy_ssl_context, use_forwarding_for_https) - connection_pool_kw['_proxy'] = self.proxy - connection_pool_kw['_proxy_headers'] = self.proxy_headers + connection_pool_kw["_proxy"] = self.proxy + connection_pool_kw["_proxy_headers"] = self.proxy_headers + connection_pool_kw["_proxy_config"] = self.proxy_config - super(ProxyManager, self).__init__( - num_pools, headers, **connection_pool_kw) + super(ProxyManager, self).__init__(num_pools, headers, **connection_pool_kw) - def connection_from_host(self, host, port=None, scheme='http', pool_kwargs=None): + def connection_from_host(self, host, port=None, scheme="http", pool_kwargs=None): if scheme == "https": return super(ProxyManager, self).connection_from_host( - host, port, scheme, pool_kwargs=pool_kwargs) + host, port, scheme, pool_kwargs=pool_kwargs + ) return super(ProxyManager, self).connection_from_host( - self.proxy.host, self.proxy.port, self.proxy.scheme, pool_kwargs=pool_kwargs) + self.proxy.host, self.proxy.port, self.proxy.scheme, pool_kwargs=pool_kwargs + ) def _set_proxy_headers(self, url, headers=None): """ Sets headers needed by proxies: specifically, the Accept and Host headers. Only sets headers not provided by the user. """ - headers_ = {'Accept': '*/*'} + headers_ = {"Accept": "*/*"} netloc = parse_url(url).netloc if netloc: - headers_['Host'] = netloc + headers_["Host"] = netloc if headers: headers_.update(headers) @@ -435,13 +522,12 @@ class ProxyManager(PoolManager): def urlopen(self, method, url, redirect=True, **kw): "Same as HTTP(S)ConnectionPool.urlopen, ``url`` must be absolute." u = parse_url(url) - - if u.scheme == "http": - # For proxied HTTPS requests, httplib sets the necessary headers - # on the CONNECT to the proxy. For HTTP, we'll definitely - # need to set 'Host' at the very least. - headers = kw.get('headers', self.headers) - kw['headers'] = self._set_proxy_headers(url, headers) + if not connection_requires_http_tunnel(self.proxy, self.proxy_config, u.scheme): + # For connections using HTTP CONNECT, httplib sets the necessary + # headers on the CONNECT to the proxy. If we're not using CONNECT, + # we'll definitely need to set 'Host' at the very least. + headers = kw.get("headers", self.headers) + kw["headers"] = self._set_proxy_headers(url, headers) return super(ProxyManager, self).urlopen(method, url, redirect=redirect, **kw) diff --git a/pipenv/patched/notpip/_vendor/urllib3/request.py b/pipenv/patched/notpip/_vendor/urllib3/request.py index 8f2f44bb..398386a5 100644 --- a/pipenv/patched/notpip/_vendor/urllib3/request.py +++ b/pipenv/patched/notpip/_vendor/urllib3/request.py @@ -3,15 +3,14 @@ from __future__ import absolute_import from .filepost import encode_multipart_formdata from .packages.six.moves.urllib.parse import urlencode - -__all__ = ['RequestMethods'] +__all__ = ["RequestMethods"] class RequestMethods(object): """ Convenience mixin for classes who implement a :meth:`urlopen` method, such - as :class:`~urllib3.connectionpool.HTTPConnectionPool` and - :class:`~urllib3.poolmanager.PoolManager`. + as :class:`urllib3.HTTPConnectionPool` and + :class:`urllib3.PoolManager`. Provides behavior for making common types of HTTP request methods and decides which type of request field encoding to use. @@ -36,16 +35,25 @@ class RequestMethods(object): explicitly. """ - _encode_url_methods = {'DELETE', 'GET', 'HEAD', 'OPTIONS'} + _encode_url_methods = {"DELETE", "GET", "HEAD", "OPTIONS"} def __init__(self, headers=None): self.headers = headers or {} - def urlopen(self, method, url, body=None, headers=None, - encode_multipart=True, multipart_boundary=None, - **kw): # Abstract - raise NotImplementedError("Classes extending RequestMethods must implement " - "their own ``urlopen`` method.") + def urlopen( + self, + method, + url, + body=None, + headers=None, + encode_multipart=True, + multipart_boundary=None, + **kw + ): # Abstract + raise NotImplementedError( + "Classes extending RequestMethods must implement " + "their own ``urlopen`` method." + ) def request(self, method, url, fields=None, headers=None, **urlopen_kw): """ @@ -60,19 +68,18 @@ class RequestMethods(object): """ method = method.upper() - urlopen_kw['request_url'] = url + urlopen_kw["request_url"] = url if method in self._encode_url_methods: - return self.request_encode_url(method, url, fields=fields, - headers=headers, - **urlopen_kw) + return self.request_encode_url( + method, url, fields=fields, headers=headers, **urlopen_kw + ) else: - return self.request_encode_body(method, url, fields=fields, - headers=headers, - **urlopen_kw) + return self.request_encode_body( + method, url, fields=fields, headers=headers, **urlopen_kw + ) - def request_encode_url(self, method, url, fields=None, headers=None, - **urlopen_kw): + def request_encode_url(self, method, url, fields=None, headers=None, **urlopen_kw): """ Make a request using :meth:`urlopen` with the ``fields`` encoded in the url. This is useful for request methods like GET, HEAD, DELETE, etc. @@ -80,25 +87,32 @@ class RequestMethods(object): if headers is None: headers = self.headers - extra_kw = {'headers': headers} + extra_kw = {"headers": headers} extra_kw.update(urlopen_kw) if fields: - url += '?' + urlencode(fields) + url += "?" + urlencode(fields) return self.urlopen(method, url, **extra_kw) - def request_encode_body(self, method, url, fields=None, headers=None, - encode_multipart=True, multipart_boundary=None, - **urlopen_kw): + def request_encode_body( + self, + method, + url, + fields=None, + headers=None, + encode_multipart=True, + multipart_boundary=None, + **urlopen_kw + ): """ Make a request using :meth:`urlopen` with the ``fields`` encoded in the body. This is useful for request methods like POST, PUT, PATCH, etc. When ``encode_multipart=True`` (default), then - :meth:`urllib3.filepost.encode_multipart_formdata` is used to encode + :func:`urllib3.encode_multipart_formdata` is used to encode the payload with the appropriate content type. Otherwise - :meth:`urllib.urlencode` is used with the + :func:`urllib.parse.urlencode` is used with the 'application/x-www-form-urlencoded' content type. Multipart encoding must be used when posting files, and it's reasonably @@ -129,22 +143,28 @@ class RequestMethods(object): if headers is None: headers = self.headers - extra_kw = {'headers': {}} + extra_kw = {"headers": {}} if fields: - if 'body' in urlopen_kw: + if "body" in urlopen_kw: raise TypeError( - "request got values for both 'fields' and 'body', can only specify one.") + "request got values for both 'fields' and 'body', can only specify one." + ) if encode_multipart: - body, content_type = encode_multipart_formdata(fields, boundary=multipart_boundary) + body, content_type = encode_multipart_formdata( + fields, boundary=multipart_boundary + ) else: - body, content_type = urlencode(fields), 'application/x-www-form-urlencoded' + body, content_type = ( + urlencode(fields), + "application/x-www-form-urlencoded", + ) - extra_kw['body'] = body - extra_kw['headers'] = {'Content-Type': content_type} + extra_kw["body"] = body + extra_kw["headers"] = {"Content-Type": content_type} - extra_kw['headers'].update(headers) + extra_kw["headers"].update(headers) extra_kw.update(urlopen_kw) return self.urlopen(method, url, **extra_kw) diff --git a/pipenv/patched/notpip/_vendor/urllib3/response.py b/pipenv/patched/notpip/_vendor/urllib3/response.py index c112690b..38693f4f 100644 --- a/pipenv/patched/notpip/_vendor/urllib3/response.py +++ b/pipenv/patched/notpip/_vendor/urllib3/response.py @@ -1,29 +1,41 @@ from __future__ import absolute_import -from contextlib import contextmanager -import zlib + import io import logging -from socket import timeout as SocketTimeout +import zlib +from contextlib import contextmanager from socket import error as SocketError +from socket import timeout as SocketTimeout + +try: + import brotli +except ImportError: + brotli = None from ._collections import HTTPHeaderDict +from .connection import BaseSSLError, HTTPException from .exceptions import ( - BodyNotHttplibCompatible, ProtocolError, DecodeError, ReadTimeoutError, - ResponseNotChunked, IncompleteRead, InvalidHeader + BodyNotHttplibCompatible, + DecodeError, + HTTPError, + IncompleteRead, + InvalidChunkLength, + InvalidHeader, + ProtocolError, + ReadTimeoutError, + ResponseNotChunked, + SSLError, ) -from .packages.six import string_types as basestring, PY3 -from .packages.six.moves import http_client as httplib -from .connection import HTTPException, BaseSSLError +from .packages import six from .util.response import is_fp_closed, is_response_to_head log = logging.getLogger(__name__) class DeflateDecoder(object): - def __init__(self): self._first_try = True - self._data = b'' + self._data = b"" self._obj = zlib.decompressobj() def __getattr__(self, name): @@ -60,7 +72,6 @@ class GzipDecoderState(object): class GzipDecoder(object): - def __init__(self): self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) self._state = GzipDecoderState.FIRST_MEMBER @@ -90,6 +101,25 @@ class GzipDecoder(object): self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) +if brotli is not None: + + class BrotliDecoder(object): + # Supports both 'brotlipy' and 'Brotli' packages + # since they share an import name. The top branches + # are for 'brotlipy' and bottom branches for 'Brotli' + def __init__(self): + self._obj = brotli.Decompressor() + if hasattr(self._obj, "decompress"): + self.decompress = self._obj.decompress + else: + self.decompress = self._obj.process + + def flush(self): + if hasattr(self._obj, "flush"): + return self._obj.flush() + return b"" + + class MultiDecoder(object): """ From RFC7231: @@ -100,7 +130,7 @@ class MultiDecoder(object): """ def __init__(self, modes): - self._decoders = [_get_decoder(m.strip()) for m in modes.split(',')] + self._decoders = [_get_decoder(m.strip()) for m in modes.split(",")] def flush(self): return self._decoders[0].flush() @@ -112,12 +142,15 @@ class MultiDecoder(object): def _get_decoder(mode): - if ',' in mode: + if "," in mode: return MultiDecoder(mode) - if mode == 'gzip': + if mode == "gzip": return GzipDecoder() + if brotli is not None and mode == "br": + return BrotliDecoder() + return DeflateDecoder() @@ -125,13 +158,13 @@ class HTTPResponse(io.IOBase): """ HTTP Response container. - Backwards-compatible to httplib's HTTPResponse but the response ``body`` is + Backwards-compatible with :class:`http.client.HTTPResponse` but the response ``body`` is loaded and decoded on-demand when the ``data`` property is accessed. This class is also compatible with the Python standard library's :mod:`io` module, and can hence be treated as a readable object in the context of that framework. - Extra parameters for behaviour not present in httplib.HTTPResponse: + Extra parameters for behaviour not present in :class:`http.client.HTTPResponse`: :param preload_content: If True, the response's body will be preloaded during construction. @@ -141,7 +174,7 @@ class HTTPResponse(io.IOBase): 'content-encoding' header. :param original_response: - When this HTTPResponse wrapper is generated from an httplib.HTTPResponse + When this HTTPResponse wrapper is generated from an :class:`http.client.HTTPResponse` object, it's convenient to include the original for debug purposes. It's otherwise unused. @@ -154,14 +187,31 @@ class HTTPResponse(io.IOBase): value of Content-Length header, if present. Otherwise, raise error. """ - CONTENT_DECODERS = ['gzip', 'deflate'] + CONTENT_DECODERS = ["gzip", "deflate"] + if brotli is not None: + CONTENT_DECODERS += ["br"] REDIRECT_STATUSES = [301, 302, 303, 307, 308] - def __init__(self, body='', headers=None, status=0, version=0, reason=None, - strict=0, preload_content=True, decode_content=True, - original_response=None, pool=None, connection=None, msg=None, - retries=None, enforce_content_length=False, - request_method=None, request_url=None): + def __init__( + self, + body="", + headers=None, + status=0, + version=0, + reason=None, + strict=0, + preload_content=True, + decode_content=True, + original_response=None, + pool=None, + connection=None, + msg=None, + retries=None, + enforce_content_length=False, + request_method=None, + request_url=None, + auto_close=True, + ): if isinstance(headers, HTTPHeaderDict): self.headers = headers @@ -174,6 +224,7 @@ class HTTPResponse(io.IOBase): self.decode_content = decode_content self.retries = retries self.enforce_content_length = enforce_content_length + self.auto_close = auto_close self._decoder = None self._body = None @@ -183,19 +234,19 @@ class HTTPResponse(io.IOBase): self.msg = msg self._request_url = request_url - if body and isinstance(body, (basestring, bytes)): + if body and isinstance(body, (six.string_types, bytes)): self._body = body self._pool = pool self._connection = connection - if hasattr(body, 'read'): + if hasattr(body, "read"): self._fp = body # Are we using the chunked-style of transfer encoding? self.chunked = False self.chunk_left = None - tr_enc = self.headers.get('transfer-encoding', '').lower() + tr_enc = self.headers.get("transfer-encoding", "").lower() # Don't incur the penalty of creating a list and then discarding it encodings = (enc.strip() for enc in tr_enc.split(",")) if "chunked" in encodings: @@ -217,7 +268,7 @@ class HTTPResponse(io.IOBase): location. ``False`` if not a redirect status code. """ if self.status in self.REDIRECT_STATUSES: - return self.headers.get('location') + return self.headers.get("location") return False @@ -228,9 +279,20 @@ class HTTPResponse(io.IOBase): self._pool._put_conn(self._connection) self._connection = None + def drain_conn(self): + """ + Read and discard any remaining HTTP response data in the response connection. + + Unread data in the HTTPResponse connection blocks the connection from being released back to the pool. + """ + try: + self.read() + except (HTTPError, SocketError, BaseSSLError, HTTPException): + pass + @property def data(self): - # For backwords-compat with earlier urllib3 0.4 and earlier. + # For backwards-compat with earlier urllib3 0.4 and earlier. if self._body: return self._body @@ -247,8 +309,8 @@ class HTTPResponse(io.IOBase): def tell(self): """ Obtain the number of bytes pulled over the wire so far. May differ from - the amount of content returned by :meth:``HTTPResponse.read`` if bytes - are encoded on the wire (e.g, compressed). + the amount of content returned by :meth:``urllib3.response.HTTPResponse.read`` + if bytes are encoded on the wire (e.g, compressed). """ return self._fp_bytes_read @@ -256,18 +318,20 @@ class HTTPResponse(io.IOBase): """ Set initial length value for Response content if available. """ - length = self.headers.get('content-length') + length = self.headers.get("content-length") if length is not None: if self.chunked: # This Response will fail with an IncompleteRead if it can't be # received as chunked. This method falls back to attempt reading # the response before raising an exception. - log.warning("Received response with both Content-Length and " - "Transfer-Encoding set. This is expressly forbidden " - "by RFC 7230 sec 3.3.2. Ignoring Content-Length and " - "attempting to process response as Transfer-Encoding: " - "chunked.") + log.warning( + "Received response with both Content-Length and " + "Transfer-Encoding set. This is expressly forbidden " + "by RFC 7230 sec 3.3.2. Ignoring Content-Length and " + "attempting to process response as Transfer-Encoding: " + "chunked." + ) return None try: @@ -276,10 +340,12 @@ class HTTPResponse(io.IOBase): # (e.g. Content-Length: 42, 42). This line ensures the values # are all valid ints and that as long as the `set` length is 1, # all values are the same. Otherwise, the header is invalid. - lengths = set([int(val) for val in length.split(',')]) + lengths = set([int(val) for val in length.split(",")]) if len(lengths) > 1: - raise InvalidHeader("Content-Length contained multiple " - "unmatching values (%s)" % length) + raise InvalidHeader( + "Content-Length contained multiple " + "unmatching values (%s)" % length + ) length = lengths.pop() except ValueError: length = None @@ -295,7 +361,7 @@ class HTTPResponse(io.IOBase): status = 0 # Check for responses that shouldn't include a body - if status in (204, 304) or 100 <= status < 200 or request_method == 'HEAD': + if status in (204, 304) or 100 <= status < 200 or request_method == "HEAD": length = 0 return length @@ -306,29 +372,41 @@ class HTTPResponse(io.IOBase): """ # Note: content-encoding value should be case-insensitive, per RFC 7230 # Section 3.2 - content_encoding = self.headers.get('content-encoding', '').lower() + content_encoding = self.headers.get("content-encoding", "").lower() if self._decoder is None: if content_encoding in self.CONTENT_DECODERS: self._decoder = _get_decoder(content_encoding) - elif ',' in content_encoding: - encodings = [e.strip() for e in content_encoding.split(',') if e.strip() in self.CONTENT_DECODERS] + elif "," in content_encoding: + encodings = [ + e.strip() + for e in content_encoding.split(",") + if e.strip() in self.CONTENT_DECODERS + ] if len(encodings): self._decoder = _get_decoder(content_encoding) + DECODER_ERROR_CLASSES = (IOError, zlib.error) + if brotli is not None: + DECODER_ERROR_CLASSES += (brotli.error,) + def _decode(self, data, decode_content, flush_decoder): """ Decode the data passed in and potentially flush the decoder. """ + if not decode_content: + return data + try: - if decode_content and self._decoder: + if self._decoder: data = self._decoder.decompress(data) - except (IOError, zlib.error) as e: - content_encoding = self.headers.get('content-encoding', '').lower() + except self.DECODER_ERROR_CLASSES as e: + content_encoding = self.headers.get("content-encoding", "").lower() raise DecodeError( "Received response with content-encoding: %s, but " - "failed to decode it." % content_encoding, e) - - if flush_decoder and decode_content: + "failed to decode it." % content_encoding, + e, + ) + if flush_decoder: data += self._flush_decoder() return data @@ -339,10 +417,10 @@ class HTTPResponse(io.IOBase): being used. """ if self._decoder: - buf = self._decoder.decompress(b'') + buf = self._decoder.decompress(b"") return buf + self._decoder.flush() - return b'' + return b"" @contextmanager def _error_catcher(self): @@ -362,20 +440,19 @@ class HTTPResponse(io.IOBase): except SocketTimeout: # FIXME: Ideally we'd like to include the url in the ReadTimeoutError but # there is yet no clean way to get at it from this context. - raise ReadTimeoutError(self._pool, None, 'Read timed out.') + raise ReadTimeoutError(self._pool, None, "Read timed out.") except BaseSSLError as e: # FIXME: Is there a better way to differentiate between SSLErrors? - if 'read operation timed out' not in str(e): # Defensive: - # This shouldn't happen but just in case we're missing an edge - # case, let's avoid swallowing SSL errors. - raise + if "read operation timed out" not in str(e): + # SSL errors related to framing/MAC get wrapped and reraised here + raise SSLError(e) - raise ReadTimeoutError(self._pool, None, 'Read timed out.') + raise ReadTimeoutError(self._pool, None, "Read timed out.") except (HTTPException, SocketError) as e: # This includes IncompleteRead. - raise ProtocolError('Connection broken: %r' % e, e) + raise ProtocolError("Connection broken: %r" % e, e) # If no exception is thrown, we should avoid cleaning up # unnecessarily. @@ -403,7 +480,7 @@ class HTTPResponse(io.IOBase): def read(self, amt=None, decode_content=None, cache_content=False): """ - Similar to :meth:`httplib.HTTPResponse.read`, but with two additional + Similar to :meth:`http.client.HTTPResponse.read`, but with two additional parameters: ``decode_content`` and ``cache_content``. :param amt: @@ -430,17 +507,19 @@ class HTTPResponse(io.IOBase): return flush_decoder = False - data = None + fp_closed = getattr(self._fp, "closed", False) with self._error_catcher(): if amt is None: # cStringIO doesn't like amt=None - data = self._fp.read() + data = self._fp.read() if not fp_closed else b"" flush_decoder = True else: cache_content = False - data = self._fp.read(amt) - if amt != 0 and not data: # Platform-specific: Buggy versions of Python. + data = self._fp.read(amt) if not fp_closed else b"" + if ( + amt != 0 and not data + ): # Platform-specific: Buggy versions of Python. # Close the connection when no data is returned # # This is redundant to what httplib/http.client _should_ @@ -450,7 +529,10 @@ class HTTPResponse(io.IOBase): # no harm in redundantly calling close. self._fp.close() flush_decoder = True - if self.enforce_content_length and self.length_remaining not in (0, None): + if self.enforce_content_length and self.length_remaining not in ( + 0, + None, + ): # This is an edge case that httplib failed to cover due # to concerns of backward compatibility. We're # addressing it here to make sure IncompleteRead is @@ -470,7 +552,7 @@ class HTTPResponse(io.IOBase): return data - def stream(self, amt=2**16, decode_content=None): + def stream(self, amt=2 ** 16, decode_content=None): """ A generator wrapper for the read() method. A call will block until ``amt`` bytes have been read from the connection or until the @@ -499,7 +581,7 @@ class HTTPResponse(io.IOBase): @classmethod def from_httplib(ResponseCls, r, **response_kw): """ - Given an :class:`httplib.HTTPResponse` instance ``r``, return a + Given an :class:`http.client.HTTPResponse` instance ``r``, return a corresponding :class:`urllib3.response.HTTPResponse` object. Remaining parameters are passed to the HTTPResponse constructor, along @@ -508,24 +590,27 @@ class HTTPResponse(io.IOBase): headers = r.msg if not isinstance(headers, HTTPHeaderDict): - if PY3: # Python 3 - headers = HTTPHeaderDict(headers.items()) - else: # Python 2 + if six.PY2: + # Python 2.7 headers = HTTPHeaderDict.from_httplib(headers) + else: + headers = HTTPHeaderDict(headers.items()) # HTTPResponse objects in Python 3 don't have a .strict attribute - strict = getattr(r, 'strict', 0) - resp = ResponseCls(body=r, - headers=headers, - status=r.status, - version=r.version, - reason=r.reason, - strict=strict, - original_response=r, - **response_kw) + strict = getattr(r, "strict", 0) + resp = ResponseCls( + body=r, + headers=headers, + status=r.status, + version=r.version, + reason=r.reason, + strict=strict, + original_response=r, + **response_kw + ) return resp - # Backwards-compatibility methods for httplib.HTTPResponse + # Backwards-compatibility methods for http.client.HTTPResponse def getheaders(self): return self.headers @@ -544,13 +629,18 @@ class HTTPResponse(io.IOBase): if self._connection: self._connection.close() + if not self.auto_close: + io.IOBase.close(self) + @property def closed(self): - if self._fp is None: + if not self.auto_close: + return io.IOBase.closed.__get__(self) + elif self._fp is None: return True - elif hasattr(self._fp, 'isclosed'): + elif hasattr(self._fp, "isclosed"): return self._fp.isclosed() - elif hasattr(self._fp, 'closed'): + elif hasattr(self._fp, "closed"): return self._fp.closed else: return True @@ -561,11 +651,17 @@ class HTTPResponse(io.IOBase): elif hasattr(self._fp, "fileno"): return self._fp.fileno() else: - raise IOError("The file-like object this HTTPResponse is wrapped " - "around has no file descriptor") + raise IOError( + "The file-like object this HTTPResponse is wrapped " + "around has no file descriptor" + ) def flush(self): - if self._fp is not None and hasattr(self._fp, 'flush'): + if ( + self._fp is not None + and hasattr(self._fp, "flush") + and not getattr(self._fp, "closed", False) + ): return self._fp.flush() def readable(self): @@ -578,17 +674,17 @@ class HTTPResponse(io.IOBase): if len(temp) == 0: return 0 else: - b[:len(temp)] = temp + b[: len(temp)] = temp return len(temp) def supports_chunked_reads(self): """ Checks if the underlying file-like object looks like a - httplib.HTTPResponse object. We do this by testing for the fp - attribute. If it is present we assume it returns raw chunks as + :class:`http.client.HTTPResponse` object. We do this by testing for + the fp attribute. If it is present we assume it returns raw chunks as processed by read_chunked(). """ - return hasattr(self._fp, 'fp') + return hasattr(self._fp, "fp") def _update_chunk_length(self): # First, we'll figure out length of a chunk and then @@ -596,13 +692,13 @@ class HTTPResponse(io.IOBase): if self.chunk_left is not None: return line = self._fp.fp.readline() - line = line.split(b';', 1)[0] + line = line.split(b";", 1)[0] try: self.chunk_left = int(line, 16) except ValueError: # Invalid chunked protocol response, abort. self.close() - raise httplib.IncompleteRead(line) + raise InvalidChunkLength(self, line) def _handle_chunk(self, amt): returned_chunk = None @@ -645,11 +741,13 @@ class HTTPResponse(io.IOBase): if not self.chunked: raise ResponseNotChunked( "Response is not chunked. " - "Header 'transfer-encoding: chunked' is missing.") + "Header 'transfer-encoding: chunked' is missing." + ) if not self.supports_chunked_reads(): raise BodyNotHttplibCompatible( - "Body should be httplib.HTTPResponse like. " - "It should have have an fp attribute which returns raw chunks.") + "Body should be http.client.HTTPResponse like. " + "It should have have an fp attribute which returns raw chunks." + ) with self._error_catcher(): # Don't bother reading the body of a HEAD request. @@ -667,8 +765,9 @@ class HTTPResponse(io.IOBase): if self.chunk_left == 0: break chunk = self._handle_chunk(amt) - decoded = self._decode(chunk, decode_content=decode_content, - flush_decoder=False) + decoded = self._decode( + chunk, decode_content=decode_content, flush_decoder=False + ) if decoded: yield decoded @@ -686,7 +785,7 @@ class HTTPResponse(io.IOBase): if not line: # Some sites may not end with '\r\n'. break - if line == b'\r\n': + if line == b"\r\n": break # We read everything; close the "file". @@ -703,3 +802,20 @@ class HTTPResponse(io.IOBase): return self.retries.history[-1].redirect_location else: return self._request_url + + def __iter__(self): + buffer = [] + for chunk in self.stream(decode_content=True): + if b"\n" in chunk: + chunk = chunk.split(b"\n") + yield b"".join(buffer) + chunk[0] + b"\n" + for x in chunk[1:-1]: + yield x + b"\n" + if chunk[-1]: + buffer = [chunk[-1]] + else: + buffer = [] + else: + buffer.append(chunk) + if buffer: + yield b"".join(buffer) diff --git a/pipenv/patched/notpip/_vendor/urllib3/util/__init__.py b/pipenv/patched/notpip/_vendor/urllib3/util/__init__.py index 2f2770b6..4547fc52 100644 --- a/pipenv/patched/notpip/_vendor/urllib3/util/__init__.py +++ b/pipenv/patched/notpip/_vendor/urllib3/util/__init__.py @@ -1,54 +1,49 @@ from __future__ import absolute_import + # For backwards compatibility, provide imports that used to be here. from .connection import is_connection_dropped -from .request import make_headers +from .request import SKIP_HEADER, SKIPPABLE_HEADERS, make_headers from .response import is_fp_closed +from .retry import Retry from .ssl_ import ( - SSLContext, + ALPN_PROTOCOLS, HAS_SNI, IS_PYOPENSSL, IS_SECURETRANSPORT, + PROTOCOL_TLS, + SSLContext, assert_fingerprint, resolve_cert_reqs, resolve_ssl_version, ssl_wrap_socket, ) -from .timeout import ( - current_time, - Timeout, -) - -from .retry import Retry -from .url import ( - get_host, - parse_url, - split_first, - Url, -) -from .wait import ( - wait_for_read, - wait_for_write -) +from .timeout import Timeout, current_time +from .url import Url, get_host, parse_url, split_first +from .wait import wait_for_read, wait_for_write __all__ = ( - 'HAS_SNI', - 'IS_PYOPENSSL', - 'IS_SECURETRANSPORT', - 'SSLContext', - 'Retry', - 'Timeout', - 'Url', - 'assert_fingerprint', - 'current_time', - 'is_connection_dropped', - 'is_fp_closed', - 'get_host', - 'parse_url', - 'make_headers', - 'resolve_cert_reqs', - 'resolve_ssl_version', - 'split_first', - 'ssl_wrap_socket', - 'wait_for_read', - 'wait_for_write' + "HAS_SNI", + "IS_PYOPENSSL", + "IS_SECURETRANSPORT", + "SSLContext", + "PROTOCOL_TLS", + "ALPN_PROTOCOLS", + "Retry", + "Timeout", + "Url", + "assert_fingerprint", + "current_time", + "is_connection_dropped", + "is_fp_closed", + "get_host", + "parse_url", + "make_headers", + "resolve_cert_reqs", + "resolve_ssl_version", + "split_first", + "ssl_wrap_socket", + "wait_for_read", + "wait_for_write", + "SKIP_HEADER", + "SKIPPABLE_HEADERS", ) diff --git a/pipenv/patched/notpip/_vendor/urllib3/util/connection.py b/pipenv/patched/notpip/_vendor/urllib3/util/connection.py index 5ad70b2f..c4ca0e29 100644 --- a/pipenv/patched/notpip/_vendor/urllib3/util/connection.py +++ b/pipenv/patched/notpip/_vendor/urllib3/util/connection.py @@ -1,7 +1,12 @@ from __future__ import absolute_import + import socket -from .wait import NoWayToWaitForSocketError, wait_for_read + +from pipenv.patched.notpip._vendor.urllib3.exceptions import LocationParseError + from ..contrib import _appengine_environ +from ..packages import six +from .wait import NoWayToWaitForSocketError, wait_for_read def is_connection_dropped(conn): # Platform-specific @@ -9,12 +14,12 @@ def is_connection_dropped(conn): # Platform-specific Returns True if the connection is dropped and should be closed. :param conn: - :class:`httplib.HTTPConnection` object. + :class:`http.client.HTTPConnection` object. Note: For platforms like AppEngine, this will always return ``False`` to let the platform handle connection recycling transparently for us. """ - sock = getattr(conn, 'sock', False) + sock = getattr(conn, "sock", False) if sock is False: # Platform-specific: AppEngine return False if sock is None: # Connection already closed (such as by httplib). @@ -30,23 +35,27 @@ def is_connection_dropped(conn): # Platform-specific # library test suite. Added to its signature is only `socket_options`. # One additional modification is that we avoid binding to IPv6 servers # discovered in DNS if the system doesn't have IPv6 functionality. -def create_connection(address, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, - source_address=None, socket_options=None): +def create_connection( + address, + timeout=socket._GLOBAL_DEFAULT_TIMEOUT, + source_address=None, + socket_options=None, +): """Connect to *address* and return the socket object. Convenience function. Connect to *address* (a 2-tuple ``(host, port)``) and return the socket object. Passing the optional *timeout* parameter will set the timeout on the socket instance before attempting to connect. If no *timeout* is supplied, the - global default timeout setting returned by :func:`getdefaulttimeout` + global default timeout setting returned by :func:`socket.getdefaulttimeout` is used. If *source_address* is set it must be a tuple of (host, port) for the socket to bind as a source address before making the connection. An host of '' or port 0 tells the OS to use the default. """ host, port = address - if host.startswith('['): - host = host.strip('[]') + if host.startswith("["): + host = host.strip("[]") err = None # Using the value from allowed_gai_family() in the context of getaddrinfo lets @@ -54,6 +63,13 @@ def create_connection(address, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, # The original create_connection function always returns all records. family = allowed_gai_family() + try: + host.encode("idna") + except UnicodeError: + return six.raise_from( + LocationParseError(u"'%s', label empty or too long" % host), None + ) + for res in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM): af, socktype, proto, canonname, sa = res sock = None @@ -102,7 +118,7 @@ def allowed_gai_family(): def _has_ipv6(host): - """ Returns True if the system can bind an IPv6 address. """ + """Returns True if the system can bind an IPv6 address.""" sock = None has_ipv6 = False @@ -117,7 +133,7 @@ def _has_ipv6(host): # has_ipv6 returns true if cPython was compiled with IPv6 support. # It does not tell us if the system has IPv6 support enabled. To # determine that we must bind to an IPv6 address. - # https://github.com/shazow/urllib3/pull/611 + # https://github.com/urllib3/urllib3/pull/611 # https://bugs.python.org/issue658327 try: sock = socket.socket(socket.AF_INET6) @@ -131,4 +147,4 @@ def _has_ipv6(host): return has_ipv6 -HAS_IPV6 = _has_ipv6('::1') +HAS_IPV6 = _has_ipv6("::1") diff --git a/pipenv/patched/notpip/_vendor/urllib3/util/proxy.py b/pipenv/patched/notpip/_vendor/urllib3/util/proxy.py new file mode 100644 index 00000000..34f884d5 --- /dev/null +++ b/pipenv/patched/notpip/_vendor/urllib3/util/proxy.py @@ -0,0 +1,56 @@ +from .ssl_ import create_urllib3_context, resolve_cert_reqs, resolve_ssl_version + + +def connection_requires_http_tunnel( + proxy_url=None, proxy_config=None, destination_scheme=None +): + """ + Returns True if the connection requires an HTTP CONNECT through the proxy. + + :param URL proxy_url: + URL of the proxy. + :param ProxyConfig proxy_config: + Proxy configuration from poolmanager.py + :param str destination_scheme: + The scheme of the destination. (i.e https, http, etc) + """ + # If we're not using a proxy, no way to use a tunnel. + if proxy_url is None: + return False + + # HTTP destinations never require tunneling, we always forward. + if destination_scheme == "http": + return False + + # Support for forwarding with HTTPS proxies and HTTPS destinations. + if ( + proxy_url.scheme == "https" + and proxy_config + and proxy_config.use_forwarding_for_https + ): + return False + + # Otherwise always use a tunnel. + return True + + +def create_proxy_ssl_context( + ssl_version, cert_reqs, ca_certs=None, ca_cert_dir=None, ca_cert_data=None +): + """ + Generates a default proxy ssl context if one hasn't been provided by the + user. + """ + ssl_context = create_urllib3_context( + ssl_version=resolve_ssl_version(ssl_version), + cert_reqs=resolve_cert_reqs(cert_reqs), + ) + if ( + not ca_certs + and not ca_cert_dir + and not ca_cert_data + and hasattr(ssl_context, "load_default_certs") + ): + ssl_context.load_default_certs() + + return ssl_context diff --git a/pipenv/patched/notpip/_vendor/urllib3/util/queue.py b/pipenv/patched/notpip/_vendor/urllib3/util/queue.py index d3d379a1..41784104 100644 --- a/pipenv/patched/notpip/_vendor/urllib3/util/queue.py +++ b/pipenv/patched/notpip/_vendor/urllib3/util/queue.py @@ -1,4 +1,5 @@ import collections + from ..packages import six from ..packages.six.moves import queue diff --git a/pipenv/patched/notpip/_vendor/urllib3/util/request.py b/pipenv/patched/notpip/_vendor/urllib3/util/request.py index 3ddfcd55..25103383 100644 --- a/pipenv/patched/notpip/_vendor/urllib3/util/request.py +++ b/pipenv/patched/notpip/_vendor/urllib3/util/request.py @@ -1,15 +1,36 @@ from __future__ import absolute_import + from base64 import b64encode -from ..packages.six import b, integer_types from ..exceptions import UnrewindableBodyError +from ..packages.six import b, integer_types + +# Pass as a value within ``headers`` to skip +# emitting some HTTP headers that are added automatically. +# The only headers that are supported are ``Accept-Encoding``, +# ``Host``, and ``User-Agent``. +SKIP_HEADER = "@@@SKIP_HEADER@@@" +SKIPPABLE_HEADERS = frozenset(["accept-encoding", "host", "user-agent"]) + +ACCEPT_ENCODING = "gzip,deflate" +try: + import brotli as _unused_module_brotli # noqa: F401 +except ImportError: + pass +else: + ACCEPT_ENCODING += ",br" -ACCEPT_ENCODING = 'gzip,deflate' _FAILEDTELL = object() -def make_headers(keep_alive=None, accept_encoding=None, user_agent=None, - basic_auth=None, proxy_basic_auth=None, disable_cache=None): +def make_headers( + keep_alive=None, + accept_encoding=None, + user_agent=None, + basic_auth=None, + proxy_basic_auth=None, + disable_cache=None, +): """ Shortcuts for generating request headers. @@ -49,27 +70,27 @@ def make_headers(keep_alive=None, accept_encoding=None, user_agent=None, if isinstance(accept_encoding, str): pass elif isinstance(accept_encoding, list): - accept_encoding = ','.join(accept_encoding) + accept_encoding = ",".join(accept_encoding) else: accept_encoding = ACCEPT_ENCODING - headers['accept-encoding'] = accept_encoding + headers["accept-encoding"] = accept_encoding if user_agent: - headers['user-agent'] = user_agent + headers["user-agent"] = user_agent if keep_alive: - headers['connection'] = 'keep-alive' + headers["connection"] = "keep-alive" if basic_auth: - headers['authorization'] = 'Basic ' + \ - b64encode(b(basic_auth)).decode('utf-8') + headers["authorization"] = "Basic " + b64encode(b(basic_auth)).decode("utf-8") if proxy_basic_auth: - headers['proxy-authorization'] = 'Basic ' + \ - b64encode(b(proxy_basic_auth)).decode('utf-8') + headers["proxy-authorization"] = "Basic " + b64encode( + b(proxy_basic_auth) + ).decode("utf-8") if disable_cache: - headers['cache-control'] = 'no-cache' + headers["cache-control"] = "no-cache" return headers @@ -81,7 +102,7 @@ def set_file_position(body, pos): """ if pos is not None: rewind_body(body, pos) - elif getattr(body, 'tell', None) is not None: + elif getattr(body, "tell", None) is not None: try: pos = body.tell() except (IOError, OSError): @@ -103,16 +124,20 @@ def rewind_body(body, body_pos): :param int pos: Position to seek to in file. """ - body_seek = getattr(body, 'seek', None) + body_seek = getattr(body, "seek", None) if body_seek is not None and isinstance(body_pos, integer_types): try: body_seek(body_pos) except (IOError, OSError): - raise UnrewindableBodyError("An error occurred when rewinding request " - "body for redirect/retry.") + raise UnrewindableBodyError( + "An error occurred when rewinding request body for redirect/retry." + ) elif body_pos is _FAILEDTELL: - raise UnrewindableBodyError("Unable to record file position for rewinding " - "request body during a redirect/retry.") + raise UnrewindableBodyError( + "Unable to record file position for rewinding " + "request body during a redirect/retry." + ) else: - raise ValueError("body_pos must be of type integer, " - "instead it was %s." % type(body_pos)) + raise ValueError( + "body_pos must be of type integer, instead it was %s." % type(body_pos) + ) diff --git a/pipenv/patched/notpip/_vendor/urllib3/util/response.py b/pipenv/patched/notpip/_vendor/urllib3/util/response.py index 3d548648..5ea609cc 100644 --- a/pipenv/patched/notpip/_vendor/urllib3/util/response.py +++ b/pipenv/patched/notpip/_vendor/urllib3/util/response.py @@ -1,7 +1,9 @@ from __future__ import absolute_import -from ..packages.six.moves import http_client as httplib + +from email.errors import MultipartInvariantViolationDefect, StartBoundaryNotFoundDefect from ..exceptions import HeaderParsingError +from ..packages.six.moves import http_client as httplib def is_fp_closed(obj): @@ -42,8 +44,7 @@ def assert_header_parsing(headers): Only works on Python 3. - :param headers: Headers to verify. - :type headers: `httplib.HTTPMessage`. + :param http.client.HTTPMessage headers: Headers to verify. :raises urllib3.exceptions.HeaderParsingError: If parsing errors are found. @@ -52,11 +53,10 @@ def assert_header_parsing(headers): # This will fail silently if we pass in the wrong kind of parameter. # To make debugging easier add an explicit check. if not isinstance(headers, httplib.HTTPMessage): - raise TypeError('expected httplib.Message, got {0}.'.format( - type(headers))) + raise TypeError("expected httplib.Message, got {0}.".format(type(headers))) - defects = getattr(headers, 'defects', None) - get_payload = getattr(headers, 'get_payload', None) + defects = getattr(headers, "defects", None) + get_payload = getattr(headers, "get_payload", None) unparsed_data = None if get_payload: @@ -67,6 +67,25 @@ def assert_header_parsing(headers): if isinstance(payload, (bytes, str)): unparsed_data = payload + if defects: + # httplib is assuming a response body is available + # when parsing headers even when httplib only sends + # header data to parse_headers() This results in + # defects on multipart responses in particular. + # See: https://github.com/urllib3/urllib3/issues/800 + + # So we ignore the following defects: + # - StartBoundaryNotFoundDefect: + # The claimed start boundary was never found. + # - MultipartInvariantViolationDefect: + # A message claimed to be a multipart but no subparts were found. + defects = [ + defect + for defect in defects + if not isinstance( + defect, (StartBoundaryNotFoundDefect, MultipartInvariantViolationDefect) + ) + ] if defects or unparsed_data: raise HeaderParsingError(defects=defects, unparsed_data=unparsed_data) @@ -77,11 +96,12 @@ def is_response_to_head(response): Checks whether the request of a response has been a HEAD-request. Handles the quirks of AppEngine. - :param conn: - :type conn: :class:`httplib.HTTPResponse` + :param http.client.HTTPResponse response: + Response to check if the originating request + used 'HEAD' as a method. """ # FIXME: Can we do this somehow without accessing private httplib _method? method = response._method if isinstance(method, int): # Platform-specific: Appengine return method == 3 - return method.upper() == 'HEAD' + return method.upper() == "HEAD" diff --git a/pipenv/patched/notpip/_vendor/urllib3/util/retry.py b/pipenv/patched/notpip/_vendor/urllib3/util/retry.py index e7d0abd6..c7dc42f1 100644 --- a/pipenv/patched/notpip/_vendor/urllib3/util/retry.py +++ b/pipenv/patched/notpip/_vendor/urllib3/util/retry.py @@ -1,32 +1,78 @@ from __future__ import absolute_import -import time + +import email import logging +import re +import time +import warnings from collections import namedtuple from itertools import takewhile -import email -import re from ..exceptions import ( ConnectTimeoutError, + InvalidHeader, MaxRetryError, ProtocolError, + ProxyError, ReadTimeoutError, ResponseError, - InvalidHeader, ) from ..packages import six - log = logging.getLogger(__name__) # Data structure for representing the metadata of requests that result in a retry. -RequestHistory = namedtuple('RequestHistory', ["method", "url", "error", - "status", "redirect_location"]) +RequestHistory = namedtuple( + "RequestHistory", ["method", "url", "error", "status", "redirect_location"] +) +# TODO: In v2 we can remove this sentinel and metaclass with deprecated options. +_Default = object() + + +class _RetryMeta(type): + @property + def DEFAULT_METHOD_WHITELIST(cls): + warnings.warn( + "Using 'Retry.DEFAULT_METHOD_WHITELIST' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_ALLOWED_METHODS' instead", + DeprecationWarning, + ) + return cls.DEFAULT_ALLOWED_METHODS + + @DEFAULT_METHOD_WHITELIST.setter + def DEFAULT_METHOD_WHITELIST(cls, value): + warnings.warn( + "Using 'Retry.DEFAULT_METHOD_WHITELIST' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_ALLOWED_METHODS' instead", + DeprecationWarning, + ) + cls.DEFAULT_ALLOWED_METHODS = value + + @property + def DEFAULT_REDIRECT_HEADERS_BLACKLIST(cls): + warnings.warn( + "Using 'Retry.DEFAULT_REDIRECT_HEADERS_BLACKLIST' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_REMOVE_HEADERS_ON_REDIRECT' instead", + DeprecationWarning, + ) + return cls.DEFAULT_REMOVE_HEADERS_ON_REDIRECT + + @DEFAULT_REDIRECT_HEADERS_BLACKLIST.setter + def DEFAULT_REDIRECT_HEADERS_BLACKLIST(cls, value): + warnings.warn( + "Using 'Retry.DEFAULT_REDIRECT_HEADERS_BLACKLIST' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_REMOVE_HEADERS_ON_REDIRECT' instead", + DeprecationWarning, + ) + cls.DEFAULT_REMOVE_HEADERS_ON_REDIRECT = value + + +@six.add_metaclass(_RetryMeta) class Retry(object): - """ Retry configuration. + """Retry configuration. Each retry attempt will create a new Retry object with updated values, so they can be safely reused. @@ -52,8 +98,7 @@ class Retry(object): Total number of retries to allow. Takes precedence over other counts. Set to ``None`` to remove this constraint and fall back on other - counts. It's a good idea to set this to some sensibly-high value to - account for unexpected edge cases and avoid infinite retry loops. + counts. Set to ``0`` to fail on the first retry. @@ -94,18 +139,35 @@ class Retry(object): Set to ``0`` to fail on the first retry of this type. - :param iterable method_whitelist: + :param int other: + How many times to retry on other errors. + + Other errors are errors that are not connect, read, redirect or status errors. + These errors might be raised after the request was sent to the server, so the + request might have side-effects. + + Set to ``0`` to fail on the first retry of this type. + + If ``total`` is not set, it's a good idea to set this to 0 to account + for unexpected edge cases and avoid infinite retry loops. + + :param iterable allowed_methods: Set of uppercased HTTP method verbs that we should retry on. By default, we only retry on methods which are considered to be idempotent (multiple requests with the same parameters end with the - same state). See :attr:`Retry.DEFAULT_METHOD_WHITELIST`. + same state). See :attr:`Retry.DEFAULT_ALLOWED_METHODS`. Set to a ``False`` value to retry on any verb. + .. warning:: + + Previously this parameter was named ``method_whitelist``, that + usage is deprecated in v1.26.0 and will be removed in v2.0. + :param iterable status_forcelist: A set of integer HTTP status codes that we should force a retry on. - A retry is initiated if the request method is in ``method_whitelist`` + A retry is initiated if the request method is in ``allowed_methods`` and the response status code is in ``status_forcelist``. By default, this is disabled with ``None``. @@ -146,26 +208,64 @@ class Retry(object): request. """ - DEFAULT_METHOD_WHITELIST = frozenset([ - 'HEAD', 'GET', 'PUT', 'DELETE', 'OPTIONS', 'TRACE']) + #: Default methods to be used for ``allowed_methods`` + DEFAULT_ALLOWED_METHODS = frozenset( + ["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE"] + ) + #: Default status codes to be used for ``status_forcelist`` RETRY_AFTER_STATUS_CODES = frozenset([413, 429, 503]) - DEFAULT_REDIRECT_HEADERS_BLACKLIST = frozenset(['Authorization']) + #: Default headers to be used for ``remove_headers_on_redirect`` + DEFAULT_REMOVE_HEADERS_ON_REDIRECT = frozenset(["Authorization"]) #: Maximum backoff time. BACKOFF_MAX = 120 - def __init__(self, total=10, connect=None, read=None, redirect=None, status=None, - method_whitelist=DEFAULT_METHOD_WHITELIST, status_forcelist=None, - backoff_factor=0, raise_on_redirect=True, raise_on_status=True, - history=None, respect_retry_after_header=True, - remove_headers_on_redirect=DEFAULT_REDIRECT_HEADERS_BLACKLIST): + def __init__( + self, + total=10, + connect=None, + read=None, + redirect=None, + status=None, + other=None, + allowed_methods=_Default, + status_forcelist=None, + backoff_factor=0, + raise_on_redirect=True, + raise_on_status=True, + history=None, + respect_retry_after_header=True, + remove_headers_on_redirect=_Default, + # TODO: Deprecated, remove in v2.0 + method_whitelist=_Default, + ): + + if method_whitelist is not _Default: + if allowed_methods is not _Default: + raise ValueError( + "Using both 'allowed_methods' and " + "'method_whitelist' together is not allowed. " + "Instead only use 'allowed_methods'" + ) + warnings.warn( + "Using 'method_whitelist' with Retry is deprecated and " + "will be removed in v2.0. Use 'allowed_methods' instead", + DeprecationWarning, + stacklevel=2, + ) + allowed_methods = method_whitelist + if allowed_methods is _Default: + allowed_methods = self.DEFAULT_ALLOWED_METHODS + if remove_headers_on_redirect is _Default: + remove_headers_on_redirect = self.DEFAULT_REMOVE_HEADERS_ON_REDIRECT self.total = total self.connect = connect self.read = read self.status = status + self.other = other if redirect is False or total is False: redirect = 0 @@ -173,32 +273,55 @@ class Retry(object): self.redirect = redirect self.status_forcelist = status_forcelist or set() - self.method_whitelist = method_whitelist + self.allowed_methods = allowed_methods self.backoff_factor = backoff_factor self.raise_on_redirect = raise_on_redirect self.raise_on_status = raise_on_status self.history = history or tuple() self.respect_retry_after_header = respect_retry_after_header - self.remove_headers_on_redirect = remove_headers_on_redirect + self.remove_headers_on_redirect = frozenset( + [h.lower() for h in remove_headers_on_redirect] + ) def new(self, **kw): params = dict( total=self.total, - connect=self.connect, read=self.read, redirect=self.redirect, status=self.status, - method_whitelist=self.method_whitelist, + connect=self.connect, + read=self.read, + redirect=self.redirect, + status=self.status, + other=self.other, status_forcelist=self.status_forcelist, backoff_factor=self.backoff_factor, raise_on_redirect=self.raise_on_redirect, raise_on_status=self.raise_on_status, history=self.history, - remove_headers_on_redirect=self.remove_headers_on_redirect + remove_headers_on_redirect=self.remove_headers_on_redirect, + respect_retry_after_header=self.respect_retry_after_header, ) + + # TODO: If already given in **kw we use what's given to us + # If not given we need to figure out what to pass. We decide + # based on whether our class has the 'method_whitelist' property + # and if so we pass the deprecated 'method_whitelist' otherwise + # we use 'allowed_methods'. Remove in v2.0 + if "method_whitelist" not in kw and "allowed_methods" not in kw: + if "method_whitelist" in self.__dict__: + warnings.warn( + "Using 'method_whitelist' with Retry is deprecated and " + "will be removed in v2.0. Use 'allowed_methods' instead", + DeprecationWarning, + ) + params["method_whitelist"] = self.allowed_methods + else: + params["allowed_methods"] = self.allowed_methods + params.update(kw) return type(self)(**params) @classmethod def from_int(cls, retries, redirect=True, default=None): - """ Backwards-compatibility for the old retries format.""" + """Backwards-compatibility for the old retries format.""" if retries is None: retries = default if default is not None else cls.DEFAULT @@ -211,13 +334,16 @@ class Retry(object): return new_retries def get_backoff_time(self): - """ Formula for computing the current backoff + """Formula for computing the current backoff :rtype: float """ # We want to consider only the last consecutive errors sequence (Ignore redirects). - consecutive_errors_len = len(list(takewhile(lambda x: x.redirect_location is None, - reversed(self.history)))) + consecutive_errors_len = len( + list( + takewhile(lambda x: x.redirect_location is None, reversed(self.history)) + ) + ) if consecutive_errors_len <= 1: return 0 @@ -229,10 +355,17 @@ class Retry(object): if re.match(r"^\s*[0-9]+\s*$", retry_after): seconds = int(retry_after) else: - retry_date_tuple = email.utils.parsedate(retry_after) + retry_date_tuple = email.utils.parsedate_tz(retry_after) if retry_date_tuple is None: raise InvalidHeader("Invalid Retry-After header: %s" % retry_after) - retry_date = time.mktime(retry_date_tuple) + if retry_date_tuple[9] is None: # Python 2 + # Assume UTC if no timezone was specified + # On Python2.7, parsedate_tz returns None for a timezone offset + # instead of 0 if no timezone is given, where mktime_tz treats + # a None timezone offset as local time. + retry_date_tuple = retry_date_tuple[:9] + (0,) + retry_date_tuple[10:] + + retry_date = email.utils.mktime_tz(retry_date_tuple) seconds = retry_date - time.time() if seconds < 0: @@ -241,7 +374,7 @@ class Retry(object): return seconds def get_retry_after(self, response): - """ Get the value of Retry-After in seconds. """ + """Get the value of Retry-After in seconds.""" retry_after = response.getheader("Retry-After") @@ -265,7 +398,7 @@ class Retry(object): time.sleep(backoff) def sleep(self, response=None): - """ Sleep between retry attempts. + """Sleep between retry attempts. This method will respect a server's ``Retry-After`` response header and sleep the duration of the time requested. If that is not present, it @@ -273,7 +406,7 @@ class Retry(object): this method will return immediately. """ - if response: + if self.respect_retry_after_header and response: slept = self.sleep_for_retry(response) if slept: return @@ -281,28 +414,41 @@ class Retry(object): self._sleep_backoff() def _is_connection_error(self, err): - """ Errors when we're fairly sure that the server did not receive the + """Errors when we're fairly sure that the server did not receive the request, so it should be safe to retry. """ + if isinstance(err, ProxyError): + err = err.original_error return isinstance(err, ConnectTimeoutError) def _is_read_error(self, err): - """ Errors that occur after the request has been started, so we should + """Errors that occur after the request has been started, so we should assume that the server began processing it. """ return isinstance(err, (ReadTimeoutError, ProtocolError)) def _is_method_retryable(self, method): - """ Checks if a given HTTP method should be retried upon, depending if - it is included on the method whitelist. + """Checks if a given HTTP method should be retried upon, depending if + it is included in the allowed_methods """ - if self.method_whitelist and method.upper() not in self.method_whitelist: - return False + # TODO: For now favor if the Retry implementation sets its own method_whitelist + # property outside of our constructor to avoid breaking custom implementations. + if "method_whitelist" in self.__dict__: + warnings.warn( + "Using 'method_whitelist' with Retry is deprecated and " + "will be removed in v2.0. Use 'allowed_methods' instead", + DeprecationWarning, + ) + allowed_methods = self.method_whitelist + else: + allowed_methods = self.allowed_methods + if allowed_methods and method.upper() not in allowed_methods: + return False return True def is_retry(self, method, status_code, has_retry_after=False): - """ Is this method/status code retryable? (Based on whitelists and control + """Is this method/status code retryable? (Based on allowlists and control variables such as the number of total retries to allow, whether to respect the Retry-After header, whether this header is present, and whether the returned status code is on the list of status codes to @@ -314,21 +460,39 @@ class Retry(object): if self.status_forcelist and status_code in self.status_forcelist: return True - return (self.total and self.respect_retry_after_header and - has_retry_after and (status_code in self.RETRY_AFTER_STATUS_CODES)) + return ( + self.total + and self.respect_retry_after_header + and has_retry_after + and (status_code in self.RETRY_AFTER_STATUS_CODES) + ) def is_exhausted(self): - """ Are we out of retries? """ - retry_counts = (self.total, self.connect, self.read, self.redirect, self.status) + """Are we out of retries?""" + retry_counts = ( + self.total, + self.connect, + self.read, + self.redirect, + self.status, + self.other, + ) retry_counts = list(filter(None, retry_counts)) if not retry_counts: return False return min(retry_counts) < 0 - def increment(self, method=None, url=None, response=None, error=None, - _pool=None, _stacktrace=None): - """ Return a new Retry object with incremented retry counters. + def increment( + self, + method=None, + url=None, + response=None, + error=None, + _pool=None, + _stacktrace=None, + ): + """Return a new Retry object with incremented retry counters. :param response: A response object, or None, if the server did not return a response. @@ -350,7 +514,8 @@ class Retry(object): read = self.read redirect = self.redirect status_count = self.status - cause = 'unknown' + other = self.other + cause = "unknown" status = None redirect_location = None @@ -368,31 +533,42 @@ class Retry(object): elif read is not None: read -= 1 + elif error: + # Other retry? + if other is not None: + other -= 1 + elif response and response.get_redirect_location(): # Redirect retry? if redirect is not None: redirect -= 1 - cause = 'too many redirects' + cause = "too many redirects" redirect_location = response.get_redirect_location() status = response.status else: # Incrementing because of a server error like a 500 in - # status_forcelist and a the given method is in the whitelist + # status_forcelist and the given method is in the allowed_methods cause = ResponseError.GENERIC_ERROR if response and response.status: if status_count is not None: status_count -= 1 - cause = ResponseError.SPECIFIC_ERROR.format( - status_code=response.status) + cause = ResponseError.SPECIFIC_ERROR.format(status_code=response.status) status = response.status - history = self.history + (RequestHistory(method, url, error, status, redirect_location),) + history = self.history + ( + RequestHistory(method, url, error, status, redirect_location), + ) new_retry = self.new( total=total, - connect=connect, read=read, redirect=redirect, status=status_count, - history=history) + connect=connect, + read=read, + redirect=redirect, + status=status_count, + other=other, + history=history, + ) if new_retry.is_exhausted(): raise MaxRetryError(_pool, url, error or ResponseError(cause)) @@ -402,9 +578,24 @@ class Retry(object): return new_retry def __repr__(self): - return ('{cls.__name__}(total={self.total}, connect={self.connect}, ' - 'read={self.read}, redirect={self.redirect}, status={self.status})').format( - cls=type(self), self=self) + return ( + "{cls.__name__}(total={self.total}, connect={self.connect}, " + "read={self.read}, redirect={self.redirect}, status={self.status})" + ).format(cls=type(self), self=self) + + def __getattr__(self, item): + if item == "method_whitelist": + # TODO: Remove this deprecated alias in v2.0 + warnings.warn( + "Using 'method_whitelist' with Retry is deprecated and " + "will be removed in v2.0. Use 'allowed_methods' instead", + DeprecationWarning, + ) + return self.allowed_methods + try: + return getattr(super(Retry, self), item) + except AttributeError: + return getattr(Retry, item) # For backwards compatibility (equivalent to pre-v1.9): diff --git a/pipenv/patched/notpip/_vendor/urllib3/util/ssl_.py b/pipenv/patched/notpip/_vendor/urllib3/util/ssl_.py index b16d6523..fceed5ee 100644 --- a/pipenv/patched/notpip/_vendor/urllib3/util/ssl_.py +++ b/pipenv/patched/notpip/_vendor/urllib3/util/ssl_.py @@ -1,27 +1,30 @@ from __future__ import absolute_import -import errno -import warnings -import hmac -import socket +import hmac +import os +import sys +import warnings from binascii import hexlify, unhexlify from hashlib import md5, sha1, sha256 -from ..exceptions import SSLError, InsecurePlatformWarning, SNIMissingWarning +from ..exceptions import ( + InsecurePlatformWarning, + ProxySchemeUnsupported, + SNIMissingWarning, + SSLError, +) from ..packages import six - +from .url import BRACELESS_IPV6_ADDRZ_RE, IPV4_RE SSLContext = None +SSLTransport = None HAS_SNI = False IS_PYOPENSSL = False IS_SECURETRANSPORT = False +ALPN_PROTOCOLS = ["http/1.1"] # Maps the length of a digest to a possible hash function producing this digest -HASHFUNC_MAP = { - 32: md5, - 40: sha1, - 64: sha256, -} +HASHFUNC_MAP = {32: md5, 40: sha1, 64: sha256} def _const_compare_digest_backport(a, b): @@ -32,47 +35,59 @@ def _const_compare_digest_backport(a, b): Returns True if the digests match, and False otherwise. """ result = abs(len(a) - len(b)) - for l, r in zip(bytearray(a), bytearray(b)): - result |= l ^ r + for left, right in zip(bytearray(a), bytearray(b)): + result |= left ^ right return result == 0 -_const_compare_digest = getattr(hmac, 'compare_digest', - _const_compare_digest_backport) - +_const_compare_digest = getattr(hmac, "compare_digest", _const_compare_digest_backport) try: # Test for SSL features import ssl - from ssl import wrap_socket, CERT_NONE, PROTOCOL_SSLv23 + from ssl import CERT_REQUIRED, wrap_socket +except ImportError: + pass + +try: from ssl import HAS_SNI # Has SNI? except ImportError: pass +try: + from .ssltransport import SSLTransport +except ImportError: + pass + + +try: # Platform-specific: Python 3.6 + from ssl import PROTOCOL_TLS + + PROTOCOL_SSLv23 = PROTOCOL_TLS +except ImportError: + try: + from ssl import PROTOCOL_SSLv23 as PROTOCOL_TLS + + PROTOCOL_SSLv23 = PROTOCOL_TLS + except ImportError: + PROTOCOL_SSLv23 = PROTOCOL_TLS = 2 try: - from ssl import OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_COMPRESSION + from ssl import PROTOCOL_TLS_CLIENT +except ImportError: + PROTOCOL_TLS_CLIENT = PROTOCOL_TLS + + +try: + from ssl import OP_NO_COMPRESSION, OP_NO_SSLv2, OP_NO_SSLv3 except ImportError: OP_NO_SSLv2, OP_NO_SSLv3 = 0x1000000, 0x2000000 OP_NO_COMPRESSION = 0x20000 -# Python 2.7 doesn't have inet_pton on non-Linux so we fallback on inet_aton in -# those cases. This means that we can only detect IPv4 addresses in this case. -if hasattr(socket, 'inet_pton'): - inet_pton = socket.inet_pton -else: - # Maybe we can use ipaddress if the user has urllib3[secure]? - try: - from pipenv.patched.notpip._vendor import ipaddress - - def inet_pton(_, host): - if isinstance(host, bytes): - host = host.decode('ascii') - return ipaddress.ip_address(host) - - except ImportError: # Platform-specific: Non-Linux - def inet_pton(_, host): - return socket.inet_aton(host) +try: # OP_NO_TICKET was added in Python 3.6 + from ssl import OP_NO_TICKET +except ImportError: + OP_NO_TICKET = 0x4000 # A secure default. @@ -83,36 +98,37 @@ else: # - https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/ # # The general intent is: -# - Prefer TLS 1.3 cipher suites # - prefer cipher suites that offer perfect forward secrecy (DHE/ECDHE), # - prefer ECDHE over DHE for better performance, # - prefer any AES-GCM and ChaCha20 over any AES-CBC for better performance and # security, # - prefer AES-GCM over ChaCha20 because hardware-accelerated AES is common, -# - disable NULL authentication, MD5 MACs and DSS for security reasons. -DEFAULT_CIPHERS = ':'.join([ - 'TLS13-AES-256-GCM-SHA384', - 'TLS13-CHACHA20-POLY1305-SHA256', - 'TLS13-AES-128-GCM-SHA256', - 'ECDH+AESGCM', - 'ECDH+CHACHA20', - 'DH+AESGCM', - 'DH+CHACHA20', - 'ECDH+AES256', - 'DH+AES256', - 'ECDH+AES128', - 'DH+AES', - 'RSA+AESGCM', - 'RSA+AES', - '!aNULL', - '!eNULL', - '!MD5', -]) +# - disable NULL authentication, MD5 MACs, DSS, and other +# insecure ciphers for security reasons. +# - NOTE: TLS 1.3 cipher suites are managed through a different interface +# not exposed by CPython (yet!) and are enabled by default if they're available. +DEFAULT_CIPHERS = ":".join( + [ + "ECDHE+AESGCM", + "ECDHE+CHACHA20", + "DHE+AESGCM", + "DHE+CHACHA20", + "ECDH+AESGCM", + "DH+AESGCM", + "ECDH+AES", + "DH+AES", + "RSA+AESGCM", + "RSA+AES", + "!aNULL", + "!eNULL", + "!MD5", + "!DSS", + ] +) try: from ssl import SSLContext # Modern SSL? except ImportError: - import sys class SSLContext(object): # Platform-specific: Python 2 def __init__(self, protocol_version): @@ -130,32 +146,35 @@ except ImportError: self.certfile = certfile self.keyfile = keyfile - def load_verify_locations(self, cafile=None, capath=None): + def load_verify_locations(self, cafile=None, capath=None, cadata=None): self.ca_certs = cafile if capath is not None: raise SSLError("CA directories not supported in older Pythons") + if cadata is not None: + raise SSLError("CA data not supported in older Pythons") + def set_ciphers(self, cipher_suite): self.ciphers = cipher_suite def wrap_socket(self, socket, server_hostname=None, server_side=False): warnings.warn( - 'A true SSLContext object is not available. This prevents ' - 'urllib3 from configuring SSL appropriately and may cause ' - 'certain SSL connections to fail. You can upgrade to a newer ' - 'version of Python to solve this. For more information, see ' - 'https://urllib3.readthedocs.io/en/latest/advanced-usage.html' - '#ssl-warnings', - InsecurePlatformWarning + "A true SSLContext object is not available. This prevents " + "urllib3 from configuring SSL appropriately and may cause " + "certain SSL connections to fail. You can upgrade to a newer " + "version of Python to solve this. For more information, see " + "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" + "#ssl-warnings", + InsecurePlatformWarning, ) kwargs = { - 'keyfile': self.keyfile, - 'certfile': self.certfile, - 'ca_certs': self.ca_certs, - 'cert_reqs': self.verify_mode, - 'ssl_version': self.protocol, - 'server_side': server_side, + "keyfile": self.keyfile, + "certfile": self.certfile, + "ca_certs": self.ca_certs, + "cert_reqs": self.verify_mode, + "ssl_version": self.protocol, + "server_side": server_side, } return wrap_socket(socket, ciphers=self.ciphers, **kwargs) @@ -170,12 +189,11 @@ def assert_fingerprint(cert, fingerprint): Fingerprint as string of hexdigits, can be interspersed by colons. """ - fingerprint = fingerprint.replace(':', '').lower() + fingerprint = fingerprint.replace(":", "").lower() digest_length = len(fingerprint) hashfunc = HASHFUNC_MAP.get(digest_length) if not hashfunc: - raise SSLError( - 'Fingerprint of invalid length: {0}'.format(fingerprint)) + raise SSLError("Fingerprint of invalid length: {0}".format(fingerprint)) # We need encode() here for py32; works on py2 and p33. fingerprint_bytes = unhexlify(fingerprint.encode()) @@ -183,15 +201,18 @@ def assert_fingerprint(cert, fingerprint): cert_digest = hashfunc(cert).digest() if not _const_compare_digest(cert_digest, fingerprint_bytes): - raise SSLError('Fingerprints did not match. Expected "{0}", got "{1}".' - .format(fingerprint, hexlify(cert_digest))) + raise SSLError( + 'Fingerprints did not match. Expected "{0}", got "{1}".'.format( + fingerprint, hexlify(cert_digest) + ) + ) def resolve_cert_reqs(candidate): """ Resolves the argument to a numeric constant, which can be passed to the wrap_socket function/method from the ssl module. - Defaults to :data:`ssl.CERT_NONE`. + Defaults to :data:`ssl.CERT_REQUIRED`. If given a string it is assumed to be the name of the constant in the :mod:`ssl` module or its abbreviation. (So you can specify `REQUIRED` instead of `CERT_REQUIRED`. @@ -199,12 +220,12 @@ def resolve_cert_reqs(candidate): constant which can directly be passed to wrap_socket. """ if candidate is None: - return CERT_NONE + return CERT_REQUIRED if isinstance(candidate, str): res = getattr(ssl, candidate, None) if res is None: - res = getattr(ssl, 'CERT_' + candidate) + res = getattr(ssl, "CERT_" + candidate) return res return candidate @@ -215,19 +236,20 @@ def resolve_ssl_version(candidate): like resolve_cert_reqs """ if candidate is None: - return PROTOCOL_SSLv23 + return PROTOCOL_TLS if isinstance(candidate, str): res = getattr(ssl, candidate, None) if res is None: - res = getattr(ssl, 'PROTOCOL_' + candidate) + res = getattr(ssl, "PROTOCOL_" + candidate) return res return candidate -def create_urllib3_context(ssl_version=None, cert_reqs=None, - options=None, ciphers=None): +def create_urllib3_context( + ssl_version=None, cert_reqs=None, options=None, ciphers=None +): """All arguments have the same meaning as ``ssl_wrap_socket``. By default, this function does a lot of the same work that @@ -254,14 +276,18 @@ def create_urllib3_context(ssl_version=None, cert_reqs=None, ``ssl.CERT_REQUIRED``. :param options: Specific OpenSSL options. These default to ``ssl.OP_NO_SSLv2``, - ``ssl.OP_NO_SSLv3``, ``ssl.OP_NO_COMPRESSION``. + ``ssl.OP_NO_SSLv3``, ``ssl.OP_NO_COMPRESSION``, and ``ssl.OP_NO_TICKET``. :param ciphers: Which cipher suites to allow the server to select. :returns: Constructed SSLContext object with specified options :rtype: SSLContext """ - context = SSLContext(ssl_version or ssl.PROTOCOL_SSLv23) + # PROTOCOL_TLS is deprecated in Python 3.10 + if not ssl_version or ssl_version == PROTOCOL_TLS: + ssl_version = PROTOCOL_TLS_CLIENT + + context = SSLContext(ssl_version) context.set_ciphers(ciphers or DEFAULT_CIPHERS) @@ -277,21 +303,70 @@ def create_urllib3_context(ssl_version=None, cert_reqs=None, # Disable compression to prevent CRIME attacks for OpenSSL 1.0+ # (issue #309) options |= OP_NO_COMPRESSION + # TLSv1.2 only. Unless set explicitly, do not request tickets. + # This may save some bandwidth on wire, and although the ticket is encrypted, + # there is a risk associated with it being on wire, + # if the server is not rotating its ticketing keys properly. + options |= OP_NO_TICKET context.options |= options - context.verify_mode = cert_reqs - if getattr(context, 'check_hostname', None) is not None: # Platform-specific: Python 3.2 - # We do our own verification, including fingerprints and alternative - # hostnames. So disable it here - context.check_hostname = False + # Enable post-handshake authentication for TLS 1.3, see GH #1634. PHA is + # necessary for conditional client cert authentication with TLS 1.3. + # The attribute is None for OpenSSL <= 1.1.0 or does not exist in older + # versions of Python. We only enable on Python 3.7.4+ or if certificate + # verification is enabled to work around Python issue #37428 + # See: https://bugs.python.org/issue37428 + if (cert_reqs == ssl.CERT_REQUIRED or sys.version_info >= (3, 7, 4)) and getattr( + context, "post_handshake_auth", None + ) is not None: + context.post_handshake_auth = True + + def disable_check_hostname(): + if ( + getattr(context, "check_hostname", None) is not None + ): # Platform-specific: Python 3.2 + # We do our own verification, including fingerprints and alternative + # hostnames. So disable it here + context.check_hostname = False + + # The order of the below lines setting verify_mode and check_hostname + # matter due to safe-guards SSLContext has to prevent an SSLContext with + # check_hostname=True, verify_mode=NONE/OPTIONAL. This is made even more + # complex because we don't know whether PROTOCOL_TLS_CLIENT will be used + # or not so we don't know the initial state of the freshly created SSLContext. + if cert_reqs == ssl.CERT_REQUIRED: + context.verify_mode = cert_reqs + disable_check_hostname() + else: + disable_check_hostname() + context.verify_mode = cert_reqs + + # Enable logging of TLS session keys via defacto standard environment variable + # 'SSLKEYLOGFILE', if the feature is available (Python 3.8+). Skip empty values. + if hasattr(context, "keylog_filename"): + sslkeylogfile = os.environ.get("SSLKEYLOGFILE") + if sslkeylogfile: + context.keylog_filename = sslkeylogfile + return context -def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, - ca_certs=None, server_hostname=None, - ssl_version=None, ciphers=None, ssl_context=None, - ca_cert_dir=None): +def ssl_wrap_socket( + sock, + keyfile=None, + certfile=None, + cert_reqs=None, + ca_certs=None, + server_hostname=None, + ssl_version=None, + ciphers=None, + ssl_context=None, + ca_cert_dir=None, + key_password=None, + ca_cert_data=None, + tls_in_tls=False, +): """ All arguments except for server_hostname, ssl_context, and ca_cert_dir have the same meaning as they do when using :func:`ssl.wrap_socket`. @@ -307,75 +382,114 @@ def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, A directory containing CA certificates in multiple separate files, as supported by OpenSSL's -CApath flag or the capath argument to SSLContext.load_verify_locations(). + :param key_password: + Optional password if the keyfile is encrypted. + :param ca_cert_data: + Optional string containing CA certificates in PEM format suitable for + passing as the cadata parameter to SSLContext.load_verify_locations() + :param tls_in_tls: + Use SSLTransport to wrap the existing socket. """ context = ssl_context if context is None: # Note: This branch of code and all the variables in it are no longer # used by urllib3 itself. We should consider deprecating and removing # this code. - context = create_urllib3_context(ssl_version, cert_reqs, - ciphers=ciphers) + context = create_urllib3_context(ssl_version, cert_reqs, ciphers=ciphers) - if ca_certs or ca_cert_dir: + if ca_certs or ca_cert_dir or ca_cert_data: try: - context.load_verify_locations(ca_certs, ca_cert_dir) - except IOError as e: # Platform-specific: Python 2.7 + context.load_verify_locations(ca_certs, ca_cert_dir, ca_cert_data) + except (IOError, OSError) as e: raise SSLError(e) - # Py33 raises FileNotFoundError which subclasses OSError - # These are not equivalent unless we check the errno attribute - except OSError as e: # Platform-specific: Python 3.3 and beyond - if e.errno == errno.ENOENT: - raise SSLError(e) - raise - elif getattr(context, 'load_default_certs', None) is not None: + + elif ssl_context is None and hasattr(context, "load_default_certs"): # try to load OS default certs; works well on Windows (require Python3.4+) context.load_default_certs() + # Attempt to detect if we get the goofy behavior of the + # keyfile being encrypted and OpenSSL asking for the + # passphrase via the terminal and instead error out. + if keyfile and key_password is None and _is_key_file_encrypted(keyfile): + raise SSLError("Client private key is encrypted, password is required") + if certfile: - context.load_cert_chain(certfile, keyfile) + if key_password is None: + context.load_cert_chain(certfile, keyfile) + else: + context.load_cert_chain(certfile, keyfile, key_password) + + try: + if hasattr(context, "set_alpn_protocols"): + context.set_alpn_protocols(ALPN_PROTOCOLS) + except NotImplementedError: # Defensive: in CI, we always have set_alpn_protocols + pass # If we detect server_hostname is an IP address then the SNI # extension should not be used according to RFC3546 Section 3.1 - # We shouldn't warn the user if SNI isn't available but we would - # not be using SNI anyways due to IP address for server_hostname. - if ((server_hostname is not None and not is_ipaddress(server_hostname)) - or IS_SECURETRANSPORT): - if HAS_SNI and server_hostname is not None: - return context.wrap_socket(sock, server_hostname=server_hostname) - + use_sni_hostname = server_hostname and not is_ipaddress(server_hostname) + # SecureTransport uses server_hostname in certificate verification. + send_sni = (use_sni_hostname and HAS_SNI) or ( + IS_SECURETRANSPORT and server_hostname + ) + # Do not warn the user if server_hostname is an invalid SNI hostname. + if not HAS_SNI and use_sni_hostname: warnings.warn( - 'An HTTPS request has been made, but the SNI (Server Name ' - 'Indication) extension to TLS is not available on this platform. ' - 'This may cause the server to present an incorrect TLS ' - 'certificate, which can cause validation failures. You can upgrade to ' - 'a newer version of Python to solve this. For more information, see ' - 'https://urllib3.readthedocs.io/en/latest/advanced-usage.html' - '#ssl-warnings', - SNIMissingWarning + "An HTTPS request has been made, but the SNI (Server Name " + "Indication) extension to TLS is not available on this platform. " + "This may cause the server to present an incorrect TLS " + "certificate, which can cause validation failures. You can upgrade to " + "a newer version of Python to solve this. For more information, see " + "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" + "#ssl-warnings", + SNIMissingWarning, ) - return context.wrap_socket(sock) + if send_sni: + ssl_sock = _ssl_wrap_socket_impl( + sock, context, tls_in_tls, server_hostname=server_hostname + ) + else: + ssl_sock = _ssl_wrap_socket_impl(sock, context, tls_in_tls) + return ssl_sock def is_ipaddress(hostname): - """Detects whether the hostname given is an IP address. + """Detects whether the hostname given is an IPv4 or IPv6 address. + Also detects IPv6 addresses with Zone IDs. :param str hostname: Hostname to examine. :return: True if the hostname is an IP address, False otherwise. """ - if six.PY3 and isinstance(hostname, bytes): + if not six.PY2 and isinstance(hostname, bytes): # IDN A-label bytes are ASCII compatible. - hostname = hostname.decode('ascii') + hostname = hostname.decode("ascii") + return bool(IPV4_RE.match(hostname) or BRACELESS_IPV6_ADDRZ_RE.match(hostname)) - families = [socket.AF_INET] - if hasattr(socket, 'AF_INET6'): - families.append(socket.AF_INET6) - for af in families: - try: - inet_pton(af, hostname) - except (socket.error, ValueError, OSError): - pass - else: - return True +def _is_key_file_encrypted(key_file): + """Detects if a key file is encrypted or not.""" + with open(key_file, "r") as f: + for line in f: + # Look for Proc-Type: 4,ENCRYPTED + if "ENCRYPTED" in line: + return True + return False + + +def _ssl_wrap_socket_impl(sock, ssl_context, tls_in_tls, server_hostname=None): + if tls_in_tls: + if not SSLTransport: + # Import error, ssl is not available. + raise ProxySchemeUnsupported( + "TLS in TLS requires support for the 'ssl' module" + ) + + SSLTransport._validate_ssl_context_for_tls_in_tls(ssl_context) + return SSLTransport(sock, ssl_context, server_hostname) + + if server_hostname: + return ssl_context.wrap_socket(sock, server_hostname=server_hostname) + else: + return ssl_context.wrap_socket(sock) diff --git a/pipenv/patched/notpip/_vendor/urllib3/util/ssltransport.py b/pipenv/patched/notpip/_vendor/urllib3/util/ssltransport.py new file mode 100644 index 00000000..b45c0208 --- /dev/null +++ b/pipenv/patched/notpip/_vendor/urllib3/util/ssltransport.py @@ -0,0 +1,221 @@ +import io +import socket +import ssl + +from pipenv.patched.notpip._vendor.urllib3.exceptions import ProxySchemeUnsupported +from pipenv.patched.notpip._vendor.urllib3.packages import six + +SSL_BLOCKSIZE = 16384 + + +class SSLTransport: + """ + The SSLTransport wraps an existing socket and establishes an SSL connection. + + Contrary to Python's implementation of SSLSocket, it allows you to chain + multiple TLS connections together. It's particularly useful if you need to + implement TLS within TLS. + + The class supports most of the socket API operations. + """ + + @staticmethod + def _validate_ssl_context_for_tls_in_tls(ssl_context): + """ + Raises a ProxySchemeUnsupported if the provided ssl_context can't be used + for TLS in TLS. + + The only requirement is that the ssl_context provides the 'wrap_bio' + methods. + """ + + if not hasattr(ssl_context, "wrap_bio"): + if six.PY2: + raise ProxySchemeUnsupported( + "TLS in TLS requires SSLContext.wrap_bio() which isn't " + "supported on Python 2" + ) + else: + raise ProxySchemeUnsupported( + "TLS in TLS requires SSLContext.wrap_bio() which isn't " + "available on non-native SSLContext" + ) + + def __init__( + self, socket, ssl_context, server_hostname=None, suppress_ragged_eofs=True + ): + """ + Create an SSLTransport around socket using the provided ssl_context. + """ + self.incoming = ssl.MemoryBIO() + self.outgoing = ssl.MemoryBIO() + + self.suppress_ragged_eofs = suppress_ragged_eofs + self.socket = socket + + self.sslobj = ssl_context.wrap_bio( + self.incoming, self.outgoing, server_hostname=server_hostname + ) + + # Perform initial handshake. + self._ssl_io_loop(self.sslobj.do_handshake) + + def __enter__(self): + return self + + def __exit__(self, *_): + self.close() + + def fileno(self): + return self.socket.fileno() + + def read(self, len=1024, buffer=None): + return self._wrap_ssl_read(len, buffer) + + def recv(self, len=1024, flags=0): + if flags != 0: + raise ValueError("non-zero flags not allowed in calls to recv") + return self._wrap_ssl_read(len) + + def recv_into(self, buffer, nbytes=None, flags=0): + if flags != 0: + raise ValueError("non-zero flags not allowed in calls to recv_into") + if buffer and (nbytes is None): + nbytes = len(buffer) + elif nbytes is None: + nbytes = 1024 + return self.read(nbytes, buffer) + + def sendall(self, data, flags=0): + if flags != 0: + raise ValueError("non-zero flags not allowed in calls to sendall") + count = 0 + with memoryview(data) as view, view.cast("B") as byte_view: + amount = len(byte_view) + while count < amount: + v = self.send(byte_view[count:]) + count += v + + def send(self, data, flags=0): + if flags != 0: + raise ValueError("non-zero flags not allowed in calls to send") + response = self._ssl_io_loop(self.sslobj.write, data) + return response + + def makefile( + self, mode="r", buffering=None, encoding=None, errors=None, newline=None + ): + """ + Python's httpclient uses makefile and buffered io when reading HTTP + messages and we need to support it. + + This is unfortunately a copy and paste of socket.py makefile with small + changes to point to the socket directly. + """ + if not set(mode) <= {"r", "w", "b"}: + raise ValueError("invalid mode %r (only r, w, b allowed)" % (mode,)) + + writing = "w" in mode + reading = "r" in mode or not writing + assert reading or writing + binary = "b" in mode + rawmode = "" + if reading: + rawmode += "r" + if writing: + rawmode += "w" + raw = socket.SocketIO(self, rawmode) + self.socket._io_refs += 1 + if buffering is None: + buffering = -1 + if buffering < 0: + buffering = io.DEFAULT_BUFFER_SIZE + if buffering == 0: + if not binary: + raise ValueError("unbuffered streams must be binary") + return raw + if reading and writing: + buffer = io.BufferedRWPair(raw, raw, buffering) + elif reading: + buffer = io.BufferedReader(raw, buffering) + else: + assert writing + buffer = io.BufferedWriter(raw, buffering) + if binary: + return buffer + text = io.TextIOWrapper(buffer, encoding, errors, newline) + text.mode = mode + return text + + def unwrap(self): + self._ssl_io_loop(self.sslobj.unwrap) + + def close(self): + self.socket.close() + + def getpeercert(self, binary_form=False): + return self.sslobj.getpeercert(binary_form) + + def version(self): + return self.sslobj.version() + + def cipher(self): + return self.sslobj.cipher() + + def selected_alpn_protocol(self): + return self.sslobj.selected_alpn_protocol() + + def selected_npn_protocol(self): + return self.sslobj.selected_npn_protocol() + + def shared_ciphers(self): + return self.sslobj.shared_ciphers() + + def compression(self): + return self.sslobj.compression() + + def settimeout(self, value): + self.socket.settimeout(value) + + def gettimeout(self): + return self.socket.gettimeout() + + def _decref_socketios(self): + self.socket._decref_socketios() + + def _wrap_ssl_read(self, len, buffer=None): + try: + return self._ssl_io_loop(self.sslobj.read, len, buffer) + except ssl.SSLError as e: + if e.errno == ssl.SSL_ERROR_EOF and self.suppress_ragged_eofs: + return 0 # eof, return 0. + else: + raise + + def _ssl_io_loop(self, func, *args): + """Performs an I/O loop between incoming/outgoing and the socket.""" + should_loop = True + ret = None + + while should_loop: + errno = None + try: + ret = func(*args) + except ssl.SSLError as e: + if e.errno not in (ssl.SSL_ERROR_WANT_READ, ssl.SSL_ERROR_WANT_WRITE): + # WANT_READ, and WANT_WRITE are expected, others are not. + raise e + errno = e.errno + + buf = self.outgoing.read() + self.socket.sendall(buf) + + if errno is None: + should_loop = False + elif errno == ssl.SSL_ERROR_WANT_READ: + buf = self.socket.recv(SSL_BLOCKSIZE) + if buf: + self.incoming.write(buf) + else: + self.incoming.write_eof() + return ret diff --git a/pipenv/patched/notpip/_vendor/urllib3/util/timeout.py b/pipenv/patched/notpip/_vendor/urllib3/util/timeout.py index cec817e6..ff69593b 100644 --- a/pipenv/patched/notpip/_vendor/urllib3/util/timeout.py +++ b/pipenv/patched/notpip/_vendor/urllib3/util/timeout.py @@ -1,8 +1,10 @@ from __future__ import absolute_import + +import time + # The default socket timeout, used by httplib to indicate that no timeout was # specified by the user from socket import _GLOBAL_DEFAULT_TIMEOUT -import time from ..exceptions import TimeoutStateError @@ -16,22 +18,28 @@ current_time = getattr(time, "monotonic", time.time) class Timeout(object): - """ Timeout configuration. + """Timeout configuration. - Timeouts can be defined as a default for a pool:: + Timeouts can be defined as a default for a pool: - timeout = Timeout(connect=2.0, read=7.0) - http = PoolManager(timeout=timeout) - response = http.request('GET', 'http://example.com/') + .. code-block:: python - Or per-request (which overrides the default for the pool):: + timeout = Timeout(connect=2.0, read=7.0) + http = PoolManager(timeout=timeout) + response = http.request('GET', 'http://example.com/') - response = http.request('GET', 'http://example.com/', timeout=Timeout(10)) + Or per-request (which overrides the default for the pool): - Timeouts can be disabled by setting all the parameters to ``None``:: + .. code-block:: python - no_timeout = Timeout(connect=None, read=None) - response = http.request('GET', 'http://example.com/, timeout=no_timeout) + response = http.request('GET', 'http://example.com/', timeout=Timeout(10)) + + Timeouts can be disabled by setting all the parameters to ``None``: + + .. code-block:: python + + no_timeout = Timeout(connect=None, read=None) + response = http.request('GET', 'http://example.com/, timeout=no_timeout) :param total: @@ -42,26 +50,27 @@ class Timeout(object): Defaults to None. - :type total: integer, float, or None + :type total: int, float, or None :param connect: - The maximum amount of time to wait for a connection attempt to a server - to succeed. Omitting the parameter will default the connect timeout to - the system default, probably `the global default timeout in socket.py + The maximum amount of time (in seconds) to wait for a connection + attempt to a server to succeed. Omitting the parameter will default the + connect timeout to the system default, probably `the global default + timeout in socket.py <http://hg.python.org/cpython/file/603b4d593758/Lib/socket.py#l535>`_. None will set an infinite timeout for connection attempts. - :type connect: integer, float, or None + :type connect: int, float, or None :param read: - The maximum amount of time to wait between consecutive - read operations for a response from the server. Omitting - the parameter will default the read timeout to the system - default, probably `the global default timeout in socket.py + The maximum amount of time (in seconds) to wait between consecutive + read operations for a response from the server. Omitting the parameter + will default the read timeout to the system default, probably `the + global default timeout in socket.py <http://hg.python.org/cpython/file/603b4d593758/Lib/socket.py#l535>`_. None will set an infinite timeout. - :type read: integer, float, or None + :type read: int, float, or None .. note:: @@ -91,18 +100,25 @@ class Timeout(object): DEFAULT_TIMEOUT = _GLOBAL_DEFAULT_TIMEOUT def __init__(self, total=None, connect=_Default, read=_Default): - self._connect = self._validate_timeout(connect, 'connect') - self._read = self._validate_timeout(read, 'read') - self.total = self._validate_timeout(total, 'total') + self._connect = self._validate_timeout(connect, "connect") + self._read = self._validate_timeout(read, "read") + self.total = self._validate_timeout(total, "total") self._start_connect = None - def __str__(self): - return '%s(connect=%r, read=%r, total=%r)' % ( - type(self).__name__, self._connect, self._read, self.total) + def __repr__(self): + return "%s(connect=%r, read=%r, total=%r)" % ( + type(self).__name__, + self._connect, + self._read, + self.total, + ) + + # __str__ provided for backwards compatibility + __str__ = __repr__ @classmethod def _validate_timeout(cls, value, name): - """ Check that a timeout attribute is valid. + """Check that a timeout attribute is valid. :param value: The timeout value to validate :param name: The name of the timeout attribute to validate. This is @@ -118,28 +134,37 @@ class Timeout(object): return value if isinstance(value, bool): - raise ValueError("Timeout cannot be a boolean value. It must " - "be an int, float or None.") + raise ValueError( + "Timeout cannot be a boolean value. It must " + "be an int, float or None." + ) try: float(value) except (TypeError, ValueError): - raise ValueError("Timeout value %s was %s, but it must be an " - "int, float or None." % (name, value)) + raise ValueError( + "Timeout value %s was %s, but it must be an " + "int, float or None." % (name, value) + ) try: if value <= 0: - raise ValueError("Attempted to set %s timeout to %s, but the " - "timeout cannot be set to a value less " - "than or equal to 0." % (name, value)) - except TypeError: # Python 3 - raise ValueError("Timeout value %s was %s, but it must be an " - "int, float or None." % (name, value)) + raise ValueError( + "Attempted to set %s timeout to %s, but the " + "timeout cannot be set to a value less " + "than or equal to 0." % (name, value) + ) + except TypeError: + # Python 3 + raise ValueError( + "Timeout value %s was %s, but it must be an " + "int, float or None." % (name, value) + ) return value @classmethod def from_float(cls, timeout): - """ Create a new Timeout from a legacy timeout value. + """Create a new Timeout from a legacy timeout value. The timeout value used by httplib.py sets the same timeout on the connect(), and recv() socket requests. This creates a :class:`Timeout` @@ -154,7 +179,7 @@ class Timeout(object): return Timeout(read=timeout, connect=timeout) def clone(self): - """ Create a copy of the timeout object + """Create a copy of the timeout object Timeout properties are stored per-pool but each request needs a fresh Timeout object to ensure each one has its own start/stop configured. @@ -165,11 +190,10 @@ class Timeout(object): # We can't use copy.deepcopy because that will also create a new object # for _GLOBAL_DEFAULT_TIMEOUT, which socket.py uses as a sentinel to # detect the user default. - return Timeout(connect=self._connect, read=self._read, - total=self.total) + return Timeout(connect=self._connect, read=self._read, total=self.total) def start_connect(self): - """ Start the timeout clock, used during a connect() attempt + """Start the timeout clock, used during a connect() attempt :raises urllib3.exceptions.TimeoutStateError: if you attempt to start a timer that has been started already. @@ -180,21 +204,22 @@ class Timeout(object): return self._start_connect def get_connect_duration(self): - """ Gets the time elapsed since the call to :meth:`start_connect`. + """Gets the time elapsed since the call to :meth:`start_connect`. - :return: Elapsed time. + :return: Elapsed time in seconds. :rtype: float :raises urllib3.exceptions.TimeoutStateError: if you attempt to get duration for a timer that hasn't been started. """ if self._start_connect is None: - raise TimeoutStateError("Can't get connect duration for timer " - "that has not started.") + raise TimeoutStateError( + "Can't get connect duration for timer that has not started." + ) return current_time() - self._start_connect @property def connect_timeout(self): - """ Get the value to use when setting a connection timeout. + """Get the value to use when setting a connection timeout. This will be a positive float or integer, the value None (never timeout), or the default system timeout. @@ -212,7 +237,7 @@ class Timeout(object): @property def read_timeout(self): - """ Get the value for the read timeout. + """Get the value for the read timeout. This assumes some time has elapsed in the connection timeout and computes the read timeout appropriately. @@ -227,15 +252,16 @@ class Timeout(object): :raises urllib3.exceptions.TimeoutStateError: If :meth:`start_connect` has not yet been called on this object. """ - if (self.total is not None and - self.total is not self.DEFAULT_TIMEOUT and - self._read is not None and - self._read is not self.DEFAULT_TIMEOUT): + if ( + self.total is not None + and self.total is not self.DEFAULT_TIMEOUT + and self._read is not None + and self._read is not self.DEFAULT_TIMEOUT + ): # In case the connect timeout has not yet been established. if self._start_connect is None: return self._read - return max(0, min(self.total - self.get_connect_duration(), - self._read)) + return max(0, min(self.total - self.get_connect_duration(), self._read)) elif self.total is not None and self.total is not self.DEFAULT_TIMEOUT: return max(0, self.total - self.get_connect_duration()) else: diff --git a/pipenv/patched/notpip/_vendor/urllib3/util/url.py b/pipenv/patched/notpip/_vendor/urllib3/util/url.py index 6b6f9968..6f1d6259 100644 --- a/pipenv/patched/notpip/_vendor/urllib3/util/url.py +++ b/pipenv/patched/notpip/_vendor/urllib3/util/url.py @@ -1,34 +1,110 @@ from __future__ import absolute_import + +import re from collections import namedtuple from ..exceptions import LocationParseError +from ..packages import six - -url_attrs = ['scheme', 'auth', 'host', 'port', 'path', 'query', 'fragment'] +url_attrs = ["scheme", "auth", "host", "port", "path", "query", "fragment"] # We only want to normalize urls with an HTTP(S) scheme. # urllib3 infers URLs without a scheme (None) to be http. -NORMALIZABLE_SCHEMES = ('http', 'https', None) +NORMALIZABLE_SCHEMES = ("http", "https", None) + +# Almost all of these patterns were derived from the +# 'rfc3986' module: https://github.com/python-hyper/rfc3986 +PERCENT_RE = re.compile(r"%[a-fA-F0-9]{2}") +SCHEME_RE = re.compile(r"^(?:[a-zA-Z][a-zA-Z0-9+-]*:|/)") +URI_RE = re.compile( + r"^(?:([a-zA-Z][a-zA-Z0-9+.-]*):)?" + r"(?://([^\\/?#]*))?" + r"([^?#]*)" + r"(?:\?([^#]*))?" + r"(?:#(.*))?$", + re.UNICODE | re.DOTALL, +) + +IPV4_PAT = r"(?:[0-9]{1,3}\.){3}[0-9]{1,3}" +HEX_PAT = "[0-9A-Fa-f]{1,4}" +LS32_PAT = "(?:{hex}:{hex}|{ipv4})".format(hex=HEX_PAT, ipv4=IPV4_PAT) +_subs = {"hex": HEX_PAT, "ls32": LS32_PAT} +_variations = [ + # 6( h16 ":" ) ls32 + "(?:%(hex)s:){6}%(ls32)s", + # "::" 5( h16 ":" ) ls32 + "::(?:%(hex)s:){5}%(ls32)s", + # [ h16 ] "::" 4( h16 ":" ) ls32 + "(?:%(hex)s)?::(?:%(hex)s:){4}%(ls32)s", + # [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 + "(?:(?:%(hex)s:)?%(hex)s)?::(?:%(hex)s:){3}%(ls32)s", + # [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 + "(?:(?:%(hex)s:){0,2}%(hex)s)?::(?:%(hex)s:){2}%(ls32)s", + # [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 + "(?:(?:%(hex)s:){0,3}%(hex)s)?::%(hex)s:%(ls32)s", + # [ *4( h16 ":" ) h16 ] "::" ls32 + "(?:(?:%(hex)s:){0,4}%(hex)s)?::%(ls32)s", + # [ *5( h16 ":" ) h16 ] "::" h16 + "(?:(?:%(hex)s:){0,5}%(hex)s)?::%(hex)s", + # [ *6( h16 ":" ) h16 ] "::" + "(?:(?:%(hex)s:){0,6}%(hex)s)?::", +] + +UNRESERVED_PAT = r"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._!\-~" +IPV6_PAT = "(?:" + "|".join([x % _subs for x in _variations]) + ")" +ZONE_ID_PAT = "(?:%25|%)(?:[" + UNRESERVED_PAT + "]|%[a-fA-F0-9]{2})+" +IPV6_ADDRZ_PAT = r"\[" + IPV6_PAT + r"(?:" + ZONE_ID_PAT + r")?\]" +REG_NAME_PAT = r"(?:[^\[\]%:/?#]|%[a-fA-F0-9]{2})*" +TARGET_RE = re.compile(r"^(/[^?#]*)(?:\?([^#]*))?(?:#.*)?$") + +IPV4_RE = re.compile("^" + IPV4_PAT + "$") +IPV6_RE = re.compile("^" + IPV6_PAT + "$") +IPV6_ADDRZ_RE = re.compile("^" + IPV6_ADDRZ_PAT + "$") +BRACELESS_IPV6_ADDRZ_RE = re.compile("^" + IPV6_ADDRZ_PAT[2:-2] + "$") +ZONE_ID_RE = re.compile("(" + ZONE_ID_PAT + r")\]$") + +_HOST_PORT_PAT = ("^(%s|%s|%s)(?::([0-9]{0,5}))?$") % ( + REG_NAME_PAT, + IPV4_PAT, + IPV6_ADDRZ_PAT, +) +_HOST_PORT_RE = re.compile(_HOST_PORT_PAT, re.UNICODE | re.DOTALL) + +UNRESERVED_CHARS = set( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-~" +) +SUB_DELIM_CHARS = set("!$&'()*+,;=") +USERINFO_CHARS = UNRESERVED_CHARS | SUB_DELIM_CHARS | {":"} +PATH_CHARS = USERINFO_CHARS | {"@", "/"} +QUERY_CHARS = FRAGMENT_CHARS = PATH_CHARS | {"?"} -class Url(namedtuple('Url', url_attrs)): +class Url(namedtuple("Url", url_attrs)): """ - Datastructure for representing an HTTP URL. Used as a return value for + Data structure for representing an HTTP URL. Used as a return value for :func:`parse_url`. Both the scheme and host are normalized as they are both case-insensitive according to RFC 3986. """ + __slots__ = () - def __new__(cls, scheme=None, auth=None, host=None, port=None, path=None, - query=None, fragment=None): - if path and not path.startswith('/'): - path = '/' + path - if scheme: + def __new__( + cls, + scheme=None, + auth=None, + host=None, + port=None, + path=None, + query=None, + fragment=None, + ): + if path and not path.startswith("/"): + path = "/" + path + if scheme is not None: scheme = scheme.lower() - if host and scheme in NORMALIZABLE_SCHEMES: - host = host.lower() - return super(Url, cls).__new__(cls, scheme, auth, host, port, path, - query, fragment) + return super(Url, cls).__new__( + cls, scheme, auth, host, port, path, query, fragment + ) @property def hostname(self): @@ -38,10 +114,10 @@ class Url(namedtuple('Url', url_attrs)): @property def request_uri(self): """Absolute path including the query string.""" - uri = self.path or '/' + uri = self.path or "/" if self.query is not None: - uri += '?' + self.query + uri += "?" + self.query return uri @@ -49,7 +125,7 @@ class Url(namedtuple('Url', url_attrs)): def netloc(self): """Network location including host and port""" if self.port: - return '%s:%d' % (self.host, self.port) + return "%s:%d" % (self.host, self.port) return self.host @property @@ -72,23 +148,23 @@ class Url(namedtuple('Url', url_attrs)): 'http://username:password@host.com:80/path?query#fragment' """ scheme, auth, host, port, path, query, fragment = self - url = '' + url = u"" # We use "is not None" we want things to happen with empty strings (or 0 port) if scheme is not None: - url += scheme + '://' + url += scheme + u"://" if auth is not None: - url += auth + '@' + url += auth + u"@" if host is not None: url += host if port is not None: - url += ':' + str(port) + url += u":" + str(port) if path is not None: url += path if query is not None: - url += '?' + query + url += u"?" + query if fragment is not None: - url += '#' + fragment + url += u"#" + fragment return url @@ -98,6 +174,8 @@ class Url(namedtuple('Url', url_attrs)): def split_first(s, delims): """ + .. deprecated:: 1.25 + Given a string and an iterable of delimiters, split on the first found delimiter. Return two split parts and the matched delimiter. @@ -124,15 +202,141 @@ def split_first(s, delims): min_delim = d if min_idx is None or min_idx < 0: - return s, '', None + return s, "", None - return s[:min_idx], s[min_idx + 1:], min_delim + return s[:min_idx], s[min_idx + 1 :], min_delim + + +def _encode_invalid_chars(component, allowed_chars, encoding="utf-8"): + """Percent-encodes a URI component without reapplying + onto an already percent-encoded component. + """ + if component is None: + return component + + component = six.ensure_text(component) + + # Normalize existing percent-encoded bytes. + # Try to see if the component we're encoding is already percent-encoded + # so we can skip all '%' characters but still encode all others. + component, percent_encodings = PERCENT_RE.subn( + lambda match: match.group(0).upper(), component + ) + + uri_bytes = component.encode("utf-8", "surrogatepass") + is_percent_encoded = percent_encodings == uri_bytes.count(b"%") + encoded_component = bytearray() + + for i in range(0, len(uri_bytes)): + # Will return a single character bytestring on both Python 2 & 3 + byte = uri_bytes[i : i + 1] + byte_ord = ord(byte) + if (is_percent_encoded and byte == b"%") or ( + byte_ord < 128 and byte.decode() in allowed_chars + ): + encoded_component += byte + continue + encoded_component.extend(b"%" + (hex(byte_ord)[2:].encode().zfill(2).upper())) + + return encoded_component.decode(encoding) + + +def _remove_path_dot_segments(path): + # See http://tools.ietf.org/html/rfc3986#section-5.2.4 for pseudo-code + segments = path.split("/") # Turn the path into a list of segments + output = [] # Initialize the variable to use to store output + + for segment in segments: + # '.' is the current directory, so ignore it, it is superfluous + if segment == ".": + continue + # Anything other than '..', should be appended to the output + elif segment != "..": + output.append(segment) + # In this case segment == '..', if we can, we should pop the last + # element + elif output: + output.pop() + + # If the path starts with '/' and the output is empty or the first string + # is non-empty + if path.startswith("/") and (not output or output[0]): + output.insert(0, "") + + # If the path starts with '/.' or '/..' ensure we add one more empty + # string to add a trailing '/' + if path.endswith(("/.", "/..")): + output.append("") + + return "/".join(output) + + +def _normalize_host(host, scheme): + if host: + if isinstance(host, six.binary_type): + host = six.ensure_str(host) + + if scheme in NORMALIZABLE_SCHEMES: + is_ipv6 = IPV6_ADDRZ_RE.match(host) + if is_ipv6: + match = ZONE_ID_RE.search(host) + if match: + start, end = match.span(1) + zone_id = host[start:end] + + if zone_id.startswith("%25") and zone_id != "%25": + zone_id = zone_id[3:] + else: + zone_id = zone_id[1:] + zone_id = "%" + _encode_invalid_chars(zone_id, UNRESERVED_CHARS) + return host[:start].lower() + zone_id + host[end:] + else: + return host.lower() + elif not IPV4_RE.match(host): + return six.ensure_str( + b".".join([_idna_encode(label) for label in host.split(".")]) + ) + return host + + +def _idna_encode(name): + if name and any([ord(x) > 128 for x in name]): + try: + from pipenv.patched.notpip._vendor import idna + except ImportError: + six.raise_from( + LocationParseError("Unable to parse URL without the 'idna' module"), + None, + ) + try: + return idna.encode(name.lower(), strict=True, std3_rules=True) + except idna.IDNAError: + six.raise_from( + LocationParseError(u"Name '%s' is not a valid IDNA label" % name), None + ) + return name.lower().encode("ascii") + + +def _encode_target(target): + """Percent-encodes a request target so that there are no invalid characters""" + path, query = TARGET_RE.match(target).groups() + target = _encode_invalid_chars(path, PATH_CHARS) + query = _encode_invalid_chars(query, QUERY_CHARS) + if query is not None: + target += "?" + query + return target def parse_url(url): """ Given a url, return a parsed :class:`.Url` namedtuple. Best-effort is performed to parse incomplete urls. Fields not provided will be None. + This parser is RFC 3986 compliant. + + The parser logic and helper functions are based heavily on + work done in the ``rfc3986`` module. + + :param str url: URL to parse into a :class:`.Url` namedtuple. Partly backwards-compatible with :mod:`urlparse`. @@ -145,81 +349,79 @@ def parse_url(url): >>> parse_url('/foo?bar') Url(scheme=None, host=None, port=None, path='/foo', query='bar', ...) """ - - # While this code has overlap with stdlib's urlparse, it is much - # simplified for our needs and less annoying. - # Additionally, this implementations does silly things to be optimal - # on CPython. - if not url: # Empty return Url() - scheme = None - auth = None - host = None - port = None - path = None - fragment = None - query = None + source_url = url + if not SCHEME_RE.search(url): + url = "//" + url - # Scheme - if '://' in url: - scheme, url = url.split('://', 1) + try: + scheme, authority, path, query, fragment = URI_RE.match(url).groups() + normalize_uri = scheme is None or scheme.lower() in NORMALIZABLE_SCHEMES - # Find the earliest Authority Terminator - # (http://tools.ietf.org/html/rfc3986#section-3.2) - url, path_, delim = split_first(url, ['/', '?', '#']) + if scheme: + scheme = scheme.lower() - if delim: - # Reassemble the path - path = delim + path_ - - # Auth - if '@' in url: - # Last '@' denotes end of auth part - auth, url = url.rsplit('@', 1) - - # IPv6 - if url and url[0] == '[': - host, url = url.split(']', 1) - host += ']' - - # Port - if ':' in url: - _host, port = url.split(':', 1) - - if not host: - host = _host - - if port: - # If given, ports must be integers. No whitespace, no plus or - # minus prefixes, no non-integer digits such as ^2 (superscript). - if not port.isdigit(): - raise LocationParseError(url) - try: - port = int(port) - except ValueError: - raise LocationParseError(url) + if authority: + auth, _, host_port = authority.rpartition("@") + auth = auth or None + host, port = _HOST_PORT_RE.match(host_port).groups() + if auth and normalize_uri: + auth = _encode_invalid_chars(auth, USERINFO_CHARS) + if port == "": + port = None else: - # Blank ports are cool, too. (rfc3986#section-3.2.3) - port = None + auth, host, port = None, None, None - elif not host and url: - host = url + if port is not None: + port = int(port) + if not (0 <= port <= 65535): + raise LocationParseError(url) + host = _normalize_host(host, scheme) + + if normalize_uri and path: + path = _remove_path_dot_segments(path) + path = _encode_invalid_chars(path, PATH_CHARS) + if normalize_uri and query: + query = _encode_invalid_chars(query, QUERY_CHARS) + if normalize_uri and fragment: + fragment = _encode_invalid_chars(fragment, FRAGMENT_CHARS) + + except (ValueError, AttributeError): + return six.raise_from(LocationParseError(source_url), None) + + # For the sake of backwards compatibility we put empty + # string values for path if there are any defined values + # beyond the path in the URL. + # TODO: Remove this when we break backwards compatibility. if not path: - return Url(scheme, auth, host, port, path, query, fragment) + if query is not None or fragment is not None: + path = "" + else: + path = None - # Fragment - if '#' in path: - path, fragment = path.split('#', 1) + # Ensure that each part of the URL is a `str` for + # backwards compatibility. + if isinstance(url, six.text_type): + ensure_func = six.ensure_text + else: + ensure_func = six.ensure_str - # Query - if '?' in path: - path, query = path.split('?', 1) + def ensure_type(x): + return x if x is None else ensure_func(x) - return Url(scheme, auth, host, port, path, query, fragment) + return Url( + scheme=ensure_type(scheme), + auth=ensure_type(auth), + host=ensure_type(host), + port=port, + path=ensure_type(path), + query=ensure_type(query), + fragment=ensure_type(fragment), + ) def get_host(url): @@ -227,4 +429,4 @@ def get_host(url): Deprecated. Use :func:`parse_url` instead. """ p = parse_url(url) - return p.scheme or 'http', p.hostname, p.port + return p.scheme or "http", p.hostname, p.port diff --git a/pipenv/patched/notpip/_vendor/urllib3/util/wait.py b/pipenv/patched/notpip/_vendor/urllib3/util/wait.py index 4db71baf..c280646c 100644 --- a/pipenv/patched/notpip/_vendor/urllib3/util/wait.py +++ b/pipenv/patched/notpip/_vendor/urllib3/util/wait.py @@ -1,7 +1,8 @@ import errno -from functools import partial import select import sys +from functools import partial + try: from time import monotonic except ImportError: @@ -40,6 +41,8 @@ if sys.version_info >= (3, 5): # Modern Python, that retries syscalls by default def _retry_on_intr(fn, timeout): return fn(timeout) + + else: # Old and broken Pythons. def _retry_on_intr(fn, timeout): @@ -137,14 +140,14 @@ def wait_for_socket(*args, **kwargs): def wait_for_read(sock, timeout=None): - """ Waits for reading to be available on a given socket. + """Waits for reading to be available on a given socket. Returns True if the socket is readable, or False if the timeout expired. """ return wait_for_socket(sock, read=True, timeout=timeout) def wait_for_write(sock, timeout=None): - """ Waits for writing to be available on a given socket. + """Waits for writing to be available on a given socket. Returns True if the socket is readable, or False if the timeout expired. """ return wait_for_socket(sock, write=True, timeout=timeout) diff --git a/pipenv/patched/notpip/_vendor/vendor.txt b/pipenv/patched/notpip/_vendor/vendor.txt index 7b548255..73e49343 100644 --- a/pipenv/patched/notpip/_vendor/vendor.txt +++ b/pipenv/patched/notpip/_vendor/vendor.txt @@ -1,23 +1,22 @@ -appdirs==1.4.3 -CacheControl==0.12.5 -colorama==0.4.1 -distlib==0.2.8 -distro==1.4.0 -html5lib==1.0.1 -ipaddress==1.0.22 # Only needed on 2.6 and 2.7 -lockfile==0.12.2 -msgpack==0.5.6 -packaging==19.0 -pep517==0.5.0 +appdirs==1.4.4 +CacheControl==0.12.6 +colorama==0.4.4 +distlib==0.3.2 +distro==1.5.0 +html5lib==1.1 +msgpack==1.0.2 +packaging==21.0 +pep517==0.11.0 progress==1.5 -pyparsing==2.4.0 -pytoml==0.1.20 -requests==2.21.0 - certifi==2019.3.9 - chardet==3.0.4 - idna==2.8 - urllib3==1.25.2 -retrying==1.3.3 -setuptools==41.0.1 -six==1.12.0 +pyparsing==2.4.7 +requests==2.26.0 + certifi==2021.05.30 + chardet==4.0.0 + idna==3.2 + urllib3==1.26.6 +resolvelib==0.7.1 +setuptools==44.0.0 +six==1.16.0 +tenacity==8.0.1 +tomli==1.0.3 webencodings==0.5.1 diff --git a/pipenv/patched/notpip/appdirs.LICENSE.txt b/pipenv/patched/notpip/appdirs.LICENSE.txt deleted file mode 100644 index 107c6140..00000000 --- a/pipenv/patched/notpip/appdirs.LICENSE.txt +++ /dev/null @@ -1,23 +0,0 @@ -# This is the MIT license - -Copyright (c) 2010 ActiveState Software Inc. - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - diff --git a/pipenv/patched/notpip/distro.LICENSE b/pipenv/patched/notpip/distro.LICENSE deleted file mode 100644 index e06d2081..00000000 --- a/pipenv/patched/notpip/distro.LICENSE +++ /dev/null @@ -1,202 +0,0 @@ -Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - diff --git a/pipenv/patched/notpip/ipaddress.LICENSE b/pipenv/patched/notpip/ipaddress.LICENSE deleted file mode 100644 index 41bd16ba..00000000 --- a/pipenv/patched/notpip/ipaddress.LICENSE +++ /dev/null @@ -1,50 +0,0 @@ -This package is a modified version of cpython's ipaddress module. -It is therefore distributed under the PSF license, as follows: - -PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 --------------------------------------------- - -1. This LICENSE AGREEMENT is between the Python Software Foundation -("PSF"), and the Individual or Organization ("Licensee") accessing and -otherwise using this software ("Python") in source or binary form and -its associated documentation. - -2. Subject to the terms and conditions of this License Agreement, PSF hereby -grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, -analyze, test, perform and/or display publicly, prepare derivative works, -distribute, and otherwise use Python alone or in any derivative version, -provided, however, that PSF's License Agreement and PSF's notice of copyright, -i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, -2011, 2012, 2013, 2014 Python Software Foundation; All Rights Reserved" are -retained in Python alone or in any derivative version prepared by Licensee. - -3. In the event Licensee prepares a derivative work that is based on -or incorporates Python or any part thereof, and wants to make -the derivative work available to others as provided herein, then -Licensee hereby agrees to include in any such work a brief summary of -the changes made to Python. - -4. PSF is making Python available to Licensee on an "AS IS" -basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON -FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS -A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, -OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -6. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -7. Nothing in this License Agreement shall be deemed to create any -relationship of agency, partnership, or joint venture between PSF and -Licensee. This License Agreement does not grant permission to use PSF -trademarks or trade name in a trademark sense to endorse or promote -products or services of Licensee, or any third party. - -8. By copying, installing or otherwise using Python, Licensee -agrees to be bound by the terms and conditions of this License -Agreement. diff --git a/pipenv/patched/notpip/packaging.LICENSE.APACHE b/pipenv/patched/notpip/packaging.LICENSE.APACHE deleted file mode 100644 index 4947287f..00000000 --- a/pipenv/patched/notpip/packaging.LICENSE.APACHE +++ /dev/null @@ -1,177 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/pipenv/patched/notpip/py.typed b/pipenv/patched/notpip/py.typed new file mode 100644 index 00000000..493b53e4 --- /dev/null +++ b/pipenv/patched/notpip/py.typed @@ -0,0 +1,4 @@ +pip is a command line program. While it is implemented in Python, and so is +available for import, you must not use pip's internal APIs in this way. Typing +information is provided as a convenience only and is not a guarantee. Expect +unannounced changes to the API and types in releases. diff --git a/pipenv/patched/notpip/pyparsing.LICENSE b/pipenv/patched/notpip/pyparsing.LICENSE deleted file mode 100644 index 1bf98523..00000000 --- a/pipenv/patched/notpip/pyparsing.LICENSE +++ /dev/null @@ -1,18 +0,0 @@ -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/pipenv/patched/notpip/retrying.LICENSE b/pipenv/patched/notpip/retrying.LICENSE deleted file mode 100644 index 7a4a3ea2..00000000 --- a/pipenv/patched/notpip/retrying.LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file diff --git a/pipenv/patched/notpip/six.LICENSE b/pipenv/patched/notpip/six.LICENSE deleted file mode 100644 index 365d1074..00000000 --- a/pipenv/patched/notpip/six.LICENSE +++ /dev/null @@ -1,18 +0,0 @@ -Copyright (c) 2010-2018 Benjamin Peterson - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/pipenv/patched/notpip/urllib3.LICENSE b/pipenv/patched/notpip/urllib3.LICENSE deleted file mode 100644 index 1c3283ee..00000000 --- a/pipenv/patched/notpip/urllib3.LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -This is the MIT license: http://www.opensource.org/licenses/mit-license.php - -Copyright 2008-2016 Andrey Petrov and contributors (see CONTRIBUTORS.txt) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this -software and associated documentation files (the "Software"), to deal in the Software -without restriction, including without limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons -to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or -substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR -PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE -FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/pipenv/patched/notpip/webencodings.LICENSE b/pipenv/patched/notpip/webencodings.LICENSE deleted file mode 100644 index 3d0d3e70..00000000 --- a/pipenv/patched/notpip/webencodings.LICENSE +++ /dev/null @@ -1,31 +0,0 @@ -Copyright (c) 2012 by Simon Sapin. - -Some rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - - * The names of the contributors may not be used to endorse or - promote products derived from this software without specific - prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/pipenv/patched/patched.txt b/pipenv/patched/patched.txt index e34df9fd..37350251 100644 --- a/pipenv/patched/patched.txt +++ b/pipenv/patched/patched.txt @@ -1,5 +1,4 @@ -safety crayons==0.1.2 +pip==21.2.2 pipfile==0.0.2 -pip-tools==3.5.0 -pip==19.0.3 +safety==1.10.3 diff --git a/pipenv/patched/pipfile/api.py b/pipenv/patched/pipfile/api.py index e8fa0277..ae8ee252 100644 --- a/pipenv/patched/pipfile/api.py +++ b/pipenv/patched/pipfile/api.py @@ -1,10 +1,10 @@ -import toml +import pipenv.vendor.toml as toml import codecs import json import hashlib import platform -import six +import pipenv.vendor.six as six import sys import os diff --git a/pipenv/patched/piptools/LICENSE b/pipenv/patched/piptools/LICENSE deleted file mode 100644 index 64719ca9..00000000 --- a/pipenv/patched/piptools/LICENSE +++ /dev/null @@ -1,26 +0,0 @@ -Copyright (c). All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of pip-tools nor the names of its contributors may be - used to endorse or promote products derived from this software without - specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/pipenv/patched/piptools/__main__.py b/pipenv/patched/piptools/__main__.py deleted file mode 100644 index b08b8494..00000000 --- a/pipenv/patched/piptools/__main__.py +++ /dev/null @@ -1,16 +0,0 @@ -import click -from piptools.scripts import compile, sync - - -@click.group() -def cli(): - pass - - -cli.add_command(compile.cli, 'compile') -cli.add_command(sync.cli, 'sync') - - -# Enable ``python -m piptools ...``. -if __name__ == '__main__': # pragma: no branch - cli() diff --git a/pipenv/patched/piptools/_compat/__init__.py b/pipenv/patched/piptools/_compat/__init__.py deleted file mode 100644 index 19adcbc5..00000000 --- a/pipenv/patched/piptools/_compat/__init__.py +++ /dev/null @@ -1,36 +0,0 @@ -# coding: utf-8 -# flake8: noqa -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import six - -if six.PY2: - from .tempfile import TemporaryDirectory - from .contextlib import ExitStack -else: - from tempfile import TemporaryDirectory - from contextlib import ExitStack - -from .pip_compat import ( - InstallRequirement, - parse_requirements, - RequirementSet, - user_cache_dir, - FAVORITE_HASH, - is_file_url, - url_to_path, - PackageFinder, - FormatControl, - Wheel, - Command, - cmdoptions, - get_installed_distributions, - PyPI, - install_req_from_line, - install_req_from_editable, - stdlib_pkgs, - DEV_PKGS, - SafeFileCache, - InstallationError -) diff --git a/pipenv/patched/piptools/_compat/contextlib.py b/pipenv/patched/piptools/_compat/contextlib.py deleted file mode 100644 index b0e161bb..00000000 --- a/pipenv/patched/piptools/_compat/contextlib.py +++ /dev/null @@ -1,123 +0,0 @@ -# coding: utf-8 -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import sys -from collections import deque - - -# Inspired by discussions on http://bugs.python.org/issue13585 -class ExitStack(object): - """Context manager for dynamic management of a stack of exit callbacks - - For example: - - with ExitStack() as stack: - files = [stack.enter_context(open(fname)) for fname in filenames] - # All opened files will automatically be closed at the end of - # the with statement, even if attempts to open files later - # in the list throw an exception - - """ - def __init__(self): - self._exit_callbacks = deque() - - def pop_all(self): - """Preserve the context stack by transferring it to a new instance""" - new_stack = type(self)() - new_stack._exit_callbacks = self._exit_callbacks - self._exit_callbacks = deque() - return new_stack - - def _push_cm_exit(self, cm, cm_exit): - """Helper to correctly register callbacks to __exit__ methods""" - def _exit_wrapper(*exc_details): - return cm_exit(cm, *exc_details) - _exit_wrapper.__self__ = cm - self.push(_exit_wrapper) - - def push(self, exit): - """Registers a callback with the standard __exit__ method signature - - Can suppress exceptions the same way __exit__ methods can. - - Also accepts any object with an __exit__ method (registering the - method instead of the object itself) - """ - # We use an unbound method rather than a bound method to follow - # the standard lookup behaviour for special methods - _cb_type = type(exit) - try: - exit_method = _cb_type.__exit__ - except AttributeError: - # Not a context manager, so assume its a callable - self._exit_callbacks.append(exit) - else: - self._push_cm_exit(exit, exit_method) - return exit # Allow use as a decorator - - def callback(self, callback, *args, **kwds): - """Registers an arbitrary callback and arguments. - - Cannot suppress exceptions. - """ - def _exit_wrapper(exc_type, exc, tb): - callback(*args, **kwds) - # We changed the signature, so using @wraps is not appropriate, but - # setting __wrapped__ may still help with introspection - _exit_wrapper.__wrapped__ = callback - self.push(_exit_wrapper) - return callback # Allow use as a decorator - - def enter_context(self, cm): - """Enters the supplied context manager - - If successful, also pushes its __exit__ method as a callback and - returns the result of the __enter__ method. - """ - # We look up the special methods on the type to match the with - # statement - _cm_type = type(cm) - _exit = _cm_type.__exit__ - result = _cm_type.__enter__(cm) - self._push_cm_exit(cm, _exit) - return result - - def close(self): - """Immediately unwind the context stack""" - self.__exit__(None, None, None) - - def __enter__(self): - return self - - def __exit__(self, *exc_details): - if not self._exit_callbacks: - return - - # This looks complicated, but it is really just - # setting up a chain of try-expect statements to ensure - # that outer callbacks still get invoked even if an - # inner one throws an exception - def _invoke_next_callback(exc_details): - # Callbacks are removed from the list in FIFO order - # but the recursion means they're invoked in LIFO order - cb = self._exit_callbacks.popleft() - if not self._exit_callbacks: - # Innermost callback is invoked directly - return cb(*exc_details) - # More callbacks left, so descend another level in the stack - try: - suppress_exc = _invoke_next_callback(exc_details) - except: - suppress_exc = cb(*sys.exc_info()) - # Check if this cb suppressed the inner exception - if not suppress_exc: - raise - else: - # Check if inner cb suppressed the original exception - if suppress_exc: - exc_details = (None, None, None) - suppress_exc = cb(*exc_details) or suppress_exc - return suppress_exc - # Kick off the recursive chain - return _invoke_next_callback(exc_details) diff --git a/pipenv/patched/piptools/_compat/pip_compat.py b/pipenv/patched/piptools/_compat/pip_compat.py deleted file mode 100644 index 715144a3..00000000 --- a/pipenv/patched/piptools/_compat/pip_compat.py +++ /dev/null @@ -1,51 +0,0 @@ -# -*- coding=utf-8 -*- -__all__ = [ - "InstallRequirement", - "parse_requirements", - "RequirementSet", - "FAVORITE_HASH", - "is_file_url", - "path_to_url", - "url_to_path", - "PackageFinder", - "FormatControl", - "Wheel", - "Command", - "cmdoptions", - "get_installed_distributions", - "PyPI", - "stdlib_pkgs", - "DEV_PKGS", - "install_req_from_line", - "install_req_from_editable", - "user_cache_dir", - "SafeFileCache", - "InstallationError" -] - -import os -os.environ["PIP_SHIMS_BASE_MODULE"] = str("pipenv.patched.notpip") - -from pip_shims.shims import ( - InstallRequirement, - parse_requirements, - RequirementSet, - FAVORITE_HASH, - is_file_url, - path_to_url, - url_to_path, - PackageFinder, - FormatControl, - Wheel, - Command, - cmdoptions, - get_installed_distributions, - PyPI, - stdlib_pkgs, - DEV_PKGS, - install_req_from_line, - install_req_from_editable, - USER_CACHE_DIR as user_cache_dir, - SafeFileCache, - InstallationError -) diff --git a/pipenv/patched/piptools/_compat/tempfile.py b/pipenv/patched/piptools/_compat/tempfile.py deleted file mode 100644 index a003d080..00000000 --- a/pipenv/patched/piptools/_compat/tempfile.py +++ /dev/null @@ -1,86 +0,0 @@ -# coding: utf-8 -from __future__ import absolute_import, division, print_function - -import os as _os -import sys as _sys -import warnings as _warnings -from tempfile import mkdtemp - - -class TemporaryDirectory(object): - """Create and return a temporary directory. This has the same - behavior as mkdtemp but can be used as a context manager. For - example: - - with TemporaryDirectory() as tmpdir: - ... - - Upon exiting the context, the directory and everything contained - in it are removed. - """ - - def __init__(self, suffix="", prefix="tmp", dir=None): - self._closed = False - self.name = None # Handle mkdtemp raising an exception - self.name = mkdtemp(suffix, prefix, dir) - - def __repr__(self): - return "<{} {!r}>".format(self.__class__.__name__, self.name) - - def __enter__(self): - return self.name - - def cleanup(self): - if self.name and not self._closed: - try: - self._rmtree(self.name) - except (TypeError, AttributeError) as ex: - # Issue #10188: Emit a warning on stderr - # if the directory could not be cleaned - # up due to missing globals - if "None" not in str(ex): - raise - print("ERROR: {!r} while cleaning up {!r}".format(ex, self,), - file=_sys.stderr) - return - self._closed = True - - def __exit__(self, exc, value, tb): - self.cleanup() - - def __del__(self): - # Issue a ResourceWarning if implicit cleanup needed - self.cleanup() - - # XXX (ncoghlan): The following code attempts to make - # this class tolerant of the module nulling out process - # that happens during CPython interpreter shutdown - # Alas, it doesn't actually manage it. See issue #10188 - _listdir = staticmethod(_os.listdir) - _path_join = staticmethod(_os.path.join) - _isdir = staticmethod(_os.path.isdir) - _islink = staticmethod(_os.path.islink) - _remove = staticmethod(_os.remove) - _rmdir = staticmethod(_os.rmdir) - _warn = _warnings.warn - - def _rmtree(self, path): - # Essentially a stripped down version of shutil.rmtree. We can't - # use globals because they may be None'ed out at shutdown. - for name in self._listdir(path): - fullname = self._path_join(path, name) - try: - isdir = self._isdir(fullname) and not self._islink(fullname) - except OSError: - isdir = False - if isdir: - self._rmtree(fullname) - else: - try: - self._remove(fullname) - except OSError: - pass - try: - self._rmdir(path) - except OSError: - pass diff --git a/pipenv/patched/piptools/cache.py b/pipenv/patched/piptools/cache.py deleted file mode 100644 index 610a4f37..00000000 --- a/pipenv/patched/piptools/cache.py +++ /dev/null @@ -1,164 +0,0 @@ -# coding: utf-8 -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import json -import os -import sys - -from pipenv.patched.notpip._vendor.packaging.requirements import Requirement - -from .exceptions import PipToolsError -from .locations import CACHE_DIR -from .utils import as_tuple, key_from_req, lookup_table - - -class CorruptCacheError(PipToolsError): - def __init__(self, path): - self.path = path - - def __str__(self): - lines = [ - 'The dependency cache seems to have been corrupted.', - 'Inspect, or delete, the following file:', - ' {}'.format(self.path), - ] - return os.linesep.join(lines) - - -def read_cache_file(cache_file_path): - with open(cache_file_path, 'r') as cache_file: - try: - doc = json.load(cache_file) - except ValueError: - raise CorruptCacheError(cache_file_path) - - # Check version and load the contents - assert doc['__format__'] == 1, 'Unknown cache file format' - return doc['dependencies'] - - -class DependencyCache(object): - """ - Creates a new persistent dependency cache for the current Python version. - The cache file is written to the appropriate user cache dir for the - current platform, i.e. - - ~/.cache/pip-tools/depcache-pyX.Y.json - - Where X.Y indicates the Python version. - """ - def __init__(self, cache_dir=None): - if cache_dir is None: - cache_dir = CACHE_DIR - if not os.path.isdir(cache_dir): - os.makedirs(cache_dir) - py_version = '.'.join(str(digit) for digit in sys.version_info[:2]) - cache_filename = 'depcache-py{}.json'.format(py_version) - - self._cache_file = os.path.join(cache_dir, cache_filename) - self._cache = None - - @property - def cache(self): - """ - The dictionary that is the actual in-memory cache. This property - lazily loads the cache from disk. - """ - if self._cache is None: - self.read_cache() - return self._cache - - def as_cache_key(self, ireq): - """ - Given a requirement, return its cache key. This behavior is a little weird in order to allow backwards - compatibility with cache files. For a requirement without extras, this will return, for example: - - ("ipython", "2.1.0") - - For a requirement with extras, the extras will be comma-separated and appended to the version, inside brackets, - like so: - - ("ipython", "2.1.0[nbconvert,notebook]") - """ - name, version, extras = as_tuple(ireq) - if not extras: - extras_string = "" - else: - extras_string = "[{}]".format(",".join(extras)) - return name, "{}{}".format(version, extras_string) - - def read_cache(self): - """Reads the cached contents into memory.""" - if os.path.exists(self._cache_file): - self._cache = read_cache_file(self._cache_file) - else: - self._cache = {} - - def write_cache(self): - """Writes the cache to disk as JSON.""" - doc = { - '__format__': 1, - 'dependencies': self._cache, - } - with open(self._cache_file, 'w') as f: - json.dump(doc, f, sort_keys=True) - - def clear(self): - self._cache = {} - self.write_cache() - - def __contains__(self, ireq): - pkgname, pkgversion_and_extras = self.as_cache_key(ireq) - return pkgversion_and_extras in self.cache.get(pkgname, {}) - - def __getitem__(self, ireq): - pkgname, pkgversion_and_extras = self.as_cache_key(ireq) - return self.cache[pkgname][pkgversion_and_extras] - - def __setitem__(self, ireq, values): - pkgname, pkgversion_and_extras = self.as_cache_key(ireq) - self.cache.setdefault(pkgname, {}) - self.cache[pkgname][pkgversion_and_extras] = values - self.write_cache() - - def get(self, ireq, default=None): - pkgname, pkgversion_and_extras = self.as_cache_key(ireq) - return self.cache.get(pkgname, {}).get(pkgversion_and_extras, default) - - def reverse_dependencies(self, ireqs): - """ - Returns a lookup table of reverse dependencies for all the given ireqs. - - Since this is all static, it only works if the dependency cache - contains the complete data, otherwise you end up with a partial view. - This is typically no problem if you use this function after the entire - dependency tree is resolved. - """ - ireqs_as_cache_values = [self.as_cache_key(ireq) for ireq in ireqs] - return self._reverse_dependencies(ireqs_as_cache_values) - - def _reverse_dependencies(self, cache_keys): - """ - Returns a lookup table of reverse dependencies for all the given cache keys. - - Example input: - - [('pep8', '1.5.7'), - ('flake8', '2.4.0'), - ('mccabe', '0.3'), - ('pyflakes', '0.8.1')] - - Example output: - - {'pep8': ['flake8'], - 'flake8': [], - 'mccabe': ['flake8'], - 'pyflakes': ['flake8']} - - """ - # First, collect all the dependencies into a sequence of (parent, child) tuples, like [('flake8', 'pep8'), - # ('flake8', 'mccabe'), ...] - return lookup_table((key_from_req(Requirement(dep_name)), name) - for name, version_and_extras in cache_keys - for dep_name in self.cache[name][version_and_extras]) diff --git a/pipenv/patched/piptools/click.py b/pipenv/patched/piptools/click.py deleted file mode 100644 index 4bab11cb..00000000 --- a/pipenv/patched/piptools/click.py +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import absolute_import - -import click -click.disable_unicode_literals_warning = True - -from click import * # noqa diff --git a/pipenv/patched/piptools/exceptions.py b/pipenv/patched/piptools/exceptions.py deleted file mode 100644 index 77c5bd40..00000000 --- a/pipenv/patched/piptools/exceptions.py +++ /dev/null @@ -1,65 +0,0 @@ -class PipToolsError(Exception): - pass - - -class NoCandidateFound(PipToolsError): - def __init__(self, ireq, candidates_tried, finder): - self.ireq = ireq - self.candidates_tried = candidates_tried - self.finder = finder - - def __str__(self): - versions = [] - pre_versions = [] - - for candidate in sorted(self.candidates_tried): - version = str(candidate.version) - if candidate.version.is_prerelease: - pre_versions.append(version) - else: - versions.append(version) - - lines = [ - 'Could not find a version that matches {}'.format(self.ireq), - ] - - if versions: - lines.append('Tried: {}'.format(', '.join(versions))) - - if pre_versions: - if self.finder.allow_all_prereleases: - line = 'Tried' - else: - line = 'Skipped' - - line += ' pre-versions: {}'.format(', '.join(pre_versions)) - lines.append(line) - - if versions or pre_versions: - lines.append('There are incompatible versions in the resolved dependencies.') - else: - lines.append('No versions found') - lines.append('{} {} reachable?'.format( - 'Were' if len(self.finder.index_urls) > 1 else 'Was', ' or '.join(self.finder.index_urls)) - ) - return '\n'.join(lines) - - -class UnsupportedConstraint(PipToolsError): - def __init__(self, message, constraint): - super(UnsupportedConstraint, self).__init__(message) - self.constraint = constraint - - def __str__(self): - message = super(UnsupportedConstraint, self).__str__() - return '{} (constraint was: {})'.format(message, str(self.constraint)) - - -class IncompatibleRequirements(PipToolsError): - def __init__(self, ireq_a, ireq_b): - self.ireq_a = ireq_a - self.ireq_b = ireq_b - - def __str__(self): - message = "Incompatible requirements found: {} and {}" - return message.format(self.ireq_a, self.ireq_b) diff --git a/pipenv/patched/piptools/io.py b/pipenv/patched/piptools/io.py deleted file mode 100644 index b6bca675..00000000 --- a/pipenv/patched/piptools/io.py +++ /dev/null @@ -1,644 +0,0 @@ -# -*- coding: utf-8 -*- -# -# NOTE: -# The classes in this module are vendored from boltons: -# https://github.com/mahmoud/boltons/blob/master/boltons/fileutils.py -# -"""Virtually every Python programmer has used Python for wrangling -disk contents, and ``fileutils`` collects solutions to some of the -most commonly-found gaps in the standard library. -""" - -from __future__ import print_function - -import os -import re -import sys -import stat -import errno -import fnmatch -from shutil import copy2, copystat, Error - - -__all__ = ['mkdir_p', 'atomic_save', 'AtomicSaver', 'FilePerms', - 'iter_find_files', 'copytree'] - - -FULL_PERMS = 511 # 0777 that both Python 2 and 3 can digest -RW_PERMS = 438 -_SINGLE_FULL_PERM = 7 # or 07 in Python 2 -try: - basestring -except NameError: - unicode = str # Python 3 compat - basestring = (str, bytes) - - -def mkdir_p(path): - """Creates a directory and any parent directories that may need to - be created along the way, without raising errors for any existing - directories. This function mimics the behavior of the ``mkdir -p`` - command available in Linux/BSD environments, but also works on - Windows. - """ - try: - os.makedirs(path) - except OSError as exc: - if exc.errno == errno.EEXIST and os.path.isdir(path): - return - raise - return - - -class FilePerms(object): - """The :class:`FilePerms` type is used to represent standard POSIX - filesystem permissions: - - * Read - * Write - * Execute - - Across three classes of user: - - * Owning (u)ser - * Owner's (g)roup - * Any (o)ther user - - This class assists with computing new permissions, as well as - working with numeric octal ``777``-style and ``rwx``-style - permissions. Currently it only considers the bottom 9 permission - bits; it does not support sticky bits or more advanced permission - systems. - - Args: - user (str): A string in the 'rwx' format, omitting characters - for which owning user's permissions are not provided. - group (str): A string in the 'rwx' format, omitting characters - for which owning group permissions are not provided. - other (str): A string in the 'rwx' format, omitting characters - for which owning other/world permissions are not provided. - - There are many ways to use :class:`FilePerms`: - - >>> FilePerms(user='rwx', group='xrw', other='wxr') # note character order - FilePerms(user='rwx', group='rwx', other='rwx') - >>> int(FilePerms('r', 'r', '')) - 288 - >>> oct(288)[-3:] # XXX Py3k - '440' - - See also the :meth:`FilePerms.from_int` and - :meth:`FilePerms.from_path` classmethods for useful alternative - ways to construct :class:`FilePerms` objects. - """ - # TODO: consider more than the lower 9 bits - class _FilePermProperty(object): - _perm_chars = 'rwx' - _perm_set = frozenset('rwx') - _perm_val = {'r': 4, 'w': 2, 'x': 1} # for sorting - - def __init__(self, attribute, offset): - self.attribute = attribute - self.offset = offset - - def __get__(self, fp_obj, type_=None): - if fp_obj is None: - return self - return getattr(fp_obj, self.attribute) - - def __set__(self, fp_obj, value): - cur = getattr(fp_obj, self.attribute) - if cur == value: - return - try: - invalid_chars = set(str(value)) - self._perm_set - except TypeError: - raise TypeError('expected string, not %r' % value) - if invalid_chars: - raise ValueError('got invalid chars %r in permission' - ' specification %r, expected empty string' - ' or one or more of %r' - % (invalid_chars, value, self._perm_chars)) - - sort_key = (lambda c: self._perm_val[c]) - new_value = ''.join(sorted(set(value), - key=sort_key, reverse=True)) - setattr(fp_obj, self.attribute, new_value) - self._update_integer(fp_obj, new_value) - - def _update_integer(self, fp_obj, value): - mode = 0 - key = 'xwr' - for symbol in value: - bit = 2 ** key.index(symbol) - mode |= (bit << (self.offset * 3)) - fp_obj._integer |= mode - - def __init__(self, user='', group='', other=''): - self._user, self._group, self._other = '', '', '' - self._integer = 0 - self.user = user - self.group = group - self.other = other - - @classmethod - def from_int(cls, i): - """Create a :class:`FilePerms` object from an integer. - - >>> FilePerms.from_int(0o644) # note the leading zero-oh for octal - FilePerms(user='rw', group='r', other='r') - """ - i &= FULL_PERMS - key = ('', 'x', 'w', 'xw', 'r', 'rx', 'rw', 'rwx') - parts = [] - while i: - parts.append(key[i & _SINGLE_FULL_PERM]) - i >>= 3 - parts.reverse() - return cls(*parts) - - @classmethod - def from_path(cls, path): - """Make a new :class:`FilePerms` object based on the permissions - assigned to the file or directory at *path*. - - Args: - path (str): Filesystem path of the target file. - - >>> from os.path import expanduser - >>> 'r' in FilePerms.from_path(expanduser('~')).user # probably - True - """ - stat_res = os.stat(path) - return cls.from_int(stat.S_IMODE(stat_res.st_mode)) - - def __int__(self): - return self._integer - - # Sphinx tip: attribute docstrings come after the attribute - user = _FilePermProperty('_user', 2) - "Stores the ``rwx``-formatted *user* permission." - group = _FilePermProperty('_group', 1) - "Stores the ``rwx``-formatted *group* permission." - other = _FilePermProperty('_other', 0) - "Stores the ``rwx``-formatted *other* permission." - - def __repr__(self): - cn = self.__class__.__name__ - return ('%s(user=%r, group=%r, other=%r)' - % (cn, self.user, self.group, self.other)) - -#### - - -_TEXT_OPENFLAGS = os.O_RDWR | os.O_CREAT | os.O_EXCL -if hasattr(os, 'O_NOINHERIT'): - _TEXT_OPENFLAGS |= os.O_NOINHERIT -if hasattr(os, 'O_NOFOLLOW'): - _TEXT_OPENFLAGS |= os.O_NOFOLLOW -_BIN_OPENFLAGS = _TEXT_OPENFLAGS -if hasattr(os, 'O_BINARY'): - _BIN_OPENFLAGS |= os.O_BINARY - - -try: - import fcntl as fcntl -except ImportError: - def set_cloexec(fd): - "Dummy set_cloexec for platforms without fcntl support" - pass -else: - def set_cloexec(fd): - """Does a best-effort :func:`fcntl.fcntl` call to set a fd to be - automatically closed by any future child processes. - - Implementation from the :mod:`tempfile` module. - """ - try: - flags = fcntl.fcntl(fd, fcntl.F_GETFD, 0) - except IOError: - pass - else: - # flags read successfully, modify - flags |= fcntl.FD_CLOEXEC - fcntl.fcntl(fd, fcntl.F_SETFD, flags) - return - - -def atomic_save(dest_path, **kwargs): - """A convenient interface to the :class:`AtomicSaver` type. See the - :class:`AtomicSaver` documentation for details. - """ - return AtomicSaver(dest_path, **kwargs) - - -def path_to_unicode(path): - if isinstance(path, unicode): - return path - encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() - return path.decode(encoding) - - -if os.name == 'nt': - import ctypes - from ctypes import c_wchar_p - from ctypes.wintypes import DWORD, LPVOID - - _ReplaceFile = ctypes.windll.kernel32.ReplaceFile - _ReplaceFile.argtypes = [c_wchar_p, c_wchar_p, c_wchar_p, - DWORD, LPVOID, LPVOID] - - def replace(src, dst): - # argument names match stdlib docs, docstring below - try: - # ReplaceFile fails if the dest file does not exist, so - # first try to rename it into position - os.rename(src, dst) - return - except WindowsError as we: - if we.errno == errno.EEXIST: - pass # continue with the ReplaceFile logic below - else: - raise - - src = path_to_unicode(src) - dst = path_to_unicode(dst) - res = _ReplaceFile(c_wchar_p(dst), c_wchar_p(src), - None, 0, None, None) - if not res: - raise OSError('failed to replace %r with %r' % (dst, src)) - return - - def atomic_rename(src, dst, overwrite=False): - "Rename *src* to *dst*, replacing *dst* if *overwrite is True" - if overwrite: - replace(src, dst) - else: - os.rename(src, dst) - return -else: - # wrapper func for cross compat + docs - def replace(src, dst): - # os.replace does the same thing on unix - return os.rename(src, dst) - - def atomic_rename(src, dst, overwrite=False): - "Rename *src* to *dst*, replacing *dst* if *overwrite is True" - if overwrite: - os.rename(src, dst) - else: - os.link(src, dst) - os.unlink(dst) - return - - -_atomic_rename = atomic_rename # backwards compat - -replace.__doc__ = """Similar to :func:`os.replace` in Python 3.3+, -this function will atomically create or replace the file at path -*dst* with the file at path *src*. - -On Windows, this function uses the ReplaceFile API for maximum -possible atomicity on a range of filesystems. -""" - - -class AtomicSaver(object): - """``AtomicSaver`` is a configurable `context manager`_ that provides - a writable :class:`file` which will be moved into place as long as - no exceptions are raised within the context manager's block. These - "part files" are created in the same directory as the destination - path to ensure atomic move operations (i.e., no cross-filesystem - moves occur). - - Args: - dest_path (str): The path where the completed file will be - written. - overwrite (bool): Whether to overwrite the destination file if - it exists at completion time. Defaults to ``True``. - file_perms (int): Integer representation of file permissions - for the newly-created file. Defaults are, when the - destination path already exists, to copy the permissions - from the previous file, or if the file did not exist, to - respect the user's configured `umask`_, usually resulting - in octal 0644 or 0664. - part_file (str): Name of the temporary *part_file*. Defaults - to *dest_path* + ``.part``. Note that this argument is - just the filename, and not the full path of the part - file. To guarantee atomic saves, part files are always - created in the same directory as the destination path. - overwrite_part (bool): Whether to overwrite the *part_file*, - should it exist at setup time. Defaults to ``False``, - which results in an :exc:`OSError` being raised on - pre-existing part files. Be careful of setting this to - ``True`` in situations when multiple threads or processes - could be writing to the same part file. - rm_part_on_exc (bool): Remove *part_file* on exception cases. - Defaults to ``True``, but ``False`` can be useful for - recovery in some cases. Note that resumption is not - automatic and by default an :exc:`OSError` is raised if - the *part_file* exists. - - Practically, the AtomicSaver serves a few purposes: - - * Avoiding overwriting an existing, valid file with a partially - written one. - * Providing a reasonable guarantee that a part file only has one - writer at a time. - * Optional recovery of partial data in failure cases. - - .. _context manager: https://docs.python.org/3/reference/compound_stmts.html#with - .. _umask: https://en.wikipedia.org/wiki/Umask - - """ - _default_file_perms = RW_PERMS - - # TODO: option to abort if target file modify date has changed since start? - def __init__(self, dest_path, **kwargs): - self.dest_path = dest_path - self.overwrite = kwargs.pop('overwrite', True) - self.file_perms = kwargs.pop('file_perms', None) - self.overwrite_part = kwargs.pop('overwrite_part', False) - self.part_filename = kwargs.pop('part_file', None) - self.rm_part_on_exc = kwargs.pop('rm_part_on_exc', True) - self.text_mode = kwargs.pop('text_mode', False) # for windows - self.buffering = kwargs.pop('buffering', -1) - if kwargs: - raise TypeError('unexpected kwargs: %r' % (kwargs.keys(),)) - - self.dest_path = os.path.abspath(self.dest_path) - self.dest_dir = os.path.dirname(self.dest_path) - if not self.part_filename: - self.part_path = dest_path + '.part' - else: - self.part_path = os.path.join(self.dest_dir, self.part_filename) - self.mode = 'w+' if self.text_mode else 'w+b' - self.open_flags = _TEXT_OPENFLAGS if self.text_mode else _BIN_OPENFLAGS - - self.part_file = None - - def _open_part_file(self): - do_chmod = True - file_perms = self.file_perms - if file_perms is None: - try: - # try to copy from file being replaced - stat_res = os.stat(self.dest_path) - file_perms = stat.S_IMODE(stat_res.st_mode) - except (OSError, IOError): - # default if no destination file exists - file_perms = self._default_file_perms - do_chmod = False # respect the umask - - fd = os.open(self.part_path, self.open_flags, file_perms) - set_cloexec(fd) - self.part_file = os.fdopen(fd, self.mode, self.buffering) - - # if default perms are overridden by the user or previous dest_path - # chmod away the effects of the umask - if do_chmod: - try: - os.chmod(self.part_path, file_perms) - except (OSError, IOError): - self.part_file.close() - raise - return - - def setup(self): - """Called on context manager entry (the :keyword:`with` statement), - the ``setup()`` method creates the temporary file in the same - directory as the destination file. - - ``setup()`` tests for a writable directory with rename permissions - early, as the part file may not be written to immediately (not - using :func:`os.access` because of the potential issues of - effective vs. real privileges). - - If the caller is not using the :class:`AtomicSaver` as a - context manager, this method should be called explicitly - before writing. - """ - if os.path.lexists(self.dest_path): - if not self.overwrite: - raise OSError(errno.EEXIST, - 'Overwrite disabled and file already exists', - self.dest_path) - if self.overwrite_part and os.path.lexists(self.part_path): - os.unlink(self.part_path) - self._open_part_file() - return - - def __enter__(self): - self.setup() - return self.part_file - - def __exit__(self, exc_type, exc_val, exc_tb): - self.part_file.close() - if exc_type: - if self.rm_part_on_exc: - try: - os.unlink(self.part_path) - except Exception: - pass # avoid masking original error - return - try: - atomic_rename(self.part_path, self.dest_path, - overwrite=self.overwrite) - except OSError: - if self.rm_part_on_exc: - try: - os.unlink(self.part_path) - except Exception: - pass # avoid masking original error - raise # could not save destination file - return - - -def iter_find_files(directory, patterns, ignored=None): - """Returns a generator that yields file paths under a *directory*, - matching *patterns* using `glob`_ syntax (e.g., ``*.txt``). Also - supports *ignored* patterns. - - Args: - directory (str): Path that serves as the root of the - search. Yielded paths will include this as a prefix. - patterns (str or list): A single pattern or list of - glob-formatted patterns to find under *directory*. - ignored (str or list): A single pattern or list of - glob-formatted patterns to ignore. - - For example, finding Python files in the directory of this module: - - >>> files = set(iter_find_files(os.path.dirname(__file__), '*.py')) - - Or, Python files while ignoring emacs lockfiles: - - >>> filenames = iter_find_files('.', '*.py', ignored='.#*') - - .. _glob: https://en.wikipedia.org/wiki/Glob_%28programming%29 - - """ - if isinstance(patterns, basestring): - patterns = [patterns] - pats_re = re.compile('|'.join([fnmatch.translate(p) for p in patterns])) - - if not ignored: - ignored = [] - elif isinstance(ignored, basestring): - ignored = [ignored] - ign_re = re.compile('|'.join([fnmatch.translate(p) for p in ignored])) - for root, dirs, files in os.walk(directory): - for basename in files: - if pats_re.match(basename): - if ignored and ign_re.match(basename): - continue - filename = os.path.join(root, basename) - yield filename - return - - -def copy_tree(src, dst, symlinks=False, ignore=None): - """The ``copy_tree`` function is an exact copy of the built-in - :func:`shutil.copytree`, with one key difference: it will not - raise an exception if part of the tree already exists. It achieves - this by using :func:`mkdir_p`. - - Args: - src (str): Path of the source directory to copy. - dst (str): Destination path. Existing directories accepted. - symlinks (bool): If ``True``, copy symlinks rather than their - contents. - ignore (callable): A callable that takes a path and directory - listing, returning the files within the listing to be ignored. - - For more details, check out :func:`shutil.copytree` and - :func:`shutil.copy2`. - - """ - names = os.listdir(src) - if ignore is not None: - ignored_names = ignore(src, names) - else: - ignored_names = set() - - mkdir_p(dst) - errors = [] - for name in names: - if name in ignored_names: - continue - srcname = os.path.join(src, name) - dstname = os.path.join(dst, name) - try: - if symlinks and os.path.islink(srcname): - linkto = os.readlink(srcname) - os.symlink(linkto, dstname) - elif os.path.isdir(srcname): - copytree(srcname, dstname, symlinks, ignore) - else: - # Will raise a SpecialFileError for unsupported file types - copy2(srcname, dstname) - # catch the Error from the recursive copytree so that we can - # continue with other files - except Error as e: - errors.extend(e.args[0]) - except EnvironmentError as why: - errors.append((srcname, dstname, str(why))) - try: - copystat(src, dst) - except OSError as why: - if WindowsError is not None and isinstance(why, WindowsError): - # Copying file access times may fail on Windows - pass - else: - errors.append((src, dst, str(why))) - if errors: - raise Error(errors) - - -copytree = copy_tree # alias for drop-in replacement of shutil - - -try: - file -except NameError: - file = object - - -# like open(os.devnull) but with even fewer side effects -class DummyFile(file): - # TODO: raise ValueErrors on closed for all methods? - # TODO: enforce read/write - def __init__(self, path, mode='r', buffering=None): - self.name = path - self.mode = mode - self.closed = False - self.errors = None - self.isatty = False - self.encoding = None - self.newlines = None - self.softspace = 0 - - def close(self): - self.closed = True - - def fileno(self): - return -1 - - def flush(self): - if self.closed: - raise ValueError('I/O operation on a closed file') - return - - def next(self): - raise StopIteration() - - def read(self, size=0): - if self.closed: - raise ValueError('I/O operation on a closed file') - return '' - - def readline(self, size=0): - if self.closed: - raise ValueError('I/O operation on a closed file') - return '' - - def readlines(self, size=0): - if self.closed: - raise ValueError('I/O operation on a closed file') - return [] - - def seek(self): - if self.closed: - raise ValueError('I/O operation on a closed file') - return - - def tell(self): - if self.closed: - raise ValueError('I/O operation on a closed file') - return 0 - - def truncate(self): - if self.closed: - raise ValueError('I/O operation on a closed file') - return - - def write(self, string): - if self.closed: - raise ValueError('I/O operation on a closed file') - return - - def writelines(self, list_of_strings): - if self.closed: - raise ValueError('I/O operation on a closed file') - return - - def __next__(self): - raise StopIteration() - - def __enter__(self): - if self.closed: - raise ValueError('I/O operation on a closed file') - return - - def __exit__(self, exc_type, exc_val, exc_tb): - return diff --git a/pipenv/patched/piptools/locations.py b/pipenv/patched/piptools/locations.py deleted file mode 100644 index 9fcea0af..00000000 --- a/pipenv/patched/piptools/locations.py +++ /dev/null @@ -1,23 +0,0 @@ -import os -from shutil import rmtree - -from .click import secho -from ._compat import user_cache_dir - -# The user_cache_dir helper comes straight from pipenv.patched.notpip itself -try: - from pipenv.environments import PIPENV_CACHE_DIR - CACHE_DIR = PIPENV_CACHE_DIR -except ImportError: - CACHE_DIR = user_cache_dir('pipenv') - -# NOTE -# We used to store the cache dir under ~/.pip-tools, which is not the -# preferred place to store caches for any platform. This has been addressed -# in pip-tools==1.0.5, but to be good citizens, we point this out explicitly -# to the user when this directory is still found. -LEGACY_CACHE_DIR = os.path.expanduser('~/.pip-tools') - -if os.path.exists(LEGACY_CACHE_DIR): - secho('Removing old cache dir {} (new cache dir is {})'.format(LEGACY_CACHE_DIR, CACHE_DIR), fg='yellow') - rmtree(LEGACY_CACHE_DIR) diff --git a/pipenv/patched/piptools/logging.py b/pipenv/patched/piptools/logging.py deleted file mode 100644 index f0bd1784..00000000 --- a/pipenv/patched/piptools/logging.py +++ /dev/null @@ -1,36 +0,0 @@ -# coding: utf-8 -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import sys - -from . import click - - -class LogContext(object): - def __init__(self, verbosity=0): - self.verbosity = verbosity - - def log(self, *args, **kwargs): - click.secho(*args, **kwargs) - - def debug(self, *args, **kwargs): - if self.verbosity >= 1: - self.log(*args, **kwargs) - - def info(self, *args, **kwargs): - if self.verbosity >= 0: - self.log(*args, **kwargs) - - def warning(self, *args, **kwargs): - kwargs.setdefault('fg', 'yellow') - kwargs.setdefault('file', sys.stderr) - self.log(*args, **kwargs) - - def error(self, *args, **kwargs): - kwargs.setdefault('fg', 'red') - kwargs.setdefault('file', sys.stderr) - self.log(*args, **kwargs) - - -log = LogContext() diff --git a/pipenv/patched/piptools/pip.py b/pipenv/patched/piptools/pip.py deleted file mode 100644 index 0419a8ab..00000000 --- a/pipenv/patched/piptools/pip.py +++ /dev/null @@ -1,30 +0,0 @@ -import optparse - -from ._compat import Command, cmdoptions - - -class PipCommand(Command): - name = 'PipCommand' - - -def get_pip_command(): - # Use pip's parser for pip.conf management and defaults. - # General options (find_links, index_url, extra_index_url, trusted_host, - # and pre) are defered to pip. - pip_command = PipCommand() - pip_command.parser.add_option(cmdoptions.no_binary()) - pip_command.parser.add_option(cmdoptions.only_binary()) - index_opts = cmdoptions.make_option_group( - cmdoptions.index_group, - pip_command.parser, - ) - pip_command.parser.insert_option_group(0, index_opts) - pip_command.parser.add_option(optparse.Option('--pre', action='store_true', default=False)) - - return pip_command - - -pip_command = get_pip_command() - -# Get default values of the pip's options (including options from pipenv.patched.notpip.conf). -pip_defaults = pip_command.parser.get_default_values() diff --git a/pipenv/patched/piptools/repositories/__init__.py b/pipenv/patched/piptools/repositories/__init__.py deleted file mode 100644 index ce5142e8..00000000 --- a/pipenv/patched/piptools/repositories/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# flake8: noqa -from .local import LocalRequirementsRepository -from .pypi import PyPIRepository diff --git a/pipenv/patched/piptools/repositories/base.py b/pipenv/patched/piptools/repositories/base.py deleted file mode 100644 index 57e85fda..00000000 --- a/pipenv/patched/piptools/repositories/base.py +++ /dev/null @@ -1,48 +0,0 @@ -# coding: utf-8 -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -from abc import ABCMeta, abstractmethod -from contextlib import contextmanager - -from six import add_metaclass - - -@add_metaclass(ABCMeta) -class BaseRepository(object): - - def clear_caches(self): - """Should clear any caches used by the implementation.""" - - def freshen_build_caches(self): - """Should start with fresh build/source caches.""" - - @abstractmethod - def find_best_match(self, ireq): - """ - Return a Version object that indicates the best match for the given - InstallRequirement according to the repository. - """ - - @abstractmethod - def get_dependencies(self, ireq): - """ - Given a pinned or an editable InstallRequirement, returns a set of - dependencies (also InstallRequirements, but not necessarily pinned). - They indicate the secondary dependencies for the given requirement. - """ - - @abstractmethod - def get_hashes(self, ireq): - """ - Given a pinned InstallRequire, returns a set of hashes that represent - all of the files for a given requirement. It is not acceptable for an - editable or unpinned requirement to be passed to this function. - """ - - @abstractmethod - @contextmanager - def allow_all_wheels(self): - """ - Monkey patches pip.Wheel to allow wheels from all platforms and Python versions. - """ diff --git a/pipenv/patched/piptools/repositories/local.py b/pipenv/patched/piptools/repositories/local.py deleted file mode 100644 index 36bafdb9..00000000 --- a/pipenv/patched/piptools/repositories/local.py +++ /dev/null @@ -1,84 +0,0 @@ -# coding: utf-8 -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -from contextlib import contextmanager - -from piptools.utils import as_tuple, key_from_req, make_install_requirement -from .base import BaseRepository -from .._compat import FAVORITE_HASH - - -def ireq_satisfied_by_existing_pin(ireq, existing_pin): - """ - Return True if the given InstallationRequirement is satisfied by the - previously encountered version pin. - """ - version = next(iter(existing_pin.req.specifier)).version - return version in ireq.req.specifier - - -class LocalRequirementsRepository(BaseRepository): - """ - The LocalRequirementsRepository proxied the _real_ repository by first - checking if a requirement can be satisfied by existing pins (i.e. the - result of a previous compile step). - - In effect, if a requirement can be satisfied with a version pinned in the - requirements file, we prefer that version over the best match found in - PyPI. This keeps updates to the requirements.txt down to a minimum. - """ - def __init__(self, existing_pins, proxied_repository): - self.repository = proxied_repository - self.existing_pins = existing_pins - - @property - def finder(self): - return self.repository.finder - - @property - def session(self): - return self.repository.session - - @property - def DEFAULT_INDEX_URL(self): - return self.repository.DEFAULT_INDEX_URL - - def clear_caches(self): - self.repository.clear_caches() - - def freshen_build_caches(self): - self.repository.freshen_build_caches() - - def find_best_match(self, ireq, prereleases=None): - key = key_from_req(ireq.req) - existing_pin = self.existing_pins.get(key) - if existing_pin and ireq_satisfied_by_existing_pin(ireq, existing_pin): - project, version, _ = as_tuple(existing_pin) - return make_install_requirement( - project, version, ireq.extras, constraint=ireq.constraint, - markers=ireq.markers - ) - else: - return self.repository.find_best_match(ireq, prereleases) - - def get_dependencies(self, ireq): - return self.repository.get_dependencies(ireq) - - def get_hashes(self, ireq): - key = key_from_req(ireq.req) - existing_pin = self.existing_pins.get(key) - if existing_pin and ireq_satisfied_by_existing_pin(ireq, existing_pin): - hashes = existing_pin.options.get('hashes', {}) - hexdigests = hashes.get(FAVORITE_HASH) - if hexdigests: - return { - ':'.join([FAVORITE_HASH, hexdigest]) - for hexdigest in hexdigests - } - return self.repository.get_hashes(ireq) - - @contextmanager - def allow_all_wheels(self): - with self.repository.allow_all_wheels(): - yield diff --git a/pipenv/patched/piptools/repositories/pypi.py b/pipenv/patched/piptools/repositories/pypi.py deleted file mode 100644 index 10a0e469..00000000 --- a/pipenv/patched/piptools/repositories/pypi.py +++ /dev/null @@ -1,453 +0,0 @@ -# coding: utf-8 -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import copy -import hashlib -import os -from contextlib import contextmanager -from shutil import rmtree - -import pkg_resources - -from packaging.requirements import Requirement -from packaging.specifiers import SpecifierSet, Specifier - -os.environ["PIP_SHIMS_BASE_MODULE"] = str("pipenv.patched.notpip") -import pip_shims -from pip_shims.shims import VcsSupport, WheelCache, InstallationError - - -from .._compat import ( - is_file_url, - url_to_path, - PackageFinder, - RequirementSet, - Wheel, - FAVORITE_HASH, - TemporaryDirectory, - PyPI, - InstallRequirement, - SafeFileCache -) - -from ..cache import CACHE_DIR -from ..exceptions import NoCandidateFound -from ..utils import (fs_str, is_pinned_requirement, lookup_table, dedup, - make_install_requirement, clean_requires_python) -from .base import BaseRepository - -try: - from pipenv.patched.notpip._internal.req.req_tracker import RequirementTracker -except ImportError: - @contextmanager - def RequirementTracker(): - yield - - -class HashCache(SafeFileCache): - """Caches hashes of PyPI artifacts so we do not need to re-download them - - Hashes are only cached when the URL appears to contain a hash in it and the cache key includes - the hash value returned from the server). This ought to avoid ssues where the location on the - server changes.""" - def __init__(self, *args, **kwargs): - session = kwargs.pop('session') - self.session = session - kwargs.setdefault('directory', os.path.join(CACHE_DIR, 'hash-cache')) - super(HashCache, self).__init__(*args, **kwargs) - - def get_hash(self, location): - # if there is no location hash (i.e., md5 / sha256 / etc) we on't want to store it - hash_value = None - vcs = VcsSupport() - orig_scheme = location.scheme - new_location = copy.deepcopy(location) - if orig_scheme in vcs.all_schemes: - new_location.url = new_location.url.split("+", 1)[-1] - can_hash = new_location.hash - if can_hash: - # hash url WITH fragment - hash_value = self.get(new_location.url) - if not hash_value: - hash_value = self._get_file_hash(new_location) if not new_location.url.startswith("ssh") else None - hash_value = hash_value.encode('utf8') if hash_value else None - if can_hash: - self.set(new_location.url, hash_value) - return hash_value.decode('utf8') if hash_value else None - - def _get_file_hash(self, location): - h = hashlib.new(FAVORITE_HASH) - with open_local_or_remote_file(location, self.session) as fp: - for chunk in iter(lambda: fp.read(8096), b""): - h.update(chunk) - return ":".join([FAVORITE_HASH, h.hexdigest()]) - - -class PyPIRepository(BaseRepository): - DEFAULT_INDEX_URL = PyPI.simple_url - - """ - The PyPIRepository will use the provided Finder instance to lookup - packages. Typically, it looks up packages on PyPI (the default implicit - config), but any other PyPI mirror can be used if index_urls is - changed/configured on the Finder. - """ - def __init__(self, pip_options, session, build_isolation=False, use_json=False): - self.session = session - self.pip_options = pip_options - self.build_isolation = build_isolation - self.use_json = use_json - - index_urls = [pip_options.index_url] + pip_options.extra_index_urls - if pip_options.no_index: - index_urls = [] - - finder_kwargs = { - "find_links": pip_options.find_links, - "index_urls": index_urls, - "trusted_hosts": pip_options.trusted_hosts, - "allow_all_prereleases": pip_options.pre, - "session": self.session, - } - - # pip 19.0 has removed process_dependency_links from the PackageFinder constructor - if pkg_resources.parse_version(pip_shims.shims.pip_version) < pkg_resources.parse_version('19.0'): - finder_kwargs["process_dependency_links"] = pip_options.process_dependency_links - - self.finder = PackageFinder(**finder_kwargs) - - # Caches - # stores project_name => InstallationCandidate mappings for all - # versions reported by PyPI, so we only have to ask once for each - # project - self._available_candidates_cache = {} - - # stores InstallRequirement => list(InstallRequirement) mappings - # of all secondary dependencies for the given requirement, so we - # only have to go to disk once for each requirement - self._dependencies_cache = {} - self._json_dep_cache = {} - - # stores *full* path + fragment => sha256 - self._hash_cache = HashCache(session=session) - - # Setup file paths - self.freshen_build_caches() - self._download_dir = fs_str(os.path.join(CACHE_DIR, 'pkgs')) - self._wheel_download_dir = fs_str(os.path.join(CACHE_DIR, 'wheels')) - - def freshen_build_caches(self): - """ - Start with fresh build/source caches. Will remove any old build - caches from disk automatically. - """ - self._build_dir = TemporaryDirectory(fs_str('build')) - self._source_dir = TemporaryDirectory(fs_str('source')) - - @property - def build_dir(self): - return self._build_dir.name - - @property - def source_dir(self): - return self._source_dir.name - - def clear_caches(self): - rmtree(self._download_dir, ignore_errors=True) - rmtree(self._wheel_download_dir, ignore_errors=True) - - def find_all_candidates(self, req_name): - if req_name not in self._available_candidates_cache: - candidates = self.finder.find_all_candidates(req_name) - self._available_candidates_cache[req_name] = candidates - return self._available_candidates_cache[req_name] - - def find_best_match(self, ireq, prereleases=None): - """ - Returns a Version object that indicates the best match for the given - InstallRequirement according to the external repository. - """ - if ireq.editable: - return ireq # return itself as the best match - - all_candidates = clean_requires_python(self.find_all_candidates(ireq.name)) - candidates_by_version = lookup_table(all_candidates, key=lambda c: c.version, unique=True) - try: - matching_versions = ireq.specifier.filter((candidate.version for candidate in all_candidates), - prereleases=prereleases) - except TypeError: - matching_versions = [candidate.version for candidate in all_candidates] - - # Reuses pip's internal candidate sort key to sort - matching_candidates = [candidates_by_version[ver] for ver in matching_versions] - if not matching_candidates: - raise NoCandidateFound(ireq, all_candidates, self.finder) - best_candidate = max(matching_candidates, key=self.finder._candidate_sort_key) - - # Turn the candidate into a pinned InstallRequirement - return make_install_requirement( - best_candidate.project, best_candidate.version, ireq.extras, ireq.markers, constraint=ireq.constraint - ) - - def get_dependencies(self, ireq): - json_results = set() - - if self.use_json: - try: - json_results = self.get_json_dependencies(ireq) - except TypeError: - json_results = set() - - legacy_results = self.get_legacy_dependencies(ireq) - json_results.update(legacy_results) - - return json_results - - def get_json_dependencies(self, ireq): - - if not (is_pinned_requirement(ireq)): - raise TypeError('Expected pinned InstallRequirement, got {}'.format(ireq)) - - def gen(ireq): - if self.DEFAULT_INDEX_URL not in self.finder.index_urls: - return - - url = 'https://pypi.org/pypi/{0}/json'.format(ireq.req.name) - releases = self.session.get(url).json()['releases'] - - matches = [ - r for r in releases - if '=={0}'.format(r) == str(ireq.req.specifier) - ] - if not matches: - return - - release_requires = self.session.get( - 'https://pypi.org/pypi/{0}/{1}/json'.format( - ireq.req.name, matches[0], - ), - ).json() - try: - requires_dist = release_requires['info']['requires_dist'] - except KeyError: - return - - for requires in requires_dist: - i = InstallRequirement.from_line(requires) - if 'extra' not in repr(i.markers): - yield i - - try: - if ireq not in self._json_dep_cache: - self._json_dep_cache[ireq] = [g for g in gen(ireq)] - - return set(self._json_dep_cache[ireq]) - except Exception: - return set() - - def resolve_reqs(self, download_dir, ireq, wheel_cache): - results = None - ireq.isolated = self.build_isolation - ireq._wheel_cache = wheel_cache - if ireq and not ireq.link: - ireq.populate_link(self.finder, False, False) - if ireq.link and not ireq.link.is_wheel: - ireq.ensure_has_source_dir(self.source_dir) - try: - from pipenv.patched.notpip._internal.operations.prepare import RequirementPreparer - except ImportError: - # Pip 9 and below - reqset = RequirementSet( - self.build_dir, - self.source_dir, - download_dir=download_dir, - wheel_download_dir=self._wheel_download_dir, - session=self.session, - ignore_installed=True, - ignore_compatibility=False, - wheel_cache=wheel_cache - ) - results = reqset._prepare_file(self.finder, ireq, ignore_requires_python=True) - else: - # pip >= 10 - preparer_kwargs = { - 'build_dir': self.build_dir, - 'src_dir': self.source_dir, - 'download_dir': download_dir, - 'wheel_download_dir': self._wheel_download_dir, - 'progress_bar': 'off', - 'build_isolation': self.build_isolation, - } - resolver_kwargs = { - 'finder': self.finder, - 'session': self.session, - 'upgrade_strategy': "to-satisfy-only", - 'force_reinstall': False, - 'ignore_dependencies': False, - 'ignore_requires_python': True, - 'ignore_installed': True, - 'ignore_compatibility': False, - 'isolated': True, - 'wheel_cache': wheel_cache, - 'use_user_site': False, - 'use_pep517': True - } - resolver = None - preparer = None - with RequirementTracker() as req_tracker: - # Pip 18 uses a requirement tracker to prevent fork bombs - if req_tracker: - preparer_kwargs['req_tracker'] = req_tracker - preparer = RequirementPreparer(**preparer_kwargs) - resolver_kwargs['preparer'] = preparer - reqset = RequirementSet() - ireq.is_direct = True - # reqset.add_requirement(ireq) - resolver = pip_shims.shims.Resolver(**resolver_kwargs) - resolver.require_hashes = False - results = resolver._resolve_one(reqset, ireq) - - cleanup_fn = getattr(reqset, "cleanup_files", None) - if cleanup_fn is not None: - try: - cleanup_fn() - except OSError: - pass - - results = set(results) if results else set() - return results, ireq - - def get_legacy_dependencies(self, ireq): - """ - Given a pinned or an editable InstallRequirement, returns a set of - dependencies (also InstallRequirements, but not necessarily pinned). - They indicate the secondary dependencies for the given requirement. - """ - if not (ireq.editable or is_pinned_requirement(ireq)): - raise TypeError('Expected pinned or editable InstallRequirement, got {}'.format(ireq)) - - if ireq not in self._dependencies_cache: - if ireq.editable and (ireq.source_dir and os.path.exists(ireq.source_dir)): - # No download_dir for locally available editable requirements. - # If a download_dir is passed, pip will unnecessarely - # archive the entire source directory - download_dir = None - elif ireq.link and not ireq.link.is_artifact: - # No download_dir for VCS sources. This also works around pip - # using git-checkout-index, which gets rid of the .git dir. - download_dir = None - else: - download_dir = self._download_dir - if not os.path.isdir(download_dir): - os.makedirs(download_dir) - if not os.path.isdir(self._wheel_download_dir): - os.makedirs(self._wheel_download_dir) - - wheel_cache = WheelCache(CACHE_DIR, self.pip_options.format_control) - prev_tracker = os.environ.get('PIP_REQ_TRACKER') - try: - results, ireq = self.resolve_reqs(download_dir, ireq, wheel_cache) - self._dependencies_cache[ireq] = results - finally: - if 'PIP_REQ_TRACKER' in os.environ: - if prev_tracker: - os.environ['PIP_REQ_TRACKER'] = prev_tracker - else: - del os.environ['PIP_REQ_TRACKER'] - try: - self.wheel_cache.cleanup() - except AttributeError: - pass - return self._dependencies_cache[ireq] - - def get_hashes(self, ireq): - """ - Given an InstallRequirement, return a set of hashes that represent all - of the files for a given requirement. Editable requirements return an - empty set. Unpinned requirements raise a TypeError. - """ - if ireq.editable: - return set() - - vcs = VcsSupport() - if ireq.link and ireq.link.scheme in vcs.all_schemes and 'ssh' in ireq.link.scheme: - return set() - - if not is_pinned_requirement(ireq): - raise TypeError( - "Expected pinned requirement, got {}".format(ireq)) - - # We need to get all of the candidates that match our current version - # pin, these will represent all of the files that could possibly - # satisfy this constraint. - matching_candidates = ( - c for c in clean_requires_python(self.find_all_candidates(ireq.name)) - if c.version in ireq.specifier - ) - - return { - h for h in map(lambda c: self._hash_cache.get_hash(c.location), - matching_candidates) if h is not None - } - - @contextmanager - def allow_all_wheels(self): - """ - Monkey patches pip.Wheel to allow wheels from all platforms and Python versions. - - This also saves the candidate cache and set a new one, or else the results from the - previous non-patched calls will interfere. - """ - def _wheel_supported(self, tags=None): - # Ignore current platform. Support everything. - return True - - def _wheel_support_index_min(self, tags=None): - # All wheels are equal priority for sorting. - return 0 - - original_wheel_supported = Wheel.supported - original_support_index_min = Wheel.support_index_min - original_cache = self._available_candidates_cache - - Wheel.supported = _wheel_supported - Wheel.support_index_min = _wheel_support_index_min - self._available_candidates_cache = {} - - try: - yield - finally: - Wheel.supported = original_wheel_supported - Wheel.support_index_min = original_support_index_min - self._available_candidates_cache = original_cache - - -@contextmanager -def open_local_or_remote_file(link, session): - """ - Open local or remote file for reading. - - :type link: pip.index.Link - :type session: requests.Session - :raises ValueError: If link points to a local directory. - :return: a context manager to the opened file-like object - """ - url = link.url_without_fragment - - if is_file_url(link): - # Local URL - local_path = url_to_path(url) - if os.path.isdir(local_path): - raise ValueError("Cannot open directory for read: {}".format(url)) - else: - with open(local_path, 'rb') as local_file: - yield local_file - else: - # Remote URL - headers = {"Accept-Encoding": "identity"} - response = session.get(url, headers=headers, stream=True) - try: - yield response.raw - finally: - response.close() diff --git a/pipenv/patched/piptools/resolver.py b/pipenv/patched/piptools/resolver.py deleted file mode 100644 index b642bc9c..00000000 --- a/pipenv/patched/piptools/resolver.py +++ /dev/null @@ -1,312 +0,0 @@ -# coding: utf-8 -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import copy -from functools import partial -from itertools import chain, count -import os - -from ._compat import install_req_from_line - -from . import click -from .cache import DependencyCache -from .exceptions import UnsupportedConstraint -from .logging import log -from .utils import (format_requirement, format_specifier, full_groupby, - is_pinned_requirement, key_from_ireq, key_from_req, UNSAFE_PACKAGES) - -green = partial(click.style, fg='green') -magenta = partial(click.style, fg='magenta') - - -class RequirementSummary(object): - """ - Summary of a requirement's properties for comparison purposes. - """ - def __init__(self, ireq): - self.req = ireq.req - self.key = key_from_req(ireq.req) - self.extras = str(sorted(ireq.extras)) - self.markers = ireq.markers - self.specifier = str(ireq.specifier) - - def __eq__(self, other): - return str(self) == str(other) - - def __hash__(self): - return hash(str(self)) - - def __str__(self): - return repr([self.key, self.specifier, self.extras]) - - -class Resolver(object): - def __init__(self, constraints, repository, cache=None, prereleases=False, clear_caches=False, allow_unsafe=False): - """ - This class resolves a given set of constraints (a collection of - InstallRequirement objects) by consulting the given Repository and the - DependencyCache. - """ - self.our_constraints = set(constraints) - self.their_constraints = set() - self.repository = repository - if cache is None: - cache = DependencyCache() # pragma: no cover - self.dependency_cache = cache - self.prereleases = prereleases - self.clear_caches = clear_caches - self.allow_unsafe = allow_unsafe - self.unsafe_constraints = set() - - @property - def constraints(self): - return set(self._group_constraints(chain(self.our_constraints, - self.their_constraints))) - - def resolve_hashes(self, ireqs): - """ - Finds acceptable hashes for all of the given InstallRequirements. - """ - with self.repository.allow_all_wheels(): - return {ireq: self.repository.get_hashes(ireq) for ireq in ireqs} - - def resolve(self, max_rounds=10): - """ - Finds concrete package versions for all the given InstallRequirements - and their recursive dependencies. The end result is a flat list of - (name, version) tuples. (Or an editable package.) - - Resolves constraints one round at a time, until they don't change - anymore. Protects against infinite loops by breaking out after a max - number rounds. - """ - if self.clear_caches: - self.dependency_cache.clear() - self.repository.clear_caches() - - self.check_constraints(chain(self.our_constraints, - self.their_constraints)) - - # Ignore existing packages - os.environ[str('PIP_EXISTS_ACTION')] = str('i') # NOTE: str() wrapping necessary for Python 2/3 compat - for current_round in count(start=1): - if current_round > max_rounds: - raise RuntimeError('No stable configuration of concrete packages ' - 'could be found for the given constraints after ' - '%d rounds of resolving.\n' - 'This is likely a bug.' % max_rounds) - - log.debug('') - log.debug(magenta('{:^60}'.format('ROUND {}'.format(current_round)))) - has_changed, best_matches = self._resolve_one_round() - log.debug('-' * 60) - log.debug('Result of round {}: {}'.format(current_round, - 'not stable' if has_changed else 'stable, done')) - if not has_changed: - break - - # If a package version (foo==2.0) was built in a previous round, - # and in this round a different version of foo needs to be built - # (i.e. foo==1.0), the directory will exist already, which will - # cause a pip build failure. The trick is to start with a new - # build cache dir for every round, so this can never happen. - self.repository.freshen_build_caches() - - del os.environ['PIP_EXISTS_ACTION'] - # Only include hard requirements and not pip constraints - return {req for req in best_matches if not req.constraint} - - @staticmethod - def check_constraints(constraints): - for constraint in constraints: - if constraint.link is not None and not constraint.editable and not constraint.is_wheel: - msg = ('pip-compile does not support URLs as packages, unless they are editable. ' - 'Perhaps add -e option?') - raise UnsupportedConstraint(msg, constraint) - - def _group_constraints(self, constraints): - """ - Groups constraints (remember, InstallRequirements!) by their key name, - and combining their SpecifierSets into a single InstallRequirement per - package. For example, given the following constraints: - - Django<1.9,>=1.4.2 - django~=1.5 - Flask~=0.7 - - This will be combined into a single entry per package: - - django~=1.5,<1.9,>=1.4.2 - flask~=0.7 - - """ - for _, ireqs in full_groupby(constraints, key=key_from_ireq): - ireqs = list(ireqs) - editable_ireq = next((ireq for ireq in ireqs if ireq.editable), None) - if editable_ireq: - yield editable_ireq # ignore all the other specs: the editable one is the one that counts - continue - - ireqs = iter(ireqs) - # deepcopy the accumulator so as to not modify the self.our_constraints invariant - combined_ireq = copy.deepcopy(next(ireqs)) - combined_ireq.comes_from = None - for ireq in ireqs: - # NOTE we may be losing some info on dropped reqs here - combined_ireq.req.specifier &= ireq.req.specifier - combined_ireq.constraint &= ireq.constraint - if not combined_ireq.markers: - combined_ireq.markers = ireq.markers - else: - _markers = combined_ireq.markers._markers - if not isinstance(_markers[0], (tuple, list)): - combined_ireq.markers._markers = [_markers, 'and', ireq.markers._markers] - # Return a sorted, de-duped tuple of extras - combined_ireq.extras = tuple(sorted(set(tuple(combined_ireq.extras) + tuple(ireq.extras)))) - yield combined_ireq - - def _resolve_one_round(self): - """ - Resolves one level of the current constraints, by finding the best - match for each package in the repository and adding all requirements - for those best package versions. Some of these constraints may be new - or updated. - - Returns whether new constraints appeared in this round. If no - constraints were added or changed, this indicates a stable - configuration. - """ - # Sort this list for readability of terminal output - constraints = sorted(self.constraints, key=key_from_ireq) - unsafe_constraints = [] - original_constraints = copy.copy(constraints) - if not self.allow_unsafe: - for constraint in original_constraints: - if constraint.name in UNSAFE_PACKAGES: - constraints.remove(constraint) - constraint.req.specifier = None - unsafe_constraints.append(constraint) - - log.debug('Current constraints:') - for constraint in constraints: - log.debug(' {}'.format(constraint)) - - log.debug('') - log.debug('Finding the best candidates:') - best_matches = {self.get_best_match(ireq) for ireq in constraints} - - # Find the new set of secondary dependencies - log.debug('') - log.debug('Finding secondary dependencies:') - - safe_constraints = [] - for best_match in best_matches: - for dep in self._iter_dependencies(best_match): - if self.allow_unsafe or dep.name not in UNSAFE_PACKAGES: - safe_constraints.append(dep) - # Grouping constraints to make clean diff between rounds - theirs = set(self._group_constraints(safe_constraints)) - - # NOTE: We need to compare RequirementSummary objects, since - # InstallRequirement does not define equality - diff = {RequirementSummary(t) for t in theirs} - {RequirementSummary(t) for t in self.their_constraints} - removed = ({RequirementSummary(t) for t in self.their_constraints} - - {RequirementSummary(t) for t in theirs}) - unsafe = ({RequirementSummary(t) for t in unsafe_constraints} - - {RequirementSummary(t) for t in self.unsafe_constraints}) - - has_changed = len(diff) > 0 or len(removed) > 0 or len(unsafe) > 0 - if has_changed: - log.debug('') - log.debug('New dependencies found in this round:') - for new_dependency in sorted(diff, key=lambda req: key_from_req(req.req)): - log.debug(' adding {}'.format(new_dependency)) - log.debug('Removed dependencies in this round:') - for removed_dependency in sorted(removed, key=lambda req: key_from_req(req.req)): - log.debug(' removing {}'.format(removed_dependency)) - log.debug('Unsafe dependencies in this round:') - for unsafe_dependency in sorted(unsafe, key=lambda req: key_from_req(req.req)): - log.debug(' remembering unsafe {}'.format(unsafe_dependency)) - - # Store the last round's results in the their_constraints - self.their_constraints = theirs - # Store the last round's unsafe constraints - self.unsafe_constraints = unsafe_constraints - return has_changed, best_matches - - def get_best_match(self, ireq): - """ - Returns a (pinned or editable) InstallRequirement, indicating the best - match to use for the given InstallRequirement (in the form of an - InstallRequirement). - - Example: - Given the constraint Flask>=0.10, may return Flask==0.10.1 at - a certain moment in time. - - Pinned requirements will always return themselves, i.e. - - Flask==0.10.1 => Flask==0.10.1 - - """ - if ireq.editable: - # NOTE: it's much quicker to immediately return instead of - # hitting the index server - best_match = ireq - elif is_pinned_requirement(ireq): - # NOTE: it's much quicker to immediately return instead of - # hitting the index server - best_match = ireq - else: - best_match = self.repository.find_best_match(ireq, prereleases=self.prereleases) - - # Format the best match - log.debug(' found candidate {} (constraint was {})'.format(format_requirement(best_match), - format_specifier(ireq))) - return best_match - - def _iter_dependencies(self, ireq): - """ - Given a pinned or editable InstallRequirement, collects all the - secondary dependencies for them, either by looking them up in a local - cache, or by reaching out to the repository. - - Editable requirements will never be looked up, as they may have - changed at any time. - """ - if ireq.editable: - for dependency in self.repository.get_dependencies(ireq): - yield dependency - return - - # fix our malformed extras - if ireq.extras: - if getattr(ireq, "extra", None): - if ireq.extras: - ireq.extras.extend(ireq.extra) - else: - ireq.extras = ireq.extra - - elif not is_pinned_requirement(ireq): - raise TypeError('Expected pinned or editable requirement, got {}'.format(ireq)) - - # Now, either get the dependencies from the dependency cache (for - # speed), or reach out to the external repository to - # download and inspect the package version and get dependencies - # from there - if ireq not in self.dependency_cache: - log.debug(' {} not in cache, need to check index'.format(format_requirement(ireq)), fg='yellow') - dependencies = self.repository.get_dependencies(ireq) - self.dependency_cache[ireq] = sorted(set(format_requirement(ireq) for ireq in dependencies)) - - # Example: ['Werkzeug>=0.9', 'Jinja2>=2.4'] - dependency_strings = self.dependency_cache[ireq] - log.debug(' {:25} requires {}'.format(format_requirement(ireq), - ', '.join(sorted(dependency_strings, key=lambda s: s.lower())) or '-')) - for dependency_string in dependency_strings: - yield install_req_from_line(dependency_string, constraint=ireq.constraint) - - def reverse_dependencies(self, ireqs): - non_editable = [ireq for ireq in ireqs if not ireq.editable] - return self.dependency_cache.reverse_dependencies(non_editable) diff --git a/pipenv/patched/piptools/scripts/compile.py b/pipenv/patched/piptools/scripts/compile.py deleted file mode 100644 index 2eaea9b3..00000000 --- a/pipenv/patched/piptools/scripts/compile.py +++ /dev/null @@ -1,254 +0,0 @@ -# coding: utf-8 -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import os -import sys -import tempfile - -from .._compat import ( - install_req_from_line, - parse_requirements, -) - -from .. import click -from ..exceptions import PipToolsError -from ..logging import log -from ..pip import get_pip_command, pip_defaults -from ..repositories import LocalRequirementsRepository, PyPIRepository -from ..resolver import Resolver -from ..utils import (dedup, is_pinned_requirement, key_from_req, UNSAFE_PACKAGES) -from ..writer import OutputWriter - -DEFAULT_REQUIREMENTS_FILE = 'requirements.in' -DEFAULT_REQUIREMENTS_OUTPUT_FILE = 'requirements.txt' - - -@click.command() -@click.version_option() -@click.option('-v', '--verbose', count=True, help="Show more output") -@click.option('-q', '--quiet', count=True, help="Give less output") -@click.option('-n', '--dry-run', is_flag=True, help="Only show what would happen, don't change anything") -@click.option('-p', '--pre', is_flag=True, default=None, help="Allow resolving to prereleases (default is not)") -@click.option('-r', '--rebuild', is_flag=True, help="Clear any caches upfront, rebuild from scratch") -@click.option('-f', '--find-links', multiple=True, help="Look for archives in this directory or on this HTML page", envvar='PIP_FIND_LINKS') # noqa -@click.option('-i', '--index-url', help="Change index URL (defaults to {})".format(pip_defaults.index_url), envvar='PIP_INDEX_URL') # noqa -@click.option('--extra-index-url', multiple=True, help="Add additional index URL to search", envvar='PIP_EXTRA_INDEX_URL') # noqa -@click.option('--cert', help="Path to alternate CA bundle.") -@click.option('--client-cert', help="Path to SSL client certificate, a single file containing the private key and the certificate in PEM format.") # noqa -@click.option('--trusted-host', multiple=True, envvar='PIP_TRUSTED_HOST', - help="Mark this host as trusted, even though it does not have " - "valid or any HTTPS.") -@click.option('--header/--no-header', is_flag=True, default=True, - help="Add header to generated file") -@click.option('--index/--no-index', is_flag=True, default=True, - help="Add index URL to generated file") -@click.option('--emit-trusted-host/--no-emit-trusted-host', is_flag=True, - default=True, help="Add trusted host option to generated file") -@click.option('--annotate/--no-annotate', is_flag=True, default=True, - help="Annotate results, indicating where dependencies come from") -@click.option('-U', '--upgrade', is_flag=True, default=False, - help='Try to upgrade all dependencies to their latest versions') -@click.option('-P', '--upgrade-package', 'upgrade_packages', nargs=1, multiple=True, - help="Specify particular packages to upgrade.") -@click.option('-o', '--output-file', nargs=1, type=str, default=None, - help=('Output file name. Required if more than one input file is given. ' - 'Will be derived from input file otherwise.')) -@click.option('--allow-unsafe', is_flag=True, default=False, - help="Pin packages considered unsafe: {}".format(', '.join(sorted(UNSAFE_PACKAGES)))) -@click.option('--generate-hashes', is_flag=True, default=False, - help="Generate pip 8 style hashes in the resulting requirements file.") -@click.option('--max-rounds', default=10, - help="Maximum number of rounds before resolving the requirements aborts.") -@click.argument('src_files', nargs=-1, type=click.Path(exists=True, allow_dash=True)) -@click.option('--build-isolation/--no-build-isolation', is_flag=True, default=False, - help="Enable isolation when building a modern source distribution. " - "Build dependencies specified by PEP 518 must be already installed " - "if build isolation is disabled.") -def cli(verbose, quiet, dry_run, pre, rebuild, find_links, index_url, extra_index_url, - cert, client_cert, trusted_host, header, index, emit_trusted_host, annotate, - upgrade, upgrade_packages, output_file, allow_unsafe, generate_hashes, - src_files, max_rounds, build_isolation): - """Compiles requirements.txt from requirements.in specs.""" - log.verbosity = verbose - quiet - - if len(src_files) == 0: - if os.path.exists(DEFAULT_REQUIREMENTS_FILE): - src_files = (DEFAULT_REQUIREMENTS_FILE,) - elif os.path.exists('setup.py'): - src_files = ('setup.py',) - else: - raise click.BadParameter(("If you do not specify an input file, " - "the default is {} or setup.py").format(DEFAULT_REQUIREMENTS_FILE)) - - if src_files == ('-',) and not output_file: - raise click.BadParameter('--output-file is required if input is from stdin') - elif src_files == ('setup.py',) and not output_file: - output_file = DEFAULT_REQUIREMENTS_OUTPUT_FILE - - if len(src_files) > 1 and not output_file: - raise click.BadParameter('--output-file is required if two or more input files are given.') - - if output_file: - dst_file = output_file - else: - base_name = src_files[0].rsplit('.', 1)[0] - dst_file = base_name + '.txt' - - if upgrade and upgrade_packages: - raise click.BadParameter('Only one of --upgrade or --upgrade-package can be provided as an argument.') - - ### - # Setup - ### - - pip_command = get_pip_command() - - pip_args = [] - if find_links: - for link in find_links: - pip_args.extend(['-f', link]) - if index_url: - pip_args.extend(['-i', index_url]) - if extra_index_url: - for extra_index in extra_index_url: - pip_args.extend(['--extra-index-url', extra_index]) - if cert: - pip_args.extend(['--cert', cert]) - if client_cert: - pip_args.extend(['--client-cert', client_cert]) - if pre: - pip_args.extend(['--pre']) - if trusted_host: - for host in trusted_host: - pip_args.extend(['--trusted-host', host]) - - pip_options, _ = pip_command.parse_args(pip_args) - - session = pip_command._build_session(pip_options) - repository = PyPIRepository(pip_options, session, build_isolation) - - upgrade_install_reqs = {} - # Proxy with a LocalRequirementsRepository if --upgrade is not specified - # (= default invocation) - if not upgrade and os.path.exists(dst_file): - ireqs = parse_requirements(dst_file, finder=repository.finder, session=repository.session, options=pip_options) - # Exclude packages from --upgrade-package/-P from the existing pins: We want to upgrade. - upgrade_reqs_gen = (install_req_from_line(pkg) for pkg in upgrade_packages) - upgrade_install_reqs = {key_from_req(install_req.req): install_req for install_req in upgrade_reqs_gen} - - existing_pins = {key_from_req(ireq.req): ireq - for ireq in ireqs - if is_pinned_requirement(ireq) and key_from_req(ireq.req) not in upgrade_install_reqs} - repository = LocalRequirementsRepository(existing_pins, repository) - - log.debug('Using indexes:') - # remove duplicate index urls before processing - repository.finder.index_urls = list(dedup(repository.finder.index_urls)) - for index_url in repository.finder.index_urls: - log.debug(' {}'.format(index_url)) - - if repository.finder.find_links: - log.debug('') - log.debug('Configuration:') - for find_link in repository.finder.find_links: - log.debug(' -f {}'.format(find_link)) - - ### - # Parsing/collecting initial requirements - ### - - constraints = [] - for src_file in src_files: - is_setup_file = os.path.basename(src_file) == 'setup.py' - if is_setup_file or src_file == '-': - # pip requires filenames and not files. Since we want to support - # piping from stdin, we need to briefly save the input from stdin - # to a temporary file and have pip read that. also used for - # reading requirements from install_requires in setup.py. - tmpfile = tempfile.NamedTemporaryFile(mode='wt', delete=False) - if is_setup_file: - from distutils.core import run_setup - dist = run_setup(src_file) - tmpfile.write('\n'.join(dist.install_requires)) - else: - tmpfile.write(sys.stdin.read()) - tmpfile.flush() - constraints.extend(parse_requirements( - tmpfile.name, finder=repository.finder, session=repository.session, options=pip_options)) - else: - constraints.extend(parse_requirements( - src_file, finder=repository.finder, session=repository.session, options=pip_options)) - - constraints.extend(upgrade_install_reqs.values()) - - # Filter out pip environment markers which do not match (PEP496) - constraints = [req for req in constraints - if req.markers is None or req.markers.evaluate()] - - # Check the given base set of constraints first - Resolver.check_constraints(constraints) - - try: - resolver = Resolver(constraints, repository, prereleases=pre, - clear_caches=rebuild, allow_unsafe=allow_unsafe) - results = resolver.resolve(max_rounds=max_rounds) - if generate_hashes: - hashes = resolver.resolve_hashes(results) - else: - hashes = None - except PipToolsError as e: - log.error(str(e)) - sys.exit(2) - - log.debug('') - - ## - # Output - ## - - # Compute reverse dependency annotations statically, from the - # dependency cache that the resolver has populated by now. - # - # TODO (1a): reverse deps for any editable package are lost - # what SHOULD happen is that they are cached in memory, just - # not persisted to disk! - # - # TODO (1b): perhaps it's easiest if the dependency cache has an API - # that could take InstallRequirements directly, like: - # - # cache.set(ireq, ...) - # - # then, when ireq is editable, it would store in - # - # editables[egg_name][link_without_fragment] = deps - # editables['pip-tools']['git+...ols.git@future'] = {'click>=3.0', 'six'} - # - # otherwise: - # - # self[as_name_version_tuple(ireq)] = {'click>=3.0', 'six'} - # - reverse_dependencies = None - if annotate: - reverse_dependencies = resolver.reverse_dependencies(results) - - writer = OutputWriter(src_files, dst_file, dry_run=dry_run, - emit_header=header, emit_index=index, - emit_trusted_host=emit_trusted_host, - annotate=annotate, - generate_hashes=generate_hashes, - default_index_url=repository.DEFAULT_INDEX_URL, - index_urls=repository.finder.index_urls, - trusted_hosts=pip_options.trusted_hosts, - format_control=repository.finder.format_control, - allow_unsafe=allow_unsafe) - writer.write(results=results, - unsafe_requirements=resolver.unsafe_constraints, - reverse_dependencies=reverse_dependencies, - primary_packages={key_from_req(ireq.req) for ireq in constraints if not ireq.constraint}, - markers={key_from_req(ireq.req): ireq.markers - for ireq in constraints if ireq.markers}, - hashes=hashes) - - if dry_run: - log.warning('Dry-run, so nothing updated.') diff --git a/pipenv/patched/piptools/scripts/sync.py b/pipenv/patched/piptools/scripts/sync.py deleted file mode 100644 index 610c1d5e..00000000 --- a/pipenv/patched/piptools/scripts/sync.py +++ /dev/null @@ -1,74 +0,0 @@ -# coding: utf-8 -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import os -import sys - - -from .. import click, sync -from .._compat import parse_requirements, get_installed_distributions -from ..exceptions import PipToolsError -from ..logging import log -from ..utils import flat_map - -DEFAULT_REQUIREMENTS_FILE = 'requirements.txt' - - -@click.command() -@click.version_option() -@click.option('-n', '--dry-run', is_flag=True, help="Only show what would happen, don't change anything") -@click.option('--force', is_flag=True, help="Proceed even if conflicts are found") -@click.option('-f', '--find-links', multiple=True, help="Look for archives in this directory or on this HTML page", envvar='PIP_FIND_LINKS') # noqa -@click.option('-i', '--index-url', help="Change index URL (defaults to PyPI)", envvar='PIP_INDEX_URL') -@click.option('--extra-index-url', multiple=True, help="Add additional index URL to search", envvar='PIP_EXTRA_INDEX_URL') # noqa -@click.option('--no-index', is_flag=True, help="Ignore package index (only looking at --find-links URLs instead)") -@click.option('-q', '--quiet', default=False, is_flag=True, help="Give less output") -@click.option('--user', 'user_only', is_flag=True, help="Restrict attention to user directory") -@click.argument('src_files', required=False, type=click.Path(exists=True), nargs=-1) -def cli(dry_run, force, find_links, index_url, extra_index_url, no_index, quiet, user_only, src_files): - """Synchronize virtual environment with requirements.txt.""" - if not src_files: - if os.path.exists(DEFAULT_REQUIREMENTS_FILE): - src_files = (DEFAULT_REQUIREMENTS_FILE,) - else: - msg = 'No requirement files given and no {} found in the current directory' - log.error(msg.format(DEFAULT_REQUIREMENTS_FILE)) - sys.exit(2) - - if any(src_file.endswith('.in') for src_file in src_files): - msg = ('Some input files have the .in extension, which is most likely an error and can ' - 'cause weird behaviour. You probably meant to use the corresponding *.txt file?') - if force: - log.warning('WARNING: ' + msg) - else: - log.error('ERROR: ' + msg) - sys.exit(2) - - requirements = flat_map(lambda src: parse_requirements(src, session=True), - src_files) - - try: - requirements = sync.merge(requirements, ignore_conflicts=force) - except PipToolsError as e: - log.error(str(e)) - sys.exit(2) - - installed_dists = get_installed_distributions(skip=[], user_only=user_only) - to_install, to_uninstall = sync.diff(requirements, installed_dists) - - install_flags = [] - for link in find_links or []: - install_flags.extend(['-f', link]) - if no_index: - install_flags.append('--no-index') - if index_url: - install_flags.extend(['-i', index_url]) - if extra_index_url: - for extra_index in extra_index_url: - install_flags.extend(['--extra-index-url', extra_index]) - if user_only: - install_flags.append('--user') - - sys.exit(sync.sync(to_install, to_uninstall, verbose=(not quiet), dry_run=dry_run, - install_flags=install_flags)) diff --git a/pipenv/patched/piptools/sync.py b/pipenv/patched/piptools/sync.py deleted file mode 100644 index f111764e..00000000 --- a/pipenv/patched/piptools/sync.py +++ /dev/null @@ -1,168 +0,0 @@ -import collections -import os -import sys -import tempfile -from subprocess import check_call - -from piptools._compat import stdlib_pkgs, DEV_PKGS -from . import click -from .exceptions import IncompatibleRequirements, UnsupportedConstraint -from .utils import flat_map, format_requirement, key_from_ireq, key_from_req, get_hashes_from_ireq - -PACKAGES_TO_IGNORE = [ - '-markerlib', - 'pip', - 'pip-tools', - 'pip-review', - 'pkg-resources', -] + list(stdlib_pkgs) + list(DEV_PKGS) - - -def dependency_tree(installed_keys, root_key): - """ - Calculate the dependency tree for the package `root_key` and return - a collection of all its dependencies. Uses a DFS traversal algorithm. - - `installed_keys` should be a {key: requirement} mapping, e.g. - {'django': from_line('django==1.8')} - `root_key` should be the key to return the dependency tree for. - """ - dependencies = set() - queue = collections.deque() - - if root_key in installed_keys: - dep = installed_keys[root_key] - queue.append(dep) - - while queue: - v = queue.popleft() - key = key_from_req(v) - if key in dependencies: - continue - - dependencies.add(key) - - for dep_specifier in v.requires(): - dep_name = key_from_req(dep_specifier) - if dep_name in installed_keys: - dep = installed_keys[dep_name] - - if dep_specifier.specifier.contains(dep.version): - queue.append(dep) - - return dependencies - - -def get_dists_to_ignore(installed): - """ - Returns a collection of package names to ignore when performing pip-sync, - based on the currently installed environment. For example, when pip-tools - is installed in the local environment, it should be ignored, including all - of its dependencies (e.g. click). When pip-tools is not installed - locally, click should also be installed/uninstalled depending on the given - requirements. - """ - installed_keys = {key_from_req(r): r for r in installed} - return list(flat_map(lambda req: dependency_tree(installed_keys, req), PACKAGES_TO_IGNORE)) - - -def merge(requirements, ignore_conflicts): - by_key = {} - - for ireq in requirements: - if ireq.link is not None and not ireq.editable: - msg = ('pip-compile does not support URLs as packages, unless they are editable. ' - 'Perhaps add -e option?') - raise UnsupportedConstraint(msg, ireq) - - key = ireq.link or key_from_req(ireq.req) - - if not ignore_conflicts: - existing_ireq = by_key.get(key) - if existing_ireq: - # NOTE: We check equality here since we can assume that the - # requirements are all pinned - if ireq.specifier != existing_ireq.specifier: - raise IncompatibleRequirements(ireq, existing_ireq) - - # TODO: Always pick the largest specifier in case of a conflict - by_key[key] = ireq - - return by_key.values() - - -def diff(compiled_requirements, installed_dists): - """ - Calculate which packages should be installed or uninstalled, given a set - of compiled requirements and a list of currently installed modules. - """ - requirements_lut = {r.link or key_from_req(r.req): r for r in compiled_requirements} - - satisfied = set() # holds keys - to_install = set() # holds InstallRequirement objects - to_uninstall = set() # holds keys - - pkgs_to_ignore = get_dists_to_ignore(installed_dists) - for dist in installed_dists: - key = key_from_req(dist) - if key not in requirements_lut or not requirements_lut[key].match_markers(): - to_uninstall.add(key) - elif requirements_lut[key].specifier.contains(dist.version): - satisfied.add(key) - - for key, requirement in requirements_lut.items(): - if key not in satisfied and requirement.match_markers(): - to_install.add(requirement) - - # Make sure to not uninstall any packages that should be ignored - to_uninstall -= set(pkgs_to_ignore) - - return (to_install, to_uninstall) - - -def sync(to_install, to_uninstall, verbose=False, dry_run=False, install_flags=None): - """ - Install and uninstalls the given sets of modules. - """ - if not to_uninstall and not to_install: - click.echo("Everything up-to-date") - - pip_flags = [] - if not verbose: - pip_flags += ['-q'] - - if to_uninstall: - if dry_run: - click.echo("Would uninstall:") - for pkg in to_uninstall: - click.echo(" {}".format(pkg)) - else: - check_call([sys.executable, '-m', 'pip', 'uninstall', '-y'] + pip_flags + sorted(to_uninstall)) - - if to_install: - if install_flags is None: - install_flags = [] - if dry_run: - click.echo("Would install:") - for ireq in to_install: - click.echo(" {}".format(format_requirement(ireq))) - else: - # prepare requirement lines - req_lines = [] - for ireq in sorted(to_install, key=key_from_ireq): - ireq_hashes = get_hashes_from_ireq(ireq) - req_lines.append(format_requirement(ireq, hashes=ireq_hashes)) - - # save requirement lines to a temporary file - tmp_req_file = tempfile.NamedTemporaryFile(mode='wt', delete=False) - tmp_req_file.write('\n'.join(req_lines)) - tmp_req_file.close() - - try: - check_call( - [sys.executable, '-m', 'pip', 'install', '-r', tmp_req_file.name] + pip_flags + install_flags - ) - finally: - os.unlink(tmp_req_file.name) - - return 0 diff --git a/pipenv/patched/piptools/utils.py b/pipenv/patched/piptools/utils.py deleted file mode 100644 index fb846cc4..00000000 --- a/pipenv/patched/piptools/utils.py +++ /dev/null @@ -1,369 +0,0 @@ -# coding: utf-8 -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import os -import sys -from itertools import chain, groupby -from collections import OrderedDict - -import six - -from pipenv.vendor.packaging.specifiers import SpecifierSet, InvalidSpecifier -from pipenv.vendor.packaging.version import Version, InvalidVersion, parse as parse_version -from pipenv.vendor.packaging.markers import Marker, Op, Value, Variable - -from ._compat import install_req_from_line - -from .click import style - - -UNSAFE_PACKAGES = {'setuptools', 'distribute', 'pip'} - - - -def simplify_markers(ireq): - """simplify_markers "This code cleans up markers for a specific :class:`~InstallRequirement`" - - Clean and deduplicate markers. - - :param ireq: An InstallRequirement to clean - :type ireq: :class:`~pip._internal.req.req_install.InstallRequirement` - :return: An InstallRequirement with cleaned Markers - :rtype: :class:`~pip._internal.req.req_install.InstallRequirement` - """ - - if not getattr(ireq, 'markers', None): - return ireq - markers = ireq.markers - marker_list = [] - if isinstance(markers, six.string_types): - if ';' in markers: - markers = [Marker(m_str.strip()) for m_str in markers.split(';')] - else: - markers = Marker(markers) - for m in markers._markers: - _single_marker = [] - if isinstance(m[0], six.string_types): - continue - if not isinstance(m[0], (list, tuple)): - marker_list.append(''.join([_piece.serialize() for _piece in m])) - continue - for _marker_part in m: - if isinstance(_marker_part, six.string_types): - _single_marker.append(_marker_part) - continue - _single_marker.append(''.join([_piece.serialize() for _piece in _marker_part])) - _single_marker = [_m.strip() for _m in _single_marker] - marker_list.append(tuple(_single_marker,)) - marker_str = ' and '.join(list(dedup(tuple(marker_list,)))) if marker_list else '' - new_markers = Marker(marker_str) - ireq.markers = new_markers - new_ireq = install_req_from_line(format_requirement(ireq)) - if ireq.constraint: - new_ireq.constraint = ireq.constraint - return new_ireq - - -def clean_requires_python(candidates): - """Get a cleaned list of all the candidates with valid specifiers in the `requires_python` attributes.""" - all_candidates = [] - py_version = parse_version(os.environ.get('PIP_PYTHON_VERSION', '.'.join(map(str, sys.version_info[:3])))) - for c in candidates: - if getattr(c, "requires_python", None): - # Old specifications had people setting this to single digits - # which is effectively the same as '>=digit,<digit+1' - if c.requires_python.isdigit(): - c.requires_python = '>={0},<{1}'.format(c.requires_python, int(c.requires_python) + 1) - try: - specifierset = SpecifierSet(c.requires_python) - except InvalidSpecifier: - continue - else: - if not specifierset.contains(py_version): - continue - all_candidates.append(c) - return all_candidates - - -def key_from_ireq(ireq): - """Get a standardized key for an InstallRequirement.""" - if ireq.req is None and ireq.link is not None: - return str(ireq.link) - else: - return key_from_req(ireq.req) - - -def key_from_req(req): - """Get an all-lowercase version of the requirement's name.""" - if hasattr(req, 'key'): - # from pkg_resources, such as installed dists for pip-sync - key = req.key - else: - # from packaging, such as install requirements from requirements.txt - key = req.name - - key = key.replace('_', '-').lower() - return key - - -def comment(text): - return style(text, fg='green') - - -def make_install_requirement(name, version, extras, markers, constraint=False): - # If no extras are specified, the extras string is blank - extras_string = "" - if extras: - # Sort extras for stability - extras_string = "[{}]".format(",".join(sorted(extras))) - - if not markers: - return install_req_from_line( - str('{}{}=={}'.format(name, extras_string, version)), - constraint=constraint) - else: - return install_req_from_line( - str('{}{}=={}; {}'.format(name, extras_string, version, str(markers))), - constraint=constraint) - - -def _requirement_to_str_lowercase_name(requirement): - """ - Formats a packaging.requirements.Requirement with a lowercase name. - - This is simply a copy of - https://github.com/pypa/packaging/blob/16.8/packaging/requirements.py#L109-L124 - modified to lowercase the dependency name. - - Previously, we were invoking the original Requirement.__str__ method and - lowercasing the entire result, which would lowercase the name, *and* other, - important stuff that should not be lowercased (such as the marker). See - this issue for more information: https://github.com/pypa/pipenv/issues/2113. - """ - parts = [requirement.name.lower()] - - if requirement.extras: - parts.append("[{0}]".format(",".join(sorted(requirement.extras)))) - - if requirement.specifier: - parts.append(str(requirement.specifier)) - - if requirement.url: - parts.append("@ {0}".format(requirement.url)) - - if requirement.marker: - parts.append("; {0}".format(requirement.marker)) - - return "".join(parts) - - -def format_requirement(ireq, marker=None, hashes=None): - """ - Generic formatter for pretty printing InstallRequirements to the terminal - in a less verbose way than using its `__str__` method. - """ - if ireq.editable: - line = '-e {}'.format(ireq.link) - else: - line = _requirement_to_str_lowercase_name(ireq.req) - - if marker and ';' not in line: - line = '{}; {}'.format(line, marker) - - if hashes: - for hash_ in sorted(hashes): - line += " \\\n --hash={}".format(hash_) - - return line - - -def format_specifier(ireq): - """ - Generic formatter for pretty printing the specifier part of - InstallRequirements to the terminal. - """ - # TODO: Ideally, this is carried over to the pip library itself - specs = ireq.specifier._specs if ireq.req is not None else [] - specs = sorted(specs, key=lambda x: x._spec[1]) - return ','.join(str(s) for s in specs) or '<any>' - - -def is_pinned_requirement(ireq): - """ - Returns whether an InstallRequirement is a "pinned" requirement. - - An InstallRequirement is considered pinned if: - - - Is not editable - - It has exactly one specifier - - That specifier is "==" - - The version does not contain a wildcard - - Examples: - django==1.8 # pinned - django>1.8 # NOT pinned - django~=1.8 # NOT pinned - django==1.* # NOT pinned - """ - if ireq.editable: - return False - - if len(ireq.specifier._specs) != 1: - return False - - op, version = next(iter(ireq.specifier._specs))._spec - return (op == '==' or op == '===') and not version.endswith('.*') - - -def as_tuple(ireq): - """ - Pulls out the (name: str, version:str, extras:(str)) tuple from the pinned InstallRequirement. - """ - if not is_pinned_requirement(ireq): - raise TypeError('Expected a pinned InstallRequirement, got {}'.format(ireq)) - - name = key_from_req(ireq.req) - version = next(iter(ireq.specifier._specs))._spec[1] - extras = tuple(sorted(ireq.extras)) - return name, version, extras - - -def full_groupby(iterable, key=None): - """Like groupby(), but sorts the input on the group key first.""" - return groupby(sorted(iterable, key=key), key=key) - - -def flat_map(fn, collection): - """Map a function over a collection and flatten the result by one-level""" - return chain.from_iterable(map(fn, collection)) - - -def lookup_table(values, key=None, keyval=None, unique=False, use_lists=False): - """ - Builds a dict-based lookup table (index) elegantly. - - Supports building normal and unique lookup tables. For example: - - >>> assert lookup_table( - ... ['foo', 'bar', 'baz', 'qux', 'quux'], lambda s: s[0]) == { - ... 'b': {'bar', 'baz'}, - ... 'f': {'foo'}, - ... 'q': {'quux', 'qux'} - ... } - - For key functions that uniquely identify values, set unique=True: - - >>> assert lookup_table( - ... ['foo', 'bar', 'baz', 'qux', 'quux'], lambda s: s[0], - ... unique=True) == { - ... 'b': 'baz', - ... 'f': 'foo', - ... 'q': 'quux' - ... } - - For the values represented as lists, set use_lists=True: - - >>> assert lookup_table( - ... ['foo', 'bar', 'baz', 'qux', 'quux'], lambda s: s[0], - ... use_lists=True) == { - ... 'b': ['bar', 'baz'], - ... 'f': ['foo'], - ... 'q': ['qux', 'quux'] - ... } - - The values of the resulting lookup table will be values, not sets. - - For extra power, you can even change the values while building up the LUT. - To do so, use the `keyval` function instead of the `key` arg: - - >>> assert lookup_table( - ... ['foo', 'bar', 'baz', 'qux', 'quux'], - ... keyval=lambda s: (s[0], s[1:])) == { - ... 'b': {'ar', 'az'}, - ... 'f': {'oo'}, - ... 'q': {'uux', 'ux'} - ... } - - """ - if keyval is None: - if key is None: - keyval = (lambda v: v) - else: - keyval = (lambda v: (key(v), v)) - - if unique: - return dict(keyval(v) for v in values) - - lut = {} - for value in values: - k, v = keyval(value) - try: - s = lut[k] - except KeyError: - if use_lists: - s = lut[k] = list() - else: - s = lut[k] = set() - if use_lists: - s.append(v) - else: - s.add(v) - return dict(lut) - - -def dedup(iterable): - """Deduplicate an iterable object like iter(set(iterable)) but - order-reserved. - """ - return iter(OrderedDict.fromkeys(iterable)) - - -def name_from_req(req): - """Get the name of the requirement""" - if hasattr(req, 'project_name'): - # from pkg_resources, such as installed dists for pip-sync - return req.project_name - else: - # from packaging, such as install requirements from requirements.txt - return req.name - - -def fs_str(string): - """ - Convert given string to a correctly encoded filesystem string. - - On Python 2, if the input string is unicode, converts it to bytes - encoded with the filesystem encoding. - - On Python 3 returns the string as is, since Python 3 uses unicode - paths and the input string shouldn't be bytes. - - >>> fs_str(u'some path component/Something') - 'some path component/Something' - >>> assert isinstance(fs_str('whatever'), str) - >>> assert isinstance(fs_str(u'whatever'), str) - - :type string: str|unicode - :rtype: str - """ - if isinstance(string, str): - return string - assert not isinstance(string, bytes) - return string.encode(_fs_encoding) - - -_fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() - - -def get_hashes_from_ireq(ireq): - """ - Given an InstallRequirement, return a list of string hashes in the format "{algorithm}:{hash}". - Return an empty list if there are no hashes in the requirement options. - """ - result = [] - ireq_hashes = ireq.options.get('hashes', {}) - for algorithm, hexdigests in ireq_hashes.items(): - for hash_ in hexdigests: - result.append("{}:{}".format(algorithm, hash_)) - return result diff --git a/pipenv/patched/piptools/writer.py b/pipenv/patched/piptools/writer.py deleted file mode 100644 index 9ac97792..00000000 --- a/pipenv/patched/piptools/writer.py +++ /dev/null @@ -1,142 +0,0 @@ -import os -import sys -from itertools import chain - -from ._compat import ExitStack -from .click import unstyle, get_os_args -from .io import AtomicSaver -from .logging import log -from .utils import comment, dedup, format_requirement, key_from_req, UNSAFE_PACKAGES - - -class OutputWriter(object): - def __init__(self, src_files, dst_file, dry_run, emit_header, emit_index, - emit_trusted_host, annotate, generate_hashes, - default_index_url, index_urls, trusted_hosts, format_control, - allow_unsafe): - self.src_files = src_files - self.dst_file = dst_file - self.dry_run = dry_run - self.emit_header = emit_header - self.emit_index = emit_index - self.emit_trusted_host = emit_trusted_host - self.annotate = annotate - self.generate_hashes = generate_hashes - self.default_index_url = default_index_url - self.index_urls = index_urls - self.trusted_hosts = trusted_hosts - self.format_control = format_control - self.allow_unsafe = allow_unsafe - - def _sort_key(self, ireq): - return (not ireq.editable, str(ireq.req).lower()) - - def write_header(self): - if self.emit_header: - yield comment('#') - yield comment('# This file is autogenerated by pip-compile') - yield comment('# To update, run:') - yield comment('#') - custom_cmd = os.environ.get('CUSTOM_COMPILE_COMMAND') - if custom_cmd: - yield comment('# {}'.format(custom_cmd)) - else: - prog = os.path.basename(sys.argv[0]) - args = ' '.join(get_os_args()) - yield comment('# {prog} {args}'.format(prog=prog, args=args)) - yield comment('#') - - def write_index_options(self): - if self.emit_index: - for index, index_url in enumerate(dedup(self.index_urls)): - if index_url.rstrip('/') == self.default_index_url: - continue - flag = '--index-url' if index == 0 else '--extra-index-url' - yield '{} {}'.format(flag, index_url) - - def write_trusted_hosts(self): - if self.emit_trusted_host: - for trusted_host in dedup(self.trusted_hosts): - yield '--trusted-host {}'.format(trusted_host) - - def write_format_controls(self): - for nb in dedup(self.format_control.no_binary): - yield '--no-binary {}'.format(nb) - for ob in dedup(self.format_control.only_binary): - yield '--only-binary {}'.format(ob) - - def write_flags(self): - emitted = False - for line in chain(self.write_index_options(), - self.write_trusted_hosts(), - self.write_format_controls()): - emitted = True - yield line - if emitted: - yield '' - - def _iter_lines(self, results, unsafe_requirements, reverse_dependencies, - primary_packages, markers, hashes): - for line in self.write_header(): - yield line - for line in self.write_flags(): - yield line - - unsafe_requirements = {r for r in results if r.name in UNSAFE_PACKAGES} if not unsafe_requirements else unsafe_requirements # noqa - packages = {r for r in results if r.name not in UNSAFE_PACKAGES} - - packages = sorted(packages, key=self._sort_key) - - for ireq in packages: - line = self._format_requirement( - ireq, reverse_dependencies, primary_packages, - markers.get(key_from_req(ireq.req)), hashes=hashes) - yield line - - if unsafe_requirements: - unsafe_requirements = sorted(unsafe_requirements, key=self._sort_key) - yield '' - yield comment('# The following packages are considered to be unsafe in a requirements file:') - - for ireq in unsafe_requirements: - req = self._format_requirement(ireq, - reverse_dependencies, - primary_packages, - marker=markers.get(key_from_req(ireq.req)), - hashes=hashes) - if not self.allow_unsafe: - yield comment('# {}'.format(req)) - else: - yield req - - def write(self, results, unsafe_requirements, reverse_dependencies, - primary_packages, markers, hashes): - with ExitStack() as stack: - f = None - if not self.dry_run: - f = stack.enter_context(AtomicSaver(self.dst_file)) - - for line in self._iter_lines(results, unsafe_requirements, reverse_dependencies, - primary_packages, markers, hashes): - log.info(line) - if f: - f.write(unstyle(line).encode('utf-8')) - f.write(os.linesep.encode('utf-8')) - - def _format_requirement(self, ireq, reverse_dependencies, primary_packages, marker=None, hashes=None): - ireq_hashes = (hashes if hashes is not None else {}).get(ireq) - - line = format_requirement(ireq, marker=marker, hashes=ireq_hashes) - - if not self.annotate or key_from_req(ireq.req) in primary_packages: - return line - - # Annotate what packages this package is required by - required_by = reverse_dependencies.get(ireq.name.lower(), []) - if required_by: - annotation = ", ".join(sorted(required_by)) - line = "{:24}{}{}".format( - line, - " \\\n " if ireq_hashes else " ", - comment("# via " + annotation)) - return line diff --git a/pipenv/patched/safety.zip b/pipenv/patched/safety.zip deleted file mode 100644 index b839971a..00000000 Binary files a/pipenv/patched/safety.zip and /dev/null differ diff --git a/pipenv/patched/safety/__init__.py b/pipenv/patched/safety/__init__.py index 69563274..1667d43d 100644 --- a/pipenv/patched/safety/__init__.py +++ b/pipenv/patched/safety/__init__.py @@ -2,4 +2,4 @@ __author__ = """pyup.io""" __email__ = 'support@pyup.io' -__version__ = '1.8.5' +__version__ = '1.10.3' diff --git a/pipenv/patched/safety/__main__.py b/pipenv/patched/safety/__main__.py index d9a0bdab..90e702a7 100644 --- a/pipenv/patched/safety/__main__.py +++ b/pipenv/patched/safety/__main__.py @@ -1,8 +1,51 @@ """Allow safety to be executable through `python -m safety`.""" from __future__ import absolute_import -from .cli import cli +import os +import sys +import sysconfig + + +PATCHED_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +PIPENV_DIR = os.path.dirname(PATCHED_DIR) +VENDORED_DIR = os.path.join("PIPENV_DIR", "vendor") + + +def get_site_packages(): + prefixes = {sys.prefix, sysconfig.get_config_var('prefix')} + try: + prefixes.add(sys.real_prefix) + except AttributeError: + pass + form = sysconfig.get_path('purelib', expand=False) + py_version_short = '{0[0]}.{0[1]}'.format(sys.version_info) + return { + form.format(base=prefix, py_version_short=py_version_short) + for prefix in prefixes + } + + +def insert_before_site_packages(*paths): + site_packages = get_site_packages() + index = None + for i, path in enumerate(sys.path): + if path in site_packages: + index = i + break + if index is None: + sys.path += list(paths) + else: + sys.path = sys.path[:index] + list(paths) + sys.path[index:] + + +def insert_pipenv_dirs(): + insert_before_site_packages(os.path.dirname(PIPENV_DIR), PATCHED_DIR, VENDORED_DIR) if __name__ == "__main__": # pragma: no cover + insert_pipenv_dirs() + yaml_lib = "pipenv.patched.yaml{0}".format(sys.version_info[0]) + locals()[yaml_lib] = __import__(yaml_lib) + sys.modules["yaml"] = sys.modules[yaml_lib] + from pipenv.patched.safety.cli import cli cli(prog_name="safety") diff --git a/pipenv/patched/safety/cli.py b/pipenv/patched/safety/cli.py index 2e8f88df..0d092fb2 100644 --- a/pipenv/patched/safety/cli.py +++ b/pipenv/patched/safety/cli.py @@ -1,13 +1,13 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import import sys -import click -from safety import __version__ -from safety import safety -from safety.formatter import report +import pipenv.vendor.click as click +from pipenv.patched.safety import __version__ +from pipenv.patched.safety import safety +from pipenv.patched.safety.formatter import report, license_report import itertools -from safety.util import read_requirements, read_vulnerabilities -from safety.errors import DatabaseFetchError, DatabaseFileNotFoundError, InvalidKeyError +from pipenv.patched.safety.util import read_requirements, read_vulnerabilities, get_proxy_dict, get_packages_licenses +from pipenv.patched.safety.errors import DatabaseFetchError, DatabaseFileNotFoundError, InvalidKeyError, TooManyRequestsError try: from json.decoder import JSONDecodeError @@ -21,7 +21,7 @@ def cli(): @cli.command() -@click.option("--key", default="", +@click.option("--key", default="", envvar="SAFETY_API_KEY", help="API Key for pyup.io's vulnerability database. Can be set as SAFETY_API_KEY " "environment variable. Default: empty") @click.option("--db", default="", @@ -32,7 +32,8 @@ def cli(): help='Full reports include a security advisory (if available). Default: ' '--short-report') @click.option("--bare/--not-bare", default=False, - help='Output vulnerable packages only. Useful in combination with other tools.' + help='Output vulnerable packages only. ' + 'Useful in combination with other tools. ' 'Default: --not-bare') @click.option("--cache/--no-cache", default=False, help="Cache requests to the vulnerability database locally. Default: --no-cache") @@ -65,20 +66,14 @@ def check(key, db, json, full_report, bare, stdin, files, cache, ignore, output, d for d in pkg_resources.working_set if d.key not in {"python", "wsgiref", "argparse"} ] - proxy_dictionary = {} - if proxyhost is not None: - if proxyprotocol in ["http", "https"]: - proxy_dictionary = {proxyprotocol: "{0}://{1}:{2}".format(proxyprotocol, proxyhost, str(proxyport))} - else: - click.secho("Proxy Protocol should be http or https only.", fg="red") - sys.exit(-1) + proxy_dictionary = get_proxy_dict(proxyprotocol, proxyhost, proxyport) try: vulns = safety.check(packages=packages, key=key, db_mirror=db, cached=cache, ignore_ids=ignore, proxy=proxy_dictionary) output_report = report(vulns=vulns, full=full_report, json_report=json, bare_report=bare, - checked_packages=len(packages), + checked_packages=len(packages), db=db, key=key) @@ -107,7 +102,7 @@ def check(key, db, json, full_report, bare, stdin, files, cache, ignore, output, help='Full reports include a security advisory (if available). Default: ' '--short-report') @click.option("--bare/--not-bare", default=False, - help='Output vulnerable packages only. Useful in combination with other tools.' + help='Output vulnerable packages only. Useful in combination with other tools. ' 'Default: --not-bare') @click.option("file", "--file", "-f", type=click.File(), required=True, help="Read input from an insecure report file. Default: empty") @@ -127,5 +122,73 @@ def review(full_report, bare, file): click.secho(output_report, nl=False if bare and not vulns else True) +@cli.command() +@click.option("--key", envvar="SAFETY_API_KEY", + help="API Key for pyup.io's vulnerability database. Can be set as SAFETY_API_KEY " + "environment variable. Default: empty") +@click.option("--db", default="", + help="Path to a local license database. Default: empty") +@click.option("--json/--no-json", default=False, + help="Output packages licenses in JSON format. Default: --no-json") +@click.option("--bare/--not-bare", default=False, + help='Output packages licenses names only. ' + 'Useful in combination with other tools. ' + 'Default: --not-bare') +@click.option("--cache/--no-cache", default=True, + help='Whether license database file should be cached.' + 'Default: --cache') +@click.option("files", "--file", "-r", multiple=True, type=click.File(), + help="Read input from one (or multiple) requirement files. Default: empty") +@click.option("proxyhost", "--proxy-host", "-ph", multiple=False, type=str, default=None, + help="Proxy host IP or DNS --proxy-host") +@click.option("proxyport", "--proxy-port", "-pp", multiple=False, type=int, default=80, + help="Proxy port number --proxy-port") +@click.option("proxyprotocol", "--proxy-protocol", "-pr", multiple=False, type=str, default='http', + help="Proxy protocol (https or http) --proxy-protocol") +def license(key, db, json, bare, cache, files, proxyprotocol, proxyhost, proxyport): + + if files: + packages = list(itertools.chain.from_iterable(read_requirements(f, resolve=True) for f in files)) + else: + import pkg_resources + packages = [ + d for d in pkg_resources.working_set + if d.key not in {"python", "wsgiref", "argparse"} + ] + + proxy_dictionary = get_proxy_dict(proxyprotocol, proxyhost, proxyport) + try: + licenses_db = safety.get_licenses(key, db, cache, proxy_dictionary) + except InvalidKeyError as invalid_key_error: + if str(invalid_key_error): + message = str(invalid_key_error) + else: + message = "Your API Key '{key}' is invalid. See {link}".format( + key=key, link='https://goo.gl/O7Y1rS' + ) + click.secho(message, fg="red", file=sys.stderr) + sys.exit(-1) + except DatabaseFileNotFoundError: + click.secho("Unable to load licenses database from {db}".format(db=db), fg="red", file=sys.stderr) + sys.exit(-1) + except TooManyRequestsError: + click.secho("Unable to load licenses database (Too many requests, please wait before another request)", + fg="red", + file=sys.stderr + ) + sys.exit(-1) + except DatabaseFetchError: + click.secho("Unable to load licenses database", fg="red", file=sys.stderr) + sys.exit(-1) + filtered_packages_licenses = get_packages_licenses(packages, licenses_db) + output_report = license_report( + packages=packages, + licenses=filtered_packages_licenses, + json_report=json, + bare_report=bare + ) + click.secho(output_report, nl=True) + + if __name__ == "__main__": cli() diff --git a/pipenv/patched/safety/constants.py b/pipenv/patched/safety/constants.py index 71cf6c47..378b00f3 100644 --- a/pipenv/patched/safety/constants.py +++ b/pipenv/patched/safety/constants.py @@ -13,6 +13,8 @@ REQUEST_TIMEOUT = 5 CACHE_VALID_SECONDS = 60 * 60 * 2 # 2 hours +CACHE_LICENSES_VALID_SECONDS = 60 * 60 * 24 * 7 # one week + CACHE_FILE = os.path.join( os.path.expanduser("~"), ".safety", diff --git a/pipenv/patched/safety/errors.py b/pipenv/patched/safety/errors.py index 346adba7..a81e7ad8 100644 --- a/pipenv/patched/safety/errors.py +++ b/pipenv/patched/safety/errors.py @@ -8,3 +8,7 @@ class DatabaseFileNotFoundError(DatabaseFetchError): class InvalidKeyError(DatabaseFetchError): pass + + +class TooManyRequestsError(DatabaseFetchError): + pass diff --git a/pipenv/patched/safety/formatter.py b/pipenv/patched/safety/formatter.py index c19bff1b..a8ff3241 100644 --- a/pipenv/patched/safety/formatter.py +++ b/pipenv/patched/safety/formatter.py @@ -5,6 +5,8 @@ import json import os import textwrap +from .util import get_packages_licenses + # python 2.7 compat try: FileNotFoundError @@ -45,54 +47,52 @@ def get_advisory(vuln): class SheetReport(object): - REPORT_BANNER = """ -╒══════════════════════════════════════════════════════════════════════════════╕ -│ │ -│ /$$$$$$ /$$ │ -│ /$$__ $$ | $$ │ -│ /$$$$$$$ /$$$$$$ | $$ \__//$$$$$$ /$$$$$$ /$$ /$$ │ -│ /$$_____/ |____ $$| $$$$ /$$__ $$|_ $$_/ | $$ | $$ │ -│ | $$$$$$ /$$$$$$$| $$_/ | $$$$$$$$ | $$ | $$ | $$ │ -│ \____ $$ /$$__ $$| $$ | $$_____/ | $$ /$$| $$ | $$ │ -│ /$$$$$$$/| $$$$$$$| $$ | $$$$$$$ | $$$$/| $$$$$$$ │ -│ |_______/ \_______/|__/ \_______/ \___/ \____ $$ │ -│ /$$ | $$ │ -│ | $$$$$$/ │ -│ by pyup.io \______/ │ -│ │ -╞══════════════════════════════════════════════════════════════════════════════╡ + REPORT_BANNER = r""" ++==============================================================================+ +| | +| /$$$$$$ /$$ | +| /$$__ $$ | $$ | +| /$$$$$$$ /$$$$$$ | $$ \__//$$$$$$ /$$$$$$ /$$ /$$ | +| /$$_____/ |____ $$| $$$$ /$$__ $$|_ $$_/ | $$ | $$ | +| | $$$$$$ /$$$$$$$| $$_/ | $$$$$$$$ | $$ | $$ | $$ | +| \____ $$ /$$__ $$| $$ | $$_____/ | $$ /$$| $$ | $$ | +| /$$$$$$$/| $$$$$$$| $$ | $$$$$$$ | $$$$/| $$$$$$$ | +| |_______/ \_______/|__/ \_______/ \___/ \____ $$ | +| /$$ | $$ | +| | $$$$$$/ | +| by pyup.io \______/ | +| | ++==============================================================================+ """.strip() - TABLE_HEADING = """ -╞════════════════════════════╤═══════════╤══════════════════════════╤══════════╡ -│ package │ installed │ affected │ ID │ -╞════════════════════════════╧═══════════╧══════════════════════════╧══════════╡ + TABLE_HEADING = r""" ++============================+===========+==========================+==========+ +| package | installed | affected | ID | ++============================+===========+==========================+==========+ """.strip() - TABLE_FOOTER = """ -╘════════════════════════════╧═══════════╧══════════════════════════╧══════════╛ + TABLE_HEADING_LICENSES = r""" ++=============================================+===========+====================+ +| package | version | license | ++=============================================+===========+====================+ """.strip() - TABLE_BREAK = """ -╞════════════════════════════╡═══════════╡══════════════════════════╡══════════╡ + REPORT_HEADING = r""" +| REPORT | """.strip() - REPORT_HEADING = """ -│ REPORT │ + REPORT_SECTION = r""" ++==============================================================================+ """.strip() - REPORT_SECTION = """ -╞══════════════════════════════════════════════════════════════════════════════╡ - """.strip() - - REPORT_FOOTER = """ -╘══════════════════════════════════════════════════════════════════════════════╛ + REPORT_FOOTER = r""" ++==============================================================================+ """.strip() @staticmethod def render(vulns, full, checked_packages, used_db): db_format_str = '{: <' + str(51 - len(str(checked_packages))) + '}' - status = "│ checked {packages} packages, using {db} │".format( + status = "| checked {packages} packages, using {db} |".format( packages=checked_packages, db=db_format_str.format(used_db), section=SheetReport.REPORT_SECTION @@ -100,7 +100,7 @@ class SheetReport(object): if vulns: table = [] for n, vuln in enumerate(vulns): - table.append("│ {:26} │ {:9} │ {:24} │ {:8} │".format( + table.append("| {:26} | {:9} | {:24} | {:8} |".format( vuln.name[:26], vuln.version[:9], vuln.spec[:24], @@ -109,16 +109,48 @@ class SheetReport(object): if full: table.append(SheetReport.REPORT_SECTION) - descr = get_advisory(vuln) + if vuln.cvssv2 is not None: + base_score = vuln.cvssv2.get("base_score", "None") + impact_score = vuln.cvssv2.get("impact_score", "None") - for pn, paragraph in enumerate(descr.replace('\r', '').split('\n\n')): - if pn: - table.append("│ {:76} │".format('')) - for line in textwrap.wrap(paragraph, width=76): + table.append("| {:76} |".format( + "CVSS v2 | BASE SCORE: {} | IMPACT SCORE: {}".format( + base_score, + impact_score, + ) + )) + table.append(SheetReport.REPORT_SECTION) + + if vuln.cvssv3 is not None: + base_score = vuln.cvssv3.get("base_score", "None") + impact_score = vuln.cvssv3.get("impact_score", "None") + base_severity = vuln.cvssv3.get("base_severity", "None") + + table.append("| {:76} |".format( + "CVSS v3 | BASE SCORE: {} | IMPACT SCORE: {} | BASE SEVERITY: {}".format( + base_score, + impact_score, + base_severity, + ) + )) + table.append(SheetReport.REPORT_SECTION) + + advisory_lines = get_advisory(vuln).replace( + '\r', '' + ).splitlines() + + for line in advisory_lines: + if line == '': + table.append("| {:76} |".format(" ")) + for wrapped_line in textwrap.wrap(line, width=76): try: - table.append("│ {:76} │".format(line.encode('utf-8'))) + table.append("| {:76} |".format( + wrapped_line.encode('utf-8') + )) except TypeError: - table.append("│ {:76} │".format(line)) + table.append("| {:76} |".format( + wrapped_line + )) # append the REPORT_SECTION only if this isn't the last entry if n + 1 < len(vulns): table.append(SheetReport.REPORT_SECTION) @@ -127,12 +159,58 @@ class SheetReport(object): "\n".join(table), SheetReport.REPORT_FOOTER] ) else: - content = "│ {:76} │".format("No known security vulnerabilities found.") + content = "| {:76} |".format("No known security vulnerabilities found.") return "\n".join( [SheetReport.REPORT_BANNER, SheetReport.REPORT_HEADING, status, SheetReport.REPORT_SECTION, content, SheetReport.REPORT_FOOTER] ) + @staticmethod + def render_licenses(packages, packages_licenses): + heading = SheetReport.REPORT_HEADING.replace(" ", "", 12).replace( + "REPORT", " Packages licenses" + ) + if not packages_licenses: + content = "| {:76} |".format("No packages licenses found.") + return "\n".join( + [SheetReport.REPORT_BANNER, heading, SheetReport.REPORT_SECTION, + content, SheetReport.REPORT_FOOTER] + ) + + table = [] + iteration = 1 + for pkg_license in packages_licenses: + max_char = last_char = 43 # defines a limit for package name. + current_line = 1 + package = pkg_license['package'] + license = pkg_license['license'] + version = pkg_license['version'] + license_line = int(int(len(package) / max_char) / 2) + 1 # Calc to get which line to add the license info. + + table.append("| {:43} | {:9} | {:18} |".format( + package[:max_char], + version[:9] if current_line == license_line else "", + license[:18] if current_line == license_line else "", + )) + + long_name = True if len(package[max_char:]) > 0 else False + while long_name: # If the package has a long name, break it into multiple lines. + current_line += 1 + table.append("| {:43} | {:9} | {:18} |".format( + package[last_char:last_char+max_char], + version[:9] if current_line == license_line else "", + license[:18] if current_line == license_line else "", + )) + last_char = last_char+max_char + long_name = True if len(package[last_char:]) > 0 else False + + if iteration != len(packages_licenses): # Do not add dashes "----" for last package. + table.append("|" + ("-" * 78) + "|") + iteration += 1 + return "\n".join( + [SheetReport.REPORT_BANNER, heading, SheetReport.TABLE_HEADING_LICENSES, + "\n".join(table), SheetReport.REPORT_FOOTER] + ) class BasicReport(object): """Basic report, intented to be used for terminals with < 80 columns""" @@ -157,6 +235,26 @@ class BasicReport(object): vuln.vuln_id )) if full: + if vuln.cvssv2 is not None: + base_score = vuln.cvssv2.get("base_score", "None") + impact_score = vuln.cvssv2.get("impact_score", "None") + + table.append("CVSS v2 -- BASE SCORE: {}, IMPACT SCORE: {}".format( + base_score, + impact_score, + )) + + if vuln.cvssv3 is not None: + base_score = vuln.cvssv3.get("base_score", "None") + impact_score = vuln.cvssv3.get("impact_score", "None") + base_severity = vuln.cvssv3.get("base_severity", "None") + + table.append("CVSS v3 -- BASE SCORE: {}, IMPACT SCORE: {}, BASE SEVERITY: {}".format( + base_score, + impact_score, + base_severity, + )) + table.append(get_advisory(vuln)) table.append("--") else: @@ -165,6 +263,24 @@ class BasicReport(object): table ) + @staticmethod + def render_licenses(packages, packages_licenses): + table = [ + "safety", + "packages licenses", + "---" + ] + if not packages_licenses: + table.append("No packages licenses found.") + return "\n".join(table) + + for pkg_license in packages_licenses: + text = pkg_license['package'] + \ + ", version " + pkg_license['version'] + \ + ", license " + pkg_license['license'] + "\n" + table.append(text) + + return "\n".join(table) class JsonReport(object): """Json report, for when the output is input for something else""" @@ -172,6 +288,10 @@ class JsonReport(object): @staticmethod def render(vulns, full): return json.dumps(vulns, indent=4, sort_keys=True) + + @staticmethod + def render_licenses(packages_licenses): + return json.dumps(packages_licenses, indent=4, sort_keys=True) class BareReport(object): @@ -180,14 +300,22 @@ class BareReport(object): def render(vulns, full): return " ".join(set([v.name for v in vulns])) + @staticmethod + def render_licenses(packages_licenses): + licenses = set([pkg_li.get('license') for pkg_li in packages_licenses]) + if "N/A" in licenses: + licenses.remove("N/A") + sorted_licenses = sorted(licenses) + return " ".join(sorted_licenses) + def get_used_db(key, db): key = key if key else os.environ.get("SAFETY_API_KEY", False) + if db: + return "local DB" if key: return "pyup.io's DB" - if db == '': - return 'default DB' - return "local DB" + return "free DB (updated once a month)" def report(vulns, full=False, json_report=False, bare_report=False, checked_packages=0, db=None, key=None): @@ -200,3 +328,15 @@ def report(vulns, full=False, json_report=False, bare_report=False, checked_pack if size.columns >= 80: return SheetReport.render(vulns, full=full, checked_packages=checked_packages, used_db=used_db) return BasicReport.render(vulns, full=full, checked_packages=checked_packages, used_db=used_db) + + +def license_report(packages, licenses, json_report=False, bare_report=False): + if json_report: + return JsonReport.render_licenses(packages_licenses=licenses) + elif bare_report: + return BareReport.render_licenses(packages_licenses=licenses) + + size = get_terminal_size() + if size.columns >= 80: + return SheetReport.render_licenses(packages, licenses) + return BasicReport.render_licenses(packages, licenses) diff --git a/pipenv/patched/safety/safety.py b/pipenv/patched/safety/safety.py index 871bd775..8a8f9c84 100644 --- a/pipenv/patched/safety/safety.py +++ b/pipenv/patched/safety/safety.py @@ -1,17 +1,22 @@ # -*- coding: utf-8 -*- -import requests -from packaging.specifiers import SpecifierSet -from .errors import DatabaseFetchError, InvalidKeyError, DatabaseFileNotFoundError -from .constants import OPEN_MIRRORS, API_MIRRORS, REQUEST_TIMEOUT, CACHE_VALID_SECONDS, CACHE_FILE -from collections import namedtuple -import os -import json -import time import errno +import json +import os +import time +from collections import namedtuple + +import pipenv.vendor.requests as requests +from pipenv.vendor.packaging.specifiers import SpecifierSet + +from .constants import (API_MIRRORS, CACHE_FILE, CACHE_LICENSES_VALID_SECONDS, + CACHE_VALID_SECONDS, OPEN_MIRRORS, REQUEST_TIMEOUT) +from .errors import (DatabaseFetchError, DatabaseFileNotFoundError, + InvalidKeyError, TooManyRequestsError) +from .util import RequirementFile class Vulnerability(namedtuple("Vulnerability", - ["name", "spec", "version", "advisory", "vuln_id"])): + ["name", "spec", "version", "advisory", "vuln_id", "cvssv2", "cvssv3"])): pass @@ -22,7 +27,13 @@ def get_from_cache(db_name): data = json.loads(f.read()) if db_name in data: if "cached_at" in data[db_name]: - if data[db_name]["cached_at"] + CACHE_VALID_SECONDS > time.time(): + if 'licenses.json' in db_name: + # Getting the specific cache time for the licenses db. + cache_valid_seconds = CACHE_LICENSES_VALID_SECONDS + else: + cache_valid_seconds = CACHE_VALID_SECONDS + + if data[db_name]["cached_at"] + cache_valid_seconds > time.time(): return data[db_name]["db"] except json.JSONDecodeError: pass @@ -84,6 +95,8 @@ def fetch_database_url(mirror, db_name, key, cached, proxy): return data elif r.status_code == 403: raise InvalidKeyError() + elif r.status_code == 429: + raise TooManyRequestsError() def fetch_database_file(path, db_name): @@ -127,6 +140,10 @@ def check(packages, key, db_mirror, cached, ignore_ids, proxy): vulnerable_packages = frozenset(db.keys()) vulnerable = [] for pkg in packages: + # Ignore recursive files not resolved + if isinstance(pkg, RequirementFile): + continue + # normalize the package name, the safety-db is converting underscores to dashes and uses # lowercase name = pkg.key.replace("_", "-").lower() @@ -137,17 +154,23 @@ def check(packages, key, db_mirror, cached, ignore_ids, proxy): spec_set = SpecifierSet(specifiers=specifier) if spec_set.contains(pkg.version): if not db_full: - db_full = fetch_database(full=True, key=key, db=db_mirror) + db_full = fetch_database(full=True, key=key, db=db_mirror, cached=cached, proxy=proxy) for data in get_vulnerabilities(pkg=name, spec=specifier, db=db_full): vuln_id = data.get("id").replace("pyup.io-", "") + cve_id = data.get("cve") + if cve_id: + cve_id = cve_id.split(",")[0].strip() if vuln_id and vuln_id not in ignore_ids: + cve_meta = db_full.get("$meta", {}).get("cve", {}).get(cve_id, {}) vulnerable.append( Vulnerability( name=name, spec=specifier, version=pkg.version, advisory=data.get("advisory"), - vuln_id=vuln_id + vuln_id=vuln_id, + cvssv2=cve_meta.get("cvssv2", None), + cvssv3=cve_meta.get("cvssv3", None), ) ) return vulnerable @@ -162,8 +185,33 @@ def review(vulnerabilities): "version": vuln[2], "advisory": vuln[3], "vuln_id": vuln[4], + "cvssv2": None, + "cvssv3": None } vulnerable.append( Vulnerability(**current_vuln) ) return vulnerable + + +def get_licenses(key, db_mirror, cached, proxy): + key = key if key else os.environ.get("SAFETY_API_KEY", False) + + if not key and not db_mirror: + raise InvalidKeyError("The API-KEY was not provided.") + if db_mirror: + mirrors = [db_mirror] + else: + mirrors = API_MIRRORS + + db_name = "licenses.json" + + for mirror in mirrors: + # mirror can either be a local path or a URL + if mirror.startswith("http://") or mirror.startswith("https://"): + licenses = fetch_database_url(mirror, db_name=db_name, key=key, cached=cached, proxy=proxy) + else: + licenses = fetch_database_file(mirror, db_name=db_name) + if licenses: + return licenses + raise DatabaseFetchError() diff --git a/pipenv/patched/safety/util.py b/pipenv/patched/safety/util.py index 16062f41..a9428054 100644 --- a/pipenv/patched/safety/util.py +++ b/pipenv/patched/safety/util.py @@ -1,6 +1,7 @@ -from dparse.parser import setuptools_parse_requirements_backport as _parse_requirements +from pipenv.vendor.dparse.parser import setuptools_parse_requirements_backport as _parse_requirements from collections import namedtuple -import click +from pipenv.vendor.packaging.version import parse as parse_version +import pipenv.vendor.click as click import sys import json import os @@ -21,6 +22,8 @@ def parse_line(line): if line.startswith('-e') or line.startswith('http://') or line.startswith('https://'): if "#egg=" in line: line = line.split("#egg=")[-1] + if ' --hash' in line: + line = line.split(" --hash")[0] return _parse_requirements(line) @@ -52,7 +55,14 @@ def read_requirements(fh, resolve=False): # if this is a tempfile, skip if is_temp_file: continue - filename = line.strip("-r ").strip("--requirement").strip() + + # strip away the recursive flag + prefixes = ["-r", "--requirement"] + filename = line.strip() + for prefix in prefixes: + if filename.startswith(prefix): + filename = filename[len(prefix):].strip() + # if there is a comment, remove it if " #" in filename: filename = filename.split(" #")[0].strip() @@ -96,3 +106,66 @@ def read_requirements(fh, resolve=False): ) except ValueError: continue + + +def get_proxy_dict(proxyprotocol, proxyhost, proxyport): + proxy_dictionary = {} + if proxyhost is not None: + if proxyprotocol in ["http", "https"]: + proxy_dictionary = {proxyprotocol: "{0}://{1}:{2}".format(proxyprotocol, proxyhost, str(proxyport))} + else: + click.secho("Proxy Protocol should be http or https only.", fg="red") + sys.exit(-1) + return proxy_dictionary + + +def get_license_name_by_id(license_id, db): + licenses = db.get('licenses', []) + for name, id in licenses.items(): + if id == license_id: + return name + return None + +def get_packages_licenses(packages, licenses_db): + """Get the licenses for the specified packages based on their version. + + :param packages: packages list + :param licenses_db: the licenses db in the raw form. + :return: list of objects with the packages and their respectives licenses. + """ + packages_licenses_db = licenses_db.get('packages', {}) + filtered_packages_licenses = [] + + for pkg in packages: + # Ignore recursive files not resolved + if isinstance(pkg, RequirementFile): + continue + # normalize the package name + pkg_name = pkg.key.replace("_", "-").lower() + # packages may have different licenses depending their version. + pkg_licenses = packages_licenses_db.get(pkg_name, []) + version_requested = parse_version(pkg.version) + license_id = None + license_name = None + for pkg_version in pkg_licenses: + license_start_version = parse_version(pkg_version['start_version']) + # Stops and return the previous stored license when a new + # license starts on a version above the requested one. + if version_requested >= license_start_version: + license_id = pkg_version['license_id'] + else: + # We found the license for the version requested + break + + if license_id: + license_name = get_license_name_by_id(license_id, licenses_db) + if not license_id or not license_name: + license_name = "N/A" + + filtered_packages_licenses.append({ + "package": pkg_name, + "version": pkg.version, + "license": license_name + }) + + return filtered_packages_licenses diff --git a/pipenv/patched/yaml3/LICENSE b/pipenv/patched/yaml3/LICENSE new file mode 100644 index 00000000..2f1b8e15 --- /dev/null +++ b/pipenv/patched/yaml3/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2017-2021 Ingy döt Net +Copyright (c) 2006-2016 Kirill Simonov + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/pipenv/patched/yaml3/__init__.py b/pipenv/patched/yaml3/__init__.py new file mode 100644 index 00000000..86d07b55 --- /dev/null +++ b/pipenv/patched/yaml3/__init__.py @@ -0,0 +1,427 @@ + +from .error import * + +from .tokens import * +from .events import * +from .nodes import * + +from .loader import * +from .dumper import * + +__version__ = '5.4.1' +try: + from .cyaml import * + __with_libyaml__ = True +except ImportError: + __with_libyaml__ = False + +import io + +#------------------------------------------------------------------------------ +# Warnings control +#------------------------------------------------------------------------------ + +# 'Global' warnings state: +_warnings_enabled = { + 'YAMLLoadWarning': True, +} + +# Get or set global warnings' state +def warnings(settings=None): + if settings is None: + return _warnings_enabled + + if type(settings) is dict: + for key in settings: + if key in _warnings_enabled: + _warnings_enabled[key] = settings[key] + +# Warn when load() is called without Loader=... +class YAMLLoadWarning(RuntimeWarning): + pass + +def load_warning(method): + if _warnings_enabled['YAMLLoadWarning'] is False: + return + + import warnings + + message = ( + "calling yaml.%s() without Loader=... is deprecated, as the " + "default Loader is unsafe. Please read " + "https://msg.pyyaml.org/load for full details." + ) % method + + warnings.warn(message, YAMLLoadWarning, stacklevel=3) + +#------------------------------------------------------------------------------ +def scan(stream, Loader=Loader): + """ + Scan a YAML stream and produce scanning tokens. + """ + loader = Loader(stream) + try: + while loader.check_token(): + yield loader.get_token() + finally: + loader.dispose() + +def parse(stream, Loader=Loader): + """ + Parse a YAML stream and produce parsing events. + """ + loader = Loader(stream) + try: + while loader.check_event(): + yield loader.get_event() + finally: + loader.dispose() + +def compose(stream, Loader=Loader): + """ + Parse the first YAML document in a stream + and produce the corresponding representation tree. + """ + loader = Loader(stream) + try: + return loader.get_single_node() + finally: + loader.dispose() + +def compose_all(stream, Loader=Loader): + """ + Parse all YAML documents in a stream + and produce corresponding representation trees. + """ + loader = Loader(stream) + try: + while loader.check_node(): + yield loader.get_node() + finally: + loader.dispose() + +def load(stream, Loader=None): + """ + Parse the first YAML document in a stream + and produce the corresponding Python object. + """ + if Loader is None: + load_warning('load') + Loader = FullLoader + + loader = Loader(stream) + try: + return loader.get_single_data() + finally: + loader.dispose() + +def load_all(stream, Loader=None): + """ + Parse all YAML documents in a stream + and produce corresponding Python objects. + """ + if Loader is None: + load_warning('load_all') + Loader = FullLoader + + loader = Loader(stream) + try: + while loader.check_data(): + yield loader.get_data() + finally: + loader.dispose() + +def full_load(stream): + """ + Parse the first YAML document in a stream + and produce the corresponding Python object. + + Resolve all tags except those known to be + unsafe on untrusted input. + """ + return load(stream, FullLoader) + +def full_load_all(stream): + """ + Parse all YAML documents in a stream + and produce corresponding Python objects. + + Resolve all tags except those known to be + unsafe on untrusted input. + """ + return load_all(stream, FullLoader) + +def safe_load(stream): + """ + Parse the first YAML document in a stream + and produce the corresponding Python object. + + Resolve only basic YAML tags. This is known + to be safe for untrusted input. + """ + return load(stream, SafeLoader) + +def safe_load_all(stream): + """ + Parse all YAML documents in a stream + and produce corresponding Python objects. + + Resolve only basic YAML tags. This is known + to be safe for untrusted input. + """ + return load_all(stream, SafeLoader) + +def unsafe_load(stream): + """ + Parse the first YAML document in a stream + and produce the corresponding Python object. + + Resolve all tags, even those known to be + unsafe on untrusted input. + """ + return load(stream, UnsafeLoader) + +def unsafe_load_all(stream): + """ + Parse all YAML documents in a stream + and produce corresponding Python objects. + + Resolve all tags, even those known to be + unsafe on untrusted input. + """ + return load_all(stream, UnsafeLoader) + +def emit(events, stream=None, Dumper=Dumper, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None): + """ + Emit YAML parsing events into a stream. + If stream is None, return the produced string instead. + """ + getvalue = None + if stream is None: + stream = io.StringIO() + getvalue = stream.getvalue + dumper = Dumper(stream, canonical=canonical, indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break) + try: + for event in events: + dumper.emit(event) + finally: + dumper.dispose() + if getvalue: + return getvalue() + +def serialize_all(nodes, stream=None, Dumper=Dumper, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None): + """ + Serialize a sequence of representation trees into a YAML stream. + If stream is None, return the produced string instead. + """ + getvalue = None + if stream is None: + if encoding is None: + stream = io.StringIO() + else: + stream = io.BytesIO() + getvalue = stream.getvalue + dumper = Dumper(stream, canonical=canonical, indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break, + encoding=encoding, version=version, tags=tags, + explicit_start=explicit_start, explicit_end=explicit_end) + try: + dumper.open() + for node in nodes: + dumper.serialize(node) + dumper.close() + finally: + dumper.dispose() + if getvalue: + return getvalue() + +def serialize(node, stream=None, Dumper=Dumper, **kwds): + """ + Serialize a representation tree into a YAML stream. + If stream is None, return the produced string instead. + """ + return serialize_all([node], stream, Dumper=Dumper, **kwds) + +def dump_all(documents, stream=None, Dumper=Dumper, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + """ + Serialize a sequence of Python objects into a YAML stream. + If stream is None, return the produced string instead. + """ + getvalue = None + if stream is None: + if encoding is None: + stream = io.StringIO() + else: + stream = io.BytesIO() + getvalue = stream.getvalue + dumper = Dumper(stream, default_style=default_style, + default_flow_style=default_flow_style, + canonical=canonical, indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break, + encoding=encoding, version=version, tags=tags, + explicit_start=explicit_start, explicit_end=explicit_end, sort_keys=sort_keys) + try: + dumper.open() + for data in documents: + dumper.represent(data) + dumper.close() + finally: + dumper.dispose() + if getvalue: + return getvalue() + +def dump(data, stream=None, Dumper=Dumper, **kwds): + """ + Serialize a Python object into a YAML stream. + If stream is None, return the produced string instead. + """ + return dump_all([data], stream, Dumper=Dumper, **kwds) + +def safe_dump_all(documents, stream=None, **kwds): + """ + Serialize a sequence of Python objects into a YAML stream. + Produce only basic YAML tags. + If stream is None, return the produced string instead. + """ + return dump_all(documents, stream, Dumper=SafeDumper, **kwds) + +def safe_dump(data, stream=None, **kwds): + """ + Serialize a Python object into a YAML stream. + Produce only basic YAML tags. + If stream is None, return the produced string instead. + """ + return dump_all([data], stream, Dumper=SafeDumper, **kwds) + +def add_implicit_resolver(tag, regexp, first=None, + Loader=None, Dumper=Dumper): + """ + Add an implicit scalar detector. + If an implicit scalar value matches the given regexp, + the corresponding tag is assigned to the scalar. + first is a sequence of possible initial characters or None. + """ + if Loader is None: + loader.Loader.add_implicit_resolver(tag, regexp, first) + loader.FullLoader.add_implicit_resolver(tag, regexp, first) + loader.UnsafeLoader.add_implicit_resolver(tag, regexp, first) + else: + Loader.add_implicit_resolver(tag, regexp, first) + Dumper.add_implicit_resolver(tag, regexp, first) + +def add_path_resolver(tag, path, kind=None, Loader=None, Dumper=Dumper): + """ + Add a path based resolver for the given tag. + A path is a list of keys that forms a path + to a node in the representation tree. + Keys can be string values, integers, or None. + """ + if Loader is None: + loader.Loader.add_path_resolver(tag, path, kind) + loader.FullLoader.add_path_resolver(tag, path, kind) + loader.UnsafeLoader.add_path_resolver(tag, path, kind) + else: + Loader.add_path_resolver(tag, path, kind) + Dumper.add_path_resolver(tag, path, kind) + +def add_constructor(tag, constructor, Loader=None): + """ + Add a constructor for the given tag. + Constructor is a function that accepts a Loader instance + and a node object and produces the corresponding Python object. + """ + if Loader is None: + loader.Loader.add_constructor(tag, constructor) + loader.FullLoader.add_constructor(tag, constructor) + loader.UnsafeLoader.add_constructor(tag, constructor) + else: + Loader.add_constructor(tag, constructor) + +def add_multi_constructor(tag_prefix, multi_constructor, Loader=None): + """ + Add a multi-constructor for the given tag prefix. + Multi-constructor is called for a node if its tag starts with tag_prefix. + Multi-constructor accepts a Loader instance, a tag suffix, + and a node object and produces the corresponding Python object. + """ + if Loader is None: + loader.Loader.add_multi_constructor(tag_prefix, multi_constructor) + loader.FullLoader.add_multi_constructor(tag_prefix, multi_constructor) + loader.UnsafeLoader.add_multi_constructor(tag_prefix, multi_constructor) + else: + Loader.add_multi_constructor(tag_prefix, multi_constructor) + +def add_representer(data_type, representer, Dumper=Dumper): + """ + Add a representer for the given type. + Representer is a function accepting a Dumper instance + and an instance of the given data type + and producing the corresponding representation node. + """ + Dumper.add_representer(data_type, representer) + +def add_multi_representer(data_type, multi_representer, Dumper=Dumper): + """ + Add a representer for the given type. + Multi-representer is a function accepting a Dumper instance + and an instance of the given data type or subtype + and producing the corresponding representation node. + """ + Dumper.add_multi_representer(data_type, multi_representer) + +class YAMLObjectMetaclass(type): + """ + The metaclass for YAMLObject. + """ + def __init__(cls, name, bases, kwds): + super(YAMLObjectMetaclass, cls).__init__(name, bases, kwds) + if 'yaml_tag' in kwds and kwds['yaml_tag'] is not None: + if isinstance(cls.yaml_loader, list): + for loader in cls.yaml_loader: + loader.add_constructor(cls.yaml_tag, cls.from_yaml) + else: + cls.yaml_loader.add_constructor(cls.yaml_tag, cls.from_yaml) + + cls.yaml_dumper.add_representer(cls, cls.to_yaml) + +class YAMLObject(metaclass=YAMLObjectMetaclass): + """ + An object that can dump itself to a YAML stream + and load itself from a YAML stream. + """ + + __slots__ = () # no direct instantiation, so allow immutable subclasses + + yaml_loader = [Loader, FullLoader, UnsafeLoader] + yaml_dumper = Dumper + + yaml_tag = None + yaml_flow_style = None + + @classmethod + def from_yaml(cls, loader, node): + """ + Convert a representation node to a Python object. + """ + return loader.construct_yaml_object(node, cls) + + @classmethod + def to_yaml(cls, dumper, data): + """ + Convert a Python object to a representation node. + """ + return dumper.represent_yaml_object(cls.yaml_tag, data, cls, + flow_style=cls.yaml_flow_style) + diff --git a/pipenv/patched/yaml3/composer.py b/pipenv/patched/yaml3/composer.py new file mode 100644 index 00000000..6d15cb40 --- /dev/null +++ b/pipenv/patched/yaml3/composer.py @@ -0,0 +1,139 @@ + +__all__ = ['Composer', 'ComposerError'] + +from .error import MarkedYAMLError +from .events import * +from .nodes import * + +class ComposerError(MarkedYAMLError): + pass + +class Composer: + + def __init__(self): + self.anchors = {} + + def check_node(self): + # Drop the STREAM-START event. + if self.check_event(StreamStartEvent): + self.get_event() + + # If there are more documents available? + return not self.check_event(StreamEndEvent) + + def get_node(self): + # Get the root node of the next document. + if not self.check_event(StreamEndEvent): + return self.compose_document() + + def get_single_node(self): + # Drop the STREAM-START event. + self.get_event() + + # Compose a document if the stream is not empty. + document = None + if not self.check_event(StreamEndEvent): + document = self.compose_document() + + # Ensure that the stream contains no more documents. + if not self.check_event(StreamEndEvent): + event = self.get_event() + raise ComposerError("expected a single document in the stream", + document.start_mark, "but found another document", + event.start_mark) + + # Drop the STREAM-END event. + self.get_event() + + return document + + def compose_document(self): + # Drop the DOCUMENT-START event. + self.get_event() + + # Compose the root node. + node = self.compose_node(None, None) + + # Drop the DOCUMENT-END event. + self.get_event() + + self.anchors = {} + return node + + def compose_node(self, parent, index): + if self.check_event(AliasEvent): + event = self.get_event() + anchor = event.anchor + if anchor not in self.anchors: + raise ComposerError(None, None, "found undefined alias %r" + % anchor, event.start_mark) + return self.anchors[anchor] + event = self.peek_event() + anchor = event.anchor + if anchor is not None: + if anchor in self.anchors: + raise ComposerError("found duplicate anchor %r; first occurrence" + % anchor, self.anchors[anchor].start_mark, + "second occurrence", event.start_mark) + self.descend_resolver(parent, index) + if self.check_event(ScalarEvent): + node = self.compose_scalar_node(anchor) + elif self.check_event(SequenceStartEvent): + node = self.compose_sequence_node(anchor) + elif self.check_event(MappingStartEvent): + node = self.compose_mapping_node(anchor) + self.ascend_resolver() + return node + + def compose_scalar_node(self, anchor): + event = self.get_event() + tag = event.tag + if tag is None or tag == '!': + tag = self.resolve(ScalarNode, event.value, event.implicit) + node = ScalarNode(tag, event.value, + event.start_mark, event.end_mark, style=event.style) + if anchor is not None: + self.anchors[anchor] = node + return node + + def compose_sequence_node(self, anchor): + start_event = self.get_event() + tag = start_event.tag + if tag is None or tag == '!': + tag = self.resolve(SequenceNode, None, start_event.implicit) + node = SequenceNode(tag, [], + start_event.start_mark, None, + flow_style=start_event.flow_style) + if anchor is not None: + self.anchors[anchor] = node + index = 0 + while not self.check_event(SequenceEndEvent): + node.value.append(self.compose_node(node, index)) + index += 1 + end_event = self.get_event() + node.end_mark = end_event.end_mark + return node + + def compose_mapping_node(self, anchor): + start_event = self.get_event() + tag = start_event.tag + if tag is None or tag == '!': + tag = self.resolve(MappingNode, None, start_event.implicit) + node = MappingNode(tag, [], + start_event.start_mark, None, + flow_style=start_event.flow_style) + if anchor is not None: + self.anchors[anchor] = node + while not self.check_event(MappingEndEvent): + #key_event = self.peek_event() + item_key = self.compose_node(node, None) + #if item_key in node.value: + # raise ComposerError("while composing a mapping", start_event.start_mark, + # "found duplicate key", key_event.start_mark) + item_value = self.compose_node(node, item_key) + #node.value[item_key] = item_value + node.value.append((item_key, item_value)) + end_event = self.get_event() + node.end_mark = end_event.end_mark + return node + diff --git a/pipenv/patched/yaml3/constructor.py b/pipenv/patched/yaml3/constructor.py new file mode 100644 index 00000000..619acd30 --- /dev/null +++ b/pipenv/patched/yaml3/constructor.py @@ -0,0 +1,748 @@ + +__all__ = [ + 'BaseConstructor', + 'SafeConstructor', + 'FullConstructor', + 'UnsafeConstructor', + 'Constructor', + 'ConstructorError' +] + +from .error import * +from .nodes import * + +import collections.abc, datetime, base64, binascii, re, sys, types + +class ConstructorError(MarkedYAMLError): + pass + +class BaseConstructor: + + yaml_constructors = {} + yaml_multi_constructors = {} + + def __init__(self): + self.constructed_objects = {} + self.recursive_objects = {} + self.state_generators = [] + self.deep_construct = False + + def check_data(self): + # If there are more documents available? + return self.check_node() + + def check_state_key(self, key): + """Block special attributes/methods from being set in a newly created + object, to prevent user-controlled methods from being called during + deserialization""" + if self.get_state_keys_blacklist_regexp().match(key): + raise ConstructorError(None, None, + "blacklisted key '%s' in instance state found" % (key,), None) + + def get_data(self): + # Construct and return the next document. + if self.check_node(): + return self.construct_document(self.get_node()) + + def get_single_data(self): + # Ensure that the stream contains a single document and construct it. + node = self.get_single_node() + if node is not None: + return self.construct_document(node) + return None + + def construct_document(self, node): + data = self.construct_object(node) + while self.state_generators: + state_generators = self.state_generators + self.state_generators = [] + for generator in state_generators: + for dummy in generator: + pass + self.constructed_objects = {} + self.recursive_objects = {} + self.deep_construct = False + return data + + def construct_object(self, node, deep=False): + if node in self.constructed_objects: + return self.constructed_objects[node] + if deep: + old_deep = self.deep_construct + self.deep_construct = True + if node in self.recursive_objects: + raise ConstructorError(None, None, + "found unconstructable recursive node", node.start_mark) + self.recursive_objects[node] = None + constructor = None + tag_suffix = None + if node.tag in self.yaml_constructors: + constructor = self.yaml_constructors[node.tag] + else: + for tag_prefix in self.yaml_multi_constructors: + if tag_prefix is not None and node.tag.startswith(tag_prefix): + tag_suffix = node.tag[len(tag_prefix):] + constructor = self.yaml_multi_constructors[tag_prefix] + break + else: + if None in self.yaml_multi_constructors: + tag_suffix = node.tag + constructor = self.yaml_multi_constructors[None] + elif None in self.yaml_constructors: + constructor = self.yaml_constructors[None] + elif isinstance(node, ScalarNode): + constructor = self.__class__.construct_scalar + elif isinstance(node, SequenceNode): + constructor = self.__class__.construct_sequence + elif isinstance(node, MappingNode): + constructor = self.__class__.construct_mapping + if tag_suffix is None: + data = constructor(self, node) + else: + data = constructor(self, tag_suffix, node) + if isinstance(data, types.GeneratorType): + generator = data + data = next(generator) + if self.deep_construct: + for dummy in generator: + pass + else: + self.state_generators.append(generator) + self.constructed_objects[node] = data + del self.recursive_objects[node] + if deep: + self.deep_construct = old_deep + return data + + def construct_scalar(self, node): + if not isinstance(node, ScalarNode): + raise ConstructorError(None, None, + "expected a scalar node, but found %s" % node.id, + node.start_mark) + return node.value + + def construct_sequence(self, node, deep=False): + if not isinstance(node, SequenceNode): + raise ConstructorError(None, None, + "expected a sequence node, but found %s" % node.id, + node.start_mark) + return [self.construct_object(child, deep=deep) + for child in node.value] + + def construct_mapping(self, node, deep=False): + if not isinstance(node, MappingNode): + raise ConstructorError(None, None, + "expected a mapping node, but found %s" % node.id, + node.start_mark) + mapping = {} + for key_node, value_node in node.value: + key = self.construct_object(key_node, deep=deep) + if not isinstance(key, collections.abc.Hashable): + raise ConstructorError("while constructing a mapping", node.start_mark, + "found unhashable key", key_node.start_mark) + value = self.construct_object(value_node, deep=deep) + mapping[key] = value + return mapping + + def construct_pairs(self, node, deep=False): + if not isinstance(node, MappingNode): + raise ConstructorError(None, None, + "expected a mapping node, but found %s" % node.id, + node.start_mark) + pairs = [] + for key_node, value_node in node.value: + key = self.construct_object(key_node, deep=deep) + value = self.construct_object(value_node, deep=deep) + pairs.append((key, value)) + return pairs + + @classmethod + def add_constructor(cls, tag, constructor): + if not 'yaml_constructors' in cls.__dict__: + cls.yaml_constructors = cls.yaml_constructors.copy() + cls.yaml_constructors[tag] = constructor + + @classmethod + def add_multi_constructor(cls, tag_prefix, multi_constructor): + if not 'yaml_multi_constructors' in cls.__dict__: + cls.yaml_multi_constructors = cls.yaml_multi_constructors.copy() + cls.yaml_multi_constructors[tag_prefix] = multi_constructor + +class SafeConstructor(BaseConstructor): + + def construct_scalar(self, node): + if isinstance(node, MappingNode): + for key_node, value_node in node.value: + if key_node.tag == 'tag:yaml.org,2002:value': + return self.construct_scalar(value_node) + return super().construct_scalar(node) + + def flatten_mapping(self, node): + merge = [] + index = 0 + while index < len(node.value): + key_node, value_node = node.value[index] + if key_node.tag == 'tag:yaml.org,2002:merge': + del node.value[index] + if isinstance(value_node, MappingNode): + self.flatten_mapping(value_node) + merge.extend(value_node.value) + elif isinstance(value_node, SequenceNode): + submerge = [] + for subnode in value_node.value: + if not isinstance(subnode, MappingNode): + raise ConstructorError("while constructing a mapping", + node.start_mark, + "expected a mapping for merging, but found %s" + % subnode.id, subnode.start_mark) + self.flatten_mapping(subnode) + submerge.append(subnode.value) + submerge.reverse() + for value in submerge: + merge.extend(value) + else: + raise ConstructorError("while constructing a mapping", node.start_mark, + "expected a mapping or list of mappings for merging, but found %s" + % value_node.id, value_node.start_mark) + elif key_node.tag == 'tag:yaml.org,2002:value': + key_node.tag = 'tag:yaml.org,2002:str' + index += 1 + else: + index += 1 + if merge: + node.value = merge + node.value + + def construct_mapping(self, node, deep=False): + if isinstance(node, MappingNode): + self.flatten_mapping(node) + return super().construct_mapping(node, deep=deep) + + def construct_yaml_null(self, node): + self.construct_scalar(node) + return None + + bool_values = { + 'yes': True, + 'no': False, + 'true': True, + 'false': False, + 'on': True, + 'off': False, + } + + def construct_yaml_bool(self, node): + value = self.construct_scalar(node) + return self.bool_values[value.lower()] + + def construct_yaml_int(self, node): + value = self.construct_scalar(node) + value = value.replace('_', '') + sign = +1 + if value[0] == '-': + sign = -1 + if value[0] in '+-': + value = value[1:] + if value == '0': + return 0 + elif value.startswith('0b'): + return sign*int(value[2:], 2) + elif value.startswith('0x'): + return sign*int(value[2:], 16) + elif value[0] == '0': + return sign*int(value, 8) + elif ':' in value: + digits = [int(part) for part in value.split(':')] + digits.reverse() + base = 1 + value = 0 + for digit in digits: + value += digit*base + base *= 60 + return sign*value + else: + return sign*int(value) + + inf_value = 1e300 + while inf_value != inf_value*inf_value: + inf_value *= inf_value + nan_value = -inf_value/inf_value # Trying to make a quiet NaN (like C99). + + def construct_yaml_float(self, node): + value = self.construct_scalar(node) + value = value.replace('_', '').lower() + sign = +1 + if value[0] == '-': + sign = -1 + if value[0] in '+-': + value = value[1:] + if value == '.inf': + return sign*self.inf_value + elif value == '.nan': + return self.nan_value + elif ':' in value: + digits = [float(part) for part in value.split(':')] + digits.reverse() + base = 1 + value = 0.0 + for digit in digits: + value += digit*base + base *= 60 + return sign*value + else: + return sign*float(value) + + def construct_yaml_binary(self, node): + try: + value = self.construct_scalar(node).encode('ascii') + except UnicodeEncodeError as exc: + raise ConstructorError(None, None, + "failed to convert base64 data into ascii: %s" % exc, + node.start_mark) + try: + if hasattr(base64, 'decodebytes'): + return base64.decodebytes(value) + else: + return base64.decodestring(value) + except binascii.Error as exc: + raise ConstructorError(None, None, + "failed to decode base64 data: %s" % exc, node.start_mark) + + timestamp_regexp = re.compile( + r'''^(?P<year>[0-9][0-9][0-9][0-9]) + -(?P<month>[0-9][0-9]?) + -(?P<day>[0-9][0-9]?) + (?:(?:[Tt]|[ \t]+) + (?P<hour>[0-9][0-9]?) + :(?P<minute>[0-9][0-9]) + :(?P<second>[0-9][0-9]) + (?:\.(?P<fraction>[0-9]*))? + (?:[ \t]*(?P<tz>Z|(?P<tz_sign>[-+])(?P<tz_hour>[0-9][0-9]?) + (?::(?P<tz_minute>[0-9][0-9]))?))?)?$''', re.X) + + def construct_yaml_timestamp(self, node): + value = self.construct_scalar(node) + match = self.timestamp_regexp.match(node.value) + values = match.groupdict() + year = int(values['year']) + month = int(values['month']) + day = int(values['day']) + if not values['hour']: + return datetime.date(year, month, day) + hour = int(values['hour']) + minute = int(values['minute']) + second = int(values['second']) + fraction = 0 + tzinfo = None + if values['fraction']: + fraction = values['fraction'][:6] + while len(fraction) < 6: + fraction += '0' + fraction = int(fraction) + if values['tz_sign']: + tz_hour = int(values['tz_hour']) + tz_minute = int(values['tz_minute'] or 0) + delta = datetime.timedelta(hours=tz_hour, minutes=tz_minute) + if values['tz_sign'] == '-': + delta = -delta + tzinfo = datetime.timezone(delta) + elif values['tz']: + tzinfo = datetime.timezone.utc + return datetime.datetime(year, month, day, hour, minute, second, fraction, + tzinfo=tzinfo) + + def construct_yaml_omap(self, node): + # Note: we do not check for duplicate keys, because it's too + # CPU-expensive. + omap = [] + yield omap + if not isinstance(node, SequenceNode): + raise ConstructorError("while constructing an ordered map", node.start_mark, + "expected a sequence, but found %s" % node.id, node.start_mark) + for subnode in node.value: + if not isinstance(subnode, MappingNode): + raise ConstructorError("while constructing an ordered map", node.start_mark, + "expected a mapping of length 1, but found %s" % subnode.id, + subnode.start_mark) + if len(subnode.value) != 1: + raise ConstructorError("while constructing an ordered map", node.start_mark, + "expected a single mapping item, but found %d items" % len(subnode.value), + subnode.start_mark) + key_node, value_node = subnode.value[0] + key = self.construct_object(key_node) + value = self.construct_object(value_node) + omap.append((key, value)) + + def construct_yaml_pairs(self, node): + # Note: the same code as `construct_yaml_omap`. + pairs = [] + yield pairs + if not isinstance(node, SequenceNode): + raise ConstructorError("while constructing pairs", node.start_mark, + "expected a sequence, but found %s" % node.id, node.start_mark) + for subnode in node.value: + if not isinstance(subnode, MappingNode): + raise ConstructorError("while constructing pairs", node.start_mark, + "expected a mapping of length 1, but found %s" % subnode.id, + subnode.start_mark) + if len(subnode.value) != 1: + raise ConstructorError("while constructing pairs", node.start_mark, + "expected a single mapping item, but found %d items" % len(subnode.value), + subnode.start_mark) + key_node, value_node = subnode.value[0] + key = self.construct_object(key_node) + value = self.construct_object(value_node) + pairs.append((key, value)) + + def construct_yaml_set(self, node): + data = set() + yield data + value = self.construct_mapping(node) + data.update(value) + + def construct_yaml_str(self, node): + return self.construct_scalar(node) + + def construct_yaml_seq(self, node): + data = [] + yield data + data.extend(self.construct_sequence(node)) + + def construct_yaml_map(self, node): + data = {} + yield data + value = self.construct_mapping(node) + data.update(value) + + def construct_yaml_object(self, node, cls): + data = cls.__new__(cls) + yield data + if hasattr(data, '__setstate__'): + state = self.construct_mapping(node, deep=True) + data.__setstate__(state) + else: + state = self.construct_mapping(node) + data.__dict__.update(state) + + def construct_undefined(self, node): + raise ConstructorError(None, None, + "could not determine a constructor for the tag %r" % node.tag, + node.start_mark) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:null', + SafeConstructor.construct_yaml_null) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:bool', + SafeConstructor.construct_yaml_bool) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:int', + SafeConstructor.construct_yaml_int) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:float', + SafeConstructor.construct_yaml_float) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:binary', + SafeConstructor.construct_yaml_binary) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:timestamp', + SafeConstructor.construct_yaml_timestamp) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:omap', + SafeConstructor.construct_yaml_omap) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:pairs', + SafeConstructor.construct_yaml_pairs) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:set', + SafeConstructor.construct_yaml_set) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:str', + SafeConstructor.construct_yaml_str) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:seq', + SafeConstructor.construct_yaml_seq) + +SafeConstructor.add_constructor( + 'tag:yaml.org,2002:map', + SafeConstructor.construct_yaml_map) + +SafeConstructor.add_constructor(None, + SafeConstructor.construct_undefined) + +class FullConstructor(SafeConstructor): + # 'extend' is blacklisted because it is used by + # construct_python_object_apply to add `listitems` to a newly generate + # python instance + def get_state_keys_blacklist(self): + return ['^extend$', '^__.*__$'] + + def get_state_keys_blacklist_regexp(self): + if not hasattr(self, 'state_keys_blacklist_regexp'): + self.state_keys_blacklist_regexp = re.compile('(' + '|'.join(self.get_state_keys_blacklist()) + ')') + return self.state_keys_blacklist_regexp + + def construct_python_str(self, node): + return self.construct_scalar(node) + + def construct_python_unicode(self, node): + return self.construct_scalar(node) + + def construct_python_bytes(self, node): + try: + value = self.construct_scalar(node).encode('ascii') + except UnicodeEncodeError as exc: + raise ConstructorError(None, None, + "failed to convert base64 data into ascii: %s" % exc, + node.start_mark) + try: + if hasattr(base64, 'decodebytes'): + return base64.decodebytes(value) + else: + return base64.decodestring(value) + except binascii.Error as exc: + raise ConstructorError(None, None, + "failed to decode base64 data: %s" % exc, node.start_mark) + + def construct_python_long(self, node): + return self.construct_yaml_int(node) + + def construct_python_complex(self, node): + return complex(self.construct_scalar(node)) + + def construct_python_tuple(self, node): + return tuple(self.construct_sequence(node)) + + def find_python_module(self, name, mark, unsafe=False): + if not name: + raise ConstructorError("while constructing a Python module", mark, + "expected non-empty name appended to the tag", mark) + if unsafe: + try: + __import__(name) + except ImportError as exc: + raise ConstructorError("while constructing a Python module", mark, + "cannot find module %r (%s)" % (name, exc), mark) + if name not in sys.modules: + raise ConstructorError("while constructing a Python module", mark, + "module %r is not imported" % name, mark) + return sys.modules[name] + + def find_python_name(self, name, mark, unsafe=False): + if not name: + raise ConstructorError("while constructing a Python object", mark, + "expected non-empty name appended to the tag", mark) + if '.' in name: + module_name, object_name = name.rsplit('.', 1) + else: + module_name = 'builtins' + object_name = name + if unsafe: + try: + __import__(module_name) + except ImportError as exc: + raise ConstructorError("while constructing a Python object", mark, + "cannot find module %r (%s)" % (module_name, exc), mark) + if module_name not in sys.modules: + raise ConstructorError("while constructing a Python object", mark, + "module %r is not imported" % module_name, mark) + module = sys.modules[module_name] + if not hasattr(module, object_name): + raise ConstructorError("while constructing a Python object", mark, + "cannot find %r in the module %r" + % (object_name, module.__name__), mark) + return getattr(module, object_name) + + def construct_python_name(self, suffix, node): + value = self.construct_scalar(node) + if value: + raise ConstructorError("while constructing a Python name", node.start_mark, + "expected the empty value, but found %r" % value, node.start_mark) + return self.find_python_name(suffix, node.start_mark) + + def construct_python_module(self, suffix, node): + value = self.construct_scalar(node) + if value: + raise ConstructorError("while constructing a Python module", node.start_mark, + "expected the empty value, but found %r" % value, node.start_mark) + return self.find_python_module(suffix, node.start_mark) + + def make_python_instance(self, suffix, node, + args=None, kwds=None, newobj=False, unsafe=False): + if not args: + args = [] + if not kwds: + kwds = {} + cls = self.find_python_name(suffix, node.start_mark) + if not (unsafe or isinstance(cls, type)): + raise ConstructorError("while constructing a Python instance", node.start_mark, + "expected a class, but found %r" % type(cls), + node.start_mark) + if newobj and isinstance(cls, type): + return cls.__new__(cls, *args, **kwds) + else: + return cls(*args, **kwds) + + def set_python_instance_state(self, instance, state, unsafe=False): + if hasattr(instance, '__setstate__'): + instance.__setstate__(state) + else: + slotstate = {} + if isinstance(state, tuple) and len(state) == 2: + state, slotstate = state + if hasattr(instance, '__dict__'): + if not unsafe and state: + for key in state.keys(): + self.check_state_key(key) + instance.__dict__.update(state) + elif state: + slotstate.update(state) + for key, value in slotstate.items(): + if not unsafe: + self.check_state_key(key) + setattr(instance, key, value) + + def construct_python_object(self, suffix, node): + # Format: + # !!python/object:module.name { ... state ... } + instance = self.make_python_instance(suffix, node, newobj=True) + yield instance + deep = hasattr(instance, '__setstate__') + state = self.construct_mapping(node, deep=deep) + self.set_python_instance_state(instance, state) + + def construct_python_object_apply(self, suffix, node, newobj=False): + # Format: + # !!python/object/apply # (or !!python/object/new) + # args: [ ... arguments ... ] + # kwds: { ... keywords ... } + # state: ... state ... + # listitems: [ ... listitems ... ] + # dictitems: { ... dictitems ... } + # or short format: + # !!python/object/apply [ ... arguments ... ] + # The difference between !!python/object/apply and !!python/object/new + # is how an object is created, check make_python_instance for details. + if isinstance(node, SequenceNode): + args = self.construct_sequence(node, deep=True) + kwds = {} + state = {} + listitems = [] + dictitems = {} + else: + value = self.construct_mapping(node, deep=True) + args = value.get('args', []) + kwds = value.get('kwds', {}) + state = value.get('state', {}) + listitems = value.get('listitems', []) + dictitems = value.get('dictitems', {}) + instance = self.make_python_instance(suffix, node, args, kwds, newobj) + if state: + self.set_python_instance_state(instance, state) + if listitems: + instance.extend(listitems) + if dictitems: + for key in dictitems: + instance[key] = dictitems[key] + return instance + + def construct_python_object_new(self, suffix, node): + return self.construct_python_object_apply(suffix, node, newobj=True) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/none', + FullConstructor.construct_yaml_null) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/bool', + FullConstructor.construct_yaml_bool) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/str', + FullConstructor.construct_python_str) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/unicode', + FullConstructor.construct_python_unicode) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/bytes', + FullConstructor.construct_python_bytes) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/int', + FullConstructor.construct_yaml_int) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/long', + FullConstructor.construct_python_long) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/float', + FullConstructor.construct_yaml_float) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/complex', + FullConstructor.construct_python_complex) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/list', + FullConstructor.construct_yaml_seq) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/tuple', + FullConstructor.construct_python_tuple) + +FullConstructor.add_constructor( + 'tag:yaml.org,2002:python/dict', + FullConstructor.construct_yaml_map) + +FullConstructor.add_multi_constructor( + 'tag:yaml.org,2002:python/name:', + FullConstructor.construct_python_name) + +class UnsafeConstructor(FullConstructor): + + def find_python_module(self, name, mark): + return super(UnsafeConstructor, self).find_python_module(name, mark, unsafe=True) + + def find_python_name(self, name, mark): + return super(UnsafeConstructor, self).find_python_name(name, mark, unsafe=True) + + def make_python_instance(self, suffix, node, args=None, kwds=None, newobj=False): + return super(UnsafeConstructor, self).make_python_instance( + suffix, node, args, kwds, newobj, unsafe=True) + + def set_python_instance_state(self, instance, state): + return super(UnsafeConstructor, self).set_python_instance_state( + instance, state, unsafe=True) + +UnsafeConstructor.add_multi_constructor( + 'tag:yaml.org,2002:python/module:', + UnsafeConstructor.construct_python_module) + +UnsafeConstructor.add_multi_constructor( + 'tag:yaml.org,2002:python/object:', + UnsafeConstructor.construct_python_object) + +UnsafeConstructor.add_multi_constructor( + 'tag:yaml.org,2002:python/object/new:', + UnsafeConstructor.construct_python_object_new) + +UnsafeConstructor.add_multi_constructor( + 'tag:yaml.org,2002:python/object/apply:', + UnsafeConstructor.construct_python_object_apply) + +# Constructor is same as UnsafeConstructor. Need to leave this in place in case +# people have extended it directly. +class Constructor(UnsafeConstructor): + pass diff --git a/pipenv/patched/yaml3/cyaml.py b/pipenv/patched/yaml3/cyaml.py new file mode 100644 index 00000000..0c213458 --- /dev/null +++ b/pipenv/patched/yaml3/cyaml.py @@ -0,0 +1,101 @@ + +__all__ = [ + 'CBaseLoader', 'CSafeLoader', 'CFullLoader', 'CUnsafeLoader', 'CLoader', + 'CBaseDumper', 'CSafeDumper', 'CDumper' +] + +from yaml._yaml import CParser, CEmitter + +from .constructor import * + +from .serializer import * +from .representer import * + +from .resolver import * + +class CBaseLoader(CParser, BaseConstructor, BaseResolver): + + def __init__(self, stream): + CParser.__init__(self, stream) + BaseConstructor.__init__(self) + BaseResolver.__init__(self) + +class CSafeLoader(CParser, SafeConstructor, Resolver): + + def __init__(self, stream): + CParser.__init__(self, stream) + SafeConstructor.__init__(self) + Resolver.__init__(self) + +class CFullLoader(CParser, FullConstructor, Resolver): + + def __init__(self, stream): + CParser.__init__(self, stream) + FullConstructor.__init__(self) + Resolver.__init__(self) + +class CUnsafeLoader(CParser, UnsafeConstructor, Resolver): + + def __init__(self, stream): + CParser.__init__(self, stream) + UnsafeConstructor.__init__(self) + Resolver.__init__(self) + +class CLoader(CParser, Constructor, Resolver): + + def __init__(self, stream): + CParser.__init__(self, stream) + Constructor.__init__(self) + Resolver.__init__(self) + +class CBaseDumper(CEmitter, BaseRepresenter, BaseResolver): + + def __init__(self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + CEmitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, encoding=encoding, + allow_unicode=allow_unicode, line_break=line_break, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + Representer.__init__(self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys) + Resolver.__init__(self) + +class CSafeDumper(CEmitter, SafeRepresenter, Resolver): + + def __init__(self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + CEmitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, encoding=encoding, + allow_unicode=allow_unicode, line_break=line_break, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + SafeRepresenter.__init__(self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys) + Resolver.__init__(self) + +class CDumper(CEmitter, Serializer, Representer, Resolver): + + def __init__(self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + CEmitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, encoding=encoding, + allow_unicode=allow_unicode, line_break=line_break, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + Representer.__init__(self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys) + Resolver.__init__(self) + diff --git a/pipenv/patched/yaml3/dumper.py b/pipenv/patched/yaml3/dumper.py new file mode 100644 index 00000000..6aadba55 --- /dev/null +++ b/pipenv/patched/yaml3/dumper.py @@ -0,0 +1,62 @@ + +__all__ = ['BaseDumper', 'SafeDumper', 'Dumper'] + +from .emitter import * +from .serializer import * +from .representer import * +from .resolver import * + +class BaseDumper(Emitter, Serializer, BaseRepresenter, BaseResolver): + + def __init__(self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + Emitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break) + Serializer.__init__(self, encoding=encoding, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + Representer.__init__(self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys) + Resolver.__init__(self) + +class SafeDumper(Emitter, Serializer, SafeRepresenter, Resolver): + + def __init__(self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + Emitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break) + Serializer.__init__(self, encoding=encoding, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + SafeRepresenter.__init__(self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys) + Resolver.__init__(self) + +class Dumper(Emitter, Serializer, Representer, Resolver): + + def __init__(self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True): + Emitter.__init__(self, stream, canonical=canonical, + indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break) + Serializer.__init__(self, encoding=encoding, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags) + Representer.__init__(self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys) + Resolver.__init__(self) + diff --git a/pipenv/patched/yaml3/emitter.py b/pipenv/patched/yaml3/emitter.py new file mode 100644 index 00000000..a664d011 --- /dev/null +++ b/pipenv/patched/yaml3/emitter.py @@ -0,0 +1,1137 @@ + +# Emitter expects events obeying the following grammar: +# stream ::= STREAM-START document* STREAM-END +# document ::= DOCUMENT-START node DOCUMENT-END +# node ::= SCALAR | sequence | mapping +# sequence ::= SEQUENCE-START node* SEQUENCE-END +# mapping ::= MAPPING-START (node node)* MAPPING-END + +__all__ = ['Emitter', 'EmitterError'] + +from .error import YAMLError +from .events import * + +class EmitterError(YAMLError): + pass + +class ScalarAnalysis: + def __init__(self, scalar, empty, multiline, + allow_flow_plain, allow_block_plain, + allow_single_quoted, allow_double_quoted, + allow_block): + self.scalar = scalar + self.empty = empty + self.multiline = multiline + self.allow_flow_plain = allow_flow_plain + self.allow_block_plain = allow_block_plain + self.allow_single_quoted = allow_single_quoted + self.allow_double_quoted = allow_double_quoted + self.allow_block = allow_block + +class Emitter: + + DEFAULT_TAG_PREFIXES = { + '!' : '!', + 'tag:yaml.org,2002:' : '!!', + } + + def __init__(self, stream, canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None): + + # The stream should have the methods `write` and possibly `flush`. + self.stream = stream + + # Encoding can be overridden by STREAM-START. + self.encoding = None + + # Emitter is a state machine with a stack of states to handle nested + # structures. + self.states = [] + self.state = self.expect_stream_start + + # Current event and the event queue. + self.events = [] + self.event = None + + # The current indentation level and the stack of previous indents. + self.indents = [] + self.indent = None + + # Flow level. + self.flow_level = 0 + + # Contexts. + self.root_context = False + self.sequence_context = False + self.mapping_context = False + self.simple_key_context = False + + # Characteristics of the last emitted character: + # - current position. + # - is it a whitespace? + # - is it an indention character + # (indentation space, '-', '?', or ':')? + self.line = 0 + self.column = 0 + self.whitespace = True + self.indention = True + + # Whether the document requires an explicit document indicator + self.open_ended = False + + # Formatting details. + self.canonical = canonical + self.allow_unicode = allow_unicode + self.best_indent = 2 + if indent and 1 < indent < 10: + self.best_indent = indent + self.best_width = 80 + if width and width > self.best_indent*2: + self.best_width = width + self.best_line_break = '\n' + if line_break in ['\r', '\n', '\r\n']: + self.best_line_break = line_break + + # Tag prefixes. + self.tag_prefixes = None + + # Prepared anchor and tag. + self.prepared_anchor = None + self.prepared_tag = None + + # Scalar analysis and style. + self.analysis = None + self.style = None + + def dispose(self): + # Reset the state attributes (to clear self-references) + self.states = [] + self.state = None + + def emit(self, event): + self.events.append(event) + while not self.need_more_events(): + self.event = self.events.pop(0) + self.state() + self.event = None + + # In some cases, we wait for a few next events before emitting. + + def need_more_events(self): + if not self.events: + return True + event = self.events[0] + if isinstance(event, DocumentStartEvent): + return self.need_events(1) + elif isinstance(event, SequenceStartEvent): + return self.need_events(2) + elif isinstance(event, MappingStartEvent): + return self.need_events(3) + else: + return False + + def need_events(self, count): + level = 0 + for event in self.events[1:]: + if isinstance(event, (DocumentStartEvent, CollectionStartEvent)): + level += 1 + elif isinstance(event, (DocumentEndEvent, CollectionEndEvent)): + level -= 1 + elif isinstance(event, StreamEndEvent): + level = -1 + if level < 0: + return False + return (len(self.events) < count+1) + + def increase_indent(self, flow=False, indentless=False): + self.indents.append(self.indent) + if self.indent is None: + if flow: + self.indent = self.best_indent + else: + self.indent = 0 + elif not indentless: + self.indent += self.best_indent + + # States. + + # Stream handlers. + + def expect_stream_start(self): + if isinstance(self.event, StreamStartEvent): + if self.event.encoding and not hasattr(self.stream, 'encoding'): + self.encoding = self.event.encoding + self.write_stream_start() + self.state = self.expect_first_document_start + else: + raise EmitterError("expected StreamStartEvent, but got %s" + % self.event) + + def expect_nothing(self): + raise EmitterError("expected nothing, but got %s" % self.event) + + # Document handlers. + + def expect_first_document_start(self): + return self.expect_document_start(first=True) + + def expect_document_start(self, first=False): + if isinstance(self.event, DocumentStartEvent): + if (self.event.version or self.event.tags) and self.open_ended: + self.write_indicator('...', True) + self.write_indent() + if self.event.version: + version_text = self.prepare_version(self.event.version) + self.write_version_directive(version_text) + self.tag_prefixes = self.DEFAULT_TAG_PREFIXES.copy() + if self.event.tags: + handles = sorted(self.event.tags.keys()) + for handle in handles: + prefix = self.event.tags[handle] + self.tag_prefixes[prefix] = handle + handle_text = self.prepare_tag_handle(handle) + prefix_text = self.prepare_tag_prefix(prefix) + self.write_tag_directive(handle_text, prefix_text) + implicit = (first and not self.event.explicit and not self.canonical + and not self.event.version and not self.event.tags + and not self.check_empty_document()) + if not implicit: + self.write_indent() + self.write_indicator('---', True) + if self.canonical: + self.write_indent() + self.state = self.expect_document_root + elif isinstance(self.event, StreamEndEvent): + if self.open_ended: + self.write_indicator('...', True) + self.write_indent() + self.write_stream_end() + self.state = self.expect_nothing + else: + raise EmitterError("expected DocumentStartEvent, but got %s" + % self.event) + + def expect_document_end(self): + if isinstance(self.event, DocumentEndEvent): + self.write_indent() + if self.event.explicit: + self.write_indicator('...', True) + self.write_indent() + self.flush_stream() + self.state = self.expect_document_start + else: + raise EmitterError("expected DocumentEndEvent, but got %s" + % self.event) + + def expect_document_root(self): + self.states.append(self.expect_document_end) + self.expect_node(root=True) + + # Node handlers. + + def expect_node(self, root=False, sequence=False, mapping=False, + simple_key=False): + self.root_context = root + self.sequence_context = sequence + self.mapping_context = mapping + self.simple_key_context = simple_key + if isinstance(self.event, AliasEvent): + self.expect_alias() + elif isinstance(self.event, (ScalarEvent, CollectionStartEvent)): + self.process_anchor('&') + self.process_tag() + if isinstance(self.event, ScalarEvent): + self.expect_scalar() + elif isinstance(self.event, SequenceStartEvent): + if self.flow_level or self.canonical or self.event.flow_style \ + or self.check_empty_sequence(): + self.expect_flow_sequence() + else: + self.expect_block_sequence() + elif isinstance(self.event, MappingStartEvent): + if self.flow_level or self.canonical or self.event.flow_style \ + or self.check_empty_mapping(): + self.expect_flow_mapping() + else: + self.expect_block_mapping() + else: + raise EmitterError("expected NodeEvent, but got %s" % self.event) + + def expect_alias(self): + if self.event.anchor is None: + raise EmitterError("anchor is not specified for alias") + self.process_anchor('*') + self.state = self.states.pop() + + def expect_scalar(self): + self.increase_indent(flow=True) + self.process_scalar() + self.indent = self.indents.pop() + self.state = self.states.pop() + + # Flow sequence handlers. + + def expect_flow_sequence(self): + self.write_indicator('[', True, whitespace=True) + self.flow_level += 1 + self.increase_indent(flow=True) + self.state = self.expect_first_flow_sequence_item + + def expect_first_flow_sequence_item(self): + if isinstance(self.event, SequenceEndEvent): + self.indent = self.indents.pop() + self.flow_level -= 1 + self.write_indicator(']', False) + self.state = self.states.pop() + else: + if self.canonical or self.column > self.best_width: + self.write_indent() + self.states.append(self.expect_flow_sequence_item) + self.expect_node(sequence=True) + + def expect_flow_sequence_item(self): + if isinstance(self.event, SequenceEndEvent): + self.indent = self.indents.pop() + self.flow_level -= 1 + if self.canonical: + self.write_indicator(',', False) + self.write_indent() + self.write_indicator(']', False) + self.state = self.states.pop() + else: + self.write_indicator(',', False) + if self.canonical or self.column > self.best_width: + self.write_indent() + self.states.append(self.expect_flow_sequence_item) + self.expect_node(sequence=True) + + # Flow mapping handlers. + + def expect_flow_mapping(self): + self.write_indicator('{', True, whitespace=True) + self.flow_level += 1 + self.increase_indent(flow=True) + self.state = self.expect_first_flow_mapping_key + + def expect_first_flow_mapping_key(self): + if isinstance(self.event, MappingEndEvent): + self.indent = self.indents.pop() + self.flow_level -= 1 + self.write_indicator('}', False) + self.state = self.states.pop() + else: + if self.canonical or self.column > self.best_width: + self.write_indent() + if not self.canonical and self.check_simple_key(): + self.states.append(self.expect_flow_mapping_simple_value) + self.expect_node(mapping=True, simple_key=True) + else: + self.write_indicator('?', True) + self.states.append(self.expect_flow_mapping_value) + self.expect_node(mapping=True) + + def expect_flow_mapping_key(self): + if isinstance(self.event, MappingEndEvent): + self.indent = self.indents.pop() + self.flow_level -= 1 + if self.canonical: + self.write_indicator(',', False) + self.write_indent() + self.write_indicator('}', False) + self.state = self.states.pop() + else: + self.write_indicator(',', False) + if self.canonical or self.column > self.best_width: + self.write_indent() + if not self.canonical and self.check_simple_key(): + self.states.append(self.expect_flow_mapping_simple_value) + self.expect_node(mapping=True, simple_key=True) + else: + self.write_indicator('?', True) + self.states.append(self.expect_flow_mapping_value) + self.expect_node(mapping=True) + + def expect_flow_mapping_simple_value(self): + self.write_indicator(':', False) + self.states.append(self.expect_flow_mapping_key) + self.expect_node(mapping=True) + + def expect_flow_mapping_value(self): + if self.canonical or self.column > self.best_width: + self.write_indent() + self.write_indicator(':', True) + self.states.append(self.expect_flow_mapping_key) + self.expect_node(mapping=True) + + # Block sequence handlers. + + def expect_block_sequence(self): + indentless = (self.mapping_context and not self.indention) + self.increase_indent(flow=False, indentless=indentless) + self.state = self.expect_first_block_sequence_item + + def expect_first_block_sequence_item(self): + return self.expect_block_sequence_item(first=True) + + def expect_block_sequence_item(self, first=False): + if not first and isinstance(self.event, SequenceEndEvent): + self.indent = self.indents.pop() + self.state = self.states.pop() + else: + self.write_indent() + self.write_indicator('-', True, indention=True) + self.states.append(self.expect_block_sequence_item) + self.expect_node(sequence=True) + + # Block mapping handlers. + + def expect_block_mapping(self): + self.increase_indent(flow=False) + self.state = self.expect_first_block_mapping_key + + def expect_first_block_mapping_key(self): + return self.expect_block_mapping_key(first=True) + + def expect_block_mapping_key(self, first=False): + if not first and isinstance(self.event, MappingEndEvent): + self.indent = self.indents.pop() + self.state = self.states.pop() + else: + self.write_indent() + if self.check_simple_key(): + self.states.append(self.expect_block_mapping_simple_value) + self.expect_node(mapping=True, simple_key=True) + else: + self.write_indicator('?', True, indention=True) + self.states.append(self.expect_block_mapping_value) + self.expect_node(mapping=True) + + def expect_block_mapping_simple_value(self): + self.write_indicator(':', False) + self.states.append(self.expect_block_mapping_key) + self.expect_node(mapping=True) + + def expect_block_mapping_value(self): + self.write_indent() + self.write_indicator(':', True, indention=True) + self.states.append(self.expect_block_mapping_key) + self.expect_node(mapping=True) + + # Checkers. + + def check_empty_sequence(self): + return (isinstance(self.event, SequenceStartEvent) and self.events + and isinstance(self.events[0], SequenceEndEvent)) + + def check_empty_mapping(self): + return (isinstance(self.event, MappingStartEvent) and self.events + and isinstance(self.events[0], MappingEndEvent)) + + def check_empty_document(self): + if not isinstance(self.event, DocumentStartEvent) or not self.events: + return False + event = self.events[0] + return (isinstance(event, ScalarEvent) and event.anchor is None + and event.tag is None and event.implicit and event.value == '') + + def check_simple_key(self): + length = 0 + if isinstance(self.event, NodeEvent) and self.event.anchor is not None: + if self.prepared_anchor is None: + self.prepared_anchor = self.prepare_anchor(self.event.anchor) + length += len(self.prepared_anchor) + if isinstance(self.event, (ScalarEvent, CollectionStartEvent)) \ + and self.event.tag is not None: + if self.prepared_tag is None: + self.prepared_tag = self.prepare_tag(self.event.tag) + length += len(self.prepared_tag) + if isinstance(self.event, ScalarEvent): + if self.analysis is None: + self.analysis = self.analyze_scalar(self.event.value) + length += len(self.analysis.scalar) + return (length < 128 and (isinstance(self.event, AliasEvent) + or (isinstance(self.event, ScalarEvent) + and not self.analysis.empty and not self.analysis.multiline) + or self.check_empty_sequence() or self.check_empty_mapping())) + + # Anchor, Tag, and Scalar processors. + + def process_anchor(self, indicator): + if self.event.anchor is None: + self.prepared_anchor = None + return + if self.prepared_anchor is None: + self.prepared_anchor = self.prepare_anchor(self.event.anchor) + if self.prepared_anchor: + self.write_indicator(indicator+self.prepared_anchor, True) + self.prepared_anchor = None + + def process_tag(self): + tag = self.event.tag + if isinstance(self.event, ScalarEvent): + if self.style is None: + self.style = self.choose_scalar_style() + if ((not self.canonical or tag is None) and + ((self.style == '' and self.event.implicit[0]) + or (self.style != '' and self.event.implicit[1]))): + self.prepared_tag = None + return + if self.event.implicit[0] and tag is None: + tag = '!' + self.prepared_tag = None + else: + if (not self.canonical or tag is None) and self.event.implicit: + self.prepared_tag = None + return + if tag is None: + raise EmitterError("tag is not specified") + if self.prepared_tag is None: + self.prepared_tag = self.prepare_tag(tag) + if self.prepared_tag: + self.write_indicator(self.prepared_tag, True) + self.prepared_tag = None + + def choose_scalar_style(self): + if self.analysis is None: + self.analysis = self.analyze_scalar(self.event.value) + if self.event.style == '"' or self.canonical: + return '"' + if not self.event.style and self.event.implicit[0]: + if (not (self.simple_key_context and + (self.analysis.empty or self.analysis.multiline)) + and (self.flow_level and self.analysis.allow_flow_plain + or (not self.flow_level and self.analysis.allow_block_plain))): + return '' + if self.event.style and self.event.style in '|>': + if (not self.flow_level and not self.simple_key_context + and self.analysis.allow_block): + return self.event.style + if not self.event.style or self.event.style == '\'': + if (self.analysis.allow_single_quoted and + not (self.simple_key_context and self.analysis.multiline)): + return '\'' + return '"' + + def process_scalar(self): + if self.analysis is None: + self.analysis = self.analyze_scalar(self.event.value) + if self.style is None: + self.style = self.choose_scalar_style() + split = (not self.simple_key_context) + #if self.analysis.multiline and split \ + # and (not self.style or self.style in '\'\"'): + # self.write_indent() + if self.style == '"': + self.write_double_quoted(self.analysis.scalar, split) + elif self.style == '\'': + self.write_single_quoted(self.analysis.scalar, split) + elif self.style == '>': + self.write_folded(self.analysis.scalar) + elif self.style == '|': + self.write_literal(self.analysis.scalar) + else: + self.write_plain(self.analysis.scalar, split) + self.analysis = None + self.style = None + + # Analyzers. + + def prepare_version(self, version): + major, minor = version + if major != 1: + raise EmitterError("unsupported YAML version: %d.%d" % (major, minor)) + return '%d.%d' % (major, minor) + + def prepare_tag_handle(self, handle): + if not handle: + raise EmitterError("tag handle must not be empty") + if handle[0] != '!' or handle[-1] != '!': + raise EmitterError("tag handle must start and end with '!': %r" % handle) + for ch in handle[1:-1]: + if not ('0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-_'): + raise EmitterError("invalid character %r in the tag handle: %r" + % (ch, handle)) + return handle + + def prepare_tag_prefix(self, prefix): + if not prefix: + raise EmitterError("tag prefix must not be empty") + chunks = [] + start = end = 0 + if prefix[0] == '!': + end = 1 + while end < len(prefix): + ch = prefix[end] + if '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-;/?!:@&=+$,_.~*\'()[]': + end += 1 + else: + if start < end: + chunks.append(prefix[start:end]) + start = end = end+1 + data = ch.encode('utf-8') + for ch in data: + chunks.append('%%%02X' % ord(ch)) + if start < end: + chunks.append(prefix[start:end]) + return ''.join(chunks) + + def prepare_tag(self, tag): + if not tag: + raise EmitterError("tag must not be empty") + if tag == '!': + return tag + handle = None + suffix = tag + prefixes = sorted(self.tag_prefixes.keys()) + for prefix in prefixes: + if tag.startswith(prefix) \ + and (prefix == '!' or len(prefix) < len(tag)): + handle = self.tag_prefixes[prefix] + suffix = tag[len(prefix):] + chunks = [] + start = end = 0 + while end < len(suffix): + ch = suffix[end] + if '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-;/?:@&=+$,_.~*\'()[]' \ + or (ch == '!' and handle != '!'): + end += 1 + else: + if start < end: + chunks.append(suffix[start:end]) + start = end = end+1 + data = ch.encode('utf-8') + for ch in data: + chunks.append('%%%02X' % ch) + if start < end: + chunks.append(suffix[start:end]) + suffix_text = ''.join(chunks) + if handle: + return '%s%s' % (handle, suffix_text) + else: + return '!<%s>' % suffix_text + + def prepare_anchor(self, anchor): + if not anchor: + raise EmitterError("anchor must not be empty") + for ch in anchor: + if not ('0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-_'): + raise EmitterError("invalid character %r in the anchor: %r" + % (ch, anchor)) + return anchor + + def analyze_scalar(self, scalar): + + # Empty scalar is a special case. + if not scalar: + return ScalarAnalysis(scalar=scalar, empty=True, multiline=False, + allow_flow_plain=False, allow_block_plain=True, + allow_single_quoted=True, allow_double_quoted=True, + allow_block=False) + + # Indicators and special characters. + block_indicators = False + flow_indicators = False + line_breaks = False + special_characters = False + + # Important whitespace combinations. + leading_space = False + leading_break = False + trailing_space = False + trailing_break = False + break_space = False + space_break = False + + # Check document indicators. + if scalar.startswith('---') or scalar.startswith('...'): + block_indicators = True + flow_indicators = True + + # First character or preceded by a whitespace. + preceded_by_whitespace = True + + # Last character or followed by a whitespace. + followed_by_whitespace = (len(scalar) == 1 or + scalar[1] in '\0 \t\r\n\x85\u2028\u2029') + + # The previous character is a space. + previous_space = False + + # The previous character is a break. + previous_break = False + + index = 0 + while index < len(scalar): + ch = scalar[index] + + # Check for indicators. + if index == 0: + # Leading indicators are special characters. + if ch in '#,[]{}&*!|>\'\"%@`': + flow_indicators = True + block_indicators = True + if ch in '?:': + flow_indicators = True + if followed_by_whitespace: + block_indicators = True + if ch == '-' and followed_by_whitespace: + flow_indicators = True + block_indicators = True + else: + # Some indicators cannot appear within a scalar as well. + if ch in ',?[]{}': + flow_indicators = True + if ch == ':': + flow_indicators = True + if followed_by_whitespace: + block_indicators = True + if ch == '#' and preceded_by_whitespace: + flow_indicators = True + block_indicators = True + + # Check for line breaks, special, and unicode characters. + if ch in '\n\x85\u2028\u2029': + line_breaks = True + if not (ch == '\n' or '\x20' <= ch <= '\x7E'): + if (ch == '\x85' or '\xA0' <= ch <= '\uD7FF' + or '\uE000' <= ch <= '\uFFFD' + or '\U00010000' <= ch < '\U0010ffff') and ch != '\uFEFF': + unicode_characters = True + if not self.allow_unicode: + special_characters = True + else: + special_characters = True + + # Detect important whitespace combinations. + if ch == ' ': + if index == 0: + leading_space = True + if index == len(scalar)-1: + trailing_space = True + if previous_break: + break_space = True + previous_space = True + previous_break = False + elif ch in '\n\x85\u2028\u2029': + if index == 0: + leading_break = True + if index == len(scalar)-1: + trailing_break = True + if previous_space: + space_break = True + previous_space = False + previous_break = True + else: + previous_space = False + previous_break = False + + # Prepare for the next character. + index += 1 + preceded_by_whitespace = (ch in '\0 \t\r\n\x85\u2028\u2029') + followed_by_whitespace = (index+1 >= len(scalar) or + scalar[index+1] in '\0 \t\r\n\x85\u2028\u2029') + + # Let's decide what styles are allowed. + allow_flow_plain = True + allow_block_plain = True + allow_single_quoted = True + allow_double_quoted = True + allow_block = True + + # Leading and trailing whitespaces are bad for plain scalars. + if (leading_space or leading_break + or trailing_space or trailing_break): + allow_flow_plain = allow_block_plain = False + + # We do not permit trailing spaces for block scalars. + if trailing_space: + allow_block = False + + # Spaces at the beginning of a new line are only acceptable for block + # scalars. + if break_space: + allow_flow_plain = allow_block_plain = allow_single_quoted = False + + # Spaces followed by breaks, as well as special character are only + # allowed for double quoted scalars. + if space_break or special_characters: + allow_flow_plain = allow_block_plain = \ + allow_single_quoted = allow_block = False + + # Although the plain scalar writer supports breaks, we never emit + # multiline plain scalars. + if line_breaks: + allow_flow_plain = allow_block_plain = False + + # Flow indicators are forbidden for flow plain scalars. + if flow_indicators: + allow_flow_plain = False + + # Block indicators are forbidden for block plain scalars. + if block_indicators: + allow_block_plain = False + + return ScalarAnalysis(scalar=scalar, + empty=False, multiline=line_breaks, + allow_flow_plain=allow_flow_plain, + allow_block_plain=allow_block_plain, + allow_single_quoted=allow_single_quoted, + allow_double_quoted=allow_double_quoted, + allow_block=allow_block) + + # Writers. + + def flush_stream(self): + if hasattr(self.stream, 'flush'): + self.stream.flush() + + def write_stream_start(self): + # Write BOM if needed. + if self.encoding and self.encoding.startswith('utf-16'): + self.stream.write('\uFEFF'.encode(self.encoding)) + + def write_stream_end(self): + self.flush_stream() + + def write_indicator(self, indicator, need_whitespace, + whitespace=False, indention=False): + if self.whitespace or not need_whitespace: + data = indicator + else: + data = ' '+indicator + self.whitespace = whitespace + self.indention = self.indention and indention + self.column += len(data) + self.open_ended = False + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + + def write_indent(self): + indent = self.indent or 0 + if not self.indention or self.column > indent \ + or (self.column == indent and not self.whitespace): + self.write_line_break() + if self.column < indent: + self.whitespace = True + data = ' '*(indent-self.column) + self.column = indent + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + + def write_line_break(self, data=None): + if data is None: + data = self.best_line_break + self.whitespace = True + self.indention = True + self.line += 1 + self.column = 0 + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + + def write_version_directive(self, version_text): + data = '%%YAML %s' % version_text + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + self.write_line_break() + + def write_tag_directive(self, handle_text, prefix_text): + data = '%%TAG %s %s' % (handle_text, prefix_text) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + self.write_line_break() + + # Scalar streams. + + def write_single_quoted(self, text, split=True): + self.write_indicator('\'', True) + spaces = False + breaks = False + start = end = 0 + while end <= len(text): + ch = None + if end < len(text): + ch = text[end] + if spaces: + if ch is None or ch != ' ': + if start+1 == end and self.column > self.best_width and split \ + and start != 0 and end != len(text): + self.write_indent() + else: + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + elif breaks: + if ch is None or ch not in '\n\x85\u2028\u2029': + if text[start] == '\n': + self.write_line_break() + for br in text[start:end]: + if br == '\n': + self.write_line_break() + else: + self.write_line_break(br) + self.write_indent() + start = end + else: + if ch is None or ch in ' \n\x85\u2028\u2029' or ch == '\'': + if start < end: + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + if ch == '\'': + data = '\'\'' + self.column += 2 + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + 1 + if ch is not None: + spaces = (ch == ' ') + breaks = (ch in '\n\x85\u2028\u2029') + end += 1 + self.write_indicator('\'', False) + + ESCAPE_REPLACEMENTS = { + '\0': '0', + '\x07': 'a', + '\x08': 'b', + '\x09': 't', + '\x0A': 'n', + '\x0B': 'v', + '\x0C': 'f', + '\x0D': 'r', + '\x1B': 'e', + '\"': '\"', + '\\': '\\', + '\x85': 'N', + '\xA0': '_', + '\u2028': 'L', + '\u2029': 'P', + } + + def write_double_quoted(self, text, split=True): + self.write_indicator('"', True) + start = end = 0 + while end <= len(text): + ch = None + if end < len(text): + ch = text[end] + if ch is None or ch in '"\\\x85\u2028\u2029\uFEFF' \ + or not ('\x20' <= ch <= '\x7E' + or (self.allow_unicode + and ('\xA0' <= ch <= '\uD7FF' + or '\uE000' <= ch <= '\uFFFD'))): + if start < end: + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + if ch is not None: + if ch in self.ESCAPE_REPLACEMENTS: + data = '\\'+self.ESCAPE_REPLACEMENTS[ch] + elif ch <= '\xFF': + data = '\\x%02X' % ord(ch) + elif ch <= '\uFFFF': + data = '\\u%04X' % ord(ch) + else: + data = '\\U%08X' % ord(ch) + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end+1 + if 0 < end < len(text)-1 and (ch == ' ' or start >= end) \ + and self.column+(end-start) > self.best_width and split: + data = text[start:end]+'\\' + if start < end: + start = end + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + self.write_indent() + self.whitespace = False + self.indention = False + if text[start] == ' ': + data = '\\' + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + end += 1 + self.write_indicator('"', False) + + def determine_block_hints(self, text): + hints = '' + if text: + if text[0] in ' \n\x85\u2028\u2029': + hints += str(self.best_indent) + if text[-1] not in '\n\x85\u2028\u2029': + hints += '-' + elif len(text) == 1 or text[-2] in '\n\x85\u2028\u2029': + hints += '+' + return hints + + def write_folded(self, text): + hints = self.determine_block_hints(text) + self.write_indicator('>'+hints, True) + if hints[-1:] == '+': + self.open_ended = True + self.write_line_break() + leading_space = True + spaces = False + breaks = True + start = end = 0 + while end <= len(text): + ch = None + if end < len(text): + ch = text[end] + if breaks: + if ch is None or ch not in '\n\x85\u2028\u2029': + if not leading_space and ch is not None and ch != ' ' \ + and text[start] == '\n': + self.write_line_break() + leading_space = (ch == ' ') + for br in text[start:end]: + if br == '\n': + self.write_line_break() + else: + self.write_line_break(br) + if ch is not None: + self.write_indent() + start = end + elif spaces: + if ch != ' ': + if start+1 == end and self.column > self.best_width: + self.write_indent() + else: + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + else: + if ch is None or ch in ' \n\x85\u2028\u2029': + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + if ch is None: + self.write_line_break() + start = end + if ch is not None: + breaks = (ch in '\n\x85\u2028\u2029') + spaces = (ch == ' ') + end += 1 + + def write_literal(self, text): + hints = self.determine_block_hints(text) + self.write_indicator('|'+hints, True) + if hints[-1:] == '+': + self.open_ended = True + self.write_line_break() + breaks = True + start = end = 0 + while end <= len(text): + ch = None + if end < len(text): + ch = text[end] + if breaks: + if ch is None or ch not in '\n\x85\u2028\u2029': + for br in text[start:end]: + if br == '\n': + self.write_line_break() + else: + self.write_line_break(br) + if ch is not None: + self.write_indent() + start = end + else: + if ch is None or ch in '\n\x85\u2028\u2029': + data = text[start:end] + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + if ch is None: + self.write_line_break() + start = end + if ch is not None: + breaks = (ch in '\n\x85\u2028\u2029') + end += 1 + + def write_plain(self, text, split=True): + if self.root_context: + self.open_ended = True + if not text: + return + if not self.whitespace: + data = ' ' + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + self.whitespace = False + self.indention = False + spaces = False + breaks = False + start = end = 0 + while end <= len(text): + ch = None + if end < len(text): + ch = text[end] + if spaces: + if ch != ' ': + if start+1 == end and self.column > self.best_width and split: + self.write_indent() + self.whitespace = False + self.indention = False + else: + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + elif breaks: + if ch not in '\n\x85\u2028\u2029': + if text[start] == '\n': + self.write_line_break() + for br in text[start:end]: + if br == '\n': + self.write_line_break() + else: + self.write_line_break(br) + self.write_indent() + self.whitespace = False + self.indention = False + start = end + else: + if ch is None or ch in ' \n\x85\u2028\u2029': + data = text[start:end] + self.column += len(data) + if self.encoding: + data = data.encode(self.encoding) + self.stream.write(data) + start = end + if ch is not None: + spaces = (ch == ' ') + breaks = (ch in '\n\x85\u2028\u2029') + end += 1 diff --git a/pipenv/patched/yaml3/error.py b/pipenv/patched/yaml3/error.py new file mode 100644 index 00000000..b796b4dc --- /dev/null +++ b/pipenv/patched/yaml3/error.py @@ -0,0 +1,75 @@ + +__all__ = ['Mark', 'YAMLError', 'MarkedYAMLError'] + +class Mark: + + def __init__(self, name, index, line, column, buffer, pointer): + self.name = name + self.index = index + self.line = line + self.column = column + self.buffer = buffer + self.pointer = pointer + + def get_snippet(self, indent=4, max_length=75): + if self.buffer is None: + return None + head = '' + start = self.pointer + while start > 0 and self.buffer[start-1] not in '\0\r\n\x85\u2028\u2029': + start -= 1 + if self.pointer-start > max_length/2-1: + head = ' ... ' + start += 5 + break + tail = '' + end = self.pointer + while end < len(self.buffer) and self.buffer[end] not in '\0\r\n\x85\u2028\u2029': + end += 1 + if end-self.pointer > max_length/2-1: + tail = ' ... ' + end -= 5 + break + snippet = self.buffer[start:end] + return ' '*indent + head + snippet + tail + '\n' \ + + ' '*(indent+self.pointer-start+len(head)) + '^' + + def __str__(self): + snippet = self.get_snippet() + where = " in \"%s\", line %d, column %d" \ + % (self.name, self.line+1, self.column+1) + if snippet is not None: + where += ":\n"+snippet + return where + +class YAMLError(Exception): + pass + +class MarkedYAMLError(YAMLError): + + def __init__(self, context=None, context_mark=None, + problem=None, problem_mark=None, note=None): + self.context = context + self.context_mark = context_mark + self.problem = problem + self.problem_mark = problem_mark + self.note = note + + def __str__(self): + lines = [] + if self.context is not None: + lines.append(self.context) + if self.context_mark is not None \ + and (self.problem is None or self.problem_mark is None + or self.context_mark.name != self.problem_mark.name + or self.context_mark.line != self.problem_mark.line + or self.context_mark.column != self.problem_mark.column): + lines.append(str(self.context_mark)) + if self.problem is not None: + lines.append(self.problem) + if self.problem_mark is not None: + lines.append(str(self.problem_mark)) + if self.note is not None: + lines.append(self.note) + return '\n'.join(lines) + diff --git a/pipenv/patched/yaml3/events.py b/pipenv/patched/yaml3/events.py new file mode 100644 index 00000000..f79ad389 --- /dev/null +++ b/pipenv/patched/yaml3/events.py @@ -0,0 +1,86 @@ + +# Abstract classes. + +class Event(object): + def __init__(self, start_mark=None, end_mark=None): + self.start_mark = start_mark + self.end_mark = end_mark + def __repr__(self): + attributes = [key for key in ['anchor', 'tag', 'implicit', 'value'] + if hasattr(self, key)] + arguments = ', '.join(['%s=%r' % (key, getattr(self, key)) + for key in attributes]) + return '%s(%s)' % (self.__class__.__name__, arguments) + +class NodeEvent(Event): + def __init__(self, anchor, start_mark=None, end_mark=None): + self.anchor = anchor + self.start_mark = start_mark + self.end_mark = end_mark + +class CollectionStartEvent(NodeEvent): + def __init__(self, anchor, tag, implicit, start_mark=None, end_mark=None, + flow_style=None): + self.anchor = anchor + self.tag = tag + self.implicit = implicit + self.start_mark = start_mark + self.end_mark = end_mark + self.flow_style = flow_style + +class CollectionEndEvent(Event): + pass + +# Implementations. + +class StreamStartEvent(Event): + def __init__(self, start_mark=None, end_mark=None, encoding=None): + self.start_mark = start_mark + self.end_mark = end_mark + self.encoding = encoding + +class StreamEndEvent(Event): + pass + +class DocumentStartEvent(Event): + def __init__(self, start_mark=None, end_mark=None, + explicit=None, version=None, tags=None): + self.start_mark = start_mark + self.end_mark = end_mark + self.explicit = explicit + self.version = version + self.tags = tags + +class DocumentEndEvent(Event): + def __init__(self, start_mark=None, end_mark=None, + explicit=None): + self.start_mark = start_mark + self.end_mark = end_mark + self.explicit = explicit + +class AliasEvent(NodeEvent): + pass + +class ScalarEvent(NodeEvent): + def __init__(self, anchor, tag, implicit, value, + start_mark=None, end_mark=None, style=None): + self.anchor = anchor + self.tag = tag + self.implicit = implicit + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + self.style = style + +class SequenceStartEvent(CollectionStartEvent): + pass + +class SequenceEndEvent(CollectionEndEvent): + pass + +class MappingStartEvent(CollectionStartEvent): + pass + +class MappingEndEvent(CollectionEndEvent): + pass + diff --git a/pipenv/patched/yaml3/loader.py b/pipenv/patched/yaml3/loader.py new file mode 100644 index 00000000..e90c1122 --- /dev/null +++ b/pipenv/patched/yaml3/loader.py @@ -0,0 +1,63 @@ + +__all__ = ['BaseLoader', 'FullLoader', 'SafeLoader', 'Loader', 'UnsafeLoader'] + +from .reader import * +from .scanner import * +from .parser import * +from .composer import * +from .constructor import * +from .resolver import * + +class BaseLoader(Reader, Scanner, Parser, Composer, BaseConstructor, BaseResolver): + + def __init__(self, stream): + Reader.__init__(self, stream) + Scanner.__init__(self) + Parser.__init__(self) + Composer.__init__(self) + BaseConstructor.__init__(self) + BaseResolver.__init__(self) + +class FullLoader(Reader, Scanner, Parser, Composer, FullConstructor, Resolver): + + def __init__(self, stream): + Reader.__init__(self, stream) + Scanner.__init__(self) + Parser.__init__(self) + Composer.__init__(self) + FullConstructor.__init__(self) + Resolver.__init__(self) + +class SafeLoader(Reader, Scanner, Parser, Composer, SafeConstructor, Resolver): + + def __init__(self, stream): + Reader.__init__(self, stream) + Scanner.__init__(self) + Parser.__init__(self) + Composer.__init__(self) + SafeConstructor.__init__(self) + Resolver.__init__(self) + +class Loader(Reader, Scanner, Parser, Composer, Constructor, Resolver): + + def __init__(self, stream): + Reader.__init__(self, stream) + Scanner.__init__(self) + Parser.__init__(self) + Composer.__init__(self) + Constructor.__init__(self) + Resolver.__init__(self) + +# UnsafeLoader is the same as Loader (which is and was always unsafe on +# untrusted input). Use of either Loader or UnsafeLoader should be rare, since +# FullLoad should be able to load almost all YAML safely. Loader is left intact +# to ensure backwards compatibility. +class UnsafeLoader(Reader, Scanner, Parser, Composer, Constructor, Resolver): + + def __init__(self, stream): + Reader.__init__(self, stream) + Scanner.__init__(self) + Parser.__init__(self) + Composer.__init__(self) + Constructor.__init__(self) + Resolver.__init__(self) diff --git a/pipenv/patched/yaml3/nodes.py b/pipenv/patched/yaml3/nodes.py new file mode 100644 index 00000000..c4f070c4 --- /dev/null +++ b/pipenv/patched/yaml3/nodes.py @@ -0,0 +1,49 @@ + +class Node(object): + def __init__(self, tag, value, start_mark, end_mark): + self.tag = tag + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + def __repr__(self): + value = self.value + #if isinstance(value, list): + # if len(value) == 0: + # value = '<empty>' + # elif len(value) == 1: + # value = '<1 item>' + # else: + # value = '<%d items>' % len(value) + #else: + # if len(value) > 75: + # value = repr(value[:70]+u' ... ') + # else: + # value = repr(value) + value = repr(value) + return '%s(tag=%r, value=%s)' % (self.__class__.__name__, self.tag, value) + +class ScalarNode(Node): + id = 'scalar' + def __init__(self, tag, value, + start_mark=None, end_mark=None, style=None): + self.tag = tag + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + self.style = style + +class CollectionNode(Node): + def __init__(self, tag, value, + start_mark=None, end_mark=None, flow_style=None): + self.tag = tag + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + self.flow_style = flow_style + +class SequenceNode(CollectionNode): + id = 'sequence' + +class MappingNode(CollectionNode): + id = 'mapping' + diff --git a/pipenv/patched/yaml3/parser.py b/pipenv/patched/yaml3/parser.py new file mode 100644 index 00000000..13a5995d --- /dev/null +++ b/pipenv/patched/yaml3/parser.py @@ -0,0 +1,589 @@ + +# The following YAML grammar is LL(1) and is parsed by a recursive descent +# parser. +# +# stream ::= STREAM-START implicit_document? explicit_document* STREAM-END +# implicit_document ::= block_node DOCUMENT-END* +# explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* +# block_node_or_indentless_sequence ::= +# ALIAS +# | properties (block_content | indentless_block_sequence)? +# | block_content +# | indentless_block_sequence +# block_node ::= ALIAS +# | properties block_content? +# | block_content +# flow_node ::= ALIAS +# | properties flow_content? +# | flow_content +# properties ::= TAG ANCHOR? | ANCHOR TAG? +# block_content ::= block_collection | flow_collection | SCALAR +# flow_content ::= flow_collection | SCALAR +# block_collection ::= block_sequence | block_mapping +# flow_collection ::= flow_sequence | flow_mapping +# block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END +# indentless_sequence ::= (BLOCK-ENTRY block_node?)+ +# block_mapping ::= BLOCK-MAPPING_START +# ((KEY block_node_or_indentless_sequence?)? +# (VALUE block_node_or_indentless_sequence?)?)* +# BLOCK-END +# flow_sequence ::= FLOW-SEQUENCE-START +# (flow_sequence_entry FLOW-ENTRY)* +# flow_sequence_entry? +# FLOW-SEQUENCE-END +# flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +# flow_mapping ::= FLOW-MAPPING-START +# (flow_mapping_entry FLOW-ENTRY)* +# flow_mapping_entry? +# FLOW-MAPPING-END +# flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? +# +# FIRST sets: +# +# stream: { STREAM-START } +# explicit_document: { DIRECTIVE DOCUMENT-START } +# implicit_document: FIRST(block_node) +# block_node: { ALIAS TAG ANCHOR SCALAR BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START } +# flow_node: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START } +# block_content: { BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START SCALAR } +# flow_content: { FLOW-SEQUENCE-START FLOW-MAPPING-START SCALAR } +# block_collection: { BLOCK-SEQUENCE-START BLOCK-MAPPING-START } +# flow_collection: { FLOW-SEQUENCE-START FLOW-MAPPING-START } +# block_sequence: { BLOCK-SEQUENCE-START } +# block_mapping: { BLOCK-MAPPING-START } +# block_node_or_indentless_sequence: { ALIAS ANCHOR TAG SCALAR BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START BLOCK-ENTRY } +# indentless_sequence: { ENTRY } +# flow_collection: { FLOW-SEQUENCE-START FLOW-MAPPING-START } +# flow_sequence: { FLOW-SEQUENCE-START } +# flow_mapping: { FLOW-MAPPING-START } +# flow_sequence_entry: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START KEY } +# flow_mapping_entry: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START KEY } + +__all__ = ['Parser', 'ParserError'] + +from .error import MarkedYAMLError +from .tokens import * +from .events import * +from .scanner import * + +class ParserError(MarkedYAMLError): + pass + +class Parser: + # Since writing a recursive-descendant parser is a straightforward task, we + # do not give many comments here. + + DEFAULT_TAGS = { + '!': '!', + '!!': 'tag:yaml.org,2002:', + } + + def __init__(self): + self.current_event = None + self.yaml_version = None + self.tag_handles = {} + self.states = [] + self.marks = [] + self.state = self.parse_stream_start + + def dispose(self): + # Reset the state attributes (to clear self-references) + self.states = [] + self.state = None + + def check_event(self, *choices): + # Check the type of the next event. + if self.current_event is None: + if self.state: + self.current_event = self.state() + if self.current_event is not None: + if not choices: + return True + for choice in choices: + if isinstance(self.current_event, choice): + return True + return False + + def peek_event(self): + # Get the next event. + if self.current_event is None: + if self.state: + self.current_event = self.state() + return self.current_event + + def get_event(self): + # Get the next event and proceed further. + if self.current_event is None: + if self.state: + self.current_event = self.state() + value = self.current_event + self.current_event = None + return value + + # stream ::= STREAM-START implicit_document? explicit_document* STREAM-END + # implicit_document ::= block_node DOCUMENT-END* + # explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* + + def parse_stream_start(self): + + # Parse the stream start. + token = self.get_token() + event = StreamStartEvent(token.start_mark, token.end_mark, + encoding=token.encoding) + + # Prepare the next state. + self.state = self.parse_implicit_document_start + + return event + + def parse_implicit_document_start(self): + + # Parse an implicit document. + if not self.check_token(DirectiveToken, DocumentStartToken, + StreamEndToken): + self.tag_handles = self.DEFAULT_TAGS + token = self.peek_token() + start_mark = end_mark = token.start_mark + event = DocumentStartEvent(start_mark, end_mark, + explicit=False) + + # Prepare the next state. + self.states.append(self.parse_document_end) + self.state = self.parse_block_node + + return event + + else: + return self.parse_document_start() + + def parse_document_start(self): + + # Parse any extra document end indicators. + while self.check_token(DocumentEndToken): + self.get_token() + + # Parse an explicit document. + if not self.check_token(StreamEndToken): + token = self.peek_token() + start_mark = token.start_mark + version, tags = self.process_directives() + if not self.check_token(DocumentStartToken): + raise ParserError(None, None, + "expected '<document start>', but found %r" + % self.peek_token().id, + self.peek_token().start_mark) + token = self.get_token() + end_mark = token.end_mark + event = DocumentStartEvent(start_mark, end_mark, + explicit=True, version=version, tags=tags) + self.states.append(self.parse_document_end) + self.state = self.parse_document_content + else: + # Parse the end of the stream. + token = self.get_token() + event = StreamEndEvent(token.start_mark, token.end_mark) + assert not self.states + assert not self.marks + self.state = None + return event + + def parse_document_end(self): + + # Parse the document end. + token = self.peek_token() + start_mark = end_mark = token.start_mark + explicit = False + if self.check_token(DocumentEndToken): + token = self.get_token() + end_mark = token.end_mark + explicit = True + event = DocumentEndEvent(start_mark, end_mark, + explicit=explicit) + + # Prepare the next state. + self.state = self.parse_document_start + + return event + + def parse_document_content(self): + if self.check_token(DirectiveToken, + DocumentStartToken, DocumentEndToken, StreamEndToken): + event = self.process_empty_scalar(self.peek_token().start_mark) + self.state = self.states.pop() + return event + else: + return self.parse_block_node() + + def process_directives(self): + self.yaml_version = None + self.tag_handles = {} + while self.check_token(DirectiveToken): + token = self.get_token() + if token.name == 'YAML': + if self.yaml_version is not None: + raise ParserError(None, None, + "found duplicate YAML directive", token.start_mark) + major, minor = token.value + if major != 1: + raise ParserError(None, None, + "found incompatible YAML document (version 1.* is required)", + token.start_mark) + self.yaml_version = token.value + elif token.name == 'TAG': + handle, prefix = token.value + if handle in self.tag_handles: + raise ParserError(None, None, + "duplicate tag handle %r" % handle, + token.start_mark) + self.tag_handles[handle] = prefix + if self.tag_handles: + value = self.yaml_version, self.tag_handles.copy() + else: + value = self.yaml_version, None + for key in self.DEFAULT_TAGS: + if key not in self.tag_handles: + self.tag_handles[key] = self.DEFAULT_TAGS[key] + return value + + # block_node_or_indentless_sequence ::= ALIAS + # | properties (block_content | indentless_block_sequence)? + # | block_content + # | indentless_block_sequence + # block_node ::= ALIAS + # | properties block_content? + # | block_content + # flow_node ::= ALIAS + # | properties flow_content? + # | flow_content + # properties ::= TAG ANCHOR? | ANCHOR TAG? + # block_content ::= block_collection | flow_collection | SCALAR + # flow_content ::= flow_collection | SCALAR + # block_collection ::= block_sequence | block_mapping + # flow_collection ::= flow_sequence | flow_mapping + + def parse_block_node(self): + return self.parse_node(block=True) + + def parse_flow_node(self): + return self.parse_node() + + def parse_block_node_or_indentless_sequence(self): + return self.parse_node(block=True, indentless_sequence=True) + + def parse_node(self, block=False, indentless_sequence=False): + if self.check_token(AliasToken): + token = self.get_token() + event = AliasEvent(token.value, token.start_mark, token.end_mark) + self.state = self.states.pop() + else: + anchor = None + tag = None + start_mark = end_mark = tag_mark = None + if self.check_token(AnchorToken): + token = self.get_token() + start_mark = token.start_mark + end_mark = token.end_mark + anchor = token.value + if self.check_token(TagToken): + token = self.get_token() + tag_mark = token.start_mark + end_mark = token.end_mark + tag = token.value + elif self.check_token(TagToken): + token = self.get_token() + start_mark = tag_mark = token.start_mark + end_mark = token.end_mark + tag = token.value + if self.check_token(AnchorToken): + token = self.get_token() + end_mark = token.end_mark + anchor = token.value + if tag is not None: + handle, suffix = tag + if handle is not None: + if handle not in self.tag_handles: + raise ParserError("while parsing a node", start_mark, + "found undefined tag handle %r" % handle, + tag_mark) + tag = self.tag_handles[handle]+suffix + else: + tag = suffix + #if tag == '!': + # raise ParserError("while parsing a node", start_mark, + # "found non-specific tag '!'", tag_mark, + # "Please check 'http://pyyaml.org/wiki/YAMLNonSpecificTag' and share your opinion.") + if start_mark is None: + start_mark = end_mark = self.peek_token().start_mark + event = None + implicit = (tag is None or tag == '!') + if indentless_sequence and self.check_token(BlockEntryToken): + end_mark = self.peek_token().end_mark + event = SequenceStartEvent(anchor, tag, implicit, + start_mark, end_mark) + self.state = self.parse_indentless_sequence_entry + else: + if self.check_token(ScalarToken): + token = self.get_token() + end_mark = token.end_mark + if (token.plain and tag is None) or tag == '!': + implicit = (True, False) + elif tag is None: + implicit = (False, True) + else: + implicit = (False, False) + event = ScalarEvent(anchor, tag, implicit, token.value, + start_mark, end_mark, style=token.style) + self.state = self.states.pop() + elif self.check_token(FlowSequenceStartToken): + end_mark = self.peek_token().end_mark + event = SequenceStartEvent(anchor, tag, implicit, + start_mark, end_mark, flow_style=True) + self.state = self.parse_flow_sequence_first_entry + elif self.check_token(FlowMappingStartToken): + end_mark = self.peek_token().end_mark + event = MappingStartEvent(anchor, tag, implicit, + start_mark, end_mark, flow_style=True) + self.state = self.parse_flow_mapping_first_key + elif block and self.check_token(BlockSequenceStartToken): + end_mark = self.peek_token().start_mark + event = SequenceStartEvent(anchor, tag, implicit, + start_mark, end_mark, flow_style=False) + self.state = self.parse_block_sequence_first_entry + elif block and self.check_token(BlockMappingStartToken): + end_mark = self.peek_token().start_mark + event = MappingStartEvent(anchor, tag, implicit, + start_mark, end_mark, flow_style=False) + self.state = self.parse_block_mapping_first_key + elif anchor is not None or tag is not None: + # Empty scalars are allowed even if a tag or an anchor is + # specified. + event = ScalarEvent(anchor, tag, (implicit, False), '', + start_mark, end_mark) + self.state = self.states.pop() + else: + if block: + node = 'block' + else: + node = 'flow' + token = self.peek_token() + raise ParserError("while parsing a %s node" % node, start_mark, + "expected the node content, but found %r" % token.id, + token.start_mark) + return event + + # block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END + + def parse_block_sequence_first_entry(self): + token = self.get_token() + self.marks.append(token.start_mark) + return self.parse_block_sequence_entry() + + def parse_block_sequence_entry(self): + if self.check_token(BlockEntryToken): + token = self.get_token() + if not self.check_token(BlockEntryToken, BlockEndToken): + self.states.append(self.parse_block_sequence_entry) + return self.parse_block_node() + else: + self.state = self.parse_block_sequence_entry + return self.process_empty_scalar(token.end_mark) + if not self.check_token(BlockEndToken): + token = self.peek_token() + raise ParserError("while parsing a block collection", self.marks[-1], + "expected <block end>, but found %r" % token.id, token.start_mark) + token = self.get_token() + event = SequenceEndEvent(token.start_mark, token.end_mark) + self.state = self.states.pop() + self.marks.pop() + return event + + # indentless_sequence ::= (BLOCK-ENTRY block_node?)+ + + def parse_indentless_sequence_entry(self): + if self.check_token(BlockEntryToken): + token = self.get_token() + if not self.check_token(BlockEntryToken, + KeyToken, ValueToken, BlockEndToken): + self.states.append(self.parse_indentless_sequence_entry) + return self.parse_block_node() + else: + self.state = self.parse_indentless_sequence_entry + return self.process_empty_scalar(token.end_mark) + token = self.peek_token() + event = SequenceEndEvent(token.start_mark, token.start_mark) + self.state = self.states.pop() + return event + + # block_mapping ::= BLOCK-MAPPING_START + # ((KEY block_node_or_indentless_sequence?)? + # (VALUE block_node_or_indentless_sequence?)?)* + # BLOCK-END + + def parse_block_mapping_first_key(self): + token = self.get_token() + self.marks.append(token.start_mark) + return self.parse_block_mapping_key() + + def parse_block_mapping_key(self): + if self.check_token(KeyToken): + token = self.get_token() + if not self.check_token(KeyToken, ValueToken, BlockEndToken): + self.states.append(self.parse_block_mapping_value) + return self.parse_block_node_or_indentless_sequence() + else: + self.state = self.parse_block_mapping_value + return self.process_empty_scalar(token.end_mark) + if not self.check_token(BlockEndToken): + token = self.peek_token() + raise ParserError("while parsing a block mapping", self.marks[-1], + "expected <block end>, but found %r" % token.id, token.start_mark) + token = self.get_token() + event = MappingEndEvent(token.start_mark, token.end_mark) + self.state = self.states.pop() + self.marks.pop() + return event + + def parse_block_mapping_value(self): + if self.check_token(ValueToken): + token = self.get_token() + if not self.check_token(KeyToken, ValueToken, BlockEndToken): + self.states.append(self.parse_block_mapping_key) + return self.parse_block_node_or_indentless_sequence() + else: + self.state = self.parse_block_mapping_key + return self.process_empty_scalar(token.end_mark) + else: + self.state = self.parse_block_mapping_key + token = self.peek_token() + return self.process_empty_scalar(token.start_mark) + + # flow_sequence ::= FLOW-SEQUENCE-START + # (flow_sequence_entry FLOW-ENTRY)* + # flow_sequence_entry? + # FLOW-SEQUENCE-END + # flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? + # + # Note that while production rules for both flow_sequence_entry and + # flow_mapping_entry are equal, their interpretations are different. + # For `flow_sequence_entry`, the part `KEY flow_node? (VALUE flow_node?)?` + # generate an inline mapping (set syntax). + + def parse_flow_sequence_first_entry(self): + token = self.get_token() + self.marks.append(token.start_mark) + return self.parse_flow_sequence_entry(first=True) + + def parse_flow_sequence_entry(self, first=False): + if not self.check_token(FlowSequenceEndToken): + if not first: + if self.check_token(FlowEntryToken): + self.get_token() + else: + token = self.peek_token() + raise ParserError("while parsing a flow sequence", self.marks[-1], + "expected ',' or ']', but got %r" % token.id, token.start_mark) + + if self.check_token(KeyToken): + token = self.peek_token() + event = MappingStartEvent(None, None, True, + token.start_mark, token.end_mark, + flow_style=True) + self.state = self.parse_flow_sequence_entry_mapping_key + return event + elif not self.check_token(FlowSequenceEndToken): + self.states.append(self.parse_flow_sequence_entry) + return self.parse_flow_node() + token = self.get_token() + event = SequenceEndEvent(token.start_mark, token.end_mark) + self.state = self.states.pop() + self.marks.pop() + return event + + def parse_flow_sequence_entry_mapping_key(self): + token = self.get_token() + if not self.check_token(ValueToken, + FlowEntryToken, FlowSequenceEndToken): + self.states.append(self.parse_flow_sequence_entry_mapping_value) + return self.parse_flow_node() + else: + self.state = self.parse_flow_sequence_entry_mapping_value + return self.process_empty_scalar(token.end_mark) + + def parse_flow_sequence_entry_mapping_value(self): + if self.check_token(ValueToken): + token = self.get_token() + if not self.check_token(FlowEntryToken, FlowSequenceEndToken): + self.states.append(self.parse_flow_sequence_entry_mapping_end) + return self.parse_flow_node() + else: + self.state = self.parse_flow_sequence_entry_mapping_end + return self.process_empty_scalar(token.end_mark) + else: + self.state = self.parse_flow_sequence_entry_mapping_end + token = self.peek_token() + return self.process_empty_scalar(token.start_mark) + + def parse_flow_sequence_entry_mapping_end(self): + self.state = self.parse_flow_sequence_entry + token = self.peek_token() + return MappingEndEvent(token.start_mark, token.start_mark) + + # flow_mapping ::= FLOW-MAPPING-START + # (flow_mapping_entry FLOW-ENTRY)* + # flow_mapping_entry? + # FLOW-MAPPING-END + # flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? + + def parse_flow_mapping_first_key(self): + token = self.get_token() + self.marks.append(token.start_mark) + return self.parse_flow_mapping_key(first=True) + + def parse_flow_mapping_key(self, first=False): + if not self.check_token(FlowMappingEndToken): + if not first: + if self.check_token(FlowEntryToken): + self.get_token() + else: + token = self.peek_token() + raise ParserError("while parsing a flow mapping", self.marks[-1], + "expected ',' or '}', but got %r" % token.id, token.start_mark) + if self.check_token(KeyToken): + token = self.get_token() + if not self.check_token(ValueToken, + FlowEntryToken, FlowMappingEndToken): + self.states.append(self.parse_flow_mapping_value) + return self.parse_flow_node() + else: + self.state = self.parse_flow_mapping_value + return self.process_empty_scalar(token.end_mark) + elif not self.check_token(FlowMappingEndToken): + self.states.append(self.parse_flow_mapping_empty_value) + return self.parse_flow_node() + token = self.get_token() + event = MappingEndEvent(token.start_mark, token.end_mark) + self.state = self.states.pop() + self.marks.pop() + return event + + def parse_flow_mapping_value(self): + if self.check_token(ValueToken): + token = self.get_token() + if not self.check_token(FlowEntryToken, FlowMappingEndToken): + self.states.append(self.parse_flow_mapping_key) + return self.parse_flow_node() + else: + self.state = self.parse_flow_mapping_key + return self.process_empty_scalar(token.end_mark) + else: + self.state = self.parse_flow_mapping_key + token = self.peek_token() + return self.process_empty_scalar(token.start_mark) + + def parse_flow_mapping_empty_value(self): + self.state = self.parse_flow_mapping_key + return self.process_empty_scalar(self.peek_token().start_mark) + + def process_empty_scalar(self, mark): + return ScalarEvent(None, None, (True, False), '', mark, mark) + diff --git a/pipenv/patched/yaml3/reader.py b/pipenv/patched/yaml3/reader.py new file mode 100644 index 00000000..774b0219 --- /dev/null +++ b/pipenv/patched/yaml3/reader.py @@ -0,0 +1,185 @@ +# This module contains abstractions for the input stream. You don't have to +# looks further, there are no pretty code. +# +# We define two classes here. +# +# Mark(source, line, column) +# It's just a record and its only use is producing nice error messages. +# Parser does not use it for any other purposes. +# +# Reader(source, data) +# Reader determines the encoding of `data` and converts it to unicode. +# Reader provides the following methods and attributes: +# reader.peek(length=1) - return the next `length` characters +# reader.forward(length=1) - move the current position to `length` characters. +# reader.index - the number of the current character. +# reader.line, stream.column - the line and the column of the current character. + +__all__ = ['Reader', 'ReaderError'] + +from .error import YAMLError, Mark + +import codecs, re + +class ReaderError(YAMLError): + + def __init__(self, name, position, character, encoding, reason): + self.name = name + self.character = character + self.position = position + self.encoding = encoding + self.reason = reason + + def __str__(self): + if isinstance(self.character, bytes): + return "'%s' codec can't decode byte #x%02x: %s\n" \ + " in \"%s\", position %d" \ + % (self.encoding, ord(self.character), self.reason, + self.name, self.position) + else: + return "unacceptable character #x%04x: %s\n" \ + " in \"%s\", position %d" \ + % (self.character, self.reason, + self.name, self.position) + +class Reader(object): + # Reader: + # - determines the data encoding and converts it to a unicode string, + # - checks if characters are in allowed range, + # - adds '\0' to the end. + + # Reader accepts + # - a `bytes` object, + # - a `str` object, + # - a file-like object with its `read` method returning `str`, + # - a file-like object with its `read` method returning `unicode`. + + # Yeah, it's ugly and slow. + + def __init__(self, stream): + self.name = None + self.stream = None + self.stream_pointer = 0 + self.eof = True + self.buffer = '' + self.pointer = 0 + self.raw_buffer = None + self.raw_decode = None + self.encoding = None + self.index = 0 + self.line = 0 + self.column = 0 + if isinstance(stream, str): + self.name = "<unicode string>" + self.check_printable(stream) + self.buffer = stream+'\0' + elif isinstance(stream, bytes): + self.name = "<byte string>" + self.raw_buffer = stream + self.determine_encoding() + else: + self.stream = stream + self.name = getattr(stream, 'name', "<file>") + self.eof = False + self.raw_buffer = None + self.determine_encoding() + + def peek(self, index=0): + try: + return self.buffer[self.pointer+index] + except IndexError: + self.update(index+1) + return self.buffer[self.pointer+index] + + def prefix(self, length=1): + if self.pointer+length >= len(self.buffer): + self.update(length) + return self.buffer[self.pointer:self.pointer+length] + + def forward(self, length=1): + if self.pointer+length+1 >= len(self.buffer): + self.update(length+1) + while length: + ch = self.buffer[self.pointer] + self.pointer += 1 + self.index += 1 + if ch in '\n\x85\u2028\u2029' \ + or (ch == '\r' and self.buffer[self.pointer] != '\n'): + self.line += 1 + self.column = 0 + elif ch != '\uFEFF': + self.column += 1 + length -= 1 + + def get_mark(self): + if self.stream is None: + return Mark(self.name, self.index, self.line, self.column, + self.buffer, self.pointer) + else: + return Mark(self.name, self.index, self.line, self.column, + None, None) + + def determine_encoding(self): + while not self.eof and (self.raw_buffer is None or len(self.raw_buffer) < 2): + self.update_raw() + if isinstance(self.raw_buffer, bytes): + if self.raw_buffer.startswith(codecs.BOM_UTF16_LE): + self.raw_decode = codecs.utf_16_le_decode + self.encoding = 'utf-16-le' + elif self.raw_buffer.startswith(codecs.BOM_UTF16_BE): + self.raw_decode = codecs.utf_16_be_decode + self.encoding = 'utf-16-be' + else: + self.raw_decode = codecs.utf_8_decode + self.encoding = 'utf-8' + self.update(1) + + NON_PRINTABLE = re.compile('[^\x09\x0A\x0D\x20-\x7E\x85\xA0-\uD7FF\uE000-\uFFFD\U00010000-\U0010ffff]') + def check_printable(self, data): + match = self.NON_PRINTABLE.search(data) + if match: + character = match.group() + position = self.index+(len(self.buffer)-self.pointer)+match.start() + raise ReaderError(self.name, position, ord(character), + 'unicode', "special characters are not allowed") + + def update(self, length): + if self.raw_buffer is None: + return + self.buffer = self.buffer[self.pointer:] + self.pointer = 0 + while len(self.buffer) < length: + if not self.eof: + self.update_raw() + if self.raw_decode is not None: + try: + data, converted = self.raw_decode(self.raw_buffer, + 'strict', self.eof) + except UnicodeDecodeError as exc: + character = self.raw_buffer[exc.start] + if self.stream is not None: + position = self.stream_pointer-len(self.raw_buffer)+exc.start + else: + position = exc.start + raise ReaderError(self.name, position, character, + exc.encoding, exc.reason) + else: + data = self.raw_buffer + converted = len(data) + self.check_printable(data) + self.buffer += data + self.raw_buffer = self.raw_buffer[converted:] + if self.eof: + self.buffer += '\0' + self.raw_buffer = None + break + + def update_raw(self, size=4096): + data = self.stream.read(size) + if self.raw_buffer is None: + self.raw_buffer = data + else: + self.raw_buffer += data + self.stream_pointer += len(data) + if not data: + self.eof = True diff --git a/pipenv/patched/yaml3/representer.py b/pipenv/patched/yaml3/representer.py new file mode 100644 index 00000000..3b0b192e --- /dev/null +++ b/pipenv/patched/yaml3/representer.py @@ -0,0 +1,389 @@ + +__all__ = ['BaseRepresenter', 'SafeRepresenter', 'Representer', + 'RepresenterError'] + +from .error import * +from .nodes import * + +import datetime, copyreg, types, base64, collections + +class RepresenterError(YAMLError): + pass + +class BaseRepresenter: + + yaml_representers = {} + yaml_multi_representers = {} + + def __init__(self, default_style=None, default_flow_style=False, sort_keys=True): + self.default_style = default_style + self.sort_keys = sort_keys + self.default_flow_style = default_flow_style + self.represented_objects = {} + self.object_keeper = [] + self.alias_key = None + + def represent(self, data): + node = self.represent_data(data) + self.serialize(node) + self.represented_objects = {} + self.object_keeper = [] + self.alias_key = None + + def represent_data(self, data): + if self.ignore_aliases(data): + self.alias_key = None + else: + self.alias_key = id(data) + if self.alias_key is not None: + if self.alias_key in self.represented_objects: + node = self.represented_objects[self.alias_key] + #if node is None: + # raise RepresenterError("recursive objects are not allowed: %r" % data) + return node + #self.represented_objects[alias_key] = None + self.object_keeper.append(data) + data_types = type(data).__mro__ + if data_types[0] in self.yaml_representers: + node = self.yaml_representers[data_types[0]](self, data) + else: + for data_type in data_types: + if data_type in self.yaml_multi_representers: + node = self.yaml_multi_representers[data_type](self, data) + break + else: + if None in self.yaml_multi_representers: + node = self.yaml_multi_representers[None](self, data) + elif None in self.yaml_representers: + node = self.yaml_representers[None](self, data) + else: + node = ScalarNode(None, str(data)) + #if alias_key is not None: + # self.represented_objects[alias_key] = node + return node + + @classmethod + def add_representer(cls, data_type, representer): + if not 'yaml_representers' in cls.__dict__: + cls.yaml_representers = cls.yaml_representers.copy() + cls.yaml_representers[data_type] = representer + + @classmethod + def add_multi_representer(cls, data_type, representer): + if not 'yaml_multi_representers' in cls.__dict__: + cls.yaml_multi_representers = cls.yaml_multi_representers.copy() + cls.yaml_multi_representers[data_type] = representer + + def represent_scalar(self, tag, value, style=None): + if style is None: + style = self.default_style + node = ScalarNode(tag, value, style=style) + if self.alias_key is not None: + self.represented_objects[self.alias_key] = node + return node + + def represent_sequence(self, tag, sequence, flow_style=None): + value = [] + node = SequenceNode(tag, value, flow_style=flow_style) + if self.alias_key is not None: + self.represented_objects[self.alias_key] = node + best_style = True + for item in sequence: + node_item = self.represent_data(item) + if not (isinstance(node_item, ScalarNode) and not node_item.style): + best_style = False + value.append(node_item) + if flow_style is None: + if self.default_flow_style is not None: + node.flow_style = self.default_flow_style + else: + node.flow_style = best_style + return node + + def represent_mapping(self, tag, mapping, flow_style=None): + value = [] + node = MappingNode(tag, value, flow_style=flow_style) + if self.alias_key is not None: + self.represented_objects[self.alias_key] = node + best_style = True + if hasattr(mapping, 'items'): + mapping = list(mapping.items()) + if self.sort_keys: + try: + mapping = sorted(mapping) + except TypeError: + pass + for item_key, item_value in mapping: + node_key = self.represent_data(item_key) + node_value = self.represent_data(item_value) + if not (isinstance(node_key, ScalarNode) and not node_key.style): + best_style = False + if not (isinstance(node_value, ScalarNode) and not node_value.style): + best_style = False + value.append((node_key, node_value)) + if flow_style is None: + if self.default_flow_style is not None: + node.flow_style = self.default_flow_style + else: + node.flow_style = best_style + return node + + def ignore_aliases(self, data): + return False + +class SafeRepresenter(BaseRepresenter): + + def ignore_aliases(self, data): + if data is None: + return True + if isinstance(data, tuple) and data == (): + return True + if isinstance(data, (str, bytes, bool, int, float)): + return True + + def represent_none(self, data): + return self.represent_scalar('tag:yaml.org,2002:null', 'null') + + def represent_str(self, data): + return self.represent_scalar('tag:yaml.org,2002:str', data) + + def represent_binary(self, data): + if hasattr(base64, 'encodebytes'): + data = base64.encodebytes(data).decode('ascii') + else: + data = base64.encodestring(data).decode('ascii') + return self.represent_scalar('tag:yaml.org,2002:binary', data, style='|') + + def represent_bool(self, data): + if data: + value = 'true' + else: + value = 'false' + return self.represent_scalar('tag:yaml.org,2002:bool', value) + + def represent_int(self, data): + return self.represent_scalar('tag:yaml.org,2002:int', str(data)) + + inf_value = 1e300 + while repr(inf_value) != repr(inf_value*inf_value): + inf_value *= inf_value + + def represent_float(self, data): + if data != data or (data == 0.0 and data == 1.0): + value = '.nan' + elif data == self.inf_value: + value = '.inf' + elif data == -self.inf_value: + value = '-.inf' + else: + value = repr(data).lower() + # Note that in some cases `repr(data)` represents a float number + # without the decimal parts. For instance: + # >>> repr(1e17) + # '1e17' + # Unfortunately, this is not a valid float representation according + # to the definition of the `!!float` tag. We fix this by adding + # '.0' before the 'e' symbol. + if '.' not in value and 'e' in value: + value = value.replace('e', '.0e', 1) + return self.represent_scalar('tag:yaml.org,2002:float', value) + + def represent_list(self, data): + #pairs = (len(data) > 0 and isinstance(data, list)) + #if pairs: + # for item in data: + # if not isinstance(item, tuple) or len(item) != 2: + # pairs = False + # break + #if not pairs: + return self.represent_sequence('tag:yaml.org,2002:seq', data) + #value = [] + #for item_key, item_value in data: + # value.append(self.represent_mapping(u'tag:yaml.org,2002:map', + # [(item_key, item_value)])) + #return SequenceNode(u'tag:yaml.org,2002:pairs', value) + + def represent_dict(self, data): + return self.represent_mapping('tag:yaml.org,2002:map', data) + + def represent_set(self, data): + value = {} + for key in data: + value[key] = None + return self.represent_mapping('tag:yaml.org,2002:set', value) + + def represent_date(self, data): + value = data.isoformat() + return self.represent_scalar('tag:yaml.org,2002:timestamp', value) + + def represent_datetime(self, data): + value = data.isoformat(' ') + return self.represent_scalar('tag:yaml.org,2002:timestamp', value) + + def represent_yaml_object(self, tag, data, cls, flow_style=None): + if hasattr(data, '__getstate__'): + state = data.__getstate__() + else: + state = data.__dict__.copy() + return self.represent_mapping(tag, state, flow_style=flow_style) + + def represent_undefined(self, data): + raise RepresenterError("cannot represent an object", data) + +SafeRepresenter.add_representer(type(None), + SafeRepresenter.represent_none) + +SafeRepresenter.add_representer(str, + SafeRepresenter.represent_str) + +SafeRepresenter.add_representer(bytes, + SafeRepresenter.represent_binary) + +SafeRepresenter.add_representer(bool, + SafeRepresenter.represent_bool) + +SafeRepresenter.add_representer(int, + SafeRepresenter.represent_int) + +SafeRepresenter.add_representer(float, + SafeRepresenter.represent_float) + +SafeRepresenter.add_representer(list, + SafeRepresenter.represent_list) + +SafeRepresenter.add_representer(tuple, + SafeRepresenter.represent_list) + +SafeRepresenter.add_representer(dict, + SafeRepresenter.represent_dict) + +SafeRepresenter.add_representer(set, + SafeRepresenter.represent_set) + +SafeRepresenter.add_representer(datetime.date, + SafeRepresenter.represent_date) + +SafeRepresenter.add_representer(datetime.datetime, + SafeRepresenter.represent_datetime) + +SafeRepresenter.add_representer(None, + SafeRepresenter.represent_undefined) + +class Representer(SafeRepresenter): + + def represent_complex(self, data): + if data.imag == 0.0: + data = '%r' % data.real + elif data.real == 0.0: + data = '%rj' % data.imag + elif data.imag > 0: + data = '%r+%rj' % (data.real, data.imag) + else: + data = '%r%rj' % (data.real, data.imag) + return self.represent_scalar('tag:yaml.org,2002:python/complex', data) + + def represent_tuple(self, data): + return self.represent_sequence('tag:yaml.org,2002:python/tuple', data) + + def represent_name(self, data): + name = '%s.%s' % (data.__module__, data.__name__) + return self.represent_scalar('tag:yaml.org,2002:python/name:'+name, '') + + def represent_module(self, data): + return self.represent_scalar( + 'tag:yaml.org,2002:python/module:'+data.__name__, '') + + def represent_object(self, data): + # We use __reduce__ API to save the data. data.__reduce__ returns + # a tuple of length 2-5: + # (function, args, state, listitems, dictitems) + + # For reconstructing, we calls function(*args), then set its state, + # listitems, and dictitems if they are not None. + + # A special case is when function.__name__ == '__newobj__'. In this + # case we create the object with args[0].__new__(*args). + + # Another special case is when __reduce__ returns a string - we don't + # support it. + + # We produce a !!python/object, !!python/object/new or + # !!python/object/apply node. + + cls = type(data) + if cls in copyreg.dispatch_table: + reduce = copyreg.dispatch_table[cls](data) + elif hasattr(data, '__reduce_ex__'): + reduce = data.__reduce_ex__(2) + elif hasattr(data, '__reduce__'): + reduce = data.__reduce__() + else: + raise RepresenterError("cannot represent an object", data) + reduce = (list(reduce)+[None]*5)[:5] + function, args, state, listitems, dictitems = reduce + args = list(args) + if state is None: + state = {} + if listitems is not None: + listitems = list(listitems) + if dictitems is not None: + dictitems = dict(dictitems) + if function.__name__ == '__newobj__': + function = args[0] + args = args[1:] + tag = 'tag:yaml.org,2002:python/object/new:' + newobj = True + else: + tag = 'tag:yaml.org,2002:python/object/apply:' + newobj = False + function_name = '%s.%s' % (function.__module__, function.__name__) + if not args and not listitems and not dictitems \ + and isinstance(state, dict) and newobj: + return self.represent_mapping( + 'tag:yaml.org,2002:python/object:'+function_name, state) + if not listitems and not dictitems \ + and isinstance(state, dict) and not state: + return self.represent_sequence(tag+function_name, args) + value = {} + if args: + value['args'] = args + if state or not isinstance(state, dict): + value['state'] = state + if listitems: + value['listitems'] = listitems + if dictitems: + value['dictitems'] = dictitems + return self.represent_mapping(tag+function_name, value) + + def represent_ordered_dict(self, data): + # Provide uniform representation across different Python versions. + data_type = type(data) + tag = 'tag:yaml.org,2002:python/object/apply:%s.%s' \ + % (data_type.__module__, data_type.__name__) + items = [[key, value] for key, value in data.items()] + return self.represent_sequence(tag, [items]) + +Representer.add_representer(complex, + Representer.represent_complex) + +Representer.add_representer(tuple, + Representer.represent_tuple) + +Representer.add_representer(type, + Representer.represent_name) + +Representer.add_representer(collections.OrderedDict, + Representer.represent_ordered_dict) + +Representer.add_representer(types.FunctionType, + Representer.represent_name) + +Representer.add_representer(types.BuiltinFunctionType, + Representer.represent_name) + +Representer.add_representer(types.ModuleType, + Representer.represent_module) + +Representer.add_multi_representer(object, + Representer.represent_object) + diff --git a/pipenv/patched/yaml3/resolver.py b/pipenv/patched/yaml3/resolver.py new file mode 100644 index 00000000..013896d2 --- /dev/null +++ b/pipenv/patched/yaml3/resolver.py @@ -0,0 +1,227 @@ + +__all__ = ['BaseResolver', 'Resolver'] + +from .error import * +from .nodes import * + +import re + +class ResolverError(YAMLError): + pass + +class BaseResolver: + + DEFAULT_SCALAR_TAG = 'tag:yaml.org,2002:str' + DEFAULT_SEQUENCE_TAG = 'tag:yaml.org,2002:seq' + DEFAULT_MAPPING_TAG = 'tag:yaml.org,2002:map' + + yaml_implicit_resolvers = {} + yaml_path_resolvers = {} + + def __init__(self): + self.resolver_exact_paths = [] + self.resolver_prefix_paths = [] + + @classmethod + def add_implicit_resolver(cls, tag, regexp, first): + if not 'yaml_implicit_resolvers' in cls.__dict__: + implicit_resolvers = {} + for key in cls.yaml_implicit_resolvers: + implicit_resolvers[key] = cls.yaml_implicit_resolvers[key][:] + cls.yaml_implicit_resolvers = implicit_resolvers + if first is None: + first = [None] + for ch in first: + cls.yaml_implicit_resolvers.setdefault(ch, []).append((tag, regexp)) + + @classmethod + def add_path_resolver(cls, tag, path, kind=None): + # Note: `add_path_resolver` is experimental. The API could be changed. + # `new_path` is a pattern that is matched against the path from the + # root to the node that is being considered. `node_path` elements are + # tuples `(node_check, index_check)`. `node_check` is a node class: + # `ScalarNode`, `SequenceNode`, `MappingNode` or `None`. `None` + # matches any kind of a node. `index_check` could be `None`, a boolean + # value, a string value, or a number. `None` and `False` match against + # any _value_ of sequence and mapping nodes. `True` matches against + # any _key_ of a mapping node. A string `index_check` matches against + # a mapping value that corresponds to a scalar key which content is + # equal to the `index_check` value. An integer `index_check` matches + # against a sequence value with the index equal to `index_check`. + if not 'yaml_path_resolvers' in cls.__dict__: + cls.yaml_path_resolvers = cls.yaml_path_resolvers.copy() + new_path = [] + for element in path: + if isinstance(element, (list, tuple)): + if len(element) == 2: + node_check, index_check = element + elif len(element) == 1: + node_check = element[0] + index_check = True + else: + raise ResolverError("Invalid path element: %s" % element) + else: + node_check = None + index_check = element + if node_check is str: + node_check = ScalarNode + elif node_check is list: + node_check = SequenceNode + elif node_check is dict: + node_check = MappingNode + elif node_check not in [ScalarNode, SequenceNode, MappingNode] \ + and not isinstance(node_check, str) \ + and node_check is not None: + raise ResolverError("Invalid node checker: %s" % node_check) + if not isinstance(index_check, (str, int)) \ + and index_check is not None: + raise ResolverError("Invalid index checker: %s" % index_check) + new_path.append((node_check, index_check)) + if kind is str: + kind = ScalarNode + elif kind is list: + kind = SequenceNode + elif kind is dict: + kind = MappingNode + elif kind not in [ScalarNode, SequenceNode, MappingNode] \ + and kind is not None: + raise ResolverError("Invalid node kind: %s" % kind) + cls.yaml_path_resolvers[tuple(new_path), kind] = tag + + def descend_resolver(self, current_node, current_index): + if not self.yaml_path_resolvers: + return + exact_paths = {} + prefix_paths = [] + if current_node: + depth = len(self.resolver_prefix_paths) + for path, kind in self.resolver_prefix_paths[-1]: + if self.check_resolver_prefix(depth, path, kind, + current_node, current_index): + if len(path) > depth: + prefix_paths.append((path, kind)) + else: + exact_paths[kind] = self.yaml_path_resolvers[path, kind] + else: + for path, kind in self.yaml_path_resolvers: + if not path: + exact_paths[kind] = self.yaml_path_resolvers[path, kind] + else: + prefix_paths.append((path, kind)) + self.resolver_exact_paths.append(exact_paths) + self.resolver_prefix_paths.append(prefix_paths) + + def ascend_resolver(self): + if not self.yaml_path_resolvers: + return + self.resolver_exact_paths.pop() + self.resolver_prefix_paths.pop() + + def check_resolver_prefix(self, depth, path, kind, + current_node, current_index): + node_check, index_check = path[depth-1] + if isinstance(node_check, str): + if current_node.tag != node_check: + return + elif node_check is not None: + if not isinstance(current_node, node_check): + return + if index_check is True and current_index is not None: + return + if (index_check is False or index_check is None) \ + and current_index is None: + return + if isinstance(index_check, str): + if not (isinstance(current_index, ScalarNode) + and index_check == current_index.value): + return + elif isinstance(index_check, int) and not isinstance(index_check, bool): + if index_check != current_index: + return + return True + + def resolve(self, kind, value, implicit): + if kind is ScalarNode and implicit[0]: + if value == '': + resolvers = self.yaml_implicit_resolvers.get('', []) + else: + resolvers = self.yaml_implicit_resolvers.get(value[0], []) + wildcard_resolvers = self.yaml_implicit_resolvers.get(None, []) + for tag, regexp in resolvers + wildcard_resolvers: + if regexp.match(value): + return tag + implicit = implicit[1] + if self.yaml_path_resolvers: + exact_paths = self.resolver_exact_paths[-1] + if kind in exact_paths: + return exact_paths[kind] + if None in exact_paths: + return exact_paths[None] + if kind is ScalarNode: + return self.DEFAULT_SCALAR_TAG + elif kind is SequenceNode: + return self.DEFAULT_SEQUENCE_TAG + elif kind is MappingNode: + return self.DEFAULT_MAPPING_TAG + +class Resolver(BaseResolver): + pass + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:bool', + re.compile(r'''^(?:yes|Yes|YES|no|No|NO + |true|True|TRUE|false|False|FALSE + |on|On|ON|off|Off|OFF)$''', re.X), + list('yYnNtTfFoO')) + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:float', + re.compile(r'''^(?:[-+]?(?:[0-9][0-9_]*)\.[0-9_]*(?:[eE][-+][0-9]+)? + |\.[0-9_]+(?:[eE][-+][0-9]+)? + |[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]* + |[-+]?\.(?:inf|Inf|INF) + |\.(?:nan|NaN|NAN))$''', re.X), + list('-+0123456789.')) + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:int', + re.compile(r'''^(?:[-+]?0b[0-1_]+ + |[-+]?0[0-7_]+ + |[-+]?(?:0|[1-9][0-9_]*) + |[-+]?0x[0-9a-fA-F_]+ + |[-+]?[1-9][0-9_]*(?::[0-5]?[0-9])+)$''', re.X), + list('-+0123456789')) + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:merge', + re.compile(r'^(?:<<)$'), + ['<']) + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:null', + re.compile(r'''^(?: ~ + |null|Null|NULL + | )$''', re.X), + ['~', 'n', 'N', '']) + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:timestamp', + re.compile(r'''^(?:[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] + |[0-9][0-9][0-9][0-9] -[0-9][0-9]? -[0-9][0-9]? + (?:[Tt]|[ \t]+)[0-9][0-9]? + :[0-9][0-9] :[0-9][0-9] (?:\.[0-9]*)? + (?:[ \t]*(?:Z|[-+][0-9][0-9]?(?::[0-9][0-9])?))?)$''', re.X), + list('0123456789')) + +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:value', + re.compile(r'^(?:=)$'), + ['=']) + +# The following resolver is only for documentation purposes. It cannot work +# because plain scalars cannot start with '!', '&', or '*'. +Resolver.add_implicit_resolver( + 'tag:yaml.org,2002:yaml', + re.compile(r'^(?:!|&|\*)$'), + list('!&*')) + diff --git a/pipenv/patched/yaml3/scanner.py b/pipenv/patched/yaml3/scanner.py new file mode 100644 index 00000000..7437ede1 --- /dev/null +++ b/pipenv/patched/yaml3/scanner.py @@ -0,0 +1,1435 @@ + +# Scanner produces tokens of the following types: +# STREAM-START +# STREAM-END +# DIRECTIVE(name, value) +# DOCUMENT-START +# DOCUMENT-END +# BLOCK-SEQUENCE-START +# BLOCK-MAPPING-START +# BLOCK-END +# FLOW-SEQUENCE-START +# FLOW-MAPPING-START +# FLOW-SEQUENCE-END +# FLOW-MAPPING-END +# BLOCK-ENTRY +# FLOW-ENTRY +# KEY +# VALUE +# ALIAS(value) +# ANCHOR(value) +# TAG(value) +# SCALAR(value, plain, style) +# +# Read comments in the Scanner code for more details. +# + +__all__ = ['Scanner', 'ScannerError'] + +from .error import MarkedYAMLError +from .tokens import * + +class ScannerError(MarkedYAMLError): + pass + +class SimpleKey: + # See below simple keys treatment. + + def __init__(self, token_number, required, index, line, column, mark): + self.token_number = token_number + self.required = required + self.index = index + self.line = line + self.column = column + self.mark = mark + +class Scanner: + + def __init__(self): + """Initialize the scanner.""" + # It is assumed that Scanner and Reader will have a common descendant. + # Reader do the dirty work of checking for BOM and converting the + # input data to Unicode. It also adds NUL to the end. + # + # Reader supports the following methods + # self.peek(i=0) # peek the next i-th character + # self.prefix(l=1) # peek the next l characters + # self.forward(l=1) # read the next l characters and move the pointer. + + # Had we reached the end of the stream? + self.done = False + + # The number of unclosed '{' and '['. `flow_level == 0` means block + # context. + self.flow_level = 0 + + # List of processed tokens that are not yet emitted. + self.tokens = [] + + # Add the STREAM-START token. + self.fetch_stream_start() + + # Number of tokens that were emitted through the `get_token` method. + self.tokens_taken = 0 + + # The current indentation level. + self.indent = -1 + + # Past indentation levels. + self.indents = [] + + # Variables related to simple keys treatment. + + # A simple key is a key that is not denoted by the '?' indicator. + # Example of simple keys: + # --- + # block simple key: value + # ? not a simple key: + # : { flow simple key: value } + # We emit the KEY token before all keys, so when we find a potential + # simple key, we try to locate the corresponding ':' indicator. + # Simple keys should be limited to a single line and 1024 characters. + + # Can a simple key start at the current position? A simple key may + # start: + # - at the beginning of the line, not counting indentation spaces + # (in block context), + # - after '{', '[', ',' (in the flow context), + # - after '?', ':', '-' (in the block context). + # In the block context, this flag also signifies if a block collection + # may start at the current position. + self.allow_simple_key = True + + # Keep track of possible simple keys. This is a dictionary. The key + # is `flow_level`; there can be no more that one possible simple key + # for each level. The value is a SimpleKey record: + # (token_number, required, index, line, column, mark) + # A simple key may start with ALIAS, ANCHOR, TAG, SCALAR(flow), + # '[', or '{' tokens. + self.possible_simple_keys = {} + + # Public methods. + + def check_token(self, *choices): + # Check if the next token is one of the given types. + while self.need_more_tokens(): + self.fetch_more_tokens() + if self.tokens: + if not choices: + return True + for choice in choices: + if isinstance(self.tokens[0], choice): + return True + return False + + def peek_token(self): + # Return the next token, but do not delete if from the queue. + # Return None if no more tokens. + while self.need_more_tokens(): + self.fetch_more_tokens() + if self.tokens: + return self.tokens[0] + else: + return None + + def get_token(self): + # Return the next token. + while self.need_more_tokens(): + self.fetch_more_tokens() + if self.tokens: + self.tokens_taken += 1 + return self.tokens.pop(0) + + # Private methods. + + def need_more_tokens(self): + if self.done: + return False + if not self.tokens: + return True + # The current token may be a potential simple key, so we + # need to look further. + self.stale_possible_simple_keys() + if self.next_possible_simple_key() == self.tokens_taken: + return True + + def fetch_more_tokens(self): + + # Eat whitespaces and comments until we reach the next token. + self.scan_to_next_token() + + # Remove obsolete possible simple keys. + self.stale_possible_simple_keys() + + # Compare the current indentation and column. It may add some tokens + # and decrease the current indentation level. + self.unwind_indent(self.column) + + # Peek the next character. + ch = self.peek() + + # Is it the end of stream? + if ch == '\0': + return self.fetch_stream_end() + + # Is it a directive? + if ch == '%' and self.check_directive(): + return self.fetch_directive() + + # Is it the document start? + if ch == '-' and self.check_document_start(): + return self.fetch_document_start() + + # Is it the document end? + if ch == '.' and self.check_document_end(): + return self.fetch_document_end() + + # TODO: support for BOM within a stream. + #if ch == '\uFEFF': + # return self.fetch_bom() <-- issue BOMToken + + # Note: the order of the following checks is NOT significant. + + # Is it the flow sequence start indicator? + if ch == '[': + return self.fetch_flow_sequence_start() + + # Is it the flow mapping start indicator? + if ch == '{': + return self.fetch_flow_mapping_start() + + # Is it the flow sequence end indicator? + if ch == ']': + return self.fetch_flow_sequence_end() + + # Is it the flow mapping end indicator? + if ch == '}': + return self.fetch_flow_mapping_end() + + # Is it the flow entry indicator? + if ch == ',': + return self.fetch_flow_entry() + + # Is it the block entry indicator? + if ch == '-' and self.check_block_entry(): + return self.fetch_block_entry() + + # Is it the key indicator? + if ch == '?' and self.check_key(): + return self.fetch_key() + + # Is it the value indicator? + if ch == ':' and self.check_value(): + return self.fetch_value() + + # Is it an alias? + if ch == '*': + return self.fetch_alias() + + # Is it an anchor? + if ch == '&': + return self.fetch_anchor() + + # Is it a tag? + if ch == '!': + return self.fetch_tag() + + # Is it a literal scalar? + if ch == '|' and not self.flow_level: + return self.fetch_literal() + + # Is it a folded scalar? + if ch == '>' and not self.flow_level: + return self.fetch_folded() + + # Is it a single quoted scalar? + if ch == '\'': + return self.fetch_single() + + # Is it a double quoted scalar? + if ch == '\"': + return self.fetch_double() + + # It must be a plain scalar then. + if self.check_plain(): + return self.fetch_plain() + + # No? It's an error. Let's produce a nice error message. + raise ScannerError("while scanning for the next token", None, + "found character %r that cannot start any token" % ch, + self.get_mark()) + + # Simple keys treatment. + + def next_possible_simple_key(self): + # Return the number of the nearest possible simple key. Actually we + # don't need to loop through the whole dictionary. We may replace it + # with the following code: + # if not self.possible_simple_keys: + # return None + # return self.possible_simple_keys[ + # min(self.possible_simple_keys.keys())].token_number + min_token_number = None + for level in self.possible_simple_keys: + key = self.possible_simple_keys[level] + if min_token_number is None or key.token_number < min_token_number: + min_token_number = key.token_number + return min_token_number + + def stale_possible_simple_keys(self): + # Remove entries that are no longer possible simple keys. According to + # the YAML specification, simple keys + # - should be limited to a single line, + # - should be no longer than 1024 characters. + # Disabling this procedure will allow simple keys of any length and + # height (may cause problems if indentation is broken though). + for level in list(self.possible_simple_keys): + key = self.possible_simple_keys[level] + if key.line != self.line \ + or self.index-key.index > 1024: + if key.required: + raise ScannerError("while scanning a simple key", key.mark, + "could not find expected ':'", self.get_mark()) + del self.possible_simple_keys[level] + + def save_possible_simple_key(self): + # The next token may start a simple key. We check if it's possible + # and save its position. This function is called for + # ALIAS, ANCHOR, TAG, SCALAR(flow), '[', and '{'. + + # Check if a simple key is required at the current position. + required = not self.flow_level and self.indent == self.column + + # The next token might be a simple key. Let's save it's number and + # position. + if self.allow_simple_key: + self.remove_possible_simple_key() + token_number = self.tokens_taken+len(self.tokens) + key = SimpleKey(token_number, required, + self.index, self.line, self.column, self.get_mark()) + self.possible_simple_keys[self.flow_level] = key + + def remove_possible_simple_key(self): + # Remove the saved possible key position at the current flow level. + if self.flow_level in self.possible_simple_keys: + key = self.possible_simple_keys[self.flow_level] + + if key.required: + raise ScannerError("while scanning a simple key", key.mark, + "could not find expected ':'", self.get_mark()) + + del self.possible_simple_keys[self.flow_level] + + # Indentation functions. + + def unwind_indent(self, column): + + ## In flow context, tokens should respect indentation. + ## Actually the condition should be `self.indent >= column` according to + ## the spec. But this condition will prohibit intuitively correct + ## constructions such as + ## key : { + ## } + #if self.flow_level and self.indent > column: + # raise ScannerError(None, None, + # "invalid indentation or unclosed '[' or '{'", + # self.get_mark()) + + # In the flow context, indentation is ignored. We make the scanner less + # restrictive then specification requires. + if self.flow_level: + return + + # In block context, we may need to issue the BLOCK-END tokens. + while self.indent > column: + mark = self.get_mark() + self.indent = self.indents.pop() + self.tokens.append(BlockEndToken(mark, mark)) + + def add_indent(self, column): + # Check if we need to increase indentation. + if self.indent < column: + self.indents.append(self.indent) + self.indent = column + return True + return False + + # Fetchers. + + def fetch_stream_start(self): + # We always add STREAM-START as the first token and STREAM-END as the + # last token. + + # Read the token. + mark = self.get_mark() + + # Add STREAM-START. + self.tokens.append(StreamStartToken(mark, mark, + encoding=self.encoding)) + + + def fetch_stream_end(self): + + # Set the current indentation to -1. + self.unwind_indent(-1) + + # Reset simple keys. + self.remove_possible_simple_key() + self.allow_simple_key = False + self.possible_simple_keys = {} + + # Read the token. + mark = self.get_mark() + + # Add STREAM-END. + self.tokens.append(StreamEndToken(mark, mark)) + + # The steam is finished. + self.done = True + + def fetch_directive(self): + + # Set the current indentation to -1. + self.unwind_indent(-1) + + # Reset simple keys. + self.remove_possible_simple_key() + self.allow_simple_key = False + + # Scan and add DIRECTIVE. + self.tokens.append(self.scan_directive()) + + def fetch_document_start(self): + self.fetch_document_indicator(DocumentStartToken) + + def fetch_document_end(self): + self.fetch_document_indicator(DocumentEndToken) + + def fetch_document_indicator(self, TokenClass): + + # Set the current indentation to -1. + self.unwind_indent(-1) + + # Reset simple keys. Note that there could not be a block collection + # after '---'. + self.remove_possible_simple_key() + self.allow_simple_key = False + + # Add DOCUMENT-START or DOCUMENT-END. + start_mark = self.get_mark() + self.forward(3) + end_mark = self.get_mark() + self.tokens.append(TokenClass(start_mark, end_mark)) + + def fetch_flow_sequence_start(self): + self.fetch_flow_collection_start(FlowSequenceStartToken) + + def fetch_flow_mapping_start(self): + self.fetch_flow_collection_start(FlowMappingStartToken) + + def fetch_flow_collection_start(self, TokenClass): + + # '[' and '{' may start a simple key. + self.save_possible_simple_key() + + # Increase the flow level. + self.flow_level += 1 + + # Simple keys are allowed after '[' and '{'. + self.allow_simple_key = True + + # Add FLOW-SEQUENCE-START or FLOW-MAPPING-START. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(TokenClass(start_mark, end_mark)) + + def fetch_flow_sequence_end(self): + self.fetch_flow_collection_end(FlowSequenceEndToken) + + def fetch_flow_mapping_end(self): + self.fetch_flow_collection_end(FlowMappingEndToken) + + def fetch_flow_collection_end(self, TokenClass): + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Decrease the flow level. + self.flow_level -= 1 + + # No simple keys after ']' or '}'. + self.allow_simple_key = False + + # Add FLOW-SEQUENCE-END or FLOW-MAPPING-END. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(TokenClass(start_mark, end_mark)) + + def fetch_flow_entry(self): + + # Simple keys are allowed after ','. + self.allow_simple_key = True + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Add FLOW-ENTRY. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(FlowEntryToken(start_mark, end_mark)) + + def fetch_block_entry(self): + + # Block context needs additional checks. + if not self.flow_level: + + # Are we allowed to start a new entry? + if not self.allow_simple_key: + raise ScannerError(None, None, + "sequence entries are not allowed here", + self.get_mark()) + + # We may need to add BLOCK-SEQUENCE-START. + if self.add_indent(self.column): + mark = self.get_mark() + self.tokens.append(BlockSequenceStartToken(mark, mark)) + + # It's an error for the block entry to occur in the flow context, + # but we let the parser detect this. + else: + pass + + # Simple keys are allowed after '-'. + self.allow_simple_key = True + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Add BLOCK-ENTRY. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(BlockEntryToken(start_mark, end_mark)) + + def fetch_key(self): + + # Block context needs additional checks. + if not self.flow_level: + + # Are we allowed to start a key (not necessary a simple)? + if not self.allow_simple_key: + raise ScannerError(None, None, + "mapping keys are not allowed here", + self.get_mark()) + + # We may need to add BLOCK-MAPPING-START. + if self.add_indent(self.column): + mark = self.get_mark() + self.tokens.append(BlockMappingStartToken(mark, mark)) + + # Simple keys are allowed after '?' in the block context. + self.allow_simple_key = not self.flow_level + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Add KEY. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(KeyToken(start_mark, end_mark)) + + def fetch_value(self): + + # Do we determine a simple key? + if self.flow_level in self.possible_simple_keys: + + # Add KEY. + key = self.possible_simple_keys[self.flow_level] + del self.possible_simple_keys[self.flow_level] + self.tokens.insert(key.token_number-self.tokens_taken, + KeyToken(key.mark, key.mark)) + + # If this key starts a new block mapping, we need to add + # BLOCK-MAPPING-START. + if not self.flow_level: + if self.add_indent(key.column): + self.tokens.insert(key.token_number-self.tokens_taken, + BlockMappingStartToken(key.mark, key.mark)) + + # There cannot be two simple keys one after another. + self.allow_simple_key = False + + # It must be a part of a complex key. + else: + + # Block context needs additional checks. + # (Do we really need them? They will be caught by the parser + # anyway.) + if not self.flow_level: + + # We are allowed to start a complex value if and only if + # we can start a simple key. + if not self.allow_simple_key: + raise ScannerError(None, None, + "mapping values are not allowed here", + self.get_mark()) + + # If this value starts a new block mapping, we need to add + # BLOCK-MAPPING-START. It will be detected as an error later by + # the parser. + if not self.flow_level: + if self.add_indent(self.column): + mark = self.get_mark() + self.tokens.append(BlockMappingStartToken(mark, mark)) + + # Simple keys are allowed after ':' in the block context. + self.allow_simple_key = not self.flow_level + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Add VALUE. + start_mark = self.get_mark() + self.forward() + end_mark = self.get_mark() + self.tokens.append(ValueToken(start_mark, end_mark)) + + def fetch_alias(self): + + # ALIAS could be a simple key. + self.save_possible_simple_key() + + # No simple keys after ALIAS. + self.allow_simple_key = False + + # Scan and add ALIAS. + self.tokens.append(self.scan_anchor(AliasToken)) + + def fetch_anchor(self): + + # ANCHOR could start a simple key. + self.save_possible_simple_key() + + # No simple keys after ANCHOR. + self.allow_simple_key = False + + # Scan and add ANCHOR. + self.tokens.append(self.scan_anchor(AnchorToken)) + + def fetch_tag(self): + + # TAG could start a simple key. + self.save_possible_simple_key() + + # No simple keys after TAG. + self.allow_simple_key = False + + # Scan and add TAG. + self.tokens.append(self.scan_tag()) + + def fetch_literal(self): + self.fetch_block_scalar(style='|') + + def fetch_folded(self): + self.fetch_block_scalar(style='>') + + def fetch_block_scalar(self, style): + + # A simple key may follow a block scalar. + self.allow_simple_key = True + + # Reset possible simple key on the current level. + self.remove_possible_simple_key() + + # Scan and add SCALAR. + self.tokens.append(self.scan_block_scalar(style)) + + def fetch_single(self): + self.fetch_flow_scalar(style='\'') + + def fetch_double(self): + self.fetch_flow_scalar(style='"') + + def fetch_flow_scalar(self, style): + + # A flow scalar could be a simple key. + self.save_possible_simple_key() + + # No simple keys after flow scalars. + self.allow_simple_key = False + + # Scan and add SCALAR. + self.tokens.append(self.scan_flow_scalar(style)) + + def fetch_plain(self): + + # A plain scalar could be a simple key. + self.save_possible_simple_key() + + # No simple keys after plain scalars. But note that `scan_plain` will + # change this flag if the scan is finished at the beginning of the + # line. + self.allow_simple_key = False + + # Scan and add SCALAR. May change `allow_simple_key`. + self.tokens.append(self.scan_plain()) + + # Checkers. + + def check_directive(self): + + # DIRECTIVE: ^ '%' ... + # The '%' indicator is already checked. + if self.column == 0: + return True + + def check_document_start(self): + + # DOCUMENT-START: ^ '---' (' '|'\n') + if self.column == 0: + if self.prefix(3) == '---' \ + and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029': + return True + + def check_document_end(self): + + # DOCUMENT-END: ^ '...' (' '|'\n') + if self.column == 0: + if self.prefix(3) == '...' \ + and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029': + return True + + def check_block_entry(self): + + # BLOCK-ENTRY: '-' (' '|'\n') + return self.peek(1) in '\0 \t\r\n\x85\u2028\u2029' + + def check_key(self): + + # KEY(flow context): '?' + if self.flow_level: + return True + + # KEY(block context): '?' (' '|'\n') + else: + return self.peek(1) in '\0 \t\r\n\x85\u2028\u2029' + + def check_value(self): + + # VALUE(flow context): ':' + if self.flow_level: + return True + + # VALUE(block context): ':' (' '|'\n') + else: + return self.peek(1) in '\0 \t\r\n\x85\u2028\u2029' + + def check_plain(self): + + # A plain scalar may start with any non-space character except: + # '-', '?', ':', ',', '[', ']', '{', '}', + # '#', '&', '*', '!', '|', '>', '\'', '\"', + # '%', '@', '`'. + # + # It may also start with + # '-', '?', ':' + # if it is followed by a non-space character. + # + # Note that we limit the last rule to the block context (except the + # '-' character) because we want the flow context to be space + # independent. + ch = self.peek() + return ch not in '\0 \t\r\n\x85\u2028\u2029-?:,[]{}#&*!|>\'\"%@`' \ + or (self.peek(1) not in '\0 \t\r\n\x85\u2028\u2029' + and (ch == '-' or (not self.flow_level and ch in '?:'))) + + # Scanners. + + def scan_to_next_token(self): + # We ignore spaces, line breaks and comments. + # If we find a line break in the block context, we set the flag + # `allow_simple_key` on. + # The byte order mark is stripped if it's the first character in the + # stream. We do not yet support BOM inside the stream as the + # specification requires. Any such mark will be considered as a part + # of the document. + # + # TODO: We need to make tab handling rules more sane. A good rule is + # Tabs cannot precede tokens + # BLOCK-SEQUENCE-START, BLOCK-MAPPING-START, BLOCK-END, + # KEY(block), VALUE(block), BLOCK-ENTRY + # So the checking code is + # if <TAB>: + # self.allow_simple_keys = False + # We also need to add the check for `allow_simple_keys == True` to + # `unwind_indent` before issuing BLOCK-END. + # Scanners for block, flow, and plain scalars need to be modified. + + if self.index == 0 and self.peek() == '\uFEFF': + self.forward() + found = False + while not found: + while self.peek() == ' ': + self.forward() + if self.peek() == '#': + while self.peek() not in '\0\r\n\x85\u2028\u2029': + self.forward() + if self.scan_line_break(): + if not self.flow_level: + self.allow_simple_key = True + else: + found = True + + def scan_directive(self): + # See the specification for details. + start_mark = self.get_mark() + self.forward() + name = self.scan_directive_name(start_mark) + value = None + if name == 'YAML': + value = self.scan_yaml_directive_value(start_mark) + end_mark = self.get_mark() + elif name == 'TAG': + value = self.scan_tag_directive_value(start_mark) + end_mark = self.get_mark() + else: + end_mark = self.get_mark() + while self.peek() not in '\0\r\n\x85\u2028\u2029': + self.forward() + self.scan_directive_ignored_line(start_mark) + return DirectiveToken(name, value, start_mark, end_mark) + + def scan_directive_name(self, start_mark): + # See the specification for details. + length = 0 + ch = self.peek(length) + while '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-_': + length += 1 + ch = self.peek(length) + if not length: + raise ScannerError("while scanning a directive", start_mark, + "expected alphabetic or numeric character, but found %r" + % ch, self.get_mark()) + value = self.prefix(length) + self.forward(length) + ch = self.peek() + if ch not in '\0 \r\n\x85\u2028\u2029': + raise ScannerError("while scanning a directive", start_mark, + "expected alphabetic or numeric character, but found %r" + % ch, self.get_mark()) + return value + + def scan_yaml_directive_value(self, start_mark): + # See the specification for details. + while self.peek() == ' ': + self.forward() + major = self.scan_yaml_directive_number(start_mark) + if self.peek() != '.': + raise ScannerError("while scanning a directive", start_mark, + "expected a digit or '.', but found %r" % self.peek(), + self.get_mark()) + self.forward() + minor = self.scan_yaml_directive_number(start_mark) + if self.peek() not in '\0 \r\n\x85\u2028\u2029': + raise ScannerError("while scanning a directive", start_mark, + "expected a digit or ' ', but found %r" % self.peek(), + self.get_mark()) + return (major, minor) + + def scan_yaml_directive_number(self, start_mark): + # See the specification for details. + ch = self.peek() + if not ('0' <= ch <= '9'): + raise ScannerError("while scanning a directive", start_mark, + "expected a digit, but found %r" % ch, self.get_mark()) + length = 0 + while '0' <= self.peek(length) <= '9': + length += 1 + value = int(self.prefix(length)) + self.forward(length) + return value + + def scan_tag_directive_value(self, start_mark): + # See the specification for details. + while self.peek() == ' ': + self.forward() + handle = self.scan_tag_directive_handle(start_mark) + while self.peek() == ' ': + self.forward() + prefix = self.scan_tag_directive_prefix(start_mark) + return (handle, prefix) + + def scan_tag_directive_handle(self, start_mark): + # See the specification for details. + value = self.scan_tag_handle('directive', start_mark) + ch = self.peek() + if ch != ' ': + raise ScannerError("while scanning a directive", start_mark, + "expected ' ', but found %r" % ch, self.get_mark()) + return value + + def scan_tag_directive_prefix(self, start_mark): + # See the specification for details. + value = self.scan_tag_uri('directive', start_mark) + ch = self.peek() + if ch not in '\0 \r\n\x85\u2028\u2029': + raise ScannerError("while scanning a directive", start_mark, + "expected ' ', but found %r" % ch, self.get_mark()) + return value + + def scan_directive_ignored_line(self, start_mark): + # See the specification for details. + while self.peek() == ' ': + self.forward() + if self.peek() == '#': + while self.peek() not in '\0\r\n\x85\u2028\u2029': + self.forward() + ch = self.peek() + if ch not in '\0\r\n\x85\u2028\u2029': + raise ScannerError("while scanning a directive", start_mark, + "expected a comment or a line break, but found %r" + % ch, self.get_mark()) + self.scan_line_break() + + def scan_anchor(self, TokenClass): + # The specification does not restrict characters for anchors and + # aliases. This may lead to problems, for instance, the document: + # [ *alias, value ] + # can be interpreted in two ways, as + # [ "value" ] + # and + # [ *alias , "value" ] + # Therefore we restrict aliases to numbers and ASCII letters. + start_mark = self.get_mark() + indicator = self.peek() + if indicator == '*': + name = 'alias' + else: + name = 'anchor' + self.forward() + length = 0 + ch = self.peek(length) + while '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-_': + length += 1 + ch = self.peek(length) + if not length: + raise ScannerError("while scanning an %s" % name, start_mark, + "expected alphabetic or numeric character, but found %r" + % ch, self.get_mark()) + value = self.prefix(length) + self.forward(length) + ch = self.peek() + if ch not in '\0 \t\r\n\x85\u2028\u2029?:,]}%@`': + raise ScannerError("while scanning an %s" % name, start_mark, + "expected alphabetic or numeric character, but found %r" + % ch, self.get_mark()) + end_mark = self.get_mark() + return TokenClass(value, start_mark, end_mark) + + def scan_tag(self): + # See the specification for details. + start_mark = self.get_mark() + ch = self.peek(1) + if ch == '<': + handle = None + self.forward(2) + suffix = self.scan_tag_uri('tag', start_mark) + if self.peek() != '>': + raise ScannerError("while parsing a tag", start_mark, + "expected '>', but found %r" % self.peek(), + self.get_mark()) + self.forward() + elif ch in '\0 \t\r\n\x85\u2028\u2029': + handle = None + suffix = '!' + self.forward() + else: + length = 1 + use_handle = False + while ch not in '\0 \r\n\x85\u2028\u2029': + if ch == '!': + use_handle = True + break + length += 1 + ch = self.peek(length) + handle = '!' + if use_handle: + handle = self.scan_tag_handle('tag', start_mark) + else: + handle = '!' + self.forward() + suffix = self.scan_tag_uri('tag', start_mark) + ch = self.peek() + if ch not in '\0 \r\n\x85\u2028\u2029': + raise ScannerError("while scanning a tag", start_mark, + "expected ' ', but found %r" % ch, self.get_mark()) + value = (handle, suffix) + end_mark = self.get_mark() + return TagToken(value, start_mark, end_mark) + + def scan_block_scalar(self, style): + # See the specification for details. + + if style == '>': + folded = True + else: + folded = False + + chunks = [] + start_mark = self.get_mark() + + # Scan the header. + self.forward() + chomping, increment = self.scan_block_scalar_indicators(start_mark) + self.scan_block_scalar_ignored_line(start_mark) + + # Determine the indentation level and go to the first non-empty line. + min_indent = self.indent+1 + if min_indent < 1: + min_indent = 1 + if increment is None: + breaks, max_indent, end_mark = self.scan_block_scalar_indentation() + indent = max(min_indent, max_indent) + else: + indent = min_indent+increment-1 + breaks, end_mark = self.scan_block_scalar_breaks(indent) + line_break = '' + + # Scan the inner part of the block scalar. + while self.column == indent and self.peek() != '\0': + chunks.extend(breaks) + leading_non_space = self.peek() not in ' \t' + length = 0 + while self.peek(length) not in '\0\r\n\x85\u2028\u2029': + length += 1 + chunks.append(self.prefix(length)) + self.forward(length) + line_break = self.scan_line_break() + breaks, end_mark = self.scan_block_scalar_breaks(indent) + if self.column == indent and self.peek() != '\0': + + # Unfortunately, folding rules are ambiguous. + # + # This is the folding according to the specification: + + if folded and line_break == '\n' \ + and leading_non_space and self.peek() not in ' \t': + if not breaks: + chunks.append(' ') + else: + chunks.append(line_break) + + # This is Clark Evans's interpretation (also in the spec + # examples): + # + #if folded and line_break == '\n': + # if not breaks: + # if self.peek() not in ' \t': + # chunks.append(' ') + # else: + # chunks.append(line_break) + #else: + # chunks.append(line_break) + else: + break + + # Chomp the tail. + if chomping is not False: + chunks.append(line_break) + if chomping is True: + chunks.extend(breaks) + + # We are done. + return ScalarToken(''.join(chunks), False, start_mark, end_mark, + style) + + def scan_block_scalar_indicators(self, start_mark): + # See the specification for details. + chomping = None + increment = None + ch = self.peek() + if ch in '+-': + if ch == '+': + chomping = True + else: + chomping = False + self.forward() + ch = self.peek() + if ch in '0123456789': + increment = int(ch) + if increment == 0: + raise ScannerError("while scanning a block scalar", start_mark, + "expected indentation indicator in the range 1-9, but found 0", + self.get_mark()) + self.forward() + elif ch in '0123456789': + increment = int(ch) + if increment == 0: + raise ScannerError("while scanning a block scalar", start_mark, + "expected indentation indicator in the range 1-9, but found 0", + self.get_mark()) + self.forward() + ch = self.peek() + if ch in '+-': + if ch == '+': + chomping = True + else: + chomping = False + self.forward() + ch = self.peek() + if ch not in '\0 \r\n\x85\u2028\u2029': + raise ScannerError("while scanning a block scalar", start_mark, + "expected chomping or indentation indicators, but found %r" + % ch, self.get_mark()) + return chomping, increment + + def scan_block_scalar_ignored_line(self, start_mark): + # See the specification for details. + while self.peek() == ' ': + self.forward() + if self.peek() == '#': + while self.peek() not in '\0\r\n\x85\u2028\u2029': + self.forward() + ch = self.peek() + if ch not in '\0\r\n\x85\u2028\u2029': + raise ScannerError("while scanning a block scalar", start_mark, + "expected a comment or a line break, but found %r" % ch, + self.get_mark()) + self.scan_line_break() + + def scan_block_scalar_indentation(self): + # See the specification for details. + chunks = [] + max_indent = 0 + end_mark = self.get_mark() + while self.peek() in ' \r\n\x85\u2028\u2029': + if self.peek() != ' ': + chunks.append(self.scan_line_break()) + end_mark = self.get_mark() + else: + self.forward() + if self.column > max_indent: + max_indent = self.column + return chunks, max_indent, end_mark + + def scan_block_scalar_breaks(self, indent): + # See the specification for details. + chunks = [] + end_mark = self.get_mark() + while self.column < indent and self.peek() == ' ': + self.forward() + while self.peek() in '\r\n\x85\u2028\u2029': + chunks.append(self.scan_line_break()) + end_mark = self.get_mark() + while self.column < indent and self.peek() == ' ': + self.forward() + return chunks, end_mark + + def scan_flow_scalar(self, style): + # See the specification for details. + # Note that we loose indentation rules for quoted scalars. Quoted + # scalars don't need to adhere indentation because " and ' clearly + # mark the beginning and the end of them. Therefore we are less + # restrictive then the specification requires. We only need to check + # that document separators are not included in scalars. + if style == '"': + double = True + else: + double = False + chunks = [] + start_mark = self.get_mark() + quote = self.peek() + self.forward() + chunks.extend(self.scan_flow_scalar_non_spaces(double, start_mark)) + while self.peek() != quote: + chunks.extend(self.scan_flow_scalar_spaces(double, start_mark)) + chunks.extend(self.scan_flow_scalar_non_spaces(double, start_mark)) + self.forward() + end_mark = self.get_mark() + return ScalarToken(''.join(chunks), False, start_mark, end_mark, + style) + + ESCAPE_REPLACEMENTS = { + '0': '\0', + 'a': '\x07', + 'b': '\x08', + 't': '\x09', + '\t': '\x09', + 'n': '\x0A', + 'v': '\x0B', + 'f': '\x0C', + 'r': '\x0D', + 'e': '\x1B', + ' ': '\x20', + '\"': '\"', + '\\': '\\', + '/': '/', + 'N': '\x85', + '_': '\xA0', + 'L': '\u2028', + 'P': '\u2029', + } + + ESCAPE_CODES = { + 'x': 2, + 'u': 4, + 'U': 8, + } + + def scan_flow_scalar_non_spaces(self, double, start_mark): + # See the specification for details. + chunks = [] + while True: + length = 0 + while self.peek(length) not in '\'\"\\\0 \t\r\n\x85\u2028\u2029': + length += 1 + if length: + chunks.append(self.prefix(length)) + self.forward(length) + ch = self.peek() + if not double and ch == '\'' and self.peek(1) == '\'': + chunks.append('\'') + self.forward(2) + elif (double and ch == '\'') or (not double and ch in '\"\\'): + chunks.append(ch) + self.forward() + elif double and ch == '\\': + self.forward() + ch = self.peek() + if ch in self.ESCAPE_REPLACEMENTS: + chunks.append(self.ESCAPE_REPLACEMENTS[ch]) + self.forward() + elif ch in self.ESCAPE_CODES: + length = self.ESCAPE_CODES[ch] + self.forward() + for k in range(length): + if self.peek(k) not in '0123456789ABCDEFabcdef': + raise ScannerError("while scanning a double-quoted scalar", start_mark, + "expected escape sequence of %d hexdecimal numbers, but found %r" % + (length, self.peek(k)), self.get_mark()) + code = int(self.prefix(length), 16) + chunks.append(chr(code)) + self.forward(length) + elif ch in '\r\n\x85\u2028\u2029': + self.scan_line_break() + chunks.extend(self.scan_flow_scalar_breaks(double, start_mark)) + else: + raise ScannerError("while scanning a double-quoted scalar", start_mark, + "found unknown escape character %r" % ch, self.get_mark()) + else: + return chunks + + def scan_flow_scalar_spaces(self, double, start_mark): + # See the specification for details. + chunks = [] + length = 0 + while self.peek(length) in ' \t': + length += 1 + whitespaces = self.prefix(length) + self.forward(length) + ch = self.peek() + if ch == '\0': + raise ScannerError("while scanning a quoted scalar", start_mark, + "found unexpected end of stream", self.get_mark()) + elif ch in '\r\n\x85\u2028\u2029': + line_break = self.scan_line_break() + breaks = self.scan_flow_scalar_breaks(double, start_mark) + if line_break != '\n': + chunks.append(line_break) + elif not breaks: + chunks.append(' ') + chunks.extend(breaks) + else: + chunks.append(whitespaces) + return chunks + + def scan_flow_scalar_breaks(self, double, start_mark): + # See the specification for details. + chunks = [] + while True: + # Instead of checking indentation, we check for document + # separators. + prefix = self.prefix(3) + if (prefix == '---' or prefix == '...') \ + and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029': + raise ScannerError("while scanning a quoted scalar", start_mark, + "found unexpected document separator", self.get_mark()) + while self.peek() in ' \t': + self.forward() + if self.peek() in '\r\n\x85\u2028\u2029': + chunks.append(self.scan_line_break()) + else: + return chunks + + def scan_plain(self): + # See the specification for details. + # We add an additional restriction for the flow context: + # plain scalars in the flow context cannot contain ',' or '?'. + # We also keep track of the `allow_simple_key` flag here. + # Indentation rules are loosed for the flow context. + chunks = [] + start_mark = self.get_mark() + end_mark = start_mark + indent = self.indent+1 + # We allow zero indentation for scalars, but then we need to check for + # document separators at the beginning of the line. + #if indent == 0: + # indent = 1 + spaces = [] + while True: + length = 0 + if self.peek() == '#': + break + while True: + ch = self.peek(length) + if ch in '\0 \t\r\n\x85\u2028\u2029' \ + or (ch == ':' and + self.peek(length+1) in '\0 \t\r\n\x85\u2028\u2029' + + (u',[]{}' if self.flow_level else u''))\ + or (self.flow_level and ch in ',?[]{}'): + break + length += 1 + if length == 0: + break + self.allow_simple_key = False + chunks.extend(spaces) + chunks.append(self.prefix(length)) + self.forward(length) + end_mark = self.get_mark() + spaces = self.scan_plain_spaces(indent, start_mark) + if not spaces or self.peek() == '#' \ + or (not self.flow_level and self.column < indent): + break + return ScalarToken(''.join(chunks), True, start_mark, end_mark) + + def scan_plain_spaces(self, indent, start_mark): + # See the specification for details. + # The specification is really confusing about tabs in plain scalars. + # We just forbid them completely. Do not use tabs in YAML! + chunks = [] + length = 0 + while self.peek(length) in ' ': + length += 1 + whitespaces = self.prefix(length) + self.forward(length) + ch = self.peek() + if ch in '\r\n\x85\u2028\u2029': + line_break = self.scan_line_break() + self.allow_simple_key = True + prefix = self.prefix(3) + if (prefix == '---' or prefix == '...') \ + and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029': + return + breaks = [] + while self.peek() in ' \r\n\x85\u2028\u2029': + if self.peek() == ' ': + self.forward() + else: + breaks.append(self.scan_line_break()) + prefix = self.prefix(3) + if (prefix == '---' or prefix == '...') \ + and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029': + return + if line_break != '\n': + chunks.append(line_break) + elif not breaks: + chunks.append(' ') + chunks.extend(breaks) + elif whitespaces: + chunks.append(whitespaces) + return chunks + + def scan_tag_handle(self, name, start_mark): + # See the specification for details. + # For some strange reasons, the specification does not allow '_' in + # tag handles. I have allowed it anyway. + ch = self.peek() + if ch != '!': + raise ScannerError("while scanning a %s" % name, start_mark, + "expected '!', but found %r" % ch, self.get_mark()) + length = 1 + ch = self.peek(length) + if ch != ' ': + while '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-_': + length += 1 + ch = self.peek(length) + if ch != '!': + self.forward(length) + raise ScannerError("while scanning a %s" % name, start_mark, + "expected '!', but found %r" % ch, self.get_mark()) + length += 1 + value = self.prefix(length) + self.forward(length) + return value + + def scan_tag_uri(self, name, start_mark): + # See the specification for details. + # Note: we do not check if URI is well-formed. + chunks = [] + length = 0 + ch = self.peek(length) + while '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-;/?:@&=+$,_.!~*\'()[]%': + if ch == '%': + chunks.append(self.prefix(length)) + self.forward(length) + length = 0 + chunks.append(self.scan_uri_escapes(name, start_mark)) + else: + length += 1 + ch = self.peek(length) + if length: + chunks.append(self.prefix(length)) + self.forward(length) + length = 0 + if not chunks: + raise ScannerError("while parsing a %s" % name, start_mark, + "expected URI, but found %r" % ch, self.get_mark()) + return ''.join(chunks) + + def scan_uri_escapes(self, name, start_mark): + # See the specification for details. + codes = [] + mark = self.get_mark() + while self.peek() == '%': + self.forward() + for k in range(2): + if self.peek(k) not in '0123456789ABCDEFabcdef': + raise ScannerError("while scanning a %s" % name, start_mark, + "expected URI escape sequence of 2 hexdecimal numbers, but found %r" + % self.peek(k), self.get_mark()) + codes.append(int(self.prefix(2), 16)) + self.forward(2) + try: + value = bytes(codes).decode('utf-8') + except UnicodeDecodeError as exc: + raise ScannerError("while scanning a %s" % name, start_mark, str(exc), mark) + return value + + def scan_line_break(self): + # Transforms: + # '\r\n' : '\n' + # '\r' : '\n' + # '\n' : '\n' + # '\x85' : '\n' + # '\u2028' : '\u2028' + # '\u2029 : '\u2029' + # default : '' + ch = self.peek() + if ch in '\r\n\x85': + if self.prefix(2) == '\r\n': + self.forward(2) + else: + self.forward() + return '\n' + elif ch in '\u2028\u2029': + self.forward() + return ch + return '' diff --git a/pipenv/patched/yaml3/serializer.py b/pipenv/patched/yaml3/serializer.py new file mode 100644 index 00000000..fe911e67 --- /dev/null +++ b/pipenv/patched/yaml3/serializer.py @@ -0,0 +1,111 @@ + +__all__ = ['Serializer', 'SerializerError'] + +from .error import YAMLError +from .events import * +from .nodes import * + +class SerializerError(YAMLError): + pass + +class Serializer: + + ANCHOR_TEMPLATE = 'id%03d' + + def __init__(self, encoding=None, + explicit_start=None, explicit_end=None, version=None, tags=None): + self.use_encoding = encoding + self.use_explicit_start = explicit_start + self.use_explicit_end = explicit_end + self.use_version = version + self.use_tags = tags + self.serialized_nodes = {} + self.anchors = {} + self.last_anchor_id = 0 + self.closed = None + + def open(self): + if self.closed is None: + self.emit(StreamStartEvent(encoding=self.use_encoding)) + self.closed = False + elif self.closed: + raise SerializerError("serializer is closed") + else: + raise SerializerError("serializer is already opened") + + def close(self): + if self.closed is None: + raise SerializerError("serializer is not opened") + elif not self.closed: + self.emit(StreamEndEvent()) + self.closed = True + + #def __del__(self): + # self.close() + + def serialize(self, node): + if self.closed is None: + raise SerializerError("serializer is not opened") + elif self.closed: + raise SerializerError("serializer is closed") + self.emit(DocumentStartEvent(explicit=self.use_explicit_start, + version=self.use_version, tags=self.use_tags)) + self.anchor_node(node) + self.serialize_node(node, None, None) + self.emit(DocumentEndEvent(explicit=self.use_explicit_end)) + self.serialized_nodes = {} + self.anchors = {} + self.last_anchor_id = 0 + + def anchor_node(self, node): + if node in self.anchors: + if self.anchors[node] is None: + self.anchors[node] = self.generate_anchor(node) + else: + self.anchors[node] = None + if isinstance(node, SequenceNode): + for item in node.value: + self.anchor_node(item) + elif isinstance(node, MappingNode): + for key, value in node.value: + self.anchor_node(key) + self.anchor_node(value) + + def generate_anchor(self, node): + self.last_anchor_id += 1 + return self.ANCHOR_TEMPLATE % self.last_anchor_id + + def serialize_node(self, node, parent, index): + alias = self.anchors[node] + if node in self.serialized_nodes: + self.emit(AliasEvent(alias)) + else: + self.serialized_nodes[node] = True + self.descend_resolver(parent, index) + if isinstance(node, ScalarNode): + detected_tag = self.resolve(ScalarNode, node.value, (True, False)) + default_tag = self.resolve(ScalarNode, node.value, (False, True)) + implicit = (node.tag == detected_tag), (node.tag == default_tag) + self.emit(ScalarEvent(alias, node.tag, implicit, node.value, + style=node.style)) + elif isinstance(node, SequenceNode): + implicit = (node.tag + == self.resolve(SequenceNode, node.value, True)) + self.emit(SequenceStartEvent(alias, node.tag, implicit, + flow_style=node.flow_style)) + index = 0 + for item in node.value: + self.serialize_node(item, node, index) + index += 1 + self.emit(SequenceEndEvent()) + elif isinstance(node, MappingNode): + implicit = (node.tag + == self.resolve(MappingNode, node.value, True)) + self.emit(MappingStartEvent(alias, node.tag, implicit, + flow_style=node.flow_style)) + for key, value in node.value: + self.serialize_node(key, node, None) + self.serialize_node(value, node, key) + self.emit(MappingEndEvent()) + self.ascend_resolver() + diff --git a/pipenv/patched/yaml3/tokens.py b/pipenv/patched/yaml3/tokens.py new file mode 100644 index 00000000..4d0b48a3 --- /dev/null +++ b/pipenv/patched/yaml3/tokens.py @@ -0,0 +1,104 @@ + +class Token(object): + def __init__(self, start_mark, end_mark): + self.start_mark = start_mark + self.end_mark = end_mark + def __repr__(self): + attributes = [key for key in self.__dict__ + if not key.endswith('_mark')] + attributes.sort() + arguments = ', '.join(['%s=%r' % (key, getattr(self, key)) + for key in attributes]) + return '%s(%s)' % (self.__class__.__name__, arguments) + +#class BOMToken(Token): +# id = '<byte order mark>' + +class DirectiveToken(Token): + id = '<directive>' + def __init__(self, name, value, start_mark, end_mark): + self.name = name + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + +class DocumentStartToken(Token): + id = '<document start>' + +class DocumentEndToken(Token): + id = '<document end>' + +class StreamStartToken(Token): + id = '<stream start>' + def __init__(self, start_mark=None, end_mark=None, + encoding=None): + self.start_mark = start_mark + self.end_mark = end_mark + self.encoding = encoding + +class StreamEndToken(Token): + id = '<stream end>' + +class BlockSequenceStartToken(Token): + id = '<block sequence start>' + +class BlockMappingStartToken(Token): + id = '<block mapping start>' + +class BlockEndToken(Token): + id = '<block end>' + +class FlowSequenceStartToken(Token): + id = '[' + +class FlowMappingStartToken(Token): + id = '{' + +class FlowSequenceEndToken(Token): + id = ']' + +class FlowMappingEndToken(Token): + id = '}' + +class KeyToken(Token): + id = '?' + +class ValueToken(Token): + id = ':' + +class BlockEntryToken(Token): + id = '-' + +class FlowEntryToken(Token): + id = ',' + +class AliasToken(Token): + id = '<alias>' + def __init__(self, value, start_mark, end_mark): + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + +class AnchorToken(Token): + id = '<anchor>' + def __init__(self, value, start_mark, end_mark): + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + +class TagToken(Token): + id = '<tag>' + def __init__(self, value, start_mark, end_mark): + self.value = value + self.start_mark = start_mark + self.end_mark = end_mark + +class ScalarToken(Token): + id = '<scalar>' + def __init__(self, value, plain, start_mark, end_mark, style=None): + self.value = value + self.plain = plain + self.start_mark = start_mark + self.end_mark = end_mark + self.style = style + diff --git a/pipenv/pep508checker.py b/pipenv/pep508checker.py index e875a6d1..a771dec0 100644 --- a/pipenv/pep508checker.py +++ b/pipenv/pep508checker.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import json import os import platform @@ -31,7 +30,7 @@ lookup = { "platform_release": platform.release(), "platform_system": platform.system(), "platform_version": platform.version(), - "python_version": platform.python_version()[:3], + "python_version": ".".join(platform.python_version().split(".")[:2]), "python_full_version": platform.python_version(), "implementation_name": implementation_name, "implementation_version": implementation_version, diff --git a/pipenv/pipenv.1 b/pipenv/pipenv.1 index 7dd63f58..fe4f266b 100644 --- a/pipenv/pipenv.1 +++ b/pipenv/pipenv.1 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "PIPENV" "1" "Jul 14, 2019" "2018.11.27.dev0" "pipenv" +.TH "PIPENV" "1" "Jan 08, 2022" "2022.1.8" "pipenv" .SH NAME pipenv \- pipenv Documentation . @@ -30,7 +30,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -\fI\%\fP\fI\%\fP\fI\%\fP\fI\%\fP +\fI\%\fP\fI\%\fP\fI\%\fP .sp .ce ---- @@ -65,13 +65,13 @@ Streamline development workflow by loading \fB\&.env\fP files. You can quickly play with Pipenv right in your browser: \fI\%Try in browser\fP.SH INSTALL PIPENV TODAY! .sp -If you\(aqre on MacOS, you can install Pipenv easily with Homebrew. You can also use Linuxbrew on Linux using the same command: +If you already have Python and pip, you can easily install Pipenv into your home directory: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C -$ brew install pipenv +$ pip install \-\-user pipenv .ft P .fi .UNINDENT @@ -89,7 +89,19 @@ $ sudo dnf install pipenv .UNINDENT .UNINDENT .sp -Otherwise, refer to the installing\-pipenv chapter for instructions. +It\(aqs possible to install Pipenv with Homebrew on MacOS, or with Linuxbrew on Linux systems. However, \fBthis is now discouraged\fP, because updates to the brewed Python distribution will break Pipenv, and perhaps all virtual environments managed by it. You\(aqll then need to re\-install Pipenv at least. If you want to give it a try despite this warning, use: +.INDENT 0.0 +.INDENT 3.5 +.sp +.nf +.ft C +$ brew install pipenv +.ft P +.fi +.UNINDENT +.UNINDENT +.sp +More detailed installation instructions can be found in the installing\-pipenv chapter. .sp ✨🍰✨ .SS Pipenv & Virtual Environments @@ -178,44 +190,39 @@ Homebrew/Linuxbrew installer takes care of pip for you. .SS ☤ Installing Pipenv .sp Pipenv is a dependency manager for Python projects. If you\(aqre familiar -with Node.js\(aq \fI\%npm\fP or Ruby\(aqs \fI\%bundler\fP, it is similar in spirit to those +with Node.js\(aqs \fI\%npm\fP or Ruby\(aqs \fI\%bundler\fP, it is similar in spirit to those tools. While pip can install Python packages, Pipenv is recommended as it\(aqs a higher\-level tool that simplifies dependency management for common use cases. -.SS ☤ Homebrew Installation of Pipenv +.SS ☤ Isolated Installation of Pipenv with Pipx .sp -\fI\%Homebrew\fP is a popular open\-source package management system for macOS. For Linux users, \fI\%Linuxbrew\fP is a Linux port of that. -.sp -Installing pipenv via Homebrew or Linuxbrew will keep pipenv and all of its dependencies in -an isolated virtual environment so it doesn\(aqt interfere with the rest of your -Python installation. -.sp -Once you have installed Homebrew or Linuxbrew simply run: +\fI\%Pipx\fP is a tool to help you install and run end\-user applications written in Python. It installs applications +into an isolated and clean environment on their own. To install pipx, just run: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C -$ brew install pipenv +$ pip install \-\-user pipx .ft P .fi .UNINDENT .UNINDENT .sp -To upgrade pipenv at any time: +Once you have \fBpipx\fP ready on your system, continue to install Pipenv: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C -$ brew upgrade pipenv +$ pipx install pipenv .ft P .fi .UNINDENT .UNINDENT .SS ☤ Pragmatic Installation of Pipenv .sp -If you have a working installation of pip, and maintain certain "toolchain" type Python modules as global utilities in your user environment, pip \fI\%user installs\fP allow for installation into your home directory. Note that due to interaction between dependencies, you should limit tools installed in this way to basic building blocks for a Python workflow like virtualenv, pipenv, tox, and similar software. +If you have a working installation of pip, and maintain certain "tool\-chain" type Python modules as global utilities in your user environment, pip \fI\%user installs\fP allow for installation into your home directory. Note that due to interaction between dependencies, you should limit tools installed in this way to basic building blocks for a Python workflow like virtualenv, pipenv, tox, and similar software. .sp To install: .INDENT 0.0 @@ -280,6 +287,45 @@ $ curl https://raw.githubusercontent.com/pypa/pipenv/master/get\-pipenv.py | pyt .fi .UNINDENT .UNINDENT +.SS ☤ Homebrew Installation of Pipenv(Discouraged) +.sp +\fI\%Homebrew\fP is a popular open\-source package management system for macOS. For Linux users, \fI\%Linuxbrew\fP is a Linux port of that. +.sp +Installing pipenv via Homebrew or Linuxbrew will keep pipenv and all of its dependencies in +an isolated virtual environment so it doesn\(aqt interfere with the rest of your +Python installation. +.sp +Once you have installed Homebrew or Linuxbrew simply run: +.INDENT 0.0 +.INDENT 3.5 +.sp +.nf +.ft C +$ brew install pipenv +.ft P +.fi +.UNINDENT +.UNINDENT +.sp +To upgrade pipenv at any time: +.INDENT 0.0 +.INDENT 3.5 +.sp +.nf +.ft C +$ brew upgrade pipenv +.ft P +.fi +.UNINDENT +.UNINDENT +.sp +\fBNOTE:\fP +.INDENT 0.0 +.INDENT 3.5 +Homebrew installation is discouraged because each time the Homebrew Python is upgraded, which Pipenv depends on, +users have to re\-install Pipenv, and perhaps all virtual environments managed by it. +.UNINDENT +.UNINDENT .SS ☤ Installing packages for your project .sp Pipenv manages dependencies on a per\-project basis. To install packages, @@ -396,6 +442,813 @@ You might want to set \fBexport PIPENV_VENV_IN_PROJECT=1\fP in your .bashrc/.zsh .sp Congratulations, you now know how to install and use Python packages! ✨ 🍰 ✨ .SS Release and Version History +.SS 2022.1.8 (2022\-01\-08) +.SS Bug Fixes +.INDENT 0.0 +.IP \(bu 2 +Remove the extra parentheses around the venv prompt. \fI\%#4877\fP +.IP \(bu 2 +Fix a bug of installation fails when extra index url is given. \fI\%#4881\fP +.IP \(bu 2 +Fix regression where lockfiles would only include the hashes for releases for the platform generating the lockfile \fI\%#4885\fP +.IP \(bu 2 +Fix the index parsing to reject illegal requirements.txt. \fI\%#4899\fP +.UNINDENT +.SS 2021.11.23 (2021\-11\-23) +.SS Bug Fixes +.INDENT 0.0 +.IP \(bu 2 +Update \fBcharset\-normalizer\fP from \fB2.0.3\fP to \fB2.0.7\fP, this fixes an import error on Python 3.6. \fI\%#4865\fP +.IP \(bu 2 +Fix a bug of deleting a virtualenv that is not managed by Pipenv. \fI\%#4867\fP +.IP \(bu 2 +Fix a bug that source is not added to \fBPipfile\fP when index url is given with \fBpipenv install\fP\&. \fI\%#4873\fP +.UNINDENT +.SS 2021.11.15 (2021\-11\-15) +.SS Bug Fixes +.INDENT 0.0 +.IP \(bu 2 +Return an empty dict when \fBPIPENV_DONT_LOAD_ENV\fP is set. \fI\%#4851\fP +.IP \(bu 2 +Don\(aqt use \fBsys.executable\fP when inside an activated venv. \fI\%#4852\fP +.UNINDENT +.SS Vendored Libraries +.INDENT 0.0 +.IP \(bu 2 +Drop the vendored \fBjinja2\fP dependency as it is not needed any more. \fI\%#4858\fP +.IP \(bu 2 +Update \fBclick\fP from \fB8.0.1\fP to \fB8.0.3\fP, to fix a problem with bash completion. \fI\%#4860\fP +.IP \(bu 2 +Drop unused vendor \fBchardet\fP\&. \fI\%#4862\fP +.UNINDENT +.SS Improved Documentation +.INDENT 0.0 +.IP \(bu 2 +Fix the documentation to reflect the fact that special characters must be percent\-encoded in the URL. \fI\%#4856\fP +.UNINDENT +.SS 2021.11.9 (2021\-11\-09) +.SS Features & Improvements +.INDENT 0.0 +.IP \(bu 2 +Replace \fBclick\-completion\fP with \fBclick\fP\(aqs own completion implementation. \fI\%#4786\fP +.UNINDENT +.SS Bug Fixes +.INDENT 0.0 +.IP \(bu 2 +Fix a bug that \fBpipenv run\fP doesn\(aqt set environment variables correctly. \fI\%#4831\fP +.IP \(bu 2 +Fix a bug that certifi can\(aqt be loaded within \fBnotpip\fP\(aqs vendor library. This makes several objects of \fBpip\fP fail to be imported. \fI\%#4833\fP +.IP \(bu 2 +Fix a bug that \fB3.10.0\fP can be found be python finder. \fI\%#4837\fP +.UNINDENT +.SS Vendored Libraries +.INDENT 0.0 +.IP \(bu 2 +Update \fBpythonfinder\fP from \fB1.2.8\fP to \fB1.2.9\fP\&. \fI\%#4837\fP +.UNINDENT +.SS 2021.11.5.post0 (2021\-11\-05) +.SS Bug Fixes +.INDENT 0.0 +.IP \(bu 2 +Fix a regression that \fBpipenv shell\fP fails to start a subshell. \fI\%#4828\fP +.IP \(bu 2 +Fix a regression that \fBpip_shims\fP object isn\(aqt imported correctly. \fI\%#4829\fP +.UNINDENT +.SS 2021.11.5 (2021\-11\-05) +.SS Features & Improvements +.INDENT 0.0 +.IP \(bu 2 +Avoid sharing states but create project objects on demand. So that most integration test cases are able to switch to a in\-process execution method. \fI\%#4757\fP +.IP \(bu 2 +Shell\-quote \fBpip\fP commands when logging. \fI\%#4760\fP +.UNINDENT +.SS Bug Fixes +.INDENT 0.0 +.IP \(bu 2 +Ignore empty .venv in rood dir and create project name base virtual environment \fI\%#4790\fP +.UNINDENT +.SS Vendored Libraries +.INDENT 0.0 +.IP \(bu 2 +Update vendored dependencies +\- \fBattrs\fP from \fB20.3.0\fP to \fB21.2.0\fP +\- \fBcerberus\fP from \fB1.3.2\fP to \fB1.3.4\fP +\- \fBcertifi\fP from \fB2020.11.8\fP to \fB2021.5.30\fP +\- \fBchardet\fP from \fB3.0.4\fP to \fB4.0.0\fP +\- \fBclick\fP from \fB7.1.2\fP to \fB8.0.1\fP +\- \fBdistlib\fP from \fB0.3.1\fP to \fB0.3.2\fP +\- \fBidna\fP from \fB2.10\fP to \fB3.2\fP +\- \fBimportlib\-metadata\fP from \fB2.0.0\fP to \fB4.6.1\fP +\- \fBimportlib\-resources\fP from \fB3.3.0\fP to \fB5.2.0\fP +\- \fBjinja2\fP from \fB2.11.2\fP to \fB3.0.1\fP +\- \fBmarkupsafe\fP from \fB1.1.1\fP to \fB2.0.1\fP +\- \fBmore\-itertools\fP from \fB5.0.0\fP to \fB8.8.0\fP +\- \fBpackaging\fP from \fB20.8\fP to \fB21.0\fP +\- \fBpep517\fP from \fB0.9.1\fP to \fB0.11.0\fP +\- \fBpipdeptree\fP from \fB1.0.0\fP to \fB2.0.0\fP +\- \fBptyprocess\fP from \fB0.6.0\fP to \fB0.7.0\fP +\- \fBpython\-dateutil\fP from \fB2.8.1\fP to \fB2.8.2\fP +\- \fBpython\-dotenv\fP from \fB0.15.0\fP to \fB0.19.0\fP +\- \fBpythonfinder\fP from \fB1.2.5\fP to \fB1.2.8\fP +\- \fBrequests\fP from \fB2.25.0\fP to \fB2.26.0\fP +\- \fBshellingham\fP from \fB1.3.2\fP to \fB1.4.0\fP +\- \fBsix\fP from \fB1.15.0\fP to \fB1.16.0\fP +\- \fBtomlkit\fP from \fB0.7.0\fP to \fB0.7.2\fP +\- \fBurllib3\fP from \fB1.26.1\fP to \fB1.26.6\fP +\- \fBzipp\fP from \fB1.2.0\fP to \fB3.5.0\fP +.sp +Add new vendored dependencies +\- \fBcharset\-normalizer 2.0.3\fP +\- \fBtermcolor 1.1.0\fP +\- \fBtomli 1.1.0\fP +\- \fBwheel 0.36.2\fP \fI\%#4747\fP +.IP \(bu 2 +Drop the dependencies for Python 2.7 compatibility purpose. \fI\%#4751\fP +.IP \(bu 2 +Switch the dependency resolver from \fBpip\-tools\fP to \fIpip\fP\&. +.sp +Update vendor libraries: +\- Update \fBrequirementslib\fP from \fB1.5.16\fP to \fB1.6.1\fP +\- Update \fBpip\-shims\fP from \fB0.5.6\fP to \fB0.6.0\fP +\- New vendor \fBplatformdirs 2.4.0\fP \fI\%#4759\fP +.UNINDENT +.SS Improved Documentation +.INDENT 0.0 +.IP \(bu 2 +remove prefixes on install commands for easy copy/pasting \fI\%#4792\fP +.IP \(bu 2 +Officially drop support for Python 2.7 and Python 3.5. \fI\%#4261\fP +.UNINDENT +.SS 2021.5.29 (2021\-05\-29) +.SS Bug Fixes +.INDENT 0.0 +.IP \(bu 2 +Fix a bug where passing \-\-skip\-lock when PIPFILE has no [SOURCE] section throws the error: "tomlkit.exceptions.NonExistentKey: \(aqKey "source" does not exist.\(aq" \fI\%#4141\fP +.IP \(bu 2 +Fix bug where environment wouldn\(aqt activate in paths containing & and $ symbols \fI\%#4538\fP +.IP \(bu 2 +Fix a bug that \fBimportlib\-metadata\fP from the project\(aqs dependencies conflicts with that from \fBpipenv\fP\(aqs. \fI\%#4549\fP +.IP \(bu 2 +Fix a bug where \fBpep508checker.py\fP did not expect double\-digit Python minor versions (e.g. "3.10"). \fI\%#4602\fP +.IP \(bu 2 +Fix bug where environment wouldn\(aqt activate in paths containing () and [] symbols \fI\%#4615\fP +.IP \(bu 2 +Fix bug preventing use of pipenv lock \-\-pre \fI\%#4642\fP +.UNINDENT +.SS Vendored Libraries +.INDENT 0.0 +.IP \(bu 2 +Update \fBpackaging\fP from \fB20.4\fP to \fB20.8\fP\&. \fI\%#4591\fP +.UNINDENT +.SS 2020.11.15 (2020\-11\-15) +.SS Features & Improvements +.INDENT 0.0 +.IP \(bu 2 +Support expanding environment variables in requirement URLs. \fI\%#3516\fP +.IP \(bu 2 +Show warning message when a dependency is skipped in locking due to the mismatch of its markers. \fI\%#4346\fP +.UNINDENT +.SS Bug Fixes +.INDENT 0.0 +.IP \(bu 2 +Fix a bug that executable scripts with leading backslash can\(aqt be executed via \fBpipenv run\fP\&. \fI\%#4368\fP +.IP \(bu 2 +Fix a bug that VCS dependencies always satisfy even if the ref has changed. \fI\%#4387\fP +.IP \(bu 2 +Restrict the acceptable hash type to SHA256 only. \fI\%#4517\fP +.IP \(bu 2 +Fix the output of \fBpipenv scripts\fP under Windows platform. \fI\%#4523\fP +.IP \(bu 2 +Fix a bug that the resolver takes wrong section to validate constraints. \fI\%#4527\fP +.UNINDENT +.SS Vendored Libraries +.INDENT 0.0 +.IP \(bu 2 +.INDENT 2.0 +.TP +.B Update vendored dependencies: +.INDENT 7.0 +.IP \(bu 2 +\fBcolorama\fP from \fB0.4.3\fP to \fB0.4.4\fP +.IP \(bu 2 +\fBpython\-dotenv\fP from \fB0.10.3\fP to \fB0.15.0\fP +.IP \(bu 2 +\fBfirst\fP from \fB2.0.1\fP to \fB2.0.2\fP +.IP \(bu 2 +\fBiso8601\fP from \fB0.1.12\fP to \fB0.1.13\fP +.IP \(bu 2 +\fBparse\fP from \fB1.15.0\fP to \fB1.18.0\fP +.IP \(bu 2 +\fBpipdeptree\fP from \fB0.13.2\fP to \fB1.0.0\fP +.IP \(bu 2 +\fBrequests\fP from \fB2.23.0\fP to \fB2.25.0\fP +.IP \(bu 2 +\fBidna\fP from \fB2.9\fP to \fB2.10\fP +.IP \(bu 2 +\fBurllib3\fP from \fB1.25.9\fP to \fB1.26.1\fP +.IP \(bu 2 +\fBcertifi\fP from \fB2020.4.5.1\fP to \fB2020.11.8\fP +.IP \(bu 2 +\fBrequirementslib\fP from \fB1.5.15\fP to \fB1.5.16\fP +.IP \(bu 2 +\fBattrs\fP from \fB19.3.0\fP to \fB20.3.0\fP +.IP \(bu 2 +\fBdistlib\fP from \fB0.3.0\fP to \fB0.3.1\fP +.IP \(bu 2 +\fBpackaging\fP from \fB20.3\fP to \fB20.4\fP +.IP \(bu 2 +\fBsix\fP from \fB1.14.0\fP to \fB1.15.0\fP +.IP \(bu 2 +\fBsemver\fP from \fB2.9.0\fP to \fB2.13.0\fP +.IP \(bu 2 +\fBtoml\fP from \fB0.10.1\fP to \fB0.10.2\fP +.IP \(bu 2 +\fBcached\-property\fP from \fB1.5.1\fP to \fB1.5.2\fP +.IP \(bu 2 +\fByaspin\fP from \fB0.14.3\fP to \fB1.2.0\fP +.IP \(bu 2 +\fBresolvelib\fP from \fB0.3.0\fP to \fB0.5.2\fP +.IP \(bu 2 +\fBpep517\fP from \fB0.8.2\fP to \fB0.9.1\fP +.IP \(bu 2 +\fBzipp\fP from \fB0.6.0\fP to \fB1.2.0\fP +.IP \(bu 2 +\fBimportlib\-metadata\fP from \fB1.6.0\fP to \fB2.0.0\fP +.IP \(bu 2 +\fBimportlib\-resources\fP from \fB1.5.0\fP to \fB3.3.0\fP \fI\%#4533\fP +.UNINDENT +.UNINDENT +.UNINDENT +.SS Improved Documentation +.INDENT 0.0 +.IP \(bu 2 +Fix suggested pyenv setup to avoid using shimmed interpreter \fI\%#4534\fP +.UNINDENT +.SS 2020.11.4 (2020\-11\-04) +.SS Features & Improvements +.INDENT 0.0 +.IP \(bu 2 +Add a new command \fBpipenv scripts\fP to display shortcuts from Pipfile. \fI\%#3686\fP +.IP \(bu 2 +Retrieve package file hash from URL to accelerate the locking process. \fI\%#3827\fP +.IP \(bu 2 +Add the missing \fB\-\-system\fP option to \fBpipenv sync\fP\&. \fI\%#4441\fP +.IP \(bu 2 +Add a new option pair \fB\-\-header/\-\-no\-header\fP to \fBpipenv lock\fP command, +which adds a header to the generated requirements.txt \fI\%#4443\fP +.UNINDENT +.SS Bug Fixes +.INDENT 0.0 +.IP \(bu 2 +Fix a bug that percent encoded characters will be unquoted incorrectly in the file URL. \fI\%#4089\fP +.IP \(bu 2 +Fix a bug where setting PIPENV_PYTHON to file path breaks environment name \fI\%#4225\fP +.IP \(bu 2 +Fix a bug that paths are not normalized before comparison. \fI\%#4330\fP +.IP \(bu 2 +Handle Python major and minor versions correctly in Pipfile creation. \fI\%#4379\fP +.IP \(bu 2 +Fix a bug that non\-wheel file requirements can be resolved successfully. \fI\%#4386\fP +.IP \(bu 2 +Fix a bug that \fBpexept.exceptions.TIMEOUT\fP is not caught correctly because of the wrong import path. \fI\%#4424\fP +.IP \(bu 2 +Fix a bug that compound TOML table is not parsed correctly. \fI\%#4433\fP +.IP \(bu 2 +Fix a bug that invalid Python paths from Windows registry break \fBpipenv install\fP\&. \fI\%#4436\fP +.IP \(bu 2 +Fix a bug that function calls in \fBsetup.py\fP can\(aqt be parsed rightly. \fI\%#4446\fP +.IP \(bu 2 +Fix a bug that dist\-info inside \fBvenv\fP directory will be mistaken as the editable package\(aqs metadata. \fI\%#4480\fP +.IP \(bu 2 +Make the order of hashes in resolution result stable. \fI\%#4513\fP +.UNINDENT +.SS Vendored Libraries +.INDENT 0.0 +.IP \(bu 2 +Update \fBtomlkit\fP from \fB0.5.11\fP to \fB0.7.0\fP\&. \fI\%#4433\fP +.IP \(bu 2 +Update \fBrequirementslib\fP from \fB1.5.13\fP to \fB1.5.14\fP\&. \fI\%#4480\fP +.UNINDENT +.SS Improved Documentation +.INDENT 0.0 +.IP \(bu 2 +Discourage homebrew installation in installation guides. \fI\%#4013\fP +.UNINDENT +.SS 2020.8.13 (2020\-08\-13) +.SS Bug Fixes +.INDENT 0.0 +.IP \(bu 2 +Fixed behaviour of \fBpipenv uninstall \-\-all\-dev\fP\&. +From now on it does not uninstall regular packages. \fI\%#3722\fP +.IP \(bu 2 +Fix a bug that incorrect Python path will be used when \fB\-\-system\fP flag is on. \fI\%#4315\fP +.IP \(bu 2 +Fix falsely flagging a Homebrew installed Python as a virtual environment \fI\%#4316\fP +.IP \(bu 2 +Fix a bug that \fBpipenv uninstall\fP throws an exception that does not exist. \fI\%#4321\fP +.IP \(bu 2 +Fix a bug that Pipenv can\(aqt locate the correct file of special directives in \fBsetup.cfg\fP of an editable package. \fI\%#4335\fP +.IP \(bu 2 +Fix a bug that \fBsetup.py\fP can\(aqt be parsed correctly when the assignment is type\-annotated. \fI\%#4342\fP +.IP \(bu 2 +Fix a bug that \fBpipenv graph\fP throws an exception that PipenvCmdError(cmd_string, c.out, c.err, return_code). \fI\%#4388\fP +.IP \(bu 2 +Do not copy the whole directory tree of local file package. \fI\%#4403\fP +.IP \(bu 2 +Correctly detect whether Pipenv in run under an activated virtualenv. \fI\%#4412\fP +.UNINDENT +.SS Vendored Libraries +.INDENT 0.0 +.IP \(bu 2 +Update \fBrequirementslib\fP to \fB1.5.12\fP\&. \fI\%#4385\fP +.IP \(bu 2 +.INDENT 2.0 +.IP \(bu 2 +Update \fBrequirements\fP to \fB1.5.13\fP\&. +.IP \(bu 2 +Update \fBpip\-shims\fP to \fB0.5.3\fP\&. \fI\%#4421\fP +.UNINDENT +.UNINDENT +.SS 2020.6.2 (2020\-06\-02) +.SS Features & Improvements +.INDENT 0.0 +.IP \(bu 2 +Pipenv will now detect existing \fBvenv\fP and \fBvirtualenv\fP based virtual environments more robustly. \fI\%#4276\fP +.UNINDENT +.SS Bug Fixes +.INDENT 0.0 +.IP \(bu 2 +\fB+\fP signs in URL authentication fragments will no longer be incorrectly replaced with space ( \(ga\(ga \(ga\(ga ) characters. \fI\%#4271\fP +.IP \(bu 2 +Fixed a regression which caused Pipenv to fail when running under \fB/\fP\&. \fI\%#4273\fP +.IP \(bu 2 +\fBsetup.py\fP files with \fBversion\fP variables read from \fBos.environ\fP are now able to be parsed successfully. \fI\%#4274\fP +.IP \(bu 2 +Fixed a bug which caused Pipenv to fail to install packages in a virtual environment if those packages were already present in the system global environment. \fI\%#4276\fP +.IP \(bu 2 +Fix a bug that caused non\-specific versions to be pinned in \fBPipfile.lock\fP\&. \fI\%#4278\fP +.IP \(bu 2 +Corrected a missing exception import and invalid function call invocations in \fBpipenv.cli.command\fP\&. \fI\%#4286\fP +.IP \(bu 2 +Fixed an issue with resolving packages with names defined by function calls in \fBsetup.py\fP\&. \fI\%#4292\fP +.IP \(bu 2 +Fixed a regression with installing the current directory, or \fB\&.\fP, inside a \fBvenv\fP based virtual environment. \fI\%#4295\fP +.IP \(bu 2 +Fixed a bug with the discovery of python paths on Windows which could prevent installation of environments during \fBpipenv install\fP\&. \fI\%#4296\fP +.IP \(bu 2 +Fixed an issue in the \fBrequirementslib\fP AST parser which prevented parsing of \fBsetup.py\fP files for dependency metadata. \fI\%#4298\fP +.IP \(bu 2 +Fix a bug where Pipenv doesn\(aqt realize the session is interactive \fI\%#4305\fP +.UNINDENT +.SS Vendored Libraries +.INDENT 0.0 +.IP \(bu 2 +Updated requirementslib to version \fB1.5.11\fP\&. \fI\%#4292\fP +.IP \(bu 2 +.INDENT 2.0 +.TP +.B Updated vendored dependencies: +.INDENT 7.0 +.IP \(bu 2 +\fBpythonfinder\fP: \fB1.2.2\fP => \fB1.2.4\fP +.IP \(bu 2 +\fBrequirementslib\fP: \fB1.5.9\fP => \fB1.5.10\fP \fI\%#4302\fP +.UNINDENT +.UNINDENT +.UNINDENT +.SS 2020.5.28 (2020\-05\-28) +.SS Features & Improvements +.INDENT 0.0 +.IP \(bu 2 +\fBpipenv install\fP and \fBpipenv sync\fP will no longer attempt to install satisfied dependencies during installation. \fI\%#3057\fP, +\fI\%#3506\fP +.IP \(bu 2 +Added support for resolution of direct\-url dependencies in \fBsetup.py\fP files to respect \fBPEP\-508\fP style URL dependencies. \fI\%#3148\fP +.IP \(bu 2 +Added full support for resolution of all dependency types including direct URLs, zip archives, tarballs, etc. +.INDENT 2.0 +.IP \(bu 2 +Improved error handling and formatting. +.IP \(bu 2 +Introduced improved cross platform stream wrappers for better \fBstdout\fP and \fBstderr\fP consistency. \fI\%#3298\fP +.UNINDENT +.IP \(bu 2 +For consistency with other commands and the \fB\-\-dev\fP option +description, \fBpipenv lock \-\-requirements \-\-dev\fP now emits +both default and development dependencies. +The new \fB\-\-dev\-only\fP option requests the previous +behaviour (e.g. to generate a \fBdev\-requirements.txt\fP file). \fI\%#3316\fP +.IP \(bu 2 +Pipenv will now successfully recursively lock VCS sub\-dependencies. \fI\%#3328\fP +.IP \(bu 2 +Added support for \fB\-\-verbose\fP output to \fBpipenv run\fP\&. \fI\%#3348\fP +.IP \(bu 2 +Pipenv will now discover and resolve the intrinsic dependencies of \fBall\fP VCS dependencies, whether they are editable or not, to prevent resolution conflicts. \fI\%#3368\fP +.IP \(bu 2 +Added a new environment variable, \fBPIPENV_RESOLVE_VCS\fP, to toggle dependency resolution off for non\-editable VCS, file, and URL based dependencies. \fI\%#3577\fP +.IP \(bu 2 +Added the ability for Windows users to enable emojis by setting \fBPIPENV_HIDE_EMOJIS=0\fP\&. \fI\%#3595\fP +.IP \(bu 2 +Allow overriding PIPENV_INSTALL_TIMEOUT environment variable (in seconds). \fI\%#3652\fP +.IP \(bu 2 +Allow overriding PIP_EXISTS_ACTION evironment variable (value is passed to pip install). +Possible values here: \fI\%https://pip.pypa.io/en/stable/reference/pip/#exists\-action\-option\fP +Useful when you need to \fIPIP_EXISTS_ACTION=i\fP (ignore existing packages) \- great for CI environments, where you need really fast setup. \fI\%#3738\fP +.IP \(bu 2 +Pipenv will no longer forcibly override \fBPIP_NO_DEPS\fP on all vcs and file dependencies as resolution happens on these in a pre\-lock step. \fI\%#3763\fP +.IP \(bu 2 +Improved verbose logging output during \fBpipenv lock\fP will now stream output to the console while maintaining a spinner. \fI\%#3810\fP +.IP \(bu 2 +Added support for automatic python installs via \fBasdf\fP and associated \fBPIPENV_DONT_USE_ASDF\fP environment variable. \fI\%#4018\fP +.IP \(bu 2 +Pyenv/asdf can now be used whether or not they are available on PATH. Setting PYENV_ROOT/ASDF_DIR in a Pipenv\(aqs .env allows Pipenv to install an interpreter without any shell customizations, so long as pyenv/asdf is installed. \fI\%#4245\fP +.IP \(bu 2 +Added \fB\-\-key\fP command line parameter for including personal PyUp.io API tokens when running \fBpipenv check\fP\&. \fI\%#4257\fP +.UNINDENT +.SS Behavior Changes +.INDENT 0.0 +.IP \(bu 2 +Make conservative checks of known exceptions when subprocess returns output, so user won\(aqt see the whole traceback \- just the error. \fI\%#2553\fP +.IP \(bu 2 +Do not touch Pipfile early and rely on it so that one can do \fBpipenv sync\fP without a Pipfile. \fI\%#3386\fP +.IP \(bu 2 +Re\-enable \fB\-\-help\fP option for \fBpipenv run\fP command. \fI\%#3844\fP +.IP \(bu 2 +Make sure \fBpipenv lock \-r \-\-pypi\-mirror {MIRROR_URL}\fP will respect the pypi\-mirror in requirements output. \fI\%#4199\fP +.UNINDENT +.SS Bug Fixes +.INDENT 0.0 +.IP \(bu 2 +Raise \fIPipenvUsageError\fP when [[source]] does not contain url field. \fI\%#2373\fP +.IP \(bu 2 +Fixed a bug which caused editable package resolution to sometimes fail with an unhelpful setuptools\-related error message. \fI\%#2722\fP +.IP \(bu 2 +Fixed an issue which caused errors due to reliance on the system utilities \fBwhich\fP and \fBwhere\fP which may not always exist on some systems. +\- Fixed a bug which caused periodic failures in python discovery when executables named \fBpython\fP were not present on the target \fB$PATH\fP\&. \fI\%#2783\fP +.IP \(bu 2 +Dependency resolution now writes hashes for local and remote files to the lockfile. \fI\%#3053\fP +.IP \(bu 2 +Fixed a bug which prevented \fBpipenv graph\fP from correctly showing all dependencies when running from within \fBpipenv shell\fP\&. \fI\%#3071\fP +.IP \(bu 2 +Fixed resolution of direct\-url dependencies in \fBsetup.py\fP files to respect \fBPEP\-508\fP style URL dependencies. \fI\%#3148\fP +.IP \(bu 2 +Fixed a bug which caused failures in warning reporting when running pipenv inside a virtualenv under some circumstances. +.INDENT 2.0 +.IP \(bu 2 +Fixed a bug with package discovery when running \fBpipenv clean\fP\&. \fI\%#3298\fP +.UNINDENT +.IP \(bu 2 +Quote command arguments with carets (\fB^\fP) on Windows to work around unintended shell escapes. \fI\%#3307\fP +.IP \(bu 2 +Handle alternate names for UTF\-8 encoding. \fI\%#3313\fP +.IP \(bu 2 +Abort pipenv before adding the non\-exist package to Pipfile. \fI\%#3318\fP +.IP \(bu 2 +Don\(aqt normalize the package name user passes in. \fI\%#3324\fP +.IP \(bu 2 +Fix a bug where custom virtualenv can not be activated with pipenv shell \fI\%#3339\fP +.IP \(bu 2 +Fix a bug that \fB\-\-site\-packages\fP flag is not recognized. \fI\%#3351\fP +.IP \(bu 2 +Fix a bug where pipenv \-\-clear is not working \fI\%#3353\fP +.IP \(bu 2 +Fix unhashable type error during \fB$ pipenv install \-\-selective\-upgrade\fP \fI\%#3384\fP +.IP \(bu 2 +Dependencies with direct \fBPEP508\fP compliant VCS URLs specified in their \fBinstall_requires\fP will now be successfully locked during the resolution process. \fI\%#3396\fP +.IP \(bu 2 +Fixed a keyerror which could occur when locking VCS dependencies in some cases. \fI\%#3404\fP +.IP \(bu 2 +Fixed a bug that \fBValidationError\fP is thrown when some fields are missing in source section. \fI\%#3427\fP +.IP \(bu 2 +Updated the index names in lock file when source name in Pipfile is changed. \fI\%#3449\fP +.IP \(bu 2 +Fixed an issue which caused \fBpipenv install \-\-help\fP to show duplicate entries for \fB\-\-pre\fP\&. \fI\%#3479\fP +.IP \(bu 2 +Fix bug causing \fB[SSL: CERTIFICATE_VERIFY_FAILED]\fP when Pipfile \fB[[source]]\fP has verify_ssl=false and url with custom port. \fI\%#3502\fP +.IP \(bu 2 +Fix \fBsync \-\-sequential\fP ignoring \fBpip install\fP errors and logs. \fI\%#3537\fP +.IP \(bu 2 +Fix the issue that lock file can\(aqt be created when \fBPIPENV_PIPFILE\fP is not under working directory. \fI\%#3584\fP +.IP \(bu 2 +Pipenv will no longer inadvertently set \fBeditable=True\fP on all vcs dependencies. \fI\%#3647\fP +.IP \(bu 2 +The \fB\-\-keep\-outdated\fP argument to \fBpipenv install\fP and \fBpipenv lock\fP will now drop specifier constraints when encountering editable dependencies. +\- In addition, \fB\-\-keep\-outdated\fP will retain specifiers that would otherwise be dropped from any entries that have not been updated. \fI\%#3656\fP +.IP \(bu 2 +Fixed a bug which sometimes caused pipenv to fail to respect the \fB\-\-site\-packages\fP flag when passed with \fBpipenv install\fP\&. \fI\%#3718\fP +.IP \(bu 2 +Normalize the package names to lowercase when comparing used and in\-Pipfile packages. \fI\%#3745\fP +.IP \(bu 2 +\fBpipenv update \-\-outdated\fP will now correctly handle comparisons between pre/post\-releases and normal releases. \fI\%#3766\fP +.IP \(bu 2 +Fixed a \fBKeyError\fP which could occur when pinning outdated VCS dependencies via \fBpipenv lock \-\-keep\-outdated\fP\&. \fI\%#3768\fP +.IP \(bu 2 +Resolved an issue which caused resolution to fail when encountering poorly formatted \fBpython_version\fP markers in \fBsetup.py\fP and \fBsetup.cfg\fP files. \fI\%#3786\fP +.IP \(bu 2 +Fix a bug that installation errors are displayed as a list. \fI\%#3794\fP +.IP \(bu 2 +Update \fBpythonfinder\fP to fix a problem that \fBpython.exe\fP will be mistakenly chosen for +virtualenv creation under WSL. \fI\%#3807\fP +.IP \(bu 2 +Fixed several bugs which could prevent editable VCS dependencies from being installed into target environments, even when reporting successful installation. \fI\%#3809\fP +.IP \(bu 2 +\fBpipenv check \-\-system\fP should find the correct Python interpreter when \fBpython\fP does not exist on the system. \fI\%#3819\fP +.IP \(bu 2 +Resolve the symlinks when the path is absolute. \fI\%#3842\fP +.IP \(bu 2 +Pass \fB\-\-pre\fP and \fB\-\-clear\fP options to \fBpipenv update \-\-outdated\fP\&. \fI\%#3879\fP +.IP \(bu 2 +Fixed a bug which prevented resolution of direct URL dependencies which have PEP508 style direct url VCS sub\-dependencies with subdirectories. \fI\%#3976\fP +.IP \(bu 2 +Honor PIPENV_SPINNER environment variable \fI\%#4045\fP +.IP \(bu 2 +Fixed an issue with \fBpipenv check\fP failing due to an invalid API key from \fBpyup.io\fP\&. \fI\%#4188\fP +.IP \(bu 2 +Fixed a bug which caused versions from VCS dependencies to be included in \fBPipfile.lock\fP inadvertently. \fI\%#4217\fP +.IP \(bu 2 +Fixed a bug which caused pipenv to search non\-existent virtual environments for \fBpip\fP when installing using \fB\-\-system\fP\&. \fI\%#4220\fP +.IP \(bu 2 +\fBRequires\-Python\fP values specifying constraint versions of python starting from \fB1.x\fP will now be parsed successfully. \fI\%#4226\fP +.IP \(bu 2 +Fix a bug of \fBpipenv update \-\-outdated\fP that can\(aqt print output correctly. \fI\%#4229\fP +.IP \(bu 2 +Fixed a bug which caused pipenv to prefer source distributions over wheels from \fBPyPI\fP during the dependency resolution phase. +Fixed an issue which prevented proper build isolation using \fBpep517\fP based builders during dependency resolution. \fI\%#4231\fP +.IP \(bu 2 +Don\(aqt fallback to system Python when no matching Python version is found. \fI\%#4232\fP +.UNINDENT +.SS Vendored Libraries +.INDENT 0.0 +.IP \(bu 2 +Updated vendored dependencies: +.INDENT 2.0 +.INDENT 3.5 +.INDENT 0.0 +.IP \(bu 2 +\fBattrs\fP: \fB18.2.0\fP => \fB19.1.0\fP +.IP \(bu 2 +\fBcertifi\fP: \fB2018.10.15\fP => \fB2019.3.9\fP +.IP \(bu 2 +\fBcached_property\fP: \fB1.4.3\fP => \fB1.5.1\fP +.IP \(bu 2 +\fBcerberus\fP: \fB1.2.0\fP => \fB1.3.1\fP +.IP \(bu 2 +\fBclick\-completion\fP: \fB0.5.0\fP => \fB0.5.1\fP +.IP \(bu 2 +\fBcolorama\fP: \fB0.3.9\fP => \fB0.4.1\fP +.IP \(bu 2 +\fBdistlib\fP: \fB0.2.8\fP => \fB0.2.9\fP +.IP \(bu 2 +\fBidna\fP: \fB2.7\fP => \fB2.8\fP +.IP \(bu 2 +\fBjinja2\fP: \fB2.10.0\fP => \fB2.10.1\fP +.IP \(bu 2 +\fBmarkupsafe\fP: \fB1.0\fP => \fB1.1.1\fP +.IP \(bu 2 +\fBorderedmultidict\fP: \fB(new)\fP => \fB1.0\fP +.IP \(bu 2 +\fBpackaging\fP: \fB18.0\fP => \fB19.0\fP +.IP \(bu 2 +\fBparse\fP: \fB1.9.0\fP => \fB1.12.0\fP +.IP \(bu 2 +\fBpathlib2\fP: \fB2.3.2\fP => \fB2.3.3\fP +.IP \(bu 2 +\fBpep517\fP: \fB(new)\fP => \fB0.5.0\fP +.IP \(bu 2 +\fBpexpect\fP: \fB4.6.0\fP => \fB4.7.0\fP +.IP \(bu 2 +\fBpipdeptree\fP: \fB0.13.0\fP => \fB0.13.2\fP +.IP \(bu 2 +\fBpyparsing\fP: \fB2.2.2\fP => \fB2.3.1\fP +.IP \(bu 2 +\fBpython\-dotenv\fP: \fB0.9.1\fP => \fB0.10.2\fP +.IP \(bu 2 +\fBpythonfinder\fP: \fB1.1.10\fP => \fB1.2.1\fP +.IP \(bu 2 +\fBpytoml\fP: \fB(new)\fP => \fB0.1.20\fP +.IP \(bu 2 +\fBrequests\fP: \fB2.20.1\fP => \fB2.21.0\fP +.IP \(bu 2 +\fBrequirementslib\fP: \fB1.3.3\fP => \fB1.5.0\fP +.IP \(bu 2 +\fBscandir\fP: \fB1.9.0\fP => \fB1.10.0\fP +.IP \(bu 2 +\fBshellingham\fP: \fB1.2.7\fP => \fB1.3.1\fP +.IP \(bu 2 +\fBsix\fP: \fB1.11.0\fP => \fB1.12.0\fP +.IP \(bu 2 +\fBtomlkit\fP: \fB0.5.2\fP => \fB0.5.3\fP +.IP \(bu 2 +\fBurllib3\fP: \fB1.24\fP => \fB1.25.2\fP +.IP \(bu 2 +\fBvistir\fP: \fB0.3.0\fP => \fB0.4.1\fP +.IP \(bu 2 +\fByaspin\fP: \fB0.14.0\fP => \fB0.14.3\fP +.UNINDENT +.UNINDENT +.UNINDENT +.INDENT 2.0 +.IP \(bu 2 +Removed vendored dependency \fBcursor\fP\&. \fI\%#3298\fP +.UNINDENT +.IP \(bu 2 +Updated \fBpip_shims\fP to support \fB\-\-outdated\fP with new pip versions. \fI\%#3766\fP +.IP \(bu 2 +Update vendored dependencies and invocations +.INDENT 2.0 +.IP \(bu 2 +Update vendored and patched dependencies +\- Update patches on \fBpiptools\fP, \fBpip\fP, \fBpip\-shims\fP, +.nf +\(ga\(ga +.fi +tomlkit\(ga +.IP \(bu 2 +Fix invocations of dependencies +\- Fix custom +.nf +\(ga\(ga +.fi +InstallCommand\(ga instantiation +\- Update +.nf +\(ga\(ga +.fi +PackageFinder\(ga usage +\- Fix +.nf +\(ga\(ga +.fi +Bool\(ga stringify attempts from +.nf +\(ga\(ga +.fi +tomlkit\(ga +.UNINDENT +.INDENT 2.0 +.TP +.B Updated vendored dependencies: +.INDENT 7.0 +.IP \(bu 2 +\fBattrs\fP: \fB\(ga18.2.0\fP => \fB\(ga19.1.0\fP +.IP \(bu 2 +\fBcertifi\fP: \fB\(ga2018.10.15\fP => \fB\(ga2019.3.9\fP +.IP \(bu 2 +\fBcached_property\fP: \fB\(ga1.4.3\fP => \fB\(ga1.5.1\fP +.IP \(bu 2 +\fBcerberus\fP: \fB\(ga1.2.0\fP => \fB\(ga1.3.1\fP +.IP \(bu 2 +\fBclick\fP: \fB\(ga7.0.0\fP => \fB\(ga7.1.1\fP +.IP \(bu 2 +\fBclick\-completion\fP: \fB\(ga0.5.0\fP => \fB\(ga0.5.1\fP +.IP \(bu 2 +\fBcolorama\fP: \fB\(ga0.3.9\fP => \fB\(ga0.4.3\fP +.IP \(bu 2 +\fBcontextlib2\fP: \fB\(ga(new)\fP => \fB\(ga0.6.0.post1\fP +.IP \(bu 2 +\fBdistlib\fP: \fB\(ga0.2.8\fP => \fB\(ga0.2.9\fP +.IP \(bu 2 +\fBfuncsigs\fP: \fB\(ga(new)\fP => \fB\(ga1.0.2\fP +.IP \(bu 2 +\fBimportlib_metadata\fP \fB\(ga1.3.0\fP => \fB\(ga1.5.1\fP +.IP \(bu 2 +\fBimportlib\-resources\fP: \fB\(ga(new)\fP => \fB\(ga1.4.0\fP +.IP \(bu 2 +\fBidna\fP: \fB\(ga2.7\fP => \fB\(ga2.9\fP +.IP \(bu 2 +\fBjinja2\fP: \fB\(ga2.10.0\fP => \fB\(ga2.11.1\fP +.IP \(bu 2 +\fBmarkupsafe\fP: \fB\(ga1.0\fP => \fB\(ga1.1.1\fP +.IP \(bu 2 +\fBmore\-itertools\fP: \fB\(ga(new)\fP => \fB\(ga5.0.0\fP +.IP \(bu 2 +\fBorderedmultidict\fP: \fB\(ga(new)\fP => \fB\(ga1.0\fP +.IP \(bu 2 +\fBpackaging\fP: \fB\(ga18.0\fP => \fB\(ga19.0\fP +.IP \(bu 2 +\fBparse\fP: \fB\(ga1.9.0\fP => \fB\(ga1.15.0\fP +.IP \(bu 2 +\fBpathlib2\fP: \fB\(ga2.3.2\fP => \fB\(ga2.3.3\fP +.IP \(bu 2 +\fBpep517\fP: \fB\(ga(new)\fP => \fB\(ga0.5.0\fP +.IP \(bu 2 +\fBpexpect\fP: \fB\(ga4.6.0\fP => \fB\(ga4.8.0\fP +.IP \(bu 2 +\fBpip\-shims\fP: \fB\(ga0.2.0\fP => \fB\(ga0.5.1\fP +.IP \(bu 2 +\fBpipdeptree\fP: \fB\(ga0.13.0\fP => \fB\(ga0.13.2\fP +.IP \(bu 2 +\fBpyparsing\fP: \fB\(ga2.2.2\fP => \fB\(ga2.4.6\fP +.IP \(bu 2 +\fBpython\-dotenv\fP: \fB\(ga0.9.1\fP => \fB\(ga0.10.2\fP +.IP \(bu 2 +\fBpythonfinder\fP: \fB\(ga1.1.10\fP => \fB\(ga1.2.2\fP +.IP \(bu 2 +\fBpytoml\fP: \fB\(ga(new)\fP => \fB\(ga0.1.20\fP +.IP \(bu 2 +\fBrequests\fP: \fB\(ga2.20.1\fP => \fB\(ga2.23.0\fP +.IP \(bu 2 +\fBrequirementslib\fP: \fB\(ga1.3.3\fP => \fB\(ga1.5.4\fP +.IP \(bu 2 +\fBscandir\fP: \fB\(ga1.9.0\fP => \fB\(ga1.10.0\fP +.IP \(bu 2 +\fBshellingham\fP: \fB\(ga1.2.7\fP => \fB\(ga1.3.2\fP +.IP \(bu 2 +\fBsix\fP: \fB\(ga1.11.0\fP => \fB\(ga1.14.0\fP +.IP \(bu 2 +\fBtomlkit\fP: \fB\(ga0.5.2\fP => \fB\(ga0.5.11\fP +.IP \(bu 2 +\fBurllib3\fP: \fB\(ga1.24\fP => \fB\(ga1.25.8\fP +.IP \(bu 2 +\fBvistir\fP: \fB\(ga0.3.0\fP => \fB\(ga0.5.0\fP +.IP \(bu 2 +\fByaspin\fP: \fB\(ga0.14.0\fP => \fB\(ga0.14.3\fP +.IP \(bu 2 +\fBzipp\fP: \fB\(ga0.6.0\fP +.UNINDENT +.UNINDENT +.INDENT 2.0 +.IP \(bu 2 +Removed vendored dependency \fBcursor\fP\&. \fI\%#4169\fP +.UNINDENT +.IP \(bu 2 +Add and update vendored dependencies to accommodate \fBsafety\fP vendoring: +\- \fBsafety\fP \fB(none)\fP => \fB1.8.7\fP +\- \fBdparse\fP \fB(none)\fP => \fB0.5.0\fP +\- \fBpyyaml\fP \fB(none)\fP => \fB5.3.1\fP +\- \fBurllib3\fP \fB1.25.8\fP => \fB1.25.9\fP +\- \fBcertifi\fP \fB2019.11.28\fP => \fB2020.4.5.1\fP +\- \fBpyparsing\fP \fB2.4.6\fP => \fB2.4.7\fP +\- \fBresolvelib\fP \fB0.2.2\fP => \fB0.3.0\fP +\- \fBimportlib\-metadata\fP \fB1.5.1\fP => \fB1.6.0\fP +\- \fBpip\-shims\fP \fB0.5.1\fP => \fB0.5.2\fP +\- \fBrequirementslib\fP \fB1.5.5\fP => \fB1.5.6\fP \fI\%#4188\fP +.IP \(bu 2 +Updated vendored \fBpip\fP => \fB20.0.2\fP and \fBpip\-tools\fP => \fB5.0.0\fP\&. \fI\%#4215\fP +.IP \(bu 2 +Updated vendored dependencies to latest versions for security and bug fixes: +.INDENT 2.0 +.IP \(bu 2 +\fBrequirementslib\fP \fB1.5.8\fP => \fB1.5.9\fP +.IP \(bu 2 +\fBvistir\fP \fB0.5.0\fP => \fB0.5.1\fP +.IP \(bu 2 +\fBjinja2\fP \fB2.11.1\fP => \fB2.11.2\fP +.IP \(bu 2 +\fBclick\fP \fB7.1.1\fP => \fB7.1.2\fP +.IP \(bu 2 +\fBdateutil\fP \fB(none)\fP => \fB2.8.1\fP +.IP \(bu 2 +\fBbackports.functools_lru_cache\fP \fB1.5.0\fP => \fB1.6.1\fP +.IP \(bu 2 +\fBenum34\fP \fB1.1.6\fP => \fB1.1.10\fP +.IP \(bu 2 +\fBtoml\fP \fB0.10.0\fP => \fB0.10.1\fP +.IP \(bu 2 +\fBimportlib_resources\fP \fB1.4.0\fP => \fB1.5.0\fP \fI\%#4226\fP +.UNINDENT +.IP \(bu 2 +Changed attrs import path in vendored dependencies to always import from \fBpipenv.vendor\fP\&. \fI\%#4267\fP +.UNINDENT +.SS Improved Documentation +.INDENT 0.0 +.IP \(bu 2 +Added documenation about variable expansion in \fBPipfile\fP entries. \fI\%#2317\fP +.IP \(bu 2 +Consolidate all contributing docs in the rst file \fI\%#3120\fP +.IP \(bu 2 +Update the out\-dated manual page. \fI\%#3246\fP +.IP \(bu 2 +Move CLI docs to its own page. \fI\%#3346\fP +.IP \(bu 2 +Replace (non\-existant) video on docs index.rst with equivalent gif. \fI\%#3499\fP +.IP \(bu 2 +Clarify wording in Basic Usage example on using double quotes to escape shell redirection \fI\%#3522\fP +.IP \(bu 2 +Ensure docs show navigation on small\-screen devices \fI\%#3527\fP +.IP \(bu 2 +Added a link to the TOML Spec under General Recommendations & Version Control to clarify how Pipfiles should be written. \fI\%#3629\fP +.IP \(bu 2 +Updated the documentation with the new \fBpytest\fP entrypoint. \fI\%#3759\fP +.IP \(bu 2 +Fix link to GIF in README.md demonstrating Pipenv\(aqs usage, and add descriptive alt text. \fI\%#3911\fP +.IP \(bu 2 +Added a line describing potential issues in fancy extension. \fI\%#3912\fP +.IP \(bu 2 +Documental description of how Pipfile works and association with Pipenv. \fI\%#3913\fP +.IP \(bu 2 +Clarify the proper value of \fBpython_version\fP and \fBpython_full_version\fP\&. \fI\%#3914\fP +.IP \(bu 2 +Write description for \-\-deploy extension and few extensions differences. \fI\%#3915\fP +.IP \(bu 2 +More documentation for \fB\&.env\fP files \fI\%#4100\fP +.IP \(bu 2 +Updated documentation to point to working links. \fI\%#4137\fP +.IP \(bu 2 +Replace docs.pipenv.org with pipenv.pypa.io \fI\%#4167\fP +.IP \(bu 2 +Added functionality to check spelling in documentation and cleaned up existing typographical issues. \fI\%#4209\fP +.UNINDENT .SS 2018.11.26 (2018\-11\-26) .SS Bug Fixes .INDENT 0.0 @@ -412,21 +1265,21 @@ Fixed an issue which prevented the parsing of named extras sections from certain .IP \(bu 2 Correctly detect the virtualenv location inside an activated virtualenv. \fI\%#3231\fP .IP \(bu 2 -Fixed a bug which caused spinner frames to be written to stdout during locking operations which could cause redirection pipes to fail. \fI\%#3239\fP +Fixed a bug which caused spinner frames to be written to standard output during locking operations which could cause redirection pipes to fail. \fI\%#3239\fP .IP \(bu 2 -Fixed a bug that editable pacakges can\(aqt be uninstalled correctly. \fI\%#3240\fP +Fixed a bug that editable packages can\(aqt be uninstalled correctly. \fI\%#3240\fP .IP \(bu 2 Corrected an issue with installation timeouts which caused dependency resolution to fail for longer duration resolution steps. \fI\%#3244\fP .IP \(bu 2 Adding normal pep 508 compatible markers is now fully functional when using VCS dependencies. \fI\%#3249\fP .IP \(bu 2 -Updated \fBrequirementslib\fP and \fBpythonfinder\fP for multiple bugfixes. \fI\%#3254\fP +Updated \fBrequirementslib\fP and \fBpythonfinder\fP for multiple bug fixes. \fI\%#3254\fP .IP \(bu 2 Pipenv will now ignore hashes when installing with \fB\-\-skip\-lock\fP\&. \fI\%#3255\fP .IP \(bu 2 Fixed an issue where pipenv could crash when multiple pipenv processes attempted to create the same directory. \fI\%#3257\fP .IP \(bu 2 -Fixed an issue which sometimes prevented successful creation of project pipfiles. \fI\%#3260\fP +Fixed an issue which sometimes prevented successful creation of a project Pipfile. \fI\%#3260\fP .IP \(bu 2 \fBpipenv install\fP will now unset the \fBPYTHONHOME\fP environment variable when not combined with \fB\-\-system\fP\&. \fI\%#3261\fP .IP \(bu 2 @@ -466,10 +1319,10 @@ Added persistent settings for all CLI flags via \fBPIPENV_{FLAG_NAME}\fP environ .IP \(bu 2 Added improved messaging about available but skipped updates due to dependency conflicts when running \fBpipenv update \-\-outdated\fP\&. \fI\%#2411\fP .IP \(bu 2 -Added environment variable \fIPIPENV_PYUP_API_KEY\fP to add ability -to override the bundled pyup.io API key. \fI\%#2825\fP +Added environment variable \fBPIPENV_PYUP_API_KEY\fP to add ability +to override the bundled PyUP.io API key. \fI\%#2825\fP .IP \(bu 2 -Added additional output to \fBpipenv update \-\-outdated\fP to indicate that the operation succeded and all packages were already up to date. \fI\%#2828\fP +Added additional output to \fBpipenv update \-\-outdated\fP to indicate that the operation succeeded and all packages were already up to date. \fI\%#2828\fP .IP \(bu 2 Updated \fBcrayons\fP patch to enable colors on native powershell but swap native blue for magenta. \fI\%#3020\fP .IP \(bu 2 @@ -486,7 +1339,7 @@ Improved runtime performance of no\-op commands such as \fBpipenv \-\-venv\fP by .IP \(bu 2 Do not show error but success for running \fBpipenv uninstall \-\-all\fP in a fresh virtual environment. \fI\%#3170\fP .IP \(bu 2 -Improved asynchronous installation and error handling via queued subprocess paralleization. \fI\%#3217\fP +Improved asynchronous installation and error handling via queued subprocess parallelization. \fI\%#3217\fP .UNINDENT .SS Bug Fixes .INDENT 0.0 @@ -497,7 +1350,7 @@ Non\-ascii characters will now be handled correctly when parsed by pipenv\(aqs \ .IP \(bu 2 Updated \fBpipenv uninstall\fP to respect the \fB\-\-skip\-lock\fP argument. \fI\%#2848\fP .IP \(bu 2 -Fixed a bug which caused uninstallation to sometimes fail to successfullly remove packages from \fBPipfiles\fP with comments on preceding or following lines. \fI\%#2885\fP, +Fixed a bug which caused uninstallation to sometimes fail to successfully remove packages from \fBPipfiles\fP with comments on preceding or following lines. \fI\%#2885\fP, \fI\%#3099\fP .IP \(bu 2 Pipenv will no longer fail when encountering python versions on Windows that have been uninstalled. \fI\%#2983\fP @@ -535,7 +1388,7 @@ Updated \fBpythonfinder\fP to correct an issue with unnesting of nested paths wh .IP \(bu 2 Added additional logic for ignoring and replacing non\-ascii characters when formatting console output on non\-UTF\-8 systems. \fI\%#3131\fP .IP \(bu 2 -Fix virtual environment discovery when \fIPIPENV_VENV_IN_PROJECT\fP is set, but the in\-project \fI\&.venv\fP is a file. \fI\%#3134\fP +Fix virtual environment discovery when \fBPIPENV_VENV_IN_PROJECT\fP is set, but the in\-project \fI\&.venv\fP is a file. \fI\%#3134\fP .IP \(bu 2 Hashes for remote and local non\-PyPI artifacts will now be included in \fBPipfile.lock\fP during resolution. \fI\%#3145\fP .IP \(bu 2 @@ -652,7 +1505,7 @@ Upgraded \fBpythonfinder => 1.1.1\fP and \fBvistir => 0.1.7\fP\&. \fI\%#3007\fP Added environment variables \fIPIPENV_VERBOSE\fP and \fIPIPENV_QUIET\fP to control output verbosity without needing to pass options. \fI\%#2527\fP .IP \(bu 2 -Updated test\-pypi addon to better support json\-api access (forward compatibility). +Updated test\-PyPI add\-on to better support json\-API access (forward compatibility). Improved testing process for new contributors. \fI\%#2568\fP .IP \(bu 2 Greatly enhanced python discovery functionality: @@ -689,11 +1542,11 @@ Fallback to shell mode if \fIrun\fP fails with Windows error 193 to handle non\- .SS Bug Fixes .INDENT 0.0 .IP \(bu 2 -Fixed a bug which prevented installation of editable requirements using \fBssh://\fP style urls \fI\%#1393\fP +Fixed a bug which prevented installation of editable requirements using \fBssh://\fP style URLs \fI\%#1393\fP .IP \(bu 2 VCS Refs for locked local editable dependencies will now update appropriately to the latest hash when running \fBpipenv update\fP\&. \fI\%#1690\fP .IP \(bu 2 -\fB\&.tar.gz\fP and \fB\&.zip\fP artifacts will now have dependencies installed even when they are missing from the lockfile. \fI\%#2173\fP +\fB\&.tar.gz\fP and \fB\&.zip\fP artifacts will now have dependencies installed even when they are missing from the Lockfile. \fI\%#2173\fP .IP \(bu 2 The command line parser will now handle multiple \fB\-e/\-\-editable\fP dependencies properly via click\(aqs option parser to help mitigate future parsing issues. \fI\%#2279\fP .IP \(bu 2 @@ -717,12 +1570,12 @@ Fixed non\-deterministic resolution issues related to changes to the internal pa .IP \(bu 2 Fix subshell invocation on Windows for Python 2. \fI\%#2515\fP .IP \(bu 2 -Fixed a bug which sometimes caused pipenv to throw a \fBTypeError\fP or to run into encoding issues when writing lockfiles on python 2. \fI\%#2561\fP +Fixed a bug which sometimes caused pipenv to throw a \fBTypeError\fP or to run into encoding issues when writing a Lockfile on python 2. \fI\%#2561\fP .IP \(bu 2 Improve quoting logic for \fBpipenv run\fP so it works better with Windows built\-in commands. \fI\%#2563\fP .IP \(bu 2 -Fixed a bug related to parsing vcs requirements with both extras and subdirectory fragments. +Fixed a bug related to parsing VCS requirements with both extras and subdirectory fragments. Corrected an issue in the \fBrequirementslib\fP parser which led to some markers being discarded rather than evaluated. \fI\%#2564\fP .IP \(bu 2 Fixed multiple issues with finding the correct system python locations. \fI\%#2582\fP @@ -751,7 +1604,7 @@ Fixed virtualenv creation failure when a .venv file is present in the project ro .IP \(bu 2 Fixed a bug which could cause the \fB\-e/\-\-editable\fP argument on a dependency to be accidentally parsed as a dependency itself. \fI\%#2714\fP .IP \(bu 2 -Correctly pass \fIverbose\fP and \fIdebug\fP flags to the resolver subprocess so it generates appropriate output. This also resolves a bug introduced by the fix to #2527. \fI\%#2732\fP +Correctly pass \fBverbose\fP and \fBdebug\fP flags to the resolver subprocess so it generates appropriate output. This also resolves a bug introduced by the fix to #2527. \fI\%#2732\fP .IP \(bu 2 All markers are now included in \fBpipenv lock \-\-requirements\fP output. \fI\%#2748\fP .IP \(bu 2 @@ -764,7 +1617,7 @@ Fixed a bug in the dependency resolver which caused regular issues when handling .B Updated vendored dependencies: .INDENT 7.0 .IP \(bu 2 -\fBpip\-tools\fP (updated and patched to latest w/ \fBpip 18.0\fP compatibilty) +\fBpip\-tools\fP (updated and patched to latest w/ \fBpip 18.0\fP compatibility) .IP \(bu 2 \fBpip 10.0.1 => 18.0\fP .IP \(bu 2 @@ -844,7 +1697,7 @@ Update vendored libraries: .B Updated vendored dependencies: .INDENT 7.0 .IP \(bu 2 -\fBpip\-tools\fP (updated and patched to latest w/ \fBpip 18.0\fP compatibilty) +\fBpip\-tools\fP (updated and patched to latest w/ \fBpip 18.0\fP compatibility) .IP \(bu 2 \fBpip 10.0.1 => 18.0\fP .IP \(bu 2 @@ -889,7 +1742,7 @@ Update vendored libraries: .IP \(bu 2 Simplified the test configuration process. \fI\%#2568\fP .IP \(bu 2 -Updated documentation to use working fortune cookie addon. \fI\%#2644\fP +Updated documentation to use working fortune cookie add\-on. \fI\%#2644\fP .IP \(bu 2 Added additional information about troubleshooting \fBpipenv shell\fP by using the the \fB$PIPENV_SHELL\fP environment variable. \fI\%#2671\fP .IP \(bu 2 @@ -900,7 +1753,7 @@ Added simple example to README.md for installing from git. \fI\%#2685\fP Stopped recommending \fI\-\-system\fP for Docker contexts. \fI\%#2762\fP .IP \(bu 2 Fixed the example url for doing "pipenv install \-e -some\-repo\-url#egg=something", it was missing the "egg=" in the fragment +some\-repository\-url#egg=something", it was missing the "egg=" in the fragment identifier. \fI\%#2792\fP .IP \(bu 2 Fixed link to the "be cordial" essay in the contribution documentation. \fI\%#2793\fP @@ -915,36 +1768,36 @@ Replace reference to uservoice with PEEP\-000 \fI\%#2909\fP .IP \(bu 2 All calls to \fBpipenv shell\fP are now implemented from the ground up using \fI\%shellingham\fP, a custom library which was purpose built to handle edge cases and shell detection. \fI\%#2371\fP .IP \(bu 2 -Added support for python 3.7 via a few small compatibility / bugfixes. \fI\%#2427\fP, +Added support for python 3.7 via a few small compatibility / bug fixes. \fI\%#2427\fP, \fI\%#2434\fP, \fI\%#2436\fP .IP \(bu 2 Added new flag \fBpipenv \-\-support\fP to replace the diagnostic command \fBpython \-m pipenv.help\fP\&. \fI\%#2477\fP, \fI\%#2478\fP .IP \(bu 2 -Improved import times and CLI runtimes with minor tweaks. \fI\%#2485\fP +Improved import times and CLI run times with minor tweaks. \fI\%#2485\fP .UNINDENT .SS Bug Fixes .INDENT 0.0 .IP \(bu 2 -Fixed an ongoing bug which sometimes resolved incompatible versions into lockfiles. \fI\%#1901\fP +Fixed an ongoing bug which sometimes resolved incompatible versions into the project Lockfile. \fI\%#1901\fP .IP \(bu 2 Fixed a bug which caused errors when creating virtualenvs which contained leading dash characters. \fI\%#2415\fP .IP \(bu 2 -Fixed a logic error which caused \fB\-\-deploy \-\-system\fP to overwrite editable vcs packages in the pipfile before installing, which caused any installation to fail by default. \fI\%#2417\fP +Fixed a logic error which caused \fB\-\-deploy \-\-system\fP to overwrite editable vcs packages in the Pipfile before installing, which caused any installation to fail by default. \fI\%#2417\fP .IP \(bu 2 Updated requirementslib to fix an issue with properly quoting markers in VCS requirements. \fI\%#2419\fP .IP \(bu 2 Installed new vendored jinja2 templates for \fBclick\-completion\fP which were causing template errors for users with completion enabled. \fI\%#2422\fP .IP \(bu 2 -Added support for python 3.7 via a few small compatibility / bugfixes. \fI\%#2427\fP +Added support for python 3.7 via a few small compatibility / bug fixes. \fI\%#2427\fP .IP \(bu 2 Fixed an issue reading package names from \fBsetup.py\fP files in projects which imported utilities such as \fBversioneer\fP\&. \fI\%#2433\fP .IP \(bu 2 Pipenv will now ensure that its internal package names registry files are written with unicode strings. \fI\%#2450\fP .IP \(bu 2 Fixed a bug causing requirements input as relative paths to be output as absolute paths or URIs. -Fixed a bug affecting normalization of \fBgit+git@host\fP uris. \fI\%#2453\fP +Fixed a bug affecting normalization of \fBgit+git@host\fP URLs. \fI\%#2453\fP .IP \(bu 2 Pipenv will now always use \fBpathlib2\fP for \fBPath\fP based filesystem interactions by default on \fBpython<3.5\fP\&. \fI\%#2454\fP .IP \(bu 2 @@ -1001,7 +1854,7 @@ Added nested JSON output to the \fBpipenv graph\fP command. \fI\%#2199\fP Dropped vendored pip 9 and vendored, patched, and migrated to pip 10. Updated patched piptools version. \fI\%#2255\fP .IP \(bu 2 -PyPI mirror URLs can now be set to override instances of PyPI urls by passing +PyPI mirror URLs can now be set to override instances of PyPI URLs by passing the \fB\-\-pypi\-mirror\fP argument from the command line or setting the \fBPIPENV_PYPI_MIRROR\fP environment variable. \fI\%#2281\fP .IP \(bu 2 @@ -1033,8 +1886,8 @@ specific CVEs. \fI\%#2408\fP .INDENT 0.0 .IP \(bu 2 Pipenv will now parse & capitalize \fBplatform_python_implementation\fP markers -.. warning:: This could cause an issue if you have an out of date \fBPipfile\fP -which lowercases the comparison value (e.g. \fBcpython\fP instead of +\&.. warning:: This could cause an issue if you have an out of date \fBPipfile\fP +which lower\-cases the comparison value (e.g. \fBcpython\fP instead of \fBCPython\fP). \fI\%#2123\fP .IP \(bu 2 Pipenv will now only search for \fBrequirements.txt\fP files when creating new @@ -1081,7 +1934,7 @@ locked have been fixed. \fI\%#2267\fP .IP \(bu 2 Fixed a bug causing pipenv graph to fail to display sometimes. \fI\%#2268\fP .IP \(bu 2 -Updated \fBrequirementslib\fP to fix a bug in pipfile parsing affecting +Updated \fBrequirementslib\fP to fix a bug in Pipfile parsing affecting relative path conversions. \fI\%#2269\fP .IP \(bu 2 Windows executable discovery now leverages \fBos.pathext\fP\&. \fI\%#2298\fP @@ -1096,10 +1949,10 @@ VCS dependencies are now manually obtained only if they do not match the requested ref. \fI\%#2304\fP .IP \(bu 2 Added error handling functionality to properly cope with single\-digit -\fBRequires\-Python\fP metatdata with no specifiers. \fI\%#2377\fP +\fBRequires\-Python\fP metadata with no specifiers. \fI\%#2377\fP .IP \(bu 2 \fBpipenv update\fP will now always run the resolver and lock before ensuring -your dependencies are in sync with your lockfile. \fI\%#2379\fP +dependencies are in sync with project Lockfile. \fI\%#2379\fP .IP \(bu 2 Resolved a bug in our patched resolvers which could cause nondeterministic resolution failures in certain conditions. Running \fBpipenv install\fP with no @@ -1129,7 +1982,7 @@ custom certificate settings. \fI\%#2193\fP Dropped vendored pip 9 and vendored, patched, and migrated to pip 10. Updated patched piptools version. \fI\%#2255\fP .IP \(bu 2 -Updated \fBrequirementslib\fP to fix a bug in pipfile parsing affecting +Updated \fBrequirementslib\fP to fix a bug in Pipfile parsing affecting relative path conversions. \fI\%#2269\fP .IP \(bu 2 Added custom shell detection library \fBshellingham\fP, a port of our changes @@ -1190,7 +2043,7 @@ Automatically generates a \fBPipfile\fP, if one doesn\(aqt exist. .IP \(bu 2 Automatically creates a virtualenv in a standard location. .IP \(bu 2 -Automatically adds/removes packages to a \fBPipfile\fP when they are un/installed. +Automatically adds/removes packages to a \fBPipfile\fP when they are installed or uninstalled. .IP \(bu 2 Automatically loads \fB\&.env\fP files, if they exist. .UNINDENT @@ -1218,7 +2071,7 @@ Otherwise, whatever virtualenv defaults to will be the default. .IP \(bu 2 \fBrun\fP will run a given command from the virtualenv, with any arguments forwarded (e.g. \fB$ pipenv run python\fP or \fB$ pipenv run pip freeze\fP). .IP \(bu 2 -\fBcheck\fP checks for security vulnerabilities and asserts that PEP 508 requirements are being met by the current environment. +\fBcheck\fP checks for security vulnerabilities and asserts that \fI\%PEP 508\fP requirements are being met by the current environment. .UNINDENT .SH FURTHER DOCUMENTATION GUIDES .SS Basic Usage of Pipenv @@ -1227,6 +2080,12 @@ Otherwise, whatever virtualenv defaults to will be the default. This document covers some of Pipenv\(aqs more basic features. .SS ☤ Example Pipfile & Pipfile.lock .sp +Pipfiles contain information for the dependencies of the project, and supersedes +the requirements.txt file used in most Python projects. You should add a Pipfile in the +Git repository letting users who clone the repository know the only thing required would be +installing Pipenv in the machine and typing \fBpipenv install\fP\&. Pipenv is a reference +implementation for using Pipfile. +.sp Here is a simple example of a \fBPipfile\fP and the resulting \fBPipfile.lock\fP\&. .SS Example Pipfile .INDENT 0.0 @@ -1348,7 +2207,7 @@ Generally, keep both \fBPipfile\fP and \fBPipfile.lock\fP in version control. .IP \(bu 2 Do not keep \fBPipfile.lock\fP in version control if multiple versions of Python are being targeted. .IP \(bu 2 -Specify your target Python version in your \fIPipfile\fP\(aqs \fB[requires]\fP section. Ideally, you should only have one target Python version, as this is a deployment tool. +Specify your target Python version in your \fIPipfile\fP\(aqs \fB[requires]\fP section. Ideally, you should only have one target Python version, as this is a deployment tool. \fBpython_version\fP should be in the format \fBX.Y\fP (or \fBX\fP) and \fBpython_full_version\fP should be in \fBX.Y.Z\fP format. .IP \(bu 2 \fBpipenv install\fP is fully compatible with \fBpip install\fP syntax, for which the full documentation can be found \fI\%here\fP\&. .IP \(bu 2 @@ -1446,7 +2305,7 @@ For example, to install requests you can use: .sp .nf .ft C -$ pipenv install requests~=1.2 # equivalent to requests~=1.2.0 +$ pipenv install requests~=1.2 .ft P .fi .UNINDENT @@ -1487,7 +2346,7 @@ The use of \fB~=\fP is preferred over the \fB==\fP identifier as the latter prev .sp .nf .ft C -$ pipenv install "requests~=2.2" # locks the major version of the package (this is equivalent to using ==2.*) +$ pipenv install "requests~=2.2" # locks the major version of the package (this is equivalent to using >=2.2, ==2.*) .ft P .fi .UNINDENT @@ -1657,6 +2516,8 @@ is unique. .IP \(bu 2 \fB\-\-system\fP — Use the system \fBpip\fP command rather than the one from your virtualenv. .IP \(bu 2 +\fB\-\-deploy\fP — Make sure the packages are properly locked in Pipfile.lock, and abort if the lock file is out\-of\-date. +.IP \(bu 2 \fB\-\-ignore\-pipfile\fP — Ignore the \fBPipfile\fP and install from the \fBPipfile.lock\fP\&. .IP \(bu 2 \fB\-\-skip\-lock\fP — Ignore the \fBPipfile.lock\fP and install from the \fBPipfile\fP\&. In addition, do not write out a \fBPipfile.lock\fP reflecting changes to the \fBPipfile\fP\&. @@ -1724,7 +2585,7 @@ You can install packages with pipenv from git and other version control systems .UNINDENT .UNINDENT .sp -The only optional section is the \fB@<branch_or_tag>\fP section. When using git over SSH, you may use the shorthand vcs and scheme alias \fBgit+git@<location>:<user_or_organization>/<repository>@<branch_or_tag>#<package_name>\fP\&. Note that this is translated to \fBgit+ssh://git@<location>\fP when parsed. +The only optional section is the \fB@<branch_or_tag>\fP section. When using git over SSH, you may use the shorthand vcs and scheme alias \fBgit+git@<location>:<user_or_organization>/<repository>@<branch_or_tag>#egg=<package_name>\fP\&. Note that this is translated to \fBgit+ssh://git@<location>\fP when parsed. .sp Note that it is \fBstrongly recommended\fP that you install any version\-controlled dependencies in editable mode, using \fBpipenv install \-e\fP, in order to ensure that dependency resolution can be performed with an up to date copy of the repository each time it is performed, and that it includes all known dependencies. .sp @@ -1768,8 +2629,8 @@ production environments for reproducible builds. .INDENT 0.0 .INDENT 3.5 If you\(aqd like a \fBrequirements.txt\fP output of the lockfile, run \fB$ pipenv lock \-r\fP\&. -This will include all hashes, however (which is great!). To get a \fBrequirements.txt\fP -without hashes, use \fB$ pipenv run pip freeze\fP\&. +This will not include hashes, however. To get a \fBrequirements.txt\fP +you can also use \fB$ pipenv run pip freeze\fP\&. .UNINDENT .UNINDENT .SS Advanced Usage of Pipenv @@ -1794,7 +2655,7 @@ If you\(aqd like a specific package to be installed with a specific package inde .nf .ft C [[source]] -url = "https://pypi.python.org/simple" +url = "https://pypi.org/simple" verify_ssl = true name = "pypi" @@ -1817,7 +2678,7 @@ records = "*" Very fancy. .SS ☤ Using a PyPI Mirror .sp -If you\(aqd like to override the default PyPI index urls with the url for a PyPI mirror, you can use the following: +If you would like to override the default PyPI index URLs with the URL for a PyPI mirror, you can use the following: .INDENT 0.0 .INDENT 3.5 .sp @@ -1860,18 +2721,26 @@ Luckily \- pipenv will hash your Pipfile \fIbefore\fP expanding environment variables (and, helpfully, will substitute the environment variables again when you install from the lock file \- so no need to commit any secrets! Woo!) .sp -If your credentials contain a special character, surround the references to the environment variables with quotation marks. For example, if your password contain a double quotation mark, surround the password variable with single quotation marks. Otherwise, you may get a \fBValueError, "No closing quotation"\fP error while installing dependencies. +If your credentials contain special characters, make sure they are URL\-encoded as specified in \fI\%rfc3986\fP\&. +.sp +Environment variables may be specified as \fB${MY_ENVAR}\fP or \fB$MY_ENVAR\fP\&. +.sp +On Windows, \fB%MY_ENVAR%\fP is supported in addition to \fB${MY_ENVAR}\fP or \fB$MY_ENVAR\fP\&. +.sp +Environment variables in the URL part of requirement specifiers can also be expanded, where the variable must be in the form of \fB${VAR_NAME}\fP\&. Neither \fB$VAR_NAME\fP nor \fB%VAR_NAME%\fP is acceptable: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C -[[source]] -url = "https://$USERNAME:\(aq${PASSWORD}\(aq@mypypi.example.com/simple" +[[package]] +requests = {git = "git://${USERNAME}:${PASSWORD}@private.git.com/psf/requests.git", ref = "2.22.0"} .ft P .fi .UNINDENT .UNINDENT +.sp +Keep in mind that environment variables are expanded in runtime, leaving the entries in \fBPipfile\fP or \fBPipfile.lock\fP untouched. This is to avoid the accidental leakage of credentials in the source code. .SS ☤ Specifying Basically Anything .sp If you\(aqd like to specify that a specific package only be installed on certain systems, @@ -1994,7 +2863,9 @@ $ pipenv \-\-python=/path/to/python \-\-site\-packages .UNINDENT .SS ☤ Generating a \fBrequirements.txt\fP .sp -You can convert a \fBPipfile\fP and \fBPipfile.lock\fP into a \fBrequirements.txt\fP file very easily, and get all the benefits of extras and other goodies we have included. +You can convert a \fBPipfile\fP and \fBPipfile.lock\fP into a \fBrequirements.txt\fP +file very easily, and get all the benefits of extras and other goodies we have +included. .sp Let\(aqs take this \fBPipfile\fP: .INDENT 0.0 @@ -2008,12 +2879,15 @@ verify_ssl = true [packages] requests = {version="*"} + +[dev\-packages] +pytest = {version="*"} .ft P .fi .UNINDENT .UNINDENT .sp -And generate a \fBrequirements.txt\fP out of it: +And generate a set of requirements out of it with only the default dependencies: .INDENT 0.0 .INDENT 3.5 .sp @@ -2030,30 +2904,19 @@ urllib3==1.22 .UNINDENT .UNINDENT .sp -If you wish to generate a \fBrequirements.txt\fP with only the development requirements you can do that too! Let\(aqs take the following \fBPipfile\fP: -.INDENT 0.0 -.INDENT 3.5 -.sp -.nf -.ft C -[[source]] -url = "https://pypi.python.org/simple" -verify_ssl = true - -[dev\-packages] -pytest = {version="*"} -.ft P -.fi -.UNINDENT -.UNINDENT -.sp -And generate a \fBrequirements.txt\fP out of it: +As with other commands, passing \fB\-\-dev\fP will include both the default and +development dependencies: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ pipenv lock \-r \-\-dev +chardet==3.0.4 +requests==2.18.4 +certifi==2017.7.27.1 +idna==2.6 +urllib3==1.22 py==1.4.34 pytest==3.2.3 .ft P @@ -2061,7 +2924,44 @@ pytest==3.2.3 .UNINDENT .UNINDENT .sp -Very fancy. +Finally, if you wish to generate a requirements file with only the +development requirements you can do that too, using the \fB\-\-dev\-only\fP +flag: +.INDENT 0.0 +.INDENT 3.5 +.sp +.nf +.ft C +$ pipenv lock \-r \-\-dev\-only +py==1.4.34 +pytest==3.2.3 +.ft P +.fi +.UNINDENT +.UNINDENT +.sp +The locked requirements are written to stdout, with shell output redirection +used to write them to a file: +.INDENT 0.0 +.INDENT 3.5 +.sp +.nf +.ft C +$ pipenv lock \-r > requirements.txt +$ pipenv lock \-r \-\-dev\-only > dev\-requirements.txt +$ cat requirements.txt +chardet==3.0.4 +requests==2.18.4 +certifi==2017.7.27.1 +idna==2.6 +urllib3==1.22 +$ cat dev\-requirements.txt +py==1.4.34 +pytest==3.2.3 +.ft P +.fi +.UNINDENT +.UNINDENT .SS ☤ Detection of Security Vulnerabilities .sp Pipenv includes the \fI\%safety\fP package, and will use it to scan your dependency graph @@ -2078,9 +2978,9 @@ $ cat Pipfile django = "==1.10.1" $ pipenv check -Checking PEP 508 requirements… +Checking PEP 508 requirements... Passed! -Checking installed package safety… +Checking installed package safety... 33075: django >=1.10,<1.10.3 resolved (1.10.1 installed)! Django before 1.8.x before 1.8.16, 1.9.x before 1.9.11, and 1.10.x before 1.10.3, when settings.DEBUG is True, allow remote attackers to conduct DNS rebinding attacks by leveraging failure to validate the HTTP Host header against settings.ALLOWED_HOSTS. @@ -2121,16 +3021,15 @@ hardened for production use and should be used only as a development aid. \fBNOTE:\fP .INDENT 0.0 .INDENT 3.5 -In order to enable this functionality while maintaining its permissive -copyright license, \fIpipenv\fP embeds an API client key for the backend -Safety API operated by pyup.io rather than including a full copy of the -CC\-BY\-NC\-SA licensed Safety\-DB database. This embedded client key is -shared across all \fIpipenv check\fP users, and hence will be subject to -API access throttling based on overall usage rather than individual -client usage. +Each month, \fIPyUp.io\fP updates the \fBsafety\fP database of +insecure Python packages and \fI\%makes it available to the +community for free\fP\&. Pipenv +makes an API call to retrieve those results and use them +each time you run \fBpipenv check\fP to show you vulnerable +dependencies. .sp -You can also use your own safety API key by setting the -environment variable \fBPIPENV_PYUP_API_KEY\fP\&. +For more up\-to\-date vulnerability data, you may also use your own safety +API key by setting the environment variable \fBPIPENV_PYUP_API_KEY\fP\&. .UNINDENT .UNINDENT .SS ☤ Community Integrations @@ -2170,7 +3069,7 @@ Pipenv allows you to open any Python module that is installed (including ones in .nf .ft C $ pipenv install \-e git+https://github.com/kennethreitz/background.git#egg=background -Installing \-e git+https://github.com/kennethreitz/background.git#egg=background… +Installing \-e git+https://github.com/kennethreitz/background.git#egg=background... \&... Updated Pipfile.lock! @@ -2213,17 +3112,17 @@ requests = "*" python_version = "3.6" $ pipenv install -Warning: Python 3.6 was not found on your system… +Warning: Python 3.6 was not found on your system... Would you like us to install latest CPython 3.6 with pyenv? [Y/n]: y -Installing CPython 3.6.2 with pyenv (this may take a few minutes)… +Installing CPython 3.6.2 with pyenv (this may take a few minutes)... \&... -Making Python installation global… -Creating a virtualenv for this project… -Using /Users/kennethreitz/.pyenv/shims/python3 to create virtualenv… +Making Python installation global... +Creating a virtualenv for this project... +Using /Users/kennethreitz/.pyenv/shims/python3 to create virtualenv... \&... No package provided, installing all dependencies. \&... -Installing dependencies from Pipfile.lock… +Installing dependencies from Pipfile.lock... 🐍 ❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒❒ 5/5 — 00:00:03 To activate this project\(aqs virtualenv, run the following: $ pipenv shell @@ -2247,7 +3146,7 @@ $ cat .env HELLO=WORLD⏎ $ pipenv run python -Loading .env environment variables… +Loading .env environment variables... Python 2.7.13 (default, Jul 18 2017, 09:17:00) [GCC 4.2.1 Compatible Apple LLVM 8.1.0 (clang\-802.0.42)] on darwin Type "help", "copyright", "credits" or "license" for more information. @@ -2259,6 +3158,28 @@ Type "help", "copyright", "credits" or "license" for more information. .UNINDENT .UNINDENT .sp +Shell like variable expansion is available in \fB\&.env\fP files using \fI${VARNAME}\fP syntax.: +.INDENT 0.0 +.INDENT 3.5 +.sp +.nf +.ft C +$ cat .env +CONFIG_PATH=${HOME}/.config/foo + +$ pipenv run python +Loading .env environment variables... +Python 3.7.6 (default, Dec 19 2019, 22:52:49) +[GCC 9.2.1 20190827 (Red Hat 9.2.1\-1)] on linux +Type "help", "copyright", "credits" or "license" for more information. +>>> import os +>>> os.environ[\(aqCONFIG_PATH\(aq] +\(aq/home/kennethreitz/.config/foo\(aq +.ft P +.fi +.UNINDENT +.UNINDENT +.sp This is very useful for keeping production credentials out of your codebase. We do not recommend committing \fB\&.env\fP files into source control! .sp @@ -2285,6 +3206,8 @@ $ PIPENV_DONT_LOAD_ENV=1 pipenv shell .fi .UNINDENT .UNINDENT +.sp +See \fI\%theskumar/python\-dotenv\fP for more information on \fB\&.env\fP files. .SS ☤ Custom Script Shortcuts .sp Pipenv supports creating custom shortcuts in the (optional) \fB[scripts]\fP section of your Pipfile. @@ -2325,41 +3248,37 @@ For example: .sp .nf .ft C +[scripts] +echospam = "echo I am really a very silly example" +.ft P +.fi +.UNINDENT +.UNINDENT +.INDENT 0.0 +.INDENT 3.5 +.sp +.nf +.ft C $ pipenv run echospam "indeed" I am really a very silly example indeed .ft P .fi .UNINDENT .UNINDENT -.SS ☤ Support for Environment Variables .sp -Pipenv supports the usage of environment variables in place of authentication fragments -in your Pipfile. These will only be parsed if they are present in the \fB[[source]]\fP -section. For example: +You can then display the names and commands of your shortcuts by running \fBpipenv scripts\fP in your terminal. .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C -[[source]] -url = "https://${PYPI_USERNAME}:${PYPI_PASSWORD}@my_private_repo.example.com/simple" -verify_ssl = true -name = "pypi" - -[dev\-packages] - -[packages] -requests = {version="*", index="home"} -maya = {version="*", index="pypi"} -records = "*" +$ pipenv scripts +command script +echospam echo I am really a very silly example .ft P .fi .UNINDENT .UNINDENT -.sp -Environment variables may be specified as \fB${MY_ENVAR}\fP or \fB$MY_ENVAR\fP\&. -.sp -On Windows, \fB%MY_ENVAR%\fP is supported in addition to \fB${MY_ENVAR}\fP or \fB$MY_ENVAR\fP\&. .SS ☤ Configuration With Environment Variables .sp Pipenv comes with a handful of options that can be enabled via shell environment @@ -2367,13 +3286,6 @@ variables. To activate them, simply create the variable in your shell and pipenv will detect it. .INDENT 0.0 .TP -.B pipenv.environments.PIPENV_CACHE_DIR = \(aq/Users/fming/Library/Caches/pipenv\(aq -Location for Pipenv to store it\(aqs package cache. -.sp -Default is to use appdir\(aqs user cache directory. -.UNINDENT -.INDENT 0.0 -.TP .B pipenv.environments.PIPENV_COLORBLIND = False If set, disable terminal colors. .sp @@ -2382,45 +3294,6 @@ to show colors. .UNINDENT .INDENT 0.0 .TP -.B pipenv.environments.PIPENV_DEFAULT_PYTHON_VERSION = None -Use this Python version when creating new virtual environments by default. -.sp -This can be set to a version string, e.g. \fB3.6\fP, or a path. Default is to use -whatever Python Pipenv is installed under (i.e. \fBsys.executable\fP). Command -line flags (e.g. \fB\-\-python\fP, \fB\-\-three\fP, and \fB\-\-two\fP) are prioritized over -this configuration. -.UNINDENT -.INDENT 0.0 -.TP -.B pipenv.environments.PIPENV_DONT_LOAD_ENV = False -If set, Pipenv does not load the \fB\&.env\fP file. -.sp -Default is to load \fB\&.env\fP for \fBrun\fP and \fBshell\fP commands. -.UNINDENT -.INDENT 0.0 -.TP -.B pipenv.environments.PIPENV_DONT_USE_PYENV = False -If set, Pipenv does not attempt to install Python with pyenv. -.sp -Default is to install Python automatically via pyenv when needed, if possible. -.UNINDENT -.INDENT 0.0 -.TP -.B pipenv.environments.PIPENV_DOTENV_LOCATION = None -If set, Pipenv loads the \fB\&.env\fP file at the specified location. -.sp -Default is to load \fB\&.env\fP from the project root, if found. -.UNINDENT -.INDENT 0.0 -.TP -.B pipenv.environments.PIPENV_EMULATOR = \(aq\(aq -If set, the terminal emulator\(aqs name for \fBpipenv shell\fP to use. -.sp -Default is to detect emulators automatically. This should be set if your -emulator, e.g. Cmder, cannot be detected correctly. -.UNINDENT -.INDENT 0.0 -.TP .B pipenv.environments.PIPENV_HIDE_EMOJIS = False Disable emojis in output. .sp @@ -2428,155 +3301,46 @@ Default is to show emojis. This is automatically set on Windows. .UNINDENT .INDENT 0.0 .TP -.B pipenv.environments.PIPENV_IGNORE_VIRTUALENVS = False -If set, Pipenv will always assign a virtual environment for this project. -.sp -By default, Pipenv tries to detect whether it is run inside a virtual -environment, and reuses it if possible. This is usually the desired behavior, -and enables the user to use any user\-built environments with Pipenv. +.B pipenv.environments.env_to_bool(val) +Convert \fBval\fP to boolean, returning True if truthy or False if falsey +.INDENT 7.0 +.TP +.B Parameters +\fBval\fP (\fIAny\fP) \-\- The value to convert +.TP +.B Returns +False if Falsey, True if truthy +.TP +.B Return type +bool +.UNINDENT .UNINDENT .INDENT 0.0 .TP -.B pipenv.environments.PIPENV_INSTALL_TIMEOUT = 900 -Max number of seconds to wait for package installation. +.B pipenv.environments.get_from_env(arg, prefix=\(aqPIPENV\(aq, check_for_negation=True) +Check the environment for a variable, returning its truthy or stringified value .sp -Defaults to 900 (15 minutes), a very long arbitrary time. -.UNINDENT -.INDENT 0.0 +For example, setting \fBPIPENV_NO_RESOLVE_VCS=1\fP would mean that +\fBget_from_env("RESOLVE_VCS", prefix="PIPENV")\fP would return \fBFalse\fP\&. +.INDENT 7.0 .TP -.B pipenv.environments.PIPENV_MAX_DEPTH = 4 -Maximum number of directories to recursively search for a Pipfile. -.sp -Default is 3. See also \fBPIPENV_NO_INHERIT\fP\&. +.B Parameters +.INDENT 7.0 +.IP \(bu 2 +\fBarg\fP (\fIstr\fP) \-\- The name of the variable to look for +.IP \(bu 2 +\fBprefix\fP (\fIstr\fP) \-\- The prefix to attach to the variable, defaults to "PIPENV" +.IP \(bu 2 +\fBcheck_for_negation\fP (\fIbool\fP) \-\- Whether to check for \fB<PREFIX>_NO_<arg>\fP, defaults +to True .UNINDENT -.INDENT 0.0 .TP -.B pipenv.environments.PIPENV_MAX_RETRIES = 0 -Specify how many retries Pipenv should attempt for network requests. -.sp -Default is 0. Automatically set to 1 on CI environments for robust testing. +.B Returns +The value from the environment if available +.TP +.B Return type +Optional[Union[str, bool]] .UNINDENT -.INDENT 0.0 -.TP -.B pipenv.environments.PIPENV_MAX_ROUNDS = 16 -Tells Pipenv how many rounds of resolving to do for Pip\-Tools. -.sp -Default is 16, an arbitrary number that works most of the time. -.UNINDENT -.INDENT 0.0 -.TP -.B pipenv.environments.PIPENV_MAX_SUBPROCESS = 8 -How many subprocesses should Pipenv use when installing. -.sp -Default is 16, an arbitrary number that seems to work. -.UNINDENT -.INDENT 0.0 -.TP -.B pipenv.environments.PIPENV_NOSPIN = False -If set, disable terminal spinner. -.sp -This can make the logs cleaner. Automatically set on Windows, and in CI -environments. -.UNINDENT -.INDENT 0.0 -.TP -.B pipenv.environments.PIPENV_NO_INHERIT = False -Tell Pipenv not to inherit parent directories. -.sp -This is useful for deployment to avoid using the wrong current directory. -Overwrites \fBPIPENV_MAX_DEPTH\fP\&. -.UNINDENT -.INDENT 0.0 -.TP -.B pipenv.environments.PIPENV_PIPFILE = None -If set, this specifies a custom Pipfile location. -.sp -When running pipenv from a location other than the same directory where the -Pipfile is located, instruct pipenv to find the Pipfile in the location -specified by this environment variable. -.sp -Default is to find Pipfile automatically in the current and parent directories. -See also \fBPIPENV_MAX_DEPTH\fP\&. -.UNINDENT -.INDENT 0.0 -.TP -.B pipenv.environments.PIPENV_PYPI_MIRROR = \(aqhttps://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple\(aq -If set, tells pipenv to override PyPI index urls with a mirror. -.sp -Default is to not mirror PyPI, i.e. use the real one, pypi.org. The -\fB\-\-pypi\-mirror\fP command line flag overwrites this. -.UNINDENT -.INDENT 0.0 -.TP -.B pipenv.environments.PIPENV_RESOLVE_VCS = False -Tells Pipenv whether to resolve all VCS dependencies in full. -.sp -As of Pipenv 2018.11.26, only editable VCS dependencies were resolved in full. -To retain this behavior and avoid handling any conflicts that arise from the new -approach, you may set this to \(aq0\(aq, \(aqoff\(aq, or \(aqfalse\(aq. -.UNINDENT -.INDENT 0.0 -.TP -.B pipenv.environments.PIPENV_SHELL = \(aq/bin/zsh\(aq -An absolute path to the preferred shell for \fBpipenv shell\fP\&. -.sp -Default is to detect automatically what shell is currently in use. -.UNINDENT -.INDENT 0.0 -.TP -.B pipenv.environments.PIPENV_SHELL_FANCY = False -If set, always use fancy mode when invoking \fBpipenv shell\fP\&. -.sp -Default is to use the compatibility shell if possible. -.UNINDENT -.INDENT 0.0 -.TP -.B pipenv.environments.PIPENV_SKIP_LOCK = False -If set, Pipenv won\(aqt lock dependencies automatically. -.sp -This might be desirable if a project has large number of dependencies, -because locking is an inherently slow operation. -.sp -Default is to lock dependencies and update \fBPipfile.lock\fP on each run. -.sp -NOTE: This only affects the \fBinstall\fP and \fBuninstall\fP commands. -.UNINDENT -.INDENT 0.0 -.TP -.B pipenv.environments.PIPENV_SPINNER = \(aqdots\(aq -Sets the default spinner type. -.sp -Spinners are identitcal to the node.js spinners and can be found at -\fI\%https://github.com/sindresorhus/cli\-spinners\fP -.UNINDENT -.INDENT 0.0 -.TP -.B pipenv.environments.PIPENV_TIMEOUT = 120 -Max number of seconds Pipenv will wait for virtualenv creation to complete. -.sp -Default is 120 seconds, an arbitrary number that seems to work. -.UNINDENT -.INDENT 0.0 -.TP -.B pipenv.environments.PIPENV_VENV_IN_PROJECT = False -If set, creates \fB\&.venv\fP in your project directory. -.sp -Default is to create new virtual environments in a global location. -.UNINDENT -.INDENT 0.0 -.TP -.B pipenv.environments.PIPENV_YES = False -If set, Pipenv automatically assumes "yes" at all prompts. -.sp -Default is to prompt the user for an answer if the current command line session -if interactive. -.UNINDENT -.INDENT 0.0 -.TP -.B pipenv.environments.PIP_EXISTS_ACTION = \(aqw\(aq -Specifies the value for pip\(aqs \-\-exists\-action option -.sp -Defaullts to (w)ipe .UNINDENT .INDENT 0.0 .TP @@ -2591,6 +3355,11 @@ True or false depending on whether we are in a regular virtualenv or not bool .UNINDENT .UNINDENT +.INDENT 0.0 +.TP +.B pipenv.environments.is_using_venv() -> bool +Check for venv\-based virtual environment which sets sys.base_prefix +.UNINDENT .sp If you\(aqd like to set these environment variables on a per\-project basis, I recommend utilizing the fantastic \fI\%direnv\fP project, in order to do so. .sp @@ -2628,7 +3397,7 @@ In addition, you can also have Pipenv stick the virtualenv in \fBproject/.venv\f .sp Pipenv is being used in projects like \fI\%Requests\fP for declaring development dependencies and running the test suite. .sp -We\(aqve currently tested deployments with both \fI\%Travis\-CI\fP and \fI\%tox\fP with success. +We have currently tested deployments with both \fI\%Travis\-CI\fP and \fI\%tox\fP with success. .SS Travis CI .sp An example Travis CI setup can be found in \fI\%Requests\fP\&. The project uses a Makefile to @@ -2722,25 +3491,37 @@ probably a good idea in any case. A 3rd party plugin, \fI\%tox\-pipenv\fP is also available to use Pipenv natively with tox. .SS ☤ Shell Completion .sp -To enable completion in fish, add this to your config: +To enable completion in fish, add this to your configuration: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C -eval (pipenv \-\-completion) +eval (env _PIPENV_COMPLETE=fish_source pipenv) .ft P .fi .UNINDENT .UNINDENT .sp -Alternatively, with bash or zsh, add this to your config: +Alternatively, with zsh, add this to your configuration: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C -eval "$(pipenv \-\-completion)" +eval "$(_PIPENV_COMPLETE=zsh_source pipenv)" +.ft P +.fi +.UNINDENT +.UNINDENT +.sp +Alternatively, with bash, add this to your configuration: +.INDENT 0.0 +.INDENT 3.5 +.sp +.nf +.ft C +eval "$(_PIPENV_COMPLETE=bash_source pipenv)" .ft P .fi .UNINDENT @@ -2785,9 +3566,9 @@ $ PIP_IGNORE_INSTALLED=1 pipenv install \-\-dev .sp There is a subtle but very important distinction to be made between \fBapplications\fP and \fBlibraries\fP\&. This is a very common source of confusion in the Python community. .sp -Libraries provide reusable functionality to other libraries and applications (let\(aqs use the umbrella term \fBprojects\fP here). They are required to work alongside other libraries, all with their own set of subdependencies. They define \fBabstract dependencies\fP\&. To avoid version conflicts in subdependencies of different libraries within a project, libraries should never ever pin dependency versions. Although they may specify lower or (less frequently) upper bounds, if they rely on some specific feature/fix/bug. Library dependencies are specified via \fBinstall_requires\fP in \fBsetup.py\fP\&. +Libraries provide reusable functionality to other libraries and applications (let\(aqs use the umbrella term \fBprojects\fP here). They are required to work alongside other libraries, all with their own set of sub\-dependencies. They define \fBabstract dependencies\fP\&. To avoid version conflicts in sub\-dependencies of different libraries within a project, libraries should never ever pin dependency versions. Although they may specify lower or (less frequently) upper bounds, if they rely on some specific feature/fix/bug. Library dependencies are specified via \fBinstall_requires\fP in \fBsetup.py\fP\&. .sp -Libraries are ultimately meant to be used in some \fBapplication\fP\&. Applications are different in that they usually are not depended on by other projects. They are meant to be deployed into some specific environment and only then should the exact versions of all their dependencies and subdependencies be made concrete. To make this process easier is currently the main goal of Pipenv. +Libraries are ultimately meant to be used in some \fBapplication\fP\&. Applications are different in that they usually are not depended on by other projects. They are meant to be deployed into some specific environment and only then should the exact versions of all their dependencies and sub\-dependencies be made concrete. To make this process easier is currently the main goal of Pipenv. .sp To summarize: .INDENT 0.0 @@ -2820,6 +3601,7 @@ You can force Pipenv to use a different cache location by setting the environmen .SS ☤ Changing Default Python Versions .sp By default, Pipenv will initialize a project using whatever version of python the python3 is. Besides starting a project with the \fB\-\-three\fP or \fB\-\-two\fP flags, you can also use \fBPIPENV_DEFAULT_PYTHON_VERSION\fP to specify what version to use when starting a project when \fB\-\-three\fP or \fB\-\-two\fP aren\(aqt used. +.SS Pipenv CLI Reference .SS Frequently Encountered Pipenv Problems .sp Pipenv is constantly being improved by volunteers, but is still a very young @@ -2863,7 +3645,7 @@ usually one of the following locations: \fB~/.cache/pipenv\fP (other operating systems) .UNINDENT .sp -Pipenv does not install prereleases (i.e. a version with an alpha/beta/etc. +Pipenv does not install pre\-releases (i.e. a version with an alpha/beta/etc. suffix, such as \fI1.0b1\fP) by default. You will need to pass the \fB\-\-pre\fP flag in your command, or set .INDENT 0.0 @@ -2892,14 +3674,9 @@ distributions, with version name like \fB3.6.4\fP or similar. .SS ☤ Pipenv does not respect pyenv’s global and local Python versions .sp Pipenv by default uses the Python it is installed against to create the -virtualenv. You can set the \fB\-\-python\fP option, or -\fB$PYENV_ROOT/shims/python\fP to let it consult pyenv when choosing the -interpreter. See specifying_versions for more information. -.sp -If you want Pipenv to automatically “do the right thing”, you can set the -environment variable \fBPIPENV_PYTHON\fP to \fB$PYENV_ROOT/shims/python\fP\&. This -will make Pipenv use pyenv’s active Python version to create virtual -environments by default. +virtualenv. You can set the \fB\-\-python\fP option to \fB$(pyenv which python)\fP +to use your current pyenv interpreter. See specifying_versions for more +information. .SS ☤ ValueError: unknown locale: UTF\-8 .sp macOS has a bug in its locale detection that prevents us from detecting your @@ -2941,22 +3718,6 @@ language/locale and encoding you use. .sp This may be related to your locale setting. See \fI\%☤ ValueError: unknown locale: UTF\-8\fP for a possible solution. -.SS ☤ \fBshell\fP does not show the virtualenv’s name in prompt -.sp -This is intentional. You can do it yourself with either shell plugins, or -clever \fBPS1\fP configuration. If you really want it back, use -.INDENT 0.0 -.INDENT 3.5 -.sp -.nf -.ft C -pipenv shell \-c -.ft P -.fi -.UNINDENT -.UNINDENT -.sp -instead (not available on Windows). .SS ☤ Pipenv does not respect dependencies in setup.py .sp No, it does not, intentionally. Pipfile and setup.py serve different purposes, @@ -2965,7 +3726,7 @@ for more information. .SS ☤ Using \fBpipenv run\fP in Supervisor program .sp When you configure a supervisor program\(aqs \fBcommand\fP with \fBpipenv run ...\fP, you -need to set locale enviroment variables properly to make it work. +need to set locale environment variables properly to make it work. .sp Add this line under \fB[supervisord]\fP section in \fB/etc/supervisor/supervisord.conf\fP: .INDENT 0.0 @@ -2979,7 +3740,7 @@ environment=LC_ALL=\(aqen_US.UTF\-8\(aq,LANG=\(aqen_US.UTF\-8\(aq .fi .UNINDENT .UNINDENT -.SS ☤ An exception is raised during \fBLocking dependencies…\fP +.SS ☤ An exception is raised during \fBLocking dependencies...\fP .sp Run \fBpipenv lock \-\-clear\fP and try again. The lock sequence caches results to speed up subsequent runs. The cache may contain faulty results if a bug @@ -2990,12 +3751,17 @@ the cache, and therefore removes the bad results. .sp Pipenv is an open but opinionated tool, created by an open but opinionated developer. .SS Management Style +.INDENT 0.0 +.INDENT 3.5 +\fBTo be updated (as of March 2020)\fP\&. +.UNINDENT +.UNINDENT .sp \fI\%Kenneth Reitz\fP is the BDFL. He has final say in any decision related to the Pipenv project. Kenneth is responsible for the direction and form of the library, as well as its presentation. In addition to making decisions based on technical merit, he is responsible for making decisions based on the development philosophy of Pipenv. .sp \fI\%Dan Ryan\fP, \fI\%Tzu\-ping Chung\fP, and \fI\%Nate Prewitt\fP are the core contributors. They are responsible for triaging bug reports, reviewing pull requests and ensuring that Kenneth is kept up to speed with developments around the library. -The day\-to\-day managing of the project is done by the core contributors. They are responsible for making judgements about whether or not a feature request is +The day\-to\-day managing of the project is done by the core contributors. They are responsible for making judgments about whether or not a feature request is likely to be accepted by Kenneth. .SS Values .INDENT 0.0 @@ -3108,10 +3874,10 @@ pipenv install \-\-dev .UNINDENT .UNINDENT .sp -This will install the repo version of Pipenv and then install the development +This will install the repository version of Pipenv and then install the development dependencies. Once that has completed, you can start developing. .sp -The repo version of Pipenv must be installed over other global versions to +The repository version of Pipenv must be installed over other global versions to resolve conflicts with the \fBpipenv\fP folder being implicitly added to \fBsys.path\fP\&. See \fI\%pypa/pipenv#2557\fP for more details. .SS Testing @@ -3142,7 +3908,7 @@ provide a test marker: \fBpytest \-m lock\fP .UNINDENT .SS Code Review .sp -Contributions will not be merged until they\(aqve been code reviewed. You should +Contributions will not be merged until they have been code reviewed. You should implement any code review feedback unless you strongly object to it. In the event that you object to the code review feedback, you should make your case clearly and calmly. If, after doing so, the feedback is judged to still apply, @@ -3179,7 +3945,7 @@ Avoid raising duplicate issues. \fIPlease\fP use the GitHub issue search feature to check whether your bug report or feature request has been mentioned in the past. Duplicate bug reports and feature requests are a huge maintenance burden on the limited resources of the project. If it is clear from your -report that you would have struggled to find the original, that\(aqs ok, but +report that you would have struggled to find the original, that\(aqs okay, but if searching for a selection of words in your issue title would have found the duplicate then the issue will likely be closed extremely abruptly. .IP 2. 3 @@ -3273,854 +4039,15 @@ unset PIP_FIND_LINKS .fi .UNINDENT .UNINDENT -.SH ☤ PIPENV USAGE -.SS pipenv -.INDENT 0.0 -.INDENT 3.5 -.sp -.nf -.ft C -pipenv [OPTIONS] COMMAND [ARGS]... -.ft P -.fi -.UNINDENT -.UNINDENT -Options.INDENT 0.0 -.TP -.B \-\-where -Output project home information. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-venv -Output virtualenv information. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-py -Output Python interpreter information. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-envs -Output Environment Variable options. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-rm -Remove the virtualenv. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-bare -Minimal output. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-completion -Output completion (to be eval\(aqd). -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-man -Display manpage. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-support -Output diagnostic information for use in GitHub issues. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-site\-packages -Enable site\-packages for the virtualenv. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-python <python> -Specify which version of Python virtualenv should use. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-three, \-\-two -Use Python 3/2 when creating virtualenv. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-clear -Clears caches (pipenv, pip, and pip\-tools). -.UNINDENT -.INDENT 0.0 -.TP -.B \-v, \-\-verbose -Verbose mode. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-pypi\-mirror <pypi_mirror> -Specify a PyPI mirror. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-version -Show the version and exit. -.UNINDENT -.SS check -.sp -Checks for security vulnerabilities and against PEP 508 markers provided in Pipfile. -.INDENT 0.0 -.INDENT 3.5 -.sp -.nf -.ft C -pipenv check [OPTIONS] [ARGS]... -.ft P -.fi -.UNINDENT -.UNINDENT -Options.INDENT 0.0 -.TP -.B \-\-unused <unused> -Given a code path, show potentially unused dependencies. -.UNINDENT -.INDENT 0.0 -.TP -.B \-i, \-\-ignore <ignore> -Ignore specified vulnerability during safety checks. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-python <python> -Specify which version of Python virtualenv should use. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-three, \-\-two -Use Python 3/2 when creating virtualenv. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-clear -Clears caches (pipenv, pip, and pip\-tools). -.UNINDENT -.INDENT 0.0 -.TP -.B \-v, \-\-verbose -Verbose mode. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-pypi\-mirror <pypi_mirror> -Specify a PyPI mirror. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-system -System pip management. -.UNINDENT -Arguments.INDENT 0.0 -.TP -.B ARGS -Optional argument(s) -.UNINDENT -.SS clean -.sp -Uninstalls all packages not specified in Pipfile.lock. -.INDENT 0.0 -.INDENT 3.5 -.sp -.nf -.ft C -pipenv clean [OPTIONS] -.ft P -.fi -.UNINDENT -.UNINDENT -Options.INDENT 0.0 -.TP -.B \-\-bare -Minimal output. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-dry\-run -Just output unneeded packages. -.UNINDENT -.INDENT 0.0 -.TP -.B \-v, \-\-verbose -Verbose mode. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-three, \-\-two -Use Python 3/2 when creating virtualenv. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-python <python> -Specify which version of Python virtualenv should use. -.UNINDENT -.SS graph -.sp -Displays currently\-installed dependency graph information. -.INDENT 0.0 -.INDENT 3.5 -.sp -.nf -.ft C -pipenv graph [OPTIONS] -.ft P -.fi -.UNINDENT -.UNINDENT -Options.INDENT 0.0 -.TP -.B \-\-bare -Minimal output. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-json -Output JSON. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-json\-tree -Output JSON in nested tree. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-reverse -Reversed dependency graph. -.UNINDENT -.SS install -.sp -Installs provided packages and adds them to Pipfile, or (if no packages are given), installs all packages from Pipfile. -.INDENT 0.0 -.INDENT 3.5 -.sp -.nf -.ft C -pipenv install [OPTIONS] [PACKAGES]... -.ft P -.fi -.UNINDENT -.UNINDENT -Options.INDENT 0.0 -.TP -.B \-\-system -System pip management. -.UNINDENT -.INDENT 0.0 -.TP -.B \-c, \-\-code <code> -Install packages automatically discovered from import statements. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-deploy -Abort if the Pipfile.lock is out\-of\-date, or Python version is wrong. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-skip\-lock -Skip locking mechanisms and use the Pipfile instead during operation. -.UNINDENT -.INDENT 0.0 -.TP -.B \-e, \-\-editable <editable> -An editable python package URL or path, often to a VCS repo. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-ignore\-pipfile -Ignore Pipfile when installing, using the Pipfile.lock. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-selective\-upgrade -Update specified packages. -.UNINDENT -.INDENT 0.0 -.TP -.B \-r, \-\-requirements <requirements> -Import a requirements.txt file. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-extra\-index\-url <extra_index_url> -URLs to the extra PyPI compatible indexes to query for package lookups. -.UNINDENT -.INDENT 0.0 -.TP -.B \-i, \-\-index <index> -Target PyPI\-compatible package index url. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-sequential -Install dependencies one\-at\-a\-time, instead of concurrently. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-keep\-outdated -Keep out\-dated dependencies from being updated in Pipfile.lock. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-pre -Allow pre\-releases. -.UNINDENT -.INDENT 0.0 -.TP -.B \-d, \-\-dev -Install both develop and default packages. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-python <python> -Specify which version of Python virtualenv should use. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-three, \-\-two -Use Python 3/2 when creating virtualenv. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-clear -Clears caches (pipenv, pip, and pip\-tools). -.UNINDENT -.INDENT 0.0 -.TP -.B \-v, \-\-verbose -Verbose mode. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-pypi\-mirror <pypi_mirror> -Specify a PyPI mirror. -.UNINDENT -Arguments.INDENT 0.0 -.TP -.B PACKAGES -Optional argument(s) -.UNINDENT -Environment variables.INDENT 0.0 -.TP -.B PIPENV_SKIP_LOCK -.INDENT 7.0 -.INDENT 3.5 -Provide a default for \fI\%\-\-skip\-lock\fP -.UNINDENT -.UNINDENT -.UNINDENT -.INDENT 0.0 -.TP -.B PIP_EXTRA_INDEX_URL -.INDENT 7.0 -.INDENT 3.5 -Provide a default for \fI\%\-\-extra\-index\-url\fP -.UNINDENT -.UNINDENT -.UNINDENT -.INDENT 0.0 -.TP -.B PIP_INDEX_URL -.INDENT 7.0 -.INDENT 3.5 -Provide a default for \fI\%\-i\fP -.UNINDENT -.UNINDENT -.UNINDENT -.SS lock -.sp -Generates Pipfile.lock. -.INDENT 0.0 -.INDENT 3.5 -.sp -.nf -.ft C -pipenv lock [OPTIONS] -.ft P -.fi -.UNINDENT -.UNINDENT -Options.INDENT 0.0 -.TP -.B \-r, \-\-requirements -Generate output in requirements.txt format. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-keep\-outdated -Keep out\-dated dependencies from being updated in Pipfile.lock. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-pre -Allow pre\-releases. -.UNINDENT -.INDENT 0.0 -.TP -.B \-d, \-\-dev -Install both develop and default packages. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-python <python> -Specify which version of Python virtualenv should use. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-three, \-\-two -Use Python 3/2 when creating virtualenv. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-clear -Clears caches (pipenv, pip, and pip\-tools). -.UNINDENT -.INDENT 0.0 -.TP -.B \-v, \-\-verbose -Verbose mode. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-pypi\-mirror <pypi_mirror> -Specify a PyPI mirror. -.UNINDENT -.SS open -.sp -View a given module in your editor. -.sp -This uses the EDITOR environment variable. You can temporarily override it, -for example: -.INDENT 0.0 -.INDENT 3.5 -EDITOR=atom pipenv open requests -.UNINDENT -.UNINDENT -.INDENT 0.0 -.INDENT 3.5 -.sp -.nf -.ft C -pipenv open [OPTIONS] MODULE -.ft P -.fi -.UNINDENT -.UNINDENT -Options.INDENT 0.0 -.TP -.B \-\-python <python> -Specify which version of Python virtualenv should use. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-three, \-\-two -Use Python 3/2 when creating virtualenv. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-clear -Clears caches (pipenv, pip, and pip\-tools). -.UNINDENT -.INDENT 0.0 -.TP -.B \-v, \-\-verbose -Verbose mode. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-pypi\-mirror <pypi_mirror> -Specify a PyPI mirror. -.UNINDENT -Arguments.INDENT 0.0 -.TP -.B MODULE -Required argument -.UNINDENT -.SS run -.sp -Spawns a command installed into the virtualenv. -.INDENT 0.0 -.INDENT 3.5 -.sp -.nf -.ft C -pipenv run [OPTIONS] COMMAND [ARGS]... -.ft P -.fi -.UNINDENT -.UNINDENT -Options.INDENT 0.0 -.TP -.B \-\-python <python> -Specify which version of Python virtualenv should use. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-three, \-\-two -Use Python 3/2 when creating virtualenv. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-clear -Clears caches (pipenv, pip, and pip\-tools). -.UNINDENT -.INDENT 0.0 -.TP -.B \-v, \-\-verbose -Verbose mode. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-pypi\-mirror <pypi_mirror> -Specify a PyPI mirror. -.UNINDENT -Arguments.INDENT 0.0 -.TP -.B COMMAND -Required argument -.UNINDENT -.INDENT 0.0 -.TP -.B ARGS -Optional argument(s) -.UNINDENT -.SS shell -.sp -Spawns a shell within the virtualenv. -.INDENT 0.0 -.INDENT 3.5 -.sp -.nf -.ft C -pipenv shell [OPTIONS] [SHELL_ARGS]... -.ft P -.fi -.UNINDENT -.UNINDENT -Options.INDENT 0.0 -.TP -.B \-\-fancy -Run in shell in fancy mode (for elegantly configured shells). -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-anyway -Always spawn a subshell, even if one is already spawned. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-pypi\-mirror <pypi_mirror> -Specify a PyPI mirror. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-three, \-\-two -Use Python 3/2 when creating virtualenv. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-python <python> -Specify which version of Python virtualenv should use. -.UNINDENT -Arguments.INDENT 0.0 -.TP -.B SHELL_ARGS -Optional argument(s) -.UNINDENT -.SS sync -.sp -Installs all packages specified in Pipfile.lock. -.INDENT 0.0 -.INDENT 3.5 -.sp -.nf -.ft C -pipenv sync [OPTIONS] -.ft P -.fi -.UNINDENT -.UNINDENT -Options.INDENT 0.0 -.TP -.B \-\-bare -Minimal output. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-sequential -Install dependencies one\-at\-a\-time, instead of concurrently. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-keep\-outdated -Keep out\-dated dependencies from being updated in Pipfile.lock. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-pre -Allow pre\-releases. -.UNINDENT -.INDENT 0.0 -.TP -.B \-d, \-\-dev -Install both develop and default packages. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-python <python> -Specify which version of Python virtualenv should use. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-three, \-\-two -Use Python 3/2 when creating virtualenv. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-clear -Clears caches (pipenv, pip, and pip\-tools). -.UNINDENT -.INDENT 0.0 -.TP -.B \-v, \-\-verbose -Verbose mode. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-pypi\-mirror <pypi_mirror> -Specify a PyPI mirror. -.UNINDENT -.SS uninstall -.sp -Un\-installs a provided package and removes it from Pipfile. -.INDENT 0.0 -.INDENT 3.5 -.sp -.nf -.ft C -pipenv uninstall [OPTIONS] [PACKAGES]... -.ft P -.fi -.UNINDENT -.UNINDENT -Options.INDENT 0.0 -.TP -.B \-\-all\-dev -Un\-install all package from [dev\-packages]. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-all -Purge all package(s) from virtualenv. Does not edit Pipfile. -.UNINDENT -.INDENT 0.0 -.TP -.B \-e, \-\-editable <editable> -An editable python package URL or path, often to a VCS repo. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-skip\-lock -Skip locking mechanisms and use the Pipfile instead during operation. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-keep\-outdated -Keep out\-dated dependencies from being updated in Pipfile.lock. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-pre -Allow pre\-releases. -.UNINDENT -.INDENT 0.0 -.TP -.B \-d, \-\-dev -Install both develop and default packages. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-python <python> -Specify which version of Python virtualenv should use. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-three, \-\-two -Use Python 3/2 when creating virtualenv. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-clear -Clears caches (pipenv, pip, and pip\-tools). -.UNINDENT -.INDENT 0.0 -.TP -.B \-v, \-\-verbose -Verbose mode. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-pypi\-mirror <pypi_mirror> -Specify a PyPI mirror. -.UNINDENT -Arguments.INDENT 0.0 -.TP -.B PACKAGES -Optional argument(s) -.UNINDENT -Environment variables.INDENT 0.0 -.TP -.B PIPENV_SKIP_LOCK -.INDENT 7.0 -.INDENT 3.5 -Provide a default for \fI\%\-\-skip\-lock\fP -.UNINDENT -.UNINDENT -.UNINDENT -.SS update -.sp -Runs lock, then sync. -.INDENT 0.0 -.INDENT 3.5 -.sp -.nf -.ft C -pipenv update [OPTIONS] [PACKAGES]... -.ft P -.fi -.UNINDENT -.UNINDENT -Options.INDENT 0.0 -.TP -.B \-\-bare -Minimal output. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-outdated -List out\-of\-date dependencies. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-dry\-run -List out\-of\-date dependencies. -.UNINDENT -.INDENT 0.0 -.TP -.B \-e, \-\-editable <editable> -An editable python package URL or path, often to a VCS repo. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-ignore\-pipfile -Ignore Pipfile when installing, using the Pipfile.lock. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-selective\-upgrade -Update specified packages. -.UNINDENT -.INDENT 0.0 -.TP -.B \-r, \-\-requirements <requirements> -Import a requirements.txt file. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-extra\-index\-url <extra_index_url> -URLs to the extra PyPI compatible indexes to query for package lookups. -.UNINDENT -.INDENT 0.0 -.TP -.B \-i, \-\-index <index> -Target PyPI\-compatible package index url. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-sequential -Install dependencies one\-at\-a\-time, instead of concurrently. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-keep\-outdated -Keep out\-dated dependencies from being updated in Pipfile.lock. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-pre -Allow pre\-releases. -.UNINDENT -.INDENT 0.0 -.TP -.B \-d, \-\-dev -Install both develop and default packages. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-python <python> -Specify which version of Python virtualenv should use. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-three, \-\-two -Use Python 3/2 when creating virtualenv. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-clear -Clears caches (pipenv, pip, and pip\-tools). -.UNINDENT -.INDENT 0.0 -.TP -.B \-v, \-\-verbose -Verbose mode. -.UNINDENT -.INDENT 0.0 -.TP -.B \-\-pypi\-mirror <pypi_mirror> -Specify a PyPI mirror. -.UNINDENT -Arguments.INDENT 0.0 -.TP -.B PACKAGES -Optional argument(s) -.UNINDENT -Environment variables.INDENT 0.0 -.TP -.B PIP_EXTRA_INDEX_URL -.INDENT 7.0 -.INDENT 3.5 -Provide a default for \fI\%\-\-extra\-index\-url\fP -.UNINDENT -.UNINDENT -.UNINDENT -.INDENT 0.0 -.TP -.B PIP_INDEX_URL -.INDENT 7.0 -.INDENT 3.5 -Provide a default for \fI\%\-i\fP -.UNINDENT -.UNINDENT -.UNINDENT .INDENT 0.0 .IP \(bu 2 genindex .IP \(bu 2 modindex -.IP \(bu 2 -search .UNINDENT .SH AUTHOR -Kenneth Reitz +Python Packaging Authority .SH COPYRIGHT -2017. A project founded by <a href="http://kennethreitz.com/pages/open-projects.html">Kenneth Reitz</a> +2020. A project founded by <a href="http://kennethreitz.com/pages/open-projects.html">Kenneth Reitz</a> .\" Generated by docutils manpage writer. . diff --git a/pipenv/process.py b/pipenv/process.py new file mode 100644 index 00000000..a7888e66 --- /dev/null +++ b/pipenv/process.py @@ -0,0 +1,72 @@ +import os +import subprocess +import threading +from time import monotonic as _time + +from pipenv._compat import DEFAULT_ENCODING +from pipenv.cmdparse import Script + + +class PopenProcess: + """A wrapper of subprocess.Popen that + doesn't need to worry about the Pipe buffer exceeding the limit. + """ + def __init__( + self, args, *, block=True, encoding=DEFAULT_ENCODING, env=None, timeout=None, **other_kwargs + ): + self.blocking = block + self.env = env + self.script = Script.parse(args) + if env is not None: + env = dict(os.environ, **env) + other_kwargs['env'] = env + other_kwargs['stdout'] = subprocess.PIPE + other_kwargs['stderr'] = subprocess.PIPE + self._process = subprocess.Popen(args, universal_newlines=True, encoding=encoding, **other_kwargs) + self._endtime = None + if timeout is not None: + self._endtime = _time() + timeout + self.out_buffer = [] + self.err_buffer = [] + self._start_polling() + + def wait(self): + try: + self._process.wait(self._remaining_time()) + except subprocess.TimeoutExpired: + self._process.kill() + raise + finally: + self.out_reader.join() + self.err_reader.join() + + @property + def return_code(self): + return self._process.returncode + + @property + def out(self): + return "".join(self.out_buffer) + + @property + def err(self): + return "".join(self.err_buffer) + + def _remaining_time(self): + if self._endtime is None: + return None + return self._endtime - _time() + + def _pipe_output(self): + for line in iter(self._process.stdout.readline, ""): + self.out_buffer.append(line) + + def _pipe_err(self): + for line in iter(self._process.stderr.readline, ""): + self.err_buffer.append(line) + + def _start_polling(self): + self.out_reader = threading.Thread(target=self._pipe_output) + self.err_reader = threading.Thread(target=self._pipe_err) + self.out_reader.start() + self.err_reader.start() diff --git a/pipenv/progress.py b/pipenv/progress.py index 8968150c..04df813e 100644 --- a/pipenv/progress.py +++ b/pipenv/progress.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ clint.textui.progress ~~~~~~~~~~~~~~~~~ @@ -7,7 +6,6 @@ This module provides the progressbar functionality. """ -from __future__ import absolute_import import os import sys @@ -15,7 +13,7 @@ import time import crayons -from .environments import PIPENV_COLORBLIND, PIPENV_HIDE_EMOJIS +from pipenv.environments import PIPENV_COLORBLIND, PIPENV_HIDE_EMOJIS STREAM = sys.stderr @@ -37,7 +35,7 @@ else: BAR_EMPTY_CHAR = str(crayons.black("▉")) if (sys.version_info[0] >= 3) and (os.name != "nt"): - BAR_TEMPLATE = u" %s%s%s %i/%i — {0}\r".format(crayons.black("%s")) + BAR_TEMPLATE = " %s%s%s %i/%i — {}\r".format(crayons.black("%s")) else: if os.name == "nt": BAR_TEMPLATE = " %s%s%s %i/%i - %s\r" @@ -51,7 +49,7 @@ ETA_INTERVAL = 1 ETA_SMA_WINDOW = 9 -class Bar(object): +class Bar: def __enter__(self): return self diff --git a/pipenv/project.py b/pipenv/project.py index 5b14106e..386b784d 100644 --- a/pipenv/project.py +++ b/pipenv/project.py @@ -1,64 +1,52 @@ # -*- coding: utf-8 -*- import base64 import fnmatch -import glob import hashlib import io import json import operator import os +from pathlib import Path import re import sys +import urllib.parse -import six +import pipfile +import pipfile.api import toml import tomlkit import vistir -import pipfile -import pipfile.api - -from .vendor.cached_property import cached_property - -from .cmdparse import Script -from .environment import Environment -from .environments import ( - PIPENV_DEFAULT_PYTHON_VERSION, PIPENV_IGNORE_VIRTUALENVS, PIPENV_MAX_DEPTH, - PIPENV_PIPFILE, PIPENV_PYTHON, PIPENV_TEST_INDEX, PIPENV_VENV_IN_PROJECT, - is_in_virtualenv, is_type_checking -) -from .vendor.requirementslib.models.utils import get_default_pyproject_backend -from .utils import ( +from pipenv.cmdparse import Script +from pipenv.core import system_which +from pipenv.environment import Environment +from pipenv.environments import Setting, is_type_checking, is_in_virtualenv, normalize_pipfile_path +from pipenv.utils import ( cleanup_toml, convert_toml_outline_tables, find_requirements, - get_canonical_names, get_url_name, get_workon_home, is_editable, - is_installable_file, is_star, is_valid_url, is_virtual_environment, - looks_like_dir, normalize_drive, pep423_name, proper_case, python_version, - safe_expandvars, get_pipenv_dist + find_windows_executable, get_canonical_names, get_pipenv_dist, get_url_name, + get_workon_home, is_editable, is_installable_file, is_star, is_valid_url, + is_virtual_environment, looks_like_dir, pep423_name, + proper_case, python_version, safe_expandvars ) +from pipenv.vendor.cached_property import cached_property +from pipenv.vendor.requirementslib.models.utils import ( + get_default_pyproject_backend +) + if is_type_checking(): - from typing import Dict, Text, Union + from typing import Dict, List, Optional, Set, Text, Tuple, Union + + import pkg_resources TSource = Dict[Text, Union[Text, bool]] + TPackageEntry = Dict[str, Union[bool, str, List[str]]] + TPackage = Dict[str, TPackageEntry] + TScripts = Dict[str, str] + TPipenv = Dict[str, bool] + TPipfile = Dict[str, Union[TPackage, TScripts, TPipenv, List[TSource]]] -def _normalized(p): - if p is None: - return None - loc = vistir.compat.Path(p) - try: - loc = loc.resolve() - except OSError: - loc = loc.absolute() - # Recase the path properly on Windows. From https://stackoverflow.com/a/35229734/5043728 - if os.name == 'nt': - matches = glob.glob(re.sub(r'([^:/\\])(?=[/\\]|$)', r'[\1]', str(loc))) - path_str = matches and matches[0] or str(loc) - else: - path_str = str(loc) - return normalize_drive(os.path.abspath(path_str)) - - -DEFAULT_NEWLINES = u"\n" +DEFAULT_NEWLINES = "\n" class _LockFileEncoder(json.JSONEncoder): @@ -77,64 +65,38 @@ class _LockFileEncoder(json.JSONEncoder): ) def default(self, obj): - if isinstance(obj, vistir.compat.Path): + if isinstance(obj, Path): obj = obj.as_posix() return super(_LockFileEncoder, self).default(obj) def encode(self, obj): content = super(_LockFileEncoder, self).encode(obj) - if not isinstance(content, six.text_type): + if not isinstance(content, str): content = content.decode("utf-8") return content def preferred_newlines(f): - if isinstance(f.newlines, six.text_type): + if isinstance(f.newlines, str): return f.newlines return DEFAULT_NEWLINES -if PIPENV_PIPFILE: - if not os.path.isfile(PIPENV_PIPFILE): - raise RuntimeError("Given PIPENV_PIPFILE is not found!") - - else: - PIPENV_PIPFILE = _normalized(PIPENV_PIPFILE) - # Overwrite environment variable so that subprocesses can get the correct path. - # See https://github.com/pypa/pipenv/issues/3584 - os.environ['PIPENV_PIPFILE'] = PIPENV_PIPFILE # (path, file contents) => TOMLFile # keeps track of pipfiles that we've seen so we do not need to re-parse 'em _pipfile_cache = {} -if PIPENV_TEST_INDEX: - DEFAULT_SOURCE = { - u"url": PIPENV_TEST_INDEX, - u"verify_ssl": True, - u"name": u"custom", - } -else: - DEFAULT_SOURCE = { - u"url": u"https://pypi.org/simple", - u"verify_ssl": True, - u"name": u"pypi", - } - -pipfile.api.DEFAULT_SOURCE = DEFAULT_SOURCE - - class SourceNotFound(KeyError): pass -class Project(object): +class Project: """docstring for Project""" _lockfile_encoder = _LockFileEncoder() - def __init__(self, which=None, python_version=None, chdir=True): - super(Project, self).__init__() + def __init__(self, python_version=None, chdir=True): self._name = None self._virtualenv_location = None self._download_location = None @@ -145,11 +107,25 @@ class Project(object): self._requirements_location = None self._original_dir = os.path.abspath(os.curdir) self._environment = None - self._which = which self._build_system = { "requires": ["setuptools", "wheel"] } self.python_version = python_version + self.s = Setting() + if self.s.PIPENV_TEST_INDEX: + self.default_source = { + u"url": self.s.PIPENV_TEST_INDEX, + u"verify_ssl": True, + u"name": u"custom", + } + else: + self.default_source = { + u"url": u"https://pypi.org/simple", + u"verify_ssl": True, + u"name": u"pypi", + } + pipfile.api.DEFAULT_SOURCE = self.default_source + # Hack to skip this during pipenv run, or -r. if ("run" not in sys.argv) and chdir: try: @@ -158,6 +134,7 @@ class Project(object): pass def path_to(self, p): + # type: (str) -> str """Returns the absolute path to a given relative path.""" if os.path.isabs(p): return p @@ -219,16 +196,19 @@ class Project(object): @property def name(self): + # type: () -> str if self._name is None: self._name = self.pipfile_location.split(os.sep)[-2] return self._name @property def pipfile_exists(self): + # type: () -> bool return os.path.isfile(self.pipfile_location) @property def required_python_version(self): + # type: () -> str if self.pipfile_exists: required = self.parsed_pipfile.get("requires", {}).get( "python_full_version" @@ -240,20 +220,24 @@ class Project(object): @property def project_directory(self): + # type: () -> str return os.path.abspath(os.path.join(self.pipfile_location, os.pardir)) @property def requirements_exists(self): + # type: () -> bool return bool(self.requirements_location) def is_venv_in_project(self): - return PIPENV_VENV_IN_PROJECT or ( + # type: () -> bool + return self.s.PIPENV_VENV_IN_PROJECT or ( self.project_directory and os.path.isdir(os.path.join(self.project_directory, ".venv")) ) @property def virtualenv_exists(self): + # type: () -> bool if os.path.exists(self.virtualenv_location): if os.name == "nt": extra = ["Scripts", "activate.bat"] @@ -264,6 +248,7 @@ class Project(object): return False def get_location_for_virtualenv(self): + # type: () -> str # If there's no project yet, set location based on config. if not self.project_directory: if self.is_venv_in_project(): @@ -286,15 +271,20 @@ class Project(object): with io.open(dot_venv) as f: name = f.read().strip() + # If .venv file is empty, set location based on config. + if not name: + return str(get_workon_home().joinpath(self.virtualenv_name)) + # If content looks like a path, use it as a relative path. # Otherwise use directory named after content in WORKON_HOME. if looks_like_dir(name): - path = vistir.compat.Path(self.project_directory, name) + path = Path(self.project_directory, name) return path.absolute().as_posix() return str(get_workon_home().joinpath(name)) @property def working_set(self): + # type: () -> pkg_resources.WorkingSet from .utils import load_path sys_path = load_path(self.which("python")) import pkg_resources @@ -306,10 +296,12 @@ class Project(object): @property def installed_package_names(self): + # type: () -> List[str] return get_canonical_names([pkg.key for pkg in self.installed_packages]) @property def lockfile_package_names(self): + # type: () -> Dict[str, Set[str]] dev_keys = get_canonical_names(self.lockfile_content["develop"].keys()) default_keys = get_canonical_names(self.lockfile_content["default"].keys()) return { @@ -320,6 +312,7 @@ class Project(object): @property def pipfile_package_names(self): + # type: () -> Dict[str, Set[str]] dev_keys = get_canonical_names(self.dev_packages.keys()) default_keys = get_canonical_names(self.packages.keys()) return { @@ -328,28 +321,42 @@ class Project(object): "combined": dev_keys | default_keys } + def get_environment(self, allow_global=False): + # type: (bool) -> Environment + is_venv = is_in_virtualenv() + if allow_global and not is_venv: + prefix = sys.prefix + python = sys.executable + else: + prefix = self.virtualenv_location + python = None + sources = self.sources if self.sources else [self.default_source] + environment = Environment( + prefix=prefix, python=python, is_venv=is_venv, sources=sources, + pipfile=self.parsed_pipfile, project=self + ) + pipenv_dist = get_pipenv_dist(pkg="pipenv") + if pipenv_dist: + environment.extend_dists(pipenv_dist) + else: + environment.add_dist("pipenv") + return environment + @property def environment(self): + # type: () -> Environment if not self._environment: - prefix = self.virtualenv_location - is_venv = is_in_virtualenv() - sources = self.sources if self.sources else [DEFAULT_SOURCE] - self._environment = Environment( - prefix=prefix, is_venv=is_venv, sources=sources, pipfile=self.parsed_pipfile, - project=self - ) - pipenv_dist = get_pipenv_dist(pkg="pipenv") - if pipenv_dist: - self._environment.extend_dists(pipenv_dist) - else: - self._environment.add_dist("pipenv") + allow_global = self.s.PIPENV_USE_SYSTEM + self._environment = self.get_environment(allow_global=allow_global) return self._environment def get_outdated_packages(self): + # type: () -> List[pkg_resources.Distribution] return self.environment.get_outdated_packages(pre=self.pipfile.get("pre", False)) @classmethod def _sanitize(cls, name): + # type: (str) -> Tuple[str, str] # Replace dangerous characters into '_'. The length of the sanitized # project name is limited as 42 because of the limit of linux kernel # @@ -362,9 +369,10 @@ class Project(object): # https://www.gnu.org/software/bash/manual/html_node/Double-Quotes.html # http://www.tldp.org/LDP/abs/html/special-chars.html#FIELDREF # https://github.com/torvalds/linux/blob/2bfe01ef/include/uapi/linux/binfmts.h#L18 - return re.sub(r'[ $`!*@"\\\r\n\t]', "_", name)[0:42] + return re.sub(r'[ &$`!*@"()\[\]\\\r\n\t]', "_", name)[0:42] def _get_virtualenv_hash(self, name): + # type: (str) -> str """Get the name of the virtualenv adjusted for windows if needed Returns (name, encoded_hash) @@ -407,19 +415,27 @@ class Project(object): @property def virtualenv_name(self): + # type: () -> str sanitized, encoded_hash = self._get_virtualenv_hash(self.name) - suffix = "-{0}".format(PIPENV_PYTHON) if PIPENV_PYTHON else "" + suffix = "" + if self.s.PIPENV_PYTHON: + if os.path.isabs(self.s.PIPENV_PYTHON): + suffix = "-{0}".format(os.path.basename(self.s.PIPENV_PYTHON)) + else: + suffix = "-{0}".format(self.s.PIPENV_PYTHON) + # If the pipfile was located at '/home/user/MY_PROJECT/Pipfile', # the name of its virtualenv will be 'my-project-wyUfYPqE' return sanitized + "-" + encoded_hash + suffix @property def virtualenv_location(self): + # type: () -> str # if VIRTUAL_ENV is set, use that. virtualenv_env = os.getenv("VIRTUAL_ENV") if ( "PIPENV_ACTIVE" not in os.environ - and not PIPENV_IGNORE_VIRTUALENVS and virtualenv_env + and not self.s.PIPENV_IGNORE_VIRTUALENVS and virtualenv_env ): return virtualenv_env @@ -430,6 +446,7 @@ class Project(object): @property def virtualenv_src_location(self): + # type: () -> str if self.virtualenv_location: loc = os.sep.join([self.virtualenv_location, "src"]) else: @@ -439,6 +456,7 @@ class Project(object): @property def download_location(self): + # type: () -> str if self._download_location is None: loc = os.sep.join([self.virtualenv_location, "downloads"]) self._download_location = loc @@ -448,8 +466,9 @@ class Project(object): @property def proper_names_db_path(self): + # type: () -> str if self._proper_names_db_path is None: - self._proper_names_db_path = vistir.compat.Path( + self._proper_names_db_path = Path( self.virtualenv_location, "pipenv-proper-names.txt" ) self._proper_names_db_path.touch() # Ensure the file exists. @@ -457,32 +476,36 @@ class Project(object): @property def proper_names(self): + # type: () -> str with self.proper_names_db_path.open() as f: return f.read().splitlines() def register_proper_name(self, name): + # type: (str) -> None """Registers a proper name to the database.""" with self.proper_names_db_path.open("a") as f: f.write(u"{0}\n".format(name)) @property def pipfile_location(self): - if PIPENV_PIPFILE: - return PIPENV_PIPFILE + # type: () -> str + if self.s.PIPENV_PIPFILE: + return self.s.PIPENV_PIPFILE if self._pipfile_location is None: try: - loc = pipfile.Pipfile.find(max_depth=PIPENV_MAX_DEPTH) + loc = pipfile.Pipfile.find(max_depth=self.s.PIPENV_MAX_DEPTH) except RuntimeError: loc = "Pipfile" - self._pipfile_location = _normalized(loc) + self._pipfile_location = normalize_pipfile_path(loc) return self._pipfile_location @property def requirements_location(self): + # type: () -> Optional[str] if self._requirements_location is None: try: - loc = find_requirements(max_depth=PIPENV_MAX_DEPTH) + loc = find_requirements(max_depth=self.s.PIPENV_MAX_DEPTH) except RuntimeError: loc = None self._requirements_location = loc @@ -490,6 +513,7 @@ class Project(object): @property def parsed_pipfile(self): + # type: () -> Union[tomlkit.toml_document.TOMLDocument, TPipfile] """Parse Pipfile into a TOMLFile and cache it (call clear_pipfile_cache() afterwards if mutating)""" @@ -502,6 +526,7 @@ class Project(object): return _pipfile_cache[cache_key] def read_pipfile(self): + # type: () -> str # Open the pipfile, read it into memory. if not self.pipfile_exists: return "" @@ -512,10 +537,12 @@ class Project(object): return contents def clear_pipfile_cache(self): + # type: () -> None """Clear pipfile cache (e.g., so we can mutate parsed pipfile)""" _pipfile_cache.clear() def _parse_pipfile(self, contents): + # type: (str) -> Union[tomlkit.toml_document.TOMLDocument, TPipfile] try: return tomlkit.parse(contents) except Exception: @@ -524,6 +551,7 @@ class Project(object): return toml.loads(contents) def _read_pyproject(self): + # type: () -> None pyproject = self.path_to("pyproject.toml") if os.path.exists(pyproject): self._pyproject = toml.load(pyproject) @@ -538,24 +566,29 @@ class Project(object): @property def build_requires(self): + # type: () -> List[str] return self._build_system.get("requires", ["setuptools>=40.8.0", "wheel"]) @property def build_backend(self): + # type: () -> str return self._build_system.get("build-backend", get_default_pyproject_backend()) @property def settings(self): + # type: () -> Union[tomlkit.items.Table, Dict[str, Union[str, bool]]] """A dictionary of the settings added to the Pipfile.""" return self.parsed_pipfile.get("pipenv", {}) def has_script(self, name): + # type: (str) -> bool try: return name in self.parsed_pipfile["scripts"] except KeyError: return False def build_script(self, name, extra_args=None): + # type: (str, Optional[List[str]]) -> Script try: script = Script.parse(self.parsed_pipfile["scripts"][name]) except KeyError: @@ -565,6 +598,7 @@ class Project(object): return script def update_settings(self, d): + # type: (Dict[str, Union[str, bool]]) -> None settings = self.settings changed = False for new in d: @@ -591,7 +625,8 @@ class Project(object): @property def _pipfile(self): - from .vendor.requirementslib.models.pipfile import Pipfile as ReqLibPipfile + from .vendor.requirementslib.models.pipfile import \ + Pipfile as ReqLibPipfile pf = ReqLibPipfile.load(self.pipfile_location) return pf @@ -666,26 +701,19 @@ class Project(object): if not self.pipfile_exists: return True - if not len(self.read_pipfile()): + if not self.read_pipfile(): return True return False def create_pipfile(self, python=None): """Creates the Pipfile, filled with juicy defaults.""" - from .vendor.pip_shims.shims import ( - ConfigOptionParser, make_option_group, index_group - ) + from .vendor.pip_shims.shims import InstallCommand - config_parser = ConfigOptionParser(name=self.name) - config_parser.add_option_group(make_option_group(index_group, config_parser)) - install = config_parser.option_groups[0] - indexes = ( - " ".join(install.get_option("--extra-index-url").default) - .lstrip("\n") - .split("\n") - ) - sources = [DEFAULT_SOURCE] + # Inherit the pip's index configuration of install command. + command = InstallCommand() + indexes = command.cmd_opts.get_option("--extra-index-url").default + sources = [self.default_source] for i, index in enumerate(indexes): if not index: continue @@ -709,9 +737,9 @@ class Project(object): required_python = self.which("python", self.virtualenv_location) else: required_python = self.which("python") - version = python_version(required_python) or PIPENV_DEFAULT_PYTHON_VERSION - if version and len(version) >= 3: - data[u"requires"] = {"python_version": version[: len("2.7")]} + version = python_version(required_python) or self.s.PIPENV_DEFAULT_PYTHON_VERSION + if version and len(version.split(".")) > 2: + data[u"requires"] = {"python_version": ".".join(version.split(".")[:2])} self.write_toml(data) @classmethod @@ -723,11 +751,12 @@ class Project(object): if "verify_ssl" not in source: source["verify_ssl"] = "https://" in source["url"] if not isinstance(source["verify_ssl"], bool): - source["verify_ssl"] = source["verify_ssl"].lower() == "true" + source["verify_ssl"] = str(source["verify_ssl"]).lower() == "true" return source def get_or_create_lockfile(self, from_pipfile=False): - from pipenv.vendor.requirementslib.models.lockfile import Lockfile as Req_Lockfile + from pipenv.vendor.requirementslib.models.lockfile import \ + Lockfile as Req_Lockfile lockfile = None if from_pipfile and self.pipfile_exists: lockfile_dict = { @@ -769,8 +798,10 @@ class Project(object): from .vendor.plette.lockfiles import PIPFILE_SPEC_CURRENT if self.lockfile_exists: sources = self.lockfile_content.get("_meta", {}).get("sources", []) - else: + elif "source" in self.parsed_pipfile: sources = [dict(source) for source in self.parsed_pipfile["source"]] + else: + sources = self.pipfile_sources if not isinstance(sources, list): sources = [sources] return { @@ -790,7 +821,7 @@ class Project(object): except Exception: document = tomlkit.document() for section in ("packages", "dev-packages"): - document[section] = tomlkit.container.Table() + document[section] = tomlkit.table() # Convert things to inline tables — fancy :) for package in data.get(section, {}): if hasattr(data[section][package], "keys"): @@ -802,8 +833,8 @@ class Project(object): formatted_data = tomlkit.dumps(document).rstrip() if ( - vistir.compat.Path(path).absolute() - == vistir.compat.Path(self.pipfile_location).absolute() + Path(path).absolute() + == Path(self.pipfile_location).absolute() ): newlines = self._pipfile_newlines else: @@ -831,7 +862,7 @@ class Project(object): @property def pipfile_sources(self): if self.pipfile_is_empty or "source" not in self.parsed_pipfile: - return [DEFAULT_SOURCE] + return [self.default_source] # We need to make copies of the source info so we don't # accidentally modify the cache. See #2100 where values are # written after the os.path.expandvars() call. @@ -957,7 +988,7 @@ class Project(object): self.write_toml(p) def src_name_from_url(self, index_url): - name, _, tld_guess = six.moves.urllib.parse.urlsplit(index_url).netloc.rpartition( + name, _, tld_guess = urllib.parse.urlsplit(index_url).netloc.rpartition( "." ) src_name = name.replace(".", "") @@ -974,20 +1005,26 @@ class Project(object): """Adds a given index to the Pipfile.""" # Read and append Pipfile. p = self.parsed_pipfile + source = None try: - self.get_source(url=index) + source = self.get_source(url=index) except SourceNotFound: - source = {"url": index, "verify_ssl": verify_ssl} - else: - return + try: + source = self.get_source(name=index) + except SourceNotFound: + pass + if source is not None: + return source["name"] + source = {"url": index, "verify_ssl": verify_ssl} source["name"] = self.src_name_from_url(index) # Add the package to the group. if "source" not in p: - p["source"] = [source] + p["source"] = [tomlkit.item(source)] else: - p["source"].append(source) + p["source"].append(tomlkit.item(source)) # Write Pipfile. self.write_toml(p) + return source["name"] def recase_pipfile(self): if self.ensure_proper_casing(): @@ -1003,7 +1040,7 @@ class Project(object): if expand_env_vars: # Expand environment variables in Pipfile.lock at runtime. - for i, source in enumerate(j["_meta"]["sources"][:]): + for i, _ in enumerate(j["_meta"]["sources"][:]): j["_meta"]["sources"][i]["url"] = os.path.expandvars( j["_meta"]["sources"][i]["url"] ) @@ -1086,3 +1123,29 @@ class Project(object): if as_path: result = str(result.path) return result + + def _which(self, command, location=None, allow_global=False): + if not allow_global and location is None: + if self.virtualenv_exists: + location = self.virtualenv_location + else: + location = os.environ.get("VIRTUAL_ENV", None) + if not (location and os.path.exists(location)) and not allow_global: + raise RuntimeError("location not created nor specified") + + version_str = "python{}".format(".".join([str(v) for v in sys.version_info[:2]])) + is_python = command in ("python", os.path.basename(sys.executable), version_str) + if not allow_global: + if os.name == "nt": + p = find_windows_executable(os.path.join(location, "Scripts"), command) + else: + p = os.path.join(location, "bin", command) + else: + if is_python: + p = sys.executable + if not os.path.exists(p): + if is_python: + p = sys.executable or system_which("python") + else: + p = system_which(command) + return p diff --git a/pipenv/resolver.py b/pipenv/resolver.py index cd04fccb..b4c01ad4 100644 --- a/pipenv/resolver.py +++ b/pipenv/resolver.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - -from __future__ import absolute_import, print_function import json import logging import os @@ -12,7 +9,7 @@ os.environ["PIP_PYTHON_PATH"] = str(sys.executable) def find_site_path(pkg, site_dir=None): import pkg_resources - if site_dir is not None: + if site_dir is None: site_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) working_set = pkg_resources.WorkingSet([site_dir] + sys.path[:]) for dist in working_set: @@ -20,12 +17,12 @@ def find_site_path(pkg, site_dir=None): base_name = dist.project_name if dist.project_name else dist.key name = None if "top_level.txt" in dist.metadata_listdir(""): - name = next(iter([l.strip() for l in dist.get_metadata_lines("top_level.txt") if l is not None]), None) + name = next(iter([line.strip() for line in dist.get_metadata_lines("top_level.txt") if line is not None]), None) if name is None: name = pkg_resources.safe_name(base_name).replace("-", "_") if not any(pkg == _ for _ in [base_name, name]): continue - path_options = [name, "{0}.py".format(name)] + path_options = [name, f"{name}.py"] path_options = [os.path.join(root, p) for p in path_options if p is not None] path = next(iter(p for p in path_options if os.path.exists(p)), None) if path is not None: @@ -80,23 +77,31 @@ def which(*args, **kwargs): def handle_parsed_args(parsed): + if "PIPENV_VERBOSITY" in os.environ: + parsed.verbose = int(os.getenv("PIPENV_VERBOSITY")) if parsed.debug: parsed.verbose = max(parsed.verbose, 2) if parsed.verbose > 1: logging.getLogger("notpip").setLevel(logging.DEBUG) elif parsed.verbose > 0: logging.getLogger("notpip").setLevel(logging.INFO) + logger = logging.getLogger( + "pipenv.patched.notpip._internal.resolution.resolvelib.reporter" + ) + logger.addHandler(logging.StreamHandler()) + logger.setLevel(logging.INFO) + os.environ["PIP_RESOLVER_DEBUG"] = "" os.environ["PIPENV_VERBOSITY"] = str(parsed.verbose) if "PIPENV_PACKAGES" in os.environ: parsed.packages += os.environ.get("PIPENV_PACKAGES", "").strip().split("\n") return parsed -class Entry(object): +class Entry: """A resolved entry from a resolver run""" def __init__(self, name, entry_dict, project, resolver, reverse_deps=None, dev=False): - super(Entry, self).__init__() + super().__init__() from pipenv.vendor.requirementslib.models.utils import tomlkit_value_to_python self.name = name if isinstance(entry_dict, dict): @@ -173,7 +178,7 @@ class Entry(object): markers = set() keys_in_dict = [k for k in marker_keys if k in entry_dict] markers = { - normalize_marker_str("{k} {v}".format(k=k, v=entry_dict.pop(k))) + normalize_marker_str(f"{k} {entry_dict.pop(k)}") for k in keys_in_dict } if "markers" in entry_dict: @@ -216,18 +221,17 @@ class Entry(object): from pipenv.vendor.requirementslib.models.markers import normalize_marker_str if not marker: return None - from pipenv.vendor import six from pipenv.vendor.vistir.compat import Mapping marker_str = None if isinstance(marker, Mapping): marker_dict, _ = Entry.get_markers_from_dict(marker) if marker_dict: - marker_str = "{0}".format(marker_dict.popitem()[1]) + marker_str = f"{marker_dict.popitem()[1]}" elif isinstance(marker, (list, set, tuple)): marker_str = " and ".join([normalize_marker_str(m) for m in marker if m]) - elif isinstance(marker, six.string_types): - marker_str = "{0}".format(normalize_marker_str(marker)) - if isinstance(marker_str, six.string_types): + elif isinstance(marker, str): + marker_str = f"{normalize_marker_str(marker)}" + if isinstance(marker_str, str): return marker_str return None @@ -250,7 +254,7 @@ class Entry(object): entry_hashes = set(self.entry.hashes) locked_hashes = set(self.lockfile_entry.hashes) if entry_hashes != locked_hashes and not self.is_updated: - self.entry_dict["hashes"] = list(entry_hashes | locked_hashes) + self.entry_dict["hashes"] = sorted(entry_hashes | locked_hashes) self.entry_dict["name"] = self.name if "version" in self.entry_dict: self.entry_dict["version"] = self.strip_version(self.entry_dict["version"]) @@ -317,9 +321,9 @@ class Entry(object): if not any(specifier.startswith(k) for k in Specifier._operators.keys()): if specifier.strip().lower() in ["any", "<any>", "*"]: return "*" - specifier = "=={0}".format(specifier) + specifier = f"=={specifier}" elif specifier.startswith("==") and specifier.count("=") > 3: - specifier = "=={0}".format(specifier.lstrip("=")) + specifier = "=={}".format(specifier.lstrip("=")) return specifier @staticmethod @@ -448,7 +452,7 @@ class Entry(object): self.can_use_updated = False satisfied_by_value = getattr(constraint, "satisfied_by", None) if satisfied_by_value: - satisfied_by = "{0}".format( + satisfied_by = "{}".format( self.clean_specifier(str(satisfied_by_value.version)) ) satisfied_by_versions.add(satisfied_by) @@ -504,55 +508,6 @@ class Entry(object): """ if self.is_in_pipfile: return self.pipfile_entry.as_ireq() - return self.constraint_from_parent_conflicts() - - def constraint_from_parent_conflicts(self): - """ - Given a resolved entry with multiple parent dependencies with different - constraints, searches for the resolution that satisfies all of the parent - constraints. - - :return: A new **InstallRequirement** satisfying all parent constraints - :raises: :exc:`~pipenv.exceptions.DependencyConflict` if resolution is impossible - """ - # ensure that we satisfy the parent dependencies of this dep - parent_dependencies = set() - has_mismatch = False - can_use_original = True - for p in self.parent_deps: - # updated dependencies should be satisfied since they were resolved already - if p.is_updated: - continue - # parents with no requirements can't conflict - if not p.requirements: - continue - entry_ref = p.get_dependency(self.name) - required = entry_ref.get("required_version", "*") - required = self.clean_specifier(required) - parent_requires = self.make_requirement(self.name, required) - parent_dependencies.add("{0} => {1} ({2})".format(p.name, self.name, required)) - # use pre=True here or else prereleases dont satisfy constraints - if parent_requires.requirement.specifier and ( - not parent_requires.requirement.specifier.contains(self.original_version, prereleases=True) - ): - can_use_original = False - if parent_requires.requirement.specifier and ( - not parent_requires.requirement.specifier.contains(self.updated_version, prereleases=True) - ): - if not self.entry.editable and self.updated_version != self.original_version: - has_mismatch = True - if has_mismatch and not can_use_original: - from pipenv.exceptions import DependencyConflict - msg = ( - "Cannot resolve {0} ({1}) due to conflicting parent dependencies: " - "\n\t{2}".format( - self.name, self.updated_version, "\n\t".join(parent_dependencies) - ) - ) - raise DependencyConflict(msg) - elif can_use_original: - return self.lockfile_entry.as_ireq() - return self.entry.as_ireq() def validate_constraints(self): """ @@ -562,19 +517,23 @@ class Entry(object): :return: True if the constraints are satisfied by the resolution provided :raises: :exc:`pipenv.exceptions.DependencyConflict` if the constraints dont exist """ + from pipenv.exceptions import DependencyConflict + constraints = self.get_constraints() + pinned_version = self.updated_version for constraint in constraints: - try: - constraint.check_if_exists(False) - except Exception: - from pipenv.exceptions import DependencyConflict - from pipenv.environments import is_verbose - if is_verbose(): - print("Tried constraint: {0!r}".format(constraint), file=sys.stderr) + if not constraint.req: + continue + if pinned_version and not constraint.req.specifier.contains( + str(pinned_version), prereleases=True + ): + if self.project.s.is_verbose(): + print(f"Tried constraint: {constraint!r}", file=sys.stderr) msg = ( - "Cannot resolve conflicting version {0}{1} while {2}{3} is " + "Cannot resolve conflicting version {}{} while {}{} is " "locked.".format( - self.name, self.updated_specifier, self.old_name, self.old_specifiers + self.name, constraint.req.specifier, + self.name, self.updated_specifier ) ) raise DependencyConflict(msg) @@ -587,8 +546,8 @@ class Entry(object): if not parent.validate_specifiers(): from pipenv.exceptions import DependencyConflict msg = ( - "Cannot resolve conflicting versions: (Root: {0}) {1}{2} (Pipfile) " - "Incompatible with {3}{4} (resolved)\n".format( + "Cannot resolve conflicting versions: (Root: {}) {}{} (Pipfile) " + "Incompatible with {}{} (resolved)\n".format( self.name, parent.pipfile_name, parent.pipfile_entry.requirement.specifiers, parent.name, parent.updated_specifiers @@ -609,7 +568,7 @@ class Entry(object): except AttributeError: result = getattr(entry, key) except AttributeError: - result = super(Entry, self).__getattribute__(key) + result = super().__getattribute__(key) return result if any(key.startswith(v) for v in old_version): lockfile_entry = Entry.__getattribute__(self, "lockfile_entry") @@ -620,9 +579,9 @@ class Entry(object): except AttributeError: result = getattr(lockfile_entry, key) except AttributeError: - result = super(Entry, self).__getattribute__(key) + result = super().__getattribute__(key) return result - return super(Entry, self).__getattribute__(key) + return super().__getattribute__(key) def clean_results(results, resolver, project, dev=False): @@ -690,7 +649,7 @@ def parse_packages(packages, pre, clear, system, requirements_dir=None): from pipenv.utils import parse_indexes parsed_packages = [] for package in packages: - indexes, trusted_hosts, line = parse_indexes(package) + *_, line = parse_indexes(package) line = " ".join(line) pf = dict() req = Requirement.from_line(line) @@ -714,7 +673,7 @@ def parse_packages(packages, pre, clear, system, requirements_dir=None): print(json.dumps([])) -def resolve_packages(pre, clear, verbose, system, write, requirements_dir, packages): +def resolve_packages(pre, clear, verbose, system, write, requirements_dir, packages, dev): from pipenv.utils import create_mirror_source, resolve_deps, replace_pypi_sources pypi_mirror_source = ( create_mirror_source(os.environ["PIPENV_PYPI_MIRROR"]) @@ -723,8 +682,6 @@ def resolve_packages(pre, clear, verbose, system, write, requirements_dir, packa ) def resolve(packages, pre, project, sources, clear, system, requirements_dir=None): - from pipenv.patched.piptools import logging as piptools_logging - piptools_logging.log.verbosity = 1 if verbose else 0 return resolve_deps( packages, which, @@ -736,7 +693,8 @@ def resolve_packages(pre, clear, verbose, system, write, requirements_dir, packa req_dir=requirements_dir ) - from pipenv.core import project + from pipenv.project import Project + project = Project() sources = ( replace_pypi_sources(project.pipfile_sources, pypi_mirror_source) if pypi_mirror_source @@ -753,9 +711,9 @@ def resolve_packages(pre, clear, verbose, system, write, requirements_dir, packa requirements_dir=requirements_dir, ) if keep_outdated: - results = clean_outdated(results, resolver, project) + results = clean_outdated(results, resolver, project, dev) else: - results = clean_results(results, resolver, project) + results = clean_results(results, resolver, project, dev) if write: with open(write, "w") as fh: if not results: @@ -770,8 +728,8 @@ def resolve_packages(pre, clear, verbose, system, write, requirements_dir, packa print(json.dumps([])) -def _main(pre, clear, verbose, system, write, requirements_dir, packages, parse_only=False): - os.environ["PIP_PYTHON_VERSION"] = ".".join([str(s) for s in sys.version_info[:3]]) +def _main(pre, clear, verbose, system, write, requirements_dir, packages, parse_only=False, dev=False): + os.environ["PIPENV_REQUESTED_PYTHON_VERSION"] = ".".join([str(s) for s in sys.version_info[:3]]) os.environ["PIP_PYTHON_PATH"] = str(sys.executable) if parse_only: parse_packages( @@ -782,12 +740,12 @@ def _main(pre, clear, verbose, system, write, requirements_dir, packages, parse_ requirements_dir=requirements_dir, ) else: - resolve_packages(pre, clear, verbose, system, write, requirements_dir, packages) + resolve_packages(pre, clear, verbose, system, write, requirements_dir, packages, dev) -def main(): +def main(argv=None): parser = get_parser() - parsed, remaining = parser.parse_known_args() + parsed, remaining = parser.parse_known_args(argv) _patch_path(pipenv_site=parsed.pipenv_site) import warnings from pipenv.vendor.vistir.compat import ResourceWarning @@ -795,12 +753,13 @@ def main(): warnings.simplefilter("ignore", category=ResourceWarning) replace_with_text_stream("stdout") replace_with_text_stream("stderr") - os.environ["PIP_DISABLE_PIP_VERSION_CHECK"] = str("1") - os.environ["PYTHONIOENCODING"] = str("utf-8") - os.environ["PYTHONUNBUFFERED"] = str("1") + os.environ["PIP_DISABLE_PIP_VERSION_CHECK"] = "1" + os.environ["PYTHONIOENCODING"] = "utf-8" + os.environ["PYTHONUNBUFFERED"] = "1" parsed = handle_parsed_args(parsed) _main(parsed.pre, parsed.clear, parsed.verbose, parsed.system, parsed.write, - parsed.requirements_dir, parsed.packages, parse_only=parsed.parse_only) + parsed.requirements_dir, parsed.packages, parse_only=parsed.parse_only, + dev=parsed.dev) if __name__ == "__main__": diff --git a/pipenv/shells.py b/pipenv/shells.py index 1d8b073a..d9abc490 100644 --- a/pipenv/shells.py +++ b/pipenv/shells.py @@ -1,14 +1,14 @@ import collections import contextlib import os +import re import signal import subprocess import sys -from .environments import PIPENV_EMULATOR, PIPENV_SHELL, PIPENV_SHELL_EXPLICIT -from .vendor import shellingham -from .vendor.vistir.compat import Path, get_terminal_size -from .vendor.vistir.contextmanagers import temp_environ +from pipenv.vendor import shellingham +from pipenv.vendor.vistir.compat import Path, get_terminal_size +from pipenv.vendor.vistir.contextmanagers import temp_environ ShellDetectionFailure = shellingham.ShellDetectionFailure @@ -18,14 +18,14 @@ def _build_info(value): return (os.path.splitext(os.path.basename(value))[0], value) -def detect_info(): - if PIPENV_SHELL_EXPLICIT: - return _build_info(PIPENV_SHELL_EXPLICIT) +def detect_info(project): + if project.s.PIPENV_SHELL_EXPLICIT: + return _build_info(project.s.PIPENV_SHELL_EXPLICIT) try: return shellingham.detect_shell() except (shellingham.ShellDetectionFailure, TypeError): - if PIPENV_SHELL: - return _build_info(PIPENV_SHELL) + if project.s.PIPENV_SHELL: + return _build_info(project.s.PIPENV_SHELL) raise ShellDetectionFailure @@ -47,11 +47,11 @@ def _get_activate_script(cmd, venv): else: suffix = "" command = "." - # Escape any spaces located within the virtualenv path to allow + # Escape any special characters located within the virtualenv path to allow # for proper activation. - venv_location = str(venv).replace(" ", r"\ ") + venv_location = re.sub(r'([ &$()\[\]])', r"\\\1", str(venv)) # The leading space can make history cleaner in some shells. - return " {2} {0}/bin/activate{1}".format(venv_location, suffix, command) + return f" {command} {venv_location}/bin/activate{suffix}" def _handover(cmd, args): @@ -62,7 +62,7 @@ def _handover(cmd, args): sys.exit(subprocess.call(args, shell=True, universal_newlines=True)) -class Shell(object): +class Shell: def __init__(self, cmd): self.cmd = cmd self.args = [] @@ -76,7 +76,7 @@ class Shell(object): @contextlib.contextmanager def inject_path(self, venv): with temp_environ(): - os.environ["PATH"] = "{0}{1}{2}".format( + os.environ["PATH"] = "{}{}{}".format( os.pathsep.join(str(p.parent) for p in _iter_python(venv)), os.pathsep, os.environ["PATH"], @@ -89,9 +89,9 @@ class Shell(object): name = os.path.basename(venv) os.environ["VIRTUAL_ENV"] = str(venv) if "PROMPT" in os.environ: - os.environ["PROMPT"] = "({0}) {1}".format(name, os.environ["PROMPT"]) + os.environ["PROMPT"] = "({}) {}".format(name, os.environ["PROMPT"]) if "PS1" in os.environ: - os.environ["PS1"] = "({0}) {1}".format(name, os.environ["PS1"]) + os.environ["PS1"] = "({}) {}".format(name, os.environ["PS1"]) with self.inject_path(venv): os.chdir(cwd) _handover(self.cmd, self.args + list(args)) @@ -141,15 +141,15 @@ class Bash(Shell): # https://github.com/berdario/pew/issues/58#issuecomment-102182346 @contextlib.contextmanager def inject_path(self, venv): - from ._compat import NamedTemporaryFile + from tempfile import NamedTemporaryFile bashrc_path = Path.home().joinpath(".bashrc") with NamedTemporaryFile("w+") as rcfile: if bashrc_path.is_file(): - base_rc_src = 'source "{0}"\n'.format(bashrc_path.as_posix()) + base_rc_src = f'source "{bashrc_path.as_posix()}"\n' rcfile.write(base_rc_src) - export_path = 'export PATH="{0}:$PATH"\n'.format(":".join( + export_path = 'export PATH="{}:$PATH"\n'.format(":".join( self._format_path(python) for python in _iter_python(venv) )) @@ -161,18 +161,18 @@ class Bash(Shell): class MsysBash(Bash): def _format_path(self, python): - s = super(MsysBash, self)._format_path(python) + s = super()._format_path(python) if not python.drive: return s # Convert "C:/something" to "/c/something". - return '/{drive}{path}'.format(drive=s[0].lower(), path=s[2:]) + return f'/{s[0].lower()}{s[2:]}' class CmderEmulatedShell(Shell): def fork(self, venv, cwd, args): if cwd: os.environ["CMDER_START"] = cwd - super(CmderEmulatedShell, self).fork(venv, cwd, args) + super().fork(venv, cwd, args) class CmderCommandPrompt(CmderEmulatedShell): @@ -180,7 +180,7 @@ class CmderCommandPrompt(CmderEmulatedShell): rc = os.path.expandvars("%CMDER_ROOT%\\vendor\\init.bat") if os.path.exists(rc): self.args.extend(["/k", rc]) - super(CmderCommandPrompt, self).fork(venv, cwd, args) + super().fork(venv, cwd, args) class CmderPowershell(Shell): @@ -195,10 +195,10 @@ class CmderPowershell(Shell): "-NoProfile", "-NoExit", "-Command", - "Invoke-Expression '. ''{0}'''".format(rc), + f"Invoke-Expression '. ''{rc}'''", ] ) - super(CmderPowershell, self).fork(venv, cwd, args) + super().fork(venv, cwd, args) # Two dimensional dict. First is the shell type, second is the emulator type. @@ -231,9 +231,9 @@ def _detect_emulator(): return ",".join(keys) -def choose_shell(): - emulator = PIPENV_EMULATOR.lower() or _detect_emulator() - type_, command = detect_info() +def choose_shell(project): + emulator = project.s.PIPENV_EMULATOR.lower() or _detect_emulator() + type_, command = detect_info(project) shell_types = SHELL_LOOKUP[type_] for key in emulator.split(","): key = key.strip().lower() diff --git a/pipenv/utils.py b/pipenv/utils.py index 32f4491c..0794df46 100644 --- a/pipenv/utils.py +++ b/pipenv/utils.py @@ -1,42 +1,52 @@ -# -*- coding: utf-8 -*- -from __future__ import print_function - import contextlib import errno import logging import os import posixpath import re +import shlex +import hashlib import shutil import signal import stat +import subprocess import sys import warnings + from contextlib import contextmanager from distutils.spawn import find_executable - -import six -import toml -from click import echo as click_echo -from six.moves.urllib.parse import urlparse +from pathlib import Path +from urllib.parse import urlparse import crayons import parse +import toml import tomlkit -from . import environments -from .exceptions import PipenvCmdError, PipenvUsageError, RequirementError, ResolutionFailure -from .pep508checker import lookup -from .vendor.packaging.markers import Marker -from .vendor.urllib3 import util as urllib3_util -from .vendor.vistir.compat import Mapping, ResourceWarning, Sequence, Set, lru_cache -from .vendor.vistir.misc import fs_str, run +from click import echo as click_echo + +from pipenv import environments +from pipenv.exceptions import ( + PipenvCmdError, PipenvUsageError, RequirementError, ResolutionFailure +) +from pipenv.pep508checker import lookup +from pipenv.vendor.packaging.markers import Marker +from pipenv.vendor.urllib3 import util as urllib3_util +from pipenv.vendor.vistir.compat import ( + Mapping, ResourceWarning, Sequence, Set, TemporaryDirectory, lru_cache +) +from pipenv.vendor.vistir.misc import fs_str, run +from pipenv.vendor.vistir.contextmanagers import open_file + if environments.MYPY_RUNNING: - from typing import Tuple, Dict, Any, List, Union, Optional, Text - from .vendor.requirementslib.models.requirements import Requirement, Line - from .vendor.requirementslib.models.pipfile import Pipfile - from .project import Project, TSource + from typing import Any, Dict, List, Optional, Text, Tuple, Union + + from pipenv.project import Project, TSource + from pipenv.vendor.requirementslib.models.pipfile import Pipfile + from pipenv.vendor.requirementslib.models.requirements import ( + Line, Requirement + ) logging.basicConfig(level=logging.ERROR) @@ -48,7 +58,7 @@ SCHEME_LIST = ("http://", "https://", "ftp://", "ftps://", "file://") requests_session = None # type: ignore -def _get_requests_session(): +def _get_requests_session(max_retries=1): """Load requests lazily.""" global requests_session if requests_session is not None: @@ -56,9 +66,7 @@ def _get_requests_session(): import requests requests_session = requests.Session() - adapter = requests.adapters.HTTPAdapter( - max_retries=environments.PIPENV_MAX_RETRIES - ) + adapter = requests.adapters.HTTPAdapter(max_retries=max_retries) requests_session.mount("https://pypi.org/pypi", adapter) return requests_session @@ -89,7 +97,11 @@ def cleanup_toml(tml): def convert_toml_outline_tables(parsed): """Converts all outline tables to inline tables.""" def convert_tomlkit_table(section): - for key, value in section._body: + if isinstance(section, tomlkit.items.Table): + body = section.value._body + else: + body = section._body + for key, value in body: if not key: continue if hasattr(value, "keys") and not isinstance(value, tomlkit.items.InlineTable): @@ -117,7 +129,7 @@ def convert_toml_outline_tables(parsed): return parsed -def run_command(cmd, *args, **kwargs): +def run_command(cmd, *args, is_verbose=False, **kwargs): """ Take an input command and run it, handling exceptions and error codes and returning its stdout and stderr. @@ -129,32 +141,26 @@ def run_command(cmd, *args, **kwargs): :raises: exceptions.PipenvCmdError """ - from pipenv.vendor import delegator from ._compat import decode_for_output from .cmdparse import Script catch_exceptions = kwargs.pop("catch_exceptions", True) - if isinstance(cmd, (six.string_types, list, tuple)): + if isinstance(cmd, ((str,), list, tuple)): cmd = Script.parse(cmd) if not isinstance(cmd, Script): raise TypeError("Command input must be a string, list or tuple") if "env" not in kwargs: kwargs["env"] = os.environ.copy() kwargs["env"]["PYTHONIOENCODING"] = "UTF-8" - try: - cmd_string = cmd.cmdify() - except TypeError: - click_echo("Error turning command into string: {0}".format(cmd), err=True) - sys.exit(1) - if environments.is_verbose(): - click_echo("Running command: $ {0}".format(cmd_string, err=True)) - c = delegator.run(cmd_string, *args, **kwargs) - return_code = c.return_code - if environments.is_verbose(): - click_echo("Command output: {0}".format( - crayons.blue(decode_for_output(c.out)) + command = [cmd.command, *cmd.args] + if is_verbose: + click_echo(f"Running command: $ {cmd.cmdify()}") + c = subprocess_run(command, *args, **kwargs) + if is_verbose: + click_echo("Command output: {}".format( + crayons.cyan(decode_for_output(c.stdout)) ), err=True) - if not c.ok and catch_exceptions: - raise PipenvCmdError(cmd_string, c.out, c.err, return_code) + if c.returncode and catch_exceptions: + raise PipenvCmdError(cmd.cmdify(), c.stdout, c.stderr, c.returncode) return c @@ -219,10 +225,10 @@ def escape_grouped_arguments(s): def clean_pkg_version(version): """Uses pip to prepare a package version string, from our internal version.""" - return six.u(pep440_version(str(version).replace("==", ""))) + return pep440_version(str(version).replace("==", "")) -class HackedPythonVersion(object): +class HackedPythonVersion: """A Beautiful hack, which allows us to tell pip which version of Python we're using.""" def __init__(self, python_version, python_path): @@ -232,14 +238,14 @@ class HackedPythonVersion(object): def __enter__(self): # Only inject when the value is valid if self.python_version: - os.environ["PIP_PYTHON_VERSION"] = str(self.python_version) + os.environ["PIPENV_REQUESTED_PYTHON_VERSION"] = str(self.python_version) if self.python_path: os.environ["PIP_PYTHON_PATH"] = str(self.python_path) def __exit__(self, *args): # Restore original Python version information. try: - del os.environ["PIP_PYTHON_VERSION"] + del os.environ["PIPENV_REQUESTED_PYTHON_VERSION"] except KeyError: pass @@ -256,9 +262,9 @@ def prepare_pip_source_args(sources, pip_args=None): # Trust the host if it's not verified. if not sources[0].get("verify_ssl", True): url_parts = urllib3_util.parse_url(package_url) - url_port = ":{0}".format(url_parts.port) if url_parts.port else "" + url_port = f":{url_parts.port}" if url_parts.port else "" pip_args.extend( - ["--trusted-host", "{0}{1}".format(url_parts.host, url_port)] + ["--trusted-host", f"{url_parts.host}{url_port}"] ) # Add additional sources as extra indexes. if len(sources) > 1: @@ -270,18 +276,16 @@ def prepare_pip_source_args(sources, pip_args=None): # Trust the host if it's not verified. if not source.get("verify_ssl", True): url_parts = urllib3_util.parse_url(url) - url_port = ":{0}".format(url_parts.port) if url_parts.port else "" + url_port = f":{url_parts.port}" if url_parts.port else "" pip_args.extend( - ["--trusted-host", "{0}{1}".format(url_parts.host, url_port)] + ["--trusted-host", f"{url_parts.host}{url_port}"] ) return pip_args -def get_project_index(index=None, trusted_hosts=None, project=None): +def get_project_index(project, index=None, trusted_hosts=None): # type: (Optional[Union[str, TSource]], Optional[List[str]], Optional[Project]) -> TSource from .project import SourceNotFound - if not project: - from .core import project if trusted_hosts is None: trusted_hosts = [] if isinstance(index, Mapping): @@ -297,23 +301,21 @@ def get_project_index(index=None, trusted_hosts=None, project=None): def get_source_list( + project, # type: Project index=None, # type: Optional[Union[str, TSource]] extra_indexes=None, # type: Optional[List[str]] trusted_hosts=None, # type: Optional[List[str]] pypi_mirror=None, # type: Optional[str] - project=None, # type: Optional[Project] ): # type: (...) -> List[TSource] sources = [] # type: List[TSource] - if not project: - from .core import project if index: - sources.append(get_project_index(index)) + sources.append(get_project_index(project, index)) if extra_indexes: - if isinstance(extra_indexes, six.string_types): + if isinstance(extra_indexes, str): extra_indexes = [extra_indexes] for source in extra_indexes: - extra_src = get_project_index(source) + extra_src = get_project_index(project, source) if not sources or extra_src["url"] != sources[0]["url"]: sources.append(extra_src) else: @@ -330,10 +332,8 @@ def get_source_list( return sources -def get_indexes_from_requirement(req, project=None, index=None, extra_indexes=None, trusted_hosts=None, pypi_mirror=None): - # type: (Requirement, Optional[Project], Optional[Text], Optional[List[Text]], Optional[List[Text]], Optional[Text]) -> Tuple[TSource, List[TSource], List[Text]] - if not project: - from .core import project +def get_indexes_from_requirement(req, project, index=None, extra_indexes=None, trusted_hosts=None, pypi_mirror=None): + # type: (Requirement, Project, Optional[Text], Optional[List[Text]], Optional[List[Text]], Optional[Text]) -> Tuple[TSource, List[TSource], List[Text]] index_sources = [] # type: List[TSource] if not trusted_hosts: trusted_hosts = [] # type: List[Text] @@ -351,7 +351,7 @@ def get_indexes_from_requirement(req, project=None, index=None, extra_indexes=No indexes.extend(project_indexes) if len(indexes) > 1: index, extra_indexes = indexes[0], indexes[1:] - index_sources = get_source_list(index=index, extra_indexes=extra_indexes, trusted_hosts=trusted_hosts, pypi_mirror=pypi_mirror, project=project) + index_sources = get_source_list(project, index=index, extra_indexes=extra_indexes, trusted_hosts=trusted_hosts, pypi_mirror=pypi_mirror) if len(index_sources) > 1: index_source, extra_index_sources = index_sources[0], index_sources[1:] else: @@ -371,15 +371,44 @@ def get_pipenv_sitedir(): return None -class Resolver(object): +class HashCacheMixin: + + """Caches hashes of PyPI artifacts so we do not need to re-download them. + + Hashes are only cached when the URL appears to contain a hash in it and the + cache key includes the hash value returned from the server). This ought to + avoid issues where the location on the server changes. + """ + def __init__(self, directory, session): + self.session = session + if not os.path.isdir(directory): + os.makedirs(directory, exist_ok=True) + super().__init__(directory=directory) + + def get_hash(self, link): + # If there is no link hash (i.e., md5, sha256, etc.), we don't want + # to store it. + hash_value = self.get(link.url) + if not hash_value: + hash_value = self._get_file_hash(link).encode() + self.set(link.url, hash_value) + return hash_value.decode("utf8") + + def _get_file_hash(self, link): + from pipenv.vendor.pip_shims import shims + + h = hashlib.new(shims.FAVORITE_HASH) + with open_file(link.url, self.session) as fp: + for chunk in iter(lambda: fp.read(8096), b""): + h.update(chunk) + return ":".join([h.name, h.hexdigest()]) + + +class Resolver: def __init__( self, constraints, req_dir, project, sources, index_lookup=None, markers_lookup=None, skipped=None, clear=False, pre=False ): - from pipenv.patched.piptools import logging as piptools_logging - if environments.is_verbose(): - logging.log.verbose = True - piptools_logging.log.verbosity = environments.PIPENV_VERBOSITY self.initial_constraints = constraints self.req_dir = req_dir self.project = project @@ -398,12 +427,14 @@ class Resolver(object): self._constraints = None self._parsed_constraints = None self._resolver = None - self._repository = None + self._finder = None + self._ignore_compatibility_finder = None self._session = None self._constraint_file = None self._pip_options = None self._pip_command = None self._retry_attempts = 0 + self._hash_cache = None def __repr__(self): return ( @@ -414,19 +445,19 @@ class Resolver(object): @staticmethod @lru_cache() def _get_pip_command(): - from .vendor.pip_shims.shims import Command, cmdoptions + from pipenv.vendor.pip_shims import shims - class PipCommand(Command): - """Needed for pip-tools.""" + return shims.InstallCommand() - name = "PipCommand" + @property + def hash_cache(self): + from pipenv.vendor.pip_shims import shims - from pipenv.patched.piptools.pip import get_pip_command - pip_cmd = get_pip_command() - pip_cmd.parser.add_option(cmdoptions.no_use_pep517()) - pip_cmd.parser.add_option(cmdoptions.use_pep517()) - pip_cmd.parser.add_option(cmdoptions.no_build_isolation()) - return pip_cmd + if not self._hash_cache: + self._hash_cache = type("HashCache", (HashCacheMixin, shims.SafeFileCache), {})( + os.path.join(self.project.s.PIPENV_CACHE_DIR, "hashes"), self.session + ) + return self._hash_cache @classmethod def get_metadata( @@ -462,8 +493,12 @@ class Resolver(object): ) index_lookup.update(req_idx) markers_lookup.update(markers_idx) + # Add dependencies of any file (e.g. wheels/tarballs), source, or local + # directories into the initial constraint pool to be resolved with the + # rest of the dependencies, while adding the files/vcs deps/paths themselves + # to the lockfile directly constraint_update, lockfile_update = cls.get_deps_from_req( - req, resolver=transient_resolver + req, resolver=transient_resolver, resolve_vcs=project.s.PIPENV_RESOLVE_VCS ) constraints |= constraint_update skipped.update(lockfile_update) @@ -479,6 +514,7 @@ class Resolver(object): ): # type: (...) -> Tuple[Requirement, Dict[str, str], Dict[str, str]] from .vendor.requirementslib.models.requirements import Requirement + from .vendor.requirementslib.models.utils import DIRECT_URL_RE if index_lookup is None: index_lookup = {} if markers_lookup is None: @@ -486,20 +522,25 @@ class Resolver(object): if project is None: from .project import Project project = Project() - url = None - indexes, trusted_hosts, remainder = parse_indexes(line) - if indexes: - url = indexes[0] + index, extra_index, trust_host, remainder = parse_indexes(line) line = " ".join(remainder) req = None # type: Requirement try: req = Requirement.from_line(line) except ValueError: - raise ResolutionFailure("Failed to resolve requirement from line: {0!s}".format(line)) - if url: + direct_url = DIRECT_URL_RE.match(line) + if direct_url: + line = "{}#egg={}".format(line, direct_url.groupdict()["name"]) + try: + req = Requirement.from_line(line) + except ValueError: + raise ResolutionFailure(f"Failed to resolve requirement from line: {line!s}") + else: + raise ResolutionFailure(f"Failed to resolve requirement from line: {line!s}") + if index: try: index_lookup[req.normalized_name] = project.get_source( - url=url, refresh=True).get("name") + url=index, refresh=True).get("name") except TypeError: pass try: @@ -514,17 +555,14 @@ class Resolver(object): return req, index_lookup, markers_lookup @classmethod - def get_deps_from_line(cls, line): - # type: (str) -> Tuple[Set[str], Dict[str, Dict[str, Union[str, bool, List[str]]]]] - req, _, _ = cls.parse_line(line) - return cls.get_deps_from_req(req) - - @classmethod - def get_deps_from_req(cls, req, resolver=None): - # type: (Requirement, Optional["Resolver"]) -> Tuple[Set[str], Dict[str, Dict[str, Union[str, bool, List[str]]]]] - from .vendor.requirementslib.models.utils import _requirement_to_str_lowercase_name + def get_deps_from_req(cls, req, resolver=None, resolve_vcs=True): + # type: (Requirement, Optional["Resolver"], bool) -> Tuple[Set[str], Dict[str, Dict[str, Union[str, bool, List[str]]]]] from .vendor.requirementslib.models.requirements import Requirement - from requirementslib.utils import is_installable_dir + from .vendor.requirementslib.models.utils import ( + _requirement_to_str_lowercase_name + ) + from .vendor.requirementslib.utils import is_installable_dir + # TODO: this is way too complex, refactor this constraints = set() # type: Set[str] locked_deps = dict() # type: Dict[str, Dict[str, Union[str, bool, List[str]]]] @@ -548,7 +586,7 @@ class Resolver(object): requirements = [] # Allow users to toggle resolution off for non-editable VCS packages # but leave it on for local, installable folders on the filesystem - if environments.PIPENV_RESOLVE_VCS or ( + if resolve_vcs or ( req.editable or parsed_line.is_wheel or ( req.is_file_or_url and parsed_line.is_local and is_installable_dir(parsed_line.path) @@ -580,7 +618,7 @@ class Resolver(object): constraints.add(line) # ensure the top level entry remains as provided # note that we shouldn't pin versions for editable vcs deps - if (not req.is_vcs or (req.is_vcs and not req.editable)): + if not req.is_vcs: if req.specifiers: locked_deps[name]["version"] = req.specifiers elif parsed_line.setup_info and parsed_line.setup_info.version: @@ -589,25 +627,33 @@ class Resolver(object): ) # if not req.is_vcs: locked_deps.update({name: entry}) - if req.is_vcs and req.editable: - constraints.add(req.constraint_line) - if req.is_file_or_url and req.req.is_local and req.editable and ( - req.req.setup_path is not None and os.path.exists(req.req.setup_path)): - constraints.add(req.constraint_line) else: # if the dependency isn't installable, don't add it to constraints # and instead add it directly to the lock if req and req.requirement and ( req.requirement.marker and not req.requirement.marker.evaluate() ): - pypi = resolver.repository if resolver else None - best_match = pypi.find_best_match(req.ireq) if pypi else None + pypi = resolver.finder if resolver else None + ireq = req.ireq + best_match = pypi.find_best_candidate(ireq.name, ireq.specifier).best_candidate if pypi else None if best_match: - hashes = resolver.collect_hashes(best_match) if resolver else [] - new_req = Requirement.from_ireq(best_match) + ireq.req.specifier = ireq.specifier.__class__(f"=={best_match.version}") + hashes = resolver.collect_hashes(ireq) if resolver else [] + new_req = Requirement.from_ireq(ireq) new_req = new_req.add_hashes(hashes) name, entry = new_req.pipfile_entry locked_deps[pep423_name(name)] = translate_markers(entry) + click_echo( + "{} doesn't match your environment, " + "its dependencies won't be resolved.".format(req.as_line()), + err=True + ) + else: + click_echo( + "Could not find a version of {} that matches your environment, " + "it will be skipped.".format(req.as_line()), + err=True + ) return constraints, locked_deps constraints.add(req.constraint_line) return constraints, locked_deps @@ -617,9 +663,9 @@ class Resolver(object): def create( cls, deps, # type: List[str] + project, # type: Project index_lookup=None, # type: Dict[str, str] markers_lookup=None, # type: Dict[str, str] - project=None, # type: Project sources=None, # type: List[str] req_dir=None, # type: str clear=False, # type: bool @@ -633,9 +679,6 @@ class Resolver(object): index_lookup = {} if markers_lookup is None: markers_lookup = {} - if project is None: - from pipenv.core import project - project = project if sources is None: sources = project.sources constraints, skipped, index_lookup, markers_lookup = cls.get_metadata( @@ -648,19 +691,17 @@ class Resolver(object): ) @classmethod - def from_pipfile(cls, project=None, pipfile=None, dev=False, pre=False, clear=False): + def from_pipfile(cls, project, pipfile=None, dev=False, pre=False, clear=False): # type: (Optional[Project], Optional[Pipfile], bool, bool, bool) -> "Resolver" from pipenv.vendor.vistir.path import create_tracked_tempdir - if not project: - from pipenv.core import project if not pipfile: pipfile = project._pipfile req_dir = create_tracked_tempdir(suffix="-requirements", prefix="pipenv-") index_lookup, markers_lookup = {}, {} deps = set() if dev: - deps.update(set([req.as_line() for req in pipfile.dev_packages])) - deps.update(set([req.as_line() for req in pipfile.packages])) + deps.update({req.as_line() for req in pipfile.dev_packages}) + deps.update({req.as_line() for req in pipfile.packages}) constraints, skipped, index_lookup, markers_lookup = cls.get_metadata( list(deps), index_lookup, markers_lookup, project, project.sources, req_dir=req_dir, pre=pre, clear=clear @@ -676,25 +717,23 @@ class Resolver(object): self._pip_command = self._get_pip_command() return self._pip_command - def prepare_pip_args(self, use_pep517=True, build_isolation=True): + def prepare_pip_args(self, use_pep517=None, build_isolation=True): pip_args = [] if self.sources: pip_args = prepare_pip_source_args(self.sources, pip_args) - if not use_pep517: + if use_pep517 is False: pip_args.append("--no-use-pep517") - if not build_isolation: + if build_isolation is False: pip_args.append("--no-build-isolation") - pip_args.extend(["--cache-dir", environments.PIPENV_CACHE_DIR]) + if self.pre: + pip_args.append("--pre") + pip_args.extend(["--cache-dir", self.project.s.PIPENV_CACHE_DIR]) return pip_args @property def pip_args(self): - use_pep517 = False if ( - os.environ.get("PIP_NO_USE_PEP517", None) is not None - ) else (True if os.environ.get("PIP_USE_PEP517", None) is not None else None) - build_isolation = False if ( - os.environ.get("PIP_NO_BUILD_ISOLATION", None) is not None - ) else (True if os.environ.get("PIP_BUILD_ISOLATION", None) is not None else None) + use_pep517 = environments.get_from_env("USE_PEP517", prefix="PIP") + build_isolation = environments.get_from_env("BUILD_ISOLATION", prefix="PIP") if self._pip_args is None: self._pip_args = self.prepare_pip_args( use_pep517=use_pep517, build_isolation=build_isolation @@ -718,9 +757,9 @@ class Resolver(object): if self.sources: requirementstxt_sources = " ".join(args_to_add) if args_to_add else "" requirementstxt_sources = requirementstxt_sources.replace(" --", "\n--") - constraints_file.write(u"{0}\n".format(requirementstxt_sources)) + constraints_file.write(f"{requirementstxt_sources}\n") constraints = self.initial_constraints - constraints_file.write(u"\n".join([c for c in constraints])) + constraints_file.write("\n".join([c for c in constraints])) constraints_file.close() return constraints_file.name @@ -734,7 +773,12 @@ class Resolver(object): def pip_options(self): if self._pip_options is None: pip_options, _ = self.pip_command.parser.parse_args(self.pip_args) - pip_options.cache_dir = environments.PIPENV_CACHE_DIR + pip_options.cache_dir = self.project.s.PIPENV_CACHE_DIR + pip_options.no_python_version_warning = True + pip_options.no_input = True + pip_options.progress_bar = "off" + pip_options.ignore_requires_python = True + pip_options.pre = self.pre or self.project.settings.get("allow_prereleases", False) self._pip_options = pip_options return self._pip_options @@ -742,89 +786,107 @@ class Resolver(object): def session(self): if self._session is None: self._session = self.pip_command._build_session(self.pip_options) - # if environments.is_verbose(): - # click_echo( - # crayons.blue("Using pip: {0}".format(" ".join(self.pip_args))), err=True - # ) return self._session @property - def repository(self): - if self._repository is None: - from pipenv.patched.piptools.repositories.pypi import PyPIRepository - self._repository = PyPIRepository( - pip_options=self.pip_options, use_json=False, session=self.session, - build_isolation=self.pip_options.build_isolation + def finder(self): + from pipenv.vendor.pip_shims import shims + if self._finder is None: + self._finder = shims.get_package_finder( + install_cmd=self.pip_command, + options=self.pip_options, + session=self.session ) - return self._repository + return self._finder @property - def constraints(self): - if self._constraints is None: - from pip_shims.shims import parse_requirements - self._constraints = parse_requirements( - self.constraint_file, finder=self.repository.finder, session=self.session, - options=self.pip_options + def ignore_compatibility_finder(self): + from pipenv.vendor.pip_shims import shims + if self._ignore_compatibility_finder is None: + ignore_compatibility_finder = shims.get_package_finder( + install_cmd=self.pip_command, + options=self.pip_options, + session=self.session, ) - return self._constraints + # It would be nice if `shims.get_package_finder` took an + # `ignore_compatibility` parameter, but that's some vendorered code + # we'd rather avoid touching. + ignore_compatibility_finder._ignore_compatibility = True + self._ignore_compatibility_finder = ignore_compatibility_finder + return self._ignore_compatibility_finder @property def parsed_constraints(self): + from pipenv.vendor.pip_shims import shims + if self._parsed_constraints is None: - self._parsed_constraints = [c for c in self.constraints] + self._parsed_constraints = shims.parse_requirements( + self.constraint_file, finder=self.finder, session=self.session, + options=self.pip_options + ) return self._parsed_constraints - def get_resolver(self, clear=False, pre=False): - from pipenv.patched.piptools.resolver import Resolver - self._resolver = Resolver( - constraints=self.parsed_constraints, repository=self.repository, - clear_caches=clear, prereleases=pre, + @property + def constraints(self): + from pipenv.patched.notpip._internal.req.constructors import install_req_from_parsed_requirement + + if self._constraints is None: + self._constraints = [ + install_req_from_parsed_requirement( + c, isolated=self.pip_options.build_isolation, + use_pep517=self.pip_options.use_pep517, user_supplied=True + ) + for c in self.parsed_constraints + ] + return self._constraints + + @contextlib.contextmanager + def get_resolver(self, clear=False): + from pipenv.vendor.pip_shims.shims import ( + WheelCache, get_requirement_tracker, global_tempdir_manager ) - @property - def resolver(self): - if self._resolver is None: - self.get_resolver(clear=self.clear, pre=self.pre) - return self._resolver + with global_tempdir_manager(), get_requirement_tracker() as req_tracker, TemporaryDirectory(suffix="-build", prefix="pipenv-") as directory: + pip_options = self.pip_options + finder = self.finder + wheel_cache = WheelCache(pip_options.cache_dir, pip_options.format_control) + directory.path = directory.name + preparer = self.pip_command.make_requirement_preparer( + temp_build_dir=directory, + options=pip_options, + req_tracker=req_tracker, + session=self.session, + finder=finder, + use_user_site=False, + ) + resolver = self.pip_command.make_resolver( + preparer=preparer, + finder=finder, + options=pip_options, + wheel_cache=wheel_cache, + use_user_site=False, + ignore_installed=True, + ignore_requires_python=pip_options.ignore_requires_python, + force_reinstall=pip_options.force_reinstall, + upgrade_strategy="to-satisfy-only", + use_pep517=pip_options.use_pep517, + ) + yield resolver def resolve(self): - from pipenv.vendor.pip_shims.shims import DistributionNotFound - from pipenv.vendor.requests.exceptions import HTTPError - from pipenv.patched.piptools.exceptions import NoCandidateFound - from pipenv.patched.piptools.cache import CorruptCacheError - from .exceptions import CacheError, ResolutionFailure - with temp_environ(): - os.environ["PIP_NO_USE_PEP517"] = str("") + from pipenv.vendor.pip_shims.shims import InstallationError + from pipenv.exceptions import ResolutionFailure + + with temp_environ(), self.get_resolver() as resolver: try: - results = self.resolver.resolve(max_rounds=environments.PIPENV_MAX_ROUNDS) - except CorruptCacheError as e: - if environments.PIPENV_IS_CI or self.clear: - if self._retry_attempts < 3: - self.get_resolver(clear=True, pre=self.pre) - self._retry_attempts += 1 - self.resolve() - else: - raise CacheError(e.path) - except (NoCandidateFound, DistributionNotFound, HTTPError) as e: + results = resolver.resolve(self.constraints, check_supported_wheels=False) + except InstallationError as e: raise ResolutionFailure(message=str(e)) else: - self.results = results - self.resolved_tree.update(results) + self.results = set(results.all_requirements) + self.resolved_tree.update(self.results) return self.resolved_tree - @lru_cache(maxsize=1024) - def fetch_candidate(self, ireq): - candidates = self.repository.find_all_candidates(ireq.name) - matched_version = next(iter(sorted( - ireq.specifier.filter((c.version for c in candidates), True), reverse=True) - ), None) - if matched_version: - matched_candidate = next(iter( - c for c in candidates if c.version == matched_version - )) - return matched_candidate - return None - def resolve_constraints(self): from .vendor.requirementslib.models.markers import marker_from_specifier new_tree = set() @@ -832,136 +894,100 @@ class Resolver(object): if result.markers: self.markers[result.name] = result.markers else: - candidate = self.fetch_candidate(result) - requires_python = getattr(candidate, "requires_python", None) - if requires_python: - marker = marker_from_specifier(candidate.requires_python) - self.markers[result.name] = marker - result.markers = marker - if result.req: - result.req.marker = marker + candidate = self.finder.find_best_candidate(result.name, result.specifier).best_candidate + if candidate: + requires_python = candidate.link.requires_python + if requires_python: + marker = marker_from_specifier(requires_python) + self.markers[result.name] = marker + result.markers = marker + if result.req: + result.req.marker = marker new_tree.add(result) self.resolved_tree = new_tree @classmethod - def prepend_hash_types(cls, checksums): - cleaned_checksums = [] + def prepend_hash_types(cls, checksums, hash_type): + cleaned_checksums = set() for checksum in checksums: if not checksum: continue - if not checksum.startswith("sha256:"): - checksum = "sha256:{0}".format(checksum) - cleaned_checksums.append(checksum) + if not checksum.startswith(f"{hash_type}:"): + checksum = f"{hash_type}:{checksum}" + cleaned_checksums.add(checksum) return cleaned_checksums + def _get_hashes_from_pypi(self, ireq): + from pipenv.vendor.pip_shims import shims + + pkg_url = f"https://pypi.org/pypi/{ireq.name}/json" + session = _get_requests_session(self.project.s.PIPENV_MAX_RETRIES) + try: + collected_hashes = set() + # Grab the hashes from the new warehouse API. + r = session.get(pkg_url, timeout=10) + api_releases = r.json()["releases"] + cleaned_releases = {} + for api_version, api_info in api_releases.items(): + api_version = clean_pkg_version(api_version) + cleaned_releases[api_version] = api_info + version = "" + if ireq.specifier: + spec = next(iter(s for s in ireq.specifier), None) + if spec: + version = spec.version + for release in cleaned_releases[version]: + collected_hashes.add(release["digests"][shims.FAVORITE_HASH]) + return self.prepend_hash_types(collected_hashes, shims.FAVORITE_HASH) + except (ValueError, KeyError, ConnectionError): + if self.project.s.is_verbose(): + click_echo( + "{}: Error generating hash for {}".format( + crayons.red("Warning", bold=True), ireq.name + ), err=True + ) + return None + def collect_hashes(self, ireq): - from .vendor.requests import ConnectionError - collected_hashes = [] - if ireq in self.hashes: - collected_hashes += list(self.hashes.get(ireq, [])) - if self._should_include_hash(ireq): - try: - hash_map = self.get_hash(ireq) - collected_hashes += list(hash_map) - except (ValueError, KeyError, IndexError, ConnectionError): - pass - elif any( + if ireq.link: + link = ireq.link + if link.is_vcs or (link.is_file and link.is_existing_dir()): + return set() + if ireq.original_link: + return {self._get_hash_from_link(ireq.original_link)} + + if not is_pinned_requirement(ireq): + return set() + + if any( "python.org" in source["url"] or "pypi.org" in source["url"] for source in self.sources ): - pkg_url = "https://pypi.org/pypi/{0}/json".format(ireq.name) - session = _get_requests_session() - try: - # Grab the hashes from the new warehouse API. - r = session.get(pkg_url, timeout=10) - api_releases = r.json()["releases"] - cleaned_releases = {} - for api_version, api_info in api_releases.items(): - api_version = clean_pkg_version(api_version) - cleaned_releases[api_version] = api_info - version = "" - if ireq.specifier: - spec = next(iter(s for s in list(ireq.specifier._specs)), None) - if spec: - version = spec.version - for release in cleaned_releases[version]: - collected_hashes.append(release["digests"]["sha256"]) - collected_hashes = self.prepend_hash_types(collected_hashes) - except (ValueError, KeyError, ConnectionError): - if environments.is_verbose(): - click_echo( - "{0}: Error generating hash for {1}".format( - crayons.red("Warning", bold=True), ireq.name - ), err=True - ) - return collected_hashes + hashes = self._get_hashes_from_pypi(ireq) + if hashes: + return hashes - @staticmethod - def _should_include_hash(ireq): - from pipenv.vendor.vistir.compat import Path, to_native_string - from pipenv.vendor.vistir.path import url_to_path - - # We can only hash artifacts. - try: - if not ireq.link.is_artifact: - return False - except AttributeError: - return False - - # But we don't want normal pypi artifcats since the normal resolver - # handles those - if is_pypi_url(ireq.link.url): - return False - - # We also don't want to try to hash directories as this will fail - # as these are editable deps and are not hashable. - if ( - ireq.link.scheme == "file" - and Path(to_native_string(url_to_path(ireq.link.url))).is_dir() - ): - return False - return True - - def get_hash(self, ireq, ireq_hashes=None): - """ - Retrieve hashes for a specific ``InstallRequirement`` instance. - - :param ireq: An ``InstallRequirement`` to retrieve hashes for - :type ireq: :class:`~pip_shims.InstallRequirement` - :return: A set of hashes. - :rtype: Set - """ - - # We _ALWAYS MUST PRIORITIZE_ the inclusion of hashes from local sources - # PLEASE *DO NOT MODIFY THIS* TO CHECK WHETHER AN IREQ ALREADY HAS A HASH - # RESOLVED. The resolver will pull hashes from PyPI and only from PyPI. - # The entire purpose of this approach is to include missing hashes. - # This fixes a race condition in resolution for missing dependency caches - # see pypa/pipenv#3289 - if not self._should_include_hash(ireq): - return add_to_set(set(), ireq_hashes) - elif self._should_include_hash(ireq) and ( - not ireq_hashes or ireq.link.scheme == "file" - ): - if not ireq_hashes: - ireq_hashes = set() - new_hashes = self.resolver.repository._hash_cache.get_hash(ireq.link) - ireq_hashes = add_to_set(ireq_hashes, new_hashes) - else: - ireq_hashes = set(ireq_hashes) - # The _ONLY CASE_ where we flat out set the value is if it isn't present - # It's a set, so otherwise we *always* need to do a union update - if ireq not in self.hashes: - return ireq_hashes - else: - return self.hashes[ireq] | ireq_hashes + applicable_candidates = self.ignore_compatibility_finder.find_best_candidate( + ireq.name, ireq.specifier + ).iter_applicable() + return { + self._get_hash_from_link(candidate.link) + for candidate in applicable_candidates + } def resolve_hashes(self): if self.results is not None: - resolved_hashes = self.resolver.resolve_hashes(self.results) - for ireq, ireq_hashes in resolved_hashes.items(): - self.hashes[ireq] = self.get_hash(ireq, ireq_hashes=ireq_hashes) - return self.hashes + for ireq in self.results: + self.hashes[ireq] = self.collect_hashes(ireq) + return self.hashes + + def _get_hash_from_link(self, link): + from pipenv.vendor.pip_shims import shims + + if link.hash and link.hash_name == shims.FAVORITE_HASH: + return f"{link.hash_name}:{link.hash}" + + return self.hash_cache.get_hash(link) def _clean_skipped_result(self, req, value): ref = None @@ -975,28 +1001,26 @@ class Resolver(object): ref = ref if ref is not None else entry.get("ref") if ref: entry["ref"] = ref - if self._should_include_hash(ireq): - collected_hashes = self.collect_hashes(ireq) - if collected_hashes: - entry["hashes"] = sorted(set(collected_hashes)) + collected_hashes = self.collect_hashes(ireq) + if collected_hashes: + entry["hashes"] = sorted(set(collected_hashes)) return req.name, entry def clean_results(self): - from pipenv.vendor.requirementslib.models.requirements import Requirement + from pipenv.vendor.requirementslib.models.requirements import ( + Requirement + ) reqs = [(Requirement.from_ireq(ireq), ireq) for ireq in self.resolved_tree] results = {} for req, ireq in reqs: if (req.vcs and req.editable and not req.is_direct_url): continue - collected_hashes = self.collect_hashes(ireq) + elif req.normalized_name in self.skipped.keys(): + continue + collected_hashes = self.hashes.get(ireq, set()) req = req.add_hashes(collected_hashes) - if not collected_hashes and self._should_include_hash(ireq): - discovered_hashes = self.hashes.get(ireq, set()) | self.get_hash(ireq) - if discovered_hashes: - req = req.add_hashes(discovered_hashes) - self.hashes[ireq] = collected_hashes = discovered_hashes if collected_hashes: - collected_hashes = sorted(set(collected_hashes)) + collected_hashes = sorted(collected_hashes) name, entry = format_requirement_for_lockfile( req, self.markers_lookup, self.index_lookup, collected_hashes ) @@ -1028,24 +1052,28 @@ def format_requirement_for_lockfile(req, markers_lookup, index_lookup, hashes=No name, pf_entry = req.pipfile_entry name = pep423_name(req.name) entry = {} - if isinstance(pf_entry, six.string_types): + if isinstance(pf_entry, str): entry["version"] = pf_entry.lstrip("=") else: entry.update(pf_entry) - if version is not None: + if version is not None and not req.is_vcs: entry["version"] = version - if req.line_instance.is_direct_url: + if req.line_instance.is_direct_url and not req.is_vcs: entry["file"] = req.req.uri if hashes: entry["hashes"] = sorted(set(hashes)) entry["name"] = name - if index: # and index != next(iter(project.sources), {}).get("name"): + if index: entry.update({"index": index}) if markers: entry.update({"markers": markers}) entry = translate_markers(entry) - if req.vcs or req.editable and entry.get("index"): - del entry["index"] + if req.vcs or req.editable: + for key in ("index", "version", "file"): + try: + del entry[key] + except KeyError: + pass return name, entry @@ -1073,7 +1101,7 @@ def actually_resolve_deps( with warnings.catch_warnings(record=True) as warning_list: resolver = Resolver.create( - deps, index_lookup, markers_lookup, project, sources, req_dir, clear, pre + deps, project, index_lookup, markers_lookup, sources, req_dir, clear, pre ) resolver.resolve() hashes = resolver.resolve_hashes() @@ -1086,13 +1114,13 @@ def actually_resolve_deps( @contextlib.contextmanager -def create_spinner(text, nospin=None, spinner_name=None): +def create_spinner(text, setting, nospin=None, spinner_name=None): from .vendor.vistir import spin from .vendor.vistir.misc import fs_str if not spinner_name: - spinner_name = environments.PIPENV_SPINNER + spinner_name = setting.PIPENV_SPINNER if nospin is None: - nospin = environments.PIPENV_NOSPIN + nospin = setting.PIPENV_NOSPIN with spin.create_spinner( spinner_name=spinner_name, start_text=fs_str(text), @@ -1101,52 +1129,35 @@ def create_spinner(text, nospin=None, spinner_name=None): yield sp -def resolve(cmd, sp): - import delegator - from .cmdparse import Script - from .vendor.pexpect.exceptions import EOF, TIMEOUT - from .vendor.vistir.compat import to_native_string - from .vendor.vistir.misc import echo - EOF.__module__ = "pexpect.exceptions" +def resolve(cmd, sp, project): from ._compat import decode_output - c = delegator.run(Script.parse(cmd).cmdify(), block=False, env=os.environ.copy()) - if environments.is_verbose(): - c.subprocess.logfile = sys.stderr - _out = decode_output("") - result = None - out = to_native_string("") - while True: - result = None - try: - result = c.expect(u"\n", timeout=environments.PIPENV_INSTALL_TIMEOUT) - except TIMEOUT: - pass - except EOF: - break - except KeyboardInterrupt: - c.kill() - break - if result: - _out = c.subprocess.before - _out = decode_output("{0}".format(_out)) - out += _out - # sp.text = to_native_string("{0}".format(_out[:100])) - if environments.is_verbose(): - sp.hide_and_write(out.splitlines()[-1].rstrip()) - else: - break - c.block() - if c.return_code != 0: + from .cmdparse import Script + from .vendor.vistir.misc import echo + c = subprocess_run(Script.parse(cmd).cmd_args, block=False, env=os.environ.copy()) + is_verbose = project.s.is_verbose() + err = "" + for line in iter(c.stderr.readline, ""): + line = decode_output(line) + if not line.rstrip(): + continue + err += line + if is_verbose: + sp.hide_and_write(line.rstrip()) + + c.wait() + returncode = c.poll() + out = c.stdout.read() + if returncode != 0: sp.red.fail(environments.PIPENV_SPINNER_FAIL_TEXT.format( "Locking Failed!" )) - echo(c.out.strip(), err=True) - if not environments.is_verbose(): - echo(out, err=True) - sys.exit(c.return_code) - if environments.is_verbose(): - echo(c.err.strip(), err=True) - return c + echo(out.strip(), err=True) + if not is_verbose: + echo(err, err=True) + sys.exit(returncode) + if is_verbose: + echo(out.strip(), err=True) + return subprocess.CompletedProcess(c.args, returncode, out, err) def get_locked_dep(dep, pipfile_section, prefer_pipfile=True): @@ -1176,7 +1187,7 @@ def get_locked_dep(dep, pipfile_section, prefer_pipfile=True): lockfile_name, lockfile_dict = lockfile_entry.copy().popitem() lockfile_version = lockfile_dict.get("version", "") # Keep pins from the lockfile - if prefer_pipfile and lockfile_version != version and version.startswith("=="): + if prefer_pipfile and lockfile_version != version and version.startswith("==") and "*" not in version: lockfile_dict["version"] = version lockfile_entry[lockfile_name] = lockfile_dict return lockfile_entry @@ -1242,12 +1253,13 @@ def venv_resolve_deps( :rtype: None """ - from .vendor.vistir.misc import fs_str - from .vendor.vistir.compat import Path, JSONDecodeError, NamedTemporaryFile - from .vendor.vistir.path import create_tracked_tempdir + import json + from . import resolver from ._compat import decode_for_output - import json + from .vendor.vistir.compat import JSONDecodeError, NamedTemporaryFile, Path + from .vendor.vistir.misc import fs_str + from .vendor.vistir.path import create_tracked_tempdir results = [] pipfile_section = "dev-packages" if dev else "packages" @@ -1283,7 +1295,7 @@ def venv_resolve_deps( os.environ.update({fs_str(k): fs_str(val) for k, val in os.environ.items()}) if pypi_mirror: os.environ["PIPENV_PYPI_MIRROR"] = str(pypi_mirror) - os.environ["PIPENV_VERBOSITY"] = str(environments.PIPENV_VERBOSITY) + os.environ["PIPENV_VERBOSITY"] = str(project.s.PIPENV_VERBOSITY) os.environ["PIPENV_REQ_DIR"] = fs_str(req_dir) os.environ["PIP_NO_INPUT"] = fs_str("1") pipenv_site_dir = get_pipenv_sitedir() @@ -1293,7 +1305,7 @@ def venv_resolve_deps( os.environ.pop("PIPENV_SITE_DIR", None) if keep_outdated: os.environ["PIPENV_KEEP_OUTDATED"] = fs_str("1") - with create_spinner(text=decode_for_output("Locking...")) as sp: + with create_spinner(text=decode_for_output("Locking..."), setting=project.s) as sp: # This conversion is somewhat slow on local and file-type requirements since # we now download those requirements / make temporary folders to perform # dependency resolution on them, so we are including this step inside the @@ -1305,20 +1317,22 @@ def venv_resolve_deps( constraints = set(deps) os.environ["PIPENV_PACKAGES"] = str("\n".join(constraints)) sp.write(decode_for_output("Resolving dependencies...")) - c = resolve(cmd, sp) - results = c.out.strip() - if c.ok: + c = resolve(cmd, sp, project=project) + results = c.stdout.strip() + if c.returncode == 0: sp.green.ok(environments.PIPENV_SPINNER_OK_TEXT.format("Success!")) + if not project.s.is_verbose() and c.stderr.strip(): + click_echo(crayons.yellow(f"Warning: {c.stderr.strip()}"), err=True) else: sp.red.fail(environments.PIPENV_SPINNER_FAIL_TEXT.format("Locking Failed!")) - click_echo("Output: {0}".format(c.out.strip()), err=True) - click_echo("Error: {0}".format(c.err.strip()), err=True) + click_echo(f"Output: {c.stdout.strip()}", err=True) + click_echo(f"Error: {c.stderr.strip()}", err=True) try: - with open(target_file.name, "r") as fh: + with open(target_file.name) as fh: results = json.load(fh) except (IndexError, JSONDecodeError): - click_echo(c.out.strip(), err=True) - click_echo(c.err.strip(), err=True) + click_echo(c.stdout.strip(), err=True) + click_echo(c.stderr.strip(), err=True) if os.path.exists(target_file.name): os.unlink(target_file.name) raise RuntimeError("There was a problem with locking.") @@ -1398,13 +1412,27 @@ def resolve_deps( def is_star(val): - return isinstance(val, six.string_types) and val == "*" + return isinstance(val, str) and val == "*" def is_pinned(val): if isinstance(val, Mapping): val = val.get("version") - return isinstance(val, six.string_types) and val.startswith("==") + return isinstance(val, str) and val.startswith("==") + + +def is_pinned_requirement(ireq): + """ + Returns whether an InstallRequirement is a "pinned" requirement. + """ + if ireq.editable: + return False + + if ireq.req is None or len(ireq.specifier) != 1: + return False + + spec = next(iter(ireq.specifier)) + return spec.operator in {"==", "==="} and not spec.version.endswith(".*") def convert_deps_to_pip(deps, project=None, r=True, include_index=True): @@ -1443,7 +1471,7 @@ def mkdir_p(newdir): pass elif os.path.isfile(newdir): raise OSError( - "a file with the same name as the desired dir, '{0}', already exists.".format( + "a file with the same name as the desired dir, '{}', already exists.".format( newdir ) ) @@ -1488,15 +1516,14 @@ def is_editable(pipfile_entry): def is_installable_file(path): """Determine if a path can potentially be installed""" - from .vendor.pip_shims.shims import is_installable_dir, is_archive_file from .patched.notpip._internal.utils.packaging import specifiers - from ._compat import Path + from .vendor.pip_shims.shims import is_archive_file, is_installable_dir if hasattr(path, "keys") and any( key for key in path.keys() if key in ["file", "path"] ): path = urlparse(path["file"]).path if "file" in path else path["path"] - if not isinstance(path, six.string_types) or path == "*": + if not isinstance(path, str) or path == "*": return False # If the string starts with a valid specifier operator, test if it is a valid @@ -1514,7 +1541,7 @@ def is_installable_file(path): return False lookup_path = Path(path) - absolute_path = "{0}".format(lookup_path.absolute()) + absolute_path = f"{lookup_path.absolute()}" if lookup_path.is_dir() and is_installable_dir(absolute_path): return True @@ -1541,10 +1568,10 @@ def is_file(package): def pep440_version(version): """Normalize version to PEP 440 standards""" - from .vendor.pip_shims.shims import parse_version - # Use pip built-in version parser. - return str(parse_version(version)) + from pipenv.vendor.pip_shims import shims + + return str(shims.parse_version(version)) def pep423_name(name): @@ -1561,11 +1588,11 @@ def proper_case(package_name): """Properly case project name from pypi.org.""" # Hit the simple API. r = _get_requests_session().get( - "https://pypi.org/pypi/{0}/json".format(package_name), timeout=0.3, stream=True + f"https://pypi.org/pypi/{package_name}/json", timeout=0.3, stream=True ) if not r.ok: - raise IOError( - "Unable to find package {0} in PyPI repository.".format(package_name) + raise OSError( + f"Unable to find package {package_name} in PyPI repository." ) r = parse.parse("https://pypi.org/pypi/{name}/json", r.url) @@ -1600,7 +1627,6 @@ def find_windows_executable(bin_path, exe_name): def path_to_url(path): - from ._compat import Path return Path(normalize_drive(os.path.abspath(path))).as_uri() @@ -1612,20 +1638,44 @@ def normalize_path(path): def get_url_name(url): - if not isinstance(url, six.string_types): + if not isinstance(url, str): return return urllib3_util.parse_url(url).host +def get_host_and_port(url): + """Get the host, or the host:port pair if port is explicitly included, for the given URL. + + Examples: + >>> get_host_and_port('example.com') + 'example.com' + >>> get_host_and_port('example.com:443') + 'example.com:443' + >>> get_host_and_port('http://example.com') + 'example.com' + >>> get_host_and_port('https://example.com/') + 'example.com' + >>> get_host_and_port('https://example.com:8081') + 'example.com:8081' + >>> get_host_and_port('ssh://example.com') + 'example.com' + + :param url: the URL string to parse + :return: a string with the host:port pair if the URL includes port number explicitly; otherwise, returns host only + """ + url = urllib3_util.parse_url(url) + return '{}:{}'.format(url.host, url.port) if url.port else url.host + + def get_canonical_names(packages): """Canonicalize a list of packages and return a set of canonical names""" from .vendor.packaging.utils import canonicalize_name if not isinstance(packages, Sequence): - if not isinstance(packages, six.string_types): + if not isinstance(packages, str): return packages packages = [packages] - return set([canonicalize_name(pkg) for pkg in packages if pkg]) + return {canonicalize_name(pkg) for pkg in packages if pkg} def walk_up(bottom): @@ -1652,8 +1702,7 @@ def walk_up(bottom): if new_path == bottom: return - for x in walk_up(new_path): - yield x + yield from walk_up(new_path) def find_requirements(max_depth=3): @@ -1694,14 +1743,14 @@ def temp_path(): def load_path(python): - from ._compat import Path - import delegator import json + + from pathlib import Path python = Path(python).as_posix() json_dump_commmand = '"import json, sys; print(json.dumps(sys.path));"' - c = delegator.run('"{0}" -c {1}'.format(python, json_dump_commmand)) - if c.return_code == 0: - return json.loads(c.out.strip()) + c = subprocess_run([python, "-c", json_dump_commmand]) + if c.returncode == 0: + return json.loads(c.stdout.strip()) else: return [] @@ -1730,11 +1779,11 @@ def create_mirror_source(url): } -def download_file(url, filename): +def download_file(url, filename, max_retries=1): """Downloads file from url to a path with filename""" - r = _get_requests_session().get(url, stream=True) + r = _get_requests_session(max_retries).get(url, stream=True) if not r.ok: - raise IOError("Unable to download file") + raise OSError("Unable to download file") with open(filename, "wb") as f: f.write(r.content) @@ -1749,13 +1798,13 @@ def normalize_drive(path): See: <https://github.com/pypa/pipenv/issues/1218> """ - if os.name != "nt" or not isinstance(path, six.string_types): + if os.name != "nt" or not isinstance(path, str): return path drive, tail = os.path.splitdrive(path) # Only match (lower cased) local drives (e.g. 'c:'), not UNC mounts. if drive.islower() and len(drive) == 2 and drive[1] == ":": - return "{}{}".format(drive.upper(), tail) + return f"{drive.upper()}{tail}" return path @@ -1772,7 +1821,7 @@ def is_readonly_path(fn): def set_write_bit(fn): - if isinstance(fn, six.string_types) and not os.path.exists(fn): + if isinstance(fn, str) and not os.path.exists(fn): return os.chmod(fn, stat.S_IWRITE | stat.S_IWUSR | stat.S_IRUSR) return @@ -1800,7 +1849,7 @@ def handle_remove_readonly(func, path, exc): set_write_bit(path) try: func(path) - except (OSError, IOError) as e: + except OSError as e: if e.errno in [errno.EACCES, errno.EPERM]: warnings.warn(default_warning_message.format(path), ResourceWarning) return @@ -1814,14 +1863,14 @@ def handle_remove_readonly(func, path, exc): def escape_cmd(cmd): if any(special_char in cmd for special_char in ["<", ">", "&", ".", "^", "|", "?"]): - cmd = '\"{0}\"'.format(cmd) + cmd = f'\"{cmd}\"' return cmd def safe_expandvars(value): """Call os.path.expandvars if value is a string, otherwise do nothing. """ - if isinstance(value, six.string_types): + if isinstance(value, str): return os.path.expandvars(value) return value @@ -1857,20 +1906,17 @@ def get_vcs_deps( if requirement.is_vcs: try: with temp_path(), locked_repository(requirement) as repo: - from pipenv.vendor.requirementslib.models.requirements import Requirement + from pipenv.vendor.requirementslib.models.requirements import ( + Requirement + ) + # from distutils.sysconfig import get_python_lib # sys.path = [repo.checkout_directory, "", ".", get_python_lib(plat_specific=0)] commit_hash = repo.get_commit_hash() name = requirement.normalized_name - version = requirement._specifiers = "=={0}".format(requirement.req.setup_info.version) lockfile[name] = requirement.pipfile_entry[1] lockfile[name]['ref'] = commit_hash result.append(requirement) - version = requirement.specifiers - if not version and requirement.specifiers: - version = requirement.specifiers - if version: - lockfile[name]['version'] = version except OSError: continue return result, lockfile @@ -1904,13 +1950,13 @@ def translate_markers(pipfile_entry): if 'extra' not in marker: marker_set.add(marker) for m in pipfile_markers: - entry = "{0}".format(pipfile_entry[m]) + entry = f"{pipfile_entry[m]}" if m != "markers": - marker_set.add(str(Marker("{0} {1}".format(m, entry)))) + marker_set.add(str(Marker(f"{m} {entry}"))) new_pipfile.pop(m) if marker_set: new_pipfile["markers"] = str(Marker(" or ".join( - "{0}".format(s) if " and " in s else s + f"{s}" if " and " in s else s for s in sorted(dedup(marker_set)) ))).replace('"', "'") return new_pipfile @@ -1923,9 +1969,9 @@ def clean_resolved_dep(dep, is_top_level=False, pipfile_entry=None): # We use this to determine if there are any markers on top level packages # So we can make sure those win out during resolution if the packages reoccur if "version" in dep and dep["version"] and not dep.get("editable", False): - version = "{0}".format(dep["version"]) + version = "{}".format(dep["version"]) if not version.startswith("=="): - version = "=={0}".format(version) + version = f"=={version}" lockfile["version"] = version if is_vcs(dep): ref = dep.get("ref", None) @@ -1973,8 +2019,6 @@ def clean_resolved_dep(dep, is_top_level=False, pipfile_entry=None): def get_workon_home(): - from ._compat import Path - workon_home = os.environ.get("WORKON_HOME") if not workon_home: if os.name == "nt": @@ -2028,7 +2072,6 @@ def locked_repository(requirement): @contextmanager def chdir(path): """Context manager to change working directories.""" - from ._compat import Path if not path: return prev_cwd = Path.cwd().as_posix() @@ -2046,24 +2089,24 @@ def looks_like_dir(path): return any(sep in path for sep in seps) -def parse_indexes(line): +def parse_indexes(line, strict=False): from argparse import ArgumentParser - parser = ArgumentParser("indexes") - parser.add_argument( - "--index", "-i", "--index-url", - metavar="index_url", action="store", nargs="?", - ) - parser.add_argument( - "--extra-index-url", "--extra-index", - metavar="extra_indexes", action="append", - ) - parser.add_argument("--trusted-host", metavar="trusted_hosts", action="append") + + comment_re = re.compile(r"(?:^|\s+)#.*$") + line = comment_re.sub("", line) + parser = ArgumentParser("indexes", allow_abbrev=False) + parser.add_argument("-i", "--index-url", dest="index") + parser.add_argument("--extra-index-url", dest="extra_index") + parser.add_argument("--trusted-host", dest="trusted_host") args, remainder = parser.parse_known_args(line.split()) - index = [] if not args.index else [args.index] - extra_indexes = [] if not args.extra_index_url else args.extra_index_url - indexes = index + extra_indexes - trusted_hosts = args.trusted_host if args.trusted_host else [] - return indexes, trusted_hosts, remainder + index = args.index + extra_index = args.extra_index + trusted_host = args.trusted_host + if strict and sum( + bool(arg) for arg in (index, extra_index, trusted_host, remainder) + ) > 1: + raise ValueError("Index arguments must be on their own lines.") + return index, extra_index, trusted_host, remainder @contextmanager @@ -2111,10 +2154,10 @@ def is_url_equal(url, other_url): "https://mydomain.com/some?some_query") False """ - if not isinstance(url, six.string_types): - raise TypeError("Expected string for url, received {0!r}".format(url)) - if not isinstance(other_url, six.string_types): - raise TypeError("Expected string for url, received {0!r}".format(other_url)) + if not isinstance(url, str): + raise TypeError(f"Expected string for url, received {url!r}") + if not isinstance(other_url, str): + raise TypeError(f"Expected string for url, received {other_url!r}") parsed_url = urllib3_util.parse_url(url) parsed_other_url = urllib3_util.parse_url(other_url) unparsed = parsed_url._replace(auth=None, query=None, fragment=None).url @@ -2139,14 +2182,14 @@ def make_posix(path): >>> make_posix("c:\\users\\user\\venvs\\some_venv") "c:/users/user/venvs/some_venv" """ - if not isinstance(path, six.string_types): - raise TypeError("Expected a string for path, received {0!r}...".format(path)) + if not isinstance(path, str): + raise TypeError(f"Expected a string for path, received {path!r}...") starts_with_sep = path.startswith(os.path.sep) separated = normalize_path(path).split(os.path.sep) if isinstance(separated, (list, tuple)): path = posixpath.join(*separated) if starts_with_sep: - path = "/{0}".format(path) + path = f"/{path}" return path @@ -2170,9 +2213,9 @@ def find_python(finder, line=None): :rtype: str """ - if line and not isinstance(line, six.string_types): + if line and not isinstance(line, str): raise TypeError( - "Invalid python search type: expected string, received {0!r}".format(line) + f"Invalid python search type: expected string, received {line!r}" ) if line and os.path.isabs(line): if os.name == "nt": @@ -2190,12 +2233,11 @@ def find_python(finder, line=None): if not result: result = finder.which(line) if not result and not line.startswith("python"): - line = "python{0}".format(line) + line = f"python{line}" result = find_python(finder, line) - if not result: - result = next(iter(finder.find_all_python_versions()), None) + if result: - if not isinstance(result, six.string_types): + if not isinstance(result, str): return result.path.as_posix() return result return @@ -2212,11 +2254,11 @@ def is_python_command(line): :rtype: bool """ - if not isinstance(line, six.string_types): - raise TypeError("Not a valid command to check: {0!r}".format(line)) + if not isinstance(line, str): + raise TypeError(f"Not a valid command to check: {line!r}") from pipenv.vendor.pythonfinder.utils import PYTHON_IMPLEMENTATIONS - is_version = re.match(r'[\d\.]+', line) + is_version = re.match(r'\d+(\.\d+)*', line) if (line.startswith("python") or is_version or any(line.startswith(v) for v in PYTHON_IMPLEMENTATIONS)): return True @@ -2226,35 +2268,6 @@ def is_python_command(line): return False -# def make_marker_from_specifier(spec): -# # type: (str) -> Optional[Marker] -# """Given a python version specifier, create a marker - -# :param spec: A specifier -# :type spec: str -# :return: A new marker -# :rtype: Optional[:class:`packaging.marker.Marker`] -# """ -# from .vendor.packaging.markers import Marker -# from .vendor.packaging.specifiers import SpecifierSet, Specifier -# from .vendor.requirementslib.models.markers import cleanup_pyspecs, format_pyversion -# if not any(spec.startswith(k) for k in Specifier._operators.keys()): -# if spec.strip().lower() in ["any", "<any>", "*"]: -# return None -# spec = "=={0}".format(spec) -# elif spec.startswith("==") and spec.count("=") > 3: -# spec = "=={0}".format(spec.lstrip("=")) -# if not spec: -# return None -# marker_segments = [] -# print(spec) -# for marker_segment in cleanup_pyspecs(spec): -# print(marker_segment) -# marker_segments.append(format_pyversion(marker_segment)) -# marker_str = " and ".join(marker_segments) -# return Marker(marker_str) - - @contextlib.contextmanager def interrupt_handled_subprocess( cmd, verbose=False, return_object=True, write_to_stdout=False, combine_stderr=True, @@ -2290,3 +2303,37 @@ def interrupt_handled_subprocess( os.kill(obj.pid, signal.SIGINT) obj.wait() raise + + +def subprocess_run( + args, *, block=True, text=True, capture_output=True, + encoding="utf-8", env=None, **other_kwargs +): + """A backward compatible version of subprocess.run(). + + It outputs text with default encoding, and store all outputs in the returned object instead of + printing onto stdout. + """ + _env = os.environ.copy() + _env["PYTHONIOENCODING"] = encoding + if env: + _env.update(env) + other_kwargs["env"] = _env + if capture_output: + other_kwargs['stdout'] = subprocess.PIPE + other_kwargs['stderr'] = subprocess.PIPE + if block: + return subprocess.run( + args, universal_newlines=text, + encoding=encoding, **other_kwargs + ) + else: + return subprocess.Popen( + args, universal_newlines=text, + encoding=encoding, **other_kwargs + ) + + +def cmd_list_to_shell(args): + """Convert a list of arguments to a quoted shell command.""" + return " ".join(shlex.quote(str(token)) for token in args) diff --git a/pipenv/vendor/HISTORY.rst b/pipenv/vendor/HISTORY.rst new file mode 100644 index 00000000..fcdb8aad --- /dev/null +++ b/pipenv/vendor/HISTORY.rst @@ -0,0 +1,237 @@ +Release History +=============== + +2.0.0 / 2021-05-25 +------------------ + +* Drop Python 2.7 and 3.5 support +* Make ``termcolor`` an external dependency +* Run CI tests under Ubuntu 20.04 +* Update dependencies + + +1.5.0 / 2021-03-21 +------------------ + +* Update cli-spinners to ``v2.6.0`` +* Update dependencies + + +1.4.1 / 2021-02-28 +------------------ + +* Fix timer round-up behavior (#118) + + +1.4.0 / 2021-02-21 +------------------ + +* Add spinner timer (#99, #108) +* fix(#107): use ``poetry_core`` as build backend +* fix(#34): allow ``write()`` to print non-string objects +* Update dependencies + + +1.3.0 / 2021-01-17 +------------------ + +* Optimization: wait of stop event instead of sleep +* Update dependencies + + +1.2.0 / 2020-10-19 +------------------ + +* Update cli-spinners to ``v2.5.0`` +* Add support for Python 3.9 + + +1.1.0 / 2020-10-04 +------------------ + +* Add ``hidden()`` context manager #68 +* fix(#70): ``hidden()`` exceptions handling +* Replace coveralls.io with codecov.io +* Update dependencies + + +1.0.0 / 2020-08-02 +------------------ + +* "Stabilize" yaspin; ``1.*`` branch will contain stable release with Python 2 +support. Drop Python 2 and switch to Python 3 completely is planned for versions +``2.*``. + + +0.18.0 / 2020-07-21 +------------------- + +* Update cli-spinners to ``v2.4.0`` +* Update dependencies +* fix(#59): remove ``tests/`` and ``examples/`` from wheels distribution + + +0.17.0 / 2020-05-08 +------------------- + +* Migrate to ``poetry`` for dependencies management, building and publishing project +* Add tests for Python 3.8 +* Deprecate support for Python 3.4 +* Run tests under Ubuntu 18.04 +* Update dev dependencies to the most recent ones (compatible with Python 2.7) +* Remove Tox from the project (use CI for tests under different versions of Python) + + +0.16.0 / 2020-01-11 +------------------- + +* Allow use inside zip bundled package +* Code improvements + + +0.15.0 / 2019-08-09 +------------------- + +* Update cli-spinners to v2.2.0 + + +0.14.3 / 2019-05-12 +------------------- + +* fix(#29): race condition between spinner thread and ``write()`` + + +0.14.2 / 2019-04-27 +------------------- + +* fix: remove extra ``\b`` written to stdout. Fixes ``write()`` in rxvt terminal + + +0.14.1 / 2019-01-28 +------------------- + +* fix(#26): traceback on PYTHONOPTIMIZE=2 + + +0.14.0 / 2018-09-05 +------------------- + +* Support for handling POSIX signals +* New function in public API: ``kbi_safe_yaspin`` + + +0.13.0 / 2018-08-14 +------------------- + +* API improvements: ``spinner``, ``color``, ``on_color``, ``attrs`` and ``side`` argument values are handled via ``__getattr__`` +* New ``yaspin`` arguments: ``on_color``, ``attrs`` +* ``right=False`` argument replaced with ``side="left"`` +* ``Yaspin.right`` replaced with ``Yaspin.side`` +* ``reverse`` argument replaced with ``reversal`` +* ``Yaspin.reverse`` replaced with ``Yaspin.reversal`` +* Remove default text stripping in ``Yaspin._freeze`` + + +0.12.0 / 2018-07-16 +------------------- + +* Add support for Python 3.7 +* Drop support for Python 2.6 and 3.3 + +* dev: Migrate to Pipfile +* dev: Speedup local unittests with pytest-xdist + + +0.11.1 / 2018-07-10 +------------------- + +* fix(#16): remove default text stripping in ``Yaspin.write`` to allow printing of the hierarchical text + + +0.11.0 / 2018-06-23 +------------------- + +* Update cli-spinners to v1.3.1 + + +0.10.0 / 2018-03-23 +------------------- + +* New ``hide`` and ``show`` methods to toggle the display of the spinner + + +0.9.0 / 2018-02-26 +------------------ + +* New ``write`` method for writing text into terminal without breaking the spinner + + +0.8.0 / 2017-12-31 +------------------ + +* Speedup reading spinners collection with simplejson + + +0.7.1 / 2017-12-02 +------------------ + +* fix(#7): handling bytes sequences in ``Spinner.frames`` + + +0.7.0 / 2017-11-28 +------------------ + +* Reverse spinner support + + +0.6.0 / 2017-11-26 +------------------ + +* Right spinner support + + +0.5.0 / 2017-11-24 +------------------ + +* Colors support + + +0.4.2 / 2017-11-17 +------------------ + +* RST vs PyPI episode 2 + + +0.4.1 / 2017-11-17 +------------------ + +* RST vs PyPI episode 1 + + +0.4.0 / 2017-11-17 +------------------ + +* Support for success and failure finalizers + + +0.3.0 / 2017-11-14 +------------------ + +* Support for changing spinner properties on the fly + + +0.2.0 / 2017-11-10 +------------------ + +* Support all spinners from `cli-spinners`_ +* API changes: + - ``yaspin.spinner`` -> ``yaspin.yaspin`` + + +0.1.0 / 2017-10-31 +------------------ + +* First version + + +.. _cli-spinners: https://github.com/sindresorhus/cli-spinners diff --git a/pipenv/vendor/delegator.py.LICENSE b/pipenv/vendor/LICENSE similarity index 95% rename from pipenv/vendor/delegator.py.LICENSE rename to pipenv/vendor/LICENSE index 00bf847d..c4902dbf 100644 --- a/pipenv/vendor/delegator.py.LICENSE +++ b/pipenv/vendor/LICENSE @@ -1,6 +1,6 @@ -The MIT License (MIT) +MIT License -Copyright 2018 Kenneth Reitz +Copyright (c) 2021 Pavlo Dmytrenko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pipenv/vendor/Misc/NEWS.d/next/Library/2021-05-14-16-06-02.bpo-44095.v_pLwY.rst b/pipenv/vendor/Misc/NEWS.d/next/Library/2021-05-14-16-06-02.bpo-44095.v_pLwY.rst new file mode 100644 index 00000000..ee03e933 --- /dev/null +++ b/pipenv/vendor/Misc/NEWS.d/next/Library/2021-05-14-16-06-02.bpo-44095.v_pLwY.rst @@ -0,0 +1,2 @@ +:class:`zipfile.Path` now supports :attr:`zipfile.Path.stem`, +:attr:`zipfile.Path.suffixes`, and :attr:`zipfile.Path.suffix` attributes. diff --git a/pipenv/vendor/README.rst b/pipenv/vendor/README.rst new file mode 100644 index 00000000..4bd0c520 --- /dev/null +++ b/pipenv/vendor/README.rst @@ -0,0 +1,451 @@ +|Logo| + +===================================================================== +``yaspin``: **Y**\ et **A**\ nother Terminal **Spin**\ ner for Python +===================================================================== + +|Build Status| |Coverage| |Codacy| |pyup| |black-fmt| + +|pypi| |Versions| |Wheel| |Examples| + +|DownloadsTot| |DownloadsW| + + +``Yaspin`` provides a full-featured terminal spinner to show the progress during long-hanging operations. + +.. image:: https://raw.githubusercontent.com/pavdmyt/yaspin/master/gifs/demo.gif + +It is easy to integrate into existing codebase by using it as a `context manager`_ +or as a function `decorator`_: + +.. code:: python + + import time + from yaspin import yaspin + + # Context manager: + with yaspin(): + time.sleep(3) # time consuming code + + # Function decorator: + @yaspin(text="Loading...") + def some_operations(): + time.sleep(3) # time consuming code + + some_operations() + + +**Yaspin** also provides an intuitive and powerful API. For example, you can easily summon a shark: + +.. code:: python + + import time + from yaspin import yaspin + + with yaspin().white.bold.shark.on_blue as sp: + sp.text = "White bold shark in a blue sea" + time.sleep(5) + +.. image:: https://raw.githubusercontent.com/pavdmyt/yaspin/master/gifs/shark.gif + + +Features +-------- + +- Runs at all major **CPython** versions (*3.6*, *3.7*, *3.8*, *3.9*), **PyPy** +- Supports all (70+) spinners from `cli-spinners`_ +- Supports all *colors*, *highlights*, *attributes* and their mixes from `termcolor`_ library +- Easy to combine with other command-line libraries, e.g. `prompt-toolkit`_ +- Flexible API, easy to integrate with existing code +- User-friendly API for handling POSIX `signals`_ +- Safe **pipes** and **redirects**: + +.. code-block:: bash + + $ python script_that_uses_yaspin.py > script.log + $ python script_that_uses_yaspin.py | grep ERROR + + +Installation +------------ + +From `PyPI`_ using ``pip`` package manager: + +.. code-block:: bash + + pip install --upgrade yaspin + + +Or install the latest sources from GitHub: + +.. code-block:: bash + + pip install https://github.com/pavdmyt/yaspin/archive/master.zip + + +Usage +----- + +Basic Example +///////////// + +.. image:: https://raw.githubusercontent.com/pavdmyt/yaspin/master/gifs/basic_example.gif + +.. code:: python + + import time + from random import randint + from yaspin import yaspin + + with yaspin(text="Loading", color="yellow") as spinner: + time.sleep(2) # time consuming code + + success = randint(0, 1) + if success: + spinner.ok("✅ ") + else: + spinner.fail("💥 ") + + +It is also possible to control spinner manually: + +.. code:: python + + import time + from yaspin import yaspin + + spinner = yaspin() + spinner.start() + + time.sleep(3) # time consuming tasks + + spinner.stop() + + +Run any spinner from `cli-spinners`_ +//////////////////////////////////// + +.. image:: https://raw.githubusercontent.com/pavdmyt/yaspin/master/gifs/cli_spinners.gif + +.. code:: python + + import time + from yaspin import yaspin + from yaspin.spinners import Spinners + + with yaspin(Spinners.earth, text="Earth") as sp: + time.sleep(2) # time consuming code + + # change spinner + sp.spinner = Spinners.moon + sp.text = "Moon" + + time.sleep(2) # time consuming code + + +Any Colour You Like `🌈`_ +///////////////////////// + +.. image:: https://raw.githubusercontent.com/pavdmyt/yaspin/master/gifs/basic_colors.gif + +.. code:: python + + import time + from yaspin import yaspin + + with yaspin(text="Colors!") as sp: + # Support all basic termcolor text colors + colors = ("red", "green", "yellow", "blue", "magenta", "cyan", "white") + + for color in colors: + sp.color, sp.text = color, color + time.sleep(1) + + +Advanced colors usage +///////////////////// + +.. image:: https://raw.githubusercontent.com/pavdmyt/yaspin/master/gifs/advanced_colors.gif + +.. code:: python + + import time + from yaspin import yaspin + from yaspin.spinners import Spinners + + text = "Bold blink magenta spinner on cyan color" + with yaspin().bold.blink.magenta.bouncingBall.on_cyan as sp: + sp.text = text + time.sleep(3) + + # The same result can be achieved by passing arguments directly + with yaspin( + Spinners.bouncingBall, + color="magenta", + on_color="on_cyan", + attrs=["bold", "blink"], + ) as sp: + sp.text = text + time.sleep(3) + + +Run any spinner you want +//////////////////////// + +.. image:: https://raw.githubusercontent.com/pavdmyt/yaspin/master/gifs/custom_spinners.gif + +.. code:: python + + import time + from yaspin import yaspin, Spinner + + # Compose new spinners with custom frame sequence and interval value + sp = Spinner(["😸", "😹", "😺", "😻", "😼", "😽", "😾", "😿", "🙀"], 200) + + with yaspin(sp, text="Cat!"): + time.sleep(3) # cat consuming code :) + + +Change spinner properties on the fly +//////////////////////////////////// + +.. image:: https://raw.githubusercontent.com/pavdmyt/yaspin/master/gifs/sp_properties.gif + +.. code:: python + + import time + from yaspin import yaspin + from yaspin.spinners import Spinners + + with yaspin(Spinners.noise, text="Noise spinner") as sp: + time.sleep(2) + + sp.spinner = Spinners.arc # spinner type + sp.text = "Arc spinner" # text along with spinner + sp.color = "green" # spinner color + sp.side = "right" # put spinner to the right + sp.reversal = True # reverse spin direction + + time.sleep(2) + + +Spinner with timer +////////////////// + +.. code:: python + + import time + from yaspin import yaspin + + with yaspin(text="elapsed time", timer=True) as sp: + time.sleep(3.1415) + sp.ok() + + +Writing messages +//////////////// + +.. image:: https://raw.githubusercontent.com/pavdmyt/yaspin/master/gifs/write_text.gif + +You should not write any message in the terminal using ``print`` while spinner is open. +To write messages in the terminal without any collision with ``yaspin`` spinner, a ``.write()`` method is provided: + +.. code:: python + + import time + from yaspin import yaspin + + with yaspin(text="Downloading images", color="cyan") as sp: + # task 1 + time.sleep(1) + sp.write("> image 1 download complete") + + # task 2 + time.sleep(2) + sp.write("> image 2 download complete") + + # finalize + sp.ok("✔") + + +Integration with other libraries +//////////////////////////////// + +.. image:: https://raw.githubusercontent.com/pavdmyt/yaspin/master/gifs/hide_show.gif + +Utilizing ``hidden`` context manager it is possible to toggle the display of +the spinner in order to call custom methods that write to the terminal. This is +helpful for allowing easy usage in other frameworks like `prompt-toolkit`_. +Using the powerful ``print_formatted_text`` function allows you even to apply +HTML formats and CSS styles to the output: + +.. code:: python + + import sys + import time + + from yaspin import yaspin + from prompt_toolkit import HTML, print_formatted_text + from prompt_toolkit.styles import Style + + # override print with feature-rich ``print_formatted_text`` from prompt_toolkit + print = print_formatted_text + + # build a basic prompt_toolkit style for styling the HTML wrapped text + style = Style.from_dict({ + 'msg': '#4caf50 bold', + 'sub-msg': '#616161 italic' + }) + + + with yaspin(text='Downloading images') as sp: + # task 1 + time.sleep(1) + with sp.hidden(): + print(HTML( + u'<b>></b> <msg>image 1</msg> <sub-msg>download complete</sub-msg>' + ), style=style) + + # task 2 + time.sleep(2) + with sp.hidden(): + print(HTML( + u'<b>></b> <msg>image 2</msg> <sub-msg>download complete</sub-msg>' + ), style=style) + + # finalize + sp.ok() + + +Handling POSIX `signals`_ +///////////////////////// + +Handling keyboard interrupts (pressing Control-C): + +.. code:: python + + import time + + from yaspin import kbi_safe_yaspin + + + with kbi_safe_yaspin(text="Press Control+C to send SIGINT (Keyboard Interrupt) signal"): + time.sleep(5) # time consuming code + + +Handling other types of signals: + +.. code:: python + + import os + import time + from signal import SIGTERM, SIGUSR1 + + from yaspin import yaspin + from yaspin.signal_handlers import default_handler, fancy_handler + + + sigmap = {SIGUSR1: default_handler, SIGTERM: fancy_handler} + with yaspin(sigmap=sigmap, text="Handling SIGUSR1 and SIGTERM signals") as sp: + sp.write("Send signals using `kill` command") + sp.write("E.g. $ kill -USR1 {0}".format(os.getpid())) + time.sleep(20) # time consuming code + + +More `examples`_. + + +Development +----------- + +Clone the repository: + +.. code-block:: bash + + git clone https://github.com/pavdmyt/yaspin.git + + +Install dev dependencies: + +.. code-block:: bash + + poetry install + + # if you don't have poetry installed: + pip install -r requirements.txt + + +Lint code: + +.. code-block:: bash + + make lint + + +Format code: + +.. code-block:: bash + + make black-fmt + + +Run tests: + +.. code-block:: bash + + make test + + +Contributing +------------ + +1. Fork it! +2. Create your feature branch: ``git checkout -b my-new-feature`` +3. Commit your changes: ``git commit -m 'Add some feature'`` +4. Push to the branch: ``git push origin my-new-feature`` +5. Submit a pull request +6. Make sure tests are passing + + +License +------- + +* MIT - Pavlo Dmytrenko; https://twitter.com/pavdmyt +* Contains data from `cli-spinners`_: MIT License, Copyright (c) Sindre Sorhus sindresorhus@gmail.com (sindresorhus.com) + + +.. |Logo| image:: https://raw.githubusercontent.com/pavdmyt/yaspin/master/static/logo_80.png + :alt: yaspin Logo +.. |Build Status| image:: https://travis-ci.org/pavdmyt/yaspin.svg?branch=master + :target: https://travis-ci.org/pavdmyt/yaspin +.. |Coverage| image:: https://codecov.io/gh/pavdmyt/yaspin/branch/master/graph/badge.svg + :target: https://codecov.io/gh/pavdmyt/yaspin +.. |Codacy| image:: https://api.codacy.com/project/badge/Grade/797c7772d0d3467c88a5e2e9dc79ec98 + :target: https://www.codacy.com/app/pavdmyt/yaspin?utm_source=github.com&utm_medium=referral&utm_content=pavdmyt/yaspin&utm_campaign=Badge_Grade +.. |pypi| image:: https://img.shields.io/pypi/v/yaspin.svg + :target: https://pypi.org/project/yaspin/ +.. |Versions| image:: https://img.shields.io/pypi/pyversions/yaspin.svg + :target: https://pypi.org/project/yaspin/ +.. |Wheel| image:: https://img.shields.io/pypi/wheel/yaspin.svg + :target: https://pypi.org/project/yaspin/ +.. |Examples| image:: https://img.shields.io/badge/learn%20by-examples-0077b3.svg + :target: https://github.com/pavdmyt/yaspin/tree/master/examples +.. |pyup| image:: https://pyup.io/repos/github/pavdmyt/yaspin/shield.svg + :target: https://pyup.io/repos/github/pavdmyt/yaspin/ +.. |black-fmt| image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/ambv/black +.. |DownloadsTot| image:: https://pepy.tech/badge/yaspin + :target: https://pepy.tech/project/yaspin +.. |DownloadsW| image:: https://pepy.tech/badge/yaspin/week + :target: https://pepy.tech/project/yaspin + + +.. _context manager: https://docs.python.org/3/reference/datamodel.html#context-managers +.. _decorator: https://www.thecodeship.com/patterns/guide-to-python-function-decorators/ +.. _cli-spinners: https://github.com/sindresorhus/cli-spinners +.. _termcolor: https://pypi.org/project/termcolor/ +.. _PyPI: https://pypi.org/ +.. _🌈: https://en.wikipedia.org/wiki/Any_Colour_You_Like +.. _examples: https://github.com/pavdmyt/yaspin/tree/master/examples +.. _prompt-toolkit: https://github.com/jonathanslenders/python-prompt-toolkit/ +.. _signals: https://www.computerhope.com/unix/signals.htm diff --git a/pipenv/vendor/appdirs.py b/pipenv/vendor/appdirs.py index ae67001a..2acd1deb 100644 --- a/pipenv/vendor/appdirs.py +++ b/pipenv/vendor/appdirs.py @@ -13,8 +13,8 @@ See <http://github.com/ActiveState/appdirs> for details and usage. # - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html # - XDG spec for Un*x: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html -__version_info__ = (1, 4, 3) -__version__ = '.'.join(map(str, __version_info__)) +__version__ = "1.4.4" +__version_info__ = tuple(int(segment) for segment in __version__.split(".")) import sys diff --git a/pipenv/vendor/attr/__init__.py b/pipenv/vendor/attr/__init__.py index 0ebe5197..b1ce7fe2 100644 --- a/pipenv/vendor/attr/__init__.py +++ b/pipenv/vendor/attr/__init__.py @@ -1,10 +1,13 @@ from __future__ import absolute_import, division, print_function +import sys + from functools import partial -from . import converters, exceptions, filters, validators +from . import converters, exceptions, filters, setters, validators +from ._cmp import cmp_using from ._config import get_run_validators, set_run_validators -from ._funcs import asdict, assoc, astuple, evolve, has +from ._funcs import asdict, assoc, astuple, evolve, has, resolve_types from ._make import ( NOTHING, Attribute, @@ -16,9 +19,11 @@ from ._make import ( make_class, validate, ) +from ._version_info import VersionInfo -__version__ = "19.1.0" +__version__ = "21.2.0" +__version_info__ = VersionInfo._from_version_string(__version__) __title__ = "attrs" __description__ = "Classes Without Boilerplate" @@ -48,6 +53,7 @@ __all__ = [ "attrib", "attributes", "attrs", + "cmp_using", "converters", "evolve", "exceptions", @@ -58,8 +64,15 @@ __all__ = [ "has", "ib", "make_class", + "resolve_types", "s", "set_run_validators", + "setters", "validate", "validators", ] + +if sys.version_info[:2] >= (3, 6): + from ._next_gen import define, field, frozen, mutable + + __all__.extend((define, field, frozen, mutable)) diff --git a/pipenv/vendor/attr/__init__.pyi b/pipenv/vendor/attr/__init__.pyi index fcb93b18..3503b073 100644 --- a/pipenv/vendor/attr/__init__.pyi +++ b/pipenv/vendor/attr/__init__.pyi @@ -1,12 +1,14 @@ +import sys + from typing import ( Any, Callable, Dict, Generic, List, + Mapping, Optional, Sequence, - Mapping, Tuple, Type, TypeVar, @@ -15,58 +17,114 @@ from typing import ( ) # `import X as X` is required to make these public +from . import converters as converters from . import exceptions as exceptions from . import filters as filters -from . import converters as converters +from . import setters as setters from . import validators as validators +from ._version_info import VersionInfo + + +__version__: str +__version_info__: VersionInfo +__title__: str +__description__: str +__url__: str +__uri__: str +__author__: str +__email__: str +__license__: str +__copyright__: str _T = TypeVar("_T") _C = TypeVar("_C", bound=type) +_EqOrderType = Union[bool, Callable[[Any], Any]] _ValidatorType = Callable[[Any, Attribute[_T], _T], Any] -_ConverterType = Callable[[Any], _T] +_ConverterType = Callable[[Any], Any] _FilterType = Callable[[Attribute[_T], _T], bool] -# FIXME: in reality, if multiple validators are passed they must be in a list or tuple, -# but those are invariant and so would prevent subtypes of _ValidatorType from working -# when passed in a list or tuple. +_ReprType = Callable[[Any], str] +_ReprArgType = Union[bool, _ReprType] +_OnSetAttrType = Callable[[Any, Attribute[Any], Any], Any] +_OnSetAttrArgType = Union[ + _OnSetAttrType, List[_OnSetAttrType], setters._NoOpType +] +_FieldTransformer = Callable[[type, List[Attribute[Any]]], List[Attribute[Any]]] +# FIXME: in reality, if multiple validators are passed they must be in a list +# or tuple, but those are invariant and so would prevent subtypes of +# _ValidatorType from working when passed in a list or tuple. _ValidatorArgType = Union[_ValidatorType[_T], Sequence[_ValidatorType[_T]]] # _make -- NOTHING: object -# NOTE: Factory lies about its return type to make this possible: `x: List[int] = Factory(list)` +# NOTE: Factory lies about its return type to make this possible: +# `x: List[int] # = Factory(list)` # Work around mypy issue #4554 in the common case by using an overload. -@overload -def Factory(factory: Callable[[], _T]) -> _T: ... -@overload -def Factory( - factory: Union[Callable[[Any], _T], Callable[[], _T]], - takes_self: bool = ..., -) -> _T: ... +if sys.version_info >= (3, 8): + from typing import Literal + + @overload + def Factory(factory: Callable[[], _T]) -> _T: ... + @overload + def Factory( + factory: Callable[[Any], _T], + takes_self: Literal[True], + ) -> _T: ... + @overload + def Factory( + factory: Callable[[], _T], + takes_self: Literal[False], + ) -> _T: ... +else: + @overload + def Factory(factory: Callable[[], _T]) -> _T: ... + @overload + def Factory( + factory: Union[Callable[[Any], _T], Callable[[], _T]], + takes_self: bool = ..., + ) -> _T: ... + +# Static type inference support via __dataclass_transform__ implemented as per: +# https://github.com/microsoft/pyright/blob/1.1.135/specs/dataclass_transforms.md +# This annotation must be applied to all overloads of "define" and "attrs" +# +# NOTE: This is a typing construct and does not exist at runtime. Extensions +# wrapping attrs decorators should declare a separate __dataclass_transform__ +# signature in the extension module using the specification linked above to +# provide pyright support. +def __dataclass_transform__( + *, + eq_default: bool = True, + order_default: bool = False, + kw_only_default: bool = False, + field_descriptors: Tuple[Union[type, Callable[..., Any]], ...] = (()), +) -> Callable[[_T], _T]: ... class Attribute(Generic[_T]): name: str default: Optional[_T] validator: Optional[_ValidatorType[_T]] - repr: bool - cmp: bool + repr: _ReprArgType + cmp: _EqOrderType + eq: _EqOrderType + order: _EqOrderType hash: Optional[bool] init: bool - converter: Optional[_ConverterType[_T]] + converter: Optional[_ConverterType] metadata: Dict[Any, Any] type: Optional[Type[_T]] kw_only: bool - def __lt__(self, x: Attribute[_T]) -> bool: ... - def __le__(self, x: Attribute[_T]) -> bool: ... - def __gt__(self, x: Attribute[_T]) -> bool: ... - def __ge__(self, x: Attribute[_T]) -> bool: ... + on_setattr: _OnSetAttrType + + def evolve(self, **changes: Any) -> "Attribute[Any]": ... # NOTE: We had several choices for the annotation to use for type arg: # 1) Type[_T] # - Pros: Handles simple cases correctly -# - Cons: Might produce less informative errors in the case of conflicting TypeVars -# e.g. `attr.ib(default='bad', type=int)` +# - Cons: Might produce less informative errors in the case of conflicting +# TypeVars e.g. `attr.ib(default='bad', type=int)` # 2) Callable[..., _T] # - Pros: Better error messages than #1 for conflicting TypeVars # - Cons: Terrible error messages for validator checks. @@ -84,38 +142,44 @@ class Attribute(Generic[_T]): # This makes this type of assignments possible: # x: int = attr(8) # -# This form catches explicit None or no default but with no other arguments returns Any. +# This form catches explicit None or no default but with no other arguments +# returns Any. @overload def attrib( default: None = ..., validator: None = ..., - repr: bool = ..., - cmp: bool = ..., + repr: _ReprArgType = ..., + cmp: Optional[_EqOrderType] = ..., hash: Optional[bool] = ..., init: bool = ..., - convert: None = ..., metadata: Optional[Mapping[Any, Any]] = ..., type: None = ..., converter: None = ..., factory: None = ..., kw_only: bool = ..., + eq: Optional[_EqOrderType] = ..., + order: Optional[_EqOrderType] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., ) -> Any: ... -# This form catches an explicit None or no default and infers the type from the other arguments. +# This form catches an explicit None or no default and infers the type from the +# other arguments. @overload def attrib( default: None = ..., validator: Optional[_ValidatorArgType[_T]] = ..., - repr: bool = ..., - cmp: bool = ..., + repr: _ReprArgType = ..., + cmp: Optional[_EqOrderType] = ..., hash: Optional[bool] = ..., init: bool = ..., - convert: Optional[_ConverterType[_T]] = ..., metadata: Optional[Mapping[Any, Any]] = ..., type: Optional[Type[_T]] = ..., - converter: Optional[_ConverterType[_T]] = ..., + converter: Optional[_ConverterType] = ..., factory: Optional[Callable[[], _T]] = ..., kw_only: bool = ..., + eq: Optional[_EqOrderType] = ..., + order: Optional[_EqOrderType] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., ) -> _T: ... # This form catches an explicit default argument. @@ -123,16 +187,18 @@ def attrib( def attrib( default: _T, validator: Optional[_ValidatorArgType[_T]] = ..., - repr: bool = ..., - cmp: bool = ..., + repr: _ReprArgType = ..., + cmp: Optional[_EqOrderType] = ..., hash: Optional[bool] = ..., init: bool = ..., - convert: Optional[_ConverterType[_T]] = ..., metadata: Optional[Mapping[Any, Any]] = ..., type: Optional[Type[_T]] = ..., - converter: Optional[_ConverterType[_T]] = ..., + converter: Optional[_ConverterType] = ..., factory: Optional[Callable[[], _T]] = ..., kw_only: bool = ..., + eq: Optional[_EqOrderType] = ..., + order: Optional[_EqOrderType] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., ) -> _T: ... # This form covers type=non-Type: e.g. forward references (str), Any @@ -140,24 +206,98 @@ def attrib( def attrib( default: Optional[_T] = ..., validator: Optional[_ValidatorArgType[_T]] = ..., - repr: bool = ..., - cmp: bool = ..., + repr: _ReprArgType = ..., + cmp: Optional[_EqOrderType] = ..., hash: Optional[bool] = ..., init: bool = ..., - convert: Optional[_ConverterType[_T]] = ..., metadata: Optional[Mapping[Any, Any]] = ..., type: object = ..., - converter: Optional[_ConverterType[_T]] = ..., + converter: Optional[_ConverterType] = ..., factory: Optional[Callable[[], _T]] = ..., kw_only: bool = ..., + eq: Optional[_EqOrderType] = ..., + order: Optional[_EqOrderType] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., ) -> Any: ... @overload +def field( + *, + default: None = ..., + validator: None = ..., + repr: _ReprArgType = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + converter: None = ..., + factory: None = ..., + kw_only: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., +) -> Any: ... + +# This form catches an explicit None or no default and infers the type from the +# other arguments. +@overload +def field( + *, + default: None = ..., + validator: Optional[_ValidatorArgType[_T]] = ..., + repr: _ReprArgType = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + converter: Optional[_ConverterType] = ..., + factory: Optional[Callable[[], _T]] = ..., + kw_only: bool = ..., + eq: Optional[_EqOrderType] = ..., + order: Optional[_EqOrderType] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., +) -> _T: ... + +# This form catches an explicit default argument. +@overload +def field( + *, + default: _T, + validator: Optional[_ValidatorArgType[_T]] = ..., + repr: _ReprArgType = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + converter: Optional[_ConverterType] = ..., + factory: Optional[Callable[[], _T]] = ..., + kw_only: bool = ..., + eq: Optional[_EqOrderType] = ..., + order: Optional[_EqOrderType] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., +) -> _T: ... + +# This form covers type=non-Type: e.g. forward references (str), Any +@overload +def field( + *, + default: Optional[_T] = ..., + validator: Optional[_ValidatorArgType[_T]] = ..., + repr: _ReprArgType = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + converter: Optional[_ConverterType] = ..., + factory: Optional[Callable[[], _T]] = ..., + kw_only: bool = ..., + eq: Optional[_EqOrderType] = ..., + order: Optional[_EqOrderType] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., +) -> Any: ... +@overload +@__dataclass_transform__(order_default=True, field_descriptors=(attrib, field)) def attrs( maybe_cls: _C, these: Optional[Dict[str, Any]] = ..., repr_ns: Optional[str] = ..., repr: bool = ..., - cmp: bool = ..., + cmp: Optional[_EqOrderType] = ..., hash: Optional[bool] = ..., init: bool = ..., slots: bool = ..., @@ -168,14 +308,22 @@ def attrs( kw_only: bool = ..., cache_hash: bool = ..., auto_exc: bool = ..., + eq: Optional[_EqOrderType] = ..., + order: Optional[_EqOrderType] = ..., + auto_detect: bool = ..., + collect_by_mro: bool = ..., + getstate_setstate: Optional[bool] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., + field_transformer: Optional[_FieldTransformer] = ..., ) -> _C: ... @overload +@__dataclass_transform__(order_default=True, field_descriptors=(attrib, field)) def attrs( maybe_cls: None = ..., these: Optional[Dict[str, Any]] = ..., repr_ns: Optional[str] = ..., repr: bool = ..., - cmp: bool = ..., + cmp: Optional[_EqOrderType] = ..., hash: Optional[bool] = ..., init: bool = ..., slots: bool = ..., @@ -186,7 +334,65 @@ def attrs( kw_only: bool = ..., cache_hash: bool = ..., auto_exc: bool = ..., + eq: Optional[_EqOrderType] = ..., + order: Optional[_EqOrderType] = ..., + auto_detect: bool = ..., + collect_by_mro: bool = ..., + getstate_setstate: Optional[bool] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., + field_transformer: Optional[_FieldTransformer] = ..., ) -> Callable[[_C], _C]: ... +@overload +@__dataclass_transform__(field_descriptors=(attrib, field)) +def define( + maybe_cls: _C, + *, + these: Optional[Dict[str, Any]] = ..., + repr: bool = ..., + hash: Optional[bool] = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., + auto_detect: bool = ..., + getstate_setstate: Optional[bool] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., + field_transformer: Optional[_FieldTransformer] = ..., +) -> _C: ... +@overload +@__dataclass_transform__(field_descriptors=(attrib, field)) +def define( + maybe_cls: None = ..., + *, + these: Optional[Dict[str, Any]] = ..., + repr: bool = ..., + hash: Optional[bool] = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., + auto_detect: bool = ..., + getstate_setstate: Optional[bool] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., + field_transformer: Optional[_FieldTransformer] = ..., +) -> Callable[[_C], _C]: ... + +mutable = define +frozen = define # they differ only in their defaults # TODO: add support for returning NamedTuple from the mypy plugin class _Fields(Tuple[Attribute[Any], ...]): @@ -195,16 +401,23 @@ class _Fields(Tuple[Attribute[Any], ...]): def fields(cls: type) -> _Fields: ... def fields_dict(cls: type) -> Dict[str, Attribute[Any]]: ... def validate(inst: Any) -> None: ... +def resolve_types( + cls: _C, + globalns: Optional[Dict[str, Any]] = ..., + localns: Optional[Dict[str, Any]] = ..., + attribs: Optional[List[Attribute[Any]]] = ..., +) -> _C: ... # TODO: add support for returning a proper attrs class from the mypy plugin -# we use Any instead of _CountingAttr so that e.g. `make_class('Foo', [attr.ib()])` is valid +# we use Any instead of _CountingAttr so that e.g. `make_class('Foo', +# [attr.ib()])` is valid def make_class( name: str, attrs: Union[List[str], Tuple[str, ...], Dict[str, Any]], bases: Tuple[type, ...] = ..., repr_ns: Optional[str] = ..., repr: bool = ..., - cmp: bool = ..., + cmp: Optional[_EqOrderType] = ..., hash: Optional[bool] = ..., init: bool = ..., slots: bool = ..., @@ -215,12 +428,18 @@ def make_class( kw_only: bool = ..., cache_hash: bool = ..., auto_exc: bool = ..., + eq: Optional[_EqOrderType] = ..., + order: Optional[_EqOrderType] = ..., + collect_by_mro: bool = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., + field_transformer: Optional[_FieldTransformer] = ..., ) -> type: ... # _funcs -- # TODO: add support for returning TypedDict from the mypy plugin -# FIXME: asdict/astuple do not honor their factory args. waiting on one of these: +# FIXME: asdict/astuple do not honor their factory args. Waiting on one of +# these: # https://github.com/python/mypy/issues/4236 # https://github.com/python/typing/issues/253 def asdict( @@ -229,6 +448,7 @@ def asdict( filter: Optional[_FilterType[Any]] = ..., dict_factory: Type[Mapping[Any, Any]] = ..., retain_collection_types: bool = ..., + value_serializer: Optional[Callable[[type, Attribute[Any], Any], Any]] = ..., ) -> Dict[str, Any]: ... # TODO: add support for returning NamedTuple from the mypy plugin diff --git a/pipenv/vendor/attr/_cmp.py b/pipenv/vendor/attr/_cmp.py new file mode 100644 index 00000000..b747b603 --- /dev/null +++ b/pipenv/vendor/attr/_cmp.py @@ -0,0 +1,152 @@ +from __future__ import absolute_import, division, print_function + +import functools + +from ._compat import new_class +from ._make import _make_ne + + +_operation_names = {"eq": "==", "lt": "<", "le": "<=", "gt": ">", "ge": ">="} + + +def cmp_using( + eq=None, + lt=None, + le=None, + gt=None, + ge=None, + require_same_type=True, + class_name="Comparable", +): + """ + Create a class that can be passed into `attr.ib`'s ``eq``, ``order``, and + ``cmp`` arguments to customize field comparison. + + The resulting class will have a full set of ordering methods if + at least one of ``{lt, le, gt, ge}`` and ``eq`` are provided. + + :param Optional[callable] eq: `callable` used to evaluate equality + of two objects. + :param Optional[callable] lt: `callable` used to evaluate whether + one object is less than another object. + :param Optional[callable] le: `callable` used to evaluate whether + one object is less than or equal to another object. + :param Optional[callable] gt: `callable` used to evaluate whether + one object is greater than another object. + :param Optional[callable] ge: `callable` used to evaluate whether + one object is greater than or equal to another object. + + :param bool require_same_type: When `True`, equality and ordering methods + will return `NotImplemented` if objects are not of the same type. + + :param Optional[str] class_name: Name of class. Defaults to 'Comparable'. + + See `comparison` for more details. + + .. versionadded:: 21.1.0 + """ + + body = { + "__slots__": ["value"], + "__init__": _make_init(), + "_requirements": [], + "_is_comparable_to": _is_comparable_to, + } + + # Add operations. + num_order_functions = 0 + has_eq_function = False + + if eq is not None: + has_eq_function = True + body["__eq__"] = _make_operator("eq", eq) + body["__ne__"] = _make_ne() + + if lt is not None: + num_order_functions += 1 + body["__lt__"] = _make_operator("lt", lt) + + if le is not None: + num_order_functions += 1 + body["__le__"] = _make_operator("le", le) + + if gt is not None: + num_order_functions += 1 + body["__gt__"] = _make_operator("gt", gt) + + if ge is not None: + num_order_functions += 1 + body["__ge__"] = _make_operator("ge", ge) + + type_ = new_class(class_name, (object,), {}, lambda ns: ns.update(body)) + + # Add same type requirement. + if require_same_type: + type_._requirements.append(_check_same_type) + + # Add total ordering if at least one operation was defined. + if 0 < num_order_functions < 4: + if not has_eq_function: + # functools.total_ordering requires __eq__ to be defined, + # so raise early error here to keep a nice stack. + raise ValueError( + "eq must be define is order to complete ordering from " + "lt, le, gt, ge." + ) + type_ = functools.total_ordering(type_) + + return type_ + + +def _make_init(): + """ + Create __init__ method. + """ + + def __init__(self, value): + """ + Initialize object with *value*. + """ + self.value = value + + return __init__ + + +def _make_operator(name, func): + """ + Create operator method. + """ + + def method(self, other): + if not self._is_comparable_to(other): + return NotImplemented + + result = func(self.value, other.value) + if result is NotImplemented: + return NotImplemented + + return result + + method.__name__ = "__%s__" % (name,) + method.__doc__ = "Return a %s b. Computed by attrs." % ( + _operation_names[name], + ) + + return method + + +def _is_comparable_to(self, other): + """ + Check whether `other` is comparable to `self`. + """ + for func in self._requirements: + if not func(self, other): + return False + return True + + +def _check_same_type(self, other): + """ + Return True if *self* and *other* are of the same type, False otherwise. + """ + return other.value.__class__ is self.value.__class__ diff --git a/pipenv/vendor/attr/_cmp.pyi b/pipenv/vendor/attr/_cmp.pyi new file mode 100644 index 00000000..7093550f --- /dev/null +++ b/pipenv/vendor/attr/_cmp.pyi @@ -0,0 +1,14 @@ +from typing import Type + +from . import _CompareWithType + + +def cmp_using( + eq: Optional[_CompareWithType], + lt: Optional[_CompareWithType], + le: Optional[_CompareWithType], + gt: Optional[_CompareWithType], + ge: Optional[_CompareWithType], + require_same_type: bool, + class_name: str, +) -> Type: ... diff --git a/pipenv/vendor/attr/_compat.py b/pipenv/vendor/attr/_compat.py index 9a99dcd9..6939f338 100644 --- a/pipenv/vendor/attr/_compat.py +++ b/pipenv/vendor/attr/_compat.py @@ -19,14 +19,24 @@ else: if PY2: + from collections import Mapping, Sequence + from UserDict import IterableUserDict - from collections import Mapping, Sequence # noqa # We 'bundle' isclass instead of using inspect as importing inspect is # fairly expensive (order of 10-15 ms for a modern machine in 2016) def isclass(klass): return isinstance(klass, (type, types.ClassType)) + def new_class(name, bases, kwds, exec_body): + """ + A minimal stub of types.new_class that we need for make_class. + """ + ns = {} + exec_body(ns) + + return type(name, bases, ns) + # TYPE is used in exceptions, repr(int) is different on Python 2 and 3. TYPE = "type" @@ -90,7 +100,7 @@ if PY2: res.data.update(d) # We blocked update, so we have to do it like this. return res - def just_warn(*args, **kw): # pragma: nocover + def just_warn(*args, **kw): # pragma: no cover """ We only warn on Python 3 because we are not aware of any concrete consequences of not setting the cell on Python 2. @@ -106,7 +116,8 @@ else: # Python 3 and later. consequences of not setting the cell on Python 2. """ warnings.warn( - "Missing ctypes. Some features like bare super() or accessing " + "Running interpreter doesn't sufficiently support code object " + "introspection. Some features like bare super() or accessing " "__class__ will not work with slotted classes.", RuntimeWarning, stacklevel=2, @@ -120,40 +131,112 @@ else: # Python 3 and later. def iteritems(d): return d.items() + new_class = types.new_class + def metadata_proxy(d): return types.MappingProxyType(dict(d)) -def import_ctypes(): - """ - Moved into a function for testability. - """ - import ctypes - - return ctypes - - def make_set_closure_cell(): + """Return a function of two arguments (cell, value) which sets + the value stored in the closure cell `cell` to `value`. """ - Moved into a function for testability. - """ - if PYPY: # pragma: no cover + # pypy makes this easy. (It also supports the logic below, but + # why not do the easy/fast thing?) + if PYPY: def set_closure_cell(cell, value): cell.__setstate__((value,)) - else: - try: - ctypes = import_ctypes() + return set_closure_cell - set_closure_cell = ctypes.pythonapi.PyCell_Set - set_closure_cell.argtypes = (ctypes.py_object, ctypes.py_object) - set_closure_cell.restype = ctypes.c_int - except Exception: - # We try best effort to set the cell, but sometimes it's not - # possible. For example on Jython or on GAE. - set_closure_cell = just_warn - return set_closure_cell + # Otherwise gotta do it the hard way. + + # Create a function that will set its first cellvar to `value`. + def set_first_cellvar_to(value): + x = value + return + + # This function will be eliminated as dead code, but + # not before its reference to `x` forces `x` to be + # represented as a closure cell rather than a local. + def force_x_to_be_a_cell(): # pragma: no cover + return x + + try: + # Extract the code object and make sure our assumptions about + # the closure behavior are correct. + if PY2: + co = set_first_cellvar_to.func_code + else: + co = set_first_cellvar_to.__code__ + if co.co_cellvars != ("x",) or co.co_freevars != (): + raise AssertionError # pragma: no cover + + # Convert this code object to a code object that sets the + # function's first _freevar_ (not cellvar) to the argument. + if sys.version_info >= (3, 8): + # CPython 3.8+ has an incompatible CodeType signature + # (added a posonlyargcount argument) but also added + # CodeType.replace() to do this without counting parameters. + set_first_freevar_code = co.replace( + co_cellvars=co.co_freevars, co_freevars=co.co_cellvars + ) + else: + args = [co.co_argcount] + if not PY2: + args.append(co.co_kwonlyargcount) + args.extend( + [ + co.co_nlocals, + co.co_stacksize, + co.co_flags, + co.co_code, + co.co_consts, + co.co_names, + co.co_varnames, + co.co_filename, + co.co_name, + co.co_firstlineno, + co.co_lnotab, + # These two arguments are reversed: + co.co_cellvars, + co.co_freevars, + ] + ) + set_first_freevar_code = types.CodeType(*args) + + def set_closure_cell(cell, value): + # Create a function using the set_first_freevar_code, + # whose first closure cell is `cell`. Calling it will + # change the value of that cell. + setter = types.FunctionType( + set_first_freevar_code, {}, "setter", (), (cell,) + ) + # And call it to set the cell. + setter(value) + + # Make sure it works on this interpreter: + def make_func_with_cell(): + x = None + + def func(): + return x # pragma: no cover + + return func + + if PY2: + cell = make_func_with_cell().func_closure[0] + else: + cell = make_func_with_cell().__closure__[0] + set_closure_cell(cell, 100) + if cell.cell_contents != 100: + raise AssertionError # pragma: no cover + + except Exception: + return just_warn + else: + return set_closure_cell set_closure_cell = make_set_closure_cell() diff --git a/pipenv/vendor/attr/_funcs.py b/pipenv/vendor/attr/_funcs.py index b61d2394..fda508c5 100644 --- a/pipenv/vendor/attr/_funcs.py +++ b/pipenv/vendor/attr/_funcs.py @@ -13,6 +13,7 @@ def asdict( filter=None, dict_factory=dict, retain_collection_types=False, + value_serializer=None, ): """ Return the ``attrs`` attribute values of *inst* as a dict. @@ -24,7 +25,7 @@ def asdict( ``attrs``-decorated. :param callable filter: A callable whose return code determines whether an attribute or element is included (``True``) or dropped (``False``). Is - called with the :class:`attr.Attribute` as the first argument and the + called with the `attr.Attribute` as the first argument and the value as the second argument. :param callable dict_factory: A callable to produce dictionaries from. For example, to produce ordered dictionaries instead of normal Python @@ -32,6 +33,10 @@ def asdict( :param bool retain_collection_types: Do not convert to ``list`` when encountering an attribute whose type is ``tuple`` or ``set``. Only meaningful if ``recurse`` is ``True``. + :param Optional[callable] value_serializer: A hook that is called for every + attribute or dict key/value. It receives the current instance, field + and value and must return the (updated) value. The hook is run *after* + the optional *filter* has been applied. :rtype: return type of *dict_factory* @@ -40,6 +45,7 @@ def asdict( .. versionadded:: 16.0.0 *dict_factory* .. versionadded:: 16.1.0 *retain_collection_types* + .. versionadded:: 20.3.0 *value_serializer* """ attrs = fields(inst.__class__) rv = dict_factory() @@ -47,17 +53,30 @@ def asdict( v = getattr(inst, a.name) if filter is not None and not filter(a, v): continue + + if value_serializer is not None: + v = value_serializer(inst, a, v) + if recurse is True: if has(v.__class__): rv[a.name] = asdict( - v, True, filter, dict_factory, retain_collection_types + v, + True, + filter, + dict_factory, + retain_collection_types, + value_serializer, ) - elif isinstance(v, (tuple, list, set)): + elif isinstance(v, (tuple, list, set, frozenset)): cf = v.__class__ if retain_collection_types is True else list rv[a.name] = cf( [ _asdict_anything( - i, filter, dict_factory, retain_collection_types + i, + filter, + dict_factory, + retain_collection_types, + value_serializer, ) for i in v ] @@ -67,10 +86,18 @@ def asdict( rv[a.name] = df( ( _asdict_anything( - kk, filter, df, retain_collection_types + kk, + filter, + df, + retain_collection_types, + value_serializer, ), _asdict_anything( - vv, filter, df, retain_collection_types + vv, + filter, + df, + retain_collection_types, + value_serializer, ), ) for kk, vv in iteritems(v) @@ -82,19 +109,36 @@ def asdict( return rv -def _asdict_anything(val, filter, dict_factory, retain_collection_types): +def _asdict_anything( + val, + filter, + dict_factory, + retain_collection_types, + value_serializer, +): """ ``asdict`` only works on attrs instances, this works on anything. """ if getattr(val.__class__, "__attrs_attrs__", None) is not None: # Attrs class. - rv = asdict(val, True, filter, dict_factory, retain_collection_types) - elif isinstance(val, (tuple, list, set)): + rv = asdict( + val, + True, + filter, + dict_factory, + retain_collection_types, + value_serializer, + ) + elif isinstance(val, (tuple, list, set, frozenset)): cf = val.__class__ if retain_collection_types is True else list rv = cf( [ _asdict_anything( - i, filter, dict_factory, retain_collection_types + i, + filter, + dict_factory, + retain_collection_types, + value_serializer, ) for i in val ] @@ -103,13 +147,20 @@ def _asdict_anything(val, filter, dict_factory, retain_collection_types): df = dict_factory rv = df( ( - _asdict_anything(kk, filter, df, retain_collection_types), - _asdict_anything(vv, filter, df, retain_collection_types), + _asdict_anything( + kk, filter, df, retain_collection_types, value_serializer + ), + _asdict_anything( + vv, filter, df, retain_collection_types, value_serializer + ), ) for kk, vv in iteritems(val) ) else: rv = val + if value_serializer is not None: + rv = value_serializer(None, None, rv) + return rv @@ -130,7 +181,7 @@ def astuple( ``attrs``-decorated. :param callable filter: A callable whose return code determines whether an attribute or element is included (``True``) or dropped (``False``). Is - called with the :class:`attr.Attribute` as the first argument and the + called with the `attr.Attribute` as the first argument and the value as the second argument. :param callable tuple_factory: A callable to produce tuples from. For example, to produce lists instead of tuples. @@ -164,7 +215,7 @@ def astuple( retain_collection_types=retain, ) ) - elif isinstance(v, (tuple, list, set)): + elif isinstance(v, (tuple, list, set, frozenset)): cf = v.__class__ if retain is True else list rv.append( cf( @@ -209,6 +260,7 @@ def astuple( rv.append(v) else: rv.append(v) + return rv if tuple_factory is list else tuple_factory(rv) @@ -219,7 +271,7 @@ def has(cls): :param type cls: Class to introspect. :raise TypeError: If *cls* is not a class. - :rtype: :class:`bool` + :rtype: bool """ return getattr(cls, "__attrs_attrs__", None) is not None @@ -239,7 +291,7 @@ def assoc(inst, **changes): class. .. deprecated:: 17.1.0 - Use :func:`evolve` instead. + Use `evolve` instead. """ import warnings @@ -287,4 +339,57 @@ def evolve(inst, **changes): init_name = attr_name if attr_name[0] != "_" else attr_name[1:] if init_name not in changes: changes[init_name] = getattr(inst, attr_name) + return cls(**changes) + + +def resolve_types(cls, globalns=None, localns=None, attribs=None): + """ + Resolve any strings and forward annotations in type annotations. + + This is only required if you need concrete types in `Attribute`'s *type* + field. In other words, you don't need to resolve your types if you only + use them for static type checking. + + With no arguments, names will be looked up in the module in which the class + was created. If this is not what you want, e.g. if the name only exists + inside a method, you may pass *globalns* or *localns* to specify other + dictionaries in which to look up these names. See the docs of + `typing.get_type_hints` for more details. + + :param type cls: Class to resolve. + :param Optional[dict] globalns: Dictionary containing global variables. + :param Optional[dict] localns: Dictionary containing local variables. + :param Optional[list] attribs: List of attribs for the given class. + This is necessary when calling from inside a ``field_transformer`` + since *cls* is not an ``attrs`` class yet. + + :raise TypeError: If *cls* is not a class. + :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + class and you didn't pass any attribs. + :raise NameError: If types cannot be resolved because of missing variables. + + :returns: *cls* so you can use this function also as a class decorator. + Please note that you have to apply it **after** `attr.s`. That means + the decorator has to come in the line **before** `attr.s`. + + .. versionadded:: 20.1.0 + .. versionadded:: 21.1.0 *attribs* + + """ + try: + # Since calling get_type_hints is expensive we cache whether we've + # done it already. + cls.__attrs_types_resolved__ + except AttributeError: + import typing + + hints = typing.get_type_hints(cls, globalns=globalns, localns=localns) + for field in fields(cls) if attribs is None else attribs: + if field.name in hints: + # Since fields have been frozen we must work around it. + _obj_setattr(field, "type", hints[field.name]) + cls.__attrs_types_resolved__ = True + + # Return the class so you can use it as a decorator too. + return cls diff --git a/pipenv/vendor/attr/_make.py b/pipenv/vendor/attr/_make.py index 827175a4..a1912b12 100644 --- a/pipenv/vendor/attr/_make.py +++ b/pipenv/vendor/attr/_make.py @@ -1,20 +1,23 @@ from __future__ import absolute_import, division, print_function import copy -import hashlib +import inspect import linecache import sys import threading +import uuid import warnings from operator import itemgetter -from . import _config +from . import _config, setters from ._compat import ( PY2, + PYPY, isclass, iteritems, metadata_proxy, + new_class, ordered_dict, set_closure_cell, ) @@ -27,14 +30,23 @@ from .exceptions import ( ) +if not PY2: + import typing + + # This is used at least twice, so cache it here. _obj_setattr = object.__setattr__ -_init_converter_pat = "__attr_converter_{}" +_init_converter_pat = "__attr_converter_%s" _init_factory_pat = "__attr_factory_{}" _tuple_property_pat = ( " {attr_name} = _attrs_property(_attrs_itemgetter({index}))" ) -_classvar_prefixes = ("typing.ClassVar", "t.ClassVar", "ClassVar") +_classvar_prefixes = ( + "typing.ClassVar", + "t.ClassVar", + "ClassVar", + "typing_extensions.ClassVar", +) # we don't use a double-underscore prefix because that triggers # name mangling when trying to create a slot for the field # (when slots=True) @@ -42,12 +54,17 @@ _hash_cache_field = "_attrs_cached_hash" _empty_metadata_singleton = metadata_proxy({}) +# Unique object for unequivocal getattr() defaults. +_sentinel = object() + class _Nothing(object): """ Sentinel class to indicate the lack of a value when ``None`` is ambiguous. ``_Nothing`` is a singleton. There is only ever one of it. + + .. versionchanged:: 21.1.0 ``bool(NOTHING)`` is now False. """ _singleton = None @@ -60,6 +77,12 @@ class _Nothing(object): def __repr__(self): return "NOTHING" + def __bool__(self): + return False + + def __len__(self): + return 0 # __bool__ for Python 2 + NOTHING = _Nothing() """ @@ -67,19 +90,46 @@ Sentinel to indicate the lack of a value when ``None`` is ambiguous. """ +class _CacheHashWrapper(int): + """ + An integer subclass that pickles / copies as None + + This is used for non-slots classes with ``cache_hash=True``, to avoid + serializing a potentially (even likely) invalid hash value. Since ``None`` + is the default value for uncalculated hashes, whenever this is copied, + the copy's value for the hash should automatically reset. + + See GH #613 for more details. + """ + + if PY2: + # For some reason `type(None)` isn't callable in Python 2, but we don't + # actually need a constructor for None objects, we just need any + # available function that returns None. + def __reduce__(self, _none_constructor=getattr, _args=(0, "", None)): + return _none_constructor, _args + + else: + + def __reduce__(self, _none_constructor=type(None), _args=()): + return _none_constructor, _args + + def attrib( default=NOTHING, validator=None, repr=True, - cmp=True, + cmp=None, hash=None, init=True, - convert=None, metadata=None, type=None, converter=None, factory=None, kw_only=False, + eq=None, + order=None, + on_setattr=None, ): """ Create a new attribute on a class. @@ -87,65 +137,87 @@ def attrib( .. warning:: Does *not* do anything unless the class is also decorated with - :func:`attr.s`! + `attr.s`! :param default: A value that is used if an ``attrs``-generated ``__init__`` is used and no value is passed while instantiating or the attribute is excluded using ``init=False``. - If the value is an instance of :class:`Factory`, its callable will be + If the value is an instance of `Factory`, its callable will be used to construct a new value (useful for mutable data types like lists or dicts). - If a default is not set (or set manually to ``attr.NOTHING``), a value - *must* be supplied when instantiating; otherwise a :exc:`TypeError` + If a default is not set (or set manually to `attr.NOTHING`), a value + *must* be supplied when instantiating; otherwise a `TypeError` will be raised. The default can also be set using decorator notation as shown below. - :type default: Any value. + :type default: Any value :param callable factory: Syntactic sugar for - ``default=attr.Factory(callable)``. + ``default=attr.Factory(factory)``. - :param validator: :func:`callable` that is called by ``attrs``-generated + :param validator: `callable` that is called by ``attrs``-generated ``__init__`` methods after the instance has been initialized. They - receive the initialized instance, the :class:`Attribute`, and the + receive the initialized instance, the `Attribute`, and the passed value. The return value is *not* inspected so the validator has to throw an exception itself. - If a ``list`` is passed, its items are treated as validators and must + If a `list` is passed, its items are treated as validators and must all pass. Validators can be globally disabled and re-enabled using - :func:`get_run_validators`. + `get_run_validators`. The validator can also be set using decorator notation as shown below. - :type validator: ``callable`` or a ``list`` of ``callable``\\ s. + :type validator: `callable` or a `list` of `callable`\\ s. - :param bool repr: Include this attribute in the generated ``__repr__`` - method. - :param bool cmp: Include this attribute in the generated comparison methods - (``__eq__`` et al). - :param hash: Include this attribute in the generated ``__hash__`` - method. If ``None`` (default), mirror *cmp*'s value. This is the - correct behavior according the Python spec. Setting this value to - anything else than ``None`` is *discouraged*. - :type hash: ``bool`` or ``None`` + :param repr: Include this attribute in the generated ``__repr__`` + method. If ``True``, include the attribute; if ``False``, omit it. By + default, the built-in ``repr()`` function is used. To override how the + attribute value is formatted, pass a ``callable`` that takes a single + value and returns a string. Note that the resulting string is used + as-is, i.e. it will be used directly *instead* of calling ``repr()`` + (the default). + :type repr: a `bool` or a `callable` to use a custom function. + + :param eq: If ``True`` (default), include this attribute in the + generated ``__eq__`` and ``__ne__`` methods that check two instances + for equality. To override how the attribute value is compared, + pass a ``callable`` that takes a single value and returns the value + to be compared. + :type eq: a `bool` or a `callable`. + + :param order: If ``True`` (default), include this attributes in the + generated ``__lt__``, ``__le__``, ``__gt__`` and ``__ge__`` methods. + To override how the attribute value is ordered, + pass a ``callable`` that takes a single value and returns the value + to be ordered. + :type order: a `bool` or a `callable`. + + :param cmp: Setting *cmp* is equivalent to setting *eq* and *order* to the + same value. Must not be mixed with *eq* or *order*. + :type cmp: a `bool` or a `callable`. + + :param Optional[bool] hash: Include this attribute in the generated + ``__hash__`` method. If ``None`` (default), mirror *eq*'s value. This + is the correct behavior according the Python spec. Setting this value + to anything else than ``None`` is *discouraged*. :param bool init: Include this attribute in the generated ``__init__`` method. It is possible to set this to ``False`` and set a default value. In that case this attributed is unconditionally initialized with the specified default value or factory. - :param callable converter: :func:`callable` that is called by - ``attrs``-generated ``__init__`` methods to converter attribute's value + :param callable converter: `callable` that is called by + ``attrs``-generated ``__init__`` methods to convert attribute's value to the desired format. It is given the passed-in value, and the returned value will be used as the new value of the attribute. The value is converted before being passed to the validator, if any. :param metadata: An arbitrary mapping, to be used by third-party - components. See :ref:`extending_metadata`. + components. See `extending_metadata`. :param type: The type of the attribute. In Python 3.6 or greater, the preferred method to specify the type is using a variable annotation (see `PEP 526 <https://www.python.org/dev/peps/pep-0526/>`_). @@ -155,16 +227,22 @@ def attrib( Please note that ``attrs`` doesn't do anything with this metadata by itself. You can use it as part of your own code or for - :doc:`static type checking <types>`. + `static type checking <types>`. :param kw_only: Make this attribute keyword-only (Python 3+) in the generated ``__init__`` (if ``init`` is ``False``, this parameter is ignored). + :param on_setattr: Allows to overwrite the *on_setattr* setting from + `attr.s`. If left `None`, the *on_setattr* value from `attr.s` is used. + Set to `attr.setters.NO_OP` to run **no** `setattr` hooks for this + attribute -- regardless of the setting in `attr.s`. + :type on_setattr: `callable`, or a list of callables, or `None`, or + `attr.setters.NO_OP` .. versionadded:: 15.2.0 *convert* .. versionadded:: 16.3.0 *metadata* .. versionchanged:: 17.1.0 *validator* can be a ``list`` now. .. versionchanged:: 17.1.0 - *hash* is ``None`` and therefore mirrors *cmp* by default. + *hash* is ``None`` and therefore mirrors *eq* by default. .. versionadded:: 17.3.0 *type* .. deprecated:: 17.4.0 *convert* .. versionadded:: 17.4.0 *converter* as a replacement for the deprecated @@ -172,26 +250,25 @@ def attrib( .. versionadded:: 18.1.0 ``factory=f`` is syntactic sugar for ``default=attr.Factory(f)``. .. versionadded:: 18.2.0 *kw_only* + .. versionchanged:: 19.2.0 *convert* keyword argument removed. + .. versionchanged:: 19.2.0 *repr* also accepts a custom callable. + .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01. + .. versionadded:: 19.2.0 *eq* and *order* + .. versionadded:: 20.1.0 *on_setattr* + .. versionchanged:: 20.3.0 *kw_only* backported to Python 2 + .. versionchanged:: 21.1.0 + *eq*, *order*, and *cmp* also accept a custom callable + .. versionchanged:: 21.1.0 *cmp* undeprecated """ + eq, eq_key, order, order_key = _determine_attrib_eq_order( + cmp, eq, order, True + ) + if hash is not None and hash is not True and hash is not False: raise TypeError( "Invalid value for hash. Must be True, False, or None." ) - if convert is not None: - if converter is not None: - raise RuntimeError( - "Can't pass both `convert` and `converter`. " - "Please use `converter` only." - ) - warnings.warn( - "The `convert` argument is deprecated in favor of `converter`. " - "It will be removed after 2019/01.", - DeprecationWarning, - stacklevel=2, - ) - converter = convert - if factory is not None: if default is not NOTHING: raise ValueError( @@ -205,20 +282,65 @@ def attrib( if metadata is None: metadata = {} + # Apply syntactic sugar by auto-wrapping. + if isinstance(on_setattr, (list, tuple)): + on_setattr = setters.pipe(*on_setattr) + + if validator and isinstance(validator, (list, tuple)): + validator = and_(*validator) + + if converter and isinstance(converter, (list, tuple)): + converter = pipe(*converter) + return _CountingAttr( default=default, validator=validator, repr=repr, - cmp=cmp, + cmp=None, hash=hash, init=init, converter=converter, metadata=metadata, type=type, kw_only=kw_only, + eq=eq, + eq_key=eq_key, + order=order, + order_key=order_key, + on_setattr=on_setattr, ) +def _compile_and_eval(script, globs, locs=None, filename=""): + """ + "Exec" the script with the given global (globs) and local (locs) variables. + """ + bytecode = compile(script, filename, "exec") + eval(bytecode, globs, locs) + + +def _make_method(name, script, filename, globs=None): + """ + Create the method with the script given and return the method object. + """ + locs = {} + if globs is None: + globs = {} + + _compile_and_eval(script, globs, locs, filename) + + # In order of debuggers like PDB being able to step through the code, + # we add a fake linecache entry. + linecache.cache[filename] = ( + len(script), + None, + script.splitlines(True), + filename, + ) + + return locs[name] + + def _make_attr_tuple_class(cls_name, attr_names): """ Create a tuple subclass to hold `Attribute`s for an `attrs` class. @@ -242,8 +364,7 @@ def _make_attr_tuple_class(cls_name, attr_names): else: attr_class_template.append(" pass") globs = {"_attrs_itemgetter": itemgetter, "_attrs_property": property} - eval(compile("\n".join(attr_class_template), "", "exec"), globs) - + _compile_and_eval("\n".join(attr_class_template), globs) return globs[attr_class_name] @@ -270,23 +391,41 @@ def _is_class_var(annot): annotations which would put attrs-based classes at a performance disadvantage compared to plain old classes. """ - return str(annot).startswith(_classvar_prefixes) + annot = str(annot) + + # Annotation can be quoted. + if annot.startswith(("'", '"')) and annot.endswith(("'", '"')): + annot = annot[1:-1] + + return annot.startswith(_classvar_prefixes) + + +def _has_own_attribute(cls, attrib_name): + """ + Check whether *cls* defines *attrib_name* (and doesn't just inherit it). + + Requires Python 3. + """ + attr = getattr(cls, attrib_name, _sentinel) + if attr is _sentinel: + return False + + for base_cls in cls.__mro__[1:]: + a = getattr(base_cls, attrib_name, None) + if attr is a: + return False + + return True def _get_annotations(cls): """ Get annotations for *cls*. """ - anns = getattr(cls, "__annotations__", None) - if anns is None: - return {} + if _has_own_attribute(cls, "__annotations__"): + return cls.__annotations__ - # Verify that the annotations aren't merely inherited. - for base_cls in cls.__mro__[1:]: - if anns is getattr(base_cls, "__annotations__", None): - return {} - - return anns + return {} def _counter_getter(e): @@ -296,12 +435,76 @@ def _counter_getter(e): return e[1].counter -def _transform_attrs(cls, these, auto_attribs, kw_only): +def _collect_base_attrs(cls, taken_attr_names): + """ + Collect attr.ibs from base classes of *cls*, except *taken_attr_names*. + """ + base_attrs = [] + base_attr_map = {} # A dictionary of base attrs to their classes. + + # Traverse the MRO and collect attributes. + for base_cls in reversed(cls.__mro__[1:-1]): + for a in getattr(base_cls, "__attrs_attrs__", []): + if a.inherited or a.name in taken_attr_names: + continue + + a = a.evolve(inherited=True) + base_attrs.append(a) + base_attr_map[a.name] = base_cls + + # For each name, only keep the freshest definition i.e. the furthest at the + # back. base_attr_map is fine because it gets overwritten with every new + # instance. + filtered = [] + seen = set() + for a in reversed(base_attrs): + if a.name in seen: + continue + filtered.insert(0, a) + seen.add(a.name) + + return filtered, base_attr_map + + +def _collect_base_attrs_broken(cls, taken_attr_names): + """ + Collect attr.ibs from base classes of *cls*, except *taken_attr_names*. + + N.B. *taken_attr_names* will be mutated. + + Adhere to the old incorrect behavior. + + Notably it collects from the front and considers inherited attributes which + leads to the buggy behavior reported in #428. + """ + base_attrs = [] + base_attr_map = {} # A dictionary of base attrs to their classes. + + # Traverse the MRO and collect attributes. + for base_cls in cls.__mro__[1:-1]: + for a in getattr(base_cls, "__attrs_attrs__", []): + if a.name in taken_attr_names: + continue + + a = a.evolve(inherited=True) + taken_attr_names.add(a.name) + base_attrs.append(a) + base_attr_map[a.name] = base_cls + + return base_attrs, base_attr_map + + +def _transform_attrs( + cls, these, auto_attribs, kw_only, collect_by_mro, field_transformer +): """ Transform all `_CountingAttr`s on a class into `Attribute`s. If *these* is passed, use that and don't look for them on the class. + *collect_by_mro* is True, collect them in the correct MRO order, otherwise + use the old -- incorrect -- order. See #428. + Return an `_Attributes`. """ cd = cls.__dict__ @@ -325,6 +528,7 @@ def _transform_attrs(cls, these, auto_attribs, kw_only): continue annot_names.add(attr_name) a = cd.get(attr_name, NOTHING) + if not isinstance(a, _CountingAttr): if a is NOTHING: a = attrib() @@ -358,74 +562,68 @@ def _transform_attrs(cls, these, auto_attribs, kw_only): for attr_name, ca in ca_list ] - base_attrs = [] - base_attr_map = {} # A dictionary of base attrs to their classes. - taken_attr_names = {a.name: a for a in own_attrs} - - # Traverse the MRO and collect attributes. - for base_cls in cls.__mro__[1:-1]: - sub_attrs = getattr(base_cls, "__attrs_attrs__", None) - if sub_attrs is not None: - for a in sub_attrs: - prev_a = taken_attr_names.get(a.name) - # Only add an attribute if it hasn't been defined before. This - # allows for overwriting attribute definitions by subclassing. - if prev_a is None: - base_attrs.append(a) - taken_attr_names[a.name] = a - base_attr_map[a.name] = base_cls + if collect_by_mro: + base_attrs, base_attr_map = _collect_base_attrs( + cls, {a.name for a in own_attrs} + ) + else: + base_attrs, base_attr_map = _collect_base_attrs_broken( + cls, {a.name for a in own_attrs} + ) attr_names = [a.name for a in base_attrs + own_attrs] AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names) if kw_only: - own_attrs = [a._assoc(kw_only=True) for a in own_attrs] - base_attrs = [a._assoc(kw_only=True) for a in base_attrs] + own_attrs = [a.evolve(kw_only=True) for a in own_attrs] + base_attrs = [a.evolve(kw_only=True) for a in base_attrs] attrs = AttrsClass(base_attrs + own_attrs) + # Mandatory vs non-mandatory attr order only matters when they are part of + # the __init__ signature and when they aren't kw_only (which are moved to + # the end and can be mandatory or non-mandatory in any order, as they will + # be specified as keyword args anyway). Check the order of those attrs: had_default = False - was_kw_only = False - for a in attrs: - if ( - was_kw_only is False - and had_default is True - and a.default is NOTHING - and a.init is True - and a.kw_only is False - ): + for a in (a for a in attrs if a.init is not False and a.kw_only is False): + if had_default is True and a.default is NOTHING: raise ValueError( "No mandatory attributes allowed after an attribute with a " "default value or factory. Attribute in question: %r" % (a,) ) - elif ( - had_default is False - and a.default is not NOTHING - and a.init is not False - and - # Keyword-only attributes without defaults can be specified - # after keyword-only attributes with defaults. - a.kw_only is False - ): - had_default = True - if was_kw_only is True and a.kw_only is False and a.init is True: - raise ValueError( - "Non keyword-only attributes are not allowed after a " - "keyword-only attribute (unless they are init=False). " - "Attribute in question: {a!r}".format(a=a) - ) - if was_kw_only is False and a.init is True and a.kw_only is True: - was_kw_only = True + if had_default is False and a.default is not NOTHING: + had_default = True + + if field_transformer is not None: + attrs = field_transformer(cls, attrs) return _Attributes((attrs, base_attrs, base_attr_map)) -def _frozen_setattrs(self, name, value): - """ - Attached to frozen classes as __setattr__. - """ - raise FrozenInstanceError() +if PYPY: + + def _frozen_setattrs(self, name, value): + """ + Attached to frozen classes as __setattr__. + """ + if isinstance(self, BaseException) and name in ( + "__cause__", + "__context__", + ): + BaseException.__setattr__(self, name, value) + return + + raise FrozenInstanceError() + + +else: + + def _frozen_setattrs(self, name, value): + """ + Attached to frozen classes as __setattr__. + """ + raise FrozenInstanceError() def _frozen_delattrs(self, name): @@ -441,19 +639,23 @@ class _ClassBuilder(object): """ __slots__ = ( + "_attr_names", + "_attrs", + "_base_attr_map", + "_base_names", + "_cache_hash", "_cls", "_cls_dict", - "_attrs", - "_base_names", - "_attr_names", - "_slots", - "_frozen", - "_weakref_slot", - "_cache_hash", - "_has_post_init", "_delete_attribs", - "_base_attr_map", + "_frozen", + "_has_pre_init", + "_has_post_init", "_is_exc", + "_on_setattr", + "_slots", + "_weakref_slot", + "_has_own_setattr", + "_has_custom_setattr", ) def __init__( @@ -463,13 +665,23 @@ class _ClassBuilder(object): slots, frozen, weakref_slot, + getstate_setstate, auto_attribs, kw_only, cache_hash, is_exc, + collect_by_mro, + on_setattr, + has_custom_setattr, + field_transformer, ): attrs, base_attrs, base_map = _transform_attrs( - cls, these, auto_attribs, kw_only + cls, + these, + auto_attribs, + kw_only, + collect_by_mro, + field_transformer, ) self._cls = cls @@ -479,12 +691,17 @@ class _ClassBuilder(object): self._base_attr_map = base_map self._attr_names = tuple(a.name for a in attrs) self._slots = slots - self._frozen = frozen or _has_frozen_base_class(cls) + self._frozen = frozen self._weakref_slot = weakref_slot self._cache_hash = cache_hash + self._has_pre_init = bool(getattr(cls, "__attrs_pre_init__", False)) self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False)) self._delete_attribs = not bool(these) self._is_exc = is_exc + self._on_setattr = on_setattr + + self._has_custom_setattr = has_custom_setattr + self._has_own_setattr = False self._cls_dict["__attrs_attrs__"] = self._attrs @@ -492,6 +709,14 @@ class _ClassBuilder(object): self._cls_dict["__setattr__"] = _frozen_setattrs self._cls_dict["__delattr__"] = _frozen_delattrs + self._has_own_setattr = True + + if getstate_setstate: + ( + self._cls_dict["__getstate__"], + self._cls_dict["__setstate__"], + ) = self._make_getstate_setstate() + def __repr__(self): return "<_ClassBuilder(cls={cls})>".format(cls=self._cls.__name__) @@ -518,7 +743,7 @@ class _ClassBuilder(object): for name in self._attr_names: if ( name not in base_names - and getattr(cls, name, None) is not None + and getattr(cls, name, _sentinel) is not _sentinel ): try: delattr(cls, name) @@ -532,25 +757,15 @@ class _ClassBuilder(object): for name, value in self._cls_dict.items(): setattr(cls, name, value) - # Attach __setstate__. This is necessary to clear the hash code - # cache on deserialization. See issue - # https://github.com/python-attrs/attrs/issues/482 . - # Note that this code only handles setstate for dict classes. - # For slotted classes, see similar code in _create_slots_class . - if self._cache_hash: - existing_set_state_method = getattr(cls, "__setstate__", None) - if existing_set_state_method: - raise NotImplementedError( - "Currently you cannot use hash caching if " - "you specify your own __setstate__ method." - "See https://github.com/python-attrs/attrs/issues/494 ." - ) + # If we've inherited an attrs __setattr__ and don't write our own, + # reset it to object's. + if not self._has_own_setattr and getattr( + cls, "__attrs_own_setattr__", False + ): + cls.__attrs_own_setattr__ = False - def cache_hash_set_state(chss_self, _): - # clear hash code cache - setattr(chss_self, _hash_cache_field, None) - - setattr(cls, "__setstate__", cache_hash_set_state) + if not self._has_custom_setattr: + cls.__setattr__ = object.__setattr__ return cls @@ -558,20 +773,44 @@ class _ClassBuilder(object): """ Build and return a new class with a `__slots__` attribute. """ - base_names = self._base_names cd = { k: v for k, v in iteritems(self._cls_dict) if k not in tuple(self._attr_names) + ("__dict__", "__weakref__") } - weakref_inherited = False + # If our class doesn't have its own implementation of __setattr__ + # (either from the user or by us), check the bases, if one of them has + # an attrs-made __setattr__, that needs to be reset. We don't walk the + # MRO because we only care about our immediate base classes. + # XXX: This can be confused by subclassing a slotted attrs class with + # XXX: a non-attrs class and subclass the resulting class with an attrs + # XXX: class. See `test_slotted_confused` for details. For now that's + # XXX: OK with us. + if not self._has_own_setattr: + cd["__attrs_own_setattr__"] = False - # Traverse the MRO to check for an existing __weakref__. + if not self._has_custom_setattr: + for base_cls in self._cls.__bases__: + if base_cls.__dict__.get("__attrs_own_setattr__", False): + cd["__setattr__"] = object.__setattr__ + break + + # Traverse the MRO to collect existing slots + # and check for an existing __weakref__. + existing_slots = dict() + weakref_inherited = False for base_cls in self._cls.__mro__[1:-1]: - if "__weakref__" in getattr(base_cls, "__dict__", ()): + if base_cls.__dict__.get("__weakref__", None) is not None: weakref_inherited = True - break + existing_slots.update( + { + name: getattr(base_cls, name) + for name in getattr(base_cls, "__slots__", []) + } + ) + + base_names = set(self._base_names) names = self._attr_names if ( @@ -583,8 +822,19 @@ class _ClassBuilder(object): names += ("__weakref__",) # We only add the names of attributes that aren't inherited. - # Settings __slots__ to inherited attributes wastes memory. + # Setting __slots__ to inherited attributes wastes memory. slot_names = [name for name in names if name not in base_names] + # There are slots for attributes from current class + # that are defined in parent classes. + # As their descriptors may be overriden by a child class, + # we collect them here and update the class dict + reused_slots = { + slot: slot_descriptor + for slot, slot_descriptor in iteritems(existing_slots) + if slot in slot_names + } + slot_names = [name for name in slot_names if name not in reused_slots] + cd.update(reused_slots) if self._cache_hash: slot_names.append(_hash_cache_field) cd["__slots__"] = tuple(slot_names) @@ -593,38 +843,6 @@ class _ClassBuilder(object): if qualname is not None: cd["__qualname__"] = qualname - # __weakref__ is not writable. - state_attr_names = tuple( - an for an in self._attr_names if an != "__weakref__" - ) - - def slots_getstate(self): - """ - Automatically created by attrs. - """ - return tuple(getattr(self, name) for name in state_attr_names) - - hash_caching_enabled = self._cache_hash - - def slots_setstate(self, state): - """ - Automatically created by attrs. - """ - __bound_setattr = _obj_setattr.__get__(self, Attribute) - for name, value in zip(state_attr_names, state): - __bound_setattr(name, value) - # Clearing the hash code cache on deserialization is needed - # because hash codes can change from run to run. See issue - # https://github.com/python-attrs/attrs/issues/482 . - # Note that this code only handles setstate for slotted classes. - # For dict classes, see similar code in _patch_original_class . - if hash_caching_enabled: - __bound_setattr(_hash_cache_field, None) - - # slots and frozen require __getstate__/__setstate__ to work - cd["__getstate__"] = slots_getstate - cd["__setstate__"] = slots_setstate - # Create new class based on old class and our methods. cls = type(self._cls)(self._cls.__name__, self._cls.__bases__, cd) @@ -639,14 +857,23 @@ class _ClassBuilder(object): # Class- and staticmethods hide their functions inside. # These might need to be rewritten as well. closure_cells = getattr(item.__func__, "__closure__", None) + elif isinstance(item, property): + # Workaround for property `super()` shortcut (PY3-only). + # There is no universal way for other descriptors. + closure_cells = getattr(item.fget, "__closure__", None) else: closure_cells = getattr(item, "__closure__", None) if not closure_cells: # Catch None or the empty list. continue for cell in closure_cells: - if cell.cell_contents is self._cls: - set_closure_cell(cell, cls) + try: + match = cell.cell_contents is self._cls + except ValueError: # ValueError: Cell is empty + pass + else: + if match: + set_closure_cell(cell, cls) return cls @@ -669,6 +896,40 @@ class _ClassBuilder(object): self._cls_dict["__str__"] = self._add_method_dunders(__str__) return self + def _make_getstate_setstate(self): + """ + Create custom __setstate__ and __getstate__ methods. + """ + # __weakref__ is not writable. + state_attr_names = tuple( + an for an in self._attr_names if an != "__weakref__" + ) + + def slots_getstate(self): + """ + Automatically created by attrs. + """ + return tuple(getattr(self, name) for name in state_attr_names) + + hash_caching_enabled = self._cache_hash + + def slots_setstate(self, state): + """ + Automatically created by attrs. + """ + __bound_setattr = _obj_setattr.__get__(self, Attribute) + for name, value in zip(state_attr_names, state): + __bound_setattr(name, value) + + # The hash code cache is not included when the object is + # serialized, but it still needs to be initialized to None to + # indicate that the first call to __hash__ should be a cache + # miss. + if hash_caching_enabled: + __bound_setattr(_hash_cache_field, None) + + return slots_getstate, slots_setstate + def make_unhashable(self): self._cls_dict["__hash__"] = None return self @@ -676,7 +937,10 @@ class _ClassBuilder(object): def add_hash(self): self._cls_dict["__hash__"] = self._add_method_dunders( _make_hash( - self._attrs, frozen=self._frozen, cache_hash=self._cache_hash + self._cls, + self._attrs, + frozen=self._frozen, + cache_hash=self._cache_hash, ) ) @@ -685,26 +949,96 @@ class _ClassBuilder(object): def add_init(self): self._cls_dict["__init__"] = self._add_method_dunders( _make_init( + self._cls, self._attrs, + self._has_pre_init, self._has_post_init, self._frozen, self._slots, self._cache_hash, self._base_attr_map, self._is_exc, + self._on_setattr is not None + and self._on_setattr is not setters.NO_OP, + attrs_init=False, ) ) return self - def add_cmp(self): + def add_attrs_init(self): + self._cls_dict["__attrs_init__"] = self._add_method_dunders( + _make_init( + self._cls, + self._attrs, + self._has_pre_init, + self._has_post_init, + self._frozen, + self._slots, + self._cache_hash, + self._base_attr_map, + self._is_exc, + self._on_setattr is not None + and self._on_setattr is not setters.NO_OP, + attrs_init=True, + ) + ) + + return self + + def add_eq(self): cd = self._cls_dict - cd["__eq__"], cd["__ne__"], cd["__lt__"], cd["__le__"], cd[ - "__gt__" - ], cd["__ge__"] = ( - self._add_method_dunders(meth) for meth in _make_cmp(self._attrs) + cd["__eq__"] = self._add_method_dunders( + _make_eq(self._cls, self._attrs) ) + cd["__ne__"] = self._add_method_dunders(_make_ne()) + + return self + + def add_order(self): + cd = self._cls_dict + + cd["__lt__"], cd["__le__"], cd["__gt__"], cd["__ge__"] = ( + self._add_method_dunders(meth) + for meth in _make_order(self._cls, self._attrs) + ) + + return self + + def add_setattr(self): + if self._frozen: + return self + + sa_attrs = {} + for a in self._attrs: + on_setattr = a.on_setattr or self._on_setattr + if on_setattr and on_setattr is not setters.NO_OP: + sa_attrs[a.name] = a, on_setattr + + if not sa_attrs: + return self + + if self._has_custom_setattr: + # We need to write a __setattr__ but there already is one! + raise ValueError( + "Can't combine custom __setattr__ with on_setattr hooks." + ) + + # docstring comes from _add_method_dunders + def __setattr__(self, name, val): + try: + a, hook = sa_attrs[name] + except KeyError: + nval = val + else: + nval = hook(self, a, val) + + _obj_setattr(self, name, nval) + + self._cls_dict["__attrs_own_setattr__"] = True + self._cls_dict["__setattr__"] = self._add_method_dunders(__setattr__) + self._has_own_setattr = True return self @@ -724,17 +1058,125 @@ class _ClassBuilder(object): except AttributeError: pass + try: + method.__doc__ = "Method generated by attrs for class %s." % ( + self._cls.__qualname__, + ) + except AttributeError: + pass + return method +_CMP_DEPRECATION = ( + "The usage of `cmp` is deprecated and will be removed on or after " + "2021-06-01. Please use `eq` and `order` instead." +) + + +def _determine_attrs_eq_order(cmp, eq, order, default_eq): + """ + Validate the combination of *cmp*, *eq*, and *order*. Derive the effective + values of eq and order. If *eq* is None, set it to *default_eq*. + """ + if cmp is not None and any((eq is not None, order is not None)): + raise ValueError("Don't mix `cmp` with `eq' and `order`.") + + # cmp takes precedence due to bw-compatibility. + if cmp is not None: + return cmp, cmp + + # If left None, equality is set to the specified default and ordering + # mirrors equality. + if eq is None: + eq = default_eq + + if order is None: + order = eq + + if eq is False and order is True: + raise ValueError("`order` can only be True if `eq` is True too.") + + return eq, order + + +def _determine_attrib_eq_order(cmp, eq, order, default_eq): + """ + Validate the combination of *cmp*, *eq*, and *order*. Derive the effective + values of eq and order. If *eq* is None, set it to *default_eq*. + """ + if cmp is not None and any((eq is not None, order is not None)): + raise ValueError("Don't mix `cmp` with `eq' and `order`.") + + def decide_callable_or_boolean(value): + """ + Decide whether a key function is used. + """ + if callable(value): + value, key = True, value + else: + key = None + return value, key + + # cmp takes precedence due to bw-compatibility. + if cmp is not None: + cmp, cmp_key = decide_callable_or_boolean(cmp) + return cmp, cmp_key, cmp, cmp_key + + # If left None, equality is set to the specified default and ordering + # mirrors equality. + if eq is None: + eq, eq_key = default_eq, None + else: + eq, eq_key = decide_callable_or_boolean(eq) + + if order is None: + order, order_key = eq, eq_key + else: + order, order_key = decide_callable_or_boolean(order) + + if eq is False and order is True: + raise ValueError("`order` can only be True if `eq` is True too.") + + return eq, eq_key, order, order_key + + +def _determine_whether_to_implement( + cls, flag, auto_detect, dunders, default=True +): + """ + Check whether we should implement a set of methods for *cls*. + + *flag* is the argument passed into @attr.s like 'init', *auto_detect* the + same as passed into @attr.s and *dunders* is a tuple of attribute names + whose presence signal that the user has implemented it themselves. + + Return *default* if no reason for either for or against is found. + + auto_detect must be False on Python 2. + """ + if flag is True or flag is False: + return flag + + if flag is None and auto_detect is False: + return default + + # Logically, flag is None and auto_detect is True here. + for dunder in dunders: + if _has_own_attribute(cls, dunder): + return False + + return default + + def attrs( maybe_cls=None, these=None, repr_ns=None, - repr=True, - cmp=True, + repr=None, + cmp=None, hash=None, - init=True, + init=None, slots=False, frozen=False, weakref_slot=True, @@ -743,13 +1185,20 @@ def attrs( kw_only=False, cache_hash=False, auto_exc=False, + eq=None, + order=None, + auto_detect=False, + collect_by_mro=False, + getstate_setstate=None, + on_setattr=None, + field_transformer=None, ): r""" A class decorator that adds `dunder <https://wiki.python.org/moin/DunderAlias>`_\ -methods according to the - specified attributes using :func:`attr.ib` or the *these* argument. + specified attributes using `attr.ib` or the *these* argument. - :param these: A dictionary of name to :func:`attr.ib` mappings. This is + :param these: A dictionary of name to `attr.ib` mappings. This is useful to avoid the definition of your attributes within the class body because you can't (e.g. if you want to add ``__repr__`` methods to Django models) or don't want to. @@ -757,32 +1206,65 @@ def attrs( If *these* is not ``None``, ``attrs`` will *not* search the class body for attributes and will *not* remove any attributes from it. - If *these* is an ordered dict (:class:`dict` on Python 3.6+, - :class:`collections.OrderedDict` otherwise), the order is deduced from + If *these* is an ordered dict (`dict` on Python 3.6+, + `collections.OrderedDict` otherwise), the order is deduced from the order of the attributes inside *these*. Otherwise the order of the definition of the attributes is used. - :type these: :class:`dict` of :class:`str` to :func:`attr.ib` + :type these: `dict` of `str` to `attr.ib` :param str repr_ns: When using nested classes, there's no way in Python 2 to automatically detect that. Therefore it's possible to set the namespace explicitly for a more meaningful ``repr`` output. + :param bool auto_detect: Instead of setting the *init*, *repr*, *eq*, + *order*, and *hash* arguments explicitly, assume they are set to + ``True`` **unless any** of the involved methods for one of the + arguments is implemented in the *current* class (i.e. it is *not* + inherited from some base class). + + So for example by implementing ``__eq__`` on a class yourself, + ``attrs`` will deduce ``eq=False`` and will create *neither* + ``__eq__`` *nor* ``__ne__`` (but Python classes come with a sensible + ``__ne__`` by default, so it *should* be enough to only implement + ``__eq__`` in most cases). + + .. warning:: + + If you prevent ``attrs`` from creating the ordering methods for you + (``order=False``, e.g. by implementing ``__le__``), it becomes + *your* responsibility to make sure its ordering is sound. The best + way is to use the `functools.total_ordering` decorator. + + + Passing ``True`` or ``False`` to *init*, *repr*, *eq*, *order*, + *cmp*, or *hash* overrides whatever *auto_detect* would determine. + + *auto_detect* requires Python 3. Setting it ``True`` on Python 2 raises + a `PythonTooOldError`. + :param bool repr: Create a ``__repr__`` method with a human readable representation of ``attrs`` attributes.. :param bool str: Create a ``__str__`` method that is identical to ``__repr__``. This is usually not necessary except for - :class:`Exception`\ s. - :param bool cmp: Create ``__eq__``, ``__ne__``, ``__lt__``, ``__le__``, - ``__gt__``, and ``__ge__`` methods that compare the class as if it were - a tuple of its ``attrs`` attributes. But the attributes are *only* - compared, if the types of both classes are *identical*! - :param hash: If ``None`` (default), the ``__hash__`` method is generated - according how *cmp* and *frozen* are set. + `Exception`\ s. + :param Optional[bool] eq: If ``True`` or ``None`` (default), add ``__eq__`` + and ``__ne__`` methods that check two instances for equality. + + They compare the instances as if they were tuples of their ``attrs`` + attributes if and only if the types of both classes are *identical*! + :param Optional[bool] order: If ``True``, add ``__lt__``, ``__le__``, + ``__gt__``, and ``__ge__`` methods that behave like *eq* above and + allow instances to be ordered. If ``None`` (default) mirror value of + *eq*. + :param Optional[bool] cmp: Setting *cmp* is equivalent to setting *eq* + and *order* to the same value. Must not be mixed with *eq* or *order*. + :param Optional[bool] hash: If ``None`` (default), the ``__hash__`` method + is generated according how *eq* and *frozen* are set. 1. If *both* are True, ``attrs`` will generate a ``__hash__`` for you. - 2. If *cmp* is True and *frozen* is False, ``__hash__`` will be set to + 2. If *eq* is True and *frozen* is False, ``__hash__`` will be set to None, marking it unhashable (which it is). - 3. If *cmp* is False, ``__hash__`` will be left untouched meaning the + 3. If *eq* is False, ``__hash__`` will be left untouched meaning the ``__hash__`` method of the base class will be used (if base class is ``object``, this means it will fall back to id-based hashing.). @@ -791,29 +1273,37 @@ def attrs( didn't freeze it programmatically) by passing ``True`` or not. Both of these cases are rather special and should be used carefully. - See the `Python documentation \ - <https://docs.python.org/3/reference/datamodel.html#object.__hash__>`_ - and the `GitHub issue that led to the default behavior \ - <https://github.com/python-attrs/attrs/issues/136>`_ for more details. - :type hash: ``bool`` or ``None`` + See our documentation on `hashing`, Python's documentation on + `object.__hash__`, and the `GitHub issue that led to the default \ + behavior <https://github.com/python-attrs/attrs/issues/136>`_ for more + details. :param bool init: Create a ``__init__`` method that initializes the - ``attrs`` attributes. Leading underscores are stripped for the - argument name. If a ``__attrs_post_init__`` method exists on the - class, it will be called after the class is fully initialized. - :param bool slots: Create a slots_-style class that's more - memory-efficient. See :ref:`slots` for further ramifications. + ``attrs`` attributes. Leading underscores are stripped for the argument + name. If a ``__attrs_pre_init__`` method exists on the class, it will + be called before the class is initialized. If a ``__attrs_post_init__`` + method exists on the class, it will be called after the class is fully + initialized. + + If ``init`` is ``False``, an ``__attrs_init__`` method will be + injected instead. This allows you to define a custom ``__init__`` + method that can do pre-init work such as ``super().__init__()``, + and then call ``__attrs_init__()`` and ``__attrs_post_init__()``. + :param bool slots: Create a `slotted class <slotted classes>` that's more + memory-efficient. Slotted classes are generally superior to the default + dict classes, but have some gotchas you should know about, so we + encourage you to read the `glossary entry <slotted classes>`. :param bool frozen: Make instances immutable after initialization. If someone attempts to modify a frozen instance, - :exc:`attr.exceptions.FrozenInstanceError` is raised. + `attr.exceptions.FrozenInstanceError` is raised. - Please note: + .. note:: 1. This is achieved by installing a custom ``__setattr__`` method - on your class so you can't implement an own one. + on your class, so you can't implement your own. 2. True immutability is impossible in Python. - 3. This *does* have a minor a runtime performance :ref:`impact + 3. This *does* have a minor a runtime performance `impact <how-frozen>` when initializing new instances. In other words: ``__init__`` is slightly slower with ``frozen=True``. @@ -822,24 +1312,35 @@ def attrs( circumvent that limitation by using ``object.__setattr__(self, "attribute_name", value)``. - .. _slots: https://docs.python.org/3/reference/datamodel.html#slots + 5. Subclasses of a frozen class are frozen too. + :param bool weakref_slot: Make instances weak-referenceable. This has no effect unless ``slots`` is also enabled. - :param bool auto_attribs: If True, collect `PEP 526`_-annotated attributes - (Python 3.6 and later only) from the class body. + :param bool auto_attribs: If ``True``, collect `PEP 526`_-annotated + attributes (Python 3.6 and later only) from the class body. In this case, you **must** annotate every field. If ``attrs`` - encounters a field that is set to an :func:`attr.ib` but lacks a type - annotation, an :exc:`attr.exceptions.UnannotatedAttributeError` is + encounters a field that is set to an `attr.ib` but lacks a type + annotation, an `attr.exceptions.UnannotatedAttributeError` is raised. Use ``field_name: typing.Any = attr.ib(...)`` if you don't want to set a type. If you assign a value to those attributes (e.g. ``x: int = 42``), that value becomes the default value like if it were passed using - ``attr.ib(default=42)``. Passing an instance of :class:`Factory` also - works as expected. + ``attr.ib(default=42)``. Passing an instance of `Factory` also + works as expected in most cases (see warning below). - Attributes annotated as :data:`typing.ClassVar` are **ignored**. + Attributes annotated as `typing.ClassVar`, and attributes that are + neither annotated nor set to an `attr.ib` are **ignored**. + + .. warning:: + For features that use the attribute name to create decorators (e.g. + `validators <validators>`), you still *must* assign `attr.ib` to + them. Otherwise Python will either not find the name or try to use + the default value to call e.g. ``validator`` on it. + + These errors can be quite confusing and probably the most common bug + report on our bug tracker. .. _`PEP 526`: https://www.python.org/dev/peps/pep-0526/ :param bool kw_only: Make all attributes keyword-only (Python 3+) @@ -852,19 +1353,59 @@ def attrs( fields involved in hash code computation or mutations of the objects those fields point to after object creation. If such changes occur, the behavior of the object's hash code is undefined. - :param bool auto_exc: If the class subclasses :class:`BaseException` + :param bool auto_exc: If the class subclasses `BaseException` (which implicitly includes any subclass of any exception), the following happens to behave like a well-behaved Python exceptions class: - - the values for *cmp* and *hash* are ignored and the instances compare - and hash by the instance's ids (N.B. ``attrs`` will *not* remove - existing implementations of ``__hash__`` or the equality methods. It - just won't add own ones.), + - the values for *eq*, *order*, and *hash* are ignored and the + instances compare and hash by the instance's ids (N.B. ``attrs`` will + *not* remove existing implementations of ``__hash__`` or the equality + methods. It just won't add own ones.), - all attributes that are either passed into ``__init__`` or have a default value are additionally available as a tuple in the ``args`` attribute, - the value of *str* is ignored leaving ``__str__`` to base classes. + :param bool collect_by_mro: Setting this to `True` fixes the way ``attrs`` + collects attributes from base classes. The default behavior is + incorrect in certain cases of multiple inheritance. It should be on by + default but is kept off for backward-compatability. + + See issue `#428 <https://github.com/python-attrs/attrs/issues/428>`_ for + more details. + + :param Optional[bool] getstate_setstate: + .. note:: + This is usually only interesting for slotted classes and you should + probably just set *auto_detect* to `True`. + + If `True`, ``__getstate__`` and + ``__setstate__`` are generated and attached to the class. This is + necessary for slotted classes to be pickleable. If left `None`, it's + `True` by default for slotted classes and ``False`` for dict classes. + + If *auto_detect* is `True`, and *getstate_setstate* is left `None`, + and **either** ``__getstate__`` or ``__setstate__`` is detected directly + on the class (i.e. not inherited), it is set to `False` (this is usually + what you want). + + :param on_setattr: A callable that is run whenever the user attempts to set + an attribute (either by assignment like ``i.x = 42`` or by using + `setattr` like ``setattr(i, "x", 42)``). It receives the same arguments + as validators: the instance, the attribute that is being modified, and + the new value. + + If no exception is raised, the attribute is set to the return value of + the callable. + + If a list of callables is passed, they're automatically wrapped in an + `attr.setters.pipe`. + + :param Optional[callable] field_transformer: + A function that is called with the original class object and all + fields right before ``attrs`` finalizes the class. You can use + this, e.g., to automatically add converters or validators to + fields based on their types. See `transform-fields` for more details. .. versionadded:: 16.0.0 *slots* .. versionadded:: 16.1.0 *frozen* @@ -879,59 +1420,122 @@ def attrs( .. versionadded:: 18.2.0 *weakref_slot* .. deprecated:: 18.2.0 ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now raise a - :class:`DeprecationWarning` if the classes compared are subclasses of + `DeprecationWarning` if the classes compared are subclasses of each other. ``__eq`` and ``__ne__`` never tried to compared subclasses to each other. + .. versionchanged:: 19.2.0 + ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now do not consider + subclasses comparable anymore. .. versionadded:: 18.2.0 *kw_only* .. versionadded:: 18.2.0 *cache_hash* .. versionadded:: 19.1.0 *auto_exc* + .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01. + .. versionadded:: 19.2.0 *eq* and *order* + .. versionadded:: 20.1.0 *auto_detect* + .. versionadded:: 20.1.0 *collect_by_mro* + .. versionadded:: 20.1.0 *getstate_setstate* + .. versionadded:: 20.1.0 *on_setattr* + .. versionadded:: 20.3.0 *field_transformer* + .. versionchanged:: 21.1.0 + ``init=False`` injects ``__attrs_init__`` + .. versionchanged:: 21.1.0 Support for ``__attrs_pre_init__`` + .. versionchanged:: 21.1.0 *cmp* undeprecated """ + if auto_detect and PY2: + raise PythonTooOldError( + "auto_detect only works on Python 3 and later." + ) + + eq_, order_ = _determine_attrs_eq_order(cmp, eq, order, None) + hash_ = hash # work around the lack of nonlocal + + if isinstance(on_setattr, (list, tuple)): + on_setattr = setters.pipe(*on_setattr) def wrap(cls): if getattr(cls, "__class__", None) is None: raise TypeError("attrs only works with new-style classes.") + is_frozen = frozen or _has_frozen_base_class(cls) is_exc = auto_exc is True and issubclass(cls, BaseException) + has_own_setattr = auto_detect and _has_own_attribute( + cls, "__setattr__" + ) + + if has_own_setattr and is_frozen: + raise ValueError("Can't freeze a class with a custom __setattr__.") builder = _ClassBuilder( cls, these, slots, - frozen, + is_frozen, weakref_slot, + _determine_whether_to_implement( + cls, + getstate_setstate, + auto_detect, + ("__getstate__", "__setstate__"), + default=slots, + ), auto_attribs, kw_only, cache_hash, is_exc, + collect_by_mro, + on_setattr, + has_own_setattr, + field_transformer, ) - - if repr is True: + if _determine_whether_to_implement( + cls, repr, auto_detect, ("__repr__",) + ): builder.add_repr(repr_ns) if str is True: builder.add_str() - if cmp is True and not is_exc: - builder.add_cmp() + eq = _determine_whether_to_implement( + cls, eq_, auto_detect, ("__eq__", "__ne__") + ) + if not is_exc and eq is True: + builder.add_eq() + if not is_exc and _determine_whether_to_implement( + cls, order_, auto_detect, ("__lt__", "__le__", "__gt__", "__ge__") + ): + builder.add_order() + + builder.add_setattr() + + if ( + hash_ is None + and auto_detect is True + and _has_own_attribute(cls, "__hash__") + ): + hash = False + else: + hash = hash_ if hash is not True and hash is not False and hash is not None: # Can't use `hash in` because 1 == True for example. raise TypeError( "Invalid value for hash. Must be True, False, or None." ) - elif hash is False or (hash is None and cmp is False): + elif hash is False or (hash is None and eq is False) or is_exc: + # Don't do anything. Should fall back to __object__'s __hash__ + # which is by id. if cache_hash: raise TypeError( "Invalid value for cache_hash. To use hash caching," " hashing must be either explicitly or implicitly " "enabled." ) - elif ( - hash is True - or (hash is None and cmp is True and frozen is True) - and is_exc is False + elif hash is True or ( + hash is None and eq is True and is_frozen is True ): + # Build a __hash__ if told so, or if it's safe. builder.add_hash() else: + # Raise TypeError on attempts to hash. if cache_hash: raise TypeError( "Invalid value for cache_hash. To use hash caching," @@ -940,9 +1544,12 @@ def attrs( ) builder.make_unhashable() - if init is True: + if _determine_whether_to_implement( + cls, init, auto_detect, ("__init__",) + ): builder.add_init() else: + builder.add_attrs_init() if cache_hash: raise TypeError( "Invalid value for cache_hash. To use hash caching," @@ -990,29 +1597,63 @@ else: return cls.__setattr__ == _frozen_setattrs -def _attrs_to_tuple(obj, attrs): +def _generate_unique_filename(cls, func_name): """ - Create a tuple of all values of *obj*'s *attrs*. + Create a "filename" suitable for a function being generated. """ - return tuple(getattr(obj, a.name) for a in attrs) + unique_id = uuid.uuid4() + extra = "" + count = 1 + + while True: + unique_filename = "<attrs generated {0} {1}.{2}{3}>".format( + func_name, + cls.__module__, + getattr(cls, "__qualname__", cls.__name__), + extra, + ) + # To handle concurrency we essentially "reserve" our spot in + # the linecache with a dummy line. The caller can then + # set this value correctly. + cache_line = (1, None, (str(unique_id),), unique_filename) + if ( + linecache.cache.setdefault(unique_filename, cache_line) + == cache_line + ): + return unique_filename + + # Looks like this spot is taken. Try again. + count += 1 + extra = "-{0}".format(count) -def _make_hash(attrs, frozen, cache_hash): +def _make_hash(cls, attrs, frozen, cache_hash): attrs = tuple( - a - for a in attrs - if a.hash is True or (a.hash is None and a.cmp is True) + a for a in attrs if a.hash is True or (a.hash is None and a.eq is True) ) tab = " " - # We cache the generated hash methods for the same kinds of attributes. - sha1 = hashlib.sha1() - sha1.update(repr(attrs).encode("utf-8")) - unique_filename = "<attrs generated hash %s>" % (sha1.hexdigest(),) + unique_filename = _generate_unique_filename(cls, "hash") type_hash = hash(unique_filename) - method_lines = ["def __hash__(self):"] + hash_def = "def __hash__(self" + hash_func = "hash((" + closing_braces = "))" + if not cache_hash: + hash_def += "):" + else: + if not PY2: + hash_def += ", *" + + hash_def += ( + ", _cache_wrapper=" + + "__import__('attr._make')._make._CacheHashWrapper):" + ) + hash_func = "_cache_wrapper(" + hash_func + closing_braces += ")" + + method_lines = [hash_def] def append_hash_computation_lines(prefix, indent): """ @@ -1020,14 +1661,18 @@ def _make_hash(attrs, frozen, cache_hash): Below this will either be returned directly or used to compute a value which is then cached, depending on the value of cache_hash """ + method_lines.extend( - [indent + prefix + "hash((", indent + " %d," % (type_hash,)] + [ + indent + prefix + hash_func, + indent + " %d," % (type_hash,), + ] ) for a in attrs: method_lines.append(indent + " self.%s," % a.name) - method_lines.append(indent + " ))") + method_lines.append(indent + " " + closing_braces) if cache_hash: method_lines.append(tab + "if self.%s is None:" % _hash_cache_field) @@ -1045,162 +1690,153 @@ def _make_hash(attrs, frozen, cache_hash): append_hash_computation_lines("return ", tab) script = "\n".join(method_lines) - globs = {} - locs = {} - bytecode = compile(script, unique_filename, "exec") - eval(bytecode, globs, locs) - - # In order of debuggers like PDB being able to step through the code, - # we add a fake linecache entry. - linecache.cache[unique_filename] = ( - len(script), - None, - script.splitlines(True), - unique_filename, - ) - - return locs["__hash__"] + return _make_method("__hash__", script, unique_filename) def _add_hash(cls, attrs): """ Add a hash method to *cls*. """ - cls.__hash__ = _make_hash(attrs, frozen=False, cache_hash=False) + cls.__hash__ = _make_hash(cls, attrs, frozen=False, cache_hash=False) return cls -def __ne__(self, other): +def _make_ne(): """ - Check equality and either forward a NotImplemented or return the result - negated. + Create __ne__ method. """ - result = self.__eq__(other) - if result is NotImplemented: - return NotImplemented - return not result + def __ne__(self, other): + """ + Check equality and either forward a NotImplemented or + return the result negated. + """ + result = self.__eq__(other) + if result is NotImplemented: + return NotImplemented + + return not result + + return __ne__ -WARNING_CMP_ISINSTANCE = ( - "Comparision of subclasses using __%s__ is deprecated and will be removed " - "in 2019." -) +def _make_eq(cls, attrs): + """ + Create __eq__ method for *cls* with *attrs*. + """ + attrs = [a for a in attrs if a.eq] - -def _make_cmp(attrs): - attrs = [a for a in attrs if a.cmp] - - # We cache the generated eq methods for the same kinds of attributes. - sha1 = hashlib.sha1() - sha1.update(repr(attrs).encode("utf-8")) - unique_filename = "<attrs generated eq %s>" % (sha1.hexdigest(),) + unique_filename = _generate_unique_filename(cls, "eq") lines = [ "def __eq__(self, other):", " if other.__class__ is not self.__class__:", " return NotImplemented", ] + # We can't just do a big self.x = other.x and... clause due to # irregularities like nan == nan is false but (nan,) == (nan,) is true. + globs = {} if attrs: lines.append(" return (") others = [" ) == ("] for a in attrs: - lines.append(" self.%s," % (a.name,)) - others.append(" other.%s," % (a.name,)) + if a.eq_key: + cmp_name = "_%s_key" % (a.name,) + # Add the key function to the global namespace + # of the evaluated function. + globs[cmp_name] = a.eq_key + lines.append( + " %s(self.%s)," + % ( + cmp_name, + a.name, + ) + ) + others.append( + " %s(other.%s)," + % ( + cmp_name, + a.name, + ) + ) + else: + lines.append(" self.%s," % (a.name,)) + others.append(" other.%s," % (a.name,)) lines += others + [" )"] else: lines.append(" return True") script = "\n".join(lines) - globs = {} - locs = {} - bytecode = compile(script, unique_filename, "exec") - eval(bytecode, globs, locs) - # In order of debuggers like PDB being able to step through the code, - # we add a fake linecache entry. - linecache.cache[unique_filename] = ( - len(script), - None, - script.splitlines(True), - unique_filename, - ) - eq = locs["__eq__"] - ne = __ne__ + return _make_method("__eq__", script, unique_filename, globs) + + +def _make_order(cls, attrs): + """ + Create ordering methods for *cls* with *attrs*. + """ + attrs = [a for a in attrs if a.order] def attrs_to_tuple(obj): """ Save us some typing. """ - return _attrs_to_tuple(obj, attrs) + return tuple( + key(value) if key else value + for value, key in ( + (getattr(obj, a.name), a.order_key) for a in attrs + ) + ) def __lt__(self, other): """ Automatically created by attrs. """ - if isinstance(other, self.__class__): - if other.__class__ is not self.__class__: - warnings.warn( - WARNING_CMP_ISINSTANCE % ("lt",), DeprecationWarning - ) + if other.__class__ is self.__class__: return attrs_to_tuple(self) < attrs_to_tuple(other) - else: - return NotImplemented + + return NotImplemented def __le__(self, other): """ Automatically created by attrs. """ - if isinstance(other, self.__class__): - if other.__class__ is not self.__class__: - warnings.warn( - WARNING_CMP_ISINSTANCE % ("le",), DeprecationWarning - ) + if other.__class__ is self.__class__: return attrs_to_tuple(self) <= attrs_to_tuple(other) - else: - return NotImplemented + + return NotImplemented def __gt__(self, other): """ Automatically created by attrs. """ - if isinstance(other, self.__class__): - if other.__class__ is not self.__class__: - warnings.warn( - WARNING_CMP_ISINSTANCE % ("gt",), DeprecationWarning - ) + if other.__class__ is self.__class__: return attrs_to_tuple(self) > attrs_to_tuple(other) - else: - return NotImplemented + + return NotImplemented def __ge__(self, other): """ Automatically created by attrs. """ - if isinstance(other, self.__class__): - if other.__class__ is not self.__class__: - warnings.warn( - WARNING_CMP_ISINSTANCE % ("ge",), DeprecationWarning - ) + if other.__class__ is self.__class__: return attrs_to_tuple(self) >= attrs_to_tuple(other) - else: - return NotImplemented - return eq, ne, __lt__, __le__, __gt__, __ge__ + return NotImplemented + + return __lt__, __le__, __gt__, __ge__ -def _add_cmp(cls, attrs=None): +def _add_eq(cls, attrs=None): """ - Add comparison methods to *cls*. + Add equality methods to *cls* with *attrs*. """ if attrs is None: attrs = cls.__attrs_attrs__ - cls.__eq__, cls.__ne__, cls.__lt__, cls.__le__, cls.__gt__, cls.__ge__ = _make_cmp( # noqa - attrs - ) + cls.__eq__ = _make_eq(cls, attrs) + cls.__ne__ = _make_ne() return cls @@ -1210,9 +1846,17 @@ _already_repring = threading.local() def _make_repr(attrs, ns): """ - Make a repr method for *attr_names* adding *ns* to the full name. + Make a repr method that includes relevant *attrs*, adding *ns* to the full + name. """ - attr_names = tuple(a.name for a in attrs if a.repr) + + # Figure out which attributes to include, and which function to use to + # format them. The a.repr value can be either bool or a custom callable. + attr_names_with_reprs = tuple( + (a.name, repr if a.repr is True else a.repr) + for a in attrs + if a.repr is not False + ) def __repr__(self): """ @@ -1244,12 +1888,14 @@ def _make_repr(attrs, ns): try: result = [class_name, "("] first = True - for name in attr_names: + for name, attr_repr in attr_names_with_reprs: if first: first = False else: result.append(", ") - result.extend((name, "=", repr(getattr(self, name, NOTHING)))) + result.extend( + (name, "=", attr_repr(getattr(self, name, NOTHING))) + ) return "".join(result) + ")" finally: working_set.remove(id(self)) @@ -1268,46 +1914,6 @@ def _add_repr(cls, ns=None, attrs=None): return cls -def _make_init( - attrs, post_init, frozen, slots, cache_hash, base_attr_map, is_exc -): - attrs = [a for a in attrs if a.init or a.default is not NOTHING] - - # We cache the generated init methods for the same kinds of attributes. - sha1 = hashlib.sha1() - sha1.update(repr(attrs).encode("utf-8")) - unique_filename = "<attrs generated init {0}>".format(sha1.hexdigest()) - - script, globs, annotations = _attrs_to_init_script( - attrs, frozen, slots, post_init, cache_hash, base_attr_map, is_exc - ) - locs = {} - bytecode = compile(script, unique_filename, "exec") - attr_dict = dict((a.name, a) for a in attrs) - globs.update({"NOTHING": NOTHING, "attr_dict": attr_dict}) - - if frozen is True: - # Save the lookup overhead in __init__ if we need to circumvent - # immutability. - globs["_cached_setattr"] = _obj_setattr - - eval(bytecode, globs, locs) - - # In order of debuggers like PDB being able to step through the code, - # we add a fake linecache entry. - linecache.cache[unique_filename] = ( - len(script), - None, - script.splitlines(True), - unique_filename, - ) - - __init__ = locs["__init__"] - __init__.__annotations__ = annotations - - return __init__ - - def fields(cls): """ Return the tuple of ``attrs`` attributes for a class. @@ -1321,7 +1927,7 @@ def fields(cls): :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` class. - :rtype: tuple (with name accessors) of :class:`attr.Attribute` + :rtype: tuple (with name accessors) of `attr.Attribute` .. versionchanged:: 16.2.0 Returned tuple allows accessing the fields by name. @@ -1348,7 +1954,7 @@ def fields_dict(cls): class. :rtype: an ordered dict where keys are attribute names and values are - :class:`attr.Attribute`\\ s. This will be a :class:`dict` if it's + `attr.Attribute`\\ s. This will be a `dict` if it's naturally ordered like on Python 3.6+ or an :class:`~collections.OrderedDict` otherwise. @@ -1392,8 +1998,193 @@ def _is_slot_attr(a_name, base_attr_map): return a_name in base_attr_map and _is_slot_cls(base_attr_map[a_name]) +def _make_init( + cls, + attrs, + pre_init, + post_init, + frozen, + slots, + cache_hash, + base_attr_map, + is_exc, + has_global_on_setattr, + attrs_init, +): + if frozen and has_global_on_setattr: + raise ValueError("Frozen classes can't use on_setattr.") + + needs_cached_setattr = cache_hash or frozen + filtered_attrs = [] + attr_dict = {} + for a in attrs: + if not a.init and a.default is NOTHING: + continue + + filtered_attrs.append(a) + attr_dict[a.name] = a + + if a.on_setattr is not None: + if frozen is True: + raise ValueError("Frozen classes can't use on_setattr.") + + needs_cached_setattr = True + elif ( + has_global_on_setattr and a.on_setattr is not setters.NO_OP + ) or _is_slot_attr(a.name, base_attr_map): + needs_cached_setattr = True + + unique_filename = _generate_unique_filename(cls, "init") + + script, globs, annotations = _attrs_to_init_script( + filtered_attrs, + frozen, + slots, + pre_init, + post_init, + cache_hash, + base_attr_map, + is_exc, + needs_cached_setattr, + has_global_on_setattr, + attrs_init, + ) + if cls.__module__ in sys.modules: + # This makes typing.get_type_hints(CLS.__init__) resolve string types. + globs.update(sys.modules[cls.__module__].__dict__) + + globs.update({"NOTHING": NOTHING, "attr_dict": attr_dict}) + + if needs_cached_setattr: + # Save the lookup overhead in __init__ if we need to circumvent + # setattr hooks. + globs["_cached_setattr"] = _obj_setattr + + init = _make_method( + "__attrs_init__" if attrs_init else "__init__", + script, + unique_filename, + globs, + ) + init.__annotations__ = annotations + + return init + + +def _setattr(attr_name, value_var, has_on_setattr): + """ + Use the cached object.setattr to set *attr_name* to *value_var*. + """ + return "_setattr('%s', %s)" % (attr_name, value_var) + + +def _setattr_with_converter(attr_name, value_var, has_on_setattr): + """ + Use the cached object.setattr to set *attr_name* to *value_var*, but run + its converter first. + """ + return "_setattr('%s', %s(%s))" % ( + attr_name, + _init_converter_pat % (attr_name,), + value_var, + ) + + +def _assign(attr_name, value, has_on_setattr): + """ + Unless *attr_name* has an on_setattr hook, use normal assignment. Otherwise + relegate to _setattr. + """ + if has_on_setattr: + return _setattr(attr_name, value, True) + + return "self.%s = %s" % (attr_name, value) + + +def _assign_with_converter(attr_name, value_var, has_on_setattr): + """ + Unless *attr_name* has an on_setattr hook, use normal assignment after + conversion. Otherwise relegate to _setattr_with_converter. + """ + if has_on_setattr: + return _setattr_with_converter(attr_name, value_var, True) + + return "self.%s = %s(%s)" % ( + attr_name, + _init_converter_pat % (attr_name,), + value_var, + ) + + +if PY2: + + def _unpack_kw_only_py2(attr_name, default=None): + """ + Unpack *attr_name* from _kw_only dict. + """ + if default is not None: + arg_default = ", %s" % default + else: + arg_default = "" + return "%s = _kw_only.pop('%s'%s)" % ( + attr_name, + attr_name, + arg_default, + ) + + def _unpack_kw_only_lines_py2(kw_only_args): + """ + Unpack all *kw_only_args* from _kw_only dict and handle errors. + + Given a list of strings "{attr_name}" and "{attr_name}={default}" + generates list of lines of code that pop attrs from _kw_only dict and + raise TypeError similar to builtin if required attr is missing or + extra key is passed. + + >>> print("\n".join(_unpack_kw_only_lines_py2(["a", "b=42"]))) + try: + a = _kw_only.pop('a') + b = _kw_only.pop('b', 42) + except KeyError as _key_error: + raise TypeError( + ... + if _kw_only: + raise TypeError( + ... + """ + lines = ["try:"] + lines.extend( + " " + _unpack_kw_only_py2(*arg.split("=")) + for arg in kw_only_args + ) + lines += """\ +except KeyError as _key_error: + raise TypeError( + '__init__() missing required keyword-only argument: %s' % _key_error + ) +if _kw_only: + raise TypeError( + '__init__() got an unexpected keyword argument %r' + % next(iter(_kw_only)) + ) +""".split( + "\n" + ) + return lines + + def _attrs_to_init_script( - attrs, frozen, slots, post_init, cache_hash, base_attr_map, is_exc + attrs, + frozen, + slots, + pre_init, + post_init, + cache_hash, + base_attr_map, + is_exc, + needs_cached_setattr, + has_global_on_setattr, + attrs_init, ): """ Return a script of an initializer for *attrs* and a dict of globals. @@ -1404,85 +2195,52 @@ def _attrs_to_init_script( a cached ``object.__setattr__``. """ lines = [] - any_slot_ancestors = any( - _is_slot_attr(a.name, base_attr_map) for a in attrs - ) + if pre_init: + lines.append("self.__attrs_pre_init__()") + + if needs_cached_setattr: + lines.append( + # Circumvent the __setattr__ descriptor to save one lookup per + # assignment. + # Note _setattr will be used again below if cache_hash is True + "_setattr = _cached_setattr.__get__(self, self.__class__)" + ) + if frozen is True: if slots is True: - lines.append( - # Circumvent the __setattr__ descriptor to save one lookup per - # assignment. - # Note _setattr will be used again below if cache_hash is True - "_setattr = _cached_setattr.__get__(self, self.__class__)" - ) - - def fmt_setter(attr_name, value_var): - return "_setattr('%(attr_name)s', %(value_var)s)" % { - "attr_name": attr_name, - "value_var": value_var, - } - - def fmt_setter_with_converter(attr_name, value_var): - conv_name = _init_converter_pat.format(attr_name) - return "_setattr('%(attr_name)s', %(conv)s(%(value_var)s))" % { - "attr_name": attr_name, - "value_var": value_var, - "conv": conv_name, - } - + fmt_setter = _setattr + fmt_setter_with_converter = _setattr_with_converter else: # Dict frozen classes assign directly to __dict__. # But only if the attribute doesn't come from an ancestor slot # class. # Note _inst_dict will be used again below if cache_hash is True lines.append("_inst_dict = self.__dict__") - if any_slot_ancestors: - lines.append( - # Circumvent the __setattr__ descriptor to save one lookup - # per assignment. - "_setattr = _cached_setattr.__get__(self, self.__class__)" + + def fmt_setter(attr_name, value_var, has_on_setattr): + if _is_slot_attr(attr_name, base_attr_map): + return _setattr(attr_name, value_var, has_on_setattr) + + return "_inst_dict['%s'] = %s" % (attr_name, value_var) + + def fmt_setter_with_converter( + attr_name, value_var, has_on_setattr + ): + if has_on_setattr or _is_slot_attr(attr_name, base_attr_map): + return _setattr_with_converter( + attr_name, value_var, has_on_setattr + ) + + return "_inst_dict['%s'] = %s(%s)" % ( + attr_name, + _init_converter_pat % (attr_name,), + value_var, ) - def fmt_setter(attr_name, value_var): - if _is_slot_attr(attr_name, base_attr_map): - res = "_setattr('%(attr_name)s', %(value_var)s)" % { - "attr_name": attr_name, - "value_var": value_var, - } - else: - res = "_inst_dict['%(attr_name)s'] = %(value_var)s" % { - "attr_name": attr_name, - "value_var": value_var, - } - return res - - def fmt_setter_with_converter(attr_name, value_var): - conv_name = _init_converter_pat.format(attr_name) - if _is_slot_attr(attr_name, base_attr_map): - tmpl = "_setattr('%(attr_name)s', %(c)s(%(value_var)s))" - else: - tmpl = "_inst_dict['%(attr_name)s'] = %(c)s(%(value_var)s)" - return tmpl % { - "attr_name": attr_name, - "value_var": value_var, - "c": conv_name, - } - else: # Not frozen. - def fmt_setter(attr_name, value): - return "self.%(attr_name)s = %(value)s" % { - "attr_name": attr_name, - "value": value, - } - - def fmt_setter_with_converter(attr_name, value_var): - conv_name = _init_converter_pat.format(attr_name) - return "self.%(attr_name)s = %(conv)s(%(value_var)s)" % { - "attr_name": attr_name, - "value_var": value_var, - "conv": conv_name, - } + fmt_setter = _assign + fmt_setter_with_converter = _assign_with_converter args = [] kw_only_args = [] @@ -1496,13 +2254,19 @@ def _attrs_to_init_script( for a in attrs: if a.validator: attrs_to_validate.append(a) + attr_name = a.name + has_on_setattr = a.on_setattr is not None or ( + a.on_setattr is not setters.NO_OP and has_global_on_setattr + ) arg_name = a.name.lstrip("_") + has_factory = isinstance(a.default, Factory) if has_factory and a.default.takes_self: maybe_self = "self" else: maybe_self = "" + if a.init is False: if has_factory: init_factory_name = _init_factory_pat.format(a.name) @@ -1510,16 +2274,18 @@ def _attrs_to_init_script( lines.append( fmt_setter_with_converter( attr_name, - init_factory_name + "({0})".format(maybe_self), + init_factory_name + "(%s)" % (maybe_self,), + has_on_setattr, ) ) - conv_name = _init_converter_pat.format(a.name) + conv_name = _init_converter_pat % (a.name,) names_for_globals[conv_name] = a.converter else: lines.append( fmt_setter( attr_name, - init_factory_name + "({0})".format(maybe_self), + init_factory_name + "(%s)" % (maybe_self,), + has_on_setattr, ) ) names_for_globals[init_factory_name] = a.default.factory @@ -1528,70 +2294,78 @@ def _attrs_to_init_script( lines.append( fmt_setter_with_converter( attr_name, - "attr_dict['{attr_name}'].default".format( - attr_name=attr_name - ), + "attr_dict['%s'].default" % (attr_name,), + has_on_setattr, ) ) - conv_name = _init_converter_pat.format(a.name) + conv_name = _init_converter_pat % (a.name,) names_for_globals[conv_name] = a.converter else: lines.append( fmt_setter( attr_name, - "attr_dict['{attr_name}'].default".format( - attr_name=attr_name - ), + "attr_dict['%s'].default" % (attr_name,), + has_on_setattr, ) ) elif a.default is not NOTHING and not has_factory: - arg = "{arg_name}=attr_dict['{attr_name}'].default".format( - arg_name=arg_name, attr_name=attr_name - ) + arg = "%s=attr_dict['%s'].default" % (arg_name, attr_name) if a.kw_only: kw_only_args.append(arg) else: args.append(arg) + if a.converter is not None: - lines.append(fmt_setter_with_converter(attr_name, arg_name)) + lines.append( + fmt_setter_with_converter( + attr_name, arg_name, has_on_setattr + ) + ) names_for_globals[ - _init_converter_pat.format(a.name) + _init_converter_pat % (a.name,) ] = a.converter else: - lines.append(fmt_setter(attr_name, arg_name)) + lines.append(fmt_setter(attr_name, arg_name, has_on_setattr)) + elif has_factory: - arg = "{arg_name}=NOTHING".format(arg_name=arg_name) + arg = "%s=NOTHING" % (arg_name,) if a.kw_only: kw_only_args.append(arg) else: args.append(arg) - lines.append( - "if {arg_name} is not NOTHING:".format(arg_name=arg_name) - ) + lines.append("if %s is not NOTHING:" % (arg_name,)) + init_factory_name = _init_factory_pat.format(a.name) if a.converter is not None: lines.append( - " " + fmt_setter_with_converter(attr_name, arg_name) + " " + + fmt_setter_with_converter( + attr_name, arg_name, has_on_setattr + ) ) lines.append("else:") lines.append( " " + fmt_setter_with_converter( attr_name, - init_factory_name + "({0})".format(maybe_self), + init_factory_name + "(" + maybe_self + ")", + has_on_setattr, ) ) names_for_globals[ - _init_converter_pat.format(a.name) + _init_converter_pat % (a.name,) ] = a.converter else: - lines.append(" " + fmt_setter(attr_name, arg_name)) + lines.append( + " " + fmt_setter(attr_name, arg_name, has_on_setattr) + ) lines.append("else:") lines.append( " " + fmt_setter( attr_name, - init_factory_name + "({0})".format(maybe_self), + init_factory_name + "(" + maybe_self + ")", + has_on_setattr, ) ) names_for_globals[init_factory_name] = a.default.factory @@ -1600,28 +2374,50 @@ def _attrs_to_init_script( kw_only_args.append(arg_name) else: args.append(arg_name) + if a.converter is not None: - lines.append(fmt_setter_with_converter(attr_name, arg_name)) + lines.append( + fmt_setter_with_converter( + attr_name, arg_name, has_on_setattr + ) + ) names_for_globals[ - _init_converter_pat.format(a.name) + _init_converter_pat % (a.name,) ] = a.converter else: - lines.append(fmt_setter(attr_name, arg_name)) + lines.append(fmt_setter(attr_name, arg_name, has_on_setattr)) - if a.init is True and a.converter is None and a.type is not None: - annotations[arg_name] = a.type + if a.init is True: + if a.type is not None and a.converter is None: + annotations[arg_name] = a.type + elif a.converter is not None and not PY2: + # Try to get the type from the converter. + sig = None + try: + sig = inspect.signature(a.converter) + except (ValueError, TypeError): # inspect failed + pass + if sig: + sig_params = list(sig.parameters.values()) + if ( + sig_params + and sig_params[0].annotation + is not inspect.Parameter.empty + ): + annotations[arg_name] = sig_params[0].annotation if attrs_to_validate: # we can skip this if there are no validators. names_for_globals["_config"] = _config lines.append("if _config._run_validators is True:") for a in attrs_to_validate: - val_name = "__attr_validator_{}".format(a.name) - attr_name = "__attr_{}".format(a.name) + val_name = "__attr_validator_" + a.name + attr_name = "__attr_" + a.name lines.append( - " {}(self, {}, self.{})".format(val_name, attr_name, a.name) + " %s(self, %s, self.%s)" % (val_name, attr_name, a.name) ) names_for_globals[val_name] = a.validator names_for_globals[attr_name] = a + if post_init: lines.append("self.__attrs_post_init__()") @@ -1652,20 +2448,22 @@ def _attrs_to_init_script( args = ", ".join(args) if kw_only_args: if PY2: - raise PythonTooOldError( - "Keyword-only arguments only work on Python 3 and later." - ) + lines = _unpack_kw_only_lines_py2(kw_only_args) + lines - args += "{leading_comma}*, {kw_only_args}".format( - leading_comma=", " if args else "", - kw_only_args=", ".join(kw_only_args), - ) + args += "%s**_kw_only" % (", " if args else "",) # leading comma + else: + args += "%s*, %s" % ( + ", " if args else "", # leading comma + ", ".join(kw_only_args), # kw_only args + ) return ( """\ -def __init__(self, {args}): +def {init_name}(self, {args}): {lines} """.format( - args=args, lines="\n ".join(lines) if lines else "pass" + init_name=("__attrs_init__" if attrs_init else "__init__"), + args=args, + lines="\n ".join(lines) if lines else "pass", ), names_for_globals, annotations, @@ -1676,11 +2474,27 @@ class Attribute(object): """ *Read-only* representation of an attribute. + Instances of this class are frequently used for introspection purposes + like: + + - `fields` returns a tuple of them. + - Validators get them passed as the first argument. + - The *field transformer* hook receives a list of them. + :attribute name: The name of the attribute. + :attribute inherited: Whether or not that attribute has been inherited from + a base class. - Plus *all* arguments of :func:`attr.ib`. + Plus *all* arguments of `attr.ib` (except for ``factory`` + which is only syntactic sugar for ``default=Factory(...)``. - For the version history of the fields, see :func:`attr.ib`. + .. versionadded:: 20.1.0 *inherited* + .. versionadded:: 20.1.0 *on_setattr* + .. versionchanged:: 20.2.0 *inherited* is not taken into account for + equality checks and hashing anymore. + .. versionadded:: 21.1.0 *eq_key* and *order_key* + + For the full version history of the fields, see `attr.ib`. """ __slots__ = ( @@ -1688,13 +2502,18 @@ class Attribute(object): "default", "validator", "repr", - "cmp", + "eq", + "eq_key", + "order", + "order_key", "hash", "init", "metadata", "type", "converter", "kw_only", + "inherited", + "on_setattr", ) def __init__( @@ -1703,39 +2522,37 @@ class Attribute(object): default, validator, repr, - cmp, + cmp, # XXX: unused, remove along with other cmp code. hash, init, - convert=None, + inherited, metadata=None, type=None, converter=None, kw_only=False, + eq=None, + eq_key=None, + order=None, + order_key=None, + on_setattr=None, ): + eq, eq_key, order, order_key = _determine_attrib_eq_order( + cmp, eq_key or eq, order_key or order, True + ) + # Cache this descriptor here to speed things up later. bound_setattr = _obj_setattr.__get__(self, Attribute) # Despite the big red warning, people *do* instantiate `Attribute` # themselves. - if convert is not None: - if converter is not None: - raise RuntimeError( - "Can't pass both `convert` and `converter`. " - "Please use `converter` only." - ) - warnings.warn( - "The `convert` argument is deprecated in favor of `converter`." - " It will be removed after 2019/01.", - DeprecationWarning, - stacklevel=2, - ) - converter = convert - bound_setattr("name", name) bound_setattr("default", default) bound_setattr("validator", validator) bound_setattr("repr", repr) - bound_setattr("cmp", cmp) + bound_setattr("eq", eq) + bound_setattr("eq_key", eq_key) + bound_setattr("order", order) + bound_setattr("order_key", order_key) bound_setattr("hash", hash) bound_setattr("init", init) bound_setattr("converter", converter) @@ -1749,20 +2566,12 @@ class Attribute(object): ) bound_setattr("type", type) bound_setattr("kw_only", kw_only) + bound_setattr("inherited", inherited) + bound_setattr("on_setattr", on_setattr) def __setattr__(self, name, value): raise FrozenInstanceError() - @property - def convert(self): - warnings.warn( - "The `convert` attribute is deprecated in favor of `converter`. " - "It will be removed after 2019/01.", - DeprecationWarning, - stacklevel=2, - ) - return self.converter - @classmethod def from_counting_attr(cls, name, ca, type=None): # type holds the annotated value. deal with conflicts: @@ -1781,7 +2590,7 @@ class Attribute(object): "validator", "default", "type", - "convert", + "inherited", ) # exclude methods and deprecated alias } return cls( @@ -1789,13 +2598,31 @@ class Attribute(object): validator=ca._validator, default=ca._default, type=type, + cmp=None, + inherited=False, **inst_dict ) - # Don't use attr.assoc since fields(Attribute) doesn't work - def _assoc(self, **changes): + @property + def cmp(self): + """ + Simulate the presence of a cmp attribute and warn. + """ + warnings.warn(_CMP_DEPRECATION, DeprecationWarning, stacklevel=2) + + return self.eq and self.order + + # Don't use attr.evolve since fields(Attribute) doesn't work + def evolve(self, **changes): """ Copy *self* and apply *changes*. + + This works similarly to `attr.evolve` but that function does not work + with ``Attribute``. + + It is mainly meant to be used for `transform-fields`. + + .. versionadded:: 20.3.0 """ new = copy.copy(self) @@ -1839,17 +2666,22 @@ _a = [ default=NOTHING, validator=None, repr=True, - cmp=True, + cmp=None, + eq=True, + order=False, hash=(name != "metadata"), init=True, + inherited=False, ) for name in Attribute.__slots__ - if name != "convert" # XXX: remove once `convert` is gone ] Attribute = _add_hash( - _add_cmp(_add_repr(Attribute, attrs=_a), attrs=_a), - attrs=[a for a in _a if a.hash], + _add_eq( + _add_repr(Attribute, attrs=_a), + attrs=[a for a in _a if a.name != "inherited"], + ), + attrs=[a for a in _a if a.hash and a.name != "inherited"], ) @@ -1866,7 +2698,10 @@ class _CountingAttr(object): "counter", "_default", "repr", - "cmp", + "eq", + "eq_key", + "order", + "order_key", "hash", "init", "metadata", @@ -1874,6 +2709,7 @@ class _CountingAttr(object): "converter", "type", "kw_only", + "on_setattr", ) __attrs_attrs__ = tuple( Attribute( @@ -1881,22 +2717,43 @@ class _CountingAttr(object): default=NOTHING, validator=None, repr=True, - cmp=True, + cmp=None, hash=True, init=True, kw_only=False, + eq=True, + eq_key=None, + order=False, + order_key=None, + inherited=False, + on_setattr=None, + ) + for name in ( + "counter", + "_default", + "repr", + "eq", + "order", + "hash", + "init", + "on_setattr", ) - for name in ("counter", "_default", "repr", "cmp", "hash", "init") ) + ( Attribute( name="metadata", default=None, validator=None, repr=True, - cmp=True, + cmp=None, hash=False, init=True, kw_only=False, + eq=True, + eq_key=None, + order=False, + order_key=None, + inherited=False, + on_setattr=None, ), ) cls_counter = 0 @@ -1913,23 +2770,28 @@ class _CountingAttr(object): metadata, type, kw_only, + eq, + eq_key, + order, + order_key, + on_setattr, ): _CountingAttr.cls_counter += 1 self.counter = _CountingAttr.cls_counter self._default = default - # If validator is a list/tuple, wrap it using helper validator. - if validator and isinstance(validator, (list, tuple)): - self._validator = and_(*validator) - else: - self._validator = validator + self._validator = validator + self.converter = converter self.repr = repr - self.cmp = cmp + self.eq = eq + self.eq_key = eq_key + self.order = order + self.order_key = order_key self.hash = hash self.init = init - self.converter = converter self.metadata = metadata self.type = type self.kw_only = kw_only + self.on_setattr = on_setattr def validator(self, meth): """ @@ -1963,15 +2825,14 @@ class _CountingAttr(object): return meth -_CountingAttr = _add_cmp(_add_repr(_CountingAttr)) +_CountingAttr = _add_eq(_add_repr(_CountingAttr)) -@attrs(slots=True, init=False, hash=True) class Factory(object): """ Stores a factory callable. - If passed as the default value to :func:`attr.ib`, the factory is used to + If passed as the default value to `attr.ib`, the factory is used to generate a new value. :param callable factory: A callable that takes either none or exactly one @@ -1982,8 +2843,7 @@ class Factory(object): .. versionadded:: 17.1.0 *takes_self* """ - factory = attrib() - takes_self = attrib() + __slots__ = ("factory", "takes_self") def __init__(self, factory, takes_self=False): """ @@ -1993,26 +2853,57 @@ class Factory(object): self.factory = factory self.takes_self = takes_self + def __getstate__(self): + """ + Play nice with pickle. + """ + return tuple(getattr(self, name) for name in self.__slots__) + + def __setstate__(self, state): + """ + Play nice with pickle. + """ + for name, value in zip(self.__slots__, state): + setattr(self, name, value) + + +_f = [ + Attribute( + name=name, + default=NOTHING, + validator=None, + repr=True, + cmp=None, + eq=True, + order=False, + hash=True, + init=True, + inherited=False, + ) + for name in Factory.__slots__ +] + +Factory = _add_hash(_add_eq(_add_repr(Factory, attrs=_f), attrs=_f), attrs=_f) + def make_class(name, attrs, bases=(object,), **attributes_arguments): """ A quick way to create a new class called *name* with *attrs*. - :param name: The name for the new class. - :type name: str + :param str name: The name for the new class. :param attrs: A list of names or a dictionary of mappings of names to attributes. - If *attrs* is a list or an ordered dict (:class:`dict` on Python 3.6+, - :class:`collections.OrderedDict` otherwise), the order is deduced from + If *attrs* is a list or an ordered dict (`dict` on Python 3.6+, + `collections.OrderedDict` otherwise), the order is deduced from the order of the names or attributes inside *attrs*. Otherwise the order of the definition of the attributes is used. - :type attrs: :class:`list` or :class:`dict` + :type attrs: `list` or `dict` :param tuple bases: Classes that the new class will subclass. - :param attributes_arguments: Passed unmodified to :func:`attr.s`. + :param attributes_arguments: Passed unmodified to `attr.s`. :return: A new class with *attrs*. :rtype: type @@ -2027,12 +2918,20 @@ def make_class(name, attrs, bases=(object,), **attributes_arguments): else: raise TypeError("attrs argument must be a dict or a list.") + pre_init = cls_dict.pop("__attrs_pre_init__", None) post_init = cls_dict.pop("__attrs_post_init__", None) - type_ = type( - name, - bases, - {} if post_init is None else {"__attrs_post_init__": post_init}, - ) + user_init = cls_dict.pop("__init__", None) + + body = {} + if pre_init is not None: + body["__attrs_pre_init__"] = pre_init + if post_init is not None: + body["__attrs_post_init__"] = post_init + if user_init is not None: + body["__init__"] = user_init + + type_ = new_class(name, bases, {}, lambda ns: ns.update(body)) + # For pickling to work, the __module__ variable needs to be set to the # frame where the class is created. Bypass this step in environments where # sys._getframe is not defined (Jython for example) or sys._getframe is not @@ -2044,11 +2943,23 @@ def make_class(name, attrs, bases=(object,), **attributes_arguments): except (AttributeError, ValueError): pass + # We do it here for proper warnings with meaningful stacklevel. + cmp = attributes_arguments.pop("cmp", None) + ( + attributes_arguments["eq"], + attributes_arguments["order"], + ) = _determine_attrs_eq_order( + cmp, + attributes_arguments.get("eq"), + attributes_arguments.get("order"), + True, + ) + return _attrs(these=cls_dict, **attributes_arguments)(type_) # These are required by within this module so we define them here and merely -# import into .validators. +# import into .validators / .converters. @attrs(slots=True, hash=True) @@ -2070,8 +2981,7 @@ def and_(*validators): When called on a value, it runs all wrapped validators. - :param validators: Arbitrary number of validators. - :type validators: callables + :param callables validators: Arbitrary number of validators. .. versionadded:: 17.1.0 """ @@ -2084,3 +2994,59 @@ def and_(*validators): ) return _AndValidator(tuple(vals)) + + +def pipe(*converters): + """ + A converter that composes multiple converters into one. + + When called on a value, it runs all wrapped converters, returning the + *last* value. + + Type annotations will be inferred from the wrapped converters', if + they have any. + + :param callables converters: Arbitrary number of converters. + + .. versionadded:: 20.1.0 + """ + + def pipe_converter(val): + for converter in converters: + val = converter(val) + + return val + + if not PY2: + if not converters: + # If the converter list is empty, pipe_converter is the identity. + A = typing.TypeVar("A") + pipe_converter.__annotations__ = {"val": A, "return": A} + else: + # Get parameter type. + sig = None + try: + sig = inspect.signature(converters[0]) + except (ValueError, TypeError): # inspect failed + pass + if sig: + params = list(sig.parameters.values()) + if ( + params + and params[0].annotation is not inspect.Parameter.empty + ): + pipe_converter.__annotations__["val"] = params[ + 0 + ].annotation + # Get return type. + sig = None + try: + sig = inspect.signature(converters[-1]) + except (ValueError, TypeError): # inspect failed + pass + if sig and sig.return_annotation is not inspect.Signature().empty: + pipe_converter.__annotations__[ + "return" + ] = sig.return_annotation + + return pipe_converter diff --git a/pipenv/vendor/attr/_next_gen.py b/pipenv/vendor/attr/_next_gen.py new file mode 100644 index 00000000..e98f4671 --- /dev/null +++ b/pipenv/vendor/attr/_next_gen.py @@ -0,0 +1,158 @@ +""" +These are Python 3.6+-only and keyword-only APIs that call `attr.s` and +`attr.ib` with different default values. +""" + +from functools import partial + +from pipenv.vendor.attr.exceptions import UnannotatedAttributeError + +from . import setters +from ._make import NOTHING, _frozen_setattrs, attrib, attrs + + +def define( + maybe_cls=None, + *, + these=None, + repr=None, + hash=None, + init=None, + slots=True, + frozen=False, + weakref_slot=True, + str=False, + auto_attribs=None, + kw_only=False, + cache_hash=False, + auto_exc=True, + eq=None, + order=False, + auto_detect=True, + getstate_setstate=None, + on_setattr=None, + field_transformer=None, +): + r""" + The only behavioral differences are the handling of the *auto_attribs* + option: + + :param Optional[bool] auto_attribs: If set to `True` or `False`, it behaves + exactly like `attr.s`. If left `None`, `attr.s` will try to guess: + + 1. If any attributes are annotated and no unannotated `attr.ib`\ s + are found, it assumes *auto_attribs=True*. + 2. Otherwise it assumes *auto_attribs=False* and tries to collect + `attr.ib`\ s. + + and that mutable classes (``frozen=False``) validate on ``__setattr__``. + + .. versionadded:: 20.1.0 + """ + + def do_it(cls, auto_attribs): + return attrs( + maybe_cls=cls, + these=these, + repr=repr, + hash=hash, + init=init, + slots=slots, + frozen=frozen, + weakref_slot=weakref_slot, + str=str, + auto_attribs=auto_attribs, + kw_only=kw_only, + cache_hash=cache_hash, + auto_exc=auto_exc, + eq=eq, + order=order, + auto_detect=auto_detect, + collect_by_mro=True, + getstate_setstate=getstate_setstate, + on_setattr=on_setattr, + field_transformer=field_transformer, + ) + + def wrap(cls): + """ + Making this a wrapper ensures this code runs during class creation. + + We also ensure that frozen-ness of classes is inherited. + """ + nonlocal frozen, on_setattr + + had_on_setattr = on_setattr not in (None, setters.NO_OP) + + # By default, mutable classes validate on setattr. + if frozen is False and on_setattr is None: + on_setattr = setters.validate + + # However, if we subclass a frozen class, we inherit the immutability + # and disable on_setattr. + for base_cls in cls.__bases__: + if base_cls.__setattr__ is _frozen_setattrs: + if had_on_setattr: + raise ValueError( + "Frozen classes can't use on_setattr " + "(frozen-ness was inherited)." + ) + + on_setattr = setters.NO_OP + break + + if auto_attribs is not None: + return do_it(cls, auto_attribs) + + try: + return do_it(cls, True) + except UnannotatedAttributeError: + return do_it(cls, False) + + # maybe_cls's type depends on the usage of the decorator. It's a class + # if it's used as `@attrs` but ``None`` if used as `@attrs()`. + if maybe_cls is None: + return wrap + else: + return wrap(maybe_cls) + + +mutable = define +frozen = partial(define, frozen=True, on_setattr=None) + + +def field( + *, + default=NOTHING, + validator=None, + repr=True, + hash=None, + init=True, + metadata=None, + converter=None, + factory=None, + kw_only=False, + eq=None, + order=None, + on_setattr=None, +): + """ + Identical to `attr.ib`, except keyword-only and with some arguments + removed. + + .. versionadded:: 20.1.0 + """ + return attrib( + default=default, + validator=validator, + repr=repr, + hash=hash, + init=init, + metadata=metadata, + converter=converter, + factory=factory, + kw_only=kw_only, + eq=eq, + order=order, + on_setattr=on_setattr, + ) diff --git a/pipenv/vendor/attr/_version_info.py b/pipenv/vendor/attr/_version_info.py new file mode 100644 index 00000000..014e78a1 --- /dev/null +++ b/pipenv/vendor/attr/_version_info.py @@ -0,0 +1,85 @@ +from __future__ import absolute_import, division, print_function + +from functools import total_ordering + +from ._funcs import astuple +from ._make import attrib, attrs + + +@total_ordering +@attrs(eq=False, order=False, slots=True, frozen=True) +class VersionInfo(object): + """ + A version object that can be compared to tuple of length 1--4: + + >>> attr.VersionInfo(19, 1, 0, "final") <= (19, 2) + True + >>> attr.VersionInfo(19, 1, 0, "final") < (19, 1, 1) + True + >>> vi = attr.VersionInfo(19, 2, 0, "final") + >>> vi < (19, 1, 1) + False + >>> vi < (19,) + False + >>> vi == (19, 2,) + True + >>> vi == (19, 2, 1) + False + + .. versionadded:: 19.2 + """ + + year = attrib(type=int) + minor = attrib(type=int) + micro = attrib(type=int) + releaselevel = attrib(type=str) + + @classmethod + def _from_version_string(cls, s): + """ + Parse *s* and return a _VersionInfo. + """ + v = s.split(".") + if len(v) == 3: + v.append("final") + + return cls( + year=int(v[0]), minor=int(v[1]), micro=int(v[2]), releaselevel=v[3] + ) + + def _ensure_tuple(self, other): + """ + Ensure *other* is a tuple of a valid length. + + Returns a possibly transformed *other* and ourselves as a tuple of + the same length as *other*. + """ + + if self.__class__ is other.__class__: + other = astuple(other) + + if not isinstance(other, tuple): + raise NotImplementedError + + if not (1 <= len(other) <= 4): + raise NotImplementedError + + return astuple(self)[: len(other)], other + + def __eq__(self, other): + try: + us, them = self._ensure_tuple(other) + except NotImplementedError: + return NotImplemented + + return us == them + + def __lt__(self, other): + try: + us, them = self._ensure_tuple(other) + except NotImplementedError: + return NotImplemented + + # Since alphabetically "dev0" < "final" < "post1" < "post2", we don't + # have to do anything special with releaselevel for now. + return us < them diff --git a/pipenv/vendor/attr/_version_info.pyi b/pipenv/vendor/attr/_version_info.pyi new file mode 100644 index 00000000..45ced086 --- /dev/null +++ b/pipenv/vendor/attr/_version_info.pyi @@ -0,0 +1,9 @@ +class VersionInfo: + @property + def year(self) -> int: ... + @property + def minor(self) -> int: ... + @property + def micro(self) -> int: ... + @property + def releaselevel(self) -> str: ... diff --git a/pipenv/vendor/attr/converters.py b/pipenv/vendor/attr/converters.py index 37c4a07a..2777db6d 100644 --- a/pipenv/vendor/attr/converters.py +++ b/pipenv/vendor/attr/converters.py @@ -4,7 +4,20 @@ Commonly useful converters. from __future__ import absolute_import, division, print_function -from ._make import NOTHING, Factory +from ._compat import PY2 +from ._make import NOTHING, Factory, pipe + + +if not PY2: + import inspect + import typing + + +__all__ = [ + "pipe", + "optional", + "default_if_none", +] def optional(converter): @@ -12,6 +25,9 @@ def optional(converter): A converter that allows an attribute to be optional. An optional attribute is one which can be set to ``None``. + Type annotations will be inferred from the wrapped converter's, if it + has any. + :param callable converter: the converter that is used for non-``None`` values. @@ -23,6 +39,23 @@ def optional(converter): return None return converter(val) + if not PY2: + sig = None + try: + sig = inspect.signature(converter) + except (ValueError, TypeError): # inspect failed + pass + if sig: + params = list(sig.parameters.values()) + if params and params[0].annotation is not inspect.Parameter.empty: + optional_converter.__annotations__["val"] = typing.Optional[ + params[0].annotation + ] + if sig.return_annotation is not inspect.Signature.empty: + optional_converter.__annotations__["return"] = typing.Optional[ + sig.return_annotation + ] + return optional_converter @@ -32,14 +65,14 @@ def default_if_none(default=NOTHING, factory=None): result of *factory*. :param default: Value to be used if ``None`` is passed. Passing an instance - of :class:`attr.Factory` is supported, however the ``takes_self`` option + of `attr.Factory` is supported, however the ``takes_self`` option is *not*. - :param callable factory: A callable that takes not parameters whose result + :param callable factory: A callable that takes no parameters whose result is used if ``None`` is passed. :raises TypeError: If **neither** *default* or *factory* is passed. :raises TypeError: If **both** *default* and *factory* are passed. - :raises ValueError: If an instance of :class:`attr.Factory` is passed with + :raises ValueError: If an instance of `attr.Factory` is passed with ``takes_self=True``. .. versionadded:: 18.2.0 diff --git a/pipenv/vendor/attr/converters.pyi b/pipenv/vendor/attr/converters.pyi index 63b2a386..84a57590 100644 --- a/pipenv/vendor/attr/converters.pyi +++ b/pipenv/vendor/attr/converters.pyi @@ -1,12 +1,13 @@ -from typing import TypeVar, Optional, Callable, overload +from typing import Callable, Optional, TypeVar, overload + from . import _ConverterType + _T = TypeVar("_T") -def optional( - converter: _ConverterType[_T] -) -> _ConverterType[Optional[_T]]: ... +def pipe(*validators: _ConverterType) -> _ConverterType: ... +def optional(converter: _ConverterType) -> _ConverterType: ... @overload -def default_if_none(default: _T) -> _ConverterType[_T]: ... +def default_if_none(default: _T) -> _ConverterType: ... @overload -def default_if_none(*, factory: Callable[[], _T]) -> _ConverterType[_T]: ... +def default_if_none(*, factory: Callable[[], _T]) -> _ConverterType: ... diff --git a/pipenv/vendor/attr/exceptions.py b/pipenv/vendor/attr/exceptions.py index b12e41e9..f6f9861b 100644 --- a/pipenv/vendor/attr/exceptions.py +++ b/pipenv/vendor/attr/exceptions.py @@ -1,20 +1,37 @@ from __future__ import absolute_import, division, print_function -class FrozenInstanceError(AttributeError): +class FrozenError(AttributeError): """ - A frozen/immutable instance has been attempted to be modified. + A frozen/immutable instance or attribute have been attempted to be + modified. It mirrors the behavior of ``namedtuples`` by using the same error message - and subclassing :exc:`AttributeError`. + and subclassing `AttributeError`. - .. versionadded:: 16.1.0 + .. versionadded:: 20.1.0 """ msg = "can't set attribute" args = [msg] +class FrozenInstanceError(FrozenError): + """ + A frozen instance has been attempted to be modified. + + .. versionadded:: 16.1.0 + """ + + +class FrozenAttributeError(FrozenError): + """ + A frozen attribute has been attempted to be modified. + + .. versionadded:: 20.1.0 + """ + + class AttrsAttributeNotFoundError(ValueError): """ An ``attrs`` function couldn't find an attribute that the user asked for. @@ -51,7 +68,25 @@ class UnannotatedAttributeError(RuntimeError): class PythonTooOldError(RuntimeError): """ - An ``attrs`` feature requiring a more recent python version has been used. + It was attempted to use an ``attrs`` feature that requires a newer Python + version. .. versionadded:: 18.2.0 """ + + +class NotCallableError(TypeError): + """ + A ``attr.ib()`` requiring a callable has been set with a value + that is not callable. + + .. versionadded:: 19.2.0 + """ + + def __init__(self, msg, value): + super(TypeError, self).__init__(msg, value) + self.msg = msg + self.value = value + + def __str__(self): + return str(self.msg) diff --git a/pipenv/vendor/attr/exceptions.pyi b/pipenv/vendor/attr/exceptions.pyi index 48fffcc1..a800fb26 100644 --- a/pipenv/vendor/attr/exceptions.pyi +++ b/pipenv/vendor/attr/exceptions.pyi @@ -1,7 +1,18 @@ -class FrozenInstanceError(AttributeError): +from typing import Any + + +class FrozenError(AttributeError): msg: str = ... +class FrozenInstanceError(FrozenError): ... +class FrozenAttributeError(FrozenError): ... class AttrsAttributeNotFoundError(ValueError): ... class NotAnAttrsClassError(ValueError): ... class DefaultAlreadySetError(RuntimeError): ... class UnannotatedAttributeError(RuntimeError): ... +class PythonTooOldError(RuntimeError): ... + +class NotCallableError(TypeError): + msg: str = ... + value: Any = ... + def __init__(self, msg: str, value: Any) -> None: ... diff --git a/pipenv/vendor/attr/filters.py b/pipenv/vendor/attr/filters.py index f1c69b8b..dc47e8fa 100644 --- a/pipenv/vendor/attr/filters.py +++ b/pipenv/vendor/attr/filters.py @@ -1,5 +1,5 @@ """ -Commonly useful filters for :func:`attr.asdict`. +Commonly useful filters for `attr.asdict`. """ from __future__ import absolute_import, division, print_function @@ -23,9 +23,9 @@ def include(*what): Whitelist *what*. :param what: What to whitelist. - :type what: :class:`list` of :class:`type` or :class:`attr.Attribute`\\ s + :type what: `list` of `type` or `attr.Attribute`\\ s - :rtype: :class:`callable` + :rtype: `callable` """ cls, attrs = _split_what(what) @@ -40,9 +40,9 @@ def exclude(*what): Blacklist *what*. :param what: What to blacklist. - :type what: :class:`list` of classes or :class:`attr.Attribute`\\ s. + :type what: `list` of classes or `attr.Attribute`\\ s. - :rtype: :class:`callable` + :rtype: `callable` """ cls, attrs = _split_what(what) diff --git a/pipenv/vendor/attr/filters.pyi b/pipenv/vendor/attr/filters.pyi index 68368fe2..f7b63f1b 100644 --- a/pipenv/vendor/attr/filters.pyi +++ b/pipenv/vendor/attr/filters.pyi @@ -1,5 +1,7 @@ -from typing import Union, Any +from typing import Any, Union + from . import Attribute, _FilterType + def include(*what: Union[type, Attribute[Any]]) -> _FilterType[Any]: ... def exclude(*what: Union[type, Attribute[Any]]) -> _FilterType[Any]: ... diff --git a/pipenv/vendor/attr/setters.py b/pipenv/vendor/attr/setters.py new file mode 100644 index 00000000..240014b3 --- /dev/null +++ b/pipenv/vendor/attr/setters.py @@ -0,0 +1,77 @@ +""" +Commonly used hooks for on_setattr. +""" + +from __future__ import absolute_import, division, print_function + +from . import _config +from .exceptions import FrozenAttributeError + + +def pipe(*setters): + """ + Run all *setters* and return the return value of the last one. + + .. versionadded:: 20.1.0 + """ + + def wrapped_pipe(instance, attrib, new_value): + rv = new_value + + for setter in setters: + rv = setter(instance, attrib, rv) + + return rv + + return wrapped_pipe + + +def frozen(_, __, ___): + """ + Prevent an attribute to be modified. + + .. versionadded:: 20.1.0 + """ + raise FrozenAttributeError() + + +def validate(instance, attrib, new_value): + """ + Run *attrib*'s validator on *new_value* if it has one. + + .. versionadded:: 20.1.0 + """ + if _config._run_validators is False: + return new_value + + v = attrib.validator + if not v: + return new_value + + v(instance, attrib, new_value) + + return new_value + + +def convert(instance, attrib, new_value): + """ + Run *attrib*'s converter -- if it has one -- on *new_value* and return the + result. + + .. versionadded:: 20.1.0 + """ + c = attrib.converter + if c: + return c(new_value) + + return new_value + + +NO_OP = object() +""" +Sentinel for disabling class-wide *on_setattr* hooks for certain attributes. + +Does not work in `pipe` or within lists. + +.. versionadded:: 20.1.0 +""" diff --git a/pipenv/vendor/attr/setters.pyi b/pipenv/vendor/attr/setters.pyi new file mode 100644 index 00000000..a921e07d --- /dev/null +++ b/pipenv/vendor/attr/setters.pyi @@ -0,0 +1,20 @@ +from typing import Any, NewType, NoReturn, TypeVar, cast + +from . import Attribute, _OnSetAttrType + + +_T = TypeVar("_T") + +def frozen( + instance: Any, attribute: Attribute[Any], new_value: Any +) -> NoReturn: ... +def pipe(*setters: _OnSetAttrType) -> _OnSetAttrType: ... +def validate(instance: Any, attribute: Attribute[_T], new_value: _T) -> _T: ... + +# convert is allowed to return Any, because they can be chained using pipe. +def convert( + instance: Any, attribute: Attribute[Any], new_value: Any +) -> Any: ... + +_NoOpType = NewType("_NoOpType", object) +NO_OP: _NoOpType diff --git a/pipenv/vendor/attr/validators.py b/pipenv/vendor/attr/validators.py index 7fc4446b..b9a73054 100644 --- a/pipenv/vendor/attr/validators.py +++ b/pipenv/vendor/attr/validators.py @@ -4,10 +4,23 @@ Commonly useful validators. from __future__ import absolute_import, division, print_function +import re + from ._make import _AndValidator, and_, attrib, attrs +from .exceptions import NotCallableError -__all__ = ["and_", "in_", "instance_of", "optional", "provides"] +__all__ = [ + "and_", + "deep_iterable", + "deep_mapping", + "in_", + "instance_of", + "is_callable", + "matches_re", + "optional", + "provides", +] @attrs(repr=False, slots=True, hash=True) @@ -40,20 +53,92 @@ class _InstanceOfValidator(object): def instance_of(type): """ - A validator that raises a :exc:`TypeError` if the initializer is called + A validator that raises a `TypeError` if the initializer is called with a wrong type for this particular attribute (checks are performed using - :func:`isinstance` therefore it's also valid to pass a tuple of types). + `isinstance` therefore it's also valid to pass a tuple of types). :param type: The type to check for. :type type: type or tuple of types :raises TypeError: With a human readable error message, the attribute - (of type :class:`attr.Attribute`), the expected type, and the value it + (of type `attr.Attribute`), the expected type, and the value it got. """ return _InstanceOfValidator(type) +@attrs(repr=False, frozen=True, slots=True) +class _MatchesReValidator(object): + regex = attrib() + flags = attrib() + match_func = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not self.match_func(value): + raise ValueError( + "'{name}' must match regex {regex!r}" + " ({value!r} doesn't)".format( + name=attr.name, regex=self.regex.pattern, value=value + ), + attr, + self.regex, + value, + ) + + def __repr__(self): + return "<matches_re validator for pattern {regex!r}>".format( + regex=self.regex + ) + + +def matches_re(regex, flags=0, func=None): + r""" + A validator that raises `ValueError` if the initializer is called + with a string that doesn't match *regex*. + + :param str regex: a regex string to match against + :param int flags: flags that will be passed to the underlying re function + (default 0) + :param callable func: which underlying `re` function to call (options + are `re.fullmatch`, `re.search`, `re.match`, default + is ``None`` which means either `re.fullmatch` or an emulation of + it on Python 2). For performance reasons, they won't be used directly + but on a pre-`re.compile`\ ed pattern. + + .. versionadded:: 19.2.0 + """ + fullmatch = getattr(re, "fullmatch", None) + valid_funcs = (fullmatch, None, re.search, re.match) + if func not in valid_funcs: + raise ValueError( + "'func' must be one of %s." + % ( + ", ".join( + sorted( + e and e.__name__ or "None" for e in set(valid_funcs) + ) + ), + ) + ) + + pattern = re.compile(regex, flags) + if func is re.match: + match_func = pattern.match + elif func is re.search: + match_func = pattern.search + else: + if fullmatch: + match_func = pattern.fullmatch + else: + pattern = re.compile(r"(?:{})\Z".format(regex), flags) + match_func = pattern.match + + return _MatchesReValidator(pattern, flags, match_func) + + @attrs(repr=False, slots=True, hash=True) class _ProvidesValidator(object): interface = attrib() @@ -81,15 +166,16 @@ class _ProvidesValidator(object): def provides(interface): """ - A validator that raises a :exc:`TypeError` if the initializer is called + A validator that raises a `TypeError` if the initializer is called with an object that does not provide the requested *interface* (checks are performed using ``interface.providedBy(value)`` (see `zope.interface <https://zopeinterface.readthedocs.io/en/latest/>`_). - :param zope.interface.Interface interface: The interface to check for. + :param interface: The interface to check for. + :type interface: ``zope.interface.Interface`` :raises TypeError: With a human readable error message, the attribute - (of type :class:`attr.Attribute`), the expected interface, and the + (of type `attr.Attribute`), the expected interface, and the value it got. """ return _ProvidesValidator(interface) @@ -119,7 +205,7 @@ def optional(validator): :param validator: A validator (or a list of validators) that is used for non-``None`` values. - :type validator: callable or :class:`list` of callables. + :type validator: callable or `list` of callables. .. versionadded:: 15.1.0 .. versionchanged:: 17.1.0 *validator* can be a list of validators. @@ -154,15 +240,15 @@ class _InValidator(object): def in_(options): """ - A validator that raises a :exc:`ValueError` if the initializer is called + A validator that raises a `ValueError` if the initializer is called with a value that does not belong in the options provided. The check is performed using ``value in options``. :param options: Allowed options. - :type options: list, tuple, :class:`enum.Enum`, ... + :type options: list, tuple, `enum.Enum`, ... :raises ValueError: With a human readable error message, the attribute (of - type :class:`attr.Attribute`), the expected options, and the value it + type `attr.Attribute`), the expected options, and the value it got. .. versionadded:: 17.1.0 @@ -177,7 +263,16 @@ class _IsCallableValidator(object): We use a callable class to be able to change the ``__repr__``. """ if not callable(value): - raise TypeError("'{name}' must be callable".format(name=attr.name)) + message = ( + "'{name}' must be callable " + "(got {value!r} that is a {actual!r})." + ) + raise NotCallableError( + msg=message.format( + name=attr.name, value=value, actual=value.__class__ + ), + value=value, + ) def __repr__(self): return "<is_callable validator>" @@ -185,13 +280,15 @@ class _IsCallableValidator(object): def is_callable(): """ - A validator that raises a :class:`TypeError` if the initializer is called - with a value for this particular attribute that is not callable. + A validator that raises a `attr.exceptions.NotCallableError` if the + initializer is called with a value for this particular attribute + that is not callable. .. versionadded:: 19.1.0 - :raises TypeError: With a human readable error message containing the - attribute (of type :class:`attr.Attribute`) name. + :raises `attr.exceptions.NotCallableError`: With a human readable error + message containing the attribute (`attr.Attribute`) name, + and the value it got. """ return _IsCallableValidator() diff --git a/pipenv/vendor/attr/validators.pyi b/pipenv/vendor/attr/validators.pyi index 01af0684..fe92aac4 100644 --- a/pipenv/vendor/attr/validators.pyi +++ b/pipenv/vendor/attr/validators.pyi @@ -1,24 +1,68 @@ -from typing import Container, List, Union, TypeVar, Type, Any, Optional, Tuple +from typing import ( + Any, + AnyStr, + Callable, + Container, + Iterable, + List, + Mapping, + Match, + Optional, + Tuple, + Type, + TypeVar, + Union, + overload, +) + from . import _ValidatorType -_T = TypeVar("_T") +_T = TypeVar("_T") +_T1 = TypeVar("_T1") +_T2 = TypeVar("_T2") +_T3 = TypeVar("_T3") +_I = TypeVar("_I", bound=Iterable) +_K = TypeVar("_K") +_V = TypeVar("_V") +_M = TypeVar("_M", bound=Mapping) + +# To be more precise on instance_of use some overloads. +# If there are more than 3 items in the tuple then we fall back to Any +@overload +def instance_of(type: Type[_T]) -> _ValidatorType[_T]: ... +@overload +def instance_of(type: Tuple[Type[_T]]) -> _ValidatorType[_T]: ... +@overload def instance_of( - type: Union[Tuple[Type[_T], ...], Type[_T]] -) -> _ValidatorType[_T]: ... + type: Tuple[Type[_T1], Type[_T2]] +) -> _ValidatorType[Union[_T1, _T2]]: ... +@overload +def instance_of( + type: Tuple[Type[_T1], Type[_T2], Type[_T3]] +) -> _ValidatorType[Union[_T1, _T2, _T3]]: ... +@overload +def instance_of(type: Tuple[type, ...]) -> _ValidatorType[Any]: ... def provides(interface: Any) -> _ValidatorType[Any]: ... def optional( validator: Union[_ValidatorType[_T], List[_ValidatorType[_T]]] ) -> _ValidatorType[Optional[_T]]: ... def in_(options: Container[_T]) -> _ValidatorType[_T]: ... def and_(*validators: _ValidatorType[_T]) -> _ValidatorType[_T]: ... +def matches_re( + regex: AnyStr, + flags: int = ..., + func: Optional[ + Callable[[AnyStr, AnyStr, int], Optional[Match[AnyStr]]] + ] = ..., +) -> _ValidatorType[AnyStr]: ... def deep_iterable( member_validator: _ValidatorType[_T], - iterable_validator: Optional[_ValidatorType[_T]], -) -> _ValidatorType[_T]: ... + iterable_validator: Optional[_ValidatorType[_I]] = ..., +) -> _ValidatorType[_I]: ... def deep_mapping( - key_validator: _ValidatorType[_T], - value_validator: _ValidatorType[_T], - mapping_validator: Optional[_ValidatorType[_T]], -) -> _ValidatorType[_T]: ... + key_validator: _ValidatorType[_K], + value_validator: _ValidatorType[_V], + mapping_validator: Optional[_ValidatorType[_M]] = ..., +) -> _ValidatorType[_M]: ... def is_callable() -> _ValidatorType[_T]: ... diff --git a/pipenv/vendor/backports/__init__.py b/pipenv/vendor/backports/__init__.py deleted file mode 100644 index e449e521..00000000 --- a/pipenv/vendor/backports/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -__path__ = __import__('pkgutil').extend_path(__path__, __name__) -from . import weakref -from . import shutil_get_terminal_size -from . import enum -from . import functools_lru_cache diff --git a/pipenv/vendor/backports/enum/LICENSE b/pipenv/vendor/backports/enum/LICENSE deleted file mode 100644 index 9003b885..00000000 --- a/pipenv/vendor/backports/enum/LICENSE +++ /dev/null @@ -1,32 +0,0 @@ -Copyright (c) 2013, Ethan Furman. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - - Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials - provided with the distribution. - - Neither the name Ethan Furman nor the names of any - contributors may be used to endorse or promote products - derived from this software without specific prior written - permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/pipenv/vendor/backports/enum/README b/pipenv/vendor/backports/enum/README deleted file mode 100644 index aa2333d8..00000000 --- a/pipenv/vendor/backports/enum/README +++ /dev/null @@ -1,3 +0,0 @@ -enum34 is the new Python stdlib enum module available in Python 3.4 -backported for previous versions of Python from 2.4 to 3.3. -tested on 2.6, 2.7, and 3.3+ diff --git a/pipenv/vendor/backports/enum/__init__.py b/pipenv/vendor/backports/enum/__init__.py deleted file mode 100644 index d6ffb3a4..00000000 --- a/pipenv/vendor/backports/enum/__init__.py +++ /dev/null @@ -1,837 +0,0 @@ -"""Python Enumerations""" - -import sys as _sys - -__all__ = ['Enum', 'IntEnum', 'unique'] - -version = 1, 1, 6 - -pyver = float('%s.%s' % _sys.version_info[:2]) - -try: - any -except NameError: - def any(iterable): - for element in iterable: - if element: - return True - return False - -try: - from collections import OrderedDict -except ImportError: - OrderedDict = None - -try: - basestring -except NameError: - # In Python 2 basestring is the ancestor of both str and unicode - # in Python 3 it's just str, but was missing in 3.1 - basestring = str - -try: - unicode -except NameError: - # In Python 3 unicode no longer exists (it's just str) - unicode = str - -class _RouteClassAttributeToGetattr(object): - """Route attribute access on a class to __getattr__. - - This is a descriptor, used to define attributes that act differently when - accessed through an instance and through a class. Instance access remains - normal, but access to an attribute through a class will be routed to the - class's __getattr__ method; this is done by raising AttributeError. - - """ - def __init__(self, fget=None): - self.fget = fget - - def __get__(self, instance, ownerclass=None): - if instance is None: - raise AttributeError() - return self.fget(instance) - - def __set__(self, instance, value): - raise AttributeError("can't set attribute") - - def __delete__(self, instance): - raise AttributeError("can't delete attribute") - - -def _is_descriptor(obj): - """Returns True if obj is a descriptor, False otherwise.""" - return ( - hasattr(obj, '__get__') or - hasattr(obj, '__set__') or - hasattr(obj, '__delete__')) - - -def _is_dunder(name): - """Returns True if a __dunder__ name, False otherwise.""" - return (name[:2] == name[-2:] == '__' and - name[2:3] != '_' and - name[-3:-2] != '_' and - len(name) > 4) - - -def _is_sunder(name): - """Returns True if a _sunder_ name, False otherwise.""" - return (name[0] == name[-1] == '_' and - name[1:2] != '_' and - name[-2:-1] != '_' and - len(name) > 2) - - -def _make_class_unpicklable(cls): - """Make the given class un-picklable.""" - def _break_on_call_reduce(self, protocol=None): - raise TypeError('%r cannot be pickled' % self) - cls.__reduce_ex__ = _break_on_call_reduce - cls.__module__ = '<unknown>' - - -class _EnumDict(dict): - """Track enum member order and ensure member names are not reused. - - EnumMeta will use the names found in self._member_names as the - enumeration member names. - - """ - def __init__(self): - super(_EnumDict, self).__init__() - self._member_names = [] - - def __setitem__(self, key, value): - """Changes anything not dundered or not a descriptor. - - If a descriptor is added with the same name as an enum member, the name - is removed from _member_names (this may leave a hole in the numerical - sequence of values). - - If an enum member name is used twice, an error is raised; duplicate - values are not checked for. - - Single underscore (sunder) names are reserved. - - Note: in 3.x __order__ is simply discarded as a not necessary piece - leftover from 2.x - - """ - if pyver >= 3.0 and key in ('_order_', '__order__'): - return - elif key == '__order__': - key = '_order_' - if _is_sunder(key): - if key != '_order_': - raise ValueError('_names_ are reserved for future Enum use') - elif _is_dunder(key): - pass - elif key in self._member_names: - # descriptor overwriting an enum? - raise TypeError('Attempted to reuse key: %r' % key) - elif not _is_descriptor(value): - if key in self: - # enum overwriting a descriptor? - raise TypeError('Key already defined as: %r' % self[key]) - self._member_names.append(key) - super(_EnumDict, self).__setitem__(key, value) - - -# Dummy value for Enum as EnumMeta explicity checks for it, but of course until -# EnumMeta finishes running the first time the Enum class doesn't exist. This -# is also why there are checks in EnumMeta like `if Enum is not None` -Enum = None - - -class EnumMeta(type): - """Metaclass for Enum""" - @classmethod - def __prepare__(metacls, cls, bases): - return _EnumDict() - - def __new__(metacls, cls, bases, classdict): - # an Enum class is final once enumeration items have been defined; it - # cannot be mixed with other types (int, float, etc.) if it has an - # inherited __new__ unless a new __new__ is defined (or the resulting - # class will fail). - if type(classdict) is dict: - original_dict = classdict - classdict = _EnumDict() - for k, v in original_dict.items(): - classdict[k] = v - - member_type, first_enum = metacls._get_mixins_(bases) - __new__, save_new, use_args = metacls._find_new_(classdict, member_type, - first_enum) - # save enum items into separate mapping so they don't get baked into - # the new class - members = dict((k, classdict[k]) for k in classdict._member_names) - for name in classdict._member_names: - del classdict[name] - - # py2 support for definition order - _order_ = classdict.get('_order_') - if _order_ is None: - if pyver < 3.0: - try: - _order_ = [name for (name, value) in sorted(members.items(), key=lambda item: item[1])] - except TypeError: - _order_ = [name for name in sorted(members.keys())] - else: - _order_ = classdict._member_names - else: - del classdict['_order_'] - if pyver < 3.0: - _order_ = _order_.replace(',', ' ').split() - aliases = [name for name in members if name not in _order_] - _order_ += aliases - - # check for illegal enum names (any others?) - invalid_names = set(members) & set(['mro']) - if invalid_names: - raise ValueError('Invalid enum member name(s): %s' % ( - ', '.join(invalid_names), )) - - # save attributes from super classes so we know if we can take - # the shortcut of storing members in the class dict - base_attributes = set([a for b in bases for a in b.__dict__]) - # create our new Enum type - enum_class = super(EnumMeta, metacls).__new__(metacls, cls, bases, classdict) - enum_class._member_names_ = [] # names in random order - if OrderedDict is not None: - enum_class._member_map_ = OrderedDict() - else: - enum_class._member_map_ = {} # name->value map - enum_class._member_type_ = member_type - - # Reverse value->name map for hashable values. - enum_class._value2member_map_ = {} - - # instantiate them, checking for duplicates as we go - # we instantiate first instead of checking for duplicates first in case - # a custom __new__ is doing something funky with the values -- such as - # auto-numbering ;) - if __new__ is None: - __new__ = enum_class.__new__ - for member_name in _order_: - value = members[member_name] - if not isinstance(value, tuple): - args = (value, ) - else: - args = value - if member_type is tuple: # special case for tuple enums - args = (args, ) # wrap it one more time - if not use_args or not args: - enum_member = __new__(enum_class) - if not hasattr(enum_member, '_value_'): - enum_member._value_ = value - else: - enum_member = __new__(enum_class, *args) - if not hasattr(enum_member, '_value_'): - enum_member._value_ = member_type(*args) - value = enum_member._value_ - enum_member._name_ = member_name - enum_member.__objclass__ = enum_class - enum_member.__init__(*args) - # If another member with the same value was already defined, the - # new member becomes an alias to the existing one. - for name, canonical_member in enum_class._member_map_.items(): - if canonical_member.value == enum_member._value_: - enum_member = canonical_member - break - else: - # Aliases don't appear in member names (only in __members__). - enum_class._member_names_.append(member_name) - # performance boost for any member that would not shadow - # a DynamicClassAttribute (aka _RouteClassAttributeToGetattr) - if member_name not in base_attributes: - setattr(enum_class, member_name, enum_member) - # now add to _member_map_ - enum_class._member_map_[member_name] = enum_member - try: - # This may fail if value is not hashable. We can't add the value - # to the map, and by-value lookups for this value will be - # linear. - enum_class._value2member_map_[value] = enum_member - except TypeError: - pass - - - # If a custom type is mixed into the Enum, and it does not know how - # to pickle itself, pickle.dumps will succeed but pickle.loads will - # fail. Rather than have the error show up later and possibly far - # from the source, sabotage the pickle protocol for this class so - # that pickle.dumps also fails. - # - # However, if the new class implements its own __reduce_ex__, do not - # sabotage -- it's on them to make sure it works correctly. We use - # __reduce_ex__ instead of any of the others as it is preferred by - # pickle over __reduce__, and it handles all pickle protocols. - unpicklable = False - if '__reduce_ex__' not in classdict: - if member_type is not object: - methods = ('__getnewargs_ex__', '__getnewargs__', - '__reduce_ex__', '__reduce__') - if not any(m in member_type.__dict__ for m in methods): - _make_class_unpicklable(enum_class) - unpicklable = True - - - # double check that repr and friends are not the mixin's or various - # things break (such as pickle) - for name in ('__repr__', '__str__', '__format__', '__reduce_ex__'): - class_method = getattr(enum_class, name) - obj_method = getattr(member_type, name, None) - enum_method = getattr(first_enum, name, None) - if name not in classdict and class_method is not enum_method: - if name == '__reduce_ex__' and unpicklable: - continue - setattr(enum_class, name, enum_method) - - # method resolution and int's are not playing nice - # Python's less than 2.6 use __cmp__ - - if pyver < 2.6: - - if issubclass(enum_class, int): - setattr(enum_class, '__cmp__', getattr(int, '__cmp__')) - - elif pyver < 3.0: - - if issubclass(enum_class, int): - for method in ( - '__le__', - '__lt__', - '__gt__', - '__ge__', - '__eq__', - '__ne__', - '__hash__', - ): - setattr(enum_class, method, getattr(int, method)) - - # replace any other __new__ with our own (as long as Enum is not None, - # anyway) -- again, this is to support pickle - if Enum is not None: - # if the user defined their own __new__, save it before it gets - # clobbered in case they subclass later - if save_new: - setattr(enum_class, '__member_new__', enum_class.__dict__['__new__']) - setattr(enum_class, '__new__', Enum.__dict__['__new__']) - return enum_class - - def __bool__(cls): - """ - classes/types should always be True. - """ - return True - - def __call__(cls, value, names=None, module=None, type=None, start=1): - """Either returns an existing member, or creates a new enum class. - - This method is used both when an enum class is given a value to match - to an enumeration member (i.e. Color(3)) and for the functional API - (i.e. Color = Enum('Color', names='red green blue')). - - When used for the functional API: `module`, if set, will be stored in - the new class' __module__ attribute; `type`, if set, will be mixed in - as the first base class. - - Note: if `module` is not set this routine will attempt to discover the - calling module by walking the frame stack; if this is unsuccessful - the resulting class will not be pickleable. - - """ - if names is None: # simple value lookup - return cls.__new__(cls, value) - # otherwise, functional API: we're creating a new Enum type - return cls._create_(value, names, module=module, type=type, start=start) - - def __contains__(cls, member): - return isinstance(member, cls) and member.name in cls._member_map_ - - def __delattr__(cls, attr): - # nicer error message when someone tries to delete an attribute - # (see issue19025). - if attr in cls._member_map_: - raise AttributeError( - "%s: cannot delete Enum member." % cls.__name__) - super(EnumMeta, cls).__delattr__(attr) - - def __dir__(self): - return (['__class__', '__doc__', '__members__', '__module__'] + - self._member_names_) - - @property - def __members__(cls): - """Returns a mapping of member name->value. - - This mapping lists all enum members, including aliases. Note that this - is a copy of the internal mapping. - - """ - return cls._member_map_.copy() - - def __getattr__(cls, name): - """Return the enum member matching `name` - - We use __getattr__ instead of descriptors or inserting into the enum - class' __dict__ in order to support `name` and `value` being both - properties for enum members (which live in the class' __dict__) and - enum members themselves. - - """ - if _is_dunder(name): - raise AttributeError(name) - try: - return cls._member_map_[name] - except KeyError: - raise AttributeError(name) - - def __getitem__(cls, name): - return cls._member_map_[name] - - def __iter__(cls): - return (cls._member_map_[name] for name in cls._member_names_) - - def __reversed__(cls): - return (cls._member_map_[name] for name in reversed(cls._member_names_)) - - def __len__(cls): - return len(cls._member_names_) - - __nonzero__ = __bool__ - - def __repr__(cls): - return "<enum %r>" % cls.__name__ - - def __setattr__(cls, name, value): - """Block attempts to reassign Enum members. - - A simple assignment to the class namespace only changes one of the - several possible ways to get an Enum member from the Enum class, - resulting in an inconsistent Enumeration. - - """ - member_map = cls.__dict__.get('_member_map_', {}) - if name in member_map: - raise AttributeError('Cannot reassign members.') - super(EnumMeta, cls).__setattr__(name, value) - - def _create_(cls, class_name, names=None, module=None, type=None, start=1): - """Convenience method to create a new Enum class. - - `names` can be: - - * A string containing member names, separated either with spaces or - commas. Values are auto-numbered from 1. - * An iterable of member names. Values are auto-numbered from 1. - * An iterable of (member name, value) pairs. - * A mapping of member name -> value. - - """ - if pyver < 3.0: - # if class_name is unicode, attempt a conversion to ASCII - if isinstance(class_name, unicode): - try: - class_name = class_name.encode('ascii') - except UnicodeEncodeError: - raise TypeError('%r is not representable in ASCII' % class_name) - metacls = cls.__class__ - if type is None: - bases = (cls, ) - else: - bases = (type, cls) - classdict = metacls.__prepare__(class_name, bases) - _order_ = [] - - # special processing needed for names? - if isinstance(names, basestring): - names = names.replace(',', ' ').split() - if isinstance(names, (tuple, list)) and isinstance(names[0], basestring): - names = [(e, i+start) for (i, e) in enumerate(names)] - - # Here, names is either an iterable of (name, value) or a mapping. - item = None # in case names is empty - for item in names: - if isinstance(item, basestring): - member_name, member_value = item, names[item] - else: - member_name, member_value = item - classdict[member_name] = member_value - _order_.append(member_name) - # only set _order_ in classdict if name/value was not from a mapping - if not isinstance(item, basestring): - classdict['_order_'] = ' '.join(_order_) - enum_class = metacls.__new__(metacls, class_name, bases, classdict) - - # TODO: replace the frame hack if a blessed way to know the calling - # module is ever developed - if module is None: - try: - module = _sys._getframe(2).f_globals['__name__'] - except (AttributeError, ValueError): - pass - if module is None: - _make_class_unpicklable(enum_class) - else: - enum_class.__module__ = module - - return enum_class - - @staticmethod - def _get_mixins_(bases): - """Returns the type for creating enum members, and the first inherited - enum class. - - bases: the tuple of bases that was given to __new__ - - """ - if not bases or Enum is None: - return object, Enum - - - # double check that we are not subclassing a class with existing - # enumeration members; while we're at it, see if any other data - # type has been mixed in so we can use the correct __new__ - member_type = first_enum = None - for base in bases: - if (base is not Enum and - issubclass(base, Enum) and - base._member_names_): - raise TypeError("Cannot extend enumerations") - # base is now the last base in bases - if not issubclass(base, Enum): - raise TypeError("new enumerations must be created as " - "`ClassName([mixin_type,] enum_type)`") - - # get correct mix-in type (either mix-in type of Enum subclass, or - # first base if last base is Enum) - if not issubclass(bases[0], Enum): - member_type = bases[0] # first data type - first_enum = bases[-1] # enum type - else: - for base in bases[0].__mro__: - # most common: (IntEnum, int, Enum, object) - # possible: (<Enum 'AutoIntEnum'>, <Enum 'IntEnum'>, - # <class 'int'>, <Enum 'Enum'>, - # <class 'object'>) - if issubclass(base, Enum): - if first_enum is None: - first_enum = base - else: - if member_type is None: - member_type = base - - return member_type, first_enum - - if pyver < 3.0: - @staticmethod - def _find_new_(classdict, member_type, first_enum): - """Returns the __new__ to be used for creating the enum members. - - classdict: the class dictionary given to __new__ - member_type: the data type whose __new__ will be used by default - first_enum: enumeration to check for an overriding __new__ - - """ - # now find the correct __new__, checking to see of one was defined - # by the user; also check earlier enum classes in case a __new__ was - # saved as __member_new__ - __new__ = classdict.get('__new__', None) - if __new__: - return None, True, True # __new__, save_new, use_args - - N__new__ = getattr(None, '__new__') - O__new__ = getattr(object, '__new__') - if Enum is None: - E__new__ = N__new__ - else: - E__new__ = Enum.__dict__['__new__'] - # check all possibles for __member_new__ before falling back to - # __new__ - for method in ('__member_new__', '__new__'): - for possible in (member_type, first_enum): - try: - target = possible.__dict__[method] - except (AttributeError, KeyError): - target = getattr(possible, method, None) - if target not in [ - None, - N__new__, - O__new__, - E__new__, - ]: - if method == '__member_new__': - classdict['__new__'] = target - return None, False, True - if isinstance(target, staticmethod): - target = target.__get__(member_type) - __new__ = target - break - if __new__ is not None: - break - else: - __new__ = object.__new__ - - # if a non-object.__new__ is used then whatever value/tuple was - # assigned to the enum member name will be passed to __new__ and to the - # new enum member's __init__ - if __new__ is object.__new__: - use_args = False - else: - use_args = True - - return __new__, False, use_args - else: - @staticmethod - def _find_new_(classdict, member_type, first_enum): - """Returns the __new__ to be used for creating the enum members. - - classdict: the class dictionary given to __new__ - member_type: the data type whose __new__ will be used by default - first_enum: enumeration to check for an overriding __new__ - - """ - # now find the correct __new__, checking to see of one was defined - # by the user; also check earlier enum classes in case a __new__ was - # saved as __member_new__ - __new__ = classdict.get('__new__', None) - - # should __new__ be saved as __member_new__ later? - save_new = __new__ is not None - - if __new__ is None: - # check all possibles for __member_new__ before falling back to - # __new__ - for method in ('__member_new__', '__new__'): - for possible in (member_type, first_enum): - target = getattr(possible, method, None) - if target not in ( - None, - None.__new__, - object.__new__, - Enum.__new__, - ): - __new__ = target - break - if __new__ is not None: - break - else: - __new__ = object.__new__ - - # if a non-object.__new__ is used then whatever value/tuple was - # assigned to the enum member name will be passed to __new__ and to the - # new enum member's __init__ - if __new__ is object.__new__: - use_args = False - else: - use_args = True - - return __new__, save_new, use_args - - -######################################################## -# In order to support Python 2 and 3 with a single -# codebase we have to create the Enum methods separately -# and then use the `type(name, bases, dict)` method to -# create the class. -######################################################## -temp_enum_dict = {} -temp_enum_dict['__doc__'] = "Generic enumeration.\n\n Derive from this class to define new enumerations.\n\n" - -def __new__(cls, value): - # all enum instances are actually created during class construction - # without calling this method; this method is called by the metaclass' - # __call__ (i.e. Color(3) ), and by pickle - if type(value) is cls: - # For lookups like Color(Color.red) - value = value.value - #return value - # by-value search for a matching enum member - # see if it's in the reverse mapping (for hashable values) - try: - if value in cls._value2member_map_: - return cls._value2member_map_[value] - except TypeError: - # not there, now do long search -- O(n) behavior - for member in cls._member_map_.values(): - if member.value == value: - return member - raise ValueError("%s is not a valid %s" % (value, cls.__name__)) -temp_enum_dict['__new__'] = __new__ -del __new__ - -def __repr__(self): - return "<%s.%s: %r>" % ( - self.__class__.__name__, self._name_, self._value_) -temp_enum_dict['__repr__'] = __repr__ -del __repr__ - -def __str__(self): - return "%s.%s" % (self.__class__.__name__, self._name_) -temp_enum_dict['__str__'] = __str__ -del __str__ - -if pyver >= 3.0: - def __dir__(self): - added_behavior = [ - m - for cls in self.__class__.mro() - for m in cls.__dict__ - if m[0] != '_' and m not in self._member_map_ - ] - return (['__class__', '__doc__', '__module__', ] + added_behavior) - temp_enum_dict['__dir__'] = __dir__ - del __dir__ - -def __format__(self, format_spec): - # mixed-in Enums should use the mixed-in type's __format__, otherwise - # we can get strange results with the Enum name showing up instead of - # the value - - # pure Enum branch - if self._member_type_ is object: - cls = str - val = str(self) - # mix-in branch - else: - cls = self._member_type_ - val = self.value - return cls.__format__(val, format_spec) -temp_enum_dict['__format__'] = __format__ -del __format__ - - -#################################### -# Python's less than 2.6 use __cmp__ - -if pyver < 2.6: - - def __cmp__(self, other): - if type(other) is self.__class__: - if self is other: - return 0 - return -1 - return NotImplemented - raise TypeError("unorderable types: %s() and %s()" % (self.__class__.__name__, other.__class__.__name__)) - temp_enum_dict['__cmp__'] = __cmp__ - del __cmp__ - -else: - - def __le__(self, other): - raise TypeError("unorderable types: %s() <= %s()" % (self.__class__.__name__, other.__class__.__name__)) - temp_enum_dict['__le__'] = __le__ - del __le__ - - def __lt__(self, other): - raise TypeError("unorderable types: %s() < %s()" % (self.__class__.__name__, other.__class__.__name__)) - temp_enum_dict['__lt__'] = __lt__ - del __lt__ - - def __ge__(self, other): - raise TypeError("unorderable types: %s() >= %s()" % (self.__class__.__name__, other.__class__.__name__)) - temp_enum_dict['__ge__'] = __ge__ - del __ge__ - - def __gt__(self, other): - raise TypeError("unorderable types: %s() > %s()" % (self.__class__.__name__, other.__class__.__name__)) - temp_enum_dict['__gt__'] = __gt__ - del __gt__ - - -def __eq__(self, other): - if type(other) is self.__class__: - return self is other - return NotImplemented -temp_enum_dict['__eq__'] = __eq__ -del __eq__ - -def __ne__(self, other): - if type(other) is self.__class__: - return self is not other - return NotImplemented -temp_enum_dict['__ne__'] = __ne__ -del __ne__ - -def __hash__(self): - return hash(self._name_) -temp_enum_dict['__hash__'] = __hash__ -del __hash__ - -def __reduce_ex__(self, proto): - return self.__class__, (self._value_, ) -temp_enum_dict['__reduce_ex__'] = __reduce_ex__ -del __reduce_ex__ - -# _RouteClassAttributeToGetattr is used to provide access to the `name` -# and `value` properties of enum members while keeping some measure of -# protection from modification, while still allowing for an enumeration -# to have members named `name` and `value`. This works because enumeration -# members are not set directly on the enum class -- __getattr__ is -# used to look them up. - -@_RouteClassAttributeToGetattr -def name(self): - return self._name_ -temp_enum_dict['name'] = name -del name - -@_RouteClassAttributeToGetattr -def value(self): - return self._value_ -temp_enum_dict['value'] = value -del value - -@classmethod -def _convert(cls, name, module, filter, source=None): - """ - Create a new Enum subclass that replaces a collection of global constants - """ - # convert all constants from source (or module) that pass filter() to - # a new Enum called name, and export the enum and its members back to - # module; - # also, replace the __reduce_ex__ method so unpickling works in - # previous Python versions - module_globals = vars(_sys.modules[module]) - if source: - source = vars(source) - else: - source = module_globals - members = dict((name, value) for name, value in source.items() if filter(name)) - cls = cls(name, members, module=module) - cls.__reduce_ex__ = _reduce_ex_by_name - module_globals.update(cls.__members__) - module_globals[name] = cls - return cls -temp_enum_dict['_convert'] = _convert -del _convert - -Enum = EnumMeta('Enum', (object, ), temp_enum_dict) -del temp_enum_dict - -# Enum has now been created -########################### - -class IntEnum(int, Enum): - """Enum where members are also (and must be) ints""" - -def _reduce_ex_by_name(self, proto): - return self.name - -def unique(enumeration): - """Class decorator that ensures only unique members exist in an enumeration.""" - duplicates = [] - for name, member in enumeration.__members__.items(): - if name != member.name: - duplicates.append((name, member.name)) - if duplicates: - duplicate_names = ', '.join( - ["%s -> %s" % (alias, name) for (alias, name) in duplicates] - ) - raise ValueError('duplicate names found in %r: %s' % - (enumeration, duplicate_names) - ) - return enumeration diff --git a/pipenv/vendor/backports/functools_lru_cache.py b/pipenv/vendor/backports/functools_lru_cache.py deleted file mode 100644 index 707c6c76..00000000 --- a/pipenv/vendor/backports/functools_lru_cache.py +++ /dev/null @@ -1,184 +0,0 @@ -from __future__ import absolute_import - -import functools -from collections import namedtuple -from threading import RLock - -_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"]) - - -@functools.wraps(functools.update_wrapper) -def update_wrapper(wrapper, - wrapped, - assigned = functools.WRAPPER_ASSIGNMENTS, - updated = functools.WRAPPER_UPDATES): - """ - Patch two bugs in functools.update_wrapper. - """ - # workaround for http://bugs.python.org/issue3445 - assigned = tuple(attr for attr in assigned if hasattr(wrapped, attr)) - wrapper = functools.update_wrapper(wrapper, wrapped, assigned, updated) - # workaround for https://bugs.python.org/issue17482 - wrapper.__wrapped__ = wrapped - return wrapper - - -class _HashedSeq(list): - __slots__ = 'hashvalue' - - def __init__(self, tup, hash=hash): - self[:] = tup - self.hashvalue = hash(tup) - - def __hash__(self): - return self.hashvalue - - -def _make_key(args, kwds, typed, - kwd_mark=(object(),), - fasttypes=set([int, str, frozenset, type(None)]), - sorted=sorted, tuple=tuple, type=type, len=len): - 'Make a cache key from optionally typed positional and keyword arguments' - key = args - if kwds: - sorted_items = sorted(kwds.items()) - key += kwd_mark - for item in sorted_items: - key += item - if typed: - key += tuple(type(v) for v in args) - if kwds: - key += tuple(type(v) for k, v in sorted_items) - elif len(key) == 1 and type(key[0]) in fasttypes: - return key[0] - return _HashedSeq(key) - - -def lru_cache(maxsize=100, typed=False): - """Least-recently-used cache decorator. - - If *maxsize* is set to None, the LRU features are disabled and the cache - can grow without bound. - - If *typed* is True, arguments of different types will be cached separately. - For example, f(3.0) and f(3) will be treated as distinct calls with - distinct results. - - Arguments to the cached function must be hashable. - - View the cache statistics named tuple (hits, misses, maxsize, currsize) with - f.cache_info(). Clear the cache and statistics with f.cache_clear(). - Access the underlying function with f.__wrapped__. - - See: http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used - - """ - - # Users should only access the lru_cache through its public API: - # cache_info, cache_clear, and f.__wrapped__ - # The internals of the lru_cache are encapsulated for thread safety and - # to allow the implementation to change (including a possible C version). - - def decorating_function(user_function): - - cache = dict() - stats = [0, 0] # make statistics updateable non-locally - HITS, MISSES = 0, 1 # names for the stats fields - make_key = _make_key - cache_get = cache.get # bound method to lookup key or return None - _len = len # localize the global len() function - lock = RLock() # because linkedlist updates aren't threadsafe - root = [] # root of the circular doubly linked list - root[:] = [root, root, None, None] # initialize by pointing to self - nonlocal_root = [root] # make updateable non-locally - PREV, NEXT, KEY, RESULT = 0, 1, 2, 3 # names for the link fields - - if maxsize == 0: - - def wrapper(*args, **kwds): - # no caching, just do a statistics update after a successful call - result = user_function(*args, **kwds) - stats[MISSES] += 1 - return result - - elif maxsize is None: - - def wrapper(*args, **kwds): - # simple caching without ordering or size limit - key = make_key(args, kwds, typed) - result = cache_get(key, root) # root used here as a unique not-found sentinel - if result is not root: - stats[HITS] += 1 - return result - result = user_function(*args, **kwds) - cache[key] = result - stats[MISSES] += 1 - return result - - else: - - def wrapper(*args, **kwds): - # size limited caching that tracks accesses by recency - key = make_key(args, kwds, typed) if kwds or typed else args - with lock: - link = cache_get(key) - if link is not None: - # record recent use of the key by moving it to the front of the list - root, = nonlocal_root - link_prev, link_next, key, result = link - link_prev[NEXT] = link_next - link_next[PREV] = link_prev - last = root[PREV] - last[NEXT] = root[PREV] = link - link[PREV] = last - link[NEXT] = root - stats[HITS] += 1 - return result - result = user_function(*args, **kwds) - with lock: - root, = nonlocal_root - if key in cache: - # getting here means that this same key was added to the - # cache while the lock was released. since the link - # update is already done, we need only return the - # computed result and update the count of misses. - pass - elif _len(cache) >= maxsize: - # use the old root to store the new key and result - oldroot = root - oldroot[KEY] = key - oldroot[RESULT] = result - # empty the oldest link and make it the new root - root = nonlocal_root[0] = oldroot[NEXT] - oldkey = root[KEY] - root[KEY] = root[RESULT] = None - # now update the cache dictionary for the new links - del cache[oldkey] - cache[key] = oldroot - else: - # put result in a new link at the front of the list - last = root[PREV] - link = [last, root, key, result] - last[NEXT] = root[PREV] = cache[key] = link - stats[MISSES] += 1 - return result - - def cache_info(): - """Report cache statistics""" - with lock: - return _CacheInfo(stats[HITS], stats[MISSES], maxsize, len(cache)) - - def cache_clear(): - """Clear the cache and cache statistics""" - with lock: - cache.clear() - root = nonlocal_root[0] - root[:] = [root, root, None, None] - stats[:] = [0, 0] - - wrapper.__wrapped__ = user_function - wrapper.cache_info = cache_info - wrapper.cache_clear = cache_clear - return update_wrapper(wrapper, user_function) - - return decorating_function diff --git a/pipenv/vendor/backports/shutil_get_terminal_size/__init__.py b/pipenv/vendor/backports/shutil_get_terminal_size/__init__.py deleted file mode 100644 index cfcbdf66..00000000 --- a/pipenv/vendor/backports/shutil_get_terminal_size/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -"""A backport of the get_terminal_size function from Python 3.3's shutil.""" - -__title__ = "backports.shutil_get_terminal_size" -__version__ = "1.0.0" -__license__ = "MIT" -__author__ = "Christopher Rosell" -__copyright__ = "Copyright 2014 Christopher Rosell" - -__all__ = ["get_terminal_size"] - -from .get_terminal_size import get_terminal_size diff --git a/pipenv/vendor/backports/shutil_get_terminal_size/get_terminal_size.py b/pipenv/vendor/backports/shutil_get_terminal_size/get_terminal_size.py deleted file mode 100644 index 28c96da8..00000000 --- a/pipenv/vendor/backports/shutil_get_terminal_size/get_terminal_size.py +++ /dev/null @@ -1,101 +0,0 @@ -"""This is a backport of shutil.get_terminal_size from Python 3.3. - -The original implementation is in C, but here we use the ctypes and -fcntl modules to create a pure Python version of os.get_terminal_size. -""" - -import os -import struct -import sys - -from collections import namedtuple - -__all__ = ["get_terminal_size"] - - -terminal_size = namedtuple("terminal_size", "columns lines") - -try: - from ctypes import windll, create_string_buffer - - _handles = { - 0: windll.kernel32.GetStdHandle(-10), - 1: windll.kernel32.GetStdHandle(-11), - 2: windll.kernel32.GetStdHandle(-12), - } - - def _get_terminal_size(fd): - columns = lines = 0 - - try: - handle = _handles[fd] - csbi = create_string_buffer(22) - res = windll.kernel32.GetConsoleScreenBufferInfo(handle, csbi) - if res: - res = struct.unpack("hhhhHhhhhhh", csbi.raw) - left, top, right, bottom = res[5:9] - columns = right - left + 1 - lines = bottom - top + 1 - except Exception: - pass - - return terminal_size(columns, lines) - -except ImportError: - import fcntl - import termios - - def _get_terminal_size(fd): - try: - res = fcntl.ioctl(fd, termios.TIOCGWINSZ, b"\x00" * 4) - lines, columns = struct.unpack("hh", res) - except Exception: - columns = lines = 0 - - return terminal_size(columns, lines) - - -def get_terminal_size(fallback=(80, 24)): - """Get the size of the terminal window. - - For each of the two dimensions, the environment variable, COLUMNS - and LINES respectively, is checked. If the variable is defined and - the value is a positive integer, it is used. - - When COLUMNS or LINES is not defined, which is the common case, - the terminal connected to sys.__stdout__ is queried - by invoking os.get_terminal_size. - - If the terminal size cannot be successfully queried, either because - the system doesn't support querying, or because we are not - connected to a terminal, the value given in fallback parameter - is used. Fallback defaults to (80, 24) which is the default - size used by many terminal emulators. - - The value returned is a named tuple of type os.terminal_size. - """ - # Try the environment first - try: - columns = int(os.environ["COLUMNS"]) - except (KeyError, ValueError): - columns = 0 - - try: - lines = int(os.environ["LINES"]) - except (KeyError, ValueError): - lines = 0 - - # Only query if necessary - if columns <= 0 or lines <= 0: - try: - size = _get_terminal_size(sys.__stdout__.fileno()) - except (NameError, OSError): - size = terminal_size(*fallback) - - if columns <= 0: - columns = size.columns - if lines <= 0: - lines = size.lines - - return terminal_size(columns, lines) - diff --git a/pipenv/vendor/backports/weakref.LICENSE b/pipenv/vendor/backports/weakref.LICENSE deleted file mode 100644 index f5d0b39a..00000000 --- a/pipenv/vendor/backports/weakref.LICENSE +++ /dev/null @@ -1,255 +0,0 @@ -A. HISTORY OF THE SOFTWARE -========================== - -Python was created in the early 1990s by Guido van Rossum at Stichting -Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands -as a successor of a language called ABC. Guido remains Python's -principal author, although it includes many contributions from others. - -In 1995, Guido continued his work on Python at the Corporation for -National Research Initiatives (CNRI, see http://www.cnri.reston.va.us) -in Reston, Virginia where he released several versions of the -software. - -In May 2000, Guido and the Python core development team moved to -BeOpen.com to form the BeOpen PythonLabs team. In October of the same -year, the PythonLabs team moved to Digital Creations (now Zope -Corporation, see http://www.zope.com). In 2001, the Python Software -Foundation (PSF, see http://www.python.org/psf/) was formed, a -non-profit organization created specifically to own Python-related -Intellectual Property. Zope Corporation is a sponsoring member of -the PSF. - -All Python releases are Open Source (see http://www.opensource.org for -the Open Source Definition). Historically, most, but not all, Python -releases have also been GPL-compatible; the table below summarizes -the various releases. - - Release Derived Year Owner GPL- - from compatible? (1) - - 0.9.0 thru 1.2 1991-1995 CWI yes - 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes - 1.6 1.5.2 2000 CNRI no - 2.0 1.6 2000 BeOpen.com no - 1.6.1 1.6 2001 CNRI yes (2) - 2.1 2.0+1.6.1 2001 PSF no - 2.0.1 2.0+1.6.1 2001 PSF yes - 2.1.1 2.1+2.0.1 2001 PSF yes - 2.1.2 2.1.1 2002 PSF yes - 2.1.3 2.1.2 2002 PSF yes - 2.2 and above 2.1.1 2001-now PSF yes - -Footnotes: - -(1) GPL-compatible doesn't mean that we're distributing Python under - the GPL. All Python licenses, unlike the GPL, let you distribute - a modified version without making your changes open source. The - GPL-compatible licenses make it possible to combine Python with - other software that is released under the GPL; the others don't. - -(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, - because its license has a choice of law clause. According to - CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 - is "not incompatible" with the GPL. - -Thanks to the many outside volunteers who have worked under Guido's -direction to make these releases possible. - - -B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON -=============================================================== - -PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 --------------------------------------------- - -1. This LICENSE AGREEMENT is between the Python Software Foundation -("PSF"), and the Individual or Organization ("Licensee") accessing and -otherwise using this software ("Python") in source or binary form and -its associated documentation. - -2. Subject to the terms and conditions of this License Agreement, PSF hereby -grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, -analyze, test, perform and/or display publicly, prepare derivative works, -distribute, and otherwise use Python alone or in any derivative version, -provided, however, that PSF's License Agreement and PSF's notice of copyright, -i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, -2011, 2012, 2013, 2014, 2015, 2016, 2017 Python Software Foundation; All Rights -Reserved" are retained in Python alone or in any derivative version prepared by -Licensee. - -3. In the event Licensee prepares a derivative work that is based on -or incorporates Python or any part thereof, and wants to make -the derivative work available to others as provided herein, then -Licensee hereby agrees to include in any such work a brief summary of -the changes made to Python. - -4. PSF is making Python available to Licensee on an "AS IS" -basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON -FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS -A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, -OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -6. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -7. Nothing in this License Agreement shall be deemed to create any -relationship of agency, partnership, or joint venture between PSF and -Licensee. This License Agreement does not grant permission to use PSF -trademarks or trade name in a trademark sense to endorse or promote -products or services of Licensee, or any third party. - -8. By copying, installing or otherwise using Python, Licensee -agrees to be bound by the terms and conditions of this License -Agreement. - - -BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 -------------------------------------------- - -BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 - -1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an -office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the -Individual or Organization ("Licensee") accessing and otherwise using -this software in source or binary form and its associated -documentation ("the Software"). - -2. Subject to the terms and conditions of this BeOpen Python License -Agreement, BeOpen hereby grants Licensee a non-exclusive, -royalty-free, world-wide license to reproduce, analyze, test, perform -and/or display publicly, prepare derivative works, distribute, and -otherwise use the Software alone or in any derivative version, -provided, however, that the BeOpen Python License is retained in the -Software, alone or in any derivative version prepared by Licensee. - -3. BeOpen is making the Software available to Licensee on an "AS IS" -basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE -SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS -AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY -DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -5. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -6. This License Agreement shall be governed by and interpreted in all -respects by the law of the State of California, excluding conflict of -law provisions. Nothing in this License Agreement shall be deemed to -create any relationship of agency, partnership, or joint venture -between BeOpen and Licensee. This License Agreement does not grant -permission to use BeOpen trademarks or trade names in a trademark -sense to endorse or promote products or services of Licensee, or any -third party. As an exception, the "BeOpen Python" logos available at -http://www.pythonlabs.com/logos.html may be used according to the -permissions granted on that web page. - -7. By copying, installing or otherwise using the software, Licensee -agrees to be bound by the terms and conditions of this License -Agreement. - - -CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 ---------------------------------------- - -1. This LICENSE AGREEMENT is between the Corporation for National -Research Initiatives, having an office at 1895 Preston White Drive, -Reston, VA 20191 ("CNRI"), and the Individual or Organization -("Licensee") accessing and otherwise using Python 1.6.1 software in -source or binary form and its associated documentation. - -2. Subject to the terms and conditions of this License Agreement, CNRI -hereby grants Licensee a nonexclusive, royalty-free, world-wide -license to reproduce, analyze, test, perform and/or display publicly, -prepare derivative works, distribute, and otherwise use Python 1.6.1 -alone or in any derivative version, provided, however, that CNRI's -License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) -1995-2001 Corporation for National Research Initiatives; All Rights -Reserved" are retained in Python 1.6.1 alone or in any derivative -version prepared by Licensee. Alternately, in lieu of CNRI's License -Agreement, Licensee may substitute the following text (omitting the -quotes): "Python 1.6.1 is made available subject to the terms and -conditions in CNRI's License Agreement. This Agreement together with -Python 1.6.1 may be located on the Internet using the following -unique, persistent identifier (known as a handle): 1895.22/1013. This -Agreement may also be obtained from a proxy server on the Internet -using the following URL: http://hdl.handle.net/1895.22/1013". - -3. In the event Licensee prepares a derivative work that is based on -or incorporates Python 1.6.1 or any part thereof, and wants to make -the derivative work available to others as provided herein, then -Licensee hereby agrees to include in any such work a brief summary of -the changes made to Python 1.6.1. - -4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" -basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON -1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS -A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, -OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -6. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -7. This License Agreement shall be governed by the federal -intellectual property law of the United States, including without -limitation the federal copyright law, and, to the extent such -U.S. federal law does not apply, by the law of the Commonwealth of -Virginia, excluding Virginia's conflict of law provisions. -Notwithstanding the foregoing, with regard to derivative works based -on Python 1.6.1 that incorporate non-separable material that was -previously distributed under the GNU General Public License (GPL), the -law of the Commonwealth of Virginia shall govern this License -Agreement only as to issues arising under or with respect to -Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this -License Agreement shall be deemed to create any relationship of -agency, partnership, or joint venture between CNRI and Licensee. This -License Agreement does not grant permission to use CNRI trademarks or -trade name in a trademark sense to endorse or promote products or -services of Licensee, or any third party. - -8. By clicking on the "ACCEPT" button where indicated, or by copying, -installing or otherwise using Python 1.6.1, Licensee agrees to be -bound by the terms and conditions of this License Agreement. - - ACCEPT - - -CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 --------------------------------------------------- - -Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, -The Netherlands. All rights reserved. - -Permission to use, copy, modify, and distribute this software and its -documentation for any purpose and without fee is hereby granted, -provided that the above copyright notice appear in all copies and that -both that copyright notice and this permission notice appear in -supporting documentation, and that the name of Stichting Mathematisch -Centrum or CWI not be used in advertising or publicity pertaining to -distribution of the software without specific, written prior -permission. - -STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO -THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE -FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT -OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/pipenv/vendor/backports/weakref.py b/pipenv/vendor/backports/weakref.py deleted file mode 100644 index de6193bd..00000000 --- a/pipenv/vendor/backports/weakref.py +++ /dev/null @@ -1,151 +0,0 @@ -""" -Partial backport of Python 3.6's weakref module: - - finalize (new in Python 3.4) - -Backport modifications are marked with "XXX backport". -""" -from __future__ import absolute_import - -import itertools -import sys -from weakref import ref - -__all__ = ['finalize'] - - -class finalize(object): - """Class for finalization of weakrefable objects - - finalize(obj, func, *args, **kwargs) returns a callable finalizer - object which will be called when obj is garbage collected. The - first time the finalizer is called it evaluates func(*arg, **kwargs) - and returns the result. After this the finalizer is dead, and - calling it just returns None. - - When the program exits any remaining finalizers for which the - atexit attribute is true will be run in reverse order of creation. - By default atexit is true. - """ - - # Finalizer objects don't have any state of their own. They are - # just used as keys to lookup _Info objects in the registry. This - # ensures that they cannot be part of a ref-cycle. - - __slots__ = () - _registry = {} - _shutdown = False - _index_iter = itertools.count() - _dirty = False - _registered_with_atexit = False - - class _Info(object): - __slots__ = ("weakref", "func", "args", "kwargs", "atexit", "index") - - def __init__(self, obj, func, *args, **kwargs): - if not self._registered_with_atexit: - # We may register the exit function more than once because - # of a thread race, but that is harmless - import atexit - atexit.register(self._exitfunc) - finalize._registered_with_atexit = True - info = self._Info() - info.weakref = ref(obj, self) - info.func = func - info.args = args - info.kwargs = kwargs or None - info.atexit = True - info.index = next(self._index_iter) - self._registry[self] = info - finalize._dirty = True - - def __call__(self, _=None): - """If alive then mark as dead and return func(*args, **kwargs); - otherwise return None""" - info = self._registry.pop(self, None) - if info and not self._shutdown: - return info.func(*info.args, **(info.kwargs or {})) - - def detach(self): - """If alive then mark as dead and return (obj, func, args, kwargs); - otherwise return None""" - info = self._registry.get(self) - obj = info and info.weakref() - if obj is not None and self._registry.pop(self, None): - return (obj, info.func, info.args, info.kwargs or {}) - - def peek(self): - """If alive then return (obj, func, args, kwargs); - otherwise return None""" - info = self._registry.get(self) - obj = info and info.weakref() - if obj is not None: - return (obj, info.func, info.args, info.kwargs or {}) - - @property - def alive(self): - """Whether finalizer is alive""" - return self in self._registry - - @property - def atexit(self): - """Whether finalizer should be called at exit""" - info = self._registry.get(self) - return bool(info) and info.atexit - - @atexit.setter - def atexit(self, value): - info = self._registry.get(self) - if info: - info.atexit = bool(value) - - def __repr__(self): - info = self._registry.get(self) - obj = info and info.weakref() - if obj is None: - return '<%s object at %#x; dead>' % (type(self).__name__, id(self)) - else: - return '<%s object at %#x; for %r at %#x>' % \ - (type(self).__name__, id(self), type(obj).__name__, id(obj)) - - @classmethod - def _select_for_exit(cls): - # Return live finalizers marked for exit, oldest first - L = [(f,i) for (f,i) in cls._registry.items() if i.atexit] - L.sort(key=lambda item:item[1].index) - return [f for (f,i) in L] - - @classmethod - def _exitfunc(cls): - # At shutdown invoke finalizers for which atexit is true. - # This is called once all other non-daemonic threads have been - # joined. - reenable_gc = False - try: - if cls._registry: - import gc - if gc.isenabled(): - reenable_gc = True - gc.disable() - pending = None - while True: - if pending is None or finalize._dirty: - pending = cls._select_for_exit() - finalize._dirty = False - if not pending: - break - f = pending.pop() - try: - # gc is disabled, so (assuming no daemonic - # threads) the following is the only line in - # this function which might trigger creation - # of a new finalizer - f() - except Exception: - sys.excepthook(*sys.exc_info()) - assert f not in cls._registry - finally: - # prevent any more finalizers from executing during shutdown - finalize._shutdown = True - if reenable_gc: - gc.enable() diff --git a/pipenv/vendor/cached_property.LICENSE b/pipenv/vendor/cached_property.LICENSE new file mode 100644 index 00000000..a181761c --- /dev/null +++ b/pipenv/vendor/cached_property.LICENSE @@ -0,0 +1,12 @@ +Copyright (c) 2015, Daniel Greenfeld +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +* Neither the name of cached-property nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/pipenv/vendor/cached_property.py b/pipenv/vendor/cached_property.py index 125f6195..3135871b 100644 --- a/pipenv/vendor/cached_property.py +++ b/pipenv/vendor/cached_property.py @@ -2,9 +2,10 @@ __author__ = "Daniel Greenfeld" __email__ = "pydanny@gmail.com" -__version__ = "1.5.1" +__version__ = "1.5.2" __license__ = "BSD" +from functools import wraps from time import time import threading @@ -36,7 +37,7 @@ class cached_property(object): return value def _wrap_in_coroutine(self, obj): - + @wraps(obj) @asyncio.coroutine def wrapper(): future = asyncio.ensure_future(self.func(obj)) diff --git a/pipenv/vendor/cerberus/__init__.py b/pipenv/vendor/cerberus/__init__.py index 1e0f0d54..b4628a52 100644 --- a/pipenv/vendor/cerberus/__init__.py +++ b/pipenv/vendor/cerberus/__init__.py @@ -1,10 +1,10 @@ """ Extensible validation for Python dictionaries. - :copyright: 2012-2016 by Nicola Iarocci. + :copyright: 2012-2021 by Nicola Iarocci. :license: ISC, see LICENSE for more details. - Full documentation is available at http://python-cerberus.org/ + Full documentation is available at https://python-cerberus.org/ """ @@ -12,9 +12,9 @@ from __future__ import absolute_import from pkg_resources import get_distribution, DistributionNotFound -from cerberus.validator import DocumentError, Validator -from cerberus.schema import rules_set_registry, schema_registry, SchemaError -from cerberus.utils import TypeDefinition +from pipenv.vendor.cerberus.validator import DocumentError, Validator +from pipenv.vendor.cerberus.schema import rules_set_registry, schema_registry, SchemaError +from pipenv.vendor.cerberus.utils import TypeDefinition try: diff --git a/pipenv/vendor/cerberus/benchmarks/__init__.py b/pipenv/vendor/cerberus/benchmarks/__init__.py new file mode 100644 index 00000000..7cdc925f --- /dev/null +++ b/pipenv/vendor/cerberus/benchmarks/__init__.py @@ -0,0 +1,4 @@ +from pathlib import Path + + +DOCUMENTS_PATH = Path(__file__).parent / "documents" diff --git a/pipenv/vendor/cerberus/benchmarks/test_overall_performance_1.py b/pipenv/vendor/cerberus/benchmarks/test_overall_performance_1.py new file mode 100644 index 00000000..1f9eeb1d --- /dev/null +++ b/pipenv/vendor/cerberus/benchmarks/test_overall_performance_1.py @@ -0,0 +1,212 @@ +""" +some notes regarding this test suite: +- results are only comparable using the semantically equal schema against and + identical set of documents in the same execution environment +- the module can be executed to generate a new set of test documents +- it is intended to detect *significant* changes in validation time +- benchmarks should run with as few other processes running on the system as + possible (e.g. an Alpine Linux on bare metal w/o a Desktop environment) +""" + +import json +from collections import Counter +from pathlib import Path +from random import choice, randrange +from typing import Callable, List + +from pytest import mark + +from pipenv.vendor.cerberus import rules_set_registry, schema_registry, TypeDefinition, Validator +from pipenv.vendor.cerberus.benchmarks import DOCUMENTS_PATH + + +rules_set_registry.add("path_rules", {"coerce": Path, "type": "path"}) + + +schema_registry.add( + "field_3_schema", + { + # an outer rule requires all fields' values to be a list + "field_31": {"contains": 0, "empty": False}, + "field_32": { + "default": [None, None, None], + "items": [ + {"type": "integer"}, + {"type": "string"}, + {"type": ["integer", "string"]}, + ], + "schema": {"nullable": True}, + }, + }, +) + + +def schema_1_field_3_allow_unknown_check_with(field, value, error): + if len(value) > 9: + error(field, "Requires a smaller list.") + + +schema_1 = { + "field_1": { + "type": "dict", + "required": True, + "allow_unknown": True, + "keysrules": {"regex": r"field_1[12345]"}, + "minlength": 3, + "maxlength": 5, + "schema": { + "field_11": { + "type": "integer", + "allowed": list(range(100)), + "dependencies": {"field_12": 0, "^field_1.field_13": 0}, + }, + "field_12": { + "type": "integer", + "default_setter": lambda _: 1, + "forbidden": (1,), + }, + "field_13": {"type": "integer"}, + "field_14": {"rename": "field_13"}, + }, + }, + "field_2": { + "type": "dict", + "allow_unknown": False, + "schema": { + "field_21": { + "type": "integer", + "coerce": [str.strip, int], + "min": 9, + "max": 89, + "anyof": [{"dependencies": "field_22"}, {"dependencies": "field_23"}], + }, + "field_22": {"excludes": "field_23", "nullable": True}, + "field_23": {"nullable": True}, + }, + }, + "field_3": { + "allow_unknown": {"check_with": schema_1_field_3_allow_unknown_check_with}, + "valuesrules": {"type": "list"}, + "require_all": True, + "schema": "field_3_schema", + }, + "field_4": "path_rules", +} + + +def init_validator(): + class TestValidator(Validator): + types_mapping = { + **Validator.types_mapping, + "path": TypeDefinition("path", (Path,), ()), + } + + return TestValidator(schema_1, purge_unknown=True) + + +def load_documents(): + with (DOCUMENTS_PATH / "overall_documents_1.json").open() as f: + documents = json.load(f) + return documents + + +def validate_documents(init_validator: Callable, documents: List[dict]): + doc_count = failed_count = 0 + error_paths = Counter() + validator = init_validator() + + def count_errors(errors): + if errors is None: + return + for error in errors: + if error.is_group_error: + count_errors(error.child_errors) + else: + error_paths[error.schema_path] += 1 + + for document in documents: + if validator.validated(document) is None: + failed_count += 1 + count_errors(validator._errors) + doc_count += 1 + + print( + f"{failed_count} out of {doc_count} documents failed with " + f"{len(error_paths)} different error leafs." + ) + print("Top 3 errors, excluding container errors:") + for path, count in error_paths.most_common(3): + print(f"{count}: {path}") + + +@mark.benchmark(group="overall-1") +def test_overall_performance_1(benchmark): + benchmark.pedantic(validate_documents, (init_validator, load_documents()), rounds=5) + + +# + + +def generate_sample_document_1() -> dict: + result = {} + for i in (1, 2, 3, 4, 5): + if randrange(100): + result[f"field_{i}"] = globals()[f"generate_document_1_field_{i}"]() + return result + + +def generate_document_1_field_1() -> dict: + result = {"field_11": randrange(100), "field_13": 0} + if randrange(100): + result["field_12"] = 0 + if not randrange(100): + result["field_14"] = None + if randrange(100): + result["field_15"] = None + return result + + +def generate_document_1_field_2() -> dict: + x = "*" if not randrange(50) else " " + result = {"field_21": x + str(randrange(100)) + x} + + if randrange(100): + result["field_22"] = None + if "field_22" in result and not randrange(100): + result["field_23"] = None + + return result + + +def generate_document_1_field_3() -> dict: + result = {} + if randrange(100): + result["field_31"] = [randrange(2) for _ in range(randrange(20))] + else: + result["field_31"] = None + if randrange(100): + result["field_32"] = [ + choice((0, 0, 0, 0, 0, 0, 0, 0, "", None)), + choice(("", "", "", "", "", "", "", "", 0, None)), + choice((0, 0, 0, 0, "", "", "", "", None)), + ] + if not randrange(10): + result["3_unknown"] = [0] * (randrange(10) + 1) + return result + + +def generate_document_1_field_4(): + return "/foo/bar" if randrange(100) else 0 + + +def generate_document_1_field_5(): + return None + + +def write_sample_documents(): + with (DOCUMENTS_PATH / "overall_documents_1.json").open("wt") as f: + json.dump([generate_sample_document_1() for _ in range(10_000)], f) + + +if __name__ == "__main__": + write_sample_documents() diff --git a/pipenv/vendor/cerberus/benchmarks/test_overall_performance_2.py b/pipenv/vendor/cerberus/benchmarks/test_overall_performance_2.py new file mode 100644 index 00000000..227f81aa --- /dev/null +++ b/pipenv/vendor/cerberus/benchmarks/test_overall_performance_2.py @@ -0,0 +1,54 @@ +import json +from collections import Counter +from typing import Callable, List +from typing import Counter as CounterType + +from pytest import mark + +from pipenv.vendor.cerberus import Validator +from pipenv.vendor.cerberus.benchmarks.schemas.overalll_schema_2 import product_schema +from pipenv.vendor.cerberus.benchmarks import DOCUMENTS_PATH + + +def init_validator(): + return Validator(product_schema, purge_unknown=True) + + +def load_documents(): + with (DOCUMENTS_PATH / "overall_documents_2.json").open() as f: + documents = json.load(f) + return documents + + +def validate_documents(init_validator: Callable, documents: List[dict]) -> None: + doc_count = failed_count = 0 + error_paths: CounterType[tuple] = Counter() + validator = init_validator() + + def count_errors(errors): + if errors is None: + return + for error in errors: + if error.is_group_error: + count_errors(error.child_errors) + else: + error_paths[error.schema_path] += 1 + + for document in documents: + if validator.validated(document) is None: + failed_count += 1 + count_errors(validator._errors) + doc_count += 1 + + print( + f"{failed_count} out of {doc_count} documents failed with " + f"{len(error_paths)} different error leafs." + ) + print("Top 3 errors, excluding container errors:") + for path, count in error_paths.most_common(3): + print(f"{count}: {path}") + + +@mark.benchmark(group="overall-2") +def test_overall_performance_2(benchmark): + benchmark.pedantic(validate_documents, (init_validator, load_documents()), rounds=5) diff --git a/pipenv/vendor/cerberus/errors.py b/pipenv/vendor/cerberus/errors.py index 14e27eb8..1492f1d1 100644 --- a/pipenv/vendor/cerberus/errors.py +++ b/pipenv/vendor/cerberus/errors.py @@ -8,8 +8,8 @@ from copy import copy, deepcopy from functools import wraps from pprint import pformat -from cerberus.platform import PYTHON_VERSION, MutableMapping -from cerberus.utils import compare_paths_lt, quote_string +from pipenv.vendor.cerberus.platform import PYTHON_VERSION, MutableMapping +from pipenv.vendor.cerberus.utils import compare_paths_lt, quote_string ErrorDefinition = namedtuple('ErrorDefinition', 'code, rule') @@ -89,7 +89,7 @@ SCHEMA_ERROR_MISSING = "validation schema missing" class ValidationError(object): - """ A simple class to store and query basic error information. """ + """A simple class to store and query basic error information.""" def __init__(self, document_path, schema_path, code, rule, constraint, value, info): self.document_path = document_path @@ -111,11 +111,11 @@ class ValidationError(object): Type: :class:`tuple` """ def __eq__(self, other): - """ Assumes the errors relate to the same document and schema. """ + """Assumes the errors relate to the same document and schema.""" return hash(self) == hash(other) def __hash__(self): - """ Expects that all other properties are transitively determined. """ + """Expects that all other properties are transitively determined.""" return hash(self.document_path) ^ hash(self.schema_path) ^ hash(self.code) def __lt__(self, other): @@ -153,9 +153,10 @@ class ValidationError(object): @property def definitions_errors(self): - """ Dictionary with errors of an *of-rule mapped to the index of the - definition it occurred in. Returns :obj:`None` if not applicable. - """ + """ + Dictionary with errors of an \*of-rule mapped to the index of the definition it + occurred in. Returns :obj:`None` if not applicable. + """ if not self.is_logic_error: return None @@ -167,7 +168,7 @@ class ValidationError(object): @property def field(self): - """ Field of the contextual mapping, possibly :obj:`None`. """ + """Field of the contextual mapping, possibly :obj:`None`.""" if self.document_path: return self.document_path[-1] else: @@ -175,25 +176,27 @@ class ValidationError(object): @property def is_group_error(self): - """ ``True`` for errors of bulk validations. """ + """``True`` for errors of bulk validations.""" return bool(self.code & ERROR_GROUP.code) @property def is_logic_error(self): - """ ``True`` for validation errors against different schemas with - *of-rules. """ + """ + ``True`` for validation errors against different schemas with \*of-rules. + """ return bool(self.code & LOGICAL.code - ERROR_GROUP.code) @property def is_normalization_error(self): - """ ``True`` for normalization errors. """ + """``True`` for normalization errors.""" return bool(self.code & NORMALIZATION.code) class ErrorList(list): - """ A list for :class:`~cerberus.errors.ValidationError` instances that - can be queried with the ``in`` keyword for a particular - :class:`~cerberus.errors.ErrorDefinition`. """ + """ + A list for :class:`~cerberus.errors.ValidationError` instances that can be queried + with the ``in`` keyword for a particular :class:`~cerberus.errors.ErrorDefinition`. + """ def __contains__(self, error_definition): if not isinstance(error_definition, ErrorDefinition): @@ -277,8 +280,10 @@ class ErrorTreeNode(MutableMapping): class ErrorTree(ErrorTreeNode): - """ Base class for :class:`~cerberus.errors.DocumentErrorTree` and - :class:`~cerberus.errors.SchemaErrorTree`. """ + """ + Base class for :class:`~cerberus.errors.DocumentErrorTree` and + :class:`~cerberus.errors.SchemaErrorTree`. + """ def __init__(self, errors=()): self.parent_node = None @@ -290,7 +295,8 @@ class ErrorTree(ErrorTreeNode): self.add(error) def add(self, error): - """ Add an error to the tree. + """ + Add an error to the tree. :param error: :class:`~cerberus.errors.ValidationError` """ @@ -301,7 +307,8 @@ class ErrorTree(ErrorTreeNode): super(ErrorTree, self).add(error) def fetch_errors_from(self, path): - """ Returns all errors for a particular path. + """ + Returns all errors for a particular path. :param path: :class:`tuple` of :term:`hashable` s. :rtype: :class:`~cerberus.errors.ErrorList` @@ -313,7 +320,8 @@ class ErrorTree(ErrorTreeNode): return ErrorList() def fetch_node_from(self, path): - """ Returns a node for a path. + """ + Returns a node for a path. :param path: Tuple of :term:`hashable` s. :rtype: :class:`~cerberus.errors.ErrorTreeNode` or :obj:`None` @@ -327,29 +335,34 @@ class ErrorTree(ErrorTreeNode): class DocumentErrorTree(ErrorTree): - """ Implements a dict-like class to query errors by indexes following the - structure of a validated document. """ + """ + Implements a dict-like class to query errors by indexes following the structure of a + validated document. + """ tree_type = 'document' class SchemaErrorTree(ErrorTree): - """ Implements a dict-like class to query errors by indexes following the - structure of the used schema. """ + """ + Implements a dict-like class to query errors by indexes following the structure of + the used schema. + """ tree_type = 'schema' class BaseErrorHandler(object): - """ Base class for all error handlers. - Subclasses are identified as error-handlers with an instance-test. """ + """Base class for all error handlers. + Subclasses are identified as error-handlers with an instance-test.""" def __init__(self, *args, **kwargs): - """ Optionally initialize a new instance. """ + """Optionally initialize a new instance.""" pass def __call__(self, errors): - """ Returns errors in a handler-specific format. + """ + Returns errors in a handler-specific format. :param errors: An object containing the errors. :type errors: :term:`iterable` of @@ -359,11 +372,12 @@ class BaseErrorHandler(object): raise NotImplementedError def __iter__(self): - """ Be a superhero and implement an iterator over errors. """ + """Be a superhero and implement an iterator over errors.""" raise NotImplementedError def add(self, error): - """ Add an error to the errors' container object of a handler. + """ + Add an error to the errors' container object of a handler. :param error: The error to add. :type error: :class:`~cerberus.errors.ValidationError` @@ -371,8 +385,9 @@ class BaseErrorHandler(object): raise NotImplementedError def emit(self, error): - """ Optionally emits an error in the handler's format to a stream. - Or light a LED, or even shut down a power plant. + """ + Optionally emits an error in the handler's format to a stream. Or light a LED, + or even shut down a power plant. :param error: The error to emit. :type error: :class:`~cerberus.errors.ValidationError` @@ -380,14 +395,17 @@ class BaseErrorHandler(object): pass def end(self, validator): - """ Gets called when a validation ends. + """ + Gets called when a validation ends. :param validator: The calling validator. - :type validator: :class:`~cerberus.Validator` """ + :type validator: :class:`~cerberus.Validator` + """ pass def extend(self, errors): - """ Adds all errors to the handler's container object. + """ + Adds all errors to the handler's container object. :param errors: The errors to add. :type errors: :term:`iterable` of @@ -397,7 +415,8 @@ class BaseErrorHandler(object): self.add(error) def start(self, validator): - """ Gets called when a validation starts. + """ + Gets called when a validation starts. :param validator: The calling validator. :type validator: :class:`~cerberus.Validator` @@ -441,9 +460,9 @@ def encode_unicode(f): class BasicErrorHandler(BaseErrorHandler): - """ Models cerberus' legacy. Returns a :class:`dict`. When mangled - through :class:`str` a pretty-formatted representation of that - tree is returned. + """ + Models cerberus' legacy. Returns a :class:`dict`. When mangled through :class:`str` + a pretty-formatted representation of that tree is returned. """ messages = { @@ -459,7 +478,7 @@ class BasicErrorHandler(BaseErrorHandler): 0x23: "null value not allowed", 0x24: "must be of {constraint} type", 0x25: "must be of dict type", - 0x26: "length of list should be {constraint}, it is {0}", + 0x26: "length of list should be {0}, it is {1}", 0x27: "min length is {constraint}", 0x28: "max length is {constraint}", 0x41: "value does not match regex '{constraint}'", @@ -532,7 +551,8 @@ class BasicErrorHandler(BaseErrorHandler): ) def _insert_error(self, path, node): - """ Adds an error or sub-tree to :attr:tree. + """ + Adds an error or sub-tree to :attr:tree. :param path: Path to the error. :type path: Tuple of strings and integers. diff --git a/pipenv/vendor/cerberus/platform.py b/pipenv/vendor/cerberus/platform.py index 66b1d5f0..ae229c7b 100644 --- a/pipenv/vendor/cerberus/platform.py +++ b/pipenv/vendor/cerberus/platform.py @@ -2,6 +2,9 @@ import sys +if sys.flags.optimize == 2: + raise RuntimeError("Cerberus can't be run with Python's optimization level 2.") + PYTHON_VERSION = float(sys.version_info[0]) + float(sys.version_info[1]) / 10 diff --git a/pipenv/vendor/cerberus/schema.py b/pipenv/vendor/cerberus/schema.py index 305e59ff..3841d384 100644 --- a/pipenv/vendor/cerberus/schema.py +++ b/pipenv/vendor/cerberus/schema.py @@ -1,10 +1,9 @@ from __future__ import absolute_import -from copy import copy from warnings import warn -from cerberus import errors -from cerberus.platform import ( +from pipenv.vendor.cerberus import errors +from pipenv.vendor.cerberus.platform import ( _str_type, Callable, Hashable, @@ -12,7 +11,7 @@ from cerberus.platform import ( MutableMapping, Sequence, ) -from cerberus.utils import ( +from pipenv.vendor.cerberus.utils import ( get_Validator_class, validator_factory, mapping_hash, @@ -25,14 +24,15 @@ class _Abort(Exception): class SchemaError(Exception): - """ Raised when the validation schema is missing, has the wrong format or - contains errors. """ + """ + Raised when the validation schema is missing, has the wrong format or contains + errors.""" pass class DefinitionSchema(MutableMapping): - """ A dict-subclass for caching of validated schemas. """ + """A dict-subclass for caching of validated schemas.""" def __new__(cls, *args, **kwargs): if 'SchemaValidator' not in globals(): @@ -111,7 +111,10 @@ class DefinitionSchema(MutableMapping): self.schema[key] = value def __str__(self): - return str(self.schema) + if hasattr(self, "schema"): + return str(self.schema) + else: + return "No schema data is set yet." def copy(self): return self.__class__(self.validator, self.schema.copy()) @@ -131,7 +134,8 @@ class DefinitionSchema(MutableMapping): @classmethod def _expand_logical_shortcuts(cls, schema): - """ Expand agglutinated rules in a definition-schema. + """ + Expand agglutinated rules in a definition-schema. :param schema: The schema-definition to expand. :return: The expanded schema-definition. @@ -142,13 +146,13 @@ class DefinitionSchema(MutableMapping): ('allof_', 'anyof_', 'noneof_', 'oneof_') ) - for field in schema: - for of_rule in (x for x in schema[field] if is_of_rule(x)): + for field, rules in schema.items(): + for of_rule in [x for x in rules if is_of_rule(x)]: operator, rule = of_rule.split('_', 1) - schema[field].update({operator: []}) - for value in schema[field][of_rule]: - schema[field][operator].append({rule: value}) - del schema[field][of_rule] + rules.update({operator: []}) + for value in rules[of_rule]: + rules[operator].append({rule: value}) + del rules[of_rule] return schema @classmethod @@ -157,8 +161,10 @@ class DefinitionSchema(MutableMapping): return isinstance(schema[field], Mapping) and 'schema' in schema[field] def has_mapping_schema(): - """ Tries to determine heuristically if the schema-constraints are - aimed to mappings. """ + """ + Tries to determine heuristically if the schema-constraints are aimed to + mappings. + """ try: return all( isinstance(x, Mapping) for x in schema[field]['schema'].values() @@ -246,12 +252,14 @@ class DefinitionSchema(MutableMapping): self.validation_schema = SchemaValidationSchema(self.validator) def validate(self, schema=None): - """ Validates a schema that defines rules against supported rules. + """ + Validates a schema that defines rules against supported rules. :param schema: The schema to be validated as a legal cerberus schema according to the rules of the assigned Validator object. Raises a :class:`~cerberus.base.SchemaError` when an invalid - schema is encountered. """ + schema is encountered. + """ if schema is None: schema = self.schema _hash = (mapping_hash(schema), mapping_hash(self.validator.types_mapping)) @@ -263,15 +271,17 @@ class DefinitionSchema(MutableMapping): if isinstance(schema, _str_type): schema = self.validator.schema_registry.get(schema, schema) - if schema is None: - raise SchemaError(errors.SCHEMA_ERROR_MISSING) + test_schema = {} + for field, rules in schema.items(): + if isinstance(rules, _str_type): + test_schema[field] = rules_set_registry.get(rules, rules) + else: + test_rules = {} + for rule, constraint in rules.items(): + test_rules[rule.replace(" ", "_")] = constraint + test_schema[field] = test_rules - schema = copy(schema) - for field in schema: - if isinstance(schema[field], _str_type): - schema[field] = rules_set_registry.get(schema[field], schema[field]) - - if not self.schema_validator(schema, normalize=False): + if not self.schema_validator(test_schema, normalize=False): raise SchemaError(self.schema_validator.errors) @@ -300,8 +310,10 @@ class SchemaValidationSchema(UnvalidatedSchema): class SchemaValidatorMixin(object): - """ This validator mixin provides mechanics to validate schemas passed to a Cerberus - validator. """ + """ + This validator mixin provides mechanics to validate schemas passed to a Cerberus + validator. + """ def __init__(self, *args, **kwargs): kwargs.setdefault('known_rules_set_refs', set()) @@ -310,22 +322,22 @@ class SchemaValidatorMixin(object): @property def known_rules_set_refs(self): - """ The encountered references to rules set registry items. """ + """The encountered references to rules set registry items.""" return self._config['known_rules_set_refs'] @property def known_schema_refs(self): - """ The encountered references to schema registry items. """ + """The encountered references to schema registry items.""" return self._config['known_schema_refs'] @property def target_schema(self): - """ The schema that is being validated. """ + """The schema that is being validated.""" return self._config['target_schema'] @property def target_validator(self): - """ The validator whose schema is being validated. """ + """The validator whose schema is being validated.""" return self._config['target_validator'] def _check_with_bulk_schema(self, field, value): @@ -431,7 +443,7 @@ class SchemaValidatorMixin(object): return definition def _validate_logical(self, rule, field, value): - """ {'allowed': ('allof', 'anyof', 'noneof', 'oneof')} """ + """{'allowed': ('allof', 'anyof', 'noneof', 'oneof')}""" if not isinstance(value, Sequence): self._error(field, errors.BAD_TYPE) return @@ -461,59 +473,70 @@ class SchemaValidatorMixin(object): class Registry(object): - """ A registry to store and retrieve schemas and parts of it by a name - that can be used in validation schemas. + """ + A registry to store and retrieve schemas and parts of it by a name that can be used + in validation schemas. :param definitions: Optional, initial definitions. - :type definitions: any :term:`mapping` """ + :type definitions: any :term:`mapping` + """ def __init__(self, definitions={}): self._storage = {} self.extend(definitions) def add(self, name, definition): - """ Register a definition to the registry. Existing definitions are - replaced silently. + """ + Register a definition to the registry. Existing definitions are replaced + silently. :param name: The name which can be used as reference in a validation schema. :type name: :class:`str` :param definition: The definition. - :type definition: any :term:`mapping` """ + :type definition: any :term:`mapping` + """ self._storage[name] = self._expand_definition(definition) def all(self): - """ Returns a :class:`dict` with all registered definitions mapped to - their name. """ + """ + Returns a :class:`dict` with all registered definitions mapped to their name. + """ return self._storage def clear(self): - """ Purge all definitions in the registry. """ + """Purge all definitions in the registry.""" self._storage.clear() def extend(self, definitions): - """ Add several definitions at once. Existing definitions are + """ + Add several definitions at once. Existing definitions are replaced silently. :param definitions: The names and definitions. :type definitions: a :term:`mapping` or an :term:`iterable` with - two-value :class:`tuple` s """ + two-value :class:`tuple` s + """ for name, definition in dict(definitions).items(): self.add(name, definition) def get(self, name, default=None): - """ Retrieve a definition from the registry. + """ + Retrieve a definition from the registry. :param name: The reference that points to the definition. :type name: :class:`str` - :param default: Return value if the reference isn't registered. """ + :param default: Return value if the reference isn't registered. + """ return self._storage.get(name, default) def remove(self, *names): - """ Unregister definitions from the registry. + """ + Unregister definitions from the registry. :param names: The names of the definitions that are to be - unregistered. """ + unregistered. + """ for name in names: self._storage.pop(name, None) diff --git a/pipenv/vendor/cerberus/tests/__init__.py b/pipenv/vendor/cerberus/tests/__init__.py index c014f3b1..b5e66e06 100644 --- a/pipenv/vendor/cerberus/tests/__init__.py +++ b/pipenv/vendor/cerberus/tests/__init__.py @@ -4,13 +4,15 @@ import re import pytest -from cerberus import errors, Validator, SchemaError, DocumentError -from cerberus.tests.conftest import sample_schema +from pipenv.vendor.cerberus import errors, Validator, SchemaError, DocumentError +from pipenv.vendor.cerberus.tests.conftest import sample_schema def assert_exception(exception, document={}, schema=None, validator=None, msg=None): - """ Tests whether a specific exception is raised. Optionally also tests - whether the exception message is as expected. """ + """ + Tests whether a specific exception is raised. Optionally also tests whether the + exception message is as expected. + """ if validator is None: validator = Validator() if msg is None: @@ -22,14 +24,12 @@ def assert_exception(exception, document={}, schema=None, validator=None, msg=No def assert_schema_error(*args): - """ Tests whether a validation raises an exception due to a malformed - schema. """ + """Tests whether a validation raises an exception due to a malformed schema.""" assert_exception(SchemaError, *args) def assert_document_error(*args): - """ Tests whether a validation raises an exception due to a malformed - document. """ + """Tests whether a validation raises an exception due to a malformed document.""" assert_exception(DocumentError, *args) @@ -42,7 +42,7 @@ def assert_fail( errors=None, child_errors=None, ): - """ Tests whether a validation fails. """ + """Tests whether a validation fails.""" if validator is None: validator = Validator(sample_schema) result = validator(document, schema, update) @@ -72,7 +72,7 @@ def assert_fail( def assert_success(document, schema=None, validator=None, update=False): - """ Tests whether a validation succeeds. """ + """Tests whether a validation succeeds.""" if validator is None: validator = Validator(sample_schema) result = validator(document, schema, update) diff --git a/pipenv/vendor/cerberus/tests/conftest.py b/pipenv/vendor/cerberus/tests/conftest.py index 776c97bc..6c701060 100644 --- a/pipenv/vendor/cerberus/tests/conftest.py +++ b/pipenv/vendor/cerberus/tests/conftest.py @@ -4,7 +4,7 @@ from copy import deepcopy import pytest -from cerberus import Validator +from pipenv.vendor.cerberus import Validator @pytest.fixture diff --git a/pipenv/vendor/cerberus/tests/test_assorted.py b/pipenv/vendor/cerberus/tests/test_assorted.py index b84ef810..f45fdd5f 100644 --- a/pipenv/vendor/cerberus/tests/test_assorted.py +++ b/pipenv/vendor/cerberus/tests/test_assorted.py @@ -5,11 +5,11 @@ from pkg_resources import Distribution, DistributionNotFound from pytest import mark -from cerberus import TypeDefinition, Validator -from cerberus.tests import assert_fail, assert_success -from cerberus.utils import validator_factory -from cerberus.validator import BareValidator -from cerberus.platform import PYTHON_VERSION +from pipenv.vendor.cerberus import TypeDefinition, Validator +from pipenv.vendor.cerberus.tests import assert_fail, assert_success +from pipenv.vendor.cerberus.utils import validator_factory +from pipenv.vendor.cerberus.validator import BareValidator +from pipenv.vendor.cerberus.platform import PYTHON_VERSION if PYTHON_VERSION > 3 and PYTHON_VERSION < 3.4: diff --git a/pipenv/vendor/cerberus/tests/test_customization.py b/pipenv/vendor/cerberus/tests/test_customization.py index 8bc3f464..4dcb8dd0 100644 --- a/pipenv/vendor/cerberus/tests/test_customization.py +++ b/pipenv/vendor/cerberus/tests/test_customization.py @@ -2,9 +2,9 @@ from pytest import mark -import cerberus -from cerberus.tests import assert_fail, assert_success -from cerberus.tests.conftest import sample_schema +import pipenv.vendor.cerberus as cerberus +from pipenv.vendor.cerberus.tests import assert_fail, assert_success +from pipenv.vendor.cerberus.tests.conftest import sample_schema def test_contextual_data_preservation(): @@ -28,11 +28,12 @@ def test_contextual_data_preservation(): def test_docstring_parsing(): class CustomValidator(cerberus.Validator): def _validate_foo(self, argument, field, value): - """ {'type': 'zap'} """ + """{'type': 'zap'}""" pass def _validate_bar(self, value): - """ Test the barreness of a value. + """ + Test the barreness of a value. The rule's arguments are validated against this schema: {'type': 'boolean'} diff --git a/pipenv/vendor/cerberus/tests/test_errors.py b/pipenv/vendor/cerberus/tests/test_errors.py index e4d9b37a..6d8ffd6c 100644 --- a/pipenv/vendor/cerberus/tests/test_errors.py +++ b/pipenv/vendor/cerberus/tests/test_errors.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- -from cerberus import Validator, errors -from cerberus.tests import assert_fail +from pipenv.vendor.cerberus import Validator, errors +from pipenv.vendor.cerberus.tests import assert_fail ValidationError = errors.ValidationError @@ -101,6 +101,16 @@ def test_error_tree_from_anyof(validator): def test_nested_error_paths(validator): + # interpreters of the same version on some platforms showed different sort results + # over various runs: + def assert_has_all_errors(errors, *ref_errs): + for ref_err in ref_errs: + for error in errors: + if error == ref_err: + break + else: + raise AssertionError + schema = { 'a_dict': { 'keysrules': {'type': 'integer'}, @@ -114,27 +124,27 @@ def test_nested_error_paths(validator): } assert_fail(document, schema, validator=validator) - _det = validator.document_error_tree - _set = validator.schema_error_tree + det = validator.document_error_tree + set = validator.schema_error_tree - assert len(_det.errors) == 0 - assert len(_set.errors) == 0 + assert len(det.errors) == 0 + assert len(set.errors) == 0 - assert len(_det['a_dict'].errors) == 2 - assert len(_set['a_dict'].errors) == 0 + assert len(det['a_dict'].errors) == 2 + assert len(set['a_dict'].errors) == 0 - assert _det['a_dict'][0] is None - assert len(_det['a_dict']['one'].errors) == 1 - assert len(_det['a_dict'][2].errors) == 1 - assert len(_det['a_dict']['three'].errors) == 2 + assert det['a_dict'][0] is None + assert len(det['a_dict']['one'].errors) == 1 + assert len(det['a_dict'][2].errors) == 1 + assert len(det['a_dict']['three'].errors) == 2 - assert len(_set['a_dict']['keysrules'].errors) == 1 - assert len(_set['a_dict']['valuesrules'].errors) == 1 + assert len(set['a_dict']['keysrules'].errors) == 1 + assert len(set['a_dict']['valuesrules'].errors) == 1 - assert len(_set['a_dict']['keysrules']['type'].errors) == 2 - assert len(_set['a_dict']['valuesrules']['regex'].errors) == 2 + assert len(set['a_dict']['keysrules']['type'].errors) == 2 + assert len(set['a_dict']['valuesrules']['regex'].errors) == 2 - _ref_err = ValidationError( + ref_err1 = ValidationError( ('a_dict', 'one'), ('a_dict', 'keysrules', 'type'), errors.BAD_TYPE.code, @@ -143,10 +153,8 @@ def test_nested_error_paths(validator): 'one', (), ) - assert _det['a_dict']['one'].errors[0] == _ref_err - assert _set['a_dict']['keysrules']['type'].errors[0] == _ref_err - _ref_err = ValidationError( + ref_err2 = ValidationError( ('a_dict', 2), ('a_dict', 'valuesrules', 'regex'), errors.REGEX_MISMATCH.code, @@ -155,10 +163,8 @@ def test_nested_error_paths(validator): 'aBc', (), ) - assert _det['a_dict'][2].errors[0] == _ref_err - assert _set['a_dict']['valuesrules']['regex'].errors[0] == _ref_err - _ref_err = ValidationError( + ref_err3 = ValidationError( ('a_dict', 'three'), ('a_dict', 'keysrules', 'type'), errors.BAD_TYPE.code, @@ -167,10 +173,7 @@ def test_nested_error_paths(validator): 'three', (), ) - assert _det['a_dict']['three'].errors[0] == _ref_err - assert _set['a_dict']['keysrules']['type'].errors[1] == _ref_err - - _ref_err = ValidationError( + ref_err4 = ValidationError( ('a_dict', 'three'), ('a_dict', 'valuesrules', 'regex'), errors.REGEX_MISMATCH.code, @@ -179,20 +182,25 @@ def test_nested_error_paths(validator): 'abC', (), ) - assert _det['a_dict']['three'].errors[1] == _ref_err - assert _set['a_dict']['valuesrules']['regex'].errors[1] == _ref_err + assert det['a_dict'][2].errors[0] == ref_err2 + assert det['a_dict']['one'].errors[0] == ref_err1 + assert_has_all_errors(det['a_dict']['three'].errors, ref_err3, ref_err4) + assert_has_all_errors(set['a_dict']['keysrules']['type'].errors, ref_err1, ref_err3) + assert_has_all_errors( + set['a_dict']['valuesrules']['regex'].errors, ref_err2, ref_err4 + ) - assert len(_det['a_list'].errors) == 1 - assert len(_det['a_list'][0].errors) == 1 - assert _det['a_list'][1] is None - assert len(_det['a_list'][2].errors) == 3 - assert len(_set['a_list'].errors) == 0 - assert len(_set['a_list']['schema'].errors) == 1 - assert len(_set['a_list']['schema']['type'].errors) == 1 - assert len(_set['a_list']['schema']['oneof'][0]['regex'].errors) == 1 - assert len(_set['a_list']['schema']['oneof'][1]['regex'].errors) == 1 + assert len(det['a_list'].errors) == 1 + assert len(det['a_list'][0].errors) == 1 + assert det['a_list'][1] is None + assert len(det['a_list'][2].errors) == 3 + assert len(set['a_list'].errors) == 0 + assert len(set['a_list']['schema'].errors) == 1 + assert len(set['a_list']['schema']['type'].errors) == 1 + assert len(set['a_list']['schema']['oneof'][0]['regex'].errors) == 1 + assert len(set['a_list']['schema']['oneof'][1]['regex'].errors) == 1 - _ref_err = ValidationError( + ref_err5 = ValidationError( ('a_list', 0), ('a_list', 'schema', 'type'), errors.BAD_TYPE.code, @@ -201,10 +209,7 @@ def test_nested_error_paths(validator): 0, (), ) - assert _det['a_list'][0].errors[0] == _ref_err - assert _set['a_list']['schema']['type'].errors[0] == _ref_err - - _ref_err = ValidationError( + ref_err6 = ValidationError( ('a_list', 2), ('a_list', 'schema', 'oneof'), errors.ONEOF.code, @@ -213,10 +218,7 @@ def test_nested_error_paths(validator): 'abC', (), ) - assert _det['a_list'][2].errors[0] == _ref_err - assert _set['a_list']['schema']['oneof'].errors[0] == _ref_err - - _ref_err = ValidationError( + ref_err7 = ValidationError( ('a_list', 2), ('a_list', 'schema', 'oneof', 0, 'regex'), errors.REGEX_MISMATCH.code, @@ -225,10 +227,7 @@ def test_nested_error_paths(validator): 'abC', (), ) - assert _det['a_list'][2].errors[1] == _ref_err - assert _set['a_list']['schema']['oneof'][0]['regex'].errors[0] == _ref_err - - _ref_err = ValidationError( + ref_err8 = ValidationError( ('a_list', 2), ('a_list', 'schema', 'oneof', 1, 'regex'), errors.REGEX_MISMATCH.code, @@ -237,8 +236,38 @@ def test_nested_error_paths(validator): 'abC', (), ) - assert _det['a_list'][2].errors[2] == _ref_err - assert _set['a_list']['schema']['oneof'][1]['regex'].errors[0] == _ref_err + + assert det['a_list'][0].errors[0] == ref_err5 + assert_has_all_errors(det['a_list'][2].errors, ref_err6, ref_err7, ref_err8) + assert set['a_list']['schema']['oneof'].errors[0] == ref_err6 + assert set['a_list']['schema']['oneof'][0]['regex'].errors[0] == ref_err7 + assert set['a_list']['schema']['oneof'][1]['regex'].errors[0] == ref_err8 + assert set['a_list']['schema']['type'].errors[0] == ref_err5 + + +def test_path_resolution_for_registry_references(): + class CustomValidator(Validator): + def _normalize_coerce_custom(self, value): + raise Exception("Failed coerce") + + validator = CustomValidator() + validator.schema_registry.add( + "schema1", {"child": {"type": "boolean", "coerce": "custom"}} + ) + validator.schema = {"parent": {"schema": "schema1"}} + validator.validate({"parent": {"child": "["}}) + + expected = { + 'parent': [ + { + 'child': [ + "must be of boolean type", + "field 'child' cannot be coerced: Failed coerce", + ] + } + ] + } + assert validator.errors == expected def test_queries(): @@ -321,3 +350,16 @@ def test_basic_error_of_errors(validator): }, ] } + + +def test_wrong_amount_of_items(validator): + # https://github.com/pyeve/cerberus/issues/505 + validator.schema = { + 'test_list': { + 'type': 'list', + 'required': True, + 'items': [{'type': 'string'}, {'type': 'string'}], + } + } + validator({'test_list': ['test']}) + assert validator.errors == {'test_list': ["length of list should be 2, it is 1"]} diff --git a/pipenv/vendor/cerberus/tests/test_normalization.py b/pipenv/vendor/cerberus/tests/test_normalization.py index adc281ef..43191504 100644 --- a/pipenv/vendor/cerberus/tests/test_normalization.py +++ b/pipenv/vendor/cerberus/tests/test_normalization.py @@ -5,8 +5,8 @@ from tempfile import NamedTemporaryFile from pytest import mark -from cerberus import Validator, errors -from cerberus.tests import ( +from pipenv.vendor.cerberus import Validator, errors +from pipenv.vendor.cerberus.tests import ( assert_fail, assert_has_error, assert_normalized, diff --git a/pipenv/vendor/cerberus/tests/test_registries.py b/pipenv/vendor/cerberus/tests/test_registries.py index b628952d..3cb3e6ef 100644 --- a/pipenv/vendor/cerberus/tests/test_registries.py +++ b/pipenv/vendor/cerberus/tests/test_registries.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- -from cerberus import schema_registry, rules_set_registry, Validator -from cerberus.tests import ( +from pipenv.vendor.cerberus import schema_registry, rules_set_registry, Validator +from pipenv.vendor.cerberus.tests import ( assert_fail, assert_normalized, assert_schema_error, diff --git a/pipenv/vendor/cerberus/tests/test_schema.py b/pipenv/vendor/cerberus/tests/test_schema.py index 84e50946..a44a4042 100644 --- a/pipenv/vendor/cerberus/tests/test_schema.py +++ b/pipenv/vendor/cerberus/tests/test_schema.py @@ -4,9 +4,9 @@ import re import pytest -from cerberus import Validator, errors, SchemaError -from cerberus.schema import UnvalidatedSchema -from cerberus.tests import assert_schema_error +from pipenv.vendor.cerberus import Validator, errors, SchemaError +from pipenv.vendor.cerberus.schema import UnvalidatedSchema +from pipenv.vendor.cerberus.tests import assert_schema_error def test_empty_schema(): @@ -88,7 +88,7 @@ def test_validated_schema_cache(): v = Validator({'foozifix': {'coerce': int}}) assert len(v._valid_schemas) == cache_size - max_cache_size = 160 + max_cache_size = 163 assert cache_size <= max_cache_size, ( "There's an unexpected high amount (%s) of cached valid " "definition schemas. Unless you added further tests, " @@ -172,3 +172,7 @@ def test_anyof_check_with(): assert validator.schema == { 'field': {'anyof': [{'check_with': foo}, {'check_with': bar}]} } + + +def test_rulename_space_is_normalized(): + Validator(schema={"field": {"default setter": lambda x: x, "type": "string"}}) diff --git a/pipenv/vendor/cerberus/tests/test_utils.py b/pipenv/vendor/cerberus/tests/test_utils.py index 6ab38790..f09cae6b 100644 --- a/pipenv/vendor/cerberus/tests/test_utils.py +++ b/pipenv/vendor/cerberus/tests/test_utils.py @@ -1,4 +1,4 @@ -from cerberus.utils import compare_paths_lt +from pipenv.vendor.cerberus.utils import compare_paths_lt def test_compare_paths(): diff --git a/pipenv/vendor/cerberus/tests/test_validation.py b/pipenv/vendor/cerberus/tests/test_validation.py index ead79517..801bf6fa 100644 --- a/pipenv/vendor/cerberus/tests/test_validation.py +++ b/pipenv/vendor/cerberus/tests/test_validation.py @@ -9,8 +9,8 @@ from string import ascii_lowercase from pytest import mark -from cerberus import errors, Validator -from cerberus.tests import ( +from pipenv.vendor.cerberus import errors, Validator +from pipenv.vendor.cerberus.tests import ( assert_bad_type, assert_document_error, assert_fail, @@ -18,7 +18,7 @@ from cerberus.tests import ( assert_not_has_error, assert_success, ) -from cerberus.tests.conftest import sample_schema +from pipenv.vendor.cerberus.tests.conftest import sample_schema def test_empty_document(): @@ -479,7 +479,7 @@ def test_array_unallowed(): (field, 'allowed'), errors.UNALLOWED_VALUES, ['agent', 'client', 'vendor'], - ['profit'], + (('profit',),), ), ) @@ -596,6 +596,11 @@ def test_regex(validator): ) +def test_regex_with_flag(): + assert_success({"item": "hOly grAil"}, {"item": {"regex": "(?i)holy grail"}}) + assert_fail({"item": "hOly grAil"}, {"item": {"regex": "holy grail"}}) + + def test_a_list_of_dicts(): assert_success( { @@ -742,7 +747,7 @@ def test_custom_datatype(): def test_custom_datatype_rule(): class MyValidator(Validator): def _validate_min_number(self, min_number, field, value): - """ {'type': 'number'} """ + """{'type': 'number'}""" if value < min_number: self._error(field, 'Below the min') @@ -769,7 +774,7 @@ def test_custom_datatype_rule(): def test_custom_validator(): class MyValidator(Validator): def _validate_isodd(self, isodd, field, value): - """ {'type': 'boolean'} """ + """{'type': 'boolean'}""" if isodd and not bool(value & 1): self._error(field, 'Not an odd number') @@ -1114,15 +1119,15 @@ def test_options_passed_to_nested_validators(validator): def test_self_root_document(): - """ Make sure self.root_document is always the root document. - See: + """ + Make sure self.root_document is always the root document. See: * https://github.com/pyeve/cerberus/pull/42 * https://github.com/pyeve/eve/issues/295 """ class MyValidator(Validator): def _validate_root_doc(self, root_doc, field, value): - """ {'type': 'boolean'} """ + """{'type': 'boolean'}""" if 'sub' not in self.root_document or len(self.root_document['sub']) != 2: self._error(field, 'self.context is not the root doc!') @@ -1251,8 +1256,10 @@ def test_unicode_allowed(): @mark.skipif(sys.version_info[0] < 3, reason='requires python 3.x') def test_unicode_allowed_py3(): - """ All strings are unicode in Python 3.x. Input doc and schema - have equal strings and validation yield success.""" + """ + All strings are unicode in Python 3.x. Input doc and schema have equal strings and + validation yield success. + """ # issue 280 doc = {'letters': u'♄εℓł☺'} @@ -1262,9 +1269,11 @@ def test_unicode_allowed_py3(): @mark.skipif(sys.version_info[0] > 2, reason='requires python 2.x') def test_unicode_allowed_py2(): - """ Python 2.x encodes value of allowed using default encoding if - the string includes characters outside ASCII range. Produced string - does not match input which is an unicode string.""" + """ + Python 2.x encodes value of allowed using default encoding if the string includes + characters outside ASCII range. Produced string does not match input which is an + unicode string. + """ # issue 280 doc = {'letters': u'♄εℓł☺'} @@ -1646,7 +1655,7 @@ def test_dependencies_on_boolean_field_with_value_in_list(): def test_document_path(): class DocumentPathTester(Validator): def _validate_trail(self, constraint, field, value): - """ {'type': 'boolean'} """ + """{'type': 'boolean'}""" test_doc = self.root_document for crumb in self.document_path: test_doc = test_doc[crumb] @@ -1946,3 +1955,21 @@ def test_require_all_and_exclude(): assert_success({'foo': 'value'}, schema, validator) assert_success({'bar': 'value'}, schema, validator) assert_fail({'foo': 'value', 'bar': 'value'}, schema, validator) + + +def test_allowed_when_passing_list_of_dicts(): + # https://github.com/pyeve/cerberus/issues/524 + doc = {'letters': [{'some': 'dict'}]} + schema = {'letters': {'type': 'list', 'allowed': ['a', 'b', 'c']}} + + assert_fail( + doc, + schema, + error=( + 'letters', + ('letters', 'allowed'), + errors.UNALLOWED_VALUES, + ['a', 'b', 'c'], + (({'some': 'dict'},),), + ), + ) diff --git a/pipenv/vendor/cerberus/utils.py b/pipenv/vendor/cerberus/utils.py index 5a015d64..908c5fa1 100644 --- a/pipenv/vendor/cerberus/utils.py +++ b/pipenv/vendor/cerberus/utils.py @@ -2,7 +2,7 @@ from __future__ import absolute_import from collections import namedtuple -from cerberus.platform import _int_types, _str_type, Mapping, Sequence, Set +from pipenv.vendor.cerberus.platform import _int_types, _str_type, Mapping, Sequence, Set TypeDefinition = namedtuple('TypeDefinition', 'name,included_types,excluded_types') @@ -50,7 +50,7 @@ def drop_item_from_tuple(t, i): def get_Validator_class(): global Validator if 'Validator' not in globals(): - from cerberus.validator import Validator + from pipenv.vendor.cerberus.validator import Validator return Validator @@ -59,10 +59,11 @@ def mapping_hash(schema): def mapping_to_frozenset(mapping): - """ Be aware that this treats any sequence type with the equal members as - equal. As it is used to identify equality of schemas, this can be - considered okay as definitions are semantically equal regardless the - container type. """ + """ + Be aware that this treats any sequence type with the equal members as equal. As it + is used to identify equality of schemas, this can be considered okay as definitions + are semantically equal regardless the container type. + """ aggregation = {} @@ -102,9 +103,10 @@ class readonly_classproperty(property): def validator_factory(name, bases=None, namespace={}): - """ Dynamically create a :class:`~cerberus.Validator` subclass. - Docstrings of mixin-classes will be added to the resulting - class' one if ``__doc__`` is not in :obj:`namespace`. + """ + Dynamically create a :class:`~cerberus.Validator` subclass. + Docstrings of mixin-classes will be added to the resulting class' one if ``__doc__`` + is not in :obj:`namespace`. :param name: The name of the new class. :type name: :class:`str` diff --git a/pipenv/vendor/cerberus/validator.py b/pipenv/vendor/cerberus/validator.py index ed1c1536..3d6b2122 100644 --- a/pipenv/vendor/cerberus/validator.py +++ b/pipenv/vendor/cerberus/validator.py @@ -16,8 +16,8 @@ from datetime import date, datetime import re from warnings import warn -from cerberus import errors -from cerberus.platform import ( +from pipenv.vendor.cerberus import errors +from pipenv.vendor.cerberus.platform import ( _int_types, _str_type, Container, @@ -27,13 +27,13 @@ from cerberus.platform import ( Sequence, Sized, ) -from cerberus.schema import ( +from pipenv.vendor.cerberus.schema import ( schema_registry, rules_set_registry, DefinitionSchema, SchemaError, ) -from cerberus.utils import drop_item_from_tuple, readonly_classproperty, TypeDefinition +from pipenv.vendor.cerberus.utils import drop_item_from_tuple, readonly_classproperty, TypeDefinition toy_error_handler = errors.ToyErrorHandler() @@ -52,20 +52,23 @@ def dummy_for_rule_validation(rule_constraints): class DocumentError(Exception): - """ Raised when the target document is missing or has the wrong format """ + """Raised when the target document is missing or has the wrong format""" pass class _SchemaRuleTypeError(Exception): - """ Raised when a schema (list) validation encounters a mapping. - Not supposed to be used outside this module. """ + """ + Raised when a schema (list) validation encounters a mapping. + Not supposed to be used outside this module. + """ pass class BareValidator(object): - """ Validator class. Normalizes and/or validates any mapping against a + """ + Validator class. Normalizes and/or validates any mapping against a validation-schema which is provided as an argument at class instantiation or upon calling the :meth:`~cerberus.Validator.validate`, :meth:`~cerberus.Validator.validated` or @@ -112,12 +115,16 @@ class BareValidator(object): """ # noqa: E501 mandatory_validations = ('nullable',) - """ Rules that are evaluated on any field, regardless whether defined in - the schema or not. - Type: :class:`tuple` """ + """ + Rules that are evaluated on any field, regardless whether defined in the schema or + not. + Type: :class:`tuple` + """ priority_validations = ('nullable', 'readonly', 'type', 'empty') - """ Rules that will be processed in that order before any other. - Type: :class:`tuple` """ + """ + Rules that will be processed in that order before any other. + Type: :class:`tuple` + """ types_mapping = { 'binary': TypeDefinition('binary', (bytes, bytearray), ()), 'boolean': TypeDefinition('boolean', (bool,), ()), @@ -130,16 +137,21 @@ class BareValidator(object): 'list': TypeDefinition('list', (Sequence,), (_str_type,)), 'number': TypeDefinition('number', (_int_types, float), (bool,)), 'set': TypeDefinition('set', (set,), ()), - 'string': TypeDefinition('string', (_str_type), ()), + 'string': TypeDefinition('string', (_str_type,), ()), } - """ This mapping holds all available constraints for the type rule and - their assigned :class:`~cerberus.TypeDefinition`. """ + """ + This mapping holds all available constraints for the type rule and their assigned + :class:`~cerberus.TypeDefinition`. + """ _valid_schemas = set() - """ A :class:`set` of hashes derived from validation schemas that are - legit for a particular ``Validator`` class. """ + """ + A :class:`set` of hashes derived from validation schemas that are legit for a + particular ``Validator`` class. + """ def __init__(self, *args, **kwargs): - """ The arguments will be treated as with this signature: + """ + The arguments will be treated as with this signature: __init__(self, schema=None, ignore_none_values=False, allow_unknown=False, require_all=False, @@ -205,7 +217,7 @@ class BareValidator(object): raise RuntimeError('Invalid error_handler.') def __store_config(self, args, kwargs): - """ Assign args to kwargs and store configuration. """ + """Assign args to kwargs and store configuration.""" signature = ( 'schema', 'ignore_none_values', @@ -226,11 +238,12 @@ class BareValidator(object): @classmethod def clear_caches(cls): - """ Purge the cache of known valid schemas. """ + """Purge the cache of known valid schemas.""" cls._valid_schemas.clear() def _error(self, *args): - """ Creates and adds one or multiple errors. + """ + Creates and adds one or multiple errors. :param args: Accepts different argument's signatures. @@ -290,15 +303,17 @@ class BareValidator(object): if not rule: constraint = None else: - field_definitions = self._resolve_rules_set(self.schema[field]) + rules_set = self._resolve_rules_set( + self._resolve_schema(self.schema)[field] + ) if rule == 'nullable': - constraint = field_definitions.get(rule, False) + constraint = rules_set.get(rule, False) elif rule == 'required': - constraint = field_definitions.get(rule, self.require_all) - if rule not in field_definitions: + constraint = rules_set.get(rule, self.require_all) + if rule not in rules_set: schema_path = "__require_all__" else: - constraint = field_definitions[rule] + constraint = rules_set[rule] value = self.document.get(field) @@ -308,9 +323,10 @@ class BareValidator(object): self._error([self.recent_error]) def _get_child_validator(self, document_crumb=None, schema_crumb=None, **kwargs): - """ Creates a new instance of Validator-(sub-)class. All initial - parameters of the parent are passed to the initialization, unless - a parameter is given as an explicit *keyword*-parameter. + """ + Creates a new instance of Validator-(sub-)class. All initial parameters of the + parent are passed to the initialization, unless a parameter is given as an + explicit *keyword*-parameter. :param document_crumb: Extends the :attr:`~cerberus.Validator.document_path` @@ -364,8 +380,8 @@ class BareValidator(object): return result def _drop_nodes_from_errorpaths(self, _errors, dp_items, sp_items): - """ Removes nodes by index from an errorpath, relatively to the - basepaths of self. + """ + Removes nodes by index from an errorpath, relatively to the basepaths of self. :param errors: A list of :class:`errors.ValidationError` instances. :param dp_items: A list of integers, pointing at the nodes to drop from @@ -387,8 +403,9 @@ class BareValidator(object): self._drop_nodes_from_errorpaths(error.child_errors, dp_items, sp_items) def _lookup_field(self, path): - """ Searches for a field as defined by path. This method is used by the - ``dependency`` evaluation logic. + """ + Searches for a field as defined by path. This method is used by the + ``dependency`` evaluation logic. :param path: Path elements are separated by a ``.``. A leading ``^`` indicates that the path relates to the document root, @@ -433,11 +450,12 @@ class BareValidator(object): @property def allow_unknown(self): - """ If ``True`` unknown fields that are not defined in the schema will - be ignored. If a mapping with a validation schema is given, any - undefined field will be validated against its rules. - Also see :ref:`allowing-the-unknown`. - Type: :class:`bool` or any :term:`mapping` """ + """ + If ``True`` unknown fields that are not defined in the schema will be ignored. + If a mapping with a validation schema is given, any undefined field will be + validated against its rules. Also see :ref:`allowing-the-unknown`. + Type: :class:`bool` or any :term:`mapping` + """ return self._config.get('allow_unknown', False) @allow_unknown.setter @@ -448,9 +466,10 @@ class BareValidator(object): @property def require_all(self): - """ If ``True`` known fields that are defined in the schema will - be required. - Type: :class:`bool` """ + """ + If ``True`` known fields that are defined in the schema will be required. + Type: :class:`bool` + """ return self._config.get('require_all', False) @require_all.setter @@ -459,14 +478,18 @@ class BareValidator(object): @property def errors(self): - """ The errors of the last processing formatted by the handler that is - bound to :attr:`~cerberus.Validator.error_handler`. """ + """ + The errors of the last processing formatted by the handler that is bound to + :attr:`~cerberus.Validator.error_handler`. + """ return self.error_handler(self._errors) @property def ignore_none_values(self): - """ Whether to not process :obj:`None`-values in a document or not. - Type: :class:`bool` """ + """ + Whether to not process :obj:`None`-values in a document or not. + Type: :class:`bool` + """ return self._config.get('ignore_none_values', False) @ignore_none_values.setter @@ -475,14 +498,16 @@ class BareValidator(object): @property def is_child(self): - """ ``True`` for child-validators obtained with + """ + ``True`` for child-validators obtained with :meth:`~cerberus.Validator._get_child_validator`. - Type: :class:`bool` """ + Type: :class:`bool` + """ return self._config.get('is_child', False) @property def _is_normalized(self): - """ ``True`` if the document is already normalized. """ + """``True`` if the document is already normalized.""" return self._config.get('_is_normalized', False) @_is_normalized.setter @@ -491,9 +516,12 @@ class BareValidator(object): @property def purge_unknown(self): - """ If ``True``, unknown fields will be deleted from the document - unless a validation is called with disabled normalization. - Also see :ref:`purging-unknown-fields`. Type: :class:`bool` """ + """ + If ``True``, unknown fields will be deleted from the document unless a + validation is called with disabled normalization. Also see + :ref:`purging-unknown-fields`. + Type: :class:`bool` + """ return self._config.get('purge_unknown', False) @purge_unknown.setter @@ -502,9 +530,11 @@ class BareValidator(object): @property def purge_readonly(self): - """ If ``True``, fields declared as readonly will be deleted from the - document unless a validation is called with disabled normalization. - Type: :class:`bool` """ + """ + If ``True``, fields declared as readonly will be deleted from the document + unless a validation is called with disabled normalization. + Type: :class:`bool` + """ return self._config.get('purge_readonly', False) @purge_readonly.setter @@ -513,26 +543,34 @@ class BareValidator(object): @property def root_allow_unknown(self): - """ The :attr:`~cerberus.Validator.allow_unknown` attribute of the - first level ancestor of a child validator. """ + """ + The :attr:`~cerberus.Validator.allow_unknown` attribute of the first level + ancestor of a child validator. + """ return self._config.get('root_allow_unknown', self.allow_unknown) @property def root_require_all(self): - """ The :attr:`~cerberus.Validator.require_all` attribute of - the first level ancestor of a child validator. """ + """ + The :attr:`~cerberus.Validator.require_all` attribute of the first level + ancestor of a child validator. + """ return self._config.get('root_require_all', self.require_all) @property def root_document(self): - """ The :attr:`~cerberus.Validator.document` attribute of the - first level ancestor of a child validator. """ + """ + The :attr:`~cerberus.Validator.document` attribute of the first level ancestor + of a child validator. + """ return self._config.get('root_document', self.document) @property def rules_set_registry(self): - """ The registry that holds referenced rules sets. - Type: :class:`~cerberus.Registry` """ + """ + The registry that holds referenced rules sets. + Type: :class:`~cerberus.Registry` + """ return self._config.get('rules_set_registry', rules_set_registry) @rules_set_registry.setter @@ -541,15 +579,19 @@ class BareValidator(object): @property def root_schema(self): - """ The :attr:`~cerberus.Validator.schema` attribute of the - first level ancestor of a child validator. """ + """ + The :attr:`~cerberus.Validator.schema` attribute of the first level ancestor of + a child validator. + """ return self._config.get('root_schema', self.schema) @property def schema(self): - """ The validation schema of a validator. When a schema is passed to - a method, it replaces this attribute. - Type: any :term:`mapping` or :obj:`None` """ + """ + The validation schema of a validator. When a schema is passed to a method, it + replaces this attribute. + Type: any :term:`mapping` or :obj:`None` + """ return self._schema @schema.setter @@ -563,8 +605,10 @@ class BareValidator(object): @property def schema_registry(self): - """ The registry that holds referenced schemas. - Type: :class:`~cerberus.Registry` """ + """ + The registry that holds referenced schemas. + Type: :class:`~cerberus.Registry` + """ return self._config.get('schema_registry', schema_registry) @schema_registry.setter @@ -575,8 +619,10 @@ class BareValidator(object): # in the API docs @readonly_classproperty def types(cls): - """ The constraints that can be used for the 'type' rule. - Type: A tuple of strings. """ + """ + The constraints that can be used for the 'type' rule. + Type: A tuple of strings. + """ redundant_types = set(cls.types_mapping) & set(cls._types_from_methods) if redundant_types: warn( @@ -611,9 +657,10 @@ class BareValidator(object): self.error_handler.start(self) def _drop_remaining_rules(self, *rules): - """ Drops rules from the queue of the rules that still need to be - evaluated for the currently processed field. - If no arguments are given, the whole queue is emptied. + """ + Drops rules from the queue of the rules that still need to be evaluated for the + currently processed field. If no arguments are given, the whole queue is + emptied. """ if rules: for rule in rules: @@ -627,8 +674,8 @@ class BareValidator(object): # # Normalizing def normalized(self, document, schema=None, always_return_document=False): - """ Returns the document normalized according to the specified rules - of a schema. + """ + Returns the document normalized according to the specified rules of a schema. :param document: The document to normalize. :type document: any :term:`mapping` @@ -673,13 +720,15 @@ class BareValidator(object): return mapping def _normalize_coerce(self, mapping, schema): - """ {'oneof': [ - {'type': 'callable'}, - {'type': 'list', - 'schema': {'oneof': [{'type': 'callable'}, - {'type': 'string'}]}}, - {'type': 'string'} - ]} """ + """ + {'oneof': [ + {'type': 'callable'}, + {'type': 'list', + 'schema': {'oneof': [{'type': 'callable'}, + {'type': 'string'}]}}, + {'type': 'string'} + ]} + """ error = errors.COERCION_FAILED for field in mapping: @@ -853,7 +902,7 @@ class BareValidator(object): @staticmethod def _normalize_purge_unknown(mapping, schema): - """ {'type': 'boolean'} """ + """{'type': 'boolean'}""" for field in [x for x in mapping if x not in schema]: mapping.pop(field) return mapping @@ -873,19 +922,21 @@ class BareValidator(object): return mapping def _normalize_rename(self, mapping, schema, field): - """ {'type': 'hashable'} """ + """{'type': 'hashable'}""" if 'rename' in schema[field]: mapping[schema[field]['rename']] = mapping[field] del mapping[field] def _normalize_rename_handler(self, mapping, schema, field): - """ {'oneof': [ - {'type': 'callable'}, - {'type': 'list', - 'schema': {'oneof': [{'type': 'callable'}, - {'type': 'string'}]}}, - {'type': 'string'} - ]} """ + """ + {'oneof': [ + {'type': 'callable'}, + {'type': 'list', + 'schema': {'oneof': [{'type': 'callable'}, + {'type': 'string'}]}}, + {'type': 'string'} + ]} + """ if 'rename_handler' not in schema[field]: return new_name = self.__normalize_coerce( @@ -947,14 +998,16 @@ class BareValidator(object): known_fields_states.add(fields_processing_state) def _normalize_default(self, mapping, schema, field): - """ {'nullable': True} """ + """{'nullable': True}""" mapping[field] = schema[field]['default'] def _normalize_default_setter(self, mapping, schema, field): - """ {'oneof': [ - {'type': 'callable'}, - {'type': 'string'} - ]} """ + """ + {'oneof': [ + {'type': 'callable'}, + {'type': 'string'} + ]} + """ if 'default_setter' in schema[field]: setter = schema[field]['default_setter'] if isinstance(setter, _str_type): @@ -964,8 +1017,8 @@ class BareValidator(object): # # Validating def validate(self, document, schema=None, update=False, normalize=True): - """ Normalizes and validates a mapping against a validation-schema of - defined rules. + """ + Normalizes and validates a mapping against a validation-schema of defined rules. :param document: The document to normalize. :type document: any :term:`mapping` @@ -1008,9 +1061,10 @@ class BareValidator(object): __call__ = validate def validated(self, *args, **kwargs): - """ Wrapper around :meth:`~cerberus.Validator.validate` that returns - the normalized and validated document or :obj:`None` if validation - failed. """ + """ + Wrapper around :meth:`~cerberus.Validator.validate` that returns the normalized + and validated document or :obj:`None` if validation failed. + """ always_return_document = kwargs.pop('always_return_document', False) self.validate(*args, **kwargs) if self._errors and not always_return_document: @@ -1034,7 +1088,7 @@ class BareValidator(object): self._error(field, errors.UNKNOWN_FIELD) def __validate_definitions(self, definitions, field): - """ Validate a field's value against its defined rules. """ + """Validate a field's value against its defined rules.""" def validate_rule(rule): validator = self.__get_rule_handler('validate', rule) @@ -1082,23 +1136,25 @@ class BareValidator(object): ) def _validate_allowed(self, allowed_values, field, value): - """ {'type': 'container'} """ + """{'type': 'container'}""" if isinstance(value, Iterable) and not isinstance(value, _str_type): - unallowed = set(value) - set(allowed_values) + unallowed = tuple(x for x in value if x not in allowed_values) if unallowed: - self._error(field, errors.UNALLOWED_VALUES, list(unallowed)) + self._error(field, errors.UNALLOWED_VALUES, unallowed) else: if value not in allowed_values: self._error(field, errors.UNALLOWED_VALUE, value) def _validate_check_with(self, checks, field, value): - """ {'oneof': [ - {'type': 'callable'}, - {'type': 'list', - 'schema': {'oneof': [{'type': 'callable'}, - {'type': 'string'}]}}, - {'type': 'string'} - ]} """ + """ + {'oneof': [ + {'type': 'callable'}, + {'type': 'list', + 'schema': {'oneof': [{'type': 'callable'}, + {'type': 'string'}]}}, + {'type': 'string'} + ]} + """ if isinstance(checks, _str_type): try: value_checker = self.__get_rule_handler('check_with', checks) @@ -1118,7 +1174,7 @@ class BareValidator(object): checks(field, value, self._error) def _validate_contains(self, expected_values, field, value): - """ {'empty': False } """ + """{'empty': False }""" if not isinstance(value, Iterable): return @@ -1134,8 +1190,7 @@ class BareValidator(object): self._error(field, errors.MISSING_MEMBERS, missing_values) def _validate_dependencies(self, dependencies, field, value): - """ {'type': ('dict', 'hashable', 'list'), - 'check_with': 'dependencies'} """ + """{'type': ('dict', 'hashable', 'list'), 'check_with': 'dependencies'}""" if isinstance(dependencies, _str_type) or not isinstance( dependencies, (Iterable, Mapping) ): @@ -1178,7 +1233,7 @@ class BareValidator(object): self._error(field, errors.DEPENDENCIES_FIELD, dependency) def _validate_empty(self, empty, field, value): - """ {'type': 'boolean'} """ + """{'type': 'boolean'}""" if isinstance(value, Sized) and len(value) == 0: self._drop_remaining_rules( 'allowed', @@ -1193,8 +1248,7 @@ class BareValidator(object): self._error(field, errors.EMPTY_NOT_ALLOWED) def _validate_excludes(self, excluded_fields, field, value): - """ {'type': ('hashable', 'list'), - 'schema': {'type': 'hashable'}} """ + """{'type': ('hashable', 'list'), 'schema': {'type': 'hashable'}}""" if isinstance(excluded_fields, Hashable): excluded_fields = [excluded_fields] @@ -1217,7 +1271,7 @@ class BareValidator(object): self._error(field, errors.EXCLUDES_FIELD, exclusion_str) def _validate_forbidden(self, forbidden_values, field, value): - """ {'type': 'list'} """ + """{'type': 'list'}""" if isinstance(value, Sequence) and not isinstance(value, _str_type): forbidden = set(value) & set(forbidden_values) if forbidden: @@ -1227,7 +1281,7 @@ class BareValidator(object): self._error(field, errors.FORBIDDEN_VALUE, value) def _validate_items(self, items, field, values): - """ {'type': 'list', 'check_with': 'items'} """ + """{'type': 'list', 'check_with': 'items'}""" if len(items) != len(values): self._error(field, errors.ITEMS_LENGTH, len(items), len(values)) else: @@ -1247,8 +1301,10 @@ class BareValidator(object): self._error(field, errors.BAD_ITEMS, validator._errors) def __validate_logical(self, operator, definitions, field, value): - """ Validates value against all definitions and logs errors according - to the operator. """ + """ + Validates value against all definitions and logs errors according to the + operator. + """ valid_counter = 0 _errors = errors.ErrorList() @@ -1272,31 +1328,31 @@ class BareValidator(object): return valid_counter, _errors def _validate_anyof(self, definitions, field, value): - """ {'type': 'list', 'logical': 'anyof'} """ + """{'type': 'list', 'logical': 'anyof'}""" valids, _errors = self.__validate_logical('anyof', definitions, field, value) if valids < 1: self._error(field, errors.ANYOF, _errors, valids, len(definitions)) def _validate_allof(self, definitions, field, value): - """ {'type': 'list', 'logical': 'allof'} """ + """{'type': 'list', 'logical': 'allof'}""" valids, _errors = self.__validate_logical('allof', definitions, field, value) if valids < len(definitions): self._error(field, errors.ALLOF, _errors, valids, len(definitions)) def _validate_noneof(self, definitions, field, value): - """ {'type': 'list', 'logical': 'noneof'} """ + """{'type': 'list', 'logical': 'noneof'}""" valids, _errors = self.__validate_logical('noneof', definitions, field, value) if valids > 0: self._error(field, errors.NONEOF, _errors, valids, len(definitions)) def _validate_oneof(self, definitions, field, value): - """ {'type': 'list', 'logical': 'oneof'} """ + """{'type': 'list', 'logical': 'oneof'}""" valids, _errors = self.__validate_logical('oneof', definitions, field, value) if valids != 1: self._error(field, errors.ONEOF, _errors, valids, len(definitions)) def _validate_max(self, max_value, field, value): - """ {'nullable': False } """ + """{'nullable': False }""" try: if value > max_value: self._error(field, errors.MAX_VALUE) @@ -1304,7 +1360,7 @@ class BareValidator(object): pass def _validate_min(self, min_value, field, value): - """ {'nullable': False } """ + """{'nullable': False }""" try: if value < min_value: self._error(field, errors.MIN_VALUE) @@ -1312,19 +1368,19 @@ class BareValidator(object): pass def _validate_maxlength(self, max_length, field, value): - """ {'type': 'integer'} """ + """{'type': 'integer'}""" if isinstance(value, Iterable) and len(value) > max_length: self._error(field, errors.MAX_LENGTH, len(value)) _validate_meta = dummy_for_rule_validation('') def _validate_minlength(self, min_length, field, value): - """ {'type': 'integer'} """ + """{'type': 'integer'}""" if isinstance(value, Iterable) and len(value) < min_length: self._error(field, errors.MIN_LENGTH, len(value)) def _validate_nullable(self, nullable, field, value): - """ {'type': 'boolean'} """ + """{'type': 'boolean'}""" if value is None: if not nullable: self._error(field, errors.NOT_NULLABLE) @@ -1345,8 +1401,11 @@ class BareValidator(object): ) def _validate_keysrules(self, schema, field, value): - """ {'type': ['dict', 'string'], 'check_with': 'bulk_schema', - 'forbidden': ['rename', 'rename_handler']} """ + """ + {'type': ['dict', 'string'], + 'check_with': 'bulk_schema', + 'forbidden': ['rename', 'rename_handler']} + """ if isinstance(value, Mapping): validator = self._get_child_validator( document_crumb=field, @@ -1358,7 +1417,7 @@ class BareValidator(object): self._error(field, errors.KEYSRULES, validator._errors) def _validate_readonly(self, readonly, field, value): - """ {'type': 'boolean'} """ + """{'type': 'boolean'}""" if readonly: if not self._is_normalized: self._error(field, errors.READONLY_FIELD) @@ -1375,7 +1434,7 @@ class BareValidator(object): self._drop_remaining_rules() def _validate_regex(self, pattern, field, value): - """ {'type': 'string'} """ + """{'type': 'string'}""" if not isinstance(value, _str_type): return if not pattern.endswith('$'): @@ -1389,7 +1448,8 @@ class BareValidator(object): _validate_require_all = dummy_for_rule_validation(""" {'type': 'boolean'} """) def __validate_required_fields(self, document): - """ Validates that required fields are not missing. + """ + Validates that required fields are not missing. :param document: The document being validated. """ @@ -1424,9 +1484,11 @@ class BareValidator(object): self._error(field, errors.REQUIRED_FIELD) def _validate_schema(self, schema, field, value): - """ {'type': ['dict', 'string'], - 'anyof': [{'check_with': 'schema'}, - {'check_with': 'bulk_schema'}]} """ + """ + {'type': ['dict', 'string'], + 'anyof': [{'check_with': 'schema'}, + {'check_with': 'bulk_schema'}]} + """ if schema is None: return @@ -1472,8 +1534,10 @@ class BareValidator(object): self._error(field, errors.SEQUENCE_SCHEMA, validator._errors) def _validate_type(self, data_type, field, value): - """ {'type': ['string', 'list'], - 'check_with': 'type'} """ + """ + {'type': ['string', 'list'], + 'check_with': 'type'} + """ if not data_type: return @@ -1504,8 +1568,11 @@ class BareValidator(object): self._drop_remaining_rules() def _validate_valuesrules(self, schema, field, value): - """ {'type': ['dict', 'string'], 'check_with': 'bulk_schema', - 'forbidden': ['rename', 'rename_handler']} """ + """ + {'type': ['dict', 'string'], + 'check_with': 'bulk_schema', + 'forbidden': ['rename', 'rename_handler']} + """ schema_crumb = (field, 'valuesrules') if isinstance(value, Mapping): validator = self._get_child_validator( @@ -1523,7 +1590,7 @@ RULE_SCHEMA_SEPARATOR = "The rule's arguments are validated against this schema: class InspectedValidator(type): - """ Metaclass for all validators """ + """Metaclass for all validators""" def __new__(cls, *args): if '__doc__' not in args[2]: diff --git a/pipenv/vendor/certifi/LICENSE b/pipenv/vendor/certifi/LICENSE index 802b53ff..c2fda9a2 100644 --- a/pipenv/vendor/certifi/LICENSE +++ b/pipenv/vendor/certifi/LICENSE @@ -1,4 +1,4 @@ -This packge contains a modified version of ca-bundle.crt: +This package contains a modified version of ca-bundle.crt: ca-bundle.crt -- Bundle of CA Root Certificates diff --git a/pipenv/vendor/certifi/__init__.py b/pipenv/vendor/certifi/__init__.py index 632db8e1..eebdf888 100644 --- a/pipenv/vendor/certifi/__init__.py +++ b/pipenv/vendor/certifi/__init__.py @@ -1,3 +1,3 @@ -from .core import where +from .core import contents, where -__version__ = "2019.03.09" +__version__ = "2021.05.30" diff --git a/pipenv/vendor/certifi/__main__.py b/pipenv/vendor/certifi/__main__.py index 5f1da0dd..244882d9 100644 --- a/pipenv/vendor/certifi/__main__.py +++ b/pipenv/vendor/certifi/__main__.py @@ -1,2 +1,12 @@ -from certifi import where -print(where()) +import argparse + +from pipenv.vendor.certifi import contents, where + +parser = argparse.ArgumentParser() +parser.add_argument("-c", "--contents", action="store_true") +args = parser.parse_args() + +if args.contents: + print(contents()) +else: + print(where()) diff --git a/pipenv/vendor/certifi/cacert.pem b/pipenv/vendor/certifi/cacert.pem index 84636dde..96e2fc65 100644 --- a/pipenv/vendor/certifi/cacert.pem +++ b/pipenv/vendor/certifi/cacert.pem @@ -58,38 +58,6 @@ AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== -----END CERTIFICATE----- -# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only -# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only -# Label: "Verisign Class 3 Public Primary Certification Authority - G3" -# Serial: 206684696279472310254277870180966723415 -# MD5 Fingerprint: cd:68:b6:a7:c7:c4:ce:75:e0:1d:4f:57:44:61:92:09 -# SHA1 Fingerprint: 13:2d:0d:45:53:4b:69:97:cd:b2:d5:c3:39:e2:55:76:60:9b:5c:c6 -# SHA256 Fingerprint: eb:04:cf:5e:b1:f3:9a:fa:76:2f:2b:b1:20:f2:96:cb:a5:20:c1:b9:7d:b1:58:95:65:b8:1c:b9:a1:7b:72:44 ------BEGIN CERTIFICATE----- -MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw -CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl -cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu -LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT -aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp -dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD -VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT -aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ -bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu -IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg -LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b -N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t -KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu -kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm -CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ -Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu -imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te -2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe -DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC -/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p -F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt -TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== ------END CERTIFICATE----- - # Issuer: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited # Subject: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited # Label: "Entrust.net Premium 2048 Secure Server CA" @@ -152,39 +120,6 @@ ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp -----END CERTIFICATE----- -# Issuer: CN=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network -# Subject: CN=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network -# Label: "AddTrust External Root" -# Serial: 1 -# MD5 Fingerprint: 1d:35:54:04:85:78:b0:3f:42:42:4d:bf:20:73:0a:3f -# SHA1 Fingerprint: 02:fa:f3:e2:91:43:54:68:60:78:57:69:4d:f5:e4:5b:68:85:18:68 -# SHA256 Fingerprint: 68:7f:a4:51:38:22:78:ff:f0:c8:b1:1f:8d:43:d5:76:67:1c:6e:b2:bc:ea:b4:13:fb:83:d9:65:d0:6d:2f:f2 ------BEGIN CERTIFICATE----- -MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU -MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs -IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290 -MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux -FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h -bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v -dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt -H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9 -uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX -mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX -a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN -E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0 -WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD -VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0 -Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU -cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx -IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN -AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH -YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 -6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC -Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX -c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a -mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= ------END CERTIFICATE----- - # Issuer: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc. # Subject: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc. # Label: "Entrust Root Certification Authority" @@ -220,112 +155,6 @@ eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m 0vdXcDazv/wor3ElhVsT/h5/WrQ8 -----END CERTIFICATE----- -# Issuer: CN=GeoTrust Global CA O=GeoTrust Inc. -# Subject: CN=GeoTrust Global CA O=GeoTrust Inc. -# Label: "GeoTrust Global CA" -# Serial: 144470 -# MD5 Fingerprint: f7:75:ab:29:fb:51:4e:b7:77:5e:ff:05:3c:99:8e:f5 -# SHA1 Fingerprint: de:28:f4:a4:ff:e5:b9:2f:a3:c5:03:d1:a3:49:a7:f9:96:2a:82:12 -# SHA256 Fingerprint: ff:85:6a:2d:25:1d:cd:88:d3:66:56:f4:50:12:67:98:cf:ab:aa:de:40:79:9c:72:2d:e4:d2:b5:db:36:a7:3a ------BEGIN CERTIFICATE----- -MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT -MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i -YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG -EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg -R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9 -9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq -fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv -iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU -1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+ -bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW -MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA -ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l -uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn -Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS -tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF -PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un -hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV -5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw== ------END CERTIFICATE----- - -# Issuer: CN=GeoTrust Universal CA O=GeoTrust Inc. -# Subject: CN=GeoTrust Universal CA O=GeoTrust Inc. -# Label: "GeoTrust Universal CA" -# Serial: 1 -# MD5 Fingerprint: 92:65:58:8b:a2:1a:31:72:73:68:5c:b4:a5:7a:07:48 -# SHA1 Fingerprint: e6:21:f3:35:43:79:05:9a:4b:68:30:9d:8a:2f:74:22:15:87:ec:79 -# SHA256 Fingerprint: a0:45:9b:9f:63:b2:25:59:f5:fa:5d:4c:6d:b3:f9:f7:2f:f1:93:42:03:35:78:f0:73:bf:1d:1b:46:cb:b9:12 ------BEGIN CERTIFICATE----- -MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEW -MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVy -c2FsIENBMB4XDTA0MDMwNDA1MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UE -BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xHjAcBgNVBAMTFUdlb1RydXN0 -IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKYV -VaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9tJPi8 -cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTT -QjOgNB0eRXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFh -F7em6fgemdtzbvQKoiFs7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2v -c7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d8Lsrlh/eezJS/R27tQahsiFepdaVaH/w -mZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7VqnJNk22CDtucvc+081xd -VHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3CgaRr0BHdCX -teGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZ -f9hBZ3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfRe -Bi9Fi1jUIxaS5BZuKGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+ -nhutxx9z3SxPGWX9f5NAEC7S8O08ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB -/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0XG0D08DYj3rWMB8GA1UdIwQY -MBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG -9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc -aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fX -IwjhmF7DWgh2qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzyn -ANXH/KttgCJwpQzgXQQpAvvLoJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0z -uzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsKxr2EoyNB3tZ3b4XUhRxQ4K5RirqN -Pnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxFKyDuSN/n3QmOGKja -QI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2DFKW -koRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9 -ER/frslKxfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQt -DF4JbAiXfKM9fJP/P6EUp8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/Sfuvm -bJxPgWp6ZKy7PtXny3YuxadIwVyQD8vIP/rmMuGNG2+k5o7Y+SlIis5z/iw= ------END CERTIFICATE----- - -# Issuer: CN=GeoTrust Universal CA 2 O=GeoTrust Inc. -# Subject: CN=GeoTrust Universal CA 2 O=GeoTrust Inc. -# Label: "GeoTrust Universal CA 2" -# Serial: 1 -# MD5 Fingerprint: 34:fc:b8:d0:36:db:9e:14:b3:c2:f2:db:8f:e4:94:c7 -# SHA1 Fingerprint: 37:9a:19:7b:41:85:45:35:0c:a6:03:69:f3:3c:2e:af:47:4f:20:79 -# SHA256 Fingerprint: a0:23:4f:3b:c8:52:7c:a5:62:8e:ec:81:ad:5d:69:89:5d:a5:68:0d:c9:1d:1c:b8:47:7f:33:f8:78:b9:5b:0b ------BEGIN CERTIFICATE----- -MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEW -MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVy -c2FsIENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYD -VQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1 -c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC -AQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0DE81 -WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUG -FF+3Qs17j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdq -XbboW0W63MOhBW9Wjo8QJqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxL -se4YuU6W3Nx2/zu+z18DwPw76L5GG//aQMJS9/7jOvdqdzXQ2o3rXhhqMcceujwb -KNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2WP0+GfPtDCapkzj4T8Fd -IgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP20gaXT73 -y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRt -hAAnZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgoc -QIgfksILAAX/8sgCSqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4 -Lt1ZrtmhN79UNdxzMk+MBB4zsslG8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAfBgNV -HSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8EBAMCAYYwDQYJ -KoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z -dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQ -L1EuxBRa3ugZ4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgr -Fg5fNuH8KrUwJM/gYwx7WBr+mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSo -ag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpqA1Ihn0CoZ1Dy81of398j9tx4TuaY -T1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpgY+RdM4kX2TGq2tbz -GDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiPpm8m -1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJV -OCiNUW7dFGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH -6aLcr34YEoP9VhdBLtUpgn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwX -QMAJKOSLakhT2+zNVVXxxvjpoixMptEmX36vWkzaH6byHCx+rgIW0lbQL1dTR+iS ------END CERTIFICATE----- - # Issuer: CN=AAA Certificate Services O=Comodo CA Limited # Subject: CN=AAA Certificate Services O=Comodo CA Limited # Label: "Comodo AAA Services root" @@ -359,48 +188,6 @@ l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3 smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== -----END CERTIFICATE----- -# Issuer: CN=QuoVadis Root Certification Authority O=QuoVadis Limited OU=Root Certification Authority -# Subject: CN=QuoVadis Root Certification Authority O=QuoVadis Limited OU=Root Certification Authority -# Label: "QuoVadis Root CA" -# Serial: 985026699 -# MD5 Fingerprint: 27:de:36:fe:72:b7:00:03:00:9d:f4:f0:1e:6c:04:24 -# SHA1 Fingerprint: de:3f:40:bd:50:93:d3:9b:6c:60:f6:da:bc:07:62:01:00:89:76:c9 -# SHA256 Fingerprint: a4:5e:de:3b:bb:f0:9c:8a:e1:5c:72:ef:c0:72:68:d6:93:a2:1c:99:6f:d5:1e:67:ca:07:94:60:fd:6d:88:73 ------BEGIN CERTIFICATE----- -MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJC -TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0 -aWZpY2F0aW9uIEF1dGhvcml0eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0 -aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAzMTkxODMzMzNaFw0yMTAzMTcxODMz -MzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUw -IwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVR -dW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Yp -li4kVEAkOPcahdxYTMukJ0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2D -rOpm2RgbaIr1VxqYuvXtdj182d6UajtLF8HVj71lODqV0D1VNk7feVcxKh7YWWVJ -WCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeLYzcS19Dsw3sgQUSj7cug -F+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWenAScOospU -xbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCC -Ak4wPQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVv -dmFkaXNvZmZzaG9yZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREw -ggENMIIBCQYJKwYBBAG+WAABMIH7MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNl -IG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBh -c3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFy -ZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh -Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYI -KwYBBQUHAgEWFmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3T -KbkGGew5Oanwl4Rqy+/fMIGuBgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rq -y+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1p -dGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYD -VQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6tlCL -MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSk -fnIYj9lofFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf8 -7C9TqnN7Az10buYWnuulLsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1R -cHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2xgI4JVrmcGmD+XcHXetwReNDWXcG31a0y -mQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi5upZIof4l/UO/erMkqQW -xFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi5nrQNiOK -SnQ2+Q== ------END CERTIFICATE----- - # Issuer: CN=QuoVadis Root CA 2 O=QuoVadis Limited # Subject: CN=QuoVadis Root CA 2 O=QuoVadis Limited # Label: "QuoVadis Root CA 2" @@ -516,33 +303,6 @@ JRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ6rBK+1YWc26sTfcioU+tHXot RSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAiFL39vmwLAw== -----END CERTIFICATE----- -# Issuer: CN=Sonera Class2 CA O=Sonera -# Subject: CN=Sonera Class2 CA O=Sonera -# Label: "Sonera Class 2 Root CA" -# Serial: 29 -# MD5 Fingerprint: a3:ec:75:0f:2e:88:df:fa:48:01:4e:0b:5c:48:6f:fb -# SHA1 Fingerprint: 37:f7:6d:e6:07:7c:90:c5:b1:3e:93:1a:b7:41:10:b4:f2:e4:9a:27 -# SHA256 Fingerprint: 79:08:b4:03:14:c1:38:10:0b:51:8d:07:35:80:7f:fb:fc:f8:51:8a:00:95:33:71:05:ba:38:6b:15:3d:d9:27 ------BEGIN CERTIFICATE----- -MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEP -MA0GA1UEChMGU29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAx -MDQwNjA3Mjk0MFoXDTIxMDQwNjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNV -BAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJhIENsYXNzMiBDQTCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3/Ei9vX+ALTU74W+o -Z6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybTdXnt -5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s -3TmVToMGf+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2Ej -vOr7nQKV0ba5cTppCD8PtOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu -8nYybieDwnPz3BjotJPqdURrBGAgcVeHnfO+oJAjPYok4doh28MCAwEAAaMzMDEw -DwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITTXjwwCwYDVR0PBAQDAgEG -MA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt0jSv9zil -zqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/ -3DEIcbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvD -FNr450kkkdAdavphOe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6 -Tk6ezAyNlNzZRZxe7EJQY670XcSxEtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2 -ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLHllpwrN9M ------END CERTIFICATE----- - # Issuer: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com # Subject: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com # Label: "XRamp Global CA Root" @@ -640,46 +400,6 @@ VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q= -----END CERTIFICATE----- -# Issuer: O=Government Root Certification Authority -# Subject: O=Government Root Certification Authority -# Label: "Taiwan GRCA" -# Serial: 42023070807708724159991140556527066870 -# MD5 Fingerprint: 37:85:44:53:32:45:1f:20:f0:f3:95:e1:25:c4:43:4e -# SHA1 Fingerprint: f4:8b:11:bf:de:ab:be:94:54:20:71:e6:41:de:6b:be:88:2b:40:b9 -# SHA256 Fingerprint: 76:00:29:5e:ef:e8:5b:9e:1f:d6:24:db:76:06:2a:aa:ae:59:81:8a:54:d2:77:4c:d4:c0:b2:c0:11:31:e1:b3 ------BEGIN CERTIFICATE----- -MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/ -MQswCQYDVQQGEwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmlj -YXRpb24gQXV0aG9yaXR5MB4XDTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1ow -PzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dvdmVybm1lbnQgUm9vdCBDZXJ0aWZp -Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB -AJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qNw8XR -IePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1q -gQdW8or5BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKy -yhwOeYHWtXBiCAEuTk8O1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAts -F/tnyMKtsc2AtJfcdgEWFelq16TheEfOhtX7MfP6Mb40qij7cEwdScevLJ1tZqa2 -jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wovJ5pGfaENda1UhhXcSTvx -ls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7Q3hub/FC -VGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHK -YS1tB6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoH -EgKXTiCQ8P8NHuJBO9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThN -Xo+EHWbNxWCWtFJaBYmOlXqYwZE8lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1Ud -DgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNVHRMEBTADAQH/MDkGBGcqBwAE -MTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg209yewDL7MTqK -UWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ -TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyf -qzvS/3WXy6TjZwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaK -ZEk9GhiHkASfQlK3T8v+R0F2Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFE -JPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlUD7gsL0u8qV1bYH+Mh6XgUmMqvtg7 -hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6QzDxARvBMB1uUO07+1 -EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+HbkZ6Mm -nD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WX -udpVBrkk7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44Vbnz -ssQwmSNOXfJIoRIM3BKQCZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDe -LMDDav7v3Aun+kbfYNucpllQdSNpc5Oy+fwC00fmcc4QAu4njIT/rEUNE1yDMuAl -pYYsfPQS ------END CERTIFICATE----- - # Issuer: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com # Subject: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com # Label: "DigiCert Assured ID Root CA" @@ -771,36 +491,6 @@ vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep +OkuE6N36B9K -----END CERTIFICATE----- -# Issuer: CN=Class 2 Primary CA O=Certplus -# Subject: CN=Class 2 Primary CA O=Certplus -# Label: "Certplus Class 2 Primary CA" -# Serial: 177770208045934040241468760488327595043 -# MD5 Fingerprint: 88:2c:8c:52:b8:a2:3c:f3:f7:bb:03:ea:ae:ac:42:0b -# SHA1 Fingerprint: 74:20:74:41:72:9c:dd:92:ec:79:31:d8:23:10:8d:c2:81:92:e2:bb -# SHA256 Fingerprint: 0f:99:3c:8a:ef:97:ba:af:56:87:14:0e:d5:9a:d1:82:1b:b4:af:ac:f0:aa:9a:58:b5:d5:7a:33:8a:3a:fb:cb ------BEGIN CERTIFICATE----- -MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAw -PTELMAkGA1UEBhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFz -cyAyIFByaW1hcnkgQ0EwHhcNOTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9 -MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2VydHBsdXMxGzAZBgNVBAMTEkNsYXNz -IDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANxQ -ltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR5aiR -VhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyL -kcAbmXuZVg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCd -EgETjdyAYveVqUSISnFOYFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yas -H7WLO7dDWWuwJKZtkIvEcupdM5i3y95ee++U8Rs+yskhwcWYAqqi9lt3m/V+llU0 -HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRMECDAGAQH/AgEKMAsGA1Ud -DwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJYIZIAYb4 -QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMu -Y29tL0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/ -AN9WM2K191EBkOvDP9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8 -yfFC82x/xXp8HVGIutIKPidd3i1RTtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMR -FcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+7UCmnYR0ObncHoUW2ikbhiMA -ybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW//1IMwrh3KWB -kJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7 -l7+ijrRU ------END CERTIFICATE----- - # Issuer: CN=DST Root CA X3 O=Digital Signature Trust Co. # Subject: CN=DST Root CA X3 O=Digital Signature Trust Co. # Label: "DST Root CA X3" @@ -911,104 +601,6 @@ hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ubDgEj8Z+7fNzcbBGXJbLy tGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u -----END CERTIFICATE----- -# Issuer: CN=GeoTrust Primary Certification Authority O=GeoTrust Inc. -# Subject: CN=GeoTrust Primary Certification Authority O=GeoTrust Inc. -# Label: "GeoTrust Primary Certification Authority" -# Serial: 32798226551256963324313806436981982369 -# MD5 Fingerprint: 02:26:c3:01:5e:08:30:37:43:a9:d0:7d:cf:37:e6:bf -# SHA1 Fingerprint: 32:3c:11:8e:1b:f7:b8:b6:52:54:e2:e2:10:0d:d6:02:90:37:f0:96 -# SHA256 Fingerprint: 37:d5:10:06:c5:12:ea:ab:62:64:21:f1:ec:8c:92:01:3f:c5:f8:2a:e9:8e:e5:33:eb:46:19:b8:de:b4:d0:6c ------BEGIN CERTIFICATE----- -MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBY -MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo -R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx -MjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQK -Ew1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQcmltYXJ5IENlcnRp -ZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC -AQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9 -AWbK7hWNb6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjA -ZIVcFU2Ix7e64HXprQU9nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE0 -7e9GceBrAqg1cmuXm2bgyxx5X9gaBGgeRwLmnWDiNpcB3841kt++Z8dtd1k7j53W -kBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGttm/81w7a4DSwDRp35+MI -mO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G -A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJ -KoZIhvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ1 -6CePbJC/kRYkRj5KTs4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl -4b7UVXGYNTq+k+qurUKykG/g/CFNNWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6K -oKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHaFloxt/m0cYASSJlyc1pZU8Fj -UjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG1riR/aYNKxoU -AT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk= ------END CERTIFICATE----- - -# Issuer: CN=thawte Primary Root CA O=thawte, Inc. OU=Certification Services Division/(c) 2006 thawte, Inc. - For authorized use only -# Subject: CN=thawte Primary Root CA O=thawte, Inc. OU=Certification Services Division/(c) 2006 thawte, Inc. - For authorized use only -# Label: "thawte Primary Root CA" -# Serial: 69529181992039203566298953787712940909 -# MD5 Fingerprint: 8c:ca:dc:0b:22:ce:f5:be:72:ac:41:1a:11:a8:d8:12 -# SHA1 Fingerprint: 91:c6:d6:ee:3e:8a:c8:63:84:e5:48:c2:99:29:5c:75:6c:81:7b:81 -# SHA256 Fingerprint: 8d:72:2f:81:a9:c1:13:c0:79:1d:f1:36:a2:96:6d:b2:6c:95:0a:97:1d:b4:6b:41:99:f4:ea:54:b7:8b:fb:9f ------BEGIN CERTIFICATE----- -MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCB -qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf -Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw -MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV -BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMzYw -NzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j -LjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYG -A1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl -IG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqG -SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsoPD7gFnUnMekz52hWXMJEEUMDSxuaPFs -W0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ1CRfBsDMRJSUjQJib+ta -3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGcq/gcfomk -6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6 -Sk/KaAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94J -NqR32HuHUETVPm4pafs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBA -MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XP -r87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAeRHAS7ORtvzw6WfU -DW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeEuzLlQRHAd9mz -YJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX -xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2 -/qxAeeWsEG89jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/ -LHbTY5xZ3Y+m4Q6gLkH3LpVHz7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7 -jVaMaA== ------END CERTIFICATE----- - -# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G5 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2006 VeriSign, Inc. - For authorized use only -# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G5 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2006 VeriSign, Inc. - For authorized use only -# Label: "VeriSign Class 3 Public Primary Certification Authority - G5" -# Serial: 33037644167568058970164719475676101450 -# MD5 Fingerprint: cb:17:e4:31:67:3e:e2:09:fe:45:57:93:f3:0a:fa:1c -# SHA1 Fingerprint: 4e:b6:d5:78:49:9b:1c:cf:5f:58:1e:ad:56:be:3d:9b:67:44:a5:e5 -# SHA256 Fingerprint: 9a:cf:ab:7e:43:c8:d8:80:d0:6b:26:2a:94:de:ee:e4:b4:65:99:89:c3:d0:ca:f1:9b:af:64:05:e4:1a:b7:df ------BEGIN CERTIFICATE----- -MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB -yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL -ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp -U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW -ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0 -aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL -MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW -ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln -biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp -U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y -aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1 -nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex -t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz -SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG -BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+ -rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/ -NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E -BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH -BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy -aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv -MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE -p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y -5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK -WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ -4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N -hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq ------END CERTIFICATE----- - # Issuer: CN=SecureTrust CA O=SecureTrust Corporation # Subject: CN=SecureTrust CA O=SecureTrust Corporation # Label: "SecureTrust CA" @@ -1157,38 +749,6 @@ fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= -----END CERTIFICATE----- -# Issuer: CN=OISTE WISeKey Global Root GA CA O=WISeKey OU=Copyright (c) 2005/OISTE Foundation Endorsed -# Subject: CN=OISTE WISeKey Global Root GA CA O=WISeKey OU=Copyright (c) 2005/OISTE Foundation Endorsed -# Label: "OISTE WISeKey Global Root GA CA" -# Serial: 86718877871133159090080555911823548314 -# MD5 Fingerprint: bc:6c:51:33:a7:e9:d3:66:63:54:15:72:1b:21:92:93 -# SHA1 Fingerprint: 59:22:a1:e1:5a:ea:16:35:21:f8:98:39:6a:46:46:b0:44:1b:0f:a9 -# SHA256 Fingerprint: 41:c9:23:86:6a:b4:ca:d6:b7:ad:57:80:81:58:2e:02:07:97:a6:cb:df:4f:ff:78:ce:83:96:b3:89:37:d7:f5 ------BEGIN CERTIFICATE----- -MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCB -ijELMAkGA1UEBhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHly -aWdodCAoYykgMjAwNTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl -ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQSBDQTAeFw0w -NTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYDVQQGEwJDSDEQMA4G -A1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIwIAYD -VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBX -SVNlS2V5IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A -MIIBCgKCAQEAy0+zAJs9Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxR -VVuuk+g3/ytr6dTqvirdqFEr12bDYVxgAsj1znJ7O7jyTmUIms2kahnBAbtzptf2 -w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbDd50kc3vkDIzh2TbhmYsF -mQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ/yxViJGg -4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t9 -4B3RLoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYw -DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQw -EAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOx -SPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vImMMkQyh2I+3QZH4VFvbBsUfk2 -ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4+vg1YFkCExh8 -vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa -hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZi -Fj4A4xylNoEYokxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ -/L7fCg0= ------END CERTIFICATE----- - # Issuer: CN=Certigna O=Dhimyotis # Subject: CN=Certigna O=Dhimyotis # Label: "Certigna" @@ -1219,36 +779,6 @@ t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== -----END CERTIFICATE----- -# Issuer: CN=Deutsche Telekom Root CA 2 O=Deutsche Telekom AG OU=T-TeleSec Trust Center -# Subject: CN=Deutsche Telekom Root CA 2 O=Deutsche Telekom AG OU=T-TeleSec Trust Center -# Label: "Deutsche Telekom Root CA 2" -# Serial: 38 -# MD5 Fingerprint: 74:01:4a:91:b1:08:c4:58:ce:47:cd:f0:dd:11:53:08 -# SHA1 Fingerprint: 85:a4:08:c0:9c:19:3e:5d:51:58:7d:cd:d6:13:30:fd:8c:de:37:bf -# SHA256 Fingerprint: b6:19:1a:50:d0:c3:97:7f:7d:a9:9b:cd:aa:c8:6a:22:7d:ae:b9:67:9e:c7:0b:a3:b0:c9:d9:22:71:c1:70:d3 ------BEGIN CERTIFICATE----- -MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEc -MBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2Vj -IFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENB -IDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5MjM1OTAwWjBxMQswCQYDVQQGEwJE -RTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxl -U2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290 -IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEU -ha88EOQ5bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhC -QN/Po7qCWWqSG6wcmtoIKyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1Mjwr -rFDa1sPeg5TKqAyZMg4ISFZbavva4VhYAUlfckE8FQYBjl2tqriTtM2e66foai1S -NNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aKSe5TBY8ZTNXeWHmb0moc -QqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTVjlsB9WoH -txa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAP -BgNVHRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOC -AQEAlGRZrTlk5ynrE/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756Abrsp -tJh6sTtU6zkXR34ajgv8HzFZMQSyzhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpa -IzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8rZ7/gFnkm0W09juwzTkZmDLl -6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4Gdyd1Lx+4ivn+ -xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU -Cm26OWMohpLzGITY+9HPBVZkVw== ------END CERTIFICATE----- - # Issuer: CN=Cybertrust Global Root O=Cybertrust, Inc # Subject: CN=Cybertrust Global Root O=Cybertrust, Inc # Label: "Cybertrust Global Root" @@ -1348,185 +878,6 @@ i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN 9u6wWk5JRFRYX0KD -----END CERTIFICATE----- -# Issuer: CN=GeoTrust Primary Certification Authority - G3 O=GeoTrust Inc. OU=(c) 2008 GeoTrust Inc. - For authorized use only -# Subject: CN=GeoTrust Primary Certification Authority - G3 O=GeoTrust Inc. OU=(c) 2008 GeoTrust Inc. - For authorized use only -# Label: "GeoTrust Primary Certification Authority - G3" -# Serial: 28809105769928564313984085209975885599 -# MD5 Fingerprint: b5:e8:34:36:c9:10:44:58:48:70:6d:2e:83:d4:b8:05 -# SHA1 Fingerprint: 03:9e:ed:b8:0b:e7:a0:3c:69:53:89:3b:20:d2:d9:32:3a:4c:2a:fd -# SHA256 Fingerprint: b4:78:b8:12:25:0d:f8:78:63:5c:2a:a7:ec:7d:15:5e:aa:62:5e:e8:29:16:e2:cd:29:43:61:88:6c:d1:fb:d4 ------BEGIN CERTIFICATE----- -MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCB -mDELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsT -MChjKSAyMDA4IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s -eTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhv -cml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIzNTk1OVowgZgxCzAJ -BgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg -MjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0 -BgNVBAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg -LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz -+uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5jK/BGvESyiaHAKAxJcCGVn2TAppMSAmUm -hsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdEc5IiaacDiGydY8hS2pgn -5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3CIShwiP/W -JmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exAL -DmKudlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZC -huOl1UcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw -HQYDVR0OBBYEFMR5yo6hTgMdHNxr2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IB -AQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9cr5HqQ6XErhK8WTTOd8lNNTB -zU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbEAp7aDHdlDkQN -kv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD -AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUH -SJsMC8tJP33st/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2G -spki4cErx5z481+oghLrGREt ------END CERTIFICATE----- - -# Issuer: CN=thawte Primary Root CA - G2 O=thawte, Inc. OU=(c) 2007 thawte, Inc. - For authorized use only -# Subject: CN=thawte Primary Root CA - G2 O=thawte, Inc. OU=(c) 2007 thawte, Inc. - For authorized use only -# Label: "thawte Primary Root CA - G2" -# Serial: 71758320672825410020661621085256472406 -# MD5 Fingerprint: 74:9d:ea:60:24:c4:fd:22:53:3e:cc:3a:72:d9:29:4f -# SHA1 Fingerprint: aa:db:bc:22:23:8f:c4:01:a1:27:bb:38:dd:f4:1d:db:08:9e:f0:12 -# SHA256 Fingerprint: a4:31:0d:50:af:18:a6:44:71:90:37:2a:86:af:af:8b:95:1f:fb:43:1d:83:7f:1e:56:88:b4:59:71:ed:15:57 ------BEGIN CERTIFICATE----- -MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDEL -MAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMp -IDIwMDcgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAi -BgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMjAeFw0wNzExMDUwMDAw -MDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh -d3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBGb3Ig -YXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9v -dCBDQSAtIEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/ -BebfowJPDQfGAFG6DAJSLSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6 -papu+7qzcMBniKI11KOasf2twu8x+qi58/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8E -BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmtgAMADna3+FGO6Lts6K -DPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUNG4k8VIZ3 -KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41ox -XZ3Krr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg== ------END CERTIFICATE----- - -# Issuer: CN=thawte Primary Root CA - G3 O=thawte, Inc. OU=Certification Services Division/(c) 2008 thawte, Inc. - For authorized use only -# Subject: CN=thawte Primary Root CA - G3 O=thawte, Inc. OU=Certification Services Division/(c) 2008 thawte, Inc. - For authorized use only -# Label: "thawte Primary Root CA - G3" -# Serial: 127614157056681299805556476275995414779 -# MD5 Fingerprint: fb:1b:5d:43:8a:94:cd:44:c6:76:f2:43:4b:47:e7:31 -# SHA1 Fingerprint: f1:8b:53:8d:1b:e9:03:b6:a6:f0:56:43:5b:17:15:89:ca:f3:6b:f2 -# SHA256 Fingerprint: 4b:03:f4:58:07:ad:70:f2:1b:fc:2c:ae:71:c9:fd:e4:60:4c:06:4c:f5:ff:b6:86:ba:e5:db:aa:d7:fd:d3:4c ------BEGIN CERTIFICATE----- -MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCB -rjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf -Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw -MDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNV -BAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0wODA0MDIwMDAwMDBa -Fw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3Rl -LCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9u -MTgwNgYDVQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXpl -ZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEcz -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsr8nLPvb2FvdeHsbnndm -gcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2AtP0LMqmsywCPLLEHd5N/8 -YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC+BsUa0Lf -b1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS9 -9irY7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2S -zhkGcuYMXDhpxwTWvGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUk -OQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNV -HQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJKoZIhvcNAQELBQADggEBABpA -2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweKA3rD6z8KLFIW -oCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu -t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7c -KUGRIjxpp7sC8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fM -m7v/OeZWYdMKp8RcTGB7BXcmer/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZu -MdRAGmI0Nj81Aa6sY6A= ------END CERTIFICATE----- - -# Issuer: CN=GeoTrust Primary Certification Authority - G2 O=GeoTrust Inc. OU=(c) 2007 GeoTrust Inc. - For authorized use only -# Subject: CN=GeoTrust Primary Certification Authority - G2 O=GeoTrust Inc. OU=(c) 2007 GeoTrust Inc. - For authorized use only -# Label: "GeoTrust Primary Certification Authority - G2" -# Serial: 80682863203381065782177908751794619243 -# MD5 Fingerprint: 01:5e:d8:6b:bd:6f:3d:8e:a1:31:f8:12:e0:98:73:6a -# SHA1 Fingerprint: 8d:17:84:d5:37:f3:03:7d:ec:70:fe:57:8b:51:9a:99:e6:10:d7:b0 -# SHA256 Fingerprint: 5e:db:7a:c4:3b:82:a0:6a:87:61:e8:d7:be:49:79:eb:f2:61:1f:7d:d7:9b:f9:1c:1c:6b:56:6a:21:9e:d7:66 ------BEGIN CERTIFICATE----- -MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDEL -MAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChj -KSAyMDA3IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2 -MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 -eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1OVowgZgxCzAJBgNV -BAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykgMjAw -NyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNV -BAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH -MjB2MBAGByqGSM49AgEGBSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcL -So17VDs6bl8VAsBQps8lL33KSLjHUGMcKiEIfJo22Av+0SbFWDEwKCXzXV2juLal -tJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO -BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+EVXVMAoG -CCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGT -qQ7mndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBucz -rD6ogRLQy7rQkgu2npaqBA+K ------END CERTIFICATE----- - -# Issuer: CN=VeriSign Universal Root Certification Authority O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2008 VeriSign, Inc. - For authorized use only -# Subject: CN=VeriSign Universal Root Certification Authority O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2008 VeriSign, Inc. - For authorized use only -# Label: "VeriSign Universal Root Certification Authority" -# Serial: 85209574734084581917763752644031726877 -# MD5 Fingerprint: 8e:ad:b5:01:aa:4d:81:e4:8c:1d:d1:e1:14:00:95:19 -# SHA1 Fingerprint: 36:79:ca:35:66:87:72:30:4d:30:a5:fb:87:3b:0f:a7:7b:b7:0d:54 -# SHA256 Fingerprint: 23:99:56:11:27:a5:71:25:de:8c:ef:ea:61:0d:df:2f:a0:78:b5:c8:06:7f:4e:82:82:90:bf:b8:60:e8:4b:3c ------BEGIN CERTIFICATE----- -MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCB -vTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL -ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJp -U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9W -ZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe -Fw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJVUzEX -MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0 -IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9y -IGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNh -bCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF -AAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj1mCOkdeQmIN65lgZOIzF -9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGPMiJhgsWH -H26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+H -LL729fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN -/BMReYTtXlT2NJ8IAfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPT -rJ9VAMf2CGqUuV/c4DPxhGD5WycRtPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1Ud -EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFsw -WTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgs -exkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud -DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4 -sAPmLGd75JR3Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+ -seQxIcaBlVZaDrHC1LGmWazxY8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz -4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTxP/jgdFcrGJ2BtMQo2pSXpXDrrB2+ -BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+PwGZsY6rp2aQW9IHR -lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3 -7M2CYfE45k+XmCpajQ== ------END CERTIFICATE----- - -# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G4 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2007 VeriSign, Inc. - For authorized use only -# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G4 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2007 VeriSign, Inc. - For authorized use only -# Label: "VeriSign Class 3 Public Primary Certification Authority - G4" -# Serial: 63143484348153506665311985501458640051 -# MD5 Fingerprint: 3a:52:e1:e7:fd:6f:3a:e3:6f:f3:6f:99:1b:f9:22:41 -# SHA1 Fingerprint: 22:d5:d8:df:8f:02:31:d1:8d:f7:9d:b7:cf:8a:2d:64:c9:3f:6c:3a -# SHA256 Fingerprint: 69:dd:d7:ea:90:bb:57:c9:3e:13:5d:c8:5e:a6:fc:d5:48:0b:60:32:39:bd:c4:54:fc:75:8b:2a:26:cf:7f:79 ------BEGIN CERTIFICATE----- -MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjEL -MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW -ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2ln -biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp -U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y -aXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjELMAkG -A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJp -U2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwg -SW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2ln -biBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 -IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8Utpkmw4tXNherJI9/gHm -GUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGzrl0Bp3ve -fLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUw -AwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJ -aW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYj -aHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMW -kf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMDA2gAMGUCMGYhDBgmYFo4e1ZC -4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIxAJw9SDkjOVga -FRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA== ------END CERTIFICATE----- - # Issuer: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services) # Subject: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services) # Label: "NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny" @@ -1559,47 +910,6 @@ uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2 XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= -----END CERTIFICATE----- -# Issuer: CN=Staat der Nederlanden Root CA - G2 O=Staat der Nederlanden -# Subject: CN=Staat der Nederlanden Root CA - G2 O=Staat der Nederlanden -# Label: "Staat der Nederlanden Root CA - G2" -# Serial: 10000012 -# MD5 Fingerprint: 7c:a5:0f:f8:5b:9a:7d:6d:30:ae:54:5a:e3:42:a2:8a -# SHA1 Fingerprint: 59:af:82:79:91:86:c7:b4:75:07:cb:cf:03:57:46:eb:04:dd:b7:16 -# SHA256 Fingerprint: 66:8c:83:94:7d:a6:3b:72:4b:ec:e1:74:3c:31:a0:e6:ae:d0:db:8e:c5:b3:1b:e3:77:bb:78:4f:91:b6:71:6f ------BEGIN CERTIFICATE----- -MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO -TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh -dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oX -DTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl -ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv -b3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ5291 -qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8Sp -uOUfiUtnvWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPU -Z5uW6M7XxgpT0GtJlvOjCwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvE -pMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiile7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp -5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCROME4HYYEhLoaJXhena/M -UGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpICT0ugpTN -GmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy -5V6548r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv -6q012iDTiIJh8BIitrzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEK -eN5KzlW/HdXZt1bv8Hb/C3m1r737qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6 -B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMBAAGjgZcwgZQwDwYDVR0TAQH/ -BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcCARYxaHR0cDov -L3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV -HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqG -SIb3DQEBCwUAA4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLyS -CZa59sCrI2AGeYwRTlHSeYAz+51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen -5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwjf/ST7ZwaUb7dRUG/kSS0H4zpX897 -IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaNkqbG9AclVMwWVxJK -gnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfkCpYL -+63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxL -vJxxcypFURmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkm -bEgeqmiSBeGCc1qb3AdbCG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvk -N1trSt8sV4pAWja63XVECDdCcAz+3F4hoKOKwJCcaNpQ5kUQR3i2TtJlycM33+FC -Y7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoVIPVVYpbtbZNQvOSqeK3Z -ywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm66+KAQ== ------END CERTIFICATE----- - # Issuer: CN=Hongkong Post Root CA 1 O=Hongkong Post # Subject: CN=Hongkong Post Root CA 1 O=Hongkong Post # Label: "Hongkong Post Root CA 1" @@ -1803,105 +1113,6 @@ naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== -----END CERTIFICATE----- -# Issuer: CN=Chambers of Commerce Root - 2008 O=AC Camerfirma S.A. -# Subject: CN=Chambers of Commerce Root - 2008 O=AC Camerfirma S.A. -# Label: "Chambers of Commerce Root - 2008" -# Serial: 11806822484801597146 -# MD5 Fingerprint: 5e:80:9e:84:5a:0e:65:0b:17:02:f3:55:18:2a:3e:d7 -# SHA1 Fingerprint: 78:6a:74:ac:76:ab:14:7f:9c:6a:30:50:ba:9e:a8:7e:fe:9a:ce:3c -# SHA256 Fingerprint: 06:3e:4a:fa:c4:91:df:d3:32:f3:08:9b:85:42:e9:46:17:d8:93:d7:fe:94:4e:10:a7:93:7e:e2:9d:96:93:c0 ------BEGIN CERTIFICATE----- -MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYD -VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0 -IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3 -MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xKTAnBgNVBAMTIENoYW1iZXJz -IG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEyMjk1MFoXDTM4MDcz -MTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBj -dXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIw -EAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEp -MCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0G -CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW9 -28sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKAXuFixrYp4YFs8r/lfTJq -VKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorjh40G072Q -DuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR -5gN/ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfL -ZEFHcpOrUMPrCXZkNNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05a -Sd+pZgvMPMZ4fKecHePOjlO+Bd5gD2vlGts/4+EhySnB8esHnFIbAURRPHsl18Tl -UlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331lubKgdaX8ZSD6e2wsWsSaR6s -+12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ0wlf2eOKNcx5 -Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj -ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAx -hduub+84Mxh2EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNV -HQ4EFgQU+SSsD7K1+HnA+mCIG8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1 -+HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpN -YWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29t -L2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVy -ZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAt -IDIwMDiCCQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRV -HSAAMCowKAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20w -DQYJKoZIhvcNAQEFBQADggIBAJASryI1wqM58C7e6bXpeHxIvj99RZJe6dqxGfwW -PJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH3qLPaYRgM+gQDROpI9CF -5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbURWpGqOt1 -glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaH -FoI6M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2 -pSB7+R5KBWIBpih1YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MD -xvbxrN8y8NmBGuScvfaAFPDRLLmF9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QG -tjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcKzBIKinmwPQN/aUv0NCB9szTq -jktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvGnrDQWzilm1De -fhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg -OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZ -d0jQ ------END CERTIFICATE----- - -# Issuer: CN=Global Chambersign Root - 2008 O=AC Camerfirma S.A. -# Subject: CN=Global Chambersign Root - 2008 O=AC Camerfirma S.A. -# Label: "Global Chambersign Root - 2008" -# Serial: 14541511773111788494 -# MD5 Fingerprint: 9e:80:ff:78:01:0c:2e:c1:36:bd:fe:96:90:6e:08:f3 -# SHA1 Fingerprint: 4a:bd:ee:ec:95:0d:35:9c:89:ae:c7:52:a1:2c:5b:29:f6:d6:aa:0c -# SHA256 Fingerprint: 13:63:35:43:93:34:a7:69:80:16:a0:d3:24:de:72:28:4e:07:9d:7b:52:20:bb:8f:bd:74:78:16:ee:be:ba:ca ------BEGIN CERTIFICATE----- -MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYD -VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0 -IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3 -MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD -aGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMxNDBaFw0zODA3MzEx -MjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3Vy -cmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAG -A1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAl -BgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZI -hvcNAQEBBQADggIPADCCAgoCggIBAMDfVtPkOpt2RbQT2//BthmLN0EYlVJH6xed -KYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXfXjaOcNFccUMd2drvXNL7 -G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0ZJJ0YPP2 -zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4 -ddPB/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyG -HoiMvvKRhI9lNNgATH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2 -Id3UwD2ln58fQ1DJu7xsepeY7s2MH/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3V -yJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfeOx2YItaswTXbo6Al/3K1dh3e -beksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSFHTynyQbehP9r -6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh -wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsog -zCtLkykPAgMBAAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQW -BBS5CcqcHtvTbDprru1U8VuTBjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDpr -ru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UEBhMCRVUxQzBBBgNVBAcTOk1hZHJp -ZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJmaXJtYS5jb20vYWRk -cmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJmaXJt -YSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiC -CQDJzdPp1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCow -KAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZI -hvcNAQEFBQADggIBAICIf3DekijZBZRG/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZ -UohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6ReAJ3spED8IXDneRRXoz -X1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/sdZ7LoR/x -fxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVz -a2Mg9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yyd -Yhz2rXzdpjEetrHHfoUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMd -SqlapskD7+3056huirRXhOukP9DuqqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9O -AP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETrP3iZ8ntxPjzxmKfFGBI/5rso -M0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVqc5iJWzouE4ge -v8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z -09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B ------END CERTIFICATE----- - # Issuer: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. # Subject: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. # Label: "Go Daddy Root Certificate Authority - G2" @@ -2200,6 +1411,45 @@ t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 -----END CERTIFICATE----- +# Issuer: CN=EC-ACC O=Agencia Catalana de Certificacio (NIF Q-0801176-I) OU=Serveis Publics de Certificacio/Vegeu https://www.catcert.net/verarrel (c)03/Jerarquia Entitats de Certificacio Catalanes +# Subject: CN=EC-ACC O=Agencia Catalana de Certificacio (NIF Q-0801176-I) OU=Serveis Publics de Certificacio/Vegeu https://www.catcert.net/verarrel (c)03/Jerarquia Entitats de Certificacio Catalanes +# Label: "EC-ACC" +# Serial: -23701579247955709139626555126524820479 +# MD5 Fingerprint: eb:f5:9d:29:0d:61:f9:42:1f:7c:c2:ba:6d:e3:15:09 +# SHA1 Fingerprint: 28:90:3a:63:5b:52:80:fa:e6:77:4c:0b:6d:a7:d6:ba:a6:4a:f2:e8 +# SHA256 Fingerprint: 88:49:7f:01:60:2f:31:54:24:6a:e2:8c:4d:5a:ef:10:f1:d8:7e:bb:76:62:6f:4a:e0:b7:f9:5b:a7:96:87:99 +-----BEGIN CERTIFICATE----- +MIIFVjCCBD6gAwIBAgIQ7is969Qh3hSoYqwE893EATANBgkqhkiG9w0BAQUFADCB +8zELMAkGA1UEBhMCRVMxOzA5BgNVBAoTMkFnZW5jaWEgQ2F0YWxhbmEgZGUgQ2Vy +dGlmaWNhY2lvIChOSUYgUS0wODAxMTc2LUkpMSgwJgYDVQQLEx9TZXJ2ZWlzIFB1 +YmxpY3MgZGUgQ2VydGlmaWNhY2lvMTUwMwYDVQQLEyxWZWdldSBodHRwczovL3d3 +dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAoYykwMzE1MDMGA1UECxMsSmVyYXJxdWlh +IEVudGl0YXRzIGRlIENlcnRpZmljYWNpbyBDYXRhbGFuZXMxDzANBgNVBAMTBkVD +LUFDQzAeFw0wMzAxMDcyMzAwMDBaFw0zMTAxMDcyMjU5NTlaMIHzMQswCQYDVQQG +EwJFUzE7MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8g +KE5JRiBRLTA4MDExNzYtSSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBD +ZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZlZ2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQu +bmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJhcnF1aWEgRW50aXRhdHMg +ZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUNDMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsyLHT+KXQpWIR4NA9h0X84NzJB5R +85iKw5K4/0CQBXCHYMkAqbWUZRkiFRfCQ2xmRJoNBD45b6VLeqpjt4pEndljkYRm +4CgPukLjbo73FCeTae6RDqNfDrHrZqJyTxIThmV6PttPB/SnCWDaOkKZx7J/sxaV +HMf5NLWUhdWZXqBIoH7nF2W4onW4HvPlQn2v7fOKSGRdghST2MDk/7NQcvJ29rNd +QlB50JQ+awwAvthrDk4q7D7SzIKiGGUzE3eeml0aE9jD2z3Il3rucO2n5nzbcc8t +lGLfbdb1OL4/pYUKGbio2Al1QnDE6u/LDsg0qBIimAy4E5S2S+zw0JDnJwIDAQAB +o4HjMIHgMB0GA1UdEQQWMBSBEmVjX2FjY0BjYXRjZXJ0Lm5ldDAPBgNVHRMBAf8E +BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUoMOLRKo3pUW/l4Ba0fF4 +opvpXY0wfwYDVR0gBHgwdjB0BgsrBgEEAfV4AQMBCjBlMCwGCCsGAQUFBwIBFiBo +dHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbDA1BggrBgEFBQcCAjApGidW +ZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAwDQYJKoZIhvcN +AQEFBQADggEBAKBIW4IB9k1IuDlVNZyAelOZ1Vr/sXE7zDkJlF7W2u++AVtd0x7Y +/X1PzaBB4DSTv8vihpw3kpBWHNzrKQXlxJ7HNd+KDM3FIUPpqojlNcAZQmNaAl6k +SBg6hW/cnbw/nZzBh7h6YQjpdwt/cKt63dmXLGQehb+8dJahw3oS7AwaboMMPOhy +Rp/7SNVel+axofjk70YllJyJ22k4vuxcDlbHZVHlUIiIv0LVKz3l+bqeLrPK9HOS +Agu+TGbrIP65y7WZf+a2E/rKS03Z7lNGBjvGTq2TWoF+bCpLagVFjPIhpDGQh2xl +nJ2lYJU6Un/10asIbvPuW/mIPX64b24D5EI= +-----END CERTIFICATE----- + # Issuer: CN=Hellenic Academic and Research Institutions RootCA 2011 O=Hellenic Academic and Research Institutions Cert. Authority # Subject: CN=Hellenic Academic and Research Institutions RootCA 2011 O=Hellenic Academic and Research Institutions Cert. Authority # Label: "Hellenic Academic and Research Institutions RootCA 2011" @@ -2274,35 +1524,6 @@ LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== -----END CERTIFICATE----- -# Issuer: O=Trustis Limited OU=Trustis FPS Root CA -# Subject: O=Trustis Limited OU=Trustis FPS Root CA -# Label: "Trustis FPS Root CA" -# Serial: 36053640375399034304724988975563710553 -# MD5 Fingerprint: 30:c9:e7:1e:6b:e6:14:eb:65:b2:16:69:20:31:67:4d -# SHA1 Fingerprint: 3b:c0:38:0b:33:c3:f6:a6:0c:86:15:22:93:d9:df:f5:4b:81:c0:04 -# SHA256 Fingerprint: c1:b4:82:99:ab:a5:20:8f:e9:63:0a:ce:55:ca:68:a0:3e:da:5a:51:9c:88:02:a0:d3:a6:73:be:8f:8e:55:7d ------BEGIN CERTIFICATE----- -MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBF -MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQL -ExNUcnVzdGlzIEZQUyBSb290IENBMB4XDTAzMTIyMzEyMTQwNloXDTI0MDEyMTEx -MzY1NFowRTELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1RydXN0aXMgTGltaXRlZDEc -MBoGA1UECxMTVHJ1c3RpcyBGUFMgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBAMVQe547NdDfxIzNjpvto8A2mfRC6qc+gIMPpqdZh8mQRUN+ -AOqGeSoDvT03mYlmt+WKVoaTnGhLaASMk5MCPjDSNzoiYYkchU59j9WvezX2fihH -iTHcDnlkH5nSW7r+f2C/revnPDgpai/lkQtV/+xvWNUtyd5MZnGPDNcE2gfmHhjj -vSkCqPoc4Vu5g6hBSLwacY3nYuUtsuvffM/bq1rKMfFMIvMFE/eC+XN5DL7XSxzA -0RU8k0Fk0ea+IxciAIleH2ulrG6nS4zto3Lmr2NNL4XSFDWaLk6M6jKYKIahkQlB -OrTh4/L68MkKokHdqeMDx4gVOxzUGpTXn2RZEm0CAwEAAaNTMFEwDwYDVR0TAQH/ -BAUwAwEB/zAfBgNVHSMEGDAWgBS6+nEleYtXQSUhhgtx67JkDoshZzAdBgNVHQ4E -FgQUuvpxJXmLV0ElIYYLceuyZA6LIWcwDQYJKoZIhvcNAQEFBQADggEBAH5Y//01 -GX2cGE+esCu8jowU/yyg2kdbw++BLa8F6nRIW/M+TgfHbcWzk88iNVy2P3UnXwmW -zaD+vkAMXBJV+JOCyinpXj9WV4s4NvdFGkwozZ5BuO1WTISkQMi4sKUraXAEasP4 -1BIy+Q7DsdwyhEQsb8tGD+pmQQ9P8Vilpg0ND2HepZ5dfWWhPBfnqFVO76DH7cZE -f1T1o+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHVl/9D7S3B2l0pKoU/rGXuhg8F -jZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYliB6XzCGcKQEN -ZetX2fNXlrtIzYE= ------END CERTIFICATE----- - # Issuer: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 # Subject: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 # Label: "Buypass Class 2 Root CA" @@ -2412,38 +1633,6 @@ e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p TpPDpFQUWw== -----END CERTIFICATE----- -# Issuer: CN=EE Certification Centre Root CA O=AS Sertifitseerimiskeskus -# Subject: CN=EE Certification Centre Root CA O=AS Sertifitseerimiskeskus -# Label: "EE Certification Centre Root CA" -# Serial: 112324828676200291871926431888494945866 -# MD5 Fingerprint: 43:5e:88:d4:7d:1a:4a:7e:fd:84:2e:52:eb:01:d4:6f -# SHA1 Fingerprint: c9:a8:b9:e7:55:80:5e:58:e3:53:77:a7:25:eb:af:c3:7b:27:cc:d7 -# SHA256 Fingerprint: 3e:84:ba:43:42:90:85:16:e7:75:73:c0:99:2f:09:79:ca:08:4e:46:85:68:1f:f1:95:cc:ba:8a:22:9b:8a:76 ------BEGIN CERTIFICATE----- -MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1 -MQswCQYDVQQGEwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1 -czEoMCYGA1UEAwwfRUUgQ2VydGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYG -CSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIwMTAxMDMwMTAxMDMwWhgPMjAzMDEy -MTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNl -ZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBS -b290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEB -AQUAA4IBDwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUy -euuOF0+W2Ap7kaJjbMeMTC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvO -bntl8jixwKIy72KyaOBhU8E2lf/slLo2rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIw -WFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw93X2PaRka9ZP585ArQ/d -MtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtNP2MbRMNE -1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYD -VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/ -zQas8fElyalL1BSZMEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYB -BQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEF -BQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+RjxY6hUFaTlrg4wCQiZrxTFGGV -v9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqMlIpPnTX/dqQG -E5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u -uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIW -iAYLtqZLICjU3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/v -GVCJYMzpJJUPwssd8m92kMfMdcGWxZ0= ------END CERTIFICATE----- - # Issuer: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH # Subject: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH # Label: "D-TRUST Root Class 3 CA 2 2009" @@ -3196,46 +2385,6 @@ KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg xwy8p2Fp8fc74SrL+SvzZpA3 -----END CERTIFICATE----- -# Issuer: CN=Staat der Nederlanden Root CA - G3 O=Staat der Nederlanden -# Subject: CN=Staat der Nederlanden Root CA - G3 O=Staat der Nederlanden -# Label: "Staat der Nederlanden Root CA - G3" -# Serial: 10003001 -# MD5 Fingerprint: 0b:46:67:07:db:10:2f:19:8c:35:50:60:d1:0b:f4:37 -# SHA1 Fingerprint: d8:eb:6b:41:51:92:59:e0:f3:e7:85:00:c0:3d:b6:88:97:c9:ee:fc -# SHA256 Fingerprint: 3c:4f:b0:b9:5a:b8:b3:00:32:f4:32:b8:6f:53:5f:e1:72:c1:85:d0:fd:39:86:58:37:cf:36:18:7f:a6:f4:28 ------BEGIN CERTIFICATE----- -MIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO -TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh -dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloX -DTI4MTExMzIzMDAwMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl -ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv -b3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL4yolQP -cPssXFnrbMSkUeiFKrPMSjTysF/zDsccPVMeiAho2G89rcKezIJnByeHaHE6n3WW -IkYFsO2tx1ueKt6c/DrGlaf1F2cY5y9JCAxcz+bMNO14+1Cx3Gsy8KL+tjzk7FqX -xz8ecAgwoNzFs21v0IJyEavSgWhZghe3eJJg+szeP4TrjTgzkApyI/o1zCZxMdFy -KJLZWyNtZrVtB0LrpjPOktvA9mxjeM3KTj215VKb8b475lRgsGYeCasH/lSJEULR -9yS6YHgamPfJEf0WwTUaVHXvQ9Plrk7O53vDxk5hUUurmkVLoR9BvUhTFXFkC4az -5S6+zqQbwSmEorXLCCN2QyIkHxcE1G6cxvx/K2Ya7Irl1s9N9WMJtxU51nus6+N8 -6U78dULI7ViVDAZCopz35HCz33JvWjdAidiFpNfxC95DGdRKWCyMijmev4SH8RY7 -Ngzp07TKbBlBUgmhHbBqv4LvcFEhMtwFdozL92TkA1CvjJFnq8Xy7ljY3r735zHP -bMk7ccHViLVlvMDoFxcHErVc0qsgk7TmgoNwNsXNo42ti+yjwUOH5kPiNL6VizXt -BznaqB16nzaeErAMZRKQFWDZJkBE41ZgpRDUajz9QdwOWke275dhdU/Z/seyHdTt -XUmzqWrLZoQT1Vyg3N9udwbRcXXIV2+vD3dbAgMBAAGjQjBAMA8GA1UdEwEB/wQF -MAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRUrfrHkleuyjWcLhL75Lpd -INyUVzANBgkqhkiG9w0BAQsFAAOCAgEAMJmdBTLIXg47mAE6iqTnB/d6+Oea31BD -U5cqPco8R5gu4RV78ZLzYdqQJRZlwJ9UXQ4DO1t3ApyEtg2YXzTdO2PCwyiBwpwp -LiniyMMB8jPqKqrMCQj3ZWfGzd/TtiunvczRDnBfuCPRy5FOCvTIeuXZYzbB1N/8 -Ipf3YF3qKS9Ysr1YvY2WTxB1v0h7PVGHoTx0IsL8B3+A3MSs/mrBcDCw6Y5p4ixp -gZQJut3+TcCDjJRYwEYgr5wfAvg1VUkvRtTA8KCWAg8zxXHzniN9lLf9OtMJgwYh -/WA9rjLA0u6NpvDntIJ8CsxwyXmA+P5M9zWEGYox+wrZ13+b8KKaa8MFSu1BYBQw -0aoRQm7TIwIEC8Zl3d1Sd9qBa7Ko+gE4uZbqKmxnl4mUnrzhVNXkanjvSr0rmj1A -fsbAddJu+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgSKL59NVzq -4BZ+Extq1z7XnvwtdbLBFNUjA9tbbws+eC8N3jONFrdI54OagQ97wUNNVQQXOEpR -1VmiiXTTn74eS9fGbbeIJG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0/ -QFH1T/U67cjF68IeHRaVesd+QnGTbksVtzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM -94B7IWcnMFk= ------END CERTIFICATE----- - # Issuer: CN=Staat der Nederlanden EV Root CA O=Staat der Nederlanden # Subject: CN=Staat der Nederlanden EV Root CA O=Staat der Nederlanden # Label: "Staat der Nederlanden EV Root CA" @@ -3453,46 +2602,6 @@ AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ 5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su -----END CERTIFICATE----- -# Issuer: CN=Certinomis - Root CA O=Certinomis OU=0002 433998903 -# Subject: CN=Certinomis - Root CA O=Certinomis OU=0002 433998903 -# Label: "Certinomis - Root CA" -# Serial: 1 -# MD5 Fingerprint: 14:0a:fd:8d:a8:28:b5:38:69:db:56:7e:61:22:03:3f -# SHA1 Fingerprint: 9d:70:bb:01:a5:a4:a0:18:11:2e:f7:1c:01:b9:32:c5:34:e7:88:a8 -# SHA256 Fingerprint: 2a:99:f5:bc:11:74:b7:3c:bb:1d:62:08:84:e0:1c:34:e5:1c:cb:39:78:da:12:5f:0e:33:26:88:83:bf:41:58 ------BEGIN CERTIFICATE----- -MIIFkjCCA3qgAwIBAgIBATANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJGUjET -MBEGA1UEChMKQ2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxHTAb -BgNVBAMTFENlcnRpbm9taXMgLSBSb290IENBMB4XDTEzMTAyMTA5MTcxOFoXDTMz -MTAyMTA5MTcxOFowWjELMAkGA1UEBhMCRlIxEzARBgNVBAoTCkNlcnRpbm9taXMx -FzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMR0wGwYDVQQDExRDZXJ0aW5vbWlzIC0g -Um9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANTMCQosP5L2 -fxSeC5yaah1AMGT9qt8OHgZbn1CF6s2Nq0Nn3rD6foCWnoR4kkjW4znuzuRZWJfl -LieY6pOod5tK8O90gC3rMB+12ceAnGInkYjwSond3IjmFPnVAy//ldu9n+ws+hQV -WZUKxkd8aRi5pwP5ynapz8dvtF4F/u7BUrJ1Mofs7SlmO/NKFoL21prbcpjp3vDF -TKWrteoB4owuZH9kb/2jJZOLyKIOSY008B/sWEUuNKqEUL3nskoTuLAPrjhdsKkb -5nPJWqHZZkCqqU2mNAKthH6yI8H7KsZn9DS2sJVqM09xRLWtwHkziOC/7aOgFLSc -CbAK42C++PhmiM1b8XcF4LVzbsF9Ri6OSyemzTUK/eVNfaoqoynHWmgE6OXWk6Ri -wsXm9E/G+Z8ajYJJGYrKWUM66A0ywfRMEwNvbqY/kXPLynNvEiCL7sCCeN5LLsJJ -wx3tFvYk9CcbXFcx3FXuqB5vbKziRcxXV4p1VxngtViZSTYxPDMBbRZKzbgqg4SG -m/lg0h9tkQPTYKbVPZrdd5A9NaSfD171UkRpucC63M9933zZxKyGIjK8e2uR73r4 -F2iw4lNVYC2vPsKD2NkJK/DAZNuHi5HMkesE/Xa0lZrmFAYb1TQdvtj/dBxThZng -WVJKYe2InmtJiUZ+IFrZ50rlau7SZRFDAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIB -BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTvkUz1pcMw6C8I6tNxIqSSaHh0 -2TAfBgNVHSMEGDAWgBTvkUz1pcMw6C8I6tNxIqSSaHh02TANBgkqhkiG9w0BAQsF -AAOCAgEAfj1U2iJdGlg+O1QnurrMyOMaauo++RLrVl89UM7g6kgmJs95Vn6RHJk/ -0KGRHCwPT5iVWVO90CLYiF2cN/z7ZMF4jIuaYAnq1fohX9B0ZedQxb8uuQsLrbWw -F6YSjNRieOpWauwK0kDDPAUwPk2Ut59KA9N9J0u2/kTO+hkzGm2kQtHdzMjI1xZS -g081lLMSVX3l4kLr5JyTCcBMWwerx20RoFAXlCOotQqSD7J6wWAsOMwaplv/8gzj -qh8c3LigkyfeY+N/IZ865Z764BNqdeuWXGKRlI5nU7aJ+BIJy29SWwNyhlCVCNSN -h4YVH5Uk2KRvms6knZtt0rJ2BobGVgjF6wnaNsIbW0G+YSrjcOa4pvi2WsS9Iff/ -ql+hbHY5ZtbqTFXhADObE5hjyW/QASAJN1LnDE8+zbz1X5YnpyACleAu6AdBBR8V -btaw5BngDwKTACdyxYvRVB9dSsNAl35VpnzBMwQUAR1JIGkLGZOdblgi90AMRgwj -Y/M50n92Uaf0yKHxDHYiI0ZSKS3io0EHVmmY0gUJvGnHWmHNj4FgFU2A3ZDifcRQ -8ow7bkrHxuaAKzyBvBGAFhAn1/DNP3nMcyrDflOR1m749fPH0FFNjkulW+YZFzvW -gQncItzujrnEj1PhZ7szuIgVRs/taTX/dQ1G885x4cVrhkIGuUE= ------END CERTIFICATE----- - # Issuer: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed # Subject: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed # Label: "OISTE WISeKey Global Root GB CA" @@ -3849,47 +2958,6 @@ CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW 1KyLa2tJElMzrdfkviT8tQp21KW8EA== -----END CERTIFICATE----- -# Issuer: CN=LuxTrust Global Root 2 O=LuxTrust S.A. -# Subject: CN=LuxTrust Global Root 2 O=LuxTrust S.A. -# Label: "LuxTrust Global Root 2" -# Serial: 59914338225734147123941058376788110305822489521 -# MD5 Fingerprint: b2:e1:09:00:61:af:f7:f1:91:6f:c4:ad:8d:5e:3b:7c -# SHA1 Fingerprint: 1e:0e:56:19:0a:d1:8b:25:98:b2:04:44:ff:66:8a:04:17:99:5f:3f -# SHA256 Fingerprint: 54:45:5f:71:29:c2:0b:14:47:c4:18:f9:97:16:8f:24:c5:8f:c5:02:3b:f5:da:5b:e2:eb:6e:1d:d8:90:2e:d5 ------BEGIN CERTIFICATE----- -MIIFwzCCA6ugAwIBAgIUCn6m30tEntpqJIWe5rgV0xZ/u7EwDQYJKoZIhvcNAQEL -BQAwRjELMAkGA1UEBhMCTFUxFjAUBgNVBAoMDUx1eFRydXN0IFMuQS4xHzAdBgNV -BAMMFkx1eFRydXN0IEdsb2JhbCBSb290IDIwHhcNMTUwMzA1MTMyMTU3WhcNMzUw -MzA1MTMyMTU3WjBGMQswCQYDVQQGEwJMVTEWMBQGA1UECgwNTHV4VHJ1c3QgUy5B -LjEfMB0GA1UEAwwWTHV4VHJ1c3QgR2xvYmFsIFJvb3QgMjCCAiIwDQYJKoZIhvcN -AQEBBQADggIPADCCAgoCggIBANeFl78RmOnwYoNMPIf5U2o3C/IPPIfOb9wmKb3F -ibrJgz337spbxm1Jc7TJRqMbNBM/wYlFV/TZsfs2ZUv7COJIcRHIbjuend+JZTem -hfY7RBi2xjcwYkSSl2l9QjAk5A0MiWtj3sXh306pFGxT4GHO9hcvHTy95iJMHZP1 -EMShduxq3sVs35a0VkBCwGKSMKEtFZSg0iAGCW5qbeXrt77U8PEVfIvmTroTzEsn -Xpk8F12PgX8zPU/TPxvsXD/wPEx1bvKm1Z3aLQdjAsZy6ZS8TEmVT4hSyNvoaYL4 -zDRbIvCGp4m9SAptZoFtyMhk+wHh9OHe2Z7d21vUKpkmFRseTJIpgp7VkoGSQXAZ -96Tlk0u8d2cx3Rz9MXANF5kM+Qw5GSoXtTBxVdUPrljhPS80m8+f9niFwpN6cj5m -j5wWEWCPnolvZ77gR1o7DJpni89Gxq44o/KnvObWhWszJHAiS8sIm7vI+AIpHb4g -DEa/a4ebsypmQjVGbKq6rfmYe+lQVRQxv7HaLe2ArWgk+2mr2HETMOZns4dA/Yl+ -8kPREd8vZS9kzl8UubG/Mb2HeFpZZYiq/FkySIbWTLkpS5XTdvN3JW1CHDiDTf2j -X5t/Lax5Gw5CMZdjpPuKadUiDTSQMC6otOBttpSsvItO13D8xTiOZCXhTTmQzsmH -hFhxAgMBAAGjgagwgaUwDwYDVR0TAQH/BAUwAwEB/zBCBgNVHSAEOzA5MDcGByuB -KwEBAQowLDAqBggrBgEFBQcCARYeaHR0cHM6Ly9yZXBvc2l0b3J5Lmx1eHRydXN0 -Lmx1MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBT/GCh2+UgFLKGu8SsbK7JT -+Et8szAdBgNVHQ4EFgQU/xgodvlIBSyhrvErGyuyU/hLfLMwDQYJKoZIhvcNAQEL -BQADggIBAGoZFO1uecEsh9QNcH7X9njJCwROxLHOk3D+sFTAMs2ZMGQXvw/l4jP9 -BzZAcg4atmpZ1gDlaCDdLnINH2pkMSCEfUmmWjfrRcmF9dTHF5kH5ptV5AzoqbTO -jFu1EVzPig4N1qx3gf4ynCSecs5U89BvolbW7MM3LGVYvlcAGvI1+ut7MV3CwRI9 -loGIlonBWVx65n9wNOeD4rHh4bhY79SV5GCc8JaXcozrhAIuZY+kt9J/Z93I055c -qqmkoCUUBpvsT34tC38ddfEz2O3OuHVtPlu5mB0xDVbYQw8wkbIEa91WvpWAVWe+ -2M2D2RjuLg+GLZKecBPs3lHJQ3gCpU3I+V/EkVhGFndadKpAvAefMLmx9xIX3eP/ -JEAdemrRTxgKqpAd60Ae36EeRJIQmvKN4dFLRp7oRUKX6kWZ8+xm1QL68qZKJKre -zrnK+T+Tb/mjuuqlPpmt/f97mfVl7vBZKGfXkJWkE4SphMHozs51k2MavDzq1WQf -LSoSOcbDWjLtR5EWDrw4wVDej8oqkDQc7kGUnF4ZLvhFSZl0kbAEb+MEWrGrKqv+ -x9CWttrhSmQGbmBNvUJO/3jaJMobtNeWOWyu8Q6qp31IiyBMz2TWuJdGsE7RKlY6 -oJO9r4Ak4Ap+58rVyuiFVdw2KuGUaJPHZnJED4AhMmwlxyOAgwrr ------END CERTIFICATE----- - # Issuer: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM # Subject: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM # Label: "TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1" @@ -4656,3 +3724,534 @@ L5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB60PZ2Pierc+xYw5F9KBa LJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fqdBb9HxEG mpv0 -----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority - G4 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2015 Entrust, Inc. - for authorized use only +# Subject: CN=Entrust Root Certification Authority - G4 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2015 Entrust, Inc. - for authorized use only +# Label: "Entrust Root Certification Authority - G4" +# Serial: 289383649854506086828220374796556676440 +# MD5 Fingerprint: 89:53:f1:83:23:b7:7c:8e:05:f1:8c:71:38:4e:1f:88 +# SHA1 Fingerprint: 14:88:4e:86:26:37:b0:26:af:59:62:5c:40:77:ec:35:29:ba:96:01 +# SHA256 Fingerprint: db:35:17:d1:f6:73:2a:2d:5a:b9:7c:53:3e:c7:07:79:ee:32:70:a6:2f:b4:ac:42:38:37:24:60:e6:f0:1e:88 +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIRANm1Q3+vqTkPAAAAAFVlrVgwDQYJKoZIhvcNAQELBQAw +gb4xCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQL +Ex9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykg +MjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMjAw +BgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEc0 +MB4XDTE1MDUyNzExMTExNloXDTM3MTIyNzExNDExNlowgb4xCzAJBgNVBAYTAlVT +MRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1 +c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJ +bmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3Qg +Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEc0MIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAsewsQu7i0TD/pZJH4i3DumSXbcr3DbVZwbPLqGgZ +2K+EbTBwXX7zLtJTmeH+H17ZSK9dE43b/2MzTdMAArzE+NEGCJR5WIoV3imz/f3E +T+iq4qA7ec2/a0My3dl0ELn39GjUu9CH1apLiipvKgS1sqbHoHrmSKvS0VnM1n4j +5pds8ELl3FFLFUHtSUrJ3hCX1nbB76W1NhSXNdh4IjVS70O92yfbYVaCNNzLiGAM +C1rlLAHGVK/XqsEQe9IFWrhAnoanw5CGAlZSCXqc0ieCU0plUmr1POeo8pyvi73T +DtTUXm6Hnmo9RR3RXRv06QqsYJn7ibT/mCzPfB3pAqoEmh643IhuJbNsZvc8kPNX +wbMv9W3y+8qh+CmdRouzavbmZwe+LGcKKh9asj5XxNMhIWNlUpEbsZmOeX7m640A +2Vqq6nPopIICR5b+W45UYaPrL0swsIsjdXJ8ITzI9vF01Bx7owVV7rtNOzK+mndm +nqxpkCIHH2E6lr7lmk/MBTwoWdPBDFSoWWG9yHJM6Nyfh3+9nEg2XpWjDrk4JFX8 +dWbrAuMINClKxuMrLzOg2qOGpRKX/YAr2hRC45K9PvJdXmd0LhyIRyk0X+IyqJwl +N4y6mACXi0mWHv0liqzc2thddG5msP9E36EYxr5ILzeUePiVSj9/E15dWf10hkNj +c0kCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFJ84xFYjwznooHFs6FRM5Og6sb9nMA0GCSqGSIb3DQEBCwUAA4ICAQAS +5UKme4sPDORGpbZgQIeMJX6tuGguW8ZAdjwD+MlZ9POrYs4QjbRaZIxowLByQzTS +Gwv2LFPSypBLhmb8qoMi9IsabyZIrHZ3CL/FmFz0Jomee8O5ZDIBf9PD3Vht7LGr +hFV0d4QEJ1JrhkzO3bll/9bGXp+aEJlLdWr+aumXIOTkdnrG0CSqkM0gkLpHZPt/ +B7NTeLUKYvJzQ85BK4FqLoUWlFPUa19yIqtRLULVAJyZv967lDtX/Zr1hstWO1uI +AeV8KEsD+UmDfLJ/fOPtjqF/YFOOVZ1QNBIPt5d7bIdKROf1beyAN/BYGW5KaHbw +H5Lk6rWS02FREAutp9lfx1/cH6NcjKF+m7ee01ZvZl4HliDtC3T7Zk6LERXpgUl+ +b7DUUH8i119lAg2m9IUe2K4GS0qn0jFmwvjO5QimpAKWRGhXxNUzzxkvFMSUHHuk +2fCfDrGA4tGeEWSpiBE6doLlYsKA2KSD7ZPvfC+QsDJMlhVoSFLUmQjAJOgc47Ol +IQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk +5F6G+TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuY +n/PIjhs4ViFqUZPTkcpG2om3PVODLAgfi49T3f+sHw== +-----END CERTIFICATE----- + +# Issuer: CN=Microsoft ECC Root Certificate Authority 2017 O=Microsoft Corporation +# Subject: CN=Microsoft ECC Root Certificate Authority 2017 O=Microsoft Corporation +# Label: "Microsoft ECC Root Certificate Authority 2017" +# Serial: 136839042543790627607696632466672567020 +# MD5 Fingerprint: dd:a1:03:e6:4a:93:10:d1:bf:f0:19:42:cb:fe:ed:67 +# SHA1 Fingerprint: 99:9a:64:c3:7f:f4:7d:9f:ab:95:f1:47:69:89:14:60:ee:c4:c3:c5 +# SHA256 Fingerprint: 35:8d:f3:9d:76:4a:f9:e1:b7:66:e9:c9:72:df:35:2e:e1:5c:fa:c2:27:af:6a:d1:d7:0e:8e:4a:6e:dc:ba:02 +-----BEGIN CERTIFICATE----- +MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYD +VQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIw +MTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4MjMxNjA0WjBlMQswCQYDVQQGEwJV +UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNy +b3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZR +ogPZnZH6thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYb +hGBKia/teQ87zvH2RPUBeMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBTIy5lycFIM+Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3 +FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlfXu5gKcs68tvWMoQZP3zV +L8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaReNtUjGUB +iudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M= +-----END CERTIFICATE----- + +# Issuer: CN=Microsoft RSA Root Certificate Authority 2017 O=Microsoft Corporation +# Subject: CN=Microsoft RSA Root Certificate Authority 2017 O=Microsoft Corporation +# Label: "Microsoft RSA Root Certificate Authority 2017" +# Serial: 40975477897264996090493496164228220339 +# MD5 Fingerprint: 10:ff:00:ff:cf:c9:f8:c7:7a:c0:ee:35:8e:c9:0f:47 +# SHA1 Fingerprint: 73:a5:e6:4a:3b:ff:83:16:ff:0e:dc:cc:61:8a:90:6e:4e:ae:4d:74 +# SHA256 Fingerprint: c7:41:f7:0f:4b:2a:8d:88:bf:2e:71:c1:41:22:ef:53:ef:10:eb:a0:cf:a5:e6:4c:fa:20:f4:18:85:30:73:e0 +-----BEGIN CERTIFICATE----- +MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBl +MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw +NAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 +IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIwNzE4MjMwMDIzWjBlMQswCQYDVQQG +EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1N +aWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZ +Nt9GkMml7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0 +ZdDMbRnMlfl7rEqUrQ7eS0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1 +HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw71VdyvD/IybLeS2v4I2wDwAW9lcfNcztm +gGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+dkC0zVJhUXAoP8XFWvLJ +jEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49FyGcohJUc +aDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaG +YaRSMLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6 +W6IYZVcSn2i51BVrlMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4K +UGsTuqwPN1q3ErWQgR5WrlcihtnJ0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH ++FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJClTUFLkqqNfs+avNJVgyeY+Q +W5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZC +LgLNFgVZJ8og6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OC +gMNPOsduET/m4xaRhPtthH80dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6 +tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk+ONVFT24bcMKpBLBaYVu32TxU5nh +SnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex/2kskZGT4d9Mozd2 +TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDyAmH3 +pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGR +xpl/j8nWZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiApp +GWSZI1b7rCoucL5mxAyE7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9 +dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKTc0QWbej09+CVgI+WXTik9KveCjCHk9hN +AHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D5KbvtwEwXlGjefVwaaZB +RA+GsCyRxj3qrg+E +-----END CERTIFICATE----- + +# Issuer: CN=e-Szigno Root CA 2017 O=Microsec Ltd. +# Subject: CN=e-Szigno Root CA 2017 O=Microsec Ltd. +# Label: "e-Szigno Root CA 2017" +# Serial: 411379200276854331539784714 +# MD5 Fingerprint: de:1f:f6:9e:84:ae:a7:b4:21:ce:1e:58:7d:d1:84:98 +# SHA1 Fingerprint: 89:d4:83:03:4f:9e:9a:48:80:5f:72:37:d4:a9:a6:ef:cb:7c:1f:d1 +# SHA256 Fingerprint: be:b0:0b:30:83:9b:9b:c3:2c:32:e4:44:79:05:95:06:41:f2:64:21:b1:5e:d0:89:19:8b:51:8a:e2:ea:1b:99 +-----BEGIN CERTIFICATE----- +MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNV +BAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRk +LjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJv +b3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZaFw00MjA4MjIxMjA3MDZaMHExCzAJ +BgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMg +THRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25v +IFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtv +xie+RJCxs1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+H +Wyx7xf58etqjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBSHERUI0arBeAyxr87GyZDvvzAEwDAfBgNVHSMEGDAWgBSHERUI0arB +eAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEAtVfd14pVCzbhhkT61Nlo +jbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxOsvxyqltZ ++efcMQ== +-----END CERTIFICATE----- + +# Issuer: O=CERTSIGN SA OU=certSIGN ROOT CA G2 +# Subject: O=CERTSIGN SA OU=certSIGN ROOT CA G2 +# Label: "certSIGN Root CA G2" +# Serial: 313609486401300475190 +# MD5 Fingerprint: 8c:f1:75:8a:c6:19:cf:94:b7:f7:65:20:87:c3:97:c7 +# SHA1 Fingerprint: 26:f9:93:b4:ed:3d:28:27:b0:b9:4b:a7:e9:15:1d:a3:8d:92:e5:32 +# SHA256 Fingerprint: 65:7c:fe:2f:a7:3f:aa:38:46:25:71:f3:32:a2:36:3a:46:fc:e7:02:09:51:71:07:02:cd:fb:b6:ee:da:33:05 +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNV +BAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04g +Uk9PVCBDQSBHMjAeFw0xNzAyMDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJ +BgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJ +R04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDF +dRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05N0Iw +vlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZ +uIt4ImfkabBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhp +n+Sc8CnTXPnGFiWeI8MgwT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKs +cpc/I1mbySKEwQdPzH/iV8oScLumZfNpdWO9lfsbl83kqK/20U6o2YpxJM02PbyW +xPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91QqhngLjYl/rNUssuHLoPj1P +rCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732jcZZroiF +DsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fx +DTvf95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgy +LcsUDFDYg2WD7rlcz8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6C +eWRgKRM+o/1Pcmqr4tTluCRVLERLiohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSCIS1mxteg4BXrzkwJ +d8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOBywaK8SJJ6ejq +kX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC +b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQl +qiCA2ClV9+BB/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0 +OJD7uNGzcgbJceaBxXntC6Z58hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+c +NywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5BiKDUyUM/FHE5r7iOZULJK2v0ZXk +ltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklWatKcsWMy5WHgUyIO +pwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tUSxfj +03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZk +PuXaTH4MNMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE +1LlSVHJ7liXMvGnjSG4N0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MX +QRBdJ3NghVdJIgc= +-----END CERTIFICATE----- + +# Issuer: CN=Trustwave Global Certification Authority O=Trustwave Holdings, Inc. +# Subject: CN=Trustwave Global Certification Authority O=Trustwave Holdings, Inc. +# Label: "Trustwave Global Certification Authority" +# Serial: 1846098327275375458322922162 +# MD5 Fingerprint: f8:1c:18:2d:2f:ba:5f:6d:a1:6c:bc:c7:ab:91:c7:0e +# SHA1 Fingerprint: 2f:8f:36:4f:e1:58:97:44:21:59:87:a5:2a:9a:d0:69:95:26:7f:b5 +# SHA256 Fingerprint: 97:55:20:15:f5:dd:fc:3c:87:88:c0:06:94:45:55:40:88:94:45:00:84:f1:00:86:70:86:bc:1a:2b:b5:8d:c8 +-----BEGIN CERTIFICATE----- +MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQsw +CQYDVQQGEwJVUzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28x +ITAfBgNVBAoMGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1 +c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMx +OTM0MTJaFw00MjA4MjMxOTM0MTJaMIGIMQswCQYDVQQGEwJVUzERMA8GA1UECAwI +SWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2ZSBI +b2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +ALldUShLPDeS0YLOvR29zd24q88KPuFd5dyqCblXAj7mY2Hf8g+CY66j96xz0Xzn +swuvCAAJWX/NKSqIk4cXGIDtiLK0thAfLdZfVaITXdHG6wZWiYj+rDKd/VzDBcdu +7oaJuogDnXIhhpCujwOl3J+IKMujkkkP7NAP4m1ET4BqstTnoApTAbqOl5F2brz8 +1Ws25kCI1nsvXwXoLG0R8+eyvpJETNKXpP7ScoFDB5zpET71ixpZfR9oWN0EACyW +80OzfpgZdNmcc9kYvkHHNHnZ9GLCQ7mzJ7Aiy/k9UscwR7PJPrhq4ufogXBeQotP +JqX+OsIgbrv4Fo7NDKm0G2x2EOFYeUY+VM6AqFcJNykbmROPDMjWLBz7BegIlT1l +RtzuzWniTY+HKE40Cz7PFNm73bZQmq131BnW2hqIyE4bJ3XYsgjxroMwuREOzYfw +hI0Vcnyh78zyiGG69Gm7DIwLdVcEuE4qFC49DxweMqZiNu5m4iK4BUBjECLzMx10 +coos9TkpoNPnG4CELcU9402x/RpvumUHO1jsQkUm+9jaJXLE9gCxInm943xZYkqc +BW89zubWR2OZxiRvchLIrH+QtAuRcOi35hYQcRfO3gZPSEF9NUqjifLJS3tBEW1n +twiYTOURGa5CgNz7kAXU+FDKvuStx8KU1xad5hePrzb7AgMBAAGjQjBAMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFJngGWcNYtt2s9o9uFvo/ULSMQ6HMA4GA1Ud +DwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAmHNw4rDT7TnsTGDZqRKGFx6W +0OhUKDtkLSGm+J1WE2pIPU/HPinbbViDVD2HfSMF1OQc3Og4ZYbFdada2zUFvXfe +uyk3QAUHw5RSn8pk3fEbK9xGChACMf1KaA0HZJDmHvUqoai7PF35owgLEQzxPy0Q +lG/+4jSHg9bP5Rs1bdID4bANqKCqRieCNqcVtgimQlRXtpla4gt5kNdXElE1GYhB +aCXUNxeEFfsBctyV3lImIJgm4nb1J2/6ADtKYdkNy1GTKv0WBpanI5ojSP5RvbbE +sLFUzt5sQa0WZ37b/TjNuThOssFgy50X31ieemKyJo90lZvkWx3SD92YHJtZuSPT +MaCm/zjdzyBP6VhWOmfD0faZmZ26NraAL4hHT4a/RDqA5Dccprrql5gR0IRiR2Qe +qu5AvzSxnI9O4fKSTx+O856X3vOmeWqJcU9LJxdI/uz0UA9PSX3MReO9ekDFQdxh +VicGaeVyQYHTtgGJoC86cnn+OjC/QezHYj6RS8fZMXZC+fc8Y+wmjHMMfRod6qh8 +h6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu3R3y4G5OBVixwJAWKqQ9 +EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP29FpHOTK +yeC2nOnOcXHebD8WpHk= +-----END CERTIFICATE----- + +# Issuer: CN=Trustwave Global ECC P256 Certification Authority O=Trustwave Holdings, Inc. +# Subject: CN=Trustwave Global ECC P256 Certification Authority O=Trustwave Holdings, Inc. +# Label: "Trustwave Global ECC P256 Certification Authority" +# Serial: 4151900041497450638097112925 +# MD5 Fingerprint: 5b:44:e3:8d:5d:36:86:26:e8:0d:05:d2:59:a7:83:54 +# SHA1 Fingerprint: b4:90:82:dd:45:0c:be:8b:5b:b1:66:d3:e2:a4:08:26:cd:ed:42:cf +# SHA256 Fingerprint: 94:5b:bc:82:5e:a5:54:f4:89:d1:fd:51:a7:3d:df:2e:a6:24:ac:70:19:a0:52:05:22:5c:22:a7:8c:cf:a8:b4 +-----BEGIN CERTIFICATE----- +MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf +BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3 +YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x +NzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYDVQQGEwJVUzERMA8G +A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0 +d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF +Q0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqG +SM49AwEHA0IABH77bOYj43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoN +FWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqmP62jQzBBMA8GA1UdEwEB/wQFMAMBAf8w +DwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt0UrrdaVKEJmzsaGLSvcw +CgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjzRM4q3wgh +DDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7 +-----END CERTIFICATE----- + +# Issuer: CN=Trustwave Global ECC P384 Certification Authority O=Trustwave Holdings, Inc. +# Subject: CN=Trustwave Global ECC P384 Certification Authority O=Trustwave Holdings, Inc. +# Label: "Trustwave Global ECC P384 Certification Authority" +# Serial: 2704997926503831671788816187 +# MD5 Fingerprint: ea:cf:60:c4:3b:b9:15:29:40:a1:97:ed:78:27:93:d6 +# SHA1 Fingerprint: e7:f3:a3:c8:cf:6f:c3:04:2e:6d:0e:67:32:c5:9e:68:95:0d:5e:d2 +# SHA256 Fingerprint: 55:90:38:59:c8:c0:c3:eb:b8:75:9e:ce:4e:25:57:22:5f:f5:75:8b:bd:38:eb:d4:82:76:60:1e:1b:d5:80:97 +-----BEGIN CERTIFICATE----- +MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf +BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3 +YXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x +NzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYDVQQGEwJVUzERMA8G +A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0 +d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF +Q0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuB +BAAiA2IABGvaDXU1CDFHBa5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJ +j9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr/TklZvFe/oyujUF5nQlgziip04pt89ZF +1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNVHQ8BAf8EBQMDBwYAMB0G +A1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNnADBkAjA3 +AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsC +MGclCrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVu +Sw== +-----END CERTIFICATE----- + +# Issuer: CN=NAVER Global Root Certification Authority O=NAVER BUSINESS PLATFORM Corp. +# Subject: CN=NAVER Global Root Certification Authority O=NAVER BUSINESS PLATFORM Corp. +# Label: "NAVER Global Root Certification Authority" +# Serial: 9013692873798656336226253319739695165984492813 +# MD5 Fingerprint: c8:7e:41:f6:25:3b:f5:09:b3:17:e8:46:3d:bf:d0:9b +# SHA1 Fingerprint: 8f:6b:f2:a9:27:4a:da:14:a0:c4:f4:8e:61:27:f9:c0:1e:78:5d:d1 +# SHA256 Fingerprint: 88:f4:38:dc:f8:ff:d1:fa:8f:42:91:15:ff:e5:f8:2a:e1:e0:6e:0c:70:c3:75:fa:ad:71:7b:34:a4:9e:72:65 +-----BEGIN CERTIFICATE----- +MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEM +BQAwaTELMAkGA1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRG +T1JNIENvcnAuMTIwMAYDVQQDDClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4NDJaFw0zNzA4MTgyMzU5NTlaMGkx +CzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVTUyBQTEFURk9STSBD +b3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVA +iQqrDZBbUGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH +38dq6SZeWYp34+hInDEW+j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lE +HoSTGEq0n+USZGnQJoViAbbJAh2+g1G7XNr4rRVqmfeSVPc0W+m/6imBEtRTkZaz +kVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2aacp+yPOiNgSnABIqKYP +szuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4Yb8Obtoq +vC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHf +nZ3zVHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaG +YQ5fG8Ir4ozVu53BA0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo +0es+nPxdGoMuK8u180SdOqcXYZaicdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3a +CJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejyYhbLgGvtPe31HzClrkvJE+2K +AQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNVHQ4EFgQU0p+I +36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB +Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoN +qo0hV4/GPnrK21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatj +cu3cvuzHV+YwIHHW1xDBE1UBjCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm ++LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bxhYTeodoS76TiEJd6eN4MUZeoIUCL +hr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTgE34h5prCy8VCZLQe +lHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTHD8z7 +p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8 +piKCk5XQA76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLR +LBT/DShycpWbXgnbiUSYqqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX +5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oGI/hGoiLtk/bdmuYqh7GYVPEi92tF4+KO +dh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmgkpzNNIaRkPpkUZ3+/uul +9XXeifdy +-----END CERTIFICATE----- + +# Issuer: CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS O=FNMT-RCM OU=Ceres +# Subject: CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS O=FNMT-RCM OU=Ceres +# Label: "AC RAIZ FNMT-RCM SERVIDORES SEGUROS" +# Serial: 131542671362353147877283741781055151509 +# MD5 Fingerprint: 19:36:9c:52:03:2f:d2:d1:bb:23:cc:dd:1e:12:55:bb +# SHA1 Fingerprint: 62:ff:d9:9e:c0:65:0d:03:ce:75:93:d2:ed:3f:2d:32:c9:e3:e5:4a +# SHA256 Fingerprint: 55:41:53:b1:3d:2c:f9:dd:b7:53:bf:be:1a:4e:0a:e0:8d:0a:a4:18:70:58:fe:60:a2:b8:62:b2:e4:b8:7b:cb +-----BEGIN CERTIFICATE----- +MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQsw +CQYDVQQGEwJFUzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgw +FgYDVQRhDA9WQVRFUy1RMjgyNjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1S +Q00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4MTIyMDA5MzczM1oXDTQzMTIyMDA5 +MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQtUkNNMQ4wDAYDVQQL +DAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNBQyBS +QUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LH +sbI6GA60XYyzZl2hNPk2LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oK +Um8BA06Oi6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqGSM49BAMDA2kAMGYCMQCu +SuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoDzBOQn5IC +MQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJy +v+c= +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign Root R46 O=GlobalSign nv-sa +# Subject: CN=GlobalSign Root R46 O=GlobalSign nv-sa +# Label: "GlobalSign Root R46" +# Serial: 1552617688466950547958867513931858518042577 +# MD5 Fingerprint: c4:14:30:e4:fa:66:43:94:2a:6a:1b:24:5f:19:d0:ef +# SHA1 Fingerprint: 53:a2:b0:4b:ca:6b:d6:45:e6:39:8a:8e:c4:0d:d2:bf:77:c3:a2:90 +# SHA256 Fingerprint: 4f:a3:12:6d:8d:3a:11:d1:c4:85:5a:4f:80:7c:ba:d6:cf:91:9d:3a:5a:88:b0:3b:ea:2c:63:72:d9:3c:40:c9 +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUA +MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYD +VQQDExNHbG9iYWxTaWduIFJvb3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMy +MDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYt +c2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08EsCVeJ +OaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQG +vGIFAha/r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud +316HCkD7rRlr+/fKYIje2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo +0q3v84RLHIf8E6M6cqJaESvWJ3En7YEtbWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSE +y132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvjK8Cd+RTyG/FWaha/LIWF +zXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD412lPFzYE ++cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCN +I/onccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzs +x2sZy/N78CsHpdlseVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqa +ByFrgY/bxFn63iLABJzjqls2k+g9vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC +4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEMBQADggIBAHx4 +7PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg +JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti +2kM3S+LGteWygxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIk +pnnpHs6i58FZFZ8d4kuaPp92CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRF +FRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZmOUdkLG5NrmJ7v2B0GbhWrJKsFjLt +rWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qqJZ4d16GLuc1CLgSk +ZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwyeqiv5 +u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP +4vkYxboznxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6 +N3ec592kD3ZDZopD8p/7DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3 +vouXsXgxT7PntgMTzlSdriVZzH81Xwj3QEUxeCp6 +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign Root E46 O=GlobalSign nv-sa +# Subject: CN=GlobalSign Root E46 O=GlobalSign nv-sa +# Label: "GlobalSign Root E46" +# Serial: 1552617690338932563915843282459653771421763 +# MD5 Fingerprint: b5:b8:66:ed:de:08:83:e3:c9:e2:01:34:06:ac:51:6f +# SHA1 Fingerprint: 39:b4:6c:d5:fe:80:06:eb:e2:2f:4a:bb:08:33:a0:af:db:b9:dd:84 +# SHA256 Fingerprint: cb:b9:c4:4d:84:b8:04:3e:10:50:ea:31:a6:9f:51:49:55:d7:bf:d2:e2:c6:b4:93:01:01:9a:d6:1d:9f:50:58 +-----BEGIN CERTIFICATE----- +MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYx +CzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQD +ExNHbG9iYWxTaWduIFJvb3QgRTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAw +MDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2Ex +HDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkBjtjq +R+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGdd +yXqBPCCjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBQxCpCPtsad0kRLgLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ +7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZkvLtoURMMA/cVi4RguYv/Uo7njLwcAjA8 ++RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+CAezNIm8BZ/3Hobui3A= +-----END CERTIFICATE----- + +# Issuer: CN=GLOBALTRUST 2020 O=e-commerce monitoring GmbH +# Subject: CN=GLOBALTRUST 2020 O=e-commerce monitoring GmbH +# Label: "GLOBALTRUST 2020" +# Serial: 109160994242082918454945253 +# MD5 Fingerprint: 8a:c7:6f:cb:6d:e3:cc:a2:f1:7c:83:fa:0e:78:d7:e8 +# SHA1 Fingerprint: d0:67:c1:13:51:01:0c:aa:d0:c7:6a:65:37:31:16:26:4f:53:71:a2 +# SHA256 Fingerprint: 9a:29:6a:51:82:d1:d4:51:a2:e3:7f:43:9b:74:da:af:a2:67:52:33:29:f9:0f:9a:0d:20:07:c3:34:e2:3c:9a +-----BEGIN CERTIFICATE----- +MIIFgjCCA2qgAwIBAgILWku9WvtPilv6ZeUwDQYJKoZIhvcNAQELBQAwTTELMAkG +A1UEBhMCQVQxIzAhBgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkw +FwYDVQQDExBHTE9CQUxUUlVTVCAyMDIwMB4XDTIwMDIxMDAwMDAwMFoXDTQwMDYx +MDAwMDAwMFowTTELMAkGA1UEBhMCQVQxIzAhBgNVBAoTGmUtY29tbWVyY2UgbW9u +aXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVTVCAyMDIwMIICIjANBgkq +hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAri5WrRsc7/aVj6B3GyvTY4+ETUWiD59b +RatZe1E0+eyLinjF3WuvvcTfk0Uev5E4C64OFudBc/jbu9G4UeDLgztzOG53ig9Z +YybNpyrOVPu44sB8R85gfD+yc/LAGbaKkoc1DZAoouQVBGM+uq/ufF7MpotQsjj3 +QWPKzv9pj2gOlTblzLmMCcpL3TGQlsjMH/1WljTbjhzqLL6FLmPdqqmV0/0plRPw +yJiT2S0WR5ARg6I6IqIoV6Lr/sCMKKCmfecqQjuCgGOlYx8ZzHyyZqjC0203b+J+ +BlHZRYQfEs4kUmSFC0iAToexIiIwquuuvuAC4EDosEKAA1GqtH6qRNdDYfOiaxaJ +SaSjpCuKAsR49GiKweR6NrFvG5Ybd0mN1MkGco/PU+PcF4UgStyYJ9ORJitHHmkH +r96i5OTUawuzXnzUJIBHKWk7buis/UDr2O1xcSvy6Fgd60GXIsUf1DnQJ4+H4xj0 +4KlGDfV0OoIu0G4skaMxXDtG6nsEEFZegB31pWXogvziB4xiRfUg3kZwhqG8k9Me +dKZssCz3AwyIDMvUclOGvGBG85hqwvG/Q/lwIHfKN0F5VVJjjVsSn8VoxIidrPIw +q7ejMZdnrY8XD2zHc+0klGvIg5rQmjdJBKuxFshsSUktq6HQjJLyQUp5ISXbY9e2 +nKd+Qmn7OmMCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFNwuH9FhN3nkq9XVsxJxaD1qaJwiMB8GA1UdIwQYMBaAFNwu +H9FhN3nkq9XVsxJxaD1qaJwiMA0GCSqGSIb3DQEBCwUAA4ICAQCR8EICaEDuw2jA +VC/f7GLDw56KoDEoqoOOpFaWEhCGVrqXctJUMHytGdUdaG/7FELYjQ7ztdGl4wJC +XtzoRlgHNQIw4Lx0SsFDKv/bGtCwr2zD/cuz9X9tAy5ZVp0tLTWMstZDFyySCstd +6IwPS3BD0IL/qMy/pJTAvoe9iuOTe8aPmxadJ2W8esVCgmxcB9CpwYhgROmYhRZf ++I/KARDOJcP5YBugxZfD0yyIMaK9MOzQ0MAS8cE54+X1+NZK3TTN+2/BT+MAi1bi +kvcoskJ3ciNnxz8RFbLEAwW+uxF7Cr+obuf/WEPPm2eggAe2HcqtbepBEX4tdJP7 +wry+UUTF72glJ4DjyKDUEuzZpTcdN3y0kcra1LGWge9oXHYQSa9+pTeAsRxSvTOB +TI/53WXZFM2KJVj04sWDpQmQ1GwUY7VA3+vA/MRYfg0UFodUJ25W5HCEuGwyEn6C +MUO+1918oa2u1qsgEu8KwxCMSZY13At1XrFP1U80DhEgB3VDRemjEdqso5nCtnkn +4rnvyOL2NSl6dPrFf4IFYqYK6miyeUcGbvJXqBUzxvd4Sj1Ce2t+/vdG6tHrju+I +aFvowdlxfv1k7/9nR4hYJS8+hge9+6jlgqispdNpQ80xiEmEU5LAsTkbOYMBMMTy +qfrQA71yN2BWHzZ8vTmR9W0Nv3vXkg== +-----END CERTIFICATE----- + +# Issuer: CN=ANF Secure Server Root CA O=ANF Autoridad de Certificacion OU=ANF CA Raiz +# Subject: CN=ANF Secure Server Root CA O=ANF Autoridad de Certificacion OU=ANF CA Raiz +# Label: "ANF Secure Server Root CA" +# Serial: 996390341000653745 +# MD5 Fingerprint: 26:a6:44:5a:d9:af:4e:2f:b2:1d:b6:65:b0:4e:e8:96 +# SHA1 Fingerprint: 5b:6e:68:d0:cc:15:b6:a0:5f:1e:c1:5f:ae:02:fc:6b:2f:5d:6f:74 +# SHA256 Fingerprint: fb:8f:ec:75:91:69:b9:10:6b:1e:51:16:44:c6:18:c5:13:04:37:3f:6c:06:43:08:8d:8b:ef:fd:1b:99:75:99 +-----BEGIN CERTIFICATE----- +MIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNV +BAUTCUc2MzI4NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlk +YWQgZGUgQ2VydGlmaWNhY2lvbjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNV +BAMTGUFORiBTZWN1cmUgU2VydmVyIFJvb3QgQ0EwHhcNMTkwOTA0MTAwMDM4WhcN +MzkwODMwMTAwMDM4WjCBhDESMBAGA1UEBRMJRzYzMjg3NTEwMQswCQYDVQQGEwJF +UzEnMCUGA1UEChMeQU5GIEF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uMRQwEgYD +VQQLEwtBTkYgQ0EgUmFpejEiMCAGA1UEAxMZQU5GIFNlY3VyZSBTZXJ2ZXIgUm9v +dCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANvrayvmZFSVgpCj +cqQZAZ2cC4Ffc0m6p6zzBE57lgvsEeBbphzOG9INgxwruJ4dfkUyYA8H6XdYfp9q +yGFOtibBTI3/TO80sh9l2Ll49a2pcbnvT1gdpd50IJeh7WhM3pIXS7yr/2WanvtH +2Vdy8wmhrnZEE26cLUQ5vPnHO6RYPUG9tMJJo8gN0pcvB2VSAKduyK9o7PQUlrZX +H1bDOZ8rbeTzPvY1ZNoMHKGESy9LS+IsJJ1tk0DrtSOOMspvRdOoiXsezx76W0OL +zc2oD2rKDF65nkeP8Nm2CgtYZRczuSPkdxl9y0oukntPLxB3sY0vaJxizOBQ+OyR +p1RMVwnVdmPF6GUe7m1qzwmd+nxPrWAI/VaZDxUse6mAq4xhj0oHdkLePfTdsiQz +W7i1o0TJrH93PB0j7IKppuLIBkwC/qxcmZkLLxCKpvR/1Yd0DVlJRfbwcVw5Kda/ +SiOL9V8BY9KHcyi1Swr1+KuCLH5zJTIdC2MKF4EA/7Z2Xue0sUDKIbvVgFHlSFJn +LNJhiQcND85Cd8BEc5xEUKDbEAotlRyBr+Qc5RQe8TZBAQIvfXOn3kLMTOmJDVb3 +n5HUA8ZsyY/b2BzgQJhdZpmYgG4t/wHFzstGH6wCxkPmrqKEPMVOHj1tyRRM4y5B +u8o5vzY8KhmqQYdOpc5LMnndkEl/AgMBAAGjYzBhMB8GA1UdIwQYMBaAFJxf0Gxj +o1+TypOYCK2Mh6UsXME3MB0GA1UdDgQWBBScX9BsY6Nfk8qTmAitjIelLFzBNzAO +BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC +AgEATh65isagmD9uw2nAalxJUqzLK114OMHVVISfk/CHGT0sZonrDUL8zPB1hT+L +9IBdeeUXZ701guLyPI59WzbLWoAAKfLOKyzxj6ptBZNscsdW699QIyjlRRA96Gej +rw5VD5AJYu9LWaL2U/HANeQvwSS9eS9OICI7/RogsKQOLHDtdD+4E5UGUcjohybK +pFtqFiGS3XNgnhAY3jyB6ugYw3yJ8otQPr0R4hUDqDZ9MwFsSBXXiJCZBMXM5gf0 +vPSQ7RPi6ovDj6MzD8EpTBNO2hVWcXNyglD2mjN8orGoGjR0ZVzO0eurU+AagNjq +OknkJjCb5RyKqKkVMoaZkgoQI1YS4PbOTOK7vtuNknMBZi9iPrJyJ0U27U1W45eZ +/zo1PqVUSlJZS2Db7v54EX9K3BR5YLZrZAPbFYPhor72I5dQ8AkzNqdxliXzuUJ9 +2zg/LFis6ELhDtjTO0wugumDLmsx2d1Hhk9tl5EuT+IocTUW0fJz/iUrB0ckYyfI ++PbZa/wSMVYIwFNCr5zQM378BvAxRAMU8Vjq8moNqRGyg77FGr8H6lnco4g175x2 +MjxNBiLOFeXdntiP2t7SxDnlF4HPOEfrf4htWRvfn0IUrn7PqLBmZdo3r5+qPeoo +tt7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw= +-----END CERTIFICATE----- + +# Issuer: CN=Certum EC-384 CA O=Asseco Data Systems S.A. OU=Certum Certification Authority +# Subject: CN=Certum EC-384 CA O=Asseco Data Systems S.A. OU=Certum Certification Authority +# Label: "Certum EC-384 CA" +# Serial: 160250656287871593594747141429395092468 +# MD5 Fingerprint: b6:65:b3:96:60:97:12:a1:ec:4e:e1:3d:a3:c6:c9:f1 +# SHA1 Fingerprint: f3:3e:78:3c:ac:df:f4:a2:cc:ac:67:55:69:56:d7:e5:16:3c:e1:ed +# SHA256 Fingerprint: 6b:32:80:85:62:53:18:aa:50:d1:73:c9:8d:8b:da:09:d5:7e:27:41:3d:11:4c:f7:87:a0:f5:d0:6c:03:0c:f6 +-----BEGIN CERTIFICATE----- +MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQsw +CQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScw +JQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMT +EENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2MDcyNDU0WhcNNDMwMzI2MDcyNDU0 +WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBT +LkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAX +BgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATE +KI6rGFtqvm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7Tm +Fy8as10CW4kjPMIRBSqniBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68Kj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI0GZnQkdjrzife81r1HfS+8 +EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNoADBlAjADVS2m5hjEfO/J +UG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0QoSZ/6vn +nvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k= +-----END CERTIFICATE----- + +# Issuer: CN=Certum Trusted Root CA O=Asseco Data Systems S.A. OU=Certum Certification Authority +# Subject: CN=Certum Trusted Root CA O=Asseco Data Systems S.A. OU=Certum Certification Authority +# Label: "Certum Trusted Root CA" +# Serial: 40870380103424195783807378461123655149 +# MD5 Fingerprint: 51:e1:c2:e7:fe:4c:84:af:59:0e:2f:f4:54:6f:ea:29 +# SHA1 Fingerprint: c8:83:44:c0:18:ae:9f:cc:f1:87:b7:8f:22:d1:c5:d7:45:84:ba:e5 +# SHA256 Fingerprint: fe:76:96:57:38:55:77:3e:37:a9:5e:7a:d4:d9:cc:96:c3:01:57:c1:5d:31:76:5b:a9:b1:57:04:e1:ae:78:fd +-----BEGIN CERTIFICATE----- +MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6 +MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEu +MScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNV +BAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwHhcNMTgwMzE2MTIxMDEzWhcNNDMw +MzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEg +U3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRo +b3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZ +n0EGze2jusDbCSzBfN8pfktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/q +p1x4EaTByIVcJdPTsuclzxFUl6s1wB52HO8AU5853BSlLCIls3Jy/I2z5T4IHhQq +NwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2fJmItdUDmj0VDT06qKhF +8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGtg/BKEiJ3 +HAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGa +mqi4NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi +7VdNIuJGmj8PkTQkfVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSF +ytKAQd8FqKPVhJBPC/PgP5sZ0jeJP/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0P +qafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSYnjYJdmZm/Bo/6khUHL4wvYBQ +v3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHKHRzQ+8S1h9E6 +Tsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1 +vALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQAD +ggIBAEii1QALLtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4 +WxmB82M+w85bj/UvXgF2Ez8sALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvo +zMrnadyHncI013nR03e4qllY/p0m+jiGPp2Kh2RX5Rc64vmNueMzeMGQ2Ljdt4NR +5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8CYyqOhNf6DR5UMEQ +GfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA4kZf +5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq +0Uc9NneoWWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7D +P78v3DSk+yshzWePS/Tj6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTM +qJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmTOPQD8rv7gmsHINFSH5pkAnuYZttcTVoP +0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZckbxJF0WddCajJFdr60qZf +E2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb +-----END CERTIFICATE----- diff --git a/pipenv/vendor/certifi/core.py b/pipenv/vendor/certifi/core.py index 7271acf4..5d2b8cd3 100644 --- a/pipenv/vendor/certifi/core.py +++ b/pipenv/vendor/certifi/core.py @@ -4,12 +4,57 @@ certifi.py ~~~~~~~~~~ -This module returns the installation location of cacert.pem. +This module returns the installation location of cacert.pem or its contents. """ import os +try: + from importlib.resources import path as get_path, read_text -def where(): - f = os.path.dirname(__file__) + _CACERT_CTX = None + _CACERT_PATH = None - return os.path.join(f, 'cacert.pem') + def where(): + # This is slightly terrible, but we want to delay extracting the file + # in cases where we're inside of a zipimport situation until someone + # actually calls where(), but we don't want to re-extract the file + # on every call of where(), so we'll do it once then store it in a + # global variable. + global _CACERT_CTX + global _CACERT_PATH + if _CACERT_PATH is None: + # This is slightly janky, the importlib.resources API wants you to + # manage the cleanup of this file, so it doesn't actually return a + # path, it returns a context manager that will give you the path + # when you enter it and will do any cleanup when you leave it. In + # the common case of not needing a temporary file, it will just + # return the file system location and the __exit__() is a no-op. + # + # We also have to hold onto the actual context manager, because + # it will do the cleanup whenever it gets garbage collected, so + # we will also store that at the global level as well. + _CACERT_CTX = get_path("certifi", "cacert.pem") + _CACERT_PATH = str(_CACERT_CTX.__enter__()) + + return _CACERT_PATH + + +except ImportError: + # This fallback will work for Python versions prior to 3.7 that lack the + # importlib.resources module but relies on the existing `where` function + # so won't address issues with environments like PyOxidizer that don't set + # __file__ on modules. + def read_text(_module, _path, encoding="ascii"): + with open(where(), "r", encoding=encoding) as data: + return data.read() + + # If we don't have importlib.resources, then we will just do the old logic + # of assuming we're on the filesystem and munge the path directly. + def where(): + f = os.path.dirname(__file__) + + return os.path.join(f, "cacert.pem") + + +def contents(): + return read_text("certifi", "cacert.pem", encoding="ascii") diff --git a/pipenv/vendor/chardet/LICENSE b/pipenv/vendor/chardet/LICENSE deleted file mode 100644 index 8add30ad..00000000 --- a/pipenv/vendor/chardet/LICENSE +++ /dev/null @@ -1,504 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations below. - - When we speak of free software, we are referring to freedom of use, -not price. Our General Public Licenses are designed to make sure that -you have the freedom to distribute copies of free software (and charge -for this service if you wish); that you receive source code or can get -it if you want it; that you can change the software and use pieces of -it in new free programs; and that you are informed that you can do -these things. - - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. - - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it becomes -a de-facto standard. To achieve this, non-free programs must be -allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. - - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation -and installation of the library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - - 2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (1) uses at run time a - copy of the library already present on the user's computer system, - rather than copying library functions into the executable, and (2) - will operate properly with a modified version of the library, if - the user installs one, as long as the modified version is - interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - e) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be distributed need not include anything that is -normally distributed (in either source or binary form) with the major -components (compiler, kernel, and so on) of the operating system on -which the executable runs, unless that component itself accompanies -the executable. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - - 9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties with -this License. - - 11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply, -and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License may add -an explicit geographical distribution limitation excluding those countries, -so that distribution is permitted only in or among countries not thus -excluded. In such case, this License incorporates the limitation as if -written in the body of this License. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Lesser General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms of the -ordinary General Public License). - - To apply these terms, attach the following notices to the library. It is -safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. - - <one line to give the library's name and a brief idea of what it does.> - Copyright (C) <year> <name of author> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James Random Hacker. - - <signature of Ty Coon>, 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! - - diff --git a/pipenv/vendor/chardet/__init__.py b/pipenv/vendor/chardet/__init__.py deleted file mode 100644 index 0f9f820e..00000000 --- a/pipenv/vendor/chardet/__init__.py +++ /dev/null @@ -1,39 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - - -from .compat import PY2, PY3 -from .universaldetector import UniversalDetector -from .version import __version__, VERSION - - -def detect(byte_str): - """ - Detect the encoding of the given byte string. - - :param byte_str: The byte sequence to examine. - :type byte_str: ``bytes`` or ``bytearray`` - """ - if not isinstance(byte_str, bytearray): - if not isinstance(byte_str, bytes): - raise TypeError('Expected object of type bytes or bytearray, got: ' - '{0}'.format(type(byte_str))) - else: - byte_str = bytearray(byte_str) - detector = UniversalDetector() - detector.feed(byte_str) - return detector.close() diff --git a/pipenv/vendor/chardet/big5freq.py b/pipenv/vendor/chardet/big5freq.py deleted file mode 100644 index 38f32517..00000000 --- a/pipenv/vendor/chardet/big5freq.py +++ /dev/null @@ -1,386 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -# Big5 frequency table -# by Taiwan's Mandarin Promotion Council -# <http://www.edu.tw:81/mandr/> -# -# 128 --> 0.42261 -# 256 --> 0.57851 -# 512 --> 0.74851 -# 1024 --> 0.89384 -# 2048 --> 0.97583 -# -# Ideal Distribution Ratio = 0.74851/(1-0.74851) =2.98 -# Random Distribution Ration = 512/(5401-512)=0.105 -# -# Typical Distribution Ratio about 25% of Ideal one, still much higher than RDR - -BIG5_TYPICAL_DISTRIBUTION_RATIO = 0.75 - -#Char to FreqOrder table -BIG5_TABLE_SIZE = 5376 - -BIG5_CHAR_TO_FREQ_ORDER = ( - 1,1801,1506, 255,1431, 198, 9, 82, 6,5008, 177, 202,3681,1256,2821, 110, # 16 -3814, 33,3274, 261, 76, 44,2114, 16,2946,2187,1176, 659,3971, 26,3451,2653, # 32 -1198,3972,3350,4202, 410,2215, 302, 590, 361,1964, 8, 204, 58,4510,5009,1932, # 48 - 63,5010,5011, 317,1614, 75, 222, 159,4203,2417,1480,5012,3555,3091, 224,2822, # 64 -3682, 3, 10,3973,1471, 29,2787,1135,2866,1940, 873, 130,3275,1123, 312,5013, # 80 -4511,2052, 507, 252, 682,5014, 142,1915, 124, 206,2947, 34,3556,3204, 64, 604, # 96 -5015,2501,1977,1978, 155,1991, 645, 641,1606,5016,3452, 337, 72, 406,5017, 80, # 112 - 630, 238,3205,1509, 263, 939,1092,2654, 756,1440,1094,3453, 449, 69,2987, 591, # 128 - 179,2096, 471, 115,2035,1844, 60, 50,2988, 134, 806,1869, 734,2036,3454, 180, # 144 - 995,1607, 156, 537,2907, 688,5018, 319,1305, 779,2145, 514,2379, 298,4512, 359, # 160 -2502, 90,2716,1338, 663, 11, 906,1099,2553, 20,2441, 182, 532,1716,5019, 732, # 176 -1376,4204,1311,1420,3206, 25,2317,1056, 113, 399, 382,1950, 242,3455,2474, 529, # 192 -3276, 475,1447,3683,5020, 117, 21, 656, 810,1297,2300,2334,3557,5021, 126,4205, # 208 - 706, 456, 150, 613,4513, 71,1118,2037,4206, 145,3092, 85, 835, 486,2115,1246, # 224 -1426, 428, 727,1285,1015, 800, 106, 623, 303,1281,5022,2128,2359, 347,3815, 221, # 240 -3558,3135,5023,1956,1153,4207, 83, 296,1199,3093, 192, 624, 93,5024, 822,1898, # 256 -2823,3136, 795,2065, 991,1554,1542,1592, 27, 43,2867, 859, 139,1456, 860,4514, # 272 - 437, 712,3974, 164,2397,3137, 695, 211,3037,2097, 195,3975,1608,3559,3560,3684, # 288 -3976, 234, 811,2989,2098,3977,2233,1441,3561,1615,2380, 668,2077,1638, 305, 228, # 304 -1664,4515, 467, 415,5025, 262,2099,1593, 239, 108, 300, 200,1033, 512,1247,2078, # 320 -5026,5027,2176,3207,3685,2682, 593, 845,1062,3277, 88,1723,2038,3978,1951, 212, # 336 - 266, 152, 149, 468,1899,4208,4516, 77, 187,5028,3038, 37, 5,2990,5029,3979, # 352 -5030,5031, 39,2524,4517,2908,3208,2079, 55, 148, 74,4518, 545, 483,1474,1029, # 368 -1665, 217,1870,1531,3138,1104,2655,4209, 24, 172,3562, 900,3980,3563,3564,4519, # 384 - 32,1408,2824,1312, 329, 487,2360,2251,2717, 784,2683, 4,3039,3351,1427,1789, # 400 - 188, 109, 499,5032,3686,1717,1790, 888,1217,3040,4520,5033,3565,5034,3352,1520, # 416 -3687,3981, 196,1034, 775,5035,5036, 929,1816, 249, 439, 38,5037,1063,5038, 794, # 432 -3982,1435,2301, 46, 178,3278,2066,5039,2381,5040, 214,1709,4521, 804, 35, 707, # 448 - 324,3688,1601,2554, 140, 459,4210,5041,5042,1365, 839, 272, 978,2262,2580,3456, # 464 -2129,1363,3689,1423, 697, 100,3094, 48, 70,1231, 495,3139,2196,5043,1294,5044, # 480 -2080, 462, 586,1042,3279, 853, 256, 988, 185,2382,3457,1698, 434,1084,5045,3458, # 496 - 314,2625,2788,4522,2335,2336, 569,2285, 637,1817,2525, 757,1162,1879,1616,3459, # 512 - 287,1577,2116, 768,4523,1671,2868,3566,2526,1321,3816, 909,2418,5046,4211, 933, # 528 -3817,4212,2053,2361,1222,4524, 765,2419,1322, 786,4525,5047,1920,1462,1677,2909, # 544 -1699,5048,4526,1424,2442,3140,3690,2600,3353,1775,1941,3460,3983,4213, 309,1369, # 560 -1130,2825, 364,2234,1653,1299,3984,3567,3985,3986,2656, 525,1085,3041, 902,2001, # 576 -1475, 964,4527, 421,1845,1415,1057,2286, 940,1364,3141, 376,4528,4529,1381, 7, # 592 -2527, 983,2383, 336,1710,2684,1846, 321,3461, 559,1131,3042,2752,1809,1132,1313, # 608 - 265,1481,1858,5049, 352,1203,2826,3280, 167,1089, 420,2827, 776, 792,1724,3568, # 624 -4214,2443,3281,5050,4215,5051, 446, 229, 333,2753, 901,3818,1200,1557,4530,2657, # 640 -1921, 395,2754,2685,3819,4216,1836, 125, 916,3209,2626,4531,5052,5053,3820,5054, # 656 -5055,5056,4532,3142,3691,1133,2555,1757,3462,1510,2318,1409,3569,5057,2146, 438, # 672 -2601,2910,2384,3354,1068, 958,3043, 461, 311,2869,2686,4217,1916,3210,4218,1979, # 688 - 383, 750,2755,2627,4219, 274, 539, 385,1278,1442,5058,1154,1965, 384, 561, 210, # 704 - 98,1295,2556,3570,5059,1711,2420,1482,3463,3987,2911,1257, 129,5060,3821, 642, # 720 - 523,2789,2790,2658,5061, 141,2235,1333, 68, 176, 441, 876, 907,4220, 603,2602, # 736 - 710, 171,3464, 404, 549, 18,3143,2398,1410,3692,1666,5062,3571,4533,2912,4534, # 752 -5063,2991, 368,5064, 146, 366, 99, 871,3693,1543, 748, 807,1586,1185, 22,2263, # 768 - 379,3822,3211,5065,3212, 505,1942,2628,1992,1382,2319,5066, 380,2362, 218, 702, # 784 -1818,1248,3465,3044,3572,3355,3282,5067,2992,3694, 930,3283,3823,5068, 59,5069, # 800 - 585, 601,4221, 497,3466,1112,1314,4535,1802,5070,1223,1472,2177,5071, 749,1837, # 816 - 690,1900,3824,1773,3988,1476, 429,1043,1791,2236,2117, 917,4222, 447,1086,1629, # 832 -5072, 556,5073,5074,2021,1654, 844,1090, 105, 550, 966,1758,2828,1008,1783, 686, # 848 -1095,5075,2287, 793,1602,5076,3573,2603,4536,4223,2948,2302,4537,3825, 980,2503, # 864 - 544, 353, 527,4538, 908,2687,2913,5077, 381,2629,1943,1348,5078,1341,1252, 560, # 880 -3095,5079,3467,2870,5080,2054, 973, 886,2081, 143,4539,5081,5082, 157,3989, 496, # 896 -4224, 57, 840, 540,2039,4540,4541,3468,2118,1445, 970,2264,1748,1966,2082,4225, # 912 -3144,1234,1776,3284,2829,3695, 773,1206,2130,1066,2040,1326,3990,1738,1725,4226, # 928 - 279,3145, 51,1544,2604, 423,1578,2131,2067, 173,4542,1880,5083,5084,1583, 264, # 944 - 610,3696,4543,2444, 280, 154,5085,5086,5087,1739, 338,1282,3096, 693,2871,1411, # 960 -1074,3826,2445,5088,4544,5089,5090,1240, 952,2399,5091,2914,1538,2688, 685,1483, # 976 -4227,2475,1436, 953,4228,2055,4545, 671,2400, 79,4229,2446,3285, 608, 567,2689, # 992 -3469,4230,4231,1691, 393,1261,1792,2401,5092,4546,5093,5094,5095,5096,1383,1672, # 1008 -3827,3213,1464, 522,1119, 661,1150, 216, 675,4547,3991,1432,3574, 609,4548,2690, # 1024 -2402,5097,5098,5099,4232,3045, 0,5100,2476, 315, 231,2447, 301,3356,4549,2385, # 1040 -5101, 233,4233,3697,1819,4550,4551,5102, 96,1777,1315,2083,5103, 257,5104,1810, # 1056 -3698,2718,1139,1820,4234,2022,1124,2164,2791,1778,2659,5105,3097, 363,1655,3214, # 1072 -5106,2993,5107,5108,5109,3992,1567,3993, 718, 103,3215, 849,1443, 341,3357,2949, # 1088 -1484,5110,1712, 127, 67, 339,4235,2403, 679,1412, 821,5111,5112, 834, 738, 351, # 1104 -2994,2147, 846, 235,1497,1881, 418,1993,3828,2719, 186,1100,2148,2756,3575,1545, # 1120 -1355,2950,2872,1377, 583,3994,4236,2581,2995,5113,1298,3699,1078,2557,3700,2363, # 1136 - 78,3829,3830, 267,1289,2100,2002,1594,4237, 348, 369,1274,2197,2178,1838,4552, # 1152 -1821,2830,3701,2757,2288,2003,4553,2951,2758, 144,3358, 882,4554,3995,2759,3470, # 1168 -4555,2915,5114,4238,1726, 320,5115,3996,3046, 788,2996,5116,2831,1774,1327,2873, # 1184 -3997,2832,5117,1306,4556,2004,1700,3831,3576,2364,2660, 787,2023, 506, 824,3702, # 1200 - 534, 323,4557,1044,3359,2024,1901, 946,3471,5118,1779,1500,1678,5119,1882,4558, # 1216 - 165, 243,4559,3703,2528, 123, 683,4239, 764,4560, 36,3998,1793, 589,2916, 816, # 1232 - 626,1667,3047,2237,1639,1555,1622,3832,3999,5120,4000,2874,1370,1228,1933, 891, # 1248 -2084,2917, 304,4240,5121, 292,2997,2720,3577, 691,2101,4241,1115,4561, 118, 662, # 1264 -5122, 611,1156, 854,2386,1316,2875, 2, 386, 515,2918,5123,5124,3286, 868,2238, # 1280 -1486, 855,2661, 785,2216,3048,5125,1040,3216,3578,5126,3146, 448,5127,1525,5128, # 1296 -2165,4562,5129,3833,5130,4242,2833,3579,3147, 503, 818,4001,3148,1568, 814, 676, # 1312 -1444, 306,1749,5131,3834,1416,1030, 197,1428, 805,2834,1501,4563,5132,5133,5134, # 1328 -1994,5135,4564,5136,5137,2198, 13,2792,3704,2998,3149,1229,1917,5138,3835,2132, # 1344 -5139,4243,4565,2404,3580,5140,2217,1511,1727,1120,5141,5142, 646,3836,2448, 307, # 1360 -5143,5144,1595,3217,5145,5146,5147,3705,1113,1356,4002,1465,2529,2530,5148, 519, # 1376 -5149, 128,2133, 92,2289,1980,5150,4003,1512, 342,3150,2199,5151,2793,2218,1981, # 1392 -3360,4244, 290,1656,1317, 789, 827,2365,5152,3837,4566, 562, 581,4004,5153, 401, # 1408 -4567,2252, 94,4568,5154,1399,2794,5155,1463,2025,4569,3218,1944,5156, 828,1105, # 1424 -4245,1262,1394,5157,4246, 605,4570,5158,1784,2876,5159,2835, 819,2102, 578,2200, # 1440 -2952,5160,1502, 436,3287,4247,3288,2836,4005,2919,3472,3473,5161,2721,2320,5162, # 1456 -5163,2337,2068, 23,4571, 193, 826,3838,2103, 699,1630,4248,3098, 390,1794,1064, # 1472 -3581,5164,1579,3099,3100,1400,5165,4249,1839,1640,2877,5166,4572,4573, 137,4250, # 1488 - 598,3101,1967, 780, 104, 974,2953,5167, 278, 899, 253, 402, 572, 504, 493,1339, # 1504 -5168,4006,1275,4574,2582,2558,5169,3706,3049,3102,2253, 565,1334,2722, 863, 41, # 1520 -5170,5171,4575,5172,1657,2338, 19, 463,2760,4251, 606,5173,2999,3289,1087,2085, # 1536 -1323,2662,3000,5174,1631,1623,1750,4252,2691,5175,2878, 791,2723,2663,2339, 232, # 1552 -2421,5176,3001,1498,5177,2664,2630, 755,1366,3707,3290,3151,2026,1609, 119,1918, # 1568 -3474, 862,1026,4253,5178,4007,3839,4576,4008,4577,2265,1952,2477,5179,1125, 817, # 1584 -4254,4255,4009,1513,1766,2041,1487,4256,3050,3291,2837,3840,3152,5180,5181,1507, # 1600 -5182,2692, 733, 40,1632,1106,2879, 345,4257, 841,2531, 230,4578,3002,1847,3292, # 1616 -3475,5183,1263, 986,3476,5184, 735, 879, 254,1137, 857, 622,1300,1180,1388,1562, # 1632 -4010,4011,2954, 967,2761,2665,1349, 592,2134,1692,3361,3003,1995,4258,1679,4012, # 1648 -1902,2188,5185, 739,3708,2724,1296,1290,5186,4259,2201,2202,1922,1563,2605,2559, # 1664 -1871,2762,3004,5187, 435,5188, 343,1108, 596, 17,1751,4579,2239,3477,3709,5189, # 1680 -4580, 294,3582,2955,1693, 477, 979, 281,2042,3583, 643,2043,3710,2631,2795,2266, # 1696 -1031,2340,2135,2303,3584,4581, 367,1249,2560,5190,3585,5191,4582,1283,3362,2005, # 1712 - 240,1762,3363,4583,4584, 836,1069,3153, 474,5192,2149,2532, 268,3586,5193,3219, # 1728 -1521,1284,5194,1658,1546,4260,5195,3587,3588,5196,4261,3364,2693,1685,4262, 961, # 1744 -1673,2632, 190,2006,2203,3841,4585,4586,5197, 570,2504,3711,1490,5198,4587,2633, # 1760 -3293,1957,4588, 584,1514, 396,1045,1945,5199,4589,1968,2449,5200,5201,4590,4013, # 1776 - 619,5202,3154,3294, 215,2007,2796,2561,3220,4591,3221,4592, 763,4263,3842,4593, # 1792 -5203,5204,1958,1767,2956,3365,3712,1174, 452,1477,4594,3366,3155,5205,2838,1253, # 1808 -2387,2189,1091,2290,4264, 492,5206, 638,1169,1825,2136,1752,4014, 648, 926,1021, # 1824 -1324,4595, 520,4596, 997, 847,1007, 892,4597,3843,2267,1872,3713,2405,1785,4598, # 1840 -1953,2957,3103,3222,1728,4265,2044,3714,4599,2008,1701,3156,1551, 30,2268,4266, # 1856 -5207,2027,4600,3589,5208, 501,5209,4267, 594,3478,2166,1822,3590,3479,3591,3223, # 1872 - 829,2839,4268,5210,1680,3157,1225,4269,5211,3295,4601,4270,3158,2341,5212,4602, # 1888 -4271,5213,4015,4016,5214,1848,2388,2606,3367,5215,4603, 374,4017, 652,4272,4273, # 1904 - 375,1140, 798,5216,5217,5218,2366,4604,2269, 546,1659, 138,3051,2450,4605,5219, # 1920 -2254, 612,1849, 910, 796,3844,1740,1371, 825,3845,3846,5220,2920,2562,5221, 692, # 1936 - 444,3052,2634, 801,4606,4274,5222,1491, 244,1053,3053,4275,4276, 340,5223,4018, # 1952 -1041,3005, 293,1168, 87,1357,5224,1539, 959,5225,2240, 721, 694,4277,3847, 219, # 1968 -1478, 644,1417,3368,2666,1413,1401,1335,1389,4019,5226,5227,3006,2367,3159,1826, # 1984 - 730,1515, 184,2840, 66,4607,5228,1660,2958, 246,3369, 378,1457, 226,3480, 975, # 2000 -4020,2959,1264,3592, 674, 696,5229, 163,5230,1141,2422,2167, 713,3593,3370,4608, # 2016 -4021,5231,5232,1186, 15,5233,1079,1070,5234,1522,3224,3594, 276,1050,2725, 758, # 2032 -1126, 653,2960,3296,5235,2342, 889,3595,4022,3104,3007, 903,1250,4609,4023,3481, # 2048 -3596,1342,1681,1718, 766,3297, 286, 89,2961,3715,5236,1713,5237,2607,3371,3008, # 2064 -5238,2962,2219,3225,2880,5239,4610,2505,2533, 181, 387,1075,4024, 731,2190,3372, # 2080 -5240,3298, 310, 313,3482,2304, 770,4278, 54,3054, 189,4611,3105,3848,4025,5241, # 2096 -1230,1617,1850, 355,3597,4279,4612,3373, 111,4280,3716,1350,3160,3483,3055,4281, # 2112 -2150,3299,3598,5242,2797,4026,4027,3009, 722,2009,5243,1071, 247,1207,2343,2478, # 2128 -1378,4613,2010, 864,1437,1214,4614, 373,3849,1142,2220, 667,4615, 442,2763,2563, # 2144 -3850,4028,1969,4282,3300,1840, 837, 170,1107, 934,1336,1883,5244,5245,2119,4283, # 2160 -2841, 743,1569,5246,4616,4284, 582,2389,1418,3484,5247,1803,5248, 357,1395,1729, # 2176 -3717,3301,2423,1564,2241,5249,3106,3851,1633,4617,1114,2086,4285,1532,5250, 482, # 2192 -2451,4618,5251,5252,1492, 833,1466,5253,2726,3599,1641,2842,5254,1526,1272,3718, # 2208 -4286,1686,1795, 416,2564,1903,1954,1804,5255,3852,2798,3853,1159,2321,5256,2881, # 2224 -4619,1610,1584,3056,2424,2764, 443,3302,1163,3161,5257,5258,4029,5259,4287,2506, # 2240 -3057,4620,4030,3162,2104,1647,3600,2011,1873,4288,5260,4289, 431,3485,5261, 250, # 2256 - 97, 81,4290,5262,1648,1851,1558, 160, 848,5263, 866, 740,1694,5264,2204,2843, # 2272 -3226,4291,4621,3719,1687, 950,2479, 426, 469,3227,3720,3721,4031,5265,5266,1188, # 2288 - 424,1996, 861,3601,4292,3854,2205,2694, 168,1235,3602,4293,5267,2087,1674,4622, # 2304 -3374,3303, 220,2565,1009,5268,3855, 670,3010, 332,1208, 717,5269,5270,3603,2452, # 2320 -4032,3375,5271, 513,5272,1209,2882,3376,3163,4623,1080,5273,5274,5275,5276,2534, # 2336 -3722,3604, 815,1587,4033,4034,5277,3605,3486,3856,1254,4624,1328,3058,1390,4035, # 2352 -1741,4036,3857,4037,5278, 236,3858,2453,3304,5279,5280,3723,3859,1273,3860,4625, # 2368 -5281, 308,5282,4626, 245,4627,1852,2480,1307,2583, 430, 715,2137,2454,5283, 270, # 2384 - 199,2883,4038,5284,3606,2727,1753, 761,1754, 725,1661,1841,4628,3487,3724,5285, # 2400 -5286, 587, 14,3305, 227,2608, 326, 480,2270, 943,2765,3607, 291, 650,1884,5287, # 2416 -1702,1226, 102,1547, 62,3488, 904,4629,3489,1164,4294,5288,5289,1224,1548,2766, # 2432 - 391, 498,1493,5290,1386,1419,5291,2056,1177,4630, 813, 880,1081,2368, 566,1145, # 2448 -4631,2291,1001,1035,2566,2609,2242, 394,1286,5292,5293,2069,5294, 86,1494,1730, # 2464 -4039, 491,1588, 745, 897,2963, 843,3377,4040,2767,2884,3306,1768, 998,2221,2070, # 2480 - 397,1827,1195,1970,3725,3011,3378, 284,5295,3861,2507,2138,2120,1904,5296,4041, # 2496 -2151,4042,4295,1036,3490,1905, 114,2567,4296, 209,1527,5297,5298,2964,2844,2635, # 2512 -2390,2728,3164, 812,2568,5299,3307,5300,1559, 737,1885,3726,1210, 885, 28,2695, # 2528 -3608,3862,5301,4297,1004,1780,4632,5302, 346,1982,2222,2696,4633,3863,1742, 797, # 2544 -1642,4043,1934,1072,1384,2152, 896,4044,3308,3727,3228,2885,3609,5303,2569,1959, # 2560 -4634,2455,1786,5304,5305,5306,4045,4298,1005,1308,3728,4299,2729,4635,4636,1528, # 2576 -2610, 161,1178,4300,1983, 987,4637,1101,4301, 631,4046,1157,3229,2425,1343,1241, # 2592 -1016,2243,2570, 372, 877,2344,2508,1160, 555,1935, 911,4047,5307, 466,1170, 169, # 2608 -1051,2921,2697,3729,2481,3012,1182,2012,2571,1251,2636,5308, 992,2345,3491,1540, # 2624 -2730,1201,2071,2406,1997,2482,5309,4638, 528,1923,2191,1503,1874,1570,2369,3379, # 2640 -3309,5310, 557,1073,5311,1828,3492,2088,2271,3165,3059,3107, 767,3108,2799,4639, # 2656 -1006,4302,4640,2346,1267,2179,3730,3230, 778,4048,3231,2731,1597,2667,5312,4641, # 2672 -5313,3493,5314,5315,5316,3310,2698,1433,3311, 131, 95,1504,4049, 723,4303,3166, # 2688 -1842,3610,2768,2192,4050,2028,2105,3731,5317,3013,4051,1218,5318,3380,3232,4052, # 2704 -4304,2584, 248,1634,3864, 912,5319,2845,3732,3060,3865, 654, 53,5320,3014,5321, # 2720 -1688,4642, 777,3494,1032,4053,1425,5322, 191, 820,2121,2846, 971,4643, 931,3233, # 2736 - 135, 664, 783,3866,1998, 772,2922,1936,4054,3867,4644,2923,3234, 282,2732, 640, # 2752 -1372,3495,1127, 922, 325,3381,5323,5324, 711,2045,5325,5326,4055,2223,2800,1937, # 2768 -4056,3382,2224,2255,3868,2305,5327,4645,3869,1258,3312,4057,3235,2139,2965,4058, # 2784 -4059,5328,2225, 258,3236,4646, 101,1227,5329,3313,1755,5330,1391,3314,5331,2924, # 2800 -2057, 893,5332,5333,5334,1402,4305,2347,5335,5336,3237,3611,5337,5338, 878,1325, # 2816 -1781,2801,4647, 259,1385,2585, 744,1183,2272,4648,5339,4060,2509,5340, 684,1024, # 2832 -4306,5341, 472,3612,3496,1165,3315,4061,4062, 322,2153, 881, 455,1695,1152,1340, # 2848 - 660, 554,2154,4649,1058,4650,4307, 830,1065,3383,4063,4651,1924,5342,1703,1919, # 2864 -5343, 932,2273, 122,5344,4652, 947, 677,5345,3870,2637, 297,1906,1925,2274,4653, # 2880 -2322,3316,5346,5347,4308,5348,4309, 84,4310, 112, 989,5349, 547,1059,4064, 701, # 2896 -3613,1019,5350,4311,5351,3497, 942, 639, 457,2306,2456, 993,2966, 407, 851, 494, # 2912 -4654,3384, 927,5352,1237,5353,2426,3385, 573,4312, 680, 921,2925,1279,1875, 285, # 2928 - 790,1448,1984, 719,2168,5354,5355,4655,4065,4066,1649,5356,1541, 563,5357,1077, # 2944 -5358,3386,3061,3498, 511,3015,4067,4068,3733,4069,1268,2572,3387,3238,4656,4657, # 2960 -5359, 535,1048,1276,1189,2926,2029,3167,1438,1373,2847,2967,1134,2013,5360,4313, # 2976 -1238,2586,3109,1259,5361, 700,5362,2968,3168,3734,4314,5363,4315,1146,1876,1907, # 2992 -4658,2611,4070, 781,2427, 132,1589, 203, 147, 273,2802,2407, 898,1787,2155,4071, # 3008 -4072,5364,3871,2803,5365,5366,4659,4660,5367,3239,5368,1635,3872, 965,5369,1805, # 3024 -2699,1516,3614,1121,1082,1329,3317,4073,1449,3873, 65,1128,2848,2927,2769,1590, # 3040 -3874,5370,5371, 12,2668, 45, 976,2587,3169,4661, 517,2535,1013,1037,3240,5372, # 3056 -3875,2849,5373,3876,5374,3499,5375,2612, 614,1999,2323,3877,3110,2733,2638,5376, # 3072 -2588,4316, 599,1269,5377,1811,3735,5378,2700,3111, 759,1060, 489,1806,3388,3318, # 3088 -1358,5379,5380,2391,1387,1215,2639,2256, 490,5381,5382,4317,1759,2392,2348,5383, # 3104 -4662,3878,1908,4074,2640,1807,3241,4663,3500,3319,2770,2349, 874,5384,5385,3501, # 3120 -3736,1859, 91,2928,3737,3062,3879,4664,5386,3170,4075,2669,5387,3502,1202,1403, # 3136 -3880,2969,2536,1517,2510,4665,3503,2511,5388,4666,5389,2701,1886,1495,1731,4076, # 3152 -2370,4667,5390,2030,5391,5392,4077,2702,1216, 237,2589,4318,2324,4078,3881,4668, # 3168 -4669,2703,3615,3504, 445,4670,5393,5394,5395,5396,2771, 61,4079,3738,1823,4080, # 3184 -5397, 687,2046, 935, 925, 405,2670, 703,1096,1860,2734,4671,4081,1877,1367,2704, # 3200 -3389, 918,2106,1782,2483, 334,3320,1611,1093,4672, 564,3171,3505,3739,3390, 945, # 3216 -2641,2058,4673,5398,1926, 872,4319,5399,3506,2705,3112, 349,4320,3740,4082,4674, # 3232 -3882,4321,3741,2156,4083,4675,4676,4322,4677,2408,2047, 782,4084, 400, 251,4323, # 3248 -1624,5400,5401, 277,3742, 299,1265, 476,1191,3883,2122,4324,4325,1109, 205,5402, # 3264 -2590,1000,2157,3616,1861,5403,5404,5405,4678,5406,4679,2573, 107,2484,2158,4085, # 3280 -3507,3172,5407,1533, 541,1301, 158, 753,4326,2886,3617,5408,1696, 370,1088,4327, # 3296 -4680,3618, 579, 327, 440, 162,2244, 269,1938,1374,3508, 968,3063, 56,1396,3113, # 3312 -2107,3321,3391,5409,1927,2159,4681,3016,5410,3619,5411,5412,3743,4682,2485,5413, # 3328 -2804,5414,1650,4683,5415,2613,5416,5417,4086,2671,3392,1149,3393,4087,3884,4088, # 3344 -5418,1076, 49,5419, 951,3242,3322,3323, 450,2850, 920,5420,1812,2805,2371,4328, # 3360 -1909,1138,2372,3885,3509,5421,3243,4684,1910,1147,1518,2428,4685,3886,5422,4686, # 3376 -2393,2614, 260,1796,3244,5423,5424,3887,3324, 708,5425,3620,1704,5426,3621,1351, # 3392 -1618,3394,3017,1887, 944,4329,3395,4330,3064,3396,4331,5427,3744, 422, 413,1714, # 3408 -3325, 500,2059,2350,4332,2486,5428,1344,1911, 954,5429,1668,5430,5431,4089,2409, # 3424 -4333,3622,3888,4334,5432,2307,1318,2512,3114, 133,3115,2887,4687, 629, 31,2851, # 3440 -2706,3889,4688, 850, 949,4689,4090,2970,1732,2089,4335,1496,1853,5433,4091, 620, # 3456 -3245, 981,1242,3745,3397,1619,3746,1643,3326,2140,2457,1971,1719,3510,2169,5434, # 3472 -3246,5435,5436,3398,1829,5437,1277,4690,1565,2048,5438,1636,3623,3116,5439, 869, # 3488 -2852, 655,3890,3891,3117,4092,3018,3892,1310,3624,4691,5440,5441,5442,1733, 558, # 3504 -4692,3747, 335,1549,3065,1756,4336,3748,1946,3511,1830,1291,1192, 470,2735,2108, # 3520 -2806, 913,1054,4093,5443,1027,5444,3066,4094,4693, 982,2672,3399,3173,3512,3247, # 3536 -3248,1947,2807,5445, 571,4694,5446,1831,5447,3625,2591,1523,2429,5448,2090, 984, # 3552 -4695,3749,1960,5449,3750, 852, 923,2808,3513,3751, 969,1519, 999,2049,2325,1705, # 3568 -5450,3118, 615,1662, 151, 597,4095,2410,2326,1049, 275,4696,3752,4337, 568,3753, # 3584 -3626,2487,4338,3754,5451,2430,2275, 409,3249,5452,1566,2888,3514,1002, 769,2853, # 3600 - 194,2091,3174,3755,2226,3327,4339, 628,1505,5453,5454,1763,2180,3019,4096, 521, # 3616 -1161,2592,1788,2206,2411,4697,4097,1625,4340,4341, 412, 42,3119, 464,5455,2642, # 3632 -4698,3400,1760,1571,2889,3515,2537,1219,2207,3893,2643,2141,2373,4699,4700,3328, # 3648 -1651,3401,3627,5456,5457,3628,2488,3516,5458,3756,5459,5460,2276,2092, 460,5461, # 3664 -4701,5462,3020, 962, 588,3629, 289,3250,2644,1116, 52,5463,3067,1797,5464,5465, # 3680 -5466,1467,5467,1598,1143,3757,4342,1985,1734,1067,4702,1280,3402, 465,4703,1572, # 3696 - 510,5468,1928,2245,1813,1644,3630,5469,4704,3758,5470,5471,2673,1573,1534,5472, # 3712 -5473, 536,1808,1761,3517,3894,3175,2645,5474,5475,5476,4705,3518,2929,1912,2809, # 3728 -5477,3329,1122, 377,3251,5478, 360,5479,5480,4343,1529, 551,5481,2060,3759,1769, # 3744 -2431,5482,2930,4344,3330,3120,2327,2109,2031,4706,1404, 136,1468,1479, 672,1171, # 3760 -3252,2308, 271,3176,5483,2772,5484,2050, 678,2736, 865,1948,4707,5485,2014,4098, # 3776 -2971,5486,2737,2227,1397,3068,3760,4708,4709,1735,2931,3403,3631,5487,3895, 509, # 3792 -2854,2458,2890,3896,5488,5489,3177,3178,4710,4345,2538,4711,2309,1166,1010, 552, # 3808 - 681,1888,5490,5491,2972,2973,4099,1287,1596,1862,3179, 358, 453, 736, 175, 478, # 3824 -1117, 905,1167,1097,5492,1854,1530,5493,1706,5494,2181,3519,2292,3761,3520,3632, # 3840 -4346,2093,4347,5495,3404,1193,2489,4348,1458,2193,2208,1863,1889,1421,3331,2932, # 3856 -3069,2182,3521, 595,2123,5496,4100,5497,5498,4349,1707,2646, 223,3762,1359, 751, # 3872 -3121, 183,3522,5499,2810,3021, 419,2374, 633, 704,3897,2394, 241,5500,5501,5502, # 3888 - 838,3022,3763,2277,2773,2459,3898,1939,2051,4101,1309,3122,2246,1181,5503,1136, # 3904 -2209,3899,2375,1446,4350,2310,4712,5504,5505,4351,1055,2615, 484,3764,5506,4102, # 3920 - 625,4352,2278,3405,1499,4353,4103,5507,4104,4354,3253,2279,2280,3523,5508,5509, # 3936 -2774, 808,2616,3765,3406,4105,4355,3123,2539, 526,3407,3900,4356, 955,5510,1620, # 3952 -4357,2647,2432,5511,1429,3766,1669,1832, 994, 928,5512,3633,1260,5513,5514,5515, # 3968 -1949,2293, 741,2933,1626,4358,2738,2460, 867,1184, 362,3408,1392,5516,5517,4106, # 3984 -4359,1770,1736,3254,2934,4713,4714,1929,2707,1459,1158,5518,3070,3409,2891,1292, # 4000 -1930,2513,2855,3767,1986,1187,2072,2015,2617,4360,5519,2574,2514,2170,3768,2490, # 4016 -3332,5520,3769,4715,5521,5522, 666,1003,3023,1022,3634,4361,5523,4716,1814,2257, # 4032 - 574,3901,1603, 295,1535, 705,3902,4362, 283, 858, 417,5524,5525,3255,4717,4718, # 4048 -3071,1220,1890,1046,2281,2461,4107,1393,1599, 689,2575, 388,4363,5526,2491, 802, # 4064 -5527,2811,3903,2061,1405,2258,5528,4719,3904,2110,1052,1345,3256,1585,5529, 809, # 4080 -5530,5531,5532, 575,2739,3524, 956,1552,1469,1144,2328,5533,2329,1560,2462,3635, # 4096 -3257,4108, 616,2210,4364,3180,2183,2294,5534,1833,5535,3525,4720,5536,1319,3770, # 4112 -3771,1211,3636,1023,3258,1293,2812,5537,5538,5539,3905, 607,2311,3906, 762,2892, # 4128 -1439,4365,1360,4721,1485,3072,5540,4722,1038,4366,1450,2062,2648,4367,1379,4723, # 4144 -2593,5541,5542,4368,1352,1414,2330,2935,1172,5543,5544,3907,3908,4724,1798,1451, # 4160 -5545,5546,5547,5548,2936,4109,4110,2492,2351, 411,4111,4112,3637,3333,3124,4725, # 4176 -1561,2674,1452,4113,1375,5549,5550, 47,2974, 316,5551,1406,1591,2937,3181,5552, # 4192 -1025,2142,3125,3182, 354,2740, 884,2228,4369,2412, 508,3772, 726,3638, 996,2433, # 4208 -3639, 729,5553, 392,2194,1453,4114,4726,3773,5554,5555,2463,3640,2618,1675,2813, # 4224 - 919,2352,2975,2353,1270,4727,4115, 73,5556,5557, 647,5558,3259,2856,2259,1550, # 4240 -1346,3024,5559,1332, 883,3526,5560,5561,5562,5563,3334,2775,5564,1212, 831,1347, # 4256 -4370,4728,2331,3909,1864,3073, 720,3910,4729,4730,3911,5565,4371,5566,5567,4731, # 4272 -5568,5569,1799,4732,3774,2619,4733,3641,1645,2376,4734,5570,2938, 669,2211,2675, # 4288 -2434,5571,2893,5572,5573,1028,3260,5574,4372,2413,5575,2260,1353,5576,5577,4735, # 4304 -3183, 518,5578,4116,5579,4373,1961,5580,2143,4374,5581,5582,3025,2354,2355,3912, # 4320 - 516,1834,1454,4117,2708,4375,4736,2229,2620,1972,1129,3642,5583,2776,5584,2976, # 4336 -1422, 577,1470,3026,1524,3410,5585,5586, 432,4376,3074,3527,5587,2594,1455,2515, # 4352 -2230,1973,1175,5588,1020,2741,4118,3528,4737,5589,2742,5590,1743,1361,3075,3529, # 4368 -2649,4119,4377,4738,2295, 895, 924,4378,2171, 331,2247,3076, 166,1627,3077,1098, # 4384 -5591,1232,2894,2231,3411,4739, 657, 403,1196,2377, 542,3775,3412,1600,4379,3530, # 4400 -5592,4740,2777,3261, 576, 530,1362,4741,4742,2540,2676,3776,4120,5593, 842,3913, # 4416 -5594,2814,2032,1014,4121, 213,2709,3413, 665, 621,4380,5595,3777,2939,2435,5596, # 4432 -2436,3335,3643,3414,4743,4381,2541,4382,4744,3644,1682,4383,3531,1380,5597, 724, # 4448 -2282, 600,1670,5598,1337,1233,4745,3126,2248,5599,1621,4746,5600, 651,4384,5601, # 4464 -1612,4385,2621,5602,2857,5603,2743,2312,3078,5604, 716,2464,3079, 174,1255,2710, # 4480 -4122,3645, 548,1320,1398, 728,4123,1574,5605,1891,1197,3080,4124,5606,3081,3082, # 4496 -3778,3646,3779, 747,5607, 635,4386,4747,5608,5609,5610,4387,5611,5612,4748,5613, # 4512 -3415,4749,2437, 451,5614,3780,2542,2073,4388,2744,4389,4125,5615,1764,4750,5616, # 4528 -4390, 350,4751,2283,2395,2493,5617,4391,4126,2249,1434,4127, 488,4752, 458,4392, # 4544 -4128,3781, 771,1330,2396,3914,2576,3184,2160,2414,1553,2677,3185,4393,5618,2494, # 4560 -2895,2622,1720,2711,4394,3416,4753,5619,2543,4395,5620,3262,4396,2778,5621,2016, # 4576 -2745,5622,1155,1017,3782,3915,5623,3336,2313, 201,1865,4397,1430,5624,4129,5625, # 4592 -5626,5627,5628,5629,4398,1604,5630, 414,1866, 371,2595,4754,4755,3532,2017,3127, # 4608 -4756,1708, 960,4399, 887, 389,2172,1536,1663,1721,5631,2232,4130,2356,2940,1580, # 4624 -5632,5633,1744,4757,2544,4758,4759,5634,4760,5635,2074,5636,4761,3647,3417,2896, # 4640 -4400,5637,4401,2650,3418,2815, 673,2712,2465, 709,3533,4131,3648,4402,5638,1148, # 4656 - 502, 634,5639,5640,1204,4762,3649,1575,4763,2623,3783,5641,3784,3128, 948,3263, # 4672 - 121,1745,3916,1110,5642,4403,3083,2516,3027,4132,3785,1151,1771,3917,1488,4133, # 4688 -1987,5643,2438,3534,5644,5645,2094,5646,4404,3918,1213,1407,2816, 531,2746,2545, # 4704 -3264,1011,1537,4764,2779,4405,3129,1061,5647,3786,3787,1867,2897,5648,2018, 120, # 4720 -4406,4407,2063,3650,3265,2314,3919,2678,3419,1955,4765,4134,5649,3535,1047,2713, # 4736 -1266,5650,1368,4766,2858, 649,3420,3920,2546,2747,1102,2859,2679,5651,5652,2000, # 4752 -5653,1111,3651,2977,5654,2495,3921,3652,2817,1855,3421,3788,5655,5656,3422,2415, # 4768 -2898,3337,3266,3653,5657,2577,5658,3654,2818,4135,1460, 856,5659,3655,5660,2899, # 4784 -2978,5661,2900,3922,5662,4408, 632,2517, 875,3923,1697,3924,2296,5663,5664,4767, # 4800 -3028,1239, 580,4768,4409,5665, 914, 936,2075,1190,4136,1039,2124,5666,5667,5668, # 4816 -5669,3423,1473,5670,1354,4410,3925,4769,2173,3084,4137, 915,3338,4411,4412,3339, # 4832 -1605,1835,5671,2748, 398,3656,4413,3926,4138, 328,1913,2860,4139,3927,1331,4414, # 4848 -3029, 937,4415,5672,3657,4140,4141,3424,2161,4770,3425, 524, 742, 538,3085,1012, # 4864 -5673,5674,3928,2466,5675, 658,1103, 225,3929,5676,5677,4771,5678,4772,5679,3267, # 4880 -1243,5680,4142, 963,2250,4773,5681,2714,3658,3186,5682,5683,2596,2332,5684,4774, # 4896 -5685,5686,5687,3536, 957,3426,2547,2033,1931,2941,2467, 870,2019,3659,1746,2780, # 4912 -2781,2439,2468,5688,3930,5689,3789,3130,3790,3537,3427,3791,5690,1179,3086,5691, # 4928 -3187,2378,4416,3792,2548,3188,3131,2749,4143,5692,3428,1556,2549,2297, 977,2901, # 4944 -2034,4144,1205,3429,5693,1765,3430,3189,2125,1271, 714,1689,4775,3538,5694,2333, # 4960 -3931, 533,4417,3660,2184, 617,5695,2469,3340,3539,2315,5696,5697,3190,5698,5699, # 4976 -3932,1988, 618, 427,2651,3540,3431,5700,5701,1244,1690,5702,2819,4418,4776,5703, # 4992 -3541,4777,5704,2284,1576, 473,3661,4419,3432, 972,5705,3662,5706,3087,5707,5708, # 5008 -4778,4779,5709,3793,4145,4146,5710, 153,4780, 356,5711,1892,2902,4420,2144, 408, # 5024 - 803,2357,5712,3933,5713,4421,1646,2578,2518,4781,4782,3934,5714,3935,4422,5715, # 5040 -2416,3433, 752,5716,5717,1962,3341,2979,5718, 746,3030,2470,4783,4423,3794, 698, # 5056 -4784,1893,4424,3663,2550,4785,3664,3936,5719,3191,3434,5720,1824,1302,4147,2715, # 5072 -3937,1974,4425,5721,4426,3192, 823,1303,1288,1236,2861,3542,4148,3435, 774,3938, # 5088 -5722,1581,4786,1304,2862,3939,4787,5723,2440,2162,1083,3268,4427,4149,4428, 344, # 5104 -1173, 288,2316, 454,1683,5724,5725,1461,4788,4150,2597,5726,5727,4789, 985, 894, # 5120 -5728,3436,3193,5729,1914,2942,3795,1989,5730,2111,1975,5731,4151,5732,2579,1194, # 5136 - 425,5733,4790,3194,1245,3796,4429,5734,5735,2863,5736, 636,4791,1856,3940, 760, # 5152 -1800,5737,4430,2212,1508,4792,4152,1894,1684,2298,5738,5739,4793,4431,4432,2213, # 5168 - 479,5740,5741, 832,5742,4153,2496,5743,2980,2497,3797, 990,3132, 627,1815,2652, # 5184 -4433,1582,4434,2126,2112,3543,4794,5744, 799,4435,3195,5745,4795,2113,1737,3031, # 5200 -1018, 543, 754,4436,3342,1676,4796,4797,4154,4798,1489,5746,3544,5747,2624,2903, # 5216 -4155,5748,5749,2981,5750,5751,5752,5753,3196,4799,4800,2185,1722,5754,3269,3270, # 5232 -1843,3665,1715, 481, 365,1976,1857,5755,5756,1963,2498,4801,5757,2127,3666,3271, # 5248 - 433,1895,2064,2076,5758, 602,2750,5759,5760,5761,5762,5763,3032,1628,3437,5764, # 5264 -3197,4802,4156,2904,4803,2519,5765,2551,2782,5766,5767,5768,3343,4804,2905,5769, # 5280 -4805,5770,2864,4806,4807,1221,2982,4157,2520,5771,5772,5773,1868,1990,5774,5775, # 5296 -5776,1896,5777,5778,4808,1897,4158, 318,5779,2095,4159,4437,5780,5781, 485,5782, # 5312 - 938,3941, 553,2680, 116,5783,3942,3667,5784,3545,2681,2783,3438,3344,2820,5785, # 5328 -3668,2943,4160,1747,2944,2983,5786,5787, 207,5788,4809,5789,4810,2521,5790,3033, # 5344 - 890,3669,3943,5791,1878,3798,3439,5792,2186,2358,3440,1652,5793,5794,5795, 941, # 5360 -2299, 208,3546,4161,2020, 330,4438,3944,2906,2499,3799,4439,4811,5796,5797,5798, # 5376 -) - diff --git a/pipenv/vendor/chardet/big5prober.py b/pipenv/vendor/chardet/big5prober.py deleted file mode 100644 index 98f99701..00000000 --- a/pipenv/vendor/chardet/big5prober.py +++ /dev/null @@ -1,47 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .mbcharsetprober import MultiByteCharSetProber -from .codingstatemachine import CodingStateMachine -from .chardistribution import Big5DistributionAnalysis -from .mbcssm import BIG5_SM_MODEL - - -class Big5Prober(MultiByteCharSetProber): - def __init__(self): - super(Big5Prober, self).__init__() - self.coding_sm = CodingStateMachine(BIG5_SM_MODEL) - self.distribution_analyzer = Big5DistributionAnalysis() - self.reset() - - @property - def charset_name(self): - return "Big5" - - @property - def language(self): - return "Chinese" diff --git a/pipenv/vendor/chardet/chardistribution.py b/pipenv/vendor/chardet/chardistribution.py deleted file mode 100644 index c0395f4a..00000000 --- a/pipenv/vendor/chardet/chardistribution.py +++ /dev/null @@ -1,233 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .euctwfreq import (EUCTW_CHAR_TO_FREQ_ORDER, EUCTW_TABLE_SIZE, - EUCTW_TYPICAL_DISTRIBUTION_RATIO) -from .euckrfreq import (EUCKR_CHAR_TO_FREQ_ORDER, EUCKR_TABLE_SIZE, - EUCKR_TYPICAL_DISTRIBUTION_RATIO) -from .gb2312freq import (GB2312_CHAR_TO_FREQ_ORDER, GB2312_TABLE_SIZE, - GB2312_TYPICAL_DISTRIBUTION_RATIO) -from .big5freq import (BIG5_CHAR_TO_FREQ_ORDER, BIG5_TABLE_SIZE, - BIG5_TYPICAL_DISTRIBUTION_RATIO) -from .jisfreq import (JIS_CHAR_TO_FREQ_ORDER, JIS_TABLE_SIZE, - JIS_TYPICAL_DISTRIBUTION_RATIO) - - -class CharDistributionAnalysis(object): - ENOUGH_DATA_THRESHOLD = 1024 - SURE_YES = 0.99 - SURE_NO = 0.01 - MINIMUM_DATA_THRESHOLD = 3 - - def __init__(self): - # Mapping table to get frequency order from char order (get from - # GetOrder()) - self._char_to_freq_order = None - self._table_size = None # Size of above table - # This is a constant value which varies from language to language, - # used in calculating confidence. See - # http://www.mozilla.org/projects/intl/UniversalCharsetDetection.html - # for further detail. - self.typical_distribution_ratio = None - self._done = None - self._total_chars = None - self._freq_chars = None - self.reset() - - def reset(self): - """reset analyser, clear any state""" - # If this flag is set to True, detection is done and conclusion has - # been made - self._done = False - self._total_chars = 0 # Total characters encountered - # The number of characters whose frequency order is less than 512 - self._freq_chars = 0 - - def feed(self, char, char_len): - """feed a character with known length""" - if char_len == 2: - # we only care about 2-bytes character in our distribution analysis - order = self.get_order(char) - else: - order = -1 - if order >= 0: - self._total_chars += 1 - # order is valid - if order < self._table_size: - if 512 > self._char_to_freq_order[order]: - self._freq_chars += 1 - - def get_confidence(self): - """return confidence based on existing data""" - # if we didn't receive any character in our consideration range, - # return negative answer - if self._total_chars <= 0 or self._freq_chars <= self.MINIMUM_DATA_THRESHOLD: - return self.SURE_NO - - if self._total_chars != self._freq_chars: - r = (self._freq_chars / ((self._total_chars - self._freq_chars) - * self.typical_distribution_ratio)) - if r < self.SURE_YES: - return r - - # normalize confidence (we don't want to be 100% sure) - return self.SURE_YES - - def got_enough_data(self): - # It is not necessary to receive all data to draw conclusion. - # For charset detection, certain amount of data is enough - return self._total_chars > self.ENOUGH_DATA_THRESHOLD - - def get_order(self, byte_str): - # We do not handle characters based on the original encoding string, - # but convert this encoding string to a number, here called order. - # This allows multiple encodings of a language to share one frequency - # table. - return -1 - - -class EUCTWDistributionAnalysis(CharDistributionAnalysis): - def __init__(self): - super(EUCTWDistributionAnalysis, self).__init__() - self._char_to_freq_order = EUCTW_CHAR_TO_FREQ_ORDER - self._table_size = EUCTW_TABLE_SIZE - self.typical_distribution_ratio = EUCTW_TYPICAL_DISTRIBUTION_RATIO - - def get_order(self, byte_str): - # for euc-TW encoding, we are interested - # first byte range: 0xc4 -- 0xfe - # second byte range: 0xa1 -- 0xfe - # no validation needed here. State machine has done that - first_char = byte_str[0] - if first_char >= 0xC4: - return 94 * (first_char - 0xC4) + byte_str[1] - 0xA1 - else: - return -1 - - -class EUCKRDistributionAnalysis(CharDistributionAnalysis): - def __init__(self): - super(EUCKRDistributionAnalysis, self).__init__() - self._char_to_freq_order = EUCKR_CHAR_TO_FREQ_ORDER - self._table_size = EUCKR_TABLE_SIZE - self.typical_distribution_ratio = EUCKR_TYPICAL_DISTRIBUTION_RATIO - - def get_order(self, byte_str): - # for euc-KR encoding, we are interested - # first byte range: 0xb0 -- 0xfe - # second byte range: 0xa1 -- 0xfe - # no validation needed here. State machine has done that - first_char = byte_str[0] - if first_char >= 0xB0: - return 94 * (first_char - 0xB0) + byte_str[1] - 0xA1 - else: - return -1 - - -class GB2312DistributionAnalysis(CharDistributionAnalysis): - def __init__(self): - super(GB2312DistributionAnalysis, self).__init__() - self._char_to_freq_order = GB2312_CHAR_TO_FREQ_ORDER - self._table_size = GB2312_TABLE_SIZE - self.typical_distribution_ratio = GB2312_TYPICAL_DISTRIBUTION_RATIO - - def get_order(self, byte_str): - # for GB2312 encoding, we are interested - # first byte range: 0xb0 -- 0xfe - # second byte range: 0xa1 -- 0xfe - # no validation needed here. State machine has done that - first_char, second_char = byte_str[0], byte_str[1] - if (first_char >= 0xB0) and (second_char >= 0xA1): - return 94 * (first_char - 0xB0) + second_char - 0xA1 - else: - return -1 - - -class Big5DistributionAnalysis(CharDistributionAnalysis): - def __init__(self): - super(Big5DistributionAnalysis, self).__init__() - self._char_to_freq_order = BIG5_CHAR_TO_FREQ_ORDER - self._table_size = BIG5_TABLE_SIZE - self.typical_distribution_ratio = BIG5_TYPICAL_DISTRIBUTION_RATIO - - def get_order(self, byte_str): - # for big5 encoding, we are interested - # first byte range: 0xa4 -- 0xfe - # second byte range: 0x40 -- 0x7e , 0xa1 -- 0xfe - # no validation needed here. State machine has done that - first_char, second_char = byte_str[0], byte_str[1] - if first_char >= 0xA4: - if second_char >= 0xA1: - return 157 * (first_char - 0xA4) + second_char - 0xA1 + 63 - else: - return 157 * (first_char - 0xA4) + second_char - 0x40 - else: - return -1 - - -class SJISDistributionAnalysis(CharDistributionAnalysis): - def __init__(self): - super(SJISDistributionAnalysis, self).__init__() - self._char_to_freq_order = JIS_CHAR_TO_FREQ_ORDER - self._table_size = JIS_TABLE_SIZE - self.typical_distribution_ratio = JIS_TYPICAL_DISTRIBUTION_RATIO - - def get_order(self, byte_str): - # for sjis encoding, we are interested - # first byte range: 0x81 -- 0x9f , 0xe0 -- 0xfe - # second byte range: 0x40 -- 0x7e, 0x81 -- oxfe - # no validation needed here. State machine has done that - first_char, second_char = byte_str[0], byte_str[1] - if (first_char >= 0x81) and (first_char <= 0x9F): - order = 188 * (first_char - 0x81) - elif (first_char >= 0xE0) and (first_char <= 0xEF): - order = 188 * (first_char - 0xE0 + 31) - else: - return -1 - order = order + second_char - 0x40 - if second_char > 0x7F: - order = -1 - return order - - -class EUCJPDistributionAnalysis(CharDistributionAnalysis): - def __init__(self): - super(EUCJPDistributionAnalysis, self).__init__() - self._char_to_freq_order = JIS_CHAR_TO_FREQ_ORDER - self._table_size = JIS_TABLE_SIZE - self.typical_distribution_ratio = JIS_TYPICAL_DISTRIBUTION_RATIO - - def get_order(self, byte_str): - # for euc-JP encoding, we are interested - # first byte range: 0xa0 -- 0xfe - # second byte range: 0xa1 -- 0xfe - # no validation needed here. State machine has done that - char = byte_str[0] - if char >= 0xA0: - return 94 * (char - 0xA1) + byte_str[1] - 0xa1 - else: - return -1 diff --git a/pipenv/vendor/chardet/charsetgroupprober.py b/pipenv/vendor/chardet/charsetgroupprober.py deleted file mode 100644 index 8b3738ef..00000000 --- a/pipenv/vendor/chardet/charsetgroupprober.py +++ /dev/null @@ -1,106 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .enums import ProbingState -from .charsetprober import CharSetProber - - -class CharSetGroupProber(CharSetProber): - def __init__(self, lang_filter=None): - super(CharSetGroupProber, self).__init__(lang_filter=lang_filter) - self._active_num = 0 - self.probers = [] - self._best_guess_prober = None - - def reset(self): - super(CharSetGroupProber, self).reset() - self._active_num = 0 - for prober in self.probers: - if prober: - prober.reset() - prober.active = True - self._active_num += 1 - self._best_guess_prober = None - - @property - def charset_name(self): - if not self._best_guess_prober: - self.get_confidence() - if not self._best_guess_prober: - return None - return self._best_guess_prober.charset_name - - @property - def language(self): - if not self._best_guess_prober: - self.get_confidence() - if not self._best_guess_prober: - return None - return self._best_guess_prober.language - - def feed(self, byte_str): - for prober in self.probers: - if not prober: - continue - if not prober.active: - continue - state = prober.feed(byte_str) - if not state: - continue - if state == ProbingState.FOUND_IT: - self._best_guess_prober = prober - return self.state - elif state == ProbingState.NOT_ME: - prober.active = False - self._active_num -= 1 - if self._active_num <= 0: - self._state = ProbingState.NOT_ME - return self.state - return self.state - - def get_confidence(self): - state = self.state - if state == ProbingState.FOUND_IT: - return 0.99 - elif state == ProbingState.NOT_ME: - return 0.01 - best_conf = 0.0 - self._best_guess_prober = None - for prober in self.probers: - if not prober: - continue - if not prober.active: - self.logger.debug('%s not active', prober.charset_name) - continue - conf = prober.get_confidence() - self.logger.debug('%s %s confidence = %s', prober.charset_name, prober.language, conf) - if best_conf < conf: - best_conf = conf - self._best_guess_prober = prober - if not self._best_guess_prober: - return 0.0 - return best_conf diff --git a/pipenv/vendor/chardet/charsetprober.py b/pipenv/vendor/chardet/charsetprober.py deleted file mode 100644 index eac4e598..00000000 --- a/pipenv/vendor/chardet/charsetprober.py +++ /dev/null @@ -1,145 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Universal charset detector code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 2001 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# Shy Shalom - original C code -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -import logging -import re - -from .enums import ProbingState - - -class CharSetProber(object): - - SHORTCUT_THRESHOLD = 0.95 - - def __init__(self, lang_filter=None): - self._state = None - self.lang_filter = lang_filter - self.logger = logging.getLogger(__name__) - - def reset(self): - self._state = ProbingState.DETECTING - - @property - def charset_name(self): - return None - - def feed(self, buf): - pass - - @property - def state(self): - return self._state - - def get_confidence(self): - return 0.0 - - @staticmethod - def filter_high_byte_only(buf): - buf = re.sub(b'([\x00-\x7F])+', b' ', buf) - return buf - - @staticmethod - def filter_international_words(buf): - """ - We define three types of bytes: - alphabet: english alphabets [a-zA-Z] - international: international characters [\x80-\xFF] - marker: everything else [^a-zA-Z\x80-\xFF] - - The input buffer can be thought to contain a series of words delimited - by markers. This function works to filter all words that contain at - least one international character. All contiguous sequences of markers - are replaced by a single space ascii character. - - This filter applies to all scripts which do not use English characters. - """ - filtered = bytearray() - - # This regex expression filters out only words that have at-least one - # international character. The word may include one marker character at - # the end. - words = re.findall(b'[a-zA-Z]*[\x80-\xFF]+[a-zA-Z]*[^a-zA-Z\x80-\xFF]?', - buf) - - for word in words: - filtered.extend(word[:-1]) - - # If the last character in the word is a marker, replace it with a - # space as markers shouldn't affect our analysis (they are used - # similarly across all languages and may thus have similar - # frequencies). - last_char = word[-1:] - if not last_char.isalpha() and last_char < b'\x80': - last_char = b' ' - filtered.extend(last_char) - - return filtered - - @staticmethod - def filter_with_english_letters(buf): - """ - Returns a copy of ``buf`` that retains only the sequences of English - alphabet and high byte characters that are not between <> characters. - Also retains English alphabet and high byte characters immediately - before occurrences of >. - - This filter can be applied to all scripts which contain both English - characters and extended ASCII characters, but is currently only used by - ``Latin1Prober``. - """ - filtered = bytearray() - in_tag = False - prev = 0 - - for curr in range(len(buf)): - # Slice here to get bytes instead of an int with Python 3 - buf_char = buf[curr:curr + 1] - # Check if we're coming out of or entering an HTML tag - if buf_char == b'>': - in_tag = False - elif buf_char == b'<': - in_tag = True - - # If current character is not extended-ASCII and not alphabetic... - if buf_char < b'\x80' and not buf_char.isalpha(): - # ...and we're not in a tag - if curr > prev and not in_tag: - # Keep everything after last non-extended-ASCII, - # non-alphabetic character - filtered.extend(buf[prev:curr]) - # Output a space to delimit stretch we kept - filtered.extend(b' ') - prev = curr + 1 - - # If we're not in a tag... - if not in_tag: - # Keep everything after last non-extended-ASCII, non-alphabetic - # character - filtered.extend(buf[prev:]) - - return filtered diff --git a/pipenv/vendor/chardet/cli/__init__.py b/pipenv/vendor/chardet/cli/__init__.py deleted file mode 100644 index 8b137891..00000000 --- a/pipenv/vendor/chardet/cli/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/pipenv/vendor/chardet/cli/chardetect.py b/pipenv/vendor/chardet/cli/chardetect.py deleted file mode 100644 index f0a4cc5d..00000000 --- a/pipenv/vendor/chardet/cli/chardetect.py +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env python -""" -Script which takes one or more file paths and reports on their detected -encodings - -Example:: - - % chardetect somefile someotherfile - somefile: windows-1252 with confidence 0.5 - someotherfile: ascii with confidence 1.0 - -If no paths are provided, it takes its input from stdin. - -""" - -from __future__ import absolute_import, print_function, unicode_literals - -import argparse -import sys - -from chardet import __version__ -from chardet.compat import PY2 -from chardet.universaldetector import UniversalDetector - - -def description_of(lines, name='stdin'): - """ - Return a string describing the probable encoding of a file or - list of strings. - - :param lines: The lines to get the encoding of. - :type lines: Iterable of bytes - :param name: Name of file or collection of lines - :type name: str - """ - u = UniversalDetector() - for line in lines: - line = bytearray(line) - u.feed(line) - # shortcut out of the loop to save reading further - particularly useful if we read a BOM. - if u.done: - break - u.close() - result = u.result - if PY2: - name = name.decode(sys.getfilesystemencoding(), 'ignore') - if result['encoding']: - return '{0}: {1} with confidence {2}'.format(name, result['encoding'], - result['confidence']) - else: - return '{0}: no result'.format(name) - - -def main(argv=None): - """ - Handles command line arguments and gets things started. - - :param argv: List of arguments, as if specified on the command-line. - If None, ``sys.argv[1:]`` is used instead. - :type argv: list of str - """ - # Get command line arguments - parser = argparse.ArgumentParser( - description="Takes one or more file paths and reports their detected \ - encodings") - parser.add_argument('input', - help='File whose encoding we would like to determine. \ - (default: stdin)', - type=argparse.FileType('rb'), nargs='*', - default=[sys.stdin if PY2 else sys.stdin.buffer]) - parser.add_argument('--version', action='version', - version='%(prog)s {0}'.format(__version__)) - args = parser.parse_args(argv) - - for f in args.input: - if f.isatty(): - print("You are running chardetect interactively. Press " + - "CTRL-D twice at the start of a blank line to signal the " + - "end of your input. If you want help, run chardetect " + - "--help\n", file=sys.stderr) - print(description_of(f, f.name)) - - -if __name__ == '__main__': - main() diff --git a/pipenv/vendor/chardet/codingstatemachine.py b/pipenv/vendor/chardet/codingstatemachine.py deleted file mode 100644 index 68fba44f..00000000 --- a/pipenv/vendor/chardet/codingstatemachine.py +++ /dev/null @@ -1,88 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is mozilla.org code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -import logging - -from .enums import MachineState - - -class CodingStateMachine(object): - """ - A state machine to verify a byte sequence for a particular encoding. For - each byte the detector receives, it will feed that byte to every active - state machine available, one byte at a time. The state machine changes its - state based on its previous state and the byte it receives. There are 3 - states in a state machine that are of interest to an auto-detector: - - START state: This is the state to start with, or a legal byte sequence - (i.e. a valid code point) for character has been identified. - - ME state: This indicates that the state machine identified a byte sequence - that is specific to the charset it is designed for and that - there is no other possible encoding which can contain this byte - sequence. This will to lead to an immediate positive answer for - the detector. - - ERROR state: This indicates the state machine identified an illegal byte - sequence for that encoding. This will lead to an immediate - negative answer for this encoding. Detector will exclude this - encoding from consideration from here on. - """ - def __init__(self, sm): - self._model = sm - self._curr_byte_pos = 0 - self._curr_char_len = 0 - self._curr_state = None - self.logger = logging.getLogger(__name__) - self.reset() - - def reset(self): - self._curr_state = MachineState.START - - def next_state(self, c): - # for each byte we get its class - # if it is first byte, we also get byte length - byte_class = self._model['class_table'][c] - if self._curr_state == MachineState.START: - self._curr_byte_pos = 0 - self._curr_char_len = self._model['char_len_table'][byte_class] - # from byte's class and state_table, we get its next state - curr_state = (self._curr_state * self._model['class_factor'] - + byte_class) - self._curr_state = self._model['state_table'][curr_state] - self._curr_byte_pos += 1 - return self._curr_state - - def get_current_charlen(self): - return self._curr_char_len - - def get_coding_state_machine(self): - return self._model['name'] - - @property - def language(self): - return self._model['language'] diff --git a/pipenv/vendor/chardet/compat.py b/pipenv/vendor/chardet/compat.py deleted file mode 100644 index ddd74687..00000000 --- a/pipenv/vendor/chardet/compat.py +++ /dev/null @@ -1,34 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# Contributor(s): -# Dan Blanchard -# Ian Cordasco -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -import sys - - -if sys.version_info < (3, 0): - PY2 = True - PY3 = False - base_str = (str, unicode) - text_type = unicode -else: - PY2 = False - PY3 = True - base_str = (bytes, str) - text_type = str diff --git a/pipenv/vendor/chardet/cp949prober.py b/pipenv/vendor/chardet/cp949prober.py deleted file mode 100644 index efd793ab..00000000 --- a/pipenv/vendor/chardet/cp949prober.py +++ /dev/null @@ -1,49 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is mozilla.org code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .chardistribution import EUCKRDistributionAnalysis -from .codingstatemachine import CodingStateMachine -from .mbcharsetprober import MultiByteCharSetProber -from .mbcssm import CP949_SM_MODEL - - -class CP949Prober(MultiByteCharSetProber): - def __init__(self): - super(CP949Prober, self).__init__() - self.coding_sm = CodingStateMachine(CP949_SM_MODEL) - # NOTE: CP949 is a superset of EUC-KR, so the distribution should be - # not different. - self.distribution_analyzer = EUCKRDistributionAnalysis() - self.reset() - - @property - def charset_name(self): - return "CP949" - - @property - def language(self): - return "Korean" diff --git a/pipenv/vendor/chardet/enums.py b/pipenv/vendor/chardet/enums.py deleted file mode 100644 index 04512072..00000000 --- a/pipenv/vendor/chardet/enums.py +++ /dev/null @@ -1,76 +0,0 @@ -""" -All of the Enums that are used throughout the chardet package. - -:author: Dan Blanchard (dan.blanchard@gmail.com) -""" - - -class InputState(object): - """ - This enum represents the different states a universal detector can be in. - """ - PURE_ASCII = 0 - ESC_ASCII = 1 - HIGH_BYTE = 2 - - -class LanguageFilter(object): - """ - This enum represents the different language filters we can apply to a - ``UniversalDetector``. - """ - CHINESE_SIMPLIFIED = 0x01 - CHINESE_TRADITIONAL = 0x02 - JAPANESE = 0x04 - KOREAN = 0x08 - NON_CJK = 0x10 - ALL = 0x1F - CHINESE = CHINESE_SIMPLIFIED | CHINESE_TRADITIONAL - CJK = CHINESE | JAPANESE | KOREAN - - -class ProbingState(object): - """ - This enum represents the different states a prober can be in. - """ - DETECTING = 0 - FOUND_IT = 1 - NOT_ME = 2 - - -class MachineState(object): - """ - This enum represents the different states a state machine can be in. - """ - START = 0 - ERROR = 1 - ITS_ME = 2 - - -class SequenceLikelihood(object): - """ - This enum represents the likelihood of a character following the previous one. - """ - NEGATIVE = 0 - UNLIKELY = 1 - LIKELY = 2 - POSITIVE = 3 - - @classmethod - def get_num_categories(cls): - """:returns: The number of likelihood categories in the enum.""" - return 4 - - -class CharacterCategory(object): - """ - This enum represents the different categories language models for - ``SingleByteCharsetProber`` put characters into. - - Anything less than CONTROL is considered a letter. - """ - UNDEFINED = 255 - LINE_BREAK = 254 - SYMBOL = 253 - DIGIT = 252 - CONTROL = 251 diff --git a/pipenv/vendor/chardet/escprober.py b/pipenv/vendor/chardet/escprober.py deleted file mode 100644 index c70493f2..00000000 --- a/pipenv/vendor/chardet/escprober.py +++ /dev/null @@ -1,101 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is mozilla.org code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .charsetprober import CharSetProber -from .codingstatemachine import CodingStateMachine -from .enums import LanguageFilter, ProbingState, MachineState -from .escsm import (HZ_SM_MODEL, ISO2022CN_SM_MODEL, ISO2022JP_SM_MODEL, - ISO2022KR_SM_MODEL) - - -class EscCharSetProber(CharSetProber): - """ - This CharSetProber uses a "code scheme" approach for detecting encodings, - whereby easily recognizable escape or shift sequences are relied on to - identify these encodings. - """ - - def __init__(self, lang_filter=None): - super(EscCharSetProber, self).__init__(lang_filter=lang_filter) - self.coding_sm = [] - if self.lang_filter & LanguageFilter.CHINESE_SIMPLIFIED: - self.coding_sm.append(CodingStateMachine(HZ_SM_MODEL)) - self.coding_sm.append(CodingStateMachine(ISO2022CN_SM_MODEL)) - if self.lang_filter & LanguageFilter.JAPANESE: - self.coding_sm.append(CodingStateMachine(ISO2022JP_SM_MODEL)) - if self.lang_filter & LanguageFilter.KOREAN: - self.coding_sm.append(CodingStateMachine(ISO2022KR_SM_MODEL)) - self.active_sm_count = None - self._detected_charset = None - self._detected_language = None - self._state = None - self.reset() - - def reset(self): - super(EscCharSetProber, self).reset() - for coding_sm in self.coding_sm: - if not coding_sm: - continue - coding_sm.active = True - coding_sm.reset() - self.active_sm_count = len(self.coding_sm) - self._detected_charset = None - self._detected_language = None - - @property - def charset_name(self): - return self._detected_charset - - @property - def language(self): - return self._detected_language - - def get_confidence(self): - if self._detected_charset: - return 0.99 - else: - return 0.00 - - def feed(self, byte_str): - for c in byte_str: - for coding_sm in self.coding_sm: - if not coding_sm or not coding_sm.active: - continue - coding_state = coding_sm.next_state(c) - if coding_state == MachineState.ERROR: - coding_sm.active = False - self.active_sm_count -= 1 - if self.active_sm_count <= 0: - self._state = ProbingState.NOT_ME - return self.state - elif coding_state == MachineState.ITS_ME: - self._state = ProbingState.FOUND_IT - self._detected_charset = coding_sm.get_coding_state_machine() - self._detected_language = coding_sm.language - return self.state - - return self.state diff --git a/pipenv/vendor/chardet/escsm.py b/pipenv/vendor/chardet/escsm.py deleted file mode 100644 index 0069523a..00000000 --- a/pipenv/vendor/chardet/escsm.py +++ /dev/null @@ -1,246 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is mozilla.org code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .enums import MachineState - -HZ_CLS = ( -1,0,0,0,0,0,0,0, # 00 - 07 -0,0,0,0,0,0,0,0, # 08 - 0f -0,0,0,0,0,0,0,0, # 10 - 17 -0,0,0,1,0,0,0,0, # 18 - 1f -0,0,0,0,0,0,0,0, # 20 - 27 -0,0,0,0,0,0,0,0, # 28 - 2f -0,0,0,0,0,0,0,0, # 30 - 37 -0,0,0,0,0,0,0,0, # 38 - 3f -0,0,0,0,0,0,0,0, # 40 - 47 -0,0,0,0,0,0,0,0, # 48 - 4f -0,0,0,0,0,0,0,0, # 50 - 57 -0,0,0,0,0,0,0,0, # 58 - 5f -0,0,0,0,0,0,0,0, # 60 - 67 -0,0,0,0,0,0,0,0, # 68 - 6f -0,0,0,0,0,0,0,0, # 70 - 77 -0,0,0,4,0,5,2,0, # 78 - 7f -1,1,1,1,1,1,1,1, # 80 - 87 -1,1,1,1,1,1,1,1, # 88 - 8f -1,1,1,1,1,1,1,1, # 90 - 97 -1,1,1,1,1,1,1,1, # 98 - 9f -1,1,1,1,1,1,1,1, # a0 - a7 -1,1,1,1,1,1,1,1, # a8 - af -1,1,1,1,1,1,1,1, # b0 - b7 -1,1,1,1,1,1,1,1, # b8 - bf -1,1,1,1,1,1,1,1, # c0 - c7 -1,1,1,1,1,1,1,1, # c8 - cf -1,1,1,1,1,1,1,1, # d0 - d7 -1,1,1,1,1,1,1,1, # d8 - df -1,1,1,1,1,1,1,1, # e0 - e7 -1,1,1,1,1,1,1,1, # e8 - ef -1,1,1,1,1,1,1,1, # f0 - f7 -1,1,1,1,1,1,1,1, # f8 - ff -) - -HZ_ST = ( -MachineState.START,MachineState.ERROR, 3,MachineState.START,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,# 00-07 -MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,# 08-0f -MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START, 4,MachineState.ERROR,# 10-17 - 5,MachineState.ERROR, 6,MachineState.ERROR, 5, 5, 4,MachineState.ERROR,# 18-1f - 4,MachineState.ERROR, 4, 4, 4,MachineState.ERROR, 4,MachineState.ERROR,# 20-27 - 4,MachineState.ITS_ME,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,# 28-2f -) - -HZ_CHAR_LEN_TABLE = (0, 0, 0, 0, 0, 0) - -HZ_SM_MODEL = {'class_table': HZ_CLS, - 'class_factor': 6, - 'state_table': HZ_ST, - 'char_len_table': HZ_CHAR_LEN_TABLE, - 'name': "HZ-GB-2312", - 'language': 'Chinese'} - -ISO2022CN_CLS = ( -2,0,0,0,0,0,0,0, # 00 - 07 -0,0,0,0,0,0,0,0, # 08 - 0f -0,0,0,0,0,0,0,0, # 10 - 17 -0,0,0,1,0,0,0,0, # 18 - 1f -0,0,0,0,0,0,0,0, # 20 - 27 -0,3,0,0,0,0,0,0, # 28 - 2f -0,0,0,0,0,0,0,0, # 30 - 37 -0,0,0,0,0,0,0,0, # 38 - 3f -0,0,0,4,0,0,0,0, # 40 - 47 -0,0,0,0,0,0,0,0, # 48 - 4f -0,0,0,0,0,0,0,0, # 50 - 57 -0,0,0,0,0,0,0,0, # 58 - 5f -0,0,0,0,0,0,0,0, # 60 - 67 -0,0,0,0,0,0,0,0, # 68 - 6f -0,0,0,0,0,0,0,0, # 70 - 77 -0,0,0,0,0,0,0,0, # 78 - 7f -2,2,2,2,2,2,2,2, # 80 - 87 -2,2,2,2,2,2,2,2, # 88 - 8f -2,2,2,2,2,2,2,2, # 90 - 97 -2,2,2,2,2,2,2,2, # 98 - 9f -2,2,2,2,2,2,2,2, # a0 - a7 -2,2,2,2,2,2,2,2, # a8 - af -2,2,2,2,2,2,2,2, # b0 - b7 -2,2,2,2,2,2,2,2, # b8 - bf -2,2,2,2,2,2,2,2, # c0 - c7 -2,2,2,2,2,2,2,2, # c8 - cf -2,2,2,2,2,2,2,2, # d0 - d7 -2,2,2,2,2,2,2,2, # d8 - df -2,2,2,2,2,2,2,2, # e0 - e7 -2,2,2,2,2,2,2,2, # e8 - ef -2,2,2,2,2,2,2,2, # f0 - f7 -2,2,2,2,2,2,2,2, # f8 - ff -) - -ISO2022CN_ST = ( -MachineState.START, 3,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,# 00-07 -MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,# 08-0f -MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,# 10-17 -MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 4,MachineState.ERROR,# 18-1f -MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,# 20-27 - 5, 6,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,# 28-2f -MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,# 30-37 -MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,MachineState.START,# 38-3f -) - -ISO2022CN_CHAR_LEN_TABLE = (0, 0, 0, 0, 0, 0, 0, 0, 0) - -ISO2022CN_SM_MODEL = {'class_table': ISO2022CN_CLS, - 'class_factor': 9, - 'state_table': ISO2022CN_ST, - 'char_len_table': ISO2022CN_CHAR_LEN_TABLE, - 'name': "ISO-2022-CN", - 'language': 'Chinese'} - -ISO2022JP_CLS = ( -2,0,0,0,0,0,0,0, # 00 - 07 -0,0,0,0,0,0,2,2, # 08 - 0f -0,0,0,0,0,0,0,0, # 10 - 17 -0,0,0,1,0,0,0,0, # 18 - 1f -0,0,0,0,7,0,0,0, # 20 - 27 -3,0,0,0,0,0,0,0, # 28 - 2f -0,0,0,0,0,0,0,0, # 30 - 37 -0,0,0,0,0,0,0,0, # 38 - 3f -6,0,4,0,8,0,0,0, # 40 - 47 -0,9,5,0,0,0,0,0, # 48 - 4f -0,0,0,0,0,0,0,0, # 50 - 57 -0,0,0,0,0,0,0,0, # 58 - 5f -0,0,0,0,0,0,0,0, # 60 - 67 -0,0,0,0,0,0,0,0, # 68 - 6f -0,0,0,0,0,0,0,0, # 70 - 77 -0,0,0,0,0,0,0,0, # 78 - 7f -2,2,2,2,2,2,2,2, # 80 - 87 -2,2,2,2,2,2,2,2, # 88 - 8f -2,2,2,2,2,2,2,2, # 90 - 97 -2,2,2,2,2,2,2,2, # 98 - 9f -2,2,2,2,2,2,2,2, # a0 - a7 -2,2,2,2,2,2,2,2, # a8 - af -2,2,2,2,2,2,2,2, # b0 - b7 -2,2,2,2,2,2,2,2, # b8 - bf -2,2,2,2,2,2,2,2, # c0 - c7 -2,2,2,2,2,2,2,2, # c8 - cf -2,2,2,2,2,2,2,2, # d0 - d7 -2,2,2,2,2,2,2,2, # d8 - df -2,2,2,2,2,2,2,2, # e0 - e7 -2,2,2,2,2,2,2,2, # e8 - ef -2,2,2,2,2,2,2,2, # f0 - f7 -2,2,2,2,2,2,2,2, # f8 - ff -) - -ISO2022JP_ST = ( -MachineState.START, 3,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,# 00-07 -MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,# 08-0f -MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,# 10-17 -MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,# 18-1f -MachineState.ERROR, 5,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 4,MachineState.ERROR,MachineState.ERROR,# 20-27 -MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 6,MachineState.ITS_ME,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,# 28-2f -MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,# 30-37 -MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,# 38-3f -MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,MachineState.START,MachineState.START,# 40-47 -) - -ISO2022JP_CHAR_LEN_TABLE = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0) - -ISO2022JP_SM_MODEL = {'class_table': ISO2022JP_CLS, - 'class_factor': 10, - 'state_table': ISO2022JP_ST, - 'char_len_table': ISO2022JP_CHAR_LEN_TABLE, - 'name': "ISO-2022-JP", - 'language': 'Japanese'} - -ISO2022KR_CLS = ( -2,0,0,0,0,0,0,0, # 00 - 07 -0,0,0,0,0,0,0,0, # 08 - 0f -0,0,0,0,0,0,0,0, # 10 - 17 -0,0,0,1,0,0,0,0, # 18 - 1f -0,0,0,0,3,0,0,0, # 20 - 27 -0,4,0,0,0,0,0,0, # 28 - 2f -0,0,0,0,0,0,0,0, # 30 - 37 -0,0,0,0,0,0,0,0, # 38 - 3f -0,0,0,5,0,0,0,0, # 40 - 47 -0,0,0,0,0,0,0,0, # 48 - 4f -0,0,0,0,0,0,0,0, # 50 - 57 -0,0,0,0,0,0,0,0, # 58 - 5f -0,0,0,0,0,0,0,0, # 60 - 67 -0,0,0,0,0,0,0,0, # 68 - 6f -0,0,0,0,0,0,0,0, # 70 - 77 -0,0,0,0,0,0,0,0, # 78 - 7f -2,2,2,2,2,2,2,2, # 80 - 87 -2,2,2,2,2,2,2,2, # 88 - 8f -2,2,2,2,2,2,2,2, # 90 - 97 -2,2,2,2,2,2,2,2, # 98 - 9f -2,2,2,2,2,2,2,2, # a0 - a7 -2,2,2,2,2,2,2,2, # a8 - af -2,2,2,2,2,2,2,2, # b0 - b7 -2,2,2,2,2,2,2,2, # b8 - bf -2,2,2,2,2,2,2,2, # c0 - c7 -2,2,2,2,2,2,2,2, # c8 - cf -2,2,2,2,2,2,2,2, # d0 - d7 -2,2,2,2,2,2,2,2, # d8 - df -2,2,2,2,2,2,2,2, # e0 - e7 -2,2,2,2,2,2,2,2, # e8 - ef -2,2,2,2,2,2,2,2, # f0 - f7 -2,2,2,2,2,2,2,2, # f8 - ff -) - -ISO2022KR_ST = ( -MachineState.START, 3,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,# 00-07 -MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,# 08-0f -MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 4,MachineState.ERROR,MachineState.ERROR,# 10-17 -MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 5,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,# 18-1f -MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.START,MachineState.START,MachineState.START,MachineState.START,# 20-27 -) - -ISO2022KR_CHAR_LEN_TABLE = (0, 0, 0, 0, 0, 0) - -ISO2022KR_SM_MODEL = {'class_table': ISO2022KR_CLS, - 'class_factor': 6, - 'state_table': ISO2022KR_ST, - 'char_len_table': ISO2022KR_CHAR_LEN_TABLE, - 'name': "ISO-2022-KR", - 'language': 'Korean'} - - diff --git a/pipenv/vendor/chardet/eucjpprober.py b/pipenv/vendor/chardet/eucjpprober.py deleted file mode 100644 index 20ce8f7d..00000000 --- a/pipenv/vendor/chardet/eucjpprober.py +++ /dev/null @@ -1,92 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is mozilla.org code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .enums import ProbingState, MachineState -from .mbcharsetprober import MultiByteCharSetProber -from .codingstatemachine import CodingStateMachine -from .chardistribution import EUCJPDistributionAnalysis -from .jpcntx import EUCJPContextAnalysis -from .mbcssm import EUCJP_SM_MODEL - - -class EUCJPProber(MultiByteCharSetProber): - def __init__(self): - super(EUCJPProber, self).__init__() - self.coding_sm = CodingStateMachine(EUCJP_SM_MODEL) - self.distribution_analyzer = EUCJPDistributionAnalysis() - self.context_analyzer = EUCJPContextAnalysis() - self.reset() - - def reset(self): - super(EUCJPProber, self).reset() - self.context_analyzer.reset() - - @property - def charset_name(self): - return "EUC-JP" - - @property - def language(self): - return "Japanese" - - def feed(self, byte_str): - for i in range(len(byte_str)): - # PY3K: byte_str is a byte array, so byte_str[i] is an int, not a byte - coding_state = self.coding_sm.next_state(byte_str[i]) - if coding_state == MachineState.ERROR: - self.logger.debug('%s %s prober hit error at byte %s', - self.charset_name, self.language, i) - self._state = ProbingState.NOT_ME - break - elif coding_state == MachineState.ITS_ME: - self._state = ProbingState.FOUND_IT - break - elif coding_state == MachineState.START: - char_len = self.coding_sm.get_current_charlen() - if i == 0: - self._last_char[1] = byte_str[0] - self.context_analyzer.feed(self._last_char, char_len) - self.distribution_analyzer.feed(self._last_char, char_len) - else: - self.context_analyzer.feed(byte_str[i - 1:i + 1], - char_len) - self.distribution_analyzer.feed(byte_str[i - 1:i + 1], - char_len) - - self._last_char[0] = byte_str[-1] - - if self.state == ProbingState.DETECTING: - if (self.context_analyzer.got_enough_data() and - (self.get_confidence() > self.SHORTCUT_THRESHOLD)): - self._state = ProbingState.FOUND_IT - - return self.state - - def get_confidence(self): - context_conf = self.context_analyzer.get_confidence() - distrib_conf = self.distribution_analyzer.get_confidence() - return max(context_conf, distrib_conf) diff --git a/pipenv/vendor/chardet/euckrfreq.py b/pipenv/vendor/chardet/euckrfreq.py deleted file mode 100644 index b68078cb..00000000 --- a/pipenv/vendor/chardet/euckrfreq.py +++ /dev/null @@ -1,195 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -# Sampling from about 20M text materials include literature and computer technology - -# 128 --> 0.79 -# 256 --> 0.92 -# 512 --> 0.986 -# 1024 --> 0.99944 -# 2048 --> 0.99999 -# -# Idea Distribution Ratio = 0.98653 / (1-0.98653) = 73.24 -# Random Distribution Ration = 512 / (2350-512) = 0.279. -# -# Typical Distribution Ratio - -EUCKR_TYPICAL_DISTRIBUTION_RATIO = 6.0 - -EUCKR_TABLE_SIZE = 2352 - -# Char to FreqOrder table , -EUCKR_CHAR_TO_FREQ_ORDER = ( - 13, 130, 120,1396, 481,1719,1720, 328, 609, 212,1721, 707, 400, 299,1722, 87, -1397,1723, 104, 536,1117,1203,1724,1267, 685,1268, 508,1725,1726,1727,1728,1398, -1399,1729,1730,1731, 141, 621, 326,1057, 368,1732, 267, 488, 20,1733,1269,1734, - 945,1400,1735, 47, 904,1270,1736,1737, 773, 248,1738, 409, 313, 786, 429,1739, - 116, 987, 813,1401, 683, 75,1204, 145,1740,1741,1742,1743, 16, 847, 667, 622, - 708,1744,1745,1746, 966, 787, 304, 129,1747, 60, 820, 123, 676,1748,1749,1750, -1751, 617,1752, 626,1753,1754,1755,1756, 653,1757,1758,1759,1760,1761,1762, 856, - 344,1763,1764,1765,1766, 89, 401, 418, 806, 905, 848,1767,1768,1769, 946,1205, - 709,1770,1118,1771, 241,1772,1773,1774,1271,1775, 569,1776, 999,1777,1778,1779, -1780, 337, 751,1058, 28, 628, 254,1781, 177, 906, 270, 349, 891,1079,1782, 19, -1783, 379,1784, 315,1785, 629, 754,1402, 559,1786, 636, 203,1206,1787, 710, 567, -1788, 935, 814,1789,1790,1207, 766, 528,1791,1792,1208,1793,1794,1795,1796,1797, -1403,1798,1799, 533,1059,1404,1405,1156,1406, 936, 884,1080,1800, 351,1801,1802, -1803,1804,1805, 801,1806,1807,1808,1119,1809,1157, 714, 474,1407,1810, 298, 899, - 885,1811,1120, 802,1158,1812, 892,1813,1814,1408, 659,1815,1816,1121,1817,1818, -1819,1820,1821,1822, 319,1823, 594, 545,1824, 815, 937,1209,1825,1826, 573,1409, -1022,1827,1210,1828,1829,1830,1831,1832,1833, 556, 722, 807,1122,1060,1834, 697, -1835, 900, 557, 715,1836,1410, 540,1411, 752,1159, 294, 597,1211, 976, 803, 770, -1412,1837,1838, 39, 794,1413, 358,1839, 371, 925,1840, 453, 661, 788, 531, 723, - 544,1023,1081, 869, 91,1841, 392, 430, 790, 602,1414, 677,1082, 457,1415,1416, -1842,1843, 475, 327,1024,1417, 795, 121,1844, 733, 403,1418,1845,1846,1847, 300, - 119, 711,1212, 627,1848,1272, 207,1849,1850, 796,1213, 382,1851, 519,1852,1083, - 893,1853,1854,1855, 367, 809, 487, 671,1856, 663,1857,1858, 956, 471, 306, 857, -1859,1860,1160,1084,1861,1862,1863,1864,1865,1061,1866,1867,1868,1869,1870,1871, - 282, 96, 574,1872, 502,1085,1873,1214,1874, 907,1875,1876, 827, 977,1419,1420, -1421, 268,1877,1422,1878,1879,1880, 308,1881, 2, 537,1882,1883,1215,1884,1885, - 127, 791,1886,1273,1423,1887, 34, 336, 404, 643,1888, 571, 654, 894, 840,1889, - 0, 886,1274, 122, 575, 260, 908, 938,1890,1275, 410, 316,1891,1892, 100,1893, -1894,1123, 48,1161,1124,1025,1895, 633, 901,1276,1896,1897, 115, 816,1898, 317, -1899, 694,1900, 909, 734,1424, 572, 866,1425, 691, 85, 524,1010, 543, 394, 841, -1901,1902,1903,1026,1904,1905,1906,1907,1908,1909, 30, 451, 651, 988, 310,1910, -1911,1426, 810,1216, 93,1912,1913,1277,1217,1914, 858, 759, 45, 58, 181, 610, - 269,1915,1916, 131,1062, 551, 443,1000, 821,1427, 957, 895,1086,1917,1918, 375, -1919, 359,1920, 687,1921, 822,1922, 293,1923,1924, 40, 662, 118, 692, 29, 939, - 887, 640, 482, 174,1925, 69,1162, 728,1428, 910,1926,1278,1218,1279, 386, 870, - 217, 854,1163, 823,1927,1928,1929,1930, 834,1931, 78,1932, 859,1933,1063,1934, -1935,1936,1937, 438,1164, 208, 595,1938,1939,1940,1941,1219,1125,1942, 280, 888, -1429,1430,1220,1431,1943,1944,1945,1946,1947,1280, 150, 510,1432,1948,1949,1950, -1951,1952,1953,1954,1011,1087,1955,1433,1043,1956, 881,1957, 614, 958,1064,1065, -1221,1958, 638,1001, 860, 967, 896,1434, 989, 492, 553,1281,1165,1959,1282,1002, -1283,1222,1960,1961,1962,1963, 36, 383, 228, 753, 247, 454,1964, 876, 678,1965, -1966,1284, 126, 464, 490, 835, 136, 672, 529, 940,1088,1435, 473,1967,1968, 467, - 50, 390, 227, 587, 279, 378, 598, 792, 968, 240, 151, 160, 849, 882,1126,1285, - 639,1044, 133, 140, 288, 360, 811, 563,1027, 561, 142, 523,1969,1970,1971, 7, - 103, 296, 439, 407, 506, 634, 990,1972,1973,1974,1975, 645,1976,1977,1978,1979, -1980,1981, 236,1982,1436,1983,1984,1089, 192, 828, 618, 518,1166, 333,1127,1985, - 818,1223,1986,1987,1988,1989,1990,1991,1992,1993, 342,1128,1286, 746, 842,1994, -1995, 560, 223,1287, 98, 8, 189, 650, 978,1288,1996,1437,1997, 17, 345, 250, - 423, 277, 234, 512, 226, 97, 289, 42, 167,1998, 201,1999,2000, 843, 836, 824, - 532, 338, 783,1090, 182, 576, 436,1438,1439, 527, 500,2001, 947, 889,2002,2003, -2004,2005, 262, 600, 314, 447,2006, 547,2007, 693, 738,1129,2008, 71,1440, 745, - 619, 688,2009, 829,2010,2011, 147,2012, 33, 948,2013,2014, 74, 224,2015, 61, - 191, 918, 399, 637,2016,1028,1130, 257, 902,2017,2018,2019,2020,2021,2022,2023, -2024,2025,2026, 837,2027,2028,2029,2030, 179, 874, 591, 52, 724, 246,2031,2032, -2033,2034,1167, 969,2035,1289, 630, 605, 911,1091,1168,2036,2037,2038,1441, 912, -2039, 623,2040,2041, 253,1169,1290,2042,1442, 146, 620, 611, 577, 433,2043,1224, - 719,1170, 959, 440, 437, 534, 84, 388, 480,1131, 159, 220, 198, 679,2044,1012, - 819,1066,1443, 113,1225, 194, 318,1003,1029,2045,2046,2047,2048,1067,2049,2050, -2051,2052,2053, 59, 913, 112,2054, 632,2055, 455, 144, 739,1291,2056, 273, 681, - 499,2057, 448,2058,2059, 760,2060,2061, 970, 384, 169, 245,1132,2062,2063, 414, -1444,2064,2065, 41, 235,2066, 157, 252, 877, 568, 919, 789, 580,2067, 725,2068, -2069,1292,2070,2071,1445,2072,1446,2073,2074, 55, 588, 66,1447, 271,1092,2075, -1226,2076, 960,1013, 372,2077,2078,2079,2080,2081,1293,2082,2083,2084,2085, 850, -2086,2087,2088,2089,2090, 186,2091,1068, 180,2092,2093,2094, 109,1227, 522, 606, -2095, 867,1448,1093, 991,1171, 926, 353,1133,2096, 581,2097,2098,2099,1294,1449, -1450,2100, 596,1172,1014,1228,2101,1451,1295,1173,1229,2102,2103,1296,1134,1452, - 949,1135,2104,2105,1094,1453,1454,1455,2106,1095,2107,2108,2109,2110,2111,2112, -2113,2114,2115,2116,2117, 804,2118,2119,1230,1231, 805,1456, 405,1136,2120,2121, -2122,2123,2124, 720, 701,1297, 992,1457, 927,1004,2125,2126,2127,2128,2129,2130, - 22, 417,2131, 303,2132, 385,2133, 971, 520, 513,2134,1174, 73,1096, 231, 274, - 962,1458, 673,2135,1459,2136, 152,1137,2137,2138,2139,2140,1005,1138,1460,1139, -2141,2142,2143,2144, 11, 374, 844,2145, 154,1232, 46,1461,2146, 838, 830, 721, -1233, 106,2147, 90, 428, 462, 578, 566,1175, 352,2148,2149, 538,1234, 124,1298, -2150,1462, 761, 565,2151, 686,2152, 649,2153, 72, 173,2154, 460, 415,2155,1463, -2156,1235, 305,2157,2158,2159,2160,2161,2162, 579,2163,2164,2165,2166,2167, 747, -2168,2169,2170,2171,1464, 669,2172,2173,2174,2175,2176,1465,2177, 23, 530, 285, -2178, 335, 729,2179, 397,2180,2181,2182,1030,2183,2184, 698,2185,2186, 325,2187, -2188, 369,2189, 799,1097,1015, 348,2190,1069, 680,2191, 851,1466,2192,2193, 10, -2194, 613, 424,2195, 979, 108, 449, 589, 27, 172, 81,1031, 80, 774, 281, 350, -1032, 525, 301, 582,1176,2196, 674,1045,2197,2198,1467, 730, 762,2199,2200,2201, -2202,1468,2203, 993,2204,2205, 266,1070, 963,1140,2206,2207,2208, 664,1098, 972, -2209,2210,2211,1177,1469,1470, 871,2212,2213,2214,2215,2216,1471,2217,2218,2219, -2220,2221,2222,2223,2224,2225,2226,2227,1472,1236,2228,2229,2230,2231,2232,2233, -2234,2235,1299,2236,2237, 200,2238, 477, 373,2239,2240, 731, 825, 777,2241,2242, -2243, 521, 486, 548,2244,2245,2246,1473,1300, 53, 549, 137, 875, 76, 158,2247, -1301,1474, 469, 396,1016, 278, 712,2248, 321, 442, 503, 767, 744, 941,1237,1178, -1475,2249, 82, 178,1141,1179, 973,2250,1302,2251, 297,2252,2253, 570,2254,2255, -2256, 18, 450, 206,2257, 290, 292,1142,2258, 511, 162, 99, 346, 164, 735,2259, -1476,1477, 4, 554, 343, 798,1099,2260,1100,2261, 43, 171,1303, 139, 215,2262, -2263, 717, 775,2264,1033, 322, 216,2265, 831,2266, 149,2267,1304,2268,2269, 702, -1238, 135, 845, 347, 309,2270, 484,2271, 878, 655, 238,1006,1478,2272, 67,2273, - 295,2274,2275, 461,2276, 478, 942, 412,2277,1034,2278,2279,2280, 265,2281, 541, -2282,2283,2284,2285,2286, 70, 852,1071,2287,2288,2289,2290, 21, 56, 509, 117, - 432,2291,2292, 331, 980, 552,1101, 148, 284, 105, 393,1180,1239, 755,2293, 187, -2294,1046,1479,2295, 340,2296, 63,1047, 230,2297,2298,1305, 763,1306, 101, 800, - 808, 494,2299,2300,2301, 903,2302, 37,1072, 14, 5,2303, 79, 675,2304, 312, -2305,2306,2307,2308,2309,1480, 6,1307,2310,2311,2312, 1, 470, 35, 24, 229, -2313, 695, 210, 86, 778, 15, 784, 592, 779, 32, 77, 855, 964,2314, 259,2315, - 501, 380,2316,2317, 83, 981, 153, 689,1308,1481,1482,1483,2318,2319, 716,1484, -2320,2321,2322,2323,2324,2325,1485,2326,2327, 128, 57, 68, 261,1048, 211, 170, -1240, 31,2328, 51, 435, 742,2329,2330,2331, 635,2332, 264, 456,2333,2334,2335, - 425,2336,1486, 143, 507, 263, 943,2337, 363, 920,1487, 256,1488,1102, 243, 601, -1489,2338,2339,2340,2341,2342,2343,2344, 861,2345,2346,2347,2348,2349,2350, 395, -2351,1490,1491, 62, 535, 166, 225,2352,2353, 668, 419,1241, 138, 604, 928,2354, -1181,2355,1492,1493,2356,2357,2358,1143,2359, 696,2360, 387, 307,1309, 682, 476, -2361,2362, 332, 12, 222, 156,2363, 232,2364, 641, 276, 656, 517,1494,1495,1035, - 416, 736,1496,2365,1017, 586,2366,2367,2368,1497,2369, 242,2370,2371,2372,1498, -2373, 965, 713,2374,2375,2376,2377, 740, 982,1499, 944,1500,1007,2378,2379,1310, -1501,2380,2381,2382, 785, 329,2383,2384,1502,2385,2386,2387, 932,2388,1503,2389, -2390,2391,2392,1242,2393,2394,2395,2396,2397, 994, 950,2398,2399,2400,2401,1504, -1311,2402,2403,2404,2405,1049, 749,2406,2407, 853, 718,1144,1312,2408,1182,1505, -2409,2410, 255, 516, 479, 564, 550, 214,1506,1507,1313, 413, 239, 444, 339,1145, -1036,1508,1509,1314,1037,1510,1315,2411,1511,2412,2413,2414, 176, 703, 497, 624, - 593, 921, 302,2415, 341, 165,1103,1512,2416,1513,2417,2418,2419, 376,2420, 700, -2421,2422,2423, 258, 768,1316,2424,1183,2425, 995, 608,2426,2427,2428,2429, 221, -2430,2431,2432,2433,2434,2435,2436,2437, 195, 323, 726, 188, 897, 983,1317, 377, - 644,1050, 879,2438, 452,2439,2440,2441,2442,2443,2444, 914,2445,2446,2447,2448, - 915, 489,2449,1514,1184,2450,2451, 515, 64, 427, 495,2452, 583,2453, 483, 485, -1038, 562, 213,1515, 748, 666,2454,2455,2456,2457, 334,2458, 780, 996,1008, 705, -1243,2459,2460,2461,2462,2463, 114,2464, 493,1146, 366, 163,1516, 961,1104,2465, - 291,2466,1318,1105,2467,1517, 365,2468, 355, 951,1244,2469,1319,2470, 631,2471, -2472, 218,1320, 364, 320, 756,1518,1519,1321,1520,1322,2473,2474,2475,2476, 997, -2477,2478,2479,2480, 665,1185,2481, 916,1521,2482,2483,2484, 584, 684,2485,2486, - 797,2487,1051,1186,2488,2489,2490,1522,2491,2492, 370,2493,1039,1187, 65,2494, - 434, 205, 463,1188,2495, 125, 812, 391, 402, 826, 699, 286, 398, 155, 781, 771, - 585,2496, 590, 505,1073,2497, 599, 244, 219, 917,1018, 952, 646,1523,2498,1323, -2499,2500, 49, 984, 354, 741,2501, 625,2502,1324,2503,1019, 190, 357, 757, 491, - 95, 782, 868,2504,2505,2506,2507,2508,2509, 134,1524,1074, 422,1525, 898,2510, - 161,2511,2512,2513,2514, 769,2515,1526,2516,2517, 411,1325,2518, 472,1527,2519, -2520,2521,2522,2523,2524, 985,2525,2526,2527,2528,2529,2530, 764,2531,1245,2532, -2533, 25, 204, 311,2534, 496,2535,1052,2536,2537,2538,2539,2540,2541,2542, 199, - 704, 504, 468, 758, 657,1528, 196, 44, 839,1246, 272, 750,2543, 765, 862,2544, -2545,1326,2546, 132, 615, 933,2547, 732,2548,2549,2550,1189,1529,2551, 283,1247, -1053, 607, 929,2552,2553,2554, 930, 183, 872, 616,1040,1147,2555,1148,1020, 441, - 249,1075,2556,2557,2558, 466, 743,2559,2560,2561, 92, 514, 426, 420, 526,2562, -2563,2564,2565,2566,2567,2568, 185,2569,2570,2571,2572, 776,1530, 658,2573, 362, -2574, 361, 922,1076, 793,2575,2576,2577,2578,2579,2580,1531, 251,2581,2582,2583, -2584,1532, 54, 612, 237,1327,2585,2586, 275, 408, 647, 111,2587,1533,1106, 465, - 3, 458, 9, 38,2588, 107, 110, 890, 209, 26, 737, 498,2589,1534,2590, 431, - 202, 88,1535, 356, 287,1107, 660,1149,2591, 381,1536, 986,1150, 445,1248,1151, - 974,2592,2593, 846,2594, 446, 953, 184,1249,1250, 727,2595, 923, 193, 883,2596, -2597,2598, 102, 324, 539, 817,2599, 421,1041,2600, 832,2601, 94, 175, 197, 406, -2602, 459,2603,2604,2605,2606,2607, 330, 555,2608,2609,2610, 706,1108, 389,2611, -2612,2613,2614, 233,2615, 833, 558, 931, 954,1251,2616,2617,1537, 546,2618,2619, -1009,2620,2621,2622,1538, 690,1328,2623, 955,2624,1539,2625,2626, 772,2627,2628, -2629,2630,2631, 924, 648, 863, 603,2632,2633, 934,1540, 864, 865,2634, 642,1042, - 670,1190,2635,2636,2637,2638, 168,2639, 652, 873, 542,1054,1541,2640,2641,2642, # 512, 256 -) - diff --git a/pipenv/vendor/chardet/euckrprober.py b/pipenv/vendor/chardet/euckrprober.py deleted file mode 100644 index 345a060d..00000000 --- a/pipenv/vendor/chardet/euckrprober.py +++ /dev/null @@ -1,47 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is mozilla.org code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .mbcharsetprober import MultiByteCharSetProber -from .codingstatemachine import CodingStateMachine -from .chardistribution import EUCKRDistributionAnalysis -from .mbcssm import EUCKR_SM_MODEL - - -class EUCKRProber(MultiByteCharSetProber): - def __init__(self): - super(EUCKRProber, self).__init__() - self.coding_sm = CodingStateMachine(EUCKR_SM_MODEL) - self.distribution_analyzer = EUCKRDistributionAnalysis() - self.reset() - - @property - def charset_name(self): - return "EUC-KR" - - @property - def language(self): - return "Korean" diff --git a/pipenv/vendor/chardet/euctwfreq.py b/pipenv/vendor/chardet/euctwfreq.py deleted file mode 100644 index ed7a995a..00000000 --- a/pipenv/vendor/chardet/euctwfreq.py +++ /dev/null @@ -1,387 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -# EUCTW frequency table -# Converted from big5 work -# by Taiwan's Mandarin Promotion Council -# <http:#www.edu.tw:81/mandr/> - -# 128 --> 0.42261 -# 256 --> 0.57851 -# 512 --> 0.74851 -# 1024 --> 0.89384 -# 2048 --> 0.97583 -# -# Idea Distribution Ratio = 0.74851/(1-0.74851) =2.98 -# Random Distribution Ration = 512/(5401-512)=0.105 -# -# Typical Distribution Ratio about 25% of Ideal one, still much higher than RDR - -EUCTW_TYPICAL_DISTRIBUTION_RATIO = 0.75 - -# Char to FreqOrder table , -EUCTW_TABLE_SIZE = 5376 - -EUCTW_CHAR_TO_FREQ_ORDER = ( - 1,1800,1506, 255,1431, 198, 9, 82, 6,7310, 177, 202,3615,1256,2808, 110, # 2742 -3735, 33,3241, 261, 76, 44,2113, 16,2931,2184,1176, 659,3868, 26,3404,2643, # 2758 -1198,3869,3313,4060, 410,2211, 302, 590, 361,1963, 8, 204, 58,4296,7311,1931, # 2774 - 63,7312,7313, 317,1614, 75, 222, 159,4061,2412,1480,7314,3500,3068, 224,2809, # 2790 -3616, 3, 10,3870,1471, 29,2774,1135,2852,1939, 873, 130,3242,1123, 312,7315, # 2806 -4297,2051, 507, 252, 682,7316, 142,1914, 124, 206,2932, 34,3501,3173, 64, 604, # 2822 -7317,2494,1976,1977, 155,1990, 645, 641,1606,7318,3405, 337, 72, 406,7319, 80, # 2838 - 630, 238,3174,1509, 263, 939,1092,2644, 756,1440,1094,3406, 449, 69,2969, 591, # 2854 - 179,2095, 471, 115,2034,1843, 60, 50,2970, 134, 806,1868, 734,2035,3407, 180, # 2870 - 995,1607, 156, 537,2893, 688,7320, 319,1305, 779,2144, 514,2374, 298,4298, 359, # 2886 -2495, 90,2707,1338, 663, 11, 906,1099,2545, 20,2436, 182, 532,1716,7321, 732, # 2902 -1376,4062,1311,1420,3175, 25,2312,1056, 113, 399, 382,1949, 242,3408,2467, 529, # 2918 -3243, 475,1447,3617,7322, 117, 21, 656, 810,1297,2295,2329,3502,7323, 126,4063, # 2934 - 706, 456, 150, 613,4299, 71,1118,2036,4064, 145,3069, 85, 835, 486,2114,1246, # 2950 -1426, 428, 727,1285,1015, 800, 106, 623, 303,1281,7324,2127,2354, 347,3736, 221, # 2966 -3503,3110,7325,1955,1153,4065, 83, 296,1199,3070, 192, 624, 93,7326, 822,1897, # 2982 -2810,3111, 795,2064, 991,1554,1542,1592, 27, 43,2853, 859, 139,1456, 860,4300, # 2998 - 437, 712,3871, 164,2392,3112, 695, 211,3017,2096, 195,3872,1608,3504,3505,3618, # 3014 -3873, 234, 811,2971,2097,3874,2229,1441,3506,1615,2375, 668,2076,1638, 305, 228, # 3030 -1664,4301, 467, 415,7327, 262,2098,1593, 239, 108, 300, 200,1033, 512,1247,2077, # 3046 -7328,7329,2173,3176,3619,2673, 593, 845,1062,3244, 88,1723,2037,3875,1950, 212, # 3062 - 266, 152, 149, 468,1898,4066,4302, 77, 187,7330,3018, 37, 5,2972,7331,3876, # 3078 -7332,7333, 39,2517,4303,2894,3177,2078, 55, 148, 74,4304, 545, 483,1474,1029, # 3094 -1665, 217,1869,1531,3113,1104,2645,4067, 24, 172,3507, 900,3877,3508,3509,4305, # 3110 - 32,1408,2811,1312, 329, 487,2355,2247,2708, 784,2674, 4,3019,3314,1427,1788, # 3126 - 188, 109, 499,7334,3620,1717,1789, 888,1217,3020,4306,7335,3510,7336,3315,1520, # 3142 -3621,3878, 196,1034, 775,7337,7338, 929,1815, 249, 439, 38,7339,1063,7340, 794, # 3158 -3879,1435,2296, 46, 178,3245,2065,7341,2376,7342, 214,1709,4307, 804, 35, 707, # 3174 - 324,3622,1601,2546, 140, 459,4068,7343,7344,1365, 839, 272, 978,2257,2572,3409, # 3190 -2128,1363,3623,1423, 697, 100,3071, 48, 70,1231, 495,3114,2193,7345,1294,7346, # 3206 -2079, 462, 586,1042,3246, 853, 256, 988, 185,2377,3410,1698, 434,1084,7347,3411, # 3222 - 314,2615,2775,4308,2330,2331, 569,2280, 637,1816,2518, 757,1162,1878,1616,3412, # 3238 - 287,1577,2115, 768,4309,1671,2854,3511,2519,1321,3737, 909,2413,7348,4069, 933, # 3254 -3738,7349,2052,2356,1222,4310, 765,2414,1322, 786,4311,7350,1919,1462,1677,2895, # 3270 -1699,7351,4312,1424,2437,3115,3624,2590,3316,1774,1940,3413,3880,4070, 309,1369, # 3286 -1130,2812, 364,2230,1653,1299,3881,3512,3882,3883,2646, 525,1085,3021, 902,2000, # 3302 -1475, 964,4313, 421,1844,1415,1057,2281, 940,1364,3116, 376,4314,4315,1381, 7, # 3318 -2520, 983,2378, 336,1710,2675,1845, 321,3414, 559,1131,3022,2742,1808,1132,1313, # 3334 - 265,1481,1857,7352, 352,1203,2813,3247, 167,1089, 420,2814, 776, 792,1724,3513, # 3350 -4071,2438,3248,7353,4072,7354, 446, 229, 333,2743, 901,3739,1200,1557,4316,2647, # 3366 -1920, 395,2744,2676,3740,4073,1835, 125, 916,3178,2616,4317,7355,7356,3741,7357, # 3382 -7358,7359,4318,3117,3625,1133,2547,1757,3415,1510,2313,1409,3514,7360,2145, 438, # 3398 -2591,2896,2379,3317,1068, 958,3023, 461, 311,2855,2677,4074,1915,3179,4075,1978, # 3414 - 383, 750,2745,2617,4076, 274, 539, 385,1278,1442,7361,1154,1964, 384, 561, 210, # 3430 - 98,1295,2548,3515,7362,1711,2415,1482,3416,3884,2897,1257, 129,7363,3742, 642, # 3446 - 523,2776,2777,2648,7364, 141,2231,1333, 68, 176, 441, 876, 907,4077, 603,2592, # 3462 - 710, 171,3417, 404, 549, 18,3118,2393,1410,3626,1666,7365,3516,4319,2898,4320, # 3478 -7366,2973, 368,7367, 146, 366, 99, 871,3627,1543, 748, 807,1586,1185, 22,2258, # 3494 - 379,3743,3180,7368,3181, 505,1941,2618,1991,1382,2314,7369, 380,2357, 218, 702, # 3510 -1817,1248,3418,3024,3517,3318,3249,7370,2974,3628, 930,3250,3744,7371, 59,7372, # 3526 - 585, 601,4078, 497,3419,1112,1314,4321,1801,7373,1223,1472,2174,7374, 749,1836, # 3542 - 690,1899,3745,1772,3885,1476, 429,1043,1790,2232,2116, 917,4079, 447,1086,1629, # 3558 -7375, 556,7376,7377,2020,1654, 844,1090, 105, 550, 966,1758,2815,1008,1782, 686, # 3574 -1095,7378,2282, 793,1602,7379,3518,2593,4322,4080,2933,2297,4323,3746, 980,2496, # 3590 - 544, 353, 527,4324, 908,2678,2899,7380, 381,2619,1942,1348,7381,1341,1252, 560, # 3606 -3072,7382,3420,2856,7383,2053, 973, 886,2080, 143,4325,7384,7385, 157,3886, 496, # 3622 -4081, 57, 840, 540,2038,4326,4327,3421,2117,1445, 970,2259,1748,1965,2081,4082, # 3638 -3119,1234,1775,3251,2816,3629, 773,1206,2129,1066,2039,1326,3887,1738,1725,4083, # 3654 - 279,3120, 51,1544,2594, 423,1578,2130,2066, 173,4328,1879,7386,7387,1583, 264, # 3670 - 610,3630,4329,2439, 280, 154,7388,7389,7390,1739, 338,1282,3073, 693,2857,1411, # 3686 -1074,3747,2440,7391,4330,7392,7393,1240, 952,2394,7394,2900,1538,2679, 685,1483, # 3702 -4084,2468,1436, 953,4085,2054,4331, 671,2395, 79,4086,2441,3252, 608, 567,2680, # 3718 -3422,4087,4088,1691, 393,1261,1791,2396,7395,4332,7396,7397,7398,7399,1383,1672, # 3734 -3748,3182,1464, 522,1119, 661,1150, 216, 675,4333,3888,1432,3519, 609,4334,2681, # 3750 -2397,7400,7401,7402,4089,3025, 0,7403,2469, 315, 231,2442, 301,3319,4335,2380, # 3766 -7404, 233,4090,3631,1818,4336,4337,7405, 96,1776,1315,2082,7406, 257,7407,1809, # 3782 -3632,2709,1139,1819,4091,2021,1124,2163,2778,1777,2649,7408,3074, 363,1655,3183, # 3798 -7409,2975,7410,7411,7412,3889,1567,3890, 718, 103,3184, 849,1443, 341,3320,2934, # 3814 -1484,7413,1712, 127, 67, 339,4092,2398, 679,1412, 821,7414,7415, 834, 738, 351, # 3830 -2976,2146, 846, 235,1497,1880, 418,1992,3749,2710, 186,1100,2147,2746,3520,1545, # 3846 -1355,2935,2858,1377, 583,3891,4093,2573,2977,7416,1298,3633,1078,2549,3634,2358, # 3862 - 78,3750,3751, 267,1289,2099,2001,1594,4094, 348, 369,1274,2194,2175,1837,4338, # 3878 -1820,2817,3635,2747,2283,2002,4339,2936,2748, 144,3321, 882,4340,3892,2749,3423, # 3894 -4341,2901,7417,4095,1726, 320,7418,3893,3026, 788,2978,7419,2818,1773,1327,2859, # 3910 -3894,2819,7420,1306,4342,2003,1700,3752,3521,2359,2650, 787,2022, 506, 824,3636, # 3926 - 534, 323,4343,1044,3322,2023,1900, 946,3424,7421,1778,1500,1678,7422,1881,4344, # 3942 - 165, 243,4345,3637,2521, 123, 683,4096, 764,4346, 36,3895,1792, 589,2902, 816, # 3958 - 626,1667,3027,2233,1639,1555,1622,3753,3896,7423,3897,2860,1370,1228,1932, 891, # 3974 -2083,2903, 304,4097,7424, 292,2979,2711,3522, 691,2100,4098,1115,4347, 118, 662, # 3990 -7425, 611,1156, 854,2381,1316,2861, 2, 386, 515,2904,7426,7427,3253, 868,2234, # 4006 -1486, 855,2651, 785,2212,3028,7428,1040,3185,3523,7429,3121, 448,7430,1525,7431, # 4022 -2164,4348,7432,3754,7433,4099,2820,3524,3122, 503, 818,3898,3123,1568, 814, 676, # 4038 -1444, 306,1749,7434,3755,1416,1030, 197,1428, 805,2821,1501,4349,7435,7436,7437, # 4054 -1993,7438,4350,7439,7440,2195, 13,2779,3638,2980,3124,1229,1916,7441,3756,2131, # 4070 -7442,4100,4351,2399,3525,7443,2213,1511,1727,1120,7444,7445, 646,3757,2443, 307, # 4086 -7446,7447,1595,3186,7448,7449,7450,3639,1113,1356,3899,1465,2522,2523,7451, 519, # 4102 -7452, 128,2132, 92,2284,1979,7453,3900,1512, 342,3125,2196,7454,2780,2214,1980, # 4118 -3323,7455, 290,1656,1317, 789, 827,2360,7456,3758,4352, 562, 581,3901,7457, 401, # 4134 -4353,2248, 94,4354,1399,2781,7458,1463,2024,4355,3187,1943,7459, 828,1105,4101, # 4150 -1262,1394,7460,4102, 605,4356,7461,1783,2862,7462,2822, 819,2101, 578,2197,2937, # 4166 -7463,1502, 436,3254,4103,3255,2823,3902,2905,3425,3426,7464,2712,2315,7465,7466, # 4182 -2332,2067, 23,4357, 193, 826,3759,2102, 699,1630,4104,3075, 390,1793,1064,3526, # 4198 -7467,1579,3076,3077,1400,7468,4105,1838,1640,2863,7469,4358,4359, 137,4106, 598, # 4214 -3078,1966, 780, 104, 974,2938,7470, 278, 899, 253, 402, 572, 504, 493,1339,7471, # 4230 -3903,1275,4360,2574,2550,7472,3640,3029,3079,2249, 565,1334,2713, 863, 41,7473, # 4246 -7474,4361,7475,1657,2333, 19, 463,2750,4107, 606,7476,2981,3256,1087,2084,1323, # 4262 -2652,2982,7477,1631,1623,1750,4108,2682,7478,2864, 791,2714,2653,2334, 232,2416, # 4278 -7479,2983,1498,7480,2654,2620, 755,1366,3641,3257,3126,2025,1609, 119,1917,3427, # 4294 - 862,1026,4109,7481,3904,3760,4362,3905,4363,2260,1951,2470,7482,1125, 817,4110, # 4310 -4111,3906,1513,1766,2040,1487,4112,3030,3258,2824,3761,3127,7483,7484,1507,7485, # 4326 -2683, 733, 40,1632,1106,2865, 345,4113, 841,2524, 230,4364,2984,1846,3259,3428, # 4342 -7486,1263, 986,3429,7487, 735, 879, 254,1137, 857, 622,1300,1180,1388,1562,3907, # 4358 -3908,2939, 967,2751,2655,1349, 592,2133,1692,3324,2985,1994,4114,1679,3909,1901, # 4374 -2185,7488, 739,3642,2715,1296,1290,7489,4115,2198,2199,1921,1563,2595,2551,1870, # 4390 -2752,2986,7490, 435,7491, 343,1108, 596, 17,1751,4365,2235,3430,3643,7492,4366, # 4406 - 294,3527,2940,1693, 477, 979, 281,2041,3528, 643,2042,3644,2621,2782,2261,1031, # 4422 -2335,2134,2298,3529,4367, 367,1249,2552,7493,3530,7494,4368,1283,3325,2004, 240, # 4438 -1762,3326,4369,4370, 836,1069,3128, 474,7495,2148,2525, 268,3531,7496,3188,1521, # 4454 -1284,7497,1658,1546,4116,7498,3532,3533,7499,4117,3327,2684,1685,4118, 961,1673, # 4470 -2622, 190,2005,2200,3762,4371,4372,7500, 570,2497,3645,1490,7501,4373,2623,3260, # 4486 -1956,4374, 584,1514, 396,1045,1944,7502,4375,1967,2444,7503,7504,4376,3910, 619, # 4502 -7505,3129,3261, 215,2006,2783,2553,3189,4377,3190,4378, 763,4119,3763,4379,7506, # 4518 -7507,1957,1767,2941,3328,3646,1174, 452,1477,4380,3329,3130,7508,2825,1253,2382, # 4534 -2186,1091,2285,4120, 492,7509, 638,1169,1824,2135,1752,3911, 648, 926,1021,1324, # 4550 -4381, 520,4382, 997, 847,1007, 892,4383,3764,2262,1871,3647,7510,2400,1784,4384, # 4566 -1952,2942,3080,3191,1728,4121,2043,3648,4385,2007,1701,3131,1551, 30,2263,4122, # 4582 -7511,2026,4386,3534,7512, 501,7513,4123, 594,3431,2165,1821,3535,3432,3536,3192, # 4598 - 829,2826,4124,7514,1680,3132,1225,4125,7515,3262,4387,4126,3133,2336,7516,4388, # 4614 -4127,7517,3912,3913,7518,1847,2383,2596,3330,7519,4389, 374,3914, 652,4128,4129, # 4630 - 375,1140, 798,7520,7521,7522,2361,4390,2264, 546,1659, 138,3031,2445,4391,7523, # 4646 -2250, 612,1848, 910, 796,3765,1740,1371, 825,3766,3767,7524,2906,2554,7525, 692, # 4662 - 444,3032,2624, 801,4392,4130,7526,1491, 244,1053,3033,4131,4132, 340,7527,3915, # 4678 -1041,2987, 293,1168, 87,1357,7528,1539, 959,7529,2236, 721, 694,4133,3768, 219, # 4694 -1478, 644,1417,3331,2656,1413,1401,1335,1389,3916,7530,7531,2988,2362,3134,1825, # 4710 - 730,1515, 184,2827, 66,4393,7532,1660,2943, 246,3332, 378,1457, 226,3433, 975, # 4726 -3917,2944,1264,3537, 674, 696,7533, 163,7534,1141,2417,2166, 713,3538,3333,4394, # 4742 -3918,7535,7536,1186, 15,7537,1079,1070,7538,1522,3193,3539, 276,1050,2716, 758, # 4758 -1126, 653,2945,3263,7539,2337, 889,3540,3919,3081,2989, 903,1250,4395,3920,3434, # 4774 -3541,1342,1681,1718, 766,3264, 286, 89,2946,3649,7540,1713,7541,2597,3334,2990, # 4790 -7542,2947,2215,3194,2866,7543,4396,2498,2526, 181, 387,1075,3921, 731,2187,3335, # 4806 -7544,3265, 310, 313,3435,2299, 770,4134, 54,3034, 189,4397,3082,3769,3922,7545, # 4822 -1230,1617,1849, 355,3542,4135,4398,3336, 111,4136,3650,1350,3135,3436,3035,4137, # 4838 -2149,3266,3543,7546,2784,3923,3924,2991, 722,2008,7547,1071, 247,1207,2338,2471, # 4854 -1378,4399,2009, 864,1437,1214,4400, 373,3770,1142,2216, 667,4401, 442,2753,2555, # 4870 -3771,3925,1968,4138,3267,1839, 837, 170,1107, 934,1336,1882,7548,7549,2118,4139, # 4886 -2828, 743,1569,7550,4402,4140, 582,2384,1418,3437,7551,1802,7552, 357,1395,1729, # 4902 -3651,3268,2418,1564,2237,7553,3083,3772,1633,4403,1114,2085,4141,1532,7554, 482, # 4918 -2446,4404,7555,7556,1492, 833,1466,7557,2717,3544,1641,2829,7558,1526,1272,3652, # 4934 -4142,1686,1794, 416,2556,1902,1953,1803,7559,3773,2785,3774,1159,2316,7560,2867, # 4950 -4405,1610,1584,3036,2419,2754, 443,3269,1163,3136,7561,7562,3926,7563,4143,2499, # 4966 -3037,4406,3927,3137,2103,1647,3545,2010,1872,4144,7564,4145, 431,3438,7565, 250, # 4982 - 97, 81,4146,7566,1648,1850,1558, 160, 848,7567, 866, 740,1694,7568,2201,2830, # 4998 -3195,4147,4407,3653,1687, 950,2472, 426, 469,3196,3654,3655,3928,7569,7570,1188, # 5014 - 424,1995, 861,3546,4148,3775,2202,2685, 168,1235,3547,4149,7571,2086,1674,4408, # 5030 -3337,3270, 220,2557,1009,7572,3776, 670,2992, 332,1208, 717,7573,7574,3548,2447, # 5046 -3929,3338,7575, 513,7576,1209,2868,3339,3138,4409,1080,7577,7578,7579,7580,2527, # 5062 -3656,3549, 815,1587,3930,3931,7581,3550,3439,3777,1254,4410,1328,3038,1390,3932, # 5078 -1741,3933,3778,3934,7582, 236,3779,2448,3271,7583,7584,3657,3780,1273,3781,4411, # 5094 -7585, 308,7586,4412, 245,4413,1851,2473,1307,2575, 430, 715,2136,2449,7587, 270, # 5110 - 199,2869,3935,7588,3551,2718,1753, 761,1754, 725,1661,1840,4414,3440,3658,7589, # 5126 -7590, 587, 14,3272, 227,2598, 326, 480,2265, 943,2755,3552, 291, 650,1883,7591, # 5142 -1702,1226, 102,1547, 62,3441, 904,4415,3442,1164,4150,7592,7593,1224,1548,2756, # 5158 - 391, 498,1493,7594,1386,1419,7595,2055,1177,4416, 813, 880,1081,2363, 566,1145, # 5174 -4417,2286,1001,1035,2558,2599,2238, 394,1286,7596,7597,2068,7598, 86,1494,1730, # 5190 -3936, 491,1588, 745, 897,2948, 843,3340,3937,2757,2870,3273,1768, 998,2217,2069, # 5206 - 397,1826,1195,1969,3659,2993,3341, 284,7599,3782,2500,2137,2119,1903,7600,3938, # 5222 -2150,3939,4151,1036,3443,1904, 114,2559,4152, 209,1527,7601,7602,2949,2831,2625, # 5238 -2385,2719,3139, 812,2560,7603,3274,7604,1559, 737,1884,3660,1210, 885, 28,2686, # 5254 -3553,3783,7605,4153,1004,1779,4418,7606, 346,1981,2218,2687,4419,3784,1742, 797, # 5270 -1642,3940,1933,1072,1384,2151, 896,3941,3275,3661,3197,2871,3554,7607,2561,1958, # 5286 -4420,2450,1785,7608,7609,7610,3942,4154,1005,1308,3662,4155,2720,4421,4422,1528, # 5302 -2600, 161,1178,4156,1982, 987,4423,1101,4157, 631,3943,1157,3198,2420,1343,1241, # 5318 -1016,2239,2562, 372, 877,2339,2501,1160, 555,1934, 911,3944,7611, 466,1170, 169, # 5334 -1051,2907,2688,3663,2474,2994,1182,2011,2563,1251,2626,7612, 992,2340,3444,1540, # 5350 -2721,1201,2070,2401,1996,2475,7613,4424, 528,1922,2188,1503,1873,1570,2364,3342, # 5366 -3276,7614, 557,1073,7615,1827,3445,2087,2266,3140,3039,3084, 767,3085,2786,4425, # 5382 -1006,4158,4426,2341,1267,2176,3664,3199, 778,3945,3200,2722,1597,2657,7616,4427, # 5398 -7617,3446,7618,7619,7620,3277,2689,1433,3278, 131, 95,1504,3946, 723,4159,3141, # 5414 -1841,3555,2758,2189,3947,2027,2104,3665,7621,2995,3948,1218,7622,3343,3201,3949, # 5430 -4160,2576, 248,1634,3785, 912,7623,2832,3666,3040,3786, 654, 53,7624,2996,7625, # 5446 -1688,4428, 777,3447,1032,3950,1425,7626, 191, 820,2120,2833, 971,4429, 931,3202, # 5462 - 135, 664, 783,3787,1997, 772,2908,1935,3951,3788,4430,2909,3203, 282,2723, 640, # 5478 -1372,3448,1127, 922, 325,3344,7627,7628, 711,2044,7629,7630,3952,2219,2787,1936, # 5494 -3953,3345,2220,2251,3789,2300,7631,4431,3790,1258,3279,3954,3204,2138,2950,3955, # 5510 -3956,7632,2221, 258,3205,4432, 101,1227,7633,3280,1755,7634,1391,3281,7635,2910, # 5526 -2056, 893,7636,7637,7638,1402,4161,2342,7639,7640,3206,3556,7641,7642, 878,1325, # 5542 -1780,2788,4433, 259,1385,2577, 744,1183,2267,4434,7643,3957,2502,7644, 684,1024, # 5558 -4162,7645, 472,3557,3449,1165,3282,3958,3959, 322,2152, 881, 455,1695,1152,1340, # 5574 - 660, 554,2153,4435,1058,4436,4163, 830,1065,3346,3960,4437,1923,7646,1703,1918, # 5590 -7647, 932,2268, 122,7648,4438, 947, 677,7649,3791,2627, 297,1905,1924,2269,4439, # 5606 -2317,3283,7650,7651,4164,7652,4165, 84,4166, 112, 989,7653, 547,1059,3961, 701, # 5622 -3558,1019,7654,4167,7655,3450, 942, 639, 457,2301,2451, 993,2951, 407, 851, 494, # 5638 -4440,3347, 927,7656,1237,7657,2421,3348, 573,4168, 680, 921,2911,1279,1874, 285, # 5654 - 790,1448,1983, 719,2167,7658,7659,4441,3962,3963,1649,7660,1541, 563,7661,1077, # 5670 -7662,3349,3041,3451, 511,2997,3964,3965,3667,3966,1268,2564,3350,3207,4442,4443, # 5686 -7663, 535,1048,1276,1189,2912,2028,3142,1438,1373,2834,2952,1134,2012,7664,4169, # 5702 -1238,2578,3086,1259,7665, 700,7666,2953,3143,3668,4170,7667,4171,1146,1875,1906, # 5718 -4444,2601,3967, 781,2422, 132,1589, 203, 147, 273,2789,2402, 898,1786,2154,3968, # 5734 -3969,7668,3792,2790,7669,7670,4445,4446,7671,3208,7672,1635,3793, 965,7673,1804, # 5750 -2690,1516,3559,1121,1082,1329,3284,3970,1449,3794, 65,1128,2835,2913,2759,1590, # 5766 -3795,7674,7675, 12,2658, 45, 976,2579,3144,4447, 517,2528,1013,1037,3209,7676, # 5782 -3796,2836,7677,3797,7678,3452,7679,2602, 614,1998,2318,3798,3087,2724,2628,7680, # 5798 -2580,4172, 599,1269,7681,1810,3669,7682,2691,3088, 759,1060, 489,1805,3351,3285, # 5814 -1358,7683,7684,2386,1387,1215,2629,2252, 490,7685,7686,4173,1759,2387,2343,7687, # 5830 -4448,3799,1907,3971,2630,1806,3210,4449,3453,3286,2760,2344, 874,7688,7689,3454, # 5846 -3670,1858, 91,2914,3671,3042,3800,4450,7690,3145,3972,2659,7691,3455,1202,1403, # 5862 -3801,2954,2529,1517,2503,4451,3456,2504,7692,4452,7693,2692,1885,1495,1731,3973, # 5878 -2365,4453,7694,2029,7695,7696,3974,2693,1216, 237,2581,4174,2319,3975,3802,4454, # 5894 -4455,2694,3560,3457, 445,4456,7697,7698,7699,7700,2761, 61,3976,3672,1822,3977, # 5910 -7701, 687,2045, 935, 925, 405,2660, 703,1096,1859,2725,4457,3978,1876,1367,2695, # 5926 -3352, 918,2105,1781,2476, 334,3287,1611,1093,4458, 564,3146,3458,3673,3353, 945, # 5942 -2631,2057,4459,7702,1925, 872,4175,7703,3459,2696,3089, 349,4176,3674,3979,4460, # 5958 -3803,4177,3675,2155,3980,4461,4462,4178,4463,2403,2046, 782,3981, 400, 251,4179, # 5974 -1624,7704,7705, 277,3676, 299,1265, 476,1191,3804,2121,4180,4181,1109, 205,7706, # 5990 -2582,1000,2156,3561,1860,7707,7708,7709,4464,7710,4465,2565, 107,2477,2157,3982, # 6006 -3460,3147,7711,1533, 541,1301, 158, 753,4182,2872,3562,7712,1696, 370,1088,4183, # 6022 -4466,3563, 579, 327, 440, 162,2240, 269,1937,1374,3461, 968,3043, 56,1396,3090, # 6038 -2106,3288,3354,7713,1926,2158,4467,2998,7714,3564,7715,7716,3677,4468,2478,7717, # 6054 -2791,7718,1650,4469,7719,2603,7720,7721,3983,2661,3355,1149,3356,3984,3805,3985, # 6070 -7722,1076, 49,7723, 951,3211,3289,3290, 450,2837, 920,7724,1811,2792,2366,4184, # 6086 -1908,1138,2367,3806,3462,7725,3212,4470,1909,1147,1518,2423,4471,3807,7726,4472, # 6102 -2388,2604, 260,1795,3213,7727,7728,3808,3291, 708,7729,3565,1704,7730,3566,1351, # 6118 -1618,3357,2999,1886, 944,4185,3358,4186,3044,3359,4187,7731,3678, 422, 413,1714, # 6134 -3292, 500,2058,2345,4188,2479,7732,1344,1910, 954,7733,1668,7734,7735,3986,2404, # 6150 -4189,3567,3809,4190,7736,2302,1318,2505,3091, 133,3092,2873,4473, 629, 31,2838, # 6166 -2697,3810,4474, 850, 949,4475,3987,2955,1732,2088,4191,1496,1852,7737,3988, 620, # 6182 -3214, 981,1242,3679,3360,1619,3680,1643,3293,2139,2452,1970,1719,3463,2168,7738, # 6198 -3215,7739,7740,3361,1828,7741,1277,4476,1565,2047,7742,1636,3568,3093,7743, 869, # 6214 -2839, 655,3811,3812,3094,3989,3000,3813,1310,3569,4477,7744,7745,7746,1733, 558, # 6230 -4478,3681, 335,1549,3045,1756,4192,3682,1945,3464,1829,1291,1192, 470,2726,2107, # 6246 -2793, 913,1054,3990,7747,1027,7748,3046,3991,4479, 982,2662,3362,3148,3465,3216, # 6262 -3217,1946,2794,7749, 571,4480,7750,1830,7751,3570,2583,1523,2424,7752,2089, 984, # 6278 -4481,3683,1959,7753,3684, 852, 923,2795,3466,3685, 969,1519, 999,2048,2320,1705, # 6294 -7754,3095, 615,1662, 151, 597,3992,2405,2321,1049, 275,4482,3686,4193, 568,3687, # 6310 -3571,2480,4194,3688,7755,2425,2270, 409,3218,7756,1566,2874,3467,1002, 769,2840, # 6326 - 194,2090,3149,3689,2222,3294,4195, 628,1505,7757,7758,1763,2177,3001,3993, 521, # 6342 -1161,2584,1787,2203,2406,4483,3994,1625,4196,4197, 412, 42,3096, 464,7759,2632, # 6358 -4484,3363,1760,1571,2875,3468,2530,1219,2204,3814,2633,2140,2368,4485,4486,3295, # 6374 -1651,3364,3572,7760,7761,3573,2481,3469,7762,3690,7763,7764,2271,2091, 460,7765, # 6390 -4487,7766,3002, 962, 588,3574, 289,3219,2634,1116, 52,7767,3047,1796,7768,7769, # 6406 -7770,1467,7771,1598,1143,3691,4198,1984,1734,1067,4488,1280,3365, 465,4489,1572, # 6422 - 510,7772,1927,2241,1812,1644,3575,7773,4490,3692,7774,7775,2663,1573,1534,7776, # 6438 -7777,4199, 536,1807,1761,3470,3815,3150,2635,7778,7779,7780,4491,3471,2915,1911, # 6454 -2796,7781,3296,1122, 377,3220,7782, 360,7783,7784,4200,1529, 551,7785,2059,3693, # 6470 -1769,2426,7786,2916,4201,3297,3097,2322,2108,2030,4492,1404, 136,1468,1479, 672, # 6486 -1171,3221,2303, 271,3151,7787,2762,7788,2049, 678,2727, 865,1947,4493,7789,2013, # 6502 -3995,2956,7790,2728,2223,1397,3048,3694,4494,4495,1735,2917,3366,3576,7791,3816, # 6518 - 509,2841,2453,2876,3817,7792,7793,3152,3153,4496,4202,2531,4497,2304,1166,1010, # 6534 - 552, 681,1887,7794,7795,2957,2958,3996,1287,1596,1861,3154, 358, 453, 736, 175, # 6550 - 478,1117, 905,1167,1097,7796,1853,1530,7797,1706,7798,2178,3472,2287,3695,3473, # 6566 -3577,4203,2092,4204,7799,3367,1193,2482,4205,1458,2190,2205,1862,1888,1421,3298, # 6582 -2918,3049,2179,3474, 595,2122,7800,3997,7801,7802,4206,1707,2636, 223,3696,1359, # 6598 - 751,3098, 183,3475,7803,2797,3003, 419,2369, 633, 704,3818,2389, 241,7804,7805, # 6614 -7806, 838,3004,3697,2272,2763,2454,3819,1938,2050,3998,1309,3099,2242,1181,7807, # 6630 -1136,2206,3820,2370,1446,4207,2305,4498,7808,7809,4208,1055,2605, 484,3698,7810, # 6646 -3999, 625,4209,2273,3368,1499,4210,4000,7811,4001,4211,3222,2274,2275,3476,7812, # 6662 -7813,2764, 808,2606,3699,3369,4002,4212,3100,2532, 526,3370,3821,4213, 955,7814, # 6678 -1620,4214,2637,2427,7815,1429,3700,1669,1831, 994, 928,7816,3578,1260,7817,7818, # 6694 -7819,1948,2288, 741,2919,1626,4215,2729,2455, 867,1184, 362,3371,1392,7820,7821, # 6710 -4003,4216,1770,1736,3223,2920,4499,4500,1928,2698,1459,1158,7822,3050,3372,2877, # 6726 -1292,1929,2506,2842,3701,1985,1187,2071,2014,2607,4217,7823,2566,2507,2169,3702, # 6742 -2483,3299,7824,3703,4501,7825,7826, 666,1003,3005,1022,3579,4218,7827,4502,1813, # 6758 -2253, 574,3822,1603, 295,1535, 705,3823,4219, 283, 858, 417,7828,7829,3224,4503, # 6774 -4504,3051,1220,1889,1046,2276,2456,4004,1393,1599, 689,2567, 388,4220,7830,2484, # 6790 - 802,7831,2798,3824,2060,1405,2254,7832,4505,3825,2109,1052,1345,3225,1585,7833, # 6806 - 809,7834,7835,7836, 575,2730,3477, 956,1552,1469,1144,2323,7837,2324,1560,2457, # 6822 -3580,3226,4005, 616,2207,3155,2180,2289,7838,1832,7839,3478,4506,7840,1319,3704, # 6838 -3705,1211,3581,1023,3227,1293,2799,7841,7842,7843,3826, 607,2306,3827, 762,2878, # 6854 -1439,4221,1360,7844,1485,3052,7845,4507,1038,4222,1450,2061,2638,4223,1379,4508, # 6870 -2585,7846,7847,4224,1352,1414,2325,2921,1172,7848,7849,3828,3829,7850,1797,1451, # 6886 -7851,7852,7853,7854,2922,4006,4007,2485,2346, 411,4008,4009,3582,3300,3101,4509, # 6902 -1561,2664,1452,4010,1375,7855,7856, 47,2959, 316,7857,1406,1591,2923,3156,7858, # 6918 -1025,2141,3102,3157, 354,2731, 884,2224,4225,2407, 508,3706, 726,3583, 996,2428, # 6934 -3584, 729,7859, 392,2191,1453,4011,4510,3707,7860,7861,2458,3585,2608,1675,2800, # 6950 - 919,2347,2960,2348,1270,4511,4012, 73,7862,7863, 647,7864,3228,2843,2255,1550, # 6966 -1346,3006,7865,1332, 883,3479,7866,7867,7868,7869,3301,2765,7870,1212, 831,1347, # 6982 -4226,4512,2326,3830,1863,3053, 720,3831,4513,4514,3832,7871,4227,7872,7873,4515, # 6998 -7874,7875,1798,4516,3708,2609,4517,3586,1645,2371,7876,7877,2924, 669,2208,2665, # 7014 -2429,7878,2879,7879,7880,1028,3229,7881,4228,2408,7882,2256,1353,7883,7884,4518, # 7030 -3158, 518,7885,4013,7886,4229,1960,7887,2142,4230,7888,7889,3007,2349,2350,3833, # 7046 - 516,1833,1454,4014,2699,4231,4519,2225,2610,1971,1129,3587,7890,2766,7891,2961, # 7062 -1422, 577,1470,3008,1524,3373,7892,7893, 432,4232,3054,3480,7894,2586,1455,2508, # 7078 -2226,1972,1175,7895,1020,2732,4015,3481,4520,7896,2733,7897,1743,1361,3055,3482, # 7094 -2639,4016,4233,4521,2290, 895, 924,4234,2170, 331,2243,3056, 166,1627,3057,1098, # 7110 -7898,1232,2880,2227,3374,4522, 657, 403,1196,2372, 542,3709,3375,1600,4235,3483, # 7126 -7899,4523,2767,3230, 576, 530,1362,7900,4524,2533,2666,3710,4017,7901, 842,3834, # 7142 -7902,2801,2031,1014,4018, 213,2700,3376, 665, 621,4236,7903,3711,2925,2430,7904, # 7158 -2431,3302,3588,3377,7905,4237,2534,4238,4525,3589,1682,4239,3484,1380,7906, 724, # 7174 -2277, 600,1670,7907,1337,1233,4526,3103,2244,7908,1621,4527,7909, 651,4240,7910, # 7190 -1612,4241,2611,7911,2844,7912,2734,2307,3058,7913, 716,2459,3059, 174,1255,2701, # 7206 -4019,3590, 548,1320,1398, 728,4020,1574,7914,1890,1197,3060,4021,7915,3061,3062, # 7222 -3712,3591,3713, 747,7916, 635,4242,4528,7917,7918,7919,4243,7920,7921,4529,7922, # 7238 -3378,4530,2432, 451,7923,3714,2535,2072,4244,2735,4245,4022,7924,1764,4531,7925, # 7254 -4246, 350,7926,2278,2390,2486,7927,4247,4023,2245,1434,4024, 488,4532, 458,4248, # 7270 -4025,3715, 771,1330,2391,3835,2568,3159,2159,2409,1553,2667,3160,4249,7928,2487, # 7286 -2881,2612,1720,2702,4250,3379,4533,7929,2536,4251,7930,3231,4252,2768,7931,2015, # 7302 -2736,7932,1155,1017,3716,3836,7933,3303,2308, 201,1864,4253,1430,7934,4026,7935, # 7318 -7936,7937,7938,7939,4254,1604,7940, 414,1865, 371,2587,4534,4535,3485,2016,3104, # 7334 -4536,1708, 960,4255, 887, 389,2171,1536,1663,1721,7941,2228,4027,2351,2926,1580, # 7350 -7942,7943,7944,1744,7945,2537,4537,4538,7946,4539,7947,2073,7948,7949,3592,3380, # 7366 -2882,4256,7950,4257,2640,3381,2802, 673,2703,2460, 709,3486,4028,3593,4258,7951, # 7382 -1148, 502, 634,7952,7953,1204,4540,3594,1575,4541,2613,3717,7954,3718,3105, 948, # 7398 -3232, 121,1745,3837,1110,7955,4259,3063,2509,3009,4029,3719,1151,1771,3838,1488, # 7414 -4030,1986,7956,2433,3487,7957,7958,2093,7959,4260,3839,1213,1407,2803, 531,2737, # 7430 -2538,3233,1011,1537,7960,2769,4261,3106,1061,7961,3720,3721,1866,2883,7962,2017, # 7446 - 120,4262,4263,2062,3595,3234,2309,3840,2668,3382,1954,4542,7963,7964,3488,1047, # 7462 -2704,1266,7965,1368,4543,2845, 649,3383,3841,2539,2738,1102,2846,2669,7966,7967, # 7478 -1999,7968,1111,3596,2962,7969,2488,3842,3597,2804,1854,3384,3722,7970,7971,3385, # 7494 -2410,2884,3304,3235,3598,7972,2569,7973,3599,2805,4031,1460, 856,7974,3600,7975, # 7510 -2885,2963,7976,2886,3843,7977,4264, 632,2510, 875,3844,1697,3845,2291,7978,7979, # 7526 -4544,3010,1239, 580,4545,4265,7980, 914, 936,2074,1190,4032,1039,2123,7981,7982, # 7542 -7983,3386,1473,7984,1354,4266,3846,7985,2172,3064,4033, 915,3305,4267,4268,3306, # 7558 -1605,1834,7986,2739, 398,3601,4269,3847,4034, 328,1912,2847,4035,3848,1331,4270, # 7574 -3011, 937,4271,7987,3602,4036,4037,3387,2160,4546,3388, 524, 742, 538,3065,1012, # 7590 -7988,7989,3849,2461,7990, 658,1103, 225,3850,7991,7992,4547,7993,4548,7994,3236, # 7606 -1243,7995,4038, 963,2246,4549,7996,2705,3603,3161,7997,7998,2588,2327,7999,4550, # 7622 -8000,8001,8002,3489,3307, 957,3389,2540,2032,1930,2927,2462, 870,2018,3604,1746, # 7638 -2770,2771,2434,2463,8003,3851,8004,3723,3107,3724,3490,3390,3725,8005,1179,3066, # 7654 -8006,3162,2373,4272,3726,2541,3163,3108,2740,4039,8007,3391,1556,2542,2292, 977, # 7670 -2887,2033,4040,1205,3392,8008,1765,3393,3164,2124,1271,1689, 714,4551,3491,8009, # 7686 -2328,3852, 533,4273,3605,2181, 617,8010,2464,3308,3492,2310,8011,8012,3165,8013, # 7702 -8014,3853,1987, 618, 427,2641,3493,3394,8015,8016,1244,1690,8017,2806,4274,4552, # 7718 -8018,3494,8019,8020,2279,1576, 473,3606,4275,3395, 972,8021,3607,8022,3067,8023, # 7734 -8024,4553,4554,8025,3727,4041,4042,8026, 153,4555, 356,8027,1891,2888,4276,2143, # 7750 - 408, 803,2352,8028,3854,8029,4277,1646,2570,2511,4556,4557,3855,8030,3856,4278, # 7766 -8031,2411,3396, 752,8032,8033,1961,2964,8034, 746,3012,2465,8035,4279,3728, 698, # 7782 -4558,1892,4280,3608,2543,4559,3609,3857,8036,3166,3397,8037,1823,1302,4043,2706, # 7798 -3858,1973,4281,8038,4282,3167, 823,1303,1288,1236,2848,3495,4044,3398, 774,3859, # 7814 -8039,1581,4560,1304,2849,3860,4561,8040,2435,2161,1083,3237,4283,4045,4284, 344, # 7830 -1173, 288,2311, 454,1683,8041,8042,1461,4562,4046,2589,8043,8044,4563, 985, 894, # 7846 -8045,3399,3168,8046,1913,2928,3729,1988,8047,2110,1974,8048,4047,8049,2571,1194, # 7862 - 425,8050,4564,3169,1245,3730,4285,8051,8052,2850,8053, 636,4565,1855,3861, 760, # 7878 -1799,8054,4286,2209,1508,4566,4048,1893,1684,2293,8055,8056,8057,4287,4288,2210, # 7894 - 479,8058,8059, 832,8060,4049,2489,8061,2965,2490,3731, 990,3109, 627,1814,2642, # 7910 -4289,1582,4290,2125,2111,3496,4567,8062, 799,4291,3170,8063,4568,2112,1737,3013, # 7926 -1018, 543, 754,4292,3309,1676,4569,4570,4050,8064,1489,8065,3497,8066,2614,2889, # 7942 -4051,8067,8068,2966,8069,8070,8071,8072,3171,4571,4572,2182,1722,8073,3238,3239, # 7958 -1842,3610,1715, 481, 365,1975,1856,8074,8075,1962,2491,4573,8076,2126,3611,3240, # 7974 - 433,1894,2063,2075,8077, 602,2741,8078,8079,8080,8081,8082,3014,1628,3400,8083, # 7990 -3172,4574,4052,2890,4575,2512,8084,2544,2772,8085,8086,8087,3310,4576,2891,8088, # 8006 -4577,8089,2851,4578,4579,1221,2967,4053,2513,8090,8091,8092,1867,1989,8093,8094, # 8022 -8095,1895,8096,8097,4580,1896,4054, 318,8098,2094,4055,4293,8099,8100, 485,8101, # 8038 - 938,3862, 553,2670, 116,8102,3863,3612,8103,3498,2671,2773,3401,3311,2807,8104, # 8054 -3613,2929,4056,1747,2930,2968,8105,8106, 207,8107,8108,2672,4581,2514,8109,3015, # 8070 - 890,3614,3864,8110,1877,3732,3402,8111,2183,2353,3403,1652,8112,8113,8114, 941, # 8086 -2294, 208,3499,4057,2019, 330,4294,3865,2892,2492,3733,4295,8115,8116,8117,8118, # 8102 -) - diff --git a/pipenv/vendor/chardet/euctwprober.py b/pipenv/vendor/chardet/euctwprober.py deleted file mode 100644 index 35669cc4..00000000 --- a/pipenv/vendor/chardet/euctwprober.py +++ /dev/null @@ -1,46 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is mozilla.org code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .mbcharsetprober import MultiByteCharSetProber -from .codingstatemachine import CodingStateMachine -from .chardistribution import EUCTWDistributionAnalysis -from .mbcssm import EUCTW_SM_MODEL - -class EUCTWProber(MultiByteCharSetProber): - def __init__(self): - super(EUCTWProber, self).__init__() - self.coding_sm = CodingStateMachine(EUCTW_SM_MODEL) - self.distribution_analyzer = EUCTWDistributionAnalysis() - self.reset() - - @property - def charset_name(self): - return "EUC-TW" - - @property - def language(self): - return "Taiwan" diff --git a/pipenv/vendor/chardet/gb2312freq.py b/pipenv/vendor/chardet/gb2312freq.py deleted file mode 100644 index 697837bd..00000000 --- a/pipenv/vendor/chardet/gb2312freq.py +++ /dev/null @@ -1,283 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -# GB2312 most frequently used character table -# -# Char to FreqOrder table , from hz6763 - -# 512 --> 0.79 -- 0.79 -# 1024 --> 0.92 -- 0.13 -# 2048 --> 0.98 -- 0.06 -# 6768 --> 1.00 -- 0.02 -# -# Ideal Distribution Ratio = 0.79135/(1-0.79135) = 3.79 -# Random Distribution Ration = 512 / (3755 - 512) = 0.157 -# -# Typical Distribution Ratio about 25% of Ideal one, still much higher that RDR - -GB2312_TYPICAL_DISTRIBUTION_RATIO = 0.9 - -GB2312_TABLE_SIZE = 3760 - -GB2312_CHAR_TO_FREQ_ORDER = ( -1671, 749,1443,2364,3924,3807,2330,3921,1704,3463,2691,1511,1515, 572,3191,2205, -2361, 224,2558, 479,1711, 963,3162, 440,4060,1905,2966,2947,3580,2647,3961,3842, -2204, 869,4207, 970,2678,5626,2944,2956,1479,4048, 514,3595, 588,1346,2820,3409, - 249,4088,1746,1873,2047,1774, 581,1813, 358,1174,3590,1014,1561,4844,2245, 670, -1636,3112, 889,1286, 953, 556,2327,3060,1290,3141, 613, 185,3477,1367, 850,3820, -1715,2428,2642,2303,2732,3041,2562,2648,3566,3946,1349, 388,3098,2091,1360,3585, - 152,1687,1539, 738,1559, 59,1232,2925,2267,1388,1249,1741,1679,2960, 151,1566, -1125,1352,4271, 924,4296, 385,3166,4459, 310,1245,2850, 70,3285,2729,3534,3575, -2398,3298,3466,1960,2265, 217,3647, 864,1909,2084,4401,2773,1010,3269,5152, 853, -3051,3121,1244,4251,1895, 364,1499,1540,2313,1180,3655,2268, 562, 715,2417,3061, - 544, 336,3768,2380,1752,4075, 950, 280,2425,4382, 183,2759,3272, 333,4297,2155, -1688,2356,1444,1039,4540, 736,1177,3349,2443,2368,2144,2225, 565, 196,1482,3406, - 927,1335,4147, 692, 878,1311,1653,3911,3622,1378,4200,1840,2969,3149,2126,1816, -2534,1546,2393,2760, 737,2494, 13, 447, 245,2747, 38,2765,2129,2589,1079, 606, - 360, 471,3755,2890, 404, 848, 699,1785,1236, 370,2221,1023,3746,2074,2026,2023, -2388,1581,2119, 812,1141,3091,2536,1519, 804,2053, 406,1596,1090, 784, 548,4414, -1806,2264,2936,1100, 343,4114,5096, 622,3358, 743,3668,1510,1626,5020,3567,2513, -3195,4115,5627,2489,2991, 24,2065,2697,1087,2719, 48,1634, 315, 68, 985,2052, - 198,2239,1347,1107,1439, 597,2366,2172, 871,3307, 919,2487,2790,1867, 236,2570, -1413,3794, 906,3365,3381,1701,1982,1818,1524,2924,1205, 616,2586,2072,2004, 575, - 253,3099, 32,1365,1182, 197,1714,2454,1201, 554,3388,3224,2748, 756,2587, 250, -2567,1507,1517,3529,1922,2761,2337,3416,1961,1677,2452,2238,3153, 615, 911,1506, -1474,2495,1265,1906,2749,3756,3280,2161, 898,2714,1759,3450,2243,2444, 563, 26, -3286,2266,3769,3344,2707,3677, 611,1402, 531,1028,2871,4548,1375, 261,2948, 835, -1190,4134, 353, 840,2684,1900,3082,1435,2109,1207,1674, 329,1872,2781,4055,2686, -2104, 608,3318,2423,2957,2768,1108,3739,3512,3271,3985,2203,1771,3520,1418,2054, -1681,1153, 225,1627,2929, 162,2050,2511,3687,1954, 124,1859,2431,1684,3032,2894, - 585,4805,3969,2869,2704,2088,2032,2095,3656,2635,4362,2209, 256, 518,2042,2105, -3777,3657, 643,2298,1148,1779, 190, 989,3544, 414, 11,2135,2063,2979,1471, 403, -3678, 126, 770,1563, 671,2499,3216,2877, 600,1179, 307,2805,4937,1268,1297,2694, - 252,4032,1448,1494,1331,1394, 127,2256, 222,1647,1035,1481,3056,1915,1048, 873, -3651, 210, 33,1608,2516, 200,1520, 415, 102, 0,3389,1287, 817, 91,3299,2940, - 836,1814, 549,2197,1396,1669,2987,3582,2297,2848,4528,1070, 687, 20,1819, 121, -1552,1364,1461,1968,2617,3540,2824,2083, 177, 948,4938,2291, 110,4549,2066, 648, -3359,1755,2110,2114,4642,4845,1693,3937,3308,1257,1869,2123, 208,1804,3159,2992, -2531,2549,3361,2418,1350,2347,2800,2568,1291,2036,2680, 72, 842,1990, 212,1233, -1154,1586, 75,2027,3410,4900,1823,1337,2710,2676, 728,2810,1522,3026,4995, 157, - 755,1050,4022, 710, 785,1936,2194,2085,1406,2777,2400, 150,1250,4049,1206, 807, -1910, 534, 529,3309,1721,1660, 274, 39,2827, 661,2670,1578, 925,3248,3815,1094, -4278,4901,4252, 41,1150,3747,2572,2227,4501,3658,4902,3813,3357,3617,2884,2258, - 887, 538,4187,3199,1294,2439,3042,2329,2343,2497,1255, 107, 543,1527, 521,3478, -3568, 194,5062, 15, 961,3870,1241,1192,2664, 66,5215,3260,2111,1295,1127,2152, -3805,4135, 901,1164,1976, 398,1278, 530,1460, 748, 904,1054,1966,1426, 53,2909, - 509, 523,2279,1534, 536,1019, 239,1685, 460,2353, 673,1065,2401,3600,4298,2272, -1272,2363, 284,1753,3679,4064,1695, 81, 815,2677,2757,2731,1386, 859, 500,4221, -2190,2566, 757,1006,2519,2068,1166,1455, 337,2654,3203,1863,1682,1914,3025,1252, -1409,1366, 847, 714,2834,2038,3209, 964,2970,1901, 885,2553,1078,1756,3049, 301, -1572,3326, 688,2130,1996,2429,1805,1648,2930,3421,2750,3652,3088, 262,1158,1254, - 389,1641,1812, 526,1719, 923,2073,1073,1902, 468, 489,4625,1140, 857,2375,3070, -3319,2863, 380, 116,1328,2693,1161,2244, 273,1212,1884,2769,3011,1775,1142, 461, -3066,1200,2147,2212, 790, 702,2695,4222,1601,1058, 434,2338,5153,3640, 67,2360, -4099,2502, 618,3472,1329, 416,1132, 830,2782,1807,2653,3211,3510,1662, 192,2124, - 296,3979,1739,1611,3684, 23, 118, 324, 446,1239,1225, 293,2520,3814,3795,2535, -3116, 17,1074, 467,2692,2201, 387,2922, 45,1326,3055,1645,3659,2817, 958, 243, -1903,2320,1339,2825,1784,3289, 356, 576, 865,2315,2381,3377,3916,1088,3122,1713, -1655, 935, 628,4689,1034,1327, 441, 800, 720, 894,1979,2183,1528,5289,2702,1071, -4046,3572,2399,1571,3281, 79, 761,1103, 327, 134, 758,1899,1371,1615, 879, 442, - 215,2605,2579, 173,2048,2485,1057,2975,3317,1097,2253,3801,4263,1403,1650,2946, - 814,4968,3487,1548,2644,1567,1285, 2, 295,2636, 97, 946,3576, 832, 141,4257, -3273, 760,3821,3521,3156,2607, 949,1024,1733,1516,1803,1920,2125,2283,2665,3180, -1501,2064,3560,2171,1592, 803,3518,1416, 732,3897,4258,1363,1362,2458, 119,1427, - 602,1525,2608,1605,1639,3175, 694,3064, 10, 465, 76,2000,4846,4208, 444,3781, -1619,3353,2206,1273,3796, 740,2483, 320,1723,2377,3660,2619,1359,1137,1762,1724, -2345,2842,1850,1862, 912, 821,1866, 612,2625,1735,2573,3369,1093, 844, 89, 937, - 930,1424,3564,2413,2972,1004,3046,3019,2011, 711,3171,1452,4178, 428, 801,1943, - 432, 445,2811, 206,4136,1472, 730, 349, 73, 397,2802,2547, 998,1637,1167, 789, - 396,3217, 154,1218, 716,1120,1780,2819,4826,1931,3334,3762,2139,1215,2627, 552, -3664,3628,3232,1405,2383,3111,1356,2652,3577,3320,3101,1703, 640,1045,1370,1246, -4996, 371,1575,2436,1621,2210, 984,4033,1734,2638, 16,4529, 663,2755,3255,1451, -3917,2257,1253,1955,2234,1263,2951, 214,1229, 617, 485, 359,1831,1969, 473,2310, - 750,2058, 165, 80,2864,2419, 361,4344,2416,2479,1134, 796,3726,1266,2943, 860, -2715, 938, 390,2734,1313,1384, 248, 202, 877,1064,2854, 522,3907, 279,1602, 297, -2357, 395,3740, 137,2075, 944,4089,2584,1267,3802, 62,1533,2285, 178, 176, 780, -2440, 201,3707, 590, 478,1560,4354,2117,1075, 30, 74,4643,4004,1635,1441,2745, - 776,2596, 238,1077,1692,1912,2844, 605, 499,1742,3947, 241,3053, 980,1749, 936, -2640,4511,2582, 515,1543,2162,5322,2892,2993, 890,2148,1924, 665,1827,3581,1032, - 968,3163, 339,1044,1896, 270, 583,1791,1720,4367,1194,3488,3669, 43,2523,1657, - 163,2167, 290,1209,1622,3378, 550, 634,2508,2510, 695,2634,2384,2512,1476,1414, - 220,1469,2341,2138,2852,3183,2900,4939,2865,3502,1211,3680, 854,3227,1299,2976, -3172, 186,2998,1459, 443,1067,3251,1495, 321,1932,3054, 909, 753,1410,1828, 436, -2441,1119,1587,3164,2186,1258, 227, 231,1425,1890,3200,3942, 247, 959, 725,5254, -2741, 577,2158,2079, 929, 120, 174, 838,2813, 591,1115, 417,2024, 40,3240,1536, -1037, 291,4151,2354, 632,1298,2406,2500,3535,1825,1846,3451, 205,1171, 345,4238, - 18,1163, 811, 685,2208,1217, 425,1312,1508,1175,4308,2552,1033, 587,1381,3059, -2984,3482, 340,1316,4023,3972, 792,3176, 519, 777,4690, 918, 933,4130,2981,3741, - 90,3360,2911,2200,5184,4550, 609,3079,2030, 272,3379,2736, 363,3881,1130,1447, - 286, 779, 357,1169,3350,3137,1630,1220,2687,2391, 747,1277,3688,2618,2682,2601, -1156,3196,5290,4034,3102,1689,3596,3128, 874, 219,2783, 798, 508,1843,2461, 269, -1658,1776,1392,1913,2983,3287,2866,2159,2372, 829,4076, 46,4253,2873,1889,1894, - 915,1834,1631,2181,2318, 298, 664,2818,3555,2735, 954,3228,3117, 527,3511,2173, - 681,2712,3033,2247,2346,3467,1652, 155,2164,3382, 113,1994, 450, 899, 494, 994, -1237,2958,1875,2336,1926,3727, 545,1577,1550, 633,3473, 204,1305,3072,2410,1956, -2471, 707,2134, 841,2195,2196,2663,3843,1026,4940, 990,3252,4997, 368,1092, 437, -3212,3258,1933,1829, 675,2977,2893, 412, 943,3723,4644,3294,3283,2230,2373,5154, -2389,2241,2661,2323,1404,2524, 593, 787, 677,3008,1275,2059, 438,2709,2609,2240, -2269,2246,1446, 36,1568,1373,3892,1574,2301,1456,3962, 693,2276,5216,2035,1143, -2720,1919,1797,1811,2763,4137,2597,1830,1699,1488,1198,2090, 424,1694, 312,3634, -3390,4179,3335,2252,1214, 561,1059,3243,2295,2561, 975,5155,2321,2751,3772, 472, -1537,3282,3398,1047,2077,2348,2878,1323,3340,3076, 690,2906, 51, 369, 170,3541, -1060,2187,2688,3670,2541,1083,1683, 928,3918, 459, 109,4427, 599,3744,4286, 143, -2101,2730,2490, 82,1588,3036,2121, 281,1860, 477,4035,1238,2812,3020,2716,3312, -1530,2188,2055,1317, 843, 636,1808,1173,3495, 649, 181,1002, 147,3641,1159,2414, -3750,2289,2795, 813,3123,2610,1136,4368, 5,3391,4541,2174, 420, 429,1728, 754, -1228,2115,2219, 347,2223,2733, 735,1518,3003,2355,3134,1764,3948,3329,1888,2424, -1001,1234,1972,3321,3363,1672,1021,1450,1584, 226, 765, 655,2526,3404,3244,2302, -3665, 731, 594,2184, 319,1576, 621, 658,2656,4299,2099,3864,1279,2071,2598,2739, - 795,3086,3699,3908,1707,2352,2402,1382,3136,2475,1465,4847,3496,3865,1085,3004, -2591,1084, 213,2287,1963,3565,2250, 822, 793,4574,3187,1772,1789,3050, 595,1484, -1959,2770,1080,2650, 456, 422,2996, 940,3322,4328,4345,3092,2742, 965,2784, 739, -4124, 952,1358,2498,2949,2565, 332,2698,2378, 660,2260,2473,4194,3856,2919, 535, -1260,2651,1208,1428,1300,1949,1303,2942, 433,2455,2450,1251,1946, 614,1269, 641, -1306,1810,2737,3078,2912, 564,2365,1419,1415,1497,4460,2367,2185,1379,3005,1307, -3218,2175,1897,3063, 682,1157,4040,4005,1712,1160,1941,1399, 394, 402,2952,1573, -1151,2986,2404, 862, 299,2033,1489,3006, 346, 171,2886,3401,1726,2932, 168,2533, - 47,2507,1030,3735,1145,3370,1395,1318,1579,3609,4560,2857,4116,1457,2529,1965, - 504,1036,2690,2988,2405, 745,5871, 849,2397,2056,3081, 863,2359,3857,2096, 99, -1397,1769,2300,4428,1643,3455,1978,1757,3718,1440, 35,4879,3742,1296,4228,2280, - 160,5063,1599,2013, 166, 520,3479,1646,3345,3012, 490,1937,1545,1264,2182,2505, -1096,1188,1369,1436,2421,1667,2792,2460,1270,2122, 727,3167,2143, 806,1706,1012, -1800,3037, 960,2218,1882, 805, 139,2456,1139,1521, 851,1052,3093,3089, 342,2039, - 744,5097,1468,1502,1585,2087, 223, 939, 326,2140,2577, 892,2481,1623,4077, 982, -3708, 135,2131, 87,2503,3114,2326,1106, 876,1616, 547,2997,2831,2093,3441,4530, -4314, 9,3256,4229,4148, 659,1462,1986,1710,2046,2913,2231,4090,4880,5255,3392, -3274,1368,3689,4645,1477, 705,3384,3635,1068,1529,2941,1458,3782,1509, 100,1656, -2548, 718,2339, 408,1590,2780,3548,1838,4117,3719,1345,3530, 717,3442,2778,3220, -2898,1892,4590,3614,3371,2043,1998,1224,3483, 891, 635, 584,2559,3355, 733,1766, -1729,1172,3789,1891,2307, 781,2982,2271,1957,1580,5773,2633,2005,4195,3097,1535, -3213,1189,1934,5693,3262, 586,3118,1324,1598, 517,1564,2217,1868,1893,4445,3728, -2703,3139,1526,1787,1992,3882,2875,1549,1199,1056,2224,1904,2711,5098,4287, 338, -1993,3129,3489,2689,1809,2815,1997, 957,1855,3898,2550,3275,3057,1105,1319, 627, -1505,1911,1883,3526, 698,3629,3456,1833,1431, 746, 77,1261,2017,2296,1977,1885, - 125,1334,1600, 525,1798,1109,2222,1470,1945, 559,2236,1186,3443,2476,1929,1411, -2411,3135,1777,3372,2621,1841,1613,3229, 668,1430,1839,2643,2916, 195,1989,2671, -2358,1387, 629,3205,2293,5256,4439, 123,1310, 888,1879,4300,3021,3605,1003,1162, -3192,2910,2010, 140,2395,2859, 55,1082,2012,2901, 662, 419,2081,1438, 680,2774, -4654,3912,1620,1731,1625,5035,4065,2328, 512,1344, 802,5443,2163,2311,2537, 524, -3399, 98,1155,2103,1918,2606,3925,2816,1393,2465,1504,3773,2177,3963,1478,4346, - 180,1113,4655,3461,2028,1698, 833,2696,1235,1322,1594,4408,3623,3013,3225,2040, -3022, 541,2881, 607,3632,2029,1665,1219, 639,1385,1686,1099,2803,3231,1938,3188, -2858, 427, 676,2772,1168,2025, 454,3253,2486,3556, 230,1950, 580, 791,1991,1280, -1086,1974,2034, 630, 257,3338,2788,4903,1017, 86,4790, 966,2789,1995,1696,1131, - 259,3095,4188,1308, 179,1463,5257, 289,4107,1248, 42,3413,1725,2288, 896,1947, - 774,4474,4254, 604,3430,4264, 392,2514,2588, 452, 237,1408,3018, 988,4531,1970, -3034,3310, 540,2370,1562,1288,2990, 502,4765,1147, 4,1853,2708, 207, 294,2814, -4078,2902,2509, 684, 34,3105,3532,2551, 644, 709,2801,2344, 573,1727,3573,3557, -2021,1081,3100,4315,2100,3681, 199,2263,1837,2385, 146,3484,1195,2776,3949, 997, -1939,3973,1008,1091,1202,1962,1847,1149,4209,5444,1076, 493, 117,5400,2521, 972, -1490,2934,1796,4542,2374,1512,2933,2657, 413,2888,1135,2762,2314,2156,1355,2369, - 766,2007,2527,2170,3124,2491,2593,2632,4757,2437, 234,3125,3591,1898,1750,1376, -1942,3468,3138, 570,2127,2145,3276,4131, 962, 132,1445,4196, 19, 941,3624,3480, -3366,1973,1374,4461,3431,2629, 283,2415,2275, 808,2887,3620,2112,2563,1353,3610, - 955,1089,3103,1053, 96, 88,4097, 823,3808,1583, 399, 292,4091,3313, 421,1128, - 642,4006, 903,2539,1877,2082, 596, 29,4066,1790, 722,2157, 130, 995,1569, 769, -1485, 464, 513,2213, 288,1923,1101,2453,4316, 133, 486,2445, 50, 625, 487,2207, - 57, 423, 481,2962, 159,3729,1558, 491, 303, 482, 501, 240,2837, 112,3648,2392, -1783, 362, 8,3433,3422, 610,2793,3277,1390,1284,1654, 21,3823, 734, 367, 623, - 193, 287, 374,1009,1483, 816, 476, 313,2255,2340,1262,2150,2899,1146,2581, 782, -2116,1659,2018,1880, 255,3586,3314,1110,2867,2137,2564, 986,2767,5185,2006, 650, - 158, 926, 762, 881,3157,2717,2362,3587, 306,3690,3245,1542,3077,2427,1691,2478, -2118,2985,3490,2438, 539,2305, 983, 129,1754, 355,4201,2386, 827,2923, 104,1773, -2838,2771, 411,2905,3919, 376, 767, 122,1114, 828,2422,1817,3506, 266,3460,1007, -1609,4998, 945,2612,4429,2274, 726,1247,1964,2914,2199,2070,4002,4108, 657,3323, -1422, 579, 455,2764,4737,1222,2895,1670, 824,1223,1487,2525, 558, 861,3080, 598, -2659,2515,1967, 752,2583,2376,2214,4180, 977, 704,2464,4999,2622,4109,1210,2961, - 819,1541, 142,2284, 44, 418, 457,1126,3730,4347,4626,1644,1876,3671,1864, 302, -1063,5694, 624, 723,1984,3745,1314,1676,2488,1610,1449,3558,3569,2166,2098, 409, -1011,2325,3704,2306, 818,1732,1383,1824,1844,3757, 999,2705,3497,1216,1423,2683, -2426,2954,2501,2726,2229,1475,2554,5064,1971,1794,1666,2014,1343, 783, 724, 191, -2434,1354,2220,5065,1763,2752,2472,4152, 131, 175,2885,3434, 92,1466,4920,2616, -3871,3872,3866, 128,1551,1632, 669,1854,3682,4691,4125,1230, 188,2973,3290,1302, -1213, 560,3266, 917, 763,3909,3249,1760, 868,1958, 764,1782,2097, 145,2277,3774, -4462, 64,1491,3062, 971,2132,3606,2442, 221,1226,1617, 218, 323,1185,3207,3147, - 571, 619,1473,1005,1744,2281, 449,1887,2396,3685, 275, 375,3816,1743,3844,3731, - 845,1983,2350,4210,1377, 773, 967,3499,3052,3743,2725,4007,1697,1022,3943,1464, -3264,2855,2722,1952,1029,2839,2467, 84,4383,2215, 820,1391,2015,2448,3672, 377, -1948,2168, 797,2545,3536,2578,2645, 94,2874,1678, 405,1259,3071, 771, 546,1315, - 470,1243,3083, 895,2468, 981, 969,2037, 846,4181, 653,1276,2928, 14,2594, 557, -3007,2474, 156, 902,1338,1740,2574, 537,2518, 973,2282,2216,2433,1928, 138,2903, -1293,2631,1612, 646,3457, 839,2935, 111, 496,2191,2847, 589,3186, 149,3994,2060, -4031,2641,4067,3145,1870, 37,3597,2136,1025,2051,3009,3383,3549,1121,1016,3261, -1301, 251,2446,2599,2153, 872,3246, 637, 334,3705, 831, 884, 921,3065,3140,4092, -2198,1944, 246,2964, 108,2045,1152,1921,2308,1031, 203,3173,4170,1907,3890, 810, -1401,2003,1690, 506, 647,1242,2828,1761,1649,3208,2249,1589,3709,2931,5156,1708, - 498, 666,2613, 834,3817,1231, 184,2851,1124, 883,3197,2261,3710,1765,1553,2658, -1178,2639,2351, 93,1193, 942,2538,2141,4402, 235,1821, 870,1591,2192,1709,1871, -3341,1618,4126,2595,2334, 603, 651, 69, 701, 268,2662,3411,2555,1380,1606, 503, - 448, 254,2371,2646, 574,1187,2309,1770, 322,2235,1292,1801, 305, 566,1133, 229, -2067,2057, 706, 167, 483,2002,2672,3295,1820,3561,3067, 316, 378,2746,3452,1112, - 136,1981, 507,1651,2917,1117, 285,4591, 182,2580,3522,1304, 335,3303,1835,2504, -1795,1792,2248, 674,1018,2106,2449,1857,2292,2845, 976,3047,1781,2600,2727,1389, -1281, 52,3152, 153, 265,3950, 672,3485,3951,4463, 430,1183, 365, 278,2169, 27, -1407,1336,2304, 209,1340,1730,2202,1852,2403,2883, 979,1737,1062, 631,2829,2542, -3876,2592, 825,2086,2226,3048,3625, 352,1417,3724, 542, 991, 431,1351,3938,1861, -2294, 826,1361,2927,3142,3503,1738, 463,2462,2723, 582,1916,1595,2808, 400,3845, -3891,2868,3621,2254, 58,2492,1123, 910,2160,2614,1372,1603,1196,1072,3385,1700, -3267,1980, 696, 480,2430, 920, 799,1570,2920,1951,2041,4047,2540,1321,4223,2469, -3562,2228,1271,2602, 401,2833,3351,2575,5157, 907,2312,1256, 410, 263,3507,1582, - 996, 678,1849,2316,1480, 908,3545,2237, 703,2322, 667,1826,2849,1531,2604,2999, -2407,3146,2151,2630,1786,3711, 469,3542, 497,3899,2409, 858, 837,4446,3393,1274, - 786, 620,1845,2001,3311, 484, 308,3367,1204,1815,3691,2332,1532,2557,1842,2020, -2724,1927,2333,4440, 567, 22,1673,2728,4475,1987,1858,1144,1597, 101,1832,3601, - 12, 974,3783,4391, 951,1412, 1,3720, 453,4608,4041, 528,1041,1027,3230,2628, -1129, 875,1051,3291,1203,2262,1069,2860,2799,2149,2615,3278, 144,1758,3040, 31, - 475,1680, 366,2685,3184, 311,1642,4008,2466,5036,1593,1493,2809, 216,1420,1668, - 233, 304,2128,3284, 232,1429,1768,1040,2008,3407,2740,2967,2543, 242,2133, 778, -1565,2022,2620, 505,2189,2756,1098,2273, 372,1614, 708, 553,2846,2094,2278, 169, -3626,2835,4161, 228,2674,3165, 809,1454,1309, 466,1705,1095, 900,3423, 880,2667, -3751,5258,2317,3109,2571,4317,2766,1503,1342, 866,4447,1118, 63,2076, 314,1881, -1348,1061, 172, 978,3515,1747, 532, 511,3970, 6, 601, 905,2699,3300,1751, 276, -1467,3725,2668, 65,4239,2544,2779,2556,1604, 578,2451,1802, 992,2331,2624,1320, -3446, 713,1513,1013, 103,2786,2447,1661, 886,1702, 916, 654,3574,2031,1556, 751, -2178,2821,2179,1498,1538,2176, 271, 914,2251,2080,1325, 638,1953,2937,3877,2432, -2754, 95,3265,1716, 260,1227,4083, 775, 106,1357,3254, 426,1607, 555,2480, 772, -1985, 244,2546, 474, 495,1046,2611,1851,2061, 71,2089,1675,2590, 742,3758,2843, -3222,1433, 267,2180,2576,2826,2233,2092,3913,2435, 956,1745,3075, 856,2113,1116, - 451, 3,1988,2896,1398, 993,2463,1878,2049,1341,2718,2721,2870,2108, 712,2904, -4363,2753,2324, 277,2872,2349,2649, 384, 987, 435, 691,3000, 922, 164,3939, 652, -1500,1184,4153,2482,3373,2165,4848,2335,3775,3508,3154,2806,2830,1554,2102,1664, -2530,1434,2408, 893,1547,2623,3447,2832,2242,2532,3169,2856,3223,2078, 49,3770, -3469, 462, 318, 656,2259,3250,3069, 679,1629,2758, 344,1138,1104,3120,1836,1283, -3115,2154,1437,4448, 934, 759,1999, 794,2862,1038, 533,2560,1722,2342, 855,2626, -1197,1663,4476,3127, 85,4240,2528, 25,1111,1181,3673, 407,3470,4561,2679,2713, - 768,1925,2841,3986,1544,1165, 932, 373,1240,2146,1930,2673, 721,4766, 354,4333, - 391,2963, 187, 61,3364,1442,1102, 330,1940,1767, 341,3809,4118, 393,2496,2062, -2211, 105, 331, 300, 439, 913,1332, 626, 379,3304,1557, 328, 689,3952, 309,1555, - 931, 317,2517,3027, 325, 569, 686,2107,3084, 60,1042,1333,2794, 264,3177,4014, -1628, 258,3712, 7,4464,1176,1043,1778, 683, 114,1975, 78,1492, 383,1886, 510, - 386, 645,5291,2891,2069,3305,4138,3867,2939,2603,2493,1935,1066,1848,3588,1015, -1282,1289,4609, 697,1453,3044,2666,3611,1856,2412, 54, 719,1330, 568,3778,2459, -1748, 788, 492, 551,1191,1000, 488,3394,3763, 282,1799, 348,2016,1523,3155,2390, -1049, 382,2019,1788,1170, 729,2968,3523, 897,3926,2785,2938,3292, 350,2319,3238, -1718,1717,2655,3453,3143,4465, 161,2889,2980,2009,1421, 56,1908,1640,2387,2232, -1917,1874,2477,4921, 148, 83,3438, 592,4245,2882,1822,1055, 741, 115,1496,1624, - 381,1638,4592,1020, 516,3214, 458, 947,4575,1432, 211,1514,2926,1865,2142, 189, - 852,1221,1400,1486, 882,2299,4036, 351, 28,1122, 700,6479,6480,6481,6482,6483, #last 512 -) - diff --git a/pipenv/vendor/chardet/gb2312prober.py b/pipenv/vendor/chardet/gb2312prober.py deleted file mode 100644 index 8446d2dd..00000000 --- a/pipenv/vendor/chardet/gb2312prober.py +++ /dev/null @@ -1,46 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is mozilla.org code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .mbcharsetprober import MultiByteCharSetProber -from .codingstatemachine import CodingStateMachine -from .chardistribution import GB2312DistributionAnalysis -from .mbcssm import GB2312_SM_MODEL - -class GB2312Prober(MultiByteCharSetProber): - def __init__(self): - super(GB2312Prober, self).__init__() - self.coding_sm = CodingStateMachine(GB2312_SM_MODEL) - self.distribution_analyzer = GB2312DistributionAnalysis() - self.reset() - - @property - def charset_name(self): - return "GB2312" - - @property - def language(self): - return "Chinese" diff --git a/pipenv/vendor/chardet/hebrewprober.py b/pipenv/vendor/chardet/hebrewprober.py deleted file mode 100644 index b0e1bf49..00000000 --- a/pipenv/vendor/chardet/hebrewprober.py +++ /dev/null @@ -1,292 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Universal charset detector code. -# -# The Initial Developer of the Original Code is -# Shy Shalom -# Portions created by the Initial Developer are Copyright (C) 2005 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .charsetprober import CharSetProber -from .enums import ProbingState - -# This prober doesn't actually recognize a language or a charset. -# It is a helper prober for the use of the Hebrew model probers - -### General ideas of the Hebrew charset recognition ### -# -# Four main charsets exist in Hebrew: -# "ISO-8859-8" - Visual Hebrew -# "windows-1255" - Logical Hebrew -# "ISO-8859-8-I" - Logical Hebrew -# "x-mac-hebrew" - ?? Logical Hebrew ?? -# -# Both "ISO" charsets use a completely identical set of code points, whereas -# "windows-1255" and "x-mac-hebrew" are two different proper supersets of -# these code points. windows-1255 defines additional characters in the range -# 0x80-0x9F as some misc punctuation marks as well as some Hebrew-specific -# diacritics and additional 'Yiddish' ligature letters in the range 0xc0-0xd6. -# x-mac-hebrew defines similar additional code points but with a different -# mapping. -# -# As far as an average Hebrew text with no diacritics is concerned, all four -# charsets are identical with respect to code points. Meaning that for the -# main Hebrew alphabet, all four map the same values to all 27 Hebrew letters -# (including final letters). -# -# The dominant difference between these charsets is their directionality. -# "Visual" directionality means that the text is ordered as if the renderer is -# not aware of a BIDI rendering algorithm. The renderer sees the text and -# draws it from left to right. The text itself when ordered naturally is read -# backwards. A buffer of Visual Hebrew generally looks like so: -# "[last word of first line spelled backwards] [whole line ordered backwards -# and spelled backwards] [first word of first line spelled backwards] -# [end of line] [last word of second line] ... etc' " -# adding punctuation marks, numbers and English text to visual text is -# naturally also "visual" and from left to right. -# -# "Logical" directionality means the text is ordered "naturally" according to -# the order it is read. It is the responsibility of the renderer to display -# the text from right to left. A BIDI algorithm is used to place general -# punctuation marks, numbers and English text in the text. -# -# Texts in x-mac-hebrew are almost impossible to find on the Internet. From -# what little evidence I could find, it seems that its general directionality -# is Logical. -# -# To sum up all of the above, the Hebrew probing mechanism knows about two -# charsets: -# Visual Hebrew - "ISO-8859-8" - backwards text - Words and sentences are -# backwards while line order is natural. For charset recognition purposes -# the line order is unimportant (In fact, for this implementation, even -# word order is unimportant). -# Logical Hebrew - "windows-1255" - normal, naturally ordered text. -# -# "ISO-8859-8-I" is a subset of windows-1255 and doesn't need to be -# specifically identified. -# "x-mac-hebrew" is also identified as windows-1255. A text in x-mac-hebrew -# that contain special punctuation marks or diacritics is displayed with -# some unconverted characters showing as question marks. This problem might -# be corrected using another model prober for x-mac-hebrew. Due to the fact -# that x-mac-hebrew texts are so rare, writing another model prober isn't -# worth the effort and performance hit. -# -#### The Prober #### -# -# The prober is divided between two SBCharSetProbers and a HebrewProber, -# all of which are managed, created, fed data, inquired and deleted by the -# SBCSGroupProber. The two SBCharSetProbers identify that the text is in -# fact some kind of Hebrew, Logical or Visual. The final decision about which -# one is it is made by the HebrewProber by combining final-letter scores -# with the scores of the two SBCharSetProbers to produce a final answer. -# -# The SBCSGroupProber is responsible for stripping the original text of HTML -# tags, English characters, numbers, low-ASCII punctuation characters, spaces -# and new lines. It reduces any sequence of such characters to a single space. -# The buffer fed to each prober in the SBCS group prober is pure text in -# high-ASCII. -# The two SBCharSetProbers (model probers) share the same language model: -# Win1255Model. -# The first SBCharSetProber uses the model normally as any other -# SBCharSetProber does, to recognize windows-1255, upon which this model was -# built. The second SBCharSetProber is told to make the pair-of-letter -# lookup in the language model backwards. This in practice exactly simulates -# a visual Hebrew model using the windows-1255 logical Hebrew model. -# -# The HebrewProber is not using any language model. All it does is look for -# final-letter evidence suggesting the text is either logical Hebrew or visual -# Hebrew. Disjointed from the model probers, the results of the HebrewProber -# alone are meaningless. HebrewProber always returns 0.00 as confidence -# since it never identifies a charset by itself. Instead, the pointer to the -# HebrewProber is passed to the model probers as a helper "Name Prober". -# When the Group prober receives a positive identification from any prober, -# it asks for the name of the charset identified. If the prober queried is a -# Hebrew model prober, the model prober forwards the call to the -# HebrewProber to make the final decision. In the HebrewProber, the -# decision is made according to the final-letters scores maintained and Both -# model probers scores. The answer is returned in the form of the name of the -# charset identified, either "windows-1255" or "ISO-8859-8". - -class HebrewProber(CharSetProber): - # windows-1255 / ISO-8859-8 code points of interest - FINAL_KAF = 0xea - NORMAL_KAF = 0xeb - FINAL_MEM = 0xed - NORMAL_MEM = 0xee - FINAL_NUN = 0xef - NORMAL_NUN = 0xf0 - FINAL_PE = 0xf3 - NORMAL_PE = 0xf4 - FINAL_TSADI = 0xf5 - NORMAL_TSADI = 0xf6 - - # Minimum Visual vs Logical final letter score difference. - # If the difference is below this, don't rely solely on the final letter score - # distance. - MIN_FINAL_CHAR_DISTANCE = 5 - - # Minimum Visual vs Logical model score difference. - # If the difference is below this, don't rely at all on the model score - # distance. - MIN_MODEL_DISTANCE = 0.01 - - VISUAL_HEBREW_NAME = "ISO-8859-8" - LOGICAL_HEBREW_NAME = "windows-1255" - - def __init__(self): - super(HebrewProber, self).__init__() - self._final_char_logical_score = None - self._final_char_visual_score = None - self._prev = None - self._before_prev = None - self._logical_prober = None - self._visual_prober = None - self.reset() - - def reset(self): - self._final_char_logical_score = 0 - self._final_char_visual_score = 0 - # The two last characters seen in the previous buffer, - # mPrev and mBeforePrev are initialized to space in order to simulate - # a word delimiter at the beginning of the data - self._prev = ' ' - self._before_prev = ' ' - # These probers are owned by the group prober. - - def set_model_probers(self, logicalProber, visualProber): - self._logical_prober = logicalProber - self._visual_prober = visualProber - - def is_final(self, c): - return c in [self.FINAL_KAF, self.FINAL_MEM, self.FINAL_NUN, - self.FINAL_PE, self.FINAL_TSADI] - - def is_non_final(self, c): - # The normal Tsadi is not a good Non-Final letter due to words like - # 'lechotet' (to chat) containing an apostrophe after the tsadi. This - # apostrophe is converted to a space in FilterWithoutEnglishLetters - # causing the Non-Final tsadi to appear at an end of a word even - # though this is not the case in the original text. - # The letters Pe and Kaf rarely display a related behavior of not being - # a good Non-Final letter. Words like 'Pop', 'Winamp' and 'Mubarak' - # for example legally end with a Non-Final Pe or Kaf. However, the - # benefit of these letters as Non-Final letters outweighs the damage - # since these words are quite rare. - return c in [self.NORMAL_KAF, self.NORMAL_MEM, - self.NORMAL_NUN, self.NORMAL_PE] - - def feed(self, byte_str): - # Final letter analysis for logical-visual decision. - # Look for evidence that the received buffer is either logical Hebrew - # or visual Hebrew. - # The following cases are checked: - # 1) A word longer than 1 letter, ending with a final letter. This is - # an indication that the text is laid out "naturally" since the - # final letter really appears at the end. +1 for logical score. - # 2) A word longer than 1 letter, ending with a Non-Final letter. In - # normal Hebrew, words ending with Kaf, Mem, Nun, Pe or Tsadi, - # should not end with the Non-Final form of that letter. Exceptions - # to this rule are mentioned above in isNonFinal(). This is an - # indication that the text is laid out backwards. +1 for visual - # score - # 3) A word longer than 1 letter, starting with a final letter. Final - # letters should not appear at the beginning of a word. This is an - # indication that the text is laid out backwards. +1 for visual - # score. - # - # The visual score and logical score are accumulated throughout the - # text and are finally checked against each other in GetCharSetName(). - # No checking for final letters in the middle of words is done since - # that case is not an indication for either Logical or Visual text. - # - # We automatically filter out all 7-bit characters (replace them with - # spaces) so the word boundary detection works properly. [MAP] - - if self.state == ProbingState.NOT_ME: - # Both model probers say it's not them. No reason to continue. - return ProbingState.NOT_ME - - byte_str = self.filter_high_byte_only(byte_str) - - for cur in byte_str: - if cur == ' ': - # We stand on a space - a word just ended - if self._before_prev != ' ': - # next-to-last char was not a space so self._prev is not a - # 1 letter word - if self.is_final(self._prev): - # case (1) [-2:not space][-1:final letter][cur:space] - self._final_char_logical_score += 1 - elif self.is_non_final(self._prev): - # case (2) [-2:not space][-1:Non-Final letter][ - # cur:space] - self._final_char_visual_score += 1 - else: - # Not standing on a space - if ((self._before_prev == ' ') and - (self.is_final(self._prev)) and (cur != ' ')): - # case (3) [-2:space][-1:final letter][cur:not space] - self._final_char_visual_score += 1 - self._before_prev = self._prev - self._prev = cur - - # Forever detecting, till the end or until both model probers return - # ProbingState.NOT_ME (handled above) - return ProbingState.DETECTING - - @property - def charset_name(self): - # Make the decision: is it Logical or Visual? - # If the final letter score distance is dominant enough, rely on it. - finalsub = self._final_char_logical_score - self._final_char_visual_score - if finalsub >= self.MIN_FINAL_CHAR_DISTANCE: - return self.LOGICAL_HEBREW_NAME - if finalsub <= -self.MIN_FINAL_CHAR_DISTANCE: - return self.VISUAL_HEBREW_NAME - - # It's not dominant enough, try to rely on the model scores instead. - modelsub = (self._logical_prober.get_confidence() - - self._visual_prober.get_confidence()) - if modelsub > self.MIN_MODEL_DISTANCE: - return self.LOGICAL_HEBREW_NAME - if modelsub < -self.MIN_MODEL_DISTANCE: - return self.VISUAL_HEBREW_NAME - - # Still no good, back to final letter distance, maybe it'll save the - # day. - if finalsub < 0.0: - return self.VISUAL_HEBREW_NAME - - # (finalsub > 0 - Logical) or (don't know what to do) default to - # Logical. - return self.LOGICAL_HEBREW_NAME - - @property - def language(self): - return 'Hebrew' - - @property - def state(self): - # Remain active as long as any of the model probers are active. - if (self._logical_prober.state == ProbingState.NOT_ME) and \ - (self._visual_prober.state == ProbingState.NOT_ME): - return ProbingState.NOT_ME - return ProbingState.DETECTING diff --git a/pipenv/vendor/chardet/jisfreq.py b/pipenv/vendor/chardet/jisfreq.py deleted file mode 100644 index 83fc082b..00000000 --- a/pipenv/vendor/chardet/jisfreq.py +++ /dev/null @@ -1,325 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -# Sampling from about 20M text materials include literature and computer technology -# -# Japanese frequency table, applied to both S-JIS and EUC-JP -# They are sorted in order. - -# 128 --> 0.77094 -# 256 --> 0.85710 -# 512 --> 0.92635 -# 1024 --> 0.97130 -# 2048 --> 0.99431 -# -# Ideal Distribution Ratio = 0.92635 / (1-0.92635) = 12.58 -# Random Distribution Ration = 512 / (2965+62+83+86-512) = 0.191 -# -# Typical Distribution Ratio, 25% of IDR - -JIS_TYPICAL_DISTRIBUTION_RATIO = 3.0 - -# Char to FreqOrder table , -JIS_TABLE_SIZE = 4368 - -JIS_CHAR_TO_FREQ_ORDER = ( - 40, 1, 6, 182, 152, 180, 295,2127, 285, 381,3295,4304,3068,4606,3165,3510, # 16 -3511,1822,2785,4607,1193,2226,5070,4608, 171,2996,1247, 18, 179,5071, 856,1661, # 32 -1262,5072, 619, 127,3431,3512,3230,1899,1700, 232, 228,1294,1298, 284, 283,2041, # 48 -2042,1061,1062, 48, 49, 44, 45, 433, 434,1040,1041, 996, 787,2997,1255,4305, # 64 -2108,4609,1684,1648,5073,5074,5075,5076,5077,5078,3687,5079,4610,5080,3927,3928, # 80 -5081,3296,3432, 290,2285,1471,2187,5082,2580,2825,1303,2140,1739,1445,2691,3375, # 96 -1691,3297,4306,4307,4611, 452,3376,1182,2713,3688,3069,4308,5083,5084,5085,5086, # 112 -5087,5088,5089,5090,5091,5092,5093,5094,5095,5096,5097,5098,5099,5100,5101,5102, # 128 -5103,5104,5105,5106,5107,5108,5109,5110,5111,5112,4097,5113,5114,5115,5116,5117, # 144 -5118,5119,5120,5121,5122,5123,5124,5125,5126,5127,5128,5129,5130,5131,5132,5133, # 160 -5134,5135,5136,5137,5138,5139,5140,5141,5142,5143,5144,5145,5146,5147,5148,5149, # 176 -5150,5151,5152,4612,5153,5154,5155,5156,5157,5158,5159,5160,5161,5162,5163,5164, # 192 -5165,5166,5167,5168,5169,5170,5171,5172,5173,5174,5175,1472, 598, 618, 820,1205, # 208 -1309,1412,1858,1307,1692,5176,5177,5178,5179,5180,5181,5182,1142,1452,1234,1172, # 224 -1875,2043,2149,1793,1382,2973, 925,2404,1067,1241, 960,1377,2935,1491, 919,1217, # 240 -1865,2030,1406,1499,2749,4098,5183,5184,5185,5186,5187,5188,2561,4099,3117,1804, # 256 -2049,3689,4309,3513,1663,5189,3166,3118,3298,1587,1561,3433,5190,3119,1625,2998, # 272 -3299,4613,1766,3690,2786,4614,5191,5192,5193,5194,2161, 26,3377, 2,3929, 20, # 288 -3691, 47,4100, 50, 17, 16, 35, 268, 27, 243, 42, 155, 24, 154, 29, 184, # 304 - 4, 91, 14, 92, 53, 396, 33, 289, 9, 37, 64, 620, 21, 39, 321, 5, # 320 - 12, 11, 52, 13, 3, 208, 138, 0, 7, 60, 526, 141, 151,1069, 181, 275, # 336 -1591, 83, 132,1475, 126, 331, 829, 15, 69, 160, 59, 22, 157, 55,1079, 312, # 352 - 109, 38, 23, 25, 10, 19, 79,5195, 61, 382,1124, 8, 30,5196,5197,5198, # 368 -5199,5200,5201,5202,5203,5204,5205,5206, 89, 62, 74, 34,2416, 112, 139, 196, # 384 - 271, 149, 84, 607, 131, 765, 46, 88, 153, 683, 76, 874, 101, 258, 57, 80, # 400 - 32, 364, 121,1508, 169,1547, 68, 235, 145,2999, 41, 360,3027, 70, 63, 31, # 416 - 43, 259, 262,1383, 99, 533, 194, 66, 93, 846, 217, 192, 56, 106, 58, 565, # 432 - 280, 272, 311, 256, 146, 82, 308, 71, 100, 128, 214, 655, 110, 261, 104,1140, # 448 - 54, 51, 36, 87, 67,3070, 185,2618,2936,2020, 28,1066,2390,2059,5207,5208, # 464 -5209,5210,5211,5212,5213,5214,5215,5216,4615,5217,5218,5219,5220,5221,5222,5223, # 480 -5224,5225,5226,5227,5228,5229,5230,5231,5232,5233,5234,5235,5236,3514,5237,5238, # 496 -5239,5240,5241,5242,5243,5244,2297,2031,4616,4310,3692,5245,3071,5246,3598,5247, # 512 -4617,3231,3515,5248,4101,4311,4618,3808,4312,4102,5249,4103,4104,3599,5250,5251, # 528 -5252,5253,5254,5255,5256,5257,5258,5259,5260,5261,5262,5263,5264,5265,5266,5267, # 544 -5268,5269,5270,5271,5272,5273,5274,5275,5276,5277,5278,5279,5280,5281,5282,5283, # 560 -5284,5285,5286,5287,5288,5289,5290,5291,5292,5293,5294,5295,5296,5297,5298,5299, # 576 -5300,5301,5302,5303,5304,5305,5306,5307,5308,5309,5310,5311,5312,5313,5314,5315, # 592 -5316,5317,5318,5319,5320,5321,5322,5323,5324,5325,5326,5327,5328,5329,5330,5331, # 608 -5332,5333,5334,5335,5336,5337,5338,5339,5340,5341,5342,5343,5344,5345,5346,5347, # 624 -5348,5349,5350,5351,5352,5353,5354,5355,5356,5357,5358,5359,5360,5361,5362,5363, # 640 -5364,5365,5366,5367,5368,5369,5370,5371,5372,5373,5374,5375,5376,5377,5378,5379, # 656 -5380,5381, 363, 642,2787,2878,2788,2789,2316,3232,2317,3434,2011, 165,1942,3930, # 672 -3931,3932,3933,5382,4619,5383,4620,5384,5385,5386,5387,5388,5389,5390,5391,5392, # 688 -5393,5394,5395,5396,5397,5398,5399,5400,5401,5402,5403,5404,5405,5406,5407,5408, # 704 -5409,5410,5411,5412,5413,5414,5415,5416,5417,5418,5419,5420,5421,5422,5423,5424, # 720 -5425,5426,5427,5428,5429,5430,5431,5432,5433,5434,5435,5436,5437,5438,5439,5440, # 736 -5441,5442,5443,5444,5445,5446,5447,5448,5449,5450,5451,5452,5453,5454,5455,5456, # 752 -5457,5458,5459,5460,5461,5462,5463,5464,5465,5466,5467,5468,5469,5470,5471,5472, # 768 -5473,5474,5475,5476,5477,5478,5479,5480,5481,5482,5483,5484,5485,5486,5487,5488, # 784 -5489,5490,5491,5492,5493,5494,5495,5496,5497,5498,5499,5500,5501,5502,5503,5504, # 800 -5505,5506,5507,5508,5509,5510,5511,5512,5513,5514,5515,5516,5517,5518,5519,5520, # 816 -5521,5522,5523,5524,5525,5526,5527,5528,5529,5530,5531,5532,5533,5534,5535,5536, # 832 -5537,5538,5539,5540,5541,5542,5543,5544,5545,5546,5547,5548,5549,5550,5551,5552, # 848 -5553,5554,5555,5556,5557,5558,5559,5560,5561,5562,5563,5564,5565,5566,5567,5568, # 864 -5569,5570,5571,5572,5573,5574,5575,5576,5577,5578,5579,5580,5581,5582,5583,5584, # 880 -5585,5586,5587,5588,5589,5590,5591,5592,5593,5594,5595,5596,5597,5598,5599,5600, # 896 -5601,5602,5603,5604,5605,5606,5607,5608,5609,5610,5611,5612,5613,5614,5615,5616, # 912 -5617,5618,5619,5620,5621,5622,5623,5624,5625,5626,5627,5628,5629,5630,5631,5632, # 928 -5633,5634,5635,5636,5637,5638,5639,5640,5641,5642,5643,5644,5645,5646,5647,5648, # 944 -5649,5650,5651,5652,5653,5654,5655,5656,5657,5658,5659,5660,5661,5662,5663,5664, # 960 -5665,5666,5667,5668,5669,5670,5671,5672,5673,5674,5675,5676,5677,5678,5679,5680, # 976 -5681,5682,5683,5684,5685,5686,5687,5688,5689,5690,5691,5692,5693,5694,5695,5696, # 992 -5697,5698,5699,5700,5701,5702,5703,5704,5705,5706,5707,5708,5709,5710,5711,5712, # 1008 -5713,5714,5715,5716,5717,5718,5719,5720,5721,5722,5723,5724,5725,5726,5727,5728, # 1024 -5729,5730,5731,5732,5733,5734,5735,5736,5737,5738,5739,5740,5741,5742,5743,5744, # 1040 -5745,5746,5747,5748,5749,5750,5751,5752,5753,5754,5755,5756,5757,5758,5759,5760, # 1056 -5761,5762,5763,5764,5765,5766,5767,5768,5769,5770,5771,5772,5773,5774,5775,5776, # 1072 -5777,5778,5779,5780,5781,5782,5783,5784,5785,5786,5787,5788,5789,5790,5791,5792, # 1088 -5793,5794,5795,5796,5797,5798,5799,5800,5801,5802,5803,5804,5805,5806,5807,5808, # 1104 -5809,5810,5811,5812,5813,5814,5815,5816,5817,5818,5819,5820,5821,5822,5823,5824, # 1120 -5825,5826,5827,5828,5829,5830,5831,5832,5833,5834,5835,5836,5837,5838,5839,5840, # 1136 -5841,5842,5843,5844,5845,5846,5847,5848,5849,5850,5851,5852,5853,5854,5855,5856, # 1152 -5857,5858,5859,5860,5861,5862,5863,5864,5865,5866,5867,5868,5869,5870,5871,5872, # 1168 -5873,5874,5875,5876,5877,5878,5879,5880,5881,5882,5883,5884,5885,5886,5887,5888, # 1184 -5889,5890,5891,5892,5893,5894,5895,5896,5897,5898,5899,5900,5901,5902,5903,5904, # 1200 -5905,5906,5907,5908,5909,5910,5911,5912,5913,5914,5915,5916,5917,5918,5919,5920, # 1216 -5921,5922,5923,5924,5925,5926,5927,5928,5929,5930,5931,5932,5933,5934,5935,5936, # 1232 -5937,5938,5939,5940,5941,5942,5943,5944,5945,5946,5947,5948,5949,5950,5951,5952, # 1248 -5953,5954,5955,5956,5957,5958,5959,5960,5961,5962,5963,5964,5965,5966,5967,5968, # 1264 -5969,5970,5971,5972,5973,5974,5975,5976,5977,5978,5979,5980,5981,5982,5983,5984, # 1280 -5985,5986,5987,5988,5989,5990,5991,5992,5993,5994,5995,5996,5997,5998,5999,6000, # 1296 -6001,6002,6003,6004,6005,6006,6007,6008,6009,6010,6011,6012,6013,6014,6015,6016, # 1312 -6017,6018,6019,6020,6021,6022,6023,6024,6025,6026,6027,6028,6029,6030,6031,6032, # 1328 -6033,6034,6035,6036,6037,6038,6039,6040,6041,6042,6043,6044,6045,6046,6047,6048, # 1344 -6049,6050,6051,6052,6053,6054,6055,6056,6057,6058,6059,6060,6061,6062,6063,6064, # 1360 -6065,6066,6067,6068,6069,6070,6071,6072,6073,6074,6075,6076,6077,6078,6079,6080, # 1376 -6081,6082,6083,6084,6085,6086,6087,6088,6089,6090,6091,6092,6093,6094,6095,6096, # 1392 -6097,6098,6099,6100,6101,6102,6103,6104,6105,6106,6107,6108,6109,6110,6111,6112, # 1408 -6113,6114,2044,2060,4621, 997,1235, 473,1186,4622, 920,3378,6115,6116, 379,1108, # 1424 -4313,2657,2735,3934,6117,3809, 636,3233, 573,1026,3693,3435,2974,3300,2298,4105, # 1440 - 854,2937,2463, 393,2581,2417, 539, 752,1280,2750,2480, 140,1161, 440, 708,1569, # 1456 - 665,2497,1746,1291,1523,3000, 164,1603, 847,1331, 537,1997, 486, 508,1693,2418, # 1472 -1970,2227, 878,1220, 299,1030, 969, 652,2751, 624,1137,3301,2619, 65,3302,2045, # 1488 -1761,1859,3120,1930,3694,3516, 663,1767, 852, 835,3695, 269, 767,2826,2339,1305, # 1504 - 896,1150, 770,1616,6118, 506,1502,2075,1012,2519, 775,2520,2975,2340,2938,4314, # 1520 -3028,2086,1224,1943,2286,6119,3072,4315,2240,1273,1987,3935,1557, 175, 597, 985, # 1536 -3517,2419,2521,1416,3029, 585, 938,1931,1007,1052,1932,1685,6120,3379,4316,4623, # 1552 - 804, 599,3121,1333,2128,2539,1159,1554,2032,3810, 687,2033,2904, 952, 675,1467, # 1568 -3436,6121,2241,1096,1786,2440,1543,1924, 980,1813,2228, 781,2692,1879, 728,1918, # 1584 -3696,4624, 548,1950,4625,1809,1088,1356,3303,2522,1944, 502, 972, 373, 513,2827, # 1600 - 586,2377,2391,1003,1976,1631,6122,2464,1084, 648,1776,4626,2141, 324, 962,2012, # 1616 -2177,2076,1384, 742,2178,1448,1173,1810, 222, 102, 301, 445, 125,2420, 662,2498, # 1632 - 277, 200,1476,1165,1068, 224,2562,1378,1446, 450,1880, 659, 791, 582,4627,2939, # 1648 -3936,1516,1274, 555,2099,3697,1020,1389,1526,3380,1762,1723,1787,2229, 412,2114, # 1664 -1900,2392,3518, 512,2597, 427,1925,2341,3122,1653,1686,2465,2499, 697, 330, 273, # 1680 - 380,2162, 951, 832, 780, 991,1301,3073, 965,2270,3519, 668,2523,2636,1286, 535, # 1696 -1407, 518, 671, 957,2658,2378, 267, 611,2197,3030,6123, 248,2299, 967,1799,2356, # 1712 - 850,1418,3437,1876,1256,1480,2828,1718,6124,6125,1755,1664,2405,6126,4628,2879, # 1728 -2829, 499,2179, 676,4629, 557,2329,2214,2090, 325,3234, 464, 811,3001, 992,2342, # 1744 -2481,1232,1469, 303,2242, 466,1070,2163, 603,1777,2091,4630,2752,4631,2714, 322, # 1760 -2659,1964,1768, 481,2188,1463,2330,2857,3600,2092,3031,2421,4632,2318,2070,1849, # 1776 -2598,4633,1302,2254,1668,1701,2422,3811,2905,3032,3123,2046,4106,1763,1694,4634, # 1792 -1604, 943,1724,1454, 917, 868,2215,1169,2940, 552,1145,1800,1228,1823,1955, 316, # 1808 -1080,2510, 361,1807,2830,4107,2660,3381,1346,1423,1134,4108,6127, 541,1263,1229, # 1824 -1148,2540, 545, 465,1833,2880,3438,1901,3074,2482, 816,3937, 713,1788,2500, 122, # 1840 -1575, 195,1451,2501,1111,6128, 859, 374,1225,2243,2483,4317, 390,1033,3439,3075, # 1856 -2524,1687, 266, 793,1440,2599, 946, 779, 802, 507, 897,1081, 528,2189,1292, 711, # 1872 -1866,1725,1167,1640, 753, 398,2661,1053, 246, 348,4318, 137,1024,3440,1600,2077, # 1888 -2129, 825,4319, 698, 238, 521, 187,2300,1157,2423,1641,1605,1464,1610,1097,2541, # 1904 -1260,1436, 759,2255,1814,2150, 705,3235, 409,2563,3304, 561,3033,2005,2564, 726, # 1920 -1956,2343,3698,4109, 949,3812,3813,3520,1669, 653,1379,2525, 881,2198, 632,2256, # 1936 -1027, 778,1074, 733,1957, 514,1481,2466, 554,2180, 702,3938,1606,1017,1398,6129, # 1952 -1380,3521, 921, 993,1313, 594, 449,1489,1617,1166, 768,1426,1360, 495,1794,3601, # 1968 -1177,3602,1170,4320,2344, 476, 425,3167,4635,3168,1424, 401,2662,1171,3382,1998, # 1984 -1089,4110, 477,3169, 474,6130,1909, 596,2831,1842, 494, 693,1051,1028,1207,3076, # 2000 - 606,2115, 727,2790,1473,1115, 743,3522, 630, 805,1532,4321,2021, 366,1057, 838, # 2016 - 684,1114,2142,4322,2050,1492,1892,1808,2271,3814,2424,1971,1447,1373,3305,1090, # 2032 -1536,3939,3523,3306,1455,2199, 336, 369,2331,1035, 584,2393, 902, 718,2600,6131, # 2048 -2753, 463,2151,1149,1611,2467, 715,1308,3124,1268, 343,1413,3236,1517,1347,2663, # 2064 -2093,3940,2022,1131,1553,2100,2941,1427,3441,2942,1323,2484,6132,1980, 872,2368, # 2080 -2441,2943, 320,2369,2116,1082, 679,1933,3941,2791,3815, 625,1143,2023, 422,2200, # 2096 -3816,6133, 730,1695, 356,2257,1626,2301,2858,2637,1627,1778, 937, 883,2906,2693, # 2112 -3002,1769,1086, 400,1063,1325,3307,2792,4111,3077, 456,2345,1046, 747,6134,1524, # 2128 - 884,1094,3383,1474,2164,1059, 974,1688,2181,2258,1047, 345,1665,1187, 358, 875, # 2144 -3170, 305, 660,3524,2190,1334,1135,3171,1540,1649,2542,1527, 927, 968,2793, 885, # 2160 -1972,1850, 482, 500,2638,1218,1109,1085,2543,1654,2034, 876, 78,2287,1482,1277, # 2176 - 861,1675,1083,1779, 724,2754, 454, 397,1132,1612,2332, 893, 672,1237, 257,2259, # 2192 -2370, 135,3384, 337,2244, 547, 352, 340, 709,2485,1400, 788,1138,2511, 540, 772, # 2208 -1682,2260,2272,2544,2013,1843,1902,4636,1999,1562,2288,4637,2201,1403,1533, 407, # 2224 - 576,3308,1254,2071, 978,3385, 170, 136,1201,3125,2664,3172,2394, 213, 912, 873, # 2240 -3603,1713,2202, 699,3604,3699, 813,3442, 493, 531,1054, 468,2907,1483, 304, 281, # 2256 -4112,1726,1252,2094, 339,2319,2130,2639, 756,1563,2944, 748, 571,2976,1588,2425, # 2272 -2715,1851,1460,2426,1528,1392,1973,3237, 288,3309, 685,3386, 296, 892,2716,2216, # 2288 -1570,2245, 722,1747,2217, 905,3238,1103,6135,1893,1441,1965, 251,1805,2371,3700, # 2304 -2601,1919,1078, 75,2182,1509,1592,1270,2640,4638,2152,6136,3310,3817, 524, 706, # 2320 -1075, 292,3818,1756,2602, 317, 98,3173,3605,3525,1844,2218,3819,2502, 814, 567, # 2336 - 385,2908,1534,6137, 534,1642,3239, 797,6138,1670,1529, 953,4323, 188,1071, 538, # 2352 - 178, 729,3240,2109,1226,1374,2000,2357,2977, 731,2468,1116,2014,2051,6139,1261, # 2368 -1593, 803,2859,2736,3443, 556, 682, 823,1541,6140,1369,2289,1706,2794, 845, 462, # 2384 -2603,2665,1361, 387, 162,2358,1740, 739,1770,1720,1304,1401,3241,1049, 627,1571, # 2400 -2427,3526,1877,3942,1852,1500, 431,1910,1503, 677, 297,2795, 286,1433,1038,1198, # 2416 -2290,1133,1596,4113,4639,2469,1510,1484,3943,6141,2442, 108, 712,4640,2372, 866, # 2432 -3701,2755,3242,1348, 834,1945,1408,3527,2395,3243,1811, 824, 994,1179,2110,1548, # 2448 -1453, 790,3003, 690,4324,4325,2832,2909,3820,1860,3821, 225,1748, 310, 346,1780, # 2464 -2470, 821,1993,2717,2796, 828, 877,3528,2860,2471,1702,2165,2910,2486,1789, 453, # 2480 - 359,2291,1676, 73,1164,1461,1127,3311, 421, 604, 314,1037, 589, 116,2487, 737, # 2496 - 837,1180, 111, 244, 735,6142,2261,1861,1362, 986, 523, 418, 581,2666,3822, 103, # 2512 - 855, 503,1414,1867,2488,1091, 657,1597, 979, 605,1316,4641,1021,2443,2078,2001, # 2528 -1209, 96, 587,2166,1032, 260,1072,2153, 173, 94, 226,3244, 819,2006,4642,4114, # 2544 -2203, 231,1744, 782, 97,2667, 786,3387, 887, 391, 442,2219,4326,1425,6143,2694, # 2560 - 633,1544,1202, 483,2015, 592,2052,1958,2472,1655, 419, 129,4327,3444,3312,1714, # 2576 -1257,3078,4328,1518,1098, 865,1310,1019,1885,1512,1734, 469,2444, 148, 773, 436, # 2592 -1815,1868,1128,1055,4329,1245,2756,3445,2154,1934,1039,4643, 579,1238, 932,2320, # 2608 - 353, 205, 801, 115,2428, 944,2321,1881, 399,2565,1211, 678, 766,3944, 335,2101, # 2624 -1459,1781,1402,3945,2737,2131,1010, 844, 981,1326,1013, 550,1816,1545,2620,1335, # 2640 -1008, 371,2881, 936,1419,1613,3529,1456,1395,2273,1834,2604,1317,2738,2503, 416, # 2656 -1643,4330, 806,1126, 229, 591,3946,1314,1981,1576,1837,1666, 347,1790, 977,3313, # 2672 - 764,2861,1853, 688,2429,1920,1462, 77, 595, 415,2002,3034, 798,1192,4115,6144, # 2688 -2978,4331,3035,2695,2582,2072,2566, 430,2430,1727, 842,1396,3947,3702, 613, 377, # 2704 - 278, 236,1417,3388,3314,3174, 757,1869, 107,3530,6145,1194, 623,2262, 207,1253, # 2720 -2167,3446,3948, 492,1117,1935, 536,1838,2757,1246,4332, 696,2095,2406,1393,1572, # 2736 -3175,1782, 583, 190, 253,1390,2230, 830,3126,3389, 934,3245,1703,1749,2979,1870, # 2752 -2545,1656,2204, 869,2346,4116,3176,1817, 496,1764,4644, 942,1504, 404,1903,1122, # 2768 -1580,3606,2945,1022, 515, 372,1735, 955,2431,3036,6146,2797,1110,2302,2798, 617, # 2784 -6147, 441, 762,1771,3447,3607,3608,1904, 840,3037, 86, 939,1385, 572,1370,2445, # 2800 -1336, 114,3703, 898, 294, 203,3315, 703,1583,2274, 429, 961,4333,1854,1951,3390, # 2816 -2373,3704,4334,1318,1381, 966,1911,2322,1006,1155, 309, 989, 458,2718,1795,1372, # 2832 -1203, 252,1689,1363,3177, 517,1936, 168,1490, 562, 193,3823,1042,4117,1835, 551, # 2848 - 470,4645, 395, 489,3448,1871,1465,2583,2641, 417,1493, 279,1295, 511,1236,1119, # 2864 - 72,1231,1982,1812,3004, 871,1564, 984,3449,1667,2696,2096,4646,2347,2833,1673, # 2880 -3609, 695,3246,2668, 807,1183,4647, 890, 388,2333,1801,1457,2911,1765,1477,1031, # 2896 -3316,3317,1278,3391,2799,2292,2526, 163,3450,4335,2669,1404,1802,6148,2323,2407, # 2912 -1584,1728,1494,1824,1269, 298, 909,3318,1034,1632, 375, 776,1683,2061, 291, 210, # 2928 -1123, 809,1249,1002,2642,3038, 206,1011,2132, 144, 975, 882,1565, 342, 667, 754, # 2944 -1442,2143,1299,2303,2062, 447, 626,2205,1221,2739,2912,1144,1214,2206,2584, 760, # 2960 -1715, 614, 950,1281,2670,2621, 810, 577,1287,2546,4648, 242,2168, 250,2643, 691, # 2976 - 123,2644, 647, 313,1029, 689,1357,2946,1650, 216, 771,1339,1306, 808,2063, 549, # 2992 - 913,1371,2913,2914,6149,1466,1092,1174,1196,1311,2605,2396,1783,1796,3079, 406, # 3008 -2671,2117,3949,4649, 487,1825,2220,6150,2915, 448,2348,1073,6151,2397,1707, 130, # 3024 - 900,1598, 329, 176,1959,2527,1620,6152,2275,4336,3319,1983,2191,3705,3610,2155, # 3040 -3706,1912,1513,1614,6153,1988, 646, 392,2304,1589,3320,3039,1826,1239,1352,1340, # 3056 -2916, 505,2567,1709,1437,2408,2547, 906,6154,2672, 384,1458,1594,1100,1329, 710, # 3072 - 423,3531,2064,2231,2622,1989,2673,1087,1882, 333, 841,3005,1296,2882,2379, 580, # 3088 -1937,1827,1293,2585, 601, 574, 249,1772,4118,2079,1120, 645, 901,1176,1690, 795, # 3104 -2207, 478,1434, 516,1190,1530, 761,2080, 930,1264, 355, 435,1552, 644,1791, 987, # 3120 - 220,1364,1163,1121,1538, 306,2169,1327,1222, 546,2645, 218, 241, 610,1704,3321, # 3136 -1984,1839,1966,2528, 451,6155,2586,3707,2568, 907,3178, 254,2947, 186,1845,4650, # 3152 - 745, 432,1757, 428,1633, 888,2246,2221,2489,3611,2118,1258,1265, 956,3127,1784, # 3168 -4337,2490, 319, 510, 119, 457,3612, 274,2035,2007,4651,1409,3128, 970,2758, 590, # 3184 -2800, 661,2247,4652,2008,3950,1420,1549,3080,3322,3951,1651,1375,2111, 485,2491, # 3200 -1429,1156,6156,2548,2183,1495, 831,1840,2529,2446, 501,1657, 307,1894,3247,1341, # 3216 - 666, 899,2156,1539,2549,1559, 886, 349,2208,3081,2305,1736,3824,2170,2759,1014, # 3232 -1913,1386, 542,1397,2948, 490, 368, 716, 362, 159, 282,2569,1129,1658,1288,1750, # 3248 -2674, 276, 649,2016, 751,1496, 658,1818,1284,1862,2209,2087,2512,3451, 622,2834, # 3264 - 376, 117,1060,2053,1208,1721,1101,1443, 247,1250,3179,1792,3952,2760,2398,3953, # 3280 -6157,2144,3708, 446,2432,1151,2570,3452,2447,2761,2835,1210,2448,3082, 424,2222, # 3296 -1251,2449,2119,2836, 504,1581,4338, 602, 817, 857,3825,2349,2306, 357,3826,1470, # 3312 -1883,2883, 255, 958, 929,2917,3248, 302,4653,1050,1271,1751,2307,1952,1430,2697, # 3328 -2719,2359, 354,3180, 777, 158,2036,4339,1659,4340,4654,2308,2949,2248,1146,2232, # 3344 -3532,2720,1696,2623,3827,6158,3129,1550,2698,1485,1297,1428, 637, 931,2721,2145, # 3360 - 914,2550,2587, 81,2450, 612, 827,2646,1242,4655,1118,2884, 472,1855,3181,3533, # 3376 -3534, 569,1353,2699,1244,1758,2588,4119,2009,2762,2171,3709,1312,1531,6159,1152, # 3392 -1938, 134,1830, 471,3710,2276,1112,1535,3323,3453,3535, 982,1337,2950, 488, 826, # 3408 - 674,1058,1628,4120,2017, 522,2399, 211, 568,1367,3454, 350, 293,1872,1139,3249, # 3424 -1399,1946,3006,1300,2360,3324, 588, 736,6160,2606, 744, 669,3536,3828,6161,1358, # 3440 - 199, 723, 848, 933, 851,1939,1505,1514,1338,1618,1831,4656,1634,3613, 443,2740, # 3456 -3829, 717,1947, 491,1914,6162,2551,1542,4121,1025,6163,1099,1223, 198,3040,2722, # 3472 - 370, 410,1905,2589, 998,1248,3182,2380, 519,1449,4122,1710, 947, 928,1153,4341, # 3488 -2277, 344,2624,1511, 615, 105, 161,1212,1076,1960,3130,2054,1926,1175,1906,2473, # 3504 - 414,1873,2801,6164,2309, 315,1319,3325, 318,2018,2146,2157, 963, 631, 223,4342, # 3520 -4343,2675, 479,3711,1197,2625,3712,2676,2361,6165,4344,4123,6166,2451,3183,1886, # 3536 -2184,1674,1330,1711,1635,1506, 799, 219,3250,3083,3954,1677,3713,3326,2081,3614, # 3552 -1652,2073,4657,1147,3041,1752, 643,1961, 147,1974,3955,6167,1716,2037, 918,3007, # 3568 -1994, 120,1537, 118, 609,3184,4345, 740,3455,1219, 332,1615,3830,6168,1621,2980, # 3584 -1582, 783, 212, 553,2350,3714,1349,2433,2082,4124, 889,6169,2310,1275,1410, 973, # 3600 - 166,1320,3456,1797,1215,3185,2885,1846,2590,2763,4658, 629, 822,3008, 763, 940, # 3616 -1990,2862, 439,2409,1566,1240,1622, 926,1282,1907,2764, 654,2210,1607, 327,1130, # 3632 -3956,1678,1623,6170,2434,2192, 686, 608,3831,3715, 903,3957,3042,6171,2741,1522, # 3648 -1915,1105,1555,2552,1359, 323,3251,4346,3457, 738,1354,2553,2311,2334,1828,2003, # 3664 -3832,1753,2351,1227,6172,1887,4125,1478,6173,2410,1874,1712,1847, 520,1204,2607, # 3680 - 264,4659, 836,2677,2102, 600,4660,3833,2278,3084,6174,4347,3615,1342, 640, 532, # 3696 - 543,2608,1888,2400,2591,1009,4348,1497, 341,1737,3616,2723,1394, 529,3252,1321, # 3712 - 983,4661,1515,2120, 971,2592, 924, 287,1662,3186,4349,2700,4350,1519, 908,1948, # 3728 -2452, 156, 796,1629,1486,2223,2055, 694,4126,1259,1036,3392,1213,2249,2742,1889, # 3744 -1230,3958,1015, 910, 408, 559,3617,4662, 746, 725, 935,4663,3959,3009,1289, 563, # 3760 - 867,4664,3960,1567,2981,2038,2626, 988,2263,2381,4351, 143,2374, 704,1895,6175, # 3776 -1188,3716,2088, 673,3085,2362,4352, 484,1608,1921,2765,2918, 215, 904,3618,3537, # 3792 - 894, 509, 976,3043,2701,3961,4353,2837,2982, 498,6176,6177,1102,3538,1332,3393, # 3808 -1487,1636,1637, 233, 245,3962, 383, 650, 995,3044, 460,1520,1206,2352, 749,3327, # 3824 - 530, 700, 389,1438,1560,1773,3963,2264, 719,2951,2724,3834, 870,1832,1644,1000, # 3840 - 839,2474,3717, 197,1630,3394, 365,2886,3964,1285,2133, 734, 922, 818,1106, 732, # 3856 - 480,2083,1774,3458, 923,2279,1350, 221,3086, 85,2233,2234,3835,1585,3010,2147, # 3872 -1387,1705,2382,1619,2475, 133, 239,2802,1991,1016,2084,2383, 411,2838,1113, 651, # 3888 -1985,1160,3328, 990,1863,3087,1048,1276,2647, 265,2627,1599,3253,2056, 150, 638, # 3904 -2019, 656, 853, 326,1479, 680,1439,4354,1001,1759, 413,3459,3395,2492,1431, 459, # 3920 -4355,1125,3329,2265,1953,1450,2065,2863, 849, 351,2678,3131,3254,3255,1104,1577, # 3936 - 227,1351,1645,2453,2193,1421,2887, 812,2121, 634, 95,2435, 201,2312,4665,1646, # 3952 -1671,2743,1601,2554,2702,2648,2280,1315,1366,2089,3132,1573,3718,3965,1729,1189, # 3968 - 328,2679,1077,1940,1136, 558,1283, 964,1195, 621,2074,1199,1743,3460,3619,1896, # 3984 -1916,1890,3836,2952,1154,2112,1064, 862, 378,3011,2066,2113,2803,1568,2839,6178, # 4000 -3088,2919,1941,1660,2004,1992,2194, 142, 707,1590,1708,1624,1922,1023,1836,1233, # 4016 -1004,2313, 789, 741,3620,6179,1609,2411,1200,4127,3719,3720,4666,2057,3721, 593, # 4032 -2840, 367,2920,1878,6180,3461,1521, 628,1168, 692,2211,2649, 300, 720,2067,2571, # 4048 -2953,3396, 959,2504,3966,3539,3462,1977, 701,6181, 954,1043, 800, 681, 183,3722, # 4064 -1803,1730,3540,4128,2103, 815,2314, 174, 467, 230,2454,1093,2134, 755,3541,3397, # 4080 -1141,1162,6182,1738,2039, 270,3256,2513,1005,1647,2185,3837, 858,1679,1897,1719, # 4096 -2954,2324,1806, 402, 670, 167,4129,1498,2158,2104, 750,6183, 915, 189,1680,1551, # 4112 - 455,4356,1501,2455, 405,1095,2955, 338,1586,1266,1819, 570, 641,1324, 237,1556, # 4128 -2650,1388,3723,6184,1368,2384,1343,1978,3089,2436, 879,3724, 792,1191, 758,3012, # 4144 -1411,2135,1322,4357, 240,4667,1848,3725,1574,6185, 420,3045,1546,1391, 714,4358, # 4160 -1967, 941,1864, 863, 664, 426, 560,1731,2680,1785,2864,1949,2363, 403,3330,1415, # 4176 -1279,2136,1697,2335, 204, 721,2097,3838, 90,6186,2085,2505, 191,3967, 124,2148, # 4192 -1376,1798,1178,1107,1898,1405, 860,4359,1243,1272,2375,2983,1558,2456,1638, 113, # 4208 -3621, 578,1923,2609, 880, 386,4130, 784,2186,2266,1422,2956,2172,1722, 497, 263, # 4224 -2514,1267,2412,2610, 177,2703,3542, 774,1927,1344, 616,1432,1595,1018, 172,4360, # 4240 -2325, 911,4361, 438,1468,3622, 794,3968,2024,2173,1681,1829,2957, 945, 895,3090, # 4256 - 575,2212,2476, 475,2401,2681, 785,2744,1745,2293,2555,1975,3133,2865, 394,4668, # 4272 -3839, 635,4131, 639, 202,1507,2195,2766,1345,1435,2572,3726,1908,1184,1181,2457, # 4288 -3727,3134,4362, 843,2611, 437, 916,4669, 234, 769,1884,3046,3047,3623, 833,6187, # 4304 -1639,2250,2402,1355,1185,2010,2047, 999, 525,1732,1290,1488,2612, 948,1578,3728, # 4320 -2413,2477,1216,2725,2159, 334,3840,1328,3624,2921,1525,4132, 564,1056, 891,4363, # 4336 -1444,1698,2385,2251,3729,1365,2281,2235,1717,6188, 864,3841,2515, 444, 527,2767, # 4352 -2922,3625, 544, 461,6189, 566, 209,2437,3398,2098,1065,2068,3331,3626,3257,2137, # 4368 #last 512 -) - - diff --git a/pipenv/vendor/chardet/jpcntx.py b/pipenv/vendor/chardet/jpcntx.py deleted file mode 100644 index 20044e4b..00000000 --- a/pipenv/vendor/chardet/jpcntx.py +++ /dev/null @@ -1,233 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - - -# This is hiragana 2-char sequence table, the number in each cell represents its frequency category -jp2CharContext = ( -(0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1), -(2,4,0,4,0,3,0,4,0,3,4,4,4,2,4,3,3,4,3,2,3,3,4,2,3,3,3,2,4,1,4,3,3,1,5,4,3,4,3,4,3,5,3,0,3,5,4,2,0,3,1,0,3,3,0,3,3,0,1,1,0,4,3,0,3,3,0,4,0,2,0,3,5,5,5,5,4,0,4,1,0,3,4), -(0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2), -(0,4,0,5,0,5,0,4,0,4,5,4,4,3,5,3,5,1,5,3,4,3,4,4,3,4,3,3,4,3,5,4,4,3,5,5,3,5,5,5,3,5,5,3,4,5,5,3,1,3,2,0,3,4,0,4,2,0,4,2,1,5,3,2,3,5,0,4,0,2,0,5,4,4,5,4,5,0,4,0,0,4,4), -(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), -(0,3,0,4,0,3,0,3,0,4,5,4,3,3,3,3,4,3,5,4,4,3,5,4,4,3,4,3,4,4,4,4,5,3,4,4,3,4,5,5,4,5,5,1,4,5,4,3,0,3,3,1,3,3,0,4,4,0,3,3,1,5,3,3,3,5,0,4,0,3,0,4,4,3,4,3,3,0,4,1,1,3,4), -(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), -(0,4,0,3,0,3,0,4,0,3,4,4,3,2,2,1,2,1,3,1,3,3,3,3,3,4,3,1,3,3,5,3,3,0,4,3,0,5,4,3,3,5,4,4,3,4,4,5,0,1,2,0,1,2,0,2,2,0,1,0,0,5,2,2,1,4,0,3,0,1,0,4,4,3,5,4,3,0,2,1,0,4,3), -(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), -(0,3,0,5,0,4,0,2,1,4,4,2,4,1,4,2,4,2,4,3,3,3,4,3,3,3,3,1,4,2,3,3,3,1,4,4,1,1,1,4,3,3,2,0,2,4,3,2,0,3,3,0,3,1,1,0,0,0,3,3,0,4,2,2,3,4,0,4,0,3,0,4,4,5,3,4,4,0,3,0,0,1,4), -(1,4,0,4,0,4,0,4,0,3,5,4,4,3,4,3,5,4,3,3,4,3,5,4,4,4,4,3,4,2,4,3,3,1,5,4,3,2,4,5,4,5,5,4,4,5,4,4,0,3,2,2,3,3,0,4,3,1,3,2,1,4,3,3,4,5,0,3,0,2,0,4,5,5,4,5,4,0,4,0,0,5,4), -(0,5,0,5,0,4,0,3,0,4,4,3,4,3,3,3,4,0,4,4,4,3,4,3,4,3,3,1,4,2,4,3,4,0,5,4,1,4,5,4,4,5,3,2,4,3,4,3,2,4,1,3,3,3,2,3,2,0,4,3,3,4,3,3,3,4,0,4,0,3,0,4,5,4,4,4,3,0,4,1,0,1,3), -(0,3,1,4,0,3,0,2,0,3,4,4,3,1,4,2,3,3,4,3,4,3,4,3,4,4,3,2,3,1,5,4,4,1,4,4,3,5,4,4,3,5,5,4,3,4,4,3,1,2,3,1,2,2,0,3,2,0,3,1,0,5,3,3,3,4,3,3,3,3,4,4,4,4,5,4,2,0,3,3,2,4,3), -(0,2,0,3,0,1,0,1,0,0,3,2,0,0,2,0,1,0,2,1,3,3,3,1,2,3,1,0,1,0,4,2,1,1,3,3,0,4,3,3,1,4,3,3,0,3,3,2,0,0,0,0,1,0,0,2,0,0,0,0,0,4,1,0,2,3,2,2,2,1,3,3,3,4,4,3,2,0,3,1,0,3,3), -(0,4,0,4,0,3,0,3,0,4,4,4,3,3,3,3,3,3,4,3,4,2,4,3,4,3,3,2,4,3,4,5,4,1,4,5,3,5,4,5,3,5,4,0,3,5,5,3,1,3,3,2,2,3,0,3,4,1,3,3,2,4,3,3,3,4,0,4,0,3,0,4,5,4,4,5,3,0,4,1,0,3,4), -(0,2,0,3,0,3,0,0,0,2,2,2,1,0,1,0,0,0,3,0,3,0,3,0,1,3,1,0,3,1,3,3,3,1,3,3,3,0,1,3,1,3,4,0,0,3,1,1,0,3,2,0,0,0,0,1,3,0,1,0,0,3,3,2,0,3,0,0,0,0,0,3,4,3,4,3,3,0,3,0,0,2,3), -(2,3,0,3,0,2,0,1,0,3,3,4,3,1,3,1,1,1,3,1,4,3,4,3,3,3,0,0,3,1,5,4,3,1,4,3,2,5,5,4,4,4,4,3,3,4,4,4,0,2,1,1,3,2,0,1,2,0,0,1,0,4,1,3,3,3,0,3,0,1,0,4,4,4,5,5,3,0,2,0,0,4,4), -(0,2,0,1,0,3,1,3,0,2,3,3,3,0,3,1,0,0,3,0,3,2,3,1,3,2,1,1,0,0,4,2,1,0,2,3,1,4,3,2,0,4,4,3,1,3,1,3,0,1,0,0,1,0,0,0,1,0,0,0,0,4,1,1,1,2,0,3,0,0,0,3,4,2,4,3,2,0,1,0,0,3,3), -(0,1,0,4,0,5,0,4,0,2,4,4,2,3,3,2,3,3,5,3,3,3,4,3,4,2,3,0,4,3,3,3,4,1,4,3,2,1,5,5,3,4,5,1,3,5,4,2,0,3,3,0,1,3,0,4,2,0,1,3,1,4,3,3,3,3,0,3,0,1,0,3,4,4,4,5,5,0,3,0,1,4,5), -(0,2,0,3,0,3,0,0,0,2,3,1,3,0,4,0,1,1,3,0,3,4,3,2,3,1,0,3,3,2,3,1,3,0,2,3,0,2,1,4,1,2,2,0,0,3,3,0,0,2,0,0,0,1,0,0,0,0,2,2,0,3,2,1,3,3,0,2,0,2,0,0,3,3,1,2,4,0,3,0,2,2,3), -(2,4,0,5,0,4,0,4,0,2,4,4,4,3,4,3,3,3,1,2,4,3,4,3,4,4,5,0,3,3,3,3,2,0,4,3,1,4,3,4,1,4,4,3,3,4,4,3,1,2,3,0,4,2,0,4,1,0,3,3,0,4,3,3,3,4,0,4,0,2,0,3,5,3,4,5,2,0,3,0,0,4,5), -(0,3,0,4,0,1,0,1,0,1,3,2,2,1,3,0,3,0,2,0,2,0,3,0,2,0,0,0,1,0,1,1,0,0,3,1,0,0,0,4,0,3,1,0,2,1,3,0,0,0,0,0,0,3,0,0,0,0,0,0,0,4,2,2,3,1,0,3,0,0,0,1,4,4,4,3,0,0,4,0,0,1,4), -(1,4,1,5,0,3,0,3,0,4,5,4,4,3,5,3,3,4,4,3,4,1,3,3,3,3,2,1,4,1,5,4,3,1,4,4,3,5,4,4,3,5,4,3,3,4,4,4,0,3,3,1,2,3,0,3,1,0,3,3,0,5,4,4,4,4,4,4,3,3,5,4,4,3,3,5,4,0,3,2,0,4,4), -(0,2,0,3,0,1,0,0,0,1,3,3,3,2,4,1,3,0,3,1,3,0,2,2,1,1,0,0,2,0,4,3,1,0,4,3,0,4,4,4,1,4,3,1,1,3,3,1,0,2,0,0,1,3,0,0,0,0,2,0,0,4,3,2,4,3,5,4,3,3,3,4,3,3,4,3,3,0,2,1,0,3,3), -(0,2,0,4,0,3,0,2,0,2,5,5,3,4,4,4,4,1,4,3,3,0,4,3,4,3,1,3,3,2,4,3,0,3,4,3,0,3,4,4,2,4,4,0,4,5,3,3,2,2,1,1,1,2,0,1,5,0,3,3,2,4,3,3,3,4,0,3,0,2,0,4,4,3,5,5,0,0,3,0,2,3,3), -(0,3,0,4,0,3,0,1,0,3,4,3,3,1,3,3,3,0,3,1,3,0,4,3,3,1,1,0,3,0,3,3,0,0,4,4,0,1,5,4,3,3,5,0,3,3,4,3,0,2,0,1,1,1,0,1,3,0,1,2,1,3,3,2,3,3,0,3,0,1,0,1,3,3,4,4,1,0,1,2,2,1,3), -(0,1,0,4,0,4,0,3,0,1,3,3,3,2,3,1,1,0,3,0,3,3,4,3,2,4,2,0,1,0,4,3,2,0,4,3,0,5,3,3,2,4,4,4,3,3,3,4,0,1,3,0,0,1,0,0,1,0,0,0,0,4,2,3,3,3,0,3,0,0,0,4,4,4,5,3,2,0,3,3,0,3,5), -(0,2,0,3,0,0,0,3,0,1,3,0,2,0,0,0,1,0,3,1,1,3,3,0,0,3,0,0,3,0,2,3,1,0,3,1,0,3,3,2,0,4,2,2,0,2,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,2,1,2,0,1,0,1,0,0,0,1,3,1,2,0,0,0,1,0,0,1,4), -(0,3,0,3,0,5,0,1,0,2,4,3,1,3,3,2,1,1,5,2,1,0,5,1,2,0,0,0,3,3,2,2,3,2,4,3,0,0,3,3,1,3,3,0,2,5,3,4,0,3,3,0,1,2,0,2,2,0,3,2,0,2,2,3,3,3,0,2,0,1,0,3,4,4,2,5,4,0,3,0,0,3,5), -(0,3,0,3,0,3,0,1,0,3,3,3,3,0,3,0,2,0,2,1,1,0,2,0,1,0,0,0,2,1,0,0,1,0,3,2,0,0,3,3,1,2,3,1,0,3,3,0,0,1,0,0,0,0,0,2,0,0,0,0,0,2,3,1,2,3,0,3,0,1,0,3,2,1,0,4,3,0,1,1,0,3,3), -(0,4,0,5,0,3,0,3,0,4,5,5,4,3,5,3,4,3,5,3,3,2,5,3,4,4,4,3,4,3,4,5,5,3,4,4,3,4,4,5,4,4,4,3,4,5,5,4,2,3,4,2,3,4,0,3,3,1,4,3,2,4,3,3,5,5,0,3,0,3,0,5,5,5,5,4,4,0,4,0,1,4,4), -(0,4,0,4,0,3,0,3,0,3,5,4,4,2,3,2,5,1,3,2,5,1,4,2,3,2,3,3,4,3,3,3,3,2,5,4,1,3,3,5,3,4,4,0,4,4,3,1,1,3,1,0,2,3,0,2,3,0,3,0,0,4,3,1,3,4,0,3,0,2,0,4,4,4,3,4,5,0,4,0,0,3,4), -(0,3,0,3,0,3,1,2,0,3,4,4,3,3,3,0,2,2,4,3,3,1,3,3,3,1,1,0,3,1,4,3,2,3,4,4,2,4,4,4,3,4,4,3,2,4,4,3,1,3,3,1,3,3,0,4,1,0,2,2,1,4,3,2,3,3,5,4,3,3,5,4,4,3,3,0,4,0,3,2,2,4,4), -(0,2,0,1,0,0,0,0,0,1,2,1,3,0,0,0,0,0,2,0,1,2,1,0,0,1,0,0,0,0,3,0,0,1,0,1,1,3,1,0,0,0,1,1,0,1,1,0,0,0,0,0,2,0,0,0,0,0,0,0,0,1,1,2,2,0,3,4,0,0,0,1,1,0,0,1,0,0,0,0,0,1,1), -(0,1,0,0,0,1,0,0,0,0,4,0,4,1,4,0,3,0,4,0,3,0,4,0,3,0,3,0,4,1,5,1,4,0,0,3,0,5,0,5,2,0,1,0,0,0,2,1,4,0,1,3,0,0,3,0,0,3,1,1,4,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0), -(1,4,0,5,0,3,0,2,0,3,5,4,4,3,4,3,5,3,4,3,3,0,4,3,3,3,3,3,3,2,4,4,3,1,3,4,4,5,4,4,3,4,4,1,3,5,4,3,3,3,1,2,2,3,3,1,3,1,3,3,3,5,3,3,4,5,0,3,0,3,0,3,4,3,4,4,3,0,3,0,2,4,3), -(0,1,0,4,0,0,0,0,0,1,4,0,4,1,4,2,4,0,3,0,1,0,1,0,0,0,0,0,2,0,3,1,1,1,0,3,0,0,0,1,2,1,0,0,1,1,1,1,0,1,0,0,0,1,0,0,3,0,0,0,0,3,2,0,2,2,0,1,0,0,0,2,3,2,3,3,0,0,0,0,2,1,0), -(0,5,1,5,0,3,0,3,0,5,4,4,5,1,5,3,3,0,4,3,4,3,5,3,4,3,3,2,4,3,4,3,3,0,3,3,1,4,4,3,4,4,4,3,4,5,5,3,2,3,1,1,3,3,1,3,1,1,3,3,2,4,5,3,3,5,0,4,0,3,0,4,4,3,5,3,3,0,3,4,0,4,3), -(0,5,0,5,0,3,0,2,0,4,4,3,5,2,4,3,3,3,4,4,4,3,5,3,5,3,3,1,4,0,4,3,3,0,3,3,0,4,4,4,4,5,4,3,3,5,5,3,2,3,1,2,3,2,0,1,0,0,3,2,2,4,4,3,1,5,0,4,0,3,0,4,3,1,3,2,1,0,3,3,0,3,3), -(0,4,0,5,0,5,0,4,0,4,5,5,5,3,4,3,3,2,5,4,4,3,5,3,5,3,4,0,4,3,4,4,3,2,4,4,3,4,5,4,4,5,5,0,3,5,5,4,1,3,3,2,3,3,1,3,1,0,4,3,1,4,4,3,4,5,0,4,0,2,0,4,3,4,4,3,3,0,4,0,0,5,5), -(0,4,0,4,0,5,0,1,1,3,3,4,4,3,4,1,3,0,5,1,3,0,3,1,3,1,1,0,3,0,3,3,4,0,4,3,0,4,4,4,3,4,4,0,3,5,4,1,0,3,0,0,2,3,0,3,1,0,3,1,0,3,2,1,3,5,0,3,0,1,0,3,2,3,3,4,4,0,2,2,0,4,4), -(2,4,0,5,0,4,0,3,0,4,5,5,4,3,5,3,5,3,5,3,5,2,5,3,4,3,3,4,3,4,5,3,2,1,5,4,3,2,3,4,5,3,4,1,2,5,4,3,0,3,3,0,3,2,0,2,3,0,4,1,0,3,4,3,3,5,0,3,0,1,0,4,5,5,5,4,3,0,4,2,0,3,5), -(0,5,0,4,0,4,0,2,0,5,4,3,4,3,4,3,3,3,4,3,4,2,5,3,5,3,4,1,4,3,4,4,4,0,3,5,0,4,4,4,4,5,3,1,3,4,5,3,3,3,3,3,3,3,0,2,2,0,3,3,2,4,3,3,3,5,3,4,1,3,3,5,3,2,0,0,0,0,4,3,1,3,3), -(0,1,0,3,0,3,0,1,0,1,3,3,3,2,3,3,3,0,3,0,0,0,3,1,3,0,0,0,2,2,2,3,0,0,3,2,0,1,2,4,1,3,3,0,0,3,3,3,0,1,0,0,2,1,0,0,3,0,3,1,0,3,0,0,1,3,0,2,0,1,0,3,3,1,3,3,0,0,1,1,0,3,3), -(0,2,0,3,0,2,1,4,0,2,2,3,1,1,3,1,1,0,2,0,3,1,2,3,1,3,0,0,1,0,4,3,2,3,3,3,1,4,2,3,3,3,3,1,0,3,1,4,0,1,1,0,1,2,0,1,1,0,1,1,0,3,1,3,2,2,0,1,0,0,0,2,3,3,3,1,0,0,0,0,0,2,3), -(0,5,0,4,0,5,0,2,0,4,5,5,3,3,4,3,3,1,5,4,4,2,4,4,4,3,4,2,4,3,5,5,4,3,3,4,3,3,5,5,4,5,5,1,3,4,5,3,1,4,3,1,3,3,0,3,3,1,4,3,1,4,5,3,3,5,0,4,0,3,0,5,3,3,1,4,3,0,4,0,1,5,3), -(0,5,0,5,0,4,0,2,0,4,4,3,4,3,3,3,3,3,5,4,4,4,4,4,4,5,3,3,5,2,4,4,4,3,4,4,3,3,4,4,5,5,3,3,4,3,4,3,3,4,3,3,3,3,1,2,2,1,4,3,3,5,4,4,3,4,0,4,0,3,0,4,4,4,4,4,1,0,4,2,0,2,4), -(0,4,0,4,0,3,0,1,0,3,5,2,3,0,3,0,2,1,4,2,3,3,4,1,4,3,3,2,4,1,3,3,3,0,3,3,0,0,3,3,3,5,3,3,3,3,3,2,0,2,0,0,2,0,0,2,0,0,1,0,0,3,1,2,2,3,0,3,0,2,0,4,4,3,3,4,1,0,3,0,0,2,4), -(0,0,0,4,0,0,0,0,0,0,1,0,1,0,2,0,0,0,0,0,1,0,2,0,1,0,0,0,0,0,3,1,3,0,3,2,0,0,0,1,0,3,2,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,4,0,2,0,0,0,0,0,0,2), -(0,2,1,3,0,2,0,2,0,3,3,3,3,1,3,1,3,3,3,3,3,3,4,2,2,1,2,1,4,0,4,3,1,3,3,3,2,4,3,5,4,3,3,3,3,3,3,3,0,1,3,0,2,0,0,1,0,0,1,0,0,4,2,0,2,3,0,3,3,0,3,3,4,2,3,1,4,0,1,2,0,2,3), -(0,3,0,3,0,1,0,3,0,2,3,3,3,0,3,1,2,0,3,3,2,3,3,2,3,2,3,1,3,0,4,3,2,0,3,3,1,4,3,3,2,3,4,3,1,3,3,1,1,0,1,1,0,1,0,1,0,1,0,0,0,4,1,1,0,3,0,3,1,0,2,3,3,3,3,3,1,0,0,2,0,3,3), -(0,0,0,0,0,0,0,0,0,0,3,0,2,0,3,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,3,0,3,0,3,1,0,1,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,2,0,2,3,0,0,0,0,0,0,0,0,3), -(0,2,0,3,1,3,0,3,0,2,3,3,3,1,3,1,3,1,3,1,3,3,3,1,3,0,2,3,1,1,4,3,3,2,3,3,1,2,2,4,1,3,3,0,1,4,2,3,0,1,3,0,3,0,0,1,3,0,2,0,0,3,3,2,1,3,0,3,0,2,0,3,4,4,4,3,1,0,3,0,0,3,3), -(0,2,0,1,0,2,0,0,0,1,3,2,2,1,3,0,1,1,3,0,3,2,3,1,2,0,2,0,1,1,3,3,3,0,3,3,1,1,2,3,2,3,3,1,2,3,2,0,0,1,0,0,0,0,0,0,3,0,1,0,0,2,1,2,1,3,0,3,0,0,0,3,4,4,4,3,2,0,2,0,0,2,4), -(0,0,0,1,0,1,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,2,2,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,3,1,0,0,0,0,0,0,0,3), -(0,3,0,3,0,2,0,3,0,3,3,3,2,3,2,2,2,0,3,1,3,3,3,2,3,3,0,0,3,0,3,2,2,0,2,3,1,4,3,4,3,3,2,3,1,5,4,4,0,3,1,2,1,3,0,3,1,1,2,0,2,3,1,3,1,3,0,3,0,1,0,3,3,4,4,2,1,0,2,1,0,2,4), -(0,1,0,3,0,1,0,2,0,1,4,2,5,1,4,0,2,0,2,1,3,1,4,0,2,1,0,0,2,1,4,1,1,0,3,3,0,5,1,3,2,3,3,1,0,3,2,3,0,1,0,0,0,0,0,0,1,0,0,0,0,4,0,1,0,3,0,2,0,1,0,3,3,3,4,3,3,0,0,0,0,2,3), -(0,0,0,1,0,0,0,0,0,0,2,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1,0,0,1,0,0,0,0,0,3), -(0,1,0,3,0,4,0,3,0,2,4,3,1,0,3,2,2,1,3,1,2,2,3,1,1,1,2,1,3,0,1,2,0,1,3,2,1,3,0,5,5,1,0,0,1,3,2,1,0,3,0,0,1,0,0,0,0,0,3,4,0,1,1,1,3,2,0,2,0,1,0,2,3,3,1,2,3,0,1,0,1,0,4), -(0,0,0,1,0,3,0,3,0,2,2,1,0,0,4,0,3,0,3,1,3,0,3,0,3,0,1,0,3,0,3,1,3,0,3,3,0,0,1,2,1,1,1,0,1,2,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,2,2,1,2,0,0,2,0,0,0,0,2,3,3,3,3,0,0,0,0,1,4), -(0,0,0,3,0,3,0,0,0,0,3,1,1,0,3,0,1,0,2,0,1,0,0,0,0,0,0,0,1,0,3,0,2,0,2,3,0,0,2,2,3,1,2,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,2,0,0,0,0,2,3), -(2,4,0,5,0,5,0,4,0,3,4,3,3,3,4,3,3,3,4,3,4,4,5,4,5,5,5,2,3,0,5,5,4,1,5,4,3,1,5,4,3,4,4,3,3,4,3,3,0,3,2,0,2,3,0,3,0,0,3,3,0,5,3,2,3,3,0,3,0,3,0,3,4,5,4,5,3,0,4,3,0,3,4), -(0,3,0,3,0,3,0,3,0,3,3,4,3,2,3,2,3,0,4,3,3,3,3,3,3,3,3,0,3,2,4,3,3,1,3,4,3,4,4,4,3,4,4,3,2,4,4,1,0,2,0,0,1,1,0,2,0,0,3,1,0,5,3,2,1,3,0,3,0,1,2,4,3,2,4,3,3,0,3,2,0,4,4), -(0,3,0,3,0,1,0,0,0,1,4,3,3,2,3,1,3,1,4,2,3,2,4,2,3,4,3,0,2,2,3,3,3,0,3,3,3,0,3,4,1,3,3,0,3,4,3,3,0,1,1,0,1,0,0,0,4,0,3,0,0,3,1,2,1,3,0,4,0,1,0,4,3,3,4,3,3,0,2,0,0,3,3), -(0,3,0,4,0,1,0,3,0,3,4,3,3,0,3,3,3,1,3,1,3,3,4,3,3,3,0,0,3,1,5,3,3,1,3,3,2,5,4,3,3,4,5,3,2,5,3,4,0,1,0,0,0,0,0,2,0,0,1,1,0,4,2,2,1,3,0,3,0,2,0,4,4,3,5,3,2,0,1,1,0,3,4), -(0,5,0,4,0,5,0,2,0,4,4,3,3,2,3,3,3,1,4,3,4,1,5,3,4,3,4,0,4,2,4,3,4,1,5,4,0,4,4,4,4,5,4,1,3,5,4,2,1,4,1,1,3,2,0,3,1,0,3,2,1,4,3,3,3,4,0,4,0,3,0,4,4,4,3,3,3,0,4,2,0,3,4), -(1,4,0,4,0,3,0,1,0,3,3,3,1,1,3,3,2,2,3,3,1,0,3,2,2,1,2,0,3,1,2,1,2,0,3,2,0,2,2,3,3,4,3,0,3,3,1,2,0,1,1,3,1,2,0,0,3,0,1,1,0,3,2,2,3,3,0,3,0,0,0,2,3,3,4,3,3,0,1,0,0,1,4), -(0,4,0,4,0,4,0,0,0,3,4,4,3,1,4,2,3,2,3,3,3,1,4,3,4,0,3,0,4,2,3,3,2,2,5,4,2,1,3,4,3,4,3,1,3,3,4,2,0,2,1,0,3,3,0,0,2,0,3,1,0,4,4,3,4,3,0,4,0,1,0,2,4,4,4,4,4,0,3,2,0,3,3), -(0,0,0,1,0,4,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,3,2,0,0,1,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,2), -(0,2,0,3,0,4,0,4,0,1,3,3,3,0,4,0,2,1,2,1,1,1,2,0,3,1,1,0,1,0,3,1,0,0,3,3,2,0,1,1,0,0,0,0,0,1,0,2,0,2,2,0,3,1,0,0,1,0,1,1,0,1,2,0,3,0,0,0,0,1,0,0,3,3,4,3,1,0,1,0,3,0,2), -(0,0,0,3,0,5,0,0,0,0,1,0,2,0,3,1,0,1,3,0,0,0,2,0,0,0,1,0,0,0,1,1,0,0,4,0,0,0,2,3,0,1,4,1,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,1,0,0,0,0,0,0,0,2,0,0,3,0,0,0,0,0,3), -(0,2,0,5,0,5,0,1,0,2,4,3,3,2,5,1,3,2,3,3,3,0,4,1,2,0,3,0,4,0,2,2,1,1,5,3,0,0,1,4,2,3,2,0,3,3,3,2,0,2,4,1,1,2,0,1,1,0,3,1,0,1,3,1,2,3,0,2,0,0,0,1,3,5,4,4,4,0,3,0,0,1,3), -(0,4,0,5,0,4,0,4,0,4,5,4,3,3,4,3,3,3,4,3,4,4,5,3,4,5,4,2,4,2,3,4,3,1,4,4,1,3,5,4,4,5,5,4,4,5,5,5,2,3,3,1,4,3,1,3,3,0,3,3,1,4,3,4,4,4,0,3,0,4,0,3,3,4,4,5,0,0,4,3,0,4,5), -(0,4,0,4,0,3,0,3,0,3,4,4,4,3,3,2,4,3,4,3,4,3,5,3,4,3,2,1,4,2,4,4,3,1,3,4,2,4,5,5,3,4,5,4,1,5,4,3,0,3,2,2,3,2,1,3,1,0,3,3,3,5,3,3,3,5,4,4,2,3,3,4,3,3,3,2,1,0,3,2,1,4,3), -(0,4,0,5,0,4,0,3,0,3,5,5,3,2,4,3,4,0,5,4,4,1,4,4,4,3,3,3,4,3,5,5,2,3,3,4,1,2,5,5,3,5,5,2,3,5,5,4,0,3,2,0,3,3,1,1,5,1,4,1,0,4,3,2,3,5,0,4,0,3,0,5,4,3,4,3,0,0,4,1,0,4,4), -(1,3,0,4,0,2,0,2,0,2,5,5,3,3,3,3,3,0,4,2,3,4,4,4,3,4,0,0,3,4,5,4,3,3,3,3,2,5,5,4,5,5,5,4,3,5,5,5,1,3,1,0,1,0,0,3,2,0,4,2,0,5,2,3,2,4,1,3,0,3,0,4,5,4,5,4,3,0,4,2,0,5,4), -(0,3,0,4,0,5,0,3,0,3,4,4,3,2,3,2,3,3,3,3,3,2,4,3,3,2,2,0,3,3,3,3,3,1,3,3,3,0,4,4,3,4,4,1,1,4,4,2,0,3,1,0,1,1,0,4,1,0,2,3,1,3,3,1,3,4,0,3,0,1,0,3,1,3,0,0,1,0,2,0,0,4,4), -(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), -(0,3,0,3,0,2,0,3,0,1,5,4,3,3,3,1,4,2,1,2,3,4,4,2,4,4,5,0,3,1,4,3,4,0,4,3,3,3,2,3,2,5,3,4,3,2,2,3,0,0,3,0,2,1,0,1,2,0,0,0,0,2,1,1,3,1,0,2,0,4,0,3,4,4,4,5,2,0,2,0,0,1,3), -(0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,1,1,0,0,1,1,0,0,0,4,2,1,1,0,1,0,3,2,0,0,3,1,1,1,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,1,0,0,0,2,0,0,0,1,4,0,4,2,1,0,0,0,0,0,1), -(0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,1,0,0,0,0,0,0,1,0,1,0,0,0,0,3,1,0,0,0,2,0,2,1,0,0,1,2,1,0,1,1,0,0,3,0,0,0,0,0,0,0,0,0,0,0,1,3,1,0,0,0,0,0,1,0,0,2,1,0,0,0,0,0,0,0,0,2), -(0,4,0,4,0,4,0,3,0,4,4,3,4,2,4,3,2,0,4,4,4,3,5,3,5,3,3,2,4,2,4,3,4,3,1,4,0,2,3,4,4,4,3,3,3,4,4,4,3,4,1,3,4,3,2,1,2,1,3,3,3,4,4,3,3,5,0,4,0,3,0,4,3,3,3,2,1,0,3,0,0,3,3), -(0,4,0,3,0,3,0,3,0,3,5,5,3,3,3,3,4,3,4,3,3,3,4,4,4,3,3,3,3,4,3,5,3,3,1,3,2,4,5,5,5,5,4,3,4,5,5,3,2,2,3,3,3,3,2,3,3,1,2,3,2,4,3,3,3,4,0,4,0,2,0,4,3,2,2,1,2,0,3,0,0,4,1), -) - -class JapaneseContextAnalysis(object): - NUM_OF_CATEGORY = 6 - DONT_KNOW = -1 - ENOUGH_REL_THRESHOLD = 100 - MAX_REL_THRESHOLD = 1000 - MINIMUM_DATA_THRESHOLD = 4 - - def __init__(self): - self._total_rel = None - self._rel_sample = None - self._need_to_skip_char_num = None - self._last_char_order = None - self._done = None - self.reset() - - def reset(self): - self._total_rel = 0 # total sequence received - # category counters, each integer counts sequence in its category - self._rel_sample = [0] * self.NUM_OF_CATEGORY - # if last byte in current buffer is not the last byte of a character, - # we need to know how many bytes to skip in next buffer - self._need_to_skip_char_num = 0 - self._last_char_order = -1 # The order of previous char - # If this flag is set to True, detection is done and conclusion has - # been made - self._done = False - - def feed(self, byte_str, num_bytes): - if self._done: - return - - # The buffer we got is byte oriented, and a character may span in more than one - # buffers. In case the last one or two byte in last buffer is not - # complete, we record how many byte needed to complete that character - # and skip these bytes here. We can choose to record those bytes as - # well and analyse the character once it is complete, but since a - # character will not make much difference, by simply skipping - # this character will simply our logic and improve performance. - i = self._need_to_skip_char_num - while i < num_bytes: - order, char_len = self.get_order(byte_str[i:i + 2]) - i += char_len - if i > num_bytes: - self._need_to_skip_char_num = i - num_bytes - self._last_char_order = -1 - else: - if (order != -1) and (self._last_char_order != -1): - self._total_rel += 1 - if self._total_rel > self.MAX_REL_THRESHOLD: - self._done = True - break - self._rel_sample[jp2CharContext[self._last_char_order][order]] += 1 - self._last_char_order = order - - def got_enough_data(self): - return self._total_rel > self.ENOUGH_REL_THRESHOLD - - def get_confidence(self): - # This is just one way to calculate confidence. It works well for me. - if self._total_rel > self.MINIMUM_DATA_THRESHOLD: - return (self._total_rel - self._rel_sample[0]) / self._total_rel - else: - return self.DONT_KNOW - - def get_order(self, byte_str): - return -1, 1 - -class SJISContextAnalysis(JapaneseContextAnalysis): - def __init__(self): - super(SJISContextAnalysis, self).__init__() - self._charset_name = "SHIFT_JIS" - - @property - def charset_name(self): - return self._charset_name - - def get_order(self, byte_str): - if not byte_str: - return -1, 1 - # find out current char's byte length - first_char = byte_str[0] - if (0x81 <= first_char <= 0x9F) or (0xE0 <= first_char <= 0xFC): - char_len = 2 - if (first_char == 0x87) or (0xFA <= first_char <= 0xFC): - self._charset_name = "CP932" - else: - char_len = 1 - - # return its order if it is hiragana - if len(byte_str) > 1: - second_char = byte_str[1] - if (first_char == 202) and (0x9F <= second_char <= 0xF1): - return second_char - 0x9F, char_len - - return -1, char_len - -class EUCJPContextAnalysis(JapaneseContextAnalysis): - def get_order(self, byte_str): - if not byte_str: - return -1, 1 - # find out current char's byte length - first_char = byte_str[0] - if (first_char == 0x8E) or (0xA1 <= first_char <= 0xFE): - char_len = 2 - elif first_char == 0x8F: - char_len = 3 - else: - char_len = 1 - - # return its order if it is hiragana - if len(byte_str) > 1: - second_char = byte_str[1] - if (first_char == 0xA4) and (0xA1 <= second_char <= 0xF3): - return second_char - 0xA1, char_len - - return -1, char_len - - diff --git a/pipenv/vendor/chardet/langbulgarianmodel.py b/pipenv/vendor/chardet/langbulgarianmodel.py deleted file mode 100644 index 2aa4fb2e..00000000 --- a/pipenv/vendor/chardet/langbulgarianmodel.py +++ /dev/null @@ -1,228 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -# 255: Control characters that usually does not exist in any text -# 254: Carriage/Return -# 253: symbol (punctuation) that does not belong to word -# 252: 0 - 9 - -# Character Mapping Table: -# this table is modified base on win1251BulgarianCharToOrderMap, so -# only number <64 is sure valid - -Latin5_BulgarianCharToOrderMap = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253, 77, 90, 99,100, 72,109,107,101, 79,185, 81,102, 76, 94, 82, # 40 -110,186,108, 91, 74,119, 84, 96,111,187,115,253,253,253,253,253, # 50 -253, 65, 69, 70, 66, 63, 68,112,103, 92,194,104, 95, 86, 87, 71, # 60 -116,195, 85, 93, 97,113,196,197,198,199,200,253,253,253,253,253, # 70 -194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209, # 80 -210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225, # 90 - 81,226,227,228,229,230,105,231,232,233,234,235,236, 45,237,238, # a0 - 31, 32, 35, 43, 37, 44, 55, 47, 40, 59, 33, 46, 38, 36, 41, 30, # b0 - 39, 28, 34, 51, 48, 49, 53, 50, 54, 57, 61,239, 67,240, 60, 56, # c0 - 1, 18, 9, 20, 11, 3, 23, 15, 2, 26, 12, 10, 14, 6, 4, 13, # d0 - 7, 8, 5, 19, 29, 25, 22, 21, 27, 24, 17, 75, 52,241, 42, 16, # e0 - 62,242,243,244, 58,245, 98,246,247,248,249,250,251, 91,252,253, # f0 -) - -win1251BulgarianCharToOrderMap = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253, 77, 90, 99,100, 72,109,107,101, 79,185, 81,102, 76, 94, 82, # 40 -110,186,108, 91, 74,119, 84, 96,111,187,115,253,253,253,253,253, # 50 -253, 65, 69, 70, 66, 63, 68,112,103, 92,194,104, 95, 86, 87, 71, # 60 -116,195, 85, 93, 97,113,196,197,198,199,200,253,253,253,253,253, # 70 -206,207,208,209,210,211,212,213,120,214,215,216,217,218,219,220, # 80 -221, 78, 64, 83,121, 98,117,105,222,223,224,225,226,227,228,229, # 90 - 88,230,231,232,233,122, 89,106,234,235,236,237,238, 45,239,240, # a0 - 73, 80,118,114,241,242,243,244,245, 62, 58,246,247,248,249,250, # b0 - 31, 32, 35, 43, 37, 44, 55, 47, 40, 59, 33, 46, 38, 36, 41, 30, # c0 - 39, 28, 34, 51, 48, 49, 53, 50, 54, 57, 61,251, 67,252, 60, 56, # d0 - 1, 18, 9, 20, 11, 3, 23, 15, 2, 26, 12, 10, 14, 6, 4, 13, # e0 - 7, 8, 5, 19, 29, 25, 22, 21, 27, 24, 17, 75, 52,253, 42, 16, # f0 -) - -# Model Table: -# total sequences: 100% -# first 512 sequences: 96.9392% -# first 1024 sequences:3.0618% -# rest sequences: 0.2992% -# negative sequences: 0.0020% -BulgarianLangModel = ( -0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,2,3,3,3,3,3, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,3,3,3,2,2,3,2,2,1,2,2, -3,1,3,3,2,3,3,3,3,3,3,3,3,3,3,3,3,0,3,3,3,3,3,3,3,3,3,3,0,3,0,1, -0,0,0,0,0,0,0,0,0,0,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,2,3,3,3,3,3,3,3,3,0,3,1,0, -0,1,0,0,0,0,0,0,0,0,1,1,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -3,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,1,3,2,3,3,3,3,3,3,3,3,0,3,0,0, -0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,2,3,3,2,3,3,3,3,3,3,3,3,3,3,3,3,1,3,2,3,3,3,3,3,3,3,3,0,3,0,0, -0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,3,2,3,2,2,1,3,3,3,3,2,2,2,1,1,2,0,1,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,3,3,2,3,2,2,3,3,1,1,2,3,3,2,3,3,3,3,2,1,2,0,2,0,3,0,0, -0,0,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,3,3,1,3,3,3,3,3,2,3,2,3,3,3,3,3,2,3,3,1,3,0,3,0,2,0,0, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,3,3,3,1,3,3,2,3,3,3,1,3,3,2,3,2,2,2,0,0,2,0,2,0,2,0,0, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,3,3,3,3,0,3,3,3,2,2,3,3,3,1,2,2,3,2,1,1,2,0,2,0,0,0,0, -1,0,0,0,0,0,0,0,0,0,2,0,0,1,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,3,3,2,3,3,1,2,3,2,2,2,3,3,3,3,3,2,2,3,1,2,0,2,1,2,0,0, -0,0,0,0,0,0,0,0,0,0,3,0,0,1,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,1,3,3,3,3,3,2,3,3,3,2,3,3,2,3,2,2,2,3,1,2,0,1,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,3,3,3,3,3,3,1,1,1,2,2,1,3,1,3,2,2,3,0,0,1,0,1,0,1,0,0, -0,0,0,1,0,0,0,0,1,0,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,2,2,3,2,2,3,1,2,1,1,1,2,3,1,3,1,2,2,0,1,1,1,1,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,1,3,2,2,3,3,1,2,3,1,1,3,3,3,3,1,2,2,1,1,1,0,2,0,2,0,1, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,2,2,3,3,3,2,2,1,1,2,0,2,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, -3,0,1,2,1,3,3,2,3,3,3,3,3,2,3,2,1,0,3,1,2,1,2,1,2,3,2,1,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,1,1,2,3,3,3,3,3,3,3,3,3,3,3,3,0,0,3,1,3,3,2,3,3,2,2,2,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,3,3,3,3,0,3,3,3,3,3,2,1,1,2,1,3,3,0,3,1,1,1,1,3,2,0,1,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, -3,3,2,2,2,3,3,3,3,3,3,3,3,3,3,3,1,1,3,1,3,3,2,3,2,2,2,3,0,2,0,0, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,2,3,3,2,2,3,2,1,1,1,1,1,3,1,3,1,1,0,0,0,1,0,0,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,2,3,2,0,3,2,0,3,0,2,0,0,2,1,3,1,0,0,1,0,0,0,1,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,2,1,1,1,1,2,1,1,2,1,1,1,2,2,1,2,1,1,1,0,1,1,0,1,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,2,1,3,1,1,2,1,3,2,1,1,0,1,2,3,2,1,1,1,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,3,3,3,3,2,2,1,0,1,0,0,1,0,0,0,2,1,0,3,0,0,1,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,2,3,2,3,3,1,3,2,1,1,1,2,1,1,2,1,3,0,1,0,0,0,1,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,1,1,2,2,3,3,2,3,2,2,2,3,1,2,2,1,1,2,1,1,2,2,0,1,1,0,1,0,2,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,2,1,3,1,0,2,2,1,3,2,1,0,0,2,0,2,0,1,0,0,0,0,0,0,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,3,1,2,0,2,3,1,2,3,2,0,1,3,1,2,1,1,1,0,0,1,0,0,2,2,2,3, -2,2,2,2,1,2,1,1,2,2,1,1,2,0,1,1,1,0,0,1,1,0,0,1,1,0,0,0,1,1,0,1, -3,3,3,3,3,2,1,2,2,1,2,0,2,0,1,0,1,2,1,2,1,1,0,0,0,1,0,1,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1, -3,3,2,3,3,1,1,3,1,0,3,2,1,0,0,0,1,2,0,2,0,1,0,0,0,1,0,1,2,1,2,2, -1,1,1,1,1,1,1,2,2,2,1,1,1,1,1,1,1,0,1,2,1,1,1,0,0,0,0,0,1,1,0,0, -3,1,0,1,0,2,3,2,2,2,3,2,2,2,2,2,1,0,2,1,2,1,1,1,0,1,2,1,2,2,2,1, -1,1,2,2,2,2,1,2,1,1,0,1,2,1,2,2,2,1,1,1,0,1,1,1,1,2,0,1,0,0,0,0, -2,3,2,3,3,0,0,2,1,0,2,1,0,0,0,0,2,3,0,2,0,0,0,0,0,1,0,0,2,0,1,2, -2,1,2,1,2,2,1,1,1,2,1,1,1,0,1,2,2,1,1,1,1,1,0,1,1,1,0,0,1,2,0,0, -3,3,2,2,3,0,2,3,1,1,2,0,0,0,1,0,0,2,0,2,0,0,0,1,0,1,0,1,2,0,2,2, -1,1,1,1,2,1,0,1,2,2,2,1,1,1,1,1,1,1,0,1,1,1,0,0,0,0,0,0,1,1,0,0, -2,3,2,3,3,0,0,3,0,1,1,0,1,0,0,0,2,2,1,2,0,0,0,0,0,0,0,0,2,0,1,2, -2,2,1,1,1,1,1,2,2,2,1,0,2,0,1,0,1,0,0,1,0,1,0,0,1,0,0,0,0,1,0,0, -3,3,3,3,2,2,2,2,2,0,2,1,1,1,1,2,1,2,1,1,0,2,0,1,0,1,0,0,2,0,1,2, -1,1,1,1,1,1,1,2,2,1,1,0,2,0,1,0,2,0,0,1,1,1,0,0,2,0,0,0,1,1,0,0, -2,3,3,3,3,1,0,0,0,0,0,0,0,0,0,0,2,0,0,1,1,0,0,0,0,0,0,1,2,0,1,2, -2,2,2,1,1,2,1,1,2,2,2,1,2,0,1,1,1,1,1,1,0,1,1,1,1,0,0,1,1,1,0,0, -2,3,3,3,3,0,2,2,0,2,1,0,0,0,1,1,1,2,0,2,0,0,0,3,0,0,0,0,2,0,2,2, -1,1,1,2,1,2,1,1,2,2,2,1,2,0,1,1,1,0,1,1,1,1,0,2,1,0,0,0,1,1,0,0, -2,3,3,3,3,0,2,1,0,0,2,0,0,0,0,0,1,2,0,2,0,0,0,0,0,0,0,0,2,0,1,2, -1,1,1,2,1,1,1,1,2,2,2,0,1,0,1,1,1,0,0,1,1,1,0,0,1,0,0,0,0,1,0,0, -3,3,2,2,3,0,1,0,1,0,0,0,0,0,0,0,1,1,0,3,0,0,0,0,0,0,0,0,1,0,2,2, -1,1,1,1,1,2,1,1,2,2,1,2,2,1,0,1,1,1,1,1,0,1,0,0,1,0,0,0,1,1,0,0, -3,1,0,1,0,2,2,2,2,3,2,1,1,1,2,3,0,0,1,0,2,1,1,0,1,1,1,1,2,1,1,1, -1,2,2,1,2,1,2,2,1,1,0,1,2,1,2,2,1,1,1,0,0,1,1,1,2,1,0,1,0,0,0,0, -2,1,0,1,0,3,1,2,2,2,2,1,2,2,1,1,1,0,2,1,2,2,1,1,2,1,1,0,2,1,1,1, -1,2,2,2,2,2,2,2,1,2,0,1,1,0,2,1,1,1,1,1,0,0,1,1,1,1,0,1,0,0,0,0, -2,1,1,1,1,2,2,2,2,1,2,2,2,1,2,2,1,1,2,1,2,3,2,2,1,1,1,1,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,2,2,3,2,0,1,2,0,1,2,1,1,0,1,0,1,2,1,2,0,0,0,1,1,0,0,0,1,0,0,2, -1,1,0,0,1,1,0,1,1,1,1,0,2,0,1,1,1,0,0,1,1,0,0,0,0,1,0,0,0,1,0,0, -2,0,0,0,0,1,2,2,2,2,2,2,2,1,2,1,1,1,1,1,1,1,0,1,1,1,1,1,2,1,1,1, -1,2,2,2,2,1,1,2,1,2,1,1,1,0,2,1,2,1,1,1,0,2,1,1,1,1,0,1,0,0,0,0, -3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0, -1,1,0,1,0,1,1,1,1,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,2,2,3,2,0,0,0,0,1,0,0,0,0,0,0,1,1,0,2,0,0,0,0,0,0,0,0,1,0,1,2, -1,1,1,1,1,1,0,0,2,2,2,2,2,0,1,1,0,1,1,1,1,1,0,0,1,0,0,0,1,1,0,1, -2,3,1,2,1,0,1,1,0,2,2,2,0,0,1,0,0,1,1,1,1,0,0,0,0,0,0,0,1,0,1,2, -1,1,1,1,2,1,1,1,1,1,1,1,1,0,1,1,0,1,0,1,0,1,0,0,1,0,0,0,0,1,0,0, -2,2,2,2,2,0,0,2,0,0,2,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,2,0,2,2, -1,1,1,1,1,0,0,1,2,1,1,0,1,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0, -1,2,2,2,2,0,0,2,0,1,1,0,0,0,1,0,0,2,0,2,0,0,0,0,0,0,0,0,0,0,1,1, -0,0,0,1,1,1,1,1,1,1,1,1,1,0,1,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0, -1,2,2,3,2,0,0,1,0,0,1,0,0,0,0,0,0,1,0,2,0,0,0,1,0,0,0,0,0,0,0,2, -1,1,0,0,1,0,0,0,1,1,0,0,1,0,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0, -2,1,2,2,2,1,2,1,2,2,1,1,2,1,1,1,0,1,1,1,1,2,0,1,0,1,1,1,1,0,1,1, -1,1,2,1,1,1,1,1,1,0,0,1,2,1,1,1,1,1,1,0,0,1,1,1,0,0,0,0,0,0,0,0, -1,0,0,1,3,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,2,2,2,1,0,0,1,0,2,0,0,0,0,0,1,1,1,0,1,0,0,0,0,0,0,0,0,2,0,0,1, -0,2,0,1,0,0,1,1,2,0,1,0,1,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0, -1,2,2,2,2,0,1,1,0,2,1,0,1,1,1,0,0,1,0,2,0,1,0,0,0,0,0,0,0,0,0,1, -0,1,0,0,1,0,0,0,1,1,0,0,1,0,0,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0, -2,2,2,2,2,0,0,1,0,0,0,1,0,1,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,1, -0,1,0,1,1,1,0,0,1,1,1,0,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0, -2,0,1,0,0,1,2,1,1,1,1,1,1,2,2,1,0,0,1,0,1,0,0,0,0,1,1,1,1,0,0,0, -1,1,2,1,1,1,1,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,2,1,2,1,0,0,1,0,0,0,0,0,0,0,0,1,1,0,1,0,0,0,0,0,0,0,0,0,0,0,1, -0,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,0,0,1,2,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0, -0,1,1,0,1,1,1,0,0,1,0,0,1,0,1,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0, -1,0,1,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,1,0,2,0,0,2,0,1,0,0,1,0,0,1, -1,1,0,0,1,1,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0, -1,1,1,1,1,1,1,2,0,0,0,0,0,0,2,1,0,1,1,0,0,1,1,1,0,1,0,0,0,0,0,0, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,1,0,1,1,1,1,1,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -) - -Latin5BulgarianModel = { - 'char_to_order_map': Latin5_BulgarianCharToOrderMap, - 'precedence_matrix': BulgarianLangModel, - 'typical_positive_ratio': 0.969392, - 'keep_english_letter': False, - 'charset_name': "ISO-8859-5", - 'language': 'Bulgairan', -} - -Win1251BulgarianModel = { - 'char_to_order_map': win1251BulgarianCharToOrderMap, - 'precedence_matrix': BulgarianLangModel, - 'typical_positive_ratio': 0.969392, - 'keep_english_letter': False, - 'charset_name': "windows-1251", - 'language': 'Bulgarian', -} diff --git a/pipenv/vendor/chardet/langcyrillicmodel.py b/pipenv/vendor/chardet/langcyrillicmodel.py deleted file mode 100644 index e5f9a1fd..00000000 --- a/pipenv/vendor/chardet/langcyrillicmodel.py +++ /dev/null @@ -1,333 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -# KOI8-R language model -# Character Mapping Table: -KOI8R_char_to_order_map = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 -155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 -253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 - 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 -191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206, # 80 -207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222, # 90 -223,224,225, 68,226,227,228,229,230,231,232,233,234,235,236,237, # a0 -238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253, # b0 - 27, 3, 21, 28, 13, 2, 39, 19, 26, 4, 23, 11, 8, 12, 5, 1, # c0 - 15, 16, 9, 7, 6, 14, 24, 10, 17, 18, 20, 25, 30, 29, 22, 54, # d0 - 59, 37, 44, 58, 41, 48, 53, 46, 55, 42, 60, 36, 49, 38, 31, 34, # e0 - 35, 43, 45, 32, 40, 52, 56, 33, 61, 62, 51, 57, 47, 63, 50, 70, # f0 -) - -win1251_char_to_order_map = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 -155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 -253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 - 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 -191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206, -207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222, -223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238, -239,240,241,242,243,244,245,246, 68,247,248,249,250,251,252,253, - 37, 44, 33, 46, 41, 48, 56, 51, 42, 60, 36, 49, 38, 31, 34, 35, - 45, 32, 40, 52, 53, 55, 58, 50, 57, 63, 70, 62, 61, 47, 59, 43, - 3, 21, 10, 19, 13, 2, 24, 20, 4, 23, 11, 8, 12, 5, 1, 15, - 9, 7, 6, 14, 39, 26, 28, 22, 25, 29, 54, 18, 17, 30, 27, 16, -) - -latin5_char_to_order_map = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 -155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 -253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 - 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 -191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206, -207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222, -223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238, - 37, 44, 33, 46, 41, 48, 56, 51, 42, 60, 36, 49, 38, 31, 34, 35, - 45, 32, 40, 52, 53, 55, 58, 50, 57, 63, 70, 62, 61, 47, 59, 43, - 3, 21, 10, 19, 13, 2, 24, 20, 4, 23, 11, 8, 12, 5, 1, 15, - 9, 7, 6, 14, 39, 26, 28, 22, 25, 29, 54, 18, 17, 30, 27, 16, -239, 68,240,241,242,243,244,245,246,247,248,249,250,251,252,255, -) - -macCyrillic_char_to_order_map = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 -155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 -253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 - 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 - 37, 44, 33, 46, 41, 48, 56, 51, 42, 60, 36, 49, 38, 31, 34, 35, - 45, 32, 40, 52, 53, 55, 58, 50, 57, 63, 70, 62, 61, 47, 59, 43, -191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206, -207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222, -223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238, -239,240,241,242,243,244,245,246,247,248,249,250,251,252, 68, 16, - 3, 21, 10, 19, 13, 2, 24, 20, 4, 23, 11, 8, 12, 5, 1, 15, - 9, 7, 6, 14, 39, 26, 28, 22, 25, 29, 54, 18, 17, 30, 27,255, -) - -IBM855_char_to_order_map = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 -155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 -253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 - 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 -191,192,193,194, 68,195,196,197,198,199,200,201,202,203,204,205, -206,207,208,209,210,211,212,213,214,215,216,217, 27, 59, 54, 70, - 3, 37, 21, 44, 28, 58, 13, 41, 2, 48, 39, 53, 19, 46,218,219, -220,221,222,223,224, 26, 55, 4, 42,225,226,227,228, 23, 60,229, -230,231,232,233,234,235, 11, 36,236,237,238,239,240,241,242,243, - 8, 49, 12, 38, 5, 31, 1, 34, 15,244,245,246,247, 35, 16,248, - 43, 9, 45, 7, 32, 6, 40, 14, 52, 24, 56, 10, 33, 17, 61,249, -250, 18, 62, 20, 51, 25, 57, 30, 47, 29, 63, 22, 50,251,252,255, -) - -IBM866_char_to_order_map = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 -155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 -253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 - 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 - 37, 44, 33, 46, 41, 48, 56, 51, 42, 60, 36, 49, 38, 31, 34, 35, - 45, 32, 40, 52, 53, 55, 58, 50, 57, 63, 70, 62, 61, 47, 59, 43, - 3, 21, 10, 19, 13, 2, 24, 20, 4, 23, 11, 8, 12, 5, 1, 15, -191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206, -207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222, -223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238, - 9, 7, 6, 14, 39, 26, 28, 22, 25, 29, 54, 18, 17, 30, 27, 16, -239, 68,240,241,242,243,244,245,246,247,248,249,250,251,252,255, -) - -# Model Table: -# total sequences: 100% -# first 512 sequences: 97.6601% -# first 1024 sequences: 2.3389% -# rest sequences: 0.1237% -# negative sequences: 0.0009% -RussianLangModel = ( -0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,1,3,3,3,3,1,3,3,3,2,3,2,3,3, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,3,2,2,2,2,2,0,0,2, -3,3,3,2,3,3,3,3,3,3,3,3,3,3,2,3,3,0,0,3,3,3,3,3,3,3,3,3,2,3,2,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,2,2,3,3,3,3,3,3,3,3,3,2,3,3,0,0,3,3,3,3,3,3,3,3,2,3,3,1,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,2,3,2,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,3,3,3,3,3,3,3,3,3,3,3,2,1, -0,0,0,0,0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,0,0,3,3,3,3,3,3,3,3,3,3,3,2,1, -0,0,0,0,0,1,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,2,2,2,3,1,3,3,1,3,3,3,3,2,2,3,0,2,2,2,3,3,2,1,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,2,3,3,3,3,3,2,2,3,2,3,3,3,2,1,2,2,0,1,2,2,2,2,2,2,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,3,0,2,2,3,3,2,1,2,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,2,3,3,1,2,3,2,2,3,2,3,3,3,3,2,2,3,0,3,2,2,3,1,1,1,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,2,2,3,3,3,3,3,2,3,3,3,3,2,2,2,0,3,3,3,2,2,2,2,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,2,3,2,3,3,3,3,3,3,2,3,2,2,0,1,3,2,1,2,2,1,0, -0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,3,2,1,1,3,0,1,1,1,1,2,1,1,0,2,2,2,1,2,0,1,0, -0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,2,3,3,2,2,2,2,1,3,2,3,2,3,2,1,2,2,0,1,1,2,1,2,1,2,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,3,3,2,2,3,2,3,3,3,2,2,2,2,0,2,2,2,2,3,1,1,0, -0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, -3,2,3,2,2,3,3,3,3,3,3,3,3,3,1,3,2,0,0,3,3,3,3,2,3,3,3,3,2,3,2,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,3,3,3,3,3,2,2,3,3,0,2,1,0,3,2,3,2,3,0,0,1,2,0,0,1,0,1,2,1,1,0, -0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,0,3,0,2,3,3,3,3,2,3,3,3,3,1,2,2,0,0,2,3,2,2,2,3,2,3,2,2,3,0,0, -0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,2,3,0,2,3,2,3,0,1,2,3,3,2,0,2,3,0,0,2,3,2,2,0,1,3,1,3,2,2,1,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,1,3,0,2,3,3,3,3,3,3,3,3,2,1,3,2,0,0,2,2,3,3,3,2,3,3,0,2,2,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,2,2,3,3,2,2,2,3,3,0,0,1,1,1,1,1,2,0,0,1,1,1,1,0,1,0, -0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,2,2,3,3,3,3,3,3,3,0,3,2,3,3,2,3,2,0,2,1,0,1,1,0,1,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,2,3,3,3,2,2,2,2,3,1,3,2,3,1,1,2,1,0,2,2,2,2,1,3,1,0, -0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, -2,2,3,3,3,3,3,1,2,2,1,3,1,0,3,0,0,3,0,0,0,1,1,0,1,2,1,0,0,0,0,0, -0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,2,2,1,1,3,3,3,2,2,1,2,2,3,1,1,2,0,0,2,2,1,3,0,0,2,1,1,2,1,1,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,2,3,3,3,3,1,2,2,2,1,2,1,3,3,1,1,2,1,2,1,2,2,0,2,0,0,1,1,0,1,0, -0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,3,3,3,3,3,2,1,3,2,2,3,2,0,3,2,0,3,0,1,0,1,1,0,0,1,1,1,1,0,1,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,2,3,3,3,2,2,2,3,3,1,2,1,2,1,0,1,0,1,1,0,1,0,0,2,1,1,1,0,1,0, -0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0, -3,1,1,2,1,2,3,3,2,2,1,2,2,3,0,2,1,0,0,2,2,3,2,1,2,2,2,2,2,3,1,0, -0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,1,1,0,1,1,2,2,1,1,3,0,0,1,3,1,1,1,0,0,0,1,0,1,1,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,1,3,3,3,2,0,0,0,2,1,0,1,0,2,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,0,1,0,0,2,3,2,2,2,1,2,2,2,1,2,1,0,0,1,1,1,0,2,0,1,1,1,0,0,1,1, -1,0,0,0,0,0,1,2,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0, -2,3,3,3,3,0,0,0,0,1,0,0,0,0,3,0,1,2,1,0,0,0,0,0,0,0,1,1,0,0,1,1, -1,0,1,0,1,2,0,0,1,1,2,1,0,1,1,1,1,0,1,1,1,1,0,1,0,0,1,0,0,1,1,0, -2,2,3,2,2,2,3,1,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,0,1,0,1,1,1,0,2,1, -1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,0,1,0,1,1,0,1,1,1,0,1,1,0, -3,3,3,2,2,2,2,3,2,2,1,1,2,2,2,2,1,1,3,1,2,1,2,0,0,1,1,0,1,0,2,1, -1,1,1,1,1,2,1,0,1,1,1,1,0,1,0,0,1,1,0,0,1,0,1,0,0,1,0,0,0,1,1,0, -2,0,0,1,0,3,2,2,2,2,1,2,1,2,1,2,0,0,0,2,1,2,2,1,1,2,2,0,1,1,0,2, -1,1,1,1,1,0,1,1,1,2,1,1,1,2,1,0,1,2,1,1,1,1,0,1,1,1,0,0,1,0,0,1, -1,3,2,2,2,1,1,1,2,3,0,0,0,0,2,0,2,2,1,0,0,0,0,0,0,1,0,0,0,0,1,1, -1,0,1,1,0,1,0,1,1,0,1,1,0,2,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,1,1,0, -2,3,2,3,2,1,2,2,2,2,1,0,0,0,2,0,0,1,1,0,0,0,0,0,0,0,1,1,0,0,2,1, -1,1,2,1,0,2,0,0,1,0,1,0,0,1,0,0,1,1,0,1,1,0,0,0,0,0,1,0,0,0,0,0, -3,0,0,1,0,2,2,2,3,2,2,2,2,2,2,2,0,0,0,2,1,2,1,1,1,2,2,0,0,0,1,2, -1,1,1,1,1,0,1,2,1,1,1,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1,1,1,1,0,0,1, -2,3,2,3,3,2,0,1,1,1,0,0,1,0,2,0,1,1,3,1,0,0,0,0,0,0,0,1,0,0,2,1, -1,1,1,1,1,1,1,0,1,0,1,1,1,1,0,1,1,1,0,0,1,1,0,1,0,0,0,0,0,0,1,0, -2,3,3,3,3,1,2,2,2,2,0,1,1,0,2,1,1,1,2,1,0,1,1,0,0,1,0,1,0,0,2,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,3,3,3,2,0,0,1,1,2,2,1,0,0,2,0,1,1,3,0,0,1,0,0,0,0,0,1,0,1,2,1, -1,1,2,0,1,1,1,0,1,0,1,1,0,1,0,1,1,1,1,0,1,0,0,0,0,0,0,1,0,1,1,0, -1,3,2,3,2,1,0,0,2,2,2,0,1,0,2,0,1,1,1,0,1,0,0,0,3,0,1,1,0,0,2,1, -1,1,1,0,1,1,0,0,0,0,1,1,0,1,0,0,2,1,1,0,1,0,0,0,1,0,1,0,0,1,1,0, -3,1,2,1,1,2,2,2,2,2,2,1,2,2,1,1,0,0,0,2,2,2,0,0,0,1,2,1,0,1,0,1, -2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,2,1,1,1,0,1,0,1,1,0,1,1,1,0,0,1, -3,0,0,0,0,2,0,1,1,1,1,1,1,1,0,1,0,0,0,1,1,1,0,1,0,1,1,0,0,1,0,1, -1,1,0,0,1,0,0,0,1,0,1,1,0,0,1,0,1,0,1,0,0,0,0,1,0,0,0,1,0,0,0,1, -1,3,3,2,2,0,0,0,2,2,0,0,0,1,2,0,1,1,2,0,0,0,0,0,0,0,0,1,0,0,2,1, -0,1,1,0,0,1,1,0,0,0,1,1,0,1,1,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0, -2,3,2,3,2,0,0,0,0,1,1,0,0,0,2,0,2,0,2,0,0,0,0,0,1,0,0,1,0,0,1,1, -1,1,2,0,1,2,1,0,1,1,2,1,1,1,1,1,2,1,1,0,1,0,0,1,1,1,1,1,0,1,1,0, -1,3,2,2,2,1,0,0,2,2,1,0,1,2,2,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,1, -0,0,1,1,0,1,1,0,0,1,1,0,1,1,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0, -1,0,0,1,0,2,3,1,2,2,2,2,2,2,1,1,0,0,0,1,0,1,0,2,1,1,1,0,0,0,0,1, -1,1,0,1,1,0,1,1,1,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0, -2,0,2,0,0,1,0,3,2,1,2,1,2,2,0,1,0,0,0,2,1,0,0,2,1,1,1,1,0,2,0,2, -2,1,1,1,1,1,1,1,1,1,1,1,1,2,1,0,1,1,1,1,0,0,0,1,1,1,1,0,1,0,0,1, -1,2,2,2,2,1,0,0,1,0,0,0,0,0,2,0,1,1,1,1,0,0,0,0,1,0,1,2,0,0,2,0, -1,0,1,1,1,2,1,0,1,0,1,1,0,0,1,0,1,1,1,0,1,0,0,0,1,0,0,1,0,1,1,0, -2,1,2,2,2,0,3,0,1,1,0,0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -0,0,0,1,1,1,0,0,1,0,1,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0, -1,2,2,3,2,2,0,0,1,1,2,0,1,2,1,0,1,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1, -0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,1,1,0,0,1,0,0,0,0,0,0,0,0,1,1,0, -2,2,1,1,2,1,2,2,2,2,2,1,2,2,0,1,0,0,0,1,2,2,2,1,2,1,1,1,1,1,2,1, -1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,0,1,1,1,0,0,0,0,1,1,1,0,1,1,0,0,1, -1,2,2,2,2,0,1,0,2,2,0,0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,2,0, -0,0,1,0,0,1,0,0,0,0,1,0,1,1,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0, -0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,2,2,2,2,0,0,0,2,2,2,0,1,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1, -0,1,1,0,0,1,1,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,2,2,2,2,0,0,0,0,1,0,0,1,1,2,0,0,0,0,1,0,1,0,0,1,0,0,2,0,0,0,1, -0,0,1,0,0,1,0,0,0,1,1,0,0,0,0,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0, -1,2,2,2,1,1,2,0,2,1,1,1,1,0,2,2,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1, -0,0,1,0,1,1,0,0,0,0,1,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0, -1,0,2,1,2,0,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0, -0,0,1,0,1,1,0,0,0,0,1,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0, -1,0,0,0,0,2,0,1,2,1,0,1,1,1,0,1,0,0,0,1,0,1,0,0,1,0,1,0,0,0,0,1, -0,0,0,0,0,1,0,0,1,1,0,0,1,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1, -2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -1,0,0,0,1,0,0,0,1,1,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,1,0,0,0,0,0, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -1,1,1,0,1,0,1,0,0,1,1,1,1,0,0,0,1,0,0,0,0,1,0,0,0,1,0,1,0,0,0,0, -1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -1,1,0,1,1,0,1,0,1,0,0,0,0,1,1,0,1,1,0,0,0,0,0,1,0,1,1,0,1,0,0,0, -0,1,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0, -) - -Koi8rModel = { - 'char_to_order_map': KOI8R_char_to_order_map, - 'precedence_matrix': RussianLangModel, - 'typical_positive_ratio': 0.976601, - 'keep_english_letter': False, - 'charset_name': "KOI8-R", - 'language': 'Russian', -} - -Win1251CyrillicModel = { - 'char_to_order_map': win1251_char_to_order_map, - 'precedence_matrix': RussianLangModel, - 'typical_positive_ratio': 0.976601, - 'keep_english_letter': False, - 'charset_name': "windows-1251", - 'language': 'Russian', -} - -Latin5CyrillicModel = { - 'char_to_order_map': latin5_char_to_order_map, - 'precedence_matrix': RussianLangModel, - 'typical_positive_ratio': 0.976601, - 'keep_english_letter': False, - 'charset_name': "ISO-8859-5", - 'language': 'Russian', -} - -MacCyrillicModel = { - 'char_to_order_map': macCyrillic_char_to_order_map, - 'precedence_matrix': RussianLangModel, - 'typical_positive_ratio': 0.976601, - 'keep_english_letter': False, - 'charset_name': "MacCyrillic", - 'language': 'Russian', -} - -Ibm866Model = { - 'char_to_order_map': IBM866_char_to_order_map, - 'precedence_matrix': RussianLangModel, - 'typical_positive_ratio': 0.976601, - 'keep_english_letter': False, - 'charset_name': "IBM866", - 'language': 'Russian', -} - -Ibm855Model = { - 'char_to_order_map': IBM855_char_to_order_map, - 'precedence_matrix': RussianLangModel, - 'typical_positive_ratio': 0.976601, - 'keep_english_letter': False, - 'charset_name': "IBM855", - 'language': 'Russian', -} diff --git a/pipenv/vendor/chardet/langgreekmodel.py b/pipenv/vendor/chardet/langgreekmodel.py deleted file mode 100644 index 53322216..00000000 --- a/pipenv/vendor/chardet/langgreekmodel.py +++ /dev/null @@ -1,225 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -# 255: Control characters that usually does not exist in any text -# 254: Carriage/Return -# 253: symbol (punctuation) that does not belong to word -# 252: 0 - 9 - -# Character Mapping Table: -Latin7_char_to_order_map = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253, 82,100,104, 94, 98,101,116,102,111,187,117, 92, 88,113, 85, # 40 - 79,118,105, 83, 67,114,119, 95, 99,109,188,253,253,253,253,253, # 50 -253, 72, 70, 80, 81, 60, 96, 93, 89, 68,120, 97, 77, 86, 69, 55, # 60 - 78,115, 65, 66, 58, 76,106,103, 87,107,112,253,253,253,253,253, # 70 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 80 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 90 -253,233, 90,253,253,253,253,253,253,253,253,253,253, 74,253,253, # a0 -253,253,253,253,247,248, 61, 36, 46, 71, 73,253, 54,253,108,123, # b0 -110, 31, 51, 43, 41, 34, 91, 40, 52, 47, 44, 53, 38, 49, 59, 39, # c0 - 35, 48,250, 37, 33, 45, 56, 50, 84, 57,120,121, 17, 18, 22, 15, # d0 -124, 1, 29, 20, 21, 3, 32, 13, 25, 5, 11, 16, 10, 6, 30, 4, # e0 - 9, 8, 14, 7, 2, 12, 28, 23, 42, 24, 64, 75, 19, 26, 27,253, # f0 -) - -win1253_char_to_order_map = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253, 82,100,104, 94, 98,101,116,102,111,187,117, 92, 88,113, 85, # 40 - 79,118,105, 83, 67,114,119, 95, 99,109,188,253,253,253,253,253, # 50 -253, 72, 70, 80, 81, 60, 96, 93, 89, 68,120, 97, 77, 86, 69, 55, # 60 - 78,115, 65, 66, 58, 76,106,103, 87,107,112,253,253,253,253,253, # 70 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 80 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 90 -253,233, 61,253,253,253,253,253,253,253,253,253,253, 74,253,253, # a0 -253,253,253,253,247,253,253, 36, 46, 71, 73,253, 54,253,108,123, # b0 -110, 31, 51, 43, 41, 34, 91, 40, 52, 47, 44, 53, 38, 49, 59, 39, # c0 - 35, 48,250, 37, 33, 45, 56, 50, 84, 57,120,121, 17, 18, 22, 15, # d0 -124, 1, 29, 20, 21, 3, 32, 13, 25, 5, 11, 16, 10, 6, 30, 4, # e0 - 9, 8, 14, 7, 2, 12, 28, 23, 42, 24, 64, 75, 19, 26, 27,253, # f0 -) - -# Model Table: -# total sequences: 100% -# first 512 sequences: 98.2851% -# first 1024 sequences:1.7001% -# rest sequences: 0.0359% -# negative sequences: 0.0148% -GreekLangModel = ( -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,3,2,2,3,3,3,3,3,3,3,3,1,3,3,3,0,2,2,3,3,0,3,0,3,2,0,3,3,3,0, -3,0,0,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,3,0,3,3,0,3,2,3,3,0,3,2,3,3,3,0,0,3,0,3,0,3,3,2,0,0,0, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0, -0,2,3,2,2,3,3,3,3,3,3,3,3,0,3,3,3,3,0,2,3,3,0,3,3,3,3,2,3,3,3,0, -2,0,0,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,2,3,3,2,3,3,3,3,3,3,3,3,3,3,3,3,0,2,1,3,3,3,3,2,3,3,2,3,3,2,0, -0,0,0,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,0,3,3,3,3,3,3,0,3,3,0,3,3,3,3,3,3,3,3,3,3,0,3,2,3,3,0, -2,0,1,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, -0,3,3,3,3,3,2,3,0,0,0,0,3,3,0,3,1,3,3,3,0,3,3,0,3,3,3,3,0,0,0,0, -2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,3,0,3,0,3,3,3,3,3,0,3,2,2,2,3,0,2,3,3,3,3,3,2,3,3,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,3,3,2,2,2,3,3,3,3,0,3,1,3,3,3,3,2,3,3,3,3,3,3,3,2,2,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,3,2,0,3,0,0,0,3,3,2,3,3,3,3,3,0,0,3,2,3,0,2,3,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,0,3,3,3,3,0,0,3,3,0,2,3,0,3,0,3,3,3,0,0,3,0,3,0,2,2,3,3,0,0, -0,0,1,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,3,2,0,3,2,3,3,3,3,0,3,3,3,3,3,0,3,3,2,3,2,3,3,2,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,2,3,2,3,3,3,3,3,3,0,2,3,2,3,2,2,2,3,2,3,3,2,3,0,2,2,2,3,0, -2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,3,0,0,0,3,3,3,2,3,3,0,0,3,0,3,0,0,0,3,2,0,3,0,3,0,0,2,0,2,0, -0,0,0,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,0,3,3,3,3,3,3,0,3,3,0,3,0,0,0,3,3,0,3,3,3,0,0,1,2,3,0, -3,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,3,2,0,0,3,2,2,3,3,0,3,3,3,3,3,2,1,3,0,3,2,3,3,2,1,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,3,3,0,2,3,3,3,3,3,3,0,0,3,0,3,0,0,0,3,3,0,3,2,3,0,0,3,3,3,0, -3,0,0,0,2,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,0,3,3,3,3,3,3,0,0,3,0,3,0,0,0,3,2,0,3,2,3,0,0,3,2,3,0, -2,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,3,1,2,2,3,3,3,3,3,3,0,2,3,0,3,0,0,0,3,3,0,3,0,2,0,0,2,3,1,0, -2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,0,3,3,3,3,0,3,0,3,3,2,3,0,3,3,3,3,3,3,0,3,3,3,0,2,3,0,0,3,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,0,3,3,3,0,0,3,0,0,0,3,3,0,3,0,2,3,3,0,0,3,0,3,0,3,3,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,3,0,0,0,3,3,3,3,3,3,0,0,3,0,2,0,0,0,3,3,0,3,0,3,0,0,2,0,2,0, -0,0,0,0,1,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,3,3,0,3,0,2,0,3,2,0,3,2,3,2,3,0,0,3,2,3,2,3,3,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,3,0,0,2,3,3,3,3,3,0,0,0,3,0,2,1,0,0,3,2,2,2,0,3,0,0,2,2,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,0,3,3,3,2,0,3,0,3,0,3,3,0,2,1,2,3,3,0,0,3,0,3,0,3,3,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,2,3,3,3,0,3,3,3,3,3,3,0,2,3,0,3,0,0,0,2,1,0,2,2,3,0,0,2,2,2,0, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,3,0,0,2,3,3,3,2,3,0,0,1,3,0,2,0,0,0,0,3,0,1,0,2,0,0,1,1,1,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,3,1,0,3,0,0,0,3,2,0,3,2,3,3,3,0,0,3,0,3,2,2,2,1,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,0,3,3,3,0,0,3,0,0,0,0,2,0,2,3,3,2,2,2,2,3,0,2,0,2,2,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,2,0,0,0,0,0,0,2,3,0,2,0,2,3,2,0,0,3,0,3,0,3,1,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,3,2,3,3,2,2,3,0,2,0,3,0,0,0,2,0,0,0,0,1,2,0,2,0,2,0, -0,2,0,2,0,2,2,0,0,1,0,2,2,2,0,2,2,2,0,2,2,2,0,0,2,0,0,1,0,0,0,0, -0,2,0,3,3,2,0,0,0,0,0,0,1,3,0,2,0,2,2,2,0,0,2,0,3,0,0,2,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,0,2,3,2,0,2,2,0,2,0,2,2,0,2,0,2,2,2,0,0,0,0,0,0,2,3,0,0,0,2, -0,1,2,0,0,0,0,2,2,0,0,0,2,1,0,2,2,0,0,0,0,0,0,1,0,2,0,0,0,0,0,0, -0,0,2,1,0,2,3,2,2,3,2,3,2,0,0,3,3,3,0,0,3,2,0,0,0,1,1,0,2,0,2,2, -0,2,0,2,0,2,2,0,0,2,0,2,2,2,0,2,2,2,2,0,0,2,0,0,0,2,0,1,0,0,0,0, -0,3,0,3,3,2,2,0,3,0,0,0,2,2,0,2,2,2,1,2,0,0,1,2,2,0,0,3,0,0,0,2, -0,1,2,0,0,0,1,2,0,0,0,0,0,0,0,2,2,0,1,0,0,2,0,0,0,2,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,2,3,3,2,2,0,0,0,2,0,2,3,3,0,2,0,0,0,0,0,0,2,2,2,0,2,2,0,2,0,2, -0,2,2,0,0,2,2,2,2,1,0,0,2,2,0,2,0,0,2,0,0,0,0,0,0,2,0,0,0,0,0,0, -0,2,0,3,2,3,0,0,0,3,0,0,2,2,0,2,0,2,2,2,0,0,2,0,0,0,0,0,0,0,0,2, -0,0,2,2,0,0,2,2,2,0,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,2,0,0,3,2,0,2,2,2,2,2,0,0,0,2,0,0,0,0,2,0,1,0,0,2,0,1,0,0,0, -0,2,2,2,0,2,2,0,1,2,0,2,2,2,0,2,2,2,2,1,2,2,0,0,2,0,0,0,0,0,0,0, -0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, -0,2,0,2,0,2,2,0,0,0,0,1,2,1,0,0,2,2,0,0,2,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,3,2,3,0,0,2,0,0,0,2,2,0,2,0,0,0,1,0,0,2,0,2,0,2,2,0,0,0,0, -0,0,2,0,0,0,0,2,2,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0, -0,2,2,3,2,2,0,0,0,0,0,0,1,3,0,2,0,2,2,0,0,0,1,0,2,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,2,0,2,0,3,2,0,2,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -0,0,2,0,0,0,0,1,1,0,0,2,1,2,0,2,2,0,1,0,0,1,0,0,0,2,0,0,0,0,0,0, -0,3,0,2,2,2,0,0,2,0,0,0,2,0,0,0,2,3,0,2,0,0,0,0,0,0,2,2,0,0,0,2, -0,1,2,0,0,0,1,2,2,1,0,0,0,2,0,0,2,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,2,1,2,0,2,2,0,2,0,0,2,0,0,0,0,1,2,1,0,2,1,0,0,0,0,0,0,0,0,0,0, -0,0,2,0,0,0,3,1,2,2,0,2,0,0,0,0,2,0,0,0,2,0,0,3,0,0,0,0,2,2,2,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,2,1,0,2,0,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,1,0,0,0,0,0,0,2, -0,2,2,0,0,2,2,2,2,2,0,1,2,0,0,0,2,2,0,1,0,2,0,0,2,2,0,0,0,0,0,0, -0,0,0,0,1,0,0,0,0,0,0,0,3,0,0,2,0,0,0,0,0,0,0,0,2,0,2,0,0,0,0,2, -0,1,2,0,0,0,0,2,2,1,0,1,0,1,0,2,2,2,1,0,0,0,0,0,0,1,0,0,0,0,0,0, -0,2,0,1,2,0,0,0,0,0,0,0,0,0,0,2,0,0,2,2,0,0,0,0,1,0,0,0,0,0,0,2, -0,2,2,0,0,0,0,2,2,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,2,0,0,0, -0,2,2,2,2,0,0,0,3,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,2,0,0,0,0,0,0,1, -0,0,2,0,0,0,0,1,2,0,0,0,0,0,0,2,2,1,1,0,0,0,0,0,0,1,0,0,0,0,0,0, -0,2,0,2,2,2,0,0,2,0,0,0,0,0,0,0,2,2,2,0,0,0,2,0,0,0,0,0,0,0,0,2, -0,0,1,0,0,0,0,2,1,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0, -0,3,0,2,0,0,0,0,0,0,0,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,2,0,0,0,0,2, -0,0,2,0,0,0,0,2,2,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,2,0,2,2,1,0,0,0,0,0,0,2,0,0,2,0,2,2,2,0,0,0,0,0,0,2,0,0,0,0,2, -0,0,2,0,0,2,0,2,2,0,0,0,0,2,0,2,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0, -0,0,3,0,0,0,2,2,0,2,2,0,0,0,0,0,2,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,2,0,0,0,0,0, -0,2,2,2,2,2,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1, -0,0,0,0,0,0,0,2,1,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,2,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, -0,2,0,0,0,2,0,0,0,0,0,1,0,0,0,0,2,2,0,0,0,1,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,2,0,0,0, -0,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,2,0,2,0,0,0, -0,0,0,0,0,0,0,0,2,1,0,0,0,0,0,0,2,0,0,0,1,2,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -) - -Latin7GreekModel = { - 'char_to_order_map': Latin7_char_to_order_map, - 'precedence_matrix': GreekLangModel, - 'typical_positive_ratio': 0.982851, - 'keep_english_letter': False, - 'charset_name': "ISO-8859-7", - 'language': 'Greek', -} - -Win1253GreekModel = { - 'char_to_order_map': win1253_char_to_order_map, - 'precedence_matrix': GreekLangModel, - 'typical_positive_ratio': 0.982851, - 'keep_english_letter': False, - 'charset_name': "windows-1253", - 'language': 'Greek', -} diff --git a/pipenv/vendor/chardet/langhebrewmodel.py b/pipenv/vendor/chardet/langhebrewmodel.py deleted file mode 100644 index 58f4c875..00000000 --- a/pipenv/vendor/chardet/langhebrewmodel.py +++ /dev/null @@ -1,200 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Universal charset detector code. -# -# The Initial Developer of the Original Code is -# Simon Montagu -# Portions created by the Initial Developer are Copyright (C) 2005 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# Shy Shalom - original C code -# Shoshannah Forbes - original C code (?) -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -# 255: Control characters that usually does not exist in any text -# 254: Carriage/Return -# 253: symbol (punctuation) that does not belong to word -# 252: 0 - 9 - -# Windows-1255 language model -# Character Mapping Table: -WIN1255_CHAR_TO_ORDER_MAP = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253, 69, 91, 79, 80, 92, 89, 97, 90, 68,111,112, 82, 73, 95, 85, # 40 - 78,121, 86, 71, 67,102,107, 84,114,103,115,253,253,253,253,253, # 50 -253, 50, 74, 60, 61, 42, 76, 70, 64, 53,105, 93, 56, 65, 54, 49, # 60 - 66,110, 51, 43, 44, 63, 81, 77, 98, 75,108,253,253,253,253,253, # 70 -124,202,203,204,205, 40, 58,206,207,208,209,210,211,212,213,214, -215, 83, 52, 47, 46, 72, 32, 94,216,113,217,109,218,219,220,221, - 34,116,222,118,100,223,224,117,119,104,125,225,226, 87, 99,227, -106,122,123,228, 55,229,230,101,231,232,120,233, 48, 39, 57,234, - 30, 59, 41, 88, 33, 37, 36, 31, 29, 35,235, 62, 28,236,126,237, -238, 38, 45,239,240,241,242,243,127,244,245,246,247,248,249,250, - 9, 8, 20, 16, 3, 2, 24, 14, 22, 1, 25, 15, 4, 11, 6, 23, - 12, 19, 13, 26, 18, 27, 21, 17, 7, 10, 5,251,252,128, 96,253, -) - -# Model Table: -# total sequences: 100% -# first 512 sequences: 98.4004% -# first 1024 sequences: 1.5981% -# rest sequences: 0.087% -# negative sequences: 0.0015% -HEBREW_LANG_MODEL = ( -0,3,3,3,3,3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,3,3,3,2,3,2,1,2,0,1,0,0, -3,0,3,1,0,0,1,3,2,0,1,1,2,0,2,2,2,1,1,1,1,2,1,1,1,2,0,0,2,2,0,1, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,2, -1,2,1,2,1,2,0,0,2,0,0,0,0,0,1,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2, -1,2,1,3,1,1,0,0,2,0,0,0,1,0,1,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,0,1,2,2,1,3, -1,2,1,1,2,2,0,0,2,2,0,0,0,0,1,0,1,0,0,0,1,0,0,0,0,0,0,1,0,1,1,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,2,2,2,2,3,2, -1,2,1,2,2,2,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,2,3,2,2,3,2,2,2,1,2,2,2,2, -1,2,1,1,2,2,0,1,2,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,0,2,2,2,2,2, -0,2,0,2,2,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,0,2,2,2, -0,2,1,2,2,2,0,0,2,1,0,0,0,0,1,0,1,0,0,0,0,0,0,2,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,3,3,3,3,3,2,1,2,3,2,2,2, -1,2,1,2,2,2,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,1,0, -3,3,3,3,3,3,3,3,3,2,3,3,3,2,3,3,3,3,3,3,3,3,3,3,3,3,3,1,0,2,0,2, -0,2,1,2,2,2,0,0,1,2,0,0,0,0,1,0,1,0,0,0,0,0,0,1,0,0,0,2,0,0,1,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,2,3,2,2,3,2,1,2,1,1,1, -0,1,1,1,1,1,3,0,1,0,0,0,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, -3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,1,1,0,0,1,0,0,1,0,0,0,0, -0,0,1,0,0,0,0,0,2,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,2,2,2,2, -0,2,0,1,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,3,2,3,3,3,2,1,2,3,3,2,3,3,3,3,2,3,2,1,2,0,2,1,2, -0,2,0,2,2,2,0,0,1,2,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0, -3,3,3,3,3,3,3,3,3,2,3,3,3,1,2,2,3,3,2,3,2,3,2,2,3,1,2,2,0,2,2,2, -0,2,1,2,2,2,0,0,1,2,0,0,0,0,1,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,2,3,3,2,2,2,3,3,3,3,1,3,2,2,2, -0,2,0,1,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,3,3,3,2,3,2,2,2,1,2,2,0,2,2,2,2, -0,2,0,2,2,2,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,1,3,2,3,3,2,3,3,2,2,1,2,2,2,2,2,2, -0,2,1,2,1,2,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,2,3,2,3,3,2,3,3,3,3,2,3,2,3,3,3,3,3,2,2,2,2,2,2,2,1, -0,2,0,1,2,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,3,2,1,2,3,3,3,3,3,3,3,2,3,2,3,2,1,2,3,0,2,1,2,2, -0,2,1,1,2,1,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,2,0, -3,3,3,3,3,3,3,3,3,2,3,3,3,3,2,1,3,1,2,2,2,1,2,3,3,1,2,1,2,2,2,2, -0,1,1,1,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,0,2,3,3,3,1,3,3,3,1,2,2,2,2,1,1,2,2,2,2,2,2, -0,2,0,1,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,2,3,3,3,2,2,3,3,3,2,1,2,3,2,3,2,2,2,2,1,2,1,1,1,2,2, -0,2,1,1,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, -3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,1,0,0,0,0,0, -1,0,1,0,0,0,0,0,2,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,2,3,3,2,3,1,2,2,2,2,3,2,3,1,1,2,2,1,2,2,1,1,0,2,2,2,2, -0,1,0,1,2,2,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, -3,0,0,1,1,0,1,0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,2,0, -0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,0,1,0,1,0,1,1,0,1,1,0,0,0,1,1,0,1,1,1,0,0,0,0,0,0,1,0,0,0,0,0, -0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,0,0,0,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, -3,2,2,1,2,2,2,2,2,2,2,1,2,2,1,2,2,1,1,1,1,1,1,1,1,2,1,1,0,3,3,3, -0,3,0,2,2,2,2,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, -2,2,2,3,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,1,2,2,2,1,1,1,2,0,1, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,0,2,2,0,0,0,0,0,0, -0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,3,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,1,0,2,1,0, -0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0, -0,3,1,1,2,2,2,2,2,1,2,2,2,1,1,2,2,2,2,2,2,2,1,2,2,1,0,1,1,1,1,0, -0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,2,1,1,1,1,2,1,1,2,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0, -0,0,2,0,0,0,0,0,0,0,0,1,1,0,0,0,0,1,1,0,0,1,1,0,0,0,0,0,0,1,0,0, -2,1,1,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,1,2,1,2,1,1,1,1,0,0,0,0, -0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,2,1,2,2,2,2,2,2,2,2,2,2,1,2,1,2,1,1,2,1,1,1,2,1,2,1,2,0,1,0,1, -0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,1,2,2,2,1,2,2,2,2,2,2,2,2,1,2,1,1,1,1,1,1,2,1,2,1,1,0,1,0,1, -0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,1,2,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2, -0,2,0,1,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, -3,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,1,1,1,1,1,1,1,0,1,1,0,1,0,0,1,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,2,0,1,1,1,0,1,0,0,0,1,1,0,1,1,0,0,0,0,0,1,1,0,0, -0,1,1,1,2,1,2,2,2,0,2,0,2,0,1,1,2,1,1,1,1,2,1,0,1,1,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,0,1,0,0,0,0,0,1,0,1,2,2,0,1,0,0,1,1,2,2,1,2,0,2,0,0,0,1,2,0,1, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,2,0,2,1,2,0,2,0,0,1,1,1,1,1,1,0,1,0,0,0,1,0,0,1, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,1,0,0,0,0,0,1,0,2,1,1,0,1,0,0,1,1,1,2,2,0,0,1,0,0,0,1,0,0,1, -1,1,2,1,0,1,1,1,0,1,0,1,1,1,1,0,0,0,1,0,1,0,0,0,0,0,0,0,0,2,2,1, -0,2,0,1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,1,0,0,1,0,1,1,1,1,0,0,0,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,1,1,1,1,1,1,1,1,2,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,1,1,1,0,1,1,0,1,0,0,0,1,1,0,1, -2,0,1,0,1,0,1,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,1,0,1,1,1,0,1,0,0,1,1,2,1,1,2,0,1,0,0,0,1,1,0,1, -1,0,0,1,0,0,1,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,1,0,1,1,2,0,1,0,0,0,0,2,1,1,2,0,2,0,0,0,1,1,0,1, -1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,1,0,2,1,1,0,1,0,0,2,2,1,2,1,1,0,1,0,0,0,1,1,0,1, -2,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,1,2,2,0,0,0,0,0,1,1,0,1,0,0,1,0,0,0,0,1,0,1, -1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,1,2,2,0,0,0,0,2,1,1,1,0,2,1,1,0,0,0,2,1,0,1, -1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,1,0,1,1,2,0,1,0,0,1,1,0,2,1,1,0,1,0,0,0,1,1,0,1, -2,2,1,1,1,0,1,1,0,1,1,0,1,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,1,0,2,1,1,0,1,0,0,1,1,0,1,2,1,0,2,0,0,0,1,1,0,1, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0, -0,1,0,0,2,0,2,1,1,0,1,0,1,0,0,1,0,0,0,0,1,0,0,0,1,0,0,0,0,0,1,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,1,0,1,1,2,0,1,0,0,1,1,1,0,1,0,0,1,0,0,0,1,0,0,1, -1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,0,0,0,0,0,0,0,1,0,1,1,0,0,1,0,0,2,1,1,1,1,1,0,1,0,0,0,0,1,0,1, -0,1,1,1,2,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,1,2,1,0,0,0,0,0,1,1,1,1,1,0,1,0,0,0,1,1,0,0, -) - -Win1255HebrewModel = { - 'char_to_order_map': WIN1255_CHAR_TO_ORDER_MAP, - 'precedence_matrix': HEBREW_LANG_MODEL, - 'typical_positive_ratio': 0.984004, - 'keep_english_letter': False, - 'charset_name': "windows-1255", - 'language': 'Hebrew', -} diff --git a/pipenv/vendor/chardet/langhungarianmodel.py b/pipenv/vendor/chardet/langhungarianmodel.py deleted file mode 100644 index bb7c095e..00000000 --- a/pipenv/vendor/chardet/langhungarianmodel.py +++ /dev/null @@ -1,225 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -# 255: Control characters that usually does not exist in any text -# 254: Carriage/Return -# 253: symbol (punctuation) that does not belong to word -# 252: 0 - 9 - -# Character Mapping Table: -Latin2_HungarianCharToOrderMap = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253, 28, 40, 54, 45, 32, 50, 49, 38, 39, 53, 36, 41, 34, 35, 47, - 46, 71, 43, 33, 37, 57, 48, 64, 68, 55, 52,253,253,253,253,253, -253, 2, 18, 26, 17, 1, 27, 12, 20, 9, 22, 7, 6, 13, 4, 8, - 23, 67, 10, 5, 3, 21, 19, 65, 62, 16, 11,253,253,253,253,253, -159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174, -175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190, -191,192,193,194,195,196,197, 75,198,199,200,201,202,203,204,205, - 79,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220, -221, 51, 81,222, 78,223,224,225,226, 44,227,228,229, 61,230,231, -232,233,234, 58,235, 66, 59,236,237,238, 60, 69, 63,239,240,241, - 82, 14, 74,242, 70, 80,243, 72,244, 15, 83, 77, 84, 30, 76, 85, -245,246,247, 25, 73, 42, 24,248,249,250, 31, 56, 29,251,252,253, -) - -win1250HungarianCharToOrderMap = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253, 28, 40, 54, 45, 32, 50, 49, 38, 39, 53, 36, 41, 34, 35, 47, - 46, 72, 43, 33, 37, 57, 48, 64, 68, 55, 52,253,253,253,253,253, -253, 2, 18, 26, 17, 1, 27, 12, 20, 9, 22, 7, 6, 13, 4, 8, - 23, 67, 10, 5, 3, 21, 19, 65, 62, 16, 11,253,253,253,253,253, -161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176, -177,178,179,180, 78,181, 69,182,183,184,185,186,187,188,189,190, -191,192,193,194,195,196,197, 76,198,199,200,201,202,203,204,205, - 81,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220, -221, 51, 83,222, 80,223,224,225,226, 44,227,228,229, 61,230,231, -232,233,234, 58,235, 66, 59,236,237,238, 60, 70, 63,239,240,241, - 84, 14, 75,242, 71, 82,243, 73,244, 15, 85, 79, 86, 30, 77, 87, -245,246,247, 25, 74, 42, 24,248,249,250, 31, 56, 29,251,252,253, -) - -# Model Table: -# total sequences: 100% -# first 512 sequences: 94.7368% -# first 1024 sequences:5.2623% -# rest sequences: 0.8894% -# negative sequences: 0.0009% -HungarianLangModel = ( -0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, -3,3,3,3,3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,2,2,3,3,1,1,2,2,2,2,2,1,2, -3,2,2,3,3,3,3,3,2,3,3,3,3,3,3,1,2,3,3,3,3,2,3,3,1,1,3,3,0,1,1,1, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0, -3,2,1,3,3,3,3,3,2,3,3,3,3,3,1,1,2,3,3,3,3,3,3,3,1,1,3,2,0,1,1,1, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,3,3,3,1,1,2,3,3,3,1,3,3,3,3,3,1,3,3,2,2,0,3,2,3, -0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0, -3,3,3,3,3,3,2,3,3,3,2,3,3,2,3,3,3,3,3,2,3,3,2,2,3,2,3,2,0,3,2,2, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0, -3,3,3,3,3,3,2,3,3,3,3,3,2,3,3,3,1,2,3,2,2,3,1,2,3,3,2,2,0,3,3,3, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,2,2,3,3,3,3,3,3,2,3,3,3,3,2,3,3,3,3,0,2,3,2, -0,0,0,1,1,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,3,1,1,1,3,3,2,1,3,2,2,3,2,1,3,2,2,1,0,3,3,1, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -3,2,2,3,3,3,3,3,1,2,3,3,3,3,1,2,1,3,3,3,3,2,2,3,1,1,3,2,0,1,1,1, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,2,2,3,3,3,3,3,2,1,3,3,3,3,3,2,2,1,3,3,3,0,1,1,2, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,2,3,3,2,3,3,3,2,0,3,2,3, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,1,0, -3,3,3,3,3,3,2,3,3,3,2,3,2,3,3,3,1,3,2,2,2,3,1,1,3,3,1,1,0,3,3,2, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,2,3,3,3,2,3,2,3,3,3,2,3,3,3,3,3,1,2,3,2,2,0,2,2,2, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -3,3,3,2,2,2,3,1,3,3,2,2,1,3,3,3,1,1,3,1,2,3,2,3,2,2,2,1,0,2,2,2, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0, -3,1,1,3,3,3,3,3,1,2,3,3,3,3,1,2,1,3,3,3,2,2,3,2,1,0,3,2,0,1,1,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,1,1,3,3,3,3,3,1,2,3,3,3,3,1,1,0,3,3,3,3,0,2,3,0,0,2,1,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,2,2,3,3,2,2,2,2,3,3,0,1,2,3,2,3,2,2,3,2,1,2,0,2,2,2, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0, -3,3,3,3,3,3,1,2,3,3,3,2,1,2,3,3,2,2,2,3,2,3,3,1,3,3,1,1,0,2,3,2, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -3,3,3,1,2,2,2,2,3,3,3,1,1,1,3,3,1,1,3,1,1,3,2,1,2,3,1,1,0,2,2,2, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -3,3,3,2,1,2,1,1,3,3,1,1,1,1,3,3,1,1,2,2,1,2,1,1,2,2,1,1,0,2,2,1, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -3,3,3,1,1,2,1,1,3,3,1,0,1,1,3,3,2,0,1,1,2,3,1,0,2,2,1,0,0,1,3,2, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -3,2,1,3,3,3,3,3,1,2,3,2,3,3,2,1,1,3,2,3,2,1,2,2,0,1,2,1,0,0,1,1, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, -3,3,3,3,2,2,2,2,3,1,2,2,1,1,3,3,0,3,2,1,2,3,2,1,3,3,1,1,0,2,1,3, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -3,3,3,2,2,2,3,2,3,3,3,2,1,1,3,3,1,1,1,2,2,3,2,3,2,2,2,1,0,2,2,1, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -1,0,0,3,3,3,3,3,0,0,3,3,2,3,0,0,0,2,3,3,1,0,1,2,0,0,1,1,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,1,2,3,3,3,3,3,1,2,3,3,2,2,1,1,0,3,3,2,2,1,2,2,1,0,2,2,0,1,1,1, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,2,2,1,3,1,2,3,3,2,2,1,1,2,2,1,1,1,1,3,2,1,1,1,1,2,1,0,1,2,1, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0, -2,3,3,1,1,1,1,1,3,3,3,0,1,1,3,3,1,1,1,1,1,2,2,0,3,1,1,2,0,2,1,1, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -3,1,0,1,2,1,2,2,0,1,2,3,1,2,0,0,0,2,1,1,1,1,1,2,0,0,1,1,0,0,0,0, -1,2,1,2,2,2,1,2,1,2,0,2,0,2,2,1,1,2,1,1,2,1,1,1,0,1,0,0,0,1,1,0, -1,1,1,2,3,2,3,3,0,1,2,2,3,1,0,1,0,2,1,2,2,0,1,1,0,0,1,1,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,0,0,3,3,2,2,1,0,0,3,2,3,2,0,0,0,1,1,3,0,0,1,1,0,0,2,1,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,1,1,2,2,3,3,1,0,1,3,2,3,1,1,1,0,1,1,1,1,1,3,1,0,0,2,2,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,1,1,1,2,2,2,1,0,1,2,3,3,2,0,0,0,2,1,1,1,2,1,1,1,0,1,1,1,0,0,0, -1,2,2,2,2,2,1,1,1,2,0,2,1,1,1,1,1,2,1,1,1,1,1,1,0,1,1,1,0,0,1,1, -3,2,2,1,0,0,1,1,2,2,0,3,0,1,2,1,1,0,0,1,1,1,0,1,1,1,1,0,2,1,1,1, -2,2,1,1,1,2,1,2,1,1,1,1,1,1,1,2,1,1,1,2,3,1,1,1,1,1,1,1,1,1,0,1, -2,3,3,0,1,0,0,0,3,3,1,0,0,1,2,2,1,0,0,0,0,2,0,0,1,1,1,0,2,1,1,1, -2,1,1,1,1,1,1,2,1,1,0,1,1,0,1,1,1,0,1,2,1,1,0,1,1,1,1,1,1,1,0,1, -2,3,3,0,1,0,0,0,2,2,0,0,0,0,1,2,2,0,0,0,0,1,0,0,1,1,0,0,2,0,1,0, -2,1,1,1,1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,2,0,1,1,1,1,1,0,1, -3,2,2,0,1,0,1,0,2,3,2,0,0,1,2,2,1,0,0,1,1,1,0,0,2,1,0,1,2,2,1,1, -2,1,1,1,1,1,1,2,1,1,1,1,1,1,0,2,1,0,1,1,0,1,1,1,0,1,1,2,1,1,0,1, -2,2,2,0,0,1,0,0,2,2,1,1,0,0,2,1,1,0,0,0,1,2,0,0,2,1,0,0,2,1,1,1, -2,1,1,1,1,2,1,2,1,1,1,2,2,1,1,2,1,1,1,2,1,1,1,1,1,1,1,1,1,1,0,1, -1,2,3,0,0,0,1,0,3,2,1,0,0,1,2,1,1,0,0,0,0,2,1,0,1,1,0,0,2,1,2,1, -1,1,0,0,0,1,0,1,1,1,1,1,2,0,0,1,0,0,0,2,0,0,1,1,1,1,1,1,1,1,0,1, -3,0,0,2,1,2,2,1,0,0,2,1,2,2,0,0,0,2,1,1,1,0,1,1,0,0,1,1,2,0,0,0, -1,2,1,2,2,1,1,2,1,2,0,1,1,1,1,1,1,1,1,1,2,1,1,0,0,1,1,1,1,0,0,1, -1,3,2,0,0,0,1,0,2,2,2,0,0,0,2,2,1,0,0,0,0,3,1,1,1,1,0,0,2,1,1,1, -2,1,0,1,1,1,0,1,1,1,1,1,1,1,0,2,1,0,0,1,0,1,1,0,1,1,1,1,1,1,0,1, -2,3,2,0,0,0,1,0,2,2,0,0,0,0,2,1,1,0,0,0,0,2,1,0,1,1,0,0,2,1,1,0, -2,1,1,1,1,2,1,2,1,2,0,1,1,1,0,2,1,1,1,2,1,1,1,1,0,1,1,1,1,1,0,1, -3,1,1,2,2,2,3,2,1,1,2,2,1,1,0,1,0,2,2,1,1,1,1,1,0,0,1,1,0,1,1,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,2,2,0,0,0,0,0,2,2,0,0,0,0,2,2,1,0,0,0,1,1,0,0,1,2,0,0,2,1,1,1, -2,2,1,1,1,2,1,2,1,1,0,1,1,1,1,2,1,1,1,2,1,1,1,1,0,1,2,1,1,1,0,1, -1,0,0,1,2,3,2,1,0,0,2,0,1,1,0,0,0,1,1,1,1,0,1,1,0,0,1,0,0,0,0,0, -1,2,1,2,1,2,1,1,1,2,0,2,1,1,1,0,1,2,0,0,1,1,1,0,0,0,0,0,0,0,0,0, -2,3,2,0,0,0,0,0,1,1,2,1,0,0,1,1,1,0,0,0,0,2,0,0,1,1,0,0,2,1,1,1, -2,1,1,1,1,1,1,2,1,0,1,1,1,1,0,2,1,1,1,1,1,1,0,1,0,1,1,1,1,1,0,1, -1,2,2,0,1,1,1,0,2,2,2,0,0,0,3,2,1,0,0,0,1,1,0,0,1,1,0,1,1,1,0,0, -1,1,0,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,1,1,0,0,1,1,1,0,1,0,1, -2,1,0,2,1,1,2,2,1,1,2,1,1,1,0,0,0,1,1,0,1,1,1,1,0,0,1,1,1,0,0,0, -1,2,2,2,2,2,1,1,1,2,0,2,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,0,0,1,0, -1,2,3,0,0,0,1,0,2,2,0,0,0,0,2,2,0,0,0,0,0,1,0,0,1,0,0,0,2,0,1,0, -2,1,1,1,1,1,0,2,0,0,0,1,2,1,1,1,1,0,1,2,0,1,0,1,0,1,1,1,0,1,0,1, -2,2,2,0,0,0,1,0,2,1,2,0,0,0,1,1,2,0,0,0,0,1,0,0,1,1,0,0,2,1,0,1, -2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,0,1,1,1,1,1,0,1, -1,2,2,0,0,0,1,0,2,2,2,0,0,0,1,1,0,0,0,0,0,1,1,0,2,0,0,1,1,1,0,1, -1,0,1,1,1,1,1,1,0,1,1,1,1,0,0,1,0,0,1,1,0,1,0,1,1,1,1,1,0,0,0,1, -1,0,0,1,0,1,2,1,0,0,1,1,1,2,0,0,0,1,1,0,1,0,1,1,0,0,1,0,0,0,0,0, -0,2,1,2,1,1,1,1,1,2,0,2,0,1,1,0,1,2,1,0,1,1,1,0,0,0,0,0,0,1,0,0, -2,1,1,0,1,2,0,0,1,1,1,0,0,0,1,1,0,0,0,0,0,1,0,0,1,0,0,0,2,1,0,1, -2,2,1,1,1,1,1,2,1,1,0,1,1,1,1,2,1,1,1,2,1,1,0,1,0,1,1,1,1,1,0,1, -1,2,2,0,0,0,0,0,1,1,0,0,0,0,2,1,0,0,0,0,0,2,0,0,2,2,0,0,2,0,0,1, -2,1,1,1,1,1,1,1,0,1,1,0,1,1,0,1,0,0,0,1,1,1,1,0,0,1,1,1,1,0,0,1, -1,1,2,0,0,3,1,0,2,1,1,1,0,0,1,1,1,0,0,0,1,1,0,0,0,1,0,0,1,0,1,0, -1,2,1,0,1,1,1,2,1,1,0,1,1,1,1,1,0,0,0,1,1,1,1,1,0,1,0,0,0,1,0,0, -2,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,1,0,0,0,1,0,0,0,0,2,0,0,0, -2,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,2,1,1,0,0,1,1,1,1,1,0,1, -2,1,1,1,2,1,1,1,0,1,1,2,1,0,0,0,0,1,1,1,1,0,1,0,0,0,0,1,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,1,0,1,1,1,1,1,0,0,1,1,2,1,0,0,0,1,1,0,0,0,1,1,0,0,1,0,1,0,0,0, -1,2,1,1,1,1,1,1,1,1,0,1,0,1,1,1,1,1,1,0,1,1,1,0,0,0,0,0,0,1,0,0, -2,0,0,0,1,1,1,1,0,0,1,1,0,0,0,0,0,1,1,1,2,0,0,1,0,0,1,0,1,0,0,0, -0,1,1,1,1,1,1,1,1,2,0,1,1,1,1,0,1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,0, -1,0,0,1,1,1,1,1,0,0,2,1,0,1,0,0,0,1,0,1,0,0,0,0,0,0,1,0,0,0,0,0, -0,1,1,1,1,1,1,0,1,1,0,1,0,1,1,0,1,1,0,0,1,1,1,0,0,0,0,0,0,0,0,0, -1,0,0,1,1,1,0,0,0,0,1,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, -0,1,1,1,1,1,0,0,1,1,0,1,0,1,0,0,1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,0, -0,0,0,1,0,0,0,0,0,0,1,1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,1,1,1,0,1,0,0,1,1,0,1,0,1,1,0,1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,0, -2,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,0,0,1,0,0,1,0,1,0,1,1,1,0,0,1,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,0,0,1,1,1,1,0,0,0,1,1,1,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0, -0,1,1,1,1,1,1,0,1,1,0,1,0,1,0,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0, -) - -Latin2HungarianModel = { - 'char_to_order_map': Latin2_HungarianCharToOrderMap, - 'precedence_matrix': HungarianLangModel, - 'typical_positive_ratio': 0.947368, - 'keep_english_letter': True, - 'charset_name': "ISO-8859-2", - 'language': 'Hungarian', -} - -Win1250HungarianModel = { - 'char_to_order_map': win1250HungarianCharToOrderMap, - 'precedence_matrix': HungarianLangModel, - 'typical_positive_ratio': 0.947368, - 'keep_english_letter': True, - 'charset_name': "windows-1250", - 'language': 'Hungarian', -} diff --git a/pipenv/vendor/chardet/langthaimodel.py b/pipenv/vendor/chardet/langthaimodel.py deleted file mode 100644 index 15f94c2d..00000000 --- a/pipenv/vendor/chardet/langthaimodel.py +++ /dev/null @@ -1,199 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -# 255: Control characters that usually does not exist in any text -# 254: Carriage/Return -# 253: symbol (punctuation) that does not belong to word -# 252: 0 - 9 - -# The following result for thai was collected from a limited sample (1M). - -# Character Mapping Table: -TIS620CharToOrderMap = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253,182,106,107,100,183,184,185,101, 94,186,187,108,109,110,111, # 40 -188,189,190, 89, 95,112,113,191,192,193,194,253,253,253,253,253, # 50 -253, 64, 72, 73,114, 74,115,116,102, 81,201,117, 90,103, 78, 82, # 60 - 96,202, 91, 79, 84,104,105, 97, 98, 92,203,253,253,253,253,253, # 70 -209,210,211,212,213, 88,214,215,216,217,218,219,220,118,221,222, -223,224, 99, 85, 83,225,226,227,228,229,230,231,232,233,234,235, -236, 5, 30,237, 24,238, 75, 8, 26, 52, 34, 51,119, 47, 58, 57, - 49, 53, 55, 43, 20, 19, 44, 14, 48, 3, 17, 25, 39, 62, 31, 54, - 45, 9, 16, 2, 61, 15,239, 12, 42, 46, 18, 21, 76, 4, 66, 63, - 22, 10, 1, 36, 23, 13, 40, 27, 32, 35, 86,240,241,242,243,244, - 11, 28, 41, 29, 33,245, 50, 37, 6, 7, 67, 77, 38, 93,246,247, - 68, 56, 59, 65, 69, 60, 70, 80, 71, 87,248,249,250,251,252,253, -) - -# Model Table: -# total sequences: 100% -# first 512 sequences: 92.6386% -# first 1024 sequences:7.3177% -# rest sequences: 1.0230% -# negative sequences: 0.0436% -ThaiLangModel = ( -0,1,3,3,3,3,0,0,3,3,0,3,3,0,3,3,3,3,3,3,3,3,0,0,3,3,3,0,3,3,3,3, -0,3,3,0,0,0,1,3,0,3,3,2,3,3,0,1,2,3,3,3,3,0,2,0,2,0,0,3,2,1,2,2, -3,0,3,3,2,3,0,0,3,3,0,3,3,0,3,3,3,3,3,3,3,3,3,0,3,2,3,0,2,2,2,3, -0,2,3,0,0,0,0,1,0,1,2,3,1,1,3,2,2,0,1,1,0,0,1,0,0,0,0,0,0,0,1,1, -3,3,3,2,3,3,3,3,3,3,3,3,3,3,3,2,2,2,2,2,2,2,3,3,2,3,2,3,3,2,2,2, -3,1,2,3,0,3,3,2,2,1,2,3,3,1,2,0,1,3,0,1,0,0,1,0,0,0,0,0,0,0,1,1, -3,3,2,2,3,3,3,3,1,2,3,3,3,3,3,2,2,2,2,3,3,2,2,3,3,2,2,3,2,3,2,2, -3,3,1,2,3,1,2,2,3,3,1,0,2,1,0,0,3,1,2,1,0,0,1,0,0,0,0,0,0,1,0,1, -3,3,3,3,3,3,2,2,3,3,3,3,2,3,2,2,3,3,2,2,3,2,2,2,2,1,1,3,1,2,1,1, -3,2,1,0,2,1,0,1,0,1,1,0,1,1,0,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0, -3,3,3,2,3,2,3,3,2,2,3,2,3,3,2,3,1,1,2,3,2,2,2,3,2,2,2,2,2,1,2,1, -2,2,1,1,3,3,2,1,0,1,2,2,0,1,3,0,0,0,1,1,0,0,0,0,0,2,3,0,0,2,1,1, -3,3,2,3,3,2,0,0,3,3,0,3,3,0,2,2,3,1,2,2,1,1,1,0,2,2,2,0,2,2,1,1, -0,2,1,0,2,0,0,2,0,1,0,0,1,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,1,0, -3,3,2,3,3,2,0,0,3,3,0,2,3,0,2,1,2,2,2,2,1,2,0,0,2,2,2,0,2,2,1,1, -0,2,1,0,2,0,0,2,0,1,1,0,1,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0, -3,3,2,3,2,3,2,0,2,2,1,3,2,1,3,2,1,2,3,2,2,3,0,2,3,2,2,1,2,2,2,2, -1,2,2,0,0,0,0,2,0,1,2,0,1,1,1,0,1,0,3,1,1,0,0,0,0,0,0,0,0,0,1,0, -3,3,2,3,3,2,3,2,2,2,3,2,2,3,2,2,1,2,3,2,2,3,1,3,2,2,2,3,2,2,2,3, -3,2,1,3,0,1,1,1,0,2,1,1,1,1,1,0,1,0,1,1,0,0,0,0,0,0,0,0,0,2,0,0, -1,0,0,3,0,3,3,3,3,3,0,0,3,0,2,2,3,3,3,3,3,0,0,0,1,1,3,0,0,0,0,2, -0,0,1,0,0,0,0,0,0,0,2,3,0,0,0,3,0,2,0,0,0,0,0,3,0,0,0,0,0,0,0,0, -2,0,3,3,3,3,0,0,2,3,0,0,3,0,3,3,2,3,3,3,3,3,0,0,3,3,3,0,0,0,3,3, -0,0,3,0,0,0,0,2,0,0,2,1,1,3,0,0,1,0,0,2,3,0,1,0,0,0,0,0,0,0,1,0, -3,3,3,3,2,3,3,3,3,3,3,3,1,2,1,3,3,2,2,1,2,2,2,3,1,1,2,0,2,1,2,1, -2,2,1,0,0,0,1,1,0,1,0,1,1,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0, -3,0,2,1,2,3,3,3,0,2,0,2,2,0,2,1,3,2,2,1,2,1,0,0,2,2,1,0,2,1,2,2, -0,1,1,0,0,0,0,1,0,1,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,2,1,3,3,1,1,3,0,2,3,1,1,3,2,1,1,2,0,2,2,3,2,1,1,1,1,1,2, -3,0,0,1,3,1,2,1,2,0,3,0,0,0,1,0,3,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0, -3,3,1,1,3,2,3,3,3,1,3,2,1,3,2,1,3,2,2,2,2,1,3,3,1,2,1,3,1,2,3,0, -2,1,1,3,2,2,2,1,2,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2, -3,3,2,3,2,3,3,2,3,2,3,2,3,3,2,1,0,3,2,2,2,1,2,2,2,1,2,2,1,2,1,1, -2,2,2,3,0,1,3,1,1,1,1,0,1,1,0,2,1,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,2,3,2,2,1,1,3,2,3,2,3,2,0,3,2,2,1,2,0,2,2,2,1,2,2,2,2,1, -3,2,1,2,2,1,0,2,0,1,0,0,1,1,0,0,0,0,0,1,1,0,1,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,2,3,1,2,3,3,2,2,3,0,1,1,2,0,3,3,2,2,3,0,1,1,3,0,0,0,0, -3,1,0,3,3,0,2,0,2,1,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,2,3,2,3,3,0,1,3,1,1,2,1,2,1,1,3,1,1,0,2,3,1,1,1,1,1,1,1,1, -3,1,1,2,2,2,2,1,1,1,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -3,2,2,1,1,2,1,3,3,2,3,2,2,3,2,2,3,1,2,2,1,2,0,3,2,1,2,2,2,2,2,1, -3,2,1,2,2,2,1,1,1,1,0,0,1,1,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,1,3,3,0,2,1,0,3,2,0,0,3,1,0,1,1,0,1,0,0,0,0,0,1, -1,0,0,1,0,3,2,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,0,2,2,2,3,0,0,1,3,0,3,2,0,3,2,2,3,3,3,3,3,1,0,2,2,2,0,2,2,1,2, -0,2,3,0,0,0,0,1,0,1,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -3,0,2,3,1,3,3,2,3,3,0,3,3,0,3,2,2,3,2,3,3,3,0,0,2,2,3,0,1,1,1,3, -0,0,3,0,0,0,2,2,0,1,3,0,1,2,2,2,3,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1, -3,2,3,3,2,0,3,3,2,2,3,1,3,2,1,3,2,0,1,2,2,0,2,3,2,1,0,3,0,0,0,0, -3,0,0,2,3,1,3,0,0,3,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,1,3,2,2,2,1,2,0,1,3,1,1,3,1,3,0,0,2,1,1,1,1,2,1,1,1,0,2,1,0,1, -1,2,0,0,0,3,1,1,0,0,0,0,1,0,1,0,0,1,0,1,0,0,0,0,0,3,1,0,0,0,1,0, -3,3,3,3,2,2,2,2,2,1,3,1,1,1,2,0,1,1,2,1,2,1,3,2,0,0,3,1,1,1,1,1, -3,1,0,2,3,0,0,0,3,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,2,3,0,3,3,0,2,0,0,0,0,0,0,0,3,0,0,1,0,0,0,0,0,0,0,0,0,0,0, -0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,2,3,1,3,0,0,1,2,0,0,2,0,3,3,2,3,3,3,2,3,0,0,2,2,2,0,0,0,2,2, -0,0,1,0,0,0,0,3,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, -0,0,0,3,0,2,0,0,0,0,0,0,0,0,0,0,1,2,3,1,3,3,0,0,1,0,3,0,0,0,0,0, -0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,1,2,3,1,2,3,1,0,3,0,2,2,1,0,2,1,1,2,0,1,0,0,1,1,1,1,0,1,0,0, -1,0,0,0,0,1,1,0,3,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,2,1,0,1,1,1,3,1,2,2,2,2,2,2,1,1,1,1,0,3,1,0,1,3,1,1,1,1, -1,1,0,2,0,1,3,1,1,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,2,0,1, -3,0,2,2,1,3,3,2,3,3,0,1,1,0,2,2,1,2,1,3,3,1,0,0,3,2,0,0,0,0,2,1, -0,1,0,0,0,0,1,2,0,1,1,3,1,1,2,2,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, -0,0,3,0,0,1,0,0,0,3,0,0,3,0,3,1,0,1,1,1,3,2,0,0,0,3,0,0,0,0,2,0, -0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0,0, -3,3,1,3,2,1,3,3,1,2,2,0,1,2,1,0,1,2,0,0,0,0,0,3,0,0,0,3,0,0,0,0, -3,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,0,1,2,0,3,3,3,2,2,0,1,1,0,1,3,0,0,0,2,2,0,0,0,0,3,1,0,1,0,0,0, -0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,0,2,3,1,2,0,0,2,1,0,3,1,0,1,2,0,1,1,1,1,3,0,0,3,1,1,0,2,2,1,1, -0,2,0,0,0,0,0,1,0,1,0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,0,0,3,1,2,0,0,2,2,0,1,2,0,1,0,1,3,1,2,1,0,0,0,2,0,3,0,0,0,1,0, -0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,0,1,1,2,2,0,0,0,2,0,2,1,0,1,1,0,1,1,1,2,1,0,0,1,1,1,0,2,1,1,1, -0,1,1,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,1, -0,0,0,2,0,1,3,1,1,1,1,0,0,0,0,3,2,0,1,0,0,0,1,2,0,0,0,1,0,0,0,0, -0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,3,3,3,3,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,0,2,3,2,2,0,0,0,1,0,0,0,0,2,3,2,1,2,2,3,0,0,0,2,3,1,0,0,0,1,1, -0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,1,1,0,1,0,0,0,0,0,0,0,0,0, -3,3,2,2,0,1,0,0,0,0,2,0,2,0,1,0,0,0,1,1,0,0,0,2,1,0,1,0,1,1,0,0, -0,1,0,2,0,0,1,0,3,0,1,0,0,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,1,0,0,1,0,0,0,0,0,1,1,2,0,0,0,0,1,0,0,1,3,1,0,0,0,0,1,1,0,0, -0,1,0,0,0,0,3,0,0,0,0,0,0,3,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0, -3,3,1,1,1,1,2,3,0,0,2,1,1,1,1,1,0,2,1,1,0,0,0,2,1,0,1,2,1,1,0,1, -2,1,0,3,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,3,1,0,0,0,0,0,0,0,3,0,0,0,3,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1, -0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,2,0,0,0,0,0,0,1,2,1,0,1,1,0,2,0,0,1,0,0,2,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,2,0,0,0,1,3,0,1,0,0,0,2,0,0,0,0,0,0,0,1,2,0,0,0,0,0, -3,3,0,0,1,1,2,0,0,1,2,1,0,1,1,1,0,1,1,0,0,2,1,1,0,1,0,0,1,1,1,0, -0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,2,2,1,0,0,0,0,1,0,0,0,0,3,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0, -2,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,3,0,0,1,1,0,0,0,2,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,1,0,1,2,0,1,2,0,0,1,1,0,2,0,1,0,0,1,0,0,0,0,1,0,0,0,2,0,0,0,0, -1,0,0,1,0,1,1,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,1,0,0,0,0,0,0,0,1,1,0,1,1,0,2,1,3,0,0,0,0,1,1,0,0,0,0,0,0,0,3, -1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,0,1,0,1,0,0,2,0,0,2,0,0,1,1,2,0,0,1,1,0,0,0,1,0,0,0,1,1,0,0,0, -1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, -1,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,1,1,0,0,0, -2,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,0,0,0,0,2,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,3,0,0,0, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,1,0,0,0,0, -1,0,0,0,0,0,0,0,0,1,0,0,0,0,2,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,1,1,0,0,2,1,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -) - -TIS620ThaiModel = { - 'char_to_order_map': TIS620CharToOrderMap, - 'precedence_matrix': ThaiLangModel, - 'typical_positive_ratio': 0.926386, - 'keep_english_letter': False, - 'charset_name': "TIS-620", - 'language': 'Thai', -} diff --git a/pipenv/vendor/chardet/langturkishmodel.py b/pipenv/vendor/chardet/langturkishmodel.py deleted file mode 100644 index a427a457..00000000 --- a/pipenv/vendor/chardet/langturkishmodel.py +++ /dev/null @@ -1,193 +0,0 @@ -# -*- coding: utf-8 -*- -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# Özgür Baskın - Turkish Language Model -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -# 255: Control characters that usually does not exist in any text -# 254: Carriage/Return -# 253: symbol (punctuation) that does not belong to word -# 252: 0 - 9 - -# Character Mapping Table: -Latin5_TurkishCharToOrderMap = ( -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, -255, 23, 37, 47, 39, 29, 52, 36, 45, 53, 60, 16, 49, 20, 46, 42, - 48, 69, 44, 35, 31, 51, 38, 62, 65, 43, 56,255,255,255,255,255, -255, 1, 21, 28, 12, 2, 18, 27, 25, 3, 24, 10, 5, 13, 4, 15, - 26, 64, 7, 8, 9, 14, 32, 57, 58, 11, 22,255,255,255,255,255, -180,179,178,177,176,175,174,173,172,171,170,169,168,167,166,165, -164,163,162,161,160,159,101,158,157,156,155,154,153,152,151,106, -150,149,148,147,146,145,144,100,143,142,141,140,139,138,137,136, - 94, 80, 93,135,105,134,133, 63,132,131,130,129,128,127,126,125, -124,104, 73, 99, 79, 85,123, 54,122, 98, 92,121,120, 91,103,119, - 68,118,117, 97,116,115, 50, 90,114,113,112,111, 55, 41, 40, 86, - 89, 70, 59, 78, 71, 82, 88, 33, 77, 66, 84, 83,110, 75, 61, 96, - 30, 67,109, 74, 87,102, 34, 95, 81,108, 76, 72, 17, 6, 19,107, -) - -TurkishLangModel = ( -3,2,3,3,3,1,3,3,3,3,3,3,3,3,2,1,1,3,3,1,3,3,0,3,3,3,3,3,0,3,1,3, -3,2,1,0,0,1,1,0,0,0,1,0,0,1,1,1,1,0,0,0,0,0,0,0,2,2,0,0,1,0,0,1, -3,2,2,3,3,0,3,3,3,3,3,3,3,2,3,1,0,3,3,1,3,3,0,3,3,3,3,3,0,3,0,3, -3,1,1,0,1,0,1,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,2,2,0,0,0,1,0,1, -3,3,2,3,3,0,3,3,3,3,3,3,3,2,3,1,1,3,3,0,3,3,1,2,3,3,3,3,0,3,0,3, -3,1,1,0,0,0,1,0,0,0,0,1,1,0,1,2,1,0,0,0,1,0,0,0,0,2,0,0,0,0,0,1, -3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,1,3,3,2,0,3,2,1,2,2,1,3,3,0,0,0,2, -2,2,0,1,0,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,0,1,0,0,1, -3,3,3,2,3,3,1,2,3,3,3,3,3,3,3,1,3,2,1,0,3,2,0,1,2,3,3,2,1,0,0,2, -2,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,2,0,2,0,0,0, -1,0,1,3,3,1,3,3,3,3,3,3,3,1,2,0,0,2,3,0,2,3,0,0,2,2,2,3,0,3,0,1, -2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,3,3,3,0,3,2,0,2,3,2,3,3,1,0,0,2, -3,2,0,0,1,0,0,0,0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,1,1,1,0,2,0,0,1, -3,3,3,2,3,3,2,3,3,3,3,2,3,3,3,0,3,3,0,0,2,1,0,0,2,3,2,2,0,0,0,2, -2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,0,1,0,2,0,0,1, -3,3,3,2,3,3,3,3,3,3,3,2,3,3,3,0,3,2,0,1,3,2,1,1,3,2,3,2,1,0,0,2, -2,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0, -3,3,3,2,3,3,3,3,3,3,3,2,3,3,3,0,3,2,2,0,2,3,0,0,2,2,2,2,0,0,0,2, -3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,2,0,1,0,0,0, -3,3,3,3,3,3,3,2,2,2,2,3,2,3,3,0,3,3,1,1,2,2,0,0,2,2,3,2,0,0,1,3, -0,3,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,1, -3,3,3,2,3,3,3,2,1,2,2,3,2,3,3,0,3,2,0,0,1,1,0,1,1,2,1,2,0,0,0,1, -0,3,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,0,0, -3,3,3,2,3,3,2,3,2,2,2,3,3,3,3,1,3,1,1,0,3,2,1,1,3,3,2,3,1,0,0,1, -1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,2,0,0,1, -3,2,2,3,3,0,3,3,3,3,3,3,3,2,2,1,0,3,3,1,3,3,0,1,3,3,2,3,0,3,0,3, -2,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0, -2,2,2,3,3,0,3,3,3,3,3,3,3,3,3,0,0,3,2,0,3,3,0,3,2,3,3,3,0,3,1,3, -2,0,0,0,0,0,0,0,0,0,0,1,0,1,2,0,1,0,0,0,0,0,0,0,2,2,0,0,1,0,0,1, -3,3,3,1,2,3,3,1,0,0,1,0,0,3,3,2,3,0,0,2,0,0,2,0,2,0,0,0,2,0,2,0, -0,3,1,0,1,0,0,0,2,2,1,0,1,1,2,1,2,2,2,0,2,1,1,0,0,0,2,0,0,0,0,0, -1,2,1,3,3,0,3,3,3,3,3,2,3,0,0,0,0,2,3,0,2,3,1,0,2,3,1,3,0,3,0,2, -3,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,1,3,3,2,2,3,2,2,0,1,2,3,0,1,2,1,0,1,0,0,0,1,0,2,2,0,0,0,1, -1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0, -3,3,3,1,3,3,1,1,3,3,1,1,3,3,1,0,2,1,2,0,2,1,0,0,1,1,2,1,0,0,0,2, -2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,1,0,2,1,3,0,0,2,0,0,3,3,0,3,0,0,1,0,1,2,0,0,1,1,2,2,0,1,0, -0,1,2,1,1,0,1,0,1,1,1,1,1,0,1,1,1,2,2,1,2,0,1,0,0,0,0,0,0,1,0,0, -3,3,3,2,3,2,3,3,0,2,2,2,3,3,3,0,3,0,0,0,2,2,0,1,2,1,1,1,0,0,0,1, -0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0, -3,3,3,3,3,3,2,1,2,2,3,3,3,3,2,0,2,0,0,0,2,2,0,0,2,1,3,3,0,0,1,1, -1,1,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0, -1,1,2,3,3,0,3,3,3,3,3,3,2,2,0,2,0,2,3,2,3,2,2,2,2,2,2,2,1,3,2,3, -2,0,2,1,2,2,2,2,1,1,2,2,1,2,2,1,2,0,0,2,1,1,0,2,1,0,0,1,0,0,0,1, -2,3,3,1,1,1,0,1,1,1,2,3,2,1,1,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0, -0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,2,2,2,3,2,3,2,2,1,3,3,3,0,2,1,2,0,2,1,0,0,1,1,1,1,1,0,0,1, -2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,2,0,1,0,0,0, -3,3,3,2,3,3,3,3,3,2,3,1,2,3,3,1,2,0,0,0,0,0,0,0,3,2,1,1,0,0,0,0, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0, -3,3,3,2,2,3,3,2,1,1,1,1,1,3,3,0,3,1,0,0,1,1,0,0,3,1,2,1,0,0,0,0, -0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0, -3,3,3,2,2,3,2,2,2,3,2,1,1,3,3,0,3,0,0,0,0,1,0,0,3,1,1,2,0,0,0,1, -1,0,0,1,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -1,1,1,3,3,0,3,3,3,3,3,2,2,2,1,2,0,2,1,2,2,1,1,0,1,2,2,2,2,2,2,2, -0,0,2,1,2,1,2,1,0,1,1,3,1,2,1,1,2,0,0,2,0,1,0,1,0,1,0,0,0,1,0,1, -3,3,3,1,3,3,3,0,1,1,0,2,2,3,1,0,3,0,0,0,1,0,0,0,1,0,0,1,0,1,0,0, -1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,2,0,0,2,2,1,0,0,1,0,0,3,3,1,3,0,0,1,1,0,2,0,3,0,0,0,2,0,1,1, -0,1,2,0,1,2,2,0,2,2,2,2,1,0,2,1,1,0,2,0,2,1,2,0,0,0,0,0,0,0,0,0, -3,3,3,1,3,2,3,2,0,2,2,2,1,3,2,0,2,1,2,0,1,2,0,0,1,0,2,2,0,0,0,2, -1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,1,0,0,0, -3,3,3,0,3,3,1,1,2,3,1,0,3,2,3,0,3,0,0,0,1,0,0,0,1,0,1,0,0,0,0,0, -1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,3,3,0,3,3,2,3,3,2,2,0,0,0,0,1,2,0,1,3,0,0,0,3,1,1,0,3,0,2, -2,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,1,2,2,1,0,3,1,1,1,1,3,3,2,3,0,0,1,0,1,2,0,2,2,0,2,2,0,2,1, -0,2,2,1,1,1,1,0,2,1,1,0,1,1,1,1,2,1,2,1,2,0,1,0,1,0,0,0,0,0,0,0, -3,3,3,0,1,1,3,0,0,1,1,0,0,2,2,0,3,0,0,1,1,0,1,0,0,0,0,0,2,0,0,0, -0,3,1,0,1,0,1,0,2,0,0,1,0,1,0,1,1,1,2,1,1,0,2,0,0,0,0,0,0,0,0,0, -3,3,3,0,2,0,2,0,1,1,1,0,0,3,3,0,2,0,0,1,0,0,2,1,1,0,1,0,1,0,1,0, -0,2,0,1,2,0,2,0,2,1,1,0,1,0,2,1,1,0,2,1,1,0,1,0,0,0,1,1,0,0,0,0, -3,2,3,0,1,0,0,0,0,0,0,0,0,1,2,0,1,0,0,1,0,0,1,0,0,0,0,0,2,0,0,0, -0,0,1,1,0,0,1,0,1,0,0,1,0,0,0,2,1,0,1,0,2,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,0,0,2,3,0,0,1,0,1,0,2,3,2,3,0,0,1,3,0,2,1,0,0,0,0,2,0,1,0, -0,2,1,0,0,1,1,0,2,1,0,0,1,0,0,1,1,0,1,1,2,0,1,0,0,0,0,1,0,0,0,0, -3,2,2,0,0,1,1,0,0,0,0,0,0,3,1,1,1,0,0,0,0,0,1,0,0,0,0,0,2,0,1,0, -0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0, -0,0,0,3,3,0,2,3,2,2,1,2,2,1,1,2,0,1,3,2,2,2,0,0,2,2,0,0,0,1,2,1, -3,0,2,1,1,0,1,1,1,0,1,2,2,2,1,1,2,0,0,0,0,1,0,1,1,0,0,0,0,0,0,0, -0,1,1,2,3,0,3,3,3,2,2,2,2,1,0,1,0,1,0,1,2,2,0,0,2,2,1,3,1,1,2,1, -0,0,1,1,2,0,1,1,0,0,1,2,0,2,1,1,2,0,0,1,0,0,0,1,0,1,0,1,0,0,0,0, -3,3,2,0,0,3,1,0,0,0,0,0,0,3,2,1,2,0,0,1,0,0,2,0,0,0,0,0,2,0,1,0, -0,2,1,1,0,0,1,0,1,2,0,0,1,1,0,0,2,1,1,1,1,0,2,0,0,0,0,0,0,0,0,0, -3,3,2,0,0,1,0,0,0,0,1,0,0,3,3,2,2,0,0,1,0,0,2,0,1,0,0,0,2,0,1,0, -0,0,1,1,0,0,2,0,2,1,0,0,1,1,2,1,2,0,2,1,2,1,1,1,0,0,1,1,0,0,0,0, -3,3,2,0,0,2,2,0,0,0,1,1,0,2,2,1,3,1,0,1,0,1,2,0,0,0,0,0,1,0,1,0, -0,1,1,0,0,0,0,0,1,0,0,1,0,0,0,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,2,0,0,0,1,0,0,1,0,0,2,3,1,2,0,0,1,0,0,2,0,0,0,1,0,2,0,2,0, -0,1,1,2,2,1,2,0,2,1,1,0,0,1,1,0,1,1,1,1,2,1,1,0,0,0,0,0,0,0,0,0, -3,3,3,0,2,1,2,1,0,0,1,1,0,3,3,1,2,0,0,1,0,0,2,0,2,0,1,1,2,0,0,0, -0,0,1,1,1,1,2,0,1,1,0,1,1,1,1,0,0,0,1,1,1,0,1,0,0,0,1,0,0,0,0,0, -3,3,3,0,2,2,3,2,0,0,1,0,0,2,3,1,0,0,0,0,0,0,2,0,2,0,0,0,2,0,0,0, -0,1,1,0,0,0,1,0,0,1,0,1,1,0,1,0,1,1,1,0,1,0,0,0,0,0,0,0,0,0,0,0, -3,2,3,0,0,0,0,0,0,0,1,0,0,2,2,2,2,0,0,1,0,0,2,0,0,0,0,0,2,0,1,0, -0,0,2,1,1,0,1,0,2,1,1,0,0,1,1,2,1,0,2,0,2,0,1,0,0,0,2,0,0,0,0,0, -0,0,0,2,2,0,2,1,1,1,1,2,2,0,0,1,0,1,0,0,1,3,0,0,0,0,1,0,0,2,1,0, -0,0,1,0,1,0,0,0,0,0,2,1,0,1,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0, -2,0,0,2,3,0,2,3,1,2,2,0,2,0,0,2,0,2,1,1,1,2,1,0,0,1,2,1,1,2,1,0, -1,0,2,0,1,0,1,1,0,0,2,2,1,2,1,1,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,0,2,1,2,0,0,0,1,0,0,3,2,0,1,0,0,1,0,0,2,0,0,0,1,2,1,0,1,0, -0,0,0,0,1,0,1,0,0,1,0,0,0,0,1,0,1,0,1,1,1,0,1,0,0,0,0,0,0,0,0,0, -0,0,0,2,2,0,2,2,1,1,0,1,1,1,1,1,0,0,1,2,1,1,1,0,1,0,0,0,1,1,1,1, -0,0,2,1,0,1,1,1,0,1,1,2,1,2,1,1,2,0,1,1,2,1,0,2,0,0,0,0,0,0,0,0, -3,2,2,0,0,2,0,0,0,0,0,0,0,2,2,0,2,0,0,1,0,0,2,0,0,0,0,0,2,0,0,0, -0,2,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0, -0,0,0,3,2,0,2,2,0,1,1,0,1,0,0,1,0,0,0,1,0,1,0,0,0,0,0,1,0,0,0,0, -2,0,1,0,1,0,1,1,0,0,1,2,0,1,0,1,1,0,0,1,0,1,0,2,0,0,0,0,0,0,0,0, -2,2,2,0,1,1,0,0,0,1,0,0,0,1,2,0,1,0,0,1,0,0,1,0,0,0,0,1,2,0,1,0, -0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,1,0,1,0,2,0,0,0,0,0,0,0,0,0,0,0, -2,2,2,2,1,0,1,1,1,0,0,0,0,1,2,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0, -1,1,2,0,1,0,0,0,1,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,2,0,0,0,0,0,1, -0,0,1,2,2,0,2,1,2,1,1,2,2,0,0,0,0,1,0,0,1,1,0,0,2,0,0,0,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0, -2,2,2,0,0,0,1,0,0,0,0,0,0,2,2,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0, -0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,1,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,2,2,0,1,0,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,1,0,0,0,0,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -) - -Latin5TurkishModel = { - 'char_to_order_map': Latin5_TurkishCharToOrderMap, - 'precedence_matrix': TurkishLangModel, - 'typical_positive_ratio': 0.970290, - 'keep_english_letter': True, - 'charset_name': "ISO-8859-9", - 'language': 'Turkish', -} diff --git a/pipenv/vendor/chardet/latin1prober.py b/pipenv/vendor/chardet/latin1prober.py deleted file mode 100644 index 7d1e8c20..00000000 --- a/pipenv/vendor/chardet/latin1prober.py +++ /dev/null @@ -1,145 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Universal charset detector code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 2001 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# Shy Shalom - original C code -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .charsetprober import CharSetProber -from .enums import ProbingState - -FREQ_CAT_NUM = 4 - -UDF = 0 # undefined -OTH = 1 # other -ASC = 2 # ascii capital letter -ASS = 3 # ascii small letter -ACV = 4 # accent capital vowel -ACO = 5 # accent capital other -ASV = 6 # accent small vowel -ASO = 7 # accent small other -CLASS_NUM = 8 # total classes - -Latin1_CharToClass = ( - OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 00 - 07 - OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 08 - 0F - OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 10 - 17 - OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 18 - 1F - OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 20 - 27 - OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 28 - 2F - OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 30 - 37 - OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 38 - 3F - OTH, ASC, ASC, ASC, ASC, ASC, ASC, ASC, # 40 - 47 - ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, # 48 - 4F - ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, # 50 - 57 - ASC, ASC, ASC, OTH, OTH, OTH, OTH, OTH, # 58 - 5F - OTH, ASS, ASS, ASS, ASS, ASS, ASS, ASS, # 60 - 67 - ASS, ASS, ASS, ASS, ASS, ASS, ASS, ASS, # 68 - 6F - ASS, ASS, ASS, ASS, ASS, ASS, ASS, ASS, # 70 - 77 - ASS, ASS, ASS, OTH, OTH, OTH, OTH, OTH, # 78 - 7F - OTH, UDF, OTH, ASO, OTH, OTH, OTH, OTH, # 80 - 87 - OTH, OTH, ACO, OTH, ACO, UDF, ACO, UDF, # 88 - 8F - UDF, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 90 - 97 - OTH, OTH, ASO, OTH, ASO, UDF, ASO, ACO, # 98 - 9F - OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # A0 - A7 - OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # A8 - AF - OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # B0 - B7 - OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # B8 - BF - ACV, ACV, ACV, ACV, ACV, ACV, ACO, ACO, # C0 - C7 - ACV, ACV, ACV, ACV, ACV, ACV, ACV, ACV, # C8 - CF - ACO, ACO, ACV, ACV, ACV, ACV, ACV, OTH, # D0 - D7 - ACV, ACV, ACV, ACV, ACV, ACO, ACO, ACO, # D8 - DF - ASV, ASV, ASV, ASV, ASV, ASV, ASO, ASO, # E0 - E7 - ASV, ASV, ASV, ASV, ASV, ASV, ASV, ASV, # E8 - EF - ASO, ASO, ASV, ASV, ASV, ASV, ASV, OTH, # F0 - F7 - ASV, ASV, ASV, ASV, ASV, ASO, ASO, ASO, # F8 - FF -) - -# 0 : illegal -# 1 : very unlikely -# 2 : normal -# 3 : very likely -Latin1ClassModel = ( -# UDF OTH ASC ASS ACV ACO ASV ASO - 0, 0, 0, 0, 0, 0, 0, 0, # UDF - 0, 3, 3, 3, 3, 3, 3, 3, # OTH - 0, 3, 3, 3, 3, 3, 3, 3, # ASC - 0, 3, 3, 3, 1, 1, 3, 3, # ASS - 0, 3, 3, 3, 1, 2, 1, 2, # ACV - 0, 3, 3, 3, 3, 3, 3, 3, # ACO - 0, 3, 1, 3, 1, 1, 1, 3, # ASV - 0, 3, 1, 3, 1, 1, 3, 3, # ASO -) - - -class Latin1Prober(CharSetProber): - def __init__(self): - super(Latin1Prober, self).__init__() - self._last_char_class = None - self._freq_counter = None - self.reset() - - def reset(self): - self._last_char_class = OTH - self._freq_counter = [0] * FREQ_CAT_NUM - CharSetProber.reset(self) - - @property - def charset_name(self): - return "ISO-8859-1" - - @property - def language(self): - return "" - - def feed(self, byte_str): - byte_str = self.filter_with_english_letters(byte_str) - for c in byte_str: - char_class = Latin1_CharToClass[c] - freq = Latin1ClassModel[(self._last_char_class * CLASS_NUM) - + char_class] - if freq == 0: - self._state = ProbingState.NOT_ME - break - self._freq_counter[freq] += 1 - self._last_char_class = char_class - - return self.state - - def get_confidence(self): - if self.state == ProbingState.NOT_ME: - return 0.01 - - total = sum(self._freq_counter) - if total < 0.01: - confidence = 0.0 - else: - confidence = ((self._freq_counter[3] - self._freq_counter[1] * 20.0) - / total) - if confidence < 0.0: - confidence = 0.0 - # lower the confidence of latin1 so that other more accurate - # detector can take priority. - confidence = confidence * 0.73 - return confidence diff --git a/pipenv/vendor/chardet/mbcharsetprober.py b/pipenv/vendor/chardet/mbcharsetprober.py deleted file mode 100644 index 6256ecfd..00000000 --- a/pipenv/vendor/chardet/mbcharsetprober.py +++ /dev/null @@ -1,91 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Universal charset detector code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 2001 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# Shy Shalom - original C code -# Proofpoint, Inc. -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .charsetprober import CharSetProber -from .enums import ProbingState, MachineState - - -class MultiByteCharSetProber(CharSetProber): - """ - MultiByteCharSetProber - """ - - def __init__(self, lang_filter=None): - super(MultiByteCharSetProber, self).__init__(lang_filter=lang_filter) - self.distribution_analyzer = None - self.coding_sm = None - self._last_char = [0, 0] - - def reset(self): - super(MultiByteCharSetProber, self).reset() - if self.coding_sm: - self.coding_sm.reset() - if self.distribution_analyzer: - self.distribution_analyzer.reset() - self._last_char = [0, 0] - - @property - def charset_name(self): - raise NotImplementedError - - @property - def language(self): - raise NotImplementedError - - def feed(self, byte_str): - for i in range(len(byte_str)): - coding_state = self.coding_sm.next_state(byte_str[i]) - if coding_state == MachineState.ERROR: - self.logger.debug('%s %s prober hit error at byte %s', - self.charset_name, self.language, i) - self._state = ProbingState.NOT_ME - break - elif coding_state == MachineState.ITS_ME: - self._state = ProbingState.FOUND_IT - break - elif coding_state == MachineState.START: - char_len = self.coding_sm.get_current_charlen() - if i == 0: - self._last_char[1] = byte_str[0] - self.distribution_analyzer.feed(self._last_char, char_len) - else: - self.distribution_analyzer.feed(byte_str[i - 1:i + 1], - char_len) - - self._last_char[0] = byte_str[-1] - - if self.state == ProbingState.DETECTING: - if (self.distribution_analyzer.got_enough_data() and - (self.get_confidence() > self.SHORTCUT_THRESHOLD)): - self._state = ProbingState.FOUND_IT - - return self.state - - def get_confidence(self): - return self.distribution_analyzer.get_confidence() diff --git a/pipenv/vendor/chardet/mbcsgroupprober.py b/pipenv/vendor/chardet/mbcsgroupprober.py deleted file mode 100644 index 530abe75..00000000 --- a/pipenv/vendor/chardet/mbcsgroupprober.py +++ /dev/null @@ -1,54 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Universal charset detector code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 2001 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# Shy Shalom - original C code -# Proofpoint, Inc. -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .charsetgroupprober import CharSetGroupProber -from .utf8prober import UTF8Prober -from .sjisprober import SJISProber -from .eucjpprober import EUCJPProber -from .gb2312prober import GB2312Prober -from .euckrprober import EUCKRProber -from .cp949prober import CP949Prober -from .big5prober import Big5Prober -from .euctwprober import EUCTWProber - - -class MBCSGroupProber(CharSetGroupProber): - def __init__(self, lang_filter=None): - super(MBCSGroupProber, self).__init__(lang_filter=lang_filter) - self.probers = [ - UTF8Prober(), - SJISProber(), - EUCJPProber(), - GB2312Prober(), - EUCKRProber(), - CP949Prober(), - Big5Prober(), - EUCTWProber() - ] - self.reset() diff --git a/pipenv/vendor/chardet/mbcssm.py b/pipenv/vendor/chardet/mbcssm.py deleted file mode 100644 index 8360d0f2..00000000 --- a/pipenv/vendor/chardet/mbcssm.py +++ /dev/null @@ -1,572 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is mozilla.org code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .enums import MachineState - -# BIG5 - -BIG5_CLS = ( - 1,1,1,1,1,1,1,1, # 00 - 07 #allow 0x00 as legal value - 1,1,1,1,1,1,0,0, # 08 - 0f - 1,1,1,1,1,1,1,1, # 10 - 17 - 1,1,1,0,1,1,1,1, # 18 - 1f - 1,1,1,1,1,1,1,1, # 20 - 27 - 1,1,1,1,1,1,1,1, # 28 - 2f - 1,1,1,1,1,1,1,1, # 30 - 37 - 1,1,1,1,1,1,1,1, # 38 - 3f - 2,2,2,2,2,2,2,2, # 40 - 47 - 2,2,2,2,2,2,2,2, # 48 - 4f - 2,2,2,2,2,2,2,2, # 50 - 57 - 2,2,2,2,2,2,2,2, # 58 - 5f - 2,2,2,2,2,2,2,2, # 60 - 67 - 2,2,2,2,2,2,2,2, # 68 - 6f - 2,2,2,2,2,2,2,2, # 70 - 77 - 2,2,2,2,2,2,2,1, # 78 - 7f - 4,4,4,4,4,4,4,4, # 80 - 87 - 4,4,4,4,4,4,4,4, # 88 - 8f - 4,4,4,4,4,4,4,4, # 90 - 97 - 4,4,4,4,4,4,4,4, # 98 - 9f - 4,3,3,3,3,3,3,3, # a0 - a7 - 3,3,3,3,3,3,3,3, # a8 - af - 3,3,3,3,3,3,3,3, # b0 - b7 - 3,3,3,3,3,3,3,3, # b8 - bf - 3,3,3,3,3,3,3,3, # c0 - c7 - 3,3,3,3,3,3,3,3, # c8 - cf - 3,3,3,3,3,3,3,3, # d0 - d7 - 3,3,3,3,3,3,3,3, # d8 - df - 3,3,3,3,3,3,3,3, # e0 - e7 - 3,3,3,3,3,3,3,3, # e8 - ef - 3,3,3,3,3,3,3,3, # f0 - f7 - 3,3,3,3,3,3,3,0 # f8 - ff -) - -BIG5_ST = ( - MachineState.ERROR,MachineState.START,MachineState.START, 3,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#00-07 - MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,#08-0f - MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START#10-17 -) - -BIG5_CHAR_LEN_TABLE = (0, 1, 1, 2, 0) - -BIG5_SM_MODEL = {'class_table': BIG5_CLS, - 'class_factor': 5, - 'state_table': BIG5_ST, - 'char_len_table': BIG5_CHAR_LEN_TABLE, - 'name': 'Big5'} - -# CP949 - -CP949_CLS = ( - 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,0,0, # 00 - 0f - 1,1,1,1,1,1,1,1, 1,1,1,0,1,1,1,1, # 10 - 1f - 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, # 20 - 2f - 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, # 30 - 3f - 1,4,4,4,4,4,4,4, 4,4,4,4,4,4,4,4, # 40 - 4f - 4,4,5,5,5,5,5,5, 5,5,5,1,1,1,1,1, # 50 - 5f - 1,5,5,5,5,5,5,5, 5,5,5,5,5,5,5,5, # 60 - 6f - 5,5,5,5,5,5,5,5, 5,5,5,1,1,1,1,1, # 70 - 7f - 0,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6, # 80 - 8f - 6,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6, # 90 - 9f - 6,7,7,7,7,7,7,7, 7,7,7,7,7,8,8,8, # a0 - af - 7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7, # b0 - bf - 7,7,7,7,7,7,9,2, 2,3,2,2,2,2,2,2, # c0 - cf - 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, # d0 - df - 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, # e0 - ef - 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,0, # f0 - ff -) - -CP949_ST = ( -#cls= 0 1 2 3 4 5 6 7 8 9 # previous state = - MachineState.ERROR,MachineState.START, 3,MachineState.ERROR,MachineState.START,MachineState.START, 4, 5,MachineState.ERROR, 6, # MachineState.START - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, # MachineState.ERROR - MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME, # MachineState.ITS_ME - MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START, # 3 - MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START, # 4 - MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START, # 5 - MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START, # 6 -) - -CP949_CHAR_LEN_TABLE = (0, 1, 2, 0, 1, 1, 2, 2, 0, 2) - -CP949_SM_MODEL = {'class_table': CP949_CLS, - 'class_factor': 10, - 'state_table': CP949_ST, - 'char_len_table': CP949_CHAR_LEN_TABLE, - 'name': 'CP949'} - -# EUC-JP - -EUCJP_CLS = ( - 4,4,4,4,4,4,4,4, # 00 - 07 - 4,4,4,4,4,4,5,5, # 08 - 0f - 4,4,4,4,4,4,4,4, # 10 - 17 - 4,4,4,5,4,4,4,4, # 18 - 1f - 4,4,4,4,4,4,4,4, # 20 - 27 - 4,4,4,4,4,4,4,4, # 28 - 2f - 4,4,4,4,4,4,4,4, # 30 - 37 - 4,4,4,4,4,4,4,4, # 38 - 3f - 4,4,4,4,4,4,4,4, # 40 - 47 - 4,4,4,4,4,4,4,4, # 48 - 4f - 4,4,4,4,4,4,4,4, # 50 - 57 - 4,4,4,4,4,4,4,4, # 58 - 5f - 4,4,4,4,4,4,4,4, # 60 - 67 - 4,4,4,4,4,4,4,4, # 68 - 6f - 4,4,4,4,4,4,4,4, # 70 - 77 - 4,4,4,4,4,4,4,4, # 78 - 7f - 5,5,5,5,5,5,5,5, # 80 - 87 - 5,5,5,5,5,5,1,3, # 88 - 8f - 5,5,5,5,5,5,5,5, # 90 - 97 - 5,5,5,5,5,5,5,5, # 98 - 9f - 5,2,2,2,2,2,2,2, # a0 - a7 - 2,2,2,2,2,2,2,2, # a8 - af - 2,2,2,2,2,2,2,2, # b0 - b7 - 2,2,2,2,2,2,2,2, # b8 - bf - 2,2,2,2,2,2,2,2, # c0 - c7 - 2,2,2,2,2,2,2,2, # c8 - cf - 2,2,2,2,2,2,2,2, # d0 - d7 - 2,2,2,2,2,2,2,2, # d8 - df - 0,0,0,0,0,0,0,0, # e0 - e7 - 0,0,0,0,0,0,0,0, # e8 - ef - 0,0,0,0,0,0,0,0, # f0 - f7 - 0,0,0,0,0,0,0,5 # f8 - ff -) - -EUCJP_ST = ( - 3, 4, 3, 5,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#00-07 - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,#08-0f - MachineState.ITS_ME,MachineState.ITS_ME,MachineState.START,MachineState.ERROR,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#10-17 - MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 3,MachineState.ERROR,#18-1f - 3,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START#20-27 -) - -EUCJP_CHAR_LEN_TABLE = (2, 2, 2, 3, 1, 0) - -EUCJP_SM_MODEL = {'class_table': EUCJP_CLS, - 'class_factor': 6, - 'state_table': EUCJP_ST, - 'char_len_table': EUCJP_CHAR_LEN_TABLE, - 'name': 'EUC-JP'} - -# EUC-KR - -EUCKR_CLS = ( - 1,1,1,1,1,1,1,1, # 00 - 07 - 1,1,1,1,1,1,0,0, # 08 - 0f - 1,1,1,1,1,1,1,1, # 10 - 17 - 1,1,1,0,1,1,1,1, # 18 - 1f - 1,1,1,1,1,1,1,1, # 20 - 27 - 1,1,1,1,1,1,1,1, # 28 - 2f - 1,1,1,1,1,1,1,1, # 30 - 37 - 1,1,1,1,1,1,1,1, # 38 - 3f - 1,1,1,1,1,1,1,1, # 40 - 47 - 1,1,1,1,1,1,1,1, # 48 - 4f - 1,1,1,1,1,1,1,1, # 50 - 57 - 1,1,1,1,1,1,1,1, # 58 - 5f - 1,1,1,1,1,1,1,1, # 60 - 67 - 1,1,1,1,1,1,1,1, # 68 - 6f - 1,1,1,1,1,1,1,1, # 70 - 77 - 1,1,1,1,1,1,1,1, # 78 - 7f - 0,0,0,0,0,0,0,0, # 80 - 87 - 0,0,0,0,0,0,0,0, # 88 - 8f - 0,0,0,0,0,0,0,0, # 90 - 97 - 0,0,0,0,0,0,0,0, # 98 - 9f - 0,2,2,2,2,2,2,2, # a0 - a7 - 2,2,2,2,2,3,3,3, # a8 - af - 2,2,2,2,2,2,2,2, # b0 - b7 - 2,2,2,2,2,2,2,2, # b8 - bf - 2,2,2,2,2,2,2,2, # c0 - c7 - 2,3,2,2,2,2,2,2, # c8 - cf - 2,2,2,2,2,2,2,2, # d0 - d7 - 2,2,2,2,2,2,2,2, # d8 - df - 2,2,2,2,2,2,2,2, # e0 - e7 - 2,2,2,2,2,2,2,2, # e8 - ef - 2,2,2,2,2,2,2,2, # f0 - f7 - 2,2,2,2,2,2,2,0 # f8 - ff -) - -EUCKR_ST = ( - MachineState.ERROR,MachineState.START, 3,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#00-07 - MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START #08-0f -) - -EUCKR_CHAR_LEN_TABLE = (0, 1, 2, 0) - -EUCKR_SM_MODEL = {'class_table': EUCKR_CLS, - 'class_factor': 4, - 'state_table': EUCKR_ST, - 'char_len_table': EUCKR_CHAR_LEN_TABLE, - 'name': 'EUC-KR'} - -# EUC-TW - -EUCTW_CLS = ( - 2,2,2,2,2,2,2,2, # 00 - 07 - 2,2,2,2,2,2,0,0, # 08 - 0f - 2,2,2,2,2,2,2,2, # 10 - 17 - 2,2,2,0,2,2,2,2, # 18 - 1f - 2,2,2,2,2,2,2,2, # 20 - 27 - 2,2,2,2,2,2,2,2, # 28 - 2f - 2,2,2,2,2,2,2,2, # 30 - 37 - 2,2,2,2,2,2,2,2, # 38 - 3f - 2,2,2,2,2,2,2,2, # 40 - 47 - 2,2,2,2,2,2,2,2, # 48 - 4f - 2,2,2,2,2,2,2,2, # 50 - 57 - 2,2,2,2,2,2,2,2, # 58 - 5f - 2,2,2,2,2,2,2,2, # 60 - 67 - 2,2,2,2,2,2,2,2, # 68 - 6f - 2,2,2,2,2,2,2,2, # 70 - 77 - 2,2,2,2,2,2,2,2, # 78 - 7f - 0,0,0,0,0,0,0,0, # 80 - 87 - 0,0,0,0,0,0,6,0, # 88 - 8f - 0,0,0,0,0,0,0,0, # 90 - 97 - 0,0,0,0,0,0,0,0, # 98 - 9f - 0,3,4,4,4,4,4,4, # a0 - a7 - 5,5,1,1,1,1,1,1, # a8 - af - 1,1,1,1,1,1,1,1, # b0 - b7 - 1,1,1,1,1,1,1,1, # b8 - bf - 1,1,3,1,3,3,3,3, # c0 - c7 - 3,3,3,3,3,3,3,3, # c8 - cf - 3,3,3,3,3,3,3,3, # d0 - d7 - 3,3,3,3,3,3,3,3, # d8 - df - 3,3,3,3,3,3,3,3, # e0 - e7 - 3,3,3,3,3,3,3,3, # e8 - ef - 3,3,3,3,3,3,3,3, # f0 - f7 - 3,3,3,3,3,3,3,0 # f8 - ff -) - -EUCTW_ST = ( - MachineState.ERROR,MachineState.ERROR,MachineState.START, 3, 3, 3, 4,MachineState.ERROR,#00-07 - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,#08-0f - MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.START,MachineState.ERROR,#10-17 - MachineState.START,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#18-1f - 5,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.ERROR,MachineState.START,MachineState.START,#20-27 - MachineState.START,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START #28-2f -) - -EUCTW_CHAR_LEN_TABLE = (0, 0, 1, 2, 2, 2, 3) - -EUCTW_SM_MODEL = {'class_table': EUCTW_CLS, - 'class_factor': 7, - 'state_table': EUCTW_ST, - 'char_len_table': EUCTW_CHAR_LEN_TABLE, - 'name': 'x-euc-tw'} - -# GB2312 - -GB2312_CLS = ( - 1,1,1,1,1,1,1,1, # 00 - 07 - 1,1,1,1,1,1,0,0, # 08 - 0f - 1,1,1,1,1,1,1,1, # 10 - 17 - 1,1,1,0,1,1,1,1, # 18 - 1f - 1,1,1,1,1,1,1,1, # 20 - 27 - 1,1,1,1,1,1,1,1, # 28 - 2f - 3,3,3,3,3,3,3,3, # 30 - 37 - 3,3,1,1,1,1,1,1, # 38 - 3f - 2,2,2,2,2,2,2,2, # 40 - 47 - 2,2,2,2,2,2,2,2, # 48 - 4f - 2,2,2,2,2,2,2,2, # 50 - 57 - 2,2,2,2,2,2,2,2, # 58 - 5f - 2,2,2,2,2,2,2,2, # 60 - 67 - 2,2,2,2,2,2,2,2, # 68 - 6f - 2,2,2,2,2,2,2,2, # 70 - 77 - 2,2,2,2,2,2,2,4, # 78 - 7f - 5,6,6,6,6,6,6,6, # 80 - 87 - 6,6,6,6,6,6,6,6, # 88 - 8f - 6,6,6,6,6,6,6,6, # 90 - 97 - 6,6,6,6,6,6,6,6, # 98 - 9f - 6,6,6,6,6,6,6,6, # a0 - a7 - 6,6,6,6,6,6,6,6, # a8 - af - 6,6,6,6,6,6,6,6, # b0 - b7 - 6,6,6,6,6,6,6,6, # b8 - bf - 6,6,6,6,6,6,6,6, # c0 - c7 - 6,6,6,6,6,6,6,6, # c8 - cf - 6,6,6,6,6,6,6,6, # d0 - d7 - 6,6,6,6,6,6,6,6, # d8 - df - 6,6,6,6,6,6,6,6, # e0 - e7 - 6,6,6,6,6,6,6,6, # e8 - ef - 6,6,6,6,6,6,6,6, # f0 - f7 - 6,6,6,6,6,6,6,0 # f8 - ff -) - -GB2312_ST = ( - MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START, 3,MachineState.ERROR,#00-07 - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,#08-0f - MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.START,#10-17 - 4,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#18-1f - MachineState.ERROR,MachineState.ERROR, 5,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,#20-27 - MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START #28-2f -) - -# To be accurate, the length of class 6 can be either 2 or 4. -# But it is not necessary to discriminate between the two since -# it is used for frequency analysis only, and we are validating -# each code range there as well. So it is safe to set it to be -# 2 here. -GB2312_CHAR_LEN_TABLE = (0, 1, 1, 1, 1, 1, 2) - -GB2312_SM_MODEL = {'class_table': GB2312_CLS, - 'class_factor': 7, - 'state_table': GB2312_ST, - 'char_len_table': GB2312_CHAR_LEN_TABLE, - 'name': 'GB2312'} - -# Shift_JIS - -SJIS_CLS = ( - 1,1,1,1,1,1,1,1, # 00 - 07 - 1,1,1,1,1,1,0,0, # 08 - 0f - 1,1,1,1,1,1,1,1, # 10 - 17 - 1,1,1,0,1,1,1,1, # 18 - 1f - 1,1,1,1,1,1,1,1, # 20 - 27 - 1,1,1,1,1,1,1,1, # 28 - 2f - 1,1,1,1,1,1,1,1, # 30 - 37 - 1,1,1,1,1,1,1,1, # 38 - 3f - 2,2,2,2,2,2,2,2, # 40 - 47 - 2,2,2,2,2,2,2,2, # 48 - 4f - 2,2,2,2,2,2,2,2, # 50 - 57 - 2,2,2,2,2,2,2,2, # 58 - 5f - 2,2,2,2,2,2,2,2, # 60 - 67 - 2,2,2,2,2,2,2,2, # 68 - 6f - 2,2,2,2,2,2,2,2, # 70 - 77 - 2,2,2,2,2,2,2,1, # 78 - 7f - 3,3,3,3,3,2,2,3, # 80 - 87 - 3,3,3,3,3,3,3,3, # 88 - 8f - 3,3,3,3,3,3,3,3, # 90 - 97 - 3,3,3,3,3,3,3,3, # 98 - 9f - #0xa0 is illegal in sjis encoding, but some pages does - #contain such byte. We need to be more error forgiven. - 2,2,2,2,2,2,2,2, # a0 - a7 - 2,2,2,2,2,2,2,2, # a8 - af - 2,2,2,2,2,2,2,2, # b0 - b7 - 2,2,2,2,2,2,2,2, # b8 - bf - 2,2,2,2,2,2,2,2, # c0 - c7 - 2,2,2,2,2,2,2,2, # c8 - cf - 2,2,2,2,2,2,2,2, # d0 - d7 - 2,2,2,2,2,2,2,2, # d8 - df - 3,3,3,3,3,3,3,3, # e0 - e7 - 3,3,3,3,3,4,4,4, # e8 - ef - 3,3,3,3,3,3,3,3, # f0 - f7 - 3,3,3,3,3,0,0,0) # f8 - ff - - -SJIS_ST = ( - MachineState.ERROR,MachineState.START,MachineState.START, 3,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#00-07 - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,#08-0f - MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START #10-17 -) - -SJIS_CHAR_LEN_TABLE = (0, 1, 1, 2, 0, 0) - -SJIS_SM_MODEL = {'class_table': SJIS_CLS, - 'class_factor': 6, - 'state_table': SJIS_ST, - 'char_len_table': SJIS_CHAR_LEN_TABLE, - 'name': 'Shift_JIS'} - -# UCS2-BE - -UCS2BE_CLS = ( - 0,0,0,0,0,0,0,0, # 00 - 07 - 0,0,1,0,0,2,0,0, # 08 - 0f - 0,0,0,0,0,0,0,0, # 10 - 17 - 0,0,0,3,0,0,0,0, # 18 - 1f - 0,0,0,0,0,0,0,0, # 20 - 27 - 0,3,3,3,3,3,0,0, # 28 - 2f - 0,0,0,0,0,0,0,0, # 30 - 37 - 0,0,0,0,0,0,0,0, # 38 - 3f - 0,0,0,0,0,0,0,0, # 40 - 47 - 0,0,0,0,0,0,0,0, # 48 - 4f - 0,0,0,0,0,0,0,0, # 50 - 57 - 0,0,0,0,0,0,0,0, # 58 - 5f - 0,0,0,0,0,0,0,0, # 60 - 67 - 0,0,0,0,0,0,0,0, # 68 - 6f - 0,0,0,0,0,0,0,0, # 70 - 77 - 0,0,0,0,0,0,0,0, # 78 - 7f - 0,0,0,0,0,0,0,0, # 80 - 87 - 0,0,0,0,0,0,0,0, # 88 - 8f - 0,0,0,0,0,0,0,0, # 90 - 97 - 0,0,0,0,0,0,0,0, # 98 - 9f - 0,0,0,0,0,0,0,0, # a0 - a7 - 0,0,0,0,0,0,0,0, # a8 - af - 0,0,0,0,0,0,0,0, # b0 - b7 - 0,0,0,0,0,0,0,0, # b8 - bf - 0,0,0,0,0,0,0,0, # c0 - c7 - 0,0,0,0,0,0,0,0, # c8 - cf - 0,0,0,0,0,0,0,0, # d0 - d7 - 0,0,0,0,0,0,0,0, # d8 - df - 0,0,0,0,0,0,0,0, # e0 - e7 - 0,0,0,0,0,0,0,0, # e8 - ef - 0,0,0,0,0,0,0,0, # f0 - f7 - 0,0,0,0,0,0,4,5 # f8 - ff -) - -UCS2BE_ST = ( - 5, 7, 7,MachineState.ERROR, 4, 3,MachineState.ERROR,MachineState.ERROR,#00-07 - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,#08-0f - MachineState.ITS_ME,MachineState.ITS_ME, 6, 6, 6, 6,MachineState.ERROR,MachineState.ERROR,#10-17 - 6, 6, 6, 6, 6,MachineState.ITS_ME, 6, 6,#18-1f - 6, 6, 6, 6, 5, 7, 7,MachineState.ERROR,#20-27 - 5, 8, 6, 6,MachineState.ERROR, 6, 6, 6,#28-2f - 6, 6, 6, 6,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START #30-37 -) - -UCS2BE_CHAR_LEN_TABLE = (2, 2, 2, 0, 2, 2) - -UCS2BE_SM_MODEL = {'class_table': UCS2BE_CLS, - 'class_factor': 6, - 'state_table': UCS2BE_ST, - 'char_len_table': UCS2BE_CHAR_LEN_TABLE, - 'name': 'UTF-16BE'} - -# UCS2-LE - -UCS2LE_CLS = ( - 0,0,0,0,0,0,0,0, # 00 - 07 - 0,0,1,0,0,2,0,0, # 08 - 0f - 0,0,0,0,0,0,0,0, # 10 - 17 - 0,0,0,3,0,0,0,0, # 18 - 1f - 0,0,0,0,0,0,0,0, # 20 - 27 - 0,3,3,3,3,3,0,0, # 28 - 2f - 0,0,0,0,0,0,0,0, # 30 - 37 - 0,0,0,0,0,0,0,0, # 38 - 3f - 0,0,0,0,0,0,0,0, # 40 - 47 - 0,0,0,0,0,0,0,0, # 48 - 4f - 0,0,0,0,0,0,0,0, # 50 - 57 - 0,0,0,0,0,0,0,0, # 58 - 5f - 0,0,0,0,0,0,0,0, # 60 - 67 - 0,0,0,0,0,0,0,0, # 68 - 6f - 0,0,0,0,0,0,0,0, # 70 - 77 - 0,0,0,0,0,0,0,0, # 78 - 7f - 0,0,0,0,0,0,0,0, # 80 - 87 - 0,0,0,0,0,0,0,0, # 88 - 8f - 0,0,0,0,0,0,0,0, # 90 - 97 - 0,0,0,0,0,0,0,0, # 98 - 9f - 0,0,0,0,0,0,0,0, # a0 - a7 - 0,0,0,0,0,0,0,0, # a8 - af - 0,0,0,0,0,0,0,0, # b0 - b7 - 0,0,0,0,0,0,0,0, # b8 - bf - 0,0,0,0,0,0,0,0, # c0 - c7 - 0,0,0,0,0,0,0,0, # c8 - cf - 0,0,0,0,0,0,0,0, # d0 - d7 - 0,0,0,0,0,0,0,0, # d8 - df - 0,0,0,0,0,0,0,0, # e0 - e7 - 0,0,0,0,0,0,0,0, # e8 - ef - 0,0,0,0,0,0,0,0, # f0 - f7 - 0,0,0,0,0,0,4,5 # f8 - ff -) - -UCS2LE_ST = ( - 6, 6, 7, 6, 4, 3,MachineState.ERROR,MachineState.ERROR,#00-07 - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,#08-0f - MachineState.ITS_ME,MachineState.ITS_ME, 5, 5, 5,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,#10-17 - 5, 5, 5,MachineState.ERROR, 5,MachineState.ERROR, 6, 6,#18-1f - 7, 6, 8, 8, 5, 5, 5,MachineState.ERROR,#20-27 - 5, 5, 5,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 5, 5,#28-2f - 5, 5, 5,MachineState.ERROR, 5,MachineState.ERROR,MachineState.START,MachineState.START #30-37 -) - -UCS2LE_CHAR_LEN_TABLE = (2, 2, 2, 2, 2, 2) - -UCS2LE_SM_MODEL = {'class_table': UCS2LE_CLS, - 'class_factor': 6, - 'state_table': UCS2LE_ST, - 'char_len_table': UCS2LE_CHAR_LEN_TABLE, - 'name': 'UTF-16LE'} - -# UTF-8 - -UTF8_CLS = ( - 1,1,1,1,1,1,1,1, # 00 - 07 #allow 0x00 as a legal value - 1,1,1,1,1,1,0,0, # 08 - 0f - 1,1,1,1,1,1,1,1, # 10 - 17 - 1,1,1,0,1,1,1,1, # 18 - 1f - 1,1,1,1,1,1,1,1, # 20 - 27 - 1,1,1,1,1,1,1,1, # 28 - 2f - 1,1,1,1,1,1,1,1, # 30 - 37 - 1,1,1,1,1,1,1,1, # 38 - 3f - 1,1,1,1,1,1,1,1, # 40 - 47 - 1,1,1,1,1,1,1,1, # 48 - 4f - 1,1,1,1,1,1,1,1, # 50 - 57 - 1,1,1,1,1,1,1,1, # 58 - 5f - 1,1,1,1,1,1,1,1, # 60 - 67 - 1,1,1,1,1,1,1,1, # 68 - 6f - 1,1,1,1,1,1,1,1, # 70 - 77 - 1,1,1,1,1,1,1,1, # 78 - 7f - 2,2,2,2,3,3,3,3, # 80 - 87 - 4,4,4,4,4,4,4,4, # 88 - 8f - 4,4,4,4,4,4,4,4, # 90 - 97 - 4,4,4,4,4,4,4,4, # 98 - 9f - 5,5,5,5,5,5,5,5, # a0 - a7 - 5,5,5,5,5,5,5,5, # a8 - af - 5,5,5,5,5,5,5,5, # b0 - b7 - 5,5,5,5,5,5,5,5, # b8 - bf - 0,0,6,6,6,6,6,6, # c0 - c7 - 6,6,6,6,6,6,6,6, # c8 - cf - 6,6,6,6,6,6,6,6, # d0 - d7 - 6,6,6,6,6,6,6,6, # d8 - df - 7,8,8,8,8,8,8,8, # e0 - e7 - 8,8,8,8,8,9,8,8, # e8 - ef - 10,11,11,11,11,11,11,11, # f0 - f7 - 12,13,13,13,14,15,0,0 # f8 - ff -) - -UTF8_ST = ( - MachineState.ERROR,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 12, 10,#00-07 - 9, 11, 8, 7, 6, 5, 4, 3,#08-0f - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#10-17 - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#18-1f - MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,#20-27 - MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,#28-2f - MachineState.ERROR,MachineState.ERROR, 5, 5, 5, 5,MachineState.ERROR,MachineState.ERROR,#30-37 - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#38-3f - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 5, 5, 5,MachineState.ERROR,MachineState.ERROR,#40-47 - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#48-4f - MachineState.ERROR,MachineState.ERROR, 7, 7, 7, 7,MachineState.ERROR,MachineState.ERROR,#50-57 - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#58-5f - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 7, 7,MachineState.ERROR,MachineState.ERROR,#60-67 - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#68-6f - MachineState.ERROR,MachineState.ERROR, 9, 9, 9, 9,MachineState.ERROR,MachineState.ERROR,#70-77 - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#78-7f - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 9,MachineState.ERROR,MachineState.ERROR,#80-87 - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#88-8f - MachineState.ERROR,MachineState.ERROR, 12, 12, 12, 12,MachineState.ERROR,MachineState.ERROR,#90-97 - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#98-9f - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 12,MachineState.ERROR,MachineState.ERROR,#a0-a7 - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#a8-af - MachineState.ERROR,MachineState.ERROR, 12, 12, 12,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#b0-b7 - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#b8-bf - MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,#c0-c7 - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR #c8-cf -) - -UTF8_CHAR_LEN_TABLE = (0, 1, 0, 0, 0, 0, 2, 3, 3, 3, 4, 4, 5, 5, 6, 6) - -UTF8_SM_MODEL = {'class_table': UTF8_CLS, - 'class_factor': 16, - 'state_table': UTF8_ST, - 'char_len_table': UTF8_CHAR_LEN_TABLE, - 'name': 'UTF-8'} diff --git a/pipenv/vendor/chardet/sbcharsetprober.py b/pipenv/vendor/chardet/sbcharsetprober.py deleted file mode 100644 index 0adb51de..00000000 --- a/pipenv/vendor/chardet/sbcharsetprober.py +++ /dev/null @@ -1,132 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Universal charset detector code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 2001 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# Shy Shalom - original C code -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .charsetprober import CharSetProber -from .enums import CharacterCategory, ProbingState, SequenceLikelihood - - -class SingleByteCharSetProber(CharSetProber): - SAMPLE_SIZE = 64 - SB_ENOUGH_REL_THRESHOLD = 1024 # 0.25 * SAMPLE_SIZE^2 - POSITIVE_SHORTCUT_THRESHOLD = 0.95 - NEGATIVE_SHORTCUT_THRESHOLD = 0.05 - - def __init__(self, model, reversed=False, name_prober=None): - super(SingleByteCharSetProber, self).__init__() - self._model = model - # TRUE if we need to reverse every pair in the model lookup - self._reversed = reversed - # Optional auxiliary prober for name decision - self._name_prober = name_prober - self._last_order = None - self._seq_counters = None - self._total_seqs = None - self._total_char = None - self._freq_char = None - self.reset() - - def reset(self): - super(SingleByteCharSetProber, self).reset() - # char order of last character - self._last_order = 255 - self._seq_counters = [0] * SequenceLikelihood.get_num_categories() - self._total_seqs = 0 - self._total_char = 0 - # characters that fall in our sampling range - self._freq_char = 0 - - @property - def charset_name(self): - if self._name_prober: - return self._name_prober.charset_name - else: - return self._model['charset_name'] - - @property - def language(self): - if self._name_prober: - return self._name_prober.language - else: - return self._model.get('language') - - def feed(self, byte_str): - if not self._model['keep_english_letter']: - byte_str = self.filter_international_words(byte_str) - if not byte_str: - return self.state - char_to_order_map = self._model['char_to_order_map'] - for i, c in enumerate(byte_str): - # XXX: Order is in range 1-64, so one would think we want 0-63 here, - # but that leads to 27 more test failures than before. - order = char_to_order_map[c] - # XXX: This was SYMBOL_CAT_ORDER before, with a value of 250, but - # CharacterCategory.SYMBOL is actually 253, so we use CONTROL - # to make it closer to the original intent. The only difference - # is whether or not we count digits and control characters for - # _total_char purposes. - if order < CharacterCategory.CONTROL: - self._total_char += 1 - if order < self.SAMPLE_SIZE: - self._freq_char += 1 - if self._last_order < self.SAMPLE_SIZE: - self._total_seqs += 1 - if not self._reversed: - i = (self._last_order * self.SAMPLE_SIZE) + order - model = self._model['precedence_matrix'][i] - else: # reverse the order of the letters in the lookup - i = (order * self.SAMPLE_SIZE) + self._last_order - model = self._model['precedence_matrix'][i] - self._seq_counters[model] += 1 - self._last_order = order - - charset_name = self._model['charset_name'] - if self.state == ProbingState.DETECTING: - if self._total_seqs > self.SB_ENOUGH_REL_THRESHOLD: - confidence = self.get_confidence() - if confidence > self.POSITIVE_SHORTCUT_THRESHOLD: - self.logger.debug('%s confidence = %s, we have a winner', - charset_name, confidence) - self._state = ProbingState.FOUND_IT - elif confidence < self.NEGATIVE_SHORTCUT_THRESHOLD: - self.logger.debug('%s confidence = %s, below negative ' - 'shortcut threshhold %s', charset_name, - confidence, - self.NEGATIVE_SHORTCUT_THRESHOLD) - self._state = ProbingState.NOT_ME - - return self.state - - def get_confidence(self): - r = 0.01 - if self._total_seqs > 0: - r = ((1.0 * self._seq_counters[SequenceLikelihood.POSITIVE]) / - self._total_seqs / self._model['typical_positive_ratio']) - r = r * self._freq_char / self._total_char - if r >= 1.0: - r = 0.99 - return r diff --git a/pipenv/vendor/chardet/sbcsgroupprober.py b/pipenv/vendor/chardet/sbcsgroupprober.py deleted file mode 100644 index 98e95dc1..00000000 --- a/pipenv/vendor/chardet/sbcsgroupprober.py +++ /dev/null @@ -1,73 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Universal charset detector code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 2001 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# Shy Shalom - original C code -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .charsetgroupprober import CharSetGroupProber -from .sbcharsetprober import SingleByteCharSetProber -from .langcyrillicmodel import (Win1251CyrillicModel, Koi8rModel, - Latin5CyrillicModel, MacCyrillicModel, - Ibm866Model, Ibm855Model) -from .langgreekmodel import Latin7GreekModel, Win1253GreekModel -from .langbulgarianmodel import Latin5BulgarianModel, Win1251BulgarianModel -# from .langhungarianmodel import Latin2HungarianModel, Win1250HungarianModel -from .langthaimodel import TIS620ThaiModel -from .langhebrewmodel import Win1255HebrewModel -from .hebrewprober import HebrewProber -from .langturkishmodel import Latin5TurkishModel - - -class SBCSGroupProber(CharSetGroupProber): - def __init__(self): - super(SBCSGroupProber, self).__init__() - self.probers = [ - SingleByteCharSetProber(Win1251CyrillicModel), - SingleByteCharSetProber(Koi8rModel), - SingleByteCharSetProber(Latin5CyrillicModel), - SingleByteCharSetProber(MacCyrillicModel), - SingleByteCharSetProber(Ibm866Model), - SingleByteCharSetProber(Ibm855Model), - SingleByteCharSetProber(Latin7GreekModel), - SingleByteCharSetProber(Win1253GreekModel), - SingleByteCharSetProber(Latin5BulgarianModel), - SingleByteCharSetProber(Win1251BulgarianModel), - # TODO: Restore Hungarian encodings (iso-8859-2 and windows-1250) - # after we retrain model. - # SingleByteCharSetProber(Latin2HungarianModel), - # SingleByteCharSetProber(Win1250HungarianModel), - SingleByteCharSetProber(TIS620ThaiModel), - SingleByteCharSetProber(Latin5TurkishModel), - ] - hebrew_prober = HebrewProber() - logical_hebrew_prober = SingleByteCharSetProber(Win1255HebrewModel, - False, hebrew_prober) - visual_hebrew_prober = SingleByteCharSetProber(Win1255HebrewModel, True, - hebrew_prober) - hebrew_prober.set_model_probers(logical_hebrew_prober, visual_hebrew_prober) - self.probers.extend([hebrew_prober, logical_hebrew_prober, - visual_hebrew_prober]) - - self.reset() diff --git a/pipenv/vendor/chardet/sjisprober.py b/pipenv/vendor/chardet/sjisprober.py deleted file mode 100644 index 9e29623b..00000000 --- a/pipenv/vendor/chardet/sjisprober.py +++ /dev/null @@ -1,92 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is mozilla.org code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .mbcharsetprober import MultiByteCharSetProber -from .codingstatemachine import CodingStateMachine -from .chardistribution import SJISDistributionAnalysis -from .jpcntx import SJISContextAnalysis -from .mbcssm import SJIS_SM_MODEL -from .enums import ProbingState, MachineState - - -class SJISProber(MultiByteCharSetProber): - def __init__(self): - super(SJISProber, self).__init__() - self.coding_sm = CodingStateMachine(SJIS_SM_MODEL) - self.distribution_analyzer = SJISDistributionAnalysis() - self.context_analyzer = SJISContextAnalysis() - self.reset() - - def reset(self): - super(SJISProber, self).reset() - self.context_analyzer.reset() - - @property - def charset_name(self): - return self.context_analyzer.charset_name - - @property - def language(self): - return "Japanese" - - def feed(self, byte_str): - for i in range(len(byte_str)): - coding_state = self.coding_sm.next_state(byte_str[i]) - if coding_state == MachineState.ERROR: - self.logger.debug('%s %s prober hit error at byte %s', - self.charset_name, self.language, i) - self._state = ProbingState.NOT_ME - break - elif coding_state == MachineState.ITS_ME: - self._state = ProbingState.FOUND_IT - break - elif coding_state == MachineState.START: - char_len = self.coding_sm.get_current_charlen() - if i == 0: - self._last_char[1] = byte_str[0] - self.context_analyzer.feed(self._last_char[2 - char_len:], - char_len) - self.distribution_analyzer.feed(self._last_char, char_len) - else: - self.context_analyzer.feed(byte_str[i + 1 - char_len:i + 3 - - char_len], char_len) - self.distribution_analyzer.feed(byte_str[i - 1:i + 1], - char_len) - - self._last_char[0] = byte_str[-1] - - if self.state == ProbingState.DETECTING: - if (self.context_analyzer.got_enough_data() and - (self.get_confidence() > self.SHORTCUT_THRESHOLD)): - self._state = ProbingState.FOUND_IT - - return self.state - - def get_confidence(self): - context_conf = self.context_analyzer.get_confidence() - distrib_conf = self.distribution_analyzer.get_confidence() - return max(context_conf, distrib_conf) diff --git a/pipenv/vendor/chardet/universaldetector.py b/pipenv/vendor/chardet/universaldetector.py deleted file mode 100644 index 7b4e92d6..00000000 --- a/pipenv/vendor/chardet/universaldetector.py +++ /dev/null @@ -1,286 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Universal charset detector code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 2001 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# Shy Shalom - original C code -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### -""" -Module containing the UniversalDetector detector class, which is the primary -class a user of ``chardet`` should use. - -:author: Mark Pilgrim (initial port to Python) -:author: Shy Shalom (original C code) -:author: Dan Blanchard (major refactoring for 3.0) -:author: Ian Cordasco -""" - - -import codecs -import logging -import re - -from .charsetgroupprober import CharSetGroupProber -from .enums import InputState, LanguageFilter, ProbingState -from .escprober import EscCharSetProber -from .latin1prober import Latin1Prober -from .mbcsgroupprober import MBCSGroupProber -from .sbcsgroupprober import SBCSGroupProber - - -class UniversalDetector(object): - """ - The ``UniversalDetector`` class underlies the ``chardet.detect`` function - and coordinates all of the different charset probers. - - To get a ``dict`` containing an encoding and its confidence, you can simply - run: - - .. code:: - - u = UniversalDetector() - u.feed(some_bytes) - u.close() - detected = u.result - - """ - - MINIMUM_THRESHOLD = 0.20 - HIGH_BYTE_DETECTOR = re.compile(b'[\x80-\xFF]') - ESC_DETECTOR = re.compile(b'(\033|~{)') - WIN_BYTE_DETECTOR = re.compile(b'[\x80-\x9F]') - ISO_WIN_MAP = {'iso-8859-1': 'Windows-1252', - 'iso-8859-2': 'Windows-1250', - 'iso-8859-5': 'Windows-1251', - 'iso-8859-6': 'Windows-1256', - 'iso-8859-7': 'Windows-1253', - 'iso-8859-8': 'Windows-1255', - 'iso-8859-9': 'Windows-1254', - 'iso-8859-13': 'Windows-1257'} - - def __init__(self, lang_filter=LanguageFilter.ALL): - self._esc_charset_prober = None - self._charset_probers = [] - self.result = None - self.done = None - self._got_data = None - self._input_state = None - self._last_char = None - self.lang_filter = lang_filter - self.logger = logging.getLogger(__name__) - self._has_win_bytes = None - self.reset() - - def reset(self): - """ - Reset the UniversalDetector and all of its probers back to their - initial states. This is called by ``__init__``, so you only need to - call this directly in between analyses of different documents. - """ - self.result = {'encoding': None, 'confidence': 0.0, 'language': None} - self.done = False - self._got_data = False - self._has_win_bytes = False - self._input_state = InputState.PURE_ASCII - self._last_char = b'' - if self._esc_charset_prober: - self._esc_charset_prober.reset() - for prober in self._charset_probers: - prober.reset() - - def feed(self, byte_str): - """ - Takes a chunk of a document and feeds it through all of the relevant - charset probers. - - After calling ``feed``, you can check the value of the ``done`` - attribute to see if you need to continue feeding the - ``UniversalDetector`` more data, or if it has made a prediction - (in the ``result`` attribute). - - .. note:: - You should always call ``close`` when you're done feeding in your - document if ``done`` is not already ``True``. - """ - if self.done: - return - - if not len(byte_str): - return - - if not isinstance(byte_str, bytearray): - byte_str = bytearray(byte_str) - - # First check for known BOMs, since these are guaranteed to be correct - if not self._got_data: - # If the data starts with BOM, we know it is UTF - if byte_str.startswith(codecs.BOM_UTF8): - # EF BB BF UTF-8 with BOM - self.result = {'encoding': "UTF-8-SIG", - 'confidence': 1.0, - 'language': ''} - elif byte_str.startswith((codecs.BOM_UTF32_LE, - codecs.BOM_UTF32_BE)): - # FF FE 00 00 UTF-32, little-endian BOM - # 00 00 FE FF UTF-32, big-endian BOM - self.result = {'encoding': "UTF-32", - 'confidence': 1.0, - 'language': ''} - elif byte_str.startswith(b'\xFE\xFF\x00\x00'): - # FE FF 00 00 UCS-4, unusual octet order BOM (3412) - self.result = {'encoding': "X-ISO-10646-UCS-4-3412", - 'confidence': 1.0, - 'language': ''} - elif byte_str.startswith(b'\x00\x00\xFF\xFE'): - # 00 00 FF FE UCS-4, unusual octet order BOM (2143) - self.result = {'encoding': "X-ISO-10646-UCS-4-2143", - 'confidence': 1.0, - 'language': ''} - elif byte_str.startswith((codecs.BOM_LE, codecs.BOM_BE)): - # FF FE UTF-16, little endian BOM - # FE FF UTF-16, big endian BOM - self.result = {'encoding': "UTF-16", - 'confidence': 1.0, - 'language': ''} - - self._got_data = True - if self.result['encoding'] is not None: - self.done = True - return - - # If none of those matched and we've only see ASCII so far, check - # for high bytes and escape sequences - if self._input_state == InputState.PURE_ASCII: - if self.HIGH_BYTE_DETECTOR.search(byte_str): - self._input_state = InputState.HIGH_BYTE - elif self._input_state == InputState.PURE_ASCII and \ - self.ESC_DETECTOR.search(self._last_char + byte_str): - self._input_state = InputState.ESC_ASCII - - self._last_char = byte_str[-1:] - - # If we've seen escape sequences, use the EscCharSetProber, which - # uses a simple state machine to check for known escape sequences in - # HZ and ISO-2022 encodings, since those are the only encodings that - # use such sequences. - if self._input_state == InputState.ESC_ASCII: - if not self._esc_charset_prober: - self._esc_charset_prober = EscCharSetProber(self.lang_filter) - if self._esc_charset_prober.feed(byte_str) == ProbingState.FOUND_IT: - self.result = {'encoding': - self._esc_charset_prober.charset_name, - 'confidence': - self._esc_charset_prober.get_confidence(), - 'language': - self._esc_charset_prober.language} - self.done = True - # If we've seen high bytes (i.e., those with values greater than 127), - # we need to do more complicated checks using all our multi-byte and - # single-byte probers that are left. The single-byte probers - # use character bigram distributions to determine the encoding, whereas - # the multi-byte probers use a combination of character unigram and - # bigram distributions. - elif self._input_state == InputState.HIGH_BYTE: - if not self._charset_probers: - self._charset_probers = [MBCSGroupProber(self.lang_filter)] - # If we're checking non-CJK encodings, use single-byte prober - if self.lang_filter & LanguageFilter.NON_CJK: - self._charset_probers.append(SBCSGroupProber()) - self._charset_probers.append(Latin1Prober()) - for prober in self._charset_probers: - if prober.feed(byte_str) == ProbingState.FOUND_IT: - self.result = {'encoding': prober.charset_name, - 'confidence': prober.get_confidence(), - 'language': prober.language} - self.done = True - break - if self.WIN_BYTE_DETECTOR.search(byte_str): - self._has_win_bytes = True - - def close(self): - """ - Stop analyzing the current document and come up with a final - prediction. - - :returns: The ``result`` attribute, a ``dict`` with the keys - `encoding`, `confidence`, and `language`. - """ - # Don't bother with checks if we're already done - if self.done: - return self.result - self.done = True - - if not self._got_data: - self.logger.debug('no data received!') - - # Default to ASCII if it is all we've seen so far - elif self._input_state == InputState.PURE_ASCII: - self.result = {'encoding': 'ascii', - 'confidence': 1.0, - 'language': ''} - - # If we have seen non-ASCII, return the best that met MINIMUM_THRESHOLD - elif self._input_state == InputState.HIGH_BYTE: - prober_confidence = None - max_prober_confidence = 0.0 - max_prober = None - for prober in self._charset_probers: - if not prober: - continue - prober_confidence = prober.get_confidence() - if prober_confidence > max_prober_confidence: - max_prober_confidence = prober_confidence - max_prober = prober - if max_prober and (max_prober_confidence > self.MINIMUM_THRESHOLD): - charset_name = max_prober.charset_name - lower_charset_name = max_prober.charset_name.lower() - confidence = max_prober.get_confidence() - # Use Windows encoding name instead of ISO-8859 if we saw any - # extra Windows-specific bytes - if lower_charset_name.startswith('iso-8859'): - if self._has_win_bytes: - charset_name = self.ISO_WIN_MAP.get(lower_charset_name, - charset_name) - self.result = {'encoding': charset_name, - 'confidence': confidence, - 'language': max_prober.language} - - # Log all prober confidences if none met MINIMUM_THRESHOLD - if self.logger.getEffectiveLevel() == logging.DEBUG: - if self.result['encoding'] is None: - self.logger.debug('no probers hit minimum threshold') - for group_prober in self._charset_probers: - if not group_prober: - continue - if isinstance(group_prober, CharSetGroupProber): - for prober in group_prober.probers: - self.logger.debug('%s %s confidence = %s', - prober.charset_name, - prober.language, - prober.get_confidence()) - else: - self.logger.debug('%s %s confidence = %s', - prober.charset_name, - prober.language, - prober.get_confidence()) - return self.result diff --git a/pipenv/vendor/chardet/utf8prober.py b/pipenv/vendor/chardet/utf8prober.py deleted file mode 100644 index 6c3196cc..00000000 --- a/pipenv/vendor/chardet/utf8prober.py +++ /dev/null @@ -1,82 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is mozilla.org code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .charsetprober import CharSetProber -from .enums import ProbingState, MachineState -from .codingstatemachine import CodingStateMachine -from .mbcssm import UTF8_SM_MODEL - - - -class UTF8Prober(CharSetProber): - ONE_CHAR_PROB = 0.5 - - def __init__(self): - super(UTF8Prober, self).__init__() - self.coding_sm = CodingStateMachine(UTF8_SM_MODEL) - self._num_mb_chars = None - self.reset() - - def reset(self): - super(UTF8Prober, self).reset() - self.coding_sm.reset() - self._num_mb_chars = 0 - - @property - def charset_name(self): - return "utf-8" - - @property - def language(self): - return "" - - def feed(self, byte_str): - for c in byte_str: - coding_state = self.coding_sm.next_state(c) - if coding_state == MachineState.ERROR: - self._state = ProbingState.NOT_ME - break - elif coding_state == MachineState.ITS_ME: - self._state = ProbingState.FOUND_IT - break - elif coding_state == MachineState.START: - if self.coding_sm.get_current_charlen() >= 2: - self._num_mb_chars += 1 - - if self.state == ProbingState.DETECTING: - if self.get_confidence() > self.SHORTCUT_THRESHOLD: - self._state = ProbingState.FOUND_IT - - return self.state - - def get_confidence(self): - unlike = 0.99 - if self._num_mb_chars < 6: - unlike *= self.ONE_CHAR_PROB ** self._num_mb_chars - return 1.0 - unlike - else: - return unlike diff --git a/pipenv/vendor/chardet/version.py b/pipenv/vendor/chardet/version.py deleted file mode 100644 index bb2a34a7..00000000 --- a/pipenv/vendor/chardet/version.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -This module exists only to simplify retrieving the version number of chardet -from within setup.py and from chardet subpackages. - -:author: Dan Blanchard (dan.blanchard@gmail.com) -""" - -__version__ = "3.0.4" -VERSION = __version__.split('.') diff --git a/pipenv/vendor/attr/LICENSE b/pipenv/vendor/charset_normalizer/LICENSE similarity index 93% rename from pipenv/vendor/attr/LICENSE rename to pipenv/vendor/charset_normalizer/LICENSE index 7ae3df93..ad82355b 100644 --- a/pipenv/vendor/attr/LICENSE +++ b/pipenv/vendor/charset_normalizer/LICENSE @@ -1,6 +1,6 @@ -The MIT License (MIT) +MIT License -Copyright (c) 2015 Hynek Schlawack +Copyright (c) 2019 TAHRI Ahmed R. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +SOFTWARE. \ No newline at end of file diff --git a/pipenv/vendor/charset_normalizer/__init__.py b/pipenv/vendor/charset_normalizer/__init__.py new file mode 100644 index 00000000..ed525034 --- /dev/null +++ b/pipenv/vendor/charset_normalizer/__init__.py @@ -0,0 +1,47 @@ +# -*- coding: utf_8 -*- +""" +Charset-Normalizer +~~~~~~~~~~~~~~ +The Real First Universal Charset Detector. +A library that helps you read text from an unknown charset encoding. +Motivated by chardet, This package is trying to resolve the issue by taking a new approach. +All IANA character set names for which the Python core library provides codecs are supported. + +Basic usage: + >>> from charset_normalizer import from_bytes + >>> results = from_bytes('Bсеки човек има право на образование. Oбразованието!'.encode('utf_8')) + >>> best_guess = results.best() + >>> str(best_guess) + 'Bсеки човек има право на образование. Oбразованието!' + +Others methods and usages are available - see the full documentation +at <https://github.com/Ousret/charset_normalizer>. +:copyright: (c) 2021 by Ahmed TAHRI +:license: MIT, see LICENSE for more details. +""" +from .api import from_bytes, from_fp, from_path, normalize +from .legacy import ( + CharsetDetector, + CharsetDoctor, + CharsetNormalizerMatch, + CharsetNormalizerMatches, + detect, +) +from .models import CharsetMatch, CharsetMatches +from .version import VERSION, __version__ + +__all__ = ( + "from_fp", + "from_path", + "from_bytes", + "normalize", + "detect", + "CharsetMatch", + "CharsetMatches", + "CharsetNormalizerMatch", + "CharsetNormalizerMatches", + "CharsetDetector", + "CharsetDoctor", + "__version__", + "VERSION", +) diff --git a/pipenv/vendor/charset_normalizer/api.py b/pipenv/vendor/charset_normalizer/api.py new file mode 100644 index 00000000..dce7cf30 --- /dev/null +++ b/pipenv/vendor/charset_normalizer/api.py @@ -0,0 +1,528 @@ +from os.path import basename, splitext +from typing import BinaryIO, List, Optional, Set + +try: + from os import PathLike +except ImportError: # pragma: no cover + PathLike = str # type: ignore + +import logging + +from .cd import ( + coherence_ratio, + encoding_languages, + mb_encoding_languages, + merge_coherence_ratios, +) +from .constant import IANA_SUPPORTED, TOO_BIG_SEQUENCE, TOO_SMALL_SEQUENCE +from .md import mess_ratio +from .models import CharsetMatch, CharsetMatches +from .utils import ( + any_specified_encoding, + iana_name, + identify_sig_or_bom, + is_cp_similar, + is_multi_byte_encoding, + should_strip_sig_or_bom, +) + +logger = logging.getLogger("charset_normalizer") +logger.setLevel(logging.DEBUG) + +handler = logging.StreamHandler() +handler.setFormatter(logging.Formatter("%(asctime)s | %(levelname)s | %(message)s")) +logger.addHandler(handler) + + +def from_bytes( + sequences: bytes, + steps: int = 5, + chunk_size: int = 512, + threshold: float = 0.2, + cp_isolation: List[str] = None, + cp_exclusion: List[str] = None, + preemptive_behaviour: bool = True, + explain: bool = False, +) -> CharsetMatches: + """ + Given a raw bytes sequence, return the best possibles charset usable to render str objects. + If there is no results, it is a strong indicator that the source is binary/not text. + By default, the process will extract 5 blocs of 512o each to assess the mess and coherence of a given sequence. + And will give up a particular code page after 20% of measured mess. Those criteria are customizable at will. + + The preemptive behavior DOES NOT replace the traditional detection workflow, it prioritize a particular code page + but never take it for granted. Can improve the performance. + + You may want to focus your attention to some code page or/and not others, use cp_isolation and cp_exclusion for that + purpose. + + This function will strip the SIG in the payload/sequence every time except on UTF-16, UTF-32. + """ + + if not isinstance(sequences, (bytearray, bytes)): + raise TypeError( + "Expected object of type bytes or bytearray, got: {0}".format( + type(sequences) + ) + ) + + if not explain: + logger.setLevel(logging.CRITICAL) + else: + logger.setLevel(logging.INFO) + + length = len(sequences) # type: int + + if length == 0: + logger.warning( + "Given content is empty, stopping the process very early, returning empty utf_8 str match" + ) + return CharsetMatches([CharsetMatch(sequences, "utf_8", 0.0, False, [], "")]) + + if cp_isolation is not None: + logger.warning( + "cp_isolation is set. use this flag for debugging purpose. " + "limited list of encoding allowed : %s.", + ", ".join(cp_isolation), + ) + cp_isolation = [iana_name(cp, False) for cp in cp_isolation] + else: + cp_isolation = [] + + if cp_exclusion is not None: + logger.warning( + "cp_exclusion is set. use this flag for debugging purpose. " + "limited list of encoding excluded : %s.", + ", ".join(cp_exclusion), + ) + cp_exclusion = [iana_name(cp, False) for cp in cp_exclusion] + else: + cp_exclusion = [] + + if length <= (chunk_size * steps): + logger.warning( + "override steps (%i) and chunk_size (%i) as content does not fit (%i byte(s) given) parameters.", + steps, + chunk_size, + length, + ) + steps = 1 + chunk_size = length + + if steps > 1 and length / steps < chunk_size: + chunk_size = int(length / steps) + + is_too_small_sequence = len(sequences) < TOO_SMALL_SEQUENCE # type: bool + is_too_large_sequence = len(sequences) >= TOO_BIG_SEQUENCE # type: bool + + if is_too_small_sequence: + logger.warning( + "Trying to detect encoding from a tiny portion of ({}) byte(s).".format( + length + ) + ) + elif is_too_large_sequence: + logger.info( + "Using lazy str decoding because the payload is quite large, ({}) byte(s).".format( + length + ) + ) + + prioritized_encodings = [] # type: List[str] + + specified_encoding = ( + any_specified_encoding(sequences) if preemptive_behaviour is True else None + ) # type: Optional[str] + + if specified_encoding is not None: + prioritized_encodings.append(specified_encoding) + logger.info( + "Detected declarative mark in sequence. Priority +1 given for %s.", + specified_encoding, + ) + + tested = set() # type: Set[str] + tested_but_hard_failure = [] # type: List[str] + tested_but_soft_failure = [] # type: List[str] + + fallback_ascii = None # type: Optional[CharsetMatch] + fallback_u8 = None # type: Optional[CharsetMatch] + fallback_specified = None # type: Optional[CharsetMatch] + + results = CharsetMatches() # type: CharsetMatches + + sig_encoding, sig_payload = identify_sig_or_bom(sequences) + + if sig_encoding is not None: + prioritized_encodings.append(sig_encoding) + logger.info( + "Detected a SIG or BOM mark on first %i byte(s). Priority +1 given for %s.", + len(sig_payload), + sig_encoding, + ) + + prioritized_encodings.append("ascii") + + if "utf_8" not in prioritized_encodings: + prioritized_encodings.append("utf_8") + + for encoding_iana in prioritized_encodings + IANA_SUPPORTED: + + if cp_isolation and encoding_iana not in cp_isolation: + continue + + if cp_exclusion and encoding_iana in cp_exclusion: + continue + + if encoding_iana in tested: + continue + + tested.add(encoding_iana) + + decoded_payload = None # type: Optional[str] + bom_or_sig_available = sig_encoding == encoding_iana # type: bool + strip_sig_or_bom = bom_or_sig_available and should_strip_sig_or_bom( + encoding_iana + ) # type: bool + + if encoding_iana in {"utf_16", "utf_32"} and bom_or_sig_available is False: + logger.info( + "Encoding %s wont be tested as-is because it require a BOM. Will try some sub-encoder LE/BE.", + encoding_iana, + ) + continue + + try: + is_multi_byte_decoder = is_multi_byte_encoding(encoding_iana) # type: bool + except (ModuleNotFoundError, ImportError): + logger.debug( + "Encoding %s does not provide an IncrementalDecoder", encoding_iana + ) + continue + + try: + if is_too_large_sequence and is_multi_byte_decoder is False: + str( + sequences[: int(50e4)] + if strip_sig_or_bom is False + else sequences[len(sig_payload) : int(50e4)], + encoding=encoding_iana, + ) + else: + decoded_payload = str( + sequences + if strip_sig_or_bom is False + else sequences[len(sig_payload) :], + encoding=encoding_iana, + ) + except (UnicodeDecodeError, LookupError) as e: + if not isinstance(e, LookupError): + logger.warning( + "Code page %s does not fit given bytes sequence at ALL. %s", + encoding_iana, + str(e), + ) + tested_but_hard_failure.append(encoding_iana) + continue + + similar_soft_failure_test = False # type: bool + + for encoding_soft_failed in tested_but_soft_failure: + if is_cp_similar(encoding_iana, encoding_soft_failed): + similar_soft_failure_test = True + break + + if similar_soft_failure_test: + logger.warning( + "%s is deemed too similar to code page %s and was consider unsuited already. Continuing!", + encoding_iana, + encoding_soft_failed, + ) + continue + + r_ = range( + 0 if bom_or_sig_available is False else len(sig_payload), + length, + int(length / steps), + ) + + multi_byte_bonus = ( + is_multi_byte_decoder + and decoded_payload is not None + and len(decoded_payload) < length + ) # type: bool + + if multi_byte_bonus: + logger.info( + "Code page %s is a multi byte encoding table and it appear that at least one character " + "was encoded using n-bytes.", + encoding_iana, + ) + + max_chunk_gave_up = int(len(r_) / 4) # type: int + + if max_chunk_gave_up < 2: + max_chunk_gave_up = 2 + + early_stop_count = 0 # type: int + + md_chunks = [] # type: List[str] + md_ratios = [] + + for i in r_: + cut_sequence = sequences[i : i + chunk_size] + + if bom_or_sig_available and strip_sig_or_bom is False: + cut_sequence = sig_payload + cut_sequence + + chunk = cut_sequence.decode(encoding_iana, errors="ignore") # type: str + + # multi-byte bad cutting detector and adjustment + # not the cleanest way to perform that fix but clever enough for now. + if is_multi_byte_decoder and i > 0 and sequences[i] >= 0x80: + + chunk_partial_size_chk = ( + 16 if chunk_size > 16 else chunk_size + ) # type: int + + if ( + decoded_payload + and chunk[:chunk_partial_size_chk] not in decoded_payload + ): + for j in range(i, i - 4, -1): + cut_sequence = sequences[j : i + chunk_size] + + if bom_or_sig_available and strip_sig_or_bom is False: + cut_sequence = sig_payload + cut_sequence + + chunk = cut_sequence.decode(encoding_iana, errors="ignore") + + if chunk[:chunk_partial_size_chk] in decoded_payload: + break + + md_chunks.append(chunk) + + md_ratios.append(mess_ratio(chunk, threshold)) + + if md_ratios[-1] >= threshold: + early_stop_count += 1 + + if (early_stop_count >= max_chunk_gave_up) or ( + bom_or_sig_available and strip_sig_or_bom is False + ): + break + + if md_ratios: + mean_mess_ratio = sum(md_ratios) / len(md_ratios) # type: float + else: + mean_mess_ratio = 0.0 + + if mean_mess_ratio >= threshold or early_stop_count >= max_chunk_gave_up: + tested_but_soft_failure.append(encoding_iana) + logger.warning( + "%s was excluded because of initial chaos probing. Gave up %i time(s). " + "Computed mean chaos is %f %%.", + encoding_iana, + early_stop_count, + round(mean_mess_ratio * 100, ndigits=3), + ) + # Preparing those fallbacks in case we got nothing. + if encoding_iana in ["ascii", "utf_8", specified_encoding]: + fallback_entry = CharsetMatch( + sequences, encoding_iana, threshold, False, [], decoded_payload + ) + if encoding_iana == specified_encoding: + fallback_specified = fallback_entry + elif encoding_iana == "ascii": + fallback_ascii = fallback_entry + else: + fallback_u8 = fallback_entry + continue + + logger.info( + "%s passed initial chaos probing. Mean measured chaos is %f %%", + encoding_iana, + round(mean_mess_ratio * 100, ndigits=3), + ) + + if not is_multi_byte_decoder: + target_languages = encoding_languages(encoding_iana) # type: List[str] + else: + target_languages = mb_encoding_languages(encoding_iana) + + if target_languages: + logger.info( + "{} should target any language(s) of {}".format( + encoding_iana, str(target_languages) + ) + ) + + cd_ratios = [] + + for chunk in md_chunks: + chunk_languages = coherence_ratio( + chunk, 0.1, ",".join(target_languages) if target_languages else None + ) + + cd_ratios.append(chunk_languages) + + cd_ratios_merged = merge_coherence_ratios(cd_ratios) + + if cd_ratios_merged: + logger.info( + "We detected language {} using {}".format( + cd_ratios_merged, encoding_iana + ) + ) + + results.append( + CharsetMatch( + sequences, + encoding_iana, + mean_mess_ratio, + bom_or_sig_available, + cd_ratios_merged, + decoded_payload, + ) + ) + + if ( + encoding_iana in [specified_encoding, "ascii", "utf_8"] + and mean_mess_ratio < 0.1 + ): + logger.info( + "%s is most likely the one. Stopping the process.", encoding_iana + ) + return CharsetMatches([results[encoding_iana]]) + + if encoding_iana == sig_encoding: + logger.info( + "%s is most likely the one as we detected a BOM or SIG within the beginning of the sequence.", + encoding_iana, + ) + return CharsetMatches([results[encoding_iana]]) + + if len(results) == 0: + if fallback_u8 or fallback_ascii or fallback_specified: + logger.warning( + "Nothing got out of the detection process. Using ASCII/UTF-8/Specified fallback." + ) + + if fallback_specified: + logger.warning( + "%s will be used as a fallback match", fallback_specified.encoding + ) + results.append(fallback_specified) + elif ( + (fallback_u8 and fallback_ascii is None) + or ( + fallback_u8 + and fallback_ascii + and fallback_u8.fingerprint != fallback_ascii.fingerprint + ) + or (fallback_u8 is not None) + ): + logger.warning("utf_8 will be used as a fallback match") + results.append(fallback_u8) + elif fallback_ascii: + logger.warning("ascii will be used as a fallback match") + results.append(fallback_ascii) + + return results + + +def from_fp( + fp: BinaryIO, + steps: int = 5, + chunk_size: int = 512, + threshold: float = 0.20, + cp_isolation: List[str] = None, + cp_exclusion: List[str] = None, + preemptive_behaviour: bool = True, + explain: bool = False, +) -> CharsetMatches: + """ + Same thing than the function from_bytes but using a file pointer that is already ready. + Will not close the file pointer. + """ + return from_bytes( + fp.read(), + steps, + chunk_size, + threshold, + cp_isolation, + cp_exclusion, + preemptive_behaviour, + explain, + ) + + +def from_path( + path: PathLike, + steps: int = 5, + chunk_size: int = 512, + threshold: float = 0.20, + cp_isolation: List[str] = None, + cp_exclusion: List[str] = None, + preemptive_behaviour: bool = True, + explain: bool = False, +) -> CharsetMatches: + """ + Same thing than the function from_bytes but with one extra step. Opening and reading given file path in binary mode. + Can raise IOError. + """ + with open(path, "rb") as fp: + return from_fp( + fp, + steps, + chunk_size, + threshold, + cp_isolation, + cp_exclusion, + preemptive_behaviour, + explain, + ) + + +def normalize( + path: PathLike, + steps: int = 5, + chunk_size: int = 512, + threshold: float = 0.20, + cp_isolation: List[str] = None, + cp_exclusion: List[str] = None, + preemptive_behaviour: bool = True, +) -> CharsetMatch: + """ + Take a (text-based) file path and try to create another file next to it, this time using UTF-8. + """ + results = from_path( + path, + steps, + chunk_size, + threshold, + cp_isolation, + cp_exclusion, + preemptive_behaviour, + ) + + filename = basename(path) + target_extensions = list(splitext(filename)) + + if len(results) == 0: + raise IOError( + 'Unable to normalize "{}", no encoding charset seems to fit.'.format( + filename + ) + ) + + result = results.best() + + target_extensions[0] += "-" + result.encoding # type: ignore + + with open( + "{}".format(str(path).replace(filename, "".join(target_extensions))), "wb" + ) as fp: + fp.write(result.output()) # type: ignore + + return result # type: ignore diff --git a/pipenv/vendor/charset_normalizer/assets/__init__.py b/pipenv/vendor/charset_normalizer/assets/__init__.py new file mode 100644 index 00000000..b2e56ff3 --- /dev/null +++ b/pipenv/vendor/charset_normalizer/assets/__init__.py @@ -0,0 +1,1244 @@ +# -*- coding: utf_8 -*- +from collections import OrderedDict + +FREQUENCIES = OrderedDict( + [ + ( + "English", + [ + "e", + "a", + "t", + "i", + "o", + "n", + "s", + "r", + "h", + "l", + "d", + "c", + "u", + "m", + "f", + "p", + "g", + "w", + "y", + "b", + "v", + "k", + "x", + "j", + "z", + "q", + ], + ), + ( + "German", + [ + "e", + "n", + "i", + "r", + "s", + "t", + "a", + "d", + "h", + "u", + "l", + "g", + "o", + "c", + "m", + "b", + "f", + "k", + "w", + "z", + "p", + "v", + "ü", + "ä", + "ö", + "j", + ], + ), + ( + "French", + [ + "e", + "a", + "s", + "n", + "i", + "t", + "r", + "l", + "u", + "o", + "d", + "c", + "p", + "m", + "é", + "v", + "g", + "f", + "b", + "h", + "q", + "à", + "x", + "è", + "y", + "j", + ], + ), + ( + "Dutch", + [ + "e", + "n", + "a", + "i", + "r", + "t", + "o", + "d", + "s", + "l", + "g", + "h", + "v", + "m", + "u", + "k", + "c", + "p", + "b", + "w", + "j", + "z", + "f", + "y", + "x", + "ë", + ], + ), + ( + "Italian", + [ + "e", + "i", + "a", + "o", + "n", + "l", + "t", + "r", + "s", + "c", + "d", + "u", + "p", + "m", + "g", + "v", + "f", + "b", + "z", + "h", + "q", + "è", + "à", + "k", + "y", + "ò", + ], + ), + ( + "Polish", + [ + "a", + "i", + "o", + "e", + "n", + "r", + "z", + "w", + "s", + "c", + "t", + "k", + "y", + "d", + "p", + "m", + "u", + "l", + "j", + "ł", + "g", + "b", + "h", + "ą", + "ę", + "ó", + ], + ), + ( + "Spanish", + [ + "e", + "a", + "o", + "n", + "s", + "r", + "i", + "l", + "d", + "t", + "c", + "u", + "m", + "p", + "b", + "g", + "v", + "f", + "y", + "ó", + "h", + "q", + "í", + "j", + "z", + "á", + ], + ), + ( + "Russian", + [ + "о", + "а", + "е", + "и", + "н", + "с", + "т", + "р", + "в", + "л", + "к", + "м", + "д", + "п", + "у", + "г", + "я", + "ы", + "з", + "б", + "й", + "ь", + "ч", + "х", + "ж", + "ц", + ], + ), + ( + "Japanese", + [ + "の", + "に", + "る", + "た", + "は", + "ー", + "と", + "し", + "を", + "で", + "て", + "が", + "い", + "ン", + "れ", + "な", + "年", + "ス", + "っ", + "ル", + "か", + "ら", + "あ", + "さ", + "も", + "り", + ], + ), + ( + "Portuguese", + [ + "a", + "e", + "o", + "s", + "i", + "r", + "d", + "n", + "t", + "m", + "u", + "c", + "l", + "p", + "g", + "v", + "b", + "f", + "h", + "ã", + "q", + "é", + "ç", + "á", + "z", + "í", + ], + ), + ( + "Swedish", + [ + "e", + "a", + "n", + "r", + "t", + "s", + "i", + "l", + "d", + "o", + "m", + "k", + "g", + "v", + "h", + "f", + "u", + "p", + "ä", + "c", + "b", + "ö", + "å", + "y", + "j", + "x", + ], + ), + ( + "Chinese", + [ + "的", + "一", + "是", + "不", + "了", + "在", + "人", + "有", + "我", + "他", + "这", + "个", + "们", + "中", + "来", + "上", + "大", + "为", + "和", + "国", + "地", + "到", + "以", + "说", + "时", + "要", + "就", + "出", + "会", + ], + ), + ( + "Ukrainian", + [ + "о", + "а", + "н", + "і", + "и", + "р", + "в", + "т", + "е", + "с", + "к", + "л", + "у", + "д", + "м", + "п", + "з", + "я", + "ь", + "б", + "г", + "й", + "ч", + "х", + "ц", + "ї", + ], + ), + ( + "Norwegian", + [ + "e", + "r", + "n", + "t", + "a", + "s", + "i", + "o", + "l", + "d", + "g", + "k", + "m", + "v", + "f", + "p", + "u", + "b", + "h", + "å", + "y", + "j", + "ø", + "c", + "æ", + "w", + ], + ), + ( + "Finnish", + [ + "a", + "i", + "n", + "t", + "e", + "s", + "l", + "o", + "u", + "k", + "ä", + "m", + "r", + "v", + "j", + "h", + "p", + "y", + "d", + "ö", + "g", + "c", + "b", + "f", + "w", + "z", + ], + ), + ( + "Vietnamese", + [ + "n", + "h", + "t", + "i", + "c", + "g", + "a", + "o", + "u", + "m", + "l", + "r", + "à", + "đ", + "s", + "e", + "v", + "p", + "b", + "y", + "ư", + "d", + "á", + "k", + "ộ", + "ế", + ], + ), + ( + "Czech", + [ + "o", + "e", + "a", + "n", + "t", + "s", + "i", + "l", + "v", + "r", + "k", + "d", + "u", + "m", + "p", + "í", + "c", + "h", + "z", + "á", + "y", + "j", + "b", + "ě", + "é", + "ř", + ], + ), + ( + "Hungarian", + [ + "e", + "a", + "t", + "l", + "s", + "n", + "k", + "r", + "i", + "o", + "z", + "á", + "é", + "g", + "m", + "b", + "y", + "v", + "d", + "h", + "u", + "p", + "j", + "ö", + "f", + "c", + ], + ), + ( + "Korean", + [ + "이", + "다", + "에", + "의", + "는", + "로", + "하", + "을", + "가", + "고", + "지", + "서", + "한", + "은", + "기", + "으", + "년", + "대", + "사", + "시", + "를", + "리", + "도", + "인", + "스", + "일", + ], + ), + ( + "Indonesian", + [ + "a", + "n", + "e", + "i", + "r", + "t", + "u", + "s", + "d", + "k", + "m", + "l", + "g", + "p", + "b", + "o", + "h", + "y", + "j", + "c", + "w", + "f", + "v", + "z", + "x", + "q", + ], + ), + ( + "Turkish", + [ + "a", + "e", + "i", + "n", + "r", + "l", + "ı", + "k", + "d", + "t", + "s", + "m", + "y", + "u", + "o", + "b", + "ü", + "ş", + "v", + "g", + "z", + "h", + "c", + "p", + "ç", + "ğ", + ], + ), + ( + "Romanian", + [ + "e", + "i", + "a", + "r", + "n", + "t", + "u", + "l", + "o", + "c", + "s", + "d", + "p", + "m", + "ă", + "f", + "v", + "î", + "g", + "b", + "ș", + "ț", + "z", + "h", + "â", + "j", + ], + ), + ( + "Farsi", + [ + "ا", + "ی", + "ر", + "د", + "ن", + "ه", + "و", + "م", + "ت", + "ب", + "س", + "ل", + "ک", + "ش", + "ز", + "ف", + "گ", + "ع", + "خ", + "ق", + "ج", + "آ", + "پ", + "ح", + "ط", + "ص", + ], + ), + ( + "Arabic", + [ + "ا", + "ل", + "ي", + "م", + "و", + "ن", + "ر", + "ت", + "ب", + "ة", + "ع", + "د", + "س", + "ف", + "ه", + "ك", + "ق", + "أ", + "ح", + "ج", + "ش", + "ط", + "ص", + "ى", + "خ", + "إ", + ], + ), + ( + "Danish", + [ + "e", + "r", + "n", + "t", + "a", + "i", + "s", + "d", + "l", + "o", + "g", + "m", + "k", + "f", + "v", + "u", + "b", + "h", + "p", + "å", + "y", + "ø", + "æ", + "c", + "j", + "w", + ], + ), + ( + "Serbian", + [ + "а", + "и", + "о", + "е", + "н", + "р", + "с", + "у", + "т", + "к", + "ј", + "в", + "д", + "м", + "п", + "л", + "г", + "з", + "б", + "a", + "i", + "e", + "o", + "n", + "ц", + "ш", + ], + ), + ( + "Lithuanian", + [ + "i", + "a", + "s", + "o", + "r", + "e", + "t", + "n", + "u", + "k", + "m", + "l", + "p", + "v", + "d", + "j", + "g", + "ė", + "b", + "y", + "ų", + "š", + "ž", + "c", + "ą", + "į", + ], + ), + ( + "Slovene", + [ + "e", + "a", + "i", + "o", + "n", + "r", + "s", + "l", + "t", + "j", + "v", + "k", + "d", + "p", + "m", + "u", + "z", + "b", + "g", + "h", + "č", + "c", + "š", + "ž", + "f", + "y", + ], + ), + ( + "Slovak", + [ + "o", + "a", + "e", + "n", + "i", + "r", + "v", + "t", + "s", + "l", + "k", + "d", + "m", + "p", + "u", + "c", + "h", + "j", + "b", + "z", + "á", + "y", + "ý", + "í", + "č", + "é", + ], + ), + ( + "Hebrew", + [ + "י", + "ו", + "ה", + "ל", + "ר", + "ב", + "ת", + "מ", + "א", + "ש", + "נ", + "ע", + "ם", + "ד", + "ק", + "ח", + "פ", + "ס", + "כ", + "ג", + "ט", + "צ", + "ן", + "ז", + "ך", + ], + ), + ( + "Bulgarian", + [ + "а", + "и", + "о", + "е", + "н", + "т", + "р", + "с", + "в", + "л", + "к", + "д", + "п", + "м", + "з", + "г", + "я", + "ъ", + "у", + "б", + "ч", + "ц", + "й", + "ж", + "щ", + "х", + ], + ), + ( + "Croatian", + [ + "a", + "i", + "o", + "e", + "n", + "r", + "j", + "s", + "t", + "u", + "k", + "l", + "v", + "d", + "m", + "p", + "g", + "z", + "b", + "c", + "č", + "h", + "š", + "ž", + "ć", + "f", + ], + ), + ( + "Hindi", + [ + "क", + "र", + "स", + "न", + "त", + "म", + "ह", + "प", + "य", + "ल", + "व", + "ज", + "द", + "ग", + "ब", + "श", + "ट", + "अ", + "ए", + "थ", + "भ", + "ड", + "च", + "ध", + "ष", + "इ", + ], + ), + ( + "Estonian", + [ + "a", + "i", + "e", + "s", + "t", + "l", + "u", + "n", + "o", + "k", + "r", + "d", + "m", + "v", + "g", + "p", + "j", + "h", + "ä", + "b", + "õ", + "ü", + "f", + "c", + "ö", + "y", + ], + ), + ( + "Simple English", + [ + "e", + "a", + "t", + "i", + "o", + "n", + "s", + "r", + "h", + "l", + "d", + "c", + "m", + "u", + "f", + "p", + "g", + "w", + "b", + "y", + "v", + "k", + "j", + "x", + "z", + "q", + ], + ), + ( + "Thai", + [ + "า", + "น", + "ร", + "อ", + "ก", + "เ", + "ง", + "ม", + "ย", + "ล", + "ว", + "ด", + "ท", + "ส", + "ต", + "ะ", + "ป", + "บ", + "ค", + "ห", + "แ", + "จ", + "พ", + "ช", + "ข", + "ใ", + ], + ), + ( + "Greek", + [ + "α", + "τ", + "ο", + "ι", + "ε", + "ν", + "ρ", + "σ", + "κ", + "η", + "π", + "ς", + "υ", + "μ", + "λ", + "ί", + "ό", + "ά", + "γ", + "έ", + "δ", + "ή", + "ω", + "χ", + "θ", + "ύ", + ], + ), + ( + "Tamil", + [ + "க", + "த", + "ப", + "ட", + "ர", + "ம", + "ல", + "ன", + "வ", + "ற", + "ய", + "ள", + "ச", + "ந", + "இ", + "ண", + "அ", + "ஆ", + "ழ", + "ங", + "எ", + "உ", + "ஒ", + "ஸ", + ], + ), + ( + "Classical Chinese", + [ + "之", + "年", + "為", + "也", + "以", + "一", + "人", + "其", + "者", + "國", + "有", + "二", + "十", + "於", + "曰", + "三", + "不", + "大", + "而", + "子", + "中", + "五", + "四", + ], + ), + ( + "Kazakh", + [ + "а", + "ы", + "е", + "н", + "т", + "р", + "л", + "і", + "д", + "с", + "м", + "қ", + "к", + "о", + "б", + "и", + "у", + "ғ", + "ж", + "ң", + "з", + "ш", + "й", + "п", + "г", + "ө", + ], + ), + ] +) diff --git a/pipenv/vendor/charset_normalizer/cd.py b/pipenv/vendor/charset_normalizer/cd.py new file mode 100644 index 00000000..a4512fbb --- /dev/null +++ b/pipenv/vendor/charset_normalizer/cd.py @@ -0,0 +1,341 @@ +import importlib +from codecs import IncrementalDecoder +from collections import Counter, OrderedDict +from functools import lru_cache +from typing import Dict, List, Optional, Tuple + +from .assets import FREQUENCIES +from .constant import KO_NAMES, TOO_SMALL_SEQUENCE, ZH_NAMES +from .md import is_suspiciously_successive_range +from .models import CoherenceMatches +from .utils import ( + is_accentuated, + is_latin, + is_multi_byte_encoding, + is_unicode_range_secondary, + unicode_range, +) + + +def encoding_unicode_range(iana_name: str) -> List[str]: + """ + Return associated unicode ranges in a single byte code page. + """ + if is_multi_byte_encoding(iana_name): + raise IOError("Function not supported on multi-byte code page") + + decoder = importlib.import_module("encodings.{}".format(iana_name)).IncrementalDecoder # type: ignore + + p = decoder(errors="ignore") # type: IncrementalDecoder + seen_ranges = {} # type: Dict[str, int] + character_count = 0 # type: int + + for i in range(0x40, 0xFF): + chunk = p.decode(bytes([i])) # type: str + + if chunk: + character_range = unicode_range(chunk) # type: Optional[str] + + if character_range is None: + continue + + if is_unicode_range_secondary(character_range) is False: + if character_range not in seen_ranges: + seen_ranges[character_range] = 0 + seen_ranges[character_range] += 1 + character_count += 1 + + return sorted( + [ + character_range + for character_range in seen_ranges + if seen_ranges[character_range] / character_count >= 0.15 + ] + ) + + +def unicode_range_languages(primary_range: str) -> List[str]: + """ + Return inferred languages used with a unicode range. + """ + languages = [] # type: List[str] + + for language, characters in FREQUENCIES.items(): + for character in characters: + if unicode_range(character) == primary_range: + languages.append(language) + break + + return languages + + +@lru_cache() +def encoding_languages(iana_name: str) -> List[str]: + """ + Single-byte encoding language association. Some code page are heavily linked to particular language(s). + This function does the correspondence. + """ + unicode_ranges = encoding_unicode_range(iana_name) # type: List[str] + primary_range = None # type: Optional[str] + + for specified_range in unicode_ranges: + if "Latin" not in specified_range: + primary_range = specified_range + break + + if primary_range is None: + return ["Latin Based"] + + return unicode_range_languages(primary_range) + + +@lru_cache() +def mb_encoding_languages(iana_name: str) -> List[str]: + """ + Multi-byte encoding language association. Some code page are heavily linked to particular language(s). + This function does the correspondence. + """ + if ( + iana_name.startswith("shift_") + or iana_name.startswith("iso2022_jp") + or iana_name.startswith("euc_j") + or iana_name == "cp932" + ): + return ["Japanese"] + if iana_name.startswith("gb") or iana_name in ZH_NAMES: + return ["Chinese", "Classical Chinese"] + if iana_name.startswith("iso2022_kr") or iana_name in KO_NAMES: + return ["Korean"] + + return [] + + +def alphabet_languages( + characters: List[str], ignore_non_latin: bool = False +) -> List[str]: + """ + Return associated languages associated to given characters. + """ + languages = [] # type: List[Tuple[str, float]] + + source_have_accents = False # type: bool + + for character in characters: + if is_accentuated(character): + source_have_accents = True + break + + for language, language_characters in FREQUENCIES.items(): + + target_have_accents = False # type: bool + target_pure_latin = True # type: bool + + for language_character in language_characters: + if target_have_accents is False and is_accentuated(language_character): + target_have_accents = True + if target_pure_latin is True and is_latin(language_character) is False: + target_pure_latin = False + + if ignore_non_latin and target_pure_latin is False: + continue + + if target_have_accents is False and source_have_accents: + continue + + character_count = len(language_characters) # type: int + + character_match_count = len( + [c for c in language_characters if c in characters] + ) # type: int + + ratio = character_match_count / character_count # type: float + + if ratio >= 0.2: + languages.append((language, ratio)) + + languages = sorted(languages, key=lambda x: x[1], reverse=True) + + return [compatible_language[0] for compatible_language in languages] + + +def characters_popularity_compare( + language: str, ordered_characters: List[str] +) -> float: + """ + Determine if a ordered characters list (by occurrence from most appearance to rarest) match a particular language. + The result is a ratio between 0. (absolutely no correspondence) and 1. (near perfect fit). + Beware that is function is not strict on the match in order to ease the detection. (Meaning close match is 1.) + """ + if language not in FREQUENCIES: + raise ValueError("{} not available".format(language)) + + character_approved_count = 0 # type: int + + for character in ordered_characters: + if character not in FREQUENCIES[language]: + continue + + characters_before_source = FREQUENCIES[language][ + 0 : FREQUENCIES[language].index(character) + ] # type: List[str] + characters_after_source = FREQUENCIES[language][ + FREQUENCIES[language].index(character) : + ] # type: List[str] + + characters_before = ordered_characters[ + 0 : ordered_characters.index(character) + ] # type: List[str] + characters_after = ordered_characters[ + ordered_characters.index(character) : + ] # type: List[str] + + before_match_count = [ + e in characters_before for e in characters_before_source + ].count( + True + ) # type: int + after_match_count = [ + e in characters_after for e in characters_after_source + ].count( + True + ) # type: int + + if len(characters_before_source) == 0 and before_match_count <= 4: + character_approved_count += 1 + continue + + if len(characters_after_source) == 0 and after_match_count <= 4: + character_approved_count += 1 + continue + + if ( + before_match_count / len(characters_before_source) >= 0.4 + or after_match_count / len(characters_after_source) >= 0.4 + ): + character_approved_count += 1 + continue + + return character_approved_count / len(ordered_characters) + + +def alpha_unicode_split(decoded_sequence: str) -> List[str]: + """ + Given a decoded text sequence, return a list of str. Unicode range / alphabet separation. + Ex. a text containing English/Latin with a bit a Hebrew will return two items in the resulting list; + One containing the latin letters and the other hebrew. + """ + layers = OrderedDict() # type: Dict[str, str] + + for character in decoded_sequence: + if character.isalpha() is False: + continue + + character_range = unicode_range(character) # type: Optional[str] + + if character_range is None: + continue + + layer_target_range = None # type: Optional[str] + + for discovered_range in layers: + if ( + is_suspiciously_successive_range(discovered_range, character_range) + is False + ): + layer_target_range = discovered_range + break + + if layer_target_range is None: + layer_target_range = character_range + + if layer_target_range not in layers: + layers[layer_target_range] = character.lower() + continue + + layers[layer_target_range] += character.lower() + + return list(layers.values()) + + +def merge_coherence_ratios(results: List[CoherenceMatches]) -> CoherenceMatches: + """ + This function merge results previously given by the function coherence_ratio. + The return type is the same as coherence_ratio. + """ + per_language_ratios = OrderedDict() # type: Dict[str, List[float]] + merge = [] # type: CoherenceMatches + + for result in results: + for sub_result in result: + language, ratio = sub_result + if language not in per_language_ratios: + per_language_ratios[language] = [ratio] + continue + per_language_ratios[language].append(ratio) + + for language in per_language_ratios: + merge.append( + ( + language, + round( + sum(per_language_ratios[language]) + / len(per_language_ratios[language]), + 4, + ), + ) + ) + + return sorted(merge, key=lambda x: x[1], reverse=True) + + +@lru_cache(maxsize=2048) +def coherence_ratio( + decoded_sequence: str, threshold: float = 0.1, lg_inclusion: Optional[str] = None +) -> CoherenceMatches: + """ + Detect ANY language that can be identified in given sequence. The sequence will be analysed by layers. + A layer = Character extraction by alphabets/ranges. + """ + + results = [] # type: List[Tuple[str, float]] + lg_inclusion_list = [] # type: List[str] + ignore_non_latin = False # type: bool + + sufficient_match_count = 0 # type: int + + if lg_inclusion is not None: + lg_inclusion_list = lg_inclusion.split(",") + + if "Latin Based" in lg_inclusion_list: + ignore_non_latin = True + lg_inclusion_list.remove("Latin Based") + + for layer in alpha_unicode_split(decoded_sequence): + sequence_frequencies = Counter(layer) # type: Counter + most_common = sequence_frequencies.most_common() + + character_count = sum([o for c, o in most_common]) # type: int + + if character_count <= TOO_SMALL_SEQUENCE: + continue + + popular_character_ordered = [c for c, o in most_common] # type: List[str] + + for language in lg_inclusion_list or alphabet_languages( + popular_character_ordered, ignore_non_latin + ): + ratio = characters_popularity_compare( + language, popular_character_ordered + ) # type: float + + if ratio < threshold: + continue + elif ratio >= 0.8: + sufficient_match_count += 1 + + results.append((language, round(ratio, 4))) + + if sufficient_match_count >= 3: + break + + return sorted(results, key=lambda x: x[1], reverse=True) diff --git a/pipenv/vendor/charset_normalizer/cli/__init__.py b/pipenv/vendor/charset_normalizer/cli/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pipenv/vendor/charset_normalizer/cli/normalizer.py b/pipenv/vendor/charset_normalizer/cli/normalizer.py new file mode 100644 index 00000000..f1911259 --- /dev/null +++ b/pipenv/vendor/charset_normalizer/cli/normalizer.py @@ -0,0 +1,291 @@ +import argparse +import sys +from json import dumps +from os.path import abspath +from platform import python_version +from typing import List + +from charset_normalizer import from_fp +from charset_normalizer.models import CliDetectionResult +from charset_normalizer.version import __version__ + + +def query_yes_no(question: str, default: str = "yes") -> bool: + """Ask a yes/no question via input() and return their answer. + + "question" is a string that is presented to the user. + "default" is the presumed answer if the user just hits <Enter>. + It must be "yes" (the default), "no" or None (meaning + an answer is required of the user). + + The "answer" return value is True for "yes" or False for "no". + + Credit goes to (c) https://stackoverflow.com/questions/3041986/apt-command-line-interface-like-yes-no-input + """ + valid = {"yes": True, "y": True, "ye": True, "no": False, "n": False} + if default is None: + prompt = " [y/n] " + elif default == "yes": + prompt = " [Y/n] " + elif default == "no": + prompt = " [y/N] " + else: + raise ValueError("invalid default answer: '%s'" % default) + + while True: + sys.stdout.write(question + prompt) + choice = input().lower() + if default is not None and choice == "": + return valid[default] + elif choice in valid: + return valid[choice] + else: + sys.stdout.write("Please respond with 'yes' or 'no' " "(or 'y' or 'n').\n") + + +def cli_detect(argv: List[str] = None) -> int: + """ + CLI assistant using ARGV and ArgumentParser + :param argv: + :return: 0 if everything is fine, anything else equal trouble + """ + parser = argparse.ArgumentParser( + description="The Real First Universal Charset Detector. " + "Discover originating encoding used on text file. " + "Normalize text to unicode." + ) + + parser.add_argument( + "files", type=argparse.FileType("rb"), nargs="+", help="File(s) to be analysed" + ) + parser.add_argument( + "-v", + "--verbose", + action="store_true", + default=False, + dest="verbose", + help="Display complementary information about file if any. " + "Stdout will contain logs about the detection process.", + ) + parser.add_argument( + "-a", + "--with-alternative", + action="store_true", + default=False, + dest="alternatives", + help="Output complementary possibilities if any. Top-level JSON WILL be a list.", + ) + parser.add_argument( + "-n", + "--normalize", + action="store_true", + default=False, + dest="normalize", + help="Permit to normalize input file. If not set, program does not write anything.", + ) + parser.add_argument( + "-m", + "--minimal", + action="store_true", + default=False, + dest="minimal", + help="Only output the charset detected to STDOUT. Disabling JSON output.", + ) + parser.add_argument( + "-r", + "--replace", + action="store_true", + default=False, + dest="replace", + help="Replace file when trying to normalize it instead of creating a new one.", + ) + parser.add_argument( + "-f", + "--force", + action="store_true", + default=False, + dest="force", + help="Replace file without asking if you are sure, use this flag with caution.", + ) + parser.add_argument( + "-t", + "--threshold", + action="store", + default=0.1, + type=float, + dest="threshold", + help="Define a custom maximum amount of chaos allowed in decoded content. 0. <= chaos <= 1.", + ) + parser.add_argument( + "--version", + action="version", + version="Charset-Normalizer {} - Python {}".format( + __version__, python_version() + ), + help="Show version information and exit.", + ) + + args = parser.parse_args(argv) + + if args.replace is True and args.normalize is False: + print("Use --replace in addition of --normalize only.", file=sys.stderr) + return 1 + + if args.force is True and args.replace is False: + print("Use --force in addition of --replace only.", file=sys.stderr) + return 1 + + if args.threshold < 0.0 or args.threshold > 1.0: + print("--threshold VALUE should be between 0. AND 1.", file=sys.stderr) + return 1 + + x_ = [] + + for my_file in args.files: + + matches = from_fp(my_file, threshold=args.threshold, explain=args.verbose) + + best_guess = matches.best() + + if best_guess is None: + print( + 'Unable to identify originating encoding for "{}". {}'.format( + my_file.name, + "Maybe try increasing maximum amount of chaos." + if args.threshold < 1.0 + else "", + ), + file=sys.stderr, + ) + x_.append( + CliDetectionResult( + abspath(my_file.name), + None, + [], + [], + "Unknown", + [], + False, + 1.0, + 0.0, + None, + True, + ) + ) + else: + x_.append( + CliDetectionResult( + abspath(my_file.name), + best_guess.encoding, + best_guess.encoding_aliases, + [ + cp + for cp in best_guess.could_be_from_charset + if cp != best_guess.encoding + ], + best_guess.language, + best_guess.alphabets, + best_guess.bom, + best_guess.percent_chaos, + best_guess.percent_coherence, + None, + True, + ) + ) + + if len(matches) > 1 and args.alternatives: + for el in matches: + if el != best_guess: + x_.append( + CliDetectionResult( + abspath(my_file.name), + el.encoding, + el.encoding_aliases, + [ + cp + for cp in el.could_be_from_charset + if cp != el.encoding + ], + el.language, + el.alphabets, + el.bom, + el.percent_chaos, + el.percent_coherence, + None, + False, + ) + ) + + if args.normalize is True: + + if best_guess.encoding.startswith("utf") is True: + print( + '"{}" file does not need to be normalized, as it already came from unicode.'.format( + my_file.name + ), + file=sys.stderr, + ) + if my_file.closed is False: + my_file.close() + continue + + o_ = my_file.name.split(".") # type: List[str] + + if args.replace is False: + o_.insert(-1, best_guess.encoding) + if my_file.closed is False: + my_file.close() + else: + if ( + args.force is False + and query_yes_no( + 'Are you sure to normalize "{}" by replacing it ?'.format( + my_file.name + ), + "no", + ) + is False + ): + if my_file.closed is False: + my_file.close() + continue + + try: + x_[0].unicode_path = abspath("./{}".format(".".join(o_))) + + with open(x_[0].unicode_path, "w", encoding="utf-8") as fp: + fp.write(str(best_guess)) + except IOError as e: + print(str(e), file=sys.stderr) + if my_file.closed is False: + my_file.close() + return 2 + + if my_file.closed is False: + my_file.close() + + if args.minimal is False: + print( + dumps( + [el.__dict__ for el in x_] if len(x_) > 1 else x_[0].__dict__, + ensure_ascii=True, + indent=4, + ) + ) + else: + for my_file in args.files: + print( + ", ".join( + [ + el.encoding if el.encoding else "undefined" + for el in x_ + if el.path == abspath(my_file.name) + ] + ) + ) + + return 0 + + +if __name__ == "__main__": + cli_detect() diff --git a/pipenv/vendor/charset_normalizer/constant.py b/pipenv/vendor/charset_normalizer/constant.py new file mode 100644 index 00000000..2e5974d9 --- /dev/null +++ b/pipenv/vendor/charset_normalizer/constant.py @@ -0,0 +1,496 @@ +from codecs import BOM_UTF8, BOM_UTF16_BE, BOM_UTF16_LE, BOM_UTF32_BE, BOM_UTF32_LE +from collections import OrderedDict +from encodings.aliases import aliases +from re import IGNORECASE, compile as re_compile +from typing import Dict, List, Set, Union + +# Contain for each eligible encoding a list of/item bytes SIG/BOM +ENCODING_MARKS = OrderedDict( + [ + ("utf_8", BOM_UTF8), + ( + "utf_7", + [ + b"\x2b\x2f\x76\x38", + b"\x2b\x2f\x76\x39", + b"\x2b\x2f\x76\x2b", + b"\x2b\x2f\x76\x2f", + b"\x2b\x2f\x76\x38\x2d", + ], + ), + ("gb18030", b"\x84\x31\x95\x33"), + ("utf_32", [BOM_UTF32_BE, BOM_UTF32_LE]), + ("utf_16", [BOM_UTF16_BE, BOM_UTF16_LE]), + ] +) # type: Dict[str, Union[bytes, List[bytes]]] + +TOO_SMALL_SEQUENCE = 32 # type: int +TOO_BIG_SEQUENCE = int(10e6) # type: int + +UTF8_MAXIMAL_ALLOCATION = 1112064 # type: int + +UNICODE_RANGES_COMBINED = { + "Control character": range(0, 31 + 1), + "Basic Latin": range(32, 127 + 1), + "Latin-1 Supplement": range(128, 255 + 1), + "Latin Extended-A": range(256, 383 + 1), + "Latin Extended-B": range(384, 591 + 1), + "IPA Extensions": range(592, 687 + 1), + "Spacing Modifier Letters": range(688, 767 + 1), + "Combining Diacritical Marks": range(768, 879 + 1), + "Greek and Coptic": range(880, 1023 + 1), + "Cyrillic": range(1024, 1279 + 1), + "Cyrillic Supplement": range(1280, 1327 + 1), + "Armenian": range(1328, 1423 + 1), + "Hebrew": range(1424, 1535 + 1), + "Arabic": range(1536, 1791 + 1), + "Syriac": range(1792, 1871 + 1), + "Arabic Supplement": range(1872, 1919 + 1), + "Thaana": range(1920, 1983 + 1), + "NKo": range(1984, 2047 + 1), + "Samaritan": range(2048, 2111 + 1), + "Mandaic": range(2112, 2143 + 1), + "Syriac Supplement": range(2144, 2159 + 1), + "Arabic Extended-A": range(2208, 2303 + 1), + "Devanagari": range(2304, 2431 + 1), + "Bengali": range(2432, 2559 + 1), + "Gurmukhi": range(2560, 2687 + 1), + "Gujarati": range(2688, 2815 + 1), + "Oriya": range(2816, 2943 + 1), + "Tamil": range(2944, 3071 + 1), + "Telugu": range(3072, 3199 + 1), + "Kannada": range(3200, 3327 + 1), + "Malayalam": range(3328, 3455 + 1), + "Sinhala": range(3456, 3583 + 1), + "Thai": range(3584, 3711 + 1), + "Lao": range(3712, 3839 + 1), + "Tibetan": range(3840, 4095 + 1), + "Myanmar": range(4096, 4255 + 1), + "Georgian": range(4256, 4351 + 1), + "Hangul Jamo": range(4352, 4607 + 1), + "Ethiopic": range(4608, 4991 + 1), + "Ethiopic Supplement": range(4992, 5023 + 1), + "Cherokee": range(5024, 5119 + 1), + "Unified Canadian Aboriginal Syllabics": range(5120, 5759 + 1), + "Ogham": range(5760, 5791 + 1), + "Runic": range(5792, 5887 + 1), + "Tagalog": range(5888, 5919 + 1), + "Hanunoo": range(5920, 5951 + 1), + "Buhid": range(5952, 5983 + 1), + "Tagbanwa": range(5984, 6015 + 1), + "Khmer": range(6016, 6143 + 1), + "Mongolian": range(6144, 6319 + 1), + "Unified Canadian Aboriginal Syllabics Extended": range(6320, 6399 + 1), + "Limbu": range(6400, 6479 + 1), + "Tai Le": range(6480, 6527 + 1), + "New Tai Lue": range(6528, 6623 + 1), + "Khmer Symbols": range(6624, 6655 + 1), + "Buginese": range(6656, 6687 + 1), + "Tai Tham": range(6688, 6831 + 1), + "Combining Diacritical Marks Extended": range(6832, 6911 + 1), + "Balinese": range(6912, 7039 + 1), + "Sundanese": range(7040, 7103 + 1), + "Batak": range(7104, 7167 + 1), + "Lepcha": range(7168, 7247 + 1), + "Ol Chiki": range(7248, 7295 + 1), + "Cyrillic Extended C": range(7296, 7311 + 1), + "Sundanese Supplement": range(7360, 7375 + 1), + "Vedic Extensions": range(7376, 7423 + 1), + "Phonetic Extensions": range(7424, 7551 + 1), + "Phonetic Extensions Supplement": range(7552, 7615 + 1), + "Combining Diacritical Marks Supplement": range(7616, 7679 + 1), + "Latin Extended Additional": range(7680, 7935 + 1), + "Greek Extended": range(7936, 8191 + 1), + "General Punctuation": range(8192, 8303 + 1), + "Superscripts and Subscripts": range(8304, 8351 + 1), + "Currency Symbols": range(8352, 8399 + 1), + "Combining Diacritical Marks for Symbols": range(8400, 8447 + 1), + "Letterlike Symbols": range(8448, 8527 + 1), + "Number Forms": range(8528, 8591 + 1), + "Arrows": range(8592, 8703 + 1), + "Mathematical Operators": range(8704, 8959 + 1), + "Miscellaneous Technical": range(8960, 9215 + 1), + "Control Pictures": range(9216, 9279 + 1), + "Optical Character Recognition": range(9280, 9311 + 1), + "Enclosed Alphanumerics": range(9312, 9471 + 1), + "Box Drawing": range(9472, 9599 + 1), + "Block Elements": range(9600, 9631 + 1), + "Geometric Shapes": range(9632, 9727 + 1), + "Miscellaneous Symbols": range(9728, 9983 + 1), + "Dingbats": range(9984, 10175 + 1), + "Miscellaneous Mathematical Symbols-A": range(10176, 10223 + 1), + "Supplemental Arrows-A": range(10224, 10239 + 1), + "Braille Patterns": range(10240, 10495 + 1), + "Supplemental Arrows-B": range(10496, 10623 + 1), + "Miscellaneous Mathematical Symbols-B": range(10624, 10751 + 1), + "Supplemental Mathematical Operators": range(10752, 11007 + 1), + "Miscellaneous Symbols and Arrows": range(11008, 11263 + 1), + "Glagolitic": range(11264, 11359 + 1), + "Latin Extended-C": range(11360, 11391 + 1), + "Coptic": range(11392, 11519 + 1), + "Georgian Supplement": range(11520, 11567 + 1), + "Tifinagh": range(11568, 11647 + 1), + "Ethiopic Extended": range(11648, 11743 + 1), + "Cyrillic Extended-A": range(11744, 11775 + 1), + "Supplemental Punctuation": range(11776, 11903 + 1), + "CJK Radicals Supplement": range(11904, 12031 + 1), + "Kangxi Radicals": range(12032, 12255 + 1), + "Ideographic Description Characters": range(12272, 12287 + 1), + "CJK Symbols and Punctuation": range(12288, 12351 + 1), + "Hiragana": range(12352, 12447 + 1), + "Katakana": range(12448, 12543 + 1), + "Bopomofo": range(12544, 12591 + 1), + "Hangul Compatibility Jamo": range(12592, 12687 + 1), + "Kanbun": range(12688, 12703 + 1), + "Bopomofo Extended": range(12704, 12735 + 1), + "CJK Strokes": range(12736, 12783 + 1), + "Katakana Phonetic Extensions": range(12784, 12799 + 1), + "Enclosed CJK Letters and Months": range(12800, 13055 + 1), + "CJK Compatibility": range(13056, 13311 + 1), + "CJK Unified Ideographs Extension A": range(13312, 19903 + 1), + "Yijing Hexagram Symbols": range(19904, 19967 + 1), + "CJK Unified Ideographs": range(19968, 40959 + 1), + "Yi Syllables": range(40960, 42127 + 1), + "Yi Radicals": range(42128, 42191 + 1), + "Lisu": range(42192, 42239 + 1), + "Vai": range(42240, 42559 + 1), + "Cyrillic Extended-B": range(42560, 42655 + 1), + "Bamum": range(42656, 42751 + 1), + "Modifier Tone Letters": range(42752, 42783 + 1), + "Latin Extended-D": range(42784, 43007 + 1), + "Syloti Nagri": range(43008, 43055 + 1), + "Common Indic Number Forms": range(43056, 43071 + 1), + "Phags-pa": range(43072, 43135 + 1), + "Saurashtra": range(43136, 43231 + 1), + "Devanagari Extended": range(43232, 43263 + 1), + "Kayah Li": range(43264, 43311 + 1), + "Rejang": range(43312, 43359 + 1), + "Hangul Jamo Extended-A": range(43360, 43391 + 1), + "Javanese": range(43392, 43487 + 1), + "Myanmar Extended-B": range(43488, 43519 + 1), + "Cham": range(43520, 43615 + 1), + "Myanmar Extended-A": range(43616, 43647 + 1), + "Tai Viet": range(43648, 43743 + 1), + "Meetei Mayek Extensions": range(43744, 43775 + 1), + "Ethiopic Extended-A": range(43776, 43823 + 1), + "Latin Extended-E": range(43824, 43887 + 1), + "Cherokee Supplement": range(43888, 43967 + 1), + "Meetei Mayek": range(43968, 44031 + 1), + "Hangul Syllables": range(44032, 55215 + 1), + "Hangul Jamo Extended-B": range(55216, 55295 + 1), + "High Surrogates": range(55296, 56191 + 1), + "High Private Use Surrogates": range(56192, 56319 + 1), + "Low Surrogates": range(56320, 57343 + 1), + "Private Use Area": range(57344, 63743 + 1), + "CJK Compatibility Ideographs": range(63744, 64255 + 1), + "Alphabetic Presentation Forms": range(64256, 64335 + 1), + "Arabic Presentation Forms-A": range(64336, 65023 + 1), + "Variation Selectors": range(65024, 65039 + 1), + "Vertical Forms": range(65040, 65055 + 1), + "Combining Half Marks": range(65056, 65071 + 1), + "CJK Compatibility Forms": range(65072, 65103 + 1), + "Small Form Variants": range(65104, 65135 + 1), + "Arabic Presentation Forms-B": range(65136, 65279 + 1), + "Halfwidth and Fullwidth Forms": range(65280, 65519 + 1), + "Specials": range(65520, 65535 + 1), + "Linear B Syllabary": range(65536, 65663 + 1), + "Linear B Ideograms": range(65664, 65791 + 1), + "Aegean Numbers": range(65792, 65855 + 1), + "Ancient Greek Numbers": range(65856, 65935 + 1), + "Ancient Symbols": range(65936, 65999 + 1), + "Phaistos Disc": range(66000, 66047 + 1), + "Lycian": range(66176, 66207 + 1), + "Carian": range(66208, 66271 + 1), + "Coptic Epact Numbers": range(66272, 66303 + 1), + "Old Italic": range(66304, 66351 + 1), + "Gothic": range(66352, 66383 + 1), + "Old Permic": range(66384, 66431 + 1), + "Ugaritic": range(66432, 66463 + 1), + "Old Persian": range(66464, 66527 + 1), + "Deseret": range(66560, 66639 + 1), + "Shavian": range(66640, 66687 + 1), + "Osmanya": range(66688, 66735 + 1), + "Osage": range(66736, 66815 + 1), + "Elbasan": range(66816, 66863 + 1), + "Caucasian Albanian": range(66864, 66927 + 1), + "Linear A": range(67072, 67455 + 1), + "Cypriot Syllabary": range(67584, 67647 + 1), + "Imperial Aramaic": range(67648, 67679 + 1), + "Palmyrene": range(67680, 67711 + 1), + "Nabataean": range(67712, 67759 + 1), + "Hatran": range(67808, 67839 + 1), + "Phoenician": range(67840, 67871 + 1), + "Lydian": range(67872, 67903 + 1), + "Meroitic Hieroglyphs": range(67968, 67999 + 1), + "Meroitic Cursive": range(68000, 68095 + 1), + "Kharoshthi": range(68096, 68191 + 1), + "Old South Arabian": range(68192, 68223 + 1), + "Old North Arabian": range(68224, 68255 + 1), + "Manichaean": range(68288, 68351 + 1), + "Avestan": range(68352, 68415 + 1), + "Inscriptional Parthian": range(68416, 68447 + 1), + "Inscriptional Pahlavi": range(68448, 68479 + 1), + "Psalter Pahlavi": range(68480, 68527 + 1), + "Old Turkic": range(68608, 68687 + 1), + "Old Hungarian": range(68736, 68863 + 1), + "Rumi Numeral Symbols": range(69216, 69247 + 1), + "Brahmi": range(69632, 69759 + 1), + "Kaithi": range(69760, 69839 + 1), + "Sora Sompeng": range(69840, 69887 + 1), + "Chakma": range(69888, 69967 + 1), + "Mahajani": range(69968, 70015 + 1), + "Sharada": range(70016, 70111 + 1), + "Sinhala Archaic Numbers": range(70112, 70143 + 1), + "Khojki": range(70144, 70223 + 1), + "Multani": range(70272, 70319 + 1), + "Khudawadi": range(70320, 70399 + 1), + "Grantha": range(70400, 70527 + 1), + "Newa": range(70656, 70783 + 1), + "Tirhuta": range(70784, 70879 + 1), + "Siddham": range(71040, 71167 + 1), + "Modi": range(71168, 71263 + 1), + "Mongolian Supplement": range(71264, 71295 + 1), + "Takri": range(71296, 71375 + 1), + "Ahom": range(71424, 71487 + 1), + "Warang Citi": range(71840, 71935 + 1), + "Zanabazar Square": range(72192, 72271 + 1), + "Soyombo": range(72272, 72367 + 1), + "Pau Cin Hau": range(72384, 72447 + 1), + "Bhaiksuki": range(72704, 72815 + 1), + "Marchen": range(72816, 72895 + 1), + "Masaram Gondi": range(72960, 73055 + 1), + "Cuneiform": range(73728, 74751 + 1), + "Cuneiform Numbers and Punctuation": range(74752, 74879 + 1), + "Early Dynastic Cuneiform": range(74880, 75087 + 1), + "Egyptian Hieroglyphs": range(77824, 78895 + 1), + "Anatolian Hieroglyphs": range(82944, 83583 + 1), + "Bamum Supplement": range(92160, 92735 + 1), + "Mro": range(92736, 92783 + 1), + "Bassa Vah": range(92880, 92927 + 1), + "Pahawh Hmong": range(92928, 93071 + 1), + "Miao": range(93952, 94111 + 1), + "Ideographic Symbols and Punctuation": range(94176, 94207 + 1), + "Tangut": range(94208, 100351 + 1), + "Tangut Components": range(100352, 101119 + 1), + "Kana Supplement": range(110592, 110847 + 1), + "Kana Extended-A": range(110848, 110895 + 1), + "Nushu": range(110960, 111359 + 1), + "Duployan": range(113664, 113823 + 1), + "Shorthand Format Controls": range(113824, 113839 + 1), + "Byzantine Musical Symbols": range(118784, 119039 + 1), + "Musical Symbols": range(119040, 119295 + 1), + "Ancient Greek Musical Notation": range(119296, 119375 + 1), + "Tai Xuan Jing Symbols": range(119552, 119647 + 1), + "Counting Rod Numerals": range(119648, 119679 + 1), + "Mathematical Alphanumeric Symbols": range(119808, 120831 + 1), + "Sutton SignWriting": range(120832, 121519 + 1), + "Glagolitic Supplement": range(122880, 122927 + 1), + "Mende Kikakui": range(124928, 125151 + 1), + "Adlam": range(125184, 125279 + 1), + "Arabic Mathematical Alphabetic Symbols": range(126464, 126719 + 1), + "Mahjong Tiles": range(126976, 127023 + 1), + "Domino Tiles": range(127024, 127135 + 1), + "Playing Cards": range(127136, 127231 + 1), + "Enclosed Alphanumeric Supplement": range(127232, 127487 + 1), + "Enclosed Ideographic Supplement": range(127488, 127743 + 1), + "Miscellaneous Symbols and Pictographs": range(127744, 128511 + 1), + "Emoticons range(Emoji)": range(128512, 128591 + 1), + "Ornamental Dingbats": range(128592, 128639 + 1), + "Transport and Map Symbols": range(128640, 128767 + 1), + "Alchemical Symbols": range(128768, 128895 + 1), + "Geometric Shapes Extended": range(128896, 129023 + 1), + "Supplemental Arrows-C": range(129024, 129279 + 1), + "Supplemental Symbols and Pictographs": range(129280, 129535 + 1), + "CJK Unified Ideographs Extension B": range(131072, 173791 + 1), + "CJK Unified Ideographs Extension C": range(173824, 177983 + 1), + "CJK Unified Ideographs Extension D": range(177984, 178207 + 1), + "CJK Unified Ideographs Extension E": range(178208, 183983 + 1), + "CJK Unified Ideographs Extension F": range(183984, 191471 + 1), + "CJK Compatibility Ideographs Supplement": range(194560, 195103 + 1), + "Tags": range(917504, 917631 + 1), + "Variation Selectors Supplement": range(917760, 917999 + 1), +} # type: Dict[str, range] + +UNICODE_SECONDARY_RANGE_KEYWORD = [ + "Supplement", + "Extended", + "Extensions", + "Modifier", + "Marks", + "Punctuation", + "Symbols", + "Forms", + "Operators", + "Miscellaneous", + "Drawing", + "Block", + "Shapes", + "Supplemental", + "Tags", +] # type: List[str] + +RE_POSSIBLE_ENCODING_INDICATION = re_compile( + r"(?:(?:encoding)|(?:charset)|(?:coding))(?:[\:= ]{1,10})(?:[\"\']?)([a-zA-Z0-9\-_]+)(?:[\"\']?)", + IGNORECASE, +) + +IANA_SUPPORTED = sorted( + filter( + lambda x: x.endswith("_codec") is False + and x not in {"rot_13", "tactis", "mbcs"}, + list(set(aliases.values())), + ) +) # type: List[str] + +IANA_SUPPORTED_COUNT = len(IANA_SUPPORTED) # type: int + +# pre-computed code page that are similar using the function cp_similarity. +IANA_SUPPORTED_SIMILAR = { + "cp037": ["cp1026", "cp1140", "cp273", "cp500"], + "cp1026": ["cp037", "cp1140", "cp273", "cp500"], + "cp1125": ["cp866"], + "cp1140": ["cp037", "cp1026", "cp273", "cp500"], + "cp1250": ["iso8859_2"], + "cp1251": ["kz1048", "ptcp154"], + "cp1252": ["cp1258", "iso8859_15", "iso8859_9", "latin_1"], + "cp1253": ["iso8859_7"], + "cp1254": ["cp1258", "iso8859_15", "iso8859_9", "latin_1"], + "cp1257": ["iso8859_13"], + "cp1258": ["cp1252", "cp1254", "iso8859_9", "latin_1"], + "cp273": ["cp037", "cp1026", "cp1140", "cp500"], + "cp437": ["cp850", "cp858", "cp860", "cp861", "cp862", "cp863", "cp865"], + "cp500": ["cp037", "cp1026", "cp1140", "cp273"], + "cp850": ["cp437", "cp857", "cp858", "cp865"], + "cp857": ["cp850", "cp858", "cp865"], + "cp858": ["cp437", "cp850", "cp857", "cp865"], + "cp860": ["cp437", "cp861", "cp862", "cp863", "cp865"], + "cp861": ["cp437", "cp860", "cp862", "cp863", "cp865"], + "cp862": ["cp437", "cp860", "cp861", "cp863", "cp865"], + "cp863": ["cp437", "cp860", "cp861", "cp862", "cp865"], + "cp865": ["cp437", "cp850", "cp857", "cp858", "cp860", "cp861", "cp862", "cp863"], + "cp866": ["cp1125"], + "iso8859_10": ["iso8859_14", "iso8859_15", "iso8859_4", "iso8859_9", "latin_1"], + "iso8859_11": ["tis_620"], + "iso8859_13": ["cp1257"], + "iso8859_14": [ + "iso8859_10", + "iso8859_15", + "iso8859_16", + "iso8859_3", + "iso8859_9", + "latin_1", + ], + "iso8859_15": [ + "cp1252", + "cp1254", + "iso8859_10", + "iso8859_14", + "iso8859_16", + "iso8859_3", + "iso8859_9", + "latin_1", + ], + "iso8859_16": [ + "iso8859_14", + "iso8859_15", + "iso8859_2", + "iso8859_3", + "iso8859_9", + "latin_1", + ], + "iso8859_2": ["cp1250", "iso8859_16", "iso8859_4"], + "iso8859_3": ["iso8859_14", "iso8859_15", "iso8859_16", "iso8859_9", "latin_1"], + "iso8859_4": ["iso8859_10", "iso8859_2", "iso8859_9", "latin_1"], + "iso8859_7": ["cp1253"], + "iso8859_9": [ + "cp1252", + "cp1254", + "cp1258", + "iso8859_10", + "iso8859_14", + "iso8859_15", + "iso8859_16", + "iso8859_3", + "iso8859_4", + "latin_1", + ], + "kz1048": ["cp1251", "ptcp154"], + "latin_1": [ + "cp1252", + "cp1254", + "cp1258", + "iso8859_10", + "iso8859_14", + "iso8859_15", + "iso8859_16", + "iso8859_3", + "iso8859_4", + "iso8859_9", + ], + "mac_iceland": ["mac_roman", "mac_turkish"], + "mac_roman": ["mac_iceland", "mac_turkish"], + "mac_turkish": ["mac_iceland", "mac_roman"], + "ptcp154": ["cp1251", "kz1048"], + "tis_620": ["iso8859_11"], +} # type: Dict[str, List[str]] + + +CHARDET_CORRESPONDENCE = { + "iso2022_kr": "ISO-2022-KR", + "iso2022_jp": "ISO-2022-JP", + "euc_kr": "EUC-KR", + "tis_620": "TIS-620", + "utf_32": "UTF-32", + "euc_jp": "EUC-JP", + "koi8_r": "KOI8-R", + "iso8859_1": "ISO-8859-1", + "iso8859_2": "ISO-8859-2", + "iso8859_5": "ISO-8859-5", + "iso8859_6": "ISO-8859-6", + "iso8859_7": "ISO-8859-7", + "iso8859_8": "ISO-8859-8", + "utf_16": "UTF-16", + "cp855": "IBM855", + "mac_cyrillic": "MacCyrillic", + "gb2312": "GB2312", + "gb18030": "GB18030", + "cp932": "CP932", + "cp866": "IBM866", + "utf_8": "utf-8", + "utf_8_sig": "UTF-8-SIG", + "shift_jis": "SHIFT_JIS", + "big5": "Big5", + "cp1250": "windows-1250", + "cp1251": "windows-1251", + "cp1252": "Windows-1252", + "cp1253": "windows-1253", + "cp1255": "windows-1255", + "cp1256": "windows-1256", + "cp1254": "Windows-1254", + "cp949": "CP949", +} # type: Dict[str, str] + + +COMMON_SAFE_ASCII_CHARACTERS = { + "<", + ">", + "=", + ":", + "/", + "&", + ";", + "{", + "}", + "[", + "]", + ",", + "|", + '"', + "-", +} # type: Set[str] + + +KO_NAMES = {"johab", "cp949", "euc_kr"} # type: Set[str] +ZH_NAMES = {"big5", "cp950", "big5hkscs", "hz"} # type: Set[str] + +NOT_PRINTABLE_PATTERN = re_compile(r"[0-9\W\n\r\t]+") diff --git a/pipenv/vendor/charset_normalizer/legacy.py b/pipenv/vendor/charset_normalizer/legacy.py new file mode 100644 index 00000000..cdebe2b8 --- /dev/null +++ b/pipenv/vendor/charset_normalizer/legacy.py @@ -0,0 +1,95 @@ +import warnings +from typing import Dict, Optional, Union + +from .api import from_bytes, from_fp, from_path, normalize +from .constant import CHARDET_CORRESPONDENCE +from .models import CharsetMatch, CharsetMatches + + +def detect(byte_str: bytes) -> Dict[str, Optional[Union[str, float]]]: + """ + chardet legacy method + Detect the encoding of the given byte string. It should be mostly backward-compatible. + Encoding name will match Chardet own writing whenever possible. (Not on encoding name unsupported by it) + This function is deprecated and should be used to migrate your project easily, consult the documentation for + further information. Not planned for removal. + + :param byte_str: The byte sequence to examine. + """ + if not isinstance(byte_str, (bytearray, bytes)): + raise TypeError( # pragma: nocover + "Expected object of type bytes or bytearray, got: " + "{0}".format(type(byte_str)) + ) + + if isinstance(byte_str, bytearray): + byte_str = bytes(byte_str) + + r = from_bytes(byte_str).best() + + encoding = r.encoding if r is not None else None + language = r.language if r is not None and r.language != "Unknown" else "" + confidence = 1.0 - r.chaos if r is not None else None + + # Note: CharsetNormalizer does not return 'UTF-8-SIG' as the sig get stripped in the detection/normalization process + # but chardet does return 'utf-8-sig' and it is a valid codec name. + if r is not None and encoding == "utf_8" and r.bom: + encoding += "_sig" + + return { + "encoding": encoding + if encoding not in CHARDET_CORRESPONDENCE + else CHARDET_CORRESPONDENCE[encoding], + "language": language, + "confidence": confidence, + } + + +class CharsetNormalizerMatch(CharsetMatch): + pass + + +class CharsetNormalizerMatches(CharsetMatches): + @staticmethod + def from_fp(*args, **kwargs): # type: ignore + warnings.warn( # pragma: nocover + "staticmethod from_fp, from_bytes, from_path and normalize are deprecated " + "and scheduled to be removed in 3.0", + DeprecationWarning, + ) + return from_fp(*args, **kwargs) # pragma: nocover + + @staticmethod + def from_bytes(*args, **kwargs): # type: ignore + warnings.warn( # pragma: nocover + "staticmethod from_fp, from_bytes, from_path and normalize are deprecated " + "and scheduled to be removed in 3.0", + DeprecationWarning, + ) + return from_bytes(*args, **kwargs) # pragma: nocover + + @staticmethod + def from_path(*args, **kwargs): # type: ignore + warnings.warn( # pragma: nocover + "staticmethod from_fp, from_bytes, from_path and normalize are deprecated " + "and scheduled to be removed in 3.0", + DeprecationWarning, + ) + return from_path(*args, **kwargs) # pragma: nocover + + @staticmethod + def normalize(*args, **kwargs): # type: ignore + warnings.warn( # pragma: nocover + "staticmethod from_fp, from_bytes, from_path and normalize are deprecated " + "and scheduled to be removed in 3.0", + DeprecationWarning, + ) + return normalize(*args, **kwargs) # pragma: nocover + + +class CharsetDetector(CharsetNormalizerMatches): + pass + + +class CharsetDoctor(CharsetNormalizerMatches): + pass diff --git a/pipenv/vendor/charset_normalizer/md.py b/pipenv/vendor/charset_normalizer/md.py new file mode 100644 index 00000000..2146d61d --- /dev/null +++ b/pipenv/vendor/charset_normalizer/md.py @@ -0,0 +1,540 @@ +from functools import lru_cache +from typing import List, Optional + +from .constant import COMMON_SAFE_ASCII_CHARACTERS, UNICODE_SECONDARY_RANGE_KEYWORD +from .utils import ( + is_accentuated, + is_ascii, + is_case_variable, + is_cjk, + is_emoticon, + is_hangul, + is_hiragana, + is_katakana, + is_latin, + is_punctuation, + is_separator, + is_symbol, + is_thai, + remove_accent, + unicode_range, +) + + +class MessDetectorPlugin: + """ + Base abstract class used for mess detection plugins. + All detectors MUST extend and implement given methods. + """ + + def eligible(self, character: str) -> bool: + """ + Determine if given character should be fed in. + """ + raise NotImplementedError # pragma: nocover + + def feed(self, character: str) -> None: + """ + The main routine to be executed upon character. + Insert the logic in witch the text would be considered chaotic. + """ + raise NotImplementedError # pragma: nocover + + def reset(self) -> None: + """ + Permit to reset the plugin to the initial state. + """ + raise NotImplementedError # pragma: nocover + + @property + def ratio(self) -> float: + """ + Compute the chaos ratio based on what your feed() has seen. + Must NOT be lower than 0.; No restriction gt 0. + """ + raise NotImplementedError # pragma: nocover + + +class TooManySymbolOrPunctuationPlugin(MessDetectorPlugin): + def __init__(self) -> None: + self._punctuation_count = 0 # type: int + self._symbol_count = 0 # type: int + self._character_count = 0 # type: int + + self._last_printable_char = None # type: Optional[str] + self._frenzy_symbol_in_word = False # type: bool + + def eligible(self, character: str) -> bool: + return character.isprintable() + + def feed(self, character: str) -> None: + self._character_count += 1 + + if ( + character != self._last_printable_char + and character not in COMMON_SAFE_ASCII_CHARACTERS + ): + if is_punctuation(character): + self._punctuation_count += 1 + elif ( + character.isdigit() is False + and is_symbol(character) + and is_emoticon(character) is False + ): + self._symbol_count += 2 + + self._last_printable_char = character + + def reset(self) -> None: + self._punctuation_count = 0 + self._character_count = 0 + self._symbol_count = 0 + + @property + def ratio(self) -> float: + if self._character_count == 0: + return 0.0 + + ratio_of_punctuation = ( + self._punctuation_count + self._symbol_count + ) / self._character_count # type: float + + return ratio_of_punctuation if ratio_of_punctuation >= 0.3 else 0.0 + + +class TooManyAccentuatedPlugin(MessDetectorPlugin): + def __init__(self) -> None: + self._character_count = 0 # type: int + self._accentuated_count = 0 # type: int + + def eligible(self, character: str) -> bool: + return character.isalpha() + + def feed(self, character: str) -> None: + self._character_count += 1 + + if is_accentuated(character): + self._accentuated_count += 1 + + def reset(self) -> None: + self._character_count = 0 + self._accentuated_count = 0 + + @property + def ratio(self) -> float: + if self._character_count == 0: + return 0.0 + ratio_of_accentuation = ( + self._accentuated_count / self._character_count + ) # type: float + return ratio_of_accentuation if ratio_of_accentuation >= 0.35 else 0.0 + + +class UnprintablePlugin(MessDetectorPlugin): + def __init__(self) -> None: + self._unprintable_count = 0 # type: int + self._character_count = 0 # type: int + + def eligible(self, character: str) -> bool: + return True + + def feed(self, character: str) -> None: + if ( + character.isspace() is False # includes \n \t \r \v + and character.isprintable() is False + and character != "\x1A" # Why? Its the ASCII substitute character. + ): + self._unprintable_count += 1 + self._character_count += 1 + + def reset(self) -> None: + self._unprintable_count = 0 + + @property + def ratio(self) -> float: + if self._character_count == 0: + return 0.0 + + return (self._unprintable_count * 8) / self._character_count + + +class SuspiciousDuplicateAccentPlugin(MessDetectorPlugin): + def __init__(self) -> None: + self._successive_count = 0 # type: int + self._character_count = 0 # type: int + + self._last_latin_character = None # type: Optional[str] + + def eligible(self, character: str) -> bool: + return character.isalpha() and is_latin(character) + + def feed(self, character: str) -> None: + self._character_count += 1 + if self._last_latin_character is not None: + if is_accentuated(character) and is_accentuated(self._last_latin_character): + if character.isupper() and self._last_latin_character.isupper(): + self._successive_count += 1 + # Worse if its the same char duplicated with different accent. + if remove_accent(character) == remove_accent( + self._last_latin_character + ): + self._successive_count += 1 + self._last_latin_character = character + + def reset(self) -> None: + self._successive_count = 0 + self._character_count = 0 + self._last_latin_character = None + + @property + def ratio(self) -> float: + if self._character_count == 0: + return 0.0 + + return (self._successive_count * 2) / self._character_count + + +class SuspiciousRange(MessDetectorPlugin): + def __init__(self) -> None: + self._suspicious_successive_range_count = 0 # type: int + self._character_count = 0 # type: int + self._last_printable_seen = None # type: Optional[str] + + def eligible(self, character: str) -> bool: + return character.isprintable() + + def feed(self, character: str) -> None: + self._character_count += 1 + + if ( + character.isspace() + or is_punctuation(character) + or character in COMMON_SAFE_ASCII_CHARACTERS + ): + self._last_printable_seen = None + return + + if self._last_printable_seen is None: + self._last_printable_seen = character + return + + unicode_range_a = unicode_range( + self._last_printable_seen + ) # type: Optional[str] + unicode_range_b = unicode_range(character) # type: Optional[str] + + if is_suspiciously_successive_range(unicode_range_a, unicode_range_b): + self._suspicious_successive_range_count += 1 + + self._last_printable_seen = character + + def reset(self) -> None: + self._character_count = 0 + self._suspicious_successive_range_count = 0 + self._last_printable_seen = None + + @property + def ratio(self) -> float: + if self._character_count == 0: + return 0.0 + + ratio_of_suspicious_range_usage = ( + self._suspicious_successive_range_count * 2 + ) / self._character_count # type: float + + if ratio_of_suspicious_range_usage < 0.1: + return 0.0 + + return ratio_of_suspicious_range_usage + + +class SuperWeirdWordPlugin(MessDetectorPlugin): + def __init__(self) -> None: + self._word_count = 0 # type: int + self._bad_word_count = 0 # type: int + self._is_current_word_bad = False # type: bool + self._foreign_long_watch = False # type: bool + + self._character_count = 0 # type: int + self._bad_character_count = 0 # type: int + + self._buffer = "" # type: str + self._buffer_accent_count = 0 # type: int + + def eligible(self, character: str) -> bool: + return True + + def feed(self, character: str) -> None: + if character.isalpha(): + self._buffer = "".join([self._buffer, character]) + if is_accentuated(character): + self._buffer_accent_count += 1 + if ( + self._foreign_long_watch is False + and is_latin(character) is False + and is_cjk(character) is False + and is_hangul(character) is False + and is_katakana(character) is False + and is_hiragana(character) is False + and is_thai(character) is False + ): + self._foreign_long_watch = True + return + if not self._buffer: + return + if ( + character.isspace() or is_punctuation(character) or is_separator(character) + ) and self._buffer: + self._word_count += 1 + buffer_length = len(self._buffer) # type: int + + self._character_count += buffer_length + + if buffer_length >= 4 and self._buffer_accent_count / buffer_length > 0.34: + self._is_current_word_bad = True + if buffer_length >= 24 and self._foreign_long_watch: + self._is_current_word_bad = True + + if self._is_current_word_bad: + self._bad_word_count += 1 + self._bad_character_count += len(self._buffer) + self._is_current_word_bad = False + + self._foreign_long_watch = False + self._buffer = "" + self._buffer_accent_count = 0 + elif ( + character not in {"<", ">", "-", "="} + and character.isdigit() is False + and is_symbol(character) + ): + self._is_current_word_bad = True + self._buffer += character + + def reset(self) -> None: + self._buffer = "" + self._is_current_word_bad = False + self._foreign_long_watch = False + self._bad_word_count = 0 + self._word_count = 0 + self._character_count = 0 + self._bad_character_count = 0 + + @property + def ratio(self) -> float: + if self._word_count <= 10: + return 0.0 + + return self._bad_character_count / self._character_count + + +class CjkInvalidStopPlugin(MessDetectorPlugin): + """ + GB(Chinese) based encoding often render the stop incorrectly when the content does not fit and + can be easily detected. Searching for the overuse of '丅' and '丄'. + """ + + def __init__(self) -> None: + self._wrong_stop_count = 0 # type: int + self._cjk_character_count = 0 # type: int + + def eligible(self, character: str) -> bool: + return True + + def feed(self, character: str) -> None: + if character in ["丅", "丄"]: + self._wrong_stop_count += 1 + return + if is_cjk(character): + self._cjk_character_count += 1 + + def reset(self) -> None: + self._wrong_stop_count = 0 + self._cjk_character_count = 0 + + @property + def ratio(self) -> float: + if self._cjk_character_count < 16: + return 0.0 + return self._wrong_stop_count / self._cjk_character_count + + +class ArchaicUpperLowerPlugin(MessDetectorPlugin): + def __init__(self) -> None: + self._buf = False # type: bool + + self._character_count_since_last_sep = 0 # type: int + + self._successive_upper_lower_count = 0 # type: int + self._successive_upper_lower_count_final = 0 # type: int + + self._character_count = 0 # type: int + + self._last_alpha_seen = None # type: Optional[str] + self._current_ascii_only = True # type: bool + + def eligible(self, character: str) -> bool: + return True + + def feed(self, character: str) -> None: + is_concerned = character.isalpha() and is_case_variable(character) + chunk_sep = is_concerned is False + + if chunk_sep and self._character_count_since_last_sep > 0: + if ( + self._character_count_since_last_sep <= 64 + and character.isdigit() is False + and self._current_ascii_only is False + ): + self._successive_upper_lower_count_final += ( + self._successive_upper_lower_count + ) + + self._successive_upper_lower_count = 0 + self._character_count_since_last_sep = 0 + self._last_alpha_seen = None + self._buf = False + self._character_count += 1 + self._current_ascii_only = True + + return + + if self._current_ascii_only is True and is_ascii(character) is False: + self._current_ascii_only = False + + if self._last_alpha_seen is not None: + if (character.isupper() and self._last_alpha_seen.islower()) or ( + character.islower() and self._last_alpha_seen.isupper() + ): + if self._buf is True: + self._successive_upper_lower_count += 2 + self._buf = False + else: + self._buf = True + else: + self._buf = False + + self._character_count += 1 + self._character_count_since_last_sep += 1 + self._last_alpha_seen = character + + def reset(self) -> None: + self._character_count = 0 + self._character_count_since_last_sep = 0 + self._successive_upper_lower_count = 0 + self._successive_upper_lower_count_final = 0 + self._last_alpha_seen = None + self._buf = False + self._current_ascii_only = True + + @property + def ratio(self) -> float: + if self._character_count == 0: + return 0.0 + + return self._successive_upper_lower_count_final / self._character_count + + +def is_suspiciously_successive_range( + unicode_range_a: Optional[str], unicode_range_b: Optional[str] +) -> bool: + """ + Determine if two Unicode range seen next to each other can be considered as suspicious. + """ + if unicode_range_a is None or unicode_range_b is None: + return True + + if unicode_range_a == unicode_range_b: + return False + + if "Latin" in unicode_range_a and "Latin" in unicode_range_b: + return False + + if "Emoticons" in unicode_range_a or "Emoticons" in unicode_range_b: + return False + + keywords_range_a, keywords_range_b = unicode_range_a.split( + " " + ), unicode_range_b.split(" ") + + for el in keywords_range_a: + if el in UNICODE_SECONDARY_RANGE_KEYWORD: + continue + if el in keywords_range_b: + return False + + # Japanese Exception + range_a_jp_chars, range_b_jp_chars = ( + unicode_range_a + in ( + "Hiragana", + "Katakana", + ), + unicode_range_b in ("Hiragana", "Katakana"), + ) + if range_a_jp_chars or range_b_jp_chars: + if "CJK" in unicode_range_a or "CJK" in unicode_range_b: + return False + if range_a_jp_chars and range_b_jp_chars: + return False + + if "Hangul" in unicode_range_a or "Hangul" in unicode_range_b: + if "CJK" in unicode_range_a or "CJK" in unicode_range_b: + return False + if unicode_range_a == "Basic Latin" or unicode_range_b == "Basic Latin": + return False + + # Chinese/Japanese use dedicated range for punctuation and/or separators. + if ("CJK" in unicode_range_a or "CJK" in unicode_range_b) or ( + unicode_range_a in ["Katakana", "Hiragana"] + and unicode_range_b in ["Katakana", "Hiragana"] + ): + if "Punctuation" in unicode_range_a or "Punctuation" in unicode_range_b: + return False + if "Forms" in unicode_range_a or "Forms" in unicode_range_b: + return False + + return True + + +@lru_cache(maxsize=2048) +def mess_ratio( + decoded_sequence: str, maximum_threshold: float = 0.2, debug: bool = False +) -> float: + """ + Compute a mess ratio given a decoded bytes sequence. The maximum threshold does stop the computation earlier. + """ + + detectors = [ + md_class() for md_class in MessDetectorPlugin.__subclasses__() + ] # type: List[MessDetectorPlugin] + + length = len(decoded_sequence) # type: int + + mean_mess_ratio = 0.0 # type: float + + if length < 512: + intermediary_mean_mess_ratio_calc = 32 # type: int + elif length <= 1024: + intermediary_mean_mess_ratio_calc = 64 + else: + intermediary_mean_mess_ratio_calc = 128 + + for character, index in zip(decoded_sequence, range(0, length)): + for detector in detectors: + if detector.eligible(character): + detector.feed(character) + + if ( + index > 0 and index % intermediary_mean_mess_ratio_calc == 0 + ) or index == length - 1: + mean_mess_ratio = sum([dt.ratio for dt in detectors]) + + if mean_mess_ratio >= maximum_threshold: + break + + if debug: + for dt in detectors: # pragma: nocover + print(dt.__class__, dt.ratio) + + return round(mean_mess_ratio, 3) diff --git a/pipenv/vendor/charset_normalizer/models.py b/pipenv/vendor/charset_normalizer/models.py new file mode 100644 index 00000000..68c27b89 --- /dev/null +++ b/pipenv/vendor/charset_normalizer/models.py @@ -0,0 +1,393 @@ +import warnings +from collections import Counter +from encodings.aliases import aliases +from hashlib import sha256 +from json import dumps +from re import sub +from typing import Any, Dict, Iterator, List, Optional, Tuple, Union + +from .constant import NOT_PRINTABLE_PATTERN, TOO_BIG_SEQUENCE +from .md import mess_ratio +from .utils import iana_name, is_multi_byte_encoding, unicode_range + + +class CharsetMatch: + def __init__( + self, + payload: bytes, + guessed_encoding: str, + mean_mess_ratio: float, + has_sig_or_bom: bool, + languages: "CoherenceMatches", + decoded_payload: Optional[str] = None, + ): + self._payload = payload # type: bytes + + self._encoding = guessed_encoding # type: str + self._mean_mess_ratio = mean_mess_ratio # type: float + self._languages = languages # type: CoherenceMatches + self._has_sig_or_bom = has_sig_or_bom # type: bool + self._unicode_ranges = None # type: Optional[List[str]] + + self._leaves = [] # type: List[CharsetMatch] + self._mean_coherence_ratio = 0.0 # type: float + + self._output_payload = None # type: Optional[bytes] + self._output_encoding = None # type: Optional[str] + + self._string = decoded_payload # type: Optional[str] + + def __eq__(self, other: object) -> bool: + if not isinstance(other, CharsetMatch): + raise TypeError( + "__eq__ cannot be invoked on {} and {}.".format( + str(other.__class__), str(self.__class__) + ) + ) + return self.encoding == other.encoding and self.fingerprint == other.fingerprint + + def __lt__(self, other: object) -> bool: + """ + Implemented to make sorted available upon CharsetMatches items. + """ + if not isinstance(other, CharsetMatch): + raise ValueError + + chaos_difference = abs(self.chaos - other.chaos) # type: float + coherence_difference = abs(self.coherence - other.coherence) # type: float + + # Bellow 1% difference --> Use Coherence + if chaos_difference < 0.01 and coherence_difference > 0.02: + # When having a tough decision, use the result that decoded as many multi-byte as possible. + if chaos_difference == 0.0 and self.coherence == other.coherence: + return self.multi_byte_usage > other.multi_byte_usage + return self.coherence > other.coherence + + return self.chaos < other.chaos + + @property + def multi_byte_usage(self) -> float: + return 1.0 - len(str(self)) / len(self.raw) + + @property + def chaos_secondary_pass(self) -> float: + """ + Check once again chaos in decoded text, except this time, with full content. + Use with caution, this can be very slow. + Notice: Will be removed in 3.0 + """ + warnings.warn( + "chaos_secondary_pass is deprecated and will be removed in 3.0", + DeprecationWarning, + ) + return mess_ratio(str(self), 1.0) + + @property + def coherence_non_latin(self) -> float: + """ + Coherence ratio on the first non-latin language detected if ANY. + Notice: Will be removed in 3.0 + """ + warnings.warn( + "coherence_non_latin is deprecated and will be removed in 3.0", + DeprecationWarning, + ) + return 0.0 + + @property + def w_counter(self) -> Counter: + """ + Word counter instance on decoded text. + Notice: Will be removed in 3.0 + """ + warnings.warn( + "w_counter is deprecated and will be removed in 3.0", DeprecationWarning + ) + + string_printable_only = sub(NOT_PRINTABLE_PATTERN, " ", str(self).lower()) + + return Counter(string_printable_only.split()) + + def __str__(self) -> str: + # Lazy Str Loading + if self._string is None: + self._string = str(self._payload, self._encoding, "strict") + return self._string + + def __repr__(self) -> str: + return "<CharsetMatch '{}' bytes({})>".format(self.encoding, self.fingerprint) + + def add_submatch(self, other: "CharsetMatch") -> None: + if not isinstance(other, CharsetMatch) or other == self: + raise ValueError( + "Unable to add instance <{}> as a submatch of a CharsetMatch".format( + other.__class__ + ) + ) + + other._string = None # Unload RAM usage; dirty trick. + self._leaves.append(other) + + @property + def encoding(self) -> str: + return self._encoding + + @property + def encoding_aliases(self) -> List[str]: + """ + Encoding name are known by many name, using this could help when searching for IBM855 when it's listed as CP855. + """ + also_known_as = [] # type: List[str] + for u, p in aliases.items(): + if self.encoding == u: + also_known_as.append(p) + elif self.encoding == p: + also_known_as.append(u) + return also_known_as + + @property + def bom(self) -> bool: + return self._has_sig_or_bom + + @property + def byte_order_mark(self) -> bool: + return self._has_sig_or_bom + + @property + def languages(self) -> List[str]: + """ + Return the complete list of possible languages found in decoded sequence. + Usually not really useful. Returned list may be empty even if 'language' property return something != 'Unknown'. + """ + return [e[0] for e in self._languages] + + @property + def language(self) -> str: + """ + Most probable language found in decoded sequence. If none were detected or inferred, the property will return + "Unknown". + """ + if not self._languages: + # Trying to infer the language based on the given encoding + # Its either English or we should not pronounce ourselves in certain cases. + if "ascii" in self.could_be_from_charset: + return "English" + + # doing it there to avoid circular import + from charset_normalizer.cd import encoding_languages, mb_encoding_languages + + languages = ( + mb_encoding_languages(self.encoding) + if is_multi_byte_encoding(self.encoding) + else encoding_languages(self.encoding) + ) + + if len(languages) == 0 or "Latin Based" in languages: + return "Unknown" + + return languages[0] + + return self._languages[0][0] + + @property + def chaos(self) -> float: + return self._mean_mess_ratio + + @property + def coherence(self) -> float: + if not self._languages: + return 0.0 + return self._languages[0][1] + + @property + def percent_chaos(self) -> float: + return round(self.chaos * 100, ndigits=3) + + @property + def percent_coherence(self) -> float: + return round(self.coherence * 100, ndigits=3) + + @property + def raw(self) -> bytes: + """ + Original untouched bytes. + """ + return self._payload + + @property + def submatch(self) -> List["CharsetMatch"]: + return self._leaves + + @property + def has_submatch(self) -> bool: + return len(self._leaves) > 0 + + @property + def alphabets(self) -> List[str]: + if self._unicode_ranges is not None: + return self._unicode_ranges + # list detected ranges + detected_ranges = [ + unicode_range(char) for char in str(self) + ] # type: List[Optional[str]] + # filter and sort + self._unicode_ranges = sorted(list({r for r in detected_ranges if r})) + return self._unicode_ranges + + @property + def could_be_from_charset(self) -> List[str]: + """ + The complete list of encoding that output the exact SAME str result and therefore could be the originating + encoding. + This list does include the encoding available in property 'encoding'. + """ + return [self._encoding] + [m.encoding for m in self._leaves] + + def first(self) -> "CharsetMatch": + """ + Kept for BC reasons. Will be removed in 3.0. + """ + return self + + def best(self) -> "CharsetMatch": + """ + Kept for BC reasons. Will be removed in 3.0. + """ + return self + + def output(self, encoding: str = "utf_8") -> bytes: + """ + Method to get re-encoded bytes payload using given target encoding. Default to UTF-8. + Any errors will be simply ignored by the encoder NOT replaced. + """ + if self._output_encoding is None or self._output_encoding != encoding: + self._output_encoding = encoding + self._output_payload = str(self).encode(encoding, "replace") + + return self._output_payload # type: ignore + + @property + def fingerprint(self) -> str: + """ + Retrieve the unique SHA256 computed using the transformed (re-encoded) payload. Not the original one. + """ + return sha256(self.output()).hexdigest() + + +class CharsetMatches: + """ + Container with every CharsetMatch items ordered by default from most probable to the less one. + Act like a list(iterable) but does not implements all related methods. + """ + + def __init__(self, results: List[CharsetMatch] = None): + self._results = sorted(results) if results else [] # type: List[CharsetMatch] + + def __iter__(self) -> Iterator[CharsetMatch]: + for result in self._results: + yield result + + def __getitem__(self, item: Union[int, str]) -> CharsetMatch: + """ + Retrieve a single item either by its position or encoding name (alias may be used here). + Raise KeyError upon invalid index or encoding not present in results. + """ + if isinstance(item, int): + return self._results[item] + if isinstance(item, str): + item = iana_name(item, False) + for result in self._results: + if item in result.could_be_from_charset: + return result + raise KeyError + + def __len__(self) -> int: + return len(self._results) + + def __bool__(self) -> bool: + return len(self._results) > 0 + + def append(self, item: CharsetMatch) -> None: + """ + Insert a single match. Will be inserted accordingly to preserve sort. + Can be inserted as a submatch. + """ + if not isinstance(item, CharsetMatch): + raise ValueError( + "Cannot append instance '{}' to CharsetMatches".format( + str(item.__class__) + ) + ) + # We should disable the submatch factoring when the input file is too heavy (conserve RAM usage) + if len(item.raw) <= TOO_BIG_SEQUENCE: + for match in self._results: + if match.fingerprint == item.fingerprint and match.chaos == item.chaos: + match.add_submatch(item) + return + self._results.append(item) + self._results = sorted(self._results) + + def best(self) -> Optional["CharsetMatch"]: + """ + Simply return the first match. Strict equivalent to matches[0]. + """ + if not self._results: + return None + return self._results[0] + + def first(self) -> Optional["CharsetMatch"]: + """ + Redundant method, call the method best(). Kept for BC reasons. + """ + return self.best() + + +CoherenceMatch = Tuple[str, float] +CoherenceMatches = List[CoherenceMatch] + + +class CliDetectionResult: + def __init__( + self, + path: str, + encoding: Optional[str], + encoding_aliases: List[str], + alternative_encodings: List[str], + language: str, + alphabets: List[str], + has_sig_or_bom: bool, + chaos: float, + coherence: float, + unicode_path: Optional[str], + is_preferred: bool, + ): + self.path = path # type: str + self.unicode_path = unicode_path # type: Optional[str] + self.encoding = encoding # type: Optional[str] + self.encoding_aliases = encoding_aliases # type: List[str] + self.alternative_encodings = alternative_encodings # type: List[str] + self.language = language # type: str + self.alphabets = alphabets # type: List[str] + self.has_sig_or_bom = has_sig_or_bom # type: bool + self.chaos = chaos # type: float + self.coherence = coherence # type: float + self.is_preferred = is_preferred # type: bool + + @property + def __dict__(self) -> Dict[str, Any]: # type: ignore + return { + "path": self.path, + "encoding": self.encoding, + "encoding_aliases": self.encoding_aliases, + "alternative_encodings": self.alternative_encodings, + "language": self.language, + "alphabets": self.alphabets, + "has_sig_or_bom": self.has_sig_or_bom, + "chaos": self.chaos, + "coherence": self.coherence, + "unicode_path": self.unicode_path, + "is_preferred": self.is_preferred, + } + + def to_json(self) -> str: + return dumps(self.__dict__, ensure_ascii=True, indent=4) diff --git a/pipenv/vendor/charset_normalizer/py.typed b/pipenv/vendor/charset_normalizer/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/pipenv/vendor/charset_normalizer/utils.py b/pipenv/vendor/charset_normalizer/utils.py new file mode 100644 index 00000000..b9d12784 --- /dev/null +++ b/pipenv/vendor/charset_normalizer/utils.py @@ -0,0 +1,333 @@ +try: + import unicodedata2 as unicodedata +except ImportError: + import unicodedata # type: ignore[no-redef] + +import importlib +from codecs import IncrementalDecoder +from encodings.aliases import aliases +from functools import lru_cache +from re import findall +from typing import List, Optional, Set, Tuple, Union + +from _multibytecodec import MultibyteIncrementalDecoder # type: ignore + +from .constant import ( + ENCODING_MARKS, + IANA_SUPPORTED_SIMILAR, + RE_POSSIBLE_ENCODING_INDICATION, + UNICODE_RANGES_COMBINED, + UNICODE_SECONDARY_RANGE_KEYWORD, + UTF8_MAXIMAL_ALLOCATION, +) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_accentuated(character: str) -> bool: + try: + description = unicodedata.name(character) # type: str + except ValueError: + return False + return ( + "WITH GRAVE" in description + or "WITH ACUTE" in description + or "WITH CEDILLA" in description + or "WITH DIAERESIS" in description + or "WITH CIRCUMFLEX" in description + or "WITH TILDE" in description + ) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def remove_accent(character: str) -> str: + decomposed = unicodedata.decomposition(character) # type: str + if not decomposed: + return character + + codes = decomposed.split(" ") # type: List[str] + + return chr(int(codes[0], 16)) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def unicode_range(character: str) -> Optional[str]: + """ + Retrieve the Unicode range official name from a single character. + """ + character_ord = ord(character) # type: int + + for range_name, ord_range in UNICODE_RANGES_COMBINED.items(): + if character_ord in ord_range: + return range_name + + return None + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_latin(character: str) -> bool: + try: + description = unicodedata.name(character) # type: str + except ValueError: + return False + return "LATIN" in description + + +def is_ascii(character: str) -> bool: + try: + character.encode("ascii") + except UnicodeEncodeError: + return False + return True + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_punctuation(character: str) -> bool: + character_category = unicodedata.category(character) # type: str + + if "P" in character_category: + return True + + character_range = unicode_range(character) # type: Optional[str] + + if character_range is None: + return False + + return "Punctuation" in character_range + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_symbol(character: str) -> bool: + character_category = unicodedata.category(character) # type: str + + if "S" in character_category or "N" in character_category: + return True + + character_range = unicode_range(character) # type: Optional[str] + + if character_range is None: + return False + + return "Forms" in character_range + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_emoticon(character: str) -> bool: + character_range = unicode_range(character) # type: Optional[str] + + if character_range is None: + return False + + return "Emoticons" in character_range + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_separator(character: str) -> bool: + if character.isspace() or character in ["|", "+", ",", ";", "<", ">"]: + return True + + character_category = unicodedata.category(character) # type: str + + return "Z" in character_category + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_case_variable(character: str) -> bool: + return character.islower() != character.isupper() + + +def is_private_use_only(character: str) -> bool: + character_category = unicodedata.category(character) # type: str + + return "Co" == character_category + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_cjk(character: str) -> bool: + try: + character_name = unicodedata.name(character) + except ValueError: + return False + + return "CJK" in character_name + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_hiragana(character: str) -> bool: + try: + character_name = unicodedata.name(character) + except ValueError: + return False + + return "HIRAGANA" in character_name + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_katakana(character: str) -> bool: + try: + character_name = unicodedata.name(character) + except ValueError: + return False + + return "KATAKANA" in character_name + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_hangul(character: str) -> bool: + try: + character_name = unicodedata.name(character) + except ValueError: + return False + + return "HANGUL" in character_name + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_thai(character: str) -> bool: + try: + character_name = unicodedata.name(character) + except ValueError: + return False + + return "THAI" in character_name + + +@lru_cache(maxsize=len(UNICODE_RANGES_COMBINED)) +def is_unicode_range_secondary(range_name: str) -> bool: + for keyword in UNICODE_SECONDARY_RANGE_KEYWORD: + if keyword in range_name: + return True + + return False + + +def any_specified_encoding(sequence: bytes, search_zone: int = 4096) -> Optional[str]: + """ + Extract using ASCII-only decoder any specified encoding in the first n-bytes. + """ + if not isinstance(sequence, bytes): + raise TypeError + + seq_len = len(sequence) # type: int + + results = findall( + RE_POSSIBLE_ENCODING_INDICATION, + sequence[: seq_len if seq_len <= search_zone else search_zone].decode( + "ascii", errors="ignore" + ), + ) # type: List[str] + + if len(results) == 0: + return None + + for specified_encoding in results: + specified_encoding = specified_encoding.lower().replace("-", "_") + + for encoding_alias, encoding_iana in aliases.items(): + if encoding_alias == specified_encoding: + return encoding_iana + if encoding_iana == specified_encoding: + return encoding_iana + + return None + + +@lru_cache(maxsize=128) +def is_multi_byte_encoding(name: str) -> bool: + """ + Verify is a specific encoding is a multi byte one based on it IANA name + """ + return name in { + "utf_8", + "utf_8_sig", + "utf_16", + "utf_16_be", + "utf_16_le", + "utf_32", + "utf_32_le", + "utf_32_be", + "utf_7", + } or issubclass( + importlib.import_module("encodings.{}".format(name)).IncrementalDecoder, # type: ignore + MultibyteIncrementalDecoder, + ) + + +def identify_sig_or_bom(sequence: bytes) -> Tuple[Optional[str], bytes]: + """ + Identify and extract SIG/BOM in given sequence. + """ + + for iana_encoding in ENCODING_MARKS: + marks = ENCODING_MARKS[iana_encoding] # type: Union[bytes, List[bytes]] + + if isinstance(marks, bytes): + marks = [marks] + + for mark in marks: + if sequence.startswith(mark): + return iana_encoding, mark + + return None, b"" + + +def should_strip_sig_or_bom(iana_encoding: str) -> bool: + return iana_encoding not in {"utf_16", "utf_32"} + + +def iana_name(cp_name: str, strict: bool = True) -> str: + cp_name = cp_name.lower().replace("-", "_") + + for encoding_alias, encoding_iana in aliases.items(): + if cp_name == encoding_alias or cp_name == encoding_iana: + return encoding_iana + + if strict: + raise ValueError("Unable to retrieve IANA for '{}'".format(cp_name)) + + return cp_name + + +def range_scan(decoded_sequence: str) -> List[str]: + ranges = set() # type: Set[str] + + for character in decoded_sequence: + character_range = unicode_range(character) # type: Optional[str] + + if character_range is None: + continue + + ranges.add(character_range) + + return list(ranges) + + +def cp_similarity(iana_name_a: str, iana_name_b: str) -> float: + + if is_multi_byte_encoding(iana_name_a) or is_multi_byte_encoding(iana_name_b): + return 0.0 + + decoder_a = importlib.import_module("encodings.{}".format(iana_name_a)).IncrementalDecoder # type: ignore + decoder_b = importlib.import_module("encodings.{}".format(iana_name_b)).IncrementalDecoder # type: ignore + + id_a = decoder_a(errors="ignore") # type: IncrementalDecoder + id_b = decoder_b(errors="ignore") # type: IncrementalDecoder + + character_match_count = 0 # type: int + + for i in range(0, 255): + to_be_decoded = bytes([i]) # type: bytes + if id_a.decode(to_be_decoded) == id_b.decode(to_be_decoded): + character_match_count += 1 + + return character_match_count / 254 + + +def is_cp_similar(iana_name_a: str, iana_name_b: str) -> bool: + """ + Determine if two code page are at least 80% similar. IANA_SUPPORTED_SIMILAR dict was generated using + the function cp_similarity. + """ + return ( + iana_name_a in IANA_SUPPORTED_SIMILAR + and iana_name_b in IANA_SUPPORTED_SIMILAR[iana_name_a] + ) diff --git a/pipenv/vendor/charset_normalizer/version.py b/pipenv/vendor/charset_normalizer/version.py new file mode 100644 index 00000000..98e53fb3 --- /dev/null +++ b/pipenv/vendor/charset_normalizer/version.py @@ -0,0 +1,6 @@ +""" +Expose version +""" + +__version__ = "2.0.7" +VERSION = __version__.split(".") diff --git a/pipenv/vendor/click/LICENSE.rst b/pipenv/vendor/click/LICENSE.rst index 87ce152a..d12a8491 100644 --- a/pipenv/vendor/click/LICENSE.rst +++ b/pipenv/vendor/click/LICENSE.rst @@ -1,39 +1,28 @@ -Copyright © 2014 by the Pallets team. +Copyright 2014 Pallets -Some rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: -Redistribution and use in source and binary forms of the software as -well as documentation, with or without modification, are permitted -provided that the following conditions are met: - -- Redistributions of source code must retain the above copyright +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -- Redistributions in binary form must reproduce the above copyright +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -- Neither the name of the copyright holder nor the names of its +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. -THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND -CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, -BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF -USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF -SUCH DAMAGE. - ----- - -Click uses parts of optparse written by Gregory P. Ward and maintained -by the Python Software Foundation. This is limited to code in parser.py. - -Copyright © 2001-2006 Gregory P. Ward. All rights reserved. -Copyright © 2002-2006 Python Software Foundation. All rights reserved. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/pipenv/vendor/click/__init__.py b/pipenv/vendor/click/__init__.py index d3c33660..a2ed5d13 100644 --- a/pipenv/vendor/click/__init__.py +++ b/pipenv/vendor/click/__init__.py @@ -1,97 +1,75 @@ -# -*- coding: utf-8 -*- """ -click -~~~~~ - Click is a simple Python module inspired by the stdlib optparse to make writing command line scripts fun. Unlike other modules, it's based around a simple API that does not come with too much magic and is composable. - -:copyright: © 2014 by the Pallets team. -:license: BSD, see LICENSE.rst for more details. """ +from .core import Argument as Argument +from .core import BaseCommand as BaseCommand +from .core import Command as Command +from .core import CommandCollection as CommandCollection +from .core import Context as Context +from .core import Group as Group +from .core import MultiCommand as MultiCommand +from .core import Option as Option +from .core import Parameter as Parameter +from .decorators import argument as argument +from .decorators import command as command +from .decorators import confirmation_option as confirmation_option +from .decorators import group as group +from .decorators import help_option as help_option +from .decorators import make_pass_decorator as make_pass_decorator +from .decorators import option as option +from .decorators import pass_context as pass_context +from .decorators import pass_obj as pass_obj +from .decorators import password_option as password_option +from .decorators import version_option as version_option +from .exceptions import Abort as Abort +from .exceptions import BadArgumentUsage as BadArgumentUsage +from .exceptions import BadOptionUsage as BadOptionUsage +from .exceptions import BadParameter as BadParameter +from .exceptions import ClickException as ClickException +from .exceptions import FileError as FileError +from .exceptions import MissingParameter as MissingParameter +from .exceptions import NoSuchOption as NoSuchOption +from .exceptions import UsageError as UsageError +from .formatting import HelpFormatter as HelpFormatter +from .formatting import wrap_text as wrap_text +from .globals import get_current_context as get_current_context +from .parser import OptionParser as OptionParser +from .termui import clear as clear +from .termui import confirm as confirm +from .termui import echo_via_pager as echo_via_pager +from .termui import edit as edit +from .termui import get_terminal_size as get_terminal_size +from .termui import getchar as getchar +from .termui import launch as launch +from .termui import pause as pause +from .termui import progressbar as progressbar +from .termui import prompt as prompt +from .termui import secho as secho +from .termui import style as style +from .termui import unstyle as unstyle +from .types import BOOL as BOOL +from .types import Choice as Choice +from .types import DateTime as DateTime +from .types import File as File +from .types import FLOAT as FLOAT +from .types import FloatRange as FloatRange +from .types import INT as INT +from .types import IntRange as IntRange +from .types import ParamType as ParamType +from .types import Path as Path +from .types import STRING as STRING +from .types import Tuple as Tuple +from .types import UNPROCESSED as UNPROCESSED +from .types import UUID as UUID +from .utils import echo as echo +from .utils import format_filename as format_filename +from .utils import get_app_dir as get_app_dir +from .utils import get_binary_stream as get_binary_stream +from .utils import get_os_args as get_os_args +from .utils import get_text_stream as get_text_stream +from .utils import open_file as open_file -# Core classes -from .core import Context, BaseCommand, Command, MultiCommand, Group, \ - CommandCollection, Parameter, Option, Argument - -# Globals -from .globals import get_current_context - -# Decorators -from .decorators import pass_context, pass_obj, make_pass_decorator, \ - command, group, argument, option, confirmation_option, \ - password_option, version_option, help_option - -# Types -from .types import ParamType, File, Path, Choice, IntRange, Tuple, \ - DateTime, STRING, INT, FLOAT, BOOL, UUID, UNPROCESSED, FloatRange - -# Utilities -from .utils import echo, get_binary_stream, get_text_stream, open_file, \ - format_filename, get_app_dir, get_os_args - -# Terminal functions -from .termui import prompt, confirm, get_terminal_size, echo_via_pager, \ - progressbar, clear, style, unstyle, secho, edit, launch, getchar, \ - pause - -# Exceptions -from .exceptions import ClickException, UsageError, BadParameter, \ - FileError, Abort, NoSuchOption, BadOptionUsage, BadArgumentUsage, \ - MissingParameter - -# Formatting -from .formatting import HelpFormatter, wrap_text - -# Parsing -from .parser import OptionParser - - -__all__ = [ - # Core classes - 'Context', 'BaseCommand', 'Command', 'MultiCommand', 'Group', - 'CommandCollection', 'Parameter', 'Option', 'Argument', - - # Globals - 'get_current_context', - - # Decorators - 'pass_context', 'pass_obj', 'make_pass_decorator', 'command', 'group', - 'argument', 'option', 'confirmation_option', 'password_option', - 'version_option', 'help_option', - - # Types - 'ParamType', 'File', 'Path', 'Choice', 'IntRange', 'Tuple', - 'DateTime', 'STRING', 'INT', 'FLOAT', 'BOOL', 'UUID', 'UNPROCESSED', - 'FloatRange', - - # Utilities - 'echo', 'get_binary_stream', 'get_text_stream', 'open_file', - 'format_filename', 'get_app_dir', 'get_os_args', - - # Terminal functions - 'prompt', 'confirm', 'get_terminal_size', 'echo_via_pager', - 'progressbar', 'clear', 'style', 'unstyle', 'secho', 'edit', 'launch', - 'getchar', 'pause', - - # Exceptions - 'ClickException', 'UsageError', 'BadParameter', 'FileError', - 'Abort', 'NoSuchOption', 'BadOptionUsage', 'BadArgumentUsage', - 'MissingParameter', - - # Formatting - 'HelpFormatter', 'wrap_text', - - # Parsing - 'OptionParser', -] - - -# Controls if click should emit the warning about the use of unicode -# literals. -disable_unicode_literals_warning = False - - -__version__ = '7.0' +__version__ = "8.0.3" diff --git a/pipenv/vendor/click/_bashcomplete.py b/pipenv/vendor/click/_bashcomplete.py deleted file mode 100644 index a5f1084c..00000000 --- a/pipenv/vendor/click/_bashcomplete.py +++ /dev/null @@ -1,293 +0,0 @@ -import copy -import os -import re - -from .utils import echo -from .parser import split_arg_string -from .core import MultiCommand, Option, Argument -from .types import Choice - -try: - from collections import abc -except ImportError: - import collections as abc - -WORDBREAK = '=' - -# Note, only BASH version 4.4 and later have the nosort option. -COMPLETION_SCRIPT_BASH = ''' -%(complete_func)s() { - local IFS=$'\n' - COMPREPLY=( $( env COMP_WORDS="${COMP_WORDS[*]}" \\ - COMP_CWORD=$COMP_CWORD \\ - %(autocomplete_var)s=complete $1 ) ) - return 0 -} - -%(complete_func)setup() { - local COMPLETION_OPTIONS="" - local BASH_VERSION_ARR=(${BASH_VERSION//./ }) - # Only BASH version 4.4 and later have the nosort option. - if [ ${BASH_VERSION_ARR[0]} -gt 4 ] || ([ ${BASH_VERSION_ARR[0]} -eq 4 ] && [ ${BASH_VERSION_ARR[1]} -ge 4 ]); then - COMPLETION_OPTIONS="-o nosort" - fi - - complete $COMPLETION_OPTIONS -F %(complete_func)s %(script_names)s -} - -%(complete_func)setup -''' - -COMPLETION_SCRIPT_ZSH = ''' -%(complete_func)s() { - local -a completions - local -a completions_with_descriptions - local -a response - response=("${(@f)$( env COMP_WORDS=\"${words[*]}\" \\ - COMP_CWORD=$((CURRENT-1)) \\ - %(autocomplete_var)s=\"complete_zsh\" \\ - %(script_names)s )}") - - for key descr in ${(kv)response}; do - if [[ "$descr" == "_" ]]; then - completions+=("$key") - else - completions_with_descriptions+=("$key":"$descr") - fi - done - - if [ -n "$completions_with_descriptions" ]; then - _describe -V unsorted completions_with_descriptions -U -Q - fi - - if [ -n "$completions" ]; then - compadd -U -V unsorted -Q -a completions - fi - compstate[insert]="automenu" -} - -compdef %(complete_func)s %(script_names)s -''' - -_invalid_ident_char_re = re.compile(r'[^a-zA-Z0-9_]') - - -def get_completion_script(prog_name, complete_var, shell): - cf_name = _invalid_ident_char_re.sub('', prog_name.replace('-', '_')) - script = COMPLETION_SCRIPT_ZSH if shell == 'zsh' else COMPLETION_SCRIPT_BASH - return (script % { - 'complete_func': '_%s_completion' % cf_name, - 'script_names': prog_name, - 'autocomplete_var': complete_var, - }).strip() + ';' - - -def resolve_ctx(cli, prog_name, args): - """ - Parse into a hierarchy of contexts. Contexts are connected through the parent variable. - :param cli: command definition - :param prog_name: the program that is running - :param args: full list of args - :return: the final context/command parsed - """ - ctx = cli.make_context(prog_name, args, resilient_parsing=True) - args = ctx.protected_args + ctx.args - while args: - if isinstance(ctx.command, MultiCommand): - if not ctx.command.chain: - cmd_name, cmd, args = ctx.command.resolve_command(ctx, args) - if cmd is None: - return ctx - ctx = cmd.make_context(cmd_name, args, parent=ctx, - resilient_parsing=True) - args = ctx.protected_args + ctx.args - else: - # Walk chained subcommand contexts saving the last one. - while args: - cmd_name, cmd, args = ctx.command.resolve_command(ctx, args) - if cmd is None: - return ctx - sub_ctx = cmd.make_context(cmd_name, args, parent=ctx, - allow_extra_args=True, - allow_interspersed_args=False, - resilient_parsing=True) - args = sub_ctx.args - ctx = sub_ctx - args = sub_ctx.protected_args + sub_ctx.args - else: - break - return ctx - - -def start_of_option(param_str): - """ - :param param_str: param_str to check - :return: whether or not this is the start of an option declaration (i.e. starts "-" or "--") - """ - return param_str and param_str[:1] == '-' - - -def is_incomplete_option(all_args, cmd_param): - """ - :param all_args: the full original list of args supplied - :param cmd_param: the current command paramter - :return: whether or not the last option declaration (i.e. starts "-" or "--") is incomplete and - corresponds to this cmd_param. In other words whether this cmd_param option can still accept - values - """ - if not isinstance(cmd_param, Option): - return False - if cmd_param.is_flag: - return False - last_option = None - for index, arg_str in enumerate(reversed([arg for arg in all_args if arg != WORDBREAK])): - if index + 1 > cmd_param.nargs: - break - if start_of_option(arg_str): - last_option = arg_str - - return True if last_option and last_option in cmd_param.opts else False - - -def is_incomplete_argument(current_params, cmd_param): - """ - :param current_params: the current params and values for this argument as already entered - :param cmd_param: the current command parameter - :return: whether or not the last argument is incomplete and corresponds to this cmd_param. In - other words whether or not the this cmd_param argument can still accept values - """ - if not isinstance(cmd_param, Argument): - return False - current_param_values = current_params[cmd_param.name] - if current_param_values is None: - return True - if cmd_param.nargs == -1: - return True - if isinstance(current_param_values, abc.Iterable) \ - and cmd_param.nargs > 1 and len(current_param_values) < cmd_param.nargs: - return True - return False - - -def get_user_autocompletions(ctx, args, incomplete, cmd_param): - """ - :param ctx: context associated with the parsed command - :param args: full list of args - :param incomplete: the incomplete text to autocomplete - :param cmd_param: command definition - :return: all the possible user-specified completions for the param - """ - results = [] - if isinstance(cmd_param.type, Choice): - # Choices don't support descriptions. - results = [(c, None) - for c in cmd_param.type.choices if str(c).startswith(incomplete)] - elif cmd_param.autocompletion is not None: - dynamic_completions = cmd_param.autocompletion(ctx=ctx, - args=args, - incomplete=incomplete) - results = [c if isinstance(c, tuple) else (c, None) - for c in dynamic_completions] - return results - - -def get_visible_commands_starting_with(ctx, starts_with): - """ - :param ctx: context associated with the parsed command - :starts_with: string that visible commands must start with. - :return: all visible (not hidden) commands that start with starts_with. - """ - for c in ctx.command.list_commands(ctx): - if c.startswith(starts_with): - command = ctx.command.get_command(ctx, c) - if not command.hidden: - yield command - - -def add_subcommand_completions(ctx, incomplete, completions_out): - # Add subcommand completions. - if isinstance(ctx.command, MultiCommand): - completions_out.extend( - [(c.name, c.get_short_help_str()) for c in get_visible_commands_starting_with(ctx, incomplete)]) - - # Walk up the context list and add any other completion possibilities from chained commands - while ctx.parent is not None: - ctx = ctx.parent - if isinstance(ctx.command, MultiCommand) and ctx.command.chain: - remaining_commands = [c for c in get_visible_commands_starting_with(ctx, incomplete) - if c.name not in ctx.protected_args] - completions_out.extend([(c.name, c.get_short_help_str()) for c in remaining_commands]) - - -def get_choices(cli, prog_name, args, incomplete): - """ - :param cli: command definition - :param prog_name: the program that is running - :param args: full list of args - :param incomplete: the incomplete text to autocomplete - :return: all the possible completions for the incomplete - """ - all_args = copy.deepcopy(args) - - ctx = resolve_ctx(cli, prog_name, args) - if ctx is None: - return [] - - # In newer versions of bash long opts with '='s are partitioned, but it's easier to parse - # without the '=' - if start_of_option(incomplete) and WORDBREAK in incomplete: - partition_incomplete = incomplete.partition(WORDBREAK) - all_args.append(partition_incomplete[0]) - incomplete = partition_incomplete[2] - elif incomplete == WORDBREAK: - incomplete = '' - - completions = [] - if start_of_option(incomplete): - # completions for partial options - for param in ctx.command.params: - if isinstance(param, Option) and not param.hidden: - param_opts = [param_opt for param_opt in param.opts + - param.secondary_opts if param_opt not in all_args or param.multiple] - completions.extend([(o, param.help) for o in param_opts if o.startswith(incomplete)]) - return completions - # completion for option values from user supplied values - for param in ctx.command.params: - if is_incomplete_option(all_args, param): - return get_user_autocompletions(ctx, all_args, incomplete, param) - # completion for argument values from user supplied values - for param in ctx.command.params: - if is_incomplete_argument(ctx.params, param): - return get_user_autocompletions(ctx, all_args, incomplete, param) - - add_subcommand_completions(ctx, incomplete, completions) - # Sort before returning so that proper ordering can be enforced in custom types. - return sorted(completions) - - -def do_complete(cli, prog_name, include_descriptions): - cwords = split_arg_string(os.environ['COMP_WORDS']) - cword = int(os.environ['COMP_CWORD']) - args = cwords[1:cword] - try: - incomplete = cwords[cword] - except IndexError: - incomplete = '' - - for item in get_choices(cli, prog_name, args, incomplete): - echo(item[0]) - if include_descriptions: - # ZSH has trouble dealing with empty array parameters when returned from commands, so use a well defined character '_' to indicate no description is present. - echo(item[1] if item[1] else '_') - - return True - - -def bashcomplete(cli, prog_name, complete_var, complete_instr): - if complete_instr.startswith('source'): - shell = 'zsh' if complete_instr == 'source_zsh' else 'bash' - echo(get_completion_script(prog_name, complete_var, shell)) - return True - elif complete_instr == 'complete' or complete_instr == 'complete_zsh': - return do_complete(cli, prog_name, complete_instr == 'complete_zsh') - return False diff --git a/pipenv/vendor/click/_compat.py b/pipenv/vendor/click/_compat.py index 937e2301..7877b529 100644 --- a/pipenv/vendor/click/_compat.py +++ b/pipenv/vendor/click/_compat.py @@ -1,92 +1,90 @@ -import re +import codecs import io import os +import re import sys -import codecs +import typing as t from weakref import WeakKeyDictionary - -PY2 = sys.version_info[0] == 2 -CYGWIN = sys.platform.startswith('cygwin') +CYGWIN = sys.platform.startswith("cygwin") +MSYS2 = sys.platform.startswith("win") and ("GCC" in sys.version) # Determine local App Engine environment, per Google's own suggestion -APP_ENGINE = ('APPENGINE_RUNTIME' in os.environ and - 'Development/' in os.environ['SERVER_SOFTWARE']) -WIN = sys.platform.startswith('win') and not APP_ENGINE -DEFAULT_COLUMNS = 80 +APP_ENGINE = "APPENGINE_RUNTIME" in os.environ and "Development/" in os.environ.get( + "SERVER_SOFTWARE", "" +) +WIN = sys.platform.startswith("win") and not APP_ENGINE and not MSYS2 +auto_wrap_for_ansi: t.Optional[t.Callable[[t.TextIO], t.TextIO]] = None +_ansi_re = re.compile(r"\033\[[;?0-9]*[a-zA-Z]") -_ansi_re = re.compile(r'\033\[((?:\d|;)*)([a-zA-Z])') - - -def get_filesystem_encoding(): +def get_filesystem_encoding() -> str: return sys.getfilesystemencoding() or sys.getdefaultencoding() -def _make_text_stream(stream, encoding, errors, - force_readable=False, force_writable=False): +def _make_text_stream( + stream: t.BinaryIO, + encoding: t.Optional[str], + errors: t.Optional[str], + force_readable: bool = False, + force_writable: bool = False, +) -> t.TextIO: if encoding is None: encoding = get_best_encoding(stream) if errors is None: - errors = 'replace' - return _NonClosingTextIOWrapper(stream, encoding, errors, - line_buffering=True, - force_readable=force_readable, - force_writable=force_writable) + errors = "replace" + return _NonClosingTextIOWrapper( + stream, + encoding, + errors, + line_buffering=True, + force_readable=force_readable, + force_writable=force_writable, + ) -def is_ascii_encoding(encoding): +def is_ascii_encoding(encoding: str) -> bool: """Checks if a given encoding is ascii.""" try: - return codecs.lookup(encoding).name == 'ascii' + return codecs.lookup(encoding).name == "ascii" except LookupError: return False -def get_best_encoding(stream): +def get_best_encoding(stream: t.IO) -> str: """Returns the default stream encoding if not found.""" - rv = getattr(stream, 'encoding', None) or sys.getdefaultencoding() + rv = getattr(stream, "encoding", None) or sys.getdefaultencoding() if is_ascii_encoding(rv): - return 'utf-8' + return "utf-8" return rv class _NonClosingTextIOWrapper(io.TextIOWrapper): + def __init__( + self, + stream: t.BinaryIO, + encoding: t.Optional[str], + errors: t.Optional[str], + force_readable: bool = False, + force_writable: bool = False, + **extra: t.Any, + ) -> None: + self._stream = stream = t.cast( + t.BinaryIO, _FixupStream(stream, force_readable, force_writable) + ) + super().__init__(stream, encoding, errors, **extra) - def __init__(self, stream, encoding, errors, - force_readable=False, force_writable=False, **extra): - self._stream = stream = _FixupStream(stream, force_readable, - force_writable) - io.TextIOWrapper.__init__(self, stream, encoding, errors, **extra) - - # The io module is a place where the Python 3 text behavior - # was forced upon Python 2, so we need to unbreak - # it to look like Python 2. - if PY2: - def write(self, x): - if isinstance(x, str) or is_bytes(x): - try: - self.flush() - except Exception: - pass - return self.buffer.write(str(x)) - return io.TextIOWrapper.write(self, x) - - def writelines(self, lines): - for line in lines: - self.write(line) - - def __del__(self): + def __del__(self) -> None: try: self.detach() except Exception: pass - def isatty(self): + def isatty(self) -> bool: # https://bitbucket.org/pypy/pypy/issue/1803 return self._stream.isatty() -class _FixupStream(object): +class _FixupStream: """The new io interface needs more from streams than streams traditionally implement. As such, this fix-up code is necessary in some circumstances. @@ -96,56 +94,58 @@ class _FixupStream(object): of jupyter notebook). """ - def __init__(self, stream, force_readable=False, force_writable=False): + def __init__( + self, + stream: t.BinaryIO, + force_readable: bool = False, + force_writable: bool = False, + ): self._stream = stream self._force_readable = force_readable self._force_writable = force_writable - def __getattr__(self, name): + def __getattr__(self, name: str) -> t.Any: return getattr(self._stream, name) - def read1(self, size): - f = getattr(self._stream, 'read1', None) + def read1(self, size: int) -> bytes: + f = getattr(self._stream, "read1", None) + if f is not None: - return f(size) - # We only dispatch to readline instead of read in Python 2 as we - # do not want cause problems with the different implementation - # of line buffering. - if PY2: - return self._stream.readline(size) + return t.cast(bytes, f(size)) + return self._stream.read(size) - def readable(self): + def readable(self) -> bool: if self._force_readable: return True - x = getattr(self._stream, 'readable', None) + x = getattr(self._stream, "readable", None) if x is not None: - return x() + return t.cast(bool, x()) try: self._stream.read(0) except Exception: return False return True - def writable(self): + def writable(self) -> bool: if self._force_writable: return True - x = getattr(self._stream, 'writable', None) + x = getattr(self._stream, "writable", None) if x is not None: - return x() + return t.cast(bool, x()) try: - self._stream.write('') + self._stream.write("") # type: ignore except Exception: try: - self._stream.write(b'') + self._stream.write(b"") except Exception: return False return True - def seekable(self): - x = getattr(self._stream, 'seekable', None) + def seekable(self) -> bool: + x = getattr(self._stream, "seekable", None) if x is not None: - return x() + return t.cast(bool, x()) try: self._stream.seek(self._stream.tell()) except Exception: @@ -153,518 +153,443 @@ class _FixupStream(object): return True -if PY2: - text_type = unicode - bytes = str - raw_input = raw_input - string_types = (str, unicode) - int_types = (int, long) - iteritems = lambda x: x.iteritems() - range_type = xrange - - def is_bytes(x): - return isinstance(x, (buffer, bytearray)) - - _identifier_re = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_]*$') - - # For Windows, we need to force stdout/stdin/stderr to binary if it's - # fetched for that. This obviously is not the most correct way to do - # it as it changes global state. Unfortunately, there does not seem to - # be a clear better way to do it as just reopening the file in binary - # mode does not change anything. - # - # An option would be to do what Python 3 does and to open the file as - # binary only, patch it back to the system, and then use a wrapper - # stream that converts newlines. It's not quite clear what's the - # correct option here. - # - # This code also lives in _winconsole for the fallback to the console - # emulation stream. - # - # There are also Windows environments where the `msvcrt` module is not - # available (which is why we use try-catch instead of the WIN variable - # here), such as the Google App Engine development server on Windows. In - # those cases there is just nothing we can do. - def set_binary_mode(f): - return f - +def _is_binary_reader(stream: t.IO, default: bool = False) -> bool: try: - import msvcrt - except ImportError: - pass - else: - def set_binary_mode(f): - try: - fileno = f.fileno() - except Exception: - pass - else: - msvcrt.setmode(fileno, os.O_BINARY) - return f + return isinstance(stream.read(0), bytes) + except Exception: + return default + # This happens in some cases where the stream was already + # closed. In this case, we assume the default. + +def _is_binary_writer(stream: t.IO, default: bool = False) -> bool: try: - import fcntl - except ImportError: - pass - else: - def set_binary_mode(f): - try: - fileno = f.fileno() - except Exception: - pass - else: - flags = fcntl.fcntl(fileno, fcntl.F_GETFL) - fcntl.fcntl(fileno, fcntl.F_SETFL, flags & ~os.O_NONBLOCK) - return f - - def isidentifier(x): - return _identifier_re.search(x) is not None - - def get_binary_stdin(): - return set_binary_mode(sys.stdin) - - def get_binary_stdout(): - _wrap_std_stream('stdout') - return set_binary_mode(sys.stdout) - - def get_binary_stderr(): - _wrap_std_stream('stderr') - return set_binary_mode(sys.stderr) - - def get_text_stdin(encoding=None, errors=None): - rv = _get_windows_console_stream(sys.stdin, encoding, errors) - if rv is not None: - return rv - return _make_text_stream(sys.stdin, encoding, errors, - force_readable=True) - - def get_text_stdout(encoding=None, errors=None): - _wrap_std_stream('stdout') - rv = _get_windows_console_stream(sys.stdout, encoding, errors) - if rv is not None: - return rv - return _make_text_stream(sys.stdout, encoding, errors, - force_writable=True) - - def get_text_stderr(encoding=None, errors=None): - _wrap_std_stream('stderr') - rv = _get_windows_console_stream(sys.stderr, encoding, errors) - if rv is not None: - return rv - return _make_text_stream(sys.stderr, encoding, errors, - force_writable=True) - - def filename_to_ui(value): - if isinstance(value, bytes): - value = value.decode(get_filesystem_encoding(), 'replace') - return value -else: - import io - text_type = str - raw_input = input - string_types = (str,) - int_types = (int,) - range_type = range - isidentifier = lambda x: x.isidentifier() - iteritems = lambda x: iter(x.items()) - - def is_bytes(x): - return isinstance(x, (bytes, memoryview, bytearray)) - - def _is_binary_reader(stream, default=False): + stream.write(b"") + except Exception: try: - return isinstance(stream.read(0), bytes) + stream.write("") + return False except Exception: - return default - # This happens in some cases where the stream was already - # closed. In this case, we assume the default. - - def _is_binary_writer(stream, default=False): - try: - stream.write(b'') - except Exception: - try: - stream.write('') - return False - except Exception: - pass - return default - return True - - def _find_binary_reader(stream): - # We need to figure out if the given stream is already binary. - # This can happen because the official docs recommend detaching - # the streams to get binary streams. Some code might do this, so - # we need to deal with this case explicitly. - if _is_binary_reader(stream, False): - return stream - - buf = getattr(stream, 'buffer', None) - - # Same situation here; this time we assume that the buffer is - # actually binary in case it's closed. - if buf is not None and _is_binary_reader(buf, True): - return buf - - def _find_binary_writer(stream): - # We need to figure out if the given stream is already binary. - # This can happen because the official docs recommend detatching - # the streams to get binary streams. Some code might do this, so - # we need to deal with this case explicitly. - if _is_binary_writer(stream, False): - return stream - - buf = getattr(stream, 'buffer', None) - - # Same situation here; this time we assume that the buffer is - # actually binary in case it's closed. - if buf is not None and _is_binary_writer(buf, True): - return buf - - def _stream_is_misconfigured(stream): - """A stream is misconfigured if its encoding is ASCII.""" - # If the stream does not have an encoding set, we assume it's set - # to ASCII. This appears to happen in certain unittest - # environments. It's not quite clear what the correct behavior is - # but this at least will force Click to recover somehow. - return is_ascii_encoding(getattr(stream, 'encoding', None) or 'ascii') - - def _is_compatible_text_stream(stream, encoding, errors): - stream_encoding = getattr(stream, 'encoding', None) - stream_errors = getattr(stream, 'errors', None) - - # Perfect match. - if stream_encoding == encoding and stream_errors == errors: - return True - - # Otherwise, it's only a compatible stream if we did not ask for - # an encoding. - if encoding is None: - return stream_encoding is not None - - return False - - def _force_correct_text_reader(text_reader, encoding, errors, - force_readable=False): - if _is_binary_reader(text_reader, False): - binary_reader = text_reader - else: - # If there is no target encoding set, we need to verify that the - # reader is not actually misconfigured. - if encoding is None and not _stream_is_misconfigured(text_reader): - return text_reader - - if _is_compatible_text_stream(text_reader, encoding, errors): - return text_reader - - # If the reader has no encoding, we try to find the underlying - # binary reader for it. If that fails because the environment is - # misconfigured, we silently go with the same reader because this - # is too common to happen. In that case, mojibake is better than - # exceptions. - binary_reader = _find_binary_reader(text_reader) - if binary_reader is None: - return text_reader - - # At this point, we default the errors to replace instead of strict - # because nobody handles those errors anyways and at this point - # we're so fundamentally fucked that nothing can repair it. - if errors is None: - errors = 'replace' - return _make_text_stream(binary_reader, encoding, errors, - force_readable=force_readable) - - def _force_correct_text_writer(text_writer, encoding, errors, - force_writable=False): - if _is_binary_writer(text_writer, False): - binary_writer = text_writer - else: - # If there is no target encoding set, we need to verify that the - # writer is not actually misconfigured. - if encoding is None and not _stream_is_misconfigured(text_writer): - return text_writer - - if _is_compatible_text_stream(text_writer, encoding, errors): - return text_writer - - # If the writer has no encoding, we try to find the underlying - # binary writer for it. If that fails because the environment is - # misconfigured, we silently go with the same writer because this - # is too common to happen. In that case, mojibake is better than - # exceptions. - binary_writer = _find_binary_writer(text_writer) - if binary_writer is None: - return text_writer - - # At this point, we default the errors to replace instead of strict - # because nobody handles those errors anyways and at this point - # we're so fundamentally fucked that nothing can repair it. - if errors is None: - errors = 'replace' - return _make_text_stream(binary_writer, encoding, errors, - force_writable=force_writable) - - def get_binary_stdin(): - reader = _find_binary_reader(sys.stdin) - if reader is None: - raise RuntimeError('Was not able to determine binary ' - 'stream for sys.stdin.') - return reader - - def get_binary_stdout(): - writer = _find_binary_writer(sys.stdout) - if writer is None: - raise RuntimeError('Was not able to determine binary ' - 'stream for sys.stdout.') - return writer - - def get_binary_stderr(): - writer = _find_binary_writer(sys.stderr) - if writer is None: - raise RuntimeError('Was not able to determine binary ' - 'stream for sys.stderr.') - return writer - - def get_text_stdin(encoding=None, errors=None): - rv = _get_windows_console_stream(sys.stdin, encoding, errors) - if rv is not None: - return rv - return _force_correct_text_reader(sys.stdin, encoding, errors, - force_readable=True) - - def get_text_stdout(encoding=None, errors=None): - rv = _get_windows_console_stream(sys.stdout, encoding, errors) - if rv is not None: - return rv - return _force_correct_text_writer(sys.stdout, encoding, errors, - force_writable=True) - - def get_text_stderr(encoding=None, errors=None): - rv = _get_windows_console_stream(sys.stderr, encoding, errors) - if rv is not None: - return rv - return _force_correct_text_writer(sys.stderr, encoding, errors, - force_writable=True) - - def filename_to_ui(value): - if isinstance(value, bytes): - value = value.decode(get_filesystem_encoding(), 'replace') - else: - value = value.encode('utf-8', 'surrogateescape') \ - .decode('utf-8', 'replace') - return value + pass + return default + return True -def get_streerror(e, default=None): - if hasattr(e, 'strerror'): - msg = e.strerror +def _find_binary_reader(stream: t.IO) -> t.Optional[t.BinaryIO]: + # We need to figure out if the given stream is already binary. + # This can happen because the official docs recommend detaching + # the streams to get binary streams. Some code might do this, so + # we need to deal with this case explicitly. + if _is_binary_reader(stream, False): + return t.cast(t.BinaryIO, stream) + + buf = getattr(stream, "buffer", None) + + # Same situation here; this time we assume that the buffer is + # actually binary in case it's closed. + if buf is not None and _is_binary_reader(buf, True): + return t.cast(t.BinaryIO, buf) + + return None + + +def _find_binary_writer(stream: t.IO) -> t.Optional[t.BinaryIO]: + # We need to figure out if the given stream is already binary. + # This can happen because the official docs recommend detaching + # the streams to get binary streams. Some code might do this, so + # we need to deal with this case explicitly. + if _is_binary_writer(stream, False): + return t.cast(t.BinaryIO, stream) + + buf = getattr(stream, "buffer", None) + + # Same situation here; this time we assume that the buffer is + # actually binary in case it's closed. + if buf is not None and _is_binary_writer(buf, True): + return t.cast(t.BinaryIO, buf) + + return None + + +def _stream_is_misconfigured(stream: t.TextIO) -> bool: + """A stream is misconfigured if its encoding is ASCII.""" + # If the stream does not have an encoding set, we assume it's set + # to ASCII. This appears to happen in certain unittest + # environments. It's not quite clear what the correct behavior is + # but this at least will force Click to recover somehow. + return is_ascii_encoding(getattr(stream, "encoding", None) or "ascii") + + +def _is_compat_stream_attr(stream: t.TextIO, attr: str, value: t.Optional[str]) -> bool: + """A stream attribute is compatible if it is equal to the + desired value or the desired value is unset and the attribute + has a value. + """ + stream_value = getattr(stream, attr, None) + return stream_value == value or (value is None and stream_value is not None) + + +def _is_compatible_text_stream( + stream: t.TextIO, encoding: t.Optional[str], errors: t.Optional[str] +) -> bool: + """Check if a stream's encoding and errors attributes are + compatible with the desired values. + """ + return _is_compat_stream_attr( + stream, "encoding", encoding + ) and _is_compat_stream_attr(stream, "errors", errors) + + +def _force_correct_text_stream( + text_stream: t.IO, + encoding: t.Optional[str], + errors: t.Optional[str], + is_binary: t.Callable[[t.IO, bool], bool], + find_binary: t.Callable[[t.IO], t.Optional[t.BinaryIO]], + force_readable: bool = False, + force_writable: bool = False, +) -> t.TextIO: + if is_binary(text_stream, False): + binary_reader = t.cast(t.BinaryIO, text_stream) else: - if default is not None: - msg = default - else: - msg = str(e) - if isinstance(msg, bytes): - msg = msg.decode('utf-8', 'replace') - return msg + text_stream = t.cast(t.TextIO, text_stream) + # If the stream looks compatible, and won't default to a + # misconfigured ascii encoding, return it as-is. + if _is_compatible_text_stream(text_stream, encoding, errors) and not ( + encoding is None and _stream_is_misconfigured(text_stream) + ): + return text_stream + + # Otherwise, get the underlying binary reader. + possible_binary_reader = find_binary(text_stream) + + # If that's not possible, silently use the original reader + # and get mojibake instead of exceptions. + if possible_binary_reader is None: + return text_stream + + binary_reader = possible_binary_reader + + # Default errors to replace instead of strict in order to get + # something that works. + if errors is None: + errors = "replace" + + # Wrap the binary stream in a text stream with the correct + # encoding parameters. + return _make_text_stream( + binary_reader, + encoding, + errors, + force_readable=force_readable, + force_writable=force_writable, + ) -def open_stream(filename, mode='r', encoding=None, errors='strict', - atomic=False): +def _force_correct_text_reader( + text_reader: t.IO, + encoding: t.Optional[str], + errors: t.Optional[str], + force_readable: bool = False, +) -> t.TextIO: + return _force_correct_text_stream( + text_reader, + encoding, + errors, + _is_binary_reader, + _find_binary_reader, + force_readable=force_readable, + ) + + +def _force_correct_text_writer( + text_writer: t.IO, + encoding: t.Optional[str], + errors: t.Optional[str], + force_writable: bool = False, +) -> t.TextIO: + return _force_correct_text_stream( + text_writer, + encoding, + errors, + _is_binary_writer, + _find_binary_writer, + force_writable=force_writable, + ) + + +def get_binary_stdin() -> t.BinaryIO: + reader = _find_binary_reader(sys.stdin) + if reader is None: + raise RuntimeError("Was not able to determine binary stream for sys.stdin.") + return reader + + +def get_binary_stdout() -> t.BinaryIO: + writer = _find_binary_writer(sys.stdout) + if writer is None: + raise RuntimeError("Was not able to determine binary stream for sys.stdout.") + return writer + + +def get_binary_stderr() -> t.BinaryIO: + writer = _find_binary_writer(sys.stderr) + if writer is None: + raise RuntimeError("Was not able to determine binary stream for sys.stderr.") + return writer + + +def get_text_stdin( + encoding: t.Optional[str] = None, errors: t.Optional[str] = None +) -> t.TextIO: + rv = _get_windows_console_stream(sys.stdin, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_reader(sys.stdin, encoding, errors, force_readable=True) + + +def get_text_stdout( + encoding: t.Optional[str] = None, errors: t.Optional[str] = None +) -> t.TextIO: + rv = _get_windows_console_stream(sys.stdout, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_writer(sys.stdout, encoding, errors, force_writable=True) + + +def get_text_stderr( + encoding: t.Optional[str] = None, errors: t.Optional[str] = None +) -> t.TextIO: + rv = _get_windows_console_stream(sys.stderr, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_writer(sys.stderr, encoding, errors, force_writable=True) + + +def _wrap_io_open( + file: t.Union[str, os.PathLike, int], + mode: str, + encoding: t.Optional[str], + errors: t.Optional[str], +) -> t.IO: + """Handles not passing ``encoding`` and ``errors`` in binary mode.""" + if "b" in mode: + return open(file, mode) + + return open(file, mode, encoding=encoding, errors=errors) + + +def open_stream( + filename: str, + mode: str = "r", + encoding: t.Optional[str] = None, + errors: t.Optional[str] = "strict", + atomic: bool = False, +) -> t.Tuple[t.IO, bool]: + binary = "b" in mode + # Standard streams first. These are simple because they don't need # special handling for the atomic flag. It's entirely ignored. - if filename == '-': - if any(m in mode for m in ['w', 'a', 'x']): - if 'b' in mode: + if filename == "-": + if any(m in mode for m in ["w", "a", "x"]): + if binary: return get_binary_stdout(), False return get_text_stdout(encoding=encoding, errors=errors), False - if 'b' in mode: + if binary: return get_binary_stdin(), False return get_text_stdin(encoding=encoding, errors=errors), False # Non-atomic writes directly go out through the regular open functions. if not atomic: - if encoding is None: - return open(filename, mode), True - return io.open(filename, mode, encoding=encoding, errors=errors), True + return _wrap_io_open(filename, mode, encoding, errors), True # Some usability stuff for atomic writes - if 'a' in mode: + if "a" in mode: raise ValueError( - 'Appending to an existing file is not supported, because that ' - 'would involve an expensive `copy`-operation to a temporary ' - 'file. Open the file in normal `w`-mode and copy explicitly ' - 'if that\'s what you\'re after.' + "Appending to an existing file is not supported, because that" + " would involve an expensive `copy`-operation to a temporary" + " file. Open the file in normal `w`-mode and copy explicitly" + " if that's what you're after." ) - if 'x' in mode: - raise ValueError('Use the `overwrite`-parameter instead.') - if 'w' not in mode: - raise ValueError('Atomic writes only make sense with `w`-mode.') + if "x" in mode: + raise ValueError("Use the `overwrite`-parameter instead.") + if "w" not in mode: + raise ValueError("Atomic writes only make sense with `w`-mode.") # Atomic writes are more complicated. They work by opening a file # as a proxy in the same folder and then using the fdopen # functionality to wrap it in a Python file. Then we wrap it in an # atomic file that moves the file over on close. - import tempfile - fd, tmp_filename = tempfile.mkstemp(dir=os.path.dirname(filename), - prefix='.__atomic-write') + import errno + import random - if encoding is not None: - f = io.open(fd, mode, encoding=encoding, errors=errors) - else: - f = os.fdopen(fd, mode) + try: + perm: t.Optional[int] = os.stat(filename).st_mode + except OSError: + perm = None - return _AtomicFile(f, tmp_filename, os.path.realpath(filename)), True + flags = os.O_RDWR | os.O_CREAT | os.O_EXCL + + if binary: + flags |= getattr(os, "O_BINARY", 0) + + while True: + tmp_filename = os.path.join( + os.path.dirname(filename), + f".__atomic-write{random.randrange(1 << 32):08x}", + ) + try: + fd = os.open(tmp_filename, flags, 0o666 if perm is None else perm) + break + except OSError as e: + if e.errno == errno.EEXIST or ( + os.name == "nt" + and e.errno == errno.EACCES + and os.path.isdir(e.filename) + and os.access(e.filename, os.W_OK) + ): + continue + raise + + if perm is not None: + os.chmod(tmp_filename, perm) # in case perm includes bits in umask + + f = _wrap_io_open(fd, mode, encoding, errors) + af = _AtomicFile(f, tmp_filename, os.path.realpath(filename)) + return t.cast(t.IO, af), True -# Used in a destructor call, needs extra protection from interpreter cleanup. -if hasattr(os, 'replace'): - _replace = os.replace - _can_replace = True -else: - _replace = os.rename - _can_replace = not WIN - - -class _AtomicFile(object): - - def __init__(self, f, tmp_filename, real_filename): +class _AtomicFile: + def __init__(self, f: t.IO, tmp_filename: str, real_filename: str) -> None: self._f = f self._tmp_filename = tmp_filename self._real_filename = real_filename self.closed = False @property - def name(self): + def name(self) -> str: return self._real_filename - def close(self, delete=False): + def close(self, delete: bool = False) -> None: if self.closed: return self._f.close() - if not _can_replace: - try: - os.remove(self._real_filename) - except OSError: - pass - _replace(self._tmp_filename, self._real_filename) + os.replace(self._tmp_filename, self._real_filename) self.closed = True - def __getattr__(self, name): + def __getattr__(self, name: str) -> t.Any: return getattr(self._f, name) - def __enter__(self): + def __enter__(self) -> "_AtomicFile": return self - def __exit__(self, exc_type, exc_value, tb): + def __exit__(self, exc_type, exc_value, tb): # type: ignore self.close(delete=exc_type is not None) - def __repr__(self): + def __repr__(self) -> str: return repr(self._f) -auto_wrap_for_ansi = None -colorama = None -get_winterm_size = None +def strip_ansi(value: str) -> str: + return _ansi_re.sub("", value) -def strip_ansi(value): - return _ansi_re.sub('', value) +def _is_jupyter_kernel_output(stream: t.IO) -> bool: + while isinstance(stream, (_FixupStream, _NonClosingTextIOWrapper)): + stream = stream._stream + + return stream.__class__.__module__.startswith("ipykernel.") -def should_strip_ansi(stream=None, color=None): +def should_strip_ansi( + stream: t.Optional[t.IO] = None, color: t.Optional[bool] = None +) -> bool: if color is None: if stream is None: stream = sys.stdin - return not isatty(stream) + return not isatty(stream) and not _is_jupyter_kernel_output(stream) return not color -# If we're on Windows, we provide transparent integration through -# colorama. This will make ANSI colors through the echo function -# work automatically. -if WIN: - # Windows has a smaller terminal - DEFAULT_COLUMNS = 79 +# On Windows, wrap the output streams with colorama to support ANSI +# color codes. +# NOTE: double check is needed so mypy does not analyze this on Linux +if sys.platform.startswith("win") and WIN: + from ._winconsole import _get_windows_console_stream - from ._winconsole import _get_windows_console_stream, _wrap_std_stream - - def _get_argv_encoding(): + def _get_argv_encoding() -> str: import locale + return locale.getpreferredencoding() - if PY2: - def raw_input(prompt=''): - sys.stderr.flush() - if prompt: - stdout = _default_text_stdout() - stdout.write(prompt) - stdin = _default_text_stdin() - return stdin.readline().rstrip('\r\n') + _ansi_stream_wrappers: t.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary() - try: - import colorama - except ImportError: - pass - else: - _ansi_stream_wrappers = WeakKeyDictionary() + def auto_wrap_for_ansi( + stream: t.TextIO, color: t.Optional[bool] = None + ) -> t.TextIO: + """Support ANSI color and style codes on Windows by wrapping a + stream with colorama. + """ + try: + cached = _ansi_stream_wrappers.get(stream) + except Exception: + cached = None - def auto_wrap_for_ansi(stream, color=None): - """This function wraps a stream so that calls through colorama - are issued to the win32 console API to recolor on demand. It - also ensures to reset the colors if a write call is interrupted - to not destroy the console afterwards. - """ + if cached is not None: + return cached + + import pipenv.vendor.colorama as colorama + + strip = should_strip_ansi(stream, color) + ansi_wrapper = colorama.AnsiToWin32(stream, strip=strip) + rv = t.cast(t.TextIO, ansi_wrapper.stream) + _write = rv.write + + def _safe_write(s): try: - cached = _ansi_stream_wrappers.get(stream) - except Exception: - cached = None - if cached is not None: - return cached - strip = should_strip_ansi(stream, color) - ansi_wrapper = colorama.AnsiToWin32(stream, strip=strip) - rv = ansi_wrapper.stream - _write = rv.write + return _write(s) + except BaseException: + ansi_wrapper.reset_all() + raise - def _safe_write(s): - try: - return _write(s) - except: - ansi_wrapper.reset_all() - raise + rv.write = _safe_write + + try: + _ansi_stream_wrappers[stream] = rv + except Exception: + pass + + return rv - rv.write = _safe_write - try: - _ansi_stream_wrappers[stream] = rv - except Exception: - pass - return rv - def get_winterm_size(): - win = colorama.win32.GetConsoleScreenBufferInfo( - colorama.win32.STDOUT).srWindow - return win.Right - win.Left, win.Bottom - win.Top else: - def _get_argv_encoding(): - return getattr(sys.stdin, 'encoding', None) or get_filesystem_encoding() - _get_windows_console_stream = lambda *x: None - _wrap_std_stream = lambda *x: None + def _get_argv_encoding() -> str: + return getattr(sys.stdin, "encoding", None) or get_filesystem_encoding() + + def _get_windows_console_stream( + f: t.TextIO, encoding: t.Optional[str], errors: t.Optional[str] + ) -> t.Optional[t.TextIO]: + return None -def term_len(x): +def term_len(x: str) -> int: return len(strip_ansi(x)) -def isatty(stream): +def isatty(stream: t.IO) -> bool: try: return stream.isatty() except Exception: return False -def _make_cached_stream_func(src_func, wrapper_func): - cache = WeakKeyDictionary() - def func(): +def _make_cached_stream_func( + src_func: t.Callable[[], t.TextIO], wrapper_func: t.Callable[[], t.TextIO] +) -> t.Callable[[], t.TextIO]: + cache: t.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary() + + def func() -> t.TextIO: stream = src_func() try: rv = cache.get(stream) @@ -674,30 +599,29 @@ def _make_cached_stream_func(src_func, wrapper_func): return rv rv = wrapper_func() try: - stream = src_func() # In case wrapper_func() modified the stream cache[stream] = rv except Exception: pass return rv + return func -_default_text_stdin = _make_cached_stream_func( - lambda: sys.stdin, get_text_stdin) -_default_text_stdout = _make_cached_stream_func( - lambda: sys.stdout, get_text_stdout) -_default_text_stderr = _make_cached_stream_func( - lambda: sys.stderr, get_text_stderr) +_default_text_stdin = _make_cached_stream_func(lambda: sys.stdin, get_text_stdin) +_default_text_stdout = _make_cached_stream_func(lambda: sys.stdout, get_text_stdout) +_default_text_stderr = _make_cached_stream_func(lambda: sys.stderr, get_text_stderr) -binary_streams = { - 'stdin': get_binary_stdin, - 'stdout': get_binary_stdout, - 'stderr': get_binary_stderr, +binary_streams: t.Mapping[str, t.Callable[[], t.BinaryIO]] = { + "stdin": get_binary_stdin, + "stdout": get_binary_stdout, + "stderr": get_binary_stderr, } -text_streams = { - 'stdin': get_text_stdin, - 'stdout': get_text_stdout, - 'stderr': get_text_stderr, +text_streams: t.Mapping[ + str, t.Callable[[t.Optional[str], t.Optional[str]], t.TextIO] +] = { + "stdin": get_text_stdin, + "stdout": get_text_stdout, + "stderr": get_text_stderr, } diff --git a/pipenv/vendor/click/_termui_impl.py b/pipenv/vendor/click/_termui_impl.py index 00a8e5ef..39c1d08f 100644 --- a/pipenv/vendor/click/_termui_impl.py +++ b/pipenv/vendor/click/_termui_impl.py @@ -1,62 +1,56 @@ -# -*- coding: utf-8 -*- """ -click._termui_impl -~~~~~~~~~~~~~~~~~~ - This module contains implementations for the termui module. To keep the import time of Click down, some infrequently used functionality is placed in this module and only imported as needed. - -:copyright: © 2014 by the Pallets team. -:license: BSD, see LICENSE.rst for more details. """ - +import contextlib +import math import os import sys import time -import math -import contextlib -from ._compat import _default_text_stdout, range_type, PY2, isatty, \ - open_stream, strip_ansi, term_len, get_best_encoding, WIN, int_types, \ - CYGWIN -from .utils import echo +import typing as t +from gettext import gettext as _ + +from ._compat import _default_text_stdout +from ._compat import CYGWIN +from ._compat import get_best_encoding +from ._compat import isatty +from ._compat import open_stream +from ._compat import strip_ansi +from ._compat import term_len +from ._compat import WIN from .exceptions import ClickException +from .utils import echo +V = t.TypeVar("V") -if os.name == 'nt': - BEFORE_BAR = '\r' - AFTER_BAR = '\n' +if os.name == "nt": + BEFORE_BAR = "\r" + AFTER_BAR = "\n" else: - BEFORE_BAR = '\r\033[?25l' - AFTER_BAR = '\033[?25h\n' + BEFORE_BAR = "\r\033[?25l" + AFTER_BAR = "\033[?25h\n" -def _length_hint(obj): - """Returns the length hint of an object.""" - try: - return len(obj) - except (AttributeError, TypeError): - try: - get_hint = type(obj).__length_hint__ - except AttributeError: - return None - try: - hint = get_hint(obj) - except TypeError: - return None - if hint is NotImplemented or \ - not isinstance(hint, int_types) or \ - hint < 0: - return None - return hint - - -class ProgressBar(object): - - def __init__(self, iterable, length=None, fill_char='#', empty_char=' ', - bar_template='%(bar)s', info_sep=' ', show_eta=True, - show_percent=None, show_pos=False, item_show_func=None, - label=None, file=None, color=None, width=30): +class ProgressBar(t.Generic[V]): + def __init__( + self, + iterable: t.Optional[t.Iterable[V]], + length: t.Optional[int] = None, + fill_char: str = "#", + empty_char: str = " ", + bar_template: str = "%(bar)s", + info_sep: str = " ", + show_eta: bool = True, + show_percent: t.Optional[bool] = None, + show_pos: bool = False, + item_show_func: t.Optional[t.Callable[[t.Optional[V]], t.Optional[str]]] = None, + label: t.Optional[str] = None, + file: t.Optional[t.TextIO] = None, + color: t.Optional[bool] = None, + update_min_steps: int = 1, + width: int = 30, + ) -> None: self.fill_char = fill_char self.empty_char = empty_char self.bar_template = bar_template @@ -65,77 +59,87 @@ class ProgressBar(object): self.show_percent = show_percent self.show_pos = show_pos self.item_show_func = item_show_func - self.label = label or '' + self.label = label or "" if file is None: file = _default_text_stdout() self.file = file self.color = color + self.update_min_steps = update_min_steps + self._completed_intervals = 0 self.width = width self.autowidth = width == 0 if length is None: - length = _length_hint(iterable) + from operator import length_hint + + length = length_hint(iterable, -1) + + if length == -1: + length = None if iterable is None: if length is None: - raise TypeError('iterable or length is required') - iterable = range_type(length) + raise TypeError("iterable or length is required") + iterable = t.cast(t.Iterable[V], range(length)) self.iter = iter(iterable) self.length = length - self.length_known = length is not None self.pos = 0 - self.avg = [] + self.avg: t.List[float] = [] self.start = self.last_eta = time.time() self.eta_known = False self.finished = False - self.max_width = None + self.max_width: t.Optional[int] = None self.entered = False - self.current_item = None + self.current_item: t.Optional[V] = None self.is_hidden = not isatty(self.file) - self._last_line = None - self.short_limit = 0.5 + self._last_line: t.Optional[str] = None - def __enter__(self): + def __enter__(self) -> "ProgressBar": self.entered = True self.render_progress() return self - def __exit__(self, exc_type, exc_value, tb): + def __exit__(self, exc_type, exc_value, tb): # type: ignore self.render_finish() - def __iter__(self): + def __iter__(self) -> t.Iterator[V]: if not self.entered: - raise RuntimeError('You need to use progress bars in a with block.') + raise RuntimeError("You need to use progress bars in a with block.") self.render_progress() return self.generator() - def is_fast(self): - return time.time() - self.start <= self.short_limit + def __next__(self) -> V: + # Iteration is defined in terms of a generator function, + # returned by iter(self); use that to define next(). This works + # because `self.iter` is an iterable consumed by that generator, + # so it is re-entry safe. Calling `next(self.generator())` + # twice works and does "what you want". + return next(iter(self)) - def render_finish(self): - if self.is_hidden or self.is_fast(): + def render_finish(self) -> None: + if self.is_hidden: return self.file.write(AFTER_BAR) self.file.flush() @property - def pct(self): + def pct(self) -> float: if self.finished: return 1.0 - return min(self.pos / (float(self.length) or 1), 1.0) + return min(self.pos / (float(self.length or 1) or 1), 1.0) @property - def time_per_iteration(self): + def time_per_iteration(self) -> float: if not self.avg: return 0.0 return sum(self.avg) / float(len(self.avg)) @property - def eta(self): - if self.length_known and not self.finished: + def eta(self) -> float: + if self.length is not None and not self.finished: return self.time_per_iteration * (self.length - self.pos) return 0.0 - def format_eta(self): + def format_eta(self) -> str: if self.eta_known: t = int(self.eta) seconds = t % 60 @@ -145,41 +149,44 @@ class ProgressBar(object): hours = t % 24 t //= 24 if t > 0: - days = t - return '%dd %02d:%02d:%02d' % (days, hours, minutes, seconds) + return f"{t}d {hours:02}:{minutes:02}:{seconds:02}" else: - return '%02d:%02d:%02d' % (hours, minutes, seconds) - return '' + return f"{hours:02}:{minutes:02}:{seconds:02}" + return "" - def format_pos(self): + def format_pos(self) -> str: pos = str(self.pos) - if self.length_known: - pos += '/%s' % self.length + if self.length is not None: + pos += f"/{self.length}" return pos - def format_pct(self): - return ('% 4d%%' % int(self.pct * 100))[1:] + def format_pct(self) -> str: + return f"{int(self.pct * 100): 4}%"[1:] - def format_bar(self): - if self.length_known: + def format_bar(self) -> str: + if self.length is not None: bar_length = int(self.pct * self.width) bar = self.fill_char * bar_length bar += self.empty_char * (self.width - bar_length) elif self.finished: bar = self.fill_char * self.width else: - bar = list(self.empty_char * (self.width or 1)) + chars = list(self.empty_char * (self.width or 1)) if self.time_per_iteration != 0: - bar[int((math.cos(self.pos * self.time_per_iteration) - / 2.0 + 0.5) * self.width)] = self.fill_char - bar = ''.join(bar) + chars[ + int( + (math.cos(self.pos * self.time_per_iteration) / 2.0 + 0.5) + * self.width + ) + ] = self.fill_char + bar = "".join(chars) return bar - def format_progress_line(self): + def format_progress_line(self) -> str: show_percent = self.show_percent info_bits = [] - if self.length_known and show_percent is None: + if self.length is not None and show_percent is None: show_percent = not self.show_pos if self.show_pos: @@ -193,16 +200,25 @@ class ProgressBar(object): if item_info is not None: info_bits.append(item_info) - return (self.bar_template % { - 'label': self.label, - 'bar': self.format_bar(), - 'info': self.info_sep.join(info_bits) - }).rstrip() + return ( + self.bar_template + % { + "label": self.label, + "bar": self.format_bar(), + "info": self.info_sep.join(info_bits), + } + ).rstrip() - def render_progress(self): - from .termui import get_terminal_size + def render_progress(self) -> None: + import shutil if self.is_hidden: + # Only output the label as it changes if the output is not a + # TTY. Use file=stderr if you expect to be piping stdout. + if self._last_line != self.label: + self._last_line = self.label + echo(self.label, file=self.file, color=self.color) + return buf = [] @@ -211,10 +227,10 @@ class ProgressBar(object): old_width = self.width self.width = 0 clutter_length = term_len(self.format_progress_line()) - new_width = max(0, get_terminal_size()[0] - clutter_length) + new_width = max(0, shutil.get_terminal_size().columns - clutter_length) if new_width < old_width: buf.append(BEFORE_BAR) - buf.append(' ' * self.max_width) + buf.append(" " * self.max_width) # type: ignore self.max_width = new_width self.width = new_width @@ -229,18 +245,18 @@ class ProgressBar(object): self.max_width = line_len buf.append(line) - buf.append(' ' * (clear_width - line_len)) - line = ''.join(buf) + buf.append(" " * (clear_width - line_len)) + line = "".join(buf) # Render the line only if it changed. - if line != self._last_line and not self.is_fast(): + if line != self._last_line: self._last_line = line echo(line, file=self.file, color=self.color, nl=False) self.file.flush() - def make_step(self, n_steps): + def make_step(self, n_steps: int) -> None: self.pos += n_steps - if self.length_known and self.pos >= self.length: + if self.length is not None and self.pos >= self.length: self.finished = True if (time.time() - self.last_eta) < 1.0: @@ -258,97 +274,134 @@ class ProgressBar(object): self.avg = self.avg[-6:] + [step] - self.eta_known = self.length_known + self.eta_known = self.length is not None - def update(self, n_steps): - self.make_step(n_steps) - self.render_progress() + def update(self, n_steps: int, current_item: t.Optional[V] = None) -> None: + """Update the progress bar by advancing a specified number of + steps, and optionally set the ``current_item`` for this new + position. - def finish(self): - self.eta_known = 0 + :param n_steps: Number of steps to advance. + :param current_item: Optional item to set as ``current_item`` + for the updated position. + + .. versionchanged:: 8.0 + Added the ``current_item`` optional parameter. + + .. versionchanged:: 8.0 + Only render when the number of steps meets the + ``update_min_steps`` threshold. + """ + if current_item is not None: + self.current_item = current_item + + self._completed_intervals += n_steps + + if self._completed_intervals >= self.update_min_steps: + self.make_step(self._completed_intervals) + self.render_progress() + self._completed_intervals = 0 + + def finish(self) -> None: + self.eta_known = False self.current_item = None self.finished = True - def generator(self): - """ - Returns a generator which yields the items added to the bar during - construction, and updates the progress bar *after* the yielded block - returns. + def generator(self) -> t.Iterator[V]: + """Return a generator which yields the items added to the bar + during construction, and updates the progress bar *after* the + yielded block returns. """ + # WARNING: the iterator interface for `ProgressBar` relies on + # this and only works because this is a simple generator which + # doesn't create or manage additional state. If this function + # changes, the impact should be evaluated both against + # `iter(bar)` and `next(bar)`. `next()` in particular may call + # `self.generator()` repeatedly, and this must remain safe in + # order for that interface to work. if not self.entered: - raise RuntimeError('You need to use progress bars in a with block.') + raise RuntimeError("You need to use progress bars in a with block.") if self.is_hidden: - for rv in self.iter: - yield rv + yield from self.iter else: for rv in self.iter: self.current_item = rv + + # This allows show_item_func to be updated before the + # item is processed. Only trigger at the beginning of + # the update interval. + if self._completed_intervals == 0: + self.render_progress() + yield rv self.update(1) + self.finish() self.render_progress() -def pager(generator, color=None): +def pager(generator: t.Iterable[str], color: t.Optional[bool] = None) -> None: """Decide what method to use for paging through text.""" stdout = _default_text_stdout() if not isatty(sys.stdin) or not isatty(stdout): return _nullpager(stdout, generator, color) - pager_cmd = (os.environ.get('PAGER', None) or '').strip() + pager_cmd = (os.environ.get("PAGER", None) or "").strip() if pager_cmd: if WIN: return _tempfilepager(generator, pager_cmd, color) return _pipepager(generator, pager_cmd, color) - if os.environ.get('TERM') in ('dumb', 'emacs'): + if os.environ.get("TERM") in ("dumb", "emacs"): return _nullpager(stdout, generator, color) - if WIN or sys.platform.startswith('os2'): - return _tempfilepager(generator, 'more <', color) - if hasattr(os, 'system') and os.system('(less) 2>/dev/null') == 0: - return _pipepager(generator, 'less', color) + if WIN or sys.platform.startswith("os2"): + return _tempfilepager(generator, "more <", color) + if hasattr(os, "system") and os.system("(less) 2>/dev/null") == 0: + return _pipepager(generator, "less", color) import tempfile + fd, filename = tempfile.mkstemp() os.close(fd) try: - if hasattr(os, 'system') and os.system('more "%s"' % filename) == 0: - return _pipepager(generator, 'more', color) + if hasattr(os, "system") and os.system(f'more "{filename}"') == 0: + return _pipepager(generator, "more", color) return _nullpager(stdout, generator, color) finally: os.unlink(filename) -def _pipepager(generator, cmd, color): +def _pipepager(generator: t.Iterable[str], cmd: str, color: t.Optional[bool]) -> None: """Page through text by feeding it to another program. Invoking a pager through this might support colors. """ import subprocess + env = dict(os.environ) # If we're piping to less we might support colors under the # condition that - cmd_detail = cmd.rsplit('/', 1)[-1].split() - if color is None and cmd_detail[0] == 'less': - less_flags = os.environ.get('LESS', '') + ' '.join(cmd_detail[1:]) + cmd_detail = cmd.rsplit("/", 1)[-1].split() + if color is None and cmd_detail[0] == "less": + less_flags = f"{os.environ.get('LESS', '')}{' '.join(cmd_detail[1:])}" if not less_flags: - env['LESS'] = '-R' + env["LESS"] = "-R" color = True - elif 'r' in less_flags or 'R' in less_flags: + elif "r" in less_flags or "R" in less_flags: color = True - c = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, - env=env) - encoding = get_best_encoding(c.stdin) + c = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, env=env) + stdin = t.cast(t.BinaryIO, c.stdin) + encoding = get_best_encoding(stdin) try: for text in generator: if not color: text = strip_ansi(text) - c.stdin.write(text.encode(encoding, 'replace')) - except (IOError, KeyboardInterrupt): + stdin.write(text.encode(encoding, "replace")) + except (OSError, KeyboardInterrupt): pass else: - c.stdin.close() + stdin.close() # Less doesn't respect ^C, but catches it for its own UI purposes (aborting # search or other commands inside less). @@ -367,24 +420,30 @@ def _pipepager(generator, cmd, color): break -def _tempfilepager(generator, cmd, color): +def _tempfilepager( + generator: t.Iterable[str], cmd: str, color: t.Optional[bool] +) -> None: """Page through text by invoking a program on a temporary file.""" import tempfile - filename = tempfile.mktemp() + + fd, filename = tempfile.mkstemp() # TODO: This never terminates if the passed generator never terminates. text = "".join(generator) if not color: text = strip_ansi(text) encoding = get_best_encoding(sys.stdout) - with open_stream(filename, 'wb')[0] as f: + with open_stream(filename, "wb")[0] as f: f.write(text.encode(encoding)) try: - os.system(cmd + ' "' + filename + '"') + os.system(f'{cmd} "{filename}"') finally: + os.close(fd) os.unlink(filename) -def _nullpager(stream, generator, color): +def _nullpager( + stream: t.TextIO, generator: t.Iterable[str], color: t.Optional[bool] +) -> None: """Simply print unformatted text. This is the ultimate fallback.""" for text in generator: if not color: @@ -392,159 +451,184 @@ def _nullpager(stream, generator, color): stream.write(text) -class Editor(object): - - def __init__(self, editor=None, env=None, require_save=True, - extension='.txt'): +class Editor: + def __init__( + self, + editor: t.Optional[str] = None, + env: t.Optional[t.Mapping[str, str]] = None, + require_save: bool = True, + extension: str = ".txt", + ) -> None: self.editor = editor self.env = env self.require_save = require_save self.extension = extension - def get_editor(self): + def get_editor(self) -> str: if self.editor is not None: return self.editor - for key in 'VISUAL', 'EDITOR': + for key in "VISUAL", "EDITOR": rv = os.environ.get(key) if rv: return rv if WIN: - return 'notepad' - for editor in 'vim', 'nano': - if os.system('which %s >/dev/null 2>&1' % editor) == 0: + return "notepad" + for editor in "sensible-editor", "vim", "nano": + if os.system(f"which {editor} >/dev/null 2>&1") == 0: return editor - return 'vi' + return "vi" - def edit_file(self, filename): + def edit_file(self, filename: str) -> None: import subprocess + editor = self.get_editor() + environ: t.Optional[t.Dict[str, str]] = None + if self.env: environ = os.environ.copy() environ.update(self.env) - else: - environ = None + try: - c = subprocess.Popen('%s "%s"' % (editor, filename), - env=environ, shell=True) + c = subprocess.Popen(f'{editor} "{filename}"', env=environ, shell=True) exit_code = c.wait() if exit_code != 0: - raise ClickException('%s: Editing failed!' % editor) + raise ClickException( + _("{editor}: Editing failed").format(editor=editor) + ) except OSError as e: - raise ClickException('%s: Editing failed: %s' % (editor, e)) + raise ClickException( + _("{editor}: Editing failed: {e}").format(editor=editor, e=e) + ) from e - def edit(self, text): + def edit(self, text: t.Optional[t.AnyStr]) -> t.Optional[t.AnyStr]: import tempfile - text = text or '' - if text and not text.endswith('\n'): - text += '\n' + if not text: + data = b"" + elif isinstance(text, (bytes, bytearray)): + data = text + else: + if text and not text.endswith("\n"): + text += "\n" - fd, name = tempfile.mkstemp(prefix='editor-', suffix=self.extension) - try: if WIN: - encoding = 'utf-8-sig' - text = text.replace('\n', '\r\n') + data = text.replace("\n", "\r\n").encode("utf-8-sig") else: - encoding = 'utf-8' - text = text.encode(encoding) + data = text.encode("utf-8") - f = os.fdopen(fd, 'wb') - f.write(text) - f.close() + fd, name = tempfile.mkstemp(prefix="editor-", suffix=self.extension) + f: t.BinaryIO + + try: + with os.fdopen(fd, "wb") as f: + f.write(data) + + # If the filesystem resolution is 1 second, like Mac OS + # 10.12 Extended, or 2 seconds, like FAT32, and the editor + # closes very fast, require_save can fail. Set the modified + # time to be 2 seconds in the past to work around this. + os.utime(name, (os.path.getatime(name), os.path.getmtime(name) - 2)) + # Depending on the resolution, the exact value might not be + # recorded, so get the new recorded value. timestamp = os.path.getmtime(name) self.edit_file(name) - if self.require_save \ - and os.path.getmtime(name) == timestamp: + if self.require_save and os.path.getmtime(name) == timestamp: return None - f = open(name, 'rb') - try: + with open(name, "rb") as f: rv = f.read() - finally: - f.close() - return rv.decode('utf-8-sig').replace('\r\n', '\n') + + if isinstance(text, (bytes, bytearray)): + return rv + + return rv.decode("utf-8-sig").replace("\r\n", "\n") # type: ignore finally: os.unlink(name) -def open_url(url, wait=False, locate=False): +def open_url(url: str, wait: bool = False, locate: bool = False) -> int: import subprocess - def _unquote_file(url): - try: - import urllib - except ImportError: - import urllib - if url.startswith('file://'): - url = urllib.unquote(url[7:]) + def _unquote_file(url: str) -> str: + from urllib.parse import unquote + + if url.startswith("file://"): + url = unquote(url[7:]) + return url - if sys.platform == 'darwin': - args = ['open'] + if sys.platform == "darwin": + args = ["open"] if wait: - args.append('-W') + args.append("-W") if locate: - args.append('-R') + args.append("-R") args.append(_unquote_file(url)) - null = open('/dev/null', 'w') + null = open("/dev/null", "w") try: return subprocess.Popen(args, stderr=null).wait() finally: null.close() elif WIN: if locate: - url = _unquote_file(url) - args = 'explorer /select,"%s"' % _unquote_file( - url.replace('"', '')) + url = _unquote_file(url.replace('"', "")) + args = f'explorer /select,"{url}"' else: - args = 'start %s "" "%s"' % ( - wait and '/WAIT' or '', url.replace('"', '')) + url = url.replace('"', "") + wait_str = "/WAIT" if wait else "" + args = f'start {wait_str} "" "{url}"' return os.system(args) elif CYGWIN: if locate: - url = _unquote_file(url) - args = 'cygstart "%s"' % (os.path.dirname(url).replace('"', '')) + url = os.path.dirname(_unquote_file(url).replace('"', "")) + args = f'cygstart "{url}"' else: - args = 'cygstart %s "%s"' % ( - wait and '-w' or '', url.replace('"', '')) + url = url.replace('"', "") + wait_str = "-w" if wait else "" + args = f'cygstart {wait_str} "{url}"' return os.system(args) try: if locate: - url = os.path.dirname(_unquote_file(url)) or '.' + url = os.path.dirname(_unquote_file(url)) or "." else: url = _unquote_file(url) - c = subprocess.Popen(['xdg-open', url]) + c = subprocess.Popen(["xdg-open", url]) if wait: return c.wait() return 0 except OSError: - if url.startswith(('http://', 'https://')) and not locate and not wait: + if url.startswith(("http://", "https://")) and not locate and not wait: import webbrowser + webbrowser.open(url) return 0 return 1 -def _translate_ch_to_exc(ch): - if ch == u'\x03': +def _translate_ch_to_exc(ch: str) -> t.Optional[BaseException]: + if ch == "\x03": raise KeyboardInterrupt() - if ch == u'\x04' and not WIN: # Unix-like, Ctrl+D + + if ch == "\x04" and not WIN: # Unix-like, Ctrl+D raise EOFError() - if ch == u'\x1a' and WIN: # Windows, Ctrl+Z + + if ch == "\x1a" and WIN: # Windows, Ctrl+Z raise EOFError() + return None + if WIN: import msvcrt @contextlib.contextmanager - def raw_terminal(): - yield + def raw_terminal() -> t.Iterator[int]: + yield -1 - def getchar(echo): + def getchar(echo: bool) -> str: # The function `getch` will return a bytes object corresponding to # the pressed character. Since Windows 10 build 1803, it will also # return \x00 when called a second time after pressing a regular key. @@ -574,48 +658,61 @@ if WIN: # # Anyway, Click doesn't claim to do this Right(tm), and using `getwch` # is doing the right thing in more situations than with `getch`. + func: t.Callable[[], str] + if echo: - func = msvcrt.getwche + func = msvcrt.getwche # type: ignore else: - func = msvcrt.getwch + func = msvcrt.getwch # type: ignore rv = func() - if rv in (u'\x00', u'\xe0'): + + if rv in ("\x00", "\xe0"): # \x00 and \xe0 are control characters that indicate special key, # see above. rv += func() + _translate_ch_to_exc(rv) return rv + + else: import tty import termios @contextlib.contextmanager - def raw_terminal(): + def raw_terminal() -> t.Iterator[int]: + f: t.Optional[t.TextIO] + fd: int + if not isatty(sys.stdin): - f = open('/dev/tty') + f = open("/dev/tty") fd = f.fileno() else: fd = sys.stdin.fileno() f = None + try: old_settings = termios.tcgetattr(fd) + try: tty.setraw(fd) yield fd finally: termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) sys.stdout.flush() + if f is not None: f.close() except termios.error: pass - def getchar(echo): + def getchar(echo: bool) -> str: with raw_terminal() as fd: - ch = os.read(fd, 32) - ch = ch.decode(get_best_encoding(sys.stdin), 'replace') + ch = os.read(fd, 32).decode(get_best_encoding(sys.stdin), "replace") + if echo and isatty(sys.stdout): sys.stdout.write(ch) + _translate_ch_to_exc(ch) return ch diff --git a/pipenv/vendor/click/_textwrap.py b/pipenv/vendor/click/_textwrap.py index 7e776031..b47dcbd4 100644 --- a/pipenv/vendor/click/_textwrap.py +++ b/pipenv/vendor/click/_textwrap.py @@ -1,10 +1,16 @@ import textwrap +import typing as t from contextlib import contextmanager class TextWrapper(textwrap.TextWrapper): - - def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width): + def _handle_long_word( + self, + reversed_chunks: t.List[str], + cur_line: t.List[str], + cur_len: int, + width: int, + ) -> None: space_left = max(width - cur_len, 1) if self.break_long_words: @@ -17,22 +23,27 @@ class TextWrapper(textwrap.TextWrapper): cur_line.append(reversed_chunks.pop()) @contextmanager - def extra_indent(self, indent): + def extra_indent(self, indent: str) -> t.Iterator[None]: old_initial_indent = self.initial_indent old_subsequent_indent = self.subsequent_indent self.initial_indent += indent self.subsequent_indent += indent + try: yield finally: self.initial_indent = old_initial_indent self.subsequent_indent = old_subsequent_indent - def indent_only(self, text): + def indent_only(self, text: str) -> str: rv = [] + for idx, line in enumerate(text.splitlines()): indent = self.initial_indent + if idx > 0: indent = self.subsequent_indent - rv.append(indent + line) - return '\n'.join(rv) + + rv.append(f"{indent}{line}") + + return "\n".join(rv) diff --git a/pipenv/vendor/click/_unicodefun.py b/pipenv/vendor/click/_unicodefun.py index 620edff3..9cb30c38 100644 --- a/pipenv/vendor/click/_unicodefun.py +++ b/pipenv/vendor/click/_unicodefun.py @@ -1,125 +1,100 @@ -import os -import sys import codecs - -from ._compat import PY2 +import os +from gettext import gettext as _ -# If someone wants to vendor click, we want to ensure the -# correct package is discovered. Ideally we could use a -# relative import here but unfortunately Python does not -# support that. -click = sys.modules[__name__.rsplit('.', 1)[0]] - - -def _find_unicode_literals_frame(): - import __future__ - if not hasattr(sys, '_getframe'): # not all Python implementations have it - return 0 - frm = sys._getframe(1) - idx = 1 - while frm is not None: - if frm.f_globals.get('__name__', '').startswith('click.'): - frm = frm.f_back - idx += 1 - elif frm.f_code.co_flags & __future__.unicode_literals.compiler_flag: - return idx - else: - break - return 0 - - -def _check_for_unicode_literals(): - if not __debug__: - return - if not PY2 or click.disable_unicode_literals_warning: - return - bad_frame = _find_unicode_literals_frame() - if bad_frame <= 0: - return - from warnings import warn - warn(Warning('Click detected the use of the unicode_literals ' - '__future__ import. This is heavily discouraged ' - 'because it can introduce subtle bugs in your ' - 'code. You should instead use explicit u"" literals ' - 'for your unicode strings. For more information see ' - 'https://click.palletsprojects.com/python3/'), - stacklevel=bad_frame) - - -def _verify_python3_env(): - """Ensures that the environment is good for unicode on Python 3.""" - if PY2: - return +def _verify_python_env() -> None: + """Ensures that the environment is good for Unicode.""" try: - import locale - fs_enc = codecs.lookup(locale.getpreferredencoding()).name + from locale import getpreferredencoding + + fs_enc = codecs.lookup(getpreferredencoding()).name except Exception: - fs_enc = 'ascii' - if fs_enc != 'ascii': + fs_enc = "ascii" + + if fs_enc != "ascii": return - extra = '' - if os.name == 'posix': + extra = [ + _( + "Click will abort further execution because Python was" + " configured to use ASCII as encoding for the environment." + " Consult https://click.palletsprojects.com/unicode-support/" + " for mitigation steps." + ) + ] + + if os.name == "posix": import subprocess + try: - rv = subprocess.Popen(['locale', '-a'], stdout=subprocess.PIPE, - stderr=subprocess.PIPE).communicate()[0] + rv = subprocess.Popen( + ["locale", "-a"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + encoding="ascii", + errors="replace", + ).communicate()[0] except OSError: - rv = b'' + rv = "" + good_locales = set() has_c_utf8 = False - # Make sure we're operating on text here. - if isinstance(rv, bytes): - rv = rv.decode('ascii', 'replace') - for line in rv.splitlines(): locale = line.strip() - if locale.lower().endswith(('.utf-8', '.utf8')): + + if locale.lower().endswith((".utf-8", ".utf8")): good_locales.add(locale) - if locale.lower() in ('c.utf8', 'c.utf-8'): + + if locale.lower() in ("c.utf8", "c.utf-8"): has_c_utf8 = True - extra += '\n\n' if not good_locales: - extra += ( - 'Additional information: on this system no suitable UTF-8\n' - 'locales were discovered. This most likely requires resolving\n' - 'by reconfiguring the locale system.' + extra.append( + _( + "Additional information: on this system no suitable" + " UTF-8 locales were discovered. This most likely" + " requires resolving by reconfiguring the locale" + " system." + ) ) elif has_c_utf8: - extra += ( - 'This system supports the C.UTF-8 locale which is recommended.\n' - 'You might be able to resolve your issue by exporting the\n' - 'following environment variables:\n\n' - ' export LC_ALL=C.UTF-8\n' - ' export LANG=C.UTF-8' + extra.append( + _( + "This system supports the C.UTF-8 locale which is" + " recommended. You might be able to resolve your" + " issue by exporting the following environment" + " variables:" + ) ) + extra.append(" export LC_ALL=C.UTF-8\n export LANG=C.UTF-8") else: - extra += ( - 'This system lists a couple of UTF-8 supporting locales that\n' - 'you can pick from. The following suitable locales were\n' - 'discovered: %s' - ) % ', '.join(sorted(good_locales)) + extra.append( + _( + "This system lists some UTF-8 supporting locales" + " that you can pick from. The following suitable" + " locales were discovered: {locales}" + ).format(locales=", ".join(sorted(good_locales))) + ) bad_locale = None - for locale in os.environ.get('LC_ALL'), os.environ.get('LANG'): - if locale and locale.lower().endswith(('.utf-8', '.utf8')): - bad_locale = locale - if locale is not None: - break - if bad_locale is not None: - extra += ( - '\n\nClick discovered that you exported a UTF-8 locale\n' - 'but the locale system could not pick up from it because\n' - 'it does not exist. The exported locale is "%s" but it\n' - 'is not supported' - ) % bad_locale - raise RuntimeError( - 'Click will abort further execution because Python 3 was' - ' configured to use ASCII as encoding for the environment.' - ' Consult https://click.palletsprojects.com/en/7.x/python3/ for' - ' mitigation steps.' + extra - ) + for env_locale in os.environ.get("LC_ALL"), os.environ.get("LANG"): + if env_locale and env_locale.lower().endswith((".utf-8", ".utf8")): + bad_locale = env_locale + + if env_locale is not None: + break + + if bad_locale is not None: + extra.append( + _( + "Click discovered that you exported a UTF-8 locale" + " but the locale system could not pick up from it" + " because it does not exist. The exported locale is" + " {locale!r} but it is not supported." + ).format(locale=bad_locale) + ) + + raise RuntimeError("\n\n".join(extra)) diff --git a/pipenv/vendor/click/_winconsole.py b/pipenv/vendor/click/_winconsole.py index bbb080dd..6b20df31 100644 --- a/pipenv/vendor/click/_winconsole.py +++ b/pipenv/vendor/click/_winconsole.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This module is based on the excellent work by Adam Bartoš who # provided a lot of what went into the implementation here in # the discussion to issue1602 in the Python bug tracker. @@ -6,26 +5,32 @@ # There are some general differences in regards to how this works # compared to the original patches as we do not need to patch # the entire interpreter but just work in our little world of -# echo and prmopt. - +# echo and prompt. import io -import os import sys -import zlib import time -import ctypes -import msvcrt -from ._compat import _NonClosingTextIOWrapper, text_type, PY2 -from ctypes import byref, POINTER, c_int, c_char, c_char_p, \ - c_void_p, py_object, c_ssize_t, c_ulong, windll, WINFUNCTYPE -try: - from ctypes import pythonapi - PyObject_GetBuffer = pythonapi.PyObject_GetBuffer - PyBuffer_Release = pythonapi.PyBuffer_Release -except ImportError: - pythonapi = None -from ctypes.wintypes import LPWSTR, LPCWSTR +import typing as t +from ctypes import byref +from ctypes import c_char +from ctypes import c_char_p +from ctypes import c_int +from ctypes import c_ssize_t +from ctypes import c_ulong +from ctypes import c_void_p +from ctypes import POINTER +from ctypes import py_object +from ctypes import Structure +from ctypes.wintypes import DWORD +from ctypes.wintypes import HANDLE +from ctypes.wintypes import LPCWSTR +from ctypes.wintypes import LPWSTR +from ._compat import _NonClosingTextIOWrapper + +assert sys.platform == "win32" +import msvcrt # noqa: E402 +from ctypes import windll # noqa: E402 +from ctypes import WINFUNCTYPE # noqa: E402 c_ssize_p = POINTER(c_ssize_t) @@ -33,19 +38,18 @@ kernel32 = windll.kernel32 GetStdHandle = kernel32.GetStdHandle ReadConsoleW = kernel32.ReadConsoleW WriteConsoleW = kernel32.WriteConsoleW +GetConsoleMode = kernel32.GetConsoleMode GetLastError = kernel32.GetLastError -GetCommandLineW = WINFUNCTYPE(LPWSTR)( - ('GetCommandLineW', windll.kernel32)) -CommandLineToArgvW = WINFUNCTYPE( - POINTER(LPWSTR), LPCWSTR, POINTER(c_int))( - ('CommandLineToArgvW', windll.shell32)) - +GetCommandLineW = WINFUNCTYPE(LPWSTR)(("GetCommandLineW", windll.kernel32)) +CommandLineToArgvW = WINFUNCTYPE(POINTER(LPWSTR), LPCWSTR, POINTER(c_int))( + ("CommandLineToArgvW", windll.shell32) +) +LocalFree = WINFUNCTYPE(c_void_p, c_void_p)(("LocalFree", windll.kernel32)) STDIN_HANDLE = GetStdHandle(-10) STDOUT_HANDLE = GetStdHandle(-11) STDERR_HANDLE = GetStdHandle(-12) - PyBUF_SIMPLE = 0 PyBUF_WRITABLE = 1 @@ -57,38 +61,40 @@ STDIN_FILENO = 0 STDOUT_FILENO = 1 STDERR_FILENO = 2 -EOF = b'\x1a' +EOF = b"\x1a" MAX_BYTES_WRITTEN = 32767 - -class Py_buffer(ctypes.Structure): - _fields_ = [ - ('buf', c_void_p), - ('obj', py_object), - ('len', c_ssize_t), - ('itemsize', c_ssize_t), - ('readonly', c_int), - ('ndim', c_int), - ('format', c_char_p), - ('shape', c_ssize_p), - ('strides', c_ssize_p), - ('suboffsets', c_ssize_p), - ('internal', c_void_p) - ] - - if PY2: - _fields_.insert(-1, ('smalltable', c_ssize_t * 2)) - - -# On PyPy we cannot get buffers so our ability to operate here is -# serverly limited. -if pythonapi is None: +try: + from ctypes import pythonapi +except ImportError: + # On PyPy we cannot get buffers so our ability to operate here is + # severely limited. get_buffer = None else: + + class Py_buffer(Structure): + _fields_ = [ + ("buf", c_void_p), + ("obj", py_object), + ("len", c_ssize_t), + ("itemsize", c_ssize_t), + ("readonly", c_int), + ("ndim", c_int), + ("format", c_char_p), + ("shape", c_ssize_p), + ("strides", c_ssize_p), + ("suboffsets", c_ssize_p), + ("internal", c_void_p), + ] + + PyObject_GetBuffer = pythonapi.PyObject_GetBuffer + PyBuffer_Release = pythonapi.PyBuffer_Release + def get_buffer(obj, writable=False): buf = Py_buffer() flags = PyBUF_WRITABLE if writable else PyBUF_SIMPLE PyObject_GetBuffer(py_object(obj), byref(buf), flags) + try: buffer_type = c_char * buf.len return buffer_type.from_address(buf.buf) @@ -97,17 +103,15 @@ else: class _WindowsConsoleRawIOBase(io.RawIOBase): - def __init__(self, handle): self.handle = handle def isatty(self): - io.RawIOBase.isatty(self) + super().isatty() return True class _WindowsConsoleReader(_WindowsConsoleRawIOBase): - def readable(self): return True @@ -116,20 +120,26 @@ class _WindowsConsoleReader(_WindowsConsoleRawIOBase): if not bytes_to_be_read: return 0 elif bytes_to_be_read % 2: - raise ValueError('cannot read odd number of bytes from ' - 'UTF-16-LE encoded console') + raise ValueError( + "cannot read odd number of bytes from UTF-16-LE encoded console" + ) buffer = get_buffer(b, writable=True) code_units_to_be_read = bytes_to_be_read // 2 code_units_read = c_ulong() - rv = ReadConsoleW(self.handle, buffer, code_units_to_be_read, - byref(code_units_read), None) + rv = ReadConsoleW( + HANDLE(self.handle), + buffer, + code_units_to_be_read, + byref(code_units_read), + None, + ) if GetLastError() == ERROR_OPERATION_ABORTED: # wait for KeyboardInterrupt time.sleep(0.1) if not rv: - raise OSError('Windows error: %s' % GetLastError()) + raise OSError(f"Windows error: {GetLastError()}") if buffer[0] == EOF: return 0 @@ -137,27 +147,30 @@ class _WindowsConsoleReader(_WindowsConsoleRawIOBase): class _WindowsConsoleWriter(_WindowsConsoleRawIOBase): - def writable(self): return True @staticmethod def _get_error_message(errno): if errno == ERROR_SUCCESS: - return 'ERROR_SUCCESS' + return "ERROR_SUCCESS" elif errno == ERROR_NOT_ENOUGH_MEMORY: - return 'ERROR_NOT_ENOUGH_MEMORY' - return 'Windows error %s' % errno + return "ERROR_NOT_ENOUGH_MEMORY" + return f"Windows error {errno}" def write(self, b): bytes_to_be_written = len(b) buf = get_buffer(b) - code_units_to_be_written = min(bytes_to_be_written, - MAX_BYTES_WRITTEN) // 2 + code_units_to_be_written = min(bytes_to_be_written, MAX_BYTES_WRITTEN) // 2 code_units_written = c_ulong() - WriteConsoleW(self.handle, buf, code_units_to_be_written, - byref(code_units_written), None) + WriteConsoleW( + HANDLE(self.handle), + buf, + code_units_to_be_written, + byref(code_units_written), + None, + ) bytes_written = 2 * code_units_written.value if bytes_written == 0 and bytes_to_be_written > 0: @@ -165,18 +178,17 @@ class _WindowsConsoleWriter(_WindowsConsoleRawIOBase): return bytes_written -class ConsoleStream(object): - - def __init__(self, text_stream, byte_stream): +class ConsoleStream: + def __init__(self, text_stream: t.TextIO, byte_stream: t.BinaryIO) -> None: self._text_stream = text_stream self.buffer = byte_stream @property - def name(self): + def name(self) -> str: return self.buffer.name - def write(self, x): - if isinstance(x, text_type): + def write(self, x: t.AnyStr) -> int: + if isinstance(x, str): return self._text_stream.write(x) try: self.flush() @@ -184,124 +196,84 @@ class ConsoleStream(object): pass return self.buffer.write(x) - def writelines(self, lines): + def writelines(self, lines: t.Iterable[t.AnyStr]) -> None: for line in lines: self.write(line) - def __getattr__(self, name): + def __getattr__(self, name: str) -> t.Any: return getattr(self._text_stream, name) - def isatty(self): + def isatty(self) -> bool: return self.buffer.isatty() def __repr__(self): - return '<ConsoleStream name=%r encoding=%r>' % ( - self.name, - self.encoding, - ) + return f"<ConsoleStream name={self.name!r} encoding={self.encoding!r}>" -class WindowsChunkedWriter(object): - """ - Wraps a stream (such as stdout), acting as a transparent proxy for all - attribute access apart from method 'write()' which we wrap to write in - limited chunks due to a Windows limitation on binary console streams. - """ - def __init__(self, wrapped): - # double-underscore everything to prevent clashes with names of - # attributes on the wrapped stream object. - self.__wrapped = wrapped - - def __getattr__(self, name): - return getattr(self.__wrapped, name) - - def write(self, text): - total_to_write = len(text) - written = 0 - - while written < total_to_write: - to_write = min(total_to_write - written, MAX_BYTES_WRITTEN) - self.__wrapped.write(text[written:written+to_write]) - written += to_write - - -_wrapped_std_streams = set() - - -def _wrap_std_stream(name): - # Python 2 & Windows 7 and below - if PY2 and sys.getwindowsversion()[:2] <= (6, 1) and name not in _wrapped_std_streams: - setattr(sys, name, WindowsChunkedWriter(getattr(sys, name))) - _wrapped_std_streams.add(name) - - -def _get_text_stdin(buffer_stream): +def _get_text_stdin(buffer_stream: t.BinaryIO) -> t.TextIO: text_stream = _NonClosingTextIOWrapper( io.BufferedReader(_WindowsConsoleReader(STDIN_HANDLE)), - 'utf-16-le', 'strict', line_buffering=True) - return ConsoleStream(text_stream, buffer_stream) + "utf-16-le", + "strict", + line_buffering=True, + ) + return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream)) -def _get_text_stdout(buffer_stream): +def _get_text_stdout(buffer_stream: t.BinaryIO) -> t.TextIO: text_stream = _NonClosingTextIOWrapper( io.BufferedWriter(_WindowsConsoleWriter(STDOUT_HANDLE)), - 'utf-16-le', 'strict', line_buffering=True) - return ConsoleStream(text_stream, buffer_stream) + "utf-16-le", + "strict", + line_buffering=True, + ) + return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream)) -def _get_text_stderr(buffer_stream): +def _get_text_stderr(buffer_stream: t.BinaryIO) -> t.TextIO: text_stream = _NonClosingTextIOWrapper( io.BufferedWriter(_WindowsConsoleWriter(STDERR_HANDLE)), - 'utf-16-le', 'strict', line_buffering=True) - return ConsoleStream(text_stream, buffer_stream) + "utf-16-le", + "strict", + line_buffering=True, + ) + return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream)) -if PY2: - def _hash_py_argv(): - return zlib.crc32('\x00'.join(sys.argv[1:])) - - _initial_argv_hash = _hash_py_argv() - - def _get_windows_argv(): - argc = c_int(0) - argv_unicode = CommandLineToArgvW(GetCommandLineW(), byref(argc)) - argv = [argv_unicode[i] for i in range(0, argc.value)] - - if not hasattr(sys, 'frozen'): - argv = argv[1:] - while len(argv) > 0: - arg = argv[0] - if not arg.startswith('-') or arg == '-': - break - argv = argv[1:] - if arg.startswith(('-c', '-m')): - break - - return argv[1:] - - -_stream_factories = { +_stream_factories: t.Mapping[int, t.Callable[[t.BinaryIO], t.TextIO]] = { 0: _get_text_stdin, 1: _get_text_stdout, 2: _get_text_stderr, } -def _get_windows_console_stream(f, encoding, errors): - if get_buffer is not None and \ - encoding in ('utf-16-le', None) \ - and errors in ('strict', None) and \ - hasattr(f, 'isatty') and f.isatty(): +def _is_console(f: t.TextIO) -> bool: + if not hasattr(f, "fileno"): + return False + + try: + fileno = f.fileno() + except (OSError, io.UnsupportedOperation): + return False + + handle = msvcrt.get_osfhandle(fileno) + return bool(GetConsoleMode(handle, byref(DWORD()))) + + +def _get_windows_console_stream( + f: t.TextIO, encoding: t.Optional[str], errors: t.Optional[str] +) -> t.Optional[t.TextIO]: + if ( + get_buffer is not None + and encoding in {"utf-16-le", None} + and errors in {"strict", None} + and _is_console(f) + ): func = _stream_factories.get(f.fileno()) if func is not None: - if not PY2: - f = getattr(f, 'buffer', None) - if f is None: - return None - else: - # If we are on Python 2 we need to set the stream that we - # deal with to binary mode as otherwise the exercise if a - # bit moot. The same problems apply as for - # get_binary_stdin and friends from _compat. - msvcrt.setmode(f.fileno(), os.O_BINARY) - return func(f) + b = getattr(f, "buffer", None) + + if b is None: + return None + + return func(b) diff --git a/pipenv/vendor/click/core.py b/pipenv/vendor/click/core.py index 7a1e3422..56f05a31 100644 --- a/pipenv/vendor/click/core.py +++ b/pipenv/vendor/click/core.py @@ -1,106 +1,103 @@ +import enum import errno -import inspect import os import sys +import typing +import typing as t +from collections import abc from contextlib import contextmanager -from itertools import repeat +from contextlib import ExitStack +from functools import partial from functools import update_wrapper +from gettext import gettext as _ +from gettext import ngettext +from itertools import repeat -from .types import convert_type, IntRange, BOOL -from .utils import PacifyFlushWrapper, make_str, make_default_short_help, \ - echo, get_os_args -from .exceptions import ClickException, UsageError, BadParameter, Abort, \ - MissingParameter, Exit -from .termui import prompt, confirm, style -from .formatting import HelpFormatter, join_options -from .parser import OptionParser, split_opt -from .globals import push_context, pop_context +from . import types +from ._unicodefun import _verify_python_env +from .exceptions import Abort +from .exceptions import BadParameter +from .exceptions import ClickException +from .exceptions import Exit +from .exceptions import MissingParameter +from .exceptions import UsageError +from .formatting import HelpFormatter +from .formatting import join_options +from .globals import pop_context +from .globals import push_context +from .parser import _flag_needs_value +from .parser import OptionParser +from .parser import split_opt +from .termui import confirm +from .termui import prompt +from .termui import style +from .utils import _detect_program_name +from .utils import _expand_args +from .utils import echo +from .utils import make_default_short_help +from .utils import make_str +from .utils import PacifyFlushWrapper -from ._compat import PY2, isidentifier, iteritems, string_types -from ._unicodefun import _check_for_unicode_literals, _verify_python3_env +if t.TYPE_CHECKING: + import typing_extensions as te + from .shell_completion import CompletionItem + +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) +V = t.TypeVar("V") -_missing = object() +def _complete_visible_commands( + ctx: "Context", incomplete: str +) -> t.Iterator[t.Tuple[str, "Command"]]: + """List all the subcommands of a group that start with the + incomplete value and aren't hidden. - -SUBCOMMAND_METAVAR = 'COMMAND [ARGS]...' -SUBCOMMANDS_METAVAR = 'COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]...' - -DEPRECATED_HELP_NOTICE = ' (DEPRECATED)' -DEPRECATED_INVOKE_NOTICE = 'DeprecationWarning: ' + \ - 'The command %(name)s is deprecated.' - - -def _maybe_show_deprecated_notice(cmd): - if cmd.deprecated: - echo(style(DEPRECATED_INVOKE_NOTICE % {'name': cmd.name}, fg='red'), err=True) - - -def fast_exit(code): - """Exit without garbage collection, this speeds up exit by about 10ms for - things like bash completion. + :param ctx: Invocation context for the group. + :param incomplete: Value being completed. May be empty. """ - sys.stdout.flush() - sys.stderr.flush() - os._exit(code) + multi = t.cast(MultiCommand, ctx.command) + + for name in multi.list_commands(ctx): + if name.startswith(incomplete): + command = multi.get_command(ctx, name) + + if command is not None and not command.hidden: + yield name, command -def _bashcomplete(cmd, prog_name, complete_var=None): - """Internal handler for the bash completion support.""" - if complete_var is None: - complete_var = '_%s_COMPLETE' % (prog_name.replace('-', '_')).upper() - complete_instr = os.environ.get(complete_var) - if not complete_instr: - return - - from ._bashcomplete import bashcomplete - if bashcomplete(cmd, prog_name, complete_var, complete_instr): - fast_exit(1) - - -def _check_multicommand(base_command, cmd_name, cmd, register=False): +def _check_multicommand( + base_command: "MultiCommand", cmd_name: str, cmd: "Command", register: bool = False +) -> None: if not base_command.chain or not isinstance(cmd, MultiCommand): return if register: - hint = 'It is not possible to add multi commands as children to ' \ - 'another multi command that is in chain mode' + hint = ( + "It is not possible to add multi commands as children to" + " another multi command that is in chain mode." + ) else: - hint = 'Found a multi command as subcommand to a multi command ' \ - 'that is in chain mode. This is not supported' - raise RuntimeError('%s. Command "%s" is set to chain and "%s" was ' - 'added as subcommand but it in itself is a ' - 'multi command. ("%s" is a %s within a chained ' - '%s named "%s").' % ( - hint, base_command.name, cmd_name, - cmd_name, cmd.__class__.__name__, - base_command.__class__.__name__, - base_command.name)) + hint = ( + "Found a multi command as subcommand to a multi command" + " that is in chain mode. This is not supported." + ) + raise RuntimeError( + f"{hint}. Command {base_command.name!r} is set to chain and" + f" {cmd_name!r} was added as a subcommand but it in itself is a" + f" multi command. ({cmd_name!r} is a {type(cmd).__name__}" + f" within a chained {type(base_command).__name__} named" + f" {base_command.name!r})." + ) -def batch(iterable, batch_size): +def batch(iterable: t.Iterable[V], batch_size: int) -> t.List[t.Tuple[V, ...]]: return list(zip(*repeat(iter(iterable), batch_size))) -def invoke_param_callback(callback, ctx, param, value): - code = getattr(callback, '__code__', None) - args = getattr(code, 'co_argcount', 3) - - if args < 3: - # This will become a warning in Click 3.0: - from warnings import warn - warn(Warning('Invoked legacy parameter callback "%s". The new ' - 'signature for such callbacks starting with ' - 'click 2.0 is (ctx, param, value).' - % callback), stacklevel=3) - return callback(ctx, value) - return callback(ctx, param, value) - - @contextmanager -def augment_usage_errors(ctx, param=None): - """Context manager that attaches extra information to exceptions that - fly. - """ +def augment_usage_errors( + ctx: "Context", param: t.Optional["Parameter"] = None +) -> t.Iterator[None]: + """Context manager that attaches extra information to exceptions.""" try: yield except BadParameter as e: @@ -115,22 +112,53 @@ def augment_usage_errors(ctx, param=None): raise -def iter_params_for_processing(invocation_order, declaration_order): +def iter_params_for_processing( + invocation_order: t.Sequence["Parameter"], + declaration_order: t.Sequence["Parameter"], +) -> t.List["Parameter"]: """Given a sequence of parameters in the order as should be considered for processing and an iterable of parameters that exist, this returns a list in the correct order as they should be processed. """ - def sort_key(item): + + def sort_key(item: "Parameter") -> t.Tuple[bool, float]: try: - idx = invocation_order.index(item) + idx: float = invocation_order.index(item) except ValueError: - idx = float('inf') - return (not item.is_eager, idx) + idx = float("inf") + + return not item.is_eager, idx return sorted(declaration_order, key=sort_key) -class Context(object): +class ParameterSource(enum.Enum): + """This is an :class:`~enum.Enum` that indicates the source of a + parameter's value. + + Use :meth:`click.Context.get_parameter_source` to get the + source for a parameter by name. + + .. versionchanged:: 8.0 + Use :class:`~enum.Enum` and drop the ``validate`` method. + + .. versionchanged:: 8.0 + Added the ``PROMPT`` value. + """ + + COMMANDLINE = enum.auto() + """The value was provided by the command line args.""" + ENVIRONMENT = enum.auto() + """The value was provided with an environment variable.""" + DEFAULT = enum.auto() + """Used the default specified by the parameter.""" + DEFAULT_MAP = enum.auto() + """Used a default provided by :attr:`Context.default_map`.""" + PROMPT = enum.auto() + """Used a prompt to confirm a default or provide a value.""" + + +class Context: """The context is a special internal object that holds state relevant for the script execution at every single level. It's normally invisible to commands unless they opt-in to getting access to it. @@ -142,18 +170,6 @@ class Context(object): A context can be used as context manager in which case it will call :meth:`close` on teardown. - .. versionadded:: 2.0 - Added the `resilient_parsing`, `help_option_names`, - `token_normalize_func` parameters. - - .. versionadded:: 3.0 - Added the `allow_extra_args` and `allow_interspersed_args` - parameters. - - .. versionadded:: 4.0 - Added the `color`, `ignore_unknown_options`, and - `max_content_width` parameters. - :param command: the command class for this context. :param parent: the parent context. :param info_name: the info name for this invocation. Generally this @@ -208,43 +224,88 @@ class Context(object): codes are used in texts that Click prints which is by default not the case. This for instance would affect help output. + :param show_default: Show defaults for all options. If not set, + defaults to the value from a parent context. Overrides an + option's ``show_default`` argument. + + .. versionchanged:: 8.0 + The ``show_default`` parameter defaults to the value from the + parent context. + + .. versionchanged:: 7.1 + Added the ``show_default`` parameter. + + .. versionchanged:: 4.0 + Added the ``color``, ``ignore_unknown_options``, and + ``max_content_width`` parameters. + + .. versionchanged:: 3.0 + Added the ``allow_extra_args`` and ``allow_interspersed_args`` + parameters. + + .. versionchanged:: 2.0 + Added the ``resilient_parsing``, ``help_option_names``, and + ``token_normalize_func`` parameters. """ - def __init__(self, command, parent=None, info_name=None, obj=None, - auto_envvar_prefix=None, default_map=None, - terminal_width=None, max_content_width=None, - resilient_parsing=False, allow_extra_args=None, - allow_interspersed_args=None, - ignore_unknown_options=None, help_option_names=None, - token_normalize_func=None, color=None): + #: The formatter class to create with :meth:`make_formatter`. + #: + #: .. versionadded:: 8.0 + formatter_class: t.Type["HelpFormatter"] = HelpFormatter + + def __init__( + self, + command: "Command", + parent: t.Optional["Context"] = None, + info_name: t.Optional[str] = None, + obj: t.Optional[t.Any] = None, + auto_envvar_prefix: t.Optional[str] = None, + default_map: t.Optional[t.Dict[str, t.Any]] = None, + terminal_width: t.Optional[int] = None, + max_content_width: t.Optional[int] = None, + resilient_parsing: bool = False, + allow_extra_args: t.Optional[bool] = None, + allow_interspersed_args: t.Optional[bool] = None, + ignore_unknown_options: t.Optional[bool] = None, + help_option_names: t.Optional[t.List[str]] = None, + token_normalize_func: t.Optional[t.Callable[[str], str]] = None, + color: t.Optional[bool] = None, + show_default: t.Optional[bool] = None, + ) -> None: #: the parent context or `None` if none exists. self.parent = parent #: the :class:`Command` for this context. self.command = command #: the descriptive information name self.info_name = info_name - #: the parsed parameters except if the value is hidden in which - #: case it's not remembered. - self.params = {} + #: Map of parameter names to their parsed values. Parameters + #: with ``expose_value=False`` are not stored. + self.params: t.Dict[str, t.Any] = {} #: the leftover arguments. - self.args = [] + self.args: t.List[str] = [] #: protected arguments. These are arguments that are prepended #: to `args` when certain parsing scenarios are encountered but #: must be never propagated to another arguments. This is used #: to implement nested parsing. - self.protected_args = [] + self.protected_args: t.List[str] = [] + if obj is None and parent is not None: obj = parent.obj + #: the user object stored. - self.obj = obj - self._meta = getattr(parent, 'meta', {}) + self.obj: t.Any = obj + self._meta: t.Dict[str, t.Any] = getattr(parent, "meta", {}) #: A dictionary (-like object) with defaults for parameters. - if default_map is None \ - and parent is not None \ - and parent.default_map is not None: + if ( + default_map is None + and info_name is not None + and parent is not None + and parent.default_map is not None + ): default_map = parent.default_map.get(info_name) - self.default_map = default_map + + self.default_map: t.Optional[t.Dict[str, t.Any]] = default_map #: This flag indicates if a subcommand is going to be executed. A #: group callback can use this information to figure out if it's @@ -255,22 +316,25 @@ class Context(object): #: If chaining is enabled this will be set to ``'*'`` in case #: any commands are executed. It is however not possible to #: figure out which ones. If you require this knowledge you - #: should use a :func:`resultcallback`. - self.invoked_subcommand = None + #: should use a :func:`result_callback`. + self.invoked_subcommand: t.Optional[str] = None if terminal_width is None and parent is not None: terminal_width = parent.terminal_width + #: The width of the terminal (None is autodetection). - self.terminal_width = terminal_width + self.terminal_width: t.Optional[int] = terminal_width if max_content_width is None and parent is not None: max_content_width = parent.max_content_width + #: The maximum width of formatted content (None implies a sensible #: default which is 80 for most things). - self.max_content_width = max_content_width + self.max_content_width: t.Optional[int] = max_content_width if allow_extra_args is None: allow_extra_args = command.allow_extra_args + #: Indicates if the context allows extra args or if it should #: fail on parsing. #: @@ -279,14 +343,16 @@ class Context(object): if allow_interspersed_args is None: allow_interspersed_args = command.allow_interspersed_args + #: Indicates if the context allows mixing of arguments and #: options or not. #: #: .. versionadded:: 3.0 - self.allow_interspersed_args = allow_interspersed_args + self.allow_interspersed_args: bool = allow_interspersed_args if ignore_unknown_options is None: ignore_unknown_options = command.ignore_unknown_options + #: Instructs click to ignore options that a command does not #: understand and will store it on the context for later #: processing. This is primarily useful for situations where you @@ -295,64 +361,102 @@ class Context(object): #: forward all arguments. #: #: .. versionadded:: 4.0 - self.ignore_unknown_options = ignore_unknown_options + self.ignore_unknown_options: bool = ignore_unknown_options if help_option_names is None: if parent is not None: help_option_names = parent.help_option_names else: - help_option_names = ['--help'] + help_option_names = ["--help"] #: The names for the help options. - self.help_option_names = help_option_names + self.help_option_names: t.List[str] = help_option_names if token_normalize_func is None and parent is not None: token_normalize_func = parent.token_normalize_func #: An optional normalization function for tokens. This is #: options, choices, commands etc. - self.token_normalize_func = token_normalize_func + self.token_normalize_func: t.Optional[ + t.Callable[[str], str] + ] = token_normalize_func #: Indicates if resilient parsing is enabled. In that case Click #: will do its best to not cause any failures and default values #: will be ignored. Useful for completion. - self.resilient_parsing = resilient_parsing + self.resilient_parsing: bool = resilient_parsing # If there is no envvar prefix yet, but the parent has one and # the command on this level has a name, we can expand the envvar # prefix automatically. if auto_envvar_prefix is None: - if parent is not None \ - and parent.auto_envvar_prefix is not None and \ - self.info_name is not None: - auto_envvar_prefix = '%s_%s' % (parent.auto_envvar_prefix, - self.info_name.upper()) + if ( + parent is not None + and parent.auto_envvar_prefix is not None + and self.info_name is not None + ): + auto_envvar_prefix = ( + f"{parent.auto_envvar_prefix}_{self.info_name.upper()}" + ) else: auto_envvar_prefix = auto_envvar_prefix.upper() - self.auto_envvar_prefix = auto_envvar_prefix + + if auto_envvar_prefix is not None: + auto_envvar_prefix = auto_envvar_prefix.replace("-", "_") + + self.auto_envvar_prefix: t.Optional[str] = auto_envvar_prefix if color is None and parent is not None: color = parent.color #: Controls if styling output is wanted or not. - self.color = color + self.color: t.Optional[bool] = color - self._close_callbacks = [] + if show_default is None and parent is not None: + show_default = parent.show_default + + #: Show option default values when formatting help text. + self.show_default: t.Optional[bool] = show_default + + self._close_callbacks: t.List[t.Callable[[], t.Any]] = [] self._depth = 0 + self._parameter_source: t.Dict[str, ParameterSource] = {} + self._exit_stack = ExitStack() - def __enter__(self): + def to_info_dict(self) -> t.Dict[str, t.Any]: + """Gather information that could be useful for a tool generating + user-facing documentation. This traverses the entire CLI + structure. + + .. code-block:: python + + with Context(cli) as ctx: + info = ctx.to_info_dict() + + .. versionadded:: 8.0 + """ + return { + "command": self.command.to_info_dict(self), + "info_name": self.info_name, + "allow_extra_args": self.allow_extra_args, + "allow_interspersed_args": self.allow_interspersed_args, + "ignore_unknown_options": self.ignore_unknown_options, + "auto_envvar_prefix": self.auto_envvar_prefix, + } + + def __enter__(self) -> "Context": self._depth += 1 push_context(self) return self - def __exit__(self, exc_type, exc_value, tb): + def __exit__(self, exc_type, exc_value, tb): # type: ignore self._depth -= 1 if self._depth == 0: self.close() pop_context() @contextmanager - def scope(self, cleanup=True): + def scope(self, cleanup: bool = True) -> t.Iterator["Context"]: """This helper method can be used with the context object to promote it to the current thread local (see :func:`get_current_context`). The default behavior of this is to invoke the cleanup functions which @@ -390,7 +494,7 @@ class Context(object): self._depth -= 1 @property - def meta(self): + def meta(self) -> t.Dict[str, t.Any]: """This is a dictionary which is shared with all the contexts that are nested. It exists so that click utilities can store some state here if they need to. It is however the responsibility of @@ -404,7 +508,7 @@ class Context(object): Example usage:: - LANG_KEY = __name__ + '.lang' + LANG_KEY = f'{__name__}.lang' def set_language(value): ctx = get_current_context() @@ -417,58 +521,109 @@ class Context(object): """ return self._meta - def make_formatter(self): - """Creates the formatter for the help and usage output.""" - return HelpFormatter(width=self.terminal_width, - max_width=self.max_content_width) + def make_formatter(self) -> HelpFormatter: + """Creates the :class:`~click.HelpFormatter` for the help and + usage output. - def call_on_close(self, f): - """This decorator remembers a function as callback that should be - executed when the context tears down. This is most useful to bind - resource handling to the script execution. For instance, file objects - opened by the :class:`File` type will register their close callbacks - here. + To quickly customize the formatter class used without overriding + this method, set the :attr:`formatter_class` attribute. - :param f: the function to execute on teardown. + .. versionchanged:: 8.0 + Added the :attr:`formatter_class` attribute. """ - self._close_callbacks.append(f) - return f + return self.formatter_class( + width=self.terminal_width, max_width=self.max_content_width + ) - def close(self): - """Invokes all close callbacks.""" - for cb in self._close_callbacks: - cb() - self._close_callbacks = [] + def with_resource(self, context_manager: t.ContextManager[V]) -> V: + """Register a resource as if it were used in a ``with`` + statement. The resource will be cleaned up when the context is + popped. + + Uses :meth:`contextlib.ExitStack.enter_context`. It calls the + resource's ``__enter__()`` method and returns the result. When + the context is popped, it closes the stack, which calls the + resource's ``__exit__()`` method. + + To register a cleanup function for something that isn't a + context manager, use :meth:`call_on_close`. Or use something + from :mod:`contextlib` to turn it into a context manager first. + + .. code-block:: python + + @click.group() + @click.option("--name") + @click.pass_context + def cli(ctx): + ctx.obj = ctx.with_resource(connect_db(name)) + + :param context_manager: The context manager to enter. + :return: Whatever ``context_manager.__enter__()`` returns. + + .. versionadded:: 8.0 + """ + return self._exit_stack.enter_context(context_manager) + + def call_on_close(self, f: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]: + """Register a function to be called when the context tears down. + + This can be used to close resources opened during the script + execution. Resources that support Python's context manager + protocol which would be used in a ``with`` statement should be + registered with :meth:`with_resource` instead. + + :param f: The function to execute on teardown. + """ + return self._exit_stack.callback(f) + + def close(self) -> None: + """Invoke all close callbacks registered with + :meth:`call_on_close`, and exit all context managers entered + with :meth:`with_resource`. + """ + self._exit_stack.close() + # In case the context is reused, create a new exit stack. + self._exit_stack = ExitStack() @property - def command_path(self): + def command_path(self) -> str: """The computed command path. This is used for the ``usage`` information on the help page. It's automatically created by combining the info names of the chain of contexts to the root. """ - rv = '' + rv = "" if self.info_name is not None: rv = self.info_name if self.parent is not None: - rv = self.parent.command_path + ' ' + rv + parent_command_path = [self.parent.command_path] + + if isinstance(self.parent.command, Command): + for param in self.parent.command.get_params(self): + parent_command_path.extend(param.get_usage_pieces(self)) + + rv = f"{' '.join(parent_command_path)} {rv}" return rv.lstrip() - def find_root(self): + def find_root(self) -> "Context": """Finds the outermost context.""" node = self while node.parent is not None: node = node.parent return node - def find_object(self, object_type): + def find_object(self, object_type: t.Type[V]) -> t.Optional[V]: """Finds the closest object of a given type.""" - node = self + node: t.Optional["Context"] = self + while node is not None: if isinstance(node.obj, object_type): return node.obj + node = node.parent - def ensure_object(self, object_type): + return None + + def ensure_object(self, object_type: t.Type[V]) -> V: """Like :meth:`find_object` but sets the innermost object to a new instance of `object_type` if it does not exist. """ @@ -477,17 +632,39 @@ class Context(object): self.obj = rv = object_type() return rv - def lookup_default(self, name): - """Looks up the default for a parameter name. This by default - looks into the :attr:`default_map` if available. + @typing.overload + def lookup_default( + self, name: str, call: "te.Literal[True]" = True + ) -> t.Optional[t.Any]: + ... + + @typing.overload + def lookup_default( + self, name: str, call: "te.Literal[False]" = ... + ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]: + ... + + def lookup_default(self, name: str, call: bool = True) -> t.Optional[t.Any]: + """Get the default for a parameter from :attr:`default_map`. + + :param name: Name of the parameter. + :param call: If the default is a callable, call it. Disable to + return the callable instead. + + .. versionchanged:: 8.0 + Added the ``call`` parameter. """ if self.default_map is not None: - rv = self.default_map.get(name) - if callable(rv): - rv = rv() - return rv + value = self.default_map.get(name) - def fail(self, message): + if call and callable(value): + return value() + + return value + + return None + + def fail(self, message: str) -> "te.NoReturn": """Aborts the execution of the program with a specific error message. @@ -495,27 +672,40 @@ class Context(object): """ raise UsageError(message, self) - def abort(self): + def abort(self) -> "te.NoReturn": """Aborts the script.""" raise Abort() - def exit(self, code=0): + def exit(self, code: int = 0) -> "te.NoReturn": """Exits the application with a given exit code.""" raise Exit(code) - def get_usage(self): + def get_usage(self) -> str: """Helper method to get formatted usage string for the current context and command. """ return self.command.get_usage(self) - def get_help(self): + def get_help(self) -> str: """Helper method to get formatted help page for the current context and command. """ return self.command.get_help(self) - def invoke(*args, **kwargs): + def _make_sub_context(self, command: "Command") -> "Context": + """Create a new context of the same type as this context, but + for a new command. + + :meta private: + """ + return type(self)(command, info_name=command.name, parent=self) + + def invoke( + __self, # noqa: B902 + __callback: t.Union["Command", t.Callable[..., t.Any]], + *args: t.Any, + **kwargs: t.Any, + ) -> t.Any: """Invokes a command callback in exactly the way it expects. There are two ways to invoke this method: @@ -530,50 +720,89 @@ class Context(object): in against the intention of this code and no context was created. For more information about this change and why it was done in a bugfix release see :ref:`upgrade-to-3.2`. - """ - self, callback = args[:2] - ctx = self - # It's also possible to invoke another command which might or - # might not have a callback. In that case we also fill - # in defaults and make a new context for this command. - if isinstance(callback, Command): - other_cmd = callback - callback = other_cmd.callback - ctx = Context(other_cmd, info_name=other_cmd.name, parent=self) - if callback is None: - raise TypeError('The given command does not have a ' - 'callback that can be invoked.') + .. versionchanged:: 8.0 + All ``kwargs`` are tracked in :attr:`params` so they will be + passed if :meth:`forward` is called at multiple levels. + """ + if isinstance(__callback, Command): + other_cmd = __callback + + if other_cmd.callback is None: + raise TypeError( + "The given command does not have a callback that can be invoked." + ) + else: + __callback = other_cmd.callback + + ctx = __self._make_sub_context(other_cmd) for param in other_cmd.params: if param.name not in kwargs and param.expose_value: - kwargs[param.name] = param.get_default(ctx) + kwargs[param.name] = param.type_cast_value( # type: ignore + ctx, param.get_default(ctx) + ) - args = args[2:] - with augment_usage_errors(self): + # Track all kwargs as params, so that forward() will pass + # them on in subsequent calls. + ctx.params.update(kwargs) + else: + ctx = __self + + with augment_usage_errors(__self): with ctx: - return callback(*args, **kwargs) + return __callback(*args, **kwargs) - def forward(*args, **kwargs): + def forward( + __self, __cmd: "Command", *args: t.Any, **kwargs: t.Any # noqa: B902 + ) -> t.Any: """Similar to :meth:`invoke` but fills in default keyword arguments from the current context if the other command expects it. This cannot invoke callbacks directly, only other commands. + + .. versionchanged:: 8.0 + All ``kwargs`` are tracked in :attr:`params` so they will be + passed if ``forward`` is called at multiple levels. """ - self, cmd = args[:2] + # Can only forward to other commands, not direct callbacks. + if not isinstance(__cmd, Command): + raise TypeError("Callback is not a command.") - # It's also possible to invoke another command which might or - # might not have a callback. - if not isinstance(cmd, Command): - raise TypeError('Callback is not a command.') - - for param in self.params: + for param in __self.params: if param not in kwargs: - kwargs[param] = self.params[param] + kwargs[param] = __self.params[param] - return self.invoke(cmd, **kwargs) + return __self.invoke(__cmd, *args, **kwargs) + + def set_parameter_source(self, name: str, source: ParameterSource) -> None: + """Set the source of a parameter. This indicates the location + from which the value of the parameter was obtained. + + :param name: The name of the parameter. + :param source: A member of :class:`~click.core.ParameterSource`. + """ + self._parameter_source[name] = source + + def get_parameter_source(self, name: str) -> t.Optional[ParameterSource]: + """Get the source of a parameter. This indicates the location + from which the value of the parameter was obtained. + + This can be useful for determining when a user specified a value + on the command line that is the same as the default value. It + will be :attr:`~click.core.ParameterSource.DEFAULT` only if the + value was actually taken from the default. + + :param name: The name of the parameter. + :rtype: ParameterSource + + .. versionchanged:: 8.0 + Returns ``None`` if the parameter was not provided from any + source. + """ + return self._parameter_source.get(name) -class BaseCommand(object): +class BaseCommand: """The base command implements the minimal API contract of commands. Most code will never use this as it does not implement a lot of useful functionality but it can act as the direct subclass of alternative @@ -594,6 +823,11 @@ class BaseCommand(object): :param context_settings: an optional dictionary with defaults that are passed to the context object. """ + + #: The context class to create with :meth:`make_context`. + #: + #: .. versionadded:: 8.0 + context_class: t.Type[Context] = Context #: the default for the :attr:`Context.allow_extra_args` flag. allow_extra_args = False #: the default for the :attr:`Context.allow_interspersed_args` flag. @@ -601,62 +835,158 @@ class BaseCommand(object): #: the default for the :attr:`Context.ignore_unknown_options` flag. ignore_unknown_options = False - def __init__(self, name, context_settings=None): + def __init__( + self, + name: t.Optional[str], + context_settings: t.Optional[t.Dict[str, t.Any]] = None, + ) -> None: #: the name the command thinks it has. Upon registering a command #: on a :class:`Group` the group will default the command name #: with this information. You should instead use the #: :class:`Context`\'s :attr:`~Context.info_name` attribute. self.name = name + if context_settings is None: context_settings = {} + #: an optional dictionary with defaults passed to the context. - self.context_settings = context_settings + self.context_settings: t.Dict[str, t.Any] = context_settings - def get_usage(self, ctx): - raise NotImplementedError('Base commands cannot get usage') + def to_info_dict(self, ctx: Context) -> t.Dict[str, t.Any]: + """Gather information that could be useful for a tool generating + user-facing documentation. This traverses the entire structure + below this command. - def get_help(self, ctx): - raise NotImplementedError('Base commands cannot get help') + Use :meth:`click.Context.to_info_dict` to traverse the entire + CLI structure. - def make_context(self, info_name, args, parent=None, **extra): + :param ctx: A :class:`Context` representing this command. + + .. versionadded:: 8.0 + """ + return {"name": self.name} + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} {self.name}>" + + def get_usage(self, ctx: Context) -> str: + raise NotImplementedError("Base commands cannot get usage") + + def get_help(self, ctx: Context) -> str: + raise NotImplementedError("Base commands cannot get help") + + def make_context( + self, + info_name: t.Optional[str], + args: t.List[str], + parent: t.Optional[Context] = None, + **extra: t.Any, + ) -> Context: """This function when given an info name and arguments will kick off the parsing and create a new :class:`Context`. It does not invoke the actual command callback though. - :param info_name: the info name for this invokation. Generally this + To quickly customize the context class used without overriding + this method, set the :attr:`context_class` attribute. + + :param info_name: the info name for this invocation. Generally this is the most descriptive name for the script or command. For the toplevel script it's usually the name of the script, for commands below it it's - the name of the script. + the name of the command. :param args: the arguments to parse as list of strings. :param parent: the parent context if available. :param extra: extra keyword arguments forwarded to the context constructor. + + .. versionchanged:: 8.0 + Added the :attr:`context_class` attribute. """ - for key, value in iteritems(self.context_settings): + for key, value in self.context_settings.items(): if key not in extra: extra[key] = value - ctx = Context(self, info_name=info_name, parent=parent, **extra) + + ctx = self.context_class( + self, info_name=info_name, parent=parent, **extra # type: ignore + ) + with ctx.scope(cleanup=False): self.parse_args(ctx, args) return ctx - def parse_args(self, ctx, args): + def parse_args(self, ctx: Context, args: t.List[str]) -> t.List[str]: """Given a context and a list of arguments this creates the parser and parses the arguments, then modifies the context as necessary. This is automatically invoked by :meth:`make_context`. """ - raise NotImplementedError('Base commands do not know how to parse ' - 'arguments.') + raise NotImplementedError("Base commands do not know how to parse arguments.") - def invoke(self, ctx): + def invoke(self, ctx: Context) -> t.Any: """Given a context, this invokes the command. The default implementation is raising a not implemented error. """ - raise NotImplementedError('Base commands are not invokable by default') + raise NotImplementedError("Base commands are not invokable by default") - def main(self, args=None, prog_name=None, complete_var=None, - standalone_mode=True, **extra): + def shell_complete(self, ctx: Context, incomplete: str) -> t.List["CompletionItem"]: + """Return a list of completions for the incomplete value. Looks + at the names of chained multi-commands. + + Any command could be part of a chained multi-command, so sibling + commands are valid at any point during command completion. Other + command classes will return more completions. + + :param ctx: Invocation context for this command. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from pipenv.vendor.click.shell_completion import CompletionItem + + results: t.List["CompletionItem"] = [] + + while ctx.parent is not None: + ctx = ctx.parent + + if isinstance(ctx.command, MultiCommand) and ctx.command.chain: + results.extend( + CompletionItem(name, help=command.get_short_help_str()) + for name, command in _complete_visible_commands(ctx, incomplete) + if name not in ctx.protected_args + ) + + return results + + @typing.overload + def main( + self, + args: t.Optional[t.Sequence[str]] = None, + prog_name: t.Optional[str] = None, + complete_var: t.Optional[str] = None, + standalone_mode: "te.Literal[True]" = True, + **extra: t.Any, + ) -> "te.NoReturn": + ... + + @typing.overload + def main( + self, + args: t.Optional[t.Sequence[str]] = None, + prog_name: t.Optional[str] = None, + complete_var: t.Optional[str] = None, + standalone_mode: bool = ..., + **extra: t.Any, + ) -> t.Any: + ... + + def main( + self, + args: t.Optional[t.Sequence[str]] = None, + prog_name: t.Optional[str] = None, + complete_var: t.Optional[str] = None, + standalone_mode: bool = True, + windows_expand_args: bool = True, + **extra: t.Any, + ) -> t.Any: """This is the way to invoke a script with all the bells and whistles as a command line application. This will always terminate the application after a call. If this is not wanted, ``SystemExit`` @@ -665,9 +995,6 @@ class BaseCommand(object): This method is also available by directly calling the instance of a :class:`Command`. - .. versionadded:: 3.0 - Added the `standalone_mode` flag to control the standalone mode. - :param args: the arguments that should be used for parsing. If not provided, ``sys.argv[1:]`` is used. :param prog_name: the program name that should be used. By default @@ -686,30 +1013,39 @@ class BaseCommand(object): propagated to the caller and the return value of this function is the return value of :meth:`invoke`. + :param windows_expand_args: Expand glob patterns, user dir, and + env vars in command line args on Windows. :param extra: extra keyword arguments are forwarded to the context constructor. See :class:`Context` for more information. + + .. versionchanged:: 8.0.1 + Added the ``windows_expand_args`` parameter to allow + disabling command line arg expansion on Windows. + + .. versionchanged:: 8.0 + When taking arguments from ``sys.argv`` on Windows, glob + patterns, user dir, and env vars are expanded. + + .. versionchanged:: 3.0 + Added the ``standalone_mode`` parameter. """ - # If we are in Python 3, we will verify that the environment is - # sane at this point or reject further execution to avoid a - # broken script. - if not PY2: - _verify_python3_env() - else: - _check_for_unicode_literals() + # Verify that the environment is configured correctly, or reject + # further execution to avoid a broken script. + _verify_python_env() if args is None: - args = get_os_args() + args = sys.argv[1:] + + if os.name == "nt" and windows_expand_args: + args = _expand_args(args) else: args = list(args) if prog_name is None: - prog_name = make_str(os.path.basename( - sys.argv and sys.argv[0] or __file__)) + prog_name = _detect_program_name() - # Hook for the Bash completion. This only activates if the Bash - # completion is actually enabled, otherwise this is quite a fast - # noop. - _bashcomplete(self, prog_name, complete_var) + # Process shell completion requests and exit early. + self._main_shell_completion(extra, prog_name, complete_var) try: try: @@ -727,16 +1063,16 @@ class BaseCommand(object): ctx.exit() except (EOFError, KeyboardInterrupt): echo(file=sys.stderr) - raise Abort() + raise Abort() from None except ClickException as e: if not standalone_mode: raise e.show() sys.exit(e.exit_code) - except IOError as e: + except OSError as e: if e.errno == errno.EPIPE: - sys.stdout = PacifyFlushWrapper(sys.stdout) - sys.stderr = PacifyFlushWrapper(sys.stderr) + sys.stdout = t.cast(t.TextIO, PacifyFlushWrapper(sys.stdout)) + sys.stderr = t.cast(t.TextIO, PacifyFlushWrapper(sys.stderr)) sys.exit(1) else: raise @@ -756,10 +1092,38 @@ class BaseCommand(object): except Abort: if not standalone_mode: raise - echo('Aborted!', file=sys.stderr) + echo(_("Aborted!"), file=sys.stderr) sys.exit(1) - def __call__(self, *args, **kwargs): + def _main_shell_completion( + self, + ctx_args: t.Dict[str, t.Any], + prog_name: str, + complete_var: t.Optional[str] = None, + ) -> None: + """Check if the shell is asking for tab completion, process + that, then exit early. Called from :meth:`main` before the + program is invoked. + + :param prog_name: Name of the executable in the shell. + :param complete_var: Name of the environment variable that holds + the completion instruction. Defaults to + ``_{PROG_NAME}_COMPLETE``. + """ + if complete_var is None: + complete_var = f"_{prog_name}_COMPLETE".replace("-", "_").upper() + + instruction = os.environ.get(complete_var) + + if not instruction: + return + + from .shell_completion import shell_complete + + rv = shell_complete(self, ctx_args, prog_name, complete_var, instruction) + sys.exit(rv) + + def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Any: """Alias for :meth:`main`.""" return self.main(*args, **kwargs) @@ -771,6 +1135,10 @@ class Command(BaseCommand): .. versionchanged:: 2.0 Added the `context_settings` parameter. + .. versionchanged:: 8.0 + Added repr showing the command name + .. versionchanged:: 7.1 + Added the `no_args_is_help` parameter. :param name: the name of the command to use unless a group overrides it. :param context_settings: an optional dictionary with defaults that are @@ -785,108 +1153,168 @@ class Command(BaseCommand): shown on the command listing of the parent command. :param add_help_option: by default each command registers a ``--help`` option. This can be disabled by this parameter. + :param no_args_is_help: this controls what happens if no arguments are + provided. This option is disabled by default. + If enabled this will add ``--help`` as argument + if no arguments are passed :param hidden: hide this command from help outputs. :param deprecated: issues a message indicating that the command is deprecated. """ - def __init__(self, name, context_settings=None, callback=None, - params=None, help=None, epilog=None, short_help=None, - options_metavar='[OPTIONS]', add_help_option=True, - hidden=False, deprecated=False): - BaseCommand.__init__(self, name, context_settings) + def __init__( + self, + name: t.Optional[str], + context_settings: t.Optional[t.Dict[str, t.Any]] = None, + callback: t.Optional[t.Callable[..., t.Any]] = None, + params: t.Optional[t.List["Parameter"]] = None, + help: t.Optional[str] = None, + epilog: t.Optional[str] = None, + short_help: t.Optional[str] = None, + options_metavar: t.Optional[str] = "[OPTIONS]", + add_help_option: bool = True, + no_args_is_help: bool = False, + hidden: bool = False, + deprecated: bool = False, + ) -> None: + super().__init__(name, context_settings) #: the callback to execute when the command fires. This might be #: `None` in which case nothing happens. self.callback = callback #: the list of parameters for this command in the order they #: should show up in the help page and execute. Eager parameters #: will automatically be handled before non eager ones. - self.params = params or [] + self.params: t.List["Parameter"] = params or [] + # if a form feed (page break) is found in the help text, truncate help # text to the content preceding the first form feed - if help and '\f' in help: - help = help.split('\f', 1)[0] + if help and "\f" in help: + help = help.split("\f", 1)[0] + self.help = help self.epilog = epilog self.options_metavar = options_metavar self.short_help = short_help self.add_help_option = add_help_option + self.no_args_is_help = no_args_is_help self.hidden = hidden self.deprecated = deprecated - def get_usage(self, ctx): + def to_info_dict(self, ctx: Context) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict(ctx) + info_dict.update( + params=[param.to_info_dict() for param in self.get_params(ctx)], + help=self.help, + epilog=self.epilog, + short_help=self.short_help, + hidden=self.hidden, + deprecated=self.deprecated, + ) + return info_dict + + def get_usage(self, ctx: Context) -> str: + """Formats the usage line into a string and returns it. + + Calls :meth:`format_usage` internally. + """ formatter = ctx.make_formatter() self.format_usage(ctx, formatter) - return formatter.getvalue().rstrip('\n') + return formatter.getvalue().rstrip("\n") - def get_params(self, ctx): + def get_params(self, ctx: Context) -> t.List["Parameter"]: rv = self.params help_option = self.get_help_option(ctx) + if help_option is not None: - rv = rv + [help_option] + rv = [*rv, help_option] + return rv - def format_usage(self, ctx, formatter): - """Writes the usage line into the formatter.""" - pieces = self.collect_usage_pieces(ctx) - formatter.write_usage(ctx.command_path, ' '.join(pieces)) + def format_usage(self, ctx: Context, formatter: HelpFormatter) -> None: + """Writes the usage line into the formatter. - def collect_usage_pieces(self, ctx): + This is a low-level method called by :meth:`get_usage`. + """ + pieces = self.collect_usage_pieces(ctx) + formatter.write_usage(ctx.command_path, " ".join(pieces)) + + def collect_usage_pieces(self, ctx: Context) -> t.List[str]: """Returns all the pieces that go into the usage line and returns it as a list of strings. """ - rv = [self.options_metavar] + rv = [self.options_metavar] if self.options_metavar else [] + for param in self.get_params(ctx): rv.extend(param.get_usage_pieces(ctx)) + return rv - def get_help_option_names(self, ctx): + def get_help_option_names(self, ctx: Context) -> t.List[str]: """Returns the names for the help option.""" all_names = set(ctx.help_option_names) for param in self.params: all_names.difference_update(param.opts) all_names.difference_update(param.secondary_opts) - return all_names + return list(all_names) - def get_help_option(self, ctx): + def get_help_option(self, ctx: Context) -> t.Optional["Option"]: """Returns the help option object.""" help_options = self.get_help_option_names(ctx) - if not help_options or not self.add_help_option: - return - def show_help(ctx, param, value): + if not help_options or not self.add_help_option: + return None + + def show_help(ctx: Context, param: "Parameter", value: str) -> None: if value and not ctx.resilient_parsing: echo(ctx.get_help(), color=ctx.color) ctx.exit() - return Option(help_options, is_flag=True, - is_eager=True, expose_value=False, - callback=show_help, - help='Show this message and exit.') - def make_parser(self, ctx): + return Option( + help_options, + is_flag=True, + is_eager=True, + expose_value=False, + callback=show_help, + help=_("Show this message and exit."), + ) + + def make_parser(self, ctx: Context) -> OptionParser: """Creates the underlying option parser for this command.""" parser = OptionParser(ctx) for param in self.get_params(ctx): param.add_to_parser(parser, ctx) return parser - def get_help(self, ctx): - """Formats the help into a string and returns it. This creates a - formatter and will call into the following formatting methods: + def get_help(self, ctx: Context) -> str: + """Formats the help into a string and returns it. + + Calls :meth:`format_help` internally. """ formatter = ctx.make_formatter() self.format_help(ctx, formatter) - return formatter.getvalue().rstrip('\n') + return formatter.getvalue().rstrip("\n") - def get_short_help_str(self, limit=45): - """Gets short help for the command or makes it by shortening the long help string.""" - return self.short_help or self.help and make_default_short_help(self.help, limit) or '' + def get_short_help_str(self, limit: int = 45) -> str: + """Gets short help for the command or makes it by shortening the + long help string. + """ + text = self.short_help or "" - def format_help(self, ctx, formatter): + if not text and self.help: + text = make_default_short_help(self.help, limit) + + if self.deprecated: + text = _("(Deprecated) {text}").format(text=text) + + return text.strip() + + def format_help(self, ctx: Context, formatter: HelpFormatter) -> None: """Writes the help into the formatter if it exists. - This calls into the following methods: + This is a low-level method called by :meth:`get_help`. + + This calls the following methods: - :meth:`format_usage` - :meth:`format_help_text` @@ -898,21 +1326,20 @@ class Command(BaseCommand): self.format_options(ctx, formatter) self.format_epilog(ctx, formatter) - def format_help_text(self, ctx, formatter): + def format_help_text(self, ctx: Context, formatter: HelpFormatter) -> None: """Writes the help text to the formatter if it exists.""" - if self.help: - formatter.write_paragraph() - with formatter.indentation(): - help_text = self.help - if self.deprecated: - help_text += DEPRECATED_HELP_NOTICE - formatter.write_text(help_text) - elif self.deprecated: - formatter.write_paragraph() - with formatter.indentation(): - formatter.write_text(DEPRECATED_HELP_NOTICE) + text = self.help or "" - def format_options(self, ctx, formatter): + if self.deprecated: + text = _("(Deprecated) {text}").format(text=text) + + if text: + formatter.write_paragraph() + + with formatter.indentation(): + formatter.write_text(text) + + def format_options(self, ctx: Context, formatter: HelpFormatter) -> None: """Writes all the options into the formatter if they exist.""" opts = [] for param in self.get_params(ctx): @@ -921,40 +1348,87 @@ class Command(BaseCommand): opts.append(rv) if opts: - with formatter.section('Options'): + with formatter.section(_("Options")): formatter.write_dl(opts) - def format_epilog(self, ctx, formatter): + def format_epilog(self, ctx: Context, formatter: HelpFormatter) -> None: """Writes the epilog into the formatter if it exists.""" if self.epilog: formatter.write_paragraph() with formatter.indentation(): formatter.write_text(self.epilog) - def parse_args(self, ctx, args): + def parse_args(self, ctx: Context, args: t.List[str]) -> t.List[str]: + if not args and self.no_args_is_help and not ctx.resilient_parsing: + echo(ctx.get_help(), color=ctx.color) + ctx.exit() + parser = self.make_parser(ctx) opts, args, param_order = parser.parse_args(args=args) - for param in iter_params_for_processing( - param_order, self.get_params(ctx)): + for param in iter_params_for_processing(param_order, self.get_params(ctx)): value, args = param.handle_parse_result(ctx, opts, args) if args and not ctx.allow_extra_args and not ctx.resilient_parsing: - ctx.fail('Got unexpected extra argument%s (%s)' - % (len(args) != 1 and 's' or '', - ' '.join(map(make_str, args)))) + ctx.fail( + ngettext( + "Got unexpected extra argument ({args})", + "Got unexpected extra arguments ({args})", + len(args), + ).format(args=" ".join(map(str, args))) + ) ctx.args = args return args - def invoke(self, ctx): + def invoke(self, ctx: Context) -> t.Any: """Given a context, this invokes the attached callback (if it exists) in the right way. """ - _maybe_show_deprecated_notice(self) + if self.deprecated: + message = _( + "DeprecationWarning: The command {name!r} is deprecated." + ).format(name=self.name) + echo(style(message, fg="red"), err=True) + if self.callback is not None: return ctx.invoke(self.callback, **ctx.params) + def shell_complete(self, ctx: Context, incomplete: str) -> t.List["CompletionItem"]: + """Return a list of completions for the incomplete value. Looks + at the names of options and chained multi-commands. + + :param ctx: Invocation context for this command. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from pipenv.vendor.click.shell_completion import CompletionItem + + results: t.List["CompletionItem"] = [] + + if incomplete and not incomplete[0].isalnum(): + for param in self.get_params(ctx): + if ( + not isinstance(param, Option) + or param.hidden + or ( + not param.multiple + and ctx.get_parameter_source(param.name) # type: ignore + is ParameterSource.COMMANDLINE + ) + ): + continue + + results.extend( + CompletionItem(name, help=param.help) + for name in [*param.opts, *param.secondary_opts] + if name.startswith(incomplete) + ) + + results.extend(super().shell_complete(ctx, incomplete)) + return results + class MultiCommand(Command): """A multi command is the basic implementation of a command that @@ -976,48 +1450,81 @@ class MultiCommand(Command): is enabled. This restricts the form of commands in that they cannot have optional arguments but it allows multiple commands to be chained together. - :param result_callback: the result callback to attach to this multi - command. + :param result_callback: The result callback to attach to this multi + command. This can be set or changed later with the + :meth:`result_callback` decorator. """ + allow_extra_args = True allow_interspersed_args = False - def __init__(self, name=None, invoke_without_command=False, - no_args_is_help=None, subcommand_metavar=None, - chain=False, result_callback=None, **attrs): - Command.__init__(self, name, **attrs) + def __init__( + self, + name: t.Optional[str] = None, + invoke_without_command: bool = False, + no_args_is_help: t.Optional[bool] = None, + subcommand_metavar: t.Optional[str] = None, + chain: bool = False, + result_callback: t.Optional[t.Callable[..., t.Any]] = None, + **attrs: t.Any, + ) -> None: + super().__init__(name, **attrs) + if no_args_is_help is None: no_args_is_help = not invoke_without_command + self.no_args_is_help = no_args_is_help self.invoke_without_command = invoke_without_command + if subcommand_metavar is None: if chain: - subcommand_metavar = SUBCOMMANDS_METAVAR + subcommand_metavar = "COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]..." else: - subcommand_metavar = SUBCOMMAND_METAVAR + subcommand_metavar = "COMMAND [ARGS]..." + self.subcommand_metavar = subcommand_metavar self.chain = chain - #: The result callback that is stored. This can be set or - #: overridden with the :func:`resultcallback` decorator. - self.result_callback = result_callback + # The result callback that is stored. This can be set or + # overridden with the :func:`result_callback` decorator. + self._result_callback = result_callback if self.chain: for param in self.params: if isinstance(param, Argument) and not param.required: - raise RuntimeError('Multi commands in chain mode cannot ' - 'have optional arguments.') + raise RuntimeError( + "Multi commands in chain mode cannot have" + " optional arguments." + ) - def collect_usage_pieces(self, ctx): - rv = Command.collect_usage_pieces(self, ctx) + def to_info_dict(self, ctx: Context) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict(ctx) + commands = {} + + for name in self.list_commands(ctx): + command = self.get_command(ctx, name) + + if command is None: + continue + + sub_ctx = ctx._make_sub_context(command) + + with sub_ctx.scope(cleanup=False): + commands[name] = command.to_info_dict(sub_ctx) + + info_dict.update(commands=commands, chain=self.chain) + return info_dict + + def collect_usage_pieces(self, ctx: Context) -> t.List[str]: + rv = super().collect_usage_pieces(ctx) rv.append(self.subcommand_metavar) return rv - def format_options(self, ctx, formatter): - Command.format_options(self, ctx, formatter) + def format_options(self, ctx: Context, formatter: HelpFormatter) -> None: + super().format_options(ctx, formatter) self.format_commands(ctx, formatter) - def resultcallback(self, replace=False): - """Adds a result callback to the chain command. By default if a + def result_callback(self, replace: bool = False) -> t.Callable[[F], F]: + """Adds a result callback to the command. By default if a result callback is already registered this will chain them but this can be disabled with the `replace` parameter. The result callback is invoked with the return value of the subcommand @@ -1032,28 +1539,47 @@ class MultiCommand(Command): def cli(input): return 42 - @cli.resultcallback() + @cli.result_callback() def process_result(result, input): return result + input - .. versionadded:: 3.0 - :param replace: if set to `True` an already existing result callback will be removed. + + .. versionchanged:: 8.0 + Renamed from ``resultcallback``. + + .. versionadded:: 3.0 """ - def decorator(f): - old_callback = self.result_callback + + def decorator(f: F) -> F: + old_callback = self._result_callback + if old_callback is None or replace: - self.result_callback = f + self._result_callback = f return f - def function(__value, *args, **kwargs): - return f(old_callback(__value, *args, **kwargs), - *args, **kwargs) - self.result_callback = rv = update_wrapper(function, f) + + def function(__value, *args, **kwargs): # type: ignore + inner = old_callback(__value, *args, **kwargs) # type: ignore + return f(inner, *args, **kwargs) + + self._result_callback = rv = update_wrapper(t.cast(F, function), f) return rv + return decorator - def format_commands(self, ctx, formatter): + def resultcallback(self, replace: bool = False) -> t.Callable[[F], F]: + import warnings + + warnings.warn( + "'resultcallback' has been renamed to 'result_callback'." + " The old name will be removed in Click 8.1.", + DeprecationWarning, + stacklevel=2, + ) + return self.result_callback(replace=replace) + + def format_commands(self, ctx: Context, formatter: HelpFormatter) -> None: """Extra format methods for multi methods that adds all the commands after the options. """ @@ -1078,15 +1604,16 @@ class MultiCommand(Command): rows.append((subcommand, help)) if rows: - with formatter.section('Commands'): + with formatter.section(_("Commands")): formatter.write_dl(rows) - def parse_args(self, ctx, args): + def parse_args(self, ctx: Context, args: t.List[str]) -> t.List[str]: if not args and self.no_args_is_help and not ctx.resilient_parsing: echo(ctx.get_help(), color=ctx.color) ctx.exit() - rest = Command.parse_args(self, ctx, args) + rest = super().parse_args(ctx, args) + if self.chain: ctx.protected_args = rest ctx.args = [] @@ -1095,30 +1622,24 @@ class MultiCommand(Command): return ctx.args - def invoke(self, ctx): - def _process_result(value): - if self.result_callback is not None: - value = ctx.invoke(self.result_callback, value, - **ctx.params) + def invoke(self, ctx: Context) -> t.Any: + def _process_result(value: t.Any) -> t.Any: + if self._result_callback is not None: + value = ctx.invoke(self._result_callback, value, **ctx.params) return value if not ctx.protected_args: - # If we are invoked without command the chain flag controls - # how this happens. If we are not in chain mode, the return - # value here is the return value of the command. - # If however we are in chain mode, the return value is the - # return value of the result processor invoked with an empty - # list (which means that no subcommand actually was executed). if self.invoke_without_command: - if not self.chain: - return Command.invoke(self, ctx) + # No subcommand was invoked, so the result callback is + # invoked with None for regular groups, or an empty list + # for chained groups. with ctx: - Command.invoke(self, ctx) - return _process_result([]) - ctx.fail('Missing command.') + super().invoke(ctx) + return _process_result([] if self.chain else None) + ctx.fail(_("Missing command.")) # Fetch args back out - args = ctx.protected_args + ctx.args + args = [*ctx.protected_args, *ctx.args] ctx.args = [] ctx.protected_args = [] @@ -1130,8 +1651,9 @@ class MultiCommand(Command): # resources until the result processor has worked. with ctx: cmd_name, cmd, args = self.resolve_command(ctx, args) + assert cmd is not None ctx.invoked_subcommand = cmd_name - Command.invoke(self, ctx) + super().invoke(ctx) sub_ctx = cmd.make_context(cmd_name, args, parent=ctx) with sub_ctx: return _process_result(sub_ctx.command.invoke(sub_ctx)) @@ -1142,8 +1664,8 @@ class MultiCommand(Command): # set to ``*`` to inform the command that subcommands are executed # but nothing else. with ctx: - ctx.invoked_subcommand = args and '*' or None - Command.invoke(self, ctx) + ctx.invoked_subcommand = "*" if args else None + super().invoke(ctx) # Otherwise we make every single context and invoke them in a # chain. In that case the return value to the result processor @@ -1151,9 +1673,14 @@ class MultiCommand(Command): contexts = [] while args: cmd_name, cmd, args = self.resolve_command(ctx, args) - sub_ctx = cmd.make_context(cmd_name, args, parent=ctx, - allow_extra_args=True, - allow_interspersed_args=False) + assert cmd is not None + sub_ctx = cmd.make_context( + cmd_name, + args, + parent=ctx, + allow_extra_args=True, + allow_interspersed_args=False, + ) contexts.append(sub_ctx) args, sub_ctx.args = sub_ctx.args, [] @@ -1163,7 +1690,9 @@ class MultiCommand(Command): rv.append(sub_ctx.command.invoke(sub_ctx)) return _process_result(rv) - def resolve_command(self, ctx, args): + def resolve_command( + self, ctx: Context, args: t.List[str] + ) -> t.Tuple[t.Optional[str], t.Optional[Command], t.List[str]]: cmd_name = make_str(args[0]) original_cmd_name = cmd_name @@ -1185,73 +1714,162 @@ class MultiCommand(Command): if cmd is None and not ctx.resilient_parsing: if split_opt(cmd_name)[0]: self.parse_args(ctx, ctx.args) - ctx.fail('No such command "%s".' % original_cmd_name) + ctx.fail(_("No such command {name!r}.").format(name=original_cmd_name)) + return cmd_name if cmd else None, cmd, args[1:] - return cmd_name, cmd, args[1:] - - def get_command(self, ctx, cmd_name): + def get_command(self, ctx: Context, cmd_name: str) -> t.Optional[Command]: """Given a context and a command name, this returns a :class:`Command` object if it exists or returns `None`. """ - raise NotImplementedError() + raise NotImplementedError - def list_commands(self, ctx): + def list_commands(self, ctx: Context) -> t.List[str]: """Returns a list of subcommand names in the order they should appear. """ return [] + def shell_complete(self, ctx: Context, incomplete: str) -> t.List["CompletionItem"]: + """Return a list of completions for the incomplete value. Looks + at the names of options, subcommands, and chained + multi-commands. + + :param ctx: Invocation context for this command. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from pipenv.vendor.click.shell_completion import CompletionItem + + results = [ + CompletionItem(name, help=command.get_short_help_str()) + for name, command in _complete_visible_commands(ctx, incomplete) + ] + results.extend(super().shell_complete(ctx, incomplete)) + return results + class Group(MultiCommand): - """A group allows a command to have subcommands attached. This is the - most common way to implement nesting in Click. + """A group allows a command to have subcommands attached. This is + the most common way to implement nesting in Click. - :param commands: a dictionary of commands. + :param name: The name of the group command. + :param commands: A dict mapping names to :class:`Command` objects. + Can also be a list of :class:`Command`, which will use + :attr:`Command.name` to create the dict. + :param attrs: Other command arguments described in + :class:`MultiCommand`, :class:`Command`, and + :class:`BaseCommand`. + + .. versionchanged:: 8.0 + The ``commmands`` argument can be a list of command objects. """ - def __init__(self, name=None, commands=None, **attrs): - MultiCommand.__init__(self, name, **attrs) - #: the registered subcommands by their exported names. - self.commands = commands or {} + #: If set, this is used by the group's :meth:`command` decorator + #: as the default :class:`Command` class. This is useful to make all + #: subcommands use a custom command class. + #: + #: .. versionadded:: 8.0 + command_class: t.Optional[t.Type[Command]] = None - def add_command(self, cmd, name=None): + #: If set, this is used by the group's :meth:`group` decorator + #: as the default :class:`Group` class. This is useful to make all + #: subgroups use a custom group class. + #: + #: If set to the special value :class:`type` (literally + #: ``group_class = type``), this group's class will be used as the + #: default class. This makes a custom group class continue to make + #: custom groups. + #: + #: .. versionadded:: 8.0 + group_class: t.Optional[t.Union[t.Type["Group"], t.Type[type]]] = None + # Literal[type] isn't valid, so use Type[type] + + def __init__( + self, + name: t.Optional[str] = None, + commands: t.Optional[t.Union[t.Dict[str, Command], t.Sequence[Command]]] = None, + **attrs: t.Any, + ) -> None: + super().__init__(name, **attrs) + + if commands is None: + commands = {} + elif isinstance(commands, abc.Sequence): + commands = {c.name: c for c in commands if c.name is not None} + + #: The registered subcommands by their exported names. + self.commands: t.Dict[str, Command] = commands + + def add_command(self, cmd: Command, name: t.Optional[str] = None) -> None: """Registers another :class:`Command` with this group. If the name is not provided, the name of the command is used. """ name = name or cmd.name if name is None: - raise TypeError('Command has no name.') + raise TypeError("Command has no name.") _check_multicommand(self, name, cmd, register=True) self.commands[name] = cmd - def command(self, *args, **kwargs): + def command( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[t.Callable[..., t.Any]], Command]: """A shortcut decorator for declaring and attaching a command to - the group. This takes the same arguments as :func:`command` but - immediately registers the created command with this instance by - calling into :meth:`add_command`. + the group. This takes the same arguments as :func:`command` and + immediately registers the created command with this group by + calling :meth:`add_command`. + + To customize the command class used, set the + :attr:`command_class` attribute. + + .. versionchanged:: 8.0 + Added the :attr:`command_class` attribute. """ - def decorator(f): + from .decorators import command + + if self.command_class is not None and "cls" not in kwargs: + kwargs["cls"] = self.command_class + + def decorator(f: t.Callable[..., t.Any]) -> Command: cmd = command(*args, **kwargs)(f) self.add_command(cmd) return cmd + return decorator - def group(self, *args, **kwargs): + def group( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[t.Callable[..., t.Any]], "Group"]: """A shortcut decorator for declaring and attaching a group to - the group. This takes the same arguments as :func:`group` but - immediately registers the created command with this instance by - calling into :meth:`add_command`. + the group. This takes the same arguments as :func:`group` and + immediately registers the created group with this group by + calling :meth:`add_command`. + + To customize the group class used, set the :attr:`group_class` + attribute. + + .. versionchanged:: 8.0 + Added the :attr:`group_class` attribute. """ - def decorator(f): + from .decorators import group + + if self.group_class is not None and "cls" not in kwargs: + if self.group_class is type: + kwargs["cls"] = type(self) + else: + kwargs["cls"] = self.group_class + + def decorator(f: t.Callable[..., t.Any]) -> "Group": cmd = group(*args, **kwargs)(f) self.add_command(cmd) return cmd + return decorator - def get_command(self, ctx, cmd_name): + def get_command(self, ctx: Context, cmd_name: str) -> t.Optional[Command]: return self.commands.get(cmd_name) - def list_commands(self, ctx): + def list_commands(self, ctx: Context) -> t.List[str]: return sorted(self.commands) @@ -1262,31 +1880,52 @@ class CommandCollection(MultiCommand): provides all the commands for each of them. """ - def __init__(self, name=None, sources=None, **attrs): - MultiCommand.__init__(self, name, **attrs) + def __init__( + self, + name: t.Optional[str] = None, + sources: t.Optional[t.List[MultiCommand]] = None, + **attrs: t.Any, + ) -> None: + super().__init__(name, **attrs) #: The list of registered multi commands. - self.sources = sources or [] + self.sources: t.List[MultiCommand] = sources or [] - def add_source(self, multi_cmd): + def add_source(self, multi_cmd: MultiCommand) -> None: """Adds a new multi command to the chain dispatcher.""" self.sources.append(multi_cmd) - def get_command(self, ctx, cmd_name): + def get_command(self, ctx: Context, cmd_name: str) -> t.Optional[Command]: for source in self.sources: rv = source.get_command(ctx, cmd_name) + if rv is not None: if self.chain: _check_multicommand(self, cmd_name, rv) + return rv - def list_commands(self, ctx): - rv = set() + return None + + def list_commands(self, ctx: Context) -> t.List[str]: + rv: t.Set[str] = set() + for source in self.sources: rv.update(source.list_commands(ctx)) + return sorted(rv) -class Parameter(object): +def _check_iter(value: t.Any) -> t.Iterator[t.Any]: + """Check if the value is iterable but not a string. Raises a type + error, or return an iterator over the value. + """ + if isinstance(value, str): + raise TypeError + + return iter(value) + + +class Parameter: r"""A parameter to a command comes in two versions: they are either :class:`Option`\s or :class:`Argument`\s. Other subclasses are currently not supported by design as some of the internals for parsing are @@ -1294,12 +1933,6 @@ class Parameter(object): Some settings are supported by both options and arguments. - .. versionchanged:: 2.0 - Changed signature for parameter callback to also be passed the - parameter. In Click 2.0, the old callback format will still work, - but it will raise a warning to give you change to migrate the - code easier. - :param param_decls: the parameter declarations for this option or argument. This is a list of flags or argument names. @@ -1310,14 +1943,15 @@ class Parameter(object): :param default: the default value if omitted. This can also be a callable, in which case it's invoked when the default is needed without any arguments. - :param callback: a callback that should be executed after the parameter - was matched. This is called as ``fn(ctx, param, - value)`` and needs to return the value. Before Click - 2.0, the signature was ``(ctx, value)``. + :param callback: A function to further process or validate the value + after type conversion. It is called as ``f(ctx, param, value)`` + and must return the value. It is called for all sources, + including prompts. :param nargs: the number of arguments to match. If not ``1`` the return value is a tuple instead of single value. The default for nargs is ``1`` (except if the type is a tuple, then it's - the arity of the tuple). + the arity of the tuple). If ``nargs=-1``, all remaining + parameters are collected. :param metavar: how the value is represented in the help page. :param expose_value: if this is `True` then the value is passed onwards to the command callback and stored on the context, @@ -1327,17 +1961,75 @@ class Parameter(object): order of processing. :param envvar: a string or list of strings that are environment variables that should be checked. + :param shell_complete: A function that returns custom shell + completions. Used instead of the param's type completion if + given. Takes ``ctx, param, incomplete`` and must return a list + of :class:`~click.shell_completion.CompletionItem` or a list of + strings. + + .. versionchanged:: 8.0 + ``process_value`` validates required parameters and bounded + ``nargs``, and invokes the parameter callback before returning + the value. This allows the callback to validate prompts. + ``full_process_value`` is removed. + + .. versionchanged:: 8.0 + ``autocompletion`` is renamed to ``shell_complete`` and has new + semantics described above. The old name is deprecated and will + be removed in 8.1, until then it will be wrapped to match the + new requirements. + + .. versionchanged:: 8.0 + For ``multiple=True, nargs>1``, the default must be a list of + tuples. + + .. versionchanged:: 8.0 + Setting a default is no longer required for ``nargs>1``, it will + default to ``None``. ``multiple=True`` or ``nargs=-1`` will + default to ``()``. + + .. versionchanged:: 7.1 + Empty environment variables are ignored rather than taking the + empty string value. This makes it possible for scripts to clear + variables if they can't unset them. + + .. versionchanged:: 2.0 + Changed signature for parameter callback to also be passed the + parameter. The old callback format will still work, but it will + raise a warning to give you a chance to migrate the code easier. """ - param_type_name = 'parameter' - def __init__(self, param_decls=None, type=None, required=False, - default=None, callback=None, nargs=None, metavar=None, - expose_value=True, is_eager=False, envvar=None, - autocompletion=None): - self.name, self.opts, self.secondary_opts = \ - self._parse_decls(param_decls or (), expose_value) + param_type_name = "parameter" - self.type = convert_type(type, default) + def __init__( + self, + param_decls: t.Optional[t.Sequence[str]] = None, + type: t.Optional[t.Union[types.ParamType, t.Any]] = None, + required: bool = False, + default: t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]] = None, + callback: t.Optional[t.Callable[[Context, "Parameter", t.Any], t.Any]] = None, + nargs: t.Optional[int] = None, + multiple: bool = False, + metavar: t.Optional[str] = None, + expose_value: bool = True, + is_eager: bool = False, + envvar: t.Optional[t.Union[str, t.Sequence[str]]] = None, + shell_complete: t.Optional[ + t.Callable[ + [Context, "Parameter", str], + t.Union[t.List["CompletionItem"], t.List[str]], + ] + ] = None, + autocompletion: t.Optional[ + t.Callable[ + [Context, t.List[str], str], t.List[t.Union[t.Tuple[str, str], str]] + ] + ] = None, + ) -> None: + self.name, self.opts, self.secondary_opts = self._parse_decls( + param_decls or (), expose_value + ) + self.type = types.convert_type(type, default) # Default nargs to what the type tells us if we have that # information available. @@ -1350,151 +2042,355 @@ class Parameter(object): self.required = required self.callback = callback self.nargs = nargs - self.multiple = False + self.multiple = multiple self.expose_value = expose_value self.default = default self.is_eager = is_eager self.metavar = metavar self.envvar = envvar - self.autocompletion = autocompletion + + if autocompletion is not None: + import warnings + + warnings.warn( + "'autocompletion' is renamed to 'shell_complete'. The old name is" + " deprecated and will be removed in Click 8.1. See the docs about" + " 'Parameter' for information about new behavior.", + DeprecationWarning, + stacklevel=2, + ) + + def shell_complete( + ctx: Context, param: "Parameter", incomplete: str + ) -> t.List["CompletionItem"]: + from pipenv.vendor.click.shell_completion import CompletionItem + + out = [] + + for c in autocompletion(ctx, [], incomplete): # type: ignore + if isinstance(c, tuple): + c = CompletionItem(c[0], help=c[1]) + elif isinstance(c, str): + c = CompletionItem(c) + + if c.value.startswith(incomplete): + out.append(c) + + return out + + self._custom_shell_complete = shell_complete + + if __debug__: + if self.type.is_composite and nargs != self.type.arity: + raise ValueError( + f"'nargs' must be {self.type.arity} (or None) for" + f" type {self.type!r}, but it was {nargs}." + ) + + # Skip no default or callable default. + check_default = default if not callable(default) else None + + if check_default is not None: + if multiple: + try: + # Only check the first value against nargs. + check_default = next(_check_iter(check_default), None) + except TypeError: + raise ValueError( + "'default' must be a list when 'multiple' is true." + ) from None + + # Can be None for multiple with empty default. + if nargs != 1 and check_default is not None: + try: + _check_iter(check_default) + except TypeError: + if multiple: + message = ( + "'default' must be a list of lists when 'multiple' is" + " true and 'nargs' != 1." + ) + else: + message = "'default' must be a list when 'nargs' != 1." + + raise ValueError(message) from None + + if nargs > 1 and len(check_default) != nargs: + subject = "item length" if multiple else "length" + raise ValueError( + f"'default' {subject} must match nargs={nargs}." + ) + + def to_info_dict(self) -> t.Dict[str, t.Any]: + """Gather information that could be useful for a tool generating + user-facing documentation. + + Use :meth:`click.Context.to_info_dict` to traverse the entire + CLI structure. + + .. versionadded:: 8.0 + """ + return { + "name": self.name, + "param_type_name": self.param_type_name, + "opts": self.opts, + "secondary_opts": self.secondary_opts, + "type": self.type.to_info_dict(), + "required": self.required, + "nargs": self.nargs, + "multiple": self.multiple, + "default": self.default, + "envvar": self.envvar, + } + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} {self.name}>" + + def _parse_decls( + self, decls: t.Sequence[str], expose_value: bool + ) -> t.Tuple[t.Optional[str], t.List[str], t.List[str]]: + raise NotImplementedError() @property - def human_readable_name(self): + def human_readable_name(self) -> str: """Returns the human readable name of this parameter. This is the same as the name for options, but the metavar for arguments. """ - return self.name + return self.name # type: ignore - def make_metavar(self): + def make_metavar(self) -> str: if self.metavar is not None: return self.metavar + metavar = self.type.get_metavar(self) + if metavar is None: metavar = self.type.name.upper() + if self.nargs != 1: - metavar += '...' + metavar += "..." + return metavar - def get_default(self, ctx): - """Given a context variable this calculates the default value.""" - # Otherwise go with the regular default. - if callable(self.default): - rv = self.default() - else: - rv = self.default - return self.type_cast_value(ctx, rv) + @typing.overload + def get_default( + self, ctx: Context, call: "te.Literal[True]" = True + ) -> t.Optional[t.Any]: + ... - def add_to_parser(self, parser, ctx): - pass + @typing.overload + def get_default( + self, ctx: Context, call: bool = ... + ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]: + ... + + def get_default( + self, ctx: Context, call: bool = True + ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]: + """Get the default for the parameter. Tries + :meth:`Context.lookup_default` first, then the local default. + + :param ctx: Current context. + :param call: If the default is a callable, call it. Disable to + return the callable instead. + + .. versionchanged:: 8.0.2 + Type casting is no longer performed when getting a default. + + .. versionchanged:: 8.0.1 + Type casting can fail in resilient parsing mode. Invalid + defaults will not prevent showing help text. + + .. versionchanged:: 8.0 + Looks at ``ctx.default_map`` first. + + .. versionchanged:: 8.0 + Added the ``call`` parameter. + """ + value = ctx.lookup_default(self.name, call=False) # type: ignore - def consume_value(self, ctx, opts): - value = opts.get(self.name) if value is None: - value = self.value_from_envvar(ctx) - if value is None: - value = ctx.lookup_default(self.name) + value = self.default + + if call and callable(value): + value = value() + return value - def type_cast_value(self, ctx, value): - """Given a value this runs it properly through the type system. - This automatically handles things like `nargs` and `multiple` as - well as composite types. + def add_to_parser(self, parser: OptionParser, ctx: Context) -> None: + raise NotImplementedError() + + def consume_value( + self, ctx: Context, opts: t.Mapping[str, t.Any] + ) -> t.Tuple[t.Any, ParameterSource]: + value = opts.get(self.name) # type: ignore + source = ParameterSource.COMMANDLINE + + if value is None: + value = self.value_from_envvar(ctx) + source = ParameterSource.ENVIRONMENT + + if value is None: + value = ctx.lookup_default(self.name) # type: ignore + source = ParameterSource.DEFAULT_MAP + + if value is None: + value = self.get_default(ctx) + source = ParameterSource.DEFAULT + + return value, source + + def type_cast_value(self, ctx: Context, value: t.Any) -> t.Any: + """Convert and validate a value against the option's + :attr:`type`, :attr:`multiple`, and :attr:`nargs`. """ - if self.type.is_composite: - if self.nargs <= 1: - raise TypeError('Attempted to invoke composite type ' - 'but nargs has been set to %s. This is ' - 'not supported; nargs needs to be set to ' - 'a fixed value > 1.' % self.nargs) - if self.multiple: - return tuple(self.type(x or (), self, ctx) for x in value or ()) - return self.type(value or (), self, ctx) + if value is None: + return () if self.multiple or self.nargs == -1 else None - def _convert(value, level): - if level == 0: - return self.type(value, self, ctx) - return tuple(_convert(x, level - 1) for x in value or ()) - return _convert(value, (self.nargs != 1) + bool(self.multiple)) + def check_iter(value: t.Any) -> t.Iterator: + try: + return _check_iter(value) + except TypeError: + # This should only happen when passing in args manually, + # the parser should construct an iterable when parsing + # the command line. + raise BadParameter( + _("Value must be an iterable."), ctx=ctx, param=self + ) from None - def process_value(self, ctx, value): - """Given a value and context this runs the logic to convert the - value as necessary. - """ - # If the value we were given is None we do nothing. This way - # code that calls this can easily figure out if something was - # not provided. Otherwise it would be converted into an empty - # tuple for multiple invocations which is inconvenient. - if value is not None: - return self.type_cast_value(ctx, value) + if self.nargs == 1 or self.type.is_composite: + convert: t.Callable[[t.Any], t.Any] = partial( + self.type, param=self, ctx=ctx + ) + elif self.nargs == -1: - def value_is_missing(self, value): + def convert(value: t.Any) -> t.Tuple: + return tuple(self.type(x, self, ctx) for x in check_iter(value)) + + else: # nargs > 1 + + def convert(value: t.Any) -> t.Tuple: + value = tuple(check_iter(value)) + + if len(value) != self.nargs: + raise BadParameter( + ngettext( + "Takes {nargs} values but 1 was given.", + "Takes {nargs} values but {len} were given.", + len(value), + ).format(nargs=self.nargs, len=len(value)), + ctx=ctx, + param=self, + ) + + return tuple(self.type(x, self, ctx) for x in value) + + if self.multiple: + return tuple(convert(x) for x in check_iter(value)) + + return convert(value) + + def value_is_missing(self, value: t.Any) -> bool: if value is None: return True + if (self.nargs != 1 or self.multiple) and value == (): return True + return False - def full_process_value(self, ctx, value): - value = self.process_value(ctx, value) - - if value is None and not ctx.resilient_parsing: - value = self.get_default(ctx) + def process_value(self, ctx: Context, value: t.Any) -> t.Any: + value = self.type_cast_value(ctx, value) if self.required and self.value_is_missing(value): raise MissingParameter(ctx=ctx, param=self) + if self.callback is not None: + value = self.callback(ctx, self, value) + return value - def resolve_envvar_value(self, ctx): + def resolve_envvar_value(self, ctx: Context) -> t.Optional[str]: if self.envvar is None: - return - if isinstance(self.envvar, (tuple, list)): + return None + + if isinstance(self.envvar, str): + rv = os.environ.get(self.envvar) + + if rv: + return rv + else: for envvar in self.envvar: rv = os.environ.get(envvar) - if rv is not None: - return rv - else: - return os.environ.get(self.envvar) - def value_from_envvar(self, ctx): - rv = self.resolve_envvar_value(ctx) + if rv: + return rv + + return None + + def value_from_envvar(self, ctx: Context) -> t.Optional[t.Any]: + rv: t.Optional[t.Any] = self.resolve_envvar_value(ctx) + if rv is not None and self.nargs != 1: rv = self.type.split_envvar_value(rv) + return rv - def handle_parse_result(self, ctx, opts, args): + def handle_parse_result( + self, ctx: Context, opts: t.Mapping[str, t.Any], args: t.List[str] + ) -> t.Tuple[t.Any, t.List[str]]: with augment_usage_errors(ctx, param=self): - value = self.consume_value(ctx, opts) + value, source = self.consume_value(ctx, opts) + ctx.set_parameter_source(self.name, source) # type: ignore + try: - value = self.full_process_value(ctx, value) + value = self.process_value(ctx, value) except Exception: if not ctx.resilient_parsing: raise + value = None - if self.callback is not None: - try: - value = invoke_param_callback( - self.callback, ctx, self, value) - except Exception: - if not ctx.resilient_parsing: - raise if self.expose_value: - ctx.params[self.name] = value + ctx.params[self.name] = value # type: ignore + return value, args - def get_help_record(self, ctx): + def get_help_record(self, ctx: Context) -> t.Optional[t.Tuple[str, str]]: pass - def get_usage_pieces(self, ctx): + def get_usage_pieces(self, ctx: Context) -> t.List[str]: return [] - def get_error_hint(self, ctx): + def get_error_hint(self, ctx: Context) -> str: """Get a stringified version of the param for use in error messages to indicate which param caused the error. """ hint_list = self.opts or [self.human_readable_name] - return ' / '.join('"%s"' % x for x in hint_list) + return " / ".join(f"'{x}'" for x in hint_list) + + def shell_complete(self, ctx: Context, incomplete: str) -> t.List["CompletionItem"]: + """Return a list of completions for the incomplete value. If a + ``shell_complete`` function was given during init, it is used. + Otherwise, the :attr:`type` + :meth:`~click.types.ParamType.shell_complete` function is used. + + :param ctx: Invocation context for this command. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + if self._custom_shell_complete is not None: + results = self._custom_shell_complete(ctx, self, incomplete) + + if results and isinstance(results[0], str): + from pipenv.vendor.click.shell_completion import CompletionItem + + results = [CompletionItem(c) for c in results] + + return t.cast(t.List["CompletionItem"], results) + + return self.type.shell_complete(ctx, self, incomplete) class Option(Parameter): @@ -1513,8 +2409,12 @@ class Option(Parameter): :param prompt: if set to `True` or a non empty string then the user will be prompted for input. If set to `True` the prompt will be the option name capitalized. - :param confirmation_prompt: if set then the value will need to be confirmed - if it was prompted for. + :param confirmation_prompt: Prompt a second time to confirm the + value if it was prompted for. Can be set to a string instead of + ``True`` to customize the message. + :param prompt_required: If set to ``False``, the user will be + prompted for input only when the option was specified as a flag + without a value. :param hide_input: if this is `True` then the input on the prompt will be hidden from the user. This is useful for password input. @@ -1534,96 +2434,149 @@ class Option(Parameter): context. :param help: the help string. :param hidden: hide this option from help outputs. - """ - param_type_name = 'option' - def __init__(self, param_decls=None, show_default=False, - prompt=False, confirmation_prompt=False, - hide_input=False, is_flag=None, flag_value=None, - multiple=False, count=False, allow_from_autoenv=True, - type=None, help=None, hidden=False, show_choices=True, - show_envvar=False, **attrs): - default_is_missing = attrs.get('default', _missing) is _missing - Parameter.__init__(self, param_decls, type=type, **attrs) + .. versionchanged:: 8.0.1 + ``type`` is detected from ``flag_value`` if given. + """ + + param_type_name = "option" + + def __init__( + self, + param_decls: t.Optional[t.Sequence[str]] = None, + show_default: t.Union[bool, str] = False, + prompt: t.Union[bool, str] = False, + confirmation_prompt: t.Union[bool, str] = False, + prompt_required: bool = True, + hide_input: bool = False, + is_flag: t.Optional[bool] = None, + flag_value: t.Optional[t.Any] = None, + multiple: bool = False, + count: bool = False, + allow_from_autoenv: bool = True, + type: t.Optional[t.Union[types.ParamType, t.Any]] = None, + help: t.Optional[str] = None, + hidden: bool = False, + show_choices: bool = True, + show_envvar: bool = False, + **attrs: t.Any, + ) -> None: + default_is_missing = "default" not in attrs + super().__init__(param_decls, type=type, multiple=multiple, **attrs) if prompt is True: - prompt_text = self.name.replace('_', ' ').capitalize() + if self.name is None: + raise TypeError("'name' is required with 'prompt=True'.") + + prompt_text: t.Optional[str] = self.name.replace("_", " ").capitalize() elif prompt is False: prompt_text = None else: - prompt_text = prompt + prompt_text = t.cast(str, prompt) + self.prompt = prompt_text self.confirmation_prompt = confirmation_prompt + self.prompt_required = prompt_required self.hide_input = hide_input self.hidden = hidden - # Flags + # If prompt is enabled but not required, then the option can be + # used as a flag to indicate using prompt or flag_value. + self._flag_needs_value = self.prompt is not None and not self.prompt_required + if is_flag is None: if flag_value is not None: + # Implicitly a flag because flag_value was set. is_flag = True + elif self._flag_needs_value: + # Not a flag, but when used as a flag it shows a prompt. + is_flag = False else: + # Implicitly a flag because flag options were given. is_flag = bool(self.secondary_opts) + elif is_flag is False and not self._flag_needs_value: + # Not a flag, and prompt is not enabled, can be used as a + # flag if flag_value is set. + self._flag_needs_value = flag_value is not None + if is_flag and default_is_missing: - self.default = False + self.default: t.Union[t.Any, t.Callable[[], t.Any]] = False + if flag_value is None: flag_value = not self.default - self.is_flag = is_flag - self.flag_value = flag_value - if self.is_flag and isinstance(self.flag_value, bool) \ - and type is None: - self.type = BOOL - self.is_bool_flag = True - else: - self.is_bool_flag = False + + if is_flag and type is None: + # Re-guess the type from the flag value instead of the + # default. + self.type = types.convert_type(None, flag_value) + + self.is_flag: bool = is_flag + self.is_bool_flag = is_flag and isinstance(self.type, types.BoolParamType) + self.flag_value: t.Any = flag_value # Counting self.count = count if count: if type is None: - self.type = IntRange(min=0) + self.type = types.IntRange(min=0) if default_is_missing: self.default = 0 - self.multiple = multiple self.allow_from_autoenv = allow_from_autoenv self.help = help self.show_default = show_default self.show_choices = show_choices self.show_envvar = show_envvar - # Sanity check for stuff we don't support if __debug__: - if self.nargs < 0: - raise TypeError('Options cannot have nargs < 0') + if self.nargs == -1: + raise TypeError("nargs=-1 is not supported for options.") + if self.prompt and self.is_flag and not self.is_bool_flag: - raise TypeError('Cannot prompt for flags that are not bools.') + raise TypeError("'prompt' is not valid for non-boolean flag.") + if not self.is_bool_flag and self.secondary_opts: - raise TypeError('Got secondary option for non boolean flag.') - if self.is_bool_flag and self.hide_input \ - and self.prompt is not None: - raise TypeError('Hidden input does not work with boolean ' - 'flag prompts.') + raise TypeError("Secondary flag is not valid for non-boolean flag.") + + if self.is_bool_flag and self.hide_input and self.prompt is not None: + raise TypeError( + "'prompt' with 'hide_input' is not valid for boolean flag." + ) + if self.count: if self.multiple: - raise TypeError('Options cannot be multiple and count ' - 'at the same time.') - elif self.is_flag: - raise TypeError('Options cannot be count and flags at ' - 'the same time.') + raise TypeError("'count' is not valid with 'multiple'.") - def _parse_decls(self, decls, expose_value): + if self.is_flag: + raise TypeError("'count' is not valid with 'is_flag'.") + + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict.update( + help=self.help, + prompt=self.prompt, + is_flag=self.is_flag, + flag_value=self.flag_value, + count=self.count, + hidden=self.hidden, + ) + return info_dict + + def _parse_decls( + self, decls: t.Sequence[str], expose_value: bool + ) -> t.Tuple[t.Optional[str], t.List[str], t.List[str]]: opts = [] secondary_opts = [] name = None possible_names = [] for decl in decls: - if isidentifier(decl): + if decl.isidentifier(): if name is not None: - raise TypeError('Name defined twice') + raise TypeError(f"Name '{name}' defined twice") name = decl else: - split_char = decl[:1] == '/' and ';' or '/' + split_char = ";" if decl[:1] == "/" else "/" if split_char in decl: first, second = decl.split(split_char, 1) first = first.rstrip() @@ -1633,123 +2586,209 @@ class Option(Parameter): second = second.lstrip() if second: secondary_opts.append(second.lstrip()) + if first == second: + raise ValueError( + f"Boolean option {decl!r} cannot use the" + " same flag for true/false." + ) else: possible_names.append(split_opt(decl)) opts.append(decl) if name is None and possible_names: possible_names.sort(key=lambda x: -len(x[0])) # group long options first - name = possible_names[0][1].replace('-', '_').lower() - if not isidentifier(name): + name = possible_names[0][1].replace("-", "_").lower() + if not name.isidentifier(): name = None if name is None: if not expose_value: return None, opts, secondary_opts - raise TypeError('Could not determine name for option') + raise TypeError("Could not determine name for option") if not opts and not secondary_opts: - raise TypeError('No options defined but a name was passed (%s). ' - 'Did you mean to declare an argument instead ' - 'of an option?' % name) + raise TypeError( + f"No options defined but a name was passed ({name})." + " Did you mean to declare an argument instead? Did" + f" you mean to pass '--{name}'?" + ) return name, opts, secondary_opts - def add_to_parser(self, parser, ctx): - kwargs = { - 'dest': self.name, - 'nargs': self.nargs, - 'obj': self, - } - + def add_to_parser(self, parser: OptionParser, ctx: Context) -> None: if self.multiple: - action = 'append' + action = "append" elif self.count: - action = 'count' + action = "count" else: - action = 'store' + action = "store" if self.is_flag: - kwargs.pop('nargs', None) + action = f"{action}_const" + if self.is_bool_flag and self.secondary_opts: - parser.add_option(self.opts, action=action + '_const', - const=True, **kwargs) - parser.add_option(self.secondary_opts, action=action + - '_const', const=False, **kwargs) + parser.add_option( + obj=self, opts=self.opts, dest=self.name, action=action, const=True + ) + parser.add_option( + obj=self, + opts=self.secondary_opts, + dest=self.name, + action=action, + const=False, + ) else: - parser.add_option(self.opts, action=action + '_const', - const=self.flag_value, - **kwargs) + parser.add_option( + obj=self, + opts=self.opts, + dest=self.name, + action=action, + const=self.flag_value, + ) else: - kwargs['action'] = action - parser.add_option(self.opts, **kwargs) + parser.add_option( + obj=self, + opts=self.opts, + dest=self.name, + action=action, + nargs=self.nargs, + ) - def get_help_record(self, ctx): + def get_help_record(self, ctx: Context) -> t.Optional[t.Tuple[str, str]]: if self.hidden: - return - any_prefix_is_slash = [] + return None + + any_prefix_is_slash = False + + def _write_opts(opts: t.Sequence[str]) -> str: + nonlocal any_prefix_is_slash - def _write_opts(opts): rv, any_slashes = join_options(opts) + if any_slashes: - any_prefix_is_slash[:] = [True] + any_prefix_is_slash = True + if not self.is_flag and not self.count: - rv += ' ' + self.make_metavar() + rv += f" {self.make_metavar()}" + return rv rv = [_write_opts(self.opts)] + if self.secondary_opts: rv.append(_write_opts(self.secondary_opts)) - help = self.help or '' + help = self.help or "" extra = [] + if self.show_envvar: envvar = self.envvar + if envvar is None: - if self.allow_from_autoenv and \ - ctx.auto_envvar_prefix is not None: - envvar = '%s_%s' % (ctx.auto_envvar_prefix, self.name.upper()) + if ( + self.allow_from_autoenv + and ctx.auto_envvar_prefix is not None + and self.name is not None + ): + envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}" + if envvar is not None: - extra.append('env var: %s' % ( - ', '.join('%s' % d for d in envvar) - if isinstance(envvar, (list, tuple)) - else envvar, )) - if self.default is not None and self.show_default: - if isinstance(self.show_default, string_types): - default_string = '({})'.format(self.show_default) - elif isinstance(self.default, (list, tuple)): - default_string = ', '.join('%s' % d for d in self.default) - elif inspect.isfunction(self.default): - default_string = "(dynamic)" + var_str = ( + envvar + if isinstance(envvar, str) + else ", ".join(str(d) for d in envvar) + ) + extra.append(_("env var: {var}").format(var=var_str)) + + # Temporarily enable resilient parsing to avoid type casting + # failing for the default. Might be possible to extend this to + # help formatting in general. + resilient = ctx.resilient_parsing + ctx.resilient_parsing = True + + try: + default_value = self.get_default(ctx, call=False) + finally: + ctx.resilient_parsing = resilient + + show_default_is_str = isinstance(self.show_default, str) + + if show_default_is_str or ( + default_value is not None and (self.show_default or ctx.show_default) + ): + if show_default_is_str: + default_string = f"({self.show_default})" + elif isinstance(default_value, (list, tuple)): + default_string = ", ".join(str(d) for d in default_value) + elif callable(default_value): + default_string = _("(dynamic)") + elif self.is_bool_flag and self.secondary_opts: + # For boolean flags that have distinct True/False opts, + # use the opt without prefix instead of the value. + default_string = split_opt( + (self.opts if self.default else self.secondary_opts)[0] + )[1] else: - default_string = self.default - extra.append('default: {}'.format(default_string)) + default_string = str(default_value) + + if default_string: + extra.append(_("default: {default}").format(default=default_string)) + + if ( + isinstance(self.type, types._NumberRangeBase) + # skip count with default range type + and not (self.count and self.type.min == 0 and self.type.max is None) + ): + range_str = self.type._describe_range() + + if range_str: + extra.append(range_str) if self.required: - extra.append('required') + extra.append(_("required")) + if extra: - help = '%s[%s]' % (help and help + ' ' or '', '; '.join(extra)) + extra_str = "; ".join(extra) + help = f"{help} [{extra_str}]" if help else f"[{extra_str}]" - return ((any_prefix_is_slash and '; ' or ' / ').join(rv), help) + return ("; " if any_prefix_is_slash else " / ").join(rv), help - def get_default(self, ctx): - # If we're a non boolean flag out default is more complex because + @typing.overload + def get_default( + self, ctx: Context, call: "te.Literal[True]" = True + ) -> t.Optional[t.Any]: + ... + + @typing.overload + def get_default( + self, ctx: Context, call: bool = ... + ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]: + ... + + def get_default( + self, ctx: Context, call: bool = True + ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]: + # If we're a non boolean flag our default is more complex because # we need to look at all flags in the same group to figure out # if we're the the default one in which case we return the flag # value as default. if self.is_flag and not self.is_bool_flag: for param in ctx.command.params: if param.name == self.name and param.default: - return param.flag_value - return None - return Parameter.get_default(self, ctx) + return param.flag_value # type: ignore - def prompt_for_value(self, ctx): + return None + + return super().get_default(ctx, call=call) + + def prompt_for_value(self, ctx: Context) -> t.Any: """This is an alternative flow that can be activated in the full value processing if a value does not exist. It will prompt the user until a valid value exists and then returns the processed value as result. """ + assert self.prompt is not None + # Calculate the default before prompting anything to be stable. default = self.get_default(ctx) @@ -1758,36 +2797,84 @@ class Option(Parameter): if self.is_bool_flag: return confirm(self.prompt, default) - return prompt(self.prompt, default=default, type=self.type, - hide_input=self.hide_input, show_choices=self.show_choices, - confirmation_prompt=self.confirmation_prompt, - value_proc=lambda x: self.process_value(ctx, x)) + return prompt( + self.prompt, + default=default, + type=self.type, + hide_input=self.hide_input, + show_choices=self.show_choices, + confirmation_prompt=self.confirmation_prompt, + value_proc=lambda x: self.process_value(ctx, x), + ) + + def resolve_envvar_value(self, ctx: Context) -> t.Optional[str]: + rv = super().resolve_envvar_value(ctx) - def resolve_envvar_value(self, ctx): - rv = Parameter.resolve_envvar_value(self, ctx) if rv is not None: return rv - if self.allow_from_autoenv and \ - ctx.auto_envvar_prefix is not None: - envvar = '%s_%s' % (ctx.auto_envvar_prefix, self.name.upper()) - return os.environ.get(envvar) - def value_from_envvar(self, ctx): - rv = self.resolve_envvar_value(ctx) - if rv is None: - return None - value_depth = (self.nargs != 1) + bool(self.multiple) - if value_depth > 0 and rv is not None: - rv = self.type.split_envvar_value(rv) - if self.multiple and self.nargs != 1: - rv = batch(rv, self.nargs) + if ( + self.allow_from_autoenv + and ctx.auto_envvar_prefix is not None + and self.name is not None + ): + envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}" + rv = os.environ.get(envvar) + return rv - def full_process_value(self, ctx, value): - if value is None and self.prompt is not None \ - and not ctx.resilient_parsing: - return self.prompt_for_value(ctx) - return Parameter.full_process_value(self, ctx, value) + def value_from_envvar(self, ctx: Context) -> t.Optional[t.Any]: + rv: t.Optional[t.Any] = self.resolve_envvar_value(ctx) + + if rv is None: + return None + + value_depth = (self.nargs != 1) + bool(self.multiple) + + if value_depth > 0: + rv = self.type.split_envvar_value(rv) + + if self.multiple and self.nargs != 1: + rv = batch(rv, self.nargs) + + return rv + + def consume_value( + self, ctx: Context, opts: t.Mapping[str, "Parameter"] + ) -> t.Tuple[t.Any, ParameterSource]: + value, source = super().consume_value(ctx, opts) + + # The parser will emit a sentinel value if the option can be + # given as a flag without a value. This is different from None + # to distinguish from the flag not being given at all. + if value is _flag_needs_value: + if self.prompt is not None and not ctx.resilient_parsing: + value = self.prompt_for_value(ctx) + source = ParameterSource.PROMPT + else: + value = self.flag_value + source = ParameterSource.COMMANDLINE + + elif ( + self.multiple + and value is not None + and any(v is _flag_needs_value for v in value) + ): + value = [self.flag_value if v is _flag_needs_value else v for v in value] + source = ParameterSource.COMMANDLINE + + # The value wasn't set, or used the param's default, prompt if + # prompting is enabled. + elif ( + source in {None, ParameterSource.DEFAULT} + and self.prompt is not None + and (self.required or self.prompt_required) + and not ctx.resilient_parsing + ): + value = self.prompt_for_value(ctx) + source = ParameterSource.PROMPT + + return value, source class Argument(Parameter): @@ -1797,60 +2884,70 @@ class Argument(Parameter): All parameters are passed onwards to the parameter constructor. """ - param_type_name = 'argument' - def __init__(self, param_decls, required=None, **attrs): + param_type_name = "argument" + + def __init__( + self, + param_decls: t.Sequence[str], + required: t.Optional[bool] = None, + **attrs: t.Any, + ) -> None: if required is None: - if attrs.get('default') is not None: + if attrs.get("default") is not None: required = False else: - required = attrs.get('nargs', 1) > 0 - Parameter.__init__(self, param_decls, required=required, **attrs) - if self.default is not None and self.nargs < 0: - raise TypeError('nargs=-1 in combination with a default value ' - 'is not supported.') + required = attrs.get("nargs", 1) > 0 + + if "multiple" in attrs: + raise TypeError("__init__() got an unexpected keyword argument 'multiple'.") + + super().__init__(param_decls, required=required, **attrs) + + if __debug__: + if self.default is not None and self.nargs == -1: + raise TypeError("'default' is not supported for nargs=-1.") @property - def human_readable_name(self): + def human_readable_name(self) -> str: if self.metavar is not None: return self.metavar - return self.name.upper() + return self.name.upper() # type: ignore - def make_metavar(self): + def make_metavar(self) -> str: if self.metavar is not None: return self.metavar var = self.type.get_metavar(self) if not var: - var = self.name.upper() + var = self.name.upper() # type: ignore if not self.required: - var = '[%s]' % var + var = f"[{var}]" if self.nargs != 1: - var += '...' + var += "..." return var - def _parse_decls(self, decls, expose_value): + def _parse_decls( + self, decls: t.Sequence[str], expose_value: bool + ) -> t.Tuple[t.Optional[str], t.List[str], t.List[str]]: if not decls: if not expose_value: return None, [], [] - raise TypeError('Could not determine name for argument') + raise TypeError("Could not determine name for argument") if len(decls) == 1: name = arg = decls[0] - name = name.replace('-', '_').lower() + name = name.replace("-", "_").lower() else: - raise TypeError('Arguments take exactly one ' - 'parameter declaration, got %d' % len(decls)) + raise TypeError( + "Arguments take exactly one parameter declaration, got" + f" {len(decls)}." + ) return name, [arg], [] - def get_usage_pieces(self, ctx): + def get_usage_pieces(self, ctx: Context) -> t.List[str]: return [self.make_metavar()] - def get_error_hint(self, ctx): - return '"%s"' % self.make_metavar() + def get_error_hint(self, ctx: Context) -> str: + return f"'{self.make_metavar()}'" - def add_to_parser(self, parser, ctx): - parser.add_argument(dest=self.name, nargs=self.nargs, - obj=self) - - -# Circular dependency between decorators and core -from .decorators import command, group + def add_to_parser(self, parser: OptionParser, ctx: Context) -> None: + parser.add_argument(dest=self.name, nargs=self.nargs, obj=self) diff --git a/pipenv/vendor/click/decorators.py b/pipenv/vendor/click/decorators.py index c57c5308..ec0523c6 100644 --- a/pipenv/vendor/click/decorators.py +++ b/pipenv/vendor/click/decorators.py @@ -1,34 +1,48 @@ -import sys import inspect - +import types +import typing as t from functools import update_wrapper +from gettext import gettext as _ -from ._compat import iteritems -from ._unicodefun import _check_for_unicode_literals -from .utils import echo +from .core import Argument +from .core import Command +from .core import Context +from .core import Group +from .core import Option +from .core import Parameter from .globals import get_current_context +from .utils import echo + +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) +FC = t.TypeVar("FC", t.Callable[..., t.Any], Command) -def pass_context(f): +def pass_context(f: F) -> F: """Marks a callback as wanting to receive the current context object as first argument. """ - def new_func(*args, **kwargs): + + def new_func(*args, **kwargs): # type: ignore return f(get_current_context(), *args, **kwargs) - return update_wrapper(new_func, f) + + return update_wrapper(t.cast(F, new_func), f) -def pass_obj(f): +def pass_obj(f: F) -> F: """Similar to :func:`pass_context`, but only pass the object on the context onwards (:attr:`Context.obj`). This is useful if that object represents the state of a nested system. """ - def new_func(*args, **kwargs): + + def new_func(*args, **kwargs): # type: ignore return f(get_current_context().obj, *args, **kwargs) - return update_wrapper(new_func, f) + + return update_wrapper(t.cast(F, new_func), f) -def make_pass_decorator(object_type, ensure=False): +def make_pass_decorator( + object_type: t.Type, ensure: bool = False +) -> "t.Callable[[F], F]": """Given an object type this creates a decorator that will work similar to :func:`pass_obj` but instead of passing the object of the current context, it will find the innermost context of type @@ -50,53 +64,107 @@ def make_pass_decorator(object_type, ensure=False): :param ensure: if set to `True`, a new object will be created and remembered on the context if it's not there yet. """ - def decorator(f): - def new_func(*args, **kwargs): + + def decorator(f: F) -> F: + def new_func(*args, **kwargs): # type: ignore ctx = get_current_context() + if ensure: obj = ctx.ensure_object(object_type) else: obj = ctx.find_object(object_type) + if obj is None: - raise RuntimeError('Managed to invoke callback without a ' - 'context object of type %r existing' - % object_type.__name__) + raise RuntimeError( + "Managed to invoke callback without a context" + f" object of type {object_type.__name__!r}" + " existing." + ) + return ctx.invoke(f, obj, *args, **kwargs) - return update_wrapper(new_func, f) + + return update_wrapper(t.cast(F, new_func), f) + return decorator -def _make_command(f, name, attrs, cls): +def pass_meta_key( + key: str, *, doc_description: t.Optional[str] = None +) -> "t.Callable[[F], F]": + """Create a decorator that passes a key from + :attr:`click.Context.meta` as the first argument to the decorated + function. + + :param key: Key in ``Context.meta`` to pass. + :param doc_description: Description of the object being passed, + inserted into the decorator's docstring. Defaults to "the 'key' + key from Context.meta". + + .. versionadded:: 8.0 + """ + + def decorator(f: F) -> F: + def new_func(*args, **kwargs): # type: ignore + ctx = get_current_context() + obj = ctx.meta[key] + return ctx.invoke(f, obj, *args, **kwargs) + + return update_wrapper(t.cast(F, new_func), f) + + if doc_description is None: + doc_description = f"the {key!r} key from :attr:`click.Context.meta`" + + decorator.__doc__ = ( + f"Decorator that passes {doc_description} as the first argument" + " to the decorated function." + ) + return decorator + + +def _make_command( + f: F, + name: t.Optional[str], + attrs: t.MutableMapping[str, t.Any], + cls: t.Type[Command], +) -> Command: if isinstance(f, Command): - raise TypeError('Attempted to convert a callback into a ' - 'command twice.') + raise TypeError("Attempted to convert a callback into a command twice.") + try: - params = f.__click_params__ + params = f.__click_params__ # type: ignore params.reverse() - del f.__click_params__ + del f.__click_params__ # type: ignore except AttributeError: params = [] - help = attrs.get('help') + + help = attrs.get("help") + if help is None: help = inspect.getdoc(f) - if isinstance(help, bytes): - help = help.decode('utf-8') else: help = inspect.cleandoc(help) - attrs['help'] = help - _check_for_unicode_literals() - return cls(name=name or f.__name__.lower().replace('_', '-'), - callback=f, params=params, **attrs) + + attrs["help"] = help + return cls( + name=name or f.__name__.lower().replace("_", "-"), + callback=f, + params=params, + **attrs, + ) -def command(name=None, cls=None, **attrs): +def command( + name: t.Optional[str] = None, + cls: t.Optional[t.Type[Command]] = None, + **attrs: t.Any, +) -> t.Callable[[F], Command]: r"""Creates a new :class:`Command` and uses the decorated function as callback. This will also automatically attach all decorated :func:`option`\s and :func:`argument`\s as parameters to the command. - The name of the command defaults to the name of the function. If you - want to change that, you can pass the intended name as the first - argument. + The name of the command defaults to the name of the function with + underscores replaced by dashes. If you want to change that, you can + pass the intended name as the first argument. All keyword arguments are forwarded to the underlying command class. @@ -111,32 +179,35 @@ def command(name=None, cls=None, **attrs): """ if cls is None: cls = Command - def decorator(f): - cmd = _make_command(f, name, attrs, cls) + + def decorator(f: t.Callable[..., t.Any]) -> Command: + cmd = _make_command(f, name, attrs, cls) # type: ignore cmd.__doc__ = f.__doc__ return cmd + return decorator -def group(name=None, **attrs): +def group(name: t.Optional[str] = None, **attrs: t.Any) -> t.Callable[[F], Group]: """Creates a new :class:`Group` with a function as callback. This works otherwise the same as :func:`command` just that the `cls` parameter is set to :class:`Group`. """ - attrs.setdefault('cls', Group) - return command(name, **attrs) + attrs.setdefault("cls", Group) + return t.cast(Group, command(name, **attrs)) -def _param_memo(f, param): +def _param_memo(f: FC, param: Parameter) -> None: if isinstance(f, Command): f.params.append(param) else: - if not hasattr(f, '__click_params__'): - f.__click_params__ = [] - f.__click_params__.append(param) + if not hasattr(f, "__click_params__"): + f.__click_params__ = [] # type: ignore + + f.__click_params__.append(param) # type: ignore -def argument(*param_decls, **attrs): +def argument(*param_decls: str, **attrs: t.Any) -> t.Callable[[FC], FC]: """Attaches an argument to the command. All positional arguments are passed as parameter declarations to :class:`Argument`; all keyword arguments are forwarded unchanged (except ``cls``). @@ -146,14 +217,16 @@ def argument(*param_decls, **attrs): :param cls: the argument class to instantiate. This defaults to :class:`Argument`. """ - def decorator(f): - ArgumentClass = attrs.pop('cls', Argument) + + def decorator(f: FC) -> FC: + ArgumentClass = attrs.pop("cls", Argument) _param_memo(f, ArgumentClass(param_decls, **attrs)) return f + return decorator -def option(*param_decls, **attrs): +def option(*param_decls: str, **attrs: t.Any) -> t.Callable[[FC], FC]: """Attaches an option to the command. All positional arguments are passed as parameter declarations to :class:`Option`; all keyword arguments are forwarded unchanged (except ``cls``). @@ -163,149 +236,201 @@ def option(*param_decls, **attrs): :param cls: the option class to instantiate. This defaults to :class:`Option`. """ - def decorator(f): + + def decorator(f: FC) -> FC: # Issue 926, copy attrs, so pre-defined options can re-use the same cls= option_attrs = attrs.copy() - if 'help' in option_attrs: - option_attrs['help'] = inspect.cleandoc(option_attrs['help']) - OptionClass = option_attrs.pop('cls', Option) + if "help" in option_attrs: + option_attrs["help"] = inspect.cleandoc(option_attrs["help"]) + OptionClass = option_attrs.pop("cls", Option) _param_memo(f, OptionClass(param_decls, **option_attrs)) return f + return decorator -def confirmation_option(*param_decls, **attrs): - """Shortcut for confirmation prompts that can be ignored by passing - ``--yes`` as parameter. +def confirmation_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: + """Add a ``--yes`` option which shows a prompt before continuing if + not passed. If the prompt is declined, the program will exit. - This is equivalent to decorating a function with :func:`option` with - the following parameters:: - - def callback(ctx, param, value): - if not value: - ctx.abort() - - @click.command() - @click.option('--yes', is_flag=True, callback=callback, - expose_value=False, prompt='Do you want to continue?') - def dropdb(): - pass + :param param_decls: One or more option names. Defaults to the single + value ``"--yes"``. + :param kwargs: Extra arguments are passed to :func:`option`. """ - def decorator(f): - def callback(ctx, param, value): - if not value: - ctx.abort() - attrs.setdefault('is_flag', True) - attrs.setdefault('callback', callback) - attrs.setdefault('expose_value', False) - attrs.setdefault('prompt', 'Do you want to continue?') - attrs.setdefault('help', 'Confirm the action without prompting.') - return option(*(param_decls or ('--yes',)), **attrs)(f) - return decorator + + def callback(ctx: Context, param: Parameter, value: bool) -> None: + if not value: + ctx.abort() + + if not param_decls: + param_decls = ("--yes",) + + kwargs.setdefault("is_flag", True) + kwargs.setdefault("callback", callback) + kwargs.setdefault("expose_value", False) + kwargs.setdefault("prompt", "Do you want to continue?") + kwargs.setdefault("help", "Confirm the action without prompting.") + return option(*param_decls, **kwargs) -def password_option(*param_decls, **attrs): - """Shortcut for password prompts. +def password_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: + """Add a ``--password`` option which prompts for a password, hiding + input and asking to enter the value again for confirmation. - This is equivalent to decorating a function with :func:`option` with - the following parameters:: - - @click.command() - @click.option('--password', prompt=True, confirmation_prompt=True, - hide_input=True) - def changeadmin(password): - pass + :param param_decls: One or more option names. Defaults to the single + value ``"--password"``. + :param kwargs: Extra arguments are passed to :func:`option`. """ - def decorator(f): - attrs.setdefault('prompt', True) - attrs.setdefault('confirmation_prompt', True) - attrs.setdefault('hide_input', True) - return option(*(param_decls or ('--password',)), **attrs)(f) - return decorator + if not param_decls: + param_decls = ("--password",) + + kwargs.setdefault("prompt", True) + kwargs.setdefault("confirmation_prompt", True) + kwargs.setdefault("hide_input", True) + return option(*param_decls, **kwargs) -def version_option(version=None, *param_decls, **attrs): - """Adds a ``--version`` option which immediately ends the program - printing out the version number. This is implemented as an eager - option that prints the version and exits the program in the callback. +def version_option( + version: t.Optional[str] = None, + *param_decls: str, + package_name: t.Optional[str] = None, + prog_name: t.Optional[str] = None, + message: t.Optional[str] = None, + **kwargs: t.Any, +) -> t.Callable[[FC], FC]: + """Add a ``--version`` option which immediately prints the version + number and exits the program. - :param version: the version number to show. If not provided Click - attempts an auto discovery via setuptools. - :param prog_name: the name of the program (defaults to autodetection) - :param message: custom message to show instead of the default - (``'%(prog)s, version %(version)s'``) - :param others: everything else is forwarded to :func:`option`. + If ``version`` is not provided, Click will try to detect it using + :func:`importlib.metadata.version` to get the version for the + ``package_name``. On Python < 3.8, the ``importlib_metadata`` + backport must be installed. + + If ``package_name`` is not provided, Click will try to detect it by + inspecting the stack frames. This will be used to detect the + version, so it must match the name of the installed package. + + :param version: The version number to show. If not provided, Click + will try to detect it. + :param param_decls: One or more option names. Defaults to the single + value ``"--version"``. + :param package_name: The package name to detect the version from. If + not provided, Click will try to detect it. + :param prog_name: The name of the CLI to show in the message. If not + provided, it will be detected from the command. + :param message: The message to show. The values ``%(prog)s``, + ``%(package)s``, and ``%(version)s`` are available. Defaults to + ``"%(prog)s, version %(version)s"``. + :param kwargs: Extra arguments are passed to :func:`option`. + :raise RuntimeError: ``version`` could not be detected. + + .. versionchanged:: 8.0 + Add the ``package_name`` parameter, and the ``%(package)s`` + value for messages. + + .. versionchanged:: 8.0 + Use :mod:`importlib.metadata` instead of ``pkg_resources``. The + version is detected based on the package name, not the entry + point name. The Python package name must match the installed + package name, or be passed with ``package_name=``. """ - if version is None: - if hasattr(sys, '_getframe'): - module = sys._getframe(1).f_globals.get('__name__') - else: - module = '' + if message is None: + message = _("%(prog)s, version %(version)s") - def decorator(f): - prog_name = attrs.pop('prog_name', None) - message = attrs.pop('message', '%(prog)s, version %(version)s') + if version is None and package_name is None: + frame = inspect.currentframe() + f_back = frame.f_back if frame is not None else None + f_globals = f_back.f_globals if f_back is not None else None + # break reference cycle + # https://docs.python.org/3/library/inspect.html#the-interpreter-stack + del frame - def callback(ctx, param, value): - if not value or ctx.resilient_parsing: - return - prog = prog_name - if prog is None: - prog = ctx.find_root().info_name - ver = version - if ver is None: - try: - import pkg_resources - except ImportError: - pass - else: - for dist in pkg_resources.working_set: - scripts = dist.get_entry_map().get('console_scripts') or {} - for script_name, entry_point in iteritems(scripts): - if entry_point.module_name == module: - ver = dist.version - break - if ver is None: - raise RuntimeError('Could not determine version') - echo(message % { - 'prog': prog, - 'version': ver, - }, color=ctx.color) - ctx.exit() + if f_globals is not None: + package_name = f_globals.get("__name__") - attrs.setdefault('is_flag', True) - attrs.setdefault('expose_value', False) - attrs.setdefault('is_eager', True) - attrs.setdefault('help', 'Show the version and exit.') - attrs['callback'] = callback - return option(*(param_decls or ('--version',)), **attrs)(f) - return decorator + if package_name == "__main__": + package_name = f_globals.get("__package__") + + if package_name: + package_name = package_name.partition(".")[0] + + def callback(ctx: Context, param: Parameter, value: bool) -> None: + if not value or ctx.resilient_parsing: + return + + nonlocal prog_name + nonlocal version + + if prog_name is None: + prog_name = ctx.find_root().info_name + + if version is None and package_name is not None: + metadata: t.Optional[types.ModuleType] + + try: + from importlib import metadata # type: ignore + except ImportError: + # Python < 3.8 + import pipenv.vendor.importlib_metadata as metadata # type: ignore + + try: + version = metadata.version(package_name) # type: ignore + except metadata.PackageNotFoundError: # type: ignore + raise RuntimeError( + f"{package_name!r} is not installed. Try passing" + " 'package_name' instead." + ) from None + + if version is None: + raise RuntimeError( + f"Could not determine the version for {package_name!r} automatically." + ) + + echo( + t.cast(str, message) + % {"prog": prog_name, "package": package_name, "version": version}, + color=ctx.color, + ) + ctx.exit() + + if not param_decls: + param_decls = ("--version",) + + kwargs.setdefault("is_flag", True) + kwargs.setdefault("expose_value", False) + kwargs.setdefault("is_eager", True) + kwargs.setdefault("help", _("Show the version and exit.")) + kwargs["callback"] = callback + return option(*param_decls, **kwargs) -def help_option(*param_decls, **attrs): - """Adds a ``--help`` option which immediately ends the program - printing out the help page. This is usually unnecessary to add as - this is added by default to all commands unless suppressed. +def help_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: + """Add a ``--help`` option which immediately prints the help page + and exits the program. - Like :func:`version_option`, this is implemented as eager option that - prints in the callback and exits. + This is usually unnecessary, as the ``--help`` option is added to + each command automatically unless ``add_help_option=False`` is + passed. - All arguments are forwarded to :func:`option`. + :param param_decls: One or more option names. Defaults to the single + value ``"--help"``. + :param kwargs: Extra arguments are passed to :func:`option`. """ - def decorator(f): - def callback(ctx, param, value): - if value and not ctx.resilient_parsing: - echo(ctx.get_help(), color=ctx.color) - ctx.exit() - attrs.setdefault('is_flag', True) - attrs.setdefault('expose_value', False) - attrs.setdefault('help', 'Show this message and exit.') - attrs.setdefault('is_eager', True) - attrs['callback'] = callback - return option(*(param_decls or ('--help',)), **attrs)(f) - return decorator + def callback(ctx: Context, param: Parameter, value: bool) -> None: + if not value or ctx.resilient_parsing: + return -# Circular dependencies between core and decorators -from .core import Command, Group, Argument, Option + echo(ctx.get_help(), color=ctx.color) + ctx.exit() + + if not param_decls: + param_decls = ("--help",) + + kwargs.setdefault("is_flag", True) + kwargs.setdefault("expose_value", False) + kwargs.setdefault("is_eager", True) + kwargs.setdefault("help", _("Show this message and exit.")) + kwargs["callback"] = callback + return option(*param_decls, **kwargs) diff --git a/pipenv/vendor/click/exceptions.py b/pipenv/vendor/click/exceptions.py index 6fa17658..9e20b3eb 100644 --- a/pipenv/vendor/click/exceptions.py +++ b/pipenv/vendor/click/exceptions.py @@ -1,43 +1,46 @@ -from ._compat import PY2, filename_to_ui, get_text_stderr +import os +import typing as t +from gettext import gettext as _ +from gettext import ngettext + +from ._compat import get_text_stderr from .utils import echo +if t.TYPE_CHECKING: + from .core import Context + from .core import Parameter + + +def _join_param_hints( + param_hint: t.Optional[t.Union[t.Sequence[str], str]] +) -> t.Optional[str]: + if param_hint is not None and not isinstance(param_hint, str): + return " / ".join(repr(x) for x in param_hint) -def _join_param_hints(param_hint): - if isinstance(param_hint, (tuple, list)): - return ' / '.join('"%s"' % x for x in param_hint) return param_hint class ClickException(Exception): """An exception that Click can handle and show to the user.""" - #: The exit code for this exception + #: The exit code for this exception. exit_code = 1 - def __init__(self, message): - ctor_msg = message - if PY2: - if ctor_msg is not None: - ctor_msg = ctor_msg.encode('utf-8') - Exception.__init__(self, ctor_msg) + def __init__(self, message: str) -> None: + super().__init__(message) self.message = message - def format_message(self): + def format_message(self) -> str: return self.message - def __str__(self): + def __str__(self) -> str: return self.message - if PY2: - __unicode__ = __str__ - - def __str__(self): - return self.message.encode('utf-8') - - def show(self, file=None): + def show(self, file: t.Optional[t.IO] = None) -> None: if file is None: file = get_text_stderr() - echo('Error: %s' % self.format_message(), file=file) + + echo(_("Error: {message}").format(message=self.format_message()), file=file) class UsageError(ClickException): @@ -48,26 +51,35 @@ class UsageError(ClickException): :param ctx: optionally the context that caused this error. Click will fill in the context automatically in some situations. """ + exit_code = 2 - def __init__(self, message, ctx=None): - ClickException.__init__(self, message) + def __init__(self, message: str, ctx: t.Optional["Context"] = None) -> None: + super().__init__(message) self.ctx = ctx - self.cmd = self.ctx and self.ctx.command or None + self.cmd = self.ctx.command if self.ctx else None - def show(self, file=None): + def show(self, file: t.Optional[t.IO] = None) -> None: if file is None: file = get_text_stderr() color = None - hint = '' - if (self.cmd is not None and - self.cmd.get_help_option(self.ctx) is not None): - hint = ('Try "%s %s" for help.\n' - % (self.ctx.command_path, self.ctx.help_option_names[0])) + hint = "" + if ( + self.ctx is not None + and self.ctx.command.get_help_option(self.ctx) is not None + ): + hint = _("Try '{command} {option}' for help.").format( + command=self.ctx.command_path, option=self.ctx.help_option_names[0] + ) + hint = f"{hint}\n" if self.ctx is not None: color = self.ctx.color - echo(self.ctx.get_usage() + '\n%s' % hint, file=file, color=color) - echo('Error: %s' % self.format_message(), file=file, color=color) + echo(f"{self.ctx.get_usage()}\n{hint}", file=file, color=color) + echo( + _("Error: {message}").format(message=self.format_message()), + file=file, + color=color, + ) class BadParameter(UsageError): @@ -88,22 +100,28 @@ class BadParameter(UsageError): each item is quoted and separated. """ - def __init__(self, message, ctx=None, param=None, - param_hint=None): - UsageError.__init__(self, message, ctx) + def __init__( + self, + message: str, + ctx: t.Optional["Context"] = None, + param: t.Optional["Parameter"] = None, + param_hint: t.Optional[str] = None, + ) -> None: + super().__init__(message, ctx) self.param = param self.param_hint = param_hint - def format_message(self): + def format_message(self) -> str: if self.param_hint is not None: param_hint = self.param_hint elif self.param is not None: - param_hint = self.param.get_error_hint(self.ctx) + param_hint = self.param.get_error_hint(self.ctx) # type: ignore else: - return 'Invalid value: %s' % self.message - param_hint = _join_param_hints(param_hint) + return _("Invalid value: {message}").format(message=self.message) - return 'Invalid value for %s: %s' % (param_hint, self.message) + return _("Invalid value for {param_hint}: {message}").format( + param_hint=_join_param_hints(param_hint), message=self.message + ) class MissingParameter(BadParameter): @@ -118,19 +136,27 @@ class MissingParameter(BadParameter): ``'option'`` or ``'argument'``. """ - def __init__(self, message=None, ctx=None, param=None, - param_hint=None, param_type=None): - BadParameter.__init__(self, message, ctx, param, param_hint) + def __init__( + self, + message: t.Optional[str] = None, + ctx: t.Optional["Context"] = None, + param: t.Optional["Parameter"] = None, + param_hint: t.Optional[str] = None, + param_type: t.Optional[str] = None, + ) -> None: + super().__init__(message or "", ctx, param, param_hint) self.param_type = param_type - def format_message(self): + def format_message(self) -> str: if self.param_hint is not None: - param_hint = self.param_hint + param_hint: t.Optional[str] = self.param_hint elif self.param is not None: - param_hint = self.param.get_error_hint(self.ctx) + param_hint = self.param.get_error_hint(self.ctx) # type: ignore else: param_hint = None + param_hint = _join_param_hints(param_hint) + param_hint = f" {param_hint}" if param_hint else "" param_type = self.param_type if param_type is None and self.param is not None: @@ -141,16 +167,30 @@ class MissingParameter(BadParameter): msg_extra = self.param.type.get_missing_message(self.param) if msg_extra: if msg: - msg += '. ' + msg_extra + msg += f". {msg_extra}" else: msg = msg_extra - return 'Missing %s%s%s%s' % ( - param_type, - param_hint and ' %s' % param_hint or '', - msg and '. ' or '.', - msg or '', - ) + msg = f" {msg}" if msg else "" + + # Translate param_type for known types. + if param_type == "argument": + missing = _("Missing argument") + elif param_type == "option": + missing = _("Missing option") + elif param_type == "parameter": + missing = _("Missing parameter") + else: + missing = _("Missing {param_type}").format(param_type=param_type) + + return f"{missing}{param_hint}.{msg}" + + def __str__(self) -> str: + if not self.message: + param_name = self.param.name if self.param else None + return _("Missing parameter: {param_name}").format(param_name=param_name) + else: + return self.message class NoSuchOption(UsageError): @@ -160,23 +200,31 @@ class NoSuchOption(UsageError): .. versionadded:: 4.0 """ - def __init__(self, option_name, message=None, possibilities=None, - ctx=None): + def __init__( + self, + option_name: str, + message: t.Optional[str] = None, + possibilities: t.Optional[t.Sequence[str]] = None, + ctx: t.Optional["Context"] = None, + ) -> None: if message is None: - message = 'no such option: %s' % option_name - UsageError.__init__(self, message, ctx) + message = _("No such option: {name}").format(name=option_name) + + super().__init__(message, ctx) self.option_name = option_name self.possibilities = possibilities - def format_message(self): - bits = [self.message] - if self.possibilities: - if len(self.possibilities) == 1: - bits.append('Did you mean %s?' % self.possibilities[0]) - else: - possibilities = sorted(self.possibilities) - bits.append('(Possible options: %s)' % ', '.join(possibilities)) - return ' '.join(bits) + def format_message(self) -> str: + if not self.possibilities: + return self.message + + possibility_str = ", ".join(sorted(self.possibilities)) + suggest = ngettext( + "Did you mean {possibility}?", + "(Possible options: {possibilities})", + len(self.possibilities), + ).format(possibility=possibility_str, possibilities=possibility_str) + return f"{self.message} {suggest}" class BadOptionUsage(UsageError): @@ -189,8 +237,10 @@ class BadOptionUsage(UsageError): :param option_name: the name of the option being used incorrectly. """ - def __init__(self, option_name, message, ctx=None): - UsageError.__init__(self, message, ctx) + def __init__( + self, option_name: str, message: str, ctx: t.Optional["Context"] = None + ) -> None: + super().__init__(message, ctx) self.option_name = option_name @@ -202,23 +252,22 @@ class BadArgumentUsage(UsageError): .. versionadded:: 6.0 """ - def __init__(self, message, ctx=None): - UsageError.__init__(self, message, ctx) - class FileError(ClickException): """Raised if a file cannot be opened.""" - def __init__(self, filename, hint=None): - ui_filename = filename_to_ui(filename) + def __init__(self, filename: str, hint: t.Optional[str] = None) -> None: if hint is None: - hint = 'unknown error' - ClickException.__init__(self, hint) - self.ui_filename = ui_filename + hint = _("unknown error") + + super().__init__(hint) + self.ui_filename = os.fsdecode(filename) self.filename = filename - def format_message(self): - return 'Could not open file %s: %s' % (self.ui_filename, self.message) + def format_message(self) -> str: + return _("Could not open file {filename!r}: {message}").format( + filename=self.ui_filename, message=self.message + ) class Abort(RuntimeError): @@ -231,5 +280,8 @@ class Exit(RuntimeError): :param code: the status code to exit with. """ - def __init__(self, code=0): + + __slots__ = ("exit_code",) + + def __init__(self, code: int = 0) -> None: self.exit_code = code diff --git a/pipenv/vendor/click/formatting.py b/pipenv/vendor/click/formatting.py index a3d6a4d3..ddd2a2f8 100644 --- a/pipenv/vendor/click/formatting.py +++ b/pipenv/vendor/click/formatting.py @@ -1,29 +1,38 @@ +import typing as t from contextlib import contextmanager -from .termui import get_terminal_size -from .parser import split_opt -from ._compat import term_len +from gettext import gettext as _ +from ._compat import term_len +from .parser import split_opt # Can force a width. This is used by the test system -FORCED_WIDTH = None +FORCED_WIDTH: t.Optional[int] = None -def measure_table(rows): - widths = {} +def measure_table(rows: t.Iterable[t.Tuple[str, str]]) -> t.Tuple[int, ...]: + widths: t.Dict[int, int] = {} + for row in rows: for idx, col in enumerate(row): widths[idx] = max(widths.get(idx, 0), term_len(col)) + return tuple(y for x, y in sorted(widths.items())) -def iter_rows(rows, col_count): +def iter_rows( + rows: t.Iterable[t.Tuple[str, str]], col_count: int +) -> t.Iterator[t.Tuple[str, ...]]: for row in rows: - row = tuple(row) - yield row + ('',) * (col_count - len(row)) + yield row + ("",) * (col_count - len(row)) -def wrap_text(text, width=78, initial_indent='', subsequent_indent='', - preserve_paragraphs=False): +def wrap_text( + text: str, + width: int = 78, + initial_indent: str = "", + subsequent_indent: str = "", + preserve_paragraphs: bool = False, +) -> str: """A helper function that intelligently wraps text. By default, it assumes that it operates on a single paragraph of text but if the `preserve_paragraphs` parameter is provided it will intelligently @@ -43,24 +52,28 @@ def wrap_text(text, width=78, initial_indent='', subsequent_indent='', intelligently handle paragraphs. """ from ._textwrap import TextWrapper + text = text.expandtabs() - wrapper = TextWrapper(width, initial_indent=initial_indent, - subsequent_indent=subsequent_indent, - replace_whitespace=False) + wrapper = TextWrapper( + width, + initial_indent=initial_indent, + subsequent_indent=subsequent_indent, + replace_whitespace=False, + ) if not preserve_paragraphs: return wrapper.fill(text) - p = [] - buf = [] + p: t.List[t.Tuple[int, bool, str]] = [] + buf: t.List[str] = [] indent = None - def _flush_par(): + def _flush_par() -> None: if not buf: return - if buf[0].strip() == '\b': - p.append((indent or 0, True, '\n'.join(buf[1:]))) + if buf[0].strip() == "\b": + p.append((indent or 0, True, "\n".join(buf[1:]))) else: - p.append((indent or 0, False, ' '.join(buf))) + p.append((indent or 0, False, " ".join(buf))) del buf[:] for line in text.splitlines(): @@ -77,16 +90,16 @@ def wrap_text(text, width=78, initial_indent='', subsequent_indent='', rv = [] for indent, raw, text in p: - with wrapper.extra_indent(' ' * indent): + with wrapper.extra_indent(" " * indent): if raw: rv.append(wrapper.indent_only(text)) else: rv.append(wrapper.fill(text)) - return '\n\n'.join(rv) + return "\n\n".join(rv) -class HelpFormatter(object): +class HelpFormatter: """This class helps with formatting text-based help pages. It's usually just needed for very special internal cases, but it's also exposed so that developers can write their own fancy outputs. @@ -98,79 +111,108 @@ class HelpFormatter(object): width clamped to a maximum of 78. """ - def __init__(self, indent_increment=2, width=None, max_width=None): + def __init__( + self, + indent_increment: int = 2, + width: t.Optional[int] = None, + max_width: t.Optional[int] = None, + ) -> None: + import shutil + self.indent_increment = indent_increment if max_width is None: max_width = 80 if width is None: width = FORCED_WIDTH if width is None: - width = max(min(get_terminal_size()[0], max_width) - 2, 50) + width = max(min(shutil.get_terminal_size().columns, max_width) - 2, 50) self.width = width self.current_indent = 0 - self.buffer = [] + self.buffer: t.List[str] = [] - def write(self, string): + def write(self, string: str) -> None: """Writes a unicode string into the internal buffer.""" self.buffer.append(string) - def indent(self): + def indent(self) -> None: """Increases the indentation.""" self.current_indent += self.indent_increment - def dedent(self): + def dedent(self) -> None: """Decreases the indentation.""" self.current_indent -= self.indent_increment - def write_usage(self, prog, args='', prefix='Usage: '): + def write_usage( + self, prog: str, args: str = "", prefix: t.Optional[str] = None + ) -> None: """Writes a usage line into the buffer. :param prog: the program name. :param args: whitespace separated list of arguments. - :param prefix: the prefix for the first line. + :param prefix: The prefix for the first line. Defaults to + ``"Usage: "``. """ - usage_prefix = '%*s%s ' % (self.current_indent, prefix, prog) + if prefix is None: + prefix = f"{_('Usage:')} " + + usage_prefix = f"{prefix:>{self.current_indent}}{prog} " text_width = self.width - self.current_indent if text_width >= (term_len(usage_prefix) + 20): # The arguments will fit to the right of the prefix. - indent = ' ' * term_len(usage_prefix) - self.write(wrap_text(args, text_width, - initial_indent=usage_prefix, - subsequent_indent=indent)) + indent = " " * term_len(usage_prefix) + self.write( + wrap_text( + args, + text_width, + initial_indent=usage_prefix, + subsequent_indent=indent, + ) + ) else: # The prefix is too long, put the arguments on the next line. self.write(usage_prefix) - self.write('\n') - indent = ' ' * (max(self.current_indent, term_len(prefix)) + 4) - self.write(wrap_text(args, text_width, - initial_indent=indent, - subsequent_indent=indent)) + self.write("\n") + indent = " " * (max(self.current_indent, term_len(prefix)) + 4) + self.write( + wrap_text( + args, text_width, initial_indent=indent, subsequent_indent=indent + ) + ) - self.write('\n') + self.write("\n") - def write_heading(self, heading): + def write_heading(self, heading: str) -> None: """Writes a heading into the buffer.""" - self.write('%*s%s:\n' % (self.current_indent, '', heading)) + self.write(f"{'':>{self.current_indent}}{heading}:\n") - def write_paragraph(self): + def write_paragraph(self) -> None: """Writes a paragraph into the buffer.""" if self.buffer: - self.write('\n') + self.write("\n") - def write_text(self, text): + def write_text(self, text: str) -> None: """Writes re-indented text into the buffer. This rewraps and preserves paragraphs. """ - text_width = max(self.width - self.current_indent, 11) - indent = ' ' * self.current_indent - self.write(wrap_text(text, text_width, - initial_indent=indent, - subsequent_indent=indent, - preserve_paragraphs=True)) - self.write('\n') + indent = " " * self.current_indent + self.write( + wrap_text( + text, + self.width, + initial_indent=indent, + subsequent_indent=indent, + preserve_paragraphs=True, + ) + ) + self.write("\n") - def write_dl(self, rows, col_max=30, col_spacing=2): + def write_dl( + self, + rows: t.Sequence[t.Tuple[str, str]], + col_max: int = 30, + col_spacing: int = 2, + ) -> None: """Writes a definition list into the buffer. This is how options and commands are usually formatted. @@ -182,33 +224,35 @@ class HelpFormatter(object): rows = list(rows) widths = measure_table(rows) if len(widths) != 2: - raise TypeError('Expected two columns for definition list') + raise TypeError("Expected two columns for definition list") first_col = min(widths[0], col_max) + col_spacing for first, second in iter_rows(rows, len(widths)): - self.write('%*s%s' % (self.current_indent, '', first)) + self.write(f"{'':>{self.current_indent}}{first}") if not second: - self.write('\n') + self.write("\n") continue if term_len(first) <= first_col - col_spacing: - self.write(' ' * (first_col - term_len(first))) + self.write(" " * (first_col - term_len(first))) else: - self.write('\n') - self.write(' ' * (first_col + self.current_indent)) + self.write("\n") + self.write(" " * (first_col + self.current_indent)) text_width = max(self.width - first_col - 2, 10) - lines = iter(wrap_text(second, text_width).splitlines()) + wrapped_text = wrap_text(second, text_width, preserve_paragraphs=True) + lines = wrapped_text.splitlines() + if lines: - self.write(next(lines) + '\n') - for line in lines: - self.write('%*s%s\n' % ( - first_col + self.current_indent, '', line)) + self.write(f"{lines[0]}\n") + + for line in lines[1:]: + self.write(f"{'':>{first_col + self.current_indent}}{line}\n") else: - self.write('\n') + self.write("\n") @contextmanager - def section(self, name): + def section(self, name: str) -> t.Iterator[None]: """Helpful context manager that writes a paragraph, a heading, and the indents. @@ -223,7 +267,7 @@ class HelpFormatter(object): self.dedent() @contextmanager - def indentation(self): + def indentation(self) -> t.Iterator[None]: """A context manager that increases the indentation.""" self.indent() try: @@ -231,12 +275,12 @@ class HelpFormatter(object): finally: self.dedent() - def getvalue(self): + def getvalue(self) -> str: """Returns the buffer contents.""" - return ''.join(self.buffer) + return "".join(self.buffer) -def join_options(options): +def join_options(options: t.Sequence[str]) -> t.Tuple[str, bool]: """Given a list of option strings this joins them in the most appropriate way and returns them in the form ``(formatted_string, any_prefix_is_slash)`` where the second item in the tuple is a flag that @@ -244,13 +288,14 @@ def join_options(options): """ rv = [] any_prefix_is_slash = False + for opt in options: prefix = split_opt(opt)[0] - if prefix == '/': + + if prefix == "/": any_prefix_is_slash = True + rv.append((len(prefix), opt)) rv.sort(key=lambda x: x[0]) - - rv = ', '.join(x[1] for x in rv) - return rv, any_prefix_is_slash + return ", ".join(x[1] for x in rv), any_prefix_is_slash diff --git a/pipenv/vendor/click/globals.py b/pipenv/vendor/click/globals.py index 843b594a..a7b0c931 100644 --- a/pipenv/vendor/click/globals.py +++ b/pipenv/vendor/click/globals.py @@ -1,10 +1,25 @@ +import typing +import typing as t from threading import local +if t.TYPE_CHECKING: + import typing_extensions as te + from .core import Context _local = local() -def get_current_context(silent=False): +@typing.overload +def get_current_context(silent: "te.Literal[False]" = False) -> "Context": + ... + + +@typing.overload +def get_current_context(silent: bool = ...) -> t.Optional["Context"]: + ... + + +def get_current_context(silent: bool = False) -> t.Optional["Context"]: """Returns the current click context. This can be used as a way to access the current context object from anywhere. This is a more implicit alternative to the :func:`pass_context` decorator. This function is @@ -15,34 +30,40 @@ def get_current_context(silent=False): .. versionadded:: 5.0 - :param silent: is set to `True` the return value is `None` if no context + :param silent: if set to `True` the return value is `None` if no context is available. The default behavior is to raise a :exc:`RuntimeError`. """ try: - return getattr(_local, 'stack')[-1] - except (AttributeError, IndexError): + return t.cast("Context", _local.stack[-1]) + except (AttributeError, IndexError) as e: if not silent: - raise RuntimeError('There is no active click context.') + raise RuntimeError("There is no active click context.") from e + + return None -def push_context(ctx): +def push_context(ctx: "Context") -> None: """Pushes a new context to the current stack.""" - _local.__dict__.setdefault('stack', []).append(ctx) + _local.__dict__.setdefault("stack", []).append(ctx) -def pop_context(): +def pop_context() -> None: """Removes the top level from the stack.""" _local.stack.pop() -def resolve_color_default(color=None): - """"Internal helper to get the default value of the color flag. If a +def resolve_color_default(color: t.Optional[bool] = None) -> t.Optional[bool]: + """Internal helper to get the default value of the color flag. If a value is passed it's returned unchanged, otherwise it's looked up from the current context. """ if color is not None: return color + ctx = get_current_context(silent=True) + if ctx is not None: return ctx.color + + return None diff --git a/pipenv/vendor/click/parser.py b/pipenv/vendor/click/parser.py index 1c3ae9c8..2d5a2ed7 100644 --- a/pipenv/vendor/click/parser.py +++ b/pipenv/vendor/click/parser.py @@ -1,8 +1,4 @@ -# -*- coding: utf-8 -*- """ -click.parser -~~~~~~~~~~~~ - This module started out as largely a copy paste from the stdlib's optparse module with the features removed that we do not need from optparse because we implement them in Click on a higher level (for @@ -14,15 +10,45 @@ The reason this is a different module and not optparse from the stdlib is that there are differences in 2.x and 3.x about the error messages generated and optparse in the stdlib uses gettext for no good reason and might cause us issues. + +Click uses parts of optparse written by Gregory P. Ward and maintained +by the Python Software Foundation. This is limited to code in parser.py. + +Copyright 2001-2006 Gregory P. Ward. All rights reserved. +Copyright 2002-2006 Python Software Foundation. All rights reserved. """ - -import re +# This code uses parts of optparse written by Gregory P. Ward and +# maintained by the Python Software Foundation. +# Copyright 2001-2006 Gregory P. Ward +# Copyright 2002-2006 Python Software Foundation +import typing as t from collections import deque -from .exceptions import UsageError, NoSuchOption, BadOptionUsage, \ - BadArgumentUsage +from gettext import gettext as _ +from gettext import ngettext + +from .exceptions import BadArgumentUsage +from .exceptions import BadOptionUsage +from .exceptions import NoSuchOption +from .exceptions import UsageError + +if t.TYPE_CHECKING: + import typing_extensions as te + from .core import Argument as CoreArgument + from .core import Context + from .core import Option as CoreOption + from .core import Parameter as CoreParameter + +V = t.TypeVar("V") + +# Sentinel value that indicates an option was passed as a flag without a +# value but is not a flag option. Option.consume_value uses this to +# prompt or use the flag_value. +_flag_needs_value = object() -def _unpack_args(args, nargs_spec): +def _unpack_args( + args: t.Sequence[str], nargs_spec: t.Sequence[int] +) -> t.Tuple[t.Sequence[t.Union[str, t.Sequence[t.Optional[str]], None]], t.List[str]]: """Given an iterable of arguments and an iterable of nargs specifications, it returns a tuple with all the unpacked arguments at the first index and all remaining arguments as the second. @@ -34,10 +60,10 @@ def _unpack_args(args, nargs_spec): """ args = deque(args) nargs_spec = deque(nargs_spec) - rv = [] - spos = None + rv: t.List[t.Union[str, t.Tuple[t.Optional[str], ...], None]] = [] + spos: t.Optional[int] = None - def _fetch(c): + def _fetch(c: "te.Deque[V]") -> t.Optional[V]: try: if spos is None: return c.popleft() @@ -48,18 +74,25 @@ def _unpack_args(args, nargs_spec): while nargs_spec: nargs = _fetch(nargs_spec) + + if nargs is None: + continue + if nargs == 1: rv.append(_fetch(args)) elif nargs > 1: x = [_fetch(args) for _ in range(nargs)] + # If we're reversed, we're pulling in the arguments in reverse, # so we need to turn them around. if spos is not None: x.reverse() + rv.append(tuple(x)) elif nargs < 0: if spos is not None: - raise TypeError('Cannot have two nargs < 0') + raise TypeError("Cannot have two nargs < 0") + spos = len(rv) rv.append(None) @@ -68,54 +101,71 @@ def _unpack_args(args, nargs_spec): if spos is not None: rv[spos] = tuple(args) args = [] - rv[spos + 1:] = reversed(rv[spos + 1:]) + rv[spos + 1 :] = reversed(rv[spos + 1 :]) return tuple(rv), list(args) -def _error_opt_args(nargs, opt): - if nargs == 1: - raise BadOptionUsage(opt, '%s option requires an argument' % opt) - raise BadOptionUsage(opt, '%s option requires %d arguments' % (opt, nargs)) - - -def split_opt(opt): +def split_opt(opt: str) -> t.Tuple[str, str]: first = opt[:1] if first.isalnum(): - return '', opt + return "", opt if opt[1:2] == first: return opt[:2], opt[2:] return first, opt[1:] -def normalize_opt(opt, ctx): +def normalize_opt(opt: str, ctx: t.Optional["Context"]) -> str: if ctx is None or ctx.token_normalize_func is None: return opt prefix, opt = split_opt(opt) - return prefix + ctx.token_normalize_func(opt) + return f"{prefix}{ctx.token_normalize_func(opt)}" -def split_arg_string(string): - """Given an argument string this attempts to split it into small parts.""" - rv = [] - for match in re.finditer(r"('([^'\\]*(?:\\.[^'\\]*)*)'" - r'|"([^"\\]*(?:\\.[^"\\]*)*)"' - r'|\S+)\s*', string, re.S): - arg = match.group().strip() - if arg[:1] == arg[-1:] and arg[:1] in '"\'': - arg = arg[1:-1].encode('ascii', 'backslashreplace') \ - .decode('unicode-escape') - try: - arg = type(string)(arg) - except UnicodeError: - pass - rv.append(arg) - return rv +def split_arg_string(string: str) -> t.List[str]: + """Split an argument string as with :func:`shlex.split`, but don't + fail if the string is incomplete. Ignores a missing closing quote or + incomplete escape sequence and uses the partial token as-is. + + .. code-block:: python + + split_arg_string("example 'my file") + ["example", "my file"] + + split_arg_string("example my\\") + ["example", "my"] + + :param string: String to split. + """ + import shlex + + lex = shlex.shlex(string, posix=True) + lex.whitespace_split = True + lex.commenters = "" + out = [] + + try: + for token in lex: + out.append(token) + except ValueError: + # Raised when end-of-string is reached in an invalid state. Use + # the partial token as-is. The quote or escape character is in + # lex.state, not lex.token. + out.append(lex.token) + + return out -class Option(object): - - def __init__(self, opts, dest, action=None, nargs=1, const=None, obj=None): +class Option: + def __init__( + self, + obj: "CoreOption", + opts: t.Sequence[str], + dest: t.Optional[str], + action: t.Optional[str] = None, + nargs: int = 1, + const: t.Optional[t.Any] = None, + ): self._short_opts = [] self._long_opts = [] self.prefixes = set() @@ -123,8 +173,7 @@ class Option(object): for opt in opts: prefix, value = split_opt(opt) if not prefix: - raise ValueError('Invalid start character for option (%s)' - % opt) + raise ValueError(f"Invalid start character for option ({opt})") self.prefixes.add(prefix[0]) if len(prefix) == 1 and len(value) == 1: self._short_opts.append(opt) @@ -133,7 +182,7 @@ class Option(object): self.prefixes.add(prefix) if action is None: - action = 'store' + action = "store" self.dest = dest self.action = action @@ -142,54 +191,66 @@ class Option(object): self.obj = obj @property - def takes_value(self): - return self.action in ('store', 'append') + def takes_value(self) -> bool: + return self.action in ("store", "append") - def process(self, value, state): - if self.action == 'store': - state.opts[self.dest] = value - elif self.action == 'store_const': - state.opts[self.dest] = self.const - elif self.action == 'append': - state.opts.setdefault(self.dest, []).append(value) - elif self.action == 'append_const': - state.opts.setdefault(self.dest, []).append(self.const) - elif self.action == 'count': - state.opts[self.dest] = state.opts.get(self.dest, 0) + 1 + def process(self, value: str, state: "ParsingState") -> None: + if self.action == "store": + state.opts[self.dest] = value # type: ignore + elif self.action == "store_const": + state.opts[self.dest] = self.const # type: ignore + elif self.action == "append": + state.opts.setdefault(self.dest, []).append(value) # type: ignore + elif self.action == "append_const": + state.opts.setdefault(self.dest, []).append(self.const) # type: ignore + elif self.action == "count": + state.opts[self.dest] = state.opts.get(self.dest, 0) + 1 # type: ignore else: - raise ValueError('unknown action %r' % self.action) + raise ValueError(f"unknown action '{self.action}'") state.order.append(self.obj) -class Argument(object): - - def __init__(self, dest, nargs=1, obj=None): +class Argument: + def __init__(self, obj: "CoreArgument", dest: t.Optional[str], nargs: int = 1): self.dest = dest self.nargs = nargs self.obj = obj - def process(self, value, state): + def process( + self, + value: t.Union[t.Optional[str], t.Sequence[t.Optional[str]]], + state: "ParsingState", + ) -> None: if self.nargs > 1: + assert value is not None holes = sum(1 for x in value if x is None) if holes == len(value): value = None elif holes != 0: - raise BadArgumentUsage('argument %s takes %d values' - % (self.dest, self.nargs)) - state.opts[self.dest] = value + raise BadArgumentUsage( + _("Argument {name!r} takes {nargs} values.").format( + name=self.dest, nargs=self.nargs + ) + ) + + if self.nargs == -1 and self.obj.envvar is not None and value == (): + # Replace empty tuple with None so that a value from the + # environment may be tried. + value = None + + state.opts[self.dest] = value # type: ignore state.order.append(self.obj) -class ParsingState(object): - - def __init__(self, rargs): - self.opts = {} - self.largs = [] +class ParsingState: + def __init__(self, rargs: t.List[str]) -> None: + self.opts: t.Dict[str, t.Any] = {} + self.largs: t.List[str] = [] self.rargs = rargs - self.order = [] + self.order: t.List["CoreParameter"] = [] -class OptionParser(object): +class OptionParser: """The option parser is an internal class that is ultimately used to parse options and arguments. It's modelled after optparse and brings a similar but vastly simplified API. It should generally not be used @@ -203,7 +264,7 @@ class OptionParser(object): should go with. """ - def __init__(self, ctx=None): + def __init__(self, ctx: t.Optional["Context"] = None) -> None: #: The :class:`~click.Context` for this parser. This might be #: `None` for some advanced use cases. self.ctx = ctx @@ -217,46 +278,54 @@ class OptionParser(object): #: second mode where it will ignore it and continue processing #: after shifting all the unknown options into the resulting args. self.ignore_unknown_options = False + if ctx is not None: self.allow_interspersed_args = ctx.allow_interspersed_args self.ignore_unknown_options = ctx.ignore_unknown_options - self._short_opt = {} - self._long_opt = {} - self._opt_prefixes = set(['-', '--']) - self._args = [] - def add_option(self, opts, dest, action=None, nargs=1, const=None, - obj=None): + self._short_opt: t.Dict[str, Option] = {} + self._long_opt: t.Dict[str, Option] = {} + self._opt_prefixes = {"-", "--"} + self._args: t.List[Argument] = [] + + def add_option( + self, + obj: "CoreOption", + opts: t.Sequence[str], + dest: t.Optional[str], + action: t.Optional[str] = None, + nargs: int = 1, + const: t.Optional[t.Any] = None, + ) -> None: """Adds a new option named `dest` to the parser. The destination is not inferred (unlike with optparse) and needs to be explicitly provided. Action can be any of ``store``, ``store_const``, - ``append``, ``appnd_const`` or ``count``. + ``append``, ``append_const`` or ``count``. The `obj` can be used to identify the option in the order list that is returned from the parser. """ - if obj is None: - obj = dest opts = [normalize_opt(opt, self.ctx) for opt in opts] - option = Option(opts, dest, action=action, nargs=nargs, - const=const, obj=obj) + option = Option(obj, opts, dest, action=action, nargs=nargs, const=const) self._opt_prefixes.update(option.prefixes) for opt in option._short_opts: self._short_opt[opt] = option for opt in option._long_opts: self._long_opt[opt] = option - def add_argument(self, dest, nargs=1, obj=None): + def add_argument( + self, obj: "CoreArgument", dest: t.Optional[str], nargs: int = 1 + ) -> None: """Adds a positional argument named `dest` to the parser. The `obj` can be used to identify the option in the order list that is returned from the parser. """ - if obj is None: - obj = dest - self._args.append(Argument(dest=dest, nargs=nargs, obj=obj)) + self._args.append(Argument(obj, dest=dest, nargs=nargs)) - def parse_args(self, args): + def parse_args( + self, args: t.List[str] + ) -> t.Tuple[t.Dict[str, t.Any], t.List[str], t.List["CoreParameter"]]: """Parses positional arguments and returns ``(values, args, order)`` for the parsed options and arguments as well as the leftover arguments if there are any. The order is a list of objects as they @@ -272,9 +341,10 @@ class OptionParser(object): raise return state.opts, state.largs, state.order - def _process_args_for_args(self, state): - pargs, args = _unpack_args(state.largs + state.rargs, - [x.nargs for x in self._args]) + def _process_args_for_args(self, state: ParsingState) -> None: + pargs, args = _unpack_args( + state.largs + state.rargs, [x.nargs for x in self._args] + ) for idx, arg in enumerate(self._args): arg.process(pargs[idx], state) @@ -282,13 +352,13 @@ class OptionParser(object): state.largs = args state.rargs = [] - def _process_args_for_options(self, state): + def _process_args_for_options(self, state: ParsingState) -> None: while state.rargs: arg = state.rargs.pop(0) arglen = len(arg) # Double dashes always handled explicitly regardless of what # prefixes are valid. - if arg == '--': + if arg == "--": return elif arg[:1] in self._opt_prefixes and arglen > 1: self._process_opts(arg, state) @@ -318,10 +388,13 @@ class OptionParser(object): # *empty* -- still a subset of [arg0, ..., arg(i-1)], but # not a very interesting subset! - def _match_long_opt(self, opt, explicit_value, state): + def _match_long_opt( + self, opt: str, explicit_value: t.Optional[str], state: ParsingState + ) -> None: if opt not in self._long_opt: - possibilities = [word for word in self._long_opt - if word.startswith(opt)] + from difflib import get_close_matches + + possibilities = get_close_matches(opt, self._long_opt) raise NoSuchOption(opt, possibilities=possibilities, ctx=self.ctx) option = self._long_opt[opt] @@ -333,31 +406,26 @@ class OptionParser(object): if explicit_value is not None: state.rargs.insert(0, explicit_value) - nargs = option.nargs - if len(state.rargs) < nargs: - _error_opt_args(nargs, opt) - elif nargs == 1: - value = state.rargs.pop(0) - else: - value = tuple(state.rargs[:nargs]) - del state.rargs[:nargs] + value = self._get_value_from_state(opt, option, state) elif explicit_value is not None: - raise BadOptionUsage(opt, '%s option does not take a value' % opt) + raise BadOptionUsage( + opt, _("Option {name!r} does not take a value.").format(name=opt) + ) else: value = None option.process(value, state) - def _match_short_opt(self, arg, state): + def _match_short_opt(self, arg: str, state: ParsingState) -> None: stop = False i = 1 prefix = arg[0] unknown_options = [] for ch in arg[1:]: - opt = normalize_opt(prefix + ch, self.ctx) + opt = normalize_opt(f"{prefix}{ch}", self.ctx) option = self._short_opt.get(opt) i += 1 @@ -373,14 +441,7 @@ class OptionParser(object): state.rargs.insert(0, arg[i:]) stop = True - nargs = option.nargs - if len(state.rargs) < nargs: - _error_opt_args(nargs, opt) - elif nargs == 1: - value = state.rargs.pop(0) - else: - value = tuple(state.rargs[:nargs]) - del state.rargs[:nargs] + value = self._get_value_from_state(opt, option, state) else: value = None @@ -395,15 +456,53 @@ class OptionParser(object): # to the state as new larg. This way there is basic combinatorics # that can be achieved while still ignoring unknown arguments. if self.ignore_unknown_options and unknown_options: - state.largs.append(prefix + ''.join(unknown_options)) + state.largs.append(f"{prefix}{''.join(unknown_options)}") - def _process_opts(self, arg, state): + def _get_value_from_state( + self, option_name: str, option: Option, state: ParsingState + ) -> t.Any: + nargs = option.nargs + + if len(state.rargs) < nargs: + if option.obj._flag_needs_value: + # Option allows omitting the value. + value = _flag_needs_value + else: + raise BadOptionUsage( + option_name, + ngettext( + "Option {name!r} requires an argument.", + "Option {name!r} requires {nargs} arguments.", + nargs, + ).format(name=option_name, nargs=nargs), + ) + elif nargs == 1: + next_rarg = state.rargs[0] + + if ( + option.obj._flag_needs_value + and isinstance(next_rarg, str) + and next_rarg[:1] in self._opt_prefixes + and len(next_rarg) > 1 + ): + # The next arg looks like the start of an option, don't + # use it as the value if omitting the value is allowed. + value = _flag_needs_value + else: + value = state.rargs.pop(0) + else: + value = tuple(state.rargs[:nargs]) + del state.rargs[:nargs] + + return value + + def _process_opts(self, arg: str, state: ParsingState) -> None: explicit_value = None # Long option handling happens in two parts. The first part is # supporting explicitly attached values. In any case, we will try # to long match the option first. - if '=' in arg: - long_opt, explicit_value = arg.split('=', 1) + if "=" in arg: + long_opt, explicit_value = arg.split("=", 1) else: long_opt = arg norm_long_opt = normalize_opt(long_opt, self.ctx) @@ -421,7 +520,10 @@ class OptionParser(object): # short option code and will instead raise the no option # error. if arg[:2] not in self._opt_prefixes: - return self._match_short_opt(arg, state) + self._match_short_opt(arg, state) + return + if not self.ignore_unknown_options: raise + state.largs.append(arg) diff --git a/pipenv/vendor/click/py.typed b/pipenv/vendor/click/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/pipenv/vendor/click/shell_completion.py b/pipenv/vendor/click/shell_completion.py new file mode 100644 index 00000000..cad080da --- /dev/null +++ b/pipenv/vendor/click/shell_completion.py @@ -0,0 +1,581 @@ +import os +import re +import typing as t +from gettext import gettext as _ + +from .core import Argument +from .core import BaseCommand +from .core import Context +from .core import MultiCommand +from .core import Option +from .core import Parameter +from .core import ParameterSource +from .parser import split_arg_string +from .utils import echo + + +def shell_complete( + cli: BaseCommand, + ctx_args: t.Dict[str, t.Any], + prog_name: str, + complete_var: str, + instruction: str, +) -> int: + """Perform shell completion for the given CLI program. + + :param cli: Command being called. + :param ctx_args: Extra arguments to pass to + ``cli.make_context``. + :param prog_name: Name of the executable in the shell. + :param complete_var: Name of the environment variable that holds + the completion instruction. + :param instruction: Value of ``complete_var`` with the completion + instruction and shell, in the form ``instruction_shell``. + :return: Status code to exit with. + """ + shell, _, instruction = instruction.partition("_") + comp_cls = get_completion_class(shell) + + if comp_cls is None: + return 1 + + comp = comp_cls(cli, ctx_args, prog_name, complete_var) + + if instruction == "source": + echo(comp.source()) + return 0 + + if instruction == "complete": + echo(comp.complete()) + return 0 + + return 1 + + +class CompletionItem: + """Represents a completion value and metadata about the value. The + default metadata is ``type`` to indicate special shell handling, + and ``help`` if a shell supports showing a help string next to the + value. + + Arbitrary parameters can be passed when creating the object, and + accessed using ``item.attr``. If an attribute wasn't passed, + accessing it returns ``None``. + + :param value: The completion suggestion. + :param type: Tells the shell script to provide special completion + support for the type. Click uses ``"dir"`` and ``"file"``. + :param help: String shown next to the value if supported. + :param kwargs: Arbitrary metadata. The built-in implementations + don't use this, but custom type completions paired with custom + shell support could use it. + """ + + __slots__ = ("value", "type", "help", "_info") + + def __init__( + self, + value: t.Any, + type: str = "plain", + help: t.Optional[str] = None, + **kwargs: t.Any, + ) -> None: + self.value = value + self.type = type + self.help = help + self._info = kwargs + + def __getattr__(self, name: str) -> t.Any: + return self._info.get(name) + + +# Only Bash >= 4.4 has the nosort option. +_SOURCE_BASH = """\ +%(complete_func)s() { + local IFS=$'\\n' + local response + + response=$(env COMP_WORDS="${COMP_WORDS[*]}" COMP_CWORD=$COMP_CWORD \ +%(complete_var)s=bash_complete $1) + + for completion in $response; do + IFS=',' read type value <<< "$completion" + + if [[ $type == 'dir' ]]; then + COMREPLY=() + compopt -o dirnames + elif [[ $type == 'file' ]]; then + COMREPLY=() + compopt -o default + elif [[ $type == 'plain' ]]; then + COMPREPLY+=($value) + fi + done + + return 0 +} + +%(complete_func)s_setup() { + complete -o nosort -F %(complete_func)s %(prog_name)s +} + +%(complete_func)s_setup; +""" + +_SOURCE_ZSH = """\ +#compdef %(prog_name)s + +%(complete_func)s() { + local -a completions + local -a completions_with_descriptions + local -a response + (( ! $+commands[%(prog_name)s] )) && return 1 + + response=("${(@f)$(env COMP_WORDS="${words[*]}" COMP_CWORD=$((CURRENT-1)) \ +%(complete_var)s=zsh_complete %(prog_name)s)}") + + for type key descr in ${response}; do + if [[ "$type" == "plain" ]]; then + if [[ "$descr" == "_" ]]; then + completions+=("$key") + else + completions_with_descriptions+=("$key":"$descr") + fi + elif [[ "$type" == "dir" ]]; then + _path_files -/ + elif [[ "$type" == "file" ]]; then + _path_files -f + fi + done + + if [ -n "$completions_with_descriptions" ]; then + _describe -V unsorted completions_with_descriptions -U + fi + + if [ -n "$completions" ]; then + compadd -U -V unsorted -a completions + fi +} + +compdef %(complete_func)s %(prog_name)s; +""" + +_SOURCE_FISH = """\ +function %(complete_func)s; + set -l response; + + for value in (env %(complete_var)s=fish_complete COMP_WORDS=(commandline -cp) \ +COMP_CWORD=(commandline -t) %(prog_name)s); + set response $response $value; + end; + + for completion in $response; + set -l metadata (string split "," $completion); + + if test $metadata[1] = "dir"; + __fish_complete_directories $metadata[2]; + else if test $metadata[1] = "file"; + __fish_complete_path $metadata[2]; + else if test $metadata[1] = "plain"; + echo $metadata[2]; + end; + end; +end; + +complete --no-files --command %(prog_name)s --arguments \ +"(%(complete_func)s)"; +""" + + +class ShellComplete: + """Base class for providing shell completion support. A subclass for + a given shell will override attributes and methods to implement the + completion instructions (``source`` and ``complete``). + + :param cli: Command being called. + :param prog_name: Name of the executable in the shell. + :param complete_var: Name of the environment variable that holds + the completion instruction. + + .. versionadded:: 8.0 + """ + + name: t.ClassVar[str] + """Name to register the shell as with :func:`add_completion_class`. + This is used in completion instructions (``{name}_source`` and + ``{name}_complete``). + """ + + source_template: t.ClassVar[str] + """Completion script template formatted by :meth:`source`. This must + be provided by subclasses. + """ + + def __init__( + self, + cli: BaseCommand, + ctx_args: t.Dict[str, t.Any], + prog_name: str, + complete_var: str, + ) -> None: + self.cli = cli + self.ctx_args = ctx_args + self.prog_name = prog_name + self.complete_var = complete_var + + @property + def func_name(self) -> str: + """The name of the shell function defined by the completion + script. + """ + safe_name = re.sub(r"\W*", "", self.prog_name.replace("-", "_"), re.ASCII) + return f"_{safe_name}_completion" + + def source_vars(self) -> t.Dict[str, t.Any]: + """Vars for formatting :attr:`source_template`. + + By default this provides ``complete_func``, ``complete_var``, + and ``prog_name``. + """ + return { + "complete_func": self.func_name, + "complete_var": self.complete_var, + "prog_name": self.prog_name, + } + + def source(self) -> str: + """Produce the shell script that defines the completion + function. By default this ``%``-style formats + :attr:`source_template` with the dict returned by + :meth:`source_vars`. + """ + return self.source_template % self.source_vars() + + def get_completion_args(self) -> t.Tuple[t.List[str], str]: + """Use the env vars defined by the shell script to return a + tuple of ``args, incomplete``. This must be implemented by + subclasses. + """ + raise NotImplementedError + + def get_completions( + self, args: t.List[str], incomplete: str + ) -> t.List[CompletionItem]: + """Determine the context and last complete command or parameter + from the complete args. Call that object's ``shell_complete`` + method to get the completions for the incomplete value. + + :param args: List of complete args before the incomplete value. + :param incomplete: Value being completed. May be empty. + """ + ctx = _resolve_context(self.cli, self.ctx_args, self.prog_name, args) + obj, incomplete = _resolve_incomplete(ctx, args, incomplete) + return obj.shell_complete(ctx, incomplete) + + def format_completion(self, item: CompletionItem) -> str: + """Format a completion item into the form recognized by the + shell script. This must be implemented by subclasses. + + :param item: Completion item to format. + """ + raise NotImplementedError + + def complete(self) -> str: + """Produce the completion data to send back to the shell. + + By default this calls :meth:`get_completion_args`, gets the + completions, then calls :meth:`format_completion` for each + completion. + """ + args, incomplete = self.get_completion_args() + completions = self.get_completions(args, incomplete) + out = [self.format_completion(item) for item in completions] + return "\n".join(out) + + +class BashComplete(ShellComplete): + """Shell completion for Bash.""" + + name = "bash" + source_template = _SOURCE_BASH + + def _check_version(self) -> None: + import subprocess + + output = subprocess.run( + ["bash", "-c", "echo ${BASH_VERSION}"], stdout=subprocess.PIPE + ) + match = re.search(r"^(\d+)\.(\d+)\.\d+", output.stdout.decode()) + + if match is not None: + major, minor = match.groups() + + if major < "4" or major == "4" and minor < "4": + raise RuntimeError( + _( + "Shell completion is not supported for Bash" + " versions older than 4.4." + ) + ) + else: + raise RuntimeError( + _("Couldn't detect Bash version, shell completion is not supported.") + ) + + def source(self) -> str: + self._check_version() + return super().source() + + def get_completion_args(self) -> t.Tuple[t.List[str], str]: + cwords = split_arg_string(os.environ["COMP_WORDS"]) + cword = int(os.environ["COMP_CWORD"]) + args = cwords[1:cword] + + try: + incomplete = cwords[cword] + except IndexError: + incomplete = "" + + return args, incomplete + + def format_completion(self, item: CompletionItem) -> str: + return f"{item.type},{item.value}" + + +class ZshComplete(ShellComplete): + """Shell completion for Zsh.""" + + name = "zsh" + source_template = _SOURCE_ZSH + + def get_completion_args(self) -> t.Tuple[t.List[str], str]: + cwords = split_arg_string(os.environ["COMP_WORDS"]) + cword = int(os.environ["COMP_CWORD"]) + args = cwords[1:cword] + + try: + incomplete = cwords[cword] + except IndexError: + incomplete = "" + + return args, incomplete + + def format_completion(self, item: CompletionItem) -> str: + return f"{item.type}\n{item.value}\n{item.help if item.help else '_'}" + + +class FishComplete(ShellComplete): + """Shell completion for Fish.""" + + name = "fish" + source_template = _SOURCE_FISH + + def get_completion_args(self) -> t.Tuple[t.List[str], str]: + cwords = split_arg_string(os.environ["COMP_WORDS"]) + incomplete = os.environ["COMP_CWORD"] + args = cwords[1:] + + # Fish stores the partial word in both COMP_WORDS and + # COMP_CWORD, remove it from complete args. + if incomplete and args and args[-1] == incomplete: + args.pop() + + return args, incomplete + + def format_completion(self, item: CompletionItem) -> str: + if item.help: + return f"{item.type},{item.value}\t{item.help}" + + return f"{item.type},{item.value}" + + +_available_shells: t.Dict[str, t.Type[ShellComplete]] = { + "bash": BashComplete, + "fish": FishComplete, + "zsh": ZshComplete, +} + + +def add_completion_class( + cls: t.Type[ShellComplete], name: t.Optional[str] = None +) -> None: + """Register a :class:`ShellComplete` subclass under the given name. + The name will be provided by the completion instruction environment + variable during completion. + + :param cls: The completion class that will handle completion for the + shell. + :param name: Name to register the class under. Defaults to the + class's ``name`` attribute. + """ + if name is None: + name = cls.name + + _available_shells[name] = cls + + +def get_completion_class(shell: str) -> t.Optional[t.Type[ShellComplete]]: + """Look up a registered :class:`ShellComplete` subclass by the name + provided by the completion instruction environment variable. If the + name isn't registered, returns ``None``. + + :param shell: Name the class is registered under. + """ + return _available_shells.get(shell) + + +def _is_incomplete_argument(ctx: Context, param: Parameter) -> bool: + """Determine if the given parameter is an argument that can still + accept values. + + :param ctx: Invocation context for the command represented by the + parsed complete args. + :param param: Argument object being checked. + """ + if not isinstance(param, Argument): + return False + + assert param.name is not None + value = ctx.params[param.name] + return ( + param.nargs == -1 + or ctx.get_parameter_source(param.name) is not ParameterSource.COMMANDLINE + or ( + param.nargs > 1 + and isinstance(value, (tuple, list)) + and len(value) < param.nargs + ) + ) + + +def _start_of_option(value: str) -> bool: + """Check if the value looks like the start of an option.""" + if not value: + return False + + c = value[0] + # Allow "/" since that starts a path. + return not c.isalnum() and c != "/" + + +def _is_incomplete_option(args: t.List[str], param: Parameter) -> bool: + """Determine if the given parameter is an option that needs a value. + + :param args: List of complete args before the incomplete value. + :param param: Option object being checked. + """ + if not isinstance(param, Option): + return False + + if param.is_flag: + return False + + last_option = None + + for index, arg in enumerate(reversed(args)): + if index + 1 > param.nargs: + break + + if _start_of_option(arg): + last_option = arg + + return last_option is not None and last_option in param.opts + + +def _resolve_context( + cli: BaseCommand, ctx_args: t.Dict[str, t.Any], prog_name: str, args: t.List[str] +) -> Context: + """Produce the context hierarchy starting with the command and + traversing the complete arguments. This only follows the commands, + it doesn't trigger input prompts or callbacks. + + :param cli: Command being called. + :param prog_name: Name of the executable in the shell. + :param args: List of complete args before the incomplete value. + """ + ctx_args["resilient_parsing"] = True + ctx = cli.make_context(prog_name, args.copy(), **ctx_args) + args = ctx.protected_args + ctx.args + + while args: + command = ctx.command + + if isinstance(command, MultiCommand): + if not command.chain: + name, cmd, args = command.resolve_command(ctx, args) + + if cmd is None: + return ctx + + ctx = cmd.make_context(name, args, parent=ctx, resilient_parsing=True) + args = ctx.protected_args + ctx.args + else: + while args: + name, cmd, args = command.resolve_command(ctx, args) + + if cmd is None: + return ctx + + sub_ctx = cmd.make_context( + name, + args, + parent=ctx, + allow_extra_args=True, + allow_interspersed_args=False, + resilient_parsing=True, + ) + args = sub_ctx.args + + ctx = sub_ctx + args = [*sub_ctx.protected_args, *sub_ctx.args] + else: + break + + return ctx + + +def _resolve_incomplete( + ctx: Context, args: t.List[str], incomplete: str +) -> t.Tuple[t.Union[BaseCommand, Parameter], str]: + """Find the Click object that will handle the completion of the + incomplete value. Return the object and the incomplete value. + + :param ctx: Invocation context for the command represented by + the parsed complete args. + :param args: List of complete args before the incomplete value. + :param incomplete: Value being completed. May be empty. + """ + # Different shells treat an "=" between a long option name and + # value differently. Might keep the value joined, return the "=" + # as a separate item, or return the split name and value. Always + # split and discard the "=" to make completion easier. + if incomplete == "=": + incomplete = "" + elif "=" in incomplete and _start_of_option(incomplete): + name, _, incomplete = incomplete.partition("=") + args.append(name) + + # The "--" marker tells Click to stop treating values as options + # even if they start with the option character. If it hasn't been + # given and the incomplete arg looks like an option, the current + # command will provide option name completions. + if "--" not in args and _start_of_option(incomplete): + return ctx.command, incomplete + + params = ctx.command.get_params(ctx) + + # If the last complete arg is an option name with an incomplete + # value, the option will provide value completions. + for param in params: + if _is_incomplete_option(args, param): + return param, incomplete + + # It's not an option name or value. The first argument without a + # parsed value will provide value completions. + for param in params: + if _is_incomplete_argument(ctx, param): + return param, incomplete + + # There were no unparsed arguments, the command may be a group that + # will provide command name completions. + return ctx.command, incomplete diff --git a/pipenv/vendor/click/termui.py b/pipenv/vendor/click/termui.py index bf9a3aa1..cf8d5f13 100644 --- a/pipenv/vendor/click/termui.py +++ b/pipenv/vendor/click/termui.py @@ -1,81 +1,110 @@ +import inspect +import io +import itertools import os import sys -import struct -import inspect -import itertools +import typing +import typing as t +from gettext import gettext as _ -from ._compat import raw_input, text_type, string_types, \ - isatty, strip_ansi, get_winterm_size, DEFAULT_COLUMNS, WIN -from .utils import echo -from .exceptions import Abort, UsageError -from .types import convert_type, Choice, Path +from ._compat import isatty +from ._compat import strip_ansi +from ._compat import WIN +from .exceptions import Abort +from .exceptions import UsageError from .globals import resolve_color_default +from .types import Choice +from .types import convert_type +from .types import ParamType +from .utils import echo +from .utils import LazyFile +if t.TYPE_CHECKING: + from ._termui_impl import ProgressBar + +V = t.TypeVar("V") # The prompt functions to use. The doc tools currently override these # functions to customize how they work. -visible_prompt_func = raw_input +visible_prompt_func: t.Callable[[str], str] = input _ansi_colors = { - 'black': 30, - 'red': 31, - 'green': 32, - 'yellow': 33, - 'blue': 34, - 'magenta': 35, - 'cyan': 36, - 'white': 37, - 'reset': 39, - 'bright_black': 90, - 'bright_red': 91, - 'bright_green': 92, - 'bright_yellow': 93, - 'bright_blue': 94, - 'bright_magenta': 95, - 'bright_cyan': 96, - 'bright_white': 97, + "black": 30, + "red": 31, + "green": 32, + "yellow": 33, + "blue": 34, + "magenta": 35, + "cyan": 36, + "white": 37, + "reset": 39, + "bright_black": 90, + "bright_red": 91, + "bright_green": 92, + "bright_yellow": 93, + "bright_blue": 94, + "bright_magenta": 95, + "bright_cyan": 96, + "bright_white": 97, } -_ansi_reset_all = '\033[0m' +_ansi_reset_all = "\033[0m" -def hidden_prompt_func(prompt): +def hidden_prompt_func(prompt: str) -> str: import getpass + return getpass.getpass(prompt) -def _build_prompt(text, suffix, show_default=False, default=None, show_choices=True, type=None): +def _build_prompt( + text: str, + suffix: str, + show_default: bool = False, + default: t.Optional[t.Any] = None, + show_choices: bool = True, + type: t.Optional[ParamType] = None, +) -> str: prompt = text if type is not None and show_choices and isinstance(type, Choice): - prompt += ' (' + ", ".join(map(str, type.choices)) + ')' + prompt += f" ({', '.join(map(str, type.choices))})" if default is not None and show_default: - prompt = '%s [%s]' % (prompt, default) - return prompt + suffix + prompt = f"{prompt} [{_format_default(default)}]" + return f"{prompt}{suffix}" -def prompt(text, default=None, hide_input=False, confirmation_prompt=False, - type=None, value_proc=None, prompt_suffix=': ', show_default=True, - err=False, show_choices=True): +def _format_default(default: t.Any) -> t.Any: + if isinstance(default, (io.IOBase, LazyFile)) and hasattr(default, "name"): + return default.name # type: ignore + + return default + + +def prompt( + text: str, + default: t.Optional[t.Any] = None, + hide_input: bool = False, + confirmation_prompt: t.Union[bool, str] = False, + type: t.Optional[t.Union[ParamType, t.Any]] = None, + value_proc: t.Optional[t.Callable[[str], t.Any]] = None, + prompt_suffix: str = ": ", + show_default: bool = True, + err: bool = False, + show_choices: bool = True, +) -> t.Any: """Prompts a user for input. This is a convenience function that can be used to prompt a user for input later. If the user aborts the input by sending a interrupt signal, this function will catch it and raise a :exc:`Abort` exception. - .. versionadded:: 7.0 - Added the show_choices parameter. - - .. versionadded:: 6.0 - Added unicode support for cmd.exe on Windows. - - .. versionadded:: 4.0 - Added the `err` parameter. - :param text: the text to show for the prompt. :param default: the default value to use if no input happens. If this is not given it will prompt until it's aborted. :param hide_input: if this is set to true then the input value will be hidden. - :param confirmation_prompt: asks for confirmation for the value. + :param confirmation_prompt: Prompt a second time to confirm the + value. Can be set to a string instead of ``True`` to customize + the message. :param type: the type to use to check the value against. :param value_proc: if this parameter is provided it's a function that is invoked instead of the type conversion to @@ -88,93 +117,134 @@ def prompt(text, default=None, hide_input=False, confirmation_prompt=False, For example if type is a Choice of either day or week, show_choices is true and text is "Group by" then the prompt will be "Group by (day, week): ". - """ - result = None - def prompt_func(text): - f = hide_input and hidden_prompt_func or visible_prompt_func + .. versionadded:: 8.0 + ``confirmation_prompt`` can be a custom string. + + .. versionadded:: 7.0 + Added the ``show_choices`` parameter. + + .. versionadded:: 6.0 + Added unicode support for cmd.exe on Windows. + + .. versionadded:: 4.0 + Added the `err` parameter. + + """ + + def prompt_func(text: str) -> str: + f = hidden_prompt_func if hide_input else visible_prompt_func try: # Write the prompt separately so that we get nice # coloring through colorama on Windows - echo(text, nl=False, err=err) - return f('') + echo(text.rstrip(" "), nl=False, err=err) + # Echo a space to stdout to work around an issue where + # readline causes backspace to clear the whole line. + return f(" ") except (KeyboardInterrupt, EOFError): # getpass doesn't print a newline if the user aborts input with ^C. # Allegedly this behavior is inherited from getpass(3). # A doc bug has been filed at https://bugs.python.org/issue24711 if hide_input: echo(None, err=err) - raise Abort() + raise Abort() from None if value_proc is None: value_proc = convert_type(type, default) - prompt = _build_prompt(text, prompt_suffix, show_default, default, show_choices, type) + prompt = _build_prompt( + text, prompt_suffix, show_default, default, show_choices, type + ) - while 1: - while 1: + if confirmation_prompt: + if confirmation_prompt is True: + confirmation_prompt = _("Repeat for confirmation") + + confirmation_prompt = t.cast(str, confirmation_prompt) + confirmation_prompt = _build_prompt(confirmation_prompt, prompt_suffix) + + while True: + while True: value = prompt_func(prompt) if value: break elif default is not None: - if isinstance(value_proc, Path): - # validate Path default value(exists, dir_okay etc.) - value = default - break - return default + value = default + break try: result = value_proc(value) except UsageError as e: - echo('Error: %s' % e.message, err=err) + if hide_input: + echo(_("Error: The value you entered was invalid."), err=err) + else: + echo(_("Error: {e.message}").format(e=e), err=err) # noqa: B306 continue if not confirmation_prompt: return result - while 1: - value2 = prompt_func('Repeat for confirmation: ') + while True: + confirmation_prompt = t.cast(str, confirmation_prompt) + value2 = prompt_func(confirmation_prompt) if value2: break if value == value2: return result - echo('Error: the two entered values do not match', err=err) + echo(_("Error: The two entered values do not match."), err=err) -def confirm(text, default=False, abort=False, prompt_suffix=': ', - show_default=True, err=False): +def confirm( + text: str, + default: t.Optional[bool] = False, + abort: bool = False, + prompt_suffix: str = ": ", + show_default: bool = True, + err: bool = False, +) -> bool: """Prompts for confirmation (yes/no question). If the user aborts the input by sending a interrupt signal this function will catch it and raise a :exc:`Abort` exception. - .. versionadded:: 4.0 - Added the `err` parameter. - :param text: the question to ask. - :param default: the default for the prompt. + :param default: The default value to use when no input is given. If + ``None``, repeat until input is given. :param abort: if this is set to `True` a negative answer aborts the exception by raising :exc:`Abort`. :param prompt_suffix: a suffix that should be added to the prompt. :param show_default: shows or hides the default value in the prompt. :param err: if set to true the file defaults to ``stderr`` instead of ``stdout``, the same as with echo. + + .. versionchanged:: 8.0 + Repeat until input is given if ``default`` is ``None``. + + .. versionadded:: 4.0 + Added the ``err`` parameter. """ - prompt = _build_prompt(text, prompt_suffix, show_default, - default and 'Y/n' or 'y/N') - while 1: + prompt = _build_prompt( + text, + prompt_suffix, + show_default, + "y/n" if default is None else ("Y/n" if default else "y/N"), + ) + + while True: try: # Write the prompt separately so that we get nice # coloring through colorama on Windows - echo(prompt, nl=False, err=err) - value = visible_prompt_func('').lower().strip() + echo(prompt.rstrip(" "), nl=False, err=err) + # Echo a space to stdout to work around an issue where + # readline causes backspace to clear the whole line. + value = visible_prompt_func(" ").lower().strip() except (KeyboardInterrupt, EOFError): - raise Abort() - if value in ('y', 'yes'): + raise Abort() from None + if value in ("y", "yes"): rv = True - elif value in ('n', 'no'): + elif value in ("n", "no"): rv = False - elif value == '': + elif default is not None and value == "": rv = default else: - echo('Error: invalid input', err=err) + echo(_("Error: invalid input"), err=err) continue break if abort and not rv: @@ -182,54 +252,30 @@ def confirm(text, default=False, abort=False, prompt_suffix=': ', return rv -def get_terminal_size(): +def get_terminal_size() -> os.terminal_size: """Returns the current size of the terminal as tuple in the form ``(width, height)`` in columns and rows. + + .. deprecated:: 8.0 + Will be removed in Click 8.1. Use + :func:`shutil.get_terminal_size` instead. """ - # If shutil has get_terminal_size() (Python 3.3 and later) use that - if sys.version_info >= (3, 3): - import shutil - shutil_get_terminal_size = getattr(shutil, 'get_terminal_size', None) - if shutil_get_terminal_size: - sz = shutil_get_terminal_size() - return sz.columns, sz.lines + import shutil + import warnings - # We provide a sensible default for get_winterm_size() when being invoked - # inside a subprocess. Without this, it would not provide a useful input. - if get_winterm_size is not None: - size = get_winterm_size() - if size == (0, 0): - return (79, 24) - else: - return size - - def ioctl_gwinsz(fd): - try: - import fcntl - import termios - cr = struct.unpack( - 'hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234')) - except Exception: - return - return cr - - cr = ioctl_gwinsz(0) or ioctl_gwinsz(1) or ioctl_gwinsz(2) - if not cr: - try: - fd = os.open(os.ctermid(), os.O_RDONLY) - try: - cr = ioctl_gwinsz(fd) - finally: - os.close(fd) - except Exception: - pass - if not cr or not cr[0] or not cr[1]: - cr = (os.environ.get('LINES', 25), - os.environ.get('COLUMNS', DEFAULT_COLUMNS)) - return int(cr[1]), int(cr[0]) + warnings.warn( + "'click.get_terminal_size()' is deprecated and will be removed" + " in Click 8.1. Use 'shutil.get_terminal_size()' instead.", + DeprecationWarning, + stacklevel=2, + ) + return shutil.get_terminal_size() -def echo_via_pager(text_or_generator, color=None): +def echo_via_pager( + text_or_generator: t.Union[t.Iterable[str], t.Callable[[], t.Iterable[str]], str], + color: t.Optional[bool] = None, +) -> None: """This function takes a text and shows it via an environment specific pager on stdout. @@ -244,25 +290,37 @@ def echo_via_pager(text_or_generator, color=None): color = resolve_color_default(color) if inspect.isgeneratorfunction(text_or_generator): - i = text_or_generator() - elif isinstance(text_or_generator, string_types): + i = t.cast(t.Callable[[], t.Iterable[str]], text_or_generator)() + elif isinstance(text_or_generator, str): i = [text_or_generator] else: - i = iter(text_or_generator) + i = iter(t.cast(t.Iterable[str], text_or_generator)) # convert every element of i to a text type if necessary - text_generator = (el if isinstance(el, string_types) else text_type(el) - for el in i) + text_generator = (el if isinstance(el, str) else str(el) for el in i) from ._termui_impl import pager + return pager(itertools.chain(text_generator, "\n"), color) -def progressbar(iterable=None, length=None, label=None, show_eta=True, - show_percent=None, show_pos=False, - item_show_func=None, fill_char='#', empty_char='-', - bar_template='%(label)s [%(bar)s] %(info)s', - info_sep=' ', width=36, file=None, color=None): +def progressbar( + iterable: t.Optional[t.Iterable[V]] = None, + length: t.Optional[int] = None, + label: t.Optional[str] = None, + show_eta: bool = True, + show_percent: t.Optional[bool] = None, + show_pos: bool = False, + item_show_func: t.Optional[t.Callable[[t.Optional[V]], t.Optional[str]]] = None, + fill_char: str = "#", + empty_char: str = "-", + bar_template: str = "%(label)s [%(bar)s] %(info)s", + info_sep: str = " ", + width: int = 36, + file: t.Optional[t.TextIO] = None, + color: t.Optional[bool] = None, + update_min_steps: int = 1, +) -> "ProgressBar[V]": """This function creates an iterable context manager that can be used to iterate over something while showing a progress bar. It will either iterate over the `iterable` or `length` items (that are counted @@ -272,11 +330,17 @@ def progressbar(iterable=None, length=None, label=None, show_eta=True, will not be rendered if the file is not a terminal. The context manager creates the progress bar. When the context - manager is entered the progress bar is already displayed. With every + manager is entered the progress bar is already created. With every iteration over the progress bar, the iterable passed to the bar is advanced and the bar is updated. When the context manager exits, a newline is printed and the progress bar is finalized on screen. + Note: The progress bar is currently designed for use cases where the + total progress can be expected to take at least several seconds. + Because of this, the ProgressBar class object won't display + progress that is considered too fast, and progress where the time + between steps is less than a second. + No printing must happen or the progress bar will be unintentionally destroyed. @@ -296,11 +360,19 @@ def progressbar(iterable=None, length=None, label=None, show_eta=True, process_chunk(chunk) bar.update(chunks.bytes) - .. versionadded:: 2.0 + The ``update()`` method also takes an optional value specifying the + ``current_item`` at the new position. This is useful when used + together with ``item_show_func`` to customize the output for each + manual step:: - .. versionadded:: 4.0 - Added the `color` parameter. Added a `update` method to the - progressbar object. + with click.progressbar( + length=total_size, + label='Unzipping archive', + item_show_func=lambda a: a.filename + ) as bar: + for archive in zip_file: + archive.extract() + bar.update(archive.size, archive) :param iterable: an iterable to iterate over. If not provided the length is required. @@ -319,10 +391,10 @@ def progressbar(iterable=None, length=None, label=None, show_eta=True, `False` if not. :param show_pos: enables or disables the absolute position display. The default is `False`. - :param item_show_func: a function called with the current item which - can return a string to show the current item - next to the progress bar. Note that the current - item can be `None`! + :param item_show_func: A function called with the current item which + can return a string to show next to the progress bar. If the + function returns ``None`` nothing is shown. The current item can + be ``None``, such as when entering and exiting the bar. :param fill_char: the character to use to show the filled part of the progress bar. :param empty_char: the character to use to show the non-filled part of @@ -334,24 +406,57 @@ def progressbar(iterable=None, length=None, label=None, show_eta=True, :param info_sep: the separator between multiple info items (eta etc.) :param width: the width of the progress bar in characters, 0 means full terminal width - :param file: the file to write to. If this is not a terminal then - only the label is printed. + :param file: The file to write to. If this is not a terminal then + only the label is printed. :param color: controls if the terminal supports ANSI colors or not. The default is autodetection. This is only needed if ANSI codes are included anywhere in the progress bar output which is not the case by default. + :param update_min_steps: Render only when this many updates have + completed. This allows tuning for very fast iterators. + + .. versionchanged:: 8.0 + Output is shown even if execution time is less than 0.5 seconds. + + .. versionchanged:: 8.0 + ``item_show_func`` shows the current item, not the previous one. + + .. versionchanged:: 8.0 + Labels are echoed if the output is not a TTY. Reverts a change + in 7.0 that removed all output. + + .. versionadded:: 8.0 + Added the ``update_min_steps`` parameter. + + .. versionchanged:: 4.0 + Added the ``color`` parameter. Added the ``update`` method to + the object. + + .. versionadded:: 2.0 """ from ._termui_impl import ProgressBar + color = resolve_color_default(color) - return ProgressBar(iterable=iterable, length=length, show_eta=show_eta, - show_percent=show_percent, show_pos=show_pos, - item_show_func=item_show_func, fill_char=fill_char, - empty_char=empty_char, bar_template=bar_template, - info_sep=info_sep, file=file, label=label, - width=width, color=color) + return ProgressBar( + iterable=iterable, + length=length, + show_eta=show_eta, + show_percent=show_percent, + show_pos=show_pos, + item_show_func=item_show_func, + fill_char=fill_char, + empty_char=empty_char, + bar_template=bar_template, + info_sep=info_sep, + file=file, + label=label, + width=width, + color=color, + update_min_steps=update_min_steps, + ) -def clear(): +def clear() -> None: """Clears the terminal screen. This will have the effect of clearing the whole visible space of the terminal and moving the cursor to the top left. This does not do anything if not connected to a terminal. @@ -360,17 +465,39 @@ def clear(): """ if not isatty(sys.stdout): return - # If we're on Windows and we don't have colorama available, then we - # clear the screen by shelling out. Otherwise we can use an escape - # sequence. if WIN: - os.system('cls') + os.system("cls") else: - sys.stdout.write('\033[2J\033[1;1H') + sys.stdout.write("\033[2J\033[1;1H") -def style(text, fg=None, bg=None, bold=None, dim=None, underline=None, - blink=None, reverse=None, reset=True): +def _interpret_color( + color: t.Union[int, t.Tuple[int, int, int], str], offset: int = 0 +) -> str: + if isinstance(color, int): + return f"{38 + offset};5;{color:d}" + + if isinstance(color, (tuple, list)): + r, g, b = color + return f"{38 + offset};2;{r:d};{g:d};{b:d}" + + return str(_ansi_colors[color] + offset) + + +def style( + text: t.Any, + fg: t.Optional[t.Union[int, t.Tuple[int, int, int], str]] = None, + bg: t.Optional[t.Union[int, t.Tuple[int, int, int], str]] = None, + bold: t.Optional[bool] = None, + dim: t.Optional[bool] = None, + underline: t.Optional[bool] = None, + overline: t.Optional[bool] = None, + italic: t.Optional[bool] = None, + blink: t.Optional[bool] = None, + reverse: t.Optional[bool] = None, + strikethrough: t.Optional[bool] = None, + reset: bool = True, +) -> str: """Styles a text with ANSI styles and returns the new string. By default the styling is self contained which means that at the end of the string a reset code is issued. This can be prevented by @@ -381,6 +508,7 @@ def style(text, fg=None, bg=None, bold=None, dim=None, underline=None, click.echo(click.style('Hello World!', fg='green')) click.echo(click.style('ATTENTION!', blink=True)) click.echo(click.style('Some things', reverse=True, fg='cyan')) + click.echo(click.style('More colors', fg=(255, 12, 128), bg=117)) Supported color names: @@ -402,10 +530,15 @@ def style(text, fg=None, bg=None, bold=None, dim=None, underline=None, * ``bright_white`` * ``reset`` (reset the color code only) - .. versionadded:: 2.0 + If the terminal supports it, color may also be specified as: - .. versionadded:: 7.0 - Added support for bright colors. + - An integer in the interval [0, 255]. The terminal must support + 8-bit/256-color mode. + - An RGB tuple of three integers in [0, 255]. The terminal must + support 24-bit/true-color mode. + + See https://en.wikipedia.org/wiki/ANSI_color and + https://gist.github.com/XVilka/8346728 for more information. :param text: the string to style with ansi codes. :param fg: if provided this will become the foreground color. @@ -414,42 +547,73 @@ def style(text, fg=None, bg=None, bold=None, dim=None, underline=None, :param dim: if provided this will enable or disable dim mode. This is badly supported. :param underline: if provided this will enable or disable underline. + :param overline: if provided this will enable or disable overline. + :param italic: if provided this will enable or disable italic. :param blink: if provided this will enable or disable blinking. :param reverse: if provided this will enable or disable inverse rendering (foreground becomes background and the other way round). + :param strikethrough: if provided this will enable or disable + striking through text. :param reset: by default a reset-all code is added at the end of the string which means that styles do not carry over. This can be disabled to compose styles. + + .. versionchanged:: 8.0 + A non-string ``message`` is converted to a string. + + .. versionchanged:: 8.0 + Added support for 256 and RGB color codes. + + .. versionchanged:: 8.0 + Added the ``strikethrough``, ``italic``, and ``overline`` + parameters. + + .. versionchanged:: 7.0 + Added support for bright colors. + + .. versionadded:: 2.0 """ + if not isinstance(text, str): + text = str(text) + bits = [] + if fg: try: - bits.append('\033[%dm' % (_ansi_colors[fg])) + bits.append(f"\033[{_interpret_color(fg)}m") except KeyError: - raise TypeError('Unknown color %r' % fg) + raise TypeError(f"Unknown color {fg!r}") from None + if bg: try: - bits.append('\033[%dm' % (_ansi_colors[bg] + 10)) + bits.append(f"\033[{_interpret_color(bg, 10)}m") except KeyError: - raise TypeError('Unknown color %r' % bg) + raise TypeError(f"Unknown color {bg!r}") from None + if bold is not None: - bits.append('\033[%dm' % (1 if bold else 22)) + bits.append(f"\033[{1 if bold else 22}m") if dim is not None: - bits.append('\033[%dm' % (2 if dim else 22)) + bits.append(f"\033[{2 if dim else 22}m") if underline is not None: - bits.append('\033[%dm' % (4 if underline else 24)) + bits.append(f"\033[{4 if underline else 24}m") + if overline is not None: + bits.append(f"\033[{53 if overline else 55}m") + if italic is not None: + bits.append(f"\033[{3 if italic else 23}m") if blink is not None: - bits.append('\033[%dm' % (5 if blink else 25)) + bits.append(f"\033[{5 if blink else 25}m") if reverse is not None: - bits.append('\033[%dm' % (7 if reverse else 27)) + bits.append(f"\033[{7 if reverse else 27}m") + if strikethrough is not None: + bits.append(f"\033[{9 if strikethrough else 29}m") bits.append(text) if reset: bits.append(_ansi_reset_all) - return ''.join(bits) + return "".join(bits) -def unstyle(text): +def unstyle(text: str) -> str: """Removes ANSI styling information from a string. Usually it's not necessary to use this function as Click's echo function will automatically remove styling if necessary. @@ -461,7 +625,14 @@ def unstyle(text): return strip_ansi(text) -def secho(message=None, file=None, nl=True, err=False, color=None, **styles): +def secho( + message: t.Optional[t.Any] = None, + file: t.Optional[t.IO] = None, + nl: bool = True, + err: bool = False, + color: t.Optional[bool] = None, + **styles: t.Any, +) -> None: """This function combines :func:`echo` and :func:`style` into one call. As such the following two calls are the same:: @@ -471,15 +642,31 @@ def secho(message=None, file=None, nl=True, err=False, color=None, **styles): All keyword arguments are forwarded to the underlying functions depending on which one they go with. + Non-string types will be converted to :class:`str`. However, + :class:`bytes` are passed directly to :meth:`echo` without applying + style. If you want to style bytes that represent text, call + :meth:`bytes.decode` first. + + .. versionchanged:: 8.0 + A non-string ``message`` is converted to a string. Bytes are + passed through without style applied. + .. versionadded:: 2.0 """ - if message is not None: + if message is not None and not isinstance(message, (bytes, bytearray)): message = style(message, **styles) + return echo(message, file=file, nl=nl, err=err, color=color) -def edit(text=None, editor=None, env=None, require_save=True, - extension='.txt', filename=None): +def edit( + text: t.Optional[t.AnyStr] = None, + editor: t.Optional[str] = None, + env: t.Optional[t.Mapping[str, str]] = None, + require_save: bool = True, + extension: str = ".txt", + filename: t.Optional[str] = None, +) -> t.Optional[t.AnyStr]: r"""Edits the given text in the defined editor. If an editor is given (should be the full path to the executable but the regular operating system search path is used for finding the executable) it overrides @@ -508,14 +695,17 @@ def edit(text=None, editor=None, env=None, require_save=True, file as an indirection in that case. """ from ._termui_impl import Editor - editor = Editor(editor=editor, env=env, require_save=require_save, - extension=extension) + + ed = Editor(editor=editor, env=env, require_save=require_save, extension=extension) + if filename is None: - return editor.edit(text) - editor.edit_file(filename) + return ed.edit(text) + + ed.edit_file(filename) + return None -def launch(url, wait=False, locate=False): +def launch(url: str, wait: bool = False, locate: bool = False) -> int: """This function launches the given URL (or filename) in the default viewer application for this file type. If this is an executable, it might launch the executable in a new session. The return value is @@ -530,7 +720,9 @@ def launch(url, wait=False, locate=False): .. versionadded:: 2.0 :param url: URL or filename of the thing to launch. - :param wait: waits for the program to stop. + :param wait: Wait for the program to exit before returning. This + only works if the launched program blocks. In particular, + ``xdg-open`` on Linux does not block. :param locate: if this is set to `True` then instead of launching the application associated with the URL it will attempt to launch a file manager with the file located. This @@ -538,15 +730,16 @@ def launch(url, wait=False, locate=False): the filesystem. """ from ._termui_impl import open_url + return open_url(url, wait=wait, locate=locate) # If this is provided, getchar() calls into this instead. This is used # for unittesting purposes. -_getchar = None +_getchar: t.Optional[t.Callable[[bool], str]] = None -def getchar(echo=False): +def getchar(echo: bool = False) -> str: """Fetches a single character from the terminal and returns it. This will always return a unicode character and under certain rare circumstances this might return more than one character. The @@ -566,18 +759,23 @@ def getchar(echo=False): :param echo: if set to `True`, the character read will also show up on the terminal. The default is to not show it. """ - f = _getchar - if f is None: + global _getchar + + if _getchar is None: from ._termui_impl import getchar as f - return f(echo) + + _getchar = f + + return _getchar(echo) -def raw_terminal(): +def raw_terminal() -> t.ContextManager[int]: from ._termui_impl import raw_terminal as f + return f() -def pause(info='Press any key to continue ...', err=False): +def pause(info: t.Optional[str] = None, err: bool = False) -> None: """This command stops execution and waits for the user to press any key to continue. This is similar to the Windows batch "pause" command. If the program is not run through a terminal, this command @@ -588,12 +786,17 @@ def pause(info='Press any key to continue ...', err=False): .. versionadded:: 4.0 Added the `err` parameter. - :param info: the info string to print before pausing. + :param info: The message to print before pausing. Defaults to + ``"Press any key to continue..."``. :param err: if set to message goes to ``stderr`` instead of ``stdout``, the same as with echo. """ if not isatty(sys.stdin) or not isatty(sys.stdout): return + + if info is None: + info = _("Press any key to continue...") + try: if info: echo(info, nl=False, err=err) diff --git a/pipenv/vendor/click/testing.py b/pipenv/vendor/click/testing.py index 1b2924e0..d19b850f 100644 --- a/pipenv/vendor/click/testing.py +++ b/pipenv/vendor/click/testing.py @@ -1,86 +1,128 @@ -import os -import sys -import shutil -import tempfile import contextlib +import io +import os import shlex +import shutil +import sys +import tempfile +import typing as t +from types import TracebackType -from ._compat import iteritems, PY2, string_types +from . import formatting +from . import termui +from . import utils +from ._compat import _find_binary_reader + +if t.TYPE_CHECKING: + from .core import BaseCommand -# If someone wants to vendor click, we want to ensure the -# correct package is discovered. Ideally we could use a -# relative import here but unfortunately Python does not -# support that. -clickpkg = sys.modules[__name__.rsplit('.', 1)[0]] - - -if PY2: - from cStringIO import StringIO -else: - import io - from ._compat import _find_binary_reader - - -class EchoingStdin(object): - - def __init__(self, input, output): +class EchoingStdin: + def __init__(self, input: t.BinaryIO, output: t.BinaryIO) -> None: self._input = input self._output = output + self._paused = False - def __getattr__(self, x): + def __getattr__(self, x: str) -> t.Any: return getattr(self._input, x) - def _echo(self, rv): - self._output.write(rv) + def _echo(self, rv: bytes) -> bytes: + if not self._paused: + self._output.write(rv) + return rv - def read(self, n=-1): + def read(self, n: int = -1) -> bytes: return self._echo(self._input.read(n)) - def readline(self, n=-1): + def read1(self, n: int = -1) -> bytes: + return self._echo(self._input.read1(n)) # type: ignore + + def readline(self, n: int = -1) -> bytes: return self._echo(self._input.readline(n)) - def readlines(self): + def readlines(self) -> t.List[bytes]: return [self._echo(x) for x in self._input.readlines()] - def __iter__(self): + def __iter__(self) -> t.Iterator[bytes]: return iter(self._echo(x) for x in self._input) - def __repr__(self): + def __repr__(self) -> str: return repr(self._input) -def make_input_stream(input, charset): +@contextlib.contextmanager +def _pause_echo(stream: t.Optional[EchoingStdin]) -> t.Iterator[None]: + if stream is None: + yield + else: + stream._paused = True + yield + stream._paused = False + + +class _NamedTextIOWrapper(io.TextIOWrapper): + def __init__( + self, buffer: t.BinaryIO, name: str, mode: str, **kwargs: t.Any + ) -> None: + super().__init__(buffer, **kwargs) + self._name = name + self._mode = mode + + @property + def name(self) -> str: + return self._name + + @property + def mode(self) -> str: + return self._mode + + +def make_input_stream( + input: t.Optional[t.Union[str, bytes, t.IO]], charset: str +) -> t.BinaryIO: # Is already an input stream. - if hasattr(input, 'read'): - if PY2: - return input - rv = _find_binary_reader(input) + if hasattr(input, "read"): + rv = _find_binary_reader(t.cast(t.IO, input)) + if rv is not None: return rv - raise TypeError('Could not find binary reader for input stream.') + + raise TypeError("Could not find binary reader for input stream.") if input is None: - input = b'' - elif not isinstance(input, bytes): + input = b"" + elif isinstance(input, str): input = input.encode(charset) - if PY2: - return StringIO(input) - return io.BytesIO(input) + + return io.BytesIO(t.cast(bytes, input)) -class Result(object): +class Result: """Holds the captured result of an invoked CLI script.""" - def __init__(self, runner, stdout_bytes, stderr_bytes, exit_code, - exception, exc_info=None): + def __init__( + self, + runner: "CliRunner", + stdout_bytes: bytes, + stderr_bytes: t.Optional[bytes], + return_value: t.Any, + exit_code: int, + exception: t.Optional[BaseException], + exc_info: t.Optional[ + t.Tuple[t.Type[BaseException], BaseException, TracebackType] + ] = None, + ): #: The runner that created the result self.runner = runner #: The standard output as bytes. self.stdout_bytes = stdout_bytes - #: The standard error as bytes, or False(y) if not available + #: The standard error as bytes, or None if not available self.stderr_bytes = stderr_bytes + #: The value returned from the invoked command. + #: + #: .. versionadded:: 8.0 + self.return_value = return_value #: The exit code as integer. self.exit_code = exit_code #: The exception that happened if one did. @@ -89,41 +131,38 @@ class Result(object): self.exc_info = exc_info @property - def output(self): + def output(self) -> str: """The (standard) output as unicode string.""" return self.stdout @property - def stdout(self): + def stdout(self) -> str: """The standard output as unicode string.""" - return self.stdout_bytes.decode(self.runner.charset, 'replace') \ - .replace('\r\n', '\n') - - @property - def stderr(self): - """The standard error as unicode string.""" - if not self.stderr_bytes: - raise ValueError("stderr not separately captured") - return self.stderr_bytes.decode(self.runner.charset, 'replace') \ - .replace('\r\n', '\n') - - - def __repr__(self): - return '<%s %s>' % ( - type(self).__name__, - self.exception and repr(self.exception) or 'okay', + return self.stdout_bytes.decode(self.runner.charset, "replace").replace( + "\r\n", "\n" ) + @property + def stderr(self) -> str: + """The standard error as unicode string.""" + if self.stderr_bytes is None: + raise ValueError("stderr not separately captured") + return self.stderr_bytes.decode(self.runner.charset, "replace").replace( + "\r\n", "\n" + ) -class CliRunner(object): + def __repr__(self) -> str: + exc_str = repr(self.exception) if self.exception else "okay" + return f"<{type(self).__name__} {exc_str}>" + + +class CliRunner: """The CLI runner provides functionality to invoke a Click command line script for unittesting purposes in a isolated environment. This only works in single-threaded systems without any concurrency as it changes the global interpreter state. - :param charset: the character set for the input and output data. This is - UTF-8 by default and should not be changed currently as - the reporting to Click only works in Python 2 properly. + :param charset: the character set for the input and output data. :param env: a dictionary with environment variables for overriding. :param echo_stdin: if this is set to `True`, then reading from stdin writes to stdout. This is useful for showing examples in @@ -136,23 +175,28 @@ class CliRunner(object): independently """ - def __init__(self, charset=None, env=None, echo_stdin=False, - mix_stderr=True): - if charset is None: - charset = 'utf-8' + def __init__( + self, + charset: str = "utf-8", + env: t.Optional[t.Mapping[str, t.Optional[str]]] = None, + echo_stdin: bool = False, + mix_stderr: bool = True, + ) -> None: self.charset = charset self.env = env or {} self.echo_stdin = echo_stdin self.mix_stderr = mix_stderr - def get_default_prog_name(self, cli): + def get_default_prog_name(self, cli: "BaseCommand") -> str: """Given a command object it will return the default program name for it. The default is the `name` attribute or ``"root"`` if not set. """ - return cli.name or 'root' + return cli.name or "root" - def make_env(self, overrides=None): + def make_env( + self, overrides: t.Optional[t.Mapping[str, t.Optional[str]]] = None + ) -> t.Mapping[str, t.Optional[str]]: """Returns the environment overrides for invoking a script.""" rv = dict(self.env) if overrides: @@ -160,7 +204,12 @@ class CliRunner(object): return rv @contextlib.contextmanager - def isolation(self, input=None, env=None, color=False): + def isolation( + self, + input: t.Optional[t.Union[str, bytes, t.IO]] = None, + env: t.Optional[t.Mapping[str, t.Optional[str]]] = None, + color: bool = False, + ) -> t.Iterator[t.Tuple[io.BytesIO, t.Optional[io.BytesIO]]]: """A context manager that sets up the isolation for invoking of a command line tool. This sets up stdin with the given input data and `os.environ` with the overrides from the given dictionary. @@ -169,87 +218,107 @@ class CliRunner(object): This is automatically done in the :meth:`invoke` method. - .. versionadded:: 4.0 - The ``color`` parameter was added. - :param input: the input stream to put into sys.stdin. :param env: the environment overrides as dictionary. :param color: whether the output should contain color codes. The application can still override this explicitly. + + .. versionchanged:: 8.0 + ``stderr`` is opened with ``errors="backslashreplace"`` + instead of the default ``"strict"``. + + .. versionchanged:: 4.0 + Added the ``color`` parameter. """ - input = make_input_stream(input, self.charset) + bytes_input = make_input_stream(input, self.charset) + echo_input = None old_stdin = sys.stdin old_stdout = sys.stdout old_stderr = sys.stderr - old_forced_width = clickpkg.formatting.FORCED_WIDTH - clickpkg.formatting.FORCED_WIDTH = 80 + old_forced_width = formatting.FORCED_WIDTH + formatting.FORCED_WIDTH = 80 env = self.make_env(env) - if PY2: - bytes_output = StringIO() - if self.echo_stdin: - input = EchoingStdin(input, bytes_output) - sys.stdout = bytes_output - if not self.mix_stderr: - bytes_error = StringIO() - sys.stderr = bytes_error - else: - bytes_output = io.BytesIO() - if self.echo_stdin: - input = EchoingStdin(input, bytes_output) - input = io.TextIOWrapper(input, encoding=self.charset) - sys.stdout = io.TextIOWrapper( - bytes_output, encoding=self.charset) - if not self.mix_stderr: - bytes_error = io.BytesIO() - sys.stderr = io.TextIOWrapper( - bytes_error, encoding=self.charset) + bytes_output = io.BytesIO() + if self.echo_stdin: + bytes_input = echo_input = t.cast( + t.BinaryIO, EchoingStdin(bytes_input, bytes_output) + ) + + sys.stdin = text_input = _NamedTextIOWrapper( + bytes_input, encoding=self.charset, name="<stdin>", mode="r" + ) + + if self.echo_stdin: + # Force unbuffered reads, otherwise TextIOWrapper reads a + # large chunk which is echoed early. + text_input._CHUNK_SIZE = 1 # type: ignore + + sys.stdout = _NamedTextIOWrapper( + bytes_output, encoding=self.charset, name="<stdout>", mode="w" + ) + + bytes_error = None if self.mix_stderr: sys.stderr = sys.stdout + else: + bytes_error = io.BytesIO() + sys.stderr = _NamedTextIOWrapper( + bytes_error, + encoding=self.charset, + name="<stderr>", + mode="w", + errors="backslashreplace", + ) - sys.stdin = input - - def visible_input(prompt=None): - sys.stdout.write(prompt or '') - val = input.readline().rstrip('\r\n') - sys.stdout.write(val + '\n') + @_pause_echo(echo_input) # type: ignore + def visible_input(prompt: t.Optional[str] = None) -> str: + sys.stdout.write(prompt or "") + val = text_input.readline().rstrip("\r\n") + sys.stdout.write(f"{val}\n") sys.stdout.flush() return val - def hidden_input(prompt=None): - sys.stdout.write((prompt or '') + '\n') + @_pause_echo(echo_input) # type: ignore + def hidden_input(prompt: t.Optional[str] = None) -> str: + sys.stdout.write(f"{prompt or ''}\n") sys.stdout.flush() - return input.readline().rstrip('\r\n') + return text_input.readline().rstrip("\r\n") - def _getchar(echo): + @_pause_echo(echo_input) # type: ignore + def _getchar(echo: bool) -> str: char = sys.stdin.read(1) + if echo: sys.stdout.write(char) - sys.stdout.flush() + + sys.stdout.flush() return char default_color = color - def should_strip_ansi(stream=None, color=None): + def should_strip_ansi( + stream: t.Optional[t.IO] = None, color: t.Optional[bool] = None + ) -> bool: if color is None: return not default_color return not color - old_visible_prompt_func = clickpkg.termui.visible_prompt_func - old_hidden_prompt_func = clickpkg.termui.hidden_prompt_func - old__getchar_func = clickpkg.termui._getchar - old_should_strip_ansi = clickpkg.utils.should_strip_ansi - clickpkg.termui.visible_prompt_func = visible_input - clickpkg.termui.hidden_prompt_func = hidden_input - clickpkg.termui._getchar = _getchar - clickpkg.utils.should_strip_ansi = should_strip_ansi + old_visible_prompt_func = termui.visible_prompt_func + old_hidden_prompt_func = termui.hidden_prompt_func + old__getchar_func = termui._getchar + old_should_strip_ansi = utils.should_strip_ansi # type: ignore + termui.visible_prompt_func = visible_input + termui.hidden_prompt_func = hidden_input + termui._getchar = _getchar + utils.should_strip_ansi = should_strip_ansi # type: ignore old_env = {} try: - for key, value in iteritems(env): + for key, value in env.items(): old_env[key] = os.environ.get(key) if value is None: try: @@ -258,9 +327,9 @@ class CliRunner(object): pass else: os.environ[key] = value - yield (bytes_output, not self.mix_stderr and bytes_error) + yield (bytes_output, bytes_error) finally: - for key, value in iteritems(old_env): + for key, value in old_env.items(): if value is None: try: del os.environ[key] @@ -271,14 +340,22 @@ class CliRunner(object): sys.stdout = old_stdout sys.stderr = old_stderr sys.stdin = old_stdin - clickpkg.termui.visible_prompt_func = old_visible_prompt_func - clickpkg.termui.hidden_prompt_func = old_hidden_prompt_func - clickpkg.termui._getchar = old__getchar_func - clickpkg.utils.should_strip_ansi = old_should_strip_ansi - clickpkg.formatting.FORCED_WIDTH = old_forced_width + termui.visible_prompt_func = old_visible_prompt_func + termui.hidden_prompt_func = old_hidden_prompt_func + termui._getchar = old__getchar_func + utils.should_strip_ansi = old_should_strip_ansi # type: ignore + formatting.FORCED_WIDTH = old_forced_width - def invoke(self, cli, args=None, input=None, env=None, - catch_exceptions=True, color=False, mix_stderr=False, **extra): + def invoke( + self, + cli: "BaseCommand", + args: t.Optional[t.Union[str, t.Sequence[str]]] = None, + input: t.Optional[t.Union[str, bytes, t.IO]] = None, + env: t.Optional[t.Mapping[str, t.Optional[str]]] = None, + catch_exceptions: bool = True, + color: bool = False, + **extra: t.Any, + ) -> Result: """Invokes a command in an isolated environment. The arguments are forwarded directly to the command line script, the `extra` keyword arguments are passed to the :meth:`~clickpkg.Command.main` function of @@ -286,16 +363,6 @@ class CliRunner(object): This returns a :class:`Result` object. - .. versionadded:: 3.0 - The ``catch_exceptions`` parameter was added. - - .. versionchanged:: 3.0 - The result object now has an `exc_info` attribute with the - traceback if available. - - .. versionadded:: 4.0 - The ``color`` parameter was added. - :param cli: the command to invoke :param args: the arguments to invoke. It may be given as an iterable or a string. When given as string it will be interpreted @@ -308,13 +375,28 @@ class CliRunner(object): :param extra: the keyword arguments to pass to :meth:`main`. :param color: whether the output should contain color codes. The application can still override this explicitly. + + .. versionchanged:: 8.0 + The result object has the ``return_value`` attribute with + the value returned from the invoked command. + + .. versionchanged:: 4.0 + Added the ``color`` parameter. + + .. versionchanged:: 3.0 + Added the ``catch_exceptions`` parameter. + + .. versionchanged:: 3.0 + The result object has the ``exc_info`` attribute with the + traceback if available. """ exc_info = None with self.isolation(input=input, env=env, color=color) as outstreams: - exception = None + return_value = None + exception: t.Optional[BaseException] = None exit_code = 0 - if isinstance(args, string_types): + if isinstance(args, str): args = shlex.split(args) try: @@ -323,20 +405,23 @@ class CliRunner(object): prog_name = self.get_default_prog_name(cli) try: - cli.main(args=args or (), prog_name=prog_name, **extra) + return_value = cli.main(args=args or (), prog_name=prog_name, **extra) except SystemExit as e: exc_info = sys.exc_info() - exit_code = e.code - if exit_code is None: - exit_code = 0 + e_code = t.cast(t.Optional[t.Union[int, t.Any]], e.code) - if exit_code != 0: + if e_code is None: + e_code = 0 + + if e_code != 0: exception = e - if not isinstance(exit_code, int): - sys.stdout.write(str(exit_code)) - sys.stdout.write('\n') - exit_code = 1 + if not isinstance(e_code, int): + sys.stdout.write(str(e_code)) + sys.stdout.write("\n") + e_code = 1 + + exit_code = e_code except Exception as e: if not catch_exceptions: @@ -347,28 +432,48 @@ class CliRunner(object): finally: sys.stdout.flush() stdout = outstreams[0].getvalue() - stderr = outstreams[1] and outstreams[1].getvalue() + if self.mix_stderr: + stderr = None + else: + stderr = outstreams[1].getvalue() # type: ignore - return Result(runner=self, - stdout_bytes=stdout, - stderr_bytes=stderr, - exit_code=exit_code, - exception=exception, - exc_info=exc_info) + return Result( + runner=self, + stdout_bytes=stdout, + stderr_bytes=stderr, + return_value=return_value, + exit_code=exit_code, + exception=exception, + exc_info=exc_info, # type: ignore + ) @contextlib.contextmanager - def isolated_filesystem(self): - """A context manager that creates a temporary folder and changes - the current working directory to it for isolated filesystem tests. + def isolated_filesystem( + self, temp_dir: t.Optional[t.Union[str, os.PathLike]] = None + ) -> t.Iterator[str]: + """A context manager that creates a temporary directory and + changes the current working directory to it. This isolates tests + that affect the contents of the CWD to prevent them from + interfering with each other. + + :param temp_dir: Create the temporary directory under this + directory. If given, the created directory is not removed + when exiting. + + .. versionchanged:: 8.0 + Added the ``temp_dir`` parameter. """ cwd = os.getcwd() - t = tempfile.mkdtemp() + t = tempfile.mkdtemp(dir=temp_dir) os.chdir(t) + try: yield t finally: os.chdir(cwd) - try: - shutil.rmtree(t) - except (OSError, IOError): - pass + + if temp_dir is None: + try: + shutil.rmtree(t) + except OSError: # noqa: B014 + pass diff --git a/pipenv/vendor/click/types.py b/pipenv/vendor/click/types.py index 1f88032f..5ad3d27b 100644 --- a/pipenv/vendor/click/types.py +++ b/pipenv/vendor/click/types.py @@ -1,30 +1,47 @@ import os import stat +import typing as t from datetime import datetime +from gettext import gettext as _ +from gettext import ngettext -from ._compat import open_stream, text_type, filename_to_ui, \ - get_filesystem_encoding, get_streerror, _get_argv_encoding, PY2 +from ._compat import _get_argv_encoding +from ._compat import get_filesystem_encoding +from ._compat import open_stream from .exceptions import BadParameter -from .utils import safecall, LazyFile +from .utils import LazyFile +from .utils import safecall + +if t.TYPE_CHECKING: + import typing_extensions as te + from .core import Context + from .core import Parameter + from .shell_completion import CompletionItem -class ParamType(object): - """Helper for converting values through types. The following is - necessary for a valid type: +class ParamType: + """Represents the type of a parameter. Validates and converts values + from the command line or Python into the correct type. - * it needs a name - * it needs to pass through None unchanged - * it needs to convert from a string - * it needs to convert its result type through unchanged - (eg: needs to be idempotent) - * it needs to be able to deal with param and context being `None`. - This can be the case when the object is used with prompt - inputs. + To implement a custom type, subclass and implement at least the + following: + + - The :attr:`name` class attribute must be set. + - Calling an instance of the type with ``None`` must return + ``None``. This is already implemented by default. + - :meth:`convert` must convert string values to the correct type. + - :meth:`convert` must accept values that are already the correct + type. + - It must be able to convert a value if the ``ctx`` and ``param`` + arguments are ``None``. This can occur when converting prompt + input. """ - is_composite = False + + is_composite: t.ClassVar[bool] = False + arity: t.ClassVar[int] = 1 #: the descriptive name of this type - name = None + name: str #: if a list of this type is expected and the value is pulled from a #: string environment variable, this is what splits it up. `None` @@ -32,29 +49,66 @@ class ParamType(object): #: whitespace splits them up. The exception are paths and files which #: are split by ``os.path.pathsep`` by default (":" on Unix and ";" on #: Windows). - envvar_list_splitter = None + envvar_list_splitter: t.ClassVar[t.Optional[str]] = None - def __call__(self, value, param=None, ctx=None): + def to_info_dict(self) -> t.Dict[str, t.Any]: + """Gather information that could be useful for a tool generating + user-facing documentation. + + Use :meth:`click.Context.to_info_dict` to traverse the entire + CLI structure. + + .. versionadded:: 8.0 + """ + # The class name without the "ParamType" suffix. + param_type = type(self).__name__.partition("ParamType")[0] + param_type = param_type.partition("ParameterType")[0] + return {"param_type": param_type, "name": self.name} + + def __call__( + self, + value: t.Any, + param: t.Optional["Parameter"] = None, + ctx: t.Optional["Context"] = None, + ) -> t.Any: if value is not None: return self.convert(value, param, ctx) - def get_metavar(self, param): + def get_metavar(self, param: "Parameter") -> t.Optional[str]: """Returns the metavar default for this param if it provides one.""" - def get_missing_message(self, param): + def get_missing_message(self, param: "Parameter") -> t.Optional[str]: """Optionally might return extra information about a missing parameter. .. versionadded:: 2.0 """ - def convert(self, value, param, ctx): - """Converts the value. This is not invoked for values that are - `None` (the missing value). + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + """Convert the value to the correct type. This is not called if + the value is ``None`` (the missing value). + + This must accept string values from the command line, as well as + values that are already the correct type. It may also convert + other compatible types. + + The ``param`` and ``ctx`` arguments may be ``None`` in certain + situations, such as when converting prompt input. + + If the value cannot be converted, call :meth:`fail` with a + descriptive message. + + :param value: The value to convert. + :param param: The parameter that is using this type to convert + its value. May be ``None``. + :param ctx: The current context that arrived at this value. May + be ``None``. """ return value - def split_envvar_value(self, rv): + def split_envvar_value(self, rv: str) -> t.Sequence[str]: """Given a value from an environment variable this splits it up into small chunks depending on the defined envvar list splitter. @@ -62,52 +116,85 @@ class ParamType(object): then leading and trailing whitespace is ignored. Otherwise, leading and trailing splitters usually lead to empty items being included. """ - return (rv or '').split(self.envvar_list_splitter) + return (rv or "").split(self.envvar_list_splitter) - def fail(self, message, param=None, ctx=None): + def fail( + self, + message: str, + param: t.Optional["Parameter"] = None, + ctx: t.Optional["Context"] = None, + ) -> "t.NoReturn": """Helper method to fail with an invalid value message.""" raise BadParameter(message, ctx=ctx, param=param) + def shell_complete( + self, ctx: "Context", param: "Parameter", incomplete: str + ) -> t.List["CompletionItem"]: + """Return a list of + :class:`~click.shell_completion.CompletionItem` objects for the + incomplete value. Most types do not provide completions, but + some do, and this allows custom types to provide custom + completions as well. + + :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + return [] + class CompositeParamType(ParamType): is_composite = True @property - def arity(self): + def arity(self) -> int: # type: ignore raise NotImplementedError() class FuncParamType(ParamType): - - def __init__(self, func): + def __init__(self, func: t.Callable[[t.Any], t.Any]) -> None: self.name = func.__name__ self.func = func - def convert(self, value, param, ctx): + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict["func"] = self.func + return info_dict + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: try: return self.func(value) except ValueError: try: - value = text_type(value) + value = str(value) except UnicodeError: - value = str(value).decode('utf-8', 'replace') + value = value.decode("utf-8", "replace") + self.fail(value, param, ctx) class UnprocessedParamType(ParamType): - name = 'text' + name = "text" - def convert(self, value, param, ctx): + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: return value - def __repr__(self): - return 'UNPROCESSED' + def __repr__(self) -> str: + return "UNPROCESSED" class StringParamType(ParamType): - name = 'text' + name = "text" - def convert(self, value, param, ctx): + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: if isinstance(value, bytes): enc = _get_argv_encoding() try: @@ -118,12 +205,14 @@ class StringParamType(ParamType): try: value = value.decode(fs_enc) except UnicodeError: - value = value.decode('utf-8', 'replace') + value = value.decode("utf-8", "replace") + else: + value = value.decode("utf-8", "replace") return value - return value + return str(value) - def __repr__(self): - return 'STRING' + def __repr__(self) -> str: + return "STRING" class Choice(ParamType): @@ -133,54 +222,104 @@ class Choice(ParamType): You should only pass a list or tuple of choices. Other iterables (like generators) may lead to surprising results. + The resulting value will always be one of the originally passed choices + regardless of ``case_sensitive`` or any ``ctx.token_normalize_func`` + being specified. + See :ref:`choice-opts` for an example. :param case_sensitive: Set to false to make choices case insensitive. Defaults to true. """ - name = 'choice' + name = "choice" - def __init__(self, choices, case_sensitive=True): + def __init__(self, choices: t.Sequence[str], case_sensitive: bool = True) -> None: self.choices = choices self.case_sensitive = case_sensitive - def get_metavar(self, param): - return '[%s]' % '|'.join(self.choices) + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict["choices"] = self.choices + info_dict["case_sensitive"] = self.case_sensitive + return info_dict - def get_missing_message(self, param): - return 'Choose from:\n\t%s.' % ',\n\t'.join(self.choices) + def get_metavar(self, param: "Parameter") -> str: + choices_str = "|".join(self.choices) - def convert(self, value, param, ctx): - # Exact match - if value in self.choices: - return value + # Use curly braces to indicate a required argument. + if param.required and param.param_type_name == "argument": + return f"{{{choices_str}}}" + # Use square braces to indicate an option or optional argument. + return f"[{choices_str}]" + + def get_missing_message(self, param: "Parameter") -> str: + return _("Choose from:\n\t{choices}").format(choices=",\n\t".join(self.choices)) + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: # Match through normalization and case sensitivity # first do token_normalize_func, then lowercase # preserve original `value` to produce an accurate message in # `self.fail` normed_value = value - normed_choices = self.choices + normed_choices = {choice: choice for choice in self.choices} - if ctx is not None and \ - ctx.token_normalize_func is not None: + if ctx is not None and ctx.token_normalize_func is not None: normed_value = ctx.token_normalize_func(value) - normed_choices = [ctx.token_normalize_func(choice) for choice in - self.choices] + normed_choices = { + ctx.token_normalize_func(normed_choice): original + for normed_choice, original in normed_choices.items() + } if not self.case_sensitive: - normed_value = normed_value.lower() - normed_choices = [choice.lower() for choice in normed_choices] + normed_value = normed_value.casefold() + normed_choices = { + normed_choice.casefold(): original + for normed_choice, original in normed_choices.items() + } if normed_value in normed_choices: - return normed_value + return normed_choices[normed_value] - self.fail('invalid choice: %s. (choose from %s)' % - (value, ', '.join(self.choices)), param, ctx) + choices_str = ", ".join(map(repr, self.choices)) + self.fail( + ngettext( + "{value!r} is not {choice}.", + "{value!r} is not one of {choices}.", + len(self.choices), + ).format(value=value, choice=choices_str, choices=choices_str), + param, + ctx, + ) - def __repr__(self): - return 'Choice(%r)' % list(self.choices) + def __repr__(self) -> str: + return f"Choice({list(self.choices)})" + + def shell_complete( + self, ctx: "Context", param: "Parameter", incomplete: str + ) -> t.List["CompletionItem"]: + """Complete choices that start with the incomplete value. + + :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from pipenv.vendor.click.shell_completion import CompletionItem + + str_choices = map(str, self.choices) + + if self.case_sensitive: + matched = (c for c in str_choices if c.startswith(incomplete)) + else: + incomplete = incomplete.lower() + matched = (c for c in str_choices if c.lower().startswith(incomplete)) + + return [CompletionItem(c) for c in matched] class DateTime(ParamType): @@ -203,175 +342,289 @@ class DateTime(ParamType): ``'%Y-%m-%d'``, ``'%Y-%m-%dT%H:%M:%S'``, ``'%Y-%m-%d %H:%M:%S'``. """ - name = 'datetime' - def __init__(self, formats=None): - self.formats = formats or [ - '%Y-%m-%d', - '%Y-%m-%dT%H:%M:%S', - '%Y-%m-%d %H:%M:%S' - ] + name = "datetime" - def get_metavar(self, param): - return '[{}]'.format('|'.join(self.formats)) + def __init__(self, formats: t.Optional[t.Sequence[str]] = None): + self.formats = formats or ["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%d %H:%M:%S"] - def _try_to_convert_date(self, value, format): + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict["formats"] = self.formats + return info_dict + + def get_metavar(self, param: "Parameter") -> str: + return f"[{'|'.join(self.formats)}]" + + def _try_to_convert_date(self, value: t.Any, format: str) -> t.Optional[datetime]: try: return datetime.strptime(value, format) except ValueError: return None - def convert(self, value, param, ctx): - # Exact match + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + if isinstance(value, datetime): + return value + for format in self.formats: - dtime = self._try_to_convert_date(value, format) - if dtime: - return dtime + converted = self._try_to_convert_date(value, format) + if converted is not None: + return converted + + formats_str = ", ".join(map(repr, self.formats)) self.fail( - 'invalid datetime format: {}. (choose from {})'.format( - value, ', '.join(self.formats))) + ngettext( + "{value!r} does not match the format {format}.", + "{value!r} does not match the formats {formats}.", + len(self.formats), + ).format(value=value, format=formats_str, formats=formats_str), + param, + ctx, + ) - def __repr__(self): - return 'DateTime' + def __repr__(self) -> str: + return "DateTime" -class IntParamType(ParamType): - name = 'integer' +class _NumberParamTypeBase(ParamType): + _number_class: t.ClassVar[t.Type] - def convert(self, value, param, ctx): + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: try: - return int(value) - except (ValueError, UnicodeError): - self.fail('%s is not a valid integer' % value, param, ctx) - - def __repr__(self): - return 'INT' + return self._number_class(value) + except ValueError: + self.fail( + _("{value!r} is not a valid {number_type}.").format( + value=value, number_type=self.name + ), + param, + ctx, + ) -class IntRange(IntParamType): - """A parameter that works similar to :data:`click.INT` but restricts - the value to fit into a range. The default behavior is to fail if the - value falls outside the range, but it can also be silently clamped - between the two edges. - - See :ref:`ranges` for an example. - """ - name = 'integer range' - - def __init__(self, min=None, max=None, clamp=False): +class _NumberRangeBase(_NumberParamTypeBase): + def __init__( + self, + min: t.Optional[float] = None, + max: t.Optional[float] = None, + min_open: bool = False, + max_open: bool = False, + clamp: bool = False, + ) -> None: self.min = min self.max = max + self.min_open = min_open + self.max_open = max_open self.clamp = clamp - def convert(self, value, param, ctx): - rv = IntParamType.convert(self, value, param, ctx) + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict.update( + min=self.min, + max=self.max, + min_open=self.min_open, + max_open=self.max_open, + clamp=self.clamp, + ) + return info_dict + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + import operator + + rv = super().convert(value, param, ctx) + lt_min: bool = self.min is not None and ( + operator.le if self.min_open else operator.lt + )(rv, self.min) + gt_max: bool = self.max is not None and ( + operator.ge if self.max_open else operator.gt + )(rv, self.max) + if self.clamp: - if self.min is not None and rv < self.min: - return self.min - if self.max is not None and rv > self.max: - return self.max - if self.min is not None and rv < self.min or \ - self.max is not None and rv > self.max: - if self.min is None: - self.fail('%s is bigger than the maximum valid value ' - '%s.' % (rv, self.max), param, ctx) - elif self.max is None: - self.fail('%s is smaller than the minimum valid value ' - '%s.' % (rv, self.min), param, ctx) - else: - self.fail('%s is not in the valid range of %s to %s.' - % (rv, self.min, self.max), param, ctx) + if lt_min: + return self._clamp(self.min, 1, self.min_open) # type: ignore + + if gt_max: + return self._clamp(self.max, -1, self.max_open) # type: ignore + + if lt_min or gt_max: + self.fail( + _("{value} is not in the range {range}.").format( + value=rv, range=self._describe_range() + ), + param, + ctx, + ) + return rv - def __repr__(self): - return 'IntRange(%r, %r)' % (self.min, self.max) + def _clamp(self, bound: float, dir: "te.Literal[1, -1]", open: bool) -> float: + """Find the valid value to clamp to bound in the given + direction. + + :param bound: The boundary value. + :param dir: 1 or -1 indicating the direction to move. + :param open: If true, the range does not include the bound. + """ + raise NotImplementedError + + def _describe_range(self) -> str: + """Describe the range for use in help text.""" + if self.min is None: + op = "<" if self.max_open else "<=" + return f"x{op}{self.max}" + + if self.max is None: + op = ">" if self.min_open else ">=" + return f"x{op}{self.min}" + + lop = "<" if self.min_open else "<=" + rop = "<" if self.max_open else "<=" + return f"{self.min}{lop}x{rop}{self.max}" + + def __repr__(self) -> str: + clamp = " clamped" if self.clamp else "" + return f"<{type(self).__name__} {self._describe_range()}{clamp}>" -class FloatParamType(ParamType): - name = 'float' +class IntParamType(_NumberParamTypeBase): + name = "integer" + _number_class = int - def convert(self, value, param, ctx): - try: - return float(value) - except (UnicodeError, ValueError): - self.fail('%s is not a valid floating point value' % - value, param, ctx) - - def __repr__(self): - return 'FLOAT' + def __repr__(self) -> str: + return "INT" -class FloatRange(FloatParamType): - """A parameter that works similar to :data:`click.FLOAT` but restricts - the value to fit into a range. The default behavior is to fail if the - value falls outside the range, but it can also be silently clamped - between the two edges. +class IntRange(_NumberRangeBase, IntParamType): + """Restrict an :data:`click.INT` value to a range of accepted + values. See :ref:`ranges`. - See :ref:`ranges` for an example. + If ``min`` or ``max`` are not passed, any value is accepted in that + direction. If ``min_open`` or ``max_open`` are enabled, the + corresponding boundary is not included in the range. + + If ``clamp`` is enabled, a value outside the range is clamped to the + boundary instead of failing. + + .. versionchanged:: 8.0 + Added the ``min_open`` and ``max_open`` parameters. """ - name = 'float range' - def __init__(self, min=None, max=None, clamp=False): - self.min = min - self.max = max - self.clamp = clamp + name = "integer range" - def convert(self, value, param, ctx): - rv = FloatParamType.convert(self, value, param, ctx) - if self.clamp: - if self.min is not None and rv < self.min: - return self.min - if self.max is not None and rv > self.max: - return self.max - if self.min is not None and rv < self.min or \ - self.max is not None and rv > self.max: - if self.min is None: - self.fail('%s is bigger than the maximum valid value ' - '%s.' % (rv, self.max), param, ctx) - elif self.max is None: - self.fail('%s is smaller than the minimum valid value ' - '%s.' % (rv, self.min), param, ctx) - else: - self.fail('%s is not in the valid range of %s to %s.' - % (rv, self.min, self.max), param, ctx) - return rv + def _clamp( # type: ignore + self, bound: int, dir: "te.Literal[1, -1]", open: bool + ) -> int: + if not open: + return bound - def __repr__(self): - return 'FloatRange(%r, %r)' % (self.min, self.max) + return bound + dir + + +class FloatParamType(_NumberParamTypeBase): + name = "float" + _number_class = float + + def __repr__(self) -> str: + return "FLOAT" + + +class FloatRange(_NumberRangeBase, FloatParamType): + """Restrict a :data:`click.FLOAT` value to a range of accepted + values. See :ref:`ranges`. + + If ``min`` or ``max`` are not passed, any value is accepted in that + direction. If ``min_open`` or ``max_open`` are enabled, the + corresponding boundary is not included in the range. + + If ``clamp`` is enabled, a value outside the range is clamped to the + boundary instead of failing. This is not supported if either + boundary is marked ``open``. + + .. versionchanged:: 8.0 + Added the ``min_open`` and ``max_open`` parameters. + """ + + name = "float range" + + def __init__( + self, + min: t.Optional[float] = None, + max: t.Optional[float] = None, + min_open: bool = False, + max_open: bool = False, + clamp: bool = False, + ) -> None: + super().__init__( + min=min, max=max, min_open=min_open, max_open=max_open, clamp=clamp + ) + + if (min_open or max_open) and clamp: + raise TypeError("Clamping is not supported for open bounds.") + + def _clamp(self, bound: float, dir: "te.Literal[1, -1]", open: bool) -> float: + if not open: + return bound + + # Could use Python 3.9's math.nextafter here, but clamping an + # open float range doesn't seem to be particularly useful. It's + # left up to the user to write a callback to do it if needed. + raise RuntimeError("Clamping is not supported for open bounds.") class BoolParamType(ParamType): - name = 'boolean' + name = "boolean" - def convert(self, value, param, ctx): - if isinstance(value, bool): + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + if value in {False, True}: return bool(value) - value = value.lower() - if value in ('true', 't', '1', 'yes', 'y'): - return True - elif value in ('false', 'f', '0', 'no', 'n'): - return False - self.fail('%s is not a valid boolean' % value, param, ctx) - def __repr__(self): - return 'BOOL' + norm = value.strip().lower() + + if norm in {"1", "true", "t", "yes", "y", "on"}: + return True + + if norm in {"0", "false", "f", "no", "n", "off"}: + return False + + self.fail( + _("{value!r} is not a valid boolean.").format(value=value), param, ctx + ) + + def __repr__(self) -> str: + return "BOOL" class UUIDParameterType(ParamType): - name = 'uuid' + name = "uuid" - def convert(self, value, param, ctx): + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: import uuid - try: - if PY2 and isinstance(value, text_type): - value = value.encode('ascii') - return uuid.UUID(value) - except (UnicodeError, ValueError): - self.fail('%s is not a valid UUID value' % value, param, ctx) - def __repr__(self): - return 'UUID' + if isinstance(value, uuid.UUID): + return value + + value = value.strip() + + try: + return uuid.UUID(value) + except ValueError: + self.fail( + _("{value!r} is not a valid UUID.").format(value=value), param, ctx + ) + + def __repr__(self) -> str: + return "UUID" class File(ParamType): @@ -400,43 +653,64 @@ class File(ParamType): See :ref:`file-args` for more information. """ - name = 'filename' + + name = "filename" envvar_list_splitter = os.path.pathsep - def __init__(self, mode='r', encoding=None, errors='strict', lazy=None, - atomic=False): + def __init__( + self, + mode: str = "r", + encoding: t.Optional[str] = None, + errors: t.Optional[str] = "strict", + lazy: t.Optional[bool] = None, + atomic: bool = False, + ) -> None: self.mode = mode self.encoding = encoding self.errors = errors self.lazy = lazy self.atomic = atomic - def resolve_lazy_flag(self, value): + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict.update(mode=self.mode, encoding=self.encoding) + return info_dict + + def resolve_lazy_flag(self, value: t.Any) -> bool: if self.lazy is not None: return self.lazy - if value == '-': + if value == "-": return False - elif 'w' in self.mode: + elif "w" in self.mode: return True return False - def convert(self, value, param, ctx): + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: try: - if hasattr(value, 'read') or hasattr(value, 'write'): + if hasattr(value, "read") or hasattr(value, "write"): return value lazy = self.resolve_lazy_flag(value) if lazy: - f = LazyFile(value, self.mode, self.encoding, self.errors, - atomic=self.atomic) + f: t.IO = t.cast( + t.IO, + LazyFile( + value, self.mode, self.encoding, self.errors, atomic=self.atomic + ), + ) + if ctx is not None: - ctx.call_on_close(f.close_intelligently) + ctx.call_on_close(f.close_intelligently) # type: ignore + return f - f, should_close = open_stream(value, self.mode, - self.encoding, self.errors, - atomic=self.atomic) + f, should_close = open_stream( + value, self.mode, self.encoding, self.errors, atomic=self.atomic + ) + # If a context is provided, we automatically close the file # at the end of the context execution (or flush out). If a # context does not exist, it's the caller's responsibility to @@ -447,12 +721,26 @@ class File(ParamType): ctx.call_on_close(safecall(f.close)) else: ctx.call_on_close(safecall(f.flush)) + return f - except (IOError, OSError) as e: - self.fail('Could not open file: %s: %s' % ( - filename_to_ui(value), - get_streerror(e), - ), param, ctx) + except OSError as e: # noqa: B014 + self.fail(f"{os.fsdecode(value)!r}: {e.strerror}", param, ctx) + + def shell_complete( + self, ctx: "Context", param: "Parameter", incomplete: str + ) -> t.List["CompletionItem"]: + """Return a special completion marker that tells the completion + system to use the shell to provide file path completions. + + :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from pipenv.vendor.click.shell_completion import CompletionItem + + return [CompletionItem(incomplete, type="file")] class Path(ParamType): @@ -461,9 +749,6 @@ class Path(ParamType): handle it returns just the filename. Secondly, it can perform various basic checks about what the file or directory should be. - .. versionchanged:: 6.0 - `allow_dash` was added. - :param exists: if set to true, the file or directory needs to exist for this value to be valid. If this is not required and a file does indeed not exist, then all further checks are @@ -479,17 +764,30 @@ class Path(ParamType): supposed to be done by the shell only. :param allow_dash: If this is set to `True`, a single dash to indicate standard streams is permitted. - :param path_type: optionally a string type that should be used to - represent the path. The default is `None` which - means the return value will be either bytes or - unicode depending on what makes most sense given the - input data Click deals with. + :param path_type: Convert the incoming path value to this type. If + ``None``, keep Python's default, which is ``str``. Useful to + convert to :class:`pathlib.Path`. + + .. versionchanged:: 8.0 + Allow passing ``type=pathlib.Path``. + + .. versionchanged:: 6.0 + Added the ``allow_dash`` parameter. """ + envvar_list_splitter = os.path.pathsep - def __init__(self, exists=False, file_okay=True, dir_okay=True, - writable=False, readable=True, resolve_path=False, - allow_dash=False, path_type=None): + def __init__( + self, + exists: bool = False, + file_okay: bool = True, + dir_okay: bool = True, + writable: bool = False, + readable: bool = True, + resolve_path: bool = False, + allow_dash: bool = False, + path_type: t.Optional[t.Type] = None, + ): self.exists = exists self.file_okay = file_okay self.dir_okay = dir_okay @@ -500,65 +798,116 @@ class Path(ParamType): self.type = path_type if self.file_okay and not self.dir_okay: - self.name = 'file' - self.path_type = 'File' + self.name = _("file") elif self.dir_okay and not self.file_okay: - self.name = 'directory' - self.path_type = 'Directory' + self.name = _("directory") else: - self.name = 'path' - self.path_type = 'Path' + self.name = _("path") - def coerce_path_result(self, rv): + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict.update( + exists=self.exists, + file_okay=self.file_okay, + dir_okay=self.dir_okay, + writable=self.writable, + readable=self.readable, + allow_dash=self.allow_dash, + ) + return info_dict + + def coerce_path_result(self, rv: t.Any) -> t.Any: if self.type is not None and not isinstance(rv, self.type): - if self.type is text_type: - rv = rv.decode(get_filesystem_encoding()) + if self.type is str: + rv = os.fsdecode(rv) + elif self.type is bytes: + rv = os.fsencode(rv) else: - rv = rv.encode(get_filesystem_encoding()) + rv = self.type(rv) + return rv - def convert(self, value, param, ctx): + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: rv = value - is_dash = self.file_okay and self.allow_dash and rv in (b'-', '-') + is_dash = self.file_okay and self.allow_dash and rv in (b"-", "-") if not is_dash: if self.resolve_path: - rv = os.path.realpath(rv) + # os.path.realpath doesn't resolve symlinks on Windows + # until Python 3.8. Use pathlib for now. + import pathlib + + rv = os.fsdecode(pathlib.Path(rv).resolve()) try: st = os.stat(rv) except OSError: if not self.exists: return self.coerce_path_result(rv) - self.fail('%s "%s" does not exist.' % ( - self.path_type, - filename_to_ui(value) - ), param, ctx) + self.fail( + _("{name} {filename!r} does not exist.").format( + name=self.name.title(), filename=os.fsdecode(value) + ), + param, + ctx, + ) if not self.file_okay and stat.S_ISREG(st.st_mode): - self.fail('%s "%s" is a file.' % ( - self.path_type, - filename_to_ui(value) - ), param, ctx) + self.fail( + _("{name} {filename!r} is a file.").format( + name=self.name.title(), filename=os.fsdecode(value) + ), + param, + ctx, + ) if not self.dir_okay and stat.S_ISDIR(st.st_mode): - self.fail('%s "%s" is a directory.' % ( - self.path_type, - filename_to_ui(value) - ), param, ctx) - if self.writable and not os.access(value, os.W_OK): - self.fail('%s "%s" is not writable.' % ( - self.path_type, - filename_to_ui(value) - ), param, ctx) - if self.readable and not os.access(value, os.R_OK): - self.fail('%s "%s" is not readable.' % ( - self.path_type, - filename_to_ui(value) - ), param, ctx) + self.fail( + _("{name} {filename!r} is a directory.").format( + name=self.name.title(), filename=os.fsdecode(value) + ), + param, + ctx, + ) + if self.writable and not os.access(rv, os.W_OK): + self.fail( + _("{name} {filename!r} is not writable.").format( + name=self.name.title(), filename=os.fsdecode(value) + ), + param, + ctx, + ) + if self.readable and not os.access(rv, os.R_OK): + self.fail( + _("{name} {filename!r} is not readable.").format( + name=self.name.title(), filename=os.fsdecode(value) + ), + param, + ctx, + ) return self.coerce_path_result(rv) + def shell_complete( + self, ctx: "Context", param: "Parameter", incomplete: str + ) -> t.List["CompletionItem"]: + """Return a special completion marker that tells the completion + system to use the shell to provide path completions for only + directories or any paths. + + :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from pipenv.vendor.click.shell_completion import CompletionItem + + type = "dir" if self.dir_okay and not self.file_okay else "file" + return [CompletionItem(incomplete, type=type)] + class Tuple(CompositeParamType): """The default behavior of Click is to apply a type on a value directly. @@ -574,72 +923,107 @@ class Tuple(CompositeParamType): :param types: a list of types that should be used for the tuple items. """ - def __init__(self, types): + def __init__(self, types: t.Sequence[t.Union[t.Type, ParamType]]) -> None: self.types = [convert_type(ty) for ty in types] - @property - def name(self): - return "<" + " ".join(ty.name for ty in self.types) + ">" + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict["types"] = [t.to_info_dict() for t in self.types] + return info_dict @property - def arity(self): + def name(self) -> str: # type: ignore + return f"<{' '.join(ty.name for ty in self.types)}>" + + @property + def arity(self) -> int: # type: ignore return len(self.types) - def convert(self, value, param, ctx): - if len(value) != len(self.types): - raise TypeError('It would appear that nargs is set to conflict ' - 'with the composite type arity.') + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + len_type = len(self.types) + len_value = len(value) + + if len_value != len_type: + self.fail( + ngettext( + "{len_type} values are required, but {len_value} was given.", + "{len_type} values are required, but {len_value} were given.", + len_value, + ).format(len_type=len_type, len_value=len_value), + param=param, + ctx=ctx, + ) + return tuple(ty(x, param, ctx) for ty, x in zip(self.types, value)) -def convert_type(ty, default=None): - """Converts a callable or python ty into the most appropriate param - ty. +def convert_type(ty: t.Optional[t.Any], default: t.Optional[t.Any] = None) -> ParamType: + """Find the most appropriate :class:`ParamType` for the given Python + type. If the type isn't provided, it can be inferred from a default + value. """ guessed_type = False + if ty is None and default is not None: - if isinstance(default, tuple): - ty = tuple(map(type, default)) + if isinstance(default, (tuple, list)): + # If the default is empty, ty will remain None and will + # return STRING. + if default: + item = default[0] + + # A tuple of tuples needs to detect the inner types. + # Can't call convert recursively because that would + # incorrectly unwind the tuple to a single type. + if isinstance(item, (tuple, list)): + ty = tuple(map(type, item)) + else: + ty = type(item) else: ty = type(default) + guessed_type = True if isinstance(ty, tuple): return Tuple(ty) + if isinstance(ty, ParamType): return ty - if ty is text_type or ty is str or ty is None: + + if ty is str or ty is None: return STRING + if ty is int: return INT - # Booleans are only okay if not guessed. This is done because for - # flags the default value is actually a bit of a lie in that it - # indicates which of the flags is the one we want. See get_default() - # for more information. - if ty is bool and not guessed_type: - return BOOL + if ty is float: return FLOAT + + if ty is bool: + return BOOL + if guessed_type: return STRING - # Catch a common mistake if __debug__: try: if issubclass(ty, ParamType): - raise AssertionError('Attempted to use an uninstantiated ' - 'parameter type (%s).' % ty) + raise AssertionError( + f"Attempted to use an uninstantiated parameter type ({ty})." + ) except TypeError: + # ty is an instance (correct), so issubclass fails. pass + return FuncParamType(ty) #: A dummy parameter type that just does nothing. From a user's -#: perspective this appears to just be the same as `STRING` but internally -#: no string conversion takes place. This is necessary to achieve the -#: same bytes/unicode behavior on Python 2/3 in situations where you want -#: to not convert argument types. This is usually useful when working -#: with file paths as they can appear in bytes and unicode. +#: perspective this appears to just be the same as `STRING` but +#: internally no string conversion takes place if the input was bytes. +#: This is usually useful when working with file paths as they can +#: appear in bytes and unicode. #: #: For path related uses the :class:`Path` type is a better choice but #: there are situations where an unprocessed type is useful which is why diff --git a/pipenv/vendor/click/utils.py b/pipenv/vendor/click/utils.py index fc84369f..16033d62 100644 --- a/pipenv/vendor/click/utils.py +++ b/pipenv/vendor/click/utils.py @@ -1,92 +1,130 @@ import os import sys +import typing as t +from functools import update_wrapper +from types import ModuleType +from ._compat import _default_text_stderr +from ._compat import _default_text_stdout +from ._compat import _find_binary_writer +from ._compat import auto_wrap_for_ansi +from ._compat import binary_streams +from ._compat import get_filesystem_encoding +from ._compat import open_stream +from ._compat import should_strip_ansi +from ._compat import strip_ansi +from ._compat import text_streams +from ._compat import WIN from .globals import resolve_color_default -from ._compat import text_type, open_stream, get_filesystem_encoding, \ - get_streerror, string_types, PY2, binary_streams, text_streams, \ - filename_to_ui, auto_wrap_for_ansi, strip_ansi, should_strip_ansi, \ - _default_text_stdout, _default_text_stderr, is_bytes, WIN +if t.TYPE_CHECKING: + import typing_extensions as te -if not PY2: - from ._compat import _find_binary_writer -elif WIN: - from ._winconsole import _get_windows_argv, \ - _hash_py_argv, _initial_argv_hash +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) -echo_native_types = string_types + (bytes, bytearray) +def _posixify(name: str) -> str: + return "-".join(name.split()).lower() -def _posixify(name): - return '-'.join(name.split()).lower() - - -def safecall(func): +def safecall(func: F) -> F: """Wraps a function so that it swallows exceptions.""" - def wrapper(*args, **kwargs): + + def wrapper(*args, **kwargs): # type: ignore try: return func(*args, **kwargs) except Exception: pass - return wrapper + + return update_wrapper(t.cast(F, wrapper), func) -def make_str(value): +def make_str(value: t.Any) -> str: """Converts a value into a valid string.""" if isinstance(value, bytes): try: return value.decode(get_filesystem_encoding()) except UnicodeError: - return value.decode('utf-8', 'replace') - return text_type(value) + return value.decode("utf-8", "replace") + return str(value) -def make_default_short_help(help, max_length=45): - """Return a condensed version of help string.""" +def make_default_short_help(help: str, max_length: int = 45) -> str: + """Returns a condensed version of help string.""" + # Consider only the first paragraph. + paragraph_end = help.find("\n\n") + + if paragraph_end != -1: + help = help[:paragraph_end] + + # Collapse newlines, tabs, and spaces. words = help.split() + + if not words: + return "" + + # The first paragraph started with a "no rewrap" marker, ignore it. + if words[0] == "\b": + words = words[1:] + total_length = 0 - result = [] - done = False + last_index = len(words) - 1 - for word in words: - if word[-1:] == '.': - done = True - new_length = result and 1 + len(word) or len(word) - if total_length + new_length > max_length: - result.append('...') - done = True - else: - if result: - result.append(' ') - result.append(word) - if done: + for i, word in enumerate(words): + total_length += len(word) + (i > 0) + + if total_length > max_length: # too long, truncate break - total_length += new_length - return ''.join(result) + if word[-1] == ".": # sentence end, truncate without "..." + return " ".join(words[: i + 1]) + + if total_length == max_length and i != last_index: + break # not at sentence end, truncate with "..." + else: + return " ".join(words) # no truncation needed + + # Account for the length of the suffix. + total_length += len("...") + + # remove words until the length is short enough + while i > 0: + total_length -= len(words[i]) + (i > 0) + + if total_length <= max_length: + break + + i -= 1 + + return " ".join(words[:i]) + "..." -class LazyFile(object): +class LazyFile: """A lazy file works like a regular file but it does not fully open the file but it does perform some basic checks early to see if the filename parameter does make sense. This is useful for safely opening files for writing. """ - def __init__(self, filename, mode='r', encoding=None, errors='strict', - atomic=False): + def __init__( + self, + filename: str, + mode: str = "r", + encoding: t.Optional[str] = None, + errors: t.Optional[str] = "strict", + atomic: bool = False, + ): self.name = filename self.mode = mode self.encoding = encoding self.errors = errors self.atomic = atomic + self._f: t.Optional[t.IO] - if filename == '-': - self._f, self.should_close = open_stream(filename, mode, - encoding, errors) + if filename == "-": + self._f, self.should_close = open_stream(filename, mode, encoding, errors) else: - if 'r' in mode: + if "r" in mode: # Open and close the file in case we're opening it for # reading so that we can catch at least some errors in # some cases early. @@ -94,15 +132,15 @@ class LazyFile(object): self._f = None self.should_close = True - def __getattr__(self, name): + def __getattr__(self, name: str) -> t.Any: return getattr(self.open(), name) - def __repr__(self): + def __repr__(self) -> str: if self._f is not None: return repr(self._f) - return '<unopened file %r %s>' % (self.name, self.mode) + return f"<unopened file '{self.name}' {self.mode}>" - def open(self): + def open(self) -> t.IO: """Opens the file if it's not yet open. This call might fail with a :exc:`FileError`. Not handling this error will produce an error that Click shows. @@ -110,106 +148,103 @@ class LazyFile(object): if self._f is not None: return self._f try: - rv, self.should_close = open_stream(self.name, self.mode, - self.encoding, - self.errors, - atomic=self.atomic) - except (IOError, OSError) as e: + rv, self.should_close = open_stream( + self.name, self.mode, self.encoding, self.errors, atomic=self.atomic + ) + except OSError as e: # noqa: E402 from .exceptions import FileError - raise FileError(self.name, hint=get_streerror(e)) + + raise FileError(self.name, hint=e.strerror) from e self._f = rv return rv - def close(self): + def close(self) -> None: """Closes the underlying file, no matter what.""" if self._f is not None: self._f.close() - def close_intelligently(self): + def close_intelligently(self) -> None: """This function only closes the file if it was opened by the lazy file wrapper. For instance this will never close stdin. """ if self.should_close: self.close() - def __enter__(self): + def __enter__(self) -> "LazyFile": return self - def __exit__(self, exc_type, exc_value, tb): + def __exit__(self, exc_type, exc_value, tb): # type: ignore self.close_intelligently() - def __iter__(self): + def __iter__(self) -> t.Iterator[t.AnyStr]: self.open() - return iter(self._f) + return iter(self._f) # type: ignore -class KeepOpenFile(object): - - def __init__(self, file): +class KeepOpenFile: + def __init__(self, file: t.IO) -> None: self._file = file - def __getattr__(self, name): + def __getattr__(self, name: str) -> t.Any: return getattr(self._file, name) - def __enter__(self): + def __enter__(self) -> "KeepOpenFile": return self - def __exit__(self, exc_type, exc_value, tb): + def __exit__(self, exc_type, exc_value, tb): # type: ignore pass - def __repr__(self): + def __repr__(self) -> str: return repr(self._file) - def __iter__(self): + def __iter__(self) -> t.Iterator[t.AnyStr]: return iter(self._file) -def echo(message=None, file=None, nl=True, err=False, color=None): - """Prints a message plus a newline to the given file or stdout. On - first sight, this looks like the print function, but it has improved - support for handling Unicode and binary data that does not fail no - matter how badly configured the system is. +def echo( + message: t.Optional[t.Any] = None, + file: t.Optional[t.IO] = None, + nl: bool = True, + err: bool = False, + color: t.Optional[bool] = None, +) -> None: + """Print a message and newline to stdout or a file. This should be + used instead of :func:`print` because it provides better support + for different data, files, and environments. - Primarily it means that you can print binary data as well as Unicode - data on both 2.x and 3.x to the given file in the most appropriate way - possible. This is a very carefree function in that it will try its - best to not fail. As of Click 6.0 this includes support for unicode - output on the Windows console. + Compared to :func:`print`, this does the following: - In addition to that, if `colorama`_ is installed, the echo function will - also support clever handling of ANSI codes. Essentially it will then - do the following: + - Ensures that the output encoding is not misconfigured on Linux. + - Supports Unicode in the Windows console. + - Supports writing to binary outputs, and supports writing bytes + to text outputs. + - Supports colors and styles on Windows. + - Removes ANSI color and style codes if the output does not look + like an interactive terminal. + - Always flushes the output. - - add transparent handling of ANSI color codes on Windows. - - hide ANSI codes automatically if the destination file is not a - terminal. - - .. _colorama: https://pypi.org/project/colorama/ + :param message: The string or bytes to output. Other objects are + converted to strings. + :param file: The file to write to. Defaults to ``stdout``. + :param err: Write to ``stderr`` instead of ``stdout``. + :param nl: Print a newline after the message. Enabled by default. + :param color: Force showing or hiding colors and other styles. By + default Click will remove color if the output does not look like + an interactive terminal. .. versionchanged:: 6.0 - As of Click 6.0 the echo function will properly support unicode - output on the windows console. Not that click does not modify - the interpreter in any way which means that `sys.stdout` or the - print statement or function will still not provide unicode support. - - .. versionchanged:: 2.0 - Starting with version 2.0 of Click, the echo function will work - with colorama if it's installed. - - .. versionadded:: 3.0 - The `err` parameter was added. + Support Unicode output on the Windows console. Click does not + modify ``sys.stdout``, so ``sys.stdout.write()`` and ``print()`` + will still not support Unicode. .. versionchanged:: 4.0 - Added the `color` flag. + Added the ``color`` parameter. - :param message: the message to print - :param file: the file to write to (defaults to ``stdout``) - :param err: if set to true the file defaults to ``stderr`` instead of - ``stdout``. This is faster and easier than calling - :func:`get_text_stderr` yourself. - :param nl: if set to `True` (the default) a newline is printed afterwards. - :param color: controls if the terminal supports ANSI colors or not. The - default is autodetection. + .. versionadded:: 3.0 + Added the ``err`` parameter. + + .. versionchanged:: 2.0 + Support colors on Windows if colorama is installed. """ if file is None: if err: @@ -218,70 +253,73 @@ def echo(message=None, file=None, nl=True, err=False, color=None): file = _default_text_stdout() # Convert non bytes/text into the native string type. - if message is not None and not isinstance(message, echo_native_types): - message = text_type(message) + if message is not None and not isinstance(message, (str, bytes, bytearray)): + out: t.Optional[t.Union[str, bytes]] = str(message) + else: + out = message if nl: - message = message or u'' - if isinstance(message, text_type): - message += u'\n' + out = out or "" + if isinstance(out, str): + out += "\n" else: - message += b'\n' + out += b"\n" - # If there is a message, and we're in Python 3, and the value looks - # like bytes, we manually need to find the binary stream and write the - # message in there. This is done separately so that most stream - # types will work as you would expect. Eg: you can write to StringIO - # for other cases. - if message and not PY2 and is_bytes(message): + if not out: + file.flush() + return + + # If there is a message and the value looks like bytes, we manually + # need to find the binary stream and write the message in there. + # This is done separately so that most stream types will work as you + # would expect. Eg: you can write to StringIO for other cases. + if isinstance(out, (bytes, bytearray)): binary_file = _find_binary_writer(file) + if binary_file is not None: file.flush() - binary_file.write(message) + binary_file.write(out) binary_file.flush() return - # ANSI-style support. If there is no message or we are dealing with - # bytes nothing is happening. If we are connected to a file we want - # to strip colors. If we are on windows we either wrap the stream - # to strip the color or we use the colorama support to translate the - # ansi codes to API calls. - if message and not is_bytes(message): + # ANSI style code support. For no message or bytes, nothing happens. + # When outputting to a file instead of a terminal, strip codes. + else: color = resolve_color_default(color) + if should_strip_ansi(file, color): - message = strip_ansi(message) + out = strip_ansi(out) elif WIN: if auto_wrap_for_ansi is not None: - file = auto_wrap_for_ansi(file) + file = auto_wrap_for_ansi(file) # type: ignore elif not color: - message = strip_ansi(message) + out = strip_ansi(out) - if message: - file.write(message) + file.write(out) # type: ignore file.flush() -def get_binary_stream(name): - """Returns a system stream for byte processing. This essentially - returns the stream from the sys module with the given name but it - solves some compatibility issues between different Python versions. - Primarily this function is necessary for getting binary streams on - Python 3. +def get_binary_stream(name: "te.Literal['stdin', 'stdout', 'stderr']") -> t.BinaryIO: + """Returns a system stream for byte processing. :param name: the name of the stream to open. Valid names are ``'stdin'``, ``'stdout'`` and ``'stderr'`` """ opener = binary_streams.get(name) if opener is None: - raise TypeError('Unknown standard stream %r' % name) + raise TypeError(f"Unknown standard stream '{name}'") return opener() -def get_text_stream(name, encoding=None, errors='strict'): +def get_text_stream( + name: "te.Literal['stdin', 'stdout', 'stderr']", + encoding: t.Optional[str] = None, + errors: t.Optional[str] = "strict", +) -> t.TextIO: """Returns a system stream for text processing. This usually returns a wrapped stream around a binary stream returned from - :func:`get_binary_stream` but it also can take shortcuts on Python 3 - for already correctly configured streams. + :func:`get_binary_stream` but it also can take shortcuts for already + correctly configured streams. :param name: the name of the stream to open. Valid names are ``'stdin'``, ``'stdout'`` and ``'stderr'`` @@ -290,12 +328,18 @@ def get_text_stream(name, encoding=None, errors='strict'): """ opener = text_streams.get(name) if opener is None: - raise TypeError('Unknown standard stream %r' % name) + raise TypeError(f"Unknown standard stream '{name}'") return opener(encoding, errors) -def open_file(filename, mode='r', encoding=None, errors='strict', - lazy=False, atomic=False): +def open_file( + filename: str, + mode: str = "r", + encoding: t.Optional[str] = None, + errors: t.Optional[str] = "strict", + lazy: bool = False, + atomic: bool = False, +) -> t.IO: """This is similar to how the :class:`File` works but for manual usage. Files are opened non lazy by default. This can open regular files as well as stdin/stdout if ``'-'`` is passed. @@ -319,36 +363,35 @@ def open_file(filename, mode='r', encoding=None, errors='strict', moved on close. """ if lazy: - return LazyFile(filename, mode, encoding, errors, atomic=atomic) - f, should_close = open_stream(filename, mode, encoding, errors, - atomic=atomic) + return t.cast(t.IO, LazyFile(filename, mode, encoding, errors, atomic=atomic)) + f, should_close = open_stream(filename, mode, encoding, errors, atomic=atomic) if not should_close: - f = KeepOpenFile(f) + f = t.cast(t.IO, KeepOpenFile(f)) return f -def get_os_args(): - """This returns the argument part of sys.argv in the most appropriate - form for processing. What this means is that this return value is in - a format that works for Click to process but does not necessarily - correspond well to what's actually standard for the interpreter. +def get_os_args() -> t.Sequence[str]: + """Returns the argument part of ``sys.argv``, removing the first + value which is the name of the script. - On most environments the return value is ``sys.argv[:1]`` unchanged. - However if you are on Windows and running Python 2 the return value - will actually be a list of unicode strings instead because the - default behavior on that platform otherwise will not be able to - carry all possible values that sys.argv can have. - - .. versionadded:: 6.0 + .. deprecated:: 8.0 + Will be removed in Click 8.1. Access ``sys.argv[1:]`` directly + instead. """ - # We can only extract the unicode argv if sys.argv has not been - # changed since the startup of the application. - if PY2 and WIN and _initial_argv_hash == _hash_py_argv(): - return _get_windows_argv() + import warnings + + warnings.warn( + "'get_os_args' is deprecated and will be removed in Click 8.1." + " Access 'sys.argv[1:]' directly instead.", + DeprecationWarning, + stacklevel=2, + ) return sys.argv[1:] -def format_filename(filename, shorten=False): +def format_filename( + filename: t.Union[str, bytes, os.PathLike], shorten: bool = False +) -> str: """Formats a filename for user display. The main purpose of this function is to ensure that the filename can be displayed at all. This will decode the filename to unicode if necessary in a way that it will @@ -362,10 +405,11 @@ def format_filename(filename, shorten=False): """ if shorten: filename = os.path.basename(filename) - return filename_to_ui(filename) + + return os.fsdecode(filename) -def get_app_dir(app_name, roaming=True, force_posix=False): +def get_app_dir(app_name: str, roaming: bool = True, force_posix: bool = False) -> str: r"""Returns the config folder for the application. The default behavior is to return whatever is most appropriate for the operating system. @@ -380,13 +424,9 @@ def get_app_dir(app_name, roaming=True, force_posix=False): ``~/.config/foo-bar`` Unix (POSIX): ``~/.foo-bar`` - Win XP (roaming): - ``C:\Documents and Settings\<user>\Local Settings\Application Data\Foo Bar`` - Win XP (not roaming): - ``C:\Documents and Settings\<user>\Application Data\Foo Bar`` - Win 7 (roaming): + Windows (roaming): ``C:\Users\<user>\AppData\Roaming\Foo Bar`` - Win 7 (not roaming): + Windows (not roaming): ``C:\Users\<user>\AppData\Local\Foo Bar`` .. versionadded:: 2.0 @@ -401,22 +441,24 @@ def get_app_dir(app_name, roaming=True, force_posix=False): application support folder. """ if WIN: - key = roaming and 'APPDATA' or 'LOCALAPPDATA' + key = "APPDATA" if roaming else "LOCALAPPDATA" folder = os.environ.get(key) if folder is None: - folder = os.path.expanduser('~') + folder = os.path.expanduser("~") return os.path.join(folder, app_name) if force_posix: - return os.path.join(os.path.expanduser('~/.' + _posixify(app_name))) - if sys.platform == 'darwin': - return os.path.join(os.path.expanduser( - '~/Library/Application Support'), app_name) + return os.path.join(os.path.expanduser(f"~/.{_posixify(app_name)}")) + if sys.platform == "darwin": + return os.path.join( + os.path.expanduser("~/Library/Application Support"), app_name + ) return os.path.join( - os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config')), - _posixify(app_name)) + os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")), + _posixify(app_name), + ) -class PacifyFlushWrapper(object): +class PacifyFlushWrapper: """This wrapper is used to catch and suppress BrokenPipeErrors resulting from ``.flush()`` being called on broken pipe during the shutdown/final-GC of the Python interpreter. Notably ``.flush()`` is always called on @@ -425,16 +467,113 @@ class PacifyFlushWrapper(object): pipe, all calls and attributes are proxied. """ - def __init__(self, wrapped): + def __init__(self, wrapped: t.IO) -> None: self.wrapped = wrapped - def flush(self): + def flush(self) -> None: try: self.wrapped.flush() - except IOError as e: + except OSError as e: import errno + if e.errno != errno.EPIPE: raise - def __getattr__(self, attr): + def __getattr__(self, attr: str) -> t.Any: return getattr(self.wrapped, attr) + + +def _detect_program_name( + path: t.Optional[str] = None, _main: ModuleType = sys.modules["__main__"] +) -> str: + """Determine the command used to run the program, for use in help + text. If a file or entry point was executed, the file name is + returned. If ``python -m`` was used to execute a module or package, + ``python -m name`` is returned. + + This doesn't try to be too precise, the goal is to give a concise + name for help text. Files are only shown as their name without the + path. ``python`` is only shown for modules, and the full path to + ``sys.executable`` is not shown. + + :param path: The Python file being executed. Python puts this in + ``sys.argv[0]``, which is used by default. + :param _main: The ``__main__`` module. This should only be passed + during internal testing. + + .. versionadded:: 8.0 + Based on command args detection in the Werkzeug reloader. + + :meta private: + """ + if not path: + path = sys.argv[0] + + # The value of __package__ indicates how Python was called. It may + # not exist if a setuptools script is installed as an egg. It may be + # set incorrectly for entry points created with pip on Windows. + if getattr(_main, "__package__", None) is None or ( + os.name == "nt" + and _main.__package__ == "" + and not os.path.exists(path) + and os.path.exists(f"{path}.exe") + ): + # Executed a file, like "python app.py". + return os.path.basename(path) + + # Executed a module, like "python -m example". + # Rewritten by Python from "-m script" to "/path/to/script.py". + # Need to look at main module to determine how it was executed. + py_module = t.cast(str, _main.__package__) + name = os.path.splitext(os.path.basename(path))[0] + + # A submodule like "example.cli". + if name != "__main__": + py_module = f"{py_module}.{name}" + + return f"python -m {py_module.lstrip('.')}" + + +def _expand_args( + args: t.Iterable[str], + *, + user: bool = True, + env: bool = True, + glob_recursive: bool = True, +) -> t.List[str]: + """Simulate Unix shell expansion with Python functions. + + See :func:`glob.glob`, :func:`os.path.expanduser`, and + :func:`os.path.expandvars`. + + This intended for use on Windows, where the shell does not do any + expansion. It may not exactly match what a Unix shell would do. + + :param args: List of command line arguments to expand. + :param user: Expand user home directory. + :param env: Expand environment variables. + :param glob_recursive: ``**`` matches directories recursively. + + .. versionadded:: 8.0 + + :meta private: + """ + from glob import glob + + out = [] + + for arg in args: + if user: + arg = os.path.expanduser(arg) + + if env: + arg = os.path.expandvars(arg) + + matches = glob(arg, recursive=glob_recursive) + + if not matches: + out.append(arg) + else: + out.extend(matches) + + return out diff --git a/pipenv/vendor/click_completion/__init__.py b/pipenv/vendor/click_completion/__init__.py deleted file mode 100644 index 620d7926..00000000 --- a/pipenv/vendor/click_completion/__init__.py +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/env python -# -*- coding:utf-8 -*- - -from __future__ import print_function, absolute_import - -import six - -from click import ParamType -if six.PY3: - try: - from enum import Enum - except ImportError: - from pipenv.vendor.backports.enum import Enum -else: - from pipenv.vendor.backports.enum import Enum - -from click_completion.core import completion_configuration, get_code, install, shells, resolve_ctx, get_choices, \ - startswith, Shell -from click_completion.lib import get_auto_shell -from click_completion.patch import patch as _patch - -__version__ = '0.5.1' - -_initialized = False - - -def init(complete_options=False, match_incomplete=None): - """Initialize the enhanced click completion - - Parameters - ---------- - complete_options : bool - always complete the options, even when the user hasn't typed a first dash (Default value = False) - match_incomplete : func - a function with two parameters choice and incomplete. Must return True - if incomplete is a correct match for choice, False otherwise. - """ - global _initialized - if not _initialized: - _patch() - completion_configuration.complete_options = complete_options - if match_incomplete is not None: - completion_configuration.match_incomplete = match_incomplete - _initialized = True - - -class DocumentedChoice(ParamType): - """The choice type allows a value to be checked against a fixed set of - supported values. All of these values have to be strings. Each value may - be associated to a help message that will be display in the error message - and during the completion. - - Parameters - ---------- - choices : dict or Enum - A dictionary with the possible choice as key, and the corresponding help string as value - """ - name = 'choice' - - def __init__(self, choices): - if isinstance(choices, Enum): - self.choices = dict((choice.name, choice.value) for choice in choices) - else: - self.choices = dict(choices) - - def get_metavar(self, param): - return '[%s]' % '|'.join(self.choices.keys()) - - def get_missing_message(self, param): - formated_choices = ['{:<12} {}'.format(k, self.choices[k] or '') for k in sorted(self.choices.keys())] - return 'Choose from\n ' + '\n '.join(formated_choices) - - def convert(self, value, param, ctx): - # Exact match - if value in self.choices: - return value - - # Match through normalization - if ctx is not None and \ - ctx.token_normalize_func is not None: - value = ctx.token_normalize_func(value) - for choice in self.choices: - if ctx.token_normalize_func(choice) == value: - return choice - - self.fail('invalid choice: %s. %s' % - (value, self.get_missing_message(param)), param, ctx) - - def __repr__(self): - return 'DocumentedChoice(%r)' % list(self.choices.keys()) - - def complete(self, ctx, incomplete): - match = completion_configuration.match_incomplete - return [(c, v) for c, v in six.iteritems(self.choices) if match(c, incomplete)] diff --git a/pipenv/vendor/click_completion/bash.j2 b/pipenv/vendor/click_completion/bash.j2 deleted file mode 100644 index dc51e734..00000000 --- a/pipenv/vendor/click_completion/bash.j2 +++ /dev/null @@ -1,12 +0,0 @@ -_{{prog_name}}_completion() { - local IFS=$'\t' - COMPREPLY=( $( env COMP_WORDS="${COMP_WORDS[*]}" \ - COMP_CWORD=$COMP_CWORD \ -{%- for k, v in extra_env.items() %} - {{k}}={{v}} \ -{%- endfor %} - {{complete_var}}=complete-bash $1 ) ) - return 0 -} - -complete -F _{{prog_name}}_completion -o default {{prog_name}} diff --git a/pipenv/vendor/click_completion/core.py b/pipenv/vendor/click_completion/core.py deleted file mode 100644 index 36150d14..00000000 --- a/pipenv/vendor/click_completion/core.py +++ /dev/null @@ -1,387 +0,0 @@ -#!/usr/bin/env python -# -*- coding:utf-8 -*- - -from __future__ import print_function, absolute_import - -import os -import re -import shlex -import subprocess - -import click -from click import Option, Argument, MultiCommand, echo -import six -if six.PY3: - try: - from enum import Enum - except ImportError: - from pipenv.vendor.backports.enum import Enum -else: - from pipenv.vendor.backports.enum import Enum - -from click_completion.lib import resolve_ctx, split_args, single_quote, double_quote, get_auto_shell - - -def startswith(string, incomplete): - """Returns True when string starts with incomplete - - It might be overridden with a fuzzier version - for example a case insensitive version - - Parameters - ---------- - string : str - The string to check - incomplete : str - The incomplete string to compare to the begining of string - - Returns - ------- - bool - True if string starts with incomplete, False otherwise - """ - return string.startswith(incomplete) - - -class CompletionConfiguration(object): - """A class to hold the completion configuration - - Attributes - ---------- - - complete_options : bool - Wether to complete the options or not. By default, the options are only completed after the user has entered - a first dash '-'. Change this value to True to always complete the options, even without first typing any - character. - match_incomplete : func - A function use to check whether a parameter match an incomplete argument typed by the user - """ - def __init__(self): - self.complete_options = False - self.match_incomplete = startswith - - -def match(string, incomplete): - import click_completion - # backward compatibility handling - if click_completion.startswith != startswith: - fn = click_completion.startswith - else: - fn = completion_configuration.match_incomplete - return fn(string, incomplete) - - -def get_choices(cli, prog_name, args, incomplete): - """ - - Parameters - ---------- - cli : click.Command - The main click Command of the program - prog_name : str - The program name on the command line - args : [str] - The arguments already written by the user on the command line - incomplete : str - The partial argument to complete - - Returns - ------- - [(str, str)] - A list of completion results. The first element of each tuple is actually the argument to complete, the second - element is an help string for this argument. - """ - ctx = resolve_ctx(cli, prog_name, args) - if ctx is None: - return - optctx = None - if args: - options = [param - for param in ctx.command.get_params(ctx) - if isinstance(param, Option)] - arguments = [param - for param in ctx.command.get_params(ctx) - if isinstance(param, Argument)] - for param in options: - if not param.is_flag and args[-1] in param.opts + param.secondary_opts: - optctx = param - if optctx is None: - for param in arguments: - if ( - not incomplete.startswith("-") - and ( - ctx.params.get(param.name) in (None, ()) - or param.nargs == -1 - ) - ): - optctx = param - break - choices = [] - if optctx: - choices += [c if isinstance(c, tuple) else (c, None) for c in optctx.type.complete(ctx, incomplete)] - else: - for param in ctx.command.get_params(ctx): - if (completion_configuration.complete_options or incomplete and not incomplete[:1].isalnum()) and isinstance(param, Option): - for opt in param.opts: - if match(opt, incomplete): - choices.append((opt, param.help)) - for opt in param.secondary_opts: - if match(opt, incomplete): - # don't put the doc so fish won't group the primary and - # and secondary options - choices.append((opt, None)) - if isinstance(ctx.command, MultiCommand): - for name in ctx.command.list_commands(ctx): - command = ctx.command.get_command(ctx, name) - if match(name, incomplete) and not command.hidden: - choices.append((name, command.get_short_help_str())) - - for item, help in choices: - yield (item, help) - - -def do_bash_complete(cli, prog_name): - """Do the completion for bash - - Parameters - ---------- - cli : click.Command - The main click Command of the program - prog_name : str - The program name on the command line - - Returns - ------- - bool - True if the completion was successful, False otherwise - """ - comp_words = os.environ['COMP_WORDS'] - try: - cwords = shlex.split(comp_words) - quoted = False - except ValueError: # No closing quotation - cwords = split_args(comp_words) - quoted = True - cword = int(os.environ['COMP_CWORD']) - args = cwords[1:cword] - try: - incomplete = cwords[cword] - except IndexError: - incomplete = '' - choices = get_choices(cli, prog_name, args, incomplete) - - if quoted: - echo('\t'.join(opt for opt, _ in choices), nl=False) - else: - echo('\t'.join(re.sub(r"""([\s\\"'()])""", r'\\\1', opt) for opt, _ in choices), nl=False) - - return True - - -def do_fish_complete(cli, prog_name): - """Do the fish completion - - Parameters - ---------- - cli : click.Command - The main click Command of the program - prog_name : str - The program name on the command line - - Returns - ------- - bool - True if the completion was successful, False otherwise - """ - commandline = os.environ['COMMANDLINE'] - args = split_args(commandline)[1:] - if args and not commandline.endswith(' '): - incomplete = args[-1] - args = args[:-1] - else: - incomplete = '' - - for item, help in get_choices(cli, prog_name, args, incomplete): - if help: - echo("%s\t%s" % (item, re.sub(r'\s', ' ', help))) - else: - echo(item) - - return True - - -def do_zsh_complete(cli, prog_name): - """Do the zsh completion - - Parameters - ---------- - cli : click.Command - The main click Command of the program - prog_name : str - The program name on the command line - - Returns - ------- - bool - True if the completion was successful, False otherwise - """ - commandline = os.environ['COMMANDLINE'] - args = split_args(commandline)[1:] - if args and not commandline.endswith(' '): - incomplete = args[-1] - args = args[:-1] - else: - incomplete = '' - - def escape(s): - return s.replace('"', '""').replace("'", "''").replace('$', '\\$').replace('`', '\\`') - res = [] - for item, help in get_choices(cli, prog_name, args, incomplete): - if help: - res.append(r'"%s"\:"%s"' % (escape(item), escape(help))) - else: - res.append('"%s"' % escape(item)) - if res: - echo("_arguments '*: :((%s))'" % '\n'.join(res)) - else: - echo("_files") - - return True - - -def do_powershell_complete(cli, prog_name): - """Do the powershell completion - - Parameters - ---------- - cli : click.Command - The main click Command of the program - prog_name : str - The program name on the command line - - Returns - ------- - bool - True if the completion was successful, False otherwise - """ - commandline = os.environ['COMMANDLINE'] - args = split_args(commandline)[1:] - quote = single_quote - incomplete = '' - if args and not commandline.endswith(' '): - incomplete = args[-1] - args = args[:-1] - quote_pos = commandline.rfind(incomplete) - 1 - if quote_pos >= 0 and commandline[quote_pos] == '"': - quote = double_quote - - for item, help in get_choices(cli, prog_name, args, incomplete): - echo(quote(item)) - - return True - - -def get_code(shell=None, prog_name=None, env_name=None, extra_env=None): - """Returns the completion code to be evaluated by the shell - - Parameters - ---------- - shell : Shell - The shell type (Default value = None) - prog_name : str - The program name on the command line (Default value = None) - env_name : str - The environment variable used to control the completion (Default value = None) - extra_env : dict - Some extra environment variables to be added to the generated code (Default value = None) - - Returns - ------- - str - The code to be evaluated by the shell - """ - from jinja2 import Environment, FileSystemLoader - if shell in [None, 'auto']: - shell = get_auto_shell() - if not isinstance(shell, Shell): - shell = Shell[shell] - prog_name = prog_name or click.get_current_context().find_root().info_name - env_name = env_name or '_%s_COMPLETE' % prog_name.upper().replace('-', '_') - extra_env = extra_env if extra_env else {} - env = Environment(loader=FileSystemLoader(os.path.dirname(__file__))) - template = env.get_template('%s.j2' % shell.name) - return template.render(prog_name=prog_name, complete_var=env_name, extra_env=extra_env) - - -def install(shell=None, prog_name=None, env_name=None, path=None, append=None, extra_env=None): - """Install the completion - - Parameters - ---------- - shell : Shell - The shell type targeted. It will be guessed with get_auto_shell() if the value is None (Default value = None) - prog_name : str - The program name on the command line. It will be automatically computed if the value is None - (Default value = None) - env_name : str - The environment variable name used to control the completion. It will be automatically computed if the value is - None (Default value = None) - path : str - The installation path of the code to be evaluated by the shell. The standard installation path is used if the - value is None (Default value = None) - append : bool - Whether to append the content to the file or to override it. The default behavior depends on the shell type - (Default value = None) - extra_env : dict - A set of environment variables and their values to be added to the generated code (Default value = None) - """ - prog_name = prog_name or click.get_current_context().find_root().info_name - shell = shell or get_auto_shell() - if append is None and path is not None: - append = True - if append is not None: - mode = 'a' if append else 'w' - else: - mode = None - - if shell == 'fish': - path = path or os.path.expanduser('~') + '/.config/fish/completions/%s.fish' % prog_name - mode = mode or 'w' - elif shell == 'bash': - path = path or os.path.expanduser('~') + '/.bash_completion' - mode = mode or 'a' - elif shell == 'zsh': - path = path or os.path.expanduser('~') + '/.zshrc' - mode = mode or 'a' - elif shell == 'powershell': - subprocess.check_call(['powershell', 'Set-ExecutionPolicy Unrestricted -Scope CurrentUser']) - path = path or subprocess.check_output(['powershell', '-NoProfile', 'echo $profile']).strip() if install else '' - mode = mode or 'a' - else: - raise click.ClickException('%s is not supported.' % shell) - - if append is not None: - mode = 'a' if append else 'w' - else: - mode = mode - d = os.path.dirname(path) - if not os.path.exists(d): - os.makedirs(d) - f = open(path, mode) - f.write(get_code(shell, prog_name, env_name, extra_env)) - f.write("\n") - f.close() - return shell, path - - -class Shell(Enum): - bash = 'Bourne again shell' - fish = 'Friendly interactive shell' - zsh = 'Z shell' - powershell = 'Windows PowerShell' - - -# deprecated - use Shell instead -shells = dict((shell.name, shell.value) for shell in Shell) - - -completion_configuration = CompletionConfiguration() diff --git a/pipenv/vendor/click_completion/fish.j2 b/pipenv/vendor/click_completion/fish.j2 deleted file mode 100644 index 186a67fa..00000000 --- a/pipenv/vendor/click_completion/fish.j2 +++ /dev/null @@ -1 +0,0 @@ -complete --command {{prog_name}} --arguments "(env {{complete_var}}=complete-fish COMMANDLINE=(commandline -cp){% for k, v in extra_env.items() %} {{k}}={{v}}{% endfor %} {{prog_name}})" -f diff --git a/pipenv/vendor/click_completion/lib.py b/pipenv/vendor/click_completion/lib.py deleted file mode 100644 index 167ecfd8..00000000 --- a/pipenv/vendor/click_completion/lib.py +++ /dev/null @@ -1,127 +0,0 @@ -#!/usr/bin/env python -# -*- coding:utf-8 -*- - -from __future__ import print_function, absolute_import - -import re -import shlex - -import click -import shellingham -from click import MultiCommand - -find_unsafe = re.compile(r'[^\w@%+=:,./-]').search - - -def single_quote(s): - """Escape a string with single quotes in order to be parsed as a single element by shlex - - Parameters - ---------- - s : str - The string to quote - - Returns - ------- - str - The quoted string - """ - if not s: - return "''" - if find_unsafe(s) is None: - return s - - # use single quotes, and put single quotes into double quotes - # the string $'b is then quoted as '$'"'"'b' - return "'" + s.replace("'", "'\"'\"'") + "'" - - -def double_quote(s): - """Escape a string with double quotes in order to be parsed as a single element by shlex - - Parameters - ---------- - s : str - The string to quote - - Returns - ------- - str - The quoted string - """ - if not s: - return '""' - if find_unsafe(s) is None: - return s - - # use double quotes, and put double quotes into single quotes - # the string $"b is then quoted as "$"'"'"b" - return '"' + s.replace('"', '"\'"\'"') + '"' - - -def resolve_ctx(cli, prog_name, args, resilient_parsing=True): - """ - - Parameters - ---------- - cli : click.Command - The main click Command of the program - prog_name : str - The program name on the command line - args : [str] - The arguments already written by the user on the command line - - Returns - ------- - click.core.Context - A new context corresponding to the current command - """ - ctx = cli.make_context(prog_name, list(args), resilient_parsing=resilient_parsing) - while ctx.args + ctx.protected_args and isinstance(ctx.command, MultiCommand): - a = ctx.protected_args + ctx.args - cmd = ctx.command.get_command(ctx, a[0]) - if cmd is None: - return None - if hasattr(cmd, "no_args_is_help"): - no_args_is_help = cmd.no_args_is_help - cmd.no_args_is_help = False - ctx = cmd.make_context(a[0], a[1:], parent=ctx, resilient_parsing=resilient_parsing) - if hasattr(cmd, "no_args_is_help"): - cmd.no_args_is_help = no_args_is_help - return ctx - - -def split_args(line): - """Version of shlex.split that silently accept incomplete strings. - - Parameters - ---------- - line : str - The string to split - - Returns - ------- - [str] - The line split in separated arguments - """ - lex = shlex.shlex(line, posix=True) - lex.whitespace_split = True - lex.commenters = '' - res = [] - try: - while True: - res.append(next(lex)) - except ValueError: # No closing quotation - pass - except StopIteration: # End of loop - pass - if lex.token: - res.append(lex.token) - return res - - -def get_auto_shell(): - """Returns the current shell - - This feature depends on psutil and will not work if it is not available""" - return shellingham.detect_shell()[0] diff --git a/pipenv/vendor/click_completion/patch.py b/pipenv/vendor/click_completion/patch.py deleted file mode 100644 index ab351f45..00000000 --- a/pipenv/vendor/click_completion/patch.py +++ /dev/null @@ -1,142 +0,0 @@ -#!/usr/bin/env python -# -*- coding:utf-8 -*- - -from __future__ import print_function, absolute_import - -import os -import sys - -import click -from click import echo - -from click_completion.core import do_bash_complete, do_fish_complete, do_zsh_complete, do_powershell_complete,\ - get_code, install, completion_configuration - -"""All the code used to monkey patch click""" - - -def param_type_complete(self, ctx, incomplete): - """Returns a set of possible completions values, along with their documentation string - - Default implementation of the complete method for click.types.ParamType just returns an empty list - - Parameters - ---------- - ctx : click.core.Context - The current context - incomplete : - The string to complete - - Returns - ------- - [(str, str)] - A list of completion results. The first element of each tuple is actually the argument to complete, the second - element is an help string for this argument. - """ - return [] - - -def choice_complete(self, ctx, incomplete): - """Returns the completion results for click.core.Choice - - Parameters - ---------- - ctx : click.core.Context - The current context - incomplete : - The string to complete - - Returns - ------- - [(str, str)] - A list of completion results - """ - return [ - (c, None) for c in self.choices - if completion_configuration.match_incomplete(c, incomplete) - ] - - -def multicommand_get_command_short_help(self, ctx, cmd_name): - """Returns the short help of a subcommand - - It allows MultiCommand subclasses to implement more efficient ways to provide the subcommand short help, for - example by leveraging some caching. - - Parameters - ---------- - ctx : click.core.Context - The current context - cmd_name : - The sub command name - - Returns - ------- - str - The sub command short help - """ - return self.get_command(ctx, cmd_name).short_help - - -def _shellcomplete(cli, prog_name, complete_var=None): - """Internal handler for the bash completion support. - - Parameters - ---------- - cli : click.Command - The main click Command of the program - prog_name : str - The program name on the command line - complete_var : str - The environment variable name used to control the completion behavior (Default value = None) - """ - if complete_var is None: - complete_var = '_%s_COMPLETE' % (prog_name.replace('-', '_')).upper() - complete_instr = os.environ.get(complete_var) - if not complete_instr: - return - - if complete_instr == 'source': - echo(get_code(prog_name=prog_name, env_name=complete_var)) - elif complete_instr == 'source-bash': - echo(get_code('bash', prog_name, complete_var)) - elif complete_instr == 'source-fish': - echo(get_code('fish', prog_name, complete_var)) - elif complete_instr == 'source-powershell': - echo(get_code('powershell', prog_name, complete_var)) - elif complete_instr == 'source-zsh': - echo(get_code('zsh', prog_name, complete_var)) - elif complete_instr in ['complete', 'complete-bash']: - # keep 'complete' for bash for backward compatibility - do_bash_complete(cli, prog_name) - elif complete_instr == 'complete-fish': - do_fish_complete(cli, prog_name) - elif complete_instr == 'complete-powershell': - do_powershell_complete(cli, prog_name) - elif complete_instr == 'complete-zsh': - do_zsh_complete(cli, prog_name) - elif complete_instr == 'install': - shell, path = install(prog_name=prog_name, env_name=complete_var) - click.echo('%s completion installed in %s' % (shell, path)) - elif complete_instr == 'install-bash': - shell, path = install(shell='bash', prog_name=prog_name, env_name=complete_var) - click.echo('%s completion installed in %s' % (shell, path)) - elif complete_instr == 'install-fish': - shell, path = install(shell='fish', prog_name=prog_name, env_name=complete_var) - click.echo('%s completion installed in %s' % (shell, path)) - elif complete_instr == 'install-zsh': - shell, path = install(shell='zsh', prog_name=prog_name, env_name=complete_var) - click.echo('%s completion installed in %s' % (shell, path)) - elif complete_instr == 'install-powershell': - shell, path = install(shell='powershell', prog_name=prog_name, env_name=complete_var) - click.echo('%s completion installed in %s' % (shell, path)) - sys.exit() - - -def patch(): - """Patch click""" - import click - click.types.ParamType.complete = param_type_complete - click.types.Choice.complete = choice_complete - click.core.MultiCommand.get_command_short_help = multicommand_get_command_short_help - click.core._bashcomplete = _shellcomplete diff --git a/pipenv/vendor/click_completion/powershell.j2 b/pipenv/vendor/click_completion/powershell.j2 deleted file mode 100644 index 0b5f182b..00000000 --- a/pipenv/vendor/click_completion/powershell.j2 +++ /dev/null @@ -1,26 +0,0 @@ -if ((Test-Path Function:\TabExpansion) -and -not (Test-Path Function:\{{prog_name}}TabExpansionBackup)) { - Rename-Item Function:\TabExpansion {{prog_name}}TabExpansionBackup -} - -function TabExpansion($line, $lastWord) { - $lastBlock = [regex]::Split($line, '[|;]')[-1].TrimStart() - $aliases = @("{{prog_name}}") + @(Get-Alias | where { $_.Definition -eq "{{prog_name}}" } | select -Exp Name) - $aliasPattern = "($($aliases -join '|'))" - if($lastBlock -match "^$aliasPattern ") { - $Env:{{complete_var}} = "complete-powershell" - $Env:COMMANDLINE = "$lastBlock" -{%- for k, v in extra_env.items() %} - $Env:{{k}} = "{{v}}" -{%- endfor %} - ({{prog_name}}) | ? {$_.trim() -ne "" } - Remove-Item Env:{{complete_var}} - Remove-Item Env:COMMANDLINE -{%- for k in extra_env.keys() %} - Remove-Item $Env:{{k}} -{%- endfor %} - } - elseif (Test-Path Function:\{{prog_name}}TabExpansionBackup) { - # Fall back on existing tab expansion - {{prog_name}}TabExpansionBackup $line $lastWord - } -} diff --git a/pipenv/vendor/click_completion/zsh.j2 b/pipenv/vendor/click_completion/zsh.j2 deleted file mode 100644 index ac796615..00000000 --- a/pipenv/vendor/click_completion/zsh.j2 +++ /dev/null @@ -1,7 +0,0 @@ -#compdef {{prog_name}} -_{{prog_name}}() { - eval $(env COMMANDLINE="${words[1,$CURRENT]}" {{complete_var}}=complete-zsh {% for k, v in extra_env.items() %} {{k}}={{v}}{% endfor %} {{prog_name}}) -} -if [[ "$(basename -- ${(%):-%x})" != "_{{prog_name}}" ]]; then - compdef _{{prog_name}} {{prog_name}} -fi diff --git a/pipenv/vendor/click_didyoumean/__init__.py b/pipenv/vendor/click_didyoumean/__init__.py index edc5bb38..c93b083b 100644 --- a/pipenv/vendor/click_didyoumean/__init__.py +++ b/pipenv/vendor/click_didyoumean/__init__.py @@ -5,7 +5,7 @@ a group with a git-like *did-you-mean* feature. """ -import click +import pipenv.vendor.click as click import difflib __version__ = "0.0.3" diff --git a/pipenv/vendor/colorama/__init__.py b/pipenv/vendor/colorama/__init__.py index 2a3bf471..b149ed79 100644 --- a/pipenv/vendor/colorama/__init__.py +++ b/pipenv/vendor/colorama/__init__.py @@ -3,4 +3,4 @@ from .initialise import init, deinit, reinit, colorama_text from .ansi import Fore, Back, Style, Cursor from .ansitowin32 import AnsiToWin32 -__version__ = '0.4.1' +__version__ = '0.4.4' diff --git a/pipenv/vendor/colorama/ansi.py b/pipenv/vendor/colorama/ansi.py index 78776588..11ec695f 100644 --- a/pipenv/vendor/colorama/ansi.py +++ b/pipenv/vendor/colorama/ansi.py @@ -6,7 +6,7 @@ See: http://en.wikipedia.org/wiki/ANSI_escape_code CSI = '\033[' OSC = '\033]' -BEL = '\007' +BEL = '\a' def code_to_chars(code): diff --git a/pipenv/vendor/colorama/ansitowin32.py b/pipenv/vendor/colorama/ansitowin32.py index 359c92be..6039a054 100644 --- a/pipenv/vendor/colorama/ansitowin32.py +++ b/pipenv/vendor/colorama/ansitowin32.py @@ -3,7 +3,7 @@ import re import sys import os -from .ansi import AnsiFore, AnsiBack, AnsiStyle, Style +from .ansi import AnsiFore, AnsiBack, AnsiStyle, Style, BEL from .winterm import WinTerm, WinColor, WinStyle from .win32 import windll, winapi_test @@ -68,7 +68,7 @@ class AnsiToWin32(object): win32 function calls. ''' ANSI_CSI_RE = re.compile('\001?\033\\[((?:\\d|;)*)([a-zA-Z])\002?') # Control Sequence Introducer - ANSI_OSC_RE = re.compile('\001?\033\\]((?:.|;)*?)(\x07)\002?') # Operating System Command + ANSI_OSC_RE = re.compile('\001?\033\\]([^\a]*)(\a)\002?') # Operating System Command def __init__(self, wrapped, convert=None, strip=None, autoreset=False): # The wrapped stream (normally sys.stdout or sys.stderr) @@ -247,11 +247,12 @@ class AnsiToWin32(object): start, end = match.span() text = text[:start] + text[end:] paramstring, command = match.groups() - if command in '\x07': # \x07 = BEL - params = paramstring.split(";") - # 0 - change title and icon (we will only change title) - # 1 - change icon (we don't support this) - # 2 - change title - if params[0] in '02': - winterm.set_title(params[1]) + if command == BEL: + if paramstring.count(";") == 1: + params = paramstring.split(";") + # 0 - change title and icon (we will only change title) + # 1 - change icon (we don't support this) + # 2 - change title + if params[0] in '02': + winterm.set_title(params[1]) return text diff --git a/pipenv/vendor/dateutil/__init__.py b/pipenv/vendor/dateutil/__init__.py new file mode 100644 index 00000000..0defb82e --- /dev/null +++ b/pipenv/vendor/dateutil/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +try: + from ._version import version as __version__ +except ImportError: + __version__ = 'unknown' + +__all__ = ['easter', 'parser', 'relativedelta', 'rrule', 'tz', + 'utils', 'zoneinfo'] diff --git a/pipenv/vendor/dateutil/_common.py b/pipenv/vendor/dateutil/_common.py new file mode 100644 index 00000000..4eb2659b --- /dev/null +++ b/pipenv/vendor/dateutil/_common.py @@ -0,0 +1,43 @@ +""" +Common code used in multiple modules. +""" + + +class weekday(object): + __slots__ = ["weekday", "n"] + + def __init__(self, weekday, n=None): + self.weekday = weekday + self.n = n + + def __call__(self, n): + if n == self.n: + return self + else: + return self.__class__(self.weekday, n) + + def __eq__(self, other): + try: + if self.weekday != other.weekday or self.n != other.n: + return False + except AttributeError: + return False + return True + + def __hash__(self): + return hash(( + self.weekday, + self.n, + )) + + def __ne__(self, other): + return not (self == other) + + def __repr__(self): + s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday] + if not self.n: + return s + else: + return "%s(%+d)" % (s, self.n) + +# vim:ts=4:sw=4:et diff --git a/pipenv/vendor/dateutil/_version.py b/pipenv/vendor/dateutil/_version.py new file mode 100644 index 00000000..b723056a --- /dev/null +++ b/pipenv/vendor/dateutil/_version.py @@ -0,0 +1,5 @@ +# coding: utf-8 +# file generated by setuptools_scm +# don't change, don't track in version control +version = '2.8.2' +version_tuple = (2, 8, 2) diff --git a/pipenv/vendor/dateutil/easter.py b/pipenv/vendor/dateutil/easter.py new file mode 100644 index 00000000..f74d1f74 --- /dev/null +++ b/pipenv/vendor/dateutil/easter.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +""" +This module offers a generic Easter computing method for any given year, using +Western, Orthodox or Julian algorithms. +""" + +import datetime + +__all__ = ["easter", "EASTER_JULIAN", "EASTER_ORTHODOX", "EASTER_WESTERN"] + +EASTER_JULIAN = 1 +EASTER_ORTHODOX = 2 +EASTER_WESTERN = 3 + + +def easter(year, method=EASTER_WESTERN): + """ + This method was ported from the work done by GM Arts, + on top of the algorithm by Claus Tondering, which was + based in part on the algorithm of Ouding (1940), as + quoted in "Explanatory Supplement to the Astronomical + Almanac", P. Kenneth Seidelmann, editor. + + This algorithm implements three different Easter + calculation methods: + + 1. Original calculation in Julian calendar, valid in + dates after 326 AD + 2. Original method, with date converted to Gregorian + calendar, valid in years 1583 to 4099 + 3. Revised method, in Gregorian calendar, valid in + years 1583 to 4099 as well + + These methods are represented by the constants: + + * ``EASTER_JULIAN = 1`` + * ``EASTER_ORTHODOX = 2`` + * ``EASTER_WESTERN = 3`` + + The default method is method 3. + + More about the algorithm may be found at: + + `GM Arts: Easter Algorithms <http://www.gmarts.org/index.php?go=415>`_ + + and + + `The Calendar FAQ: Easter <https://www.tondering.dk/claus/cal/easter.php>`_ + + """ + + if not (1 <= method <= 3): + raise ValueError("invalid method") + + # g - Golden year - 1 + # c - Century + # h - (23 - Epact) mod 30 + # i - Number of days from March 21 to Paschal Full Moon + # j - Weekday for PFM (0=Sunday, etc) + # p - Number of days from March 21 to Sunday on or before PFM + # (-6 to 28 methods 1 & 3, to 56 for method 2) + # e - Extra days to add for method 2 (converting Julian + # date to Gregorian date) + + y = year + g = y % 19 + e = 0 + if method < 3: + # Old method + i = (19*g + 15) % 30 + j = (y + y//4 + i) % 7 + if method == 2: + # Extra dates to convert Julian to Gregorian date + e = 10 + if y > 1600: + e = e + y//100 - 16 - (y//100 - 16)//4 + else: + # New method + c = y//100 + h = (c - c//4 - (8*c + 13)//25 + 19*g + 15) % 30 + i = h - (h//28)*(1 - (h//28)*(29//(h + 1))*((21 - g)//11)) + j = (y + y//4 + i + 2 - c + c//4) % 7 + + # p can be from -6 to 56 corresponding to dates 22 March to 23 May + # (later dates apply to method 2, although 23 May never actually occurs) + p = i - j + e + d = 1 + (p + 27 + (p + 6)//40) % 31 + m = 3 + (p + 26)//30 + return datetime.date(int(y), int(m), int(d)) diff --git a/pipenv/vendor/dateutil/parser/__init__.py b/pipenv/vendor/dateutil/parser/__init__.py new file mode 100644 index 00000000..d174b0e4 --- /dev/null +++ b/pipenv/vendor/dateutil/parser/__init__.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +from ._parser import parse, parser, parserinfo, ParserError +from ._parser import DEFAULTPARSER, DEFAULTTZPARSER +from ._parser import UnknownTimezoneWarning + +from ._parser import __doc__ + +from .isoparser import isoparser, isoparse + +__all__ = ['parse', 'parser', 'parserinfo', + 'isoparse', 'isoparser', + 'ParserError', + 'UnknownTimezoneWarning'] + + +### +# Deprecate portions of the private interface so that downstream code that +# is improperly relying on it is given *some* notice. + + +def __deprecated_private_func(f): + from functools import wraps + import warnings + + msg = ('{name} is a private function and may break without warning, ' + 'it will be moved and or renamed in future versions.') + msg = msg.format(name=f.__name__) + + @wraps(f) + def deprecated_func(*args, **kwargs): + warnings.warn(msg, DeprecationWarning) + return f(*args, **kwargs) + + return deprecated_func + +def __deprecate_private_class(c): + import warnings + + msg = ('{name} is a private class and may break without warning, ' + 'it will be moved and or renamed in future versions.') + msg = msg.format(name=c.__name__) + + class private_class(c): + __doc__ = c.__doc__ + + def __init__(self, *args, **kwargs): + warnings.warn(msg, DeprecationWarning) + super(private_class, self).__init__(*args, **kwargs) + + private_class.__name__ = c.__name__ + + return private_class + + +from ._parser import _timelex, _resultbase +from ._parser import _tzparser, _parsetz + +_timelex = __deprecate_private_class(_timelex) +_tzparser = __deprecate_private_class(_tzparser) +_resultbase = __deprecate_private_class(_resultbase) +_parsetz = __deprecated_private_func(_parsetz) diff --git a/pipenv/vendor/dateutil/parser/_parser.py b/pipenv/vendor/dateutil/parser/_parser.py new file mode 100644 index 00000000..e1cc0c17 --- /dev/null +++ b/pipenv/vendor/dateutil/parser/_parser.py @@ -0,0 +1,1613 @@ +# -*- coding: utf-8 -*- +""" +This module offers a generic date/time string parser which is able to parse +most known formats to represent a date and/or time. + +This module attempts to be forgiving with regards to unlikely input formats, +returning a datetime object even for dates which are ambiguous. If an element +of a date/time stamp is omitted, the following rules are applied: + +- If AM or PM is left unspecified, a 24-hour clock is assumed, however, an hour + on a 12-hour clock (``0 <= hour <= 12``) *must* be specified if AM or PM is + specified. +- If a time zone is omitted, a timezone-naive datetime is returned. + +If any other elements are missing, they are taken from the +:class:`datetime.datetime` object passed to the parameter ``default``. If this +results in a day number exceeding the valid number of days per month, the +value falls back to the end of the month. + +Additional resources about date/time string formats can be found below: + +- `A summary of the international standard date and time notation + <https://www.cl.cam.ac.uk/~mgk25/iso-time.html>`_ +- `W3C Date and Time Formats <https://www.w3.org/TR/NOTE-datetime>`_ +- `Time Formats (Planetary Rings Node) <https://pds-rings.seti.org:443/tools/time_formats.html>`_ +- `CPAN ParseDate module + <https://metacpan.org/pod/release/MUIR/Time-modules-2013.0912/lib/Time/ParseDate.pm>`_ +- `Java SimpleDateFormat Class + <https://docs.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html>`_ +""" +from __future__ import unicode_literals + +import datetime +import re +import string +import time +import warnings + +from calendar import monthrange +from io import StringIO + +import pipenv.vendor.six as six +from pipenv.vendor.six import integer_types, text_type + +from decimal import Decimal + +from warnings import warn + +from .. import relativedelta +from .. import tz + +__all__ = ["parse", "parserinfo", "ParserError"] + + +# TODO: pandas.core.tools.datetimes imports this explicitly. Might be worth +# making public and/or figuring out if there is something we can +# take off their plate. +class _timelex(object): + # Fractional seconds are sometimes split by a comma + _split_decimal = re.compile("([.,])") + + def __init__(self, instream): + if isinstance(instream, (bytes, bytearray)): + instream = instream.decode() + + if isinstance(instream, text_type): + instream = StringIO(instream) + elif getattr(instream, 'read', None) is None: + raise TypeError('Parser must be a string or character stream, not ' + '{itype}'.format(itype=instream.__class__.__name__)) + + self.instream = instream + self.charstack = [] + self.tokenstack = [] + self.eof = False + + def get_token(self): + """ + This function breaks the time string into lexical units (tokens), which + can be parsed by the parser. Lexical units are demarcated by changes in + the character set, so any continuous string of letters is considered + one unit, any continuous string of numbers is considered one unit. + + The main complication arises from the fact that dots ('.') can be used + both as separators (e.g. "Sep.20.2009") or decimal points (e.g. + "4:30:21.447"). As such, it is necessary to read the full context of + any dot-separated strings before breaking it into tokens; as such, this + function maintains a "token stack", for when the ambiguous context + demands that multiple tokens be parsed at once. + """ + if self.tokenstack: + return self.tokenstack.pop(0) + + seenletters = False + token = None + state = None + + while not self.eof: + # We only realize that we've reached the end of a token when we + # find a character that's not part of the current token - since + # that character may be part of the next token, it's stored in the + # charstack. + if self.charstack: + nextchar = self.charstack.pop(0) + else: + nextchar = self.instream.read(1) + while nextchar == '\x00': + nextchar = self.instream.read(1) + + if not nextchar: + self.eof = True + break + elif not state: + # First character of the token - determines if we're starting + # to parse a word, a number or something else. + token = nextchar + if self.isword(nextchar): + state = 'a' + elif self.isnum(nextchar): + state = '0' + elif self.isspace(nextchar): + token = ' ' + break # emit token + else: + break # emit token + elif state == 'a': + # If we've already started reading a word, we keep reading + # letters until we find something that's not part of a word. + seenletters = True + if self.isword(nextchar): + token += nextchar + elif nextchar == '.': + token += nextchar + state = 'a.' + else: + self.charstack.append(nextchar) + break # emit token + elif state == '0': + # If we've already started reading a number, we keep reading + # numbers until we find something that doesn't fit. + if self.isnum(nextchar): + token += nextchar + elif nextchar == '.' or (nextchar == ',' and len(token) >= 2): + token += nextchar + state = '0.' + else: + self.charstack.append(nextchar) + break # emit token + elif state == 'a.': + # If we've seen some letters and a dot separator, continue + # parsing, and the tokens will be broken up later. + seenletters = True + if nextchar == '.' or self.isword(nextchar): + token += nextchar + elif self.isnum(nextchar) and token[-1] == '.': + token += nextchar + state = '0.' + else: + self.charstack.append(nextchar) + break # emit token + elif state == '0.': + # If we've seen at least one dot separator, keep going, we'll + # break up the tokens later. + if nextchar == '.' or self.isnum(nextchar): + token += nextchar + elif self.isword(nextchar) and token[-1] == '.': + token += nextchar + state = 'a.' + else: + self.charstack.append(nextchar) + break # emit token + + if (state in ('a.', '0.') and (seenletters or token.count('.') > 1 or + token[-1] in '.,')): + l = self._split_decimal.split(token) + token = l[0] + for tok in l[1:]: + if tok: + self.tokenstack.append(tok) + + if state == '0.' and token.count('.') == 0: + token = token.replace(',', '.') + + return token + + def __iter__(self): + return self + + def __next__(self): + token = self.get_token() + if token is None: + raise StopIteration + + return token + + def next(self): + return self.__next__() # Python 2.x support + + @classmethod + def split(cls, s): + return list(cls(s)) + + @classmethod + def isword(cls, nextchar): + """ Whether or not the next character is part of a word """ + return nextchar.isalpha() + + @classmethod + def isnum(cls, nextchar): + """ Whether the next character is part of a number """ + return nextchar.isdigit() + + @classmethod + def isspace(cls, nextchar): + """ Whether the next character is whitespace """ + return nextchar.isspace() + + +class _resultbase(object): + + def __init__(self): + for attr in self.__slots__: + setattr(self, attr, None) + + def _repr(self, classname): + l = [] + for attr in self.__slots__: + value = getattr(self, attr) + if value is not None: + l.append("%s=%s" % (attr, repr(value))) + return "%s(%s)" % (classname, ", ".join(l)) + + def __len__(self): + return (sum(getattr(self, attr) is not None + for attr in self.__slots__)) + + def __repr__(self): + return self._repr(self.__class__.__name__) + + +class parserinfo(object): + """ + Class which handles what inputs are accepted. Subclass this to customize + the language and acceptable values for each parameter. + + :param dayfirst: + Whether to interpret the first value in an ambiguous 3-integer date + (e.g. 01/05/09) as the day (``True``) or month (``False``). If + ``yearfirst`` is set to ``True``, this distinguishes between YDM + and YMD. Default is ``False``. + + :param yearfirst: + Whether to interpret the first value in an ambiguous 3-integer date + (e.g. 01/05/09) as the year. If ``True``, the first number is taken + to be the year, otherwise the last number is taken to be the year. + Default is ``False``. + """ + + # m from a.m/p.m, t from ISO T separator + JUMP = [" ", ".", ",", ";", "-", "/", "'", + "at", "on", "and", "ad", "m", "t", "of", + "st", "nd", "rd", "th"] + + WEEKDAYS = [("Mon", "Monday"), + ("Tue", "Tuesday"), # TODO: "Tues" + ("Wed", "Wednesday"), + ("Thu", "Thursday"), # TODO: "Thurs" + ("Fri", "Friday"), + ("Sat", "Saturday"), + ("Sun", "Sunday")] + MONTHS = [("Jan", "January"), + ("Feb", "February"), # TODO: "Febr" + ("Mar", "March"), + ("Apr", "April"), + ("May", "May"), + ("Jun", "June"), + ("Jul", "July"), + ("Aug", "August"), + ("Sep", "Sept", "September"), + ("Oct", "October"), + ("Nov", "November"), + ("Dec", "December")] + HMS = [("h", "hour", "hours"), + ("m", "minute", "minutes"), + ("s", "second", "seconds")] + AMPM = [("am", "a"), + ("pm", "p")] + UTCZONE = ["UTC", "GMT", "Z", "z"] + PERTAIN = ["of"] + TZOFFSET = {} + # TODO: ERA = ["AD", "BC", "CE", "BCE", "Stardate", + # "Anno Domini", "Year of Our Lord"] + + def __init__(self, dayfirst=False, yearfirst=False): + self._jump = self._convert(self.JUMP) + self._weekdays = self._convert(self.WEEKDAYS) + self._months = self._convert(self.MONTHS) + self._hms = self._convert(self.HMS) + self._ampm = self._convert(self.AMPM) + self._utczone = self._convert(self.UTCZONE) + self._pertain = self._convert(self.PERTAIN) + + self.dayfirst = dayfirst + self.yearfirst = yearfirst + + self._year = time.localtime().tm_year + self._century = self._year // 100 * 100 + + def _convert(self, lst): + dct = {} + for i, v in enumerate(lst): + if isinstance(v, tuple): + for v in v: + dct[v.lower()] = i + else: + dct[v.lower()] = i + return dct + + def jump(self, name): + return name.lower() in self._jump + + def weekday(self, name): + try: + return self._weekdays[name.lower()] + except KeyError: + pass + return None + + def month(self, name): + try: + return self._months[name.lower()] + 1 + except KeyError: + pass + return None + + def hms(self, name): + try: + return self._hms[name.lower()] + except KeyError: + return None + + def ampm(self, name): + try: + return self._ampm[name.lower()] + except KeyError: + return None + + def pertain(self, name): + return name.lower() in self._pertain + + def utczone(self, name): + return name.lower() in self._utczone + + def tzoffset(self, name): + if name in self._utczone: + return 0 + + return self.TZOFFSET.get(name) + + def convertyear(self, year, century_specified=False): + """ + Converts two-digit years to year within [-50, 49] + range of self._year (current local time) + """ + + # Function contract is that the year is always positive + assert year >= 0 + + if year < 100 and not century_specified: + # assume current century to start + year += self._century + + if year >= self._year + 50: # if too far in future + year -= 100 + elif year < self._year - 50: # if too far in past + year += 100 + + return year + + def validate(self, res): + # move to info + if res.year is not None: + res.year = self.convertyear(res.year, res.century_specified) + + if ((res.tzoffset == 0 and not res.tzname) or + (res.tzname == 'Z' or res.tzname == 'z')): + res.tzname = "UTC" + res.tzoffset = 0 + elif res.tzoffset != 0 and res.tzname and self.utczone(res.tzname): + res.tzoffset = 0 + return True + + +class _ymd(list): + def __init__(self, *args, **kwargs): + super(self.__class__, self).__init__(*args, **kwargs) + self.century_specified = False + self.dstridx = None + self.mstridx = None + self.ystridx = None + + @property + def has_year(self): + return self.ystridx is not None + + @property + def has_month(self): + return self.mstridx is not None + + @property + def has_day(self): + return self.dstridx is not None + + def could_be_day(self, value): + if self.has_day: + return False + elif not self.has_month: + return 1 <= value <= 31 + elif not self.has_year: + # Be permissive, assume leap year + month = self[self.mstridx] + return 1 <= value <= monthrange(2000, month)[1] + else: + month = self[self.mstridx] + year = self[self.ystridx] + return 1 <= value <= monthrange(year, month)[1] + + def append(self, val, label=None): + if hasattr(val, '__len__'): + if val.isdigit() and len(val) > 2: + self.century_specified = True + if label not in [None, 'Y']: # pragma: no cover + raise ValueError(label) + label = 'Y' + elif val > 100: + self.century_specified = True + if label not in [None, 'Y']: # pragma: no cover + raise ValueError(label) + label = 'Y' + + super(self.__class__, self).append(int(val)) + + if label == 'M': + if self.has_month: + raise ValueError('Month is already set') + self.mstridx = len(self) - 1 + elif label == 'D': + if self.has_day: + raise ValueError('Day is already set') + self.dstridx = len(self) - 1 + elif label == 'Y': + if self.has_year: + raise ValueError('Year is already set') + self.ystridx = len(self) - 1 + + def _resolve_from_stridxs(self, strids): + """ + Try to resolve the identities of year/month/day elements using + ystridx, mstridx, and dstridx, if enough of these are specified. + """ + if len(self) == 3 and len(strids) == 2: + # we can back out the remaining stridx value + missing = [x for x in range(3) if x not in strids.values()] + key = [x for x in ['y', 'm', 'd'] if x not in strids] + assert len(missing) == len(key) == 1 + key = key[0] + val = missing[0] + strids[key] = val + + assert len(self) == len(strids) # otherwise this should not be called + out = {key: self[strids[key]] for key in strids} + return (out.get('y'), out.get('m'), out.get('d')) + + def resolve_ymd(self, yearfirst, dayfirst): + len_ymd = len(self) + year, month, day = (None, None, None) + + strids = (('y', self.ystridx), + ('m', self.mstridx), + ('d', self.dstridx)) + + strids = {key: val for key, val in strids if val is not None} + if (len(self) == len(strids) > 0 or + (len(self) == 3 and len(strids) == 2)): + return self._resolve_from_stridxs(strids) + + mstridx = self.mstridx + + if len_ymd > 3: + raise ValueError("More than three YMD values") + elif len_ymd == 1 or (mstridx is not None and len_ymd == 2): + # One member, or two members with a month string + if mstridx is not None: + month = self[mstridx] + # since mstridx is 0 or 1, self[mstridx-1] always + # looks up the other element + other = self[mstridx - 1] + else: + other = self[0] + + if len_ymd > 1 or mstridx is None: + if other > 31: + year = other + else: + day = other + + elif len_ymd == 2: + # Two members with numbers + if self[0] > 31: + # 99-01 + year, month = self + elif self[1] > 31: + # 01-99 + month, year = self + elif dayfirst and self[1] <= 12: + # 13-01 + day, month = self + else: + # 01-13 + month, day = self + + elif len_ymd == 3: + # Three members + if mstridx == 0: + if self[1] > 31: + # Apr-2003-25 + month, year, day = self + else: + month, day, year = self + elif mstridx == 1: + if self[0] > 31 or (yearfirst and self[2] <= 31): + # 99-Jan-01 + year, month, day = self + else: + # 01-Jan-01 + # Give precedence to day-first, since + # two-digit years is usually hand-written. + day, month, year = self + + elif mstridx == 2: + # WTF!? + if self[1] > 31: + # 01-99-Jan + day, year, month = self + else: + # 99-01-Jan + year, day, month = self + + else: + if (self[0] > 31 or + self.ystridx == 0 or + (yearfirst and self[1] <= 12 and self[2] <= 31)): + # 99-01-01 + if dayfirst and self[2] <= 12: + year, day, month = self + else: + year, month, day = self + elif self[0] > 12 or (dayfirst and self[1] <= 12): + # 13-01-01 + day, month, year = self + else: + # 01-13-01 + month, day, year = self + + return year, month, day + + +class parser(object): + def __init__(self, info=None): + self.info = info or parserinfo() + + def parse(self, timestr, default=None, + ignoretz=False, tzinfos=None, **kwargs): + """ + Parse the date/time string into a :class:`datetime.datetime` object. + + :param timestr: + Any date/time string using the supported formats. + + :param default: + The default datetime object, if this is a datetime object and not + ``None``, elements specified in ``timestr`` replace elements in the + default object. + + :param ignoretz: + If set ``True``, time zones in parsed strings are ignored and a + naive :class:`datetime.datetime` object is returned. + + :param tzinfos: + Additional time zone names / aliases which may be present in the + string. This argument maps time zone names (and optionally offsets + from those time zones) to time zones. This parameter can be a + dictionary with timezone aliases mapping time zone names to time + zones or a function taking two parameters (``tzname`` and + ``tzoffset``) and returning a time zone. + + The timezones to which the names are mapped can be an integer + offset from UTC in seconds or a :class:`tzinfo` object. + + .. doctest:: + :options: +NORMALIZE_WHITESPACE + + >>> from dateutil.parser import parse + >>> from dateutil.tz import gettz + >>> tzinfos = {"BRST": -7200, "CST": gettz("America/Chicago")} + >>> parse("2012-01-19 17:21:00 BRST", tzinfos=tzinfos) + datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzoffset(u'BRST', -7200)) + >>> parse("2012-01-19 17:21:00 CST", tzinfos=tzinfos) + datetime.datetime(2012, 1, 19, 17, 21, + tzinfo=tzfile('/usr/share/zoneinfo/America/Chicago')) + + This parameter is ignored if ``ignoretz`` is set. + + :param \\*\\*kwargs: + Keyword arguments as passed to ``_parse()``. + + :return: + Returns a :class:`datetime.datetime` object or, if the + ``fuzzy_with_tokens`` option is ``True``, returns a tuple, the + first element being a :class:`datetime.datetime` object, the second + a tuple containing the fuzzy tokens. + + :raises ParserError: + Raised for invalid or unknown string format, if the provided + :class:`tzinfo` is not in a valid format, or if an invalid date + would be created. + + :raises TypeError: + Raised for non-string or character stream input. + + :raises OverflowError: + Raised if the parsed date exceeds the largest valid C integer on + your system. + """ + + if default is None: + default = datetime.datetime.now().replace(hour=0, minute=0, + second=0, microsecond=0) + + res, skipped_tokens = self._parse(timestr, **kwargs) + + if res is None: + raise ParserError("Unknown string format: %s", timestr) + + if len(res) == 0: + raise ParserError("String does not contain a date: %s", timestr) + + try: + ret = self._build_naive(res, default) + except ValueError as e: + six.raise_from(ParserError(str(e) + ": %s", timestr), e) + + if not ignoretz: + ret = self._build_tzaware(ret, res, tzinfos) + + if kwargs.get('fuzzy_with_tokens', False): + return ret, skipped_tokens + else: + return ret + + class _result(_resultbase): + __slots__ = ["year", "month", "day", "weekday", + "hour", "minute", "second", "microsecond", + "tzname", "tzoffset", "ampm","any_unused_tokens"] + + def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False, + fuzzy_with_tokens=False): + """ + Private method which performs the heavy lifting of parsing, called from + ``parse()``, which passes on its ``kwargs`` to this function. + + :param timestr: + The string to parse. + + :param dayfirst: + Whether to interpret the first value in an ambiguous 3-integer date + (e.g. 01/05/09) as the day (``True``) or month (``False``). If + ``yearfirst`` is set to ``True``, this distinguishes between YDM + and YMD. If set to ``None``, this value is retrieved from the + current :class:`parserinfo` object (which itself defaults to + ``False``). + + :param yearfirst: + Whether to interpret the first value in an ambiguous 3-integer date + (e.g. 01/05/09) as the year. If ``True``, the first number is taken + to be the year, otherwise the last number is taken to be the year. + If this is set to ``None``, the value is retrieved from the current + :class:`parserinfo` object (which itself defaults to ``False``). + + :param fuzzy: + Whether to allow fuzzy parsing, allowing for string like "Today is + January 1, 2047 at 8:21:00AM". + + :param fuzzy_with_tokens: + If ``True``, ``fuzzy`` is automatically set to True, and the parser + will return a tuple where the first element is the parsed + :class:`datetime.datetime` datetimestamp and the second element is + a tuple containing the portions of the string which were ignored: + + .. doctest:: + + >>> from dateutil.parser import parse + >>> parse("Today is January 1, 2047 at 8:21:00AM", fuzzy_with_tokens=True) + (datetime.datetime(2047, 1, 1, 8, 21), (u'Today is ', u' ', u'at ')) + + """ + if fuzzy_with_tokens: + fuzzy = True + + info = self.info + + if dayfirst is None: + dayfirst = info.dayfirst + + if yearfirst is None: + yearfirst = info.yearfirst + + res = self._result() + l = _timelex.split(timestr) # Splits the timestr into tokens + + skipped_idxs = [] + + # year/month/day list + ymd = _ymd() + + len_l = len(l) + i = 0 + try: + while i < len_l: + + # Check if it's a number + value_repr = l[i] + try: + value = float(value_repr) + except ValueError: + value = None + + if value is not None: + # Numeric token + i = self._parse_numeric_token(l, i, info, ymd, res, fuzzy) + + # Check weekday + elif info.weekday(l[i]) is not None: + value = info.weekday(l[i]) + res.weekday = value + + # Check month name + elif info.month(l[i]) is not None: + value = info.month(l[i]) + ymd.append(value, 'M') + + if i + 1 < len_l: + if l[i + 1] in ('-', '/'): + # Jan-01[-99] + sep = l[i + 1] + ymd.append(l[i + 2]) + + if i + 3 < len_l and l[i + 3] == sep: + # Jan-01-99 + ymd.append(l[i + 4]) + i += 2 + + i += 2 + + elif (i + 4 < len_l and l[i + 1] == l[i + 3] == ' ' and + info.pertain(l[i + 2])): + # Jan of 01 + # In this case, 01 is clearly year + if l[i + 4].isdigit(): + # Convert it here to become unambiguous + value = int(l[i + 4]) + year = str(info.convertyear(value)) + ymd.append(year, 'Y') + else: + # Wrong guess + pass + # TODO: not hit in tests + i += 4 + + # Check am/pm + elif info.ampm(l[i]) is not None: + value = info.ampm(l[i]) + val_is_ampm = self._ampm_valid(res.hour, res.ampm, fuzzy) + + if val_is_ampm: + res.hour = self._adjust_ampm(res.hour, value) + res.ampm = value + + elif fuzzy: + skipped_idxs.append(i) + + # Check for a timezone name + elif self._could_be_tzname(res.hour, res.tzname, res.tzoffset, l[i]): + res.tzname = l[i] + res.tzoffset = info.tzoffset(res.tzname) + + # Check for something like GMT+3, or BRST+3. Notice + # that it doesn't mean "I am 3 hours after GMT", but + # "my time +3 is GMT". If found, we reverse the + # logic so that timezone parsing code will get it + # right. + if i + 1 < len_l and l[i + 1] in ('+', '-'): + l[i + 1] = ('+', '-')[l[i + 1] == '+'] + res.tzoffset = None + if info.utczone(res.tzname): + # With something like GMT+3, the timezone + # is *not* GMT. + res.tzname = None + + # Check for a numbered timezone + elif res.hour is not None and l[i] in ('+', '-'): + signal = (-1, 1)[l[i] == '+'] + len_li = len(l[i + 1]) + + # TODO: check that l[i + 1] is integer? + if len_li == 4: + # -0300 + hour_offset = int(l[i + 1][:2]) + min_offset = int(l[i + 1][2:]) + elif i + 2 < len_l and l[i + 2] == ':': + # -03:00 + hour_offset = int(l[i + 1]) + min_offset = int(l[i + 3]) # TODO: Check that l[i+3] is minute-like? + i += 2 + elif len_li <= 2: + # -[0]3 + hour_offset = int(l[i + 1][:2]) + min_offset = 0 + else: + raise ValueError(timestr) + + res.tzoffset = signal * (hour_offset * 3600 + min_offset * 60) + + # Look for a timezone name between parenthesis + if (i + 5 < len_l and + info.jump(l[i + 2]) and l[i + 3] == '(' and + l[i + 5] == ')' and + 3 <= len(l[i + 4]) and + self._could_be_tzname(res.hour, res.tzname, + None, l[i + 4])): + # -0300 (BRST) + res.tzname = l[i + 4] + i += 4 + + i += 1 + + # Check jumps + elif not (info.jump(l[i]) or fuzzy): + raise ValueError(timestr) + + else: + skipped_idxs.append(i) + i += 1 + + # Process year/month/day + year, month, day = ymd.resolve_ymd(yearfirst, dayfirst) + + res.century_specified = ymd.century_specified + res.year = year + res.month = month + res.day = day + + except (IndexError, ValueError): + return None, None + + if not info.validate(res): + return None, None + + if fuzzy_with_tokens: + skipped_tokens = self._recombine_skipped(l, skipped_idxs) + return res, tuple(skipped_tokens) + else: + return res, None + + def _parse_numeric_token(self, tokens, idx, info, ymd, res, fuzzy): + # Token is a number + value_repr = tokens[idx] + try: + value = self._to_decimal(value_repr) + except Exception as e: + six.raise_from(ValueError('Unknown numeric token'), e) + + len_li = len(value_repr) + + len_l = len(tokens) + + if (len(ymd) == 3 and len_li in (2, 4) and + res.hour is None and + (idx + 1 >= len_l or + (tokens[idx + 1] != ':' and + info.hms(tokens[idx + 1]) is None))): + # 19990101T23[59] + s = tokens[idx] + res.hour = int(s[:2]) + + if len_li == 4: + res.minute = int(s[2:]) + + elif len_li == 6 or (len_li > 6 and tokens[idx].find('.') == 6): + # YYMMDD or HHMMSS[.ss] + s = tokens[idx] + + if not ymd and '.' not in tokens[idx]: + ymd.append(s[:2]) + ymd.append(s[2:4]) + ymd.append(s[4:]) + else: + # 19990101T235959[.59] + + # TODO: Check if res attributes already set. + res.hour = int(s[:2]) + res.minute = int(s[2:4]) + res.second, res.microsecond = self._parsems(s[4:]) + + elif len_li in (8, 12, 14): + # YYYYMMDD + s = tokens[idx] + ymd.append(s[:4], 'Y') + ymd.append(s[4:6]) + ymd.append(s[6:8]) + + if len_li > 8: + res.hour = int(s[8:10]) + res.minute = int(s[10:12]) + + if len_li > 12: + res.second = int(s[12:]) + + elif self._find_hms_idx(idx, tokens, info, allow_jump=True) is not None: + # HH[ ]h or MM[ ]m or SS[.ss][ ]s + hms_idx = self._find_hms_idx(idx, tokens, info, allow_jump=True) + (idx, hms) = self._parse_hms(idx, tokens, info, hms_idx) + if hms is not None: + # TODO: checking that hour/minute/second are not + # already set? + self._assign_hms(res, value_repr, hms) + + elif idx + 2 < len_l and tokens[idx + 1] == ':': + # HH:MM[:SS[.ss]] + res.hour = int(value) + value = self._to_decimal(tokens[idx + 2]) # TODO: try/except for this? + (res.minute, res.second) = self._parse_min_sec(value) + + if idx + 4 < len_l and tokens[idx + 3] == ':': + res.second, res.microsecond = self._parsems(tokens[idx + 4]) + + idx += 2 + + idx += 2 + + elif idx + 1 < len_l and tokens[idx + 1] in ('-', '/', '.'): + sep = tokens[idx + 1] + ymd.append(value_repr) + + if idx + 2 < len_l and not info.jump(tokens[idx + 2]): + if tokens[idx + 2].isdigit(): + # 01-01[-01] + ymd.append(tokens[idx + 2]) + else: + # 01-Jan[-01] + value = info.month(tokens[idx + 2]) + + if value is not None: + ymd.append(value, 'M') + else: + raise ValueError() + + if idx + 3 < len_l and tokens[idx + 3] == sep: + # We have three members + value = info.month(tokens[idx + 4]) + + if value is not None: + ymd.append(value, 'M') + else: + ymd.append(tokens[idx + 4]) + idx += 2 + + idx += 1 + idx += 1 + + elif idx + 1 >= len_l or info.jump(tokens[idx + 1]): + if idx + 2 < len_l and info.ampm(tokens[idx + 2]) is not None: + # 12 am + hour = int(value) + res.hour = self._adjust_ampm(hour, info.ampm(tokens[idx + 2])) + idx += 1 + else: + # Year, month or day + ymd.append(value) + idx += 1 + + elif info.ampm(tokens[idx + 1]) is not None and (0 <= value < 24): + # 12am + hour = int(value) + res.hour = self._adjust_ampm(hour, info.ampm(tokens[idx + 1])) + idx += 1 + + elif ymd.could_be_day(value): + ymd.append(value) + + elif not fuzzy: + raise ValueError() + + return idx + + def _find_hms_idx(self, idx, tokens, info, allow_jump): + len_l = len(tokens) + + if idx+1 < len_l and info.hms(tokens[idx+1]) is not None: + # There is an "h", "m", or "s" label following this token. We take + # assign the upcoming label to the current token. + # e.g. the "12" in 12h" + hms_idx = idx + 1 + + elif (allow_jump and idx+2 < len_l and tokens[idx+1] == ' ' and + info.hms(tokens[idx+2]) is not None): + # There is a space and then an "h", "m", or "s" label. + # e.g. the "12" in "12 h" + hms_idx = idx + 2 + + elif idx > 0 and info.hms(tokens[idx-1]) is not None: + # There is a "h", "m", or "s" preceding this token. Since neither + # of the previous cases was hit, there is no label following this + # token, so we use the previous label. + # e.g. the "04" in "12h04" + hms_idx = idx-1 + + elif (1 < idx == len_l-1 and tokens[idx-1] == ' ' and + info.hms(tokens[idx-2]) is not None): + # If we are looking at the final token, we allow for a + # backward-looking check to skip over a space. + # TODO: Are we sure this is the right condition here? + hms_idx = idx - 2 + + else: + hms_idx = None + + return hms_idx + + def _assign_hms(self, res, value_repr, hms): + # See GH issue #427, fixing float rounding + value = self._to_decimal(value_repr) + + if hms == 0: + # Hour + res.hour = int(value) + if value % 1: + res.minute = int(60*(value % 1)) + + elif hms == 1: + (res.minute, res.second) = self._parse_min_sec(value) + + elif hms == 2: + (res.second, res.microsecond) = self._parsems(value_repr) + + def _could_be_tzname(self, hour, tzname, tzoffset, token): + return (hour is not None and + tzname is None and + tzoffset is None and + len(token) <= 5 and + (all(x in string.ascii_uppercase for x in token) + or token in self.info.UTCZONE)) + + def _ampm_valid(self, hour, ampm, fuzzy): + """ + For fuzzy parsing, 'a' or 'am' (both valid English words) + may erroneously trigger the AM/PM flag. Deal with that + here. + """ + val_is_ampm = True + + # If there's already an AM/PM flag, this one isn't one. + if fuzzy and ampm is not None: + val_is_ampm = False + + # If AM/PM is found and hour is not, raise a ValueError + if hour is None: + if fuzzy: + val_is_ampm = False + else: + raise ValueError('No hour specified with AM or PM flag.') + elif not 0 <= hour <= 12: + # If AM/PM is found, it's a 12 hour clock, so raise + # an error for invalid range + if fuzzy: + val_is_ampm = False + else: + raise ValueError('Invalid hour specified for 12-hour clock.') + + return val_is_ampm + + def _adjust_ampm(self, hour, ampm): + if hour < 12 and ampm == 1: + hour += 12 + elif hour == 12 and ampm == 0: + hour = 0 + return hour + + def _parse_min_sec(self, value): + # TODO: Every usage of this function sets res.second to the return + # value. Are there any cases where second will be returned as None and + # we *don't* want to set res.second = None? + minute = int(value) + second = None + + sec_remainder = value % 1 + if sec_remainder: + second = int(60 * sec_remainder) + return (minute, second) + + def _parse_hms(self, idx, tokens, info, hms_idx): + # TODO: Is this going to admit a lot of false-positives for when we + # just happen to have digits and "h", "m" or "s" characters in non-date + # text? I guess hex hashes won't have that problem, but there's plenty + # of random junk out there. + if hms_idx is None: + hms = None + new_idx = idx + elif hms_idx > idx: + hms = info.hms(tokens[hms_idx]) + new_idx = hms_idx + else: + # Looking backwards, increment one. + hms = info.hms(tokens[hms_idx]) + 1 + new_idx = idx + + return (new_idx, hms) + + # ------------------------------------------------------------------ + # Handling for individual tokens. These are kept as methods instead + # of functions for the sake of customizability via subclassing. + + def _parsems(self, value): + """Parse a I[.F] seconds value into (seconds, microseconds).""" + if "." not in value: + return int(value), 0 + else: + i, f = value.split(".") + return int(i), int(f.ljust(6, "0")[:6]) + + def _to_decimal(self, val): + try: + decimal_value = Decimal(val) + # See GH 662, edge case, infinite value should not be converted + # via `_to_decimal` + if not decimal_value.is_finite(): + raise ValueError("Converted decimal value is infinite or NaN") + except Exception as e: + msg = "Could not convert %s to decimal" % val + six.raise_from(ValueError(msg), e) + else: + return decimal_value + + # ------------------------------------------------------------------ + # Post-Parsing construction of datetime output. These are kept as + # methods instead of functions for the sake of customizability via + # subclassing. + + def _build_tzinfo(self, tzinfos, tzname, tzoffset): + if callable(tzinfos): + tzdata = tzinfos(tzname, tzoffset) + else: + tzdata = tzinfos.get(tzname) + # handle case where tzinfo is paased an options that returns None + # eg tzinfos = {'BRST' : None} + if isinstance(tzdata, datetime.tzinfo) or tzdata is None: + tzinfo = tzdata + elif isinstance(tzdata, text_type): + tzinfo = tz.tzstr(tzdata) + elif isinstance(tzdata, integer_types): + tzinfo = tz.tzoffset(tzname, tzdata) + else: + raise TypeError("Offset must be tzinfo subclass, tz string, " + "or int offset.") + return tzinfo + + def _build_tzaware(self, naive, res, tzinfos): + if (callable(tzinfos) or (tzinfos and res.tzname in tzinfos)): + tzinfo = self._build_tzinfo(tzinfos, res.tzname, res.tzoffset) + aware = naive.replace(tzinfo=tzinfo) + aware = self._assign_tzname(aware, res.tzname) + + elif res.tzname and res.tzname in time.tzname: + aware = naive.replace(tzinfo=tz.tzlocal()) + + # Handle ambiguous local datetime + aware = self._assign_tzname(aware, res.tzname) + + # This is mostly relevant for winter GMT zones parsed in the UK + if (aware.tzname() != res.tzname and + res.tzname in self.info.UTCZONE): + aware = aware.replace(tzinfo=tz.UTC) + + elif res.tzoffset == 0: + aware = naive.replace(tzinfo=tz.UTC) + + elif res.tzoffset: + aware = naive.replace(tzinfo=tz.tzoffset(res.tzname, res.tzoffset)) + + elif not res.tzname and not res.tzoffset: + # i.e. no timezone information was found. + aware = naive + + elif res.tzname: + # tz-like string was parsed but we don't know what to do + # with it + warnings.warn("tzname {tzname} identified but not understood. " + "Pass `tzinfos` argument in order to correctly " + "return a timezone-aware datetime. In a future " + "version, this will raise an " + "exception.".format(tzname=res.tzname), + category=UnknownTimezoneWarning) + aware = naive + + return aware + + def _build_naive(self, res, default): + repl = {} + for attr in ("year", "month", "day", "hour", + "minute", "second", "microsecond"): + value = getattr(res, attr) + if value is not None: + repl[attr] = value + + if 'day' not in repl: + # If the default day exceeds the last day of the month, fall back + # to the end of the month. + cyear = default.year if res.year is None else res.year + cmonth = default.month if res.month is None else res.month + cday = default.day if res.day is None else res.day + + if cday > monthrange(cyear, cmonth)[1]: + repl['day'] = monthrange(cyear, cmonth)[1] + + naive = default.replace(**repl) + + if res.weekday is not None and not res.day: + naive = naive + relativedelta.relativedelta(weekday=res.weekday) + + return naive + + def _assign_tzname(self, dt, tzname): + if dt.tzname() != tzname: + new_dt = tz.enfold(dt, fold=1) + if new_dt.tzname() == tzname: + return new_dt + + return dt + + def _recombine_skipped(self, tokens, skipped_idxs): + """ + >>> tokens = ["foo", " ", "bar", " ", "19June2000", "baz"] + >>> skipped_idxs = [0, 1, 2, 5] + >>> _recombine_skipped(tokens, skipped_idxs) + ["foo bar", "baz"] + """ + skipped_tokens = [] + for i, idx in enumerate(sorted(skipped_idxs)): + if i > 0 and idx - 1 == skipped_idxs[i - 1]: + skipped_tokens[-1] = skipped_tokens[-1] + tokens[idx] + else: + skipped_tokens.append(tokens[idx]) + + return skipped_tokens + + +DEFAULTPARSER = parser() + + +def parse(timestr, parserinfo=None, **kwargs): + """ + + Parse a string in one of the supported formats, using the + ``parserinfo`` parameters. + + :param timestr: + A string containing a date/time stamp. + + :param parserinfo: + A :class:`parserinfo` object containing parameters for the parser. + If ``None``, the default arguments to the :class:`parserinfo` + constructor are used. + + The ``**kwargs`` parameter takes the following keyword arguments: + + :param default: + The default datetime object, if this is a datetime object and not + ``None``, elements specified in ``timestr`` replace elements in the + default object. + + :param ignoretz: + If set ``True``, time zones in parsed strings are ignored and a naive + :class:`datetime` object is returned. + + :param tzinfos: + Additional time zone names / aliases which may be present in the + string. This argument maps time zone names (and optionally offsets + from those time zones) to time zones. This parameter can be a + dictionary with timezone aliases mapping time zone names to time + zones or a function taking two parameters (``tzname`` and + ``tzoffset``) and returning a time zone. + + The timezones to which the names are mapped can be an integer + offset from UTC in seconds or a :class:`tzinfo` object. + + .. doctest:: + :options: +NORMALIZE_WHITESPACE + + >>> from dateutil.parser import parse + >>> from dateutil.tz import gettz + >>> tzinfos = {"BRST": -7200, "CST": gettz("America/Chicago")} + >>> parse("2012-01-19 17:21:00 BRST", tzinfos=tzinfos) + datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzoffset(u'BRST', -7200)) + >>> parse("2012-01-19 17:21:00 CST", tzinfos=tzinfos) + datetime.datetime(2012, 1, 19, 17, 21, + tzinfo=tzfile('/usr/share/zoneinfo/America/Chicago')) + + This parameter is ignored if ``ignoretz`` is set. + + :param dayfirst: + Whether to interpret the first value in an ambiguous 3-integer date + (e.g. 01/05/09) as the day (``True``) or month (``False``). If + ``yearfirst`` is set to ``True``, this distinguishes between YDM and + YMD. If set to ``None``, this value is retrieved from the current + :class:`parserinfo` object (which itself defaults to ``False``). + + :param yearfirst: + Whether to interpret the first value in an ambiguous 3-integer date + (e.g. 01/05/09) as the year. If ``True``, the first number is taken to + be the year, otherwise the last number is taken to be the year. If + this is set to ``None``, the value is retrieved from the current + :class:`parserinfo` object (which itself defaults to ``False``). + + :param fuzzy: + Whether to allow fuzzy parsing, allowing for string like "Today is + January 1, 2047 at 8:21:00AM". + + :param fuzzy_with_tokens: + If ``True``, ``fuzzy`` is automatically set to True, and the parser + will return a tuple where the first element is the parsed + :class:`datetime.datetime` datetimestamp and the second element is + a tuple containing the portions of the string which were ignored: + + .. doctest:: + + >>> from dateutil.parser import parse + >>> parse("Today is January 1, 2047 at 8:21:00AM", fuzzy_with_tokens=True) + (datetime.datetime(2047, 1, 1, 8, 21), (u'Today is ', u' ', u'at ')) + + :return: + Returns a :class:`datetime.datetime` object or, if the + ``fuzzy_with_tokens`` option is ``True``, returns a tuple, the + first element being a :class:`datetime.datetime` object, the second + a tuple containing the fuzzy tokens. + + :raises ParserError: + Raised for invalid or unknown string formats, if the provided + :class:`tzinfo` is not in a valid format, or if an invalid date would + be created. + + :raises OverflowError: + Raised if the parsed date exceeds the largest valid C integer on + your system. + """ + if parserinfo: + return parser(parserinfo).parse(timestr, **kwargs) + else: + return DEFAULTPARSER.parse(timestr, **kwargs) + + +class _tzparser(object): + + class _result(_resultbase): + + __slots__ = ["stdabbr", "stdoffset", "dstabbr", "dstoffset", + "start", "end"] + + class _attr(_resultbase): + __slots__ = ["month", "week", "weekday", + "yday", "jyday", "day", "time"] + + def __repr__(self): + return self._repr("") + + def __init__(self): + _resultbase.__init__(self) + self.start = self._attr() + self.end = self._attr() + + def parse(self, tzstr): + res = self._result() + l = [x for x in re.split(r'([,:.]|[a-zA-Z]+|[0-9]+)',tzstr) if x] + used_idxs = list() + try: + + len_l = len(l) + + i = 0 + while i < len_l: + # BRST+3[BRDT[+2]] + j = i + while j < len_l and not [x for x in l[j] + if x in "0123456789:,-+"]: + j += 1 + if j != i: + if not res.stdabbr: + offattr = "stdoffset" + res.stdabbr = "".join(l[i:j]) + else: + offattr = "dstoffset" + res.dstabbr = "".join(l[i:j]) + + for ii in range(j): + used_idxs.append(ii) + i = j + if (i < len_l and (l[i] in ('+', '-') or l[i][0] in + "0123456789")): + if l[i] in ('+', '-'): + # Yes, that's right. See the TZ variable + # documentation. + signal = (1, -1)[l[i] == '+'] + used_idxs.append(i) + i += 1 + else: + signal = -1 + len_li = len(l[i]) + if len_li == 4: + # -0300 + setattr(res, offattr, (int(l[i][:2]) * 3600 + + int(l[i][2:]) * 60) * signal) + elif i + 1 < len_l and l[i + 1] == ':': + # -03:00 + setattr(res, offattr, + (int(l[i]) * 3600 + + int(l[i + 2]) * 60) * signal) + used_idxs.append(i) + i += 2 + elif len_li <= 2: + # -[0]3 + setattr(res, offattr, + int(l[i][:2]) * 3600 * signal) + else: + return None + used_idxs.append(i) + i += 1 + if res.dstabbr: + break + else: + break + + + if i < len_l: + for j in range(i, len_l): + if l[j] == ';': + l[j] = ',' + + assert l[i] == ',' + + i += 1 + + if i >= len_l: + pass + elif (8 <= l.count(',') <= 9 and + not [y for x in l[i:] if x != ',' + for y in x if y not in "0123456789+-"]): + # GMT0BST,3,0,30,3600,10,0,26,7200[,3600] + for x in (res.start, res.end): + x.month = int(l[i]) + used_idxs.append(i) + i += 2 + if l[i] == '-': + value = int(l[i + 1]) * -1 + used_idxs.append(i) + i += 1 + else: + value = int(l[i]) + used_idxs.append(i) + i += 2 + if value: + x.week = value + x.weekday = (int(l[i]) - 1) % 7 + else: + x.day = int(l[i]) + used_idxs.append(i) + i += 2 + x.time = int(l[i]) + used_idxs.append(i) + i += 2 + if i < len_l: + if l[i] in ('-', '+'): + signal = (-1, 1)[l[i] == "+"] + used_idxs.append(i) + i += 1 + else: + signal = 1 + used_idxs.append(i) + res.dstoffset = (res.stdoffset + int(l[i]) * signal) + + # This was a made-up format that is not in normal use + warn(('Parsed time zone "%s"' % tzstr) + + 'is in a non-standard dateutil-specific format, which ' + + 'is now deprecated; support for parsing this format ' + + 'will be removed in future versions. It is recommended ' + + 'that you switch to a standard format like the GNU ' + + 'TZ variable format.', tz.DeprecatedTzFormatWarning) + elif (l.count(',') == 2 and l[i:].count('/') <= 2 and + not [y for x in l[i:] if x not in (',', '/', 'J', 'M', + '.', '-', ':') + for y in x if y not in "0123456789"]): + for x in (res.start, res.end): + if l[i] == 'J': + # non-leap year day (1 based) + used_idxs.append(i) + i += 1 + x.jyday = int(l[i]) + elif l[i] == 'M': + # month[-.]week[-.]weekday + used_idxs.append(i) + i += 1 + x.month = int(l[i]) + used_idxs.append(i) + i += 1 + assert l[i] in ('-', '.') + used_idxs.append(i) + i += 1 + x.week = int(l[i]) + if x.week == 5: + x.week = -1 + used_idxs.append(i) + i += 1 + assert l[i] in ('-', '.') + used_idxs.append(i) + i += 1 + x.weekday = (int(l[i]) - 1) % 7 + else: + # year day (zero based) + x.yday = int(l[i]) + 1 + + used_idxs.append(i) + i += 1 + + if i < len_l and l[i] == '/': + used_idxs.append(i) + i += 1 + # start time + len_li = len(l[i]) + if len_li == 4: + # -0300 + x.time = (int(l[i][:2]) * 3600 + + int(l[i][2:]) * 60) + elif i + 1 < len_l and l[i + 1] == ':': + # -03:00 + x.time = int(l[i]) * 3600 + int(l[i + 2]) * 60 + used_idxs.append(i) + i += 2 + if i + 1 < len_l and l[i + 1] == ':': + used_idxs.append(i) + i += 2 + x.time += int(l[i]) + elif len_li <= 2: + # -[0]3 + x.time = (int(l[i][:2]) * 3600) + else: + return None + used_idxs.append(i) + i += 1 + + assert i == len_l or l[i] == ',' + + i += 1 + + assert i >= len_l + + except (IndexError, ValueError, AssertionError): + return None + + unused_idxs = set(range(len_l)).difference(used_idxs) + res.any_unused_tokens = not {l[n] for n in unused_idxs}.issubset({",",":"}) + return res + + +DEFAULTTZPARSER = _tzparser() + + +def _parsetz(tzstr): + return DEFAULTTZPARSER.parse(tzstr) + + +class ParserError(ValueError): + """Exception subclass used for any failure to parse a datetime string. + + This is a subclass of :py:exc:`ValueError`, and should be raised any time + earlier versions of ``dateutil`` would have raised ``ValueError``. + + .. versionadded:: 2.8.1 + """ + def __str__(self): + try: + return self.args[0] % self.args[1:] + except (TypeError, IndexError): + return super(ParserError, self).__str__() + + def __repr__(self): + args = ", ".join("'%s'" % arg for arg in self.args) + return "%s(%s)" % (self.__class__.__name__, args) + + +class UnknownTimezoneWarning(RuntimeWarning): + """Raised when the parser finds a timezone it cannot parse into a tzinfo. + + .. versionadded:: 2.7.0 + """ +# vim:ts=4:sw=4:et diff --git a/pipenv/vendor/dateutil/parser/isoparser.py b/pipenv/vendor/dateutil/parser/isoparser.py new file mode 100644 index 00000000..4c84bd98 --- /dev/null +++ b/pipenv/vendor/dateutil/parser/isoparser.py @@ -0,0 +1,416 @@ +# -*- coding: utf-8 -*- +""" +This module offers a parser for ISO-8601 strings + +It is intended to support all valid date, time and datetime formats per the +ISO-8601 specification. + +..versionadded:: 2.7.0 +""" +from datetime import datetime, timedelta, time, date +import calendar +from pipenv.vendor.dateutil import tz + +from functools import wraps + +import re +import pipenv.vendor.six as six + +__all__ = ["isoparse", "isoparser"] + + +def _takes_ascii(f): + @wraps(f) + def func(self, str_in, *args, **kwargs): + # If it's a stream, read the whole thing + str_in = getattr(str_in, 'read', lambda: str_in)() + + # If it's unicode, turn it into bytes, since ISO-8601 only covers ASCII + if isinstance(str_in, six.text_type): + # ASCII is the same in UTF-8 + try: + str_in = str_in.encode('ascii') + except UnicodeEncodeError as e: + msg = 'ISO-8601 strings should contain only ASCII characters' + six.raise_from(ValueError(msg), e) + + return f(self, str_in, *args, **kwargs) + + return func + + +class isoparser(object): + def __init__(self, sep=None): + """ + :param sep: + A single character that separates date and time portions. If + ``None``, the parser will accept any single character. + For strict ISO-8601 adherence, pass ``'T'``. + """ + if sep is not None: + if (len(sep) != 1 or ord(sep) >= 128 or sep in '0123456789'): + raise ValueError('Separator must be a single, non-numeric ' + + 'ASCII character') + + sep = sep.encode('ascii') + + self._sep = sep + + @_takes_ascii + def isoparse(self, dt_str): + """ + Parse an ISO-8601 datetime string into a :class:`datetime.datetime`. + + An ISO-8601 datetime string consists of a date portion, followed + optionally by a time portion - the date and time portions are separated + by a single character separator, which is ``T`` in the official + standard. Incomplete date formats (such as ``YYYY-MM``) may *not* be + combined with a time portion. + + Supported date formats are: + + Common: + + - ``YYYY`` + - ``YYYY-MM`` or ``YYYYMM`` + - ``YYYY-MM-DD`` or ``YYYYMMDD`` + + Uncommon: + + - ``YYYY-Www`` or ``YYYYWww`` - ISO week (day defaults to 0) + - ``YYYY-Www-D`` or ``YYYYWwwD`` - ISO week and day + + The ISO week and day numbering follows the same logic as + :func:`datetime.date.isocalendar`. + + Supported time formats are: + + - ``hh`` + - ``hh:mm`` or ``hhmm`` + - ``hh:mm:ss`` or ``hhmmss`` + - ``hh:mm:ss.ssssss`` (Up to 6 sub-second digits) + + Midnight is a special case for `hh`, as the standard supports both + 00:00 and 24:00 as a representation. The decimal separator can be + either a dot or a comma. + + + .. caution:: + + Support for fractional components other than seconds is part of the + ISO-8601 standard, but is not currently implemented in this parser. + + Supported time zone offset formats are: + + - `Z` (UTC) + - `±HH:MM` + - `±HHMM` + - `±HH` + + Offsets will be represented as :class:`dateutil.tz.tzoffset` objects, + with the exception of UTC, which will be represented as + :class:`dateutil.tz.tzutc`. Time zone offsets equivalent to UTC (such + as `+00:00`) will also be represented as :class:`dateutil.tz.tzutc`. + + :param dt_str: + A string or stream containing only an ISO-8601 datetime string + + :return: + Returns a :class:`datetime.datetime` representing the string. + Unspecified components default to their lowest value. + + .. warning:: + + As of version 2.7.0, the strictness of the parser should not be + considered a stable part of the contract. Any valid ISO-8601 string + that parses correctly with the default settings will continue to + parse correctly in future versions, but invalid strings that + currently fail (e.g. ``2017-01-01T00:00+00:00:00``) are not + guaranteed to continue failing in future versions if they encode + a valid date. + + .. versionadded:: 2.7.0 + """ + components, pos = self._parse_isodate(dt_str) + + if len(dt_str) > pos: + if self._sep is None or dt_str[pos:pos + 1] == self._sep: + components += self._parse_isotime(dt_str[pos + 1:]) + else: + raise ValueError('String contains unknown ISO components') + + if len(components) > 3 and components[3] == 24: + components[3] = 0 + return datetime(*components) + timedelta(days=1) + + return datetime(*components) + + @_takes_ascii + def parse_isodate(self, datestr): + """ + Parse the date portion of an ISO string. + + :param datestr: + The string portion of an ISO string, without a separator + + :return: + Returns a :class:`datetime.date` object + """ + components, pos = self._parse_isodate(datestr) + if pos < len(datestr): + raise ValueError('String contains unknown ISO ' + + 'components: {!r}'.format(datestr.decode('ascii'))) + return date(*components) + + @_takes_ascii + def parse_isotime(self, timestr): + """ + Parse the time portion of an ISO string. + + :param timestr: + The time portion of an ISO string, without a separator + + :return: + Returns a :class:`datetime.time` object + """ + components = self._parse_isotime(timestr) + if components[0] == 24: + components[0] = 0 + return time(*components) + + @_takes_ascii + def parse_tzstr(self, tzstr, zero_as_utc=True): + """ + Parse a valid ISO time zone string. + + See :func:`isoparser.isoparse` for details on supported formats. + + :param tzstr: + A string representing an ISO time zone offset + + :param zero_as_utc: + Whether to return :class:`dateutil.tz.tzutc` for zero-offset zones + + :return: + Returns :class:`dateutil.tz.tzoffset` for offsets and + :class:`dateutil.tz.tzutc` for ``Z`` and (if ``zero_as_utc`` is + specified) offsets equivalent to UTC. + """ + return self._parse_tzstr(tzstr, zero_as_utc=zero_as_utc) + + # Constants + _DATE_SEP = b'-' + _TIME_SEP = b':' + _FRACTION_REGEX = re.compile(b'[\\.,]([0-9]+)') + + def _parse_isodate(self, dt_str): + try: + return self._parse_isodate_common(dt_str) + except ValueError: + return self._parse_isodate_uncommon(dt_str) + + def _parse_isodate_common(self, dt_str): + len_str = len(dt_str) + components = [1, 1, 1] + + if len_str < 4: + raise ValueError('ISO string too short') + + # Year + components[0] = int(dt_str[0:4]) + pos = 4 + if pos >= len_str: + return components, pos + + has_sep = dt_str[pos:pos + 1] == self._DATE_SEP + if has_sep: + pos += 1 + + # Month + if len_str - pos < 2: + raise ValueError('Invalid common month') + + components[1] = int(dt_str[pos:pos + 2]) + pos += 2 + + if pos >= len_str: + if has_sep: + return components, pos + else: + raise ValueError('Invalid ISO format') + + if has_sep: + if dt_str[pos:pos + 1] != self._DATE_SEP: + raise ValueError('Invalid separator in ISO string') + pos += 1 + + # Day + if len_str - pos < 2: + raise ValueError('Invalid common day') + components[2] = int(dt_str[pos:pos + 2]) + return components, pos + 2 + + def _parse_isodate_uncommon(self, dt_str): + if len(dt_str) < 4: + raise ValueError('ISO string too short') + + # All ISO formats start with the year + year = int(dt_str[0:4]) + + has_sep = dt_str[4:5] == self._DATE_SEP + + pos = 4 + has_sep # Skip '-' if it's there + if dt_str[pos:pos + 1] == b'W': + # YYYY-?Www-?D? + pos += 1 + weekno = int(dt_str[pos:pos + 2]) + pos += 2 + + dayno = 1 + if len(dt_str) > pos: + if (dt_str[pos:pos + 1] == self._DATE_SEP) != has_sep: + raise ValueError('Inconsistent use of dash separator') + + pos += has_sep + + dayno = int(dt_str[pos:pos + 1]) + pos += 1 + + base_date = self._calculate_weekdate(year, weekno, dayno) + else: + # YYYYDDD or YYYY-DDD + if len(dt_str) - pos < 3: + raise ValueError('Invalid ordinal day') + + ordinal_day = int(dt_str[pos:pos + 3]) + pos += 3 + + if ordinal_day < 1 or ordinal_day > (365 + calendar.isleap(year)): + raise ValueError('Invalid ordinal day' + + ' {} for year {}'.format(ordinal_day, year)) + + base_date = date(year, 1, 1) + timedelta(days=ordinal_day - 1) + + components = [base_date.year, base_date.month, base_date.day] + return components, pos + + def _calculate_weekdate(self, year, week, day): + """ + Calculate the day of corresponding to the ISO year-week-day calendar. + + This function is effectively the inverse of + :func:`datetime.date.isocalendar`. + + :param year: + The year in the ISO calendar + + :param week: + The week in the ISO calendar - range is [1, 53] + + :param day: + The day in the ISO calendar - range is [1 (MON), 7 (SUN)] + + :return: + Returns a :class:`datetime.date` + """ + if not 0 < week < 54: + raise ValueError('Invalid week: {}'.format(week)) + + if not 0 < day < 8: # Range is 1-7 + raise ValueError('Invalid weekday: {}'.format(day)) + + # Get week 1 for the specific year: + jan_4 = date(year, 1, 4) # Week 1 always has January 4th in it + week_1 = jan_4 - timedelta(days=jan_4.isocalendar()[2] - 1) + + # Now add the specific number of weeks and days to get what we want + week_offset = (week - 1) * 7 + (day - 1) + return week_1 + timedelta(days=week_offset) + + def _parse_isotime(self, timestr): + len_str = len(timestr) + components = [0, 0, 0, 0, None] + pos = 0 + comp = -1 + + if len_str < 2: + raise ValueError('ISO time too short') + + has_sep = False + + while pos < len_str and comp < 5: + comp += 1 + + if timestr[pos:pos + 1] in b'-+Zz': + # Detect time zone boundary + components[-1] = self._parse_tzstr(timestr[pos:]) + pos = len_str + break + + if comp == 1 and timestr[pos:pos+1] == self._TIME_SEP: + has_sep = True + pos += 1 + elif comp == 2 and has_sep: + if timestr[pos:pos+1] != self._TIME_SEP: + raise ValueError('Inconsistent use of colon separator') + pos += 1 + + if comp < 3: + # Hour, minute, second + components[comp] = int(timestr[pos:pos + 2]) + pos += 2 + + if comp == 3: + # Fraction of a second + frac = self._FRACTION_REGEX.match(timestr[pos:]) + if not frac: + continue + + us_str = frac.group(1)[:6] # Truncate to microseconds + components[comp] = int(us_str) * 10**(6 - len(us_str)) + pos += len(frac.group()) + + if pos < len_str: + raise ValueError('Unused components in ISO string') + + if components[0] == 24: + # Standard supports 00:00 and 24:00 as representations of midnight + if any(component != 0 for component in components[1:4]): + raise ValueError('Hour may only be 24 at 24:00:00.000') + + return components + + def _parse_tzstr(self, tzstr, zero_as_utc=True): + if tzstr == b'Z' or tzstr == b'z': + return tz.UTC + + if len(tzstr) not in {3, 5, 6}: + raise ValueError('Time zone offset must be 1, 3, 5 or 6 characters') + + if tzstr[0:1] == b'-': + mult = -1 + elif tzstr[0:1] == b'+': + mult = 1 + else: + raise ValueError('Time zone offset requires sign') + + hours = int(tzstr[1:3]) + if len(tzstr) == 3: + minutes = 0 + else: + minutes = int(tzstr[(4 if tzstr[3:4] == self._TIME_SEP else 3):]) + + if zero_as_utc and hours == 0 and minutes == 0: + return tz.UTC + else: + if minutes > 59: + raise ValueError('Invalid minutes in time zone offset') + + if hours > 23: + raise ValueError('Invalid hours in time zone offset') + + return tz.tzoffset(None, mult * (hours * 60 + minutes) * 60) + + +DEFAULT_ISOPARSER = isoparser() +isoparse = DEFAULT_ISOPARSER.isoparse diff --git a/pipenv/vendor/dateutil/relativedelta.py b/pipenv/vendor/dateutil/relativedelta.py new file mode 100644 index 00000000..452884fb --- /dev/null +++ b/pipenv/vendor/dateutil/relativedelta.py @@ -0,0 +1,599 @@ +# -*- coding: utf-8 -*- +import datetime +import calendar + +import operator +from math import copysign + +from pipenv.vendor.six import integer_types +from warnings import warn + +from ._common import weekday + +MO, TU, WE, TH, FR, SA, SU = weekdays = tuple(weekday(x) for x in range(7)) + +__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"] + + +class relativedelta(object): + """ + The relativedelta type is designed to be applied to an existing datetime and + can replace specific components of that datetime, or represents an interval + of time. + + It is based on the specification of the excellent work done by M.-A. Lemburg + in his + `mx.DateTime <https://www.egenix.com/products/python/mxBase/mxDateTime/>`_ extension. + However, notice that this type does *NOT* implement the same algorithm as + his work. Do *NOT* expect it to behave like mx.DateTime's counterpart. + + There are two different ways to build a relativedelta instance. The + first one is passing it two date/datetime classes:: + + relativedelta(datetime1, datetime2) + + The second one is passing it any number of the following keyword arguments:: + + relativedelta(arg1=x,arg2=y,arg3=z...) + + year, month, day, hour, minute, second, microsecond: + Absolute information (argument is singular); adding or subtracting a + relativedelta with absolute information does not perform an arithmetic + operation, but rather REPLACES the corresponding value in the + original datetime with the value(s) in relativedelta. + + years, months, weeks, days, hours, minutes, seconds, microseconds: + Relative information, may be negative (argument is plural); adding + or subtracting a relativedelta with relative information performs + the corresponding arithmetic operation on the original datetime value + with the information in the relativedelta. + + weekday: + One of the weekday instances (MO, TU, etc) available in the + relativedelta module. These instances may receive a parameter N, + specifying the Nth weekday, which could be positive or negative + (like MO(+1) or MO(-2)). Not specifying it is the same as specifying + +1. You can also use an integer, where 0=MO. This argument is always + relative e.g. if the calculated date is already Monday, using MO(1) + or MO(-1) won't change the day. To effectively make it absolute, use + it in combination with the day argument (e.g. day=1, MO(1) for first + Monday of the month). + + leapdays: + Will add given days to the date found, if year is a leap + year, and the date found is post 28 of february. + + yearday, nlyearday: + Set the yearday or the non-leap year day (jump leap days). + These are converted to day/month/leapdays information. + + There are relative and absolute forms of the keyword + arguments. The plural is relative, and the singular is + absolute. For each argument in the order below, the absolute form + is applied first (by setting each attribute to that value) and + then the relative form (by adding the value to the attribute). + + The order of attributes considered when this relativedelta is + added to a datetime is: + + 1. Year + 2. Month + 3. Day + 4. Hours + 5. Minutes + 6. Seconds + 7. Microseconds + + Finally, weekday is applied, using the rule described above. + + For example + + >>> from datetime import datetime + >>> from dateutil.relativedelta import relativedelta, MO + >>> dt = datetime(2018, 4, 9, 13, 37, 0) + >>> delta = relativedelta(hours=25, day=1, weekday=MO(1)) + >>> dt + delta + datetime.datetime(2018, 4, 2, 14, 37) + + First, the day is set to 1 (the first of the month), then 25 hours + are added, to get to the 2nd day and 14th hour, finally the + weekday is applied, but since the 2nd is already a Monday there is + no effect. + + """ + + def __init__(self, dt1=None, dt2=None, + years=0, months=0, days=0, leapdays=0, weeks=0, + hours=0, minutes=0, seconds=0, microseconds=0, + year=None, month=None, day=None, weekday=None, + yearday=None, nlyearday=None, + hour=None, minute=None, second=None, microsecond=None): + + if dt1 and dt2: + # datetime is a subclass of date. So both must be date + if not (isinstance(dt1, datetime.date) and + isinstance(dt2, datetime.date)): + raise TypeError("relativedelta only diffs datetime/date") + + # We allow two dates, or two datetimes, so we coerce them to be + # of the same type + if (isinstance(dt1, datetime.datetime) != + isinstance(dt2, datetime.datetime)): + if not isinstance(dt1, datetime.datetime): + dt1 = datetime.datetime.fromordinal(dt1.toordinal()) + elif not isinstance(dt2, datetime.datetime): + dt2 = datetime.datetime.fromordinal(dt2.toordinal()) + + self.years = 0 + self.months = 0 + self.days = 0 + self.leapdays = 0 + self.hours = 0 + self.minutes = 0 + self.seconds = 0 + self.microseconds = 0 + self.year = None + self.month = None + self.day = None + self.weekday = None + self.hour = None + self.minute = None + self.second = None + self.microsecond = None + self._has_time = 0 + + # Get year / month delta between the two + months = (dt1.year - dt2.year) * 12 + (dt1.month - dt2.month) + self._set_months(months) + + # Remove the year/month delta so the timedelta is just well-defined + # time units (seconds, days and microseconds) + dtm = self.__radd__(dt2) + + # If we've overshot our target, make an adjustment + if dt1 < dt2: + compare = operator.gt + increment = 1 + else: + compare = operator.lt + increment = -1 + + while compare(dt1, dtm): + months += increment + self._set_months(months) + dtm = self.__radd__(dt2) + + # Get the timedelta between the "months-adjusted" date and dt1 + delta = dt1 - dtm + self.seconds = delta.seconds + delta.days * 86400 + self.microseconds = delta.microseconds + else: + # Check for non-integer values in integer-only quantities + if any(x is not None and x != int(x) for x in (years, months)): + raise ValueError("Non-integer years and months are " + "ambiguous and not currently supported.") + + # Relative information + self.years = int(years) + self.months = int(months) + self.days = days + weeks * 7 + self.leapdays = leapdays + self.hours = hours + self.minutes = minutes + self.seconds = seconds + self.microseconds = microseconds + + # Absolute information + self.year = year + self.month = month + self.day = day + self.hour = hour + self.minute = minute + self.second = second + self.microsecond = microsecond + + if any(x is not None and int(x) != x + for x in (year, month, day, hour, + minute, second, microsecond)): + # For now we'll deprecate floats - later it'll be an error. + warn("Non-integer value passed as absolute information. " + + "This is not a well-defined condition and will raise " + + "errors in future versions.", DeprecationWarning) + + if isinstance(weekday, integer_types): + self.weekday = weekdays[weekday] + else: + self.weekday = weekday + + yday = 0 + if nlyearday: + yday = nlyearday + elif yearday: + yday = yearday + if yearday > 59: + self.leapdays = -1 + if yday: + ydayidx = [31, 59, 90, 120, 151, 181, 212, + 243, 273, 304, 334, 366] + for idx, ydays in enumerate(ydayidx): + if yday <= ydays: + self.month = idx+1 + if idx == 0: + self.day = yday + else: + self.day = yday-ydayidx[idx-1] + break + else: + raise ValueError("invalid year day (%d)" % yday) + + self._fix() + + def _fix(self): + if abs(self.microseconds) > 999999: + s = _sign(self.microseconds) + div, mod = divmod(self.microseconds * s, 1000000) + self.microseconds = mod * s + self.seconds += div * s + if abs(self.seconds) > 59: + s = _sign(self.seconds) + div, mod = divmod(self.seconds * s, 60) + self.seconds = mod * s + self.minutes += div * s + if abs(self.minutes) > 59: + s = _sign(self.minutes) + div, mod = divmod(self.minutes * s, 60) + self.minutes = mod * s + self.hours += div * s + if abs(self.hours) > 23: + s = _sign(self.hours) + div, mod = divmod(self.hours * s, 24) + self.hours = mod * s + self.days += div * s + if abs(self.months) > 11: + s = _sign(self.months) + div, mod = divmod(self.months * s, 12) + self.months = mod * s + self.years += div * s + if (self.hours or self.minutes or self.seconds or self.microseconds + or self.hour is not None or self.minute is not None or + self.second is not None or self.microsecond is not None): + self._has_time = 1 + else: + self._has_time = 0 + + @property + def weeks(self): + return int(self.days / 7.0) + + @weeks.setter + def weeks(self, value): + self.days = self.days - (self.weeks * 7) + value * 7 + + def _set_months(self, months): + self.months = months + if abs(self.months) > 11: + s = _sign(self.months) + div, mod = divmod(self.months * s, 12) + self.months = mod * s + self.years = div * s + else: + self.years = 0 + + def normalized(self): + """ + Return a version of this object represented entirely using integer + values for the relative attributes. + + >>> relativedelta(days=1.5, hours=2).normalized() + relativedelta(days=+1, hours=+14) + + :return: + Returns a :class:`dateutil.relativedelta.relativedelta` object. + """ + # Cascade remainders down (rounding each to roughly nearest microsecond) + days = int(self.days) + + hours_f = round(self.hours + 24 * (self.days - days), 11) + hours = int(hours_f) + + minutes_f = round(self.minutes + 60 * (hours_f - hours), 10) + minutes = int(minutes_f) + + seconds_f = round(self.seconds + 60 * (minutes_f - minutes), 8) + seconds = int(seconds_f) + + microseconds = round(self.microseconds + 1e6 * (seconds_f - seconds)) + + # Constructor carries overflow back up with call to _fix() + return self.__class__(years=self.years, months=self.months, + days=days, hours=hours, minutes=minutes, + seconds=seconds, microseconds=microseconds, + leapdays=self.leapdays, year=self.year, + month=self.month, day=self.day, + weekday=self.weekday, hour=self.hour, + minute=self.minute, second=self.second, + microsecond=self.microsecond) + + def __add__(self, other): + if isinstance(other, relativedelta): + return self.__class__(years=other.years + self.years, + months=other.months + self.months, + days=other.days + self.days, + hours=other.hours + self.hours, + minutes=other.minutes + self.minutes, + seconds=other.seconds + self.seconds, + microseconds=(other.microseconds + + self.microseconds), + leapdays=other.leapdays or self.leapdays, + year=(other.year if other.year is not None + else self.year), + month=(other.month if other.month is not None + else self.month), + day=(other.day if other.day is not None + else self.day), + weekday=(other.weekday if other.weekday is not None + else self.weekday), + hour=(other.hour if other.hour is not None + else self.hour), + minute=(other.minute if other.minute is not None + else self.minute), + second=(other.second if other.second is not None + else self.second), + microsecond=(other.microsecond if other.microsecond + is not None else + self.microsecond)) + if isinstance(other, datetime.timedelta): + return self.__class__(years=self.years, + months=self.months, + days=self.days + other.days, + hours=self.hours, + minutes=self.minutes, + seconds=self.seconds + other.seconds, + microseconds=self.microseconds + other.microseconds, + leapdays=self.leapdays, + year=self.year, + month=self.month, + day=self.day, + weekday=self.weekday, + hour=self.hour, + minute=self.minute, + second=self.second, + microsecond=self.microsecond) + if not isinstance(other, datetime.date): + return NotImplemented + elif self._has_time and not isinstance(other, datetime.datetime): + other = datetime.datetime.fromordinal(other.toordinal()) + year = (self.year or other.year)+self.years + month = self.month or other.month + if self.months: + assert 1 <= abs(self.months) <= 12 + month += self.months + if month > 12: + year += 1 + month -= 12 + elif month < 1: + year -= 1 + month += 12 + day = min(calendar.monthrange(year, month)[1], + self.day or other.day) + repl = {"year": year, "month": month, "day": day} + for attr in ["hour", "minute", "second", "microsecond"]: + value = getattr(self, attr) + if value is not None: + repl[attr] = value + days = self.days + if self.leapdays and month > 2 and calendar.isleap(year): + days += self.leapdays + ret = (other.replace(**repl) + + datetime.timedelta(days=days, + hours=self.hours, + minutes=self.minutes, + seconds=self.seconds, + microseconds=self.microseconds)) + if self.weekday: + weekday, nth = self.weekday.weekday, self.weekday.n or 1 + jumpdays = (abs(nth) - 1) * 7 + if nth > 0: + jumpdays += (7 - ret.weekday() + weekday) % 7 + else: + jumpdays += (ret.weekday() - weekday) % 7 + jumpdays *= -1 + ret += datetime.timedelta(days=jumpdays) + return ret + + def __radd__(self, other): + return self.__add__(other) + + def __rsub__(self, other): + return self.__neg__().__radd__(other) + + def __sub__(self, other): + if not isinstance(other, relativedelta): + return NotImplemented # In case the other object defines __rsub__ + return self.__class__(years=self.years - other.years, + months=self.months - other.months, + days=self.days - other.days, + hours=self.hours - other.hours, + minutes=self.minutes - other.minutes, + seconds=self.seconds - other.seconds, + microseconds=self.microseconds - other.microseconds, + leapdays=self.leapdays or other.leapdays, + year=(self.year if self.year is not None + else other.year), + month=(self.month if self.month is not None else + other.month), + day=(self.day if self.day is not None else + other.day), + weekday=(self.weekday if self.weekday is not None else + other.weekday), + hour=(self.hour if self.hour is not None else + other.hour), + minute=(self.minute if self.minute is not None else + other.minute), + second=(self.second if self.second is not None else + other.second), + microsecond=(self.microsecond if self.microsecond + is not None else + other.microsecond)) + + def __abs__(self): + return self.__class__(years=abs(self.years), + months=abs(self.months), + days=abs(self.days), + hours=abs(self.hours), + minutes=abs(self.minutes), + seconds=abs(self.seconds), + microseconds=abs(self.microseconds), + leapdays=self.leapdays, + year=self.year, + month=self.month, + day=self.day, + weekday=self.weekday, + hour=self.hour, + minute=self.minute, + second=self.second, + microsecond=self.microsecond) + + def __neg__(self): + return self.__class__(years=-self.years, + months=-self.months, + days=-self.days, + hours=-self.hours, + minutes=-self.minutes, + seconds=-self.seconds, + microseconds=-self.microseconds, + leapdays=self.leapdays, + year=self.year, + month=self.month, + day=self.day, + weekday=self.weekday, + hour=self.hour, + minute=self.minute, + second=self.second, + microsecond=self.microsecond) + + def __bool__(self): + return not (not self.years and + not self.months and + not self.days and + not self.hours and + not self.minutes and + not self.seconds and + not self.microseconds and + not self.leapdays and + self.year is None and + self.month is None and + self.day is None and + self.weekday is None and + self.hour is None and + self.minute is None and + self.second is None and + self.microsecond is None) + # Compatibility with Python 2.x + __nonzero__ = __bool__ + + def __mul__(self, other): + try: + f = float(other) + except TypeError: + return NotImplemented + + return self.__class__(years=int(self.years * f), + months=int(self.months * f), + days=int(self.days * f), + hours=int(self.hours * f), + minutes=int(self.minutes * f), + seconds=int(self.seconds * f), + microseconds=int(self.microseconds * f), + leapdays=self.leapdays, + year=self.year, + month=self.month, + day=self.day, + weekday=self.weekday, + hour=self.hour, + minute=self.minute, + second=self.second, + microsecond=self.microsecond) + + __rmul__ = __mul__ + + def __eq__(self, other): + if not isinstance(other, relativedelta): + return NotImplemented + if self.weekday or other.weekday: + if not self.weekday or not other.weekday: + return False + if self.weekday.weekday != other.weekday.weekday: + return False + n1, n2 = self.weekday.n, other.weekday.n + if n1 != n2 and not ((not n1 or n1 == 1) and (not n2 or n2 == 1)): + return False + return (self.years == other.years and + self.months == other.months and + self.days == other.days and + self.hours == other.hours and + self.minutes == other.minutes and + self.seconds == other.seconds and + self.microseconds == other.microseconds and + self.leapdays == other.leapdays and + self.year == other.year and + self.month == other.month and + self.day == other.day and + self.hour == other.hour and + self.minute == other.minute and + self.second == other.second and + self.microsecond == other.microsecond) + + def __hash__(self): + return hash(( + self.weekday, + self.years, + self.months, + self.days, + self.hours, + self.minutes, + self.seconds, + self.microseconds, + self.leapdays, + self.year, + self.month, + self.day, + self.hour, + self.minute, + self.second, + self.microsecond, + )) + + def __ne__(self, other): + return not self.__eq__(other) + + def __div__(self, other): + try: + reciprocal = 1 / float(other) + except TypeError: + return NotImplemented + + return self.__mul__(reciprocal) + + __truediv__ = __div__ + + def __repr__(self): + l = [] + for attr in ["years", "months", "days", "leapdays", + "hours", "minutes", "seconds", "microseconds"]: + value = getattr(self, attr) + if value: + l.append("{attr}={value:+g}".format(attr=attr, value=value)) + for attr in ["year", "month", "day", "weekday", + "hour", "minute", "second", "microsecond"]: + value = getattr(self, attr) + if value is not None: + l.append("{attr}={value}".format(attr=attr, value=repr(value))) + return "{classname}({attrs})".format(classname=self.__class__.__name__, + attrs=", ".join(l)) + + +def _sign(x): + return int(copysign(1, x)) + +# vim:ts=4:sw=4:et diff --git a/pipenv/vendor/dateutil/rrule.py b/pipenv/vendor/dateutil/rrule.py new file mode 100644 index 00000000..168e9449 --- /dev/null +++ b/pipenv/vendor/dateutil/rrule.py @@ -0,0 +1,1737 @@ +# -*- coding: utf-8 -*- +""" +The rrule module offers a small, complete, and very fast, implementation of +the recurrence rules documented in the +`iCalendar RFC <https://tools.ietf.org/html/rfc5545>`_, +including support for caching of results. +""" +import calendar +import datetime +import heapq +import itertools +import re +import sys +from functools import wraps +# For warning about deprecation of until and count +from warnings import warn + +from pipenv.vendor.six import advance_iterator, integer_types + +from pipenv.vendor.six.moves import _thread, range + +from ._common import weekday as weekdaybase + +try: + from math import gcd +except ImportError: + from fractions import gcd + +__all__ = ["rrule", "rruleset", "rrulestr", + "YEARLY", "MONTHLY", "WEEKLY", "DAILY", + "HOURLY", "MINUTELY", "SECONDLY", + "MO", "TU", "WE", "TH", "FR", "SA", "SU"] + +# Every mask is 7 days longer to handle cross-year weekly periods. +M366MASK = tuple([1]*31+[2]*29+[3]*31+[4]*30+[5]*31+[6]*30 + + [7]*31+[8]*31+[9]*30+[10]*31+[11]*30+[12]*31+[1]*7) +M365MASK = list(M366MASK) +M29, M30, M31 = list(range(1, 30)), list(range(1, 31)), list(range(1, 32)) +MDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7]) +MDAY365MASK = list(MDAY366MASK) +M29, M30, M31 = list(range(-29, 0)), list(range(-30, 0)), list(range(-31, 0)) +NMDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7]) +NMDAY365MASK = list(NMDAY366MASK) +M366RANGE = (0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366) +M365RANGE = (0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365) +WDAYMASK = [0, 1, 2, 3, 4, 5, 6]*55 +del M29, M30, M31, M365MASK[59], MDAY365MASK[59], NMDAY365MASK[31] +MDAY365MASK = tuple(MDAY365MASK) +M365MASK = tuple(M365MASK) + +FREQNAMES = ['YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY', 'HOURLY', 'MINUTELY', 'SECONDLY'] + +(YEARLY, + MONTHLY, + WEEKLY, + DAILY, + HOURLY, + MINUTELY, + SECONDLY) = list(range(7)) + +# Imported on demand. +easter = None +parser = None + + +class weekday(weekdaybase): + """ + This version of weekday does not allow n = 0. + """ + def __init__(self, wkday, n=None): + if n == 0: + raise ValueError("Can't create weekday with n==0") + + super(weekday, self).__init__(wkday, n) + + +MO, TU, WE, TH, FR, SA, SU = weekdays = tuple(weekday(x) for x in range(7)) + + +def _invalidates_cache(f): + """ + Decorator for rruleset methods which may invalidate the + cached length. + """ + @wraps(f) + def inner_func(self, *args, **kwargs): + rv = f(self, *args, **kwargs) + self._invalidate_cache() + return rv + + return inner_func + + +class rrulebase(object): + def __init__(self, cache=False): + if cache: + self._cache = [] + self._cache_lock = _thread.allocate_lock() + self._invalidate_cache() + else: + self._cache = None + self._cache_complete = False + self._len = None + + def __iter__(self): + if self._cache_complete: + return iter(self._cache) + elif self._cache is None: + return self._iter() + else: + return self._iter_cached() + + def _invalidate_cache(self): + if self._cache is not None: + self._cache = [] + self._cache_complete = False + self._cache_gen = self._iter() + + if self._cache_lock.locked(): + self._cache_lock.release() + + self._len = None + + def _iter_cached(self): + i = 0 + gen = self._cache_gen + cache = self._cache + acquire = self._cache_lock.acquire + release = self._cache_lock.release + while gen: + if i == len(cache): + acquire() + if self._cache_complete: + break + try: + for j in range(10): + cache.append(advance_iterator(gen)) + except StopIteration: + self._cache_gen = gen = None + self._cache_complete = True + break + release() + yield cache[i] + i += 1 + while i < self._len: + yield cache[i] + i += 1 + + def __getitem__(self, item): + if self._cache_complete: + return self._cache[item] + elif isinstance(item, slice): + if item.step and item.step < 0: + return list(iter(self))[item] + else: + return list(itertools.islice(self, + item.start or 0, + item.stop or sys.maxsize, + item.step or 1)) + elif item >= 0: + gen = iter(self) + try: + for i in range(item+1): + res = advance_iterator(gen) + except StopIteration: + raise IndexError + return res + else: + return list(iter(self))[item] + + def __contains__(self, item): + if self._cache_complete: + return item in self._cache + else: + for i in self: + if i == item: + return True + elif i > item: + return False + return False + + # __len__() introduces a large performance penalty. + def count(self): + """ Returns the number of recurrences in this set. It will have go + trough the whole recurrence, if this hasn't been done before. """ + if self._len is None: + for x in self: + pass + return self._len + + def before(self, dt, inc=False): + """ Returns the last recurrence before the given datetime instance. The + inc keyword defines what happens if dt is an occurrence. With + inc=True, if dt itself is an occurrence, it will be returned. """ + if self._cache_complete: + gen = self._cache + else: + gen = self + last = None + if inc: + for i in gen: + if i > dt: + break + last = i + else: + for i in gen: + if i >= dt: + break + last = i + return last + + def after(self, dt, inc=False): + """ Returns the first recurrence after the given datetime instance. The + inc keyword defines what happens if dt is an occurrence. With + inc=True, if dt itself is an occurrence, it will be returned. """ + if self._cache_complete: + gen = self._cache + else: + gen = self + if inc: + for i in gen: + if i >= dt: + return i + else: + for i in gen: + if i > dt: + return i + return None + + def xafter(self, dt, count=None, inc=False): + """ + Generator which yields up to `count` recurrences after the given + datetime instance, equivalent to `after`. + + :param dt: + The datetime at which to start generating recurrences. + + :param count: + The maximum number of recurrences to generate. If `None` (default), + dates are generated until the recurrence rule is exhausted. + + :param inc: + If `dt` is an instance of the rule and `inc` is `True`, it is + included in the output. + + :yields: Yields a sequence of `datetime` objects. + """ + + if self._cache_complete: + gen = self._cache + else: + gen = self + + # Select the comparison function + if inc: + comp = lambda dc, dtc: dc >= dtc + else: + comp = lambda dc, dtc: dc > dtc + + # Generate dates + n = 0 + for d in gen: + if comp(d, dt): + if count is not None: + n += 1 + if n > count: + break + + yield d + + def between(self, after, before, inc=False, count=1): + """ Returns all the occurrences of the rrule between after and before. + The inc keyword defines what happens if after and/or before are + themselves occurrences. With inc=True, they will be included in the + list, if they are found in the recurrence set. """ + if self._cache_complete: + gen = self._cache + else: + gen = self + started = False + l = [] + if inc: + for i in gen: + if i > before: + break + elif not started: + if i >= after: + started = True + l.append(i) + else: + l.append(i) + else: + for i in gen: + if i >= before: + break + elif not started: + if i > after: + started = True + l.append(i) + else: + l.append(i) + return l + + +class rrule(rrulebase): + """ + That's the base of the rrule operation. It accepts all the keywords + defined in the RFC as its constructor parameters (except byday, + which was renamed to byweekday) and more. The constructor prototype is:: + + rrule(freq) + + Where freq must be one of YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, + or SECONDLY. + + .. note:: + Per RFC section 3.3.10, recurrence instances falling on invalid dates + and times are ignored rather than coerced: + + Recurrence rules may generate recurrence instances with an invalid + date (e.g., February 30) or nonexistent local time (e.g., 1:30 AM + on a day where the local time is moved forward by an hour at 1:00 + AM). Such recurrence instances MUST be ignored and MUST NOT be + counted as part of the recurrence set. + + This can lead to possibly surprising behavior when, for example, the + start date occurs at the end of the month: + + >>> from dateutil.rrule import rrule, MONTHLY + >>> from datetime import datetime + >>> start_date = datetime(2014, 12, 31) + >>> list(rrule(freq=MONTHLY, count=4, dtstart=start_date)) + ... # doctest: +NORMALIZE_WHITESPACE + [datetime.datetime(2014, 12, 31, 0, 0), + datetime.datetime(2015, 1, 31, 0, 0), + datetime.datetime(2015, 3, 31, 0, 0), + datetime.datetime(2015, 5, 31, 0, 0)] + + Additionally, it supports the following keyword arguments: + + :param dtstart: + The recurrence start. Besides being the base for the recurrence, + missing parameters in the final recurrence instances will also be + extracted from this date. If not given, datetime.now() will be used + instead. + :param interval: + The interval between each freq iteration. For example, when using + YEARLY, an interval of 2 means once every two years, but with HOURLY, + it means once every two hours. The default interval is 1. + :param wkst: + The week start day. Must be one of the MO, TU, WE constants, or an + integer, specifying the first day of the week. This will affect + recurrences based on weekly periods. The default week start is got + from calendar.firstweekday(), and may be modified by + calendar.setfirstweekday(). + :param count: + If given, this determines how many occurrences will be generated. + + .. note:: + As of version 2.5.0, the use of the keyword ``until`` in conjunction + with ``count`` is deprecated, to make sure ``dateutil`` is fully + compliant with `RFC-5545 Sec. 3.3.10 <https://tools.ietf.org/ + html/rfc5545#section-3.3.10>`_. Therefore, ``until`` and ``count`` + **must not** occur in the same call to ``rrule``. + :param until: + If given, this must be a datetime instance specifying the upper-bound + limit of the recurrence. The last recurrence in the rule is the greatest + datetime that is less than or equal to the value specified in the + ``until`` parameter. + + .. note:: + As of version 2.5.0, the use of the keyword ``until`` in conjunction + with ``count`` is deprecated, to make sure ``dateutil`` is fully + compliant with `RFC-5545 Sec. 3.3.10 <https://tools.ietf.org/ + html/rfc5545#section-3.3.10>`_. Therefore, ``until`` and ``count`` + **must not** occur in the same call to ``rrule``. + :param bysetpos: + If given, it must be either an integer, or a sequence of integers, + positive or negative. Each given integer will specify an occurrence + number, corresponding to the nth occurrence of the rule inside the + frequency period. For example, a bysetpos of -1 if combined with a + MONTHLY frequency, and a byweekday of (MO, TU, WE, TH, FR), will + result in the last work day of every month. + :param bymonth: + If given, it must be either an integer, or a sequence of integers, + meaning the months to apply the recurrence to. + :param bymonthday: + If given, it must be either an integer, or a sequence of integers, + meaning the month days to apply the recurrence to. + :param byyearday: + If given, it must be either an integer, or a sequence of integers, + meaning the year days to apply the recurrence to. + :param byeaster: + If given, it must be either an integer, or a sequence of integers, + positive or negative. Each integer will define an offset from the + Easter Sunday. Passing the offset 0 to byeaster will yield the Easter + Sunday itself. This is an extension to the RFC specification. + :param byweekno: + If given, it must be either an integer, or a sequence of integers, + meaning the week numbers to apply the recurrence to. Week numbers + have the meaning described in ISO8601, that is, the first week of + the year is that containing at least four days of the new year. + :param byweekday: + If given, it must be either an integer (0 == MO), a sequence of + integers, one of the weekday constants (MO, TU, etc), or a sequence + of these constants. When given, these variables will define the + weekdays where the recurrence will be applied. It's also possible to + use an argument n for the weekday instances, which will mean the nth + occurrence of this weekday in the period. For example, with MONTHLY, + or with YEARLY and BYMONTH, using FR(+1) in byweekday will specify the + first friday of the month where the recurrence happens. Notice that in + the RFC documentation, this is specified as BYDAY, but was renamed to + avoid the ambiguity of that keyword. + :param byhour: + If given, it must be either an integer, or a sequence of integers, + meaning the hours to apply the recurrence to. + :param byminute: + If given, it must be either an integer, or a sequence of integers, + meaning the minutes to apply the recurrence to. + :param bysecond: + If given, it must be either an integer, or a sequence of integers, + meaning the seconds to apply the recurrence to. + :param cache: + If given, it must be a boolean value specifying to enable or disable + caching of results. If you will use the same rrule instance multiple + times, enabling caching will improve the performance considerably. + """ + def __init__(self, freq, dtstart=None, + interval=1, wkst=None, count=None, until=None, bysetpos=None, + bymonth=None, bymonthday=None, byyearday=None, byeaster=None, + byweekno=None, byweekday=None, + byhour=None, byminute=None, bysecond=None, + cache=False): + super(rrule, self).__init__(cache) + global easter + if not dtstart: + if until and until.tzinfo: + dtstart = datetime.datetime.now(tz=until.tzinfo).replace(microsecond=0) + else: + dtstart = datetime.datetime.now().replace(microsecond=0) + elif not isinstance(dtstart, datetime.datetime): + dtstart = datetime.datetime.fromordinal(dtstart.toordinal()) + else: + dtstart = dtstart.replace(microsecond=0) + self._dtstart = dtstart + self._tzinfo = dtstart.tzinfo + self._freq = freq + self._interval = interval + self._count = count + + # Cache the original byxxx rules, if they are provided, as the _byxxx + # attributes do not necessarily map to the inputs, and this can be + # a problem in generating the strings. Only store things if they've + # been supplied (the string retrieval will just use .get()) + self._original_rule = {} + + if until and not isinstance(until, datetime.datetime): + until = datetime.datetime.fromordinal(until.toordinal()) + self._until = until + + if self._dtstart and self._until: + if (self._dtstart.tzinfo is not None) != (self._until.tzinfo is not None): + # According to RFC5545 Section 3.3.10: + # https://tools.ietf.org/html/rfc5545#section-3.3.10 + # + # > If the "DTSTART" property is specified as a date with UTC + # > time or a date with local time and time zone reference, + # > then the UNTIL rule part MUST be specified as a date with + # > UTC time. + raise ValueError( + 'RRULE UNTIL values must be specified in UTC when DTSTART ' + 'is timezone-aware' + ) + + if count is not None and until: + warn("Using both 'count' and 'until' is inconsistent with RFC 5545" + " and has been deprecated in dateutil. Future versions will " + "raise an error.", DeprecationWarning) + + if wkst is None: + self._wkst = calendar.firstweekday() + elif isinstance(wkst, integer_types): + self._wkst = wkst + else: + self._wkst = wkst.weekday + + if bysetpos is None: + self._bysetpos = None + elif isinstance(bysetpos, integer_types): + if bysetpos == 0 or not (-366 <= bysetpos <= 366): + raise ValueError("bysetpos must be between 1 and 366, " + "or between -366 and -1") + self._bysetpos = (bysetpos,) + else: + self._bysetpos = tuple(bysetpos) + for pos in self._bysetpos: + if pos == 0 or not (-366 <= pos <= 366): + raise ValueError("bysetpos must be between 1 and 366, " + "or between -366 and -1") + + if self._bysetpos: + self._original_rule['bysetpos'] = self._bysetpos + + if (byweekno is None and byyearday is None and bymonthday is None and + byweekday is None and byeaster is None): + if freq == YEARLY: + if bymonth is None: + bymonth = dtstart.month + self._original_rule['bymonth'] = None + bymonthday = dtstart.day + self._original_rule['bymonthday'] = None + elif freq == MONTHLY: + bymonthday = dtstart.day + self._original_rule['bymonthday'] = None + elif freq == WEEKLY: + byweekday = dtstart.weekday() + self._original_rule['byweekday'] = None + + # bymonth + if bymonth is None: + self._bymonth = None + else: + if isinstance(bymonth, integer_types): + bymonth = (bymonth,) + + self._bymonth = tuple(sorted(set(bymonth))) + + if 'bymonth' not in self._original_rule: + self._original_rule['bymonth'] = self._bymonth + + # byyearday + if byyearday is None: + self._byyearday = None + else: + if isinstance(byyearday, integer_types): + byyearday = (byyearday,) + + self._byyearday = tuple(sorted(set(byyearday))) + self._original_rule['byyearday'] = self._byyearday + + # byeaster + if byeaster is not None: + if not easter: + from pipenv.vendor.dateutil import easter + if isinstance(byeaster, integer_types): + self._byeaster = (byeaster,) + else: + self._byeaster = tuple(sorted(byeaster)) + + self._original_rule['byeaster'] = self._byeaster + else: + self._byeaster = None + + # bymonthday + if bymonthday is None: + self._bymonthday = () + self._bynmonthday = () + else: + if isinstance(bymonthday, integer_types): + bymonthday = (bymonthday,) + + bymonthday = set(bymonthday) # Ensure it's unique + + self._bymonthday = tuple(sorted(x for x in bymonthday if x > 0)) + self._bynmonthday = tuple(sorted(x for x in bymonthday if x < 0)) + + # Storing positive numbers first, then negative numbers + if 'bymonthday' not in self._original_rule: + self._original_rule['bymonthday'] = tuple( + itertools.chain(self._bymonthday, self._bynmonthday)) + + # byweekno + if byweekno is None: + self._byweekno = None + else: + if isinstance(byweekno, integer_types): + byweekno = (byweekno,) + + self._byweekno = tuple(sorted(set(byweekno))) + + self._original_rule['byweekno'] = self._byweekno + + # byweekday / bynweekday + if byweekday is None: + self._byweekday = None + self._bynweekday = None + else: + # If it's one of the valid non-sequence types, convert to a + # single-element sequence before the iterator that builds the + # byweekday set. + if isinstance(byweekday, integer_types) or hasattr(byweekday, "n"): + byweekday = (byweekday,) + + self._byweekday = set() + self._bynweekday = set() + for wday in byweekday: + if isinstance(wday, integer_types): + self._byweekday.add(wday) + elif not wday.n or freq > MONTHLY: + self._byweekday.add(wday.weekday) + else: + self._bynweekday.add((wday.weekday, wday.n)) + + if not self._byweekday: + self._byweekday = None + elif not self._bynweekday: + self._bynweekday = None + + if self._byweekday is not None: + self._byweekday = tuple(sorted(self._byweekday)) + orig_byweekday = [weekday(x) for x in self._byweekday] + else: + orig_byweekday = () + + if self._bynweekday is not None: + self._bynweekday = tuple(sorted(self._bynweekday)) + orig_bynweekday = [weekday(*x) for x in self._bynweekday] + else: + orig_bynweekday = () + + if 'byweekday' not in self._original_rule: + self._original_rule['byweekday'] = tuple(itertools.chain( + orig_byweekday, orig_bynweekday)) + + # byhour + if byhour is None: + if freq < HOURLY: + self._byhour = {dtstart.hour} + else: + self._byhour = None + else: + if isinstance(byhour, integer_types): + byhour = (byhour,) + + if freq == HOURLY: + self._byhour = self.__construct_byset(start=dtstart.hour, + byxxx=byhour, + base=24) + else: + self._byhour = set(byhour) + + self._byhour = tuple(sorted(self._byhour)) + self._original_rule['byhour'] = self._byhour + + # byminute + if byminute is None: + if freq < MINUTELY: + self._byminute = {dtstart.minute} + else: + self._byminute = None + else: + if isinstance(byminute, integer_types): + byminute = (byminute,) + + if freq == MINUTELY: + self._byminute = self.__construct_byset(start=dtstart.minute, + byxxx=byminute, + base=60) + else: + self._byminute = set(byminute) + + self._byminute = tuple(sorted(self._byminute)) + self._original_rule['byminute'] = self._byminute + + # bysecond + if bysecond is None: + if freq < SECONDLY: + self._bysecond = ((dtstart.second,)) + else: + self._bysecond = None + else: + if isinstance(bysecond, integer_types): + bysecond = (bysecond,) + + self._bysecond = set(bysecond) + + if freq == SECONDLY: + self._bysecond = self.__construct_byset(start=dtstart.second, + byxxx=bysecond, + base=60) + else: + self._bysecond = set(bysecond) + + self._bysecond = tuple(sorted(self._bysecond)) + self._original_rule['bysecond'] = self._bysecond + + if self._freq >= HOURLY: + self._timeset = None + else: + self._timeset = [] + for hour in self._byhour: + for minute in self._byminute: + for second in self._bysecond: + self._timeset.append( + datetime.time(hour, minute, second, + tzinfo=self._tzinfo)) + self._timeset.sort() + self._timeset = tuple(self._timeset) + + def __str__(self): + """ + Output a string that would generate this RRULE if passed to rrulestr. + This is mostly compatible with RFC5545, except for the + dateutil-specific extension BYEASTER. + """ + + output = [] + h, m, s = [None] * 3 + if self._dtstart: + output.append(self._dtstart.strftime('DTSTART:%Y%m%dT%H%M%S')) + h, m, s = self._dtstart.timetuple()[3:6] + + parts = ['FREQ=' + FREQNAMES[self._freq]] + if self._interval != 1: + parts.append('INTERVAL=' + str(self._interval)) + + if self._wkst: + parts.append('WKST=' + repr(weekday(self._wkst))[0:2]) + + if self._count is not None: + parts.append('COUNT=' + str(self._count)) + + if self._until: + parts.append(self._until.strftime('UNTIL=%Y%m%dT%H%M%S')) + + if self._original_rule.get('byweekday') is not None: + # The str() method on weekday objects doesn't generate + # RFC5545-compliant strings, so we should modify that. + original_rule = dict(self._original_rule) + wday_strings = [] + for wday in original_rule['byweekday']: + if wday.n: + wday_strings.append('{n:+d}{wday}'.format( + n=wday.n, + wday=repr(wday)[0:2])) + else: + wday_strings.append(repr(wday)) + + original_rule['byweekday'] = wday_strings + else: + original_rule = self._original_rule + + partfmt = '{name}={vals}' + for name, key in [('BYSETPOS', 'bysetpos'), + ('BYMONTH', 'bymonth'), + ('BYMONTHDAY', 'bymonthday'), + ('BYYEARDAY', 'byyearday'), + ('BYWEEKNO', 'byweekno'), + ('BYDAY', 'byweekday'), + ('BYHOUR', 'byhour'), + ('BYMINUTE', 'byminute'), + ('BYSECOND', 'bysecond'), + ('BYEASTER', 'byeaster')]: + value = original_rule.get(key) + if value: + parts.append(partfmt.format(name=name, vals=(','.join(str(v) + for v in value)))) + + output.append('RRULE:' + ';'.join(parts)) + return '\n'.join(output) + + def replace(self, **kwargs): + """Return new rrule with same attributes except for those attributes given new + values by whichever keyword arguments are specified.""" + new_kwargs = {"interval": self._interval, + "count": self._count, + "dtstart": self._dtstart, + "freq": self._freq, + "until": self._until, + "wkst": self._wkst, + "cache": False if self._cache is None else True } + new_kwargs.update(self._original_rule) + new_kwargs.update(kwargs) + return rrule(**new_kwargs) + + def _iter(self): + year, month, day, hour, minute, second, weekday, yearday, _ = \ + self._dtstart.timetuple() + + # Some local variables to speed things up a bit + freq = self._freq + interval = self._interval + wkst = self._wkst + until = self._until + bymonth = self._bymonth + byweekno = self._byweekno + byyearday = self._byyearday + byweekday = self._byweekday + byeaster = self._byeaster + bymonthday = self._bymonthday + bynmonthday = self._bynmonthday + bysetpos = self._bysetpos + byhour = self._byhour + byminute = self._byminute + bysecond = self._bysecond + + ii = _iterinfo(self) + ii.rebuild(year, month) + + getdayset = {YEARLY: ii.ydayset, + MONTHLY: ii.mdayset, + WEEKLY: ii.wdayset, + DAILY: ii.ddayset, + HOURLY: ii.ddayset, + MINUTELY: ii.ddayset, + SECONDLY: ii.ddayset}[freq] + + if freq < HOURLY: + timeset = self._timeset + else: + gettimeset = {HOURLY: ii.htimeset, + MINUTELY: ii.mtimeset, + SECONDLY: ii.stimeset}[freq] + if ((freq >= HOURLY and + self._byhour and hour not in self._byhour) or + (freq >= MINUTELY and + self._byminute and minute not in self._byminute) or + (freq >= SECONDLY and + self._bysecond and second not in self._bysecond)): + timeset = () + else: + timeset = gettimeset(hour, minute, second) + + total = 0 + count = self._count + while True: + # Get dayset with the right frequency + dayset, start, end = getdayset(year, month, day) + + # Do the "hard" work ;-) + filtered = False + for i in dayset[start:end]: + if ((bymonth and ii.mmask[i] not in bymonth) or + (byweekno and not ii.wnomask[i]) or + (byweekday and ii.wdaymask[i] not in byweekday) or + (ii.nwdaymask and not ii.nwdaymask[i]) or + (byeaster and not ii.eastermask[i]) or + ((bymonthday or bynmonthday) and + ii.mdaymask[i] not in bymonthday and + ii.nmdaymask[i] not in bynmonthday) or + (byyearday and + ((i < ii.yearlen and i+1 not in byyearday and + -ii.yearlen+i not in byyearday) or + (i >= ii.yearlen and i+1-ii.yearlen not in byyearday and + -ii.nextyearlen+i-ii.yearlen not in byyearday)))): + dayset[i] = None + filtered = True + + # Output results + if bysetpos and timeset: + poslist = [] + for pos in bysetpos: + if pos < 0: + daypos, timepos = divmod(pos, len(timeset)) + else: + daypos, timepos = divmod(pos-1, len(timeset)) + try: + i = [x for x in dayset[start:end] + if x is not None][daypos] + time = timeset[timepos] + except IndexError: + pass + else: + date = datetime.date.fromordinal(ii.yearordinal+i) + res = datetime.datetime.combine(date, time) + if res not in poslist: + poslist.append(res) + poslist.sort() + for res in poslist: + if until and res > until: + self._len = total + return + elif res >= self._dtstart: + if count is not None: + count -= 1 + if count < 0: + self._len = total + return + total += 1 + yield res + else: + for i in dayset[start:end]: + if i is not None: + date = datetime.date.fromordinal(ii.yearordinal + i) + for time in timeset: + res = datetime.datetime.combine(date, time) + if until and res > until: + self._len = total + return + elif res >= self._dtstart: + if count is not None: + count -= 1 + if count < 0: + self._len = total + return + + total += 1 + yield res + + # Handle frequency and interval + fixday = False + if freq == YEARLY: + year += interval + if year > datetime.MAXYEAR: + self._len = total + return + ii.rebuild(year, month) + elif freq == MONTHLY: + month += interval + if month > 12: + div, mod = divmod(month, 12) + month = mod + year += div + if month == 0: + month = 12 + year -= 1 + if year > datetime.MAXYEAR: + self._len = total + return + ii.rebuild(year, month) + elif freq == WEEKLY: + if wkst > weekday: + day += -(weekday+1+(6-wkst))+self._interval*7 + else: + day += -(weekday-wkst)+self._interval*7 + weekday = wkst + fixday = True + elif freq == DAILY: + day += interval + fixday = True + elif freq == HOURLY: + if filtered: + # Jump to one iteration before next day + hour += ((23-hour)//interval)*interval + + if byhour: + ndays, hour = self.__mod_distance(value=hour, + byxxx=self._byhour, + base=24) + else: + ndays, hour = divmod(hour+interval, 24) + + if ndays: + day += ndays + fixday = True + + timeset = gettimeset(hour, minute, second) + elif freq == MINUTELY: + if filtered: + # Jump to one iteration before next day + minute += ((1439-(hour*60+minute))//interval)*interval + + valid = False + rep_rate = (24*60) + for j in range(rep_rate // gcd(interval, rep_rate)): + if byminute: + nhours, minute = \ + self.__mod_distance(value=minute, + byxxx=self._byminute, + base=60) + else: + nhours, minute = divmod(minute+interval, 60) + + div, hour = divmod(hour+nhours, 24) + if div: + day += div + fixday = True + filtered = False + + if not byhour or hour in byhour: + valid = True + break + + if not valid: + raise ValueError('Invalid combination of interval and ' + + 'byhour resulting in empty rule.') + + timeset = gettimeset(hour, minute, second) + elif freq == SECONDLY: + if filtered: + # Jump to one iteration before next day + second += (((86399 - (hour * 3600 + minute * 60 + second)) + // interval) * interval) + + rep_rate = (24 * 3600) + valid = False + for j in range(0, rep_rate // gcd(interval, rep_rate)): + if bysecond: + nminutes, second = \ + self.__mod_distance(value=second, + byxxx=self._bysecond, + base=60) + else: + nminutes, second = divmod(second+interval, 60) + + div, minute = divmod(minute+nminutes, 60) + if div: + hour += div + div, hour = divmod(hour, 24) + if div: + day += div + fixday = True + + if ((not byhour or hour in byhour) and + (not byminute or minute in byminute) and + (not bysecond or second in bysecond)): + valid = True + break + + if not valid: + raise ValueError('Invalid combination of interval, ' + + 'byhour and byminute resulting in empty' + + ' rule.') + + timeset = gettimeset(hour, minute, second) + + if fixday and day > 28: + daysinmonth = calendar.monthrange(year, month)[1] + if day > daysinmonth: + while day > daysinmonth: + day -= daysinmonth + month += 1 + if month == 13: + month = 1 + year += 1 + if year > datetime.MAXYEAR: + self._len = total + return + daysinmonth = calendar.monthrange(year, month)[1] + ii.rebuild(year, month) + + def __construct_byset(self, start, byxxx, base): + """ + If a `BYXXX` sequence is passed to the constructor at the same level as + `FREQ` (e.g. `FREQ=HOURLY,BYHOUR={2,4,7},INTERVAL=3`), there are some + specifications which cannot be reached given some starting conditions. + + This occurs whenever the interval is not coprime with the base of a + given unit and the difference between the starting position and the + ending position is not coprime with the greatest common denominator + between the interval and the base. For example, with a FREQ of hourly + starting at 17:00 and an interval of 4, the only valid values for + BYHOUR would be {21, 1, 5, 9, 13, 17}, because 4 and 24 are not + coprime. + + :param start: + Specifies the starting position. + :param byxxx: + An iterable containing the list of allowed values. + :param base: + The largest allowable value for the specified frequency (e.g. + 24 hours, 60 minutes). + + This does not preserve the type of the iterable, returning a set, since + the values should be unique and the order is irrelevant, this will + speed up later lookups. + + In the event of an empty set, raises a :exception:`ValueError`, as this + results in an empty rrule. + """ + + cset = set() + + # Support a single byxxx value. + if isinstance(byxxx, integer_types): + byxxx = (byxxx, ) + + for num in byxxx: + i_gcd = gcd(self._interval, base) + # Use divmod rather than % because we need to wrap negative nums. + if i_gcd == 1 or divmod(num - start, i_gcd)[1] == 0: + cset.add(num) + + if len(cset) == 0: + raise ValueError("Invalid rrule byxxx generates an empty set.") + + return cset + + def __mod_distance(self, value, byxxx, base): + """ + Calculates the next value in a sequence where the `FREQ` parameter is + specified along with a `BYXXX` parameter at the same "level" + (e.g. `HOURLY` specified with `BYHOUR`). + + :param value: + The old value of the component. + :param byxxx: + The `BYXXX` set, which should have been generated by + `rrule._construct_byset`, or something else which checks that a + valid rule is present. + :param base: + The largest allowable value for the specified frequency (e.g. + 24 hours, 60 minutes). + + If a valid value is not found after `base` iterations (the maximum + number before the sequence would start to repeat), this raises a + :exception:`ValueError`, as no valid values were found. + + This returns a tuple of `divmod(n*interval, base)`, where `n` is the + smallest number of `interval` repetitions until the next specified + value in `byxxx` is found. + """ + accumulator = 0 + for ii in range(1, base + 1): + # Using divmod() over % to account for negative intervals + div, value = divmod(value + self._interval, base) + accumulator += div + if value in byxxx: + return (accumulator, value) + + +class _iterinfo(object): + __slots__ = ["rrule", "lastyear", "lastmonth", + "yearlen", "nextyearlen", "yearordinal", "yearweekday", + "mmask", "mrange", "mdaymask", "nmdaymask", + "wdaymask", "wnomask", "nwdaymask", "eastermask"] + + def __init__(self, rrule): + for attr in self.__slots__: + setattr(self, attr, None) + self.rrule = rrule + + def rebuild(self, year, month): + # Every mask is 7 days longer to handle cross-year weekly periods. + rr = self.rrule + if year != self.lastyear: + self.yearlen = 365 + calendar.isleap(year) + self.nextyearlen = 365 + calendar.isleap(year + 1) + firstyday = datetime.date(year, 1, 1) + self.yearordinal = firstyday.toordinal() + self.yearweekday = firstyday.weekday() + + wday = datetime.date(year, 1, 1).weekday() + if self.yearlen == 365: + self.mmask = M365MASK + self.mdaymask = MDAY365MASK + self.nmdaymask = NMDAY365MASK + self.wdaymask = WDAYMASK[wday:] + self.mrange = M365RANGE + else: + self.mmask = M366MASK + self.mdaymask = MDAY366MASK + self.nmdaymask = NMDAY366MASK + self.wdaymask = WDAYMASK[wday:] + self.mrange = M366RANGE + + if not rr._byweekno: + self.wnomask = None + else: + self.wnomask = [0]*(self.yearlen+7) + # no1wkst = firstwkst = self.wdaymask.index(rr._wkst) + no1wkst = firstwkst = (7-self.yearweekday+rr._wkst) % 7 + if no1wkst >= 4: + no1wkst = 0 + # Number of days in the year, plus the days we got + # from last year. + wyearlen = self.yearlen+(self.yearweekday-rr._wkst) % 7 + else: + # Number of days in the year, minus the days we + # left in last year. + wyearlen = self.yearlen-no1wkst + div, mod = divmod(wyearlen, 7) + numweeks = div+mod//4 + for n in rr._byweekno: + if n < 0: + n += numweeks+1 + if not (0 < n <= numweeks): + continue + if n > 1: + i = no1wkst+(n-1)*7 + if no1wkst != firstwkst: + i -= 7-firstwkst + else: + i = no1wkst + for j in range(7): + self.wnomask[i] = 1 + i += 1 + if self.wdaymask[i] == rr._wkst: + break + if 1 in rr._byweekno: + # Check week number 1 of next year as well + # TODO: Check -numweeks for next year. + i = no1wkst+numweeks*7 + if no1wkst != firstwkst: + i -= 7-firstwkst + if i < self.yearlen: + # If week starts in next year, we + # don't care about it. + for j in range(7): + self.wnomask[i] = 1 + i += 1 + if self.wdaymask[i] == rr._wkst: + break + if no1wkst: + # Check last week number of last year as + # well. If no1wkst is 0, either the year + # started on week start, or week number 1 + # got days from last year, so there are no + # days from last year's last week number in + # this year. + if -1 not in rr._byweekno: + lyearweekday = datetime.date(year-1, 1, 1).weekday() + lno1wkst = (7-lyearweekday+rr._wkst) % 7 + lyearlen = 365+calendar.isleap(year-1) + if lno1wkst >= 4: + lno1wkst = 0 + lnumweeks = 52+(lyearlen + + (lyearweekday-rr._wkst) % 7) % 7//4 + else: + lnumweeks = 52+(self.yearlen-no1wkst) % 7//4 + else: + lnumweeks = -1 + if lnumweeks in rr._byweekno: + for i in range(no1wkst): + self.wnomask[i] = 1 + + if (rr._bynweekday and (month != self.lastmonth or + year != self.lastyear)): + ranges = [] + if rr._freq == YEARLY: + if rr._bymonth: + for month in rr._bymonth: + ranges.append(self.mrange[month-1:month+1]) + else: + ranges = [(0, self.yearlen)] + elif rr._freq == MONTHLY: + ranges = [self.mrange[month-1:month+1]] + if ranges: + # Weekly frequency won't get here, so we may not + # care about cross-year weekly periods. + self.nwdaymask = [0]*self.yearlen + for first, last in ranges: + last -= 1 + for wday, n in rr._bynweekday: + if n < 0: + i = last+(n+1)*7 + i -= (self.wdaymask[i]-wday) % 7 + else: + i = first+(n-1)*7 + i += (7-self.wdaymask[i]+wday) % 7 + if first <= i <= last: + self.nwdaymask[i] = 1 + + if rr._byeaster: + self.eastermask = [0]*(self.yearlen+7) + eyday = easter.easter(year).toordinal()-self.yearordinal + for offset in rr._byeaster: + self.eastermask[eyday+offset] = 1 + + self.lastyear = year + self.lastmonth = month + + def ydayset(self, year, month, day): + return list(range(self.yearlen)), 0, self.yearlen + + def mdayset(self, year, month, day): + dset = [None]*self.yearlen + start, end = self.mrange[month-1:month+1] + for i in range(start, end): + dset[i] = i + return dset, start, end + + def wdayset(self, year, month, day): + # We need to handle cross-year weeks here. + dset = [None]*(self.yearlen+7) + i = datetime.date(year, month, day).toordinal()-self.yearordinal + start = i + for j in range(7): + dset[i] = i + i += 1 + # if (not (0 <= i < self.yearlen) or + # self.wdaymask[i] == self.rrule._wkst): + # This will cross the year boundary, if necessary. + if self.wdaymask[i] == self.rrule._wkst: + break + return dset, start, i + + def ddayset(self, year, month, day): + dset = [None] * self.yearlen + i = datetime.date(year, month, day).toordinal() - self.yearordinal + dset[i] = i + return dset, i, i + 1 + + def htimeset(self, hour, minute, second): + tset = [] + rr = self.rrule + for minute in rr._byminute: + for second in rr._bysecond: + tset.append(datetime.time(hour, minute, second, + tzinfo=rr._tzinfo)) + tset.sort() + return tset + + def mtimeset(self, hour, minute, second): + tset = [] + rr = self.rrule + for second in rr._bysecond: + tset.append(datetime.time(hour, minute, second, tzinfo=rr._tzinfo)) + tset.sort() + return tset + + def stimeset(self, hour, minute, second): + return (datetime.time(hour, minute, second, + tzinfo=self.rrule._tzinfo),) + + +class rruleset(rrulebase): + """ The rruleset type allows more complex recurrence setups, mixing + multiple rules, dates, exclusion rules, and exclusion dates. The type + constructor takes the following keyword arguments: + + :param cache: If True, caching of results will be enabled, improving + performance of multiple queries considerably. """ + + class _genitem(object): + def __init__(self, genlist, gen): + try: + self.dt = advance_iterator(gen) + genlist.append(self) + except StopIteration: + pass + self.genlist = genlist + self.gen = gen + + def __next__(self): + try: + self.dt = advance_iterator(self.gen) + except StopIteration: + if self.genlist[0] is self: + heapq.heappop(self.genlist) + else: + self.genlist.remove(self) + heapq.heapify(self.genlist) + + next = __next__ + + def __lt__(self, other): + return self.dt < other.dt + + def __gt__(self, other): + return self.dt > other.dt + + def __eq__(self, other): + return self.dt == other.dt + + def __ne__(self, other): + return self.dt != other.dt + + def __init__(self, cache=False): + super(rruleset, self).__init__(cache) + self._rrule = [] + self._rdate = [] + self._exrule = [] + self._exdate = [] + + @_invalidates_cache + def rrule(self, rrule): + """ Include the given :py:class:`rrule` instance in the recurrence set + generation. """ + self._rrule.append(rrule) + + @_invalidates_cache + def rdate(self, rdate): + """ Include the given :py:class:`datetime` instance in the recurrence + set generation. """ + self._rdate.append(rdate) + + @_invalidates_cache + def exrule(self, exrule): + """ Include the given rrule instance in the recurrence set exclusion + list. Dates which are part of the given recurrence rules will not + be generated, even if some inclusive rrule or rdate matches them. + """ + self._exrule.append(exrule) + + @_invalidates_cache + def exdate(self, exdate): + """ Include the given datetime instance in the recurrence set + exclusion list. Dates included that way will not be generated, + even if some inclusive rrule or rdate matches them. """ + self._exdate.append(exdate) + + def _iter(self): + rlist = [] + self._rdate.sort() + self._genitem(rlist, iter(self._rdate)) + for gen in [iter(x) for x in self._rrule]: + self._genitem(rlist, gen) + exlist = [] + self._exdate.sort() + self._genitem(exlist, iter(self._exdate)) + for gen in [iter(x) for x in self._exrule]: + self._genitem(exlist, gen) + lastdt = None + total = 0 + heapq.heapify(rlist) + heapq.heapify(exlist) + while rlist: + ritem = rlist[0] + if not lastdt or lastdt != ritem.dt: + while exlist and exlist[0] < ritem: + exitem = exlist[0] + advance_iterator(exitem) + if exlist and exlist[0] is exitem: + heapq.heapreplace(exlist, exitem) + if not exlist or ritem != exlist[0]: + total += 1 + yield ritem.dt + lastdt = ritem.dt + advance_iterator(ritem) + if rlist and rlist[0] is ritem: + heapq.heapreplace(rlist, ritem) + self._len = total + + + + +class _rrulestr(object): + """ Parses a string representation of a recurrence rule or set of + recurrence rules. + + :param s: + Required, a string defining one or more recurrence rules. + + :param dtstart: + If given, used as the default recurrence start if not specified in the + rule string. + + :param cache: + If set ``True`` caching of results will be enabled, improving + performance of multiple queries considerably. + + :param unfold: + If set ``True`` indicates that a rule string is split over more + than one line and should be joined before processing. + + :param forceset: + If set ``True`` forces a :class:`dateutil.rrule.rruleset` to + be returned. + + :param compatible: + If set ``True`` forces ``unfold`` and ``forceset`` to be ``True``. + + :param ignoretz: + If set ``True``, time zones in parsed strings are ignored and a naive + :class:`datetime.datetime` object is returned. + + :param tzids: + If given, a callable or mapping used to retrieve a + :class:`datetime.tzinfo` from a string representation. + Defaults to :func:`dateutil.tz.gettz`. + + :param tzinfos: + Additional time zone names / aliases which may be present in a string + representation. See :func:`dateutil.parser.parse` for more + information. + + :return: + Returns a :class:`dateutil.rrule.rruleset` or + :class:`dateutil.rrule.rrule` + """ + + _freq_map = {"YEARLY": YEARLY, + "MONTHLY": MONTHLY, + "WEEKLY": WEEKLY, + "DAILY": DAILY, + "HOURLY": HOURLY, + "MINUTELY": MINUTELY, + "SECONDLY": SECONDLY} + + _weekday_map = {"MO": 0, "TU": 1, "WE": 2, "TH": 3, + "FR": 4, "SA": 5, "SU": 6} + + def _handle_int(self, rrkwargs, name, value, **kwargs): + rrkwargs[name.lower()] = int(value) + + def _handle_int_list(self, rrkwargs, name, value, **kwargs): + rrkwargs[name.lower()] = [int(x) for x in value.split(',')] + + _handle_INTERVAL = _handle_int + _handle_COUNT = _handle_int + _handle_BYSETPOS = _handle_int_list + _handle_BYMONTH = _handle_int_list + _handle_BYMONTHDAY = _handle_int_list + _handle_BYYEARDAY = _handle_int_list + _handle_BYEASTER = _handle_int_list + _handle_BYWEEKNO = _handle_int_list + _handle_BYHOUR = _handle_int_list + _handle_BYMINUTE = _handle_int_list + _handle_BYSECOND = _handle_int_list + + def _handle_FREQ(self, rrkwargs, name, value, **kwargs): + rrkwargs["freq"] = self._freq_map[value] + + def _handle_UNTIL(self, rrkwargs, name, value, **kwargs): + global parser + if not parser: + from pipenv.vendor.dateutil import parser + try: + rrkwargs["until"] = parser.parse(value, + ignoretz=kwargs.get("ignoretz"), + tzinfos=kwargs.get("tzinfos")) + except ValueError: + raise ValueError("invalid until date") + + def _handle_WKST(self, rrkwargs, name, value, **kwargs): + rrkwargs["wkst"] = self._weekday_map[value] + + def _handle_BYWEEKDAY(self, rrkwargs, name, value, **kwargs): + """ + Two ways to specify this: +1MO or MO(+1) + """ + l = [] + for wday in value.split(','): + if '(' in wday: + # If it's of the form TH(+1), etc. + splt = wday.split('(') + w = splt[0] + n = int(splt[1][:-1]) + elif len(wday): + # If it's of the form +1MO + for i in range(len(wday)): + if wday[i] not in '+-0123456789': + break + n = wday[:i] or None + w = wday[i:] + if n: + n = int(n) + else: + raise ValueError("Invalid (empty) BYDAY specification.") + + l.append(weekdays[self._weekday_map[w]](n)) + rrkwargs["byweekday"] = l + + _handle_BYDAY = _handle_BYWEEKDAY + + def _parse_rfc_rrule(self, line, + dtstart=None, + cache=False, + ignoretz=False, + tzinfos=None): + if line.find(':') != -1: + name, value = line.split(':') + if name != "RRULE": + raise ValueError("unknown parameter name") + else: + value = line + rrkwargs = {} + for pair in value.split(';'): + name, value = pair.split('=') + name = name.upper() + value = value.upper() + try: + getattr(self, "_handle_"+name)(rrkwargs, name, value, + ignoretz=ignoretz, + tzinfos=tzinfos) + except AttributeError: + raise ValueError("unknown parameter '%s'" % name) + except (KeyError, ValueError): + raise ValueError("invalid '%s': %s" % (name, value)) + return rrule(dtstart=dtstart, cache=cache, **rrkwargs) + + def _parse_date_value(self, date_value, parms, rule_tzids, + ignoretz, tzids, tzinfos): + global parser + if not parser: + from pipenv.vendor.dateutil import parser + + datevals = [] + value_found = False + TZID = None + + for parm in parms: + if parm.startswith("TZID="): + try: + tzkey = rule_tzids[parm.split('TZID=')[-1]] + except KeyError: + continue + if tzids is None: + from . import tz + tzlookup = tz.gettz + elif callable(tzids): + tzlookup = tzids + else: + tzlookup = getattr(tzids, 'get', None) + if tzlookup is None: + msg = ('tzids must be a callable, mapping, or None, ' + 'not %s' % tzids) + raise ValueError(msg) + + TZID = tzlookup(tzkey) + continue + + # RFC 5445 3.8.2.4: The VALUE parameter is optional, but may be found + # only once. + if parm not in {"VALUE=DATE-TIME", "VALUE=DATE"}: + raise ValueError("unsupported parm: " + parm) + else: + if value_found: + msg = ("Duplicate value parameter found in: " + parm) + raise ValueError(msg) + value_found = True + + for datestr in date_value.split(','): + date = parser.parse(datestr, ignoretz=ignoretz, tzinfos=tzinfos) + if TZID is not None: + if date.tzinfo is None: + date = date.replace(tzinfo=TZID) + else: + raise ValueError('DTSTART/EXDATE specifies multiple timezone') + datevals.append(date) + + return datevals + + def _parse_rfc(self, s, + dtstart=None, + cache=False, + unfold=False, + forceset=False, + compatible=False, + ignoretz=False, + tzids=None, + tzinfos=None): + global parser + if compatible: + forceset = True + unfold = True + + TZID_NAMES = dict(map( + lambda x: (x.upper(), x), + re.findall('TZID=(?P<name>[^:]+):', s) + )) + s = s.upper() + if not s.strip(): + raise ValueError("empty string") + if unfold: + lines = s.splitlines() + i = 0 + while i < len(lines): + line = lines[i].rstrip() + if not line: + del lines[i] + elif i > 0 and line[0] == " ": + lines[i-1] += line[1:] + del lines[i] + else: + i += 1 + else: + lines = s.split() + if (not forceset and len(lines) == 1 and (s.find(':') == -1 or + s.startswith('RRULE:'))): + return self._parse_rfc_rrule(lines[0], cache=cache, + dtstart=dtstart, ignoretz=ignoretz, + tzinfos=tzinfos) + else: + rrulevals = [] + rdatevals = [] + exrulevals = [] + exdatevals = [] + for line in lines: + if not line: + continue + if line.find(':') == -1: + name = "RRULE" + value = line + else: + name, value = line.split(':', 1) + parms = name.split(';') + if not parms: + raise ValueError("empty property name") + name = parms[0] + parms = parms[1:] + if name == "RRULE": + for parm in parms: + raise ValueError("unsupported RRULE parm: "+parm) + rrulevals.append(value) + elif name == "RDATE": + for parm in parms: + if parm != "VALUE=DATE-TIME": + raise ValueError("unsupported RDATE parm: "+parm) + rdatevals.append(value) + elif name == "EXRULE": + for parm in parms: + raise ValueError("unsupported EXRULE parm: "+parm) + exrulevals.append(value) + elif name == "EXDATE": + exdatevals.extend( + self._parse_date_value(value, parms, + TZID_NAMES, ignoretz, + tzids, tzinfos) + ) + elif name == "DTSTART": + dtvals = self._parse_date_value(value, parms, TZID_NAMES, + ignoretz, tzids, tzinfos) + if len(dtvals) != 1: + raise ValueError("Multiple DTSTART values specified:" + + value) + dtstart = dtvals[0] + else: + raise ValueError("unsupported property: "+name) + if (forceset or len(rrulevals) > 1 or rdatevals + or exrulevals or exdatevals): + if not parser and (rdatevals or exdatevals): + from pipenv.vendor.dateutil import parser + rset = rruleset(cache=cache) + for value in rrulevals: + rset.rrule(self._parse_rfc_rrule(value, dtstart=dtstart, + ignoretz=ignoretz, + tzinfos=tzinfos)) + for value in rdatevals: + for datestr in value.split(','): + rset.rdate(parser.parse(datestr, + ignoretz=ignoretz, + tzinfos=tzinfos)) + for value in exrulevals: + rset.exrule(self._parse_rfc_rrule(value, dtstart=dtstart, + ignoretz=ignoretz, + tzinfos=tzinfos)) + for value in exdatevals: + rset.exdate(value) + if compatible and dtstart: + rset.rdate(dtstart) + return rset + else: + return self._parse_rfc_rrule(rrulevals[0], + dtstart=dtstart, + cache=cache, + ignoretz=ignoretz, + tzinfos=tzinfos) + + def __call__(self, s, **kwargs): + return self._parse_rfc(s, **kwargs) + + +rrulestr = _rrulestr() + +# vim:ts=4:sw=4:et diff --git a/pipenv/vendor/dateutil/tz/__init__.py b/pipenv/vendor/dateutil/tz/__init__.py new file mode 100644 index 00000000..af1352c4 --- /dev/null +++ b/pipenv/vendor/dateutil/tz/__init__.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +from .tz import * +from .tz import __doc__ + +__all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange", + "tzstr", "tzical", "tzwin", "tzwinlocal", "gettz", + "enfold", "datetime_ambiguous", "datetime_exists", + "resolve_imaginary", "UTC", "DeprecatedTzFormatWarning"] + + +class DeprecatedTzFormatWarning(Warning): + """Warning raised when time zones are parsed from deprecated formats.""" diff --git a/pipenv/vendor/dateutil/tz/_common.py b/pipenv/vendor/dateutil/tz/_common.py new file mode 100644 index 00000000..393c5c9a --- /dev/null +++ b/pipenv/vendor/dateutil/tz/_common.py @@ -0,0 +1,419 @@ +from pipenv.vendor.six import PY2 + +from functools import wraps + +from datetime import datetime, timedelta, tzinfo + + +ZERO = timedelta(0) + +__all__ = ['tzname_in_python2', 'enfold'] + + +def tzname_in_python2(namefunc): + """Change unicode output into bytestrings in Python 2 + + tzname() API changed in Python 3. It used to return bytes, but was changed + to unicode strings + """ + if PY2: + @wraps(namefunc) + def adjust_encoding(*args, **kwargs): + name = namefunc(*args, **kwargs) + if name is not None: + name = name.encode() + + return name + + return adjust_encoding + else: + return namefunc + + +# The following is adapted from Alexander Belopolsky's tz library +# https://github.com/abalkin/tz +if hasattr(datetime, 'fold'): + # This is the pre-python 3.6 fold situation + def enfold(dt, fold=1): + """ + Provides a unified interface for assigning the ``fold`` attribute to + datetimes both before and after the implementation of PEP-495. + + :param fold: + The value for the ``fold`` attribute in the returned datetime. This + should be either 0 or 1. + + :return: + Returns an object for which ``getattr(dt, 'fold', 0)`` returns + ``fold`` for all versions of Python. In versions prior to + Python 3.6, this is a ``_DatetimeWithFold`` object, which is a + subclass of :py:class:`datetime.datetime` with the ``fold`` + attribute added, if ``fold`` is 1. + + .. versionadded:: 2.6.0 + """ + return dt.replace(fold=fold) + +else: + class _DatetimeWithFold(datetime): + """ + This is a class designed to provide a PEP 495-compliant interface for + Python versions before 3.6. It is used only for dates in a fold, so + the ``fold`` attribute is fixed at ``1``. + + .. versionadded:: 2.6.0 + """ + __slots__ = () + + def replace(self, *args, **kwargs): + """ + Return a datetime with the same attributes, except for those + attributes given new values by whichever keyword arguments are + specified. Note that tzinfo=None can be specified to create a naive + datetime from an aware datetime with no conversion of date and time + data. + + This is reimplemented in ``_DatetimeWithFold`` because pypy3 will + return a ``datetime.datetime`` even if ``fold`` is unchanged. + """ + argnames = ( + 'year', 'month', 'day', 'hour', 'minute', 'second', + 'microsecond', 'tzinfo' + ) + + for arg, argname in zip(args, argnames): + if argname in kwargs: + raise TypeError('Duplicate argument: {}'.format(argname)) + + kwargs[argname] = arg + + for argname in argnames: + if argname not in kwargs: + kwargs[argname] = getattr(self, argname) + + dt_class = self.__class__ if kwargs.get('fold', 1) else datetime + + return dt_class(**kwargs) + + @property + def fold(self): + return 1 + + def enfold(dt, fold=1): + """ + Provides a unified interface for assigning the ``fold`` attribute to + datetimes both before and after the implementation of PEP-495. + + :param fold: + The value for the ``fold`` attribute in the returned datetime. This + should be either 0 or 1. + + :return: + Returns an object for which ``getattr(dt, 'fold', 0)`` returns + ``fold`` for all versions of Python. In versions prior to + Python 3.6, this is a ``_DatetimeWithFold`` object, which is a + subclass of :py:class:`datetime.datetime` with the ``fold`` + attribute added, if ``fold`` is 1. + + .. versionadded:: 2.6.0 + """ + if getattr(dt, 'fold', 0) == fold: + return dt + + args = dt.timetuple()[:6] + args += (dt.microsecond, dt.tzinfo) + + if fold: + return _DatetimeWithFold(*args) + else: + return datetime(*args) + + +def _validate_fromutc_inputs(f): + """ + The CPython version of ``fromutc`` checks that the input is a ``datetime`` + object and that ``self`` is attached as its ``tzinfo``. + """ + @wraps(f) + def fromutc(self, dt): + if not isinstance(dt, datetime): + raise TypeError("fromutc() requires a datetime argument") + if dt.tzinfo is not self: + raise ValueError("dt.tzinfo is not self") + + return f(self, dt) + + return fromutc + + +class _tzinfo(tzinfo): + """ + Base class for all ``dateutil`` ``tzinfo`` objects. + """ + + def is_ambiguous(self, dt): + """ + Whether or not the "wall time" of a given datetime is ambiguous in this + zone. + + :param dt: + A :py:class:`datetime.datetime`, naive or time zone aware. + + + :return: + Returns ``True`` if ambiguous, ``False`` otherwise. + + .. versionadded:: 2.6.0 + """ + + dt = dt.replace(tzinfo=self) + + wall_0 = enfold(dt, fold=0) + wall_1 = enfold(dt, fold=1) + + same_offset = wall_0.utcoffset() == wall_1.utcoffset() + same_dt = wall_0.replace(tzinfo=None) == wall_1.replace(tzinfo=None) + + return same_dt and not same_offset + + def _fold_status(self, dt_utc, dt_wall): + """ + Determine the fold status of a "wall" datetime, given a representation + of the same datetime as a (naive) UTC datetime. This is calculated based + on the assumption that ``dt.utcoffset() - dt.dst()`` is constant for all + datetimes, and that this offset is the actual number of hours separating + ``dt_utc`` and ``dt_wall``. + + :param dt_utc: + Representation of the datetime as UTC + + :param dt_wall: + Representation of the datetime as "wall time". This parameter must + either have a `fold` attribute or have a fold-naive + :class:`datetime.tzinfo` attached, otherwise the calculation may + fail. + """ + if self.is_ambiguous(dt_wall): + delta_wall = dt_wall - dt_utc + _fold = int(delta_wall == (dt_utc.utcoffset() - dt_utc.dst())) + else: + _fold = 0 + + return _fold + + def _fold(self, dt): + return getattr(dt, 'fold', 0) + + def _fromutc(self, dt): + """ + Given a timezone-aware datetime in a given timezone, calculates a + timezone-aware datetime in a new timezone. + + Since this is the one time that we *know* we have an unambiguous + datetime object, we take this opportunity to determine whether the + datetime is ambiguous and in a "fold" state (e.g. if it's the first + occurrence, chronologically, of the ambiguous datetime). + + :param dt: + A timezone-aware :class:`datetime.datetime` object. + """ + + # Re-implement the algorithm from Python's datetime.py + dtoff = dt.utcoffset() + if dtoff is None: + raise ValueError("fromutc() requires a non-None utcoffset() " + "result") + + # The original datetime.py code assumes that `dst()` defaults to + # zero during ambiguous times. PEP 495 inverts this presumption, so + # for pre-PEP 495 versions of python, we need to tweak the algorithm. + dtdst = dt.dst() + if dtdst is None: + raise ValueError("fromutc() requires a non-None dst() result") + delta = dtoff - dtdst + + dt += delta + # Set fold=1 so we can default to being in the fold for + # ambiguous dates. + dtdst = enfold(dt, fold=1).dst() + if dtdst is None: + raise ValueError("fromutc(): dt.dst gave inconsistent " + "results; cannot convert") + return dt + dtdst + + @_validate_fromutc_inputs + def fromutc(self, dt): + """ + Given a timezone-aware datetime in a given timezone, calculates a + timezone-aware datetime in a new timezone. + + Since this is the one time that we *know* we have an unambiguous + datetime object, we take this opportunity to determine whether the + datetime is ambiguous and in a "fold" state (e.g. if it's the first + occurrence, chronologically, of the ambiguous datetime). + + :param dt: + A timezone-aware :class:`datetime.datetime` object. + """ + dt_wall = self._fromutc(dt) + + # Calculate the fold status given the two datetimes. + _fold = self._fold_status(dt, dt_wall) + + # Set the default fold value for ambiguous dates + return enfold(dt_wall, fold=_fold) + + +class tzrangebase(_tzinfo): + """ + This is an abstract base class for time zones represented by an annual + transition into and out of DST. Child classes should implement the following + methods: + + * ``__init__(self, *args, **kwargs)`` + * ``transitions(self, year)`` - this is expected to return a tuple of + datetimes representing the DST on and off transitions in standard + time. + + A fully initialized ``tzrangebase`` subclass should also provide the + following attributes: + * ``hasdst``: Boolean whether or not the zone uses DST. + * ``_dst_offset`` / ``_std_offset``: :class:`datetime.timedelta` objects + representing the respective UTC offsets. + * ``_dst_abbr`` / ``_std_abbr``: Strings representing the timezone short + abbreviations in DST and STD, respectively. + * ``_hasdst``: Whether or not the zone has DST. + + .. versionadded:: 2.6.0 + """ + def __init__(self): + raise NotImplementedError('tzrangebase is an abstract base class') + + def utcoffset(self, dt): + isdst = self._isdst(dt) + + if isdst is None: + return None + elif isdst: + return self._dst_offset + else: + return self._std_offset + + def dst(self, dt): + isdst = self._isdst(dt) + + if isdst is None: + return None + elif isdst: + return self._dst_base_offset + else: + return ZERO + + @tzname_in_python2 + def tzname(self, dt): + if self._isdst(dt): + return self._dst_abbr + else: + return self._std_abbr + + def fromutc(self, dt): + """ Given a datetime in UTC, return local time """ + if not isinstance(dt, datetime): + raise TypeError("fromutc() requires a datetime argument") + + if dt.tzinfo is not self: + raise ValueError("dt.tzinfo is not self") + + # Get transitions - if there are none, fixed offset + transitions = self.transitions(dt.year) + if transitions is None: + return dt + self.utcoffset(dt) + + # Get the transition times in UTC + dston, dstoff = transitions + + dston -= self._std_offset + dstoff -= self._std_offset + + utc_transitions = (dston, dstoff) + dt_utc = dt.replace(tzinfo=None) + + isdst = self._naive_isdst(dt_utc, utc_transitions) + + if isdst: + dt_wall = dt + self._dst_offset + else: + dt_wall = dt + self._std_offset + + _fold = int(not isdst and self.is_ambiguous(dt_wall)) + + return enfold(dt_wall, fold=_fold) + + def is_ambiguous(self, dt): + """ + Whether or not the "wall time" of a given datetime is ambiguous in this + zone. + + :param dt: + A :py:class:`datetime.datetime`, naive or time zone aware. + + + :return: + Returns ``True`` if ambiguous, ``False`` otherwise. + + .. versionadded:: 2.6.0 + """ + if not self.hasdst: + return False + + start, end = self.transitions(dt.year) + + dt = dt.replace(tzinfo=None) + return (end <= dt < end + self._dst_base_offset) + + def _isdst(self, dt): + if not self.hasdst: + return False + elif dt is None: + return None + + transitions = self.transitions(dt.year) + + if transitions is None: + return False + + dt = dt.replace(tzinfo=None) + + isdst = self._naive_isdst(dt, transitions) + + # Handle ambiguous dates + if not isdst and self.is_ambiguous(dt): + return not self._fold(dt) + else: + return isdst + + def _naive_isdst(self, dt, transitions): + dston, dstoff = transitions + + dt = dt.replace(tzinfo=None) + + if dston < dstoff: + isdst = dston <= dt < dstoff + else: + isdst = not dstoff <= dt < dston + + return isdst + + @property + def _dst_base_offset(self): + return self._dst_offset - self._std_offset + + __hash__ = None + + def __ne__(self, other): + return not (self == other) + + def __repr__(self): + return "%s(...)" % self.__class__.__name__ + + __reduce__ = object.__reduce__ diff --git a/pipenv/vendor/dateutil/tz/_factories.py b/pipenv/vendor/dateutil/tz/_factories.py new file mode 100644 index 00000000..c8cdf6fd --- /dev/null +++ b/pipenv/vendor/dateutil/tz/_factories.py @@ -0,0 +1,80 @@ +from datetime import timedelta +import weakref +from collections import OrderedDict + +from pipenv.vendor.six.moves import _thread + + +class _TzSingleton(type): + def __init__(cls, *args, **kwargs): + cls.__instance = None + super(_TzSingleton, cls).__init__(*args, **kwargs) + + def __call__(cls): + if cls.__instance is None: + cls.__instance = super(_TzSingleton, cls).__call__() + return cls.__instance + + +class _TzFactory(type): + def instance(cls, *args, **kwargs): + """Alternate constructor that returns a fresh instance""" + return type.__call__(cls, *args, **kwargs) + + +class _TzOffsetFactory(_TzFactory): + def __init__(cls, *args, **kwargs): + cls.__instances = weakref.WeakValueDictionary() + cls.__strong_cache = OrderedDict() + cls.__strong_cache_size = 8 + + cls._cache_lock = _thread.allocate_lock() + + def __call__(cls, name, offset): + if isinstance(offset, timedelta): + key = (name, offset.total_seconds()) + else: + key = (name, offset) + + instance = cls.__instances.get(key, None) + if instance is None: + instance = cls.__instances.setdefault(key, + cls.instance(name, offset)) + + # This lock may not be necessary in Python 3. See GH issue #901 + with cls._cache_lock: + cls.__strong_cache[key] = cls.__strong_cache.pop(key, instance) + + # Remove an item if the strong cache is overpopulated + if len(cls.__strong_cache) > cls.__strong_cache_size: + cls.__strong_cache.popitem(last=False) + + return instance + + +class _TzStrFactory(_TzFactory): + def __init__(cls, *args, **kwargs): + cls.__instances = weakref.WeakValueDictionary() + cls.__strong_cache = OrderedDict() + cls.__strong_cache_size = 8 + + cls.__cache_lock = _thread.allocate_lock() + + def __call__(cls, s, posix_offset=False): + key = (s, posix_offset) + instance = cls.__instances.get(key, None) + + if instance is None: + instance = cls.__instances.setdefault(key, + cls.instance(s, posix_offset)) + + # This lock may not be necessary in Python 3. See GH issue #901 + with cls.__cache_lock: + cls.__strong_cache[key] = cls.__strong_cache.pop(key, instance) + + # Remove an item if the strong cache is overpopulated + if len(cls.__strong_cache) > cls.__strong_cache_size: + cls.__strong_cache.popitem(last=False) + + return instance + diff --git a/pipenv/vendor/dateutil/tz/tz.py b/pipenv/vendor/dateutil/tz/tz.py new file mode 100644 index 00000000..8c934c31 --- /dev/null +++ b/pipenv/vendor/dateutil/tz/tz.py @@ -0,0 +1,1849 @@ +# -*- coding: utf-8 -*- +""" +This module offers timezone implementations subclassing the abstract +:py:class:`datetime.tzinfo` type. There are classes to handle tzfile format +files (usually are in :file:`/etc/localtime`, :file:`/usr/share/zoneinfo`, +etc), TZ environment string (in all known formats), given ranges (with help +from relative deltas), local machine timezone, fixed offset timezone, and UTC +timezone. +""" +import datetime +import struct +import time +import sys +import os +import bisect +import weakref +from collections import OrderedDict + +import pipenv.vendor.six as six +from pipenv.vendor.six import string_types +from pipenv.vendor.six.moves import _thread +from ._common import tzname_in_python2, _tzinfo +from ._common import tzrangebase, enfold +from ._common import _validate_fromutc_inputs + +from ._factories import _TzSingleton, _TzOffsetFactory +from ._factories import _TzStrFactory +try: + from .win import tzwin, tzwinlocal +except ImportError: + tzwin = tzwinlocal = None + +# For warning about rounding tzinfo +from warnings import warn + +ZERO = datetime.timedelta(0) +EPOCH = datetime.datetime.utcfromtimestamp(0) +EPOCHORDINAL = EPOCH.toordinal() + + +@six.add_metaclass(_TzSingleton) +class tzutc(datetime.tzinfo): + """ + This is a tzinfo object that represents the UTC time zone. + + **Examples:** + + .. doctest:: + + >>> from datetime import * + >>> from dateutil.tz import * + + >>> datetime.now() + datetime.datetime(2003, 9, 27, 9, 40, 1, 521290) + + >>> datetime.now(tzutc()) + datetime.datetime(2003, 9, 27, 12, 40, 12, 156379, tzinfo=tzutc()) + + >>> datetime.now(tzutc()).tzname() + 'UTC' + + .. versionchanged:: 2.7.0 + ``tzutc()`` is now a singleton, so the result of ``tzutc()`` will + always return the same object. + + .. doctest:: + + >>> from dateutil.tz import tzutc, UTC + >>> tzutc() is tzutc() + True + >>> tzutc() is UTC + True + """ + def utcoffset(self, dt): + return ZERO + + def dst(self, dt): + return ZERO + + @tzname_in_python2 + def tzname(self, dt): + return "UTC" + + def is_ambiguous(self, dt): + """ + Whether or not the "wall time" of a given datetime is ambiguous in this + zone. + + :param dt: + A :py:class:`datetime.datetime`, naive or time zone aware. + + + :return: + Returns ``True`` if ambiguous, ``False`` otherwise. + + .. versionadded:: 2.6.0 + """ + return False + + @_validate_fromutc_inputs + def fromutc(self, dt): + """ + Fast track version of fromutc() returns the original ``dt`` object for + any valid :py:class:`datetime.datetime` object. + """ + return dt + + def __eq__(self, other): + if not isinstance(other, (tzutc, tzoffset)): + return NotImplemented + + return (isinstance(other, tzutc) or + (isinstance(other, tzoffset) and other._offset == ZERO)) + + __hash__ = None + + def __ne__(self, other): + return not (self == other) + + def __repr__(self): + return "%s()" % self.__class__.__name__ + + __reduce__ = object.__reduce__ + + +#: Convenience constant providing a :class:`tzutc()` instance +#: +#: .. versionadded:: 2.7.0 +UTC = tzutc() + + +@six.add_metaclass(_TzOffsetFactory) +class tzoffset(datetime.tzinfo): + """ + A simple class for representing a fixed offset from UTC. + + :param name: + The timezone name, to be returned when ``tzname()`` is called. + :param offset: + The time zone offset in seconds, or (since version 2.6.0, represented + as a :py:class:`datetime.timedelta` object). + """ + def __init__(self, name, offset): + self._name = name + + try: + # Allow a timedelta + offset = offset.total_seconds() + except (TypeError, AttributeError): + pass + + self._offset = datetime.timedelta(seconds=_get_supported_offset(offset)) + + def utcoffset(self, dt): + return self._offset + + def dst(self, dt): + return ZERO + + @tzname_in_python2 + def tzname(self, dt): + return self._name + + @_validate_fromutc_inputs + def fromutc(self, dt): + return dt + self._offset + + def is_ambiguous(self, dt): + """ + Whether or not the "wall time" of a given datetime is ambiguous in this + zone. + + :param dt: + A :py:class:`datetime.datetime`, naive or time zone aware. + :return: + Returns ``True`` if ambiguous, ``False`` otherwise. + + .. versionadded:: 2.6.0 + """ + return False + + def __eq__(self, other): + if not isinstance(other, tzoffset): + return NotImplemented + + return self._offset == other._offset + + __hash__ = None + + def __ne__(self, other): + return not (self == other) + + def __repr__(self): + return "%s(%s, %s)" % (self.__class__.__name__, + repr(self._name), + int(self._offset.total_seconds())) + + __reduce__ = object.__reduce__ + + +class tzlocal(_tzinfo): + """ + A :class:`tzinfo` subclass built around the ``time`` timezone functions. + """ + def __init__(self): + super(tzlocal, self).__init__() + + self._std_offset = datetime.timedelta(seconds=-time.timezone) + if time.daylight: + self._dst_offset = datetime.timedelta(seconds=-time.altzone) + else: + self._dst_offset = self._std_offset + + self._dst_saved = self._dst_offset - self._std_offset + self._hasdst = bool(self._dst_saved) + self._tznames = tuple(time.tzname) + + def utcoffset(self, dt): + if dt is None and self._hasdst: + return None + + if self._isdst(dt): + return self._dst_offset + else: + return self._std_offset + + def dst(self, dt): + if dt is None and self._hasdst: + return None + + if self._isdst(dt): + return self._dst_offset - self._std_offset + else: + return ZERO + + @tzname_in_python2 + def tzname(self, dt): + return self._tznames[self._isdst(dt)] + + def is_ambiguous(self, dt): + """ + Whether or not the "wall time" of a given datetime is ambiguous in this + zone. + + :param dt: + A :py:class:`datetime.datetime`, naive or time zone aware. + + + :return: + Returns ``True`` if ambiguous, ``False`` otherwise. + + .. versionadded:: 2.6.0 + """ + naive_dst = self._naive_is_dst(dt) + return (not naive_dst and + (naive_dst != self._naive_is_dst(dt - self._dst_saved))) + + def _naive_is_dst(self, dt): + timestamp = _datetime_to_timestamp(dt) + return time.localtime(timestamp + time.timezone).tm_isdst + + def _isdst(self, dt, fold_naive=True): + # We can't use mktime here. It is unstable when deciding if + # the hour near to a change is DST or not. + # + # timestamp = time.mktime((dt.year, dt.month, dt.day, dt.hour, + # dt.minute, dt.second, dt.weekday(), 0, -1)) + # return time.localtime(timestamp).tm_isdst + # + # The code above yields the following result: + # + # >>> import tz, datetime + # >>> t = tz.tzlocal() + # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname() + # 'BRDT' + # >>> datetime.datetime(2003,2,16,0,tzinfo=t).tzname() + # 'BRST' + # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname() + # 'BRST' + # >>> datetime.datetime(2003,2,15,22,tzinfo=t).tzname() + # 'BRDT' + # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname() + # 'BRDT' + # + # Here is a more stable implementation: + # + if not self._hasdst: + return False + + # Check for ambiguous times: + dstval = self._naive_is_dst(dt) + fold = getattr(dt, 'fold', None) + + if self.is_ambiguous(dt): + if fold is not None: + return not self._fold(dt) + else: + return True + + return dstval + + def __eq__(self, other): + if isinstance(other, tzlocal): + return (self._std_offset == other._std_offset and + self._dst_offset == other._dst_offset) + elif isinstance(other, tzutc): + return (not self._hasdst and + self._tznames[0] in {'UTC', 'GMT'} and + self._std_offset == ZERO) + elif isinstance(other, tzoffset): + return (not self._hasdst and + self._tznames[0] == other._name and + self._std_offset == other._offset) + else: + return NotImplemented + + __hash__ = None + + def __ne__(self, other): + return not (self == other) + + def __repr__(self): + return "%s()" % self.__class__.__name__ + + __reduce__ = object.__reduce__ + + +class _ttinfo(object): + __slots__ = ["offset", "delta", "isdst", "abbr", + "isstd", "isgmt", "dstoffset"] + + def __init__(self): + for attr in self.__slots__: + setattr(self, attr, None) + + def __repr__(self): + l = [] + for attr in self.__slots__: + value = getattr(self, attr) + if value is not None: + l.append("%s=%s" % (attr, repr(value))) + return "%s(%s)" % (self.__class__.__name__, ", ".join(l)) + + def __eq__(self, other): + if not isinstance(other, _ttinfo): + return NotImplemented + + return (self.offset == other.offset and + self.delta == other.delta and + self.isdst == other.isdst and + self.abbr == other.abbr and + self.isstd == other.isstd and + self.isgmt == other.isgmt and + self.dstoffset == other.dstoffset) + + __hash__ = None + + def __ne__(self, other): + return not (self == other) + + def __getstate__(self): + state = {} + for name in self.__slots__: + state[name] = getattr(self, name, None) + return state + + def __setstate__(self, state): + for name in self.__slots__: + if name in state: + setattr(self, name, state[name]) + + +class _tzfile(object): + """ + Lightweight class for holding the relevant transition and time zone + information read from binary tzfiles. + """ + attrs = ['trans_list', 'trans_list_utc', 'trans_idx', 'ttinfo_list', + 'ttinfo_std', 'ttinfo_dst', 'ttinfo_before', 'ttinfo_first'] + + def __init__(self, **kwargs): + for attr in self.attrs: + setattr(self, attr, kwargs.get(attr, None)) + + +class tzfile(_tzinfo): + """ + This is a ``tzinfo`` subclass that allows one to use the ``tzfile(5)`` + format timezone files to extract current and historical zone information. + + :param fileobj: + This can be an opened file stream or a file name that the time zone + information can be read from. + + :param filename: + This is an optional parameter specifying the source of the time zone + information in the event that ``fileobj`` is a file object. If omitted + and ``fileobj`` is a file stream, this parameter will be set either to + ``fileobj``'s ``name`` attribute or to ``repr(fileobj)``. + + See `Sources for Time Zone and Daylight Saving Time Data + <https://data.iana.org/time-zones/tz-link.html>`_ for more information. + Time zone files can be compiled from the `IANA Time Zone database files + <https://www.iana.org/time-zones>`_ with the `zic time zone compiler + <https://www.freebsd.org/cgi/man.cgi?query=zic&sektion=8>`_ + + .. note:: + + Only construct a ``tzfile`` directly if you have a specific timezone + file on disk that you want to read into a Python ``tzinfo`` object. + If you want to get a ``tzfile`` representing a specific IANA zone, + (e.g. ``'America/New_York'``), you should call + :func:`dateutil.tz.gettz` with the zone identifier. + + + **Examples:** + + Using the US Eastern time zone as an example, we can see that a ``tzfile`` + provides time zone information for the standard Daylight Saving offsets: + + .. testsetup:: tzfile + + from pipenv.vendor.dateutil.tz import gettz + from datetime import datetime + + .. doctest:: tzfile + + >>> NYC = gettz('America/New_York') + >>> NYC + tzfile('/usr/share/zoneinfo/America/New_York') + + >>> print(datetime(2016, 1, 3, tzinfo=NYC)) # EST + 2016-01-03 00:00:00-05:00 + + >>> print(datetime(2016, 7, 7, tzinfo=NYC)) # EDT + 2016-07-07 00:00:00-04:00 + + + The ``tzfile`` structure contains a fully history of the time zone, + so historical dates will also have the right offsets. For example, before + the adoption of the UTC standards, New York used local solar mean time: + + .. doctest:: tzfile + + >>> print(datetime(1901, 4, 12, tzinfo=NYC)) # LMT + 1901-04-12 00:00:00-04:56 + + And during World War II, New York was on "Eastern War Time", which was a + state of permanent daylight saving time: + + .. doctest:: tzfile + + >>> print(datetime(1944, 2, 7, tzinfo=NYC)) # EWT + 1944-02-07 00:00:00-04:00 + + """ + + def __init__(self, fileobj, filename=None): + super(tzfile, self).__init__() + + file_opened_here = False + if isinstance(fileobj, string_types): + self._filename = fileobj + fileobj = open(fileobj, 'rb') + file_opened_here = True + elif filename is not None: + self._filename = filename + elif hasattr(fileobj, "name"): + self._filename = fileobj.name + else: + self._filename = repr(fileobj) + + if fileobj is not None: + if not file_opened_here: + fileobj = _nullcontext(fileobj) + + with fileobj as file_stream: + tzobj = self._read_tzfile(file_stream) + + self._set_tzdata(tzobj) + + def _set_tzdata(self, tzobj): + """ Set the time zone data of this object from a _tzfile object """ + # Copy the relevant attributes over as private attributes + for attr in _tzfile.attrs: + setattr(self, '_' + attr, getattr(tzobj, attr)) + + def _read_tzfile(self, fileobj): + out = _tzfile() + + # From tzfile(5): + # + # The time zone information files used by tzset(3) + # begin with the magic characters "TZif" to identify + # them as time zone information files, followed by + # sixteen bytes reserved for future use, followed by + # six four-byte values of type long, written in a + # ``standard'' byte order (the high-order byte + # of the value is written first). + if fileobj.read(4).decode() != "TZif": + raise ValueError("magic not found") + + fileobj.read(16) + + ( + # The number of UTC/local indicators stored in the file. + ttisgmtcnt, + + # The number of standard/wall indicators stored in the file. + ttisstdcnt, + + # The number of leap seconds for which data is + # stored in the file. + leapcnt, + + # The number of "transition times" for which data + # is stored in the file. + timecnt, + + # The number of "local time types" for which data + # is stored in the file (must not be zero). + typecnt, + + # The number of characters of "time zone + # abbreviation strings" stored in the file. + charcnt, + + ) = struct.unpack(">6l", fileobj.read(24)) + + # The above header is followed by tzh_timecnt four-byte + # values of type long, sorted in ascending order. + # These values are written in ``standard'' byte order. + # Each is used as a transition time (as returned by + # time(2)) at which the rules for computing local time + # change. + + if timecnt: + out.trans_list_utc = list(struct.unpack(">%dl" % timecnt, + fileobj.read(timecnt*4))) + else: + out.trans_list_utc = [] + + # Next come tzh_timecnt one-byte values of type unsigned + # char; each one tells which of the different types of + # ``local time'' types described in the file is associated + # with the same-indexed transition time. These values + # serve as indices into an array of ttinfo structures that + # appears next in the file. + + if timecnt: + out.trans_idx = struct.unpack(">%dB" % timecnt, + fileobj.read(timecnt)) + else: + out.trans_idx = [] + + # Each ttinfo structure is written as a four-byte value + # for tt_gmtoff of type long, in a standard byte + # order, followed by a one-byte value for tt_isdst + # and a one-byte value for tt_abbrind. In each + # structure, tt_gmtoff gives the number of + # seconds to be added to UTC, tt_isdst tells whether + # tm_isdst should be set by localtime(3), and + # tt_abbrind serves as an index into the array of + # time zone abbreviation characters that follow the + # ttinfo structure(s) in the file. + + ttinfo = [] + + for i in range(typecnt): + ttinfo.append(struct.unpack(">lbb", fileobj.read(6))) + + abbr = fileobj.read(charcnt).decode() + + # Then there are tzh_leapcnt pairs of four-byte + # values, written in standard byte order; the + # first value of each pair gives the time (as + # returned by time(2)) at which a leap second + # occurs; the second gives the total number of + # leap seconds to be applied after the given time. + # The pairs of values are sorted in ascending order + # by time. + + # Not used, for now (but seek for correct file position) + if leapcnt: + fileobj.seek(leapcnt * 8, os.SEEK_CUR) + + # Then there are tzh_ttisstdcnt standard/wall + # indicators, each stored as a one-byte value; + # they tell whether the transition times associated + # with local time types were specified as standard + # time or wall clock time, and are used when + # a time zone file is used in handling POSIX-style + # time zone environment variables. + + if ttisstdcnt: + isstd = struct.unpack(">%db" % ttisstdcnt, + fileobj.read(ttisstdcnt)) + + # Finally, there are tzh_ttisgmtcnt UTC/local + # indicators, each stored as a one-byte value; + # they tell whether the transition times associated + # with local time types were specified as UTC or + # local time, and are used when a time zone file + # is used in handling POSIX-style time zone envi- + # ronment variables. + + if ttisgmtcnt: + isgmt = struct.unpack(">%db" % ttisgmtcnt, + fileobj.read(ttisgmtcnt)) + + # Build ttinfo list + out.ttinfo_list = [] + for i in range(typecnt): + gmtoff, isdst, abbrind = ttinfo[i] + gmtoff = _get_supported_offset(gmtoff) + tti = _ttinfo() + tti.offset = gmtoff + tti.dstoffset = datetime.timedelta(0) + tti.delta = datetime.timedelta(seconds=gmtoff) + tti.isdst = isdst + tti.abbr = abbr[abbrind:abbr.find('\x00', abbrind)] + tti.isstd = (ttisstdcnt > i and isstd[i] != 0) + tti.isgmt = (ttisgmtcnt > i and isgmt[i] != 0) + out.ttinfo_list.append(tti) + + # Replace ttinfo indexes for ttinfo objects. + out.trans_idx = [out.ttinfo_list[idx] for idx in out.trans_idx] + + # Set standard, dst, and before ttinfos. before will be + # used when a given time is before any transitions, + # and will be set to the first non-dst ttinfo, or to + # the first dst, if all of them are dst. + out.ttinfo_std = None + out.ttinfo_dst = None + out.ttinfo_before = None + if out.ttinfo_list: + if not out.trans_list_utc: + out.ttinfo_std = out.ttinfo_first = out.ttinfo_list[0] + else: + for i in range(timecnt-1, -1, -1): + tti = out.trans_idx[i] + if not out.ttinfo_std and not tti.isdst: + out.ttinfo_std = tti + elif not out.ttinfo_dst and tti.isdst: + out.ttinfo_dst = tti + + if out.ttinfo_std and out.ttinfo_dst: + break + else: + if out.ttinfo_dst and not out.ttinfo_std: + out.ttinfo_std = out.ttinfo_dst + + for tti in out.ttinfo_list: + if not tti.isdst: + out.ttinfo_before = tti + break + else: + out.ttinfo_before = out.ttinfo_list[0] + + # Now fix transition times to become relative to wall time. + # + # I'm not sure about this. In my tests, the tz source file + # is setup to wall time, and in the binary file isstd and + # isgmt are off, so it should be in wall time. OTOH, it's + # always in gmt time. Let me know if you have comments + # about this. + lastdst = None + lastoffset = None + lastdstoffset = None + lastbaseoffset = None + out.trans_list = [] + + for i, tti in enumerate(out.trans_idx): + offset = tti.offset + dstoffset = 0 + + if lastdst is not None: + if tti.isdst: + if not lastdst: + dstoffset = offset - lastoffset + + if not dstoffset and lastdstoffset: + dstoffset = lastdstoffset + + tti.dstoffset = datetime.timedelta(seconds=dstoffset) + lastdstoffset = dstoffset + + # If a time zone changes its base offset during a DST transition, + # then you need to adjust by the previous base offset to get the + # transition time in local time. Otherwise you use the current + # base offset. Ideally, I would have some mathematical proof of + # why this is true, but I haven't really thought about it enough. + baseoffset = offset - dstoffset + adjustment = baseoffset + if (lastbaseoffset is not None and baseoffset != lastbaseoffset + and tti.isdst != lastdst): + # The base DST has changed + adjustment = lastbaseoffset + + lastdst = tti.isdst + lastoffset = offset + lastbaseoffset = baseoffset + + out.trans_list.append(out.trans_list_utc[i] + adjustment) + + out.trans_idx = tuple(out.trans_idx) + out.trans_list = tuple(out.trans_list) + out.trans_list_utc = tuple(out.trans_list_utc) + + return out + + def _find_last_transition(self, dt, in_utc=False): + # If there's no list, there are no transitions to find + if not self._trans_list: + return None + + timestamp = _datetime_to_timestamp(dt) + + # Find where the timestamp fits in the transition list - if the + # timestamp is a transition time, it's part of the "after" period. + trans_list = self._trans_list_utc if in_utc else self._trans_list + idx = bisect.bisect_right(trans_list, timestamp) + + # We want to know when the previous transition was, so subtract off 1 + return idx - 1 + + def _get_ttinfo(self, idx): + # For no list or after the last transition, default to _ttinfo_std + if idx is None or (idx + 1) >= len(self._trans_list): + return self._ttinfo_std + + # If there is a list and the time is before it, return _ttinfo_before + if idx < 0: + return self._ttinfo_before + + return self._trans_idx[idx] + + def _find_ttinfo(self, dt): + idx = self._resolve_ambiguous_time(dt) + + return self._get_ttinfo(idx) + + def fromutc(self, dt): + """ + The ``tzfile`` implementation of :py:func:`datetime.tzinfo.fromutc`. + + :param dt: + A :py:class:`datetime.datetime` object. + + :raises TypeError: + Raised if ``dt`` is not a :py:class:`datetime.datetime` object. + + :raises ValueError: + Raised if this is called with a ``dt`` which does not have this + ``tzinfo`` attached. + + :return: + Returns a :py:class:`datetime.datetime` object representing the + wall time in ``self``'s time zone. + """ + # These isinstance checks are in datetime.tzinfo, so we'll preserve + # them, even if we don't care about duck typing. + if not isinstance(dt, datetime.datetime): + raise TypeError("fromutc() requires a datetime argument") + + if dt.tzinfo is not self: + raise ValueError("dt.tzinfo is not self") + + # First treat UTC as wall time and get the transition we're in. + idx = self._find_last_transition(dt, in_utc=True) + tti = self._get_ttinfo(idx) + + dt_out = dt + datetime.timedelta(seconds=tti.offset) + + fold = self.is_ambiguous(dt_out, idx=idx) + + return enfold(dt_out, fold=int(fold)) + + def is_ambiguous(self, dt, idx=None): + """ + Whether or not the "wall time" of a given datetime is ambiguous in this + zone. + + :param dt: + A :py:class:`datetime.datetime`, naive or time zone aware. + + + :return: + Returns ``True`` if ambiguous, ``False`` otherwise. + + .. versionadded:: 2.6.0 + """ + if idx is None: + idx = self._find_last_transition(dt) + + # Calculate the difference in offsets from current to previous + timestamp = _datetime_to_timestamp(dt) + tti = self._get_ttinfo(idx) + + if idx is None or idx <= 0: + return False + + od = self._get_ttinfo(idx - 1).offset - tti.offset + tt = self._trans_list[idx] # Transition time + + return timestamp < tt + od + + def _resolve_ambiguous_time(self, dt): + idx = self._find_last_transition(dt) + + # If we have no transitions, return the index + _fold = self._fold(dt) + if idx is None or idx == 0: + return idx + + # If it's ambiguous and we're in a fold, shift to a different index. + idx_offset = int(not _fold and self.is_ambiguous(dt, idx)) + + return idx - idx_offset + + def utcoffset(self, dt): + if dt is None: + return None + + if not self._ttinfo_std: + return ZERO + + return self._find_ttinfo(dt).delta + + def dst(self, dt): + if dt is None: + return None + + if not self._ttinfo_dst: + return ZERO + + tti = self._find_ttinfo(dt) + + if not tti.isdst: + return ZERO + + # The documentation says that utcoffset()-dst() must + # be constant for every dt. + return tti.dstoffset + + @tzname_in_python2 + def tzname(self, dt): + if not self._ttinfo_std or dt is None: + return None + return self._find_ttinfo(dt).abbr + + def __eq__(self, other): + if not isinstance(other, tzfile): + return NotImplemented + return (self._trans_list == other._trans_list and + self._trans_idx == other._trans_idx and + self._ttinfo_list == other._ttinfo_list) + + __hash__ = None + + def __ne__(self, other): + return not (self == other) + + def __repr__(self): + return "%s(%s)" % (self.__class__.__name__, repr(self._filename)) + + def __reduce__(self): + return self.__reduce_ex__(None) + + def __reduce_ex__(self, protocol): + return (self.__class__, (None, self._filename), self.__dict__) + + +class tzrange(tzrangebase): + """ + The ``tzrange`` object is a time zone specified by a set of offsets and + abbreviations, equivalent to the way the ``TZ`` variable can be specified + in POSIX-like systems, but using Python delta objects to specify DST + start, end and offsets. + + :param stdabbr: + The abbreviation for standard time (e.g. ``'EST'``). + + :param stdoffset: + An integer or :class:`datetime.timedelta` object or equivalent + specifying the base offset from UTC. + + If unspecified, +00:00 is used. + + :param dstabbr: + The abbreviation for DST / "Summer" time (e.g. ``'EDT'``). + + If specified, with no other DST information, DST is assumed to occur + and the default behavior or ``dstoffset``, ``start`` and ``end`` is + used. If unspecified and no other DST information is specified, it + is assumed that this zone has no DST. + + If this is unspecified and other DST information is *is* specified, + DST occurs in the zone but the time zone abbreviation is left + unchanged. + + :param dstoffset: + A an integer or :class:`datetime.timedelta` object or equivalent + specifying the UTC offset during DST. If unspecified and any other DST + information is specified, it is assumed to be the STD offset +1 hour. + + :param start: + A :class:`relativedelta.relativedelta` object or equivalent specifying + the time and time of year that daylight savings time starts. To + specify, for example, that DST starts at 2AM on the 2nd Sunday in + March, pass: + + ``relativedelta(hours=2, month=3, day=1, weekday=SU(+2))`` + + If unspecified and any other DST information is specified, the default + value is 2 AM on the first Sunday in April. + + :param end: + A :class:`relativedelta.relativedelta` object or equivalent + representing the time and time of year that daylight savings time + ends, with the same specification method as in ``start``. One note is + that this should point to the first time in the *standard* zone, so if + a transition occurs at 2AM in the DST zone and the clocks are set back + 1 hour to 1AM, set the ``hours`` parameter to +1. + + + **Examples:** + + .. testsetup:: tzrange + + from pipenv.vendor.dateutil.tz import tzrange, tzstr + + .. doctest:: tzrange + + >>> tzstr('EST5EDT') == tzrange("EST", -18000, "EDT") + True + + >>> from dateutil.relativedelta import * + >>> range1 = tzrange("EST", -18000, "EDT") + >>> range2 = tzrange("EST", -18000, "EDT", -14400, + ... relativedelta(hours=+2, month=4, day=1, + ... weekday=SU(+1)), + ... relativedelta(hours=+1, month=10, day=31, + ... weekday=SU(-1))) + >>> tzstr('EST5EDT') == range1 == range2 + True + + """ + def __init__(self, stdabbr, stdoffset=None, + dstabbr=None, dstoffset=None, + start=None, end=None): + + global relativedelta + from pipenv.vendor.dateutil import relativedelta + + self._std_abbr = stdabbr + self._dst_abbr = dstabbr + + try: + stdoffset = stdoffset.total_seconds() + except (TypeError, AttributeError): + pass + + try: + dstoffset = dstoffset.total_seconds() + except (TypeError, AttributeError): + pass + + if stdoffset is not None: + self._std_offset = datetime.timedelta(seconds=stdoffset) + else: + self._std_offset = ZERO + + if dstoffset is not None: + self._dst_offset = datetime.timedelta(seconds=dstoffset) + elif dstabbr and stdoffset is not None: + self._dst_offset = self._std_offset + datetime.timedelta(hours=+1) + else: + self._dst_offset = ZERO + + if dstabbr and start is None: + self._start_delta = relativedelta.relativedelta( + hours=+2, month=4, day=1, weekday=relativedelta.SU(+1)) + else: + self._start_delta = start + + if dstabbr and end is None: + self._end_delta = relativedelta.relativedelta( + hours=+1, month=10, day=31, weekday=relativedelta.SU(-1)) + else: + self._end_delta = end + + self._dst_base_offset_ = self._dst_offset - self._std_offset + self.hasdst = bool(self._start_delta) + + def transitions(self, year): + """ + For a given year, get the DST on and off transition times, expressed + always on the standard time side. For zones with no transitions, this + function returns ``None``. + + :param year: + The year whose transitions you would like to query. + + :return: + Returns a :class:`tuple` of :class:`datetime.datetime` objects, + ``(dston, dstoff)`` for zones with an annual DST transition, or + ``None`` for fixed offset zones. + """ + if not self.hasdst: + return None + + base_year = datetime.datetime(year, 1, 1) + + start = base_year + self._start_delta + end = base_year + self._end_delta + + return (start, end) + + def __eq__(self, other): + if not isinstance(other, tzrange): + return NotImplemented + + return (self._std_abbr == other._std_abbr and + self._dst_abbr == other._dst_abbr and + self._std_offset == other._std_offset and + self._dst_offset == other._dst_offset and + self._start_delta == other._start_delta and + self._end_delta == other._end_delta) + + @property + def _dst_base_offset(self): + return self._dst_base_offset_ + + +@six.add_metaclass(_TzStrFactory) +class tzstr(tzrange): + """ + ``tzstr`` objects are time zone objects specified by a time-zone string as + it would be passed to a ``TZ`` variable on POSIX-style systems (see + the `GNU C Library: TZ Variable`_ for more details). + + There is one notable exception, which is that POSIX-style time zones use an + inverted offset format, so normally ``GMT+3`` would be parsed as an offset + 3 hours *behind* GMT. The ``tzstr`` time zone object will parse this as an + offset 3 hours *ahead* of GMT. If you would like to maintain the POSIX + behavior, pass a ``True`` value to ``posix_offset``. + + The :class:`tzrange` object provides the same functionality, but is + specified using :class:`relativedelta.relativedelta` objects. rather than + strings. + + :param s: + A time zone string in ``TZ`` variable format. This can be a + :class:`bytes` (2.x: :class:`str`), :class:`str` (2.x: + :class:`unicode`) or a stream emitting unicode characters + (e.g. :class:`StringIO`). + + :param posix_offset: + Optional. If set to ``True``, interpret strings such as ``GMT+3`` or + ``UTC+3`` as being 3 hours *behind* UTC rather than ahead, per the + POSIX standard. + + .. caution:: + + Prior to version 2.7.0, this function also supported time zones + in the format: + + * ``EST5EDT,4,0,6,7200,10,0,26,7200,3600`` + * ``EST5EDT,4,1,0,7200,10,-1,0,7200,3600`` + + This format is non-standard and has been deprecated; this function + will raise a :class:`DeprecatedTZFormatWarning` until + support is removed in a future version. + + .. _`GNU C Library: TZ Variable`: + https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html + """ + def __init__(self, s, posix_offset=False): + global parser + from pipenv.vendor.dateutil.parser import _parser as parser + + self._s = s + + res = parser._parsetz(s) + if res is None or res.any_unused_tokens: + raise ValueError("unknown string format") + + # Here we break the compatibility with the TZ variable handling. + # GMT-3 actually *means* the timezone -3. + if res.stdabbr in ("GMT", "UTC") and not posix_offset: + res.stdoffset *= -1 + + # We must initialize it first, since _delta() needs + # _std_offset and _dst_offset set. Use False in start/end + # to avoid building it two times. + tzrange.__init__(self, res.stdabbr, res.stdoffset, + res.dstabbr, res.dstoffset, + start=False, end=False) + + if not res.dstabbr: + self._start_delta = None + self._end_delta = None + else: + self._start_delta = self._delta(res.start) + if self._start_delta: + self._end_delta = self._delta(res.end, isend=1) + + self.hasdst = bool(self._start_delta) + + def _delta(self, x, isend=0): + from pipenv.vendor.dateutil import relativedelta + kwargs = {} + if x.month is not None: + kwargs["month"] = x.month + if x.weekday is not None: + kwargs["weekday"] = relativedelta.weekday(x.weekday, x.week) + if x.week > 0: + kwargs["day"] = 1 + else: + kwargs["day"] = 31 + elif x.day: + kwargs["day"] = x.day + elif x.yday is not None: + kwargs["yearday"] = x.yday + elif x.jyday is not None: + kwargs["nlyearday"] = x.jyday + if not kwargs: + # Default is to start on first sunday of april, and end + # on last sunday of october. + if not isend: + kwargs["month"] = 4 + kwargs["day"] = 1 + kwargs["weekday"] = relativedelta.SU(+1) + else: + kwargs["month"] = 10 + kwargs["day"] = 31 + kwargs["weekday"] = relativedelta.SU(-1) + if x.time is not None: + kwargs["seconds"] = x.time + else: + # Default is 2AM. + kwargs["seconds"] = 7200 + if isend: + # Convert to standard time, to follow the documented way + # of working with the extra hour. See the documentation + # of the tzinfo class. + delta = self._dst_offset - self._std_offset + kwargs["seconds"] -= delta.seconds + delta.days * 86400 + return relativedelta.relativedelta(**kwargs) + + def __repr__(self): + return "%s(%s)" % (self.__class__.__name__, repr(self._s)) + + +class _tzicalvtzcomp(object): + def __init__(self, tzoffsetfrom, tzoffsetto, isdst, + tzname=None, rrule=None): + self.tzoffsetfrom = datetime.timedelta(seconds=tzoffsetfrom) + self.tzoffsetto = datetime.timedelta(seconds=tzoffsetto) + self.tzoffsetdiff = self.tzoffsetto - self.tzoffsetfrom + self.isdst = isdst + self.tzname = tzname + self.rrule = rrule + + +class _tzicalvtz(_tzinfo): + def __init__(self, tzid, comps=[]): + super(_tzicalvtz, self).__init__() + + self._tzid = tzid + self._comps = comps + self._cachedate = [] + self._cachecomp = [] + self._cache_lock = _thread.allocate_lock() + + def _find_comp(self, dt): + if len(self._comps) == 1: + return self._comps[0] + + dt = dt.replace(tzinfo=None) + + try: + with self._cache_lock: + return self._cachecomp[self._cachedate.index( + (dt, self._fold(dt)))] + except ValueError: + pass + + lastcompdt = None + lastcomp = None + + for comp in self._comps: + compdt = self._find_compdt(comp, dt) + + if compdt and (not lastcompdt or lastcompdt < compdt): + lastcompdt = compdt + lastcomp = comp + + if not lastcomp: + # RFC says nothing about what to do when a given + # time is before the first onset date. We'll look for the + # first standard component, or the first component, if + # none is found. + for comp in self._comps: + if not comp.isdst: + lastcomp = comp + break + else: + lastcomp = comp[0] + + with self._cache_lock: + self._cachedate.insert(0, (dt, self._fold(dt))) + self._cachecomp.insert(0, lastcomp) + + if len(self._cachedate) > 10: + self._cachedate.pop() + self._cachecomp.pop() + + return lastcomp + + def _find_compdt(self, comp, dt): + if comp.tzoffsetdiff < ZERO and self._fold(dt): + dt -= comp.tzoffsetdiff + + compdt = comp.rrule.before(dt, inc=True) + + return compdt + + def utcoffset(self, dt): + if dt is None: + return None + + return self._find_comp(dt).tzoffsetto + + def dst(self, dt): + comp = self._find_comp(dt) + if comp.isdst: + return comp.tzoffsetdiff + else: + return ZERO + + @tzname_in_python2 + def tzname(self, dt): + return self._find_comp(dt).tzname + + def __repr__(self): + return "<tzicalvtz %s>" % repr(self._tzid) + + __reduce__ = object.__reduce__ + + +class tzical(object): + """ + This object is designed to parse an iCalendar-style ``VTIMEZONE`` structure + as set out in `RFC 5545`_ Section 4.6.5 into one or more `tzinfo` objects. + + :param `fileobj`: + A file or stream in iCalendar format, which should be UTF-8 encoded + with CRLF endings. + + .. _`RFC 5545`: https://tools.ietf.org/html/rfc5545 + """ + def __init__(self, fileobj): + global rrule + from pipenv.vendor.dateutil import rrule + + if isinstance(fileobj, string_types): + self._s = fileobj + # ical should be encoded in UTF-8 with CRLF + fileobj = open(fileobj, 'r') + else: + self._s = getattr(fileobj, 'name', repr(fileobj)) + fileobj = _nullcontext(fileobj) + + self._vtz = {} + + with fileobj as fobj: + self._parse_rfc(fobj.read()) + + def keys(self): + """ + Retrieves the available time zones as a list. + """ + return list(self._vtz.keys()) + + def get(self, tzid=None): + """ + Retrieve a :py:class:`datetime.tzinfo` object by its ``tzid``. + + :param tzid: + If there is exactly one time zone available, omitting ``tzid`` + or passing :py:const:`None` value returns it. Otherwise a valid + key (which can be retrieved from :func:`keys`) is required. + + :raises ValueError: + Raised if ``tzid`` is not specified but there are either more + or fewer than 1 zone defined. + + :returns: + Returns either a :py:class:`datetime.tzinfo` object representing + the relevant time zone or :py:const:`None` if the ``tzid`` was + not found. + """ + if tzid is None: + if len(self._vtz) == 0: + raise ValueError("no timezones defined") + elif len(self._vtz) > 1: + raise ValueError("more than one timezone available") + tzid = next(iter(self._vtz)) + + return self._vtz.get(tzid) + + def _parse_offset(self, s): + s = s.strip() + if not s: + raise ValueError("empty offset") + if s[0] in ('+', '-'): + signal = (-1, +1)[s[0] == '+'] + s = s[1:] + else: + signal = +1 + if len(s) == 4: + return (int(s[:2]) * 3600 + int(s[2:]) * 60) * signal + elif len(s) == 6: + return (int(s[:2]) * 3600 + int(s[2:4]) * 60 + int(s[4:])) * signal + else: + raise ValueError("invalid offset: " + s) + + def _parse_rfc(self, s): + lines = s.splitlines() + if not lines: + raise ValueError("empty string") + + # Unfold + i = 0 + while i < len(lines): + line = lines[i].rstrip() + if not line: + del lines[i] + elif i > 0 and line[0] == " ": + lines[i-1] += line[1:] + del lines[i] + else: + i += 1 + + tzid = None + comps = [] + invtz = False + comptype = None + for line in lines: + if not line: + continue + name, value = line.split(':', 1) + parms = name.split(';') + if not parms: + raise ValueError("empty property name") + name = parms[0].upper() + parms = parms[1:] + if invtz: + if name == "BEGIN": + if value in ("STANDARD", "DAYLIGHT"): + # Process component + pass + else: + raise ValueError("unknown component: "+value) + comptype = value + founddtstart = False + tzoffsetfrom = None + tzoffsetto = None + rrulelines = [] + tzname = None + elif name == "END": + if value == "VTIMEZONE": + if comptype: + raise ValueError("component not closed: "+comptype) + if not tzid: + raise ValueError("mandatory TZID not found") + if not comps: + raise ValueError( + "at least one component is needed") + # Process vtimezone + self._vtz[tzid] = _tzicalvtz(tzid, comps) + invtz = False + elif value == comptype: + if not founddtstart: + raise ValueError("mandatory DTSTART not found") + if tzoffsetfrom is None: + raise ValueError( + "mandatory TZOFFSETFROM not found") + if tzoffsetto is None: + raise ValueError( + "mandatory TZOFFSETFROM not found") + # Process component + rr = None + if rrulelines: + rr = rrule.rrulestr("\n".join(rrulelines), + compatible=True, + ignoretz=True, + cache=True) + comp = _tzicalvtzcomp(tzoffsetfrom, tzoffsetto, + (comptype == "DAYLIGHT"), + tzname, rr) + comps.append(comp) + comptype = None + else: + raise ValueError("invalid component end: "+value) + elif comptype: + if name == "DTSTART": + # DTSTART in VTIMEZONE takes a subset of valid RRULE + # values under RFC 5545. + for parm in parms: + if parm != 'VALUE=DATE-TIME': + msg = ('Unsupported DTSTART param in ' + + 'VTIMEZONE: ' + parm) + raise ValueError(msg) + rrulelines.append(line) + founddtstart = True + elif name in ("RRULE", "RDATE", "EXRULE", "EXDATE"): + rrulelines.append(line) + elif name == "TZOFFSETFROM": + if parms: + raise ValueError( + "unsupported %s parm: %s " % (name, parms[0])) + tzoffsetfrom = self._parse_offset(value) + elif name == "TZOFFSETTO": + if parms: + raise ValueError( + "unsupported TZOFFSETTO parm: "+parms[0]) + tzoffsetto = self._parse_offset(value) + elif name == "TZNAME": + if parms: + raise ValueError( + "unsupported TZNAME parm: "+parms[0]) + tzname = value + elif name == "COMMENT": + pass + else: + raise ValueError("unsupported property: "+name) + else: + if name == "TZID": + if parms: + raise ValueError( + "unsupported TZID parm: "+parms[0]) + tzid = value + elif name in ("TZURL", "LAST-MODIFIED", "COMMENT"): + pass + else: + raise ValueError("unsupported property: "+name) + elif name == "BEGIN" and value == "VTIMEZONE": + tzid = None + comps = [] + invtz = True + + def __repr__(self): + return "%s(%s)" % (self.__class__.__name__, repr(self._s)) + + +if sys.platform != "win32": + TZFILES = ["/etc/localtime", "localtime"] + TZPATHS = ["/usr/share/zoneinfo", + "/usr/lib/zoneinfo", + "/usr/share/lib/zoneinfo", + "/etc/zoneinfo"] +else: + TZFILES = [] + TZPATHS = [] + + +def __get_gettz(): + tzlocal_classes = (tzlocal,) + if tzwinlocal is not None: + tzlocal_classes += (tzwinlocal,) + + class GettzFunc(object): + """ + Retrieve a time zone object from a string representation + + This function is intended to retrieve the :py:class:`tzinfo` subclass + that best represents the time zone that would be used if a POSIX + `TZ variable`_ were set to the same value. + + If no argument or an empty string is passed to ``gettz``, local time + is returned: + + .. code-block:: python3 + + >>> gettz() + tzfile('/etc/localtime') + + This function is also the preferred way to map IANA tz database keys + to :class:`tzfile` objects: + + .. code-block:: python3 + + >>> gettz('Pacific/Kiritimati') + tzfile('/usr/share/zoneinfo/Pacific/Kiritimati') + + On Windows, the standard is extended to include the Windows-specific + zone names provided by the operating system: + + .. code-block:: python3 + + >>> gettz('Egypt Standard Time') + tzwin('Egypt Standard Time') + + Passing a GNU ``TZ`` style string time zone specification returns a + :class:`tzstr` object: + + .. code-block:: python3 + + >>> gettz('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3') + tzstr('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3') + + :param name: + A time zone name (IANA, or, on Windows, Windows keys), location of + a ``tzfile(5)`` zoneinfo file or ``TZ`` variable style time zone + specifier. An empty string, no argument or ``None`` is interpreted + as local time. + + :return: + Returns an instance of one of ``dateutil``'s :py:class:`tzinfo` + subclasses. + + .. versionchanged:: 2.7.0 + + After version 2.7.0, any two calls to ``gettz`` using the same + input strings will return the same object: + + .. code-block:: python3 + + >>> tz.gettz('America/Chicago') is tz.gettz('America/Chicago') + True + + In addition to improving performance, this ensures that + `"same zone" semantics`_ are used for datetimes in the same zone. + + + .. _`TZ variable`: + https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html + + .. _`"same zone" semantics`: + https://blog.ganssle.io/articles/2018/02/aware-datetime-arithmetic.html + """ + def __init__(self): + + self.__instances = weakref.WeakValueDictionary() + self.__strong_cache_size = 8 + self.__strong_cache = OrderedDict() + self._cache_lock = _thread.allocate_lock() + + def __call__(self, name=None): + with self._cache_lock: + rv = self.__instances.get(name, None) + + if rv is None: + rv = self.nocache(name=name) + if not (name is None + or isinstance(rv, tzlocal_classes) + or rv is None): + # tzlocal is slightly more complicated than the other + # time zone providers because it depends on environment + # at construction time, so don't cache that. + # + # We also cannot store weak references to None, so we + # will also not store that. + self.__instances[name] = rv + else: + # No need for strong caching, return immediately + return rv + + self.__strong_cache[name] = self.__strong_cache.pop(name, rv) + + if len(self.__strong_cache) > self.__strong_cache_size: + self.__strong_cache.popitem(last=False) + + return rv + + def set_cache_size(self, size): + with self._cache_lock: + self.__strong_cache_size = size + while len(self.__strong_cache) > size: + self.__strong_cache.popitem(last=False) + + def cache_clear(self): + with self._cache_lock: + self.__instances = weakref.WeakValueDictionary() + self.__strong_cache.clear() + + @staticmethod + def nocache(name=None): + """A non-cached version of gettz""" + tz = None + if not name: + try: + name = os.environ["TZ"] + except KeyError: + pass + if name is None or name in ("", ":"): + for filepath in TZFILES: + if not os.path.isabs(filepath): + filename = filepath + for path in TZPATHS: + filepath = os.path.join(path, filename) + if os.path.isfile(filepath): + break + else: + continue + if os.path.isfile(filepath): + try: + tz = tzfile(filepath) + break + except (IOError, OSError, ValueError): + pass + else: + tz = tzlocal() + else: + try: + if name.startswith(":"): + name = name[1:] + except TypeError as e: + if isinstance(name, bytes): + new_msg = "gettz argument should be str, not bytes" + six.raise_from(TypeError(new_msg), e) + else: + raise + if os.path.isabs(name): + if os.path.isfile(name): + tz = tzfile(name) + else: + tz = None + else: + for path in TZPATHS: + filepath = os.path.join(path, name) + if not os.path.isfile(filepath): + filepath = filepath.replace(' ', '_') + if not os.path.isfile(filepath): + continue + try: + tz = tzfile(filepath) + break + except (IOError, OSError, ValueError): + pass + else: + tz = None + if tzwin is not None: + try: + tz = tzwin(name) + except (WindowsError, UnicodeEncodeError): + # UnicodeEncodeError is for Python 2.7 compat + tz = None + + if not tz: + from pipenv.vendor.dateutil.zoneinfo import get_zonefile_instance + tz = get_zonefile_instance().get(name) + + if not tz: + for c in name: + # name is not a tzstr unless it has at least + # one offset. For short values of "name", an + # explicit for loop seems to be the fastest way + # To determine if a string contains a digit + if c in "0123456789": + try: + tz = tzstr(name) + except ValueError: + pass + break + else: + if name in ("GMT", "UTC"): + tz = UTC + elif name in time.tzname: + tz = tzlocal() + return tz + + return GettzFunc() + + +gettz = __get_gettz() +del __get_gettz + + +def datetime_exists(dt, tz=None): + """ + Given a datetime and a time zone, determine whether or not a given datetime + would fall in a gap. + + :param dt: + A :class:`datetime.datetime` (whose time zone will be ignored if ``tz`` + is provided.) + + :param tz: + A :class:`datetime.tzinfo` with support for the ``fold`` attribute. If + ``None`` or not provided, the datetime's own time zone will be used. + + :return: + Returns a boolean value whether or not the "wall time" exists in + ``tz``. + + .. versionadded:: 2.7.0 + """ + if tz is None: + if dt.tzinfo is None: + raise ValueError('Datetime is naive and no time zone provided.') + tz = dt.tzinfo + + dt = dt.replace(tzinfo=None) + + # This is essentially a test of whether or not the datetime can survive + # a round trip to UTC. + dt_rt = dt.replace(tzinfo=tz).astimezone(UTC).astimezone(tz) + dt_rt = dt_rt.replace(tzinfo=None) + + return dt == dt_rt + + +def datetime_ambiguous(dt, tz=None): + """ + Given a datetime and a time zone, determine whether or not a given datetime + is ambiguous (i.e if there are two times differentiated only by their DST + status). + + :param dt: + A :class:`datetime.datetime` (whose time zone will be ignored if ``tz`` + is provided.) + + :param tz: + A :class:`datetime.tzinfo` with support for the ``fold`` attribute. If + ``None`` or not provided, the datetime's own time zone will be used. + + :return: + Returns a boolean value whether or not the "wall time" is ambiguous in + ``tz``. + + .. versionadded:: 2.6.0 + """ + if tz is None: + if dt.tzinfo is None: + raise ValueError('Datetime is naive and no time zone provided.') + + tz = dt.tzinfo + + # If a time zone defines its own "is_ambiguous" function, we'll use that. + is_ambiguous_fn = getattr(tz, 'is_ambiguous', None) + if is_ambiguous_fn is not None: + try: + return tz.is_ambiguous(dt) + except Exception: + pass + + # If it doesn't come out and tell us it's ambiguous, we'll just check if + # the fold attribute has any effect on this particular date and time. + dt = dt.replace(tzinfo=tz) + wall_0 = enfold(dt, fold=0) + wall_1 = enfold(dt, fold=1) + + same_offset = wall_0.utcoffset() == wall_1.utcoffset() + same_dst = wall_0.dst() == wall_1.dst() + + return not (same_offset and same_dst) + + +def resolve_imaginary(dt): + """ + Given a datetime that may be imaginary, return an existing datetime. + + This function assumes that an imaginary datetime represents what the + wall time would be in a zone had the offset transition not occurred, so + it will always fall forward by the transition's change in offset. + + .. doctest:: + + >>> from dateutil import tz + >>> from datetime import datetime + >>> NYC = tz.gettz('America/New_York') + >>> print(tz.resolve_imaginary(datetime(2017, 3, 12, 2, 30, tzinfo=NYC))) + 2017-03-12 03:30:00-04:00 + + >>> KIR = tz.gettz('Pacific/Kiritimati') + >>> print(tz.resolve_imaginary(datetime(1995, 1, 1, 12, 30, tzinfo=KIR))) + 1995-01-02 12:30:00+14:00 + + As a note, :func:`datetime.astimezone` is guaranteed to produce a valid, + existing datetime, so a round-trip to and from UTC is sufficient to get + an extant datetime, however, this generally "falls back" to an earlier time + rather than falling forward to the STD side (though no guarantees are made + about this behavior). + + :param dt: + A :class:`datetime.datetime` which may or may not exist. + + :return: + Returns an existing :class:`datetime.datetime`. If ``dt`` was not + imaginary, the datetime returned is guaranteed to be the same object + passed to the function. + + .. versionadded:: 2.7.0 + """ + if dt.tzinfo is not None and not datetime_exists(dt): + + curr_offset = (dt + datetime.timedelta(hours=24)).utcoffset() + old_offset = (dt - datetime.timedelta(hours=24)).utcoffset() + + dt += curr_offset - old_offset + + return dt + + +def _datetime_to_timestamp(dt): + """ + Convert a :class:`datetime.datetime` object to an epoch timestamp in + seconds since January 1, 1970, ignoring the time zone. + """ + return (dt.replace(tzinfo=None) - EPOCH).total_seconds() + + +if sys.version_info >= (3, 6): + def _get_supported_offset(second_offset): + return second_offset +else: + def _get_supported_offset(second_offset): + # For python pre-3.6, round to full-minutes if that's not the case. + # Python's datetime doesn't accept sub-minute timezones. Check + # http://python.org/sf/1447945 or https://bugs.python.org/issue5288 + # for some information. + old_offset = second_offset + calculated_offset = 60 * ((second_offset + 30) // 60) + return calculated_offset + + +try: + # Python 3.7 feature + from contextlib import nullcontext as _nullcontext +except ImportError: + class _nullcontext(object): + """ + Class for wrapping contexts so that they are passed through in a + with statement. + """ + def __init__(self, context): + self.context = context + + def __enter__(self): + return self.context + + def __exit__(*args, **kwargs): + pass + +# vim:ts=4:sw=4:et diff --git a/pipenv/vendor/dateutil/tz/win.py b/pipenv/vendor/dateutil/tz/win.py new file mode 100644 index 00000000..c366aabd --- /dev/null +++ b/pipenv/vendor/dateutil/tz/win.py @@ -0,0 +1,370 @@ +# -*- coding: utf-8 -*- +""" +This module provides an interface to the native time zone data on Windows, +including :py:class:`datetime.tzinfo` implementations. + +Attempting to import this module on a non-Windows platform will raise an +:py:obj:`ImportError`. +""" +# This code was originally contributed by Jeffrey Harris. +import datetime +import struct + +from pipenv.vendor.six.moves import winreg +from pipenv.vendor.six import text_type + +try: + import ctypes + from ctypes import wintypes +except ValueError: + # ValueError is raised on non-Windows systems for some horrible reason. + raise ImportError("Running tzwin on non-Windows system") + +from ._common import tzrangebase + +__all__ = ["tzwin", "tzwinlocal", "tzres"] + +ONEWEEK = datetime.timedelta(7) + +TZKEYNAMENT = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones" +TZKEYNAME9X = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones" +TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation" + + +def _settzkeyname(): + handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) + try: + winreg.OpenKey(handle, TZKEYNAMENT).Close() + TZKEYNAME = TZKEYNAMENT + except WindowsError: + TZKEYNAME = TZKEYNAME9X + handle.Close() + return TZKEYNAME + + +TZKEYNAME = _settzkeyname() + + +class tzres(object): + """ + Class for accessing ``tzres.dll``, which contains timezone name related + resources. + + .. versionadded:: 2.5.0 + """ + p_wchar = ctypes.POINTER(wintypes.WCHAR) # Pointer to a wide char + + def __init__(self, tzres_loc='tzres.dll'): + # Load the user32 DLL so we can load strings from tzres + user32 = ctypes.WinDLL('user32') + + # Specify the LoadStringW function + user32.LoadStringW.argtypes = (wintypes.HINSTANCE, + wintypes.UINT, + wintypes.LPWSTR, + ctypes.c_int) + + self.LoadStringW = user32.LoadStringW + self._tzres = ctypes.WinDLL(tzres_loc) + self.tzres_loc = tzres_loc + + def load_name(self, offset): + """ + Load a timezone name from a DLL offset (integer). + + >>> from dateutil.tzwin import tzres + >>> tzr = tzres() + >>> print(tzr.load_name(112)) + 'Eastern Standard Time' + + :param offset: + A positive integer value referring to a string from the tzres dll. + + .. note:: + + Offsets found in the registry are generally of the form + ``@tzres.dll,-114``. The offset in this case is 114, not -114. + + """ + resource = self.p_wchar() + lpBuffer = ctypes.cast(ctypes.byref(resource), wintypes.LPWSTR) + nchar = self.LoadStringW(self._tzres._handle, offset, lpBuffer, 0) + return resource[:nchar] + + def name_from_string(self, tzname_str): + """ + Parse strings as returned from the Windows registry into the time zone + name as defined in the registry. + + >>> from dateutil.tzwin import tzres + >>> tzr = tzres() + >>> print(tzr.name_from_string('@tzres.dll,-251')) + 'Dateline Daylight Time' + >>> print(tzr.name_from_string('Eastern Standard Time')) + 'Eastern Standard Time' + + :param tzname_str: + A timezone name string as returned from a Windows registry key. + + :return: + Returns the localized timezone string from tzres.dll if the string + is of the form `@tzres.dll,-offset`, else returns the input string. + """ + if not tzname_str.startswith('@'): + return tzname_str + + name_splt = tzname_str.split(',-') + try: + offset = int(name_splt[1]) + except: + raise ValueError("Malformed timezone string.") + + return self.load_name(offset) + + +class tzwinbase(tzrangebase): + """tzinfo class based on win32's timezones available in the registry.""" + def __init__(self): + raise NotImplementedError('tzwinbase is an abstract base class') + + def __eq__(self, other): + # Compare on all relevant dimensions, including name. + if not isinstance(other, tzwinbase): + return NotImplemented + + return (self._std_offset == other._std_offset and + self._dst_offset == other._dst_offset and + self._stddayofweek == other._stddayofweek and + self._dstdayofweek == other._dstdayofweek and + self._stdweeknumber == other._stdweeknumber and + self._dstweeknumber == other._dstweeknumber and + self._stdhour == other._stdhour and + self._dsthour == other._dsthour and + self._stdminute == other._stdminute and + self._dstminute == other._dstminute and + self._std_abbr == other._std_abbr and + self._dst_abbr == other._dst_abbr) + + @staticmethod + def list(): + """Return a list of all time zones known to the system.""" + with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle: + with winreg.OpenKey(handle, TZKEYNAME) as tzkey: + result = [winreg.EnumKey(tzkey, i) + for i in range(winreg.QueryInfoKey(tzkey)[0])] + return result + + def display(self): + """ + Return the display name of the time zone. + """ + return self._display + + def transitions(self, year): + """ + For a given year, get the DST on and off transition times, expressed + always on the standard time side. For zones with no transitions, this + function returns ``None``. + + :param year: + The year whose transitions you would like to query. + + :return: + Returns a :class:`tuple` of :class:`datetime.datetime` objects, + ``(dston, dstoff)`` for zones with an annual DST transition, or + ``None`` for fixed offset zones. + """ + + if not self.hasdst: + return None + + dston = picknthweekday(year, self._dstmonth, self._dstdayofweek, + self._dsthour, self._dstminute, + self._dstweeknumber) + + dstoff = picknthweekday(year, self._stdmonth, self._stddayofweek, + self._stdhour, self._stdminute, + self._stdweeknumber) + + # Ambiguous dates default to the STD side + dstoff -= self._dst_base_offset + + return dston, dstoff + + def _get_hasdst(self): + return self._dstmonth != 0 + + @property + def _dst_base_offset(self): + return self._dst_base_offset_ + + +class tzwin(tzwinbase): + """ + Time zone object created from the zone info in the Windows registry + + These are similar to :py:class:`dateutil.tz.tzrange` objects in that + the time zone data is provided in the format of a single offset rule + for either 0 or 2 time zone transitions per year. + + :param: name + The name of a Windows time zone key, e.g. "Eastern Standard Time". + The full list of keys can be retrieved with :func:`tzwin.list`. + """ + + def __init__(self, name): + self._name = name + + with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle: + tzkeyname = text_type("{kn}\\{name}").format(kn=TZKEYNAME, name=name) + with winreg.OpenKey(handle, tzkeyname) as tzkey: + keydict = valuestodict(tzkey) + + self._std_abbr = keydict["Std"] + self._dst_abbr = keydict["Dlt"] + + self._display = keydict["Display"] + + # See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm + tup = struct.unpack("=3l16h", keydict["TZI"]) + stdoffset = -tup[0]-tup[1] # Bias + StandardBias * -1 + dstoffset = stdoffset-tup[2] # + DaylightBias * -1 + self._std_offset = datetime.timedelta(minutes=stdoffset) + self._dst_offset = datetime.timedelta(minutes=dstoffset) + + # for the meaning see the win32 TIME_ZONE_INFORMATION structure docs + # http://msdn.microsoft.com/en-us/library/windows/desktop/ms725481(v=vs.85).aspx + (self._stdmonth, + self._stddayofweek, # Sunday = 0 + self._stdweeknumber, # Last = 5 + self._stdhour, + self._stdminute) = tup[4:9] + + (self._dstmonth, + self._dstdayofweek, # Sunday = 0 + self._dstweeknumber, # Last = 5 + self._dsthour, + self._dstminute) = tup[12:17] + + self._dst_base_offset_ = self._dst_offset - self._std_offset + self.hasdst = self._get_hasdst() + + def __repr__(self): + return "tzwin(%s)" % repr(self._name) + + def __reduce__(self): + return (self.__class__, (self._name,)) + + +class tzwinlocal(tzwinbase): + """ + Class representing the local time zone information in the Windows registry + + While :class:`dateutil.tz.tzlocal` makes system calls (via the :mod:`time` + module) to retrieve time zone information, ``tzwinlocal`` retrieves the + rules directly from the Windows registry and creates an object like + :class:`dateutil.tz.tzwin`. + + Because Windows does not have an equivalent of :func:`time.tzset`, on + Windows, :class:`dateutil.tz.tzlocal` instances will always reflect the + time zone settings *at the time that the process was started*, meaning + changes to the machine's time zone settings during the run of a program + on Windows will **not** be reflected by :class:`dateutil.tz.tzlocal`. + Because ``tzwinlocal`` reads the registry directly, it is unaffected by + this issue. + """ + def __init__(self): + with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle: + with winreg.OpenKey(handle, TZLOCALKEYNAME) as tzlocalkey: + keydict = valuestodict(tzlocalkey) + + self._std_abbr = keydict["StandardName"] + self._dst_abbr = keydict["DaylightName"] + + try: + tzkeyname = text_type('{kn}\\{sn}').format(kn=TZKEYNAME, + sn=self._std_abbr) + with winreg.OpenKey(handle, tzkeyname) as tzkey: + _keydict = valuestodict(tzkey) + self._display = _keydict["Display"] + except OSError: + self._display = None + + stdoffset = -keydict["Bias"]-keydict["StandardBias"] + dstoffset = stdoffset-keydict["DaylightBias"] + + self._std_offset = datetime.timedelta(minutes=stdoffset) + self._dst_offset = datetime.timedelta(minutes=dstoffset) + + # For reasons unclear, in this particular key, the day of week has been + # moved to the END of the SYSTEMTIME structure. + tup = struct.unpack("=8h", keydict["StandardStart"]) + + (self._stdmonth, + self._stdweeknumber, # Last = 5 + self._stdhour, + self._stdminute) = tup[1:5] + + self._stddayofweek = tup[7] + + tup = struct.unpack("=8h", keydict["DaylightStart"]) + + (self._dstmonth, + self._dstweeknumber, # Last = 5 + self._dsthour, + self._dstminute) = tup[1:5] + + self._dstdayofweek = tup[7] + + self._dst_base_offset_ = self._dst_offset - self._std_offset + self.hasdst = self._get_hasdst() + + def __repr__(self): + return "tzwinlocal()" + + def __str__(self): + # str will return the standard name, not the daylight name. + return "tzwinlocal(%s)" % repr(self._std_abbr) + + def __reduce__(self): + return (self.__class__, ()) + + +def picknthweekday(year, month, dayofweek, hour, minute, whichweek): + """ dayofweek == 0 means Sunday, whichweek 5 means last instance """ + first = datetime.datetime(year, month, 1, hour, minute) + + # This will work if dayofweek is ISO weekday (1-7) or Microsoft-style (0-6), + # Because 7 % 7 = 0 + weekdayone = first.replace(day=((dayofweek - first.isoweekday()) % 7) + 1) + wd = weekdayone + ((whichweek - 1) * ONEWEEK) + if (wd.month != month): + wd -= ONEWEEK + + return wd + + +def valuestodict(key): + """Convert a registry key's values to a dictionary.""" + dout = {} + size = winreg.QueryInfoKey(key)[1] + tz_res = None + + for i in range(size): + key_name, value, dtype = winreg.EnumValue(key, i) + if dtype == winreg.REG_DWORD or dtype == winreg.REG_DWORD_LITTLE_ENDIAN: + # If it's a DWORD (32-bit integer), it's stored as unsigned - convert + # that to a proper signed integer + if value & (1 << 31): + value = value - (1 << 32) + elif dtype == winreg.REG_SZ: + # If it's a reference to the tzres DLL, load the actual string + if value.startswith('@tzres'): + tz_res = tz_res or tzres() + value = tz_res.name_from_string(value) + + value = value.rstrip('\x00') # Remove trailing nulls + + dout[key_name] = value + + return dout diff --git a/pipenv/vendor/dateutil/tzwin.py b/pipenv/vendor/dateutil/tzwin.py new file mode 100644 index 00000000..cebc673e --- /dev/null +++ b/pipenv/vendor/dateutil/tzwin.py @@ -0,0 +1,2 @@ +# tzwin has moved to dateutil.tz.win +from .tz.win import * diff --git a/pipenv/vendor/dateutil/utils.py b/pipenv/vendor/dateutil/utils.py new file mode 100644 index 00000000..dd2d245a --- /dev/null +++ b/pipenv/vendor/dateutil/utils.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +""" +This module offers general convenience and utility functions for dealing with +datetimes. + +.. versionadded:: 2.7.0 +""" +from __future__ import unicode_literals + +from datetime import datetime, time + + +def today(tzinfo=None): + """ + Returns a :py:class:`datetime` representing the current day at midnight + + :param tzinfo: + The time zone to attach (also used to determine the current day). + + :return: + A :py:class:`datetime.datetime` object representing the current day + at midnight. + """ + + dt = datetime.now(tzinfo) + return datetime.combine(dt.date(), time(0, tzinfo=tzinfo)) + + +def default_tzinfo(dt, tzinfo): + """ + Sets the ``tzinfo`` parameter on naive datetimes only + + This is useful for example when you are provided a datetime that may have + either an implicit or explicit time zone, such as when parsing a time zone + string. + + .. doctest:: + + >>> from dateutil.tz import tzoffset + >>> from dateutil.parser import parse + >>> from dateutil.utils import default_tzinfo + >>> dflt_tz = tzoffset("EST", -18000) + >>> print(default_tzinfo(parse('2014-01-01 12:30 UTC'), dflt_tz)) + 2014-01-01 12:30:00+00:00 + >>> print(default_tzinfo(parse('2014-01-01 12:30'), dflt_tz)) + 2014-01-01 12:30:00-05:00 + + :param dt: + The datetime on which to replace the time zone + + :param tzinfo: + The :py:class:`datetime.tzinfo` subclass instance to assign to + ``dt`` if (and only if) it is naive. + + :return: + Returns an aware :py:class:`datetime.datetime`. + """ + if dt.tzinfo is not None: + return dt + else: + return dt.replace(tzinfo=tzinfo) + + +def within_delta(dt1, dt2, delta): + """ + Useful for comparing two datetimes that may have a negligible difference + to be considered equal. + """ + delta = abs(delta) + difference = dt1 - dt2 + return -delta <= difference <= delta diff --git a/pipenv/vendor/dateutil/zoneinfo/__init__.py b/pipenv/vendor/dateutil/zoneinfo/__init__.py new file mode 100644 index 00000000..338dd94a --- /dev/null +++ b/pipenv/vendor/dateutil/zoneinfo/__init__.py @@ -0,0 +1,167 @@ +# -*- coding: utf-8 -*- +import warnings +import json + +from tarfile import TarFile +from pkgutil import get_data +from io import BytesIO + +from pipenv.vendor.dateutil.tz import tzfile as _tzfile + +__all__ = ["get_zonefile_instance", "gettz", "gettz_db_metadata"] + +ZONEFILENAME = "dateutil-zoneinfo.tar.gz" +METADATA_FN = 'METADATA' + + +class tzfile(_tzfile): + def __reduce__(self): + return (gettz, (self._filename,)) + + +def getzoneinfofile_stream(): + try: + return BytesIO(get_data(__name__, ZONEFILENAME)) + except IOError as e: # TODO switch to FileNotFoundError? + warnings.warn("I/O error({0}): {1}".format(e.errno, e.strerror)) + return None + + +class ZoneInfoFile(object): + def __init__(self, zonefile_stream=None): + if zonefile_stream is not None: + with TarFile.open(fileobj=zonefile_stream) as tf: + self.zones = {zf.name: tzfile(tf.extractfile(zf), filename=zf.name) + for zf in tf.getmembers() + if zf.isfile() and zf.name != METADATA_FN} + # deal with links: They'll point to their parent object. Less + # waste of memory + links = {zl.name: self.zones[zl.linkname] + for zl in tf.getmembers() if + zl.islnk() or zl.issym()} + self.zones.update(links) + try: + metadata_json = tf.extractfile(tf.getmember(METADATA_FN)) + metadata_str = metadata_json.read().decode('UTF-8') + self.metadata = json.loads(metadata_str) + except KeyError: + # no metadata in tar file + self.metadata = None + else: + self.zones = {} + self.metadata = None + + def get(self, name, default=None): + """ + Wrapper for :func:`ZoneInfoFile.zones.get`. This is a convenience method + for retrieving zones from the zone dictionary. + + :param name: + The name of the zone to retrieve. (Generally IANA zone names) + + :param default: + The value to return in the event of a missing key. + + .. versionadded:: 2.6.0 + + """ + return self.zones.get(name, default) + + +# The current API has gettz as a module function, although in fact it taps into +# a stateful class. So as a workaround for now, without changing the API, we +# will create a new "global" class instance the first time a user requests a +# timezone. Ugly, but adheres to the api. +# +# TODO: Remove after deprecation period. +_CLASS_ZONE_INSTANCE = [] + + +def get_zonefile_instance(new_instance=False): + """ + This is a convenience function which provides a :class:`ZoneInfoFile` + instance using the data provided by the ``dateutil`` package. By default, it + caches a single instance of the ZoneInfoFile object and returns that. + + :param new_instance: + If ``True``, a new instance of :class:`ZoneInfoFile` is instantiated and + used as the cached instance for the next call. Otherwise, new instances + are created only as necessary. + + :return: + Returns a :class:`ZoneInfoFile` object. + + .. versionadded:: 2.6 + """ + if new_instance: + zif = None + else: + zif = getattr(get_zonefile_instance, '_cached_instance', None) + + if zif is None: + zif = ZoneInfoFile(getzoneinfofile_stream()) + + get_zonefile_instance._cached_instance = zif + + return zif + + +def gettz(name): + """ + This retrieves a time zone from the local zoneinfo tarball that is packaged + with dateutil. + + :param name: + An IANA-style time zone name, as found in the zoneinfo file. + + :return: + Returns a :class:`dateutil.tz.tzfile` time zone object. + + .. warning:: + It is generally inadvisable to use this function, and it is only + provided for API compatibility with earlier versions. This is *not* + equivalent to ``dateutil.tz.gettz()``, which selects an appropriate + time zone based on the inputs, favoring system zoneinfo. This is ONLY + for accessing the dateutil-specific zoneinfo (which may be out of + date compared to the system zoneinfo). + + .. deprecated:: 2.6 + If you need to use a specific zoneinfofile over the system zoneinfo, + instantiate a :class:`dateutil.zoneinfo.ZoneInfoFile` object and call + :func:`dateutil.zoneinfo.ZoneInfoFile.get(name)` instead. + + Use :func:`get_zonefile_instance` to retrieve an instance of the + dateutil-provided zoneinfo. + """ + warnings.warn("zoneinfo.gettz() will be removed in future versions, " + "to use the dateutil-provided zoneinfo files, instantiate a " + "ZoneInfoFile object and use ZoneInfoFile.zones.get() " + "instead. See the documentation for details.", + DeprecationWarning) + + if len(_CLASS_ZONE_INSTANCE) == 0: + _CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream())) + return _CLASS_ZONE_INSTANCE[0].zones.get(name) + + +def gettz_db_metadata(): + """ Get the zonefile metadata + + See `zonefile_metadata`_ + + :returns: + A dictionary with the database metadata + + .. deprecated:: 2.6 + See deprecation warning in :func:`zoneinfo.gettz`. To get metadata, + query the attribute ``zoneinfo.ZoneInfoFile.metadata``. + """ + warnings.warn("zoneinfo.gettz_db_metadata() will be removed in future " + "versions, to use the dateutil-provided zoneinfo files, " + "ZoneInfoFile object and query the 'metadata' attribute " + "instead. See the documentation for details.", + DeprecationWarning) + + if len(_CLASS_ZONE_INSTANCE) == 0: + _CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream())) + return _CLASS_ZONE_INSTANCE[0].metadata diff --git a/pipenv/vendor/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz b/pipenv/vendor/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz new file mode 100644 index 00000000..524c48e1 Binary files /dev/null and b/pipenv/vendor/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz differ diff --git a/pipenv/vendor/dateutil/zoneinfo/rebuild.py b/pipenv/vendor/dateutil/zoneinfo/rebuild.py new file mode 100644 index 00000000..c7282e89 --- /dev/null +++ b/pipenv/vendor/dateutil/zoneinfo/rebuild.py @@ -0,0 +1,75 @@ +import logging +import os +import tempfile +import shutil +import json +from subprocess import check_call, check_output +from tarfile import TarFile + +from pipenv.vendor.dateutil.zoneinfo import METADATA_FN, ZONEFILENAME + + +def rebuild(filename, tag=None, format="gz", zonegroups=[], metadata=None): + """Rebuild the internal timezone info in dateutil/zoneinfo/zoneinfo*tar* + + filename is the timezone tarball from ``ftp.iana.org/tz``. + + """ + tmpdir = tempfile.mkdtemp() + zonedir = os.path.join(tmpdir, "zoneinfo") + moduledir = os.path.dirname(__file__) + try: + with TarFile.open(filename) as tf: + for name in zonegroups: + tf.extract(name, tmpdir) + filepaths = [os.path.join(tmpdir, n) for n in zonegroups] + + _run_zic(zonedir, filepaths) + + # write metadata file + with open(os.path.join(zonedir, METADATA_FN), 'w') as f: + json.dump(metadata, f, indent=4, sort_keys=True) + target = os.path.join(moduledir, ZONEFILENAME) + with TarFile.open(target, "w:%s" % format) as tf: + for entry in os.listdir(zonedir): + entrypath = os.path.join(zonedir, entry) + tf.add(entrypath, entry) + finally: + shutil.rmtree(tmpdir) + + +def _run_zic(zonedir, filepaths): + """Calls the ``zic`` compiler in a compatible way to get a "fat" binary. + + Recent versions of ``zic`` default to ``-b slim``, while older versions + don't even have the ``-b`` option (but default to "fat" binaries). The + current version of dateutil does not support Version 2+ TZif files, which + causes problems when used in conjunction with "slim" binaries, so this + function is used to ensure that we always get a "fat" binary. + """ + + try: + help_text = check_output(["zic", "--help"]) + except OSError as e: + _print_on_nosuchfile(e) + raise + + if b"-b " in help_text: + bloat_args = ["-b", "fat"] + else: + bloat_args = [] + + check_call(["zic"] + bloat_args + ["-d", zonedir] + filepaths) + + +def _print_on_nosuchfile(e): + """Print helpful troubleshooting message + + e is an exception raised by subprocess.check_call() + + """ + if e.errno == 2: + logging.error( + "Could not find zic. Perhaps you need to install " + "libc-bin or some other package that provides it, " + "or it's not in your PATH?") diff --git a/pipenv/vendor/delegator.py b/pipenv/vendor/delegator.py deleted file mode 100644 index 367f52b0..00000000 --- a/pipenv/vendor/delegator.py +++ /dev/null @@ -1,341 +0,0 @@ -import os -import subprocess -import shlex -import signal -import sys -import locale -import errno - -from pexpect.popen_spawn import PopenSpawn -import pexpect -pexpect.EOF.__module__ = "pexpect.exceptions" - -# Include `unicode` in STR_TYPES for Python 2.X -try: - STR_TYPES = (str, unicode) -except NameError: - STR_TYPES = (str,) - -TIMEOUT = 30 - - -def pid_exists(pid): - """Check whether pid exists in the current process table.""" - if pid == 0: - # According to "man 2 kill" PID 0 has a special meaning: - # it refers to <<every process in the process group of the - # calling process>> so we don't want to go any further. - # If we get here it means this UNIX platform *does* have - # a process with id 0. - return True - try: - os.kill(pid, 0) - except OSError as err: - if err.errno == errno.ESRCH: - # ESRCH == No such process - return False - elif err.errno == errno.EPERM: - # EPERM clearly means there's a process to deny access to - return True - else: - # According to "man 2 kill" possible error values are - # (EINVAL, EPERM, ESRCH) therefore we should never get - # here. If we do let's be explicit in considering this - # an error. - raise err - else: - return True - - -class Command(object): - def __init__(self, cmd, timeout=TIMEOUT): - super(Command, self).__init__() - self.cmd = cmd - self.timeout = timeout - self.subprocess = None - self.blocking = None - self.was_run = False - self.__out = None - self.__err = None - - def __repr__(self): - return "<Command {!r}>".format(self.cmd) - - @property - def _popen_args(self): - return self.cmd - - @property - def _default_popen_kwargs(self): - return { - "env": os.environ.copy(), - "stdin": subprocess.PIPE, - "stdout": subprocess.PIPE, - "stderr": subprocess.PIPE, - "shell": True, - "universal_newlines": True, - "bufsize": 0, - } - - @property - def _default_pexpect_kwargs(self): - encoding = "utf-8" - if sys.platform == "win32": - default_encoding = locale.getdefaultlocale()[1] - if default_encoding is not None: - encoding = default_encoding - return {"env": os.environ.copy(), "encoding": encoding, "timeout": self.timeout} - - @property - def _uses_subprocess(self): - return isinstance(self.subprocess, subprocess.Popen) - - @property - def _uses_pexpect(self): - return isinstance(self.subprocess, PopenSpawn) - - @property - def std_out(self): - return self.subprocess.stdout - - @property - def ok(self): - return self.return_code == 0 - - @property - def _pexpect_out(self): - if self.subprocess.encoding: - result = "" - else: - result = b"" - - if self.subprocess.before: - result += self.subprocess.before - - if self.subprocess.after and self.subprocess.after not in (pexpect.EOF, pexpect.TIMEOUT): - try: - result += self.subprocess.after - except (pexpect.EOF, pexpect.TIMEOUT): - pass - - result += self.subprocess.read() - return result - - @property - def out(self): - """Std/out output (cached)""" - if self.__out is not None: - return self.__out - - if self._uses_subprocess: - self.__out = self.std_out.read() - else: - self.__out = self._pexpect_out - - return self.__out - - @property - def std_err(self): - return self.subprocess.stderr - - @property - def err(self): - """Std/err output (cached)""" - if self.__err is not None: - return self.__err - - if self._uses_subprocess: - self.__err = self.std_err.read() - return self.__err - else: - return self._pexpect_out - - @property - def pid(self): - """The process' PID.""" - # Support for pexpect's functionality. - if hasattr(self.subprocess, "proc"): - return self.subprocess.proc.pid - # Standard subprocess method. - return self.subprocess.pid - - @property - def is_alive(self): - """Is the process alive?""" - return pid_exists(self.pid) - - @property - def return_code(self): - # Support for pexpect's functionality. - if self._uses_pexpect: - return self.subprocess.exitstatus - # Standard subprocess method. - return self.subprocess.returncode - - @property - def std_in(self): - return self.subprocess.stdin - - def run(self, block=True, binary=False, cwd=None, env=None): - """Runs the given command, with or without pexpect functionality enabled.""" - self.blocking = block - - # Use subprocess. - if self.blocking: - popen_kwargs = self._default_popen_kwargs.copy() - del popen_kwargs["stdin"] - popen_kwargs["universal_newlines"] = not binary - if cwd: - popen_kwargs["cwd"] = cwd - if env: - popen_kwargs["env"].update(env) - s = subprocess.Popen(self._popen_args, **popen_kwargs) - # Otherwise, use pexpect. - else: - pexpect_kwargs = self._default_pexpect_kwargs.copy() - if binary: - pexpect_kwargs["encoding"] = None - if cwd: - pexpect_kwargs["cwd"] = cwd - if env: - pexpect_kwargs["env"].update(env) - # Enable Python subprocesses to work with expect functionality. - pexpect_kwargs["env"]["PYTHONUNBUFFERED"] = "1" - s = PopenSpawn(self._popen_args, **pexpect_kwargs) - self.subprocess = s - self.was_run = True - - def expect(self, pattern, timeout=-1): - """Waits on the given pattern to appear in std_out""" - - if self.blocking: - raise RuntimeError("expect can only be used on non-blocking commands.") - - try: - self.subprocess.expect(pattern=pattern, timeout=timeout) - except pexpect.EOF: - pass - - def send(self, s, end=os.linesep, signal=False): - """Sends the given string or signal to std_in.""" - - if self.blocking: - raise RuntimeError("send can only be used on non-blocking commands.") - - if not signal: - if self._uses_subprocess: - return self.subprocess.communicate(s + end) - else: - return self.subprocess.send(s + end) - else: - self.subprocess.send_signal(s) - - def terminate(self): - self.subprocess.terminate() - - def kill(self): - if self._uses_pexpect: - self.subprocess.kill(signal.SIGINT) - else: - self.subprocess.send_signal(signal.SIGINT) - - def block(self): - """Blocks until process is complete.""" - if self._uses_subprocess: - # consume stdout and stderr - if self.blocking: - try: - stdout, stderr = self.subprocess.communicate() - self.__out = stdout - self.__err = stderr - except ValueError: - pass # Don't read from finished subprocesses. - else: - self.subprocess.stdin.close() - self.std_out.close() - self.std_err.close() - self.subprocess.wait() - else: - self.subprocess.sendeof() - try: - self.subprocess.wait() - finally: - if self.subprocess.proc.stdout: - self.subprocess.proc.stdout.close() - - def pipe(self, command, timeout=None, cwd=None): - """Runs the current command and passes its output to the next - given process. - """ - if not timeout: - timeout = self.timeout - - if not self.was_run: - self.run(block=False, cwd=cwd) - - data = self.out - - if timeout: - c = Command(command, timeout) - else: - c = Command(command) - - c.run(block=False, cwd=cwd) - if data: - c.send(data) - c.block() - return c - - -def _expand_args(command): - """Parses command strings and returns a Popen-ready list.""" - - # Prepare arguments. - if isinstance(command, STR_TYPES): - if sys.version_info[0] == 2: - splitter = shlex.shlex(command.encode("utf-8")) - elif sys.version_info[0] == 3: - splitter = shlex.shlex(command) - else: - splitter = shlex.shlex(command.encode("utf-8")) - splitter.whitespace = "|" - splitter.whitespace_split = True - command = [] - - while True: - token = splitter.get_token() - if token: - command.append(token) - else: - break - - command = list(map(shlex.split, command)) - - return command - - -def chain(command, timeout=TIMEOUT, cwd=None, env=None): - commands = _expand_args(command) - data = None - - for command in commands: - - c = run(command, block=False, timeout=timeout, cwd=cwd, env=env) - - if data: - c.send(data) - c.subprocess.sendeof() - - data = c.out - - return c - - -def run(command, block=True, binary=False, timeout=TIMEOUT, cwd=None, env=None): - c = Command(command, timeout=timeout) - c.run(block=block, binary=binary, cwd=cwd, env=env) - - if block: - c.block() - - return c diff --git a/pipenv/vendor/distlib/__init__.py b/pipenv/vendor/distlib/__init__.py index 08fe1fc4..492c2c70 100644 --- a/pipenv/vendor/distlib/__init__.py +++ b/pipenv/vendor/distlib/__init__.py @@ -1,12 +1,12 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2012-2017 Vinay Sajip. +# Copyright (C) 2012-2019 Vinay Sajip. # Licensed to the Python Software Foundation under a contributor agreement. # See LICENSE.txt and CONTRIBUTORS.txt. # import logging -__version__ = '0.2.9' +__version__ = '0.3.2' class DistlibException(Exception): pass diff --git a/pipenv/vendor/distlib/_backport/shutil.py b/pipenv/vendor/distlib/_backport/shutil.py index 159e49ee..10ed3625 100644 --- a/pipenv/vendor/distlib/_backport/shutil.py +++ b/pipenv/vendor/distlib/_backport/shutil.py @@ -14,7 +14,10 @@ import sys import stat from os.path import abspath import fnmatch -import collections +try: + from collections.abc import Callable +except ImportError: + from collections import Callable import errno from . import tarfile @@ -528,7 +531,7 @@ def register_archive_format(name, function, extra_args=None, description=''): """ if extra_args is None: extra_args = [] - if not isinstance(function, collections.Callable): + if not isinstance(function, Callable): raise TypeError('The %s object is not callable' % function) if not isinstance(extra_args, (tuple, list)): raise TypeError('extra_args needs to be a sequence') @@ -621,7 +624,7 @@ def _check_unpack_options(extensions, function, extra_args): raise RegistryError(msg % (extension, existing_extensions[extension])) - if not isinstance(function, collections.Callable): + if not isinstance(function, Callable): raise TypeError('The registered function must be a callable') diff --git a/pipenv/vendor/distlib/_backport/sysconfig.py b/pipenv/vendor/distlib/_backport/sysconfig.py index 1df3aba1..b470a373 100644 --- a/pipenv/vendor/distlib/_backport/sysconfig.py +++ b/pipenv/vendor/distlib/_backport/sysconfig.py @@ -119,11 +119,9 @@ def _expand_globals(config): #_expand_globals(_SCHEMES) - # FIXME don't rely on sys.version here, its format is an implementation detail - # of CPython, use sys.version_info or sys.hexversion -_PY_VERSION = sys.version.split()[0] -_PY_VERSION_SHORT = sys.version[:3] -_PY_VERSION_SHORT_NO_DOT = _PY_VERSION[0] + _PY_VERSION[2] +_PY_VERSION = '%s.%s.%s' % sys.version_info[:3] +_PY_VERSION_SHORT = '%s.%s' % sys.version_info[:2] +_PY_VERSION_SHORT_NO_DOT = '%s%s' % sys.version_info[:2] _PREFIX = os.path.normpath(sys.prefix) _EXEC_PREFIX = os.path.normpath(sys.exec_prefix) _CONFIG_VARS = None diff --git a/pipenv/vendor/distlib/compat.py b/pipenv/vendor/distlib/compat.py index ff328c8e..c316fd97 100644 --- a/pipenv/vendor/distlib/compat.py +++ b/pipenv/vendor/distlib/compat.py @@ -319,7 +319,7 @@ except ImportError: # pragma: no cover try: callable = callable except NameError: # pragma: no cover - from collections import Callable + from collections.abc import Callable def callable(obj): return isinstance(obj, Callable) diff --git a/pipenv/vendor/distlib/database.py b/pipenv/vendor/distlib/database.py index b13cdac9..0a90c300 100644 --- a/pipenv/vendor/distlib/database.py +++ b/pipenv/vendor/distlib/database.py @@ -550,7 +550,7 @@ class InstalledDistribution(BaseInstalledDistribution): r = finder.find(WHEEL_METADATA_FILENAME) # Temporary - for legacy support if r is None: - r = finder.find('METADATA') + r = finder.find(LEGACY_METADATA_FILENAME) if r is None: raise ValueError('no %s found in %s' % (METADATA_FILENAME, path)) @@ -567,7 +567,7 @@ class InstalledDistribution(BaseInstalledDistribution): p = os.path.join(path, 'top_level.txt') if os.path.exists(p): with open(p, 'rb') as f: - data = f.read() + data = f.read().decode('utf-8') self.modules = data.splitlines() def __repr__(self): diff --git a/pipenv/vendor/distlib/index.py b/pipenv/vendor/distlib/index.py index 7a87cdcf..b1fbbf8e 100644 --- a/pipenv/vendor/distlib/index.py +++ b/pipenv/vendor/distlib/index.py @@ -18,7 +18,7 @@ except ImportError: from . import DistlibException from .compat import (HTTPBasicAuthHandler, Request, HTTPPasswordMgr, urlparse, build_opener, string_types) -from .util import cached_property, zip_dir, ServerProxy +from .util import zip_dir, ServerProxy logger = logging.getLogger(__name__) @@ -67,21 +67,17 @@ class PackageIndex(object): Get the distutils command for interacting with PyPI configurations. :return: the command. """ - from distutils.core import Distribution - from distutils.config import PyPIRCCommand - d = Distribution() - return PyPIRCCommand(d) + from .util import _get_pypirc_command as cmd + return cmd() def read_configuration(self): """ - Read the PyPI access configuration as supported by distutils, getting - PyPI to do the actual work. This populates ``username``, ``password``, - ``realm`` and ``url`` attributes from the configuration. + Read the PyPI access configuration as supported by distutils. This populates + ``username``, ``password``, ``realm`` and ``url`` attributes from the + configuration. """ - # get distutils to do the work - c = self._get_pypirc_command() - c.repository = self.url - cfg = c._read_pypirc() + from .util import _load_pypirc + cfg = _load_pypirc(self) self.username = cfg.get('username') self.password = cfg.get('password') self.realm = cfg.get('realm', 'pypi') @@ -91,13 +87,10 @@ class PackageIndex(object): """ Save the PyPI access configuration. You must have set ``username`` and ``password`` attributes before calling this method. - - Again, distutils is used to do the actual work. """ self.check_credentials() - # get distutils to do the work - c = self._get_pypirc_command() - c._store_pypirc(self.username, self.password) + from .util import _store_pypirc + _store_pypirc(self) def check_credentials(self): """ diff --git a/pipenv/vendor/distlib/locators.py b/pipenv/vendor/distlib/locators.py index a7ed9469..0c7d6391 100644 --- a/pipenv/vendor/distlib/locators.py +++ b/pipenv/vendor/distlib/locators.py @@ -20,14 +20,14 @@ import zlib from . import DistlibException from .compat import (urljoin, urlparse, urlunparse, url2pathname, pathname2url, - queue, quote, unescape, string_types, build_opener, + queue, quote, unescape, build_opener, HTTPRedirectHandler as BaseRedirectHandler, text_type, Request, HTTPError, URLError) from .database import Distribution, DistributionPath, make_dist from .metadata import Metadata, MetadataInvalidError -from .util import (cached_property, parse_credentials, ensure_slash, - split_filename, get_project_data, parse_requirement, - parse_name_and_version, ServerProxy, normalize_name) +from .util import (cached_property, ensure_slash, split_filename, get_project_data, + parse_requirement, parse_name_and_version, ServerProxy, + normalize_name) from .version import get_scheme, UnsupportedVersionError from .wheel import Wheel, is_compatible @@ -304,18 +304,25 @@ class Locator(object): def _get_digest(self, info): """ - Get a digest from a dictionary by looking at keys of the form - 'algo_digest'. + Get a digest from a dictionary by looking at a "digests" dictionary + or keys of the form 'algo_digest'. Returns a 2-tuple (algo, digest) if found, else None. Currently looks only for SHA256, then MD5. """ result = None - for algo in ('sha256', 'md5'): - key = '%s_digest' % algo - if key in info: - result = (algo, info[key]) - break + if 'digests' in info: + digests = info['digests'] + for algo in ('sha256', 'md5'): + if algo in digests: + result = (algo, digests[algo]) + break + if not result: + for algo in ('sha256', 'md5'): + key = '%s_digest' % algo + if key in info: + result = (algo, info[key]) + break return result def _update_version_data(self, result, info): @@ -371,13 +378,13 @@ class Locator(object): continue try: if not matcher.match(k): - logger.debug('%s did not match %r', matcher, k) + pass # logger.debug('%s did not match %r', matcher, k) else: if prereleases or not vcls(k).is_prerelease: slist.append(k) - else: - logger.debug('skipping pre-release ' - 'version %s of %s', k, matcher.name) + # else: + # logger.debug('skipping pre-release ' + # 'version %s of %s', k, matcher.name) except Exception: # pragma: no cover logger.warning('error matching %s with %r', matcher, k) pass # slist.append(k) @@ -586,7 +593,7 @@ class SimpleScrapingLocator(Locator): # These are used to deal with various Content-Encoding schemes. decoders = { 'deflate': zlib.decompress, - 'gzip': lambda b: gzip.GzipFile(fileobj=BytesIO(d)).read(), + 'gzip': lambda b: gzip.GzipFile(fileobj=BytesIO(b)).read(), 'none': lambda b: b, } @@ -1055,8 +1062,6 @@ default_locator = AggregatingLocator( locate = default_locator.locate -NAME_VERSION_RE = re.compile(r'(?P<name>[\w-]+)\s*' - r'\(\s*(==\s*)?(?P<ver>[^)]+)\)$') class DependencyFinder(object): """ diff --git a/pipenv/vendor/distlib/markers.py b/pipenv/vendor/distlib/markers.py index ee1f3e23..923a832b 100644 --- a/pipenv/vendor/distlib/markers.py +++ b/pipenv/vendor/distlib/markers.py @@ -15,9 +15,8 @@ Parser for the environment markers micro-language defined in PEP 508. import os import sys import platform -import re -from .compat import python_implementation, urlparse, string_types +from .compat import string_types from .util import in_venv, parse_marker __all__ = ['interpret'] diff --git a/pipenv/vendor/distlib/metadata.py b/pipenv/vendor/distlib/metadata.py index 2d61378e..6a26b0ab 100644 --- a/pipenv/vendor/distlib/metadata.py +++ b/pipenv/vendor/distlib/metadata.py @@ -5,7 +5,7 @@ # """Implementation of the Metadata for Python packages PEPs. -Supports all metadata formats (1.0, 1.1, 1.2, and 2.0 experimental). +Supports all metadata formats (1.0, 1.1, 1.2, 1.3/2.1 and withdrawn 2.0). """ from __future__ import unicode_literals @@ -94,8 +94,9 @@ _426_MARKERS = ('Private-Version', 'Provides-Extra', 'Obsoleted-By', # See issue #106: Sometimes 'Requires' and 'Provides' occur wrongly in # the metadata. Include them in the tuple literal below to allow them # (for now). +# Ditto for Obsoletes - see issue #140. _566_FIELDS = _426_FIELDS + ('Description-Content-Type', - 'Requires', 'Provides') + 'Requires', 'Provides', 'Obsoletes') _566_MARKERS = ('Description-Content-Type',) @@ -117,7 +118,8 @@ def _version2fieldlist(version): elif version == '1.2': return _345_FIELDS elif version in ('1.3', '2.1'): - return _345_FIELDS + _566_FIELDS + # avoid adding field names if already there + return _345_FIELDS + tuple(f for f in _566_FIELDS if f not in _345_FIELDS) elif version == '2.0': return _426_FIELDS raise MetadataUnrecognizedVersionError(version) @@ -194,38 +196,12 @@ def _best_version(fields): return '2.0' +# This follows the rules about transforming keys as described in +# https://www.python.org/dev/peps/pep-0566/#id17 _ATTR2FIELD = { - 'metadata_version': 'Metadata-Version', - 'name': 'Name', - 'version': 'Version', - 'platform': 'Platform', - 'supported_platform': 'Supported-Platform', - 'summary': 'Summary', - 'description': 'Description', - 'keywords': 'Keywords', - 'home_page': 'Home-page', - 'author': 'Author', - 'author_email': 'Author-email', - 'maintainer': 'Maintainer', - 'maintainer_email': 'Maintainer-email', - 'license': 'License', - 'classifier': 'Classifier', - 'download_url': 'Download-URL', - 'obsoletes_dist': 'Obsoletes-Dist', - 'provides_dist': 'Provides-Dist', - 'requires_dist': 'Requires-Dist', - 'setup_requires_dist': 'Setup-Requires-Dist', - 'requires_python': 'Requires-Python', - 'requires_external': 'Requires-External', - 'requires': 'Requires', - 'provides': 'Provides', - 'obsoletes': 'Obsoletes', - 'project_url': 'Project-URL', - 'private_version': 'Private-Version', - 'obsoleted_by': 'Obsoleted-By', - 'extension': 'Extension', - 'provides_extra': 'Provides-Extra', + name.lower().replace("-", "_"): name for name in _ALL_FIELDS } +_FIELD2ATTR = {field: attr for attr, field in _ATTR2FIELD.items()} _PREDICATE_FIELDS = ('Requires-Dist', 'Obsoletes-Dist', 'Provides-Dist') _VERSIONS_FIELDS = ('Requires-Python',) @@ -262,7 +238,7 @@ def _get_name_and_version(name, version, for_filename=False): class LegacyMetadata(object): """The legacy metadata of a release. - Supports versions 1.0, 1.1 and 1.2 (auto-detected). You can + Supports versions 1.0, 1.1, 1.2, 2.0 and 1.3/2.1 (auto-detected). You can instantiate the class with one of these arguments (or none): - *path*, the path to a metadata file - *fileobj* give a file-like object with metadata as content @@ -381,6 +357,11 @@ class LegacyMetadata(object): value = msg[field] if value is not None and value != 'UNKNOWN': self.set(field, value) + + # PEP 566 specifies that the body be used for the description, if + # available + body = msg.get_payload() + self["Description"] = body if body else self["Description"] # logger.debug('Attempting to set metadata for %s', self) # self.set_metadata_version() @@ -567,57 +548,21 @@ class LegacyMetadata(object): Field names will be converted to use the underscore-lowercase style instead of hyphen-mixed case (i.e. home_page instead of Home-page). + This is as per https://www.python.org/dev/peps/pep-0566/#id17. """ self.set_metadata_version() - mapping_1_0 = ( - ('metadata_version', 'Metadata-Version'), - ('name', 'Name'), - ('version', 'Version'), - ('summary', 'Summary'), - ('home_page', 'Home-page'), - ('author', 'Author'), - ('author_email', 'Author-email'), - ('license', 'License'), - ('description', 'Description'), - ('keywords', 'Keywords'), - ('platform', 'Platform'), - ('classifiers', 'Classifier'), - ('download_url', 'Download-URL'), - ) + fields = _version2fieldlist(self['Metadata-Version']) data = {} - for key, field_name in mapping_1_0: + + for field_name in fields: if not skip_missing or field_name in self._fields: - data[key] = self[field_name] - - if self['Metadata-Version'] == '1.2': - mapping_1_2 = ( - ('requires_dist', 'Requires-Dist'), - ('requires_python', 'Requires-Python'), - ('requires_external', 'Requires-External'), - ('provides_dist', 'Provides-Dist'), - ('obsoletes_dist', 'Obsoletes-Dist'), - ('project_url', 'Project-URL'), - ('maintainer', 'Maintainer'), - ('maintainer_email', 'Maintainer-email'), - ) - for key, field_name in mapping_1_2: - if not skip_missing or field_name in self._fields: - if key != 'project_url': - data[key] = self[field_name] - else: - data[key] = [','.join(u) for u in self[field_name]] - - elif self['Metadata-Version'] == '1.1': - mapping_1_1 = ( - ('provides', 'Provides'), - ('requires', 'Requires'), - ('obsoletes', 'Obsoletes'), - ) - for key, field_name in mapping_1_1: - if not skip_missing or field_name in self._fields: + key = _FIELD2ATTR[field_name] + if key != 'project_url': data[key] = self[field_name] + else: + data[key] = [','.join(u) for u in self[field_name]] return data @@ -1003,10 +948,14 @@ class Metadata(object): LEGACY_MAPPING = { 'name': 'Name', 'version': 'Version', - 'license': 'License', + ('extensions', 'python.details', 'license'): 'License', 'summary': 'Summary', 'description': 'Description', - 'classifiers': 'Classifier', + ('extensions', 'python.project', 'project_urls', 'Home'): 'Home-page', + ('extensions', 'python.project', 'contacts', 0, 'name'): 'Author', + ('extensions', 'python.project', 'contacts', 0, 'email'): 'Author-email', + 'source_url': 'Download-URL', + ('extensions', 'python.details', 'classifiers'): 'Classifier', } def _to_legacy(self): @@ -1034,16 +983,29 @@ class Metadata(object): assert self._data and not self._legacy result = LegacyMetadata() nmd = self._data + # import pdb; pdb.set_trace() for nk, ok in self.LEGACY_MAPPING.items(): - if nk in nmd: - result[ok] = nmd[nk] + if not isinstance(nk, tuple): + if nk in nmd: + result[ok] = nmd[nk] + else: + d = nmd + found = True + for k in nk: + try: + d = d[k] + except (KeyError, IndexError): + found = False + break + if found: + result[ok] = d r1 = process_entries(self.run_requires + self.meta_requires) r2 = process_entries(self.build_requires + self.dev_requires) if self.extras: result['Provides-Extra'] = sorted(self.extras) result['Requires-Dist'] = sorted(r1) result['Setup-Requires-Dist'] = sorted(r2) - # TODO: other fields such as contacts + # TODO: any other fields wanted return result def write(self, path=None, fileobj=None, legacy=False, skip_unknown=True): diff --git a/pipenv/vendor/distlib/resources.py b/pipenv/vendor/distlib/resources.py index 18840167..fef52aa1 100644 --- a/pipenv/vendor/distlib/resources.py +++ b/pipenv/vendor/distlib/resources.py @@ -11,13 +11,12 @@ import io import logging import os import pkgutil -import shutil import sys import types import zipimport from . import DistlibException -from .util import cached_property, get_cache_base, path_to_cache_dir, Cache +from .util import cached_property, get_cache_base, Cache logger = logging.getLogger(__name__) @@ -283,6 +282,7 @@ class ZipResourceFinder(ResourceFinder): result = False return result + _finder_registry = { type(None): ResourceFinder, zipimport.zipimporter: ZipResourceFinder @@ -296,6 +296,8 @@ try: import _frozen_importlib as _fi _finder_registry[_fi.SourceFileLoader] = ResourceFinder _finder_registry[_fi.FileFinder] = ResourceFinder + # See issue #146 + _finder_registry[_fi.SourcelessFileLoader] = ResourceFinder del _fi except (ImportError, AttributeError): pass @@ -304,6 +306,7 @@ except (ImportError, AttributeError): def register_finder(loader, finder_maker): _finder_registry[type(loader)] = finder_maker + _finder_cache = {} diff --git a/pipenv/vendor/distlib/scripts.py b/pipenv/vendor/distlib/scripts.py index 5965e241..1ac01dde 100644 --- a/pipenv/vendor/distlib/scripts.py +++ b/pipenv/vendor/distlib/scripts.py @@ -48,7 +48,7 @@ if __name__ == '__main__': ''' -def _enquote_executable(executable): +def enquote_executable(executable): if ' ' in executable: # make sure we quote only the executable in case of env # for example /usr/bin/env "/dir with spaces/bin/jython" @@ -63,6 +63,8 @@ def _enquote_executable(executable): executable = '"%s"' % executable return executable +# Keep the old name around (for now), as there is at least one project using it! +_enquote_executable = enquote_executable class ScriptMaker(object): """ @@ -88,6 +90,7 @@ class ScriptMaker(object): self._is_nt = os.name == 'nt' or ( os.name == 'java' and os._name == 'nt') + self.version_info = sys.version_info def _get_alternate_executable(self, executable, options): if options.get('gui', False) and self._is_nt: # pragma: no cover @@ -172,12 +175,20 @@ class ScriptMaker(object): if sys.platform.startswith('java'): # pragma: no cover executable = self._fix_jython_executable(executable) - # Normalise case for Windows - executable = os.path.normcase(executable) + + # Normalise case for Windows - COMMENTED OUT + # executable = os.path.normcase(executable) + # N.B. The normalising operation above has been commented out: See + # issue #124. Although paths in Windows are generally case-insensitive, + # they aren't always. For example, a path containing a ẞ (which is a + # LATIN CAPITAL LETTER SHARP S - U+1E9E) is normcased to ß (which is a + # LATIN SMALL LETTER SHARP S' - U+00DF). The two are not considered by + # Windows as equivalent in path names. + # If the user didn't specify an executable, it may be necessary to # cater for executable paths with spaces (not uncommon on Windows) if enquote: - executable = _enquote_executable(executable) + executable = enquote_executable(executable) # Issue #51: don't use fsencode, since we later try to # check that the shebang is decodable using utf-8. executable = executable.encode('utf-8') @@ -271,6 +282,19 @@ class ScriptMaker(object): self._fileop.set_executable_mode([outname]) filenames.append(outname) + variant_separator = '-' + + def get_script_filenames(self, name): + result = set() + if '' in self.variants: + result.add(name) + if 'X' in self.variants: + result.add('%s%s' % (name, self.version_info[0])) + if 'X.Y' in self.variants: + result.add('%s%s%s.%s' % (name, self.variant_separator, + self.version_info[0], self.version_info[1])) + return result + def _make_script(self, entry, filenames, options=None): post_interp = b'' if options: @@ -280,14 +304,7 @@ class ScriptMaker(object): post_interp = args.encode('utf-8') shebang = self._get_shebang('utf-8', post_interp, options=options) script = self._get_script_text(entry).encode('utf-8') - name = entry.name - scriptnames = set() - if '' in self.variants: - scriptnames.add(name) - if 'X' in self.variants: - scriptnames.add('%s%s' % (name, sys.version[0])) - if 'X.Y' in self.variants: - scriptnames.add('%s-%s' % (name, sys.version[:3])) + scriptnames = self.get_script_filenames(entry.name) if options and options.get('gui', False): ext = 'pyw' else: @@ -314,8 +331,7 @@ class ScriptMaker(object): else: first_line = f.readline() if not first_line: # pragma: no cover - logger.warning('%s: %s is an empty file (skipping)', - self.get_command_name(), script) + logger.warning('%s is an empty file (skipping)', script) return match = FIRST_LINE_RE.match(first_line.replace(b'\r\n', b'\n')) @@ -367,8 +383,12 @@ class ScriptMaker(object): # Issue 31: don't hardcode an absolute package name, but # determine it relative to the current package distlib_package = __name__.rsplit('.', 1)[0] - result = finder(distlib_package).find(name).bytes - return result + resource = finder(distlib_package).find(name) + if not resource: + msg = ('Unable to find resource %s in package %s' % (name, + distlib_package)) + raise ValueError(msg) + return resource.bytes # Public API follows diff --git a/pipenv/vendor/distlib/t32.exe b/pipenv/vendor/distlib/t32.exe index a09d9268..8932a18e 100644 Binary files a/pipenv/vendor/distlib/t32.exe and b/pipenv/vendor/distlib/t32.exe differ diff --git a/pipenv/vendor/distlib/t64.exe b/pipenv/vendor/distlib/t64.exe index 9da9b40d..325b8057 100644 Binary files a/pipenv/vendor/distlib/t64.exe and b/pipenv/vendor/distlib/t64.exe differ diff --git a/pipenv/vendor/distlib/util.py b/pipenv/vendor/distlib/util.py index e851146c..b9e2c695 100644 --- a/pipenv/vendor/distlib/util.py +++ b/pipenv/vendor/distlib/util.py @@ -1,5 +1,5 @@ # -# Copyright (C) 2012-2017 The Python Software Foundation. +# Copyright (C) 2012-2021 The Python Software Foundation. # See LICENSE.txt and CONTRIBUTORS.txt. # import codecs @@ -309,7 +309,9 @@ def get_executable(): # else: # result = sys.executable # return result - result = os.path.normcase(sys.executable) + # Avoid normcasing: see issue #143 + # result = os.path.normcase(sys.executable) + result = sys.executable if not isinstance(result, text_type): result = fsdecode(result) return result @@ -703,7 +705,7 @@ class ExportEntry(object): ENTRY_RE = re.compile(r'''(?P<name>(\w|[-.+])+) \s*=\s*(?P<callable>(\w+)([:\.]\w+)*) - \s*(\[\s*(?P<flags>\w+(=\w+)?(,\s*\w+(=\w+)?)*)\s*\])? + \s*(\[\s*(?P<flags>[\w-]+(=\w+)?(,\s*\w+(=\w+)?)*)\s*\])? ''', re.VERBOSE) def get_export_entry(specification): @@ -1438,7 +1440,8 @@ if ssl: ca_certs=self.ca_certs) else: # pragma: no cover context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) - context.options |= ssl.OP_NO_SSLv2 + if hasattr(ssl, 'OP_NO_SSLv2'): + context.options |= ssl.OP_NO_SSLv2 if self.cert_file: context.load_cert_chain(self.cert_file, self.key_file) kwargs = {} @@ -1569,7 +1572,8 @@ class ServerProxy(xmlrpclib.ServerProxy): # The above classes only come into play if a timeout # is specified if timeout is not None: - scheme, _ = splittype(uri) + # scheme = splittype(uri) # deprecated as of Python 3.8 + scheme = urlparse(uri)[0] use_datetime = kwargs.get('use_datetime', 0) if scheme == 'https': tcls = SafeTransport @@ -1758,3 +1762,204 @@ def normalize_name(name): """Normalize a python package name a la PEP 503""" # https://www.python.org/dev/peps/pep-0503/#normalized-names return re.sub('[-_.]+', '-', name).lower() + +# def _get_pypirc_command(): + # """ + # Get the distutils command for interacting with PyPI configurations. + # :return: the command. + # """ + # from distutils.core import Distribution + # from distutils.config import PyPIRCCommand + # d = Distribution() + # return PyPIRCCommand(d) + +class PyPIRCFile(object): + + DEFAULT_REPOSITORY = 'https://upload.pypi.org/legacy/' + DEFAULT_REALM = 'pypi' + + def __init__(self, fn=None, url=None): + if fn is None: + fn = os.path.join(os.path.expanduser('~'), '.pypirc') + self.filename = fn + self.url = url + + def read(self): + result = {} + + if os.path.exists(self.filename): + repository = self.url or self.DEFAULT_REPOSITORY + + config = configparser.RawConfigParser() + config.read(self.filename) + sections = config.sections() + if 'distutils' in sections: + # let's get the list of servers + index_servers = config.get('distutils', 'index-servers') + _servers = [server.strip() for server in + index_servers.split('\n') + if server.strip() != ''] + if _servers == []: + # nothing set, let's try to get the default pypi + if 'pypi' in sections: + _servers = ['pypi'] + else: + for server in _servers: + result = {'server': server} + result['username'] = config.get(server, 'username') + + # optional params + for key, default in (('repository', self.DEFAULT_REPOSITORY), + ('realm', self.DEFAULT_REALM), + ('password', None)): + if config.has_option(server, key): + result[key] = config.get(server, key) + else: + result[key] = default + + # work around people having "repository" for the "pypi" + # section of their config set to the HTTP (rather than + # HTTPS) URL + if (server == 'pypi' and + repository in (self.DEFAULT_REPOSITORY, 'pypi')): + result['repository'] = self.DEFAULT_REPOSITORY + elif (result['server'] != repository and + result['repository'] != repository): + result = {} + elif 'server-login' in sections: + # old format + server = 'server-login' + if config.has_option(server, 'repository'): + repository = config.get(server, 'repository') + else: + repository = self.DEFAULT_REPOSITORY + result = { + 'username': config.get(server, 'username'), + 'password': config.get(server, 'password'), + 'repository': repository, + 'server': server, + 'realm': self.DEFAULT_REALM + } + return result + + def update(self, username, password): + # import pdb; pdb.set_trace() + config = configparser.RawConfigParser() + fn = self.filename + config.read(fn) + if not config.has_section('pypi'): + config.add_section('pypi') + config.set('pypi', 'username', username) + config.set('pypi', 'password', password) + with open(fn, 'w') as f: + config.write(f) + +def _load_pypirc(index): + """ + Read the PyPI access configuration as supported by distutils. + """ + return PyPIRCFile(url=index.url).read() + +def _store_pypirc(index): + PyPIRCFile().update(index.username, index.password) + +# +# get_platform()/get_host_platform() copied from Python 3.10.a0 source, with some minor +# tweaks +# + +def get_host_platform(): + """Return a string that identifies the current platform. This is used mainly to + distinguish platform-specific build directories and platform-specific built + distributions. Typically includes the OS name and version and the + architecture (as supplied by 'os.uname()'), although the exact information + included depends on the OS; eg. on Linux, the kernel version isn't + particularly important. + + Examples of returned values: + linux-i586 + linux-alpha (?) + solaris-2.6-sun4u + + Windows will return one of: + win-amd64 (64bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc) + win32 (all others - specifically, sys.platform is returned) + + For other non-POSIX platforms, currently just returns 'sys.platform'. + + """ + if os.name == 'nt': + if 'amd64' in sys.version.lower(): + return 'win-amd64' + if '(arm)' in sys.version.lower(): + return 'win-arm32' + if '(arm64)' in sys.version.lower(): + return 'win-arm64' + return sys.platform + + # Set for cross builds explicitly + if "_PYTHON_HOST_PLATFORM" in os.environ: + return os.environ["_PYTHON_HOST_PLATFORM"] + + if os.name != 'posix' or not hasattr(os, 'uname'): + # XXX what about the architecture? NT is Intel or Alpha, + # Mac OS is M68k or PPC, etc. + return sys.platform + + # Try to distinguish various flavours of Unix + + (osname, host, release, version, machine) = os.uname() + + # Convert the OS name to lowercase, remove '/' characters, and translate + # spaces (for "Power Macintosh") + osname = osname.lower().replace('/', '') + machine = machine.replace(' ', '_').replace('/', '-') + + if osname[:5] == 'linux': + # At least on Linux/Intel, 'machine' is the processor -- + # i386, etc. + # XXX what about Alpha, SPARC, etc? + return "%s-%s" % (osname, machine) + + elif osname[:5] == 'sunos': + if release[0] >= '5': # SunOS 5 == Solaris 2 + osname = 'solaris' + release = '%d.%s' % (int(release[0]) - 3, release[2:]) + # We can't use 'platform.architecture()[0]' because a + # bootstrap problem. We use a dict to get an error + # if some suspicious happens. + bitness = {2147483647:'32bit', 9223372036854775807:'64bit'} + machine += '.%s' % bitness[sys.maxsize] + # fall through to standard osname-release-machine representation + elif osname[:3] == 'aix': + from _aix_support import aix_platform + return aix_platform() + elif osname[:6] == 'cygwin': + osname = 'cygwin' + rel_re = re.compile (r'[\d.]+', re.ASCII) + m = rel_re.match(release) + if m: + release = m.group() + elif osname[:6] == 'darwin': + import _osx_support, distutils.sysconfig + osname, release, machine = _osx_support.get_platform_osx( + distutils.sysconfig.get_config_vars(), + osname, release, machine) + + return '%s-%s-%s' % (osname, release, machine) + + +_TARGET_TO_PLAT = { + 'x86' : 'win32', + 'x64' : 'win-amd64', + 'arm' : 'win-arm32', +} + + +def get_platform(): + if os.name != 'nt': + return get_host_platform() + cross_compilation_target = os.environ.get('VSCMD_ARG_TGT_ARCH') + if cross_compilation_target not in _TARGET_TO_PLAT: + return get_host_platform() + return _TARGET_TO_PLAT[cross_compilation_target] diff --git a/pipenv/vendor/distlib/version.py b/pipenv/vendor/distlib/version.py index 3eebe18e..86c069a7 100644 --- a/pipenv/vendor/distlib/version.py +++ b/pipenv/vendor/distlib/version.py @@ -710,6 +710,9 @@ class VersionScheme(object): """ Used for processing some metadata fields """ + # See issue #140. Be tolerant of a single trailing comma. + if s.endswith(','): + s = s[:-1] return self.is_valid_matcher('dummy_name (%s)' % s) def suggest(self, s): diff --git a/pipenv/vendor/distlib/w32.exe b/pipenv/vendor/distlib/w32.exe index 732215a9..e6439e9e 100644 Binary files a/pipenv/vendor/distlib/w32.exe and b/pipenv/vendor/distlib/w32.exe differ diff --git a/pipenv/vendor/distlib/w64.exe b/pipenv/vendor/distlib/w64.exe index c41bd0a0..46139dbf 100644 Binary files a/pipenv/vendor/distlib/w64.exe and b/pipenv/vendor/distlib/w64.exe differ diff --git a/pipenv/vendor/distlib/wheel.py b/pipenv/vendor/distlib/wheel.py index 0c8efad9..5262c832 100644 --- a/pipenv/vendor/distlib/wheel.py +++ b/pipenv/vendor/distlib/wheel.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2013-2017 Vinay Sajip. +# Copyright (C) 2013-2020 Vinay Sajip. # Licensed to the Python Software Foundation under a contributor agreement. # See LICENSE.txt and CONTRIBUTORS.txt. # @@ -9,7 +9,6 @@ from __future__ import unicode_literals import base64 import codecs import datetime -import distutils.util from email import message_from_file import hashlib import imp @@ -26,9 +25,11 @@ import zipfile from . import __version__, DistlibException from .compat import sysconfig, ZipFile, fsdecode, text_type, filter from .database import InstalledDistribution -from .metadata import Metadata, METADATA_FILENAME, WHEEL_METADATA_FILENAME +from .metadata import (Metadata, METADATA_FILENAME, WHEEL_METADATA_FILENAME, + LEGACY_METADATA_FILENAME) from .util import (FileOperator, convert_path, CSVReader, CSVWriter, Cache, - cached_property, get_cache_base, read_exports, tempdir) + cached_property, get_cache_base, read_exports, tempdir, + get_platform) from .version import NormalizedVersion, UnsupportedVersionError logger = logging.getLogger(__name__) @@ -46,15 +47,18 @@ else: VER_SUFFIX = sysconfig.get_config_var('py_version_nodot') if not VER_SUFFIX: # pragma: no cover - VER_SUFFIX = '%s%s' % sys.version_info[:2] + if sys.version_info[1] >= 10: + VER_SUFFIX = '%s_%s' % sys.version_info[:2] # PEP 641 (draft) + else: + VER_SUFFIX = '%s%s' % sys.version_info[:2] PYVER = 'py' + VER_SUFFIX IMPVER = IMP_PREFIX + VER_SUFFIX -ARCH = distutils.util.get_platform().replace('-', '_').replace('.', '_') +ARCH = get_platform().replace('-', '_').replace('.', '_') ABI = sysconfig.get_config_var('SOABI') if ABI and ABI.startswith('cpython-'): - ABI = ABI.replace('cpython-', 'cp') + ABI = ABI.replace('cpython-', 'cp').split('-')[0] else: def _derive_abi(): parts = ['cp', VER_SUFFIX] @@ -221,10 +225,12 @@ class Wheel(object): wheel_metadata = self.get_wheel_metadata(zf) wv = wheel_metadata['Wheel-Version'].split('.', 1) file_version = tuple([int(i) for i in wv]) - if file_version < (1, 1): - fns = [WHEEL_METADATA_FILENAME, METADATA_FILENAME, 'METADATA'] - else: - fns = [WHEEL_METADATA_FILENAME, METADATA_FILENAME] + # if file_version < (1, 1): + # fns = [WHEEL_METADATA_FILENAME, METADATA_FILENAME, + # LEGACY_METADATA_FILENAME] + # else: + # fns = [WHEEL_METADATA_FILENAME, METADATA_FILENAME] + fns = [WHEEL_METADATA_FILENAME, LEGACY_METADATA_FILENAME] result = None for fn in fns: try: @@ -299,10 +305,9 @@ class Wheel(object): return hash_kind, result def write_record(self, records, record_path, base): - records = list(records) # make a copy for sorting + records = list(records) # make a copy, as mutated p = to_posix(os.path.relpath(record_path, base)) records.append((p, '', '')) - records.sort() with CSVWriter(record_path) as writer: for row in records: writer.writerow(row) @@ -425,6 +430,18 @@ class Wheel(object): ap = to_posix(os.path.join(info_dir, 'WHEEL')) archive_paths.append((ap, p)) + # sort the entries by archive path. Not needed by any spec, but it + # keeps the archive listing and RECORD tidier than they would otherwise + # be. Use the number of path segments to keep directory entries together, + # and keep the dist-info stuff at the end. + def sorter(t): + ap = t[0] + n = ap.count('/') + if '.dist-info' in ap: + n += 10000 + return (n, ap) + archive_paths = sorted(archive_paths, key=sorter) + # Now, at last, RECORD. # Paths in here are archive paths - nothing else makes sense. self.write_records((distinfo, info_dir), libdir, archive_paths) @@ -476,7 +493,7 @@ class Wheel(object): data_dir = '%s.data' % name_ver info_dir = '%s.dist-info' % name_ver - metadata_name = posixpath.join(info_dir, METADATA_FILENAME) + metadata_name = posixpath.join(info_dir, LEGACY_METADATA_FILENAME) wheel_metadata_name = posixpath.join(info_dir, 'WHEEL') record_name = posixpath.join(info_dir, 'RECORD') @@ -562,6 +579,13 @@ class Wheel(object): if not is_script: with zf.open(arcname) as bf: fileop.copy_stream(bf, outfile) + # Issue #147: permission bits aren't preserved. Using + # zf.extract(zinfo, libdir) should have worked, but didn't, + # see https://www.thetopsites.net/article/53834422.shtml + # So ... manually preserve permission bits as given in zinfo + if os.name == 'posix': + # just set the normal permission bits + os.chmod(outfile, (zinfo.external_attr >> 16) & 0x1FF) outfiles.append(outfile) # Double check the digest of the written file if not dry_run and row[1]: @@ -619,7 +643,7 @@ class Wheel(object): for v in epdata[k].values(): s = '%s:%s' % (v.prefix, v.suffix) if v.flags: - s += ' %s' % v.flags + s += ' [%s]' % ','.join(v.flags) d[v.name] = s except Exception: logger.warning('Unable to read legacy script ' @@ -684,7 +708,7 @@ class Wheel(object): if cache is None: # Use native string to avoid issues on 2.x: see Python #20140. base = os.path.join(get_cache_base(), str('dylib-cache'), - sys.version[:3]) + '%s.%s' % sys.version_info[:2]) cache = Cache(base) return cache @@ -773,7 +797,7 @@ class Wheel(object): data_dir = '%s.data' % name_ver info_dir = '%s.dist-info' % name_ver - metadata_name = posixpath.join(info_dir, METADATA_FILENAME) + metadata_name = posixpath.join(info_dir, LEGACY_METADATA_FILENAME) wheel_metadata_name = posixpath.join(info_dir, 'WHEEL') record_name = posixpath.join(info_dir, 'RECORD') @@ -842,7 +866,7 @@ class Wheel(object): def get_version(path_map, info_dir): version = path = None - key = '%s/%s' % (info_dir, METADATA_FILENAME) + key = '%s/%s' % (info_dir, LEGACY_METADATA_FILENAME) if key not in path_map: key = '%s/PKG-INFO' % info_dir if key in path_map: @@ -868,7 +892,7 @@ class Wheel(object): if updated: md = Metadata(path=path) md.version = updated - legacy = not path.endswith(METADATA_FILENAME) + legacy = path.endswith(LEGACY_METADATA_FILENAME) md.write(path=path, legacy=legacy) logger.debug('Version updated from %r to %r', version, updated) @@ -924,6 +948,16 @@ class Wheel(object): shutil.copyfile(newpath, pathname) return modified +def _get_glibc_version(): + import platform + ver = platform.libc_ver() + result = [] + if ver[0] == 'glibc': + for s in ver[1].split('.'): + result.append(int(s) if s.isdigit() else 0) + result = tuple(result) + return result + def compatible_tags(): """ Return (pyver, abi, arch) tuples compatible with this Python. @@ -971,6 +1005,23 @@ def compatible_tags(): for abi in abis: for arch in arches: result.append((''.join((IMP_PREFIX, versions[0])), abi, arch)) + # manylinux + if abi != 'none' and sys.platform.startswith('linux'): + arch = arch.replace('linux_', '') + parts = _get_glibc_version() + if len(parts) == 2: + if parts >= (2, 5): + result.append((''.join((IMP_PREFIX, versions[0])), abi, + 'manylinux1_%s' % arch)) + if parts >= (2, 12): + result.append((''.join((IMP_PREFIX, versions[0])), abi, + 'manylinux2010_%s' % arch)) + if parts >= (2, 17): + result.append((''.join((IMP_PREFIX, versions[0])), abi, + 'manylinux2014_%s' % arch)) + result.append((''.join((IMP_PREFIX, versions[0])), abi, + 'manylinux_%s_%s_%s' % (parts[0], parts[1], + arch))) # where no ABI / arch dependency, but IMP_PREFIX dependency for i, version in enumerate(versions): @@ -983,6 +1034,7 @@ def compatible_tags(): result.append((''.join(('py', version)), 'none', 'any')) if i == 0: result.append((''.join(('py', version[0])), 'none', 'any')) + return set(result) diff --git a/pipenv/vendor/dotenv/LICENSE b/pipenv/vendor/dotenv/LICENSE deleted file mode 100644 index 39372fee..00000000 --- a/pipenv/vendor/dotenv/LICENSE +++ /dev/null @@ -1,87 +0,0 @@ -python-dotenv -Copyright (c) 2014, Saurabh Kumar - -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - * Neither the name of python-dotenv nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -django-dotenv-rw -Copyright (c) 2013, Ted Tieken - -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - * Neither the name of django-dotenv nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -Original django-dotenv -Copyright (c) 2013, Jacob Kaplan-Moss - -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - * Neither the name of django-dotenv nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/pipenv/vendor/dotenv/__init__.py b/pipenv/vendor/dotenv/__init__.py index b88d9bc2..3512d101 100644 --- a/pipenv/vendor/dotenv/__init__.py +++ b/pipenv/vendor/dotenv/__init__.py @@ -1,18 +1,21 @@ -from .compat import IS_TYPE_CHECKING -from .main import load_dotenv, get_key, set_key, unset_key, find_dotenv, dotenv_values +from typing import Any, Optional -if IS_TYPE_CHECKING: - from typing import Any, Optional +from .main import (dotenv_values, find_dotenv, get_key, load_dotenv, set_key, + unset_key) -def load_ipython_extension(ipython): - # type: (Any) -> None +def load_ipython_extension(ipython: Any) -> None: from .ipython import load_ipython_extension load_ipython_extension(ipython) -def get_cli_string(path=None, action=None, key=None, value=None, quote=None): - # type: (Optional[str], Optional[str], Optional[str], Optional[str], Optional[str]) -> str +def get_cli_string( + path: Optional[str] = None, + action: Optional[str] = None, + key: Optional[str] = None, + value: Optional[str] = None, + quote: Optional[str] = None, +): """Returns a string suitable for running as a shell script. Useful for converting a arguments passed to a fabric task diff --git a/pipenv/vendor/dotenv/cli.py b/pipenv/vendor/dotenv/cli.py index 829b14ad..5c7ab4ba 100644 --- a/pipenv/vendor/dotenv/cli.py +++ b/pipenv/vendor/dotenv/cli.py @@ -1,44 +1,49 @@ import os import sys +from subprocess import Popen +from typing import Any, Dict, List try: - import click + import pipenv.vendor.click as click except ImportError: sys.stderr.write('It seems python-dotenv is not installed with cli option. \n' 'Run pip install "python-dotenv[cli]" to fix this.') sys.exit(1) -from .compat import IS_TYPE_CHECKING -from .main import dotenv_values, get_key, set_key, unset_key, run_command +from .main import dotenv_values, get_key, set_key, unset_key from .version import __version__ -if IS_TYPE_CHECKING: - from typing import Any, List - @click.group() @click.option('-f', '--file', default=os.path.join(os.getcwd(), '.env'), - type=click.Path(exists=True), + type=click.Path(file_okay=True), help="Location of the .env file, defaults to .env file in current working directory.") @click.option('-q', '--quote', default='always', type=click.Choice(['always', 'never', 'auto']), help="Whether to quote or not the variable values. Default mode is always. This does not affect parsing.") +@click.option('-e', '--export', default=False, + type=click.BOOL, + help="Whether to write the dot file as an executable bash script.") @click.version_option(version=__version__) @click.pass_context -def cli(ctx, file, quote): - # type: (click.Context, Any, Any) -> None +def cli(ctx: click.Context, file: Any, quote: Any, export: Any) -> None: '''This script is used to set, get or unset values from a .env file.''' ctx.obj = {} - ctx.obj['FILE'] = file ctx.obj['QUOTE'] = quote + ctx.obj['EXPORT'] = export + ctx.obj['FILE'] = file @cli.command() @click.pass_context -def list(ctx): - # type: (click.Context) -> None +def list(ctx: click.Context) -> None: '''Display all the stored key/value.''' file = ctx.obj['FILE'] + if not os.path.isfile(file): + raise click.BadParameter( + 'Path "%s" does not exist.' % (file), + ctx=ctx + ) dotenv_as_dict = dotenv_values(file) for k, v in dotenv_as_dict.items(): click.echo('%s=%s' % (k, v)) @@ -48,12 +53,12 @@ def list(ctx): @click.pass_context @click.argument('key', required=True) @click.argument('value', required=True) -def set(ctx, key, value): - # type: (click.Context, Any, Any) -> None +def set(ctx: click.Context, key: Any, value: Any) -> None: '''Store the given key/value.''' file = ctx.obj['FILE'] quote = ctx.obj['QUOTE'] - success, key, value = set_key(file, key, value, quote) + export = ctx.obj['EXPORT'] + success, key, value = set_key(file, key, value, quote, export) if success: click.echo('%s=%s' % (key, value)) else: @@ -63,13 +68,17 @@ def set(ctx, key, value): @cli.command() @click.pass_context @click.argument('key', required=True) -def get(ctx, key): - # type: (click.Context, Any) -> None +def get(ctx: click.Context, key: Any) -> None: '''Retrieve the value for the given key.''' file = ctx.obj['FILE'] + if not os.path.isfile(file): + raise click.BadParameter( + 'Path "%s" does not exist.' % (file), + ctx=ctx + ) stored_value = get_key(file, key) if stored_value: - click.echo('%s=%s' % (key, stored_value)) + click.echo(stored_value) else: exit(1) @@ -77,8 +86,7 @@ def get(ctx, key): @cli.command() @click.pass_context @click.argument('key', required=True) -def unset(ctx, key): - # type: (click.Context, Any) -> None +def unset(ctx: click.Context, key: Any) -> None: '''Removes the given key.''' file = ctx.obj['FILE'] quote = ctx.obj['QUOTE'] @@ -91,18 +99,66 @@ def unset(ctx, key): @cli.command(context_settings={'ignore_unknown_options': True}) @click.pass_context +@click.option( + "--override/--no-override", + default=True, + help="Override variables from the environment file with those from the .env file.", +) @click.argument('commandline', nargs=-1, type=click.UNPROCESSED) -def run(ctx, commandline): - # type: (click.Context, List[str]) -> None +def run(ctx: click.Context, override: bool, commandline: List[str]) -> None: """Run command with environment variables present.""" file = ctx.obj['FILE'] - dotenv_as_dict = dotenv_values(file) + if not os.path.isfile(file): + raise click.BadParameter( + 'Invalid value for \'-f\' "%s" does not exist.' % (file), + ctx=ctx + ) + dotenv_as_dict = { + k: v + for (k, v) in dotenv_values(file).items() + if v is not None and (override or k not in os.environ) + } + if not commandline: click.echo('No command given.') exit(1) - ret = run_command(commandline, dotenv_as_dict) # type: ignore + ret = run_command(commandline, dotenv_as_dict) exit(ret) +def run_command(command: List[str], env: Dict[str, str]) -> int: + """Run command in sub process. + + Runs the command in a sub process with the variables from `env` + added in the current environment variables. + + Parameters + ---------- + command: List[str] + The command and it's parameters + env: Dict + The additional environment variables + + Returns + ------- + int + The return code of the command + + """ + # copy the current environment variables and add the vales from + # `env` + cmd_env = os.environ.copy() + cmd_env.update(env) + + p = Popen(command, + universal_newlines=True, + bufsize=0, + shell=False, + env=cmd_env) + _, _ = p.communicate() + + return p.returncode + + if __name__ == "__main__": cli() diff --git a/pipenv/vendor/dotenv/compat.py b/pipenv/vendor/dotenv/compat.py deleted file mode 100644 index 7a8694fc..00000000 --- a/pipenv/vendor/dotenv/compat.py +++ /dev/null @@ -1,21 +0,0 @@ -import os -import sys - -if sys.version_info >= (3, 0): - from io import StringIO # noqa -else: - from StringIO import StringIO # noqa - -PY2 = sys.version_info[0] == 2 # type: bool - - -def is_type_checking(): - # type: () -> bool - try: - from typing import TYPE_CHECKING - except ImportError: - return False - return TYPE_CHECKING - - -IS_TYPE_CHECKING = os.environ.get("MYPY_RUNNING", is_type_checking()) diff --git a/pipenv/vendor/dotenv/ipython.py b/pipenv/vendor/dotenv/ipython.py index 7f1b13d6..7df727cd 100644 --- a/pipenv/vendor/dotenv/ipython.py +++ b/pipenv/vendor/dotenv/ipython.py @@ -1,5 +1,3 @@ -from __future__ import print_function - from IPython.core.magic import Magics, line_magic, magics_class # type: ignore from IPython.core.magic_arguments import (argument, magic_arguments, # type: ignore parse_argstring) # type: ignore diff --git a/pipenv/vendor/dotenv/main.py b/pipenv/vendor/dotenv/main.py index 64d42696..b8d0a4e0 100644 --- a/pipenv/vendor/dotenv/main.py +++ b/pipenv/vendor/dotenv/main.py @@ -1,184 +1,101 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals - -import codecs import io +import logging import os -import re import shutil import sys -from subprocess import Popen import tempfile -import warnings from collections import OrderedDict from contextlib import contextmanager +from typing import (IO, Dict, Iterable, Iterator, Mapping, Optional, Tuple, + Union) -from .compat import StringIO, PY2, IS_TYPE_CHECKING +from .parser import Binding, parse_stream +from .variables import parse_variables -if IS_TYPE_CHECKING: # pragma: no cover - from typing import ( - Dict, Iterator, List, Match, Optional, Pattern, Union, - Text, IO, Tuple - ) - if sys.version_info >= (3, 6): - _PathLike = os.PathLike - else: - _PathLike = Text +logger = logging.getLogger(__name__) - if sys.version_info >= (3, 0): - _StringIO = StringIO - else: - _StringIO = StringIO[Text] - -__posix_variable = re.compile(r'\$\{[^\}]*\}') # type: Pattern[Text] - -_binding = re.compile( - r""" - ( - \s* # leading whitespace - (?:export{0}+)? # export - - ( '[^']+' # single-quoted key - | [^=\#\s]+ # or unquoted key - )? - - (?: - (?:{0}*={0}*) # equal sign - - ( '(?:\\'|[^'])*' # single-quoted value - | "(?:\\"|[^"])*" # or double-quoted value - | [^\#\r\n]* # or unquoted value - ) - )? - - \s* # trailing whitespace - (?:\#[^\r\n]*)? # comment - (?:\r|\n|\r\n)? # newline - ) - """.format(r'[^\S\r\n]'), - re.MULTILINE | re.VERBOSE, -) # type: Pattern[Text] - -_escape_sequence = re.compile(r"\\[\\'\"abfnrtv]") # type: Pattern[Text] - -try: - from typing import NamedTuple, Optional, Text - Binding = NamedTuple("Binding", [("key", Optional[Text]), - ("value", Optional[Text]), - ("original", Text)]) -except ImportError: - from collections import namedtuple - Binding = namedtuple("Binding", ["key", "value", "original"]) +if sys.version_info >= (3, 6): + _PathLike = os.PathLike +else: + _PathLike = str -def decode_escapes(string): - # type: (Text) -> Text - def decode_match(match): - # type: (Match[Text]) -> Text - return codecs.decode(match.group(0), 'unicode-escape') # type: ignore - - return _escape_sequence.sub(decode_match, string) - - -def is_surrounded_by(string, char): - # type: (Text, Text) -> bool - return ( - len(string) > 1 - and string[0] == string[-1] == char - ) - - -def parse_binding(string, position): - # type: (Text, int) -> Tuple[Binding, int] - match = _binding.match(string, position) - assert match is not None - (matched, key, value) = match.groups() - if key is None or value is None: - key = None - value = None - else: - value_quoted = is_surrounded_by(value, "'") or is_surrounded_by(value, '"') - if value_quoted: - value = decode_escapes(value[1:-1]) - else: - value = value.strip() - return (Binding(key=key, value=value, original=matched), match.end()) - - -def parse_stream(stream): - # type:(IO[Text]) -> Iterator[Binding] - string = stream.read() - position = 0 - length = len(string) - while position < length: - (binding, position) = parse_binding(string, position) - yield binding - - -def to_env(text): - # type: (Text) -> str - """ - Encode a string the same way whether it comes from the environment or a `.env` file. - """ - if PY2: - return text.encode(sys.getfilesystemencoding() or "utf-8") - else: - return text +def with_warn_for_invalid_lines(mappings: Iterator[Binding]) -> Iterator[Binding]: + for mapping in mappings: + if mapping.error: + logger.warning( + "Python-dotenv could not parse statement starting at line %s", + mapping.original.line, + ) + yield mapping class DotEnv(): - - def __init__(self, dotenv_path, verbose=False, encoding=None): - # type: (Union[Text, _PathLike, _StringIO], bool, Union[None, Text]) -> None - self.dotenv_path = dotenv_path # type: Union[Text,_PathLike, _StringIO] - self._dict = None # type: Optional[Dict[Text, Text]] + def __init__( + self, + dotenv_path: Optional[Union[str, _PathLike]], + stream: Optional[IO[str]] = None, + verbose: bool = False, + encoding: Union[None, str] = None, + interpolate: bool = True, + override: bool = True, + ) -> None: + self.dotenv_path = dotenv_path # type: Optional[Union[str, _PathLike]] + self.stream = stream # type: Optional[IO[str]] + self._dict = None # type: Optional[Dict[str, Optional[str]]] self.verbose = verbose # type: bool - self.encoding = encoding # type: Union[None, Text] + self.encoding = encoding # type: Union[None, str] + self.interpolate = interpolate # type: bool + self.override = override # type: bool @contextmanager - def _get_stream(self): - # type: () -> Iterator[IO[Text]] - if isinstance(self.dotenv_path, StringIO): - yield self.dotenv_path - elif os.path.isfile(self.dotenv_path): + def _get_stream(self) -> Iterator[IO[str]]: + if self.dotenv_path and os.path.isfile(self.dotenv_path): with io.open(self.dotenv_path, encoding=self.encoding) as stream: yield stream + elif self.stream is not None: + yield self.stream else: if self.verbose: - warnings.warn("File doesn't exist {}".format(self.dotenv_path)) # type: ignore - yield StringIO('') + logger.info( + "Python-dotenv could not find configuration file %s.", + self.dotenv_path or '.env', + ) + yield io.StringIO('') - def dict(self): - # type: () -> Dict[Text, Text] + def dict(self) -> Dict[str, Optional[str]]: """Return dotenv as dict""" if self._dict: return self._dict - values = OrderedDict(self.parse()) - self._dict = resolve_nested_variables(values) + raw_values = self.parse() + + if self.interpolate: + self._dict = OrderedDict(resolve_variables(raw_values, override=self.override)) + else: + self._dict = OrderedDict(raw_values) + return self._dict - def parse(self): - # type: () -> Iterator[Tuple[Text, Text]] + def parse(self) -> Iterator[Tuple[str, Optional[str]]]: with self._get_stream() as stream: - for mapping in parse_stream(stream): - if mapping.key is not None and mapping.value is not None: + for mapping in with_warn_for_invalid_lines(parse_stream(stream)): + if mapping.key is not None: yield mapping.key, mapping.value - def set_as_environment_variables(self, override=False): - # type: (bool) -> bool + def set_as_environment_variables(self) -> bool: """ - Load the current dotenv as system environemt variable. + Load the current dotenv as system environment variable. """ for k, v in self.dict().items(): - if k in os.environ and not override: + if k in os.environ and not self.override: continue - os.environ[to_env(k)] = to_env(v) + if v is not None: + os.environ[k] = v return True - def get(self, key): - # type: (Text) -> Optional[Text] + def get(self, key: str) -> Optional[str]: """ """ data = self.dict() @@ -187,13 +104,12 @@ class DotEnv(): return data[key] if self.verbose: - warnings.warn("key %s not found in %s." % (key, self.dotenv_path)) # type: ignore + logger.warning("Key %s not found in %s.", key, self.dotenv_path) return None -def get_key(dotenv_path, key_to_get): - # type: (Union[Text, _PathLike], Text) -> Optional[Text] +def get_key(dotenv_path: Union[str, _PathLike], key_to_get: str) -> Optional[str]: """ Gets the value of a given key from the given .env @@ -203,9 +119,11 @@ def get_key(dotenv_path, key_to_get): @contextmanager -def rewrite(path): - # type: (_PathLike) -> Iterator[Tuple[IO[Text], IO[Text]]] +def rewrite(path: Union[str, _PathLike]) -> Iterator[Tuple[IO[str], IO[str]]]: try: + if not os.path.isfile(path): + with io.open(path, "w+") as source: + source.write("") with tempfile.NamedTemporaryFile(mode="w+", delete=False) as dest: with io.open(path) as source: yield (source, dest) # type: ignore @@ -217,41 +135,55 @@ def rewrite(path): shutil.move(dest.name, path) -def set_key(dotenv_path, key_to_set, value_to_set, quote_mode="always"): - # type: (_PathLike, Text, Text, Text) -> Tuple[Optional[bool], Text, Text] +def set_key( + dotenv_path: Union[str, _PathLike], + key_to_set: str, + value_to_set: str, + quote_mode: str = "always", + export: bool = False, +) -> Tuple[Optional[bool], str, str]: """ Adds or Updates a key/value to the given .env If the .env path given doesn't exist, fails instead of risking creating an orphan .env somewhere in the filesystem """ - value_to_set = value_to_set.strip("'").strip('"') - if not os.path.exists(dotenv_path): - warnings.warn("can't write to %s - it doesn't exist." % dotenv_path) # type: ignore - return None, key_to_set, value_to_set + if quote_mode not in ("always", "auto", "never"): + raise ValueError("Unknown quote_mode: {}".format(quote_mode)) - if " " in value_to_set: - quote_mode = "always" + quote = ( + quote_mode == "always" + or (quote_mode == "auto" and not value_to_set.isalnum()) + ) - line_template = '{}="{}"\n' if quote_mode == "always" else '{}={}\n' - line_out = line_template.format(key_to_set, value_to_set) + if quote: + value_out = "'{}'".format(value_to_set.replace("'", "\\'")) + else: + value_out = value_to_set + if export: + line_out = 'export {}={}\n'.format(key_to_set, value_out) + else: + line_out = "{}={}\n".format(key_to_set, value_out) with rewrite(dotenv_path) as (source, dest): replaced = False - for mapping in parse_stream(source): + for mapping in with_warn_for_invalid_lines(parse_stream(source)): if mapping.key == key_to_set: dest.write(line_out) replaced = True else: - dest.write(mapping.original) + dest.write(mapping.original.string) if not replaced: dest.write(line_out) return True, key_to_set, value_to_set -def unset_key(dotenv_path, key_to_unset, quote_mode="always"): - # type: (_PathLike, Text, Text) -> Tuple[Optional[bool], Text] +def unset_key( + dotenv_path: Union[str, _PathLike], + key_to_unset: str, + quote_mode: str = "always", +) -> Tuple[Optional[bool], str]: """ Removes a given key from the given .env @@ -259,54 +191,50 @@ def unset_key(dotenv_path, key_to_unset, quote_mode="always"): If the given key doesn't exist in the .env, fails """ if not os.path.exists(dotenv_path): - warnings.warn("can't delete from %s - it doesn't exist." % dotenv_path) # type: ignore + logger.warning("Can't delete from %s - it doesn't exist.", dotenv_path) return None, key_to_unset removed = False with rewrite(dotenv_path) as (source, dest): - for mapping in parse_stream(source): + for mapping in with_warn_for_invalid_lines(parse_stream(source)): if mapping.key == key_to_unset: removed = True else: - dest.write(mapping.original) + dest.write(mapping.original.string) if not removed: - warnings.warn("key %s not removed from %s - key doesn't exist." % (key_to_unset, dotenv_path)) # type: ignore + logger.warning("Key %s not removed from %s - key doesn't exist.", key_to_unset, dotenv_path) return None, key_to_unset return removed, key_to_unset -def resolve_nested_variables(values): - # type: (Dict[Text, Text]) -> Dict[Text, Text] - def _replacement(name): - # type: (Text) -> Text - """ - get appropriate value for a variable name. - first search in environ, if not found, - then look into the dotenv variables - """ - ret = os.getenv(name, new_values.get(name, "")) - return ret +def resolve_variables( + values: Iterable[Tuple[str, Optional[str]]], + override: bool, +) -> Mapping[str, Optional[str]]: + new_values = {} # type: Dict[str, Optional[str]] - def _re_sub_callback(match_object): - # type: (Match[Text]) -> Text - """ - From a match object gets the variable name and returns - the correct replacement - """ - return _replacement(match_object.group()[2:-1]) + for (name, value) in values: + if value is None: + result = None + else: + atoms = parse_variables(value) + env = {} # type: Dict[str, Optional[str]] + if override: + env.update(os.environ) # type: ignore + env.update(new_values) + else: + env.update(new_values) + env.update(os.environ) # type: ignore + result = "".join(atom.resolve(env) for atom in atoms) - new_values = {} - - for k, v in values.items(): - new_values[k] = __posix_variable.sub(_re_sub_callback, v) + new_values[name] = result return new_values -def _walk_to_root(path): - # type: (Text) -> Iterator[Text] +def _walk_to_root(path: str) -> Iterator[str]: """ Yield directories starting from the given directory up to the root """ @@ -324,28 +252,32 @@ def _walk_to_root(path): last_dir, current_dir = current_dir, parent_dir -def find_dotenv(filename='.env', raise_error_if_not_found=False, usecwd=False): - # type: (Text, bool, bool) -> Text +def find_dotenv( + filename: str = '.env', + raise_error_if_not_found: bool = False, + usecwd: bool = False, +) -> str: """ Search in increasingly higher folders for the given file Returns path to the file if found, or an empty string otherwise """ - if usecwd or '__file__' not in globals(): - # should work without __file__, e.g. in REPL or IPython notebook + + def _is_interactive(): + """ Decide whether this is running in a REPL or IPython notebook """ + main = __import__('__main__', None, None, fromlist=['__file__']) + return not hasattr(main, '__file__') + + if usecwd or _is_interactive() or getattr(sys, 'frozen', False): + # Should work without __file__, e.g. in REPL or IPython notebook. path = os.getcwd() else: # will work for .py files frame = sys._getframe() - # find first frame that is outside of this file - if PY2 and not __file__.endswith('.py'): - # in Python2 __file__ extension could be .pyc or .pyo (this doesn't account - # for edge case of Python compiled for non-standard extension) - current_file = __file__.rsplit('.', 1)[0] + '.py' - else: - current_file = __file__ + current_file = __file__ while frame.f_code.co_filename == current_file: + assert frame.f_back is not None frame = frame.f_back frame_filename = frame.f_code.co_filename path = os.path.dirname(os.path.abspath(frame_filename)) @@ -361,48 +293,68 @@ def find_dotenv(filename='.env', raise_error_if_not_found=False, usecwd=False): return '' -def load_dotenv(dotenv_path=None, stream=None, verbose=False, override=False, **kwargs): - # type: (Union[Text, _PathLike, None], Optional[_StringIO], bool, bool, Union[None, Text]) -> bool - f = dotenv_path or stream or find_dotenv() - return DotEnv(f, verbose=verbose, **kwargs).set_as_environment_variables(override=override) +def load_dotenv( + dotenv_path: Union[str, _PathLike, None] = None, + stream: Optional[IO[str]] = None, + verbose: bool = False, + override: bool = False, + interpolate: bool = True, + encoding: Optional[str] = "utf-8", +) -> bool: + """Parse a .env file and then load all the variables found as environment variables. + - *dotenv_path*: absolute or relative path to .env file. + - *stream*: Text stream (such as `io.StringIO`) with .env content, used if + `dotenv_path` is `None`. + - *verbose*: whether to output a warning the .env file is missing. Defaults to + `False`. + - *override*: whether to override the system environment variables with the variables + in `.env` file. Defaults to `False`. + - *encoding*: encoding to be used to read the file. -def dotenv_values(dotenv_path=None, stream=None, verbose=False, **kwargs): - # type: (Union[Text, _PathLike, None], Optional[_StringIO], bool, Union[None, Text]) -> Dict[Text, Text] - f = dotenv_path or stream or find_dotenv() - return DotEnv(f, verbose=verbose, **kwargs).dict() - - -def run_command(command, env): - # type: (List[str], Dict[str, str]) -> int - """Run command in sub process. - - Runs the command in a sub process with the variables from `env` - added in the current environment variables. - - Parameters - ---------- - command: List[str] - The command and it's parameters - env: Dict - The additional environment variables - - Returns - ------- - int - The return code of the command - + If both `dotenv_path` and `stream`, `find_dotenv()` is used to find the .env file. """ - # copy the current environment variables and add the vales from - # `env` - cmd_env = os.environ.copy() - cmd_env.update(env) + if dotenv_path is None and stream is None: + dotenv_path = find_dotenv() - p = Popen(command, - universal_newlines=True, - bufsize=0, - shell=False, - env=cmd_env) - _, _ = p.communicate() + dotenv = DotEnv( + dotenv_path=dotenv_path, + stream=stream, + verbose=verbose, + interpolate=interpolate, + override=override, + encoding=encoding, + ) + return dotenv.set_as_environment_variables() - return p.returncode + +def dotenv_values( + dotenv_path: Union[str, _PathLike, None] = None, + stream: Optional[IO[str]] = None, + verbose: bool = False, + interpolate: bool = True, + encoding: Optional[str] = "utf-8", +) -> Dict[str, Optional[str]]: + """ + Parse a .env file and return its content as a dict. + + - *dotenv_path*: absolute or relative path to .env file. + - *stream*: `StringIO` object with .env content, used if `dotenv_path` is `None`. + - *verbose*: whether to output a warning the .env file is missing. Defaults to + `False`. + in `.env` file. Defaults to `False`. + - *encoding*: encoding to be used to read the file. + + If both `dotenv_path` and `stream`, `find_dotenv()` is used to find the .env file. + """ + if dotenv_path is None and stream is None: + dotenv_path = find_dotenv() + + return DotEnv( + dotenv_path=dotenv_path, + stream=stream, + verbose=verbose, + interpolate=interpolate, + override=True, + encoding=encoding, + ).dict() diff --git a/pipenv/vendor/dotenv/parser.py b/pipenv/vendor/dotenv/parser.py new file mode 100644 index 00000000..398bd49a --- /dev/null +++ b/pipenv/vendor/dotenv/parser.py @@ -0,0 +1,182 @@ +import codecs +import re +from typing import (IO, Iterator, Match, NamedTuple, Optional, # noqa:F401 + Pattern, Sequence, Tuple) + + +def make_regex(string: str, extra_flags: int = 0) -> Pattern[str]: + return re.compile(string, re.UNICODE | extra_flags) + + +_newline = make_regex(r"(\r\n|\n|\r)") +_multiline_whitespace = make_regex(r"\s*", extra_flags=re.MULTILINE) +_whitespace = make_regex(r"[^\S\r\n]*") +_export = make_regex(r"(?:export[^\S\r\n]+)?") +_single_quoted_key = make_regex(r"'([^']+)'") +_unquoted_key = make_regex(r"([^=\#\s]+)") +_equal_sign = make_regex(r"(=[^\S\r\n]*)") +_single_quoted_value = make_regex(r"'((?:\\'|[^'])*)'") +_double_quoted_value = make_regex(r'"((?:\\"|[^"])*)"') +_unquoted_value = make_regex(r"([^\r\n]*)") +_comment = make_regex(r"(?:[^\S\r\n]*#[^\r\n]*)?") +_end_of_line = make_regex(r"[^\S\r\n]*(?:\r\n|\n|\r|$)") +_rest_of_line = make_regex(r"[^\r\n]*(?:\r|\n|\r\n)?") +_double_quote_escapes = make_regex(r"\\[\\'\"abfnrtv]") +_single_quote_escapes = make_regex(r"\\[\\']") + + +Original = NamedTuple( + "Original", + [ + ("string", str), + ("line", int), + ], +) + +Binding = NamedTuple( + "Binding", + [ + ("key", Optional[str]), + ("value", Optional[str]), + ("original", Original), + ("error", bool), + ], +) + + +class Position: + def __init__(self, chars: int, line: int) -> None: + self.chars = chars + self.line = line + + @classmethod + def start(cls) -> "Position": + return cls(chars=0, line=1) + + def set(self, other: "Position") -> None: + self.chars = other.chars + self.line = other.line + + def advance(self, string: str) -> None: + self.chars += len(string) + self.line += len(re.findall(_newline, string)) + + +class Error(Exception): + pass + + +class Reader: + def __init__(self, stream: IO[str]) -> None: + self.string = stream.read() + self.position = Position.start() + self.mark = Position.start() + + def has_next(self) -> bool: + return self.position.chars < len(self.string) + + def set_mark(self) -> None: + self.mark.set(self.position) + + def get_marked(self) -> Original: + return Original( + string=self.string[self.mark.chars:self.position.chars], + line=self.mark.line, + ) + + def peek(self, count: int) -> str: + return self.string[self.position.chars:self.position.chars + count] + + def read(self, count: int) -> str: + result = self.string[self.position.chars:self.position.chars + count] + if len(result) < count: + raise Error("read: End of string") + self.position.advance(result) + return result + + def read_regex(self, regex: Pattern[str]) -> Sequence[str]: + match = regex.match(self.string, self.position.chars) + if match is None: + raise Error("read_regex: Pattern not found") + self.position.advance(self.string[match.start():match.end()]) + return match.groups() + + +def decode_escapes(regex: Pattern[str], string: str) -> str: + def decode_match(match: Match[str]) -> str: + return codecs.decode(match.group(0), 'unicode-escape') # type: ignore + + return regex.sub(decode_match, string) + + +def parse_key(reader: Reader) -> Optional[str]: + char = reader.peek(1) + if char == "#": + return None + elif char == "'": + (key,) = reader.read_regex(_single_quoted_key) + else: + (key,) = reader.read_regex(_unquoted_key) + return key + + +def parse_unquoted_value(reader: Reader) -> str: + (part,) = reader.read_regex(_unquoted_value) + return re.sub(r"\s+#.*", "", part).rstrip() + + +def parse_value(reader: Reader) -> str: + char = reader.peek(1) + if char == u"'": + (value,) = reader.read_regex(_single_quoted_value) + return decode_escapes(_single_quote_escapes, value) + elif char == u'"': + (value,) = reader.read_regex(_double_quoted_value) + return decode_escapes(_double_quote_escapes, value) + elif char in (u"", u"\n", u"\r"): + return u"" + else: + return parse_unquoted_value(reader) + + +def parse_binding(reader: Reader) -> Binding: + reader.set_mark() + try: + reader.read_regex(_multiline_whitespace) + if not reader.has_next(): + return Binding( + key=None, + value=None, + original=reader.get_marked(), + error=False, + ) + reader.read_regex(_export) + key = parse_key(reader) + reader.read_regex(_whitespace) + if reader.peek(1) == "=": + reader.read_regex(_equal_sign) + value = parse_value(reader) # type: Optional[str] + else: + value = None + reader.read_regex(_comment) + reader.read_regex(_end_of_line) + return Binding( + key=key, + value=value, + original=reader.get_marked(), + error=False, + ) + except Error: + reader.read_regex(_rest_of_line) + return Binding( + key=None, + value=None, + original=reader.get_marked(), + error=True, + ) + + +def parse_stream(stream: IO[str]) -> Iterator[Binding]: + reader = Reader(stream) + while reader.has_next(): + yield parse_binding(reader) diff --git a/pipenv/vendor/dotenv/variables.py b/pipenv/vendor/dotenv/variables.py new file mode 100644 index 00000000..d77b700c --- /dev/null +++ b/pipenv/vendor/dotenv/variables.py @@ -0,0 +1,88 @@ +import re +from abc import ABCMeta +from typing import Iterator, Mapping, Optional, Pattern + +_posix_variable = re.compile( + r""" + \$\{ + (?P<name>[^\}:]*) + (?::- + (?P<default>[^\}]*) + )? + \} + """, + re.VERBOSE, +) # type: Pattern[str] + + +class Atom(): + __metaclass__ = ABCMeta + + def __ne__(self, other: object) -> bool: + result = self.__eq__(other) + if result is NotImplemented: + return NotImplemented + return not result + + def resolve(self, env: Mapping[str, Optional[str]]) -> str: + raise NotImplementedError + + +class Literal(Atom): + def __init__(self, value: str) -> None: + self.value = value + + def __repr__(self) -> str: + return "Literal(value={})".format(self.value) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, self.__class__): + return NotImplemented + return self.value == other.value + + def __hash__(self) -> int: + return hash((self.__class__, self.value)) + + def resolve(self, env: Mapping[str, Optional[str]]) -> str: + return self.value + + +class Variable(Atom): + def __init__(self, name: str, default: Optional[str]) -> None: + self.name = name + self.default = default + + def __repr__(self) -> str: + return "Variable(name={}, default={})".format(self.name, self.default) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, self.__class__): + return NotImplemented + return (self.name, self.default) == (other.name, other.default) + + def __hash__(self) -> int: + return hash((self.__class__, self.name, self.default)) + + def resolve(self, env: Mapping[str, Optional[str]]) -> str: + default = self.default if self.default is not None else "" + result = env.get(self.name, default) + return result if result is not None else "" + + +def parse_variables(value: str) -> Iterator[Atom]: + cursor = 0 + + for match in _posix_variable.finditer(value): + (start, end) = match.span() + name = match.groupdict()["name"] + default = match.groupdict()["default"] + + if start > cursor: + yield Literal(value=value[cursor:start]) + + yield Variable(name=name, default=default) + cursor = end + + length = len(value) + if cursor < length: + yield Literal(value=value[cursor:length]) diff --git a/pipenv/vendor/dotenv/version.py b/pipenv/vendor/dotenv/version.py index 17c1a626..11ac8e1a 100644 --- a/pipenv/vendor/dotenv/version.py +++ b/pipenv/vendor/dotenv/version.py @@ -1 +1 @@ -__version__ = "0.10.2" +__version__ = "0.19.0" diff --git a/pipenv/vendor/backports/functools_lru_cache.LICENSE b/pipenv/vendor/dparse/LICENSE similarity index 95% rename from pipenv/vendor/backports/functools_lru_cache.LICENSE rename to pipenv/vendor/dparse/LICENSE index 5e795a61..49d08568 100644 --- a/pipenv/vendor/backports/functools_lru_cache.LICENSE +++ b/pipenv/vendor/dparse/LICENSE @@ -1,7 +1,11 @@ -Copyright Jason R. Coombs + +MIT License + +Copyright (c) 2017, Jannis Gebauer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/pipenv/vendor/dparse/__init__.py b/pipenv/vendor/dparse/__init__.py new file mode 100644 index 00000000..afe8f63f --- /dev/null +++ b/pipenv/vendor/dparse/__init__.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals, absolute_import +"""Top-level package for Dependency Parser.""" + +__author__ = """Jannis Gebauer""" +__email__ = 'support@pyup.io' +__version__ = '0.5.1' + +from .parser import parse # noqa diff --git a/pipenv/vendor/dparse/dependencies.py b/pipenv/vendor/dparse/dependencies.py new file mode 100644 index 00000000..8881a9bc --- /dev/null +++ b/pipenv/vendor/dparse/dependencies.py @@ -0,0 +1,196 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals, absolute_import + +import json + +from . import filetypes, errors + + +class Dependency(object): + """ + + """ + + def __init__(self, name, specs, line, source="pypi", meta={}, extras=[], line_numbers=None, index_server=None, hashes=(), dependency_type=None, section=None): + """ + + :param name: + :param specs: + :param line: + :param source: + :param extras: + :param line_numbers: + :param index_server: + :param hashes: + :param dependency_type: + """ + self.name = name + self.key = name.lower().replace("_", "-") + self.specs = specs + self.line = line + self.source = source + self.meta = meta + self.line_numbers = line_numbers + self.index_server = index_server + self.hashes = hashes + self.dependency_type = dependency_type + self.extras = extras + self.section = section + + def __str__(self): # pragma: no cover + """ + + :return: + """ + return "Dependency({name}, {specs}, {line})".format( + name=self.name, + specs=self.specs, + line=self.line + ) + + def serialize(self): + """ + + :return: + """ + return { + "name": self.name, + "specs": self.specs, + "line": self.line, + "source": self.source, + "meta": self.meta, + "line_numbers": self.line_numbers, + "index_server": self.index_server, + "hashes": self.hashes, + "dependency_type": self.dependency_type, + "extras": self.extras, + "section": self.section + } + + @classmethod + def deserialize(cls, d): + """ + + :param d: + :return: + """ + return cls(**d) + + @property + def full_name(self): + """ + + :return: + """ + if self.extras: + return "{}[{}]".format(self.name, ",".join(self.extras)) + return self.name + + +class DependencyFile(object): + """ + + """ + + def __init__(self, content, path=None, sha=None, file_type=None, marker=((), ()), parser=None): + """ + + :param content: + :param path: + :param sha: + :param marker: + :param file_type: + :param parser: + """ + self.content = content + self.file_type = file_type + self.path = path + self.sha = sha + self.marker = marker + + self.dependencies = [] + self.resolved_files = [] + self.is_valid = False + self.file_marker, self.line_marker = marker + + if parser: + self.parser = parser + else: + from . import parser as parser_class + if file_type is not None: + if file_type == filetypes.requirements_txt: + self.parser = parser_class.RequirementsTXTParser + elif file_type == filetypes.tox_ini: + self.parser = parser_class.ToxINIParser + elif file_type == filetypes.conda_yml: + self.parser = parser_class.CondaYMLParser + elif file_type == filetypes.pipfile: + self.parser = parser_class.PipfileParser + elif file_type == filetypes.pipfile_lock: + self.parser = parser_class.PipfileLockParser + elif file_type == filetypes.setup_cfg: + self.parser = parser_class.SetupCfgParser + + elif path is not None: + if path.endswith(".txt"): + self.parser = parser_class.RequirementsTXTParser + elif path.endswith(".yml"): + self.parser = parser_class.CondaYMLParser + elif path.endswith(".ini"): + self.parser = parser_class.ToxINIParser + elif path.endswith("Pipfile"): + self.parser = parser_class.PipfileParser + elif path.endswith("Pipfile.lock"): + self.parser = parser_class.PipfileLockParser + elif path.endswith("setup.cfg"): + self.parser = parser_class.SetupCfgParser + + if not hasattr(self, "parser"): + raise errors.UnknownDependencyFileError + + self.parser = self.parser(self) + + def serialize(self): + """ + + :return: + """ + return { + "file_type": self.file_type, + "content": self.content, + "path": self.path, + "sha": self.sha, + "dependencies": [dep.serialize() for dep in self.dependencies] + } + + @classmethod + def deserialize(cls, d): + """ + + :param d: + :return: + """ + dependencies = [Dependency.deserialize(dep) for dep in d.pop("dependencies", [])] + instance = cls(**d) + instance.dependencies = dependencies + return instance + + def json(self): # pragma: no cover + """ + + :return: + """ + return json.dumps(self.serialize(), indent=2) + + def parse(self): + """ + + :return: + """ + if self.parser.is_marked_file: + self.is_valid = False + return self + self.parser.parse() + + self.is_valid = len(self.dependencies) > 0 or len(self.resolved_files) > 0 + return self diff --git a/pipenv/vendor/dparse/errors.py b/pipenv/vendor/dparse/errors.py new file mode 100644 index 00000000..b7a65430 --- /dev/null +++ b/pipenv/vendor/dparse/errors.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals, absolute_import + +class UnknownDependencyFileError(Exception): + """ + + """ + pass diff --git a/pipenv/vendor/dparse/filetypes.py b/pipenv/vendor/dparse/filetypes.py new file mode 100644 index 00000000..df8a913e --- /dev/null +++ b/pipenv/vendor/dparse/filetypes.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals, absolute_import + +requirements_txt = "requirements.txt" +conda_yml = "conda.yml" +setup_cfg = "setup.cfg" +tox_ini = "tox.ini" +pipfile = "Pipfile" +pipfile_lock = "Pipfile.lock" diff --git a/pipenv/vendor/dparse/parser.py b/pipenv/vendor/dparse/parser.py new file mode 100644 index 00000000..368a2883 --- /dev/null +++ b/pipenv/vendor/dparse/parser.py @@ -0,0 +1,427 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals, absolute_import +from collections import OrderedDict +import re +import yaml + +from io import StringIO + +from configparser import SafeConfigParser, NoOptionError + + +from .regex import URL_REGEX, HASH_REGEX + +from .dependencies import DependencyFile, Dependency +from pipenv.vendor.packaging.requirements import Requirement as PackagingRequirement, InvalidRequirement +from . import filetypes +import pipenv.vendor.toml as toml +from pipenv.vendor.packaging.specifiers import SpecifierSet +import json + + +# this is a backport from setuptools 26.1 +def setuptools_parse_requirements_backport(strs): # pragma: no cover + # Copyright (C) 2016 Jason R Coombs <jaraco@jaraco.com> + # + # Permission is hereby granted, free of charge, to any person obtaining a copy of + # this software and associated documentation files (the "Software"), to deal in + # the Software without restriction, including without limitation the rights to + # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + # of the Software, and to permit persons to whom the Software is furnished to do + # so, subject to the following conditions: + # + # The above copyright notice and this permission notice shall be included in all + # copies or substantial portions of the Software. + # + # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + # SOFTWARE. + """Yield ``Requirement`` objects for each specification in `strs` + + `strs` must be a string, or a (possibly-nested) iterable thereof. + """ + # create a steppable iterator, so we can handle \-continuations + def yield_lines(strs): + """Yield non-empty/non-comment lines of a string or sequence""" + if isinstance(strs, str): + for s in strs.splitlines(): + s = s.strip() + # skip blank lines/comments + if s and not s.startswith('#'): + yield s + else: + for ss in strs: + for s in yield_lines(ss): + yield s + lines = iter(yield_lines(strs)) + + for line in lines: + # Drop comments -- a hash without a space may be in a URL. + if ' #' in line: + line = line[:line.find(' #')] + # If there is a line continuation, drop it, and append the next line. + if line.endswith('\\'): + line = line[:-2].strip() + line += next(lines) + yield PackagingRequirement(line) + + +class RequirementsTXTLineParser(object): + """ + + """ + + @classmethod + def parse(cls, line): + """ + + :param line: + :return: + """ + try: + # setuptools requires a space before the comment. If this isn't the case, add it. + if "\t#" in line: + parsed, = setuptools_parse_requirements_backport(line.replace("\t#", "\t #")) + else: + parsed, = setuptools_parse_requirements_backport(line) + except InvalidRequirement: + return None + dep = Dependency( + name=parsed.name, + specs=parsed.specifier, + line=line, + extras=parsed.extras, + dependency_type=filetypes.requirements_txt + ) + return dep + + +class Parser(object): + """ + + """ + + def __init__(self, obj): + """ + + :param obj: + """ + self.obj = obj + self._lines = None + + def iter_lines(self, lineno=0): + """ + + :param lineno: + :return: + """ + for line in self.lines[lineno:]: + yield line + + @property + def lines(self): + """ + + :return: + """ + if self._lines is None: + self._lines = self.obj.content.splitlines() + return self._lines + + @property + def is_marked_file(self): + """ + + :return: + """ + for n, line in enumerate(self.iter_lines()): + for marker in self.obj.file_marker: + if marker in line: + return True + if n >= 2: + break + return False + + def is_marked_line(self, line): + """ + + :param line: + :return: + """ + for marker in self.obj.line_marker: + if marker in line: + return True + return False + + @classmethod + def parse_hashes(cls, line): + """ + + :param line: + :return: + """ + hashes = [] + for match in re.finditer(HASH_REGEX, line): + hashes.append(line[match.start():match.end()]) + return re.sub(HASH_REGEX, "", line).strip(), hashes + + @classmethod + def parse_index_server(cls, line): + """ + + :param line: + :return: + """ + matches = URL_REGEX.findall(line) + if matches: + url = matches[0] + return url if url.endswith("/") else url + "/" + return None + + @classmethod + def resolve_file(cls, file_path, line): + """ + + :param file_path: + :param line: + :return: + """ + line = line.replace("-r ", "").replace("--requirement ", "") + parts = file_path.split("/") + if " #" in line: + line = line.split("#")[0].strip() + if len(parts) == 1: + return line + return "/".join(parts[:-1]) + "/" + line + + +class RequirementsTXTParser(Parser): + """ + + """ + + def parse(self): + """ + Parses a requirements.txt-like file + """ + index_server = None + for num, line in enumerate(self.iter_lines()): + line = line.rstrip() + if not line: + continue + if line.startswith('#'): + # comments are lines that start with # only + continue + if line.startswith('-i') or \ + line.startswith('--index-url') or \ + line.startswith('--extra-index-url'): + # this file is using a private index server, try to parse it + index_server = self.parse_index_server(line) + continue + elif self.obj.path and (line.startswith('-r') or line.startswith('--requirement')): + self.obj.resolved_files.append(self.resolve_file(self.obj.path, line)) + elif line.startswith('-f') or line.startswith('--find-links') or \ + line.startswith('--no-index') or line.startswith('--allow-external') or \ + line.startswith('--allow-unverified') or line.startswith('-Z') or \ + line.startswith('--always-unzip'): + continue + elif self.is_marked_line(line): + continue + else: + try: + + parseable_line = line + + # multiline requirements are not parseable + if "\\" in line: + parseable_line = line.replace("\\", "") + for next_line in self.iter_lines(num + 1): + parseable_line += next_line.strip().replace("\\", "") + line += "\n" + next_line + if "\\" in next_line: + continue + break + # ignore multiline requirements if they are marked + if self.is_marked_line(parseable_line): + continue + + hashes = [] + if "--hash" in parseable_line: + parseable_line, hashes = Parser.parse_hashes(parseable_line) + + req = RequirementsTXTLineParser.parse(parseable_line) + if req: + req.hashes = hashes + req.index_server = index_server + # replace the requirements line with the 'real' line + req.line = line + self.obj.dependencies.append(req) + except ValueError: + continue + + +class ToxINIParser(Parser): + """ + + """ + + def parse(self): + """ + + :return: + """ + parser = SafeConfigParser() + parser.readfp(StringIO(self.obj.content)) + for section in parser.sections(): + try: + content = parser.get(section=section, option="deps") + for n, line in enumerate(content.splitlines()): + if self.is_marked_line(line): + continue + if line: + req = RequirementsTXTLineParser.parse(line) + if req: + req.dependency_type = self.obj.file_type + self.obj.dependencies.append(req) + except NoOptionError: + pass + + +class CondaYMLParser(Parser): + """ + + """ + + def parse(self): + """ + + :return: + """ + try: + data = yaml.safe_load(self.obj.content) + if data and 'dependencies' in data and isinstance(data['dependencies'], list): + for dep in data['dependencies']: + if isinstance(dep, dict) and 'pip' in dep: + for n, line in enumerate(dep['pip']): + if self.is_marked_line(line): + continue + req = RequirementsTXTLineParser.parse(line) + if req: + req.dependency_type = self.obj.file_type + self.obj.dependencies.append(req) + except yaml.YAMLError: + pass + + +class PipfileParser(Parser): + + def parse(self): + """ + Parse a Pipfile (as seen in pipenv) + :return: + """ + try: + data = toml.loads(self.obj.content, _dict=OrderedDict) + if data: + for package_type in ['packages', 'dev-packages']: + if package_type in data: + for name, specs in data[package_type].items(): + # skip on VCS dependencies + if not isinstance(specs, str): + continue + if specs == '*': + specs = '' + self.obj.dependencies.append( + Dependency( + name=name, specs=SpecifierSet(specs), + dependency_type=filetypes.pipfile, + line=''.join([name, specs]), + section=package_type + ) + ) + except (toml.TomlDecodeError, IndexError) as e: + pass + +class PipfileLockParser(Parser): + + def parse(self): + """ + Parse a Pipfile.lock (as seen in pipenv) + :return: + """ + try: + data = json.loads(self.obj.content, object_pairs_hook=OrderedDict) + if data: + for package_type in ['default', 'develop']: + if package_type in data: + for name, meta in data[package_type].items(): + # skip VCS dependencies + if 'version' not in meta: + continue + specs = meta['version'] + hashes = meta['hashes'] + self.obj.dependencies.append( + Dependency( + name=name, specs=SpecifierSet(specs), + dependency_type=filetypes.pipfile_lock, + hashes=hashes, + line=''.join([name, specs]), + section=package_type + ) + ) + except ValueError: + pass + + +class SetupCfgParser(Parser): + def parse(self): + parser = SafeConfigParser() + parser.readfp(StringIO(self.obj.content)) + for section in parser.values(): + if section.name == 'options': + options = 'install_requires', 'setup_requires', 'test_require' + for name in options: + content = section.get(name) + if not content: + continue + self._parse_content(content) + elif section.name == 'options.extras_require': + for content in section.values(): + self._parse_content(content) + + def _parse_content(self, content): + for n, line in enumerate(content.splitlines()): + if self.is_marked_line(line): + continue + if line: + req = RequirementsTXTLineParser.parse(line) + if req: + req.dependency_type = self.obj.file_type + self.obj.dependencies.append(req) + + +def parse(content, file_type=None, path=None, sha=None, marker=((), ()), parser=None): + """ + + :param content: + :param file_type: + :param path: + :param sha: + :param marker: + :param parser: + :return: + """ + dep_file = DependencyFile( + content=content, + path=path, + sha=sha, + marker=marker, + file_type=file_type, + parser=parser + ) + + return dep_file.parse() diff --git a/pipenv/vendor/dparse/regex.py b/pipenv/vendor/dparse/regex.py new file mode 100644 index 00000000..40cc4091 --- /dev/null +++ b/pipenv/vendor/dparse/regex.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, print_function, unicode_literals + +import re +# see https://gist.github.com/dperini/729294 +URL_REGEX = re.compile( + # protocol identifier + "(?:(?:https?|ftp)://)" + # user:pass authentication + "(?:\S+(?::\S*)?@)?" + "(?:" + # IP address exclusion + # private & local networks + "(?!(?:10|127)(?:\.\d{1,3}){3})" + "(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})" + "(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})" + # IP address dotted notation octets + # excludes loopback network 0.0.0.0 + # excludes reserved space >= 224.0.0.0 + # excludes network & broadcast addresses + # (first & last IP address of each class) + "(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])" + "(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}" + "(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))" + "|" + # host name + "(?:(?:[a-z\u00a1-\uffff0-9]-?)*[a-z\u00a1-\uffff0-9]+)" + # domain name + "(?:\.(?:[a-z\u00a1-\uffff0-9]-?)*[a-z\u00a1-\uffff0-9]+)*" + # TLD identifier + "(?:\.(?:[a-z\u00a1-\uffff]{2,}))" + ")" + # port number + "(?::\d{2,5})?" + # resource path + "(?:/\S*)?", + re.UNICODE) + +HASH_REGEX = r"--hash[=| ][\w]+:[\w]+" diff --git a/pipenv/vendor/dparse/updater.py b/pipenv/vendor/dparse/updater.py new file mode 100644 index 00000000..48117110 --- /dev/null +++ b/pipenv/vendor/dparse/updater.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, print_function, unicode_literals +import re +import json +import tempfile +import pipenv.vendor.toml as toml +import os + + +class RequirementsTXTUpdater(object): + + SUB_REGEX = r"^{}(?=\s*\r?\n?$)" + + @classmethod + def update(cls, content, dependency, version, spec="==", hashes=()): + """ + Updates the requirement to the latest version for the given content and adds hashes + if neccessary. + :param content: str, content + :return: str, updated content + """ + new_line = "{name}{spec}{version}".format(name=dependency.full_name, spec=spec, version=version) + appendix = '' + # leave environment markers intact + if ";" in dependency.line: + # condense multiline, split out the env marker, strip comments and --hashes + new_line += ";" + dependency.line.splitlines()[0].split(";", 1)[1] \ + .split("#")[0].split("--hash")[0].rstrip() + # add the comment + if "#" in dependency.line: + # split the line into parts: requirement and comment + parts = dependency.line.split("#") + requirement, comment = parts[0], "#".join(parts[1:]) + # find all whitespaces between the requirement and the comment + whitespaces = (hex(ord('\t')), hex(ord(' '))) + trailing_whitespace = '' + for c in requirement[::-1]: + if hex(ord(c)) in whitespaces: + trailing_whitespace += c + else: + break + appendix += trailing_whitespace + "#" + comment + # if this is a hashed requirement, add a multiline break before the comment + if dependency.hashes and not new_line.endswith("\\"): + new_line += " \\" + # if this is a hashed requirement, add the hashes + if hashes: + for n, new_hash in enumerate(hashes): + new_line += "\n --hash={method}:{hash}".format( + method=new_hash['method'], + hash=new_hash['hash'] + ) + # append a new multiline break if this is not the last line + if len(hashes) > n + 1: + new_line += " \\" + new_line += appendix + + regex = cls.SUB_REGEX.format(re.escape(dependency.line)) + + return re.sub(regex, new_line, content, flags=re.MULTILINE) + + +class CondaYMLUpdater(RequirementsTXTUpdater): + + SUB_REGEX = r"{}(?=\s*\r?\n?$)" + + +class ToxINIUpdater(CondaYMLUpdater): + pass + + +class SetupCFGUpdater(CondaYMLUpdater): + pass + + +class PipfileUpdater(object): + @classmethod + def update(cls, content, dependency, version, spec="==", hashes=()): + data = toml.loads(content) + if data: + for package_type in ['packages', 'dev-packages']: + if package_type in data: + if dependency.full_name in data[package_type]: + data[package_type][dependency.full_name] = "{spec}{version}".format( + spec=spec, version=version + ) + try: + from pipenv.project import Project + except ImportError: + raise ImportError("Updating a Pipfile requires the pipenv extra to be installed. Install it with " + "pip install dparse[pipenv]") + pipfile = tempfile.NamedTemporaryFile(delete=False) + p = Project(chdir=False) + p.write_toml(data=data, path=pipfile.name) + data = open(pipfile.name).read() + os.remove(pipfile.name) + return data + + +class PipfileLockUpdater(object): + @classmethod + def update(cls, content, dependency, version, spec="==", hashes=()): + data = json.loads(content) + if data: + for package_type in ['default', 'develop']: + if package_type in data: + if dependency.full_name in data[package_type]: + data[package_type][dependency.full_name] = { + 'hashes': [ + "{method}:{hash}".format( + hash=h['hash'], + method=h['method'] + ) for h in hashes + ], + 'version': "{spec}{version}".format( + spec=spec, version=version + ) + } + return json.dumps(data, indent=4, separators=(',', ': ')) + "\n" diff --git a/pipenv/vendor/first.py b/pipenv/vendor/first.py deleted file mode 100644 index 479e0bad..00000000 --- a/pipenv/vendor/first.py +++ /dev/null @@ -1,78 +0,0 @@ -## -*- coding: utf-8 -*- - -""" -first -===== - -first is the function you always missed in Python. - -In the simplest case, it returns the first true element from an iterable: - ->>> from first import first ->>> first([0, False, None, [], (), 42]) -42 - -Or None if there is none: - ->>> from first import first ->>> first([]) is None -True ->>> first([0, False, None, [], ()]) is None -True - -It also supports the passing of a key argument to help selecting the first -match in a more advanced way. - ->>> from first import first ->>> first([1, 1, 3, 4, 5], key=lambda x: x % 2 == 0) -4 - -:copyright: (c) 2012 by Hynek Schlawack. -:license: MIT, see LICENSE for more details. - -""" - -__title__ = 'first' -__version__ = '2.0.1' -__author__ = 'Hynek Schlawack' -__license__ = 'MIT' -__copyright__ = 'Copyright 2012–2013 Hynek Schlawack' - - -def first(iterable, default=None, key=None): - """ - Return first element of `iterable` that evaluates true, else return None - (or an optional default value). - - >>> first([0, False, None, [], (), 42]) - 42 - - >>> first([0, False, None, [], ()]) is None - True - - >>> first([0, False, None, [], ()], default='ohai') - 'ohai' - - >>> import re - >>> m = first(re.match(regex, 'abc') for regex in ['b.*', 'a(.*)']) - >>> m.group(1) - 'bc' - - The optional `key` argument specifies a one-argument predicate function - like that used for `filter()`. The `key` argument, if supplied, must be - in keyword form. For example: - - >>> first([1, 1, 3, 4, 5], key=lambda x: x % 2 == 0) - 4 - - """ - if key is None: - for el in iterable: - if el: - return el - else: - for el in iterable: - if key(el): - return el - - return default diff --git a/pipenv/vendor/funcsigs/LICENSE b/pipenv/vendor/funcsigs/LICENSE new file mode 100644 index 00000000..3e563d6f --- /dev/null +++ b/pipenv/vendor/funcsigs/LICENSE @@ -0,0 +1,13 @@ +Copyright 2013 Aaron Iles + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/pipenv/vendor/funcsigs/__init__.py b/pipenv/vendor/funcsigs/__init__.py new file mode 100644 index 00000000..dec83820 --- /dev/null +++ b/pipenv/vendor/funcsigs/__init__.py @@ -0,0 +1,829 @@ +# Copyright 2001-2013 Python Software Foundation; All Rights Reserved +"""Function signature objects for callables + +Back port of Python 3.3's function signature tools from the inspect module, +modified to be compatible with Python 2.6, 2.7 and 3.3+. +""" +from __future__ import absolute_import, division, print_function +import itertools +import functools +import re +import types + +try: + from collections import OrderedDict +except ImportError: + from ordereddict import OrderedDict + +from pipenv.vendor.funcsigs.version import __version__ + +__all__ = ['BoundArguments', 'Parameter', 'Signature', 'signature'] + + +_WrapperDescriptor = type(type.__call__) +_MethodWrapper = type(all.__call__) + +_NonUserDefinedCallables = (_WrapperDescriptor, + _MethodWrapper, + types.BuiltinFunctionType) + + +def formatannotation(annotation, base_module=None): + if isinstance(annotation, type): + if annotation.__module__ in ('builtins', '__builtin__', base_module): + return annotation.__name__ + return annotation.__module__+'.'+annotation.__name__ + return repr(annotation) + + +def _get_user_defined_method(cls, method_name, *nested): + try: + if cls is type: + return + meth = getattr(cls, method_name) + for name in nested: + meth = getattr(meth, name, meth) + except AttributeError: + return + else: + if not isinstance(meth, _NonUserDefinedCallables): + # Once '__signature__' will be added to 'C'-level + # callables, this check won't be necessary + return meth + + +def signature(obj): + '''Get a signature object for the passed callable.''' + + if not callable(obj): + raise TypeError('{0!r} is not a callable object'.format(obj)) + + if isinstance(obj, types.MethodType): + sig = signature(obj.__func__) + if obj.__self__ is None: + # Unbound method - preserve as-is. + return sig + else: + # Bound method. Eat self - if we can. + params = tuple(sig.parameters.values()) + + if not params or params[0].kind in (_VAR_KEYWORD, _KEYWORD_ONLY): + raise ValueError('invalid method signature') + + kind = params[0].kind + if kind in (_POSITIONAL_OR_KEYWORD, _POSITIONAL_ONLY): + # Drop first parameter: + # '(p1, p2[, ...])' -> '(p2[, ...])' + params = params[1:] + else: + if kind is not _VAR_POSITIONAL: + # Unless we add a new parameter type we never + # get here + raise ValueError('invalid argument type') + # It's a var-positional parameter. + # Do nothing. '(*args[, ...])' -> '(*args[, ...])' + + return sig.replace(parameters=params) + + try: + sig = obj.__signature__ + except AttributeError: + pass + else: + if sig is not None: + return sig + + try: + # Was this function wrapped by a decorator? + wrapped = obj.__wrapped__ + except AttributeError: + pass + else: + return signature(wrapped) + + if isinstance(obj, types.FunctionType): + return Signature.from_function(obj) + + if isinstance(obj, functools.partial): + sig = signature(obj.func) + + new_params = OrderedDict(sig.parameters.items()) + + partial_args = obj.args or () + partial_keywords = obj.keywords or {} + try: + ba = sig.bind_partial(*partial_args, **partial_keywords) + except TypeError as ex: + msg = 'partial object {0!r} has incorrect arguments'.format(obj) + raise ValueError(msg) + + for arg_name, arg_value in ba.arguments.items(): + param = new_params[arg_name] + if arg_name in partial_keywords: + # We set a new default value, because the following code + # is correct: + # + # >>> def foo(a): print(a) + # >>> print(partial(partial(foo, a=10), a=20)()) + # 20 + # >>> print(partial(partial(foo, a=10), a=20)(a=30)) + # 30 + # + # So, with 'partial' objects, passing a keyword argument is + # like setting a new default value for the corresponding + # parameter + # + # We also mark this parameter with '_partial_kwarg' + # flag. Later, in '_bind', the 'default' value of this + # parameter will be added to 'kwargs', to simulate + # the 'functools.partial' real call. + new_params[arg_name] = param.replace(default=arg_value, + _partial_kwarg=True) + + elif (param.kind not in (_VAR_KEYWORD, _VAR_POSITIONAL) and + not param._partial_kwarg): + new_params.pop(arg_name) + + return sig.replace(parameters=new_params.values()) + + sig = None + if isinstance(obj, type): + # obj is a class or a metaclass + + # First, let's see if it has an overloaded __call__ defined + # in its metaclass + call = _get_user_defined_method(type(obj), '__call__') + if call is not None: + sig = signature(call) + else: + # Now we check if the 'obj' class has a '__new__' method + new = _get_user_defined_method(obj, '__new__') + if new is not None: + sig = signature(new) + else: + # Finally, we should have at least __init__ implemented + init = _get_user_defined_method(obj, '__init__') + if init is not None: + sig = signature(init) + elif not isinstance(obj, _NonUserDefinedCallables): + # An object with __call__ + # We also check that the 'obj' is not an instance of + # _WrapperDescriptor or _MethodWrapper to avoid + # infinite recursion (and even potential segfault) + call = _get_user_defined_method(type(obj), '__call__', 'im_func') + if call is not None: + sig = signature(call) + + if sig is not None: + # For classes and objects we skip the first parameter of their + # __call__, __new__, or __init__ methods + return sig.replace(parameters=tuple(sig.parameters.values())[1:]) + + if isinstance(obj, types.BuiltinFunctionType): + # Raise a nicer error message for builtins + msg = 'no signature found for builtin function {0!r}'.format(obj) + raise ValueError(msg) + + raise ValueError('callable {0!r} is not supported by signature'.format(obj)) + + +class _void(object): + '''A private marker - used in Parameter & Signature''' + + +class _empty(object): + pass + + +class _ParameterKind(int): + def __new__(self, *args, **kwargs): + obj = int.__new__(self, *args) + obj._name = kwargs['name'] + return obj + + def __str__(self): + return self._name + + def __repr__(self): + return '<_ParameterKind: {0!r}>'.format(self._name) + + +_POSITIONAL_ONLY = _ParameterKind(0, name='POSITIONAL_ONLY') +_POSITIONAL_OR_KEYWORD = _ParameterKind(1, name='POSITIONAL_OR_KEYWORD') +_VAR_POSITIONAL = _ParameterKind(2, name='VAR_POSITIONAL') +_KEYWORD_ONLY = _ParameterKind(3, name='KEYWORD_ONLY') +_VAR_KEYWORD = _ParameterKind(4, name='VAR_KEYWORD') + + +class Parameter(object): + '''Represents a parameter in a function signature. + + Has the following public attributes: + + * name : str + The name of the parameter as a string. + * default : object + The default value for the parameter if specified. If the + parameter has no default value, this attribute is not set. + * annotation + The annotation for the parameter if specified. If the + parameter has no annotation, this attribute is not set. + * kind : str + Describes how argument values are bound to the parameter. + Possible values: `Parameter.POSITIONAL_ONLY`, + `Parameter.POSITIONAL_OR_KEYWORD`, `Parameter.VAR_POSITIONAL`, + `Parameter.KEYWORD_ONLY`, `Parameter.VAR_KEYWORD`. + ''' + + __slots__ = ('_name', '_kind', '_default', '_annotation', '_partial_kwarg') + + POSITIONAL_ONLY = _POSITIONAL_ONLY + POSITIONAL_OR_KEYWORD = _POSITIONAL_OR_KEYWORD + VAR_POSITIONAL = _VAR_POSITIONAL + KEYWORD_ONLY = _KEYWORD_ONLY + VAR_KEYWORD = _VAR_KEYWORD + + empty = _empty + + def __init__(self, name, kind, default=_empty, annotation=_empty, + _partial_kwarg=False): + + if kind not in (_POSITIONAL_ONLY, _POSITIONAL_OR_KEYWORD, + _VAR_POSITIONAL, _KEYWORD_ONLY, _VAR_KEYWORD): + raise ValueError("invalid value for 'Parameter.kind' attribute") + self._kind = kind + + if default is not _empty: + if kind in (_VAR_POSITIONAL, _VAR_KEYWORD): + msg = '{0} parameters cannot have default values'.format(kind) + raise ValueError(msg) + self._default = default + self._annotation = annotation + + if name is None: + if kind != _POSITIONAL_ONLY: + raise ValueError("None is not a valid name for a " + "non-positional-only parameter") + self._name = name + else: + name = str(name) + if kind != _POSITIONAL_ONLY and not re.match(r'[a-z_]\w*$', name, re.I): + msg = '{0!r} is not a valid parameter name'.format(name) + raise ValueError(msg) + self._name = name + + self._partial_kwarg = _partial_kwarg + + @property + def name(self): + return self._name + + @property + def default(self): + return self._default + + @property + def annotation(self): + return self._annotation + + @property + def kind(self): + return self._kind + + def replace(self, name=_void, kind=_void, annotation=_void, + default=_void, _partial_kwarg=_void): + '''Creates a customized copy of the Parameter.''' + + if name is _void: + name = self._name + + if kind is _void: + kind = self._kind + + if annotation is _void: + annotation = self._annotation + + if default is _void: + default = self._default + + if _partial_kwarg is _void: + _partial_kwarg = self._partial_kwarg + + return type(self)(name, kind, default=default, annotation=annotation, + _partial_kwarg=_partial_kwarg) + + def __str__(self): + kind = self.kind + + formatted = self._name + if kind == _POSITIONAL_ONLY: + if formatted is None: + formatted = '' + formatted = '<{0}>'.format(formatted) + + # Add annotation and default value + if self._annotation is not _empty: + formatted = '{0}:{1}'.format(formatted, + formatannotation(self._annotation)) + + if self._default is not _empty: + formatted = '{0}={1}'.format(formatted, repr(self._default)) + + if kind == _VAR_POSITIONAL: + formatted = '*' + formatted + elif kind == _VAR_KEYWORD: + formatted = '**' + formatted + + return formatted + + def __repr__(self): + return '<{0} at {1:#x} {2!r}>'.format(self.__class__.__name__, + id(self), self.name) + + def __hash__(self): + msg = "unhashable type: '{0}'".format(self.__class__.__name__) + raise TypeError(msg) + + def __eq__(self, other): + return (issubclass(other.__class__, Parameter) and + self._name == other._name and + self._kind == other._kind and + self._default == other._default and + self._annotation == other._annotation) + + def __ne__(self, other): + return not self.__eq__(other) + + +class BoundArguments(object): + '''Result of `Signature.bind` call. Holds the mapping of arguments + to the function's parameters. + + Has the following public attributes: + + * arguments : OrderedDict + An ordered mutable mapping of parameters' names to arguments' values. + Does not contain arguments' default values. + * signature : Signature + The Signature object that created this instance. + * args : tuple + Tuple of positional arguments values. + * kwargs : dict + Dict of keyword arguments values. + ''' + + def __init__(self, signature, arguments): + self.arguments = arguments + self._signature = signature + + @property + def signature(self): + return self._signature + + @property + def args(self): + args = [] + for param_name, param in self._signature.parameters.items(): + if (param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY) or + param._partial_kwarg): + # Keyword arguments mapped by 'functools.partial' + # (Parameter._partial_kwarg is True) are mapped + # in 'BoundArguments.kwargs', along with VAR_KEYWORD & + # KEYWORD_ONLY + break + + try: + arg = self.arguments[param_name] + except KeyError: + # We're done here. Other arguments + # will be mapped in 'BoundArguments.kwargs' + break + else: + if param.kind == _VAR_POSITIONAL: + # *args + args.extend(arg) + else: + # plain argument + args.append(arg) + + return tuple(args) + + @property + def kwargs(self): + kwargs = {} + kwargs_started = False + for param_name, param in self._signature.parameters.items(): + if not kwargs_started: + if (param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY) or + param._partial_kwarg): + kwargs_started = True + else: + if param_name not in self.arguments: + kwargs_started = True + continue + + if not kwargs_started: + continue + + try: + arg = self.arguments[param_name] + except KeyError: + pass + else: + if param.kind == _VAR_KEYWORD: + # **kwargs + kwargs.update(arg) + else: + # plain keyword argument + kwargs[param_name] = arg + + return kwargs + + def __hash__(self): + msg = "unhashable type: '{0}'".format(self.__class__.__name__) + raise TypeError(msg) + + def __eq__(self, other): + return (issubclass(other.__class__, BoundArguments) and + self.signature == other.signature and + self.arguments == other.arguments) + + def __ne__(self, other): + return not self.__eq__(other) + + +class Signature(object): + '''A Signature object represents the overall signature of a function. + It stores a Parameter object for each parameter accepted by the + function, as well as information specific to the function itself. + + A Signature object has the following public attributes and methods: + + * parameters : OrderedDict + An ordered mapping of parameters' names to the corresponding + Parameter objects (keyword-only arguments are in the same order + as listed in `code.co_varnames`). + * return_annotation : object + The annotation for the return type of the function if specified. + If the function has no annotation for its return type, this + attribute is not set. + * bind(*args, **kwargs) -> BoundArguments + Creates a mapping from positional and keyword arguments to + parameters. + * bind_partial(*args, **kwargs) -> BoundArguments + Creates a partial mapping from positional and keyword arguments + to parameters (simulating 'functools.partial' behavior.) + ''' + + __slots__ = ('_return_annotation', '_parameters') + + _parameter_cls = Parameter + _bound_arguments_cls = BoundArguments + + empty = _empty + + def __init__(self, parameters=None, return_annotation=_empty, + __validate_parameters__=True): + '''Constructs Signature from the given list of Parameter + objects and 'return_annotation'. All arguments are optional. + ''' + + if parameters is None: + params = OrderedDict() + else: + if __validate_parameters__: + params = OrderedDict() + top_kind = _POSITIONAL_ONLY + + for idx, param in enumerate(parameters): + kind = param.kind + if kind < top_kind: + msg = 'wrong parameter order: {0} before {1}' + msg = msg.format(top_kind, param.kind) + raise ValueError(msg) + else: + top_kind = kind + + name = param.name + if name is None: + name = str(idx) + param = param.replace(name=name) + + if name in params: + msg = 'duplicate parameter name: {0!r}'.format(name) + raise ValueError(msg) + params[name] = param + else: + params = OrderedDict(((param.name, param) + for param in parameters)) + + self._parameters = params + self._return_annotation = return_annotation + + @classmethod + def from_function(cls, func): + '''Constructs Signature for the given python function''' + + if not isinstance(func, types.FunctionType): + raise TypeError('{0!r} is not a Python function'.format(func)) + + Parameter = cls._parameter_cls + + # Parameter information. + func_code = func.__code__ + pos_count = func_code.co_argcount + arg_names = func_code.co_varnames + positional = tuple(arg_names[:pos_count]) + keyword_only_count = getattr(func_code, 'co_kwonlyargcount', 0) + keyword_only = arg_names[pos_count:(pos_count + keyword_only_count)] + annotations = getattr(func, '__annotations__', {}) + defaults = func.__defaults__ + kwdefaults = getattr(func, '__kwdefaults__', None) + + if defaults: + pos_default_count = len(defaults) + else: + pos_default_count = 0 + + parameters = [] + + # Non-keyword-only parameters w/o defaults. + non_default_count = pos_count - pos_default_count + for name in positional[:non_default_count]: + annotation = annotations.get(name, _empty) + parameters.append(Parameter(name, annotation=annotation, + kind=_POSITIONAL_OR_KEYWORD)) + + # ... w/ defaults. + for offset, name in enumerate(positional[non_default_count:]): + annotation = annotations.get(name, _empty) + parameters.append(Parameter(name, annotation=annotation, + kind=_POSITIONAL_OR_KEYWORD, + default=defaults[offset])) + + # *args + if func_code.co_flags & 0x04: + name = arg_names[pos_count + keyword_only_count] + annotation = annotations.get(name, _empty) + parameters.append(Parameter(name, annotation=annotation, + kind=_VAR_POSITIONAL)) + + # Keyword-only parameters. + for name in keyword_only: + default = _empty + if kwdefaults is not None: + default = kwdefaults.get(name, _empty) + + annotation = annotations.get(name, _empty) + parameters.append(Parameter(name, annotation=annotation, + kind=_KEYWORD_ONLY, + default=default)) + # **kwargs + if func_code.co_flags & 0x08: + index = pos_count + keyword_only_count + if func_code.co_flags & 0x04: + index += 1 + + name = arg_names[index] + annotation = annotations.get(name, _empty) + parameters.append(Parameter(name, annotation=annotation, + kind=_VAR_KEYWORD)) + + return cls(parameters, + return_annotation=annotations.get('return', _empty), + __validate_parameters__=False) + + @property + def parameters(self): + try: + return types.MappingProxyType(self._parameters) + except AttributeError: + return OrderedDict(self._parameters.items()) + + @property + def return_annotation(self): + return self._return_annotation + + def replace(self, parameters=_void, return_annotation=_void): + '''Creates a customized copy of the Signature. + Pass 'parameters' and/or 'return_annotation' arguments + to override them in the new copy. + ''' + + if parameters is _void: + parameters = self.parameters.values() + + if return_annotation is _void: + return_annotation = self._return_annotation + + return type(self)(parameters, + return_annotation=return_annotation) + + def __hash__(self): + msg = "unhashable type: '{0}'".format(self.__class__.__name__) + raise TypeError(msg) + + def __eq__(self, other): + if (not issubclass(type(other), Signature) or + self.return_annotation != other.return_annotation or + len(self.parameters) != len(other.parameters)): + return False + + other_positions = dict((param, idx) + for idx, param in enumerate(other.parameters.keys())) + + for idx, (param_name, param) in enumerate(self.parameters.items()): + if param.kind == _KEYWORD_ONLY: + try: + other_param = other.parameters[param_name] + except KeyError: + return False + else: + if param != other_param: + return False + else: + try: + other_idx = other_positions[param_name] + except KeyError: + return False + else: + if (idx != other_idx or + param != other.parameters[param_name]): + return False + + return True + + def __ne__(self, other): + return not self.__eq__(other) + + def _bind(self, args, kwargs, partial=False): + '''Private method. Don't use directly.''' + + arguments = OrderedDict() + + parameters = iter(self.parameters.values()) + parameters_ex = () + arg_vals = iter(args) + + if partial: + # Support for binding arguments to 'functools.partial' objects. + # See 'functools.partial' case in 'signature()' implementation + # for details. + for param_name, param in self.parameters.items(): + if (param._partial_kwarg and param_name not in kwargs): + # Simulating 'functools.partial' behavior + kwargs[param_name] = param.default + + while True: + # Let's iterate through the positional arguments and corresponding + # parameters + try: + arg_val = next(arg_vals) + except StopIteration: + # No more positional arguments + try: + param = next(parameters) + except StopIteration: + # No more parameters. That's it. Just need to check that + # we have no `kwargs` after this while loop + break + else: + if param.kind == _VAR_POSITIONAL: + # That's OK, just empty *args. Let's start parsing + # kwargs + break + elif param.name in kwargs: + if param.kind == _POSITIONAL_ONLY: + msg = '{arg!r} parameter is positional only, ' \ + 'but was passed as a keyword' + msg = msg.format(arg=param.name) + raise TypeError(msg) + parameters_ex = (param,) + break + elif (param.kind == _VAR_KEYWORD or + param.default is not _empty): + # That's fine too - we have a default value for this + # parameter. So, lets start parsing `kwargs`, starting + # with the current parameter + parameters_ex = (param,) + break + else: + if partial: + parameters_ex = (param,) + break + else: + msg = '{arg!r} parameter lacking default value' + msg = msg.format(arg=param.name) + raise TypeError(msg) + else: + # We have a positional argument to process + try: + param = next(parameters) + except StopIteration: + raise TypeError('too many positional arguments') + else: + if param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY): + # Looks like we have no parameter for this positional + # argument + raise TypeError('too many positional arguments') + + if param.kind == _VAR_POSITIONAL: + # We have an '*args'-like argument, let's fill it with + # all positional arguments we have left and move on to + # the next phase + values = [arg_val] + values.extend(arg_vals) + arguments[param.name] = tuple(values) + break + + if param.name in kwargs: + raise TypeError('multiple values for argument ' + '{arg!r}'.format(arg=param.name)) + + arguments[param.name] = arg_val + + # Now, we iterate through the remaining parameters to process + # keyword arguments + kwargs_param = None + for param in itertools.chain(parameters_ex, parameters): + if param.kind == _POSITIONAL_ONLY: + # This should never happen in case of a properly built + # Signature object (but let's have this check here + # to ensure correct behaviour just in case) + raise TypeError('{arg!r} parameter is positional only, ' + 'but was passed as a keyword'. \ + format(arg=param.name)) + + if param.kind == _VAR_KEYWORD: + # Memorize that we have a '**kwargs'-like parameter + kwargs_param = param + continue + + param_name = param.name + try: + arg_val = kwargs.pop(param_name) + except KeyError: + # We have no value for this parameter. It's fine though, + # if it has a default value, or it is an '*args'-like + # parameter, left alone by the processing of positional + # arguments. + if (not partial and param.kind != _VAR_POSITIONAL and + param.default is _empty): + raise TypeError('{arg!r} parameter lacking default value'. \ + format(arg=param_name)) + + else: + arguments[param_name] = arg_val + + if kwargs: + if kwargs_param is not None: + # Process our '**kwargs'-like parameter + arguments[kwargs_param.name] = kwargs + else: + raise TypeError('too many keyword arguments %r' % kwargs) + + return self._bound_arguments_cls(self, arguments) + + def bind(*args, **kwargs): + '''Get a BoundArguments object, that maps the passed `args` + and `kwargs` to the function's signature. Raises `TypeError` + if the passed arguments can not be bound. + ''' + return args[0]._bind(args[1:], kwargs) + + def bind_partial(self, *args, **kwargs): + '''Get a BoundArguments object, that partially maps the + passed `args` and `kwargs` to the function's signature. + Raises `TypeError` if the passed arguments can not be bound. + ''' + return self._bind(args, kwargs, partial=True) + + def __str__(self): + result = [] + render_kw_only_separator = True + for idx, param in enumerate(self.parameters.values()): + formatted = str(param) + + kind = param.kind + if kind == _VAR_POSITIONAL: + # OK, we have an '*args'-like parameter, so we won't need + # a '*' to separate keyword-only arguments + render_kw_only_separator = False + elif kind == _KEYWORD_ONLY and render_kw_only_separator: + # We have a keyword-only parameter to render and we haven't + # rendered an '*args'-like parameter before, so add a '*' + # separator to the parameters list ("foo(arg1, *, arg2)" case) + result.append('*') + # This condition should be only triggered once, so + # reset the flag + render_kw_only_separator = False + + result.append(formatted) + + rendered = '({0})'.format(', '.join(result)) + + if self.return_annotation is not _empty: + anno = formatannotation(self.return_annotation) + rendered += ' -> {0}'.format(anno) + + return rendered diff --git a/pipenv/vendor/funcsigs/version.py b/pipenv/vendor/funcsigs/version.py new file mode 100644 index 00000000..7863915f --- /dev/null +++ b/pipenv/vendor/funcsigs/version.py @@ -0,0 +1 @@ +__version__ = "1.0.2" diff --git a/pipenv/vendor/idna/LICENSE.md b/pipenv/vendor/idna/LICENSE.md new file mode 100644 index 00000000..b6f87326 --- /dev/null +++ b/pipenv/vendor/idna/LICENSE.md @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2013-2021, Kim Davies +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/pipenv/vendor/idna/LICENSE.rst b/pipenv/vendor/idna/LICENSE.rst deleted file mode 100644 index 3ee64fba..00000000 --- a/pipenv/vendor/idna/LICENSE.rst +++ /dev/null @@ -1,80 +0,0 @@ -License -------- - -Copyright (c) 2013-2018, Kim Davies. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -#. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -#. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided with - the distribution. - -#. Neither the name of the copyright holder nor the names of the - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -#. THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS "AS IS" AND ANY - EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE - USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH - DAMAGE. - -Portions of the codec implementation and unit tests are derived from the -Python standard library, which carries the `Python Software Foundation -License <https://docs.python.org/2/license.html>`_: - - Copyright (c) 2001-2014 Python Software Foundation; All Rights Reserved - -Portions of the unit tests are derived from the Unicode standard, which -is subject to the Unicode, Inc. License Agreement: - - Copyright (c) 1991-2014 Unicode, Inc. All rights reserved. - Distributed under the Terms of Use in - <http://www.unicode.org/copyright.html>. - - Permission is hereby granted, free of charge, to any person obtaining - a copy of the Unicode data files and any associated documentation - (the "Data Files") or Unicode software and any associated documentation - (the "Software") to deal in the Data Files or Software - without restriction, including without limitation the rights to use, - copy, modify, merge, publish, distribute, and/or sell copies of - the Data Files or Software, and to permit persons to whom the Data Files - or Software are furnished to do so, provided that - - (a) this copyright and permission notice appear with all copies - of the Data Files or Software, - - (b) this copyright and permission notice appear in associated - documentation, and - - (c) there is clear notice in each modified Data File or in the Software - as well as in the documentation associated with the Data File(s) or - Software that the data or software has been modified. - - THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF - ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE - WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT OF THIRD PARTY RIGHTS. - IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS - NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL - DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, - DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER - TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - PERFORMANCE OF THE DATA FILES OR SOFTWARE. - - Except as contained in this notice, the name of a copyright holder - shall not be used in advertising or otherwise to promote the sale, - use or other dealings in these Data Files or Software without prior - written authorization of the copyright holder. diff --git a/pipenv/vendor/idna/__init__.py b/pipenv/vendor/idna/__init__.py index 847bf935..a40eeafc 100644 --- a/pipenv/vendor/idna/__init__.py +++ b/pipenv/vendor/idna/__init__.py @@ -1,2 +1,44 @@ from .package_data import __version__ -from .core import * +from .core import ( + IDNABidiError, + IDNAError, + InvalidCodepoint, + InvalidCodepointContext, + alabel, + check_bidi, + check_hyphen_ok, + check_initial_combiner, + check_label, + check_nfc, + decode, + encode, + ulabel, + uts46_remap, + valid_contextj, + valid_contexto, + valid_label_length, + valid_string_length, +) +from .intranges import intranges_contain + +__all__ = [ + "IDNABidiError", + "IDNAError", + "InvalidCodepoint", + "InvalidCodepointContext", + "alabel", + "check_bidi", + "check_hyphen_ok", + "check_initial_combiner", + "check_label", + "check_nfc", + "decode", + "encode", + "intranges_contain", + "ulabel", + "uts46_remap", + "valid_contextj", + "valid_contexto", + "valid_label_length", + "valid_string_length", +] diff --git a/pipenv/vendor/idna/codec.py b/pipenv/vendor/idna/codec.py index 98c65ead..080f22a3 100644 --- a/pipenv/vendor/idna/codec.py +++ b/pipenv/vendor/idna/codec.py @@ -1,41 +1,43 @@ from .core import encode, decode, alabel, ulabel, IDNAError import codecs import re +from typing import Tuple, Optional -_unicode_dots_re = re.compile(u'[\u002e\u3002\uff0e\uff61]') +_unicode_dots_re = re.compile('[\u002e\u3002\uff0e\uff61]') class Codec(codecs.Codec): def encode(self, data, errors='strict'): - + # type: (str, str) -> Tuple[bytes, int] if errors != 'strict': - raise IDNAError("Unsupported error handling \"{0}\"".format(errors)) + raise IDNAError('Unsupported error handling \"{}\"'.format(errors)) if not data: - return "", 0 + return b"", 0 return encode(data), len(data) def decode(self, data, errors='strict'): - + # type: (bytes, str) -> Tuple[str, int] if errors != 'strict': - raise IDNAError("Unsupported error handling \"{0}\"".format(errors)) + raise IDNAError('Unsupported error handling \"{}\"'.format(errors)) if not data: - return u"", 0 + return '', 0 return decode(data), len(data) class IncrementalEncoder(codecs.BufferedIncrementalEncoder): - def _buffer_encode(self, data, errors, final): + def _buffer_encode(self, data, errors, final): # type: ignore + # type: (str, str, bool) -> Tuple[str, int] if errors != 'strict': - raise IDNAError("Unsupported error handling \"{0}\"".format(errors)) + raise IDNAError('Unsupported error handling \"{}\"'.format(errors)) if not data: - return ("", 0) + return "", 0 labels = _unicode_dots_re.split(data) - trailing_dot = u'' + trailing_dot = '' if labels: if not labels[-1]: trailing_dot = '.' @@ -55,37 +57,30 @@ class IncrementalEncoder(codecs.BufferedIncrementalEncoder): size += len(label) # Join with U+002E - result = ".".join(result) + trailing_dot + result_str = '.'.join(result) + trailing_dot # type: ignore size += len(trailing_dot) - return (result, size) + return result_str, size class IncrementalDecoder(codecs.BufferedIncrementalDecoder): - def _buffer_decode(self, data, errors, final): + def _buffer_decode(self, data, errors, final): # type: ignore + # type: (str, str, bool) -> Tuple[str, int] if errors != 'strict': - raise IDNAError("Unsupported error handling \"{0}\"".format(errors)) + raise IDNAError('Unsupported error handling \"{}\"'.format(errors)) if not data: - return (u"", 0) + return ('', 0) - # IDNA allows decoding to operate on Unicode strings, too. - if isinstance(data, unicode): - labels = _unicode_dots_re.split(data) - else: - # Must be ASCII string - data = str(data) - unicode(data, "ascii") - labels = data.split(".") - - trailing_dot = u'' + labels = _unicode_dots_re.split(data) + trailing_dot = '' if labels: if not labels[-1]: - trailing_dot = u'.' + trailing_dot = '.' del labels[-1] elif not final: # Keep potentially unfinished label until the next call del labels[-1] if labels: - trailing_dot = u'.' + trailing_dot = '.' result = [] size = 0 @@ -95,22 +90,26 @@ class IncrementalDecoder(codecs.BufferedIncrementalDecoder): size += 1 size += len(label) - result = u".".join(result) + trailing_dot + result_str = '.'.join(result) + trailing_dot size += len(trailing_dot) - return (result, size) + return (result_str, size) class StreamWriter(Codec, codecs.StreamWriter): pass + class StreamReader(Codec, codecs.StreamReader): pass + def getregentry(): + # type: () -> codecs.CodecInfo + # Compatibility as a search_function for codecs.register() return codecs.CodecInfo( name='idna', - encode=Codec().encode, - decode=Codec().decode, + encode=Codec().encode, # type: ignore + decode=Codec().decode, # type: ignore incrementalencoder=IncrementalEncoder, incrementaldecoder=IncrementalDecoder, streamwriter=StreamWriter, diff --git a/pipenv/vendor/idna/compat.py b/pipenv/vendor/idna/compat.py index 4d47f336..dc896c76 100644 --- a/pipenv/vendor/idna/compat.py +++ b/pipenv/vendor/idna/compat.py @@ -1,12 +1,16 @@ from .core import * from .codec import * +from typing import Any, Union def ToASCII(label): + # type: (str) -> bytes return encode(label) def ToUnicode(label): + # type: (Union[bytes, bytearray]) -> str return decode(label) def nameprep(s): - raise NotImplementedError("IDNA 2008 does not utilise nameprep protocol") + # type: (Any) -> None + raise NotImplementedError('IDNA 2008 does not utilise nameprep protocol') diff --git a/pipenv/vendor/idna/core.py b/pipenv/vendor/idna/core.py index 104624ad..d6051297 100644 --- a/pipenv/vendor/idna/core.py +++ b/pipenv/vendor/idna/core.py @@ -2,16 +2,12 @@ from . import idnadata import bisect import unicodedata import re -import sys +from typing import Union, Optional from .intranges import intranges_contain _virama_combining_class = 9 _alabel_prefix = b'xn--' -_unicode_dots_re = re.compile(u'[\u002e\u3002\uff0e\uff61]') - -if sys.version_info[0] == 3: - unicode = str - unichr = chr +_unicode_dots_re = re.compile('[\u002e\u3002\uff0e\uff61]') class IDNAError(UnicodeError): """ Base exception for all IDNA-encoding related problems """ @@ -34,45 +30,49 @@ class InvalidCodepointContext(IDNAError): def _combining_class(cp): - v = unicodedata.combining(unichr(cp)) + # type: (int) -> int + v = unicodedata.combining(chr(cp)) if v == 0: - if not unicodedata.name(unichr(cp)): - raise ValueError("Unknown character in unicodedata") + if not unicodedata.name(chr(cp)): + raise ValueError('Unknown character in unicodedata') return v def _is_script(cp, script): + # type: (str, str) -> bool return intranges_contain(ord(cp), idnadata.scripts[script]) def _punycode(s): + # type: (str) -> bytes return s.encode('punycode') def _unot(s): - return 'U+{0:04X}'.format(s) + # type: (int) -> str + return 'U+{:04X}'.format(s) def valid_label_length(label): - + # type: (Union[bytes, str]) -> bool if len(label) > 63: return False return True def valid_string_length(label, trailing_dot): - + # type: (Union[bytes, str], bool) -> bool if len(label) > (254 if trailing_dot else 253): return False return True def check_bidi(label, check_ltr=False): - + # type: (str, bool) -> bool # Bidi rules should only be applied if string contains RTL characters bidi_label = False for (idx, cp) in enumerate(label, 1): direction = unicodedata.bidirectional(cp) if direction == '': # String likely comes from a newer version of Unicode - raise IDNABidiError('Unknown directionality in label {0} at position {1}'.format(repr(label), idx)) + raise IDNABidiError('Unknown directionality in label {} at position {}'.format(repr(label), idx)) if direction in ['R', 'AL', 'AN']: bidi_label = True if not bidi_label and not check_ltr: @@ -85,17 +85,17 @@ def check_bidi(label, check_ltr=False): elif direction == 'L': rtl = False else: - raise IDNABidiError('First codepoint in label {0} must be directionality L, R or AL'.format(repr(label))) + raise IDNABidiError('First codepoint in label {} must be directionality L, R or AL'.format(repr(label))) valid_ending = False - number_type = False + number_type = None # type: Optional[str] for (idx, cp) in enumerate(label, 1): direction = unicodedata.bidirectional(cp) if rtl: # Bidi rule 2 if not direction in ['R', 'AL', 'AN', 'EN', 'ES', 'CS', 'ET', 'ON', 'BN', 'NSM']: - raise IDNABidiError('Invalid direction for codepoint at position {0} in a right-to-left label'.format(idx)) + raise IDNABidiError('Invalid direction for codepoint at position {} in a right-to-left label'.format(idx)) # Bidi rule 3 if direction in ['R', 'AL', 'EN', 'AN']: valid_ending = True @@ -111,7 +111,7 @@ def check_bidi(label, check_ltr=False): else: # Bidi rule 5 if not direction in ['L', 'EN', 'ES', 'CS', 'ET', 'ON', 'BN', 'NSM']: - raise IDNABidiError('Invalid direction for codepoint at position {0} in a left-to-right label'.format(idx)) + raise IDNABidiError('Invalid direction for codepoint at position {} in a left-to-right label'.format(idx)) # Bidi rule 6 if direction in ['L', 'EN']: valid_ending = True @@ -125,14 +125,14 @@ def check_bidi(label, check_ltr=False): def check_initial_combiner(label): - + # type: (str) -> bool if unicodedata.category(label[0])[0] == 'M': raise IDNAError('Label begins with an illegal combining character') return True def check_hyphen_ok(label): - + # type: (str) -> bool if label[2:4] == '--': raise IDNAError('Label has disallowed hyphens in 3rd and 4th position') if label[0] == '-' or label[-1] == '-': @@ -141,13 +141,13 @@ def check_hyphen_ok(label): def check_nfc(label): - + # type: (str) -> None if unicodedata.normalize('NFC', label) != label: raise IDNAError('Label must be in Normalization Form C') def valid_contextj(label, pos): - + # type: (str, int) -> bool cp_value = ord(label[pos]) if cp_value == 0x200c: @@ -191,7 +191,7 @@ def valid_contextj(label, pos): def valid_contexto(label, pos, exception=False): - + # type: (str, int, bool) -> bool cp_value = ord(label[pos]) if cp_value == 0x00b7: @@ -212,7 +212,7 @@ def valid_contexto(label, pos, exception=False): elif cp_value == 0x30fb: for cp in label: - if cp == u'\u30fb': + if cp == '\u30fb': continue if _is_script(cp, 'Hiragana') or _is_script(cp, 'Katakana') or _is_script(cp, 'Han'): return True @@ -230,9 +230,11 @@ def valid_contexto(label, pos, exception=False): return False return True + return False + def check_label(label): - + # type: (Union[str, bytes, bytearray]) -> None if isinstance(label, (bytes, bytearray)): label = label.decode('utf-8') if len(label) == 0: @@ -249,98 +251,109 @@ def check_label(label): elif intranges_contain(cp_value, idnadata.codepoint_classes['CONTEXTJ']): try: if not valid_contextj(label, pos): - raise InvalidCodepointContext('Joiner {0} not allowed at position {1} in {2}'.format( + raise InvalidCodepointContext('Joiner {} not allowed at position {} in {}'.format( _unot(cp_value), pos+1, repr(label))) except ValueError: - raise IDNAError('Unknown codepoint adjacent to joiner {0} at position {1} in {2}'.format( + raise IDNAError('Unknown codepoint adjacent to joiner {} at position {} in {}'.format( _unot(cp_value), pos+1, repr(label))) elif intranges_contain(cp_value, idnadata.codepoint_classes['CONTEXTO']): if not valid_contexto(label, pos): - raise InvalidCodepointContext('Codepoint {0} not allowed at position {1} in {2}'.format(_unot(cp_value), pos+1, repr(label))) + raise InvalidCodepointContext('Codepoint {} not allowed at position {} in {}'.format(_unot(cp_value), pos+1, repr(label))) else: - raise InvalidCodepoint('Codepoint {0} at position {1} of {2} not allowed'.format(_unot(cp_value), pos+1, repr(label))) + raise InvalidCodepoint('Codepoint {} at position {} of {} not allowed'.format(_unot(cp_value), pos+1, repr(label))) check_bidi(label) def alabel(label): - + # type: (str) -> bytes try: - label = label.encode('ascii') - ulabel(label) - if not valid_label_length(label): + label_bytes = label.encode('ascii') + ulabel(label_bytes) + if not valid_label_length(label_bytes): raise IDNAError('Label too long') - return label + return label_bytes except UnicodeEncodeError: pass if not label: raise IDNAError('No Input') - label = unicode(label) + label = str(label) check_label(label) - label = _punycode(label) - label = _alabel_prefix + label + label_bytes = _punycode(label) + label_bytes = _alabel_prefix + label_bytes - if not valid_label_length(label): + if not valid_label_length(label_bytes): raise IDNAError('Label too long') - return label + return label_bytes def ulabel(label): - + # type: (Union[str, bytes, bytearray]) -> str if not isinstance(label, (bytes, bytearray)): try: - label = label.encode('ascii') + label_bytes = label.encode('ascii') except UnicodeEncodeError: check_label(label) return label - - label = label.lower() - if label.startswith(_alabel_prefix): - label = label[len(_alabel_prefix):] else: - check_label(label) - return label.decode('ascii') + label_bytes = label - label = label.decode('punycode') + label_bytes = label_bytes.lower() + if label_bytes.startswith(_alabel_prefix): + label_bytes = label_bytes[len(_alabel_prefix):] + if not label_bytes: + raise IDNAError('Malformed A-label, no Punycode eligible content found') + if label_bytes.decode('ascii')[-1] == '-': + raise IDNAError('A-label must not end with a hyphen') + else: + check_label(label_bytes) + return label_bytes.decode('ascii') + + label = label_bytes.decode('punycode') check_label(label) return label def uts46_remap(domain, std3_rules=True, transitional=False): + # type: (str, bool, bool) -> str """Re-map the characters in the string according to UTS46 processing.""" from .uts46data import uts46data - output = u"" - try: - for pos, char in enumerate(domain): - code_point = ord(char) + output = '' + + for pos, char in enumerate(domain): + code_point = ord(char) + try: uts46row = uts46data[code_point if code_point < 256 else - bisect.bisect_left(uts46data, (code_point, "Z")) - 1] + bisect.bisect_left(uts46data, (code_point, 'Z')) - 1] status = uts46row[1] - replacement = uts46row[2] if len(uts46row) == 3 else None - if (status == "V" or - (status == "D" and not transitional) or - (status == "3" and not std3_rules and replacement is None)): + replacement = None # type: Optional[str] + if len(uts46row) == 3: + replacement = uts46row[2] # type: ignore + if (status == 'V' or + (status == 'D' and not transitional) or + (status == '3' and not std3_rules and replacement is None)): output += char - elif replacement is not None and (status == "M" or - (status == "3" and not std3_rules) or - (status == "D" and transitional)): + elif replacement is not None and (status == 'M' or + (status == '3' and not std3_rules) or + (status == 'D' and transitional)): output += replacement - elif status != "I": + elif status != 'I': raise IndexError() - return unicodedata.normalize("NFC", output) - except IndexError: - raise InvalidCodepoint( - "Codepoint {0} not allowed at position {1} in {2}".format( - _unot(code_point), pos + 1, repr(domain))) + except IndexError: + raise InvalidCodepoint( + 'Codepoint {} not allowed at position {} in {}'.format( + _unot(code_point), pos + 1, repr(domain))) + + return unicodedata.normalize('NFC', output) def encode(s, strict=False, uts46=False, std3_rules=False, transitional=False): - + # type: (Union[str, bytes, bytearray], bool, bool, bool, bool) -> bytes if isinstance(s, (bytes, bytearray)): - s = s.decode("ascii") + s = s.decode('ascii') if uts46: s = uts46_remap(s, std3_rules, transitional) trailing_dot = False @@ -369,9 +382,9 @@ def encode(s, strict=False, uts46=False, std3_rules=False, transitional=False): def decode(s, strict=False, uts46=False, std3_rules=False): - + # type: (Union[str, bytes, bytearray], bool, bool, bool) -> str if isinstance(s, (bytes, bytearray)): - s = s.decode("ascii") + s = s.decode('ascii') if uts46: s = uts46_remap(s, std3_rules, False) trailing_dot = False @@ -379,7 +392,7 @@ def decode(s, strict=False, uts46=False, std3_rules=False): if not strict: labels = _unicode_dots_re.split(s) else: - labels = s.split(u'.') + labels = s.split('.') if not labels or labels == ['']: raise IDNAError('Empty domain') if not labels[-1]: @@ -392,5 +405,5 @@ def decode(s, strict=False, uts46=False, std3_rules=False): else: raise IDNAError('Empty label') if trailing_dot: - result.append(u'') - return u'.'.join(result) + result.append('') + return '.'.join(result) diff --git a/pipenv/vendor/idna/idnadata.py b/pipenv/vendor/idna/idnadata.py index a80c959d..b86a3e06 100644 --- a/pipenv/vendor/idna/idnadata.py +++ b/pipenv/vendor/idna/idnadata.py @@ -1,6 +1,6 @@ # This file is automatically generated by tools/idna-data -__version__ = "11.0.0" +__version__ = '13.0.0' scripts = { 'Greek': ( 0x37000000374, @@ -48,16 +48,18 @@ scripts = { 0x300700003008, 0x30210000302a, 0x30380000303c, - 0x340000004db6, - 0x4e0000009ff0, + 0x340000004dc0, + 0x4e0000009ffd, 0xf9000000fa6e, 0xfa700000fada, - 0x200000002a6d7, + 0x16ff000016ff2, + 0x200000002a6de, 0x2a7000002b735, 0x2b7400002b81e, 0x2b8200002cea2, 0x2ceb00002ebe1, 0x2f8000002fa1e, + 0x300000003134b, ), 'Hebrew': ( 0x591000005c8, @@ -74,6 +76,7 @@ scripts = { 0x304100003097, 0x309d000030a0, 0x1b0010001b11f, + 0x1b1500001b153, 0x1f2000001f201, ), 'Katakana': ( @@ -85,6 +88,7 @@ scripts = { 0xff660000ff70, 0xff710000ff9e, 0x1b0000001b001, + 0x1b1640001b168, ), } joining_types = { @@ -387,9 +391,9 @@ joining_types = { 0x853: 68, 0x854: 82, 0x855: 68, - 0x856: 85, - 0x857: 85, - 0x858: 85, + 0x856: 82, + 0x857: 82, + 0x858: 82, 0x860: 68, 0x861: 85, 0x862: 68, @@ -430,6 +434,16 @@ joining_types = { 0x8bb: 68, 0x8bc: 68, 0x8bd: 68, + 0x8be: 68, + 0x8bf: 68, + 0x8c0: 68, + 0x8c1: 68, + 0x8c2: 68, + 0x8c3: 68, + 0x8c4: 68, + 0x8c5: 68, + 0x8c6: 68, + 0x8c7: 68, 0x8e2: 85, 0x1806: 85, 0x1807: 68, @@ -754,6 +768,34 @@ joining_types = { 0x10f52: 68, 0x10f53: 68, 0x10f54: 82, + 0x10fb0: 68, + 0x10fb1: 85, + 0x10fb2: 68, + 0x10fb3: 68, + 0x10fb4: 82, + 0x10fb5: 82, + 0x10fb6: 82, + 0x10fb7: 85, + 0x10fb8: 68, + 0x10fb9: 82, + 0x10fba: 82, + 0x10fbb: 68, + 0x10fbc: 68, + 0x10fbd: 82, + 0x10fbe: 68, + 0x10fbf: 68, + 0x10fc0: 85, + 0x10fc1: 68, + 0x10fc2: 82, + 0x10fc3: 82, + 0x10fc4: 68, + 0x10fc5: 85, + 0x10fc6: 85, + 0x10fc7: 85, + 0x10fc8: 85, + 0x10fc9: 82, + 0x10fca: 68, + 0x10fcb: 76, 0x110bd: 85, 0x110cd: 85, 0x1e900: 68, @@ -824,6 +866,7 @@ joining_types = { 0x1e941: 68, 0x1e942: 68, 0x1e943: 68, + 0x1e94b: 84, } codepoint_classes = { 'PVALID': ( @@ -1126,7 +1169,7 @@ codepoint_classes = { 0x8400000085c, 0x8600000086b, 0x8a0000008b5, - 0x8b6000008be, + 0x8b6000008c8, 0x8d3000008e2, 0x8e300000958, 0x96000000964, @@ -1185,7 +1228,7 @@ codepoint_classes = { 0xb3c00000b45, 0xb4700000b49, 0xb4b00000b4e, - 0xb5600000b58, + 0xb5500000b58, 0xb5f00000b64, 0xb6600000b70, 0xb7100000b72, @@ -1230,8 +1273,7 @@ codepoint_classes = { 0xce000000ce4, 0xce600000cf0, 0xcf100000cf3, - 0xd0000000d04, - 0xd0500000d0d, + 0xd0000000d0d, 0xd0e00000d11, 0xd1200000d45, 0xd4600000d49, @@ -1240,7 +1282,7 @@ codepoint_classes = { 0xd5f00000d64, 0xd6600000d70, 0xd7a00000d80, - 0xd8200000d84, + 0xd8100000d84, 0xd8500000d97, 0xd9a00000db2, 0xdb300000dbc, @@ -1258,18 +1300,11 @@ codepoint_classes = { 0xe5000000e5a, 0xe8100000e83, 0xe8400000e85, - 0xe8700000e89, - 0xe8a00000e8b, - 0xe8d00000e8e, - 0xe9400000e98, - 0xe9900000ea0, - 0xea100000ea4, + 0xe8600000e8b, + 0xe8c00000ea4, 0xea500000ea6, - 0xea700000ea8, - 0xeaa00000eac, - 0xead00000eb3, - 0xeb400000eba, - 0xebb00000ebe, + 0xea700000eb3, + 0xeb400000ebe, 0xec000000ec5, 0xec600000ec7, 0xec800000ece, @@ -1362,6 +1397,7 @@ codepoint_classes = { 0x1a9000001a9a, 0x1aa700001aa8, 0x1ab000001abe, + 0x1abf00001ac1, 0x1b0000001b4c, 0x1b5000001b5a, 0x1b6b00001b74, @@ -1370,7 +1406,7 @@ codepoint_classes = { 0x1c4000001c4a, 0x1c4d00001c7e, 0x1cd000001cd3, - 0x1cd400001cfa, + 0x1cd400001cfb, 0x1d0000001d2c, 0x1d2f00001d30, 0x1d3b00001d3c, @@ -1613,10 +1649,10 @@ codepoint_classes = { 0x30a1000030fb, 0x30fc000030ff, 0x310500003130, - 0x31a0000031bb, + 0x31a0000031c0, 0x31f000003200, - 0x340000004db6, - 0x4e0000009ff0, + 0x340000004dc0, + 0x4e0000009ffd, 0xa0000000a48d, 0xa4d00000a4fe, 0xa5000000a60d, @@ -1727,8 +1763,15 @@ codepoint_classes = { 0xa7b50000a7b6, 0xa7b70000a7b8, 0xa7b90000a7ba, - 0xa7f70000a7f8, + 0xa7bb0000a7bc, + 0xa7bd0000a7be, + 0xa7bf0000a7c0, + 0xa7c30000a7c4, + 0xa7c80000a7c9, + 0xa7ca0000a7cb, + 0xa7f60000a7f8, 0xa7fa0000a828, + 0xa82c0000a82d, 0xa8400000a874, 0xa8800000a8c6, 0xa8d00000a8da, @@ -1753,7 +1796,7 @@ codepoint_classes = { 0xab200000ab27, 0xab280000ab2f, 0xab300000ab5b, - 0xab600000ab66, + 0xab600000ab6a, 0xabc00000abeb, 0xabec0000abee, 0xabf00000abfa, @@ -1827,9 +1870,14 @@ codepoint_classes = { 0x10cc000010cf3, 0x10d0000010d28, 0x10d3000010d3a, + 0x10e8000010eaa, + 0x10eab00010ead, + 0x10eb000010eb2, 0x10f0000010f1d, 0x10f2700010f28, 0x10f3000010f51, + 0x10fb000010fc5, + 0x10fe000010ff7, 0x1100000011047, 0x1106600011070, 0x1107f000110bb, @@ -1837,12 +1885,12 @@ codepoint_classes = { 0x110f0000110fa, 0x1110000011135, 0x1113600011140, - 0x1114400011147, + 0x1114400011148, 0x1115000011174, 0x1117600011177, 0x11180000111c5, 0x111c9000111cd, - 0x111d0000111db, + 0x111ce000111db, 0x111dc000111dd, 0x1120000011212, 0x1121300011238, @@ -1871,7 +1919,7 @@ codepoint_classes = { 0x1137000011375, 0x114000001144b, 0x114500001145a, - 0x1145e0001145f, + 0x1145e00011462, 0x11480000114c6, 0x114c7000114c8, 0x114d0000114da, @@ -1881,18 +1929,28 @@ codepoint_classes = { 0x1160000011641, 0x1164400011645, 0x116500001165a, - 0x11680000116b8, + 0x11680000116b9, 0x116c0000116ca, 0x117000001171b, 0x1171d0001172c, 0x117300001173a, 0x118000001183b, 0x118c0000118ea, - 0x118ff00011900, + 0x118ff00011907, + 0x119090001190a, + 0x1190c00011914, + 0x1191500011917, + 0x1191800011936, + 0x1193700011939, + 0x1193b00011944, + 0x119500001195a, + 0x119a0000119a8, + 0x119aa000119d8, + 0x119da000119e2, + 0x119e3000119e5, 0x11a0000011a3f, 0x11a4700011a48, - 0x11a5000011a84, - 0x11a8600011a9a, + 0x11a5000011a9a, 0x11a9d00011a9e, 0x11ac000011af9, 0x11c0000011c09, @@ -1916,6 +1974,7 @@ codepoint_classes = { 0x11d9300011d99, 0x11da000011daa, 0x11ee000011ef7, + 0x11fb000011fb1, 0x120000001239a, 0x1248000012544, 0x130000001342f, @@ -1931,13 +1990,18 @@ codepoint_classes = { 0x16b6300016b78, 0x16b7d00016b90, 0x16e6000016e80, - 0x16f0000016f45, - 0x16f5000016f7f, + 0x16f0000016f4b, + 0x16f4f00016f88, 0x16f8f00016fa0, 0x16fe000016fe2, - 0x17000000187f2, - 0x1880000018af3, + 0x16fe300016fe5, + 0x16ff000016ff2, + 0x17000000187f8, + 0x1880000018cd6, + 0x18d0000018d09, 0x1b0000001b11f, + 0x1b1500001b153, + 0x1b1640001b168, 0x1b1700001b2fc, 0x1bc000001bc6b, 0x1bc700001bc7d, @@ -1955,15 +2019,22 @@ codepoint_classes = { 0x1e01b0001e022, 0x1e0230001e025, 0x1e0260001e02b, + 0x1e1000001e12d, + 0x1e1300001e13e, + 0x1e1400001e14a, + 0x1e14e0001e14f, + 0x1e2c00001e2fa, 0x1e8000001e8c5, 0x1e8d00001e8d7, - 0x1e9220001e94b, + 0x1e9220001e94c, 0x1e9500001e95a, - 0x200000002a6d7, + 0x1fbf00001fbfa, + 0x200000002a6de, 0x2a7000002b735, 0x2b7400002b81e, 0x2b8200002cea2, 0x2ceb00002ebe1, + 0x300000003134b, ), 'CONTEXTJ': ( 0x200c0000200e, diff --git a/pipenv/vendor/idna/intranges.py b/pipenv/vendor/idna/intranges.py index fa8a7356..ee364a90 100644 --- a/pipenv/vendor/idna/intranges.py +++ b/pipenv/vendor/idna/intranges.py @@ -6,8 +6,10 @@ in the original list?" in time O(log(# runs)). """ import bisect +from typing import List, Tuple def intranges_from_list(list_): + # type: (List[int]) -> Tuple[int, ...] """Represent a list of integers as a sequence of ranges: ((start_0, end_0), (start_1, end_1), ...), such that the original integers are exactly those x such that start_i <= x < end_i for some i. @@ -29,13 +31,16 @@ def intranges_from_list(list_): return tuple(ranges) def _encode_range(start, end): + # type: (int, int) -> int return (start << 32) | end def _decode_range(r): + # type: (int) -> Tuple[int, int] return (r >> 32), (r & ((1 << 32) - 1)) def intranges_contain(int_, ranges): + # type: (int, Tuple[int, ...]) -> bool """Determine if `int_` falls into one of the ranges in `ranges`.""" tuple_ = _encode_range(int_, 0) pos = bisect.bisect_left(ranges, tuple_) diff --git a/pipenv/vendor/idna/package_data.py b/pipenv/vendor/idna/package_data.py index 257e8989..e096d1d5 100644 --- a/pipenv/vendor/idna/package_data.py +++ b/pipenv/vendor/idna/package_data.py @@ -1,2 +1,2 @@ -__version__ = '2.8' +__version__ = '3.2' diff --git a/pipenv/vendor/idna/py.typed b/pipenv/vendor/idna/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/pipenv/vendor/idna/uts46data.py b/pipenv/vendor/idna/uts46data.py index a68ed4c0..f382ce38 100644 --- a/pipenv/vendor/idna/uts46data.py +++ b/pipenv/vendor/idna/uts46data.py @@ -1,11 +1,13 @@ # This file is automatically generated by tools/idna-data -# vim: set fileencoding=utf-8 : + +from typing import List, Tuple, Union """IDNA Mapping Table from UTS46.""" -__version__ = "11.0.0" +__version__ = '13.0.0' def _seg_0(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ (0x0, '3'), (0x1, '3'), @@ -72,32 +74,32 @@ def _seg_0(): (0x3E, '3'), (0x3F, '3'), (0x40, '3'), - (0x41, 'M', u'a'), - (0x42, 'M', u'b'), - (0x43, 'M', u'c'), - (0x44, 'M', u'd'), - (0x45, 'M', u'e'), - (0x46, 'M', u'f'), - (0x47, 'M', u'g'), - (0x48, 'M', u'h'), - (0x49, 'M', u'i'), - (0x4A, 'M', u'j'), - (0x4B, 'M', u'k'), - (0x4C, 'M', u'l'), - (0x4D, 'M', u'm'), - (0x4E, 'M', u'n'), - (0x4F, 'M', u'o'), - (0x50, 'M', u'p'), - (0x51, 'M', u'q'), - (0x52, 'M', u'r'), - (0x53, 'M', u's'), - (0x54, 'M', u't'), - (0x55, 'M', u'u'), - (0x56, 'M', u'v'), - (0x57, 'M', u'w'), - (0x58, 'M', u'x'), - (0x59, 'M', u'y'), - (0x5A, 'M', u'z'), + (0x41, 'M', 'a'), + (0x42, 'M', 'b'), + (0x43, 'M', 'c'), + (0x44, 'M', 'd'), + (0x45, 'M', 'e'), + (0x46, 'M', 'f'), + (0x47, 'M', 'g'), + (0x48, 'M', 'h'), + (0x49, 'M', 'i'), + (0x4A, 'M', 'j'), + (0x4B, 'M', 'k'), + (0x4C, 'M', 'l'), + (0x4D, 'M', 'm'), + (0x4E, 'M', 'n'), + (0x4F, 'M', 'o'), + (0x50, 'M', 'p'), + (0x51, 'M', 'q'), + (0x52, 'M', 'r'), + (0x53, 'M', 's'), + (0x54, 'M', 't'), + (0x55, 'M', 'u'), + (0x56, 'M', 'v'), + (0x57, 'M', 'w'), + (0x58, 'M', 'x'), + (0x59, 'M', 'y'), + (0x5A, 'M', 'z'), (0x5B, '3'), (0x5C, '3'), (0x5D, '3'), @@ -110,6 +112,7 @@ def _seg_0(): ] def _seg_1(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ (0x64, 'V'), (0x65, 'V'), @@ -171,7 +174,7 @@ def _seg_1(): (0x9D, 'X'), (0x9E, 'X'), (0x9F, 'X'), - (0xA0, '3', u' '), + (0xA0, '3', ' '), (0xA1, 'V'), (0xA2, 'V'), (0xA3, 'V'), @@ -179,66 +182,67 @@ def _seg_1(): (0xA5, 'V'), (0xA6, 'V'), (0xA7, 'V'), - (0xA8, '3', u' ̈'), + (0xA8, '3', ' ̈'), (0xA9, 'V'), - (0xAA, 'M', u'a'), + (0xAA, 'M', 'a'), (0xAB, 'V'), (0xAC, 'V'), (0xAD, 'I'), (0xAE, 'V'), - (0xAF, '3', u' ̄'), + (0xAF, '3', ' ̄'), (0xB0, 'V'), (0xB1, 'V'), - (0xB2, 'M', u'2'), - (0xB3, 'M', u'3'), - (0xB4, '3', u' ́'), - (0xB5, 'M', u'μ'), + (0xB2, 'M', '2'), + (0xB3, 'M', '3'), + (0xB4, '3', ' ́'), + (0xB5, 'M', 'μ'), (0xB6, 'V'), (0xB7, 'V'), - (0xB8, '3', u' ̧'), - (0xB9, 'M', u'1'), - (0xBA, 'M', u'o'), + (0xB8, '3', ' ̧'), + (0xB9, 'M', '1'), + (0xBA, 'M', 'o'), (0xBB, 'V'), - (0xBC, 'M', u'1⁄4'), - (0xBD, 'M', u'1⁄2'), - (0xBE, 'M', u'3⁄4'), + (0xBC, 'M', '1⁄4'), + (0xBD, 'M', '1⁄2'), + (0xBE, 'M', '3⁄4'), (0xBF, 'V'), - (0xC0, 'M', u'à'), - (0xC1, 'M', u'á'), - (0xC2, 'M', u'â'), - (0xC3, 'M', u'ã'), - (0xC4, 'M', u'ä'), - (0xC5, 'M', u'å'), - (0xC6, 'M', u'æ'), - (0xC7, 'M', u'ç'), + (0xC0, 'M', 'à'), + (0xC1, 'M', 'á'), + (0xC2, 'M', 'â'), + (0xC3, 'M', 'ã'), + (0xC4, 'M', 'ä'), + (0xC5, 'M', 'å'), + (0xC6, 'M', 'æ'), + (0xC7, 'M', 'ç'), ] def _seg_2(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0xC8, 'M', u'è'), - (0xC9, 'M', u'é'), - (0xCA, 'M', u'ê'), - (0xCB, 'M', u'ë'), - (0xCC, 'M', u'ì'), - (0xCD, 'M', u'í'), - (0xCE, 'M', u'î'), - (0xCF, 'M', u'ï'), - (0xD0, 'M', u'ð'), - (0xD1, 'M', u'ñ'), - (0xD2, 'M', u'ò'), - (0xD3, 'M', u'ó'), - (0xD4, 'M', u'ô'), - (0xD5, 'M', u'õ'), - (0xD6, 'M', u'ö'), + (0xC8, 'M', 'è'), + (0xC9, 'M', 'é'), + (0xCA, 'M', 'ê'), + (0xCB, 'M', 'ë'), + (0xCC, 'M', 'ì'), + (0xCD, 'M', 'í'), + (0xCE, 'M', 'î'), + (0xCF, 'M', 'ï'), + (0xD0, 'M', 'ð'), + (0xD1, 'M', 'ñ'), + (0xD2, 'M', 'ò'), + (0xD3, 'M', 'ó'), + (0xD4, 'M', 'ô'), + (0xD5, 'M', 'õ'), + (0xD6, 'M', 'ö'), (0xD7, 'V'), - (0xD8, 'M', u'ø'), - (0xD9, 'M', u'ù'), - (0xDA, 'M', u'ú'), - (0xDB, 'M', u'û'), - (0xDC, 'M', u'ü'), - (0xDD, 'M', u'ý'), - (0xDE, 'M', u'þ'), - (0xDF, 'D', u'ss'), + (0xD8, 'M', 'ø'), + (0xD9, 'M', 'ù'), + (0xDA, 'M', 'ú'), + (0xDB, 'M', 'û'), + (0xDC, 'M', 'ü'), + (0xDD, 'M', 'ý'), + (0xDE, 'M', 'þ'), + (0xDF, 'D', 'ss'), (0xE0, 'V'), (0xE1, 'V'), (0xE2, 'V'), @@ -271,765 +275,772 @@ def _seg_2(): (0xFD, 'V'), (0xFE, 'V'), (0xFF, 'V'), - (0x100, 'M', u'ā'), + (0x100, 'M', 'ā'), (0x101, 'V'), - (0x102, 'M', u'ă'), + (0x102, 'M', 'ă'), (0x103, 'V'), - (0x104, 'M', u'ą'), + (0x104, 'M', 'ą'), (0x105, 'V'), - (0x106, 'M', u'ć'), + (0x106, 'M', 'ć'), (0x107, 'V'), - (0x108, 'M', u'ĉ'), + (0x108, 'M', 'ĉ'), (0x109, 'V'), - (0x10A, 'M', u'ċ'), + (0x10A, 'M', 'ċ'), (0x10B, 'V'), - (0x10C, 'M', u'č'), + (0x10C, 'M', 'č'), (0x10D, 'V'), - (0x10E, 'M', u'ď'), + (0x10E, 'M', 'ď'), (0x10F, 'V'), - (0x110, 'M', u'đ'), + (0x110, 'M', 'đ'), (0x111, 'V'), - (0x112, 'M', u'ē'), + (0x112, 'M', 'ē'), (0x113, 'V'), - (0x114, 'M', u'ĕ'), + (0x114, 'M', 'ĕ'), (0x115, 'V'), - (0x116, 'M', u'ė'), + (0x116, 'M', 'ė'), (0x117, 'V'), - (0x118, 'M', u'ę'), + (0x118, 'M', 'ę'), (0x119, 'V'), - (0x11A, 'M', u'ě'), + (0x11A, 'M', 'ě'), (0x11B, 'V'), - (0x11C, 'M', u'ĝ'), + (0x11C, 'M', 'ĝ'), (0x11D, 'V'), - (0x11E, 'M', u'ğ'), + (0x11E, 'M', 'ğ'), (0x11F, 'V'), - (0x120, 'M', u'ġ'), + (0x120, 'M', 'ġ'), (0x121, 'V'), - (0x122, 'M', u'ģ'), + (0x122, 'M', 'ģ'), (0x123, 'V'), - (0x124, 'M', u'ĥ'), + (0x124, 'M', 'ĥ'), (0x125, 'V'), - (0x126, 'M', u'ħ'), + (0x126, 'M', 'ħ'), (0x127, 'V'), - (0x128, 'M', u'ĩ'), + (0x128, 'M', 'ĩ'), (0x129, 'V'), - (0x12A, 'M', u'ī'), + (0x12A, 'M', 'ī'), (0x12B, 'V'), ] def _seg_3(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x12C, 'M', u'ĭ'), + (0x12C, 'M', 'ĭ'), (0x12D, 'V'), - (0x12E, 'M', u'į'), + (0x12E, 'M', 'į'), (0x12F, 'V'), - (0x130, 'M', u'i̇'), + (0x130, 'M', 'i̇'), (0x131, 'V'), - (0x132, 'M', u'ij'), - (0x134, 'M', u'ĵ'), + (0x132, 'M', 'ij'), + (0x134, 'M', 'ĵ'), (0x135, 'V'), - (0x136, 'M', u'ķ'), + (0x136, 'M', 'ķ'), (0x137, 'V'), - (0x139, 'M', u'ĺ'), + (0x139, 'M', 'ĺ'), (0x13A, 'V'), - (0x13B, 'M', u'ļ'), + (0x13B, 'M', 'ļ'), (0x13C, 'V'), - (0x13D, 'M', u'ľ'), + (0x13D, 'M', 'ľ'), (0x13E, 'V'), - (0x13F, 'M', u'l·'), - (0x141, 'M', u'ł'), + (0x13F, 'M', 'l·'), + (0x141, 'M', 'ł'), (0x142, 'V'), - (0x143, 'M', u'ń'), + (0x143, 'M', 'ń'), (0x144, 'V'), - (0x145, 'M', u'ņ'), + (0x145, 'M', 'ņ'), (0x146, 'V'), - (0x147, 'M', u'ň'), + (0x147, 'M', 'ň'), (0x148, 'V'), - (0x149, 'M', u'ʼn'), - (0x14A, 'M', u'ŋ'), + (0x149, 'M', 'ʼn'), + (0x14A, 'M', 'ŋ'), (0x14B, 'V'), - (0x14C, 'M', u'ō'), + (0x14C, 'M', 'ō'), (0x14D, 'V'), - (0x14E, 'M', u'ŏ'), + (0x14E, 'M', 'ŏ'), (0x14F, 'V'), - (0x150, 'M', u'ő'), + (0x150, 'M', 'ő'), (0x151, 'V'), - (0x152, 'M', u'œ'), + (0x152, 'M', 'œ'), (0x153, 'V'), - (0x154, 'M', u'ŕ'), + (0x154, 'M', 'ŕ'), (0x155, 'V'), - (0x156, 'M', u'ŗ'), + (0x156, 'M', 'ŗ'), (0x157, 'V'), - (0x158, 'M', u'ř'), + (0x158, 'M', 'ř'), (0x159, 'V'), - (0x15A, 'M', u'ś'), + (0x15A, 'M', 'ś'), (0x15B, 'V'), - (0x15C, 'M', u'ŝ'), + (0x15C, 'M', 'ŝ'), (0x15D, 'V'), - (0x15E, 'M', u'ş'), + (0x15E, 'M', 'ş'), (0x15F, 'V'), - (0x160, 'M', u'š'), + (0x160, 'M', 'š'), (0x161, 'V'), - (0x162, 'M', u'ţ'), + (0x162, 'M', 'ţ'), (0x163, 'V'), - (0x164, 'M', u'ť'), + (0x164, 'M', 'ť'), (0x165, 'V'), - (0x166, 'M', u'ŧ'), + (0x166, 'M', 'ŧ'), (0x167, 'V'), - (0x168, 'M', u'ũ'), + (0x168, 'M', 'ũ'), (0x169, 'V'), - (0x16A, 'M', u'ū'), + (0x16A, 'M', 'ū'), (0x16B, 'V'), - (0x16C, 'M', u'ŭ'), + (0x16C, 'M', 'ŭ'), (0x16D, 'V'), - (0x16E, 'M', u'ů'), + (0x16E, 'M', 'ů'), (0x16F, 'V'), - (0x170, 'M', u'ű'), + (0x170, 'M', 'ű'), (0x171, 'V'), - (0x172, 'M', u'ų'), + (0x172, 'M', 'ų'), (0x173, 'V'), - (0x174, 'M', u'ŵ'), + (0x174, 'M', 'ŵ'), (0x175, 'V'), - (0x176, 'M', u'ŷ'), + (0x176, 'M', 'ŷ'), (0x177, 'V'), - (0x178, 'M', u'ÿ'), - (0x179, 'M', u'ź'), + (0x178, 'M', 'ÿ'), + (0x179, 'M', 'ź'), (0x17A, 'V'), - (0x17B, 'M', u'ż'), + (0x17B, 'M', 'ż'), (0x17C, 'V'), - (0x17D, 'M', u'ž'), + (0x17D, 'M', 'ž'), (0x17E, 'V'), - (0x17F, 'M', u's'), + (0x17F, 'M', 's'), (0x180, 'V'), - (0x181, 'M', u'ɓ'), - (0x182, 'M', u'ƃ'), + (0x181, 'M', 'ɓ'), + (0x182, 'M', 'ƃ'), (0x183, 'V'), - (0x184, 'M', u'ƅ'), + (0x184, 'M', 'ƅ'), (0x185, 'V'), - (0x186, 'M', u'ɔ'), - (0x187, 'M', u'ƈ'), + (0x186, 'M', 'ɔ'), + (0x187, 'M', 'ƈ'), (0x188, 'V'), - (0x189, 'M', u'ɖ'), - (0x18A, 'M', u'ɗ'), - (0x18B, 'M', u'ƌ'), + (0x189, 'M', 'ɖ'), + (0x18A, 'M', 'ɗ'), + (0x18B, 'M', 'ƌ'), (0x18C, 'V'), - (0x18E, 'M', u'ǝ'), - (0x18F, 'M', u'ə'), - (0x190, 'M', u'ɛ'), - (0x191, 'M', u'ƒ'), + (0x18E, 'M', 'ǝ'), + (0x18F, 'M', 'ə'), + (0x190, 'M', 'ɛ'), + (0x191, 'M', 'ƒ'), (0x192, 'V'), - (0x193, 'M', u'ɠ'), + (0x193, 'M', 'ɠ'), ] def _seg_4(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x194, 'M', u'ɣ'), + (0x194, 'M', 'ɣ'), (0x195, 'V'), - (0x196, 'M', u'ɩ'), - (0x197, 'M', u'ɨ'), - (0x198, 'M', u'ƙ'), + (0x196, 'M', 'ɩ'), + (0x197, 'M', 'ɨ'), + (0x198, 'M', 'ƙ'), (0x199, 'V'), - (0x19C, 'M', u'ɯ'), - (0x19D, 'M', u'ɲ'), + (0x19C, 'M', 'ɯ'), + (0x19D, 'M', 'ɲ'), (0x19E, 'V'), - (0x19F, 'M', u'ɵ'), - (0x1A0, 'M', u'ơ'), + (0x19F, 'M', 'ɵ'), + (0x1A0, 'M', 'ơ'), (0x1A1, 'V'), - (0x1A2, 'M', u'ƣ'), + (0x1A2, 'M', 'ƣ'), (0x1A3, 'V'), - (0x1A4, 'M', u'ƥ'), + (0x1A4, 'M', 'ƥ'), (0x1A5, 'V'), - (0x1A6, 'M', u'ʀ'), - (0x1A7, 'M', u'ƨ'), + (0x1A6, 'M', 'ʀ'), + (0x1A7, 'M', 'ƨ'), (0x1A8, 'V'), - (0x1A9, 'M', u'ʃ'), + (0x1A9, 'M', 'ʃ'), (0x1AA, 'V'), - (0x1AC, 'M', u'ƭ'), + (0x1AC, 'M', 'ƭ'), (0x1AD, 'V'), - (0x1AE, 'M', u'ʈ'), - (0x1AF, 'M', u'ư'), + (0x1AE, 'M', 'ʈ'), + (0x1AF, 'M', 'ư'), (0x1B0, 'V'), - (0x1B1, 'M', u'ʊ'), - (0x1B2, 'M', u'ʋ'), - (0x1B3, 'M', u'ƴ'), + (0x1B1, 'M', 'ʊ'), + (0x1B2, 'M', 'ʋ'), + (0x1B3, 'M', 'ƴ'), (0x1B4, 'V'), - (0x1B5, 'M', u'ƶ'), + (0x1B5, 'M', 'ƶ'), (0x1B6, 'V'), - (0x1B7, 'M', u'ʒ'), - (0x1B8, 'M', u'ƹ'), + (0x1B7, 'M', 'ʒ'), + (0x1B8, 'M', 'ƹ'), (0x1B9, 'V'), - (0x1BC, 'M', u'ƽ'), + (0x1BC, 'M', 'ƽ'), (0x1BD, 'V'), - (0x1C4, 'M', u'dž'), - (0x1C7, 'M', u'lj'), - (0x1CA, 'M', u'nj'), - (0x1CD, 'M', u'ǎ'), + (0x1C4, 'M', 'dž'), + (0x1C7, 'M', 'lj'), + (0x1CA, 'M', 'nj'), + (0x1CD, 'M', 'ǎ'), (0x1CE, 'V'), - (0x1CF, 'M', u'ǐ'), + (0x1CF, 'M', 'ǐ'), (0x1D0, 'V'), - (0x1D1, 'M', u'ǒ'), + (0x1D1, 'M', 'ǒ'), (0x1D2, 'V'), - (0x1D3, 'M', u'ǔ'), + (0x1D3, 'M', 'ǔ'), (0x1D4, 'V'), - (0x1D5, 'M', u'ǖ'), + (0x1D5, 'M', 'ǖ'), (0x1D6, 'V'), - (0x1D7, 'M', u'ǘ'), + (0x1D7, 'M', 'ǘ'), (0x1D8, 'V'), - (0x1D9, 'M', u'ǚ'), + (0x1D9, 'M', 'ǚ'), (0x1DA, 'V'), - (0x1DB, 'M', u'ǜ'), + (0x1DB, 'M', 'ǜ'), (0x1DC, 'V'), - (0x1DE, 'M', u'ǟ'), + (0x1DE, 'M', 'ǟ'), (0x1DF, 'V'), - (0x1E0, 'M', u'ǡ'), + (0x1E0, 'M', 'ǡ'), (0x1E1, 'V'), - (0x1E2, 'M', u'ǣ'), + (0x1E2, 'M', 'ǣ'), (0x1E3, 'V'), - (0x1E4, 'M', u'ǥ'), + (0x1E4, 'M', 'ǥ'), (0x1E5, 'V'), - (0x1E6, 'M', u'ǧ'), + (0x1E6, 'M', 'ǧ'), (0x1E7, 'V'), - (0x1E8, 'M', u'ǩ'), + (0x1E8, 'M', 'ǩ'), (0x1E9, 'V'), - (0x1EA, 'M', u'ǫ'), + (0x1EA, 'M', 'ǫ'), (0x1EB, 'V'), - (0x1EC, 'M', u'ǭ'), + (0x1EC, 'M', 'ǭ'), (0x1ED, 'V'), - (0x1EE, 'M', u'ǯ'), + (0x1EE, 'M', 'ǯ'), (0x1EF, 'V'), - (0x1F1, 'M', u'dz'), - (0x1F4, 'M', u'ǵ'), + (0x1F1, 'M', 'dz'), + (0x1F4, 'M', 'ǵ'), (0x1F5, 'V'), - (0x1F6, 'M', u'ƕ'), - (0x1F7, 'M', u'ƿ'), - (0x1F8, 'M', u'ǹ'), + (0x1F6, 'M', 'ƕ'), + (0x1F7, 'M', 'ƿ'), + (0x1F8, 'M', 'ǹ'), (0x1F9, 'V'), - (0x1FA, 'M', u'ǻ'), + (0x1FA, 'M', 'ǻ'), (0x1FB, 'V'), - (0x1FC, 'M', u'ǽ'), + (0x1FC, 'M', 'ǽ'), (0x1FD, 'V'), - (0x1FE, 'M', u'ǿ'), + (0x1FE, 'M', 'ǿ'), (0x1FF, 'V'), - (0x200, 'M', u'ȁ'), + (0x200, 'M', 'ȁ'), (0x201, 'V'), - (0x202, 'M', u'ȃ'), + (0x202, 'M', 'ȃ'), (0x203, 'V'), - (0x204, 'M', u'ȅ'), + (0x204, 'M', 'ȅ'), (0x205, 'V'), - (0x206, 'M', u'ȇ'), + (0x206, 'M', 'ȇ'), (0x207, 'V'), - (0x208, 'M', u'ȉ'), + (0x208, 'M', 'ȉ'), (0x209, 'V'), - (0x20A, 'M', u'ȋ'), + (0x20A, 'M', 'ȋ'), (0x20B, 'V'), - (0x20C, 'M', u'ȍ'), + (0x20C, 'M', 'ȍ'), ] def _seg_5(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ (0x20D, 'V'), - (0x20E, 'M', u'ȏ'), + (0x20E, 'M', 'ȏ'), (0x20F, 'V'), - (0x210, 'M', u'ȑ'), + (0x210, 'M', 'ȑ'), (0x211, 'V'), - (0x212, 'M', u'ȓ'), + (0x212, 'M', 'ȓ'), (0x213, 'V'), - (0x214, 'M', u'ȕ'), + (0x214, 'M', 'ȕ'), (0x215, 'V'), - (0x216, 'M', u'ȗ'), + (0x216, 'M', 'ȗ'), (0x217, 'V'), - (0x218, 'M', u'ș'), + (0x218, 'M', 'ș'), (0x219, 'V'), - (0x21A, 'M', u'ț'), + (0x21A, 'M', 'ț'), (0x21B, 'V'), - (0x21C, 'M', u'ȝ'), + (0x21C, 'M', 'ȝ'), (0x21D, 'V'), - (0x21E, 'M', u'ȟ'), + (0x21E, 'M', 'ȟ'), (0x21F, 'V'), - (0x220, 'M', u'ƞ'), + (0x220, 'M', 'ƞ'), (0x221, 'V'), - (0x222, 'M', u'ȣ'), + (0x222, 'M', 'ȣ'), (0x223, 'V'), - (0x224, 'M', u'ȥ'), + (0x224, 'M', 'ȥ'), (0x225, 'V'), - (0x226, 'M', u'ȧ'), + (0x226, 'M', 'ȧ'), (0x227, 'V'), - (0x228, 'M', u'ȩ'), + (0x228, 'M', 'ȩ'), (0x229, 'V'), - (0x22A, 'M', u'ȫ'), + (0x22A, 'M', 'ȫ'), (0x22B, 'V'), - (0x22C, 'M', u'ȭ'), + (0x22C, 'M', 'ȭ'), (0x22D, 'V'), - (0x22E, 'M', u'ȯ'), + (0x22E, 'M', 'ȯ'), (0x22F, 'V'), - (0x230, 'M', u'ȱ'), + (0x230, 'M', 'ȱ'), (0x231, 'V'), - (0x232, 'M', u'ȳ'), + (0x232, 'M', 'ȳ'), (0x233, 'V'), - (0x23A, 'M', u'ⱥ'), - (0x23B, 'M', u'ȼ'), + (0x23A, 'M', 'ⱥ'), + (0x23B, 'M', 'ȼ'), (0x23C, 'V'), - (0x23D, 'M', u'ƚ'), - (0x23E, 'M', u'ⱦ'), + (0x23D, 'M', 'ƚ'), + (0x23E, 'M', 'ⱦ'), (0x23F, 'V'), - (0x241, 'M', u'ɂ'), + (0x241, 'M', 'ɂ'), (0x242, 'V'), - (0x243, 'M', u'ƀ'), - (0x244, 'M', u'ʉ'), - (0x245, 'M', u'ʌ'), - (0x246, 'M', u'ɇ'), + (0x243, 'M', 'ƀ'), + (0x244, 'M', 'ʉ'), + (0x245, 'M', 'ʌ'), + (0x246, 'M', 'ɇ'), (0x247, 'V'), - (0x248, 'M', u'ɉ'), + (0x248, 'M', 'ɉ'), (0x249, 'V'), - (0x24A, 'M', u'ɋ'), + (0x24A, 'M', 'ɋ'), (0x24B, 'V'), - (0x24C, 'M', u'ɍ'), + (0x24C, 'M', 'ɍ'), (0x24D, 'V'), - (0x24E, 'M', u'ɏ'), + (0x24E, 'M', 'ɏ'), (0x24F, 'V'), - (0x2B0, 'M', u'h'), - (0x2B1, 'M', u'ɦ'), - (0x2B2, 'M', u'j'), - (0x2B3, 'M', u'r'), - (0x2B4, 'M', u'ɹ'), - (0x2B5, 'M', u'ɻ'), - (0x2B6, 'M', u'ʁ'), - (0x2B7, 'M', u'w'), - (0x2B8, 'M', u'y'), + (0x2B0, 'M', 'h'), + (0x2B1, 'M', 'ɦ'), + (0x2B2, 'M', 'j'), + (0x2B3, 'M', 'r'), + (0x2B4, 'M', 'ɹ'), + (0x2B5, 'M', 'ɻ'), + (0x2B6, 'M', 'ʁ'), + (0x2B7, 'M', 'w'), + (0x2B8, 'M', 'y'), (0x2B9, 'V'), - (0x2D8, '3', u' ̆'), - (0x2D9, '3', u' ̇'), - (0x2DA, '3', u' ̊'), - (0x2DB, '3', u' ̨'), - (0x2DC, '3', u' ̃'), - (0x2DD, '3', u' ̋'), + (0x2D8, '3', ' ̆'), + (0x2D9, '3', ' ̇'), + (0x2DA, '3', ' ̊'), + (0x2DB, '3', ' ̨'), + (0x2DC, '3', ' ̃'), + (0x2DD, '3', ' ̋'), (0x2DE, 'V'), - (0x2E0, 'M', u'ɣ'), - (0x2E1, 'M', u'l'), - (0x2E2, 'M', u's'), - (0x2E3, 'M', u'x'), - (0x2E4, 'M', u'ʕ'), + (0x2E0, 'M', 'ɣ'), + (0x2E1, 'M', 'l'), + (0x2E2, 'M', 's'), + (0x2E3, 'M', 'x'), + (0x2E4, 'M', 'ʕ'), (0x2E5, 'V'), - (0x340, 'M', u'̀'), - (0x341, 'M', u'́'), + (0x340, 'M', '̀'), + (0x341, 'M', '́'), (0x342, 'V'), - (0x343, 'M', u'̓'), - (0x344, 'M', u'̈́'), - (0x345, 'M', u'ι'), + (0x343, 'M', '̓'), + (0x344, 'M', '̈́'), + (0x345, 'M', 'ι'), (0x346, 'V'), (0x34F, 'I'), (0x350, 'V'), - (0x370, 'M', u'ͱ'), + (0x370, 'M', 'ͱ'), (0x371, 'V'), - (0x372, 'M', u'ͳ'), + (0x372, 'M', 'ͳ'), (0x373, 'V'), - (0x374, 'M', u'ʹ'), + (0x374, 'M', 'ʹ'), (0x375, 'V'), - (0x376, 'M', u'ͷ'), + (0x376, 'M', 'ͷ'), (0x377, 'V'), ] def _seg_6(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ (0x378, 'X'), - (0x37A, '3', u' ι'), + (0x37A, '3', ' ι'), (0x37B, 'V'), - (0x37E, '3', u';'), - (0x37F, 'M', u'ϳ'), + (0x37E, '3', ';'), + (0x37F, 'M', 'ϳ'), (0x380, 'X'), - (0x384, '3', u' ́'), - (0x385, '3', u' ̈́'), - (0x386, 'M', u'ά'), - (0x387, 'M', u'·'), - (0x388, 'M', u'έ'), - (0x389, 'M', u'ή'), - (0x38A, 'M', u'ί'), + (0x384, '3', ' ́'), + (0x385, '3', ' ̈́'), + (0x386, 'M', 'ά'), + (0x387, 'M', '·'), + (0x388, 'M', 'έ'), + (0x389, 'M', 'ή'), + (0x38A, 'M', 'ί'), (0x38B, 'X'), - (0x38C, 'M', u'ό'), + (0x38C, 'M', 'ό'), (0x38D, 'X'), - (0x38E, 'M', u'ύ'), - (0x38F, 'M', u'ώ'), + (0x38E, 'M', 'ύ'), + (0x38F, 'M', 'ώ'), (0x390, 'V'), - (0x391, 'M', u'α'), - (0x392, 'M', u'β'), - (0x393, 'M', u'γ'), - (0x394, 'M', u'δ'), - (0x395, 'M', u'ε'), - (0x396, 'M', u'ζ'), - (0x397, 'M', u'η'), - (0x398, 'M', u'θ'), - (0x399, 'M', u'ι'), - (0x39A, 'M', u'κ'), - (0x39B, 'M', u'λ'), - (0x39C, 'M', u'μ'), - (0x39D, 'M', u'ν'), - (0x39E, 'M', u'ξ'), - (0x39F, 'M', u'ο'), - (0x3A0, 'M', u'π'), - (0x3A1, 'M', u'ρ'), + (0x391, 'M', 'α'), + (0x392, 'M', 'β'), + (0x393, 'M', 'γ'), + (0x394, 'M', 'δ'), + (0x395, 'M', 'ε'), + (0x396, 'M', 'ζ'), + (0x397, 'M', 'η'), + (0x398, 'M', 'θ'), + (0x399, 'M', 'ι'), + (0x39A, 'M', 'κ'), + (0x39B, 'M', 'λ'), + (0x39C, 'M', 'μ'), + (0x39D, 'M', 'ν'), + (0x39E, 'M', 'ξ'), + (0x39F, 'M', 'ο'), + (0x3A0, 'M', 'π'), + (0x3A1, 'M', 'ρ'), (0x3A2, 'X'), - (0x3A3, 'M', u'σ'), - (0x3A4, 'M', u'τ'), - (0x3A5, 'M', u'υ'), - (0x3A6, 'M', u'φ'), - (0x3A7, 'M', u'χ'), - (0x3A8, 'M', u'ψ'), - (0x3A9, 'M', u'ω'), - (0x3AA, 'M', u'ϊ'), - (0x3AB, 'M', u'ϋ'), + (0x3A3, 'M', 'σ'), + (0x3A4, 'M', 'τ'), + (0x3A5, 'M', 'υ'), + (0x3A6, 'M', 'φ'), + (0x3A7, 'M', 'χ'), + (0x3A8, 'M', 'ψ'), + (0x3A9, 'M', 'ω'), + (0x3AA, 'M', 'ϊ'), + (0x3AB, 'M', 'ϋ'), (0x3AC, 'V'), - (0x3C2, 'D', u'σ'), + (0x3C2, 'D', 'σ'), (0x3C3, 'V'), - (0x3CF, 'M', u'ϗ'), - (0x3D0, 'M', u'β'), - (0x3D1, 'M', u'θ'), - (0x3D2, 'M', u'υ'), - (0x3D3, 'M', u'ύ'), - (0x3D4, 'M', u'ϋ'), - (0x3D5, 'M', u'φ'), - (0x3D6, 'M', u'π'), + (0x3CF, 'M', 'ϗ'), + (0x3D0, 'M', 'β'), + (0x3D1, 'M', 'θ'), + (0x3D2, 'M', 'υ'), + (0x3D3, 'M', 'ύ'), + (0x3D4, 'M', 'ϋ'), + (0x3D5, 'M', 'φ'), + (0x3D6, 'M', 'π'), (0x3D7, 'V'), - (0x3D8, 'M', u'ϙ'), + (0x3D8, 'M', 'ϙ'), (0x3D9, 'V'), - (0x3DA, 'M', u'ϛ'), + (0x3DA, 'M', 'ϛ'), (0x3DB, 'V'), - (0x3DC, 'M', u'ϝ'), + (0x3DC, 'M', 'ϝ'), (0x3DD, 'V'), - (0x3DE, 'M', u'ϟ'), + (0x3DE, 'M', 'ϟ'), (0x3DF, 'V'), - (0x3E0, 'M', u'ϡ'), + (0x3E0, 'M', 'ϡ'), (0x3E1, 'V'), - (0x3E2, 'M', u'ϣ'), + (0x3E2, 'M', 'ϣ'), (0x3E3, 'V'), - (0x3E4, 'M', u'ϥ'), + (0x3E4, 'M', 'ϥ'), (0x3E5, 'V'), - (0x3E6, 'M', u'ϧ'), + (0x3E6, 'M', 'ϧ'), (0x3E7, 'V'), - (0x3E8, 'M', u'ϩ'), + (0x3E8, 'M', 'ϩ'), (0x3E9, 'V'), - (0x3EA, 'M', u'ϫ'), + (0x3EA, 'M', 'ϫ'), (0x3EB, 'V'), - (0x3EC, 'M', u'ϭ'), + (0x3EC, 'M', 'ϭ'), (0x3ED, 'V'), - (0x3EE, 'M', u'ϯ'), + (0x3EE, 'M', 'ϯ'), (0x3EF, 'V'), - (0x3F0, 'M', u'κ'), - (0x3F1, 'M', u'ρ'), - (0x3F2, 'M', u'σ'), + (0x3F0, 'M', 'κ'), + (0x3F1, 'M', 'ρ'), + (0x3F2, 'M', 'σ'), (0x3F3, 'V'), - (0x3F4, 'M', u'θ'), - (0x3F5, 'M', u'ε'), + (0x3F4, 'M', 'θ'), + (0x3F5, 'M', 'ε'), (0x3F6, 'V'), - (0x3F7, 'M', u'ϸ'), + (0x3F7, 'M', 'ϸ'), (0x3F8, 'V'), - (0x3F9, 'M', u'σ'), - (0x3FA, 'M', u'ϻ'), + (0x3F9, 'M', 'σ'), + (0x3FA, 'M', 'ϻ'), (0x3FB, 'V'), - (0x3FD, 'M', u'ͻ'), - (0x3FE, 'M', u'ͼ'), - (0x3FF, 'M', u'ͽ'), - (0x400, 'M', u'ѐ'), - (0x401, 'M', u'ё'), - (0x402, 'M', u'ђ'), + (0x3FD, 'M', 'ͻ'), + (0x3FE, 'M', 'ͼ'), + (0x3FF, 'M', 'ͽ'), + (0x400, 'M', 'ѐ'), + (0x401, 'M', 'ё'), + (0x402, 'M', 'ђ'), ] def _seg_7(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x403, 'M', u'ѓ'), - (0x404, 'M', u'є'), - (0x405, 'M', u'ѕ'), - (0x406, 'M', u'і'), - (0x407, 'M', u'ї'), - (0x408, 'M', u'ј'), - (0x409, 'M', u'љ'), - (0x40A, 'M', u'њ'), - (0x40B, 'M', u'ћ'), - (0x40C, 'M', u'ќ'), - (0x40D, 'M', u'ѝ'), - (0x40E, 'M', u'ў'), - (0x40F, 'M', u'џ'), - (0x410, 'M', u'а'), - (0x411, 'M', u'б'), - (0x412, 'M', u'в'), - (0x413, 'M', u'г'), - (0x414, 'M', u'д'), - (0x415, 'M', u'е'), - (0x416, 'M', u'ж'), - (0x417, 'M', u'з'), - (0x418, 'M', u'и'), - (0x419, 'M', u'й'), - (0x41A, 'M', u'к'), - (0x41B, 'M', u'л'), - (0x41C, 'M', u'м'), - (0x41D, 'M', u'н'), - (0x41E, 'M', u'о'), - (0x41F, 'M', u'п'), - (0x420, 'M', u'р'), - (0x421, 'M', u'с'), - (0x422, 'M', u'т'), - (0x423, 'M', u'у'), - (0x424, 'M', u'ф'), - (0x425, 'M', u'х'), - (0x426, 'M', u'ц'), - (0x427, 'M', u'ч'), - (0x428, 'M', u'ш'), - (0x429, 'M', u'щ'), - (0x42A, 'M', u'ъ'), - (0x42B, 'M', u'ы'), - (0x42C, 'M', u'ь'), - (0x42D, 'M', u'э'), - (0x42E, 'M', u'ю'), - (0x42F, 'M', u'я'), + (0x403, 'M', 'ѓ'), + (0x404, 'M', 'є'), + (0x405, 'M', 'ѕ'), + (0x406, 'M', 'і'), + (0x407, 'M', 'ї'), + (0x408, 'M', 'ј'), + (0x409, 'M', 'љ'), + (0x40A, 'M', 'њ'), + (0x40B, 'M', 'ћ'), + (0x40C, 'M', 'ќ'), + (0x40D, 'M', 'ѝ'), + (0x40E, 'M', 'ў'), + (0x40F, 'M', 'џ'), + (0x410, 'M', 'а'), + (0x411, 'M', 'б'), + (0x412, 'M', 'в'), + (0x413, 'M', 'г'), + (0x414, 'M', 'д'), + (0x415, 'M', 'е'), + (0x416, 'M', 'ж'), + (0x417, 'M', 'з'), + (0x418, 'M', 'и'), + (0x419, 'M', 'й'), + (0x41A, 'M', 'к'), + (0x41B, 'M', 'л'), + (0x41C, 'M', 'м'), + (0x41D, 'M', 'н'), + (0x41E, 'M', 'о'), + (0x41F, 'M', 'п'), + (0x420, 'M', 'р'), + (0x421, 'M', 'с'), + (0x422, 'M', 'т'), + (0x423, 'M', 'у'), + (0x424, 'M', 'ф'), + (0x425, 'M', 'х'), + (0x426, 'M', 'ц'), + (0x427, 'M', 'ч'), + (0x428, 'M', 'ш'), + (0x429, 'M', 'щ'), + (0x42A, 'M', 'ъ'), + (0x42B, 'M', 'ы'), + (0x42C, 'M', 'ь'), + (0x42D, 'M', 'э'), + (0x42E, 'M', 'ю'), + (0x42F, 'M', 'я'), (0x430, 'V'), - (0x460, 'M', u'ѡ'), + (0x460, 'M', 'ѡ'), (0x461, 'V'), - (0x462, 'M', u'ѣ'), + (0x462, 'M', 'ѣ'), (0x463, 'V'), - (0x464, 'M', u'ѥ'), + (0x464, 'M', 'ѥ'), (0x465, 'V'), - (0x466, 'M', u'ѧ'), + (0x466, 'M', 'ѧ'), (0x467, 'V'), - (0x468, 'M', u'ѩ'), + (0x468, 'M', 'ѩ'), (0x469, 'V'), - (0x46A, 'M', u'ѫ'), + (0x46A, 'M', 'ѫ'), (0x46B, 'V'), - (0x46C, 'M', u'ѭ'), + (0x46C, 'M', 'ѭ'), (0x46D, 'V'), - (0x46E, 'M', u'ѯ'), + (0x46E, 'M', 'ѯ'), (0x46F, 'V'), - (0x470, 'M', u'ѱ'), + (0x470, 'M', 'ѱ'), (0x471, 'V'), - (0x472, 'M', u'ѳ'), + (0x472, 'M', 'ѳ'), (0x473, 'V'), - (0x474, 'M', u'ѵ'), + (0x474, 'M', 'ѵ'), (0x475, 'V'), - (0x476, 'M', u'ѷ'), + (0x476, 'M', 'ѷ'), (0x477, 'V'), - (0x478, 'M', u'ѹ'), + (0x478, 'M', 'ѹ'), (0x479, 'V'), - (0x47A, 'M', u'ѻ'), + (0x47A, 'M', 'ѻ'), (0x47B, 'V'), - (0x47C, 'M', u'ѽ'), + (0x47C, 'M', 'ѽ'), (0x47D, 'V'), - (0x47E, 'M', u'ѿ'), + (0x47E, 'M', 'ѿ'), (0x47F, 'V'), - (0x480, 'M', u'ҁ'), + (0x480, 'M', 'ҁ'), (0x481, 'V'), - (0x48A, 'M', u'ҋ'), + (0x48A, 'M', 'ҋ'), (0x48B, 'V'), - (0x48C, 'M', u'ҍ'), + (0x48C, 'M', 'ҍ'), (0x48D, 'V'), - (0x48E, 'M', u'ҏ'), + (0x48E, 'M', 'ҏ'), (0x48F, 'V'), - (0x490, 'M', u'ґ'), + (0x490, 'M', 'ґ'), (0x491, 'V'), - (0x492, 'M', u'ғ'), + (0x492, 'M', 'ғ'), (0x493, 'V'), - (0x494, 'M', u'ҕ'), + (0x494, 'M', 'ҕ'), (0x495, 'V'), - (0x496, 'M', u'җ'), + (0x496, 'M', 'җ'), (0x497, 'V'), - (0x498, 'M', u'ҙ'), + (0x498, 'M', 'ҙ'), (0x499, 'V'), - (0x49A, 'M', u'қ'), + (0x49A, 'M', 'қ'), (0x49B, 'V'), - (0x49C, 'M', u'ҝ'), + (0x49C, 'M', 'ҝ'), (0x49D, 'V'), ] def _seg_8(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x49E, 'M', u'ҟ'), + (0x49E, 'M', 'ҟ'), (0x49F, 'V'), - (0x4A0, 'M', u'ҡ'), + (0x4A0, 'M', 'ҡ'), (0x4A1, 'V'), - (0x4A2, 'M', u'ң'), + (0x4A2, 'M', 'ң'), (0x4A3, 'V'), - (0x4A4, 'M', u'ҥ'), + (0x4A4, 'M', 'ҥ'), (0x4A5, 'V'), - (0x4A6, 'M', u'ҧ'), + (0x4A6, 'M', 'ҧ'), (0x4A7, 'V'), - (0x4A8, 'M', u'ҩ'), + (0x4A8, 'M', 'ҩ'), (0x4A9, 'V'), - (0x4AA, 'M', u'ҫ'), + (0x4AA, 'M', 'ҫ'), (0x4AB, 'V'), - (0x4AC, 'M', u'ҭ'), + (0x4AC, 'M', 'ҭ'), (0x4AD, 'V'), - (0x4AE, 'M', u'ү'), + (0x4AE, 'M', 'ү'), (0x4AF, 'V'), - (0x4B0, 'M', u'ұ'), + (0x4B0, 'M', 'ұ'), (0x4B1, 'V'), - (0x4B2, 'M', u'ҳ'), + (0x4B2, 'M', 'ҳ'), (0x4B3, 'V'), - (0x4B4, 'M', u'ҵ'), + (0x4B4, 'M', 'ҵ'), (0x4B5, 'V'), - (0x4B6, 'M', u'ҷ'), + (0x4B6, 'M', 'ҷ'), (0x4B7, 'V'), - (0x4B8, 'M', u'ҹ'), + (0x4B8, 'M', 'ҹ'), (0x4B9, 'V'), - (0x4BA, 'M', u'һ'), + (0x4BA, 'M', 'һ'), (0x4BB, 'V'), - (0x4BC, 'M', u'ҽ'), + (0x4BC, 'M', 'ҽ'), (0x4BD, 'V'), - (0x4BE, 'M', u'ҿ'), + (0x4BE, 'M', 'ҿ'), (0x4BF, 'V'), (0x4C0, 'X'), - (0x4C1, 'M', u'ӂ'), + (0x4C1, 'M', 'ӂ'), (0x4C2, 'V'), - (0x4C3, 'M', u'ӄ'), + (0x4C3, 'M', 'ӄ'), (0x4C4, 'V'), - (0x4C5, 'M', u'ӆ'), + (0x4C5, 'M', 'ӆ'), (0x4C6, 'V'), - (0x4C7, 'M', u'ӈ'), + (0x4C7, 'M', 'ӈ'), (0x4C8, 'V'), - (0x4C9, 'M', u'ӊ'), + (0x4C9, 'M', 'ӊ'), (0x4CA, 'V'), - (0x4CB, 'M', u'ӌ'), + (0x4CB, 'M', 'ӌ'), (0x4CC, 'V'), - (0x4CD, 'M', u'ӎ'), + (0x4CD, 'M', 'ӎ'), (0x4CE, 'V'), - (0x4D0, 'M', u'ӑ'), + (0x4D0, 'M', 'ӑ'), (0x4D1, 'V'), - (0x4D2, 'M', u'ӓ'), + (0x4D2, 'M', 'ӓ'), (0x4D3, 'V'), - (0x4D4, 'M', u'ӕ'), + (0x4D4, 'M', 'ӕ'), (0x4D5, 'V'), - (0x4D6, 'M', u'ӗ'), + (0x4D6, 'M', 'ӗ'), (0x4D7, 'V'), - (0x4D8, 'M', u'ә'), + (0x4D8, 'M', 'ә'), (0x4D9, 'V'), - (0x4DA, 'M', u'ӛ'), + (0x4DA, 'M', 'ӛ'), (0x4DB, 'V'), - (0x4DC, 'M', u'ӝ'), + (0x4DC, 'M', 'ӝ'), (0x4DD, 'V'), - (0x4DE, 'M', u'ӟ'), + (0x4DE, 'M', 'ӟ'), (0x4DF, 'V'), - (0x4E0, 'M', u'ӡ'), + (0x4E0, 'M', 'ӡ'), (0x4E1, 'V'), - (0x4E2, 'M', u'ӣ'), + (0x4E2, 'M', 'ӣ'), (0x4E3, 'V'), - (0x4E4, 'M', u'ӥ'), + (0x4E4, 'M', 'ӥ'), (0x4E5, 'V'), - (0x4E6, 'M', u'ӧ'), + (0x4E6, 'M', 'ӧ'), (0x4E7, 'V'), - (0x4E8, 'M', u'ө'), + (0x4E8, 'M', 'ө'), (0x4E9, 'V'), - (0x4EA, 'M', u'ӫ'), + (0x4EA, 'M', 'ӫ'), (0x4EB, 'V'), - (0x4EC, 'M', u'ӭ'), + (0x4EC, 'M', 'ӭ'), (0x4ED, 'V'), - (0x4EE, 'M', u'ӯ'), + (0x4EE, 'M', 'ӯ'), (0x4EF, 'V'), - (0x4F0, 'M', u'ӱ'), + (0x4F0, 'M', 'ӱ'), (0x4F1, 'V'), - (0x4F2, 'M', u'ӳ'), + (0x4F2, 'M', 'ӳ'), (0x4F3, 'V'), - (0x4F4, 'M', u'ӵ'), + (0x4F4, 'M', 'ӵ'), (0x4F5, 'V'), - (0x4F6, 'M', u'ӷ'), + (0x4F6, 'M', 'ӷ'), (0x4F7, 'V'), - (0x4F8, 'M', u'ӹ'), + (0x4F8, 'M', 'ӹ'), (0x4F9, 'V'), - (0x4FA, 'M', u'ӻ'), + (0x4FA, 'M', 'ӻ'), (0x4FB, 'V'), - (0x4FC, 'M', u'ӽ'), + (0x4FC, 'M', 'ӽ'), (0x4FD, 'V'), - (0x4FE, 'M', u'ӿ'), + (0x4FE, 'M', 'ӿ'), (0x4FF, 'V'), - (0x500, 'M', u'ԁ'), + (0x500, 'M', 'ԁ'), (0x501, 'V'), - (0x502, 'M', u'ԃ'), + (0x502, 'M', 'ԃ'), ] def _seg_9(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ (0x503, 'V'), - (0x504, 'M', u'ԅ'), + (0x504, 'M', 'ԅ'), (0x505, 'V'), - (0x506, 'M', u'ԇ'), + (0x506, 'M', 'ԇ'), (0x507, 'V'), - (0x508, 'M', u'ԉ'), + (0x508, 'M', 'ԉ'), (0x509, 'V'), - (0x50A, 'M', u'ԋ'), + (0x50A, 'M', 'ԋ'), (0x50B, 'V'), - (0x50C, 'M', u'ԍ'), + (0x50C, 'M', 'ԍ'), (0x50D, 'V'), - (0x50E, 'M', u'ԏ'), + (0x50E, 'M', 'ԏ'), (0x50F, 'V'), - (0x510, 'M', u'ԑ'), + (0x510, 'M', 'ԑ'), (0x511, 'V'), - (0x512, 'M', u'ԓ'), + (0x512, 'M', 'ԓ'), (0x513, 'V'), - (0x514, 'M', u'ԕ'), + (0x514, 'M', 'ԕ'), (0x515, 'V'), - (0x516, 'M', u'ԗ'), + (0x516, 'M', 'ԗ'), (0x517, 'V'), - (0x518, 'M', u'ԙ'), + (0x518, 'M', 'ԙ'), (0x519, 'V'), - (0x51A, 'M', u'ԛ'), + (0x51A, 'M', 'ԛ'), (0x51B, 'V'), - (0x51C, 'M', u'ԝ'), + (0x51C, 'M', 'ԝ'), (0x51D, 'V'), - (0x51E, 'M', u'ԟ'), + (0x51E, 'M', 'ԟ'), (0x51F, 'V'), - (0x520, 'M', u'ԡ'), + (0x520, 'M', 'ԡ'), (0x521, 'V'), - (0x522, 'M', u'ԣ'), + (0x522, 'M', 'ԣ'), (0x523, 'V'), - (0x524, 'M', u'ԥ'), + (0x524, 'M', 'ԥ'), (0x525, 'V'), - (0x526, 'M', u'ԧ'), + (0x526, 'M', 'ԧ'), (0x527, 'V'), - (0x528, 'M', u'ԩ'), + (0x528, 'M', 'ԩ'), (0x529, 'V'), - (0x52A, 'M', u'ԫ'), + (0x52A, 'M', 'ԫ'), (0x52B, 'V'), - (0x52C, 'M', u'ԭ'), + (0x52C, 'M', 'ԭ'), (0x52D, 'V'), - (0x52E, 'M', u'ԯ'), + (0x52E, 'M', 'ԯ'), (0x52F, 'V'), (0x530, 'X'), - (0x531, 'M', u'ա'), - (0x532, 'M', u'բ'), - (0x533, 'M', u'գ'), - (0x534, 'M', u'դ'), - (0x535, 'M', u'ե'), - (0x536, 'M', u'զ'), - (0x537, 'M', u'է'), - (0x538, 'M', u'ը'), - (0x539, 'M', u'թ'), - (0x53A, 'M', u'ժ'), - (0x53B, 'M', u'ի'), - (0x53C, 'M', u'լ'), - (0x53D, 'M', u'խ'), - (0x53E, 'M', u'ծ'), - (0x53F, 'M', u'կ'), - (0x540, 'M', u'հ'), - (0x541, 'M', u'ձ'), - (0x542, 'M', u'ղ'), - (0x543, 'M', u'ճ'), - (0x544, 'M', u'մ'), - (0x545, 'M', u'յ'), - (0x546, 'M', u'ն'), - (0x547, 'M', u'շ'), - (0x548, 'M', u'ո'), - (0x549, 'M', u'չ'), - (0x54A, 'M', u'պ'), - (0x54B, 'M', u'ջ'), - (0x54C, 'M', u'ռ'), - (0x54D, 'M', u'ս'), - (0x54E, 'M', u'վ'), - (0x54F, 'M', u'տ'), - (0x550, 'M', u'ր'), - (0x551, 'M', u'ց'), - (0x552, 'M', u'ւ'), - (0x553, 'M', u'փ'), - (0x554, 'M', u'ք'), - (0x555, 'M', u'օ'), - (0x556, 'M', u'ֆ'), + (0x531, 'M', 'ա'), + (0x532, 'M', 'բ'), + (0x533, 'M', 'գ'), + (0x534, 'M', 'դ'), + (0x535, 'M', 'ե'), + (0x536, 'M', 'զ'), + (0x537, 'M', 'է'), + (0x538, 'M', 'ը'), + (0x539, 'M', 'թ'), + (0x53A, 'M', 'ժ'), + (0x53B, 'M', 'ի'), + (0x53C, 'M', 'լ'), + (0x53D, 'M', 'խ'), + (0x53E, 'M', 'ծ'), + (0x53F, 'M', 'կ'), + (0x540, 'M', 'հ'), + (0x541, 'M', 'ձ'), + (0x542, 'M', 'ղ'), + (0x543, 'M', 'ճ'), + (0x544, 'M', 'մ'), + (0x545, 'M', 'յ'), + (0x546, 'M', 'ն'), + (0x547, 'M', 'շ'), + (0x548, 'M', 'ո'), + (0x549, 'M', 'չ'), + (0x54A, 'M', 'պ'), + (0x54B, 'M', 'ջ'), + (0x54C, 'M', 'ռ'), + (0x54D, 'M', 'ս'), + (0x54E, 'M', 'վ'), + (0x54F, 'M', 'տ'), + (0x550, 'M', 'ր'), + (0x551, 'M', 'ց'), + (0x552, 'M', 'ւ'), + (0x553, 'M', 'փ'), + (0x554, 'M', 'ք'), + (0x555, 'M', 'օ'), + (0x556, 'M', 'ֆ'), (0x557, 'X'), (0x559, 'V'), - (0x587, 'M', u'եւ'), + (0x587, 'M', 'եւ'), (0x588, 'V'), (0x58B, 'X'), (0x58D, 'V'), @@ -1046,11 +1057,12 @@ def _seg_9(): ] def _seg_10(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x675, 'M', u'اٴ'), - (0x676, 'M', u'وٴ'), - (0x677, 'M', u'ۇٴ'), - (0x678, 'M', u'يٴ'), + (0x675, 'M', 'اٴ'), + (0x676, 'M', 'وٴ'), + (0x677, 'M', 'ۇٴ'), + (0x678, 'M', 'يٴ'), (0x679, 'V'), (0x6DD, 'X'), (0x6DE, 'V'), @@ -1074,18 +1086,18 @@ def _seg_10(): (0x8A0, 'V'), (0x8B5, 'X'), (0x8B6, 'V'), - (0x8BE, 'X'), + (0x8C8, 'X'), (0x8D3, 'V'), (0x8E2, 'X'), (0x8E3, 'V'), - (0x958, 'M', u'क़'), - (0x959, 'M', u'ख़'), - (0x95A, 'M', u'ग़'), - (0x95B, 'M', u'ज़'), - (0x95C, 'M', u'ड़'), - (0x95D, 'M', u'ढ़'), - (0x95E, 'M', u'फ़'), - (0x95F, 'M', u'य़'), + (0x958, 'M', 'क़'), + (0x959, 'M', 'ख़'), + (0x95A, 'M', 'ग़'), + (0x95B, 'M', 'ज़'), + (0x95C, 'M', 'ड़'), + (0x95D, 'M', 'ढ़'), + (0x95E, 'M', 'फ़'), + (0x95F, 'M', 'य़'), (0x960, 'V'), (0x984, 'X'), (0x985, 'V'), @@ -1108,10 +1120,10 @@ def _seg_10(): (0x9CF, 'X'), (0x9D7, 'V'), (0x9D8, 'X'), - (0x9DC, 'M', u'ড়'), - (0x9DD, 'M', u'ঢ়'), + (0x9DC, 'M', 'ড়'), + (0x9DD, 'M', 'ঢ়'), (0x9DE, 'X'), - (0x9DF, 'M', u'য়'), + (0x9DF, 'M', 'য়'), (0x9E0, 'V'), (0x9E4, 'X'), (0x9E6, 'V'), @@ -1127,10 +1139,10 @@ def _seg_10(): (0xA2A, 'V'), (0xA31, 'X'), (0xA32, 'V'), - (0xA33, 'M', u'ਲ਼'), + (0xA33, 'M', 'ਲ਼'), (0xA34, 'X'), (0xA35, 'V'), - (0xA36, 'M', u'ਸ਼'), + (0xA36, 'M', 'ਸ਼'), (0xA37, 'X'), (0xA38, 'V'), (0xA3A, 'X'), @@ -1144,16 +1156,17 @@ def _seg_10(): (0xA4E, 'X'), (0xA51, 'V'), (0xA52, 'X'), - (0xA59, 'M', u'ਖ਼'), - (0xA5A, 'M', u'ਗ਼'), - (0xA5B, 'M', u'ਜ਼'), + (0xA59, 'M', 'ਖ਼'), + (0xA5A, 'M', 'ਗ਼'), + (0xA5B, 'M', 'ਜ਼'), ] def _seg_11(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ (0xA5C, 'V'), (0xA5D, 'X'), - (0xA5E, 'M', u'ਫ਼'), + (0xA5E, 'M', 'ਫ਼'), (0xA5F, 'X'), (0xA66, 'V'), (0xA77, 'X'), @@ -1205,10 +1218,10 @@ def _seg_11(): (0xB49, 'X'), (0xB4B, 'V'), (0xB4E, 'X'), - (0xB56, 'V'), + (0xB55, 'V'), (0xB58, 'X'), - (0xB5C, 'M', u'ଡ଼'), - (0xB5D, 'M', u'ଢ଼'), + (0xB5C, 'M', 'ଡ଼'), + (0xB5D, 'M', 'ଢ଼'), (0xB5E, 'X'), (0xB5F, 'V'), (0xB64, 'X'), @@ -1254,6 +1267,7 @@ def _seg_11(): ] def _seg_12(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ (0xC29, 'X'), (0xC2A, 'V'), @@ -1272,7 +1286,7 @@ def _seg_12(): (0xC64, 'X'), (0xC66, 'V'), (0xC70, 'X'), - (0xC78, 'V'), + (0xC77, 'V'), (0xC8D, 'X'), (0xC8E, 'V'), (0xC91, 'X'), @@ -1299,8 +1313,6 @@ def _seg_12(): (0xCF1, 'V'), (0xCF3, 'X'), (0xD00, 'V'), - (0xD04, 'X'), - (0xD05, 'V'), (0xD0D, 'X'), (0xD0E, 'V'), (0xD11, 'X'), @@ -1314,7 +1326,7 @@ def _seg_12(): (0xD64, 'X'), (0xD66, 'V'), (0xD80, 'X'), - (0xD82, 'V'), + (0xD81, 'V'), (0xD84, 'X'), (0xD85, 'V'), (0xD97, 'X'), @@ -1339,7 +1351,7 @@ def _seg_12(): (0xDF2, 'V'), (0xDF5, 'X'), (0xE01, 'V'), - (0xE33, 'M', u'ํา'), + (0xE33, 'M', 'ํา'), (0xE34, 'V'), (0xE3B, 'X'), (0xE3F, 'V'), @@ -1348,33 +1360,20 @@ def _seg_12(): (0xE83, 'X'), (0xE84, 'V'), (0xE85, 'X'), - (0xE87, 'V'), - (0xE89, 'X'), - (0xE8A, 'V'), + (0xE86, 'V'), (0xE8B, 'X'), - (0xE8D, 'V'), - (0xE8E, 'X'), - (0xE94, 'V'), - ] - -def _seg_13(): - return [ - (0xE98, 'X'), - (0xE99, 'V'), - (0xEA0, 'X'), - (0xEA1, 'V'), + (0xE8C, 'V'), (0xEA4, 'X'), (0xEA5, 'V'), (0xEA6, 'X'), (0xEA7, 'V'), - (0xEA8, 'X'), - (0xEAA, 'V'), - (0xEAC, 'X'), - (0xEAD, 'V'), - (0xEB3, 'M', u'ໍາ'), + (0xEB3, 'M', 'ໍາ'), (0xEB4, 'V'), - (0xEBA, 'X'), - (0xEBB, 'V'), + ] + +def _seg_13(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ (0xEBE, 'X'), (0xEC0, 'V'), (0xEC5, 'X'), @@ -1384,52 +1383,52 @@ def _seg_13(): (0xECE, 'X'), (0xED0, 'V'), (0xEDA, 'X'), - (0xEDC, 'M', u'ຫນ'), - (0xEDD, 'M', u'ຫມ'), + (0xEDC, 'M', 'ຫນ'), + (0xEDD, 'M', 'ຫມ'), (0xEDE, 'V'), (0xEE0, 'X'), (0xF00, 'V'), - (0xF0C, 'M', u'་'), + (0xF0C, 'M', '་'), (0xF0D, 'V'), - (0xF43, 'M', u'གྷ'), + (0xF43, 'M', 'གྷ'), (0xF44, 'V'), (0xF48, 'X'), (0xF49, 'V'), - (0xF4D, 'M', u'ཌྷ'), + (0xF4D, 'M', 'ཌྷ'), (0xF4E, 'V'), - (0xF52, 'M', u'དྷ'), + (0xF52, 'M', 'དྷ'), (0xF53, 'V'), - (0xF57, 'M', u'བྷ'), + (0xF57, 'M', 'བྷ'), (0xF58, 'V'), - (0xF5C, 'M', u'ཛྷ'), + (0xF5C, 'M', 'ཛྷ'), (0xF5D, 'V'), - (0xF69, 'M', u'ཀྵ'), + (0xF69, 'M', 'ཀྵ'), (0xF6A, 'V'), (0xF6D, 'X'), (0xF71, 'V'), - (0xF73, 'M', u'ཱི'), + (0xF73, 'M', 'ཱི'), (0xF74, 'V'), - (0xF75, 'M', u'ཱུ'), - (0xF76, 'M', u'ྲྀ'), - (0xF77, 'M', u'ྲཱྀ'), - (0xF78, 'M', u'ླྀ'), - (0xF79, 'M', u'ླཱྀ'), + (0xF75, 'M', 'ཱུ'), + (0xF76, 'M', 'ྲྀ'), + (0xF77, 'M', 'ྲཱྀ'), + (0xF78, 'M', 'ླྀ'), + (0xF79, 'M', 'ླཱྀ'), (0xF7A, 'V'), - (0xF81, 'M', u'ཱྀ'), + (0xF81, 'M', 'ཱྀ'), (0xF82, 'V'), - (0xF93, 'M', u'ྒྷ'), + (0xF93, 'M', 'ྒྷ'), (0xF94, 'V'), (0xF98, 'X'), (0xF99, 'V'), - (0xF9D, 'M', u'ྜྷ'), + (0xF9D, 'M', 'ྜྷ'), (0xF9E, 'V'), - (0xFA2, 'M', u'ྡྷ'), + (0xFA2, 'M', 'ྡྷ'), (0xFA3, 'V'), - (0xFA7, 'M', u'ྦྷ'), + (0xFA7, 'M', 'ྦྷ'), (0xFA8, 'V'), - (0xFAC, 'M', u'ྫྷ'), + (0xFAC, 'M', 'ྫྷ'), (0xFAD, 'V'), - (0xFB9, 'M', u'ྐྵ'), + (0xFB9, 'M', 'ྐྵ'), (0xFBA, 'V'), (0xFBD, 'X'), (0xFBE, 'V'), @@ -1438,12 +1437,12 @@ def _seg_13(): (0xFDB, 'X'), (0x1000, 'V'), (0x10A0, 'X'), - (0x10C7, 'M', u'ⴧ'), + (0x10C7, 'M', 'ⴧ'), (0x10C8, 'X'), - (0x10CD, 'M', u'ⴭ'), + (0x10CD, 'M', 'ⴭ'), (0x10CE, 'X'), (0x10D0, 'V'), - (0x10FC, 'M', u'ნ'), + (0x10FC, 'M', 'ნ'), (0x10FD, 'V'), (0x115F, 'X'), (0x1161, 'V'), @@ -1459,10 +1458,6 @@ def _seg_13(): (0x1260, 'V'), (0x1289, 'X'), (0x128A, 'V'), - ] - -def _seg_14(): - return [ (0x128E, 'X'), (0x1290, 'V'), (0x12B1, 'X'), @@ -1479,6 +1474,11 @@ def _seg_14(): (0x12D8, 'V'), (0x1311, 'X'), (0x1312, 'V'), + ] + +def _seg_14(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ (0x1316, 'X'), (0x1318, 'V'), (0x135B, 'X'), @@ -1488,12 +1488,12 @@ def _seg_14(): (0x139A, 'X'), (0x13A0, 'V'), (0x13F6, 'X'), - (0x13F8, 'M', u'Ᏸ'), - (0x13F9, 'M', u'Ᏹ'), - (0x13FA, 'M', u'Ᏺ'), - (0x13FB, 'M', u'Ᏻ'), - (0x13FC, 'M', u'Ᏼ'), - (0x13FD, 'M', u'Ᏽ'), + (0x13F8, 'M', 'Ᏸ'), + (0x13F9, 'M', 'Ᏹ'), + (0x13FA, 'M', 'Ᏺ'), + (0x13FB, 'M', 'Ᏻ'), + (0x13FC, 'M', 'Ᏼ'), + (0x13FD, 'M', 'Ᏽ'), (0x13FE, 'X'), (0x1400, 'V'), (0x1680, 'X'), @@ -1563,15 +1563,11 @@ def _seg_14(): (0x1A7F, 'V'), (0x1A8A, 'X'), (0x1A90, 'V'), - ] - -def _seg_15(): - return [ (0x1A9A, 'X'), (0x1AA0, 'V'), (0x1AAE, 'X'), (0x1AB0, 'V'), - (0x1ABF, 'X'), + (0x1AC1, 'X'), (0x1B00, 'V'), (0x1B4C, 'X'), (0x1B50, 'V'), @@ -1583,1148 +1579,1208 @@ def _seg_15(): (0x1C3B, 'V'), (0x1C4A, 'X'), (0x1C4D, 'V'), - (0x1C80, 'M', u'в'), - (0x1C81, 'M', u'д'), - (0x1C82, 'M', u'о'), - (0x1C83, 'M', u'с'), - (0x1C84, 'M', u'т'), - (0x1C86, 'M', u'ъ'), - (0x1C87, 'M', u'ѣ'), - (0x1C88, 'M', u'ꙋ'), + ] + +def _seg_15(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ + (0x1C80, 'M', 'в'), + (0x1C81, 'M', 'д'), + (0x1C82, 'M', 'о'), + (0x1C83, 'M', 'с'), + (0x1C84, 'M', 'т'), + (0x1C86, 'M', 'ъ'), + (0x1C87, 'M', 'ѣ'), + (0x1C88, 'M', 'ꙋ'), (0x1C89, 'X'), + (0x1C90, 'M', 'ა'), + (0x1C91, 'M', 'ბ'), + (0x1C92, 'M', 'გ'), + (0x1C93, 'M', 'დ'), + (0x1C94, 'M', 'ე'), + (0x1C95, 'M', 'ვ'), + (0x1C96, 'M', 'ზ'), + (0x1C97, 'M', 'თ'), + (0x1C98, 'M', 'ი'), + (0x1C99, 'M', 'კ'), + (0x1C9A, 'M', 'ლ'), + (0x1C9B, 'M', 'მ'), + (0x1C9C, 'M', 'ნ'), + (0x1C9D, 'M', 'ო'), + (0x1C9E, 'M', 'პ'), + (0x1C9F, 'M', 'ჟ'), + (0x1CA0, 'M', 'რ'), + (0x1CA1, 'M', 'ს'), + (0x1CA2, 'M', 'ტ'), + (0x1CA3, 'M', 'უ'), + (0x1CA4, 'M', 'ფ'), + (0x1CA5, 'M', 'ქ'), + (0x1CA6, 'M', 'ღ'), + (0x1CA7, 'M', 'ყ'), + (0x1CA8, 'M', 'შ'), + (0x1CA9, 'M', 'ჩ'), + (0x1CAA, 'M', 'ც'), + (0x1CAB, 'M', 'ძ'), + (0x1CAC, 'M', 'წ'), + (0x1CAD, 'M', 'ჭ'), + (0x1CAE, 'M', 'ხ'), + (0x1CAF, 'M', 'ჯ'), + (0x1CB0, 'M', 'ჰ'), + (0x1CB1, 'M', 'ჱ'), + (0x1CB2, 'M', 'ჲ'), + (0x1CB3, 'M', 'ჳ'), + (0x1CB4, 'M', 'ჴ'), + (0x1CB5, 'M', 'ჵ'), + (0x1CB6, 'M', 'ჶ'), + (0x1CB7, 'M', 'ჷ'), + (0x1CB8, 'M', 'ჸ'), + (0x1CB9, 'M', 'ჹ'), + (0x1CBA, 'M', 'ჺ'), + (0x1CBB, 'X'), + (0x1CBD, 'M', 'ჽ'), + (0x1CBE, 'M', 'ჾ'), + (0x1CBF, 'M', 'ჿ'), (0x1CC0, 'V'), (0x1CC8, 'X'), (0x1CD0, 'V'), - (0x1CFA, 'X'), + (0x1CFB, 'X'), (0x1D00, 'V'), - (0x1D2C, 'M', u'a'), - (0x1D2D, 'M', u'æ'), - (0x1D2E, 'M', u'b'), + (0x1D2C, 'M', 'a'), + (0x1D2D, 'M', 'æ'), + (0x1D2E, 'M', 'b'), (0x1D2F, 'V'), - (0x1D30, 'M', u'd'), - (0x1D31, 'M', u'e'), - (0x1D32, 'M', u'ǝ'), - (0x1D33, 'M', u'g'), - (0x1D34, 'M', u'h'), - (0x1D35, 'M', u'i'), - (0x1D36, 'M', u'j'), - (0x1D37, 'M', u'k'), - (0x1D38, 'M', u'l'), - (0x1D39, 'M', u'm'), - (0x1D3A, 'M', u'n'), + (0x1D30, 'M', 'd'), + (0x1D31, 'M', 'e'), + (0x1D32, 'M', 'ǝ'), + (0x1D33, 'M', 'g'), + (0x1D34, 'M', 'h'), + (0x1D35, 'M', 'i'), + (0x1D36, 'M', 'j'), + (0x1D37, 'M', 'k'), + (0x1D38, 'M', 'l'), + (0x1D39, 'M', 'm'), + (0x1D3A, 'M', 'n'), (0x1D3B, 'V'), - (0x1D3C, 'M', u'o'), - (0x1D3D, 'M', u'ȣ'), - (0x1D3E, 'M', u'p'), - (0x1D3F, 'M', u'r'), - (0x1D40, 'M', u't'), - (0x1D41, 'M', u'u'), - (0x1D42, 'M', u'w'), - (0x1D43, 'M', u'a'), - (0x1D44, 'M', u'ɐ'), - (0x1D45, 'M', u'ɑ'), - (0x1D46, 'M', u'ᴂ'), - (0x1D47, 'M', u'b'), - (0x1D48, 'M', u'd'), - (0x1D49, 'M', u'e'), - (0x1D4A, 'M', u'ə'), - (0x1D4B, 'M', u'ɛ'), - (0x1D4C, 'M', u'ɜ'), - (0x1D4D, 'M', u'g'), + (0x1D3C, 'M', 'o'), + (0x1D3D, 'M', 'ȣ'), + (0x1D3E, 'M', 'p'), + (0x1D3F, 'M', 'r'), + (0x1D40, 'M', 't'), + (0x1D41, 'M', 'u'), + (0x1D42, 'M', 'w'), + (0x1D43, 'M', 'a'), + (0x1D44, 'M', 'ɐ'), + (0x1D45, 'M', 'ɑ'), + (0x1D46, 'M', 'ᴂ'), + (0x1D47, 'M', 'b'), + (0x1D48, 'M', 'd'), + (0x1D49, 'M', 'e'), + (0x1D4A, 'M', 'ə'), + (0x1D4B, 'M', 'ɛ'), + (0x1D4C, 'M', 'ɜ'), + (0x1D4D, 'M', 'g'), (0x1D4E, 'V'), - (0x1D4F, 'M', u'k'), - (0x1D50, 'M', u'm'), - (0x1D51, 'M', u'ŋ'), - (0x1D52, 'M', u'o'), - (0x1D53, 'M', u'ɔ'), - (0x1D54, 'M', u'ᴖ'), - (0x1D55, 'M', u'ᴗ'), - (0x1D56, 'M', u'p'), - (0x1D57, 'M', u't'), - (0x1D58, 'M', u'u'), - (0x1D59, 'M', u'ᴝ'), - (0x1D5A, 'M', u'ɯ'), - (0x1D5B, 'M', u'v'), - (0x1D5C, 'M', u'ᴥ'), - (0x1D5D, 'M', u'β'), - (0x1D5E, 'M', u'γ'), - (0x1D5F, 'M', u'δ'), - (0x1D60, 'M', u'φ'), - (0x1D61, 'M', u'χ'), - (0x1D62, 'M', u'i'), - (0x1D63, 'M', u'r'), - (0x1D64, 'M', u'u'), - (0x1D65, 'M', u'v'), - (0x1D66, 'M', u'β'), - (0x1D67, 'M', u'γ'), - (0x1D68, 'M', u'ρ'), - (0x1D69, 'M', u'φ'), - (0x1D6A, 'M', u'χ'), - (0x1D6B, 'V'), - (0x1D78, 'M', u'н'), - (0x1D79, 'V'), - (0x1D9B, 'M', u'ɒ'), - (0x1D9C, 'M', u'c'), - (0x1D9D, 'M', u'ɕ'), - (0x1D9E, 'M', u'ð'), + (0x1D4F, 'M', 'k'), + (0x1D50, 'M', 'm'), + (0x1D51, 'M', 'ŋ'), + (0x1D52, 'M', 'o'), ] def _seg_16(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x1D9F, 'M', u'ɜ'), - (0x1DA0, 'M', u'f'), - (0x1DA1, 'M', u'ɟ'), - (0x1DA2, 'M', u'ɡ'), - (0x1DA3, 'M', u'ɥ'), - (0x1DA4, 'M', u'ɨ'), - (0x1DA5, 'M', u'ɩ'), - (0x1DA6, 'M', u'ɪ'), - (0x1DA7, 'M', u'ᵻ'), - (0x1DA8, 'M', u'ʝ'), - (0x1DA9, 'M', u'ɭ'), - (0x1DAA, 'M', u'ᶅ'), - (0x1DAB, 'M', u'ʟ'), - (0x1DAC, 'M', u'ɱ'), - (0x1DAD, 'M', u'ɰ'), - (0x1DAE, 'M', u'ɲ'), - (0x1DAF, 'M', u'ɳ'), - (0x1DB0, 'M', u'ɴ'), - (0x1DB1, 'M', u'ɵ'), - (0x1DB2, 'M', u'ɸ'), - (0x1DB3, 'M', u'ʂ'), - (0x1DB4, 'M', u'ʃ'), - (0x1DB5, 'M', u'ƫ'), - (0x1DB6, 'M', u'ʉ'), - (0x1DB7, 'M', u'ʊ'), - (0x1DB8, 'M', u'ᴜ'), - (0x1DB9, 'M', u'ʋ'), - (0x1DBA, 'M', u'ʌ'), - (0x1DBB, 'M', u'z'), - (0x1DBC, 'M', u'ʐ'), - (0x1DBD, 'M', u'ʑ'), - (0x1DBE, 'M', u'ʒ'), - (0x1DBF, 'M', u'θ'), + (0x1D53, 'M', 'ɔ'), + (0x1D54, 'M', 'ᴖ'), + (0x1D55, 'M', 'ᴗ'), + (0x1D56, 'M', 'p'), + (0x1D57, 'M', 't'), + (0x1D58, 'M', 'u'), + (0x1D59, 'M', 'ᴝ'), + (0x1D5A, 'M', 'ɯ'), + (0x1D5B, 'M', 'v'), + (0x1D5C, 'M', 'ᴥ'), + (0x1D5D, 'M', 'β'), + (0x1D5E, 'M', 'γ'), + (0x1D5F, 'M', 'δ'), + (0x1D60, 'M', 'φ'), + (0x1D61, 'M', 'χ'), + (0x1D62, 'M', 'i'), + (0x1D63, 'M', 'r'), + (0x1D64, 'M', 'u'), + (0x1D65, 'M', 'v'), + (0x1D66, 'M', 'β'), + (0x1D67, 'M', 'γ'), + (0x1D68, 'M', 'ρ'), + (0x1D69, 'M', 'φ'), + (0x1D6A, 'M', 'χ'), + (0x1D6B, 'V'), + (0x1D78, 'M', 'н'), + (0x1D79, 'V'), + (0x1D9B, 'M', 'ɒ'), + (0x1D9C, 'M', 'c'), + (0x1D9D, 'M', 'ɕ'), + (0x1D9E, 'M', 'ð'), + (0x1D9F, 'M', 'ɜ'), + (0x1DA0, 'M', 'f'), + (0x1DA1, 'M', 'ɟ'), + (0x1DA2, 'M', 'ɡ'), + (0x1DA3, 'M', 'ɥ'), + (0x1DA4, 'M', 'ɨ'), + (0x1DA5, 'M', 'ɩ'), + (0x1DA6, 'M', 'ɪ'), + (0x1DA7, 'M', 'ᵻ'), + (0x1DA8, 'M', 'ʝ'), + (0x1DA9, 'M', 'ɭ'), + (0x1DAA, 'M', 'ᶅ'), + (0x1DAB, 'M', 'ʟ'), + (0x1DAC, 'M', 'ɱ'), + (0x1DAD, 'M', 'ɰ'), + (0x1DAE, 'M', 'ɲ'), + (0x1DAF, 'M', 'ɳ'), + (0x1DB0, 'M', 'ɴ'), + (0x1DB1, 'M', 'ɵ'), + (0x1DB2, 'M', 'ɸ'), + (0x1DB3, 'M', 'ʂ'), + (0x1DB4, 'M', 'ʃ'), + (0x1DB5, 'M', 'ƫ'), + (0x1DB6, 'M', 'ʉ'), + (0x1DB7, 'M', 'ʊ'), + (0x1DB8, 'M', 'ᴜ'), + (0x1DB9, 'M', 'ʋ'), + (0x1DBA, 'M', 'ʌ'), + (0x1DBB, 'M', 'z'), + (0x1DBC, 'M', 'ʐ'), + (0x1DBD, 'M', 'ʑ'), + (0x1DBE, 'M', 'ʒ'), + (0x1DBF, 'M', 'θ'), (0x1DC0, 'V'), (0x1DFA, 'X'), (0x1DFB, 'V'), - (0x1E00, 'M', u'ḁ'), + (0x1E00, 'M', 'ḁ'), (0x1E01, 'V'), - (0x1E02, 'M', u'ḃ'), + (0x1E02, 'M', 'ḃ'), (0x1E03, 'V'), - (0x1E04, 'M', u'ḅ'), + (0x1E04, 'M', 'ḅ'), (0x1E05, 'V'), - (0x1E06, 'M', u'ḇ'), + (0x1E06, 'M', 'ḇ'), (0x1E07, 'V'), - (0x1E08, 'M', u'ḉ'), + (0x1E08, 'M', 'ḉ'), (0x1E09, 'V'), - (0x1E0A, 'M', u'ḋ'), + (0x1E0A, 'M', 'ḋ'), (0x1E0B, 'V'), - (0x1E0C, 'M', u'ḍ'), + (0x1E0C, 'M', 'ḍ'), (0x1E0D, 'V'), - (0x1E0E, 'M', u'ḏ'), + (0x1E0E, 'M', 'ḏ'), (0x1E0F, 'V'), - (0x1E10, 'M', u'ḑ'), + (0x1E10, 'M', 'ḑ'), (0x1E11, 'V'), - (0x1E12, 'M', u'ḓ'), + (0x1E12, 'M', 'ḓ'), (0x1E13, 'V'), - (0x1E14, 'M', u'ḕ'), + (0x1E14, 'M', 'ḕ'), (0x1E15, 'V'), - (0x1E16, 'M', u'ḗ'), + (0x1E16, 'M', 'ḗ'), (0x1E17, 'V'), - (0x1E18, 'M', u'ḙ'), + (0x1E18, 'M', 'ḙ'), (0x1E19, 'V'), - (0x1E1A, 'M', u'ḛ'), + (0x1E1A, 'M', 'ḛ'), (0x1E1B, 'V'), - (0x1E1C, 'M', u'ḝ'), + (0x1E1C, 'M', 'ḝ'), (0x1E1D, 'V'), - (0x1E1E, 'M', u'ḟ'), + (0x1E1E, 'M', 'ḟ'), (0x1E1F, 'V'), - (0x1E20, 'M', u'ḡ'), - (0x1E21, 'V'), - (0x1E22, 'M', u'ḣ'), - (0x1E23, 'V'), - (0x1E24, 'M', u'ḥ'), - (0x1E25, 'V'), - (0x1E26, 'M', u'ḧ'), - (0x1E27, 'V'), - (0x1E28, 'M', u'ḩ'), - (0x1E29, 'V'), - (0x1E2A, 'M', u'ḫ'), - (0x1E2B, 'V'), - (0x1E2C, 'M', u'ḭ'), - (0x1E2D, 'V'), - (0x1E2E, 'M', u'ḯ'), - (0x1E2F, 'V'), - (0x1E30, 'M', u'ḱ'), - (0x1E31, 'V'), - (0x1E32, 'M', u'ḳ'), - (0x1E33, 'V'), - (0x1E34, 'M', u'ḵ'), - (0x1E35, 'V'), - (0x1E36, 'M', u'ḷ'), - (0x1E37, 'V'), - (0x1E38, 'M', u'ḹ'), - (0x1E39, 'V'), - (0x1E3A, 'M', u'ḻ'), - (0x1E3B, 'V'), - (0x1E3C, 'M', u'ḽ'), - (0x1E3D, 'V'), - (0x1E3E, 'M', u'ḿ'), - (0x1E3F, 'V'), + (0x1E20, 'M', 'ḡ'), ] def _seg_17(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x1E40, 'M', u'ṁ'), + (0x1E21, 'V'), + (0x1E22, 'M', 'ḣ'), + (0x1E23, 'V'), + (0x1E24, 'M', 'ḥ'), + (0x1E25, 'V'), + (0x1E26, 'M', 'ḧ'), + (0x1E27, 'V'), + (0x1E28, 'M', 'ḩ'), + (0x1E29, 'V'), + (0x1E2A, 'M', 'ḫ'), + (0x1E2B, 'V'), + (0x1E2C, 'M', 'ḭ'), + (0x1E2D, 'V'), + (0x1E2E, 'M', 'ḯ'), + (0x1E2F, 'V'), + (0x1E30, 'M', 'ḱ'), + (0x1E31, 'V'), + (0x1E32, 'M', 'ḳ'), + (0x1E33, 'V'), + (0x1E34, 'M', 'ḵ'), + (0x1E35, 'V'), + (0x1E36, 'M', 'ḷ'), + (0x1E37, 'V'), + (0x1E38, 'M', 'ḹ'), + (0x1E39, 'V'), + (0x1E3A, 'M', 'ḻ'), + (0x1E3B, 'V'), + (0x1E3C, 'M', 'ḽ'), + (0x1E3D, 'V'), + (0x1E3E, 'M', 'ḿ'), + (0x1E3F, 'V'), + (0x1E40, 'M', 'ṁ'), (0x1E41, 'V'), - (0x1E42, 'M', u'ṃ'), + (0x1E42, 'M', 'ṃ'), (0x1E43, 'V'), - (0x1E44, 'M', u'ṅ'), + (0x1E44, 'M', 'ṅ'), (0x1E45, 'V'), - (0x1E46, 'M', u'ṇ'), + (0x1E46, 'M', 'ṇ'), (0x1E47, 'V'), - (0x1E48, 'M', u'ṉ'), + (0x1E48, 'M', 'ṉ'), (0x1E49, 'V'), - (0x1E4A, 'M', u'ṋ'), + (0x1E4A, 'M', 'ṋ'), (0x1E4B, 'V'), - (0x1E4C, 'M', u'ṍ'), + (0x1E4C, 'M', 'ṍ'), (0x1E4D, 'V'), - (0x1E4E, 'M', u'ṏ'), + (0x1E4E, 'M', 'ṏ'), (0x1E4F, 'V'), - (0x1E50, 'M', u'ṑ'), + (0x1E50, 'M', 'ṑ'), (0x1E51, 'V'), - (0x1E52, 'M', u'ṓ'), + (0x1E52, 'M', 'ṓ'), (0x1E53, 'V'), - (0x1E54, 'M', u'ṕ'), + (0x1E54, 'M', 'ṕ'), (0x1E55, 'V'), - (0x1E56, 'M', u'ṗ'), + (0x1E56, 'M', 'ṗ'), (0x1E57, 'V'), - (0x1E58, 'M', u'ṙ'), + (0x1E58, 'M', 'ṙ'), (0x1E59, 'V'), - (0x1E5A, 'M', u'ṛ'), + (0x1E5A, 'M', 'ṛ'), (0x1E5B, 'V'), - (0x1E5C, 'M', u'ṝ'), + (0x1E5C, 'M', 'ṝ'), (0x1E5D, 'V'), - (0x1E5E, 'M', u'ṟ'), + (0x1E5E, 'M', 'ṟ'), (0x1E5F, 'V'), - (0x1E60, 'M', u'ṡ'), + (0x1E60, 'M', 'ṡ'), (0x1E61, 'V'), - (0x1E62, 'M', u'ṣ'), + (0x1E62, 'M', 'ṣ'), (0x1E63, 'V'), - (0x1E64, 'M', u'ṥ'), + (0x1E64, 'M', 'ṥ'), (0x1E65, 'V'), - (0x1E66, 'M', u'ṧ'), + (0x1E66, 'M', 'ṧ'), (0x1E67, 'V'), - (0x1E68, 'M', u'ṩ'), + (0x1E68, 'M', 'ṩ'), (0x1E69, 'V'), - (0x1E6A, 'M', u'ṫ'), + (0x1E6A, 'M', 'ṫ'), (0x1E6B, 'V'), - (0x1E6C, 'M', u'ṭ'), + (0x1E6C, 'M', 'ṭ'), (0x1E6D, 'V'), - (0x1E6E, 'M', u'ṯ'), + (0x1E6E, 'M', 'ṯ'), (0x1E6F, 'V'), - (0x1E70, 'M', u'ṱ'), + (0x1E70, 'M', 'ṱ'), (0x1E71, 'V'), - (0x1E72, 'M', u'ṳ'), + (0x1E72, 'M', 'ṳ'), (0x1E73, 'V'), - (0x1E74, 'M', u'ṵ'), + (0x1E74, 'M', 'ṵ'), (0x1E75, 'V'), - (0x1E76, 'M', u'ṷ'), + (0x1E76, 'M', 'ṷ'), (0x1E77, 'V'), - (0x1E78, 'M', u'ṹ'), + (0x1E78, 'M', 'ṹ'), (0x1E79, 'V'), - (0x1E7A, 'M', u'ṻ'), + (0x1E7A, 'M', 'ṻ'), (0x1E7B, 'V'), - (0x1E7C, 'M', u'ṽ'), + (0x1E7C, 'M', 'ṽ'), (0x1E7D, 'V'), - (0x1E7E, 'M', u'ṿ'), + (0x1E7E, 'M', 'ṿ'), (0x1E7F, 'V'), - (0x1E80, 'M', u'ẁ'), + (0x1E80, 'M', 'ẁ'), (0x1E81, 'V'), - (0x1E82, 'M', u'ẃ'), + (0x1E82, 'M', 'ẃ'), (0x1E83, 'V'), - (0x1E84, 'M', u'ẅ'), - (0x1E85, 'V'), - (0x1E86, 'M', u'ẇ'), - (0x1E87, 'V'), - (0x1E88, 'M', u'ẉ'), - (0x1E89, 'V'), - (0x1E8A, 'M', u'ẋ'), - (0x1E8B, 'V'), - (0x1E8C, 'M', u'ẍ'), - (0x1E8D, 'V'), - (0x1E8E, 'M', u'ẏ'), - (0x1E8F, 'V'), - (0x1E90, 'M', u'ẑ'), - (0x1E91, 'V'), - (0x1E92, 'M', u'ẓ'), - (0x1E93, 'V'), - (0x1E94, 'M', u'ẕ'), - (0x1E95, 'V'), - (0x1E9A, 'M', u'aʾ'), - (0x1E9B, 'M', u'ṡ'), - (0x1E9C, 'V'), - (0x1E9E, 'M', u'ss'), - (0x1E9F, 'V'), - (0x1EA0, 'M', u'ạ'), - (0x1EA1, 'V'), - (0x1EA2, 'M', u'ả'), - (0x1EA3, 'V'), - (0x1EA4, 'M', u'ấ'), - (0x1EA5, 'V'), - (0x1EA6, 'M', u'ầ'), - (0x1EA7, 'V'), - (0x1EA8, 'M', u'ẩ'), + (0x1E84, 'M', 'ẅ'), ] def _seg_18(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ + (0x1E85, 'V'), + (0x1E86, 'M', 'ẇ'), + (0x1E87, 'V'), + (0x1E88, 'M', 'ẉ'), + (0x1E89, 'V'), + (0x1E8A, 'M', 'ẋ'), + (0x1E8B, 'V'), + (0x1E8C, 'M', 'ẍ'), + (0x1E8D, 'V'), + (0x1E8E, 'M', 'ẏ'), + (0x1E8F, 'V'), + (0x1E90, 'M', 'ẑ'), + (0x1E91, 'V'), + (0x1E92, 'M', 'ẓ'), + (0x1E93, 'V'), + (0x1E94, 'M', 'ẕ'), + (0x1E95, 'V'), + (0x1E9A, 'M', 'aʾ'), + (0x1E9B, 'M', 'ṡ'), + (0x1E9C, 'V'), + (0x1E9E, 'M', 'ss'), + (0x1E9F, 'V'), + (0x1EA0, 'M', 'ạ'), + (0x1EA1, 'V'), + (0x1EA2, 'M', 'ả'), + (0x1EA3, 'V'), + (0x1EA4, 'M', 'ấ'), + (0x1EA5, 'V'), + (0x1EA6, 'M', 'ầ'), + (0x1EA7, 'V'), + (0x1EA8, 'M', 'ẩ'), (0x1EA9, 'V'), - (0x1EAA, 'M', u'ẫ'), + (0x1EAA, 'M', 'ẫ'), (0x1EAB, 'V'), - (0x1EAC, 'M', u'ậ'), + (0x1EAC, 'M', 'ậ'), (0x1EAD, 'V'), - (0x1EAE, 'M', u'ắ'), + (0x1EAE, 'M', 'ắ'), (0x1EAF, 'V'), - (0x1EB0, 'M', u'ằ'), + (0x1EB0, 'M', 'ằ'), (0x1EB1, 'V'), - (0x1EB2, 'M', u'ẳ'), + (0x1EB2, 'M', 'ẳ'), (0x1EB3, 'V'), - (0x1EB4, 'M', u'ẵ'), + (0x1EB4, 'M', 'ẵ'), (0x1EB5, 'V'), - (0x1EB6, 'M', u'ặ'), + (0x1EB6, 'M', 'ặ'), (0x1EB7, 'V'), - (0x1EB8, 'M', u'ẹ'), + (0x1EB8, 'M', 'ẹ'), (0x1EB9, 'V'), - (0x1EBA, 'M', u'ẻ'), + (0x1EBA, 'M', 'ẻ'), (0x1EBB, 'V'), - (0x1EBC, 'M', u'ẽ'), + (0x1EBC, 'M', 'ẽ'), (0x1EBD, 'V'), - (0x1EBE, 'M', u'ế'), + (0x1EBE, 'M', 'ế'), (0x1EBF, 'V'), - (0x1EC0, 'M', u'ề'), + (0x1EC0, 'M', 'ề'), (0x1EC1, 'V'), - (0x1EC2, 'M', u'ể'), + (0x1EC2, 'M', 'ể'), (0x1EC3, 'V'), - (0x1EC4, 'M', u'ễ'), + (0x1EC4, 'M', 'ễ'), (0x1EC5, 'V'), - (0x1EC6, 'M', u'ệ'), + (0x1EC6, 'M', 'ệ'), (0x1EC7, 'V'), - (0x1EC8, 'M', u'ỉ'), + (0x1EC8, 'M', 'ỉ'), (0x1EC9, 'V'), - (0x1ECA, 'M', u'ị'), + (0x1ECA, 'M', 'ị'), (0x1ECB, 'V'), - (0x1ECC, 'M', u'ọ'), + (0x1ECC, 'M', 'ọ'), (0x1ECD, 'V'), - (0x1ECE, 'M', u'ỏ'), + (0x1ECE, 'M', 'ỏ'), (0x1ECF, 'V'), - (0x1ED0, 'M', u'ố'), + (0x1ED0, 'M', 'ố'), (0x1ED1, 'V'), - (0x1ED2, 'M', u'ồ'), + (0x1ED2, 'M', 'ồ'), (0x1ED3, 'V'), - (0x1ED4, 'M', u'ổ'), + (0x1ED4, 'M', 'ổ'), (0x1ED5, 'V'), - (0x1ED6, 'M', u'ỗ'), + (0x1ED6, 'M', 'ỗ'), (0x1ED7, 'V'), - (0x1ED8, 'M', u'ộ'), + (0x1ED8, 'M', 'ộ'), (0x1ED9, 'V'), - (0x1EDA, 'M', u'ớ'), + (0x1EDA, 'M', 'ớ'), (0x1EDB, 'V'), - (0x1EDC, 'M', u'ờ'), + (0x1EDC, 'M', 'ờ'), (0x1EDD, 'V'), - (0x1EDE, 'M', u'ở'), + (0x1EDE, 'M', 'ở'), (0x1EDF, 'V'), - (0x1EE0, 'M', u'ỡ'), + (0x1EE0, 'M', 'ỡ'), (0x1EE1, 'V'), - (0x1EE2, 'M', u'ợ'), + (0x1EE2, 'M', 'ợ'), (0x1EE3, 'V'), - (0x1EE4, 'M', u'ụ'), + (0x1EE4, 'M', 'ụ'), (0x1EE5, 'V'), - (0x1EE6, 'M', u'ủ'), + (0x1EE6, 'M', 'ủ'), (0x1EE7, 'V'), - (0x1EE8, 'M', u'ứ'), + (0x1EE8, 'M', 'ứ'), (0x1EE9, 'V'), - (0x1EEA, 'M', u'ừ'), + (0x1EEA, 'M', 'ừ'), (0x1EEB, 'V'), - (0x1EEC, 'M', u'ử'), + (0x1EEC, 'M', 'ử'), (0x1EED, 'V'), - (0x1EEE, 'M', u'ữ'), - (0x1EEF, 'V'), - (0x1EF0, 'M', u'ự'), - (0x1EF1, 'V'), - (0x1EF2, 'M', u'ỳ'), - (0x1EF3, 'V'), - (0x1EF4, 'M', u'ỵ'), - (0x1EF5, 'V'), - (0x1EF6, 'M', u'ỷ'), - (0x1EF7, 'V'), - (0x1EF8, 'M', u'ỹ'), - (0x1EF9, 'V'), - (0x1EFA, 'M', u'ỻ'), - (0x1EFB, 'V'), - (0x1EFC, 'M', u'ỽ'), - (0x1EFD, 'V'), - (0x1EFE, 'M', u'ỿ'), - (0x1EFF, 'V'), - (0x1F08, 'M', u'ἀ'), - (0x1F09, 'M', u'ἁ'), - (0x1F0A, 'M', u'ἂ'), - (0x1F0B, 'M', u'ἃ'), - (0x1F0C, 'M', u'ἄ'), - (0x1F0D, 'M', u'ἅ'), - (0x1F0E, 'M', u'ἆ'), - (0x1F0F, 'M', u'ἇ'), - (0x1F10, 'V'), - (0x1F16, 'X'), - (0x1F18, 'M', u'ἐ'), - (0x1F19, 'M', u'ἑ'), - (0x1F1A, 'M', u'ἒ'), ] def _seg_19(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x1F1B, 'M', u'ἓ'), - (0x1F1C, 'M', u'ἔ'), - (0x1F1D, 'M', u'ἕ'), + (0x1EEE, 'M', 'ữ'), + (0x1EEF, 'V'), + (0x1EF0, 'M', 'ự'), + (0x1EF1, 'V'), + (0x1EF2, 'M', 'ỳ'), + (0x1EF3, 'V'), + (0x1EF4, 'M', 'ỵ'), + (0x1EF5, 'V'), + (0x1EF6, 'M', 'ỷ'), + (0x1EF7, 'V'), + (0x1EF8, 'M', 'ỹ'), + (0x1EF9, 'V'), + (0x1EFA, 'M', 'ỻ'), + (0x1EFB, 'V'), + (0x1EFC, 'M', 'ỽ'), + (0x1EFD, 'V'), + (0x1EFE, 'M', 'ỿ'), + (0x1EFF, 'V'), + (0x1F08, 'M', 'ἀ'), + (0x1F09, 'M', 'ἁ'), + (0x1F0A, 'M', 'ἂ'), + (0x1F0B, 'M', 'ἃ'), + (0x1F0C, 'M', 'ἄ'), + (0x1F0D, 'M', 'ἅ'), + (0x1F0E, 'M', 'ἆ'), + (0x1F0F, 'M', 'ἇ'), + (0x1F10, 'V'), + (0x1F16, 'X'), + (0x1F18, 'M', 'ἐ'), + (0x1F19, 'M', 'ἑ'), + (0x1F1A, 'M', 'ἒ'), + (0x1F1B, 'M', 'ἓ'), + (0x1F1C, 'M', 'ἔ'), + (0x1F1D, 'M', 'ἕ'), (0x1F1E, 'X'), (0x1F20, 'V'), - (0x1F28, 'M', u'ἠ'), - (0x1F29, 'M', u'ἡ'), - (0x1F2A, 'M', u'ἢ'), - (0x1F2B, 'M', u'ἣ'), - (0x1F2C, 'M', u'ἤ'), - (0x1F2D, 'M', u'ἥ'), - (0x1F2E, 'M', u'ἦ'), - (0x1F2F, 'M', u'ἧ'), + (0x1F28, 'M', 'ἠ'), + (0x1F29, 'M', 'ἡ'), + (0x1F2A, 'M', 'ἢ'), + (0x1F2B, 'M', 'ἣ'), + (0x1F2C, 'M', 'ἤ'), + (0x1F2D, 'M', 'ἥ'), + (0x1F2E, 'M', 'ἦ'), + (0x1F2F, 'M', 'ἧ'), (0x1F30, 'V'), - (0x1F38, 'M', u'ἰ'), - (0x1F39, 'M', u'ἱ'), - (0x1F3A, 'M', u'ἲ'), - (0x1F3B, 'M', u'ἳ'), - (0x1F3C, 'M', u'ἴ'), - (0x1F3D, 'M', u'ἵ'), - (0x1F3E, 'M', u'ἶ'), - (0x1F3F, 'M', u'ἷ'), + (0x1F38, 'M', 'ἰ'), + (0x1F39, 'M', 'ἱ'), + (0x1F3A, 'M', 'ἲ'), + (0x1F3B, 'M', 'ἳ'), + (0x1F3C, 'M', 'ἴ'), + (0x1F3D, 'M', 'ἵ'), + (0x1F3E, 'M', 'ἶ'), + (0x1F3F, 'M', 'ἷ'), (0x1F40, 'V'), (0x1F46, 'X'), - (0x1F48, 'M', u'ὀ'), - (0x1F49, 'M', u'ὁ'), - (0x1F4A, 'M', u'ὂ'), - (0x1F4B, 'M', u'ὃ'), - (0x1F4C, 'M', u'ὄ'), - (0x1F4D, 'M', u'ὅ'), + (0x1F48, 'M', 'ὀ'), + (0x1F49, 'M', 'ὁ'), + (0x1F4A, 'M', 'ὂ'), + (0x1F4B, 'M', 'ὃ'), + (0x1F4C, 'M', 'ὄ'), + (0x1F4D, 'M', 'ὅ'), (0x1F4E, 'X'), (0x1F50, 'V'), (0x1F58, 'X'), - (0x1F59, 'M', u'ὑ'), + (0x1F59, 'M', 'ὑ'), (0x1F5A, 'X'), - (0x1F5B, 'M', u'ὓ'), + (0x1F5B, 'M', 'ὓ'), (0x1F5C, 'X'), - (0x1F5D, 'M', u'ὕ'), + (0x1F5D, 'M', 'ὕ'), (0x1F5E, 'X'), - (0x1F5F, 'M', u'ὗ'), + (0x1F5F, 'M', 'ὗ'), (0x1F60, 'V'), - (0x1F68, 'M', u'ὠ'), - (0x1F69, 'M', u'ὡ'), - (0x1F6A, 'M', u'ὢ'), - (0x1F6B, 'M', u'ὣ'), - (0x1F6C, 'M', u'ὤ'), - (0x1F6D, 'M', u'ὥ'), - (0x1F6E, 'M', u'ὦ'), - (0x1F6F, 'M', u'ὧ'), + (0x1F68, 'M', 'ὠ'), + (0x1F69, 'M', 'ὡ'), + (0x1F6A, 'M', 'ὢ'), + (0x1F6B, 'M', 'ὣ'), + (0x1F6C, 'M', 'ὤ'), + (0x1F6D, 'M', 'ὥ'), + (0x1F6E, 'M', 'ὦ'), + (0x1F6F, 'M', 'ὧ'), (0x1F70, 'V'), - (0x1F71, 'M', u'ά'), + (0x1F71, 'M', 'ά'), (0x1F72, 'V'), - (0x1F73, 'M', u'έ'), + (0x1F73, 'M', 'έ'), (0x1F74, 'V'), - (0x1F75, 'M', u'ή'), + (0x1F75, 'M', 'ή'), (0x1F76, 'V'), - (0x1F77, 'M', u'ί'), + (0x1F77, 'M', 'ί'), (0x1F78, 'V'), - (0x1F79, 'M', u'ό'), + (0x1F79, 'M', 'ό'), (0x1F7A, 'V'), - (0x1F7B, 'M', u'ύ'), + (0x1F7B, 'M', 'ύ'), (0x1F7C, 'V'), - (0x1F7D, 'M', u'ώ'), + (0x1F7D, 'M', 'ώ'), (0x1F7E, 'X'), - (0x1F80, 'M', u'ἀι'), - (0x1F81, 'M', u'ἁι'), - (0x1F82, 'M', u'ἂι'), - (0x1F83, 'M', u'ἃι'), - (0x1F84, 'M', u'ἄι'), - (0x1F85, 'M', u'ἅι'), - (0x1F86, 'M', u'ἆι'), - (0x1F87, 'M', u'ἇι'), - (0x1F88, 'M', u'ἀι'), - (0x1F89, 'M', u'ἁι'), - (0x1F8A, 'M', u'ἂι'), - (0x1F8B, 'M', u'ἃι'), - (0x1F8C, 'M', u'ἄι'), - (0x1F8D, 'M', u'ἅι'), - (0x1F8E, 'M', u'ἆι'), - (0x1F8F, 'M', u'ἇι'), - (0x1F90, 'M', u'ἠι'), - (0x1F91, 'M', u'ἡι'), - (0x1F92, 'M', u'ἢι'), - (0x1F93, 'M', u'ἣι'), - (0x1F94, 'M', u'ἤι'), - (0x1F95, 'M', u'ἥι'), - (0x1F96, 'M', u'ἦι'), - (0x1F97, 'M', u'ἧι'), - (0x1F98, 'M', u'ἠι'), - (0x1F99, 'M', u'ἡι'), - (0x1F9A, 'M', u'ἢι'), - (0x1F9B, 'M', u'ἣι'), - (0x1F9C, 'M', u'ἤι'), - (0x1F9D, 'M', u'ἥι'), - (0x1F9E, 'M', u'ἦι'), - (0x1F9F, 'M', u'ἧι'), - (0x1FA0, 'M', u'ὠι'), - (0x1FA1, 'M', u'ὡι'), - (0x1FA2, 'M', u'ὢι'), - (0x1FA3, 'M', u'ὣι'), + (0x1F80, 'M', 'ἀι'), + (0x1F81, 'M', 'ἁι'), + (0x1F82, 'M', 'ἂι'), + (0x1F83, 'M', 'ἃι'), + (0x1F84, 'M', 'ἄι'), ] def _seg_20(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x1FA4, 'M', u'ὤι'), - (0x1FA5, 'M', u'ὥι'), - (0x1FA6, 'M', u'ὦι'), - (0x1FA7, 'M', u'ὧι'), - (0x1FA8, 'M', u'ὠι'), - (0x1FA9, 'M', u'ὡι'), - (0x1FAA, 'M', u'ὢι'), - (0x1FAB, 'M', u'ὣι'), - (0x1FAC, 'M', u'ὤι'), - (0x1FAD, 'M', u'ὥι'), - (0x1FAE, 'M', u'ὦι'), - (0x1FAF, 'M', u'ὧι'), + (0x1F85, 'M', 'ἅι'), + (0x1F86, 'M', 'ἆι'), + (0x1F87, 'M', 'ἇι'), + (0x1F88, 'M', 'ἀι'), + (0x1F89, 'M', 'ἁι'), + (0x1F8A, 'M', 'ἂι'), + (0x1F8B, 'M', 'ἃι'), + (0x1F8C, 'M', 'ἄι'), + (0x1F8D, 'M', 'ἅι'), + (0x1F8E, 'M', 'ἆι'), + (0x1F8F, 'M', 'ἇι'), + (0x1F90, 'M', 'ἠι'), + (0x1F91, 'M', 'ἡι'), + (0x1F92, 'M', 'ἢι'), + (0x1F93, 'M', 'ἣι'), + (0x1F94, 'M', 'ἤι'), + (0x1F95, 'M', 'ἥι'), + (0x1F96, 'M', 'ἦι'), + (0x1F97, 'M', 'ἧι'), + (0x1F98, 'M', 'ἠι'), + (0x1F99, 'M', 'ἡι'), + (0x1F9A, 'M', 'ἢι'), + (0x1F9B, 'M', 'ἣι'), + (0x1F9C, 'M', 'ἤι'), + (0x1F9D, 'M', 'ἥι'), + (0x1F9E, 'M', 'ἦι'), + (0x1F9F, 'M', 'ἧι'), + (0x1FA0, 'M', 'ὠι'), + (0x1FA1, 'M', 'ὡι'), + (0x1FA2, 'M', 'ὢι'), + (0x1FA3, 'M', 'ὣι'), + (0x1FA4, 'M', 'ὤι'), + (0x1FA5, 'M', 'ὥι'), + (0x1FA6, 'M', 'ὦι'), + (0x1FA7, 'M', 'ὧι'), + (0x1FA8, 'M', 'ὠι'), + (0x1FA9, 'M', 'ὡι'), + (0x1FAA, 'M', 'ὢι'), + (0x1FAB, 'M', 'ὣι'), + (0x1FAC, 'M', 'ὤι'), + (0x1FAD, 'M', 'ὥι'), + (0x1FAE, 'M', 'ὦι'), + (0x1FAF, 'M', 'ὧι'), (0x1FB0, 'V'), - (0x1FB2, 'M', u'ὰι'), - (0x1FB3, 'M', u'αι'), - (0x1FB4, 'M', u'άι'), + (0x1FB2, 'M', 'ὰι'), + (0x1FB3, 'M', 'αι'), + (0x1FB4, 'M', 'άι'), (0x1FB5, 'X'), (0x1FB6, 'V'), - (0x1FB7, 'M', u'ᾶι'), - (0x1FB8, 'M', u'ᾰ'), - (0x1FB9, 'M', u'ᾱ'), - (0x1FBA, 'M', u'ὰ'), - (0x1FBB, 'M', u'ά'), - (0x1FBC, 'M', u'αι'), - (0x1FBD, '3', u' ̓'), - (0x1FBE, 'M', u'ι'), - (0x1FBF, '3', u' ̓'), - (0x1FC0, '3', u' ͂'), - (0x1FC1, '3', u' ̈͂'), - (0x1FC2, 'M', u'ὴι'), - (0x1FC3, 'M', u'ηι'), - (0x1FC4, 'M', u'ήι'), + (0x1FB7, 'M', 'ᾶι'), + (0x1FB8, 'M', 'ᾰ'), + (0x1FB9, 'M', 'ᾱ'), + (0x1FBA, 'M', 'ὰ'), + (0x1FBB, 'M', 'ά'), + (0x1FBC, 'M', 'αι'), + (0x1FBD, '3', ' ̓'), + (0x1FBE, 'M', 'ι'), + (0x1FBF, '3', ' ̓'), + (0x1FC0, '3', ' ͂'), + (0x1FC1, '3', ' ̈͂'), + (0x1FC2, 'M', 'ὴι'), + (0x1FC3, 'M', 'ηι'), + (0x1FC4, 'M', 'ήι'), (0x1FC5, 'X'), (0x1FC6, 'V'), - (0x1FC7, 'M', u'ῆι'), - (0x1FC8, 'M', u'ὲ'), - (0x1FC9, 'M', u'έ'), - (0x1FCA, 'M', u'ὴ'), - (0x1FCB, 'M', u'ή'), - (0x1FCC, 'M', u'ηι'), - (0x1FCD, '3', u' ̓̀'), - (0x1FCE, '3', u' ̓́'), - (0x1FCF, '3', u' ̓͂'), + (0x1FC7, 'M', 'ῆι'), + (0x1FC8, 'M', 'ὲ'), + (0x1FC9, 'M', 'έ'), + (0x1FCA, 'M', 'ὴ'), + (0x1FCB, 'M', 'ή'), + (0x1FCC, 'M', 'ηι'), + (0x1FCD, '3', ' ̓̀'), + (0x1FCE, '3', ' ̓́'), + (0x1FCF, '3', ' ̓͂'), (0x1FD0, 'V'), - (0x1FD3, 'M', u'ΐ'), + (0x1FD3, 'M', 'ΐ'), (0x1FD4, 'X'), (0x1FD6, 'V'), - (0x1FD8, 'M', u'ῐ'), - (0x1FD9, 'M', u'ῑ'), - (0x1FDA, 'M', u'ὶ'), - (0x1FDB, 'M', u'ί'), + (0x1FD8, 'M', 'ῐ'), + (0x1FD9, 'M', 'ῑ'), + (0x1FDA, 'M', 'ὶ'), + (0x1FDB, 'M', 'ί'), (0x1FDC, 'X'), - (0x1FDD, '3', u' ̔̀'), - (0x1FDE, '3', u' ̔́'), - (0x1FDF, '3', u' ̔͂'), + (0x1FDD, '3', ' ̔̀'), + (0x1FDE, '3', ' ̔́'), + (0x1FDF, '3', ' ̔͂'), (0x1FE0, 'V'), - (0x1FE3, 'M', u'ΰ'), + (0x1FE3, 'M', 'ΰ'), (0x1FE4, 'V'), - (0x1FE8, 'M', u'ῠ'), - (0x1FE9, 'M', u'ῡ'), - (0x1FEA, 'M', u'ὺ'), - (0x1FEB, 'M', u'ύ'), - (0x1FEC, 'M', u'ῥ'), - (0x1FED, '3', u' ̈̀'), - (0x1FEE, '3', u' ̈́'), - (0x1FEF, '3', u'`'), + (0x1FE8, 'M', 'ῠ'), + (0x1FE9, 'M', 'ῡ'), + (0x1FEA, 'M', 'ὺ'), + (0x1FEB, 'M', 'ύ'), + (0x1FEC, 'M', 'ῥ'), + (0x1FED, '3', ' ̈̀'), + (0x1FEE, '3', ' ̈́'), + (0x1FEF, '3', '`'), (0x1FF0, 'X'), - (0x1FF2, 'M', u'ὼι'), - (0x1FF3, 'M', u'ωι'), - (0x1FF4, 'M', u'ώι'), + (0x1FF2, 'M', 'ὼι'), + (0x1FF3, 'M', 'ωι'), + ] + +def _seg_21(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ + (0x1FF4, 'M', 'ώι'), (0x1FF5, 'X'), (0x1FF6, 'V'), - (0x1FF7, 'M', u'ῶι'), - (0x1FF8, 'M', u'ὸ'), - (0x1FF9, 'M', u'ό'), - (0x1FFA, 'M', u'ὼ'), - (0x1FFB, 'M', u'ώ'), - (0x1FFC, 'M', u'ωι'), - (0x1FFD, '3', u' ́'), - (0x1FFE, '3', u' ̔'), + (0x1FF7, 'M', 'ῶι'), + (0x1FF8, 'M', 'ὸ'), + (0x1FF9, 'M', 'ό'), + (0x1FFA, 'M', 'ὼ'), + (0x1FFB, 'M', 'ώ'), + (0x1FFC, 'M', 'ωι'), + (0x1FFD, '3', ' ́'), + (0x1FFE, '3', ' ̔'), (0x1FFF, 'X'), - (0x2000, '3', u' '), + (0x2000, '3', ' '), (0x200B, 'I'), - (0x200C, 'D', u''), + (0x200C, 'D', ''), (0x200E, 'X'), (0x2010, 'V'), - (0x2011, 'M', u'‐'), + (0x2011, 'M', '‐'), (0x2012, 'V'), - (0x2017, '3', u' ̳'), + (0x2017, '3', ' ̳'), (0x2018, 'V'), (0x2024, 'X'), (0x2027, 'V'), (0x2028, 'X'), - (0x202F, '3', u' '), + (0x202F, '3', ' '), (0x2030, 'V'), - (0x2033, 'M', u'′′'), - (0x2034, 'M', u'′′′'), + (0x2033, 'M', '′′'), + (0x2034, 'M', '′′′'), (0x2035, 'V'), - (0x2036, 'M', u'‵‵'), - (0x2037, 'M', u'‵‵‵'), - ] - -def _seg_21(): - return [ + (0x2036, 'M', '‵‵'), + (0x2037, 'M', '‵‵‵'), (0x2038, 'V'), - (0x203C, '3', u'!!'), + (0x203C, '3', '!!'), (0x203D, 'V'), - (0x203E, '3', u' ̅'), + (0x203E, '3', ' ̅'), (0x203F, 'V'), - (0x2047, '3', u'??'), - (0x2048, '3', u'?!'), - (0x2049, '3', u'!?'), + (0x2047, '3', '??'), + (0x2048, '3', '?!'), + (0x2049, '3', '!?'), (0x204A, 'V'), - (0x2057, 'M', u'′′′′'), + (0x2057, 'M', '′′′′'), (0x2058, 'V'), - (0x205F, '3', u' '), + (0x205F, '3', ' '), (0x2060, 'I'), (0x2061, 'X'), (0x2064, 'I'), (0x2065, 'X'), - (0x2070, 'M', u'0'), - (0x2071, 'M', u'i'), + (0x2070, 'M', '0'), + (0x2071, 'M', 'i'), (0x2072, 'X'), - (0x2074, 'M', u'4'), - (0x2075, 'M', u'5'), - (0x2076, 'M', u'6'), - (0x2077, 'M', u'7'), - (0x2078, 'M', u'8'), - (0x2079, 'M', u'9'), - (0x207A, '3', u'+'), - (0x207B, 'M', u'−'), - (0x207C, '3', u'='), - (0x207D, '3', u'('), - (0x207E, '3', u')'), - (0x207F, 'M', u'n'), - (0x2080, 'M', u'0'), - (0x2081, 'M', u'1'), - (0x2082, 'M', u'2'), - (0x2083, 'M', u'3'), - (0x2084, 'M', u'4'), - (0x2085, 'M', u'5'), - (0x2086, 'M', u'6'), - (0x2087, 'M', u'7'), - (0x2088, 'M', u'8'), - (0x2089, 'M', u'9'), - (0x208A, '3', u'+'), - (0x208B, 'M', u'−'), - (0x208C, '3', u'='), - (0x208D, '3', u'('), - (0x208E, '3', u')'), + (0x2074, 'M', '4'), + (0x2075, 'M', '5'), + (0x2076, 'M', '6'), + (0x2077, 'M', '7'), + (0x2078, 'M', '8'), + (0x2079, 'M', '9'), + (0x207A, '3', '+'), + (0x207B, 'M', '−'), + (0x207C, '3', '='), + (0x207D, '3', '('), + (0x207E, '3', ')'), + (0x207F, 'M', 'n'), + (0x2080, 'M', '0'), + (0x2081, 'M', '1'), + (0x2082, 'M', '2'), + (0x2083, 'M', '3'), + (0x2084, 'M', '4'), + (0x2085, 'M', '5'), + (0x2086, 'M', '6'), + (0x2087, 'M', '7'), + (0x2088, 'M', '8'), + (0x2089, 'M', '9'), + (0x208A, '3', '+'), + (0x208B, 'M', '−'), + (0x208C, '3', '='), + (0x208D, '3', '('), + (0x208E, '3', ')'), (0x208F, 'X'), - (0x2090, 'M', u'a'), - (0x2091, 'M', u'e'), - (0x2092, 'M', u'o'), - (0x2093, 'M', u'x'), - (0x2094, 'M', u'ə'), - (0x2095, 'M', u'h'), - (0x2096, 'M', u'k'), - (0x2097, 'M', u'l'), - (0x2098, 'M', u'm'), - (0x2099, 'M', u'n'), - (0x209A, 'M', u'p'), - (0x209B, 'M', u's'), - (0x209C, 'M', u't'), + (0x2090, 'M', 'a'), + (0x2091, 'M', 'e'), + (0x2092, 'M', 'o'), + (0x2093, 'M', 'x'), + (0x2094, 'M', 'ə'), + (0x2095, 'M', 'h'), + (0x2096, 'M', 'k'), + (0x2097, 'M', 'l'), + (0x2098, 'M', 'm'), + (0x2099, 'M', 'n'), + (0x209A, 'M', 'p'), + (0x209B, 'M', 's'), + (0x209C, 'M', 't'), (0x209D, 'X'), (0x20A0, 'V'), - (0x20A8, 'M', u'rs'), + (0x20A8, 'M', 'rs'), (0x20A9, 'V'), (0x20C0, 'X'), (0x20D0, 'V'), (0x20F1, 'X'), - (0x2100, '3', u'a/c'), - (0x2101, '3', u'a/s'), - (0x2102, 'M', u'c'), - (0x2103, 'M', u'°c'), - (0x2104, 'V'), - (0x2105, '3', u'c/o'), - (0x2106, '3', u'c/u'), - (0x2107, 'M', u'ɛ'), - (0x2108, 'V'), - (0x2109, 'M', u'°f'), - (0x210A, 'M', u'g'), - (0x210B, 'M', u'h'), - (0x210F, 'M', u'ħ'), - (0x2110, 'M', u'i'), - (0x2112, 'M', u'l'), - (0x2114, 'V'), - (0x2115, 'M', u'n'), - (0x2116, 'M', u'no'), - (0x2117, 'V'), - (0x2119, 'M', u'p'), - (0x211A, 'M', u'q'), - (0x211B, 'M', u'r'), - (0x211E, 'V'), - (0x2120, 'M', u'sm'), - (0x2121, 'M', u'tel'), - (0x2122, 'M', u'tm'), - (0x2123, 'V'), - (0x2124, 'M', u'z'), - (0x2125, 'V'), - (0x2126, 'M', u'ω'), - (0x2127, 'V'), - (0x2128, 'M', u'z'), - (0x2129, 'V'), + (0x2100, '3', 'a/c'), + (0x2101, '3', 'a/s'), ] def _seg_22(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x212A, 'M', u'k'), - (0x212B, 'M', u'å'), - (0x212C, 'M', u'b'), - (0x212D, 'M', u'c'), + (0x2102, 'M', 'c'), + (0x2103, 'M', '°c'), + (0x2104, 'V'), + (0x2105, '3', 'c/o'), + (0x2106, '3', 'c/u'), + (0x2107, 'M', 'ɛ'), + (0x2108, 'V'), + (0x2109, 'M', '°f'), + (0x210A, 'M', 'g'), + (0x210B, 'M', 'h'), + (0x210F, 'M', 'ħ'), + (0x2110, 'M', 'i'), + (0x2112, 'M', 'l'), + (0x2114, 'V'), + (0x2115, 'M', 'n'), + (0x2116, 'M', 'no'), + (0x2117, 'V'), + (0x2119, 'M', 'p'), + (0x211A, 'M', 'q'), + (0x211B, 'M', 'r'), + (0x211E, 'V'), + (0x2120, 'M', 'sm'), + (0x2121, 'M', 'tel'), + (0x2122, 'M', 'tm'), + (0x2123, 'V'), + (0x2124, 'M', 'z'), + (0x2125, 'V'), + (0x2126, 'M', 'ω'), + (0x2127, 'V'), + (0x2128, 'M', 'z'), + (0x2129, 'V'), + (0x212A, 'M', 'k'), + (0x212B, 'M', 'å'), + (0x212C, 'M', 'b'), + (0x212D, 'M', 'c'), (0x212E, 'V'), - (0x212F, 'M', u'e'), - (0x2131, 'M', u'f'), + (0x212F, 'M', 'e'), + (0x2131, 'M', 'f'), (0x2132, 'X'), - (0x2133, 'M', u'm'), - (0x2134, 'M', u'o'), - (0x2135, 'M', u'א'), - (0x2136, 'M', u'ב'), - (0x2137, 'M', u'ג'), - (0x2138, 'M', u'ד'), - (0x2139, 'M', u'i'), + (0x2133, 'M', 'm'), + (0x2134, 'M', 'o'), + (0x2135, 'M', 'א'), + (0x2136, 'M', 'ב'), + (0x2137, 'M', 'ג'), + (0x2138, 'M', 'ד'), + (0x2139, 'M', 'i'), (0x213A, 'V'), - (0x213B, 'M', u'fax'), - (0x213C, 'M', u'π'), - (0x213D, 'M', u'γ'), - (0x213F, 'M', u'π'), - (0x2140, 'M', u'∑'), + (0x213B, 'M', 'fax'), + (0x213C, 'M', 'π'), + (0x213D, 'M', 'γ'), + (0x213F, 'M', 'π'), + (0x2140, 'M', '∑'), (0x2141, 'V'), - (0x2145, 'M', u'd'), - (0x2147, 'M', u'e'), - (0x2148, 'M', u'i'), - (0x2149, 'M', u'j'), + (0x2145, 'M', 'd'), + (0x2147, 'M', 'e'), + (0x2148, 'M', 'i'), + (0x2149, 'M', 'j'), (0x214A, 'V'), - (0x2150, 'M', u'1⁄7'), - (0x2151, 'M', u'1⁄9'), - (0x2152, 'M', u'1⁄10'), - (0x2153, 'M', u'1⁄3'), - (0x2154, 'M', u'2⁄3'), - (0x2155, 'M', u'1⁄5'), - (0x2156, 'M', u'2⁄5'), - (0x2157, 'M', u'3⁄5'), - (0x2158, 'M', u'4⁄5'), - (0x2159, 'M', u'1⁄6'), - (0x215A, 'M', u'5⁄6'), - (0x215B, 'M', u'1⁄8'), - (0x215C, 'M', u'3⁄8'), - (0x215D, 'M', u'5⁄8'), - (0x215E, 'M', u'7⁄8'), - (0x215F, 'M', u'1⁄'), - (0x2160, 'M', u'i'), - (0x2161, 'M', u'ii'), - (0x2162, 'M', u'iii'), - (0x2163, 'M', u'iv'), - (0x2164, 'M', u'v'), - (0x2165, 'M', u'vi'), - (0x2166, 'M', u'vii'), - (0x2167, 'M', u'viii'), - (0x2168, 'M', u'ix'), - (0x2169, 'M', u'x'), - (0x216A, 'M', u'xi'), - (0x216B, 'M', u'xii'), - (0x216C, 'M', u'l'), - (0x216D, 'M', u'c'), - (0x216E, 'M', u'd'), - (0x216F, 'M', u'm'), - (0x2170, 'M', u'i'), - (0x2171, 'M', u'ii'), - (0x2172, 'M', u'iii'), - (0x2173, 'M', u'iv'), - (0x2174, 'M', u'v'), - (0x2175, 'M', u'vi'), - (0x2176, 'M', u'vii'), - (0x2177, 'M', u'viii'), - (0x2178, 'M', u'ix'), - (0x2179, 'M', u'x'), - (0x217A, 'M', u'xi'), - (0x217B, 'M', u'xii'), - (0x217C, 'M', u'l'), - (0x217D, 'M', u'c'), - (0x217E, 'M', u'd'), - (0x217F, 'M', u'm'), + (0x2150, 'M', '1⁄7'), + (0x2151, 'M', '1⁄9'), + (0x2152, 'M', '1⁄10'), + (0x2153, 'M', '1⁄3'), + (0x2154, 'M', '2⁄3'), + (0x2155, 'M', '1⁄5'), + (0x2156, 'M', '2⁄5'), + (0x2157, 'M', '3⁄5'), + (0x2158, 'M', '4⁄5'), + (0x2159, 'M', '1⁄6'), + (0x215A, 'M', '5⁄6'), + (0x215B, 'M', '1⁄8'), + (0x215C, 'M', '3⁄8'), + (0x215D, 'M', '5⁄8'), + (0x215E, 'M', '7⁄8'), + (0x215F, 'M', '1⁄'), + (0x2160, 'M', 'i'), + (0x2161, 'M', 'ii'), + (0x2162, 'M', 'iii'), + (0x2163, 'M', 'iv'), + (0x2164, 'M', 'v'), + (0x2165, 'M', 'vi'), + (0x2166, 'M', 'vii'), + (0x2167, 'M', 'viii'), + (0x2168, 'M', 'ix'), + (0x2169, 'M', 'x'), + (0x216A, 'M', 'xi'), + (0x216B, 'M', 'xii'), + (0x216C, 'M', 'l'), + (0x216D, 'M', 'c'), + (0x216E, 'M', 'd'), + (0x216F, 'M', 'm'), + (0x2170, 'M', 'i'), + (0x2171, 'M', 'ii'), + (0x2172, 'M', 'iii'), + (0x2173, 'M', 'iv'), + (0x2174, 'M', 'v'), + (0x2175, 'M', 'vi'), + (0x2176, 'M', 'vii'), + (0x2177, 'M', 'viii'), + (0x2178, 'M', 'ix'), + (0x2179, 'M', 'x'), + ] + +def _seg_23(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ + (0x217A, 'M', 'xi'), + (0x217B, 'M', 'xii'), + (0x217C, 'M', 'l'), + (0x217D, 'M', 'c'), + (0x217E, 'M', 'd'), + (0x217F, 'M', 'm'), (0x2180, 'V'), (0x2183, 'X'), (0x2184, 'V'), - (0x2189, 'M', u'0⁄3'), + (0x2189, 'M', '0⁄3'), (0x218A, 'V'), (0x218C, 'X'), (0x2190, 'V'), - (0x222C, 'M', u'∫∫'), - (0x222D, 'M', u'∫∫∫'), + (0x222C, 'M', '∫∫'), + (0x222D, 'M', '∫∫∫'), (0x222E, 'V'), - (0x222F, 'M', u'∮∮'), - (0x2230, 'M', u'∮∮∮'), + (0x222F, 'M', '∮∮'), + (0x2230, 'M', '∮∮∮'), (0x2231, 'V'), (0x2260, '3'), (0x2261, 'V'), (0x226E, '3'), (0x2270, 'V'), - (0x2329, 'M', u'〈'), - (0x232A, 'M', u'〉'), + (0x2329, 'M', '〈'), + (0x232A, 'M', '〉'), (0x232B, 'V'), (0x2427, 'X'), (0x2440, 'V'), (0x244B, 'X'), - (0x2460, 'M', u'1'), - (0x2461, 'M', u'2'), - ] - -def _seg_23(): - return [ - (0x2462, 'M', u'3'), - (0x2463, 'M', u'4'), - (0x2464, 'M', u'5'), - (0x2465, 'M', u'6'), - (0x2466, 'M', u'7'), - (0x2467, 'M', u'8'), - (0x2468, 'M', u'9'), - (0x2469, 'M', u'10'), - (0x246A, 'M', u'11'), - (0x246B, 'M', u'12'), - (0x246C, 'M', u'13'), - (0x246D, 'M', u'14'), - (0x246E, 'M', u'15'), - (0x246F, 'M', u'16'), - (0x2470, 'M', u'17'), - (0x2471, 'M', u'18'), - (0x2472, 'M', u'19'), - (0x2473, 'M', u'20'), - (0x2474, '3', u'(1)'), - (0x2475, '3', u'(2)'), - (0x2476, '3', u'(3)'), - (0x2477, '3', u'(4)'), - (0x2478, '3', u'(5)'), - (0x2479, '3', u'(6)'), - (0x247A, '3', u'(7)'), - (0x247B, '3', u'(8)'), - (0x247C, '3', u'(9)'), - (0x247D, '3', u'(10)'), - (0x247E, '3', u'(11)'), - (0x247F, '3', u'(12)'), - (0x2480, '3', u'(13)'), - (0x2481, '3', u'(14)'), - (0x2482, '3', u'(15)'), - (0x2483, '3', u'(16)'), - (0x2484, '3', u'(17)'), - (0x2485, '3', u'(18)'), - (0x2486, '3', u'(19)'), - (0x2487, '3', u'(20)'), + (0x2460, 'M', '1'), + (0x2461, 'M', '2'), + (0x2462, 'M', '3'), + (0x2463, 'M', '4'), + (0x2464, 'M', '5'), + (0x2465, 'M', '6'), + (0x2466, 'M', '7'), + (0x2467, 'M', '8'), + (0x2468, 'M', '9'), + (0x2469, 'M', '10'), + (0x246A, 'M', '11'), + (0x246B, 'M', '12'), + (0x246C, 'M', '13'), + (0x246D, 'M', '14'), + (0x246E, 'M', '15'), + (0x246F, 'M', '16'), + (0x2470, 'M', '17'), + (0x2471, 'M', '18'), + (0x2472, 'M', '19'), + (0x2473, 'M', '20'), + (0x2474, '3', '(1)'), + (0x2475, '3', '(2)'), + (0x2476, '3', '(3)'), + (0x2477, '3', '(4)'), + (0x2478, '3', '(5)'), + (0x2479, '3', '(6)'), + (0x247A, '3', '(7)'), + (0x247B, '3', '(8)'), + (0x247C, '3', '(9)'), + (0x247D, '3', '(10)'), + (0x247E, '3', '(11)'), + (0x247F, '3', '(12)'), + (0x2480, '3', '(13)'), + (0x2481, '3', '(14)'), + (0x2482, '3', '(15)'), + (0x2483, '3', '(16)'), + (0x2484, '3', '(17)'), + (0x2485, '3', '(18)'), + (0x2486, '3', '(19)'), + (0x2487, '3', '(20)'), (0x2488, 'X'), - (0x249C, '3', u'(a)'), - (0x249D, '3', u'(b)'), - (0x249E, '3', u'(c)'), - (0x249F, '3', u'(d)'), - (0x24A0, '3', u'(e)'), - (0x24A1, '3', u'(f)'), - (0x24A2, '3', u'(g)'), - (0x24A3, '3', u'(h)'), - (0x24A4, '3', u'(i)'), - (0x24A5, '3', u'(j)'), - (0x24A6, '3', u'(k)'), - (0x24A7, '3', u'(l)'), - (0x24A8, '3', u'(m)'), - (0x24A9, '3', u'(n)'), - (0x24AA, '3', u'(o)'), - (0x24AB, '3', u'(p)'), - (0x24AC, '3', u'(q)'), - (0x24AD, '3', u'(r)'), - (0x24AE, '3', u'(s)'), - (0x24AF, '3', u'(t)'), - (0x24B0, '3', u'(u)'), - (0x24B1, '3', u'(v)'), - (0x24B2, '3', u'(w)'), - (0x24B3, '3', u'(x)'), - (0x24B4, '3', u'(y)'), - (0x24B5, '3', u'(z)'), - (0x24B6, 'M', u'a'), - (0x24B7, 'M', u'b'), - (0x24B8, 'M', u'c'), - (0x24B9, 'M', u'd'), - (0x24BA, 'M', u'e'), - (0x24BB, 'M', u'f'), - (0x24BC, 'M', u'g'), - (0x24BD, 'M', u'h'), - (0x24BE, 'M', u'i'), - (0x24BF, 'M', u'j'), - (0x24C0, 'M', u'k'), - (0x24C1, 'M', u'l'), - (0x24C2, 'M', u'm'), - (0x24C3, 'M', u'n'), - (0x24C4, 'M', u'o'), - (0x24C5, 'M', u'p'), - (0x24C6, 'M', u'q'), - (0x24C7, 'M', u'r'), - (0x24C8, 'M', u's'), - (0x24C9, 'M', u't'), - (0x24CA, 'M', u'u'), - (0x24CB, 'M', u'v'), - (0x24CC, 'M', u'w'), - (0x24CD, 'M', u'x'), - (0x24CE, 'M', u'y'), - (0x24CF, 'M', u'z'), - (0x24D0, 'M', u'a'), - (0x24D1, 'M', u'b'), - (0x24D2, 'M', u'c'), - (0x24D3, 'M', u'd'), - (0x24D4, 'M', u'e'), - (0x24D5, 'M', u'f'), - (0x24D6, 'M', u'g'), - (0x24D7, 'M', u'h'), - (0x24D8, 'M', u'i'), + (0x249C, '3', '(a)'), + (0x249D, '3', '(b)'), + (0x249E, '3', '(c)'), + (0x249F, '3', '(d)'), + (0x24A0, '3', '(e)'), + (0x24A1, '3', '(f)'), + (0x24A2, '3', '(g)'), + (0x24A3, '3', '(h)'), + (0x24A4, '3', '(i)'), + (0x24A5, '3', '(j)'), + (0x24A6, '3', '(k)'), + (0x24A7, '3', '(l)'), + (0x24A8, '3', '(m)'), + (0x24A9, '3', '(n)'), + (0x24AA, '3', '(o)'), + (0x24AB, '3', '(p)'), + (0x24AC, '3', '(q)'), + (0x24AD, '3', '(r)'), + (0x24AE, '3', '(s)'), + (0x24AF, '3', '(t)'), + (0x24B0, '3', '(u)'), + (0x24B1, '3', '(v)'), + (0x24B2, '3', '(w)'), + (0x24B3, '3', '(x)'), + (0x24B4, '3', '(y)'), + (0x24B5, '3', '(z)'), + (0x24B6, 'M', 'a'), + (0x24B7, 'M', 'b'), + (0x24B8, 'M', 'c'), + (0x24B9, 'M', 'd'), ] def _seg_24(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x24D9, 'M', u'j'), - (0x24DA, 'M', u'k'), - (0x24DB, 'M', u'l'), - (0x24DC, 'M', u'm'), - (0x24DD, 'M', u'n'), - (0x24DE, 'M', u'o'), - (0x24DF, 'M', u'p'), - (0x24E0, 'M', u'q'), - (0x24E1, 'M', u'r'), - (0x24E2, 'M', u's'), - (0x24E3, 'M', u't'), - (0x24E4, 'M', u'u'), - (0x24E5, 'M', u'v'), - (0x24E6, 'M', u'w'), - (0x24E7, 'M', u'x'), - (0x24E8, 'M', u'y'), - (0x24E9, 'M', u'z'), - (0x24EA, 'M', u'0'), + (0x24BA, 'M', 'e'), + (0x24BB, 'M', 'f'), + (0x24BC, 'M', 'g'), + (0x24BD, 'M', 'h'), + (0x24BE, 'M', 'i'), + (0x24BF, 'M', 'j'), + (0x24C0, 'M', 'k'), + (0x24C1, 'M', 'l'), + (0x24C2, 'M', 'm'), + (0x24C3, 'M', 'n'), + (0x24C4, 'M', 'o'), + (0x24C5, 'M', 'p'), + (0x24C6, 'M', 'q'), + (0x24C7, 'M', 'r'), + (0x24C8, 'M', 's'), + (0x24C9, 'M', 't'), + (0x24CA, 'M', 'u'), + (0x24CB, 'M', 'v'), + (0x24CC, 'M', 'w'), + (0x24CD, 'M', 'x'), + (0x24CE, 'M', 'y'), + (0x24CF, 'M', 'z'), + (0x24D0, 'M', 'a'), + (0x24D1, 'M', 'b'), + (0x24D2, 'M', 'c'), + (0x24D3, 'M', 'd'), + (0x24D4, 'M', 'e'), + (0x24D5, 'M', 'f'), + (0x24D6, 'M', 'g'), + (0x24D7, 'M', 'h'), + (0x24D8, 'M', 'i'), + (0x24D9, 'M', 'j'), + (0x24DA, 'M', 'k'), + (0x24DB, 'M', 'l'), + (0x24DC, 'M', 'm'), + (0x24DD, 'M', 'n'), + (0x24DE, 'M', 'o'), + (0x24DF, 'M', 'p'), + (0x24E0, 'M', 'q'), + (0x24E1, 'M', 'r'), + (0x24E2, 'M', 's'), + (0x24E3, 'M', 't'), + (0x24E4, 'M', 'u'), + (0x24E5, 'M', 'v'), + (0x24E6, 'M', 'w'), + (0x24E7, 'M', 'x'), + (0x24E8, 'M', 'y'), + (0x24E9, 'M', 'z'), + (0x24EA, 'M', '0'), (0x24EB, 'V'), - (0x2A0C, 'M', u'∫∫∫∫'), + (0x2A0C, 'M', '∫∫∫∫'), (0x2A0D, 'V'), - (0x2A74, '3', u'::='), - (0x2A75, '3', u'=='), - (0x2A76, '3', u'==='), + (0x2A74, '3', '::='), + (0x2A75, '3', '=='), + (0x2A76, '3', '==='), (0x2A77, 'V'), - (0x2ADC, 'M', u'⫝̸'), + (0x2ADC, 'M', '⫝̸'), (0x2ADD, 'V'), (0x2B74, 'X'), (0x2B76, 'V'), (0x2B96, 'X'), - (0x2B98, 'V'), - (0x2BC9, 'X'), - (0x2BCA, 'V'), - (0x2BFF, 'X'), - (0x2C00, 'M', u'ⰰ'), - (0x2C01, 'M', u'ⰱ'), - (0x2C02, 'M', u'ⰲ'), - (0x2C03, 'M', u'ⰳ'), - (0x2C04, 'M', u'ⰴ'), - (0x2C05, 'M', u'ⰵ'), - (0x2C06, 'M', u'ⰶ'), - (0x2C07, 'M', u'ⰷ'), - (0x2C08, 'M', u'ⰸ'), - (0x2C09, 'M', u'ⰹ'), - (0x2C0A, 'M', u'ⰺ'), - (0x2C0B, 'M', u'ⰻ'), - (0x2C0C, 'M', u'ⰼ'), - (0x2C0D, 'M', u'ⰽ'), - (0x2C0E, 'M', u'ⰾ'), - (0x2C0F, 'M', u'ⰿ'), - (0x2C10, 'M', u'ⱀ'), - (0x2C11, 'M', u'ⱁ'), - (0x2C12, 'M', u'ⱂ'), - (0x2C13, 'M', u'ⱃ'), - (0x2C14, 'M', u'ⱄ'), - (0x2C15, 'M', u'ⱅ'), - (0x2C16, 'M', u'ⱆ'), - (0x2C17, 'M', u'ⱇ'), - (0x2C18, 'M', u'ⱈ'), - (0x2C19, 'M', u'ⱉ'), - (0x2C1A, 'M', u'ⱊ'), - (0x2C1B, 'M', u'ⱋ'), - (0x2C1C, 'M', u'ⱌ'), - (0x2C1D, 'M', u'ⱍ'), - (0x2C1E, 'M', u'ⱎ'), - (0x2C1F, 'M', u'ⱏ'), - (0x2C20, 'M', u'ⱐ'), - (0x2C21, 'M', u'ⱑ'), - (0x2C22, 'M', u'ⱒ'), - (0x2C23, 'M', u'ⱓ'), - (0x2C24, 'M', u'ⱔ'), - (0x2C25, 'M', u'ⱕ'), - (0x2C26, 'M', u'ⱖ'), - (0x2C27, 'M', u'ⱗ'), - (0x2C28, 'M', u'ⱘ'), - (0x2C29, 'M', u'ⱙ'), - (0x2C2A, 'M', u'ⱚ'), - (0x2C2B, 'M', u'ⱛ'), - (0x2C2C, 'M', u'ⱜ'), - (0x2C2D, 'M', u'ⱝ'), - (0x2C2E, 'M', u'ⱞ'), - (0x2C2F, 'X'), - (0x2C30, 'V'), - (0x2C5F, 'X'), - (0x2C60, 'M', u'ⱡ'), - (0x2C61, 'V'), - (0x2C62, 'M', u'ɫ'), - (0x2C63, 'M', u'ᵽ'), - (0x2C64, 'M', u'ɽ'), - (0x2C65, 'V'), - (0x2C67, 'M', u'ⱨ'), - (0x2C68, 'V'), - (0x2C69, 'M', u'ⱪ'), - (0x2C6A, 'V'), - (0x2C6B, 'M', u'ⱬ'), - (0x2C6C, 'V'), - (0x2C6D, 'M', u'ɑ'), - (0x2C6E, 'M', u'ɱ'), - (0x2C6F, 'M', u'ɐ'), - (0x2C70, 'M', u'ɒ'), + (0x2B97, 'V'), + (0x2C00, 'M', 'ⰰ'), + (0x2C01, 'M', 'ⰱ'), + (0x2C02, 'M', 'ⰲ'), + (0x2C03, 'M', 'ⰳ'), + (0x2C04, 'M', 'ⰴ'), + (0x2C05, 'M', 'ⰵ'), + (0x2C06, 'M', 'ⰶ'), + (0x2C07, 'M', 'ⰷ'), + (0x2C08, 'M', 'ⰸ'), + (0x2C09, 'M', 'ⰹ'), + (0x2C0A, 'M', 'ⰺ'), + (0x2C0B, 'M', 'ⰻ'), + (0x2C0C, 'M', 'ⰼ'), + (0x2C0D, 'M', 'ⰽ'), + (0x2C0E, 'M', 'ⰾ'), + (0x2C0F, 'M', 'ⰿ'), + (0x2C10, 'M', 'ⱀ'), + (0x2C11, 'M', 'ⱁ'), + (0x2C12, 'M', 'ⱂ'), + (0x2C13, 'M', 'ⱃ'), + (0x2C14, 'M', 'ⱄ'), + (0x2C15, 'M', 'ⱅ'), + (0x2C16, 'M', 'ⱆ'), + (0x2C17, 'M', 'ⱇ'), + (0x2C18, 'M', 'ⱈ'), + (0x2C19, 'M', 'ⱉ'), + (0x2C1A, 'M', 'ⱊ'), + (0x2C1B, 'M', 'ⱋ'), + (0x2C1C, 'M', 'ⱌ'), + (0x2C1D, 'M', 'ⱍ'), + (0x2C1E, 'M', 'ⱎ'), + (0x2C1F, 'M', 'ⱏ'), + (0x2C20, 'M', 'ⱐ'), + (0x2C21, 'M', 'ⱑ'), + (0x2C22, 'M', 'ⱒ'), + (0x2C23, 'M', 'ⱓ'), + (0x2C24, 'M', 'ⱔ'), + (0x2C25, 'M', 'ⱕ'), ] def _seg_25(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ + (0x2C26, 'M', 'ⱖ'), + (0x2C27, 'M', 'ⱗ'), + (0x2C28, 'M', 'ⱘ'), + (0x2C29, 'M', 'ⱙ'), + (0x2C2A, 'M', 'ⱚ'), + (0x2C2B, 'M', 'ⱛ'), + (0x2C2C, 'M', 'ⱜ'), + (0x2C2D, 'M', 'ⱝ'), + (0x2C2E, 'M', 'ⱞ'), + (0x2C2F, 'X'), + (0x2C30, 'V'), + (0x2C5F, 'X'), + (0x2C60, 'M', 'ⱡ'), + (0x2C61, 'V'), + (0x2C62, 'M', 'ɫ'), + (0x2C63, 'M', 'ᵽ'), + (0x2C64, 'M', 'ɽ'), + (0x2C65, 'V'), + (0x2C67, 'M', 'ⱨ'), + (0x2C68, 'V'), + (0x2C69, 'M', 'ⱪ'), + (0x2C6A, 'V'), + (0x2C6B, 'M', 'ⱬ'), + (0x2C6C, 'V'), + (0x2C6D, 'M', 'ɑ'), + (0x2C6E, 'M', 'ɱ'), + (0x2C6F, 'M', 'ɐ'), + (0x2C70, 'M', 'ɒ'), (0x2C71, 'V'), - (0x2C72, 'M', u'ⱳ'), + (0x2C72, 'M', 'ⱳ'), (0x2C73, 'V'), - (0x2C75, 'M', u'ⱶ'), + (0x2C75, 'M', 'ⱶ'), (0x2C76, 'V'), - (0x2C7C, 'M', u'j'), - (0x2C7D, 'M', u'v'), - (0x2C7E, 'M', u'ȿ'), - (0x2C7F, 'M', u'ɀ'), - (0x2C80, 'M', u'ⲁ'), + (0x2C7C, 'M', 'j'), + (0x2C7D, 'M', 'v'), + (0x2C7E, 'M', 'ȿ'), + (0x2C7F, 'M', 'ɀ'), + (0x2C80, 'M', 'ⲁ'), (0x2C81, 'V'), - (0x2C82, 'M', u'ⲃ'), + (0x2C82, 'M', 'ⲃ'), (0x2C83, 'V'), - (0x2C84, 'M', u'ⲅ'), + (0x2C84, 'M', 'ⲅ'), (0x2C85, 'V'), - (0x2C86, 'M', u'ⲇ'), + (0x2C86, 'M', 'ⲇ'), (0x2C87, 'V'), - (0x2C88, 'M', u'ⲉ'), + (0x2C88, 'M', 'ⲉ'), (0x2C89, 'V'), - (0x2C8A, 'M', u'ⲋ'), + (0x2C8A, 'M', 'ⲋ'), (0x2C8B, 'V'), - (0x2C8C, 'M', u'ⲍ'), + (0x2C8C, 'M', 'ⲍ'), (0x2C8D, 'V'), - (0x2C8E, 'M', u'ⲏ'), + (0x2C8E, 'M', 'ⲏ'), (0x2C8F, 'V'), - (0x2C90, 'M', u'ⲑ'), + (0x2C90, 'M', 'ⲑ'), (0x2C91, 'V'), - (0x2C92, 'M', u'ⲓ'), + (0x2C92, 'M', 'ⲓ'), (0x2C93, 'V'), - (0x2C94, 'M', u'ⲕ'), + (0x2C94, 'M', 'ⲕ'), (0x2C95, 'V'), - (0x2C96, 'M', u'ⲗ'), + (0x2C96, 'M', 'ⲗ'), (0x2C97, 'V'), - (0x2C98, 'M', u'ⲙ'), + (0x2C98, 'M', 'ⲙ'), (0x2C99, 'V'), - (0x2C9A, 'M', u'ⲛ'), + (0x2C9A, 'M', 'ⲛ'), (0x2C9B, 'V'), - (0x2C9C, 'M', u'ⲝ'), + (0x2C9C, 'M', 'ⲝ'), (0x2C9D, 'V'), - (0x2C9E, 'M', u'ⲟ'), + (0x2C9E, 'M', 'ⲟ'), (0x2C9F, 'V'), - (0x2CA0, 'M', u'ⲡ'), + (0x2CA0, 'M', 'ⲡ'), (0x2CA1, 'V'), - (0x2CA2, 'M', u'ⲣ'), + (0x2CA2, 'M', 'ⲣ'), (0x2CA3, 'V'), - (0x2CA4, 'M', u'ⲥ'), + (0x2CA4, 'M', 'ⲥ'), (0x2CA5, 'V'), - (0x2CA6, 'M', u'ⲧ'), + (0x2CA6, 'M', 'ⲧ'), (0x2CA7, 'V'), - (0x2CA8, 'M', u'ⲩ'), + (0x2CA8, 'M', 'ⲩ'), (0x2CA9, 'V'), - (0x2CAA, 'M', u'ⲫ'), + (0x2CAA, 'M', 'ⲫ'), (0x2CAB, 'V'), - (0x2CAC, 'M', u'ⲭ'), + (0x2CAC, 'M', 'ⲭ'), (0x2CAD, 'V'), - (0x2CAE, 'M', u'ⲯ'), + (0x2CAE, 'M', 'ⲯ'), (0x2CAF, 'V'), - (0x2CB0, 'M', u'ⲱ'), + (0x2CB0, 'M', 'ⲱ'), (0x2CB1, 'V'), - (0x2CB2, 'M', u'ⲳ'), + (0x2CB2, 'M', 'ⲳ'), (0x2CB3, 'V'), - (0x2CB4, 'M', u'ⲵ'), + (0x2CB4, 'M', 'ⲵ'), (0x2CB5, 'V'), - (0x2CB6, 'M', u'ⲷ'), + (0x2CB6, 'M', 'ⲷ'), (0x2CB7, 'V'), - (0x2CB8, 'M', u'ⲹ'), + (0x2CB8, 'M', 'ⲹ'), (0x2CB9, 'V'), - (0x2CBA, 'M', u'ⲻ'), + (0x2CBA, 'M', 'ⲻ'), (0x2CBB, 'V'), - (0x2CBC, 'M', u'ⲽ'), + (0x2CBC, 'M', 'ⲽ'), (0x2CBD, 'V'), - (0x2CBE, 'M', u'ⲿ'), - (0x2CBF, 'V'), - (0x2CC0, 'M', u'ⳁ'), - (0x2CC1, 'V'), - (0x2CC2, 'M', u'ⳃ'), - (0x2CC3, 'V'), - (0x2CC4, 'M', u'ⳅ'), - (0x2CC5, 'V'), - (0x2CC6, 'M', u'ⳇ'), - (0x2CC7, 'V'), - (0x2CC8, 'M', u'ⳉ'), - (0x2CC9, 'V'), - (0x2CCA, 'M', u'ⳋ'), - (0x2CCB, 'V'), - (0x2CCC, 'M', u'ⳍ'), - (0x2CCD, 'V'), - (0x2CCE, 'M', u'ⳏ'), - (0x2CCF, 'V'), - (0x2CD0, 'M', u'ⳑ'), - (0x2CD1, 'V'), - (0x2CD2, 'M', u'ⳓ'), - (0x2CD3, 'V'), - (0x2CD4, 'M', u'ⳕ'), - (0x2CD5, 'V'), - (0x2CD6, 'M', u'ⳗ'), - (0x2CD7, 'V'), - (0x2CD8, 'M', u'ⳙ'), - (0x2CD9, 'V'), - (0x2CDA, 'M', u'ⳛ'), + (0x2CBE, 'M', 'ⲿ'), ] def _seg_26(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ + (0x2CBF, 'V'), + (0x2CC0, 'M', 'ⳁ'), + (0x2CC1, 'V'), + (0x2CC2, 'M', 'ⳃ'), + (0x2CC3, 'V'), + (0x2CC4, 'M', 'ⳅ'), + (0x2CC5, 'V'), + (0x2CC6, 'M', 'ⳇ'), + (0x2CC7, 'V'), + (0x2CC8, 'M', 'ⳉ'), + (0x2CC9, 'V'), + (0x2CCA, 'M', 'ⳋ'), + (0x2CCB, 'V'), + (0x2CCC, 'M', 'ⳍ'), + (0x2CCD, 'V'), + (0x2CCE, 'M', 'ⳏ'), + (0x2CCF, 'V'), + (0x2CD0, 'M', 'ⳑ'), + (0x2CD1, 'V'), + (0x2CD2, 'M', 'ⳓ'), + (0x2CD3, 'V'), + (0x2CD4, 'M', 'ⳕ'), + (0x2CD5, 'V'), + (0x2CD6, 'M', 'ⳗ'), + (0x2CD7, 'V'), + (0x2CD8, 'M', 'ⳙ'), + (0x2CD9, 'V'), + (0x2CDA, 'M', 'ⳛ'), (0x2CDB, 'V'), - (0x2CDC, 'M', u'ⳝ'), + (0x2CDC, 'M', 'ⳝ'), (0x2CDD, 'V'), - (0x2CDE, 'M', u'ⳟ'), + (0x2CDE, 'M', 'ⳟ'), (0x2CDF, 'V'), - (0x2CE0, 'M', u'ⳡ'), + (0x2CE0, 'M', 'ⳡ'), (0x2CE1, 'V'), - (0x2CE2, 'M', u'ⳣ'), + (0x2CE2, 'M', 'ⳣ'), (0x2CE3, 'V'), - (0x2CEB, 'M', u'ⳬ'), + (0x2CEB, 'M', 'ⳬ'), (0x2CEC, 'V'), - (0x2CED, 'M', u'ⳮ'), + (0x2CED, 'M', 'ⳮ'), (0x2CEE, 'V'), - (0x2CF2, 'M', u'ⳳ'), + (0x2CF2, 'M', 'ⳳ'), (0x2CF3, 'V'), (0x2CF4, 'X'), (0x2CF9, 'V'), @@ -2735,7 +2791,7 @@ def _seg_26(): (0x2D2E, 'X'), (0x2D30, 'V'), (0x2D68, 'X'), - (0x2D6F, 'M', u'ⵡ'), + (0x2D6F, 'M', 'ⵡ'), (0x2D70, 'V'), (0x2D71, 'X'), (0x2D7F, 'V'), @@ -2757,1148 +2813,1172 @@ def _seg_26(): (0x2DD8, 'V'), (0x2DDF, 'X'), (0x2DE0, 'V'), - (0x2E4F, 'X'), + (0x2E53, 'X'), (0x2E80, 'V'), (0x2E9A, 'X'), (0x2E9B, 'V'), - (0x2E9F, 'M', u'母'), + (0x2E9F, 'M', '母'), (0x2EA0, 'V'), - (0x2EF3, 'M', u'龟'), + (0x2EF3, 'M', '龟'), (0x2EF4, 'X'), - (0x2F00, 'M', u'一'), - (0x2F01, 'M', u'丨'), - (0x2F02, 'M', u'丶'), - (0x2F03, 'M', u'丿'), - (0x2F04, 'M', u'乙'), - (0x2F05, 'M', u'亅'), - (0x2F06, 'M', u'二'), - (0x2F07, 'M', u'亠'), - (0x2F08, 'M', u'人'), - (0x2F09, 'M', u'儿'), - (0x2F0A, 'M', u'入'), - (0x2F0B, 'M', u'八'), - (0x2F0C, 'M', u'冂'), - (0x2F0D, 'M', u'冖'), - (0x2F0E, 'M', u'冫'), - (0x2F0F, 'M', u'几'), - (0x2F10, 'M', u'凵'), - (0x2F11, 'M', u'刀'), - (0x2F12, 'M', u'力'), - (0x2F13, 'M', u'勹'), - (0x2F14, 'M', u'匕'), - (0x2F15, 'M', u'匚'), - (0x2F16, 'M', u'匸'), - (0x2F17, 'M', u'十'), - (0x2F18, 'M', u'卜'), - (0x2F19, 'M', u'卩'), - (0x2F1A, 'M', u'厂'), - (0x2F1B, 'M', u'厶'), - (0x2F1C, 'M', u'又'), - (0x2F1D, 'M', u'口'), - (0x2F1E, 'M', u'囗'), - (0x2F1F, 'M', u'土'), - (0x2F20, 'M', u'士'), - (0x2F21, 'M', u'夂'), - (0x2F22, 'M', u'夊'), - (0x2F23, 'M', u'夕'), - (0x2F24, 'M', u'大'), - (0x2F25, 'M', u'女'), - (0x2F26, 'M', u'子'), - (0x2F27, 'M', u'宀'), - (0x2F28, 'M', u'寸'), - (0x2F29, 'M', u'小'), - (0x2F2A, 'M', u'尢'), - (0x2F2B, 'M', u'尸'), - (0x2F2C, 'M', u'屮'), - (0x2F2D, 'M', u'山'), + (0x2F00, 'M', '一'), + (0x2F01, 'M', '丨'), + (0x2F02, 'M', '丶'), + (0x2F03, 'M', '丿'), + (0x2F04, 'M', '乙'), + (0x2F05, 'M', '亅'), + (0x2F06, 'M', '二'), + (0x2F07, 'M', '亠'), + (0x2F08, 'M', '人'), + (0x2F09, 'M', '儿'), + (0x2F0A, 'M', '入'), + (0x2F0B, 'M', '八'), + (0x2F0C, 'M', '冂'), + (0x2F0D, 'M', '冖'), + (0x2F0E, 'M', '冫'), + (0x2F0F, 'M', '几'), + (0x2F10, 'M', '凵'), + (0x2F11, 'M', '刀'), ] def _seg_27(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x2F2E, 'M', u'巛'), - (0x2F2F, 'M', u'工'), - (0x2F30, 'M', u'己'), - (0x2F31, 'M', u'巾'), - (0x2F32, 'M', u'干'), - (0x2F33, 'M', u'幺'), - (0x2F34, 'M', u'广'), - (0x2F35, 'M', u'廴'), - (0x2F36, 'M', u'廾'), - (0x2F37, 'M', u'弋'), - (0x2F38, 'M', u'弓'), - (0x2F39, 'M', u'彐'), - (0x2F3A, 'M', u'彡'), - (0x2F3B, 'M', u'彳'), - (0x2F3C, 'M', u'心'), - (0x2F3D, 'M', u'戈'), - (0x2F3E, 'M', u'戶'), - (0x2F3F, 'M', u'手'), - (0x2F40, 'M', u'支'), - (0x2F41, 'M', u'攴'), - (0x2F42, 'M', u'文'), - (0x2F43, 'M', u'斗'), - (0x2F44, 'M', u'斤'), - (0x2F45, 'M', u'方'), - (0x2F46, 'M', u'无'), - (0x2F47, 'M', u'日'), - (0x2F48, 'M', u'曰'), - (0x2F49, 'M', u'月'), - (0x2F4A, 'M', u'木'), - (0x2F4B, 'M', u'欠'), - (0x2F4C, 'M', u'止'), - (0x2F4D, 'M', u'歹'), - (0x2F4E, 'M', u'殳'), - (0x2F4F, 'M', u'毋'), - (0x2F50, 'M', u'比'), - (0x2F51, 'M', u'毛'), - (0x2F52, 'M', u'氏'), - (0x2F53, 'M', u'气'), - (0x2F54, 'M', u'水'), - (0x2F55, 'M', u'火'), - (0x2F56, 'M', u'爪'), - (0x2F57, 'M', u'父'), - (0x2F58, 'M', u'爻'), - (0x2F59, 'M', u'爿'), - (0x2F5A, 'M', u'片'), - (0x2F5B, 'M', u'牙'), - (0x2F5C, 'M', u'牛'), - (0x2F5D, 'M', u'犬'), - (0x2F5E, 'M', u'玄'), - (0x2F5F, 'M', u'玉'), - (0x2F60, 'M', u'瓜'), - (0x2F61, 'M', u'瓦'), - (0x2F62, 'M', u'甘'), - (0x2F63, 'M', u'生'), - (0x2F64, 'M', u'用'), - (0x2F65, 'M', u'田'), - (0x2F66, 'M', u'疋'), - (0x2F67, 'M', u'疒'), - (0x2F68, 'M', u'癶'), - (0x2F69, 'M', u'白'), - (0x2F6A, 'M', u'皮'), - (0x2F6B, 'M', u'皿'), - (0x2F6C, 'M', u'目'), - (0x2F6D, 'M', u'矛'), - (0x2F6E, 'M', u'矢'), - (0x2F6F, 'M', u'石'), - (0x2F70, 'M', u'示'), - (0x2F71, 'M', u'禸'), - (0x2F72, 'M', u'禾'), - (0x2F73, 'M', u'穴'), - (0x2F74, 'M', u'立'), - (0x2F75, 'M', u'竹'), - (0x2F76, 'M', u'米'), - (0x2F77, 'M', u'糸'), - (0x2F78, 'M', u'缶'), - (0x2F79, 'M', u'网'), - (0x2F7A, 'M', u'羊'), - (0x2F7B, 'M', u'羽'), - (0x2F7C, 'M', u'老'), - (0x2F7D, 'M', u'而'), - (0x2F7E, 'M', u'耒'), - (0x2F7F, 'M', u'耳'), - (0x2F80, 'M', u'聿'), - (0x2F81, 'M', u'肉'), - (0x2F82, 'M', u'臣'), - (0x2F83, 'M', u'自'), - (0x2F84, 'M', u'至'), - (0x2F85, 'M', u'臼'), - (0x2F86, 'M', u'舌'), - (0x2F87, 'M', u'舛'), - (0x2F88, 'M', u'舟'), - (0x2F89, 'M', u'艮'), - (0x2F8A, 'M', u'色'), - (0x2F8B, 'M', u'艸'), - (0x2F8C, 'M', u'虍'), - (0x2F8D, 'M', u'虫'), - (0x2F8E, 'M', u'血'), - (0x2F8F, 'M', u'行'), - (0x2F90, 'M', u'衣'), - (0x2F91, 'M', u'襾'), + (0x2F12, 'M', '力'), + (0x2F13, 'M', '勹'), + (0x2F14, 'M', '匕'), + (0x2F15, 'M', '匚'), + (0x2F16, 'M', '匸'), + (0x2F17, 'M', '十'), + (0x2F18, 'M', '卜'), + (0x2F19, 'M', '卩'), + (0x2F1A, 'M', '厂'), + (0x2F1B, 'M', '厶'), + (0x2F1C, 'M', '又'), + (0x2F1D, 'M', '口'), + (0x2F1E, 'M', '囗'), + (0x2F1F, 'M', '土'), + (0x2F20, 'M', '士'), + (0x2F21, 'M', '夂'), + (0x2F22, 'M', '夊'), + (0x2F23, 'M', '夕'), + (0x2F24, 'M', '大'), + (0x2F25, 'M', '女'), + (0x2F26, 'M', '子'), + (0x2F27, 'M', '宀'), + (0x2F28, 'M', '寸'), + (0x2F29, 'M', '小'), + (0x2F2A, 'M', '尢'), + (0x2F2B, 'M', '尸'), + (0x2F2C, 'M', '屮'), + (0x2F2D, 'M', '山'), + (0x2F2E, 'M', '巛'), + (0x2F2F, 'M', '工'), + (0x2F30, 'M', '己'), + (0x2F31, 'M', '巾'), + (0x2F32, 'M', '干'), + (0x2F33, 'M', '幺'), + (0x2F34, 'M', '广'), + (0x2F35, 'M', '廴'), + (0x2F36, 'M', '廾'), + (0x2F37, 'M', '弋'), + (0x2F38, 'M', '弓'), + (0x2F39, 'M', '彐'), + (0x2F3A, 'M', '彡'), + (0x2F3B, 'M', '彳'), + (0x2F3C, 'M', '心'), + (0x2F3D, 'M', '戈'), + (0x2F3E, 'M', '戶'), + (0x2F3F, 'M', '手'), + (0x2F40, 'M', '支'), + (0x2F41, 'M', '攴'), + (0x2F42, 'M', '文'), + (0x2F43, 'M', '斗'), + (0x2F44, 'M', '斤'), + (0x2F45, 'M', '方'), + (0x2F46, 'M', '无'), + (0x2F47, 'M', '日'), + (0x2F48, 'M', '曰'), + (0x2F49, 'M', '月'), + (0x2F4A, 'M', '木'), + (0x2F4B, 'M', '欠'), + (0x2F4C, 'M', '止'), + (0x2F4D, 'M', '歹'), + (0x2F4E, 'M', '殳'), + (0x2F4F, 'M', '毋'), + (0x2F50, 'M', '比'), + (0x2F51, 'M', '毛'), + (0x2F52, 'M', '氏'), + (0x2F53, 'M', '气'), + (0x2F54, 'M', '水'), + (0x2F55, 'M', '火'), + (0x2F56, 'M', '爪'), + (0x2F57, 'M', '父'), + (0x2F58, 'M', '爻'), + (0x2F59, 'M', '爿'), + (0x2F5A, 'M', '片'), + (0x2F5B, 'M', '牙'), + (0x2F5C, 'M', '牛'), + (0x2F5D, 'M', '犬'), + (0x2F5E, 'M', '玄'), + (0x2F5F, 'M', '玉'), + (0x2F60, 'M', '瓜'), + (0x2F61, 'M', '瓦'), + (0x2F62, 'M', '甘'), + (0x2F63, 'M', '生'), + (0x2F64, 'M', '用'), + (0x2F65, 'M', '田'), + (0x2F66, 'M', '疋'), + (0x2F67, 'M', '疒'), + (0x2F68, 'M', '癶'), + (0x2F69, 'M', '白'), + (0x2F6A, 'M', '皮'), + (0x2F6B, 'M', '皿'), + (0x2F6C, 'M', '目'), + (0x2F6D, 'M', '矛'), + (0x2F6E, 'M', '矢'), + (0x2F6F, 'M', '石'), + (0x2F70, 'M', '示'), + (0x2F71, 'M', '禸'), + (0x2F72, 'M', '禾'), + (0x2F73, 'M', '穴'), + (0x2F74, 'M', '立'), + (0x2F75, 'M', '竹'), ] def _seg_28(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x2F92, 'M', u'見'), - (0x2F93, 'M', u'角'), - (0x2F94, 'M', u'言'), - (0x2F95, 'M', u'谷'), - (0x2F96, 'M', u'豆'), - (0x2F97, 'M', u'豕'), - (0x2F98, 'M', u'豸'), - (0x2F99, 'M', u'貝'), - (0x2F9A, 'M', u'赤'), - (0x2F9B, 'M', u'走'), - (0x2F9C, 'M', u'足'), - (0x2F9D, 'M', u'身'), - (0x2F9E, 'M', u'車'), - (0x2F9F, 'M', u'辛'), - (0x2FA0, 'M', u'辰'), - (0x2FA1, 'M', u'辵'), - (0x2FA2, 'M', u'邑'), - (0x2FA3, 'M', u'酉'), - (0x2FA4, 'M', u'釆'), - (0x2FA5, 'M', u'里'), - (0x2FA6, 'M', u'金'), - (0x2FA7, 'M', u'長'), - (0x2FA8, 'M', u'門'), - (0x2FA9, 'M', u'阜'), - (0x2FAA, 'M', u'隶'), - (0x2FAB, 'M', u'隹'), - (0x2FAC, 'M', u'雨'), - (0x2FAD, 'M', u'靑'), - (0x2FAE, 'M', u'非'), - (0x2FAF, 'M', u'面'), - (0x2FB0, 'M', u'革'), - (0x2FB1, 'M', u'韋'), - (0x2FB2, 'M', u'韭'), - (0x2FB3, 'M', u'音'), - (0x2FB4, 'M', u'頁'), - (0x2FB5, 'M', u'風'), - (0x2FB6, 'M', u'飛'), - (0x2FB7, 'M', u'食'), - (0x2FB8, 'M', u'首'), - (0x2FB9, 'M', u'香'), - (0x2FBA, 'M', u'馬'), - (0x2FBB, 'M', u'骨'), - (0x2FBC, 'M', u'高'), - (0x2FBD, 'M', u'髟'), - (0x2FBE, 'M', u'鬥'), - (0x2FBF, 'M', u'鬯'), - (0x2FC0, 'M', u'鬲'), - (0x2FC1, 'M', u'鬼'), - (0x2FC2, 'M', u'魚'), - (0x2FC3, 'M', u'鳥'), - (0x2FC4, 'M', u'鹵'), - (0x2FC5, 'M', u'鹿'), - (0x2FC6, 'M', u'麥'), - (0x2FC7, 'M', u'麻'), - (0x2FC8, 'M', u'黃'), - (0x2FC9, 'M', u'黍'), - (0x2FCA, 'M', u'黑'), - (0x2FCB, 'M', u'黹'), - (0x2FCC, 'M', u'黽'), - (0x2FCD, 'M', u'鼎'), - (0x2FCE, 'M', u'鼓'), - (0x2FCF, 'M', u'鼠'), - (0x2FD0, 'M', u'鼻'), - (0x2FD1, 'M', u'齊'), - (0x2FD2, 'M', u'齒'), - (0x2FD3, 'M', u'龍'), - (0x2FD4, 'M', u'龜'), - (0x2FD5, 'M', u'龠'), + (0x2F76, 'M', '米'), + (0x2F77, 'M', '糸'), + (0x2F78, 'M', '缶'), + (0x2F79, 'M', '网'), + (0x2F7A, 'M', '羊'), + (0x2F7B, 'M', '羽'), + (0x2F7C, 'M', '老'), + (0x2F7D, 'M', '而'), + (0x2F7E, 'M', '耒'), + (0x2F7F, 'M', '耳'), + (0x2F80, 'M', '聿'), + (0x2F81, 'M', '肉'), + (0x2F82, 'M', '臣'), + (0x2F83, 'M', '自'), + (0x2F84, 'M', '至'), + (0x2F85, 'M', '臼'), + (0x2F86, 'M', '舌'), + (0x2F87, 'M', '舛'), + (0x2F88, 'M', '舟'), + (0x2F89, 'M', '艮'), + (0x2F8A, 'M', '色'), + (0x2F8B, 'M', '艸'), + (0x2F8C, 'M', '虍'), + (0x2F8D, 'M', '虫'), + (0x2F8E, 'M', '血'), + (0x2F8F, 'M', '行'), + (0x2F90, 'M', '衣'), + (0x2F91, 'M', '襾'), + (0x2F92, 'M', '見'), + (0x2F93, 'M', '角'), + (0x2F94, 'M', '言'), + (0x2F95, 'M', '谷'), + (0x2F96, 'M', '豆'), + (0x2F97, 'M', '豕'), + (0x2F98, 'M', '豸'), + (0x2F99, 'M', '貝'), + (0x2F9A, 'M', '赤'), + (0x2F9B, 'M', '走'), + (0x2F9C, 'M', '足'), + (0x2F9D, 'M', '身'), + (0x2F9E, 'M', '車'), + (0x2F9F, 'M', '辛'), + (0x2FA0, 'M', '辰'), + (0x2FA1, 'M', '辵'), + (0x2FA2, 'M', '邑'), + (0x2FA3, 'M', '酉'), + (0x2FA4, 'M', '釆'), + (0x2FA5, 'M', '里'), + (0x2FA6, 'M', '金'), + (0x2FA7, 'M', '長'), + (0x2FA8, 'M', '門'), + (0x2FA9, 'M', '阜'), + (0x2FAA, 'M', '隶'), + (0x2FAB, 'M', '隹'), + (0x2FAC, 'M', '雨'), + (0x2FAD, 'M', '靑'), + (0x2FAE, 'M', '非'), + (0x2FAF, 'M', '面'), + (0x2FB0, 'M', '革'), + (0x2FB1, 'M', '韋'), + (0x2FB2, 'M', '韭'), + (0x2FB3, 'M', '音'), + (0x2FB4, 'M', '頁'), + (0x2FB5, 'M', '風'), + (0x2FB6, 'M', '飛'), + (0x2FB7, 'M', '食'), + (0x2FB8, 'M', '首'), + (0x2FB9, 'M', '香'), + (0x2FBA, 'M', '馬'), + (0x2FBB, 'M', '骨'), + (0x2FBC, 'M', '高'), + (0x2FBD, 'M', '髟'), + (0x2FBE, 'M', '鬥'), + (0x2FBF, 'M', '鬯'), + (0x2FC0, 'M', '鬲'), + (0x2FC1, 'M', '鬼'), + (0x2FC2, 'M', '魚'), + (0x2FC3, 'M', '鳥'), + (0x2FC4, 'M', '鹵'), + (0x2FC5, 'M', '鹿'), + (0x2FC6, 'M', '麥'), + (0x2FC7, 'M', '麻'), + (0x2FC8, 'M', '黃'), + (0x2FC9, 'M', '黍'), + (0x2FCA, 'M', '黑'), + (0x2FCB, 'M', '黹'), + (0x2FCC, 'M', '黽'), + (0x2FCD, 'M', '鼎'), + (0x2FCE, 'M', '鼓'), + (0x2FCF, 'M', '鼠'), + (0x2FD0, 'M', '鼻'), + (0x2FD1, 'M', '齊'), + (0x2FD2, 'M', '齒'), + (0x2FD3, 'M', '龍'), + (0x2FD4, 'M', '龜'), + (0x2FD5, 'M', '龠'), (0x2FD6, 'X'), - (0x3000, '3', u' '), + (0x3000, '3', ' '), (0x3001, 'V'), - (0x3002, 'M', u'.'), + (0x3002, 'M', '.'), + ] + +def _seg_29(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ (0x3003, 'V'), - (0x3036, 'M', u'〒'), + (0x3036, 'M', '〒'), (0x3037, 'V'), - (0x3038, 'M', u'十'), - (0x3039, 'M', u'卄'), - (0x303A, 'M', u'卅'), + (0x3038, 'M', '十'), + (0x3039, 'M', '卄'), + (0x303A, 'M', '卅'), (0x303B, 'V'), (0x3040, 'X'), (0x3041, 'V'), (0x3097, 'X'), (0x3099, 'V'), - (0x309B, '3', u' ゙'), - (0x309C, '3', u' ゚'), + (0x309B, '3', ' ゙'), + (0x309C, '3', ' ゚'), (0x309D, 'V'), - (0x309F, 'M', u'より'), + (0x309F, 'M', 'より'), (0x30A0, 'V'), - (0x30FF, 'M', u'コト'), + (0x30FF, 'M', 'コト'), (0x3100, 'X'), (0x3105, 'V'), (0x3130, 'X'), - (0x3131, 'M', u'ᄀ'), - (0x3132, 'M', u'ᄁ'), - (0x3133, 'M', u'ᆪ'), - (0x3134, 'M', u'ᄂ'), - (0x3135, 'M', u'ᆬ'), - (0x3136, 'M', u'ᆭ'), - (0x3137, 'M', u'ᄃ'), - (0x3138, 'M', u'ᄄ'), - ] - -def _seg_29(): - return [ - (0x3139, 'M', u'ᄅ'), - (0x313A, 'M', u'ᆰ'), - (0x313B, 'M', u'ᆱ'), - (0x313C, 'M', u'ᆲ'), - (0x313D, 'M', u'ᆳ'), - (0x313E, 'M', u'ᆴ'), - (0x313F, 'M', u'ᆵ'), - (0x3140, 'M', u'ᄚ'), - (0x3141, 'M', u'ᄆ'), - (0x3142, 'M', u'ᄇ'), - (0x3143, 'M', u'ᄈ'), - (0x3144, 'M', u'ᄡ'), - (0x3145, 'M', u'ᄉ'), - (0x3146, 'M', u'ᄊ'), - (0x3147, 'M', u'ᄋ'), - (0x3148, 'M', u'ᄌ'), - (0x3149, 'M', u'ᄍ'), - (0x314A, 'M', u'ᄎ'), - (0x314B, 'M', u'ᄏ'), - (0x314C, 'M', u'ᄐ'), - (0x314D, 'M', u'ᄑ'), - (0x314E, 'M', u'ᄒ'), - (0x314F, 'M', u'ᅡ'), - (0x3150, 'M', u'ᅢ'), - (0x3151, 'M', u'ᅣ'), - (0x3152, 'M', u'ᅤ'), - (0x3153, 'M', u'ᅥ'), - (0x3154, 'M', u'ᅦ'), - (0x3155, 'M', u'ᅧ'), - (0x3156, 'M', u'ᅨ'), - (0x3157, 'M', u'ᅩ'), - (0x3158, 'M', u'ᅪ'), - (0x3159, 'M', u'ᅫ'), - (0x315A, 'M', u'ᅬ'), - (0x315B, 'M', u'ᅭ'), - (0x315C, 'M', u'ᅮ'), - (0x315D, 'M', u'ᅯ'), - (0x315E, 'M', u'ᅰ'), - (0x315F, 'M', u'ᅱ'), - (0x3160, 'M', u'ᅲ'), - (0x3161, 'M', u'ᅳ'), - (0x3162, 'M', u'ᅴ'), - (0x3163, 'M', u'ᅵ'), + (0x3131, 'M', 'ᄀ'), + (0x3132, 'M', 'ᄁ'), + (0x3133, 'M', 'ᆪ'), + (0x3134, 'M', 'ᄂ'), + (0x3135, 'M', 'ᆬ'), + (0x3136, 'M', 'ᆭ'), + (0x3137, 'M', 'ᄃ'), + (0x3138, 'M', 'ᄄ'), + (0x3139, 'M', 'ᄅ'), + (0x313A, 'M', 'ᆰ'), + (0x313B, 'M', 'ᆱ'), + (0x313C, 'M', 'ᆲ'), + (0x313D, 'M', 'ᆳ'), + (0x313E, 'M', 'ᆴ'), + (0x313F, 'M', 'ᆵ'), + (0x3140, 'M', 'ᄚ'), + (0x3141, 'M', 'ᄆ'), + (0x3142, 'M', 'ᄇ'), + (0x3143, 'M', 'ᄈ'), + (0x3144, 'M', 'ᄡ'), + (0x3145, 'M', 'ᄉ'), + (0x3146, 'M', 'ᄊ'), + (0x3147, 'M', 'ᄋ'), + (0x3148, 'M', 'ᄌ'), + (0x3149, 'M', 'ᄍ'), + (0x314A, 'M', 'ᄎ'), + (0x314B, 'M', 'ᄏ'), + (0x314C, 'M', 'ᄐ'), + (0x314D, 'M', 'ᄑ'), + (0x314E, 'M', 'ᄒ'), + (0x314F, 'M', 'ᅡ'), + (0x3150, 'M', 'ᅢ'), + (0x3151, 'M', 'ᅣ'), + (0x3152, 'M', 'ᅤ'), + (0x3153, 'M', 'ᅥ'), + (0x3154, 'M', 'ᅦ'), + (0x3155, 'M', 'ᅧ'), + (0x3156, 'M', 'ᅨ'), + (0x3157, 'M', 'ᅩ'), + (0x3158, 'M', 'ᅪ'), + (0x3159, 'M', 'ᅫ'), + (0x315A, 'M', 'ᅬ'), + (0x315B, 'M', 'ᅭ'), + (0x315C, 'M', 'ᅮ'), + (0x315D, 'M', 'ᅯ'), + (0x315E, 'M', 'ᅰ'), + (0x315F, 'M', 'ᅱ'), + (0x3160, 'M', 'ᅲ'), + (0x3161, 'M', 'ᅳ'), + (0x3162, 'M', 'ᅴ'), + (0x3163, 'M', 'ᅵ'), (0x3164, 'X'), - (0x3165, 'M', u'ᄔ'), - (0x3166, 'M', u'ᄕ'), - (0x3167, 'M', u'ᇇ'), - (0x3168, 'M', u'ᇈ'), - (0x3169, 'M', u'ᇌ'), - (0x316A, 'M', u'ᇎ'), - (0x316B, 'M', u'ᇓ'), - (0x316C, 'M', u'ᇗ'), - (0x316D, 'M', u'ᇙ'), - (0x316E, 'M', u'ᄜ'), - (0x316F, 'M', u'ᇝ'), - (0x3170, 'M', u'ᇟ'), - (0x3171, 'M', u'ᄝ'), - (0x3172, 'M', u'ᄞ'), - (0x3173, 'M', u'ᄠ'), - (0x3174, 'M', u'ᄢ'), - (0x3175, 'M', u'ᄣ'), - (0x3176, 'M', u'ᄧ'), - (0x3177, 'M', u'ᄩ'), - (0x3178, 'M', u'ᄫ'), - (0x3179, 'M', u'ᄬ'), - (0x317A, 'M', u'ᄭ'), - (0x317B, 'M', u'ᄮ'), - (0x317C, 'M', u'ᄯ'), - (0x317D, 'M', u'ᄲ'), - (0x317E, 'M', u'ᄶ'), - (0x317F, 'M', u'ᅀ'), - (0x3180, 'M', u'ᅇ'), - (0x3181, 'M', u'ᅌ'), - (0x3182, 'M', u'ᇱ'), - (0x3183, 'M', u'ᇲ'), - (0x3184, 'M', u'ᅗ'), - (0x3185, 'M', u'ᅘ'), - (0x3186, 'M', u'ᅙ'), - (0x3187, 'M', u'ᆄ'), - (0x3188, 'M', u'ᆅ'), - (0x3189, 'M', u'ᆈ'), - (0x318A, 'M', u'ᆑ'), - (0x318B, 'M', u'ᆒ'), - (0x318C, 'M', u'ᆔ'), - (0x318D, 'M', u'ᆞ'), - (0x318E, 'M', u'ᆡ'), - (0x318F, 'X'), - (0x3190, 'V'), - (0x3192, 'M', u'一'), - (0x3193, 'M', u'二'), - (0x3194, 'M', u'三'), - (0x3195, 'M', u'四'), - (0x3196, 'M', u'上'), - (0x3197, 'M', u'中'), - (0x3198, 'M', u'下'), - (0x3199, 'M', u'甲'), - (0x319A, 'M', u'乙'), - (0x319B, 'M', u'丙'), - (0x319C, 'M', u'丁'), - (0x319D, 'M', u'天'), + (0x3165, 'M', 'ᄔ'), + (0x3166, 'M', 'ᄕ'), + (0x3167, 'M', 'ᇇ'), + (0x3168, 'M', 'ᇈ'), + (0x3169, 'M', 'ᇌ'), + (0x316A, 'M', 'ᇎ'), + (0x316B, 'M', 'ᇓ'), + (0x316C, 'M', 'ᇗ'), + (0x316D, 'M', 'ᇙ'), + (0x316E, 'M', 'ᄜ'), + (0x316F, 'M', 'ᇝ'), + (0x3170, 'M', 'ᇟ'), + (0x3171, 'M', 'ᄝ'), + (0x3172, 'M', 'ᄞ'), + (0x3173, 'M', 'ᄠ'), + (0x3174, 'M', 'ᄢ'), + (0x3175, 'M', 'ᄣ'), + (0x3176, 'M', 'ᄧ'), + (0x3177, 'M', 'ᄩ'), + (0x3178, 'M', 'ᄫ'), + (0x3179, 'M', 'ᄬ'), + (0x317A, 'M', 'ᄭ'), + (0x317B, 'M', 'ᄮ'), + (0x317C, 'M', 'ᄯ'), + (0x317D, 'M', 'ᄲ'), + (0x317E, 'M', 'ᄶ'), + (0x317F, 'M', 'ᅀ'), + (0x3180, 'M', 'ᅇ'), ] def _seg_30(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x319E, 'M', u'地'), - (0x319F, 'M', u'人'), + (0x3181, 'M', 'ᅌ'), + (0x3182, 'M', 'ᇱ'), + (0x3183, 'M', 'ᇲ'), + (0x3184, 'M', 'ᅗ'), + (0x3185, 'M', 'ᅘ'), + (0x3186, 'M', 'ᅙ'), + (0x3187, 'M', 'ᆄ'), + (0x3188, 'M', 'ᆅ'), + (0x3189, 'M', 'ᆈ'), + (0x318A, 'M', 'ᆑ'), + (0x318B, 'M', 'ᆒ'), + (0x318C, 'M', 'ᆔ'), + (0x318D, 'M', 'ᆞ'), + (0x318E, 'M', 'ᆡ'), + (0x318F, 'X'), + (0x3190, 'V'), + (0x3192, 'M', '一'), + (0x3193, 'M', '二'), + (0x3194, 'M', '三'), + (0x3195, 'M', '四'), + (0x3196, 'M', '上'), + (0x3197, 'M', '中'), + (0x3198, 'M', '下'), + (0x3199, 'M', '甲'), + (0x319A, 'M', '乙'), + (0x319B, 'M', '丙'), + (0x319C, 'M', '丁'), + (0x319D, 'M', '天'), + (0x319E, 'M', '地'), + (0x319F, 'M', '人'), (0x31A0, 'V'), - (0x31BB, 'X'), - (0x31C0, 'V'), (0x31E4, 'X'), (0x31F0, 'V'), - (0x3200, '3', u'(ᄀ)'), - (0x3201, '3', u'(ᄂ)'), - (0x3202, '3', u'(ᄃ)'), - (0x3203, '3', u'(ᄅ)'), - (0x3204, '3', u'(ᄆ)'), - (0x3205, '3', u'(ᄇ)'), - (0x3206, '3', u'(ᄉ)'), - (0x3207, '3', u'(ᄋ)'), - (0x3208, '3', u'(ᄌ)'), - (0x3209, '3', u'(ᄎ)'), - (0x320A, '3', u'(ᄏ)'), - (0x320B, '3', u'(ᄐ)'), - (0x320C, '3', u'(ᄑ)'), - (0x320D, '3', u'(ᄒ)'), - (0x320E, '3', u'(가)'), - (0x320F, '3', u'(나)'), - (0x3210, '3', u'(다)'), - (0x3211, '3', u'(라)'), - (0x3212, '3', u'(마)'), - (0x3213, '3', u'(바)'), - (0x3214, '3', u'(사)'), - (0x3215, '3', u'(아)'), - (0x3216, '3', u'(자)'), - (0x3217, '3', u'(차)'), - (0x3218, '3', u'(카)'), - (0x3219, '3', u'(타)'), - (0x321A, '3', u'(파)'), - (0x321B, '3', u'(하)'), - (0x321C, '3', u'(주)'), - (0x321D, '3', u'(오전)'), - (0x321E, '3', u'(오후)'), + (0x3200, '3', '(ᄀ)'), + (0x3201, '3', '(ᄂ)'), + (0x3202, '3', '(ᄃ)'), + (0x3203, '3', '(ᄅ)'), + (0x3204, '3', '(ᄆ)'), + (0x3205, '3', '(ᄇ)'), + (0x3206, '3', '(ᄉ)'), + (0x3207, '3', '(ᄋ)'), + (0x3208, '3', '(ᄌ)'), + (0x3209, '3', '(ᄎ)'), + (0x320A, '3', '(ᄏ)'), + (0x320B, '3', '(ᄐ)'), + (0x320C, '3', '(ᄑ)'), + (0x320D, '3', '(ᄒ)'), + (0x320E, '3', '(가)'), + (0x320F, '3', '(나)'), + (0x3210, '3', '(다)'), + (0x3211, '3', '(라)'), + (0x3212, '3', '(마)'), + (0x3213, '3', '(바)'), + (0x3214, '3', '(사)'), + (0x3215, '3', '(아)'), + (0x3216, '3', '(자)'), + (0x3217, '3', '(차)'), + (0x3218, '3', '(카)'), + (0x3219, '3', '(타)'), + (0x321A, '3', '(파)'), + (0x321B, '3', '(하)'), + (0x321C, '3', '(주)'), + (0x321D, '3', '(오전)'), + (0x321E, '3', '(오후)'), (0x321F, 'X'), - (0x3220, '3', u'(一)'), - (0x3221, '3', u'(二)'), - (0x3222, '3', u'(三)'), - (0x3223, '3', u'(四)'), - (0x3224, '3', u'(五)'), - (0x3225, '3', u'(六)'), - (0x3226, '3', u'(七)'), - (0x3227, '3', u'(八)'), - (0x3228, '3', u'(九)'), - (0x3229, '3', u'(十)'), - (0x322A, '3', u'(月)'), - (0x322B, '3', u'(火)'), - (0x322C, '3', u'(水)'), - (0x322D, '3', u'(木)'), - (0x322E, '3', u'(金)'), - (0x322F, '3', u'(土)'), - (0x3230, '3', u'(日)'), - (0x3231, '3', u'(株)'), - (0x3232, '3', u'(有)'), - (0x3233, '3', u'(社)'), - (0x3234, '3', u'(名)'), - (0x3235, '3', u'(特)'), - (0x3236, '3', u'(財)'), - (0x3237, '3', u'(祝)'), - (0x3238, '3', u'(労)'), - (0x3239, '3', u'(代)'), - (0x323A, '3', u'(呼)'), - (0x323B, '3', u'(学)'), - (0x323C, '3', u'(監)'), - (0x323D, '3', u'(企)'), - (0x323E, '3', u'(資)'), - (0x323F, '3', u'(協)'), - (0x3240, '3', u'(祭)'), - (0x3241, '3', u'(休)'), - (0x3242, '3', u'(自)'), - (0x3243, '3', u'(至)'), - (0x3244, 'M', u'問'), - (0x3245, 'M', u'幼'), - (0x3246, 'M', u'文'), - (0x3247, 'M', u'箏'), - (0x3248, 'V'), - (0x3250, 'M', u'pte'), - (0x3251, 'M', u'21'), - (0x3252, 'M', u'22'), - (0x3253, 'M', u'23'), - (0x3254, 'M', u'24'), - (0x3255, 'M', u'25'), - (0x3256, 'M', u'26'), - (0x3257, 'M', u'27'), - (0x3258, 'M', u'28'), - (0x3259, 'M', u'29'), - (0x325A, 'M', u'30'), - (0x325B, 'M', u'31'), - (0x325C, 'M', u'32'), - (0x325D, 'M', u'33'), - (0x325E, 'M', u'34'), - (0x325F, 'M', u'35'), - (0x3260, 'M', u'ᄀ'), - (0x3261, 'M', u'ᄂ'), - (0x3262, 'M', u'ᄃ'), - (0x3263, 'M', u'ᄅ'), + (0x3220, '3', '(一)'), + (0x3221, '3', '(二)'), + (0x3222, '3', '(三)'), + (0x3223, '3', '(四)'), + (0x3224, '3', '(五)'), + (0x3225, '3', '(六)'), + (0x3226, '3', '(七)'), + (0x3227, '3', '(八)'), + (0x3228, '3', '(九)'), + (0x3229, '3', '(十)'), + (0x322A, '3', '(月)'), + (0x322B, '3', '(火)'), + (0x322C, '3', '(水)'), + (0x322D, '3', '(木)'), + (0x322E, '3', '(金)'), + (0x322F, '3', '(土)'), + (0x3230, '3', '(日)'), + (0x3231, '3', '(株)'), + (0x3232, '3', '(有)'), + (0x3233, '3', '(社)'), + (0x3234, '3', '(名)'), + (0x3235, '3', '(特)'), + (0x3236, '3', '(財)'), + (0x3237, '3', '(祝)'), + (0x3238, '3', '(労)'), + (0x3239, '3', '(代)'), + (0x323A, '3', '(呼)'), + (0x323B, '3', '(学)'), + (0x323C, '3', '(監)'), + (0x323D, '3', '(企)'), + (0x323E, '3', '(資)'), + (0x323F, '3', '(協)'), + (0x3240, '3', '(祭)'), + (0x3241, '3', '(休)'), + (0x3242, '3', '(自)'), ] def _seg_31(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x3264, 'M', u'ᄆ'), - (0x3265, 'M', u'ᄇ'), - (0x3266, 'M', u'ᄉ'), - (0x3267, 'M', u'ᄋ'), - (0x3268, 'M', u'ᄌ'), - (0x3269, 'M', u'ᄎ'), - (0x326A, 'M', u'ᄏ'), - (0x326B, 'M', u'ᄐ'), - (0x326C, 'M', u'ᄑ'), - (0x326D, 'M', u'ᄒ'), - (0x326E, 'M', u'가'), - (0x326F, 'M', u'나'), - (0x3270, 'M', u'다'), - (0x3271, 'M', u'라'), - (0x3272, 'M', u'마'), - (0x3273, 'M', u'바'), - (0x3274, 'M', u'사'), - (0x3275, 'M', u'아'), - (0x3276, 'M', u'자'), - (0x3277, 'M', u'차'), - (0x3278, 'M', u'카'), - (0x3279, 'M', u'타'), - (0x327A, 'M', u'파'), - (0x327B, 'M', u'하'), - (0x327C, 'M', u'참고'), - (0x327D, 'M', u'주의'), - (0x327E, 'M', u'우'), + (0x3243, '3', '(至)'), + (0x3244, 'M', '問'), + (0x3245, 'M', '幼'), + (0x3246, 'M', '文'), + (0x3247, 'M', '箏'), + (0x3248, 'V'), + (0x3250, 'M', 'pte'), + (0x3251, 'M', '21'), + (0x3252, 'M', '22'), + (0x3253, 'M', '23'), + (0x3254, 'M', '24'), + (0x3255, 'M', '25'), + (0x3256, 'M', '26'), + (0x3257, 'M', '27'), + (0x3258, 'M', '28'), + (0x3259, 'M', '29'), + (0x325A, 'M', '30'), + (0x325B, 'M', '31'), + (0x325C, 'M', '32'), + (0x325D, 'M', '33'), + (0x325E, 'M', '34'), + (0x325F, 'M', '35'), + (0x3260, 'M', 'ᄀ'), + (0x3261, 'M', 'ᄂ'), + (0x3262, 'M', 'ᄃ'), + (0x3263, 'M', 'ᄅ'), + (0x3264, 'M', 'ᄆ'), + (0x3265, 'M', 'ᄇ'), + (0x3266, 'M', 'ᄉ'), + (0x3267, 'M', 'ᄋ'), + (0x3268, 'M', 'ᄌ'), + (0x3269, 'M', 'ᄎ'), + (0x326A, 'M', 'ᄏ'), + (0x326B, 'M', 'ᄐ'), + (0x326C, 'M', 'ᄑ'), + (0x326D, 'M', 'ᄒ'), + (0x326E, 'M', '가'), + (0x326F, 'M', '나'), + (0x3270, 'M', '다'), + (0x3271, 'M', '라'), + (0x3272, 'M', '마'), + (0x3273, 'M', '바'), + (0x3274, 'M', '사'), + (0x3275, 'M', '아'), + (0x3276, 'M', '자'), + (0x3277, 'M', '차'), + (0x3278, 'M', '카'), + (0x3279, 'M', '타'), + (0x327A, 'M', '파'), + (0x327B, 'M', '하'), + (0x327C, 'M', '참고'), + (0x327D, 'M', '주의'), + (0x327E, 'M', '우'), (0x327F, 'V'), - (0x3280, 'M', u'一'), - (0x3281, 'M', u'二'), - (0x3282, 'M', u'三'), - (0x3283, 'M', u'四'), - (0x3284, 'M', u'五'), - (0x3285, 'M', u'六'), - (0x3286, 'M', u'七'), - (0x3287, 'M', u'八'), - (0x3288, 'M', u'九'), - (0x3289, 'M', u'十'), - (0x328A, 'M', u'月'), - (0x328B, 'M', u'火'), - (0x328C, 'M', u'水'), - (0x328D, 'M', u'木'), - (0x328E, 'M', u'金'), - (0x328F, 'M', u'土'), - (0x3290, 'M', u'日'), - (0x3291, 'M', u'株'), - (0x3292, 'M', u'有'), - (0x3293, 'M', u'社'), - (0x3294, 'M', u'名'), - (0x3295, 'M', u'特'), - (0x3296, 'M', u'財'), - (0x3297, 'M', u'祝'), - (0x3298, 'M', u'労'), - (0x3299, 'M', u'秘'), - (0x329A, 'M', u'男'), - (0x329B, 'M', u'女'), - (0x329C, 'M', u'適'), - (0x329D, 'M', u'優'), - (0x329E, 'M', u'印'), - (0x329F, 'M', u'注'), - (0x32A0, 'M', u'項'), - (0x32A1, 'M', u'休'), - (0x32A2, 'M', u'写'), - (0x32A3, 'M', u'正'), - (0x32A4, 'M', u'上'), - (0x32A5, 'M', u'中'), - (0x32A6, 'M', u'下'), - (0x32A7, 'M', u'左'), - (0x32A8, 'M', u'右'), - (0x32A9, 'M', u'医'), - (0x32AA, 'M', u'宗'), - (0x32AB, 'M', u'学'), - (0x32AC, 'M', u'監'), - (0x32AD, 'M', u'企'), - (0x32AE, 'M', u'資'), - (0x32AF, 'M', u'協'), - (0x32B0, 'M', u'夜'), - (0x32B1, 'M', u'36'), - (0x32B2, 'M', u'37'), - (0x32B3, 'M', u'38'), - (0x32B4, 'M', u'39'), - (0x32B5, 'M', u'40'), - (0x32B6, 'M', u'41'), - (0x32B7, 'M', u'42'), - (0x32B8, 'M', u'43'), - (0x32B9, 'M', u'44'), - (0x32BA, 'M', u'45'), - (0x32BB, 'M', u'46'), - (0x32BC, 'M', u'47'), - (0x32BD, 'M', u'48'), - (0x32BE, 'M', u'49'), - (0x32BF, 'M', u'50'), - (0x32C0, 'M', u'1月'), - (0x32C1, 'M', u'2月'), - (0x32C2, 'M', u'3月'), - (0x32C3, 'M', u'4月'), - (0x32C4, 'M', u'5月'), - (0x32C5, 'M', u'6月'), - (0x32C6, 'M', u'7月'), - (0x32C7, 'M', u'8月'), + (0x3280, 'M', '一'), + (0x3281, 'M', '二'), + (0x3282, 'M', '三'), + (0x3283, 'M', '四'), + (0x3284, 'M', '五'), + (0x3285, 'M', '六'), + (0x3286, 'M', '七'), + (0x3287, 'M', '八'), + (0x3288, 'M', '九'), + (0x3289, 'M', '十'), + (0x328A, 'M', '月'), + (0x328B, 'M', '火'), + (0x328C, 'M', '水'), + (0x328D, 'M', '木'), + (0x328E, 'M', '金'), + (0x328F, 'M', '土'), + (0x3290, 'M', '日'), + (0x3291, 'M', '株'), + (0x3292, 'M', '有'), + (0x3293, 'M', '社'), + (0x3294, 'M', '名'), + (0x3295, 'M', '特'), + (0x3296, 'M', '財'), + (0x3297, 'M', '祝'), + (0x3298, 'M', '労'), + (0x3299, 'M', '秘'), + (0x329A, 'M', '男'), + (0x329B, 'M', '女'), + (0x329C, 'M', '適'), + (0x329D, 'M', '優'), + (0x329E, 'M', '印'), + (0x329F, 'M', '注'), + (0x32A0, 'M', '項'), + (0x32A1, 'M', '休'), + (0x32A2, 'M', '写'), + (0x32A3, 'M', '正'), + (0x32A4, 'M', '上'), + (0x32A5, 'M', '中'), + (0x32A6, 'M', '下'), + (0x32A7, 'M', '左'), + (0x32A8, 'M', '右'), + (0x32A9, 'M', '医'), + (0x32AA, 'M', '宗'), + (0x32AB, 'M', '学'), + (0x32AC, 'M', '監'), + (0x32AD, 'M', '企'), ] def _seg_32(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x32C8, 'M', u'9月'), - (0x32C9, 'M', u'10月'), - (0x32CA, 'M', u'11月'), - (0x32CB, 'M', u'12月'), - (0x32CC, 'M', u'hg'), - (0x32CD, 'M', u'erg'), - (0x32CE, 'M', u'ev'), - (0x32CF, 'M', u'ltd'), - (0x32D0, 'M', u'ア'), - (0x32D1, 'M', u'イ'), - (0x32D2, 'M', u'ウ'), - (0x32D3, 'M', u'エ'), - (0x32D4, 'M', u'オ'), - (0x32D5, 'M', u'カ'), - (0x32D6, 'M', u'キ'), - (0x32D7, 'M', u'ク'), - (0x32D8, 'M', u'ケ'), - (0x32D9, 'M', u'コ'), - (0x32DA, 'M', u'サ'), - (0x32DB, 'M', u'シ'), - (0x32DC, 'M', u'ス'), - (0x32DD, 'M', u'セ'), - (0x32DE, 'M', u'ソ'), - (0x32DF, 'M', u'タ'), - (0x32E0, 'M', u'チ'), - (0x32E1, 'M', u'ツ'), - (0x32E2, 'M', u'テ'), - (0x32E3, 'M', u'ト'), - (0x32E4, 'M', u'ナ'), - (0x32E5, 'M', u'ニ'), - (0x32E6, 'M', u'ヌ'), - (0x32E7, 'M', u'ネ'), - (0x32E8, 'M', u'ノ'), - (0x32E9, 'M', u'ハ'), - (0x32EA, 'M', u'ヒ'), - (0x32EB, 'M', u'フ'), - (0x32EC, 'M', u'ヘ'), - (0x32ED, 'M', u'ホ'), - (0x32EE, 'M', u'マ'), - (0x32EF, 'M', u'ミ'), - (0x32F0, 'M', u'ム'), - (0x32F1, 'M', u'メ'), - (0x32F2, 'M', u'モ'), - (0x32F3, 'M', u'ヤ'), - (0x32F4, 'M', u'ユ'), - (0x32F5, 'M', u'ヨ'), - (0x32F6, 'M', u'ラ'), - (0x32F7, 'M', u'リ'), - (0x32F8, 'M', u'ル'), - (0x32F9, 'M', u'レ'), - (0x32FA, 'M', u'ロ'), - (0x32FB, 'M', u'ワ'), - (0x32FC, 'M', u'ヰ'), - (0x32FD, 'M', u'ヱ'), - (0x32FE, 'M', u'ヲ'), - (0x32FF, 'X'), - (0x3300, 'M', u'アパート'), - (0x3301, 'M', u'アルファ'), - (0x3302, 'M', u'アンペア'), - (0x3303, 'M', u'アール'), - (0x3304, 'M', u'イニング'), - (0x3305, 'M', u'インチ'), - (0x3306, 'M', u'ウォン'), - (0x3307, 'M', u'エスクード'), - (0x3308, 'M', u'エーカー'), - (0x3309, 'M', u'オンス'), - (0x330A, 'M', u'オーム'), - (0x330B, 'M', u'カイリ'), - (0x330C, 'M', u'カラット'), - (0x330D, 'M', u'カロリー'), - (0x330E, 'M', u'ガロン'), - (0x330F, 'M', u'ガンマ'), - (0x3310, 'M', u'ギガ'), - (0x3311, 'M', u'ギニー'), - (0x3312, 'M', u'キュリー'), - (0x3313, 'M', u'ギルダー'), - (0x3314, 'M', u'キロ'), - (0x3315, 'M', u'キログラム'), - (0x3316, 'M', u'キロメートル'), - (0x3317, 'M', u'キロワット'), - (0x3318, 'M', u'グラム'), - (0x3319, 'M', u'グラムトン'), - (0x331A, 'M', u'クルゼイロ'), - (0x331B, 'M', u'クローネ'), - (0x331C, 'M', u'ケース'), - (0x331D, 'M', u'コルナ'), - (0x331E, 'M', u'コーポ'), - (0x331F, 'M', u'サイクル'), - (0x3320, 'M', u'サンチーム'), - (0x3321, 'M', u'シリング'), - (0x3322, 'M', u'センチ'), - (0x3323, 'M', u'セント'), - (0x3324, 'M', u'ダース'), - (0x3325, 'M', u'デシ'), - (0x3326, 'M', u'ドル'), - (0x3327, 'M', u'トン'), - (0x3328, 'M', u'ナノ'), - (0x3329, 'M', u'ノット'), - (0x332A, 'M', u'ハイツ'), - (0x332B, 'M', u'パーセント'), + (0x32AE, 'M', '資'), + (0x32AF, 'M', '協'), + (0x32B0, 'M', '夜'), + (0x32B1, 'M', '36'), + (0x32B2, 'M', '37'), + (0x32B3, 'M', '38'), + (0x32B4, 'M', '39'), + (0x32B5, 'M', '40'), + (0x32B6, 'M', '41'), + (0x32B7, 'M', '42'), + (0x32B8, 'M', '43'), + (0x32B9, 'M', '44'), + (0x32BA, 'M', '45'), + (0x32BB, 'M', '46'), + (0x32BC, 'M', '47'), + (0x32BD, 'M', '48'), + (0x32BE, 'M', '49'), + (0x32BF, 'M', '50'), + (0x32C0, 'M', '1月'), + (0x32C1, 'M', '2月'), + (0x32C2, 'M', '3月'), + (0x32C3, 'M', '4月'), + (0x32C4, 'M', '5月'), + (0x32C5, 'M', '6月'), + (0x32C6, 'M', '7月'), + (0x32C7, 'M', '8月'), + (0x32C8, 'M', '9月'), + (0x32C9, 'M', '10月'), + (0x32CA, 'M', '11月'), + (0x32CB, 'M', '12月'), + (0x32CC, 'M', 'hg'), + (0x32CD, 'M', 'erg'), + (0x32CE, 'M', 'ev'), + (0x32CF, 'M', 'ltd'), + (0x32D0, 'M', 'ア'), + (0x32D1, 'M', 'イ'), + (0x32D2, 'M', 'ウ'), + (0x32D3, 'M', 'エ'), + (0x32D4, 'M', 'オ'), + (0x32D5, 'M', 'カ'), + (0x32D6, 'M', 'キ'), + (0x32D7, 'M', 'ク'), + (0x32D8, 'M', 'ケ'), + (0x32D9, 'M', 'コ'), + (0x32DA, 'M', 'サ'), + (0x32DB, 'M', 'シ'), + (0x32DC, 'M', 'ス'), + (0x32DD, 'M', 'セ'), + (0x32DE, 'M', 'ソ'), + (0x32DF, 'M', 'タ'), + (0x32E0, 'M', 'チ'), + (0x32E1, 'M', 'ツ'), + (0x32E2, 'M', 'テ'), + (0x32E3, 'M', 'ト'), + (0x32E4, 'M', 'ナ'), + (0x32E5, 'M', 'ニ'), + (0x32E6, 'M', 'ヌ'), + (0x32E7, 'M', 'ネ'), + (0x32E8, 'M', 'ノ'), + (0x32E9, 'M', 'ハ'), + (0x32EA, 'M', 'ヒ'), + (0x32EB, 'M', 'フ'), + (0x32EC, 'M', 'ヘ'), + (0x32ED, 'M', 'ホ'), + (0x32EE, 'M', 'マ'), + (0x32EF, 'M', 'ミ'), + (0x32F0, 'M', 'ム'), + (0x32F1, 'M', 'メ'), + (0x32F2, 'M', 'モ'), + (0x32F3, 'M', 'ヤ'), + (0x32F4, 'M', 'ユ'), + (0x32F5, 'M', 'ヨ'), + (0x32F6, 'M', 'ラ'), + (0x32F7, 'M', 'リ'), + (0x32F8, 'M', 'ル'), + (0x32F9, 'M', 'レ'), + (0x32FA, 'M', 'ロ'), + (0x32FB, 'M', 'ワ'), + (0x32FC, 'M', 'ヰ'), + (0x32FD, 'M', 'ヱ'), + (0x32FE, 'M', 'ヲ'), + (0x32FF, 'M', '令和'), + (0x3300, 'M', 'アパート'), + (0x3301, 'M', 'アルファ'), + (0x3302, 'M', 'アンペア'), + (0x3303, 'M', 'アール'), + (0x3304, 'M', 'イニング'), + (0x3305, 'M', 'インチ'), + (0x3306, 'M', 'ウォン'), + (0x3307, 'M', 'エスクード'), + (0x3308, 'M', 'エーカー'), + (0x3309, 'M', 'オンス'), + (0x330A, 'M', 'オーム'), + (0x330B, 'M', 'カイリ'), + (0x330C, 'M', 'カラット'), + (0x330D, 'M', 'カロリー'), + (0x330E, 'M', 'ガロン'), + (0x330F, 'M', 'ガンマ'), + (0x3310, 'M', 'ギガ'), + (0x3311, 'M', 'ギニー'), ] def _seg_33(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x332C, 'M', u'パーツ'), - (0x332D, 'M', u'バーレル'), - (0x332E, 'M', u'ピアストル'), - (0x332F, 'M', u'ピクル'), - (0x3330, 'M', u'ピコ'), - (0x3331, 'M', u'ビル'), - (0x3332, 'M', u'ファラッド'), - (0x3333, 'M', u'フィート'), - (0x3334, 'M', u'ブッシェル'), - (0x3335, 'M', u'フラン'), - (0x3336, 'M', u'ヘクタール'), - (0x3337, 'M', u'ペソ'), - (0x3338, 'M', u'ペニヒ'), - (0x3339, 'M', u'ヘルツ'), - (0x333A, 'M', u'ペンス'), - (0x333B, 'M', u'ページ'), - (0x333C, 'M', u'ベータ'), - (0x333D, 'M', u'ポイント'), - (0x333E, 'M', u'ボルト'), - (0x333F, 'M', u'ホン'), - (0x3340, 'M', u'ポンド'), - (0x3341, 'M', u'ホール'), - (0x3342, 'M', u'ホーン'), - (0x3343, 'M', u'マイクロ'), - (0x3344, 'M', u'マイル'), - (0x3345, 'M', u'マッハ'), - (0x3346, 'M', u'マルク'), - (0x3347, 'M', u'マンション'), - (0x3348, 'M', u'ミクロン'), - (0x3349, 'M', u'ミリ'), - (0x334A, 'M', u'ミリバール'), - (0x334B, 'M', u'メガ'), - (0x334C, 'M', u'メガトン'), - (0x334D, 'M', u'メートル'), - (0x334E, 'M', u'ヤード'), - (0x334F, 'M', u'ヤール'), - (0x3350, 'M', u'ユアン'), - (0x3351, 'M', u'リットル'), - (0x3352, 'M', u'リラ'), - (0x3353, 'M', u'ルピー'), - (0x3354, 'M', u'ルーブル'), - (0x3355, 'M', u'レム'), - (0x3356, 'M', u'レントゲン'), - (0x3357, 'M', u'ワット'), - (0x3358, 'M', u'0点'), - (0x3359, 'M', u'1点'), - (0x335A, 'M', u'2点'), - (0x335B, 'M', u'3点'), - (0x335C, 'M', u'4点'), - (0x335D, 'M', u'5点'), - (0x335E, 'M', u'6点'), - (0x335F, 'M', u'7点'), - (0x3360, 'M', u'8点'), - (0x3361, 'M', u'9点'), - (0x3362, 'M', u'10点'), - (0x3363, 'M', u'11点'), - (0x3364, 'M', u'12点'), - (0x3365, 'M', u'13点'), - (0x3366, 'M', u'14点'), - (0x3367, 'M', u'15点'), - (0x3368, 'M', u'16点'), - (0x3369, 'M', u'17点'), - (0x336A, 'M', u'18点'), - (0x336B, 'M', u'19点'), - (0x336C, 'M', u'20点'), - (0x336D, 'M', u'21点'), - (0x336E, 'M', u'22点'), - (0x336F, 'M', u'23点'), - (0x3370, 'M', u'24点'), - (0x3371, 'M', u'hpa'), - (0x3372, 'M', u'da'), - (0x3373, 'M', u'au'), - (0x3374, 'M', u'bar'), - (0x3375, 'M', u'ov'), - (0x3376, 'M', u'pc'), - (0x3377, 'M', u'dm'), - (0x3378, 'M', u'dm2'), - (0x3379, 'M', u'dm3'), - (0x337A, 'M', u'iu'), - (0x337B, 'M', u'平成'), - (0x337C, 'M', u'昭和'), - (0x337D, 'M', u'大正'), - (0x337E, 'M', u'明治'), - (0x337F, 'M', u'株式会社'), - (0x3380, 'M', u'pa'), - (0x3381, 'M', u'na'), - (0x3382, 'M', u'μa'), - (0x3383, 'M', u'ma'), - (0x3384, 'M', u'ka'), - (0x3385, 'M', u'kb'), - (0x3386, 'M', u'mb'), - (0x3387, 'M', u'gb'), - (0x3388, 'M', u'cal'), - (0x3389, 'M', u'kcal'), - (0x338A, 'M', u'pf'), - (0x338B, 'M', u'nf'), - (0x338C, 'M', u'μf'), - (0x338D, 'M', u'μg'), - (0x338E, 'M', u'mg'), - (0x338F, 'M', u'kg'), + (0x3312, 'M', 'キュリー'), + (0x3313, 'M', 'ギルダー'), + (0x3314, 'M', 'キロ'), + (0x3315, 'M', 'キログラム'), + (0x3316, 'M', 'キロメートル'), + (0x3317, 'M', 'キロワット'), + (0x3318, 'M', 'グラム'), + (0x3319, 'M', 'グラムトン'), + (0x331A, 'M', 'クルゼイロ'), + (0x331B, 'M', 'クローネ'), + (0x331C, 'M', 'ケース'), + (0x331D, 'M', 'コルナ'), + (0x331E, 'M', 'コーポ'), + (0x331F, 'M', 'サイクル'), + (0x3320, 'M', 'サンチーム'), + (0x3321, 'M', 'シリング'), + (0x3322, 'M', 'センチ'), + (0x3323, 'M', 'セント'), + (0x3324, 'M', 'ダース'), + (0x3325, 'M', 'デシ'), + (0x3326, 'M', 'ドル'), + (0x3327, 'M', 'トン'), + (0x3328, 'M', 'ナノ'), + (0x3329, 'M', 'ノット'), + (0x332A, 'M', 'ハイツ'), + (0x332B, 'M', 'パーセント'), + (0x332C, 'M', 'パーツ'), + (0x332D, 'M', 'バーレル'), + (0x332E, 'M', 'ピアストル'), + (0x332F, 'M', 'ピクル'), + (0x3330, 'M', 'ピコ'), + (0x3331, 'M', 'ビル'), + (0x3332, 'M', 'ファラッド'), + (0x3333, 'M', 'フィート'), + (0x3334, 'M', 'ブッシェル'), + (0x3335, 'M', 'フラン'), + (0x3336, 'M', 'ヘクタール'), + (0x3337, 'M', 'ペソ'), + (0x3338, 'M', 'ペニヒ'), + (0x3339, 'M', 'ヘルツ'), + (0x333A, 'M', 'ペンス'), + (0x333B, 'M', 'ページ'), + (0x333C, 'M', 'ベータ'), + (0x333D, 'M', 'ポイント'), + (0x333E, 'M', 'ボルト'), + (0x333F, 'M', 'ホン'), + (0x3340, 'M', 'ポンド'), + (0x3341, 'M', 'ホール'), + (0x3342, 'M', 'ホーン'), + (0x3343, 'M', 'マイクロ'), + (0x3344, 'M', 'マイル'), + (0x3345, 'M', 'マッハ'), + (0x3346, 'M', 'マルク'), + (0x3347, 'M', 'マンション'), + (0x3348, 'M', 'ミクロン'), + (0x3349, 'M', 'ミリ'), + (0x334A, 'M', 'ミリバール'), + (0x334B, 'M', 'メガ'), + (0x334C, 'M', 'メガトン'), + (0x334D, 'M', 'メートル'), + (0x334E, 'M', 'ヤード'), + (0x334F, 'M', 'ヤール'), + (0x3350, 'M', 'ユアン'), + (0x3351, 'M', 'リットル'), + (0x3352, 'M', 'リラ'), + (0x3353, 'M', 'ルピー'), + (0x3354, 'M', 'ルーブル'), + (0x3355, 'M', 'レム'), + (0x3356, 'M', 'レントゲン'), + (0x3357, 'M', 'ワット'), + (0x3358, 'M', '0点'), + (0x3359, 'M', '1点'), + (0x335A, 'M', '2点'), + (0x335B, 'M', '3点'), + (0x335C, 'M', '4点'), + (0x335D, 'M', '5点'), + (0x335E, 'M', '6点'), + (0x335F, 'M', '7点'), + (0x3360, 'M', '8点'), + (0x3361, 'M', '9点'), + (0x3362, 'M', '10点'), + (0x3363, 'M', '11点'), + (0x3364, 'M', '12点'), + (0x3365, 'M', '13点'), + (0x3366, 'M', '14点'), + (0x3367, 'M', '15点'), + (0x3368, 'M', '16点'), + (0x3369, 'M', '17点'), + (0x336A, 'M', '18点'), + (0x336B, 'M', '19点'), + (0x336C, 'M', '20点'), + (0x336D, 'M', '21点'), + (0x336E, 'M', '22点'), + (0x336F, 'M', '23点'), + (0x3370, 'M', '24点'), + (0x3371, 'M', 'hpa'), + (0x3372, 'M', 'da'), + (0x3373, 'M', 'au'), + (0x3374, 'M', 'bar'), + (0x3375, 'M', 'ov'), ] def _seg_34(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x3390, 'M', u'hz'), - (0x3391, 'M', u'khz'), - (0x3392, 'M', u'mhz'), - (0x3393, 'M', u'ghz'), - (0x3394, 'M', u'thz'), - (0x3395, 'M', u'μl'), - (0x3396, 'M', u'ml'), - (0x3397, 'M', u'dl'), - (0x3398, 'M', u'kl'), - (0x3399, 'M', u'fm'), - (0x339A, 'M', u'nm'), - (0x339B, 'M', u'μm'), - (0x339C, 'M', u'mm'), - (0x339D, 'M', u'cm'), - (0x339E, 'M', u'km'), - (0x339F, 'M', u'mm2'), - (0x33A0, 'M', u'cm2'), - (0x33A1, 'M', u'm2'), - (0x33A2, 'M', u'km2'), - (0x33A3, 'M', u'mm3'), - (0x33A4, 'M', u'cm3'), - (0x33A5, 'M', u'm3'), - (0x33A6, 'M', u'km3'), - (0x33A7, 'M', u'm∕s'), - (0x33A8, 'M', u'm∕s2'), - (0x33A9, 'M', u'pa'), - (0x33AA, 'M', u'kpa'), - (0x33AB, 'M', u'mpa'), - (0x33AC, 'M', u'gpa'), - (0x33AD, 'M', u'rad'), - (0x33AE, 'M', u'rad∕s'), - (0x33AF, 'M', u'rad∕s2'), - (0x33B0, 'M', u'ps'), - (0x33B1, 'M', u'ns'), - (0x33B2, 'M', u'μs'), - (0x33B3, 'M', u'ms'), - (0x33B4, 'M', u'pv'), - (0x33B5, 'M', u'nv'), - (0x33B6, 'M', u'μv'), - (0x33B7, 'M', u'mv'), - (0x33B8, 'M', u'kv'), - (0x33B9, 'M', u'mv'), - (0x33BA, 'M', u'pw'), - (0x33BB, 'M', u'nw'), - (0x33BC, 'M', u'μw'), - (0x33BD, 'M', u'mw'), - (0x33BE, 'M', u'kw'), - (0x33BF, 'M', u'mw'), - (0x33C0, 'M', u'kω'), - (0x33C1, 'M', u'mω'), + (0x3376, 'M', 'pc'), + (0x3377, 'M', 'dm'), + (0x3378, 'M', 'dm2'), + (0x3379, 'M', 'dm3'), + (0x337A, 'M', 'iu'), + (0x337B, 'M', '平成'), + (0x337C, 'M', '昭和'), + (0x337D, 'M', '大正'), + (0x337E, 'M', '明治'), + (0x337F, 'M', '株式会社'), + (0x3380, 'M', 'pa'), + (0x3381, 'M', 'na'), + (0x3382, 'M', 'μa'), + (0x3383, 'M', 'ma'), + (0x3384, 'M', 'ka'), + (0x3385, 'M', 'kb'), + (0x3386, 'M', 'mb'), + (0x3387, 'M', 'gb'), + (0x3388, 'M', 'cal'), + (0x3389, 'M', 'kcal'), + (0x338A, 'M', 'pf'), + (0x338B, 'M', 'nf'), + (0x338C, 'M', 'μf'), + (0x338D, 'M', 'μg'), + (0x338E, 'M', 'mg'), + (0x338F, 'M', 'kg'), + (0x3390, 'M', 'hz'), + (0x3391, 'M', 'khz'), + (0x3392, 'M', 'mhz'), + (0x3393, 'M', 'ghz'), + (0x3394, 'M', 'thz'), + (0x3395, 'M', 'μl'), + (0x3396, 'M', 'ml'), + (0x3397, 'M', 'dl'), + (0x3398, 'M', 'kl'), + (0x3399, 'M', 'fm'), + (0x339A, 'M', 'nm'), + (0x339B, 'M', 'μm'), + (0x339C, 'M', 'mm'), + (0x339D, 'M', 'cm'), + (0x339E, 'M', 'km'), + (0x339F, 'M', 'mm2'), + (0x33A0, 'M', 'cm2'), + (0x33A1, 'M', 'm2'), + (0x33A2, 'M', 'km2'), + (0x33A3, 'M', 'mm3'), + (0x33A4, 'M', 'cm3'), + (0x33A5, 'M', 'm3'), + (0x33A6, 'M', 'km3'), + (0x33A7, 'M', 'm∕s'), + (0x33A8, 'M', 'm∕s2'), + (0x33A9, 'M', 'pa'), + (0x33AA, 'M', 'kpa'), + (0x33AB, 'M', 'mpa'), + (0x33AC, 'M', 'gpa'), + (0x33AD, 'M', 'rad'), + (0x33AE, 'M', 'rad∕s'), + (0x33AF, 'M', 'rad∕s2'), + (0x33B0, 'M', 'ps'), + (0x33B1, 'M', 'ns'), + (0x33B2, 'M', 'μs'), + (0x33B3, 'M', 'ms'), + (0x33B4, 'M', 'pv'), + (0x33B5, 'M', 'nv'), + (0x33B6, 'M', 'μv'), + (0x33B7, 'M', 'mv'), + (0x33B8, 'M', 'kv'), + (0x33B9, 'M', 'mv'), + (0x33BA, 'M', 'pw'), + (0x33BB, 'M', 'nw'), + (0x33BC, 'M', 'μw'), + (0x33BD, 'M', 'mw'), + (0x33BE, 'M', 'kw'), + (0x33BF, 'M', 'mw'), + (0x33C0, 'M', 'kω'), + (0x33C1, 'M', 'mω'), (0x33C2, 'X'), - (0x33C3, 'M', u'bq'), - (0x33C4, 'M', u'cc'), - (0x33C5, 'M', u'cd'), - (0x33C6, 'M', u'c∕kg'), + (0x33C3, 'M', 'bq'), + (0x33C4, 'M', 'cc'), + (0x33C5, 'M', 'cd'), + (0x33C6, 'M', 'c∕kg'), (0x33C7, 'X'), - (0x33C8, 'M', u'db'), - (0x33C9, 'M', u'gy'), - (0x33CA, 'M', u'ha'), - (0x33CB, 'M', u'hp'), - (0x33CC, 'M', u'in'), - (0x33CD, 'M', u'kk'), - (0x33CE, 'M', u'km'), - (0x33CF, 'M', u'kt'), - (0x33D0, 'M', u'lm'), - (0x33D1, 'M', u'ln'), - (0x33D2, 'M', u'log'), - (0x33D3, 'M', u'lx'), - (0x33D4, 'M', u'mb'), - (0x33D5, 'M', u'mil'), - (0x33D6, 'M', u'mol'), - (0x33D7, 'M', u'ph'), + (0x33C8, 'M', 'db'), + (0x33C9, 'M', 'gy'), + (0x33CA, 'M', 'ha'), + (0x33CB, 'M', 'hp'), + (0x33CC, 'M', 'in'), + (0x33CD, 'M', 'kk'), + (0x33CE, 'M', 'km'), + (0x33CF, 'M', 'kt'), + (0x33D0, 'M', 'lm'), + (0x33D1, 'M', 'ln'), + (0x33D2, 'M', 'log'), + (0x33D3, 'M', 'lx'), + (0x33D4, 'M', 'mb'), + (0x33D5, 'M', 'mil'), + (0x33D6, 'M', 'mol'), + (0x33D7, 'M', 'ph'), (0x33D8, 'X'), - (0x33D9, 'M', u'ppm'), - (0x33DA, 'M', u'pr'), - (0x33DB, 'M', u'sr'), - (0x33DC, 'M', u'sv'), - (0x33DD, 'M', u'wb'), - (0x33DE, 'M', u'v∕m'), - (0x33DF, 'M', u'a∕m'), - (0x33E0, 'M', u'1日'), - (0x33E1, 'M', u'2日'), - (0x33E2, 'M', u'3日'), - (0x33E3, 'M', u'4日'), - (0x33E4, 'M', u'5日'), - (0x33E5, 'M', u'6日'), - (0x33E6, 'M', u'7日'), - (0x33E7, 'M', u'8日'), - (0x33E8, 'M', u'9日'), - (0x33E9, 'M', u'10日'), - (0x33EA, 'M', u'11日'), - (0x33EB, 'M', u'12日'), - (0x33EC, 'M', u'13日'), - (0x33ED, 'M', u'14日'), - (0x33EE, 'M', u'15日'), - (0x33EF, 'M', u'16日'), - (0x33F0, 'M', u'17日'), - (0x33F1, 'M', u'18日'), - (0x33F2, 'M', u'19日'), - (0x33F3, 'M', u'20日'), + (0x33D9, 'M', 'ppm'), ] def _seg_35(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x33F4, 'M', u'21日'), - (0x33F5, 'M', u'22日'), - (0x33F6, 'M', u'23日'), - (0x33F7, 'M', u'24日'), - (0x33F8, 'M', u'25日'), - (0x33F9, 'M', u'26日'), - (0x33FA, 'M', u'27日'), - (0x33FB, 'M', u'28日'), - (0x33FC, 'M', u'29日'), - (0x33FD, 'M', u'30日'), - (0x33FE, 'M', u'31日'), - (0x33FF, 'M', u'gal'), + (0x33DA, 'M', 'pr'), + (0x33DB, 'M', 'sr'), + (0x33DC, 'M', 'sv'), + (0x33DD, 'M', 'wb'), + (0x33DE, 'M', 'v∕m'), + (0x33DF, 'M', 'a∕m'), + (0x33E0, 'M', '1日'), + (0x33E1, 'M', '2日'), + (0x33E2, 'M', '3日'), + (0x33E3, 'M', '4日'), + (0x33E4, 'M', '5日'), + (0x33E5, 'M', '6日'), + (0x33E6, 'M', '7日'), + (0x33E7, 'M', '8日'), + (0x33E8, 'M', '9日'), + (0x33E9, 'M', '10日'), + (0x33EA, 'M', '11日'), + (0x33EB, 'M', '12日'), + (0x33EC, 'M', '13日'), + (0x33ED, 'M', '14日'), + (0x33EE, 'M', '15日'), + (0x33EF, 'M', '16日'), + (0x33F0, 'M', '17日'), + (0x33F1, 'M', '18日'), + (0x33F2, 'M', '19日'), + (0x33F3, 'M', '20日'), + (0x33F4, 'M', '21日'), + (0x33F5, 'M', '22日'), + (0x33F6, 'M', '23日'), + (0x33F7, 'M', '24日'), + (0x33F8, 'M', '25日'), + (0x33F9, 'M', '26日'), + (0x33FA, 'M', '27日'), + (0x33FB, 'M', '28日'), + (0x33FC, 'M', '29日'), + (0x33FD, 'M', '30日'), + (0x33FE, 'M', '31日'), + (0x33FF, 'M', 'gal'), (0x3400, 'V'), - (0x4DB6, 'X'), - (0x4DC0, 'V'), - (0x9FF0, 'X'), + (0x9FFD, 'X'), (0xA000, 'V'), (0xA48D, 'X'), (0xA490, 'V'), (0xA4C7, 'X'), (0xA4D0, 'V'), (0xA62C, 'X'), - (0xA640, 'M', u'ꙁ'), + (0xA640, 'M', 'ꙁ'), (0xA641, 'V'), - (0xA642, 'M', u'ꙃ'), + (0xA642, 'M', 'ꙃ'), (0xA643, 'V'), - (0xA644, 'M', u'ꙅ'), + (0xA644, 'M', 'ꙅ'), (0xA645, 'V'), - (0xA646, 'M', u'ꙇ'), + (0xA646, 'M', 'ꙇ'), (0xA647, 'V'), - (0xA648, 'M', u'ꙉ'), + (0xA648, 'M', 'ꙉ'), (0xA649, 'V'), - (0xA64A, 'M', u'ꙋ'), + (0xA64A, 'M', 'ꙋ'), (0xA64B, 'V'), - (0xA64C, 'M', u'ꙍ'), + (0xA64C, 'M', 'ꙍ'), (0xA64D, 'V'), - (0xA64E, 'M', u'ꙏ'), + (0xA64E, 'M', 'ꙏ'), (0xA64F, 'V'), - (0xA650, 'M', u'ꙑ'), + (0xA650, 'M', 'ꙑ'), (0xA651, 'V'), - (0xA652, 'M', u'ꙓ'), + (0xA652, 'M', 'ꙓ'), (0xA653, 'V'), - (0xA654, 'M', u'ꙕ'), + (0xA654, 'M', 'ꙕ'), (0xA655, 'V'), - (0xA656, 'M', u'ꙗ'), + (0xA656, 'M', 'ꙗ'), (0xA657, 'V'), - (0xA658, 'M', u'ꙙ'), + (0xA658, 'M', 'ꙙ'), (0xA659, 'V'), - (0xA65A, 'M', u'ꙛ'), + (0xA65A, 'M', 'ꙛ'), (0xA65B, 'V'), - (0xA65C, 'M', u'ꙝ'), + (0xA65C, 'M', 'ꙝ'), (0xA65D, 'V'), - (0xA65E, 'M', u'ꙟ'), + (0xA65E, 'M', 'ꙟ'), (0xA65F, 'V'), - (0xA660, 'M', u'ꙡ'), + (0xA660, 'M', 'ꙡ'), (0xA661, 'V'), - (0xA662, 'M', u'ꙣ'), + (0xA662, 'M', 'ꙣ'), (0xA663, 'V'), - (0xA664, 'M', u'ꙥ'), + (0xA664, 'M', 'ꙥ'), (0xA665, 'V'), - (0xA666, 'M', u'ꙧ'), + (0xA666, 'M', 'ꙧ'), (0xA667, 'V'), - (0xA668, 'M', u'ꙩ'), + (0xA668, 'M', 'ꙩ'), (0xA669, 'V'), - (0xA66A, 'M', u'ꙫ'), + (0xA66A, 'M', 'ꙫ'), (0xA66B, 'V'), - (0xA66C, 'M', u'ꙭ'), + (0xA66C, 'M', 'ꙭ'), (0xA66D, 'V'), - (0xA680, 'M', u'ꚁ'), + (0xA680, 'M', 'ꚁ'), (0xA681, 'V'), - (0xA682, 'M', u'ꚃ'), + (0xA682, 'M', 'ꚃ'), (0xA683, 'V'), - (0xA684, 'M', u'ꚅ'), + (0xA684, 'M', 'ꚅ'), (0xA685, 'V'), - (0xA686, 'M', u'ꚇ'), + (0xA686, 'M', 'ꚇ'), (0xA687, 'V'), - (0xA688, 'M', u'ꚉ'), - (0xA689, 'V'), - (0xA68A, 'M', u'ꚋ'), - (0xA68B, 'V'), - (0xA68C, 'M', u'ꚍ'), - (0xA68D, 'V'), - (0xA68E, 'M', u'ꚏ'), - (0xA68F, 'V'), - (0xA690, 'M', u'ꚑ'), - (0xA691, 'V'), - (0xA692, 'M', u'ꚓ'), - (0xA693, 'V'), - (0xA694, 'M', u'ꚕ'), - (0xA695, 'V'), - (0xA696, 'M', u'ꚗ'), - (0xA697, 'V'), - (0xA698, 'M', u'ꚙ'), - (0xA699, 'V'), - (0xA69A, 'M', u'ꚛ'), - (0xA69B, 'V'), - (0xA69C, 'M', u'ъ'), - (0xA69D, 'M', u'ь'), - (0xA69E, 'V'), - (0xA6F8, 'X'), ] def _seg_36(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ + (0xA688, 'M', 'ꚉ'), + (0xA689, 'V'), + (0xA68A, 'M', 'ꚋ'), + (0xA68B, 'V'), + (0xA68C, 'M', 'ꚍ'), + (0xA68D, 'V'), + (0xA68E, 'M', 'ꚏ'), + (0xA68F, 'V'), + (0xA690, 'M', 'ꚑ'), + (0xA691, 'V'), + (0xA692, 'M', 'ꚓ'), + (0xA693, 'V'), + (0xA694, 'M', 'ꚕ'), + (0xA695, 'V'), + (0xA696, 'M', 'ꚗ'), + (0xA697, 'V'), + (0xA698, 'M', 'ꚙ'), + (0xA699, 'V'), + (0xA69A, 'M', 'ꚛ'), + (0xA69B, 'V'), + (0xA69C, 'M', 'ъ'), + (0xA69D, 'M', 'ь'), + (0xA69E, 'V'), + (0xA6F8, 'X'), (0xA700, 'V'), - (0xA722, 'M', u'ꜣ'), + (0xA722, 'M', 'ꜣ'), (0xA723, 'V'), - (0xA724, 'M', u'ꜥ'), + (0xA724, 'M', 'ꜥ'), (0xA725, 'V'), - (0xA726, 'M', u'ꜧ'), + (0xA726, 'M', 'ꜧ'), (0xA727, 'V'), - (0xA728, 'M', u'ꜩ'), + (0xA728, 'M', 'ꜩ'), (0xA729, 'V'), - (0xA72A, 'M', u'ꜫ'), + (0xA72A, 'M', 'ꜫ'), (0xA72B, 'V'), - (0xA72C, 'M', u'ꜭ'), + (0xA72C, 'M', 'ꜭ'), (0xA72D, 'V'), - (0xA72E, 'M', u'ꜯ'), + (0xA72E, 'M', 'ꜯ'), (0xA72F, 'V'), - (0xA732, 'M', u'ꜳ'), + (0xA732, 'M', 'ꜳ'), (0xA733, 'V'), - (0xA734, 'M', u'ꜵ'), + (0xA734, 'M', 'ꜵ'), (0xA735, 'V'), - (0xA736, 'M', u'ꜷ'), + (0xA736, 'M', 'ꜷ'), (0xA737, 'V'), - (0xA738, 'M', u'ꜹ'), + (0xA738, 'M', 'ꜹ'), (0xA739, 'V'), - (0xA73A, 'M', u'ꜻ'), + (0xA73A, 'M', 'ꜻ'), (0xA73B, 'V'), - (0xA73C, 'M', u'ꜽ'), + (0xA73C, 'M', 'ꜽ'), (0xA73D, 'V'), - (0xA73E, 'M', u'ꜿ'), + (0xA73E, 'M', 'ꜿ'), (0xA73F, 'V'), - (0xA740, 'M', u'ꝁ'), + (0xA740, 'M', 'ꝁ'), (0xA741, 'V'), - (0xA742, 'M', u'ꝃ'), + (0xA742, 'M', 'ꝃ'), (0xA743, 'V'), - (0xA744, 'M', u'ꝅ'), + (0xA744, 'M', 'ꝅ'), (0xA745, 'V'), - (0xA746, 'M', u'ꝇ'), + (0xA746, 'M', 'ꝇ'), (0xA747, 'V'), - (0xA748, 'M', u'ꝉ'), + (0xA748, 'M', 'ꝉ'), (0xA749, 'V'), - (0xA74A, 'M', u'ꝋ'), + (0xA74A, 'M', 'ꝋ'), (0xA74B, 'V'), - (0xA74C, 'M', u'ꝍ'), + (0xA74C, 'M', 'ꝍ'), (0xA74D, 'V'), - (0xA74E, 'M', u'ꝏ'), + (0xA74E, 'M', 'ꝏ'), (0xA74F, 'V'), - (0xA750, 'M', u'ꝑ'), + (0xA750, 'M', 'ꝑ'), (0xA751, 'V'), - (0xA752, 'M', u'ꝓ'), + (0xA752, 'M', 'ꝓ'), (0xA753, 'V'), - (0xA754, 'M', u'ꝕ'), + (0xA754, 'M', 'ꝕ'), (0xA755, 'V'), - (0xA756, 'M', u'ꝗ'), + (0xA756, 'M', 'ꝗ'), (0xA757, 'V'), - (0xA758, 'M', u'ꝙ'), + (0xA758, 'M', 'ꝙ'), (0xA759, 'V'), - (0xA75A, 'M', u'ꝛ'), + (0xA75A, 'M', 'ꝛ'), (0xA75B, 'V'), - (0xA75C, 'M', u'ꝝ'), + (0xA75C, 'M', 'ꝝ'), (0xA75D, 'V'), - (0xA75E, 'M', u'ꝟ'), + (0xA75E, 'M', 'ꝟ'), (0xA75F, 'V'), - (0xA760, 'M', u'ꝡ'), + (0xA760, 'M', 'ꝡ'), (0xA761, 'V'), - (0xA762, 'M', u'ꝣ'), + (0xA762, 'M', 'ꝣ'), (0xA763, 'V'), - (0xA764, 'M', u'ꝥ'), + (0xA764, 'M', 'ꝥ'), (0xA765, 'V'), - (0xA766, 'M', u'ꝧ'), + (0xA766, 'M', 'ꝧ'), (0xA767, 'V'), - (0xA768, 'M', u'ꝩ'), + (0xA768, 'M', 'ꝩ'), (0xA769, 'V'), - (0xA76A, 'M', u'ꝫ'), + (0xA76A, 'M', 'ꝫ'), (0xA76B, 'V'), - (0xA76C, 'M', u'ꝭ'), + (0xA76C, 'M', 'ꝭ'), (0xA76D, 'V'), - (0xA76E, 'M', u'ꝯ'), - (0xA76F, 'V'), - (0xA770, 'M', u'ꝯ'), - (0xA771, 'V'), - (0xA779, 'M', u'ꝺ'), - (0xA77A, 'V'), - (0xA77B, 'M', u'ꝼ'), - (0xA77C, 'V'), - (0xA77D, 'M', u'ᵹ'), - (0xA77E, 'M', u'ꝿ'), - (0xA77F, 'V'), - (0xA780, 'M', u'ꞁ'), - (0xA781, 'V'), - (0xA782, 'M', u'ꞃ'), - (0xA783, 'V'), - (0xA784, 'M', u'ꞅ'), - (0xA785, 'V'), - (0xA786, 'M', u'ꞇ'), - (0xA787, 'V'), - (0xA78B, 'M', u'ꞌ'), - (0xA78C, 'V'), - (0xA78D, 'M', u'ɥ'), - (0xA78E, 'V'), - (0xA790, 'M', u'ꞑ'), - (0xA791, 'V'), + (0xA76E, 'M', 'ꝯ'), ] def _seg_37(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0xA792, 'M', u'ꞓ'), + (0xA76F, 'V'), + (0xA770, 'M', 'ꝯ'), + (0xA771, 'V'), + (0xA779, 'M', 'ꝺ'), + (0xA77A, 'V'), + (0xA77B, 'M', 'ꝼ'), + (0xA77C, 'V'), + (0xA77D, 'M', 'ᵹ'), + (0xA77E, 'M', 'ꝿ'), + (0xA77F, 'V'), + (0xA780, 'M', 'ꞁ'), + (0xA781, 'V'), + (0xA782, 'M', 'ꞃ'), + (0xA783, 'V'), + (0xA784, 'M', 'ꞅ'), + (0xA785, 'V'), + (0xA786, 'M', 'ꞇ'), + (0xA787, 'V'), + (0xA78B, 'M', 'ꞌ'), + (0xA78C, 'V'), + (0xA78D, 'M', 'ɥ'), + (0xA78E, 'V'), + (0xA790, 'M', 'ꞑ'), + (0xA791, 'V'), + (0xA792, 'M', 'ꞓ'), (0xA793, 'V'), - (0xA796, 'M', u'ꞗ'), + (0xA796, 'M', 'ꞗ'), (0xA797, 'V'), - (0xA798, 'M', u'ꞙ'), + (0xA798, 'M', 'ꞙ'), (0xA799, 'V'), - (0xA79A, 'M', u'ꞛ'), + (0xA79A, 'M', 'ꞛ'), (0xA79B, 'V'), - (0xA79C, 'M', u'ꞝ'), + (0xA79C, 'M', 'ꞝ'), (0xA79D, 'V'), - (0xA79E, 'M', u'ꞟ'), + (0xA79E, 'M', 'ꞟ'), (0xA79F, 'V'), - (0xA7A0, 'M', u'ꞡ'), + (0xA7A0, 'M', 'ꞡ'), (0xA7A1, 'V'), - (0xA7A2, 'M', u'ꞣ'), + (0xA7A2, 'M', 'ꞣ'), (0xA7A3, 'V'), - (0xA7A4, 'M', u'ꞥ'), + (0xA7A4, 'M', 'ꞥ'), (0xA7A5, 'V'), - (0xA7A6, 'M', u'ꞧ'), + (0xA7A6, 'M', 'ꞧ'), (0xA7A7, 'V'), - (0xA7A8, 'M', u'ꞩ'), + (0xA7A8, 'M', 'ꞩ'), (0xA7A9, 'V'), - (0xA7AA, 'M', u'ɦ'), - (0xA7AB, 'M', u'ɜ'), - (0xA7AC, 'M', u'ɡ'), - (0xA7AD, 'M', u'ɬ'), - (0xA7AE, 'M', u'ɪ'), + (0xA7AA, 'M', 'ɦ'), + (0xA7AB, 'M', 'ɜ'), + (0xA7AC, 'M', 'ɡ'), + (0xA7AD, 'M', 'ɬ'), + (0xA7AE, 'M', 'ɪ'), (0xA7AF, 'V'), - (0xA7B0, 'M', u'ʞ'), - (0xA7B1, 'M', u'ʇ'), - (0xA7B2, 'M', u'ʝ'), - (0xA7B3, 'M', u'ꭓ'), - (0xA7B4, 'M', u'ꞵ'), + (0xA7B0, 'M', 'ʞ'), + (0xA7B1, 'M', 'ʇ'), + (0xA7B2, 'M', 'ʝ'), + (0xA7B3, 'M', 'ꭓ'), + (0xA7B4, 'M', 'ꞵ'), (0xA7B5, 'V'), - (0xA7B6, 'M', u'ꞷ'), + (0xA7B6, 'M', 'ꞷ'), (0xA7B7, 'V'), - (0xA7B8, 'X'), + (0xA7B8, 'M', 'ꞹ'), (0xA7B9, 'V'), - (0xA7BA, 'X'), - (0xA7F7, 'V'), - (0xA7F8, 'M', u'ħ'), - (0xA7F9, 'M', u'œ'), + (0xA7BA, 'M', 'ꞻ'), + (0xA7BB, 'V'), + (0xA7BC, 'M', 'ꞽ'), + (0xA7BD, 'V'), + (0xA7BE, 'M', 'ꞿ'), + (0xA7BF, 'V'), + (0xA7C0, 'X'), + (0xA7C2, 'M', 'ꟃ'), + (0xA7C3, 'V'), + (0xA7C4, 'M', 'ꞔ'), + (0xA7C5, 'M', 'ʂ'), + (0xA7C6, 'M', 'ᶎ'), + (0xA7C7, 'M', 'ꟈ'), + (0xA7C8, 'V'), + (0xA7C9, 'M', 'ꟊ'), + (0xA7CA, 'V'), + (0xA7CB, 'X'), + (0xA7F5, 'M', 'ꟶ'), + (0xA7F6, 'V'), + (0xA7F8, 'M', 'ħ'), + (0xA7F9, 'M', 'œ'), (0xA7FA, 'V'), - (0xA82C, 'X'), + (0xA82D, 'X'), (0xA830, 'V'), (0xA83A, 'X'), (0xA840, 'V'), @@ -3914,6 +3994,11 @@ def _seg_37(): (0xA980, 'V'), (0xA9CE, 'X'), (0xA9CF, 'V'), + ] + +def _seg_38(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ (0xA9DA, 'X'), (0xA9DE, 'V'), (0xA9FF, 'X'), @@ -3938,96 +4023,99 @@ def _seg_37(): (0xAB28, 'V'), (0xAB2F, 'X'), (0xAB30, 'V'), - (0xAB5C, 'M', u'ꜧ'), - (0xAB5D, 'M', u'ꬷ'), - (0xAB5E, 'M', u'ɫ'), - (0xAB5F, 'M', u'ꭒ'), + (0xAB5C, 'M', 'ꜧ'), + (0xAB5D, 'M', 'ꬷ'), + (0xAB5E, 'M', 'ɫ'), + (0xAB5F, 'M', 'ꭒ'), (0xAB60, 'V'), - (0xAB66, 'X'), - (0xAB70, 'M', u'Ꭰ'), - (0xAB71, 'M', u'Ꭱ'), - (0xAB72, 'M', u'Ꭲ'), - (0xAB73, 'M', u'Ꭳ'), - (0xAB74, 'M', u'Ꭴ'), - (0xAB75, 'M', u'Ꭵ'), - (0xAB76, 'M', u'Ꭶ'), - (0xAB77, 'M', u'Ꭷ'), - (0xAB78, 'M', u'Ꭸ'), - (0xAB79, 'M', u'Ꭹ'), - (0xAB7A, 'M', u'Ꭺ'), + (0xAB69, 'M', 'ʍ'), + (0xAB6A, 'V'), + (0xAB6C, 'X'), + (0xAB70, 'M', 'Ꭰ'), + (0xAB71, 'M', 'Ꭱ'), + (0xAB72, 'M', 'Ꭲ'), + (0xAB73, 'M', 'Ꭳ'), + (0xAB74, 'M', 'Ꭴ'), + (0xAB75, 'M', 'Ꭵ'), + (0xAB76, 'M', 'Ꭶ'), + (0xAB77, 'M', 'Ꭷ'), + (0xAB78, 'M', 'Ꭸ'), + (0xAB79, 'M', 'Ꭹ'), + (0xAB7A, 'M', 'Ꭺ'), + (0xAB7B, 'M', 'Ꭻ'), + (0xAB7C, 'M', 'Ꭼ'), + (0xAB7D, 'M', 'Ꭽ'), + (0xAB7E, 'M', 'Ꭾ'), + (0xAB7F, 'M', 'Ꭿ'), + (0xAB80, 'M', 'Ꮀ'), + (0xAB81, 'M', 'Ꮁ'), + (0xAB82, 'M', 'Ꮂ'), + (0xAB83, 'M', 'Ꮃ'), + (0xAB84, 'M', 'Ꮄ'), + (0xAB85, 'M', 'Ꮅ'), + (0xAB86, 'M', 'Ꮆ'), + (0xAB87, 'M', 'Ꮇ'), + (0xAB88, 'M', 'Ꮈ'), + (0xAB89, 'M', 'Ꮉ'), + (0xAB8A, 'M', 'Ꮊ'), + (0xAB8B, 'M', 'Ꮋ'), + (0xAB8C, 'M', 'Ꮌ'), + (0xAB8D, 'M', 'Ꮍ'), + (0xAB8E, 'M', 'Ꮎ'), + (0xAB8F, 'M', 'Ꮏ'), + (0xAB90, 'M', 'Ꮐ'), + (0xAB91, 'M', 'Ꮑ'), + (0xAB92, 'M', 'Ꮒ'), + (0xAB93, 'M', 'Ꮓ'), + (0xAB94, 'M', 'Ꮔ'), + (0xAB95, 'M', 'Ꮕ'), + (0xAB96, 'M', 'Ꮖ'), + (0xAB97, 'M', 'Ꮗ'), + (0xAB98, 'M', 'Ꮘ'), + (0xAB99, 'M', 'Ꮙ'), + (0xAB9A, 'M', 'Ꮚ'), + (0xAB9B, 'M', 'Ꮛ'), + (0xAB9C, 'M', 'Ꮜ'), + (0xAB9D, 'M', 'Ꮝ'), + (0xAB9E, 'M', 'Ꮞ'), + (0xAB9F, 'M', 'Ꮟ'), + (0xABA0, 'M', 'Ꮠ'), + (0xABA1, 'M', 'Ꮡ'), + (0xABA2, 'M', 'Ꮢ'), + (0xABA3, 'M', 'Ꮣ'), + (0xABA4, 'M', 'Ꮤ'), + (0xABA5, 'M', 'Ꮥ'), + (0xABA6, 'M', 'Ꮦ'), + (0xABA7, 'M', 'Ꮧ'), + (0xABA8, 'M', 'Ꮨ'), + (0xABA9, 'M', 'Ꮩ'), + (0xABAA, 'M', 'Ꮪ'), + (0xABAB, 'M', 'Ꮫ'), + (0xABAC, 'M', 'Ꮬ'), + (0xABAD, 'M', 'Ꮭ'), + (0xABAE, 'M', 'Ꮮ'), + (0xABAF, 'M', 'Ꮯ'), + (0xABB0, 'M', 'Ꮰ'), + (0xABB1, 'M', 'Ꮱ'), + (0xABB2, 'M', 'Ꮲ'), + (0xABB3, 'M', 'Ꮳ'), ] -def _seg_38(): +def _seg_39(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0xAB7B, 'M', u'Ꭻ'), - (0xAB7C, 'M', u'Ꭼ'), - (0xAB7D, 'M', u'Ꭽ'), - (0xAB7E, 'M', u'Ꭾ'), - (0xAB7F, 'M', u'Ꭿ'), - (0xAB80, 'M', u'Ꮀ'), - (0xAB81, 'M', u'Ꮁ'), - (0xAB82, 'M', u'Ꮂ'), - (0xAB83, 'M', u'Ꮃ'), - (0xAB84, 'M', u'Ꮄ'), - (0xAB85, 'M', u'Ꮅ'), - (0xAB86, 'M', u'Ꮆ'), - (0xAB87, 'M', u'Ꮇ'), - (0xAB88, 'M', u'Ꮈ'), - (0xAB89, 'M', u'Ꮉ'), - (0xAB8A, 'M', u'Ꮊ'), - (0xAB8B, 'M', u'Ꮋ'), - (0xAB8C, 'M', u'Ꮌ'), - (0xAB8D, 'M', u'Ꮍ'), - (0xAB8E, 'M', u'Ꮎ'), - (0xAB8F, 'M', u'Ꮏ'), - (0xAB90, 'M', u'Ꮐ'), - (0xAB91, 'M', u'Ꮑ'), - (0xAB92, 'M', u'Ꮒ'), - (0xAB93, 'M', u'Ꮓ'), - (0xAB94, 'M', u'Ꮔ'), - (0xAB95, 'M', u'Ꮕ'), - (0xAB96, 'M', u'Ꮖ'), - (0xAB97, 'M', u'Ꮗ'), - (0xAB98, 'M', u'Ꮘ'), - (0xAB99, 'M', u'Ꮙ'), - (0xAB9A, 'M', u'Ꮚ'), - (0xAB9B, 'M', u'Ꮛ'), - (0xAB9C, 'M', u'Ꮜ'), - (0xAB9D, 'M', u'Ꮝ'), - (0xAB9E, 'M', u'Ꮞ'), - (0xAB9F, 'M', u'Ꮟ'), - (0xABA0, 'M', u'Ꮠ'), - (0xABA1, 'M', u'Ꮡ'), - (0xABA2, 'M', u'Ꮢ'), - (0xABA3, 'M', u'Ꮣ'), - (0xABA4, 'M', u'Ꮤ'), - (0xABA5, 'M', u'Ꮥ'), - (0xABA6, 'M', u'Ꮦ'), - (0xABA7, 'M', u'Ꮧ'), - (0xABA8, 'M', u'Ꮨ'), - (0xABA9, 'M', u'Ꮩ'), - (0xABAA, 'M', u'Ꮪ'), - (0xABAB, 'M', u'Ꮫ'), - (0xABAC, 'M', u'Ꮬ'), - (0xABAD, 'M', u'Ꮭ'), - (0xABAE, 'M', u'Ꮮ'), - (0xABAF, 'M', u'Ꮯ'), - (0xABB0, 'M', u'Ꮰ'), - (0xABB1, 'M', u'Ꮱ'), - (0xABB2, 'M', u'Ꮲ'), - (0xABB3, 'M', u'Ꮳ'), - (0xABB4, 'M', u'Ꮴ'), - (0xABB5, 'M', u'Ꮵ'), - (0xABB6, 'M', u'Ꮶ'), - (0xABB7, 'M', u'Ꮷ'), - (0xABB8, 'M', u'Ꮸ'), - (0xABB9, 'M', u'Ꮹ'), - (0xABBA, 'M', u'Ꮺ'), - (0xABBB, 'M', u'Ꮻ'), - (0xABBC, 'M', u'Ꮼ'), - (0xABBD, 'M', u'Ꮽ'), - (0xABBE, 'M', u'Ꮾ'), - (0xABBF, 'M', u'Ꮿ'), + (0xABB4, 'M', 'Ꮴ'), + (0xABB5, 'M', 'Ꮵ'), + (0xABB6, 'M', 'Ꮶ'), + (0xABB7, 'M', 'Ꮷ'), + (0xABB8, 'M', 'Ꮸ'), + (0xABB9, 'M', 'Ꮹ'), + (0xABBA, 'M', 'Ꮺ'), + (0xABBB, 'M', 'Ꮻ'), + (0xABBC, 'M', 'Ꮼ'), + (0xABBD, 'M', 'Ꮽ'), + (0xABBE, 'M', 'Ꮾ'), + (0xABBF, 'M', 'Ꮿ'), (0xABC0, 'V'), (0xABEE, 'X'), (0xABF0, 'V'), @@ -4038,1440 +4126,1454 @@ def _seg_38(): (0xD7C7, 'X'), (0xD7CB, 'V'), (0xD7FC, 'X'), - (0xF900, 'M', u'豈'), - (0xF901, 'M', u'更'), - (0xF902, 'M', u'車'), - (0xF903, 'M', u'賈'), - (0xF904, 'M', u'滑'), - (0xF905, 'M', u'串'), - (0xF906, 'M', u'句'), - (0xF907, 'M', u'龜'), - (0xF909, 'M', u'契'), - (0xF90A, 'M', u'金'), - (0xF90B, 'M', u'喇'), - (0xF90C, 'M', u'奈'), - (0xF90D, 'M', u'懶'), - (0xF90E, 'M', u'癩'), - (0xF90F, 'M', u'羅'), - (0xF910, 'M', u'蘿'), - (0xF911, 'M', u'螺'), - (0xF912, 'M', u'裸'), - (0xF913, 'M', u'邏'), - (0xF914, 'M', u'樂'), - (0xF915, 'M', u'洛'), - ] - -def _seg_39(): - return [ - (0xF916, 'M', u'烙'), - (0xF917, 'M', u'珞'), - (0xF918, 'M', u'落'), - (0xF919, 'M', u'酪'), - (0xF91A, 'M', u'駱'), - (0xF91B, 'M', u'亂'), - (0xF91C, 'M', u'卵'), - (0xF91D, 'M', u'欄'), - (0xF91E, 'M', u'爛'), - (0xF91F, 'M', u'蘭'), - (0xF920, 'M', u'鸞'), - (0xF921, 'M', u'嵐'), - (0xF922, 'M', u'濫'), - (0xF923, 'M', u'藍'), - (0xF924, 'M', u'襤'), - (0xF925, 'M', u'拉'), - (0xF926, 'M', u'臘'), - (0xF927, 'M', u'蠟'), - (0xF928, 'M', u'廊'), - (0xF929, 'M', u'朗'), - (0xF92A, 'M', u'浪'), - (0xF92B, 'M', u'狼'), - (0xF92C, 'M', u'郎'), - (0xF92D, 'M', u'來'), - (0xF92E, 'M', u'冷'), - (0xF92F, 'M', u'勞'), - (0xF930, 'M', u'擄'), - (0xF931, 'M', u'櫓'), - (0xF932, 'M', u'爐'), - (0xF933, 'M', u'盧'), - (0xF934, 'M', u'老'), - (0xF935, 'M', u'蘆'), - (0xF936, 'M', u'虜'), - (0xF937, 'M', u'路'), - (0xF938, 'M', u'露'), - (0xF939, 'M', u'魯'), - (0xF93A, 'M', u'鷺'), - (0xF93B, 'M', u'碌'), - (0xF93C, 'M', u'祿'), - (0xF93D, 'M', u'綠'), - (0xF93E, 'M', u'菉'), - (0xF93F, 'M', u'錄'), - (0xF940, 'M', u'鹿'), - (0xF941, 'M', u'論'), - (0xF942, 'M', u'壟'), - (0xF943, 'M', u'弄'), - (0xF944, 'M', u'籠'), - (0xF945, 'M', u'聾'), - (0xF946, 'M', u'牢'), - (0xF947, 'M', u'磊'), - (0xF948, 'M', u'賂'), - (0xF949, 'M', u'雷'), - (0xF94A, 'M', u'壘'), - (0xF94B, 'M', u'屢'), - (0xF94C, 'M', u'樓'), - (0xF94D, 'M', u'淚'), - (0xF94E, 'M', u'漏'), - (0xF94F, 'M', u'累'), - (0xF950, 'M', u'縷'), - (0xF951, 'M', u'陋'), - (0xF952, 'M', u'勒'), - (0xF953, 'M', u'肋'), - (0xF954, 'M', u'凜'), - (0xF955, 'M', u'凌'), - (0xF956, 'M', u'稜'), - (0xF957, 'M', u'綾'), - (0xF958, 'M', u'菱'), - (0xF959, 'M', u'陵'), - (0xF95A, 'M', u'讀'), - (0xF95B, 'M', u'拏'), - (0xF95C, 'M', u'樂'), - (0xF95D, 'M', u'諾'), - (0xF95E, 'M', u'丹'), - (0xF95F, 'M', u'寧'), - (0xF960, 'M', u'怒'), - (0xF961, 'M', u'率'), - (0xF962, 'M', u'異'), - (0xF963, 'M', u'北'), - (0xF964, 'M', u'磻'), - (0xF965, 'M', u'便'), - (0xF966, 'M', u'復'), - (0xF967, 'M', u'不'), - (0xF968, 'M', u'泌'), - (0xF969, 'M', u'數'), - (0xF96A, 'M', u'索'), - (0xF96B, 'M', u'參'), - (0xF96C, 'M', u'塞'), - (0xF96D, 'M', u'省'), - (0xF96E, 'M', u'葉'), - (0xF96F, 'M', u'說'), - (0xF970, 'M', u'殺'), - (0xF971, 'M', u'辰'), - (0xF972, 'M', u'沈'), - (0xF973, 'M', u'拾'), - (0xF974, 'M', u'若'), - (0xF975, 'M', u'掠'), - (0xF976, 'M', u'略'), - (0xF977, 'M', u'亮'), - (0xF978, 'M', u'兩'), - (0xF979, 'M', u'凉'), + (0xF900, 'M', '豈'), + (0xF901, 'M', '更'), + (0xF902, 'M', '車'), + (0xF903, 'M', '賈'), + (0xF904, 'M', '滑'), + (0xF905, 'M', '串'), + (0xF906, 'M', '句'), + (0xF907, 'M', '龜'), + (0xF909, 'M', '契'), + (0xF90A, 'M', '金'), + (0xF90B, 'M', '喇'), + (0xF90C, 'M', '奈'), + (0xF90D, 'M', '懶'), + (0xF90E, 'M', '癩'), + (0xF90F, 'M', '羅'), + (0xF910, 'M', '蘿'), + (0xF911, 'M', '螺'), + (0xF912, 'M', '裸'), + (0xF913, 'M', '邏'), + (0xF914, 'M', '樂'), + (0xF915, 'M', '洛'), + (0xF916, 'M', '烙'), + (0xF917, 'M', '珞'), + (0xF918, 'M', '落'), + (0xF919, 'M', '酪'), + (0xF91A, 'M', '駱'), + (0xF91B, 'M', '亂'), + (0xF91C, 'M', '卵'), + (0xF91D, 'M', '欄'), + (0xF91E, 'M', '爛'), + (0xF91F, 'M', '蘭'), + (0xF920, 'M', '鸞'), + (0xF921, 'M', '嵐'), + (0xF922, 'M', '濫'), + (0xF923, 'M', '藍'), + (0xF924, 'M', '襤'), + (0xF925, 'M', '拉'), + (0xF926, 'M', '臘'), + (0xF927, 'M', '蠟'), + (0xF928, 'M', '廊'), + (0xF929, 'M', '朗'), + (0xF92A, 'M', '浪'), + (0xF92B, 'M', '狼'), + (0xF92C, 'M', '郎'), + (0xF92D, 'M', '來'), + (0xF92E, 'M', '冷'), + (0xF92F, 'M', '勞'), + (0xF930, 'M', '擄'), + (0xF931, 'M', '櫓'), + (0xF932, 'M', '爐'), + (0xF933, 'M', '盧'), + (0xF934, 'M', '老'), + (0xF935, 'M', '蘆'), + (0xF936, 'M', '虜'), + (0xF937, 'M', '路'), + (0xF938, 'M', '露'), + (0xF939, 'M', '魯'), + (0xF93A, 'M', '鷺'), + (0xF93B, 'M', '碌'), + (0xF93C, 'M', '祿'), + (0xF93D, 'M', '綠'), + (0xF93E, 'M', '菉'), + (0xF93F, 'M', '錄'), + (0xF940, 'M', '鹿'), + (0xF941, 'M', '論'), + (0xF942, 'M', '壟'), + (0xF943, 'M', '弄'), + (0xF944, 'M', '籠'), + (0xF945, 'M', '聾'), + (0xF946, 'M', '牢'), + (0xF947, 'M', '磊'), + (0xF948, 'M', '賂'), + (0xF949, 'M', '雷'), + (0xF94A, 'M', '壘'), + (0xF94B, 'M', '屢'), + (0xF94C, 'M', '樓'), + (0xF94D, 'M', '淚'), + (0xF94E, 'M', '漏'), ] def _seg_40(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0xF97A, 'M', u'梁'), - (0xF97B, 'M', u'糧'), - (0xF97C, 'M', u'良'), - (0xF97D, 'M', u'諒'), - (0xF97E, 'M', u'量'), - (0xF97F, 'M', u'勵'), - (0xF980, 'M', u'呂'), - (0xF981, 'M', u'女'), - (0xF982, 'M', u'廬'), - (0xF983, 'M', u'旅'), - (0xF984, 'M', u'濾'), - (0xF985, 'M', u'礪'), - (0xF986, 'M', u'閭'), - (0xF987, 'M', u'驪'), - (0xF988, 'M', u'麗'), - (0xF989, 'M', u'黎'), - (0xF98A, 'M', u'力'), - (0xF98B, 'M', u'曆'), - (0xF98C, 'M', u'歷'), - (0xF98D, 'M', u'轢'), - (0xF98E, 'M', u'年'), - (0xF98F, 'M', u'憐'), - (0xF990, 'M', u'戀'), - (0xF991, 'M', u'撚'), - (0xF992, 'M', u'漣'), - (0xF993, 'M', u'煉'), - (0xF994, 'M', u'璉'), - (0xF995, 'M', u'秊'), - (0xF996, 'M', u'練'), - (0xF997, 'M', u'聯'), - (0xF998, 'M', u'輦'), - (0xF999, 'M', u'蓮'), - (0xF99A, 'M', u'連'), - (0xF99B, 'M', u'鍊'), - (0xF99C, 'M', u'列'), - (0xF99D, 'M', u'劣'), - (0xF99E, 'M', u'咽'), - (0xF99F, 'M', u'烈'), - (0xF9A0, 'M', u'裂'), - (0xF9A1, 'M', u'說'), - (0xF9A2, 'M', u'廉'), - (0xF9A3, 'M', u'念'), - (0xF9A4, 'M', u'捻'), - (0xF9A5, 'M', u'殮'), - (0xF9A6, 'M', u'簾'), - (0xF9A7, 'M', u'獵'), - (0xF9A8, 'M', u'令'), - (0xF9A9, 'M', u'囹'), - (0xF9AA, 'M', u'寧'), - (0xF9AB, 'M', u'嶺'), - (0xF9AC, 'M', u'怜'), - (0xF9AD, 'M', u'玲'), - (0xF9AE, 'M', u'瑩'), - (0xF9AF, 'M', u'羚'), - (0xF9B0, 'M', u'聆'), - (0xF9B1, 'M', u'鈴'), - (0xF9B2, 'M', u'零'), - (0xF9B3, 'M', u'靈'), - (0xF9B4, 'M', u'領'), - (0xF9B5, 'M', u'例'), - (0xF9B6, 'M', u'禮'), - (0xF9B7, 'M', u'醴'), - (0xF9B8, 'M', u'隸'), - (0xF9B9, 'M', u'惡'), - (0xF9BA, 'M', u'了'), - (0xF9BB, 'M', u'僚'), - (0xF9BC, 'M', u'寮'), - (0xF9BD, 'M', u'尿'), - (0xF9BE, 'M', u'料'), - (0xF9BF, 'M', u'樂'), - (0xF9C0, 'M', u'燎'), - (0xF9C1, 'M', u'療'), - (0xF9C2, 'M', u'蓼'), - (0xF9C3, 'M', u'遼'), - (0xF9C4, 'M', u'龍'), - (0xF9C5, 'M', u'暈'), - (0xF9C6, 'M', u'阮'), - (0xF9C7, 'M', u'劉'), - (0xF9C8, 'M', u'杻'), - (0xF9C9, 'M', u'柳'), - (0xF9CA, 'M', u'流'), - (0xF9CB, 'M', u'溜'), - (0xF9CC, 'M', u'琉'), - (0xF9CD, 'M', u'留'), - (0xF9CE, 'M', u'硫'), - (0xF9CF, 'M', u'紐'), - (0xF9D0, 'M', u'類'), - (0xF9D1, 'M', u'六'), - (0xF9D2, 'M', u'戮'), - (0xF9D3, 'M', u'陸'), - (0xF9D4, 'M', u'倫'), - (0xF9D5, 'M', u'崙'), - (0xF9D6, 'M', u'淪'), - (0xF9D7, 'M', u'輪'), - (0xF9D8, 'M', u'律'), - (0xF9D9, 'M', u'慄'), - (0xF9DA, 'M', u'栗'), - (0xF9DB, 'M', u'率'), - (0xF9DC, 'M', u'隆'), - (0xF9DD, 'M', u'利'), + (0xF94F, 'M', '累'), + (0xF950, 'M', '縷'), + (0xF951, 'M', '陋'), + (0xF952, 'M', '勒'), + (0xF953, 'M', '肋'), + (0xF954, 'M', '凜'), + (0xF955, 'M', '凌'), + (0xF956, 'M', '稜'), + (0xF957, 'M', '綾'), + (0xF958, 'M', '菱'), + (0xF959, 'M', '陵'), + (0xF95A, 'M', '讀'), + (0xF95B, 'M', '拏'), + (0xF95C, 'M', '樂'), + (0xF95D, 'M', '諾'), + (0xF95E, 'M', '丹'), + (0xF95F, 'M', '寧'), + (0xF960, 'M', '怒'), + (0xF961, 'M', '率'), + (0xF962, 'M', '異'), + (0xF963, 'M', '北'), + (0xF964, 'M', '磻'), + (0xF965, 'M', '便'), + (0xF966, 'M', '復'), + (0xF967, 'M', '不'), + (0xF968, 'M', '泌'), + (0xF969, 'M', '數'), + (0xF96A, 'M', '索'), + (0xF96B, 'M', '參'), + (0xF96C, 'M', '塞'), + (0xF96D, 'M', '省'), + (0xF96E, 'M', '葉'), + (0xF96F, 'M', '說'), + (0xF970, 'M', '殺'), + (0xF971, 'M', '辰'), + (0xF972, 'M', '沈'), + (0xF973, 'M', '拾'), + (0xF974, 'M', '若'), + (0xF975, 'M', '掠'), + (0xF976, 'M', '略'), + (0xF977, 'M', '亮'), + (0xF978, 'M', '兩'), + (0xF979, 'M', '凉'), + (0xF97A, 'M', '梁'), + (0xF97B, 'M', '糧'), + (0xF97C, 'M', '良'), + (0xF97D, 'M', '諒'), + (0xF97E, 'M', '量'), + (0xF97F, 'M', '勵'), + (0xF980, 'M', '呂'), + (0xF981, 'M', '女'), + (0xF982, 'M', '廬'), + (0xF983, 'M', '旅'), + (0xF984, 'M', '濾'), + (0xF985, 'M', '礪'), + (0xF986, 'M', '閭'), + (0xF987, 'M', '驪'), + (0xF988, 'M', '麗'), + (0xF989, 'M', '黎'), + (0xF98A, 'M', '力'), + (0xF98B, 'M', '曆'), + (0xF98C, 'M', '歷'), + (0xF98D, 'M', '轢'), + (0xF98E, 'M', '年'), + (0xF98F, 'M', '憐'), + (0xF990, 'M', '戀'), + (0xF991, 'M', '撚'), + (0xF992, 'M', '漣'), + (0xF993, 'M', '煉'), + (0xF994, 'M', '璉'), + (0xF995, 'M', '秊'), + (0xF996, 'M', '練'), + (0xF997, 'M', '聯'), + (0xF998, 'M', '輦'), + (0xF999, 'M', '蓮'), + (0xF99A, 'M', '連'), + (0xF99B, 'M', '鍊'), + (0xF99C, 'M', '列'), + (0xF99D, 'M', '劣'), + (0xF99E, 'M', '咽'), + (0xF99F, 'M', '烈'), + (0xF9A0, 'M', '裂'), + (0xF9A1, 'M', '說'), + (0xF9A2, 'M', '廉'), + (0xF9A3, 'M', '念'), + (0xF9A4, 'M', '捻'), + (0xF9A5, 'M', '殮'), + (0xF9A6, 'M', '簾'), + (0xF9A7, 'M', '獵'), + (0xF9A8, 'M', '令'), + (0xF9A9, 'M', '囹'), + (0xF9AA, 'M', '寧'), + (0xF9AB, 'M', '嶺'), + (0xF9AC, 'M', '怜'), + (0xF9AD, 'M', '玲'), + (0xF9AE, 'M', '瑩'), + (0xF9AF, 'M', '羚'), + (0xF9B0, 'M', '聆'), + (0xF9B1, 'M', '鈴'), + (0xF9B2, 'M', '零'), ] def _seg_41(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0xF9DE, 'M', u'吏'), - (0xF9DF, 'M', u'履'), - (0xF9E0, 'M', u'易'), - (0xF9E1, 'M', u'李'), - (0xF9E2, 'M', u'梨'), - (0xF9E3, 'M', u'泥'), - (0xF9E4, 'M', u'理'), - (0xF9E5, 'M', u'痢'), - (0xF9E6, 'M', u'罹'), - (0xF9E7, 'M', u'裏'), - (0xF9E8, 'M', u'裡'), - (0xF9E9, 'M', u'里'), - (0xF9EA, 'M', u'離'), - (0xF9EB, 'M', u'匿'), - (0xF9EC, 'M', u'溺'), - (0xF9ED, 'M', u'吝'), - (0xF9EE, 'M', u'燐'), - (0xF9EF, 'M', u'璘'), - (0xF9F0, 'M', u'藺'), - (0xF9F1, 'M', u'隣'), - (0xF9F2, 'M', u'鱗'), - (0xF9F3, 'M', u'麟'), - (0xF9F4, 'M', u'林'), - (0xF9F5, 'M', u'淋'), - (0xF9F6, 'M', u'臨'), - (0xF9F7, 'M', u'立'), - (0xF9F8, 'M', u'笠'), - (0xF9F9, 'M', u'粒'), - (0xF9FA, 'M', u'狀'), - (0xF9FB, 'M', u'炙'), - (0xF9FC, 'M', u'識'), - (0xF9FD, 'M', u'什'), - (0xF9FE, 'M', u'茶'), - (0xF9FF, 'M', u'刺'), - (0xFA00, 'M', u'切'), - (0xFA01, 'M', u'度'), - (0xFA02, 'M', u'拓'), - (0xFA03, 'M', u'糖'), - (0xFA04, 'M', u'宅'), - (0xFA05, 'M', u'洞'), - (0xFA06, 'M', u'暴'), - (0xFA07, 'M', u'輻'), - (0xFA08, 'M', u'行'), - (0xFA09, 'M', u'降'), - (0xFA0A, 'M', u'見'), - (0xFA0B, 'M', u'廓'), - (0xFA0C, 'M', u'兀'), - (0xFA0D, 'M', u'嗀'), + (0xF9B3, 'M', '靈'), + (0xF9B4, 'M', '領'), + (0xF9B5, 'M', '例'), + (0xF9B6, 'M', '禮'), + (0xF9B7, 'M', '醴'), + (0xF9B8, 'M', '隸'), + (0xF9B9, 'M', '惡'), + (0xF9BA, 'M', '了'), + (0xF9BB, 'M', '僚'), + (0xF9BC, 'M', '寮'), + (0xF9BD, 'M', '尿'), + (0xF9BE, 'M', '料'), + (0xF9BF, 'M', '樂'), + (0xF9C0, 'M', '燎'), + (0xF9C1, 'M', '療'), + (0xF9C2, 'M', '蓼'), + (0xF9C3, 'M', '遼'), + (0xF9C4, 'M', '龍'), + (0xF9C5, 'M', '暈'), + (0xF9C6, 'M', '阮'), + (0xF9C7, 'M', '劉'), + (0xF9C8, 'M', '杻'), + (0xF9C9, 'M', '柳'), + (0xF9CA, 'M', '流'), + (0xF9CB, 'M', '溜'), + (0xF9CC, 'M', '琉'), + (0xF9CD, 'M', '留'), + (0xF9CE, 'M', '硫'), + (0xF9CF, 'M', '紐'), + (0xF9D0, 'M', '類'), + (0xF9D1, 'M', '六'), + (0xF9D2, 'M', '戮'), + (0xF9D3, 'M', '陸'), + (0xF9D4, 'M', '倫'), + (0xF9D5, 'M', '崙'), + (0xF9D6, 'M', '淪'), + (0xF9D7, 'M', '輪'), + (0xF9D8, 'M', '律'), + (0xF9D9, 'M', '慄'), + (0xF9DA, 'M', '栗'), + (0xF9DB, 'M', '率'), + (0xF9DC, 'M', '隆'), + (0xF9DD, 'M', '利'), + (0xF9DE, 'M', '吏'), + (0xF9DF, 'M', '履'), + (0xF9E0, 'M', '易'), + (0xF9E1, 'M', '李'), + (0xF9E2, 'M', '梨'), + (0xF9E3, 'M', '泥'), + (0xF9E4, 'M', '理'), + (0xF9E5, 'M', '痢'), + (0xF9E6, 'M', '罹'), + (0xF9E7, 'M', '裏'), + (0xF9E8, 'M', '裡'), + (0xF9E9, 'M', '里'), + (0xF9EA, 'M', '離'), + (0xF9EB, 'M', '匿'), + (0xF9EC, 'M', '溺'), + (0xF9ED, 'M', '吝'), + (0xF9EE, 'M', '燐'), + (0xF9EF, 'M', '璘'), + (0xF9F0, 'M', '藺'), + (0xF9F1, 'M', '隣'), + (0xF9F2, 'M', '鱗'), + (0xF9F3, 'M', '麟'), + (0xF9F4, 'M', '林'), + (0xF9F5, 'M', '淋'), + (0xF9F6, 'M', '臨'), + (0xF9F7, 'M', '立'), + (0xF9F8, 'M', '笠'), + (0xF9F9, 'M', '粒'), + (0xF9FA, 'M', '狀'), + (0xF9FB, 'M', '炙'), + (0xF9FC, 'M', '識'), + (0xF9FD, 'M', '什'), + (0xF9FE, 'M', '茶'), + (0xF9FF, 'M', '刺'), + (0xFA00, 'M', '切'), + (0xFA01, 'M', '度'), + (0xFA02, 'M', '拓'), + (0xFA03, 'M', '糖'), + (0xFA04, 'M', '宅'), + (0xFA05, 'M', '洞'), + (0xFA06, 'M', '暴'), + (0xFA07, 'M', '輻'), + (0xFA08, 'M', '行'), + (0xFA09, 'M', '降'), + (0xFA0A, 'M', '見'), + (0xFA0B, 'M', '廓'), + (0xFA0C, 'M', '兀'), + (0xFA0D, 'M', '嗀'), (0xFA0E, 'V'), - (0xFA10, 'M', u'塚'), + (0xFA10, 'M', '塚'), (0xFA11, 'V'), - (0xFA12, 'M', u'晴'), + (0xFA12, 'M', '晴'), (0xFA13, 'V'), - (0xFA15, 'M', u'凞'), - (0xFA16, 'M', u'猪'), - (0xFA17, 'M', u'益'), - (0xFA18, 'M', u'礼'), - (0xFA19, 'M', u'神'), - (0xFA1A, 'M', u'祥'), - (0xFA1B, 'M', u'福'), - (0xFA1C, 'M', u'靖'), - (0xFA1D, 'M', u'精'), - (0xFA1E, 'M', u'羽'), - (0xFA1F, 'V'), - (0xFA20, 'M', u'蘒'), - (0xFA21, 'V'), - (0xFA22, 'M', u'諸'), - (0xFA23, 'V'), - (0xFA25, 'M', u'逸'), - (0xFA26, 'M', u'都'), - (0xFA27, 'V'), - (0xFA2A, 'M', u'飯'), - (0xFA2B, 'M', u'飼'), - (0xFA2C, 'M', u'館'), - (0xFA2D, 'M', u'鶴'), - (0xFA2E, 'M', u'郞'), - (0xFA2F, 'M', u'隷'), - (0xFA30, 'M', u'侮'), - (0xFA31, 'M', u'僧'), - (0xFA32, 'M', u'免'), - (0xFA33, 'M', u'勉'), - (0xFA34, 'M', u'勤'), - (0xFA35, 'M', u'卑'), - (0xFA36, 'M', u'喝'), - (0xFA37, 'M', u'嘆'), - (0xFA38, 'M', u'器'), - (0xFA39, 'M', u'塀'), - (0xFA3A, 'M', u'墨'), - (0xFA3B, 'M', u'層'), - (0xFA3C, 'M', u'屮'), - (0xFA3D, 'M', u'悔'), - (0xFA3E, 'M', u'慨'), - (0xFA3F, 'M', u'憎'), - (0xFA40, 'M', u'懲'), - (0xFA41, 'M', u'敏'), - (0xFA42, 'M', u'既'), - (0xFA43, 'M', u'暑'), - (0xFA44, 'M', u'梅'), - (0xFA45, 'M', u'海'), - (0xFA46, 'M', u'渚'), + (0xFA15, 'M', '凞'), + (0xFA16, 'M', '猪'), + (0xFA17, 'M', '益'), + (0xFA18, 'M', '礼'), ] def _seg_42(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0xFA47, 'M', u'漢'), - (0xFA48, 'M', u'煮'), - (0xFA49, 'M', u'爫'), - (0xFA4A, 'M', u'琢'), - (0xFA4B, 'M', u'碑'), - (0xFA4C, 'M', u'社'), - (0xFA4D, 'M', u'祉'), - (0xFA4E, 'M', u'祈'), - (0xFA4F, 'M', u'祐'), - (0xFA50, 'M', u'祖'), - (0xFA51, 'M', u'祝'), - (0xFA52, 'M', u'禍'), - (0xFA53, 'M', u'禎'), - (0xFA54, 'M', u'穀'), - (0xFA55, 'M', u'突'), - (0xFA56, 'M', u'節'), - (0xFA57, 'M', u'練'), - (0xFA58, 'M', u'縉'), - (0xFA59, 'M', u'繁'), - (0xFA5A, 'M', u'署'), - (0xFA5B, 'M', u'者'), - (0xFA5C, 'M', u'臭'), - (0xFA5D, 'M', u'艹'), - (0xFA5F, 'M', u'著'), - (0xFA60, 'M', u'褐'), - (0xFA61, 'M', u'視'), - (0xFA62, 'M', u'謁'), - (0xFA63, 'M', u'謹'), - (0xFA64, 'M', u'賓'), - (0xFA65, 'M', u'贈'), - (0xFA66, 'M', u'辶'), - (0xFA67, 'M', u'逸'), - (0xFA68, 'M', u'難'), - (0xFA69, 'M', u'響'), - (0xFA6A, 'M', u'頻'), - (0xFA6B, 'M', u'恵'), - (0xFA6C, 'M', u'𤋮'), - (0xFA6D, 'M', u'舘'), + (0xFA19, 'M', '神'), + (0xFA1A, 'M', '祥'), + (0xFA1B, 'M', '福'), + (0xFA1C, 'M', '靖'), + (0xFA1D, 'M', '精'), + (0xFA1E, 'M', '羽'), + (0xFA1F, 'V'), + (0xFA20, 'M', '蘒'), + (0xFA21, 'V'), + (0xFA22, 'M', '諸'), + (0xFA23, 'V'), + (0xFA25, 'M', '逸'), + (0xFA26, 'M', '都'), + (0xFA27, 'V'), + (0xFA2A, 'M', '飯'), + (0xFA2B, 'M', '飼'), + (0xFA2C, 'M', '館'), + (0xFA2D, 'M', '鶴'), + (0xFA2E, 'M', '郞'), + (0xFA2F, 'M', '隷'), + (0xFA30, 'M', '侮'), + (0xFA31, 'M', '僧'), + (0xFA32, 'M', '免'), + (0xFA33, 'M', '勉'), + (0xFA34, 'M', '勤'), + (0xFA35, 'M', '卑'), + (0xFA36, 'M', '喝'), + (0xFA37, 'M', '嘆'), + (0xFA38, 'M', '器'), + (0xFA39, 'M', '塀'), + (0xFA3A, 'M', '墨'), + (0xFA3B, 'M', '層'), + (0xFA3C, 'M', '屮'), + (0xFA3D, 'M', '悔'), + (0xFA3E, 'M', '慨'), + (0xFA3F, 'M', '憎'), + (0xFA40, 'M', '懲'), + (0xFA41, 'M', '敏'), + (0xFA42, 'M', '既'), + (0xFA43, 'M', '暑'), + (0xFA44, 'M', '梅'), + (0xFA45, 'M', '海'), + (0xFA46, 'M', '渚'), + (0xFA47, 'M', '漢'), + (0xFA48, 'M', '煮'), + (0xFA49, 'M', '爫'), + (0xFA4A, 'M', '琢'), + (0xFA4B, 'M', '碑'), + (0xFA4C, 'M', '社'), + (0xFA4D, 'M', '祉'), + (0xFA4E, 'M', '祈'), + (0xFA4F, 'M', '祐'), + (0xFA50, 'M', '祖'), + (0xFA51, 'M', '祝'), + (0xFA52, 'M', '禍'), + (0xFA53, 'M', '禎'), + (0xFA54, 'M', '穀'), + (0xFA55, 'M', '突'), + (0xFA56, 'M', '節'), + (0xFA57, 'M', '練'), + (0xFA58, 'M', '縉'), + (0xFA59, 'M', '繁'), + (0xFA5A, 'M', '署'), + (0xFA5B, 'M', '者'), + (0xFA5C, 'M', '臭'), + (0xFA5D, 'M', '艹'), + (0xFA5F, 'M', '著'), + (0xFA60, 'M', '褐'), + (0xFA61, 'M', '視'), + (0xFA62, 'M', '謁'), + (0xFA63, 'M', '謹'), + (0xFA64, 'M', '賓'), + (0xFA65, 'M', '贈'), + (0xFA66, 'M', '辶'), + (0xFA67, 'M', '逸'), + (0xFA68, 'M', '難'), + (0xFA69, 'M', '響'), + (0xFA6A, 'M', '頻'), + (0xFA6B, 'M', '恵'), + (0xFA6C, 'M', '𤋮'), + (0xFA6D, 'M', '舘'), (0xFA6E, 'X'), - (0xFA70, 'M', u'並'), - (0xFA71, 'M', u'况'), - (0xFA72, 'M', u'全'), - (0xFA73, 'M', u'侀'), - (0xFA74, 'M', u'充'), - (0xFA75, 'M', u'冀'), - (0xFA76, 'M', u'勇'), - (0xFA77, 'M', u'勺'), - (0xFA78, 'M', u'喝'), - (0xFA79, 'M', u'啕'), - (0xFA7A, 'M', u'喙'), - (0xFA7B, 'M', u'嗢'), - (0xFA7C, 'M', u'塚'), - (0xFA7D, 'M', u'墳'), - (0xFA7E, 'M', u'奄'), - (0xFA7F, 'M', u'奔'), - (0xFA80, 'M', u'婢'), - (0xFA81, 'M', u'嬨'), - (0xFA82, 'M', u'廒'), - (0xFA83, 'M', u'廙'), - (0xFA84, 'M', u'彩'), - (0xFA85, 'M', u'徭'), - (0xFA86, 'M', u'惘'), - (0xFA87, 'M', u'慎'), - (0xFA88, 'M', u'愈'), - (0xFA89, 'M', u'憎'), - (0xFA8A, 'M', u'慠'), - (0xFA8B, 'M', u'懲'), - (0xFA8C, 'M', u'戴'), - (0xFA8D, 'M', u'揄'), - (0xFA8E, 'M', u'搜'), - (0xFA8F, 'M', u'摒'), - (0xFA90, 'M', u'敖'), - (0xFA91, 'M', u'晴'), - (0xFA92, 'M', u'朗'), - (0xFA93, 'M', u'望'), - (0xFA94, 'M', u'杖'), - (0xFA95, 'M', u'歹'), - (0xFA96, 'M', u'殺'), - (0xFA97, 'M', u'流'), - (0xFA98, 'M', u'滛'), - (0xFA99, 'M', u'滋'), - (0xFA9A, 'M', u'漢'), - (0xFA9B, 'M', u'瀞'), - (0xFA9C, 'M', u'煮'), - (0xFA9D, 'M', u'瞧'), - (0xFA9E, 'M', u'爵'), - (0xFA9F, 'M', u'犯'), - (0xFAA0, 'M', u'猪'), - (0xFAA1, 'M', u'瑱'), - (0xFAA2, 'M', u'甆'), - (0xFAA3, 'M', u'画'), - (0xFAA4, 'M', u'瘝'), - (0xFAA5, 'M', u'瘟'), - (0xFAA6, 'M', u'益'), - (0xFAA7, 'M', u'盛'), - (0xFAA8, 'M', u'直'), - (0xFAA9, 'M', u'睊'), - (0xFAAA, 'M', u'着'), - (0xFAAB, 'M', u'磌'), - (0xFAAC, 'M', u'窱'), + (0xFA70, 'M', '並'), + (0xFA71, 'M', '况'), + (0xFA72, 'M', '全'), + (0xFA73, 'M', '侀'), + (0xFA74, 'M', '充'), + (0xFA75, 'M', '冀'), + (0xFA76, 'M', '勇'), + (0xFA77, 'M', '勺'), + (0xFA78, 'M', '喝'), + (0xFA79, 'M', '啕'), + (0xFA7A, 'M', '喙'), + (0xFA7B, 'M', '嗢'), + (0xFA7C, 'M', '塚'), + (0xFA7D, 'M', '墳'), + (0xFA7E, 'M', '奄'), + (0xFA7F, 'M', '奔'), + (0xFA80, 'M', '婢'), + (0xFA81, 'M', '嬨'), ] def _seg_43(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0xFAAD, 'M', u'節'), - (0xFAAE, 'M', u'类'), - (0xFAAF, 'M', u'絛'), - (0xFAB0, 'M', u'練'), - (0xFAB1, 'M', u'缾'), - (0xFAB2, 'M', u'者'), - (0xFAB3, 'M', u'荒'), - (0xFAB4, 'M', u'華'), - (0xFAB5, 'M', u'蝹'), - (0xFAB6, 'M', u'襁'), - (0xFAB7, 'M', u'覆'), - (0xFAB8, 'M', u'視'), - (0xFAB9, 'M', u'調'), - (0xFABA, 'M', u'諸'), - (0xFABB, 'M', u'請'), - (0xFABC, 'M', u'謁'), - (0xFABD, 'M', u'諾'), - (0xFABE, 'M', u'諭'), - (0xFABF, 'M', u'謹'), - (0xFAC0, 'M', u'變'), - (0xFAC1, 'M', u'贈'), - (0xFAC2, 'M', u'輸'), - (0xFAC3, 'M', u'遲'), - (0xFAC4, 'M', u'醙'), - (0xFAC5, 'M', u'鉶'), - (0xFAC6, 'M', u'陼'), - (0xFAC7, 'M', u'難'), - (0xFAC8, 'M', u'靖'), - (0xFAC9, 'M', u'韛'), - (0xFACA, 'M', u'響'), - (0xFACB, 'M', u'頋'), - (0xFACC, 'M', u'頻'), - (0xFACD, 'M', u'鬒'), - (0xFACE, 'M', u'龜'), - (0xFACF, 'M', u'𢡊'), - (0xFAD0, 'M', u'𢡄'), - (0xFAD1, 'M', u'𣏕'), - (0xFAD2, 'M', u'㮝'), - (0xFAD3, 'M', u'䀘'), - (0xFAD4, 'M', u'䀹'), - (0xFAD5, 'M', u'𥉉'), - (0xFAD6, 'M', u'𥳐'), - (0xFAD7, 'M', u'𧻓'), - (0xFAD8, 'M', u'齃'), - (0xFAD9, 'M', u'龎'), + (0xFA82, 'M', '廒'), + (0xFA83, 'M', '廙'), + (0xFA84, 'M', '彩'), + (0xFA85, 'M', '徭'), + (0xFA86, 'M', '惘'), + (0xFA87, 'M', '慎'), + (0xFA88, 'M', '愈'), + (0xFA89, 'M', '憎'), + (0xFA8A, 'M', '慠'), + (0xFA8B, 'M', '懲'), + (0xFA8C, 'M', '戴'), + (0xFA8D, 'M', '揄'), + (0xFA8E, 'M', '搜'), + (0xFA8F, 'M', '摒'), + (0xFA90, 'M', '敖'), + (0xFA91, 'M', '晴'), + (0xFA92, 'M', '朗'), + (0xFA93, 'M', '望'), + (0xFA94, 'M', '杖'), + (0xFA95, 'M', '歹'), + (0xFA96, 'M', '殺'), + (0xFA97, 'M', '流'), + (0xFA98, 'M', '滛'), + (0xFA99, 'M', '滋'), + (0xFA9A, 'M', '漢'), + (0xFA9B, 'M', '瀞'), + (0xFA9C, 'M', '煮'), + (0xFA9D, 'M', '瞧'), + (0xFA9E, 'M', '爵'), + (0xFA9F, 'M', '犯'), + (0xFAA0, 'M', '猪'), + (0xFAA1, 'M', '瑱'), + (0xFAA2, 'M', '甆'), + (0xFAA3, 'M', '画'), + (0xFAA4, 'M', '瘝'), + (0xFAA5, 'M', '瘟'), + (0xFAA6, 'M', '益'), + (0xFAA7, 'M', '盛'), + (0xFAA8, 'M', '直'), + (0xFAA9, 'M', '睊'), + (0xFAAA, 'M', '着'), + (0xFAAB, 'M', '磌'), + (0xFAAC, 'M', '窱'), + (0xFAAD, 'M', '節'), + (0xFAAE, 'M', '类'), + (0xFAAF, 'M', '絛'), + (0xFAB0, 'M', '練'), + (0xFAB1, 'M', '缾'), + (0xFAB2, 'M', '者'), + (0xFAB3, 'M', '荒'), + (0xFAB4, 'M', '華'), + (0xFAB5, 'M', '蝹'), + (0xFAB6, 'M', '襁'), + (0xFAB7, 'M', '覆'), + (0xFAB8, 'M', '視'), + (0xFAB9, 'M', '調'), + (0xFABA, 'M', '諸'), + (0xFABB, 'M', '請'), + (0xFABC, 'M', '謁'), + (0xFABD, 'M', '諾'), + (0xFABE, 'M', '諭'), + (0xFABF, 'M', '謹'), + (0xFAC0, 'M', '變'), + (0xFAC1, 'M', '贈'), + (0xFAC2, 'M', '輸'), + (0xFAC3, 'M', '遲'), + (0xFAC4, 'M', '醙'), + (0xFAC5, 'M', '鉶'), + (0xFAC6, 'M', '陼'), + (0xFAC7, 'M', '難'), + (0xFAC8, 'M', '靖'), + (0xFAC9, 'M', '韛'), + (0xFACA, 'M', '響'), + (0xFACB, 'M', '頋'), + (0xFACC, 'M', '頻'), + (0xFACD, 'M', '鬒'), + (0xFACE, 'M', '龜'), + (0xFACF, 'M', '𢡊'), + (0xFAD0, 'M', '𢡄'), + (0xFAD1, 'M', '𣏕'), + (0xFAD2, 'M', '㮝'), + (0xFAD3, 'M', '䀘'), + (0xFAD4, 'M', '䀹'), + (0xFAD5, 'M', '𥉉'), + (0xFAD6, 'M', '𥳐'), + (0xFAD7, 'M', '𧻓'), + (0xFAD8, 'M', '齃'), + (0xFAD9, 'M', '龎'), (0xFADA, 'X'), - (0xFB00, 'M', u'ff'), - (0xFB01, 'M', u'fi'), - (0xFB02, 'M', u'fl'), - (0xFB03, 'M', u'ffi'), - (0xFB04, 'M', u'ffl'), - (0xFB05, 'M', u'st'), + (0xFB00, 'M', 'ff'), + (0xFB01, 'M', 'fi'), + (0xFB02, 'M', 'fl'), + (0xFB03, 'M', 'ffi'), + (0xFB04, 'M', 'ffl'), + (0xFB05, 'M', 'st'), (0xFB07, 'X'), - (0xFB13, 'M', u'մն'), - (0xFB14, 'M', u'մե'), - (0xFB15, 'M', u'մի'), - (0xFB16, 'M', u'վն'), - (0xFB17, 'M', u'մխ'), - (0xFB18, 'X'), - (0xFB1D, 'M', u'יִ'), - (0xFB1E, 'V'), - (0xFB1F, 'M', u'ײַ'), - (0xFB20, 'M', u'ע'), - (0xFB21, 'M', u'א'), - (0xFB22, 'M', u'ד'), - (0xFB23, 'M', u'ה'), - (0xFB24, 'M', u'כ'), - (0xFB25, 'M', u'ל'), - (0xFB26, 'M', u'ם'), - (0xFB27, 'M', u'ר'), - (0xFB28, 'M', u'ת'), - (0xFB29, '3', u'+'), - (0xFB2A, 'M', u'שׁ'), - (0xFB2B, 'M', u'שׂ'), - (0xFB2C, 'M', u'שּׁ'), - (0xFB2D, 'M', u'שּׂ'), - (0xFB2E, 'M', u'אַ'), - (0xFB2F, 'M', u'אָ'), - (0xFB30, 'M', u'אּ'), - (0xFB31, 'M', u'בּ'), - (0xFB32, 'M', u'גּ'), - (0xFB33, 'M', u'דּ'), - (0xFB34, 'M', u'הּ'), - (0xFB35, 'M', u'וּ'), - (0xFB36, 'M', u'זּ'), - (0xFB37, 'X'), - (0xFB38, 'M', u'טּ'), - (0xFB39, 'M', u'יּ'), - (0xFB3A, 'M', u'ךּ'), - (0xFB3B, 'M', u'כּ'), - (0xFB3C, 'M', u'לּ'), - (0xFB3D, 'X'), - (0xFB3E, 'M', u'מּ'), - (0xFB3F, 'X'), - (0xFB40, 'M', u'נּ'), - (0xFB41, 'M', u'סּ'), - (0xFB42, 'X'), - (0xFB43, 'M', u'ףּ'), - (0xFB44, 'M', u'פּ'), - (0xFB45, 'X'), + (0xFB13, 'M', 'մն'), + (0xFB14, 'M', 'մե'), + (0xFB15, 'M', 'մի'), + (0xFB16, 'M', 'վն'), ] def _seg_44(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0xFB46, 'M', u'צּ'), - (0xFB47, 'M', u'קּ'), - (0xFB48, 'M', u'רּ'), - (0xFB49, 'M', u'שּ'), - (0xFB4A, 'M', u'תּ'), - (0xFB4B, 'M', u'וֹ'), - (0xFB4C, 'M', u'בֿ'), - (0xFB4D, 'M', u'כֿ'), - (0xFB4E, 'M', u'פֿ'), - (0xFB4F, 'M', u'אל'), - (0xFB50, 'M', u'ٱ'), - (0xFB52, 'M', u'ٻ'), - (0xFB56, 'M', u'پ'), - (0xFB5A, 'M', u'ڀ'), - (0xFB5E, 'M', u'ٺ'), - (0xFB62, 'M', u'ٿ'), - (0xFB66, 'M', u'ٹ'), - (0xFB6A, 'M', u'ڤ'), - (0xFB6E, 'M', u'ڦ'), - (0xFB72, 'M', u'ڄ'), - (0xFB76, 'M', u'ڃ'), - (0xFB7A, 'M', u'چ'), - (0xFB7E, 'M', u'ڇ'), - (0xFB82, 'M', u'ڍ'), - (0xFB84, 'M', u'ڌ'), - (0xFB86, 'M', u'ڎ'), - (0xFB88, 'M', u'ڈ'), - (0xFB8A, 'M', u'ژ'), - (0xFB8C, 'M', u'ڑ'), - (0xFB8E, 'M', u'ک'), - (0xFB92, 'M', u'گ'), - (0xFB96, 'M', u'ڳ'), - (0xFB9A, 'M', u'ڱ'), - (0xFB9E, 'M', u'ں'), - (0xFBA0, 'M', u'ڻ'), - (0xFBA4, 'M', u'ۀ'), - (0xFBA6, 'M', u'ہ'), - (0xFBAA, 'M', u'ھ'), - (0xFBAE, 'M', u'ے'), - (0xFBB0, 'M', u'ۓ'), + (0xFB17, 'M', 'մխ'), + (0xFB18, 'X'), + (0xFB1D, 'M', 'יִ'), + (0xFB1E, 'V'), + (0xFB1F, 'M', 'ײַ'), + (0xFB20, 'M', 'ע'), + (0xFB21, 'M', 'א'), + (0xFB22, 'M', 'ד'), + (0xFB23, 'M', 'ה'), + (0xFB24, 'M', 'כ'), + (0xFB25, 'M', 'ל'), + (0xFB26, 'M', 'ם'), + (0xFB27, 'M', 'ר'), + (0xFB28, 'M', 'ת'), + (0xFB29, '3', '+'), + (0xFB2A, 'M', 'שׁ'), + (0xFB2B, 'M', 'שׂ'), + (0xFB2C, 'M', 'שּׁ'), + (0xFB2D, 'M', 'שּׂ'), + (0xFB2E, 'M', 'אַ'), + (0xFB2F, 'M', 'אָ'), + (0xFB30, 'M', 'אּ'), + (0xFB31, 'M', 'בּ'), + (0xFB32, 'M', 'גּ'), + (0xFB33, 'M', 'דּ'), + (0xFB34, 'M', 'הּ'), + (0xFB35, 'M', 'וּ'), + (0xFB36, 'M', 'זּ'), + (0xFB37, 'X'), + (0xFB38, 'M', 'טּ'), + (0xFB39, 'M', 'יּ'), + (0xFB3A, 'M', 'ךּ'), + (0xFB3B, 'M', 'כּ'), + (0xFB3C, 'M', 'לּ'), + (0xFB3D, 'X'), + (0xFB3E, 'M', 'מּ'), + (0xFB3F, 'X'), + (0xFB40, 'M', 'נּ'), + (0xFB41, 'M', 'סּ'), + (0xFB42, 'X'), + (0xFB43, 'M', 'ףּ'), + (0xFB44, 'M', 'פּ'), + (0xFB45, 'X'), + (0xFB46, 'M', 'צּ'), + (0xFB47, 'M', 'קּ'), + (0xFB48, 'M', 'רּ'), + (0xFB49, 'M', 'שּ'), + (0xFB4A, 'M', 'תּ'), + (0xFB4B, 'M', 'וֹ'), + (0xFB4C, 'M', 'בֿ'), + (0xFB4D, 'M', 'כֿ'), + (0xFB4E, 'M', 'פֿ'), + (0xFB4F, 'M', 'אל'), + (0xFB50, 'M', 'ٱ'), + (0xFB52, 'M', 'ٻ'), + (0xFB56, 'M', 'پ'), + (0xFB5A, 'M', 'ڀ'), + (0xFB5E, 'M', 'ٺ'), + (0xFB62, 'M', 'ٿ'), + (0xFB66, 'M', 'ٹ'), + (0xFB6A, 'M', 'ڤ'), + (0xFB6E, 'M', 'ڦ'), + (0xFB72, 'M', 'ڄ'), + (0xFB76, 'M', 'ڃ'), + (0xFB7A, 'M', 'چ'), + (0xFB7E, 'M', 'ڇ'), + (0xFB82, 'M', 'ڍ'), + (0xFB84, 'M', 'ڌ'), + (0xFB86, 'M', 'ڎ'), + (0xFB88, 'M', 'ڈ'), + (0xFB8A, 'M', 'ژ'), + (0xFB8C, 'M', 'ڑ'), + (0xFB8E, 'M', 'ک'), + (0xFB92, 'M', 'گ'), + (0xFB96, 'M', 'ڳ'), + (0xFB9A, 'M', 'ڱ'), + (0xFB9E, 'M', 'ں'), + (0xFBA0, 'M', 'ڻ'), + (0xFBA4, 'M', 'ۀ'), + (0xFBA6, 'M', 'ہ'), + (0xFBAA, 'M', 'ھ'), + (0xFBAE, 'M', 'ے'), + (0xFBB0, 'M', 'ۓ'), (0xFBB2, 'V'), (0xFBC2, 'X'), - (0xFBD3, 'M', u'ڭ'), - (0xFBD7, 'M', u'ۇ'), - (0xFBD9, 'M', u'ۆ'), - (0xFBDB, 'M', u'ۈ'), - (0xFBDD, 'M', u'ۇٴ'), - (0xFBDE, 'M', u'ۋ'), - (0xFBE0, 'M', u'ۅ'), - (0xFBE2, 'M', u'ۉ'), - (0xFBE4, 'M', u'ې'), - (0xFBE8, 'M', u'ى'), - (0xFBEA, 'M', u'ئا'), - (0xFBEC, 'M', u'ئە'), - (0xFBEE, 'M', u'ئو'), - (0xFBF0, 'M', u'ئۇ'), - (0xFBF2, 'M', u'ئۆ'), - (0xFBF4, 'M', u'ئۈ'), - (0xFBF6, 'M', u'ئې'), - (0xFBF9, 'M', u'ئى'), - (0xFBFC, 'M', u'ی'), - (0xFC00, 'M', u'ئج'), - (0xFC01, 'M', u'ئح'), - (0xFC02, 'M', u'ئم'), - (0xFC03, 'M', u'ئى'), - (0xFC04, 'M', u'ئي'), - (0xFC05, 'M', u'بج'), - (0xFC06, 'M', u'بح'), - (0xFC07, 'M', u'بخ'), - (0xFC08, 'M', u'بم'), - (0xFC09, 'M', u'بى'), - (0xFC0A, 'M', u'بي'), - (0xFC0B, 'M', u'تج'), - (0xFC0C, 'M', u'تح'), - (0xFC0D, 'M', u'تخ'), - (0xFC0E, 'M', u'تم'), - (0xFC0F, 'M', u'تى'), - (0xFC10, 'M', u'تي'), - (0xFC11, 'M', u'ثج'), - (0xFC12, 'M', u'ثم'), - (0xFC13, 'M', u'ثى'), - (0xFC14, 'M', u'ثي'), - (0xFC15, 'M', u'جح'), - (0xFC16, 'M', u'جم'), - (0xFC17, 'M', u'حج'), - (0xFC18, 'M', u'حم'), - (0xFC19, 'M', u'خج'), - (0xFC1A, 'M', u'خح'), - (0xFC1B, 'M', u'خم'), - (0xFC1C, 'M', u'سج'), - (0xFC1D, 'M', u'سح'), - (0xFC1E, 'M', u'سخ'), - (0xFC1F, 'M', u'سم'), - (0xFC20, 'M', u'صح'), - (0xFC21, 'M', u'صم'), - (0xFC22, 'M', u'ضج'), - (0xFC23, 'M', u'ضح'), - (0xFC24, 'M', u'ضخ'), - (0xFC25, 'M', u'ضم'), - (0xFC26, 'M', u'طح'), + (0xFBD3, 'M', 'ڭ'), + (0xFBD7, 'M', 'ۇ'), + (0xFBD9, 'M', 'ۆ'), + (0xFBDB, 'M', 'ۈ'), + (0xFBDD, 'M', 'ۇٴ'), + (0xFBDE, 'M', 'ۋ'), + (0xFBE0, 'M', 'ۅ'), + (0xFBE2, 'M', 'ۉ'), + (0xFBE4, 'M', 'ې'), + (0xFBE8, 'M', 'ى'), + (0xFBEA, 'M', 'ئا'), + (0xFBEC, 'M', 'ئە'), + (0xFBEE, 'M', 'ئو'), + (0xFBF0, 'M', 'ئۇ'), + (0xFBF2, 'M', 'ئۆ'), ] def _seg_45(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0xFC27, 'M', u'طم'), - (0xFC28, 'M', u'ظم'), - (0xFC29, 'M', u'عج'), - (0xFC2A, 'M', u'عم'), - (0xFC2B, 'M', u'غج'), - (0xFC2C, 'M', u'غم'), - (0xFC2D, 'M', u'فج'), - (0xFC2E, 'M', u'فح'), - (0xFC2F, 'M', u'فخ'), - (0xFC30, 'M', u'فم'), - (0xFC31, 'M', u'فى'), - (0xFC32, 'M', u'في'), - (0xFC33, 'M', u'قح'), - (0xFC34, 'M', u'قم'), - (0xFC35, 'M', u'قى'), - (0xFC36, 'M', u'قي'), - (0xFC37, 'M', u'كا'), - (0xFC38, 'M', u'كج'), - (0xFC39, 'M', u'كح'), - (0xFC3A, 'M', u'كخ'), - (0xFC3B, 'M', u'كل'), - (0xFC3C, 'M', u'كم'), - (0xFC3D, 'M', u'كى'), - (0xFC3E, 'M', u'كي'), - (0xFC3F, 'M', u'لج'), - (0xFC40, 'M', u'لح'), - (0xFC41, 'M', u'لخ'), - (0xFC42, 'M', u'لم'), - (0xFC43, 'M', u'لى'), - (0xFC44, 'M', u'لي'), - (0xFC45, 'M', u'مج'), - (0xFC46, 'M', u'مح'), - (0xFC47, 'M', u'مخ'), - (0xFC48, 'M', u'مم'), - (0xFC49, 'M', u'مى'), - (0xFC4A, 'M', u'مي'), - (0xFC4B, 'M', u'نج'), - (0xFC4C, 'M', u'نح'), - (0xFC4D, 'M', u'نخ'), - (0xFC4E, 'M', u'نم'), - (0xFC4F, 'M', u'نى'), - (0xFC50, 'M', u'ني'), - (0xFC51, 'M', u'هج'), - (0xFC52, 'M', u'هم'), - (0xFC53, 'M', u'هى'), - (0xFC54, 'M', u'هي'), - (0xFC55, 'M', u'يج'), - (0xFC56, 'M', u'يح'), - (0xFC57, 'M', u'يخ'), - (0xFC58, 'M', u'يم'), - (0xFC59, 'M', u'يى'), - (0xFC5A, 'M', u'يي'), - (0xFC5B, 'M', u'ذٰ'), - (0xFC5C, 'M', u'رٰ'), - (0xFC5D, 'M', u'ىٰ'), - (0xFC5E, '3', u' ٌّ'), - (0xFC5F, '3', u' ٍّ'), - (0xFC60, '3', u' َّ'), - (0xFC61, '3', u' ُّ'), - (0xFC62, '3', u' ِّ'), - (0xFC63, '3', u' ّٰ'), - (0xFC64, 'M', u'ئر'), - (0xFC65, 'M', u'ئز'), - (0xFC66, 'M', u'ئم'), - (0xFC67, 'M', u'ئن'), - (0xFC68, 'M', u'ئى'), - (0xFC69, 'M', u'ئي'), - (0xFC6A, 'M', u'بر'), - (0xFC6B, 'M', u'بز'), - (0xFC6C, 'M', u'بم'), - (0xFC6D, 'M', u'بن'), - (0xFC6E, 'M', u'بى'), - (0xFC6F, 'M', u'بي'), - (0xFC70, 'M', u'تر'), - (0xFC71, 'M', u'تز'), - (0xFC72, 'M', u'تم'), - (0xFC73, 'M', u'تن'), - (0xFC74, 'M', u'تى'), - (0xFC75, 'M', u'تي'), - (0xFC76, 'M', u'ثر'), - (0xFC77, 'M', u'ثز'), - (0xFC78, 'M', u'ثم'), - (0xFC79, 'M', u'ثن'), - (0xFC7A, 'M', u'ثى'), - (0xFC7B, 'M', u'ثي'), - (0xFC7C, 'M', u'فى'), - (0xFC7D, 'M', u'في'), - (0xFC7E, 'M', u'قى'), - (0xFC7F, 'M', u'قي'), - (0xFC80, 'M', u'كا'), - (0xFC81, 'M', u'كل'), - (0xFC82, 'M', u'كم'), - (0xFC83, 'M', u'كى'), - (0xFC84, 'M', u'كي'), - (0xFC85, 'M', u'لم'), - (0xFC86, 'M', u'لى'), - (0xFC87, 'M', u'لي'), - (0xFC88, 'M', u'ما'), - (0xFC89, 'M', u'مم'), - (0xFC8A, 'M', u'نر'), + (0xFBF4, 'M', 'ئۈ'), + (0xFBF6, 'M', 'ئې'), + (0xFBF9, 'M', 'ئى'), + (0xFBFC, 'M', 'ی'), + (0xFC00, 'M', 'ئج'), + (0xFC01, 'M', 'ئح'), + (0xFC02, 'M', 'ئم'), + (0xFC03, 'M', 'ئى'), + (0xFC04, 'M', 'ئي'), + (0xFC05, 'M', 'بج'), + (0xFC06, 'M', 'بح'), + (0xFC07, 'M', 'بخ'), + (0xFC08, 'M', 'بم'), + (0xFC09, 'M', 'بى'), + (0xFC0A, 'M', 'بي'), + (0xFC0B, 'M', 'تج'), + (0xFC0C, 'M', 'تح'), + (0xFC0D, 'M', 'تخ'), + (0xFC0E, 'M', 'تم'), + (0xFC0F, 'M', 'تى'), + (0xFC10, 'M', 'تي'), + (0xFC11, 'M', 'ثج'), + (0xFC12, 'M', 'ثم'), + (0xFC13, 'M', 'ثى'), + (0xFC14, 'M', 'ثي'), + (0xFC15, 'M', 'جح'), + (0xFC16, 'M', 'جم'), + (0xFC17, 'M', 'حج'), + (0xFC18, 'M', 'حم'), + (0xFC19, 'M', 'خج'), + (0xFC1A, 'M', 'خح'), + (0xFC1B, 'M', 'خم'), + (0xFC1C, 'M', 'سج'), + (0xFC1D, 'M', 'سح'), + (0xFC1E, 'M', 'سخ'), + (0xFC1F, 'M', 'سم'), + (0xFC20, 'M', 'صح'), + (0xFC21, 'M', 'صم'), + (0xFC22, 'M', 'ضج'), + (0xFC23, 'M', 'ضح'), + (0xFC24, 'M', 'ضخ'), + (0xFC25, 'M', 'ضم'), + (0xFC26, 'M', 'طح'), + (0xFC27, 'M', 'طم'), + (0xFC28, 'M', 'ظم'), + (0xFC29, 'M', 'عج'), + (0xFC2A, 'M', 'عم'), + (0xFC2B, 'M', 'غج'), + (0xFC2C, 'M', 'غم'), + (0xFC2D, 'M', 'فج'), + (0xFC2E, 'M', 'فح'), + (0xFC2F, 'M', 'فخ'), + (0xFC30, 'M', 'فم'), + (0xFC31, 'M', 'فى'), + (0xFC32, 'M', 'في'), + (0xFC33, 'M', 'قح'), + (0xFC34, 'M', 'قم'), + (0xFC35, 'M', 'قى'), + (0xFC36, 'M', 'قي'), + (0xFC37, 'M', 'كا'), + (0xFC38, 'M', 'كج'), + (0xFC39, 'M', 'كح'), + (0xFC3A, 'M', 'كخ'), + (0xFC3B, 'M', 'كل'), + (0xFC3C, 'M', 'كم'), + (0xFC3D, 'M', 'كى'), + (0xFC3E, 'M', 'كي'), + (0xFC3F, 'M', 'لج'), + (0xFC40, 'M', 'لح'), + (0xFC41, 'M', 'لخ'), + (0xFC42, 'M', 'لم'), + (0xFC43, 'M', 'لى'), + (0xFC44, 'M', 'لي'), + (0xFC45, 'M', 'مج'), + (0xFC46, 'M', 'مح'), + (0xFC47, 'M', 'مخ'), + (0xFC48, 'M', 'مم'), + (0xFC49, 'M', 'مى'), + (0xFC4A, 'M', 'مي'), + (0xFC4B, 'M', 'نج'), + (0xFC4C, 'M', 'نح'), + (0xFC4D, 'M', 'نخ'), + (0xFC4E, 'M', 'نم'), + (0xFC4F, 'M', 'نى'), + (0xFC50, 'M', 'ني'), + (0xFC51, 'M', 'هج'), + (0xFC52, 'M', 'هم'), + (0xFC53, 'M', 'هى'), + (0xFC54, 'M', 'هي'), + (0xFC55, 'M', 'يج'), + (0xFC56, 'M', 'يح'), + (0xFC57, 'M', 'يخ'), + (0xFC58, 'M', 'يم'), + (0xFC59, 'M', 'يى'), + (0xFC5A, 'M', 'يي'), + (0xFC5B, 'M', 'ذٰ'), + (0xFC5C, 'M', 'رٰ'), + (0xFC5D, 'M', 'ىٰ'), + (0xFC5E, '3', ' ٌّ'), + (0xFC5F, '3', ' ٍّ'), ] def _seg_46(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0xFC8B, 'M', u'نز'), - (0xFC8C, 'M', u'نم'), - (0xFC8D, 'M', u'نن'), - (0xFC8E, 'M', u'نى'), - (0xFC8F, 'M', u'ني'), - (0xFC90, 'M', u'ىٰ'), - (0xFC91, 'M', u'ير'), - (0xFC92, 'M', u'يز'), - (0xFC93, 'M', u'يم'), - (0xFC94, 'M', u'ين'), - (0xFC95, 'M', u'يى'), - (0xFC96, 'M', u'يي'), - (0xFC97, 'M', u'ئج'), - (0xFC98, 'M', u'ئح'), - (0xFC99, 'M', u'ئخ'), - (0xFC9A, 'M', u'ئم'), - (0xFC9B, 'M', u'ئه'), - (0xFC9C, 'M', u'بج'), - (0xFC9D, 'M', u'بح'), - (0xFC9E, 'M', u'بخ'), - (0xFC9F, 'M', u'بم'), - (0xFCA0, 'M', u'به'), - (0xFCA1, 'M', u'تج'), - (0xFCA2, 'M', u'تح'), - (0xFCA3, 'M', u'تخ'), - (0xFCA4, 'M', u'تم'), - (0xFCA5, 'M', u'ته'), - (0xFCA6, 'M', u'ثم'), - (0xFCA7, 'M', u'جح'), - (0xFCA8, 'M', u'جم'), - (0xFCA9, 'M', u'حج'), - (0xFCAA, 'M', u'حم'), - (0xFCAB, 'M', u'خج'), - (0xFCAC, 'M', u'خم'), - (0xFCAD, 'M', u'سج'), - (0xFCAE, 'M', u'سح'), - (0xFCAF, 'M', u'سخ'), - (0xFCB0, 'M', u'سم'), - (0xFCB1, 'M', u'صح'), - (0xFCB2, 'M', u'صخ'), - (0xFCB3, 'M', u'صم'), - (0xFCB4, 'M', u'ضج'), - (0xFCB5, 'M', u'ضح'), - (0xFCB6, 'M', u'ضخ'), - (0xFCB7, 'M', u'ضم'), - (0xFCB8, 'M', u'طح'), - (0xFCB9, 'M', u'ظم'), - (0xFCBA, 'M', u'عج'), - (0xFCBB, 'M', u'عم'), - (0xFCBC, 'M', u'غج'), - (0xFCBD, 'M', u'غم'), - (0xFCBE, 'M', u'فج'), - (0xFCBF, 'M', u'فح'), - (0xFCC0, 'M', u'فخ'), - (0xFCC1, 'M', u'فم'), - (0xFCC2, 'M', u'قح'), - (0xFCC3, 'M', u'قم'), - (0xFCC4, 'M', u'كج'), - (0xFCC5, 'M', u'كح'), - (0xFCC6, 'M', u'كخ'), - (0xFCC7, 'M', u'كل'), - (0xFCC8, 'M', u'كم'), - (0xFCC9, 'M', u'لج'), - (0xFCCA, 'M', u'لح'), - (0xFCCB, 'M', u'لخ'), - (0xFCCC, 'M', u'لم'), - (0xFCCD, 'M', u'له'), - (0xFCCE, 'M', u'مج'), - (0xFCCF, 'M', u'مح'), - (0xFCD0, 'M', u'مخ'), - (0xFCD1, 'M', u'مم'), - (0xFCD2, 'M', u'نج'), - (0xFCD3, 'M', u'نح'), - (0xFCD4, 'M', u'نخ'), - (0xFCD5, 'M', u'نم'), - (0xFCD6, 'M', u'نه'), - (0xFCD7, 'M', u'هج'), - (0xFCD8, 'M', u'هم'), - (0xFCD9, 'M', u'هٰ'), - (0xFCDA, 'M', u'يج'), - (0xFCDB, 'M', u'يح'), - (0xFCDC, 'M', u'يخ'), - (0xFCDD, 'M', u'يم'), - (0xFCDE, 'M', u'يه'), - (0xFCDF, 'M', u'ئم'), - (0xFCE0, 'M', u'ئه'), - (0xFCE1, 'M', u'بم'), - (0xFCE2, 'M', u'به'), - (0xFCE3, 'M', u'تم'), - (0xFCE4, 'M', u'ته'), - (0xFCE5, 'M', u'ثم'), - (0xFCE6, 'M', u'ثه'), - (0xFCE7, 'M', u'سم'), - (0xFCE8, 'M', u'سه'), - (0xFCE9, 'M', u'شم'), - (0xFCEA, 'M', u'شه'), - (0xFCEB, 'M', u'كل'), - (0xFCEC, 'M', u'كم'), - (0xFCED, 'M', u'لم'), - (0xFCEE, 'M', u'نم'), + (0xFC60, '3', ' َّ'), + (0xFC61, '3', ' ُّ'), + (0xFC62, '3', ' ِّ'), + (0xFC63, '3', ' ّٰ'), + (0xFC64, 'M', 'ئر'), + (0xFC65, 'M', 'ئز'), + (0xFC66, 'M', 'ئم'), + (0xFC67, 'M', 'ئن'), + (0xFC68, 'M', 'ئى'), + (0xFC69, 'M', 'ئي'), + (0xFC6A, 'M', 'بر'), + (0xFC6B, 'M', 'بز'), + (0xFC6C, 'M', 'بم'), + (0xFC6D, 'M', 'بن'), + (0xFC6E, 'M', 'بى'), + (0xFC6F, 'M', 'بي'), + (0xFC70, 'M', 'تر'), + (0xFC71, 'M', 'تز'), + (0xFC72, 'M', 'تم'), + (0xFC73, 'M', 'تن'), + (0xFC74, 'M', 'تى'), + (0xFC75, 'M', 'تي'), + (0xFC76, 'M', 'ثر'), + (0xFC77, 'M', 'ثز'), + (0xFC78, 'M', 'ثم'), + (0xFC79, 'M', 'ثن'), + (0xFC7A, 'M', 'ثى'), + (0xFC7B, 'M', 'ثي'), + (0xFC7C, 'M', 'فى'), + (0xFC7D, 'M', 'في'), + (0xFC7E, 'M', 'قى'), + (0xFC7F, 'M', 'قي'), + (0xFC80, 'M', 'كا'), + (0xFC81, 'M', 'كل'), + (0xFC82, 'M', 'كم'), + (0xFC83, 'M', 'كى'), + (0xFC84, 'M', 'كي'), + (0xFC85, 'M', 'لم'), + (0xFC86, 'M', 'لى'), + (0xFC87, 'M', 'لي'), + (0xFC88, 'M', 'ما'), + (0xFC89, 'M', 'مم'), + (0xFC8A, 'M', 'نر'), + (0xFC8B, 'M', 'نز'), + (0xFC8C, 'M', 'نم'), + (0xFC8D, 'M', 'نن'), + (0xFC8E, 'M', 'نى'), + (0xFC8F, 'M', 'ني'), + (0xFC90, 'M', 'ىٰ'), + (0xFC91, 'M', 'ير'), + (0xFC92, 'M', 'يز'), + (0xFC93, 'M', 'يم'), + (0xFC94, 'M', 'ين'), + (0xFC95, 'M', 'يى'), + (0xFC96, 'M', 'يي'), + (0xFC97, 'M', 'ئج'), + (0xFC98, 'M', 'ئح'), + (0xFC99, 'M', 'ئخ'), + (0xFC9A, 'M', 'ئم'), + (0xFC9B, 'M', 'ئه'), + (0xFC9C, 'M', 'بج'), + (0xFC9D, 'M', 'بح'), + (0xFC9E, 'M', 'بخ'), + (0xFC9F, 'M', 'بم'), + (0xFCA0, 'M', 'به'), + (0xFCA1, 'M', 'تج'), + (0xFCA2, 'M', 'تح'), + (0xFCA3, 'M', 'تخ'), + (0xFCA4, 'M', 'تم'), + (0xFCA5, 'M', 'ته'), + (0xFCA6, 'M', 'ثم'), + (0xFCA7, 'M', 'جح'), + (0xFCA8, 'M', 'جم'), + (0xFCA9, 'M', 'حج'), + (0xFCAA, 'M', 'حم'), + (0xFCAB, 'M', 'خج'), + (0xFCAC, 'M', 'خم'), + (0xFCAD, 'M', 'سج'), + (0xFCAE, 'M', 'سح'), + (0xFCAF, 'M', 'سخ'), + (0xFCB0, 'M', 'سم'), + (0xFCB1, 'M', 'صح'), + (0xFCB2, 'M', 'صخ'), + (0xFCB3, 'M', 'صم'), + (0xFCB4, 'M', 'ضج'), + (0xFCB5, 'M', 'ضح'), + (0xFCB6, 'M', 'ضخ'), + (0xFCB7, 'M', 'ضم'), + (0xFCB8, 'M', 'طح'), + (0xFCB9, 'M', 'ظم'), + (0xFCBA, 'M', 'عج'), + (0xFCBB, 'M', 'عم'), + (0xFCBC, 'M', 'غج'), + (0xFCBD, 'M', 'غم'), + (0xFCBE, 'M', 'فج'), + (0xFCBF, 'M', 'فح'), + (0xFCC0, 'M', 'فخ'), + (0xFCC1, 'M', 'فم'), + (0xFCC2, 'M', 'قح'), + (0xFCC3, 'M', 'قم'), ] def _seg_47(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0xFCEF, 'M', u'نه'), - (0xFCF0, 'M', u'يم'), - (0xFCF1, 'M', u'يه'), - (0xFCF2, 'M', u'ـَّ'), - (0xFCF3, 'M', u'ـُّ'), - (0xFCF4, 'M', u'ـِّ'), - (0xFCF5, 'M', u'طى'), - (0xFCF6, 'M', u'طي'), - (0xFCF7, 'M', u'عى'), - (0xFCF8, 'M', u'عي'), - (0xFCF9, 'M', u'غى'), - (0xFCFA, 'M', u'غي'), - (0xFCFB, 'M', u'سى'), - (0xFCFC, 'M', u'سي'), - (0xFCFD, 'M', u'شى'), - (0xFCFE, 'M', u'شي'), - (0xFCFF, 'M', u'حى'), - (0xFD00, 'M', u'حي'), - (0xFD01, 'M', u'جى'), - (0xFD02, 'M', u'جي'), - (0xFD03, 'M', u'خى'), - (0xFD04, 'M', u'خي'), - (0xFD05, 'M', u'صى'), - (0xFD06, 'M', u'صي'), - (0xFD07, 'M', u'ضى'), - (0xFD08, 'M', u'ضي'), - (0xFD09, 'M', u'شج'), - (0xFD0A, 'M', u'شح'), - (0xFD0B, 'M', u'شخ'), - (0xFD0C, 'M', u'شم'), - (0xFD0D, 'M', u'شر'), - (0xFD0E, 'M', u'سر'), - (0xFD0F, 'M', u'صر'), - (0xFD10, 'M', u'ضر'), - (0xFD11, 'M', u'طى'), - (0xFD12, 'M', u'طي'), - (0xFD13, 'M', u'عى'), - (0xFD14, 'M', u'عي'), - (0xFD15, 'M', u'غى'), - (0xFD16, 'M', u'غي'), - (0xFD17, 'M', u'سى'), - (0xFD18, 'M', u'سي'), - (0xFD19, 'M', u'شى'), - (0xFD1A, 'M', u'شي'), - (0xFD1B, 'M', u'حى'), - (0xFD1C, 'M', u'حي'), - (0xFD1D, 'M', u'جى'), - (0xFD1E, 'M', u'جي'), - (0xFD1F, 'M', u'خى'), - (0xFD20, 'M', u'خي'), - (0xFD21, 'M', u'صى'), - (0xFD22, 'M', u'صي'), - (0xFD23, 'M', u'ضى'), - (0xFD24, 'M', u'ضي'), - (0xFD25, 'M', u'شج'), - (0xFD26, 'M', u'شح'), - (0xFD27, 'M', u'شخ'), - (0xFD28, 'M', u'شم'), - (0xFD29, 'M', u'شر'), - (0xFD2A, 'M', u'سر'), - (0xFD2B, 'M', u'صر'), - (0xFD2C, 'M', u'ضر'), - (0xFD2D, 'M', u'شج'), - (0xFD2E, 'M', u'شح'), - (0xFD2F, 'M', u'شخ'), - (0xFD30, 'M', u'شم'), - (0xFD31, 'M', u'سه'), - (0xFD32, 'M', u'شه'), - (0xFD33, 'M', u'طم'), - (0xFD34, 'M', u'سج'), - (0xFD35, 'M', u'سح'), - (0xFD36, 'M', u'سخ'), - (0xFD37, 'M', u'شج'), - (0xFD38, 'M', u'شح'), - (0xFD39, 'M', u'شخ'), - (0xFD3A, 'M', u'طم'), - (0xFD3B, 'M', u'ظم'), - (0xFD3C, 'M', u'اً'), - (0xFD3E, 'V'), - (0xFD40, 'X'), - (0xFD50, 'M', u'تجم'), - (0xFD51, 'M', u'تحج'), - (0xFD53, 'M', u'تحم'), - (0xFD54, 'M', u'تخم'), - (0xFD55, 'M', u'تمج'), - (0xFD56, 'M', u'تمح'), - (0xFD57, 'M', u'تمخ'), - (0xFD58, 'M', u'جمح'), - (0xFD5A, 'M', u'حمي'), - (0xFD5B, 'M', u'حمى'), - (0xFD5C, 'M', u'سحج'), - (0xFD5D, 'M', u'سجح'), - (0xFD5E, 'M', u'سجى'), - (0xFD5F, 'M', u'سمح'), - (0xFD61, 'M', u'سمج'), - (0xFD62, 'M', u'سمم'), - (0xFD64, 'M', u'صحح'), - (0xFD66, 'M', u'صمم'), - (0xFD67, 'M', u'شحم'), - (0xFD69, 'M', u'شجي'), + (0xFCC4, 'M', 'كج'), + (0xFCC5, 'M', 'كح'), + (0xFCC6, 'M', 'كخ'), + (0xFCC7, 'M', 'كل'), + (0xFCC8, 'M', 'كم'), + (0xFCC9, 'M', 'لج'), + (0xFCCA, 'M', 'لح'), + (0xFCCB, 'M', 'لخ'), + (0xFCCC, 'M', 'لم'), + (0xFCCD, 'M', 'له'), + (0xFCCE, 'M', 'مج'), + (0xFCCF, 'M', 'مح'), + (0xFCD0, 'M', 'مخ'), + (0xFCD1, 'M', 'مم'), + (0xFCD2, 'M', 'نج'), + (0xFCD3, 'M', 'نح'), + (0xFCD4, 'M', 'نخ'), + (0xFCD5, 'M', 'نم'), + (0xFCD6, 'M', 'نه'), + (0xFCD7, 'M', 'هج'), + (0xFCD8, 'M', 'هم'), + (0xFCD9, 'M', 'هٰ'), + (0xFCDA, 'M', 'يج'), + (0xFCDB, 'M', 'يح'), + (0xFCDC, 'M', 'يخ'), + (0xFCDD, 'M', 'يم'), + (0xFCDE, 'M', 'يه'), + (0xFCDF, 'M', 'ئم'), + (0xFCE0, 'M', 'ئه'), + (0xFCE1, 'M', 'بم'), + (0xFCE2, 'M', 'به'), + (0xFCE3, 'M', 'تم'), + (0xFCE4, 'M', 'ته'), + (0xFCE5, 'M', 'ثم'), + (0xFCE6, 'M', 'ثه'), + (0xFCE7, 'M', 'سم'), + (0xFCE8, 'M', 'سه'), + (0xFCE9, 'M', 'شم'), + (0xFCEA, 'M', 'شه'), + (0xFCEB, 'M', 'كل'), + (0xFCEC, 'M', 'كم'), + (0xFCED, 'M', 'لم'), + (0xFCEE, 'M', 'نم'), + (0xFCEF, 'M', 'نه'), + (0xFCF0, 'M', 'يم'), + (0xFCF1, 'M', 'يه'), + (0xFCF2, 'M', 'ـَّ'), + (0xFCF3, 'M', 'ـُّ'), + (0xFCF4, 'M', 'ـِّ'), + (0xFCF5, 'M', 'طى'), + (0xFCF6, 'M', 'طي'), + (0xFCF7, 'M', 'عى'), + (0xFCF8, 'M', 'عي'), + (0xFCF9, 'M', 'غى'), + (0xFCFA, 'M', 'غي'), + (0xFCFB, 'M', 'سى'), + (0xFCFC, 'M', 'سي'), + (0xFCFD, 'M', 'شى'), + (0xFCFE, 'M', 'شي'), + (0xFCFF, 'M', 'حى'), + (0xFD00, 'M', 'حي'), + (0xFD01, 'M', 'جى'), + (0xFD02, 'M', 'جي'), + (0xFD03, 'M', 'خى'), + (0xFD04, 'M', 'خي'), + (0xFD05, 'M', 'صى'), + (0xFD06, 'M', 'صي'), + (0xFD07, 'M', 'ضى'), + (0xFD08, 'M', 'ضي'), + (0xFD09, 'M', 'شج'), + (0xFD0A, 'M', 'شح'), + (0xFD0B, 'M', 'شخ'), + (0xFD0C, 'M', 'شم'), + (0xFD0D, 'M', 'شر'), + (0xFD0E, 'M', 'سر'), + (0xFD0F, 'M', 'صر'), + (0xFD10, 'M', 'ضر'), + (0xFD11, 'M', 'طى'), + (0xFD12, 'M', 'طي'), + (0xFD13, 'M', 'عى'), + (0xFD14, 'M', 'عي'), + (0xFD15, 'M', 'غى'), + (0xFD16, 'M', 'غي'), + (0xFD17, 'M', 'سى'), + (0xFD18, 'M', 'سي'), + (0xFD19, 'M', 'شى'), + (0xFD1A, 'M', 'شي'), + (0xFD1B, 'M', 'حى'), + (0xFD1C, 'M', 'حي'), + (0xFD1D, 'M', 'جى'), + (0xFD1E, 'M', 'جي'), + (0xFD1F, 'M', 'خى'), + (0xFD20, 'M', 'خي'), + (0xFD21, 'M', 'صى'), + (0xFD22, 'M', 'صي'), + (0xFD23, 'M', 'ضى'), + (0xFD24, 'M', 'ضي'), + (0xFD25, 'M', 'شج'), + (0xFD26, 'M', 'شح'), + (0xFD27, 'M', 'شخ'), ] def _seg_48(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0xFD6A, 'M', u'شمخ'), - (0xFD6C, 'M', u'شمم'), - (0xFD6E, 'M', u'ضحى'), - (0xFD6F, 'M', u'ضخم'), - (0xFD71, 'M', u'طمح'), - (0xFD73, 'M', u'طمم'), - (0xFD74, 'M', u'طمي'), - (0xFD75, 'M', u'عجم'), - (0xFD76, 'M', u'عمم'), - (0xFD78, 'M', u'عمى'), - (0xFD79, 'M', u'غمم'), - (0xFD7A, 'M', u'غمي'), - (0xFD7B, 'M', u'غمى'), - (0xFD7C, 'M', u'فخم'), - (0xFD7E, 'M', u'قمح'), - (0xFD7F, 'M', u'قمم'), - (0xFD80, 'M', u'لحم'), - (0xFD81, 'M', u'لحي'), - (0xFD82, 'M', u'لحى'), - (0xFD83, 'M', u'لجج'), - (0xFD85, 'M', u'لخم'), - (0xFD87, 'M', u'لمح'), - (0xFD89, 'M', u'محج'), - (0xFD8A, 'M', u'محم'), - (0xFD8B, 'M', u'محي'), - (0xFD8C, 'M', u'مجح'), - (0xFD8D, 'M', u'مجم'), - (0xFD8E, 'M', u'مخج'), - (0xFD8F, 'M', u'مخم'), + (0xFD28, 'M', 'شم'), + (0xFD29, 'M', 'شر'), + (0xFD2A, 'M', 'سر'), + (0xFD2B, 'M', 'صر'), + (0xFD2C, 'M', 'ضر'), + (0xFD2D, 'M', 'شج'), + (0xFD2E, 'M', 'شح'), + (0xFD2F, 'M', 'شخ'), + (0xFD30, 'M', 'شم'), + (0xFD31, 'M', 'سه'), + (0xFD32, 'M', 'شه'), + (0xFD33, 'M', 'طم'), + (0xFD34, 'M', 'سج'), + (0xFD35, 'M', 'سح'), + (0xFD36, 'M', 'سخ'), + (0xFD37, 'M', 'شج'), + (0xFD38, 'M', 'شح'), + (0xFD39, 'M', 'شخ'), + (0xFD3A, 'M', 'طم'), + (0xFD3B, 'M', 'ظم'), + (0xFD3C, 'M', 'اً'), + (0xFD3E, 'V'), + (0xFD40, 'X'), + (0xFD50, 'M', 'تجم'), + (0xFD51, 'M', 'تحج'), + (0xFD53, 'M', 'تحم'), + (0xFD54, 'M', 'تخم'), + (0xFD55, 'M', 'تمج'), + (0xFD56, 'M', 'تمح'), + (0xFD57, 'M', 'تمخ'), + (0xFD58, 'M', 'جمح'), + (0xFD5A, 'M', 'حمي'), + (0xFD5B, 'M', 'حمى'), + (0xFD5C, 'M', 'سحج'), + (0xFD5D, 'M', 'سجح'), + (0xFD5E, 'M', 'سجى'), + (0xFD5F, 'M', 'سمح'), + (0xFD61, 'M', 'سمج'), + (0xFD62, 'M', 'سمم'), + (0xFD64, 'M', 'صحح'), + (0xFD66, 'M', 'صمم'), + (0xFD67, 'M', 'شحم'), + (0xFD69, 'M', 'شجي'), + (0xFD6A, 'M', 'شمخ'), + (0xFD6C, 'M', 'شمم'), + (0xFD6E, 'M', 'ضحى'), + (0xFD6F, 'M', 'ضخم'), + (0xFD71, 'M', 'طمح'), + (0xFD73, 'M', 'طمم'), + (0xFD74, 'M', 'طمي'), + (0xFD75, 'M', 'عجم'), + (0xFD76, 'M', 'عمم'), + (0xFD78, 'M', 'عمى'), + (0xFD79, 'M', 'غمم'), + (0xFD7A, 'M', 'غمي'), + (0xFD7B, 'M', 'غمى'), + (0xFD7C, 'M', 'فخم'), + (0xFD7E, 'M', 'قمح'), + (0xFD7F, 'M', 'قمم'), + (0xFD80, 'M', 'لحم'), + (0xFD81, 'M', 'لحي'), + (0xFD82, 'M', 'لحى'), + (0xFD83, 'M', 'لجج'), + (0xFD85, 'M', 'لخم'), + (0xFD87, 'M', 'لمح'), + (0xFD89, 'M', 'محج'), + (0xFD8A, 'M', 'محم'), + (0xFD8B, 'M', 'محي'), + (0xFD8C, 'M', 'مجح'), + (0xFD8D, 'M', 'مجم'), + (0xFD8E, 'M', 'مخج'), + (0xFD8F, 'M', 'مخم'), (0xFD90, 'X'), - (0xFD92, 'M', u'مجخ'), - (0xFD93, 'M', u'همج'), - (0xFD94, 'M', u'همم'), - (0xFD95, 'M', u'نحم'), - (0xFD96, 'M', u'نحى'), - (0xFD97, 'M', u'نجم'), - (0xFD99, 'M', u'نجى'), - (0xFD9A, 'M', u'نمي'), - (0xFD9B, 'M', u'نمى'), - (0xFD9C, 'M', u'يمم'), - (0xFD9E, 'M', u'بخي'), - (0xFD9F, 'M', u'تجي'), - (0xFDA0, 'M', u'تجى'), - (0xFDA1, 'M', u'تخي'), - (0xFDA2, 'M', u'تخى'), - (0xFDA3, 'M', u'تمي'), - (0xFDA4, 'M', u'تمى'), - (0xFDA5, 'M', u'جمي'), - (0xFDA6, 'M', u'جحى'), - (0xFDA7, 'M', u'جمى'), - (0xFDA8, 'M', u'سخى'), - (0xFDA9, 'M', u'صحي'), - (0xFDAA, 'M', u'شحي'), - (0xFDAB, 'M', u'ضحي'), - (0xFDAC, 'M', u'لجي'), - (0xFDAD, 'M', u'لمي'), - (0xFDAE, 'M', u'يحي'), - (0xFDAF, 'M', u'يجي'), - (0xFDB0, 'M', u'يمي'), - (0xFDB1, 'M', u'ممي'), - (0xFDB2, 'M', u'قمي'), - (0xFDB3, 'M', u'نحي'), - (0xFDB4, 'M', u'قمح'), - (0xFDB5, 'M', u'لحم'), - (0xFDB6, 'M', u'عمي'), - (0xFDB7, 'M', u'كمي'), - (0xFDB8, 'M', u'نجح'), - (0xFDB9, 'M', u'مخي'), - (0xFDBA, 'M', u'لجم'), - (0xFDBB, 'M', u'كمم'), - (0xFDBC, 'M', u'لجم'), - (0xFDBD, 'M', u'نجح'), - (0xFDBE, 'M', u'جحي'), - (0xFDBF, 'M', u'حجي'), - (0xFDC0, 'M', u'مجي'), - (0xFDC1, 'M', u'فمي'), - (0xFDC2, 'M', u'بحي'), - (0xFDC3, 'M', u'كمم'), - (0xFDC4, 'M', u'عجم'), - (0xFDC5, 'M', u'صمم'), - (0xFDC6, 'M', u'سخي'), - (0xFDC7, 'M', u'نجي'), - (0xFDC8, 'X'), - (0xFDF0, 'M', u'صلے'), - (0xFDF1, 'M', u'قلے'), - (0xFDF2, 'M', u'الله'), - (0xFDF3, 'M', u'اكبر'), - (0xFDF4, 'M', u'محمد'), - (0xFDF5, 'M', u'صلعم'), - (0xFDF6, 'M', u'رسول'), - (0xFDF7, 'M', u'عليه'), - (0xFDF8, 'M', u'وسلم'), - (0xFDF9, 'M', u'صلى'), - (0xFDFA, '3', u'صلى الله عليه وسلم'), - (0xFDFB, '3', u'جل جلاله'), - (0xFDFC, 'M', u'ریال'), - (0xFDFD, 'V'), - (0xFDFE, 'X'), - (0xFE00, 'I'), - (0xFE10, '3', u','), + (0xFD92, 'M', 'مجخ'), + (0xFD93, 'M', 'همج'), + (0xFD94, 'M', 'همم'), + (0xFD95, 'M', 'نحم'), + (0xFD96, 'M', 'نحى'), + (0xFD97, 'M', 'نجم'), + (0xFD99, 'M', 'نجى'), + (0xFD9A, 'M', 'نمي'), + (0xFD9B, 'M', 'نمى'), + (0xFD9C, 'M', 'يمم'), + (0xFD9E, 'M', 'بخي'), + (0xFD9F, 'M', 'تجي'), + (0xFDA0, 'M', 'تجى'), + (0xFDA1, 'M', 'تخي'), + (0xFDA2, 'M', 'تخى'), + (0xFDA3, 'M', 'تمي'), + (0xFDA4, 'M', 'تمى'), + (0xFDA5, 'M', 'جمي'), + (0xFDA6, 'M', 'جحى'), + (0xFDA7, 'M', 'جمى'), + (0xFDA8, 'M', 'سخى'), + (0xFDA9, 'M', 'صحي'), + (0xFDAA, 'M', 'شحي'), + (0xFDAB, 'M', 'ضحي'), + (0xFDAC, 'M', 'لجي'), + (0xFDAD, 'M', 'لمي'), + (0xFDAE, 'M', 'يحي'), ] def _seg_49(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0xFE11, 'M', u'、'), + (0xFDAF, 'M', 'يجي'), + (0xFDB0, 'M', 'يمي'), + (0xFDB1, 'M', 'ممي'), + (0xFDB2, 'M', 'قمي'), + (0xFDB3, 'M', 'نحي'), + (0xFDB4, 'M', 'قمح'), + (0xFDB5, 'M', 'لحم'), + (0xFDB6, 'M', 'عمي'), + (0xFDB7, 'M', 'كمي'), + (0xFDB8, 'M', 'نجح'), + (0xFDB9, 'M', 'مخي'), + (0xFDBA, 'M', 'لجم'), + (0xFDBB, 'M', 'كمم'), + (0xFDBC, 'M', 'لجم'), + (0xFDBD, 'M', 'نجح'), + (0xFDBE, 'M', 'جحي'), + (0xFDBF, 'M', 'حجي'), + (0xFDC0, 'M', 'مجي'), + (0xFDC1, 'M', 'فمي'), + (0xFDC2, 'M', 'بحي'), + (0xFDC3, 'M', 'كمم'), + (0xFDC4, 'M', 'عجم'), + (0xFDC5, 'M', 'صمم'), + (0xFDC6, 'M', 'سخي'), + (0xFDC7, 'M', 'نجي'), + (0xFDC8, 'X'), + (0xFDF0, 'M', 'صلے'), + (0xFDF1, 'M', 'قلے'), + (0xFDF2, 'M', 'الله'), + (0xFDF3, 'M', 'اكبر'), + (0xFDF4, 'M', 'محمد'), + (0xFDF5, 'M', 'صلعم'), + (0xFDF6, 'M', 'رسول'), + (0xFDF7, 'M', 'عليه'), + (0xFDF8, 'M', 'وسلم'), + (0xFDF9, 'M', 'صلى'), + (0xFDFA, '3', 'صلى الله عليه وسلم'), + (0xFDFB, '3', 'جل جلاله'), + (0xFDFC, 'M', 'ریال'), + (0xFDFD, 'V'), + (0xFDFE, 'X'), + (0xFE00, 'I'), + (0xFE10, '3', ','), + (0xFE11, 'M', '、'), (0xFE12, 'X'), - (0xFE13, '3', u':'), - (0xFE14, '3', u';'), - (0xFE15, '3', u'!'), - (0xFE16, '3', u'?'), - (0xFE17, 'M', u'〖'), - (0xFE18, 'M', u'〗'), + (0xFE13, '3', ':'), + (0xFE14, '3', ';'), + (0xFE15, '3', '!'), + (0xFE16, '3', '?'), + (0xFE17, 'M', '〖'), + (0xFE18, 'M', '〗'), (0xFE19, 'X'), (0xFE20, 'V'), (0xFE30, 'X'), - (0xFE31, 'M', u'—'), - (0xFE32, 'M', u'–'), - (0xFE33, '3', u'_'), - (0xFE35, '3', u'('), - (0xFE36, '3', u')'), - (0xFE37, '3', u'{'), - (0xFE38, '3', u'}'), - (0xFE39, 'M', u'〔'), - (0xFE3A, 'M', u'〕'), - (0xFE3B, 'M', u'【'), - (0xFE3C, 'M', u'】'), - (0xFE3D, 'M', u'《'), - (0xFE3E, 'M', u'》'), - (0xFE3F, 'M', u'〈'), - (0xFE40, 'M', u'〉'), - (0xFE41, 'M', u'「'), - (0xFE42, 'M', u'」'), - (0xFE43, 'M', u'『'), - (0xFE44, 'M', u'』'), + (0xFE31, 'M', '—'), + (0xFE32, 'M', '–'), + (0xFE33, '3', '_'), + (0xFE35, '3', '('), + (0xFE36, '3', ')'), + (0xFE37, '3', '{'), + (0xFE38, '3', '}'), + (0xFE39, 'M', '〔'), + (0xFE3A, 'M', '〕'), + (0xFE3B, 'M', '【'), + (0xFE3C, 'M', '】'), + (0xFE3D, 'M', '《'), + (0xFE3E, 'M', '》'), + (0xFE3F, 'M', '〈'), + (0xFE40, 'M', '〉'), + (0xFE41, 'M', '「'), + (0xFE42, 'M', '」'), + (0xFE43, 'M', '『'), + (0xFE44, 'M', '』'), (0xFE45, 'V'), - (0xFE47, '3', u'['), - (0xFE48, '3', u']'), - (0xFE49, '3', u' ̅'), - (0xFE4D, '3', u'_'), - (0xFE50, '3', u','), - (0xFE51, 'M', u'、'), + (0xFE47, '3', '['), + (0xFE48, '3', ']'), + (0xFE49, '3', ' ̅'), + (0xFE4D, '3', '_'), + (0xFE50, '3', ','), + (0xFE51, 'M', '、'), (0xFE52, 'X'), - (0xFE54, '3', u';'), - (0xFE55, '3', u':'), - (0xFE56, '3', u'?'), - (0xFE57, '3', u'!'), - (0xFE58, 'M', u'—'), - (0xFE59, '3', u'('), - (0xFE5A, '3', u')'), - (0xFE5B, '3', u'{'), - (0xFE5C, '3', u'}'), - (0xFE5D, 'M', u'〔'), - (0xFE5E, 'M', u'〕'), - (0xFE5F, '3', u'#'), - (0xFE60, '3', u'&'), - (0xFE61, '3', u'*'), - (0xFE62, '3', u'+'), - (0xFE63, 'M', u'-'), - (0xFE64, '3', u'<'), - (0xFE65, '3', u'>'), - (0xFE66, '3', u'='), - (0xFE67, 'X'), - (0xFE68, '3', u'\\'), - (0xFE69, '3', u'$'), - (0xFE6A, '3', u'%'), - (0xFE6B, '3', u'@'), - (0xFE6C, 'X'), - (0xFE70, '3', u' ً'), - (0xFE71, 'M', u'ـً'), - (0xFE72, '3', u' ٌ'), - (0xFE73, 'V'), - (0xFE74, '3', u' ٍ'), - (0xFE75, 'X'), - (0xFE76, '3', u' َ'), - (0xFE77, 'M', u'ـَ'), - (0xFE78, '3', u' ُ'), - (0xFE79, 'M', u'ـُ'), - (0xFE7A, '3', u' ِ'), - (0xFE7B, 'M', u'ـِ'), - (0xFE7C, '3', u' ّ'), - (0xFE7D, 'M', u'ـّ'), - (0xFE7E, '3', u' ْ'), - (0xFE7F, 'M', u'ـْ'), - (0xFE80, 'M', u'ء'), - (0xFE81, 'M', u'آ'), - (0xFE83, 'M', u'أ'), - (0xFE85, 'M', u'ؤ'), - (0xFE87, 'M', u'إ'), - (0xFE89, 'M', u'ئ'), - (0xFE8D, 'M', u'ا'), - (0xFE8F, 'M', u'ب'), - (0xFE93, 'M', u'ة'), - (0xFE95, 'M', u'ت'), - (0xFE99, 'M', u'ث'), - (0xFE9D, 'M', u'ج'), - (0xFEA1, 'M', u'ح'), - (0xFEA5, 'M', u'خ'), - (0xFEA9, 'M', u'د'), - (0xFEAB, 'M', u'ذ'), - (0xFEAD, 'M', u'ر'), - (0xFEAF, 'M', u'ز'), - (0xFEB1, 'M', u'س'), - (0xFEB5, 'M', u'ش'), - (0xFEB9, 'M', u'ص'), + (0xFE54, '3', ';'), + (0xFE55, '3', ':'), + (0xFE56, '3', '?'), + (0xFE57, '3', '!'), + (0xFE58, 'M', '—'), + (0xFE59, '3', '('), + (0xFE5A, '3', ')'), + (0xFE5B, '3', '{'), + (0xFE5C, '3', '}'), + (0xFE5D, 'M', '〔'), + (0xFE5E, 'M', '〕'), + (0xFE5F, '3', '#'), + (0xFE60, '3', '&'), + (0xFE61, '3', '*'), + (0xFE62, '3', '+'), + (0xFE63, 'M', '-'), + (0xFE64, '3', '<'), + (0xFE65, '3', '>'), + (0xFE66, '3', '='), ] def _seg_50(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0xFEBD, 'M', u'ض'), - (0xFEC1, 'M', u'ط'), - (0xFEC5, 'M', u'ظ'), - (0xFEC9, 'M', u'ع'), - (0xFECD, 'M', u'غ'), - (0xFED1, 'M', u'ف'), - (0xFED5, 'M', u'ق'), - (0xFED9, 'M', u'ك'), - (0xFEDD, 'M', u'ل'), - (0xFEE1, 'M', u'م'), - (0xFEE5, 'M', u'ن'), - (0xFEE9, 'M', u'ه'), - (0xFEED, 'M', u'و'), - (0xFEEF, 'M', u'ى'), - (0xFEF1, 'M', u'ي'), - (0xFEF5, 'M', u'لآ'), - (0xFEF7, 'M', u'لأ'), - (0xFEF9, 'M', u'لإ'), - (0xFEFB, 'M', u'لا'), + (0xFE67, 'X'), + (0xFE68, '3', '\\'), + (0xFE69, '3', '$'), + (0xFE6A, '3', '%'), + (0xFE6B, '3', '@'), + (0xFE6C, 'X'), + (0xFE70, '3', ' ً'), + (0xFE71, 'M', 'ـً'), + (0xFE72, '3', ' ٌ'), + (0xFE73, 'V'), + (0xFE74, '3', ' ٍ'), + (0xFE75, 'X'), + (0xFE76, '3', ' َ'), + (0xFE77, 'M', 'ـَ'), + (0xFE78, '3', ' ُ'), + (0xFE79, 'M', 'ـُ'), + (0xFE7A, '3', ' ِ'), + (0xFE7B, 'M', 'ـِ'), + (0xFE7C, '3', ' ّ'), + (0xFE7D, 'M', 'ـّ'), + (0xFE7E, '3', ' ْ'), + (0xFE7F, 'M', 'ـْ'), + (0xFE80, 'M', 'ء'), + (0xFE81, 'M', 'آ'), + (0xFE83, 'M', 'أ'), + (0xFE85, 'M', 'ؤ'), + (0xFE87, 'M', 'إ'), + (0xFE89, 'M', 'ئ'), + (0xFE8D, 'M', 'ا'), + (0xFE8F, 'M', 'ب'), + (0xFE93, 'M', 'ة'), + (0xFE95, 'M', 'ت'), + (0xFE99, 'M', 'ث'), + (0xFE9D, 'M', 'ج'), + (0xFEA1, 'M', 'ح'), + (0xFEA5, 'M', 'خ'), + (0xFEA9, 'M', 'د'), + (0xFEAB, 'M', 'ذ'), + (0xFEAD, 'M', 'ر'), + (0xFEAF, 'M', 'ز'), + (0xFEB1, 'M', 'س'), + (0xFEB5, 'M', 'ش'), + (0xFEB9, 'M', 'ص'), + (0xFEBD, 'M', 'ض'), + (0xFEC1, 'M', 'ط'), + (0xFEC5, 'M', 'ظ'), + (0xFEC9, 'M', 'ع'), + (0xFECD, 'M', 'غ'), + (0xFED1, 'M', 'ف'), + (0xFED5, 'M', 'ق'), + (0xFED9, 'M', 'ك'), + (0xFEDD, 'M', 'ل'), + (0xFEE1, 'M', 'م'), + (0xFEE5, 'M', 'ن'), + (0xFEE9, 'M', 'ه'), + (0xFEED, 'M', 'و'), + (0xFEEF, 'M', 'ى'), + (0xFEF1, 'M', 'ي'), + (0xFEF5, 'M', 'لآ'), + (0xFEF7, 'M', 'لأ'), + (0xFEF9, 'M', 'لإ'), + (0xFEFB, 'M', 'لا'), (0xFEFD, 'X'), (0xFEFF, 'I'), (0xFF00, 'X'), - (0xFF01, '3', u'!'), - (0xFF02, '3', u'"'), - (0xFF03, '3', u'#'), - (0xFF04, '3', u'$'), - (0xFF05, '3', u'%'), - (0xFF06, '3', u'&'), - (0xFF07, '3', u'\''), - (0xFF08, '3', u'('), - (0xFF09, '3', u')'), - (0xFF0A, '3', u'*'), - (0xFF0B, '3', u'+'), - (0xFF0C, '3', u','), - (0xFF0D, 'M', u'-'), - (0xFF0E, 'M', u'.'), - (0xFF0F, '3', u'/'), - (0xFF10, 'M', u'0'), - (0xFF11, 'M', u'1'), - (0xFF12, 'M', u'2'), - (0xFF13, 'M', u'3'), - (0xFF14, 'M', u'4'), - (0xFF15, 'M', u'5'), - (0xFF16, 'M', u'6'), - (0xFF17, 'M', u'7'), - (0xFF18, 'M', u'8'), - (0xFF19, 'M', u'9'), - (0xFF1A, '3', u':'), - (0xFF1B, '3', u';'), - (0xFF1C, '3', u'<'), - (0xFF1D, '3', u'='), - (0xFF1E, '3', u'>'), - (0xFF1F, '3', u'?'), - (0xFF20, '3', u'@'), - (0xFF21, 'M', u'a'), - (0xFF22, 'M', u'b'), - (0xFF23, 'M', u'c'), - (0xFF24, 'M', u'd'), - (0xFF25, 'M', u'e'), - (0xFF26, 'M', u'f'), - (0xFF27, 'M', u'g'), - (0xFF28, 'M', u'h'), - (0xFF29, 'M', u'i'), - (0xFF2A, 'M', u'j'), - (0xFF2B, 'M', u'k'), - (0xFF2C, 'M', u'l'), - (0xFF2D, 'M', u'm'), - (0xFF2E, 'M', u'n'), - (0xFF2F, 'M', u'o'), - (0xFF30, 'M', u'p'), - (0xFF31, 'M', u'q'), - (0xFF32, 'M', u'r'), - (0xFF33, 'M', u's'), - (0xFF34, 'M', u't'), - (0xFF35, 'M', u'u'), - (0xFF36, 'M', u'v'), - (0xFF37, 'M', u'w'), - (0xFF38, 'M', u'x'), - (0xFF39, 'M', u'y'), - (0xFF3A, 'M', u'z'), - (0xFF3B, '3', u'['), - (0xFF3C, '3', u'\\'), - (0xFF3D, '3', u']'), - (0xFF3E, '3', u'^'), - (0xFF3F, '3', u'_'), - (0xFF40, '3', u'`'), - (0xFF41, 'M', u'a'), - (0xFF42, 'M', u'b'), - (0xFF43, 'M', u'c'), - (0xFF44, 'M', u'd'), - (0xFF45, 'M', u'e'), - (0xFF46, 'M', u'f'), - (0xFF47, 'M', u'g'), - (0xFF48, 'M', u'h'), - (0xFF49, 'M', u'i'), - (0xFF4A, 'M', u'j'), - (0xFF4B, 'M', u'k'), - (0xFF4C, 'M', u'l'), - (0xFF4D, 'M', u'm'), - (0xFF4E, 'M', u'n'), + (0xFF01, '3', '!'), + (0xFF02, '3', '"'), + (0xFF03, '3', '#'), + (0xFF04, '3', '$'), + (0xFF05, '3', '%'), + (0xFF06, '3', '&'), + (0xFF07, '3', '\''), + (0xFF08, '3', '('), + (0xFF09, '3', ')'), + (0xFF0A, '3', '*'), + (0xFF0B, '3', '+'), + (0xFF0C, '3', ','), + (0xFF0D, 'M', '-'), + (0xFF0E, 'M', '.'), + (0xFF0F, '3', '/'), + (0xFF10, 'M', '0'), + (0xFF11, 'M', '1'), + (0xFF12, 'M', '2'), + (0xFF13, 'M', '3'), + (0xFF14, 'M', '4'), + (0xFF15, 'M', '5'), + (0xFF16, 'M', '6'), + (0xFF17, 'M', '7'), + (0xFF18, 'M', '8'), + (0xFF19, 'M', '9'), + (0xFF1A, '3', ':'), + (0xFF1B, '3', ';'), + (0xFF1C, '3', '<'), + (0xFF1D, '3', '='), + (0xFF1E, '3', '>'), + (0xFF1F, '3', '?'), + (0xFF20, '3', '@'), + (0xFF21, 'M', 'a'), + (0xFF22, 'M', 'b'), + (0xFF23, 'M', 'c'), ] def _seg_51(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0xFF4F, 'M', u'o'), - (0xFF50, 'M', u'p'), - (0xFF51, 'M', u'q'), - (0xFF52, 'M', u'r'), - (0xFF53, 'M', u's'), - (0xFF54, 'M', u't'), - (0xFF55, 'M', u'u'), - (0xFF56, 'M', u'v'), - (0xFF57, 'M', u'w'), - (0xFF58, 'M', u'x'), - (0xFF59, 'M', u'y'), - (0xFF5A, 'M', u'z'), - (0xFF5B, '3', u'{'), - (0xFF5C, '3', u'|'), - (0xFF5D, '3', u'}'), - (0xFF5E, '3', u'~'), - (0xFF5F, 'M', u'⦅'), - (0xFF60, 'M', u'⦆'), - (0xFF61, 'M', u'.'), - (0xFF62, 'M', u'「'), - (0xFF63, 'M', u'」'), - (0xFF64, 'M', u'、'), - (0xFF65, 'M', u'・'), - (0xFF66, 'M', u'ヲ'), - (0xFF67, 'M', u'ァ'), - (0xFF68, 'M', u'ィ'), - (0xFF69, 'M', u'ゥ'), - (0xFF6A, 'M', u'ェ'), - (0xFF6B, 'M', u'ォ'), - (0xFF6C, 'M', u'ャ'), - (0xFF6D, 'M', u'ュ'), - (0xFF6E, 'M', u'ョ'), - (0xFF6F, 'M', u'ッ'), - (0xFF70, 'M', u'ー'), - (0xFF71, 'M', u'ア'), - (0xFF72, 'M', u'イ'), - (0xFF73, 'M', u'ウ'), - (0xFF74, 'M', u'エ'), - (0xFF75, 'M', u'オ'), - (0xFF76, 'M', u'カ'), - (0xFF77, 'M', u'キ'), - (0xFF78, 'M', u'ク'), - (0xFF79, 'M', u'ケ'), - (0xFF7A, 'M', u'コ'), - (0xFF7B, 'M', u'サ'), - (0xFF7C, 'M', u'シ'), - (0xFF7D, 'M', u'ス'), - (0xFF7E, 'M', u'セ'), - (0xFF7F, 'M', u'ソ'), - (0xFF80, 'M', u'タ'), - (0xFF81, 'M', u'チ'), - (0xFF82, 'M', u'ツ'), - (0xFF83, 'M', u'テ'), - (0xFF84, 'M', u'ト'), - (0xFF85, 'M', u'ナ'), - (0xFF86, 'M', u'ニ'), - (0xFF87, 'M', u'ヌ'), - (0xFF88, 'M', u'ネ'), - (0xFF89, 'M', u'ノ'), - (0xFF8A, 'M', u'ハ'), - (0xFF8B, 'M', u'ヒ'), - (0xFF8C, 'M', u'フ'), - (0xFF8D, 'M', u'ヘ'), - (0xFF8E, 'M', u'ホ'), - (0xFF8F, 'M', u'マ'), - (0xFF90, 'M', u'ミ'), - (0xFF91, 'M', u'ム'), - (0xFF92, 'M', u'メ'), - (0xFF93, 'M', u'モ'), - (0xFF94, 'M', u'ヤ'), - (0xFF95, 'M', u'ユ'), - (0xFF96, 'M', u'ヨ'), - (0xFF97, 'M', u'ラ'), - (0xFF98, 'M', u'リ'), - (0xFF99, 'M', u'ル'), - (0xFF9A, 'M', u'レ'), - (0xFF9B, 'M', u'ロ'), - (0xFF9C, 'M', u'ワ'), - (0xFF9D, 'M', u'ン'), - (0xFF9E, 'M', u'゙'), - (0xFF9F, 'M', u'゚'), - (0xFFA0, 'X'), - (0xFFA1, 'M', u'ᄀ'), - (0xFFA2, 'M', u'ᄁ'), - (0xFFA3, 'M', u'ᆪ'), - (0xFFA4, 'M', u'ᄂ'), - (0xFFA5, 'M', u'ᆬ'), - (0xFFA6, 'M', u'ᆭ'), - (0xFFA7, 'M', u'ᄃ'), - (0xFFA8, 'M', u'ᄄ'), - (0xFFA9, 'M', u'ᄅ'), - (0xFFAA, 'M', u'ᆰ'), - (0xFFAB, 'M', u'ᆱ'), - (0xFFAC, 'M', u'ᆲ'), - (0xFFAD, 'M', u'ᆳ'), - (0xFFAE, 'M', u'ᆴ'), - (0xFFAF, 'M', u'ᆵ'), - (0xFFB0, 'M', u'ᄚ'), - (0xFFB1, 'M', u'ᄆ'), - (0xFFB2, 'M', u'ᄇ'), + (0xFF24, 'M', 'd'), + (0xFF25, 'M', 'e'), + (0xFF26, 'M', 'f'), + (0xFF27, 'M', 'g'), + (0xFF28, 'M', 'h'), + (0xFF29, 'M', 'i'), + (0xFF2A, 'M', 'j'), + (0xFF2B, 'M', 'k'), + (0xFF2C, 'M', 'l'), + (0xFF2D, 'M', 'm'), + (0xFF2E, 'M', 'n'), + (0xFF2F, 'M', 'o'), + (0xFF30, 'M', 'p'), + (0xFF31, 'M', 'q'), + (0xFF32, 'M', 'r'), + (0xFF33, 'M', 's'), + (0xFF34, 'M', 't'), + (0xFF35, 'M', 'u'), + (0xFF36, 'M', 'v'), + (0xFF37, 'M', 'w'), + (0xFF38, 'M', 'x'), + (0xFF39, 'M', 'y'), + (0xFF3A, 'M', 'z'), + (0xFF3B, '3', '['), + (0xFF3C, '3', '\\'), + (0xFF3D, '3', ']'), + (0xFF3E, '3', '^'), + (0xFF3F, '3', '_'), + (0xFF40, '3', '`'), + (0xFF41, 'M', 'a'), + (0xFF42, 'M', 'b'), + (0xFF43, 'M', 'c'), + (0xFF44, 'M', 'd'), + (0xFF45, 'M', 'e'), + (0xFF46, 'M', 'f'), + (0xFF47, 'M', 'g'), + (0xFF48, 'M', 'h'), + (0xFF49, 'M', 'i'), + (0xFF4A, 'M', 'j'), + (0xFF4B, 'M', 'k'), + (0xFF4C, 'M', 'l'), + (0xFF4D, 'M', 'm'), + (0xFF4E, 'M', 'n'), + (0xFF4F, 'M', 'o'), + (0xFF50, 'M', 'p'), + (0xFF51, 'M', 'q'), + (0xFF52, 'M', 'r'), + (0xFF53, 'M', 's'), + (0xFF54, 'M', 't'), + (0xFF55, 'M', 'u'), + (0xFF56, 'M', 'v'), + (0xFF57, 'M', 'w'), + (0xFF58, 'M', 'x'), + (0xFF59, 'M', 'y'), + (0xFF5A, 'M', 'z'), + (0xFF5B, '3', '{'), + (0xFF5C, '3', '|'), + (0xFF5D, '3', '}'), + (0xFF5E, '3', '~'), + (0xFF5F, 'M', '⦅'), + (0xFF60, 'M', '⦆'), + (0xFF61, 'M', '.'), + (0xFF62, 'M', '「'), + (0xFF63, 'M', '」'), + (0xFF64, 'M', '、'), + (0xFF65, 'M', '・'), + (0xFF66, 'M', 'ヲ'), + (0xFF67, 'M', 'ァ'), + (0xFF68, 'M', 'ィ'), + (0xFF69, 'M', 'ゥ'), + (0xFF6A, 'M', 'ェ'), + (0xFF6B, 'M', 'ォ'), + (0xFF6C, 'M', 'ャ'), + (0xFF6D, 'M', 'ュ'), + (0xFF6E, 'M', 'ョ'), + (0xFF6F, 'M', 'ッ'), + (0xFF70, 'M', 'ー'), + (0xFF71, 'M', 'ア'), + (0xFF72, 'M', 'イ'), + (0xFF73, 'M', 'ウ'), + (0xFF74, 'M', 'エ'), + (0xFF75, 'M', 'オ'), + (0xFF76, 'M', 'カ'), + (0xFF77, 'M', 'キ'), + (0xFF78, 'M', 'ク'), + (0xFF79, 'M', 'ケ'), + (0xFF7A, 'M', 'コ'), + (0xFF7B, 'M', 'サ'), + (0xFF7C, 'M', 'シ'), + (0xFF7D, 'M', 'ス'), + (0xFF7E, 'M', 'セ'), + (0xFF7F, 'M', 'ソ'), + (0xFF80, 'M', 'タ'), + (0xFF81, 'M', 'チ'), + (0xFF82, 'M', 'ツ'), + (0xFF83, 'M', 'テ'), + (0xFF84, 'M', 'ト'), + (0xFF85, 'M', 'ナ'), + (0xFF86, 'M', 'ニ'), + (0xFF87, 'M', 'ヌ'), ] def _seg_52(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0xFFB3, 'M', u'ᄈ'), - (0xFFB4, 'M', u'ᄡ'), - (0xFFB5, 'M', u'ᄉ'), - (0xFFB6, 'M', u'ᄊ'), - (0xFFB7, 'M', u'ᄋ'), - (0xFFB8, 'M', u'ᄌ'), - (0xFFB9, 'M', u'ᄍ'), - (0xFFBA, 'M', u'ᄎ'), - (0xFFBB, 'M', u'ᄏ'), - (0xFFBC, 'M', u'ᄐ'), - (0xFFBD, 'M', u'ᄑ'), - (0xFFBE, 'M', u'ᄒ'), + (0xFF88, 'M', 'ネ'), + (0xFF89, 'M', 'ノ'), + (0xFF8A, 'M', 'ハ'), + (0xFF8B, 'M', 'ヒ'), + (0xFF8C, 'M', 'フ'), + (0xFF8D, 'M', 'ヘ'), + (0xFF8E, 'M', 'ホ'), + (0xFF8F, 'M', 'マ'), + (0xFF90, 'M', 'ミ'), + (0xFF91, 'M', 'ム'), + (0xFF92, 'M', 'メ'), + (0xFF93, 'M', 'モ'), + (0xFF94, 'M', 'ヤ'), + (0xFF95, 'M', 'ユ'), + (0xFF96, 'M', 'ヨ'), + (0xFF97, 'M', 'ラ'), + (0xFF98, 'M', 'リ'), + (0xFF99, 'M', 'ル'), + (0xFF9A, 'M', 'レ'), + (0xFF9B, 'M', 'ロ'), + (0xFF9C, 'M', 'ワ'), + (0xFF9D, 'M', 'ン'), + (0xFF9E, 'M', '゙'), + (0xFF9F, 'M', '゚'), + (0xFFA0, 'X'), + (0xFFA1, 'M', 'ᄀ'), + (0xFFA2, 'M', 'ᄁ'), + (0xFFA3, 'M', 'ᆪ'), + (0xFFA4, 'M', 'ᄂ'), + (0xFFA5, 'M', 'ᆬ'), + (0xFFA6, 'M', 'ᆭ'), + (0xFFA7, 'M', 'ᄃ'), + (0xFFA8, 'M', 'ᄄ'), + (0xFFA9, 'M', 'ᄅ'), + (0xFFAA, 'M', 'ᆰ'), + (0xFFAB, 'M', 'ᆱ'), + (0xFFAC, 'M', 'ᆲ'), + (0xFFAD, 'M', 'ᆳ'), + (0xFFAE, 'M', 'ᆴ'), + (0xFFAF, 'M', 'ᆵ'), + (0xFFB0, 'M', 'ᄚ'), + (0xFFB1, 'M', 'ᄆ'), + (0xFFB2, 'M', 'ᄇ'), + (0xFFB3, 'M', 'ᄈ'), + (0xFFB4, 'M', 'ᄡ'), + (0xFFB5, 'M', 'ᄉ'), + (0xFFB6, 'M', 'ᄊ'), + (0xFFB7, 'M', 'ᄋ'), + (0xFFB8, 'M', 'ᄌ'), + (0xFFB9, 'M', 'ᄍ'), + (0xFFBA, 'M', 'ᄎ'), + (0xFFBB, 'M', 'ᄏ'), + (0xFFBC, 'M', 'ᄐ'), + (0xFFBD, 'M', 'ᄑ'), + (0xFFBE, 'M', 'ᄒ'), (0xFFBF, 'X'), - (0xFFC2, 'M', u'ᅡ'), - (0xFFC3, 'M', u'ᅢ'), - (0xFFC4, 'M', u'ᅣ'), - (0xFFC5, 'M', u'ᅤ'), - (0xFFC6, 'M', u'ᅥ'), - (0xFFC7, 'M', u'ᅦ'), + (0xFFC2, 'M', 'ᅡ'), + (0xFFC3, 'M', 'ᅢ'), + (0xFFC4, 'M', 'ᅣ'), + (0xFFC5, 'M', 'ᅤ'), + (0xFFC6, 'M', 'ᅥ'), + (0xFFC7, 'M', 'ᅦ'), (0xFFC8, 'X'), - (0xFFCA, 'M', u'ᅧ'), - (0xFFCB, 'M', u'ᅨ'), - (0xFFCC, 'M', u'ᅩ'), - (0xFFCD, 'M', u'ᅪ'), - (0xFFCE, 'M', u'ᅫ'), - (0xFFCF, 'M', u'ᅬ'), + (0xFFCA, 'M', 'ᅧ'), + (0xFFCB, 'M', 'ᅨ'), + (0xFFCC, 'M', 'ᅩ'), + (0xFFCD, 'M', 'ᅪ'), + (0xFFCE, 'M', 'ᅫ'), + (0xFFCF, 'M', 'ᅬ'), (0xFFD0, 'X'), - (0xFFD2, 'M', u'ᅭ'), - (0xFFD3, 'M', u'ᅮ'), - (0xFFD4, 'M', u'ᅯ'), - (0xFFD5, 'M', u'ᅰ'), - (0xFFD6, 'M', u'ᅱ'), - (0xFFD7, 'M', u'ᅲ'), + (0xFFD2, 'M', 'ᅭ'), + (0xFFD3, 'M', 'ᅮ'), + (0xFFD4, 'M', 'ᅯ'), + (0xFFD5, 'M', 'ᅰ'), + (0xFFD6, 'M', 'ᅱ'), + (0xFFD7, 'M', 'ᅲ'), (0xFFD8, 'X'), - (0xFFDA, 'M', u'ᅳ'), - (0xFFDB, 'M', u'ᅴ'), - (0xFFDC, 'M', u'ᅵ'), + (0xFFDA, 'M', 'ᅳ'), + (0xFFDB, 'M', 'ᅴ'), + (0xFFDC, 'M', 'ᅵ'), (0xFFDD, 'X'), - (0xFFE0, 'M', u'¢'), - (0xFFE1, 'M', u'£'), - (0xFFE2, 'M', u'¬'), - (0xFFE3, '3', u' ̄'), - (0xFFE4, 'M', u'¦'), - (0xFFE5, 'M', u'¥'), - (0xFFE6, 'M', u'₩'), + (0xFFE0, 'M', '¢'), + (0xFFE1, 'M', '£'), + (0xFFE2, 'M', '¬'), + (0xFFE3, '3', ' ̄'), + (0xFFE4, 'M', '¦'), + (0xFFE5, 'M', '¥'), + (0xFFE6, 'M', '₩'), (0xFFE7, 'X'), - (0xFFE8, 'M', u'│'), - (0xFFE9, 'M', u'←'), - (0xFFEA, 'M', u'↑'), - (0xFFEB, 'M', u'→'), - (0xFFEC, 'M', u'↓'), - (0xFFED, 'M', u'■'), - (0xFFEE, 'M', u'○'), + (0xFFE8, 'M', '│'), + (0xFFE9, 'M', '←'), + (0xFFEA, 'M', '↑'), + (0xFFEB, 'M', '→'), + (0xFFEC, 'M', '↓'), + (0xFFED, 'M', '■'), + (0xFFEE, 'M', '○'), (0xFFEF, 'X'), (0x10000, 'V'), (0x1000C, 'X'), (0x1000D, 'V'), + ] + +def _seg_53(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ (0x10027, 'X'), (0x10028, 'V'), (0x1003B, 'X'), @@ -5490,7 +5592,7 @@ def _seg_52(): (0x10137, 'V'), (0x1018F, 'X'), (0x10190, 'V'), - (0x1019C, 'X'), + (0x1019D, 'X'), (0x101A0, 'V'), (0x101A1, 'X'), (0x101D0, 'V'), @@ -5513,90 +5615,91 @@ def _seg_52(): (0x103C4, 'X'), (0x103C8, 'V'), (0x103D6, 'X'), - (0x10400, 'M', u'𐐨'), - (0x10401, 'M', u'𐐩'), - ] - -def _seg_53(): - return [ - (0x10402, 'M', u'𐐪'), - (0x10403, 'M', u'𐐫'), - (0x10404, 'M', u'𐐬'), - (0x10405, 'M', u'𐐭'), - (0x10406, 'M', u'𐐮'), - (0x10407, 'M', u'𐐯'), - (0x10408, 'M', u'𐐰'), - (0x10409, 'M', u'𐐱'), - (0x1040A, 'M', u'𐐲'), - (0x1040B, 'M', u'𐐳'), - (0x1040C, 'M', u'𐐴'), - (0x1040D, 'M', u'𐐵'), - (0x1040E, 'M', u'𐐶'), - (0x1040F, 'M', u'𐐷'), - (0x10410, 'M', u'𐐸'), - (0x10411, 'M', u'𐐹'), - (0x10412, 'M', u'𐐺'), - (0x10413, 'M', u'𐐻'), - (0x10414, 'M', u'𐐼'), - (0x10415, 'M', u'𐐽'), - (0x10416, 'M', u'𐐾'), - (0x10417, 'M', u'𐐿'), - (0x10418, 'M', u'𐑀'), - (0x10419, 'M', u'𐑁'), - (0x1041A, 'M', u'𐑂'), - (0x1041B, 'M', u'𐑃'), - (0x1041C, 'M', u'𐑄'), - (0x1041D, 'M', u'𐑅'), - (0x1041E, 'M', u'𐑆'), - (0x1041F, 'M', u'𐑇'), - (0x10420, 'M', u'𐑈'), - (0x10421, 'M', u'𐑉'), - (0x10422, 'M', u'𐑊'), - (0x10423, 'M', u'𐑋'), - (0x10424, 'M', u'𐑌'), - (0x10425, 'M', u'𐑍'), - (0x10426, 'M', u'𐑎'), - (0x10427, 'M', u'𐑏'), + (0x10400, 'M', '𐐨'), + (0x10401, 'M', '𐐩'), + (0x10402, 'M', '𐐪'), + (0x10403, 'M', '𐐫'), + (0x10404, 'M', '𐐬'), + (0x10405, 'M', '𐐭'), + (0x10406, 'M', '𐐮'), + (0x10407, 'M', '𐐯'), + (0x10408, 'M', '𐐰'), + (0x10409, 'M', '𐐱'), + (0x1040A, 'M', '𐐲'), + (0x1040B, 'M', '𐐳'), + (0x1040C, 'M', '𐐴'), + (0x1040D, 'M', '𐐵'), + (0x1040E, 'M', '𐐶'), + (0x1040F, 'M', '𐐷'), + (0x10410, 'M', '𐐸'), + (0x10411, 'M', '𐐹'), + (0x10412, 'M', '𐐺'), + (0x10413, 'M', '𐐻'), + (0x10414, 'M', '𐐼'), + (0x10415, 'M', '𐐽'), + (0x10416, 'M', '𐐾'), + (0x10417, 'M', '𐐿'), + (0x10418, 'M', '𐑀'), + (0x10419, 'M', '𐑁'), + (0x1041A, 'M', '𐑂'), + (0x1041B, 'M', '𐑃'), + (0x1041C, 'M', '𐑄'), + (0x1041D, 'M', '𐑅'), + (0x1041E, 'M', '𐑆'), + (0x1041F, 'M', '𐑇'), + (0x10420, 'M', '𐑈'), + (0x10421, 'M', '𐑉'), + (0x10422, 'M', '𐑊'), + (0x10423, 'M', '𐑋'), + (0x10424, 'M', '𐑌'), + (0x10425, 'M', '𐑍'), + (0x10426, 'M', '𐑎'), + (0x10427, 'M', '𐑏'), (0x10428, 'V'), (0x1049E, 'X'), (0x104A0, 'V'), (0x104AA, 'X'), - (0x104B0, 'M', u'𐓘'), - (0x104B1, 'M', u'𐓙'), - (0x104B2, 'M', u'𐓚'), - (0x104B3, 'M', u'𐓛'), - (0x104B4, 'M', u'𐓜'), - (0x104B5, 'M', u'𐓝'), - (0x104B6, 'M', u'𐓞'), - (0x104B7, 'M', u'𐓟'), - (0x104B8, 'M', u'𐓠'), - (0x104B9, 'M', u'𐓡'), - (0x104BA, 'M', u'𐓢'), - (0x104BB, 'M', u'𐓣'), - (0x104BC, 'M', u'𐓤'), - (0x104BD, 'M', u'𐓥'), - (0x104BE, 'M', u'𐓦'), - (0x104BF, 'M', u'𐓧'), - (0x104C0, 'M', u'𐓨'), - (0x104C1, 'M', u'𐓩'), - (0x104C2, 'M', u'𐓪'), - (0x104C3, 'M', u'𐓫'), - (0x104C4, 'M', u'𐓬'), - (0x104C5, 'M', u'𐓭'), - (0x104C6, 'M', u'𐓮'), - (0x104C7, 'M', u'𐓯'), - (0x104C8, 'M', u'𐓰'), - (0x104C9, 'M', u'𐓱'), - (0x104CA, 'M', u'𐓲'), - (0x104CB, 'M', u'𐓳'), - (0x104CC, 'M', u'𐓴'), - (0x104CD, 'M', u'𐓵'), - (0x104CE, 'M', u'𐓶'), - (0x104CF, 'M', u'𐓷'), - (0x104D0, 'M', u'𐓸'), - (0x104D1, 'M', u'𐓹'), - (0x104D2, 'M', u'𐓺'), - (0x104D3, 'M', u'𐓻'), + (0x104B0, 'M', '𐓘'), + (0x104B1, 'M', '𐓙'), + (0x104B2, 'M', '𐓚'), + (0x104B3, 'M', '𐓛'), + (0x104B4, 'M', '𐓜'), + (0x104B5, 'M', '𐓝'), + (0x104B6, 'M', '𐓞'), + (0x104B7, 'M', '𐓟'), + (0x104B8, 'M', '𐓠'), + (0x104B9, 'M', '𐓡'), + (0x104BA, 'M', '𐓢'), + (0x104BB, 'M', '𐓣'), + (0x104BC, 'M', '𐓤'), + (0x104BD, 'M', '𐓥'), + (0x104BE, 'M', '𐓦'), + ] + +def _seg_54(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ + (0x104BF, 'M', '𐓧'), + (0x104C0, 'M', '𐓨'), + (0x104C1, 'M', '𐓩'), + (0x104C2, 'M', '𐓪'), + (0x104C3, 'M', '𐓫'), + (0x104C4, 'M', '𐓬'), + (0x104C5, 'M', '𐓭'), + (0x104C6, 'M', '𐓮'), + (0x104C7, 'M', '𐓯'), + (0x104C8, 'M', '𐓰'), + (0x104C9, 'M', '𐓱'), + (0x104CA, 'M', '𐓲'), + (0x104CB, 'M', '𐓳'), + (0x104CC, 'M', '𐓴'), + (0x104CD, 'M', '𐓵'), + (0x104CE, 'M', '𐓶'), + (0x104CF, 'M', '𐓷'), + (0x104D0, 'M', '𐓸'), + (0x104D1, 'M', '𐓹'), + (0x104D2, 'M', '𐓺'), + (0x104D3, 'M', '𐓻'), (0x104D4, 'X'), (0x104D8, 'V'), (0x104FC, 'X'), @@ -5619,10 +5722,6 @@ def _seg_53(): (0x1080A, 'V'), (0x10836, 'X'), (0x10837, 'V'), - ] - -def _seg_54(): - return [ (0x10839, 'X'), (0x1083C, 'V'), (0x1083D, 'X'), @@ -5680,63 +5779,64 @@ def _seg_54(): (0x10B9D, 'X'), (0x10BA9, 'V'), (0x10BB0, 'X'), - (0x10C00, 'V'), - (0x10C49, 'X'), - (0x10C80, 'M', u'𐳀'), - (0x10C81, 'M', u'𐳁'), - (0x10C82, 'M', u'𐳂'), - (0x10C83, 'M', u'𐳃'), - (0x10C84, 'M', u'𐳄'), - (0x10C85, 'M', u'𐳅'), - (0x10C86, 'M', u'𐳆'), - (0x10C87, 'M', u'𐳇'), - (0x10C88, 'M', u'𐳈'), - (0x10C89, 'M', u'𐳉'), - (0x10C8A, 'M', u'𐳊'), - (0x10C8B, 'M', u'𐳋'), - (0x10C8C, 'M', u'𐳌'), - (0x10C8D, 'M', u'𐳍'), - (0x10C8E, 'M', u'𐳎'), - (0x10C8F, 'M', u'𐳏'), - (0x10C90, 'M', u'𐳐'), - (0x10C91, 'M', u'𐳑'), - (0x10C92, 'M', u'𐳒'), - (0x10C93, 'M', u'𐳓'), - (0x10C94, 'M', u'𐳔'), - (0x10C95, 'M', u'𐳕'), - (0x10C96, 'M', u'𐳖'), - (0x10C97, 'M', u'𐳗'), - (0x10C98, 'M', u'𐳘'), - (0x10C99, 'M', u'𐳙'), - (0x10C9A, 'M', u'𐳚'), - (0x10C9B, 'M', u'𐳛'), - (0x10C9C, 'M', u'𐳜'), - (0x10C9D, 'M', u'𐳝'), - (0x10C9E, 'M', u'𐳞'), - (0x10C9F, 'M', u'𐳟'), - (0x10CA0, 'M', u'𐳠'), - (0x10CA1, 'M', u'𐳡'), - (0x10CA2, 'M', u'𐳢'), - (0x10CA3, 'M', u'𐳣'), - (0x10CA4, 'M', u'𐳤'), - (0x10CA5, 'M', u'𐳥'), - (0x10CA6, 'M', u'𐳦'), - (0x10CA7, 'M', u'𐳧'), - (0x10CA8, 'M', u'𐳨'), ] def _seg_55(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x10CA9, 'M', u'𐳩'), - (0x10CAA, 'M', u'𐳪'), - (0x10CAB, 'M', u'𐳫'), - (0x10CAC, 'M', u'𐳬'), - (0x10CAD, 'M', u'𐳭'), - (0x10CAE, 'M', u'𐳮'), - (0x10CAF, 'M', u'𐳯'), - (0x10CB0, 'M', u'𐳰'), - (0x10CB1, 'M', u'𐳱'), - (0x10CB2, 'M', u'𐳲'), + (0x10C00, 'V'), + (0x10C49, 'X'), + (0x10C80, 'M', '𐳀'), + (0x10C81, 'M', '𐳁'), + (0x10C82, 'M', '𐳂'), + (0x10C83, 'M', '𐳃'), + (0x10C84, 'M', '𐳄'), + (0x10C85, 'M', '𐳅'), + (0x10C86, 'M', '𐳆'), + (0x10C87, 'M', '𐳇'), + (0x10C88, 'M', '𐳈'), + (0x10C89, 'M', '𐳉'), + (0x10C8A, 'M', '𐳊'), + (0x10C8B, 'M', '𐳋'), + (0x10C8C, 'M', '𐳌'), + (0x10C8D, 'M', '𐳍'), + (0x10C8E, 'M', '𐳎'), + (0x10C8F, 'M', '𐳏'), + (0x10C90, 'M', '𐳐'), + (0x10C91, 'M', '𐳑'), + (0x10C92, 'M', '𐳒'), + (0x10C93, 'M', '𐳓'), + (0x10C94, 'M', '𐳔'), + (0x10C95, 'M', '𐳕'), + (0x10C96, 'M', '𐳖'), + (0x10C97, 'M', '𐳗'), + (0x10C98, 'M', '𐳘'), + (0x10C99, 'M', '𐳙'), + (0x10C9A, 'M', '𐳚'), + (0x10C9B, 'M', '𐳛'), + (0x10C9C, 'M', '𐳜'), + (0x10C9D, 'M', '𐳝'), + (0x10C9E, 'M', '𐳞'), + (0x10C9F, 'M', '𐳟'), + (0x10CA0, 'M', '𐳠'), + (0x10CA1, 'M', '𐳡'), + (0x10CA2, 'M', '𐳢'), + (0x10CA3, 'M', '𐳣'), + (0x10CA4, 'M', '𐳤'), + (0x10CA5, 'M', '𐳥'), + (0x10CA6, 'M', '𐳦'), + (0x10CA7, 'M', '𐳧'), + (0x10CA8, 'M', '𐳨'), + (0x10CA9, 'M', '𐳩'), + (0x10CAA, 'M', '𐳪'), + (0x10CAB, 'M', '𐳫'), + (0x10CAC, 'M', '𐳬'), + (0x10CAD, 'M', '𐳭'), + (0x10CAE, 'M', '𐳮'), + (0x10CAF, 'M', '𐳯'), + (0x10CB0, 'M', '𐳰'), + (0x10CB1, 'M', '𐳱'), + (0x10CB2, 'M', '𐳲'), (0x10CB3, 'X'), (0x10CC0, 'V'), (0x10CF3, 'X'), @@ -5746,10 +5846,20 @@ def _seg_55(): (0x10D3A, 'X'), (0x10E60, 'V'), (0x10E7F, 'X'), + (0x10E80, 'V'), + (0x10EAA, 'X'), + (0x10EAB, 'V'), + (0x10EAE, 'X'), + (0x10EB0, 'V'), + (0x10EB2, 'X'), (0x10F00, 'V'), (0x10F28, 'X'), (0x10F30, 'V'), (0x10F5A, 'X'), + (0x10FB0, 'V'), + (0x10FCC, 'X'), + (0x10FE0, 'V'), + (0x10FF7, 'X'), (0x11000, 'V'), (0x1104E, 'X'), (0x11052, 'V'), @@ -5765,17 +5875,20 @@ def _seg_55(): (0x11100, 'V'), (0x11135, 'X'), (0x11136, 'V'), - (0x11147, 'X'), + (0x11148, 'X'), (0x11150, 'V'), (0x11177, 'X'), (0x11180, 'V'), - (0x111CE, 'X'), - (0x111D0, 'V'), (0x111E0, 'X'), (0x111E1, 'V'), (0x111F5, 'X'), (0x11200, 'V'), (0x11212, 'X'), + ] + +def _seg_56(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ (0x11213, 'V'), (0x1123F, 'X'), (0x11280, 'V'), @@ -5823,15 +5936,9 @@ def _seg_55(): (0x11370, 'V'), (0x11375, 'X'), (0x11400, 'V'), - (0x1145A, 'X'), - (0x1145B, 'V'), (0x1145C, 'X'), (0x1145D, 'V'), - ] - -def _seg_56(): - return [ - (0x1145F, 'X'), + (0x11462, 'X'), (0x11480, 'V'), (0x114C8, 'X'), (0x114D0, 'V'), @@ -5847,7 +5954,7 @@ def _seg_56(): (0x11660, 'V'), (0x1166D, 'X'), (0x11680, 'V'), - (0x116B8, 'X'), + (0x116B9, 'X'), (0x116C0, 'V'), (0x116CA, 'X'), (0x11700, 'V'), @@ -5858,47 +5965,70 @@ def _seg_56(): (0x11740, 'X'), (0x11800, 'V'), (0x1183C, 'X'), - (0x118A0, 'M', u'𑣀'), - (0x118A1, 'M', u'𑣁'), - (0x118A2, 'M', u'𑣂'), - (0x118A3, 'M', u'𑣃'), - (0x118A4, 'M', u'𑣄'), - (0x118A5, 'M', u'𑣅'), - (0x118A6, 'M', u'𑣆'), - (0x118A7, 'M', u'𑣇'), - (0x118A8, 'M', u'𑣈'), - (0x118A9, 'M', u'𑣉'), - (0x118AA, 'M', u'𑣊'), - (0x118AB, 'M', u'𑣋'), - (0x118AC, 'M', u'𑣌'), - (0x118AD, 'M', u'𑣍'), - (0x118AE, 'M', u'𑣎'), - (0x118AF, 'M', u'𑣏'), - (0x118B0, 'M', u'𑣐'), - (0x118B1, 'M', u'𑣑'), - (0x118B2, 'M', u'𑣒'), - (0x118B3, 'M', u'𑣓'), - (0x118B4, 'M', u'𑣔'), - (0x118B5, 'M', u'𑣕'), - (0x118B6, 'M', u'𑣖'), - (0x118B7, 'M', u'𑣗'), - (0x118B8, 'M', u'𑣘'), - (0x118B9, 'M', u'𑣙'), - (0x118BA, 'M', u'𑣚'), - (0x118BB, 'M', u'𑣛'), - (0x118BC, 'M', u'𑣜'), - (0x118BD, 'M', u'𑣝'), - (0x118BE, 'M', u'𑣞'), - (0x118BF, 'M', u'𑣟'), + (0x118A0, 'M', '𑣀'), + (0x118A1, 'M', '𑣁'), + (0x118A2, 'M', '𑣂'), + (0x118A3, 'M', '𑣃'), + (0x118A4, 'M', '𑣄'), + (0x118A5, 'M', '𑣅'), + (0x118A6, 'M', '𑣆'), + (0x118A7, 'M', '𑣇'), + (0x118A8, 'M', '𑣈'), + (0x118A9, 'M', '𑣉'), + (0x118AA, 'M', '𑣊'), + (0x118AB, 'M', '𑣋'), + (0x118AC, 'M', '𑣌'), + (0x118AD, 'M', '𑣍'), + (0x118AE, 'M', '𑣎'), + (0x118AF, 'M', '𑣏'), + (0x118B0, 'M', '𑣐'), + (0x118B1, 'M', '𑣑'), + (0x118B2, 'M', '𑣒'), + (0x118B3, 'M', '𑣓'), + (0x118B4, 'M', '𑣔'), + (0x118B5, 'M', '𑣕'), + (0x118B6, 'M', '𑣖'), + (0x118B7, 'M', '𑣗'), + ] + +def _seg_57(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ + (0x118B8, 'M', '𑣘'), + (0x118B9, 'M', '𑣙'), + (0x118BA, 'M', '𑣚'), + (0x118BB, 'M', '𑣛'), + (0x118BC, 'M', '𑣜'), + (0x118BD, 'M', '𑣝'), + (0x118BE, 'M', '𑣞'), + (0x118BF, 'M', '𑣟'), (0x118C0, 'V'), (0x118F3, 'X'), (0x118FF, 'V'), - (0x11900, 'X'), + (0x11907, 'X'), + (0x11909, 'V'), + (0x1190A, 'X'), + (0x1190C, 'V'), + (0x11914, 'X'), + (0x11915, 'V'), + (0x11917, 'X'), + (0x11918, 'V'), + (0x11936, 'X'), + (0x11937, 'V'), + (0x11939, 'X'), + (0x1193B, 'V'), + (0x11947, 'X'), + (0x11950, 'V'), + (0x1195A, 'X'), + (0x119A0, 'V'), + (0x119A8, 'X'), + (0x119AA, 'V'), + (0x119D8, 'X'), + (0x119DA, 'V'), + (0x119E5, 'X'), (0x11A00, 'V'), (0x11A48, 'X'), (0x11A50, 'V'), - (0x11A84, 'X'), - (0x11A86, 'V'), (0x11AA3, 'X'), (0x11AC0, 'V'), (0x11AF9, 'X'), @@ -5931,10 +6061,6 @@ def _seg_56(): (0x11D50, 'V'), (0x11D5A, 'X'), (0x11D60, 'V'), - ] - -def _seg_57(): - return [ (0x11D66, 'X'), (0x11D67, 'V'), (0x11D69, 'X'), @@ -5948,7 +6074,11 @@ def _seg_57(): (0x11DAA, 'X'), (0x11EE0, 'V'), (0x11EF9, 'X'), - (0x12000, 'V'), + (0x11FB0, 'V'), + (0x11FB1, 'X'), + (0x11FC0, 'V'), + (0x11FF2, 'X'), + (0x11FFF, 'V'), (0x1239A, 'X'), (0x12400, 'V'), (0x1246F, 'X'), @@ -5964,6 +6094,11 @@ def _seg_57(): (0x16A39, 'X'), (0x16A40, 'V'), (0x16A5F, 'X'), + ] + +def _seg_58(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ (0x16A60, 'V'), (0x16A6A, 'X'), (0x16A6E, 'V'), @@ -5982,22 +6117,62 @@ def _seg_57(): (0x16B78, 'X'), (0x16B7D, 'V'), (0x16B90, 'X'), + (0x16E40, 'M', '𖹠'), + (0x16E41, 'M', '𖹡'), + (0x16E42, 'M', '𖹢'), + (0x16E43, 'M', '𖹣'), + (0x16E44, 'M', '𖹤'), + (0x16E45, 'M', '𖹥'), + (0x16E46, 'M', '𖹦'), + (0x16E47, 'M', '𖹧'), + (0x16E48, 'M', '𖹨'), + (0x16E49, 'M', '𖹩'), + (0x16E4A, 'M', '𖹪'), + (0x16E4B, 'M', '𖹫'), + (0x16E4C, 'M', '𖹬'), + (0x16E4D, 'M', '𖹭'), + (0x16E4E, 'M', '𖹮'), + (0x16E4F, 'M', '𖹯'), + (0x16E50, 'M', '𖹰'), + (0x16E51, 'M', '𖹱'), + (0x16E52, 'M', '𖹲'), + (0x16E53, 'M', '𖹳'), + (0x16E54, 'M', '𖹴'), + (0x16E55, 'M', '𖹵'), + (0x16E56, 'M', '𖹶'), + (0x16E57, 'M', '𖹷'), + (0x16E58, 'M', '𖹸'), + (0x16E59, 'M', '𖹹'), + (0x16E5A, 'M', '𖹺'), + (0x16E5B, 'M', '𖹻'), + (0x16E5C, 'M', '𖹼'), + (0x16E5D, 'M', '𖹽'), + (0x16E5E, 'M', '𖹾'), + (0x16E5F, 'M', '𖹿'), (0x16E60, 'V'), (0x16E9B, 'X'), (0x16F00, 'V'), - (0x16F45, 'X'), - (0x16F50, 'V'), - (0x16F7F, 'X'), + (0x16F4B, 'X'), + (0x16F4F, 'V'), + (0x16F88, 'X'), (0x16F8F, 'V'), (0x16FA0, 'X'), (0x16FE0, 'V'), - (0x16FE2, 'X'), + (0x16FE5, 'X'), + (0x16FF0, 'V'), + (0x16FF2, 'X'), (0x17000, 'V'), - (0x187F2, 'X'), + (0x187F8, 'X'), (0x18800, 'V'), - (0x18AF3, 'X'), + (0x18CD6, 'X'), + (0x18D00, 'V'), + (0x18D09, 'X'), (0x1B000, 'V'), (0x1B11F, 'X'), + (0x1B150, 'V'), + (0x1B153, 'X'), + (0x1B164, 'V'), + (0x1B168, 'X'), (0x1B170, 'V'), (0x1B2FC, 'X'), (0x1BC00, 'V'), @@ -6016,29 +6191,30 @@ def _seg_57(): (0x1D100, 'V'), (0x1D127, 'X'), (0x1D129, 'V'), - (0x1D15E, 'M', u'𝅗𝅥'), - (0x1D15F, 'M', u'𝅘𝅥'), - (0x1D160, 'M', u'𝅘𝅥𝅮'), - (0x1D161, 'M', u'𝅘𝅥𝅯'), - (0x1D162, 'M', u'𝅘𝅥𝅰'), - (0x1D163, 'M', u'𝅘𝅥𝅱'), - (0x1D164, 'M', u'𝅘𝅥𝅲'), + (0x1D15E, 'M', '𝅗𝅥'), + (0x1D15F, 'M', '𝅘𝅥'), + (0x1D160, 'M', '𝅘𝅥𝅮'), + (0x1D161, 'M', '𝅘𝅥𝅯'), + (0x1D162, 'M', '𝅘𝅥𝅰'), + (0x1D163, 'M', '𝅘𝅥𝅱'), + (0x1D164, 'M', '𝅘𝅥𝅲'), (0x1D165, 'V'), + ] + +def _seg_59(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ (0x1D173, 'X'), (0x1D17B, 'V'), - (0x1D1BB, 'M', u'𝆹𝅥'), - (0x1D1BC, 'M', u'𝆺𝅥'), - (0x1D1BD, 'M', u'𝆹𝅥𝅮'), - (0x1D1BE, 'M', u'𝆺𝅥𝅮'), - (0x1D1BF, 'M', u'𝆹𝅥𝅯'), - (0x1D1C0, 'M', u'𝆺𝅥𝅯'), + (0x1D1BB, 'M', '𝆹𝅥'), + (0x1D1BC, 'M', '𝆺𝅥'), + (0x1D1BD, 'M', '𝆹𝅥𝅮'), + (0x1D1BE, 'M', '𝆺𝅥𝅮'), + (0x1D1BF, 'M', '𝆹𝅥𝅯'), + (0x1D1C0, 'M', '𝆺𝅥𝅯'), (0x1D1C1, 'V'), (0x1D1E9, 'X'), (0x1D200, 'V'), - ] - -def _seg_58(): - return [ (0x1D246, 'X'), (0x1D2E0, 'V'), (0x1D2F4, 'X'), @@ -6046,1056 +6222,1066 @@ def _seg_58(): (0x1D357, 'X'), (0x1D360, 'V'), (0x1D379, 'X'), - (0x1D400, 'M', u'a'), - (0x1D401, 'M', u'b'), - (0x1D402, 'M', u'c'), - (0x1D403, 'M', u'd'), - (0x1D404, 'M', u'e'), - (0x1D405, 'M', u'f'), - (0x1D406, 'M', u'g'), - (0x1D407, 'M', u'h'), - (0x1D408, 'M', u'i'), - (0x1D409, 'M', u'j'), - (0x1D40A, 'M', u'k'), - (0x1D40B, 'M', u'l'), - (0x1D40C, 'M', u'm'), - (0x1D40D, 'M', u'n'), - (0x1D40E, 'M', u'o'), - (0x1D40F, 'M', u'p'), - (0x1D410, 'M', u'q'), - (0x1D411, 'M', u'r'), - (0x1D412, 'M', u's'), - (0x1D413, 'M', u't'), - (0x1D414, 'M', u'u'), - (0x1D415, 'M', u'v'), - (0x1D416, 'M', u'w'), - (0x1D417, 'M', u'x'), - (0x1D418, 'M', u'y'), - (0x1D419, 'M', u'z'), - (0x1D41A, 'M', u'a'), - (0x1D41B, 'M', u'b'), - (0x1D41C, 'M', u'c'), - (0x1D41D, 'M', u'd'), - (0x1D41E, 'M', u'e'), - (0x1D41F, 'M', u'f'), - (0x1D420, 'M', u'g'), - (0x1D421, 'M', u'h'), - (0x1D422, 'M', u'i'), - (0x1D423, 'M', u'j'), - (0x1D424, 'M', u'k'), - (0x1D425, 'M', u'l'), - (0x1D426, 'M', u'm'), - (0x1D427, 'M', u'n'), - (0x1D428, 'M', u'o'), - (0x1D429, 'M', u'p'), - (0x1D42A, 'M', u'q'), - (0x1D42B, 'M', u'r'), - (0x1D42C, 'M', u's'), - (0x1D42D, 'M', u't'), - (0x1D42E, 'M', u'u'), - (0x1D42F, 'M', u'v'), - (0x1D430, 'M', u'w'), - (0x1D431, 'M', u'x'), - (0x1D432, 'M', u'y'), - (0x1D433, 'M', u'z'), - (0x1D434, 'M', u'a'), - (0x1D435, 'M', u'b'), - (0x1D436, 'M', u'c'), - (0x1D437, 'M', u'd'), - (0x1D438, 'M', u'e'), - (0x1D439, 'M', u'f'), - (0x1D43A, 'M', u'g'), - (0x1D43B, 'M', u'h'), - (0x1D43C, 'M', u'i'), - (0x1D43D, 'M', u'j'), - (0x1D43E, 'M', u'k'), - (0x1D43F, 'M', u'l'), - (0x1D440, 'M', u'm'), - (0x1D441, 'M', u'n'), - (0x1D442, 'M', u'o'), - (0x1D443, 'M', u'p'), - (0x1D444, 'M', u'q'), - (0x1D445, 'M', u'r'), - (0x1D446, 'M', u's'), - (0x1D447, 'M', u't'), - (0x1D448, 'M', u'u'), - (0x1D449, 'M', u'v'), - (0x1D44A, 'M', u'w'), - (0x1D44B, 'M', u'x'), - (0x1D44C, 'M', u'y'), - (0x1D44D, 'M', u'z'), - (0x1D44E, 'M', u'a'), - (0x1D44F, 'M', u'b'), - (0x1D450, 'M', u'c'), - (0x1D451, 'M', u'd'), - (0x1D452, 'M', u'e'), - (0x1D453, 'M', u'f'), - (0x1D454, 'M', u'g'), - (0x1D455, 'X'), - (0x1D456, 'M', u'i'), - (0x1D457, 'M', u'j'), - (0x1D458, 'M', u'k'), - (0x1D459, 'M', u'l'), - (0x1D45A, 'M', u'm'), - (0x1D45B, 'M', u'n'), - (0x1D45C, 'M', u'o'), - ] - -def _seg_59(): - return [ - (0x1D45D, 'M', u'p'), - (0x1D45E, 'M', u'q'), - (0x1D45F, 'M', u'r'), - (0x1D460, 'M', u's'), - (0x1D461, 'M', u't'), - (0x1D462, 'M', u'u'), - (0x1D463, 'M', u'v'), - (0x1D464, 'M', u'w'), - (0x1D465, 'M', u'x'), - (0x1D466, 'M', u'y'), - (0x1D467, 'M', u'z'), - (0x1D468, 'M', u'a'), - (0x1D469, 'M', u'b'), - (0x1D46A, 'M', u'c'), - (0x1D46B, 'M', u'd'), - (0x1D46C, 'M', u'e'), - (0x1D46D, 'M', u'f'), - (0x1D46E, 'M', u'g'), - (0x1D46F, 'M', u'h'), - (0x1D470, 'M', u'i'), - (0x1D471, 'M', u'j'), - (0x1D472, 'M', u'k'), - (0x1D473, 'M', u'l'), - (0x1D474, 'M', u'm'), - (0x1D475, 'M', u'n'), - (0x1D476, 'M', u'o'), - (0x1D477, 'M', u'p'), - (0x1D478, 'M', u'q'), - (0x1D479, 'M', u'r'), - (0x1D47A, 'M', u's'), - (0x1D47B, 'M', u't'), - (0x1D47C, 'M', u'u'), - (0x1D47D, 'M', u'v'), - (0x1D47E, 'M', u'w'), - (0x1D47F, 'M', u'x'), - (0x1D480, 'M', u'y'), - (0x1D481, 'M', u'z'), - (0x1D482, 'M', u'a'), - (0x1D483, 'M', u'b'), - (0x1D484, 'M', u'c'), - (0x1D485, 'M', u'd'), - (0x1D486, 'M', u'e'), - (0x1D487, 'M', u'f'), - (0x1D488, 'M', u'g'), - (0x1D489, 'M', u'h'), - (0x1D48A, 'M', u'i'), - (0x1D48B, 'M', u'j'), - (0x1D48C, 'M', u'k'), - (0x1D48D, 'M', u'l'), - (0x1D48E, 'M', u'm'), - (0x1D48F, 'M', u'n'), - (0x1D490, 'M', u'o'), - (0x1D491, 'M', u'p'), - (0x1D492, 'M', u'q'), - (0x1D493, 'M', u'r'), - (0x1D494, 'M', u's'), - (0x1D495, 'M', u't'), - (0x1D496, 'M', u'u'), - (0x1D497, 'M', u'v'), - (0x1D498, 'M', u'w'), - (0x1D499, 'M', u'x'), - (0x1D49A, 'M', u'y'), - (0x1D49B, 'M', u'z'), - (0x1D49C, 'M', u'a'), - (0x1D49D, 'X'), - (0x1D49E, 'M', u'c'), - (0x1D49F, 'M', u'd'), - (0x1D4A0, 'X'), - (0x1D4A2, 'M', u'g'), - (0x1D4A3, 'X'), - (0x1D4A5, 'M', u'j'), - (0x1D4A6, 'M', u'k'), - (0x1D4A7, 'X'), - (0x1D4A9, 'M', u'n'), - (0x1D4AA, 'M', u'o'), - (0x1D4AB, 'M', u'p'), - (0x1D4AC, 'M', u'q'), - (0x1D4AD, 'X'), - (0x1D4AE, 'M', u's'), - (0x1D4AF, 'M', u't'), - (0x1D4B0, 'M', u'u'), - (0x1D4B1, 'M', u'v'), - (0x1D4B2, 'M', u'w'), - (0x1D4B3, 'M', u'x'), - (0x1D4B4, 'M', u'y'), - (0x1D4B5, 'M', u'z'), - (0x1D4B6, 'M', u'a'), - (0x1D4B7, 'M', u'b'), - (0x1D4B8, 'M', u'c'), - (0x1D4B9, 'M', u'd'), - (0x1D4BA, 'X'), - (0x1D4BB, 'M', u'f'), - (0x1D4BC, 'X'), - (0x1D4BD, 'M', u'h'), - (0x1D4BE, 'M', u'i'), - (0x1D4BF, 'M', u'j'), - (0x1D4C0, 'M', u'k'), - (0x1D4C1, 'M', u'l'), - (0x1D4C2, 'M', u'm'), - (0x1D4C3, 'M', u'n'), + (0x1D400, 'M', 'a'), + (0x1D401, 'M', 'b'), + (0x1D402, 'M', 'c'), + (0x1D403, 'M', 'd'), + (0x1D404, 'M', 'e'), + (0x1D405, 'M', 'f'), + (0x1D406, 'M', 'g'), + (0x1D407, 'M', 'h'), + (0x1D408, 'M', 'i'), + (0x1D409, 'M', 'j'), + (0x1D40A, 'M', 'k'), + (0x1D40B, 'M', 'l'), + (0x1D40C, 'M', 'm'), + (0x1D40D, 'M', 'n'), + (0x1D40E, 'M', 'o'), + (0x1D40F, 'M', 'p'), + (0x1D410, 'M', 'q'), + (0x1D411, 'M', 'r'), + (0x1D412, 'M', 's'), + (0x1D413, 'M', 't'), + (0x1D414, 'M', 'u'), + (0x1D415, 'M', 'v'), + (0x1D416, 'M', 'w'), + (0x1D417, 'M', 'x'), + (0x1D418, 'M', 'y'), + (0x1D419, 'M', 'z'), + (0x1D41A, 'M', 'a'), + (0x1D41B, 'M', 'b'), + (0x1D41C, 'M', 'c'), + (0x1D41D, 'M', 'd'), + (0x1D41E, 'M', 'e'), + (0x1D41F, 'M', 'f'), + (0x1D420, 'M', 'g'), + (0x1D421, 'M', 'h'), + (0x1D422, 'M', 'i'), + (0x1D423, 'M', 'j'), + (0x1D424, 'M', 'k'), + (0x1D425, 'M', 'l'), + (0x1D426, 'M', 'm'), + (0x1D427, 'M', 'n'), + (0x1D428, 'M', 'o'), + (0x1D429, 'M', 'p'), + (0x1D42A, 'M', 'q'), + (0x1D42B, 'M', 'r'), + (0x1D42C, 'M', 's'), + (0x1D42D, 'M', 't'), + (0x1D42E, 'M', 'u'), + (0x1D42F, 'M', 'v'), + (0x1D430, 'M', 'w'), + (0x1D431, 'M', 'x'), + (0x1D432, 'M', 'y'), + (0x1D433, 'M', 'z'), + (0x1D434, 'M', 'a'), + (0x1D435, 'M', 'b'), + (0x1D436, 'M', 'c'), + (0x1D437, 'M', 'd'), + (0x1D438, 'M', 'e'), + (0x1D439, 'M', 'f'), + (0x1D43A, 'M', 'g'), + (0x1D43B, 'M', 'h'), + (0x1D43C, 'M', 'i'), + (0x1D43D, 'M', 'j'), + (0x1D43E, 'M', 'k'), + (0x1D43F, 'M', 'l'), + (0x1D440, 'M', 'm'), + (0x1D441, 'M', 'n'), + (0x1D442, 'M', 'o'), + (0x1D443, 'M', 'p'), + (0x1D444, 'M', 'q'), + (0x1D445, 'M', 'r'), + (0x1D446, 'M', 's'), + (0x1D447, 'M', 't'), + (0x1D448, 'M', 'u'), + (0x1D449, 'M', 'v'), + (0x1D44A, 'M', 'w'), + (0x1D44B, 'M', 'x'), + (0x1D44C, 'M', 'y'), + (0x1D44D, 'M', 'z'), + (0x1D44E, 'M', 'a'), + (0x1D44F, 'M', 'b'), + (0x1D450, 'M', 'c'), + (0x1D451, 'M', 'd'), ] def _seg_60(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x1D4C4, 'X'), - (0x1D4C5, 'M', u'p'), - (0x1D4C6, 'M', u'q'), - (0x1D4C7, 'M', u'r'), - (0x1D4C8, 'M', u's'), - (0x1D4C9, 'M', u't'), - (0x1D4CA, 'M', u'u'), - (0x1D4CB, 'M', u'v'), - (0x1D4CC, 'M', u'w'), - (0x1D4CD, 'M', u'x'), - (0x1D4CE, 'M', u'y'), - (0x1D4CF, 'M', u'z'), - (0x1D4D0, 'M', u'a'), - (0x1D4D1, 'M', u'b'), - (0x1D4D2, 'M', u'c'), - (0x1D4D3, 'M', u'd'), - (0x1D4D4, 'M', u'e'), - (0x1D4D5, 'M', u'f'), - (0x1D4D6, 'M', u'g'), - (0x1D4D7, 'M', u'h'), - (0x1D4D8, 'M', u'i'), - (0x1D4D9, 'M', u'j'), - (0x1D4DA, 'M', u'k'), - (0x1D4DB, 'M', u'l'), - (0x1D4DC, 'M', u'm'), - (0x1D4DD, 'M', u'n'), - (0x1D4DE, 'M', u'o'), - (0x1D4DF, 'M', u'p'), - (0x1D4E0, 'M', u'q'), - (0x1D4E1, 'M', u'r'), - (0x1D4E2, 'M', u's'), - (0x1D4E3, 'M', u't'), - (0x1D4E4, 'M', u'u'), - (0x1D4E5, 'M', u'v'), - (0x1D4E6, 'M', u'w'), - (0x1D4E7, 'M', u'x'), - (0x1D4E8, 'M', u'y'), - (0x1D4E9, 'M', u'z'), - (0x1D4EA, 'M', u'a'), - (0x1D4EB, 'M', u'b'), - (0x1D4EC, 'M', u'c'), - (0x1D4ED, 'M', u'd'), - (0x1D4EE, 'M', u'e'), - (0x1D4EF, 'M', u'f'), - (0x1D4F0, 'M', u'g'), - (0x1D4F1, 'M', u'h'), - (0x1D4F2, 'M', u'i'), - (0x1D4F3, 'M', u'j'), - (0x1D4F4, 'M', u'k'), - (0x1D4F5, 'M', u'l'), - (0x1D4F6, 'M', u'm'), - (0x1D4F7, 'M', u'n'), - (0x1D4F8, 'M', u'o'), - (0x1D4F9, 'M', u'p'), - (0x1D4FA, 'M', u'q'), - (0x1D4FB, 'M', u'r'), - (0x1D4FC, 'M', u's'), - (0x1D4FD, 'M', u't'), - (0x1D4FE, 'M', u'u'), - (0x1D4FF, 'M', u'v'), - (0x1D500, 'M', u'w'), - (0x1D501, 'M', u'x'), - (0x1D502, 'M', u'y'), - (0x1D503, 'M', u'z'), - (0x1D504, 'M', u'a'), - (0x1D505, 'M', u'b'), - (0x1D506, 'X'), - (0x1D507, 'M', u'd'), - (0x1D508, 'M', u'e'), - (0x1D509, 'M', u'f'), - (0x1D50A, 'M', u'g'), - (0x1D50B, 'X'), - (0x1D50D, 'M', u'j'), - (0x1D50E, 'M', u'k'), - (0x1D50F, 'M', u'l'), - (0x1D510, 'M', u'm'), - (0x1D511, 'M', u'n'), - (0x1D512, 'M', u'o'), - (0x1D513, 'M', u'p'), - (0x1D514, 'M', u'q'), - (0x1D515, 'X'), - (0x1D516, 'M', u's'), - (0x1D517, 'M', u't'), - (0x1D518, 'M', u'u'), - (0x1D519, 'M', u'v'), - (0x1D51A, 'M', u'w'), - (0x1D51B, 'M', u'x'), - (0x1D51C, 'M', u'y'), - (0x1D51D, 'X'), - (0x1D51E, 'M', u'a'), - (0x1D51F, 'M', u'b'), - (0x1D520, 'M', u'c'), - (0x1D521, 'M', u'd'), - (0x1D522, 'M', u'e'), - (0x1D523, 'M', u'f'), - (0x1D524, 'M', u'g'), - (0x1D525, 'M', u'h'), - (0x1D526, 'M', u'i'), - (0x1D527, 'M', u'j'), - (0x1D528, 'M', u'k'), + (0x1D452, 'M', 'e'), + (0x1D453, 'M', 'f'), + (0x1D454, 'M', 'g'), + (0x1D455, 'X'), + (0x1D456, 'M', 'i'), + (0x1D457, 'M', 'j'), + (0x1D458, 'M', 'k'), + (0x1D459, 'M', 'l'), + (0x1D45A, 'M', 'm'), + (0x1D45B, 'M', 'n'), + (0x1D45C, 'M', 'o'), + (0x1D45D, 'M', 'p'), + (0x1D45E, 'M', 'q'), + (0x1D45F, 'M', 'r'), + (0x1D460, 'M', 's'), + (0x1D461, 'M', 't'), + (0x1D462, 'M', 'u'), + (0x1D463, 'M', 'v'), + (0x1D464, 'M', 'w'), + (0x1D465, 'M', 'x'), + (0x1D466, 'M', 'y'), + (0x1D467, 'M', 'z'), + (0x1D468, 'M', 'a'), + (0x1D469, 'M', 'b'), + (0x1D46A, 'M', 'c'), + (0x1D46B, 'M', 'd'), + (0x1D46C, 'M', 'e'), + (0x1D46D, 'M', 'f'), + (0x1D46E, 'M', 'g'), + (0x1D46F, 'M', 'h'), + (0x1D470, 'M', 'i'), + (0x1D471, 'M', 'j'), + (0x1D472, 'M', 'k'), + (0x1D473, 'M', 'l'), + (0x1D474, 'M', 'm'), + (0x1D475, 'M', 'n'), + (0x1D476, 'M', 'o'), + (0x1D477, 'M', 'p'), + (0x1D478, 'M', 'q'), + (0x1D479, 'M', 'r'), + (0x1D47A, 'M', 's'), + (0x1D47B, 'M', 't'), + (0x1D47C, 'M', 'u'), + (0x1D47D, 'M', 'v'), + (0x1D47E, 'M', 'w'), + (0x1D47F, 'M', 'x'), + (0x1D480, 'M', 'y'), + (0x1D481, 'M', 'z'), + (0x1D482, 'M', 'a'), + (0x1D483, 'M', 'b'), + (0x1D484, 'M', 'c'), + (0x1D485, 'M', 'd'), + (0x1D486, 'M', 'e'), + (0x1D487, 'M', 'f'), + (0x1D488, 'M', 'g'), + (0x1D489, 'M', 'h'), + (0x1D48A, 'M', 'i'), + (0x1D48B, 'M', 'j'), + (0x1D48C, 'M', 'k'), + (0x1D48D, 'M', 'l'), + (0x1D48E, 'M', 'm'), + (0x1D48F, 'M', 'n'), + (0x1D490, 'M', 'o'), + (0x1D491, 'M', 'p'), + (0x1D492, 'M', 'q'), + (0x1D493, 'M', 'r'), + (0x1D494, 'M', 's'), + (0x1D495, 'M', 't'), + (0x1D496, 'M', 'u'), + (0x1D497, 'M', 'v'), + (0x1D498, 'M', 'w'), + (0x1D499, 'M', 'x'), + (0x1D49A, 'M', 'y'), + (0x1D49B, 'M', 'z'), + (0x1D49C, 'M', 'a'), + (0x1D49D, 'X'), + (0x1D49E, 'M', 'c'), + (0x1D49F, 'M', 'd'), + (0x1D4A0, 'X'), + (0x1D4A2, 'M', 'g'), + (0x1D4A3, 'X'), + (0x1D4A5, 'M', 'j'), + (0x1D4A6, 'M', 'k'), + (0x1D4A7, 'X'), + (0x1D4A9, 'M', 'n'), + (0x1D4AA, 'M', 'o'), + (0x1D4AB, 'M', 'p'), + (0x1D4AC, 'M', 'q'), + (0x1D4AD, 'X'), + (0x1D4AE, 'M', 's'), + (0x1D4AF, 'M', 't'), + (0x1D4B0, 'M', 'u'), + (0x1D4B1, 'M', 'v'), + (0x1D4B2, 'M', 'w'), + (0x1D4B3, 'M', 'x'), + (0x1D4B4, 'M', 'y'), + (0x1D4B5, 'M', 'z'), + (0x1D4B6, 'M', 'a'), + (0x1D4B7, 'M', 'b'), + (0x1D4B8, 'M', 'c'), ] def _seg_61(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x1D529, 'M', u'l'), - (0x1D52A, 'M', u'm'), - (0x1D52B, 'M', u'n'), - (0x1D52C, 'M', u'o'), - (0x1D52D, 'M', u'p'), - (0x1D52E, 'M', u'q'), - (0x1D52F, 'M', u'r'), - (0x1D530, 'M', u's'), - (0x1D531, 'M', u't'), - (0x1D532, 'M', u'u'), - (0x1D533, 'M', u'v'), - (0x1D534, 'M', u'w'), - (0x1D535, 'M', u'x'), - (0x1D536, 'M', u'y'), - (0x1D537, 'M', u'z'), - (0x1D538, 'M', u'a'), - (0x1D539, 'M', u'b'), - (0x1D53A, 'X'), - (0x1D53B, 'M', u'd'), - (0x1D53C, 'M', u'e'), - (0x1D53D, 'M', u'f'), - (0x1D53E, 'M', u'g'), - (0x1D53F, 'X'), - (0x1D540, 'M', u'i'), - (0x1D541, 'M', u'j'), - (0x1D542, 'M', u'k'), - (0x1D543, 'M', u'l'), - (0x1D544, 'M', u'm'), - (0x1D545, 'X'), - (0x1D546, 'M', u'o'), - (0x1D547, 'X'), - (0x1D54A, 'M', u's'), - (0x1D54B, 'M', u't'), - (0x1D54C, 'M', u'u'), - (0x1D54D, 'M', u'v'), - (0x1D54E, 'M', u'w'), - (0x1D54F, 'M', u'x'), - (0x1D550, 'M', u'y'), - (0x1D551, 'X'), - (0x1D552, 'M', u'a'), - (0x1D553, 'M', u'b'), - (0x1D554, 'M', u'c'), - (0x1D555, 'M', u'd'), - (0x1D556, 'M', u'e'), - (0x1D557, 'M', u'f'), - (0x1D558, 'M', u'g'), - (0x1D559, 'M', u'h'), - (0x1D55A, 'M', u'i'), - (0x1D55B, 'M', u'j'), - (0x1D55C, 'M', u'k'), - (0x1D55D, 'M', u'l'), - (0x1D55E, 'M', u'm'), - (0x1D55F, 'M', u'n'), - (0x1D560, 'M', u'o'), - (0x1D561, 'M', u'p'), - (0x1D562, 'M', u'q'), - (0x1D563, 'M', u'r'), - (0x1D564, 'M', u's'), - (0x1D565, 'M', u't'), - (0x1D566, 'M', u'u'), - (0x1D567, 'M', u'v'), - (0x1D568, 'M', u'w'), - (0x1D569, 'M', u'x'), - (0x1D56A, 'M', u'y'), - (0x1D56B, 'M', u'z'), - (0x1D56C, 'M', u'a'), - (0x1D56D, 'M', u'b'), - (0x1D56E, 'M', u'c'), - (0x1D56F, 'M', u'd'), - (0x1D570, 'M', u'e'), - (0x1D571, 'M', u'f'), - (0x1D572, 'M', u'g'), - (0x1D573, 'M', u'h'), - (0x1D574, 'M', u'i'), - (0x1D575, 'M', u'j'), - (0x1D576, 'M', u'k'), - (0x1D577, 'M', u'l'), - (0x1D578, 'M', u'm'), - (0x1D579, 'M', u'n'), - (0x1D57A, 'M', u'o'), - (0x1D57B, 'M', u'p'), - (0x1D57C, 'M', u'q'), - (0x1D57D, 'M', u'r'), - (0x1D57E, 'M', u's'), - (0x1D57F, 'M', u't'), - (0x1D580, 'M', u'u'), - (0x1D581, 'M', u'v'), - (0x1D582, 'M', u'w'), - (0x1D583, 'M', u'x'), - (0x1D584, 'M', u'y'), - (0x1D585, 'M', u'z'), - (0x1D586, 'M', u'a'), - (0x1D587, 'M', u'b'), - (0x1D588, 'M', u'c'), - (0x1D589, 'M', u'd'), - (0x1D58A, 'M', u'e'), - (0x1D58B, 'M', u'f'), - (0x1D58C, 'M', u'g'), - (0x1D58D, 'M', u'h'), - (0x1D58E, 'M', u'i'), + (0x1D4B9, 'M', 'd'), + (0x1D4BA, 'X'), + (0x1D4BB, 'M', 'f'), + (0x1D4BC, 'X'), + (0x1D4BD, 'M', 'h'), + (0x1D4BE, 'M', 'i'), + (0x1D4BF, 'M', 'j'), + (0x1D4C0, 'M', 'k'), + (0x1D4C1, 'M', 'l'), + (0x1D4C2, 'M', 'm'), + (0x1D4C3, 'M', 'n'), + (0x1D4C4, 'X'), + (0x1D4C5, 'M', 'p'), + (0x1D4C6, 'M', 'q'), + (0x1D4C7, 'M', 'r'), + (0x1D4C8, 'M', 's'), + (0x1D4C9, 'M', 't'), + (0x1D4CA, 'M', 'u'), + (0x1D4CB, 'M', 'v'), + (0x1D4CC, 'M', 'w'), + (0x1D4CD, 'M', 'x'), + (0x1D4CE, 'M', 'y'), + (0x1D4CF, 'M', 'z'), + (0x1D4D0, 'M', 'a'), + (0x1D4D1, 'M', 'b'), + (0x1D4D2, 'M', 'c'), + (0x1D4D3, 'M', 'd'), + (0x1D4D4, 'M', 'e'), + (0x1D4D5, 'M', 'f'), + (0x1D4D6, 'M', 'g'), + (0x1D4D7, 'M', 'h'), + (0x1D4D8, 'M', 'i'), + (0x1D4D9, 'M', 'j'), + (0x1D4DA, 'M', 'k'), + (0x1D4DB, 'M', 'l'), + (0x1D4DC, 'M', 'm'), + (0x1D4DD, 'M', 'n'), + (0x1D4DE, 'M', 'o'), + (0x1D4DF, 'M', 'p'), + (0x1D4E0, 'M', 'q'), + (0x1D4E1, 'M', 'r'), + (0x1D4E2, 'M', 's'), + (0x1D4E3, 'M', 't'), + (0x1D4E4, 'M', 'u'), + (0x1D4E5, 'M', 'v'), + (0x1D4E6, 'M', 'w'), + (0x1D4E7, 'M', 'x'), + (0x1D4E8, 'M', 'y'), + (0x1D4E9, 'M', 'z'), + (0x1D4EA, 'M', 'a'), + (0x1D4EB, 'M', 'b'), + (0x1D4EC, 'M', 'c'), + (0x1D4ED, 'M', 'd'), + (0x1D4EE, 'M', 'e'), + (0x1D4EF, 'M', 'f'), + (0x1D4F0, 'M', 'g'), + (0x1D4F1, 'M', 'h'), + (0x1D4F2, 'M', 'i'), + (0x1D4F3, 'M', 'j'), + (0x1D4F4, 'M', 'k'), + (0x1D4F5, 'M', 'l'), + (0x1D4F6, 'M', 'm'), + (0x1D4F7, 'M', 'n'), + (0x1D4F8, 'M', 'o'), + (0x1D4F9, 'M', 'p'), + (0x1D4FA, 'M', 'q'), + (0x1D4FB, 'M', 'r'), + (0x1D4FC, 'M', 's'), + (0x1D4FD, 'M', 't'), + (0x1D4FE, 'M', 'u'), + (0x1D4FF, 'M', 'v'), + (0x1D500, 'M', 'w'), + (0x1D501, 'M', 'x'), + (0x1D502, 'M', 'y'), + (0x1D503, 'M', 'z'), + (0x1D504, 'M', 'a'), + (0x1D505, 'M', 'b'), + (0x1D506, 'X'), + (0x1D507, 'M', 'd'), + (0x1D508, 'M', 'e'), + (0x1D509, 'M', 'f'), + (0x1D50A, 'M', 'g'), + (0x1D50B, 'X'), + (0x1D50D, 'M', 'j'), + (0x1D50E, 'M', 'k'), + (0x1D50F, 'M', 'l'), + (0x1D510, 'M', 'm'), + (0x1D511, 'M', 'n'), + (0x1D512, 'M', 'o'), + (0x1D513, 'M', 'p'), + (0x1D514, 'M', 'q'), + (0x1D515, 'X'), + (0x1D516, 'M', 's'), + (0x1D517, 'M', 't'), + (0x1D518, 'M', 'u'), + (0x1D519, 'M', 'v'), + (0x1D51A, 'M', 'w'), + (0x1D51B, 'M', 'x'), + (0x1D51C, 'M', 'y'), + (0x1D51D, 'X'), ] def _seg_62(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x1D58F, 'M', u'j'), - (0x1D590, 'M', u'k'), - (0x1D591, 'M', u'l'), - (0x1D592, 'M', u'm'), - (0x1D593, 'M', u'n'), - (0x1D594, 'M', u'o'), - (0x1D595, 'M', u'p'), - (0x1D596, 'M', u'q'), - (0x1D597, 'M', u'r'), - (0x1D598, 'M', u's'), - (0x1D599, 'M', u't'), - (0x1D59A, 'M', u'u'), - (0x1D59B, 'M', u'v'), - (0x1D59C, 'M', u'w'), - (0x1D59D, 'M', u'x'), - (0x1D59E, 'M', u'y'), - (0x1D59F, 'M', u'z'), - (0x1D5A0, 'M', u'a'), - (0x1D5A1, 'M', u'b'), - (0x1D5A2, 'M', u'c'), - (0x1D5A3, 'M', u'd'), - (0x1D5A4, 'M', u'e'), - (0x1D5A5, 'M', u'f'), - (0x1D5A6, 'M', u'g'), - (0x1D5A7, 'M', u'h'), - (0x1D5A8, 'M', u'i'), - (0x1D5A9, 'M', u'j'), - (0x1D5AA, 'M', u'k'), - (0x1D5AB, 'M', u'l'), - (0x1D5AC, 'M', u'm'), - (0x1D5AD, 'M', u'n'), - (0x1D5AE, 'M', u'o'), - (0x1D5AF, 'M', u'p'), - (0x1D5B0, 'M', u'q'), - (0x1D5B1, 'M', u'r'), - (0x1D5B2, 'M', u's'), - (0x1D5B3, 'M', u't'), - (0x1D5B4, 'M', u'u'), - (0x1D5B5, 'M', u'v'), - (0x1D5B6, 'M', u'w'), - (0x1D5B7, 'M', u'x'), - (0x1D5B8, 'M', u'y'), - (0x1D5B9, 'M', u'z'), - (0x1D5BA, 'M', u'a'), - (0x1D5BB, 'M', u'b'), - (0x1D5BC, 'M', u'c'), - (0x1D5BD, 'M', u'd'), - (0x1D5BE, 'M', u'e'), - (0x1D5BF, 'M', u'f'), - (0x1D5C0, 'M', u'g'), - (0x1D5C1, 'M', u'h'), - (0x1D5C2, 'M', u'i'), - (0x1D5C3, 'M', u'j'), - (0x1D5C4, 'M', u'k'), - (0x1D5C5, 'M', u'l'), - (0x1D5C6, 'M', u'm'), - (0x1D5C7, 'M', u'n'), - (0x1D5C8, 'M', u'o'), - (0x1D5C9, 'M', u'p'), - (0x1D5CA, 'M', u'q'), - (0x1D5CB, 'M', u'r'), - (0x1D5CC, 'M', u's'), - (0x1D5CD, 'M', u't'), - (0x1D5CE, 'M', u'u'), - (0x1D5CF, 'M', u'v'), - (0x1D5D0, 'M', u'w'), - (0x1D5D1, 'M', u'x'), - (0x1D5D2, 'M', u'y'), - (0x1D5D3, 'M', u'z'), - (0x1D5D4, 'M', u'a'), - (0x1D5D5, 'M', u'b'), - (0x1D5D6, 'M', u'c'), - (0x1D5D7, 'M', u'd'), - (0x1D5D8, 'M', u'e'), - (0x1D5D9, 'M', u'f'), - (0x1D5DA, 'M', u'g'), - (0x1D5DB, 'M', u'h'), - (0x1D5DC, 'M', u'i'), - (0x1D5DD, 'M', u'j'), - (0x1D5DE, 'M', u'k'), - (0x1D5DF, 'M', u'l'), - (0x1D5E0, 'M', u'm'), - (0x1D5E1, 'M', u'n'), - (0x1D5E2, 'M', u'o'), - (0x1D5E3, 'M', u'p'), - (0x1D5E4, 'M', u'q'), - (0x1D5E5, 'M', u'r'), - (0x1D5E6, 'M', u's'), - (0x1D5E7, 'M', u't'), - (0x1D5E8, 'M', u'u'), - (0x1D5E9, 'M', u'v'), - (0x1D5EA, 'M', u'w'), - (0x1D5EB, 'M', u'x'), - (0x1D5EC, 'M', u'y'), - (0x1D5ED, 'M', u'z'), - (0x1D5EE, 'M', u'a'), - (0x1D5EF, 'M', u'b'), - (0x1D5F0, 'M', u'c'), - (0x1D5F1, 'M', u'd'), - (0x1D5F2, 'M', u'e'), + (0x1D51E, 'M', 'a'), + (0x1D51F, 'M', 'b'), + (0x1D520, 'M', 'c'), + (0x1D521, 'M', 'd'), + (0x1D522, 'M', 'e'), + (0x1D523, 'M', 'f'), + (0x1D524, 'M', 'g'), + (0x1D525, 'M', 'h'), + (0x1D526, 'M', 'i'), + (0x1D527, 'M', 'j'), + (0x1D528, 'M', 'k'), + (0x1D529, 'M', 'l'), + (0x1D52A, 'M', 'm'), + (0x1D52B, 'M', 'n'), + (0x1D52C, 'M', 'o'), + (0x1D52D, 'M', 'p'), + (0x1D52E, 'M', 'q'), + (0x1D52F, 'M', 'r'), + (0x1D530, 'M', 's'), + (0x1D531, 'M', 't'), + (0x1D532, 'M', 'u'), + (0x1D533, 'M', 'v'), + (0x1D534, 'M', 'w'), + (0x1D535, 'M', 'x'), + (0x1D536, 'M', 'y'), + (0x1D537, 'M', 'z'), + (0x1D538, 'M', 'a'), + (0x1D539, 'M', 'b'), + (0x1D53A, 'X'), + (0x1D53B, 'M', 'd'), + (0x1D53C, 'M', 'e'), + (0x1D53D, 'M', 'f'), + (0x1D53E, 'M', 'g'), + (0x1D53F, 'X'), + (0x1D540, 'M', 'i'), + (0x1D541, 'M', 'j'), + (0x1D542, 'M', 'k'), + (0x1D543, 'M', 'l'), + (0x1D544, 'M', 'm'), + (0x1D545, 'X'), + (0x1D546, 'M', 'o'), + (0x1D547, 'X'), + (0x1D54A, 'M', 's'), + (0x1D54B, 'M', 't'), + (0x1D54C, 'M', 'u'), + (0x1D54D, 'M', 'v'), + (0x1D54E, 'M', 'w'), + (0x1D54F, 'M', 'x'), + (0x1D550, 'M', 'y'), + (0x1D551, 'X'), + (0x1D552, 'M', 'a'), + (0x1D553, 'M', 'b'), + (0x1D554, 'M', 'c'), + (0x1D555, 'M', 'd'), + (0x1D556, 'M', 'e'), + (0x1D557, 'M', 'f'), + (0x1D558, 'M', 'g'), + (0x1D559, 'M', 'h'), + (0x1D55A, 'M', 'i'), + (0x1D55B, 'M', 'j'), + (0x1D55C, 'M', 'k'), + (0x1D55D, 'M', 'l'), + (0x1D55E, 'M', 'm'), + (0x1D55F, 'M', 'n'), + (0x1D560, 'M', 'o'), + (0x1D561, 'M', 'p'), + (0x1D562, 'M', 'q'), + (0x1D563, 'M', 'r'), + (0x1D564, 'M', 's'), + (0x1D565, 'M', 't'), + (0x1D566, 'M', 'u'), + (0x1D567, 'M', 'v'), + (0x1D568, 'M', 'w'), + (0x1D569, 'M', 'x'), + (0x1D56A, 'M', 'y'), + (0x1D56B, 'M', 'z'), + (0x1D56C, 'M', 'a'), + (0x1D56D, 'M', 'b'), + (0x1D56E, 'M', 'c'), + (0x1D56F, 'M', 'd'), + (0x1D570, 'M', 'e'), + (0x1D571, 'M', 'f'), + (0x1D572, 'M', 'g'), + (0x1D573, 'M', 'h'), + (0x1D574, 'M', 'i'), + (0x1D575, 'M', 'j'), + (0x1D576, 'M', 'k'), + (0x1D577, 'M', 'l'), + (0x1D578, 'M', 'm'), + (0x1D579, 'M', 'n'), + (0x1D57A, 'M', 'o'), + (0x1D57B, 'M', 'p'), + (0x1D57C, 'M', 'q'), + (0x1D57D, 'M', 'r'), + (0x1D57E, 'M', 's'), + (0x1D57F, 'M', 't'), + (0x1D580, 'M', 'u'), + (0x1D581, 'M', 'v'), + (0x1D582, 'M', 'w'), + (0x1D583, 'M', 'x'), ] def _seg_63(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x1D5F3, 'M', u'f'), - (0x1D5F4, 'M', u'g'), - (0x1D5F5, 'M', u'h'), - (0x1D5F6, 'M', u'i'), - (0x1D5F7, 'M', u'j'), - (0x1D5F8, 'M', u'k'), - (0x1D5F9, 'M', u'l'), - (0x1D5FA, 'M', u'm'), - (0x1D5FB, 'M', u'n'), - (0x1D5FC, 'M', u'o'), - (0x1D5FD, 'M', u'p'), - (0x1D5FE, 'M', u'q'), - (0x1D5FF, 'M', u'r'), - (0x1D600, 'M', u's'), - (0x1D601, 'M', u't'), - (0x1D602, 'M', u'u'), - (0x1D603, 'M', u'v'), - (0x1D604, 'M', u'w'), - (0x1D605, 'M', u'x'), - (0x1D606, 'M', u'y'), - (0x1D607, 'M', u'z'), - (0x1D608, 'M', u'a'), - (0x1D609, 'M', u'b'), - (0x1D60A, 'M', u'c'), - (0x1D60B, 'M', u'd'), - (0x1D60C, 'M', u'e'), - (0x1D60D, 'M', u'f'), - (0x1D60E, 'M', u'g'), - (0x1D60F, 'M', u'h'), - (0x1D610, 'M', u'i'), - (0x1D611, 'M', u'j'), - (0x1D612, 'M', u'k'), - (0x1D613, 'M', u'l'), - (0x1D614, 'M', u'm'), - (0x1D615, 'M', u'n'), - (0x1D616, 'M', u'o'), - (0x1D617, 'M', u'p'), - (0x1D618, 'M', u'q'), - (0x1D619, 'M', u'r'), - (0x1D61A, 'M', u's'), - (0x1D61B, 'M', u't'), - (0x1D61C, 'M', u'u'), - (0x1D61D, 'M', u'v'), - (0x1D61E, 'M', u'w'), - (0x1D61F, 'M', u'x'), - (0x1D620, 'M', u'y'), - (0x1D621, 'M', u'z'), - (0x1D622, 'M', u'a'), - (0x1D623, 'M', u'b'), - (0x1D624, 'M', u'c'), - (0x1D625, 'M', u'd'), - (0x1D626, 'M', u'e'), - (0x1D627, 'M', u'f'), - (0x1D628, 'M', u'g'), - (0x1D629, 'M', u'h'), - (0x1D62A, 'M', u'i'), - (0x1D62B, 'M', u'j'), - (0x1D62C, 'M', u'k'), - (0x1D62D, 'M', u'l'), - (0x1D62E, 'M', u'm'), - (0x1D62F, 'M', u'n'), - (0x1D630, 'M', u'o'), - (0x1D631, 'M', u'p'), - (0x1D632, 'M', u'q'), - (0x1D633, 'M', u'r'), - (0x1D634, 'M', u's'), - (0x1D635, 'M', u't'), - (0x1D636, 'M', u'u'), - (0x1D637, 'M', u'v'), - (0x1D638, 'M', u'w'), - (0x1D639, 'M', u'x'), - (0x1D63A, 'M', u'y'), - (0x1D63B, 'M', u'z'), - (0x1D63C, 'M', u'a'), - (0x1D63D, 'M', u'b'), - (0x1D63E, 'M', u'c'), - (0x1D63F, 'M', u'd'), - (0x1D640, 'M', u'e'), - (0x1D641, 'M', u'f'), - (0x1D642, 'M', u'g'), - (0x1D643, 'M', u'h'), - (0x1D644, 'M', u'i'), - (0x1D645, 'M', u'j'), - (0x1D646, 'M', u'k'), - (0x1D647, 'M', u'l'), - (0x1D648, 'M', u'm'), - (0x1D649, 'M', u'n'), - (0x1D64A, 'M', u'o'), - (0x1D64B, 'M', u'p'), - (0x1D64C, 'M', u'q'), - (0x1D64D, 'M', u'r'), - (0x1D64E, 'M', u's'), - (0x1D64F, 'M', u't'), - (0x1D650, 'M', u'u'), - (0x1D651, 'M', u'v'), - (0x1D652, 'M', u'w'), - (0x1D653, 'M', u'x'), - (0x1D654, 'M', u'y'), - (0x1D655, 'M', u'z'), - (0x1D656, 'M', u'a'), + (0x1D584, 'M', 'y'), + (0x1D585, 'M', 'z'), + (0x1D586, 'M', 'a'), + (0x1D587, 'M', 'b'), + (0x1D588, 'M', 'c'), + (0x1D589, 'M', 'd'), + (0x1D58A, 'M', 'e'), + (0x1D58B, 'M', 'f'), + (0x1D58C, 'M', 'g'), + (0x1D58D, 'M', 'h'), + (0x1D58E, 'M', 'i'), + (0x1D58F, 'M', 'j'), + (0x1D590, 'M', 'k'), + (0x1D591, 'M', 'l'), + (0x1D592, 'M', 'm'), + (0x1D593, 'M', 'n'), + (0x1D594, 'M', 'o'), + (0x1D595, 'M', 'p'), + (0x1D596, 'M', 'q'), + (0x1D597, 'M', 'r'), + (0x1D598, 'M', 's'), + (0x1D599, 'M', 't'), + (0x1D59A, 'M', 'u'), + (0x1D59B, 'M', 'v'), + (0x1D59C, 'M', 'w'), + (0x1D59D, 'M', 'x'), + (0x1D59E, 'M', 'y'), + (0x1D59F, 'M', 'z'), + (0x1D5A0, 'M', 'a'), + (0x1D5A1, 'M', 'b'), + (0x1D5A2, 'M', 'c'), + (0x1D5A3, 'M', 'd'), + (0x1D5A4, 'M', 'e'), + (0x1D5A5, 'M', 'f'), + (0x1D5A6, 'M', 'g'), + (0x1D5A7, 'M', 'h'), + (0x1D5A8, 'M', 'i'), + (0x1D5A9, 'M', 'j'), + (0x1D5AA, 'M', 'k'), + (0x1D5AB, 'M', 'l'), + (0x1D5AC, 'M', 'm'), + (0x1D5AD, 'M', 'n'), + (0x1D5AE, 'M', 'o'), + (0x1D5AF, 'M', 'p'), + (0x1D5B0, 'M', 'q'), + (0x1D5B1, 'M', 'r'), + (0x1D5B2, 'M', 's'), + (0x1D5B3, 'M', 't'), + (0x1D5B4, 'M', 'u'), + (0x1D5B5, 'M', 'v'), + (0x1D5B6, 'M', 'w'), + (0x1D5B7, 'M', 'x'), + (0x1D5B8, 'M', 'y'), + (0x1D5B9, 'M', 'z'), + (0x1D5BA, 'M', 'a'), + (0x1D5BB, 'M', 'b'), + (0x1D5BC, 'M', 'c'), + (0x1D5BD, 'M', 'd'), + (0x1D5BE, 'M', 'e'), + (0x1D5BF, 'M', 'f'), + (0x1D5C0, 'M', 'g'), + (0x1D5C1, 'M', 'h'), + (0x1D5C2, 'M', 'i'), + (0x1D5C3, 'M', 'j'), + (0x1D5C4, 'M', 'k'), + (0x1D5C5, 'M', 'l'), + (0x1D5C6, 'M', 'm'), + (0x1D5C7, 'M', 'n'), + (0x1D5C8, 'M', 'o'), + (0x1D5C9, 'M', 'p'), + (0x1D5CA, 'M', 'q'), + (0x1D5CB, 'M', 'r'), + (0x1D5CC, 'M', 's'), + (0x1D5CD, 'M', 't'), + (0x1D5CE, 'M', 'u'), + (0x1D5CF, 'M', 'v'), + (0x1D5D0, 'M', 'w'), + (0x1D5D1, 'M', 'x'), + (0x1D5D2, 'M', 'y'), + (0x1D5D3, 'M', 'z'), + (0x1D5D4, 'M', 'a'), + (0x1D5D5, 'M', 'b'), + (0x1D5D6, 'M', 'c'), + (0x1D5D7, 'M', 'd'), + (0x1D5D8, 'M', 'e'), + (0x1D5D9, 'M', 'f'), + (0x1D5DA, 'M', 'g'), + (0x1D5DB, 'M', 'h'), + (0x1D5DC, 'M', 'i'), + (0x1D5DD, 'M', 'j'), + (0x1D5DE, 'M', 'k'), + (0x1D5DF, 'M', 'l'), + (0x1D5E0, 'M', 'm'), + (0x1D5E1, 'M', 'n'), + (0x1D5E2, 'M', 'o'), + (0x1D5E3, 'M', 'p'), + (0x1D5E4, 'M', 'q'), + (0x1D5E5, 'M', 'r'), + (0x1D5E6, 'M', 's'), + (0x1D5E7, 'M', 't'), ] def _seg_64(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x1D657, 'M', u'b'), - (0x1D658, 'M', u'c'), - (0x1D659, 'M', u'd'), - (0x1D65A, 'M', u'e'), - (0x1D65B, 'M', u'f'), - (0x1D65C, 'M', u'g'), - (0x1D65D, 'M', u'h'), - (0x1D65E, 'M', u'i'), - (0x1D65F, 'M', u'j'), - (0x1D660, 'M', u'k'), - (0x1D661, 'M', u'l'), - (0x1D662, 'M', u'm'), - (0x1D663, 'M', u'n'), - (0x1D664, 'M', u'o'), - (0x1D665, 'M', u'p'), - (0x1D666, 'M', u'q'), - (0x1D667, 'M', u'r'), - (0x1D668, 'M', u's'), - (0x1D669, 'M', u't'), - (0x1D66A, 'M', u'u'), - (0x1D66B, 'M', u'v'), - (0x1D66C, 'M', u'w'), - (0x1D66D, 'M', u'x'), - (0x1D66E, 'M', u'y'), - (0x1D66F, 'M', u'z'), - (0x1D670, 'M', u'a'), - (0x1D671, 'M', u'b'), - (0x1D672, 'M', u'c'), - (0x1D673, 'M', u'd'), - (0x1D674, 'M', u'e'), - (0x1D675, 'M', u'f'), - (0x1D676, 'M', u'g'), - (0x1D677, 'M', u'h'), - (0x1D678, 'M', u'i'), - (0x1D679, 'M', u'j'), - (0x1D67A, 'M', u'k'), - (0x1D67B, 'M', u'l'), - (0x1D67C, 'M', u'm'), - (0x1D67D, 'M', u'n'), - (0x1D67E, 'M', u'o'), - (0x1D67F, 'M', u'p'), - (0x1D680, 'M', u'q'), - (0x1D681, 'M', u'r'), - (0x1D682, 'M', u's'), - (0x1D683, 'M', u't'), - (0x1D684, 'M', u'u'), - (0x1D685, 'M', u'v'), - (0x1D686, 'M', u'w'), - (0x1D687, 'M', u'x'), - (0x1D688, 'M', u'y'), - (0x1D689, 'M', u'z'), - (0x1D68A, 'M', u'a'), - (0x1D68B, 'M', u'b'), - (0x1D68C, 'M', u'c'), - (0x1D68D, 'M', u'd'), - (0x1D68E, 'M', u'e'), - (0x1D68F, 'M', u'f'), - (0x1D690, 'M', u'g'), - (0x1D691, 'M', u'h'), - (0x1D692, 'M', u'i'), - (0x1D693, 'M', u'j'), - (0x1D694, 'M', u'k'), - (0x1D695, 'M', u'l'), - (0x1D696, 'M', u'm'), - (0x1D697, 'M', u'n'), - (0x1D698, 'M', u'o'), - (0x1D699, 'M', u'p'), - (0x1D69A, 'M', u'q'), - (0x1D69B, 'M', u'r'), - (0x1D69C, 'M', u's'), - (0x1D69D, 'M', u't'), - (0x1D69E, 'M', u'u'), - (0x1D69F, 'M', u'v'), - (0x1D6A0, 'M', u'w'), - (0x1D6A1, 'M', u'x'), - (0x1D6A2, 'M', u'y'), - (0x1D6A3, 'M', u'z'), - (0x1D6A4, 'M', u'ı'), - (0x1D6A5, 'M', u'ȷ'), - (0x1D6A6, 'X'), - (0x1D6A8, 'M', u'α'), - (0x1D6A9, 'M', u'β'), - (0x1D6AA, 'M', u'γ'), - (0x1D6AB, 'M', u'δ'), - (0x1D6AC, 'M', u'ε'), - (0x1D6AD, 'M', u'ζ'), - (0x1D6AE, 'M', u'η'), - (0x1D6AF, 'M', u'θ'), - (0x1D6B0, 'M', u'ι'), - (0x1D6B1, 'M', u'κ'), - (0x1D6B2, 'M', u'λ'), - (0x1D6B3, 'M', u'μ'), - (0x1D6B4, 'M', u'ν'), - (0x1D6B5, 'M', u'ξ'), - (0x1D6B6, 'M', u'ο'), - (0x1D6B7, 'M', u'π'), - (0x1D6B8, 'M', u'ρ'), - (0x1D6B9, 'M', u'θ'), - (0x1D6BA, 'M', u'σ'), - (0x1D6BB, 'M', u'τ'), + (0x1D5E8, 'M', 'u'), + (0x1D5E9, 'M', 'v'), + (0x1D5EA, 'M', 'w'), + (0x1D5EB, 'M', 'x'), + (0x1D5EC, 'M', 'y'), + (0x1D5ED, 'M', 'z'), + (0x1D5EE, 'M', 'a'), + (0x1D5EF, 'M', 'b'), + (0x1D5F0, 'M', 'c'), + (0x1D5F1, 'M', 'd'), + (0x1D5F2, 'M', 'e'), + (0x1D5F3, 'M', 'f'), + (0x1D5F4, 'M', 'g'), + (0x1D5F5, 'M', 'h'), + (0x1D5F6, 'M', 'i'), + (0x1D5F7, 'M', 'j'), + (0x1D5F8, 'M', 'k'), + (0x1D5F9, 'M', 'l'), + (0x1D5FA, 'M', 'm'), + (0x1D5FB, 'M', 'n'), + (0x1D5FC, 'M', 'o'), + (0x1D5FD, 'M', 'p'), + (0x1D5FE, 'M', 'q'), + (0x1D5FF, 'M', 'r'), + (0x1D600, 'M', 's'), + (0x1D601, 'M', 't'), + (0x1D602, 'M', 'u'), + (0x1D603, 'M', 'v'), + (0x1D604, 'M', 'w'), + (0x1D605, 'M', 'x'), + (0x1D606, 'M', 'y'), + (0x1D607, 'M', 'z'), + (0x1D608, 'M', 'a'), + (0x1D609, 'M', 'b'), + (0x1D60A, 'M', 'c'), + (0x1D60B, 'M', 'd'), + (0x1D60C, 'M', 'e'), + (0x1D60D, 'M', 'f'), + (0x1D60E, 'M', 'g'), + (0x1D60F, 'M', 'h'), + (0x1D610, 'M', 'i'), + (0x1D611, 'M', 'j'), + (0x1D612, 'M', 'k'), + (0x1D613, 'M', 'l'), + (0x1D614, 'M', 'm'), + (0x1D615, 'M', 'n'), + (0x1D616, 'M', 'o'), + (0x1D617, 'M', 'p'), + (0x1D618, 'M', 'q'), + (0x1D619, 'M', 'r'), + (0x1D61A, 'M', 's'), + (0x1D61B, 'M', 't'), + (0x1D61C, 'M', 'u'), + (0x1D61D, 'M', 'v'), + (0x1D61E, 'M', 'w'), + (0x1D61F, 'M', 'x'), + (0x1D620, 'M', 'y'), + (0x1D621, 'M', 'z'), + (0x1D622, 'M', 'a'), + (0x1D623, 'M', 'b'), + (0x1D624, 'M', 'c'), + (0x1D625, 'M', 'd'), + (0x1D626, 'M', 'e'), + (0x1D627, 'M', 'f'), + (0x1D628, 'M', 'g'), + (0x1D629, 'M', 'h'), + (0x1D62A, 'M', 'i'), + (0x1D62B, 'M', 'j'), + (0x1D62C, 'M', 'k'), + (0x1D62D, 'M', 'l'), + (0x1D62E, 'M', 'm'), + (0x1D62F, 'M', 'n'), + (0x1D630, 'M', 'o'), + (0x1D631, 'M', 'p'), + (0x1D632, 'M', 'q'), + (0x1D633, 'M', 'r'), + (0x1D634, 'M', 's'), + (0x1D635, 'M', 't'), + (0x1D636, 'M', 'u'), + (0x1D637, 'M', 'v'), + (0x1D638, 'M', 'w'), + (0x1D639, 'M', 'x'), + (0x1D63A, 'M', 'y'), + (0x1D63B, 'M', 'z'), + (0x1D63C, 'M', 'a'), + (0x1D63D, 'M', 'b'), + (0x1D63E, 'M', 'c'), + (0x1D63F, 'M', 'd'), + (0x1D640, 'M', 'e'), + (0x1D641, 'M', 'f'), + (0x1D642, 'M', 'g'), + (0x1D643, 'M', 'h'), + (0x1D644, 'M', 'i'), + (0x1D645, 'M', 'j'), + (0x1D646, 'M', 'k'), + (0x1D647, 'M', 'l'), + (0x1D648, 'M', 'm'), + (0x1D649, 'M', 'n'), + (0x1D64A, 'M', 'o'), + (0x1D64B, 'M', 'p'), ] def _seg_65(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x1D6BC, 'M', u'υ'), - (0x1D6BD, 'M', u'φ'), - (0x1D6BE, 'M', u'χ'), - (0x1D6BF, 'M', u'ψ'), - (0x1D6C0, 'M', u'ω'), - (0x1D6C1, 'M', u'∇'), - (0x1D6C2, 'M', u'α'), - (0x1D6C3, 'M', u'β'), - (0x1D6C4, 'M', u'γ'), - (0x1D6C5, 'M', u'δ'), - (0x1D6C6, 'M', u'ε'), - (0x1D6C7, 'M', u'ζ'), - (0x1D6C8, 'M', u'η'), - (0x1D6C9, 'M', u'θ'), - (0x1D6CA, 'M', u'ι'), - (0x1D6CB, 'M', u'κ'), - (0x1D6CC, 'M', u'λ'), - (0x1D6CD, 'M', u'μ'), - (0x1D6CE, 'M', u'ν'), - (0x1D6CF, 'M', u'ξ'), - (0x1D6D0, 'M', u'ο'), - (0x1D6D1, 'M', u'π'), - (0x1D6D2, 'M', u'ρ'), - (0x1D6D3, 'M', u'σ'), - (0x1D6D5, 'M', u'τ'), - (0x1D6D6, 'M', u'υ'), - (0x1D6D7, 'M', u'φ'), - (0x1D6D8, 'M', u'χ'), - (0x1D6D9, 'M', u'ψ'), - (0x1D6DA, 'M', u'ω'), - (0x1D6DB, 'M', u'∂'), - (0x1D6DC, 'M', u'ε'), - (0x1D6DD, 'M', u'θ'), - (0x1D6DE, 'M', u'κ'), - (0x1D6DF, 'M', u'φ'), - (0x1D6E0, 'M', u'ρ'), - (0x1D6E1, 'M', u'π'), - (0x1D6E2, 'M', u'α'), - (0x1D6E3, 'M', u'β'), - (0x1D6E4, 'M', u'γ'), - (0x1D6E5, 'M', u'δ'), - (0x1D6E6, 'M', u'ε'), - (0x1D6E7, 'M', u'ζ'), - (0x1D6E8, 'M', u'η'), - (0x1D6E9, 'M', u'θ'), - (0x1D6EA, 'M', u'ι'), - (0x1D6EB, 'M', u'κ'), - (0x1D6EC, 'M', u'λ'), - (0x1D6ED, 'M', u'μ'), - (0x1D6EE, 'M', u'ν'), - (0x1D6EF, 'M', u'ξ'), - (0x1D6F0, 'M', u'ο'), - (0x1D6F1, 'M', u'π'), - (0x1D6F2, 'M', u'ρ'), - (0x1D6F3, 'M', u'θ'), - (0x1D6F4, 'M', u'σ'), - (0x1D6F5, 'M', u'τ'), - (0x1D6F6, 'M', u'υ'), - (0x1D6F7, 'M', u'φ'), - (0x1D6F8, 'M', u'χ'), - (0x1D6F9, 'M', u'ψ'), - (0x1D6FA, 'M', u'ω'), - (0x1D6FB, 'M', u'∇'), - (0x1D6FC, 'M', u'α'), - (0x1D6FD, 'M', u'β'), - (0x1D6FE, 'M', u'γ'), - (0x1D6FF, 'M', u'δ'), - (0x1D700, 'M', u'ε'), - (0x1D701, 'M', u'ζ'), - (0x1D702, 'M', u'η'), - (0x1D703, 'M', u'θ'), - (0x1D704, 'M', u'ι'), - (0x1D705, 'M', u'κ'), - (0x1D706, 'M', u'λ'), - (0x1D707, 'M', u'μ'), - (0x1D708, 'M', u'ν'), - (0x1D709, 'M', u'ξ'), - (0x1D70A, 'M', u'ο'), - (0x1D70B, 'M', u'π'), - (0x1D70C, 'M', u'ρ'), - (0x1D70D, 'M', u'σ'), - (0x1D70F, 'M', u'τ'), - (0x1D710, 'M', u'υ'), - (0x1D711, 'M', u'φ'), - (0x1D712, 'M', u'χ'), - (0x1D713, 'M', u'ψ'), - (0x1D714, 'M', u'ω'), - (0x1D715, 'M', u'∂'), - (0x1D716, 'M', u'ε'), - (0x1D717, 'M', u'θ'), - (0x1D718, 'M', u'κ'), - (0x1D719, 'M', u'φ'), - (0x1D71A, 'M', u'ρ'), - (0x1D71B, 'M', u'π'), - (0x1D71C, 'M', u'α'), - (0x1D71D, 'M', u'β'), - (0x1D71E, 'M', u'γ'), - (0x1D71F, 'M', u'δ'), - (0x1D720, 'M', u'ε'), - (0x1D721, 'M', u'ζ'), + (0x1D64C, 'M', 'q'), + (0x1D64D, 'M', 'r'), + (0x1D64E, 'M', 's'), + (0x1D64F, 'M', 't'), + (0x1D650, 'M', 'u'), + (0x1D651, 'M', 'v'), + (0x1D652, 'M', 'w'), + (0x1D653, 'M', 'x'), + (0x1D654, 'M', 'y'), + (0x1D655, 'M', 'z'), + (0x1D656, 'M', 'a'), + (0x1D657, 'M', 'b'), + (0x1D658, 'M', 'c'), + (0x1D659, 'M', 'd'), + (0x1D65A, 'M', 'e'), + (0x1D65B, 'M', 'f'), + (0x1D65C, 'M', 'g'), + (0x1D65D, 'M', 'h'), + (0x1D65E, 'M', 'i'), + (0x1D65F, 'M', 'j'), + (0x1D660, 'M', 'k'), + (0x1D661, 'M', 'l'), + (0x1D662, 'M', 'm'), + (0x1D663, 'M', 'n'), + (0x1D664, 'M', 'o'), + (0x1D665, 'M', 'p'), + (0x1D666, 'M', 'q'), + (0x1D667, 'M', 'r'), + (0x1D668, 'M', 's'), + (0x1D669, 'M', 't'), + (0x1D66A, 'M', 'u'), + (0x1D66B, 'M', 'v'), + (0x1D66C, 'M', 'w'), + (0x1D66D, 'M', 'x'), + (0x1D66E, 'M', 'y'), + (0x1D66F, 'M', 'z'), + (0x1D670, 'M', 'a'), + (0x1D671, 'M', 'b'), + (0x1D672, 'M', 'c'), + (0x1D673, 'M', 'd'), + (0x1D674, 'M', 'e'), + (0x1D675, 'M', 'f'), + (0x1D676, 'M', 'g'), + (0x1D677, 'M', 'h'), + (0x1D678, 'M', 'i'), + (0x1D679, 'M', 'j'), + (0x1D67A, 'M', 'k'), + (0x1D67B, 'M', 'l'), + (0x1D67C, 'M', 'm'), + (0x1D67D, 'M', 'n'), + (0x1D67E, 'M', 'o'), + (0x1D67F, 'M', 'p'), + (0x1D680, 'M', 'q'), + (0x1D681, 'M', 'r'), + (0x1D682, 'M', 's'), + (0x1D683, 'M', 't'), + (0x1D684, 'M', 'u'), + (0x1D685, 'M', 'v'), + (0x1D686, 'M', 'w'), + (0x1D687, 'M', 'x'), + (0x1D688, 'M', 'y'), + (0x1D689, 'M', 'z'), + (0x1D68A, 'M', 'a'), + (0x1D68B, 'M', 'b'), + (0x1D68C, 'M', 'c'), + (0x1D68D, 'M', 'd'), + (0x1D68E, 'M', 'e'), + (0x1D68F, 'M', 'f'), + (0x1D690, 'M', 'g'), + (0x1D691, 'M', 'h'), + (0x1D692, 'M', 'i'), + (0x1D693, 'M', 'j'), + (0x1D694, 'M', 'k'), + (0x1D695, 'M', 'l'), + (0x1D696, 'M', 'm'), + (0x1D697, 'M', 'n'), + (0x1D698, 'M', 'o'), + (0x1D699, 'M', 'p'), + (0x1D69A, 'M', 'q'), + (0x1D69B, 'M', 'r'), + (0x1D69C, 'M', 's'), + (0x1D69D, 'M', 't'), + (0x1D69E, 'M', 'u'), + (0x1D69F, 'M', 'v'), + (0x1D6A0, 'M', 'w'), + (0x1D6A1, 'M', 'x'), + (0x1D6A2, 'M', 'y'), + (0x1D6A3, 'M', 'z'), + (0x1D6A4, 'M', 'ı'), + (0x1D6A5, 'M', 'ȷ'), + (0x1D6A6, 'X'), + (0x1D6A8, 'M', 'α'), + (0x1D6A9, 'M', 'β'), + (0x1D6AA, 'M', 'γ'), + (0x1D6AB, 'M', 'δ'), + (0x1D6AC, 'M', 'ε'), + (0x1D6AD, 'M', 'ζ'), + (0x1D6AE, 'M', 'η'), + (0x1D6AF, 'M', 'θ'), + (0x1D6B0, 'M', 'ι'), ] def _seg_66(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x1D722, 'M', u'η'), - (0x1D723, 'M', u'θ'), - (0x1D724, 'M', u'ι'), - (0x1D725, 'M', u'κ'), - (0x1D726, 'M', u'λ'), - (0x1D727, 'M', u'μ'), - (0x1D728, 'M', u'ν'), - (0x1D729, 'M', u'ξ'), - (0x1D72A, 'M', u'ο'), - (0x1D72B, 'M', u'π'), - (0x1D72C, 'M', u'ρ'), - (0x1D72D, 'M', u'θ'), - (0x1D72E, 'M', u'σ'), - (0x1D72F, 'M', u'τ'), - (0x1D730, 'M', u'υ'), - (0x1D731, 'M', u'φ'), - (0x1D732, 'M', u'χ'), - (0x1D733, 'M', u'ψ'), - (0x1D734, 'M', u'ω'), - (0x1D735, 'M', u'∇'), - (0x1D736, 'M', u'α'), - (0x1D737, 'M', u'β'), - (0x1D738, 'M', u'γ'), - (0x1D739, 'M', u'δ'), - (0x1D73A, 'M', u'ε'), - (0x1D73B, 'M', u'ζ'), - (0x1D73C, 'M', u'η'), - (0x1D73D, 'M', u'θ'), - (0x1D73E, 'M', u'ι'), - (0x1D73F, 'M', u'κ'), - (0x1D740, 'M', u'λ'), - (0x1D741, 'M', u'μ'), - (0x1D742, 'M', u'ν'), - (0x1D743, 'M', u'ξ'), - (0x1D744, 'M', u'ο'), - (0x1D745, 'M', u'π'), - (0x1D746, 'M', u'ρ'), - (0x1D747, 'M', u'σ'), - (0x1D749, 'M', u'τ'), - (0x1D74A, 'M', u'υ'), - (0x1D74B, 'M', u'φ'), - (0x1D74C, 'M', u'χ'), - (0x1D74D, 'M', u'ψ'), - (0x1D74E, 'M', u'ω'), - (0x1D74F, 'M', u'∂'), - (0x1D750, 'M', u'ε'), - (0x1D751, 'M', u'θ'), - (0x1D752, 'M', u'κ'), - (0x1D753, 'M', u'φ'), - (0x1D754, 'M', u'ρ'), - (0x1D755, 'M', u'π'), - (0x1D756, 'M', u'α'), - (0x1D757, 'M', u'β'), - (0x1D758, 'M', u'γ'), - (0x1D759, 'M', u'δ'), - (0x1D75A, 'M', u'ε'), - (0x1D75B, 'M', u'ζ'), - (0x1D75C, 'M', u'η'), - (0x1D75D, 'M', u'θ'), - (0x1D75E, 'M', u'ι'), - (0x1D75F, 'M', u'κ'), - (0x1D760, 'M', u'λ'), - (0x1D761, 'M', u'μ'), - (0x1D762, 'M', u'ν'), - (0x1D763, 'M', u'ξ'), - (0x1D764, 'M', u'ο'), - (0x1D765, 'M', u'π'), - (0x1D766, 'M', u'ρ'), - (0x1D767, 'M', u'θ'), - (0x1D768, 'M', u'σ'), - (0x1D769, 'M', u'τ'), - (0x1D76A, 'M', u'υ'), - (0x1D76B, 'M', u'φ'), - (0x1D76C, 'M', u'χ'), - (0x1D76D, 'M', u'ψ'), - (0x1D76E, 'M', u'ω'), - (0x1D76F, 'M', u'∇'), - (0x1D770, 'M', u'α'), - (0x1D771, 'M', u'β'), - (0x1D772, 'M', u'γ'), - (0x1D773, 'M', u'δ'), - (0x1D774, 'M', u'ε'), - (0x1D775, 'M', u'ζ'), - (0x1D776, 'M', u'η'), - (0x1D777, 'M', u'θ'), - (0x1D778, 'M', u'ι'), - (0x1D779, 'M', u'κ'), - (0x1D77A, 'M', u'λ'), - (0x1D77B, 'M', u'μ'), - (0x1D77C, 'M', u'ν'), - (0x1D77D, 'M', u'ξ'), - (0x1D77E, 'M', u'ο'), - (0x1D77F, 'M', u'π'), - (0x1D780, 'M', u'ρ'), - (0x1D781, 'M', u'σ'), - (0x1D783, 'M', u'τ'), - (0x1D784, 'M', u'υ'), - (0x1D785, 'M', u'φ'), - (0x1D786, 'M', u'χ'), - (0x1D787, 'M', u'ψ'), + (0x1D6B1, 'M', 'κ'), + (0x1D6B2, 'M', 'λ'), + (0x1D6B3, 'M', 'μ'), + (0x1D6B4, 'M', 'ν'), + (0x1D6B5, 'M', 'ξ'), + (0x1D6B6, 'M', 'ο'), + (0x1D6B7, 'M', 'π'), + (0x1D6B8, 'M', 'ρ'), + (0x1D6B9, 'M', 'θ'), + (0x1D6BA, 'M', 'σ'), + (0x1D6BB, 'M', 'τ'), + (0x1D6BC, 'M', 'υ'), + (0x1D6BD, 'M', 'φ'), + (0x1D6BE, 'M', 'χ'), + (0x1D6BF, 'M', 'ψ'), + (0x1D6C0, 'M', 'ω'), + (0x1D6C1, 'M', '∇'), + (0x1D6C2, 'M', 'α'), + (0x1D6C3, 'M', 'β'), + (0x1D6C4, 'M', 'γ'), + (0x1D6C5, 'M', 'δ'), + (0x1D6C6, 'M', 'ε'), + (0x1D6C7, 'M', 'ζ'), + (0x1D6C8, 'M', 'η'), + (0x1D6C9, 'M', 'θ'), + (0x1D6CA, 'M', 'ι'), + (0x1D6CB, 'M', 'κ'), + (0x1D6CC, 'M', 'λ'), + (0x1D6CD, 'M', 'μ'), + (0x1D6CE, 'M', 'ν'), + (0x1D6CF, 'M', 'ξ'), + (0x1D6D0, 'M', 'ο'), + (0x1D6D1, 'M', 'π'), + (0x1D6D2, 'M', 'ρ'), + (0x1D6D3, 'M', 'σ'), + (0x1D6D5, 'M', 'τ'), + (0x1D6D6, 'M', 'υ'), + (0x1D6D7, 'M', 'φ'), + (0x1D6D8, 'M', 'χ'), + (0x1D6D9, 'M', 'ψ'), + (0x1D6DA, 'M', 'ω'), + (0x1D6DB, 'M', '∂'), + (0x1D6DC, 'M', 'ε'), + (0x1D6DD, 'M', 'θ'), + (0x1D6DE, 'M', 'κ'), + (0x1D6DF, 'M', 'φ'), + (0x1D6E0, 'M', 'ρ'), + (0x1D6E1, 'M', 'π'), + (0x1D6E2, 'M', 'α'), + (0x1D6E3, 'M', 'β'), + (0x1D6E4, 'M', 'γ'), + (0x1D6E5, 'M', 'δ'), + (0x1D6E6, 'M', 'ε'), + (0x1D6E7, 'M', 'ζ'), + (0x1D6E8, 'M', 'η'), + (0x1D6E9, 'M', 'θ'), + (0x1D6EA, 'M', 'ι'), + (0x1D6EB, 'M', 'κ'), + (0x1D6EC, 'M', 'λ'), + (0x1D6ED, 'M', 'μ'), + (0x1D6EE, 'M', 'ν'), + (0x1D6EF, 'M', 'ξ'), + (0x1D6F0, 'M', 'ο'), + (0x1D6F1, 'M', 'π'), + (0x1D6F2, 'M', 'ρ'), + (0x1D6F3, 'M', 'θ'), + (0x1D6F4, 'M', 'σ'), + (0x1D6F5, 'M', 'τ'), + (0x1D6F6, 'M', 'υ'), + (0x1D6F7, 'M', 'φ'), + (0x1D6F8, 'M', 'χ'), + (0x1D6F9, 'M', 'ψ'), + (0x1D6FA, 'M', 'ω'), + (0x1D6FB, 'M', '∇'), + (0x1D6FC, 'M', 'α'), + (0x1D6FD, 'M', 'β'), + (0x1D6FE, 'M', 'γ'), + (0x1D6FF, 'M', 'δ'), + (0x1D700, 'M', 'ε'), + (0x1D701, 'M', 'ζ'), + (0x1D702, 'M', 'η'), + (0x1D703, 'M', 'θ'), + (0x1D704, 'M', 'ι'), + (0x1D705, 'M', 'κ'), + (0x1D706, 'M', 'λ'), + (0x1D707, 'M', 'μ'), + (0x1D708, 'M', 'ν'), + (0x1D709, 'M', 'ξ'), + (0x1D70A, 'M', 'ο'), + (0x1D70B, 'M', 'π'), + (0x1D70C, 'M', 'ρ'), + (0x1D70D, 'M', 'σ'), + (0x1D70F, 'M', 'τ'), + (0x1D710, 'M', 'υ'), + (0x1D711, 'M', 'φ'), + (0x1D712, 'M', 'χ'), + (0x1D713, 'M', 'ψ'), + (0x1D714, 'M', 'ω'), + (0x1D715, 'M', '∂'), + (0x1D716, 'M', 'ε'), ] def _seg_67(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x1D788, 'M', u'ω'), - (0x1D789, 'M', u'∂'), - (0x1D78A, 'M', u'ε'), - (0x1D78B, 'M', u'θ'), - (0x1D78C, 'M', u'κ'), - (0x1D78D, 'M', u'φ'), - (0x1D78E, 'M', u'ρ'), - (0x1D78F, 'M', u'π'), - (0x1D790, 'M', u'α'), - (0x1D791, 'M', u'β'), - (0x1D792, 'M', u'γ'), - (0x1D793, 'M', u'δ'), - (0x1D794, 'M', u'ε'), - (0x1D795, 'M', u'ζ'), - (0x1D796, 'M', u'η'), - (0x1D797, 'M', u'θ'), - (0x1D798, 'M', u'ι'), - (0x1D799, 'M', u'κ'), - (0x1D79A, 'M', u'λ'), - (0x1D79B, 'M', u'μ'), - (0x1D79C, 'M', u'ν'), - (0x1D79D, 'M', u'ξ'), - (0x1D79E, 'M', u'ο'), - (0x1D79F, 'M', u'π'), - (0x1D7A0, 'M', u'ρ'), - (0x1D7A1, 'M', u'θ'), - (0x1D7A2, 'M', u'σ'), - (0x1D7A3, 'M', u'τ'), - (0x1D7A4, 'M', u'υ'), - (0x1D7A5, 'M', u'φ'), - (0x1D7A6, 'M', u'χ'), - (0x1D7A7, 'M', u'ψ'), - (0x1D7A8, 'M', u'ω'), - (0x1D7A9, 'M', u'∇'), - (0x1D7AA, 'M', u'α'), - (0x1D7AB, 'M', u'β'), - (0x1D7AC, 'M', u'γ'), - (0x1D7AD, 'M', u'δ'), - (0x1D7AE, 'M', u'ε'), - (0x1D7AF, 'M', u'ζ'), - (0x1D7B0, 'M', u'η'), - (0x1D7B1, 'M', u'θ'), - (0x1D7B2, 'M', u'ι'), - (0x1D7B3, 'M', u'κ'), - (0x1D7B4, 'M', u'λ'), - (0x1D7B5, 'M', u'μ'), - (0x1D7B6, 'M', u'ν'), - (0x1D7B7, 'M', u'ξ'), - (0x1D7B8, 'M', u'ο'), - (0x1D7B9, 'M', u'π'), - (0x1D7BA, 'M', u'ρ'), - (0x1D7BB, 'M', u'σ'), - (0x1D7BD, 'M', u'τ'), - (0x1D7BE, 'M', u'υ'), - (0x1D7BF, 'M', u'φ'), - (0x1D7C0, 'M', u'χ'), - (0x1D7C1, 'M', u'ψ'), - (0x1D7C2, 'M', u'ω'), - (0x1D7C3, 'M', u'∂'), - (0x1D7C4, 'M', u'ε'), - (0x1D7C5, 'M', u'θ'), - (0x1D7C6, 'M', u'κ'), - (0x1D7C7, 'M', u'φ'), - (0x1D7C8, 'M', u'ρ'), - (0x1D7C9, 'M', u'π'), - (0x1D7CA, 'M', u'ϝ'), - (0x1D7CC, 'X'), - (0x1D7CE, 'M', u'0'), - (0x1D7CF, 'M', u'1'), - (0x1D7D0, 'M', u'2'), - (0x1D7D1, 'M', u'3'), - (0x1D7D2, 'M', u'4'), - (0x1D7D3, 'M', u'5'), - (0x1D7D4, 'M', u'6'), - (0x1D7D5, 'M', u'7'), - (0x1D7D6, 'M', u'8'), - (0x1D7D7, 'M', u'9'), - (0x1D7D8, 'M', u'0'), - (0x1D7D9, 'M', u'1'), - (0x1D7DA, 'M', u'2'), - (0x1D7DB, 'M', u'3'), - (0x1D7DC, 'M', u'4'), - (0x1D7DD, 'M', u'5'), - (0x1D7DE, 'M', u'6'), - (0x1D7DF, 'M', u'7'), - (0x1D7E0, 'M', u'8'), - (0x1D7E1, 'M', u'9'), - (0x1D7E2, 'M', u'0'), - (0x1D7E3, 'M', u'1'), - (0x1D7E4, 'M', u'2'), - (0x1D7E5, 'M', u'3'), - (0x1D7E6, 'M', u'4'), - (0x1D7E7, 'M', u'5'), - (0x1D7E8, 'M', u'6'), - (0x1D7E9, 'M', u'7'), - (0x1D7EA, 'M', u'8'), - (0x1D7EB, 'M', u'9'), - (0x1D7EC, 'M', u'0'), - (0x1D7ED, 'M', u'1'), - (0x1D7EE, 'M', u'2'), + (0x1D717, 'M', 'θ'), + (0x1D718, 'M', 'κ'), + (0x1D719, 'M', 'φ'), + (0x1D71A, 'M', 'ρ'), + (0x1D71B, 'M', 'π'), + (0x1D71C, 'M', 'α'), + (0x1D71D, 'M', 'β'), + (0x1D71E, 'M', 'γ'), + (0x1D71F, 'M', 'δ'), + (0x1D720, 'M', 'ε'), + (0x1D721, 'M', 'ζ'), + (0x1D722, 'M', 'η'), + (0x1D723, 'M', 'θ'), + (0x1D724, 'M', 'ι'), + (0x1D725, 'M', 'κ'), + (0x1D726, 'M', 'λ'), + (0x1D727, 'M', 'μ'), + (0x1D728, 'M', 'ν'), + (0x1D729, 'M', 'ξ'), + (0x1D72A, 'M', 'ο'), + (0x1D72B, 'M', 'π'), + (0x1D72C, 'M', 'ρ'), + (0x1D72D, 'M', 'θ'), + (0x1D72E, 'M', 'σ'), + (0x1D72F, 'M', 'τ'), + (0x1D730, 'M', 'υ'), + (0x1D731, 'M', 'φ'), + (0x1D732, 'M', 'χ'), + (0x1D733, 'M', 'ψ'), + (0x1D734, 'M', 'ω'), + (0x1D735, 'M', '∇'), + (0x1D736, 'M', 'α'), + (0x1D737, 'M', 'β'), + (0x1D738, 'M', 'γ'), + (0x1D739, 'M', 'δ'), + (0x1D73A, 'M', 'ε'), + (0x1D73B, 'M', 'ζ'), + (0x1D73C, 'M', 'η'), + (0x1D73D, 'M', 'θ'), + (0x1D73E, 'M', 'ι'), + (0x1D73F, 'M', 'κ'), + (0x1D740, 'M', 'λ'), + (0x1D741, 'M', 'μ'), + (0x1D742, 'M', 'ν'), + (0x1D743, 'M', 'ξ'), + (0x1D744, 'M', 'ο'), + (0x1D745, 'M', 'π'), + (0x1D746, 'M', 'ρ'), + (0x1D747, 'M', 'σ'), + (0x1D749, 'M', 'τ'), + (0x1D74A, 'M', 'υ'), + (0x1D74B, 'M', 'φ'), + (0x1D74C, 'M', 'χ'), + (0x1D74D, 'M', 'ψ'), + (0x1D74E, 'M', 'ω'), + (0x1D74F, 'M', '∂'), + (0x1D750, 'M', 'ε'), + (0x1D751, 'M', 'θ'), + (0x1D752, 'M', 'κ'), + (0x1D753, 'M', 'φ'), + (0x1D754, 'M', 'ρ'), + (0x1D755, 'M', 'π'), + (0x1D756, 'M', 'α'), + (0x1D757, 'M', 'β'), + (0x1D758, 'M', 'γ'), + (0x1D759, 'M', 'δ'), + (0x1D75A, 'M', 'ε'), + (0x1D75B, 'M', 'ζ'), + (0x1D75C, 'M', 'η'), + (0x1D75D, 'M', 'θ'), + (0x1D75E, 'M', 'ι'), + (0x1D75F, 'M', 'κ'), + (0x1D760, 'M', 'λ'), + (0x1D761, 'M', 'μ'), + (0x1D762, 'M', 'ν'), + (0x1D763, 'M', 'ξ'), + (0x1D764, 'M', 'ο'), + (0x1D765, 'M', 'π'), + (0x1D766, 'M', 'ρ'), + (0x1D767, 'M', 'θ'), + (0x1D768, 'M', 'σ'), + (0x1D769, 'M', 'τ'), + (0x1D76A, 'M', 'υ'), + (0x1D76B, 'M', 'φ'), + (0x1D76C, 'M', 'χ'), + (0x1D76D, 'M', 'ψ'), + (0x1D76E, 'M', 'ω'), + (0x1D76F, 'M', '∇'), + (0x1D770, 'M', 'α'), + (0x1D771, 'M', 'β'), + (0x1D772, 'M', 'γ'), + (0x1D773, 'M', 'δ'), + (0x1D774, 'M', 'ε'), + (0x1D775, 'M', 'ζ'), + (0x1D776, 'M', 'η'), + (0x1D777, 'M', 'θ'), + (0x1D778, 'M', 'ι'), + (0x1D779, 'M', 'κ'), + (0x1D77A, 'M', 'λ'), + (0x1D77B, 'M', 'μ'), ] def _seg_68(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x1D7EF, 'M', u'3'), - (0x1D7F0, 'M', u'4'), - (0x1D7F1, 'M', u'5'), - (0x1D7F2, 'M', u'6'), - (0x1D7F3, 'M', u'7'), - (0x1D7F4, 'M', u'8'), - (0x1D7F5, 'M', u'9'), - (0x1D7F6, 'M', u'0'), - (0x1D7F7, 'M', u'1'), - (0x1D7F8, 'M', u'2'), - (0x1D7F9, 'M', u'3'), - (0x1D7FA, 'M', u'4'), - (0x1D7FB, 'M', u'5'), - (0x1D7FC, 'M', u'6'), - (0x1D7FD, 'M', u'7'), - (0x1D7FE, 'M', u'8'), - (0x1D7FF, 'M', u'9'), + (0x1D77C, 'M', 'ν'), + (0x1D77D, 'M', 'ξ'), + (0x1D77E, 'M', 'ο'), + (0x1D77F, 'M', 'π'), + (0x1D780, 'M', 'ρ'), + (0x1D781, 'M', 'σ'), + (0x1D783, 'M', 'τ'), + (0x1D784, 'M', 'υ'), + (0x1D785, 'M', 'φ'), + (0x1D786, 'M', 'χ'), + (0x1D787, 'M', 'ψ'), + (0x1D788, 'M', 'ω'), + (0x1D789, 'M', '∂'), + (0x1D78A, 'M', 'ε'), + (0x1D78B, 'M', 'θ'), + (0x1D78C, 'M', 'κ'), + (0x1D78D, 'M', 'φ'), + (0x1D78E, 'M', 'ρ'), + (0x1D78F, 'M', 'π'), + (0x1D790, 'M', 'α'), + (0x1D791, 'M', 'β'), + (0x1D792, 'M', 'γ'), + (0x1D793, 'M', 'δ'), + (0x1D794, 'M', 'ε'), + (0x1D795, 'M', 'ζ'), + (0x1D796, 'M', 'η'), + (0x1D797, 'M', 'θ'), + (0x1D798, 'M', 'ι'), + (0x1D799, 'M', 'κ'), + (0x1D79A, 'M', 'λ'), + (0x1D79B, 'M', 'μ'), + (0x1D79C, 'M', 'ν'), + (0x1D79D, 'M', 'ξ'), + (0x1D79E, 'M', 'ο'), + (0x1D79F, 'M', 'π'), + (0x1D7A0, 'M', 'ρ'), + (0x1D7A1, 'M', 'θ'), + (0x1D7A2, 'M', 'σ'), + (0x1D7A3, 'M', 'τ'), + (0x1D7A4, 'M', 'υ'), + (0x1D7A5, 'M', 'φ'), + (0x1D7A6, 'M', 'χ'), + (0x1D7A7, 'M', 'ψ'), + (0x1D7A8, 'M', 'ω'), + (0x1D7A9, 'M', '∇'), + (0x1D7AA, 'M', 'α'), + (0x1D7AB, 'M', 'β'), + (0x1D7AC, 'M', 'γ'), + (0x1D7AD, 'M', 'δ'), + (0x1D7AE, 'M', 'ε'), + (0x1D7AF, 'M', 'ζ'), + (0x1D7B0, 'M', 'η'), + (0x1D7B1, 'M', 'θ'), + (0x1D7B2, 'M', 'ι'), + (0x1D7B3, 'M', 'κ'), + (0x1D7B4, 'M', 'λ'), + (0x1D7B5, 'M', 'μ'), + (0x1D7B6, 'M', 'ν'), + (0x1D7B7, 'M', 'ξ'), + (0x1D7B8, 'M', 'ο'), + (0x1D7B9, 'M', 'π'), + (0x1D7BA, 'M', 'ρ'), + (0x1D7BB, 'M', 'σ'), + (0x1D7BD, 'M', 'τ'), + (0x1D7BE, 'M', 'υ'), + (0x1D7BF, 'M', 'φ'), + (0x1D7C0, 'M', 'χ'), + (0x1D7C1, 'M', 'ψ'), + (0x1D7C2, 'M', 'ω'), + (0x1D7C3, 'M', '∂'), + (0x1D7C4, 'M', 'ε'), + (0x1D7C5, 'M', 'θ'), + (0x1D7C6, 'M', 'κ'), + (0x1D7C7, 'M', 'φ'), + (0x1D7C8, 'M', 'ρ'), + (0x1D7C9, 'M', 'π'), + (0x1D7CA, 'M', 'ϝ'), + (0x1D7CC, 'X'), + (0x1D7CE, 'M', '0'), + (0x1D7CF, 'M', '1'), + (0x1D7D0, 'M', '2'), + (0x1D7D1, 'M', '3'), + (0x1D7D2, 'M', '4'), + (0x1D7D3, 'M', '5'), + (0x1D7D4, 'M', '6'), + (0x1D7D5, 'M', '7'), + (0x1D7D6, 'M', '8'), + (0x1D7D7, 'M', '9'), + (0x1D7D8, 'M', '0'), + (0x1D7D9, 'M', '1'), + (0x1D7DA, 'M', '2'), + (0x1D7DB, 'M', '3'), + (0x1D7DC, 'M', '4'), + (0x1D7DD, 'M', '5'), + (0x1D7DE, 'M', '6'), + (0x1D7DF, 'M', '7'), + (0x1D7E0, 'M', '8'), + (0x1D7E1, 'M', '9'), + (0x1D7E2, 'M', '0'), + (0x1D7E3, 'M', '1'), + ] + +def _seg_69(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ + (0x1D7E4, 'M', '2'), + (0x1D7E5, 'M', '3'), + (0x1D7E6, 'M', '4'), + (0x1D7E7, 'M', '5'), + (0x1D7E8, 'M', '6'), + (0x1D7E9, 'M', '7'), + (0x1D7EA, 'M', '8'), + (0x1D7EB, 'M', '9'), + (0x1D7EC, 'M', '0'), + (0x1D7ED, 'M', '1'), + (0x1D7EE, 'M', '2'), + (0x1D7EF, 'M', '3'), + (0x1D7F0, 'M', '4'), + (0x1D7F1, 'M', '5'), + (0x1D7F2, 'M', '6'), + (0x1D7F3, 'M', '7'), + (0x1D7F4, 'M', '8'), + (0x1D7F5, 'M', '9'), + (0x1D7F6, 'M', '0'), + (0x1D7F7, 'M', '1'), + (0x1D7F8, 'M', '2'), + (0x1D7F9, 'M', '3'), + (0x1D7FA, 'M', '4'), + (0x1D7FB, 'M', '5'), + (0x1D7FC, 'M', '6'), + (0x1D7FD, 'M', '7'), + (0x1D7FE, 'M', '8'), + (0x1D7FF, 'M', '9'), (0x1D800, 'V'), (0x1DA8C, 'X'), (0x1DA9B, 'V'), @@ -7112,233 +7298,249 @@ def _seg_68(): (0x1E025, 'X'), (0x1E026, 'V'), (0x1E02B, 'X'), + (0x1E100, 'V'), + (0x1E12D, 'X'), + (0x1E130, 'V'), + (0x1E13E, 'X'), + (0x1E140, 'V'), + (0x1E14A, 'X'), + (0x1E14E, 'V'), + (0x1E150, 'X'), + (0x1E2C0, 'V'), + (0x1E2FA, 'X'), + (0x1E2FF, 'V'), + (0x1E300, 'X'), (0x1E800, 'V'), (0x1E8C5, 'X'), (0x1E8C7, 'V'), (0x1E8D7, 'X'), - (0x1E900, 'M', u'𞤢'), - (0x1E901, 'M', u'𞤣'), - (0x1E902, 'M', u'𞤤'), - (0x1E903, 'M', u'𞤥'), - (0x1E904, 'M', u'𞤦'), - (0x1E905, 'M', u'𞤧'), - (0x1E906, 'M', u'𞤨'), - (0x1E907, 'M', u'𞤩'), - (0x1E908, 'M', u'𞤪'), - (0x1E909, 'M', u'𞤫'), - (0x1E90A, 'M', u'𞤬'), - (0x1E90B, 'M', u'𞤭'), - (0x1E90C, 'M', u'𞤮'), - (0x1E90D, 'M', u'𞤯'), - (0x1E90E, 'M', u'𞤰'), - (0x1E90F, 'M', u'𞤱'), - (0x1E910, 'M', u'𞤲'), - (0x1E911, 'M', u'𞤳'), - (0x1E912, 'M', u'𞤴'), - (0x1E913, 'M', u'𞤵'), - (0x1E914, 'M', u'𞤶'), - (0x1E915, 'M', u'𞤷'), - (0x1E916, 'M', u'𞤸'), - (0x1E917, 'M', u'𞤹'), - (0x1E918, 'M', u'𞤺'), - (0x1E919, 'M', u'𞤻'), - (0x1E91A, 'M', u'𞤼'), - (0x1E91B, 'M', u'𞤽'), - (0x1E91C, 'M', u'𞤾'), - (0x1E91D, 'M', u'𞤿'), - (0x1E91E, 'M', u'𞥀'), - (0x1E91F, 'M', u'𞥁'), - (0x1E920, 'M', u'𞥂'), - (0x1E921, 'M', u'𞥃'), + (0x1E900, 'M', '𞤢'), + (0x1E901, 'M', '𞤣'), + (0x1E902, 'M', '𞤤'), + (0x1E903, 'M', '𞤥'), + (0x1E904, 'M', '𞤦'), + (0x1E905, 'M', '𞤧'), + (0x1E906, 'M', '𞤨'), + (0x1E907, 'M', '𞤩'), + (0x1E908, 'M', '𞤪'), + (0x1E909, 'M', '𞤫'), + (0x1E90A, 'M', '𞤬'), + (0x1E90B, 'M', '𞤭'), + (0x1E90C, 'M', '𞤮'), + (0x1E90D, 'M', '𞤯'), + (0x1E90E, 'M', '𞤰'), + (0x1E90F, 'M', '𞤱'), + (0x1E910, 'M', '𞤲'), + (0x1E911, 'M', '𞤳'), + (0x1E912, 'M', '𞤴'), + (0x1E913, 'M', '𞤵'), + (0x1E914, 'M', '𞤶'), + (0x1E915, 'M', '𞤷'), + (0x1E916, 'M', '𞤸'), + (0x1E917, 'M', '𞤹'), + (0x1E918, 'M', '𞤺'), + (0x1E919, 'M', '𞤻'), + (0x1E91A, 'M', '𞤼'), + (0x1E91B, 'M', '𞤽'), + (0x1E91C, 'M', '𞤾'), + (0x1E91D, 'M', '𞤿'), + (0x1E91E, 'M', '𞥀'), + (0x1E91F, 'M', '𞥁'), + (0x1E920, 'M', '𞥂'), + (0x1E921, 'M', '𞥃'), (0x1E922, 'V'), - (0x1E94B, 'X'), + (0x1E94C, 'X'), (0x1E950, 'V'), (0x1E95A, 'X'), (0x1E95E, 'V'), (0x1E960, 'X'), - (0x1EC71, 'V'), - (0x1ECB5, 'X'), - (0x1EE00, 'M', u'ا'), - (0x1EE01, 'M', u'ب'), - (0x1EE02, 'M', u'ج'), - (0x1EE03, 'M', u'د'), - (0x1EE04, 'X'), - (0x1EE05, 'M', u'و'), - (0x1EE06, 'M', u'ز'), - (0x1EE07, 'M', u'ح'), - (0x1EE08, 'M', u'ط'), - (0x1EE09, 'M', u'ي'), - (0x1EE0A, 'M', u'ك'), - (0x1EE0B, 'M', u'ل'), - (0x1EE0C, 'M', u'م'), - (0x1EE0D, 'M', u'ن'), - (0x1EE0E, 'M', u'س'), - (0x1EE0F, 'M', u'ع'), - (0x1EE10, 'M', u'ف'), - (0x1EE11, 'M', u'ص'), - (0x1EE12, 'M', u'ق'), - (0x1EE13, 'M', u'ر'), - (0x1EE14, 'M', u'ش'), - ] - -def _seg_69(): - return [ - (0x1EE15, 'M', u'ت'), - (0x1EE16, 'M', u'ث'), - (0x1EE17, 'M', u'خ'), - (0x1EE18, 'M', u'ذ'), - (0x1EE19, 'M', u'ض'), - (0x1EE1A, 'M', u'ظ'), - (0x1EE1B, 'M', u'غ'), - (0x1EE1C, 'M', u'ٮ'), - (0x1EE1D, 'M', u'ں'), - (0x1EE1E, 'M', u'ڡ'), - (0x1EE1F, 'M', u'ٯ'), - (0x1EE20, 'X'), - (0x1EE21, 'M', u'ب'), - (0x1EE22, 'M', u'ج'), - (0x1EE23, 'X'), - (0x1EE24, 'M', u'ه'), - (0x1EE25, 'X'), - (0x1EE27, 'M', u'ح'), - (0x1EE28, 'X'), - (0x1EE29, 'M', u'ي'), - (0x1EE2A, 'M', u'ك'), - (0x1EE2B, 'M', u'ل'), - (0x1EE2C, 'M', u'م'), - (0x1EE2D, 'M', u'ن'), - (0x1EE2E, 'M', u'س'), - (0x1EE2F, 'M', u'ع'), - (0x1EE30, 'M', u'ف'), - (0x1EE31, 'M', u'ص'), - (0x1EE32, 'M', u'ق'), - (0x1EE33, 'X'), - (0x1EE34, 'M', u'ش'), - (0x1EE35, 'M', u'ت'), - (0x1EE36, 'M', u'ث'), - (0x1EE37, 'M', u'خ'), - (0x1EE38, 'X'), - (0x1EE39, 'M', u'ض'), - (0x1EE3A, 'X'), - (0x1EE3B, 'M', u'غ'), - (0x1EE3C, 'X'), - (0x1EE42, 'M', u'ج'), - (0x1EE43, 'X'), - (0x1EE47, 'M', u'ح'), - (0x1EE48, 'X'), - (0x1EE49, 'M', u'ي'), - (0x1EE4A, 'X'), - (0x1EE4B, 'M', u'ل'), - (0x1EE4C, 'X'), - (0x1EE4D, 'M', u'ن'), - (0x1EE4E, 'M', u'س'), - (0x1EE4F, 'M', u'ع'), - (0x1EE50, 'X'), - (0x1EE51, 'M', u'ص'), - (0x1EE52, 'M', u'ق'), - (0x1EE53, 'X'), - (0x1EE54, 'M', u'ش'), - (0x1EE55, 'X'), - (0x1EE57, 'M', u'خ'), - (0x1EE58, 'X'), - (0x1EE59, 'M', u'ض'), - (0x1EE5A, 'X'), - (0x1EE5B, 'M', u'غ'), - (0x1EE5C, 'X'), - (0x1EE5D, 'M', u'ں'), - (0x1EE5E, 'X'), - (0x1EE5F, 'M', u'ٯ'), - (0x1EE60, 'X'), - (0x1EE61, 'M', u'ب'), - (0x1EE62, 'M', u'ج'), - (0x1EE63, 'X'), - (0x1EE64, 'M', u'ه'), - (0x1EE65, 'X'), - (0x1EE67, 'M', u'ح'), - (0x1EE68, 'M', u'ط'), - (0x1EE69, 'M', u'ي'), - (0x1EE6A, 'M', u'ك'), - (0x1EE6B, 'X'), - (0x1EE6C, 'M', u'م'), - (0x1EE6D, 'M', u'ن'), - (0x1EE6E, 'M', u'س'), - (0x1EE6F, 'M', u'ع'), - (0x1EE70, 'M', u'ف'), - (0x1EE71, 'M', u'ص'), - (0x1EE72, 'M', u'ق'), - (0x1EE73, 'X'), - (0x1EE74, 'M', u'ش'), - (0x1EE75, 'M', u'ت'), - (0x1EE76, 'M', u'ث'), - (0x1EE77, 'M', u'خ'), - (0x1EE78, 'X'), - (0x1EE79, 'M', u'ض'), - (0x1EE7A, 'M', u'ظ'), - (0x1EE7B, 'M', u'غ'), - (0x1EE7C, 'M', u'ٮ'), - (0x1EE7D, 'X'), - (0x1EE7E, 'M', u'ڡ'), - (0x1EE7F, 'X'), - (0x1EE80, 'M', u'ا'), - (0x1EE81, 'M', u'ب'), - (0x1EE82, 'M', u'ج'), - (0x1EE83, 'M', u'د'), ] def _seg_70(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x1EE84, 'M', u'ه'), - (0x1EE85, 'M', u'و'), - (0x1EE86, 'M', u'ز'), - (0x1EE87, 'M', u'ح'), - (0x1EE88, 'M', u'ط'), - (0x1EE89, 'M', u'ي'), + (0x1EC71, 'V'), + (0x1ECB5, 'X'), + (0x1ED01, 'V'), + (0x1ED3E, 'X'), + (0x1EE00, 'M', 'ا'), + (0x1EE01, 'M', 'ب'), + (0x1EE02, 'M', 'ج'), + (0x1EE03, 'M', 'د'), + (0x1EE04, 'X'), + (0x1EE05, 'M', 'و'), + (0x1EE06, 'M', 'ز'), + (0x1EE07, 'M', 'ح'), + (0x1EE08, 'M', 'ط'), + (0x1EE09, 'M', 'ي'), + (0x1EE0A, 'M', 'ك'), + (0x1EE0B, 'M', 'ل'), + (0x1EE0C, 'M', 'م'), + (0x1EE0D, 'M', 'ن'), + (0x1EE0E, 'M', 'س'), + (0x1EE0F, 'M', 'ع'), + (0x1EE10, 'M', 'ف'), + (0x1EE11, 'M', 'ص'), + (0x1EE12, 'M', 'ق'), + (0x1EE13, 'M', 'ر'), + (0x1EE14, 'M', 'ش'), + (0x1EE15, 'M', 'ت'), + (0x1EE16, 'M', 'ث'), + (0x1EE17, 'M', 'خ'), + (0x1EE18, 'M', 'ذ'), + (0x1EE19, 'M', 'ض'), + (0x1EE1A, 'M', 'ظ'), + (0x1EE1B, 'M', 'غ'), + (0x1EE1C, 'M', 'ٮ'), + (0x1EE1D, 'M', 'ں'), + (0x1EE1E, 'M', 'ڡ'), + (0x1EE1F, 'M', 'ٯ'), + (0x1EE20, 'X'), + (0x1EE21, 'M', 'ب'), + (0x1EE22, 'M', 'ج'), + (0x1EE23, 'X'), + (0x1EE24, 'M', 'ه'), + (0x1EE25, 'X'), + (0x1EE27, 'M', 'ح'), + (0x1EE28, 'X'), + (0x1EE29, 'M', 'ي'), + (0x1EE2A, 'M', 'ك'), + (0x1EE2B, 'M', 'ل'), + (0x1EE2C, 'M', 'م'), + (0x1EE2D, 'M', 'ن'), + (0x1EE2E, 'M', 'س'), + (0x1EE2F, 'M', 'ع'), + (0x1EE30, 'M', 'ف'), + (0x1EE31, 'M', 'ص'), + (0x1EE32, 'M', 'ق'), + (0x1EE33, 'X'), + (0x1EE34, 'M', 'ش'), + (0x1EE35, 'M', 'ت'), + (0x1EE36, 'M', 'ث'), + (0x1EE37, 'M', 'خ'), + (0x1EE38, 'X'), + (0x1EE39, 'M', 'ض'), + (0x1EE3A, 'X'), + (0x1EE3B, 'M', 'غ'), + (0x1EE3C, 'X'), + (0x1EE42, 'M', 'ج'), + (0x1EE43, 'X'), + (0x1EE47, 'M', 'ح'), + (0x1EE48, 'X'), + (0x1EE49, 'M', 'ي'), + (0x1EE4A, 'X'), + (0x1EE4B, 'M', 'ل'), + (0x1EE4C, 'X'), + (0x1EE4D, 'M', 'ن'), + (0x1EE4E, 'M', 'س'), + (0x1EE4F, 'M', 'ع'), + (0x1EE50, 'X'), + (0x1EE51, 'M', 'ص'), + (0x1EE52, 'M', 'ق'), + (0x1EE53, 'X'), + (0x1EE54, 'M', 'ش'), + (0x1EE55, 'X'), + (0x1EE57, 'M', 'خ'), + (0x1EE58, 'X'), + (0x1EE59, 'M', 'ض'), + (0x1EE5A, 'X'), + (0x1EE5B, 'M', 'غ'), + (0x1EE5C, 'X'), + (0x1EE5D, 'M', 'ں'), + (0x1EE5E, 'X'), + (0x1EE5F, 'M', 'ٯ'), + (0x1EE60, 'X'), + (0x1EE61, 'M', 'ب'), + (0x1EE62, 'M', 'ج'), + (0x1EE63, 'X'), + (0x1EE64, 'M', 'ه'), + (0x1EE65, 'X'), + (0x1EE67, 'M', 'ح'), + (0x1EE68, 'M', 'ط'), + (0x1EE69, 'M', 'ي'), + (0x1EE6A, 'M', 'ك'), + ] + +def _seg_71(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ + (0x1EE6B, 'X'), + (0x1EE6C, 'M', 'م'), + (0x1EE6D, 'M', 'ن'), + (0x1EE6E, 'M', 'س'), + (0x1EE6F, 'M', 'ع'), + (0x1EE70, 'M', 'ف'), + (0x1EE71, 'M', 'ص'), + (0x1EE72, 'M', 'ق'), + (0x1EE73, 'X'), + (0x1EE74, 'M', 'ش'), + (0x1EE75, 'M', 'ت'), + (0x1EE76, 'M', 'ث'), + (0x1EE77, 'M', 'خ'), + (0x1EE78, 'X'), + (0x1EE79, 'M', 'ض'), + (0x1EE7A, 'M', 'ظ'), + (0x1EE7B, 'M', 'غ'), + (0x1EE7C, 'M', 'ٮ'), + (0x1EE7D, 'X'), + (0x1EE7E, 'M', 'ڡ'), + (0x1EE7F, 'X'), + (0x1EE80, 'M', 'ا'), + (0x1EE81, 'M', 'ب'), + (0x1EE82, 'M', 'ج'), + (0x1EE83, 'M', 'د'), + (0x1EE84, 'M', 'ه'), + (0x1EE85, 'M', 'و'), + (0x1EE86, 'M', 'ز'), + (0x1EE87, 'M', 'ح'), + (0x1EE88, 'M', 'ط'), + (0x1EE89, 'M', 'ي'), (0x1EE8A, 'X'), - (0x1EE8B, 'M', u'ل'), - (0x1EE8C, 'M', u'م'), - (0x1EE8D, 'M', u'ن'), - (0x1EE8E, 'M', u'س'), - (0x1EE8F, 'M', u'ع'), - (0x1EE90, 'M', u'ف'), - (0x1EE91, 'M', u'ص'), - (0x1EE92, 'M', u'ق'), - (0x1EE93, 'M', u'ر'), - (0x1EE94, 'M', u'ش'), - (0x1EE95, 'M', u'ت'), - (0x1EE96, 'M', u'ث'), - (0x1EE97, 'M', u'خ'), - (0x1EE98, 'M', u'ذ'), - (0x1EE99, 'M', u'ض'), - (0x1EE9A, 'M', u'ظ'), - (0x1EE9B, 'M', u'غ'), + (0x1EE8B, 'M', 'ل'), + (0x1EE8C, 'M', 'م'), + (0x1EE8D, 'M', 'ن'), + (0x1EE8E, 'M', 'س'), + (0x1EE8F, 'M', 'ع'), + (0x1EE90, 'M', 'ف'), + (0x1EE91, 'M', 'ص'), + (0x1EE92, 'M', 'ق'), + (0x1EE93, 'M', 'ر'), + (0x1EE94, 'M', 'ش'), + (0x1EE95, 'M', 'ت'), + (0x1EE96, 'M', 'ث'), + (0x1EE97, 'M', 'خ'), + (0x1EE98, 'M', 'ذ'), + (0x1EE99, 'M', 'ض'), + (0x1EE9A, 'M', 'ظ'), + (0x1EE9B, 'M', 'غ'), (0x1EE9C, 'X'), - (0x1EEA1, 'M', u'ب'), - (0x1EEA2, 'M', u'ج'), - (0x1EEA3, 'M', u'د'), + (0x1EEA1, 'M', 'ب'), + (0x1EEA2, 'M', 'ج'), + (0x1EEA3, 'M', 'د'), (0x1EEA4, 'X'), - (0x1EEA5, 'M', u'و'), - (0x1EEA6, 'M', u'ز'), - (0x1EEA7, 'M', u'ح'), - (0x1EEA8, 'M', u'ط'), - (0x1EEA9, 'M', u'ي'), + (0x1EEA5, 'M', 'و'), + (0x1EEA6, 'M', 'ز'), + (0x1EEA7, 'M', 'ح'), + (0x1EEA8, 'M', 'ط'), + (0x1EEA9, 'M', 'ي'), (0x1EEAA, 'X'), - (0x1EEAB, 'M', u'ل'), - (0x1EEAC, 'M', u'م'), - (0x1EEAD, 'M', u'ن'), - (0x1EEAE, 'M', u'س'), - (0x1EEAF, 'M', u'ع'), - (0x1EEB0, 'M', u'ف'), - (0x1EEB1, 'M', u'ص'), - (0x1EEB2, 'M', u'ق'), - (0x1EEB3, 'M', u'ر'), - (0x1EEB4, 'M', u'ش'), - (0x1EEB5, 'M', u'ت'), - (0x1EEB6, 'M', u'ث'), - (0x1EEB7, 'M', u'خ'), - (0x1EEB8, 'M', u'ذ'), - (0x1EEB9, 'M', u'ض'), - (0x1EEBA, 'M', u'ظ'), - (0x1EEBB, 'M', u'غ'), + (0x1EEAB, 'M', 'ل'), + (0x1EEAC, 'M', 'م'), + (0x1EEAD, 'M', 'ن'), + (0x1EEAE, 'M', 'س'), + (0x1EEAF, 'M', 'ع'), + (0x1EEB0, 'M', 'ف'), + (0x1EEB1, 'M', 'ص'), + (0x1EEB2, 'M', 'ق'), + (0x1EEB3, 'M', 'ر'), + (0x1EEB4, 'M', 'ش'), + (0x1EEB5, 'M', 'ت'), + (0x1EEB6, 'M', 'ث'), + (0x1EEB7, 'M', 'خ'), + (0x1EEB8, 'M', 'ذ'), + (0x1EEB9, 'M', 'ض'), + (0x1EEBA, 'M', 'ظ'), + (0x1EEBB, 'M', 'غ'), (0x1EEBC, 'X'), (0x1EEF0, 'V'), (0x1EEF2, 'X'), @@ -7354,173 +7556,176 @@ def _seg_70(): (0x1F0D0, 'X'), (0x1F0D1, 'V'), (0x1F0F6, 'X'), - (0x1F101, '3', u'0,'), - (0x1F102, '3', u'1,'), - (0x1F103, '3', u'2,'), - (0x1F104, '3', u'3,'), - (0x1F105, '3', u'4,'), - (0x1F106, '3', u'5,'), - (0x1F107, '3', u'6,'), - (0x1F108, '3', u'7,'), - (0x1F109, '3', u'8,'), - (0x1F10A, '3', u'9,'), - (0x1F10B, 'V'), - (0x1F10D, 'X'), - (0x1F110, '3', u'(a)'), - (0x1F111, '3', u'(b)'), - (0x1F112, '3', u'(c)'), - (0x1F113, '3', u'(d)'), - (0x1F114, '3', u'(e)'), - (0x1F115, '3', u'(f)'), - (0x1F116, '3', u'(g)'), - (0x1F117, '3', u'(h)'), - (0x1F118, '3', u'(i)'), - (0x1F119, '3', u'(j)'), - (0x1F11A, '3', u'(k)'), - (0x1F11B, '3', u'(l)'), - (0x1F11C, '3', u'(m)'), - (0x1F11D, '3', u'(n)'), - (0x1F11E, '3', u'(o)'), - (0x1F11F, '3', u'(p)'), - (0x1F120, '3', u'(q)'), - (0x1F121, '3', u'(r)'), - (0x1F122, '3', u'(s)'), - (0x1F123, '3', u'(t)'), - (0x1F124, '3', u'(u)'), - ] - -def _seg_71(): - return [ - (0x1F125, '3', u'(v)'), - (0x1F126, '3', u'(w)'), - (0x1F127, '3', u'(x)'), - (0x1F128, '3', u'(y)'), - (0x1F129, '3', u'(z)'), - (0x1F12A, 'M', u'〔s〕'), - (0x1F12B, 'M', u'c'), - (0x1F12C, 'M', u'r'), - (0x1F12D, 'M', u'cd'), - (0x1F12E, 'M', u'wz'), - (0x1F12F, 'V'), - (0x1F130, 'M', u'a'), - (0x1F131, 'M', u'b'), - (0x1F132, 'M', u'c'), - (0x1F133, 'M', u'd'), - (0x1F134, 'M', u'e'), - (0x1F135, 'M', u'f'), - (0x1F136, 'M', u'g'), - (0x1F137, 'M', u'h'), - (0x1F138, 'M', u'i'), - (0x1F139, 'M', u'j'), - (0x1F13A, 'M', u'k'), - (0x1F13B, 'M', u'l'), - (0x1F13C, 'M', u'm'), - (0x1F13D, 'M', u'n'), - (0x1F13E, 'M', u'o'), - (0x1F13F, 'M', u'p'), - (0x1F140, 'M', u'q'), - (0x1F141, 'M', u'r'), - (0x1F142, 'M', u's'), - (0x1F143, 'M', u't'), - (0x1F144, 'M', u'u'), - (0x1F145, 'M', u'v'), - (0x1F146, 'M', u'w'), - (0x1F147, 'M', u'x'), - (0x1F148, 'M', u'y'), - (0x1F149, 'M', u'z'), - (0x1F14A, 'M', u'hv'), - (0x1F14B, 'M', u'mv'), - (0x1F14C, 'M', u'sd'), - (0x1F14D, 'M', u'ss'), - (0x1F14E, 'M', u'ppv'), - (0x1F14F, 'M', u'wc'), - (0x1F150, 'V'), - (0x1F16A, 'M', u'mc'), - (0x1F16B, 'M', u'md'), - (0x1F16C, 'X'), - (0x1F170, 'V'), - (0x1F190, 'M', u'dj'), - (0x1F191, 'V'), - (0x1F1AD, 'X'), - (0x1F1E6, 'V'), - (0x1F200, 'M', u'ほか'), - (0x1F201, 'M', u'ココ'), - (0x1F202, 'M', u'サ'), - (0x1F203, 'X'), - (0x1F210, 'M', u'手'), - (0x1F211, 'M', u'字'), - (0x1F212, 'M', u'双'), - (0x1F213, 'M', u'デ'), - (0x1F214, 'M', u'二'), - (0x1F215, 'M', u'多'), - (0x1F216, 'M', u'解'), - (0x1F217, 'M', u'天'), - (0x1F218, 'M', u'交'), - (0x1F219, 'M', u'映'), - (0x1F21A, 'M', u'無'), - (0x1F21B, 'M', u'料'), - (0x1F21C, 'M', u'前'), - (0x1F21D, 'M', u'後'), - (0x1F21E, 'M', u'再'), - (0x1F21F, 'M', u'新'), - (0x1F220, 'M', u'初'), - (0x1F221, 'M', u'終'), - (0x1F222, 'M', u'生'), - (0x1F223, 'M', u'販'), - (0x1F224, 'M', u'声'), - (0x1F225, 'M', u'吹'), - (0x1F226, 'M', u'演'), - (0x1F227, 'M', u'投'), - (0x1F228, 'M', u'捕'), - (0x1F229, 'M', u'一'), - (0x1F22A, 'M', u'三'), - (0x1F22B, 'M', u'遊'), - (0x1F22C, 'M', u'左'), - (0x1F22D, 'M', u'中'), - (0x1F22E, 'M', u'右'), - (0x1F22F, 'M', u'指'), - (0x1F230, 'M', u'走'), - (0x1F231, 'M', u'打'), - (0x1F232, 'M', u'禁'), - (0x1F233, 'M', u'空'), - (0x1F234, 'M', u'合'), - (0x1F235, 'M', u'満'), - (0x1F236, 'M', u'有'), - (0x1F237, 'M', u'月'), - (0x1F238, 'M', u'申'), - (0x1F239, 'M', u'割'), - (0x1F23A, 'M', u'営'), - (0x1F23B, 'M', u'配'), + (0x1F101, '3', '0,'), + (0x1F102, '3', '1,'), + (0x1F103, '3', '2,'), + (0x1F104, '3', '3,'), + (0x1F105, '3', '4,'), + (0x1F106, '3', '5,'), + (0x1F107, '3', '6,'), + (0x1F108, '3', '7,'), ] def _seg_72(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ + (0x1F109, '3', '8,'), + (0x1F10A, '3', '9,'), + (0x1F10B, 'V'), + (0x1F110, '3', '(a)'), + (0x1F111, '3', '(b)'), + (0x1F112, '3', '(c)'), + (0x1F113, '3', '(d)'), + (0x1F114, '3', '(e)'), + (0x1F115, '3', '(f)'), + (0x1F116, '3', '(g)'), + (0x1F117, '3', '(h)'), + (0x1F118, '3', '(i)'), + (0x1F119, '3', '(j)'), + (0x1F11A, '3', '(k)'), + (0x1F11B, '3', '(l)'), + (0x1F11C, '3', '(m)'), + (0x1F11D, '3', '(n)'), + (0x1F11E, '3', '(o)'), + (0x1F11F, '3', '(p)'), + (0x1F120, '3', '(q)'), + (0x1F121, '3', '(r)'), + (0x1F122, '3', '(s)'), + (0x1F123, '3', '(t)'), + (0x1F124, '3', '(u)'), + (0x1F125, '3', '(v)'), + (0x1F126, '3', '(w)'), + (0x1F127, '3', '(x)'), + (0x1F128, '3', '(y)'), + (0x1F129, '3', '(z)'), + (0x1F12A, 'M', '〔s〕'), + (0x1F12B, 'M', 'c'), + (0x1F12C, 'M', 'r'), + (0x1F12D, 'M', 'cd'), + (0x1F12E, 'M', 'wz'), + (0x1F12F, 'V'), + (0x1F130, 'M', 'a'), + (0x1F131, 'M', 'b'), + (0x1F132, 'M', 'c'), + (0x1F133, 'M', 'd'), + (0x1F134, 'M', 'e'), + (0x1F135, 'M', 'f'), + (0x1F136, 'M', 'g'), + (0x1F137, 'M', 'h'), + (0x1F138, 'M', 'i'), + (0x1F139, 'M', 'j'), + (0x1F13A, 'M', 'k'), + (0x1F13B, 'M', 'l'), + (0x1F13C, 'M', 'm'), + (0x1F13D, 'M', 'n'), + (0x1F13E, 'M', 'o'), + (0x1F13F, 'M', 'p'), + (0x1F140, 'M', 'q'), + (0x1F141, 'M', 'r'), + (0x1F142, 'M', 's'), + (0x1F143, 'M', 't'), + (0x1F144, 'M', 'u'), + (0x1F145, 'M', 'v'), + (0x1F146, 'M', 'w'), + (0x1F147, 'M', 'x'), + (0x1F148, 'M', 'y'), + (0x1F149, 'M', 'z'), + (0x1F14A, 'M', 'hv'), + (0x1F14B, 'M', 'mv'), + (0x1F14C, 'M', 'sd'), + (0x1F14D, 'M', 'ss'), + (0x1F14E, 'M', 'ppv'), + (0x1F14F, 'M', 'wc'), + (0x1F150, 'V'), + (0x1F16A, 'M', 'mc'), + (0x1F16B, 'M', 'md'), + (0x1F16C, 'M', 'mr'), + (0x1F16D, 'V'), + (0x1F190, 'M', 'dj'), + (0x1F191, 'V'), + (0x1F1AE, 'X'), + (0x1F1E6, 'V'), + (0x1F200, 'M', 'ほか'), + (0x1F201, 'M', 'ココ'), + (0x1F202, 'M', 'サ'), + (0x1F203, 'X'), + (0x1F210, 'M', '手'), + (0x1F211, 'M', '字'), + (0x1F212, 'M', '双'), + (0x1F213, 'M', 'デ'), + (0x1F214, 'M', '二'), + (0x1F215, 'M', '多'), + (0x1F216, 'M', '解'), + (0x1F217, 'M', '天'), + (0x1F218, 'M', '交'), + (0x1F219, 'M', '映'), + (0x1F21A, 'M', '無'), + (0x1F21B, 'M', '料'), + (0x1F21C, 'M', '前'), + (0x1F21D, 'M', '後'), + (0x1F21E, 'M', '再'), + (0x1F21F, 'M', '新'), + (0x1F220, 'M', '初'), + (0x1F221, 'M', '終'), + (0x1F222, 'M', '生'), + (0x1F223, 'M', '販'), + ] + +def _seg_73(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ + (0x1F224, 'M', '声'), + (0x1F225, 'M', '吹'), + (0x1F226, 'M', '演'), + (0x1F227, 'M', '投'), + (0x1F228, 'M', '捕'), + (0x1F229, 'M', '一'), + (0x1F22A, 'M', '三'), + (0x1F22B, 'M', '遊'), + (0x1F22C, 'M', '左'), + (0x1F22D, 'M', '中'), + (0x1F22E, 'M', '右'), + (0x1F22F, 'M', '指'), + (0x1F230, 'M', '走'), + (0x1F231, 'M', '打'), + (0x1F232, 'M', '禁'), + (0x1F233, 'M', '空'), + (0x1F234, 'M', '合'), + (0x1F235, 'M', '満'), + (0x1F236, 'M', '有'), + (0x1F237, 'M', '月'), + (0x1F238, 'M', '申'), + (0x1F239, 'M', '割'), + (0x1F23A, 'M', '営'), + (0x1F23B, 'M', '配'), (0x1F23C, 'X'), - (0x1F240, 'M', u'〔本〕'), - (0x1F241, 'M', u'〔三〕'), - (0x1F242, 'M', u'〔二〕'), - (0x1F243, 'M', u'〔安〕'), - (0x1F244, 'M', u'〔点〕'), - (0x1F245, 'M', u'〔打〕'), - (0x1F246, 'M', u'〔盗〕'), - (0x1F247, 'M', u'〔勝〕'), - (0x1F248, 'M', u'〔敗〕'), + (0x1F240, 'M', '〔本〕'), + (0x1F241, 'M', '〔三〕'), + (0x1F242, 'M', '〔二〕'), + (0x1F243, 'M', '〔安〕'), + (0x1F244, 'M', '〔点〕'), + (0x1F245, 'M', '〔打〕'), + (0x1F246, 'M', '〔盗〕'), + (0x1F247, 'M', '〔勝〕'), + (0x1F248, 'M', '〔敗〕'), (0x1F249, 'X'), - (0x1F250, 'M', u'得'), - (0x1F251, 'M', u'可'), + (0x1F250, 'M', '得'), + (0x1F251, 'M', '可'), (0x1F252, 'X'), (0x1F260, 'V'), (0x1F266, 'X'), (0x1F300, 'V'), - (0x1F6D5, 'X'), + (0x1F6D8, 'X'), (0x1F6E0, 'V'), (0x1F6ED, 'X'), (0x1F6F0, 'V'), - (0x1F6FA, 'X'), + (0x1F6FD, 'X'), (0x1F700, 'V'), (0x1F774, 'X'), (0x1F780, 'V'), (0x1F7D9, 'X'), + (0x1F7E0, 'V'), + (0x1F7EC, 'X'), (0x1F800, 'V'), (0x1F80C, 'X'), (0x1F810, 'V'), @@ -7531,28 +7736,52 @@ def _seg_72(): (0x1F888, 'X'), (0x1F890, 'V'), (0x1F8AE, 'X'), + (0x1F8B0, 'V'), + (0x1F8B2, 'X'), (0x1F900, 'V'), - (0x1F90C, 'X'), - (0x1F910, 'V'), - (0x1F93F, 'X'), - (0x1F940, 'V'), - (0x1F971, 'X'), - (0x1F973, 'V'), - (0x1F977, 'X'), + (0x1F979, 'X'), (0x1F97A, 'V'), - (0x1F97B, 'X'), - (0x1F97C, 'V'), - (0x1F9A3, 'X'), - (0x1F9B0, 'V'), - (0x1F9BA, 'X'), - (0x1F9C0, 'V'), - (0x1F9C3, 'X'), - (0x1F9D0, 'V'), - (0x1FA00, 'X'), + (0x1F9CC, 'X'), + (0x1F9CD, 'V'), + (0x1FA54, 'X'), (0x1FA60, 'V'), (0x1FA6E, 'X'), + (0x1FA70, 'V'), + (0x1FA75, 'X'), + (0x1FA78, 'V'), + (0x1FA7B, 'X'), + (0x1FA80, 'V'), + (0x1FA87, 'X'), + (0x1FA90, 'V'), + (0x1FAA9, 'X'), + (0x1FAB0, 'V'), + (0x1FAB7, 'X'), + (0x1FAC0, 'V'), + (0x1FAC3, 'X'), + (0x1FAD0, 'V'), + (0x1FAD7, 'X'), + (0x1FB00, 'V'), + (0x1FB93, 'X'), + (0x1FB94, 'V'), + (0x1FBCB, 'X'), + (0x1FBF0, 'M', '0'), + (0x1FBF1, 'M', '1'), + (0x1FBF2, 'M', '2'), + (0x1FBF3, 'M', '3'), + (0x1FBF4, 'M', '4'), + (0x1FBF5, 'M', '5'), + (0x1FBF6, 'M', '6'), + (0x1FBF7, 'M', '7'), + (0x1FBF8, 'M', '8'), + (0x1FBF9, 'M', '9'), + ] + +def _seg_74(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ + (0x1FBFA, 'X'), (0x20000, 'V'), - (0x2A6D7, 'X'), + (0x2A6DE, 'X'), (0x2A700, 'V'), (0x2B735, 'X'), (0x2B740, 'V'), @@ -7561,564 +7790,567 @@ def _seg_72(): (0x2CEA2, 'X'), (0x2CEB0, 'V'), (0x2EBE1, 'X'), - (0x2F800, 'M', u'丽'), - (0x2F801, 'M', u'丸'), - (0x2F802, 'M', u'乁'), - (0x2F803, 'M', u'𠄢'), - (0x2F804, 'M', u'你'), - (0x2F805, 'M', u'侮'), - (0x2F806, 'M', u'侻'), - (0x2F807, 'M', u'倂'), - (0x2F808, 'M', u'偺'), - (0x2F809, 'M', u'備'), - (0x2F80A, 'M', u'僧'), - (0x2F80B, 'M', u'像'), - (0x2F80C, 'M', u'㒞'), - (0x2F80D, 'M', u'𠘺'), - (0x2F80E, 'M', u'免'), - (0x2F80F, 'M', u'兔'), - (0x2F810, 'M', u'兤'), - (0x2F811, 'M', u'具'), - (0x2F812, 'M', u'𠔜'), - (0x2F813, 'M', u'㒹'), - (0x2F814, 'M', u'內'), - (0x2F815, 'M', u'再'), - (0x2F816, 'M', u'𠕋'), - (0x2F817, 'M', u'冗'), - (0x2F818, 'M', u'冤'), - (0x2F819, 'M', u'仌'), - (0x2F81A, 'M', u'冬'), - (0x2F81B, 'M', u'况'), - (0x2F81C, 'M', u'𩇟'), - (0x2F81D, 'M', u'凵'), - (0x2F81E, 'M', u'刃'), - (0x2F81F, 'M', u'㓟'), - (0x2F820, 'M', u'刻'), - (0x2F821, 'M', u'剆'), - ] - -def _seg_73(): - return [ - (0x2F822, 'M', u'割'), - (0x2F823, 'M', u'剷'), - (0x2F824, 'M', u'㔕'), - (0x2F825, 'M', u'勇'), - (0x2F826, 'M', u'勉'), - (0x2F827, 'M', u'勤'), - (0x2F828, 'M', u'勺'), - (0x2F829, 'M', u'包'), - (0x2F82A, 'M', u'匆'), - (0x2F82B, 'M', u'北'), - (0x2F82C, 'M', u'卉'), - (0x2F82D, 'M', u'卑'), - (0x2F82E, 'M', u'博'), - (0x2F82F, 'M', u'即'), - (0x2F830, 'M', u'卽'), - (0x2F831, 'M', u'卿'), - (0x2F834, 'M', u'𠨬'), - (0x2F835, 'M', u'灰'), - (0x2F836, 'M', u'及'), - (0x2F837, 'M', u'叟'), - (0x2F838, 'M', u'𠭣'), - (0x2F839, 'M', u'叫'), - (0x2F83A, 'M', u'叱'), - (0x2F83B, 'M', u'吆'), - (0x2F83C, 'M', u'咞'), - (0x2F83D, 'M', u'吸'), - (0x2F83E, 'M', u'呈'), - (0x2F83F, 'M', u'周'), - (0x2F840, 'M', u'咢'), - (0x2F841, 'M', u'哶'), - (0x2F842, 'M', u'唐'), - (0x2F843, 'M', u'啓'), - (0x2F844, 'M', u'啣'), - (0x2F845, 'M', u'善'), - (0x2F847, 'M', u'喙'), - (0x2F848, 'M', u'喫'), - (0x2F849, 'M', u'喳'), - (0x2F84A, 'M', u'嗂'), - (0x2F84B, 'M', u'圖'), - (0x2F84C, 'M', u'嘆'), - (0x2F84D, 'M', u'圗'), - (0x2F84E, 'M', u'噑'), - (0x2F84F, 'M', u'噴'), - (0x2F850, 'M', u'切'), - (0x2F851, 'M', u'壮'), - (0x2F852, 'M', u'城'), - (0x2F853, 'M', u'埴'), - (0x2F854, 'M', u'堍'), - (0x2F855, 'M', u'型'), - (0x2F856, 'M', u'堲'), - (0x2F857, 'M', u'報'), - (0x2F858, 'M', u'墬'), - (0x2F859, 'M', u'𡓤'), - (0x2F85A, 'M', u'売'), - (0x2F85B, 'M', u'壷'), - (0x2F85C, 'M', u'夆'), - (0x2F85D, 'M', u'多'), - (0x2F85E, 'M', u'夢'), - (0x2F85F, 'M', u'奢'), - (0x2F860, 'M', u'𡚨'), - (0x2F861, 'M', u'𡛪'), - (0x2F862, 'M', u'姬'), - (0x2F863, 'M', u'娛'), - (0x2F864, 'M', u'娧'), - (0x2F865, 'M', u'姘'), - (0x2F866, 'M', u'婦'), - (0x2F867, 'M', u'㛮'), - (0x2F868, 'X'), - (0x2F869, 'M', u'嬈'), - (0x2F86A, 'M', u'嬾'), - (0x2F86C, 'M', u'𡧈'), - (0x2F86D, 'M', u'寃'), - (0x2F86E, 'M', u'寘'), - (0x2F86F, 'M', u'寧'), - (0x2F870, 'M', u'寳'), - (0x2F871, 'M', u'𡬘'), - (0x2F872, 'M', u'寿'), - (0x2F873, 'M', u'将'), - (0x2F874, 'X'), - (0x2F875, 'M', u'尢'), - (0x2F876, 'M', u'㞁'), - (0x2F877, 'M', u'屠'), - (0x2F878, 'M', u'屮'), - (0x2F879, 'M', u'峀'), - (0x2F87A, 'M', u'岍'), - (0x2F87B, 'M', u'𡷤'), - (0x2F87C, 'M', u'嵃'), - (0x2F87D, 'M', u'𡷦'), - (0x2F87E, 'M', u'嵮'), - (0x2F87F, 'M', u'嵫'), - (0x2F880, 'M', u'嵼'), - (0x2F881, 'M', u'巡'), - (0x2F882, 'M', u'巢'), - (0x2F883, 'M', u'㠯'), - (0x2F884, 'M', u'巽'), - (0x2F885, 'M', u'帨'), - (0x2F886, 'M', u'帽'), - (0x2F887, 'M', u'幩'), - (0x2F888, 'M', u'㡢'), - (0x2F889, 'M', u'𢆃'), - ] - -def _seg_74(): - return [ - (0x2F88A, 'M', u'㡼'), - (0x2F88B, 'M', u'庰'), - (0x2F88C, 'M', u'庳'), - (0x2F88D, 'M', u'庶'), - (0x2F88E, 'M', u'廊'), - (0x2F88F, 'M', u'𪎒'), - (0x2F890, 'M', u'廾'), - (0x2F891, 'M', u'𢌱'), - (0x2F893, 'M', u'舁'), - (0x2F894, 'M', u'弢'), - (0x2F896, 'M', u'㣇'), - (0x2F897, 'M', u'𣊸'), - (0x2F898, 'M', u'𦇚'), - (0x2F899, 'M', u'形'), - (0x2F89A, 'M', u'彫'), - (0x2F89B, 'M', u'㣣'), - (0x2F89C, 'M', u'徚'), - (0x2F89D, 'M', u'忍'), - (0x2F89E, 'M', u'志'), - (0x2F89F, 'M', u'忹'), - (0x2F8A0, 'M', u'悁'), - (0x2F8A1, 'M', u'㤺'), - (0x2F8A2, 'M', u'㤜'), - (0x2F8A3, 'M', u'悔'), - (0x2F8A4, 'M', u'𢛔'), - (0x2F8A5, 'M', u'惇'), - (0x2F8A6, 'M', u'慈'), - (0x2F8A7, 'M', u'慌'), - (0x2F8A8, 'M', u'慎'), - (0x2F8A9, 'M', u'慌'), - (0x2F8AA, 'M', u'慺'), - (0x2F8AB, 'M', u'憎'), - (0x2F8AC, 'M', u'憲'), - (0x2F8AD, 'M', u'憤'), - (0x2F8AE, 'M', u'憯'), - (0x2F8AF, 'M', u'懞'), - (0x2F8B0, 'M', u'懲'), - (0x2F8B1, 'M', u'懶'), - (0x2F8B2, 'M', u'成'), - (0x2F8B3, 'M', u'戛'), - (0x2F8B4, 'M', u'扝'), - (0x2F8B5, 'M', u'抱'), - (0x2F8B6, 'M', u'拔'), - (0x2F8B7, 'M', u'捐'), - (0x2F8B8, 'M', u'𢬌'), - (0x2F8B9, 'M', u'挽'), - (0x2F8BA, 'M', u'拼'), - (0x2F8BB, 'M', u'捨'), - (0x2F8BC, 'M', u'掃'), - (0x2F8BD, 'M', u'揤'), - (0x2F8BE, 'M', u'𢯱'), - (0x2F8BF, 'M', u'搢'), - (0x2F8C0, 'M', u'揅'), - (0x2F8C1, 'M', u'掩'), - (0x2F8C2, 'M', u'㨮'), - (0x2F8C3, 'M', u'摩'), - (0x2F8C4, 'M', u'摾'), - (0x2F8C5, 'M', u'撝'), - (0x2F8C6, 'M', u'摷'), - (0x2F8C7, 'M', u'㩬'), - (0x2F8C8, 'M', u'敏'), - (0x2F8C9, 'M', u'敬'), - (0x2F8CA, 'M', u'𣀊'), - (0x2F8CB, 'M', u'旣'), - (0x2F8CC, 'M', u'書'), - (0x2F8CD, 'M', u'晉'), - (0x2F8CE, 'M', u'㬙'), - (0x2F8CF, 'M', u'暑'), - (0x2F8D0, 'M', u'㬈'), - (0x2F8D1, 'M', u'㫤'), - (0x2F8D2, 'M', u'冒'), - (0x2F8D3, 'M', u'冕'), - (0x2F8D4, 'M', u'最'), - (0x2F8D5, 'M', u'暜'), - (0x2F8D6, 'M', u'肭'), - (0x2F8D7, 'M', u'䏙'), - (0x2F8D8, 'M', u'朗'), - (0x2F8D9, 'M', u'望'), - (0x2F8DA, 'M', u'朡'), - (0x2F8DB, 'M', u'杞'), - (0x2F8DC, 'M', u'杓'), - (0x2F8DD, 'M', u'𣏃'), - (0x2F8DE, 'M', u'㭉'), - (0x2F8DF, 'M', u'柺'), - (0x2F8E0, 'M', u'枅'), - (0x2F8E1, 'M', u'桒'), - (0x2F8E2, 'M', u'梅'), - (0x2F8E3, 'M', u'𣑭'), - (0x2F8E4, 'M', u'梎'), - (0x2F8E5, 'M', u'栟'), - (0x2F8E6, 'M', u'椔'), - (0x2F8E7, 'M', u'㮝'), - (0x2F8E8, 'M', u'楂'), - (0x2F8E9, 'M', u'榣'), - (0x2F8EA, 'M', u'槪'), - (0x2F8EB, 'M', u'檨'), - (0x2F8EC, 'M', u'𣚣'), - (0x2F8ED, 'M', u'櫛'), - (0x2F8EE, 'M', u'㰘'), - (0x2F8EF, 'M', u'次'), + (0x2F800, 'M', '丽'), + (0x2F801, 'M', '丸'), + (0x2F802, 'M', '乁'), + (0x2F803, 'M', '𠄢'), + (0x2F804, 'M', '你'), + (0x2F805, 'M', '侮'), + (0x2F806, 'M', '侻'), + (0x2F807, 'M', '倂'), + (0x2F808, 'M', '偺'), + (0x2F809, 'M', '備'), + (0x2F80A, 'M', '僧'), + (0x2F80B, 'M', '像'), + (0x2F80C, 'M', '㒞'), + (0x2F80D, 'M', '𠘺'), + (0x2F80E, 'M', '免'), + (0x2F80F, 'M', '兔'), + (0x2F810, 'M', '兤'), + (0x2F811, 'M', '具'), + (0x2F812, 'M', '𠔜'), + (0x2F813, 'M', '㒹'), + (0x2F814, 'M', '內'), + (0x2F815, 'M', '再'), + (0x2F816, 'M', '𠕋'), + (0x2F817, 'M', '冗'), + (0x2F818, 'M', '冤'), + (0x2F819, 'M', '仌'), + (0x2F81A, 'M', '冬'), + (0x2F81B, 'M', '况'), + (0x2F81C, 'M', '𩇟'), + (0x2F81D, 'M', '凵'), + (0x2F81E, 'M', '刃'), + (0x2F81F, 'M', '㓟'), + (0x2F820, 'M', '刻'), + (0x2F821, 'M', '剆'), + (0x2F822, 'M', '割'), + (0x2F823, 'M', '剷'), + (0x2F824, 'M', '㔕'), + (0x2F825, 'M', '勇'), + (0x2F826, 'M', '勉'), + (0x2F827, 'M', '勤'), + (0x2F828, 'M', '勺'), + (0x2F829, 'M', '包'), + (0x2F82A, 'M', '匆'), + (0x2F82B, 'M', '北'), + (0x2F82C, 'M', '卉'), + (0x2F82D, 'M', '卑'), + (0x2F82E, 'M', '博'), + (0x2F82F, 'M', '即'), + (0x2F830, 'M', '卽'), + (0x2F831, 'M', '卿'), + (0x2F834, 'M', '𠨬'), + (0x2F835, 'M', '灰'), + (0x2F836, 'M', '及'), + (0x2F837, 'M', '叟'), + (0x2F838, 'M', '𠭣'), + (0x2F839, 'M', '叫'), + (0x2F83A, 'M', '叱'), + (0x2F83B, 'M', '吆'), + (0x2F83C, 'M', '咞'), + (0x2F83D, 'M', '吸'), + (0x2F83E, 'M', '呈'), + (0x2F83F, 'M', '周'), + (0x2F840, 'M', '咢'), + (0x2F841, 'M', '哶'), + (0x2F842, 'M', '唐'), + (0x2F843, 'M', '啓'), + (0x2F844, 'M', '啣'), + (0x2F845, 'M', '善'), + (0x2F847, 'M', '喙'), + (0x2F848, 'M', '喫'), + (0x2F849, 'M', '喳'), + (0x2F84A, 'M', '嗂'), + (0x2F84B, 'M', '圖'), + (0x2F84C, 'M', '嘆'), + (0x2F84D, 'M', '圗'), + (0x2F84E, 'M', '噑'), + (0x2F84F, 'M', '噴'), + (0x2F850, 'M', '切'), + (0x2F851, 'M', '壮'), + (0x2F852, 'M', '城'), + (0x2F853, 'M', '埴'), + (0x2F854, 'M', '堍'), + (0x2F855, 'M', '型'), + (0x2F856, 'M', '堲'), + (0x2F857, 'M', '報'), + (0x2F858, 'M', '墬'), + (0x2F859, 'M', '𡓤'), + (0x2F85A, 'M', '売'), + (0x2F85B, 'M', '壷'), ] def _seg_75(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x2F8F0, 'M', u'𣢧'), - (0x2F8F1, 'M', u'歔'), - (0x2F8F2, 'M', u'㱎'), - (0x2F8F3, 'M', u'歲'), - (0x2F8F4, 'M', u'殟'), - (0x2F8F5, 'M', u'殺'), - (0x2F8F6, 'M', u'殻'), - (0x2F8F7, 'M', u'𣪍'), - (0x2F8F8, 'M', u'𡴋'), - (0x2F8F9, 'M', u'𣫺'), - (0x2F8FA, 'M', u'汎'), - (0x2F8FB, 'M', u'𣲼'), - (0x2F8FC, 'M', u'沿'), - (0x2F8FD, 'M', u'泍'), - (0x2F8FE, 'M', u'汧'), - (0x2F8FF, 'M', u'洖'), - (0x2F900, 'M', u'派'), - (0x2F901, 'M', u'海'), - (0x2F902, 'M', u'流'), - (0x2F903, 'M', u'浩'), - (0x2F904, 'M', u'浸'), - (0x2F905, 'M', u'涅'), - (0x2F906, 'M', u'𣴞'), - (0x2F907, 'M', u'洴'), - (0x2F908, 'M', u'港'), - (0x2F909, 'M', u'湮'), - (0x2F90A, 'M', u'㴳'), - (0x2F90B, 'M', u'滋'), - (0x2F90C, 'M', u'滇'), - (0x2F90D, 'M', u'𣻑'), - (0x2F90E, 'M', u'淹'), - (0x2F90F, 'M', u'潮'), - (0x2F910, 'M', u'𣽞'), - (0x2F911, 'M', u'𣾎'), - (0x2F912, 'M', u'濆'), - (0x2F913, 'M', u'瀹'), - (0x2F914, 'M', u'瀞'), - (0x2F915, 'M', u'瀛'), - (0x2F916, 'M', u'㶖'), - (0x2F917, 'M', u'灊'), - (0x2F918, 'M', u'災'), - (0x2F919, 'M', u'灷'), - (0x2F91A, 'M', u'炭'), - (0x2F91B, 'M', u'𠔥'), - (0x2F91C, 'M', u'煅'), - (0x2F91D, 'M', u'𤉣'), - (0x2F91E, 'M', u'熜'), - (0x2F91F, 'X'), - (0x2F920, 'M', u'爨'), - (0x2F921, 'M', u'爵'), - (0x2F922, 'M', u'牐'), - (0x2F923, 'M', u'𤘈'), - (0x2F924, 'M', u'犀'), - (0x2F925, 'M', u'犕'), - (0x2F926, 'M', u'𤜵'), - (0x2F927, 'M', u'𤠔'), - (0x2F928, 'M', u'獺'), - (0x2F929, 'M', u'王'), - (0x2F92A, 'M', u'㺬'), - (0x2F92B, 'M', u'玥'), - (0x2F92C, 'M', u'㺸'), - (0x2F92E, 'M', u'瑇'), - (0x2F92F, 'M', u'瑜'), - (0x2F930, 'M', u'瑱'), - (0x2F931, 'M', u'璅'), - (0x2F932, 'M', u'瓊'), - (0x2F933, 'M', u'㼛'), - (0x2F934, 'M', u'甤'), - (0x2F935, 'M', u'𤰶'), - (0x2F936, 'M', u'甾'), - (0x2F937, 'M', u'𤲒'), - (0x2F938, 'M', u'異'), - (0x2F939, 'M', u'𢆟'), - (0x2F93A, 'M', u'瘐'), - (0x2F93B, 'M', u'𤾡'), - (0x2F93C, 'M', u'𤾸'), - (0x2F93D, 'M', u'𥁄'), - (0x2F93E, 'M', u'㿼'), - (0x2F93F, 'M', u'䀈'), - (0x2F940, 'M', u'直'), - (0x2F941, 'M', u'𥃳'), - (0x2F942, 'M', u'𥃲'), - (0x2F943, 'M', u'𥄙'), - (0x2F944, 'M', u'𥄳'), - (0x2F945, 'M', u'眞'), - (0x2F946, 'M', u'真'), - (0x2F948, 'M', u'睊'), - (0x2F949, 'M', u'䀹'), - (0x2F94A, 'M', u'瞋'), - (0x2F94B, 'M', u'䁆'), - (0x2F94C, 'M', u'䂖'), - (0x2F94D, 'M', u'𥐝'), - (0x2F94E, 'M', u'硎'), - (0x2F94F, 'M', u'碌'), - (0x2F950, 'M', u'磌'), - (0x2F951, 'M', u'䃣'), - (0x2F952, 'M', u'𥘦'), - (0x2F953, 'M', u'祖'), - (0x2F954, 'M', u'𥚚'), - (0x2F955, 'M', u'𥛅'), + (0x2F85C, 'M', '夆'), + (0x2F85D, 'M', '多'), + (0x2F85E, 'M', '夢'), + (0x2F85F, 'M', '奢'), + (0x2F860, 'M', '𡚨'), + (0x2F861, 'M', '𡛪'), + (0x2F862, 'M', '姬'), + (0x2F863, 'M', '娛'), + (0x2F864, 'M', '娧'), + (0x2F865, 'M', '姘'), + (0x2F866, 'M', '婦'), + (0x2F867, 'M', '㛮'), + (0x2F868, 'X'), + (0x2F869, 'M', '嬈'), + (0x2F86A, 'M', '嬾'), + (0x2F86C, 'M', '𡧈'), + (0x2F86D, 'M', '寃'), + (0x2F86E, 'M', '寘'), + (0x2F86F, 'M', '寧'), + (0x2F870, 'M', '寳'), + (0x2F871, 'M', '𡬘'), + (0x2F872, 'M', '寿'), + (0x2F873, 'M', '将'), + (0x2F874, 'X'), + (0x2F875, 'M', '尢'), + (0x2F876, 'M', '㞁'), + (0x2F877, 'M', '屠'), + (0x2F878, 'M', '屮'), + (0x2F879, 'M', '峀'), + (0x2F87A, 'M', '岍'), + (0x2F87B, 'M', '𡷤'), + (0x2F87C, 'M', '嵃'), + (0x2F87D, 'M', '𡷦'), + (0x2F87E, 'M', '嵮'), + (0x2F87F, 'M', '嵫'), + (0x2F880, 'M', '嵼'), + (0x2F881, 'M', '巡'), + (0x2F882, 'M', '巢'), + (0x2F883, 'M', '㠯'), + (0x2F884, 'M', '巽'), + (0x2F885, 'M', '帨'), + (0x2F886, 'M', '帽'), + (0x2F887, 'M', '幩'), + (0x2F888, 'M', '㡢'), + (0x2F889, 'M', '𢆃'), + (0x2F88A, 'M', '㡼'), + (0x2F88B, 'M', '庰'), + (0x2F88C, 'M', '庳'), + (0x2F88D, 'M', '庶'), + (0x2F88E, 'M', '廊'), + (0x2F88F, 'M', '𪎒'), + (0x2F890, 'M', '廾'), + (0x2F891, 'M', '𢌱'), + (0x2F893, 'M', '舁'), + (0x2F894, 'M', '弢'), + (0x2F896, 'M', '㣇'), + (0x2F897, 'M', '𣊸'), + (0x2F898, 'M', '𦇚'), + (0x2F899, 'M', '形'), + (0x2F89A, 'M', '彫'), + (0x2F89B, 'M', '㣣'), + (0x2F89C, 'M', '徚'), + (0x2F89D, 'M', '忍'), + (0x2F89E, 'M', '志'), + (0x2F89F, 'M', '忹'), + (0x2F8A0, 'M', '悁'), + (0x2F8A1, 'M', '㤺'), + (0x2F8A2, 'M', '㤜'), + (0x2F8A3, 'M', '悔'), + (0x2F8A4, 'M', '𢛔'), + (0x2F8A5, 'M', '惇'), + (0x2F8A6, 'M', '慈'), + (0x2F8A7, 'M', '慌'), + (0x2F8A8, 'M', '慎'), + (0x2F8A9, 'M', '慌'), + (0x2F8AA, 'M', '慺'), + (0x2F8AB, 'M', '憎'), + (0x2F8AC, 'M', '憲'), + (0x2F8AD, 'M', '憤'), + (0x2F8AE, 'M', '憯'), + (0x2F8AF, 'M', '懞'), + (0x2F8B0, 'M', '懲'), + (0x2F8B1, 'M', '懶'), + (0x2F8B2, 'M', '成'), + (0x2F8B3, 'M', '戛'), + (0x2F8B4, 'M', '扝'), + (0x2F8B5, 'M', '抱'), + (0x2F8B6, 'M', '拔'), + (0x2F8B7, 'M', '捐'), + (0x2F8B8, 'M', '𢬌'), + (0x2F8B9, 'M', '挽'), + (0x2F8BA, 'M', '拼'), + (0x2F8BB, 'M', '捨'), + (0x2F8BC, 'M', '掃'), + (0x2F8BD, 'M', '揤'), + (0x2F8BE, 'M', '𢯱'), + (0x2F8BF, 'M', '搢'), + (0x2F8C0, 'M', '揅'), + (0x2F8C1, 'M', '掩'), + (0x2F8C2, 'M', '㨮'), ] def _seg_76(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x2F956, 'M', u'福'), - (0x2F957, 'M', u'秫'), - (0x2F958, 'M', u'䄯'), - (0x2F959, 'M', u'穀'), - (0x2F95A, 'M', u'穊'), - (0x2F95B, 'M', u'穏'), - (0x2F95C, 'M', u'𥥼'), - (0x2F95D, 'M', u'𥪧'), - (0x2F95F, 'X'), - (0x2F960, 'M', u'䈂'), - (0x2F961, 'M', u'𥮫'), - (0x2F962, 'M', u'篆'), - (0x2F963, 'M', u'築'), - (0x2F964, 'M', u'䈧'), - (0x2F965, 'M', u'𥲀'), - (0x2F966, 'M', u'糒'), - (0x2F967, 'M', u'䊠'), - (0x2F968, 'M', u'糨'), - (0x2F969, 'M', u'糣'), - (0x2F96A, 'M', u'紀'), - (0x2F96B, 'M', u'𥾆'), - (0x2F96C, 'M', u'絣'), - (0x2F96D, 'M', u'䌁'), - (0x2F96E, 'M', u'緇'), - (0x2F96F, 'M', u'縂'), - (0x2F970, 'M', u'繅'), - (0x2F971, 'M', u'䌴'), - (0x2F972, 'M', u'𦈨'), - (0x2F973, 'M', u'𦉇'), - (0x2F974, 'M', u'䍙'), - (0x2F975, 'M', u'𦋙'), - (0x2F976, 'M', u'罺'), - (0x2F977, 'M', u'𦌾'), - (0x2F978, 'M', u'羕'), - (0x2F979, 'M', u'翺'), - (0x2F97A, 'M', u'者'), - (0x2F97B, 'M', u'𦓚'), - (0x2F97C, 'M', u'𦔣'), - (0x2F97D, 'M', u'聠'), - (0x2F97E, 'M', u'𦖨'), - (0x2F97F, 'M', u'聰'), - (0x2F980, 'M', u'𣍟'), - (0x2F981, 'M', u'䏕'), - (0x2F982, 'M', u'育'), - (0x2F983, 'M', u'脃'), - (0x2F984, 'M', u'䐋'), - (0x2F985, 'M', u'脾'), - (0x2F986, 'M', u'媵'), - (0x2F987, 'M', u'𦞧'), - (0x2F988, 'M', u'𦞵'), - (0x2F989, 'M', u'𣎓'), - (0x2F98A, 'M', u'𣎜'), - (0x2F98B, 'M', u'舁'), - (0x2F98C, 'M', u'舄'), - (0x2F98D, 'M', u'辞'), - (0x2F98E, 'M', u'䑫'), - (0x2F98F, 'M', u'芑'), - (0x2F990, 'M', u'芋'), - (0x2F991, 'M', u'芝'), - (0x2F992, 'M', u'劳'), - (0x2F993, 'M', u'花'), - (0x2F994, 'M', u'芳'), - (0x2F995, 'M', u'芽'), - (0x2F996, 'M', u'苦'), - (0x2F997, 'M', u'𦬼'), - (0x2F998, 'M', u'若'), - (0x2F999, 'M', u'茝'), - (0x2F99A, 'M', u'荣'), - (0x2F99B, 'M', u'莭'), - (0x2F99C, 'M', u'茣'), - (0x2F99D, 'M', u'莽'), - (0x2F99E, 'M', u'菧'), - (0x2F99F, 'M', u'著'), - (0x2F9A0, 'M', u'荓'), - (0x2F9A1, 'M', u'菊'), - (0x2F9A2, 'M', u'菌'), - (0x2F9A3, 'M', u'菜'), - (0x2F9A4, 'M', u'𦰶'), - (0x2F9A5, 'M', u'𦵫'), - (0x2F9A6, 'M', u'𦳕'), - (0x2F9A7, 'M', u'䔫'), - (0x2F9A8, 'M', u'蓱'), - (0x2F9A9, 'M', u'蓳'), - (0x2F9AA, 'M', u'蔖'), - (0x2F9AB, 'M', u'𧏊'), - (0x2F9AC, 'M', u'蕤'), - (0x2F9AD, 'M', u'𦼬'), - (0x2F9AE, 'M', u'䕝'), - (0x2F9AF, 'M', u'䕡'), - (0x2F9B0, 'M', u'𦾱'), - (0x2F9B1, 'M', u'𧃒'), - (0x2F9B2, 'M', u'䕫'), - (0x2F9B3, 'M', u'虐'), - (0x2F9B4, 'M', u'虜'), - (0x2F9B5, 'M', u'虧'), - (0x2F9B6, 'M', u'虩'), - (0x2F9B7, 'M', u'蚩'), - (0x2F9B8, 'M', u'蚈'), - (0x2F9B9, 'M', u'蜎'), - (0x2F9BA, 'M', u'蛢'), + (0x2F8C3, 'M', '摩'), + (0x2F8C4, 'M', '摾'), + (0x2F8C5, 'M', '撝'), + (0x2F8C6, 'M', '摷'), + (0x2F8C7, 'M', '㩬'), + (0x2F8C8, 'M', '敏'), + (0x2F8C9, 'M', '敬'), + (0x2F8CA, 'M', '𣀊'), + (0x2F8CB, 'M', '旣'), + (0x2F8CC, 'M', '書'), + (0x2F8CD, 'M', '晉'), + (0x2F8CE, 'M', '㬙'), + (0x2F8CF, 'M', '暑'), + (0x2F8D0, 'M', '㬈'), + (0x2F8D1, 'M', '㫤'), + (0x2F8D2, 'M', '冒'), + (0x2F8D3, 'M', '冕'), + (0x2F8D4, 'M', '最'), + (0x2F8D5, 'M', '暜'), + (0x2F8D6, 'M', '肭'), + (0x2F8D7, 'M', '䏙'), + (0x2F8D8, 'M', '朗'), + (0x2F8D9, 'M', '望'), + (0x2F8DA, 'M', '朡'), + (0x2F8DB, 'M', '杞'), + (0x2F8DC, 'M', '杓'), + (0x2F8DD, 'M', '𣏃'), + (0x2F8DE, 'M', '㭉'), + (0x2F8DF, 'M', '柺'), + (0x2F8E0, 'M', '枅'), + (0x2F8E1, 'M', '桒'), + (0x2F8E2, 'M', '梅'), + (0x2F8E3, 'M', '𣑭'), + (0x2F8E4, 'M', '梎'), + (0x2F8E5, 'M', '栟'), + (0x2F8E6, 'M', '椔'), + (0x2F8E7, 'M', '㮝'), + (0x2F8E8, 'M', '楂'), + (0x2F8E9, 'M', '榣'), + (0x2F8EA, 'M', '槪'), + (0x2F8EB, 'M', '檨'), + (0x2F8EC, 'M', '𣚣'), + (0x2F8ED, 'M', '櫛'), + (0x2F8EE, 'M', '㰘'), + (0x2F8EF, 'M', '次'), + (0x2F8F0, 'M', '𣢧'), + (0x2F8F1, 'M', '歔'), + (0x2F8F2, 'M', '㱎'), + (0x2F8F3, 'M', '歲'), + (0x2F8F4, 'M', '殟'), + (0x2F8F5, 'M', '殺'), + (0x2F8F6, 'M', '殻'), + (0x2F8F7, 'M', '𣪍'), + (0x2F8F8, 'M', '𡴋'), + (0x2F8F9, 'M', '𣫺'), + (0x2F8FA, 'M', '汎'), + (0x2F8FB, 'M', '𣲼'), + (0x2F8FC, 'M', '沿'), + (0x2F8FD, 'M', '泍'), + (0x2F8FE, 'M', '汧'), + (0x2F8FF, 'M', '洖'), + (0x2F900, 'M', '派'), + (0x2F901, 'M', '海'), + (0x2F902, 'M', '流'), + (0x2F903, 'M', '浩'), + (0x2F904, 'M', '浸'), + (0x2F905, 'M', '涅'), + (0x2F906, 'M', '𣴞'), + (0x2F907, 'M', '洴'), + (0x2F908, 'M', '港'), + (0x2F909, 'M', '湮'), + (0x2F90A, 'M', '㴳'), + (0x2F90B, 'M', '滋'), + (0x2F90C, 'M', '滇'), + (0x2F90D, 'M', '𣻑'), + (0x2F90E, 'M', '淹'), + (0x2F90F, 'M', '潮'), + (0x2F910, 'M', '𣽞'), + (0x2F911, 'M', '𣾎'), + (0x2F912, 'M', '濆'), + (0x2F913, 'M', '瀹'), + (0x2F914, 'M', '瀞'), + (0x2F915, 'M', '瀛'), + (0x2F916, 'M', '㶖'), + (0x2F917, 'M', '灊'), + (0x2F918, 'M', '災'), + (0x2F919, 'M', '灷'), + (0x2F91A, 'M', '炭'), + (0x2F91B, 'M', '𠔥'), + (0x2F91C, 'M', '煅'), + (0x2F91D, 'M', '𤉣'), + (0x2F91E, 'M', '熜'), + (0x2F91F, 'X'), + (0x2F920, 'M', '爨'), + (0x2F921, 'M', '爵'), + (0x2F922, 'M', '牐'), + (0x2F923, 'M', '𤘈'), + (0x2F924, 'M', '犀'), + (0x2F925, 'M', '犕'), + (0x2F926, 'M', '𤜵'), ] def _seg_77(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ - (0x2F9BB, 'M', u'蝹'), - (0x2F9BC, 'M', u'蜨'), - (0x2F9BD, 'M', u'蝫'), - (0x2F9BE, 'M', u'螆'), - (0x2F9BF, 'X'), - (0x2F9C0, 'M', u'蟡'), - (0x2F9C1, 'M', u'蠁'), - (0x2F9C2, 'M', u'䗹'), - (0x2F9C3, 'M', u'衠'), - (0x2F9C4, 'M', u'衣'), - (0x2F9C5, 'M', u'𧙧'), - (0x2F9C6, 'M', u'裗'), - (0x2F9C7, 'M', u'裞'), - (0x2F9C8, 'M', u'䘵'), - (0x2F9C9, 'M', u'裺'), - (0x2F9CA, 'M', u'㒻'), - (0x2F9CB, 'M', u'𧢮'), - (0x2F9CC, 'M', u'𧥦'), - (0x2F9CD, 'M', u'䚾'), - (0x2F9CE, 'M', u'䛇'), - (0x2F9CF, 'M', u'誠'), - (0x2F9D0, 'M', u'諭'), - (0x2F9D1, 'M', u'變'), - (0x2F9D2, 'M', u'豕'), - (0x2F9D3, 'M', u'𧲨'), - (0x2F9D4, 'M', u'貫'), - (0x2F9D5, 'M', u'賁'), - (0x2F9D6, 'M', u'贛'), - (0x2F9D7, 'M', u'起'), - (0x2F9D8, 'M', u'𧼯'), - (0x2F9D9, 'M', u'𠠄'), - (0x2F9DA, 'M', u'跋'), - (0x2F9DB, 'M', u'趼'), - (0x2F9DC, 'M', u'跰'), - (0x2F9DD, 'M', u'𠣞'), - (0x2F9DE, 'M', u'軔'), - (0x2F9DF, 'M', u'輸'), - (0x2F9E0, 'M', u'𨗒'), - (0x2F9E1, 'M', u'𨗭'), - (0x2F9E2, 'M', u'邔'), - (0x2F9E3, 'M', u'郱'), - (0x2F9E4, 'M', u'鄑'), - (0x2F9E5, 'M', u'𨜮'), - (0x2F9E6, 'M', u'鄛'), - (0x2F9E7, 'M', u'鈸'), - (0x2F9E8, 'M', u'鋗'), - (0x2F9E9, 'M', u'鋘'), - (0x2F9EA, 'M', u'鉼'), - (0x2F9EB, 'M', u'鏹'), - (0x2F9EC, 'M', u'鐕'), - (0x2F9ED, 'M', u'𨯺'), - (0x2F9EE, 'M', u'開'), - (0x2F9EF, 'M', u'䦕'), - (0x2F9F0, 'M', u'閷'), - (0x2F9F1, 'M', u'𨵷'), - (0x2F9F2, 'M', u'䧦'), - (0x2F9F3, 'M', u'雃'), - (0x2F9F4, 'M', u'嶲'), - (0x2F9F5, 'M', u'霣'), - (0x2F9F6, 'M', u'𩅅'), - (0x2F9F7, 'M', u'𩈚'), - (0x2F9F8, 'M', u'䩮'), - (0x2F9F9, 'M', u'䩶'), - (0x2F9FA, 'M', u'韠'), - (0x2F9FB, 'M', u'𩐊'), - (0x2F9FC, 'M', u'䪲'), - (0x2F9FD, 'M', u'𩒖'), - (0x2F9FE, 'M', u'頋'), - (0x2FA00, 'M', u'頩'), - (0x2FA01, 'M', u'𩖶'), - (0x2FA02, 'M', u'飢'), - (0x2FA03, 'M', u'䬳'), - (0x2FA04, 'M', u'餩'), - (0x2FA05, 'M', u'馧'), - (0x2FA06, 'M', u'駂'), - (0x2FA07, 'M', u'駾'), - (0x2FA08, 'M', u'䯎'), - (0x2FA09, 'M', u'𩬰'), - (0x2FA0A, 'M', u'鬒'), - (0x2FA0B, 'M', u'鱀'), - (0x2FA0C, 'M', u'鳽'), - (0x2FA0D, 'M', u'䳎'), - (0x2FA0E, 'M', u'䳭'), - (0x2FA0F, 'M', u'鵧'), - (0x2FA10, 'M', u'𪃎'), - (0x2FA11, 'M', u'䳸'), - (0x2FA12, 'M', u'𪄅'), - (0x2FA13, 'M', u'𪈎'), - (0x2FA14, 'M', u'𪊑'), - (0x2FA15, 'M', u'麻'), - (0x2FA16, 'M', u'䵖'), - (0x2FA17, 'M', u'黹'), - (0x2FA18, 'M', u'黾'), - (0x2FA19, 'M', u'鼅'), - (0x2FA1A, 'M', u'鼏'), - (0x2FA1B, 'M', u'鼖'), - (0x2FA1C, 'M', u'鼻'), - (0x2FA1D, 'M', u'𪘀'), - (0x2FA1E, 'X'), - (0xE0100, 'I'), + (0x2F927, 'M', '𤠔'), + (0x2F928, 'M', '獺'), + (0x2F929, 'M', '王'), + (0x2F92A, 'M', '㺬'), + (0x2F92B, 'M', '玥'), + (0x2F92C, 'M', '㺸'), + (0x2F92E, 'M', '瑇'), + (0x2F92F, 'M', '瑜'), + (0x2F930, 'M', '瑱'), + (0x2F931, 'M', '璅'), + (0x2F932, 'M', '瓊'), + (0x2F933, 'M', '㼛'), + (0x2F934, 'M', '甤'), + (0x2F935, 'M', '𤰶'), + (0x2F936, 'M', '甾'), + (0x2F937, 'M', '𤲒'), + (0x2F938, 'M', '異'), + (0x2F939, 'M', '𢆟'), + (0x2F93A, 'M', '瘐'), + (0x2F93B, 'M', '𤾡'), + (0x2F93C, 'M', '𤾸'), + (0x2F93D, 'M', '𥁄'), + (0x2F93E, 'M', '㿼'), + (0x2F93F, 'M', '䀈'), + (0x2F940, 'M', '直'), + (0x2F941, 'M', '𥃳'), + (0x2F942, 'M', '𥃲'), + (0x2F943, 'M', '𥄙'), + (0x2F944, 'M', '𥄳'), + (0x2F945, 'M', '眞'), + (0x2F946, 'M', '真'), + (0x2F948, 'M', '睊'), + (0x2F949, 'M', '䀹'), + (0x2F94A, 'M', '瞋'), + (0x2F94B, 'M', '䁆'), + (0x2F94C, 'M', '䂖'), + (0x2F94D, 'M', '𥐝'), + (0x2F94E, 'M', '硎'), + (0x2F94F, 'M', '碌'), + (0x2F950, 'M', '磌'), + (0x2F951, 'M', '䃣'), + (0x2F952, 'M', '𥘦'), + (0x2F953, 'M', '祖'), + (0x2F954, 'M', '𥚚'), + (0x2F955, 'M', '𥛅'), + (0x2F956, 'M', '福'), + (0x2F957, 'M', '秫'), + (0x2F958, 'M', '䄯'), + (0x2F959, 'M', '穀'), + (0x2F95A, 'M', '穊'), + (0x2F95B, 'M', '穏'), + (0x2F95C, 'M', '𥥼'), + (0x2F95D, 'M', '𥪧'), + (0x2F95F, 'X'), + (0x2F960, 'M', '䈂'), + (0x2F961, 'M', '𥮫'), + (0x2F962, 'M', '篆'), + (0x2F963, 'M', '築'), + (0x2F964, 'M', '䈧'), + (0x2F965, 'M', '𥲀'), + (0x2F966, 'M', '糒'), + (0x2F967, 'M', '䊠'), + (0x2F968, 'M', '糨'), + (0x2F969, 'M', '糣'), + (0x2F96A, 'M', '紀'), + (0x2F96B, 'M', '𥾆'), + (0x2F96C, 'M', '絣'), + (0x2F96D, 'M', '䌁'), + (0x2F96E, 'M', '緇'), + (0x2F96F, 'M', '縂'), + (0x2F970, 'M', '繅'), + (0x2F971, 'M', '䌴'), + (0x2F972, 'M', '𦈨'), + (0x2F973, 'M', '𦉇'), + (0x2F974, 'M', '䍙'), + (0x2F975, 'M', '𦋙'), + (0x2F976, 'M', '罺'), + (0x2F977, 'M', '𦌾'), + (0x2F978, 'M', '羕'), + (0x2F979, 'M', '翺'), + (0x2F97A, 'M', '者'), + (0x2F97B, 'M', '𦓚'), + (0x2F97C, 'M', '𦔣'), + (0x2F97D, 'M', '聠'), + (0x2F97E, 'M', '𦖨'), + (0x2F97F, 'M', '聰'), + (0x2F980, 'M', '𣍟'), + (0x2F981, 'M', '䏕'), + (0x2F982, 'M', '育'), + (0x2F983, 'M', '脃'), + (0x2F984, 'M', '䐋'), + (0x2F985, 'M', '脾'), + (0x2F986, 'M', '媵'), + (0x2F987, 'M', '𦞧'), + (0x2F988, 'M', '𦞵'), + (0x2F989, 'M', '𣎓'), + (0x2F98A, 'M', '𣎜'), + (0x2F98B, 'M', '舁'), + (0x2F98C, 'M', '舄'), + (0x2F98D, 'M', '辞'), ] def _seg_78(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] return [ + (0x2F98E, 'M', '䑫'), + (0x2F98F, 'M', '芑'), + (0x2F990, 'M', '芋'), + (0x2F991, 'M', '芝'), + (0x2F992, 'M', '劳'), + (0x2F993, 'M', '花'), + (0x2F994, 'M', '芳'), + (0x2F995, 'M', '芽'), + (0x2F996, 'M', '苦'), + (0x2F997, 'M', '𦬼'), + (0x2F998, 'M', '若'), + (0x2F999, 'M', '茝'), + (0x2F99A, 'M', '荣'), + (0x2F99B, 'M', '莭'), + (0x2F99C, 'M', '茣'), + (0x2F99D, 'M', '莽'), + (0x2F99E, 'M', '菧'), + (0x2F99F, 'M', '著'), + (0x2F9A0, 'M', '荓'), + (0x2F9A1, 'M', '菊'), + (0x2F9A2, 'M', '菌'), + (0x2F9A3, 'M', '菜'), + (0x2F9A4, 'M', '𦰶'), + (0x2F9A5, 'M', '𦵫'), + (0x2F9A6, 'M', '𦳕'), + (0x2F9A7, 'M', '䔫'), + (0x2F9A8, 'M', '蓱'), + (0x2F9A9, 'M', '蓳'), + (0x2F9AA, 'M', '蔖'), + (0x2F9AB, 'M', '𧏊'), + (0x2F9AC, 'M', '蕤'), + (0x2F9AD, 'M', '𦼬'), + (0x2F9AE, 'M', '䕝'), + (0x2F9AF, 'M', '䕡'), + (0x2F9B0, 'M', '𦾱'), + (0x2F9B1, 'M', '𧃒'), + (0x2F9B2, 'M', '䕫'), + (0x2F9B3, 'M', '虐'), + (0x2F9B4, 'M', '虜'), + (0x2F9B5, 'M', '虧'), + (0x2F9B6, 'M', '虩'), + (0x2F9B7, 'M', '蚩'), + (0x2F9B8, 'M', '蚈'), + (0x2F9B9, 'M', '蜎'), + (0x2F9BA, 'M', '蛢'), + (0x2F9BB, 'M', '蝹'), + (0x2F9BC, 'M', '蜨'), + (0x2F9BD, 'M', '蝫'), + (0x2F9BE, 'M', '螆'), + (0x2F9BF, 'X'), + (0x2F9C0, 'M', '蟡'), + (0x2F9C1, 'M', '蠁'), + (0x2F9C2, 'M', '䗹'), + (0x2F9C3, 'M', '衠'), + (0x2F9C4, 'M', '衣'), + (0x2F9C5, 'M', '𧙧'), + (0x2F9C6, 'M', '裗'), + (0x2F9C7, 'M', '裞'), + (0x2F9C8, 'M', '䘵'), + (0x2F9C9, 'M', '裺'), + (0x2F9CA, 'M', '㒻'), + (0x2F9CB, 'M', '𧢮'), + (0x2F9CC, 'M', '𧥦'), + (0x2F9CD, 'M', '䚾'), + (0x2F9CE, 'M', '䛇'), + (0x2F9CF, 'M', '誠'), + (0x2F9D0, 'M', '諭'), + (0x2F9D1, 'M', '變'), + (0x2F9D2, 'M', '豕'), + (0x2F9D3, 'M', '𧲨'), + (0x2F9D4, 'M', '貫'), + (0x2F9D5, 'M', '賁'), + (0x2F9D6, 'M', '贛'), + (0x2F9D7, 'M', '起'), + (0x2F9D8, 'M', '𧼯'), + (0x2F9D9, 'M', '𠠄'), + (0x2F9DA, 'M', '跋'), + (0x2F9DB, 'M', '趼'), + (0x2F9DC, 'M', '跰'), + (0x2F9DD, 'M', '𠣞'), + (0x2F9DE, 'M', '軔'), + (0x2F9DF, 'M', '輸'), + (0x2F9E0, 'M', '𨗒'), + (0x2F9E1, 'M', '𨗭'), + (0x2F9E2, 'M', '邔'), + (0x2F9E3, 'M', '郱'), + (0x2F9E4, 'M', '鄑'), + (0x2F9E5, 'M', '𨜮'), + (0x2F9E6, 'M', '鄛'), + (0x2F9E7, 'M', '鈸'), + (0x2F9E8, 'M', '鋗'), + (0x2F9E9, 'M', '鋘'), + (0x2F9EA, 'M', '鉼'), + (0x2F9EB, 'M', '鏹'), + (0x2F9EC, 'M', '鐕'), + (0x2F9ED, 'M', '𨯺'), + (0x2F9EE, 'M', '開'), + (0x2F9EF, 'M', '䦕'), + (0x2F9F0, 'M', '閷'), + (0x2F9F1, 'M', '𨵷'), + ] + +def _seg_79(): + # type: () -> List[Union[Tuple[int, str], Tuple[int, str, str]]] + return [ + (0x2F9F2, 'M', '䧦'), + (0x2F9F3, 'M', '雃'), + (0x2F9F4, 'M', '嶲'), + (0x2F9F5, 'M', '霣'), + (0x2F9F6, 'M', '𩅅'), + (0x2F9F7, 'M', '𩈚'), + (0x2F9F8, 'M', '䩮'), + (0x2F9F9, 'M', '䩶'), + (0x2F9FA, 'M', '韠'), + (0x2F9FB, 'M', '𩐊'), + (0x2F9FC, 'M', '䪲'), + (0x2F9FD, 'M', '𩒖'), + (0x2F9FE, 'M', '頋'), + (0x2FA00, 'M', '頩'), + (0x2FA01, 'M', '𩖶'), + (0x2FA02, 'M', '飢'), + (0x2FA03, 'M', '䬳'), + (0x2FA04, 'M', '餩'), + (0x2FA05, 'M', '馧'), + (0x2FA06, 'M', '駂'), + (0x2FA07, 'M', '駾'), + (0x2FA08, 'M', '䯎'), + (0x2FA09, 'M', '𩬰'), + (0x2FA0A, 'M', '鬒'), + (0x2FA0B, 'M', '鱀'), + (0x2FA0C, 'M', '鳽'), + (0x2FA0D, 'M', '䳎'), + (0x2FA0E, 'M', '䳭'), + (0x2FA0F, 'M', '鵧'), + (0x2FA10, 'M', '𪃎'), + (0x2FA11, 'M', '䳸'), + (0x2FA12, 'M', '𪄅'), + (0x2FA13, 'M', '𪈎'), + (0x2FA14, 'M', '𪊑'), + (0x2FA15, 'M', '麻'), + (0x2FA16, 'M', '䵖'), + (0x2FA17, 'M', '黹'), + (0x2FA18, 'M', '黾'), + (0x2FA19, 'M', '鼅'), + (0x2FA1A, 'M', '鼏'), + (0x2FA1B, 'M', '鼖'), + (0x2FA1C, 'M', '鼻'), + (0x2FA1D, 'M', '𪘀'), + (0x2FA1E, 'X'), + (0x30000, 'V'), + (0x3134B, 'X'), + (0xE0100, 'I'), (0xE01F0, 'X'), ] @@ -8202,4 +8434,5 @@ uts46data = tuple( + _seg_76() + _seg_77() + _seg_78() -) + + _seg_79() +) # type: Tuple[Union[Tuple[int, str], Tuple[int, str, str]], ...] diff --git a/pipenv/vendor/importlib_metadata/LICENSE b/pipenv/vendor/importlib_metadata/LICENSE new file mode 100644 index 00000000..be7e092b --- /dev/null +++ b/pipenv/vendor/importlib_metadata/LICENSE @@ -0,0 +1,13 @@ +Copyright 2017-2019 Jason R. Coombs, Barry Warsaw + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/pipenv/vendor/importlib_metadata/__init__.py b/pipenv/vendor/importlib_metadata/__init__.py new file mode 100644 index 00000000..ebdad84f --- /dev/null +++ b/pipenv/vendor/importlib_metadata/__init__.py @@ -0,0 +1,1028 @@ +import os +import re +import abc +import csv +import sys +import pipenv.vendor.zipp as zipp +import email +import pathlib +import operator +import platform +import textwrap +import warnings +import functools +import itertools +import posixpath +import collections + +from . import _adapters, _meta +from ._collections import FreezableDefaultDict, Pair +from ._compat import ( + NullFinder, + PyPy_repr, + install, +) +from ._functools import method_cache +from ._itertools import unique_everseen +from ._meta import PackageMetadata, SimplePath + +from contextlib import suppress +from importlib import import_module +from importlib.abc import MetaPathFinder +from itertools import starmap +from typing import List, Mapping, Optional, Union + + +__all__ = [ + 'Distribution', + 'DistributionFinder', + 'PackageMetadata', + 'PackageNotFoundError', + 'distribution', + 'distributions', + 'entry_points', + 'files', + 'metadata', + 'packages_distributions', + 'requires', + 'version', +] + + +def _pypy_partial(val): + """ + Adjust for variable stacklevel on partial under PyPy. + + Workaround for #327. + """ + is_pypy = platform.python_implementation() == 'PyPy' + return val + is_pypy + + +class PackageNotFoundError(ModuleNotFoundError): + """The package was not found.""" + + def __str__(self): + return f"No package metadata was found for {self.name}" + + @property + def name(self): + (name,) = self.args + return name + + +class Sectioned: + """ + A simple entry point config parser for performance + + >>> for item in Sectioned.read(Sectioned._sample): + ... print(item) + Pair(name='sec1', value='# comments ignored') + Pair(name='sec1', value='a = 1') + Pair(name='sec1', value='b = 2') + Pair(name='sec2', value='a = 2') + + >>> res = Sectioned.section_pairs(Sectioned._sample) + >>> item = next(res) + >>> item.name + 'sec1' + >>> item.value + Pair(name='a', value='1') + >>> item = next(res) + >>> item.value + Pair(name='b', value='2') + >>> item = next(res) + >>> item.name + 'sec2' + >>> item.value + Pair(name='a', value='2') + >>> list(res) + [] + """ + + _sample = textwrap.dedent( + """ + [sec1] + # comments ignored + a = 1 + b = 2 + + [sec2] + a = 2 + """ + ).lstrip() + + @classmethod + def section_pairs(cls, text): + return ( + section._replace(value=Pair.parse(section.value)) + for section in cls.read(text, filter_=cls.valid) + if section.name is not None + ) + + @staticmethod + def read(text, filter_=None): + lines = filter(filter_, map(str.strip, text.splitlines())) + name = None + for value in lines: + section_match = value.startswith('[') and value.endswith(']') + if section_match: + name = value.strip('[]') + continue + yield Pair(name, value) + + @staticmethod + def valid(line): + return line and not line.startswith('#') + + +class EntryPoint( + PyPy_repr, collections.namedtuple('EntryPointBase', 'name value group') +): + """An entry point as defined by Python packaging conventions. + + See `the packaging docs on entry points + <https://packaging.python.org/specifications/entry-points/>`_ + for more information. + """ + + pattern = re.compile( + r'(?P<module>[\w.]+)\s*' + r'(:\s*(?P<attr>[\w.]+))?\s*' + r'(?P<extras>\[.*\])?\s*$' + ) + """ + A regular expression describing the syntax for an entry point, + which might look like: + + - module + - package.module + - package.module:attribute + - package.module:object.attribute + - package.module:attr [extra1, extra2] + + Other combinations are possible as well. + + The expression is lenient about whitespace around the ':', + following the attr, and following any extras. + """ + + dist: Optional['Distribution'] = None + + def load(self): + """Load the entry point from its definition. If only a module + is indicated by the value, return that module. Otherwise, + return the named object. + """ + match = self.pattern.match(self.value) + module = import_module(match.group('module')) + attrs = filter(None, (match.group('attr') or '').split('.')) + return functools.reduce(getattr, attrs, module) + + @property + def module(self): + match = self.pattern.match(self.value) + return match.group('module') + + @property + def attr(self): + match = self.pattern.match(self.value) + return match.group('attr') + + @property + def extras(self): + match = self.pattern.match(self.value) + return list(re.finditer(r'\w+', match.group('extras') or '')) + + def _for(self, dist): + self.dist = dist + return self + + def __iter__(self): + """ + Supply iter so one may construct dicts of EntryPoints by name. + """ + msg = ( + "Construction of dict of EntryPoints is deprecated in " + "favor of EntryPoints." + ) + warnings.warn(msg, DeprecationWarning) + return iter((self.name, self)) + + def __reduce__(self): + return ( + self.__class__, + (self.name, self.value, self.group), + ) + + def matches(self, **params): + attrs = (getattr(self, param) for param in params) + return all(map(operator.eq, params.values(), attrs)) + + +class DeprecatedList(list): + """ + Allow an otherwise immutable object to implement mutability + for compatibility. + + >>> recwarn = getfixture('recwarn') + >>> dl = DeprecatedList(range(3)) + >>> dl[0] = 1 + >>> dl.append(3) + >>> del dl[3] + >>> dl.reverse() + >>> dl.sort() + >>> dl.extend([4]) + >>> dl.pop(-1) + 4 + >>> dl.remove(1) + >>> dl += [5] + >>> dl + [6] + [1, 2, 5, 6] + >>> dl + (6,) + [1, 2, 5, 6] + >>> dl.insert(0, 0) + >>> dl + [0, 1, 2, 5] + >>> dl == [0, 1, 2, 5] + True + >>> dl == (0, 1, 2, 5) + True + >>> len(recwarn) + 1 + """ + + _warn = functools.partial( + warnings.warn, + "EntryPoints list interface is deprecated. Cast to list if needed.", + DeprecationWarning, + stacklevel=_pypy_partial(2), + ) + + def __setitem__(self, *args, **kwargs): + self._warn() + return super().__setitem__(*args, **kwargs) + + def __delitem__(self, *args, **kwargs): + self._warn() + return super().__delitem__(*args, **kwargs) + + def append(self, *args, **kwargs): + self._warn() + return super().append(*args, **kwargs) + + def reverse(self, *args, **kwargs): + self._warn() + return super().reverse(*args, **kwargs) + + def extend(self, *args, **kwargs): + self._warn() + return super().extend(*args, **kwargs) + + def pop(self, *args, **kwargs): + self._warn() + return super().pop(*args, **kwargs) + + def remove(self, *args, **kwargs): + self._warn() + return super().remove(*args, **kwargs) + + def __iadd__(self, *args, **kwargs): + self._warn() + return super().__iadd__(*args, **kwargs) + + def __add__(self, other): + if not isinstance(other, tuple): + self._warn() + other = tuple(other) + return self.__class__(tuple(self) + other) + + def insert(self, *args, **kwargs): + self._warn() + return super().insert(*args, **kwargs) + + def sort(self, *args, **kwargs): + self._warn() + return super().sort(*args, **kwargs) + + def __eq__(self, other): + if not isinstance(other, tuple): + self._warn() + other = tuple(other) + + return tuple(self).__eq__(other) + + +class EntryPoints(DeprecatedList): + """ + An immutable collection of selectable EntryPoint objects. + """ + + __slots__ = () + + def __getitem__(self, name): # -> EntryPoint: + """ + Get the EntryPoint in self matching name. + """ + if isinstance(name, int): + warnings.warn( + "Accessing entry points by index is deprecated. " + "Cast to tuple if needed.", + DeprecationWarning, + stacklevel=2, + ) + return super().__getitem__(name) + try: + return next(iter(self.select(name=name))) + except StopIteration: + raise KeyError(name) + + def select(self, **params): + """ + Select entry points from self that match the + given parameters (typically group and/or name). + """ + return EntryPoints(ep for ep in self if ep.matches(**params)) + + @property + def names(self): + """ + Return the set of all names of all entry points. + """ + return set(ep.name for ep in self) + + @property + def groups(self): + """ + Return the set of all groups of all entry points. + + For coverage while SelectableGroups is present. + >>> EntryPoints().groups + set() + """ + return set(ep.group for ep in self) + + @classmethod + def _from_text_for(cls, text, dist): + return cls(ep._for(dist) for ep in cls._from_text(text)) + + @classmethod + def _from_text(cls, text): + return itertools.starmap(EntryPoint, cls._parse_groups(text or '')) + + @staticmethod + def _parse_groups(text): + return ( + (item.value.name, item.value.value, item.name) + for item in Sectioned.section_pairs(text) + ) + + +class Deprecated: + """ + Compatibility add-in for mapping to indicate that + mapping behavior is deprecated. + + >>> recwarn = getfixture('recwarn') + >>> class DeprecatedDict(Deprecated, dict): pass + >>> dd = DeprecatedDict(foo='bar') + >>> dd.get('baz', None) + >>> dd['foo'] + 'bar' + >>> list(dd) + ['foo'] + >>> list(dd.keys()) + ['foo'] + >>> 'foo' in dd + True + >>> list(dd.values()) + ['bar'] + >>> len(recwarn) + 1 + """ + + _warn = functools.partial( + warnings.warn, + "SelectableGroups dict interface is deprecated. Use select.", + DeprecationWarning, + stacklevel=_pypy_partial(2), + ) + + def __getitem__(self, name): + self._warn() + return super().__getitem__(name) + + def get(self, name, default=None): + self._warn() + return super().get(name, default) + + def __iter__(self): + self._warn() + return super().__iter__() + + def __contains__(self, *args): + self._warn() + return super().__contains__(*args) + + def keys(self): + self._warn() + return super().keys() + + def values(self): + self._warn() + return super().values() + + +class SelectableGroups(Deprecated, dict): + """ + A backward- and forward-compatible result from + entry_points that fully implements the dict interface. + """ + + @classmethod + def load(cls, eps): + by_group = operator.attrgetter('group') + ordered = sorted(eps, key=by_group) + grouped = itertools.groupby(ordered, by_group) + return cls((group, EntryPoints(eps)) for group, eps in grouped) + + @property + def _all(self): + """ + Reconstruct a list of all entrypoints from the groups. + """ + groups = super(Deprecated, self).values() + return EntryPoints(itertools.chain.from_iterable(groups)) + + @property + def groups(self): + return self._all.groups + + @property + def names(self): + """ + for coverage: + >>> SelectableGroups().names + set() + """ + return self._all.names + + def select(self, **params): + if not params: + return self + return self._all.select(**params) + + +class PackagePath(pathlib.PurePosixPath): + """A reference to a path in a package""" + + def read_text(self, encoding='utf-8'): + with self.locate().open(encoding=encoding) as stream: + return stream.read() + + def read_binary(self): + with self.locate().open('rb') as stream: + return stream.read() + + def locate(self): + """Return a path-like object for this path""" + return self.dist.locate_file(self) + + +class FileHash: + def __init__(self, spec): + self.mode, _, self.value = spec.partition('=') + + def __repr__(self): + return f'<FileHash mode: {self.mode} value: {self.value}>' + + +class Distribution: + """A Python distribution package.""" + + @abc.abstractmethod + def read_text(self, filename): + """Attempt to load metadata file given by the name. + + :param filename: The name of the file in the distribution info. + :return: The text if found, otherwise None. + """ + + @abc.abstractmethod + def locate_file(self, path): + """ + Given a path to a file in this distribution, return a path + to it. + """ + + @classmethod + def from_name(cls, name): + """Return the Distribution for the given package name. + + :param name: The name of the distribution package to search for. + :return: The Distribution instance (or subclass thereof) for the named + package, if found. + :raises PackageNotFoundError: When the named package's distribution + metadata cannot be found. + """ + for resolver in cls._discover_resolvers(): + dists = resolver(DistributionFinder.Context(name=name)) + dist = next(iter(dists), None) + if dist is not None: + return dist + else: + raise PackageNotFoundError(name) + + @classmethod + def discover(cls, **kwargs): + """Return an iterable of Distribution objects for all packages. + + Pass a ``context`` or pass keyword arguments for constructing + a context. + + :context: A ``DistributionFinder.Context`` object. + :return: Iterable of Distribution objects for all packages. + """ + context = kwargs.pop('context', None) + if context and kwargs: + raise ValueError("cannot accept context and kwargs") + context = context or DistributionFinder.Context(**kwargs) + return itertools.chain.from_iterable( + resolver(context) for resolver in cls._discover_resolvers() + ) + + @staticmethod + def at(path): + """Return a Distribution for the indicated metadata path + + :param path: a string or path-like object + :return: a concrete Distribution instance for the path + """ + return PathDistribution(pathlib.Path(path)) + + @staticmethod + def _discover_resolvers(): + """Search the meta_path for resolvers.""" + declared = ( + getattr(finder, 'find_distributions', None) for finder in sys.meta_path + ) + return filter(None, declared) + + @classmethod + def _local(cls, root='.'): + from pipenv.vendor.pep517 import build, meta + + system = build.compat_system(root) + builder = functools.partial( + meta.build, + source_dir=root, + system=system, + ) + return PathDistribution(zipp.Path(meta.build_as_zip(builder))) + + @property + def metadata(self) -> _meta.PackageMetadata: + """Return the parsed metadata for this Distribution. + + The returned object will have keys that name the various bits of + metadata. See PEP 566 for details. + """ + text = ( + self.read_text('METADATA') + or self.read_text('PKG-INFO') + # This last clause is here to support old egg-info files. Its + # effect is to just end up using the PathDistribution's self._path + # (which points to the egg-info file) attribute unchanged. + or self.read_text('') + ) + return _adapters.Message(email.message_from_string(text)) + + @property + def name(self): + """Return the 'Name' metadata for the distribution package.""" + return self.metadata['Name'] + + @property + def _normalized_name(self): + """Return a normalized version of the name.""" + return Prepared.normalize(self.name) + + @property + def version(self): + """Return the 'Version' metadata for the distribution package.""" + return self.metadata['Version'] + + @property + def entry_points(self): + return EntryPoints._from_text_for(self.read_text('entry_points.txt'), self) + + @property + def files(self): + """Files in this distribution. + + :return: List of PackagePath for this distribution or None + + Result is `None` if the metadata file that enumerates files + (i.e. RECORD for dist-info or SOURCES.txt for egg-info) is + missing. + Result may be empty if the metadata exists but is empty. + """ + file_lines = self._read_files_distinfo() or self._read_files_egginfo() + + def make_file(name, hash=None, size_str=None): + result = PackagePath(name) + result.hash = FileHash(hash) if hash else None + result.size = int(size_str) if size_str else None + result.dist = self + return result + + return file_lines and list(starmap(make_file, csv.reader(file_lines))) + + def _read_files_distinfo(self): + """ + Read the lines of RECORD + """ + text = self.read_text('RECORD') + return text and text.splitlines() + + def _read_files_egginfo(self): + """ + SOURCES.txt might contain literal commas, so wrap each line + in quotes. + """ + text = self.read_text('SOURCES.txt') + return text and map('"{}"'.format, text.splitlines()) + + @property + def requires(self): + """Generated requirements specified for this Distribution""" + reqs = self._read_dist_info_reqs() or self._read_egg_info_reqs() + return reqs and list(reqs) + + def _read_dist_info_reqs(self): + return self.metadata.get_all('Requires-Dist') + + def _read_egg_info_reqs(self): + source = self.read_text('requires.txt') + return source and self._deps_from_requires_text(source) + + @classmethod + def _deps_from_requires_text(cls, source): + return cls._convert_egg_info_reqs_to_simple_reqs(Sectioned.read(source)) + + @staticmethod + def _convert_egg_info_reqs_to_simple_reqs(sections): + """ + Historically, setuptools would solicit and store 'extra' + requirements, including those with environment markers, + in separate sections. More modern tools expect each + dependency to be defined separately, with any relevant + extras and environment markers attached directly to that + requirement. This method converts the former to the + latter. See _test_deps_from_requires_text for an example. + """ + + def make_condition(name): + return name and f'extra == "{name}"' + + def parse_condition(section): + section = section or '' + extra, sep, markers = section.partition(':') + if extra and markers: + markers = f'({markers})' + conditions = list(filter(None, [markers, make_condition(extra)])) + return '; ' + ' and '.join(conditions) if conditions else '' + + for section in sections: + yield section.value + parse_condition(section.name) + + +class DistributionFinder(MetaPathFinder): + """ + A MetaPathFinder capable of discovering installed distributions. + """ + + class Context: + """ + Keyword arguments presented by the caller to + ``distributions()`` or ``Distribution.discover()`` + to narrow the scope of a search for distributions + in all DistributionFinders. + + Each DistributionFinder may expect any parameters + and should attempt to honor the canonical + parameters defined below when appropriate. + """ + + name = None + """ + Specific name for which a distribution finder should match. + A name of ``None`` matches all distributions. + """ + + def __init__(self, **kwargs): + vars(self).update(kwargs) + + @property + def path(self): + """ + The sequence of directory path that a distribution finder + should search. + + Typically refers to Python installed package paths such as + "site-packages" directories and defaults to ``sys.path``. + """ + return vars(self).get('path', sys.path) + + @abc.abstractmethod + def find_distributions(self, context=Context()): + """ + Find distributions. + + Return an iterable of all Distribution instances capable of + loading the metadata for packages matching the ``context``, + a DistributionFinder.Context instance. + """ + + +class FastPath: + """ + Micro-optimized class for searching a path for + children. + """ + + @functools.lru_cache() # type: ignore + def __new__(cls, root): + return super().__new__(cls) + + def __init__(self, root): + self.root = str(root) + + def joinpath(self, child): + return pathlib.Path(self.root, child) + + def children(self): + with suppress(Exception): + return os.listdir(self.root or '') + with suppress(Exception): + return self.zip_children() + return [] + + def zip_children(self): + zip_path = zipp.Path(self.root) + names = zip_path.root.namelist() + self.joinpath = zip_path.joinpath + + return dict.fromkeys(child.split(posixpath.sep, 1)[0] for child in names) + + def search(self, name): + return self.lookup(self.mtime).search(name) + + @property + def mtime(self): + with suppress(OSError): + return os.stat(self.root).st_mtime + self.lookup.cache_clear() + + @method_cache + def lookup(self, mtime): + return Lookup(self) + + +class Lookup: + def __init__(self, path: FastPath): + base = os.path.basename(path.root).lower() + base_is_egg = base.endswith(".egg") + self.infos = FreezableDefaultDict(list) + self.eggs = FreezableDefaultDict(list) + + for child in path.children(): + low = child.lower() + if low.endswith((".dist-info", ".egg-info")): + # rpartition is faster than splitext and suitable for this purpose. + name = low.rpartition(".")[0].partition("-")[0] + normalized = Prepared.normalize(name) + self.infos[normalized].append(path.joinpath(child)) + elif base_is_egg and low == "egg-info": + name = base.rpartition(".")[0].partition("-")[0] + legacy_normalized = Prepared.legacy_normalize(name) + self.eggs[legacy_normalized].append(path.joinpath(child)) + + self.infos.freeze() + self.eggs.freeze() + + def search(self, prepared): + infos = ( + self.infos[prepared.normalized] + if prepared + else itertools.chain.from_iterable(self.infos.values()) + ) + eggs = ( + self.eggs[prepared.legacy_normalized] + if prepared + else itertools.chain.from_iterable(self.eggs.values()) + ) + return itertools.chain(infos, eggs) + + +class Prepared: + """ + A prepared search for metadata on a possibly-named package. + """ + + normalized = None + legacy_normalized = None + + def __init__(self, name): + self.name = name + if name is None: + return + self.normalized = self.normalize(name) + self.legacy_normalized = self.legacy_normalize(name) + + @staticmethod + def normalize(name): + """ + PEP 503 normalization plus dashes as underscores. + """ + return re.sub(r"[-_.]+", "-", name).lower().replace('-', '_') + + @staticmethod + def legacy_normalize(name): + """ + Normalize the package name as found in the convention in + older packaging tools versions and specs. + """ + return name.lower().replace('-', '_') + + def __bool__(self): + return bool(self.name) + + +@install +class MetadataPathFinder(NullFinder, DistributionFinder): + """A degenerate finder for distribution packages on the file system. + + This finder supplies only a find_distributions() method for versions + of Python that do not have a PathFinder find_distributions(). + """ + + def find_distributions(self, context=DistributionFinder.Context()): + """ + Find distributions. + + Return an iterable of all Distribution instances capable of + loading the metadata for packages matching ``context.name`` + (or all names if ``None`` indicated) along the paths in the list + of directories ``context.path``. + """ + found = self._search_paths(context.name, context.path) + return map(PathDistribution, found) + + @classmethod + def _search_paths(cls, name, paths): + """Find metadata directories in paths heuristically.""" + prepared = Prepared(name) + return itertools.chain.from_iterable( + path.search(prepared) for path in map(FastPath, paths) + ) + + def invalidate_caches(cls): + FastPath.__new__.cache_clear() + + +class PathDistribution(Distribution): + def __init__(self, path: SimplePath): + """Construct a distribution. + + :param path: SimplePath indicating the metadata directory. + """ + self._path = path + + def read_text(self, filename): + with suppress( + FileNotFoundError, + IsADirectoryError, + KeyError, + NotADirectoryError, + PermissionError, + ): + return self._path.joinpath(filename).read_text(encoding='utf-8') + + read_text.__doc__ = Distribution.read_text.__doc__ + + def locate_file(self, path): + return self._path.parent / path + + @property + def _normalized_name(self): + """ + Performance optimization: where possible, resolve the + normalized name from the file system path. + """ + stem = os.path.basename(str(self._path)) + return self._name_from_stem(stem) or super()._normalized_name + + def _name_from_stem(self, stem): + name, ext = os.path.splitext(stem) + if ext not in ('.dist-info', '.egg-info'): + return + name, sep, rest = stem.partition('-') + return name + + +def distribution(distribution_name): + """Get the ``Distribution`` instance for the named package. + + :param distribution_name: The name of the distribution package as a string. + :return: A ``Distribution`` instance (or subclass thereof). + """ + return Distribution.from_name(distribution_name) + + +def distributions(**kwargs): + """Get all ``Distribution`` instances in the current environment. + + :return: An iterable of ``Distribution`` instances. + """ + return Distribution.discover(**kwargs) + + +def metadata(distribution_name) -> _meta.PackageMetadata: + """Get the metadata for the named package. + + :param distribution_name: The name of the distribution package to query. + :return: A PackageMetadata containing the parsed metadata. + """ + return Distribution.from_name(distribution_name).metadata + + +def version(distribution_name): + """Get the version string for the named package. + + :param distribution_name: The name of the distribution package to query. + :return: The version string for the package as defined in the package's + "Version" metadata key. + """ + return distribution(distribution_name).version + + +def entry_points(**params) -> Union[EntryPoints, SelectableGroups]: + """Return EntryPoint objects for all installed packages. + + Pass selection parameters (group or name) to filter the + result to entry points matching those properties (see + EntryPoints.select()). + + For compatibility, returns ``SelectableGroups`` object unless + selection parameters are supplied. In the future, this function + will return ``EntryPoints`` instead of ``SelectableGroups`` + even when no selection parameters are supplied. + + For maximum future compatibility, pass selection parameters + or invoke ``.select`` with parameters on the result. + + :return: EntryPoints or SelectableGroups for all installed packages. + """ + norm_name = operator.attrgetter('_normalized_name') + unique = functools.partial(unique_everseen, key=norm_name) + eps = itertools.chain.from_iterable( + dist.entry_points for dist in unique(distributions()) + ) + return SelectableGroups.load(eps).select(**params) + + +def files(distribution_name): + """Return a list of files for the named package. + + :param distribution_name: The name of the distribution package to query. + :return: List of files composing the distribution. + """ + return distribution(distribution_name).files + + +def requires(distribution_name): + """ + Return a list of requirements for the named package. + + :return: An iterator of requirements, suitable for + packaging.requirement.Requirement. + """ + return distribution(distribution_name).requires + + +def packages_distributions() -> Mapping[str, List[str]]: + """ + Return a mapping of top-level packages to their + distributions. + + >>> import collections.abc + >>> pkgs = packages_distributions() + >>> all(isinstance(dist, collections.abc.Sequence) for dist in pkgs.values()) + True + """ + pkg_to_dist = collections.defaultdict(list) + for dist in distributions(): + for pkg in (dist.read_text('top_level.txt') or '').split(): + pkg_to_dist[pkg].append(dist.metadata['Name']) + return dict(pkg_to_dist) diff --git a/pipenv/vendor/importlib_metadata/_adapters.py b/pipenv/vendor/importlib_metadata/_adapters.py new file mode 100644 index 00000000..aa460d3e --- /dev/null +++ b/pipenv/vendor/importlib_metadata/_adapters.py @@ -0,0 +1,68 @@ +import re +import textwrap +import email.message + +from ._text import FoldedCase + + +class Message(email.message.Message): + multiple_use_keys = set( + map( + FoldedCase, + [ + 'Classifier', + 'Obsoletes-Dist', + 'Platform', + 'Project-URL', + 'Provides-Dist', + 'Provides-Extra', + 'Requires-Dist', + 'Requires-External', + 'Supported-Platform', + 'Dynamic', + ], + ) + ) + """ + Keys that may be indicated multiple times per PEP 566. + """ + + def __new__(cls, orig: email.message.Message): + res = super().__new__(cls) + vars(res).update(vars(orig)) + return res + + def __init__(self, *args, **kwargs): + self._headers = self._repair_headers() + + # suppress spurious error from mypy + def __iter__(self): + return super().__iter__() + + def _repair_headers(self): + def redent(value): + "Correct for RFC822 indentation" + if not value or '\n' not in value: + return value + return textwrap.dedent(' ' * 8 + value) + + headers = [(key, redent(value)) for key, value in vars(self)['_headers']] + if self._payload: + headers.append(('Description', self.get_payload())) + return headers + + @property + def json(self): + """ + Convert PackageMetadata to a JSON-compatible format + per PEP 0566. + """ + + def transform(key): + value = self.get_all(key) if key in self.multiple_use_keys else self[key] + if key == 'Keywords': + value = re.split(r'\s+', value) + tk = key.lower().replace('-', '_') + return tk, value + + return dict(map(transform, map(FoldedCase, self))) diff --git a/pipenv/vendor/importlib_metadata/_collections.py b/pipenv/vendor/importlib_metadata/_collections.py new file mode 100644 index 00000000..cf0954e1 --- /dev/null +++ b/pipenv/vendor/importlib_metadata/_collections.py @@ -0,0 +1,30 @@ +import collections + + +# from jaraco.collections 3.3 +class FreezableDefaultDict(collections.defaultdict): + """ + Often it is desirable to prevent the mutation of + a default dict after its initial construction, such + as to prevent mutation during iteration. + + >>> dd = FreezableDefaultDict(list) + >>> dd[0].append('1') + >>> dd.freeze() + >>> dd[1] + [] + >>> len(dd) + 1 + """ + + def __missing__(self, key): + return getattr(self, '_frozen', super().__missing__)(key) + + def freeze(self): + self._frozen = lambda key: self.default_factory() + + +class Pair(collections.namedtuple('Pair', 'name value')): + @classmethod + def parse(cls, text): + return cls(*map(str.strip, text.split("=", 1))) diff --git a/pipenv/vendor/importlib_metadata/_compat.py b/pipenv/vendor/importlib_metadata/_compat.py new file mode 100644 index 00000000..043ece02 --- /dev/null +++ b/pipenv/vendor/importlib_metadata/_compat.py @@ -0,0 +1,86 @@ +import sys + + +__all__ = ['install', 'NullFinder', 'PyPy_repr', 'Protocol'] + + +try: + from typing import Protocol +except ImportError: # pragma: no cover + """ + pytest-mypy complains here because: + error: Incompatible import of "Protocol" (imported name has type + "typing_extensions._SpecialForm", local name has type "typing._SpecialForm") + """ + from typing_extensions import Protocol # type: ignore + + +def install(cls): + """ + Class decorator for installation on sys.meta_path. + + Adds the backport DistributionFinder to sys.meta_path and + attempts to disable the finder functionality of the stdlib + DistributionFinder. + """ + sys.meta_path.append(cls()) + disable_stdlib_finder() + return cls + + +def disable_stdlib_finder(): + """ + Give the backport primacy for discovering path-based distributions + by monkey-patching the stdlib O_O. + + See #91 for more background for rationale on this sketchy + behavior. + """ + + def matches(finder): + return getattr( + finder, '__module__', None + ) == '_frozen_importlib_external' and hasattr(finder, 'find_distributions') + + for finder in filter(matches, sys.meta_path): # pragma: nocover + del finder.find_distributions + + +class NullFinder: + """ + A "Finder" (aka "MetaClassFinder") that never finds any modules, + but may find distributions. + """ + + @staticmethod + def find_spec(*args, **kwargs): + return None + + # In Python 2, the import system requires finders + # to have a find_module() method, but this usage + # is deprecated in Python 3 in favor of find_spec(). + # For the purposes of this finder (i.e. being present + # on sys.meta_path but having no other import + # system functionality), the two methods are identical. + find_module = find_spec + + +class PyPy_repr: + """ + Override repr for EntryPoint objects on PyPy to avoid __iter__ access. + Ref #97, #102. + """ + + affected = hasattr(sys, 'pypy_version_info') + + def __compat_repr__(self): # pragma: nocover + def make_param(name): + value = getattr(self, name) + return f'{name}={value!r}' + + params = ', '.join(map(make_param, self._fields)) + return f'EntryPoint({params})' + + if affected: # pragma: nocover + __repr__ = __compat_repr__ + del affected diff --git a/pipenv/vendor/importlib_metadata/_functools.py b/pipenv/vendor/importlib_metadata/_functools.py new file mode 100644 index 00000000..73f50d00 --- /dev/null +++ b/pipenv/vendor/importlib_metadata/_functools.py @@ -0,0 +1,85 @@ +import types +import functools + + +# from jaraco.functools 3.3 +def method_cache(method, cache_wrapper=None): + """ + Wrap lru_cache to support storing the cache data in the object instances. + + Abstracts the common paradigm where the method explicitly saves an + underscore-prefixed protected property on first call and returns that + subsequently. + + >>> class MyClass: + ... calls = 0 + ... + ... @method_cache + ... def method(self, value): + ... self.calls += 1 + ... return value + + >>> a = MyClass() + >>> a.method(3) + 3 + >>> for x in range(75): + ... res = a.method(x) + >>> a.calls + 75 + + Note that the apparent behavior will be exactly like that of lru_cache + except that the cache is stored on each instance, so values in one + instance will not flush values from another, and when an instance is + deleted, so are the cached values for that instance. + + >>> b = MyClass() + >>> for x in range(35): + ... res = b.method(x) + >>> b.calls + 35 + >>> a.method(0) + 0 + >>> a.calls + 75 + + Note that if method had been decorated with ``functools.lru_cache()``, + a.calls would have been 76 (due to the cached value of 0 having been + flushed by the 'b' instance). + + Clear the cache with ``.cache_clear()`` + + >>> a.method.cache_clear() + + Same for a method that hasn't yet been called. + + >>> c = MyClass() + >>> c.method.cache_clear() + + Another cache wrapper may be supplied: + + >>> cache = functools.lru_cache(maxsize=2) + >>> MyClass.method2 = method_cache(lambda self: 3, cache_wrapper=cache) + >>> a = MyClass() + >>> a.method2() + 3 + + Caution - do not subsequently wrap the method with another decorator, such + as ``@property``, which changes the semantics of the function. + + See also + http://code.activestate.com/recipes/577452-a-memoize-decorator-for-instance-methods/ + for another implementation and additional justification. + """ + cache_wrapper = cache_wrapper or functools.lru_cache() + + def wrapper(self, *args, **kwargs): + # it's the first call, replace the method with a cached, bound method + bound_method = types.MethodType(method, self) + cached_method = cache_wrapper(bound_method) + setattr(self, method.__name__, cached_method) + return cached_method(*args, **kwargs) + + # Support cache clear even before cache has been created. + wrapper.cache_clear = lambda: None + + return wrapper diff --git a/pipenv/vendor/importlib_metadata/_itertools.py b/pipenv/vendor/importlib_metadata/_itertools.py new file mode 100644 index 00000000..dd45f2f0 --- /dev/null +++ b/pipenv/vendor/importlib_metadata/_itertools.py @@ -0,0 +1,19 @@ +from itertools import filterfalse + + +def unique_everseen(iterable, key=None): + "List unique elements, preserving order. Remember all elements ever seen." + # unique_everseen('AAAABBBCCDAABBB') --> A B C D + # unique_everseen('ABBCcAD', str.lower) --> A B C D + seen = set() + seen_add = seen.add + if key is None: + for element in filterfalse(seen.__contains__, iterable): + seen_add(element) + yield element + else: + for element in iterable: + k = key(element) + if k not in seen: + seen_add(k) + yield element diff --git a/pipenv/vendor/importlib_metadata/_meta.py b/pipenv/vendor/importlib_metadata/_meta.py new file mode 100644 index 00000000..dd68c429 --- /dev/null +++ b/pipenv/vendor/importlib_metadata/_meta.py @@ -0,0 +1,48 @@ +from ._compat import Protocol +from typing import Any, Dict, Iterator, List, TypeVar, Union + + +_T = TypeVar("_T") + + +class PackageMetadata(Protocol): + def __len__(self) -> int: + ... # pragma: no cover + + def __contains__(self, item: str) -> bool: + ... # pragma: no cover + + def __getitem__(self, key: str) -> str: + ... # pragma: no cover + + def __iter__(self) -> Iterator[str]: + ... # pragma: no cover + + def get_all(self, name: str, failobj: _T = ...) -> Union[List[Any], _T]: + """ + Return all values associated with a possibly multi-valued key. + """ + + @property + def json(self) -> Dict[str, Union[str, List[str]]]: + """ + A JSON-compatible form of the metadata. + """ + + +class SimplePath(Protocol): + """ + A minimal subset of pathlib.Path required by PathDistribution. + """ + + def joinpath(self) -> 'SimplePath': + ... # pragma: no cover + + def __div__(self) -> 'SimplePath': + ... # pragma: no cover + + def parent(self) -> 'SimplePath': + ... # pragma: no cover + + def read_text(self) -> str: + ... # pragma: no cover diff --git a/pipenv/vendor/importlib_metadata/_text.py b/pipenv/vendor/importlib_metadata/_text.py new file mode 100644 index 00000000..766979d9 --- /dev/null +++ b/pipenv/vendor/importlib_metadata/_text.py @@ -0,0 +1,99 @@ +import re + +from ._functools import method_cache + + +# from jaraco.text 3.5 +class FoldedCase(str): + """ + A case insensitive string class; behaves just like str + except compares equal when the only variation is case. + + >>> s = FoldedCase('hello world') + + >>> s == 'Hello World' + True + + >>> 'Hello World' == s + True + + >>> s != 'Hello World' + False + + >>> s.index('O') + 4 + + >>> s.split('O') + ['hell', ' w', 'rld'] + + >>> sorted(map(FoldedCase, ['GAMMA', 'alpha', 'Beta'])) + ['alpha', 'Beta', 'GAMMA'] + + Sequence membership is straightforward. + + >>> "Hello World" in [s] + True + >>> s in ["Hello World"] + True + + You may test for set inclusion, but candidate and elements + must both be folded. + + >>> FoldedCase("Hello World") in {s} + True + >>> s in {FoldedCase("Hello World")} + True + + String inclusion works as long as the FoldedCase object + is on the right. + + >>> "hello" in FoldedCase("Hello World") + True + + But not if the FoldedCase object is on the left: + + >>> FoldedCase('hello') in 'Hello World' + False + + In that case, use in_: + + >>> FoldedCase('hello').in_('Hello World') + True + + >>> FoldedCase('hello') > FoldedCase('Hello') + False + """ + + def __lt__(self, other): + return self.lower() < other.lower() + + def __gt__(self, other): + return self.lower() > other.lower() + + def __eq__(self, other): + return self.lower() == other.lower() + + def __ne__(self, other): + return self.lower() != other.lower() + + def __hash__(self): + return hash(self.lower()) + + def __contains__(self, other): + return super(FoldedCase, self).lower().__contains__(other.lower()) + + def in_(self, other): + "Does self appear in other?" + return self in FoldedCase(other) + + # cache lower since it's likely to be called frequently. + @method_cache + def lower(self): + return super(FoldedCase, self).lower() + + def index(self, sub): + return self.lower().index(sub.lower()) + + def split(self, splitter=' ', maxsplit=0): + pattern = re.compile(re.escape(splitter), re.I) + return pattern.split(self, maxsplit) diff --git a/pipenv/vendor/importlib_metadata/py.typed b/pipenv/vendor/importlib_metadata/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/pipenv/vendor/importlib_resources/LICENSE b/pipenv/vendor/importlib_resources/LICENSE new file mode 100644 index 00000000..378b991a --- /dev/null +++ b/pipenv/vendor/importlib_resources/LICENSE @@ -0,0 +1,13 @@ +Copyright 2017-2019 Brett Cannon, Barry Warsaw + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/pipenv/vendor/importlib_resources/__init__.py b/pipenv/vendor/importlib_resources/__init__.py new file mode 100644 index 00000000..c3e77741 --- /dev/null +++ b/pipenv/vendor/importlib_resources/__init__.py @@ -0,0 +1,36 @@ +"""Read resources contained within a package.""" + +from ._common import ( + as_file, + files, + Package, + Resource, +) + +from ._legacy import ( + contents, + open_binary, + read_binary, + open_text, + read_text, + is_resource, + path, +) + +from pipenv.vendor.importlib_resources.abc import ResourceReader + + +__all__ = [ + 'Package', + 'Resource', + 'ResourceReader', + 'as_file', + 'contents', + 'files', + 'is_resource', + 'open_binary', + 'open_text', + 'path', + 'read_binary', + 'read_text', +] diff --git a/pipenv/vendor/importlib_resources/_adapters.py b/pipenv/vendor/importlib_resources/_adapters.py new file mode 100644 index 00000000..9907b148 --- /dev/null +++ b/pipenv/vendor/importlib_resources/_adapters.py @@ -0,0 +1,170 @@ +from contextlib import suppress +from io import TextIOWrapper + +from . import abc + + +class SpecLoaderAdapter: + """ + Adapt a package spec to adapt the underlying loader. + """ + + def __init__(self, spec, adapter=lambda spec: spec.loader): + self.spec = spec + self.loader = adapter(spec) + + def __getattr__(self, name): + return getattr(self.spec, name) + + +class TraversableResourcesLoader: + """ + Adapt a loader to provide TraversableResources. + """ + + def __init__(self, spec): + self.spec = spec + + def get_resource_reader(self, name): + return CompatibilityFiles(self.spec)._native() + + +def _io_wrapper(file, mode='r', *args, **kwargs): + if mode == 'r': + return TextIOWrapper(file, *args, **kwargs) + elif mode == 'rb': + return file + raise ValueError( + "Invalid mode value '{}', only 'r' and 'rb' are supported".format(mode) + ) + + +class CompatibilityFiles: + """ + Adapter for an existing or non-existant resource reader + to provide a compability .files(). + """ + + class SpecPath(abc.Traversable): + """ + Path tied to a module spec. + Can be read and exposes the resource reader children. + """ + + def __init__(self, spec, reader): + self._spec = spec + self._reader = reader + + def iterdir(self): + if not self._reader: + return iter(()) + return iter( + CompatibilityFiles.ChildPath(self._reader, path) + for path in self._reader.contents() + ) + + def is_file(self): + return False + + is_dir = is_file + + def joinpath(self, other): + if not self._reader: + return CompatibilityFiles.OrphanPath(other) + return CompatibilityFiles.ChildPath(self._reader, other) + + @property + def name(self): + return self._spec.name + + def open(self, mode='r', *args, **kwargs): + return _io_wrapper(self._reader.open_resource(None), mode, *args, **kwargs) + + class ChildPath(abc.Traversable): + """ + Path tied to a resource reader child. + Can be read but doesn't expose any meaningfull children. + """ + + def __init__(self, reader, name): + self._reader = reader + self._name = name + + def iterdir(self): + return iter(()) + + def is_file(self): + return self._reader.is_resource(self.name) + + def is_dir(self): + return not self.is_file() + + def joinpath(self, other): + return CompatibilityFiles.OrphanPath(self.name, other) + + @property + def name(self): + return self._name + + def open(self, mode='r', *args, **kwargs): + return _io_wrapper( + self._reader.open_resource(self.name), mode, *args, **kwargs + ) + + class OrphanPath(abc.Traversable): + """ + Orphan path, not tied to a module spec or resource reader. + Can't be read and doesn't expose any meaningful children. + """ + + def __init__(self, *path_parts): + if len(path_parts) < 1: + raise ValueError('Need at least one path part to construct a path') + self._path = path_parts + + def iterdir(self): + return iter(()) + + def is_file(self): + return False + + is_dir = is_file + + def joinpath(self, other): + return CompatibilityFiles.OrphanPath(*self._path, other) + + @property + def name(self): + return self._path[-1] + + def open(self, mode='r', *args, **kwargs): + raise FileNotFoundError("Can't open orphan path") + + def __init__(self, spec): + self.spec = spec + + @property + def _reader(self): + with suppress(AttributeError): + return self.spec.loader.get_resource_reader(self.spec.name) + + def _native(self): + """ + Return the native reader if it supports files(). + """ + reader = self._reader + return reader if hasattr(reader, 'files') else self + + def __getattr__(self, attr): + return getattr(self._reader, attr) + + def files(self): + return CompatibilityFiles.SpecPath(self.spec, self._reader) + + +def wrap_spec(package): + """ + Construct a package spec with traversable compatibility + on the spec/loader/reader. + """ + return SpecLoaderAdapter(package.__spec__, TraversableResourcesLoader) diff --git a/pipenv/vendor/importlib_resources/_common.py b/pipenv/vendor/importlib_resources/_common.py new file mode 100644 index 00000000..9bb6bda5 --- /dev/null +++ b/pipenv/vendor/importlib_resources/_common.py @@ -0,0 +1,116 @@ +import os +import pathlib +import tempfile +import functools +import contextlib +import types +import importlib + +from typing import Union, Any, Optional +from .abc import ResourceReader, Traversable + +from ._compat import wrap_spec + +Package = Union[types.ModuleType, str] +Resource = Union[str, os.PathLike] + + +def files(package): + # type: (Package) -> Traversable + """ + Get a Traversable resource from a package + """ + return from_package(get_package(package)) + + +def normalize_path(path): + # type: (Any) -> str + """Normalize a path by ensuring it is a string. + + If the resulting string contains path separators, an exception is raised. + """ + str_path = str(path) + parent, file_name = os.path.split(str_path) + if parent: + raise ValueError(f'{path!r} must be only a file name') + return file_name + + +def get_resource_reader(package): + # type: (types.ModuleType) -> Optional[ResourceReader] + """ + Return the package's loader if it's a ResourceReader. + """ + # We can't use + # a issubclass() check here because apparently abc.'s __subclasscheck__() + # hook wants to create a weak reference to the object, but + # zipimport.zipimporter does not support weak references, resulting in a + # TypeError. That seems terrible. + spec = package.__spec__ + reader = getattr(spec.loader, 'get_resource_reader', None) # type: ignore + if reader is None: + return None + return reader(spec.name) # type: ignore + + +def resolve(cand): + # type: (Package) -> types.ModuleType + return cand if isinstance(cand, types.ModuleType) else importlib.import_module(cand) + + +def get_package(package): + # type: (Package) -> types.ModuleType + """Take a package name or module object and return the module. + + Raise an exception if the resolved module is not a package. + """ + resolved = resolve(package) + if wrap_spec(resolved).submodule_search_locations is None: + raise TypeError(f'{package!r} is not a package') + return resolved + + +def from_package(package): + """ + Return a Traversable object for the given package. + + """ + spec = wrap_spec(package) + reader = spec.loader.get_resource_reader(spec.name) + return reader.files() + + +@contextlib.contextmanager +def _tempfile(reader, suffix=''): + # Not using tempfile.NamedTemporaryFile as it leads to deeper 'try' + # blocks due to the need to close the temporary file to work on Windows + # properly. + fd, raw_path = tempfile.mkstemp(suffix=suffix) + try: + os.write(fd, reader()) + os.close(fd) + del reader + yield pathlib.Path(raw_path) + finally: + try: + os.remove(raw_path) + except (FileNotFoundError, PermissionError): + pass + + +@functools.singledispatch +def as_file(path): + """ + Given a Traversable object, return that object as a + path on the local file system in a context manager. + """ + return _tempfile(path.read_bytes, suffix=path.name) + + +@as_file.register(pathlib.Path) +@contextlib.contextmanager +def _(path): + """ + Degenerate behavior for pathlib.Path objects. + """ + yield path diff --git a/pipenv/vendor/importlib_resources/_compat.py b/pipenv/vendor/importlib_resources/_compat.py new file mode 100644 index 00000000..ce9d4374 --- /dev/null +++ b/pipenv/vendor/importlib_resources/_compat.py @@ -0,0 +1,98 @@ +# flake8: noqa + +import abc +import sys +import pathlib +from contextlib import suppress + +if sys.version_info >= (3, 10): + from zipfile import Path as ZipPath # type: ignore +else: + from pipenv.vendor.zipp import Path as ZipPath # type: ignore + + +try: + from typing import runtime_checkable # type: ignore +except ImportError: + + def runtime_checkable(cls): # type: ignore + return cls + + +try: + from typing import Protocol # type: ignore +except ImportError: + Protocol = abc.ABC # type: ignore + + +class TraversableResourcesLoader: + """ + Adapt loaders to provide TraversableResources and other + compatibility. + + Used primarily for Python 3.9 and earlier where the native + loaders do not yet implement TraversableResources. + """ + + def __init__(self, spec): + self.spec = spec + + @property + def path(self): + return self.spec.origin + + def get_resource_reader(self, name): + from . import readers, _adapters + + def _zip_reader(spec): + with suppress(AttributeError): + return readers.ZipReader(spec.loader, spec.name) + + def _namespace_reader(spec): + with suppress(AttributeError, ValueError): + return readers.NamespaceReader(spec.submodule_search_locations) + + def _available_reader(spec): + with suppress(AttributeError): + return spec.loader.get_resource_reader(spec.name) + + def _native_reader(spec): + reader = _available_reader(spec) + return reader if hasattr(reader, 'files') else None + + def _file_reader(spec): + try: + path = pathlib.Path(self.path) + except TypeError: + return None + if path.exists(): + return readers.FileReader(self) + + return ( + # native reader if it supplies 'files' + _native_reader(self.spec) + or + # local ZipReader if a zip module + _zip_reader(self.spec) + or + # local NamespaceReader if a namespace module + _namespace_reader(self.spec) + or + # local FileReader + _file_reader(self.spec) + # fallback - adapt the spec ResourceReader to TraversableReader + or _adapters.CompatibilityFiles(self.spec) + ) + + +def wrap_spec(package): + """ + Construct a package spec with traversable compatibility + on the spec/loader/reader. + + Supersedes _adapters.wrap_spec to use TraversableResourcesLoader + from above for older Python compatibility (<3.10). + """ + from . import _adapters + + return _adapters.SpecLoaderAdapter(package.__spec__, TraversableResourcesLoader) diff --git a/pipenv/vendor/importlib_resources/_itertools.py b/pipenv/vendor/importlib_resources/_itertools.py new file mode 100644 index 00000000..dd45f2f0 --- /dev/null +++ b/pipenv/vendor/importlib_resources/_itertools.py @@ -0,0 +1,19 @@ +from itertools import filterfalse + + +def unique_everseen(iterable, key=None): + "List unique elements, preserving order. Remember all elements ever seen." + # unique_everseen('AAAABBBCCDAABBB') --> A B C D + # unique_everseen('ABBCcAD', str.lower) --> A B C D + seen = set() + seen_add = seen.add + if key is None: + for element in filterfalse(seen.__contains__, iterable): + seen_add(element) + yield element + else: + for element in iterable: + k = key(element) + if k not in seen: + seen_add(k) + yield element diff --git a/pipenv/vendor/importlib_resources/_legacy.py b/pipenv/vendor/importlib_resources/_legacy.py new file mode 100644 index 00000000..3b9a5342 --- /dev/null +++ b/pipenv/vendor/importlib_resources/_legacy.py @@ -0,0 +1,85 @@ +import os +import pathlib +import types + +from typing import Union, Iterable, ContextManager +from typing.io import BinaryIO, TextIO + +from . import _common + +Package = Union[types.ModuleType, str] +Resource = Union[str, os.PathLike] + + +def open_binary(package: Package, resource: Resource) -> BinaryIO: + """Return a file-like object opened for binary reading of the resource.""" + return (_common.files(package) / _common.normalize_path(resource)).open('rb') + + +def read_binary(package: Package, resource: Resource) -> bytes: + """Return the binary contents of the resource.""" + return (_common.files(package) / _common.normalize_path(resource)).read_bytes() + + +def open_text( + package: Package, + resource: Resource, + encoding: str = 'utf-8', + errors: str = 'strict', +) -> TextIO: + """Return a file-like object opened for text reading of the resource.""" + return (_common.files(package) / _common.normalize_path(resource)).open( + 'r', encoding=encoding, errors=errors + ) + + +def read_text( + package: Package, + resource: Resource, + encoding: str = 'utf-8', + errors: str = 'strict', +) -> str: + """Return the decoded string of the resource. + + The decoding-related arguments have the same semantics as those of + bytes.decode(). + """ + with open_text(package, resource, encoding, errors) as fp: + return fp.read() + + +def contents(package: Package) -> Iterable[str]: + """Return an iterable of entries in `package`. + + Note that not all entries are resources. Specifically, directories are + not considered resources. Use `is_resource()` on each entry returned here + to check if it is a resource or not. + """ + return [path.name for path in _common.files(package).iterdir()] + + +def is_resource(package: Package, name: str) -> bool: + """True if `name` is a resource inside `package`. + + Directories are *not* resources. + """ + resource = _common.normalize_path(name) + return any( + traversable.name == resource and traversable.is_file() + for traversable in _common.files(package).iterdir() + ) + + +def path( + package: Package, + resource: Resource, +) -> ContextManager[pathlib.Path]: + """A context manager providing a file path object to the resource. + + If the resource does not already exist on its own on the file system, + a temporary file will be created. If the file was created, the file + will be deleted upon exiting the context manager (no exception is + raised if the file was deleted prior to the context manager + exiting). + """ + return _common.as_file(_common.files(package) / _common.normalize_path(resource)) diff --git a/pipenv/vendor/importlib_resources/abc.py b/pipenv/vendor/importlib_resources/abc.py new file mode 100644 index 00000000..56dc8127 --- /dev/null +++ b/pipenv/vendor/importlib_resources/abc.py @@ -0,0 +1,137 @@ +import abc +from typing import BinaryIO, Iterable, Text + +from ._compat import runtime_checkable, Protocol + + +class ResourceReader(metaclass=abc.ABCMeta): + """Abstract base class for loaders to provide resource reading support.""" + + @abc.abstractmethod + def open_resource(self, resource: Text) -> BinaryIO: + """Return an opened, file-like object for binary reading. + + The 'resource' argument is expected to represent only a file name. + If the resource cannot be found, FileNotFoundError is raised. + """ + # This deliberately raises FileNotFoundError instead of + # NotImplementedError so that if this method is accidentally called, + # it'll still do the right thing. + raise FileNotFoundError + + @abc.abstractmethod + def resource_path(self, resource: Text) -> Text: + """Return the file system path to the specified resource. + + The 'resource' argument is expected to represent only a file name. + If the resource does not exist on the file system, raise + FileNotFoundError. + """ + # This deliberately raises FileNotFoundError instead of + # NotImplementedError so that if this method is accidentally called, + # it'll still do the right thing. + raise FileNotFoundError + + @abc.abstractmethod + def is_resource(self, path: Text) -> bool: + """Return True if the named 'path' is a resource. + + Files are resources, directories are not. + """ + raise FileNotFoundError + + @abc.abstractmethod + def contents(self) -> Iterable[str]: + """Return an iterable of entries in `package`.""" + raise FileNotFoundError + + +@runtime_checkable +class Traversable(Protocol): + """ + An object with a subset of pathlib.Path methods suitable for + traversing directories and opening files. + """ + + @abc.abstractmethod + def iterdir(self): + """ + Yield Traversable objects in self + """ + + def read_bytes(self): + """ + Read contents of self as bytes + """ + with self.open('rb') as strm: + return strm.read() + + def read_text(self, encoding=None): + """ + Read contents of self as text + """ + with self.open(encoding=encoding) as strm: + return strm.read() + + @abc.abstractmethod + def is_dir(self) -> bool: + """ + Return True if self is a dir + """ + + @abc.abstractmethod + def is_file(self) -> bool: + """ + Return True if self is a file + """ + + @abc.abstractmethod + def joinpath(self, child): + """ + Return Traversable child in self + """ + + def __truediv__(self, child): + """ + Return Traversable child in self + """ + return self.joinpath(child) + + @abc.abstractmethod + def open(self, mode='r', *args, **kwargs): + """ + mode may be 'r' or 'rb' to open as text or binary. Return a handle + suitable for reading (same as pathlib.Path.open). + + When opening as text, accepts encoding parameters such as those + accepted by io.TextIOWrapper. + """ + + @abc.abstractproperty + def name(self) -> str: + """ + The base name of this object without any parent references. + """ + + +class TraversableResources(ResourceReader): + """ + The required interface for providing traversable + resources. + """ + + @abc.abstractmethod + def files(self): + """Return a Traversable object for the loaded package.""" + + def open_resource(self, resource): + return self.files().joinpath(resource).open('rb') + + def resource_path(self, resource): + raise FileNotFoundError(resource) + + def is_resource(self, path): + return self.files().joinpath(path).is_file() + + def contents(self): + return (item.name for item in self.files().iterdir()) diff --git a/pipenv/vendor/importlib_resources/py.typed b/pipenv/vendor/importlib_resources/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/pipenv/vendor/importlib_resources/readers.py b/pipenv/vendor/importlib_resources/readers.py new file mode 100644 index 00000000..f1190ca4 --- /dev/null +++ b/pipenv/vendor/importlib_resources/readers.py @@ -0,0 +1,122 @@ +import collections +import pathlib +import operator + +from . import abc + +from ._itertools import unique_everseen +from ._compat import ZipPath + + +def remove_duplicates(items): + return iter(collections.OrderedDict.fromkeys(items)) + + +class FileReader(abc.TraversableResources): + def __init__(self, loader): + self.path = pathlib.Path(loader.path).parent + + def resource_path(self, resource): + """ + Return the file system path to prevent + `resources.path()` from creating a temporary + copy. + """ + return str(self.path.joinpath(resource)) + + def files(self): + return self.path + + +class ZipReader(abc.TraversableResources): + def __init__(self, loader, module): + _, _, name = module.rpartition('.') + self.prefix = loader.prefix.replace('\\', '/') + name + '/' + self.archive = loader.archive + + def open_resource(self, resource): + try: + return super().open_resource(resource) + except KeyError as exc: + raise FileNotFoundError(exc.args[0]) + + def is_resource(self, path): + # workaround for `zipfile.Path.is_file` returning true + # for non-existent paths. + target = self.files().joinpath(path) + return target.is_file() and target.exists() + + def files(self): + return ZipPath(self.archive, self.prefix) + + +class MultiplexedPath(abc.Traversable): + """ + Given a series of Traversable objects, implement a merged + version of the interface across all objects. Useful for + namespace packages which may be multihomed at a single + name. + """ + + def __init__(self, *paths): + self._paths = list(map(pathlib.Path, remove_duplicates(paths))) + if not self._paths: + message = 'MultiplexedPath must contain at least one path' + raise FileNotFoundError(message) + if not all(path.is_dir() for path in self._paths): + raise NotADirectoryError('MultiplexedPath only supports directories') + + def iterdir(self): + files = (file for path in self._paths for file in path.iterdir()) + return unique_everseen(files, key=operator.attrgetter('name')) + + def read_bytes(self): + raise FileNotFoundError(f'{self} is not a file') + + def read_text(self, *args, **kwargs): + raise FileNotFoundError(f'{self} is not a file') + + def is_dir(self): + return True + + def is_file(self): + return False + + def joinpath(self, child): + # first try to find child in current paths + for file in self.iterdir(): + if file.name == child: + return file + # if it does not exist, construct it with the first path + return self._paths[0] / child + + __truediv__ = joinpath + + def open(self, *args, **kwargs): + raise FileNotFoundError(f'{self} is not a file') + + @property + def name(self): + return self._paths[0].name + + def __repr__(self): + paths = ', '.join(f"'{path}'" for path in self._paths) + return f'MultiplexedPath({paths})' + + +class NamespaceReader(abc.TraversableResources): + def __init__(self, namespace_path): + if 'NamespacePath' not in str(namespace_path): + raise ValueError('Invalid path') + self.path = MultiplexedPath(*list(namespace_path)) + + def resource_path(self, resource): + """ + Return the file system path to prevent + `resources.path()` from creating a temporary + copy. + """ + return str(self.path.joinpath(resource)) + + def files(self): + return self.path diff --git a/pipenv/vendor/importlib_resources/simple.py b/pipenv/vendor/importlib_resources/simple.py new file mode 100644 index 00000000..da073cbd --- /dev/null +++ b/pipenv/vendor/importlib_resources/simple.py @@ -0,0 +1,116 @@ +""" +Interface adapters for low-level readers. +""" + +import abc +import io +import itertools +from typing import BinaryIO, List + +from .abc import Traversable, TraversableResources + + +class SimpleReader(abc.ABC): + """ + The minimum, low-level interface required from a resource + provider. + """ + + @abc.abstractproperty + def package(self): + # type: () -> str + """ + The name of the package for which this reader loads resources. + """ + + @abc.abstractmethod + def children(self): + # type: () -> List['SimpleReader'] + """ + Obtain an iterable of SimpleReader for available + child containers (e.g. directories). + """ + + @abc.abstractmethod + def resources(self): + # type: () -> List[str] + """ + Obtain available named resources for this virtual package. + """ + + @abc.abstractmethod + def open_binary(self, resource): + # type: (str) -> BinaryIO + """ + Obtain a File-like for a named resource. + """ + + @property + def name(self): + return self.package.split('.')[-1] + + +class ResourceHandle(Traversable): + """ + Handle to a named resource in a ResourceReader. + """ + + def __init__(self, parent, name): + # type: (ResourceContainer, str) -> None + self.parent = parent + self.name = name # type: ignore + + def is_file(self): + return True + + def is_dir(self): + return False + + def open(self, mode='r', *args, **kwargs): + stream = self.parent.reader.open_binary(self.name) + if 'b' not in mode: + stream = io.TextIOWrapper(*args, **kwargs) + return stream + + def joinpath(self, name): + raise RuntimeError("Cannot traverse into a resource") + + +class ResourceContainer(Traversable): + """ + Traversable container for a package's resources via its reader. + """ + + def __init__(self, reader): + # type: (SimpleReader) -> None + self.reader = reader + + def is_dir(self): + return True + + def is_file(self): + return False + + def iterdir(self): + files = (ResourceHandle(self, name) for name in self.reader.resources) + dirs = map(ResourceContainer, self.reader.children()) + return itertools.chain(files, dirs) + + def open(self, *args, **kwargs): + raise IsADirectoryError() + + def joinpath(self, name): + return next( + traversable for traversable in self.iterdir() if traversable.name == name + ) + + +class TraversableReader(TraversableResources, SimpleReader): + """ + A TraversableResources based on SimpleReader. Resource providers + may derive from this class to provide the TraversableResources + interface by supplying the SimpleReader interface. + """ + + def files(self): + return ResourceContainer(self) diff --git a/pipenv/vendor/importlib_resources/tests/__init__.py b/pipenv/vendor/importlib_resources/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pipenv/vendor/importlib_resources/tests/_compat.py b/pipenv/vendor/importlib_resources/tests/_compat.py new file mode 100644 index 00000000..4c99cffd --- /dev/null +++ b/pipenv/vendor/importlib_resources/tests/_compat.py @@ -0,0 +1,19 @@ +import os + + +try: + from test.support import import_helper # type: ignore +except ImportError: + # Python 3.9 and earlier + class import_helper: # type: ignore + from test.support import modules_setup, modules_cleanup + + +try: + # Python 3.10 + from test.support.os_helper import unlink +except ImportError: + from test.support import unlink as _unlink + + def unlink(target): + return _unlink(os.fspath(target)) diff --git a/pipenv/vendor/importlib_resources/tests/data01/__init__.py b/pipenv/vendor/importlib_resources/tests/data01/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pipenv/vendor/importlib_resources/tests/data01/binary.file b/pipenv/vendor/importlib_resources/tests/data01/binary.file new file mode 100644 index 00000000..eaf36c1d Binary files /dev/null and b/pipenv/vendor/importlib_resources/tests/data01/binary.file differ diff --git a/pipenv/vendor/importlib_resources/tests/data01/subdirectory/__init__.py b/pipenv/vendor/importlib_resources/tests/data01/subdirectory/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pipenv/vendor/importlib_resources/tests/data01/subdirectory/binary.file b/pipenv/vendor/importlib_resources/tests/data01/subdirectory/binary.file new file mode 100644 index 00000000..eaf36c1d Binary files /dev/null and b/pipenv/vendor/importlib_resources/tests/data01/subdirectory/binary.file differ diff --git a/pipenv/vendor/importlib_resources/tests/data01/utf-16.file b/pipenv/vendor/importlib_resources/tests/data01/utf-16.file new file mode 100644 index 00000000..2cb77229 Binary files /dev/null and b/pipenv/vendor/importlib_resources/tests/data01/utf-16.file differ diff --git a/pipenv/vendor/importlib_resources/tests/data01/utf-8.file b/pipenv/vendor/importlib_resources/tests/data01/utf-8.file new file mode 100644 index 00000000..1c0132ad --- /dev/null +++ b/pipenv/vendor/importlib_resources/tests/data01/utf-8.file @@ -0,0 +1 @@ +Hello, UTF-8 world! diff --git a/pipenv/vendor/importlib_resources/tests/data02/__init__.py b/pipenv/vendor/importlib_resources/tests/data02/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pipenv/vendor/importlib_resources/tests/data02/one/__init__.py b/pipenv/vendor/importlib_resources/tests/data02/one/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pipenv/vendor/importlib_resources/tests/data02/one/resource1.txt b/pipenv/vendor/importlib_resources/tests/data02/one/resource1.txt new file mode 100644 index 00000000..61a813e4 --- /dev/null +++ b/pipenv/vendor/importlib_resources/tests/data02/one/resource1.txt @@ -0,0 +1 @@ +one resource diff --git a/pipenv/vendor/importlib_resources/tests/data02/two/__init__.py b/pipenv/vendor/importlib_resources/tests/data02/two/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pipenv/vendor/importlib_resources/tests/data02/two/resource2.txt b/pipenv/vendor/importlib_resources/tests/data02/two/resource2.txt new file mode 100644 index 00000000..a80ce46e --- /dev/null +++ b/pipenv/vendor/importlib_resources/tests/data02/two/resource2.txt @@ -0,0 +1 @@ +two resource diff --git a/pipenv/vendor/importlib_resources/tests/namespacedata01/binary.file b/pipenv/vendor/importlib_resources/tests/namespacedata01/binary.file new file mode 100644 index 00000000..eaf36c1d Binary files /dev/null and b/pipenv/vendor/importlib_resources/tests/namespacedata01/binary.file differ diff --git a/pipenv/vendor/importlib_resources/tests/namespacedata01/utf-16.file b/pipenv/vendor/importlib_resources/tests/namespacedata01/utf-16.file new file mode 100644 index 00000000..2cb77229 Binary files /dev/null and b/pipenv/vendor/importlib_resources/tests/namespacedata01/utf-16.file differ diff --git a/pipenv/vendor/importlib_resources/tests/namespacedata01/utf-8.file b/pipenv/vendor/importlib_resources/tests/namespacedata01/utf-8.file new file mode 100644 index 00000000..1c0132ad --- /dev/null +++ b/pipenv/vendor/importlib_resources/tests/namespacedata01/utf-8.file @@ -0,0 +1 @@ +Hello, UTF-8 world! diff --git a/pipenv/vendor/importlib_resources/tests/test_compatibilty_files.py b/pipenv/vendor/importlib_resources/tests/test_compatibilty_files.py new file mode 100644 index 00000000..1e5a1ce2 --- /dev/null +++ b/pipenv/vendor/importlib_resources/tests/test_compatibilty_files.py @@ -0,0 +1,102 @@ +import io +import unittest + +import pipenv.vendor.importlib_resources as resources + +from pipenv.vendor.importlib_resources._adapters import ( + CompatibilityFiles, + wrap_spec, +) + +from . import util + + +class CompatibilityFilesTests(unittest.TestCase): + @property + def package(self): + bytes_data = io.BytesIO(b'Hello, world!') + return util.create_package( + file=bytes_data, + path='some_path', + contents=('a', 'b', 'c'), + ) + + @property + def files(self): + return resources.files(self.package) + + def test_spec_path_iter(self): + self.assertEqual( + sorted(path.name for path in self.files.iterdir()), + ['a', 'b', 'c'], + ) + + def test_child_path_iter(self): + self.assertEqual(list((self.files / 'a').iterdir()), []) + + def test_orphan_path_iter(self): + self.assertEqual(list((self.files / 'a' / 'a').iterdir()), []) + self.assertEqual(list((self.files / 'a' / 'a' / 'a').iterdir()), []) + + def test_spec_path_is(self): + self.assertFalse(self.files.is_file()) + self.assertFalse(self.files.is_dir()) + + def test_child_path_is(self): + self.assertTrue((self.files / 'a').is_file()) + self.assertFalse((self.files / 'a').is_dir()) + + def test_orphan_path_is(self): + self.assertFalse((self.files / 'a' / 'a').is_file()) + self.assertFalse((self.files / 'a' / 'a').is_dir()) + self.assertFalse((self.files / 'a' / 'a' / 'a').is_file()) + self.assertFalse((self.files / 'a' / 'a' / 'a').is_dir()) + + def test_spec_path_name(self): + self.assertEqual(self.files.name, 'testingpackage') + + def test_child_path_name(self): + self.assertEqual((self.files / 'a').name, 'a') + + def test_orphan_path_name(self): + self.assertEqual((self.files / 'a' / 'b').name, 'b') + self.assertEqual((self.files / 'a' / 'b' / 'c').name, 'c') + + def test_spec_path_open(self): + self.assertEqual(self.files.read_bytes(), b'Hello, world!') + self.assertEqual(self.files.read_text(), 'Hello, world!') + + def test_child_path_open(self): + self.assertEqual((self.files / 'a').read_bytes(), b'Hello, world!') + self.assertEqual((self.files / 'a').read_text(), 'Hello, world!') + + def test_orphan_path_open(self): + with self.assertRaises(FileNotFoundError): + (self.files / 'a' / 'b').read_bytes() + with self.assertRaises(FileNotFoundError): + (self.files / 'a' / 'b' / 'c').read_bytes() + + def test_open_invalid_mode(self): + with self.assertRaises(ValueError): + self.files.open('0') + + def test_orphan_path_invalid(self): + with self.assertRaises(ValueError): + CompatibilityFiles.OrphanPath() + + def test_wrap_spec(self): + spec = wrap_spec(self.package) + self.assertIsInstance(spec.loader.get_resource_reader(None), CompatibilityFiles) + + +class CompatibilityFilesNoReaderTests(unittest.TestCase): + @property + def package(self): + return util.create_package_from_loader(None) + + @property + def files(self): + return resources.files(self.package) + + def test_spec_path_joinpath(self): + self.assertIsInstance(self.files / 'a', CompatibilityFiles.OrphanPath) diff --git a/pipenv/vendor/importlib_resources/tests/test_contents.py b/pipenv/vendor/importlib_resources/tests/test_contents.py new file mode 100644 index 00000000..a73c4888 --- /dev/null +++ b/pipenv/vendor/importlib_resources/tests/test_contents.py @@ -0,0 +1,42 @@ +import unittest +import pipenv.vendor.importlib_resources as resources + +from . import data01 +from . import util + + +class ContentsTests: + expected = { + '__init__.py', + 'binary.file', + 'subdirectory', + 'utf-16.file', + 'utf-8.file', + } + + def test_contents(self): + assert self.expected <= set(resources.contents(self.data)) + + +class ContentsDiskTests(ContentsTests, unittest.TestCase): + def setUp(self): + self.data = data01 + + +class ContentsZipTests(ContentsTests, util.ZipSetup, unittest.TestCase): + pass + + +class ContentsNamespaceTests(ContentsTests, unittest.TestCase): + expected = { + # no __init__ because of namespace design + # no subdirectory as incidental difference in fixture + 'binary.file', + 'utf-16.file', + 'utf-8.file', + } + + def setUp(self): + from . import namespacedata01 + + self.data = namespacedata01 diff --git a/pipenv/vendor/importlib_resources/tests/test_files.py b/pipenv/vendor/importlib_resources/tests/test_files.py new file mode 100644 index 00000000..4826bf94 --- /dev/null +++ b/pipenv/vendor/importlib_resources/tests/test_files.py @@ -0,0 +1,46 @@ +import typing +import unittest + +import pipenv.vendor.importlib_resources as resources +from pipenv.vendor.importlib_resources.abc import Traversable +from . import data01 +from . import util + + +class FilesTests: + def test_read_bytes(self): + files = resources.files(self.data) + actual = files.joinpath('utf-8.file').read_bytes() + assert actual == b'Hello, UTF-8 world!\n' + + def test_read_text(self): + files = resources.files(self.data) + actual = files.joinpath('utf-8.file').read_text(encoding='utf-8') + assert actual == 'Hello, UTF-8 world!\n' + + @unittest.skipUnless( + hasattr(typing, 'runtime_checkable'), + "Only suitable when typing supports runtime_checkable", + ) + def test_traversable(self): + assert isinstance(resources.files(self.data), Traversable) + + +class OpenDiskTests(FilesTests, unittest.TestCase): + def setUp(self): + self.data = data01 + + +class OpenZipTests(FilesTests, util.ZipSetup, unittest.TestCase): + pass + + +class OpenNamespaceTests(FilesTests, unittest.TestCase): + def setUp(self): + from . import namespacedata01 + + self.data = namespacedata01 + + +if __name__ == '__main__': + unittest.main() diff --git a/pipenv/vendor/importlib_resources/tests/test_open.py b/pipenv/vendor/importlib_resources/tests/test_open.py new file mode 100644 index 00000000..f394f01f --- /dev/null +++ b/pipenv/vendor/importlib_resources/tests/test_open.py @@ -0,0 +1,77 @@ +import unittest + +import pipenv.vendor.importlib_resources as resources +from . import data01 +from . import util + + +class CommonBinaryTests(util.CommonTests, unittest.TestCase): + def execute(self, package, path): + with resources.open_binary(package, path): + pass + + +class CommonTextTests(util.CommonTests, unittest.TestCase): + def execute(self, package, path): + with resources.open_text(package, path): + pass + + +class OpenTests: + def test_open_binary(self): + with resources.open_binary(self.data, 'utf-8.file') as fp: + result = fp.read() + self.assertEqual(result, b'Hello, UTF-8 world!\n') + + def test_open_text_default_encoding(self): + with resources.open_text(self.data, 'utf-8.file') as fp: + result = fp.read() + self.assertEqual(result, 'Hello, UTF-8 world!\n') + + def test_open_text_given_encoding(self): + with resources.open_text(self.data, 'utf-16.file', 'utf-16', 'strict') as fp: + result = fp.read() + self.assertEqual(result, 'Hello, UTF-16 world!\n') + + def test_open_text_with_errors(self): + # Raises UnicodeError without the 'errors' argument. + with resources.open_text(self.data, 'utf-16.file', 'utf-8', 'strict') as fp: + self.assertRaises(UnicodeError, fp.read) + with resources.open_text(self.data, 'utf-16.file', 'utf-8', 'ignore') as fp: + result = fp.read() + self.assertEqual( + result, + 'H\x00e\x00l\x00l\x00o\x00,\x00 ' + '\x00U\x00T\x00F\x00-\x001\x006\x00 ' + '\x00w\x00o\x00r\x00l\x00d\x00!\x00\n\x00', + ) + + def test_open_binary_FileNotFoundError(self): + self.assertRaises( + FileNotFoundError, resources.open_binary, self.data, 'does-not-exist' + ) + + def test_open_text_FileNotFoundError(self): + self.assertRaises( + FileNotFoundError, resources.open_text, self.data, 'does-not-exist' + ) + + +class OpenDiskTests(OpenTests, unittest.TestCase): + def setUp(self): + self.data = data01 + + +class OpenDiskNamespaceTests(OpenTests, unittest.TestCase): + def setUp(self): + from . import namespacedata01 + + self.data = namespacedata01 + + +class OpenZipTests(OpenTests, util.ZipSetup, unittest.TestCase): + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/pipenv/vendor/importlib_resources/tests/test_path.py b/pipenv/vendor/importlib_resources/tests/test_path.py new file mode 100644 index 00000000..964b8a6b --- /dev/null +++ b/pipenv/vendor/importlib_resources/tests/test_path.py @@ -0,0 +1,61 @@ +import io +import unittest + +import pipenv.vendor.importlib_resources as resources +from . import data01 +from . import util + + +class CommonTests(util.CommonTests, unittest.TestCase): + def execute(self, package, path): + with resources.path(package, path): + pass + + +class PathTests: + def test_reading(self): + # Path should be readable. + # Test also implicitly verifies the returned object is a pathlib.Path + # instance. + with resources.path(self.data, 'utf-8.file') as path: + self.assertTrue(path.name.endswith("utf-8.file"), repr(path)) + # pathlib.Path.read_text() was introduced in Python 3.5. + with path.open('r', encoding='utf-8') as file: + text = file.read() + self.assertEqual('Hello, UTF-8 world!\n', text) + + +class PathDiskTests(PathTests, unittest.TestCase): + data = data01 + + def test_natural_path(self): + """ + Guarantee the internal implementation detail that + file-system-backed resources do not get the tempdir + treatment. + """ + with resources.path(self.data, 'utf-8.file') as path: + assert 'data' in str(path) + + +class PathMemoryTests(PathTests, unittest.TestCase): + def setUp(self): + file = io.BytesIO(b'Hello, UTF-8 world!\n') + self.addCleanup(file.close) + self.data = util.create_package( + file=file, path=FileNotFoundError("package exists only in memory") + ) + self.data.__spec__.origin = None + self.data.__spec__.has_location = False + + +class PathZipTests(PathTests, util.ZipSetup, unittest.TestCase): + def test_remove_in_context_manager(self): + # It is not an error if the file that was temporarily stashed on the + # file system is removed inside the `with` stanza. + with resources.path(self.data, 'utf-8.file') as path: + path.unlink() + + +if __name__ == '__main__': + unittest.main() diff --git a/pipenv/vendor/importlib_resources/tests/test_read.py b/pipenv/vendor/importlib_resources/tests/test_read.py new file mode 100644 index 00000000..3ca2852d --- /dev/null +++ b/pipenv/vendor/importlib_resources/tests/test_read.py @@ -0,0 +1,67 @@ +import unittest +import pipenv.vendor.importlib_resources as resources + +from . import data01 +from . import util +from importlib import import_module + + +class CommonBinaryTests(util.CommonTests, unittest.TestCase): + def execute(self, package, path): + resources.read_binary(package, path) + + +class CommonTextTests(util.CommonTests, unittest.TestCase): + def execute(self, package, path): + resources.read_text(package, path) + + +class ReadTests: + def test_read_binary(self): + result = resources.read_binary(self.data, 'binary.file') + self.assertEqual(result, b'\0\1\2\3') + + def test_read_text_default_encoding(self): + result = resources.read_text(self.data, 'utf-8.file') + self.assertEqual(result, 'Hello, UTF-8 world!\n') + + def test_read_text_given_encoding(self): + result = resources.read_text(self.data, 'utf-16.file', encoding='utf-16') + self.assertEqual(result, 'Hello, UTF-16 world!\n') + + def test_read_text_with_errors(self): + # Raises UnicodeError without the 'errors' argument. + self.assertRaises(UnicodeError, resources.read_text, self.data, 'utf-16.file') + result = resources.read_text(self.data, 'utf-16.file', errors='ignore') + self.assertEqual( + result, + 'H\x00e\x00l\x00l\x00o\x00,\x00 ' + '\x00U\x00T\x00F\x00-\x001\x006\x00 ' + '\x00w\x00o\x00r\x00l\x00d\x00!\x00\n\x00', + ) + + +class ReadDiskTests(ReadTests, unittest.TestCase): + data = data01 + + +class ReadZipTests(ReadTests, util.ZipSetup, unittest.TestCase): + def test_read_submodule_resource(self): + submodule = import_module('ziptestdata.subdirectory') + result = resources.read_binary(submodule, 'binary.file') + self.assertEqual(result, b'\0\1\2\3') + + def test_read_submodule_resource_by_name(self): + result = resources.read_binary('ziptestdata.subdirectory', 'binary.file') + self.assertEqual(result, b'\0\1\2\3') + + +class ReadNamespaceTests(ReadTests, unittest.TestCase): + def setUp(self): + from . import namespacedata01 + + self.data = namespacedata01 + + +if __name__ == '__main__': + unittest.main() diff --git a/pipenv/vendor/importlib_resources/tests/test_reader.py b/pipenv/vendor/importlib_resources/tests/test_reader.py new file mode 100644 index 00000000..409849a3 --- /dev/null +++ b/pipenv/vendor/importlib_resources/tests/test_reader.py @@ -0,0 +1,128 @@ +import os.path +import sys +import pathlib +import unittest + +from importlib import import_module +from pipenv.vendor.importlib_resources.readers import MultiplexedPath, NamespaceReader + + +class MultiplexedPathTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + path = pathlib.Path(__file__).parent / 'namespacedata01' + cls.folder = str(path) + + def test_init_no_paths(self): + with self.assertRaises(FileNotFoundError): + MultiplexedPath() + + def test_init_file(self): + with self.assertRaises(NotADirectoryError): + MultiplexedPath(os.path.join(self.folder, 'binary.file')) + + def test_iterdir(self): + contents = {path.name for path in MultiplexedPath(self.folder).iterdir()} + try: + contents.remove('__pycache__') + except (KeyError, ValueError): + pass + self.assertEqual(contents, {'binary.file', 'utf-16.file', 'utf-8.file'}) + + def test_iterdir_duplicate(self): + data01 = os.path.abspath(os.path.join(__file__, '..', 'data01')) + contents = { + path.name for path in MultiplexedPath(self.folder, data01).iterdir() + } + for remove in ('__pycache__', '__init__.pyc'): + try: + contents.remove(remove) + except (KeyError, ValueError): + pass + self.assertEqual( + contents, + {'__init__.py', 'binary.file', 'subdirectory', 'utf-16.file', 'utf-8.file'}, + ) + + def test_is_dir(self): + self.assertEqual(MultiplexedPath(self.folder).is_dir(), True) + + def test_is_file(self): + self.assertEqual(MultiplexedPath(self.folder).is_file(), False) + + def test_open_file(self): + path = MultiplexedPath(self.folder) + with self.assertRaises(FileNotFoundError): + path.read_bytes() + with self.assertRaises(FileNotFoundError): + path.read_text() + with self.assertRaises(FileNotFoundError): + path.open() + + def test_join_path(self): + prefix = os.path.abspath(os.path.join(__file__, '..')) + data01 = os.path.join(prefix, 'data01') + path = MultiplexedPath(self.folder, data01) + self.assertEqual( + str(path.joinpath('binary.file'))[len(prefix) + 1 :], + os.path.join('namespacedata01', 'binary.file'), + ) + self.assertEqual( + str(path.joinpath('subdirectory'))[len(prefix) + 1 :], + os.path.join('data01', 'subdirectory'), + ) + self.assertEqual( + str(path.joinpath('imaginary'))[len(prefix) + 1 :], + os.path.join('namespacedata01', 'imaginary'), + ) + + def test_repr(self): + self.assertEqual( + repr(MultiplexedPath(self.folder)), + f"MultiplexedPath('{self.folder}')", + ) + + def test_name(self): + self.assertEqual( + MultiplexedPath(self.folder).name, + os.path.basename(self.folder), + ) + + +class NamespaceReaderTest(unittest.TestCase): + site_dir = str(pathlib.Path(__file__).parent) + + @classmethod + def setUpClass(cls): + sys.path.append(cls.site_dir) + + @classmethod + def tearDownClass(cls): + sys.path.remove(cls.site_dir) + + def test_init_error(self): + with self.assertRaises(ValueError): + NamespaceReader(['path1', 'path2']) + + def test_resource_path(self): + namespacedata01 = import_module('namespacedata01') + reader = NamespaceReader(namespacedata01.__spec__.submodule_search_locations) + + root = os.path.abspath(os.path.join(__file__, '..', 'namespacedata01')) + self.assertEqual( + reader.resource_path('binary.file'), os.path.join(root, 'binary.file') + ) + self.assertEqual( + reader.resource_path('imaginary'), os.path.join(root, 'imaginary') + ) + + def test_files(self): + namespacedata01 = import_module('namespacedata01') + reader = NamespaceReader(namespacedata01.__spec__.submodule_search_locations) + root = os.path.abspath(os.path.join(__file__, '..', 'namespacedata01')) + self.assertIsInstance(reader.files(), MultiplexedPath) + self.assertEqual(repr(reader.files()), f"MultiplexedPath('{root}')") + + +if __name__ == '__main__': + unittest.main() diff --git a/pipenv/vendor/importlib_resources/tests/test_resource.py b/pipenv/vendor/importlib_resources/tests/test_resource.py new file mode 100644 index 00000000..2d2d2e3f --- /dev/null +++ b/pipenv/vendor/importlib_resources/tests/test_resource.py @@ -0,0 +1,254 @@ +import sys +import unittest +import pipenv.vendor.importlib_resources as resources +import uuid +import pathlib + +from . import data01 +from . import zipdata01, zipdata02 +from . import util +from importlib import import_module +from ._compat import import_helper, unlink + + +class ResourceTests: + # Subclasses are expected to set the `data` attribute. + + def test_is_resource_good_path(self): + self.assertTrue(resources.is_resource(self.data, 'binary.file')) + + def test_is_resource_missing(self): + self.assertFalse(resources.is_resource(self.data, 'not-a-file')) + + def test_is_resource_subresource_directory(self): + # Directories are not resources. + self.assertFalse(resources.is_resource(self.data, 'subdirectory')) + + def test_contents(self): + contents = set(resources.contents(self.data)) + # There may be cruft in the directory listing of the data directory. + # It could have a __pycache__ directory, + # an artifact of the + # test suite importing these modules, which + # are not germane to this test, so just filter them out. + contents.discard('__pycache__') + self.assertEqual( + sorted(contents), + [ + '__init__.py', + 'binary.file', + 'subdirectory', + 'utf-16.file', + 'utf-8.file', + ], + ) + + +class ResourceDiskTests(ResourceTests, unittest.TestCase): + def setUp(self): + self.data = data01 + + +class ResourceZipTests(ResourceTests, util.ZipSetup, unittest.TestCase): + pass + + +class ResourceLoaderTests(unittest.TestCase): + def test_resource_contents(self): + package = util.create_package( + file=data01, path=data01.__file__, contents=['A', 'B', 'C'] + ) + self.assertEqual(set(resources.contents(package)), {'A', 'B', 'C'}) + + def test_resource_is_resource(self): + package = util.create_package( + file=data01, path=data01.__file__, contents=['A', 'B', 'C', 'D/E', 'D/F'] + ) + self.assertTrue(resources.is_resource(package, 'B')) + + def test_resource_directory_is_not_resource(self): + package = util.create_package( + file=data01, path=data01.__file__, contents=['A', 'B', 'C', 'D/E', 'D/F'] + ) + self.assertFalse(resources.is_resource(package, 'D')) + + def test_resource_missing_is_not_resource(self): + package = util.create_package( + file=data01, path=data01.__file__, contents=['A', 'B', 'C', 'D/E', 'D/F'] + ) + self.assertFalse(resources.is_resource(package, 'Z')) + + +class ResourceCornerCaseTests(unittest.TestCase): + def test_package_has_no_reader_fallback(self): + # Test odd ball packages which: + # 1. Do not have a ResourceReader as a loader + # 2. Are not on the file system + # 3. Are not in a zip file + module = util.create_package( + file=data01, path=data01.__file__, contents=['A', 'B', 'C'] + ) + # Give the module a dummy loader. + module.__loader__ = object() + # Give the module a dummy origin. + module.__file__ = '/path/which/shall/not/be/named' + module.__spec__.loader = module.__loader__ + module.__spec__.origin = module.__file__ + self.assertFalse(resources.is_resource(module, 'A')) + + +class ResourceFromZipsTest01(util.ZipSetupBase, unittest.TestCase): + ZIP_MODULE = zipdata01 # type: ignore + + def test_is_submodule_resource(self): + submodule = import_module('ziptestdata.subdirectory') + self.assertTrue(resources.is_resource(submodule, 'binary.file')) + + def test_read_submodule_resource_by_name(self): + self.assertTrue( + resources.is_resource('ziptestdata.subdirectory', 'binary.file') + ) + + def test_submodule_contents(self): + submodule = import_module('ziptestdata.subdirectory') + self.assertEqual( + set(resources.contents(submodule)), {'__init__.py', 'binary.file'} + ) + + def test_submodule_contents_by_name(self): + self.assertEqual( + set(resources.contents('ziptestdata.subdirectory')), + {'__init__.py', 'binary.file'}, + ) + + +class ResourceFromZipsTest02(util.ZipSetupBase, unittest.TestCase): + ZIP_MODULE = zipdata02 # type: ignore + + def test_unrelated_contents(self): + """ + Test thata zip with two unrelated subpackages return + distinct resources. Ref python/importlib_resources#44. + """ + self.assertEqual( + set(resources.contents('ziptestdata.one')), {'__init__.py', 'resource1.txt'} + ) + self.assertEqual( + set(resources.contents('ziptestdata.two')), {'__init__.py', 'resource2.txt'} + ) + + +class DeletingZipsTest(unittest.TestCase): + """Having accessed resources in a zip file should not keep an open + reference to the zip. + """ + + ZIP_MODULE = zipdata01 + + def setUp(self): + modules = import_helper.modules_setup() + self.addCleanup(import_helper.modules_cleanup, *modules) + + data_path = pathlib.Path(self.ZIP_MODULE.__file__) + data_dir = data_path.parent + self.source_zip_path = data_dir / 'ziptestdata.zip' + self.zip_path = pathlib.Path(f'{uuid.uuid4()}.zip').absolute() + self.zip_path.write_bytes(self.source_zip_path.read_bytes()) + sys.path.append(str(self.zip_path)) + self.data = import_module('ziptestdata') + + def tearDown(self): + try: + sys.path.remove(str(self.zip_path)) + except ValueError: + pass + + try: + del sys.path_importer_cache[str(self.zip_path)] + del sys.modules[self.data.__name__] + except KeyError: + pass + + try: + unlink(self.zip_path) + except OSError: + # If the test fails, this will probably fail too + pass + + def test_contents_does_not_keep_open(self): + c = resources.contents('ziptestdata') + self.zip_path.unlink() + del c + + def test_is_resource_does_not_keep_open(self): + c = resources.is_resource('ziptestdata', 'binary.file') + self.zip_path.unlink() + del c + + def test_is_resource_failure_does_not_keep_open(self): + c = resources.is_resource('ziptestdata', 'not-present') + self.zip_path.unlink() + del c + + @unittest.skip("Desired but not supported.") + def test_path_does_not_keep_open(self): + c = resources.path('ziptestdata', 'binary.file') + self.zip_path.unlink() + del c + + def test_entered_path_does_not_keep_open(self): + # This is what certifi does on import to make its bundle + # available for the process duration. + c = resources.path('ziptestdata', 'binary.file').__enter__() + self.zip_path.unlink() + del c + + def test_read_binary_does_not_keep_open(self): + c = resources.read_binary('ziptestdata', 'binary.file') + self.zip_path.unlink() + del c + + def test_read_text_does_not_keep_open(self): + c = resources.read_text('ziptestdata', 'utf-8.file', encoding='utf-8') + self.zip_path.unlink() + del c + + +class ResourceFromNamespaceTest01(unittest.TestCase): + site_dir = str(pathlib.Path(__file__).parent) + + @classmethod + def setUpClass(cls): + sys.path.append(cls.site_dir) + + @classmethod + def tearDownClass(cls): + sys.path.remove(cls.site_dir) + + def test_is_submodule_resource(self): + self.assertTrue( + resources.is_resource(import_module('namespacedata01'), 'binary.file') + ) + + def test_read_submodule_resource_by_name(self): + self.assertTrue(resources.is_resource('namespacedata01', 'binary.file')) + + def test_submodule_contents(self): + contents = set(resources.contents(import_module('namespacedata01'))) + try: + contents.remove('__pycache__') + except KeyError: + pass + self.assertEqual(contents, {'binary.file', 'utf-8.file', 'utf-16.file'}) + + def test_submodule_contents_by_name(self): + contents = set(resources.contents('namespacedata01')) + try: + contents.remove('__pycache__') + except KeyError: + pass + self.assertEqual(contents, {'binary.file', 'utf-8.file', 'utf-16.file'}) + + +if __name__ == '__main__': + unittest.main() diff --git a/pipenv/vendor/importlib_resources/tests/update-zips.py b/pipenv/vendor/importlib_resources/tests/update-zips.py new file mode 100644 index 00000000..9ef0224c --- /dev/null +++ b/pipenv/vendor/importlib_resources/tests/update-zips.py @@ -0,0 +1,53 @@ +""" +Generate the zip test data files. + +Run to build the tests/zipdataNN/ziptestdata.zip files from +files in tests/dataNN. + +Replaces the file with the working copy, but does commit anything +to the source repo. +""" + +import contextlib +import os +import pathlib +import zipfile + + +def main(): + """ + >>> from unittest import mock + >>> monkeypatch = getfixture('monkeypatch') + >>> monkeypatch.setattr(zipfile, 'ZipFile', mock.MagicMock()) + >>> print(); main() # print workaround for bpo-32509 + <BLANKLINE> + ...data01... -> ziptestdata/... + ... + ...data02... -> ziptestdata/... + ... + """ + suffixes = '01', '02' + tuple(map(generate, suffixes)) + + +def generate(suffix): + root = pathlib.Path(__file__).parent.relative_to(os.getcwd()) + zfpath = root / f'zipdata{suffix}/ziptestdata.zip' + with zipfile.ZipFile(zfpath, 'w') as zf: + for src, rel in walk(root / f'data{suffix}'): + dst = 'ziptestdata' / pathlib.PurePosixPath(rel.as_posix()) + print(src, '->', dst) + zf.write(src, dst) + + +def walk(datapath): + for dirpath, dirnames, filenames in os.walk(datapath): + with contextlib.suppress(KeyError): + dirnames.remove('__pycache__') + for filename in filenames: + res = pathlib.Path(dirpath) / filename + rel = res.relative_to(datapath) + yield res, rel + + +__name__ == '__main__' and main() diff --git a/pipenv/vendor/importlib_resources/tests/util.py b/pipenv/vendor/importlib_resources/tests/util.py new file mode 100644 index 00000000..6ac4332e --- /dev/null +++ b/pipenv/vendor/importlib_resources/tests/util.py @@ -0,0 +1,190 @@ +import abc +import importlib +import io +import sys +import types +from pathlib import Path, PurePath + +from . import data01 +from . import zipdata01 +from ..abc import ResourceReader +from ._compat import import_helper + + +from importlib.machinery import ModuleSpec + + +class Reader(ResourceReader): + def __init__(self, **kwargs): + vars(self).update(kwargs) + + def get_resource_reader(self, package): + return self + + def open_resource(self, path): + self._path = path + if isinstance(self.file, Exception): + raise self.file + return self.file + + def resource_path(self, path_): + self._path = path_ + if isinstance(self.path, Exception): + raise self.path + return self.path + + def is_resource(self, path_): + self._path = path_ + if isinstance(self.path, Exception): + raise self.path + + def part(entry): + return entry.split('/') + + return any( + len(parts) == 1 and parts[0] == path_ for parts in map(part, self._contents) + ) + + def contents(self): + if isinstance(self.path, Exception): + raise self.path + yield from self._contents + + +def create_package_from_loader(loader, is_package=True): + name = 'testingpackage' + module = types.ModuleType(name) + spec = ModuleSpec(name, loader, origin='does-not-exist', is_package=is_package) + module.__spec__ = spec + module.__loader__ = loader + return module + + +def create_package(file=None, path=None, is_package=True, contents=()): + return create_package_from_loader( + Reader(file=file, path=path, _contents=contents), + is_package, + ) + + +class CommonTests(metaclass=abc.ABCMeta): + """ + Tests shared by test_open, test_path, and test_read. + """ + + @abc.abstractmethod + def execute(self, package, path): + """ + Call the pertinent legacy API function (e.g. open_text, path) + on package and path. + """ + + def test_package_name(self): + # Passing in the package name should succeed. + self.execute(data01.__name__, 'utf-8.file') + + def test_package_object(self): + # Passing in the package itself should succeed. + self.execute(data01, 'utf-8.file') + + def test_string_path(self): + # Passing in a string for the path should succeed. + path = 'utf-8.file' + self.execute(data01, path) + + def test_pathlib_path(self): + # Passing in a pathlib.PurePath object for the path should succeed. + path = PurePath('utf-8.file') + self.execute(data01, path) + + def test_absolute_path(self): + # An absolute path is a ValueError. + path = Path(__file__) + full_path = path.parent / 'utf-8.file' + with self.assertRaises(ValueError): + self.execute(data01, full_path) + + def test_relative_path(self): + # A reative path is a ValueError. + with self.assertRaises(ValueError): + self.execute(data01, '../data01/utf-8.file') + + def test_importing_module_as_side_effect(self): + # The anchor package can already be imported. + del sys.modules[data01.__name__] + self.execute(data01.__name__, 'utf-8.file') + + def test_non_package_by_name(self): + # The anchor package cannot be a module. + with self.assertRaises(TypeError): + self.execute(__name__, 'utf-8.file') + + def test_non_package_by_package(self): + # The anchor package cannot be a module. + with self.assertRaises(TypeError): + module = sys.modules['importlib_resources.tests.util'] + self.execute(module, 'utf-8.file') + + def test_missing_path(self): + # Attempting to open or read or request the path for a + # non-existent path should succeed if open_resource + # can return a viable data stream. + bytes_data = io.BytesIO(b'Hello, world!') + package = create_package(file=bytes_data, path=FileNotFoundError()) + self.execute(package, 'utf-8.file') + self.assertEqual(package.__loader__._path, 'utf-8.file') + + def test_extant_path(self): + # Attempting to open or read or request the path when the + # path does exist should still succeed. Does not assert + # anything about the result. + bytes_data = io.BytesIO(b'Hello, world!') + # any path that exists + path = __file__ + package = create_package(file=bytes_data, path=path) + self.execute(package, 'utf-8.file') + self.assertEqual(package.__loader__._path, 'utf-8.file') + + def test_useless_loader(self): + package = create_package(file=FileNotFoundError(), path=FileNotFoundError()) + with self.assertRaises(FileNotFoundError): + self.execute(package, 'utf-8.file') + + +class ZipSetupBase: + ZIP_MODULE = None + + @classmethod + def setUpClass(cls): + data_path = Path(cls.ZIP_MODULE.__file__) + data_dir = data_path.parent + cls._zip_path = str(data_dir / 'ziptestdata.zip') + sys.path.append(cls._zip_path) + cls.data = importlib.import_module('ziptestdata') + + @classmethod + def tearDownClass(cls): + try: + sys.path.remove(cls._zip_path) + except ValueError: + pass + + try: + del sys.path_importer_cache[cls._zip_path] + del sys.modules[cls.data.__name__] + except KeyError: + pass + + try: + del cls.data + del cls._zip_path + except AttributeError: + pass + + def setUp(self): + modules = import_helper.modules_setup() + self.addCleanup(import_helper.modules_cleanup, *modules) + + +class ZipSetup(ZipSetupBase): + ZIP_MODULE = zipdata01 # type: ignore diff --git a/pipenv/vendor/importlib_resources/tests/zipdata01/__init__.py b/pipenv/vendor/importlib_resources/tests/zipdata01/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pipenv/vendor/importlib_resources/tests/zipdata01/ziptestdata.zip b/pipenv/vendor/importlib_resources/tests/zipdata01/ziptestdata.zip new file mode 100644 index 00000000..9a3bb073 Binary files /dev/null and b/pipenv/vendor/importlib_resources/tests/zipdata01/ziptestdata.zip differ diff --git a/pipenv/vendor/importlib_resources/tests/zipdata02/__init__.py b/pipenv/vendor/importlib_resources/tests/zipdata02/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pipenv/vendor/importlib_resources/tests/zipdata02/ziptestdata.zip b/pipenv/vendor/importlib_resources/tests/zipdata02/ziptestdata.zip new file mode 100644 index 00000000..d63ff512 Binary files /dev/null and b/pipenv/vendor/importlib_resources/tests/zipdata02/ziptestdata.zip differ diff --git a/pipenv/vendor/iso8601/__init__.pyi b/pipenv/vendor/iso8601/__init__.pyi new file mode 100644 index 00000000..11b1adcb --- /dev/null +++ b/pipenv/vendor/iso8601/__init__.pyi @@ -0,0 +1 @@ +from .iso8601 import * diff --git a/pipenv/vendor/iso8601/iso8601.py b/pipenv/vendor/iso8601/iso8601.py index 0c149f67..1f54cdfe 100644 --- a/pipenv/vendor/iso8601/iso8601.py +++ b/pipenv/vendor/iso8601/iso8601.py @@ -67,7 +67,7 @@ ISO8601_REGEX = re.compile( re.VERBOSE ) -class ParseError(Exception): +class ParseError(ValueError): """Raised when there is a problem parsing a date string""" if sys.version_info >= (3, 2, 0): diff --git a/pipenv/vendor/iso8601/iso8601.pyi b/pipenv/vendor/iso8601/iso8601.pyi new file mode 100644 index 00000000..ba5fe398 --- /dev/null +++ b/pipenv/vendor/iso8601/iso8601.pyi @@ -0,0 +1,67 @@ +import datetime +import sys +from typing import Any, Mapping, Optional, Pattern, TypeVar + + +__all__ = ["parse_date", "ParseError", "UTC", "FixedOffset"] + + +if sys.version_info >= (3, 0, 0): + basestring = str + +if sys.version_info >= (3, 2): + UTC: datetime.timezone = ... + + def FixedOffset( + offset_hours: float, offset_minutes: float, name: str + ) -> datetime.timezone: + ... + + +else: + ZERO: datetime.timedelta = ... + + class Utc(datetime.tzinfo): + ... + + UTC: Utc = ... + + class FixedOffset(datetime.tzinfo): + def __init__( + self, offset_hours: float, offset_minutes: float, name: str + ) -> None: + ... + + +ISO8601_REGEX: Pattern[basestring] = ... + + +class ParseError(ValueError): + + ... + + +_T = TypeVar("_T") + + +def to_int( + d: Mapping[_T, Any], + key: _T, + default_to_zero: bool = ..., + default: Any = ..., + required: bool = ..., +) -> int: + ... + + +def parse_timezone( + matches: Mapping[basestring, basestring], + default_timezone: Optional[datetime.tzinfo] = ..., +) -> datetime.tzinfo: + ... + + +def parse_date( + datestring: basestring, default_timezone: Optional[datetime.tzinfo] = ... +) -> datetime.datetime: + ... diff --git a/pipenv/vendor/iso8601/test_iso8601.py b/pipenv/vendor/iso8601/test_iso8601.py index 0d01ffbb..24a057f6 100644 --- a/pipenv/vendor/iso8601/test_iso8601.py +++ b/pipenv/vendor/iso8601/test_iso8601.py @@ -7,7 +7,7 @@ import pickle import pytest -from iso8601 import iso8601 +from pipenv.vendor.iso8601 import iso8601 def test_iso8601_regex(): assert iso8601.ISO8601_REGEX.match("2006-10-11T00:14:33Z") diff --git a/pipenv/vendor/jinja2/LICENSE b/pipenv/vendor/jinja2/LICENSE deleted file mode 100644 index 31bf900e..00000000 --- a/pipenv/vendor/jinja2/LICENSE +++ /dev/null @@ -1,31 +0,0 @@ -Copyright (c) 2009 by the Jinja Team, see AUTHORS for more details. - -Some rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - - * The names of the contributors may not be used to endorse or - promote products derived from this software without specific - prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/pipenv/vendor/jinja2/__init__.py b/pipenv/vendor/jinja2/__init__.py deleted file mode 100644 index 42aa763d..00000000 --- a/pipenv/vendor/jinja2/__init__.py +++ /dev/null @@ -1,83 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2 - ~~~~~~ - - Jinja2 is a template engine written in pure Python. It provides a - Django inspired non-XML syntax but supports inline expressions and - an optional sandboxed environment. - - Nutshell - -------- - - Here a small example of a Jinja2 template:: - - {% extends 'base.html' %} - {% block title %}Memberlist{% endblock %} - {% block content %} - <ul> - {% for user in users %} - <li><a href="{{ user.url }}">{{ user.username }}</a></li> - {% endfor %} - </ul> - {% endblock %} - - - :copyright: (c) 2017 by the Jinja Team. - :license: BSD, see LICENSE for more details. -""" -__docformat__ = 'restructuredtext en' -__version__ = '2.10' - -# high level interface -from jinja2.environment import Environment, Template - -# loaders -from jinja2.loaders import BaseLoader, FileSystemLoader, PackageLoader, \ - DictLoader, FunctionLoader, PrefixLoader, ChoiceLoader, \ - ModuleLoader - -# bytecode caches -from jinja2.bccache import BytecodeCache, FileSystemBytecodeCache, \ - MemcachedBytecodeCache - -# undefined types -from jinja2.runtime import Undefined, DebugUndefined, StrictUndefined, \ - make_logging_undefined - -# exceptions -from jinja2.exceptions import TemplateError, UndefinedError, \ - TemplateNotFound, TemplatesNotFound, TemplateSyntaxError, \ - TemplateAssertionError, TemplateRuntimeError - -# decorators and public utilities -from jinja2.filters import environmentfilter, contextfilter, \ - evalcontextfilter -from jinja2.utils import Markup, escape, clear_caches, \ - environmentfunction, evalcontextfunction, contextfunction, \ - is_undefined, select_autoescape - -__all__ = [ - 'Environment', 'Template', 'BaseLoader', 'FileSystemLoader', - 'PackageLoader', 'DictLoader', 'FunctionLoader', 'PrefixLoader', - 'ChoiceLoader', 'BytecodeCache', 'FileSystemBytecodeCache', - 'MemcachedBytecodeCache', 'Undefined', 'DebugUndefined', - 'StrictUndefined', 'TemplateError', 'UndefinedError', 'TemplateNotFound', - 'TemplatesNotFound', 'TemplateSyntaxError', 'TemplateAssertionError', - 'TemplateRuntimeError', - 'ModuleLoader', 'environmentfilter', 'contextfilter', 'Markup', 'escape', - 'environmentfunction', 'contextfunction', 'clear_caches', 'is_undefined', - 'evalcontextfilter', 'evalcontextfunction', 'make_logging_undefined', - 'select_autoescape', -] - - -def _patch_async(): - from jinja2.utils import have_async_gen - if have_async_gen: - from jinja2.asyncsupport import patch_all - patch_all() - - -_patch_async() -del _patch_async diff --git a/pipenv/vendor/jinja2/_compat.py b/pipenv/vendor/jinja2/_compat.py deleted file mode 100644 index 61d85301..00000000 --- a/pipenv/vendor/jinja2/_compat.py +++ /dev/null @@ -1,99 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2._compat - ~~~~~~~~~~~~~~ - - Some py2/py3 compatibility support based on a stripped down - version of six so we don't have to depend on a specific version - of it. - - :copyright: Copyright 2013 by the Jinja team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" -import sys - -PY2 = sys.version_info[0] == 2 -PYPY = hasattr(sys, 'pypy_translation_info') -_identity = lambda x: x - - -if not PY2: - unichr = chr - range_type = range - text_type = str - string_types = (str,) - integer_types = (int,) - - iterkeys = lambda d: iter(d.keys()) - itervalues = lambda d: iter(d.values()) - iteritems = lambda d: iter(d.items()) - - import pickle - from io import BytesIO, StringIO - NativeStringIO = StringIO - - def reraise(tp, value, tb=None): - if value.__traceback__ is not tb: - raise value.with_traceback(tb) - raise value - - ifilter = filter - imap = map - izip = zip - intern = sys.intern - - implements_iterator = _identity - implements_to_string = _identity - encode_filename = _identity - -else: - unichr = unichr - text_type = unicode - range_type = xrange - string_types = (str, unicode) - integer_types = (int, long) - - iterkeys = lambda d: d.iterkeys() - itervalues = lambda d: d.itervalues() - iteritems = lambda d: d.iteritems() - - import cPickle as pickle - from cStringIO import StringIO as BytesIO, StringIO - NativeStringIO = BytesIO - - exec('def reraise(tp, value, tb=None):\n raise tp, value, tb') - - from itertools import imap, izip, ifilter - intern = intern - - def implements_iterator(cls): - cls.next = cls.__next__ - del cls.__next__ - return cls - - def implements_to_string(cls): - cls.__unicode__ = cls.__str__ - cls.__str__ = lambda x: x.__unicode__().encode('utf-8') - return cls - - def encode_filename(filename): - if isinstance(filename, unicode): - return filename.encode('utf-8') - return filename - - -def with_metaclass(meta, *bases): - """Create a base class with a metaclass.""" - # This requires a bit of explanation: the basic idea is to make a - # dummy metaclass for one level of class instantiation that replaces - # itself with the actual metaclass. - class metaclass(type): - def __new__(cls, name, this_bases, d): - return meta(name, bases, d) - return type.__new__(metaclass, 'temporary_class', (), {}) - - -try: - from urllib.parse import quote_from_bytes as url_quote -except ImportError: - from urllib import quote as url_quote diff --git a/pipenv/vendor/jinja2/_identifier.py b/pipenv/vendor/jinja2/_identifier.py deleted file mode 100644 index 2eac35d5..00000000 --- a/pipenv/vendor/jinja2/_identifier.py +++ /dev/null @@ -1,2 +0,0 @@ -# generated by scripts/generate_identifier_pattern.py -pattern = '·̀-ͯ·҃-֑҇-ׇֽֿׁׂׅׄؐ-ًؚ-ٰٟۖ-ۜ۟-۪ۤۧۨ-ܑۭܰ-݊ަ-ް߫-߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࡙࠭-࡛ࣔ-ࣣ࣡-ःऺ-़ा-ॏ॑-ॗॢॣঁ-ঃ়া-ৄেৈো-্ৗৢৣਁ-ਃ਼ਾ-ੂੇੈੋ-੍ੑੰੱੵઁ-ઃ઼ા-ૅે-ૉો-્ૢૣଁ-ଃ଼ା-ୄେୈୋ-୍ୖୗୢୣஂா-ூெ-ைொ-்ௗఀ-ఃా-ౄె-ైొ-్ౕౖౢౣಁ-ಃ಼ಾ-ೄೆ-ೈೊ-್ೕೖೢೣഁ-ഃാ-ൄെ-ൈൊ-്ൗൢൣංඃ්ා-ුූෘ-ෟෲෳัิ-ฺ็-๎ັິ-ູົຼ່-ໍ༹༘༙༵༷༾༿ཱ-྄྆྇ྍ-ྗྙ-ྼ࿆ါ-ှၖ-ၙၞ-ၠၢ-ၤၧ-ၭၱ-ၴႂ-ႍႏႚ-ႝ፝-፟ᜒ-᜔ᜲ-᜴ᝒᝓᝲᝳ឴-៓៝᠋-᠍ᢅᢆᢩᤠ-ᤫᤰ-᤻ᨗ-ᨛᩕ-ᩞ᩠-᩿᩼᪰-᪽ᬀ-ᬄ᬴-᭄᭫-᭳ᮀ-ᮂᮡ-ᮭ᯦-᯳ᰤ-᰷᳐-᳔᳒-᳨᳭ᳲ-᳴᳸᳹᷀-᷵᷻-᷿‿⁀⁔⃐-⃥⃜⃡-⃰℘℮⳯-⵿⳱ⷠ-〪ⷿ-゙゚〯꙯ꙴ-꙽ꚞꚟ꛰꛱ꠂ꠆ꠋꠣ-ꠧꢀꢁꢴ-ꣅ꣠-꣱ꤦ-꤭ꥇ-꥓ꦀ-ꦃ꦳-꧀ꧥꨩ-ꨶꩃꩌꩍꩻ-ꩽꪰꪲ-ꪴꪷꪸꪾ꪿꫁ꫫ-ꫯꫵ꫶ꯣ-ꯪ꯬꯭ﬞ︀-️︠-︯︳︴﹍-﹏_𐇽𐋠𐍶-𐍺𐨁-𐨃𐨅𐨆𐨌-𐨏𐨸-𐨿𐨺𐫦𐫥𑀀-𑀂𑀸-𑁆𑁿-𑂂𑂰-𑂺𑄀-𑄂𑄧-𑅳𑄴𑆀-𑆂𑆳-𑇊𑇀-𑇌𑈬-𑈷𑈾𑋟-𑋪𑌀-𑌃𑌼𑌾-𑍄𑍇𑍈𑍋-𑍍𑍗𑍢𑍣𑍦-𑍬𑍰-𑍴𑐵-𑑆𑒰-𑓃𑖯-𑖵𑖸-𑗀𑗜𑗝𑘰-𑙀𑚫-𑚷𑜝-𑜫𑰯-𑰶𑰸-𑰿𑲒-𑲧𑲩-𑲶𖫰-𖫴𖬰-𖬶𖽑-𖽾𖾏-𖾒𛲝𛲞𝅥-𝅩𝅭-𝅲𝅻-𝆂𝆅-𝆋𝆪-𝆭𝉂-𝉄𝨀-𝨶𝨻-𝩬𝩵𝪄𝪛-𝪟𝪡-𝪯𞀀-𞀆𞀈-𞀘𞀛-𞀡𞀣𞀤𞀦-𞣐𞀪-𞣖𞥄-𞥊󠄀-󠇯' diff --git a/pipenv/vendor/jinja2/asyncfilters.py b/pipenv/vendor/jinja2/asyncfilters.py deleted file mode 100644 index 5c1f46d7..00000000 --- a/pipenv/vendor/jinja2/asyncfilters.py +++ /dev/null @@ -1,146 +0,0 @@ -from functools import wraps - -from jinja2.asyncsupport import auto_aiter -from jinja2 import filters - - -async def auto_to_seq(value): - seq = [] - if hasattr(value, '__aiter__'): - async for item in value: - seq.append(item) - else: - for item in value: - seq.append(item) - return seq - - -async def async_select_or_reject(args, kwargs, modfunc, lookup_attr): - seq, func = filters.prepare_select_or_reject( - args, kwargs, modfunc, lookup_attr) - if seq: - async for item in auto_aiter(seq): - if func(item): - yield item - - -def dualfilter(normal_filter, async_filter): - wrap_evalctx = False - if getattr(normal_filter, 'environmentfilter', False): - is_async = lambda args: args[0].is_async - wrap_evalctx = False - else: - if not getattr(normal_filter, 'evalcontextfilter', False) and \ - not getattr(normal_filter, 'contextfilter', False): - wrap_evalctx = True - is_async = lambda args: args[0].environment.is_async - - @wraps(normal_filter) - def wrapper(*args, **kwargs): - b = is_async(args) - if wrap_evalctx: - args = args[1:] - if b: - return async_filter(*args, **kwargs) - return normal_filter(*args, **kwargs) - - if wrap_evalctx: - wrapper.evalcontextfilter = True - - wrapper.asyncfiltervariant = True - - return wrapper - - -def asyncfiltervariant(original): - def decorator(f): - return dualfilter(original, f) - return decorator - - -@asyncfiltervariant(filters.do_first) -async def do_first(environment, seq): - try: - return await auto_aiter(seq).__anext__() - except StopAsyncIteration: - return environment.undefined('No first item, sequence was empty.') - - -@asyncfiltervariant(filters.do_groupby) -async def do_groupby(environment, value, attribute): - expr = filters.make_attrgetter(environment, attribute) - return [filters._GroupTuple(key, await auto_to_seq(values)) - for key, values in filters.groupby(sorted( - await auto_to_seq(value), key=expr), expr)] - - -@asyncfiltervariant(filters.do_join) -async def do_join(eval_ctx, value, d=u'', attribute=None): - return filters.do_join(eval_ctx, await auto_to_seq(value), d, attribute) - - -@asyncfiltervariant(filters.do_list) -async def do_list(value): - return await auto_to_seq(value) - - -@asyncfiltervariant(filters.do_reject) -async def do_reject(*args, **kwargs): - return async_select_or_reject(args, kwargs, lambda x: not x, False) - - -@asyncfiltervariant(filters.do_rejectattr) -async def do_rejectattr(*args, **kwargs): - return async_select_or_reject(args, kwargs, lambda x: not x, True) - - -@asyncfiltervariant(filters.do_select) -async def do_select(*args, **kwargs): - return async_select_or_reject(args, kwargs, lambda x: x, False) - - -@asyncfiltervariant(filters.do_selectattr) -async def do_selectattr(*args, **kwargs): - return async_select_or_reject(args, kwargs, lambda x: x, True) - - -@asyncfiltervariant(filters.do_map) -async def do_map(*args, **kwargs): - seq, func = filters.prepare_map(args, kwargs) - if seq: - async for item in auto_aiter(seq): - yield func(item) - - -@asyncfiltervariant(filters.do_sum) -async def do_sum(environment, iterable, attribute=None, start=0): - rv = start - if attribute is not None: - func = filters.make_attrgetter(environment, attribute) - else: - func = lambda x: x - async for item in auto_aiter(iterable): - rv += func(item) - return rv - - -@asyncfiltervariant(filters.do_slice) -async def do_slice(value, slices, fill_with=None): - return filters.do_slice(await auto_to_seq(value), slices, fill_with) - - -ASYNC_FILTERS = { - 'first': do_first, - 'groupby': do_groupby, - 'join': do_join, - 'list': do_list, - # we intentionally do not support do_last because that would be - # ridiculous - 'reject': do_reject, - 'rejectattr': do_rejectattr, - 'map': do_map, - 'select': do_select, - 'selectattr': do_selectattr, - 'sum': do_sum, - 'slice': do_slice, -} diff --git a/pipenv/vendor/jinja2/asyncsupport.py b/pipenv/vendor/jinja2/asyncsupport.py deleted file mode 100644 index b1e7b5ce..00000000 --- a/pipenv/vendor/jinja2/asyncsupport.py +++ /dev/null @@ -1,256 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.asyncsupport - ~~~~~~~~~~~~~~~~~~~ - - Has all the code for async support which is implemented as a patch - for supported Python versions. - - :copyright: (c) 2017 by the Jinja Team. - :license: BSD, see LICENSE for more details. -""" -import sys -import asyncio -import inspect -from functools import update_wrapper - -from jinja2.utils import concat, internalcode, Markup -from jinja2.environment import TemplateModule -from jinja2.runtime import LoopContextBase, _last_iteration - - -async def concat_async(async_gen): - rv = [] - async def collect(): - async for event in async_gen: - rv.append(event) - await collect() - return concat(rv) - - -async def generate_async(self, *args, **kwargs): - vars = dict(*args, **kwargs) - try: - async for event in self.root_render_func(self.new_context(vars)): - yield event - except Exception: - exc_info = sys.exc_info() - else: - return - yield self.environment.handle_exception(exc_info, True) - - -def wrap_generate_func(original_generate): - def _convert_generator(self, loop, args, kwargs): - async_gen = self.generate_async(*args, **kwargs) - try: - while 1: - yield loop.run_until_complete(async_gen.__anext__()) - except StopAsyncIteration: - pass - def generate(self, *args, **kwargs): - if not self.environment.is_async: - return original_generate(self, *args, **kwargs) - return _convert_generator(self, asyncio.get_event_loop(), args, kwargs) - return update_wrapper(generate, original_generate) - - -async def render_async(self, *args, **kwargs): - if not self.environment.is_async: - raise RuntimeError('The environment was not created with async mode ' - 'enabled.') - - vars = dict(*args, **kwargs) - ctx = self.new_context(vars) - - try: - return await concat_async(self.root_render_func(ctx)) - except Exception: - exc_info = sys.exc_info() - return self.environment.handle_exception(exc_info, True) - - -def wrap_render_func(original_render): - def render(self, *args, **kwargs): - if not self.environment.is_async: - return original_render(self, *args, **kwargs) - loop = asyncio.get_event_loop() - return loop.run_until_complete(self.render_async(*args, **kwargs)) - return update_wrapper(render, original_render) - - -def wrap_block_reference_call(original_call): - @internalcode - async def async_call(self): - rv = await concat_async(self._stack[self._depth](self._context)) - if self._context.eval_ctx.autoescape: - rv = Markup(rv) - return rv - - @internalcode - def __call__(self): - if not self._context.environment.is_async: - return original_call(self) - return async_call(self) - - return update_wrapper(__call__, original_call) - - -def wrap_macro_invoke(original_invoke): - @internalcode - async def async_invoke(self, arguments, autoescape): - rv = await self._func(*arguments) - if autoescape: - rv = Markup(rv) - return rv - - @internalcode - def _invoke(self, arguments, autoescape): - if not self._environment.is_async: - return original_invoke(self, arguments, autoescape) - return async_invoke(self, arguments, autoescape) - return update_wrapper(_invoke, original_invoke) - - -@internalcode -async def get_default_module_async(self): - if self._module is not None: - return self._module - self._module = rv = await self.make_module_async() - return rv - - -def wrap_default_module(original_default_module): - @internalcode - def _get_default_module(self): - if self.environment.is_async: - raise RuntimeError('Template module attribute is unavailable ' - 'in async mode') - return original_default_module(self) - return _get_default_module - - -async def make_module_async(self, vars=None, shared=False, locals=None): - context = self.new_context(vars, shared, locals) - body_stream = [] - async for item in self.root_render_func(context): - body_stream.append(item) - return TemplateModule(self, context, body_stream) - - -def patch_template(): - from jinja2 import Template - Template.generate = wrap_generate_func(Template.generate) - Template.generate_async = update_wrapper( - generate_async, Template.generate_async) - Template.render_async = update_wrapper( - render_async, Template.render_async) - Template.render = wrap_render_func(Template.render) - Template._get_default_module = wrap_default_module( - Template._get_default_module) - Template._get_default_module_async = get_default_module_async - Template.make_module_async = update_wrapper( - make_module_async, Template.make_module_async) - - -def patch_runtime(): - from jinja2.runtime import BlockReference, Macro - BlockReference.__call__ = wrap_block_reference_call( - BlockReference.__call__) - Macro._invoke = wrap_macro_invoke(Macro._invoke) - - -def patch_filters(): - from jinja2.filters import FILTERS - from jinja2.asyncfilters import ASYNC_FILTERS - FILTERS.update(ASYNC_FILTERS) - - -def patch_all(): - patch_template() - patch_runtime() - patch_filters() - - -async def auto_await(value): - if inspect.isawaitable(value): - return await value - return value - - -async def auto_aiter(iterable): - if hasattr(iterable, '__aiter__'): - async for item in iterable: - yield item - return - for item in iterable: - yield item - - -class AsyncLoopContext(LoopContextBase): - - def __init__(self, async_iterator, undefined, after, length, recurse=None, - depth0=0): - LoopContextBase.__init__(self, undefined, recurse, depth0) - self._async_iterator = async_iterator - self._after = after - self._length = length - - @property - def length(self): - if self._length is None: - raise TypeError('Loop length for some iterators cannot be ' - 'lazily calculated in async mode') - return self._length - - def __aiter__(self): - return AsyncLoopContextIterator(self) - - -class AsyncLoopContextIterator(object): - __slots__ = ('context',) - - def __init__(self, context): - self.context = context - - def __aiter__(self): - return self - - async def __anext__(self): - ctx = self.context - ctx.index0 += 1 - if ctx._after is _last_iteration: - raise StopAsyncIteration() - ctx._before = ctx._current - ctx._current = ctx._after - try: - ctx._after = await ctx._async_iterator.__anext__() - except StopAsyncIteration: - ctx._after = _last_iteration - return ctx._current, ctx - - -async def make_async_loop_context(iterable, undefined, recurse=None, depth0=0): - # Length is more complicated and less efficient in async mode. The - # reason for this is that we cannot know if length will be used - # upfront but because length is a property we cannot lazily execute it - # later. This means that we need to buffer it up and measure :( - # - # We however only do this for actual iterators, not for async - # iterators as blocking here does not seem like the best idea in the - # world. - try: - length = len(iterable) - except (TypeError, AttributeError): - if not hasattr(iterable, '__aiter__'): - iterable = tuple(iterable) - length = len(iterable) - else: - length = None - async_iterator = auto_aiter(iterable) - try: - after = await async_iterator.__anext__() - except StopAsyncIteration: - after = _last_iteration - return AsyncLoopContext(async_iterator, undefined, after, length, recurse, - depth0) diff --git a/pipenv/vendor/jinja2/bccache.py b/pipenv/vendor/jinja2/bccache.py deleted file mode 100644 index 080e527c..00000000 --- a/pipenv/vendor/jinja2/bccache.py +++ /dev/null @@ -1,362 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.bccache - ~~~~~~~~~~~~~~ - - This module implements the bytecode cache system Jinja is optionally - using. This is useful if you have very complex template situations and - the compiliation of all those templates slow down your application too - much. - - Situations where this is useful are often forking web applications that - are initialized on the first request. - - :copyright: (c) 2017 by the Jinja Team. - :license: BSD. -""" -from os import path, listdir -import os -import sys -import stat -import errno -import marshal -import tempfile -import fnmatch -from hashlib import sha1 -from jinja2.utils import open_if_exists -from jinja2._compat import BytesIO, pickle, PY2, text_type - - -# marshal works better on 3.x, one hack less required -if not PY2: - marshal_dump = marshal.dump - marshal_load = marshal.load -else: - - def marshal_dump(code, f): - if isinstance(f, file): - marshal.dump(code, f) - else: - f.write(marshal.dumps(code)) - - def marshal_load(f): - if isinstance(f, file): - return marshal.load(f) - return marshal.loads(f.read()) - - -bc_version = 3 - -# magic version used to only change with new jinja versions. With 2.6 -# we change this to also take Python version changes into account. The -# reason for this is that Python tends to segfault if fed earlier bytecode -# versions because someone thought it would be a good idea to reuse opcodes -# or make Python incompatible with earlier versions. -bc_magic = 'j2'.encode('ascii') + \ - pickle.dumps(bc_version, 2) + \ - pickle.dumps((sys.version_info[0] << 24) | sys.version_info[1]) - - -class Bucket(object): - """Buckets are used to store the bytecode for one template. It's created - and initialized by the bytecode cache and passed to the loading functions. - - The buckets get an internal checksum from the cache assigned and use this - to automatically reject outdated cache material. Individual bytecode - cache subclasses don't have to care about cache invalidation. - """ - - def __init__(self, environment, key, checksum): - self.environment = environment - self.key = key - self.checksum = checksum - self.reset() - - def reset(self): - """Resets the bucket (unloads the bytecode).""" - self.code = None - - def load_bytecode(self, f): - """Loads bytecode from a file or file like object.""" - # make sure the magic header is correct - magic = f.read(len(bc_magic)) - if magic != bc_magic: - self.reset() - return - # the source code of the file changed, we need to reload - checksum = pickle.load(f) - if self.checksum != checksum: - self.reset() - return - # if marshal_load fails then we need to reload - try: - self.code = marshal_load(f) - except (EOFError, ValueError, TypeError): - self.reset() - return - - def write_bytecode(self, f): - """Dump the bytecode into the file or file like object passed.""" - if self.code is None: - raise TypeError('can\'t write empty bucket') - f.write(bc_magic) - pickle.dump(self.checksum, f, 2) - marshal_dump(self.code, f) - - def bytecode_from_string(self, string): - """Load bytecode from a string.""" - self.load_bytecode(BytesIO(string)) - - def bytecode_to_string(self): - """Return the bytecode as string.""" - out = BytesIO() - self.write_bytecode(out) - return out.getvalue() - - -class BytecodeCache(object): - """To implement your own bytecode cache you have to subclass this class - and override :meth:`load_bytecode` and :meth:`dump_bytecode`. Both of - these methods are passed a :class:`~jinja2.bccache.Bucket`. - - A very basic bytecode cache that saves the bytecode on the file system:: - - from os import path - - class MyCache(BytecodeCache): - - def __init__(self, directory): - self.directory = directory - - def load_bytecode(self, bucket): - filename = path.join(self.directory, bucket.key) - if path.exists(filename): - with open(filename, 'rb') as f: - bucket.load_bytecode(f) - - def dump_bytecode(self, bucket): - filename = path.join(self.directory, bucket.key) - with open(filename, 'wb') as f: - bucket.write_bytecode(f) - - A more advanced version of a filesystem based bytecode cache is part of - Jinja2. - """ - - def load_bytecode(self, bucket): - """Subclasses have to override this method to load bytecode into a - bucket. If they are not able to find code in the cache for the - bucket, it must not do anything. - """ - raise NotImplementedError() - - def dump_bytecode(self, bucket): - """Subclasses have to override this method to write the bytecode - from a bucket back to the cache. If it unable to do so it must not - fail silently but raise an exception. - """ - raise NotImplementedError() - - def clear(self): - """Clears the cache. This method is not used by Jinja2 but should be - implemented to allow applications to clear the bytecode cache used - by a particular environment. - """ - - def get_cache_key(self, name, filename=None): - """Returns the unique hash key for this template name.""" - hash = sha1(name.encode('utf-8')) - if filename is not None: - filename = '|' + filename - if isinstance(filename, text_type): - filename = filename.encode('utf-8') - hash.update(filename) - return hash.hexdigest() - - def get_source_checksum(self, source): - """Returns a checksum for the source.""" - return sha1(source.encode('utf-8')).hexdigest() - - def get_bucket(self, environment, name, filename, source): - """Return a cache bucket for the given template. All arguments are - mandatory but filename may be `None`. - """ - key = self.get_cache_key(name, filename) - checksum = self.get_source_checksum(source) - bucket = Bucket(environment, key, checksum) - self.load_bytecode(bucket) - return bucket - - def set_bucket(self, bucket): - """Put the bucket into the cache.""" - self.dump_bytecode(bucket) - - -class FileSystemBytecodeCache(BytecodeCache): - """A bytecode cache that stores bytecode on the filesystem. It accepts - two arguments: The directory where the cache items are stored and a - pattern string that is used to build the filename. - - If no directory is specified a default cache directory is selected. On - Windows the user's temp directory is used, on UNIX systems a directory - is created for the user in the system temp directory. - - The pattern can be used to have multiple separate caches operate on the - same directory. The default pattern is ``'__jinja2_%s.cache'``. ``%s`` - is replaced with the cache key. - - >>> bcc = FileSystemBytecodeCache('/tmp/jinja_cache', '%s.cache') - - This bytecode cache supports clearing of the cache using the clear method. - """ - - def __init__(self, directory=None, pattern='__jinja2_%s.cache'): - if directory is None: - directory = self._get_default_cache_dir() - self.directory = directory - self.pattern = pattern - - def _get_default_cache_dir(self): - def _unsafe_dir(): - raise RuntimeError('Cannot determine safe temp directory. You ' - 'need to explicitly provide one.') - - tmpdir = tempfile.gettempdir() - - # On windows the temporary directory is used specific unless - # explicitly forced otherwise. We can just use that. - if os.name == 'nt': - return tmpdir - if not hasattr(os, 'getuid'): - _unsafe_dir() - - dirname = '_jinja2-cache-%d' % os.getuid() - actual_dir = os.path.join(tmpdir, dirname) - - try: - os.mkdir(actual_dir, stat.S_IRWXU) - except OSError as e: - if e.errno != errno.EEXIST: - raise - try: - os.chmod(actual_dir, stat.S_IRWXU) - actual_dir_stat = os.lstat(actual_dir) - if actual_dir_stat.st_uid != os.getuid() \ - or not stat.S_ISDIR(actual_dir_stat.st_mode) \ - or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU: - _unsafe_dir() - except OSError as e: - if e.errno != errno.EEXIST: - raise - - actual_dir_stat = os.lstat(actual_dir) - if actual_dir_stat.st_uid != os.getuid() \ - or not stat.S_ISDIR(actual_dir_stat.st_mode) \ - or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU: - _unsafe_dir() - - return actual_dir - - def _get_cache_filename(self, bucket): - return path.join(self.directory, self.pattern % bucket.key) - - def load_bytecode(self, bucket): - f = open_if_exists(self._get_cache_filename(bucket), 'rb') - if f is not None: - try: - bucket.load_bytecode(f) - finally: - f.close() - - def dump_bytecode(self, bucket): - f = open(self._get_cache_filename(bucket), 'wb') - try: - bucket.write_bytecode(f) - finally: - f.close() - - def clear(self): - # imported lazily here because google app-engine doesn't support - # write access on the file system and the function does not exist - # normally. - from os import remove - files = fnmatch.filter(listdir(self.directory), self.pattern % '*') - for filename in files: - try: - remove(path.join(self.directory, filename)) - except OSError: - pass - - -class MemcachedBytecodeCache(BytecodeCache): - """This class implements a bytecode cache that uses a memcache cache for - storing the information. It does not enforce a specific memcache library - (tummy's memcache or cmemcache) but will accept any class that provides - the minimal interface required. - - Libraries compatible with this class: - - - `werkzeug <http://werkzeug.pocoo.org/>`_.contrib.cache - - `python-memcached <https://www.tummy.com/Community/software/python-memcached/>`_ - - `cmemcache <http://gijsbert.org/cmemcache/>`_ - - (Unfortunately the django cache interface is not compatible because it - does not support storing binary data, only unicode. You can however pass - the underlying cache client to the bytecode cache which is available - as `django.core.cache.cache._client`.) - - The minimal interface for the client passed to the constructor is this: - - .. class:: MinimalClientInterface - - .. method:: set(key, value[, timeout]) - - Stores the bytecode in the cache. `value` is a string and - `timeout` the timeout of the key. If timeout is not provided - a default timeout or no timeout should be assumed, if it's - provided it's an integer with the number of seconds the cache - item should exist. - - .. method:: get(key) - - Returns the value for the cache key. If the item does not - exist in the cache the return value must be `None`. - - The other arguments to the constructor are the prefix for all keys that - is added before the actual cache key and the timeout for the bytecode in - the cache system. We recommend a high (or no) timeout. - - This bytecode cache does not support clearing of used items in the cache. - The clear method is a no-operation function. - - .. versionadded:: 2.7 - Added support for ignoring memcache errors through the - `ignore_memcache_errors` parameter. - """ - - def __init__(self, client, prefix='jinja2/bytecode/', timeout=None, - ignore_memcache_errors=True): - self.client = client - self.prefix = prefix - self.timeout = timeout - self.ignore_memcache_errors = ignore_memcache_errors - - def load_bytecode(self, bucket): - try: - code = self.client.get(self.prefix + bucket.key) - except Exception: - if not self.ignore_memcache_errors: - raise - code = None - if code is not None: - bucket.bytecode_from_string(code) - - def dump_bytecode(self, bucket): - args = (self.prefix + bucket.key, bucket.bytecode_to_string()) - if self.timeout is not None: - args += (self.timeout,) - try: - self.client.set(*args) - except Exception: - if not self.ignore_memcache_errors: - raise diff --git a/pipenv/vendor/jinja2/compiler.py b/pipenv/vendor/jinja2/compiler.py deleted file mode 100644 index d534a827..00000000 --- a/pipenv/vendor/jinja2/compiler.py +++ /dev/null @@ -1,1721 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.compiler - ~~~~~~~~~~~~~~~ - - Compiles nodes into python code. - - :copyright: (c) 2017 by the Jinja Team. - :license: BSD, see LICENSE for more details. -""" -from itertools import chain -from copy import deepcopy -from keyword import iskeyword as is_python_keyword -from functools import update_wrapper -from jinja2 import nodes -from jinja2.nodes import EvalContext -from jinja2.visitor import NodeVisitor -from jinja2.optimizer import Optimizer -from jinja2.exceptions import TemplateAssertionError -from jinja2.utils import Markup, concat, escape -from jinja2._compat import range_type, text_type, string_types, \ - iteritems, NativeStringIO, imap, izip -from jinja2.idtracking import Symbols, VAR_LOAD_PARAMETER, \ - VAR_LOAD_RESOLVE, VAR_LOAD_ALIAS, VAR_LOAD_UNDEFINED - - -operators = { - 'eq': '==', - 'ne': '!=', - 'gt': '>', - 'gteq': '>=', - 'lt': '<', - 'lteq': '<=', - 'in': 'in', - 'notin': 'not in' -} - -# what method to iterate over items do we want to use for dict iteration -# in generated code? on 2.x let's go with iteritems, on 3.x with items -if hasattr(dict, 'iteritems'): - dict_item_iter = 'iteritems' -else: - dict_item_iter = 'items' - -code_features = ['division'] - -# does this python version support generator stops? (PEP 0479) -try: - exec('from __future__ import generator_stop') - code_features.append('generator_stop') -except SyntaxError: - pass - -# does this python version support yield from? -try: - exec('def f(): yield from x()') -except SyntaxError: - supports_yield_from = False -else: - supports_yield_from = True - - -def optimizeconst(f): - def new_func(self, node, frame, **kwargs): - # Only optimize if the frame is not volatile - if self.optimized and not frame.eval_ctx.volatile: - new_node = self.optimizer.visit(node, frame.eval_ctx) - if new_node != node: - return self.visit(new_node, frame) - return f(self, node, frame, **kwargs) - return update_wrapper(new_func, f) - - -def generate(node, environment, name, filename, stream=None, - defer_init=False, optimized=True): - """Generate the python source for a node tree.""" - if not isinstance(node, nodes.Template): - raise TypeError('Can\'t compile non template nodes') - generator = environment.code_generator_class(environment, name, filename, - stream, defer_init, - optimized) - generator.visit(node) - if stream is None: - return generator.stream.getvalue() - - -def has_safe_repr(value): - """Does the node have a safe representation?""" - if value is None or value is NotImplemented or value is Ellipsis: - return True - if type(value) in (bool, int, float, complex, range_type, Markup) + string_types: - return True - if type(value) in (tuple, list, set, frozenset): - for item in value: - if not has_safe_repr(item): - return False - return True - elif type(value) is dict: - for key, value in iteritems(value): - if not has_safe_repr(key): - return False - if not has_safe_repr(value): - return False - return True - return False - - -def find_undeclared(nodes, names): - """Check if the names passed are accessed undeclared. The return value - is a set of all the undeclared names from the sequence of names found. - """ - visitor = UndeclaredNameVisitor(names) - try: - for node in nodes: - visitor.visit(node) - except VisitorExit: - pass - return visitor.undeclared - - -class MacroRef(object): - - def __init__(self, node): - self.node = node - self.accesses_caller = False - self.accesses_kwargs = False - self.accesses_varargs = False - - -class Frame(object): - """Holds compile time information for us.""" - - def __init__(self, eval_ctx, parent=None, level=None): - self.eval_ctx = eval_ctx - self.symbols = Symbols(parent and parent.symbols or None, - level=level) - - # a toplevel frame is the root + soft frames such as if conditions. - self.toplevel = False - - # the root frame is basically just the outermost frame, so no if - # conditions. This information is used to optimize inheritance - # situations. - self.rootlevel = False - - # in some dynamic inheritance situations the compiler needs to add - # write tests around output statements. - self.require_output_check = parent and parent.require_output_check - - # inside some tags we are using a buffer rather than yield statements. - # this for example affects {% filter %} or {% macro %}. If a frame - # is buffered this variable points to the name of the list used as - # buffer. - self.buffer = None - - # the name of the block we're in, otherwise None. - self.block = parent and parent.block or None - - # the parent of this frame - self.parent = parent - - if parent is not None: - self.buffer = parent.buffer - - def copy(self): - """Create a copy of the current one.""" - rv = object.__new__(self.__class__) - rv.__dict__.update(self.__dict__) - rv.symbols = self.symbols.copy() - return rv - - def inner(self, isolated=False): - """Return an inner frame.""" - if isolated: - return Frame(self.eval_ctx, level=self.symbols.level + 1) - return Frame(self.eval_ctx, self) - - def soft(self): - """Return a soft frame. A soft frame may not be modified as - standalone thing as it shares the resources with the frame it - was created of, but it's not a rootlevel frame any longer. - - This is only used to implement if-statements. - """ - rv = self.copy() - rv.rootlevel = False - return rv - - __copy__ = copy - - -class VisitorExit(RuntimeError): - """Exception used by the `UndeclaredNameVisitor` to signal a stop.""" - - -class DependencyFinderVisitor(NodeVisitor): - """A visitor that collects filter and test calls.""" - - def __init__(self): - self.filters = set() - self.tests = set() - - def visit_Filter(self, node): - self.generic_visit(node) - self.filters.add(node.name) - - def visit_Test(self, node): - self.generic_visit(node) - self.tests.add(node.name) - - def visit_Block(self, node): - """Stop visiting at blocks.""" - - -class UndeclaredNameVisitor(NodeVisitor): - """A visitor that checks if a name is accessed without being - declared. This is different from the frame visitor as it will - not stop at closure frames. - """ - - def __init__(self, names): - self.names = set(names) - self.undeclared = set() - - def visit_Name(self, node): - if node.ctx == 'load' and node.name in self.names: - self.undeclared.add(node.name) - if self.undeclared == self.names: - raise VisitorExit() - else: - self.names.discard(node.name) - - def visit_Block(self, node): - """Stop visiting a blocks.""" - - -class CompilerExit(Exception): - """Raised if the compiler encountered a situation where it just - doesn't make sense to further process the code. Any block that - raises such an exception is not further processed. - """ - - -class CodeGenerator(NodeVisitor): - - def __init__(self, environment, name, filename, stream=None, - defer_init=False, optimized=True): - if stream is None: - stream = NativeStringIO() - self.environment = environment - self.name = name - self.filename = filename - self.stream = stream - self.created_block_context = False - self.defer_init = defer_init - self.optimized = optimized - if optimized: - self.optimizer = Optimizer(environment) - - # aliases for imports - self.import_aliases = {} - - # a registry for all blocks. Because blocks are moved out - # into the global python scope they are registered here - self.blocks = {} - - # the number of extends statements so far - self.extends_so_far = 0 - - # some templates have a rootlevel extends. In this case we - # can safely assume that we're a child template and do some - # more optimizations. - self.has_known_extends = False - - # the current line number - self.code_lineno = 1 - - # registry of all filters and tests (global, not block local) - self.tests = {} - self.filters = {} - - # the debug information - self.debug_info = [] - self._write_debug_info = None - - # the number of new lines before the next write() - self._new_lines = 0 - - # the line number of the last written statement - self._last_line = 0 - - # true if nothing was written so far. - self._first_write = True - - # used by the `temporary_identifier` method to get new - # unique, temporary identifier - self._last_identifier = 0 - - # the current indentation - self._indentation = 0 - - # Tracks toplevel assignments - self._assign_stack = [] - - # Tracks parameter definition blocks - self._param_def_block = [] - - # Tracks the current context. - self._context_reference_stack = ['context'] - - # -- Various compilation helpers - - def fail(self, msg, lineno): - """Fail with a :exc:`TemplateAssertionError`.""" - raise TemplateAssertionError(msg, lineno, self.name, self.filename) - - def temporary_identifier(self): - """Get a new unique identifier.""" - self._last_identifier += 1 - return 't_%d' % self._last_identifier - - def buffer(self, frame): - """Enable buffering for the frame from that point onwards.""" - frame.buffer = self.temporary_identifier() - self.writeline('%s = []' % frame.buffer) - - def return_buffer_contents(self, frame, force_unescaped=False): - """Return the buffer contents of the frame.""" - if not force_unescaped: - if frame.eval_ctx.volatile: - self.writeline('if context.eval_ctx.autoescape:') - self.indent() - self.writeline('return Markup(concat(%s))' % frame.buffer) - self.outdent() - self.writeline('else:') - self.indent() - self.writeline('return concat(%s)' % frame.buffer) - self.outdent() - return - elif frame.eval_ctx.autoescape: - self.writeline('return Markup(concat(%s))' % frame.buffer) - return - self.writeline('return concat(%s)' % frame.buffer) - - def indent(self): - """Indent by one.""" - self._indentation += 1 - - def outdent(self, step=1): - """Outdent by step.""" - self._indentation -= step - - def start_write(self, frame, node=None): - """Yield or write into the frame buffer.""" - if frame.buffer is None: - self.writeline('yield ', node) - else: - self.writeline('%s.append(' % frame.buffer, node) - - def end_write(self, frame): - """End the writing process started by `start_write`.""" - if frame.buffer is not None: - self.write(')') - - def simple_write(self, s, frame, node=None): - """Simple shortcut for start_write + write + end_write.""" - self.start_write(frame, node) - self.write(s) - self.end_write(frame) - - def blockvisit(self, nodes, frame): - """Visit a list of nodes as block in a frame. If the current frame - is no buffer a dummy ``if 0: yield None`` is written automatically. - """ - try: - self.writeline('pass') - for node in nodes: - self.visit(node, frame) - except CompilerExit: - pass - - def write(self, x): - """Write a string into the output stream.""" - if self._new_lines: - if not self._first_write: - self.stream.write('\n' * self._new_lines) - self.code_lineno += self._new_lines - if self._write_debug_info is not None: - self.debug_info.append((self._write_debug_info, - self.code_lineno)) - self._write_debug_info = None - self._first_write = False - self.stream.write(' ' * self._indentation) - self._new_lines = 0 - self.stream.write(x) - - def writeline(self, x, node=None, extra=0): - """Combination of newline and write.""" - self.newline(node, extra) - self.write(x) - - def newline(self, node=None, extra=0): - """Add one or more newlines before the next write.""" - self._new_lines = max(self._new_lines, 1 + extra) - if node is not None and node.lineno != self._last_line: - self._write_debug_info = node.lineno - self._last_line = node.lineno - - def signature(self, node, frame, extra_kwargs=None): - """Writes a function call to the stream for the current node. - A leading comma is added automatically. The extra keyword - arguments may not include python keywords otherwise a syntax - error could occour. The extra keyword arguments should be given - as python dict. - """ - # if any of the given keyword arguments is a python keyword - # we have to make sure that no invalid call is created. - kwarg_workaround = False - for kwarg in chain((x.key for x in node.kwargs), extra_kwargs or ()): - if is_python_keyword(kwarg): - kwarg_workaround = True - break - - for arg in node.args: - self.write(', ') - self.visit(arg, frame) - - if not kwarg_workaround: - for kwarg in node.kwargs: - self.write(', ') - self.visit(kwarg, frame) - if extra_kwargs is not None: - for key, value in iteritems(extra_kwargs): - self.write(', %s=%s' % (key, value)) - if node.dyn_args: - self.write(', *') - self.visit(node.dyn_args, frame) - - if kwarg_workaround: - if node.dyn_kwargs is not None: - self.write(', **dict({') - else: - self.write(', **{') - for kwarg in node.kwargs: - self.write('%r: ' % kwarg.key) - self.visit(kwarg.value, frame) - self.write(', ') - if extra_kwargs is not None: - for key, value in iteritems(extra_kwargs): - self.write('%r: %s, ' % (key, value)) - if node.dyn_kwargs is not None: - self.write('}, **') - self.visit(node.dyn_kwargs, frame) - self.write(')') - else: - self.write('}') - - elif node.dyn_kwargs is not None: - self.write(', **') - self.visit(node.dyn_kwargs, frame) - - def pull_dependencies(self, nodes): - """Pull all the dependencies.""" - visitor = DependencyFinderVisitor() - for node in nodes: - visitor.visit(node) - for dependency in 'filters', 'tests': - mapping = getattr(self, dependency) - for name in getattr(visitor, dependency): - if name not in mapping: - mapping[name] = self.temporary_identifier() - self.writeline('%s = environment.%s[%r]' % - (mapping[name], dependency, name)) - - def enter_frame(self, frame): - undefs = [] - for target, (action, param) in iteritems(frame.symbols.loads): - if action == VAR_LOAD_PARAMETER: - pass - elif action == VAR_LOAD_RESOLVE: - self.writeline('%s = %s(%r)' % - (target, self.get_resolve_func(), param)) - elif action == VAR_LOAD_ALIAS: - self.writeline('%s = %s' % (target, param)) - elif action == VAR_LOAD_UNDEFINED: - undefs.append(target) - else: - raise NotImplementedError('unknown load instruction') - if undefs: - self.writeline('%s = missing' % ' = '.join(undefs)) - - def leave_frame(self, frame, with_python_scope=False): - if not with_python_scope: - undefs = [] - for target, _ in iteritems(frame.symbols.loads): - undefs.append(target) - if undefs: - self.writeline('%s = missing' % ' = '.join(undefs)) - - def func(self, name): - if self.environment.is_async: - return 'async def %s' % name - return 'def %s' % name - - def macro_body(self, node, frame): - """Dump the function def of a macro or call block.""" - frame = frame.inner() - frame.symbols.analyze_node(node) - macro_ref = MacroRef(node) - - explicit_caller = None - skip_special_params = set() - args = [] - for idx, arg in enumerate(node.args): - if arg.name == 'caller': - explicit_caller = idx - if arg.name in ('kwargs', 'varargs'): - skip_special_params.add(arg.name) - args.append(frame.symbols.ref(arg.name)) - - undeclared = find_undeclared(node.body, ('caller', 'kwargs', 'varargs')) - - if 'caller' in undeclared: - # In older Jinja2 versions there was a bug that allowed caller - # to retain the special behavior even if it was mentioned in - # the argument list. However thankfully this was only really - # working if it was the last argument. So we are explicitly - # checking this now and error out if it is anywhere else in - # the argument list. - if explicit_caller is not None: - try: - node.defaults[explicit_caller - len(node.args)] - except IndexError: - self.fail('When defining macros or call blocks the ' - 'special "caller" argument must be omitted ' - 'or be given a default.', node.lineno) - else: - args.append(frame.symbols.declare_parameter('caller')) - macro_ref.accesses_caller = True - if 'kwargs' in undeclared and not 'kwargs' in skip_special_params: - args.append(frame.symbols.declare_parameter('kwargs')) - macro_ref.accesses_kwargs = True - if 'varargs' in undeclared and not 'varargs' in skip_special_params: - args.append(frame.symbols.declare_parameter('varargs')) - macro_ref.accesses_varargs = True - - # macros are delayed, they never require output checks - frame.require_output_check = False - frame.symbols.analyze_node(node) - self.writeline('%s(%s):' % (self.func('macro'), ', '.join(args)), node) - self.indent() - - self.buffer(frame) - self.enter_frame(frame) - - self.push_parameter_definitions(frame) - for idx, arg in enumerate(node.args): - ref = frame.symbols.ref(arg.name) - self.writeline('if %s is missing:' % ref) - self.indent() - try: - default = node.defaults[idx - len(node.args)] - except IndexError: - self.writeline('%s = undefined(%r, name=%r)' % ( - ref, - 'parameter %r was not provided' % arg.name, - arg.name)) - else: - self.writeline('%s = ' % ref) - self.visit(default, frame) - self.mark_parameter_stored(ref) - self.outdent() - self.pop_parameter_definitions() - - self.blockvisit(node.body, frame) - self.return_buffer_contents(frame, force_unescaped=True) - self.leave_frame(frame, with_python_scope=True) - self.outdent() - - return frame, macro_ref - - def macro_def(self, macro_ref, frame): - """Dump the macro definition for the def created by macro_body.""" - arg_tuple = ', '.join(repr(x.name) for x in macro_ref.node.args) - name = getattr(macro_ref.node, 'name', None) - if len(macro_ref.node.args) == 1: - arg_tuple += ',' - self.write('Macro(environment, macro, %r, (%s), %r, %r, %r, ' - 'context.eval_ctx.autoescape)' % - (name, arg_tuple, macro_ref.accesses_kwargs, - macro_ref.accesses_varargs, macro_ref.accesses_caller)) - - def position(self, node): - """Return a human readable position for the node.""" - rv = 'line %d' % node.lineno - if self.name is not None: - rv += ' in ' + repr(self.name) - return rv - - def dump_local_context(self, frame): - return '{%s}' % ', '.join( - '%r: %s' % (name, target) for name, target - in iteritems(frame.symbols.dump_stores())) - - def write_commons(self): - """Writes a common preamble that is used by root and block functions. - Primarily this sets up common local helpers and enforces a generator - through a dead branch. - """ - self.writeline('resolve = context.resolve_or_missing') - self.writeline('undefined = environment.undefined') - self.writeline('if 0: yield None') - - def push_parameter_definitions(self, frame): - """Pushes all parameter targets from the given frame into a local - stack that permits tracking of yet to be assigned parameters. In - particular this enables the optimization from `visit_Name` to skip - undefined expressions for parameters in macros as macros can reference - otherwise unbound parameters. - """ - self._param_def_block.append(frame.symbols.dump_param_targets()) - - def pop_parameter_definitions(self): - """Pops the current parameter definitions set.""" - self._param_def_block.pop() - - def mark_parameter_stored(self, target): - """Marks a parameter in the current parameter definitions as stored. - This will skip the enforced undefined checks. - """ - if self._param_def_block: - self._param_def_block[-1].discard(target) - - def push_context_reference(self, target): - self._context_reference_stack.append(target) - - def pop_context_reference(self): - self._context_reference_stack.pop() - - def get_context_ref(self): - return self._context_reference_stack[-1] - - def get_resolve_func(self): - target = self._context_reference_stack[-1] - if target == 'context': - return 'resolve' - return '%s.resolve' % target - - def derive_context(self, frame): - return '%s.derived(%s)' % ( - self.get_context_ref(), - self.dump_local_context(frame), - ) - - def parameter_is_undeclared(self, target): - """Checks if a given target is an undeclared parameter.""" - if not self._param_def_block: - return False - return target in self._param_def_block[-1] - - def push_assign_tracking(self): - """Pushes a new layer for assignment tracking.""" - self._assign_stack.append(set()) - - def pop_assign_tracking(self, frame): - """Pops the topmost level for assignment tracking and updates the - context variables if necessary. - """ - vars = self._assign_stack.pop() - if not frame.toplevel or not vars: - return - public_names = [x for x in vars if x[:1] != '_'] - if len(vars) == 1: - name = next(iter(vars)) - ref = frame.symbols.ref(name) - self.writeline('context.vars[%r] = %s' % (name, ref)) - else: - self.writeline('context.vars.update({') - for idx, name in enumerate(vars): - if idx: - self.write(', ') - ref = frame.symbols.ref(name) - self.write('%r: %s' % (name, ref)) - self.write('})') - if public_names: - if len(public_names) == 1: - self.writeline('context.exported_vars.add(%r)' % - public_names[0]) - else: - self.writeline('context.exported_vars.update((%s))' % - ', '.join(imap(repr, public_names))) - - # -- Statement Visitors - - def visit_Template(self, node, frame=None): - assert frame is None, 'no root frame allowed' - eval_ctx = EvalContext(self.environment, self.name) - - from jinja2.runtime import __all__ as exported - self.writeline('from __future__ import %s' % ', '.join(code_features)) - self.writeline('from jinja2.runtime import ' + ', '.join(exported)) - - if self.environment.is_async: - self.writeline('from jinja2.asyncsupport import auto_await, ' - 'auto_aiter, make_async_loop_context') - - # if we want a deferred initialization we cannot move the - # environment into a local name - envenv = not self.defer_init and ', environment=environment' or '' - - # do we have an extends tag at all? If not, we can save some - # overhead by just not processing any inheritance code. - have_extends = node.find(nodes.Extends) is not None - - # find all blocks - for block in node.find_all(nodes.Block): - if block.name in self.blocks: - self.fail('block %r defined twice' % block.name, block.lineno) - self.blocks[block.name] = block - - # find all imports and import them - for import_ in node.find_all(nodes.ImportedName): - if import_.importname not in self.import_aliases: - imp = import_.importname - self.import_aliases[imp] = alias = self.temporary_identifier() - if '.' in imp: - module, obj = imp.rsplit('.', 1) - self.writeline('from %s import %s as %s' % - (module, obj, alias)) - else: - self.writeline('import %s as %s' % (imp, alias)) - - # add the load name - self.writeline('name = %r' % self.name) - - # generate the root render function. - self.writeline('%s(context, missing=missing%s):' % - (self.func('root'), envenv), extra=1) - self.indent() - self.write_commons() - - # process the root - frame = Frame(eval_ctx) - if 'self' in find_undeclared(node.body, ('self',)): - ref = frame.symbols.declare_parameter('self') - self.writeline('%s = TemplateReference(context)' % ref) - frame.symbols.analyze_node(node) - frame.toplevel = frame.rootlevel = True - frame.require_output_check = have_extends and not self.has_known_extends - if have_extends: - self.writeline('parent_template = None') - self.enter_frame(frame) - self.pull_dependencies(node.body) - self.blockvisit(node.body, frame) - self.leave_frame(frame, with_python_scope=True) - self.outdent() - - # make sure that the parent root is called. - if have_extends: - if not self.has_known_extends: - self.indent() - self.writeline('if parent_template is not None:') - self.indent() - if supports_yield_from and not self.environment.is_async: - self.writeline('yield from parent_template.' - 'root_render_func(context)') - else: - self.writeline('%sfor event in parent_template.' - 'root_render_func(context):' % - (self.environment.is_async and 'async ' or '')) - self.indent() - self.writeline('yield event') - self.outdent() - self.outdent(1 + (not self.has_known_extends)) - - # at this point we now have the blocks collected and can visit them too. - for name, block in iteritems(self.blocks): - self.writeline('%s(context, missing=missing%s):' % - (self.func('block_' + name), envenv), - block, 1) - self.indent() - self.write_commons() - # It's important that we do not make this frame a child of the - # toplevel template. This would cause a variety of - # interesting issues with identifier tracking. - block_frame = Frame(eval_ctx) - undeclared = find_undeclared(block.body, ('self', 'super')) - if 'self' in undeclared: - ref = block_frame.symbols.declare_parameter('self') - self.writeline('%s = TemplateReference(context)' % ref) - if 'super' in undeclared: - ref = block_frame.symbols.declare_parameter('super') - self.writeline('%s = context.super(%r, ' - 'block_%s)' % (ref, name, name)) - block_frame.symbols.analyze_node(block) - block_frame.block = name - self.enter_frame(block_frame) - self.pull_dependencies(block.body) - self.blockvisit(block.body, block_frame) - self.leave_frame(block_frame, with_python_scope=True) - self.outdent() - - self.writeline('blocks = {%s}' % ', '.join('%r: block_%s' % (x, x) - for x in self.blocks), - extra=1) - - # add a function that returns the debug info - self.writeline('debug_info = %r' % '&'.join('%s=%s' % x for x - in self.debug_info)) - - def visit_Block(self, node, frame): - """Call a block and register it for the template.""" - level = 0 - if frame.toplevel: - # if we know that we are a child template, there is no need to - # check if we are one - if self.has_known_extends: - return - if self.extends_so_far > 0: - self.writeline('if parent_template is None:') - self.indent() - level += 1 - - if node.scoped: - context = self.derive_context(frame) - else: - context = self.get_context_ref() - - if supports_yield_from and not self.environment.is_async and \ - frame.buffer is None: - self.writeline('yield from context.blocks[%r][0](%s)' % ( - node.name, context), node) - else: - loop = self.environment.is_async and 'async for' or 'for' - self.writeline('%s event in context.blocks[%r][0](%s):' % ( - loop, node.name, context), node) - self.indent() - self.simple_write('event', frame) - self.outdent() - - self.outdent(level) - - def visit_Extends(self, node, frame): - """Calls the extender.""" - if not frame.toplevel: - self.fail('cannot use extend from a non top-level scope', - node.lineno) - - # if the number of extends statements in general is zero so - # far, we don't have to add a check if something extended - # the template before this one. - if self.extends_so_far > 0: - - # if we have a known extends we just add a template runtime - # error into the generated code. We could catch that at compile - # time too, but i welcome it not to confuse users by throwing the - # same error at different times just "because we can". - if not self.has_known_extends: - self.writeline('if parent_template is not None:') - self.indent() - self.writeline('raise TemplateRuntimeError(%r)' % - 'extended multiple times') - - # if we have a known extends already we don't need that code here - # as we know that the template execution will end here. - if self.has_known_extends: - raise CompilerExit() - else: - self.outdent() - - self.writeline('parent_template = environment.get_template(', node) - self.visit(node.template, frame) - self.write(', %r)' % self.name) - self.writeline('for name, parent_block in parent_template.' - 'blocks.%s():' % dict_item_iter) - self.indent() - self.writeline('context.blocks.setdefault(name, []).' - 'append(parent_block)') - self.outdent() - - # if this extends statement was in the root level we can take - # advantage of that information and simplify the generated code - # in the top level from this point onwards - if frame.rootlevel: - self.has_known_extends = True - - # and now we have one more - self.extends_so_far += 1 - - def visit_Include(self, node, frame): - """Handles includes.""" - if node.ignore_missing: - self.writeline('try:') - self.indent() - - func_name = 'get_or_select_template' - if isinstance(node.template, nodes.Const): - if isinstance(node.template.value, string_types): - func_name = 'get_template' - elif isinstance(node.template.value, (tuple, list)): - func_name = 'select_template' - elif isinstance(node.template, (nodes.Tuple, nodes.List)): - func_name = 'select_template' - - self.writeline('template = environment.%s(' % func_name, node) - self.visit(node.template, frame) - self.write(', %r)' % self.name) - if node.ignore_missing: - self.outdent() - self.writeline('except TemplateNotFound:') - self.indent() - self.writeline('pass') - self.outdent() - self.writeline('else:') - self.indent() - - skip_event_yield = False - if node.with_context: - loop = self.environment.is_async and 'async for' or 'for' - self.writeline('%s event in template.root_render_func(' - 'template.new_context(context.get_all(), True, ' - '%s)):' % (loop, self.dump_local_context(frame))) - elif self.environment.is_async: - self.writeline('for event in (await ' - 'template._get_default_module_async())' - '._body_stream:') - else: - if supports_yield_from: - self.writeline('yield from template._get_default_module()' - '._body_stream') - skip_event_yield = True - else: - self.writeline('for event in template._get_default_module()' - '._body_stream:') - - if not skip_event_yield: - self.indent() - self.simple_write('event', frame) - self.outdent() - - if node.ignore_missing: - self.outdent() - - def visit_Import(self, node, frame): - """Visit regular imports.""" - self.writeline('%s = ' % frame.symbols.ref(node.target), node) - if frame.toplevel: - self.write('context.vars[%r] = ' % node.target) - if self.environment.is_async: - self.write('await ') - self.write('environment.get_template(') - self.visit(node.template, frame) - self.write(', %r).' % self.name) - if node.with_context: - self.write('make_module%s(context.get_all(), True, %s)' - % (self.environment.is_async and '_async' or '', - self.dump_local_context(frame))) - elif self.environment.is_async: - self.write('_get_default_module_async()') - else: - self.write('_get_default_module()') - if frame.toplevel and not node.target.startswith('_'): - self.writeline('context.exported_vars.discard(%r)' % node.target) - - def visit_FromImport(self, node, frame): - """Visit named imports.""" - self.newline(node) - self.write('included_template = %senvironment.get_template(' - % (self.environment.is_async and 'await ' or '')) - self.visit(node.template, frame) - self.write(', %r).' % self.name) - if node.with_context: - self.write('make_module%s(context.get_all(), True, %s)' - % (self.environment.is_async and '_async' or '', - self.dump_local_context(frame))) - elif self.environment.is_async: - self.write('_get_default_module_async()') - else: - self.write('_get_default_module()') - - var_names = [] - discarded_names = [] - for name in node.names: - if isinstance(name, tuple): - name, alias = name - else: - alias = name - self.writeline('%s = getattr(included_template, ' - '%r, missing)' % (frame.symbols.ref(alias), name)) - self.writeline('if %s is missing:' % frame.symbols.ref(alias)) - self.indent() - self.writeline('%s = undefined(%r %% ' - 'included_template.__name__, ' - 'name=%r)' % - (frame.symbols.ref(alias), - 'the template %%r (imported on %s) does ' - 'not export the requested name %s' % ( - self.position(node), - repr(name) - ), name)) - self.outdent() - if frame.toplevel: - var_names.append(alias) - if not alias.startswith('_'): - discarded_names.append(alias) - - if var_names: - if len(var_names) == 1: - name = var_names[0] - self.writeline('context.vars[%r] = %s' % - (name, frame.symbols.ref(name))) - else: - self.writeline('context.vars.update({%s})' % ', '.join( - '%r: %s' % (name, frame.symbols.ref(name)) for name in var_names - )) - if discarded_names: - if len(discarded_names) == 1: - self.writeline('context.exported_vars.discard(%r)' % - discarded_names[0]) - else: - self.writeline('context.exported_vars.difference_' - 'update((%s))' % ', '.join(imap(repr, discarded_names))) - - def visit_For(self, node, frame): - loop_frame = frame.inner() - test_frame = frame.inner() - else_frame = frame.inner() - - # try to figure out if we have an extended loop. An extended loop - # is necessary if the loop is in recursive mode if the special loop - # variable is accessed in the body. - extended_loop = node.recursive or 'loop' in \ - find_undeclared(node.iter_child_nodes( - only=('body',)), ('loop',)) - - loop_ref = None - if extended_loop: - loop_ref = loop_frame.symbols.declare_parameter('loop') - - loop_frame.symbols.analyze_node(node, for_branch='body') - if node.else_: - else_frame.symbols.analyze_node(node, for_branch='else') - - if node.test: - loop_filter_func = self.temporary_identifier() - test_frame.symbols.analyze_node(node, for_branch='test') - self.writeline('%s(fiter):' % self.func(loop_filter_func), node.test) - self.indent() - self.enter_frame(test_frame) - self.writeline(self.environment.is_async and 'async for ' or 'for ') - self.visit(node.target, loop_frame) - self.write(' in ') - self.write(self.environment.is_async and 'auto_aiter(fiter)' or 'fiter') - self.write(':') - self.indent() - self.writeline('if ', node.test) - self.visit(node.test, test_frame) - self.write(':') - self.indent() - self.writeline('yield ') - self.visit(node.target, loop_frame) - self.outdent(3) - self.leave_frame(test_frame, with_python_scope=True) - - # if we don't have an recursive loop we have to find the shadowed - # variables at that point. Because loops can be nested but the loop - # variable is a special one we have to enforce aliasing for it. - if node.recursive: - self.writeline('%s(reciter, loop_render_func, depth=0):' % - self.func('loop'), node) - self.indent() - self.buffer(loop_frame) - - # Use the same buffer for the else frame - else_frame.buffer = loop_frame.buffer - - # make sure the loop variable is a special one and raise a template - # assertion error if a loop tries to write to loop - if extended_loop: - self.writeline('%s = missing' % loop_ref) - - for name in node.find_all(nodes.Name): - if name.ctx == 'store' and name.name == 'loop': - self.fail('Can\'t assign to special loop variable ' - 'in for-loop target', name.lineno) - - if node.else_: - iteration_indicator = self.temporary_identifier() - self.writeline('%s = 1' % iteration_indicator) - - self.writeline(self.environment.is_async and 'async for ' or 'for ', node) - self.visit(node.target, loop_frame) - if extended_loop: - if self.environment.is_async: - self.write(', %s in await make_async_loop_context(' % loop_ref) - else: - self.write(', %s in LoopContext(' % loop_ref) - else: - self.write(' in ') - - if node.test: - self.write('%s(' % loop_filter_func) - if node.recursive: - self.write('reciter') - else: - if self.environment.is_async and not extended_loop: - self.write('auto_aiter(') - self.visit(node.iter, frame) - if self.environment.is_async and not extended_loop: - self.write(')') - if node.test: - self.write(')') - - if node.recursive: - self.write(', undefined, loop_render_func, depth):') - else: - self.write(extended_loop and ', undefined):' or ':') - - self.indent() - self.enter_frame(loop_frame) - - self.blockvisit(node.body, loop_frame) - if node.else_: - self.writeline('%s = 0' % iteration_indicator) - self.outdent() - self.leave_frame(loop_frame, with_python_scope=node.recursive - and not node.else_) - - if node.else_: - self.writeline('if %s:' % iteration_indicator) - self.indent() - self.enter_frame(else_frame) - self.blockvisit(node.else_, else_frame) - self.leave_frame(else_frame) - self.outdent() - - # if the node was recursive we have to return the buffer contents - # and start the iteration code - if node.recursive: - self.return_buffer_contents(loop_frame) - self.outdent() - self.start_write(frame, node) - if self.environment.is_async: - self.write('await ') - self.write('loop(') - if self.environment.is_async: - self.write('auto_aiter(') - self.visit(node.iter, frame) - if self.environment.is_async: - self.write(')') - self.write(', loop)') - self.end_write(frame) - - def visit_If(self, node, frame): - if_frame = frame.soft() - self.writeline('if ', node) - self.visit(node.test, if_frame) - self.write(':') - self.indent() - self.blockvisit(node.body, if_frame) - self.outdent() - for elif_ in node.elif_: - self.writeline('elif ', elif_) - self.visit(elif_.test, if_frame) - self.write(':') - self.indent() - self.blockvisit(elif_.body, if_frame) - self.outdent() - if node.else_: - self.writeline('else:') - self.indent() - self.blockvisit(node.else_, if_frame) - self.outdent() - - def visit_Macro(self, node, frame): - macro_frame, macro_ref = self.macro_body(node, frame) - self.newline() - if frame.toplevel: - if not node.name.startswith('_'): - self.write('context.exported_vars.add(%r)' % node.name) - ref = frame.symbols.ref(node.name) - self.writeline('context.vars[%r] = ' % node.name) - self.write('%s = ' % frame.symbols.ref(node.name)) - self.macro_def(macro_ref, macro_frame) - - def visit_CallBlock(self, node, frame): - call_frame, macro_ref = self.macro_body(node, frame) - self.writeline('caller = ') - self.macro_def(macro_ref, call_frame) - self.start_write(frame, node) - self.visit_Call(node.call, frame, forward_caller=True) - self.end_write(frame) - - def visit_FilterBlock(self, node, frame): - filter_frame = frame.inner() - filter_frame.symbols.analyze_node(node) - self.enter_frame(filter_frame) - self.buffer(filter_frame) - self.blockvisit(node.body, filter_frame) - self.start_write(frame, node) - self.visit_Filter(node.filter, filter_frame) - self.end_write(frame) - self.leave_frame(filter_frame) - - def visit_With(self, node, frame): - with_frame = frame.inner() - with_frame.symbols.analyze_node(node) - self.enter_frame(with_frame) - for idx, (target, expr) in enumerate(izip(node.targets, node.values)): - self.newline() - self.visit(target, with_frame) - self.write(' = ') - self.visit(expr, frame) - self.blockvisit(node.body, with_frame) - self.leave_frame(with_frame) - - def visit_ExprStmt(self, node, frame): - self.newline(node) - self.visit(node.node, frame) - - def visit_Output(self, node, frame): - # if we have a known extends statement, we don't output anything - # if we are in a require_output_check section - if self.has_known_extends and frame.require_output_check: - return - - allow_constant_finalize = True - if self.environment.finalize: - func = self.environment.finalize - if getattr(func, 'contextfunction', False) or \ - getattr(func, 'evalcontextfunction', False): - allow_constant_finalize = False - elif getattr(func, 'environmentfunction', False): - finalize = lambda x: text_type( - self.environment.finalize(self.environment, x)) - else: - finalize = lambda x: text_type(self.environment.finalize(x)) - else: - finalize = text_type - - # if we are inside a frame that requires output checking, we do so - outdent_later = False - if frame.require_output_check: - self.writeline('if parent_template is None:') - self.indent() - outdent_later = True - - # try to evaluate as many chunks as possible into a static - # string at compile time. - body = [] - for child in node.nodes: - try: - if not allow_constant_finalize: - raise nodes.Impossible() - const = child.as_const(frame.eval_ctx) - except nodes.Impossible: - body.append(child) - continue - # the frame can't be volatile here, becaus otherwise the - # as_const() function would raise an Impossible exception - # at that point. - try: - if frame.eval_ctx.autoescape: - if hasattr(const, '__html__'): - const = const.__html__() - else: - const = escape(const) - const = finalize(const) - except Exception: - # if something goes wrong here we evaluate the node - # at runtime for easier debugging - body.append(child) - continue - if body and isinstance(body[-1], list): - body[-1].append(const) - else: - body.append([const]) - - # if we have less than 3 nodes or a buffer we yield or extend/append - if len(body) < 3 or frame.buffer is not None: - if frame.buffer is not None: - # for one item we append, for more we extend - if len(body) == 1: - self.writeline('%s.append(' % frame.buffer) - else: - self.writeline('%s.extend((' % frame.buffer) - self.indent() - for item in body: - if isinstance(item, list): - val = repr(concat(item)) - if frame.buffer is None: - self.writeline('yield ' + val) - else: - self.writeline(val + ',') - else: - if frame.buffer is None: - self.writeline('yield ', item) - else: - self.newline(item) - close = 1 - if frame.eval_ctx.volatile: - self.write('(escape if context.eval_ctx.autoescape' - ' else to_string)(') - elif frame.eval_ctx.autoescape: - self.write('escape(') - else: - self.write('to_string(') - if self.environment.finalize is not None: - self.write('environment.finalize(') - if getattr(self.environment.finalize, - "contextfunction", False): - self.write('context, ') - close += 1 - self.visit(item, frame) - self.write(')' * close) - if frame.buffer is not None: - self.write(',') - if frame.buffer is not None: - # close the open parentheses - self.outdent() - self.writeline(len(body) == 1 and ')' or '))') - - # otherwise we create a format string as this is faster in that case - else: - format = [] - arguments = [] - for item in body: - if isinstance(item, list): - format.append(concat(item).replace('%', '%%')) - else: - format.append('%s') - arguments.append(item) - self.writeline('yield ') - self.write(repr(concat(format)) + ' % (') - self.indent() - for argument in arguments: - self.newline(argument) - close = 0 - if frame.eval_ctx.volatile: - self.write('(escape if context.eval_ctx.autoescape else' - ' to_string)(') - close += 1 - elif frame.eval_ctx.autoescape: - self.write('escape(') - close += 1 - if self.environment.finalize is not None: - self.write('environment.finalize(') - if getattr(self.environment.finalize, - 'contextfunction', False): - self.write('context, ') - elif getattr(self.environment.finalize, - 'evalcontextfunction', False): - self.write('context.eval_ctx, ') - elif getattr(self.environment.finalize, - 'environmentfunction', False): - self.write('environment, ') - close += 1 - self.visit(argument, frame) - self.write(')' * close + ', ') - self.outdent() - self.writeline(')') - - if outdent_later: - self.outdent() - - def visit_Assign(self, node, frame): - self.push_assign_tracking() - self.newline(node) - self.visit(node.target, frame) - self.write(' = ') - self.visit(node.node, frame) - self.pop_assign_tracking(frame) - - def visit_AssignBlock(self, node, frame): - self.push_assign_tracking() - block_frame = frame.inner() - # This is a special case. Since a set block always captures we - # will disable output checks. This way one can use set blocks - # toplevel even in extended templates. - block_frame.require_output_check = False - block_frame.symbols.analyze_node(node) - self.enter_frame(block_frame) - self.buffer(block_frame) - self.blockvisit(node.body, block_frame) - self.newline(node) - self.visit(node.target, frame) - self.write(' = (Markup if context.eval_ctx.autoescape ' - 'else identity)(') - if node.filter is not None: - self.visit_Filter(node.filter, block_frame) - else: - self.write('concat(%s)' % block_frame.buffer) - self.write(')') - self.pop_assign_tracking(frame) - self.leave_frame(block_frame) - - # -- Expression Visitors - - def visit_Name(self, node, frame): - if node.ctx == 'store' and frame.toplevel: - if self._assign_stack: - self._assign_stack[-1].add(node.name) - ref = frame.symbols.ref(node.name) - - # If we are looking up a variable we might have to deal with the - # case where it's undefined. We can skip that case if the load - # instruction indicates a parameter which are always defined. - if node.ctx == 'load': - load = frame.symbols.find_load(ref) - if not (load is not None and load[0] == VAR_LOAD_PARAMETER and \ - not self.parameter_is_undeclared(ref)): - self.write('(undefined(name=%r) if %s is missing else %s)' % - (node.name, ref, ref)) - return - - self.write(ref) - - def visit_NSRef(self, node, frame): - # NSRefs can only be used to store values; since they use the normal - # `foo.bar` notation they will be parsed as a normal attribute access - # when used anywhere but in a `set` context - ref = frame.symbols.ref(node.name) - self.writeline('if not isinstance(%s, Namespace):' % ref) - self.indent() - self.writeline('raise TemplateRuntimeError(%r)' % - 'cannot assign attribute on non-namespace object') - self.outdent() - self.writeline('%s[%r]' % (ref, node.attr)) - - def visit_Const(self, node, frame): - val = node.as_const(frame.eval_ctx) - if isinstance(val, float): - self.write(str(val)) - else: - self.write(repr(val)) - - def visit_TemplateData(self, node, frame): - try: - self.write(repr(node.as_const(frame.eval_ctx))) - except nodes.Impossible: - self.write('(Markup if context.eval_ctx.autoescape else identity)(%r)' - % node.data) - - def visit_Tuple(self, node, frame): - self.write('(') - idx = -1 - for idx, item in enumerate(node.items): - if idx: - self.write(', ') - self.visit(item, frame) - self.write(idx == 0 and ',)' or ')') - - def visit_List(self, node, frame): - self.write('[') - for idx, item in enumerate(node.items): - if idx: - self.write(', ') - self.visit(item, frame) - self.write(']') - - def visit_Dict(self, node, frame): - self.write('{') - for idx, item in enumerate(node.items): - if idx: - self.write(', ') - self.visit(item.key, frame) - self.write(': ') - self.visit(item.value, frame) - self.write('}') - - def binop(operator, interceptable=True): - @optimizeconst - def visitor(self, node, frame): - if self.environment.sandboxed and \ - operator in self.environment.intercepted_binops: - self.write('environment.call_binop(context, %r, ' % operator) - self.visit(node.left, frame) - self.write(', ') - self.visit(node.right, frame) - else: - self.write('(') - self.visit(node.left, frame) - self.write(' %s ' % operator) - self.visit(node.right, frame) - self.write(')') - return visitor - - def uaop(operator, interceptable=True): - @optimizeconst - def visitor(self, node, frame): - if self.environment.sandboxed and \ - operator in self.environment.intercepted_unops: - self.write('environment.call_unop(context, %r, ' % operator) - self.visit(node.node, frame) - else: - self.write('(' + operator) - self.visit(node.node, frame) - self.write(')') - return visitor - - visit_Add = binop('+') - visit_Sub = binop('-') - visit_Mul = binop('*') - visit_Div = binop('/') - visit_FloorDiv = binop('//') - visit_Pow = binop('**') - visit_Mod = binop('%') - visit_And = binop('and', interceptable=False) - visit_Or = binop('or', interceptable=False) - visit_Pos = uaop('+') - visit_Neg = uaop('-') - visit_Not = uaop('not ', interceptable=False) - del binop, uaop - - @optimizeconst - def visit_Concat(self, node, frame): - if frame.eval_ctx.volatile: - func_name = '(context.eval_ctx.volatile and' \ - ' markup_join or unicode_join)' - elif frame.eval_ctx.autoescape: - func_name = 'markup_join' - else: - func_name = 'unicode_join' - self.write('%s((' % func_name) - for arg in node.nodes: - self.visit(arg, frame) - self.write(', ') - self.write('))') - - @optimizeconst - def visit_Compare(self, node, frame): - self.visit(node.expr, frame) - for op in node.ops: - self.visit(op, frame) - - def visit_Operand(self, node, frame): - self.write(' %s ' % operators[node.op]) - self.visit(node.expr, frame) - - @optimizeconst - def visit_Getattr(self, node, frame): - self.write('environment.getattr(') - self.visit(node.node, frame) - self.write(', %r)' % node.attr) - - @optimizeconst - def visit_Getitem(self, node, frame): - # slices bypass the environment getitem method. - if isinstance(node.arg, nodes.Slice): - self.visit(node.node, frame) - self.write('[') - self.visit(node.arg, frame) - self.write(']') - else: - self.write('environment.getitem(') - self.visit(node.node, frame) - self.write(', ') - self.visit(node.arg, frame) - self.write(')') - - def visit_Slice(self, node, frame): - if node.start is not None: - self.visit(node.start, frame) - self.write(':') - if node.stop is not None: - self.visit(node.stop, frame) - if node.step is not None: - self.write(':') - self.visit(node.step, frame) - - @optimizeconst - def visit_Filter(self, node, frame): - if self.environment.is_async: - self.write('await auto_await(') - self.write(self.filters[node.name] + '(') - func = self.environment.filters.get(node.name) - if func is None: - self.fail('no filter named %r' % node.name, node.lineno) - if getattr(func, 'contextfilter', False): - self.write('context, ') - elif getattr(func, 'evalcontextfilter', False): - self.write('context.eval_ctx, ') - elif getattr(func, 'environmentfilter', False): - self.write('environment, ') - - # if the filter node is None we are inside a filter block - # and want to write to the current buffer - if node.node is not None: - self.visit(node.node, frame) - elif frame.eval_ctx.volatile: - self.write('(context.eval_ctx.autoescape and' - ' Markup(concat(%s)) or concat(%s))' % - (frame.buffer, frame.buffer)) - elif frame.eval_ctx.autoescape: - self.write('Markup(concat(%s))' % frame.buffer) - else: - self.write('concat(%s)' % frame.buffer) - self.signature(node, frame) - self.write(')') - if self.environment.is_async: - self.write(')') - - @optimizeconst - def visit_Test(self, node, frame): - self.write(self.tests[node.name] + '(') - if node.name not in self.environment.tests: - self.fail('no test named %r' % node.name, node.lineno) - self.visit(node.node, frame) - self.signature(node, frame) - self.write(')') - - @optimizeconst - def visit_CondExpr(self, node, frame): - def write_expr2(): - if node.expr2 is not None: - return self.visit(node.expr2, frame) - self.write('undefined(%r)' % ('the inline if-' - 'expression on %s evaluated to false and ' - 'no else section was defined.' % self.position(node))) - - self.write('(') - self.visit(node.expr1, frame) - self.write(' if ') - self.visit(node.test, frame) - self.write(' else ') - write_expr2() - self.write(')') - - @optimizeconst - def visit_Call(self, node, frame, forward_caller=False): - if self.environment.is_async: - self.write('await auto_await(') - if self.environment.sandboxed: - self.write('environment.call(context, ') - else: - self.write('context.call(') - self.visit(node.node, frame) - extra_kwargs = forward_caller and {'caller': 'caller'} or None - self.signature(node, frame, extra_kwargs) - self.write(')') - if self.environment.is_async: - self.write(')') - - def visit_Keyword(self, node, frame): - self.write(node.key + '=') - self.visit(node.value, frame) - - # -- Unused nodes for extensions - - def visit_MarkSafe(self, node, frame): - self.write('Markup(') - self.visit(node.expr, frame) - self.write(')') - - def visit_MarkSafeIfAutoescape(self, node, frame): - self.write('(context.eval_ctx.autoescape and Markup or identity)(') - self.visit(node.expr, frame) - self.write(')') - - def visit_EnvironmentAttribute(self, node, frame): - self.write('environment.' + node.name) - - def visit_ExtensionAttribute(self, node, frame): - self.write('environment.extensions[%r].%s' % (node.identifier, node.name)) - - def visit_ImportedName(self, node, frame): - self.write(self.import_aliases[node.importname]) - - def visit_InternalName(self, node, frame): - self.write(node.name) - - def visit_ContextReference(self, node, frame): - self.write('context') - - def visit_Continue(self, node, frame): - self.writeline('continue', node) - - def visit_Break(self, node, frame): - self.writeline('break', node) - - def visit_Scope(self, node, frame): - scope_frame = frame.inner() - scope_frame.symbols.analyze_node(node) - self.enter_frame(scope_frame) - self.blockvisit(node.body, scope_frame) - self.leave_frame(scope_frame) - - def visit_OverlayScope(self, node, frame): - ctx = self.temporary_identifier() - self.writeline('%s = %s' % (ctx, self.derive_context(frame))) - self.writeline('%s.vars = ' % ctx) - self.visit(node.context, frame) - self.push_context_reference(ctx) - - scope_frame = frame.inner(isolated=True) - scope_frame.symbols.analyze_node(node) - self.enter_frame(scope_frame) - self.blockvisit(node.body, scope_frame) - self.leave_frame(scope_frame) - self.pop_context_reference() - - def visit_EvalContextModifier(self, node, frame): - for keyword in node.options: - self.writeline('context.eval_ctx.%s = ' % keyword.key) - self.visit(keyword.value, frame) - try: - val = keyword.value.as_const(frame.eval_ctx) - except nodes.Impossible: - frame.eval_ctx.volatile = True - else: - setattr(frame.eval_ctx, keyword.key, val) - - def visit_ScopedEvalContextModifier(self, node, frame): - old_ctx_name = self.temporary_identifier() - saved_ctx = frame.eval_ctx.save() - self.writeline('%s = context.eval_ctx.save()' % old_ctx_name) - self.visit_EvalContextModifier(node, frame) - for child in node.body: - self.visit(child, frame) - frame.eval_ctx.revert(saved_ctx) - self.writeline('context.eval_ctx.revert(%s)' % old_ctx_name) diff --git a/pipenv/vendor/jinja2/constants.py b/pipenv/vendor/jinja2/constants.py deleted file mode 100644 index 11efd1ed..00000000 --- a/pipenv/vendor/jinja2/constants.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja.constants - ~~~~~~~~~~~~~~~ - - Various constants. - - :copyright: (c) 2017 by the Jinja Team. - :license: BSD, see LICENSE for more details. -""" - - -#: list of lorem ipsum words used by the lipsum() helper function -LOREM_IPSUM_WORDS = u'''\ -a ac accumsan ad adipiscing aenean aliquam aliquet amet ante aptent arcu at -auctor augue bibendum blandit class commodo condimentum congue consectetuer -consequat conubia convallis cras cubilia cum curabitur curae cursus dapibus -diam dictum dictumst dignissim dis dolor donec dui duis egestas eget eleifend -elementum elit enim erat eros est et etiam eu euismod facilisi facilisis fames -faucibus felis fermentum feugiat fringilla fusce gravida habitant habitasse hac -hendrerit hymenaeos iaculis id imperdiet in inceptos integer interdum ipsum -justo lacinia lacus laoreet lectus leo libero ligula litora lobortis lorem -luctus maecenas magna magnis malesuada massa mattis mauris metus mi molestie -mollis montes morbi mus nam nascetur natoque nec neque netus nibh nisi nisl non -nonummy nostra nulla nullam nunc odio orci ornare parturient pede pellentesque -penatibus per pharetra phasellus placerat platea porta porttitor posuere -potenti praesent pretium primis proin pulvinar purus quam quis quisque rhoncus -ridiculus risus rutrum sagittis sapien scelerisque sed sem semper senectus sit -sociis sociosqu sodales sollicitudin suscipit suspendisse taciti tellus tempor -tempus tincidunt torquent tortor tristique turpis ullamcorper ultrices -ultricies urna ut varius vehicula vel velit venenatis vestibulum vitae vivamus -viverra volutpat vulputate''' diff --git a/pipenv/vendor/jinja2/debug.py b/pipenv/vendor/jinja2/debug.py deleted file mode 100644 index b61139f0..00000000 --- a/pipenv/vendor/jinja2/debug.py +++ /dev/null @@ -1,372 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.debug - ~~~~~~~~~~~~ - - Implements the debug interface for Jinja. This module does some pretty - ugly stuff with the Python traceback system in order to achieve tracebacks - with correct line numbers, locals and contents. - - :copyright: (c) 2017 by the Jinja Team. - :license: BSD, see LICENSE for more details. -""" -import sys -import traceback -from types import TracebackType, CodeType -from jinja2.utils import missing, internal_code -from jinja2.exceptions import TemplateSyntaxError -from jinja2._compat import iteritems, reraise, PY2 - -# on pypy we can take advantage of transparent proxies -try: - from __pypy__ import tproxy -except ImportError: - tproxy = None - - -# how does the raise helper look like? -try: - exec("raise TypeError, 'foo'") -except SyntaxError: - raise_helper = 'raise __jinja_exception__[1]' -except TypeError: - raise_helper = 'raise __jinja_exception__[0], __jinja_exception__[1]' - - -class TracebackFrameProxy(object): - """Proxies a traceback frame.""" - - def __init__(self, tb): - self.tb = tb - self._tb_next = None - - @property - def tb_next(self): - return self._tb_next - - def set_next(self, next): - if tb_set_next is not None: - try: - tb_set_next(self.tb, next and next.tb or None) - except Exception: - # this function can fail due to all the hackery it does - # on various python implementations. We just catch errors - # down and ignore them if necessary. - pass - self._tb_next = next - - @property - def is_jinja_frame(self): - return '__jinja_template__' in self.tb.tb_frame.f_globals - - def __getattr__(self, name): - return getattr(self.tb, name) - - -def make_frame_proxy(frame): - proxy = TracebackFrameProxy(frame) - if tproxy is None: - return proxy - def operation_handler(operation, *args, **kwargs): - if operation in ('__getattribute__', '__getattr__'): - return getattr(proxy, args[0]) - elif operation == '__setattr__': - proxy.__setattr__(*args, **kwargs) - else: - return getattr(proxy, operation)(*args, **kwargs) - return tproxy(TracebackType, operation_handler) - - -class ProcessedTraceback(object): - """Holds a Jinja preprocessed traceback for printing or reraising.""" - - def __init__(self, exc_type, exc_value, frames): - assert frames, 'no frames for this traceback?' - self.exc_type = exc_type - self.exc_value = exc_value - self.frames = frames - - # newly concatenate the frames (which are proxies) - prev_tb = None - for tb in self.frames: - if prev_tb is not None: - prev_tb.set_next(tb) - prev_tb = tb - prev_tb.set_next(None) - - def render_as_text(self, limit=None): - """Return a string with the traceback.""" - lines = traceback.format_exception(self.exc_type, self.exc_value, - self.frames[0], limit=limit) - return ''.join(lines).rstrip() - - def render_as_html(self, full=False): - """Return a unicode string with the traceback as rendered HTML.""" - from jinja2.debugrenderer import render_traceback - return u'%s\n\n<!--\n%s\n-->' % ( - render_traceback(self, full=full), - self.render_as_text().decode('utf-8', 'replace') - ) - - @property - def is_template_syntax_error(self): - """`True` if this is a template syntax error.""" - return isinstance(self.exc_value, TemplateSyntaxError) - - @property - def exc_info(self): - """Exception info tuple with a proxy around the frame objects.""" - return self.exc_type, self.exc_value, self.frames[0] - - @property - def standard_exc_info(self): - """Standard python exc_info for re-raising""" - tb = self.frames[0] - # the frame will be an actual traceback (or transparent proxy) if - # we are on pypy or a python implementation with support for tproxy - if type(tb) is not TracebackType: - tb = tb.tb - return self.exc_type, self.exc_value, tb - - -def make_traceback(exc_info, source_hint=None): - """Creates a processed traceback object from the exc_info.""" - exc_type, exc_value, tb = exc_info - if isinstance(exc_value, TemplateSyntaxError): - exc_info = translate_syntax_error(exc_value, source_hint) - initial_skip = 0 - else: - initial_skip = 1 - return translate_exception(exc_info, initial_skip) - - -def translate_syntax_error(error, source=None): - """Rewrites a syntax error to please traceback systems.""" - error.source = source - error.translated = True - exc_info = (error.__class__, error, None) - filename = error.filename - if filename is None: - filename = '<unknown>' - return fake_exc_info(exc_info, filename, error.lineno) - - -def translate_exception(exc_info, initial_skip=0): - """If passed an exc_info it will automatically rewrite the exceptions - all the way down to the correct line numbers and frames. - """ - tb = exc_info[2] - frames = [] - - # skip some internal frames if wanted - for x in range(initial_skip): - if tb is not None: - tb = tb.tb_next - initial_tb = tb - - while tb is not None: - # skip frames decorated with @internalcode. These are internal - # calls we can't avoid and that are useless in template debugging - # output. - if tb.tb_frame.f_code in internal_code: - tb = tb.tb_next - continue - - # save a reference to the next frame if we override the current - # one with a faked one. - next = tb.tb_next - - # fake template exceptions - template = tb.tb_frame.f_globals.get('__jinja_template__') - if template is not None: - lineno = template.get_corresponding_lineno(tb.tb_lineno) - tb = fake_exc_info(exc_info[:2] + (tb,), template.filename, - lineno)[2] - - frames.append(make_frame_proxy(tb)) - tb = next - - # if we don't have any exceptions in the frames left, we have to - # reraise it unchanged. - # XXX: can we backup here? when could this happen? - if not frames: - reraise(exc_info[0], exc_info[1], exc_info[2]) - - return ProcessedTraceback(exc_info[0], exc_info[1], frames) - - -def get_jinja_locals(real_locals): - ctx = real_locals.get('context') - if ctx: - locals = ctx.get_all().copy() - else: - locals = {} - - local_overrides = {} - - for name, value in iteritems(real_locals): - if not name.startswith('l_') or value is missing: - continue - try: - _, depth, name = name.split('_', 2) - depth = int(depth) - except ValueError: - continue - cur_depth = local_overrides.get(name, (-1,))[0] - if cur_depth < depth: - local_overrides[name] = (depth, value) - - for name, (_, value) in iteritems(local_overrides): - if value is missing: - locals.pop(name, None) - else: - locals[name] = value - - return locals - - -def fake_exc_info(exc_info, filename, lineno): - """Helper for `translate_exception`.""" - exc_type, exc_value, tb = exc_info - - # figure the real context out - if tb is not None: - locals = get_jinja_locals(tb.tb_frame.f_locals) - - # if there is a local called __jinja_exception__, we get - # rid of it to not break the debug functionality. - locals.pop('__jinja_exception__', None) - else: - locals = {} - - # assamble fake globals we need - globals = { - '__name__': filename, - '__file__': filename, - '__jinja_exception__': exc_info[:2], - - # we don't want to keep the reference to the template around - # to not cause circular dependencies, but we mark it as Jinja - # frame for the ProcessedTraceback - '__jinja_template__': None - } - - # and fake the exception - code = compile('\n' * (lineno - 1) + raise_helper, filename, 'exec') - - # if it's possible, change the name of the code. This won't work - # on some python environments such as google appengine - try: - if tb is None: - location = 'template' - else: - function = tb.tb_frame.f_code.co_name - if function == 'root': - location = 'top-level template code' - elif function.startswith('block_'): - location = 'block "%s"' % function[6:] - else: - location = 'template' - - if PY2: - code = CodeType(0, code.co_nlocals, code.co_stacksize, - code.co_flags, code.co_code, code.co_consts, - code.co_names, code.co_varnames, filename, - location, code.co_firstlineno, - code.co_lnotab, (), ()) - else: - code = CodeType(0, code.co_kwonlyargcount, - code.co_nlocals, code.co_stacksize, - code.co_flags, code.co_code, code.co_consts, - code.co_names, code.co_varnames, filename, - location, code.co_firstlineno, - code.co_lnotab, (), ()) - except Exception as e: - pass - - # execute the code and catch the new traceback - try: - exec(code, globals, locals) - except: - exc_info = sys.exc_info() - new_tb = exc_info[2].tb_next - - # return without this frame - return exc_info[:2] + (new_tb,) - - -def _init_ugly_crap(): - """This function implements a few ugly things so that we can patch the - traceback objects. The function returned allows resetting `tb_next` on - any python traceback object. Do not attempt to use this on non cpython - interpreters - """ - import ctypes - from types import TracebackType - - if PY2: - # figure out size of _Py_ssize_t for Python 2: - if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'): - _Py_ssize_t = ctypes.c_int64 - else: - _Py_ssize_t = ctypes.c_int - else: - # platform ssize_t on Python 3 - _Py_ssize_t = ctypes.c_ssize_t - - # regular python - class _PyObject(ctypes.Structure): - pass - _PyObject._fields_ = [ - ('ob_refcnt', _Py_ssize_t), - ('ob_type', ctypes.POINTER(_PyObject)) - ] - - # python with trace - if hasattr(sys, 'getobjects'): - class _PyObject(ctypes.Structure): - pass - _PyObject._fields_ = [ - ('_ob_next', ctypes.POINTER(_PyObject)), - ('_ob_prev', ctypes.POINTER(_PyObject)), - ('ob_refcnt', _Py_ssize_t), - ('ob_type', ctypes.POINTER(_PyObject)) - ] - - class _Traceback(_PyObject): - pass - _Traceback._fields_ = [ - ('tb_next', ctypes.POINTER(_Traceback)), - ('tb_frame', ctypes.POINTER(_PyObject)), - ('tb_lasti', ctypes.c_int), - ('tb_lineno', ctypes.c_int) - ] - - def tb_set_next(tb, next): - """Set the tb_next attribute of a traceback object.""" - if not (isinstance(tb, TracebackType) and - (next is None or isinstance(next, TracebackType))): - raise TypeError('tb_set_next arguments must be traceback objects') - obj = _Traceback.from_address(id(tb)) - if tb.tb_next is not None: - old = _Traceback.from_address(id(tb.tb_next)) - old.ob_refcnt -= 1 - if next is None: - obj.tb_next = ctypes.POINTER(_Traceback)() - else: - next = _Traceback.from_address(id(next)) - next.ob_refcnt += 1 - obj.tb_next = ctypes.pointer(next) - - return tb_set_next - - -# try to get a tb_set_next implementation if we don't have transparent -# proxies. -tb_set_next = None -if tproxy is None: - try: - tb_set_next = _init_ugly_crap() - except: - pass - del _init_ugly_crap diff --git a/pipenv/vendor/jinja2/defaults.py b/pipenv/vendor/jinja2/defaults.py deleted file mode 100644 index 7c93dec0..00000000 --- a/pipenv/vendor/jinja2/defaults.py +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.defaults - ~~~~~~~~~~~~~~~ - - Jinja default filters and tags. - - :copyright: (c) 2017 by the Jinja Team. - :license: BSD, see LICENSE for more details. -""" -from jinja2._compat import range_type -from jinja2.utils import generate_lorem_ipsum, Cycler, Joiner, Namespace - - -# defaults for the parser / lexer -BLOCK_START_STRING = '{%' -BLOCK_END_STRING = '%}' -VARIABLE_START_STRING = '{{' -VARIABLE_END_STRING = '}}' -COMMENT_START_STRING = '{#' -COMMENT_END_STRING = '#}' -LINE_STATEMENT_PREFIX = None -LINE_COMMENT_PREFIX = None -TRIM_BLOCKS = False -LSTRIP_BLOCKS = False -NEWLINE_SEQUENCE = '\n' -KEEP_TRAILING_NEWLINE = False - - -# default filters, tests and namespace -from jinja2.filters import FILTERS as DEFAULT_FILTERS -from jinja2.tests import TESTS as DEFAULT_TESTS -DEFAULT_NAMESPACE = { - 'range': range_type, - 'dict': dict, - 'lipsum': generate_lorem_ipsum, - 'cycler': Cycler, - 'joiner': Joiner, - 'namespace': Namespace -} - - -# default policies -DEFAULT_POLICIES = { - 'compiler.ascii_str': True, - 'urlize.rel': 'noopener', - 'urlize.target': None, - 'truncate.leeway': 5, - 'json.dumps_function': None, - 'json.dumps_kwargs': {'sort_keys': True}, - 'ext.i18n.trimmed': False, -} - - -# export all constants -__all__ = tuple(x for x in locals().keys() if x.isupper()) diff --git a/pipenv/vendor/jinja2/environment.py b/pipenv/vendor/jinja2/environment.py deleted file mode 100644 index 549d9afa..00000000 --- a/pipenv/vendor/jinja2/environment.py +++ /dev/null @@ -1,1276 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.environment - ~~~~~~~~~~~~~~~~~~ - - Provides a class that holds runtime and parsing time options. - - :copyright: (c) 2017 by the Jinja Team. - :license: BSD, see LICENSE for more details. -""" -import os -import sys -import weakref -from functools import reduce, partial -from jinja2 import nodes -from jinja2.defaults import BLOCK_START_STRING, \ - BLOCK_END_STRING, VARIABLE_START_STRING, VARIABLE_END_STRING, \ - COMMENT_START_STRING, COMMENT_END_STRING, LINE_STATEMENT_PREFIX, \ - LINE_COMMENT_PREFIX, TRIM_BLOCKS, NEWLINE_SEQUENCE, \ - DEFAULT_FILTERS, DEFAULT_TESTS, DEFAULT_NAMESPACE, \ - DEFAULT_POLICIES, KEEP_TRAILING_NEWLINE, LSTRIP_BLOCKS -from jinja2.lexer import get_lexer, TokenStream -from jinja2.parser import Parser -from jinja2.nodes import EvalContext -from jinja2.compiler import generate, CodeGenerator -from jinja2.runtime import Undefined, new_context, Context -from jinja2.exceptions import TemplateSyntaxError, TemplateNotFound, \ - TemplatesNotFound, TemplateRuntimeError -from jinja2.utils import import_string, LRUCache, Markup, missing, \ - concat, consume, internalcode, have_async_gen -from jinja2._compat import imap, ifilter, string_types, iteritems, \ - text_type, reraise, implements_iterator, implements_to_string, \ - encode_filename, PY2, PYPY - - -# for direct template usage we have up to ten living environments -_spontaneous_environments = LRUCache(10) - -# the function to create jinja traceback objects. This is dynamically -# imported on the first exception in the exception handler. -_make_traceback = None - - -def get_spontaneous_environment(*args): - """Return a new spontaneous environment. A spontaneous environment is an - unnamed and unaccessible (in theory) environment that is used for - templates generated from a string and not from the file system. - """ - try: - env = _spontaneous_environments.get(args) - except TypeError: - return Environment(*args) - if env is not None: - return env - _spontaneous_environments[args] = env = Environment(*args) - env.shared = True - return env - - -def create_cache(size): - """Return the cache class for the given size.""" - if size == 0: - return None - if size < 0: - return {} - return LRUCache(size) - - -def copy_cache(cache): - """Create an empty copy of the given cache.""" - if cache is None: - return None - elif type(cache) is dict: - return {} - return LRUCache(cache.capacity) - - -def load_extensions(environment, extensions): - """Load the extensions from the list and bind it to the environment. - Returns a dict of instantiated environments. - """ - result = {} - for extension in extensions: - if isinstance(extension, string_types): - extension = import_string(extension) - result[extension.identifier] = extension(environment) - return result - - -def fail_for_missing_callable(string, name): - msg = string % name - if isinstance(name, Undefined): - try: - name._fail_with_undefined_error() - except Exception as e: - msg = '%s (%s; did you forget to quote the callable name?)' % (msg, e) - raise TemplateRuntimeError(msg) - - -def _environment_sanity_check(environment): - """Perform a sanity check on the environment.""" - assert issubclass(environment.undefined, Undefined), 'undefined must ' \ - 'be a subclass of undefined because filters depend on it.' - assert environment.block_start_string != \ - environment.variable_start_string != \ - environment.comment_start_string, 'block, variable and comment ' \ - 'start strings must be different' - assert environment.newline_sequence in ('\r', '\r\n', '\n'), \ - 'newline_sequence set to unknown line ending string.' - return environment - - -class Environment(object): - r"""The core component of Jinja is the `Environment`. It contains - important shared variables like configuration, filters, tests, - globals and others. Instances of this class may be modified if - they are not shared and if no template was loaded so far. - Modifications on environments after the first template was loaded - will lead to surprising effects and undefined behavior. - - Here are the possible initialization parameters: - - `block_start_string` - The string marking the beginning of a block. Defaults to ``'{%'``. - - `block_end_string` - The string marking the end of a block. Defaults to ``'%}'``. - - `variable_start_string` - The string marking the beginning of a print statement. - Defaults to ``'{{'``. - - `variable_end_string` - The string marking the end of a print statement. Defaults to - ``'}}'``. - - `comment_start_string` - The string marking the beginning of a comment. Defaults to ``'{#'``. - - `comment_end_string` - The string marking the end of a comment. Defaults to ``'#}'``. - - `line_statement_prefix` - If given and a string, this will be used as prefix for line based - statements. See also :ref:`line-statements`. - - `line_comment_prefix` - If given and a string, this will be used as prefix for line based - comments. See also :ref:`line-statements`. - - .. versionadded:: 2.2 - - `trim_blocks` - If this is set to ``True`` the first newline after a block is - removed (block, not variable tag!). Defaults to `False`. - - `lstrip_blocks` - If this is set to ``True`` leading spaces and tabs are stripped - from the start of a line to a block. Defaults to `False`. - - `newline_sequence` - The sequence that starts a newline. Must be one of ``'\r'``, - ``'\n'`` or ``'\r\n'``. The default is ``'\n'`` which is a - useful default for Linux and OS X systems as well as web - applications. - - `keep_trailing_newline` - Preserve the trailing newline when rendering templates. - The default is ``False``, which causes a single newline, - if present, to be stripped from the end of the template. - - .. versionadded:: 2.7 - - `extensions` - List of Jinja extensions to use. This can either be import paths - as strings or extension classes. For more information have a - look at :ref:`the extensions documentation <jinja-extensions>`. - - `optimized` - should the optimizer be enabled? Default is ``True``. - - `undefined` - :class:`Undefined` or a subclass of it that is used to represent - undefined values in the template. - - `finalize` - A callable that can be used to process the result of a variable - expression before it is output. For example one can convert - ``None`` implicitly into an empty string here. - - `autoescape` - If set to ``True`` the XML/HTML autoescaping feature is enabled by - default. For more details about autoescaping see - :class:`~jinja2.utils.Markup`. As of Jinja 2.4 this can also - be a callable that is passed the template name and has to - return ``True`` or ``False`` depending on autoescape should be - enabled by default. - - .. versionchanged:: 2.4 - `autoescape` can now be a function - - `loader` - The template loader for this environment. - - `cache_size` - The size of the cache. Per default this is ``400`` which means - that if more than 400 templates are loaded the loader will clean - out the least recently used template. If the cache size is set to - ``0`` templates are recompiled all the time, if the cache size is - ``-1`` the cache will not be cleaned. - - .. versionchanged:: 2.8 - The cache size was increased to 400 from a low 50. - - `auto_reload` - Some loaders load templates from locations where the template - sources may change (ie: file system or database). If - ``auto_reload`` is set to ``True`` (default) every time a template is - requested the loader checks if the source changed and if yes, it - will reload the template. For higher performance it's possible to - disable that. - - `bytecode_cache` - If set to a bytecode cache object, this object will provide a - cache for the internal Jinja bytecode so that templates don't - have to be parsed if they were not changed. - - See :ref:`bytecode-cache` for more information. - - `enable_async` - If set to true this enables async template execution which allows - you to take advantage of newer Python features. This requires - Python 3.6 or later. - """ - - #: if this environment is sandboxed. Modifying this variable won't make - #: the environment sandboxed though. For a real sandboxed environment - #: have a look at jinja2.sandbox. This flag alone controls the code - #: generation by the compiler. - sandboxed = False - - #: True if the environment is just an overlay - overlayed = False - - #: the environment this environment is linked to if it is an overlay - linked_to = None - - #: shared environments have this set to `True`. A shared environment - #: must not be modified - shared = False - - #: these are currently EXPERIMENTAL undocumented features. - exception_handler = None - exception_formatter = None - - #: the class that is used for code generation. See - #: :class:`~jinja2.compiler.CodeGenerator` for more information. - code_generator_class = CodeGenerator - - #: the context class thatis used for templates. See - #: :class:`~jinja2.runtime.Context` for more information. - context_class = Context - - def __init__(self, - block_start_string=BLOCK_START_STRING, - block_end_string=BLOCK_END_STRING, - variable_start_string=VARIABLE_START_STRING, - variable_end_string=VARIABLE_END_STRING, - comment_start_string=COMMENT_START_STRING, - comment_end_string=COMMENT_END_STRING, - line_statement_prefix=LINE_STATEMENT_PREFIX, - line_comment_prefix=LINE_COMMENT_PREFIX, - trim_blocks=TRIM_BLOCKS, - lstrip_blocks=LSTRIP_BLOCKS, - newline_sequence=NEWLINE_SEQUENCE, - keep_trailing_newline=KEEP_TRAILING_NEWLINE, - extensions=(), - optimized=True, - undefined=Undefined, - finalize=None, - autoescape=False, - loader=None, - cache_size=400, - auto_reload=True, - bytecode_cache=None, - enable_async=False): - # !!Important notice!! - # The constructor accepts quite a few arguments that should be - # passed by keyword rather than position. However it's important to - # not change the order of arguments because it's used at least - # internally in those cases: - # - spontaneous environments (i18n extension and Template) - # - unittests - # If parameter changes are required only add parameters at the end - # and don't change the arguments (or the defaults!) of the arguments - # existing already. - - # lexer / parser information - self.block_start_string = block_start_string - self.block_end_string = block_end_string - self.variable_start_string = variable_start_string - self.variable_end_string = variable_end_string - self.comment_start_string = comment_start_string - self.comment_end_string = comment_end_string - self.line_statement_prefix = line_statement_prefix - self.line_comment_prefix = line_comment_prefix - self.trim_blocks = trim_blocks - self.lstrip_blocks = lstrip_blocks - self.newline_sequence = newline_sequence - self.keep_trailing_newline = keep_trailing_newline - - # runtime information - self.undefined = undefined - self.optimized = optimized - self.finalize = finalize - self.autoescape = autoescape - - # defaults - self.filters = DEFAULT_FILTERS.copy() - self.tests = DEFAULT_TESTS.copy() - self.globals = DEFAULT_NAMESPACE.copy() - - # set the loader provided - self.loader = loader - self.cache = create_cache(cache_size) - self.bytecode_cache = bytecode_cache - self.auto_reload = auto_reload - - # configurable policies - self.policies = DEFAULT_POLICIES.copy() - - # load extensions - self.extensions = load_extensions(self, extensions) - - self.enable_async = enable_async - self.is_async = self.enable_async and have_async_gen - - _environment_sanity_check(self) - - def add_extension(self, extension): - """Adds an extension after the environment was created. - - .. versionadded:: 2.5 - """ - self.extensions.update(load_extensions(self, [extension])) - - def extend(self, **attributes): - """Add the items to the instance of the environment if they do not exist - yet. This is used by :ref:`extensions <writing-extensions>` to register - callbacks and configuration values without breaking inheritance. - """ - for key, value in iteritems(attributes): - if not hasattr(self, key): - setattr(self, key, value) - - def overlay(self, block_start_string=missing, block_end_string=missing, - variable_start_string=missing, variable_end_string=missing, - comment_start_string=missing, comment_end_string=missing, - line_statement_prefix=missing, line_comment_prefix=missing, - trim_blocks=missing, lstrip_blocks=missing, - extensions=missing, optimized=missing, - undefined=missing, finalize=missing, autoescape=missing, - loader=missing, cache_size=missing, auto_reload=missing, - bytecode_cache=missing): - """Create a new overlay environment that shares all the data with the - current environment except for cache and the overridden attributes. - Extensions cannot be removed for an overlayed environment. An overlayed - environment automatically gets all the extensions of the environment it - is linked to plus optional extra extensions. - - Creating overlays should happen after the initial environment was set - up completely. Not all attributes are truly linked, some are just - copied over so modifications on the original environment may not shine - through. - """ - args = dict(locals()) - del args['self'], args['cache_size'], args['extensions'] - - rv = object.__new__(self.__class__) - rv.__dict__.update(self.__dict__) - rv.overlayed = True - rv.linked_to = self - - for key, value in iteritems(args): - if value is not missing: - setattr(rv, key, value) - - if cache_size is not missing: - rv.cache = create_cache(cache_size) - else: - rv.cache = copy_cache(self.cache) - - rv.extensions = {} - for key, value in iteritems(self.extensions): - rv.extensions[key] = value.bind(rv) - if extensions is not missing: - rv.extensions.update(load_extensions(rv, extensions)) - - return _environment_sanity_check(rv) - - lexer = property(get_lexer, doc="The lexer for this environment.") - - def iter_extensions(self): - """Iterates over the extensions by priority.""" - return iter(sorted(self.extensions.values(), - key=lambda x: x.priority)) - - def getitem(self, obj, argument): - """Get an item or attribute of an object but prefer the item.""" - try: - return obj[argument] - except (AttributeError, TypeError, LookupError): - if isinstance(argument, string_types): - try: - attr = str(argument) - except Exception: - pass - else: - try: - return getattr(obj, attr) - except AttributeError: - pass - return self.undefined(obj=obj, name=argument) - - def getattr(self, obj, attribute): - """Get an item or attribute of an object but prefer the attribute. - Unlike :meth:`getitem` the attribute *must* be a bytestring. - """ - try: - return getattr(obj, attribute) - except AttributeError: - pass - try: - return obj[attribute] - except (TypeError, LookupError, AttributeError): - return self.undefined(obj=obj, name=attribute) - - def call_filter(self, name, value, args=None, kwargs=None, - context=None, eval_ctx=None): - """Invokes a filter on a value the same way the compiler does it. - - Note that on Python 3 this might return a coroutine in case the - filter is running from an environment in async mode and the filter - supports async execution. It's your responsibility to await this - if needed. - - .. versionadded:: 2.7 - """ - func = self.filters.get(name) - if func is None: - fail_for_missing_callable('no filter named %r', name) - args = [value] + list(args or ()) - if getattr(func, 'contextfilter', False): - if context is None: - raise TemplateRuntimeError('Attempted to invoke context ' - 'filter without context') - args.insert(0, context) - elif getattr(func, 'evalcontextfilter', False): - if eval_ctx is None: - if context is not None: - eval_ctx = context.eval_ctx - else: - eval_ctx = EvalContext(self) - args.insert(0, eval_ctx) - elif getattr(func, 'environmentfilter', False): - args.insert(0, self) - return func(*args, **(kwargs or {})) - - def call_test(self, name, value, args=None, kwargs=None): - """Invokes a test on a value the same way the compiler does it. - - .. versionadded:: 2.7 - """ - func = self.tests.get(name) - if func is None: - fail_for_missing_callable('no test named %r', name) - return func(value, *(args or ()), **(kwargs or {})) - - @internalcode - def parse(self, source, name=None, filename=None): - """Parse the sourcecode and return the abstract syntax tree. This - tree of nodes is used by the compiler to convert the template into - executable source- or bytecode. This is useful for debugging or to - extract information from templates. - - If you are :ref:`developing Jinja2 extensions <writing-extensions>` - this gives you a good overview of the node tree generated. - """ - try: - return self._parse(source, name, filename) - except TemplateSyntaxError: - exc_info = sys.exc_info() - self.handle_exception(exc_info, source_hint=source) - - def _parse(self, source, name, filename): - """Internal parsing function used by `parse` and `compile`.""" - return Parser(self, source, name, encode_filename(filename)).parse() - - def lex(self, source, name=None, filename=None): - """Lex the given sourcecode and return a generator that yields - tokens as tuples in the form ``(lineno, token_type, value)``. - This can be useful for :ref:`extension development <writing-extensions>` - and debugging templates. - - This does not perform preprocessing. If you want the preprocessing - of the extensions to be applied you have to filter source through - the :meth:`preprocess` method. - """ - source = text_type(source) - try: - return self.lexer.tokeniter(source, name, filename) - except TemplateSyntaxError: - exc_info = sys.exc_info() - self.handle_exception(exc_info, source_hint=source) - - def preprocess(self, source, name=None, filename=None): - """Preprocesses the source with all extensions. This is automatically - called for all parsing and compiling methods but *not* for :meth:`lex` - because there you usually only want the actual source tokenized. - """ - return reduce(lambda s, e: e.preprocess(s, name, filename), - self.iter_extensions(), text_type(source)) - - def _tokenize(self, source, name, filename=None, state=None): - """Called by the parser to do the preprocessing and filtering - for all the extensions. Returns a :class:`~jinja2.lexer.TokenStream`. - """ - source = self.preprocess(source, name, filename) - stream = self.lexer.tokenize(source, name, filename, state) - for ext in self.iter_extensions(): - stream = ext.filter_stream(stream) - if not isinstance(stream, TokenStream): - stream = TokenStream(stream, name, filename) - return stream - - def _generate(self, source, name, filename, defer_init=False): - """Internal hook that can be overridden to hook a different generate - method in. - - .. versionadded:: 2.5 - """ - return generate(source, self, name, filename, defer_init=defer_init, - optimized=self.optimized) - - def _compile(self, source, filename): - """Internal hook that can be overridden to hook a different compile - method in. - - .. versionadded:: 2.5 - """ - return compile(source, filename, 'exec') - - @internalcode - def compile(self, source, name=None, filename=None, raw=False, - defer_init=False): - """Compile a node or template source code. The `name` parameter is - the load name of the template after it was joined using - :meth:`join_path` if necessary, not the filename on the file system. - the `filename` parameter is the estimated filename of the template on - the file system. If the template came from a database or memory this - can be omitted. - - The return value of this method is a python code object. If the `raw` - parameter is `True` the return value will be a string with python - code equivalent to the bytecode returned otherwise. This method is - mainly used internally. - - `defer_init` is use internally to aid the module code generator. This - causes the generated code to be able to import without the global - environment variable to be set. - - .. versionadded:: 2.4 - `defer_init` parameter added. - """ - source_hint = None - try: - if isinstance(source, string_types): - source_hint = source - source = self._parse(source, name, filename) - source = self._generate(source, name, filename, - defer_init=defer_init) - if raw: - return source - if filename is None: - filename = '<template>' - else: - filename = encode_filename(filename) - return self._compile(source, filename) - except TemplateSyntaxError: - exc_info = sys.exc_info() - self.handle_exception(exc_info, source_hint=source_hint) - - def compile_expression(self, source, undefined_to_none=True): - """A handy helper method that returns a callable that accepts keyword - arguments that appear as variables in the expression. If called it - returns the result of the expression. - - This is useful if applications want to use the same rules as Jinja - in template "configuration files" or similar situations. - - Example usage: - - >>> env = Environment() - >>> expr = env.compile_expression('foo == 42') - >>> expr(foo=23) - False - >>> expr(foo=42) - True - - Per default the return value is converted to `None` if the - expression returns an undefined value. This can be changed - by setting `undefined_to_none` to `False`. - - >>> env.compile_expression('var')() is None - True - >>> env.compile_expression('var', undefined_to_none=False)() - Undefined - - .. versionadded:: 2.1 - """ - parser = Parser(self, source, state='variable') - exc_info = None - try: - expr = parser.parse_expression() - if not parser.stream.eos: - raise TemplateSyntaxError('chunk after expression', - parser.stream.current.lineno, - None, None) - expr.set_environment(self) - except TemplateSyntaxError: - exc_info = sys.exc_info() - if exc_info is not None: - self.handle_exception(exc_info, source_hint=source) - body = [nodes.Assign(nodes.Name('result', 'store'), expr, lineno=1)] - template = self.from_string(nodes.Template(body, lineno=1)) - return TemplateExpression(template, undefined_to_none) - - def compile_templates(self, target, extensions=None, filter_func=None, - zip='deflated', log_function=None, - ignore_errors=True, py_compile=False): - """Finds all the templates the loader can find, compiles them - and stores them in `target`. If `zip` is `None`, instead of in a - zipfile, the templates will be stored in a directory. - By default a deflate zip algorithm is used. To switch to - the stored algorithm, `zip` can be set to ``'stored'``. - - `extensions` and `filter_func` are passed to :meth:`list_templates`. - Each template returned will be compiled to the target folder or - zipfile. - - By default template compilation errors are ignored. In case a - log function is provided, errors are logged. If you want template - syntax errors to abort the compilation you can set `ignore_errors` - to `False` and you will get an exception on syntax errors. - - If `py_compile` is set to `True` .pyc files will be written to the - target instead of standard .py files. This flag does not do anything - on pypy and Python 3 where pyc files are not picked up by itself and - don't give much benefit. - - .. versionadded:: 2.4 - """ - from jinja2.loaders import ModuleLoader - - if log_function is None: - log_function = lambda x: None - - if py_compile: - if not PY2 or PYPY: - from warnings import warn - warn(Warning('py_compile has no effect on pypy or Python 3')) - py_compile = False - else: - import imp - import marshal - py_header = imp.get_magic() + \ - u'\xff\xff\xff\xff'.encode('iso-8859-15') - - # Python 3.3 added a source filesize to the header - if sys.version_info >= (3, 3): - py_header += u'\x00\x00\x00\x00'.encode('iso-8859-15') - - def write_file(filename, data, mode): - if zip: - info = ZipInfo(filename) - info.external_attr = 0o755 << 16 - zip_file.writestr(info, data) - else: - f = open(os.path.join(target, filename), mode) - try: - f.write(data) - finally: - f.close() - - if zip is not None: - from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED, ZIP_STORED - zip_file = ZipFile(target, 'w', dict(deflated=ZIP_DEFLATED, - stored=ZIP_STORED)[zip]) - log_function('Compiling into Zip archive "%s"' % target) - else: - if not os.path.isdir(target): - os.makedirs(target) - log_function('Compiling into folder "%s"' % target) - - try: - for name in self.list_templates(extensions, filter_func): - source, filename, _ = self.loader.get_source(self, name) - try: - code = self.compile(source, name, filename, True, True) - except TemplateSyntaxError as e: - if not ignore_errors: - raise - log_function('Could not compile "%s": %s' % (name, e)) - continue - - filename = ModuleLoader.get_module_filename(name) - - if py_compile: - c = self._compile(code, encode_filename(filename)) - write_file(filename + 'c', py_header + - marshal.dumps(c), 'wb') - log_function('Byte-compiled "%s" as %s' % - (name, filename + 'c')) - else: - write_file(filename, code, 'w') - log_function('Compiled "%s" as %s' % (name, filename)) - finally: - if zip: - zip_file.close() - - log_function('Finished compiling templates') - - def list_templates(self, extensions=None, filter_func=None): - """Returns a list of templates for this environment. This requires - that the loader supports the loader's - :meth:`~BaseLoader.list_templates` method. - - If there are other files in the template folder besides the - actual templates, the returned list can be filtered. There are two - ways: either `extensions` is set to a list of file extensions for - templates, or a `filter_func` can be provided which is a callable that - is passed a template name and should return `True` if it should end up - in the result list. - - If the loader does not support that, a :exc:`TypeError` is raised. - - .. versionadded:: 2.4 - """ - x = self.loader.list_templates() - if extensions is not None: - if filter_func is not None: - raise TypeError('either extensions or filter_func ' - 'can be passed, but not both') - filter_func = lambda x: '.' in x and \ - x.rsplit('.', 1)[1] in extensions - if filter_func is not None: - x = list(ifilter(filter_func, x)) - return x - - def handle_exception(self, exc_info=None, rendered=False, source_hint=None): - """Exception handling helper. This is used internally to either raise - rewritten exceptions or return a rendered traceback for the template. - """ - global _make_traceback - if exc_info is None: - exc_info = sys.exc_info() - - # the debugging module is imported when it's used for the first time. - # we're doing a lot of stuff there and for applications that do not - # get any exceptions in template rendering there is no need to load - # all of that. - if _make_traceback is None: - from jinja2.debug import make_traceback as _make_traceback - traceback = _make_traceback(exc_info, source_hint) - if rendered and self.exception_formatter is not None: - return self.exception_formatter(traceback) - if self.exception_handler is not None: - self.exception_handler(traceback) - exc_type, exc_value, tb = traceback.standard_exc_info - reraise(exc_type, exc_value, tb) - - def join_path(self, template, parent): - """Join a template with the parent. By default all the lookups are - relative to the loader root so this method returns the `template` - parameter unchanged, but if the paths should be relative to the - parent template, this function can be used to calculate the real - template name. - - Subclasses may override this method and implement template path - joining here. - """ - return template - - @internalcode - def _load_template(self, name, globals): - if self.loader is None: - raise TypeError('no loader for this environment specified') - cache_key = (weakref.ref(self.loader), name) - if self.cache is not None: - template = self.cache.get(cache_key) - if template is not None and (not self.auto_reload or - template.is_up_to_date): - return template - template = self.loader.load(self, name, globals) - if self.cache is not None: - self.cache[cache_key] = template - return template - - @internalcode - def get_template(self, name, parent=None, globals=None): - """Load a template from the loader. If a loader is configured this - method asks the loader for the template and returns a :class:`Template`. - If the `parent` parameter is not `None`, :meth:`join_path` is called - to get the real template name before loading. - - The `globals` parameter can be used to provide template wide globals. - These variables are available in the context at render time. - - If the template does not exist a :exc:`TemplateNotFound` exception is - raised. - - .. versionchanged:: 2.4 - If `name` is a :class:`Template` object it is returned from the - function unchanged. - """ - if isinstance(name, Template): - return name - if parent is not None: - name = self.join_path(name, parent) - return self._load_template(name, self.make_globals(globals)) - - @internalcode - def select_template(self, names, parent=None, globals=None): - """Works like :meth:`get_template` but tries a number of templates - before it fails. If it cannot find any of the templates, it will - raise a :exc:`TemplatesNotFound` exception. - - .. versionadded:: 2.3 - - .. versionchanged:: 2.4 - If `names` contains a :class:`Template` object it is returned - from the function unchanged. - """ - if not names: - raise TemplatesNotFound(message=u'Tried to select from an empty list ' - u'of templates.') - globals = self.make_globals(globals) - for name in names: - if isinstance(name, Template): - return name - if parent is not None: - name = self.join_path(name, parent) - try: - return self._load_template(name, globals) - except TemplateNotFound: - pass - raise TemplatesNotFound(names) - - @internalcode - def get_or_select_template(self, template_name_or_list, - parent=None, globals=None): - """Does a typecheck and dispatches to :meth:`select_template` - if an iterable of template names is given, otherwise to - :meth:`get_template`. - - .. versionadded:: 2.3 - """ - if isinstance(template_name_or_list, string_types): - return self.get_template(template_name_or_list, parent, globals) - elif isinstance(template_name_or_list, Template): - return template_name_or_list - return self.select_template(template_name_or_list, parent, globals) - - def from_string(self, source, globals=None, template_class=None): - """Load a template from a string. This parses the source given and - returns a :class:`Template` object. - """ - globals = self.make_globals(globals) - cls = template_class or self.template_class - return cls.from_code(self, self.compile(source), globals, None) - - def make_globals(self, d): - """Return a dict for the globals.""" - if not d: - return self.globals - return dict(self.globals, **d) - - -class Template(object): - """The central template object. This class represents a compiled template - and is used to evaluate it. - - Normally the template object is generated from an :class:`Environment` but - it also has a constructor that makes it possible to create a template - instance directly using the constructor. It takes the same arguments as - the environment constructor but it's not possible to specify a loader. - - Every template object has a few methods and members that are guaranteed - to exist. However it's important that a template object should be - considered immutable. Modifications on the object are not supported. - - Template objects created from the constructor rather than an environment - do have an `environment` attribute that points to a temporary environment - that is probably shared with other templates created with the constructor - and compatible settings. - - >>> template = Template('Hello {{ name }}!') - >>> template.render(name='John Doe') == u'Hello John Doe!' - True - >>> stream = template.stream(name='John Doe') - >>> next(stream) == u'Hello John Doe!' - True - >>> next(stream) - Traceback (most recent call last): - ... - StopIteration - """ - - def __new__(cls, source, - block_start_string=BLOCK_START_STRING, - block_end_string=BLOCK_END_STRING, - variable_start_string=VARIABLE_START_STRING, - variable_end_string=VARIABLE_END_STRING, - comment_start_string=COMMENT_START_STRING, - comment_end_string=COMMENT_END_STRING, - line_statement_prefix=LINE_STATEMENT_PREFIX, - line_comment_prefix=LINE_COMMENT_PREFIX, - trim_blocks=TRIM_BLOCKS, - lstrip_blocks=LSTRIP_BLOCKS, - newline_sequence=NEWLINE_SEQUENCE, - keep_trailing_newline=KEEP_TRAILING_NEWLINE, - extensions=(), - optimized=True, - undefined=Undefined, - finalize=None, - autoescape=False, - enable_async=False): - env = get_spontaneous_environment( - block_start_string, block_end_string, variable_start_string, - variable_end_string, comment_start_string, comment_end_string, - line_statement_prefix, line_comment_prefix, trim_blocks, - lstrip_blocks, newline_sequence, keep_trailing_newline, - frozenset(extensions), optimized, undefined, finalize, autoescape, - None, 0, False, None, enable_async) - return env.from_string(source, template_class=cls) - - @classmethod - def from_code(cls, environment, code, globals, uptodate=None): - """Creates a template object from compiled code and the globals. This - is used by the loaders and environment to create a template object. - """ - namespace = { - 'environment': environment, - '__file__': code.co_filename - } - exec(code, namespace) - rv = cls._from_namespace(environment, namespace, globals) - rv._uptodate = uptodate - return rv - - @classmethod - def from_module_dict(cls, environment, module_dict, globals): - """Creates a template object from a module. This is used by the - module loader to create a template object. - - .. versionadded:: 2.4 - """ - return cls._from_namespace(environment, module_dict, globals) - - @classmethod - def _from_namespace(cls, environment, namespace, globals): - t = object.__new__(cls) - t.environment = environment - t.globals = globals - t.name = namespace['name'] - t.filename = namespace['__file__'] - t.blocks = namespace['blocks'] - - # render function and module - t.root_render_func = namespace['root'] - t._module = None - - # debug and loader helpers - t._debug_info = namespace['debug_info'] - t._uptodate = None - - # store the reference - namespace['environment'] = environment - namespace['__jinja_template__'] = t - - return t - - def render(self, *args, **kwargs): - """This method accepts the same arguments as the `dict` constructor: - A dict, a dict subclass or some keyword arguments. If no arguments - are given the context will be empty. These two calls do the same:: - - template.render(knights='that say nih') - template.render({'knights': 'that say nih'}) - - This will return the rendered template as unicode string. - """ - vars = dict(*args, **kwargs) - try: - return concat(self.root_render_func(self.new_context(vars))) - except Exception: - exc_info = sys.exc_info() - return self.environment.handle_exception(exc_info, True) - - def render_async(self, *args, **kwargs): - """This works similar to :meth:`render` but returns a coroutine - that when awaited returns the entire rendered template string. This - requires the async feature to be enabled. - - Example usage:: - - await template.render_async(knights='that say nih; asynchronously') - """ - # see asyncsupport for the actual implementation - raise NotImplementedError('This feature is not available for this ' - 'version of Python') - - def stream(self, *args, **kwargs): - """Works exactly like :meth:`generate` but returns a - :class:`TemplateStream`. - """ - return TemplateStream(self.generate(*args, **kwargs)) - - def generate(self, *args, **kwargs): - """For very large templates it can be useful to not render the whole - template at once but evaluate each statement after another and yield - piece for piece. This method basically does exactly that and returns - a generator that yields one item after another as unicode strings. - - It accepts the same arguments as :meth:`render`. - """ - vars = dict(*args, **kwargs) - try: - for event in self.root_render_func(self.new_context(vars)): - yield event - except Exception: - exc_info = sys.exc_info() - else: - return - yield self.environment.handle_exception(exc_info, True) - - def generate_async(self, *args, **kwargs): - """An async version of :meth:`generate`. Works very similarly but - returns an async iterator instead. - """ - # see asyncsupport for the actual implementation - raise NotImplementedError('This feature is not available for this ' - 'version of Python') - - def new_context(self, vars=None, shared=False, locals=None): - """Create a new :class:`Context` for this template. The vars - provided will be passed to the template. Per default the globals - are added to the context. If shared is set to `True` the data - is passed as it to the context without adding the globals. - - `locals` can be a dict of local variables for internal usage. - """ - return new_context(self.environment, self.name, self.blocks, - vars, shared, self.globals, locals) - - def make_module(self, vars=None, shared=False, locals=None): - """This method works like the :attr:`module` attribute when called - without arguments but it will evaluate the template on every call - rather than caching it. It's also possible to provide - a dict which is then used as context. The arguments are the same - as for the :meth:`new_context` method. - """ - return TemplateModule(self, self.new_context(vars, shared, locals)) - - def make_module_async(self, vars=None, shared=False, locals=None): - """As template module creation can invoke template code for - asynchronous exections this method must be used instead of the - normal :meth:`make_module` one. Likewise the module attribute - becomes unavailable in async mode. - """ - # see asyncsupport for the actual implementation - raise NotImplementedError('This feature is not available for this ' - 'version of Python') - - @internalcode - def _get_default_module(self): - if self._module is not None: - return self._module - self._module = rv = self.make_module() - return rv - - @property - def module(self): - """The template as module. This is used for imports in the - template runtime but is also useful if one wants to access - exported template variables from the Python layer: - - >>> t = Template('{% macro foo() %}42{% endmacro %}23') - >>> str(t.module) - '23' - >>> t.module.foo() == u'42' - True - - This attribute is not available if async mode is enabled. - """ - return self._get_default_module() - - def get_corresponding_lineno(self, lineno): - """Return the source line number of a line number in the - generated bytecode as they are not in sync. - """ - for template_line, code_line in reversed(self.debug_info): - if code_line <= lineno: - return template_line - return 1 - - @property - def is_up_to_date(self): - """If this variable is `False` there is a newer version available.""" - if self._uptodate is None: - return True - return self._uptodate() - - @property - def debug_info(self): - """The debug info mapping.""" - return [tuple(imap(int, x.split('='))) for x in - self._debug_info.split('&')] - - def __repr__(self): - if self.name is None: - name = 'memory:%x' % id(self) - else: - name = repr(self.name) - return '<%s %s>' % (self.__class__.__name__, name) - - -@implements_to_string -class TemplateModule(object): - """Represents an imported template. All the exported names of the - template are available as attributes on this object. Additionally - converting it into an unicode- or bytestrings renders the contents. - """ - - def __init__(self, template, context, body_stream=None): - if body_stream is None: - if context.environment.is_async: - raise RuntimeError('Async mode requires a body stream ' - 'to be passed to a template module. Use ' - 'the async methods of the API you are ' - 'using.') - body_stream = list(template.root_render_func(context)) - self._body_stream = body_stream - self.__dict__.update(context.get_exported()) - self.__name__ = template.name - - def __html__(self): - return Markup(concat(self._body_stream)) - - def __str__(self): - return concat(self._body_stream) - - def __repr__(self): - if self.__name__ is None: - name = 'memory:%x' % id(self) - else: - name = repr(self.__name__) - return '<%s %s>' % (self.__class__.__name__, name) - - -class TemplateExpression(object): - """The :meth:`jinja2.Environment.compile_expression` method returns an - instance of this object. It encapsulates the expression-like access - to the template with an expression it wraps. - """ - - def __init__(self, template, undefined_to_none): - self._template = template - self._undefined_to_none = undefined_to_none - - def __call__(self, *args, **kwargs): - context = self._template.new_context(dict(*args, **kwargs)) - consume(self._template.root_render_func(context)) - rv = context.vars['result'] - if self._undefined_to_none and isinstance(rv, Undefined): - rv = None - return rv - - -@implements_iterator -class TemplateStream(object): - """A template stream works pretty much like an ordinary python generator - but it can buffer multiple items to reduce the number of total iterations. - Per default the output is unbuffered which means that for every unbuffered - instruction in the template one unicode string is yielded. - - If buffering is enabled with a buffer size of 5, five items are combined - into a new unicode string. This is mainly useful if you are streaming - big templates to a client via WSGI which flushes after each iteration. - """ - - def __init__(self, gen): - self._gen = gen - self.disable_buffering() - - def dump(self, fp, encoding=None, errors='strict'): - """Dump the complete stream into a file or file-like object. - Per default unicode strings are written, if you want to encode - before writing specify an `encoding`. - - Example usage:: - - Template('Hello {{ name }}!').stream(name='foo').dump('hello.html') - """ - close = False - if isinstance(fp, string_types): - if encoding is None: - encoding = 'utf-8' - fp = open(fp, 'wb') - close = True - try: - if encoding is not None: - iterable = (x.encode(encoding, errors) for x in self) - else: - iterable = self - if hasattr(fp, 'writelines'): - fp.writelines(iterable) - else: - for item in iterable: - fp.write(item) - finally: - if close: - fp.close() - - def disable_buffering(self): - """Disable the output buffering.""" - self._next = partial(next, self._gen) - self.buffered = False - - def _buffered_generator(self, size): - buf = [] - c_size = 0 - push = buf.append - - while 1: - try: - while c_size < size: - c = next(self._gen) - push(c) - if c: - c_size += 1 - except StopIteration: - if not c_size: - return - yield concat(buf) - del buf[:] - c_size = 0 - - def enable_buffering(self, size=5): - """Enable buffering. Buffer `size` items before yielding them.""" - if size <= 1: - raise ValueError('buffer size too small') - - self.buffered = True - self._next = partial(next, self._buffered_generator(size)) - - def __iter__(self): - return self - - def __next__(self): - return self._next() - - -# hook in default template class. if anyone reads this comment: ignore that -# it's possible to use custom templates ;-) -Environment.template_class = Template diff --git a/pipenv/vendor/jinja2/exceptions.py b/pipenv/vendor/jinja2/exceptions.py deleted file mode 100644 index c018a33e..00000000 --- a/pipenv/vendor/jinja2/exceptions.py +++ /dev/null @@ -1,146 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.exceptions - ~~~~~~~~~~~~~~~~~ - - Jinja exceptions. - - :copyright: (c) 2017 by the Jinja Team. - :license: BSD, see LICENSE for more details. -""" -from jinja2._compat import imap, text_type, PY2, implements_to_string - - -class TemplateError(Exception): - """Baseclass for all template errors.""" - - if PY2: - def __init__(self, message=None): - if message is not None: - message = text_type(message).encode('utf-8') - Exception.__init__(self, message) - - @property - def message(self): - if self.args: - message = self.args[0] - if message is not None: - return message.decode('utf-8', 'replace') - - def __unicode__(self): - return self.message or u'' - else: - def __init__(self, message=None): - Exception.__init__(self, message) - - @property - def message(self): - if self.args: - message = self.args[0] - if message is not None: - return message - - -@implements_to_string -class TemplateNotFound(IOError, LookupError, TemplateError): - """Raised if a template does not exist.""" - - # looks weird, but removes the warning descriptor that just - # bogusly warns us about message being deprecated - message = None - - def __init__(self, name, message=None): - IOError.__init__(self) - if message is None: - message = name - self.message = message - self.name = name - self.templates = [name] - - def __str__(self): - return self.message - - -class TemplatesNotFound(TemplateNotFound): - """Like :class:`TemplateNotFound` but raised if multiple templates - are selected. This is a subclass of :class:`TemplateNotFound` - exception, so just catching the base exception will catch both. - - .. versionadded:: 2.2 - """ - - def __init__(self, names=(), message=None): - if message is None: - message = u'none of the templates given were found: ' + \ - u', '.join(imap(text_type, names)) - TemplateNotFound.__init__(self, names and names[-1] or None, message) - self.templates = list(names) - - -@implements_to_string -class TemplateSyntaxError(TemplateError): - """Raised to tell the user that there is a problem with the template.""" - - def __init__(self, message, lineno, name=None, filename=None): - TemplateError.__init__(self, message) - self.lineno = lineno - self.name = name - self.filename = filename - self.source = None - - # this is set to True if the debug.translate_syntax_error - # function translated the syntax error into a new traceback - self.translated = False - - def __str__(self): - # for translated errors we only return the message - if self.translated: - return self.message - - # otherwise attach some stuff - location = 'line %d' % self.lineno - name = self.filename or self.name - if name: - location = 'File "%s", %s' % (name, location) - lines = [self.message, ' ' + location] - - # if the source is set, add the line to the output - if self.source is not None: - try: - line = self.source.splitlines()[self.lineno - 1] - except IndexError: - line = None - if line: - lines.append(' ' + line.strip()) - - return u'\n'.join(lines) - - -class TemplateAssertionError(TemplateSyntaxError): - """Like a template syntax error, but covers cases where something in the - template caused an error at compile time that wasn't necessarily caused - by a syntax error. However it's a direct subclass of - :exc:`TemplateSyntaxError` and has the same attributes. - """ - - -class TemplateRuntimeError(TemplateError): - """A generic runtime error in the template engine. Under some situations - Jinja may raise this exception. - """ - - -class UndefinedError(TemplateRuntimeError): - """Raised if a template tries to operate on :class:`Undefined`.""" - - -class SecurityError(TemplateRuntimeError): - """Raised if a template tries to do something insecure if the - sandbox is enabled. - """ - - -class FilterArgumentError(TemplateRuntimeError): - """This error is raised if a filter was called with inappropriate - arguments - """ diff --git a/pipenv/vendor/jinja2/ext.py b/pipenv/vendor/jinja2/ext.py deleted file mode 100644 index 0734a84f..00000000 --- a/pipenv/vendor/jinja2/ext.py +++ /dev/null @@ -1,627 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.ext - ~~~~~~~~~~ - - Jinja extensions allow to add custom tags similar to the way django custom - tags work. By default two example extensions exist: an i18n and a cache - extension. - - :copyright: (c) 2017 by the Jinja Team. - :license: BSD. -""" -import re - -from jinja2 import nodes -from jinja2.defaults import BLOCK_START_STRING, \ - BLOCK_END_STRING, VARIABLE_START_STRING, VARIABLE_END_STRING, \ - COMMENT_START_STRING, COMMENT_END_STRING, LINE_STATEMENT_PREFIX, \ - LINE_COMMENT_PREFIX, TRIM_BLOCKS, NEWLINE_SEQUENCE, \ - KEEP_TRAILING_NEWLINE, LSTRIP_BLOCKS -from jinja2.environment import Environment -from jinja2.runtime import concat -from jinja2.exceptions import TemplateAssertionError, TemplateSyntaxError -from jinja2.utils import contextfunction, import_string, Markup -from jinja2._compat import with_metaclass, string_types, iteritems - - -# the only real useful gettext functions for a Jinja template. Note -# that ugettext must be assigned to gettext as Jinja doesn't support -# non unicode strings. -GETTEXT_FUNCTIONS = ('_', 'gettext', 'ngettext') - - -class ExtensionRegistry(type): - """Gives the extension an unique identifier.""" - - def __new__(cls, name, bases, d): - rv = type.__new__(cls, name, bases, d) - rv.identifier = rv.__module__ + '.' + rv.__name__ - return rv - - -class Extension(with_metaclass(ExtensionRegistry, object)): - """Extensions can be used to add extra functionality to the Jinja template - system at the parser level. Custom extensions are bound to an environment - but may not store environment specific data on `self`. The reason for - this is that an extension can be bound to another environment (for - overlays) by creating a copy and reassigning the `environment` attribute. - - As extensions are created by the environment they cannot accept any - arguments for configuration. One may want to work around that by using - a factory function, but that is not possible as extensions are identified - by their import name. The correct way to configure the extension is - storing the configuration values on the environment. Because this way the - environment ends up acting as central configuration storage the - attributes may clash which is why extensions have to ensure that the names - they choose for configuration are not too generic. ``prefix`` for example - is a terrible name, ``fragment_cache_prefix`` on the other hand is a good - name as includes the name of the extension (fragment cache). - """ - - #: if this extension parses this is the list of tags it's listening to. - tags = set() - - #: the priority of that extension. This is especially useful for - #: extensions that preprocess values. A lower value means higher - #: priority. - #: - #: .. versionadded:: 2.4 - priority = 100 - - def __init__(self, environment): - self.environment = environment - - def bind(self, environment): - """Create a copy of this extension bound to another environment.""" - rv = object.__new__(self.__class__) - rv.__dict__.update(self.__dict__) - rv.environment = environment - return rv - - def preprocess(self, source, name, filename=None): - """This method is called before the actual lexing and can be used to - preprocess the source. The `filename` is optional. The return value - must be the preprocessed source. - """ - return source - - def filter_stream(self, stream): - """It's passed a :class:`~jinja2.lexer.TokenStream` that can be used - to filter tokens returned. This method has to return an iterable of - :class:`~jinja2.lexer.Token`\\s, but it doesn't have to return a - :class:`~jinja2.lexer.TokenStream`. - - In the `ext` folder of the Jinja2 source distribution there is a file - called `inlinegettext.py` which implements a filter that utilizes this - method. - """ - return stream - - def parse(self, parser): - """If any of the :attr:`tags` matched this method is called with the - parser as first argument. The token the parser stream is pointing at - is the name token that matched. This method has to return one or a - list of multiple nodes. - """ - raise NotImplementedError() - - def attr(self, name, lineno=None): - """Return an attribute node for the current extension. This is useful - to pass constants on extensions to generated template code. - - :: - - self.attr('_my_attribute', lineno=lineno) - """ - return nodes.ExtensionAttribute(self.identifier, name, lineno=lineno) - - def call_method(self, name, args=None, kwargs=None, dyn_args=None, - dyn_kwargs=None, lineno=None): - """Call a method of the extension. This is a shortcut for - :meth:`attr` + :class:`jinja2.nodes.Call`. - """ - if args is None: - args = [] - if kwargs is None: - kwargs = [] - return nodes.Call(self.attr(name, lineno=lineno), args, kwargs, - dyn_args, dyn_kwargs, lineno=lineno) - - -@contextfunction -def _gettext_alias(__context, *args, **kwargs): - return __context.call(__context.resolve('gettext'), *args, **kwargs) - - -def _make_new_gettext(func): - @contextfunction - def gettext(__context, __string, **variables): - rv = __context.call(func, __string) - if __context.eval_ctx.autoescape: - rv = Markup(rv) - return rv % variables - return gettext - - -def _make_new_ngettext(func): - @contextfunction - def ngettext(__context, __singular, __plural, __num, **variables): - variables.setdefault('num', __num) - rv = __context.call(func, __singular, __plural, __num) - if __context.eval_ctx.autoescape: - rv = Markup(rv) - return rv % variables - return ngettext - - -class InternationalizationExtension(Extension): - """This extension adds gettext support to Jinja2.""" - tags = set(['trans']) - - # TODO: the i18n extension is currently reevaluating values in a few - # situations. Take this example: - # {% trans count=something() %}{{ count }} foo{% pluralize - # %}{{ count }} fooss{% endtrans %} - # something is called twice here. One time for the gettext value and - # the other time for the n-parameter of the ngettext function. - - def __init__(self, environment): - Extension.__init__(self, environment) - environment.globals['_'] = _gettext_alias - environment.extend( - install_gettext_translations=self._install, - install_null_translations=self._install_null, - install_gettext_callables=self._install_callables, - uninstall_gettext_translations=self._uninstall, - extract_translations=self._extract, - newstyle_gettext=False - ) - - def _install(self, translations, newstyle=None): - gettext = getattr(translations, 'ugettext', None) - if gettext is None: - gettext = translations.gettext - ngettext = getattr(translations, 'ungettext', None) - if ngettext is None: - ngettext = translations.ngettext - self._install_callables(gettext, ngettext, newstyle) - - def _install_null(self, newstyle=None): - self._install_callables( - lambda x: x, - lambda s, p, n: (n != 1 and (p,) or (s,))[0], - newstyle - ) - - def _install_callables(self, gettext, ngettext, newstyle=None): - if newstyle is not None: - self.environment.newstyle_gettext = newstyle - if self.environment.newstyle_gettext: - gettext = _make_new_gettext(gettext) - ngettext = _make_new_ngettext(ngettext) - self.environment.globals.update( - gettext=gettext, - ngettext=ngettext - ) - - def _uninstall(self, translations): - for key in 'gettext', 'ngettext': - self.environment.globals.pop(key, None) - - def _extract(self, source, gettext_functions=GETTEXT_FUNCTIONS): - if isinstance(source, string_types): - source = self.environment.parse(source) - return extract_from_ast(source, gettext_functions) - - def parse(self, parser): - """Parse a translatable tag.""" - lineno = next(parser.stream).lineno - num_called_num = False - - # find all the variables referenced. Additionally a variable can be - # defined in the body of the trans block too, but this is checked at - # a later state. - plural_expr = None - plural_expr_assignment = None - variables = {} - trimmed = None - while parser.stream.current.type != 'block_end': - if variables: - parser.stream.expect('comma') - - # skip colon for python compatibility - if parser.stream.skip_if('colon'): - break - - name = parser.stream.expect('name') - if name.value in variables: - parser.fail('translatable variable %r defined twice.' % - name.value, name.lineno, - exc=TemplateAssertionError) - - # expressions - if parser.stream.current.type == 'assign': - next(parser.stream) - variables[name.value] = var = parser.parse_expression() - elif trimmed is None and name.value in ('trimmed', 'notrimmed'): - trimmed = name.value == 'trimmed' - continue - else: - variables[name.value] = var = nodes.Name(name.value, 'load') - - if plural_expr is None: - if isinstance(var, nodes.Call): - plural_expr = nodes.Name('_trans', 'load') - variables[name.value] = plural_expr - plural_expr_assignment = nodes.Assign( - nodes.Name('_trans', 'store'), var) - else: - plural_expr = var - num_called_num = name.value == 'num' - - parser.stream.expect('block_end') - - plural = None - have_plural = False - referenced = set() - - # now parse until endtrans or pluralize - singular_names, singular = self._parse_block(parser, True) - if singular_names: - referenced.update(singular_names) - if plural_expr is None: - plural_expr = nodes.Name(singular_names[0], 'load') - num_called_num = singular_names[0] == 'num' - - # if we have a pluralize block, we parse that too - if parser.stream.current.test('name:pluralize'): - have_plural = True - next(parser.stream) - if parser.stream.current.type != 'block_end': - name = parser.stream.expect('name') - if name.value not in variables: - parser.fail('unknown variable %r for pluralization' % - name.value, name.lineno, - exc=TemplateAssertionError) - plural_expr = variables[name.value] - num_called_num = name.value == 'num' - parser.stream.expect('block_end') - plural_names, plural = self._parse_block(parser, False) - next(parser.stream) - referenced.update(plural_names) - else: - next(parser.stream) - - # register free names as simple name expressions - for var in referenced: - if var not in variables: - variables[var] = nodes.Name(var, 'load') - - if not have_plural: - plural_expr = None - elif plural_expr is None: - parser.fail('pluralize without variables', lineno) - - if trimmed is None: - trimmed = self.environment.policies['ext.i18n.trimmed'] - if trimmed: - singular = self._trim_whitespace(singular) - if plural: - plural = self._trim_whitespace(plural) - - node = self._make_node(singular, plural, variables, plural_expr, - bool(referenced), - num_called_num and have_plural) - node.set_lineno(lineno) - if plural_expr_assignment is not None: - return [plural_expr_assignment, node] - else: - return node - - def _trim_whitespace(self, string, _ws_re=re.compile(r'\s*\n\s*')): - return _ws_re.sub(' ', string.strip()) - - def _parse_block(self, parser, allow_pluralize): - """Parse until the next block tag with a given name.""" - referenced = [] - buf = [] - while 1: - if parser.stream.current.type == 'data': - buf.append(parser.stream.current.value.replace('%', '%%')) - next(parser.stream) - elif parser.stream.current.type == 'variable_begin': - next(parser.stream) - name = parser.stream.expect('name').value - referenced.append(name) - buf.append('%%(%s)s' % name) - parser.stream.expect('variable_end') - elif parser.stream.current.type == 'block_begin': - next(parser.stream) - if parser.stream.current.test('name:endtrans'): - break - elif parser.stream.current.test('name:pluralize'): - if allow_pluralize: - break - parser.fail('a translatable section can have only one ' - 'pluralize section') - parser.fail('control structures in translatable sections are ' - 'not allowed') - elif parser.stream.eos: - parser.fail('unclosed translation block') - else: - assert False, 'internal parser error' - - return referenced, concat(buf) - - def _make_node(self, singular, plural, variables, plural_expr, - vars_referenced, num_called_num): - """Generates a useful node from the data provided.""" - # no variables referenced? no need to escape for old style - # gettext invocations only if there are vars. - if not vars_referenced and not self.environment.newstyle_gettext: - singular = singular.replace('%%', '%') - if plural: - plural = plural.replace('%%', '%') - - # singular only: - if plural_expr is None: - gettext = nodes.Name('gettext', 'load') - node = nodes.Call(gettext, [nodes.Const(singular)], - [], None, None) - - # singular and plural - else: - ngettext = nodes.Name('ngettext', 'load') - node = nodes.Call(ngettext, [ - nodes.Const(singular), - nodes.Const(plural), - plural_expr - ], [], None, None) - - # in case newstyle gettext is used, the method is powerful - # enough to handle the variable expansion and autoescape - # handling itself - if self.environment.newstyle_gettext: - for key, value in iteritems(variables): - # the function adds that later anyways in case num was - # called num, so just skip it. - if num_called_num and key == 'num': - continue - node.kwargs.append(nodes.Keyword(key, value)) - - # otherwise do that here - else: - # mark the return value as safe if we are in an - # environment with autoescaping turned on - node = nodes.MarkSafeIfAutoescape(node) - if variables: - node = nodes.Mod(node, nodes.Dict([ - nodes.Pair(nodes.Const(key), value) - for key, value in variables.items() - ])) - return nodes.Output([node]) - - -class ExprStmtExtension(Extension): - """Adds a `do` tag to Jinja2 that works like the print statement just - that it doesn't print the return value. - """ - tags = set(['do']) - - def parse(self, parser): - node = nodes.ExprStmt(lineno=next(parser.stream).lineno) - node.node = parser.parse_tuple() - return node - - -class LoopControlExtension(Extension): - """Adds break and continue to the template engine.""" - tags = set(['break', 'continue']) - - def parse(self, parser): - token = next(parser.stream) - if token.value == 'break': - return nodes.Break(lineno=token.lineno) - return nodes.Continue(lineno=token.lineno) - - -class WithExtension(Extension): - pass - - -class AutoEscapeExtension(Extension): - pass - - -def extract_from_ast(node, gettext_functions=GETTEXT_FUNCTIONS, - babel_style=True): - """Extract localizable strings from the given template node. Per - default this function returns matches in babel style that means non string - parameters as well as keyword arguments are returned as `None`. This - allows Babel to figure out what you really meant if you are using - gettext functions that allow keyword arguments for placeholder expansion. - If you don't want that behavior set the `babel_style` parameter to `False` - which causes only strings to be returned and parameters are always stored - in tuples. As a consequence invalid gettext calls (calls without a single - string parameter or string parameters after non-string parameters) are - skipped. - - This example explains the behavior: - - >>> from jinja2 import Environment - >>> env = Environment() - >>> node = env.parse('{{ (_("foo"), _(), ngettext("foo", "bar", 42)) }}') - >>> list(extract_from_ast(node)) - [(1, '_', 'foo'), (1, '_', ()), (1, 'ngettext', ('foo', 'bar', None))] - >>> list(extract_from_ast(node, babel_style=False)) - [(1, '_', ('foo',)), (1, 'ngettext', ('foo', 'bar'))] - - For every string found this function yields a ``(lineno, function, - message)`` tuple, where: - - * ``lineno`` is the number of the line on which the string was found, - * ``function`` is the name of the ``gettext`` function used (if the - string was extracted from embedded Python code), and - * ``message`` is the string itself (a ``unicode`` object, or a tuple - of ``unicode`` objects for functions with multiple string arguments). - - This extraction function operates on the AST and is because of that unable - to extract any comments. For comment support you have to use the babel - extraction interface or extract comments yourself. - """ - for node in node.find_all(nodes.Call): - if not isinstance(node.node, nodes.Name) or \ - node.node.name not in gettext_functions: - continue - - strings = [] - for arg in node.args: - if isinstance(arg, nodes.Const) and \ - isinstance(arg.value, string_types): - strings.append(arg.value) - else: - strings.append(None) - - for arg in node.kwargs: - strings.append(None) - if node.dyn_args is not None: - strings.append(None) - if node.dyn_kwargs is not None: - strings.append(None) - - if not babel_style: - strings = tuple(x for x in strings if x is not None) - if not strings: - continue - else: - if len(strings) == 1: - strings = strings[0] - else: - strings = tuple(strings) - yield node.lineno, node.node.name, strings - - -class _CommentFinder(object): - """Helper class to find comments in a token stream. Can only - find comments for gettext calls forwards. Once the comment - from line 4 is found, a comment for line 1 will not return a - usable value. - """ - - def __init__(self, tokens, comment_tags): - self.tokens = tokens - self.comment_tags = comment_tags - self.offset = 0 - self.last_lineno = 0 - - def find_backwards(self, offset): - try: - for _, token_type, token_value in \ - reversed(self.tokens[self.offset:offset]): - if token_type in ('comment', 'linecomment'): - try: - prefix, comment = token_value.split(None, 1) - except ValueError: - continue - if prefix in self.comment_tags: - return [comment.rstrip()] - return [] - finally: - self.offset = offset - - def find_comments(self, lineno): - if not self.comment_tags or self.last_lineno > lineno: - return [] - for idx, (token_lineno, _, _) in enumerate(self.tokens[self.offset:]): - if token_lineno > lineno: - return self.find_backwards(self.offset + idx) - return self.find_backwards(len(self.tokens)) - - -def babel_extract(fileobj, keywords, comment_tags, options): - """Babel extraction method for Jinja templates. - - .. versionchanged:: 2.3 - Basic support for translation comments was added. If `comment_tags` - is now set to a list of keywords for extraction, the extractor will - try to find the best preceeding comment that begins with one of the - keywords. For best results, make sure to not have more than one - gettext call in one line of code and the matching comment in the - same line or the line before. - - .. versionchanged:: 2.5.1 - The `newstyle_gettext` flag can be set to `True` to enable newstyle - gettext calls. - - .. versionchanged:: 2.7 - A `silent` option can now be provided. If set to `False` template - syntax errors are propagated instead of being ignored. - - :param fileobj: the file-like object the messages should be extracted from - :param keywords: a list of keywords (i.e. function names) that should be - recognized as translation functions - :param comment_tags: a list of translator tags to search for and include - in the results. - :param options: a dictionary of additional options (optional) - :return: an iterator over ``(lineno, funcname, message, comments)`` tuples. - (comments will be empty currently) - """ - extensions = set() - for extension in options.get('extensions', '').split(','): - extension = extension.strip() - if not extension: - continue - extensions.add(import_string(extension)) - if InternationalizationExtension not in extensions: - extensions.add(InternationalizationExtension) - - def getbool(options, key, default=False): - return options.get(key, str(default)).lower() in \ - ('1', 'on', 'yes', 'true') - - silent = getbool(options, 'silent', True) - environment = Environment( - options.get('block_start_string', BLOCK_START_STRING), - options.get('block_end_string', BLOCK_END_STRING), - options.get('variable_start_string', VARIABLE_START_STRING), - options.get('variable_end_string', VARIABLE_END_STRING), - options.get('comment_start_string', COMMENT_START_STRING), - options.get('comment_end_string', COMMENT_END_STRING), - options.get('line_statement_prefix') or LINE_STATEMENT_PREFIX, - options.get('line_comment_prefix') or LINE_COMMENT_PREFIX, - getbool(options, 'trim_blocks', TRIM_BLOCKS), - getbool(options, 'lstrip_blocks', LSTRIP_BLOCKS), - NEWLINE_SEQUENCE, - getbool(options, 'keep_trailing_newline', KEEP_TRAILING_NEWLINE), - frozenset(extensions), - cache_size=0, - auto_reload=False - ) - - if getbool(options, 'trimmed'): - environment.policies['ext.i18n.trimmed'] = True - if getbool(options, 'newstyle_gettext'): - environment.newstyle_gettext = True - - source = fileobj.read().decode(options.get('encoding', 'utf-8')) - try: - node = environment.parse(source) - tokens = list(environment.lex(environment.preprocess(source))) - except TemplateSyntaxError as e: - if not silent: - raise - # skip templates with syntax errors - return - - finder = _CommentFinder(tokens, comment_tags) - for lineno, func, message in extract_from_ast(node, keywords): - yield lineno, func, message, finder.find_comments(lineno) - - -#: nicer import names -i18n = InternationalizationExtension -do = ExprStmtExtension -loopcontrols = LoopControlExtension -with_ = WithExtension -autoescape = AutoEscapeExtension diff --git a/pipenv/vendor/jinja2/filters.py b/pipenv/vendor/jinja2/filters.py deleted file mode 100644 index 267dddda..00000000 --- a/pipenv/vendor/jinja2/filters.py +++ /dev/null @@ -1,1190 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.filters - ~~~~~~~~~~~~~~ - - Bundled jinja filters. - - :copyright: (c) 2017 by the Jinja Team. - :license: BSD, see LICENSE for more details. -""" -import re -import math -import random -import warnings - -from itertools import groupby, chain -from collections import namedtuple -from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode, \ - unicode_urlencode, htmlsafe_json_dumps -from jinja2.runtime import Undefined -from jinja2.exceptions import FilterArgumentError -from jinja2._compat import imap, string_types, text_type, iteritems, PY2 - - -_word_re = re.compile(r'\w+', re.UNICODE) -_word_beginning_split_re = re.compile(r'([-\s\(\{\[\<]+)', re.UNICODE) - - -def contextfilter(f): - """Decorator for marking context dependent filters. The current - :class:`Context` will be passed as first argument. - """ - f.contextfilter = True - return f - - -def evalcontextfilter(f): - """Decorator for marking eval-context dependent filters. An eval - context object is passed as first argument. For more information - about the eval context, see :ref:`eval-context`. - - .. versionadded:: 2.4 - """ - f.evalcontextfilter = True - return f - - -def environmentfilter(f): - """Decorator for marking environment dependent filters. The current - :class:`Environment` is passed to the filter as first argument. - """ - f.environmentfilter = True - return f - - -def ignore_case(value): - """For use as a postprocessor for :func:`make_attrgetter`. Converts strings - to lowercase and returns other types as-is.""" - return value.lower() if isinstance(value, string_types) else value - - -def make_attrgetter(environment, attribute, postprocess=None): - """Returns a callable that looks up the given attribute from a - passed object with the rules of the environment. Dots are allowed - to access attributes of attributes. Integer parts in paths are - looked up as integers. - """ - if attribute is None: - attribute = [] - elif isinstance(attribute, string_types): - attribute = [int(x) if x.isdigit() else x for x in attribute.split('.')] - else: - attribute = [attribute] - - def attrgetter(item): - for part in attribute: - item = environment.getitem(item, part) - - if postprocess is not None: - item = postprocess(item) - - return item - - return attrgetter - - -def do_forceescape(value): - """Enforce HTML escaping. This will probably double escape variables.""" - if hasattr(value, '__html__'): - value = value.__html__() - return escape(text_type(value)) - - -def do_urlencode(value): - """Escape strings for use in URLs (uses UTF-8 encoding). It accepts both - dictionaries and regular strings as well as pairwise iterables. - - .. versionadded:: 2.7 - """ - itemiter = None - if isinstance(value, dict): - itemiter = iteritems(value) - elif not isinstance(value, string_types): - try: - itemiter = iter(value) - except TypeError: - pass - if itemiter is None: - return unicode_urlencode(value) - return u'&'.join(unicode_urlencode(k) + '=' + - unicode_urlencode(v, for_qs=True) - for k, v in itemiter) - - -@evalcontextfilter -def do_replace(eval_ctx, s, old, new, count=None): - """Return a copy of the value with all occurrences of a substring - replaced with a new one. The first argument is the substring - that should be replaced, the second is the replacement string. - If the optional third argument ``count`` is given, only the first - ``count`` occurrences are replaced: - - .. sourcecode:: jinja - - {{ "Hello World"|replace("Hello", "Goodbye") }} - -> Goodbye World - - {{ "aaaaargh"|replace("a", "d'oh, ", 2) }} - -> d'oh, d'oh, aaargh - """ - if count is None: - count = -1 - if not eval_ctx.autoescape: - return text_type(s).replace(text_type(old), text_type(new), count) - if hasattr(old, '__html__') or hasattr(new, '__html__') and \ - not hasattr(s, '__html__'): - s = escape(s) - else: - s = soft_unicode(s) - return s.replace(soft_unicode(old), soft_unicode(new), count) - - -def do_upper(s): - """Convert a value to uppercase.""" - return soft_unicode(s).upper() - - -def do_lower(s): - """Convert a value to lowercase.""" - return soft_unicode(s).lower() - - -@evalcontextfilter -def do_xmlattr(_eval_ctx, d, autospace=True): - """Create an SGML/XML attribute string based on the items in a dict. - All values that are neither `none` nor `undefined` are automatically - escaped: - - .. sourcecode:: html+jinja - - <ul{{ {'class': 'my_list', 'missing': none, - 'id': 'list-%d'|format(variable)}|xmlattr }}> - ... - </ul> - - Results in something like this: - - .. sourcecode:: html - - <ul class="my_list" id="list-42"> - ... - </ul> - - As you can see it automatically prepends a space in front of the item - if the filter returned something unless the second parameter is false. - """ - rv = u' '.join( - u'%s="%s"' % (escape(key), escape(value)) - for key, value in iteritems(d) - if value is not None and not isinstance(value, Undefined) - ) - if autospace and rv: - rv = u' ' + rv - if _eval_ctx.autoescape: - rv = Markup(rv) - return rv - - -def do_capitalize(s): - """Capitalize a value. The first character will be uppercase, all others - lowercase. - """ - return soft_unicode(s).capitalize() - - -def do_title(s): - """Return a titlecased version of the value. I.e. words will start with - uppercase letters, all remaining characters are lowercase. - """ - return ''.join( - [item[0].upper() + item[1:].lower() - for item in _word_beginning_split_re.split(soft_unicode(s)) - if item]) - - -def do_dictsort(value, case_sensitive=False, by='key', reverse=False): - """Sort a dict and yield (key, value) pairs. Because python dicts are - unsorted you may want to use this function to order them by either - key or value: - - .. sourcecode:: jinja - - {% for item in mydict|dictsort %} - sort the dict by key, case insensitive - - {% for item in mydict|dictsort(reverse=true) %} - sort the dict by key, case insensitive, reverse order - - {% for item in mydict|dictsort(true) %} - sort the dict by key, case sensitive - - {% for item in mydict|dictsort(false, 'value') %} - sort the dict by value, case insensitive - """ - if by == 'key': - pos = 0 - elif by == 'value': - pos = 1 - else: - raise FilterArgumentError( - 'You can only sort by either "key" or "value"' - ) - - def sort_func(item): - value = item[pos] - - if not case_sensitive: - value = ignore_case(value) - - return value - - return sorted(value.items(), key=sort_func, reverse=reverse) - - -@environmentfilter -def do_sort( - environment, value, reverse=False, case_sensitive=False, attribute=None -): - """Sort an iterable. Per default it sorts ascending, if you pass it - true as first argument it will reverse the sorting. - - If the iterable is made of strings the third parameter can be used to - control the case sensitiveness of the comparison which is disabled by - default. - - .. sourcecode:: jinja - - {% for item in iterable|sort %} - ... - {% endfor %} - - It is also possible to sort by an attribute (for example to sort - by the date of an object) by specifying the `attribute` parameter: - - .. sourcecode:: jinja - - {% for item in iterable|sort(attribute='date') %} - ... - {% endfor %} - - .. versionchanged:: 2.6 - The `attribute` parameter was added. - """ - key_func = make_attrgetter( - environment, attribute, - postprocess=ignore_case if not case_sensitive else None - ) - return sorted(value, key=key_func, reverse=reverse) - - -@environmentfilter -def do_unique(environment, value, case_sensitive=False, attribute=None): - """Returns a list of unique items from the the given iterable. - - .. sourcecode:: jinja - - {{ ['foo', 'bar', 'foobar', 'FooBar']|unique }} - -> ['foo', 'bar', 'foobar'] - - The unique items are yielded in the same order as their first occurrence in - the iterable passed to the filter. - - :param case_sensitive: Treat upper and lower case strings as distinct. - :param attribute: Filter objects with unique values for this attribute. - """ - getter = make_attrgetter( - environment, attribute, - postprocess=ignore_case if not case_sensitive else None - ) - seen = set() - - for item in value: - key = getter(item) - - if key not in seen: - seen.add(key) - yield item - - -def _min_or_max(environment, value, func, case_sensitive, attribute): - it = iter(value) - - try: - first = next(it) - except StopIteration: - return environment.undefined('No aggregated item, sequence was empty.') - - key_func = make_attrgetter( - environment, attribute, - ignore_case if not case_sensitive else None - ) - return func(chain([first], it), key=key_func) - - -@environmentfilter -def do_min(environment, value, case_sensitive=False, attribute=None): - """Return the smallest item from the sequence. - - .. sourcecode:: jinja - - {{ [1, 2, 3]|min }} - -> 1 - - :param case_sensitive: Treat upper and lower case strings as distinct. - :param attribute: Get the object with the max value of this attribute. - """ - return _min_or_max(environment, value, min, case_sensitive, attribute) - - -@environmentfilter -def do_max(environment, value, case_sensitive=False, attribute=None): - """Return the largest item from the sequence. - - .. sourcecode:: jinja - - {{ [1, 2, 3]|max }} - -> 3 - - :param case_sensitive: Treat upper and lower case strings as distinct. - :param attribute: Get the object with the max value of this attribute. - """ - return _min_or_max(environment, value, max, case_sensitive, attribute) - - -def do_default(value, default_value=u'', boolean=False): - """If the value is undefined it will return the passed default value, - otherwise the value of the variable: - - .. sourcecode:: jinja - - {{ my_variable|default('my_variable is not defined') }} - - This will output the value of ``my_variable`` if the variable was - defined, otherwise ``'my_variable is not defined'``. If you want - to use default with variables that evaluate to false you have to - set the second parameter to `true`: - - .. sourcecode:: jinja - - {{ ''|default('the string was empty', true) }} - """ - if isinstance(value, Undefined) or (boolean and not value): - return default_value - return value - - -@evalcontextfilter -def do_join(eval_ctx, value, d=u'', attribute=None): - """Return a string which is the concatenation of the strings in the - sequence. The separator between elements is an empty string per - default, you can define it with the optional parameter: - - .. sourcecode:: jinja - - {{ [1, 2, 3]|join('|') }} - -> 1|2|3 - - {{ [1, 2, 3]|join }} - -> 123 - - It is also possible to join certain attributes of an object: - - .. sourcecode:: jinja - - {{ users|join(', ', attribute='username') }} - - .. versionadded:: 2.6 - The `attribute` parameter was added. - """ - if attribute is not None: - value = imap(make_attrgetter(eval_ctx.environment, attribute), value) - - # no automatic escaping? joining is a lot eaiser then - if not eval_ctx.autoescape: - return text_type(d).join(imap(text_type, value)) - - # if the delimiter doesn't have an html representation we check - # if any of the items has. If yes we do a coercion to Markup - if not hasattr(d, '__html__'): - value = list(value) - do_escape = False - for idx, item in enumerate(value): - if hasattr(item, '__html__'): - do_escape = True - else: - value[idx] = text_type(item) - if do_escape: - d = escape(d) - else: - d = text_type(d) - return d.join(value) - - # no html involved, to normal joining - return soft_unicode(d).join(imap(soft_unicode, value)) - - -def do_center(value, width=80): - """Centers the value in a field of a given width.""" - return text_type(value).center(width) - - -@environmentfilter -def do_first(environment, seq): - """Return the first item of a sequence.""" - try: - return next(iter(seq)) - except StopIteration: - return environment.undefined('No first item, sequence was empty.') - - -@environmentfilter -def do_last(environment, seq): - """Return the last item of a sequence.""" - try: - return next(iter(reversed(seq))) - except StopIteration: - return environment.undefined('No last item, sequence was empty.') - - -@contextfilter -def do_random(context, seq): - """Return a random item from the sequence.""" - try: - return random.choice(seq) - except IndexError: - return context.environment.undefined('No random item, sequence was empty.') - - -def do_filesizeformat(value, binary=False): - """Format the value like a 'human-readable' file size (i.e. 13 kB, - 4.1 MB, 102 Bytes, etc). Per default decimal prefixes are used (Mega, - Giga, etc.), if the second parameter is set to `True` the binary - prefixes are used (Mebi, Gibi). - """ - bytes = float(value) - base = binary and 1024 or 1000 - prefixes = [ - (binary and 'KiB' or 'kB'), - (binary and 'MiB' or 'MB'), - (binary and 'GiB' or 'GB'), - (binary and 'TiB' or 'TB'), - (binary and 'PiB' or 'PB'), - (binary and 'EiB' or 'EB'), - (binary and 'ZiB' or 'ZB'), - (binary and 'YiB' or 'YB') - ] - if bytes == 1: - return '1 Byte' - elif bytes < base: - return '%d Bytes' % bytes - else: - for i, prefix in enumerate(prefixes): - unit = base ** (i + 2) - if bytes < unit: - return '%.1f %s' % ((base * bytes / unit), prefix) - return '%.1f %s' % ((base * bytes / unit), prefix) - - -def do_pprint(value, verbose=False): - """Pretty print a variable. Useful for debugging. - - With Jinja 1.2 onwards you can pass it a parameter. If this parameter - is truthy the output will be more verbose (this requires `pretty`) - """ - return pformat(value, verbose=verbose) - - -@evalcontextfilter -def do_urlize(eval_ctx, value, trim_url_limit=None, nofollow=False, - target=None, rel=None): - """Converts URLs in plain text into clickable links. - - If you pass the filter an additional integer it will shorten the urls - to that number. Also a third argument exists that makes the urls - "nofollow": - - .. sourcecode:: jinja - - {{ mytext|urlize(40, true) }} - links are shortened to 40 chars and defined with rel="nofollow" - - If *target* is specified, the ``target`` attribute will be added to the - ``<a>`` tag: - - .. sourcecode:: jinja - - {{ mytext|urlize(40, target='_blank') }} - - .. versionchanged:: 2.8+ - The *target* parameter was added. - """ - policies = eval_ctx.environment.policies - rel = set((rel or '').split() or []) - if nofollow: - rel.add('nofollow') - rel.update((policies['urlize.rel'] or '').split()) - if target is None: - target = policies['urlize.target'] - rel = ' '.join(sorted(rel)) or None - rv = urlize(value, trim_url_limit, rel=rel, target=target) - if eval_ctx.autoescape: - rv = Markup(rv) - return rv - - -def do_indent( - s, width=4, first=False, blank=False, indentfirst=None -): - """Return a copy of the string with each line indented by 4 spaces. The - first line and blank lines are not indented by default. - - :param width: Number of spaces to indent by. - :param first: Don't skip indenting the first line. - :param blank: Don't skip indenting empty lines. - - .. versionchanged:: 2.10 - Blank lines are not indented by default. - - Rename the ``indentfirst`` argument to ``first``. - """ - if indentfirst is not None: - warnings.warn(DeprecationWarning( - 'The "indentfirst" argument is renamed to "first".' - ), stacklevel=2) - first = indentfirst - - s += u'\n' # this quirk is necessary for splitlines method - indention = u' ' * width - - if blank: - rv = (u'\n' + indention).join(s.splitlines()) - else: - lines = s.splitlines() - rv = lines.pop(0) - - if lines: - rv += u'\n' + u'\n'.join( - indention + line if line else line for line in lines - ) - - if first: - rv = indention + rv - - return rv - - -@environmentfilter -def do_truncate(env, s, length=255, killwords=False, end='...', leeway=None): - """Return a truncated copy of the string. The length is specified - with the first parameter which defaults to ``255``. If the second - parameter is ``true`` the filter will cut the text at length. Otherwise - it will discard the last word. If the text was in fact - truncated it will append an ellipsis sign (``"..."``). If you want a - different ellipsis sign than ``"..."`` you can specify it using the - third parameter. Strings that only exceed the length by the tolerance - margin given in the fourth parameter will not be truncated. - - .. sourcecode:: jinja - - {{ "foo bar baz qux"|truncate(9) }} - -> "foo..." - {{ "foo bar baz qux"|truncate(9, True) }} - -> "foo ba..." - {{ "foo bar baz qux"|truncate(11) }} - -> "foo bar baz qux" - {{ "foo bar baz qux"|truncate(11, False, '...', 0) }} - -> "foo bar..." - - The default leeway on newer Jinja2 versions is 5 and was 0 before but - can be reconfigured globally. - """ - if leeway is None: - leeway = env.policies['truncate.leeway'] - assert length >= len(end), 'expected length >= %s, got %s' % (len(end), length) - assert leeway >= 0, 'expected leeway >= 0, got %s' % leeway - if len(s) <= length + leeway: - return s - if killwords: - return s[:length - len(end)] + end - result = s[:length - len(end)].rsplit(' ', 1)[0] - return result + end - - -@environmentfilter -def do_wordwrap(environment, s, width=79, break_long_words=True, - wrapstring=None): - """ - Return a copy of the string passed to the filter wrapped after - ``79`` characters. You can override this default using the first - parameter. If you set the second parameter to `false` Jinja will not - split words apart if they are longer than `width`. By default, the newlines - will be the default newlines for the environment, but this can be changed - using the wrapstring keyword argument. - - .. versionadded:: 2.7 - Added support for the `wrapstring` parameter. - """ - if not wrapstring: - wrapstring = environment.newline_sequence - import textwrap - return wrapstring.join(textwrap.wrap(s, width=width, expand_tabs=False, - replace_whitespace=False, - break_long_words=break_long_words)) - - -def do_wordcount(s): - """Count the words in that string.""" - return len(_word_re.findall(s)) - - -def do_int(value, default=0, base=10): - """Convert the value into an integer. If the - conversion doesn't work it will return ``0``. You can - override this default using the first parameter. You - can also override the default base (10) in the second - parameter, which handles input with prefixes such as - 0b, 0o and 0x for bases 2, 8 and 16 respectively. - The base is ignored for decimal numbers and non-string values. - """ - try: - if isinstance(value, string_types): - return int(value, base) - return int(value) - except (TypeError, ValueError): - # this quirk is necessary so that "42.23"|int gives 42. - try: - return int(float(value)) - except (TypeError, ValueError): - return default - - -def do_float(value, default=0.0): - """Convert the value into a floating point number. If the - conversion doesn't work it will return ``0.0``. You can - override this default using the first parameter. - """ - try: - return float(value) - except (TypeError, ValueError): - return default - - -def do_format(value, *args, **kwargs): - """ - Apply python string formatting on an object: - - .. sourcecode:: jinja - - {{ "%s - %s"|format("Hello?", "Foo!") }} - -> Hello? - Foo! - """ - if args and kwargs: - raise FilterArgumentError('can\'t handle positional and keyword ' - 'arguments at the same time') - return soft_unicode(value) % (kwargs or args) - - -def do_trim(value): - """Strip leading and trailing whitespace.""" - return soft_unicode(value).strip() - - -def do_striptags(value): - """Strip SGML/XML tags and replace adjacent whitespace by one space. - """ - if hasattr(value, '__html__'): - value = value.__html__() - return Markup(text_type(value)).striptags() - - -def do_slice(value, slices, fill_with=None): - """Slice an iterator and return a list of lists containing - those items. Useful if you want to create a div containing - three ul tags that represent columns: - - .. sourcecode:: html+jinja - - <div class="columwrapper"> - {%- for column in items|slice(3) %} - <ul class="column-{{ loop.index }}"> - {%- for item in column %} - <li>{{ item }}</li> - {%- endfor %} - </ul> - {%- endfor %} - </div> - - If you pass it a second argument it's used to fill missing - values on the last iteration. - """ - seq = list(value) - length = len(seq) - items_per_slice = length // slices - slices_with_extra = length % slices - offset = 0 - for slice_number in range(slices): - start = offset + slice_number * items_per_slice - if slice_number < slices_with_extra: - offset += 1 - end = offset + (slice_number + 1) * items_per_slice - tmp = seq[start:end] - if fill_with is not None and slice_number >= slices_with_extra: - tmp.append(fill_with) - yield tmp - - -def do_batch(value, linecount, fill_with=None): - """ - A filter that batches items. It works pretty much like `slice` - just the other way round. It returns a list of lists with the - given number of items. If you provide a second parameter this - is used to fill up missing items. See this example: - - .. sourcecode:: html+jinja - - <table> - {%- for row in items|batch(3, ' ') %} - <tr> - {%- for column in row %} - <td>{{ column }}</td> - {%- endfor %} - </tr> - {%- endfor %} - </table> - """ - tmp = [] - for item in value: - if len(tmp) == linecount: - yield tmp - tmp = [] - tmp.append(item) - if tmp: - if fill_with is not None and len(tmp) < linecount: - tmp += [fill_with] * (linecount - len(tmp)) - yield tmp - - -def do_round(value, precision=0, method='common'): - """Round the number to a given precision. The first - parameter specifies the precision (default is ``0``), the - second the rounding method: - - - ``'common'`` rounds either up or down - - ``'ceil'`` always rounds up - - ``'floor'`` always rounds down - - If you don't specify a method ``'common'`` is used. - - .. sourcecode:: jinja - - {{ 42.55|round }} - -> 43.0 - {{ 42.55|round(1, 'floor') }} - -> 42.5 - - Note that even if rounded to 0 precision, a float is returned. If - you need a real integer, pipe it through `int`: - - .. sourcecode:: jinja - - {{ 42.55|round|int }} - -> 43 - """ - if not method in ('common', 'ceil', 'floor'): - raise FilterArgumentError('method must be common, ceil or floor') - if method == 'common': - return round(value, precision) - func = getattr(math, method) - return func(value * (10 ** precision)) / (10 ** precision) - - -# Use a regular tuple repr here. This is what we did in the past and we -# really want to hide this custom type as much as possible. In particular -# we do not want to accidentally expose an auto generated repr in case -# people start to print this out in comments or something similar for -# debugging. -_GroupTuple = namedtuple('_GroupTuple', ['grouper', 'list']) -_GroupTuple.__repr__ = tuple.__repr__ -_GroupTuple.__str__ = tuple.__str__ - -@environmentfilter -def do_groupby(environment, value, attribute): - """Group a sequence of objects by a common attribute. - - If you for example have a list of dicts or objects that represent persons - with `gender`, `first_name` and `last_name` attributes and you want to - group all users by genders you can do something like the following - snippet: - - .. sourcecode:: html+jinja - - <ul> - {% for group in persons|groupby('gender') %} - <li>{{ group.grouper }}<ul> - {% for person in group.list %} - <li>{{ person.first_name }} {{ person.last_name }}</li> - {% endfor %}</ul></li> - {% endfor %} - </ul> - - Additionally it's possible to use tuple unpacking for the grouper and - list: - - .. sourcecode:: html+jinja - - <ul> - {% for grouper, list in persons|groupby('gender') %} - ... - {% endfor %} - </ul> - - As you can see the item we're grouping by is stored in the `grouper` - attribute and the `list` contains all the objects that have this grouper - in common. - - .. versionchanged:: 2.6 - It's now possible to use dotted notation to group by the child - attribute of another attribute. - """ - expr = make_attrgetter(environment, attribute) - return [_GroupTuple(key, list(values)) for key, values - in groupby(sorted(value, key=expr), expr)] - - -@environmentfilter -def do_sum(environment, iterable, attribute=None, start=0): - """Returns the sum of a sequence of numbers plus the value of parameter - 'start' (which defaults to 0). When the sequence is empty it returns - start. - - It is also possible to sum up only certain attributes: - - .. sourcecode:: jinja - - Total: {{ items|sum(attribute='price') }} - - .. versionchanged:: 2.6 - The `attribute` parameter was added to allow suming up over - attributes. Also the `start` parameter was moved on to the right. - """ - if attribute is not None: - iterable = imap(make_attrgetter(environment, attribute), iterable) - return sum(iterable, start) - - -def do_list(value): - """Convert the value into a list. If it was a string the returned list - will be a list of characters. - """ - return list(value) - - -def do_mark_safe(value): - """Mark the value as safe which means that in an environment with automatic - escaping enabled this variable will not be escaped. - """ - return Markup(value) - - -def do_mark_unsafe(value): - """Mark a value as unsafe. This is the reverse operation for :func:`safe`.""" - return text_type(value) - - -def do_reverse(value): - """Reverse the object or return an iterator that iterates over it the other - way round. - """ - if isinstance(value, string_types): - return value[::-1] - try: - return reversed(value) - except TypeError: - try: - rv = list(value) - rv.reverse() - return rv - except TypeError: - raise FilterArgumentError('argument must be iterable') - - -@environmentfilter -def do_attr(environment, obj, name): - """Get an attribute of an object. ``foo|attr("bar")`` works like - ``foo.bar`` just that always an attribute is returned and items are not - looked up. - - See :ref:`Notes on subscriptions <notes-on-subscriptions>` for more details. - """ - try: - name = str(name) - except UnicodeError: - pass - else: - try: - value = getattr(obj, name) - except AttributeError: - pass - else: - if environment.sandboxed and not \ - environment.is_safe_attribute(obj, name, value): - return environment.unsafe_undefined(obj, name) - return value - return environment.undefined(obj=obj, name=name) - - -@contextfilter -def do_map(*args, **kwargs): - """Applies a filter on a sequence of objects or looks up an attribute. - This is useful when dealing with lists of objects but you are really - only interested in a certain value of it. - - The basic usage is mapping on an attribute. Imagine you have a list - of users but you are only interested in a list of usernames: - - .. sourcecode:: jinja - - Users on this page: {{ users|map(attribute='username')|join(', ') }} - - Alternatively you can let it invoke a filter by passing the name of the - filter and the arguments afterwards. A good example would be applying a - text conversion filter on a sequence: - - .. sourcecode:: jinja - - Users on this page: {{ titles|map('lower')|join(', ') }} - - .. versionadded:: 2.7 - """ - seq, func = prepare_map(args, kwargs) - if seq: - for item in seq: - yield func(item) - - -@contextfilter -def do_select(*args, **kwargs): - """Filters a sequence of objects by applying a test to each object, - and only selecting the objects with the test succeeding. - - If no test is specified, each object will be evaluated as a boolean. - - Example usage: - - .. sourcecode:: jinja - - {{ numbers|select("odd") }} - {{ numbers|select("odd") }} - {{ numbers|select("divisibleby", 3) }} - {{ numbers|select("lessthan", 42) }} - {{ strings|select("equalto", "mystring") }} - - .. versionadded:: 2.7 - """ - return select_or_reject(args, kwargs, lambda x: x, False) - - -@contextfilter -def do_reject(*args, **kwargs): - """Filters a sequence of objects by applying a test to each object, - and rejecting the objects with the test succeeding. - - If no test is specified, each object will be evaluated as a boolean. - - Example usage: - - .. sourcecode:: jinja - - {{ numbers|reject("odd") }} - - .. versionadded:: 2.7 - """ - return select_or_reject(args, kwargs, lambda x: not x, False) - - -@contextfilter -def do_selectattr(*args, **kwargs): - """Filters a sequence of objects by applying a test to the specified - attribute of each object, and only selecting the objects with the - test succeeding. - - If no test is specified, the attribute's value will be evaluated as - a boolean. - - Example usage: - - .. sourcecode:: jinja - - {{ users|selectattr("is_active") }} - {{ users|selectattr("email", "none") }} - - .. versionadded:: 2.7 - """ - return select_or_reject(args, kwargs, lambda x: x, True) - - -@contextfilter -def do_rejectattr(*args, **kwargs): - """Filters a sequence of objects by applying a test to the specified - attribute of each object, and rejecting the objects with the test - succeeding. - - If no test is specified, the attribute's value will be evaluated as - a boolean. - - .. sourcecode:: jinja - - {{ users|rejectattr("is_active") }} - {{ users|rejectattr("email", "none") }} - - .. versionadded:: 2.7 - """ - return select_or_reject(args, kwargs, lambda x: not x, True) - - -@evalcontextfilter -def do_tojson(eval_ctx, value, indent=None): - """Dumps a structure to JSON so that it's safe to use in ``<script>`` - tags. It accepts the same arguments and returns a JSON string. Note that - this is available in templates through the ``|tojson`` filter which will - also mark the result as safe. Due to how this function escapes certain - characters this is safe even if used outside of ``<script>`` tags. - - The following characters are escaped in strings: - - - ``<`` - - ``>`` - - ``&`` - - ``'`` - - This makes it safe to embed such strings in any place in HTML with the - notable exception of double quoted attributes. In that case single - quote your attributes or HTML escape it in addition. - - The indent parameter can be used to enable pretty printing. Set it to - the number of spaces that the structures should be indented with. - - Note that this filter is for use in HTML contexts only. - - .. versionadded:: 2.9 - """ - policies = eval_ctx.environment.policies - dumper = policies['json.dumps_function'] - options = policies['json.dumps_kwargs'] - if indent is not None: - options = dict(options) - options['indent'] = indent - return htmlsafe_json_dumps(value, dumper=dumper, **options) - - -def prepare_map(args, kwargs): - context = args[0] - seq = args[1] - - if len(args) == 2 and 'attribute' in kwargs: - attribute = kwargs.pop('attribute') - if kwargs: - raise FilterArgumentError('Unexpected keyword argument %r' % - next(iter(kwargs))) - func = make_attrgetter(context.environment, attribute) - else: - try: - name = args[2] - args = args[3:] - except LookupError: - raise FilterArgumentError('map requires a filter argument') - func = lambda item: context.environment.call_filter( - name, item, args, kwargs, context=context) - - return seq, func - - -def prepare_select_or_reject(args, kwargs, modfunc, lookup_attr): - context = args[0] - seq = args[1] - if lookup_attr: - try: - attr = args[2] - except LookupError: - raise FilterArgumentError('Missing parameter for attribute name') - transfunc = make_attrgetter(context.environment, attr) - off = 1 - else: - off = 0 - transfunc = lambda x: x - - try: - name = args[2 + off] - args = args[3 + off:] - func = lambda item: context.environment.call_test( - name, item, args, kwargs) - except LookupError: - func = bool - - return seq, lambda item: modfunc(func(transfunc(item))) - - -def select_or_reject(args, kwargs, modfunc, lookup_attr): - seq, func = prepare_select_or_reject(args, kwargs, modfunc, lookup_attr) - if seq: - for item in seq: - if func(item): - yield item - - -FILTERS = { - 'abs': abs, - 'attr': do_attr, - 'batch': do_batch, - 'capitalize': do_capitalize, - 'center': do_center, - 'count': len, - 'd': do_default, - 'default': do_default, - 'dictsort': do_dictsort, - 'e': escape, - 'escape': escape, - 'filesizeformat': do_filesizeformat, - 'first': do_first, - 'float': do_float, - 'forceescape': do_forceescape, - 'format': do_format, - 'groupby': do_groupby, - 'indent': do_indent, - 'int': do_int, - 'join': do_join, - 'last': do_last, - 'length': len, - 'list': do_list, - 'lower': do_lower, - 'map': do_map, - 'min': do_min, - 'max': do_max, - 'pprint': do_pprint, - 'random': do_random, - 'reject': do_reject, - 'rejectattr': do_rejectattr, - 'replace': do_replace, - 'reverse': do_reverse, - 'round': do_round, - 'safe': do_mark_safe, - 'select': do_select, - 'selectattr': do_selectattr, - 'slice': do_slice, - 'sort': do_sort, - 'string': soft_unicode, - 'striptags': do_striptags, - 'sum': do_sum, - 'title': do_title, - 'trim': do_trim, - 'truncate': do_truncate, - 'unique': do_unique, - 'upper': do_upper, - 'urlencode': do_urlencode, - 'urlize': do_urlize, - 'wordcount': do_wordcount, - 'wordwrap': do_wordwrap, - 'xmlattr': do_xmlattr, - 'tojson': do_tojson, -} diff --git a/pipenv/vendor/jinja2/idtracking.py b/pipenv/vendor/jinja2/idtracking.py deleted file mode 100644 index 491bfe08..00000000 --- a/pipenv/vendor/jinja2/idtracking.py +++ /dev/null @@ -1,286 +0,0 @@ -from jinja2.visitor import NodeVisitor -from jinja2._compat import iteritems - - -VAR_LOAD_PARAMETER = 'param' -VAR_LOAD_RESOLVE = 'resolve' -VAR_LOAD_ALIAS = 'alias' -VAR_LOAD_UNDEFINED = 'undefined' - - -def find_symbols(nodes, parent_symbols=None): - sym = Symbols(parent=parent_symbols) - visitor = FrameSymbolVisitor(sym) - for node in nodes: - visitor.visit(node) - return sym - - -def symbols_for_node(node, parent_symbols=None): - sym = Symbols(parent=parent_symbols) - sym.analyze_node(node) - return sym - - -class Symbols(object): - - def __init__(self, parent=None, level=None): - if level is None: - if parent is None: - level = 0 - else: - level = parent.level + 1 - self.level = level - self.parent = parent - self.refs = {} - self.loads = {} - self.stores = set() - - def analyze_node(self, node, **kwargs): - visitor = RootVisitor(self) - visitor.visit(node, **kwargs) - - def _define_ref(self, name, load=None): - ident = 'l_%d_%s' % (self.level, name) - self.refs[name] = ident - if load is not None: - self.loads[ident] = load - return ident - - def find_load(self, target): - if target in self.loads: - return self.loads[target] - if self.parent is not None: - return self.parent.find_load(target) - - def find_ref(self, name): - if name in self.refs: - return self.refs[name] - if self.parent is not None: - return self.parent.find_ref(name) - - def ref(self, name): - rv = self.find_ref(name) - if rv is None: - raise AssertionError('Tried to resolve a name to a reference that ' - 'was unknown to the frame (%r)' % name) - return rv - - def copy(self): - rv = object.__new__(self.__class__) - rv.__dict__.update(self.__dict__) - rv.refs = self.refs.copy() - rv.loads = self.loads.copy() - rv.stores = self.stores.copy() - return rv - - def store(self, name): - self.stores.add(name) - - # If we have not see the name referenced yet, we need to figure - # out what to set it to. - if name not in self.refs: - # If there is a parent scope we check if the name has a - # reference there. If it does it means we might have to alias - # to a variable there. - if self.parent is not None: - outer_ref = self.parent.find_ref(name) - if outer_ref is not None: - self._define_ref(name, load=(VAR_LOAD_ALIAS, outer_ref)) - return - - # Otherwise we can just set it to undefined. - self._define_ref(name, load=(VAR_LOAD_UNDEFINED, None)) - - def declare_parameter(self, name): - self.stores.add(name) - return self._define_ref(name, load=(VAR_LOAD_PARAMETER, None)) - - def load(self, name): - target = self.find_ref(name) - if target is None: - self._define_ref(name, load=(VAR_LOAD_RESOLVE, name)) - - def branch_update(self, branch_symbols): - stores = {} - for branch in branch_symbols: - for target in branch.stores: - if target in self.stores: - continue - stores[target] = stores.get(target, 0) + 1 - - for sym in branch_symbols: - self.refs.update(sym.refs) - self.loads.update(sym.loads) - self.stores.update(sym.stores) - - for name, branch_count in iteritems(stores): - if branch_count == len(branch_symbols): - continue - target = self.find_ref(name) - assert target is not None, 'should not happen' - - if self.parent is not None: - outer_target = self.parent.find_ref(name) - if outer_target is not None: - self.loads[target] = (VAR_LOAD_ALIAS, outer_target) - continue - self.loads[target] = (VAR_LOAD_RESOLVE, name) - - def dump_stores(self): - rv = {} - node = self - while node is not None: - for name in node.stores: - if name not in rv: - rv[name] = self.find_ref(name) - node = node.parent - return rv - - def dump_param_targets(self): - rv = set() - node = self - while node is not None: - for target, (instr, _) in iteritems(self.loads): - if instr == VAR_LOAD_PARAMETER: - rv.add(target) - node = node.parent - return rv - - -class RootVisitor(NodeVisitor): - - def __init__(self, symbols): - self.sym_visitor = FrameSymbolVisitor(symbols) - - def _simple_visit(self, node, **kwargs): - for child in node.iter_child_nodes(): - self.sym_visitor.visit(child) - - visit_Template = visit_Block = visit_Macro = visit_FilterBlock = \ - visit_Scope = visit_If = visit_ScopedEvalContextModifier = \ - _simple_visit - - def visit_AssignBlock(self, node, **kwargs): - for child in node.body: - self.sym_visitor.visit(child) - - def visit_CallBlock(self, node, **kwargs): - for child in node.iter_child_nodes(exclude=('call',)): - self.sym_visitor.visit(child) - - def visit_OverlayScope(self, node, **kwargs): - for child in node.body: - self.sym_visitor.visit(child) - - def visit_For(self, node, for_branch='body', **kwargs): - if for_branch == 'body': - self.sym_visitor.visit(node.target, store_as_param=True) - branch = node.body - elif for_branch == 'else': - branch = node.else_ - elif for_branch == 'test': - self.sym_visitor.visit(node.target, store_as_param=True) - if node.test is not None: - self.sym_visitor.visit(node.test) - return - else: - raise RuntimeError('Unknown for branch') - for item in branch or (): - self.sym_visitor.visit(item) - - def visit_With(self, node, **kwargs): - for target in node.targets: - self.sym_visitor.visit(target) - for child in node.body: - self.sym_visitor.visit(child) - - def generic_visit(self, node, *args, **kwargs): - raise NotImplementedError('Cannot find symbols for %r' % - node.__class__.__name__) - - -class FrameSymbolVisitor(NodeVisitor): - """A visitor for `Frame.inspect`.""" - - def __init__(self, symbols): - self.symbols = symbols - - def visit_Name(self, node, store_as_param=False, **kwargs): - """All assignments to names go through this function.""" - if store_as_param or node.ctx == 'param': - self.symbols.declare_parameter(node.name) - elif node.ctx == 'store': - self.symbols.store(node.name) - elif node.ctx == 'load': - self.symbols.load(node.name) - - def visit_NSRef(self, node, **kwargs): - self.symbols.load(node.name) - - def visit_If(self, node, **kwargs): - self.visit(node.test, **kwargs) - - original_symbols = self.symbols - - def inner_visit(nodes): - self.symbols = rv = original_symbols.copy() - for subnode in nodes: - self.visit(subnode, **kwargs) - self.symbols = original_symbols - return rv - - body_symbols = inner_visit(node.body) - elif_symbols = inner_visit(node.elif_) - else_symbols = inner_visit(node.else_ or ()) - - self.symbols.branch_update([body_symbols, elif_symbols, else_symbols]) - - def visit_Macro(self, node, **kwargs): - self.symbols.store(node.name) - - def visit_Import(self, node, **kwargs): - self.generic_visit(node, **kwargs) - self.symbols.store(node.target) - - def visit_FromImport(self, node, **kwargs): - self.generic_visit(node, **kwargs) - for name in node.names: - if isinstance(name, tuple): - self.symbols.store(name[1]) - else: - self.symbols.store(name) - - def visit_Assign(self, node, **kwargs): - """Visit assignments in the correct order.""" - self.visit(node.node, **kwargs) - self.visit(node.target, **kwargs) - - def visit_For(self, node, **kwargs): - """Visiting stops at for blocks. However the block sequence - is visited as part of the outer scope. - """ - self.visit(node.iter, **kwargs) - - def visit_CallBlock(self, node, **kwargs): - self.visit(node.call, **kwargs) - - def visit_FilterBlock(self, node, **kwargs): - self.visit(node.filter, **kwargs) - - def visit_With(self, node, **kwargs): - for target in node.values: - self.visit(target) - - def visit_AssignBlock(self, node, **kwargs): - """Stop visiting at block assigns.""" - self.visit(node.target, **kwargs) - - def visit_Scope(self, node, **kwargs): - """Stop visiting at scopes.""" - - def visit_Block(self, node, **kwargs): - """Stop visiting at blocks.""" - - def visit_OverlayScope(self, node, **kwargs): - """Do not visit into overlay scopes.""" diff --git a/pipenv/vendor/jinja2/lexer.py b/pipenv/vendor/jinja2/lexer.py deleted file mode 100644 index 6fd135dd..00000000 --- a/pipenv/vendor/jinja2/lexer.py +++ /dev/null @@ -1,739 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.lexer - ~~~~~~~~~~~~ - - This module implements a Jinja / Python combination lexer. The - `Lexer` class provided by this module is used to do some preprocessing - for Jinja. - - On the one hand it filters out invalid operators like the bitshift - operators we don't allow in templates. On the other hand it separates - template code and python code in expressions. - - :copyright: (c) 2017 by the Jinja Team. - :license: BSD, see LICENSE for more details. -""" -import re -from collections import deque -from operator import itemgetter - -from jinja2._compat import implements_iterator, intern, iteritems, text_type -from jinja2.exceptions import TemplateSyntaxError -from jinja2.utils import LRUCache - -# cache for the lexers. Exists in order to be able to have multiple -# environments with the same lexer -_lexer_cache = LRUCache(50) - -# static regular expressions -whitespace_re = re.compile(r'\s+', re.U) -string_re = re.compile(r"('([^'\\]*(?:\\.[^'\\]*)*)'" - r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S) -integer_re = re.compile(r'\d+') - -try: - # check if this Python supports Unicode identifiers - compile('föö', '<unknown>', 'eval') -except SyntaxError: - # no Unicode support, use ASCII identifiers - name_re = re.compile(r'[a-zA-Z_][a-zA-Z0-9_]*') - check_ident = False -else: - # Unicode support, build a pattern to match valid characters, and set flag - # to use str.isidentifier to validate during lexing - from jinja2 import _identifier - name_re = re.compile(r'[\w{0}]+'.format(_identifier.pattern)) - check_ident = True - # remove the pattern from memory after building the regex - import sys - del sys.modules['jinja2._identifier'] - import jinja2 - del jinja2._identifier - del _identifier - -float_re = re.compile(r'(?<!\.)\d+\.\d+') -newline_re = re.compile(r'(\r\n|\r|\n)') - -# internal the tokens and keep references to them -TOKEN_ADD = intern('add') -TOKEN_ASSIGN = intern('assign') -TOKEN_COLON = intern('colon') -TOKEN_COMMA = intern('comma') -TOKEN_DIV = intern('div') -TOKEN_DOT = intern('dot') -TOKEN_EQ = intern('eq') -TOKEN_FLOORDIV = intern('floordiv') -TOKEN_GT = intern('gt') -TOKEN_GTEQ = intern('gteq') -TOKEN_LBRACE = intern('lbrace') -TOKEN_LBRACKET = intern('lbracket') -TOKEN_LPAREN = intern('lparen') -TOKEN_LT = intern('lt') -TOKEN_LTEQ = intern('lteq') -TOKEN_MOD = intern('mod') -TOKEN_MUL = intern('mul') -TOKEN_NE = intern('ne') -TOKEN_PIPE = intern('pipe') -TOKEN_POW = intern('pow') -TOKEN_RBRACE = intern('rbrace') -TOKEN_RBRACKET = intern('rbracket') -TOKEN_RPAREN = intern('rparen') -TOKEN_SEMICOLON = intern('semicolon') -TOKEN_SUB = intern('sub') -TOKEN_TILDE = intern('tilde') -TOKEN_WHITESPACE = intern('whitespace') -TOKEN_FLOAT = intern('float') -TOKEN_INTEGER = intern('integer') -TOKEN_NAME = intern('name') -TOKEN_STRING = intern('string') -TOKEN_OPERATOR = intern('operator') -TOKEN_BLOCK_BEGIN = intern('block_begin') -TOKEN_BLOCK_END = intern('block_end') -TOKEN_VARIABLE_BEGIN = intern('variable_begin') -TOKEN_VARIABLE_END = intern('variable_end') -TOKEN_RAW_BEGIN = intern('raw_begin') -TOKEN_RAW_END = intern('raw_end') -TOKEN_COMMENT_BEGIN = intern('comment_begin') -TOKEN_COMMENT_END = intern('comment_end') -TOKEN_COMMENT = intern('comment') -TOKEN_LINESTATEMENT_BEGIN = intern('linestatement_begin') -TOKEN_LINESTATEMENT_END = intern('linestatement_end') -TOKEN_LINECOMMENT_BEGIN = intern('linecomment_begin') -TOKEN_LINECOMMENT_END = intern('linecomment_end') -TOKEN_LINECOMMENT = intern('linecomment') -TOKEN_DATA = intern('data') -TOKEN_INITIAL = intern('initial') -TOKEN_EOF = intern('eof') - -# bind operators to token types -operators = { - '+': TOKEN_ADD, - '-': TOKEN_SUB, - '/': TOKEN_DIV, - '//': TOKEN_FLOORDIV, - '*': TOKEN_MUL, - '%': TOKEN_MOD, - '**': TOKEN_POW, - '~': TOKEN_TILDE, - '[': TOKEN_LBRACKET, - ']': TOKEN_RBRACKET, - '(': TOKEN_LPAREN, - ')': TOKEN_RPAREN, - '{': TOKEN_LBRACE, - '}': TOKEN_RBRACE, - '==': TOKEN_EQ, - '!=': TOKEN_NE, - '>': TOKEN_GT, - '>=': TOKEN_GTEQ, - '<': TOKEN_LT, - '<=': TOKEN_LTEQ, - '=': TOKEN_ASSIGN, - '.': TOKEN_DOT, - ':': TOKEN_COLON, - '|': TOKEN_PIPE, - ',': TOKEN_COMMA, - ';': TOKEN_SEMICOLON -} - -reverse_operators = dict([(v, k) for k, v in iteritems(operators)]) -assert len(operators) == len(reverse_operators), 'operators dropped' -operator_re = re.compile('(%s)' % '|'.join(re.escape(x) for x in - sorted(operators, key=lambda x: -len(x)))) - -ignored_tokens = frozenset([TOKEN_COMMENT_BEGIN, TOKEN_COMMENT, - TOKEN_COMMENT_END, TOKEN_WHITESPACE, - TOKEN_LINECOMMENT_BEGIN, TOKEN_LINECOMMENT_END, - TOKEN_LINECOMMENT]) -ignore_if_empty = frozenset([TOKEN_WHITESPACE, TOKEN_DATA, - TOKEN_COMMENT, TOKEN_LINECOMMENT]) - - -def _describe_token_type(token_type): - if token_type in reverse_operators: - return reverse_operators[token_type] - return { - TOKEN_COMMENT_BEGIN: 'begin of comment', - TOKEN_COMMENT_END: 'end of comment', - TOKEN_COMMENT: 'comment', - TOKEN_LINECOMMENT: 'comment', - TOKEN_BLOCK_BEGIN: 'begin of statement block', - TOKEN_BLOCK_END: 'end of statement block', - TOKEN_VARIABLE_BEGIN: 'begin of print statement', - TOKEN_VARIABLE_END: 'end of print statement', - TOKEN_LINESTATEMENT_BEGIN: 'begin of line statement', - TOKEN_LINESTATEMENT_END: 'end of line statement', - TOKEN_DATA: 'template data / text', - TOKEN_EOF: 'end of template' - }.get(token_type, token_type) - - -def describe_token(token): - """Returns a description of the token.""" - if token.type == 'name': - return token.value - return _describe_token_type(token.type) - - -def describe_token_expr(expr): - """Like `describe_token` but for token expressions.""" - if ':' in expr: - type, value = expr.split(':', 1) - if type == 'name': - return value - else: - type = expr - return _describe_token_type(type) - - -def count_newlines(value): - """Count the number of newline characters in the string. This is - useful for extensions that filter a stream. - """ - return len(newline_re.findall(value)) - - -def compile_rules(environment): - """Compiles all the rules from the environment into a list of rules.""" - e = re.escape - rules = [ - (len(environment.comment_start_string), 'comment', - e(environment.comment_start_string)), - (len(environment.block_start_string), 'block', - e(environment.block_start_string)), - (len(environment.variable_start_string), 'variable', - e(environment.variable_start_string)) - ] - - if environment.line_statement_prefix is not None: - rules.append((len(environment.line_statement_prefix), 'linestatement', - r'^[ \t\v]*' + e(environment.line_statement_prefix))) - if environment.line_comment_prefix is not None: - rules.append((len(environment.line_comment_prefix), 'linecomment', - r'(?:^|(?<=\S))[^\S\r\n]*' + - e(environment.line_comment_prefix))) - - return [x[1:] for x in sorted(rules, reverse=True)] - - -class Failure(object): - """Class that raises a `TemplateSyntaxError` if called. - Used by the `Lexer` to specify known errors. - """ - - def __init__(self, message, cls=TemplateSyntaxError): - self.message = message - self.error_class = cls - - def __call__(self, lineno, filename): - raise self.error_class(self.message, lineno, filename) - - -class Token(tuple): - """Token class.""" - __slots__ = () - lineno, type, value = (property(itemgetter(x)) for x in range(3)) - - def __new__(cls, lineno, type, value): - return tuple.__new__(cls, (lineno, intern(str(type)), value)) - - def __str__(self): - if self.type in reverse_operators: - return reverse_operators[self.type] - elif self.type == 'name': - return self.value - return self.type - - def test(self, expr): - """Test a token against a token expression. This can either be a - token type or ``'token_type:token_value'``. This can only test - against string values and types. - """ - # here we do a regular string equality check as test_any is usually - # passed an iterable of not interned strings. - if self.type == expr: - return True - elif ':' in expr: - return expr.split(':', 1) == [self.type, self.value] - return False - - def test_any(self, *iterable): - """Test against multiple token expressions.""" - for expr in iterable: - if self.test(expr): - return True - return False - - def __repr__(self): - return 'Token(%r, %r, %r)' % ( - self.lineno, - self.type, - self.value - ) - - -@implements_iterator -class TokenStreamIterator(object): - """The iterator for tokenstreams. Iterate over the stream - until the eof token is reached. - """ - - def __init__(self, stream): - self.stream = stream - - def __iter__(self): - return self - - def __next__(self): - token = self.stream.current - if token.type is TOKEN_EOF: - self.stream.close() - raise StopIteration() - next(self.stream) - return token - - -@implements_iterator -class TokenStream(object): - """A token stream is an iterable that yields :class:`Token`\\s. The - parser however does not iterate over it but calls :meth:`next` to go - one token ahead. The current active token is stored as :attr:`current`. - """ - - def __init__(self, generator, name, filename): - self._iter = iter(generator) - self._pushed = deque() - self.name = name - self.filename = filename - self.closed = False - self.current = Token(1, TOKEN_INITIAL, '') - next(self) - - def __iter__(self): - return TokenStreamIterator(self) - - def __bool__(self): - return bool(self._pushed) or self.current.type is not TOKEN_EOF - __nonzero__ = __bool__ # py2 - - eos = property(lambda x: not x, doc="Are we at the end of the stream?") - - def push(self, token): - """Push a token back to the stream.""" - self._pushed.append(token) - - def look(self): - """Look at the next token.""" - old_token = next(self) - result = self.current - self.push(result) - self.current = old_token - return result - - def skip(self, n=1): - """Got n tokens ahead.""" - for x in range(n): - next(self) - - def next_if(self, expr): - """Perform the token test and return the token if it matched. - Otherwise the return value is `None`. - """ - if self.current.test(expr): - return next(self) - - def skip_if(self, expr): - """Like :meth:`next_if` but only returns `True` or `False`.""" - return self.next_if(expr) is not None - - def __next__(self): - """Go one token ahead and return the old one. - - Use the built-in :func:`next` instead of calling this directly. - """ - rv = self.current - if self._pushed: - self.current = self._pushed.popleft() - elif self.current.type is not TOKEN_EOF: - try: - self.current = next(self._iter) - except StopIteration: - self.close() - return rv - - def close(self): - """Close the stream.""" - self.current = Token(self.current.lineno, TOKEN_EOF, '') - self._iter = None - self.closed = True - - def expect(self, expr): - """Expect a given token type and return it. This accepts the same - argument as :meth:`jinja2.lexer.Token.test`. - """ - if not self.current.test(expr): - expr = describe_token_expr(expr) - if self.current.type is TOKEN_EOF: - raise TemplateSyntaxError('unexpected end of template, ' - 'expected %r.' % expr, - self.current.lineno, - self.name, self.filename) - raise TemplateSyntaxError("expected token %r, got %r" % - (expr, describe_token(self.current)), - self.current.lineno, - self.name, self.filename) - try: - return self.current - finally: - next(self) - - -def get_lexer(environment): - """Return a lexer which is probably cached.""" - key = (environment.block_start_string, - environment.block_end_string, - environment.variable_start_string, - environment.variable_end_string, - environment.comment_start_string, - environment.comment_end_string, - environment.line_statement_prefix, - environment.line_comment_prefix, - environment.trim_blocks, - environment.lstrip_blocks, - environment.newline_sequence, - environment.keep_trailing_newline) - lexer = _lexer_cache.get(key) - if lexer is None: - lexer = Lexer(environment) - _lexer_cache[key] = lexer - return lexer - - -class Lexer(object): - """Class that implements a lexer for a given environment. Automatically - created by the environment class, usually you don't have to do that. - - Note that the lexer is not automatically bound to an environment. - Multiple environments can share the same lexer. - """ - - def __init__(self, environment): - # shortcuts - c = lambda x: re.compile(x, re.M | re.S) - e = re.escape - - # lexing rules for tags - tag_rules = [ - (whitespace_re, TOKEN_WHITESPACE, None), - (float_re, TOKEN_FLOAT, None), - (integer_re, TOKEN_INTEGER, None), - (name_re, TOKEN_NAME, None), - (string_re, TOKEN_STRING, None), - (operator_re, TOKEN_OPERATOR, None) - ] - - # assemble the root lexing rule. because "|" is ungreedy - # we have to sort by length so that the lexer continues working - # as expected when we have parsing rules like <% for block and - # <%= for variables. (if someone wants asp like syntax) - # variables are just part of the rules if variable processing - # is required. - root_tag_rules = compile_rules(environment) - - # block suffix if trimming is enabled - block_suffix_re = environment.trim_blocks and '\\n?' or '' - - # strip leading spaces if lstrip_blocks is enabled - prefix_re = {} - if environment.lstrip_blocks: - # use '{%+' to manually disable lstrip_blocks behavior - no_lstrip_re = e('+') - # detect overlap between block and variable or comment strings - block_diff = c(r'^%s(.*)' % e(environment.block_start_string)) - # make sure we don't mistake a block for a variable or a comment - m = block_diff.match(environment.comment_start_string) - no_lstrip_re += m and r'|%s' % e(m.group(1)) or '' - m = block_diff.match(environment.variable_start_string) - no_lstrip_re += m and r'|%s' % e(m.group(1)) or '' - - # detect overlap between comment and variable strings - comment_diff = c(r'^%s(.*)' % e(environment.comment_start_string)) - m = comment_diff.match(environment.variable_start_string) - no_variable_re = m and r'(?!%s)' % e(m.group(1)) or '' - - lstrip_re = r'^[ \t]*' - block_prefix_re = r'%s%s(?!%s)|%s\+?' % ( - lstrip_re, - e(environment.block_start_string), - no_lstrip_re, - e(environment.block_start_string), - ) - comment_prefix_re = r'%s%s%s|%s\+?' % ( - lstrip_re, - e(environment.comment_start_string), - no_variable_re, - e(environment.comment_start_string), - ) - prefix_re['block'] = block_prefix_re - prefix_re['comment'] = comment_prefix_re - else: - block_prefix_re = '%s' % e(environment.block_start_string) - - self.newline_sequence = environment.newline_sequence - self.keep_trailing_newline = environment.keep_trailing_newline - - # global lexing rules - self.rules = { - 'root': [ - # directives - (c('(.*?)(?:%s)' % '|'.join( - [r'(?P<raw_begin>(?:\s*%s\-|%s)\s*raw\s*(?:\-%s\s*|%s))' % ( - e(environment.block_start_string), - block_prefix_re, - e(environment.block_end_string), - e(environment.block_end_string) - )] + [ - r'(?P<%s_begin>\s*%s\-|%s)' % (n, r, prefix_re.get(n,r)) - for n, r in root_tag_rules - ])), (TOKEN_DATA, '#bygroup'), '#bygroup'), - # data - (c('.+'), TOKEN_DATA, None) - ], - # comments - TOKEN_COMMENT_BEGIN: [ - (c(r'(.*?)((?:\-%s\s*|%s)%s)' % ( - e(environment.comment_end_string), - e(environment.comment_end_string), - block_suffix_re - )), (TOKEN_COMMENT, TOKEN_COMMENT_END), '#pop'), - (c('(.)'), (Failure('Missing end of comment tag'),), None) - ], - # blocks - TOKEN_BLOCK_BEGIN: [ - (c(r'(?:\-%s\s*|%s)%s' % ( - e(environment.block_end_string), - e(environment.block_end_string), - block_suffix_re - )), TOKEN_BLOCK_END, '#pop'), - ] + tag_rules, - # variables - TOKEN_VARIABLE_BEGIN: [ - (c(r'\-%s\s*|%s' % ( - e(environment.variable_end_string), - e(environment.variable_end_string) - )), TOKEN_VARIABLE_END, '#pop') - ] + tag_rules, - # raw block - TOKEN_RAW_BEGIN: [ - (c(r'(.*?)((?:\s*%s\-|%s)\s*endraw\s*(?:\-%s\s*|%s%s))' % ( - e(environment.block_start_string), - block_prefix_re, - e(environment.block_end_string), - e(environment.block_end_string), - block_suffix_re - )), (TOKEN_DATA, TOKEN_RAW_END), '#pop'), - (c('(.)'), (Failure('Missing end of raw directive'),), None) - ], - # line statements - TOKEN_LINESTATEMENT_BEGIN: [ - (c(r'\s*(\n|$)'), TOKEN_LINESTATEMENT_END, '#pop') - ] + tag_rules, - # line comments - TOKEN_LINECOMMENT_BEGIN: [ - (c(r'(.*?)()(?=\n|$)'), (TOKEN_LINECOMMENT, - TOKEN_LINECOMMENT_END), '#pop') - ] - } - - def _normalize_newlines(self, value): - """Called for strings and template data to normalize it to unicode.""" - return newline_re.sub(self.newline_sequence, value) - - def tokenize(self, source, name=None, filename=None, state=None): - """Calls tokeniter + tokenize and wraps it in a token stream. - """ - stream = self.tokeniter(source, name, filename, state) - return TokenStream(self.wrap(stream, name, filename), name, filename) - - def wrap(self, stream, name=None, filename=None): - """This is called with the stream as returned by `tokenize` and wraps - every token in a :class:`Token` and converts the value. - """ - for lineno, token, value in stream: - if token in ignored_tokens: - continue - elif token == 'linestatement_begin': - token = 'block_begin' - elif token == 'linestatement_end': - token = 'block_end' - # we are not interested in those tokens in the parser - elif token in ('raw_begin', 'raw_end'): - continue - elif token == 'data': - value = self._normalize_newlines(value) - elif token == 'keyword': - token = value - elif token == 'name': - value = str(value) - if check_ident and not value.isidentifier(): - raise TemplateSyntaxError( - 'Invalid character in identifier', - lineno, name, filename) - elif token == 'string': - # try to unescape string - try: - value = self._normalize_newlines(value[1:-1]) \ - .encode('ascii', 'backslashreplace') \ - .decode('unicode-escape') - except Exception as e: - msg = str(e).split(':')[-1].strip() - raise TemplateSyntaxError(msg, lineno, name, filename) - elif token == 'integer': - value = int(value) - elif token == 'float': - value = float(value) - elif token == 'operator': - token = operators[value] - yield Token(lineno, token, value) - - def tokeniter(self, source, name, filename=None, state=None): - """This method tokenizes the text and returns the tokens in a - generator. Use this method if you just want to tokenize a template. - """ - source = text_type(source) - lines = source.splitlines() - if self.keep_trailing_newline and source: - for newline in ('\r\n', '\r', '\n'): - if source.endswith(newline): - lines.append('') - break - source = '\n'.join(lines) - pos = 0 - lineno = 1 - stack = ['root'] - if state is not None and state != 'root': - assert state in ('variable', 'block'), 'invalid state' - stack.append(state + '_begin') - else: - state = 'root' - statetokens = self.rules[stack[-1]] - source_length = len(source) - - balancing_stack = [] - - while 1: - # tokenizer loop - for regex, tokens, new_state in statetokens: - m = regex.match(source, pos) - # if no match we try again with the next rule - if m is None: - continue - - # we only match blocks and variables if braces / parentheses - # are balanced. continue parsing with the lower rule which - # is the operator rule. do this only if the end tags look - # like operators - if balancing_stack and \ - tokens in ('variable_end', 'block_end', - 'linestatement_end'): - continue - - # tuples support more options - if isinstance(tokens, tuple): - for idx, token in enumerate(tokens): - # failure group - if token.__class__ is Failure: - raise token(lineno, filename) - # bygroup is a bit more complex, in that case we - # yield for the current token the first named - # group that matched - elif token == '#bygroup': - for key, value in iteritems(m.groupdict()): - if value is not None: - yield lineno, key, value - lineno += value.count('\n') - break - else: - raise RuntimeError('%r wanted to resolve ' - 'the token dynamically' - ' but no group matched' - % regex) - # normal group - else: - data = m.group(idx + 1) - if data or token not in ignore_if_empty: - yield lineno, token, data - lineno += data.count('\n') - - # strings as token just are yielded as it. - else: - data = m.group() - # update brace/parentheses balance - if tokens == 'operator': - if data == '{': - balancing_stack.append('}') - elif data == '(': - balancing_stack.append(')') - elif data == '[': - balancing_stack.append(']') - elif data in ('}', ')', ']'): - if not balancing_stack: - raise TemplateSyntaxError('unexpected \'%s\'' % - data, lineno, name, - filename) - expected_op = balancing_stack.pop() - if expected_op != data: - raise TemplateSyntaxError('unexpected \'%s\', ' - 'expected \'%s\'' % - (data, expected_op), - lineno, name, - filename) - # yield items - if data or tokens not in ignore_if_empty: - yield lineno, tokens, data - lineno += data.count('\n') - - # fetch new position into new variable so that we can check - # if there is a internal parsing error which would result - # in an infinite loop - pos2 = m.end() - - # handle state changes - if new_state is not None: - # remove the uppermost state - if new_state == '#pop': - stack.pop() - # resolve the new state by group checking - elif new_state == '#bygroup': - for key, value in iteritems(m.groupdict()): - if value is not None: - stack.append(key) - break - else: - raise RuntimeError('%r wanted to resolve the ' - 'new state dynamically but' - ' no group matched' % - regex) - # direct state name given - else: - stack.append(new_state) - statetokens = self.rules[stack[-1]] - # we are still at the same position and no stack change. - # this means a loop without break condition, avoid that and - # raise error - elif pos2 == pos: - raise RuntimeError('%r yielded empty string without ' - 'stack change' % regex) - # publish new function and start again - pos = pos2 - break - # if loop terminated without break we haven't found a single match - # either we are at the end of the file or we have a problem - else: - # end of text - if pos >= source_length: - return - # something went wrong - raise TemplateSyntaxError('unexpected char %r at %d' % - (source[pos], pos), lineno, - name, filename) diff --git a/pipenv/vendor/jinja2/loaders.py b/pipenv/vendor/jinja2/loaders.py deleted file mode 100644 index 4c797937..00000000 --- a/pipenv/vendor/jinja2/loaders.py +++ /dev/null @@ -1,481 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.loaders - ~~~~~~~~~~~~~~ - - Jinja loader classes. - - :copyright: (c) 2017 by the Jinja Team. - :license: BSD, see LICENSE for more details. -""" -import os -import sys -import weakref -from types import ModuleType -from os import path -from hashlib import sha1 -from jinja2.exceptions import TemplateNotFound -from jinja2.utils import open_if_exists, internalcode -from jinja2._compat import string_types, iteritems - - -def split_template_path(template): - """Split a path into segments and perform a sanity check. If it detects - '..' in the path it will raise a `TemplateNotFound` error. - """ - pieces = [] - for piece in template.split('/'): - if path.sep in piece \ - or (path.altsep and path.altsep in piece) or \ - piece == path.pardir: - raise TemplateNotFound(template) - elif piece and piece != '.': - pieces.append(piece) - return pieces - - -class BaseLoader(object): - """Baseclass for all loaders. Subclass this and override `get_source` to - implement a custom loading mechanism. The environment provides a - `get_template` method that calls the loader's `load` method to get the - :class:`Template` object. - - A very basic example for a loader that looks up templates on the file - system could look like this:: - - from jinja2 import BaseLoader, TemplateNotFound - from os.path import join, exists, getmtime - - class MyLoader(BaseLoader): - - def __init__(self, path): - self.path = path - - def get_source(self, environment, template): - path = join(self.path, template) - if not exists(path): - raise TemplateNotFound(template) - mtime = getmtime(path) - with file(path) as f: - source = f.read().decode('utf-8') - return source, path, lambda: mtime == getmtime(path) - """ - - #: if set to `False` it indicates that the loader cannot provide access - #: to the source of templates. - #: - #: .. versionadded:: 2.4 - has_source_access = True - - def get_source(self, environment, template): - """Get the template source, filename and reload helper for a template. - It's passed the environment and template name and has to return a - tuple in the form ``(source, filename, uptodate)`` or raise a - `TemplateNotFound` error if it can't locate the template. - - The source part of the returned tuple must be the source of the - template as unicode string or a ASCII bytestring. The filename should - be the name of the file on the filesystem if it was loaded from there, - otherwise `None`. The filename is used by python for the tracebacks - if no loader extension is used. - - The last item in the tuple is the `uptodate` function. If auto - reloading is enabled it's always called to check if the template - changed. No arguments are passed so the function must store the - old state somewhere (for example in a closure). If it returns `False` - the template will be reloaded. - """ - if not self.has_source_access: - raise RuntimeError('%s cannot provide access to the source' % - self.__class__.__name__) - raise TemplateNotFound(template) - - def list_templates(self): - """Iterates over all templates. If the loader does not support that - it should raise a :exc:`TypeError` which is the default behavior. - """ - raise TypeError('this loader cannot iterate over all templates') - - @internalcode - def load(self, environment, name, globals=None): - """Loads a template. This method looks up the template in the cache - or loads one by calling :meth:`get_source`. Subclasses should not - override this method as loaders working on collections of other - loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`) - will not call this method but `get_source` directly. - """ - code = None - if globals is None: - globals = {} - - # first we try to get the source for this template together - # with the filename and the uptodate function. - source, filename, uptodate = self.get_source(environment, name) - - # try to load the code from the bytecode cache if there is a - # bytecode cache configured. - bcc = environment.bytecode_cache - if bcc is not None: - bucket = bcc.get_bucket(environment, name, filename, source) - code = bucket.code - - # if we don't have code so far (not cached, no longer up to - # date) etc. we compile the template - if code is None: - code = environment.compile(source, name, filename) - - # if the bytecode cache is available and the bucket doesn't - # have a code so far, we give the bucket the new code and put - # it back to the bytecode cache. - if bcc is not None and bucket.code is None: - bucket.code = code - bcc.set_bucket(bucket) - - return environment.template_class.from_code(environment, code, - globals, uptodate) - - -class FileSystemLoader(BaseLoader): - """Loads templates from the file system. This loader can find templates - in folders on the file system and is the preferred way to load them. - - The loader takes the path to the templates as string, or if multiple - locations are wanted a list of them which is then looked up in the - given order:: - - >>> loader = FileSystemLoader('/path/to/templates') - >>> loader = FileSystemLoader(['/path/to/templates', '/other/path']) - - Per default the template encoding is ``'utf-8'`` which can be changed - by setting the `encoding` parameter to something else. - - To follow symbolic links, set the *followlinks* parameter to ``True``:: - - >>> loader = FileSystemLoader('/path/to/templates', followlinks=True) - - .. versionchanged:: 2.8+ - The *followlinks* parameter was added. - """ - - def __init__(self, searchpath, encoding='utf-8', followlinks=False): - if isinstance(searchpath, string_types): - searchpath = [searchpath] - self.searchpath = list(searchpath) - self.encoding = encoding - self.followlinks = followlinks - - def get_source(self, environment, template): - pieces = split_template_path(template) - for searchpath in self.searchpath: - filename = path.join(searchpath, *pieces) - f = open_if_exists(filename) - if f is None: - continue - try: - contents = f.read().decode(self.encoding) - finally: - f.close() - - mtime = path.getmtime(filename) - - def uptodate(): - try: - return path.getmtime(filename) == mtime - except OSError: - return False - return contents, filename, uptodate - raise TemplateNotFound(template) - - def list_templates(self): - found = set() - for searchpath in self.searchpath: - walk_dir = os.walk(searchpath, followlinks=self.followlinks) - for dirpath, dirnames, filenames in walk_dir: - for filename in filenames: - template = os.path.join(dirpath, filename) \ - [len(searchpath):].strip(os.path.sep) \ - .replace(os.path.sep, '/') - if template[:2] == './': - template = template[2:] - if template not in found: - found.add(template) - return sorted(found) - - -class PackageLoader(BaseLoader): - """Load templates from python eggs or packages. It is constructed with - the name of the python package and the path to the templates in that - package:: - - loader = PackageLoader('mypackage', 'views') - - If the package path is not given, ``'templates'`` is assumed. - - Per default the template encoding is ``'utf-8'`` which can be changed - by setting the `encoding` parameter to something else. Due to the nature - of eggs it's only possible to reload templates if the package was loaded - from the file system and not a zip file. - """ - - def __init__(self, package_name, package_path='templates', - encoding='utf-8'): - from pkg_resources import DefaultProvider, ResourceManager, \ - get_provider - provider = get_provider(package_name) - self.encoding = encoding - self.manager = ResourceManager() - self.filesystem_bound = isinstance(provider, DefaultProvider) - self.provider = provider - self.package_path = package_path - - def get_source(self, environment, template): - pieces = split_template_path(template) - p = '/'.join((self.package_path,) + tuple(pieces)) - if not self.provider.has_resource(p): - raise TemplateNotFound(template) - - filename = uptodate = None - if self.filesystem_bound: - filename = self.provider.get_resource_filename(self.manager, p) - mtime = path.getmtime(filename) - def uptodate(): - try: - return path.getmtime(filename) == mtime - except OSError: - return False - - source = self.provider.get_resource_string(self.manager, p) - return source.decode(self.encoding), filename, uptodate - - def list_templates(self): - path = self.package_path - if path[:2] == './': - path = path[2:] - elif path == '.': - path = '' - offset = len(path) - results = [] - def _walk(path): - for filename in self.provider.resource_listdir(path): - fullname = path + '/' + filename - if self.provider.resource_isdir(fullname): - _walk(fullname) - else: - results.append(fullname[offset:].lstrip('/')) - _walk(path) - results.sort() - return results - - -class DictLoader(BaseLoader): - """Loads a template from a python dict. It's passed a dict of unicode - strings bound to template names. This loader is useful for unittesting: - - >>> loader = DictLoader({'index.html': 'source here'}) - - Because auto reloading is rarely useful this is disabled per default. - """ - - def __init__(self, mapping): - self.mapping = mapping - - def get_source(self, environment, template): - if template in self.mapping: - source = self.mapping[template] - return source, None, lambda: source == self.mapping.get(template) - raise TemplateNotFound(template) - - def list_templates(self): - return sorted(self.mapping) - - -class FunctionLoader(BaseLoader): - """A loader that is passed a function which does the loading. The - function receives the name of the template and has to return either - an unicode string with the template source, a tuple in the form ``(source, - filename, uptodatefunc)`` or `None` if the template does not exist. - - >>> def load_template(name): - ... if name == 'index.html': - ... return '...' - ... - >>> loader = FunctionLoader(load_template) - - The `uptodatefunc` is a function that is called if autoreload is enabled - and has to return `True` if the template is still up to date. For more - details have a look at :meth:`BaseLoader.get_source` which has the same - return value. - """ - - def __init__(self, load_func): - self.load_func = load_func - - def get_source(self, environment, template): - rv = self.load_func(template) - if rv is None: - raise TemplateNotFound(template) - elif isinstance(rv, string_types): - return rv, None, None - return rv - - -class PrefixLoader(BaseLoader): - """A loader that is passed a dict of loaders where each loader is bound - to a prefix. The prefix is delimited from the template by a slash per - default, which can be changed by setting the `delimiter` argument to - something else:: - - loader = PrefixLoader({ - 'app1': PackageLoader('mypackage.app1'), - 'app2': PackageLoader('mypackage.app2') - }) - - By loading ``'app1/index.html'`` the file from the app1 package is loaded, - by loading ``'app2/index.html'`` the file from the second. - """ - - def __init__(self, mapping, delimiter='/'): - self.mapping = mapping - self.delimiter = delimiter - - def get_loader(self, template): - try: - prefix, name = template.split(self.delimiter, 1) - loader = self.mapping[prefix] - except (ValueError, KeyError): - raise TemplateNotFound(template) - return loader, name - - def get_source(self, environment, template): - loader, name = self.get_loader(template) - try: - return loader.get_source(environment, name) - except TemplateNotFound: - # re-raise the exception with the correct filename here. - # (the one that includes the prefix) - raise TemplateNotFound(template) - - @internalcode - def load(self, environment, name, globals=None): - loader, local_name = self.get_loader(name) - try: - return loader.load(environment, local_name, globals) - except TemplateNotFound: - # re-raise the exception with the correct filename here. - # (the one that includes the prefix) - raise TemplateNotFound(name) - - def list_templates(self): - result = [] - for prefix, loader in iteritems(self.mapping): - for template in loader.list_templates(): - result.append(prefix + self.delimiter + template) - return result - - -class ChoiceLoader(BaseLoader): - """This loader works like the `PrefixLoader` just that no prefix is - specified. If a template could not be found by one loader the next one - is tried. - - >>> loader = ChoiceLoader([ - ... FileSystemLoader('/path/to/user/templates'), - ... FileSystemLoader('/path/to/system/templates') - ... ]) - - This is useful if you want to allow users to override builtin templates - from a different location. - """ - - def __init__(self, loaders): - self.loaders = loaders - - def get_source(self, environment, template): - for loader in self.loaders: - try: - return loader.get_source(environment, template) - except TemplateNotFound: - pass - raise TemplateNotFound(template) - - @internalcode - def load(self, environment, name, globals=None): - for loader in self.loaders: - try: - return loader.load(environment, name, globals) - except TemplateNotFound: - pass - raise TemplateNotFound(name) - - def list_templates(self): - found = set() - for loader in self.loaders: - found.update(loader.list_templates()) - return sorted(found) - - -class _TemplateModule(ModuleType): - """Like a normal module but with support for weak references""" - - -class ModuleLoader(BaseLoader): - """This loader loads templates from precompiled templates. - - Example usage: - - >>> loader = ChoiceLoader([ - ... ModuleLoader('/path/to/compiled/templates'), - ... FileSystemLoader('/path/to/templates') - ... ]) - - Templates can be precompiled with :meth:`Environment.compile_templates`. - """ - - has_source_access = False - - def __init__(self, path): - package_name = '_jinja2_module_templates_%x' % id(self) - - # create a fake module that looks for the templates in the - # path given. - mod = _TemplateModule(package_name) - if isinstance(path, string_types): - path = [path] - else: - path = list(path) - mod.__path__ = path - - sys.modules[package_name] = weakref.proxy(mod, - lambda x: sys.modules.pop(package_name, None)) - - # the only strong reference, the sys.modules entry is weak - # so that the garbage collector can remove it once the - # loader that created it goes out of business. - self.module = mod - self.package_name = package_name - - @staticmethod - def get_template_key(name): - return 'tmpl_' + sha1(name.encode('utf-8')).hexdigest() - - @staticmethod - def get_module_filename(name): - return ModuleLoader.get_template_key(name) + '.py' - - @internalcode - def load(self, environment, name, globals=None): - key = self.get_template_key(name) - module = '%s.%s' % (self.package_name, key) - mod = getattr(self.module, module, None) - if mod is None: - try: - mod = __import__(module, None, None, ['root']) - except ImportError: - raise TemplateNotFound(name) - - # remove the entry from sys.modules, we only want the attribute - # on the module object we have stored on the loader. - sys.modules.pop(module, None) - - return environment.template_class.from_module_dict( - environment, mod.__dict__, globals) diff --git a/pipenv/vendor/jinja2/meta.py b/pipenv/vendor/jinja2/meta.py deleted file mode 100644 index 7421914f..00000000 --- a/pipenv/vendor/jinja2/meta.py +++ /dev/null @@ -1,106 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.meta - ~~~~~~~~~~~ - - This module implements various functions that exposes information about - templates that might be interesting for various kinds of applications. - - :copyright: (c) 2017 by the Jinja Team, see AUTHORS for more details. - :license: BSD, see LICENSE for more details. -""" -from jinja2 import nodes -from jinja2.compiler import CodeGenerator -from jinja2._compat import string_types, iteritems - - -class TrackingCodeGenerator(CodeGenerator): - """We abuse the code generator for introspection.""" - - def __init__(self, environment): - CodeGenerator.__init__(self, environment, '<introspection>', - '<introspection>') - self.undeclared_identifiers = set() - - def write(self, x): - """Don't write.""" - - def enter_frame(self, frame): - """Remember all undeclared identifiers.""" - CodeGenerator.enter_frame(self, frame) - for _, (action, param) in iteritems(frame.symbols.loads): - if action == 'resolve': - self.undeclared_identifiers.add(param) - - -def find_undeclared_variables(ast): - """Returns a set of all variables in the AST that will be looked up from - the context at runtime. Because at compile time it's not known which - variables will be used depending on the path the execution takes at - runtime, all variables are returned. - - >>> from jinja2 import Environment, meta - >>> env = Environment() - >>> ast = env.parse('{% set foo = 42 %}{{ bar + foo }}') - >>> meta.find_undeclared_variables(ast) == set(['bar']) - True - - .. admonition:: Implementation - - Internally the code generator is used for finding undeclared variables. - This is good to know because the code generator might raise a - :exc:`TemplateAssertionError` during compilation and as a matter of - fact this function can currently raise that exception as well. - """ - codegen = TrackingCodeGenerator(ast.environment) - codegen.visit(ast) - return codegen.undeclared_identifiers - - -def find_referenced_templates(ast): - """Finds all the referenced templates from the AST. This will return an - iterator over all the hardcoded template extensions, inclusions and - imports. If dynamic inheritance or inclusion is used, `None` will be - yielded. - - >>> from jinja2 import Environment, meta - >>> env = Environment() - >>> ast = env.parse('{% extends "layout.html" %}{% include helper %}') - >>> list(meta.find_referenced_templates(ast)) - ['layout.html', None] - - This function is useful for dependency tracking. For example if you want - to rebuild parts of the website after a layout template has changed. - """ - for node in ast.find_all((nodes.Extends, nodes.FromImport, nodes.Import, - nodes.Include)): - if not isinstance(node.template, nodes.Const): - # a tuple with some non consts in there - if isinstance(node.template, (nodes.Tuple, nodes.List)): - for template_name in node.template.items: - # something const, only yield the strings and ignore - # non-string consts that really just make no sense - if isinstance(template_name, nodes.Const): - if isinstance(template_name.value, string_types): - yield template_name.value - # something dynamic in there - else: - yield None - # something dynamic we don't know about here - else: - yield None - continue - # constant is a basestring, direct template name - if isinstance(node.template.value, string_types): - yield node.template.value - # a tuple or list (latter *should* not happen) made of consts, - # yield the consts that are strings. We could warn here for - # non string values - elif isinstance(node, nodes.Include) and \ - isinstance(node.template.value, (tuple, list)): - for template_name in node.template.value: - if isinstance(template_name, string_types): - yield template_name - # something else we don't care about, we could warn here - else: - yield None diff --git a/pipenv/vendor/jinja2/nativetypes.py b/pipenv/vendor/jinja2/nativetypes.py deleted file mode 100644 index fe17e413..00000000 --- a/pipenv/vendor/jinja2/nativetypes.py +++ /dev/null @@ -1,220 +0,0 @@ -import sys -from ast import literal_eval -from itertools import islice, chain -from jinja2 import nodes -from jinja2._compat import text_type -from jinja2.compiler import CodeGenerator, has_safe_repr -from jinja2.environment import Environment, Template -from jinja2.utils import concat, escape - - -def native_concat(nodes): - """Return a native Python type from the list of compiled nodes. If the - result is a single node, its value is returned. Otherwise, the nodes are - concatenated as strings. If the result can be parsed with - :func:`ast.literal_eval`, the parsed value is returned. Otherwise, the - string is returned. - """ - head = list(islice(nodes, 2)) - - if not head: - return None - - if len(head) == 1: - out = head[0] - else: - out = u''.join([text_type(v) for v in chain(head, nodes)]) - - try: - return literal_eval(out) - except (ValueError, SyntaxError, MemoryError): - return out - - -class NativeCodeGenerator(CodeGenerator): - """A code generator which avoids injecting ``to_string()`` calls around the - internal code Jinja uses to render templates. - """ - - def visit_Output(self, node, frame): - """Same as :meth:`CodeGenerator.visit_Output`, but do not call - ``to_string`` on output nodes in generated code. - """ - if self.has_known_extends and frame.require_output_check: - return - - finalize = self.environment.finalize - finalize_context = getattr(finalize, 'contextfunction', False) - finalize_eval = getattr(finalize, 'evalcontextfunction', False) - finalize_env = getattr(finalize, 'environmentfunction', False) - - if finalize is not None: - if finalize_context or finalize_eval: - const_finalize = None - elif finalize_env: - def const_finalize(x): - return finalize(self.environment, x) - else: - const_finalize = finalize - else: - def const_finalize(x): - return x - - # If we are inside a frame that requires output checking, we do so. - outdent_later = False - - if frame.require_output_check: - self.writeline('if parent_template is None:') - self.indent() - outdent_later = True - - # Try to evaluate as many chunks as possible into a static string at - # compile time. - body = [] - - for child in node.nodes: - try: - if const_finalize is None: - raise nodes.Impossible() - - const = child.as_const(frame.eval_ctx) - if not has_safe_repr(const): - raise nodes.Impossible() - except nodes.Impossible: - body.append(child) - continue - - # the frame can't be volatile here, because otherwise the as_const - # function would raise an Impossible exception at that point - try: - if frame.eval_ctx.autoescape: - if hasattr(const, '__html__'): - const = const.__html__() - else: - const = escape(const) - - const = const_finalize(const) - except Exception: - # if something goes wrong here we evaluate the node at runtime - # for easier debugging - body.append(child) - continue - - if body and isinstance(body[-1], list): - body[-1].append(const) - else: - body.append([const]) - - # if we have less than 3 nodes or a buffer we yield or extend/append - if len(body) < 3 or frame.buffer is not None: - if frame.buffer is not None: - # for one item we append, for more we extend - if len(body) == 1: - self.writeline('%s.append(' % frame.buffer) - else: - self.writeline('%s.extend((' % frame.buffer) - - self.indent() - - for item in body: - if isinstance(item, list): - val = repr(native_concat(item)) - - if frame.buffer is None: - self.writeline('yield ' + val) - else: - self.writeline(val + ',') - else: - if frame.buffer is None: - self.writeline('yield ', item) - else: - self.newline(item) - - close = 0 - - if finalize is not None: - self.write('environment.finalize(') - - if finalize_context: - self.write('context, ') - - close += 1 - - self.visit(item, frame) - - if close > 0: - self.write(')' * close) - - if frame.buffer is not None: - self.write(',') - - if frame.buffer is not None: - # close the open parentheses - self.outdent() - self.writeline(len(body) == 1 and ')' or '))') - - # otherwise we create a format string as this is faster in that case - else: - format = [] - arguments = [] - - for item in body: - if isinstance(item, list): - format.append(native_concat(item).replace('%', '%%')) - else: - format.append('%s') - arguments.append(item) - - self.writeline('yield ') - self.write(repr(concat(format)) + ' % (') - self.indent() - - for argument in arguments: - self.newline(argument) - close = 0 - - if finalize is not None: - self.write('environment.finalize(') - - if finalize_context: - self.write('context, ') - elif finalize_eval: - self.write('context.eval_ctx, ') - elif finalize_env: - self.write('environment, ') - - close += 1 - - self.visit(argument, frame) - self.write(')' * close + ', ') - - self.outdent() - self.writeline(')') - - if outdent_later: - self.outdent() - - -class NativeTemplate(Template): - def render(self, *args, **kwargs): - """Render the template to produce a native Python type. If the result - is a single node, its value is returned. Otherwise, the nodes are - concatenated as strings. If the result can be parsed with - :func:`ast.literal_eval`, the parsed value is returned. Otherwise, the - string is returned. - """ - vars = dict(*args, **kwargs) - - try: - return native_concat(self.root_render_func(self.new_context(vars))) - except Exception: - exc_info = sys.exc_info() - - return self.environment.handle_exception(exc_info, True) - - -class NativeEnvironment(Environment): - """An environment that renders templates to native Python types.""" - - code_generator_class = NativeCodeGenerator - template_class = NativeTemplate diff --git a/pipenv/vendor/jinja2/nodes.py b/pipenv/vendor/jinja2/nodes.py deleted file mode 100644 index 4d9a01ad..00000000 --- a/pipenv/vendor/jinja2/nodes.py +++ /dev/null @@ -1,999 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.nodes - ~~~~~~~~~~~~ - - This module implements additional nodes derived from the ast base node. - - It also provides some node tree helper functions like `in_lineno` and - `get_nodes` used by the parser and translator in order to normalize - python and jinja nodes. - - :copyright: (c) 2017 by the Jinja Team. - :license: BSD, see LICENSE for more details. -""" -import types -import operator - -from collections import deque -from jinja2.utils import Markup -from jinja2._compat import izip, with_metaclass, text_type, PY2 - - -#: the types we support for context functions -_context_function_types = (types.FunctionType, types.MethodType) - - -_binop_to_func = { - '*': operator.mul, - '/': operator.truediv, - '//': operator.floordiv, - '**': operator.pow, - '%': operator.mod, - '+': operator.add, - '-': operator.sub -} - -_uaop_to_func = { - 'not': operator.not_, - '+': operator.pos, - '-': operator.neg -} - -_cmpop_to_func = { - 'eq': operator.eq, - 'ne': operator.ne, - 'gt': operator.gt, - 'gteq': operator.ge, - 'lt': operator.lt, - 'lteq': operator.le, - 'in': lambda a, b: a in b, - 'notin': lambda a, b: a not in b -} - - -class Impossible(Exception): - """Raised if the node could not perform a requested action.""" - - -class NodeType(type): - """A metaclass for nodes that handles the field and attribute - inheritance. fields and attributes from the parent class are - automatically forwarded to the child.""" - - def __new__(cls, name, bases, d): - for attr in 'fields', 'attributes': - storage = [] - storage.extend(getattr(bases[0], attr, ())) - storage.extend(d.get(attr, ())) - assert len(bases) == 1, 'multiple inheritance not allowed' - assert len(storage) == len(set(storage)), 'layout conflict' - d[attr] = tuple(storage) - d.setdefault('abstract', False) - return type.__new__(cls, name, bases, d) - - -class EvalContext(object): - """Holds evaluation time information. Custom attributes can be attached - to it in extensions. - """ - - def __init__(self, environment, template_name=None): - self.environment = environment - if callable(environment.autoescape): - self.autoescape = environment.autoescape(template_name) - else: - self.autoescape = environment.autoescape - self.volatile = False - - def save(self): - return self.__dict__.copy() - - def revert(self, old): - self.__dict__.clear() - self.__dict__.update(old) - - -def get_eval_context(node, ctx): - if ctx is None: - if node.environment is None: - raise RuntimeError('if no eval context is passed, the ' - 'node must have an attached ' - 'environment.') - return EvalContext(node.environment) - return ctx - - -class Node(with_metaclass(NodeType, object)): - """Baseclass for all Jinja2 nodes. There are a number of nodes available - of different types. There are four major types: - - - :class:`Stmt`: statements - - :class:`Expr`: expressions - - :class:`Helper`: helper nodes - - :class:`Template`: the outermost wrapper node - - All nodes have fields and attributes. Fields may be other nodes, lists, - or arbitrary values. Fields are passed to the constructor as regular - positional arguments, attributes as keyword arguments. Each node has - two attributes: `lineno` (the line number of the node) and `environment`. - The `environment` attribute is set at the end of the parsing process for - all nodes automatically. - """ - fields = () - attributes = ('lineno', 'environment') - abstract = True - - def __init__(self, *fields, **attributes): - if self.abstract: - raise TypeError('abstract nodes are not instanciable') - if fields: - if len(fields) != len(self.fields): - if not self.fields: - raise TypeError('%r takes 0 arguments' % - self.__class__.__name__) - raise TypeError('%r takes 0 or %d argument%s' % ( - self.__class__.__name__, - len(self.fields), - len(self.fields) != 1 and 's' or '' - )) - for name, arg in izip(self.fields, fields): - setattr(self, name, arg) - for attr in self.attributes: - setattr(self, attr, attributes.pop(attr, None)) - if attributes: - raise TypeError('unknown attribute %r' % - next(iter(attributes))) - - def iter_fields(self, exclude=None, only=None): - """This method iterates over all fields that are defined and yields - ``(key, value)`` tuples. Per default all fields are returned, but - it's possible to limit that to some fields by providing the `only` - parameter or to exclude some using the `exclude` parameter. Both - should be sets or tuples of field names. - """ - for name in self.fields: - if (exclude is only is None) or \ - (exclude is not None and name not in exclude) or \ - (only is not None and name in only): - try: - yield name, getattr(self, name) - except AttributeError: - pass - - def iter_child_nodes(self, exclude=None, only=None): - """Iterates over all direct child nodes of the node. This iterates - over all fields and yields the values of they are nodes. If the value - of a field is a list all the nodes in that list are returned. - """ - for field, item in self.iter_fields(exclude, only): - if isinstance(item, list): - for n in item: - if isinstance(n, Node): - yield n - elif isinstance(item, Node): - yield item - - def find(self, node_type): - """Find the first node of a given type. If no such node exists the - return value is `None`. - """ - for result in self.find_all(node_type): - return result - - def find_all(self, node_type): - """Find all the nodes of a given type. If the type is a tuple, - the check is performed for any of the tuple items. - """ - for child in self.iter_child_nodes(): - if isinstance(child, node_type): - yield child - for result in child.find_all(node_type): - yield result - - def set_ctx(self, ctx): - """Reset the context of a node and all child nodes. Per default the - parser will all generate nodes that have a 'load' context as it's the - most common one. This method is used in the parser to set assignment - targets and other nodes to a store context. - """ - todo = deque([self]) - while todo: - node = todo.popleft() - if 'ctx' in node.fields: - node.ctx = ctx - todo.extend(node.iter_child_nodes()) - return self - - def set_lineno(self, lineno, override=False): - """Set the line numbers of the node and children.""" - todo = deque([self]) - while todo: - node = todo.popleft() - if 'lineno' in node.attributes: - if node.lineno is None or override: - node.lineno = lineno - todo.extend(node.iter_child_nodes()) - return self - - def set_environment(self, environment): - """Set the environment for all nodes.""" - todo = deque([self]) - while todo: - node = todo.popleft() - node.environment = environment - todo.extend(node.iter_child_nodes()) - return self - - def __eq__(self, other): - return type(self) is type(other) and \ - tuple(self.iter_fields()) == tuple(other.iter_fields()) - - def __ne__(self, other): - return not self.__eq__(other) - - # Restore Python 2 hashing behavior on Python 3 - __hash__ = object.__hash__ - - def __repr__(self): - return '%s(%s)' % ( - self.__class__.__name__, - ', '.join('%s=%r' % (arg, getattr(self, arg, None)) for - arg in self.fields) - ) - - def dump(self): - def _dump(node): - if not isinstance(node, Node): - buf.append(repr(node)) - return - - buf.append('nodes.%s(' % node.__class__.__name__) - if not node.fields: - buf.append(')') - return - for idx, field in enumerate(node.fields): - if idx: - buf.append(', ') - value = getattr(node, field) - if isinstance(value, list): - buf.append('[') - for idx, item in enumerate(value): - if idx: - buf.append(', ') - _dump(item) - buf.append(']') - else: - _dump(value) - buf.append(')') - buf = [] - _dump(self) - return ''.join(buf) - - - -class Stmt(Node): - """Base node for all statements.""" - abstract = True - - -class Helper(Node): - """Nodes that exist in a specific context only.""" - abstract = True - - -class Template(Node): - """Node that represents a template. This must be the outermost node that - is passed to the compiler. - """ - fields = ('body',) - - -class Output(Stmt): - """A node that holds multiple expressions which are then printed out. - This is used both for the `print` statement and the regular template data. - """ - fields = ('nodes',) - - -class Extends(Stmt): - """Represents an extends statement.""" - fields = ('template',) - - -class For(Stmt): - """The for loop. `target` is the target for the iteration (usually a - :class:`Name` or :class:`Tuple`), `iter` the iterable. `body` is a list - of nodes that are used as loop-body, and `else_` a list of nodes for the - `else` block. If no else node exists it has to be an empty list. - - For filtered nodes an expression can be stored as `test`, otherwise `None`. - """ - fields = ('target', 'iter', 'body', 'else_', 'test', 'recursive') - - -class If(Stmt): - """If `test` is true, `body` is rendered, else `else_`.""" - fields = ('test', 'body', 'elif_', 'else_') - - -class Macro(Stmt): - """A macro definition. `name` is the name of the macro, `args` a list of - arguments and `defaults` a list of defaults if there are any. `body` is - a list of nodes for the macro body. - """ - fields = ('name', 'args', 'defaults', 'body') - - -class CallBlock(Stmt): - """Like a macro without a name but a call instead. `call` is called with - the unnamed macro as `caller` argument this node holds. - """ - fields = ('call', 'args', 'defaults', 'body') - - -class FilterBlock(Stmt): - """Node for filter sections.""" - fields = ('body', 'filter') - - -class With(Stmt): - """Specific node for with statements. In older versions of Jinja the - with statement was implemented on the base of the `Scope` node instead. - - .. versionadded:: 2.9.3 - """ - fields = ('targets', 'values', 'body') - - -class Block(Stmt): - """A node that represents a block.""" - fields = ('name', 'body', 'scoped') - - -class Include(Stmt): - """A node that represents the include tag.""" - fields = ('template', 'with_context', 'ignore_missing') - - -class Import(Stmt): - """A node that represents the import tag.""" - fields = ('template', 'target', 'with_context') - - -class FromImport(Stmt): - """A node that represents the from import tag. It's important to not - pass unsafe names to the name attribute. The compiler translates the - attribute lookups directly into getattr calls and does *not* use the - subscript callback of the interface. As exported variables may not - start with double underscores (which the parser asserts) this is not a - problem for regular Jinja code, but if this node is used in an extension - extra care must be taken. - - The list of names may contain tuples if aliases are wanted. - """ - fields = ('template', 'names', 'with_context') - - -class ExprStmt(Stmt): - """A statement that evaluates an expression and discards the result.""" - fields = ('node',) - - -class Assign(Stmt): - """Assigns an expression to a target.""" - fields = ('target', 'node') - - -class AssignBlock(Stmt): - """Assigns a block to a target.""" - fields = ('target', 'filter', 'body') - - -class Expr(Node): - """Baseclass for all expressions.""" - abstract = True - - def as_const(self, eval_ctx=None): - """Return the value of the expression as constant or raise - :exc:`Impossible` if this was not possible. - - An :class:`EvalContext` can be provided, if none is given - a default context is created which requires the nodes to have - an attached environment. - - .. versionchanged:: 2.4 - the `eval_ctx` parameter was added. - """ - raise Impossible() - - def can_assign(self): - """Check if it's possible to assign something to this node.""" - return False - - -class BinExpr(Expr): - """Baseclass for all binary expressions.""" - fields = ('left', 'right') - operator = None - abstract = True - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - # intercepted operators cannot be folded at compile time - if self.environment.sandboxed and \ - self.operator in self.environment.intercepted_binops: - raise Impossible() - f = _binop_to_func[self.operator] - try: - return f(self.left.as_const(eval_ctx), self.right.as_const(eval_ctx)) - except Exception: - raise Impossible() - - -class UnaryExpr(Expr): - """Baseclass for all unary expressions.""" - fields = ('node',) - operator = None - abstract = True - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - # intercepted operators cannot be folded at compile time - if self.environment.sandboxed and \ - self.operator in self.environment.intercepted_unops: - raise Impossible() - f = _uaop_to_func[self.operator] - try: - return f(self.node.as_const(eval_ctx)) - except Exception: - raise Impossible() - - -class Name(Expr): - """Looks up a name or stores a value in a name. - The `ctx` of the node can be one of the following values: - - - `store`: store a value in the name - - `load`: load that name - - `param`: like `store` but if the name was defined as function parameter. - """ - fields = ('name', 'ctx') - - def can_assign(self): - return self.name not in ('true', 'false', 'none', - 'True', 'False', 'None') - - -class NSRef(Expr): - """Reference to a namespace value assignment""" - fields = ('name', 'attr') - - def can_assign(self): - # We don't need any special checks here; NSRef assignments have a - # runtime check to ensure the target is a namespace object which will - # have been checked already as it is created using a normal assignment - # which goes through a `Name` node. - return True - - -class Literal(Expr): - """Baseclass for literals.""" - abstract = True - - -class Const(Literal): - """All constant values. The parser will return this node for simple - constants such as ``42`` or ``"foo"`` but it can be used to store more - complex values such as lists too. Only constants with a safe - representation (objects where ``eval(repr(x)) == x`` is true). - """ - fields = ('value',) - - def as_const(self, eval_ctx=None): - rv = self.value - if PY2 and type(rv) is text_type and \ - self.environment.policies['compiler.ascii_str']: - try: - rv = rv.encode('ascii') - except UnicodeError: - pass - return rv - - @classmethod - def from_untrusted(cls, value, lineno=None, environment=None): - """Return a const object if the value is representable as - constant value in the generated code, otherwise it will raise - an `Impossible` exception. - """ - from .compiler import has_safe_repr - if not has_safe_repr(value): - raise Impossible() - return cls(value, lineno=lineno, environment=environment) - - -class TemplateData(Literal): - """A constant template string.""" - fields = ('data',) - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - if eval_ctx.volatile: - raise Impossible() - if eval_ctx.autoescape: - return Markup(self.data) - return self.data - - -class Tuple(Literal): - """For loop unpacking and some other things like multiple arguments - for subscripts. Like for :class:`Name` `ctx` specifies if the tuple - is used for loading the names or storing. - """ - fields = ('items', 'ctx') - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - return tuple(x.as_const(eval_ctx) for x in self.items) - - def can_assign(self): - for item in self.items: - if not item.can_assign(): - return False - return True - - -class List(Literal): - """Any list literal such as ``[1, 2, 3]``""" - fields = ('items',) - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - return [x.as_const(eval_ctx) for x in self.items] - - -class Dict(Literal): - """Any dict literal such as ``{1: 2, 3: 4}``. The items must be a list of - :class:`Pair` nodes. - """ - fields = ('items',) - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - return dict(x.as_const(eval_ctx) for x in self.items) - - -class Pair(Helper): - """A key, value pair for dicts.""" - fields = ('key', 'value') - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - return self.key.as_const(eval_ctx), self.value.as_const(eval_ctx) - - -class Keyword(Helper): - """A key, value pair for keyword arguments where key is a string.""" - fields = ('key', 'value') - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - return self.key, self.value.as_const(eval_ctx) - - -class CondExpr(Expr): - """A conditional expression (inline if expression). (``{{ - foo if bar else baz }}``) - """ - fields = ('test', 'expr1', 'expr2') - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - if self.test.as_const(eval_ctx): - return self.expr1.as_const(eval_ctx) - - # if we evaluate to an undefined object, we better do that at runtime - if self.expr2 is None: - raise Impossible() - - return self.expr2.as_const(eval_ctx) - - -def args_as_const(node, eval_ctx): - args = [x.as_const(eval_ctx) for x in node.args] - kwargs = dict(x.as_const(eval_ctx) for x in node.kwargs) - - if node.dyn_args is not None: - try: - args.extend(node.dyn_args.as_const(eval_ctx)) - except Exception: - raise Impossible() - - if node.dyn_kwargs is not None: - try: - kwargs.update(node.dyn_kwargs.as_const(eval_ctx)) - except Exception: - raise Impossible() - - return args, kwargs - - -class Filter(Expr): - """This node applies a filter on an expression. `name` is the name of - the filter, the rest of the fields are the same as for :class:`Call`. - - If the `node` of a filter is `None` the contents of the last buffer are - filtered. Buffers are created by macros and filter blocks. - """ - - fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs') - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - - if eval_ctx.volatile or self.node is None: - raise Impossible() - - # we have to be careful here because we call filter_ below. - # if this variable would be called filter, 2to3 would wrap the - # call in a list beause it is assuming we are talking about the - # builtin filter function here which no longer returns a list in - # python 3. because of that, do not rename filter_ to filter! - filter_ = self.environment.filters.get(self.name) - - if filter_ is None or getattr(filter_, 'contextfilter', False): - raise Impossible() - - # We cannot constant handle async filters, so we need to make sure - # to not go down this path. - if ( - eval_ctx.environment.is_async - and getattr(filter_, 'asyncfiltervariant', False) - ): - raise Impossible() - - args, kwargs = args_as_const(self, eval_ctx) - args.insert(0, self.node.as_const(eval_ctx)) - - if getattr(filter_, 'evalcontextfilter', False): - args.insert(0, eval_ctx) - elif getattr(filter_, 'environmentfilter', False): - args.insert(0, self.environment) - - try: - return filter_(*args, **kwargs) - except Exception: - raise Impossible() - - -class Test(Expr): - """Applies a test on an expression. `name` is the name of the test, the - rest of the fields are the same as for :class:`Call`. - """ - - fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs') - - def as_const(self, eval_ctx=None): - test = self.environment.tests.get(self.name) - - if test is None: - raise Impossible() - - eval_ctx = get_eval_context(self, eval_ctx) - args, kwargs = args_as_const(self, eval_ctx) - args.insert(0, self.node.as_const(eval_ctx)) - - try: - return test(*args, **kwargs) - except Exception: - raise Impossible() - - -class Call(Expr): - """Calls an expression. `args` is a list of arguments, `kwargs` a list - of keyword arguments (list of :class:`Keyword` nodes), and `dyn_args` - and `dyn_kwargs` has to be either `None` or a node that is used as - node for dynamic positional (``*args``) or keyword (``**kwargs``) - arguments. - """ - fields = ('node', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs') - - -class Getitem(Expr): - """Get an attribute or item from an expression and prefer the item.""" - fields = ('node', 'arg', 'ctx') - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - if self.ctx != 'load': - raise Impossible() - try: - return self.environment.getitem(self.node.as_const(eval_ctx), - self.arg.as_const(eval_ctx)) - except Exception: - raise Impossible() - - def can_assign(self): - return False - - -class Getattr(Expr): - """Get an attribute or item from an expression that is a ascii-only - bytestring and prefer the attribute. - """ - fields = ('node', 'attr', 'ctx') - - def as_const(self, eval_ctx=None): - if self.ctx != 'load': - raise Impossible() - try: - eval_ctx = get_eval_context(self, eval_ctx) - return self.environment.getattr(self.node.as_const(eval_ctx), - self.attr) - except Exception: - raise Impossible() - - def can_assign(self): - return False - - -class Slice(Expr): - """Represents a slice object. This must only be used as argument for - :class:`Subscript`. - """ - fields = ('start', 'stop', 'step') - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - def const(obj): - if obj is None: - return None - return obj.as_const(eval_ctx) - return slice(const(self.start), const(self.stop), const(self.step)) - - -class Concat(Expr): - """Concatenates the list of expressions provided after converting them to - unicode. - """ - fields = ('nodes',) - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - return ''.join(text_type(x.as_const(eval_ctx)) for x in self.nodes) - - -class Compare(Expr): - """Compares an expression with some other expressions. `ops` must be a - list of :class:`Operand`\\s. - """ - fields = ('expr', 'ops') - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - result = value = self.expr.as_const(eval_ctx) - try: - for op in self.ops: - new_value = op.expr.as_const(eval_ctx) - result = _cmpop_to_func[op.op](value, new_value) - value = new_value - except Exception: - raise Impossible() - return result - - -class Operand(Helper): - """Holds an operator and an expression.""" - fields = ('op', 'expr') - -if __debug__: - Operand.__doc__ += '\nThe following operators are available: ' + \ - ', '.join(sorted('``%s``' % x for x in set(_binop_to_func) | - set(_uaop_to_func) | set(_cmpop_to_func))) - - -class Mul(BinExpr): - """Multiplies the left with the right node.""" - operator = '*' - - -class Div(BinExpr): - """Divides the left by the right node.""" - operator = '/' - - -class FloorDiv(BinExpr): - """Divides the left by the right node and truncates conver the - result into an integer by truncating. - """ - operator = '//' - - -class Add(BinExpr): - """Add the left to the right node.""" - operator = '+' - - -class Sub(BinExpr): - """Subtract the right from the left node.""" - operator = '-' - - -class Mod(BinExpr): - """Left modulo right.""" - operator = '%' - - -class Pow(BinExpr): - """Left to the power of right.""" - operator = '**' - - -class And(BinExpr): - """Short circuited AND.""" - operator = 'and' - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - return self.left.as_const(eval_ctx) and self.right.as_const(eval_ctx) - - -class Or(BinExpr): - """Short circuited OR.""" - operator = 'or' - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - return self.left.as_const(eval_ctx) or self.right.as_const(eval_ctx) - - -class Not(UnaryExpr): - """Negate the expression.""" - operator = 'not' - - -class Neg(UnaryExpr): - """Make the expression negative.""" - operator = '-' - - -class Pos(UnaryExpr): - """Make the expression positive (noop for most expressions)""" - operator = '+' - - -# Helpers for extensions - - -class EnvironmentAttribute(Expr): - """Loads an attribute from the environment object. This is useful for - extensions that want to call a callback stored on the environment. - """ - fields = ('name',) - - -class ExtensionAttribute(Expr): - """Returns the attribute of an extension bound to the environment. - The identifier is the identifier of the :class:`Extension`. - - This node is usually constructed by calling the - :meth:`~jinja2.ext.Extension.attr` method on an extension. - """ - fields = ('identifier', 'name') - - -class ImportedName(Expr): - """If created with an import name the import name is returned on node - access. For example ``ImportedName('cgi.escape')`` returns the `escape` - function from the cgi module on evaluation. Imports are optimized by the - compiler so there is no need to assign them to local variables. - """ - fields = ('importname',) - - -class InternalName(Expr): - """An internal name in the compiler. You cannot create these nodes - yourself but the parser provides a - :meth:`~jinja2.parser.Parser.free_identifier` method that creates - a new identifier for you. This identifier is not available from the - template and is not threated specially by the compiler. - """ - fields = ('name',) - - def __init__(self): - raise TypeError('Can\'t create internal names. Use the ' - '`free_identifier` method on a parser.') - - -class MarkSafe(Expr): - """Mark the wrapped expression as safe (wrap it as `Markup`).""" - fields = ('expr',) - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - return Markup(self.expr.as_const(eval_ctx)) - - -class MarkSafeIfAutoescape(Expr): - """Mark the wrapped expression as safe (wrap it as `Markup`) but - only if autoescaping is active. - - .. versionadded:: 2.5 - """ - fields = ('expr',) - - def as_const(self, eval_ctx=None): - eval_ctx = get_eval_context(self, eval_ctx) - if eval_ctx.volatile: - raise Impossible() - expr = self.expr.as_const(eval_ctx) - if eval_ctx.autoescape: - return Markup(expr) - return expr - - -class ContextReference(Expr): - """Returns the current template context. It can be used like a - :class:`Name` node, with a ``'load'`` ctx and will return the - current :class:`~jinja2.runtime.Context` object. - - Here an example that assigns the current template name to a - variable named `foo`:: - - Assign(Name('foo', ctx='store'), - Getattr(ContextReference(), 'name')) - """ - - -class Continue(Stmt): - """Continue a loop.""" - - -class Break(Stmt): - """Break a loop.""" - - -class Scope(Stmt): - """An artificial scope.""" - fields = ('body',) - - -class OverlayScope(Stmt): - """An overlay scope for extensions. This is a largely unoptimized scope - that however can be used to introduce completely arbitrary variables into - a sub scope from a dictionary or dictionary like object. The `context` - field has to evaluate to a dictionary object. - - Example usage:: - - OverlayScope(context=self.call_method('get_context'), - body=[...]) - - .. versionadded:: 2.10 - """ - fields = ('context', 'body') - - -class EvalContextModifier(Stmt): - """Modifies the eval context. For each option that should be modified, - a :class:`Keyword` has to be added to the :attr:`options` list. - - Example to change the `autoescape` setting:: - - EvalContextModifier(options=[Keyword('autoescape', Const(True))]) - """ - fields = ('options',) - - -class ScopedEvalContextModifier(EvalContextModifier): - """Modifies the eval context and reverts it later. Works exactly like - :class:`EvalContextModifier` but will only modify the - :class:`~jinja2.nodes.EvalContext` for nodes in the :attr:`body`. - """ - fields = ('body',) - - -# make sure nobody creates custom nodes -def _failing_new(*args, **kwargs): - raise TypeError('can\'t create custom node types') -NodeType.__new__ = staticmethod(_failing_new); del _failing_new diff --git a/pipenv/vendor/jinja2/optimizer.py b/pipenv/vendor/jinja2/optimizer.py deleted file mode 100644 index 65ab3ceb..00000000 --- a/pipenv/vendor/jinja2/optimizer.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.optimizer - ~~~~~~~~~~~~~~~~ - - The jinja optimizer is currently trying to constant fold a few expressions - and modify the AST in place so that it should be easier to evaluate it. - - Because the AST does not contain all the scoping information and the - compiler has to find that out, we cannot do all the optimizations we - want. For example loop unrolling doesn't work because unrolled loops would - have a different scoping. - - The solution would be a second syntax tree that has the scoping rules stored. - - :copyright: (c) 2017 by the Jinja Team. - :license: BSD. -""" -from jinja2 import nodes -from jinja2.visitor import NodeTransformer - - -def optimize(node, environment): - """The context hint can be used to perform an static optimization - based on the context given.""" - optimizer = Optimizer(environment) - return optimizer.visit(node) - - -class Optimizer(NodeTransformer): - - def __init__(self, environment): - self.environment = environment - - def fold(self, node, eval_ctx=None): - """Do constant folding.""" - node = self.generic_visit(node) - try: - return nodes.Const.from_untrusted(node.as_const(eval_ctx), - lineno=node.lineno, - environment=self.environment) - except nodes.Impossible: - return node - - visit_Add = visit_Sub = visit_Mul = visit_Div = visit_FloorDiv = \ - visit_Pow = visit_Mod = visit_And = visit_Or = visit_Pos = visit_Neg = \ - visit_Not = visit_Compare = visit_Getitem = visit_Getattr = visit_Call = \ - visit_Filter = visit_Test = visit_CondExpr = fold - del fold diff --git a/pipenv/vendor/jinja2/parser.py b/pipenv/vendor/jinja2/parser.py deleted file mode 100644 index ed00d970..00000000 --- a/pipenv/vendor/jinja2/parser.py +++ /dev/null @@ -1,903 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.parser - ~~~~~~~~~~~~~ - - Implements the template parser. - - :copyright: (c) 2017 by the Jinja Team. - :license: BSD, see LICENSE for more details. -""" -from jinja2 import nodes -from jinja2.exceptions import TemplateSyntaxError, TemplateAssertionError -from jinja2.lexer import describe_token, describe_token_expr -from jinja2._compat import imap - - -_statement_keywords = frozenset(['for', 'if', 'block', 'extends', 'print', - 'macro', 'include', 'from', 'import', - 'set', 'with', 'autoescape']) -_compare_operators = frozenset(['eq', 'ne', 'lt', 'lteq', 'gt', 'gteq']) - -_math_nodes = { - 'add': nodes.Add, - 'sub': nodes.Sub, - 'mul': nodes.Mul, - 'div': nodes.Div, - 'floordiv': nodes.FloorDiv, - 'mod': nodes.Mod, -} - - -class Parser(object): - """This is the central parsing class Jinja2 uses. It's passed to - extensions and can be used to parse expressions or statements. - """ - - def __init__(self, environment, source, name=None, filename=None, - state=None): - self.environment = environment - self.stream = environment._tokenize(source, name, filename, state) - self.name = name - self.filename = filename - self.closed = False - self.extensions = {} - for extension in environment.iter_extensions(): - for tag in extension.tags: - self.extensions[tag] = extension.parse - self._last_identifier = 0 - self._tag_stack = [] - self._end_token_stack = [] - - def fail(self, msg, lineno=None, exc=TemplateSyntaxError): - """Convenience method that raises `exc` with the message, passed - line number or last line number as well as the current name and - filename. - """ - if lineno is None: - lineno = self.stream.current.lineno - raise exc(msg, lineno, self.name, self.filename) - - def _fail_ut_eof(self, name, end_token_stack, lineno): - expected = [] - for exprs in end_token_stack: - expected.extend(imap(describe_token_expr, exprs)) - if end_token_stack: - currently_looking = ' or '.join( - "'%s'" % describe_token_expr(expr) - for expr in end_token_stack[-1]) - else: - currently_looking = None - - if name is None: - message = ['Unexpected end of template.'] - else: - message = ['Encountered unknown tag \'%s\'.' % name] - - if currently_looking: - if name is not None and name in expected: - message.append('You probably made a nesting mistake. Jinja ' - 'is expecting this tag, but currently looking ' - 'for %s.' % currently_looking) - else: - message.append('Jinja was looking for the following tags: ' - '%s.' % currently_looking) - - if self._tag_stack: - message.append('The innermost block that needs to be ' - 'closed is \'%s\'.' % self._tag_stack[-1]) - - self.fail(' '.join(message), lineno) - - def fail_unknown_tag(self, name, lineno=None): - """Called if the parser encounters an unknown tag. Tries to fail - with a human readable error message that could help to identify - the problem. - """ - return self._fail_ut_eof(name, self._end_token_stack, lineno) - - def fail_eof(self, end_tokens=None, lineno=None): - """Like fail_unknown_tag but for end of template situations.""" - stack = list(self._end_token_stack) - if end_tokens is not None: - stack.append(end_tokens) - return self._fail_ut_eof(None, stack, lineno) - - def is_tuple_end(self, extra_end_rules=None): - """Are we at the end of a tuple?""" - if self.stream.current.type in ('variable_end', 'block_end', 'rparen'): - return True - elif extra_end_rules is not None: - return self.stream.current.test_any(extra_end_rules) - return False - - def free_identifier(self, lineno=None): - """Return a new free identifier as :class:`~jinja2.nodes.InternalName`.""" - self._last_identifier += 1 - rv = object.__new__(nodes.InternalName) - nodes.Node.__init__(rv, 'fi%d' % self._last_identifier, lineno=lineno) - return rv - - def parse_statement(self): - """Parse a single statement.""" - token = self.stream.current - if token.type != 'name': - self.fail('tag name expected', token.lineno) - self._tag_stack.append(token.value) - pop_tag = True - try: - if token.value in _statement_keywords: - return getattr(self, 'parse_' + self.stream.current.value)() - if token.value == 'call': - return self.parse_call_block() - if token.value == 'filter': - return self.parse_filter_block() - ext = self.extensions.get(token.value) - if ext is not None: - return ext(self) - - # did not work out, remove the token we pushed by accident - # from the stack so that the unknown tag fail function can - # produce a proper error message. - self._tag_stack.pop() - pop_tag = False - self.fail_unknown_tag(token.value, token.lineno) - finally: - if pop_tag: - self._tag_stack.pop() - - def parse_statements(self, end_tokens, drop_needle=False): - """Parse multiple statements into a list until one of the end tokens - is reached. This is used to parse the body of statements as it also - parses template data if appropriate. The parser checks first if the - current token is a colon and skips it if there is one. Then it checks - for the block end and parses until if one of the `end_tokens` is - reached. Per default the active token in the stream at the end of - the call is the matched end token. If this is not wanted `drop_needle` - can be set to `True` and the end token is removed. - """ - # the first token may be a colon for python compatibility - self.stream.skip_if('colon') - - # in the future it would be possible to add whole code sections - # by adding some sort of end of statement token and parsing those here. - self.stream.expect('block_end') - result = self.subparse(end_tokens) - - # we reached the end of the template too early, the subparser - # does not check for this, so we do that now - if self.stream.current.type == 'eof': - self.fail_eof(end_tokens) - - if drop_needle: - next(self.stream) - return result - - def parse_set(self): - """Parse an assign statement.""" - lineno = next(self.stream).lineno - target = self.parse_assign_target(with_namespace=True) - if self.stream.skip_if('assign'): - expr = self.parse_tuple() - return nodes.Assign(target, expr, lineno=lineno) - filter_node = self.parse_filter(None) - body = self.parse_statements(('name:endset',), - drop_needle=True) - return nodes.AssignBlock(target, filter_node, body, lineno=lineno) - - def parse_for(self): - """Parse a for loop.""" - lineno = self.stream.expect('name:for').lineno - target = self.parse_assign_target(extra_end_rules=('name:in',)) - self.stream.expect('name:in') - iter = self.parse_tuple(with_condexpr=False, - extra_end_rules=('name:recursive',)) - test = None - if self.stream.skip_if('name:if'): - test = self.parse_expression() - recursive = self.stream.skip_if('name:recursive') - body = self.parse_statements(('name:endfor', 'name:else')) - if next(self.stream).value == 'endfor': - else_ = [] - else: - else_ = self.parse_statements(('name:endfor',), drop_needle=True) - return nodes.For(target, iter, body, else_, test, - recursive, lineno=lineno) - - def parse_if(self): - """Parse an if construct.""" - node = result = nodes.If(lineno=self.stream.expect('name:if').lineno) - while 1: - node.test = self.parse_tuple(with_condexpr=False) - node.body = self.parse_statements(('name:elif', 'name:else', - 'name:endif')) - node.elif_ = [] - node.else_ = [] - token = next(self.stream) - if token.test('name:elif'): - node = nodes.If(lineno=self.stream.current.lineno) - result.elif_.append(node) - continue - elif token.test('name:else'): - result.else_ = self.parse_statements(('name:endif',), - drop_needle=True) - break - return result - - def parse_with(self): - node = nodes.With(lineno=next(self.stream).lineno) - targets = [] - values = [] - while self.stream.current.type != 'block_end': - lineno = self.stream.current.lineno - if targets: - self.stream.expect('comma') - target = self.parse_assign_target() - target.set_ctx('param') - targets.append(target) - self.stream.expect('assign') - values.append(self.parse_expression()) - node.targets = targets - node.values = values - node.body = self.parse_statements(('name:endwith',), - drop_needle=True) - return node - - def parse_autoescape(self): - node = nodes.ScopedEvalContextModifier(lineno=next(self.stream).lineno) - node.options = [ - nodes.Keyword('autoescape', self.parse_expression()) - ] - node.body = self.parse_statements(('name:endautoescape',), - drop_needle=True) - return nodes.Scope([node]) - - def parse_block(self): - node = nodes.Block(lineno=next(self.stream).lineno) - node.name = self.stream.expect('name').value - node.scoped = self.stream.skip_if('name:scoped') - - # common problem people encounter when switching from django - # to jinja. we do not support hyphens in block names, so let's - # raise a nicer error message in that case. - if self.stream.current.type == 'sub': - self.fail('Block names in Jinja have to be valid Python ' - 'identifiers and may not contain hyphens, use an ' - 'underscore instead.') - - node.body = self.parse_statements(('name:endblock',), drop_needle=True) - self.stream.skip_if('name:' + node.name) - return node - - def parse_extends(self): - node = nodes.Extends(lineno=next(self.stream).lineno) - node.template = self.parse_expression() - return node - - def parse_import_context(self, node, default): - if self.stream.current.test_any('name:with', 'name:without') and \ - self.stream.look().test('name:context'): - node.with_context = next(self.stream).value == 'with' - self.stream.skip() - else: - node.with_context = default - return node - - def parse_include(self): - node = nodes.Include(lineno=next(self.stream).lineno) - node.template = self.parse_expression() - if self.stream.current.test('name:ignore') and \ - self.stream.look().test('name:missing'): - node.ignore_missing = True - self.stream.skip(2) - else: - node.ignore_missing = False - return self.parse_import_context(node, True) - - def parse_import(self): - node = nodes.Import(lineno=next(self.stream).lineno) - node.template = self.parse_expression() - self.stream.expect('name:as') - node.target = self.parse_assign_target(name_only=True).name - return self.parse_import_context(node, False) - - def parse_from(self): - node = nodes.FromImport(lineno=next(self.stream).lineno) - node.template = self.parse_expression() - self.stream.expect('name:import') - node.names = [] - - def parse_context(): - if self.stream.current.value in ('with', 'without') and \ - self.stream.look().test('name:context'): - node.with_context = next(self.stream).value == 'with' - self.stream.skip() - return True - return False - - while 1: - if node.names: - self.stream.expect('comma') - if self.stream.current.type == 'name': - if parse_context(): - break - target = self.parse_assign_target(name_only=True) - if target.name.startswith('_'): - self.fail('names starting with an underline can not ' - 'be imported', target.lineno, - exc=TemplateAssertionError) - if self.stream.skip_if('name:as'): - alias = self.parse_assign_target(name_only=True) - node.names.append((target.name, alias.name)) - else: - node.names.append(target.name) - if parse_context() or self.stream.current.type != 'comma': - break - else: - self.stream.expect('name') - if not hasattr(node, 'with_context'): - node.with_context = False - return node - - def parse_signature(self, node): - node.args = args = [] - node.defaults = defaults = [] - self.stream.expect('lparen') - while self.stream.current.type != 'rparen': - if args: - self.stream.expect('comma') - arg = self.parse_assign_target(name_only=True) - arg.set_ctx('param') - if self.stream.skip_if('assign'): - defaults.append(self.parse_expression()) - elif defaults: - self.fail('non-default argument follows default argument') - args.append(arg) - self.stream.expect('rparen') - - def parse_call_block(self): - node = nodes.CallBlock(lineno=next(self.stream).lineno) - if self.stream.current.type == 'lparen': - self.parse_signature(node) - else: - node.args = [] - node.defaults = [] - - node.call = self.parse_expression() - if not isinstance(node.call, nodes.Call): - self.fail('expected call', node.lineno) - node.body = self.parse_statements(('name:endcall',), drop_needle=True) - return node - - def parse_filter_block(self): - node = nodes.FilterBlock(lineno=next(self.stream).lineno) - node.filter = self.parse_filter(None, start_inline=True) - node.body = self.parse_statements(('name:endfilter',), - drop_needle=True) - return node - - def parse_macro(self): - node = nodes.Macro(lineno=next(self.stream).lineno) - node.name = self.parse_assign_target(name_only=True).name - self.parse_signature(node) - node.body = self.parse_statements(('name:endmacro',), - drop_needle=True) - return node - - def parse_print(self): - node = nodes.Output(lineno=next(self.stream).lineno) - node.nodes = [] - while self.stream.current.type != 'block_end': - if node.nodes: - self.stream.expect('comma') - node.nodes.append(self.parse_expression()) - return node - - def parse_assign_target(self, with_tuple=True, name_only=False, - extra_end_rules=None, with_namespace=False): - """Parse an assignment target. As Jinja2 allows assignments to - tuples, this function can parse all allowed assignment targets. Per - default assignments to tuples are parsed, that can be disable however - by setting `with_tuple` to `False`. If only assignments to names are - wanted `name_only` can be set to `True`. The `extra_end_rules` - parameter is forwarded to the tuple parsing function. If - `with_namespace` is enabled, a namespace assignment may be parsed. - """ - if with_namespace and self.stream.look().type == 'dot': - token = self.stream.expect('name') - next(self.stream) # dot - attr = self.stream.expect('name') - target = nodes.NSRef(token.value, attr.value, lineno=token.lineno) - elif name_only: - token = self.stream.expect('name') - target = nodes.Name(token.value, 'store', lineno=token.lineno) - else: - if with_tuple: - target = self.parse_tuple(simplified=True, - extra_end_rules=extra_end_rules) - else: - target = self.parse_primary() - target.set_ctx('store') - if not target.can_assign(): - self.fail('can\'t assign to %r' % target.__class__. - __name__.lower(), target.lineno) - return target - - def parse_expression(self, with_condexpr=True): - """Parse an expression. Per default all expressions are parsed, if - the optional `with_condexpr` parameter is set to `False` conditional - expressions are not parsed. - """ - if with_condexpr: - return self.parse_condexpr() - return self.parse_or() - - def parse_condexpr(self): - lineno = self.stream.current.lineno - expr1 = self.parse_or() - while self.stream.skip_if('name:if'): - expr2 = self.parse_or() - if self.stream.skip_if('name:else'): - expr3 = self.parse_condexpr() - else: - expr3 = None - expr1 = nodes.CondExpr(expr2, expr1, expr3, lineno=lineno) - lineno = self.stream.current.lineno - return expr1 - - def parse_or(self): - lineno = self.stream.current.lineno - left = self.parse_and() - while self.stream.skip_if('name:or'): - right = self.parse_and() - left = nodes.Or(left, right, lineno=lineno) - lineno = self.stream.current.lineno - return left - - def parse_and(self): - lineno = self.stream.current.lineno - left = self.parse_not() - while self.stream.skip_if('name:and'): - right = self.parse_not() - left = nodes.And(left, right, lineno=lineno) - lineno = self.stream.current.lineno - return left - - def parse_not(self): - if self.stream.current.test('name:not'): - lineno = next(self.stream).lineno - return nodes.Not(self.parse_not(), lineno=lineno) - return self.parse_compare() - - def parse_compare(self): - lineno = self.stream.current.lineno - expr = self.parse_math1() - ops = [] - while 1: - token_type = self.stream.current.type - if token_type in _compare_operators: - next(self.stream) - ops.append(nodes.Operand(token_type, self.parse_math1())) - elif self.stream.skip_if('name:in'): - ops.append(nodes.Operand('in', self.parse_math1())) - elif (self.stream.current.test('name:not') and - self.stream.look().test('name:in')): - self.stream.skip(2) - ops.append(nodes.Operand('notin', self.parse_math1())) - else: - break - lineno = self.stream.current.lineno - if not ops: - return expr - return nodes.Compare(expr, ops, lineno=lineno) - - def parse_math1(self): - lineno = self.stream.current.lineno - left = self.parse_concat() - while self.stream.current.type in ('add', 'sub'): - cls = _math_nodes[self.stream.current.type] - next(self.stream) - right = self.parse_concat() - left = cls(left, right, lineno=lineno) - lineno = self.stream.current.lineno - return left - - def parse_concat(self): - lineno = self.stream.current.lineno - args = [self.parse_math2()] - while self.stream.current.type == 'tilde': - next(self.stream) - args.append(self.parse_math2()) - if len(args) == 1: - return args[0] - return nodes.Concat(args, lineno=lineno) - - def parse_math2(self): - lineno = self.stream.current.lineno - left = self.parse_pow() - while self.stream.current.type in ('mul', 'div', 'floordiv', 'mod'): - cls = _math_nodes[self.stream.current.type] - next(self.stream) - right = self.parse_pow() - left = cls(left, right, lineno=lineno) - lineno = self.stream.current.lineno - return left - - def parse_pow(self): - lineno = self.stream.current.lineno - left = self.parse_unary() - while self.stream.current.type == 'pow': - next(self.stream) - right = self.parse_unary() - left = nodes.Pow(left, right, lineno=lineno) - lineno = self.stream.current.lineno - return left - - def parse_unary(self, with_filter=True): - token_type = self.stream.current.type - lineno = self.stream.current.lineno - if token_type == 'sub': - next(self.stream) - node = nodes.Neg(self.parse_unary(False), lineno=lineno) - elif token_type == 'add': - next(self.stream) - node = nodes.Pos(self.parse_unary(False), lineno=lineno) - else: - node = self.parse_primary() - node = self.parse_postfix(node) - if with_filter: - node = self.parse_filter_expr(node) - return node - - def parse_primary(self): - token = self.stream.current - if token.type == 'name': - if token.value in ('true', 'false', 'True', 'False'): - node = nodes.Const(token.value in ('true', 'True'), - lineno=token.lineno) - elif token.value in ('none', 'None'): - node = nodes.Const(None, lineno=token.lineno) - else: - node = nodes.Name(token.value, 'load', lineno=token.lineno) - next(self.stream) - elif token.type == 'string': - next(self.stream) - buf = [token.value] - lineno = token.lineno - while self.stream.current.type == 'string': - buf.append(self.stream.current.value) - next(self.stream) - node = nodes.Const(''.join(buf), lineno=lineno) - elif token.type in ('integer', 'float'): - next(self.stream) - node = nodes.Const(token.value, lineno=token.lineno) - elif token.type == 'lparen': - next(self.stream) - node = self.parse_tuple(explicit_parentheses=True) - self.stream.expect('rparen') - elif token.type == 'lbracket': - node = self.parse_list() - elif token.type == 'lbrace': - node = self.parse_dict() - else: - self.fail("unexpected '%s'" % describe_token(token), token.lineno) - return node - - def parse_tuple(self, simplified=False, with_condexpr=True, - extra_end_rules=None, explicit_parentheses=False): - """Works like `parse_expression` but if multiple expressions are - delimited by a comma a :class:`~jinja2.nodes.Tuple` node is created. - This method could also return a regular expression instead of a tuple - if no commas where found. - - The default parsing mode is a full tuple. If `simplified` is `True` - only names and literals are parsed. The `no_condexpr` parameter is - forwarded to :meth:`parse_expression`. - - Because tuples do not require delimiters and may end in a bogus comma - an extra hint is needed that marks the end of a tuple. For example - for loops support tuples between `for` and `in`. In that case the - `extra_end_rules` is set to ``['name:in']``. - - `explicit_parentheses` is true if the parsing was triggered by an - expression in parentheses. This is used to figure out if an empty - tuple is a valid expression or not. - """ - lineno = self.stream.current.lineno - if simplified: - parse = self.parse_primary - elif with_condexpr: - parse = self.parse_expression - else: - parse = lambda: self.parse_expression(with_condexpr=False) - args = [] - is_tuple = False - while 1: - if args: - self.stream.expect('comma') - if self.is_tuple_end(extra_end_rules): - break - args.append(parse()) - if self.stream.current.type == 'comma': - is_tuple = True - else: - break - lineno = self.stream.current.lineno - - if not is_tuple: - if args: - return args[0] - - # if we don't have explicit parentheses, an empty tuple is - # not a valid expression. This would mean nothing (literally - # nothing) in the spot of an expression would be an empty - # tuple. - if not explicit_parentheses: - self.fail('Expected an expression, got \'%s\'' % - describe_token(self.stream.current)) - - return nodes.Tuple(args, 'load', lineno=lineno) - - def parse_list(self): - token = self.stream.expect('lbracket') - items = [] - while self.stream.current.type != 'rbracket': - if items: - self.stream.expect('comma') - if self.stream.current.type == 'rbracket': - break - items.append(self.parse_expression()) - self.stream.expect('rbracket') - return nodes.List(items, lineno=token.lineno) - - def parse_dict(self): - token = self.stream.expect('lbrace') - items = [] - while self.stream.current.type != 'rbrace': - if items: - self.stream.expect('comma') - if self.stream.current.type == 'rbrace': - break - key = self.parse_expression() - self.stream.expect('colon') - value = self.parse_expression() - items.append(nodes.Pair(key, value, lineno=key.lineno)) - self.stream.expect('rbrace') - return nodes.Dict(items, lineno=token.lineno) - - def parse_postfix(self, node): - while 1: - token_type = self.stream.current.type - if token_type == 'dot' or token_type == 'lbracket': - node = self.parse_subscript(node) - # calls are valid both after postfix expressions (getattr - # and getitem) as well as filters and tests - elif token_type == 'lparen': - node = self.parse_call(node) - else: - break - return node - - def parse_filter_expr(self, node): - while 1: - token_type = self.stream.current.type - if token_type == 'pipe': - node = self.parse_filter(node) - elif token_type == 'name' and self.stream.current.value == 'is': - node = self.parse_test(node) - # calls are valid both after postfix expressions (getattr - # and getitem) as well as filters and tests - elif token_type == 'lparen': - node = self.parse_call(node) - else: - break - return node - - def parse_subscript(self, node): - token = next(self.stream) - if token.type == 'dot': - attr_token = self.stream.current - next(self.stream) - if attr_token.type == 'name': - return nodes.Getattr(node, attr_token.value, 'load', - lineno=token.lineno) - elif attr_token.type != 'integer': - self.fail('expected name or number', attr_token.lineno) - arg = nodes.Const(attr_token.value, lineno=attr_token.lineno) - return nodes.Getitem(node, arg, 'load', lineno=token.lineno) - if token.type == 'lbracket': - args = [] - while self.stream.current.type != 'rbracket': - if args: - self.stream.expect('comma') - args.append(self.parse_subscribed()) - self.stream.expect('rbracket') - if len(args) == 1: - arg = args[0] - else: - arg = nodes.Tuple(args, 'load', lineno=token.lineno) - return nodes.Getitem(node, arg, 'load', lineno=token.lineno) - self.fail('expected subscript expression', self.lineno) - - def parse_subscribed(self): - lineno = self.stream.current.lineno - - if self.stream.current.type == 'colon': - next(self.stream) - args = [None] - else: - node = self.parse_expression() - if self.stream.current.type != 'colon': - return node - next(self.stream) - args = [node] - - if self.stream.current.type == 'colon': - args.append(None) - elif self.stream.current.type not in ('rbracket', 'comma'): - args.append(self.parse_expression()) - else: - args.append(None) - - if self.stream.current.type == 'colon': - next(self.stream) - if self.stream.current.type not in ('rbracket', 'comma'): - args.append(self.parse_expression()) - else: - args.append(None) - else: - args.append(None) - - return nodes.Slice(lineno=lineno, *args) - - def parse_call(self, node): - token = self.stream.expect('lparen') - args = [] - kwargs = [] - dyn_args = dyn_kwargs = None - require_comma = False - - def ensure(expr): - if not expr: - self.fail('invalid syntax for function call expression', - token.lineno) - - while self.stream.current.type != 'rparen': - if require_comma: - self.stream.expect('comma') - # support for trailing comma - if self.stream.current.type == 'rparen': - break - if self.stream.current.type == 'mul': - ensure(dyn_args is None and dyn_kwargs is None) - next(self.stream) - dyn_args = self.parse_expression() - elif self.stream.current.type == 'pow': - ensure(dyn_kwargs is None) - next(self.stream) - dyn_kwargs = self.parse_expression() - else: - ensure(dyn_args is None and dyn_kwargs is None) - if self.stream.current.type == 'name' and \ - self.stream.look().type == 'assign': - key = self.stream.current.value - self.stream.skip(2) - value = self.parse_expression() - kwargs.append(nodes.Keyword(key, value, - lineno=value.lineno)) - else: - ensure(not kwargs) - args.append(self.parse_expression()) - - require_comma = True - self.stream.expect('rparen') - - if node is None: - return args, kwargs, dyn_args, dyn_kwargs - return nodes.Call(node, args, kwargs, dyn_args, dyn_kwargs, - lineno=token.lineno) - - def parse_filter(self, node, start_inline=False): - while self.stream.current.type == 'pipe' or start_inline: - if not start_inline: - next(self.stream) - token = self.stream.expect('name') - name = token.value - while self.stream.current.type == 'dot': - next(self.stream) - name += '.' + self.stream.expect('name').value - if self.stream.current.type == 'lparen': - args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None) - else: - args = [] - kwargs = [] - dyn_args = dyn_kwargs = None - node = nodes.Filter(node, name, args, kwargs, dyn_args, - dyn_kwargs, lineno=token.lineno) - start_inline = False - return node - - def parse_test(self, node): - token = next(self.stream) - if self.stream.current.test('name:not'): - next(self.stream) - negated = True - else: - negated = False - name = self.stream.expect('name').value - while self.stream.current.type == 'dot': - next(self.stream) - name += '.' + self.stream.expect('name').value - dyn_args = dyn_kwargs = None - kwargs = [] - if self.stream.current.type == 'lparen': - args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None) - elif (self.stream.current.type in ('name', 'string', 'integer', - 'float', 'lparen', 'lbracket', - 'lbrace') and not - self.stream.current.test_any('name:else', 'name:or', - 'name:and')): - if self.stream.current.test('name:is'): - self.fail('You cannot chain multiple tests with is') - args = [self.parse_primary()] - else: - args = [] - node = nodes.Test(node, name, args, kwargs, dyn_args, - dyn_kwargs, lineno=token.lineno) - if negated: - node = nodes.Not(node, lineno=token.lineno) - return node - - def subparse(self, end_tokens=None): - body = [] - data_buffer = [] - add_data = data_buffer.append - - if end_tokens is not None: - self._end_token_stack.append(end_tokens) - - def flush_data(): - if data_buffer: - lineno = data_buffer[0].lineno - body.append(nodes.Output(data_buffer[:], lineno=lineno)) - del data_buffer[:] - - try: - while self.stream: - token = self.stream.current - if token.type == 'data': - if token.value: - add_data(nodes.TemplateData(token.value, - lineno=token.lineno)) - next(self.stream) - elif token.type == 'variable_begin': - next(self.stream) - add_data(self.parse_tuple(with_condexpr=True)) - self.stream.expect('variable_end') - elif token.type == 'block_begin': - flush_data() - next(self.stream) - if end_tokens is not None and \ - self.stream.current.test_any(*end_tokens): - return body - rv = self.parse_statement() - if isinstance(rv, list): - body.extend(rv) - else: - body.append(rv) - self.stream.expect('block_end') - else: - raise AssertionError('internal parsing error') - - flush_data() - finally: - if end_tokens is not None: - self._end_token_stack.pop() - - return body - - def parse(self): - """Parse the whole template into a `Template` node.""" - result = nodes.Template(self.subparse(), lineno=1) - result.set_environment(self.environment) - return result diff --git a/pipenv/vendor/jinja2/runtime.py b/pipenv/vendor/jinja2/runtime.py deleted file mode 100644 index f9d7a680..00000000 --- a/pipenv/vendor/jinja2/runtime.py +++ /dev/null @@ -1,813 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.runtime - ~~~~~~~~~~~~~~ - - Runtime helpers. - - :copyright: (c) 2017 by the Jinja Team. - :license: BSD. -""" -import sys - -from itertools import chain -from types import MethodType - -from jinja2.nodes import EvalContext, _context_function_types -from jinja2.utils import Markup, soft_unicode, escape, missing, concat, \ - internalcode, object_type_repr, evalcontextfunction, Namespace -from jinja2.exceptions import UndefinedError, TemplateRuntimeError, \ - TemplateNotFound -from jinja2._compat import imap, text_type, iteritems, \ - implements_iterator, implements_to_string, string_types, PY2, \ - with_metaclass - - -# these variables are exported to the template runtime -__all__ = ['LoopContext', 'TemplateReference', 'Macro', 'Markup', - 'TemplateRuntimeError', 'missing', 'concat', 'escape', - 'markup_join', 'unicode_join', 'to_string', 'identity', - 'TemplateNotFound', 'Namespace'] - -#: the name of the function that is used to convert something into -#: a string. We can just use the text type here. -to_string = text_type - -#: the identity function. Useful for certain things in the environment -identity = lambda x: x - -_first_iteration = object() -_last_iteration = object() - - -def markup_join(seq): - """Concatenation that escapes if necessary and converts to unicode.""" - buf = [] - iterator = imap(soft_unicode, seq) - for arg in iterator: - buf.append(arg) - if hasattr(arg, '__html__'): - return Markup(u'').join(chain(buf, iterator)) - return concat(buf) - - -def unicode_join(seq): - """Simple args to unicode conversion and concatenation.""" - return concat(imap(text_type, seq)) - - -def new_context(environment, template_name, blocks, vars=None, - shared=None, globals=None, locals=None): - """Internal helper to for context creation.""" - if vars is None: - vars = {} - if shared: - parent = vars - else: - parent = dict(globals or (), **vars) - if locals: - # if the parent is shared a copy should be created because - # we don't want to modify the dict passed - if shared: - parent = dict(parent) - for key, value in iteritems(locals): - if value is not missing: - parent[key] = value - return environment.context_class(environment, parent, template_name, - blocks) - - -class TemplateReference(object): - """The `self` in templates.""" - - def __init__(self, context): - self.__context = context - - def __getitem__(self, name): - blocks = self.__context.blocks[name] - return BlockReference(name, self.__context, blocks, 0) - - def __repr__(self): - return '<%s %r>' % ( - self.__class__.__name__, - self.__context.name - ) - - -def _get_func(x): - return getattr(x, '__func__', x) - - -class ContextMeta(type): - - def __new__(cls, name, bases, d): - rv = type.__new__(cls, name, bases, d) - if bases == (): - return rv - - resolve = _get_func(rv.resolve) - default_resolve = _get_func(Context.resolve) - resolve_or_missing = _get_func(rv.resolve_or_missing) - default_resolve_or_missing = _get_func(Context.resolve_or_missing) - - # If we have a changed resolve but no changed default or missing - # resolve we invert the call logic. - if resolve is not default_resolve and \ - resolve_or_missing is default_resolve_or_missing: - rv._legacy_resolve_mode = True - elif resolve is default_resolve and \ - resolve_or_missing is default_resolve_or_missing: - rv._fast_resolve_mode = True - - return rv - - -def resolve_or_missing(context, key, missing=missing): - if key in context.vars: - return context.vars[key] - if key in context.parent: - return context.parent[key] - return missing - - -class Context(with_metaclass(ContextMeta)): - """The template context holds the variables of a template. It stores the - values passed to the template and also the names the template exports. - Creating instances is neither supported nor useful as it's created - automatically at various stages of the template evaluation and should not - be created by hand. - - The context is immutable. Modifications on :attr:`parent` **must not** - happen and modifications on :attr:`vars` are allowed from generated - template code only. Template filters and global functions marked as - :func:`contextfunction`\\s get the active context passed as first argument - and are allowed to access the context read-only. - - The template context supports read only dict operations (`get`, - `keys`, `values`, `items`, `iterkeys`, `itervalues`, `iteritems`, - `__getitem__`, `__contains__`). Additionally there is a :meth:`resolve` - method that doesn't fail with a `KeyError` but returns an - :class:`Undefined` object for missing variables. - """ - # XXX: we want to eventually make this be a deprecation warning and - # remove it. - _legacy_resolve_mode = False - _fast_resolve_mode = False - - def __init__(self, environment, parent, name, blocks): - self.parent = parent - self.vars = {} - self.environment = environment - self.eval_ctx = EvalContext(self.environment, name) - self.exported_vars = set() - self.name = name - - # create the initial mapping of blocks. Whenever template inheritance - # takes place the runtime will update this mapping with the new blocks - # from the template. - self.blocks = dict((k, [v]) for k, v in iteritems(blocks)) - - # In case we detect the fast resolve mode we can set up an alias - # here that bypasses the legacy code logic. - if self._fast_resolve_mode: - self.resolve_or_missing = MethodType(resolve_or_missing, self) - - def super(self, name, current): - """Render a parent block.""" - try: - blocks = self.blocks[name] - index = blocks.index(current) + 1 - blocks[index] - except LookupError: - return self.environment.undefined('there is no parent block ' - 'called %r.' % name, - name='super') - return BlockReference(name, self, blocks, index) - - def get(self, key, default=None): - """Returns an item from the template context, if it doesn't exist - `default` is returned. - """ - try: - return self[key] - except KeyError: - return default - - def resolve(self, key): - """Looks up a variable like `__getitem__` or `get` but returns an - :class:`Undefined` object with the name of the name looked up. - """ - if self._legacy_resolve_mode: - rv = resolve_or_missing(self, key) - else: - rv = self.resolve_or_missing(key) - if rv is missing: - return self.environment.undefined(name=key) - return rv - - def resolve_or_missing(self, key): - """Resolves a variable like :meth:`resolve` but returns the - special `missing` value if it cannot be found. - """ - if self._legacy_resolve_mode: - rv = self.resolve(key) - if isinstance(rv, Undefined): - rv = missing - return rv - return resolve_or_missing(self, key) - - def get_exported(self): - """Get a new dict with the exported variables.""" - return dict((k, self.vars[k]) for k in self.exported_vars) - - def get_all(self): - """Return the complete context as dict including the exported - variables. For optimizations reasons this might not return an - actual copy so be careful with using it. - """ - if not self.vars: - return self.parent - if not self.parent: - return self.vars - return dict(self.parent, **self.vars) - - @internalcode - def call(__self, __obj, *args, **kwargs): - """Call the callable with the arguments and keyword arguments - provided but inject the active context or environment as first - argument if the callable is a :func:`contextfunction` or - :func:`environmentfunction`. - """ - if __debug__: - __traceback_hide__ = True # noqa - - # Allow callable classes to take a context - if hasattr(__obj, '__call__'): - fn = __obj.__call__ - for fn_type in ('contextfunction', - 'evalcontextfunction', - 'environmentfunction'): - if hasattr(fn, fn_type): - __obj = fn - break - - if isinstance(__obj, _context_function_types): - if getattr(__obj, 'contextfunction', 0): - args = (__self,) + args - elif getattr(__obj, 'evalcontextfunction', 0): - args = (__self.eval_ctx,) + args - elif getattr(__obj, 'environmentfunction', 0): - args = (__self.environment,) + args - try: - return __obj(*args, **kwargs) - except StopIteration: - return __self.environment.undefined('value was undefined because ' - 'a callable raised a ' - 'StopIteration exception') - - def derived(self, locals=None): - """Internal helper function to create a derived context. This is - used in situations where the system needs a new context in the same - template that is independent. - """ - context = new_context(self.environment, self.name, {}, - self.get_all(), True, None, locals) - context.eval_ctx = self.eval_ctx - context.blocks.update((k, list(v)) for k, v in iteritems(self.blocks)) - return context - - def _all(meth): - proxy = lambda self: getattr(self.get_all(), meth)() - proxy.__doc__ = getattr(dict, meth).__doc__ - proxy.__name__ = meth - return proxy - - keys = _all('keys') - values = _all('values') - items = _all('items') - - # not available on python 3 - if PY2: - iterkeys = _all('iterkeys') - itervalues = _all('itervalues') - iteritems = _all('iteritems') - del _all - - def __contains__(self, name): - return name in self.vars or name in self.parent - - def __getitem__(self, key): - """Lookup a variable or raise `KeyError` if the variable is - undefined. - """ - item = self.resolve_or_missing(key) - if item is missing: - raise KeyError(key) - return item - - def __repr__(self): - return '<%s %s of %r>' % ( - self.__class__.__name__, - repr(self.get_all()), - self.name - ) - - -# register the context as mapping if possible -try: - from collections import Mapping - Mapping.register(Context) -except ImportError: - pass - - -class BlockReference(object): - """One block on a template reference.""" - - def __init__(self, name, context, stack, depth): - self.name = name - self._context = context - self._stack = stack - self._depth = depth - - @property - def super(self): - """Super the block.""" - if self._depth + 1 >= len(self._stack): - return self._context.environment. \ - undefined('there is no parent block called %r.' % - self.name, name='super') - return BlockReference(self.name, self._context, self._stack, - self._depth + 1) - - @internalcode - def __call__(self): - rv = concat(self._stack[self._depth](self._context)) - if self._context.eval_ctx.autoescape: - rv = Markup(rv) - return rv - - -class LoopContextBase(object): - """A loop context for dynamic iteration.""" - - _before = _first_iteration - _current = _first_iteration - _after = _last_iteration - _length = None - - def __init__(self, undefined, recurse=None, depth0=0): - self._undefined = undefined - self._recurse = recurse - self.index0 = -1 - self.depth0 = depth0 - self._last_checked_value = missing - - def cycle(self, *args): - """Cycles among the arguments with the current loop index.""" - if not args: - raise TypeError('no items for cycling given') - return args[self.index0 % len(args)] - - def changed(self, *value): - """Checks whether the value has changed since the last call.""" - if self._last_checked_value != value: - self._last_checked_value = value - return True - return False - - first = property(lambda x: x.index0 == 0) - last = property(lambda x: x._after is _last_iteration) - index = property(lambda x: x.index0 + 1) - revindex = property(lambda x: x.length - x.index0) - revindex0 = property(lambda x: x.length - x.index) - depth = property(lambda x: x.depth0 + 1) - - @property - def previtem(self): - if self._before is _first_iteration: - return self._undefined('there is no previous item') - return self._before - - @property - def nextitem(self): - if self._after is _last_iteration: - return self._undefined('there is no next item') - return self._after - - def __len__(self): - return self.length - - @internalcode - def loop(self, iterable): - if self._recurse is None: - raise TypeError('Tried to call non recursive loop. Maybe you ' - "forgot the 'recursive' modifier.") - return self._recurse(iterable, self._recurse, self.depth0 + 1) - - # a nifty trick to enhance the error message if someone tried to call - # the the loop without or with too many arguments. - __call__ = loop - del loop - - def __repr__(self): - return '<%s %r/%r>' % ( - self.__class__.__name__, - self.index, - self.length - ) - - -class LoopContext(LoopContextBase): - - def __init__(self, iterable, undefined, recurse=None, depth0=0): - LoopContextBase.__init__(self, undefined, recurse, depth0) - self._iterator = iter(iterable) - - # try to get the length of the iterable early. This must be done - # here because there are some broken iterators around where there - # __len__ is the number of iterations left (i'm looking at your - # listreverseiterator!). - try: - self._length = len(iterable) - except (TypeError, AttributeError): - self._length = None - self._after = self._safe_next() - - @property - def length(self): - if self._length is None: - # if was not possible to get the length of the iterator when - # the loop context was created (ie: iterating over a generator) - # we have to convert the iterable into a sequence and use the - # length of that + the number of iterations so far. - iterable = tuple(self._iterator) - self._iterator = iter(iterable) - iterations_done = self.index0 + 2 - self._length = len(iterable) + iterations_done - return self._length - - def __iter__(self): - return LoopContextIterator(self) - - def _safe_next(self): - try: - return next(self._iterator) - except StopIteration: - return _last_iteration - - -@implements_iterator -class LoopContextIterator(object): - """The iterator for a loop context.""" - __slots__ = ('context',) - - def __init__(self, context): - self.context = context - - def __iter__(self): - return self - - def __next__(self): - ctx = self.context - ctx.index0 += 1 - if ctx._after is _last_iteration: - raise StopIteration() - ctx._before = ctx._current - ctx._current = ctx._after - ctx._after = ctx._safe_next() - return ctx._current, ctx - - -class Macro(object): - """Wraps a macro function.""" - - def __init__(self, environment, func, name, arguments, - catch_kwargs, catch_varargs, caller, - default_autoescape=None): - self._environment = environment - self._func = func - self._argument_count = len(arguments) - self.name = name - self.arguments = arguments - self.catch_kwargs = catch_kwargs - self.catch_varargs = catch_varargs - self.caller = caller - self.explicit_caller = 'caller' in arguments - if default_autoescape is None: - default_autoescape = environment.autoescape - self._default_autoescape = default_autoescape - - @internalcode - @evalcontextfunction - def __call__(self, *args, **kwargs): - # This requires a bit of explanation, In the past we used to - # decide largely based on compile-time information if a macro is - # safe or unsafe. While there was a volatile mode it was largely - # unused for deciding on escaping. This turns out to be - # problemtic for macros because if a macro is safe or not not so - # much depends on the escape mode when it was defined but when it - # was used. - # - # Because however we export macros from the module system and - # there are historic callers that do not pass an eval context (and - # will continue to not pass one), we need to perform an instance - # check here. - # - # This is considered safe because an eval context is not a valid - # argument to callables otherwise anwyays. Worst case here is - # that if no eval context is passed we fall back to the compile - # time autoescape flag. - if args and isinstance(args[0], EvalContext): - autoescape = args[0].autoescape - args = args[1:] - else: - autoescape = self._default_autoescape - - # try to consume the positional arguments - arguments = list(args[:self._argument_count]) - off = len(arguments) - - # For information why this is necessary refer to the handling - # of caller in the `macro_body` handler in the compiler. - found_caller = False - - # if the number of arguments consumed is not the number of - # arguments expected we start filling in keyword arguments - # and defaults. - if off != self._argument_count: - for idx, name in enumerate(self.arguments[len(arguments):]): - try: - value = kwargs.pop(name) - except KeyError: - value = missing - if name == 'caller': - found_caller = True - arguments.append(value) - else: - found_caller = self.explicit_caller - - # it's important that the order of these arguments does not change - # if not also changed in the compiler's `function_scoping` method. - # the order is caller, keyword arguments, positional arguments! - if self.caller and not found_caller: - caller = kwargs.pop('caller', None) - if caller is None: - caller = self._environment.undefined('No caller defined', - name='caller') - arguments.append(caller) - - if self.catch_kwargs: - arguments.append(kwargs) - elif kwargs: - if 'caller' in kwargs: - raise TypeError('macro %r was invoked with two values for ' - 'the special caller argument. This is ' - 'most likely a bug.' % self.name) - raise TypeError('macro %r takes no keyword argument %r' % - (self.name, next(iter(kwargs)))) - if self.catch_varargs: - arguments.append(args[self._argument_count:]) - elif len(args) > self._argument_count: - raise TypeError('macro %r takes not more than %d argument(s)' % - (self.name, len(self.arguments))) - - return self._invoke(arguments, autoescape) - - def _invoke(self, arguments, autoescape): - """This method is being swapped out by the async implementation.""" - rv = self._func(*arguments) - if autoescape: - rv = Markup(rv) - return rv - - def __repr__(self): - return '<%s %s>' % ( - self.__class__.__name__, - self.name is None and 'anonymous' or repr(self.name) - ) - - -@implements_to_string -class Undefined(object): - """The default undefined type. This undefined type can be printed and - iterated over, but every other access will raise an :exc:`jinja2.exceptions.UndefinedError`: - - >>> foo = Undefined(name='foo') - >>> str(foo) - '' - >>> not foo - True - >>> foo + 42 - Traceback (most recent call last): - ... - jinja2.exceptions.UndefinedError: 'foo' is undefined - """ - __slots__ = ('_undefined_hint', '_undefined_obj', '_undefined_name', - '_undefined_exception') - - def __init__(self, hint=None, obj=missing, name=None, exc=UndefinedError): - self._undefined_hint = hint - self._undefined_obj = obj - self._undefined_name = name - self._undefined_exception = exc - - @internalcode - def _fail_with_undefined_error(self, *args, **kwargs): - """Regular callback function for undefined objects that raises an - `jinja2.exceptions.UndefinedError` on call. - """ - if self._undefined_hint is None: - if self._undefined_obj is missing: - hint = '%r is undefined' % self._undefined_name - elif not isinstance(self._undefined_name, string_types): - hint = '%s has no element %r' % ( - object_type_repr(self._undefined_obj), - self._undefined_name - ) - else: - hint = '%r has no attribute %r' % ( - object_type_repr(self._undefined_obj), - self._undefined_name - ) - else: - hint = self._undefined_hint - raise self._undefined_exception(hint) - - @internalcode - def __getattr__(self, name): - if name[:2] == '__': - raise AttributeError(name) - return self._fail_with_undefined_error() - - __add__ = __radd__ = __mul__ = __rmul__ = __div__ = __rdiv__ = \ - __truediv__ = __rtruediv__ = __floordiv__ = __rfloordiv__ = \ - __mod__ = __rmod__ = __pos__ = __neg__ = __call__ = \ - __getitem__ = __lt__ = __le__ = __gt__ = __ge__ = __int__ = \ - __float__ = __complex__ = __pow__ = __rpow__ = __sub__ = \ - __rsub__ = _fail_with_undefined_error - - def __eq__(self, other): - return type(self) is type(other) - - def __ne__(self, other): - return not self.__eq__(other) - - def __hash__(self): - return id(type(self)) - - def __str__(self): - return u'' - - def __len__(self): - return 0 - - def __iter__(self): - if 0: - yield None - - def __nonzero__(self): - return False - __bool__ = __nonzero__ - - def __repr__(self): - return 'Undefined' - - -def make_logging_undefined(logger=None, base=None): - """Given a logger object this returns a new undefined class that will - log certain failures. It will log iterations and printing. If no - logger is given a default logger is created. - - Example:: - - logger = logging.getLogger(__name__) - LoggingUndefined = make_logging_undefined( - logger=logger, - base=Undefined - ) - - .. versionadded:: 2.8 - - :param logger: the logger to use. If not provided, a default logger - is created. - :param base: the base class to add logging functionality to. This - defaults to :class:`Undefined`. - """ - if logger is None: - import logging - logger = logging.getLogger(__name__) - logger.addHandler(logging.StreamHandler(sys.stderr)) - if base is None: - base = Undefined - - def _log_message(undef): - if undef._undefined_hint is None: - if undef._undefined_obj is missing: - hint = '%s is undefined' % undef._undefined_name - elif not isinstance(undef._undefined_name, string_types): - hint = '%s has no element %s' % ( - object_type_repr(undef._undefined_obj), - undef._undefined_name) - else: - hint = '%s has no attribute %s' % ( - object_type_repr(undef._undefined_obj), - undef._undefined_name) - else: - hint = undef._undefined_hint - logger.warning('Template variable warning: %s', hint) - - class LoggingUndefined(base): - - def _fail_with_undefined_error(self, *args, **kwargs): - try: - return base._fail_with_undefined_error(self, *args, **kwargs) - except self._undefined_exception as e: - logger.error('Template variable error: %s', str(e)) - raise e - - def __str__(self): - rv = base.__str__(self) - _log_message(self) - return rv - - def __iter__(self): - rv = base.__iter__(self) - _log_message(self) - return rv - - if PY2: - def __nonzero__(self): - rv = base.__nonzero__(self) - _log_message(self) - return rv - - def __unicode__(self): - rv = base.__unicode__(self) - _log_message(self) - return rv - else: - def __bool__(self): - rv = base.__bool__(self) - _log_message(self) - return rv - - return LoggingUndefined - - -@implements_to_string -class DebugUndefined(Undefined): - """An undefined that returns the debug info when printed. - - >>> foo = DebugUndefined(name='foo') - >>> str(foo) - '{{ foo }}' - >>> not foo - True - >>> foo + 42 - Traceback (most recent call last): - ... - jinja2.exceptions.UndefinedError: 'foo' is undefined - """ - __slots__ = () - - def __str__(self): - if self._undefined_hint is None: - if self._undefined_obj is missing: - return u'{{ %s }}' % self._undefined_name - return '{{ no such element: %s[%r] }}' % ( - object_type_repr(self._undefined_obj), - self._undefined_name - ) - return u'{{ undefined value printed: %s }}' % self._undefined_hint - - -@implements_to_string -class StrictUndefined(Undefined): - """An undefined that barks on print and iteration as well as boolean - tests and all kinds of comparisons. In other words: you can do nothing - with it except checking if it's defined using the `defined` test. - - >>> foo = StrictUndefined(name='foo') - >>> str(foo) - Traceback (most recent call last): - ... - jinja2.exceptions.UndefinedError: 'foo' is undefined - >>> not foo - Traceback (most recent call last): - ... - jinja2.exceptions.UndefinedError: 'foo' is undefined - >>> foo + 42 - Traceback (most recent call last): - ... - jinja2.exceptions.UndefinedError: 'foo' is undefined - """ - __slots__ = () - __iter__ = __str__ = __len__ = __nonzero__ = __eq__ = \ - __ne__ = __bool__ = __hash__ = \ - Undefined._fail_with_undefined_error - - -# remove remaining slots attributes, after the metaclass did the magic they -# are unneeded and irritating as they contain wrong data for the subclasses. -del Undefined.__slots__, DebugUndefined.__slots__, StrictUndefined.__slots__ diff --git a/pipenv/vendor/jinja2/sandbox.py b/pipenv/vendor/jinja2/sandbox.py deleted file mode 100644 index 93fb9d45..00000000 --- a/pipenv/vendor/jinja2/sandbox.py +++ /dev/null @@ -1,475 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.sandbox - ~~~~~~~~~~~~~~ - - Adds a sandbox layer to Jinja as it was the default behavior in the old - Jinja 1 releases. This sandbox is slightly different from Jinja 1 as the - default behavior is easier to use. - - The behavior can be changed by subclassing the environment. - - :copyright: (c) 2017 by the Jinja Team. - :license: BSD. -""" -import types -import operator -from collections import Mapping -from jinja2.environment import Environment -from jinja2.exceptions import SecurityError -from jinja2._compat import string_types, PY2 -from jinja2.utils import Markup - -from markupsafe import EscapeFormatter -from string import Formatter - - -#: maximum number of items a range may produce -MAX_RANGE = 100000 - -#: attributes of function objects that are considered unsafe. -if PY2: - UNSAFE_FUNCTION_ATTRIBUTES = set(['func_closure', 'func_code', 'func_dict', - 'func_defaults', 'func_globals']) -else: - # On versions > python 2 the special attributes on functions are gone, - # but they remain on methods and generators for whatever reason. - UNSAFE_FUNCTION_ATTRIBUTES = set() - - -#: unsafe method attributes. function attributes are unsafe for methods too -UNSAFE_METHOD_ATTRIBUTES = set(['im_class', 'im_func', 'im_self']) - -#: unsafe generator attirbutes. -UNSAFE_GENERATOR_ATTRIBUTES = set(['gi_frame', 'gi_code']) - -#: unsafe attributes on coroutines -UNSAFE_COROUTINE_ATTRIBUTES = set(['cr_frame', 'cr_code']) - -#: unsafe attributes on async generators -UNSAFE_ASYNC_GENERATOR_ATTRIBUTES = set(['ag_code', 'ag_frame']) - -import warnings - -# make sure we don't warn in python 2.6 about stuff we don't care about -warnings.filterwarnings('ignore', 'the sets module', DeprecationWarning, - module='jinja2.sandbox') - -from collections import deque - -_mutable_set_types = (set,) -_mutable_mapping_types = (dict,) -_mutable_sequence_types = (list,) - - -# on python 2.x we can register the user collection types -try: - from UserDict import UserDict, DictMixin - from UserList import UserList - _mutable_mapping_types += (UserDict, DictMixin) - _mutable_set_types += (UserList,) -except ImportError: - pass - -# if sets is still available, register the mutable set from there as well -try: - from sets import Set - _mutable_set_types += (Set,) -except ImportError: - pass - -#: register Python 2.6 abstract base classes -from collections import MutableSet, MutableMapping, MutableSequence -_mutable_set_types += (MutableSet,) -_mutable_mapping_types += (MutableMapping,) -_mutable_sequence_types += (MutableSequence,) - - -_mutable_spec = ( - (_mutable_set_types, frozenset([ - 'add', 'clear', 'difference_update', 'discard', 'pop', 'remove', - 'symmetric_difference_update', 'update' - ])), - (_mutable_mapping_types, frozenset([ - 'clear', 'pop', 'popitem', 'setdefault', 'update' - ])), - (_mutable_sequence_types, frozenset([ - 'append', 'reverse', 'insert', 'sort', 'extend', 'remove' - ])), - (deque, frozenset([ - 'append', 'appendleft', 'clear', 'extend', 'extendleft', 'pop', - 'popleft', 'remove', 'rotate' - ])) -) - - -class _MagicFormatMapping(Mapping): - """This class implements a dummy wrapper to fix a bug in the Python - standard library for string formatting. - - See https://bugs.python.org/issue13598 for information about why - this is necessary. - """ - - def __init__(self, args, kwargs): - self._args = args - self._kwargs = kwargs - self._last_index = 0 - - def __getitem__(self, key): - if key == '': - idx = self._last_index - self._last_index += 1 - try: - return self._args[idx] - except LookupError: - pass - key = str(idx) - return self._kwargs[key] - - def __iter__(self): - return iter(self._kwargs) - - def __len__(self): - return len(self._kwargs) - - -def inspect_format_method(callable): - if not isinstance(callable, (types.MethodType, - types.BuiltinMethodType)) or \ - callable.__name__ != 'format': - return None - obj = callable.__self__ - if isinstance(obj, string_types): - return obj - - -def safe_range(*args): - """A range that can't generate ranges with a length of more than - MAX_RANGE items. - """ - rng = range(*args) - if len(rng) > MAX_RANGE: - raise OverflowError('range too big, maximum size for range is %d' % - MAX_RANGE) - return rng - - -def unsafe(f): - """Marks a function or method as unsafe. - - :: - - @unsafe - def delete(self): - pass - """ - f.unsafe_callable = True - return f - - -def is_internal_attribute(obj, attr): - """Test if the attribute given is an internal python attribute. For - example this function returns `True` for the `func_code` attribute of - python objects. This is useful if the environment method - :meth:`~SandboxedEnvironment.is_safe_attribute` is overridden. - - >>> from jinja2.sandbox import is_internal_attribute - >>> is_internal_attribute(str, "mro") - True - >>> is_internal_attribute(str, "upper") - False - """ - if isinstance(obj, types.FunctionType): - if attr in UNSAFE_FUNCTION_ATTRIBUTES: - return True - elif isinstance(obj, types.MethodType): - if attr in UNSAFE_FUNCTION_ATTRIBUTES or \ - attr in UNSAFE_METHOD_ATTRIBUTES: - return True - elif isinstance(obj, type): - if attr == 'mro': - return True - elif isinstance(obj, (types.CodeType, types.TracebackType, types.FrameType)): - return True - elif isinstance(obj, types.GeneratorType): - if attr in UNSAFE_GENERATOR_ATTRIBUTES: - return True - elif hasattr(types, 'CoroutineType') and isinstance(obj, types.CoroutineType): - if attr in UNSAFE_COROUTINE_ATTRIBUTES: - return True - elif hasattr(types, 'AsyncGeneratorType') and isinstance(obj, types.AsyncGeneratorType): - if attr in UNSAFE_ASYNC_GENERATOR_ATTRIBUTES: - return True - return attr.startswith('__') - - -def modifies_known_mutable(obj, attr): - """This function checks if an attribute on a builtin mutable object - (list, dict, set or deque) would modify it if called. It also supports - the "user"-versions of the objects (`sets.Set`, `UserDict.*` etc.) and - with Python 2.6 onwards the abstract base classes `MutableSet`, - `MutableMapping`, and `MutableSequence`. - - >>> modifies_known_mutable({}, "clear") - True - >>> modifies_known_mutable({}, "keys") - False - >>> modifies_known_mutable([], "append") - True - >>> modifies_known_mutable([], "index") - False - - If called with an unsupported object (such as unicode) `False` is - returned. - - >>> modifies_known_mutable("foo", "upper") - False - """ - for typespec, unsafe in _mutable_spec: - if isinstance(obj, typespec): - return attr in unsafe - return False - - -class SandboxedEnvironment(Environment): - """The sandboxed environment. It works like the regular environment but - tells the compiler to generate sandboxed code. Additionally subclasses of - this environment may override the methods that tell the runtime what - attributes or functions are safe to access. - - If the template tries to access insecure code a :exc:`SecurityError` is - raised. However also other exceptions may occur during the rendering so - the caller has to ensure that all exceptions are caught. - """ - sandboxed = True - - #: default callback table for the binary operators. A copy of this is - #: available on each instance of a sandboxed environment as - #: :attr:`binop_table` - default_binop_table = { - '+': operator.add, - '-': operator.sub, - '*': operator.mul, - '/': operator.truediv, - '//': operator.floordiv, - '**': operator.pow, - '%': operator.mod - } - - #: default callback table for the unary operators. A copy of this is - #: available on each instance of a sandboxed environment as - #: :attr:`unop_table` - default_unop_table = { - '+': operator.pos, - '-': operator.neg - } - - #: a set of binary operators that should be intercepted. Each operator - #: that is added to this set (empty by default) is delegated to the - #: :meth:`call_binop` method that will perform the operator. The default - #: operator callback is specified by :attr:`binop_table`. - #: - #: The following binary operators are interceptable: - #: ``//``, ``%``, ``+``, ``*``, ``-``, ``/``, and ``**`` - #: - #: The default operation form the operator table corresponds to the - #: builtin function. Intercepted calls are always slower than the native - #: operator call, so make sure only to intercept the ones you are - #: interested in. - #: - #: .. versionadded:: 2.6 - intercepted_binops = frozenset() - - #: a set of unary operators that should be intercepted. Each operator - #: that is added to this set (empty by default) is delegated to the - #: :meth:`call_unop` method that will perform the operator. The default - #: operator callback is specified by :attr:`unop_table`. - #: - #: The following unary operators are interceptable: ``+``, ``-`` - #: - #: The default operation form the operator table corresponds to the - #: builtin function. Intercepted calls are always slower than the native - #: operator call, so make sure only to intercept the ones you are - #: interested in. - #: - #: .. versionadded:: 2.6 - intercepted_unops = frozenset() - - def intercept_unop(self, operator): - """Called during template compilation with the name of a unary - operator to check if it should be intercepted at runtime. If this - method returns `True`, :meth:`call_unop` is excuted for this unary - operator. The default implementation of :meth:`call_unop` will use - the :attr:`unop_table` dictionary to perform the operator with the - same logic as the builtin one. - - The following unary operators are interceptable: ``+`` and ``-`` - - Intercepted calls are always slower than the native operator call, - so make sure only to intercept the ones you are interested in. - - .. versionadded:: 2.6 - """ - return False - - - def __init__(self, *args, **kwargs): - Environment.__init__(self, *args, **kwargs) - self.globals['range'] = safe_range - self.binop_table = self.default_binop_table.copy() - self.unop_table = self.default_unop_table.copy() - - def is_safe_attribute(self, obj, attr, value): - """The sandboxed environment will call this method to check if the - attribute of an object is safe to access. Per default all attributes - starting with an underscore are considered private as well as the - special attributes of internal python objects as returned by the - :func:`is_internal_attribute` function. - """ - return not (attr.startswith('_') or is_internal_attribute(obj, attr)) - - def is_safe_callable(self, obj): - """Check if an object is safely callable. Per default a function is - considered safe unless the `unsafe_callable` attribute exists and is - True. Override this method to alter the behavior, but this won't - affect the `unsafe` decorator from this module. - """ - return not (getattr(obj, 'unsafe_callable', False) or - getattr(obj, 'alters_data', False)) - - def call_binop(self, context, operator, left, right): - """For intercepted binary operator calls (:meth:`intercepted_binops`) - this function is executed instead of the builtin operator. This can - be used to fine tune the behavior of certain operators. - - .. versionadded:: 2.6 - """ - return self.binop_table[operator](left, right) - - def call_unop(self, context, operator, arg): - """For intercepted unary operator calls (:meth:`intercepted_unops`) - this function is executed instead of the builtin operator. This can - be used to fine tune the behavior of certain operators. - - .. versionadded:: 2.6 - """ - return self.unop_table[operator](arg) - - def getitem(self, obj, argument): - """Subscribe an object from sandboxed code.""" - try: - return obj[argument] - except (TypeError, LookupError): - if isinstance(argument, string_types): - try: - attr = str(argument) - except Exception: - pass - else: - try: - value = getattr(obj, attr) - except AttributeError: - pass - else: - if self.is_safe_attribute(obj, argument, value): - return value - return self.unsafe_undefined(obj, argument) - return self.undefined(obj=obj, name=argument) - - def getattr(self, obj, attribute): - """Subscribe an object from sandboxed code and prefer the - attribute. The attribute passed *must* be a bytestring. - """ - try: - value = getattr(obj, attribute) - except AttributeError: - try: - return obj[attribute] - except (TypeError, LookupError): - pass - else: - if self.is_safe_attribute(obj, attribute, value): - return value - return self.unsafe_undefined(obj, attribute) - return self.undefined(obj=obj, name=attribute) - - def unsafe_undefined(self, obj, attribute): - """Return an undefined object for unsafe attributes.""" - return self.undefined('access to attribute %r of %r ' - 'object is unsafe.' % ( - attribute, - obj.__class__.__name__ - ), name=attribute, obj=obj, exc=SecurityError) - - def format_string(self, s, args, kwargs): - """If a format call is detected, then this is routed through this - method so that our safety sandbox can be used for it. - """ - if isinstance(s, Markup): - formatter = SandboxedEscapeFormatter(self, s.escape) - else: - formatter = SandboxedFormatter(self) - kwargs = _MagicFormatMapping(args, kwargs) - rv = formatter.vformat(s, args, kwargs) - return type(s)(rv) - - def call(__self, __context, __obj, *args, **kwargs): - """Call an object from sandboxed code.""" - fmt = inspect_format_method(__obj) - if fmt is not None: - return __self.format_string(fmt, args, kwargs) - - # the double prefixes are to avoid double keyword argument - # errors when proxying the call. - if not __self.is_safe_callable(__obj): - raise SecurityError('%r is not safely callable' % (__obj,)) - return __context.call(__obj, *args, **kwargs) - - -class ImmutableSandboxedEnvironment(SandboxedEnvironment): - """Works exactly like the regular `SandboxedEnvironment` but does not - permit modifications on the builtin mutable objects `list`, `set`, and - `dict` by using the :func:`modifies_known_mutable` function. - """ - - def is_safe_attribute(self, obj, attr, value): - if not SandboxedEnvironment.is_safe_attribute(self, obj, attr, value): - return False - return not modifies_known_mutable(obj, attr) - - -# This really is not a public API apparenlty. -try: - from _string import formatter_field_name_split -except ImportError: - def formatter_field_name_split(field_name): - return field_name._formatter_field_name_split() - - -class SandboxedFormatterMixin(object): - - def __init__(self, env): - self._env = env - - def get_field(self, field_name, args, kwargs): - first, rest = formatter_field_name_split(field_name) - obj = self.get_value(first, args, kwargs) - for is_attr, i in rest: - if is_attr: - obj = self._env.getattr(obj, i) - else: - obj = self._env.getitem(obj, i) - return obj, first - -class SandboxedFormatter(SandboxedFormatterMixin, Formatter): - - def __init__(self, env): - SandboxedFormatterMixin.__init__(self, env) - Formatter.__init__(self) - -class SandboxedEscapeFormatter(SandboxedFormatterMixin, EscapeFormatter): - - def __init__(self, env, escape): - SandboxedFormatterMixin.__init__(self, env) - EscapeFormatter.__init__(self, escape) diff --git a/pipenv/vendor/jinja2/tests.py b/pipenv/vendor/jinja2/tests.py deleted file mode 100644 index 0adc3d4d..00000000 --- a/pipenv/vendor/jinja2/tests.py +++ /dev/null @@ -1,175 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.tests - ~~~~~~~~~~~~ - - Jinja test functions. Used with the "is" operator. - - :copyright: (c) 2017 by the Jinja Team. - :license: BSD, see LICENSE for more details. -""" -import operator -import re -from collections import Mapping -from jinja2.runtime import Undefined -from jinja2._compat import text_type, string_types, integer_types -import decimal - -number_re = re.compile(r'^-?\d+(\.\d+)?$') -regex_type = type(number_re) - - -test_callable = callable - - -def test_odd(value): - """Return true if the variable is odd.""" - return value % 2 == 1 - - -def test_even(value): - """Return true if the variable is even.""" - return value % 2 == 0 - - -def test_divisibleby(value, num): - """Check if a variable is divisible by a number.""" - return value % num == 0 - - -def test_defined(value): - """Return true if the variable is defined: - - .. sourcecode:: jinja - - {% if variable is defined %} - value of variable: {{ variable }} - {% else %} - variable is not defined - {% endif %} - - See the :func:`default` filter for a simple way to set undefined - variables. - """ - return not isinstance(value, Undefined) - - -def test_undefined(value): - """Like :func:`defined` but the other way round.""" - return isinstance(value, Undefined) - - -def test_none(value): - """Return true if the variable is none.""" - return value is None - - -def test_lower(value): - """Return true if the variable is lowercased.""" - return text_type(value).islower() - - -def test_upper(value): - """Return true if the variable is uppercased.""" - return text_type(value).isupper() - - -def test_string(value): - """Return true if the object is a string.""" - return isinstance(value, string_types) - - -def test_mapping(value): - """Return true if the object is a mapping (dict etc.). - - .. versionadded:: 2.6 - """ - return isinstance(value, Mapping) - - -def test_number(value): - """Return true if the variable is a number.""" - return isinstance(value, integer_types + (float, complex, decimal.Decimal)) - - -def test_sequence(value): - """Return true if the variable is a sequence. Sequences are variables - that are iterable. - """ - try: - len(value) - value.__getitem__ - except: - return False - return True - - -def test_sameas(value, other): - """Check if an object points to the same memory address than another - object: - - .. sourcecode:: jinja - - {% if foo.attribute is sameas false %} - the foo attribute really is the `False` singleton - {% endif %} - """ - return value is other - - -def test_iterable(value): - """Check if it's possible to iterate over an object.""" - try: - iter(value) - except TypeError: - return False - return True - - -def test_escaped(value): - """Check if the value is escaped.""" - return hasattr(value, '__html__') - - -def test_in(value, seq): - """Check if value is in seq. - - .. versionadded:: 2.10 - """ - return value in seq - - -TESTS = { - 'odd': test_odd, - 'even': test_even, - 'divisibleby': test_divisibleby, - 'defined': test_defined, - 'undefined': test_undefined, - 'none': test_none, - 'lower': test_lower, - 'upper': test_upper, - 'string': test_string, - 'mapping': test_mapping, - 'number': test_number, - 'sequence': test_sequence, - 'iterable': test_iterable, - 'callable': test_callable, - 'sameas': test_sameas, - 'escaped': test_escaped, - 'in': test_in, - '==': operator.eq, - 'eq': operator.eq, - 'equalto': operator.eq, - '!=': operator.ne, - 'ne': operator.ne, - '>': operator.gt, - 'gt': operator.gt, - 'greaterthan': operator.gt, - 'ge': operator.ge, - '>=': operator.ge, - '<': operator.lt, - 'lt': operator.lt, - 'lessthan': operator.lt, - '<=': operator.le, - 'le': operator.le, -} diff --git a/pipenv/vendor/jinja2/utils.py b/pipenv/vendor/jinja2/utils.py deleted file mode 100644 index 502a311c..00000000 --- a/pipenv/vendor/jinja2/utils.py +++ /dev/null @@ -1,647 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.utils - ~~~~~~~~~~~~ - - Utility functions. - - :copyright: (c) 2017 by the Jinja Team. - :license: BSD, see LICENSE for more details. -""" -import re -import json -import errno -from collections import deque -from threading import Lock -from jinja2._compat import text_type, string_types, implements_iterator, \ - url_quote - - -_word_split_re = re.compile(r'(\s+)') -_punctuation_re = re.compile( - '^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % ( - '|'.join(map(re.escape, ('(', '<', '<'))), - '|'.join(map(re.escape, ('.', ',', ')', '>', '\n', '>'))) - ) -) -_simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$') -_striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)') -_entity_re = re.compile(r'&([^;]+);') -_letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' -_digits = '0123456789' - -# special singleton representing missing values for the runtime -missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})() - -# internal code -internal_code = set() - -concat = u''.join - -_slash_escape = '\\/' not in json.dumps('/') - - -def contextfunction(f): - """This decorator can be used to mark a function or method context callable. - A context callable is passed the active :class:`Context` as first argument when - called from the template. This is useful if a function wants to get access - to the context or functions provided on the context object. For example - a function that returns a sorted list of template variables the current - template exports could look like this:: - - @contextfunction - def get_exported_names(context): - return sorted(context.exported_vars) - """ - f.contextfunction = True - return f - - -def evalcontextfunction(f): - """This decorator can be used to mark a function or method as an eval - context callable. This is similar to the :func:`contextfunction` - but instead of passing the context, an evaluation context object is - passed. For more information about the eval context, see - :ref:`eval-context`. - - .. versionadded:: 2.4 - """ - f.evalcontextfunction = True - return f - - -def environmentfunction(f): - """This decorator can be used to mark a function or method as environment - callable. This decorator works exactly like the :func:`contextfunction` - decorator just that the first argument is the active :class:`Environment` - and not context. - """ - f.environmentfunction = True - return f - - -def internalcode(f): - """Marks the function as internally used""" - internal_code.add(f.__code__) - return f - - -def is_undefined(obj): - """Check if the object passed is undefined. This does nothing more than - performing an instance check against :class:`Undefined` but looks nicer. - This can be used for custom filters or tests that want to react to - undefined variables. For example a custom default filter can look like - this:: - - def default(var, default=''): - if is_undefined(var): - return default - return var - """ - from jinja2.runtime import Undefined - return isinstance(obj, Undefined) - - -def consume(iterable): - """Consumes an iterable without doing anything with it.""" - for event in iterable: - pass - - -def clear_caches(): - """Jinja2 keeps internal caches for environments and lexers. These are - used so that Jinja2 doesn't have to recreate environments and lexers all - the time. Normally you don't have to care about that but if you are - measuring memory consumption you may want to clean the caches. - """ - from jinja2.environment import _spontaneous_environments - from jinja2.lexer import _lexer_cache - _spontaneous_environments.clear() - _lexer_cache.clear() - - -def import_string(import_name, silent=False): - """Imports an object based on a string. This is useful if you want to - use import paths as endpoints or something similar. An import path can - be specified either in dotted notation (``xml.sax.saxutils.escape``) - or with a colon as object delimiter (``xml.sax.saxutils:escape``). - - If the `silent` is True the return value will be `None` if the import - fails. - - :return: imported object - """ - try: - if ':' in import_name: - module, obj = import_name.split(':', 1) - elif '.' in import_name: - items = import_name.split('.') - module = '.'.join(items[:-1]) - obj = items[-1] - else: - return __import__(import_name) - return getattr(__import__(module, None, None, [obj]), obj) - except (ImportError, AttributeError): - if not silent: - raise - - -def open_if_exists(filename, mode='rb'): - """Returns a file descriptor for the filename if that file exists, - otherwise `None`. - """ - try: - return open(filename, mode) - except IOError as e: - if e.errno not in (errno.ENOENT, errno.EISDIR, errno.EINVAL): - raise - - -def object_type_repr(obj): - """Returns the name of the object's type. For some recognized - singletons the name of the object is returned instead. (For - example for `None` and `Ellipsis`). - """ - if obj is None: - return 'None' - elif obj is Ellipsis: - return 'Ellipsis' - # __builtin__ in 2.x, builtins in 3.x - if obj.__class__.__module__ in ('__builtin__', 'builtins'): - name = obj.__class__.__name__ - else: - name = obj.__class__.__module__ + '.' + obj.__class__.__name__ - return '%s object' % name - - -def pformat(obj, verbose=False): - """Prettyprint an object. Either use the `pretty` library or the - builtin `pprint`. - """ - try: - from pretty import pretty - return pretty(obj, verbose=verbose) - except ImportError: - from pprint import pformat - return pformat(obj) - - -def urlize(text, trim_url_limit=None, rel=None, target=None): - """Converts any URLs in text into clickable links. Works on http://, - https:// and www. links. Links can have trailing punctuation (periods, - commas, close-parens) and leading punctuation (opening parens) and - it'll still do the right thing. - - If trim_url_limit is not None, the URLs in link text will be limited - to trim_url_limit characters. - - If nofollow is True, the URLs in link text will get a rel="nofollow" - attribute. - - If target is not None, a target attribute will be added to the link. - """ - trim_url = lambda x, limit=trim_url_limit: limit is not None \ - and (x[:limit] + (len(x) >=limit and '...' - or '')) or x - words = _word_split_re.split(text_type(escape(text))) - rel_attr = rel and ' rel="%s"' % text_type(escape(rel)) or '' - target_attr = target and ' target="%s"' % escape(target) or '' - - for i, word in enumerate(words): - match = _punctuation_re.match(word) - if match: - lead, middle, trail = match.groups() - if middle.startswith('www.') or ( - '@' not in middle and - not middle.startswith('http://') and - not middle.startswith('https://') and - len(middle) > 0 and - middle[0] in _letters + _digits and ( - middle.endswith('.org') or - middle.endswith('.net') or - middle.endswith('.com') - )): - middle = '<a href="http://%s"%s%s>%s</a>' % (middle, - rel_attr, target_attr, trim_url(middle)) - if middle.startswith('http://') or \ - middle.startswith('https://'): - middle = '<a href="%s"%s%s>%s</a>' % (middle, - rel_attr, target_attr, trim_url(middle)) - if '@' in middle and not middle.startswith('www.') and \ - not ':' in middle and _simple_email_re.match(middle): - middle = '<a href="mailto:%s">%s</a>' % (middle, middle) - if lead + middle + trail != word: - words[i] = lead + middle + trail - return u''.join(words) - - -def generate_lorem_ipsum(n=5, html=True, min=20, max=100): - """Generate some lorem ipsum for the template.""" - from jinja2.constants import LOREM_IPSUM_WORDS - from random import choice, randrange - words = LOREM_IPSUM_WORDS.split() - result = [] - - for _ in range(n): - next_capitalized = True - last_comma = last_fullstop = 0 - word = None - last = None - p = [] - - # each paragraph contains out of 20 to 100 words. - for idx, _ in enumerate(range(randrange(min, max))): - while True: - word = choice(words) - if word != last: - last = word - break - if next_capitalized: - word = word.capitalize() - next_capitalized = False - # add commas - if idx - randrange(3, 8) > last_comma: - last_comma = idx - last_fullstop += 2 - word += ',' - # add end of sentences - if idx - randrange(10, 20) > last_fullstop: - last_comma = last_fullstop = idx - word += '.' - next_capitalized = True - p.append(word) - - # ensure that the paragraph ends with a dot. - p = u' '.join(p) - if p.endswith(','): - p = p[:-1] + '.' - elif not p.endswith('.'): - p += '.' - result.append(p) - - if not html: - return u'\n\n'.join(result) - return Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result)) - - -def unicode_urlencode(obj, charset='utf-8', for_qs=False): - """URL escapes a single bytestring or unicode string with the - given charset if applicable to URL safe quoting under all rules - that need to be considered under all supported Python versions. - - If non strings are provided they are converted to their unicode - representation first. - """ - if not isinstance(obj, string_types): - obj = text_type(obj) - if isinstance(obj, text_type): - obj = obj.encode(charset) - safe = not for_qs and b'/' or b'' - rv = text_type(url_quote(obj, safe)) - if for_qs: - rv = rv.replace('%20', '+') - return rv - - -class LRUCache(object): - """A simple LRU Cache implementation.""" - - # this is fast for small capacities (something below 1000) but doesn't - # scale. But as long as it's only used as storage for templates this - # won't do any harm. - - def __init__(self, capacity): - self.capacity = capacity - self._mapping = {} - self._queue = deque() - self._postinit() - - def _postinit(self): - # alias all queue methods for faster lookup - self._popleft = self._queue.popleft - self._pop = self._queue.pop - self._remove = self._queue.remove - self._wlock = Lock() - self._append = self._queue.append - - def __getstate__(self): - return { - 'capacity': self.capacity, - '_mapping': self._mapping, - '_queue': self._queue - } - - def __setstate__(self, d): - self.__dict__.update(d) - self._postinit() - - def __getnewargs__(self): - return (self.capacity,) - - def copy(self): - """Return a shallow copy of the instance.""" - rv = self.__class__(self.capacity) - rv._mapping.update(self._mapping) - rv._queue = deque(self._queue) - return rv - - def get(self, key, default=None): - """Return an item from the cache dict or `default`""" - try: - return self[key] - except KeyError: - return default - - def setdefault(self, key, default=None): - """Set `default` if the key is not in the cache otherwise - leave unchanged. Return the value of this key. - """ - self._wlock.acquire() - try: - try: - return self[key] - except KeyError: - self[key] = default - return default - finally: - self._wlock.release() - - def clear(self): - """Clear the cache.""" - self._wlock.acquire() - try: - self._mapping.clear() - self._queue.clear() - finally: - self._wlock.release() - - def __contains__(self, key): - """Check if a key exists in this cache.""" - return key in self._mapping - - def __len__(self): - """Return the current size of the cache.""" - return len(self._mapping) - - def __repr__(self): - return '<%s %r>' % ( - self.__class__.__name__, - self._mapping - ) - - def __getitem__(self, key): - """Get an item from the cache. Moves the item up so that it has the - highest priority then. - - Raise a `KeyError` if it does not exist. - """ - self._wlock.acquire() - try: - rv = self._mapping[key] - if self._queue[-1] != key: - try: - self._remove(key) - except ValueError: - # if something removed the key from the container - # when we read, ignore the ValueError that we would - # get otherwise. - pass - self._append(key) - return rv - finally: - self._wlock.release() - - def __setitem__(self, key, value): - """Sets the value for an item. Moves the item up so that it - has the highest priority then. - """ - self._wlock.acquire() - try: - if key in self._mapping: - self._remove(key) - elif len(self._mapping) == self.capacity: - del self._mapping[self._popleft()] - self._append(key) - self._mapping[key] = value - finally: - self._wlock.release() - - def __delitem__(self, key): - """Remove an item from the cache dict. - Raise a `KeyError` if it does not exist. - """ - self._wlock.acquire() - try: - del self._mapping[key] - try: - self._remove(key) - except ValueError: - # __getitem__ is not locked, it might happen - pass - finally: - self._wlock.release() - - def items(self): - """Return a list of items.""" - result = [(key, self._mapping[key]) for key in list(self._queue)] - result.reverse() - return result - - def iteritems(self): - """Iterate over all items.""" - return iter(self.items()) - - def values(self): - """Return a list of all values.""" - return [x[1] for x in self.items()] - - def itervalue(self): - """Iterate over all values.""" - return iter(self.values()) - - def keys(self): - """Return a list of all keys ordered by most recent usage.""" - return list(self) - - def iterkeys(self): - """Iterate over all keys in the cache dict, ordered by - the most recent usage. - """ - return reversed(tuple(self._queue)) - - __iter__ = iterkeys - - def __reversed__(self): - """Iterate over the values in the cache dict, oldest items - coming first. - """ - return iter(tuple(self._queue)) - - __copy__ = copy - - -# register the LRU cache as mutable mapping if possible -try: - from collections import MutableMapping - MutableMapping.register(LRUCache) -except ImportError: - pass - - -def select_autoescape(enabled_extensions=('html', 'htm', 'xml'), - disabled_extensions=(), - default_for_string=True, - default=False): - """Intelligently sets the initial value of autoescaping based on the - filename of the template. This is the recommended way to configure - autoescaping if you do not want to write a custom function yourself. - - If you want to enable it for all templates created from strings or - for all templates with `.html` and `.xml` extensions:: - - from jinja2 import Environment, select_autoescape - env = Environment(autoescape=select_autoescape( - enabled_extensions=('html', 'xml'), - default_for_string=True, - )) - - Example configuration to turn it on at all times except if the template - ends with `.txt`:: - - from jinja2 import Environment, select_autoescape - env = Environment(autoescape=select_autoescape( - disabled_extensions=('txt',), - default_for_string=True, - default=True, - )) - - The `enabled_extensions` is an iterable of all the extensions that - autoescaping should be enabled for. Likewise `disabled_extensions` is - a list of all templates it should be disabled for. If a template is - loaded from a string then the default from `default_for_string` is used. - If nothing matches then the initial value of autoescaping is set to the - value of `default`. - - For security reasons this function operates case insensitive. - - .. versionadded:: 2.9 - """ - enabled_patterns = tuple('.' + x.lstrip('.').lower() - for x in enabled_extensions) - disabled_patterns = tuple('.' + x.lstrip('.').lower() - for x in disabled_extensions) - def autoescape(template_name): - if template_name is None: - return default_for_string - template_name = template_name.lower() - if template_name.endswith(enabled_patterns): - return True - if template_name.endswith(disabled_patterns): - return False - return default - return autoescape - - -def htmlsafe_json_dumps(obj, dumper=None, **kwargs): - """Works exactly like :func:`dumps` but is safe for use in ``<script>`` - tags. It accepts the same arguments and returns a JSON string. Note that - this is available in templates through the ``|tojson`` filter which will - also mark the result as safe. Due to how this function escapes certain - characters this is safe even if used outside of ``<script>`` tags. - - The following characters are escaped in strings: - - - ``<`` - - ``>`` - - ``&`` - - ``'`` - - This makes it safe to embed such strings in any place in HTML with the - notable exception of double quoted attributes. In that case single - quote your attributes or HTML escape it in addition. - """ - if dumper is None: - dumper = json.dumps - rv = dumper(obj, **kwargs) \ - .replace(u'<', u'\\u003c') \ - .replace(u'>', u'\\u003e') \ - .replace(u'&', u'\\u0026') \ - .replace(u"'", u'\\u0027') - return Markup(rv) - - -@implements_iterator -class Cycler(object): - """A cycle helper for templates.""" - - def __init__(self, *items): - if not items: - raise RuntimeError('at least one item has to be provided') - self.items = items - self.reset() - - def reset(self): - """Resets the cycle.""" - self.pos = 0 - - @property - def current(self): - """Returns the current item.""" - return self.items[self.pos] - - def next(self): - """Goes one item ahead and returns it.""" - rv = self.current - self.pos = (self.pos + 1) % len(self.items) - return rv - - __next__ = next - - -class Joiner(object): - """A joining helper for templates.""" - - def __init__(self, sep=u', '): - self.sep = sep - self.used = False - - def __call__(self): - if not self.used: - self.used = True - return u'' - return self.sep - - -class Namespace(object): - """A namespace object that can hold arbitrary attributes. It may be - initialized from a dictionary or with keyword argments.""" - - def __init__(*args, **kwargs): - self, args = args[0], args[1:] - self.__attrs = dict(*args, **kwargs) - - def __getattribute__(self, name): - if name == '_Namespace__attrs': - return object.__getattribute__(self, name) - try: - return self.__attrs[name] - except KeyError: - raise AttributeError(name) - - def __setitem__(self, name, value): - self.__attrs[name] = value - - def __repr__(self): - return '<Namespace %r>' % self.__attrs - - -# does this python version support async for in and async generators? -try: - exec('async def _():\n async for _ in ():\n yield _') - have_async_gen = True -except SyntaxError: - have_async_gen = False - - -# Imported here because that's where it was in the past -from markupsafe import Markup, escape, soft_unicode diff --git a/pipenv/vendor/jinja2/visitor.py b/pipenv/vendor/jinja2/visitor.py deleted file mode 100644 index ba526dfa..00000000 --- a/pipenv/vendor/jinja2/visitor.py +++ /dev/null @@ -1,87 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja2.visitor - ~~~~~~~~~~~~~~ - - This module implements a visitor for the nodes. - - :copyright: (c) 2017 by the Jinja Team. - :license: BSD. -""" -from jinja2.nodes import Node - - -class NodeVisitor(object): - """Walks the abstract syntax tree and call visitor functions for every - node found. The visitor functions may return values which will be - forwarded by the `visit` method. - - Per default the visitor functions for the nodes are ``'visit_'`` + - class name of the node. So a `TryFinally` node visit function would - be `visit_TryFinally`. This behavior can be changed by overriding - the `get_visitor` function. If no visitor function exists for a node - (return value `None`) the `generic_visit` visitor is used instead. - """ - - def get_visitor(self, node): - """Return the visitor function for this node or `None` if no visitor - exists for this node. In that case the generic visit function is - used instead. - """ - method = 'visit_' + node.__class__.__name__ - return getattr(self, method, None) - - def visit(self, node, *args, **kwargs): - """Visit a node.""" - f = self.get_visitor(node) - if f is not None: - return f(node, *args, **kwargs) - return self.generic_visit(node, *args, **kwargs) - - def generic_visit(self, node, *args, **kwargs): - """Called if no explicit visitor function exists for a node.""" - for node in node.iter_child_nodes(): - self.visit(node, *args, **kwargs) - - -class NodeTransformer(NodeVisitor): - """Walks the abstract syntax tree and allows modifications of nodes. - - The `NodeTransformer` will walk the AST and use the return value of the - visitor functions to replace or remove the old node. If the return - value of the visitor function is `None` the node will be removed - from the previous location otherwise it's replaced with the return - value. The return value may be the original node in which case no - replacement takes place. - """ - - def generic_visit(self, node, *args, **kwargs): - for field, old_value in node.iter_fields(): - if isinstance(old_value, list): - new_values = [] - for value in old_value: - if isinstance(value, Node): - value = self.visit(value, *args, **kwargs) - if value is None: - continue - elif not isinstance(value, Node): - new_values.extend(value) - continue - new_values.append(value) - old_value[:] = new_values - elif isinstance(old_value, Node): - new_node = self.visit(old_value, *args, **kwargs) - if new_node is None: - delattr(node, field) - else: - setattr(node, field, new_node) - return node - - def visit_list(self, node, *args, **kwargs): - """As transformers may return lists in some places this method - can be used to enforce a list as return value. - """ - rv = self.visit(node, *args, **kwargs) - if not isinstance(rv, list): - rv = [rv] - return rv diff --git a/pipenv/vendor/markupsafe/__init__.py b/pipenv/vendor/markupsafe/__init__.py index da05ed32..d331ac36 100644 --- a/pipenv/vendor/markupsafe/__init__.py +++ b/pipenv/vendor/markupsafe/__init__.py @@ -1,34 +1,34 @@ -# -*- coding: utf-8 -*- -""" -markupsafe -~~~~~~~~~~ - -Implements an escape function and a Markup string to replace HTML -special characters with safe representations. - -:copyright: 2010 Pallets -:license: BSD-3-Clause -""" +import functools import re import string +import typing as t -from ._compat import int_types -from ._compat import iteritems -from ._compat import Mapping -from ._compat import PY2 -from ._compat import string_types -from ._compat import text_type -from ._compat import unichr +if t.TYPE_CHECKING: + import typing_extensions as te -__version__ = "1.1.1" + class HasHTML(te.Protocol): + def __html__(self) -> str: + pass -__all__ = ["Markup", "soft_unicode", "escape", "escape_silent"] + +__version__ = "2.0.1" _striptags_re = re.compile(r"(<!--.*?-->|<[^>]*>)") -_entity_re = re.compile(r"&([^& ;]+);") -class Markup(text_type): +def _simple_escaping_wrapper(name: str) -> t.Callable[..., "Markup"]: + orig = getattr(str, name) + + @functools.wraps(orig) + def wrapped(self: "Markup", *args: t.Any, **kwargs: t.Any) -> "Markup": + args = _escape_argspec(list(args), enumerate(args), self.escape) # type: ignore + _escape_argspec(kwargs, kwargs.items(), self.escape) + return self.__class__(orig(self, *args, **kwargs)) + + return wrapped + + +class Markup(str): """A string that is ready to be safely inserted into an HTML or XML document, either because it was escaped or because it was marked safe. @@ -37,11 +37,11 @@ class Markup(text_type): it to mark it safe without escaping. To escape the text, use the :meth:`escape` class method instead. - >>> Markup('Hello, <em>World</em>!') + >>> Markup("Hello, <em>World</em>!") Markup('Hello, <em>World</em>!') >>> Markup(42) Markup('42') - >>> Markup.escape('Hello, <em>World</em>!') + >>> Markup.escape("Hello, <em>World</em>!") Markup('Hello <em>World</em>!') This implements the ``__html__()`` interface that some frameworks @@ -55,132 +55,119 @@ class Markup(text_type): >>> Markup(Foo()) Markup('<a href="/foo">foo</a>') - This is a subclass of the text type (``str`` in Python 3, - ``unicode`` in Python 2). It has the same methods as that type, but - all methods escape their arguments and return a ``Markup`` instance. + This is a subclass of :class:`str`. It has the same methods, but + escapes their arguments and returns a ``Markup`` instance. - >>> Markup('<em>%s</em>') % 'foo & bar' + >>> Markup("<em>%s</em>") % ("foo & bar",) Markup('<em>foo & bar</em>') - >>> Markup('<em>Hello</em> ') + '<foo>' + >>> Markup("<em>Hello</em> ") + "<foo>" Markup('<em>Hello</em> <foo>') """ __slots__ = () - def __new__(cls, base=u"", encoding=None, errors="strict"): + def __new__( + cls, base: t.Any = "", encoding: t.Optional[str] = None, errors: str = "strict" + ) -> "Markup": if hasattr(base, "__html__"): base = base.__html__() - if encoding is None: - return text_type.__new__(cls, base) - return text_type.__new__(cls, base, encoding, errors) - def __html__(self): + if encoding is None: + return super().__new__(cls, base) + + return super().__new__(cls, base, encoding, errors) + + def __html__(self) -> "Markup": return self - def __add__(self, other): - if isinstance(other, string_types) or hasattr(other, "__html__"): - return self.__class__(super(Markup, self).__add__(self.escape(other))) + def __add__(self, other: t.Union[str, "HasHTML"]) -> "Markup": + if isinstance(other, str) or hasattr(other, "__html__"): + return self.__class__(super().__add__(self.escape(other))) + return NotImplemented - def __radd__(self, other): - if hasattr(other, "__html__") or isinstance(other, string_types): + def __radd__(self, other: t.Union[str, "HasHTML"]) -> "Markup": + if isinstance(other, str) or hasattr(other, "__html__"): return self.escape(other).__add__(self) + return NotImplemented - def __mul__(self, num): - if isinstance(num, int_types): - return self.__class__(text_type.__mul__(self, num)) - return NotImplemented + def __mul__(self, num: int) -> "Markup": + if isinstance(num, int): + return self.__class__(super().__mul__(num)) + + return NotImplemented # type: ignore __rmul__ = __mul__ - def __mod__(self, arg): + def __mod__(self, arg: t.Any) -> "Markup": if isinstance(arg, tuple): arg = tuple(_MarkupEscapeHelper(x, self.escape) for x in arg) else: arg = _MarkupEscapeHelper(arg, self.escape) - return self.__class__(text_type.__mod__(self, arg)) - def __repr__(self): - return "%s(%s)" % (self.__class__.__name__, text_type.__repr__(self)) + return self.__class__(super().__mod__(arg)) - def join(self, seq): - return self.__class__(text_type.join(self, map(self.escape, seq))) + def __repr__(self) -> str: + return f"{self.__class__.__name__}({super().__repr__()})" - join.__doc__ = text_type.join.__doc__ + def join(self, seq: t.Iterable[t.Union[str, "HasHTML"]]) -> "Markup": + return self.__class__(super().join(map(self.escape, seq))) - def split(self, *args, **kwargs): - return list(map(self.__class__, text_type.split(self, *args, **kwargs))) + join.__doc__ = str.join.__doc__ - split.__doc__ = text_type.split.__doc__ + def split( # type: ignore + self, sep: t.Optional[str] = None, maxsplit: int = -1 + ) -> t.List["Markup"]: + return [self.__class__(v) for v in super().split(sep, maxsplit)] - def rsplit(self, *args, **kwargs): - return list(map(self.__class__, text_type.rsplit(self, *args, **kwargs))) + split.__doc__ = str.split.__doc__ - rsplit.__doc__ = text_type.rsplit.__doc__ + def rsplit( # type: ignore + self, sep: t.Optional[str] = None, maxsplit: int = -1 + ) -> t.List["Markup"]: + return [self.__class__(v) for v in super().rsplit(sep, maxsplit)] - def splitlines(self, *args, **kwargs): - return list(map(self.__class__, text_type.splitlines(self, *args, **kwargs))) + rsplit.__doc__ = str.rsplit.__doc__ - splitlines.__doc__ = text_type.splitlines.__doc__ + def splitlines(self, keepends: bool = False) -> t.List["Markup"]: # type: ignore + return [self.__class__(v) for v in super().splitlines(keepends)] - def unescape(self): + splitlines.__doc__ = str.splitlines.__doc__ + + def unescape(self) -> str: """Convert escaped markup back into a text string. This replaces HTML entities with the characters they represent. - >>> Markup('Main » <em>About</em>').unescape() + >>> Markup("Main » <em>About</em>").unescape() 'Main » <em>About</em>' """ - from ._constants import HTML_ENTITIES + from html import unescape - def handle_match(m): - name = m.group(1) - if name in HTML_ENTITIES: - return unichr(HTML_ENTITIES[name]) - try: - if name[:2] in ("#x", "#X"): - return unichr(int(name[2:], 16)) - elif name.startswith("#"): - return unichr(int(name[1:])) - except ValueError: - pass - # Don't modify unexpected input. - return m.group() + return unescape(str(self)) - return _entity_re.sub(handle_match, text_type(self)) - - def striptags(self): + def striptags(self) -> str: """:meth:`unescape` the markup, remove tags, and normalize whitespace to single spaces. - >>> Markup('Main »\t<em>About</em>').striptags() + >>> Markup("Main »\t<em>About</em>").striptags() 'Main » About' """ - stripped = u" ".join(_striptags_re.sub("", self).split()) + stripped = " ".join(_striptags_re.sub("", self).split()) return Markup(stripped).unescape() @classmethod - def escape(cls, s): + def escape(cls, s: t.Any) -> "Markup": """Escape a string. Calls :func:`escape` and ensures that for subclasses the correct type is returned. """ rv = escape(s) + if rv.__class__ is not cls: return cls(rv) + return rv - def make_simple_escaping_wrapper(name): # noqa: B902 - orig = getattr(text_type, name) - - def func(self, *args, **kwargs): - args = _escape_argspec(list(args), enumerate(args), self.escape) - _escape_argspec(kwargs, iteritems(kwargs), self.escape) - return self.__class__(orig(self, *args, **kwargs)) - - func.__name__ = orig.__name__ - func.__doc__ = orig.__doc__ - return func - for method in ( "__getitem__", "capitalize", @@ -199,129 +186,103 @@ class Markup(text_type): "swapcase", "zfill", ): - locals()[method] = make_simple_escaping_wrapper(method) + locals()[method] = _simple_escaping_wrapper(method) - def partition(self, sep): - return tuple(map(self.__class__, text_type.partition(self, self.escape(sep)))) + del method - def rpartition(self, sep): - return tuple(map(self.__class__, text_type.rpartition(self, self.escape(sep)))) + def partition(self, sep: str) -> t.Tuple["Markup", "Markup", "Markup"]: + l, s, r = super().partition(self.escape(sep)) + cls = self.__class__ + return cls(l), cls(s), cls(r) - def format(self, *args, **kwargs): + def rpartition(self, sep: str) -> t.Tuple["Markup", "Markup", "Markup"]: + l, s, r = super().rpartition(self.escape(sep)) + cls = self.__class__ + return cls(l), cls(s), cls(r) + + def format(self, *args: t.Any, **kwargs: t.Any) -> "Markup": formatter = EscapeFormatter(self.escape) - kwargs = _MagicFormatMapping(args, kwargs) return self.__class__(formatter.vformat(self, args, kwargs)) - def __html_format__(self, format_spec): + def __html_format__(self, format_spec: str) -> "Markup": if format_spec: - raise ValueError("Unsupported format specification " "for Markup.") + raise ValueError("Unsupported format specification for Markup.") + return self - # not in python 3 - if hasattr(text_type, "__getslice__"): - __getslice__ = make_simple_escaping_wrapper("__getslice__") - del method, make_simple_escaping_wrapper +class EscapeFormatter(string.Formatter): + __slots__ = ("escape",) + + def __init__(self, escape: t.Callable[[t.Any], Markup]) -> None: + self.escape = escape + super().__init__() + + def format_field(self, value: t.Any, format_spec: str) -> str: + if hasattr(value, "__html_format__"): + rv = value.__html_format__(format_spec) + elif hasattr(value, "__html__"): + if format_spec: + raise ValueError( + f"Format specifier {format_spec} given, but {type(value)} does not" + " define __html_format__. A class that defines __html__ must define" + " __html_format__ to work with format specifiers." + ) + rv = value.__html__() + else: + # We need to make sure the format spec is str here as + # otherwise the wrong callback methods are invoked. + rv = string.Formatter.format_field(self, value, str(format_spec)) + return str(self.escape(rv)) -class _MagicFormatMapping(Mapping): - """This class implements a dummy wrapper to fix a bug in the Python - standard library for string formatting. - - See http://bugs.python.org/issue13598 for information about why - this is necessary. - """ - - def __init__(self, args, kwargs): - self._args = args - self._kwargs = kwargs - self._last_index = 0 - - def __getitem__(self, key): - if key == "": - idx = self._last_index - self._last_index += 1 - try: - return self._args[idx] - except LookupError: - pass - key = str(idx) - return self._kwargs[key] - - def __iter__(self): - return iter(self._kwargs) - - def __len__(self): - return len(self._kwargs) +_ListOrDict = t.TypeVar("_ListOrDict", list, dict) -if hasattr(text_type, "format"): - - class EscapeFormatter(string.Formatter): - def __init__(self, escape): - self.escape = escape - - def format_field(self, value, format_spec): - if hasattr(value, "__html_format__"): - rv = value.__html_format__(format_spec) - elif hasattr(value, "__html__"): - if format_spec: - raise ValueError( - "Format specifier {0} given, but {1} does not" - " define __html_format__. A class that defines" - " __html__ must define __html_format__ to work" - " with format specifiers.".format(format_spec, type(value)) - ) - rv = value.__html__() - else: - # We need to make sure the format spec is unicode here as - # otherwise the wrong callback methods are invoked. For - # instance a byte string there would invoke __str__ and - # not __unicode__. - rv = string.Formatter.format_field(self, value, text_type(format_spec)) - return text_type(self.escape(rv)) - - -def _escape_argspec(obj, iterable, escape): +def _escape_argspec( + obj: _ListOrDict, iterable: t.Iterable[t.Any], escape: t.Callable[[t.Any], Markup] +) -> _ListOrDict: """Helper for various string-wrapped functions.""" for key, value in iterable: - if hasattr(value, "__html__") or isinstance(value, string_types): + if isinstance(value, str) or hasattr(value, "__html__"): obj[key] = escape(value) + return obj -class _MarkupEscapeHelper(object): - """Helper for Markup.__mod__""" +class _MarkupEscapeHelper: + """Helper for :meth:`Markup.__mod__`.""" - def __init__(self, obj, escape): + __slots__ = ("obj", "escape") + + def __init__(self, obj: t.Any, escape: t.Callable[[t.Any], Markup]) -> None: self.obj = obj self.escape = escape - def __getitem__(self, item): + def __getitem__(self, item: t.Any) -> "_MarkupEscapeHelper": return _MarkupEscapeHelper(self.obj[item], self.escape) - def __str__(self): - return text_type(self.escape(self.obj)) + def __str__(self) -> str: + return str(self.escape(self.obj)) - __unicode__ = __str__ - - def __repr__(self): + def __repr__(self) -> str: return str(self.escape(repr(self.obj))) - def __int__(self): + def __int__(self) -> int: return int(self.obj) - def __float__(self): + def __float__(self) -> float: return float(self.obj) -# we have to import it down here as the speedups and native -# modules imports the markup type which is define above. +# circular import try: - from ._speedups import escape, escape_silent, soft_unicode + from ._speedups import escape as escape + from ._speedups import escape_silent as escape_silent + from ._speedups import soft_str as soft_str + from ._speedups import soft_unicode except ImportError: - from ._native import escape, escape_silent, soft_unicode - -if not PY2: - soft_str = soft_unicode - __all__.append("soft_str") + from ._native import escape as escape + from ._native import escape_silent as escape_silent # noqa: F401 + from ._native import soft_str as soft_str # noqa: F401 + from ._native import soft_unicode # noqa: F401 diff --git a/pipenv/vendor/markupsafe/_compat.py b/pipenv/vendor/markupsafe/_compat.py deleted file mode 100644 index bc05090f..00000000 --- a/pipenv/vendor/markupsafe/_compat.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -""" -markupsafe._compat -~~~~~~~~~~~~~~~~~~ - -:copyright: 2010 Pallets -:license: BSD-3-Clause -""" -import sys - -PY2 = sys.version_info[0] == 2 - -if not PY2: - text_type = str - string_types = (str,) - unichr = chr - int_types = (int,) - - def iteritems(x): - return iter(x.items()) - - from collections.abc import Mapping - -else: - text_type = unicode - string_types = (str, unicode) - unichr = unichr - int_types = (int, long) - - def iteritems(x): - return x.iteritems() - - from collections import Mapping diff --git a/pipenv/vendor/markupsafe/_constants.py b/pipenv/vendor/markupsafe/_constants.py deleted file mode 100644 index 7c57c2d2..00000000 --- a/pipenv/vendor/markupsafe/_constants.py +++ /dev/null @@ -1,264 +0,0 @@ -# -*- coding: utf-8 -*- -""" -markupsafe._constants -~~~~~~~~~~~~~~~~~~~~~ - -:copyright: 2010 Pallets -:license: BSD-3-Clause -""" - -HTML_ENTITIES = { - "AElig": 198, - "Aacute": 193, - "Acirc": 194, - "Agrave": 192, - "Alpha": 913, - "Aring": 197, - "Atilde": 195, - "Auml": 196, - "Beta": 914, - "Ccedil": 199, - "Chi": 935, - "Dagger": 8225, - "Delta": 916, - "ETH": 208, - "Eacute": 201, - "Ecirc": 202, - "Egrave": 200, - "Epsilon": 917, - "Eta": 919, - "Euml": 203, - "Gamma": 915, - "Iacute": 205, - "Icirc": 206, - "Igrave": 204, - "Iota": 921, - "Iuml": 207, - "Kappa": 922, - "Lambda": 923, - "Mu": 924, - "Ntilde": 209, - "Nu": 925, - "OElig": 338, - "Oacute": 211, - "Ocirc": 212, - "Ograve": 210, - "Omega": 937, - "Omicron": 927, - "Oslash": 216, - "Otilde": 213, - "Ouml": 214, - "Phi": 934, - "Pi": 928, - "Prime": 8243, - "Psi": 936, - "Rho": 929, - "Scaron": 352, - "Sigma": 931, - "THORN": 222, - "Tau": 932, - "Theta": 920, - "Uacute": 218, - "Ucirc": 219, - "Ugrave": 217, - "Upsilon": 933, - "Uuml": 220, - "Xi": 926, - "Yacute": 221, - "Yuml": 376, - "Zeta": 918, - "aacute": 225, - "acirc": 226, - "acute": 180, - "aelig": 230, - "agrave": 224, - "alefsym": 8501, - "alpha": 945, - "amp": 38, - "and": 8743, - "ang": 8736, - "apos": 39, - "aring": 229, - "asymp": 8776, - "atilde": 227, - "auml": 228, - "bdquo": 8222, - "beta": 946, - "brvbar": 166, - "bull": 8226, - "cap": 8745, - "ccedil": 231, - "cedil": 184, - "cent": 162, - "chi": 967, - "circ": 710, - "clubs": 9827, - "cong": 8773, - "copy": 169, - "crarr": 8629, - "cup": 8746, - "curren": 164, - "dArr": 8659, - "dagger": 8224, - "darr": 8595, - "deg": 176, - "delta": 948, - "diams": 9830, - "divide": 247, - "eacute": 233, - "ecirc": 234, - "egrave": 232, - "empty": 8709, - "emsp": 8195, - "ensp": 8194, - "epsilon": 949, - "equiv": 8801, - "eta": 951, - "eth": 240, - "euml": 235, - "euro": 8364, - "exist": 8707, - "fnof": 402, - "forall": 8704, - "frac12": 189, - "frac14": 188, - "frac34": 190, - "frasl": 8260, - "gamma": 947, - "ge": 8805, - "gt": 62, - "hArr": 8660, - "harr": 8596, - "hearts": 9829, - "hellip": 8230, - "iacute": 237, - "icirc": 238, - "iexcl": 161, - "igrave": 236, - "image": 8465, - "infin": 8734, - "int": 8747, - "iota": 953, - "iquest": 191, - "isin": 8712, - "iuml": 239, - "kappa": 954, - "lArr": 8656, - "lambda": 955, - "lang": 9001, - "laquo": 171, - "larr": 8592, - "lceil": 8968, - "ldquo": 8220, - "le": 8804, - "lfloor": 8970, - "lowast": 8727, - "loz": 9674, - "lrm": 8206, - "lsaquo": 8249, - "lsquo": 8216, - "lt": 60, - "macr": 175, - "mdash": 8212, - "micro": 181, - "middot": 183, - "minus": 8722, - "mu": 956, - "nabla": 8711, - "nbsp": 160, - "ndash": 8211, - "ne": 8800, - "ni": 8715, - "not": 172, - "notin": 8713, - "nsub": 8836, - "ntilde": 241, - "nu": 957, - "oacute": 243, - "ocirc": 244, - "oelig": 339, - "ograve": 242, - "oline": 8254, - "omega": 969, - "omicron": 959, - "oplus": 8853, - "or": 8744, - "ordf": 170, - "ordm": 186, - "oslash": 248, - "otilde": 245, - "otimes": 8855, - "ouml": 246, - "para": 182, - "part": 8706, - "permil": 8240, - "perp": 8869, - "phi": 966, - "pi": 960, - "piv": 982, - "plusmn": 177, - "pound": 163, - "prime": 8242, - "prod": 8719, - "prop": 8733, - "psi": 968, - "quot": 34, - "rArr": 8658, - "radic": 8730, - "rang": 9002, - "raquo": 187, - "rarr": 8594, - "rceil": 8969, - "rdquo": 8221, - "real": 8476, - "reg": 174, - "rfloor": 8971, - "rho": 961, - "rlm": 8207, - "rsaquo": 8250, - "rsquo": 8217, - "sbquo": 8218, - "scaron": 353, - "sdot": 8901, - "sect": 167, - "shy": 173, - "sigma": 963, - "sigmaf": 962, - "sim": 8764, - "spades": 9824, - "sub": 8834, - "sube": 8838, - "sum": 8721, - "sup": 8835, - "sup1": 185, - "sup2": 178, - "sup3": 179, - "supe": 8839, - "szlig": 223, - "tau": 964, - "there4": 8756, - "theta": 952, - "thetasym": 977, - "thinsp": 8201, - "thorn": 254, - "tilde": 732, - "times": 215, - "trade": 8482, - "uArr": 8657, - "uacute": 250, - "uarr": 8593, - "ucirc": 251, - "ugrave": 249, - "uml": 168, - "upsih": 978, - "upsilon": 965, - "uuml": 252, - "weierp": 8472, - "xi": 958, - "yacute": 253, - "yen": 165, - "yuml": 255, - "zeta": 950, - "zwj": 8205, - "zwnj": 8204, -} diff --git a/pipenv/vendor/markupsafe/_native.py b/pipenv/vendor/markupsafe/_native.py index cd08752c..6f7eb7a8 100644 --- a/pipenv/vendor/markupsafe/_native.py +++ b/pipenv/vendor/markupsafe/_native.py @@ -1,18 +1,9 @@ -# -*- coding: utf-8 -*- -""" -markupsafe._native -~~~~~~~~~~~~~~~~~~ +import typing as t -Native Python implementation used when the C module is not compiled. - -:copyright: 2010 Pallets -:license: BSD-3-Clause -""" from . import Markup -from ._compat import text_type -def escape(s): +def escape(s: t.Any) -> Markup: """Replace the characters ``&``, ``<``, ``>``, ``'``, and ``"`` in the string with HTML-safe sequences. Use this if you need to display text that might contain such characters in HTML. @@ -25,8 +16,9 @@ def escape(s): """ if hasattr(s, "__html__"): return Markup(s.__html__()) + return Markup( - text_type(s) + str(s) .replace("&", "&") .replace(">", ">") .replace("<", "<") @@ -35,7 +27,7 @@ def escape(s): ) -def escape_silent(s): +def escape_silent(s: t.Optional[t.Any]) -> Markup: """Like :func:`escape` but treats ``None`` as the empty string. Useful with optional values, as otherwise you get the string ``'None'`` when the value is ``None``. @@ -47,23 +39,37 @@ def escape_silent(s): """ if s is None: return Markup() + return escape(s) -def soft_unicode(s): +def soft_str(s: t.Any) -> str: """Convert an object to a string if it isn't already. This preserves a :class:`Markup` string rather than converting it back to a basic string, so it will still be marked as safe and won't be escaped again. - >>> value = escape('<User 1>') + >>> value = escape("<User 1>") >>> value Markup('<User 1>') >>> escape(str(value)) Markup('&lt;User 1&gt;') - >>> escape(soft_unicode(value)) + >>> escape(soft_str(value)) Markup('<User 1>') """ - if not isinstance(s, text_type): - s = text_type(s) + if not isinstance(s, str): + return str(s) + return s + + +def soft_unicode(s: t.Any) -> str: + import warnings + + warnings.warn( + "'soft_unicode' has been renamed to 'soft_str'. The old name" + " will be removed in MarkupSafe 2.1.", + DeprecationWarning, + stacklevel=2, + ) + return soft_str(s) diff --git a/pipenv/vendor/markupsafe/_speedups.c b/pipenv/vendor/markupsafe/_speedups.c index 12d2c4a7..44967b1f 100644 --- a/pipenv/vendor/markupsafe/_speedups.c +++ b/pipenv/vendor/markupsafe/_speedups.c @@ -1,23 +1,5 @@ -/** - * markupsafe._speedups - * ~~~~~~~~~~~~~~~~~~~~ - * - * C implementation of escaping for better performance. Used instead of - * the native Python implementation when compiled. - * - * :copyright: 2010 Pallets - * :license: BSD-3-Clause - */ #include <Python.h> -#if PY_MAJOR_VERSION < 3 -#define ESCAPED_CHARS_TABLE_SIZE 63 -#define UNICHR(x) (PyUnicode_AS_UNICODE((PyUnicodeObject*)PyUnicode_DecodeASCII(x, strlen(x), NULL))); - -static Py_ssize_t escaped_chars_delta_len[ESCAPED_CHARS_TABLE_SIZE]; -static Py_UNICODE *escaped_chars_repl[ESCAPED_CHARS_TABLE_SIZE]; -#endif - static PyObject* markup; static int @@ -25,21 +7,6 @@ init_constants(void) { PyObject *module; -#if PY_MAJOR_VERSION < 3 - /* mapping of characters to replace */ - escaped_chars_repl['"'] = UNICHR("""); - escaped_chars_repl['\''] = UNICHR("'"); - escaped_chars_repl['&'] = UNICHR("&"); - escaped_chars_repl['<'] = UNICHR("<"); - escaped_chars_repl['>'] = UNICHR(">"); - - /* lengths of those characters when replaced - 1 */ - memset(escaped_chars_delta_len, 0, sizeof (escaped_chars_delta_len)); - escaped_chars_delta_len['"'] = escaped_chars_delta_len['\''] = \ - escaped_chars_delta_len['&'] = 4; - escaped_chars_delta_len['<'] = escaped_chars_delta_len['>'] = 3; -#endif - /* import markup type so that we can mark the return value */ module = PyImport_ImportModule("markupsafe"); if (!module) @@ -50,137 +17,74 @@ init_constants(void) return 1; } -#if PY_MAJOR_VERSION < 3 -static PyObject* -escape_unicode(PyUnicodeObject *in) -{ - PyUnicodeObject *out; - Py_UNICODE *inp = PyUnicode_AS_UNICODE(in); - const Py_UNICODE *inp_end = PyUnicode_AS_UNICODE(in) + PyUnicode_GET_SIZE(in); - Py_UNICODE *next_escp; - Py_UNICODE *outp; - Py_ssize_t delta=0, erepl=0, delta_len=0; - - /* First we need to figure out how long the escaped string will be */ - while (*(inp) || inp < inp_end) { - if (*inp < ESCAPED_CHARS_TABLE_SIZE) { - delta += escaped_chars_delta_len[*inp]; - erepl += !!escaped_chars_delta_len[*inp]; - } - ++inp; - } - - /* Do we need to escape anything at all? */ - if (!erepl) { - Py_INCREF(in); - return (PyObject*)in; - } - - out = (PyUnicodeObject*)PyUnicode_FromUnicode(NULL, PyUnicode_GET_SIZE(in) + delta); - if (!out) - return NULL; - - outp = PyUnicode_AS_UNICODE(out); - inp = PyUnicode_AS_UNICODE(in); - while (erepl-- > 0) { - /* look for the next substitution */ - next_escp = inp; - while (next_escp < inp_end) { - if (*next_escp < ESCAPED_CHARS_TABLE_SIZE && - (delta_len = escaped_chars_delta_len[*next_escp])) { - ++delta_len; - break; - } - ++next_escp; - } - - if (next_escp > inp) { - /* copy unescaped chars between inp and next_escp */ - Py_UNICODE_COPY(outp, inp, next_escp-inp); - outp += next_escp - inp; - } - - /* escape 'next_escp' */ - Py_UNICODE_COPY(outp, escaped_chars_repl[*next_escp], delta_len); - outp += delta_len; - - inp = next_escp + 1; - } - if (inp < inp_end) - Py_UNICODE_COPY(outp, inp, PyUnicode_GET_SIZE(in) - (inp - PyUnicode_AS_UNICODE(in))); - - return (PyObject*)out; -} -#else /* PY_MAJOR_VERSION < 3 */ - #define GET_DELTA(inp, inp_end, delta) \ - while (inp < inp_end) { \ - switch (*inp++) { \ - case '"': \ - case '\'': \ - case '&': \ - delta += 4; \ - break; \ - case '<': \ - case '>': \ - delta += 3; \ - break; \ - } \ + while (inp < inp_end) { \ + switch (*inp++) { \ + case '"': \ + case '\'': \ + case '&': \ + delta += 4; \ + break; \ + case '<': \ + case '>': \ + delta += 3; \ + break; \ + } \ } #define DO_ESCAPE(inp, inp_end, outp) \ - { \ - Py_ssize_t ncopy = 0; \ - while (inp < inp_end) { \ - switch (*inp) { \ - case '"': \ + { \ + Py_ssize_t ncopy = 0; \ + while (inp < inp_end) { \ + switch (*inp) { \ + case '"': \ memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \ outp += ncopy; ncopy = 0; \ - *outp++ = '&'; \ - *outp++ = '#'; \ - *outp++ = '3'; \ - *outp++ = '4'; \ - *outp++ = ';'; \ - break; \ - case '\'': \ + *outp++ = '&'; \ + *outp++ = '#'; \ + *outp++ = '3'; \ + *outp++ = '4'; \ + *outp++ = ';'; \ + break; \ + case '\'': \ memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \ outp += ncopy; ncopy = 0; \ - *outp++ = '&'; \ - *outp++ = '#'; \ - *outp++ = '3'; \ - *outp++ = '9'; \ - *outp++ = ';'; \ - break; \ - case '&': \ + *outp++ = '&'; \ + *outp++ = '#'; \ + *outp++ = '3'; \ + *outp++ = '9'; \ + *outp++ = ';'; \ + break; \ + case '&': \ memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \ outp += ncopy; ncopy = 0; \ - *outp++ = '&'; \ - *outp++ = 'a'; \ - *outp++ = 'm'; \ - *outp++ = 'p'; \ - *outp++ = ';'; \ - break; \ - case '<': \ + *outp++ = '&'; \ + *outp++ = 'a'; \ + *outp++ = 'm'; \ + *outp++ = 'p'; \ + *outp++ = ';'; \ + break; \ + case '<': \ memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \ outp += ncopy; ncopy = 0; \ - *outp++ = '&'; \ - *outp++ = 'l'; \ - *outp++ = 't'; \ - *outp++ = ';'; \ - break; \ - case '>': \ + *outp++ = '&'; \ + *outp++ = 'l'; \ + *outp++ = 't'; \ + *outp++ = ';'; \ + break; \ + case '>': \ memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \ outp += ncopy; ncopy = 0; \ - *outp++ = '&'; \ - *outp++ = 'g'; \ - *outp++ = 't'; \ - *outp++ = ';'; \ - break; \ - default: \ + *outp++ = '&'; \ + *outp++ = 'g'; \ + *outp++ = 't'; \ + *outp++ = ';'; \ + break; \ + default: \ ncopy++; \ - } \ - inp++; \ - } \ + } \ + inp++; \ + } \ memcpy(outp, inp-ncopy, sizeof(*outp)*ncopy); \ } @@ -278,7 +182,6 @@ escape_unicode(PyUnicodeObject *in) assert(0); /* shouldn't happen */ return NULL; } -#endif /* PY_MAJOR_VERSION < 3 */ static PyObject* escape(PyObject *self, PyObject *text) @@ -287,11 +190,7 @@ escape(PyObject *self, PyObject *text) PyObject *s = NULL, *rv = NULL, *html; if (id_html == NULL) { -#if PY_MAJOR_VERSION < 3 - id_html = PyString_InternFromString("__html__"); -#else id_html = PyUnicode_InternFromString("__html__"); -#endif if (id_html == NULL) { return NULL; } @@ -299,11 +198,8 @@ escape(PyObject *self, PyObject *text) /* we don't have to escape integers, bools or floats */ if (PyLong_CheckExact(text) || -#if PY_MAJOR_VERSION < 3 - PyInt_CheckExact(text) || -#endif - PyFloat_CheckExact(text) || PyBool_Check(text) || - text == Py_None) + PyFloat_CheckExact(text) || PyBool_Check(text) || + text == Py_None) return PyObject_CallFunctionObjArgs(markup, text, NULL); /* if the object has an __html__ method that performs the escaping */ @@ -323,11 +219,7 @@ escape(PyObject *self, PyObject *text) /* otherwise make the object unicode if it isn't, then escape */ PyErr_Clear(); if (!PyUnicode_Check(text)) { -#if PY_MAJOR_VERSION < 3 - PyObject *unicode = PyObject_Unicode(text); -#else PyObject *unicode = PyObject_Str(text); -#endif if (!unicode) return NULL; s = escape_unicode((PyUnicodeObject*)unicode); @@ -353,54 +245,80 @@ escape_silent(PyObject *self, PyObject *text) static PyObject* -soft_unicode(PyObject *self, PyObject *s) +soft_str(PyObject *self, PyObject *s) { if (!PyUnicode_Check(s)) -#if PY_MAJOR_VERSION < 3 - return PyObject_Unicode(s); -#else return PyObject_Str(s); -#endif Py_INCREF(s); return s; } -static PyMethodDef module_methods[] = { - {"escape", (PyCFunction)escape, METH_O, - "escape(s) -> markup\n\n" - "Convert the characters &, <, >, ', and \" in string s to HTML-safe\n" - "sequences. Use this if you need to display text that might contain\n" - "such characters in HTML. Marks return value as markup string."}, - {"escape_silent", (PyCFunction)escape_silent, METH_O, - "escape_silent(s) -> markup\n\n" - "Like escape but converts None to an empty string."}, - {"soft_unicode", (PyCFunction)soft_unicode, METH_O, - "soft_unicode(object) -> string\n\n" - "Make a string unicode if it isn't already. That way a markup\n" - "string is not converted back to unicode."}, - {NULL, NULL, 0, NULL} /* Sentinel */ -}; - - -#if PY_MAJOR_VERSION < 3 - -#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ -#define PyMODINIT_FUNC void -#endif -PyMODINIT_FUNC -init_speedups(void) +static PyObject* +soft_unicode(PyObject *self, PyObject *s) { - if (!init_constants()) - return; - - Py_InitModule3("markupsafe._speedups", module_methods, ""); + PyErr_WarnEx( + PyExc_DeprecationWarning, + "'soft_unicode' has been renamed to 'soft_str'. The old name" + " will be removed in MarkupSafe 2.1.", + 2 + ); + return soft_str(self, s); } -#else /* Python 3.x module initialization */ + +static PyMethodDef module_methods[] = { + { + "escape", + (PyCFunction)escape, + METH_O, + "Replace the characters ``&``, ``<``, ``>``, ``'``, and ``\"`` in" + " the string with HTML-safe sequences. Use this if you need to display" + " text that might contain such characters in HTML.\n\n" + "If the object has an ``__html__`` method, it is called and the" + " return value is assumed to already be safe for HTML.\n\n" + ":param s: An object to be converted to a string and escaped.\n" + ":return: A :class:`Markup` string with the escaped text.\n" + }, + { + "escape_silent", + (PyCFunction)escape_silent, + METH_O, + "Like :func:`escape` but treats ``None`` as the empty string." + " Useful with optional values, as otherwise you get the string" + " ``'None'`` when the value is ``None``.\n\n" + ">>> escape(None)\n" + "Markup('None')\n" + ">>> escape_silent(None)\n" + "Markup('')\n" + }, + { + "soft_str", + (PyCFunction)soft_str, + METH_O, + "Convert an object to a string if it isn't already. This preserves" + " a :class:`Markup` string rather than converting it back to a basic" + " string, so it will still be marked as safe and won't be escaped" + " again.\n\n" + ">>> value = escape(\"<User 1>\")\n" + ">>> value\n" + "Markup('<User 1>')\n" + ">>> escape(str(value))\n" + "Markup('&lt;User 1&gt;')\n" + ">>> escape(soft_str(value))\n" + "Markup('<User 1>')\n" + }, + { + "soft_unicode", + (PyCFunction)soft_unicode, + METH_O, + "" + }, + {NULL, NULL, 0, NULL} /* Sentinel */ +}; static struct PyModuleDef module_definition = { - PyModuleDef_HEAD_INIT, + PyModuleDef_HEAD_INIT, "markupsafe._speedups", NULL, -1, @@ -419,5 +337,3 @@ PyInit__speedups(void) return PyModule_Create(&module_definition); } - -#endif diff --git a/pipenv/vendor/markupsafe/_speedups.pyi b/pipenv/vendor/markupsafe/_speedups.pyi new file mode 100644 index 00000000..f673240f --- /dev/null +++ b/pipenv/vendor/markupsafe/_speedups.pyi @@ -0,0 +1,9 @@ +from typing import Any +from typing import Optional + +from . import Markup + +def escape(s: Any) -> Markup: ... +def escape_silent(s: Optional[Any]) -> Markup: ... +def soft_str(s: Any) -> str: ... +def soft_unicode(s: Any) -> str: ... diff --git a/pipenv/vendor/markupsafe/py.typed b/pipenv/vendor/markupsafe/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/pipenv/vendor/first.LICENSE b/pipenv/vendor/more_itertools/LICENSE similarity index 96% rename from pipenv/vendor/first.LICENSE rename to pipenv/vendor/more_itertools/LICENSE index a9c8c9db..0a523bec 100644 --- a/pipenv/vendor/first.LICENSE +++ b/pipenv/vendor/more_itertools/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2012 Hynek Schlawack +Copyright (c) 2012 Erik Rose Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/pipenv/vendor/more_itertools/__init__.py b/pipenv/vendor/more_itertools/__init__.py new file mode 100644 index 00000000..19a169fc --- /dev/null +++ b/pipenv/vendor/more_itertools/__init__.py @@ -0,0 +1,4 @@ +from .more import * # noqa +from .recipes import * # noqa + +__version__ = '8.8.0' diff --git a/pipenv/vendor/more_itertools/__init__.pyi b/pipenv/vendor/more_itertools/__init__.pyi new file mode 100644 index 00000000..96f6e36c --- /dev/null +++ b/pipenv/vendor/more_itertools/__init__.pyi @@ -0,0 +1,2 @@ +from .more import * +from .recipes import * diff --git a/pipenv/vendor/more_itertools/more.py b/pipenv/vendor/more_itertools/more.py new file mode 100644 index 00000000..0f7d282a --- /dev/null +++ b/pipenv/vendor/more_itertools/more.py @@ -0,0 +1,3825 @@ +import warnings + +from collections import Counter, defaultdict, deque, abc +from collections.abc import Sequence +from concurrent.futures import ThreadPoolExecutor +from functools import partial, reduce, wraps +from heapq import merge, heapify, heapreplace, heappop +from itertools import ( + chain, + compress, + count, + cycle, + dropwhile, + groupby, + islice, + repeat, + starmap, + takewhile, + tee, + zip_longest, +) +from math import exp, factorial, floor, log +from queue import Empty, Queue +from random import random, randrange, uniform +from operator import itemgetter, mul, sub, gt, lt +from sys import hexversion, maxsize +from time import monotonic + +from .recipes import ( + consume, + flatten, + pairwise, + powerset, + take, + unique_everseen, +) + +__all__ = [ + 'AbortThread', + 'adjacent', + 'always_iterable', + 'always_reversible', + 'bucket', + 'callback_iter', + 'chunked', + 'circular_shifts', + 'collapse', + 'collate', + 'consecutive_groups', + 'consumer', + 'countable', + 'count_cycle', + 'mark_ends', + 'difference', + 'distinct_combinations', + 'distinct_permutations', + 'distribute', + 'divide', + 'exactly_n', + 'filter_except', + 'first', + 'groupby_transform', + 'ilen', + 'interleave_longest', + 'interleave', + 'intersperse', + 'islice_extended', + 'iterate', + 'ichunked', + 'is_sorted', + 'last', + 'locate', + 'lstrip', + 'make_decorator', + 'map_except', + 'map_reduce', + 'nth_or_last', + 'nth_permutation', + 'nth_product', + 'numeric_range', + 'one', + 'only', + 'padded', + 'partitions', + 'set_partitions', + 'peekable', + 'repeat_last', + 'replace', + 'rlocate', + 'rstrip', + 'run_length', + 'sample', + 'seekable', + 'SequenceView', + 'side_effect', + 'sliced', + 'sort_together', + 'split_at', + 'split_after', + 'split_before', + 'split_when', + 'split_into', + 'spy', + 'stagger', + 'strip', + 'substrings', + 'substrings_indexes', + 'time_limited', + 'unique_to_each', + 'unzip', + 'windowed', + 'with_iter', + 'UnequalIterablesError', + 'zip_equal', + 'zip_offset', + 'windowed_complete', + 'all_unique', + 'value_chain', + 'product_index', + 'combination_index', + 'permutation_index', +] + +_marker = object() + + +def chunked(iterable, n, strict=False): + """Break *iterable* into lists of length *n*: + + >>> list(chunked([1, 2, 3, 4, 5, 6], 3)) + [[1, 2, 3], [4, 5, 6]] + + By the default, the last yielded list will have fewer than *n* elements + if the length of *iterable* is not divisible by *n*: + + >>> list(chunked([1, 2, 3, 4, 5, 6, 7, 8], 3)) + [[1, 2, 3], [4, 5, 6], [7, 8]] + + To use a fill-in value instead, see the :func:`grouper` recipe. + + If the length of *iterable* is not divisible by *n* and *strict* is + ``True``, then ``ValueError`` will be raised before the last + list is yielded. + + """ + iterator = iter(partial(take, n, iter(iterable)), []) + if strict: + + def ret(): + for chunk in iterator: + if len(chunk) != n: + raise ValueError('iterable is not divisible by n.') + yield chunk + + return iter(ret()) + else: + return iterator + + +def first(iterable, default=_marker): + """Return the first item of *iterable*, or *default* if *iterable* is + empty. + + >>> first([0, 1, 2, 3]) + 0 + >>> first([], 'some default') + 'some default' + + If *default* is not provided and there are no items in the iterable, + raise ``ValueError``. + + :func:`first` is useful when you have a generator of expensive-to-retrieve + values and want any arbitrary one. It is marginally shorter than + ``next(iter(iterable), default)``. + + """ + try: + return next(iter(iterable)) + except StopIteration as e: + if default is _marker: + raise ValueError( + 'first() was called on an empty iterable, and no ' + 'default value was provided.' + ) from e + return default + + +def last(iterable, default=_marker): + """Return the last item of *iterable*, or *default* if *iterable* is + empty. + + >>> last([0, 1, 2, 3]) + 3 + >>> last([], 'some default') + 'some default' + + If *default* is not provided and there are no items in the iterable, + raise ``ValueError``. + """ + try: + if isinstance(iterable, Sequence): + return iterable[-1] + # Work around https://bugs.python.org/issue38525 + elif hasattr(iterable, '__reversed__') and (hexversion != 0x030800F0): + return next(reversed(iterable)) + else: + return deque(iterable, maxlen=1)[-1] + except (IndexError, TypeError, StopIteration): + if default is _marker: + raise ValueError( + 'last() was called on an empty iterable, and no default was ' + 'provided.' + ) + return default + + +def nth_or_last(iterable, n, default=_marker): + """Return the nth or the last item of *iterable*, + or *default* if *iterable* is empty. + + >>> nth_or_last([0, 1, 2, 3], 2) + 2 + >>> nth_or_last([0, 1], 2) + 1 + >>> nth_or_last([], 0, 'some default') + 'some default' + + If *default* is not provided and there are no items in the iterable, + raise ``ValueError``. + """ + return last(islice(iterable, n + 1), default=default) + + +class peekable: + """Wrap an iterator to allow lookahead and prepending elements. + + Call :meth:`peek` on the result to get the value that will be returned + by :func:`next`. This won't advance the iterator: + + >>> p = peekable(['a', 'b']) + >>> p.peek() + 'a' + >>> next(p) + 'a' + + Pass :meth:`peek` a default value to return that instead of raising + ``StopIteration`` when the iterator is exhausted. + + >>> p = peekable([]) + >>> p.peek('hi') + 'hi' + + peekables also offer a :meth:`prepend` method, which "inserts" items + at the head of the iterable: + + >>> p = peekable([1, 2, 3]) + >>> p.prepend(10, 11, 12) + >>> next(p) + 10 + >>> p.peek() + 11 + >>> list(p) + [11, 12, 1, 2, 3] + + peekables can be indexed. Index 0 is the item that will be returned by + :func:`next`, index 1 is the item after that, and so on: + The values up to the given index will be cached. + + >>> p = peekable(['a', 'b', 'c', 'd']) + >>> p[0] + 'a' + >>> p[1] + 'b' + >>> next(p) + 'a' + + Negative indexes are supported, but be aware that they will cache the + remaining items in the source iterator, which may require significant + storage. + + To check whether a peekable is exhausted, check its truth value: + + >>> p = peekable(['a', 'b']) + >>> if p: # peekable has items + ... list(p) + ['a', 'b'] + >>> if not p: # peekable is exhausted + ... list(p) + [] + + """ + + def __init__(self, iterable): + self._it = iter(iterable) + self._cache = deque() + + def __iter__(self): + return self + + def __bool__(self): + try: + self.peek() + except StopIteration: + return False + return True + + def peek(self, default=_marker): + """Return the item that will be next returned from ``next()``. + + Return ``default`` if there are no items left. If ``default`` is not + provided, raise ``StopIteration``. + + """ + if not self._cache: + try: + self._cache.append(next(self._it)) + except StopIteration: + if default is _marker: + raise + return default + return self._cache[0] + + def prepend(self, *items): + """Stack up items to be the next ones returned from ``next()`` or + ``self.peek()``. The items will be returned in + first in, first out order:: + + >>> p = peekable([1, 2, 3]) + >>> p.prepend(10, 11, 12) + >>> next(p) + 10 + >>> list(p) + [11, 12, 1, 2, 3] + + It is possible, by prepending items, to "resurrect" a peekable that + previously raised ``StopIteration``. + + >>> p = peekable([]) + >>> next(p) + Traceback (most recent call last): + ... + StopIteration + >>> p.prepend(1) + >>> next(p) + 1 + >>> next(p) + Traceback (most recent call last): + ... + StopIteration + + """ + self._cache.extendleft(reversed(items)) + + def __next__(self): + if self._cache: + return self._cache.popleft() + + return next(self._it) + + def _get_slice(self, index): + # Normalize the slice's arguments + step = 1 if (index.step is None) else index.step + if step > 0: + start = 0 if (index.start is None) else index.start + stop = maxsize if (index.stop is None) else index.stop + elif step < 0: + start = -1 if (index.start is None) else index.start + stop = (-maxsize - 1) if (index.stop is None) else index.stop + else: + raise ValueError('slice step cannot be zero') + + # If either the start or stop index is negative, we'll need to cache + # the rest of the iterable in order to slice from the right side. + if (start < 0) or (stop < 0): + self._cache.extend(self._it) + # Otherwise we'll need to find the rightmost index and cache to that + # point. + else: + n = min(max(start, stop) + 1, maxsize) + cache_len = len(self._cache) + if n >= cache_len: + self._cache.extend(islice(self._it, n - cache_len)) + + return list(self._cache)[index] + + def __getitem__(self, index): + if isinstance(index, slice): + return self._get_slice(index) + + cache_len = len(self._cache) + if index < 0: + self._cache.extend(self._it) + elif index >= cache_len: + self._cache.extend(islice(self._it, index + 1 - cache_len)) + + return self._cache[index] + + +def collate(*iterables, **kwargs): + """Return a sorted merge of the items from each of several already-sorted + *iterables*. + + >>> list(collate('ACDZ', 'AZ', 'JKL')) + ['A', 'A', 'C', 'D', 'J', 'K', 'L', 'Z', 'Z'] + + Works lazily, keeping only the next value from each iterable in memory. Use + :func:`collate` to, for example, perform a n-way mergesort of items that + don't fit in memory. + + If a *key* function is specified, the iterables will be sorted according + to its result: + + >>> key = lambda s: int(s) # Sort by numeric value, not by string + >>> list(collate(['1', '10'], ['2', '11'], key=key)) + ['1', '2', '10', '11'] + + + If the *iterables* are sorted in descending order, set *reverse* to + ``True``: + + >>> list(collate([5, 3, 1], [4, 2, 0], reverse=True)) + [5, 4, 3, 2, 1, 0] + + If the elements of the passed-in iterables are out of order, you might get + unexpected results. + + On Python 3.5+, this function is an alias for :func:`heapq.merge`. + + """ + warnings.warn( + "collate is no longer part of more_itertools, use heapq.merge", + DeprecationWarning, + ) + return merge(*iterables, **kwargs) + + +def consumer(func): + """Decorator that automatically advances a PEP-342-style "reverse iterator" + to its first yield point so you don't have to call ``next()`` on it + manually. + + >>> @consumer + ... def tally(): + ... i = 0 + ... while True: + ... print('Thing number %s is %s.' % (i, (yield))) + ... i += 1 + ... + >>> t = tally() + >>> t.send('red') + Thing number 0 is red. + >>> t.send('fish') + Thing number 1 is fish. + + Without the decorator, you would have to call ``next(t)`` before + ``t.send()`` could be used. + + """ + + @wraps(func) + def wrapper(*args, **kwargs): + gen = func(*args, **kwargs) + next(gen) + return gen + + return wrapper + + +def ilen(iterable): + """Return the number of items in *iterable*. + + >>> ilen(x for x in range(1000000) if x % 3 == 0) + 333334 + + This consumes the iterable, so handle with care. + + """ + # This approach was selected because benchmarks showed it's likely the + # fastest of the known implementations at the time of writing. + # See GitHub tracker: #236, #230. + counter = count() + deque(zip(iterable, counter), maxlen=0) + return next(counter) + + +def iterate(func, start): + """Return ``start``, ``func(start)``, ``func(func(start))``, ... + + >>> from itertools import islice + >>> list(islice(iterate(lambda x: 2*x, 1), 10)) + [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] + + """ + while True: + yield start + start = func(start) + + +def with_iter(context_manager): + """Wrap an iterable in a ``with`` statement, so it closes once exhausted. + + For example, this will close the file when the iterator is exhausted:: + + upper_lines = (line.upper() for line in with_iter(open('foo'))) + + Any context manager which returns an iterable is a candidate for + ``with_iter``. + + """ + with context_manager as iterable: + yield from iterable + + +def one(iterable, too_short=None, too_long=None): + """Return the first item from *iterable*, which is expected to contain only + that item. Raise an exception if *iterable* is empty or has more than one + item. + + :func:`one` is useful for ensuring that an iterable contains only one item. + For example, it can be used to retrieve the result of a database query + that is expected to return a single row. + + If *iterable* is empty, ``ValueError`` will be raised. You may specify a + different exception with the *too_short* keyword: + + >>> it = [] + >>> one(it) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + ValueError: too many items in iterable (expected 1)' + >>> too_short = IndexError('too few items') + >>> one(it, too_short=too_short) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + IndexError: too few items + + Similarly, if *iterable* contains more than one item, ``ValueError`` will + be raised. You may specify a different exception with the *too_long* + keyword: + + >>> it = ['too', 'many'] + >>> one(it) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + ValueError: Expected exactly one item in iterable, but got 'too', + 'many', and perhaps more. + >>> too_long = RuntimeError + >>> one(it, too_long=too_long) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + RuntimeError + + Note that :func:`one` attempts to advance *iterable* twice to ensure there + is only one item. See :func:`spy` or :func:`peekable` to check iterable + contents less destructively. + + """ + it = iter(iterable) + + try: + first_value = next(it) + except StopIteration as e: + raise ( + too_short or ValueError('too few items in iterable (expected 1)') + ) from e + + try: + second_value = next(it) + except StopIteration: + pass + else: + msg = ( + 'Expected exactly one item in iterable, but got {!r}, {!r}, ' + 'and perhaps more.'.format(first_value, second_value) + ) + raise too_long or ValueError(msg) + + return first_value + + +def distinct_permutations(iterable, r=None): + """Yield successive distinct permutations of the elements in *iterable*. + + >>> sorted(distinct_permutations([1, 0, 1])) + [(0, 1, 1), (1, 0, 1), (1, 1, 0)] + + Equivalent to ``set(permutations(iterable))``, except duplicates are not + generated and thrown away. For larger input sequences this is much more + efficient. + + Duplicate permutations arise when there are duplicated elements in the + input iterable. The number of items returned is + `n! / (x_1! * x_2! * ... * x_n!)`, where `n` is the total number of + items input, and each `x_i` is the count of a distinct item in the input + sequence. + + If *r* is given, only the *r*-length permutations are yielded. + + >>> sorted(distinct_permutations([1, 0, 1], r=2)) + [(0, 1), (1, 0), (1, 1)] + >>> sorted(distinct_permutations(range(3), r=2)) + [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)] + + """ + # Algorithm: https://w.wiki/Qai + def _full(A): + while True: + # Yield the permutation we have + yield tuple(A) + + # Find the largest index i such that A[i] < A[i + 1] + for i in range(size - 2, -1, -1): + if A[i] < A[i + 1]: + break + # If no such index exists, this permutation is the last one + else: + return + + # Find the largest index j greater than j such that A[i] < A[j] + for j in range(size - 1, i, -1): + if A[i] < A[j]: + break + + # Swap the value of A[i] with that of A[j], then reverse the + # sequence from A[i + 1] to form the new permutation + A[i], A[j] = A[j], A[i] + A[i + 1 :] = A[: i - size : -1] # A[i + 1:][::-1] + + # Algorithm: modified from the above + def _partial(A, r): + # Split A into the first r items and the last r items + head, tail = A[:r], A[r:] + right_head_indexes = range(r - 1, -1, -1) + left_tail_indexes = range(len(tail)) + + while True: + # Yield the permutation we have + yield tuple(head) + + # Starting from the right, find the first index of the head with + # value smaller than the maximum value of the tail - call it i. + pivot = tail[-1] + for i in right_head_indexes: + if head[i] < pivot: + break + pivot = head[i] + else: + return + + # Starting from the left, find the first value of the tail + # with a value greater than head[i] and swap. + for j in left_tail_indexes: + if tail[j] > head[i]: + head[i], tail[j] = tail[j], head[i] + break + # If we didn't find one, start from the right and find the first + # index of the head with a value greater than head[i] and swap. + else: + for j in right_head_indexes: + if head[j] > head[i]: + head[i], head[j] = head[j], head[i] + break + + # Reverse head[i + 1:] and swap it with tail[:r - (i + 1)] + tail += head[: i - r : -1] # head[i + 1:][::-1] + i += 1 + head[i:], tail[:] = tail[: r - i], tail[r - i :] + + items = sorted(iterable) + + size = len(items) + if r is None: + r = size + + if 0 < r <= size: + return _full(items) if (r == size) else _partial(items, r) + + return iter(() if r else ((),)) + + +def intersperse(e, iterable, n=1): + """Intersperse filler element *e* among the items in *iterable*, leaving + *n* items between each filler element. + + >>> list(intersperse('!', [1, 2, 3, 4, 5])) + [1, '!', 2, '!', 3, '!', 4, '!', 5] + + >>> list(intersperse(None, [1, 2, 3, 4, 5], n=2)) + [1, 2, None, 3, 4, None, 5] + + """ + if n == 0: + raise ValueError('n must be > 0') + elif n == 1: + # interleave(repeat(e), iterable) -> e, x_0, e, e, x_1, e, x_2... + # islice(..., 1, None) -> x_0, e, e, x_1, e, x_2... + return islice(interleave(repeat(e), iterable), 1, None) + else: + # interleave(filler, chunks) -> [e], [x_0, x_1], [e], [x_2, x_3]... + # islice(..., 1, None) -> [x_0, x_1], [e], [x_2, x_3]... + # flatten(...) -> x_0, x_1, e, x_2, x_3... + filler = repeat([e]) + chunks = chunked(iterable, n) + return flatten(islice(interleave(filler, chunks), 1, None)) + + +def unique_to_each(*iterables): + """Return the elements from each of the input iterables that aren't in the + other input iterables. + + For example, suppose you have a set of packages, each with a set of + dependencies:: + + {'pkg_1': {'A', 'B'}, 'pkg_2': {'B', 'C'}, 'pkg_3': {'B', 'D'}} + + If you remove one package, which dependencies can also be removed? + + If ``pkg_1`` is removed, then ``A`` is no longer necessary - it is not + associated with ``pkg_2`` or ``pkg_3``. Similarly, ``C`` is only needed for + ``pkg_2``, and ``D`` is only needed for ``pkg_3``:: + + >>> unique_to_each({'A', 'B'}, {'B', 'C'}, {'B', 'D'}) + [['A'], ['C'], ['D']] + + If there are duplicates in one input iterable that aren't in the others + they will be duplicated in the output. Input order is preserved:: + + >>> unique_to_each("mississippi", "missouri") + [['p', 'p'], ['o', 'u', 'r']] + + It is assumed that the elements of each iterable are hashable. + + """ + pool = [list(it) for it in iterables] + counts = Counter(chain.from_iterable(map(set, pool))) + uniques = {element for element in counts if counts[element] == 1} + return [list(filter(uniques.__contains__, it)) for it in pool] + + +def windowed(seq, n, fillvalue=None, step=1): + """Return a sliding window of width *n* over the given iterable. + + >>> all_windows = windowed([1, 2, 3, 4, 5], 3) + >>> list(all_windows) + [(1, 2, 3), (2, 3, 4), (3, 4, 5)] + + When the window is larger than the iterable, *fillvalue* is used in place + of missing values: + + >>> list(windowed([1, 2, 3], 4)) + [(1, 2, 3, None)] + + Each window will advance in increments of *step*: + + >>> list(windowed([1, 2, 3, 4, 5, 6], 3, fillvalue='!', step=2)) + [(1, 2, 3), (3, 4, 5), (5, 6, '!')] + + To slide into the iterable's items, use :func:`chain` to add filler items + to the left: + + >>> iterable = [1, 2, 3, 4] + >>> n = 3 + >>> padding = [None] * (n - 1) + >>> list(windowed(chain(padding, iterable), 3)) + [(None, None, 1), (None, 1, 2), (1, 2, 3), (2, 3, 4)] + """ + if n < 0: + raise ValueError('n must be >= 0') + if n == 0: + yield tuple() + return + if step < 1: + raise ValueError('step must be >= 1') + + window = deque(maxlen=n) + i = n + for _ in map(window.append, seq): + i -= 1 + if not i: + i = step + yield tuple(window) + + size = len(window) + if size < n: + yield tuple(chain(window, repeat(fillvalue, n - size))) + elif 0 < i < min(step, n): + window += (fillvalue,) * i + yield tuple(window) + + +def substrings(iterable): + """Yield all of the substrings of *iterable*. + + >>> [''.join(s) for s in substrings('more')] + ['m', 'o', 'r', 'e', 'mo', 'or', 're', 'mor', 'ore', 'more'] + + Note that non-string iterables can also be subdivided. + + >>> list(substrings([0, 1, 2])) + [(0,), (1,), (2,), (0, 1), (1, 2), (0, 1, 2)] + + """ + # The length-1 substrings + seq = [] + for item in iter(iterable): + seq.append(item) + yield (item,) + seq = tuple(seq) + item_count = len(seq) + + # And the rest + for n in range(2, item_count + 1): + for i in range(item_count - n + 1): + yield seq[i : i + n] + + +def substrings_indexes(seq, reverse=False): + """Yield all substrings and their positions in *seq* + + The items yielded will be a tuple of the form ``(substr, i, j)``, where + ``substr == seq[i:j]``. + + This function only works for iterables that support slicing, such as + ``str`` objects. + + >>> for item in substrings_indexes('more'): + ... print(item) + ('m', 0, 1) + ('o', 1, 2) + ('r', 2, 3) + ('e', 3, 4) + ('mo', 0, 2) + ('or', 1, 3) + ('re', 2, 4) + ('mor', 0, 3) + ('ore', 1, 4) + ('more', 0, 4) + + Set *reverse* to ``True`` to yield the same items in the opposite order. + + + """ + r = range(1, len(seq) + 1) + if reverse: + r = reversed(r) + return ( + (seq[i : i + L], i, i + L) for L in r for i in range(len(seq) - L + 1) + ) + + +class bucket: + """Wrap *iterable* and return an object that buckets it iterable into + child iterables based on a *key* function. + + >>> iterable = ['a1', 'b1', 'c1', 'a2', 'b2', 'c2', 'b3'] + >>> s = bucket(iterable, key=lambda x: x[0]) # Bucket by 1st character + >>> sorted(list(s)) # Get the keys + ['a', 'b', 'c'] + >>> a_iterable = s['a'] + >>> next(a_iterable) + 'a1' + >>> next(a_iterable) + 'a2' + >>> list(s['b']) + ['b1', 'b2', 'b3'] + + The original iterable will be advanced and its items will be cached until + they are used by the child iterables. This may require significant storage. + + By default, attempting to select a bucket to which no items belong will + exhaust the iterable and cache all values. + If you specify a *validator* function, selected buckets will instead be + checked against it. + + >>> from itertools import count + >>> it = count(1, 2) # Infinite sequence of odd numbers + >>> key = lambda x: x % 10 # Bucket by last digit + >>> validator = lambda x: x in {1, 3, 5, 7, 9} # Odd digits only + >>> s = bucket(it, key=key, validator=validator) + >>> 2 in s + False + >>> list(s[2]) + [] + + """ + + def __init__(self, iterable, key, validator=None): + self._it = iter(iterable) + self._key = key + self._cache = defaultdict(deque) + self._validator = validator or (lambda x: True) + + def __contains__(self, value): + if not self._validator(value): + return False + + try: + item = next(self[value]) + except StopIteration: + return False + else: + self._cache[value].appendleft(item) + + return True + + def _get_values(self, value): + """ + Helper to yield items from the parent iterator that match *value*. + Items that don't match are stored in the local cache as they + are encountered. + """ + while True: + # If we've cached some items that match the target value, emit + # the first one and evict it from the cache. + if self._cache[value]: + yield self._cache[value].popleft() + # Otherwise we need to advance the parent iterator to search for + # a matching item, caching the rest. + else: + while True: + try: + item = next(self._it) + except StopIteration: + return + item_value = self._key(item) + if item_value == value: + yield item + break + elif self._validator(item_value): + self._cache[item_value].append(item) + + def __iter__(self): + for item in self._it: + item_value = self._key(item) + if self._validator(item_value): + self._cache[item_value].append(item) + + yield from self._cache.keys() + + def __getitem__(self, value): + if not self._validator(value): + return iter(()) + + return self._get_values(value) + + +def spy(iterable, n=1): + """Return a 2-tuple with a list containing the first *n* elements of + *iterable*, and an iterator with the same items as *iterable*. + This allows you to "look ahead" at the items in the iterable without + advancing it. + + There is one item in the list by default: + + >>> iterable = 'abcdefg' + >>> head, iterable = spy(iterable) + >>> head + ['a'] + >>> list(iterable) + ['a', 'b', 'c', 'd', 'e', 'f', 'g'] + + You may use unpacking to retrieve items instead of lists: + + >>> (head,), iterable = spy('abcdefg') + >>> head + 'a' + >>> (first, second), iterable = spy('abcdefg', 2) + >>> first + 'a' + >>> second + 'b' + + The number of items requested can be larger than the number of items in + the iterable: + + >>> iterable = [1, 2, 3, 4, 5] + >>> head, iterable = spy(iterable, 10) + >>> head + [1, 2, 3, 4, 5] + >>> list(iterable) + [1, 2, 3, 4, 5] + + """ + it = iter(iterable) + head = take(n, it) + + return head.copy(), chain(head, it) + + +def interleave(*iterables): + """Return a new iterable yielding from each iterable in turn, + until the shortest is exhausted. + + >>> list(interleave([1, 2, 3], [4, 5], [6, 7, 8])) + [1, 4, 6, 2, 5, 7] + + For a version that doesn't terminate after the shortest iterable is + exhausted, see :func:`interleave_longest`. + + """ + return chain.from_iterable(zip(*iterables)) + + +def interleave_longest(*iterables): + """Return a new iterable yielding from each iterable in turn, + skipping any that are exhausted. + + >>> list(interleave_longest([1, 2, 3], [4, 5], [6, 7, 8])) + [1, 4, 6, 2, 5, 7, 3, 8] + + This function produces the same output as :func:`roundrobin`, but may + perform better for some inputs (in particular when the number of iterables + is large). + + """ + i = chain.from_iterable(zip_longest(*iterables, fillvalue=_marker)) + return (x for x in i if x is not _marker) + + +def collapse(iterable, base_type=None, levels=None): + """Flatten an iterable with multiple levels of nesting (e.g., a list of + lists of tuples) into non-iterable types. + + >>> iterable = [(1, 2), ([3, 4], [[5], [6]])] + >>> list(collapse(iterable)) + [1, 2, 3, 4, 5, 6] + + Binary and text strings are not considered iterable and + will not be collapsed. + + To avoid collapsing other types, specify *base_type*: + + >>> iterable = ['ab', ('cd', 'ef'), ['gh', 'ij']] + >>> list(collapse(iterable, base_type=tuple)) + ['ab', ('cd', 'ef'), 'gh', 'ij'] + + Specify *levels* to stop flattening after a certain level: + + >>> iterable = [('a', ['b']), ('c', ['d'])] + >>> list(collapse(iterable)) # Fully flattened + ['a', 'b', 'c', 'd'] + >>> list(collapse(iterable, levels=1)) # Only one level flattened + ['a', ['b'], 'c', ['d']] + + """ + + def walk(node, level): + if ( + ((levels is not None) and (level > levels)) + or isinstance(node, (str, bytes)) + or ((base_type is not None) and isinstance(node, base_type)) + ): + yield node + return + + try: + tree = iter(node) + except TypeError: + yield node + return + else: + for child in tree: + yield from walk(child, level + 1) + + yield from walk(iterable, 0) + + +def side_effect(func, iterable, chunk_size=None, before=None, after=None): + """Invoke *func* on each item in *iterable* (or on each *chunk_size* group + of items) before yielding the item. + + `func` must be a function that takes a single argument. Its return value + will be discarded. + + *before* and *after* are optional functions that take no arguments. They + will be executed before iteration starts and after it ends, respectively. + + `side_effect` can be used for logging, updating progress bars, or anything + that is not functionally "pure." + + Emitting a status message: + + >>> from more_itertools import consume + >>> func = lambda item: print('Received {}'.format(item)) + >>> consume(side_effect(func, range(2))) + Received 0 + Received 1 + + Operating on chunks of items: + + >>> pair_sums = [] + >>> func = lambda chunk: pair_sums.append(sum(chunk)) + >>> list(side_effect(func, [0, 1, 2, 3, 4, 5], 2)) + [0, 1, 2, 3, 4, 5] + >>> list(pair_sums) + [1, 5, 9] + + Writing to a file-like object: + + >>> from io import StringIO + >>> from more_itertools import consume + >>> f = StringIO() + >>> func = lambda x: print(x, file=f) + >>> before = lambda: print(u'HEADER', file=f) + >>> after = f.close + >>> it = [u'a', u'b', u'c'] + >>> consume(side_effect(func, it, before=before, after=after)) + >>> f.closed + True + + """ + try: + if before is not None: + before() + + if chunk_size is None: + for item in iterable: + func(item) + yield item + else: + for chunk in chunked(iterable, chunk_size): + func(chunk) + yield from chunk + finally: + if after is not None: + after() + + +def sliced(seq, n, strict=False): + """Yield slices of length *n* from the sequence *seq*. + + >>> list(sliced((1, 2, 3, 4, 5, 6), 3)) + [(1, 2, 3), (4, 5, 6)] + + By the default, the last yielded slice will have fewer than *n* elements + if the length of *seq* is not divisible by *n*: + + >>> list(sliced((1, 2, 3, 4, 5, 6, 7, 8), 3)) + [(1, 2, 3), (4, 5, 6), (7, 8)] + + If the length of *seq* is not divisible by *n* and *strict* is + ``True``, then ``ValueError`` will be raised before the last + slice is yielded. + + This function will only work for iterables that support slicing. + For non-sliceable iterables, see :func:`chunked`. + + """ + iterator = takewhile(len, (seq[i : i + n] for i in count(0, n))) + if strict: + + def ret(): + for _slice in iterator: + if len(_slice) != n: + raise ValueError("seq is not divisible by n.") + yield _slice + + return iter(ret()) + else: + return iterator + + +def split_at(iterable, pred, maxsplit=-1, keep_separator=False): + """Yield lists of items from *iterable*, where each list is delimited by + an item where callable *pred* returns ``True``. + + >>> list(split_at('abcdcba', lambda x: x == 'b')) + [['a'], ['c', 'd', 'c'], ['a']] + + >>> list(split_at(range(10), lambda n: n % 2 == 1)) + [[0], [2], [4], [6], [8], []] + + At most *maxsplit* splits are done. If *maxsplit* is not specified or -1, + then there is no limit on the number of splits: + + >>> list(split_at(range(10), lambda n: n % 2 == 1, maxsplit=2)) + [[0], [2], [4, 5, 6, 7, 8, 9]] + + By default, the delimiting items are not included in the output. + The include them, set *keep_separator* to ``True``. + + >>> list(split_at('abcdcba', lambda x: x == 'b', keep_separator=True)) + [['a'], ['b'], ['c', 'd', 'c'], ['b'], ['a']] + + """ + if maxsplit == 0: + yield list(iterable) + return + + buf = [] + it = iter(iterable) + for item in it: + if pred(item): + yield buf + if keep_separator: + yield [item] + if maxsplit == 1: + yield list(it) + return + buf = [] + maxsplit -= 1 + else: + buf.append(item) + yield buf + + +def split_before(iterable, pred, maxsplit=-1): + """Yield lists of items from *iterable*, where each list ends just before + an item for which callable *pred* returns ``True``: + + >>> list(split_before('OneTwo', lambda s: s.isupper())) + [['O', 'n', 'e'], ['T', 'w', 'o']] + + >>> list(split_before(range(10), lambda n: n % 3 == 0)) + [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]] + + At most *maxsplit* splits are done. If *maxsplit* is not specified or -1, + then there is no limit on the number of splits: + + >>> list(split_before(range(10), lambda n: n % 3 == 0, maxsplit=2)) + [[0, 1, 2], [3, 4, 5], [6, 7, 8, 9]] + """ + if maxsplit == 0: + yield list(iterable) + return + + buf = [] + it = iter(iterable) + for item in it: + if pred(item) and buf: + yield buf + if maxsplit == 1: + yield [item] + list(it) + return + buf = [] + maxsplit -= 1 + buf.append(item) + if buf: + yield buf + + +def split_after(iterable, pred, maxsplit=-1): + """Yield lists of items from *iterable*, where each list ends with an + item where callable *pred* returns ``True``: + + >>> list(split_after('one1two2', lambda s: s.isdigit())) + [['o', 'n', 'e', '1'], ['t', 'w', 'o', '2']] + + >>> list(split_after(range(10), lambda n: n % 3 == 0)) + [[0], [1, 2, 3], [4, 5, 6], [7, 8, 9]] + + At most *maxsplit* splits are done. If *maxsplit* is not specified or -1, + then there is no limit on the number of splits: + + >>> list(split_after(range(10), lambda n: n % 3 == 0, maxsplit=2)) + [[0], [1, 2, 3], [4, 5, 6, 7, 8, 9]] + + """ + if maxsplit == 0: + yield list(iterable) + return + + buf = [] + it = iter(iterable) + for item in it: + buf.append(item) + if pred(item) and buf: + yield buf + if maxsplit == 1: + yield list(it) + return + buf = [] + maxsplit -= 1 + if buf: + yield buf + + +def split_when(iterable, pred, maxsplit=-1): + """Split *iterable* into pieces based on the output of *pred*. + *pred* should be a function that takes successive pairs of items and + returns ``True`` if the iterable should be split in between them. + + For example, to find runs of increasing numbers, split the iterable when + element ``i`` is larger than element ``i + 1``: + + >>> list(split_when([1, 2, 3, 3, 2, 5, 2, 4, 2], lambda x, y: x > y)) + [[1, 2, 3, 3], [2, 5], [2, 4], [2]] + + At most *maxsplit* splits are done. If *maxsplit* is not specified or -1, + then there is no limit on the number of splits: + + >>> list(split_when([1, 2, 3, 3, 2, 5, 2, 4, 2], + ... lambda x, y: x > y, maxsplit=2)) + [[1, 2, 3, 3], [2, 5], [2, 4, 2]] + + """ + if maxsplit == 0: + yield list(iterable) + return + + it = iter(iterable) + try: + cur_item = next(it) + except StopIteration: + return + + buf = [cur_item] + for next_item in it: + if pred(cur_item, next_item): + yield buf + if maxsplit == 1: + yield [next_item] + list(it) + return + buf = [] + maxsplit -= 1 + + buf.append(next_item) + cur_item = next_item + + yield buf + + +def split_into(iterable, sizes): + """Yield a list of sequential items from *iterable* of length 'n' for each + integer 'n' in *sizes*. + + >>> list(split_into([1,2,3,4,5,6], [1,2,3])) + [[1], [2, 3], [4, 5, 6]] + + If the sum of *sizes* is smaller than the length of *iterable*, then the + remaining items of *iterable* will not be returned. + + >>> list(split_into([1,2,3,4,5,6], [2,3])) + [[1, 2], [3, 4, 5]] + + If the sum of *sizes* is larger than the length of *iterable*, fewer items + will be returned in the iteration that overruns *iterable* and further + lists will be empty: + + >>> list(split_into([1,2,3,4], [1,2,3,4])) + [[1], [2, 3], [4], []] + + When a ``None`` object is encountered in *sizes*, the returned list will + contain items up to the end of *iterable* the same way that itertools.slice + does: + + >>> list(split_into([1,2,3,4,5,6,7,8,9,0], [2,3,None])) + [[1, 2], [3, 4, 5], [6, 7, 8, 9, 0]] + + :func:`split_into` can be useful for grouping a series of items where the + sizes of the groups are not uniform. An example would be where in a row + from a table, multiple columns represent elements of the same feature + (e.g. a point represented by x,y,z) but, the format is not the same for + all columns. + """ + # convert the iterable argument into an iterator so its contents can + # be consumed by islice in case it is a generator + it = iter(iterable) + + for size in sizes: + if size is None: + yield list(it) + return + else: + yield list(islice(it, size)) + + +def padded(iterable, fillvalue=None, n=None, next_multiple=False): + """Yield the elements from *iterable*, followed by *fillvalue*, such that + at least *n* items are emitted. + + >>> list(padded([1, 2, 3], '?', 5)) + [1, 2, 3, '?', '?'] + + If *next_multiple* is ``True``, *fillvalue* will be emitted until the + number of items emitted is a multiple of *n*:: + + >>> list(padded([1, 2, 3, 4], n=3, next_multiple=True)) + [1, 2, 3, 4, None, None] + + If *n* is ``None``, *fillvalue* will be emitted indefinitely. + + """ + it = iter(iterable) + if n is None: + yield from chain(it, repeat(fillvalue)) + elif n < 1: + raise ValueError('n must be at least 1') + else: + item_count = 0 + for item in it: + yield item + item_count += 1 + + remaining = (n - item_count) % n if next_multiple else n - item_count + for _ in range(remaining): + yield fillvalue + + +def repeat_last(iterable, default=None): + """After the *iterable* is exhausted, keep yielding its last element. + + >>> list(islice(repeat_last(range(3)), 5)) + [0, 1, 2, 2, 2] + + If the iterable is empty, yield *default* forever:: + + >>> list(islice(repeat_last(range(0), 42), 5)) + [42, 42, 42, 42, 42] + + """ + item = _marker + for item in iterable: + yield item + final = default if item is _marker else item + yield from repeat(final) + + +def distribute(n, iterable): + """Distribute the items from *iterable* among *n* smaller iterables. + + >>> group_1, group_2 = distribute(2, [1, 2, 3, 4, 5, 6]) + >>> list(group_1) + [1, 3, 5] + >>> list(group_2) + [2, 4, 6] + + If the length of *iterable* is not evenly divisible by *n*, then the + length of the returned iterables will not be identical: + + >>> children = distribute(3, [1, 2, 3, 4, 5, 6, 7]) + >>> [list(c) for c in children] + [[1, 4, 7], [2, 5], [3, 6]] + + If the length of *iterable* is smaller than *n*, then the last returned + iterables will be empty: + + >>> children = distribute(5, [1, 2, 3]) + >>> [list(c) for c in children] + [[1], [2], [3], [], []] + + This function uses :func:`itertools.tee` and may require significant + storage. If you need the order items in the smaller iterables to match the + original iterable, see :func:`divide`. + + """ + if n < 1: + raise ValueError('n must be at least 1') + + children = tee(iterable, n) + return [islice(it, index, None, n) for index, it in enumerate(children)] + + +def stagger(iterable, offsets=(-1, 0, 1), longest=False, fillvalue=None): + """Yield tuples whose elements are offset from *iterable*. + The amount by which the `i`-th item in each tuple is offset is given by + the `i`-th item in *offsets*. + + >>> list(stagger([0, 1, 2, 3])) + [(None, 0, 1), (0, 1, 2), (1, 2, 3)] + >>> list(stagger(range(8), offsets=(0, 2, 4))) + [(0, 2, 4), (1, 3, 5), (2, 4, 6), (3, 5, 7)] + + By default, the sequence will end when the final element of a tuple is the + last item in the iterable. To continue until the first element of a tuple + is the last item in the iterable, set *longest* to ``True``:: + + >>> list(stagger([0, 1, 2, 3], longest=True)) + [(None, 0, 1), (0, 1, 2), (1, 2, 3), (2, 3, None), (3, None, None)] + + By default, ``None`` will be used to replace offsets beyond the end of the + sequence. Specify *fillvalue* to use some other value. + + """ + children = tee(iterable, len(offsets)) + + return zip_offset( + *children, offsets=offsets, longest=longest, fillvalue=fillvalue + ) + + +class UnequalIterablesError(ValueError): + def __init__(self, details=None): + msg = 'Iterables have different lengths' + if details is not None: + msg += (': index 0 has length {}; index {} has length {}').format( + *details + ) + + super().__init__(msg) + + +def _zip_equal_generator(iterables): + for combo in zip_longest(*iterables, fillvalue=_marker): + for val in combo: + if val is _marker: + raise UnequalIterablesError() + yield combo + + +def zip_equal(*iterables): + """``zip`` the input *iterables* together, but raise + ``UnequalIterablesError`` if they aren't all the same length. + + >>> it_1 = range(3) + >>> it_2 = iter('abc') + >>> list(zip_equal(it_1, it_2)) + [(0, 'a'), (1, 'b'), (2, 'c')] + + >>> it_1 = range(3) + >>> it_2 = iter('abcd') + >>> list(zip_equal(it_1, it_2)) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + more_itertools.more.UnequalIterablesError: Iterables have different + lengths + + """ + if hexversion >= 0x30A00A6: + warnings.warn( + ( + 'zip_equal will be removed in a future version of ' + 'more-itertools. Use the builtin zip function with ' + 'strict=True instead.' + ), + DeprecationWarning, + ) + # Check whether the iterables are all the same size. + try: + first_size = len(iterables[0]) + for i, it in enumerate(iterables[1:], 1): + size = len(it) + if size != first_size: + break + else: + # If we didn't break out, we can use the built-in zip. + return zip(*iterables) + + # If we did break out, there was a mismatch. + raise UnequalIterablesError(details=(first_size, i, size)) + # If any one of the iterables didn't have a length, start reading + # them until one runs out. + except TypeError: + return _zip_equal_generator(iterables) + + +def zip_offset(*iterables, offsets, longest=False, fillvalue=None): + """``zip`` the input *iterables* together, but offset the `i`-th iterable + by the `i`-th item in *offsets*. + + >>> list(zip_offset('0123', 'abcdef', offsets=(0, 1))) + [('0', 'b'), ('1', 'c'), ('2', 'd'), ('3', 'e')] + + This can be used as a lightweight alternative to SciPy or pandas to analyze + data sets in which some series have a lead or lag relationship. + + By default, the sequence will end when the shortest iterable is exhausted. + To continue until the longest iterable is exhausted, set *longest* to + ``True``. + + >>> list(zip_offset('0123', 'abcdef', offsets=(0, 1), longest=True)) + [('0', 'b'), ('1', 'c'), ('2', 'd'), ('3', 'e'), (None, 'f')] + + By default, ``None`` will be used to replace offsets beyond the end of the + sequence. Specify *fillvalue* to use some other value. + + """ + if len(iterables) != len(offsets): + raise ValueError("Number of iterables and offsets didn't match") + + staggered = [] + for it, n in zip(iterables, offsets): + if n < 0: + staggered.append(chain(repeat(fillvalue, -n), it)) + elif n > 0: + staggered.append(islice(it, n, None)) + else: + staggered.append(it) + + if longest: + return zip_longest(*staggered, fillvalue=fillvalue) + + return zip(*staggered) + + +def sort_together(iterables, key_list=(0,), key=None, reverse=False): + """Return the input iterables sorted together, with *key_list* as the + priority for sorting. All iterables are trimmed to the length of the + shortest one. + + This can be used like the sorting function in a spreadsheet. If each + iterable represents a column of data, the key list determines which + columns are used for sorting. + + By default, all iterables are sorted using the ``0``-th iterable:: + + >>> iterables = [(4, 3, 2, 1), ('a', 'b', 'c', 'd')] + >>> sort_together(iterables) + [(1, 2, 3, 4), ('d', 'c', 'b', 'a')] + + Set a different key list to sort according to another iterable. + Specifying multiple keys dictates how ties are broken:: + + >>> iterables = [(3, 1, 2), (0, 1, 0), ('c', 'b', 'a')] + >>> sort_together(iterables, key_list=(1, 2)) + [(2, 3, 1), (0, 0, 1), ('a', 'c', 'b')] + + To sort by a function of the elements of the iterable, pass a *key* + function. Its arguments are the elements of the iterables corresponding to + the key list:: + + >>> names = ('a', 'b', 'c') + >>> lengths = (1, 2, 3) + >>> widths = (5, 2, 1) + >>> def area(length, width): + ... return length * width + >>> sort_together([names, lengths, widths], key_list=(1, 2), key=area) + [('c', 'b', 'a'), (3, 2, 1), (1, 2, 5)] + + Set *reverse* to ``True`` to sort in descending order. + + >>> sort_together([(1, 2, 3), ('c', 'b', 'a')], reverse=True) + [(3, 2, 1), ('a', 'b', 'c')] + + """ + if key is None: + # if there is no key function, the key argument to sorted is an + # itemgetter + key_argument = itemgetter(*key_list) + else: + # if there is a key function, call it with the items at the offsets + # specified by the key function as arguments + key_list = list(key_list) + if len(key_list) == 1: + # if key_list contains a single item, pass the item at that offset + # as the only argument to the key function + key_offset = key_list[0] + key_argument = lambda zipped_items: key(zipped_items[key_offset]) + else: + # if key_list contains multiple items, use itemgetter to return a + # tuple of items, which we pass as *args to the key function + get_key_items = itemgetter(*key_list) + key_argument = lambda zipped_items: key( + *get_key_items(zipped_items) + ) + + return list( + zip(*sorted(zip(*iterables), key=key_argument, reverse=reverse)) + ) + + +def unzip(iterable): + """The inverse of :func:`zip`, this function disaggregates the elements + of the zipped *iterable*. + + The ``i``-th iterable contains the ``i``-th element from each element + of the zipped iterable. The first element is used to to determine the + length of the remaining elements. + + >>> iterable = [('a', 1), ('b', 2), ('c', 3), ('d', 4)] + >>> letters, numbers = unzip(iterable) + >>> list(letters) + ['a', 'b', 'c', 'd'] + >>> list(numbers) + [1, 2, 3, 4] + + This is similar to using ``zip(*iterable)``, but it avoids reading + *iterable* into memory. Note, however, that this function uses + :func:`itertools.tee` and thus may require significant storage. + + """ + head, iterable = spy(iter(iterable)) + if not head: + # empty iterable, e.g. zip([], [], []) + return () + # spy returns a one-length iterable as head + head = head[0] + iterables = tee(iterable, len(head)) + + def itemgetter(i): + def getter(obj): + try: + return obj[i] + except IndexError: + # basically if we have an iterable like + # iter([(1, 2, 3), (4, 5), (6,)]) + # the second unzipped iterable would fail at the third tuple + # since it would try to access tup[1] + # same with the third unzipped iterable and the second tuple + # to support these "improperly zipped" iterables, + # we create a custom itemgetter + # which just stops the unzipped iterables + # at first length mismatch + raise StopIteration + + return getter + + return tuple(map(itemgetter(i), it) for i, it in enumerate(iterables)) + + +def divide(n, iterable): + """Divide the elements from *iterable* into *n* parts, maintaining + order. + + >>> group_1, group_2 = divide(2, [1, 2, 3, 4, 5, 6]) + >>> list(group_1) + [1, 2, 3] + >>> list(group_2) + [4, 5, 6] + + If the length of *iterable* is not evenly divisible by *n*, then the + length of the returned iterables will not be identical: + + >>> children = divide(3, [1, 2, 3, 4, 5, 6, 7]) + >>> [list(c) for c in children] + [[1, 2, 3], [4, 5], [6, 7]] + + If the length of the iterable is smaller than n, then the last returned + iterables will be empty: + + >>> children = divide(5, [1, 2, 3]) + >>> [list(c) for c in children] + [[1], [2], [3], [], []] + + This function will exhaust the iterable before returning and may require + significant storage. If order is not important, see :func:`distribute`, + which does not first pull the iterable into memory. + + """ + if n < 1: + raise ValueError('n must be at least 1') + + try: + iterable[:0] + except TypeError: + seq = tuple(iterable) + else: + seq = iterable + + q, r = divmod(len(seq), n) + + ret = [] + stop = 0 + for i in range(1, n + 1): + start = stop + stop += q + 1 if i <= r else q + ret.append(iter(seq[start:stop])) + + return ret + + +def always_iterable(obj, base_type=(str, bytes)): + """If *obj* is iterable, return an iterator over its items:: + + >>> obj = (1, 2, 3) + >>> list(always_iterable(obj)) + [1, 2, 3] + + If *obj* is not iterable, return a one-item iterable containing *obj*:: + + >>> obj = 1 + >>> list(always_iterable(obj)) + [1] + + If *obj* is ``None``, return an empty iterable: + + >>> obj = None + >>> list(always_iterable(None)) + [] + + By default, binary and text strings are not considered iterable:: + + >>> obj = 'foo' + >>> list(always_iterable(obj)) + ['foo'] + + If *base_type* is set, objects for which ``isinstance(obj, base_type)`` + returns ``True`` won't be considered iterable. + + >>> obj = {'a': 1} + >>> list(always_iterable(obj)) # Iterate over the dict's keys + ['a'] + >>> list(always_iterable(obj, base_type=dict)) # Treat dicts as a unit + [{'a': 1}] + + Set *base_type* to ``None`` to avoid any special handling and treat objects + Python considers iterable as iterable: + + >>> obj = 'foo' + >>> list(always_iterable(obj, base_type=None)) + ['f', 'o', 'o'] + """ + if obj is None: + return iter(()) + + if (base_type is not None) and isinstance(obj, base_type): + return iter((obj,)) + + try: + return iter(obj) + except TypeError: + return iter((obj,)) + + +def adjacent(predicate, iterable, distance=1): + """Return an iterable over `(bool, item)` tuples where the `item` is + drawn from *iterable* and the `bool` indicates whether + that item satisfies the *predicate* or is adjacent to an item that does. + + For example, to find whether items are adjacent to a ``3``:: + + >>> list(adjacent(lambda x: x == 3, range(6))) + [(False, 0), (False, 1), (True, 2), (True, 3), (True, 4), (False, 5)] + + Set *distance* to change what counts as adjacent. For example, to find + whether items are two places away from a ``3``: + + >>> list(adjacent(lambda x: x == 3, range(6), distance=2)) + [(False, 0), (True, 1), (True, 2), (True, 3), (True, 4), (True, 5)] + + This is useful for contextualizing the results of a search function. + For example, a code comparison tool might want to identify lines that + have changed, but also surrounding lines to give the viewer of the diff + context. + + The predicate function will only be called once for each item in the + iterable. + + See also :func:`groupby_transform`, which can be used with this function + to group ranges of items with the same `bool` value. + + """ + # Allow distance=0 mainly for testing that it reproduces results with map() + if distance < 0: + raise ValueError('distance must be at least 0') + + i1, i2 = tee(iterable) + padding = [False] * distance + selected = chain(padding, map(predicate, i1), padding) + adjacent_to_selected = map(any, windowed(selected, 2 * distance + 1)) + return zip(adjacent_to_selected, i2) + + +def groupby_transform(iterable, keyfunc=None, valuefunc=None, reducefunc=None): + """An extension of :func:`itertools.groupby` that can apply transformations + to the grouped data. + + * *keyfunc* is a function computing a key value for each item in *iterable* + * *valuefunc* is a function that transforms the individual items from + *iterable* after grouping + * *reducefunc* is a function that transforms each group of items + + >>> iterable = 'aAAbBBcCC' + >>> keyfunc = lambda k: k.upper() + >>> valuefunc = lambda v: v.lower() + >>> reducefunc = lambda g: ''.join(g) + >>> list(groupby_transform(iterable, keyfunc, valuefunc, reducefunc)) + [('A', 'aaa'), ('B', 'bbb'), ('C', 'ccc')] + + Each optional argument defaults to an identity function if not specified. + + :func:`groupby_transform` is useful when grouping elements of an iterable + using a separate iterable as the key. To do this, :func:`zip` the iterables + and pass a *keyfunc* that extracts the first element and a *valuefunc* + that extracts the second element:: + + >>> from operator import itemgetter + >>> keys = [0, 0, 1, 1, 1, 2, 2, 2, 3] + >>> values = 'abcdefghi' + >>> iterable = zip(keys, values) + >>> grouper = groupby_transform(iterable, itemgetter(0), itemgetter(1)) + >>> [(k, ''.join(g)) for k, g in grouper] + [(0, 'ab'), (1, 'cde'), (2, 'fgh'), (3, 'i')] + + Note that the order of items in the iterable is significant. + Only adjacent items are grouped together, so if you don't want any + duplicate groups, you should sort the iterable by the key function. + + """ + ret = groupby(iterable, keyfunc) + if valuefunc: + ret = ((k, map(valuefunc, g)) for k, g in ret) + if reducefunc: + ret = ((k, reducefunc(g)) for k, g in ret) + + return ret + + +class numeric_range(abc.Sequence, abc.Hashable): + """An extension of the built-in ``range()`` function whose arguments can + be any orderable numeric type. + + With only *stop* specified, *start* defaults to ``0`` and *step* + defaults to ``1``. The output items will match the type of *stop*: + + >>> list(numeric_range(3.5)) + [0.0, 1.0, 2.0, 3.0] + + With only *start* and *stop* specified, *step* defaults to ``1``. The + output items will match the type of *start*: + + >>> from decimal import Decimal + >>> start = Decimal('2.1') + >>> stop = Decimal('5.1') + >>> list(numeric_range(start, stop)) + [Decimal('2.1'), Decimal('3.1'), Decimal('4.1')] + + With *start*, *stop*, and *step* specified the output items will match + the type of ``start + step``: + + >>> from fractions import Fraction + >>> start = Fraction(1, 2) # Start at 1/2 + >>> stop = Fraction(5, 2) # End at 5/2 + >>> step = Fraction(1, 2) # Count by 1/2 + >>> list(numeric_range(start, stop, step)) + [Fraction(1, 2), Fraction(1, 1), Fraction(3, 2), Fraction(2, 1)] + + If *step* is zero, ``ValueError`` is raised. Negative steps are supported: + + >>> list(numeric_range(3, -1, -1.0)) + [3.0, 2.0, 1.0, 0.0] + + Be aware of the limitations of floating point numbers; the representation + of the yielded numbers may be surprising. + + ``datetime.datetime`` objects can be used for *start* and *stop*, if *step* + is a ``datetime.timedelta`` object: + + >>> import datetime + >>> start = datetime.datetime(2019, 1, 1) + >>> stop = datetime.datetime(2019, 1, 3) + >>> step = datetime.timedelta(days=1) + >>> items = iter(numeric_range(start, stop, step)) + >>> next(items) + datetime.datetime(2019, 1, 1, 0, 0) + >>> next(items) + datetime.datetime(2019, 1, 2, 0, 0) + + """ + + _EMPTY_HASH = hash(range(0, 0)) + + def __init__(self, *args): + argc = len(args) + if argc == 1: + (self._stop,) = args + self._start = type(self._stop)(0) + self._step = type(self._stop - self._start)(1) + elif argc == 2: + self._start, self._stop = args + self._step = type(self._stop - self._start)(1) + elif argc == 3: + self._start, self._stop, self._step = args + elif argc == 0: + raise TypeError( + 'numeric_range expected at least ' + '1 argument, got {}'.format(argc) + ) + else: + raise TypeError( + 'numeric_range expected at most ' + '3 arguments, got {}'.format(argc) + ) + + self._zero = type(self._step)(0) + if self._step == self._zero: + raise ValueError('numeric_range() arg 3 must not be zero') + self._growing = self._step > self._zero + self._init_len() + + def __bool__(self): + if self._growing: + return self._start < self._stop + else: + return self._start > self._stop + + def __contains__(self, elem): + if self._growing: + if self._start <= elem < self._stop: + return (elem - self._start) % self._step == self._zero + else: + if self._start >= elem > self._stop: + return (self._start - elem) % (-self._step) == self._zero + + return False + + def __eq__(self, other): + if isinstance(other, numeric_range): + empty_self = not bool(self) + empty_other = not bool(other) + if empty_self or empty_other: + return empty_self and empty_other # True if both empty + else: + return ( + self._start == other._start + and self._step == other._step + and self._get_by_index(-1) == other._get_by_index(-1) + ) + else: + return False + + def __getitem__(self, key): + if isinstance(key, int): + return self._get_by_index(key) + elif isinstance(key, slice): + step = self._step if key.step is None else key.step * self._step + + if key.start is None or key.start <= -self._len: + start = self._start + elif key.start >= self._len: + start = self._stop + else: # -self._len < key.start < self._len + start = self._get_by_index(key.start) + + if key.stop is None or key.stop >= self._len: + stop = self._stop + elif key.stop <= -self._len: + stop = self._start + else: # -self._len < key.stop < self._len + stop = self._get_by_index(key.stop) + + return numeric_range(start, stop, step) + else: + raise TypeError( + 'numeric range indices must be ' + 'integers or slices, not {}'.format(type(key).__name__) + ) + + def __hash__(self): + if self: + return hash((self._start, self._get_by_index(-1), self._step)) + else: + return self._EMPTY_HASH + + def __iter__(self): + values = (self._start + (n * self._step) for n in count()) + if self._growing: + return takewhile(partial(gt, self._stop), values) + else: + return takewhile(partial(lt, self._stop), values) + + def __len__(self): + return self._len + + def _init_len(self): + if self._growing: + start = self._start + stop = self._stop + step = self._step + else: + start = self._stop + stop = self._start + step = -self._step + distance = stop - start + if distance <= self._zero: + self._len = 0 + else: # distance > 0 and step > 0: regular euclidean division + q, r = divmod(distance, step) + self._len = int(q) + int(r != self._zero) + + def __reduce__(self): + return numeric_range, (self._start, self._stop, self._step) + + def __repr__(self): + if self._step == 1: + return "numeric_range({}, {})".format( + repr(self._start), repr(self._stop) + ) + else: + return "numeric_range({}, {}, {})".format( + repr(self._start), repr(self._stop), repr(self._step) + ) + + def __reversed__(self): + return iter( + numeric_range( + self._get_by_index(-1), self._start - self._step, -self._step + ) + ) + + def count(self, value): + return int(value in self) + + def index(self, value): + if self._growing: + if self._start <= value < self._stop: + q, r = divmod(value - self._start, self._step) + if r == self._zero: + return int(q) + else: + if self._start >= value > self._stop: + q, r = divmod(self._start - value, -self._step) + if r == self._zero: + return int(q) + + raise ValueError("{} is not in numeric range".format(value)) + + def _get_by_index(self, i): + if i < 0: + i += self._len + if i < 0 or i >= self._len: + raise IndexError("numeric range object index out of range") + return self._start + i * self._step + + +def count_cycle(iterable, n=None): + """Cycle through the items from *iterable* up to *n* times, yielding + the number of completed cycles along with each item. If *n* is omitted the + process repeats indefinitely. + + >>> list(count_cycle('AB', 3)) + [(0, 'A'), (0, 'B'), (1, 'A'), (1, 'B'), (2, 'A'), (2, 'B')] + + """ + iterable = tuple(iterable) + if not iterable: + return iter(()) + counter = count() if n is None else range(n) + return ((i, item) for i in counter for item in iterable) + + +def mark_ends(iterable): + """Yield 3-tuples of the form ``(is_first, is_last, item)``. + + >>> list(mark_ends('ABC')) + [(True, False, 'A'), (False, False, 'B'), (False, True, 'C')] + + Use this when looping over an iterable to take special action on its first + and/or last items: + + >>> iterable = ['Header', 100, 200, 'Footer'] + >>> total = 0 + >>> for is_first, is_last, item in mark_ends(iterable): + ... if is_first: + ... continue # Skip the header + ... if is_last: + ... continue # Skip the footer + ... total += item + >>> print(total) + 300 + """ + it = iter(iterable) + + try: + b = next(it) + except StopIteration: + return + + try: + for i in count(): + a = b + b = next(it) + yield i == 0, False, a + + except StopIteration: + yield i == 0, True, a + + +def locate(iterable, pred=bool, window_size=None): + """Yield the index of each item in *iterable* for which *pred* returns + ``True``. + + *pred* defaults to :func:`bool`, which will select truthy items: + + >>> list(locate([0, 1, 1, 0, 1, 0, 0])) + [1, 2, 4] + + Set *pred* to a custom function to, e.g., find the indexes for a particular + item. + + >>> list(locate(['a', 'b', 'c', 'b'], lambda x: x == 'b')) + [1, 3] + + If *window_size* is given, then the *pred* function will be called with + that many items. This enables searching for sub-sequences: + + >>> iterable = [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3] + >>> pred = lambda *args: args == (1, 2, 3) + >>> list(locate(iterable, pred=pred, window_size=3)) + [1, 5, 9] + + Use with :func:`seekable` to find indexes and then retrieve the associated + items: + + >>> from itertools import count + >>> from more_itertools import seekable + >>> source = (3 * n + 1 if (n % 2) else n // 2 for n in count()) + >>> it = seekable(source) + >>> pred = lambda x: x > 100 + >>> indexes = locate(it, pred=pred) + >>> i = next(indexes) + >>> it.seek(i) + >>> next(it) + 106 + + """ + if window_size is None: + return compress(count(), map(pred, iterable)) + + if window_size < 1: + raise ValueError('window size must be at least 1') + + it = windowed(iterable, window_size, fillvalue=_marker) + return compress(count(), starmap(pred, it)) + + +def lstrip(iterable, pred): + """Yield the items from *iterable*, but strip any from the beginning + for which *pred* returns ``True``. + + For example, to remove a set of items from the start of an iterable: + + >>> iterable = (None, False, None, 1, 2, None, 3, False, None) + >>> pred = lambda x: x in {None, False, ''} + >>> list(lstrip(iterable, pred)) + [1, 2, None, 3, False, None] + + This function is analogous to to :func:`str.lstrip`, and is essentially + an wrapper for :func:`itertools.dropwhile`. + + """ + return dropwhile(pred, iterable) + + +def rstrip(iterable, pred): + """Yield the items from *iterable*, but strip any from the end + for which *pred* returns ``True``. + + For example, to remove a set of items from the end of an iterable: + + >>> iterable = (None, False, None, 1, 2, None, 3, False, None) + >>> pred = lambda x: x in {None, False, ''} + >>> list(rstrip(iterable, pred)) + [None, False, None, 1, 2, None, 3] + + This function is analogous to :func:`str.rstrip`. + + """ + cache = [] + cache_append = cache.append + cache_clear = cache.clear + for x in iterable: + if pred(x): + cache_append(x) + else: + yield from cache + cache_clear() + yield x + + +def strip(iterable, pred): + """Yield the items from *iterable*, but strip any from the + beginning and end for which *pred* returns ``True``. + + For example, to remove a set of items from both ends of an iterable: + + >>> iterable = (None, False, None, 1, 2, None, 3, False, None) + >>> pred = lambda x: x in {None, False, ''} + >>> list(strip(iterable, pred)) + [1, 2, None, 3] + + This function is analogous to :func:`str.strip`. + + """ + return rstrip(lstrip(iterable, pred), pred) + + +class islice_extended: + """An extension of :func:`itertools.islice` that supports negative values + for *stop*, *start*, and *step*. + + >>> iterable = iter('abcdefgh') + >>> list(islice_extended(iterable, -4, -1)) + ['e', 'f', 'g'] + + Slices with negative values require some caching of *iterable*, but this + function takes care to minimize the amount of memory required. + + For example, you can use a negative step with an infinite iterator: + + >>> from itertools import count + >>> list(islice_extended(count(), 110, 99, -2)) + [110, 108, 106, 104, 102, 100] + + You can also use slice notation directly: + + >>> iterable = map(str, count()) + >>> it = islice_extended(iterable)[10:20:2] + >>> list(it) + ['10', '12', '14', '16', '18'] + + """ + + def __init__(self, iterable, *args): + it = iter(iterable) + if args: + self._iterable = _islice_helper(it, slice(*args)) + else: + self._iterable = it + + def __iter__(self): + return self + + def __next__(self): + return next(self._iterable) + + def __getitem__(self, key): + if isinstance(key, slice): + return islice_extended(_islice_helper(self._iterable, key)) + + raise TypeError('islice_extended.__getitem__ argument must be a slice') + + +def _islice_helper(it, s): + start = s.start + stop = s.stop + if s.step == 0: + raise ValueError('step argument must be a non-zero integer or None.') + step = s.step or 1 + + if step > 0: + start = 0 if (start is None) else start + + if start < 0: + # Consume all but the last -start items + cache = deque(enumerate(it, 1), maxlen=-start) + len_iter = cache[-1][0] if cache else 0 + + # Adjust start to be positive + i = max(len_iter + start, 0) + + # Adjust stop to be positive + if stop is None: + j = len_iter + elif stop >= 0: + j = min(stop, len_iter) + else: + j = max(len_iter + stop, 0) + + # Slice the cache + n = j - i + if n <= 0: + return + + for index, item in islice(cache, 0, n, step): + yield item + elif (stop is not None) and (stop < 0): + # Advance to the start position + next(islice(it, start, start), None) + + # When stop is negative, we have to carry -stop items while + # iterating + cache = deque(islice(it, -stop), maxlen=-stop) + + for index, item in enumerate(it): + cached_item = cache.popleft() + if index % step == 0: + yield cached_item + cache.append(item) + else: + # When both start and stop are positive we have the normal case + yield from islice(it, start, stop, step) + else: + start = -1 if (start is None) else start + + if (stop is not None) and (stop < 0): + # Consume all but the last items + n = -stop - 1 + cache = deque(enumerate(it, 1), maxlen=n) + len_iter = cache[-1][0] if cache else 0 + + # If start and stop are both negative they are comparable and + # we can just slice. Otherwise we can adjust start to be negative + # and then slice. + if start < 0: + i, j = start, stop + else: + i, j = min(start - len_iter, -1), None + + for index, item in list(cache)[i:j:step]: + yield item + else: + # Advance to the stop position + if stop is not None: + m = stop + 1 + next(islice(it, m, m), None) + + # stop is positive, so if start is negative they are not comparable + # and we need the rest of the items. + if start < 0: + i = start + n = None + # stop is None and start is positive, so we just need items up to + # the start index. + elif stop is None: + i = None + n = start + 1 + # Both stop and start are positive, so they are comparable. + else: + i = None + n = start - stop + if n <= 0: + return + + cache = list(islice(it, n)) + + yield from cache[i::step] + + +def always_reversible(iterable): + """An extension of :func:`reversed` that supports all iterables, not + just those which implement the ``Reversible`` or ``Sequence`` protocols. + + >>> print(*always_reversible(x for x in range(3))) + 2 1 0 + + If the iterable is already reversible, this function returns the + result of :func:`reversed()`. If the iterable is not reversible, + this function will cache the remaining items in the iterable and + yield them in reverse order, which may require significant storage. + """ + try: + return reversed(iterable) + except TypeError: + return reversed(list(iterable)) + + +def consecutive_groups(iterable, ordering=lambda x: x): + """Yield groups of consecutive items using :func:`itertools.groupby`. + The *ordering* function determines whether two items are adjacent by + returning their position. + + By default, the ordering function is the identity function. This is + suitable for finding runs of numbers: + + >>> iterable = [1, 10, 11, 12, 20, 30, 31, 32, 33, 40] + >>> for group in consecutive_groups(iterable): + ... print(list(group)) + [1] + [10, 11, 12] + [20] + [30, 31, 32, 33] + [40] + + For finding runs of adjacent letters, try using the :meth:`index` method + of a string of letters: + + >>> from string import ascii_lowercase + >>> iterable = 'abcdfgilmnop' + >>> ordering = ascii_lowercase.index + >>> for group in consecutive_groups(iterable, ordering): + ... print(list(group)) + ['a', 'b', 'c', 'd'] + ['f', 'g'] + ['i'] + ['l', 'm', 'n', 'o', 'p'] + + Each group of consecutive items is an iterator that shares it source with + *iterable*. When an an output group is advanced, the previous group is + no longer available unless its elements are copied (e.g., into a ``list``). + + >>> iterable = [1, 2, 11, 12, 21, 22] + >>> saved_groups = [] + >>> for group in consecutive_groups(iterable): + ... saved_groups.append(list(group)) # Copy group elements + >>> saved_groups + [[1, 2], [11, 12], [21, 22]] + + """ + for k, g in groupby( + enumerate(iterable), key=lambda x: x[0] - ordering(x[1]) + ): + yield map(itemgetter(1), g) + + +def difference(iterable, func=sub, *, initial=None): + """This function is the inverse of :func:`itertools.accumulate`. By default + it will compute the first difference of *iterable* using + :func:`operator.sub`: + + >>> from itertools import accumulate + >>> iterable = accumulate([0, 1, 2, 3, 4]) # produces 0, 1, 3, 6, 10 + >>> list(difference(iterable)) + [0, 1, 2, 3, 4] + + *func* defaults to :func:`operator.sub`, but other functions can be + specified. They will be applied as follows:: + + A, B, C, D, ... --> A, func(B, A), func(C, B), func(D, C), ... + + For example, to do progressive division: + + >>> iterable = [1, 2, 6, 24, 120] + >>> func = lambda x, y: x // y + >>> list(difference(iterable, func)) + [1, 2, 3, 4, 5] + + If the *initial* keyword is set, the first element will be skipped when + computing successive differences. + + >>> it = [10, 11, 13, 16] # from accumulate([1, 2, 3], initial=10) + >>> list(difference(it, initial=10)) + [1, 2, 3] + + """ + a, b = tee(iterable) + try: + first = [next(b)] + except StopIteration: + return iter([]) + + if initial is not None: + first = [] + + return chain(first, starmap(func, zip(b, a))) + + +class SequenceView(Sequence): + """Return a read-only view of the sequence object *target*. + + :class:`SequenceView` objects are analogous to Python's built-in + "dictionary view" types. They provide a dynamic view of a sequence's items, + meaning that when the sequence updates, so does the view. + + >>> seq = ['0', '1', '2'] + >>> view = SequenceView(seq) + >>> view + SequenceView(['0', '1', '2']) + >>> seq.append('3') + >>> view + SequenceView(['0', '1', '2', '3']) + + Sequence views support indexing, slicing, and length queries. They act + like the underlying sequence, except they don't allow assignment: + + >>> view[1] + '1' + >>> view[1:-1] + ['1', '2'] + >>> len(view) + 4 + + Sequence views are useful as an alternative to copying, as they don't + require (much) extra storage. + + """ + + def __init__(self, target): + if not isinstance(target, Sequence): + raise TypeError + self._target = target + + def __getitem__(self, index): + return self._target[index] + + def __len__(self): + return len(self._target) + + def __repr__(self): + return '{}({})'.format(self.__class__.__name__, repr(self._target)) + + +class seekable: + """Wrap an iterator to allow for seeking backward and forward. This + progressively caches the items in the source iterable so they can be + re-visited. + + Call :meth:`seek` with an index to seek to that position in the source + iterable. + + To "reset" an iterator, seek to ``0``: + + >>> from itertools import count + >>> it = seekable((str(n) for n in count())) + >>> next(it), next(it), next(it) + ('0', '1', '2') + >>> it.seek(0) + >>> next(it), next(it), next(it) + ('0', '1', '2') + >>> next(it) + '3' + + You can also seek forward: + + >>> it = seekable((str(n) for n in range(20))) + >>> it.seek(10) + >>> next(it) + '10' + >>> it.seek(20) # Seeking past the end of the source isn't a problem + >>> list(it) + [] + >>> it.seek(0) # Resetting works even after hitting the end + >>> next(it), next(it), next(it) + ('0', '1', '2') + + Call :meth:`peek` to look ahead one item without advancing the iterator: + + >>> it = seekable('1234') + >>> it.peek() + '1' + >>> list(it) + ['1', '2', '3', '4'] + >>> it.peek(default='empty') + 'empty' + + Before the iterator is at its end, calling :func:`bool` on it will return + ``True``. After it will return ``False``: + + >>> it = seekable('5678') + >>> bool(it) + True + >>> list(it) + ['5', '6', '7', '8'] + >>> bool(it) + False + + You may view the contents of the cache with the :meth:`elements` method. + That returns a :class:`SequenceView`, a view that updates automatically: + + >>> it = seekable((str(n) for n in range(10))) + >>> next(it), next(it), next(it) + ('0', '1', '2') + >>> elements = it.elements() + >>> elements + SequenceView(['0', '1', '2']) + >>> next(it) + '3' + >>> elements + SequenceView(['0', '1', '2', '3']) + + By default, the cache grows as the source iterable progresses, so beware of + wrapping very large or infinite iterables. Supply *maxlen* to limit the + size of the cache (this of course limits how far back you can seek). + + >>> from itertools import count + >>> it = seekable((str(n) for n in count()), maxlen=2) + >>> next(it), next(it), next(it), next(it) + ('0', '1', '2', '3') + >>> list(it.elements()) + ['2', '3'] + >>> it.seek(0) + >>> next(it), next(it), next(it), next(it) + ('2', '3', '4', '5') + >>> next(it) + '6' + + """ + + def __init__(self, iterable, maxlen=None): + self._source = iter(iterable) + if maxlen is None: + self._cache = [] + else: + self._cache = deque([], maxlen) + self._index = None + + def __iter__(self): + return self + + def __next__(self): + if self._index is not None: + try: + item = self._cache[self._index] + except IndexError: + self._index = None + else: + self._index += 1 + return item + + item = next(self._source) + self._cache.append(item) + return item + + def __bool__(self): + try: + self.peek() + except StopIteration: + return False + return True + + def peek(self, default=_marker): + try: + peeked = next(self) + except StopIteration: + if default is _marker: + raise + return default + if self._index is None: + self._index = len(self._cache) + self._index -= 1 + return peeked + + def elements(self): + return SequenceView(self._cache) + + def seek(self, index): + self._index = index + remainder = index - len(self._cache) + if remainder > 0: + consume(self, remainder) + + +class run_length: + """ + :func:`run_length.encode` compresses an iterable with run-length encoding. + It yields groups of repeated items with the count of how many times they + were repeated: + + >>> uncompressed = 'abbcccdddd' + >>> list(run_length.encode(uncompressed)) + [('a', 1), ('b', 2), ('c', 3), ('d', 4)] + + :func:`run_length.decode` decompresses an iterable that was previously + compressed with run-length encoding. It yields the items of the + decompressed iterable: + + >>> compressed = [('a', 1), ('b', 2), ('c', 3), ('d', 4)] + >>> list(run_length.decode(compressed)) + ['a', 'b', 'b', 'c', 'c', 'c', 'd', 'd', 'd', 'd'] + + """ + + @staticmethod + def encode(iterable): + return ((k, ilen(g)) for k, g in groupby(iterable)) + + @staticmethod + def decode(iterable): + return chain.from_iterable(repeat(k, n) for k, n in iterable) + + +def exactly_n(iterable, n, predicate=bool): + """Return ``True`` if exactly ``n`` items in the iterable are ``True`` + according to the *predicate* function. + + >>> exactly_n([True, True, False], 2) + True + >>> exactly_n([True, True, False], 1) + False + >>> exactly_n([0, 1, 2, 3, 4, 5], 3, lambda x: x < 3) + True + + The iterable will be advanced until ``n + 1`` truthy items are encountered, + so avoid calling it on infinite iterables. + + """ + return len(take(n + 1, filter(predicate, iterable))) == n + + +def circular_shifts(iterable): + """Return a list of circular shifts of *iterable*. + + >>> circular_shifts(range(4)) + [(0, 1, 2, 3), (1, 2, 3, 0), (2, 3, 0, 1), (3, 0, 1, 2)] + """ + lst = list(iterable) + return take(len(lst), windowed(cycle(lst), len(lst))) + + +def make_decorator(wrapping_func, result_index=0): + """Return a decorator version of *wrapping_func*, which is a function that + modifies an iterable. *result_index* is the position in that function's + signature where the iterable goes. + + This lets you use itertools on the "production end," i.e. at function + definition. This can augment what the function returns without changing the + function's code. + + For example, to produce a decorator version of :func:`chunked`: + + >>> from more_itertools import chunked + >>> chunker = make_decorator(chunked, result_index=0) + >>> @chunker(3) + ... def iter_range(n): + ... return iter(range(n)) + ... + >>> list(iter_range(9)) + [[0, 1, 2], [3, 4, 5], [6, 7, 8]] + + To only allow truthy items to be returned: + + >>> truth_serum = make_decorator(filter, result_index=1) + >>> @truth_serum(bool) + ... def boolean_test(): + ... return [0, 1, '', ' ', False, True] + ... + >>> list(boolean_test()) + [1, ' ', True] + + The :func:`peekable` and :func:`seekable` wrappers make for practical + decorators: + + >>> from more_itertools import peekable + >>> peekable_function = make_decorator(peekable) + >>> @peekable_function() + ... def str_range(*args): + ... return (str(x) for x in range(*args)) + ... + >>> it = str_range(1, 20, 2) + >>> next(it), next(it), next(it) + ('1', '3', '5') + >>> it.peek() + '7' + >>> next(it) + '7' + + """ + # See https://sites.google.com/site/bbayles/index/decorator_factory for + # notes on how this works. + def decorator(*wrapping_args, **wrapping_kwargs): + def outer_wrapper(f): + def inner_wrapper(*args, **kwargs): + result = f(*args, **kwargs) + wrapping_args_ = list(wrapping_args) + wrapping_args_.insert(result_index, result) + return wrapping_func(*wrapping_args_, **wrapping_kwargs) + + return inner_wrapper + + return outer_wrapper + + return decorator + + +def map_reduce(iterable, keyfunc, valuefunc=None, reducefunc=None): + """Return a dictionary that maps the items in *iterable* to categories + defined by *keyfunc*, transforms them with *valuefunc*, and + then summarizes them by category with *reducefunc*. + + *valuefunc* defaults to the identity function if it is unspecified. + If *reducefunc* is unspecified, no summarization takes place: + + >>> keyfunc = lambda x: x.upper() + >>> result = map_reduce('abbccc', keyfunc) + >>> sorted(result.items()) + [('A', ['a']), ('B', ['b', 'b']), ('C', ['c', 'c', 'c'])] + + Specifying *valuefunc* transforms the categorized items: + + >>> keyfunc = lambda x: x.upper() + >>> valuefunc = lambda x: 1 + >>> result = map_reduce('abbccc', keyfunc, valuefunc) + >>> sorted(result.items()) + [('A', [1]), ('B', [1, 1]), ('C', [1, 1, 1])] + + Specifying *reducefunc* summarizes the categorized items: + + >>> keyfunc = lambda x: x.upper() + >>> valuefunc = lambda x: 1 + >>> reducefunc = sum + >>> result = map_reduce('abbccc', keyfunc, valuefunc, reducefunc) + >>> sorted(result.items()) + [('A', 1), ('B', 2), ('C', 3)] + + You may want to filter the input iterable before applying the map/reduce + procedure: + + >>> all_items = range(30) + >>> items = [x for x in all_items if 10 <= x <= 20] # Filter + >>> keyfunc = lambda x: x % 2 # Evens map to 0; odds to 1 + >>> categories = map_reduce(items, keyfunc=keyfunc) + >>> sorted(categories.items()) + [(0, [10, 12, 14, 16, 18, 20]), (1, [11, 13, 15, 17, 19])] + >>> summaries = map_reduce(items, keyfunc=keyfunc, reducefunc=sum) + >>> sorted(summaries.items()) + [(0, 90), (1, 75)] + + Note that all items in the iterable are gathered into a list before the + summarization step, which may require significant storage. + + The returned object is a :obj:`collections.defaultdict` with the + ``default_factory`` set to ``None``, such that it behaves like a normal + dictionary. + + """ + valuefunc = (lambda x: x) if (valuefunc is None) else valuefunc + + ret = defaultdict(list) + for item in iterable: + key = keyfunc(item) + value = valuefunc(item) + ret[key].append(value) + + if reducefunc is not None: + for key, value_list in ret.items(): + ret[key] = reducefunc(value_list) + + ret.default_factory = None + return ret + + +def rlocate(iterable, pred=bool, window_size=None): + """Yield the index of each item in *iterable* for which *pred* returns + ``True``, starting from the right and moving left. + + *pred* defaults to :func:`bool`, which will select truthy items: + + >>> list(rlocate([0, 1, 1, 0, 1, 0, 0])) # Truthy at 1, 2, and 4 + [4, 2, 1] + + Set *pred* to a custom function to, e.g., find the indexes for a particular + item: + + >>> iterable = iter('abcb') + >>> pred = lambda x: x == 'b' + >>> list(rlocate(iterable, pred)) + [3, 1] + + If *window_size* is given, then the *pred* function will be called with + that many items. This enables searching for sub-sequences: + + >>> iterable = [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3] + >>> pred = lambda *args: args == (1, 2, 3) + >>> list(rlocate(iterable, pred=pred, window_size=3)) + [9, 5, 1] + + Beware, this function won't return anything for infinite iterables. + If *iterable* is reversible, ``rlocate`` will reverse it and search from + the right. Otherwise, it will search from the left and return the results + in reverse order. + + See :func:`locate` to for other example applications. + + """ + if window_size is None: + try: + len_iter = len(iterable) + return (len_iter - i - 1 for i in locate(reversed(iterable), pred)) + except TypeError: + pass + + return reversed(list(locate(iterable, pred, window_size))) + + +def replace(iterable, pred, substitutes, count=None, window_size=1): + """Yield the items from *iterable*, replacing the items for which *pred* + returns ``True`` with the items from the iterable *substitutes*. + + >>> iterable = [1, 1, 0, 1, 1, 0, 1, 1] + >>> pred = lambda x: x == 0 + >>> substitutes = (2, 3) + >>> list(replace(iterable, pred, substitutes)) + [1, 1, 2, 3, 1, 1, 2, 3, 1, 1] + + If *count* is given, the number of replacements will be limited: + + >>> iterable = [1, 1, 0, 1, 1, 0, 1, 1, 0] + >>> pred = lambda x: x == 0 + >>> substitutes = [None] + >>> list(replace(iterable, pred, substitutes, count=2)) + [1, 1, None, 1, 1, None, 1, 1, 0] + + Use *window_size* to control the number of items passed as arguments to + *pred*. This allows for locating and replacing subsequences. + + >>> iterable = [0, 1, 2, 5, 0, 1, 2, 5] + >>> window_size = 3 + >>> pred = lambda *args: args == (0, 1, 2) # 3 items passed to pred + >>> substitutes = [3, 4] # Splice in these items + >>> list(replace(iterable, pred, substitutes, window_size=window_size)) + [3, 4, 5, 3, 4, 5] + + """ + if window_size < 1: + raise ValueError('window_size must be at least 1') + + # Save the substitutes iterable, since it's used more than once + substitutes = tuple(substitutes) + + # Add padding such that the number of windows matches the length of the + # iterable + it = chain(iterable, [_marker] * (window_size - 1)) + windows = windowed(it, window_size) + + n = 0 + for w in windows: + # If the current window matches our predicate (and we haven't hit + # our maximum number of replacements), splice in the substitutes + # and then consume the following windows that overlap with this one. + # For example, if the iterable is (0, 1, 2, 3, 4...) + # and the window size is 2, we have (0, 1), (1, 2), (2, 3)... + # If the predicate matches on (0, 1), we need to zap (0, 1) and (1, 2) + if pred(*w): + if (count is None) or (n < count): + n += 1 + yield from substitutes + consume(windows, window_size - 1) + continue + + # If there was no match (or we've reached the replacement limit), + # yield the first item from the window. + if w and (w[0] is not _marker): + yield w[0] + + +def partitions(iterable): + """Yield all possible order-preserving partitions of *iterable*. + + >>> iterable = 'abc' + >>> for part in partitions(iterable): + ... print([''.join(p) for p in part]) + ['abc'] + ['a', 'bc'] + ['ab', 'c'] + ['a', 'b', 'c'] + + This is unrelated to :func:`partition`. + + """ + sequence = list(iterable) + n = len(sequence) + for i in powerset(range(1, n)): + yield [sequence[i:j] for i, j in zip((0,) + i, i + (n,))] + + +def set_partitions(iterable, k=None): + """ + Yield the set partitions of *iterable* into *k* parts. Set partitions are + not order-preserving. + + >>> iterable = 'abc' + >>> for part in set_partitions(iterable, 2): + ... print([''.join(p) for p in part]) + ['a', 'bc'] + ['ab', 'c'] + ['b', 'ac'] + + + If *k* is not given, every set partition is generated. + + >>> iterable = 'abc' + >>> for part in set_partitions(iterable): + ... print([''.join(p) for p in part]) + ['abc'] + ['a', 'bc'] + ['ab', 'c'] + ['b', 'ac'] + ['a', 'b', 'c'] + + """ + L = list(iterable) + n = len(L) + if k is not None: + if k < 1: + raise ValueError( + "Can't partition in a negative or zero number of groups" + ) + elif k > n: + return + + def set_partitions_helper(L, k): + n = len(L) + if k == 1: + yield [L] + elif n == k: + yield [[s] for s in L] + else: + e, *M = L + for p in set_partitions_helper(M, k - 1): + yield [[e], *p] + for p in set_partitions_helper(M, k): + for i in range(len(p)): + yield p[:i] + [[e] + p[i]] + p[i + 1 :] + + if k is None: + for k in range(1, n + 1): + yield from set_partitions_helper(L, k) + else: + yield from set_partitions_helper(L, k) + + +class time_limited: + """ + Yield items from *iterable* until *limit_seconds* have passed. + If the time limit expires before all items have been yielded, the + ``timed_out`` parameter will be set to ``True``. + + >>> from time import sleep + >>> def generator(): + ... yield 1 + ... yield 2 + ... sleep(0.2) + ... yield 3 + >>> iterable = time_limited(0.1, generator()) + >>> list(iterable) + [1, 2] + >>> iterable.timed_out + True + + Note that the time is checked before each item is yielded, and iteration + stops if the time elapsed is greater than *limit_seconds*. If your time + limit is 1 second, but it takes 2 seconds to generate the first item from + the iterable, the function will run for 2 seconds and not yield anything. + + """ + + def __init__(self, limit_seconds, iterable): + if limit_seconds < 0: + raise ValueError('limit_seconds must be positive') + self.limit_seconds = limit_seconds + self._iterable = iter(iterable) + self._start_time = monotonic() + self.timed_out = False + + def __iter__(self): + return self + + def __next__(self): + item = next(self._iterable) + if monotonic() - self._start_time > self.limit_seconds: + self.timed_out = True + raise StopIteration + + return item + + +def only(iterable, default=None, too_long=None): + """If *iterable* has only one item, return it. + If it has zero items, return *default*. + If it has more than one item, raise the exception given by *too_long*, + which is ``ValueError`` by default. + + >>> only([], default='missing') + 'missing' + >>> only([1]) + 1 + >>> only([1, 2]) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + ValueError: Expected exactly one item in iterable, but got 1, 2, + and perhaps more.' + >>> only([1, 2], too_long=TypeError) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + TypeError + + Note that :func:`only` attempts to advance *iterable* twice to ensure there + is only one item. See :func:`spy` or :func:`peekable` to check + iterable contents less destructively. + """ + it = iter(iterable) + first_value = next(it, default) + + try: + second_value = next(it) + except StopIteration: + pass + else: + msg = ( + 'Expected exactly one item in iterable, but got {!r}, {!r}, ' + 'and perhaps more.'.format(first_value, second_value) + ) + raise too_long or ValueError(msg) + + return first_value + + +def ichunked(iterable, n): + """Break *iterable* into sub-iterables with *n* elements each. + :func:`ichunked` is like :func:`chunked`, but it yields iterables + instead of lists. + + If the sub-iterables are read in order, the elements of *iterable* + won't be stored in memory. + If they are read out of order, :func:`itertools.tee` is used to cache + elements as necessary. + + >>> from itertools import count + >>> all_chunks = ichunked(count(), 4) + >>> c_1, c_2, c_3 = next(all_chunks), next(all_chunks), next(all_chunks) + >>> list(c_2) # c_1's elements have been cached; c_3's haven't been + [4, 5, 6, 7] + >>> list(c_1) + [0, 1, 2, 3] + >>> list(c_3) + [8, 9, 10, 11] + + """ + source = iter(iterable) + + while True: + # Check to see whether we're at the end of the source iterable + item = next(source, _marker) + if item is _marker: + return + + # Clone the source and yield an n-length slice + source, it = tee(chain([item], source)) + yield islice(it, n) + + # Advance the source iterable + consume(source, n) + + +def distinct_combinations(iterable, r): + """Yield the distinct combinations of *r* items taken from *iterable*. + + >>> list(distinct_combinations([0, 0, 1], 2)) + [(0, 0), (0, 1)] + + Equivalent to ``set(combinations(iterable))``, except duplicates are not + generated and thrown away. For larger input sequences this is much more + efficient. + + """ + if r < 0: + raise ValueError('r must be non-negative') + elif r == 0: + yield () + return + pool = tuple(iterable) + generators = [unique_everseen(enumerate(pool), key=itemgetter(1))] + current_combo = [None] * r + level = 0 + while generators: + try: + cur_idx, p = next(generators[-1]) + except StopIteration: + generators.pop() + level -= 1 + continue + current_combo[level] = p + if level + 1 == r: + yield tuple(current_combo) + else: + generators.append( + unique_everseen( + enumerate(pool[cur_idx + 1 :], cur_idx + 1), + key=itemgetter(1), + ) + ) + level += 1 + + +def filter_except(validator, iterable, *exceptions): + """Yield the items from *iterable* for which the *validator* function does + not raise one of the specified *exceptions*. + + *validator* is called for each item in *iterable*. + It should be a function that accepts one argument and raises an exception + if that item is not valid. + + >>> iterable = ['1', '2', 'three', '4', None] + >>> list(filter_except(int, iterable, ValueError, TypeError)) + ['1', '2', '4'] + + If an exception other than one given by *exceptions* is raised by + *validator*, it is raised like normal. + """ + for item in iterable: + try: + validator(item) + except exceptions: + pass + else: + yield item + + +def map_except(function, iterable, *exceptions): + """Transform each item from *iterable* with *function* and yield the + result, unless *function* raises one of the specified *exceptions*. + + *function* is called to transform each item in *iterable*. + It should be a accept one argument. + + >>> iterable = ['1', '2', 'three', '4', None] + >>> list(map_except(int, iterable, ValueError, TypeError)) + [1, 2, 4] + + If an exception other than one given by *exceptions* is raised by + *function*, it is raised like normal. + """ + for item in iterable: + try: + yield function(item) + except exceptions: + pass + + +def _sample_unweighted(iterable, k): + # Implementation of "Algorithm L" from the 1994 paper by Kim-Hung Li: + # "Reservoir-Sampling Algorithms of Time Complexity O(n(1+log(N/n)))". + + # Fill up the reservoir (collection of samples) with the first `k` samples + reservoir = take(k, iterable) + + # Generate random number that's the largest in a sample of k U(0,1) numbers + # Largest order statistic: https://en.wikipedia.org/wiki/Order_statistic + W = exp(log(random()) / k) + + # The number of elements to skip before changing the reservoir is a random + # number with a geometric distribution. Sample it using random() and logs. + next_index = k + floor(log(random()) / log(1 - W)) + + for index, element in enumerate(iterable, k): + + if index == next_index: + reservoir[randrange(k)] = element + # The new W is the largest in a sample of k U(0, `old_W`) numbers + W *= exp(log(random()) / k) + next_index += floor(log(random()) / log(1 - W)) + 1 + + return reservoir + + +def _sample_weighted(iterable, k, weights): + # Implementation of "A-ExpJ" from the 2006 paper by Efraimidis et al. : + # "Weighted random sampling with a reservoir". + + # Log-transform for numerical stability for weights that are small/large + weight_keys = (log(random()) / weight for weight in weights) + + # Fill up the reservoir (collection of samples) with the first `k` + # weight-keys and elements, then heapify the list. + reservoir = take(k, zip(weight_keys, iterable)) + heapify(reservoir) + + # The number of jumps before changing the reservoir is a random variable + # with an exponential distribution. Sample it using random() and logs. + smallest_weight_key, _ = reservoir[0] + weights_to_skip = log(random()) / smallest_weight_key + + for weight, element in zip(weights, iterable): + if weight >= weights_to_skip: + # The notation here is consistent with the paper, but we store + # the weight-keys in log-space for better numerical stability. + smallest_weight_key, _ = reservoir[0] + t_w = exp(weight * smallest_weight_key) + r_2 = uniform(t_w, 1) # generate U(t_w, 1) + weight_key = log(r_2) / weight + heapreplace(reservoir, (weight_key, element)) + smallest_weight_key, _ = reservoir[0] + weights_to_skip = log(random()) / smallest_weight_key + else: + weights_to_skip -= weight + + # Equivalent to [element for weight_key, element in sorted(reservoir)] + return [heappop(reservoir)[1] for _ in range(k)] + + +def sample(iterable, k, weights=None): + """Return a *k*-length list of elements chosen (without replacement) + from the *iterable*. Like :func:`random.sample`, but works on iterables + of unknown length. + + >>> iterable = range(100) + >>> sample(iterable, 5) # doctest: +SKIP + [81, 60, 96, 16, 4] + + An iterable with *weights* may also be given: + + >>> iterable = range(100) + >>> weights = (i * i + 1 for i in range(100)) + >>> sampled = sample(iterable, 5, weights=weights) # doctest: +SKIP + [79, 67, 74, 66, 78] + + The algorithm can also be used to generate weighted random permutations. + The relative weight of each item determines the probability that it + appears late in the permutation. + + >>> data = "abcdefgh" + >>> weights = range(1, len(data) + 1) + >>> sample(data, k=len(data), weights=weights) # doctest: +SKIP + ['c', 'a', 'b', 'e', 'g', 'd', 'h', 'f'] + """ + if k == 0: + return [] + + iterable = iter(iterable) + if weights is None: + return _sample_unweighted(iterable, k) + else: + weights = iter(weights) + return _sample_weighted(iterable, k, weights) + + +def is_sorted(iterable, key=None, reverse=False): + """Returns ``True`` if the items of iterable are in sorted order, and + ``False`` otherwise. *key* and *reverse* have the same meaning that they do + in the built-in :func:`sorted` function. + + >>> is_sorted(['1', '2', '3', '4', '5'], key=int) + True + >>> is_sorted([5, 4, 3, 1, 2], reverse=True) + False + + The function returns ``False`` after encountering the first out-of-order + item. If there are no out-of-order items, the iterable is exhausted. + """ + + compare = lt if reverse else gt + it = iterable if (key is None) else map(key, iterable) + return not any(starmap(compare, pairwise(it))) + + +class AbortThread(BaseException): + pass + + +class callback_iter: + """Convert a function that uses callbacks to an iterator. + + Let *func* be a function that takes a `callback` keyword argument. + For example: + + >>> def func(callback=None): + ... for i, c in [(1, 'a'), (2, 'b'), (3, 'c')]: + ... if callback: + ... callback(i, c) + ... return 4 + + + Use ``with callback_iter(func)`` to get an iterator over the parameters + that are delivered to the callback. + + >>> with callback_iter(func) as it: + ... for args, kwargs in it: + ... print(args) + (1, 'a') + (2, 'b') + (3, 'c') + + The function will be called in a background thread. The ``done`` property + indicates whether it has completed execution. + + >>> it.done + True + + If it completes successfully, its return value will be available + in the ``result`` property. + + >>> it.result + 4 + + Notes: + + * If the function uses some keyword argument besides ``callback``, supply + *callback_kwd*. + * If it finished executing, but raised an exception, accessing the + ``result`` property will raise the same exception. + * If it hasn't finished executing, accessing the ``result`` + property from within the ``with`` block will raise ``RuntimeError``. + * If it hasn't finished executing, accessing the ``result`` property from + outside the ``with`` block will raise a + ``more_itertools.AbortThread`` exception. + * Provide *wait_seconds* to adjust how frequently the it is polled for + output. + + """ + + def __init__(self, func, callback_kwd='callback', wait_seconds=0.1): + self._func = func + self._callback_kwd = callback_kwd + self._aborted = False + self._future = None + self._wait_seconds = wait_seconds + self._executor = ThreadPoolExecutor(max_workers=1) + self._iterator = self._reader() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self._aborted = True + self._executor.shutdown() + + def __iter__(self): + return self + + def __next__(self): + return next(self._iterator) + + @property + def done(self): + if self._future is None: + return False + return self._future.done() + + @property + def result(self): + if not self.done: + raise RuntimeError('Function has not yet completed') + + return self._future.result() + + def _reader(self): + q = Queue() + + def callback(*args, **kwargs): + if self._aborted: + raise AbortThread('canceled by user') + + q.put((args, kwargs)) + + self._future = self._executor.submit( + self._func, **{self._callback_kwd: callback} + ) + + while True: + try: + item = q.get(timeout=self._wait_seconds) + except Empty: + pass + else: + q.task_done() + yield item + + if self._future.done(): + break + + remaining = [] + while True: + try: + item = q.get_nowait() + except Empty: + break + else: + q.task_done() + remaining.append(item) + q.join() + yield from remaining + + +def windowed_complete(iterable, n): + """ + Yield ``(beginning, middle, end)`` tuples, where: + + * Each ``middle`` has *n* items from *iterable* + * Each ``beginning`` has the items before the ones in ``middle`` + * Each ``end`` has the items after the ones in ``middle`` + + >>> iterable = range(7) + >>> n = 3 + >>> for beginning, middle, end in windowed_complete(iterable, n): + ... print(beginning, middle, end) + () (0, 1, 2) (3, 4, 5, 6) + (0,) (1, 2, 3) (4, 5, 6) + (0, 1) (2, 3, 4) (5, 6) + (0, 1, 2) (3, 4, 5) (6,) + (0, 1, 2, 3) (4, 5, 6) () + + Note that *n* must be at least 0 and most equal to the length of + *iterable*. + + This function will exhaust the iterable and may require significant + storage. + """ + if n < 0: + raise ValueError('n must be >= 0') + + seq = tuple(iterable) + size = len(seq) + + if n > size: + raise ValueError('n must be <= len(seq)') + + for i in range(size - n + 1): + beginning = seq[:i] + middle = seq[i : i + n] + end = seq[i + n :] + yield beginning, middle, end + + +def all_unique(iterable, key=None): + """ + Returns ``True`` if all the elements of *iterable* are unique (no two + elements are equal). + + >>> all_unique('ABCB') + False + + If a *key* function is specified, it will be used to make comparisons. + + >>> all_unique('ABCb') + True + >>> all_unique('ABCb', str.lower) + False + + The function returns as soon as the first non-unique element is + encountered. Iterables with a mix of hashable and unhashable items can + be used, but the function will be slower for unhashable items. + """ + seenset = set() + seenset_add = seenset.add + seenlist = [] + seenlist_add = seenlist.append + for element in map(key, iterable) if key else iterable: + try: + if element in seenset: + return False + seenset_add(element) + except TypeError: + if element in seenlist: + return False + seenlist_add(element) + return True + + +def nth_product(index, *args): + """Equivalent to ``list(product(*args))[index]``. + + The products of *args* can be ordered lexicographically. + :func:`nth_product` computes the product at sort position *index* without + computing the previous products. + + >>> nth_product(8, range(2), range(2), range(2), range(2)) + (1, 0, 0, 0) + + ``IndexError`` will be raised if the given *index* is invalid. + """ + pools = list(map(tuple, reversed(args))) + ns = list(map(len, pools)) + + c = reduce(mul, ns) + + if index < 0: + index += c + + if not 0 <= index < c: + raise IndexError + + result = [] + for pool, n in zip(pools, ns): + result.append(pool[index % n]) + index //= n + + return tuple(reversed(result)) + + +def nth_permutation(iterable, r, index): + """Equivalent to ``list(permutations(iterable, r))[index]``` + + The subsequences of *iterable* that are of length *r* where order is + important can be ordered lexicographically. :func:`nth_permutation` + computes the subsequence at sort position *index* directly, without + computing the previous subsequences. + + >>> nth_permutation('ghijk', 2, 5) + ('h', 'i') + + ``ValueError`` will be raised If *r* is negative or greater than the length + of *iterable*. + ``IndexError`` will be raised if the given *index* is invalid. + """ + pool = list(iterable) + n = len(pool) + + if r is None or r == n: + r, c = n, factorial(n) + elif not 0 <= r < n: + raise ValueError + else: + c = factorial(n) // factorial(n - r) + + if index < 0: + index += c + + if not 0 <= index < c: + raise IndexError + + if c == 0: + return tuple() + + result = [0] * r + q = index * factorial(n) // c if r < n else index + for d in range(1, n + 1): + q, i = divmod(q, d) + if 0 <= n - d < r: + result[n - d] = i + if q == 0: + break + + return tuple(map(pool.pop, result)) + + +def value_chain(*args): + """Yield all arguments passed to the function in the same order in which + they were passed. If an argument itself is iterable then iterate over its + values. + + >>> list(value_chain(1, 2, 3, [4, 5, 6])) + [1, 2, 3, 4, 5, 6] + + Binary and text strings are not considered iterable and are emitted + as-is: + + >>> list(value_chain('12', '34', ['56', '78'])) + ['12', '34', '56', '78'] + + + Multiple levels of nesting are not flattened. + + """ + for value in args: + if isinstance(value, (str, bytes)): + yield value + continue + try: + yield from value + except TypeError: + yield value + + +def product_index(element, *args): + """Equivalent to ``list(product(*args)).index(element)`` + + The products of *args* can be ordered lexicographically. + :func:`product_index` computes the first index of *element* without + computing the previous products. + + >>> product_index([8, 2], range(10), range(5)) + 42 + + ``ValueError`` will be raised if the given *element* isn't in the product + of *args*. + """ + index = 0 + + for x, pool in zip_longest(element, args, fillvalue=_marker): + if x is _marker or pool is _marker: + raise ValueError('element is not a product of args') + + pool = tuple(pool) + index = index * len(pool) + pool.index(x) + + return index + + +def combination_index(element, iterable): + """Equivalent to ``list(combinations(iterable, r)).index(element)`` + + The subsequences of *iterable* that are of length *r* can be ordered + lexicographically. :func:`combination_index` computes the index of the + first *element*, without computing the previous combinations. + + >>> combination_index('adf', 'abcdefg') + 10 + + ``ValueError`` will be raised if the given *element* isn't one of the + combinations of *iterable*. + """ + element = enumerate(element) + k, y = next(element, (None, None)) + if k is None: + return 0 + + indexes = [] + pool = enumerate(iterable) + for n, x in pool: + if x == y: + indexes.append(n) + tmp, y = next(element, (None, None)) + if tmp is None: + break + else: + k = tmp + else: + raise ValueError('element is not a combination of iterable') + + n, _ = last(pool, default=(n, None)) + + # Python versiosn below 3.8 don't have math.comb + index = 1 + for i, j in enumerate(reversed(indexes), start=1): + j = n - j + if i <= j: + index += factorial(j) // (factorial(i) * factorial(j - i)) + + return factorial(n + 1) // (factorial(k + 1) * factorial(n - k)) - index + + +def permutation_index(element, iterable): + """Equivalent to ``list(permutations(iterable, r)).index(element)``` + + The subsequences of *iterable* that are of length *r* where order is + important can be ordered lexicographically. :func:`permutation_index` + computes the index of the first *element* directly, without computing + the previous permutations. + + >>> permutation_index([1, 3, 2], range(5)) + 19 + + ``ValueError`` will be raised if the given *element* isn't one of the + permutations of *iterable*. + """ + index = 0 + pool = list(iterable) + for i, x in zip(range(len(pool), -1, -1), element): + r = pool.index(x) + index = index * i + r + del pool[r] + + return index + + +class countable: + """Wrap *iterable* and keep a count of how many items have been consumed. + + The ``items_seen`` attribute starts at ``0`` and increments as the iterable + is consumed: + + >>> iterable = map(str, range(10)) + >>> it = countable(iterable) + >>> it.items_seen + 0 + >>> next(it), next(it) + ('0', '1') + >>> list(it) + ['2', '3', '4', '5', '6', '7', '8', '9'] + >>> it.items_seen + 10 + """ + + def __init__(self, iterable): + self._it = iter(iterable) + self.items_seen = 0 + + def __iter__(self): + return self + + def __next__(self): + item = next(self._it) + self.items_seen += 1 + + return item diff --git a/pipenv/vendor/more_itertools/more.pyi b/pipenv/vendor/more_itertools/more.pyi new file mode 100644 index 00000000..2fba9cb3 --- /dev/null +++ b/pipenv/vendor/more_itertools/more.pyi @@ -0,0 +1,480 @@ +"""Stubs for more_itertools.more""" + +from typing import ( + Any, + Callable, + Container, + Dict, + Generic, + Hashable, + Iterable, + Iterator, + List, + Optional, + Reversible, + Sequence, + Sized, + Tuple, + Union, + TypeVar, + type_check_only, +) +from types import TracebackType +from typing_extensions import ContextManager, Protocol, Type, overload + +# Type and type variable definitions +_T = TypeVar('_T') +_U = TypeVar('_U') +_V = TypeVar('_V') +_W = TypeVar('_W') +_T_co = TypeVar('_T_co', covariant=True) +_GenFn = TypeVar('_GenFn', bound=Callable[..., Iterator[object]]) +_Raisable = Union[BaseException, 'Type[BaseException]'] + +@type_check_only +class _SizedIterable(Protocol[_T_co], Sized, Iterable[_T_co]): ... + +@type_check_only +class _SizedReversible(Protocol[_T_co], Sized, Reversible[_T_co]): ... + +def chunked( + iterable: Iterable[_T], n: int, strict: bool = ... +) -> Iterator[List[_T]]: ... +@overload +def first(iterable: Iterable[_T]) -> _T: ... +@overload +def first(iterable: Iterable[_T], default: _U) -> Union[_T, _U]: ... +@overload +def last(iterable: Iterable[_T]) -> _T: ... +@overload +def last(iterable: Iterable[_T], default: _U) -> Union[_T, _U]: ... +@overload +def nth_or_last(iterable: Iterable[_T], n: int) -> _T: ... +@overload +def nth_or_last( + iterable: Iterable[_T], n: int, default: _U +) -> Union[_T, _U]: ... + +class peekable(Generic[_T], Iterator[_T]): + def __init__(self, iterable: Iterable[_T]) -> None: ... + def __iter__(self) -> peekable[_T]: ... + def __bool__(self) -> bool: ... + @overload + def peek(self) -> _T: ... + @overload + def peek(self, default: _U) -> Union[_T, _U]: ... + def prepend(self, *items: _T) -> None: ... + def __next__(self) -> _T: ... + @overload + def __getitem__(self, index: int) -> _T: ... + @overload + def __getitem__(self, index: slice) -> List[_T]: ... + +def collate(*iterables: Iterable[_T], **kwargs: Any) -> Iterable[_T]: ... +def consumer(func: _GenFn) -> _GenFn: ... +def ilen(iterable: Iterable[object]) -> int: ... +def iterate(func: Callable[[_T], _T], start: _T) -> Iterator[_T]: ... +def with_iter( + context_manager: ContextManager[Iterable[_T]], +) -> Iterator[_T]: ... +def one( + iterable: Iterable[_T], + too_short: Optional[_Raisable] = ..., + too_long: Optional[_Raisable] = ..., +) -> _T: ... +def distinct_permutations( + iterable: Iterable[_T], r: Optional[int] = ... +) -> Iterator[Tuple[_T, ...]]: ... +def intersperse( + e: _U, iterable: Iterable[_T], n: int = ... +) -> Iterator[Union[_T, _U]]: ... +def unique_to_each(*iterables: Iterable[_T]) -> List[List[_T]]: ... +@overload +def windowed( + seq: Iterable[_T], n: int, *, step: int = ... +) -> Iterator[Tuple[Optional[_T], ...]]: ... +@overload +def windowed( + seq: Iterable[_T], n: int, fillvalue: _U, step: int = ... +) -> Iterator[Tuple[Union[_T, _U], ...]]: ... +def substrings(iterable: Iterable[_T]) -> Iterator[Tuple[_T, ...]]: ... +def substrings_indexes( + seq: Sequence[_T], reverse: bool = ... +) -> Iterator[Tuple[Sequence[_T], int, int]]: ... + +class bucket(Generic[_T, _U], Container[_U]): + def __init__( + self, + iterable: Iterable[_T], + key: Callable[[_T], _U], + validator: Optional[Callable[[object], object]] = ..., + ) -> None: ... + def __contains__(self, value: object) -> bool: ... + def __iter__(self) -> Iterator[_U]: ... + def __getitem__(self, value: object) -> Iterator[_T]: ... + +def spy( + iterable: Iterable[_T], n: int = ... +) -> Tuple[List[_T], Iterator[_T]]: ... +def interleave(*iterables: Iterable[_T]) -> Iterator[_T]: ... +def interleave_longest(*iterables: Iterable[_T]) -> Iterator[_T]: ... +def collapse( + iterable: Iterable[Any], + base_type: Optional[type] = ..., + levels: Optional[int] = ..., +) -> Iterator[Any]: ... +@overload +def side_effect( + func: Callable[[_T], object], + iterable: Iterable[_T], + chunk_size: None = ..., + before: Optional[Callable[[], object]] = ..., + after: Optional[Callable[[], object]] = ..., +) -> Iterator[_T]: ... +@overload +def side_effect( + func: Callable[[List[_T]], object], + iterable: Iterable[_T], + chunk_size: int, + before: Optional[Callable[[], object]] = ..., + after: Optional[Callable[[], object]] = ..., +) -> Iterator[_T]: ... +def sliced( + seq: Sequence[_T], n: int, strict: bool = ... +) -> Iterator[Sequence[_T]]: ... +def split_at( + iterable: Iterable[_T], + pred: Callable[[_T], object], + maxsplit: int = ..., + keep_separator: bool = ..., +) -> Iterator[List[_T]]: ... +def split_before( + iterable: Iterable[_T], pred: Callable[[_T], object], maxsplit: int = ... +) -> Iterator[List[_T]]: ... +def split_after( + iterable: Iterable[_T], pred: Callable[[_T], object], maxsplit: int = ... +) -> Iterator[List[_T]]: ... +def split_when( + iterable: Iterable[_T], + pred: Callable[[_T, _T], object], + maxsplit: int = ..., +) -> Iterator[List[_T]]: ... +def split_into( + iterable: Iterable[_T], sizes: Iterable[Optional[int]] +) -> Iterator[List[_T]]: ... +@overload +def padded( + iterable: Iterable[_T], + *, + n: Optional[int] = ..., + next_multiple: bool = ... +) -> Iterator[Optional[_T]]: ... +@overload +def padded( + iterable: Iterable[_T], + fillvalue: _U, + n: Optional[int] = ..., + next_multiple: bool = ..., +) -> Iterator[Union[_T, _U]]: ... +@overload +def repeat_last(iterable: Iterable[_T]) -> Iterator[_T]: ... +@overload +def repeat_last( + iterable: Iterable[_T], default: _U +) -> Iterator[Union[_T, _U]]: ... +def distribute(n: int, iterable: Iterable[_T]) -> List[Iterator[_T]]: ... +@overload +def stagger( + iterable: Iterable[_T], + offsets: _SizedIterable[int] = ..., + longest: bool = ..., +) -> Iterator[Tuple[Optional[_T], ...]]: ... +@overload +def stagger( + iterable: Iterable[_T], + offsets: _SizedIterable[int] = ..., + longest: bool = ..., + fillvalue: _U = ..., +) -> Iterator[Tuple[Union[_T, _U], ...]]: ... + +class UnequalIterablesError(ValueError): + def __init__( + self, details: Optional[Tuple[int, int, int]] = ... + ) -> None: ... + +def zip_equal(*iterables: Iterable[_T]) -> Iterator[Tuple[_T, ...]]: ... +@overload +def zip_offset( + *iterables: Iterable[_T], offsets: _SizedIterable[int], longest: bool = ... +) -> Iterator[Tuple[Optional[_T], ...]]: ... +@overload +def zip_offset( + *iterables: Iterable[_T], + offsets: _SizedIterable[int], + longest: bool = ..., + fillvalue: _U +) -> Iterator[Tuple[Union[_T, _U], ...]]: ... +def sort_together( + iterables: Iterable[Iterable[_T]], + key_list: Iterable[int] = ..., + key: Optional[Callable[..., Any]] = ..., + reverse: bool = ..., +) -> List[Tuple[_T, ...]]: ... +def unzip(iterable: Iterable[Sequence[_T]]) -> Tuple[Iterator[_T], ...]: ... +def divide(n: int, iterable: Iterable[_T]) -> List[Iterator[_T]]: ... +def always_iterable( + obj: object, + base_type: Union[ + type, Tuple[Union[type, Tuple[Any, ...]], ...], None + ] = ..., +) -> Iterator[Any]: ... +def adjacent( + predicate: Callable[[_T], bool], + iterable: Iterable[_T], + distance: int = ..., +) -> Iterator[Tuple[bool, _T]]: ... +def groupby_transform( + iterable: Iterable[_T], + keyfunc: Optional[Callable[[_T], _U]] = ..., + valuefunc: Optional[Callable[[_T], _V]] = ..., + reducefunc: Optional[Callable[..., _W]] = ..., +) -> Iterator[Tuple[_T, _W]]: ... + +class numeric_range(Generic[_T, _U], Sequence[_T], Hashable, Reversible[_T]): + @overload + def __init__(self, __stop: _T) -> None: ... + @overload + def __init__(self, __start: _T, __stop: _T) -> None: ... + @overload + def __init__(self, __start: _T, __stop: _T, __step: _U) -> None: ... + def __bool__(self) -> bool: ... + def __contains__(self, elem: object) -> bool: ... + def __eq__(self, other: object) -> bool: ... + @overload + def __getitem__(self, key: int) -> _T: ... + @overload + def __getitem__(self, key: slice) -> numeric_range[_T, _U]: ... + def __hash__(self) -> int: ... + def __iter__(self) -> Iterator[_T]: ... + def __len__(self) -> int: ... + def __reduce__( + self, + ) -> Tuple[Type[numeric_range[_T, _U]], Tuple[_T, _T, _U]]: ... + def __repr__(self) -> str: ... + def __reversed__(self) -> Iterator[_T]: ... + def count(self, value: _T) -> int: ... + def index(self, value: _T) -> int: ... # type: ignore + +def count_cycle( + iterable: Iterable[_T], n: Optional[int] = ... +) -> Iterable[Tuple[int, _T]]: ... +def mark_ends( + iterable: Iterable[_T], +) -> Iterable[Tuple[bool, bool, _T]]: ... +def locate( + iterable: Iterable[object], + pred: Callable[..., Any] = ..., + window_size: Optional[int] = ..., +) -> Iterator[int]: ... +def lstrip( + iterable: Iterable[_T], pred: Callable[[_T], object] +) -> Iterator[_T]: ... +def rstrip( + iterable: Iterable[_T], pred: Callable[[_T], object] +) -> Iterator[_T]: ... +def strip( + iterable: Iterable[_T], pred: Callable[[_T], object] +) -> Iterator[_T]: ... + +class islice_extended(Generic[_T], Iterator[_T]): + def __init__( + self, iterable: Iterable[_T], *args: Optional[int] + ) -> None: ... + def __iter__(self) -> islice_extended[_T]: ... + def __next__(self) -> _T: ... + def __getitem__(self, index: slice) -> islice_extended[_T]: ... + +def always_reversible(iterable: Iterable[_T]) -> Iterator[_T]: ... +def consecutive_groups( + iterable: Iterable[_T], ordering: Callable[[_T], int] = ... +) -> Iterator[Iterator[_T]]: ... +@overload +def difference( + iterable: Iterable[_T], + func: Callable[[_T, _T], _U] = ..., + *, + initial: None = ... +) -> Iterator[Union[_T, _U]]: ... +@overload +def difference( + iterable: Iterable[_T], func: Callable[[_T, _T], _U] = ..., *, initial: _U +) -> Iterator[_U]: ... + +class SequenceView(Generic[_T], Sequence[_T]): + def __init__(self, target: Sequence[_T]) -> None: ... + @overload + def __getitem__(self, index: int) -> _T: ... + @overload + def __getitem__(self, index: slice) -> Sequence[_T]: ... + def __len__(self) -> int: ... + +class seekable(Generic[_T], Iterator[_T]): + def __init__( + self, iterable: Iterable[_T], maxlen: Optional[int] = ... + ) -> None: ... + def __iter__(self) -> seekable[_T]: ... + def __next__(self) -> _T: ... + def __bool__(self) -> bool: ... + @overload + def peek(self) -> _T: ... + @overload + def peek(self, default: _U) -> Union[_T, _U]: ... + def elements(self) -> SequenceView[_T]: ... + def seek(self, index: int) -> None: ... + +class run_length: + @staticmethod + def encode(iterable: Iterable[_T]) -> Iterator[Tuple[_T, int]]: ... + @staticmethod + def decode(iterable: Iterable[Tuple[_T, int]]) -> Iterator[_T]: ... + +def exactly_n( + iterable: Iterable[_T], n: int, predicate: Callable[[_T], object] = ... +) -> bool: ... +def circular_shifts(iterable: Iterable[_T]) -> List[Tuple[_T, ...]]: ... +def make_decorator( + wrapping_func: Callable[..., _U], result_index: int = ... +) -> Callable[..., Callable[[Callable[..., Any]], Callable[..., _U]]]: ... +@overload +def map_reduce( + iterable: Iterable[_T], + keyfunc: Callable[[_T], _U], + valuefunc: None = ..., + reducefunc: None = ..., +) -> Dict[_U, List[_T]]: ... +@overload +def map_reduce( + iterable: Iterable[_T], + keyfunc: Callable[[_T], _U], + valuefunc: Callable[[_T], _V], + reducefunc: None = ..., +) -> Dict[_U, List[_V]]: ... +@overload +def map_reduce( + iterable: Iterable[_T], + keyfunc: Callable[[_T], _U], + valuefunc: None = ..., + reducefunc: Callable[[List[_T]], _W] = ..., +) -> Dict[_U, _W]: ... +@overload +def map_reduce( + iterable: Iterable[_T], + keyfunc: Callable[[_T], _U], + valuefunc: Callable[[_T], _V], + reducefunc: Callable[[List[_V]], _W], +) -> Dict[_U, _W]: ... +def rlocate( + iterable: Iterable[_T], + pred: Callable[..., object] = ..., + window_size: Optional[int] = ..., +) -> Iterator[int]: ... +def replace( + iterable: Iterable[_T], + pred: Callable[..., object], + substitutes: Iterable[_U], + count: Optional[int] = ..., + window_size: int = ..., +) -> Iterator[Union[_T, _U]]: ... +def partitions(iterable: Iterable[_T]) -> Iterator[List[List[_T]]]: ... +def set_partitions( + iterable: Iterable[_T], k: Optional[int] = ... +) -> Iterator[List[List[_T]]]: ... + +class time_limited(Generic[_T], Iterator[_T]): + def __init__( + self, limit_seconds: float, iterable: Iterable[_T] + ) -> None: ... + def __iter__(self) -> islice_extended[_T]: ... + def __next__(self) -> _T: ... + +@overload +def only( + iterable: Iterable[_T], *, too_long: Optional[_Raisable] = ... +) -> Optional[_T]: ... +@overload +def only( + iterable: Iterable[_T], default: _U, too_long: Optional[_Raisable] = ... +) -> Union[_T, _U]: ... +def ichunked(iterable: Iterable[_T], n: int) -> Iterator[Iterator[_T]]: ... +def distinct_combinations( + iterable: Iterable[_T], r: int +) -> Iterator[Tuple[_T, ...]]: ... +def filter_except( + validator: Callable[[Any], object], + iterable: Iterable[_T], + *exceptions: Type[BaseException] +) -> Iterator[_T]: ... +def map_except( + function: Callable[[Any], _U], + iterable: Iterable[_T], + *exceptions: Type[BaseException] +) -> Iterator[_U]: ... +def sample( + iterable: Iterable[_T], + k: int, + weights: Optional[Iterable[float]] = ..., +) -> List[_T]: ... +def is_sorted( + iterable: Iterable[_T], + key: Optional[Callable[[_T], _U]] = ..., + reverse: bool = False, +) -> bool: ... + +class AbortThread(BaseException): + pass + +class callback_iter(Generic[_T], Iterator[_T]): + def __init__( + self, + func: Callable[..., Any], + callback_kwd: str = ..., + wait_seconds: float = ..., + ) -> None: ... + def __enter__(self) -> callback_iter[_T]: ... + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_value: Optional[BaseException], + traceback: Optional[TracebackType], + ) -> Optional[bool]: ... + def __iter__(self) -> callback_iter[_T]: ... + def __next__(self) -> _T: ... + def _reader(self) -> Iterator[_T]: ... + @property + def done(self) -> bool: ... + @property + def result(self) -> Any: ... + +def windowed_complete( + iterable: Iterable[_T], n: int +) -> Iterator[Tuple[_T, ...]]: ... +def all_unique( + iterable: Iterable[_T], key: Optional[Callable[[_T], _U]] = ... +) -> bool: ... +def nth_product(index: int, *args: Iterable[_T]) -> Tuple[_T, ...]: ... +def nth_permutation( + iterable: Iterable[_T], r: int, index: int +) -> Tuple[_T, ...]: ... +def value_chain(*args: Union[_T, Iterable[_T]]) -> Iterable[_T]: ... +def product_index(element: Iterable[_T], *args: Iterable[_T]) -> int: ... +def combination_index( + element: Iterable[_T], iterable: Iterable[_T] +) -> int: ... +def permutation_index( + element: Iterable[_T], iterable: Iterable[_T] +) -> int: ... + +class countable(Generic[_T], Iterator[_T]): + def __init__(self, iterable: Iterable[_T]) -> None: ... + def __iter__(self) -> countable[_T]: ... + def __next__(self) -> _T: ... diff --git a/pipenv/vendor/more_itertools/py.typed b/pipenv/vendor/more_itertools/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/pipenv/vendor/more_itertools/recipes.py b/pipenv/vendor/more_itertools/recipes.py new file mode 100644 index 00000000..521abd7c --- /dev/null +++ b/pipenv/vendor/more_itertools/recipes.py @@ -0,0 +1,620 @@ +"""Imported from the recipes section of the itertools documentation. + +All functions taken from the recipes section of the itertools library docs +[1]_. +Some backward-compatible usability improvements have been made. + +.. [1] http://docs.python.org/library/itertools.html#recipes + +""" +import warnings +from collections import deque +from itertools import ( + chain, + combinations, + count, + cycle, + groupby, + islice, + repeat, + starmap, + tee, + zip_longest, +) +import operator +from random import randrange, sample, choice + +__all__ = [ + 'all_equal', + 'consume', + 'convolve', + 'dotproduct', + 'first_true', + 'flatten', + 'grouper', + 'iter_except', + 'ncycles', + 'nth', + 'nth_combination', + 'padnone', + 'pad_none', + 'pairwise', + 'partition', + 'powerset', + 'prepend', + 'quantify', + 'random_combination_with_replacement', + 'random_combination', + 'random_permutation', + 'random_product', + 'repeatfunc', + 'roundrobin', + 'tabulate', + 'tail', + 'take', + 'unique_everseen', + 'unique_justseen', +] + + +def take(n, iterable): + """Return first *n* items of the iterable as a list. + + >>> take(3, range(10)) + [0, 1, 2] + + If there are fewer than *n* items in the iterable, all of them are + returned. + + >>> take(10, range(3)) + [0, 1, 2] + + """ + return list(islice(iterable, n)) + + +def tabulate(function, start=0): + """Return an iterator over the results of ``func(start)``, + ``func(start + 1)``, ``func(start + 2)``... + + *func* should be a function that accepts one integer argument. + + If *start* is not specified it defaults to 0. It will be incremented each + time the iterator is advanced. + + >>> square = lambda x: x ** 2 + >>> iterator = tabulate(square, -3) + >>> take(4, iterator) + [9, 4, 1, 0] + + """ + return map(function, count(start)) + + +def tail(n, iterable): + """Return an iterator over the last *n* items of *iterable*. + + >>> t = tail(3, 'ABCDEFG') + >>> list(t) + ['E', 'F', 'G'] + + """ + return iter(deque(iterable, maxlen=n)) + + +def consume(iterator, n=None): + """Advance *iterable* by *n* steps. If *n* is ``None``, consume it + entirely. + + Efficiently exhausts an iterator without returning values. Defaults to + consuming the whole iterator, but an optional second argument may be + provided to limit consumption. + + >>> i = (x for x in range(10)) + >>> next(i) + 0 + >>> consume(i, 3) + >>> next(i) + 4 + >>> consume(i) + >>> next(i) + Traceback (most recent call last): + File "<stdin>", line 1, in <module> + StopIteration + + If the iterator has fewer items remaining than the provided limit, the + whole iterator will be consumed. + + >>> i = (x for x in range(3)) + >>> consume(i, 5) + >>> next(i) + Traceback (most recent call last): + File "<stdin>", line 1, in <module> + StopIteration + + """ + # Use functions that consume iterators at C speed. + if n is None: + # feed the entire iterator into a zero-length deque + deque(iterator, maxlen=0) + else: + # advance to the empty slice starting at position n + next(islice(iterator, n, n), None) + + +def nth(iterable, n, default=None): + """Returns the nth item or a default value. + + >>> l = range(10) + >>> nth(l, 3) + 3 + >>> nth(l, 20, "zebra") + 'zebra' + + """ + return next(islice(iterable, n, None), default) + + +def all_equal(iterable): + """ + Returns ``True`` if all the elements are equal to each other. + + >>> all_equal('aaaa') + True + >>> all_equal('aaab') + False + + """ + g = groupby(iterable) + return next(g, True) and not next(g, False) + + +def quantify(iterable, pred=bool): + """Return the how many times the predicate is true. + + >>> quantify([True, False, True]) + 2 + + """ + return sum(map(pred, iterable)) + + +def pad_none(iterable): + """Returns the sequence of elements and then returns ``None`` indefinitely. + + >>> take(5, pad_none(range(3))) + [0, 1, 2, None, None] + + Useful for emulating the behavior of the built-in :func:`map` function. + + See also :func:`padded`. + + """ + return chain(iterable, repeat(None)) + + +padnone = pad_none + + +def ncycles(iterable, n): + """Returns the sequence elements *n* times + + >>> list(ncycles(["a", "b"], 3)) + ['a', 'b', 'a', 'b', 'a', 'b'] + + """ + return chain.from_iterable(repeat(tuple(iterable), n)) + + +def dotproduct(vec1, vec2): + """Returns the dot product of the two iterables. + + >>> dotproduct([10, 10], [20, 20]) + 400 + + """ + return sum(map(operator.mul, vec1, vec2)) + + +def flatten(listOfLists): + """Return an iterator flattening one level of nesting in a list of lists. + + >>> list(flatten([[0, 1], [2, 3]])) + [0, 1, 2, 3] + + See also :func:`collapse`, which can flatten multiple levels of nesting. + + """ + return chain.from_iterable(listOfLists) + + +def repeatfunc(func, times=None, *args): + """Call *func* with *args* repeatedly, returning an iterable over the + results. + + If *times* is specified, the iterable will terminate after that many + repetitions: + + >>> from operator import add + >>> times = 4 + >>> args = 3, 5 + >>> list(repeatfunc(add, times, *args)) + [8, 8, 8, 8] + + If *times* is ``None`` the iterable will not terminate: + + >>> from random import randrange + >>> times = None + >>> args = 1, 11 + >>> take(6, repeatfunc(randrange, times, *args)) # doctest:+SKIP + [2, 4, 8, 1, 8, 4] + + """ + if times is None: + return starmap(func, repeat(args)) + return starmap(func, repeat(args, times)) + + +def _pairwise(iterable): + """Returns an iterator of paired items, overlapping, from the original + + >>> take(4, pairwise(count())) + [(0, 1), (1, 2), (2, 3), (3, 4)] + + On Python 3.10 and above, this is an alias for :func:`itertools.pairwise`. + + """ + a, b = tee(iterable) + next(b, None) + yield from zip(a, b) + + +try: + from itertools import pairwise as itertools_pairwise +except ImportError: + pairwise = _pairwise +else: + + def pairwise(iterable): + yield from itertools_pairwise(iterable) + + pairwise.__doc__ = _pairwise.__doc__ + + +def grouper(iterable, n, fillvalue=None): + """Collect data into fixed-length chunks or blocks. + + >>> list(grouper('ABCDEFG', 3, 'x')) + [('A', 'B', 'C'), ('D', 'E', 'F'), ('G', 'x', 'x')] + + """ + if isinstance(iterable, int): + warnings.warn( + "grouper expects iterable as first parameter", DeprecationWarning + ) + n, iterable = iterable, n + args = [iter(iterable)] * n + return zip_longest(fillvalue=fillvalue, *args) + + +def roundrobin(*iterables): + """Yields an item from each iterable, alternating between them. + + >>> list(roundrobin('ABC', 'D', 'EF')) + ['A', 'D', 'E', 'B', 'F', 'C'] + + This function produces the same output as :func:`interleave_longest`, but + may perform better for some inputs (in particular when the number of + iterables is small). + + """ + # Recipe credited to George Sakkis + pending = len(iterables) + nexts = cycle(iter(it).__next__ for it in iterables) + while pending: + try: + for next in nexts: + yield next() + except StopIteration: + pending -= 1 + nexts = cycle(islice(nexts, pending)) + + +def partition(pred, iterable): + """ + Returns a 2-tuple of iterables derived from the input iterable. + The first yields the items that have ``pred(item) == False``. + The second yields the items that have ``pred(item) == True``. + + >>> is_odd = lambda x: x % 2 != 0 + >>> iterable = range(10) + >>> even_items, odd_items = partition(is_odd, iterable) + >>> list(even_items), list(odd_items) + ([0, 2, 4, 6, 8], [1, 3, 5, 7, 9]) + + If *pred* is None, :func:`bool` is used. + + >>> iterable = [0, 1, False, True, '', ' '] + >>> false_items, true_items = partition(None, iterable) + >>> list(false_items), list(true_items) + ([0, False, ''], [1, True, ' ']) + + """ + if pred is None: + pred = bool + + evaluations = ((pred(x), x) for x in iterable) + t1, t2 = tee(evaluations) + return ( + (x for (cond, x) in t1 if not cond), + (x for (cond, x) in t2 if cond), + ) + + +def powerset(iterable): + """Yields all possible subsets of the iterable. + + >>> list(powerset([1, 2, 3])) + [(), (1,), (2,), (3,), (1, 2), (1, 3), (2, 3), (1, 2, 3)] + + :func:`powerset` will operate on iterables that aren't :class:`set` + instances, so repeated elements in the input will produce repeated elements + in the output. Use :func:`unique_everseen` on the input to avoid generating + duplicates: + + >>> seq = [1, 1, 0] + >>> list(powerset(seq)) + [(), (1,), (1,), (0,), (1, 1), (1, 0), (1, 0), (1, 1, 0)] + >>> from more_itertools import unique_everseen + >>> list(powerset(unique_everseen(seq))) + [(), (1,), (0,), (1, 0)] + + """ + s = list(iterable) + return chain.from_iterable(combinations(s, r) for r in range(len(s) + 1)) + + +def unique_everseen(iterable, key=None): + """ + Yield unique elements, preserving order. + + >>> list(unique_everseen('AAAABBBCCDAABBB')) + ['A', 'B', 'C', 'D'] + >>> list(unique_everseen('ABBCcAD', str.lower)) + ['A', 'B', 'C', 'D'] + + Sequences with a mix of hashable and unhashable items can be used. + The function will be slower (i.e., `O(n^2)`) for unhashable items. + + Remember that ``list`` objects are unhashable - you can use the *key* + parameter to transform the list to a tuple (which is hashable) to + avoid a slowdown. + + >>> iterable = ([1, 2], [2, 3], [1, 2]) + >>> list(unique_everseen(iterable)) # Slow + [[1, 2], [2, 3]] + >>> list(unique_everseen(iterable, key=tuple)) # Faster + [[1, 2], [2, 3]] + + Similary, you may want to convert unhashable ``set`` objects with + ``key=frozenset``. For ``dict`` objects, + ``key=lambda x: frozenset(x.items())`` can be used. + + """ + seenset = set() + seenset_add = seenset.add + seenlist = [] + seenlist_add = seenlist.append + use_key = key is not None + + for element in iterable: + k = key(element) if use_key else element + try: + if k not in seenset: + seenset_add(k) + yield element + except TypeError: + if k not in seenlist: + seenlist_add(k) + yield element + + +def unique_justseen(iterable, key=None): + """Yields elements in order, ignoring serial duplicates + + >>> list(unique_justseen('AAAABBBCCDAABBB')) + ['A', 'B', 'C', 'D', 'A', 'B'] + >>> list(unique_justseen('ABBCcAD', str.lower)) + ['A', 'B', 'C', 'A', 'D'] + + """ + return map(next, map(operator.itemgetter(1), groupby(iterable, key))) + + +def iter_except(func, exception, first=None): + """Yields results from a function repeatedly until an exception is raised. + + Converts a call-until-exception interface to an iterator interface. + Like ``iter(func, sentinel)``, but uses an exception instead of a sentinel + to end the loop. + + >>> l = [0, 1, 2] + >>> list(iter_except(l.pop, IndexError)) + [2, 1, 0] + + """ + try: + if first is not None: + yield first() + while 1: + yield func() + except exception: + pass + + +def first_true(iterable, default=None, pred=None): + """ + Returns the first true value in the iterable. + + If no true value is found, returns *default* + + If *pred* is not None, returns the first item for which + ``pred(item) == True`` . + + >>> first_true(range(10)) + 1 + >>> first_true(range(10), pred=lambda x: x > 5) + 6 + >>> first_true(range(10), default='missing', pred=lambda x: x > 9) + 'missing' + + """ + return next(filter(pred, iterable), default) + + +def random_product(*args, repeat=1): + """Draw an item at random from each of the input iterables. + + >>> random_product('abc', range(4), 'XYZ') # doctest:+SKIP + ('c', 3, 'Z') + + If *repeat* is provided as a keyword argument, that many items will be + drawn from each iterable. + + >>> random_product('abcd', range(4), repeat=2) # doctest:+SKIP + ('a', 2, 'd', 3) + + This equivalent to taking a random selection from + ``itertools.product(*args, **kwarg)``. + + """ + pools = [tuple(pool) for pool in args] * repeat + return tuple(choice(pool) for pool in pools) + + +def random_permutation(iterable, r=None): + """Return a random *r* length permutation of the elements in *iterable*. + + If *r* is not specified or is ``None``, then *r* defaults to the length of + *iterable*. + + >>> random_permutation(range(5)) # doctest:+SKIP + (3, 4, 0, 1, 2) + + This equivalent to taking a random selection from + ``itertools.permutations(iterable, r)``. + + """ + pool = tuple(iterable) + r = len(pool) if r is None else r + return tuple(sample(pool, r)) + + +def random_combination(iterable, r): + """Return a random *r* length subsequence of the elements in *iterable*. + + >>> random_combination(range(5), 3) # doctest:+SKIP + (2, 3, 4) + + This equivalent to taking a random selection from + ``itertools.combinations(iterable, r)``. + + """ + pool = tuple(iterable) + n = len(pool) + indices = sorted(sample(range(n), r)) + return tuple(pool[i] for i in indices) + + +def random_combination_with_replacement(iterable, r): + """Return a random *r* length subsequence of elements in *iterable*, + allowing individual elements to be repeated. + + >>> random_combination_with_replacement(range(3), 5) # doctest:+SKIP + (0, 0, 1, 2, 2) + + This equivalent to taking a random selection from + ``itertools.combinations_with_replacement(iterable, r)``. + + """ + pool = tuple(iterable) + n = len(pool) + indices = sorted(randrange(n) for i in range(r)) + return tuple(pool[i] for i in indices) + + +def nth_combination(iterable, r, index): + """Equivalent to ``list(combinations(iterable, r))[index]``. + + The subsequences of *iterable* that are of length *r* can be ordered + lexicographically. :func:`nth_combination` computes the subsequence at + sort position *index* directly, without computing the previous + subsequences. + + >>> nth_combination(range(5), 3, 5) + (0, 3, 4) + + ``ValueError`` will be raised If *r* is negative or greater than the length + of *iterable*. + ``IndexError`` will be raised if the given *index* is invalid. + """ + pool = tuple(iterable) + n = len(pool) + if (r < 0) or (r > n): + raise ValueError + + c = 1 + k = min(r, n - r) + for i in range(1, k + 1): + c = c * (n - k + i) // i + + if index < 0: + index += c + + if (index < 0) or (index >= c): + raise IndexError + + result = [] + while r: + c, n, r = c * r // n, n - 1, r - 1 + while index >= c: + index -= c + c, n = c * (n - r) // n, n - 1 + result.append(pool[-1 - n]) + + return tuple(result) + + +def prepend(value, iterator): + """Yield *value*, followed by the elements in *iterator*. + + >>> value = '0' + >>> iterator = ['1', '2', '3'] + >>> list(prepend(value, iterator)) + ['0', '1', '2', '3'] + + To prepend multiple values, see :func:`itertools.chain` + or :func:`value_chain`. + + """ + return chain([value], iterator) + + +def convolve(signal, kernel): + """Convolve the iterable *signal* with the iterable *kernel*. + + >>> signal = (1, 2, 3, 4, 5) + >>> kernel = [3, 2, 1] + >>> list(convolve(signal, kernel)) + [3, 8, 14, 20, 26, 14, 5] + + Note: the input arguments are not interchangeable, as the *kernel* + is immediately consumed and stored. + + """ + kernel = tuple(kernel)[::-1] + n = len(kernel) + window = deque([0], maxlen=n) * n + for x in chain(signal, repeat(0, n - 1)): + window.append(x) + yield sum(map(operator.mul, kernel, window)) diff --git a/pipenv/vendor/more_itertools/recipes.pyi b/pipenv/vendor/more_itertools/recipes.pyi new file mode 100644 index 00000000..5e39d963 --- /dev/null +++ b/pipenv/vendor/more_itertools/recipes.pyi @@ -0,0 +1,103 @@ +"""Stubs for more_itertools.recipes""" +from typing import ( + Any, + Callable, + Iterable, + Iterator, + List, + Optional, + Tuple, + TypeVar, + Union, +) +from typing_extensions import overload, Type + +# Type and type variable definitions +_T = TypeVar('_T') +_U = TypeVar('_U') + +def take(n: int, iterable: Iterable[_T]) -> List[_T]: ... +def tabulate( + function: Callable[[int], _T], start: int = ... +) -> Iterator[_T]: ... +def tail(n: int, iterable: Iterable[_T]) -> Iterator[_T]: ... +def consume(iterator: Iterable[object], n: Optional[int] = ...) -> None: ... +@overload +def nth(iterable: Iterable[_T], n: int) -> Optional[_T]: ... +@overload +def nth(iterable: Iterable[_T], n: int, default: _U) -> Union[_T, _U]: ... +def all_equal(iterable: Iterable[object]) -> bool: ... +def quantify( + iterable: Iterable[_T], pred: Callable[[_T], bool] = ... +) -> int: ... +def pad_none(iterable: Iterable[_T]) -> Iterator[Optional[_T]]: ... +def padnone(iterable: Iterable[_T]) -> Iterator[Optional[_T]]: ... +def ncycles(iterable: Iterable[_T], n: int) -> Iterator[_T]: ... +def dotproduct(vec1: Iterable[object], vec2: Iterable[object]) -> object: ... +def flatten(listOfLists: Iterable[Iterable[_T]]) -> Iterator[_T]: ... +def repeatfunc( + func: Callable[..., _U], times: Optional[int] = ..., *args: Any +) -> Iterator[_U]: ... +def pairwise(iterable: Iterable[_T]) -> Iterator[Tuple[_T, _T]]: ... +@overload +def grouper( + iterable: Iterable[_T], n: int +) -> Iterator[Tuple[Optional[_T], ...]]: ... +@overload +def grouper( + iterable: Iterable[_T], n: int, fillvalue: _U +) -> Iterator[Tuple[Union[_T, _U], ...]]: ... +@overload +def grouper( # Deprecated interface + iterable: int, n: Iterable[_T] +) -> Iterator[Tuple[Optional[_T], ...]]: ... +@overload +def grouper( # Deprecated interface + iterable: int, n: Iterable[_T], fillvalue: _U +) -> Iterator[Tuple[Union[_T, _U], ...]]: ... +def roundrobin(*iterables: Iterable[_T]) -> Iterator[_T]: ... +def partition( + pred: Optional[Callable[[_T], object]], iterable: Iterable[_T] +) -> Tuple[Iterator[_T], Iterator[_T]]: ... +def powerset(iterable: Iterable[_T]) -> Iterator[Tuple[_T, ...]]: ... +def unique_everseen( + iterable: Iterable[_T], key: Optional[Callable[[_T], _U]] = ... +) -> Iterator[_T]: ... +def unique_justseen( + iterable: Iterable[_T], key: Optional[Callable[[_T], object]] = ... +) -> Iterator[_T]: ... +@overload +def iter_except( + func: Callable[[], _T], exception: Type[BaseException], first: None = ... +) -> Iterator[_T]: ... +@overload +def iter_except( + func: Callable[[], _T], + exception: Type[BaseException], + first: Callable[[], _U], +) -> Iterator[Union[_T, _U]]: ... +@overload +def first_true( + iterable: Iterable[_T], *, pred: Optional[Callable[[_T], object]] = ... +) -> Optional[_T]: ... +@overload +def first_true( + iterable: Iterable[_T], + default: _U, + pred: Optional[Callable[[_T], object]] = ..., +) -> Union[_T, _U]: ... +def random_product( + *args: Iterable[_T], repeat: int = ... +) -> Tuple[_T, ...]: ... +def random_permutation( + iterable: Iterable[_T], r: Optional[int] = ... +) -> Tuple[_T, ...]: ... +def random_combination(iterable: Iterable[_T], r: int) -> Tuple[_T, ...]: ... +def random_combination_with_replacement( + iterable: Iterable[_T], r: int +) -> Tuple[_T, ...]: ... +def nth_combination( + iterable: Iterable[_T], r: int, index: int +) -> Tuple[_T, ...]: ... +def prepend(value: _T, iterator: Iterable[_U]) -> Iterator[Union[_T, _U]]: ... +def convolve(signal: Iterable[_T], kernel: Iterable[_T]) -> Iterator[_T]: ... diff --git a/pipenv/vendor/orderedmultidict/LICENSE.md b/pipenv/vendor/orderedmultidict/LICENSE.md index 210e8658..fd832f40 100644 --- a/pipenv/vendor/orderedmultidict/LICENSE.md +++ b/pipenv/vendor/orderedmultidict/LICENSE.md @@ -28,4 +28,4 @@ OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\ SOFTWARE. -For more information, please refer to <http://unlicense.org/> +For more information, please refer to <https://unlicense.org/> diff --git a/pipenv/vendor/orderedmultidict/__init__.py b/pipenv/vendor/orderedmultidict/__init__.py index 4f0ce2f7..d122e35c 100755 --- a/pipenv/vendor/orderedmultidict/__init__.py +++ b/pipenv/vendor/orderedmultidict/__init__.py @@ -8,14 +8,14 @@ # grunseid@gmail.com # # License: Build Amazing Things (Unlicense) +# -from __future__ import absolute_import +from os.path import dirname, join as pjoin from .orderedmultidict import * # noqa -__title__ = 'orderedmultidict' -__version__ = '1.0' -__author__ = 'Ansgar Grunseid' -__contact__ = 'grunseid@gmail.com' -__license__ = 'Unlicense' -__url__ = 'https://github.com/gruns/orderedmultidict' +# Import all variables in __version__.py without explicit imports. +meta = {} +with open(pjoin(dirname(__file__), '__version__.py')) as f: + exec(f.read(), meta) +globals().update(dict((k, v) for k, v in meta.items() if k not in globals())) diff --git a/pipenv/vendor/orderedmultidict/__version__.py b/pipenv/vendor/orderedmultidict/__version__.py new file mode 100755 index 00000000..29e47e48 --- /dev/null +++ b/pipenv/vendor/orderedmultidict/__version__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- + +# +# omdict - Ordered Multivalue Dictionary. +# +# Ansgar Grunseid +# grunseid.com +# grunseid@gmail.com +# +# License: Build Amazing Things (Unlicense) +# + +__title__ = 'orderedmultidict' +__version__ = '1.0.1' +__license__ = 'Unlicense' +__author__ = 'Ansgar Grunseid' +__contact__ = 'grunseid@gmail.com' +__description__ = 'Ordered Multivalue Dictionary' +__url__ = 'https://github.com/gruns/orderedmultidict' diff --git a/pipenv/vendor/orderedmultidict/itemlist.py b/pipenv/vendor/orderedmultidict/itemlist.py index e9e96c72..6d31addc 100755 --- a/pipenv/vendor/orderedmultidict/itemlist.py +++ b/pipenv/vendor/orderedmultidict/itemlist.py @@ -8,10 +8,11 @@ # grunseid@gmail.com # # License: Build Amazing Things (Unlicense) +# from __future__ import absolute_import -from six.moves import zip_longest +from pipenv.vendor.six.moves import zip_longest _absent = object() # Marker that means no parameter was provided. diff --git a/pipenv/vendor/orderedmultidict/orderedmultidict.py b/pipenv/vendor/orderedmultidict/orderedmultidict.py index 924dd8d2..fd33f4ad 100755 --- a/pipenv/vendor/orderedmultidict/orderedmultidict.py +++ b/pipenv/vendor/orderedmultidict/orderedmultidict.py @@ -8,26 +8,30 @@ # grunseid@gmail.com # # License: Build Amazing Things (Unlicense) +# from __future__ import absolute_import +import sys from itertools import chain -from collections import MutableMapping -import six -from six.moves import map, zip_longest +import pipenv.vendor.six as six +from pipenv.vendor.six.moves import map, zip_longest from .itemlist import itemlist +if six.PY2: + from collections import MutableMapping +else: + from collections.abc import MutableMapping try: from collections import OrderedDict as odict # Python 2.7 and later. except ImportError: from ordereddict import OrderedDict as odict # Python 2.6 and earlier. -import sys -items_attr = 'items' if sys.version_info[0] >= 3 else 'iteritems' _absent = object() # Marker that means no parameter was provided. +_items_attr = 'items' if sys.version_info[0] >= 3 else 'iteritems' def callable_attr(obj, attr): @@ -765,7 +769,7 @@ class omdict(MutableMapping): for i1, i2 in zip_longest(myiter, otheriter, fillvalue=_absent): if i1 != i2 or i1 is _absent or i2 is _absent: return False - elif not hasattr(other, '__len__') or not hasattr(other, items_attr): + elif not hasattr(other, '__len__') or not hasattr(other, _items_attr): return False # Ignore order so we can compare ordered omdicts with unordered dicts. else: @@ -809,3 +813,21 @@ class omdict(MutableMapping): def __repr__(self): return '%s(%s)' % (self.__class__.__name__, self.allitems()) + + def __or__(self, other): + return self.__class__(chain(_get_items(self), _get_items(other))) + + def __ior__(self, other): + for k, v in _get_items(other): + self.add(k, value=v) + return self + + +def _get_items(mapping): + """Find item iterator for an object.""" + names = ('iterallitems', 'allitems', 'iteritems', 'items') + exist = (n for n in names if callable_attr(mapping, n)) + for a in exist: + return getattr(mapping, a)() + raise TypeError( + "Object {} has no compatible items interface.".format(mapping)) diff --git a/pipenv/vendor/packaging/LICENSE.APACHE b/pipenv/vendor/packaging/LICENSE.APACHE deleted file mode 100644 index 4947287f..00000000 --- a/pipenv/vendor/packaging/LICENSE.APACHE +++ /dev/null @@ -1,177 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/pipenv/vendor/packaging/LICENSE.BSD b/pipenv/vendor/packaging/LICENSE.BSD deleted file mode 100644 index 42ce7b75..00000000 --- a/pipenv/vendor/packaging/LICENSE.BSD +++ /dev/null @@ -1,23 +0,0 @@ -Copyright (c) Donald Stufft and individual contributors. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/pipenv/vendor/packaging/__about__.py b/pipenv/vendor/packaging/__about__.py index 7481c9e2..e70d692c 100644 --- a/pipenv/vendor/packaging/__about__.py +++ b/pipenv/vendor/packaging/__about__.py @@ -1,7 +1,6 @@ # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function __all__ = [ "__title__", @@ -18,10 +17,10 @@ __title__ = "packaging" __summary__ = "Core utilities for Python packages" __uri__ = "https://github.com/pypa/packaging" -__version__ = "19.0" +__version__ = "21.0" __author__ = "Donald Stufft and individual contributors" __email__ = "donald@stufft.io" -__license__ = "BSD or Apache License, Version 2.0" -__copyright__ = "Copyright 2014-2019 %s" % __author__ +__license__ = "BSD-2-Clause or Apache-2.0" +__copyright__ = "2014-2019 %s" % __author__ diff --git a/pipenv/vendor/packaging/__init__.py b/pipenv/vendor/packaging/__init__.py index a0cf67df..3c50c5dc 100644 --- a/pipenv/vendor/packaging/__init__.py +++ b/pipenv/vendor/packaging/__init__.py @@ -1,7 +1,6 @@ # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function from .__about__ import ( __author__, diff --git a/pipenv/vendor/packaging/_compat.py b/pipenv/vendor/packaging/_compat.py deleted file mode 100644 index 25da473c..00000000 --- a/pipenv/vendor/packaging/_compat.py +++ /dev/null @@ -1,31 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. -from __future__ import absolute_import, division, print_function - -import sys - - -PY2 = sys.version_info[0] == 2 -PY3 = sys.version_info[0] == 3 - -# flake8: noqa - -if PY3: - string_types = (str,) -else: - string_types = (basestring,) - - -def with_metaclass(meta, *bases): - """ - Create a base class with a metaclass. - """ - # This requires a bit of explanation: the basic idea is to make a dummy - # metaclass for one level of class instantiation that replaces itself with - # the actual metaclass. - class metaclass(meta): - def __new__(cls, name, this_bases, d): - return meta(name, bases, d) - - return type.__new__(metaclass, "temporary_class", (), {}) diff --git a/pipenv/vendor/packaging/_manylinux.py b/pipenv/vendor/packaging/_manylinux.py new file mode 100644 index 00000000..4c379aa6 --- /dev/null +++ b/pipenv/vendor/packaging/_manylinux.py @@ -0,0 +1,301 @@ +import collections +import functools +import os +import re +import struct +import sys +import warnings +from typing import IO, Dict, Iterator, NamedTuple, Optional, Tuple + + +# Python does not provide platform information at sufficient granularity to +# identify the architecture of the running executable in some cases, so we +# determine it dynamically by reading the information from the running +# process. This only applies on Linux, which uses the ELF format. +class _ELFFileHeader: + # https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header + class _InvalidELFFileHeader(ValueError): + """ + An invalid ELF file header was found. + """ + + ELF_MAGIC_NUMBER = 0x7F454C46 + ELFCLASS32 = 1 + ELFCLASS64 = 2 + ELFDATA2LSB = 1 + ELFDATA2MSB = 2 + EM_386 = 3 + EM_S390 = 22 + EM_ARM = 40 + EM_X86_64 = 62 + EF_ARM_ABIMASK = 0xFF000000 + EF_ARM_ABI_VER5 = 0x05000000 + EF_ARM_ABI_FLOAT_HARD = 0x00000400 + + def __init__(self, file: IO[bytes]) -> None: + def unpack(fmt: str) -> int: + try: + data = file.read(struct.calcsize(fmt)) + result: Tuple[int, ...] = struct.unpack(fmt, data) + except struct.error: + raise _ELFFileHeader._InvalidELFFileHeader() + return result[0] + + self.e_ident_magic = unpack(">I") + if self.e_ident_magic != self.ELF_MAGIC_NUMBER: + raise _ELFFileHeader._InvalidELFFileHeader() + self.e_ident_class = unpack("B") + if self.e_ident_class not in {self.ELFCLASS32, self.ELFCLASS64}: + raise _ELFFileHeader._InvalidELFFileHeader() + self.e_ident_data = unpack("B") + if self.e_ident_data not in {self.ELFDATA2LSB, self.ELFDATA2MSB}: + raise _ELFFileHeader._InvalidELFFileHeader() + self.e_ident_version = unpack("B") + self.e_ident_osabi = unpack("B") + self.e_ident_abiversion = unpack("B") + self.e_ident_pad = file.read(7) + format_h = "<H" if self.e_ident_data == self.ELFDATA2LSB else ">H" + format_i = "<I" if self.e_ident_data == self.ELFDATA2LSB else ">I" + format_q = "<Q" if self.e_ident_data == self.ELFDATA2LSB else ">Q" + format_p = format_i if self.e_ident_class == self.ELFCLASS32 else format_q + self.e_type = unpack(format_h) + self.e_machine = unpack(format_h) + self.e_version = unpack(format_i) + self.e_entry = unpack(format_p) + self.e_phoff = unpack(format_p) + self.e_shoff = unpack(format_p) + self.e_flags = unpack(format_i) + self.e_ehsize = unpack(format_h) + self.e_phentsize = unpack(format_h) + self.e_phnum = unpack(format_h) + self.e_shentsize = unpack(format_h) + self.e_shnum = unpack(format_h) + self.e_shstrndx = unpack(format_h) + + +def _get_elf_header() -> Optional[_ELFFileHeader]: + try: + with open(sys.executable, "rb") as f: + elf_header = _ELFFileHeader(f) + except (OSError, TypeError, _ELFFileHeader._InvalidELFFileHeader): + return None + return elf_header + + +def _is_linux_armhf() -> bool: + # hard-float ABI can be detected from the ELF header of the running + # process + # https://static.docs.arm.com/ihi0044/g/aaelf32.pdf + elf_header = _get_elf_header() + if elf_header is None: + return False + result = elf_header.e_ident_class == elf_header.ELFCLASS32 + result &= elf_header.e_ident_data == elf_header.ELFDATA2LSB + result &= elf_header.e_machine == elf_header.EM_ARM + result &= ( + elf_header.e_flags & elf_header.EF_ARM_ABIMASK + ) == elf_header.EF_ARM_ABI_VER5 + result &= ( + elf_header.e_flags & elf_header.EF_ARM_ABI_FLOAT_HARD + ) == elf_header.EF_ARM_ABI_FLOAT_HARD + return result + + +def _is_linux_i686() -> bool: + elf_header = _get_elf_header() + if elf_header is None: + return False + result = elf_header.e_ident_class == elf_header.ELFCLASS32 + result &= elf_header.e_ident_data == elf_header.ELFDATA2LSB + result &= elf_header.e_machine == elf_header.EM_386 + return result + + +def _have_compatible_abi(arch: str) -> bool: + if arch == "armv7l": + return _is_linux_armhf() + if arch == "i686": + return _is_linux_i686() + return arch in {"x86_64", "aarch64", "ppc64", "ppc64le", "s390x"} + + +# If glibc ever changes its major version, we need to know what the last +# minor version was, so we can build the complete list of all versions. +# For now, guess what the highest minor version might be, assume it will +# be 50 for testing. Once this actually happens, update the dictionary +# with the actual value. +_LAST_GLIBC_MINOR: Dict[int, int] = collections.defaultdict(lambda: 50) + + +class _GLibCVersion(NamedTuple): + major: int + minor: int + + +def _glibc_version_string_confstr() -> Optional[str]: + """ + Primary implementation of glibc_version_string using os.confstr. + """ + # os.confstr is quite a bit faster than ctypes.DLL. It's also less likely + # to be broken or missing. This strategy is used in the standard library + # platform module. + # https://github.com/python/cpython/blob/fcf1d003bf4f0100c/Lib/platform.py#L175-L183 + try: + # os.confstr("CS_GNU_LIBC_VERSION") returns a string like "glibc 2.17". + version_string = os.confstr("CS_GNU_LIBC_VERSION") + assert version_string is not None + _, version = version_string.split() + except (AssertionError, AttributeError, OSError, ValueError): + # os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)... + return None + return version + + +def _glibc_version_string_ctypes() -> Optional[str]: + """ + Fallback implementation of glibc_version_string using ctypes. + """ + try: + import ctypes + except ImportError: + return None + + # ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen + # manpage says, "If filename is NULL, then the returned handle is for the + # main program". This way we can let the linker do the work to figure out + # which libc our process is actually using. + # + # We must also handle the special case where the executable is not a + # dynamically linked executable. This can occur when using musl libc, + # for example. In this situation, dlopen() will error, leading to an + # OSError. Interestingly, at least in the case of musl, there is no + # errno set on the OSError. The single string argument used to construct + # OSError comes from libc itself and is therefore not portable to + # hard code here. In any case, failure to call dlopen() means we + # can proceed, so we bail on our attempt. + try: + process_namespace = ctypes.CDLL(None) + except OSError: + return None + + try: + gnu_get_libc_version = process_namespace.gnu_get_libc_version + except AttributeError: + # Symbol doesn't exist -> therefore, we are not linked to + # glibc. + return None + + # Call gnu_get_libc_version, which returns a string like "2.5" + gnu_get_libc_version.restype = ctypes.c_char_p + version_str: str = gnu_get_libc_version() + # py2 / py3 compatibility: + if not isinstance(version_str, str): + version_str = version_str.decode("ascii") + + return version_str + + +def _glibc_version_string() -> Optional[str]: + """Returns glibc version string, or None if not using glibc.""" + return _glibc_version_string_confstr() or _glibc_version_string_ctypes() + + +def _parse_glibc_version(version_str: str) -> Tuple[int, int]: + """Parse glibc version. + + We use a regexp instead of str.split because we want to discard any + random junk that might come after the minor version -- this might happen + in patched/forked versions of glibc (e.g. Linaro's version of glibc + uses version strings like "2.20-2014.11"). See gh-3588. + """ + m = re.match(r"(?P<major>[0-9]+)\.(?P<minor>[0-9]+)", version_str) + if not m: + warnings.warn( + "Expected glibc version with 2 components major.minor," + " got: %s" % version_str, + RuntimeWarning, + ) + return -1, -1 + return int(m.group("major")), int(m.group("minor")) + + +@functools.lru_cache() +def _get_glibc_version() -> Tuple[int, int]: + version_str = _glibc_version_string() + if version_str is None: + return (-1, -1) + return _parse_glibc_version(version_str) + + +# From PEP 513, PEP 600 +def _is_compatible(name: str, arch: str, version: _GLibCVersion) -> bool: + sys_glibc = _get_glibc_version() + if sys_glibc < version: + return False + # Check for presence of _manylinux module. + try: + import _manylinux # noqa + except ImportError: + return True + if hasattr(_manylinux, "manylinux_compatible"): + result = _manylinux.manylinux_compatible(version[0], version[1], arch) + if result is not None: + return bool(result) + return True + if version == _GLibCVersion(2, 5): + if hasattr(_manylinux, "manylinux1_compatible"): + return bool(_manylinux.manylinux1_compatible) + if version == _GLibCVersion(2, 12): + if hasattr(_manylinux, "manylinux2010_compatible"): + return bool(_manylinux.manylinux2010_compatible) + if version == _GLibCVersion(2, 17): + if hasattr(_manylinux, "manylinux2014_compatible"): + return bool(_manylinux.manylinux2014_compatible) + return True + + +_LEGACY_MANYLINUX_MAP = { + # CentOS 7 w/ glibc 2.17 (PEP 599) + (2, 17): "manylinux2014", + # CentOS 6 w/ glibc 2.12 (PEP 571) + (2, 12): "manylinux2010", + # CentOS 5 w/ glibc 2.5 (PEP 513) + (2, 5): "manylinux1", +} + + +def platform_tags(linux: str, arch: str) -> Iterator[str]: + if not _have_compatible_abi(arch): + return + # Oldest glibc to be supported regardless of architecture is (2, 17). + too_old_glibc2 = _GLibCVersion(2, 16) + if arch in {"x86_64", "i686"}: + # On x86/i686 also oldest glibc to be supported is (2, 5). + too_old_glibc2 = _GLibCVersion(2, 4) + current_glibc = _GLibCVersion(*_get_glibc_version()) + glibc_max_list = [current_glibc] + # We can assume compatibility across glibc major versions. + # https://sourceware.org/bugzilla/show_bug.cgi?id=24636 + # + # Build a list of maximum glibc versions so that we can + # output the canonical list of all glibc from current_glibc + # down to too_old_glibc2, including all intermediary versions. + for glibc_major in range(current_glibc.major - 1, 1, -1): + glibc_minor = _LAST_GLIBC_MINOR[glibc_major] + glibc_max_list.append(_GLibCVersion(glibc_major, glibc_minor)) + for glibc_max in glibc_max_list: + if glibc_max.major == too_old_glibc2.major: + min_minor = too_old_glibc2.minor + else: + # For other glibc major versions oldest supported is (x, 0). + min_minor = -1 + for glibc_minor in range(glibc_max.minor, min_minor, -1): + glibc_version = _GLibCVersion(glibc_max.major, glibc_minor) + tag = "manylinux_{}_{}".format(*glibc_version) + if _is_compatible(tag, arch, glibc_version): + yield linux.replace("linux", tag) + # Handle the legacy manylinux1, manylinux2010, manylinux2014 tags. + if glibc_version in _LEGACY_MANYLINUX_MAP: + legacy_tag = _LEGACY_MANYLINUX_MAP[glibc_version] + if _is_compatible(legacy_tag, arch, glibc_version): + yield linux.replace("linux", legacy_tag) diff --git a/pipenv/vendor/packaging/_musllinux.py b/pipenv/vendor/packaging/_musllinux.py new file mode 100644 index 00000000..85450faf --- /dev/null +++ b/pipenv/vendor/packaging/_musllinux.py @@ -0,0 +1,136 @@ +"""PEP 656 support. + +This module implements logic to detect if the currently running Python is +linked against musl, and what musl version is used. +""" + +import contextlib +import functools +import operator +import os +import re +import struct +import subprocess +import sys +from typing import IO, Iterator, NamedTuple, Optional, Tuple + + +def _read_unpacked(f: IO[bytes], fmt: str) -> Tuple[int, ...]: + return struct.unpack(fmt, f.read(struct.calcsize(fmt))) + + +def _parse_ld_musl_from_elf(f: IO[bytes]) -> Optional[str]: + """Detect musl libc location by parsing the Python executable. + + Based on: https://gist.github.com/lyssdod/f51579ae8d93c8657a5564aefc2ffbca + ELF header: https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html + """ + f.seek(0) + try: + ident = _read_unpacked(f, "16B") + except struct.error: + return None + if ident[:4] != tuple(b"\x7fELF"): # Invalid magic, not ELF. + return None + f.seek(struct.calcsize("HHI"), 1) # Skip file type, machine, and version. + + try: + # e_fmt: Format for program header. + # p_fmt: Format for section header. + # p_idx: Indexes to find p_type, p_offset, and p_filesz. + e_fmt, p_fmt, p_idx = { + 1: ("IIIIHHH", "IIIIIIII", (0, 1, 4)), # 32-bit. + 2: ("QQQIHHH", "IIQQQQQQ", (0, 2, 5)), # 64-bit. + }[ident[4]] + except KeyError: + return None + else: + p_get = operator.itemgetter(*p_idx) + + # Find the interpreter section and return its content. + try: + _, e_phoff, _, _, _, e_phentsize, e_phnum = _read_unpacked(f, e_fmt) + except struct.error: + return None + for i in range(e_phnum + 1): + f.seek(e_phoff + e_phentsize * i) + try: + p_type, p_offset, p_filesz = p_get(_read_unpacked(f, p_fmt)) + except struct.error: + return None + if p_type != 3: # Not PT_INTERP. + continue + f.seek(p_offset) + interpreter = os.fsdecode(f.read(p_filesz)).strip("\0") + if "musl" not in interpreter: + return None + return interpreter + return None + + +class _MuslVersion(NamedTuple): + major: int + minor: int + + +def _parse_musl_version(output: str) -> Optional[_MuslVersion]: + lines = [n for n in (n.strip() for n in output.splitlines()) if n] + if len(lines) < 2 or lines[0][:4] != "musl": + return None + m = re.match(r"Version (\d+)\.(\d+)", lines[1]) + if not m: + return None + return _MuslVersion(major=int(m.group(1)), minor=int(m.group(2))) + + +@functools.lru_cache() +def _get_musl_version(executable: str) -> Optional[_MuslVersion]: + """Detect currently-running musl runtime version. + + This is done by checking the specified executable's dynamic linking + information, and invoking the loader to parse its output for a version + string. If the loader is musl, the output would be something like:: + + musl libc (x86_64) + Version 1.2.2 + Dynamic Program Loader + """ + with contextlib.ExitStack() as stack: + try: + f = stack.enter_context(open(executable, "rb")) + except IOError: + return None + ld = _parse_ld_musl_from_elf(f) + if not ld: + return None + proc = subprocess.run([ld], stderr=subprocess.PIPE, universal_newlines=True) + return _parse_musl_version(proc.stderr) + + +def platform_tags(arch: str) -> Iterator[str]: + """Generate musllinux tags compatible to the current platform. + + :param arch: Should be the part of platform tag after the ``linux_`` + prefix, e.g. ``x86_64``. The ``linux_`` prefix is assumed as a + prerequisite for the current platform to be musllinux-compatible. + + :returns: An iterator of compatible musllinux tags. + """ + sys_musl = _get_musl_version(sys.executable) + if sys_musl is None: # Python not dynamically linked against musl. + return + for minor in range(sys_musl.minor, -1, -1): + yield f"musllinux_{sys_musl.major}_{minor}_{arch}" + + +if __name__ == "__main__": # pragma: no cover + import sysconfig + + plat = sysconfig.get_platform() + assert plat.startswith("linux-"), "not linux" + + print("plat:", plat) + print("musl:", _get_musl_version(sys.executable)) + print("tags:", end=" ") + for t in platform_tags(re.sub(r"[.-]", "_", plat.split("-", 1)[-1])): + print(t, end="\n ") diff --git a/pipenv/vendor/packaging/_structures.py b/pipenv/vendor/packaging/_structures.py index 68dcca63..95154975 100644 --- a/pipenv/vendor/packaging/_structures.py +++ b/pipenv/vendor/packaging/_structures.py @@ -1,68 +1,67 @@ # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function -class Infinity(object): - def __repr__(self): +class InfinityType: + def __repr__(self) -> str: return "Infinity" - def __hash__(self): + def __hash__(self) -> int: return hash(repr(self)) - def __lt__(self, other): + def __lt__(self, other: object) -> bool: return False - def __le__(self, other): + def __le__(self, other: object) -> bool: return False - def __eq__(self, other): + def __eq__(self, other: object) -> bool: return isinstance(other, self.__class__) - def __ne__(self, other): + def __ne__(self, other: object) -> bool: return not isinstance(other, self.__class__) - def __gt__(self, other): + def __gt__(self, other: object) -> bool: return True - def __ge__(self, other): + def __ge__(self, other: object) -> bool: return True - def __neg__(self): + def __neg__(self: object) -> "NegativeInfinityType": return NegativeInfinity -Infinity = Infinity() +Infinity = InfinityType() -class NegativeInfinity(object): - def __repr__(self): +class NegativeInfinityType: + def __repr__(self) -> str: return "-Infinity" - def __hash__(self): + def __hash__(self) -> int: return hash(repr(self)) - def __lt__(self, other): + def __lt__(self, other: object) -> bool: return True - def __le__(self, other): + def __le__(self, other: object) -> bool: return True - def __eq__(self, other): + def __eq__(self, other: object) -> bool: return isinstance(other, self.__class__) - def __ne__(self, other): + def __ne__(self, other: object) -> bool: return not isinstance(other, self.__class__) - def __gt__(self, other): + def __gt__(self, other: object) -> bool: return False - def __ge__(self, other): + def __ge__(self, other: object) -> bool: return False - def __neg__(self): + def __neg__(self: object) -> InfinityType: return Infinity -NegativeInfinity = NegativeInfinity() +NegativeInfinity = NegativeInfinityType() diff --git a/pipenv/vendor/packaging/markers.py b/pipenv/vendor/packaging/markers.py index eff5abbb..86582c84 100644 --- a/pipenv/vendor/packaging/markers.py +++ b/pipenv/vendor/packaging/markers.py @@ -1,20 +1,26 @@ # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import operator import os import platform import sys +from typing import Any, Callable, Dict, List, Optional, Tuple, Union -from pyparsing import ParseException, ParseResults, stringStart, stringEnd -from pyparsing import ZeroOrMore, Group, Forward, QuotedString -from pyparsing import Literal as L # noqa - -from ._compat import string_types -from .specifiers import Specifier, InvalidSpecifier +from pipenv.vendor.pyparsing import ( # noqa: N817 + Forward, + Group, + Literal as L, + ParseException, + ParseResults, + QuotedString, + ZeroOrMore, + stringEnd, + stringStart, +) +from .specifiers import InvalidSpecifier, Specifier __all__ = [ "InvalidMarker", @@ -24,6 +30,8 @@ __all__ = [ "default_environment", ] +Operator = Callable[[str, str], bool] + class InvalidMarker(ValueError): """ @@ -44,32 +52,32 @@ class UndefinedEnvironmentName(ValueError): """ -class Node(object): - def __init__(self, value): +class Node: + def __init__(self, value: Any) -> None: self.value = value - def __str__(self): + def __str__(self) -> str: return str(self.value) - def __repr__(self): - return "<{0}({1!r})>".format(self.__class__.__name__, str(self)) + def __repr__(self) -> str: + return f"<{self.__class__.__name__}('{self}')>" - def serialize(self): + def serialize(self) -> str: raise NotImplementedError class Variable(Node): - def serialize(self): + def serialize(self) -> str: return str(self) class Value(Node): - def serialize(self): - return '"{0}"'.format(self) + def serialize(self) -> str: + return f'"{self}"' class Op(Node): - def serialize(self): + def serialize(self) -> str: return str(self) @@ -85,13 +93,13 @@ VARIABLE = ( | L("python_version") | L("sys_platform") | L("os_name") - | L("os.name") + | L("os.name") # PEP-345 | L("sys.platform") # PEP-345 | L("platform.version") # PEP-345 | L("platform.machine") # PEP-345 | L("platform.python_implementation") # PEP-345 - | L("python_implementation") # PEP-345 - | L("extra") # undocumented setuptools legacy + | L("python_implementation") # undocumented setuptools legacy + | L("extra") # PEP-508 ) ALIASES = { "os.name": "os_name", @@ -130,15 +138,18 @@ MARKER_EXPR << MARKER_ATOM + ZeroOrMore(BOOLOP + MARKER_EXPR) MARKER = stringStart + MARKER_EXPR + stringEnd -def _coerce_parse_result(results): +def _coerce_parse_result(results: Union[ParseResults, List[Any]]) -> List[Any]: if isinstance(results, ParseResults): return [_coerce_parse_result(i) for i in results] else: return results -def _format_marker(marker, first=True): - assert isinstance(marker, (list, tuple, string_types)) +def _format_marker( + marker: Union[List[str], Tuple[Node, ...], str], first: Optional[bool] = True +) -> str: + + assert isinstance(marker, (list, tuple, str)) # Sometimes we have a structure like [[...]] which is a single item list # where the single item is itself it's own list. In that case we want skip @@ -163,7 +174,7 @@ def _format_marker(marker, first=True): return marker -_operators = { +_operators: Dict[str, Operator] = { "in": lambda lhs, rhs: lhs in rhs, "not in": lambda lhs, rhs: lhs not in rhs, "<": operator.lt, @@ -175,7 +186,7 @@ _operators = { } -def _eval_op(lhs, op, rhs): +def _eval_op(lhs: str, op: Op, rhs: str) -> bool: try: spec = Specifier("".join([op.serialize(), rhs])) except InvalidSpecifier: @@ -183,34 +194,36 @@ def _eval_op(lhs, op, rhs): else: return spec.contains(lhs) - oper = _operators.get(op.serialize()) + oper: Optional[Operator] = _operators.get(op.serialize()) if oper is None: - raise UndefinedComparison( - "Undefined {0!r} on {1!r} and {2!r}.".format(op, lhs, rhs) - ) + raise UndefinedComparison(f"Undefined {op!r} on {lhs!r} and {rhs!r}.") return oper(lhs, rhs) -_undefined = object() +class Undefined: + pass -def _get_env(environment, name): - value = environment.get(name, _undefined) +_undefined = Undefined() - if value is _undefined: + +def _get_env(environment: Dict[str, str], name: str) -> str: + value: Union[str, Undefined] = environment.get(name, _undefined) + + if isinstance(value, Undefined): raise UndefinedEnvironmentName( - "{0!r} does not exist in evaluation environment.".format(name) + f"{name!r} does not exist in evaluation environment." ) return value -def _evaluate_markers(markers, environment): - groups = [[]] +def _evaluate_markers(markers: List[Any], environment: Dict[str, str]) -> bool: + groups: List[List[bool]] = [[]] for marker in markers: - assert isinstance(marker, (list, tuple, string_types)) + assert isinstance(marker, (list, tuple, str)) if isinstance(marker, list): groups[-1].append(_evaluate_markers(marker, environment)) @@ -233,7 +246,7 @@ def _evaluate_markers(markers, environment): return any(all(item) for item in groups) -def format_full_version(info): +def format_full_version(info: "sys._version_info") -> str: version = "{0.major}.{0.minor}.{0.micro}".format(info) kind = info.releaselevel if kind != "final": @@ -241,14 +254,9 @@ def format_full_version(info): return version -def default_environment(): - if hasattr(sys, "implementation"): - iver = format_full_version(sys.implementation.version) - implementation_name = sys.implementation.name - else: - iver = "0" - implementation_name = "" - +def default_environment() -> Dict[str, str]: + iver = format_full_version(sys.implementation.version) + implementation_name = sys.implementation.name return { "implementation_name": implementation_name, "implementation_version": iver, @@ -259,28 +267,28 @@ def default_environment(): "platform_version": platform.version(), "python_full_version": platform.python_version(), "platform_python_implementation": platform.python_implementation(), - "python_version": platform.python_version()[:3], + "python_version": ".".join(platform.python_version_tuple()[:2]), "sys_platform": sys.platform, } -class Marker(object): - def __init__(self, marker): +class Marker: + def __init__(self, marker: str) -> None: try: self._markers = _coerce_parse_result(MARKER.parseString(marker)) except ParseException as e: - err_str = "Invalid marker: {0!r}, parse error at {1!r}".format( - marker, marker[e.loc : e.loc + 8] + raise InvalidMarker( + f"Invalid marker: {marker!r}, parse error at " + f"{marker[e.loc : e.loc + 8]!r}" ) - raise InvalidMarker(err_str) - def __str__(self): + def __str__(self) -> str: return _format_marker(self._markers) - def __repr__(self): - return "<Marker({0!r})>".format(str(self)) + def __repr__(self) -> str: + return f"<Marker('{self}')>" - def evaluate(self, environment=None): + def evaluate(self, environment: Optional[Dict[str, str]] = None) -> bool: """Evaluate a marker. Return the boolean from evaluating the given marker against the diff --git a/pipenv/vendor/packaging/py.typed b/pipenv/vendor/packaging/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/pipenv/vendor/packaging/requirements.py b/pipenv/vendor/packaging/requirements.py index 4d9688b9..284c2e70 100644 --- a/pipenv/vendor/packaging/requirements.py +++ b/pipenv/vendor/packaging/requirements.py @@ -1,15 +1,24 @@ # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function -import string import re +import string +import urllib.parse +from typing import List, Optional as TOptional, Set -from pyparsing import stringStart, stringEnd, originalTextFor, ParseException -from pyparsing import ZeroOrMore, Word, Optional, Regex, Combine -from pyparsing import Literal as L # noqa -from six.moves.urllib import parse as urlparse +from pipenv.vendor.pyparsing import ( # noqa + Combine, + Literal as L, + Optional, + ParseException, + Regex, + Word, + ZeroOrMore, + originalTextFor, + stringEnd, + stringStart, +) from .markers import MARKER_EXPR, Marker from .specifiers import LegacySpecifier, Specifier, SpecifierSet @@ -51,7 +60,7 @@ VERSION_ONE = VERSION_PEP440 ^ VERSION_LEGACY VERSION_MANY = Combine( VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE), joinString=",", adjacent=False )("_raw_spec") -_VERSION_SPEC = Optional(((LPAREN + VERSION_MANY + RPAREN) | VERSION_MANY)) +_VERSION_SPEC = Optional((LPAREN + VERSION_MANY + RPAREN) | VERSION_MANY) _VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or "") VERSION_SPEC = originalTextFor(_VERSION_SPEC)("specifier") @@ -75,7 +84,7 @@ REQUIREMENT = stringStart + NAMED_REQUIREMENT + stringEnd REQUIREMENT.parseString("x[]") -class Requirement(object): +class Requirement: """Parse a requirement. Parse a given requirement string into its parts, such as name, specifier, @@ -88,51 +97,50 @@ class Requirement(object): # the thing as well as the version? What about the markers? # TODO: Can we normalize the name and extra name? - def __init__(self, requirement_string): + def __init__(self, requirement_string: str) -> None: try: req = REQUIREMENT.parseString(requirement_string) except ParseException as e: raise InvalidRequirement( - 'Parse error at "{0!r}": {1}'.format( - requirement_string[e.loc : e.loc + 8], e.msg - ) + f'Parse error at "{ requirement_string[e.loc : e.loc + 8]!r}": {e.msg}' ) - self.name = req.name + self.name: str = req.name if req.url: - parsed_url = urlparse.urlparse(req.url) + parsed_url = urllib.parse.urlparse(req.url) if parsed_url.scheme == "file": - if urlparse.urlunparse(parsed_url) != req.url: + if urllib.parse.urlunparse(parsed_url) != req.url: raise InvalidRequirement("Invalid URL given") elif not (parsed_url.scheme and parsed_url.netloc) or ( not parsed_url.scheme and not parsed_url.netloc ): - raise InvalidRequirement("Invalid URL: {0}".format(req.url)) - self.url = req.url + raise InvalidRequirement(f"Invalid URL: {req.url}") + self.url: TOptional[str] = req.url else: self.url = None - self.extras = set(req.extras.asList() if req.extras else []) - self.specifier = SpecifierSet(req.specifier) - self.marker = req.marker if req.marker else None + self.extras: Set[str] = set(req.extras.asList() if req.extras else []) + self.specifier: SpecifierSet = SpecifierSet(req.specifier) + self.marker: TOptional[Marker] = req.marker if req.marker else None - def __str__(self): - parts = [self.name] + def __str__(self) -> str: + parts: List[str] = [self.name] if self.extras: - parts.append("[{0}]".format(",".join(sorted(self.extras)))) + formatted_extras = ",".join(sorted(self.extras)) + parts.append(f"[{formatted_extras}]") if self.specifier: parts.append(str(self.specifier)) if self.url: - parts.append("@ {0}".format(self.url)) + parts.append(f"@ {self.url}") if self.marker: parts.append(" ") if self.marker: - parts.append("; {0}".format(self.marker)) + parts.append(f"; {self.marker}") return "".join(parts) - def __repr__(self): - return "<Requirement({0!r})>".format(str(self)) + def __repr__(self) -> str: + return f"<Requirement('{self}')>" diff --git a/pipenv/vendor/packaging/specifiers.py b/pipenv/vendor/packaging/specifiers.py index 743576a0..ce66bd4a 100644 --- a/pipenv/vendor/packaging/specifiers.py +++ b/pipenv/vendor/packaging/specifiers.py @@ -1,15 +1,33 @@ # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import abc import functools import itertools import re +import warnings +from typing import ( + Callable, + Dict, + Iterable, + Iterator, + List, + Optional, + Pattern, + Set, + Tuple, + TypeVar, + Union, +) -from ._compat import string_types, with_metaclass -from .version import Version, LegacyVersion, parse +from .utils import canonicalize_version +from .version import LegacyVersion, Version, parse + +ParsedVersion = Union[Version, LegacyVersion] +UnparsedVersion = Union[Version, LegacyVersion, str] +VersionTypeVar = TypeVar("VersionTypeVar", bound=UnparsedVersion) +CallableOperator = Callable[[ParsedVersion, str], bool] class InvalidSpecifier(ValueError): @@ -18,56 +36,58 @@ class InvalidSpecifier(ValueError): """ -class BaseSpecifier(with_metaclass(abc.ABCMeta, object)): +class BaseSpecifier(metaclass=abc.ABCMeta): @abc.abstractmethod - def __str__(self): + def __str__(self) -> str: """ Returns the str representation of this Specifier like object. This should be representative of the Specifier itself. """ @abc.abstractmethod - def __hash__(self): + def __hash__(self) -> int: """ Returns a hash value for this Specifier like object. """ @abc.abstractmethod - def __eq__(self, other): + def __eq__(self, other: object) -> bool: """ Returns a boolean representing whether or not the two Specifier like objects are equal. """ @abc.abstractmethod - def __ne__(self, other): + def __ne__(self, other: object) -> bool: """ Returns a boolean representing whether or not the two Specifier like objects are not equal. """ @abc.abstractproperty - def prereleases(self): + def prereleases(self) -> Optional[bool]: """ Returns whether or not pre-releases as a whole are allowed by this specifier. """ @prereleases.setter - def prereleases(self, value): + def prereleases(self, value: bool) -> None: """ Sets whether or not pre-releases as a whole are allowed by this specifier. """ @abc.abstractmethod - def contains(self, item, prereleases=None): + def contains(self, item: str, prereleases: Optional[bool] = None) -> bool: """ Determines if the given item is contained within this specifier. """ @abc.abstractmethod - def filter(self, iterable, prereleases=None): + def filter( + self, iterable: Iterable[VersionTypeVar], prereleases: Optional[bool] = None + ) -> Iterable[VersionTypeVar]: """ Takes an iterable of items and filters them so that only items which are contained within this specifier are allowed in it. @@ -76,48 +96,56 @@ class BaseSpecifier(with_metaclass(abc.ABCMeta, object)): class _IndividualSpecifier(BaseSpecifier): - _operators = {} + _operators: Dict[str, str] = {} + _regex: Pattern[str] - def __init__(self, spec="", prereleases=None): + def __init__(self, spec: str = "", prereleases: Optional[bool] = None) -> None: match = self._regex.search(spec) if not match: - raise InvalidSpecifier("Invalid specifier: '{0}'".format(spec)) + raise InvalidSpecifier(f"Invalid specifier: '{spec}'") - self._spec = (match.group("operator").strip(), match.group("version").strip()) + self._spec: Tuple[str, str] = ( + match.group("operator").strip(), + match.group("version").strip(), + ) # Store whether or not this Specifier should accept prereleases self._prereleases = prereleases - def __repr__(self): + def __repr__(self) -> str: pre = ( - ", prereleases={0!r}".format(self.prereleases) + f", prereleases={self.prereleases!r}" if self._prereleases is not None else "" ) - return "<{0}({1!r}{2})>".format(self.__class__.__name__, str(self), pre) + return "<{}({!r}{})>".format(self.__class__.__name__, str(self), pre) - def __str__(self): - return "{0}{1}".format(*self._spec) + def __str__(self) -> str: + return "{}{}".format(*self._spec) - def __hash__(self): - return hash(self._spec) + @property + def _canonical_spec(self) -> Tuple[str, str]: + return self._spec[0], canonicalize_version(self._spec[1]) - def __eq__(self, other): - if isinstance(other, string_types): + def __hash__(self) -> int: + return hash(self._canonical_spec) + + def __eq__(self, other: object) -> bool: + if isinstance(other, str): try: - other = self.__class__(other) + other = self.__class__(str(other)) except InvalidSpecifier: return NotImplemented elif not isinstance(other, self.__class__): return NotImplemented - return self._spec == other._spec + return self._canonical_spec == other._canonical_spec - def __ne__(self, other): - if isinstance(other, string_types): + def __ne__(self, other: object) -> bool: + if isinstance(other, str): try: - other = self.__class__(other) + other = self.__class__(str(other)) except InvalidSpecifier: return NotImplemented elif not isinstance(other, self.__class__): @@ -125,53 +153,63 @@ class _IndividualSpecifier(BaseSpecifier): return self._spec != other._spec - def _get_operator(self, op): - return getattr(self, "_compare_{0}".format(self._operators[op])) + def _get_operator(self, op: str) -> CallableOperator: + operator_callable: CallableOperator = getattr( + self, f"_compare_{self._operators[op]}" + ) + return operator_callable - def _coerce_version(self, version): + def _coerce_version(self, version: UnparsedVersion) -> ParsedVersion: if not isinstance(version, (LegacyVersion, Version)): version = parse(version) return version @property - def operator(self): + def operator(self) -> str: return self._spec[0] @property - def version(self): + def version(self) -> str: return self._spec[1] @property - def prereleases(self): + def prereleases(self) -> Optional[bool]: return self._prereleases @prereleases.setter - def prereleases(self, value): + def prereleases(self, value: bool) -> None: self._prereleases = value - def __contains__(self, item): + def __contains__(self, item: str) -> bool: return self.contains(item) - def contains(self, item, prereleases=None): + def contains( + self, item: UnparsedVersion, prereleases: Optional[bool] = None + ) -> bool: + # Determine if prereleases are to be allowed or not. if prereleases is None: prereleases = self.prereleases # Normalize item to a Version or LegacyVersion, this allows us to have # a shortcut for ``"2.0" in Specifier(">=2") - item = self._coerce_version(item) + normalized_item = self._coerce_version(item) # Determine if we should be supporting prereleases in this specifier # or not, if we do not support prereleases than we can short circuit # logic if this version is a prereleases. - if item.is_prerelease and not prereleases: + if normalized_item.is_prerelease and not prereleases: return False # Actually do the comparison to determine if this item is contained # within this Specifier or not. - return self._get_operator(self.operator)(item, self.version) + operator_callable: CallableOperator = self._get_operator(self.operator) + return operator_callable(normalized_item, self.version) + + def filter( + self, iterable: Iterable[VersionTypeVar], prereleases: Optional[bool] = None + ) -> Iterable[VersionTypeVar]: - def filter(self, iterable, prereleases=None): yielded = False found_prereleases = [] @@ -184,7 +222,7 @@ class _IndividualSpecifier(BaseSpecifier): if self.contains(parsed_version, **kw): # If our version is a prerelease, and we were not set to allow - # prereleases, then we'll store it for later incase nothing + # prereleases, then we'll store it for later in case nothing # else matches this specifier. if parsed_version.is_prerelease and not ( prereleases or self.prereleases @@ -229,33 +267,46 @@ class LegacySpecifier(_IndividualSpecifier): ">": "greater_than", } - def _coerce_version(self, version): + def __init__(self, spec: str = "", prereleases: Optional[bool] = None) -> None: + super().__init__(spec, prereleases) + + warnings.warn( + "Creating a LegacyVersion has been deprecated and will be " + "removed in the next major release", + DeprecationWarning, + ) + + def _coerce_version(self, version: UnparsedVersion) -> LegacyVersion: if not isinstance(version, LegacyVersion): version = LegacyVersion(str(version)) return version - def _compare_equal(self, prospective, spec): + def _compare_equal(self, prospective: LegacyVersion, spec: str) -> bool: return prospective == self._coerce_version(spec) - def _compare_not_equal(self, prospective, spec): + def _compare_not_equal(self, prospective: LegacyVersion, spec: str) -> bool: return prospective != self._coerce_version(spec) - def _compare_less_than_equal(self, prospective, spec): + def _compare_less_than_equal(self, prospective: LegacyVersion, spec: str) -> bool: return prospective <= self._coerce_version(spec) - def _compare_greater_than_equal(self, prospective, spec): + def _compare_greater_than_equal( + self, prospective: LegacyVersion, spec: str + ) -> bool: return prospective >= self._coerce_version(spec) - def _compare_less_than(self, prospective, spec): + def _compare_less_than(self, prospective: LegacyVersion, spec: str) -> bool: return prospective < self._coerce_version(spec) - def _compare_greater_than(self, prospective, spec): + def _compare_greater_than(self, prospective: LegacyVersion, spec: str) -> bool: return prospective > self._coerce_version(spec) -def _require_version_compare(fn): +def _require_version_compare( + fn: Callable[["Specifier", ParsedVersion, str], bool] +) -> Callable[["Specifier", ParsedVersion, str], bool]: @functools.wraps(fn) - def wrapped(self, prospective, spec): + def wrapped(self: "Specifier", prospective: ParsedVersion, spec: str) -> bool: if not isinstance(prospective, Version): return False return fn(self, prospective, spec) @@ -372,7 +423,8 @@ class Specifier(_IndividualSpecifier): } @_require_version_compare - def _compare_compatible(self, prospective, spec): + def _compare_compatible(self, prospective: ParsedVersion, spec: str) -> bool: + # Compatible releases have an equivalent combination of >= and ==. That # is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to # implement this in terms of the other specifiers instead of @@ -380,15 +432,9 @@ class Specifier(_IndividualSpecifier): # the other specifiers. # We want everything but the last item in the version, but we want to - # ignore post and dev releases and we want to treat the pre-release as - # it's own separate segment. + # ignore suffix segments. prefix = ".".join( - list( - itertools.takewhile( - lambda x: (not x.startswith("post") and not x.startswith("dev")), - _version_split(spec), - ) - )[:-1] + list(itertools.takewhile(_is_not_suffix, _version_split(spec)))[:-1] ) # Add the prefix notation to the end of our string @@ -399,57 +445,73 @@ class Specifier(_IndividualSpecifier): ) @_require_version_compare - def _compare_equal(self, prospective, spec): + def _compare_equal(self, prospective: ParsedVersion, spec: str) -> bool: + # We need special logic to handle prefix matching if spec.endswith(".*"): # In the case of prefix matching we want to ignore local segment. prospective = Version(prospective.public) # Split the spec out by dots, and pretend that there is an implicit # dot in between a release segment and a pre-release segment. - spec = _version_split(spec[:-2]) # Remove the trailing .* + split_spec = _version_split(spec[:-2]) # Remove the trailing .* # Split the prospective version out by dots, and pretend that there # is an implicit dot in between a release segment and a pre-release # segment. - prospective = _version_split(str(prospective)) + split_prospective = _version_split(str(prospective)) # Shorten the prospective version to be the same length as the spec # so that we can determine if the specifier is a prefix of the # prospective version or not. - prospective = prospective[: len(spec)] + shortened_prospective = split_prospective[: len(split_spec)] # Pad out our two sides with zeros so that they both equal the same # length. - spec, prospective = _pad_version(spec, prospective) + padded_spec, padded_prospective = _pad_version( + split_spec, shortened_prospective + ) + + return padded_prospective == padded_spec else: # Convert our spec string into a Version - spec = Version(spec) + spec_version = Version(spec) # If the specifier does not have a local segment, then we want to # act as if the prospective version also does not have a local # segment. - if not spec.local: + if not spec_version.local: prospective = Version(prospective.public) - return prospective == spec + return prospective == spec_version @_require_version_compare - def _compare_not_equal(self, prospective, spec): + def _compare_not_equal(self, prospective: ParsedVersion, spec: str) -> bool: return not self._compare_equal(prospective, spec) @_require_version_compare - def _compare_less_than_equal(self, prospective, spec): - return prospective <= Version(spec) + def _compare_less_than_equal(self, prospective: ParsedVersion, spec: str) -> bool: + + # NB: Local version identifiers are NOT permitted in the version + # specifier, so local version labels can be universally removed from + # the prospective version. + return Version(prospective.public) <= Version(spec) @_require_version_compare - def _compare_greater_than_equal(self, prospective, spec): - return prospective >= Version(spec) + def _compare_greater_than_equal( + self, prospective: ParsedVersion, spec: str + ) -> bool: + + # NB: Local version identifiers are NOT permitted in the version + # specifier, so local version labels can be universally removed from + # the prospective version. + return Version(prospective.public) >= Version(spec) @_require_version_compare - def _compare_less_than(self, prospective, spec): + def _compare_less_than(self, prospective: ParsedVersion, spec_str: str) -> bool: + # Convert our spec to a Version instance, since we'll want to work with # it as a version. - spec = Version(spec) + spec = Version(spec_str) # Check to see if the prospective version is less than the spec # version. If it's not we can short circuit and just return False now @@ -471,10 +533,11 @@ class Specifier(_IndividualSpecifier): return True @_require_version_compare - def _compare_greater_than(self, prospective, spec): + def _compare_greater_than(self, prospective: ParsedVersion, spec_str: str) -> bool: + # Convert our spec to a Version instance, since we'll want to work with # it as a version. - spec = Version(spec) + spec = Version(spec_str) # Check to see if the prospective version is greater than the spec # version. If it's not we can short circuit and just return False now @@ -501,11 +564,12 @@ class Specifier(_IndividualSpecifier): # same version in the spec. return True - def _compare_arbitrary(self, prospective, spec): + def _compare_arbitrary(self, prospective: Version, spec: str) -> bool: return str(prospective).lower() == str(spec).lower() @property - def prereleases(self): + def prereleases(self) -> bool: + # If there is an explicit prereleases set for this, then we'll just # blindly use that. if self._prereleases is not None: @@ -529,15 +593,15 @@ class Specifier(_IndividualSpecifier): return False @prereleases.setter - def prereleases(self, value): + def prereleases(self, value: bool) -> None: self._prereleases = value _prefix_regex = re.compile(r"^([0-9]+)((?:a|b|c|rc)[0-9]+)$") -def _version_split(version): - result = [] +def _version_split(version: str) -> List[str]: + result: List[str] = [] for item in version.split("."): match = _prefix_regex.search(item) if match: @@ -547,7 +611,13 @@ def _version_split(version): return result -def _pad_version(left, right): +def _is_not_suffix(segment: str) -> bool: + return not any( + segment.startswith(prefix) for prefix in ("dev", "a", "b", "rc", "post") + ) + + +def _pad_version(left: List[str], right: List[str]) -> Tuple[List[str], List[str]]: left_split, right_split = [], [] # Get the release segment of our versions @@ -566,15 +636,18 @@ def _pad_version(left, right): class SpecifierSet(BaseSpecifier): - def __init__(self, specifiers="", prereleases=None): - # Split on , to break each indidivual specifier into it's own item, and + def __init__( + self, specifiers: str = "", prereleases: Optional[bool] = None + ) -> None: + + # Split on , to break each individual specifier into it's own item, and # strip each item to remove leading/trailing whitespace. - specifiers = [s.strip() for s in specifiers.split(",") if s.strip()] + split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()] # Parsed each individual specifier, attempting first to make it a # Specifier and falling back to a LegacySpecifier. - parsed = set() - for specifier in specifiers: + parsed: Set[_IndividualSpecifier] = set() + for specifier in split_specifiers: try: parsed.add(Specifier(specifier)) except InvalidSpecifier: @@ -587,23 +660,23 @@ class SpecifierSet(BaseSpecifier): # we accept prereleases or not. self._prereleases = prereleases - def __repr__(self): + def __repr__(self) -> str: pre = ( - ", prereleases={0!r}".format(self.prereleases) + f", prereleases={self.prereleases!r}" if self._prereleases is not None else "" ) - return "<SpecifierSet({0!r}{1})>".format(str(self), pre) + return "<SpecifierSet({!r}{})>".format(str(self), pre) - def __str__(self): + def __str__(self) -> str: return ",".join(sorted(str(s) for s in self._specs)) - def __hash__(self): + def __hash__(self) -> int: return hash(self._specs) - def __and__(self, other): - if isinstance(other, string_types): + def __and__(self, other: Union["SpecifierSet", str]) -> "SpecifierSet": + if isinstance(other, str): other = SpecifierSet(other) elif not isinstance(other, SpecifierSet): return NotImplemented @@ -625,34 +698,31 @@ class SpecifierSet(BaseSpecifier): return specifier - def __eq__(self, other): - if isinstance(other, string_types): - other = SpecifierSet(other) - elif isinstance(other, _IndividualSpecifier): + def __eq__(self, other: object) -> bool: + if isinstance(other, (str, _IndividualSpecifier)): other = SpecifierSet(str(other)) elif not isinstance(other, SpecifierSet): return NotImplemented return self._specs == other._specs - def __ne__(self, other): - if isinstance(other, string_types): - other = SpecifierSet(other) - elif isinstance(other, _IndividualSpecifier): + def __ne__(self, other: object) -> bool: + if isinstance(other, (str, _IndividualSpecifier)): other = SpecifierSet(str(other)) elif not isinstance(other, SpecifierSet): return NotImplemented return self._specs != other._specs - def __len__(self): + def __len__(self) -> int: return len(self._specs) - def __iter__(self): + def __iter__(self) -> Iterator[_IndividualSpecifier]: return iter(self._specs) @property - def prereleases(self): + def prereleases(self) -> Optional[bool]: + # If we have been given an explicit prerelease modifier, then we'll # pass that through here. if self._prereleases is not None: @@ -669,13 +739,16 @@ class SpecifierSet(BaseSpecifier): return any(s.prereleases for s in self._specs) @prereleases.setter - def prereleases(self, value): + def prereleases(self, value: bool) -> None: self._prereleases = value - def __contains__(self, item): + def __contains__(self, item: UnparsedVersion) -> bool: return self.contains(item) - def contains(self, item, prereleases=None): + def contains( + self, item: UnparsedVersion, prereleases: Optional[bool] = None + ) -> bool: + # Ensure that our item is a Version or LegacyVersion instance. if not isinstance(item, (LegacyVersion, Version)): item = parse(item) @@ -701,7 +774,10 @@ class SpecifierSet(BaseSpecifier): # will always return True, this is an explicit design decision. return all(s.contains(item, prereleases=prereleases) for s in self._specs) - def filter(self, iterable, prereleases=None): + def filter( + self, iterable: Iterable[VersionTypeVar], prereleases: Optional[bool] = None + ) -> Iterable[VersionTypeVar]: + # Determine if we're forcing a prerelease or not, if we're not forcing # one for this particular filter call, then we'll use whatever the # SpecifierSet thinks for whether or not we should support prereleases. @@ -719,8 +795,11 @@ class SpecifierSet(BaseSpecifier): # which will filter out any pre-releases, unless there are no final # releases, and which will filter out LegacyVersion in general. else: - filtered = [] - found_prereleases = [] + filtered: List[VersionTypeVar] = [] + found_prereleases: List[VersionTypeVar] = [] + + item: UnparsedVersion + parsed_version: Union[Version, LegacyVersion] for item in iterable: # Ensure that we some kind of Version class for this item. diff --git a/pipenv/vendor/packaging/tags.py b/pipenv/vendor/packaging/tags.py new file mode 100644 index 00000000..82a47cda --- /dev/null +++ b/pipenv/vendor/packaging/tags.py @@ -0,0 +1,484 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import logging +import platform +import sys +import sysconfig +from importlib.machinery import EXTENSION_SUFFIXES +from typing import ( + Dict, + FrozenSet, + Iterable, + Iterator, + List, + Optional, + Sequence, + Tuple, + Union, + cast, +) + +from . import _manylinux, _musllinux + +logger = logging.getLogger(__name__) + +PythonVersion = Sequence[int] +MacVersion = Tuple[int, int] + +INTERPRETER_SHORT_NAMES: Dict[str, str] = { + "python": "py", # Generic. + "cpython": "cp", + "pypy": "pp", + "ironpython": "ip", + "jython": "jy", +} + + +_32_BIT_INTERPRETER = sys.maxsize <= 2 ** 32 + + +class Tag: + """ + A representation of the tag triple for a wheel. + + Instances are considered immutable and thus are hashable. Equality checking + is also supported. + """ + + __slots__ = ["_interpreter", "_abi", "_platform", "_hash"] + + def __init__(self, interpreter: str, abi: str, platform: str) -> None: + self._interpreter = interpreter.lower() + self._abi = abi.lower() + self._platform = platform.lower() + # The __hash__ of every single element in a Set[Tag] will be evaluated each time + # that a set calls its `.disjoint()` method, which may be called hundreds of + # times when scanning a page of links for packages with tags matching that + # Set[Tag]. Pre-computing the value here produces significant speedups for + # downstream consumers. + self._hash = hash((self._interpreter, self._abi, self._platform)) + + @property + def interpreter(self) -> str: + return self._interpreter + + @property + def abi(self) -> str: + return self._abi + + @property + def platform(self) -> str: + return self._platform + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Tag): + return NotImplemented + + return ( + (self._hash == other._hash) # Short-circuit ASAP for perf reasons. + and (self._platform == other._platform) + and (self._abi == other._abi) + and (self._interpreter == other._interpreter) + ) + + def __hash__(self) -> int: + return self._hash + + def __str__(self) -> str: + return f"{self._interpreter}-{self._abi}-{self._platform}" + + def __repr__(self) -> str: + return "<{self} @ {self_id}>".format(self=self, self_id=id(self)) + + +def parse_tag(tag: str) -> FrozenSet[Tag]: + """ + Parses the provided tag (e.g. `py3-none-any`) into a frozenset of Tag instances. + + Returning a set is required due to the possibility that the tag is a + compressed tag set. + """ + tags = set() + interpreters, abis, platforms = tag.split("-") + for interpreter in interpreters.split("."): + for abi in abis.split("."): + for platform_ in platforms.split("."): + tags.add(Tag(interpreter, abi, platform_)) + return frozenset(tags) + + +def _get_config_var(name: str, warn: bool = False) -> Union[int, str, None]: + value = sysconfig.get_config_var(name) + if value is None and warn: + logger.debug( + "Config variable '%s' is unset, Python ABI tag may be incorrect", name + ) + return value + + +def _normalize_string(string: str) -> str: + return string.replace(".", "_").replace("-", "_") + + +def _abi3_applies(python_version: PythonVersion) -> bool: + """ + Determine if the Python version supports abi3. + + PEP 384 was first implemented in Python 3.2. + """ + return len(python_version) > 1 and tuple(python_version) >= (3, 2) + + +def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> List[str]: + py_version = tuple(py_version) # To allow for version comparison. + abis = [] + version = _version_nodot(py_version[:2]) + debug = pymalloc = ucs4 = "" + with_debug = _get_config_var("Py_DEBUG", warn) + has_refcount = hasattr(sys, "gettotalrefcount") + # Windows doesn't set Py_DEBUG, so checking for support of debug-compiled + # extension modules is the best option. + # https://github.com/pypa/pip/issues/3383#issuecomment-173267692 + has_ext = "_d.pyd" in EXTENSION_SUFFIXES + if with_debug or (with_debug is None and (has_refcount or has_ext)): + debug = "d" + if py_version < (3, 8): + with_pymalloc = _get_config_var("WITH_PYMALLOC", warn) + if with_pymalloc or with_pymalloc is None: + pymalloc = "m" + if py_version < (3, 3): + unicode_size = _get_config_var("Py_UNICODE_SIZE", warn) + if unicode_size == 4 or ( + unicode_size is None and sys.maxunicode == 0x10FFFF + ): + ucs4 = "u" + elif debug: + # Debug builds can also load "normal" extension modules. + # We can also assume no UCS-4 or pymalloc requirement. + abis.append(f"cp{version}") + abis.insert( + 0, + "cp{version}{debug}{pymalloc}{ucs4}".format( + version=version, debug=debug, pymalloc=pymalloc, ucs4=ucs4 + ), + ) + return abis + + +def cpython_tags( + python_version: Optional[PythonVersion] = None, + abis: Optional[Iterable[str]] = None, + platforms: Optional[Iterable[str]] = None, + *, + warn: bool = False, +) -> Iterator[Tag]: + """ + Yields the tags for a CPython interpreter. + + The tags consist of: + - cp<python_version>-<abi>-<platform> + - cp<python_version>-abi3-<platform> + - cp<python_version>-none-<platform> + - cp<less than python_version>-abi3-<platform> # Older Python versions down to 3.2. + + If python_version only specifies a major version then user-provided ABIs and + the 'none' ABItag will be used. + + If 'abi3' or 'none' are specified in 'abis' then they will be yielded at + their normal position and not at the beginning. + """ + if not python_version: + python_version = sys.version_info[:2] + + interpreter = "cp{}".format(_version_nodot(python_version[:2])) + + if abis is None: + if len(python_version) > 1: + abis = _cpython_abis(python_version, warn) + else: + abis = [] + abis = list(abis) + # 'abi3' and 'none' are explicitly handled later. + for explicit_abi in ("abi3", "none"): + try: + abis.remove(explicit_abi) + except ValueError: + pass + + platforms = list(platforms or _platform_tags()) + for abi in abis: + for platform_ in platforms: + yield Tag(interpreter, abi, platform_) + if _abi3_applies(python_version): + yield from (Tag(interpreter, "abi3", platform_) for platform_ in platforms) + yield from (Tag(interpreter, "none", platform_) for platform_ in platforms) + + if _abi3_applies(python_version): + for minor_version in range(python_version[1] - 1, 1, -1): + for platform_ in platforms: + interpreter = "cp{version}".format( + version=_version_nodot((python_version[0], minor_version)) + ) + yield Tag(interpreter, "abi3", platform_) + + +def _generic_abi() -> Iterator[str]: + abi = sysconfig.get_config_var("SOABI") + if abi: + yield _normalize_string(abi) + + +def generic_tags( + interpreter: Optional[str] = None, + abis: Optional[Iterable[str]] = None, + platforms: Optional[Iterable[str]] = None, + *, + warn: bool = False, +) -> Iterator[Tag]: + """ + Yields the tags for a generic interpreter. + + The tags consist of: + - <interpreter>-<abi>-<platform> + + The "none" ABI will be added if it was not explicitly provided. + """ + if not interpreter: + interp_name = interpreter_name() + interp_version = interpreter_version(warn=warn) + interpreter = "".join([interp_name, interp_version]) + if abis is None: + abis = _generic_abi() + platforms = list(platforms or _platform_tags()) + abis = list(abis) + if "none" not in abis: + abis.append("none") + for abi in abis: + for platform_ in platforms: + yield Tag(interpreter, abi, platform_) + + +def _py_interpreter_range(py_version: PythonVersion) -> Iterator[str]: + """ + Yields Python versions in descending order. + + After the latest version, the major-only version will be yielded, and then + all previous versions of that major version. + """ + if len(py_version) > 1: + yield "py{version}".format(version=_version_nodot(py_version[:2])) + yield "py{major}".format(major=py_version[0]) + if len(py_version) > 1: + for minor in range(py_version[1] - 1, -1, -1): + yield "py{version}".format(version=_version_nodot((py_version[0], minor))) + + +def compatible_tags( + python_version: Optional[PythonVersion] = None, + interpreter: Optional[str] = None, + platforms: Optional[Iterable[str]] = None, +) -> Iterator[Tag]: + """ + Yields the sequence of tags that are compatible with a specific version of Python. + + The tags consist of: + - py*-none-<platform> + - <interpreter>-none-any # ... if `interpreter` is provided. + - py*-none-any + """ + if not python_version: + python_version = sys.version_info[:2] + platforms = list(platforms or _platform_tags()) + for version in _py_interpreter_range(python_version): + for platform_ in platforms: + yield Tag(version, "none", platform_) + if interpreter: + yield Tag(interpreter, "none", "any") + for version in _py_interpreter_range(python_version): + yield Tag(version, "none", "any") + + +def _mac_arch(arch: str, is_32bit: bool = _32_BIT_INTERPRETER) -> str: + if not is_32bit: + return arch + + if arch.startswith("ppc"): + return "ppc" + + return "i386" + + +def _mac_binary_formats(version: MacVersion, cpu_arch: str) -> List[str]: + formats = [cpu_arch] + if cpu_arch == "x86_64": + if version < (10, 4): + return [] + formats.extend(["intel", "fat64", "fat32"]) + + elif cpu_arch == "i386": + if version < (10, 4): + return [] + formats.extend(["intel", "fat32", "fat"]) + + elif cpu_arch == "ppc64": + # TODO: Need to care about 32-bit PPC for ppc64 through 10.2? + if version > (10, 5) or version < (10, 4): + return [] + formats.append("fat64") + + elif cpu_arch == "ppc": + if version > (10, 6): + return [] + formats.extend(["fat32", "fat"]) + + if cpu_arch in {"arm64", "x86_64"}: + formats.append("universal2") + + if cpu_arch in {"x86_64", "i386", "ppc64", "ppc", "intel"}: + formats.append("universal") + + return formats + + +def mac_platforms( + version: Optional[MacVersion] = None, arch: Optional[str] = None +) -> Iterator[str]: + """ + Yields the platform tags for a macOS system. + + The `version` parameter is a two-item tuple specifying the macOS version to + generate platform tags for. The `arch` parameter is the CPU architecture to + generate platform tags for. Both parameters default to the appropriate value + for the current system. + """ + version_str, _, cpu_arch = platform.mac_ver() + if version is None: + version = cast("MacVersion", tuple(map(int, version_str.split(".")[:2]))) + else: + version = version + if arch is None: + arch = _mac_arch(cpu_arch) + else: + arch = arch + + if (10, 0) <= version and version < (11, 0): + # Prior to Mac OS 11, each yearly release of Mac OS bumped the + # "minor" version number. The major version was always 10. + for minor_version in range(version[1], -1, -1): + compat_version = 10, minor_version + binary_formats = _mac_binary_formats(compat_version, arch) + for binary_format in binary_formats: + yield "macosx_{major}_{minor}_{binary_format}".format( + major=10, minor=minor_version, binary_format=binary_format + ) + + if version >= (11, 0): + # Starting with Mac OS 11, each yearly release bumps the major version + # number. The minor versions are now the midyear updates. + for major_version in range(version[0], 10, -1): + compat_version = major_version, 0 + binary_formats = _mac_binary_formats(compat_version, arch) + for binary_format in binary_formats: + yield "macosx_{major}_{minor}_{binary_format}".format( + major=major_version, minor=0, binary_format=binary_format + ) + + if version >= (11, 0): + # Mac OS 11 on x86_64 is compatible with binaries from previous releases. + # Arm64 support was introduced in 11.0, so no Arm binaries from previous + # releases exist. + # + # However, the "universal2" binary format can have a + # macOS version earlier than 11.0 when the x86_64 part of the binary supports + # that version of macOS. + if arch == "x86_64": + for minor_version in range(16, 3, -1): + compat_version = 10, minor_version + binary_formats = _mac_binary_formats(compat_version, arch) + for binary_format in binary_formats: + yield "macosx_{major}_{minor}_{binary_format}".format( + major=compat_version[0], + minor=compat_version[1], + binary_format=binary_format, + ) + else: + for minor_version in range(16, 3, -1): + compat_version = 10, minor_version + binary_format = "universal2" + yield "macosx_{major}_{minor}_{binary_format}".format( + major=compat_version[0], + minor=compat_version[1], + binary_format=binary_format, + ) + + +def _linux_platforms(is_32bit: bool = _32_BIT_INTERPRETER) -> Iterator[str]: + linux = _normalize_string(sysconfig.get_platform()) + if is_32bit: + if linux == "linux_x86_64": + linux = "linux_i686" + elif linux == "linux_aarch64": + linux = "linux_armv7l" + _, arch = linux.split("_", 1) + yield from _manylinux.platform_tags(linux, arch) + yield from _musllinux.platform_tags(arch) + yield linux + + +def _generic_platforms() -> Iterator[str]: + yield _normalize_string(sysconfig.get_platform()) + + +def _platform_tags() -> Iterator[str]: + """ + Provides the platform tags for this installation. + """ + if platform.system() == "Darwin": + return mac_platforms() + elif platform.system() == "Linux": + return _linux_platforms() + else: + return _generic_platforms() + + +def interpreter_name() -> str: + """ + Returns the name of the running interpreter. + """ + name = sys.implementation.name + return INTERPRETER_SHORT_NAMES.get(name) or name + + +def interpreter_version(*, warn: bool = False) -> str: + """ + Returns the version of the running interpreter. + """ + version = _get_config_var("py_version_nodot", warn=warn) + if version: + version = str(version) + else: + version = _version_nodot(sys.version_info[:2]) + return version + + +def _version_nodot(version: PythonVersion) -> str: + return "".join(map(str, version)) + + +def sys_tags(*, warn: bool = False) -> Iterator[Tag]: + """ + Returns the sequence of tag triples for the running interpreter. + + The order of the sequence corresponds to priority order for the + interpreter, from most to least important. + """ + + interp_name = interpreter_name() + if interp_name == "cp": + yield from cpython_tags(warn=warn) + else: + yield from generic_tags() + + yield from compatible_tags() diff --git a/pipenv/vendor/packaging/utils.py b/pipenv/vendor/packaging/utils.py index 88418786..bab11b80 100644 --- a/pipenv/vendor/packaging/utils.py +++ b/pipenv/vendor/packaging/utils.py @@ -1,57 +1,136 @@ # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import re +from typing import FrozenSet, NewType, Tuple, Union, cast +from .tags import Tag, parse_tag from .version import InvalidVersion, Version +BuildTag = Union[Tuple[()], Tuple[int, str]] +NormalizedName = NewType("NormalizedName", str) + + +class InvalidWheelFilename(ValueError): + """ + An invalid wheel filename was found, users should refer to PEP 427. + """ + + +class InvalidSdistFilename(ValueError): + """ + An invalid sdist filename was found, users should refer to the packaging user guide. + """ + _canonicalize_regex = re.compile(r"[-_.]+") +# PEP 427: The build number must start with a digit. +_build_tag_regex = re.compile(r"(\d+)(.*)") -def canonicalize_name(name): +def canonicalize_name(name: str) -> NormalizedName: # This is taken from PEP 503. - return _canonicalize_regex.sub("-", name).lower() + value = _canonicalize_regex.sub("-", name).lower() + return cast(NormalizedName, value) -def canonicalize_version(version): +def canonicalize_version(version: Union[Version, str]) -> str: """ - This is very similar to Version.__str__, but has one subtle differences + This is very similar to Version.__str__, but has one subtle difference with the way it handles the release segment. """ - - try: - version = Version(version) - except InvalidVersion: - # Legacy versions cannot be normalized - return version + if isinstance(version, str): + try: + parsed = Version(version) + except InvalidVersion: + # Legacy versions cannot be normalized + return version + else: + parsed = version parts = [] # Epoch - if version.epoch != 0: - parts.append("{0}!".format(version.epoch)) + if parsed.epoch != 0: + parts.append(f"{parsed.epoch}!") # Release segment # NB: This strips trailing '.0's to normalize - parts.append(re.sub(r"(\.0)+$", "", ".".join(str(x) for x in version.release))) + parts.append(re.sub(r"(\.0)+$", "", ".".join(str(x) for x in parsed.release))) # Pre-release - if version.pre is not None: - parts.append("".join(str(x) for x in version.pre)) + if parsed.pre is not None: + parts.append("".join(str(x) for x in parsed.pre)) # Post-release - if version.post is not None: - parts.append(".post{0}".format(version.post)) + if parsed.post is not None: + parts.append(f".post{parsed.post}") # Development release - if version.dev is not None: - parts.append(".dev{0}".format(version.dev)) + if parsed.dev is not None: + parts.append(f".dev{parsed.dev}") # Local version segment - if version.local is not None: - parts.append("+{0}".format(version.local)) + if parsed.local is not None: + parts.append(f"+{parsed.local}") return "".join(parts) + + +def parse_wheel_filename( + filename: str, +) -> Tuple[NormalizedName, Version, BuildTag, FrozenSet[Tag]]: + if not filename.endswith(".whl"): + raise InvalidWheelFilename( + f"Invalid wheel filename (extension must be '.whl'): {filename}" + ) + + filename = filename[:-4] + dashes = filename.count("-") + if dashes not in (4, 5): + raise InvalidWheelFilename( + f"Invalid wheel filename (wrong number of parts): {filename}" + ) + + parts = filename.split("-", dashes - 2) + name_part = parts[0] + # See PEP 427 for the rules on escaping the project name + if "__" in name_part or re.match(r"^[\w\d._]*$", name_part, re.UNICODE) is None: + raise InvalidWheelFilename(f"Invalid project name: {filename}") + name = canonicalize_name(name_part) + version = Version(parts[1]) + if dashes == 5: + build_part = parts[2] + build_match = _build_tag_regex.match(build_part) + if build_match is None: + raise InvalidWheelFilename( + f"Invalid build number: {build_part} in '{filename}'" + ) + build = cast(BuildTag, (int(build_match.group(1)), build_match.group(2))) + else: + build = () + tags = parse_tag(parts[-1]) + return (name, version, build, tags) + + +def parse_sdist_filename(filename: str) -> Tuple[NormalizedName, Version]: + if filename.endswith(".tar.gz"): + file_stem = filename[: -len(".tar.gz")] + elif filename.endswith(".zip"): + file_stem = filename[: -len(".zip")] + else: + raise InvalidSdistFilename( + f"Invalid sdist filename (extension must be '.tar.gz' or '.zip'):" + f" {filename}" + ) + + # We are requiring a PEP 440 version, which cannot contain dashes, + # so we split on the last dash. + name_part, sep, version_part = file_stem.rpartition("-") + if not sep: + raise InvalidSdistFilename(f"Invalid sdist filename: {filename}") + + name = canonicalize_name(name_part) + version = Version(version_part) + return (name, version) diff --git a/pipenv/vendor/packaging/version.py b/pipenv/vendor/packaging/version.py index 95157a1f..de9a09a4 100644 --- a/pipenv/vendor/packaging/version.py +++ b/pipenv/vendor/packaging/version.py @@ -1,24 +1,45 @@ # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import collections import itertools import re +import warnings +from typing import Callable, Iterator, List, Optional, SupportsInt, Tuple, Union -from ._structures import Infinity - +from ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType __all__ = ["parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN"] +InfiniteTypes = Union[InfinityType, NegativeInfinityType] +PrePostDevType = Union[InfiniteTypes, Tuple[str, int]] +SubLocalType = Union[InfiniteTypes, int, str] +LocalType = Union[ + NegativeInfinityType, + Tuple[ + Union[ + SubLocalType, + Tuple[SubLocalType, str], + Tuple[NegativeInfinityType, SubLocalType], + ], + ..., + ], +] +CmpKey = Tuple[ + int, Tuple[int, ...], PrePostDevType, PrePostDevType, PrePostDevType, LocalType +] +LegacyCmpKey = Tuple[int, Tuple[str, ...]] +VersionComparisonMethod = Callable[ + [Union[CmpKey, LegacyCmpKey], Union[CmpKey, LegacyCmpKey]], bool +] _Version = collections.namedtuple( "_Version", ["epoch", "release", "dev", "pre", "post", "local"] ) -def parse(version): +def parse(version: str) -> Union["LegacyVersion", "Version"]: """ Parse the given version string and return either a :class:`Version` object or a :class:`LegacyVersion` object depending on if the given version is @@ -36,88 +57,111 @@ class InvalidVersion(ValueError): """ -class _BaseVersion(object): - def __hash__(self): +class _BaseVersion: + _key: Union[CmpKey, LegacyCmpKey] + + def __hash__(self) -> int: return hash(self._key) - def __lt__(self, other): - return self._compare(other, lambda s, o: s < o) - - def __le__(self, other): - return self._compare(other, lambda s, o: s <= o) - - def __eq__(self, other): - return self._compare(other, lambda s, o: s == o) - - def __ge__(self, other): - return self._compare(other, lambda s, o: s >= o) - - def __gt__(self, other): - return self._compare(other, lambda s, o: s > o) - - def __ne__(self, other): - return self._compare(other, lambda s, o: s != o) - - def _compare(self, other, method): + # Please keep the duplicated `isinstance` check + # in the six comparisons hereunder + # unless you find a way to avoid adding overhead function calls. + def __lt__(self, other: "_BaseVersion") -> bool: if not isinstance(other, _BaseVersion): return NotImplemented - return method(self._key, other._key) + return self._key < other._key + + def __le__(self, other: "_BaseVersion") -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key <= other._key + + def __eq__(self, other: object) -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key == other._key + + def __ge__(self, other: "_BaseVersion") -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key >= other._key + + def __gt__(self, other: "_BaseVersion") -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key > other._key + + def __ne__(self, other: object) -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key != other._key class LegacyVersion(_BaseVersion): - def __init__(self, version): + def __init__(self, version: str) -> None: self._version = str(version) self._key = _legacy_cmpkey(self._version) - def __str__(self): + warnings.warn( + "Creating a LegacyVersion has been deprecated and will be " + "removed in the next major release", + DeprecationWarning, + ) + + def __str__(self) -> str: return self._version - def __repr__(self): - return "<LegacyVersion({0})>".format(repr(str(self))) + def __repr__(self) -> str: + return f"<LegacyVersion('{self}')>" @property - def public(self): + def public(self) -> str: return self._version @property - def base_version(self): + def base_version(self) -> str: return self._version @property - def epoch(self): + def epoch(self) -> int: return -1 @property - def release(self): + def release(self) -> None: return None @property - def pre(self): + def pre(self) -> None: return None @property - def post(self): + def post(self) -> None: return None @property - def dev(self): + def dev(self) -> None: return None @property - def local(self): + def local(self) -> None: return None @property - def is_prerelease(self): + def is_prerelease(self) -> bool: return False @property - def is_postrelease(self): + def is_postrelease(self) -> bool: return False @property - def is_devrelease(self): + def is_devrelease(self) -> bool: return False @@ -132,7 +176,7 @@ _legacy_version_replacement_map = { } -def _parse_version_parts(s): +def _parse_version_parts(s: str) -> Iterator[str]: for part in _legacy_version_component_re.split(s): part = _legacy_version_replacement_map.get(part, part) @@ -149,7 +193,8 @@ def _parse_version_parts(s): yield "*final" -def _legacy_cmpkey(version): +def _legacy_cmpkey(version: str) -> LegacyCmpKey: + # We hardcode an epoch of -1 here. A PEP 440 version can only have a epoch # greater than or equal to 0. This will effectively put the LegacyVersion, # which uses the defacto standard originally implemented by setuptools, @@ -158,7 +203,7 @@ def _legacy_cmpkey(version): # This scheme is taken from pkg_resources.parse_version setuptools prior to # it's adoption of the packaging library. - parts = [] + parts: List[str] = [] for part in _parse_version_parts(version.lower()): if part.startswith("*"): # remove "-" before a prerelease tag @@ -171,9 +216,8 @@ def _legacy_cmpkey(version): parts.pop() parts.append(part) - parts = tuple(parts) - return epoch, parts + return epoch, tuple(parts) # Deliberately not anchored to the start and end of the string, to make it @@ -214,11 +258,12 @@ class Version(_BaseVersion): _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE) - def __init__(self, version): + def __init__(self, version: str) -> None: + # Validate the version and parse it into pieces match = self._regex.search(version) if not match: - raise InvalidVersion("Invalid version: '{0}'".format(version)) + raise InvalidVersion(f"Invalid version: '{version}'") # Store the parsed out pieces of the version self._version = _Version( @@ -242,15 +287,15 @@ class Version(_BaseVersion): self._version.local, ) - def __repr__(self): - return "<Version({0})>".format(repr(str(self))) + def __repr__(self) -> str: + return f"<Version('{self}')>" - def __str__(self): + def __str__(self) -> str: parts = [] # Epoch if self.epoch != 0: - parts.append("{0}!".format(self.epoch)) + parts.append(f"{self.epoch}!") # Release segment parts.append(".".join(str(x) for x in self.release)) @@ -261,56 +306,59 @@ class Version(_BaseVersion): # Post-release if self.post is not None: - parts.append(".post{0}".format(self.post)) + parts.append(f".post{self.post}") # Development release if self.dev is not None: - parts.append(".dev{0}".format(self.dev)) + parts.append(f".dev{self.dev}") # Local version segment if self.local is not None: - parts.append("+{0}".format(self.local)) + parts.append(f"+{self.local}") return "".join(parts) @property - def epoch(self): - return self._version.epoch + def epoch(self) -> int: + _epoch: int = self._version.epoch + return _epoch @property - def release(self): - return self._version.release + def release(self) -> Tuple[int, ...]: + _release: Tuple[int, ...] = self._version.release + return _release @property - def pre(self): - return self._version.pre + def pre(self) -> Optional[Tuple[str, int]]: + _pre: Optional[Tuple[str, int]] = self._version.pre + return _pre @property - def post(self): + def post(self) -> Optional[int]: return self._version.post[1] if self._version.post else None @property - def dev(self): + def dev(self) -> Optional[int]: return self._version.dev[1] if self._version.dev else None @property - def local(self): + def local(self) -> Optional[str]: if self._version.local: return ".".join(str(x) for x in self._version.local) else: return None @property - def public(self): + def public(self) -> str: return str(self).split("+", 1)[0] @property - def base_version(self): + def base_version(self) -> str: parts = [] # Epoch if self.epoch != 0: - parts.append("{0}!".format(self.epoch)) + parts.append(f"{self.epoch}!") # Release segment parts.append(".".join(str(x) for x in self.release)) @@ -318,19 +366,34 @@ class Version(_BaseVersion): return "".join(parts) @property - def is_prerelease(self): + def is_prerelease(self) -> bool: return self.dev is not None or self.pre is not None @property - def is_postrelease(self): + def is_postrelease(self) -> bool: return self.post is not None @property - def is_devrelease(self): + def is_devrelease(self) -> bool: return self.dev is not None + @property + def major(self) -> int: + return self.release[0] if len(self.release) >= 1 else 0 + + @property + def minor(self) -> int: + return self.release[1] if len(self.release) >= 2 else 0 + + @property + def micro(self) -> int: + return self.release[2] if len(self.release) >= 3 else 0 + + +def _parse_letter_version( + letter: str, number: Union[str, bytes, SupportsInt] +) -> Optional[Tuple[str, int]]: -def _parse_letter_version(letter, number): if letter: # We consider there to be an implicit 0 in a pre-release if there is # not a numeral associated with it. @@ -360,11 +423,13 @@ def _parse_letter_version(letter, number): return letter, int(number) + return None + _local_version_separators = re.compile(r"[\._-]") -def _parse_local_version(local): +def _parse_local_version(local: str) -> Optional[LocalType]: """ Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve"). """ @@ -373,15 +438,24 @@ def _parse_local_version(local): part.lower() if not part.isdigit() else int(part) for part in _local_version_separators.split(local) ) + return None -def _cmpkey(epoch, release, pre, post, dev, local): +def _cmpkey( + epoch: int, + release: Tuple[int, ...], + pre: Optional[Tuple[str, int]], + post: Optional[Tuple[str, int]], + dev: Optional[Tuple[str, int]], + local: Optional[Tuple[SubLocalType]], +) -> CmpKey: + # When we compare a release version, we want to compare it with all of the # trailing zeros removed. So we'll use a reverse the list, drop all the now # leading zeros until we come to something non zero, then take the rest # re-reverse it back into the correct order and make it a tuple and use # that for our sorting key. - release = tuple( + _release = tuple( reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release)))) ) @@ -390,23 +464,31 @@ def _cmpkey(epoch, release, pre, post, dev, local): # if there is not a pre or a post segment. If we have one of those then # the normal sorting rules will handle this case correctly. if pre is None and post is None and dev is not None: - pre = -Infinity + _pre: PrePostDevType = NegativeInfinity # Versions without a pre-release (except as noted above) should sort after # those with one. elif pre is None: - pre = Infinity + _pre = Infinity + else: + _pre = pre # Versions without a post segment should sort before those with one. if post is None: - post = -Infinity + _post: PrePostDevType = NegativeInfinity + + else: + _post = post # Versions without a development segment should sort after those with one. if dev is None: - dev = Infinity + _dev: PrePostDevType = Infinity + + else: + _dev = dev if local is None: # Versions without a local segment should sort before those with one. - local = -Infinity + _local: LocalType = NegativeInfinity else: # Versions with a local segment need that segment parsed to implement # the sorting rules in PEP440. @@ -415,6 +497,8 @@ def _cmpkey(epoch, release, pre, post, dev, local): # - Numeric segments sort numerically # - Shorter versions sort before longer versions when the prefixes # match exactly - local = tuple((i, "") if isinstance(i, int) else (-Infinity, i) for i in local) + _local = tuple( + (i, "") if isinstance(i, int) else (NegativeInfinity, i) for i in local + ) - return epoch, release, pre, post, dev, local + return epoch, _release, _pre, _post, _dev, _local diff --git a/pipenv/vendor/parse.LICENSE b/pipenv/vendor/parse.LICENSE index 3163ad6d..6c73b16c 100644 --- a/pipenv/vendor/parse.LICENSE +++ b/pipenv/vendor/parse.LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2012-2013 Richard Jones <richard@python.org> +Copyright (c) 2012-2019 Richard Jones <richard@python.org> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -7,8 +7,8 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, @@ -17,5 +17,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - diff --git a/pipenv/vendor/parse.py b/pipenv/vendor/parse.py index 0b5cce23..062a4212 100644 --- a/pipenv/vendor/parse.py +++ b/pipenv/vendor/parse.py @@ -9,30 +9,38 @@ and ``with_pattern()`` when ``import \*`` is used: From there it's a simple thing to parse a string: ->>> parse("It's {}, I love it!", "It's spam, I love it!") -<Result ('spam',) {}> ->>> _[0] -'spam' +.. code-block:: pycon + + >>> parse("It's {}, I love it!", "It's spam, I love it!") + <Result ('spam',) {}> + >>> _[0] + 'spam' Or to search a string for some pattern: ->>> search('Age: {:d}\n', 'Name: Rufus\nAge: 42\nColor: red\n') -<Result (42,) {}> +.. code-block:: pycon + + >>> search('Age: {:d}\n', 'Name: Rufus\nAge: 42\nColor: red\n') + <Result (42,) {}> Or find all the occurrences of some pattern in a string: ->>> ''.join(r.fixed[0] for r in findall(">{}<", "<p>the <b>bold</b> text</p>")) -'the bold text' +.. code-block:: pycon + + >>> ''.join(r[0] for r in findall(">{}<", "<p>the <b>bold</b> text</p>")) + 'the bold text' If you're going to use the same pattern to match lots of strings you can compile it once: ->>> from parse import compile ->>> p = compile("It's {}, I love it!") ->>> print(p) -<Parser "It's {}, I love it!"> ->>> p.parse("It's spam, I love it!") -<Result ('spam',) {}> +.. code-block:: pycon + + >>> from parse import compile + >>> p = compile("It's {}, I love it!") + >>> print(p) + <Parser "It's {}, I love it!"> + >>> p.parse("It's spam, I love it!") + <Result ('spam',) {}> ("compile" is not exported for ``import *`` usage as it would override the built-in ``compile()`` function) @@ -40,8 +48,10 @@ built-in ``compile()`` function) The default behaviour is to match strings case insensitively. You may match with case by specifying `case_sensitive=True`: ->>> parse('SPAM', 'spam', case_sensitive=True) is None -True +.. code-block:: pycon + + >>> parse('SPAM', 'spam', case_sensitive=True) is None + True Format Syntax @@ -64,40 +74,51 @@ There are no "!" field conversions like ``format()`` has. Some simple parse() format string examples: ->>> parse("Bring me a {}", "Bring me a shrubbery") -<Result ('shrubbery',) {}> ->>> r = parse("The {} who say {}", "The knights who say Ni!") ->>> print(r) -<Result ('knights', 'Ni!') {}> ->>> print(r.fixed) -('knights', 'Ni!') ->>> r = parse("Bring out the holy {item}", "Bring out the holy hand grenade") ->>> print(r) -<Result () {'item': 'hand grenade'}> ->>> print(r.named) -{'item': 'hand grenade'} ->>> print(r['item']) -hand grenade ->>> 'item' in r -True +.. code-block:: pycon -Note that `in` only works if you have named fields. Dotted names and indexes -are possible though the application must make additional sense of the result: + >>> parse("Bring me a {}", "Bring me a shrubbery") + <Result ('shrubbery',) {}> + >>> r = parse("The {} who {} {}", "The knights who say Ni!") + >>> print(r) + <Result ('knights', 'say', 'Ni!') {}> + >>> print(r.fixed) + ('knights', 'say', 'Ni!') + >>> print(r[0]) + knights + >>> print(r[1:]) + ('say', 'Ni!') + >>> r = parse("Bring out the holy {item}", "Bring out the holy hand grenade") + >>> print(r) + <Result () {'item': 'hand grenade'}> + >>> print(r.named) + {'item': 'hand grenade'} + >>> print(r['item']) + hand grenade + >>> 'item' in r + True ->>> r = parse("Mmm, {food.type}, I love it!", "Mmm, spam, I love it!") ->>> print(r) -<Result () {'food.type': 'spam'}> ->>> print(r.named) -{'food.type': 'spam'} ->>> print(r['food.type']) -spam ->>> r = parse("My quest is {quest[name]}", "My quest is to seek the holy grail!") ->>> print(r) -<Result () {'quest': {'name': 'to seek the holy grail!'}}> ->>> print(r['quest']) -{'name': 'to seek the holy grail!'} ->>> print(r['quest']['name']) -to seek the holy grail! +Note that `in` only works if you have named fields. + +Dotted names and indexes are possible with some limits. Only word identifiers +are supported (ie. no numeric indexes) and the application must make additional +sense of the result: + +.. code-block:: pycon + + >>> r = parse("Mmm, {food.type}, I love it!", "Mmm, spam, I love it!") + >>> print(r) + <Result () {'food.type': 'spam'}> + >>> print(r.named) + {'food.type': 'spam'} + >>> print(r['food.type']) + spam + >>> r = parse("My quest is {quest[name]}", "My quest is to seek the holy grail!") + >>> print(r) + <Result () {'quest': {'name': 'to seek the holy grail!'}}> + >>> print(r['quest']) + {'name': 'to seek the holy grail!'} + >>> print(r['quest']['name']) + to seek the holy grail! If the text you're matching has braces in it you can match those by including a double-brace ``{{`` or ``}}`` in your format string, just like format() does. @@ -129,7 +150,8 @@ The differences between `parse()` and `format()` are: In addition some regular expression character group types "D", "w", "W", "s" and "S" are also available. - The "e" and "g" types are case-insensitive so there is not need for - the "E" or "G" types. + the "E" or "G" types. The "e" type handles Fortran formatted numbers (no + leading 0 before the decimal point). ===== =========================================== ======== Type Characters Matched Output @@ -173,18 +195,22 @@ tt Time time Some examples of typed parsing with ``None`` returned if the typing does not match: ->>> parse('Our {:d} {:w} are...', 'Our 3 weapons are...') -<Result (3, 'weapons') {}> ->>> parse('Our {:d} {:w} are...', 'Our three weapons are...') ->>> parse('Meet at {:tg}', 'Meet at 1/2/2011 11:00 PM') -<Result (datetime.datetime(2011, 2, 1, 23, 0),) {}> +.. code-block:: pycon + + >>> parse('Our {:d} {:w} are...', 'Our 3 weapons are...') + <Result (3, 'weapons') {}> + >>> parse('Our {:d} {:w} are...', 'Our three weapons are...') + >>> parse('Meet at {:tg}', 'Meet at 1/2/2011 11:00 PM') + <Result (datetime.datetime(2011, 2, 1, 23, 0),) {}> And messing about with alignment: ->>> parse('with {:>} herring', 'with a herring') -<Result ('a',) {}> ->>> parse('spam {:^} spam', 'spam lovely spam') -<Result ('lovely',) {}> +.. code-block:: pycon + + >>> parse('with {:>} herring', 'with a herring') + <Result ('a',) {}> + >>> parse('spam {:^} spam', 'spam lovely spam') + <Result ('lovely',) {}> Note that the "center" alignment does not test to make sure the value is centered - it just strips leading and trailing whitespace. @@ -193,14 +219,16 @@ Width and precision may be used to restrict the size of matched text from the input. Width specifies a minimum size and precision specifies a maximum. For example: ->>> parse('{:.2}{:.2}', 'look') # specifying precision -<Result ('lo', 'ok') {}> ->>> parse('{:4}{:4}', 'look at that') # specifying width -<Result ('look', 'at that') {}> ->>> parse('{:4}{:.4}', 'look at that') # specifying both -<Result ('look at ', 'that') {}> ->>> parse('{:2d}{:2d}', '0440') # parsing two contiguous numbers -<Result (4, 40) {}> +.. code-block:: pycon + + >>> parse('{:.2}{:.2}', 'look') # specifying precision + <Result ('lo', 'ok') {}> + >>> parse('{:4}{:4}', 'look at that') # specifying width + <Result ('look', 'at that') {}> + >>> parse('{:4}{:.4}', 'look at that') # specifying both + <Result ('look at ', 'that') {}> + >>> parse('{:2d}{:2d}', '0440') # parsing two contiguous numbers + <Result (4, 40) {}> Some notes for the date and time types: @@ -245,18 +273,18 @@ The result of a ``parse()`` and ``search()`` operation is either ``None`` (no ma The ``Result`` instance has three attributes: -fixed +``fixed`` A tuple of the fixed-position, anonymous fields extracted from the input. -named +``named`` A dictionary of the named fields extracted from the input. -spans +``spans`` A dictionary mapping the names and fixed position indices matched to a 2-tuple slice range of where the match occurred in the input. The span does not include any stripped padding (alignment or width). The ``Match`` instance has one method: -evaluate_result() +``evaluate_result()`` Generates and returns a ``Result`` instance for this ``Match`` object. @@ -272,56 +300,66 @@ The converter will be passed the field string matched. Whatever it returns will be substituted in the ``Result`` instance for that field. Your custom type conversions may override the builtin types if you supply one -with the same identifier. +with the same identifier: ->>> def shouty(string): -... return string.upper() -... ->>> parse('{:shouty} world', 'hello world', dict(shouty=shouty)) -<Result ('HELLO',) {}> +.. code-block:: pycon + + >>> def shouty(string): + ... return string.upper() + ... + >>> parse('{:shouty} world', 'hello world', dict(shouty=shouty)) + <Result ('HELLO',) {}> If the type converter has the optional ``pattern`` attribute, it is used as -regular expression for better pattern matching (instead of the default one). +regular expression for better pattern matching (instead of the default one): ->>> def parse_number(text): -... return int(text) ->>> parse_number.pattern = r'\d+' ->>> parse('Answer: {number:Number}', 'Answer: 42', dict(Number=parse_number)) -<Result () {'number': 42}> ->>> _ = parse('Answer: {:Number}', 'Answer: Alice', dict(Number=parse_number)) ->>> assert _ is None, "MISMATCH" +.. code-block:: pycon + + >>> def parse_number(text): + ... return int(text) + >>> parse_number.pattern = r'\d+' + >>> parse('Answer: {number:Number}', 'Answer: 42', dict(Number=parse_number)) + <Result () {'number': 42}> + >>> _ = parse('Answer: {:Number}', 'Answer: Alice', dict(Number=parse_number)) + >>> assert _ is None, "MISMATCH" You can also use the ``with_pattern(pattern)`` decorator to add this information to a type converter function: ->>> from parse import with_pattern ->>> @with_pattern(r'\d+') -... def parse_number(text): -... return int(text) ->>> parse('Answer: {number:Number}', 'Answer: 42', dict(Number=parse_number)) -<Result () {'number': 42}> +.. code-block:: pycon + + >>> from parse import with_pattern + >>> @with_pattern(r'\d+') + ... def parse_number(text): + ... return int(text) + >>> parse('Answer: {number:Number}', 'Answer: 42', dict(Number=parse_number)) + <Result () {'number': 42}> A more complete example of a custom type might be: ->>> yesno_mapping = { -... "yes": True, "no": False, -... "on": True, "off": False, -... "true": True, "false": False, -... } ->>> @with_pattern(r"|".join(yesno_mapping)) -... def parse_yesno(text): -... return yesno_mapping[text.lower()] +.. code-block:: pycon + + >>> yesno_mapping = { + ... "yes": True, "no": False, + ... "on": True, "off": False, + ... "true": True, "false": False, + ... } + >>> @with_pattern(r"|".join(yesno_mapping)) + ... def parse_yesno(text): + ... return yesno_mapping[text.lower()] If the type converter ``pattern`` uses regex-grouping (with parenthesis), you should indicate this by using the optional ``regex_group_count`` parameter in the ``with_pattern()`` decorator: ->>> @with_pattern(r'((\d+))', regex_group_count=2) -... def parse_number2(text): -... return int(text) ->>> parse('Answer: {:Number2} {:Number2}', 'Answer: 42 43', dict(Number2=parse_number2)) -<Result (42, 43) {}> +.. code-block:: pycon + + >>> @with_pattern(r'((\d+))', regex_group_count=2) + ... def parse_number2(text): + ... return int(text) + >>> parse('Answer: {:Number2} {:Number2}', 'Answer: 42 43', dict(Number2=parse_number2)) + <Result (42, 43) {}> Otherwise, this may cause parsing problems with unnamed/fixed parameters. @@ -329,22 +367,36 @@ Otherwise, this may cause parsing problems with unnamed/fixed parameters. Potential Gotchas ----------------- -`parse()` will always match the shortest text necessary (from left to right) +``parse()`` will always match the shortest text necessary (from left to right) to fulfil the parse pattern, so for example: ->>> pattern = '{dir1}/{dir2}' ->>> data = 'root/parent/subdir' ->>> sorted(parse(pattern, data).named.items()) -[('dir1', 'root'), ('dir2', 'parent/subdir')] + +.. code-block:: pycon + + >>> pattern = '{dir1}/{dir2}' + >>> data = 'root/parent/subdir' + >>> sorted(parse(pattern, data).named.items()) + [('dir1', 'root'), ('dir2', 'parent/subdir')] So, even though `{'dir1': 'root/parent', 'dir2': 'subdir'}` would also fit the pattern, the actual match represents the shortest successful match for -`dir1`. +``dir1``. ---- -**Version history (in brief)**: - +- 1.19.0 Added slice access to fixed results (thanks @jonathangjertsen). + Also corrected matching of *full string* vs. *full line* (thanks @giladreti) + Fix issue with using digit field numbering and types +- 1.18.0 Correct bug in int parsing introduced in 1.16.0 (thanks @maxxk) +- 1.17.0 Make left- and center-aligned search consume up to next space +- 1.16.0 Make compiled parse objects pickleable (thanks @martinResearch) +- 1.15.0 Several fixes for parsing non-base 10 numbers (thanks @vladikcomper) +- 1.14.0 More broad acceptance of Fortran number format (thanks @purpleskyfall) +- 1.13.1 Project metadata correction. +- 1.13.0 Handle Fortran formatted numbers with no leading 0 before decimal + point (thanks @purpleskyfall). + Handle comparison of FixedTzOffset with other types of object. +- 1.12.1 Actually use the `case_sensitive` arg in compile (thanks @jacquev6) - 1.12.0 Do not assume closing brace when an opening one is found (thanks @mattsep) - 1.11.1 Revert having unicode char in docstring, it breaks Bamboo builds(?!) - 1.11.0 Implement `__contains__` for Result instances. @@ -411,12 +463,13 @@ the pattern, the actual match represents the shortest successful match for and removed the restriction on mixing fixed-position and named fields - 1.0.0 initial release -This code is copyright 2012-2019 Richard Jones <richard@python.org> +This code is copyright 2012-2021 Richard Jones <richard@python.org> See the end of the source file for the license of use. ''' from __future__ import absolute_import -__version__ = '1.12.0' + +__version__ = '1.19.0' # yes, I now have two problems import re @@ -453,57 +506,85 @@ def with_pattern(pattern, regex_group_count=None): :param regex_group_count: Indicates how many regex-groups are in pattern. :return: wrapped function """ + def decorator(func): func.pattern = pattern func.regex_group_count = regex_group_count return func + return decorator -def int_convert(base): - '''Convert a string to an integer. +class int_convert: + """Convert a string to an integer. The string may start with a sign. - It may be of a base other than 10. + It may be of a base other than 2, 8, 10 or 16. - If may start with a base indicator, 0#nnnn, which we assume should - override the specified base. + If base isn't specified, it will be detected automatically based + on a string format. When string starts with a base indicator, 0#nnnn, + it overrides the default base of 10. It may also have other non-numeric characters that we can ignore. - ''' + """ + CHARS = '0123456789abcdefghijklmnopqrstuvwxyz' - def f(string, match, base=base): + def __init__(self, base=None): + self.base = base + + def __call__(self, string, match): if string[0] == '-': sign = -1 + number_start = 1 + elif string[0] == '+': + sign = 1 + number_start = 1 else: sign = 1 + number_start = 0 - if string[0] == '0' and len(string) > 2: - if string[1] in 'bB': - base = 2 - elif string[1] in 'oO': - base = 8 - elif string[1] in 'xX': - base = 16 - else: - # just go with the base specifed - pass + base = self.base + # If base wasn't specified, detect it automatically + if base is None: - chars = CHARS[:base] + # Assume decimal number, unless different base is detected + base = 10 + + # For number formats starting with 0b, 0o, 0x, use corresponding base ... + if string[number_start] == '0' and len(string) - number_start > 2: + if string[number_start + 1] in 'bB': + base = 2 + elif string[number_start + 1] in 'oO': + base = 8 + elif string[number_start + 1] in 'xX': + base = 16 + + chars = int_convert.CHARS[:base] string = re.sub('[^%s]' % chars, '', string.lower()) return sign * int(string, base) - return f + + +class convert_first: + """Convert the first element of a pair. + This equivalent to lambda s,m: converter(s). But unlike a lambda function, it can be pickled + """ + + def __init__(self, converter): + self.converter = converter + + def __call__(self, string, match): + return self.converter(string) def percentage(string, match): - return float(string[:-1]) / 100. + return float(string[:-1]) / 100.0 class FixedTzOffset(tzinfo): - """Fixed offset in minutes east from UTC. - """ + """Fixed offset in minutes east from UTC.""" + ZERO = timedelta(0) def __init__(self, offset, name): @@ -511,8 +592,7 @@ class FixedTzOffset(tzinfo): self._name = name def __repr__(self): - return '<%s %s %s>' % (self.__class__.__name__, self._name, - self._offset) + return '<%s %s %s>' % (self.__class__.__name__, self._name, self._offset) def utcoffset(self, dt): return self._offset @@ -524,22 +604,35 @@ class FixedTzOffset(tzinfo): return self.ZERO def __eq__(self, other): + if not isinstance(other, FixedTzOffset): + return False return self._name == other._name and self._offset == other._offset MONTHS_MAP = dict( - Jan=1, January=1, - Feb=2, February=2, - Mar=3, March=3, - Apr=4, April=4, + Jan=1, + January=1, + Feb=2, + February=2, + Mar=3, + March=3, + Apr=4, + April=4, May=5, - Jun=6, June=6, - Jul=7, July=7, - Aug=8, August=8, - Sep=9, September=9, - Oct=10, October=10, - Nov=11, November=11, - Dec=12, December=12 + Jun=6, + June=6, + Jul=7, + July=7, + Aug=8, + August=8, + Sep=9, + September=9, + Oct=10, + October=10, + Nov=11, + November=11, + Dec=12, + December=12, ) DAYS_PAT = r'(Mon|Tue|Wed|Thu|Fri|Sat|Sun)' MONTHS_PAT = r'(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)' @@ -549,17 +642,28 @@ AM_PAT = r'(\s+[AP]M)' TZ_PAT = r'(\s+[-+]\d\d?:?\d\d)' -def date_convert(string, match, ymd=None, mdy=None, dmy=None, - d_m_y=None, hms=None, am=None, tz=None, mm=None, dd=None): - '''Convert the incoming string containing some date / time info into a +def date_convert( + string, + match, + ymd=None, + mdy=None, + dmy=None, + d_m_y=None, + hms=None, + am=None, + tz=None, + mm=None, + dd=None, +): + """Convert the incoming string containing some date / time info into a datetime instance. - ''' + """ groups = match.groups() time_only = False if mm and dd: - y=datetime.today().year - m=groups[mm] - d=groups[dd] + y = datetime.today().year + m = groups[mm] + d = groups[dd] elif ymd is not None: y, m, d = re.split(r'[-/\s]', groups[ymd]) elif mdy is not None: @@ -650,13 +754,11 @@ class RepeatedNameError(ValueError): REGEX_SAFETY = re.compile(r'([?\\\\.[\]()*+\^$!\|])') # allowed field types -ALLOWED_TYPES = set(list('nbox%fFegwWdDsSl') + - ['t' + c for c in 'ieahgcts']) +ALLOWED_TYPES = set(list('nbox%fFegwWdDsSl') + ['t' + c for c in 'ieahgcts']) def extract_format(format, extra_types): - '''Pull apart the format [[fill]align][0][width][.precision][type] - ''' + """Pull apart the format [[fill]align][0][width][.precision][type]""" fill = align = None if format[0] in '<>=^': align = format[0] @@ -701,8 +803,8 @@ PARSE_RE = re.compile(r"""({{|}}|{\w*(?:(?:\.\w+)|(?:\[[^\]]+\]))*(?::[^}]+)?})" class Parser(object): - '''Encapsulate a format string that may be used to parse other strings. - ''' + """Encapsulate a format string that may be used to parse other strings.""" + def __init__(self, format, extra_types=None, case_sensitive=False): # a mapping of a name as in {hello.world} to a regex-group compatible # name, like hello__world Its used to prevent the transformation of @@ -736,8 +838,7 @@ class Parser(object): def __repr__(self): if len(self._format) > 20: - return '<%s %r>' % (self.__class__.__name__, - self._format[:17] + '...') + return '<%s %r>' % (self.__class__.__name__, self._format[:17] + '...') return '<%s %r>' % (self.__class__.__name__, self._format) @property @@ -749,33 +850,44 @@ class Parser(object): # access error through sys to keep py3k and backward compat e = str(sys.exc_info()[1]) if e.endswith('this version only supports 100 named groups'): - raise TooManyFields('sorry, you are attempting to parse ' - 'too many complex fields') + raise TooManyFields( + 'sorry, you are attempting to parse ' 'too many complex fields' + ) return self.__search_re @property def _match_re(self): if self.__match_re is None: - expression = r'^%s$' % self._expression + expression = r'\A%s\Z' % self._expression try: self.__match_re = re.compile(expression, self._re_flags) except AssertionError: # access error through sys to keep py3k and backward compat e = str(sys.exc_info()[1]) if e.endswith('this version only supports 100 named groups'): - raise TooManyFields('sorry, you are attempting to parse ' - 'too many complex fields') + raise TooManyFields( + 'sorry, you are attempting to parse ' 'too many complex fields' + ) except re.error: - raise NotImplementedError("Group names (e.g. (?P<name>) can " - "cause failure, as they are not escaped properly: '%s'" % - expression) + raise NotImplementedError( + "Group names (e.g. (?P<name>) can " + "cause failure, as they are not escaped properly: '%s'" % expression + ) return self.__match_re + @property + def named_fields(self): + return self._named_fields.copy() + + @property + def fixed_fields(self): + return self._fixed_fields.copy() + def parse(self, string, evaluate_result=True): - '''Match my format to the string exactly. + """Match my format to the string exactly. Return a Result or Match instance or None if there's no match. - ''' + """ m = self._match_re.match(string) if m is None: return None @@ -786,7 +898,7 @@ class Parser(object): return Match(self, m) def search(self, string, pos=0, endpos=None, evaluate_result=True): - '''Search the string for my format. + """Search the string for my format. Optionally start the search at "pos" character index and limit the search to a maximum index of endpos - equivalent to @@ -796,7 +908,7 @@ class Parser(object): Match instance is returned instead of the actual Result instance. Return either a Result instance or None if there's no match. - ''' + """ if endpos is None: endpos = len(string) m = self._search_re.search(string, pos, endpos) @@ -808,8 +920,10 @@ class Parser(object): else: return Match(self, m) - def findall(self, string, pos=0, endpos=None, extra_types=None, evaluate_result=True): - '''Search "string" for all occurrences of "format". + def findall( + self, string, pos=0, endpos=None, extra_types=None, evaluate_result=True + ): + """Search "string" for all occurrences of "format". Optionally start the search at "pos" character index and limit the search to a maximum index of endpos - equivalent to @@ -817,10 +931,12 @@ class Parser(object): Returns an iterator that holds Result or Match instances for each format match found. - ''' + """ if endpos is None: endpos = len(string) - return ResultIterator(self, string, pos, endpos, evaluate_result=evaluate_result) + return ResultIterator( + self, string, pos, endpos, evaluate_result=evaluate_result + ) def _expand_named_fields(self, named_fields): result = {} @@ -834,7 +950,7 @@ class Parser(object): if subkeys: for subkey in re.findall(r'\[[^\]]+\]', subkeys): - d = d.setdefault(k,{}) + d = d.setdefault(k, {}) k = subkey[1:-1] # assign the value to the last key @@ -867,8 +983,7 @@ class Parser(object): # now figure the match spans spans = dict((n, m.span(name_map[n])) for n in named_fields) - spans.update((i, m.span(n + 1)) - for i, n in enumerate(self._fixed_fields)) + spans.update((i, m.span(n + 1)) for i, n in enumerate(self._fixed_fields)) # and that's our result return Result(fixed_fields, self._expand_named_fields(named_fields), spans) @@ -922,16 +1037,24 @@ class Parser(object): # now figure whether this is an anonymous or named field, and whether # there's any format specification format = '' - if field and field[0].isalpha(): - if ':' in field: - name, format = field.split(':') - else: - name = field + + if ':' in field: + name, format = field.split(':') + else: + name = field + + # This *should* be more flexible, but parsing complicated structures + # out of the string is hard (and not necessarily useful) ... and I'm + # being lazy. So for now `identifier` is "anything starting with a + # letter" and digit args don't get attribute or element stuff. + if name and name[0].isalpha(): if name in self._name_to_group_map: if self._name_types[name] != format: - raise RepeatedNameError('field type %r for field "%s" ' - 'does not match previous seen type %r' % (format, - name, self._name_types[name])) + raise RepeatedNameError( + 'field type %r for field "%s" ' + 'does not match previous seen type %r' + % (format, name, self._name_types[name]) + ) group = self._name_to_group_map[name] # match previously-seen value return r'(?P=%s)' % group @@ -944,8 +1067,6 @@ class Parser(object): else: self._fixed_fields.append(self._group_index) wrap = r'(%s)' - if ':' in field: - format = field[1:] group = self._group_index # simplest case: no type specifier ({} or {name}) @@ -958,7 +1079,7 @@ class Parser(object): # figure type conversions, if any type = format['type'] - is_numeric = type and type in 'n%fegdobh' + is_numeric = type and type in 'n%fegdobx' if type in self._extra_types: type_converter = self._extra_types[type] s = getattr(type_converter, 'pattern', r'.+?') @@ -966,10 +1087,7 @@ class Parser(object): if regex_group_count is None: regex_group_count = 0 self._group_index += regex_group_count - - def f(string, m): - return type_converter(string) - self._type_conversions[group] = f + self._type_conversions[group] = convert_first(type_converter) elif type == 'n': s = r'\d{1,3}([,.]\d{3})*' self._group_index += 1 @@ -991,80 +1109,105 @@ class Parser(object): self._group_index += 1 self._type_conversions[group] = percentage elif type == 'f': - s = r'\d+\.\d+' - self._type_conversions[group] = lambda s, m: float(s) + s = r'\d*\.\d+' + self._type_conversions[group] = convert_first(float) elif type == 'F': - s = r'\d+\.\d+' - self._type_conversions[group] = lambda s, m: Decimal(s) + s = r'\d*\.\d+' + self._type_conversions[group] = convert_first(Decimal) elif type == 'e': - s = r'\d+\.\d+[eE][-+]?\d+|nan|NAN|[-+]?inf|[-+]?INF' - self._type_conversions[group] = lambda s, m: float(s) + s = r'\d*\.\d+[eE][-+]?\d+|nan|NAN|[-+]?inf|[-+]?INF' + self._type_conversions[group] = convert_first(float) elif type == 'g': s = r'\d+(\.\d+)?([eE][-+]?\d+)?|nan|NAN|[-+]?inf|[-+]?INF' self._group_index += 2 - self._type_conversions[group] = lambda s, m: float(s) + self._type_conversions[group] = convert_first(float) elif type == 'd': if format.get('width'): width = r'{1,%s}' % int(format['width']) else: width = '+' - s = r'\d{w}|0[xX][0-9a-fA-F]{w}|0[bB][01]{w}|0[oO][0-7]{w}'.format(w=width) - self._type_conversions[group] = int_convert(10) + s = r'\d{w}|[-+ ]?0[xX][0-9a-fA-F]{w}|[-+ ]?0[bB][01]{w}|[-+ ]?0[oO][0-7]{w}'.format( + w=width + ) + self._type_conversions[ + group + ] = int_convert() # do not specify number base, determine it automatically elif type == 'ti': - s = r'(\d{4}-\d\d-\d\d)((\s+|T)%s)?(Z|\s*[-+]\d\d:?\d\d)?' % \ - TIME_PAT + s = r'(\d{4}-\d\d-\d\d)((\s+|T)%s)?(Z|\s*[-+]\d\d:?\d\d)?' % TIME_PAT n = self._group_index - self._type_conversions[group] = partial(date_convert, ymd=n + 1, - hms=n + 4, tz=n + 7) + self._type_conversions[group] = partial( + date_convert, ymd=n + 1, hms=n + 4, tz=n + 7 + ) self._group_index += 7 elif type == 'tg': s = r'(\d{1,2}[-/](\d{1,2}|%s)[-/]\d{4})(\s+%s)?%s?%s?' % ( - ALL_MONTHS_PAT, TIME_PAT, AM_PAT, TZ_PAT) + ALL_MONTHS_PAT, + TIME_PAT, + AM_PAT, + TZ_PAT, + ) n = self._group_index - self._type_conversions[group] = partial(date_convert, dmy=n + 1, - hms=n + 5, am=n + 8, tz=n + 9) + self._type_conversions[group] = partial( + date_convert, dmy=n + 1, hms=n + 5, am=n + 8, tz=n + 9 + ) self._group_index += 9 elif type == 'ta': s = r'((\d{1,2}|%s)[-/]\d{1,2}[-/]\d{4})(\s+%s)?%s?%s?' % ( - ALL_MONTHS_PAT, TIME_PAT, AM_PAT, TZ_PAT) + ALL_MONTHS_PAT, + TIME_PAT, + AM_PAT, + TZ_PAT, + ) n = self._group_index - self._type_conversions[group] = partial(date_convert, mdy=n + 1, - hms=n + 5, am=n + 8, tz=n + 9) + self._type_conversions[group] = partial( + date_convert, mdy=n + 1, hms=n + 5, am=n + 8, tz=n + 9 + ) self._group_index += 9 elif type == 'te': # this will allow microseconds through if they're present, but meh - s = r'(%s,\s+)?(\d{1,2}\s+%s\s+\d{4})\s+%s%s' % (DAYS_PAT, - MONTHS_PAT, TIME_PAT, TZ_PAT) + s = r'(%s,\s+)?(\d{1,2}\s+%s\s+\d{4})\s+%s%s' % ( + DAYS_PAT, + MONTHS_PAT, + TIME_PAT, + TZ_PAT, + ) n = self._group_index - self._type_conversions[group] = partial(date_convert, dmy=n + 3, - hms=n + 5, tz=n + 8) + self._type_conversions[group] = partial( + date_convert, dmy=n + 3, hms=n + 5, tz=n + 8 + ) self._group_index += 8 elif type == 'th': # slight flexibility here from the stock Apache format - s = r'(\d{1,2}[-/]%s[-/]\d{4}):%s%s' % (MONTHS_PAT, TIME_PAT, - TZ_PAT) + s = r'(\d{1,2}[-/]%s[-/]\d{4}):%s%s' % (MONTHS_PAT, TIME_PAT, TZ_PAT) n = self._group_index - self._type_conversions[group] = partial(date_convert, dmy=n + 1, - hms=n + 3, tz=n + 6) + self._type_conversions[group] = partial( + date_convert, dmy=n + 1, hms=n + 3, tz=n + 6 + ) self._group_index += 6 elif type == 'tc': s = r'(%s)\s+%s\s+(\d{1,2})\s+%s\s+(\d{4})' % ( - DAYS_PAT, MONTHS_PAT, TIME_PAT) + DAYS_PAT, + MONTHS_PAT, + TIME_PAT, + ) n = self._group_index - self._type_conversions[group] = partial(date_convert, - d_m_y=(n + 4, n + 3, n + 8), hms=n + 5) + self._type_conversions[group] = partial( + date_convert, d_m_y=(n + 4, n + 3, n + 8), hms=n + 5 + ) self._group_index += 8 elif type == 'tt': s = r'%s?%s?%s?' % (TIME_PAT, AM_PAT, TZ_PAT) n = self._group_index - self._type_conversions[group] = partial(date_convert, hms=n + 1, - am=n + 4, tz=n + 5) + self._type_conversions[group] = partial( + date_convert, hms=n + 1, am=n + 4, tz=n + 5 + ) self._group_index += 5 elif type == 'ts': s = r'%s(\s+)(\d+)(\s+)(\d{1,2}:\d{1,2}:\d{1,2})?' % MONTHS_PAT n = self._group_index - self._type_conversions[group] = partial(date_convert, mm=n+1, dd=n+3, - hms=n + 5) + self._type_conversions[group] = partial( + date_convert, mm=n + 1, dd=n + 3, hms=n + 5 + ) self._group_index += 5 elif type == 'l': s = r'[A-Za-z]+' @@ -1118,48 +1261,50 @@ class Parser(object): # align "=" has been handled if align == '<': - s = '%s%s*' % (s, fill) + s = '%s%s+' % (s, fill) elif align == '>': s = '%s*%s' % (fill, s) elif align == '^': - s = '%s*%s%s*' % (fill, s, fill) + s = '%s*%s%s+' % (fill, s, fill) return s class Result(object): - '''The result of a parse() or search(). + """The result of a parse() or search(). Fixed results may be looked up using `result[index]`. + Slices of fixed results may also be looked up. Named results may be looked up using `result['name']`. Named results may be tested for existence using `'name' in result`. - ''' + """ + def __init__(self, fixed, named, spans): self.fixed = fixed self.named = named self.spans = spans def __getitem__(self, item): - if isinstance(item, int): + if isinstance(item, (int, slice)): return self.fixed[item] return self.named[item] def __repr__(self): - return '<%s %r %r>' % (self.__class__.__name__, self.fixed, - self.named) + return '<%s %r %r>' % (self.__class__.__name__, self.fixed, self.named) def __contains__(self, name): return name in self.named class Match(object): - '''The result of a parse() or search() if no results are generated. + """The result of a parse() or search() if no results are generated. This class is only used to expose internal used regex match objects to the user and use them for external Parser.evaluate_result calls. - ''' + """ + def __init__(self, parser, match): self.parser = parser self.match = match @@ -1170,10 +1315,11 @@ class Match(object): class ResultIterator(object): - '''The result of a findall() operation. + """The result of a findall() operation. Each element is a Result instance. - ''' + """ + def __init__(self, parser, string, pos, endpos, evaluate_result=True): self.parser = parser self.string = string @@ -1200,7 +1346,7 @@ class ResultIterator(object): def parse(format, string, extra_types=None, evaluate_result=True, case_sensitive=False): - '''Using "format" attempt to pull values from "string". + """Using "format" attempt to pull values from "string". The format must match the string contents exactly. If the value you're looking for is instead just a part of the string use @@ -1224,14 +1370,21 @@ def parse(format, string, extra_types=None, evaluate_result=True, case_sensitive See the module documentation for the use of "extra_types". In the case there is no match parse() will return None. - ''' + """ p = Parser(format, extra_types=extra_types, case_sensitive=case_sensitive) return p.parse(string, evaluate_result=evaluate_result) -def search(format, string, pos=0, endpos=None, extra_types=None, evaluate_result=True, - case_sensitive=False): - '''Search "string" for the first occurrence of "format". +def search( + format, + string, + pos=0, + endpos=None, + extra_types=None, + evaluate_result=True, + case_sensitive=False, +): + """Search "string" for the first occurrence of "format". The format may occur anywhere within the string. If instead you wish for the format to exactly match the string @@ -1258,14 +1411,21 @@ def search(format, string, pos=0, endpos=None, extra_types=None, evaluate_result See the module documentation for the use of "extra_types". In the case there is no match parse() will return None. - ''' + """ p = Parser(format, extra_types=extra_types, case_sensitive=case_sensitive) return p.search(string, pos, endpos, evaluate_result=evaluate_result) -def findall(format, string, pos=0, endpos=None, extra_types=None, evaluate_result=True, - case_sensitive=False): - '''Search "string" for all occurrences of "format". +def findall( + format, + string, + pos=0, + endpos=None, + extra_types=None, + evaluate_result=True, + case_sensitive=False, +): + """Search "string" for all occurrences of "format". You will be returned an iterator that holds Result instances for each format match found. @@ -1289,13 +1449,13 @@ def findall(format, string, pos=0, endpos=None, extra_types=None, evaluate_resul If the format is invalid a ValueError will be raised. See the module documentation for the use of "extra_types". - ''' + """ p = Parser(format, extra_types=extra_types, case_sensitive=case_sensitive) - return Parser(format, extra_types=extra_types).findall(string, pos, endpos, evaluate_result=evaluate_result) + return p.findall(string, pos, endpos, evaluate_result=evaluate_result) def compile(format, extra_types=None, case_sensitive=False): - '''Create a Parser instance to parse "format". + """Create a Parser instance to parse "format". The resultant Parser has a method .parse(string) which behaves in the same manner as parse(format, string). @@ -1309,11 +1469,11 @@ def compile(format, extra_types=None, case_sensitive=False): See the module documentation for the use of "extra_types". Returns a Parser instance. - ''' - return Parser(format, extra_types=extra_types) + """ + return Parser(format, extra_types=extra_types, case_sensitive=case_sensitive) -# Copyright (c) 2012-2019 Richard Jones <richard@python.org> +# Copyright (c) 2012-2020 Richard Jones <richard@python.org> # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal diff --git a/pipenv/vendor/passa/LICENSE b/pipenv/vendor/passa/LICENSE deleted file mode 100644 index cd41e272..00000000 --- a/pipenv/vendor/passa/LICENSE +++ /dev/null @@ -1,13 +0,0 @@ -Copyright (c) 2018, Dan Ryan <dan@danryan.co> and Tzu-ping Chung <uranusjr@gmail.com> - -Permission to use, copy, modify, and distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/pipenv/vendor/passa/__init__.py b/pipenv/vendor/passa/__init__.py deleted file mode 100644 index ea633f0a..00000000 --- a/pipenv/vendor/passa/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# -*- coding=utf-8 -*- - -__all__ = [ - '__version__' -] - -__version__ = '0.3.1.dev0' diff --git a/pipenv/vendor/passa/__main__.py b/pipenv/vendor/passa/__main__.py deleted file mode 100644 index 76c2e6a6..00000000 --- a/pipenv/vendor/passa/__main__.py +++ /dev/null @@ -1,6 +0,0 @@ -# -*- coding=utf-8 -*- - -from .cli import main - -if __name__ == '__main__': - main() diff --git a/pipenv/vendor/passa/actions/add.py b/pipenv/vendor/passa/actions/add.py deleted file mode 100644 index 63384667..00000000 --- a/pipenv/vendor/passa/actions/add.py +++ /dev/null @@ -1,57 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, print_function, unicode_literals - -import itertools -import sys - - -def add_packages(packages=[], editables=[], project=None, dev=False, sync=False, clean=False): - from passa.models.lockers import PinReuseLocker - from passa.operations.lock import lock - - lines = list(itertools.chain( - packages, - ("-e {}".format(e) for e in editables), - )) - - project = project - for line in lines: - try: - project.add_line_to_pipfile(line, develop=dev) - except (TypeError, ValueError) as e: - print("Cannot add {line!r} to Pipfile: {error}".format( - line=line, error=str(e), - ), file=sys.stderr) - return 2 - - prev_lockfile = project.lockfile - - locker = PinReuseLocker(project) - success = lock(locker) - if not success: - return 1 - - project._p.write() - project._l.write() - print("Written to project at", project.root) - - if not sync: - return - - from passa.models.synchronizers import Synchronizer - from passa.operations.sync import sync - - lockfile_diff = project.difference_lockfile(prev_lockfile) - default = any(lockfile_diff.default) - develop = any(lockfile_diff.develop) - - syncer = Synchronizer( - project, default=default, develop=develop, - clean_unneeded=clean, - ) - success = sync(syncer) - if not success: - return 1 - - print("Synchronized project at", project.root) diff --git a/pipenv/vendor/passa/actions/clean.py b/pipenv/vendor/passa/actions/clean.py deleted file mode 100644 index 3570e4db..00000000 --- a/pipenv/vendor/passa/actions/clean.py +++ /dev/null @@ -1,16 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, print_function, unicode_literals - - -def clean(project, dev=False): - from passa.models.synchronizers import Cleaner - from passa.operations.sync import clean - - cleaner = Cleaner(project, default=True, develop=dev) - - success = clean(cleaner) - if not success: - return 1 - - print("Cleaned project at", project.root) diff --git a/pipenv/vendor/passa/actions/freeze.py b/pipenv/vendor/passa/actions/freeze.py deleted file mode 100644 index ca4dbb2a..00000000 --- a/pipenv/vendor/passa/actions/freeze.py +++ /dev/null @@ -1,93 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, print_function, unicode_literals - -import contextlib -import io -import itertools -import sys - -import vistir.misc - - -def _source_as_lines(source, extra): - url = source["url"] - if extra: - lines = ["--extra-index-url {}".format(url)] - else: - lines = ["--index-url {}".format(url)] - if not source.get("verify_ssl", True): - lines = ["--trusted-host {}".format(url)] - return lines - - -def _requirement_as_line(requirement, sources, include_hashes): - if requirement.index: - sources = sources - else: - sources = None - line = vistir.misc.to_text( - requirement.as_line(sources=sources, include_hashes=include_hashes) - ) - return line - - -@contextlib.contextmanager -def open_for_output(filename): - if filename is None: - yield sys.stdout - return - with io.open(filename, "w", encoding="utf-8", newline="\n") as f: - yield f - - -def freeze(project=None, default=True, dev=True, include_hashes=None, target=None): - from requirementslib import Requirement - - lockfile = project.lockfile - if not lockfile: - print("Pipfile.lock is required to export.", file=sys.stderr) - return 1 - - section_names = [] - if default: - section_names.append("default") - if dev: - section_names.append("develop") - requirements = [ - Requirement.from_pipfile(key, entry._data) - for key, entry in itertools.chain.from_iterable( - lockfile.get(name, {}).items() - for name in section_names - ) - ] - - if include_hashes is None: - include_hashes = all(r.is_named for r in requirements) - - sources = lockfile.meta.sources._data - - source_lines = list(vistir.misc.dedup(itertools.chain( - itertools.chain.from_iterable( - _source_as_lines(source, False) - for source in sources[:1] - ), - itertools.chain.from_iterable( - _source_as_lines(source, True) - for source in sources[1:] - ), - ))) - - requirement_lines = sorted(vistir.misc.dedup( - _requirement_as_line(requirement, sources, include_hashes) - for requirement in requirements - )) - - with open_for_output(target) as f: - for line in source_lines: - f.write(line) - f.write("\n") - f.write("\n") - for line in requirement_lines: - f.write(line) - f.write("\n") diff --git a/pipenv/vendor/passa/actions/init.py b/pipenv/vendor/passa/actions/init.py deleted file mode 100644 index 1d9f5923..00000000 --- a/pipenv/vendor/passa/actions/init.py +++ /dev/null @@ -1,59 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, print_function, unicode_literals - -import io -import os -from pip_shims import Command as PipCommand, cmdoptions -import plette -import six -import vistir - - -class PipCmd(PipCommand): - name = "PipCmd" - - -def get_sources(urls, trusted_hosts): - trusted_hosts = [six.moves.urllib.parse.urlparse(url).netloc for url in trusted_hosts] - sources = [] - for url in urls: - parsed_url = six.moves.urllib.parse.urlparse(url) - netloc = parsed_url.netloc - if '@' in netloc: - _, _, netloc = netloc.rpartition('@') - name, _, _ = netloc.partition('.') # Just use the domain name as the source name - verify_ssl = True - if netloc in trusted_hosts: - verify_ssl = False - sources.append({"url": url, "name": name, "verify_ssl": verify_ssl}) - return sources - - -def init_project(root=None, python_version=None): - pipfile_path = os.path.join(root, "Pipfile") - if os.path.isfile(pipfile_path): - raise RuntimeError("{0!r} is already a Pipfile project".format(root)) - if not os.path.exists(root): - vistir.path.mkdir_p(root, mode=0o755) - pip_command = PipCmd() - cmdoptions.make_option_group(cmdoptions.index_group, pip_command.parser) - parsed, _ = pip_command.parser.parse_args([]) - index_urls = [parsed.index_url] + parsed.extra_index_urls - sources = get_sources(index_urls, parsed.trusted_hosts) - data = { - "sources": sources, - "packages": {}, - "dev-packages": {}, - } - if python_version: - data["requires"] = {"python_version": python_version} - return create_project(pipfile_path=pipfile_path, data=data) - - -def create_project(pipfile_path, data={}): - pipfile = plette.pipfiles.Pipfile(data=data) - with io.open(pipfile_path, "w") as fh: - pipfile.dump(fh) - print("Successfully created new pipfile at {0!r}".format(pipfile_path)) - return 0 diff --git a/pipenv/vendor/passa/actions/install.py b/pipenv/vendor/passa/actions/install.py deleted file mode 100644 index 1728dae5..00000000 --- a/pipenv/vendor/passa/actions/install.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, print_function, unicode_literals - - -def install(project=None, check=True, dev=False, clean=True): - from passa.models.lockers import BasicLocker - from passa.operations.lock import lock - - project = project - - if not check or not project.is_synced(): - locker = BasicLocker(project) - success = lock(locker) - if not success: - return 1 - project._l.write() - print("Written to project at", project.root) - - from passa.models.synchronizers import Synchronizer - from passa.operations.sync import sync - - syncer = Synchronizer( - project, default=True, develop=dev, - clean_unneeded=clean, - ) - - success = sync(syncer) - if not success: - return 1 - - print("Synchronized project at", project.root) diff --git a/pipenv/vendor/passa/actions/lock.py b/pipenv/vendor/passa/actions/lock.py deleted file mode 100644 index 7c094695..00000000 --- a/pipenv/vendor/passa/actions/lock.py +++ /dev/null @@ -1,17 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, print_function, unicode_literals - - -def lock(project=None): - from passa.models.lockers import BasicLocker - from passa.operations.lock import lock - - project = project - locker = BasicLocker(project) - success = lock(locker) - if not success: - return - - project._l.write() - print("Written to project at", project.root) diff --git a/pipenv/vendor/passa/actions/remove.py b/pipenv/vendor/passa/actions/remove.py deleted file mode 100644 index 158f5e69..00000000 --- a/pipenv/vendor/passa/actions/remove.py +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, print_function, unicode_literals - - -def remove(project=None, only="default", packages=[], clean=True): - from passa.models.lockers import PinReuseLocker - from passa.operations.lock import lock - - default = (only != "dev") - develop = (only != "default") - - project = project - project.remove_keys_from_pipfile( - packages, default=default, develop=develop, - ) - - locker = PinReuseLocker(project) - success = lock(locker) - if not success: - return 1 - - project._p.write() - project._l.write() - print("Written to project at", project.root) - - if not clean: - return - - from passa.models.synchronizers import Cleaner - from passa.operations.sync import clean - - cleaner = Cleaner(project, default=True, develop=True) - success = clean(cleaner) - if not success: - return 1 - - print("Cleaned project at", project.root) diff --git a/pipenv/vendor/passa/actions/sync.py b/pipenv/vendor/passa/actions/sync.py deleted file mode 100644 index 23e36eeb..00000000 --- a/pipenv/vendor/passa/actions/sync.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, print_function, unicode_literals - - -def sync(project=None, dev=False, clean=True): - from passa.models.synchronizers import Synchronizer - from passa.operations.sync import sync - - project = project - syncer = Synchronizer( - project, default=True, develop=dev, - clean_unneeded=clean, - ) - - success = sync(syncer) - if not success: - return 1 - - print("Synchronized project at", project.root) diff --git a/pipenv/vendor/passa/actions/upgrade.py b/pipenv/vendor/passa/actions/upgrade.py deleted file mode 100644 index fb3ad7f5..00000000 --- a/pipenv/vendor/passa/actions/upgrade.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, print_function, unicode_literals - -import sys - - -def upgrade(project=None, strategy="only-if-needed", sync=True, packages=[]): - from passa.models.lockers import EagerUpgradeLocker, PinReuseLocker - from passa.operations.lock import lock - - for package in packages: - if not project.contains_key_in_pipfile(package): - print("{package!r} not found in Pipfile".format( - package=package, - ), file=sys.stderr) - return 2 - - project.remove_keys_from_lockfile(packages) - - prev_lockfile = project.lockfile - - if strategy == "eager": - locker = EagerUpgradeLocker(project, packages) - else: - locker = PinReuseLocker(project) - success = lock(locker) - if not success: - return 1 - - project._l.write() - print("Written to project at", project.root) - - if not sync: - return - - from passa.operations.sync import sync - from passa.models.synchronizers import Synchronizer - - lockfile_diff = project.difference_lockfile(prev_lockfile) - default = bool(any(lockfile_diff.default)) - develop = bool(any(lockfile_diff.develop)) - - syncer = Synchronizer( - project, default=default, develop=develop, - clean_unneeded=False, - ) - success = sync(syncer) - if not success: - return 1 - - print("Synchronized project at", project.root) diff --git a/pipenv/vendor/passa/cli/__init__.py b/pipenv/vendor/passa/cli/__init__.py deleted file mode 100644 index b6891d38..00000000 --- a/pipenv/vendor/passa/cli/__init__.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, unicode_literals - -import argparse -import importlib -import pkgutil -import sys - -from passa import __version__ - - -CURRENT_MODULE_PATH = sys.modules[__name__].__path__ - - -def main(argv=None): - root_parser = argparse.ArgumentParser( - prog="passa", - description="Pipfile project management tool.", - ) - root_parser.add_argument( - "--version", - action="version", - version="%(prog)s, version {}".format(__version__), - help="show the version and exit", - ) - - subparsers = root_parser.add_subparsers() - for _, name, _ in pkgutil.iter_modules(CURRENT_MODULE_PATH, "."): - module = importlib.import_module(name, __name__) - try: - klass = module.Command - except AttributeError: - continue - parser = subparsers.add_parser(klass.name, help=klass.description) - command = klass(parser) - parser.set_defaults(func=command.run) - - options = root_parser.parse_args(argv) - - try: - f = options.func - except AttributeError: - root_parser.print_help() - result = -1 - else: - result = f(options) - if result is not None: - sys.exit(result) diff --git a/pipenv/vendor/passa/cli/_base.py b/pipenv/vendor/passa/cli/_base.py deleted file mode 100644 index 0ca48682..00000000 --- a/pipenv/vendor/passa/cli/_base.py +++ /dev/null @@ -1,61 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, unicode_literals - -import argparse -import os -import sys - -from .options import project - - -class BaseCommand(object): - """A CLI command. - """ - name = None - description = None - default_arguments = [project] - arguments = [] - - def __init__(self, parser=None): - if not parser: - parser = argparse.ArgumentParser( - prog=os.path.basename(sys.argv[0]), - description="Base argument parser for passa" - ) - self.parser = parser - self.add_arguments() - - @classmethod - def build_parser(cls): - parser = argparse.ArgumentParser( - prog="passa {}".format(cls.name), - description=cls.description, - ) - return cls(parser) - - @classmethod - def run_parser(cls): - parser = cls.build_parser() - parser() - - def __call__(self, argv=None): - options = self.parser.parse_args(argv) - result = self.main(options) - if result is not None: - sys.exit(result) - - def add_default_arguments(self): - for arg in self.default_arguments: - arg.add_to_parser(self.parser) - - def add_arguments(self): - self.add_default_arguments() - for arg in self.arguments: - arg.add_to_parser(self.parser) - - def main(self, options): - return self.run(options) - - def run(self, options): - raise NotImplementedError diff --git a/pipenv/vendor/passa/cli/add.py b/pipenv/vendor/passa/cli/add.py deleted file mode 100644 index d5596cde..00000000 --- a/pipenv/vendor/passa/cli/add.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, print_function, unicode_literals - -from ..actions.add import add_packages -from ._base import BaseCommand -from .options import package_group - - -class Command(BaseCommand): - - name = "add" - description = "Add packages to project." - arguments = [package_group] - - def run(self, options): - if not options.editables and not options.packages: - self.parser.error("Must supply either a requirement or --editable") - return add_packages( - packages=options.packages, - editables=options.editables, - project=options.project, - dev=options.dev - ) - - -if __name__ == "__main__": - Command.run_parser() diff --git a/pipenv/vendor/passa/cli/clean.py b/pipenv/vendor/passa/cli/clean.py deleted file mode 100644 index e23d5ee5..00000000 --- a/pipenv/vendor/passa/cli/clean.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, print_function, unicode_literals - -from ..actions.clean import clean -from ._base import BaseCommand -from .options import dev, no_default - - -class Command(BaseCommand): - - name = "clean" - description = "Uninstall unlisted packages from the environment." - arguments = [dev, no_default] - - def run(self, options): - return clean(project=options.project, default=options.default, dev=options.dev) - - -if __name__ == "__main__": - Command.run_parser() diff --git a/pipenv/vendor/passa/cli/freeze.py b/pipenv/vendor/passa/cli/freeze.py deleted file mode 100644 index 053c7273..00000000 --- a/pipenv/vendor/passa/cli/freeze.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, print_function, unicode_literals - -from ..actions.freeze import freeze -from ._base import BaseCommand -from .options import dev, include_hashes_group, no_default, target - - -class Command(BaseCommand): - - name = "freeze" - description = "Export project depenencies to requirements.txt." - arguments = [dev, no_default, target, include_hashes_group] - - def run(self, options): - return freeze( - project=options.project, default=options.default, dev=options.dev, - include_hashes=options.include_hashes - ) - - -if __name__ == "__main__": - Command.run_parser() diff --git a/pipenv/vendor/passa/cli/init.py b/pipenv/vendor/passa/cli/init.py deleted file mode 100644 index 95ce8d84..00000000 --- a/pipenv/vendor/passa/cli/init.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, print_function, unicode_literals - -import argparse -import os - -from ..actions.init import init_project -from ._base import BaseCommand -from .options import new_project_group - - -class Command(BaseCommand): - - name = "init" - description = "Create a new project." - default_arguments = [] - arguments = [new_project_group] - - def run(self, options): - pipfile_path = os.path.join(options.project, "Pipfile") - if os.path.exists(pipfile_path): - raise argparse.ArgumentError( - "{0!r} is already a Pipfile project".format(options.project), - ) - return init_project( - root=options.project, python_version=options.python_version - ) - - -if __name__ == "__main__": - Command.run_parser() diff --git a/pipenv/vendor/passa/cli/install.py b/pipenv/vendor/passa/cli/install.py deleted file mode 100644 index 1c0b4591..00000000 --- a/pipenv/vendor/passa/cli/install.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, print_function, unicode_literals - -from ..actions.install import install -from ._base import BaseCommand -from .options import dev, no_check, no_clean - - -class Command(BaseCommand): - - name = "install" - description = "Generate Pipfile.lock to synchronize the environment." - arguments = [no_check, dev, no_clean] - - def run(self, options): - return install(project=options.project, check=options.check, dev=options.dev, - clean=options.clean) - - -if __name__ == "__main__": - Command.run_parser() diff --git a/pipenv/vendor/passa/cli/lock.py b/pipenv/vendor/passa/cli/lock.py deleted file mode 100644 index 9b0651a1..00000000 --- a/pipenv/vendor/passa/cli/lock.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, print_function, unicode_literals - -from ..actions.lock import lock -from ._base import BaseCommand - - -class Command(BaseCommand): - name = "lock" - description = "Generate Pipfile.lock." - - def run(self, options): - return lock(project=options.project) - - -if __name__ == "__main__": - Command.run_parser() diff --git a/pipenv/vendor/passa/cli/options.py b/pipenv/vendor/passa/cli/options.py deleted file mode 100644 index f20b612a..00000000 --- a/pipenv/vendor/passa/cli/options.py +++ /dev/null @@ -1,156 +0,0 @@ -# -*- coding=utf-8 -*- -from __future__ import absolute_import - -import argparse -import os -import sys - -import tomlkit.exceptions - -import passa.models.projects -import vistir - - -PYTHON_VERSION = ".".join(str(v) for v in sys.version_info[:2]) - - -class Project(passa.models.projects.Project): - def __init__(self, root, *args, **kwargs): - root = vistir.compat.Path(root).absolute() - pipfile = root.joinpath("Pipfile") - if not pipfile.is_file(): - raise argparse.ArgumentError( - "project", "{0!r} is not a Pipfile project".format(root), - ) - try: - super(Project, self).__init__(root.as_posix(), *args, **kwargs) - except tomlkit.exceptions.ParseError as e: - raise argparse.ArgumentError( - "project", "failed to parse Pipfile: {0!r}".format(str(e)), - ) - - def __name__(self): - return "Project Root" - - -class Option(object): - def __init__(self, *args, **kwargs): - self.args = args - self.kwargs = kwargs - - def add_to_parser(self, parser): - parser.add_argument(*self.args, **self.kwargs) - - def add_to_group(self, group): - group.add_argument(*self.args, **self.kwargs) - - -class ArgumentGroup(object): - def __init__( - self, name, parser=None, - is_mutually_exclusive=False, - required=None, options=None): - self.name = name - self.options = options or [] - self.parser = parser - self.required = required - self.is_mutually_exclusive = is_mutually_exclusive - self.argument_group = None - - def add_to_parser(self, parser): - group = None - if self.is_mutually_exclusive: - group = parser.add_mutually_exclusive_group(required=self.required) - else: - group = parser.add_argument_group() - for option in self.options: - option.add_to_group(group) - self.argument_group = group - self.parser = parser - - -project = Option( - "--project", metavar="project", default=os.getcwd(), type=Project, - help="path to project root (directory containing Pipfile)", -) - -new_project = Option( - "--project", metavar="project", default=os.getcwd(), type=str, - help="path to project root (directory containing Pipfile)", -) - -python_version = Option( - "--py-version", "--python-version", "--requires-python", metavar="python-version", - dest="python_version", default=PYTHON_VERSION, type=str, - help="required minor python version for the project" -) - -packages = Option( - "packages", metavar="package", nargs="*", - help="requirement to add (can be used multiple times)", -) - -editable = Option( - '-e', '--editable', dest='editables', nargs="*", default=[], metavar='path/vcs', - help="editable requirement to add (can be used multiple times)", -) - -dev = Option( - "--dev", action="store_true", default=False, - help="Use [dev-packages] for install/freeze/uninstall operations", -) - -no_sync = Option( - "--no-sync", dest="sync", action="store_false", default=True, - help="do not synchronize the environment", -) - -target = Option( - "-t", "--target", default=None, - help="file to export into (default is to print to stdout)" -) - -no_default = Option( - "--no-default", dest="default", action="store_false", default=True, - help="do not include default packages when exporting, importing, or cleaning" -) - -include_hashes = Option( - "--include-hashes", dest="include_hashes", action="store_true", - help="output hashes in requirements.txt (default is to guess)", -) - -no_include_hashes = Option( - "--no-include-hashes", dest="include_hashes", action="store_false", - help="do not output hashes in requirements.txt (default is to guess)", -) - -no_check = Option( - "--no-check", dest="check", action="store_false", default=True, - help="do not check if Pipfile.lock is up to date, always resolve", -) - -no_clean = Option( - "--no-clean", dest="clean", action="store_false", default=True, - help="do not remove packages not specified in Pipfile.lock", -) - -dev_only = Option( - "--dev", dest="only", action="store_const", const="dev", - help="only try to modify [dev-packages]", -) - -default_only = Option( - "--default", dest="only", action="store_const", const="default", - help="only try to modify [default]", -) - -strategy = Option( - "--strategy", choices=["eager", "only-if-needed"], default="only-if-needed", - help="how dependency upgrading is handled", -) - -include_hashes_group = ArgumentGroup("include_hashes", is_mutually_exclusive=True, options=[include_hashes, no_include_hashes]) -dev_group = ArgumentGroup("dev", is_mutually_exclusive="True", options=[dev_only, default_only]) -package_group = ArgumentGroup("packages", options=[packages, editable, dev, no_sync]) -new_project_group = ArgumentGroup("new-project", options=[new_project, python_version]) diff --git a/pipenv/vendor/passa/cli/remove.py b/pipenv/vendor/passa/cli/remove.py deleted file mode 100644 index 538acbf9..00000000 --- a/pipenv/vendor/passa/cli/remove.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, print_function, unicode_literals - -from ..actions.remove import remove -from ._base import BaseCommand -from .options import dev_group, no_clean, packages - - -class Command(BaseCommand): - - name = "remove" - description = "Remove packages from project." - arguments = [dev_group, no_clean, packages] - - def run(self, options): - return remove(project=options.project, only=options.only, - packages=options.packages, clean=options.clean) - - -if __name__ == "__main__": - Command.run_parser() diff --git a/pipenv/vendor/passa/cli/sync.py b/pipenv/vendor/passa/cli/sync.py deleted file mode 100644 index a09b7842..00000000 --- a/pipenv/vendor/passa/cli/sync.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, print_function, unicode_literals - -from ..actions.sync import sync -from ._base import BaseCommand -from .options import dev, no_clean - - -class Command(BaseCommand): - - name = "sync" - description = "Install Pipfile.lock into the environment." - arguments = [dev, no_clean] - - def run(self, options): - return sync(project=options.project, dev=options.dev, clean=options.clean) - - -if __name__ == "__main__": - Command.run_parser() diff --git a/pipenv/vendor/passa/cli/upgrade.py b/pipenv/vendor/passa/cli/upgrade.py deleted file mode 100644 index cf7f5021..00000000 --- a/pipenv/vendor/passa/cli/upgrade.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding=utf-8 -*- -from __future__ import absolute_import, print_function, unicode_literals - -from ..actions.upgrade import upgrade -from ._base import BaseCommand -from .options import no_clean, no_sync, packages, strategy - - -class Command(BaseCommand): - - name = "upgrade" - description = "Upgrade packages in project." - arguments = [packages, strategy, no_clean, no_sync] - - def run(self, options): - return upgrade(project=options.project, strategy=options.strategy, - sync=options.sync, packages=options.packages) - - -if __name__ == "__main__": - Command.run_parser() diff --git a/pipenv/vendor/passa/internals/_pip.py b/pipenv/vendor/passa/internals/_pip.py deleted file mode 100644 index 2aa143a2..00000000 --- a/pipenv/vendor/passa/internals/_pip.py +++ /dev/null @@ -1,397 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, unicode_literals - -import contextlib -import io -import itertools -import distutils.log -import os - -import distlib.database -import distlib.scripts -import distlib.wheel -import packaging.utils -import pip_shims -import setuptools.dist -import six -import vistir - -from ..models.caches import CACHE_DIR -from ._pip_shims import VCS_SUPPORT, build_wheel as _build_wheel, unpack_url -from .utils import filter_sources - - -@vistir.path.ensure_mkdir_p(mode=0o775) -def _get_src_dir(): - src = os.environ.get("PIP_SRC") - if src: - return src - virtual_env = os.environ.get("VIRTUAL_ENV") - if virtual_env: - return os.path.join(virtual_env, "src") - return os.path.join(os.getcwd(), "src") # Match pip's behavior. - - -def _prepare_wheel_building_kwargs(ireq): - download_dir = os.path.join(CACHE_DIR, "pkgs") - vistir.mkdir_p(download_dir) - - wheel_download_dir = os.path.join(CACHE_DIR, "wheels") - vistir.mkdir_p(wheel_download_dir) - - if ireq.source_dir is not None: - src_dir = ireq.source_dir - elif ireq.editable: - src_dir = _get_src_dir() - else: - src_dir = vistir.path.create_tracked_tempdir(prefix='passa-src') - - # This logic matches pip's behavior, although I don't fully understand the - # intention. I guess the idea is to build editables in-place, otherwise out - # of the source tree? - if ireq.editable: - build_dir = src_dir - else: - build_dir = vistir.path.create_tracked_tempdir(prefix="passa-build") - - return { - "build_dir": build_dir, - "src_dir": src_dir, - "download_dir": download_dir, - "wheel_download_dir": wheel_download_dir, - } - - -def _get_pip_index_urls(sources): - index_urls = [] - trusted_hosts = [] - for source in sources: - url = source.get("url") - if not url: - continue - index_urls.append(url) - if source.get("verify_ssl", True): - continue - host = six.moves.urllib.parse.urlparse(source["url"]).hostname - trusted_hosts.append(host) - return index_urls, trusted_hosts - - -class _PipCommand(pip_shims.Command): - name = "PipCommand" - - -def _get_pip_session(trusted_hosts): - cmd = _PipCommand() - options, _ = cmd.parser.parse_args([]) - options.cache_dir = CACHE_DIR - options.trusted_hosts = trusted_hosts - session = cmd._build_session(options) - return session - - -def _get_finder(sources): - index_urls, trusted_hosts = _get_pip_index_urls(sources) - session = _get_pip_session(trusted_hosts) - finder = pip_shims.PackageFinder( - find_links=[], - index_urls=index_urls, - trusted_hosts=trusted_hosts, - allow_all_prereleases=True, - session=session, - ) - return finder - - -def _get_wheel_cache(): - format_control = pip_shims.FormatControl(set(), set()) - wheel_cache = pip_shims.WheelCache(CACHE_DIR, format_control) - return wheel_cache - - -def _convert_hashes(values): - """Convert Pipfile.lock hash lines into InstallRequirement option format. - - The option format uses a str-list mapping. Keys are hash algorithms, and - the list contains all values of that algorithm. - """ - hashes = {} - if not values: - return hashes - for value in values: - try: - name, value = value.split(":", 1) - except ValueError: - name = "sha256" - if name not in hashes: - hashes[name] = [] - hashes[name].append(value) - return hashes - - -class WheelBuildError(RuntimeError): - pass - - -def build_wheel(ireq, sources, hashes=None): - """Build a wheel file for the InstallRequirement object. - - An artifact is downloaded (or read from cache). If the artifact is not a - wheel, build one out of it. The dynamically built wheel is ephemeral; do - not depend on its existence after the returned wheel goes out of scope. - - If `hashes` is truthy, it is assumed to be a list of hashes (as formatted - in Pipfile.lock) to be checked against the download. - - Returns a `distlib.wheel.Wheel` instance. Raises a `WheelBuildError` (a - `RuntimeError` subclass) if the wheel cannot be built. - """ - kwargs = _prepare_wheel_building_kwargs(ireq) - finder = _get_finder(sources) - - # Not for upgrade, hash not required. Hashes are not required here even - # when we provide them, because pip skips local wheel cache if we set it - # to True. Hashes are checked later if we need to download the file. - ireq.populate_link(finder, False, False) - - # Ensure ireq.source_dir is set. - # This is intentionally set to build_dir, not src_dir. Comments from pip: - # [...] if filesystem packages are not marked editable in a req, a non - # deterministic error occurs when the script attempts to unpack the - # build directory. - # Also see comments in `_prepare_wheel_building_kwargs()` -- If the ireq - # is editable, build_dir is actually src_dir, making the build in-place. - ireq.ensure_has_source_dir(kwargs["build_dir"]) - - # Ensure the source is fetched. For wheels, it is enough to just download - # because we'll use them directly. For an sdist, we need to unpack so we - # can build it. - if not ireq.editable or not pip_shims.is_file_url(ireq.link): - if ireq.is_wheel: - only_download = True - download_dir = kwargs["wheel_download_dir"] - else: - only_download = False - download_dir = kwargs["download_dir"] - ireq.options["hashes"] = _convert_hashes(hashes) - unpack_url( - ireq.link, ireq.source_dir, download_dir, - only_download=only_download, session=finder.session, - hashes=ireq.hashes(False), progress_bar="off", - ) - - if ireq.is_wheel: - # If this is a wheel, use the downloaded thing. - output_dir = kwargs["wheel_download_dir"] - wheel_path = os.path.join(output_dir, ireq.link.filename) - else: - # Othereise we need to build an ephemeral wheel. - wheel_path = _build_wheel( - ireq, vistir.path.create_tracked_tempdir(prefix="ephem"), - finder, _get_wheel_cache(), kwargs, - ) - if wheel_path is None or not os.path.exists(wheel_path): - raise WheelBuildError - return distlib.wheel.Wheel(wheel_path) - - -def _obtrain_ref(vcs_obj, src_dir, name, rev=None): - target_dir = os.path.join(src_dir, name) - target_rev = vcs_obj.make_rev_options(rev) - if not os.path.exists(target_dir): - vcs_obj.obtain(target_dir) - if (not vcs_obj.is_commit_id_equal(target_dir, rev) and - not vcs_obj.is_commit_id_equal(target_dir, target_rev)): - vcs_obj.update(target_dir, target_rev) - return vcs_obj.get_revision(target_dir) - - -def get_vcs_ref(requirement): - backend = VCS_SUPPORT.get_backend(requirement.vcs) - vcs = backend(url=requirement.req.vcs_uri) - src = _get_src_dir() - name = requirement.normalized_name - ref = _obtrain_ref(vcs, src, name, rev=requirement.req.ref) - return ref - - -def find_installation_candidates(ireq, sources): - finder = _get_finder(sources) - return finder.find_all_candidates(ireq.name) - - -class RequirementUninstaller(object): - """A context manager to remove a package for the inner block. - - This uses `UninstallPathSet` to control the workflow. If the inner block - exits correctly, the uninstallation is committed, otherwise rolled back. - """ - def __init__(self, ireq, auto_confirm, verbose): - self.ireq = ireq - self.pathset = None - self.auto_confirm = auto_confirm - self.verbose = verbose - - def __enter__(self): - self.pathset = self.ireq.uninstall( - auto_confirm=self.auto_confirm, - verbose=self.verbose, - ) - return self.pathset - - def __exit__(self, exc_type, exc_value, traceback): - if self.pathset is None: - return - if exc_type is None: - self.pathset.commit() - else: - self.pathset.rollback() - - -def uninstall(name, **kwargs): - ireq = pip_shims.InstallRequirement.from_line(name) - return RequirementUninstaller(ireq, **kwargs) - - -@contextlib.contextmanager -def _suppress_distutils_logs(): - """Hack to hide noise generated by `setup.py develop`. - - There isn't a good way to suppress them now, so let's monky-patch. - See https://bugs.python.org/issue25392. - """ - f = distutils.log.Log._log - - def _log(log, level, msg, args): - if level >= distutils.log.ERROR: - f(log, level, msg, args) - - distutils.log.Log._log = _log - yield - distutils.log.Log._log = f - - -class NoopInstaller(object): - """An installer. - - This class is not designed to be instantiated by itself, but used as a - common interface for subclassing. - - An installer has two methods, `prepare()` and `install()`. Neither takes - arguments, and should be called in that order to prepare an installation - operation, and to actually install things. - """ - def prepare(self): - pass - - def install(self): - pass - - -class EditableInstaller(NoopInstaller): - """Installer to handle editable. - """ - def __init__(self, requirement): - ireq = requirement.as_ireq() - self.working_directory = ireq.setup_py_dir - self.setup_py = ireq.setup_py - - def install(self): - with vistir.cd(self.working_directory), _suppress_distutils_logs(): - # Access from Setuptools to ensure things are patched correctly. - setuptools.dist.distutils.core.run_setup( - self.setup_py, ["develop", "--no-deps"], - ) - - -class WheelInstaller(NoopInstaller): - """Installer by building a wheel. - - The wheel is built during `prepare()`, and installed in `install()`. - """ - def __init__(self, requirement, sources, paths): - self.ireq = requirement.as_ireq() - self.sources = filter_sources(requirement, sources) - self.hashes = requirement.hashes or None - self.paths = paths - self.wheel = None - - def prepare(self): - self.wheel = build_wheel(self.ireq, self.sources, self.hashes) - - def install(self): - self.wheel.install(self.paths, distlib.scripts.ScriptMaker(None, None)) - - -def _iter_egg_info_directories(root, name): - name = packaging.utils.canonicalize_name(name) - for parent, dirnames, filenames in os.walk(root): - matched_indexes = [] - for i, dirname in enumerate(dirnames): - if not dirname.lower().endswith("egg-info"): - continue - egg_info_name = packaging.utils.canonicalize_name(dirname[:-9]) - if egg_info_name != name: - continue - matched_indexes.append(i) - yield os.path.join(parent, dirname) - - # Modify dirnames in-place to NOT look into egg-info directories. - # This is a documented behavior in stdlib. - for i in reversed(matched_indexes): - del dirnames[i] - - -def _read_pkg_info(directory): - path = os.path.join(directory, "PKG-INFO") - try: - with io.open(path, encoding="utf-8", errors="replace") as f: - return f.read() - except (IOError, OSError): - return None - - -def _find_egg_info(ireq): - """Find this package's .egg-info directory. - - Due to how sdists are designed, the .egg-info directory cannot be reliably - found without running setup.py to aggregate all configurations. This - function instead uses some heuristics to locate the egg-info directory - that most likely represents this package. - - The best .egg-info directory's path is returned as a string. None is - returned if no matches can be found. - """ - root = ireq.setup_py_dir - - directory_iterator = _iter_egg_info_directories(root, ireq.name) - try: - top_egg_info = next(directory_iterator) - except StopIteration: # No egg-info found. Wat. - return None - directory_iterator = itertools.chain([top_egg_info], directory_iterator) - - # Read the sdist's PKG-INFO to determine which egg_info is best. - pkg_info = _read_pkg_info(root) - - # PKG-INFO not readable. Just return whatever comes first, I guess. - if pkg_info is None: - return top_egg_info - - # Walk the sdist to find the egg-info with matching PKG-INFO. - for directory in directory_iterator: - egg_pkg_info = _read_pkg_info(directory) - if egg_pkg_info == pkg_info: - return directory - - # Nothing matches...? Use the first one we found, I guess. - return top_egg_info - - -def read_sdist_metadata(ireq): - egg_info_dir = _find_egg_info(ireq) - if not egg_info_dir: - return None - distribution = distlib.database.EggInfoDistribution(egg_info_dir) - return distribution.metadata diff --git a/pipenv/vendor/passa/internals/_pip_shims.py b/pipenv/vendor/passa/internals/_pip_shims.py deleted file mode 100644 index b2c7b6ea..00000000 --- a/pipenv/vendor/passa/internals/_pip_shims.py +++ /dev/null @@ -1,61 +0,0 @@ -# -*- coding=utf-8 -*- - -"""Shims to make the pip interface more consistent accross versions. - -There are currently two members: - -* VCS_SUPPORT is an instance of VcsSupport. -* build_wheel abstracts the process to build a wheel out of a bunch parameters. -* unpack_url wraps the actual function in pip to accept modern parameters. -""" - -from __future__ import absolute_import, unicode_literals - -import pip_shims - - -def _build_wheel_pre10(ireq, output_dir, finder, wheel_cache, kwargs): - kwargs.update({"wheel_cache": wheel_cache, "session": finder.session}) - reqset = pip_shims.RequirementSet(**kwargs) - builder = pip_shims.WheelBuilder(reqset, finder) - return builder._build_one(ireq, output_dir) - - -def _build_wheel_modern(ireq, output_dir, finder, wheel_cache, kwargs): - """Build a wheel. - - * ireq: The InstallRequirement object to build - * output_dir: The directory to build the wheel in. - * finder: pip's internal Finder object to find the source out of ireq. - * kwargs: Various keyword arguments from `_prepare_wheel_building_kwargs`. - """ - kwargs.update({"progress_bar": "off", "build_isolation": False}) - with pip_shims.RequirementTracker() as req_tracker: - if req_tracker: - kwargs["req_tracker"] = req_tracker - preparer = pip_shims.RequirementPreparer(**kwargs) - builder = pip_shims.WheelBuilder(finder, preparer, wheel_cache) - return builder._build_one(ireq, output_dir) - - -def _unpack_url_pre10(*args, **kwargs): - """Shim for unpack_url in various pip versions. - - pip before 10.0 does not accept `progress_bar` here. Simply drop it. - """ - kwargs.pop("progress_bar", None) - return pip_shims.unpack_url(*args, **kwargs) - - -PIP_VERSION = pip_shims.utils._parse(pip_shims.pip_version) -VERSION_10 = pip_shims.utils._parse("10") - - -VCS_SUPPORT = pip_shims.VcsSupport() - -build_wheel = _build_wheel_modern -unpack_url = pip_shims.unpack_url - -if PIP_VERSION < VERSION_10: - build_wheel = _build_wheel_pre10 - unpack_url = _unpack_url_pre10 diff --git a/pipenv/vendor/passa/internals/candidates.py b/pipenv/vendor/passa/internals/candidates.py deleted file mode 100644 index 67b09049..00000000 --- a/pipenv/vendor/passa/internals/candidates.py +++ /dev/null @@ -1,86 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, unicode_literals - -import packaging.specifiers -import packaging.version -import requirementslib - -from ._pip import find_installation_candidates, get_vcs_ref - - -def _filter_matching_python_requirement(candidates, required_python): - # TODO: This should also takes the parent's python_version and - # python_full_version markers, and only return matches with valid - # intersections. For example, if parent requires `python_version >= '3.0'`, - # this should not return entries with "Requires-Python: <3". - for c in candidates: - try: - requires_python = c.requires_python - except AttributeError: - requires_python = c.location.requires_python - if required_python and requires_python: - # Old specifications had people setting this to single digits - # which is effectively the same as '>=digit,<digit+1' - if requires_python.isdigit(): - requires_python = '>={0},<{1}'.format( - requires_python, int(requires_python) + 1, - ) - try: - specset = packaging.specifiers.SpecifierSet(requires_python) - except packaging.specifiers.InvalidSpecifier: - continue - if not specset.contains(required_python): - continue - yield c - - -def _copy_requirement(requirement): - # Markers are intentionally dropped here. They will be added to candidates - # after resolution, so we can perform marker aggregation. - new = requirement.copy() - new.markers = None - return new - - -def _requirement_from_metadata(name, version, extras, index): - # Markers are intentionally dropped here. They will be added to candidates - # after resolution, so we can perform marker aggregation. - r = requirementslib.Requirement.from_metadata(name, version, extras, None) - r.index = index - return r - - -def find_candidates(requirement, sources, requires_python, allow_prereleases): - # A non-named requirement has exactly one candidate that is itself. For - # VCS, we also lock the requirement to an exact ref. - if not requirement.is_named: - candidate = _copy_requirement(requirement) - if candidate.is_vcs: - candidate.req.ref = get_vcs_ref(candidate) - return [candidate] - - ireq = requirement.as_ireq() - icans = find_installation_candidates(ireq, sources) - - if requires_python: - matching_icans = list(_filter_matching_python_requirement( - icans, packaging.version.parse(requires_python), - )) - icans = matching_icans or icans - - versions = sorted(ireq.specifier.filter( - (c.version for c in icans), allow_prereleases, - )) - if not allow_prereleases and not versions: - versions = sorted(ireq.specifier.filter( - (c.version for c in icans), True, - )) - - name = requirement.normalized_name - extras = requirement.extras - index = requirement.index - return [ - _requirement_from_metadata(name, version, extras, index) - for version in versions - ] diff --git a/pipenv/vendor/passa/internals/dependencies.py b/pipenv/vendor/passa/internals/dependencies.py deleted file mode 100644 index 358cc33b..00000000 --- a/pipenv/vendor/passa/internals/dependencies.py +++ /dev/null @@ -1,273 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, unicode_literals - -import functools -import os -import sys - -import packaging.specifiers -import packaging.utils -import packaging.version -import requests -import requirementslib -import six - -from ..models.caches import DependencyCache, RequiresPythonCache -from ._pip import WheelBuildError, build_wheel, read_sdist_metadata -from .markers import contains_extra, get_contained_extras, get_without_extra -from .utils import get_pinned_version, is_pinned - - -DEPENDENCY_CACHE = DependencyCache() -REQUIRES_PYTHON_CACHE = RequiresPythonCache() - - -def _cached(f, **kwargs): - - @functools.wraps(f) - def wrapped(ireq): - result = f(ireq, **kwargs) - if result is not None and is_pinned(ireq): - deps, requires_python = result - DEPENDENCY_CACHE[ireq] = deps - REQUIRES_PYTHON_CACHE[ireq] = requires_python - return result - - return wrapped - - -def _is_cache_broken(line, parent_name): - dep_req = requirementslib.Requirement.from_line(line) - if contains_extra(dep_req.markers): - return True # The "extra =" marker breaks everything. - elif dep_req.normalized_name == parent_name: - return True # A package cannot depend on itself. - return False - - -def _get_dependencies_from_cache(ireq): - """Retrieves dependencies for the requirement from the dependency cache. - """ - if os.environ.get("PASSA_IGNORE_LOCAL_CACHE"): - return - if ireq.editable: - return - try: - deps = DEPENDENCY_CACHE[ireq] - pyrq = REQUIRES_PYTHON_CACHE[ireq] - except KeyError: - return - - # Preserving sanity: Run through the cache and make sure every entry if - # valid. If this fails, something is wrong with the cache. Drop it. - try: - packaging.specifiers.SpecifierSet(pyrq) - ireq_name = packaging.utils.canonicalize_name(ireq.name) - if any(_is_cache_broken(line, ireq_name) for line in deps): - broken = True - else: - broken = False - except Exception: - broken = True - - if broken: - print("dropping broken cache for {0}".format(ireq.name)) - del DEPENDENCY_CACHE[ireq] - del REQUIRES_PYTHON_CACHE[ireq] - return - - return deps, pyrq - - -def _get_dependencies_from_json_url(url, session): - response = session.get(url) - response.raise_for_status() - info = response.json()["info"] - - requires_python = info["requires_python"] or "" - try: - requirement_lines = info["requires_dist"] - except KeyError: - requirement_lines = info["requires"] - - # The JSON API returns null both when there are not requirements, or the - # requirement list cannot be retrieved. We can't safely assume, so it's - # better to drop it and fall back to downloading the package. - try: - dependency_requirements_iterator = ( - requirementslib.Requirement.from_line(line) - for line in requirement_lines - ) - except TypeError: - return - - dependencies = [ - dep_req.as_line(include_hashes=False) - for dep_req in dependency_requirements_iterator - if not contains_extra(dep_req.markers) - ] - return dependencies, requires_python - - -def _get_dependencies_from_json(ireq, sources): - """Retrieves dependencies for the install requirement from the JSON API. - - :param ireq: A single InstallRequirement - :type ireq: :class:`~pip._internal.req.req_install.InstallRequirement` - :return: A set of dependency lines for generating new InstallRequirements. - :rtype: set(str) or None - """ - if os.environ.get("PASSA_IGNORE_JSON_API"): - return - - # It is technically possible to parse extras out of the JSON API's - # requirement format, but it is such a chore let's just use the simple API. - if ireq.extras: - return - - try: - version = get_pinned_version(ireq) - except ValueError: - return - - url_prefixes = [ - proc_url[:-7] # Strip "/simple". - for proc_url in ( - raw_url.rstrip("/") - for raw_url in (source.get("url", "") for source in sources) - ) - if proc_url.endswith("/simple") - ] - - session = requests.session() - - for prefix in url_prefixes: - url = "{prefix}/pypi/{name}/{version}/json".format( - prefix=prefix, - name=packaging.utils.canonicalize_name(ireq.name), - version=version, - ) - try: - dependencies = _get_dependencies_from_json_url(url, session) - if dependencies is not None: - return dependencies - except Exception as e: - print("unable to read dependencies via {0} ({1})".format(url, e)) - session.close() - return - - -def _read_requirements(metadata, extras): - """Read wheel metadata to know what it depends on. - - The `run_requires` attribute contains a list of dict or str specifying - requirements. For dicts, it may contain an "extra" key to specify these - requirements are for a specific extra. Unfortunately, not all fields are - specificed like this (I don't know why); some are specified with markers. - So we jump though these terrible hoops to know exactly what we need. - - The extra extraction is not comprehensive. Tt assumes the marker is NEVER - something like `extra == "foo" and extra == "bar"`. I guess this never - makes sense anyway? Markers are just terrible. - """ - extras = extras or () - requirements = [] - for entry in metadata.run_requires: - if isinstance(entry, six.text_type): - entry = {"requires": [entry]} - extra = None - else: - extra = entry.get("extra") - if extra is not None and extra not in extras: - continue - for line in entry.get("requires", []): - r = requirementslib.Requirement.from_line(line) - if r.markers: - contained = get_contained_extras(r.markers) - if (contained and not any(e in contained for e in extras)): - continue - marker = get_without_extra(r.markers) - r.markers = str(marker) if marker else None - line = r.as_line(include_hashes=False) - requirements.append(line) - return requirements - - -def _read_requires_python(metadata): - """Read wheel metadata to know the value of Requires-Python. - - This is surprisingly poorly supported in Distlib. This function tries - several ways to get this information: - - * Metadata 2.0: metadata.dictionary.get("requires_python") is not None - * Metadata 2.1: metadata._legacy.get("Requires-Python") is not None - * Metadata 1.2: metadata._legacy.get("Requires-Python") != "UNKNOWN" - """ - # TODO: Support more metadata formats. - value = metadata.dictionary.get("requires_python") - if value is not None: - return value - if metadata._legacy: - value = metadata._legacy.get("Requires-Python") - if value is not None and value != "UNKNOWN": - return value - return "" - - -def _get_dependencies_from_pip(ireq, sources): - """Retrieves dependencies for the requirement from pipenv.patched.notpip internals. - - The current strategy is to try the followings in order, returning the - first successful result. - - 1. Try to build a wheel out of the ireq, and read metadata out of it. - 2. Read metadata out of the egg-info directory if it is present. - """ - extras = ireq.extras or () - try: - wheel = build_wheel(ireq, sources) - except WheelBuildError: - # XXX: This depends on a side effect of `build_wheel`. This block is - # reached when it fails to build an sdist, where the sdist would have - # been downloaded, extracted into `ireq.source_dir`, and partially - # built (hopefully containing .egg-info). - metadata = read_sdist_metadata(ireq) - if not metadata: - raise - else: - metadata = wheel.metadata - requirements = _read_requirements(metadata, extras) - requires_python = _read_requires_python(metadata) - return requirements, requires_python - - -def get_dependencies(requirement, sources): - """Get all dependencies for a given install requirement. - - :param requirement: A requirement - :param sources: Pipfile-formatted sources - :type sources: list[dict] - """ - getters = [ - _get_dependencies_from_cache, - _cached(_get_dependencies_from_json, sources=sources), - _cached(_get_dependencies_from_pip, sources=sources), - ] - ireq = requirement.as_ireq() - last_exc = None - for getter in getters: - try: - result = getter(ireq) - except Exception as e: - last_exc = sys.exc_info() - continue - if result is not None: - deps, pyreq = result - reqs = [requirementslib.Requirement.from_line(d) for d in deps] - return reqs, pyreq - if last_exc: - six.reraise(*last_exc) - raise RuntimeError("failed to get dependencies for {}".format( - requirement.as_line(), - )) diff --git a/pipenv/vendor/passa/internals/hashes.py b/pipenv/vendor/passa/internals/hashes.py deleted file mode 100644 index fe049274..00000000 --- a/pipenv/vendor/passa/internals/hashes.py +++ /dev/null @@ -1,61 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, unicode_literals - -import contextlib - -from pip_shims import Wheel - - -def _wheel_supported(self, tags=None): - # Ignore current platform. Support everything. - return True - - -def _wheel_support_index_min(self, tags=None): - # All wheels are equal priority for sorting. - return 0 - - -@contextlib.contextmanager -def _allow_all_wheels(): - """Monkey patch pip.Wheel to allow all wheels - - The usual checks against platforms and Python versions are ignored to allow - fetching all available entries in PyPI. This also saves the candidate cache - and set a new one, or else the results from the previous non-patched calls - will interfere. - """ - original_wheel_supported = Wheel.supported - original_support_index_min = Wheel.support_index_min - - Wheel.supported = _wheel_supported - Wheel.support_index_min = _wheel_support_index_min - yield - Wheel.supported = original_wheel_supported - Wheel.support_index_min = original_support_index_min - - -def get_hashes(cache, req): - if req.is_vcs: - return set() - - ireq = req.as_ireq() - - if ireq.editable: - return set() - - if req.is_file_or_url: - # TODO: Get the hash of the linked artifact? - return set() - - if not ireq.is_pinned: - return set() - - with _allow_all_wheels(): - matching_candidates = req.find_all_matches() - - return { - cache.get_hash(candidate.location) - for candidate in matching_candidates - } diff --git a/pipenv/vendor/passa/internals/markers.py b/pipenv/vendor/passa/internals/markers.py deleted file mode 100644 index 95efab95..00000000 --- a/pipenv/vendor/passa/internals/markers.py +++ /dev/null @@ -1,101 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, unicode_literals - -from packaging.markers import Marker - - -def _strip_extra(elements): - """Remove the "extra == ..." operands from the list. - - This is not a comprehensive implementation, but relies on an important - characteristic of metadata generation: The "extra == ..." operand is always - associated with an "and" operator. This means that we can simply remove the - operand and the "and" operator associated with it. - """ - extra_indexes = [] - for i, element in enumerate(elements): - if isinstance(element, list): - cancelled = _strip_extra(element) - if cancelled: - extra_indexes.append(i) - elif isinstance(element, tuple) and element[0].value == "extra": - extra_indexes.append(i) - for i in reversed(extra_indexes): - del elements[i] - if i > 0 and elements[i - 1] == "and": - # Remove the "and" before it. - del elements[i - 1] - elif elements: - # This shouldn't ever happen, but is included for completeness. - # If there is not an "and" before this element, try to remove the - # operator after it. - del elements[0] - return (not elements) - - -def get_without_extra(marker): - """Build a new marker without the `extra == ...` part. - - The implementation relies very deep into packaging's internals, but I don't - have a better way now (except implementing the whole thing myself). - - This could return `None` if the `extra == ...` part is the only one in the - input marker. - """ - # TODO: Why is this very deep in the internals? Why is a better solution - # implementing it yourself when someone is already maintaining a codebase - # for this? It's literally a grammar implementation that is required to - # meet the demands of a pep... -d - if not marker: - return None - marker = Marker(str(marker)) - elements = marker._markers - _strip_extra(elements) - if elements: - return marker - return None - - -def _markers_collect_extras(markers, collection): - # Optimization: the marker element is usually appended at the end. - for el in reversed(markers): - if (isinstance(el, tuple) and - el[0].value == "extra" and - el[1].value == "=="): - collection.add(el[2].value) - elif isinstance(el, list): - _markers_collect_extras(el, collection) - - -def get_contained_extras(marker): - """Collect "extra == ..." operands from a marker. - - Returns a list of str. Each str is a speficied extra in this marker. - """ - if not marker: - return set() - marker = Marker(str(marker)) - extras = set() - _markers_collect_extras(marker._markers, extras) - return extras - - -def _markers_contains_extra(markers): - # Optimization: the marker element is usually appended at the end. - for element in reversed(markers): - if isinstance(element, tuple) and element[0].value == "extra": - return True - elif isinstance(element, list): - if _markers_contains_extra(element): - return True - return False - - -def contains_extra(marker): - """Check whehter a marker contains an "extra == ..." operand. - """ - if not marker: - return False - marker = Marker(str(marker)) - return _markers_contains_extra(marker._markers) diff --git a/pipenv/vendor/passa/internals/reporters.py b/pipenv/vendor/passa/internals/reporters.py deleted file mode 100644 index 4fe6c0b8..00000000 --- a/pipenv/vendor/passa/internals/reporters.py +++ /dev/null @@ -1,90 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, print_function, unicode_literals - -import resolvelib - -from .traces import trace_graph - - -def print_title(text): - print('\n{:=^84}\n'.format(text)) - - -def print_requirement(r, end='\n'): - print('{:>40}'.format(r.as_line(include_hashes=False)), end=end) - - -def print_dependency(state, key): - print_requirement(state.mapping[key], end='') - parents = sorted( - state.graph.iter_parents(key), - key=lambda n: (-1, '') if n is None else (ord(n[0].lower()), n), - ) - for i, p in enumerate(parents): - if p is None: - line = '(user)' - else: - line = state.mapping[p].as_line(include_hashes=False) - if i == 0: - padding = ' <= ' - else: - padding = ' ' * 44 - print('{pad}{line}'.format(pad=padding, line=line)) - - -class StdOutReporter(resolvelib.BaseReporter): - """Simple reporter that prints things to stdout. - """ - def __init__(self, requirements): - super(StdOutReporter, self).__init__() - self.requirements = requirements - - def starting(self): - self._prev = None - print_title(' User requirements ') - for r in self.requirements: - print_requirement(r) - - def ending_round(self, index, state): - print_title(' Round {} '.format(index)) - mapping = state.mapping - if self._prev is None: - difference = set(mapping.keys()) - changed = set() - else: - difference = set(mapping.keys()) - set(self._prev.keys()) - changed = set( - k for k, v in mapping.items() - if k in self._prev and self._prev[k] != v - ) - self._prev = mapping - - if difference: - print('New pins: ') - for k in difference: - print_dependency(state, k) - print() - - if changed: - print('Changed pins:') - for k in changed: - print_dependency(state, k) - print() - - def ending(self, state): - print_title(" STABLE PINS ") - path_lists = trace_graph(state.graph) - for k in sorted(state.mapping): - print(state.mapping[k].as_line(include_hashes=False)) - paths = path_lists[k] - for path in paths: - if path == [None]: - print(' User requirement') - continue - print(' ', end='') - for v in reversed(path[1:]): - line = state.mapping[v].as_line(include_hashes=False) - print(' <=', line, end='') - print() - print() diff --git a/pipenv/vendor/passa/internals/specifiers.py b/pipenv/vendor/passa/internals/specifiers.py deleted file mode 100644 index 75afb6ad..00000000 --- a/pipenv/vendor/passa/internals/specifiers.py +++ /dev/null @@ -1,136 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, unicode_literals - -import itertools -import operator - -from packaging.specifiers import SpecifierSet, Specifier -from vistir.misc import dedup - - -def _tuplize_version(version): - return tuple(int(x) for x in version.split(".")) - - -def _format_version(version): - return ".".join(str(i) for i in version) - - -# Prefer [x,y) ranges. -REPLACE_RANGES = {">": ">=", "<=": "<"} - - -def _format_pyspec(specifier): - if isinstance(specifier, str): - if not any(op in specifier for op in Specifier._operators.keys()): - specifier = "=={0}".format(specifier) - specifier = Specifier(specifier) - if specifier.operator == "==" and specifier.version.endswith(".*"): - specifier = Specifier("=={0}".format(specifier.version[:-2])) - try: - op = REPLACE_RANGES[specifier.operator] - except KeyError: - return specifier - version = specifier.version.replace(".*", "") - curr_tuple = _tuplize_version(version) - try: - next_tuple = (curr_tuple[0], curr_tuple[1] + 1) - except IndexError: - next_tuple = (curr_tuple[0], 1) - specifier = Specifier("{0}{1}".format(op, _format_version(next_tuple))) - return specifier - - -def _get_specs(specset): - if isinstance(specset, Specifier): - specset = str(specset) - if isinstance(specset, str): - specset = SpecifierSet(specset.replace(".*", "")) - return [ - (spec._spec[0], _tuplize_version(spec._spec[1])) - for spec in getattr(specset, "_specs", []) - ] - - -def _group_by_op(specs): - specs = [_get_specs(x) for x in list(specs)] - flattened = [(op, version) for spec in specs for op, version in spec] - specs = sorted(flattened, key=operator.itemgetter(1)) - grouping = itertools.groupby(specs, key=operator.itemgetter(0)) - return grouping - - -def cleanup_pyspecs(specs, joiner="or"): - specs = {_format_pyspec(spec) for spec in specs} - # for != operator we want to group by version - # if all are consecutive, join as a list - results = set() - for op, versions in _group_by_op(specs): - versions = [version[1] for version in versions] - versions = sorted(dedup(versions)) - # if we are doing an or operation, we need to use the min for >= - # this way OR(>=2.6, >=2.7, >=3.6) picks >=2.6 - # if we do an AND operation we need to use MAX to be more selective - if op in (">", ">="): - if joiner == "or": - results.add((op, _format_version(min(versions)))) - else: - results.add((op, _format_version(max(versions)))) - # we use inverse logic here so we will take the max value if we are - # using OR but the min value if we are using AND - elif op in ("<=", "<"): - if joiner == "or": - results.add((op, _format_version(max(versions)))) - else: - results.add((op, _format_version(min(versions)))) - # leave these the same no matter what operator we use - elif op in ("!=", "==", "~="): - version_list = sorted( - "{0}".format(_format_version(version)) - for version in versions - ) - version = ", ".join(version_list) - if len(version_list) == 1: - results.add((op, version)) - elif op == "!=": - results.add(("not in", version)) - elif op == "==": - results.add(("in", version)) - else: - specifier = SpecifierSet(",".join(sorted( - "{0}".format(op, v) for v in version_list - )))._specs - for s in specifier: - results &= (specifier._spec[0], specifier._spec[1]) - else: - if len(version) == 1: - results.add((op, version)) - else: - specifier = SpecifierSet("{0}".format(version))._specs - for s in specifier: - results |= (specifier._spec[0], specifier._spec[1]) - return results - - -def pyspec_from_markers(marker): - if marker._markers[0][0] != 'python_version': - return - op = marker._markers[0][1].value - version = marker._markers[0][2].value - specset = set() - if op == "in": - specset.update( - Specifier("=={0}".format(v.strip())) - for v in version.split(",") - ) - elif op == "not in": - specset.update( - Specifier("!={0}".format(v.strip())) - for v in version.split(",") - ) - else: - specset.add(Specifier("".join([op, version]))) - if specset: - return specset - return None diff --git a/pipenv/vendor/passa/internals/traces.py b/pipenv/vendor/passa/internals/traces.py deleted file mode 100644 index 9715db97..00000000 --- a/pipenv/vendor/passa/internals/traces.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, unicode_literals - - -def _trace_visit_vertex(graph, current, target, visited, path, paths): - if current == target: - paths.append(path) - return - for v in graph.iter_children(current): - if v == current or v in visited: - continue - next_path = path + [current] - next_visited = visited | {current} - _trace_visit_vertex(graph, v, target, next_visited, next_path, paths) - - -def trace_graph(graph): - """Build a collection of "traces" for each package. - - A trace is a list of names that eventually leads to the package. For - example, if A and B are root dependencies, A depends on C and D, B - depends on C, and C depends on D, the return value would be like:: - - { - None: [], - "A": [None], - "B": [None], - "C": [[None, "A"], [None, "B"]], - "D": [[None, "B", "C"], [None, "A"]], - } - """ - result = {None: []} - for vertex in graph: - result[vertex] = [] - for root in graph.iter_children(None): - paths = [] - _trace_visit_vertex(graph, root, vertex, {None}, [None], paths) - result[vertex].extend(paths) - return result diff --git a/pipenv/vendor/passa/internals/utils.py b/pipenv/vendor/passa/internals/utils.py deleted file mode 100644 index 8f8e6fd0..00000000 --- a/pipenv/vendor/passa/internals/utils.py +++ /dev/null @@ -1,118 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, unicode_literals - - -def identify_requirment(r): - """Produce an identifier for a requirement to use in the resolver. - - Note that we are treating the same package with different extras as - distinct. This allows semantics like "I only want this extra in - development, not production". - - This also makes the resolver's implementation much simpler, with the minor - costs of possibly needing a few extra resolution steps if we happen to have - the same package apprearing multiple times. - """ - return "{0}{1}".format(r.normalized_name, r.extras_as_pip) - - -def get_pinned_version(ireq): - """Get the pinned version of an InstallRequirement. - - An InstallRequirement is considered pinned if: - - - Is not editable - - It has exactly one specifier - - That specifier is "==" - - The version does not contain a wildcard - - Examples: - django==1.8 # pinned - django>1.8 # NOT pinned - django~=1.8 # NOT pinned - django==1.* # NOT pinned - - Raises `TypeError` if the input is not a valid InstallRequirement, or - `ValueError` if the InstallRequirement is not pinned. - """ - try: - specifier = ireq.specifier - except AttributeError: - raise TypeError("Expected InstallRequirement, not {}".format( - type(ireq).__name__, - )) - - if ireq.editable: - raise ValueError("InstallRequirement is editable") - if not specifier: - raise ValueError("InstallRequirement has no version specification") - if len(specifier._specs) != 1: - raise ValueError("InstallRequirement has multiple specifications") - - op, version = next(iter(specifier._specs))._spec - if op not in ('==', '===') or version.endswith('.*'): - raise ValueError("InstallRequirement not pinned (is {0!r})".format( - op + version, - )) - - return version - - -def is_pinned(ireq): - """Returns whether an InstallRequirement is a "pinned" requirement. - - An InstallRequirement is considered pinned if: - - - Is not editable - - It has exactly one specifier - - That specifier is "==" - - The version does not contain a wildcard - - Examples: - django==1.8 # pinned - django>1.8 # NOT pinned - django~=1.8 # NOT pinned - django==1.* # NOT pinned - """ - try: - get_pinned_version(ireq) - except (TypeError, ValueError): - return False - return True - - -def filter_sources(requirement, sources): - """Returns a filtered list of sources for this requirement. - - This considers the index specified by the requirement, and returns only - matching source entries if there is at least one. - """ - if not sources or not requirement.index: - return sources - filtered_sources = [ - source for source in sources - if source.get("name") == requirement.index - ] - return filtered_sources or sources - - -def get_allow_prereleases(requirement, global_setting): - # TODO: Implement per-package prereleases flag. (pypa/pipenv#1696) - return global_setting - - -def are_requirements_equal(this, that): - return ( - this.as_line(include_hashes=False) == - that.as_line(include_hashes=False) - ) - - -def strip_extras(requirement): - """Returns a new requirement object with extras removed. - """ - line = requirement.as_line() - new = type(requirement).from_line(line) - new.extras = None - return new diff --git a/pipenv/vendor/passa/models/caches.py b/pipenv/vendor/passa/models/caches.py deleted file mode 100644 index c6d29b5e..00000000 --- a/pipenv/vendor/passa/models/caches.py +++ /dev/null @@ -1,214 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, unicode_literals - -import copy -import hashlib -import json -import os -import sys - -import appdirs -import pip_shims -import requests -import vistir - -from ..internals._pip_shims import VCS_SUPPORT -from ..internals.utils import get_pinned_version - - -CACHE_DIR = os.environ.get("PASSA_CACHE_DIR", appdirs.user_cache_dir("passa")) - - -class HashCache(pip_shims.SafeFileCache): - """Caches hashes of PyPI artifacts so we do not need to re-download them. - - Hashes are only cached when the URL appears to contain a hash in it and the - cache key includes the hash value returned from the server). This ought to - avoid ssues where the location on the server changes. - """ - def __init__(self, *args, **kwargs): - session = kwargs.pop('session', requests.session()) - self.session = session - kwargs.setdefault('directory', os.path.join(CACHE_DIR, 'hash-cache')) - super(HashCache, self).__init__(*args, **kwargs) - - def get_hash(self, location): - # If there is no location hash (i.e., md5, sha256, etc.), we don't want - # to store it. - hash_value = None - orig_scheme = location.scheme - new_location = copy.deepcopy(location) - if orig_scheme in VCS_SUPPORT.all_schemes: - new_location.url = new_location.url.split("+", 1)[-1] - can_hash = new_location.hash - if can_hash: - # hash url WITH fragment - hash_value = self.get(new_location.url) - if not hash_value: - hash_value = self._get_file_hash(new_location) - hash_value = hash_value.encode('utf8') - if can_hash: - self.set(new_location.url, hash_value) - return hash_value.decode('utf8') - - def _get_file_hash(self, location): - h = hashlib.new(pip_shims.FAVORITE_HASH) - with vistir.open_file(location, self.session) as fp: - for chunk in iter(lambda: fp.read(8096), b""): - h.update(chunk) - return ":".join([h.name, h.hexdigest()]) - - -# pip-tools's dependency cache implementation. -class CorruptCacheError(Exception): - def __init__(self, path): - self.path = path - - def __str__(self): - lines = [ - 'The dependency cache seems to have been corrupted.', - 'Inspect, or delete, the following file:', - ' {}'.format(self.path), - ] - return os.linesep.join(lines) - - -def _key_from_req(req): - """Get an all-lowercase version of the requirement's name.""" - if hasattr(req, 'key'): - # from pkg_resources, such as installed dists for pip-sync - key = req.key - else: - # from packaging, such as install requirements from requirements.txt - key = req.name - - key = key.replace('_', '-').lower() - return key - - -def _read_cache_file(cache_file_path): - with open(cache_file_path, 'r') as cache_file: - try: - doc = json.load(cache_file) - except ValueError: - raise CorruptCacheError(cache_file_path) - - # Check version and load the contents - assert doc['__format__'] == 1, 'Unknown cache file format' - return doc['dependencies'] - - -class _JSONCache(object): - """A persistent cache backed by a JSON file. - - The cache file is written to the appropriate user cache dir for the - current platform, i.e. - - ~/.cache/pip-tools/depcache-pyX.Y.json - - Where X.Y indicates the Python version. - """ - filename_format = None - - def __init__(self, cache_dir=CACHE_DIR): - vistir.mkdir_p(cache_dir) - python_version = ".".join(str(digit) for digit in sys.version_info[:2]) - cache_filename = self.filename_format.format( - python_version=python_version, - ) - self._cache_file = os.path.join(cache_dir, cache_filename) - self._cache = None - - @property - def cache(self): - """The dictionary that is the actual in-memory cache. - - This property lazily loads the cache from disk. - """ - if self._cache is None: - self.read_cache() - return self._cache - - def as_cache_key(self, ireq): - """Given a requirement, return its cache key. - - This behavior is a little weird in order to allow backwards - compatibility with cache files. For a requirement without extras, this - will return, for example:: - - ("ipython", "2.1.0") - - For a requirement with extras, the extras will be comma-separated and - appended to the version, inside brackets, like so:: - - ("ipython", "2.1.0[nbconvert,notebook]") - """ - extras = tuple(sorted(ireq.extras)) - if not extras: - extras_string = "" - else: - extras_string = "[{}]".format(",".join(extras)) - name = _key_from_req(ireq.req) - version = get_pinned_version(ireq) - return name, "{}{}".format(version, extras_string) - - def read_cache(self): - """Reads the cached contents into memory. - """ - if os.path.exists(self._cache_file): - self._cache = _read_cache_file(self._cache_file) - else: - self._cache = {} - - def write_cache(self): - """Writes the cache to disk as JSON. - """ - doc = { - '__format__': 1, - 'dependencies': self._cache, - } - with open(self._cache_file, 'w') as f: - json.dump(doc, f, sort_keys=True) - - def clear(self): - self._cache = {} - self.write_cache() - - def __contains__(self, ireq): - pkgname, pkgversion_and_extras = self.as_cache_key(ireq) - return pkgversion_and_extras in self.cache.get(pkgname, {}) - - def __getitem__(self, ireq): - pkgname, pkgversion_and_extras = self.as_cache_key(ireq) - return self.cache[pkgname][pkgversion_and_extras] - - def __setitem__(self, ireq, values): - pkgname, pkgversion_and_extras = self.as_cache_key(ireq) - self.cache.setdefault(pkgname, {}) - self.cache[pkgname][pkgversion_and_extras] = values - self.write_cache() - - def __delitem__(self, ireq): - pkgname, pkgversion_and_extras = self.as_cache_key(ireq) - try: - del self.cache[pkgname][pkgversion_and_extras] - except KeyError: - return - self.write_cache() - - def get(self, ireq, default=None): - pkgname, pkgversion_and_extras = self.as_cache_key(ireq) - return self.cache.get(pkgname, {}).get(pkgversion_and_extras, default) - - -class DependencyCache(_JSONCache): - """Cache the dependency of cancidates. - """ - filename_format = "depcache-py{python_version}.json" - - -class RequiresPythonCache(_JSONCache): - """Cache a candidate's Requires-Python information. - """ - filename_format = "pyreqcache-py{python_version}.json" diff --git a/pipenv/vendor/passa/models/lockers.py b/pipenv/vendor/passa/models/lockers.py deleted file mode 100644 index c25ca60d..00000000 --- a/pipenv/vendor/passa/models/lockers.py +++ /dev/null @@ -1,214 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, unicode_literals - -import itertools - -import resolvelib - -import plette -import requirementslib -import vistir - -from ..internals.hashes import get_hashes -from ..internals.reporters import StdOutReporter -from ..internals.traces import trace_graph -from ..internals.utils import identify_requirment -from .caches import HashCache -from .metadata import set_metadata -from .providers import BasicProvider, EagerUpgradeProvider, PinReuseProvider - - -def _get_requirements(model, section_name): - """Produce a mapping of identifier: requirement from the section. - """ - if not model: - return {} - return {identify_requirment(r): r for r in ( - requirementslib.Requirement.from_pipfile(name, package._data) - for name, package in model.get(section_name, {}).items() - )} - - -def _get_requires_python(pipfile): - try: - requires = pipfile.requires - except AttributeError: - return "" - try: - return requires.python_full_version - except AttributeError: - pass - try: - return requires.python_version - except AttributeError: - return "" - - -def _collect_derived_entries(state, traces, identifiers): - """Produce a mapping containing all candidates derived from `identifiers`. - - `identifiers` should provide a collection of requirement identifications - from a section (i.e. `packages` or `dev-packages`). This function uses - `trace` to filter out candidates in the state that are present because of - an entry in that collection. - """ - identifiers = set(identifiers) - if not identifiers: - return {} - - entries = {} - extras = {} - for identifier, requirement in state.mapping.items(): - routes = {trace[1] for trace in traces[identifier] if len(trace) > 1} - if identifier not in identifiers and not (identifiers & routes): - continue - name = requirement.normalized_name - if requirement.extras: - # Aggregate extras from multiple routes so we can produce their - # union in the lock file. (sarugaku/passa#24) - try: - extras[name].extend(requirement.extras) - except KeyError: - extras[name] = list(requirement.extras) - entries[name] = next(iter(requirement.as_pipfile().values())) - for name, ext in extras.items(): - entries[name]["extras"] = ext - - return entries - - -class AbstractLocker(object): - """Helper class to produce a new lock file for a project. - - This is not intended for instantiation. You should use one of its concrete - subclasses instead. The class contains logic to: - - * Prepare a project for locking - * Perform the actually resolver invocation - * Convert resolver output into lock file format - * Update the project to have the new lock file - """ - def __init__(self, project): - self.project = project - self.default_requirements = _get_requirements( - project.pipfile, "packages", - ) - self.develop_requirements = _get_requirements( - project.pipfile, "dev-packages", - ) - - # This comprehension dance ensures we merge packages from both - # sections, and definitions in the default section win. - self.requirements = {k: r for k, r in itertools.chain( - self.develop_requirements.items(), - self.default_requirements.items(), - )}.values() - - self.sources = [s._data.copy() for s in project.pipfile.sources] - self.allow_prereleases = bool( - project.pipfile.get("pipenv", {}).get("allow_prereleases", False), - ) - self.requires_python = _get_requires_python(project.pipfile) - - def __repr__(self): - return "<{0} @ {1!r}>".format(type(self).__name__, self.project.root) - - def get_provider(self): - raise NotImplementedError - - def get_reporter(self): - # TODO: Build SpinnerReporter, and use this only in verbose mode. - return StdOutReporter(self.requirements) - - def lock(self): - """Lock specified (abstract) requirements into (concrete) candidates. - - The locking procedure consists of four stages: - - * Resolve versions and dependency graph (powered by ResolveLib). - * Walk the graph to determine "why" each candidate came to be, i.e. - what top-level requirements result in a given candidate. - * Populate hashes for resolved candidates. - * Populate markers based on dependency specifications of each - candidate, and the dependency graph. - """ - provider = self.get_provider() - reporter = self.get_reporter() - resolver = resolvelib.Resolver(provider, reporter) - - with vistir.cd(self.project.root): - state = resolver.resolve(self.requirements) - - traces = trace_graph(state.graph) - - hash_cache = HashCache() - for r in state.mapping.values(): - if not r.hashes: - r.hashes = get_hashes(hash_cache, r) - - set_metadata( - state.mapping, traces, - provider.fetched_dependencies, - provider.collected_requires_pythons, - ) - - lockfile = plette.Lockfile.with_meta_from(self.project.pipfile) - lockfile["default"] = _collect_derived_entries( - state, traces, self.default_requirements, - ) - lockfile["develop"] = _collect_derived_entries( - state, traces, self.develop_requirements, - ) - self.project.lockfile = lockfile - - -class BasicLocker(AbstractLocker): - """Basic concrete locker. - - This takes a project, generates a lock file from its Pipfile, and sets - the lock file property to the project. - """ - def get_provider(self): - return BasicProvider( - self.requirements, self.sources, - self.requires_python, self.allow_prereleases, - ) - - -class PinReuseLocker(AbstractLocker): - """A specialized locker to handle re-locking based on existing pins. - - See :class:`.providers.PinReuseProvider` for more information. - """ - def __init__(self, project): - super(PinReuseLocker, self).__init__(project) - pins = _get_requirements(project.lockfile, "develop") - pins.update(_get_requirements(project.lockfile, "default")) - for pin in pins.values(): - pin.markers = None - self.preferred_pins = pins - - def get_provider(self): - return PinReuseProvider( - self.preferred_pins, self.requirements, self.sources, - self.requires_python, self.allow_prereleases, - ) - - -class EagerUpgradeLocker(PinReuseLocker): - """A specialized locker to handle the "eager" upgrade strategy. - - See :class:`.providers.EagerUpgradeProvider` for more - information. - """ - def __init__(self, tracked_names, *args, **kwargs): - super(EagerUpgradeLocker, self).__init__(*args, **kwargs) - self.tracked_names = tracked_names - - def get_provider(self): - return EagerUpgradeProvider( - self.tracked_names, self.preferred_pins, - self.requirements, self.sources, - self.requires_python, self.allow_prereleases, - ) diff --git a/pipenv/vendor/passa/models/metadata.py b/pipenv/vendor/passa/models/metadata.py deleted file mode 100644 index a949f1e9..00000000 --- a/pipenv/vendor/passa/models/metadata.py +++ /dev/null @@ -1,169 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, unicode_literals - -import copy -import itertools - -import packaging.markers -import packaging.specifiers -import vistir -import vistir.misc - -from ..internals.markers import get_without_extra -from ..internals.specifiers import cleanup_pyspecs, pyspec_from_markers - - -def dedup_markers(s): - # TODO: Implement better logic. - deduped = sorted(vistir.misc.dedup(s)) - return deduped - - -class MetaSet(object): - """Representation of a "metadata set". - - This holds multiple metadata representaions. Each metadata representation - includes a marker, and a specifier set of Python versions required. - """ - def __init__(self): - self.markerset = frozenset() - self.pyspecset = packaging.specifiers.SpecifierSet() - - def __repr__(self): - return "MetaSet(markerset={0!r}, pyspecset={1!r})".format( - ",".join(sorted(self.markerset)), str(self.pyspecset), - ) - - def __str__(self): - pyspecs = set() - markerset = set() - for m in self.markerset: - marker_specs = pyspec_from_markers(packaging.markers.Marker(m)) - if marker_specs: - pyspecs.add(marker_specs) - else: - markerset.add(m) - if pyspecs: - self.pyspecset._specs &= pyspecs - self.markerset = frozenset(markerset) - return " and ".join(dedup_markers(itertools.chain( - # Make sure to always use the same quotes so we can dedup properly. - ( - "{0}".format(ms) if " or " in ms else ms - for ms in (str(m).replace('"', "'") for m in self.markerset) - ), - ( - "python_version {0[0]} '{0[1]}'".format(spec) - for spec in cleanup_pyspecs(self.pyspecset) - ), - ))) - - def __bool__(self): - return bool(self.markerset or self.pyspecset) - - def __nonzero__(self): # Python 2. - return self.__bool__() - - def __or__(self, pair): - marker, specset = pair - markerset = set(self.markerset) - if marker: - marker_specs = pyspec_from_markers(marker) - if not marker_specs: - markerset.add(str(marker)) - else: - specset._specs &= marker_specs - metaset = MetaSet() - metaset.markerset = frozenset(markerset) - # TODO: Implement some logic to clean up dups like '3.0.*' and '3.0'. - metaset.pyspecset &= self.pyspecset & specset - return metaset - - -def _build_metasets(dependencies, pythons, key, trace, all_metasets): - all_parent_metasets = [] - for route in trace: - parent = route[-1] - try: - parent_metasets = all_metasets[parent] - except KeyError: # Parent not calculated yet. Wait for it. - return - all_parent_metasets.append((parent, parent_metasets)) - - metaset_iters = [] - for parent, parent_metasets in all_parent_metasets: - r = dependencies[parent][key] - python = pythons[key] - metaset = ( - get_without_extra(r.markers), - packaging.specifiers.SpecifierSet(python), - ) - metaset_iters.append( - parent_metaset | metaset - for parent_metaset in parent_metasets - ) - return list(itertools.chain.from_iterable(metaset_iters)) - - -def _calculate_metasets_mapping(dependencies, pythons, traces): - all_metasets = {None: [MetaSet()]} - - del traces[None] - while traces: - new_metasets = {} - for key, trace in traces.items(): - assert key not in all_metasets, key # Sanity check for debug. - metasets = _build_metasets( - dependencies, pythons, key, trace, all_metasets, - ) - if metasets is None: - continue - new_metasets[key] = metasets - if not new_metasets: - break # No progress? Deadlocked. Give up. - all_metasets.update(new_metasets) - for key in new_metasets: - del traces[key] - - return all_metasets - - -def _format_metasets(metasets): - # If there is an unconditional route, this needs to be unconditional. - if not metasets or not all(metasets): - return None - - # This extra str(Marker()) call helps simplify the expression. - return str(packaging.markers.Marker(" or ".join( - "{0}".format(s) if " and " in s else s - for s in dedup_markers(str(metaset) for metaset in metasets - if metaset) - ))) - - -def set_metadata(candidates, traces, dependencies, pythons): - """Add "metadata" to candidates based on the dependency tree. - - Metadata for a candidate includes markers and a specifier for Python - version requirements. - - :param candidates: A key-candidate mapping. Candidates in the mapping will - have their markers set. - :param traces: A graph trace (produced by `traces.trace_graph`) providing - information about dependency relationships between candidates. - :param dependencies: A key-collection mapping containing what dependencies - each candidate in `candidates` requested. - :param pythons: A key-str mapping containing Requires-Python information - of each candidate. - - Keys in mappings and entries in the trace are identifiers of a package, as - implemented by the `identify` method of the resolver's provider. - - The candidates are modified in-place. - """ - metasets_mapping = _calculate_metasets_mapping( - dependencies, pythons, copy.deepcopy(traces), - ) - for key, candidate in candidates.items(): - candidate.markers = _format_metasets(metasets_mapping[key]) diff --git a/pipenv/vendor/passa/models/projects.py b/pipenv/vendor/passa/models/projects.py deleted file mode 100644 index f6e037d6..00000000 --- a/pipenv/vendor/passa/models/projects.py +++ /dev/null @@ -1,241 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, unicode_literals - -import collections -import io -import os - -import attr -import packaging.markers -import packaging.utils -import plette -import plette.models -import six -import tomlkit - - -SectionDifference = collections.namedtuple("SectionDifference", [ - "inthis", "inthat", -]) -FileDifference = collections.namedtuple("FileDifference", [ - "default", "develop", -]) - - -def _are_pipfile_entries_equal(a, b): - a = {k: v for k, v in a.items() if k not in ("markers", "hashes", "hash")} - b = {k: v for k, v in b.items() if k not in ("markers", "hashes", "hash")} - if a != b: - return False - try: - marker_eval_a = packaging.markers.Marker(a["markers"]).evaluate() - except (AttributeError, KeyError, TypeError, ValueError): - marker_eval_a = True - try: - marker_eval_b = packaging.markers.Marker(b["markers"]).evaluate() - except (AttributeError, KeyError, TypeError, ValueError): - marker_eval_b = True - return marker_eval_a == marker_eval_b - - -DEFAULT_NEWLINES = "\n" - - -def preferred_newlines(f): - if isinstance(f.newlines, six.text_type): - return f.newlines - return DEFAULT_NEWLINES - - -@attr.s -class ProjectFile(object): - """A file in the Pipfile project. - """ - location = attr.ib() - line_ending = attr.ib() - model = attr.ib() - - @classmethod - def read(cls, location, model_cls, invalid_ok=False): - try: - with io.open(location, encoding="utf-8") as f: - model = model_cls.load(f) - line_ending = preferred_newlines(f) - except Exception: - if not invalid_ok: - raise - model = None - line_ending = DEFAULT_NEWLINES - return cls(location=location, line_ending=line_ending, model=model) - - def write(self): - kwargs = {"encoding": "utf-8", "newline": self.line_ending} - with io.open(self.location, "w", **kwargs) as f: - self.model.dump(f) - - def dumps(self): - strio = six.StringIO() - self.model.dump(strio) - return strio.getvalue() - - -@attr.s -class Project(object): - - root = attr.ib() - _p = attr.ib(init=False) - _l = attr.ib(init=False) - - def __attrs_post_init__(self): - self.root = root = os.path.abspath(self.root) - self._p = ProjectFile.read( - os.path.join(root, "Pipfile"), - plette.Pipfile, - ) - self._l = ProjectFile.read( - os.path.join(root, "Pipfile.lock"), - plette.Lockfile, - invalid_ok=True, - ) - - @property - def pipfile(self): - return self._p.model - - @property - def pipfile_location(self): - return self._p.location - - @property - def lockfile(self): - return self._l.model - - @property - def lockfile_location(self): - return self._l.location - - @lockfile.setter - def lockfile(self, new): - self._l.model = new - - def is_synced(self): - return self.lockfile and self.lockfile.is_up_to_date(self.pipfile) - - def _get_pipfile_section(self, develop, insert=True): - name = "dev-packages" if develop else "packages" - try: - section = self.pipfile[name] - except KeyError: - section = plette.models.PackageCollection(tomlkit.table()) - if insert: - self.pipfile[name] = section - return section - - def contains_key_in_pipfile(self, key): - sections = [ - self._get_pipfile_section(develop=False, insert=False), - self._get_pipfile_section(develop=True, insert=False), - ] - return any( - (packaging.utils.canonicalize_name(name) == - packaging.utils.canonicalize_name(key)) - for section in sections - for name in section - ) - - def add_line_to_pipfile(self, line, develop): - from requirementslib import Requirement - requirement = Requirement.from_line(line) - section = self._get_pipfile_section(develop=develop) - key = requirement.normalized_name - entry = next(iter(requirement.as_pipfile().values())) - if isinstance(entry, dict): - # HACK: TOMLKit prefers to expand tables by default, but we - # always want inline tables here. Also tomlkit.inline_table - # does not have `update()`. - table = tomlkit.inline_table() - for k, v in entry.items(): - table[k] = v - entry = table - section[key] = entry - - def remove_keys_from_pipfile(self, keys, default, develop): - keys = {packaging.utils.canonicalize_name(key) for key in keys} - sections = [] - if default: - sections.append(self._get_pipfile_section( - develop=False, insert=False, - )) - if develop: - sections.append(self._get_pipfile_section( - develop=True, insert=False, - )) - for section in sections: - removals = set() - for name in section: - if packaging.utils.canonicalize_name(name) in keys: - removals.add(name) - for key in removals: - del section._data[key] - - def remove_keys_from_lockfile(self, keys): - keys = {packaging.utils.canonicalize_name(key) for key in keys} - removed = False - for section_name in ("default", "develop"): - try: - section = self.lockfile[section_name] - except KeyError: - continue - removals = set() - for name in section: - if packaging.utils.canonicalize_name(name) in keys: - removals.add(name) - removed = removed or bool(removals) - for key in removals: - del section._data[key] - - if removed: - # HACK: The lock file no longer represents the Pipfile at this - # point. Set the hash to an arbitrary invalid value. - self.lockfile.meta.hash = plette.models.Hash({"__invalid__": ""}) - - def difference_lockfile(self, lockfile): - """Generate a difference between the current and given lockfiles. - - Returns a 2-tuple containing differences in default in develop - sections. - - Each element is a 2-tuple of dicts. The first, `inthis`, contains - entries only present in the current lockfile; the second, `inthat`, - contains entries only present in the given one. - - If a key exists in both this and that, but the values differ, the key - is present in both dicts, pointing to values from each file. - """ - diff_data = { - "default": SectionDifference({}, {}), - "develop": SectionDifference({}, {}), - } - for section_name, section_diff in diff_data.items(): - try: - this = self.lockfile[section_name]._data - except (KeyError, TypeError): - this = {} - try: - that = lockfile[section_name]._data - except (KeyError, TypeError): - that = {} - for key, this_value in this.items(): - try: - that_value = that[key] - except KeyError: - section_diff.inthis[key] = this_value - continue - if not _are_pipfile_entries_equal(this_value, that_value): - section_diff.inthis[key] = this_value - section_diff.inthat[key] = that_value - for key, that_value in that.items(): - if key not in this: - section_diff.inthat[key] = that_value - return FileDifference(**diff_data) diff --git a/pipenv/vendor/passa/models/providers.py b/pipenv/vendor/passa/models/providers.py deleted file mode 100644 index 36b2f2ea..00000000 --- a/pipenv/vendor/passa/models/providers.py +++ /dev/null @@ -1,198 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, print_function, unicode_literals - -import os - -import resolvelib - -from ..internals.candidates import find_candidates -from ..internals.dependencies import get_dependencies -from ..internals.utils import ( - filter_sources, get_allow_prereleases, identify_requirment, strip_extras, -) - - -PROTECTED_PACKAGE_NAMES = {"pip", "setuptools"} - - -class BasicProvider(resolvelib.AbstractProvider): - """Provider implementation to interface with `requirementslib.Requirement`. - """ - def __init__(self, root_requirements, sources, - requires_python, allow_prereleases): - self.sources = sources - self.requires_python = requires_python - self.allow_prereleases = bool(allow_prereleases) - self.invalid_candidates = set() - - # Remember requirements of each pinned candidate. The resolver calls - # `get_dependencies()` only when it wants to repin, so the last time - # the dependencies we got when it is last called on a package, are - # the set used by the resolver. We use this later to trace how a given - # dependency is specified by a package. - self.fetched_dependencies = {None: { - self.identify(r): r for r in root_requirements - }} - - # Should Pipfile's requires.python_[full_]version be included? - self.collected_requires_pythons = {None: ""} - - def identify(self, dependency): - return identify_requirment(dependency) - - def get_preference(self, resolution, candidates, information): - # TODO: Provide better sorting logic. This simply resolve the ones with - # less choices first. Not sophisticated, but sounds reasonable? - return len(candidates) - - def find_matches(self, requirement): - sources = filter_sources(requirement, self.sources) - candidates = find_candidates( - requirement, sources, self.requires_python, - get_allow_prereleases(requirement, self.allow_prereleases), - ) - return candidates - - def is_satisfied_by(self, requirement, candidate): - # A non-named requirement has exactly one candidate, as implemented in - # `find_matches()`. Since pip does not yet implement URL based lookup - # (PEP 508) yet, it must match unless there are duplicated entries in - # Pipfile. If there is, the user takes the blame. (sarugaku/passa#34) - if not requirement.is_named: - return True - - # A non-named candidate can only come from a non-named requirement, - # which, since pip does not implement URL based lookup (PEP 508) yet, - # can only come from Pipfile. Assume the user knows what they're doing, - # and use it without checking. (sarugaku/passa#34) - if not candidate.is_named: - return True - - # Optimization: Everything matches if there are no specifiers. - if not requirement.specifiers: - return True - - # We can't handle old version strings before PEP 440. Drop them all. - # Practically this shouldn't be a problem if the user is specifying a - # remotely reasonable dependency not from before 2013. - candidate_line = candidate.as_line(include_hashes=False) - if candidate_line in self.invalid_candidates: - return False - try: - version = candidate.get_specifier().version - except (TypeError, ValueError): - print('ignoring invalid version from {!r}'.format(candidate_line)) - self.invalid_candidates.add(candidate_line) - return False - - return requirement.as_ireq().specifier.contains(version) - - def get_dependencies(self, candidate): - sources = filter_sources(candidate, self.sources) - try: - dependencies, requires_python = get_dependencies( - candidate, sources=sources, - ) - except Exception as e: - if os.environ.get("PASSA_NO_SUPPRESS_EXCEPTIONS"): - raise - print("failed to get dependencies for {0!r}: {1}".format( - candidate.as_line(include_hashes=False), e, - )) - dependencies = [] - requires_python = "" - # Exclude protected packages from the list. This prevents those - # packages from being locked, unless the user is actually working on - # them, and explicitly lists them as top-level requirements -- those - # packages are not added via this code path. (sarugaku/passa#15) - dependencies = [ - dependency for dependency in dependencies - if dependency.normalized_name not in PROTECTED_PACKAGE_NAMES - ] - if candidate.extras: - # HACK: If this candidate has extras, add the original candidate - # (same pinned version, no extras) as its dependency. This ensures - # the same package with different extras (treated as distinct by - # the resolver) have the same version. (sarugaku/passa#4) - dependencies.append(strip_extras(candidate)) - candidate_key = self.identify(candidate) - self.fetched_dependencies[candidate_key] = { - self.identify(r): r for r in dependencies - } - self.collected_requires_pythons[candidate_key] = requires_python - return dependencies - - -class PinReuseProvider(BasicProvider): - """A provider that reuses preferred pins if possible. - - This is used to implement "add", "remove", and "only-if-needed upgrade", - where already-pinned candidates in Pipfile.lock should be preferred. - """ - def __init__(self, preferred_pins, *args, **kwargs): - super(PinReuseProvider, self).__init__(*args, **kwargs) - self.preferred_pins = preferred_pins - - def find_matches(self, requirement): - candidates = super(PinReuseProvider, self).find_matches(requirement) - try: - # Add the preferred pin. Remember the resolve prefer candidates - # at the end of the list, so the most preferred should be last. - candidates.append(self.preferred_pins[self.identify(requirement)]) - except KeyError: - pass - return candidates - - -class EagerUpgradeProvider(PinReuseProvider): - """A specialized provider to handle an "eager" upgrade strategy. - - An eager upgrade tries to upgrade not only packages specified, but also - their dependencies (recursively). This contrasts to the "only-if-needed" - default, which only promises to upgrade the specified package, and - prevents touching anything else if at all possible. - - The provider is implemented as to keep track of all dependencies of the - specified packages to upgrade, and free their pins when it has a chance. - """ - def __init__(self, tracked_names, *args, **kwargs): - super(EagerUpgradeProvider, self).__init__(*args, **kwargs) - self.tracked_names = set(tracked_names) - for name in tracked_names: - self.preferred_pins.pop(name, None) - - # HACK: Set this special flag to distinguish preferred pins from - # regular, to tell the resolver to NOT use them for tracked packages. - for pin in self.preferred_pins.values(): - pin._preferred_by_provider = True - - def is_satisfied_by(self, requirement, candidate): - # If this is a tracking package, tell the resolver out of using the - # preferred pin, and into a "normal" candidate selection process. - if (self.identify(requirement) in self.tracked_names and - getattr(candidate, "_preferred_by_provider", False)): - return False - return super(EagerUpgradeProvider, self).is_satisfied_by( - requirement, candidate, - ) - - def get_dependencies(self, candidate): - # If this package is being tracked for upgrade, remove pins of its - # dependencies, and start tracking these new packages. - dependencies = super(EagerUpgradeProvider, self).get_dependencies( - candidate, - ) - if self.identify(candidate) in self.tracked_names: - for dependency in dependencies: - name = self.identify(dependency) - self.tracked_names.add(name) - self.preferred_pins.pop(name, None) - return dependencies - - def get_preference(self, resolution, candidates, information): - # Resolve tracking packages so we have a chance to unpin them first. - name = self.identify(candidates[0]) - if name in self.tracked_names: - return -1 - return len(candidates) diff --git a/pipenv/vendor/passa/models/synchronizers.py b/pipenv/vendor/passa/models/synchronizers.py deleted file mode 100644 index bad49052..00000000 --- a/pipenv/vendor/passa/models/synchronizers.py +++ /dev/null @@ -1,214 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, unicode_literals - -import collections -import contextlib -import os -import sys -import sysconfig - -import pkg_resources - -import packaging.markers -import packaging.version -import requirementslib - -from ..internals._pip import uninstall, EditableInstaller, WheelInstaller - - -def _is_installation_local(name): - """Check whether the distribution is in the current Python installation. - - This is used to distinguish packages seen by a virtual environment. A venv - may be able to see global packages, but we don't want to mess with them. - """ - loc = os.path.normcase(pkg_resources.working_set.by_key[name].location) - pre = os.path.normcase(sys.prefix) - return os.path.commonprefix([loc, pre]) == pre - - -def _is_up_to_date(distro, version): - # This is done in strings to avoid type mismatches caused by vendering. - return str(version) == str(packaging.version.parse(distro.version)) - - -GroupCollection = collections.namedtuple("GroupCollection", [ - "uptodate", "outdated", "noremove", "unneeded", -]) - - -def _group_installed_names(packages): - """Group locally installed packages based on given specifications. - - `packages` is a name-package mapping that are used as baseline to - determine how the installed package should be grouped. - - Returns a 3-tuple of disjoint sets, all containing names of installed - packages: - - * `uptodate`: These match the specifications. - * `outdated`: These installations are specified, but don't match the - specifications in `packages`. - * `unneeded`: These are installed, but not specified in `packages`. - """ - groupcoll = GroupCollection(set(), set(), set(), set()) - - for distro in pkg_resources.working_set: - name = distro.key - try: - package = packages[name] - except KeyError: - groupcoll.unneeded.add(name) - continue - - r = requirementslib.Requirement.from_pipfile(name, package) - if not r.is_named: - # Always mark non-named. I think pip does something similar? - groupcoll.outdated.add(name) - elif not _is_up_to_date(distro, r.get_version()): - groupcoll.outdated.add(name) - else: - groupcoll.uptodate.add(name) - - return groupcoll - - -@contextlib.contextmanager -def _remove_package(name): - if name is None or not _is_installation_local(name): - yield None - return - with uninstall(name, auto_confirm=True, verbose=False) as uninstaller: - yield uninstaller - - -def _get_packages(lockfile, default, develop): - # Don't need to worry about duplicates because only extras can differ. - # Extras don't matter because they only affect dependencies, and we - # don't install dependencies anyway! - packages = {} - if default: - packages.update(lockfile.default._data) - if develop: - packages.update(lockfile.develop._data) - return packages - - -def _build_paths(): - """Prepare paths for distlib.wheel.Wheel to install into. - """ - paths = sysconfig.get_paths() - return { - "prefix": sys.prefix, - "data": paths["data"], - "scripts": paths["scripts"], - "headers": paths["include"], - "purelib": paths["purelib"], - "platlib": paths["platlib"], - } - - -PROTECTED_FROM_CLEAN = {"setuptools", "pip", "wheel"} - - -def _clean(names): - cleaned = set() - for name in names: - if name in PROTECTED_FROM_CLEAN: - continue - with _remove_package(name) as uninst: - if uninst: - cleaned.add(name) - return cleaned - - -class Synchronizer(object): - """Helper class to install packages from a project's lock file. - """ - def __init__(self, project, default, develop, clean_unneeded): - self._root = project.root # Only for repr. - self.packages = _get_packages(project.lockfile, default, develop) - self.sources = project.lockfile.meta.sources._data - self.paths = _build_paths() - self.clean_unneeded = clean_unneeded - - def __repr__(self): - return "<{0} @ {1!r}>".format(type(self).__name__, self._root) - - def sync(self): - groupcoll = _group_installed_names(self.packages) - - installed = set() - updated = set() - cleaned = set() - - # TODO: Show a prompt to confirm cleaning. We will need to implement a - # reporter pattern for this as well. - if self.clean_unneeded: - names = _clean(groupcoll.unneeded) - cleaned.update(names) - - # TODO: Specify installation order? (pypa/pipenv#2274) - installers = [] - for name, package in self.packages.items(): - r = requirementslib.Requirement.from_pipfile(name, package) - name = r.normalized_name - if name in groupcoll.uptodate: - continue - markers = r.markers - if markers and not packaging.markers.Marker(markers).evaluate(): - continue - r.markers = None - if r.editable: - installer = EditableInstaller(r) - else: - installer = WheelInstaller(r, self.sources, self.paths) - try: - installer.prepare() - except Exception as e: - if os.environ.get("PASSA_NO_SUPPRESS_EXCEPTIONS"): - raise - print("failed to prepare {0!r}: {1}".format( - r.as_line(include_hashes=False), e, - )) - else: - installers.append((name, installer)) - - for name, installer in installers: - if name in groupcoll.outdated: - name_to_remove = name - else: - name_to_remove = None - try: - with _remove_package(name_to_remove): - installer.install() - except Exception as e: - if os.environ.get("PASSA_NO_SUPPRESS_EXCEPTIONS"): - raise - print("failed to install {0!r}: {1}".format( - r.as_line(include_hashes=False), e, - )) - continue - if name in groupcoll.outdated or name in groupcoll.noremove: - updated.add(name) - else: - installed.add(name) - - return installed, updated, cleaned - - -class Cleaner(object): - """Helper class to clean packages not in a project's lock file. - """ - def __init__(self, project, default, develop): - self._root = project.root # Only for repr. - self.packages = _get_packages(project.lockfile, default, develop) - - def __repr__(self): - return "<{0} @ {1!r}>".format(type(self).__name__, self._root) - - def clean(self): - groupcoll = _group_installed_names(self.packages) - cleaned = _clean(groupcoll.unneeded) - return cleaned diff --git a/pipenv/vendor/passa/operations/lock.py b/pipenv/vendor/passa/operations/lock.py deleted file mode 100644 index 200735ac..00000000 --- a/pipenv/vendor/passa/operations/lock.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, print_function, unicode_literals - -from resolvelib import NoVersionsAvailable, ResolutionImpossible - -from passa.internals.reporters import print_requirement - - -def lock(locker): - success = False - try: - locker.lock() - except NoVersionsAvailable as e: - print("\nCANNOT RESOLVE. NO CANDIDATES FOUND FOR:") - print("{:>40}".format(e.requirement.as_line(include_hashes=False))) - if e.parent: - line = e.parent.as_line(include_hashes=False) - print("{:>41}".format("(from {})".format(line))) - else: - print("{:>41}".format("(user)")) - except ResolutionImpossible as e: - print("\nCANNOT RESOLVE.\nOFFENDING REQUIREMENTS:") - for r in e.requirements: - print_requirement(r) - else: - success = True - return success diff --git a/pipenv/vendor/passa/operations/sync.py b/pipenv/vendor/passa/operations/sync.py deleted file mode 100644 index 3014e8d9..00000000 --- a/pipenv/vendor/passa/operations/sync.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding=utf-8 -*- - -from __future__ import absolute_import, print_function, unicode_literals - - -def sync(syncer): - print("Starting synchronization") - installed, updated, cleaned = syncer.sync() - if cleaned: - print("Uninstalled: {}".format(", ".join(sorted(cleaned)))) - if installed: - print("Installed: {}".format(", ".join(sorted(installed)))) - if updated: - print("Updated: {}".format(", ".join(sorted(updated)))) - return True - - -def clean(cleaner): - print("Cleaning") - cleaned = cleaner.clean() - if cleaned: - print("Uninstalled: {}".format(", ".join(sorted(cleaned)))) - return True diff --git a/pipenv/vendor/pathlib2/LICENSE.rst b/pipenv/vendor/pathlib2/LICENSE.rst deleted file mode 100644 index 1715d3d7..00000000 --- a/pipenv/vendor/pathlib2/LICENSE.rst +++ /dev/null @@ -1,23 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014-2017 Matthias C. M. Troffaes -Copyright (c) 2012-2014 Antoine Pitrou and contributors - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - diff --git a/pipenv/vendor/pathlib2/__init__.py b/pipenv/vendor/pathlib2/__init__.py deleted file mode 100644 index 2eb41e30..00000000 --- a/pipenv/vendor/pathlib2/__init__.py +++ /dev/null @@ -1,1670 +0,0 @@ -# Copyright (c) 2014-2017 Matthias C. M. Troffaes -# Copyright (c) 2012-2014 Antoine Pitrou and contributors -# Distributed under the terms of the MIT License. - -import ctypes -import fnmatch -import functools -import io -import ntpath -import os -import posixpath -import re -import six -import sys -from collections import Sequence -from errno import EINVAL, ENOENT, ENOTDIR, EEXIST, EPERM, EACCES -from operator import attrgetter - -from stat import ( - S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO) -try: - from urllib import quote as urlquote_from_bytes -except ImportError: - from urllib.parse import quote_from_bytes as urlquote_from_bytes - - -try: - intern = intern -except NameError: - intern = sys.intern - -supports_symlinks = True -if os.name == 'nt': - import nt - if sys.getwindowsversion()[:2] >= (6, 0) and sys.version_info >= (3, 2): - from nt import _getfinalpathname - else: - supports_symlinks = False - _getfinalpathname = None -else: - nt = None - -try: - from os import scandir as os_scandir -except ImportError: - from scandir import scandir as os_scandir - -__all__ = [ - "PurePath", "PurePosixPath", "PureWindowsPath", - "Path", "PosixPath", "WindowsPath", - ] - -# -# Internals -# - - -def _py2_fsencode(parts): - # py2 => minimal unicode support - assert six.PY2 - return [part.encode('ascii') if isinstance(part, six.text_type) - else part for part in parts] - - -def _try_except_fileexistserror(try_func, except_func, else_func=None): - if sys.version_info >= (3, 3): - try: - try_func() - except FileExistsError as exc: - except_func(exc) - else: - if else_func is not None: - else_func() - else: - try: - try_func() - except EnvironmentError as exc: - if exc.errno != EEXIST: - raise - else: - except_func(exc) - else: - if else_func is not None: - else_func() - - -def _try_except_filenotfounderror(try_func, except_func): - if sys.version_info >= (3, 3): - try: - try_func() - except FileNotFoundError as exc: - except_func(exc) - else: - try: - try_func() - except EnvironmentError as exc: - if exc.errno != ENOENT: - raise - else: - except_func(exc) - - -def _try_except_permissionerror_iter(try_iter, except_iter): - if sys.version_info >= (3, 3): - try: - for x in try_iter(): - yield x - except PermissionError as exc: - for x in except_iter(exc): - yield x - else: - try: - for x in try_iter(): - yield x - except EnvironmentError as exc: - if exc.errno not in (EPERM, EACCES): - raise - else: - for x in except_iter(exc): - yield x - - -def _win32_get_unique_path_id(path): - # get file information, needed for samefile on older Python versions - # see http://timgolden.me.uk/python/win32_how_do_i/ - # see_if_two_files_are_the_same_file.html - from ctypes import POINTER, Structure, WinError - from ctypes.wintypes import DWORD, HANDLE, BOOL - - class FILETIME(Structure): - _fields_ = [("datetime_lo", DWORD), - ("datetime_hi", DWORD), - ] - - class BY_HANDLE_FILE_INFORMATION(Structure): - _fields_ = [("attributes", DWORD), - ("created_at", FILETIME), - ("accessed_at", FILETIME), - ("written_at", FILETIME), - ("volume", DWORD), - ("file_hi", DWORD), - ("file_lo", DWORD), - ("n_links", DWORD), - ("index_hi", DWORD), - ("index_lo", DWORD), - ] - - CreateFile = ctypes.windll.kernel32.CreateFileW - CreateFile.argtypes = [ctypes.c_wchar_p, DWORD, DWORD, ctypes.c_void_p, - DWORD, DWORD, HANDLE] - CreateFile.restype = HANDLE - GetFileInformationByHandle = ( - ctypes.windll.kernel32.GetFileInformationByHandle) - GetFileInformationByHandle.argtypes = [ - HANDLE, POINTER(BY_HANDLE_FILE_INFORMATION)] - GetFileInformationByHandle.restype = BOOL - CloseHandle = ctypes.windll.kernel32.CloseHandle - CloseHandle.argtypes = [HANDLE] - CloseHandle.restype = BOOL - GENERIC_READ = 0x80000000 - FILE_SHARE_READ = 0x00000001 - FILE_FLAG_BACKUP_SEMANTICS = 0x02000000 - OPEN_EXISTING = 3 - if os.path.isdir(path): - flags = FILE_FLAG_BACKUP_SEMANTICS - else: - flags = 0 - hfile = CreateFile(path, GENERIC_READ, FILE_SHARE_READ, - None, OPEN_EXISTING, flags, None) - if hfile == 0xffffffff: - if sys.version_info >= (3, 3): - raise FileNotFoundError(path) - else: - exc = OSError("file not found: path") - exc.errno = ENOENT - raise exc - info = BY_HANDLE_FILE_INFORMATION() - success = GetFileInformationByHandle(hfile, info) - CloseHandle(hfile) - if success == 0: - raise WinError() - return info.volume, info.index_hi, info.index_lo - - -def _is_wildcard_pattern(pat): - # Whether this pattern needs actual matching using fnmatch, or can - # be looked up directly as a file. - return "*" in pat or "?" in pat or "[" in pat - - -class _Flavour(object): - - """A flavour implements a particular (platform-specific) set of path - semantics.""" - - def __init__(self): - self.join = self.sep.join - - def parse_parts(self, parts): - if six.PY2: - parts = _py2_fsencode(parts) - parsed = [] - sep = self.sep - altsep = self.altsep - drv = root = '' - it = reversed(parts) - for part in it: - if not part: - continue - if altsep: - part = part.replace(altsep, sep) - drv, root, rel = self.splitroot(part) - if sep in rel: - for x in reversed(rel.split(sep)): - if x and x != '.': - parsed.append(intern(x)) - else: - if rel and rel != '.': - parsed.append(intern(rel)) - if drv or root: - if not drv: - # If no drive is present, try to find one in the previous - # parts. This makes the result of parsing e.g. - # ("C:", "/", "a") reasonably intuitive. - for part in it: - if not part: - continue - if altsep: - part = part.replace(altsep, sep) - drv = self.splitroot(part)[0] - if drv: - break - break - if drv or root: - parsed.append(drv + root) - parsed.reverse() - return drv, root, parsed - - def join_parsed_parts(self, drv, root, parts, drv2, root2, parts2): - """ - Join the two paths represented by the respective - (drive, root, parts) tuples. Return a new (drive, root, parts) tuple. - """ - if root2: - if not drv2 and drv: - return drv, root2, [drv + root2] + parts2[1:] - elif drv2: - if drv2 == drv or self.casefold(drv2) == self.casefold(drv): - # Same drive => second path is relative to the first - return drv, root, parts + parts2[1:] - else: - # Second path is non-anchored (common case) - return drv, root, parts + parts2 - return drv2, root2, parts2 - - -class _WindowsFlavour(_Flavour): - # Reference for Windows paths can be found at - # http://msdn.microsoft.com/en-us/library/aa365247%28v=vs.85%29.aspx - - sep = '\\' - altsep = '/' - has_drv = True - pathmod = ntpath - - is_supported = (os.name == 'nt') - - drive_letters = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') - ext_namespace_prefix = '\\\\?\\' - - reserved_names = ( - set(['CON', 'PRN', 'AUX', 'NUL']) | - set(['COM%d' % i for i in range(1, 10)]) | - set(['LPT%d' % i for i in range(1, 10)]) - ) - - # Interesting findings about extended paths: - # - '\\?\c:\a', '//?/c:\a' and '//?/c:/a' are all supported - # but '\\?\c:/a' is not - # - extended paths are always absolute; "relative" extended paths will - # fail. - - def splitroot(self, part, sep=sep): - first = part[0:1] - second = part[1:2] - if (second == sep and first == sep): - # XXX extended paths should also disable the collapsing of "." - # components (according to MSDN docs). - prefix, part = self._split_extended_path(part) - first = part[0:1] - second = part[1:2] - else: - prefix = '' - third = part[2:3] - if (second == sep and first == sep and third != sep): - # is a UNC path: - # vvvvvvvvvvvvvvvvvvvvv root - # \\machine\mountpoint\directory\etc\... - # directory ^^^^^^^^^^^^^^ - index = part.find(sep, 2) - if index != -1: - index2 = part.find(sep, index + 1) - # a UNC path can't have two slashes in a row - # (after the initial two) - if index2 != index + 1: - if index2 == -1: - index2 = len(part) - if prefix: - return prefix + part[1:index2], sep, part[index2 + 1:] - else: - return part[:index2], sep, part[index2 + 1:] - drv = root = '' - if second == ':' and first in self.drive_letters: - drv = part[:2] - part = part[2:] - first = third - if first == sep: - root = first - part = part.lstrip(sep) - return prefix + drv, root, part - - def casefold(self, s): - return s.lower() - - def casefold_parts(self, parts): - return [p.lower() for p in parts] - - def resolve(self, path, strict=False): - s = str(path) - if not s: - return os.getcwd() - previous_s = None - if _getfinalpathname is not None: - if strict: - return self._ext_to_normal(_getfinalpathname(s)) - else: - # End of the path after the first one not found - tail_parts = [] - while True: - try: - s = self._ext_to_normal(_getfinalpathname(s)) - except FileNotFoundError: - previous_s = s - s, tail = os.path.split(s) - tail_parts.append(tail) - if previous_s == s: - return path - else: - return os.path.join(s, *reversed(tail_parts)) - # Means fallback on absolute - return None - - def _split_extended_path(self, s, ext_prefix=ext_namespace_prefix): - prefix = '' - if s.startswith(ext_prefix): - prefix = s[:4] - s = s[4:] - if s.startswith('UNC\\'): - prefix += s[:3] - s = '\\' + s[3:] - return prefix, s - - def _ext_to_normal(self, s): - # Turn back an extended path into a normal DOS-like path - return self._split_extended_path(s)[1] - - def is_reserved(self, parts): - # NOTE: the rules for reserved names seem somewhat complicated - # (e.g. r"..\NUL" is reserved but not r"foo\NUL"). - # We err on the side of caution and return True for paths which are - # not considered reserved by Windows. - if not parts: - return False - if parts[0].startswith('\\\\'): - # UNC paths are never reserved - return False - return parts[-1].partition('.')[0].upper() in self.reserved_names - - def make_uri(self, path): - # Under Windows, file URIs use the UTF-8 encoding. - drive = path.drive - if len(drive) == 2 and drive[1] == ':': - # It's a path on a local drive => 'file:///c:/a/b' - rest = path.as_posix()[2:].lstrip('/') - return 'file:///%s/%s' % ( - drive, urlquote_from_bytes(rest.encode('utf-8'))) - else: - # It's a path on a network drive => 'file://host/share/a/b' - return 'file:' + urlquote_from_bytes( - path.as_posix().encode('utf-8')) - - def gethomedir(self, username): - if 'HOME' in os.environ: - userhome = os.environ['HOME'] - elif 'USERPROFILE' in os.environ: - userhome = os.environ['USERPROFILE'] - elif 'HOMEPATH' in os.environ: - try: - drv = os.environ['HOMEDRIVE'] - except KeyError: - drv = '' - userhome = drv + os.environ['HOMEPATH'] - else: - raise RuntimeError("Can't determine home directory") - - if username: - # Try to guess user home directory. By default all users - # directories are located in the same place and are named by - # corresponding usernames. If current user home directory points - # to nonstandard place, this guess is likely wrong. - if os.environ['USERNAME'] != username: - drv, root, parts = self.parse_parts((userhome,)) - if parts[-1] != os.environ['USERNAME']: - raise RuntimeError("Can't determine home directory " - "for %r" % username) - parts[-1] = username - if drv or root: - userhome = drv + root + self.join(parts[1:]) - else: - userhome = self.join(parts) - return userhome - - -class _PosixFlavour(_Flavour): - sep = '/' - altsep = '' - has_drv = False - pathmod = posixpath - - is_supported = (os.name != 'nt') - - def splitroot(self, part, sep=sep): - if part and part[0] == sep: - stripped_part = part.lstrip(sep) - # According to POSIX path resolution: - # http://pubs.opengroup.org/onlinepubs/009695399/basedefs/ - # xbd_chap04.html#tag_04_11 - # "A pathname that begins with two successive slashes may be - # interpreted in an implementation-defined manner, although more - # than two leading slashes shall be treated as a single slash". - if len(part) - len(stripped_part) == 2: - return '', sep * 2, stripped_part - else: - return '', sep, stripped_part - else: - return '', '', part - - def casefold(self, s): - return s - - def casefold_parts(self, parts): - return parts - - def resolve(self, path, strict=False): - sep = self.sep - accessor = path._accessor - seen = {} - - def _resolve(path, rest): - if rest.startswith(sep): - path = '' - - for name in rest.split(sep): - if not name or name == '.': - # current dir - continue - if name == '..': - # parent dir - path, _, _ = path.rpartition(sep) - continue - newpath = path + sep + name - if newpath in seen: - # Already seen this path - path = seen[newpath] - if path is not None: - # use cached value - continue - # The symlink is not resolved, so we must have a symlink - # loop. - raise RuntimeError("Symlink loop from %r" % newpath) - # Resolve the symbolic link - try: - target = accessor.readlink(newpath) - except OSError as e: - if e.errno != EINVAL and strict: - raise - # Not a symlink, or non-strict mode. We just leave the path - # untouched. - path = newpath - else: - seen[newpath] = None # not resolved symlink - path = _resolve(path, target) - seen[newpath] = path # resolved symlink - - return path - # NOTE: according to POSIX, getcwd() cannot contain path components - # which are symlinks. - base = '' if path.is_absolute() else os.getcwd() - return _resolve(base, str(path)) or sep - - def is_reserved(self, parts): - return False - - def make_uri(self, path): - # We represent the path using the local filesystem encoding, - # for portability to other applications. - bpath = bytes(path) - return 'file://' + urlquote_from_bytes(bpath) - - def gethomedir(self, username): - if not username: - try: - return os.environ['HOME'] - except KeyError: - import pwd - return pwd.getpwuid(os.getuid()).pw_dir - else: - import pwd - try: - return pwd.getpwnam(username).pw_dir - except KeyError: - raise RuntimeError("Can't determine home directory " - "for %r" % username) - - -_windows_flavour = _WindowsFlavour() -_posix_flavour = _PosixFlavour() - - -class _Accessor: - - """An accessor implements a particular (system-specific or not) way of - accessing paths on the filesystem.""" - - -class _NormalAccessor(_Accessor): - - def _wrap_strfunc(strfunc): - @functools.wraps(strfunc) - def wrapped(pathobj, *args): - return strfunc(str(pathobj), *args) - return staticmethod(wrapped) - - def _wrap_binary_strfunc(strfunc): - @functools.wraps(strfunc) - def wrapped(pathobjA, pathobjB, *args): - return strfunc(str(pathobjA), str(pathobjB), *args) - return staticmethod(wrapped) - - stat = _wrap_strfunc(os.stat) - - lstat = _wrap_strfunc(os.lstat) - - open = _wrap_strfunc(os.open) - - listdir = _wrap_strfunc(os.listdir) - - scandir = _wrap_strfunc(os_scandir) - - chmod = _wrap_strfunc(os.chmod) - - if hasattr(os, "lchmod"): - lchmod = _wrap_strfunc(os.lchmod) - else: - def lchmod(self, pathobj, mode): - raise NotImplementedError("lchmod() not available on this system") - - mkdir = _wrap_strfunc(os.mkdir) - - unlink = _wrap_strfunc(os.unlink) - - rmdir = _wrap_strfunc(os.rmdir) - - rename = _wrap_binary_strfunc(os.rename) - - if sys.version_info >= (3, 3): - replace = _wrap_binary_strfunc(os.replace) - - if nt: - if supports_symlinks: - symlink = _wrap_binary_strfunc(os.symlink) - else: - def symlink(a, b, target_is_directory): - raise NotImplementedError( - "symlink() not available on this system") - else: - # Under POSIX, os.symlink() takes two args - @staticmethod - def symlink(a, b, target_is_directory): - return os.symlink(str(a), str(b)) - - utime = _wrap_strfunc(os.utime) - - # Helper for resolve() - def readlink(self, path): - return os.readlink(path) - - -_normal_accessor = _NormalAccessor() - - -# -# Globbing helpers -# - -def _make_selector(pattern_parts): - pat = pattern_parts[0] - child_parts = pattern_parts[1:] - if pat == '**': - cls = _RecursiveWildcardSelector - elif '**' in pat: - raise ValueError( - "Invalid pattern: '**' can only be an entire path component") - elif _is_wildcard_pattern(pat): - cls = _WildcardSelector - else: - cls = _PreciseSelector - return cls(pat, child_parts) - - -if hasattr(functools, "lru_cache"): - _make_selector = functools.lru_cache()(_make_selector) - - -class _Selector: - - """A selector matches a specific glob pattern part against the children - of a given path.""" - - def __init__(self, child_parts): - self.child_parts = child_parts - if child_parts: - self.successor = _make_selector(child_parts) - self.dironly = True - else: - self.successor = _TerminatingSelector() - self.dironly = False - - def select_from(self, parent_path): - """Iterate over all child paths of `parent_path` matched by this - selector. This can contain parent_path itself.""" - path_cls = type(parent_path) - is_dir = path_cls.is_dir - exists = path_cls.exists - scandir = parent_path._accessor.scandir - if not is_dir(parent_path): - return iter([]) - return self._select_from(parent_path, is_dir, exists, scandir) - - -class _TerminatingSelector: - - def _select_from(self, parent_path, is_dir, exists, scandir): - yield parent_path - - -class _PreciseSelector(_Selector): - - def __init__(self, name, child_parts): - self.name = name - _Selector.__init__(self, child_parts) - - def _select_from(self, parent_path, is_dir, exists, scandir): - def try_iter(): - path = parent_path._make_child_relpath(self.name) - if (is_dir if self.dironly else exists)(path): - for p in self.successor._select_from( - path, is_dir, exists, scandir): - yield p - - def except_iter(exc): - return - yield - - for x in _try_except_permissionerror_iter(try_iter, except_iter): - yield x - - -class _WildcardSelector(_Selector): - - def __init__(self, pat, child_parts): - self.pat = re.compile(fnmatch.translate(pat)) - _Selector.__init__(self, child_parts) - - def _select_from(self, parent_path, is_dir, exists, scandir): - def try_iter(): - cf = parent_path._flavour.casefold - entries = list(scandir(parent_path)) - for entry in entries: - if not self.dironly or entry.is_dir(): - name = entry.name - casefolded = cf(name) - if self.pat.match(casefolded): - path = parent_path._make_child_relpath(name) - for p in self.successor._select_from( - path, is_dir, exists, scandir): - yield p - - def except_iter(exc): - return - yield - - for x in _try_except_permissionerror_iter(try_iter, except_iter): - yield x - - -class _RecursiveWildcardSelector(_Selector): - - def __init__(self, pat, child_parts): - _Selector.__init__(self, child_parts) - - def _iterate_directories(self, parent_path, is_dir, scandir): - yield parent_path - - def try_iter(): - entries = list(scandir(parent_path)) - for entry in entries: - if entry.is_dir() and not entry.is_symlink(): - path = parent_path._make_child_relpath(entry.name) - for p in self._iterate_directories(path, is_dir, scandir): - yield p - - def except_iter(exc): - return - yield - - for x in _try_except_permissionerror_iter(try_iter, except_iter): - yield x - - def _select_from(self, parent_path, is_dir, exists, scandir): - def try_iter(): - yielded = set() - try: - successor_select = self.successor._select_from - for starting_point in self._iterate_directories( - parent_path, is_dir, scandir): - for p in successor_select( - starting_point, is_dir, exists, scandir): - if p not in yielded: - yield p - yielded.add(p) - finally: - yielded.clear() - - def except_iter(exc): - return - yield - - for x in _try_except_permissionerror_iter(try_iter, except_iter): - yield x - - -# -# Public API -# - -class _PathParents(Sequence): - - """This object provides sequence-like access to the logical ancestors - of a path. Don't try to construct it yourself.""" - __slots__ = ('_pathcls', '_drv', '_root', '_parts') - - def __init__(self, path): - # We don't store the instance to avoid reference cycles - self._pathcls = type(path) - self._drv = path._drv - self._root = path._root - self._parts = path._parts - - def __len__(self): - if self._drv or self._root: - return len(self._parts) - 1 - else: - return len(self._parts) - - def __getitem__(self, idx): - if idx < 0 or idx >= len(self): - raise IndexError(idx) - return self._pathcls._from_parsed_parts(self._drv, self._root, - self._parts[:-idx - 1]) - - def __repr__(self): - return "<{0}.parents>".format(self._pathcls.__name__) - - -class PurePath(object): - - """PurePath represents a filesystem path and offers operations which - don't imply any actual filesystem I/O. Depending on your system, - instantiating a PurePath will return either a PurePosixPath or a - PureWindowsPath object. You can also instantiate either of these classes - directly, regardless of your system. - """ - __slots__ = ( - '_drv', '_root', '_parts', - '_str', '_hash', '_pparts', '_cached_cparts', - ) - - def __new__(cls, *args): - """Construct a PurePath from one or several strings and or existing - PurePath objects. The strings and path objects are combined so as - to yield a canonicalized path, which is incorporated into the - new PurePath object. - """ - if cls is PurePath: - cls = PureWindowsPath if os.name == 'nt' else PurePosixPath - return cls._from_parts(args) - - def __reduce__(self): - # Using the parts tuple helps share interned path parts - # when pickling related paths. - return (self.__class__, tuple(self._parts)) - - @classmethod - def _parse_args(cls, args): - # This is useful when you don't want to create an instance, just - # canonicalize some constructor arguments. - parts = [] - for a in args: - if isinstance(a, PurePath): - parts += a._parts - else: - if sys.version_info >= (3, 6): - a = os.fspath(a) - else: - # duck typing for older Python versions - if hasattr(a, "__fspath__"): - a = a.__fspath__() - if isinstance(a, str): - # Force-cast str subclasses to str (issue #21127) - parts.append(str(a)) - # also handle unicode for PY2 (six.text_type = unicode) - elif six.PY2 and isinstance(a, six.text_type): - # cast to str using filesystem encoding - parts.append(a.encode(sys.getfilesystemencoding())) - else: - raise TypeError( - "argument should be a str object or an os.PathLike " - "object returning str, not %r" - % type(a)) - return cls._flavour.parse_parts(parts) - - @classmethod - def _from_parts(cls, args, init=True): - # We need to call _parse_args on the instance, so as to get the - # right flavour. - self = object.__new__(cls) - drv, root, parts = self._parse_args(args) - self._drv = drv - self._root = root - self._parts = parts - if init: - self._init() - return self - - @classmethod - def _from_parsed_parts(cls, drv, root, parts, init=True): - self = object.__new__(cls) - self._drv = drv - self._root = root - self._parts = parts - if init: - self._init() - return self - - @classmethod - def _format_parsed_parts(cls, drv, root, parts): - if drv or root: - return drv + root + cls._flavour.join(parts[1:]) - else: - return cls._flavour.join(parts) - - def _init(self): - # Overridden in concrete Path - pass - - def _make_child(self, args): - drv, root, parts = self._parse_args(args) - drv, root, parts = self._flavour.join_parsed_parts( - self._drv, self._root, self._parts, drv, root, parts) - return self._from_parsed_parts(drv, root, parts) - - def __str__(self): - """Return the string representation of the path, suitable for - passing to system calls.""" - try: - return self._str - except AttributeError: - self._str = self._format_parsed_parts(self._drv, self._root, - self._parts) or '.' - return self._str - - def __fspath__(self): - return str(self) - - def as_posix(self): - """Return the string representation of the path with forward (/) - slashes.""" - f = self._flavour - return str(self).replace(f.sep, '/') - - def __bytes__(self): - """Return the bytes representation of the path. This is only - recommended to use under Unix.""" - if sys.version_info < (3, 2): - raise NotImplementedError("needs Python 3.2 or later") - return os.fsencode(str(self)) - - def __repr__(self): - return "{0}({1!r})".format(self.__class__.__name__, self.as_posix()) - - def as_uri(self): - """Return the path as a 'file' URI.""" - if not self.is_absolute(): - raise ValueError("relative path can't be expressed as a file URI") - return self._flavour.make_uri(self) - - @property - def _cparts(self): - # Cached casefolded parts, for hashing and comparison - try: - return self._cached_cparts - except AttributeError: - self._cached_cparts = self._flavour.casefold_parts(self._parts) - return self._cached_cparts - - def __eq__(self, other): - if not isinstance(other, PurePath): - return NotImplemented - return ( - self._cparts == other._cparts - and self._flavour is other._flavour) - - def __ne__(self, other): - return not self == other - - def __hash__(self): - try: - return self._hash - except AttributeError: - self._hash = hash(tuple(self._cparts)) - return self._hash - - def __lt__(self, other): - if (not isinstance(other, PurePath) - or self._flavour is not other._flavour): - return NotImplemented - return self._cparts < other._cparts - - def __le__(self, other): - if (not isinstance(other, PurePath) - or self._flavour is not other._flavour): - return NotImplemented - return self._cparts <= other._cparts - - def __gt__(self, other): - if (not isinstance(other, PurePath) - or self._flavour is not other._flavour): - return NotImplemented - return self._cparts > other._cparts - - def __ge__(self, other): - if (not isinstance(other, PurePath) - or self._flavour is not other._flavour): - return NotImplemented - return self._cparts >= other._cparts - - drive = property(attrgetter('_drv'), - doc="""The drive prefix (letter or UNC path), if any.""") - - root = property(attrgetter('_root'), - doc="""The root of the path, if any.""") - - @property - def anchor(self): - """The concatenation of the drive and root, or ''.""" - anchor = self._drv + self._root - return anchor - - @property - def name(self): - """The final path component, if any.""" - parts = self._parts - if len(parts) == (1 if (self._drv or self._root) else 0): - return '' - return parts[-1] - - @property - def suffix(self): - """The final component's last suffix, if any.""" - name = self.name - i = name.rfind('.') - if 0 < i < len(name) - 1: - return name[i:] - else: - return '' - - @property - def suffixes(self): - """A list of the final component's suffixes, if any.""" - name = self.name - if name.endswith('.'): - return [] - name = name.lstrip('.') - return ['.' + suffix for suffix in name.split('.')[1:]] - - @property - def stem(self): - """The final path component, minus its last suffix.""" - name = self.name - i = name.rfind('.') - if 0 < i < len(name) - 1: - return name[:i] - else: - return name - - def with_name(self, name): - """Return a new path with the file name changed.""" - if not self.name: - raise ValueError("%r has an empty name" % (self,)) - drv, root, parts = self._flavour.parse_parts((name,)) - if (not name or name[-1] in [self._flavour.sep, self._flavour.altsep] - or drv or root or len(parts) != 1): - raise ValueError("Invalid name %r" % (name)) - return self._from_parsed_parts(self._drv, self._root, - self._parts[:-1] + [name]) - - def with_suffix(self, suffix): - """Return a new path with the file suffix changed (or added, if - none). - """ - # XXX if suffix is None, should the current suffix be removed? - f = self._flavour - if f.sep in suffix or f.altsep and f.altsep in suffix: - raise ValueError("Invalid suffix %r" % (suffix)) - if suffix and not suffix.startswith('.') or suffix == '.': - raise ValueError("Invalid suffix %r" % (suffix)) - name = self.name - if not name: - raise ValueError("%r has an empty name" % (self,)) - old_suffix = self.suffix - if not old_suffix: - name = name + suffix - else: - name = name[:-len(old_suffix)] + suffix - return self._from_parsed_parts(self._drv, self._root, - self._parts[:-1] + [name]) - - def relative_to(self, *other): - """Return the relative path to another path identified by the passed - arguments. If the operation is not possible (because this is not - a subpath of the other path), raise ValueError. - """ - # For the purpose of this method, drive and root are considered - # separate parts, i.e.: - # Path('c:/').relative_to('c:') gives Path('/') - # Path('c:/').relative_to('/') raise ValueError - if not other: - raise TypeError("need at least one argument") - parts = self._parts - drv = self._drv - root = self._root - if root: - abs_parts = [drv, root] + parts[1:] - else: - abs_parts = parts - to_drv, to_root, to_parts = self._parse_args(other) - if to_root: - to_abs_parts = [to_drv, to_root] + to_parts[1:] - else: - to_abs_parts = to_parts - n = len(to_abs_parts) - cf = self._flavour.casefold_parts - if (root or drv) if n == 0 else cf(abs_parts[:n]) != cf(to_abs_parts): - formatted = self._format_parsed_parts(to_drv, to_root, to_parts) - raise ValueError("{0!r} does not start with {1!r}" - .format(str(self), str(formatted))) - return self._from_parsed_parts('', root if n == 1 else '', - abs_parts[n:]) - - @property - def parts(self): - """An object providing sequence-like access to the - components in the filesystem path.""" - # We cache the tuple to avoid building a new one each time .parts - # is accessed. XXX is this necessary? - try: - return self._pparts - except AttributeError: - self._pparts = tuple(self._parts) - return self._pparts - - def joinpath(self, *args): - """Combine this path with one or several arguments, and return a - new path representing either a subpath (if all arguments are relative - paths) or a totally different path (if one of the arguments is - anchored). - """ - return self._make_child(args) - - def __truediv__(self, key): - return self._make_child((key,)) - - def __rtruediv__(self, key): - return self._from_parts([key] + self._parts) - - if six.PY2: - __div__ = __truediv__ - __rdiv__ = __rtruediv__ - - @property - def parent(self): - """The logical parent of the path.""" - drv = self._drv - root = self._root - parts = self._parts - if len(parts) == 1 and (drv or root): - return self - return self._from_parsed_parts(drv, root, parts[:-1]) - - @property - def parents(self): - """A sequence of this path's logical parents.""" - return _PathParents(self) - - def is_absolute(self): - """True if the path is absolute (has both a root and, if applicable, - a drive).""" - if not self._root: - return False - return not self._flavour.has_drv or bool(self._drv) - - def is_reserved(self): - """Return True if the path contains one of the special names reserved - by the system, if any.""" - return self._flavour.is_reserved(self._parts) - - def match(self, path_pattern): - """ - Return True if this path matches the given pattern. - """ - cf = self._flavour.casefold - path_pattern = cf(path_pattern) - drv, root, pat_parts = self._flavour.parse_parts((path_pattern,)) - if not pat_parts: - raise ValueError("empty pattern") - if drv and drv != cf(self._drv): - return False - if root and root != cf(self._root): - return False - parts = self._cparts - if drv or root: - if len(pat_parts) != len(parts): - return False - pat_parts = pat_parts[1:] - elif len(pat_parts) > len(parts): - return False - for part, pat in zip(reversed(parts), reversed(pat_parts)): - if not fnmatch.fnmatchcase(part, pat): - return False - return True - - -# Can't subclass os.PathLike from PurePath and keep the constructor -# optimizations in PurePath._parse_args(). -if sys.version_info >= (3, 6): - os.PathLike.register(PurePath) - - -class PurePosixPath(PurePath): - _flavour = _posix_flavour - __slots__ = () - - -class PureWindowsPath(PurePath): - _flavour = _windows_flavour - __slots__ = () - - -# Filesystem-accessing classes - - -class Path(PurePath): - __slots__ = ( - '_accessor', - '_closed', - ) - - def __new__(cls, *args, **kwargs): - if cls is Path: - cls = WindowsPath if os.name == 'nt' else PosixPath - self = cls._from_parts(args, init=False) - if not self._flavour.is_supported: - raise NotImplementedError("cannot instantiate %r on your system" - % (cls.__name__,)) - self._init() - return self - - def _init(self, - # Private non-constructor arguments - template=None, - ): - self._closed = False - if template is not None: - self._accessor = template._accessor - else: - self._accessor = _normal_accessor - - def _make_child_relpath(self, part): - # This is an optimization used for dir walking. `part` must be - # a single part relative to this path. - parts = self._parts + [part] - return self._from_parsed_parts(self._drv, self._root, parts) - - def __enter__(self): - if self._closed: - self._raise_closed() - return self - - def __exit__(self, t, v, tb): - self._closed = True - - def _raise_closed(self): - raise ValueError("I/O operation on closed path") - - def _opener(self, name, flags, mode=0o666): - # A stub for the opener argument to built-in open() - return self._accessor.open(self, flags, mode) - - def _raw_open(self, flags, mode=0o777): - """ - Open the file pointed by this path and return a file descriptor, - as os.open() does. - """ - if self._closed: - self._raise_closed() - return self._accessor.open(self, flags, mode) - - # Public API - - @classmethod - def cwd(cls): - """Return a new path pointing to the current working directory - (as returned by os.getcwd()). - """ - return cls(os.getcwd()) - - @classmethod - def home(cls): - """Return a new path pointing to the user's home directory (as - returned by os.path.expanduser('~')). - """ - return cls(cls()._flavour.gethomedir(None)) - - def samefile(self, other_path): - """Return whether other_path is the same or not as this file - (as returned by os.path.samefile()). - """ - if hasattr(os.path, "samestat"): - st = self.stat() - try: - other_st = other_path.stat() - except AttributeError: - other_st = os.stat(other_path) - return os.path.samestat(st, other_st) - else: - filename1 = six.text_type(self) - filename2 = six.text_type(other_path) - st1 = _win32_get_unique_path_id(filename1) - st2 = _win32_get_unique_path_id(filename2) - return st1 == st2 - - def iterdir(self): - """Iterate over the files in this directory. Does not yield any - result for the special paths '.' and '..'. - """ - if self._closed: - self._raise_closed() - for name in self._accessor.listdir(self): - if name in ('.', '..'): - # Yielding a path object for these makes little sense - continue - yield self._make_child_relpath(name) - if self._closed: - self._raise_closed() - - def glob(self, pattern): - """Iterate over this subtree and yield all existing files (of any - kind, including directories) matching the given pattern. - """ - if not pattern: - raise ValueError("Unacceptable pattern: {0!r}".format(pattern)) - pattern = self._flavour.casefold(pattern) - drv, root, pattern_parts = self._flavour.parse_parts((pattern,)) - if drv or root: - raise NotImplementedError("Non-relative patterns are unsupported") - selector = _make_selector(tuple(pattern_parts)) - for p in selector.select_from(self): - yield p - - def rglob(self, pattern): - """Recursively yield all existing files (of any kind, including - directories) matching the given pattern, anywhere in this subtree. - """ - pattern = self._flavour.casefold(pattern) - drv, root, pattern_parts = self._flavour.parse_parts((pattern,)) - if drv or root: - raise NotImplementedError("Non-relative patterns are unsupported") - selector = _make_selector(("**",) + tuple(pattern_parts)) - for p in selector.select_from(self): - yield p - - def absolute(self): - """Return an absolute version of this path. This function works - even if the path doesn't point to anything. - - No normalization is done, i.e. all '.' and '..' will be kept along. - Use resolve() to get the canonical path to a file. - """ - # XXX untested yet! - if self._closed: - self._raise_closed() - if self.is_absolute(): - return self - # FIXME this must defer to the specific flavour (and, under Windows, - # use nt._getfullpathname()) - obj = self._from_parts([os.getcwd()] + self._parts, init=False) - obj._init(template=self) - return obj - - def resolve(self, strict=False): - """ - Make the path absolute, resolving all symlinks on the way and also - normalizing it (for example turning slashes into backslashes under - Windows). - """ - if self._closed: - self._raise_closed() - s = self._flavour.resolve(self, strict=strict) - if s is None: - # No symlink resolution => for consistency, raise an error if - # the path doesn't exist or is forbidden - self.stat() - s = str(self.absolute()) - # Now we have no symlinks in the path, it's safe to normalize it. - normed = self._flavour.pathmod.normpath(s) - obj = self._from_parts((normed,), init=False) - obj._init(template=self) - return obj - - def stat(self): - """ - Return the result of the stat() system call on this path, like - os.stat() does. - """ - return self._accessor.stat(self) - - def owner(self): - """ - Return the login name of the file owner. - """ - import pwd - return pwd.getpwuid(self.stat().st_uid).pw_name - - def group(self): - """ - Return the group name of the file gid. - """ - import grp - return grp.getgrgid(self.stat().st_gid).gr_name - - def open(self, mode='r', buffering=-1, encoding=None, - errors=None, newline=None): - """ - Open the file pointed by this path and return a file object, as - the built-in open() function does. - """ - if self._closed: - self._raise_closed() - if sys.version_info >= (3, 3): - return io.open( - str(self), mode, buffering, encoding, errors, newline, - opener=self._opener) - else: - return io.open(str(self), mode, buffering, - encoding, errors, newline) - - def read_bytes(self): - """ - Open the file in bytes mode, read it, and close the file. - """ - with self.open(mode='rb') as f: - return f.read() - - def read_text(self, encoding=None, errors=None): - """ - Open the file in text mode, read it, and close the file. - """ - with self.open(mode='r', encoding=encoding, errors=errors) as f: - return f.read() - - def write_bytes(self, data): - """ - Open the file in bytes mode, write to it, and close the file. - """ - if not isinstance(data, six.binary_type): - raise TypeError( - 'data must be %s, not %s' % - (six.binary_type.__name__, data.__class__.__name__)) - with self.open(mode='wb') as f: - return f.write(data) - - def write_text(self, data, encoding=None, errors=None): - """ - Open the file in text mode, write to it, and close the file. - """ - if not isinstance(data, six.text_type): - raise TypeError( - 'data must be %s, not %s' % - (six.text_type.__name__, data.__class__.__name__)) - with self.open(mode='w', encoding=encoding, errors=errors) as f: - return f.write(data) - - def touch(self, mode=0o666, exist_ok=True): - """ - Create this file with the given access mode, if it doesn't exist. - """ - if self._closed: - self._raise_closed() - if exist_ok: - # First try to bump modification time - # Implementation note: GNU touch uses the UTIME_NOW option of - # the utimensat() / futimens() functions. - try: - self._accessor.utime(self, None) - except OSError: - # Avoid exception chaining - pass - else: - return - flags = os.O_CREAT | os.O_WRONLY - if not exist_ok: - flags |= os.O_EXCL - fd = self._raw_open(flags, mode) - os.close(fd) - - def mkdir(self, mode=0o777, parents=False, exist_ok=False): - """ - Create a new directory at this given path. - """ - if self._closed: - self._raise_closed() - - def _try_func(): - self._accessor.mkdir(self, mode) - - def _exc_func(exc): - if not parents or self.parent == self: - raise exc - self.parent.mkdir(parents=True, exist_ok=True) - self.mkdir(mode, parents=False, exist_ok=exist_ok) - - try: - _try_except_filenotfounderror(_try_func, _exc_func) - except OSError: - if not exist_ok or not self.is_dir(): - raise - - def chmod(self, mode): - """ - Change the permissions of the path, like os.chmod(). - """ - if self._closed: - self._raise_closed() - self._accessor.chmod(self, mode) - - def lchmod(self, mode): - """ - Like chmod(), except if the path points to a symlink, the symlink's - permissions are changed, rather than its target's. - """ - if self._closed: - self._raise_closed() - self._accessor.lchmod(self, mode) - - def unlink(self): - """ - Remove this file or link. - If the path is a directory, use rmdir() instead. - """ - if self._closed: - self._raise_closed() - self._accessor.unlink(self) - - def rmdir(self): - """ - Remove this directory. The directory must be empty. - """ - if self._closed: - self._raise_closed() - self._accessor.rmdir(self) - - def lstat(self): - """ - Like stat(), except if the path points to a symlink, the symlink's - status information is returned, rather than its target's. - """ - if self._closed: - self._raise_closed() - return self._accessor.lstat(self) - - def rename(self, target): - """ - Rename this path to the given path. - """ - if self._closed: - self._raise_closed() - self._accessor.rename(self, target) - - def replace(self, target): - """ - Rename this path to the given path, clobbering the existing - destination if it exists. - """ - if sys.version_info < (3, 3): - raise NotImplementedError("replace() is only available " - "with Python 3.3 and later") - if self._closed: - self._raise_closed() - self._accessor.replace(self, target) - - def symlink_to(self, target, target_is_directory=False): - """ - Make this path a symlink pointing to the given path. - Note the order of arguments (self, target) is the reverse of - os.symlink's. - """ - if self._closed: - self._raise_closed() - self._accessor.symlink(target, self, target_is_directory) - - # Convenience functions for querying the stat results - - def exists(self): - """ - Whether this path exists. - """ - try: - self.stat() - except OSError as e: - if e.errno not in (ENOENT, ENOTDIR): - raise - return False - return True - - def is_dir(self): - """ - Whether this path is a directory. - """ - try: - return S_ISDIR(self.stat().st_mode) - except OSError as e: - if e.errno not in (ENOENT, ENOTDIR): - raise - # Path doesn't exist or is a broken symlink - # (see https://bitbucket.org/pitrou/pathlib/issue/12/) - return False - - def is_file(self): - """ - Whether this path is a regular file (also True for symlinks pointing - to regular files). - """ - try: - return S_ISREG(self.stat().st_mode) - except OSError as e: - if e.errno not in (ENOENT, ENOTDIR): - raise - # Path doesn't exist or is a broken symlink - # (see https://bitbucket.org/pitrou/pathlib/issue/12/) - return False - - def is_symlink(self): - """ - Whether this path is a symbolic link. - """ - try: - return S_ISLNK(self.lstat().st_mode) - except OSError as e: - if e.errno not in (ENOENT, ENOTDIR): - raise - # Path doesn't exist - return False - - def is_block_device(self): - """ - Whether this path is a block device. - """ - try: - return S_ISBLK(self.stat().st_mode) - except OSError as e: - if e.errno not in (ENOENT, ENOTDIR): - raise - # Path doesn't exist or is a broken symlink - # (see https://bitbucket.org/pitrou/pathlib/issue/12/) - return False - - def is_char_device(self): - """ - Whether this path is a character device. - """ - try: - return S_ISCHR(self.stat().st_mode) - except OSError as e: - if e.errno not in (ENOENT, ENOTDIR): - raise - # Path doesn't exist or is a broken symlink - # (see https://bitbucket.org/pitrou/pathlib/issue/12/) - return False - - def is_fifo(self): - """ - Whether this path is a FIFO. - """ - try: - return S_ISFIFO(self.stat().st_mode) - except OSError as e: - if e.errno not in (ENOENT, ENOTDIR): - raise - # Path doesn't exist or is a broken symlink - # (see https://bitbucket.org/pitrou/pathlib/issue/12/) - return False - - def is_socket(self): - """ - Whether this path is a socket. - """ - try: - return S_ISSOCK(self.stat().st_mode) - except OSError as e: - if e.errno not in (ENOENT, ENOTDIR): - raise - # Path doesn't exist or is a broken symlink - # (see https://bitbucket.org/pitrou/pathlib/issue/12/) - return False - - def expanduser(self): - """ Return a new path with expanded ~ and ~user constructs - (as returned by os.path.expanduser) - """ - if (not (self._drv or self._root) - and self._parts and self._parts[0][:1] == '~'): - homedir = self._flavour.gethomedir(self._parts[0][1:]) - return self._from_parts([homedir] + self._parts[1:]) - - return self - - -class PosixPath(Path, PurePosixPath): - __slots__ = () - - -class WindowsPath(Path, PureWindowsPath): - __slots__ = () - - def owner(self): - raise NotImplementedError("Path.owner() is unsupported on this system") - - def group(self): - raise NotImplementedError("Path.group() is unsupported on this system") diff --git a/pipenv/vendor/pep517/__init__.py b/pipenv/vendor/pep517/__init__.py index 9c1a098f..f064d60c 100644 --- a/pipenv/vendor/pep517/__init__.py +++ b/pipenv/vendor/pep517/__init__.py @@ -1,4 +1,6 @@ """Wrappers to build Python packages using PEP 517 hooks """ -__version__ = '0.5.0' +__version__ = '0.11.0' + +from .wrappers import * # noqa: F401, F403 diff --git a/pipenv/vendor/pep517/_in_process.py b/pipenv/vendor/pep517/_in_process.py deleted file mode 100644 index d6524b66..00000000 --- a/pipenv/vendor/pep517/_in_process.py +++ /dev/null @@ -1,207 +0,0 @@ -"""This is invoked in a subprocess to call the build backend hooks. - -It expects: -- Command line args: hook_name, control_dir -- Environment variable: PEP517_BUILD_BACKEND=entry.point:spec -- control_dir/input.json: - - {"kwargs": {...}} - -Results: -- control_dir/output.json - - {"return_val": ...} -""" -from glob import glob -from importlib import import_module -import os -from os.path import join as pjoin -import re -import shutil -import sys - -# This is run as a script, not a module, so it can't do a relative import -import compat - - -class BackendUnavailable(Exception): - """Raised if we cannot import the backend""" - - -def _build_backend(): - """Find and load the build backend""" - ep = os.environ['PEP517_BUILD_BACKEND'] - mod_path, _, obj_path = ep.partition(':') - try: - obj = import_module(mod_path) - except ImportError: - raise BackendUnavailable - if obj_path: - for path_part in obj_path.split('.'): - obj = getattr(obj, path_part) - return obj - - -def get_requires_for_build_wheel(config_settings): - """Invoke the optional get_requires_for_build_wheel hook - - Returns [] if the hook is not defined. - """ - backend = _build_backend() - try: - hook = backend.get_requires_for_build_wheel - except AttributeError: - return [] - else: - return hook(config_settings) - - -def prepare_metadata_for_build_wheel(metadata_directory, config_settings): - """Invoke optional prepare_metadata_for_build_wheel - - Implements a fallback by building a wheel if the hook isn't defined. - """ - backend = _build_backend() - try: - hook = backend.prepare_metadata_for_build_wheel - except AttributeError: - return _get_wheel_metadata_from_wheel(backend, metadata_directory, - config_settings) - else: - return hook(metadata_directory, config_settings) - - -WHEEL_BUILT_MARKER = 'PEP517_ALREADY_BUILT_WHEEL' - - -def _dist_info_files(whl_zip): - """Identify the .dist-info folder inside a wheel ZipFile.""" - res = [] - for path in whl_zip.namelist(): - m = re.match(r'[^/\\]+-[^/\\]+\.dist-info/', path) - if m: - res.append(path) - if res: - return res - raise Exception("No .dist-info folder found in wheel") - - -def _get_wheel_metadata_from_wheel( - backend, metadata_directory, config_settings): - """Build a wheel and extract the metadata from it. - - Fallback for when the build backend does not - define the 'get_wheel_metadata' hook. - """ - from zipfile import ZipFile - whl_basename = backend.build_wheel(metadata_directory, config_settings) - with open(os.path.join(metadata_directory, WHEEL_BUILT_MARKER), 'wb'): - pass # Touch marker file - - whl_file = os.path.join(metadata_directory, whl_basename) - with ZipFile(whl_file) as zipf: - dist_info = _dist_info_files(zipf) - zipf.extractall(path=metadata_directory, members=dist_info) - return dist_info[0].split('/')[0] - - -def _find_already_built_wheel(metadata_directory): - """Check for a wheel already built during the get_wheel_metadata hook. - """ - if not metadata_directory: - return None - metadata_parent = os.path.dirname(metadata_directory) - if not os.path.isfile(pjoin(metadata_parent, WHEEL_BUILT_MARKER)): - return None - - whl_files = glob(os.path.join(metadata_parent, '*.whl')) - if not whl_files: - print('Found wheel built marker, but no .whl files') - return None - if len(whl_files) > 1: - print('Found multiple .whl files; unspecified behaviour. ' - 'Will call build_wheel.') - return None - - # Exactly one .whl file - return whl_files[0] - - -def build_wheel(wheel_directory, config_settings, metadata_directory=None): - """Invoke the mandatory build_wheel hook. - - If a wheel was already built in the - prepare_metadata_for_build_wheel fallback, this - will copy it rather than rebuilding the wheel. - """ - prebuilt_whl = _find_already_built_wheel(metadata_directory) - if prebuilt_whl: - shutil.copy2(prebuilt_whl, wheel_directory) - return os.path.basename(prebuilt_whl) - - return _build_backend().build_wheel(wheel_directory, config_settings, - metadata_directory) - - -def get_requires_for_build_sdist(config_settings): - """Invoke the optional get_requires_for_build_wheel hook - - Returns [] if the hook is not defined. - """ - backend = _build_backend() - try: - hook = backend.get_requires_for_build_sdist - except AttributeError: - return [] - else: - return hook(config_settings) - - -class _DummyException(Exception): - """Nothing should ever raise this exception""" - - -class GotUnsupportedOperation(Exception): - """For internal use when backend raises UnsupportedOperation""" - - -def build_sdist(sdist_directory, config_settings): - """Invoke the mandatory build_sdist hook.""" - backend = _build_backend() - try: - return backend.build_sdist(sdist_directory, config_settings) - except getattr(backend, 'UnsupportedOperation', _DummyException): - raise GotUnsupportedOperation - - -HOOK_NAMES = { - 'get_requires_for_build_wheel', - 'prepare_metadata_for_build_wheel', - 'build_wheel', - 'get_requires_for_build_sdist', - 'build_sdist', -} - - -def main(): - if len(sys.argv) < 3: - sys.exit("Needs args: hook_name, control_dir") - hook_name = sys.argv[1] - control_dir = sys.argv[2] - if hook_name not in HOOK_NAMES: - sys.exit("Unknown hook: %s" % hook_name) - hook = globals()[hook_name] - - hook_input = compat.read_json(pjoin(control_dir, 'input.json')) - - json_out = {'unsupported': False, 'return_val': None} - try: - json_out['return_val'] = hook(**hook_input['kwargs']) - except BackendUnavailable: - json_out['no_backend'] = True - except GotUnsupportedOperation: - json_out['unsupported'] = True - - compat.write_json(json_out, pjoin(control_dir, 'output.json'), indent=2) - - -if __name__ == '__main__': - main() diff --git a/pipenv/vendor/pep517/build.py b/pipenv/vendor/pep517/build.py index 6fca39a8..3b752145 100644 --- a/pipenv/vendor/pep517/build.py +++ b/pipenv/vendor/pep517/build.py @@ -1,27 +1,58 @@ """Build a project using PEP 517 hooks. """ import argparse +import io import logging import os -import contextlib -import pytoml import shutil -import errno -import tempfile from .envbuild import BuildEnvironment from .wrappers import Pep517HookCaller +from .dirtools import tempdir, mkdir_p +from .compat import FileNotFoundError, toml_load log = logging.getLogger(__name__) -@contextlib.contextmanager -def tempdir(): - td = tempfile.mkdtemp() +def validate_system(system): + """ + Ensure build system has the requisite fields. + """ + required = {'requires', 'build-backend'} + if not (required <= set(system)): + message = "Missing required fields: {missing}".format( + missing=required-set(system), + ) + raise ValueError(message) + + +def load_system(source_dir): + """ + Load the build system from a source dir (pyproject.toml). + """ + pyproject = os.path.join(source_dir, 'pyproject.toml') + with io.open(pyproject, encoding="utf-8") as f: + pyproject_data = toml_load(f) + return pyproject_data['build-system'] + + +def compat_system(source_dir): + """ + Given a source dir, attempt to get a build system backend + and requirements from pyproject.toml. Fallback to + setuptools but only if the file was not found or a build + system was not indicated. + """ try: - yield td - finally: - shutil.rmtree(td) + system = load_system(source_dir) + except (FileNotFoundError, KeyError): + system = {} + system.setdefault( + 'build-backend', + 'setuptools.build_meta:__legacy__', + ) + system.setdefault('requires', ['setuptools', 'wheel']) + return system def _do_build(hooks, env, dist, dest): @@ -42,33 +73,18 @@ def _do_build(hooks, env, dist, dest): shutil.move(source, os.path.join(dest, os.path.basename(filename))) -def mkdir_p(*args, **kwargs): - """Like `mkdir`, but does not raise an exception if the - directory already exists. - """ - try: - return os.mkdir(*args, **kwargs) - except OSError as exc: - if exc.errno != errno.EEXIST: - raise - - -def build(source_dir, dist, dest=None): - pyproject = os.path.join(source_dir, 'pyproject.toml') +def build(source_dir, dist, dest=None, system=None): + system = system or load_system(source_dir) dest = os.path.join(source_dir, dest or 'dist') mkdir_p(dest) - with open(pyproject) as f: - pyproject_data = pytoml.load(f) - # Ensure the mandatory data can be loaded - buildsys = pyproject_data['build-system'] - requires = buildsys['requires'] - backend = buildsys['build-backend'] - - hooks = Pep517HookCaller(source_dir, backend) + validate_system(system) + hooks = Pep517HookCaller( + source_dir, system['build-backend'], system.get('backend-path') + ) with BuildEnvironment() as env: - env.pip_install(requires) + env.pip_install(system['requires']) _do_build(hooks, env, dist, dest) @@ -94,6 +110,9 @@ parser.add_argument( def main(args): + log.warning('pep517.build is deprecated. ' + 'Consider switching to https://pypi.org/project/build/') + # determine which dists to build dists = list(filter(None, ( 'sdist' if args.source or not args.binary else None, diff --git a/pipenv/vendor/pep517/check.py b/pipenv/vendor/pep517/check.py index fc82cca7..719be040 100644 --- a/pipenv/vendor/pep517/check.py +++ b/pipenv/vendor/pep517/check.py @@ -1,10 +1,10 @@ """Check a project and backend by attempting to build using PEP 517 hooks. """ import argparse +import io import logging import os from os.path import isfile, join as pjoin -from pytoml import TomlError, load as toml_load import shutil from subprocess import CalledProcessError import sys @@ -13,6 +13,7 @@ from tempfile import mkdtemp import zipfile from .colorlog import enable_colourful_output +from .compat import TOMLDecodeError, toml_load from .envbuild import BuildEnvironment from .wrappers import Pep517HookCaller @@ -141,18 +142,19 @@ def check(source_dir): return False try: - with open(pyproject) as f: + with io.open(pyproject, encoding="utf-8") as f: pyproject_data = toml_load(f) # Ensure the mandatory data can be loaded buildsys = pyproject_data['build-system'] requires = buildsys['requires'] backend = buildsys['build-backend'] + backend_path = buildsys.get('backend-path') log.info('Loaded pyproject.toml') - except (TomlError, KeyError): + except (TOMLDecodeError, KeyError): log.error("Invalid pyproject.toml", exc_info=True) return False - hooks = Pep517HookCaller(source_dir, backend) + hooks = Pep517HookCaller(source_dir, backend, backend_path) sdist_ok = check_build_sdist(hooks, requires) wheel_ok = check_build_wheel(hooks, requires) @@ -166,6 +168,9 @@ def check(source_dir): def main(argv=None): + log.warning('pep517.check is deprecated. ' + 'Consider switching to https://pypi.org/project/build/') + ap = argparse.ArgumentParser() ap.add_argument( 'source_dir', diff --git a/pipenv/vendor/pep517/compat.py b/pipenv/vendor/pep517/compat.py index 01c66fc7..900f48a2 100644 --- a/pipenv/vendor/pep517/compat.py +++ b/pipenv/vendor/pep517/compat.py @@ -1,7 +1,10 @@ -"""Handle reading and writing JSON in UTF-8, on Python 3 and 2.""" +"""Python 2/3 compatibility""" import json import sys + +# Handle reading and writing JSON in UTF-8, on Python 3 and 2. + if sys.version_info[0] >= 3: # Python 3 def write_json(obj, path, **kwargs): @@ -21,3 +24,19 @@ else: def read_json(path): with open(path, 'rb') as f: return json.load(f) + + +# FileNotFoundError + +try: + FileNotFoundError = FileNotFoundError +except NameError: + FileNotFoundError = IOError + + +if sys.version_info < (3, 6): + from pipenv.vendor.toml import load as toml_load # noqa: F401 + from pipenv.vendor.toml import TomlDecodeError as TOMLDecodeError # noqa: F401 +else: + from pipenv.vendor.tomli import load as toml_load # noqa: F401 + from pipenv.vendor.tomli import TOMLDecodeError # noqa: F401 diff --git a/pipenv/vendor/pep517/dirtools.py b/pipenv/vendor/pep517/dirtools.py new file mode 100644 index 00000000..58c6ca0c --- /dev/null +++ b/pipenv/vendor/pep517/dirtools.py @@ -0,0 +1,44 @@ +import os +import io +import contextlib +import tempfile +import shutil +import errno +import zipfile + + +@contextlib.contextmanager +def tempdir(): + """Create a temporary directory in a context manager.""" + td = tempfile.mkdtemp() + try: + yield td + finally: + shutil.rmtree(td) + + +def mkdir_p(*args, **kwargs): + """Like `mkdir`, but does not raise an exception if the + directory already exists. + """ + try: + return os.mkdir(*args, **kwargs) + except OSError as exc: + if exc.errno != errno.EEXIST: + raise + + +def dir_to_zipfile(root): + """Construct an in-memory zip file for a directory.""" + buffer = io.BytesIO() + zip_file = zipfile.ZipFile(buffer, 'w') + for root, dirs, files in os.walk(root): + for path in dirs: + fs_path = os.path.join(root, path) + rel_path = os.path.relpath(fs_path, root) + zip_file.writestr(rel_path + '/', '') + for path in files: + fs_path = os.path.join(root, path) + rel_path = os.path.relpath(fs_path, root) + zip_file.write(fs_path, rel_path) + return zip_file diff --git a/pipenv/vendor/pep517/envbuild.py b/pipenv/vendor/pep517/envbuild.py index 61253f4d..7c2344bf 100644 --- a/pipenv/vendor/pep517/envbuild.py +++ b/pipenv/vendor/pep517/envbuild.py @@ -1,25 +1,33 @@ """Build wheels/sdists by installing build deps to a temporary environment. """ +import io import os import logging -import pytoml import shutil from subprocess import check_call import sys from sysconfig import get_paths from tempfile import mkdtemp -from .wrappers import Pep517HookCaller +from .compat import toml_load +from .wrappers import Pep517HookCaller, LoggerWrapper log = logging.getLogger(__name__) def _load_pyproject(source_dir): - with open(os.path.join(source_dir, 'pyproject.toml')) as f: - pyproject_data = pytoml.load(f) + with io.open( + os.path.join(source_dir, 'pyproject.toml'), + encoding="utf-8", + ) as f: + pyproject_data = toml_load(f) buildsys = pyproject_data['build-system'] - return buildsys['requires'], buildsys['build-backend'] + return ( + buildsys['requires'], + buildsys['build-backend'], + buildsys.get('backend-path'), + ) class BuildEnvironment(object): @@ -90,9 +98,14 @@ class BuildEnvironment(object): if not reqs: return log.info('Calling pip to install %s', reqs) - check_call([ + cmd = [ sys.executable, '-m', 'pip', 'install', '--ignore-installed', - '--prefix', self.path] + list(reqs)) + '--prefix', self.path] + list(reqs) + check_call( + cmd, + stdout=LoggerWrapper(log, logging.INFO), + stderr=LoggerWrapper(log, logging.ERROR), + ) def __exit__(self, exc_type, exc_val, exc_tb): needs_cleanup = ( @@ -126,8 +139,8 @@ def build_wheel(source_dir, wheel_dir, config_settings=None): """ if config_settings is None: config_settings = {} - requires, backend = _load_pyproject(source_dir) - hooks = Pep517HookCaller(source_dir, backend) + requires, backend, backend_path = _load_pyproject(source_dir) + hooks = Pep517HookCaller(source_dir, backend, backend_path) with BuildEnvironment() as env: env.pip_install(requires) @@ -148,8 +161,8 @@ def build_sdist(source_dir, sdist_dir, config_settings=None): """ if config_settings is None: config_settings = {} - requires, backend = _load_pyproject(source_dir) - hooks = Pep517HookCaller(source_dir, backend) + requires, backend, backend_path = _load_pyproject(source_dir) + hooks = Pep517HookCaller(source_dir, backend, backend_path) with BuildEnvironment() as env: env.pip_install(requires) diff --git a/pipenv/vendor/pep517/in_process/__init__.py b/pipenv/vendor/pep517/in_process/__init__.py new file mode 100644 index 00000000..c932313b --- /dev/null +++ b/pipenv/vendor/pep517/in_process/__init__.py @@ -0,0 +1,17 @@ +"""This is a subpackage because the directory is on sys.path for _in_process.py + +The subpackage should stay as empty as possible to avoid shadowing modules that +the backend might import. +""" +from os.path import dirname, abspath, join as pjoin +from contextlib import contextmanager + +try: + import importlib.resources as resources + + def _in_proc_script_path(): + return resources.path(__package__, '_in_process.py') +except ImportError: + @contextmanager + def _in_proc_script_path(): + yield pjoin(dirname(abspath(__file__)), '_in_process.py') diff --git a/pipenv/vendor/pep517/in_process/_in_process.py b/pipenv/vendor/pep517/in_process/_in_process.py new file mode 100644 index 00000000..c7f5f057 --- /dev/null +++ b/pipenv/vendor/pep517/in_process/_in_process.py @@ -0,0 +1,349 @@ +"""This is invoked in a subprocess to call the build backend hooks. + +It expects: +- Command line args: hook_name, control_dir +- Environment variables: + PEP517_BUILD_BACKEND=entry.point:spec + PEP517_BACKEND_PATH=paths (separated with os.pathsep) +- control_dir/input.json: + - {"kwargs": {...}} + +Results: +- control_dir/output.json + - {"return_val": ...} +""" +from glob import glob +from importlib import import_module +import json +import os +import os.path +from os.path import join as pjoin +import re +import shutil +import sys +import traceback + +# This file is run as a script, and `import compat` is not zip-safe, so we +# include write_json() and read_json() from compat.py. +# +# Handle reading and writing JSON in UTF-8, on Python 3 and 2. + +if sys.version_info[0] >= 3: + # Python 3 + def write_json(obj, path, **kwargs): + with open(path, 'w', encoding='utf-8') as f: + json.dump(obj, f, **kwargs) + + def read_json(path): + with open(path, 'r', encoding='utf-8') as f: + return json.load(f) + +else: + # Python 2 + def write_json(obj, path, **kwargs): + with open(path, 'wb') as f: + json.dump(obj, f, encoding='utf-8', **kwargs) + + def read_json(path): + with open(path, 'rb') as f: + return json.load(f) + + +class BackendUnavailable(Exception): + """Raised if we cannot import the backend""" + def __init__(self, traceback): + self.traceback = traceback + + +class BackendInvalid(Exception): + """Raised if the backend is invalid""" + def __init__(self, message): + self.message = message + + +class HookMissing(Exception): + """Raised if a hook is missing and we are not executing the fallback""" + def __init__(self, hook_name=None): + super(HookMissing, self).__init__(hook_name) + self.hook_name = hook_name + + +def contained_in(filename, directory): + """Test if a file is located within the given directory.""" + filename = os.path.normcase(os.path.abspath(filename)) + directory = os.path.normcase(os.path.abspath(directory)) + return os.path.commonprefix([filename, directory]) == directory + + +def _build_backend(): + """Find and load the build backend""" + # Add in-tree backend directories to the front of sys.path. + backend_path = os.environ.get('PEP517_BACKEND_PATH') + if backend_path: + extra_pathitems = backend_path.split(os.pathsep) + sys.path[:0] = extra_pathitems + + ep = os.environ['PEP517_BUILD_BACKEND'] + mod_path, _, obj_path = ep.partition(':') + try: + obj = import_module(mod_path) + except ImportError: + raise BackendUnavailable(traceback.format_exc()) + + if backend_path: + if not any( + contained_in(obj.__file__, path) + for path in extra_pathitems + ): + raise BackendInvalid("Backend was not loaded from backend-path") + + if obj_path: + for path_part in obj_path.split('.'): + obj = getattr(obj, path_part) + return obj + + +def get_requires_for_build_wheel(config_settings): + """Invoke the optional get_requires_for_build_wheel hook + + Returns [] if the hook is not defined. + """ + backend = _build_backend() + try: + hook = backend.get_requires_for_build_wheel + except AttributeError: + return [] + else: + return hook(config_settings) + + +def get_requires_for_build_editable(config_settings): + """Invoke the optional get_requires_for_build_editable hook + + Returns [] if the hook is not defined. + """ + backend = _build_backend() + try: + hook = backend.get_requires_for_build_editable + except AttributeError: + return [] + else: + return hook(config_settings) + + +def prepare_metadata_for_build_wheel( + metadata_directory, config_settings, _allow_fallback): + """Invoke optional prepare_metadata_for_build_wheel + + Implements a fallback by building a wheel if the hook isn't defined, + unless _allow_fallback is False in which case HookMissing is raised. + """ + backend = _build_backend() + try: + hook = backend.prepare_metadata_for_build_wheel + except AttributeError: + if not _allow_fallback: + raise HookMissing() + whl_basename = backend.build_wheel(metadata_directory, config_settings) + return _get_wheel_metadata_from_wheel(whl_basename, metadata_directory, + config_settings) + else: + return hook(metadata_directory, config_settings) + + +def prepare_metadata_for_build_editable( + metadata_directory, config_settings, _allow_fallback): + """Invoke optional prepare_metadata_for_build_editable + + Implements a fallback by building an editable wheel if the hook isn't + defined, unless _allow_fallback is False in which case HookMissing is + raised. + """ + backend = _build_backend() + try: + hook = backend.prepare_metadata_for_build_editable + except AttributeError: + if not _allow_fallback: + raise HookMissing() + try: + build_hook = backend.build_editable + except AttributeError: + raise HookMissing(hook_name='build_editable') + else: + whl_basename = build_hook(metadata_directory, config_settings) + return _get_wheel_metadata_from_wheel(whl_basename, + metadata_directory, + config_settings) + else: + return hook(metadata_directory, config_settings) + + +WHEEL_BUILT_MARKER = 'PEP517_ALREADY_BUILT_WHEEL' + + +def _dist_info_files(whl_zip): + """Identify the .dist-info folder inside a wheel ZipFile.""" + res = [] + for path in whl_zip.namelist(): + m = re.match(r'[^/\\]+-[^/\\]+\.dist-info/', path) + if m: + res.append(path) + if res: + return res + raise Exception("No .dist-info folder found in wheel") + + +def _get_wheel_metadata_from_wheel( + whl_basename, metadata_directory, config_settings): + """Extract the metadata from a wheel. + + Fallback for when the build backend does not + define the 'get_wheel_metadata' hook. + """ + from zipfile import ZipFile + with open(os.path.join(metadata_directory, WHEEL_BUILT_MARKER), 'wb'): + pass # Touch marker file + + whl_file = os.path.join(metadata_directory, whl_basename) + with ZipFile(whl_file) as zipf: + dist_info = _dist_info_files(zipf) + zipf.extractall(path=metadata_directory, members=dist_info) + return dist_info[0].split('/')[0] + + +def _find_already_built_wheel(metadata_directory): + """Check for a wheel already built during the get_wheel_metadata hook. + """ + if not metadata_directory: + return None + metadata_parent = os.path.dirname(metadata_directory) + if not os.path.isfile(pjoin(metadata_parent, WHEEL_BUILT_MARKER)): + return None + + whl_files = glob(os.path.join(metadata_parent, '*.whl')) + if not whl_files: + print('Found wheel built marker, but no .whl files') + return None + if len(whl_files) > 1: + print('Found multiple .whl files; unspecified behaviour. ' + 'Will call build_wheel.') + return None + + # Exactly one .whl file + return whl_files[0] + + +def build_wheel(wheel_directory, config_settings, metadata_directory=None): + """Invoke the mandatory build_wheel hook. + + If a wheel was already built in the + prepare_metadata_for_build_wheel fallback, this + will copy it rather than rebuilding the wheel. + """ + prebuilt_whl = _find_already_built_wheel(metadata_directory) + if prebuilt_whl: + shutil.copy2(prebuilt_whl, wheel_directory) + return os.path.basename(prebuilt_whl) + + return _build_backend().build_wheel(wheel_directory, config_settings, + metadata_directory) + + +def build_editable(wheel_directory, config_settings, metadata_directory=None): + """Invoke the optional build_editable hook. + + If a wheel was already built in the + prepare_metadata_for_build_editable fallback, this + will copy it rather than rebuilding the wheel. + """ + backend = _build_backend() + try: + hook = backend.build_editable + except AttributeError: + raise HookMissing() + else: + prebuilt_whl = _find_already_built_wheel(metadata_directory) + if prebuilt_whl: + shutil.copy2(prebuilt_whl, wheel_directory) + return os.path.basename(prebuilt_whl) + + return hook(wheel_directory, config_settings, metadata_directory) + + +def get_requires_for_build_sdist(config_settings): + """Invoke the optional get_requires_for_build_wheel hook + + Returns [] if the hook is not defined. + """ + backend = _build_backend() + try: + hook = backend.get_requires_for_build_sdist + except AttributeError: + return [] + else: + return hook(config_settings) + + +class _DummyException(Exception): + """Nothing should ever raise this exception""" + + +class GotUnsupportedOperation(Exception): + """For internal use when backend raises UnsupportedOperation""" + def __init__(self, traceback): + self.traceback = traceback + + +def build_sdist(sdist_directory, config_settings): + """Invoke the mandatory build_sdist hook.""" + backend = _build_backend() + try: + return backend.build_sdist(sdist_directory, config_settings) + except getattr(backend, 'UnsupportedOperation', _DummyException): + raise GotUnsupportedOperation(traceback.format_exc()) + + +HOOK_NAMES = { + 'get_requires_for_build_wheel', + 'prepare_metadata_for_build_wheel', + 'build_wheel', + 'get_requires_for_build_editable', + 'prepare_metadata_for_build_editable', + 'build_editable', + 'get_requires_for_build_sdist', + 'build_sdist', +} + + +def main(): + if len(sys.argv) < 3: + sys.exit("Needs args: hook_name, control_dir") + hook_name = sys.argv[1] + control_dir = sys.argv[2] + if hook_name not in HOOK_NAMES: + sys.exit("Unknown hook: %s" % hook_name) + hook = globals()[hook_name] + + hook_input = read_json(pjoin(control_dir, 'input.json')) + + json_out = {'unsupported': False, 'return_val': None} + try: + json_out['return_val'] = hook(**hook_input['kwargs']) + except BackendUnavailable as e: + json_out['no_backend'] = True + json_out['traceback'] = e.traceback + except BackendInvalid as e: + json_out['backend_invalid'] = True + json_out['backend_error'] = e.message + except GotUnsupportedOperation as e: + json_out['unsupported'] = True + json_out['traceback'] = e.traceback + except HookMissing as e: + json_out['hook_missing'] = True + json_out['missing_hook_name'] = e.hook_name or hook_name + + write_json(json_out, pjoin(control_dir, 'output.json'), indent=2) + + +if __name__ == '__main__': + main() diff --git a/pipenv/vendor/pep517/meta.py b/pipenv/vendor/pep517/meta.py new file mode 100644 index 00000000..5f7d9485 --- /dev/null +++ b/pipenv/vendor/pep517/meta.py @@ -0,0 +1,92 @@ +"""Build metadata for a project using PEP 517 hooks. +""" +import argparse +import logging +import os +import shutil +import functools + +try: + import importlib.metadata as imp_meta +except ImportError: + import pipenv.vendor.importlib_metadata as imp_meta + +try: + from zipfile import Path +except ImportError: + from pipenv.vendor.zipp import Path + +from .envbuild import BuildEnvironment +from .wrappers import Pep517HookCaller, quiet_subprocess_runner +from .dirtools import tempdir, mkdir_p, dir_to_zipfile +from .build import validate_system, load_system, compat_system + +log = logging.getLogger(__name__) + + +def _prep_meta(hooks, env, dest): + reqs = hooks.get_requires_for_build_wheel({}) + log.info('Got build requires: %s', reqs) + + env.pip_install(reqs) + log.info('Installed dynamic build dependencies') + + with tempdir() as td: + log.info('Trying to build metadata in %s', td) + filename = hooks.prepare_metadata_for_build_wheel(td, {}) + source = os.path.join(td, filename) + shutil.move(source, os.path.join(dest, os.path.basename(filename))) + + +def build(source_dir='.', dest=None, system=None): + system = system or load_system(source_dir) + dest = os.path.join(source_dir, dest or 'dist') + mkdir_p(dest) + validate_system(system) + hooks = Pep517HookCaller( + source_dir, system['build-backend'], system.get('backend-path') + ) + + with hooks.subprocess_runner(quiet_subprocess_runner): + with BuildEnvironment() as env: + env.pip_install(system['requires']) + _prep_meta(hooks, env, dest) + + +def build_as_zip(builder=build): + with tempdir() as out_dir: + builder(dest=out_dir) + return dir_to_zipfile(out_dir) + + +def load(root): + """ + Given a source directory (root) of a package, + return an importlib.metadata.Distribution object + with metadata build from that package. + """ + root = os.path.expanduser(root) + system = compat_system(root) + builder = functools.partial(build, source_dir=root, system=system) + path = Path(build_as_zip(builder)) + return imp_meta.PathDistribution(path) + + +parser = argparse.ArgumentParser() +parser.add_argument( + 'source_dir', + help="A directory containing pyproject.toml", +) +parser.add_argument( + '--out-dir', '-o', + help="Destination in which to save the builds relative to source dir", +) + + +def main(): + args = parser.parse_args() + build(args.source_dir, args.out_dir) + + +if __name__ == '__main__': + main() diff --git a/pipenv/vendor/pep517/wrappers.py b/pipenv/vendor/pep517/wrappers.py index b14b8991..52da22e8 100644 --- a/pipenv/vendor/pep517/wrappers.py +++ b/pipenv/vendor/pep517/wrappers.py @@ -1,14 +1,24 @@ +import threading from contextlib import contextmanager import os -from os.path import dirname, abspath, join as pjoin +from os.path import abspath, join as pjoin import shutil -from subprocess import check_call +from subprocess import check_call, check_output, STDOUT import sys from tempfile import mkdtemp from . import compat +from .in_process import _in_proc_script_path -_in_proc_script = pjoin(dirname(abspath(__file__)), '_in_process.py') +__all__ = [ + 'BackendUnavailable', + 'BackendInvalid', + 'HookMissing', + 'UnsupportedOperation', + 'default_subprocess_runner', + 'quiet_subprocess_runner', + 'Pep517HookCaller', +] @contextmanager @@ -22,10 +32,29 @@ def tempdir(): class BackendUnavailable(Exception): """Will be raised if the backend cannot be imported in the hook process.""" + def __init__(self, traceback): + self.traceback = traceback + + +class BackendInvalid(Exception): + """Will be raised if the backend is invalid.""" + def __init__(self, backend_name, backend_path, message): + self.backend_name = backend_name + self.backend_path = backend_path + self.message = message + + +class HookMissing(Exception): + """Will be raised on missing hooks.""" + def __init__(self, hook_name): + super(HookMissing, self).__init__(hook_name) + self.hook_name = hook_name class UnsupportedOperation(Exception): """May be raised by build_sdist if the backend indicates that it can't.""" + def __init__(self, traceback): + self.traceback = traceback def default_subprocess_runner(cmd, cwd=None, extra_environ=None): @@ -37,30 +66,99 @@ def default_subprocess_runner(cmd, cwd=None, extra_environ=None): check_call(cmd, cwd=cwd, env=env) +def quiet_subprocess_runner(cmd, cwd=None, extra_environ=None): + """A method of calling the wrapper subprocess while suppressing output.""" + env = os.environ.copy() + if extra_environ: + env.update(extra_environ) + + check_output(cmd, cwd=cwd, env=env, stderr=STDOUT) + + +def norm_and_check(source_tree, requested): + """Normalise and check a backend path. + + Ensure that the requested backend path is specified as a relative path, + and resolves to a location under the given source tree. + + Return an absolute version of the requested path. + """ + if os.path.isabs(requested): + raise ValueError("paths must be relative") + + abs_source = os.path.abspath(source_tree) + abs_requested = os.path.normpath(os.path.join(abs_source, requested)) + # We have to use commonprefix for Python 2.7 compatibility. So we + # normalise case to avoid problems because commonprefix is a character + # based comparison :-( + norm_source = os.path.normcase(abs_source) + norm_requested = os.path.normcase(abs_requested) + if os.path.commonprefix([norm_source, norm_requested]) != norm_source: + raise ValueError("paths must be inside source tree") + + return abs_requested + + class Pep517HookCaller(object): """A wrapper around a source directory to be built with a PEP 517 backend. - source_dir : The path to the source directory, containing pyproject.toml. - backend : The build backend spec, as per PEP 517, from pyproject.toml. + :param source_dir: The path to the source directory, containing + pyproject.toml. + :param build_backend: The build backend spec, as per PEP 517, from + pyproject.toml. + :param backend_path: The backend path, as per PEP 517, from pyproject.toml. + :param runner: A callable that invokes the wrapper subprocess. + :param python_executable: The Python executable used to invoke the backend + + The 'runner', if provided, must expect the following: + + - cmd: a list of strings representing the command and arguments to + execute, as would be passed to e.g. 'subprocess.check_call'. + - cwd: a string representing the working directory that must be + used for the subprocess. Corresponds to the provided source_dir. + - extra_environ: a dict mapping environment variable names to values + which must be set for the subprocess execution. """ - def __init__(self, source_dir, build_backend): + def __init__( + self, + source_dir, + build_backend, + backend_path=None, + runner=None, + python_executable=None, + ): + if runner is None: + runner = default_subprocess_runner + self.source_dir = abspath(source_dir) self.build_backend = build_backend - self._subprocess_runner = default_subprocess_runner + if backend_path: + backend_path = [ + norm_and_check(self.source_dir, p) for p in backend_path + ] + self.backend_path = backend_path + self._subprocess_runner = runner + if not python_executable: + python_executable = sys.executable + self.python_executable = python_executable - # TODO: Is this over-engineered? Maybe frontends only need to - # set this when creating the wrapper, not on every call. @contextmanager def subprocess_runner(self, runner): + """A context manager for temporarily overriding the default subprocess + runner. + """ prev = self._subprocess_runner self._subprocess_runner = runner - yield - self._subprocess_runner = prev + try: + yield + finally: + self._subprocess_runner = prev def get_requires_for_build_wheel(self, config_settings=None): """Identify packages required for building a wheel - Returns a list of dependency specifications, e.g.: + Returns a list of dependency specifications, e.g.:: + ["wheel >= 0.25", "setuptools"] This does not include requirements specified in pyproject.toml. @@ -72,18 +170,21 @@ class Pep517HookCaller(object): }) def prepare_metadata_for_build_wheel( - self, metadata_directory, config_settings=None): - """Prepare a *.dist-info folder with metadata for this project. + self, metadata_directory, config_settings=None, + _allow_fallback=True): + """Prepare a ``*.dist-info`` folder with metadata for this project. Returns the name of the newly created folder. If the build backend defines a hook with this name, it will be called in a subprocess. If not, the backend will be asked to build a wheel, - and the dist-info extracted from that. + and the dist-info extracted from that (unless _allow_fallback is + False). """ return self._call_hook('prepare_metadata_for_build_wheel', { 'metadata_directory': abspath(metadata_directory), 'config_settings': config_settings, + '_allow_fallback': _allow_fallback, }) def build_wheel( @@ -106,10 +207,64 @@ class Pep517HookCaller(object): 'metadata_directory': metadata_directory, }) + def get_requires_for_build_editable(self, config_settings=None): + """Identify packages required for building an editable wheel + + Returns a list of dependency specifications, e.g.:: + + ["wheel >= 0.25", "setuptools"] + + This does not include requirements specified in pyproject.toml. + It returns the result of calling the equivalently named hook in a + subprocess. + """ + return self._call_hook('get_requires_for_build_editable', { + 'config_settings': config_settings + }) + + def prepare_metadata_for_build_editable( + self, metadata_directory, config_settings=None, + _allow_fallback=True): + """Prepare a ``*.dist-info`` folder with metadata for this project. + + Returns the name of the newly created folder. + + If the build backend defines a hook with this name, it will be called + in a subprocess. If not, the backend will be asked to build an editable + wheel, and the dist-info extracted from that (unless _allow_fallback is + False). + """ + return self._call_hook('prepare_metadata_for_build_editable', { + 'metadata_directory': abspath(metadata_directory), + 'config_settings': config_settings, + '_allow_fallback': _allow_fallback, + }) + + def build_editable( + self, wheel_directory, config_settings=None, + metadata_directory=None): + """Build an editable wheel from this project. + + Returns the name of the newly created file. + + In general, this will call the 'build_editable' hook in the backend. + However, if that was previously called by + 'prepare_metadata_for_build_editable', and the same metadata_directory + is used, the previously built wheel will be copied to wheel_directory. + """ + if metadata_directory is not None: + metadata_directory = abspath(metadata_directory) + return self._call_hook('build_editable', { + 'wheel_directory': abspath(wheel_directory), + 'config_settings': config_settings, + 'metadata_directory': metadata_directory, + }) + def get_requires_for_build_sdist(self, config_settings=None): """Identify packages required for building a wheel - Returns a list of dependency specifications, e.g.: + Returns a list of dependency specifications, e.g.:: + ["setuptools >= 26"] This does not include requirements specified in pyproject.toml. @@ -139,25 +294,78 @@ class Pep517HookCaller(object): # letters, digits and _, . and : characters, and will be used as a # Python identifier, so non-ASCII content is wrong on Python 2 in # any case). + # For backend_path, we use sys.getfilesystemencoding. if sys.version_info[0] == 2: build_backend = self.build_backend.encode('ASCII') else: build_backend = self.build_backend + extra_environ = {'PEP517_BUILD_BACKEND': build_backend} + + if self.backend_path: + backend_path = os.pathsep.join(self.backend_path) + if sys.version_info[0] == 2: + backend_path = backend_path.encode(sys.getfilesystemencoding()) + extra_environ['PEP517_BACKEND_PATH'] = backend_path with tempdir() as td: - compat.write_json({'kwargs': kwargs}, pjoin(td, 'input.json'), + hook_input = {'kwargs': kwargs} + compat.write_json(hook_input, pjoin(td, 'input.json'), indent=2) # Run the hook in a subprocess - self._subprocess_runner( - [sys.executable, _in_proc_script, hook_name, td], - cwd=self.source_dir, - extra_environ={'PEP517_BUILD_BACKEND': build_backend} - ) + with _in_proc_script_path() as script: + python = self.python_executable + self._subprocess_runner( + [python, abspath(str(script)), hook_name, td], + cwd=self.source_dir, + extra_environ=extra_environ + ) data = compat.read_json(pjoin(td, 'output.json')) if data.get('unsupported'): - raise UnsupportedOperation + raise UnsupportedOperation(data.get('traceback', '')) if data.get('no_backend'): - raise BackendUnavailable + raise BackendUnavailable(data.get('traceback', '')) + if data.get('backend_invalid'): + raise BackendInvalid( + backend_name=self.build_backend, + backend_path=self.backend_path, + message=data.get('backend_error', '') + ) + if data.get('hook_missing'): + raise HookMissing(data.get('missing_hook_name') or hook_name) return data['return_val'] + + +class LoggerWrapper(threading.Thread): + """ + Read messages from a pipe and redirect them + to a logger (see python's logging module). + """ + + def __init__(self, logger, level): + threading.Thread.__init__(self) + self.daemon = True + + self.logger = logger + self.level = level + + # create the pipe and reader + self.fd_read, self.fd_write = os.pipe() + self.reader = os.fdopen(self.fd_read) + + self.start() + + def fileno(self): + return self.fd_write + + @staticmethod + def remove_newline(msg): + return msg[:-1] if msg.endswith(os.linesep) else msg + + def run(self): + for line in self.reader: + self._write(self.remove_newline(line)) + + def _write(self, message): + self.logger.log(self.level, message) diff --git a/pipenv/vendor/pexpect/__init__.py b/pipenv/vendor/pexpect/__init__.py index cf7a70d0..7e304537 100644 --- a/pipenv/vendor/pexpect/__init__.py +++ b/pipenv/vendor/pexpect/__init__.py @@ -75,7 +75,7 @@ if sys.platform != 'win32': from .pty_spawn import spawn, spawnu from .run import run, runu -__version__ = '4.7.0' +__version__ = '4.8.0' __revision__ = '' __all__ = ['ExceptionPexpect', 'EOF', 'TIMEOUT', 'spawn', 'spawnu', 'run', 'runu', 'which', 'split_command_line', '__version__', '__revision__'] diff --git a/pipenv/vendor/pexpect/_async.py b/pipenv/vendor/pexpect/_async.py index ca2044e1..91a87d0e 100644 --- a/pipenv/vendor/pexpect/_async.py +++ b/pipenv/vendor/pexpect/_async.py @@ -2,16 +2,13 @@ import asyncio import errno import signal -from pexpect import EOF +from pipenv.vendor.pexpect import EOF @asyncio.coroutine def expect_async(expecter, timeout=None): # First process data that was previously read - if it maches, we don't need # async stuff. - previously_read = expecter.spawn.buffer - expecter.spawn._buffer = expecter.spawn.buffer_type() - expecter.spawn._before = expecter.spawn.buffer_type() - idx = expecter.new_data(previously_read) + idx = expecter.existing_data() if idx is not None: return idx if not expecter.spawn.async_pw_transport: @@ -74,6 +71,7 @@ class PatternWaiter(asyncio.Protocol): spawn._log(s, 'read') if self.fut.done(): + spawn._before.write(s) spawn._buffer.write(s) return diff --git a/pipenv/vendor/pexpect/expect.py b/pipenv/vendor/pexpect/expect.py index db376d59..d3409db9 100644 --- a/pipenv/vendor/pexpect/expect.py +++ b/pipenv/vendor/pexpect/expect.py @@ -6,45 +6,101 @@ class Expecter(object): def __init__(self, spawn, searcher, searchwindowsize=-1): self.spawn = spawn self.searcher = searcher + # A value of -1 means to use the figure from spawn, which should + # be None or a positive number. if searchwindowsize == -1: searchwindowsize = spawn.searchwindowsize self.searchwindowsize = searchwindowsize + self.lookback = None + if hasattr(searcher, 'longest_string'): + self.lookback = searcher.longest_string - def new_data(self, data): + def do_search(self, window, freshlen): spawn = self.spawn searcher = self.searcher - - pos = spawn._buffer.tell() - spawn._buffer.write(data) - spawn._before.write(data) - - # determine which chunk of data to search; if a windowsize is - # specified, this is the *new* data + the preceding <windowsize> bytes - if self.searchwindowsize: - spawn._buffer.seek(max(0, pos - self.searchwindowsize)) - window = spawn._buffer.read(self.searchwindowsize + len(data)) - else: - # otherwise, search the whole buffer (really slow for large datasets) - window = spawn.buffer - index = searcher.search(window, len(data)) + if freshlen > len(window): + freshlen = len(window) + index = searcher.search(window, freshlen, self.searchwindowsize) if index >= 0: spawn._buffer = spawn.buffer_type() spawn._buffer.write(window[searcher.end:]) - spawn.before = spawn._before.getvalue()[0:-(len(window) - searcher.start)] + spawn.before = spawn._before.getvalue()[ + 0:-(len(window) - searcher.start)] spawn._before = spawn.buffer_type() - spawn.after = window[searcher.start: searcher.end] + spawn._before.write(window[searcher.end:]) + spawn.after = window[searcher.start:searcher.end] spawn.match = searcher.match spawn.match_index = index # Found a match return index - elif self.searchwindowsize: - spawn._buffer = spawn.buffer_type() - spawn._buffer.write(window) + elif self.searchwindowsize or self.lookback: + maintain = self.searchwindowsize or self.lookback + if spawn._buffer.tell() > maintain: + spawn._buffer = spawn.buffer_type() + spawn._buffer.write(window[-maintain:]) + + def existing_data(self): + # First call from a new call to expect_loop or expect_async. + # self.searchwindowsize may have changed. + # Treat all data as fresh. + spawn = self.spawn + before_len = spawn._before.tell() + buf_len = spawn._buffer.tell() + freshlen = before_len + if before_len > buf_len: + if not self.searchwindowsize: + spawn._buffer = spawn.buffer_type() + window = spawn._before.getvalue() + spawn._buffer.write(window) + elif buf_len < self.searchwindowsize: + spawn._buffer = spawn.buffer_type() + spawn._before.seek( + max(0, before_len - self.searchwindowsize)) + window = spawn._before.read() + spawn._buffer.write(window) + else: + spawn._buffer.seek(max(0, buf_len - self.searchwindowsize)) + window = spawn._buffer.read() + else: + if self.searchwindowsize: + spawn._buffer.seek(max(0, buf_len - self.searchwindowsize)) + window = spawn._buffer.read() + else: + window = spawn._buffer.getvalue() + return self.do_search(window, freshlen) + + def new_data(self, data): + # A subsequent call, after a call to existing_data. + spawn = self.spawn + freshlen = len(data) + spawn._before.write(data) + if not self.searchwindowsize: + if self.lookback: + # search lookback + new data. + old_len = spawn._buffer.tell() + spawn._buffer.write(data) + spawn._buffer.seek(max(0, old_len - self.lookback)) + window = spawn._buffer.read() + else: + # copy the whole buffer (really slow for large datasets). + spawn._buffer.write(data) + window = spawn.buffer + else: + if len(data) >= self.searchwindowsize or not spawn._buffer.tell(): + window = data[-self.searchwindowsize:] + spawn._buffer = spawn.buffer_type() + spawn._buffer.write(window[-self.searchwindowsize:]) + else: + spawn._buffer.write(data) + new_len = spawn._buffer.tell() + spawn._buffer.seek(max(0, new_len - self.searchwindowsize)) + window = spawn._buffer.read() + return self.do_search(window, freshlen) def eof(self, err=None): spawn = self.spawn - spawn.before = spawn.buffer + spawn.before = spawn._before.getvalue() spawn._buffer = spawn.buffer_type() spawn._before = spawn.buffer_type() spawn.after = EOF @@ -60,12 +116,15 @@ class Expecter(object): msg += '\nsearcher: %s' % self.searcher if err is not None: msg = str(err) + '\n' + msg - raise EOF(msg) - + + exc = EOF(msg) + exc.__cause__ = None # in Python 3.x we can use "raise exc from None" + raise exc + def timeout(self, err=None): spawn = self.spawn - spawn.before = spawn.buffer + spawn.before = spawn._before.getvalue() spawn.after = TIMEOUT index = self.searcher.timeout_index if index >= 0: @@ -79,15 +138,18 @@ class Expecter(object): msg += '\nsearcher: %s' % self.searcher if err is not None: msg = str(err) + '\n' + msg - raise TIMEOUT(msg) + + exc = TIMEOUT(msg) + exc.__cause__ = None # in Python 3.x we can use "raise exc from None" + raise exc def errored(self): spawn = self.spawn - spawn.before = spawn.buffer + spawn.before = spawn._before.getvalue() spawn.after = None spawn.match = None spawn.match_index = None - + def expect_loop(self, timeout=-1): """Blocking expect""" spawn = self.spawn @@ -96,14 +158,10 @@ class Expecter(object): end_time = time.time() + timeout try: - incoming = spawn.buffer - spawn._buffer = spawn.buffer_type() - spawn._before = spawn.buffer_type() + idx = self.existing_data() + if idx is not None: + return idx while True: - idx = self.new_data(incoming) - # Keep reading until exception or return. - if idx is not None: - return idx # No match at this point if (timeout is not None) and (timeout < 0): return self.timeout() @@ -111,6 +169,10 @@ class Expecter(object): incoming = spawn.read_nonblocking(spawn.maxread, timeout) if self.spawn.delayafterread is not None: time.sleep(self.spawn.delayafterread) + idx = self.new_data(incoming) + # Keep reading until exception or return. + if idx is not None: + return idx if timeout is not None: timeout = end_time - time.time() except EOF as e: @@ -148,6 +210,7 @@ class searcher_string(object): self.eof_index = -1 self.timeout_index = -1 self._strings = [] + self.longest_string = 0 for n, s in enumerate(strings): if s is EOF: self.eof_index = n @@ -156,6 +219,8 @@ class searcher_string(object): self.timeout_index = n continue self._strings.append((n, s)) + if len(s) > self.longest_string: + self.longest_string = len(s) def __str__(self): '''This returns a human-readable string that represents the state of diff --git a/pipenv/vendor/pexpect/pty_spawn.py b/pipenv/vendor/pexpect/pty_spawn.py index 691c2c63..72fd3b22 100644 --- a/pipenv/vendor/pexpect/pty_spawn.py +++ b/pipenv/vendor/pexpect/pty_spawn.py @@ -7,8 +7,8 @@ import errno import signal from contextlib import contextmanager -import ptyprocess -from ptyprocess.ptyprocess import use_native_pty_fork +import pipenv.vendor.ptyprocess as ptyprocess +from pipenv.vendor.ptyprocess.ptyprocess import use_native_pty_fork from .exceptions import ExceptionPexpect, EOF, TIMEOUT from .spawnbase import SpawnBase @@ -191,6 +191,7 @@ class spawn(SpawnBase): self.STDIN_FILENO = pty.STDIN_FILENO self.STDOUT_FILENO = pty.STDOUT_FILENO self.STDERR_FILENO = pty.STDERR_FILENO + self.str_last_chars = 100 self.cwd = cwd self.env = env self.echo = echo @@ -212,8 +213,8 @@ class spawn(SpawnBase): s.append(repr(self)) s.append('command: ' + str(self.command)) s.append('args: %r' % (self.args,)) - s.append('buffer (last 100 chars): %r' % self.buffer[-100:]) - s.append('before (last 100 chars): %r' % self.before[-100:] if self.before else '') + s.append('buffer (last %s chars): %r' % (self.str_last_chars,self.buffer[-self.str_last_chars:])) + s.append('before (last %s chars): %r' % (self.str_last_chars,self.before[-self.str_last_chars:] if self.before else '')) s.append('after: %r' % (self.after,)) s.append('match: %r' % (self.match,)) s.append('match_index: ' + str(self.match_index)) @@ -752,17 +753,21 @@ class spawn(SpawnBase): child process in interact mode is duplicated to the given log. You may pass in optional input and output filter functions. These - functions should take a string and return a string. The output_filter - will be passed all the output from the child process. The input_filter - will be passed all the keyboard input from the user. The input_filter - is run BEFORE the check for the escape_character. + functions should take bytes array and return bytes array too. Even + with ``encoding='utf-8'`` support, meth:`interact` will always pass + input_filter and output_filter bytes. You may need to wrap your + function to decode and encode back to UTF-8. + + The output_filter will be passed all the output from the child process. + The input_filter will be passed all the keyboard input from the user. + The input_filter is run BEFORE the check for the escape_character. Note that if you change the window size of the parent the SIGWINCH signal will not be passed through to the child. If you want the child window size to change when the parent's window size changes then do something like the following example:: - import pexpect, struct, fcntl, termios, signal, sys + import pipenv.vendor.pexpect as pexpect, struct, fcntl, termios, signal, sys def sigwinch_passthrough (sig, data): s = struct.pack("HHHH", 0, 0, 0, 0) a = struct.unpack('hhhh', fcntl.ioctl(sys.stdout.fileno(), diff --git a/pipenv/vendor/pexpect/pxssh.py b/pipenv/vendor/pexpect/pxssh.py index 3d53bd97..de2e5d97 100644 --- a/pipenv/vendor/pexpect/pxssh.py +++ b/pipenv/vendor/pexpect/pxssh.py @@ -20,7 +20,7 @@ PEXPECT LICENSE ''' -from pexpect import ExceptionPexpect, TIMEOUT, EOF, spawn +from pipenv.vendor.pexpect import ExceptionPexpect, TIMEOUT, EOF, spawn import time import os import sys @@ -64,7 +64,7 @@ class pxssh (spawn): Example that runs a few commands on a remote server and prints the result:: - from pexpect import pxssh + from pipenv.vendor.pexpect import pxssh import getpass try: s = pxssh.pxssh() @@ -88,7 +88,7 @@ class pxssh (spawn): Example showing how to specify SSH options:: - from pexpect import pxssh + from pipenv.vendor.pexpect import pxssh s = pxssh.pxssh(options={ "StrictHostKeyChecking": "no", "UserKnownHostsFile": "/dev/null"}) diff --git a/pipenv/vendor/pexpect/replwrap.py b/pipenv/vendor/pexpect/replwrap.py index c930f1e4..d705f508 100644 --- a/pipenv/vendor/pexpect/replwrap.py +++ b/pipenv/vendor/pexpect/replwrap.py @@ -4,7 +4,7 @@ import os.path import signal import sys -import pexpect +import pipenv.vendor.pexpect as pexpect PY3 = (sys.version_info[0] >= 3) diff --git a/pipenv/vendor/pexpect/run.py b/pipenv/vendor/pexpect/run.py index d9dfe76b..e179cdc2 100644 --- a/pipenv/vendor/pexpect/run.py +++ b/pipenv/vendor/pexpect/run.py @@ -21,31 +21,31 @@ def run(command, timeout=30, withexitstatus=False, events=None, The run() function can often be used instead of creating a spawn instance. For example, the following code uses spawn:: - from pexpect import * + from pipenv.vendor.pexpect import * child = spawn('scp foo user@example.com:.') child.expect('(?i)password') child.sendline(mypassword) The previous code can be replace with the following:: - from pexpect import * + from pipenv.vendor.pexpect import * run('scp foo user@example.com:.', events={'(?i)password': mypassword}) **Examples** Start the apache daemon on the local machine:: - from pexpect import * + from pipenv.vendor.pexpect import * run("/usr/local/apache/bin/apachectl start") Check in a file using SVN:: - from pexpect import * + from pipenv.vendor.pexpect import * run("svn ci -m 'automatic commit' my_file.py") Run a command and capture exit status:: - from pexpect import * + from pipenv.vendor.pexpect import * (command_output, exitstatus) = run('ls -l /bin', withexitstatus=1) The following will run SSH and execute 'ls -l' on the remote machine. The @@ -57,7 +57,7 @@ def run(command, timeout=30, withexitstatus=False, events=None, This will start mencoder to rip a video from DVD. This will also display progress ticks every 5 seconds as it runs. For example:: - from pexpect import * + from pipenv.vendor.pexpect import * def print_ticks(d): print d['event_count'], run("mencoder dvd://1 -o video.avi -oac copy -ovc copy", @@ -67,7 +67,7 @@ def run(command, timeout=30, withexitstatus=False, events=None, contains patterns and responses. Whenever one of the patterns is seen in the command output, run() will send the associated response string. So, run() in the above example can be also written as: - + run("mencoder dvd://1 -o video.avi -oac copy -ovc copy", events=[(TIMEOUT,print_ticks)], timeout=5) diff --git a/pipenv/vendor/pexpect/screen.py b/pipenv/vendor/pexpect/screen.py index 5ab45b94..79f95c4e 100644 --- a/pipenv/vendor/pexpect/screen.py +++ b/pipenv/vendor/pexpect/screen.py @@ -90,7 +90,7 @@ class screen: self.encoding = encoding self.encoding_errors = encoding_errors if encoding is not None: - self.decoder = codecs.getincrementaldecoder(encoding)(encoding_errors) + self.decoder = codecs.getincrementaldecoder(encoding)(encoding_errors) else: self.decoder = None self.cur_r = 1 diff --git a/pipenv/vendor/pexpect/spawnbase.py b/pipenv/vendor/pexpect/spawnbase.py index 63c0b420..59e90576 100644 --- a/pipenv/vendor/pexpect/spawnbase.py +++ b/pipenv/vendor/pexpect/spawnbase.py @@ -120,6 +120,9 @@ class SpawnBase(object): self.async_pw_transport = None # This is the read buffer. See maxread. self._buffer = self.buffer_type() + # The buffer may be trimmed for efficiency reasons. This is the + # untrimmed buffer, used to create the before attribute. + self._before = self.buffer_type() def _log(self, s, direction): if self.logfile is not None: diff --git a/pipenv/vendor/pip_shims/LICENSE b/pipenv/vendor/pip_shims/LICENSE index e1a278e7..b8978687 100644 --- a/pipenv/vendor/pip_shims/LICENSE +++ b/pipenv/vendor/pip_shims/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2018, Dan Ryan <dan@danryan.co> +Copyright (c) 2018-2021, Dan Ryan <dan@danryan.co> and Frost Ming Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above diff --git a/pipenv/vendor/pip_shims/__init__.py b/pipenv/vendor/pip_shims/__init__.py index 9320a437..febea8df 100644 --- a/pipenv/vendor/pip_shims/__init__.py +++ b/pipenv/vendor/pip_shims/__init__.py @@ -1,24 +1,54 @@ # -*- coding=utf-8 -*- +""" +This library is a set of compatibilty access shims to the ``pip`` internal API. +It provides compatibility with pip versions 8.0 through the current release. The +shims are provided using a lazy import strategy by hacking a module by overloading +a class instance's ``getattr`` method. This library exists due to my constant +writing of the same set of import shims. + +Submodules +========== + +.. autosummary:: + :toctree: _autosummary + + pip_shims.models + pip_shims.compat + pip_shims.utils + pip_shims.shims + pip_shims.environment + +""" from __future__ import absolute_import import sys -__version__ = "0.3.3" - from . import shims - -old_module = sys.modules[__name__] +__version__ = "0.6.0" -module = sys.modules[__name__] = shims._new() +if "pip_shims" in sys.modules: + # mainly to keep a reference to the old module on hand so it doesn't get + # weakref'd away + if __name__ != "pip_shims": + del sys.modules["pip_shims"] + + +if __name__ in sys.modules: + old_module = sys.modules[__name__] + + +module = sys.modules["pip_shims"] = sys.modules[__name__] = shims._new() module.shims = shims -module.__dict__.update({ - '__file__': __file__, - '__package__': "pip_shims", - '__path__': __path__, - '__doc__': __doc__, - '__all__': module.__all__ + ['shims',], - '__version__': __version__, - '__name__': __name__ -}) +module.__dict__.update( + { + "__file__": __file__, + "__package__": "pip_shims", + "__path__": __path__, + "__doc__": __doc__, + "__all__": module.__all__ + ["shims"], + "__version__": __version__, + "__name__": __name__, + } +) diff --git a/pipenv/vendor/pip_shims/compat.py b/pipenv/vendor/pip_shims/compat.py new file mode 100644 index 00000000..4dd596ea --- /dev/null +++ b/pipenv/vendor/pip_shims/compat.py @@ -0,0 +1,1595 @@ +""" +Backports and helper functionality to support using new functionality. +""" + +import atexit +import contextlib +import functools +import inspect +import os +import re +import sys +import types + +from pipenv.vendor.packaging import specifiers + +from .environment import MYPY_RUNNING +from .utils import ( + call_function_with_correct_args, + filter_allowed_args, + get_allowed_args, + get_method_args, + nullcontext, + suppress_setattr, +) + +if sys.version_info[:2] < (3, 5): + from backports.tempfile import TemporaryDirectory +else: + from tempfile import TemporaryDirectory + +from contextlib import ExitStack + +if MYPY_RUNNING: + from optparse import Values + from typing import ( + Any, + Callable, + Dict, + Generator, + Generic, + Iterable, + Iterator, + List, + Optional, + Tuple, + Type, + TypeVar, + Union, + ) + + from pipenv.vendor.requests import Session + + from .utils import TShim, TShimmedFunc, TShimmedPath + + TFinder = TypeVar("TFinder") + TResolver = TypeVar("TResolver") + TReqTracker = TypeVar("TReqTracker") + TReqSet = TypeVar("TReqSet") + TLink = TypeVar("TLink") + TSession = TypeVar("TSession", bound=Session) + TCommand = TypeVar("TCommand", covariant=True) + TCommandInstance = TypeVar("TCommandInstance") + TCmdDict = Dict[str, Union[Tuple[str, str, str], TCommandInstance]] + TInstallRequirement = TypeVar("TInstallRequirement") + TFormatControl = TypeVar("TFormatControl") + TShimmedCmdDict = Union[TShim, TCmdDict] + TWheelCache = TypeVar("TWheelCache") + TPreparer = TypeVar("TPreparer") + + +class SearchScope(object): + def __init__(self, find_links=None, index_urls=None): + self.index_urls = index_urls if index_urls else [] + self.find_links = find_links + + @classmethod + def create(cls, find_links=None, index_urls=None): + if not index_urls: + index_urls = ["https://pypi.org/simple"] + return cls(find_links=find_links, index_urls=index_urls) + + +class SelectionPreferences(object): + def __init__( + self, + allow_yanked=True, + allow_all_prereleases=False, + format_control=None, + prefer_binary=False, + ignore_requires_python=False, + ): + self.allow_yanked = allow_yanked + self.allow_all_prereleases = allow_all_prereleases + self.format_control = format_control + self.prefer_binary = prefer_binary + self.ignore_requires_python = ignore_requires_python + + +class TargetPython(object): + fallback_get_tags = None # type: Optional[TShimmedFunc] + + def __init__( + self, + platform=None, # type: Optional[str] + py_version_info=None, # type: Optional[Tuple[int, ...]] + abi=None, # type: Optional[str] + implementation=None, # type: Optional[str] + ): + # type: (...) -> None + self._given_py_version_info = py_version_info + if py_version_info is None: + py_version_info = sys.version_info[:3] + elif len(py_version_info) < 3: + py_version_info += (3 - len(py_version_info)) * (0,) + else: + py_version_info = py_version_info[:3] + py_version = ".".join(map(str, py_version_info[:2])) + self.abi = abi + self.implementation = implementation + self.platform = platform + self.py_version = py_version + self.py_version_info = py_version_info + self._valid_tags = None + + def get_tags(self): + if self._valid_tags is None and self.fallback_get_tags: + fallback_func = resolve_possible_shim(self.fallback_get_tags) + versions = None + if self._given_py_version_info: + versions = ["".join(map(str, self._given_py_version_info[:2]))] + self._valid_tags = fallback_func( + versions=versions, + platform=self.platform, + abi=self.abi, + impl=self.implementation, + ) + return self._valid_tags + + +class CandidatePreferences(object): + def __init__(self, prefer_binary=False, allow_all_prereleases=False): + self.prefer_binary = prefer_binary + self.allow_all_prereleases = allow_all_prereleases + + +class LinkCollector(object): + def __init__(self, session=None, search_scope=None): + self.session = session + self.search_scope = search_scope + + +class CandidateEvaluator(object): + @classmethod + def create( + cls, + project_name, # type: str + target_python=None, # type: Optional[TargetPython] + prefer_binary=False, # type: bool + allow_all_prereleases=False, # type: bool + specifier=None, # type: Optional[specifiers.BaseSpecifier] + hashes=None, # type: Optional[Any] + ): + if target_python is None: + target_python = TargetPython() + if specifier is None: + specifier = specifiers.SpecifierSet() + + supported_tags = target_python.get_tags() + + return cls( + project_name=project_name, + supported_tags=supported_tags, + specifier=specifier, + prefer_binary=prefer_binary, + allow_all_prereleases=allow_all_prereleases, + hashes=hashes, + ) + + def __init__( + self, + project_name, # type: str + supported_tags, # type: List[Any] + specifier, # type: specifiers.BaseSpecifier + prefer_binary=False, # type: bool + allow_all_prereleases=False, # type: bool + hashes=None, # type: Optional[Any] + ): + self._allow_all_prereleases = allow_all_prereleases + self._hashes = hashes + self._prefer_binary = prefer_binary + self._project_name = project_name + self._specifier = specifier + self._supported_tags = supported_tags + + +class LinkEvaluator(object): + def __init__( + self, + allow_yanked, + project_name, + canonical_name, + formats, + target_python, + ignore_requires_python=False, + ignore_compatibility=True, + ): + self._allow_yanked = allow_yanked + self._canonical_name = canonical_name + self._ignore_requires_python = ignore_requires_python + self._formats = formats + self._target_python = target_python + self._ignore_compatibility = ignore_compatibility + + self.project_name = project_name + + +class InvalidWheelFilename(Exception): + """Wheel Filename is Invalid""" + + +class Wheel(object): + wheel_file_re = re.compile( + r"""^(?P<namever>(?P<name>.+?)-(?P<ver>.*?)) + ((-(?P<build>\d[^-]*?))?-(?P<pyver>.+?)-(?P<abi>.+?)-(?P<plat>.+?) + \.whl|\.dist-info)$""", + re.VERBOSE, + ) + + def __init__(self, filename): + # type: (str) -> None + wheel_info = self.wheel_file_re.match(filename) + if not wheel_info: + raise InvalidWheelFilename("%s is not a valid wheel filename." % filename) + self.filename = filename + self.name = wheel_info.group("name").replace("_", "-") + # we'll assume "_" means "-" due to wheel naming scheme + # (https://github.com/pypa/pip/issues/1150) + self.version = wheel_info.group("ver").replace("_", "-") + self.build_tag = wheel_info.group("build") + self.pyversions = wheel_info.group("pyver").split(".") + self.abis = wheel_info.group("abi").split(".") + self.plats = wheel_info.group("plat").split(".") + + # All the tag combinations from this file + self.file_tags = { + (x, y, z) for x in self.pyversions for y in self.abis for z in self.plats + } + + def get_formatted_file_tags(self): + # type: () -> List[str] + """ + Return the wheel's tags as a sorted list of strings. + """ + return sorted("-".join(tag) for tag in self.file_tags) + + def support_index_min(self, tags): + # type: (List[Any]) -> int + """ + Return the lowest index that one of the wheel's file_tag combinations + achieves in the given list of supported tags. + + For example, if there are 8 supported tags and one of the file tags + is first in the list, then return 0. + + :param tags: the PEP 425 tags to check the wheel against, in order + with most preferred first. + :raises ValueError: If none of the wheel's file tags match one of + the supported tags. + """ + return min(tags.index(tag) for tag in self.file_tags if tag in tags) + + def supported(self, tags): + # type: (List[Any]) -> bool + """ + Return whether the wheel is compatible with one of the given tags. + + :param tags: the PEP 425 tags to check the wheel against. + """ + return not self.file_tags.isdisjoint(tags) + + +def resolve_possible_shim(target): + # type: (TShimmedFunc) -> Optional[Union[Type, Callable]] + if target is None: + return target + if getattr(target, "shim", None) and isinstance( + target.shim, (types.MethodType, types.FunctionType) + ): + return target.shim() + return target + + +@contextlib.contextmanager +def temp_environ(): + """Allow the ability to set os.environ temporarily""" + environ = dict(os.environ) + try: + yield + finally: + os.environ.clear() + os.environ.update(environ) + + +@contextlib.contextmanager +def get_requirement_tracker(req_tracker_creator=None): + # type: (Optional[Callable]) -> Generator[Optional[TReqTracker], None, None] + root = os.environ.get("PIP_REQ_TRACKER") + if not req_tracker_creator: + yield None + else: + req_tracker_args = [] + _, required_args = get_method_args(req_tracker_creator.__init__) # type: ignore + with ExitStack() as ctx: + if root is None: + root = ctx.enter_context(TemporaryDirectory(prefix="req-tracker")) + if root: + root = str(root) + ctx.enter_context(temp_environ()) + os.environ["PIP_REQ_TRACKER"] = root + if required_args is not None and "root" in required_args: + req_tracker_args.append(root) + with req_tracker_creator(*req_tracker_args) as tracker: + yield tracker + + +@contextlib.contextmanager +def ensure_resolution_dirs(**kwargs): + # type: (Any) -> Iterator[Dict[str, Any]] + """ + Ensures that the proper directories are scaffolded and present in the provided kwargs + for performing dependency resolution via pip. + + :return: A new kwargs dictionary with scaffolded directories for **build_dir**, **src_dir**, + **download_dir**, and **wheel_download_dir** added to the key value pairs. + :rtype: Dict[str, Any] + """ + keys = ("build_dir", "src_dir", "download_dir", "wheel_download_dir") + if not any(kwargs.get(key) is None for key in keys): + yield kwargs + else: + with TemporaryDirectory(prefix="pip-shims-") as base_dir: + for key in keys: + if kwargs.get(key) is not None: + continue + target = os.path.join(base_dir, key) + os.makedirs(target) + kwargs[key] = target + yield kwargs + + +@contextlib.contextmanager +def wheel_cache( + cache_dir=None, # type: str + format_control=None, # type: Any + wheel_cache_provider=None, # type: TShimmedFunc + format_control_provider=None, # type: Optional[TShimmedFunc] + tempdir_manager_provider=None, # type: TShimmedFunc +): + tempdir_manager_provider = resolve_possible_shim(tempdir_manager_provider) + wheel_cache_provider = resolve_possible_shim(wheel_cache_provider) + format_control_provider = resolve_possible_shim(format_control_provider) + if not format_control and not format_control_provider: + raise TypeError("Format control or provider needed for wheel cache!") + if not format_control: + format_control = format_control_provider(None, None) + with ExitStack() as ctx: + ctx.enter_context(tempdir_manager_provider()) + wheel_cache = wheel_cache_provider(cache_dir, format_control) + yield wheel_cache + + +def partial_command(shimmed_path, cmd_mapping=None): + # type: (Type, Optional[TShimmedCmdDict]) -> Union[Type[TCommandInstance], functools.partial] + """ + Maps a default set of arguments across all members of a + :class:`~pip_shims.models.ShimmedPath` instance, specifically for + :class:`~pipenv.patched.notpip._internal.command.Command` instances which need + `summary` and `name` arguments. + + :param :class:`~pip_shims.models.ShimmedPath` shimmed_path: A + :class:`~pip_shims.models.ShimmedCollection` instance + :param Any cmd_mapping: A reference to use for mapping against, e.g. an + import that depends on pip also + :return: A dictionary mapping new arguments to their default values + :rtype: Dict[str, str] + """ + basecls = shimmed_path.shim() + resolved_cmd_mapping = None # type: Optional[Dict[str, Any]] + cmd_mapping = resolve_possible_shim(cmd_mapping) + if cmd_mapping is not None and isinstance(cmd_mapping, dict): + resolved_cmd_mapping = cmd_mapping.copy() + base_args = [] # type: List[str] + for root_cls in basecls.mro(): + if root_cls.__name__ == "Command": + _, root_init_args = get_method_args(root_cls.__init__) + if root_init_args is not None: + base_args = root_init_args.args + needs_name_and_summary = any(arg in base_args for arg in ("name", "summary")) + if not needs_name_and_summary: + basecls.name = shimmed_path.name + return basecls + elif ( + not resolved_cmd_mapping + and needs_name_and_summary + and getattr(functools, "partialmethod", None) + ): + new_init = functools.partial( + basecls.__init__, name=shimmed_path.name, summary="Summary" + ) + basecls.__init__ = new_init + result = basecls + assert resolved_cmd_mapping is not None + for command_name, command_info in resolved_cmd_mapping.items(): + if getattr(command_info, "class_name", None) == shimmed_path.name: + summary = getattr(command_info, "summary", "Command summary") + result = functools.partial(basecls, command_name, summary) + break + return result + + +def get_session( + install_cmd_provider=None, # type: Optional[TShimmedFunc] + install_cmd=None, # type: TCommandInstance + options=None, # type: Optional[Values] +): + # type: (...) -> TSession + session = None # type: Optional[TSession] + if install_cmd is None: + assert install_cmd_provider is not None + install_cmd_provider = resolve_possible_shim(install_cmd_provider) + assert isinstance(install_cmd_provider, (type, functools.partial)) + install_cmd = install_cmd_provider() + if options is None: + options, _ = install_cmd.parser.parse_args([]) # type: ignore + session = install_cmd._build_session(options) # type: ignore + assert session is not None + atexit.register(session.close) + return session + + +def populate_options( + install_command=None, # type: TCommandInstance + options=None, # type: Optional[Values] + **kwargs # type: Any +): + # (...) -> Tuple[Dict[str, Any], Values] + results = {} + if install_command is None and options is None: + raise TypeError("Must pass either options or InstallCommand to populate options") + if options is None and install_command is not None: + options, _ = install_command.parser.parse_args([]) # type: ignore + options_dict = options.__dict__ + for provided_key, provided_value in kwargs.items(): + if provided_key == "isolated": + options_key = "isolated_mode" + elif provided_key == "source_dir": + options_key = "src_dir" + else: + options_key = provided_key + if provided_key in options_dict and provided_value is not None: + setattr(options, options_key, provided_value) + results[provided_key] = provided_value + elif getattr(options, options_key, None) is not None: + results[provided_key] = getattr(options, options_key) + else: + results[provided_key] = provided_value + return results, options + + +def get_requirement_set( + install_command=None, # type: Optional[TCommandInstance] + req_set_provider=None, # type: Optional[TShimmedFunc] + build_dir=None, # type: Optional[str] + src_dir=None, # type: Optional[str] + download_dir=None, # type: Optional[str] + wheel_download_dir=None, # type: Optional[str] + session=None, # type: Optional[TSession] + wheel_cache=None, # type: Optional[TWheelCache] + upgrade=False, # type: bool + upgrade_strategy=None, # type: Optional[str] + ignore_installed=False, # type: bool + ignore_dependencies=False, # type: bool + force_reinstall=False, # type: bool + use_user_site=False, # type: bool + isolated=False, # type: bool + ignore_requires_python=False, # type: bool + require_hashes=None, # type: bool + cache_dir=None, # type: Optional[str] + options=None, # type: Optional[Values] + install_cmd_provider=None, # type: Optional[TShimmedFunc] + wheel_cache_provider=None, # type: Optional[TShimmedFunc] +): + # (...) -> TRequirementSet + """ + Creates a requirement set from the supplied parameters. + + Not all parameters are passed through for all pip versions, but any + invalid parameters will be ignored if they are not needed to generate a + requirement set on the current pip version. + + :param :class:`~pip_shims.models.ShimmedPathCollection` wheel_cache_provider: A + context manager provider which resolves to a `WheelCache` instance + :param install_command: A :class:`~pipenv.patched.notpip._internal.commands.install.InstallCommand` + instance which is used to generate the finder. + :param :class:`~pip_shims.models.ShimmedPathCollection` req_set_provider: A provider + to build requirement set instances. + :param str build_dir: The directory to build requirements in. Removed in pip 10, + defeaults to None + :param str source_dir: The directory to use for source requirements. Removed in + pip 10, defaults to None + :param str download_dir: The directory to download requirement artifacts to. Removed + in pip 10, defaults to None + :param str wheel_download_dir: The directory to download wheels to. Removed in pip + 10, defaults ot None + :param :class:`~requests.Session` session: The pip session to use. Removed in pip 10, + defaults to None + :param WheelCache wheel_cache: The pip WheelCache instance to use for caching wheels. + Removed in pip 10, defaults to None + :param bool upgrade: Whether to try to upgrade existing requirements. Removed in pip + 10, defaults to False. + :param str upgrade_strategy: The upgrade strategy to use, e.g. "only-if-needed". + Removed in pip 10, defaults to None. + :param bool ignore_installed: Whether to ignore installed packages when resolving. + Removed in pip 10, defaults to False. + :param bool ignore_dependencies: Whether to ignore dependencies of requirements + when resolving. Removed in pip 10, defaults to False. + :param bool force_reinstall: Whether to force reinstall of packages when resolving. + Removed in pip 10, defaults to False. + :param bool use_user_site: Whether to use user site packages when resolving. Removed + in pip 10, defaults to False. + :param bool isolated: Whether to resolve in isolation. Removed in pip 10, defaults + to False. + :param bool ignore_requires_python: Removed in pip 10, defaults to False. + :param bool require_hashes: Whether to require hashes when resolving. Defaults to + False. + :param Values options: An :class:`~optparse.Values` instance from an install cmd + :param install_cmd_provider: A shim for providing new install command instances. + :type install_cmd_provider: :class:`~pip_shims.models.ShimmedPathCollection` + :return: A new requirement set instance + :rtype: :class:`~pipenv.patched.notpip._internal.req.req_set.RequirementSet` + """ + wheel_cache_provider = resolve_possible_shim(wheel_cache_provider) + req_set_provider = resolve_possible_shim(req_set_provider) + if install_command is None: + install_cmd_provider = resolve_possible_shim(install_cmd_provider) + assert isinstance(install_cmd_provider, (type, functools.partial)) + install_command = install_cmd_provider() + required_args = inspect.getargs( + req_set_provider.__init__.__code__ + ).args # type: ignore + results, options = populate_options( + install_command, + options, + build_dir=build_dir, + src_dir=src_dir, + download_dir=download_dir, + upgrade=upgrade, + upgrade_strategy=upgrade_strategy, + ignore_installed=ignore_installed, + ignore_dependencies=ignore_dependencies, + force_reinstall=force_reinstall, + use_user_site=use_user_site, + isolated=isolated, + ignore_requires_python=ignore_requires_python, + require_hashes=require_hashes, + cache_dir=cache_dir, + ) + if session is None and "session" in required_args: + session = get_session(install_cmd=install_command, options=options) + with ExitStack() as stack: + if wheel_cache is None: + wheel_cache = stack.enter_context(wheel_cache_provider(cache_dir=cache_dir)) + results["wheel_cache"] = wheel_cache + results["session"] = session + results["wheel_download_dir"] = wheel_download_dir + return call_function_with_correct_args(req_set_provider, **results) + + +def get_package_finder( + install_cmd=None, # type: Optional[TCommand] + options=None, # type: Optional[Values] + session=None, # type: Optional[TSession] + platform=None, # type: Optional[str] + python_versions=None, # type: Optional[Tuple[str, ...]] + abi=None, # type: Optional[str] + implementation=None, # type: Optional[str] + target_python=None, # type: Optional[Any] + ignore_requires_python=None, # type: Optional[bool] + target_python_builder=None, # type: Optional[TShimmedFunc] + install_cmd_provider=None, # type: Optional[TShimmedFunc] +): + # type: (...) -> TFinder + """Shim for compatibility to generate package finders. + + Build and return a :class:`~pipenv.patched.notpip._internal.index.package_finder.PackageFinder` + instance using the :class:`~pipenv.patched.notpip._internal.commands.install.InstallCommand` helper + method to construct the finder, shimmed with backports as needed for compatibility. + + :param install_cmd_provider: A shim for providing new install command instances. + :type install_cmd_provider: :class:`~pip_shims.models.ShimmedPathCollection` + :param install_cmd: A :class:`~pipenv.patched.notpip._internal.commands.install.InstallCommand` + instance which is used to generate the finder. + :param optparse.Values options: An optional :class:`optparse.Values` instance + generated by calling `install_cmd.parser.parse_args()` typically. + :param session: An optional session instance, can be created by the `install_cmd`. + :param Optional[str] platform: An optional platform string, e.g. linux_x86_64 + :param Optional[Tuple[str, ...]] python_versions: A tuple of 2-digit strings + representing python versions, e.g. ("27", "35", "36", "37"...) + :param Optional[str] abi: The target abi to support, e.g. "cp38" + :param Optional[str] implementation: An optional implementation string for limiting + searches to a specific implementation, e.g. "cp" or "py" + :param target_python: A :class:`~pipenv.patched.notpip._internal.models.target_python.TargetPython` + instance (will be translated to alternate arguments if necessary on incompatible + pip versions). + :param Optional[bool] ignore_requires_python: Whether to ignore `requires_python` + on resulting candidates, only valid after pip version 19.3.1 + :param target_python_builder: A 'TargetPython' builder (e.g. the class itself, + uninstantiated) + :return: A :class:`pipenv.patched.notpip._internal.index.package_finder.PackageFinder` instance + :rtype: :class:`pipenv.patched.notpip._internal.index.package_finder.PackageFinder` + + :Example: + + >>> from pip_shims.shims import InstallCommand, get_package_finder + >>> install_cmd = InstallCommand() + >>> finder = get_package_finder( + ... install_cmd, python_versions=("27", "35", "36", "37", "38"), implementation=" + cp" + ... ) + >>> candidates = finder.find_all_candidates("requests") + >>> requests_222 = next(iter(c for c in candidates if c.version.public == "2.22.0")) + >>> requests_222 + <InstallationCandidate('requests', <Version('2.22.0')>, <Link https://files.pythonhos + ted.org/packages/51/bd/23c926cd341ea6b7dd0b2a00aba99ae0f828be89d72b2190f27c11d4b7fb/r + equests-2.22.0-py2.py3-none-any.whl#sha256=9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9 + a590f48c010551dc6c4b31 (from https://pypi.org/simple/requests/) (requires-python:>=2. + 7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*)>)> + """ + if install_cmd is None: + install_cmd_provider = resolve_possible_shim(install_cmd_provider) + assert isinstance(install_cmd_provider, (type, functools.partial)) + install_cmd = install_cmd_provider() + if options is None: + options, _ = install_cmd.parser.parse_args([]) # type: ignore + if session is None: + session = get_session(install_cmd=install_cmd, options=options) # type: ignore + builder_args = inspect.getargs( + install_cmd._build_package_finder.__code__ + ) # type: ignore + build_kwargs = {"options": options, "session": session} + expects_targetpython = "target_python" in builder_args.args + received_python = any(arg for arg in [platform, python_versions, abi, implementation]) + if expects_targetpython and received_python and not target_python: + if target_python_builder is None: + target_python_builder = TargetPython + py_version_info = None + if python_versions: + py_version_info_python = max(python_versions) + py_version_info = tuple([int(part) for part in py_version_info_python]) + target_python_args = { + "platform": platform, + "platforms": [platform] if platform else None, + "abi": abi, + "abis": [abi] if abi else None, + "implementation": implementation, + "py_version_info": py_version_info, + } + target_python = call_function_with_correct_args( + target_python_builder, **target_python_args + ) + build_kwargs["target_python"] = target_python + elif any( + arg in builder_args.args + for arg in ["platform", "python_versions", "abi", "implementation"] + ): + if target_python and not received_python: + tags = target_python.get_tags() + version_impl = {t[0] for t in tags} + # impls = set([v[:2] for v in version_impl]) + # impls.remove("py") + # impl = next(iter(impls), "py") if not target_python + versions = {v[2:] for v in version_impl} + build_kwargs.update( + { + "platform": target_python.platform, + "python_versions": versions, + "abi": target_python.abi, + "implementation": target_python.implementation, + } + ) + if ( + ignore_requires_python is not None + and "ignore_requires_python" in builder_args.args + ): + build_kwargs["ignore_requires_python"] = ignore_requires_python + + return install_cmd._build_package_finder(**build_kwargs) # type: ignore + + +def shim_unpack( + unpack_fn, # type: TShimmedFunc + download_dir, # type str + tempdir_manager_provider, # type: TShimmedFunc + ireq=None, # type: Optional[Any] + link=None, # type: Optional[Any] + location=None, # type Optional[str], + hashes=None, # type: Optional[Any] + progress_bar="off", # type: str + only_download=None, # type: Optional[bool] + downloader_provider=None, # type: Optional[TShimmedFunc] + session=None, # type: Optional[Any] +): + # (...) -> None + """ + Accepts all parameters that have been valid to pass + to :func:`pipenv.patched.notpip._internal.download.unpack_url` and selects or + drops parameters as needed before invoking the provided + callable. + + :param unpack_fn: A callable or shim referring to the pip implementation + :type unpack_fn: Callable + :param str download_dir: The directory to download the file to + :param TShimmedFunc tempdir_manager_provider: A callable or shim referring to + `global_tempdir_manager` function from pip or a shimmed no-op context manager + :param Optional[:class:`~pipenv.patched.notpip._internal.req.req_install.InstallRequirement`] ireq: + an Install Requirement instance, defaults to None + :param Optional[:class:`~pipenv.patched.notpip._internal.models.link.Link`] link: A Link instance, + defaults to None. + :param Optional[str] location: A location or source directory if the target is + a VCS url, defaults to None. + :param Optional[Any] hashes: A Hashes instance, defaults to None + :param str progress_bar: Indicates progress par usage during download, defatuls to + off. + :param Optional[bool] only_download: Whether to skip install, defaults to None. + :param Optional[ShimmedPathCollection] downloader_provider: A downloader class + to instantiate, if applicable. + :param Optional[`~requests.Session`] session: A PipSession instance, defaults to + None. + :return: The result of unpacking the url. + :rtype: None + """ + unpack_fn = resolve_possible_shim(unpack_fn) + downloader_provider = resolve_possible_shim(downloader_provider) + tempdir_manager_provider = resolve_possible_shim(tempdir_manager_provider) + required_args = inspect.getargs(unpack_fn.__code__).args # type: ignore + unpack_kwargs = {"download_dir": download_dir} + with tempdir_manager_provider(): + if ireq: + if not link and ireq.link: + link = ireq.link + if only_download is None: + only_download = ireq.is_wheel + if hashes is None: + hashes = ireq.hashes(True) + if location is None and getattr(ireq, "source_dir", None): + location = ireq.source_dir + unpack_kwargs.update({"link": link, "location": location}) + if hashes is not None and "hashes" in required_args: + unpack_kwargs["hashes"] = hashes + if "progress_bar" in required_args: + unpack_kwargs["progress_bar"] = progress_bar + if only_download is not None and "only_download" in required_args: + unpack_kwargs["only_download"] = only_download + if session is not None and "session" in required_args: + unpack_kwargs["session"] = session + if ( + "download" in required_args or "downloader" in required_args + ) and downloader_provider is not None: + arg_name = "download" if "download" in required_args else "downloader" + assert session is not None + assert progress_bar is not None + unpack_kwargs[arg_name] = downloader_provider(session, progress_bar) + return unpack_fn(**unpack_kwargs) # type: ignore + + +def _ensure_finder( + finder=None, # type: Optional[TFinder] + finder_provider=None, # type: Optional[Callable] + install_cmd=None, # type: Optional[TCommandInstance] + options=None, # type: Optional[Values] + session=None, # type: Optional[TSession] +): + if not any([finder, finder_provider, install_cmd]): + raise TypeError( + "RequirementPreparer requires a packagefinder but no InstallCommand" + " was provided to build one and none was passed in." + ) + if finder is not None: + return finder + else: + if session is None: + session = get_session(install_cmd=install_cmd, options=options) + if finder_provider is not None and options is not None: + finder_provider(options=options, session=session) + else: + finder = get_package_finder(install_cmd, options=options, session=session) + return finder + + +@contextlib.contextmanager +def make_preparer( + preparer_fn, # type: TShimmedFunc + req_tracker_fn=None, # type: Optional[TShimmedFunc] + build_dir=None, # type: Optional[str] + src_dir=None, # type: Optional[str] + download_dir=None, # type: Optional[str] + wheel_download_dir=None, # type: Optional[str] + progress_bar="off", # type: str + build_isolation=False, # type: bool + session=None, # type: Optional[TSession] + finder=None, # type: Optional[TFinder] + options=None, # type: Optional[Values] + require_hashes=None, # type: Optional[bool] + use_user_site=None, # type: Optional[bool] + req_tracker=None, # type: Optional[Union[TReqTracker, TShimmedFunc]] + install_cmd_provider=None, # type: Optional[TShimmedFunc] + downloader_provider=None, # type: Optional[TShimmedFunc] + install_cmd=None, # type: Optional[TCommandInstance] + finder_provider=None, # type: Optional[TShimmedFunc] +): + # (...) -> ContextManager + """ + Creates a requirement preparer for preparing pip requirements. + + Provides a compatibilty shim that accepts all previously valid arguments and + discards any that are no longer used. + + :raises TypeError: No requirement tracker provided and one cannot be generated + :raises TypeError: No valid sessions provided and one cannot be generated + :raises TypeError: No valid finders provided and one cannot be generated + :param TShimmedFunc preparer_fn: Callable or shim for generating preparers. + :param Optional[TShimmedFunc] req_tracker_fn: Callable or shim for generating + requirement trackers, defualts to None + :param Optional[str] build_dir: Directory for building packages and wheels, + defaults to None + :param Optional[str] src_dir: Directory to find or extract source files, defaults + to None + :param Optional[str] download_dir: Target directory to download files, defaults to + None + :param Optional[str] wheel_download_dir: Target directoryto download wheels, defaults + to None + :param str progress_bar: Whether to display a progress bar, defaults to off + :param bool build_isolation: Whether to build requirements in isolation, defaults + to False + :param Optional[TSession] session: Existing session to use for getting requirements, + defaults to None + :param Optional[TFinder] finder: The package finder to use during resolution, + defaults to None + :param Optional[Values] options: Pip options to use if needed, defaults to None + :param Optional[bool] require_hashes: Whether to require hashes for preparation + :param Optional[bool] use_user_site: Whether to use the user site directory for + preparing requirements + :param Optional[Union[TReqTracker, TShimmedFunc]] req_tracker: The requirement + tracker to use for building packages, defaults to None + :param Optional[TShimmedFunc] downloader_provider: A downloader provider + :param Optional[TCommandInstance] install_cmd: The install command used to create + the finder, session, and options if needed, defaults to None + :param Optional[TShimmedFunc] finder_provider: A package finder provider + :yield: A new requirement preparer instance + :rtype: ContextManager[:class:`~pipenv.patched.notpip._internal.operations.prepare.RequirementPreparer`] + + :Example: + + >>> from pip_shims.shims import ( + ... InstallCommand, get_package_finder, make_preparer, get_requirement_tracker + ... ) + >>> install_cmd = InstallCommand() + >>> pip_options, _ = install_cmd.parser.parse_args([]) + >>> session = install_cmd._build_session(pip_options) + >>> finder = get_package_finder( + ... install_cmd, session=session, options=pip_options + ... ) + >>> with make_preparer( + ... options=pip_options, finder=finder, session=session, install_cmd=ic + ... ) as preparer: + ... print(preparer) + <pipenv.patched.notpip._internal.operations.prepare.RequirementPreparer object at 0x7f8a2734be80> + """ + preparer_fn = resolve_possible_shim(preparer_fn) + downloader_provider = resolve_possible_shim(downloader_provider) + finder_provider = resolve_possible_shim(finder_provider) + required_args, required_kwargs = get_allowed_args(preparer_fn) # type: ignore + if not req_tracker and not req_tracker_fn and "req_tracker" in required_args: + raise TypeError("No requirement tracker and no req tracker generator found!") + if "downloader" in required_args and not downloader_provider: + raise TypeError("no downloader provided, but one is required to continue!") + req_tracker_fn = resolve_possible_shim(req_tracker_fn) + pip_options_created = options is None + session_is_required = "session" in required_args + finder_is_required = "finder" in required_args + downloader_is_required = "downloader" in required_args + options_map = { + "src_dir": src_dir, + "download_dir": download_dir, + "wheel_download_dir": wheel_download_dir, + "build_dir": build_dir, + "progress_bar": progress_bar, + "build_isolation": build_isolation, + "require_hashes": require_hashes, + "use_user_site": use_user_site, + } + if install_cmd is None: + assert install_cmd_provider is not None + install_cmd_provider = resolve_possible_shim(install_cmd_provider) + assert isinstance(install_cmd_provider, (type, functools.partial)) + install_cmd = install_cmd_provider() + preparer_args, options = populate_options(install_cmd, options, **options_map) + if options is not None and pip_options_created: + for k, v in options_map.items(): + suppress_setattr(options, k, v, filter_none=True) + if session_is_required: + if session is None: + session = get_session(install_cmd=install_cmd, options=options) + preparer_args["session"] = session + if finder_is_required: + finder = _ensure_finder( + finder=finder, + finder_provider=finder_provider, + install_cmd=install_cmd, + options=options, + session=session, + ) + preparer_args["finder"] = finder + if downloader_is_required: + preparer_args["downloader"] = downloader_provider(session, progress_bar) + if "in_tree_build" in required_args: + preparer_args["in_tree_build"] = True + req_tracker_fn = nullcontext if not req_tracker_fn else req_tracker_fn + with req_tracker_fn() as tracker_ctx: + if "req_tracker" in required_args: + req_tracker = tracker_ctx if req_tracker is None else req_tracker + preparer_args["req_tracker"] = req_tracker + preparer_args["lazy_wheel"] = True + result = call_function_with_correct_args(preparer_fn, **preparer_args) + yield result + + +@contextlib.contextmanager +def _ensure_wheel_cache( + wheel_cache=None, # type: Optional[Type[TWheelCache]] + wheel_cache_provider=None, # type: Optional[Callable] + format_control=None, # type: Optional[TFormatControl] + format_control_provider=None, # type: Optional[Type[TShimmedFunc]] + options=None, # type: Optional[Values] + cache_dir=None, # type: Optional[str] +): + if wheel_cache is not None: + yield wheel_cache + elif wheel_cache_provider is not None: + with ExitStack() as stack: + cache_dir = getattr(options, "cache_dir", cache_dir) + format_control = getattr( + options, + "format_control", + format_control_provider(None, None), # TFormatControl + ) + wheel_cache = stack.enter_context( + wheel_cache_provider(cache_dir, format_control) + ) + yield wheel_cache + + +def get_ireq_output_path(wheel_cache, ireq): + if getattr(wheel_cache, "get_path_for_link", None): + return wheel_cache.get_path_for_link(ireq.link) + elif getattr(wheel_cache, "cached_wheel", None): + return wheel_cache.cached_wheel(ireq.link, ireq.name).url_without_fragment + + +def get_resolver( + resolver_fn, # type: TShimmedFunc + install_req_provider=None, # type: Optional[TShimmedFunc] + format_control_provider=None, # type: Optional[TShimmedFunc] + wheel_cache_provider=None, # type: Optional[TShimmedFunc] + finder=None, # type: Optional[TFinder] + upgrade_strategy="to-satisfy-only", # type: str + force_reinstall=None, # type: Optional[bool] + ignore_dependencies=None, # type: Optional[bool] + ignore_requires_python=None, # type: Optional[bool] + ignore_installed=True, # type: bool + use_user_site=False, # type: bool + isolated=None, # type: Optional[bool] + wheel_cache=None, # type: Optional[TWheelCache] + preparer=None, # type: Optional[TPreparer] + session=None, # type: Optional[TSession] + options=None, # type: Optional[Values] + make_install_req=None, # type: Optional[Callable] + install_cmd_provider=None, # type: Optional[TShimmedFunc] + install_cmd=None, # type: Optional[TCommandInstance] + use_pep517=True, # type: bool +): + # (...) -> TResolver + """ + A resolver creation compatibility shim for generating a resolver. + + Consumes any argument that was previously used to instantiate a + resolver, discards anything that is no longer valid. + + .. note:: This is only valid for **pip >= 10.0.0** + + :raises ValueError: A session is required but not provided and one cannot be created + :raises ValueError: A finder is required but not provided and one cannot be created + :raises ValueError: An install requirement provider is required and has not been + provided + :param TShimmedFunc resolver_fn: The resolver function used to create new resolver + instances. + :param TShimmedFunc install_req_provider: The provider function to use to generate + install requirements if needed. + :param TShimmedFunc format_control_provider: The provider function to use to generate + a format_control instance if needed. + :param TShimmedFunc wheel_cache_provider: The provider function to use to generate + a wheel cache if needed. + :param Optional[TFinder] finder: The package finder to use during resolution, + defaults to None. + :param str upgrade_strategy: Upgrade strategy to use, defaults to ``only-if-needed``. + :param Optional[bool] force_reinstall: Whether to simulate or assume package + reinstallation during resolution, defaults to None + :param Optional[bool] ignore_dependencies: Whether to ignore package dependencies, + defaults to None + :param Optional[bool] ignore_requires_python: Whether to ignore indicated + required_python versions on packages, defaults to None + :param bool ignore_installed: Whether to ignore installed packages during resolution, + defaults to True + :param bool use_user_site: Whether to use the user site location during resolution, + defaults to False + :param Optional[bool] isolated: Whether to isolate the resolution process, defaults + to None + :param Optional[TWheelCache] wheel_cache: The wheel cache to use, defaults to None + :param Optional[TPreparer] preparer: The requirement preparer to use, defaults to + None + :param Optional[TSession] session: Existing session to use for getting requirements, + defaults to None + :param Optional[Values] options: Pip options to use if needed, defaults to None + :param Optional[functools.partial] make_install_req: The partial function to pass in + to the resolver for actually generating install requirements, if necessary + :param Optional[TCommandInstance] install_cmd: The install command used to create + the finder, session, and options if needed, defaults to None. + :param bool use_pep517: Whether to use the pep517 build process. + :return: A new resolver instance. + :rtype: :class:`~pipenv.patched.notpip._internal.legacy_resolve.Resolver` + + :Example: + + >>> import os + >>> from tempdir import TemporaryDirectory + >>> from pip_shims.shims import ( + ... InstallCommand, get_package_finder, make_preparer, get_requirement_tracker, + ... get_resolver, InstallRequirement, RequirementSet + ... ) + >>> install_cmd = InstallCommand() + >>> pip_options, _ = install_cmd.parser.parse_args([]) + >>> session = install_cmd._build_session(pip_options) + >>> finder = get_package_finder( + ... install_cmd, session=session, options=pip_options + ... ) + >>> wheel_cache = WheelCache(USER_CACHE_DIR, FormatControl(None, None)) + >>> with TemporaryDirectory() as temp_base: + ... reqset = RequirementSet() + ... ireq = InstallRequirement.from_line("requests") + ... ireq.is_direct = True + ... build_dir = os.path.join(temp_base, "build") + ... src_dir = os.path.join(temp_base, "src") + ... ireq.build_location(build_dir) + ... with make_preparer( + ... options=pip_options, finder=finder, session=session, + ... build_dir=build_dir, install_cmd=install_cmd, + ... ) as preparer: + ... resolver = get_resolver( + ... finder=finder, ignore_dependencies=False, ignore_requires_python=True, + ... preparer=preparer, session=session, options=pip_options, + ... install_cmd=install_cmd, wheel_cache=wheel_cache, + ... ) + ... resolver.require_hashes = False + ... reqset.add_requirement(ireq) + ... results = resolver.resolve(reqset) + ... #reqset.cleanup_files() + ... for result_req in reqset.requirements: + ... print(result_req) + requests + chardet + certifi + urllib3 + idna + """ + resolver_fn = resolve_possible_shim(resolver_fn) + install_req_provider = resolve_possible_shim(install_req_provider) + format_control_provider = resolve_possible_shim(format_control_provider) + wheel_cache_provider = resolve_possible_shim(wheel_cache_provider) + install_cmd_provider = resolve_possible_shim(install_cmd_provider) + required_args = inspect.getargs(resolver_fn.__init__.__code__).args # type: ignore + install_cmd_dependency_map = {"session": session, "finder": finder} + resolver_kwargs = {} # type: Dict[str, Any] + if install_cmd is None: + assert isinstance(install_cmd_provider, (type, functools.partial)) + install_cmd = install_cmd_provider() + if options is None and install_cmd is not None: + options, _ = install_cmd.parser.parse_args([]) # type: ignore + for arg, val in install_cmd_dependency_map.items(): + if arg not in required_args: + continue + elif val is None and install_cmd is None: + raise TypeError( + "Preparer requires a {} but did not receive one " + "and cannot generate one".format(arg) + ) + elif arg == "session" and val is None: + val = get_session(install_cmd=install_cmd, options=options) + elif arg == "finder" and val is None: + val = get_package_finder(install_cmd, options=options, session=session) + resolver_kwargs[arg] = val + if "make_install_req" in required_args: + if make_install_req is None and install_req_provider is not None: + make_install_req_kwargs = { + "isolated": isolated, + "wheel_cache": wheel_cache, + "use_pep517": use_pep517, + } + factory_args, factory_kwargs = filter_allowed_args( + install_req_provider, **make_install_req_kwargs + ) + make_install_req = functools.partial( + install_req_provider, *factory_args, **factory_kwargs + ) + assert make_install_req is not None + resolver_kwargs["make_install_req"] = make_install_req + if "isolated" in required_args: + resolver_kwargs["isolated"] = isolated + resolver_kwargs.update( + { + "upgrade_strategy": upgrade_strategy, + "force_reinstall": force_reinstall, + "ignore_dependencies": ignore_dependencies, + "ignore_requires_python": ignore_requires_python, + "ignore_installed": ignore_installed, + "use_user_site": use_user_site, + "preparer": preparer, + } + ) + if "wheel_cache" in required_args: + with _ensure_wheel_cache( + wheel_cache=wheel_cache, + wheel_cache_provider=wheel_cache_provider, + format_control_provider=format_control_provider, + options=options, + ) as wheel_cache: + resolver_kwargs["wheel_cache"] = wheel_cache + return resolver_fn(**resolver_kwargs) # type: ignore + return resolver_fn(**resolver_kwargs) # type: ignore + + +def resolve( # noqa:C901 + ireq, # type: TInstallRequirement + reqset_provider=None, # type: Optional[TShimmedFunc] + req_tracker_provider=None, # type: Optional[TShimmedFunc] + install_cmd_provider=None, # type: Optional[TShimmedFunc] + install_command=None, # type: Optional[TCommand] + finder_provider=None, # type: Optional[TShimmedFunc] + resolver_provider=None, # type: Optional[TShimmedFunc] + wheel_cache_provider=None, # type: Optional[TShimmedFunc] + format_control_provider=None, # type: Optional[TShimmedFunc] + make_preparer_provider=None, # type: Optional[TShimmedFunc] + tempdir_manager_provider=None, # type: Optional[TShimmedFunc] + options=None, # type: Optional[Values] + session=None, # type: Optional[TSession] + resolver=None, # type: Optional[TResolver] + finder=None, # type: Optional[TFinder] + upgrade_strategy="to-satisfy-only", # type: str + force_reinstall=None, # type: Optional[bool] + ignore_dependencies=None, # type: Optional[bool] + ignore_requires_python=None, # type: Optional[bool] + ignore_installed=True, # type: bool + use_user_site=False, # type: bool + isolated=None, # type: Optional[bool] + build_dir=None, # type: Optional[str] + source_dir=None, # type: Optional[str] + download_dir=None, # type: Optional[str] + cache_dir=None, # type: Optional[str] + wheel_download_dir=None, # type: Optional[str] + wheel_cache=None, # type: Optional[TWheelCache] + require_hashes=None, # type: bool + check_supported_wheels=True, # type: bool +): + # (...) -> Set[TInstallRequirement] + """ + Resolves the provided **InstallRequirement**, returning a dictionary. + + Maps a dictionary of names to corresponding ``InstallRequirement`` values. + + :param :class:`~pipenv.patched.notpip._internal.req.req_install.InstallRequirement` ireq: An + InstallRequirement to initiate the resolution process + :param :class:`~pip_shims.models.ShimmedPathCollection` reqset_provider: A provider + to build requirement set instances. + :param :class:`~pip_shims.models.ShimmedPathCollection` req_tracker_provider: A + provider to build requirement tracker instances + :param install_cmd_provider: A shim for providing new install command instances. + :type install_cmd_provider: :class:`~pip_shims.models.ShimmedPathCollection` + :param Optional[TCommandInstance] install_command: The install command used to + create the finder, session, and options if needed, defaults to None. + :param :class:`~pip_shims.models.ShimmedPathCollection` finder_provider: A provider + to package finder instances. + :param :class:`~pip_shims.models.ShimmedPathCollection` resolver_provider: A provider + to build resolver instances + :param TShimmedFunc wheel_cache_provider: The provider function to use to generate a + wheel cache if needed. + :param TShimmedFunc format_control_provider: The provider function to use to generate + a format_control instance if needed. + :param TShimmedFunc make_preparer_provider: Callable or shim for generating preparers. + :param Optional[TShimmedFunc] tempdir_manager_provider: Shim for generating tempdir + manager for pip temporary directories + :param Optional[Values] options: Pip options to use if needed, defaults to None + :param Optional[TSession] session: Existing session to use for getting requirements, + defaults to None + :param :class:`~pipenv.patched.notpip._internal.legacy_resolve.Resolver` resolver: A pre-existing + resolver instance to use for resolution + :param Optional[TFinder] finder: The package finder to use during resolution, + defaults to None. + :param str upgrade_strategy: Upgrade strategy to use, defaults to ``only-if-needed``. + :param Optional[bool] force_reinstall: Whether to simulate or assume package + reinstallation during resolution, defaults to None + :param Optional[bool] ignore_dependencies: Whether to ignore package dependencies, + defaults to None + :param Optional[bool] ignore_requires_python: Whether to ignore indicated + required_python versions on packages, defaults to None + :param bool ignore_installed: Whether to ignore installed packages during + resolution, defaults to True + :param bool use_user_site: Whether to use the user site location during resolution, + defaults to False + :param Optional[bool] isolated: Whether to isolate the resolution process, defaults + to None + :param Optional[str] build_dir: Directory for building packages and wheels, defaults + to None + :param str source_dir: The directory to use for source requirements. Removed in pip + 10, defaults to None + :param Optional[str] download_dir: Target directory to download files, defaults to + None + :param str cache_dir: The cache directory to use for caching artifacts during + resolution + :param Optional[str] wheel_download_dir: Target directoryto download wheels, defaults + to None + :param Optional[TWheelCache] wheel_cache: The wheel cache to use, defaults to None + :param bool require_hashes: Whether to require hashes when resolving. Defaults to + False. + :param bool check_supported_wheels: Whether to check support of wheels before including + them in resolution. + :return: A dictionary mapping requirements to corresponding + :class:`~pipenv.patched.notpip._internal.req.req_install.InstallRequirement`s + :rtype: :class:`~pipenv.patched.notpip._internal.req.req_install.InstallRequirement` + + :Example: + + >>> from pip_shims.shims import resolve, InstallRequirement + >>> ireq = InstallRequirement.from_line("requests>=2.20") + >>> results = resolve(ireq) + >>> for k, v in results.items(): + ... print("{0}: {1!r}".format(k, v)) + requests: <InstallRequirement object: requests>=2.20 from https://files.pythonhosted. + org/packages/51/bd/23c926cd341ea6b7dd0b2a00aba99ae0f828be89d72b2190f27c11d4b7fb/reque + sts-2.22.0-py2.py3-none-any.whl#sha256=9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590 + f48c010551dc6c4b31 editable=False> + idna: <InstallRequirement object: idna<2.9,>=2.5 from https://files.pythonhosted.org/ + packages/14/2c/cd551d81dbe15200be1cf41cd03869a46fe7226e7450af7a6545bfc474c9/idna-2.8- + py2.py3-none-any.whl#sha256=ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432 + f7e4a3c (from requests>=2.20) editable=False> + urllib3: <InstallRequirement object: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 from htt + ps://files.pythonhosted.org/packages/b4/40/a9837291310ee1ccc242ceb6ebfd9eb21539649f19 + 3a7c8c86ba15b98539/urllib3-1.25.7-py2.py3-none-any.whl#sha256=a8a318824cc77d1fd4b2bec + 2ded92646630d7fe8619497b142c84a9e6f5a7293 (from requests>=2.20) editable=False> + chardet: <InstallRequirement object: chardet<3.1.0,>=3.0.2 from https://files.pythonh + osted.org/packages/bc/a9/01ffebfb562e4274b6487b4bb1ddec7ca55ec7510b22e4c51f14098443b8 + /chardet-3.0.4-py2.py3-none-any.whl#sha256=fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed + 4531e3e15460124c106691 (from requests>=2.20) editable=False> + certifi: <InstallRequirement object: certifi>=2017.4.17 from https://files.pythonhost + ed.org/packages/18/b0/8146a4f8dd402f60744fa380bc73ca47303cccf8b9190fd16a827281eac2/ce + rtifi-2019.9.11-py2.py3-none-any.whl#sha256=fd7c7c74727ddcf00e9acd26bba8da604ffec95bf + 1c2144e67aff7a8b50e6cef (from requests>=2.20) editable=False> + """ + reqset_provider = resolve_possible_shim(reqset_provider) + finder_provider = resolve_possible_shim(finder_provider) + resolver_provider = resolve_possible_shim(resolver_provider) + wheel_cache_provider = resolve_possible_shim(wheel_cache_provider) + format_control_provider = resolve_possible_shim(format_control_provider) + make_preparer_provider = resolve_possible_shim(make_preparer_provider) + req_tracker_provider = resolve_possible_shim(req_tracker_provider) + install_cmd_provider = resolve_possible_shim(install_cmd_provider) + tempdir_manager_provider = resolve_possible_shim(tempdir_manager_provider) + if install_command is None: + assert isinstance(install_cmd_provider, (type, functools.partial)) + install_command = install_cmd_provider() + kwarg_map = { + "upgrade_strategy": upgrade_strategy, + "force_reinstall": force_reinstall, + "ignore_dependencies": ignore_dependencies, + "ignore_requires_python": ignore_requires_python, + "ignore_installed": ignore_installed, + "use_user_site": use_user_site, + "isolated": isolated, + "build_dir": build_dir, + "src_dir": source_dir, + "download_dir": download_dir, + "require_hashes": require_hashes, + "cache_dir": cache_dir, + } + kwargs, options = populate_options(install_command, options, **kwarg_map) + with ExitStack() as ctx: + ctx.enter_context(tempdir_manager_provider()) + kwargs = ctx.enter_context( + ensure_resolution_dirs(wheel_download_dir=wheel_download_dir, **kwargs) + ) + wheel_download_dir = kwargs.pop("wheel_download_dir") + if session is None: + session = get_session(install_cmd=install_command, options=options) + if finder is None: + finder = finder_provider( + install_command, options=options, session=session + ) # type: ignore + format_control = getattr(options, "format_control", None) + if not format_control: + format_control = format_control_provider(None, None) # type: ignore + wheel_cache = ctx.enter_context( + wheel_cache_provider(kwargs["cache_dir"], format_control) + ) # type: ignore + ireq.is_direct = True # type: ignore + build_location_kwargs = { + "build_dir": kwargs["build_dir"], + "autodelete": True, + "parallel_builds": False, + } + call_function_with_correct_args(ireq.build_location, **build_location_kwargs) + if reqset_provider is None: + raise TypeError( + "cannot resolve without a requirement set provider... failed!" + ) + reqset = reqset_provider( + install_command, + options=options, + session=session, + wheel_download_dir=wheel_download_dir, + **kwargs + ) # type: ignore + if getattr(reqset, "prepare_files", None): + reqset.add_requirement(ireq) + results = reqset.prepare_files(finder) + result = reqset.requirements + reqset.cleanup_files() + return result + if make_preparer_provider is None: + raise TypeError("Cannot create requirement preparer, cannot resolve!") + + preparer_args = { + "build_dir": kwargs["build_dir"], + "src_dir": kwargs["src_dir"], + "download_dir": kwargs["download_dir"], + "wheel_download_dir": wheel_download_dir, + "build_isolation": kwargs["isolated"], + "install_cmd": install_command, + "options": options, + "finder": finder, + "session": session, + "use_user_site": use_user_site, + "require_hashes": require_hashes, + } + if isinstance(req_tracker_provider, (types.FunctionType, functools.partial)): + preparer_args["req_tracker"] = ctx.enter_context(req_tracker_provider()) + resolver_keys = [ + "upgrade_strategy", + "force_reinstall", + "ignore_dependencies", + "ignore_installed", + "use_user_site", + "isolated", + "use_user_site", + ] + resolver_args = {key: kwargs[key] for key in resolver_keys if key in kwargs} + if resolver_provider is None: + raise TypeError("Cannot resolve without a resolver provider... failed!") + preparer = ctx.enter_context(make_preparer_provider(**preparer_args)) + resolver = resolver_provider( + finder=finder, + preparer=preparer, + session=session, + options=options, + install_cmd=install_command, + wheel_cache=wheel_cache, + **resolver_args + ) # type: ignore + resolver.require_hashes = kwargs.get("require_hashes", False) # type: ignore + _, required_resolver_args = get_method_args(resolver.resolve) + resolver_args = [] + if "requirement_set" in required_resolver_args.args: + reqset.add_requirement(ireq) + resolver_args.append(reqset) + elif "root_reqs" in required_resolver_args.args: + resolver_args.append([ireq]) + if "check_supported_wheels" in required_resolver_args.args: + resolver_args.append(check_supported_wheels) + result_reqset = resolver.resolve(*resolver_args) # type: ignore + if result_reqset is None: + result_reqset = reqset + results = result_reqset.requirements + cleanup_fn = getattr(reqset, "cleanup_files", None) + if cleanup_fn is not None: + cleanup_fn() + return results + + +def build_wheel( # noqa:C901 + req=None, # type: Optional[TInstallRequirement] + reqset=None, # type: Optional[Union[TReqSet, Iterable[TInstallRequirement]]] + output_dir=None, # type: Optional[str] + preparer=None, # type: Optional[TPreparer] + wheel_cache=None, # type: Optional[TWheelCache] + build_options=None, # type: Optional[List[str]] + global_options=None, # type: Optional[List[str]] + check_binary_allowed=None, # type: Optional[Callable[TInstallRequirement, bool]] + no_clean=False, # type: bool + session=None, # type: Optional[TSession] + finder=None, # type: Optional[TFinder] + install_command=None, # type: Optional[TCommand] + req_tracker=None, # type: Optional[TReqTracker] + build_dir=None, # type: Optional[str] + src_dir=None, # type: Optional[str] + download_dir=None, # type: Optional[str] + wheel_download_dir=None, # type: Optional[str] + cache_dir=None, # type: Optional[str] + use_user_site=False, # type: bool + use_pep517=None, # type: Optional[bool] + verify=False, # type: bool + editable=False, # type: bool + format_control_provider=None, # type: Optional[TShimmedFunc] + wheel_cache_provider=None, # type: Optional[TShimmedFunc] + preparer_provider=None, # type: Optional[TShimmedFunc] + wheel_builder_provider=None, # type: Optional[TShimmedFunc] + build_one_provider=None, # type: Optional[TShimmedFunc] + build_one_inside_env_provider=None, # type: Optional[TShimmedFunc] + build_many_provider=None, # type: Optional[TShimmedFunc] + install_command_provider=None, # type: Optional[TShimmedFunc] + finder_provider=None, # type: Optional[TShimmedFunc] + reqset_provider=None, # type: Optional[TShimmedFunc] +): + # type: (...) -> Generator[Union[str, Tuple[List[TInstallRequirement], ...]], None, None] + """ + Build a wheel or a set of wheels + + :raises TypeError: Raised when no requirements are provided + :param Optional[TInstallRequirement] req: An `InstallRequirement` to build + :param Optional[TReqSet] reqset: A `RequirementSet` instance (`pip<10`) or an + iterable of `InstallRequirement` instances (`pip>=10`) to build + :param Optional[str] output_dir: Target output directory, only useful when building + one wheel using pip>=20.0 + :param Optional[TPreparer] preparer: A preparer instance, defaults to None + :param Optional[TWheelCache] wheel_cache: A wheel cache instance, defaults to None + :param Optional[List[str]] build_options: A list of build options to pass in + :param Optional[List[str]] global_options: A list of global options to pass in + :param Optional[Callable[TInstallRequirement, bool]] check_binary_allowed: A callable + to check whether we are allowed to build and cache wheels for an ireq + :param bool no_clean: Whether to avoid cleaning up wheels + :param Optional[TSession] session: A `PipSession` instance to pass to create a + `finder` if necessary + :param Optional[TFinder] finder: A `PackageFinder` instance to use for generating a + `WheelBuilder` instance on `pip<20` + :param Optional[TCommandInstance] install_command: The install command used to + create the finder, session, and options if needed, defaults to None. + :param Optional[TReqTracker] req_tracker: An optional requirement tracker instance, + if one already exists + :param Optional[str] build_dir: Passthrough parameter for building preparer + :param Optional[str] src_dir: Passthrough parameter for building preparer + :param Optional[str] download_dir: Passthrough parameter for building preparer + :param Optional[str] wheel_download_dir: Passthrough parameter for building preparer + :param Optional[str] cache_dir: Passthrough cache directory for wheel cache options + :param bool use_user_site: Whether to use the user site directory when preparing + install requirements on `pip<20` + :param Optional[bool] use_pep517: When set to *True* or *False*, prefers building + with or without pep517 as specified, otherwise uses requirement preference. + Only works for single requirements. + :param Optional[TShimmedFunc] format_control_provider: A provider for the + `FormatControl` class + :param Optional[TShimmedFunc] wheel_cache_provider: A provider for the `WheelCache` + class + :param Optional[TShimmedFunc] preparer_provider: A provider for the + `RequirementPreparer` class + :param Optional[TShimmedFunc] wheel_builder_provider: A provider for the + `WheelBuilder` class, if it exists + :param Optional[TShimmedFunc] build_one_provider: A provider for the `_build_one` + function, if it exists + :param Optional[TShimmedFunc] build_one_inside_env_provider: A provider for the + `_build_one_inside_env` function, if it exists + :param Optional[TShimmedFunc] build_many_provider: A provider for the `build` + function, if it exists + :param Optional[TShimmedFunc] install_command_provider: A shim for providing new + install command instances + :param TShimmedFunc finder_provider: A provider to package finder instances + :param TShimmedFunc reqset_provider: A provider for requirement set generation + :return: A tuple of successful and failed install requirements or else a path to + a wheel + :rtype: Optional[Union[str, Tuple[List[TInstallRequirement], List[TInstallRequirement]]]] + """ + wheel_cache_provider = resolve_possible_shim(wheel_cache_provider) + preparer_provider = resolve_possible_shim(preparer_provider) + wheel_builder_provider = resolve_possible_shim(wheel_builder_provider) + build_one_provider = resolve_possible_shim(build_one_provider) + build_one_inside_env_provider = resolve_possible_shim(build_one_inside_env_provider) + build_many_provider = resolve_possible_shim(build_many_provider) + install_cmd_provider = resolve_possible_shim(install_command_provider) + format_control_provider = resolve_possible_shim(format_control_provider) + finder_provider = resolve_possible_shim(finder_provider) or get_package_finder + reqset_provider = resolve_possible_shim(reqset_provider) + global_options = [] if global_options is None else global_options + build_options = [] if build_options is None else build_options + options = None + kwarg_map = { + "cache_dir": cache_dir, + "src_dir": src_dir, + "download_dir": download_dir, + "wheel_download_dir": wheel_download_dir, + "build_dir": build_dir, + "use_user_site": use_user_site, + } + if not req and not reqset: + raise TypeError("Must provide either a requirement or requirement set to build") + with ExitStack() as ctx: + kwargs = kwarg_map.copy() + if wheel_cache is None and (reqset is not None or output_dir is None): + if install_command is None: + assert isinstance(install_cmd_provider, (type, functools.partial)) + install_command = install_cmd_provider() + kwargs, options = populate_options(install_command, options, **kwarg_map) + format_control = getattr(options, "format_control", None) + if not format_control: + format_control = format_control_provider(None, None) # type: ignore + wheel_cache = ctx.enter_context( + wheel_cache_provider(options.cache_dir, format_control) + ) + if req and not reqset and not output_dir: + output_dir = get_ireq_output_path(wheel_cache, req) + if not reqset and build_one_provider: + build_one_kwargs = { + "req": req, + "output_dir": output_dir, + "verify": verify, + "editable": editable, + "build_options": build_options, + "global_options": global_options, + } + yield call_function_with_correct_args(build_one_provider, **build_one_kwargs) + elif build_many_provider: + yield build_many_provider( + reqset, wheel_cache, build_options, global_options, check_binary_allowed + ) + else: + builder_args, builder_kwargs = get_allowed_args(wheel_builder_provider) + if "requirement_set" in builder_args and not reqset: + reqset = reqset_provider() + if session is None and finder is None: + session = get_session(install_cmd=install_command, options=options) + finder = finder_provider( + install_command, options=options, session=session + ) + if preparer is None: + preparer_kwargs = { + "build_dir": kwargs["build_dir"], + "src_dir": kwargs["src_dir"], + "download_dir": kwargs["download_dir"], + "wheel_download_dir": kwargs["wheel_download_dir"], + "finder": finder, + "session": session + if session + else get_session(install_cmd=install_command, options=options), + "install_cmd": install_command, + "options": options, + "use_user_site": use_user_site, + "req_tracker": req_tracker, + } + preparer = ctx.enter_context(preparer_provider(**preparer_kwargs)) + check_bin = check_binary_allowed if check_binary_allowed else lambda x: True + builder_kwargs = { + "requirement_set": reqset, + "finder": finder, + "preparer": preparer, + "wheel_cache": wheel_cache, + "no_clean": no_clean, + "build_options": build_options, + "global_options": global_options, + "check_binary_allowed": check_bin, + } + builder = call_function_with_correct_args( + wheel_builder_provider, **builder_kwargs + ) + if req and not reqset: + if not output_dir: + output_dir = get_ireq_output_path(wheel_cache, req) + if use_pep517 is not None: + req.use_pep517 = use_pep517 + yield builder._build_one(req, output_dir) + else: + yield builder.build(reqset) diff --git a/pipenv/vendor/pip_shims/environment.py b/pipenv/vendor/pip_shims/environment.py new file mode 100644 index 00000000..2268ea26 --- /dev/null +++ b/pipenv/vendor/pip_shims/environment.py @@ -0,0 +1,45 @@ +# -*- coding=utf-8 -*- +""" +Module with functionality to learn about the environment. +""" +from __future__ import absolute_import + +import importlib +import os + + +def get_base_import_path(): + base_import_path = os.environ.get("PIP_SHIMS_BASE_MODULE", "pip") + return base_import_path + + +BASE_IMPORT_PATH = get_base_import_path() + + +def get_pip_version(import_path=BASE_IMPORT_PATH): + try: + pip = importlib.import_module(import_path) + except ImportError: + if import_path != "pip": + return get_pip_version(import_path="pip") + else: + import subprocess + + version = subprocess.check_output(["pip", "--version"]) + if version: + version = version.decode("utf-8").split()[1] + return version + return "0.0.0" + version = getattr(pip, "__version__", None) + return version + + +def is_type_checking(): + try: + from typing import TYPE_CHECKING + except ImportError: + return False + return TYPE_CHECKING + + +MYPY_RUNNING = os.environ.get("MYPY_RUNNING", is_type_checking()) diff --git a/pipenv/vendor/pip_shims/models.py b/pipenv/vendor/pip_shims/models.py new file mode 100644 index 00000000..cbdbb75b --- /dev/null +++ b/pipenv/vendor/pip_shims/models.py @@ -0,0 +1,1201 @@ +# -*- coding: utf-8 -*- +""" +Helper module for shimming functionality across pip versions. +""" +from __future__ import absolute_import, print_function + +import collections +import functools +import importlib +import inspect +import operator +import sys +import types +import weakref +from collections.abc import Mapping, Sequence + +from . import compat +from .environment import BASE_IMPORT_PATH, MYPY_RUNNING, get_pip_version +from .utils import ( + add_mixin_to_class, + apply_alias, + ensure_function, + fallback_is_artifact, + fallback_is_file_url, + fallback_is_vcs, + get_method_args, + has_property, + make_classmethod, + make_method, + nullcontext, + parse_version, + resolve_possible_shim, + set_default_kwargs, + split_package, + suppress_setattr, +) + +if MYPY_RUNNING: + import packaging.version + + Module = types.ModuleType + from typing import ( # noqa:F811 + Any, + Callable, + ContextManager, + Dict, + Iterable, + List, + Mapping, + Optional, + Set, + Tuple, + Type, + TypeVar, + Union, + ) + + +PIP_VERSION_SET = { + "7.0.0", + "7.0.1", + "7.0.2", + "7.0.3", + "7.1.0", + "7.1.1", + "7.1.2", + "8.0.0", + "8.0.1", + "8.0.2", + "8.0.3", + "8.1.0", + "8.1.1", + "8.1.2", + "9.0.0", + "9.0.1", + "9.0.2", + "9.0.3", + "10.0.0", + "10.0.1", + "18.0", + "18.1", + "19.0", + "19.0.1", + "19.0.2", + "19.0.3", + "19.1", + "19.1.1", + "19.2", + "19.2.1", + "19.2.2", + "19.2.3", + "19.3", + "19.3.1", + "20.0", + "20.0.1", + "20.0.2", +} + + +ImportTypesBase = collections.namedtuple( + "ImportTypes", ["FUNCTION", "CLASS", "MODULE", "CONTEXTMANAGER"] +) + + +class ImportTypes(ImportTypesBase): + FUNCTION = 0 + CLASS = 1 + MODULE = 2 + CONTEXTMANAGER = 3 + METHOD = 4 + ATTRIBUTE = 5 + + +class PipVersion(Sequence): + def __init__( + self, + version, + round_prereleases_up=True, + base_import_path=None, + vendor_import_path="pipenv.patched.notpip._vendor", + ): + # type: (str, bool, Optional[str], str) -> None + self.version = version + self.vendor_import_path = vendor_import_path + self.round_prereleases_up = round_prereleases_up + parsed_version = self._parse() + if round_prereleases_up and parsed_version.is_prerelease: + parsed_version._version = parsed_version._version._replace(dev=None, pre=None) + self.version = str(parsed_version) + parsed_version = self._parse() + if base_import_path is None: + if parsed_version >= parse_version("10.0.0"): + base_import_path = "{}._internal".format(BASE_IMPORT_PATH) + else: + base_import_path = "{}".format(BASE_IMPORT_PATH) + self.base_import_path = base_import_path + self.parsed_version = parsed_version + + @property + def version_tuple(self): + return tuple(self.parsed_version._version) + + @property + def version_key(self): + return self.parsed_version._key + + def is_valid(self, compared_to): + # type: (PipVersion) -> bool + return self == compared_to + + def __len__(self): + # type: () -> int + return len(self.version_tuple) + + def __getitem__(self, item): + return self.version_tuple[item] + + def _parse(self): + # type: () -> packaging.version._BaseVersion + return parse_version(self.version) + + def __hash__(self): + # type: () -> int + return hash(self.parsed_version) + + def __str__(self): + # type: () -> str + return "{!s}".format(self.parsed_version) + + def __repr__(self): + # type: () -> str + return ( + "<PipVersion {!r}, Path: {!r}, Vendor Path: {!r}, " "Parsed Version: {!r}>" + ).format( + self.version, + self.base_import_path, + self.vendor_import_path, + self.parsed_version, + ) + + def __gt__(self, other): + # type: (PipVersion) -> bool + return self.parsed_version > other.parsed_version + + def __lt__(self, other): + # type: (PipVersion) -> bool + return self.parsed_version < other.parsed_version + + def __le__(self, other): + # type: (PipVersion) -> bool + return self.parsed_version <= other.parsed_version + + def __ge__(self, other): + # type: (PipVersion) -> bool + return self.parsed_version >= other.parsed_version + + def __ne__(self, other): + # type: (object) -> bool + if not isinstance(other, PipVersion): + return NotImplemented + return self.parsed_version != other.parsed_version + + def __eq__(self, other): + # type: (object) -> bool + if not isinstance(other, PipVersion): + return NotImplemented + return self.parsed_version == other.parsed_version + + +version_cache = weakref.WeakValueDictionary() # type: Mapping[str, PipVersion] +CURRENT_PIP_VERSION = None # type: Optional[PipVersion] + + +def pip_version_lookup(version, *args, **kwargs): + # type: (str, Any, Any) -> PipVersion + try: + cached = version_cache.get(version) + except KeyError: + cached = None + if cached is not None: + return cached + pip_version = PipVersion(version, *args, **kwargs) + version_cache[version] = pip_version + return pip_version + + +def lookup_current_pip_version(): + # type: () -> PipVersion + global CURRENT_PIP_VERSION + if CURRENT_PIP_VERSION is not None: + return CURRENT_PIP_VERSION + CURRENT_PIP_VERSION = pip_version_lookup(get_pip_version()) + return CURRENT_PIP_VERSION + + +class PipVersionRange(Sequence): + def __init__(self, start, end): + # type: (PipVersion, PipVersion) -> None + if start > end: + raise ValueError("Start version must come before end version") + self._versions = (start, end) + + def __str__(self): + # type: () -> str + return "{!s} -> {!s}".format(self._versions[0], self._versions[-1]) + + @property + def base_import_paths(self): + # type: () -> Set[str] + return {version.base_import_path for version in self._versions} + + @property + def vendor_import_paths(self): + # type: () -> Set[str] + return {version.vendor_import_path for version in self._versions} + + def is_valid(self): + # type: () -> bool + return pip_version_lookup(get_pip_version()) in self + + def __contains__(self, item): + # type: (PipVersion) -> bool + if not isinstance(item, PipVersion): + raise TypeError("Need a PipVersion instance to compare") + return item >= self[0] and item <= self[-1] + + def __getitem__(self, item): + # type: (int) -> PipVersion + return self._versions[item] + + def __len__(self): + # type: () -> int + return len(self._versions) + + def __lt__(self, other): + # type: ("PipVersionRange") -> bool + return (other.is_valid() and not self.is_valid()) or ( + not (self.is_valid() or other.is_valid()) + or (self.is_valid() and other.is_valid()) + and self._versions[-1] < other._versions[-1] + ) + + def __hash__(self): + # type: () -> int + return hash(self._versions) + + +class ShimmedPath(object): + __modules = {} # type: Dict[str, Module] + + def __init__( + self, + name, # type: str + import_target, # type: str + import_type, # type: int + version_range, # type: PipVersionRange + provided_methods=None, # type: Optional[Dict[str, Callable]] + provided_functions=None, # type: Optional[Dict[str, Callable]] + provided_classmethods=None, # type: Optional[Dict[str, Callable]] + provided_contextmanagers=None, # type: Optional[Dict[str, Callable]] + provided_mixins=None, # type: Optional[List[Type]] + default_args=None, # type: Dict[str, Sequence[List[Any], Dict[str, Any]]] + ): + # type: (...) -> None + if provided_methods is None: + provided_methods = {} + if provided_classmethods is None: + provided_classmethods = {} + if provided_functions is None: + provided_functions = {} + if provided_contextmanagers is None: + provided_contextmanagers = {} + if provided_mixins is None: + provided_mixins = [] + if default_args is None: + default_args = {} + self.version_range = version_range + self.name = name + self.full_import_path = import_target + module_path, name_to_import = split_package(import_target) + self.module_path = module_path + self.name_to_import = name_to_import + self.import_type = import_type + self._imported = None # type: Optional[Module] + self._provided = None # type: Optional[Union[Module, Type, Callable, Any]] + self.provided_methods = provided_methods + self.provided_functions = provided_functions + self.provided_classmethods = provided_classmethods + self.provided_contextmanagers = provided_contextmanagers + self.provided_mixins = [m for m in provided_mixins if m is not None] + self.default_args = default_args + self.aliases = [] # type: List[List[str]] + self._shimmed = None # type: Optional[Any] + + def _as_tuple(self): + # type: () -> Tuple[str, PipVersionRange, str, int] + return (self.name, self.version_range, self.full_import_path, self.import_type) + + def alias(self, aliases): + # type: (List[str]) -> "ShimmedPath" + self.aliases.append(aliases) + return self + + @classmethod + def _import_module(cls, module): + # type: (str) -> Optional[Module] + if module in ShimmedPath.__modules: + result = ShimmedPath.__modules[module] + if result is not None: + return result + try: + imported = importlib.import_module(module) + except ImportError: + return None + else: + ShimmedPath.__modules[module] = imported + return imported + + @classmethod + def _parse_provides_dict( + cls, + provides, # type: Dict[str, Callable] + prepend_arg_to_callables=None, # type: Optional[str] + ): + # type: (...) -> Dict[str, Callable] + creating_methods = False + creating_classmethods = False + if prepend_arg_to_callables is not None: + if prepend_arg_to_callables == "self": + creating_methods = True + elif prepend_arg_to_callables == "cls": + creating_classmethods = True + provides_map = {} + for item_name, item_value in provides.items(): + if isinstance(item_value, ShimmedPath): + item_value = item_value.shim() + if inspect.isfunction(item_value): + callable_args = inspect.getargs(item_value.__code__).args + if "self" not in callable_args and creating_methods: + item_value = make_method(item_value)(item_name) + elif "cls" not in callable_args and creating_classmethods: + item_value = make_classmethod(item_value)(item_name) + elif isinstance(item_value, str): + module_path, name = split_package(item_value) + module = cls._import_module(module_path) + item_value = getattr(module, name, None) + if item_value is not None: + provides_map[item_name] = item_value + return provides_map + + def _update_default_kwargs(self, parent, provided): + # type: (Union[Module, None], Union[Type, Module]) -> Tuple[Optional[Module], Union[Type, Module]] # noqa + for func_name, defaults in self.default_args.items(): + # * Note that we set default args here because we have the + # * option to use it, even though currently we dont + # * so we are forcibly ignoring the linter warning about it + default_args, default_kwargs = defaults # noqa:W0612 + provided = set_default_kwargs( + provided, func_name, *default_args, **default_kwargs + ) + return parent, provided + + def _ensure_functions(self, provided): + # type: (Union[Module, Type, None]) -> Any + functions = self._parse_provides_dict(self.provided_functions) + if provided is None: + provided = __module__ # type: ignore # noqa:F821 + for funcname, func in functions.items(): + func = ensure_function(provided, funcname, func) + setattr(provided, funcname, func) + return provided + + def _ensure_methods(self, provided): + # type: (Type) -> Type + """Given a base class, a new name, and any number of functions to + attach, turns those functions into classmethods, attaches them, + and returns an updated class object. + """ + if not self.is_class: + return provided + if not inspect.isclass(provided): + raise TypeError("Provided argument is not a class: {!r}".format(provided)) + methods = self._parse_provides_dict( + self.provided_methods, prepend_arg_to_callables="self" + ) + classmethods = self._parse_provides_dict( + self.provided_classmethods, prepend_arg_to_callables="cls" + ) + if not methods and not classmethods: + return provided + classname = provided.__name__ + type_ = type(classname, (provided,), {}) + + if classmethods: + for method_name, clsmethod in classmethods.items(): + if method_name not in provided.__dict__: + type.__setattr__(type_, method_name, clsmethod) + + if methods: + for method_name, clsmethod in methods.items(): + if method_name not in provided.__dict__: + type.__setattr__(type_, method_name, clsmethod) + return type_ + + @property + def is_class(self): + # type: () -> bool + return self.import_type == ImportTypes.CLASS + + @property + def is_module(self): + # type: () -> bool + return self.import_type == ImportTypes.MODULE + + @property + def is_method(self): + # type: () -> bool + return self.import_type == ImportTypes.METHOD + + @property + def is_function(self): + # type: () -> bool + return self.import_type == ImportTypes.FUNCTION + + @property + def is_contextmanager(self): + # type: () -> bool + return self.import_type == ImportTypes.CONTEXTMANAGER + + @property + def is_attribute(self): + # type: () -> bool + return self.import_type == ImportTypes.ATTRIBUTE + + def __contains__(self, pip_version): + # type: (str) -> bool + return pip_version_lookup(pip_version) in self.version_range + + @property + def is_valid(self): + # type: () -> bool + return self.version_range.is_valid() + + @property + def sort_order(self): + # type: () -> int + return 1 if self.is_valid else 0 + + def _shim_base(self, imported, attribute_name): + # type: (Union[Module, None], str) -> Any + result = getattr(imported, attribute_name, None) + return self._apply_aliases(imported, result) + + def _apply_aliases(self, imported, target): + # type: (Union[Module, None], Any) -> Any + for alias_list in self.aliases: + target = apply_alias(imported, target, *alias_list) + suppress_setattr(imported, self.name, target) + return target + + def _shim_parent(self, imported, attribute_name): + # type: (Union[Module, None], str) -> Tuple[Optional[Module], Any] + result = self._shim_base(imported, attribute_name) + if result is not None: + imported, result = self._update_default_kwargs(imported, result) + suppress_setattr(imported, attribute_name, result) + return imported, result + + def update_sys_modules(self, imported): + # type: (Optional[Module]) -> None + if imported is None: + return None + if self.calculated_module_path in sys.modules: + del sys.modules[self.calculated_module_path] + sys.modules[self.calculated_module_path] = imported + + def shim_class(self, imported, attribute_name): + # type: (Union[Module, None], str) -> Type + imported, result = self._shim_parent(imported, attribute_name) + if result is not None: + assert inspect.isclass(result) # noqa + result = self._ensure_methods(result) + if self.provided_mixins: + result = add_mixin_to_class(result, self.provided_mixins) + self._imported = imported + self._provided = result + self.update_sys_modules(imported) + if imported is not None: + ShimmedPath.__modules[imported.__name__] = imported + return result + + def shim_module(self, imported, attribute_name): + # type: (Union[Module, None], str) -> Module + imported, result = self._shim_parent(imported, attribute_name) + if result is not None: + result = self._ensure_functions(result) + full_import_path = "{}.{}".format(self.calculated_module_path, attribute_name) + self._imported = imported + assert isinstance(result, types.ModuleType) + self._provided = result + if full_import_path in sys.modules: + del sys.modules[full_import_path] + sys.modules[full_import_path] = result + self.update_sys_modules(imported) + if imported is not None: + ShimmedPath.__modules[imported.__name__] = imported + return result # type: ignore + + def shim_function(self, imported, attribute_name): + # type: (Union[Module, None], str) -> Callable + return self._shim_base(imported, attribute_name) + + def shim_attribute(self, imported, attribute_name): + # type: (Union[Module, None], Any) -> Any + return self._shim_base(imported, attribute_name) + + def shim_contextmanager(self, imported, attribute_name): + # type: (Union[Module, None], str) -> Callable + result = self._shim_base(imported, attribute_name) + if result is None: + result = nullcontext + suppress_setattr(imported, attribute_name, result) + self.update_sys_modules(imported) + return result + + @property + def shimmed(self): + # type: () -> Any + if self._shimmed is None: + self._shimmed = self.shim() + return self._shimmed + + def shim(self): + # type: () -> (Union[Module, Callable, ContextManager, Type]) + imported = self._import() + if self.is_class: + return self.shim_class(imported, self.name_to_import) + elif self.is_module: + return self.shim_module(imported, self.name_to_import) + elif self.is_contextmanager: + return self.shim_contextmanager(imported, self.name_to_import) + elif self.is_function: + return self.shim_function(imported, self.name_to_import) + elif self.is_attribute: + return self.shim_attribute(imported, self.name_to_import) + return self._shim_base(imported, self.name_to_import) + + @property + def calculated_module_path(self): + current_pip = lookup_current_pip_version() + prefix = current_pip.base_import_path + return ".".join([prefix, self.module_path]).rstrip(".") + + def _import(self, prefix=None): + # type: (Optional[str]) -> Optional[Module] + # TODO: Decide whether to use _imported and _shimmed or to set the shimmed + # always to _imported and never save the unshimmed module + if self._imported is not None: + return self._imported + result = self._import_module(self.calculated_module_path) + return result + + def __hash__(self): + # type: () -> int + return hash(self._as_tuple()) + + +class ShimmedPathCollection(object): + + __registry = {} # type: Dict[str, Any] + + def __init__(self, name, import_type, paths=None): + # type: (str, int, Optional[Sequence[ShimmedPath]]) -> None + self.name = name + self.import_type = import_type + self.paths = set() # type: Set[ShimmedPath] + self.top_path = None + self._default = None + self._default_args = {} # type: Dict[str, Sequence[List[Any], Dict[str, Any]]] + self.provided_methods = {} # type: Dict[str, Callable] + self.provided_functions = {} # type: Dict[str, Callable] + self.provided_contextmanagers = {} # type: Dict[str, Callable] + self.provided_classmethods = {} # type: Dict[str, Callable] + self.provided_mixins = [] # type: List[Type] + self.pre_shim_functions = [] # type: List[Callable] + self.aliases = [] # type: List[List[str]] + if paths is not None: + if isinstance(paths, str): + self.create_path(paths, version_start=lookup_current_pip_version()) + else: + self.paths.update(set(paths)) + self.register() + + def register(self): + # type: () -> None + self.__registry[self.name] = self + + @classmethod + def get_registry(cls): + # type: () -> Dict[str, "ShimmedPathCollection"] + return cls.__registry.copy() + + def add_path(self, path): + # type: (ShimmedPath) -> None + self.paths.add(path) + + def set_default(self, default): + # type: (Any) -> None + if isinstance(default, (ShimmedPath, ShimmedPathCollection)): + default = default.shim() + try: + default.__qualname__ = default.__name__ = self.name + except AttributeError: + pass + self._default = default + + def set_default_args(self, callable_name, *args, **kwargs): + # type: (str, Any, Any) -> None + self._default_args.update({callable_name: [args, kwargs]}) + + def provide_function(self, name, fn): + # type: (str, Union[Callable, ShimmedPath, ShimmedPathCollection]) -> None + if isinstance(fn, (ShimmedPath, ShimmedPathCollection)): + fn = resolve_possible_shim(fn) # type: ignore + self.provided_functions[name] = fn # type: ignore + + def provide_method(self, name, fn): + # type: (str, Union[Callable, ShimmedPath, ShimmedPathCollection, property]) -> None + if isinstance(fn, (ShimmedPath, ShimmedPathCollection)): + fn = resolve_possible_shim(fn) # type: ignore + self.provided_methods[name] = fn # type: ignore + + def alias(self, aliases): + # type: (List[str]) -> None + """ + Takes a list of methods, functions, attributes, etc and ensures they + all exist on the object pointing at the same referent. + + :param List[str] aliases: Names to map to the same functionality if they do not + exist. + :return: None + :rtype: None + """ + self.aliases.append(aliases) + + def add_mixin(self, mixin): + # type: (Optional[Union[Type, ShimmedPathCollection]]) -> None + if isinstance(mixin, ShimmedPathCollection): + mixin = mixin.shim() + if mixin is not None and inspect.isclass(mixin): + self.provided_mixins.append(mixin) + + def create_path(self, import_path, version_start, version_end=None): + # type: (str, str, Optional[str]) -> None + pip_version_start = pip_version_lookup(version_start) + if version_end is None: + version_end = "9999" + pip_version_end = pip_version_lookup(version_end) + version_range = PipVersionRange(pip_version_start, pip_version_end) + new_path = ShimmedPath( + self.name, + import_path, + self.import_type, + version_range, + self.provided_methods, + self.provided_functions, + self.provided_classmethods, + self.provided_contextmanagers, + self.provided_mixins, + self._default_args, + ) + if self.aliases: + for alias_list in self.aliases: + new_path.alias(alias_list) + self.add_path(new_path) + + def _sort_paths(self): + # type: () -> List[ShimmedPath] + return sorted(self.paths, key=operator.attrgetter("version_range"), reverse=True) + + def _get_top_path(self): + # type: () -> Optional[ShimmedPath] + return next(iter(self._sort_paths()), None) + + @classmethod + def traverse(cls, shim): + # type: (Union[ShimmedPath, ShimmedPathCollection, Any]) -> Any + if isinstance(shim, (ShimmedPath, ShimmedPathCollection)): + result = shim.shim() + return result + return shim + + def shim(self): + # type: () -> Any + top_path = self._get_top_path() # type: Union[ShimmedPath, None] + if not self.pre_shim_functions: + result = self.traverse(top_path) + else: + for fn in self.pre_shim_functions: + result = fn(top_path) + result = self.traverse(result) + if result == nullcontext and self._default is not None: + default_result = self.traverse(self._default) + if default_result: + return default_result + if result is None and self._default is not None: + result = self.traverse(self._default) + return result + + def pre_shim(self, fn): + # type: (Callable) -> None + self.pre_shim_functions.append(fn) + + +def import_pip(): + return importlib.import_module("pip") + + +_strip_extras = ShimmedPathCollection("_strip_extras", ImportTypes.FUNCTION) +_strip_extras.create_path("req.req_install._strip_extras", "7.0.0", "18.0.0") +_strip_extras.create_path("req.constructors._strip_extras", "18.1.0") + +cmdoptions = ShimmedPathCollection("cmdoptions", ImportTypes.MODULE) +cmdoptions.create_path("cli.cmdoptions", "18.1", "9999") +cmdoptions.create_path("cmdoptions", "7.0.0", "18.0") + +commands_dict = ShimmedPathCollection("commands_dict", ImportTypes.ATTRIBUTE) +commands_dict.create_path("commands.commands_dict", "7.0.0", "9999") + +SessionCommandMixin = ShimmedPathCollection("SessionCommandMixin", ImportTypes.CLASS) +SessionCommandMixin.create_path("cli.req_command.SessionCommandMixin", "19.3.0", "9999") + +Command = ShimmedPathCollection("Command", ImportTypes.CLASS) +Command.set_default_args("__init__", name="PipCommand", summary="Default pip command.") +Command.add_mixin(SessionCommandMixin) +Command.create_path("cli.base_command.Command", "18.1", "9999") +Command.create_path("basecommand.Command", "7.0.0", "18.0") + +ConfigOptionParser = ShimmedPathCollection("ConfigOptionParser", ImportTypes.CLASS) +ConfigOptionParser.create_path("cli.parser.ConfigOptionParser", "18.1", "9999") +ConfigOptionParser.create_path("baseparser.ConfigOptionParser", "7.0.0", "18.0") + +InstallCommand = ShimmedPathCollection("InstallCommand", ImportTypes.CLASS) +InstallCommand.pre_shim( + functools.partial(compat.partial_command, cmd_mapping=commands_dict) +) +InstallCommand.create_path("commands.install.InstallCommand", "7.0.0", "9999") + +DistributionNotFound = ShimmedPathCollection("DistributionNotFound", ImportTypes.CLASS) +DistributionNotFound.create_path("exceptions.DistributionNotFound", "7.0.0", "9999") + +FAVORITE_HASH = ShimmedPathCollection("FAVORITE_HASH", ImportTypes.ATTRIBUTE) +FAVORITE_HASH.create_path("utils.hashes.FAVORITE_HASH", "7.0.0", "9999") + +FormatControl = ShimmedPathCollection("FormatControl", ImportTypes.CLASS) +FormatControl.create_path("models.format_control.FormatControl", "18.1", "9999") +FormatControl.create_path("index.FormatControl", "7.0.0", "18.0") + +FrozenRequirement = ShimmedPathCollection("FrozenRequirement", ImportTypes.CLASS) +FrozenRequirement.create_path("FrozenRequirement", "7.0.0", "9.0.3") +FrozenRequirement.create_path("operations.freeze.FrozenRequirement", "10.0.0", "9999") + +get_installed_distributions = ShimmedPathCollection( + "get_installed_distributions", ImportTypes.FUNCTION +) +get_installed_distributions.create_path( + "utils.misc.get_installed_distributions", "10", "21.2.999" +) +get_installed_distributions.create_path("utils.get_installed_distributions", "7", "9.0.3") + +get_supported = ShimmedPathCollection("get_supported", ImportTypes.FUNCTION) +get_supported.create_path("pep425tags.get_supported", "7.0.0", "9999") + +get_tags = ShimmedPathCollection("get_tags", ImportTypes.FUNCTION) +get_tags.create_path("pep425tags.get_tags", "7.0.0", "9999") + +index_group = ShimmedPathCollection("index_group", ImportTypes.FUNCTION) +index_group.create_path("cli.cmdoptions.index_group", "18.1", "9999") +index_group.create_path("cmdoptions.index_group", "7.0.0", "18.0") + +InstallationError = ShimmedPathCollection("InstallationError", ImportTypes.CLASS) +InstallationError.create_path("exceptions.InstallationError", "7.0.0", "9999") + +UninstallationError = ShimmedPathCollection("UninstallationError", ImportTypes.CLASS) +UninstallationError.create_path("exceptions.UninstallationError", "7.0.0", "9999") + +DistributionNotFound = ShimmedPathCollection("DistributionNotFound", ImportTypes.CLASS) +DistributionNotFound.create_path("exceptions.DistributionNotFound", "7.0.0", "9999") + +RequirementsFileParseError = ShimmedPathCollection( + "RequirementsFileParseError", ImportTypes.CLASS +) +RequirementsFileParseError.create_path( + "exceptions.RequirementsFileParseError", "7.0.0", "9999" +) + +BestVersionAlreadyInstalled = ShimmedPathCollection( + "BestVersionAlreadyInstalled", ImportTypes.CLASS +) +BestVersionAlreadyInstalled.create_path( + "exceptions.BestVersionAlreadyInstalled", "7.0.0", "9999" +) + +BadCommand = ShimmedPathCollection("BadCommand", ImportTypes.CLASS) +BadCommand.create_path("exceptions.BadCommand", "7.0.0", "9999") + +CommandError = ShimmedPathCollection("CommandError", ImportTypes.CLASS) +CommandError.create_path("exceptions.CommandError", "7.0.0", "9999") + +PreviousBuildDirError = ShimmedPathCollection("PreviousBuildDirError", ImportTypes.CLASS) +PreviousBuildDirError.create_path("exceptions.PreviousBuildDirError", "7.0.0", "9999") + +install_req_from_editable = ShimmedPathCollection( + "install_req_from_editable", ImportTypes.FUNCTION +) +install_req_from_editable.create_path( + "req.constructors.install_req_from_editable", "18.1", "9999" +) +install_req_from_editable.create_path( + "req.req_install.InstallRequirement.from_editable", "7.0.0", "18.0" +) + +install_req_from_line = ShimmedPathCollection( + "install_req_from_line", ImportTypes.FUNCTION +) +install_req_from_line.create_path( + "req.constructors.install_req_from_line", "18.1", "9999" +) +install_req_from_line.create_path( + "req.req_install.InstallRequirement.from_line", "7.0.0", "18.0" +) + +install_req_from_req_string = ShimmedPathCollection( + "install_req_from_req_string", ImportTypes.FUNCTION +) +install_req_from_req_string.create_path( + "req.constructors.install_req_from_req_string", "19.0", "9999" +) + +InstallRequirement = ShimmedPathCollection("InstallRequirement", ImportTypes.CLASS) +InstallRequirement.provide_method("from_line", install_req_from_line) +InstallRequirement.provide_method("from_editable", install_req_from_editable) +InstallRequirement.alias(["build_location", "ensure_build_location"]) + +InstallRequirement.create_path("req.req_install.InstallRequirement", "7.0.0", "9999") + +is_archive_file = ShimmedPathCollection("is_archive_file", ImportTypes.FUNCTION) +is_archive_file.create_path("req.constructors.is_archive_file", "19.3", "9999") +is_archive_file.create_path("download.is_archive_file", "7.0.0", "19.2.3") + +is_file_url = ShimmedPathCollection("is_file_url", ImportTypes.FUNCTION) +is_file_url.set_default(fallback_is_file_url) +is_file_url.create_path("download.is_file_url", "7.0.0", "19.2.3") + +Downloader = ShimmedPathCollection("Downloader", ImportTypes.CLASS) +Downloader.create_path("network.download.Downloader", "19.3.9", "9999") + +unpack_url = ShimmedPathCollection("unpack_url", ImportTypes.FUNCTION) +unpack_url.create_path("download.unpack_url", "7.0.0", "19.3.9") +unpack_url.create_path("operations.prepare.unpack_url", "20.0", "9999") + +is_installable_dir = ShimmedPathCollection("is_installable_dir", ImportTypes.FUNCTION) +is_installable_dir.create_path("utils.misc.is_installable_dir", "10.0.0", "9999") +is_installable_dir.create_path("utils.is_installable_dir", "7.0.0", "9.0.3") + +Link = ShimmedPathCollection("Link", ImportTypes.CLASS) +Link.provide_method("is_vcs", property(fallback_is_vcs)) +Link.provide_method("is_artifact", property(fallback_is_artifact)) +Link.create_path("models.link.Link", "19.0.0", "9999") +Link.create_path("index.Link", "7.0.0", "18.1") + +make_abstract_dist = ShimmedPathCollection("make_abstract_dist", ImportTypes.FUNCTION) +make_abstract_dist.create_path( + "distributions.make_distribution_for_install_requirement", "20.0.0", "9999" +) +make_abstract_dist.create_path( + "distributions.make_distribution_for_install_requirement", "19.1.2", "19.3.9" +) +make_abstract_dist.create_path( + "operations.prepare.make_abstract_dist", "10.0.0", "19.1.1" +) +make_abstract_dist.create_path("req.req_set.make_abstract_dist", "7.0.0", "9.0.3") + +make_distribution_for_install_requirement = ShimmedPathCollection( + "make_distribution_for_install_requirement", ImportTypes.FUNCTION +) +make_distribution_for_install_requirement.create_path( + "distributions.make_distribution_for_install_requirement", "20.0.0", "9999" +) +make_distribution_for_install_requirement.create_path( + "distributions.make_distribution_for_install_requirement", "19.1.2", "19.9.9" +) + +make_option_group = ShimmedPathCollection("make_option_group", ImportTypes.FUNCTION) +make_option_group.create_path("cli.cmdoptions.make_option_group", "18.1", "9999") +make_option_group.create_path("cmdoptions.make_option_group", "7.0.0", "18.0") + +PackageFinder = ShimmedPathCollection("PackageFinder", ImportTypes.CLASS) +PackageFinder.create_path("index.PackageFinder", "7.0.0", "19.9") +PackageFinder.create_path("index.package_finder.PackageFinder", "20.0", "9999") + +CandidateEvaluator = ShimmedPathCollection("CandidateEvaluator", ImportTypes.CLASS) +CandidateEvaluator.set_default(compat.CandidateEvaluator) +CandidateEvaluator.create_path("index.CandidateEvaluator", "19.1.0", "19.3.9") +CandidateEvaluator.create_path("index.package_finder.CandidateEvaluator", "20.0", "9999") + +CandidatePreferences = ShimmedPathCollection("CandidatePreferences", ImportTypes.CLASS) +CandidatePreferences.set_default(compat.CandidatePreferences) +CandidatePreferences.create_path("index.CandidatePreferences", "19.2.0", "19.9") +CandidatePreferences.create_path( + "index.package_finder.CandidatePreferences", "20.0", "9999" +) + +LinkCollector = ShimmedPathCollection("LinkCollector", ImportTypes.CLASS) +LinkCollector.set_default(compat.LinkCollector) +LinkCollector.create_path("collector.LinkCollector", "19.3.0", "19.9") +LinkCollector.create_path("index.collector.LinkCollector", "20.0", "9999") + +LinkEvaluator = ShimmedPathCollection("LinkEvaluator", ImportTypes.CLASS) +LinkEvaluator.set_default(compat.LinkEvaluator) +LinkEvaluator.create_path("index.LinkEvaluator", "19.2.0", "19.9") +LinkEvaluator.create_path("index.package_finder.LinkEvaluator", "20.0", "9999") + +TargetPython = ShimmedPathCollection("TargetPython", ImportTypes.CLASS) +compat.TargetPython.fallback_get_tags = get_tags +TargetPython.set_default(compat.TargetPython) +TargetPython.create_path("models.target_python.TargetPython", "19.2.0", "9999") + +SearchScope = ShimmedPathCollection("SearchScope", ImportTypes.CLASS) +SearchScope.set_default(compat.SearchScope) +SearchScope.create_path("models.search_scope.SearchScope", "19.2.0", "9999") + +SelectionPreferences = ShimmedPathCollection("SelectionPreferences", ImportTypes.CLASS) +SelectionPreferences.set_default(compat.SelectionPreferences) +SelectionPreferences.create_path( + "models.selection_prefs.SelectionPreferences", "19.2.0", "9999" +) + +parse_requirements = ShimmedPathCollection("parse_requirements", ImportTypes.FUNCTION) +parse_requirements.create_path("req.req_file.parse_requirements", "7.0.0", "9999") + +path_to_url = ShimmedPathCollection("path_to_url", ImportTypes.FUNCTION) +path_to_url.create_path("download.path_to_url", "7.0.0", "19.2.3") +path_to_url.create_path("utils.urls.path_to_url", "19.3.0", "9999") + +PipError = ShimmedPathCollection("PipError", ImportTypes.CLASS) +PipError.create_path("exceptions.PipError", "7.0.0", "9999") + +RequirementPreparer = ShimmedPathCollection("RequirementPreparer", ImportTypes.CLASS) +RequirementPreparer.create_path("operations.prepare.RequirementPreparer", "7", "9999") + +RequirementSet = ShimmedPathCollection("RequirementSet", ImportTypes.CLASS) +RequirementSet.create_path("req.req_set.RequirementSet", "7.0.0", "9999") + +RequirementTracker = ShimmedPathCollection( + "RequirementTracker", ImportTypes.CONTEXTMANAGER +) +RequirementTracker.create_path("req.req_tracker.RequirementTracker", "7.0.0", "9999") + +TempDirectory = ShimmedPathCollection("TempDirectory", ImportTypes.CLASS) +TempDirectory.create_path("utils.temp_dir.TempDirectory", "7.0.0", "9999") + +global_tempdir_manager = ShimmedPathCollection( + "global_tempdir_manager", ImportTypes.CONTEXTMANAGER +) +global_tempdir_manager.create_path( + "utils.temp_dir.global_tempdir_manager", "7.0.0", "9999" +) + +shim_unpack = ShimmedPathCollection("shim_unpack", ImportTypes.FUNCTION) +shim_unpack.set_default( + functools.partial( + compat.shim_unpack, + unpack_fn=unpack_url, + downloader_provider=Downloader, + tempdir_manager_provider=global_tempdir_manager, + ) +) + +get_requirement_tracker = ShimmedPathCollection( + "get_requirement_tracker", ImportTypes.CONTEXTMANAGER +) +get_requirement_tracker.set_default( + functools.partial(compat.get_requirement_tracker, RequirementTracker.shim()) +) +get_requirement_tracker.create_path( + "req.req_tracker.get_requirement_tracker", "7.0.0", "9999" +) + +Resolver = ShimmedPathCollection("Resolver", ImportTypes.CLASS) +Resolver.create_path("resolve.Resolver", "7.0.0", "19.1.1") +Resolver.create_path("legacy_resolve.Resolver", "19.1.2", "20.0.89999") +Resolver.create_path("resolution.legacy.resolver.Resolver", "20.0.99999", "99999") + +SafeFileCache = ShimmedPathCollection("SafeFileCache", ImportTypes.CLASS) +SafeFileCache.create_path("network.cache.SafeFileCache", "19.3.0", "9999") +SafeFileCache.create_path("download.SafeFileCache", "7.0.0", "19.2.3") + +UninstallPathSet = ShimmedPathCollection("UninstallPathSet", ImportTypes.CLASS) +UninstallPathSet.create_path("req.req_uninstall.UninstallPathSet", "7.0.0", "9999") + +url_to_path = ShimmedPathCollection("url_to_path", ImportTypes.FUNCTION) +url_to_path.create_path("download.url_to_path", "7.0.0", "19.2.3") +url_to_path.create_path("utils.urls.url_to_path", "19.3.0", "9999") + +USER_CACHE_DIR = ShimmedPathCollection("USER_CACHE_DIR", ImportTypes.ATTRIBUTE) +USER_CACHE_DIR.create_path("locations.USER_CACHE_DIR", "7.0.0", "9999") + +VcsSupport = ShimmedPathCollection("VcsSupport", ImportTypes.CLASS) +VcsSupport.create_path("vcs.VcsSupport", "7.0.0", "19.1.1") +VcsSupport.create_path("vcs.versioncontrol.VcsSupport", "19.2", "9999") + +Wheel = ShimmedPathCollection("Wheel", ImportTypes.CLASS) +Wheel.create_path("wheel.Wheel", "7.0.0", "19.3.9") +Wheel.set_default(compat.Wheel) + +WheelCache = ShimmedPathCollection("WheelCache", ImportTypes.CLASS) +WheelCache.create_path("cache.WheelCache", "10.0.0", "9999") +WheelCache.create_path("wheel.WheelCache", "7", "9.0.3") + +WheelBuilder = ShimmedPathCollection("WheelBuilder", ImportTypes.CLASS) +WheelBuilder.create_path("wheel.WheelBuilder", "7.0.0", "19.9") + +build = ShimmedPathCollection("build", ImportTypes.FUNCTION) +build.create_path("wheel_builder.build", "19.9", "9999") + +build_one = ShimmedPathCollection("build_one", ImportTypes.FUNCTION) +build_one.create_path("wheel_builder._build_one", "19.9", "9999") + +build_one_inside_env = ShimmedPathCollection("build_one_inside_env", ImportTypes.FUNCTION) +build_one_inside_env.create_path("wheel_builder._build_one_inside_env", "19.9", "9999") + +AbstractDistribution = ShimmedPathCollection("AbstractDistribution", ImportTypes.CLASS) +AbstractDistribution.create_path( + "distributions.base.AbstractDistribution", "19.1.2", "9999" +) + +InstalledDistribution = ShimmedPathCollection("InstalledDistribution", ImportTypes.CLASS) +InstalledDistribution.create_path( + "distributions.installed.InstalledDistribution", "19.1.2", "9999" +) + +SourceDistribution = ShimmedPathCollection("SourceDistribution", ImportTypes.CLASS) +SourceDistribution.create_path("req.req_set.IsSDist", "7.0.0", "9.0.3") +SourceDistribution.create_path("operations.prepare.IsSDist", "10.0.0", "19.1.1") +SourceDistribution.create_path( + "distributions.source.SourceDistribution", "19.1.2", "19.2.3" +) +SourceDistribution.create_path( + "distributions.source.legacy.SourceDistribution", "19.3.0", "19.9" +) +SourceDistribution.create_path("distributions.sdist.SourceDistribution", "20.0", "9999") + +WheelDistribution = ShimmedPathCollection("WheelDistribution", ImportTypes.CLASS) +WheelDistribution.create_path("distributions.wheel.WheelDistribution", "19.1.2", "9999") + +Downloader = ShimmedPathCollection("Downloader", ImportTypes.CLASS) +Downloader.create_path("network.download.Downloader", "20.0.0", "9999") + +PyPI = ShimmedPathCollection("PyPI", ImportTypes.ATTRIBUTE) +PyPI.create_path("models.index.PyPI", "7.0.0", "9999") + +stdlib_pkgs = ShimmedPathCollection("stdlib_pkgs", ImportTypes.ATTRIBUTE) +stdlib_pkgs.create_path("utils.compat.stdlib_pkgs", "18.1", "9999") +stdlib_pkgs.create_path("compat.stdlib_pkgs", "7", "18.0") + +DEV_PKGS = ShimmedPathCollection("DEV_PKGS", ImportTypes.ATTRIBUTE) +DEV_PKGS.create_path("commands.freeze.DEV_PKGS", "9.0.0", "9999") +DEV_PKGS.set_default({"setuptools", "pip", "distribute", "wheel"}) + + +wheel_cache = ShimmedPathCollection("wheel_cache", ImportTypes.FUNCTION) +wheel_cache.set_default( + functools.partial( + compat.wheel_cache, + wheel_cache_provider=WheelCache, + tempdir_manager_provider=global_tempdir_manager, + format_control_provider=FormatControl, + ) +) + + +get_package_finder = ShimmedPathCollection("get_package_finder", ImportTypes.FUNCTION) +get_package_finder.set_default( + functools.partial( + compat.get_package_finder, + install_cmd_provider=InstallCommand, + target_python_builder=TargetPython.shim(), + ) +) + + +make_preparer = ShimmedPathCollection("make_preparer", ImportTypes.FUNCTION) +make_preparer.set_default( + functools.partial( + compat.make_preparer, + install_cmd_provider=InstallCommand, + preparer_fn=RequirementPreparer, + downloader_provider=Downloader, + req_tracker_fn=get_requirement_tracker, + finder_provider=get_package_finder, + ) +) + + +get_resolver = ShimmedPathCollection("get_resolver", ImportTypes.FUNCTION) +get_resolver.set_default( + functools.partial( + compat.get_resolver, + install_cmd_provider=InstallCommand, + resolver_fn=Resolver, + install_req_provider=install_req_from_req_string, + wheel_cache_provider=wheel_cache, + format_control_provider=FormatControl, + ) +) + + +get_requirement_set = ShimmedPathCollection("get_requirement_set", ImportTypes.FUNCTION) +get_requirement_set.set_default( + functools.partial( + compat.get_requirement_set, + install_cmd_provider=InstallCommand, + req_set_provider=RequirementSet, + wheel_cache_provider=wheel_cache, + ) +) + + +resolve = ShimmedPathCollection("resolve", ImportTypes.FUNCTION) +resolve.set_default( + functools.partial( + compat.resolve, + install_cmd_provider=InstallCommand, + reqset_provider=get_requirement_set, + finder_provider=get_package_finder, + resolver_provider=get_resolver, + wheel_cache_provider=wheel_cache, + format_control_provider=FormatControl, + make_preparer_provider=make_preparer, + req_tracker_provider=get_requirement_tracker, + tempdir_manager_provider=global_tempdir_manager, + ) +) + + +build_wheel = ShimmedPathCollection("build_wheel", ImportTypes.FUNCTION) +build_wheel.set_default( + functools.partial( + compat.build_wheel, + install_command_provider=InstallCommand, + wheel_cache_provider=wheel_cache, + wheel_builder_provider=WheelBuilder, + build_one_provider=build_one, + build_one_inside_env_provider=build_one_inside_env, + build_many_provider=build, + preparer_provider=make_preparer, + format_control_provider=FormatControl, + reqset_provider=get_requirement_set, + ) +) diff --git a/pipenv/vendor/pip_shims/shims.py b/pipenv/vendor/pip_shims/shims.py index 4cad473a..48f6c432 100644 --- a/pipenv/vendor/pip_shims/shims.py +++ b/pipenv/vendor/pip_shims/shims.py @@ -1,23 +1,28 @@ # -*- coding=utf-8 -*- -import importlib -import os +""" +Main module with magic self-replacement mechanisms to handle import speedups. +""" +from __future__ import absolute_import + import sys -from collections import namedtuple -from contextlib import contextmanager +import types -import six +from pipenv.vendor.packaging.version import parse as parse_version -# format: off -six.add_move(six.MovedAttribute("Callable", "collections", "collections.abc")) # noqa -from six.moves import Callable # type: ignore # noqa # isort:skip - -# format: on +from .models import ( + ShimmedPathCollection, + get_package_finder, + import_pip, + lookup_current_pip_version, +) -class _shims(object): - CURRENT_PIP_VERSION = "19.1.1" - BASE_IMPORT_PATH = os.environ.get("PIP_SHIMS_BASE_MODULE", "pip") - path_info = namedtuple("PathInfo", "path start_version end_version") +class _shims(types.ModuleType): + CURRENT_PIP_VERSION = str(lookup_current_pip_version()) + + @classmethod + def parse_version(cls, version): + return parse_version(version) def __dir__(self): result = list(self._locations.keys()) + list(self.__dict__.keys()) @@ -44,364 +49,18 @@ class _shims(object): return list(self._locations.keys()) def __init__(self): - # from .utils import _parse, get_package, STRING_TYPES - from . import utils - - self.utils = utils - self._parse = utils._parse - self.get_package = utils.get_package - self.STRING_TYPES = utils.STRING_TYPES - self._modules = { - "pip": importlib.import_module(self.BASE_IMPORT_PATH), - "pip_shims.utils": utils, - } - self.pip_version = getattr(self._modules["pip"], "__version__") - version_types = ["post", "pre", "dev", "rc"] - if any(post in self.pip_version.rsplit(".")[-1] for post in version_types): - self.pip_version, _, _ = self.pip_version.rpartition(".") - self.parsed_pip_version = self._parse(self.pip_version) - self._contextmanagers = ("RequirementTracker",) - self._moves = { - "InstallRequirement": { - "from_editable": "install_req_from_editable", - "from_line": "install_req_from_line", - } - } - self._locations = { - "parse_version": ("index.parse_version", "7", "9999"), - "_strip_extras": ( - ("req.req_install._strip_extras", "7", "18.0"), - ("req.constructors._strip_extras", "18.1", "9999"), - ), - "cmdoptions": ( - ("cli.cmdoptions", "18.1", "9999"), - ("cmdoptions", "7.0.0", "18.0"), - ), - "Command": ( - ("cli.base_command.Command", "18.1", "9999"), - ("basecommand.Command", "7.0.0", "18.0"), - ), - "ConfigOptionParser": ( - ("cli.parser.ConfigOptionParser", "18.1", "9999"), - ("baseparser.ConfigOptionParser", "7.0.0", "18.0"), - ), - "DistributionNotFound": ("exceptions.DistributionNotFound", "7.0.0", "9999"), - "FAVORITE_HASH": ("utils.hashes.FAVORITE_HASH", "7.0.0", "9999"), - "FormatControl": ( - ("models.format_control.FormatControl", "18.1", "9999"), - ("index.FormatControl", "7.0.0", "18.0"), - ), - "FrozenRequirement": ( - ("FrozenRequirement", "7.0.0", "9.0.3"), - ("operations.freeze.FrozenRequirement", "10.0.0", "9999"), - ), - "get_installed_distributions": ( - ("utils.misc.get_installed_distributions", "10", "9999"), - ("utils.get_installed_distributions", "7", "9.0.3"), - ), - "index_group": ( - ("cli.cmdoptions.index_group", "18.1", "9999"), - ("cmdoptions.index_group", "7.0.0", "18.0"), - ), - "InstallRequirement": ("req.req_install.InstallRequirement", "7.0.0", "9999"), - "InstallationError": ("exceptions.InstallationError", "7.0.0", "9999"), - "UninstallationError": ("exceptions.UninstallationError", "7.0.0", "9999"), - "DistributionNotFound": ("exceptions.DistributionNotFound", "7.0.0", "9999"), - "RequirementsFileParseError": ( - "exceptions.RequirementsFileParseError", - "7.0.0", - "9999", - ), - "BestVersionAlreadyInstalled": ( - "exceptions.BestVersionAlreadyInstalled", - "7.0.0", - "9999", - ), - "BadCommand": ("exceptions.BadCommand", "7.0.0", "9999"), - "CommandError": ("exceptions.CommandError", "7.0.0", "9999"), - "PreviousBuildDirError": ( - "exceptions.PreviousBuildDirError", - "7.0.0", - "9999", - ), - "install_req_from_editable": ( - ("req.constructors.install_req_from_editable", "18.1", "9999"), - ("req.req_install.InstallRequirement.from_editable", "7.0.0", "18.0"), - ), - "install_req_from_line": ( - ("req.constructors.install_req_from_line", "18.1", "9999"), - ("req.req_install.InstallRequirement.from_line", "7.0.0", "18.0"), - ), - "is_archive_file": ("download.is_archive_file", "7.0.0", "9999"), - "is_file_url": ("download.is_file_url", "7.0.0", "9999"), - "unpack_url": ("download.unpack_url", "7.0.0", "9999"), - "is_installable_dir": ( - ("utils.misc.is_installable_dir", "10.0.0", "9999"), - ("utils.is_installable_dir", "7.0.0", "9.0.3"), - ), - "Link": ("index.Link", "7.0.0", "9999"), - "make_abstract_dist": ( - ( - "distributions.make_distribution_for_install_requirement", - "19.1.2", - "9999", - ), - ("operations.prepare.make_abstract_dist", "10.0.0", "19.1.1"), - ("req.req_set.make_abstract_dist", "7.0.0", "9.0.3"), - ), - "make_distribution_for_install_requirement": ( - "distributions.make_distribution_for_install_requirement", - "19.1.2", - "9999", - ), - "make_option_group": ( - ("cli.cmdoptions.make_option_group", "18.1", "9999"), - ("cmdoptions.make_option_group", "7.0.0", "18.0"), - ), - "PackageFinder": ("index.PackageFinder", "7.0.0", "9999"), - "CandidateEvaluator": ("index.CandidateEvaluator", "19.1", "9999"), - "parse_requirements": ("req.req_file.parse_requirements", "7.0.0", "9999"), - "path_to_url": ("download.path_to_url", "7.0.0", "9999"), - "PipError": ("exceptions.PipError", "7.0.0", "9999"), - "RequirementPreparer": ( - "operations.prepare.RequirementPreparer", - "7", - "9999", - ), - "RequirementSet": ("req.req_set.RequirementSet", "7.0.0", "9999"), - "RequirementTracker": ("req.req_tracker.RequirementTracker", "7.0.0", "9999"), - "Resolver": ( - ("resolve.Resolver", "7.0.0", "19.1.1"), - ("legacy_resolve.Resolver", "19.1.2", "9999"), - ), - "SafeFileCache": ("download.SafeFileCache", "7.0.0", "9999"), - "UninstallPathSet": ("req.req_uninstall.UninstallPathSet", "7.0.0", "9999"), - "url_to_path": ("download.url_to_path", "7.0.0", "9999"), - "USER_CACHE_DIR": ("locations.USER_CACHE_DIR", "7.0.0", "9999"), - "VcsSupport": ( - ("vcs.VcsSupport", "7.0.0", "19.1.1"), - ("vcs.versioncontrol.VcsSupport", "19.2", "9999"), - ), - "Wheel": ("wheel.Wheel", "7.0.0", "9999"), - "WheelCache": ( - ("cache.WheelCache", "10.0.0", "9999"), - ("wheel.WheelCache", "7", "9.0.3"), - ), - "WheelBuilder": ("wheel.WheelBuilder", "7.0.0", "9999"), - "AbstractDistribution": ( - "distributions.base.AbstractDistribution", - "19.1.2", - "9999", - ), - "InstalledDistribution": ( - "distributions.installed.InstalledDistribution", - "19.1.2", - "9999", - ), - "SourceDistribution": ( - ("req.req_set.IsSDist", "7.0.0", "9.0.3"), - ("operations.prepare.IsSDist", "10.0.0", "19.1.1"), - ("distributions.source.SourceDistribution", "19.1.2", "9999"), - ), - "WheelDistribution": ( - "distributions.wheel.WheelDistribution", - "19.1.2", - "9999", - ), - "PyPI": ("models.index.PyPI", "7.0.0", "9999"), - "stdlib_pkgs": ( - ("utils.compat.stdlib_pkgs", "18.1", "9999"), - ("compat.stdlib_pkgs", "7", "18.0"), - ), - "DEV_PKGS": ( - ("commands.freeze.DEV_PKGS", "9.0.0", "9999"), - ({"setuptools", "pip", "distribute", "wheel"}, "7.0.0", "8.1.2"), - ), - } - - def _ensure_methods(self, cls, classname, *methods): - method_names = [m[0] for m in methods] - if all(getattr(cls, m, None) for m in method_names): - return cls - new_functions = {} - - class BaseFunc(Callable): - def __init__(self, func_base, name, *args, **kwargs): - self.func = func_base - self.__name__ = self.__qualname__ = name - - def __call__(self, cls, *args, **kwargs): - return self.func(*args, **kwargs) - - for method_name, fn in methods: - new_functions[method_name] = classmethod(BaseFunc(fn, method_name)) - if six.PY2: - classname = classname.encode(sys.getdefaultencoding()) - type_ = type(classname, (cls,), new_functions) - return type_ - - def _get_module_paths(self, module, base_path=None): - if not base_path: - base_path = self.BASE_IMPORT_PATH - module = self._locations[module] - if not isinstance(next(iter(module)), (tuple, list)): - module_paths = self.get_pathinfo(module) - else: - module_paths = [self.get_pathinfo(pth) for pth in module] - return self.sort_paths(module_paths, base_path) - - def _get_remapped_methods(self, moved_package): - original_base, original_target = moved_package - original_import = self._import(self._locations[original_target]) - old_to_new = {} - new_to_old = {} - for method_name, new_method_name in self._moves.get(original_target, {}).items(): - module_paths = self._get_module_paths(new_method_name) - target = next( - iter( - sorted(set([tgt for mod, tgt in map(self.get_package, module_paths)])) - ), - None, - ) - old_to_new[method_name] = { - "target": target, - "name": new_method_name, - "location": self._locations[new_method_name], - "module": self._import(self._locations[new_method_name]), - } - new_to_old[new_method_name] = { - "target": original_target, - "name": method_name, - "location": self._locations[original_target], - "module": original_import, - } - return (old_to_new, new_to_old) - - def _import_moved_module(self, moved_package): - old_to_new, new_to_old = self._get_remapped_methods(moved_package) - imported = None - method_map = [] - new_target = None - for old_method, remapped in old_to_new.items(): - new_name = remapped["name"] - new_target = new_to_old[new_name]["target"] - if not imported: - imported = self._modules[new_target] = new_to_old[new_name]["module"] - method_map.append((old_method, remapped["module"])) - if getattr(imported, "__class__", "") == type: - imported = self._ensure_methods(imported, new_target, *method_map) - self._modules[new_target] = imported - if imported: - return imported - return - - def _check_moved_methods(self, search_pth, moves): - module_paths = [ - self.get_package(pth) for pth in self._get_module_paths(search_pth) - ] - moved_methods = [ - (base, target_cls) for base, target_cls in module_paths if target_cls in moves - ] - return next(iter(moved_methods), None) + self.pip = import_pip() + self._locations = ShimmedPathCollection.get_registry() + self._locations["get_package_finder"] = get_package_finder + self.pip_version = str(lookup_current_pip_version()) + self.parsed_pip_version = lookup_current_pip_version() def __getattr__(self, *args, **kwargs): locations = super(_shims, self).__getattribute__("_locations") - contextmanagers = super(_shims, self).__getattribute__("_contextmanagers") - moves = super(_shims, self).__getattribute__("_moves") if args[0] in locations: - moved_package = self._check_moved_methods(args[0], moves) - if moved_package: - imported = self._import_moved_module(moved_package) - if imported: - return imported - else: - imported = self._import(locations[args[0]]) - if not imported and args[0] in contextmanagers: - return self.nullcontext - return imported + return locations[args[0]].shim() return super(_shims, self).__getattribute__(*args, **kwargs) - def is_valid(self, path_info_tuple): - if ( - path_info_tuple.start_version <= self.parsed_pip_version - and path_info_tuple.end_version >= self.parsed_pip_version - ): - return 1 - return 0 - - def sort_paths(self, module_paths, base_path): - if not isinstance(module_paths, list): - module_paths = [module_paths] - prefix_order = [pth.format(base_path) for pth in ["{0}._internal", "{0}"]] - # Pip 10 introduced the internal api division - if self._parse(self.pip_version) < self._parse("10.0.0"): - prefix_order = reversed(prefix_order) - paths = sorted(module_paths, key=self.is_valid, reverse=True) - search_order = [ - "{0}.{1}".format(p, pth.path) - for p in prefix_order - for pth in paths - if pth is not None - ] - return search_order - - def import_module(self, module): - if module in self._modules: - return self._modules[module] - if not isinstance(module, six.string_types): - return module - try: - imported = importlib.import_module(module) - except ImportError: - imported = None - else: - self._modules[module] = imported - return imported - - def none_or_ctxmanager(self, pkg_name): - if pkg_name in self._contextmanagers: - return self.nullcontext - return None - - def get_package_from_modules(self, modules): - modules = [ - (package_name, self.import_module(m)) - for m, package_name in map(self.get_package, modules) - ] - imports = [ - getattr(m, pkg, self.none_or_ctxmanager(pkg)) - for pkg, m in modules - if m is not None - ] - return next(iter(imports), None) - - def _import(self, module_paths, base_path=None): - if not base_path: - base_path = self.BASE_IMPORT_PATH - if not isinstance(next(iter(module_paths)), (tuple, list)): - module_paths = self.get_pathinfo(module_paths) - else: - module_paths = [self.get_pathinfo(pth) for pth in module_paths] - search_order = self.sort_paths(module_paths, base_path) - return self.get_package_from_modules(search_order) - - def do_import(self, *args, **kwargs): - return self._import(*args, **kwargs) - - @contextmanager - def nullcontext(self, *args, **kwargs): - try: - yield - finally: - pass - - def get_pathinfo(self, module_path): - assert isinstance(module_path, (list, tuple)) - module_path, start_version, end_version = module_path - return self.path_info( - module_path, self._parse(start_version), self._parse(end_version) - ) - old_module = sys.modules[__name__] if __name__ in sys.modules else None module = sys.modules[__name__] = _shims() diff --git a/pipenv/vendor/pip_shims/utils.py b/pipenv/vendor/pip_shims/utils.py index d6101e21..d3b5e708 100644 --- a/pipenv/vendor/pip_shims/utils.py +++ b/pipenv/vendor/pip_shims/utils.py @@ -1,13 +1,90 @@ # -*- coding=utf-8 -*- -from functools import wraps +""" +Shared utility functions which are not specific to any particular module. +""" +from __future__ import absolute_import + +import contextlib +import copy +import inspect import sys +from collections.abc import Callable +from functools import wraps + +from pipenv.vendor.packaging import version as pkg_version + +from .environment import MYPY_RUNNING + +if MYPY_RUNNING: + from types import ModuleType + from typing import ( + Any, + Dict, + Iterator, + List, + Optional, + Sequence, + Tuple, + Type, + TypeVar, + Union, + ) + + TShimmedPath = TypeVar("TShimmedPath") + TShimmedPathCollection = TypeVar("TShimmedPathCollection") + TShim = Union[TShimmedPath, TShimmedPathCollection] + TShimmedFunc = Union[TShimmedPath, TShimmedPathCollection, Callable, Type] + STRING_TYPES = (str,) if sys.version_info < (3, 0): - STRING_TYPES = STRING_TYPES + (unicode,) + STRING_TYPES = STRING_TYPES + (unicode,) # noqa:F821 + + +class BaseMethod(Callable): + def __init__(self, func_base, name, *args, **kwargs): + # type: (Callable, str, Any, Any) -> None + self.func = func_base + self.__name__ = self.__qualname__ = name + + def __call__(self, *args, **kwargs): + # type: (Any, Any) -> Any + return self.func(*args, **kwargs) + + +class BaseClassMethod(Callable): + def __init__(self, func_base, name, *args, **kwargs): + # type: (Callable, str, Any, Any) -> None + self.func = func_base + self.__name__ = self.__qualname__ = name + + def __call__(self, cls, *args, **kwargs): + # type: (Type, Any, Any) -> Any + return self.func(*args, **kwargs) + + +def make_method(fn): + # type: (Callable) -> Callable + @wraps(fn) + def method_creator(*args, **kwargs): + # type: (Any, Any) -> Callable + return BaseMethod(fn, *args, **kwargs) + + return method_creator + + +def make_classmethod(fn): + # type: (Callable) -> Callable + @wraps(fn) + def classmethod_creator(*args, **kwargs): + # type: (Any, Any) -> Callable + return classmethod(BaseClassMethod(fn, *args, **kwargs)) + + return classmethod_creator def memoize(obj): + # type: (Any) -> Callable cache = obj.cache = {} @wraps(obj) @@ -16,20 +93,361 @@ def memoize(obj): if key not in cache: cache[key] = obj(*args, **kwargs) return cache[key] + return memoizer @memoize def _parse(version): + # type: (str) -> Tuple[int, ...] if isinstance(version, STRING_TYPES): - return tuple((int(i) for i in version.split("."))) + return tuple(int(i) for i in version.split(".")) return version -def get_package(module, subimport=None): +@memoize +def parse_version(version): + # type: (str) -> pkg_version._BaseVersion + if not isinstance(version, STRING_TYPES): + raise TypeError("Can only derive versions from string, got {!r}".format(version)) + return pkg_version.parse(version) + + +@memoize +def split_package(module, subimport=None): + # type: (str, Optional[str]) -> Tuple[str, str] + """ + Used to determine what target to import. + + Either splits off the final segment or uses the provided sub-import to return a + 2-tuple of the import path and the target module or sub-path. + + :param str module: A package to import from + :param Optional[str] subimport: A class, function, or subpackage to import + :return: A 2-tuple of the corresponding import package and sub-import path + :rtype: Tuple[str, str] + + :Example: + + >>> from pip_shims.utils import split_package + >>> split_package("pip._internal.req.req_install", subimport="InstallRequirement") + ("pip._internal.req.req_install", "InstallRequirement") + >>> split_package("pip._internal.cli.base_command") + ("pip._internal.cli", "base_command") + """ package = None if subimport: package = subimport else: module, _, package = module.rpartition(".") return module, package + + +def get_method_args(target_method): + # type: (Callable) -> Tuple[Callable, Optional[inspect.Arguments]] + """ + Returns the arguments for a callable. + + :param Callable target_method: A callable to retrieve arguments for + :return: A 2-tuple of the original callable and its resulting arguments + :rtype: Tuple[Callable, Optional[inspect.Arguments]] + """ + inspected_args = None + try: + inspected_args = inspect.getargs(target_method.__code__) + except AttributeError: + target_func = getattr(target_method, "__func__", None) + if target_func is not None: + inspected_args = inspect.getargs(target_func.__code__) + else: + target_func = target_method + return target_func, inspected_args + + +def set_default_kwargs(basecls, method, *args, **default_kwargs): + # type: (Union[Type, ModuleType], Callable, Any, Any) -> Union[Type, ModuleType] # noqa + target_method = getattr(basecls, method, None) + if target_method is None: + return basecls + target_func, inspected_args = get_method_args(target_method) + if inspected_args is not None: + pos_args = inspected_args.args + else: + pos_args = [] + # Spit back the base class if we can't find matching arguments + # to put defaults in place of + if not any(arg in pos_args for arg in list(default_kwargs.keys())): + return basecls + prepended_defaults = tuple() # type: Tuple[Any, ...] + # iterate from the function's argument order to make sure we fill this + # out in the correct order + for arg in args: + prepended_defaults += (arg,) + for arg in pos_args: + if arg in default_kwargs: + prepended_defaults = prepended_defaults + (default_kwargs[arg],) + if not prepended_defaults: + return basecls + new_defaults = prepended_defaults + target_method.__defaults__ + target_method.__defaults__ = new_defaults + setattr(basecls, method, target_method) + return basecls + + +def ensure_function(parent, funcname, func): + # type: (Union[ModuleType, Type, Callable, Any], str, Callable) -> Callable + """Given a module, a function name, and a function object, attaches the given + function to the module and ensures it is named properly according to the provided + argument + + :param Any parent: The parent to attack the function to + :param str funcname: The name to give the function + :param Callable func: The function to rename and attach to **parent** + :returns: The function with its name, qualname, etc set to mirror **parent** + :rtype: Callable + """ + qualname = funcname + if parent is None: + parent = __module__ # type: ignore # noqa:F821 + parent_is_module = inspect.ismodule(parent) + parent_is_class = inspect.isclass(parent) + module = None + if parent_is_module: + module = parent.__name__ + elif parent_is_class: + qualname = "{}.{}".format(parent.__name__, qualname) + module = getattr(parent, "__module__", None) + else: + module = getattr(parent, "__module__", None) + try: + func.__name__ = funcname + except AttributeError: + if getattr(func, "__func__", None) is not None: + func = func.__func__ + func.__name__ = funcname + func.__qualname__ = qualname + + func.__module__ = module + return func + + +def add_mixin_to_class(basecls, mixins): + # type: (Type, List[Type]) -> Type + """ + Given a class, adds the provided mixin classes as base classes and gives a new class + + :param Type basecls: An initial class to generate a new class from + :param List[Type] mixins: A list of mixins to add as base classes + :return: A new class with the provided mixins as base classes + :rtype: Type[basecls, *mixins] + """ + if not any(mixins): + return basecls + base_dict = basecls.__dict__.copy() + class_tuple = (basecls,) # type: Tuple[Type, ...] + for mixin in mixins: + if not mixin: + continue + mixin_dict = mixin.__dict__.copy() + base_dict.update(mixin_dict) + class_tuple = class_tuple + (mixin,) + base_dict.update(basecls.__dict__) + return type(basecls.__name__, class_tuple, base_dict) + + +def fallback_is_file_url(link): + # type: (Any) -> bool + return link.url.lower().startswith("file:") + + +def fallback_is_artifact(self): + # type: (Any) -> bool + return not getattr(self, "is_vcs", False) + + +def fallback_is_vcs(self): + # type: (Any) -> bool + return not getattr(self, "is_artifact", True) + + +def resolve_possible_shim(target): + # type: (TShimmedFunc) -> Optional[Union[Type, Callable]] + if target is None: + return target + if getattr(target, "shim", None): + return target.shim() + return target + + +@contextlib.contextmanager +def nullcontext(*args, **kwargs): + # type: (Any, Any) -> Iterator + try: + yield + finally: + pass + + +def has_property(target, name): + # type: (Any, str) -> bool + if getattr(target, name, None) is not None: + return True + return False + + +def apply_alias(imported, target, *aliases): + # type: (Union[ModuleType, Type, None], Any, Any) -> Any + """ + Given a target with attributes, point non-existant aliases at the first existing one + + :param Union[ModuleType, Type] imported: A Module or Class base + :param Any target: The target which is a member of **imported** and will have aliases + :param str aliases: A list of aliases, the first found attribute will be the basis + for all non-existant names which will be created as pointers + :return: The original target + :rtype: Any + """ + base_value = None # type: Optional[Any] + applied_aliases = set() + unapplied_aliases = set() + for alias in aliases: + if has_property(target, alias): + base_value = getattr(target, alias) + applied_aliases.add(alias) + else: + unapplied_aliases.add(alias) + is_callable = inspect.ismethod(base_value) or inspect.isfunction(base_value) + for alias in unapplied_aliases: + if is_callable: + func_copy = copy.deepcopy(base_value) + alias_value = ensure_function(imported, alias, func_copy) + else: + alias_value = base_value + setattr(target, alias, alias_value) + return target + + +def suppress_setattr(obj, attr, value, filter_none=False): + """ + Set an attribute, suppressing any exceptions and skipping the attempt on failure. + + :param Any obj: Object to set the attribute on + :param str attr: The attribute name to set + :param Any value: The value to set the attribute to + :param bool filter_none: [description], defaults to False + :return: Nothing + :rtype: None + + :Example: + + >>> class MyClass(object): + ... def __init__(self, name): + ... self.name = name + ... self.parent = None + ... def __repr__(self): + ... return "<{0!r} instance (name={1!r}, parent={2!r})>".format( + ... self.__class__.__name__, self.name, self.parent + ... ) + ... def __str__(self): + ... return self.name + >>> me = MyClass("Dan") + >>> dad = MyClass("John") + >>> grandfather = MyClass("Joe") + >>> suppress_setattr(dad, "parent", grandfather) + >>> dad + <'MyClass' instance (name='John', parent=<'MyClass' instance (name='Joe', parent=None + )>)> + >>> suppress_setattr(me, "parent", dad) + >>> me + <'MyClass' instance (name='Dan', parent=<'MyClass' instance (name='John', parent=<'My + Class' instance (name='Joe', parent=None)>)>)> + >>> suppress_setattr(me, "grandparent", grandfather) + >>> me + <'MyClass' instance (name='Dan', parent=<'MyClass' instance (name='John', parent=<'My + Class' instance (name='Joe', parent=None)>)>)> + """ + if filter_none and value is None: + pass + try: + setattr(obj, attr, value) + except Exception: # noqa + pass + + +def get_allowed_args(fn_or_class): + # type: (Union[Callable, Type]) -> Tuple[List[str], Dict[str, Any]] + """ + Given a callable or a class, returns the arguments and default kwargs passed in. + + :param Union[Callable, Type] fn_or_class: A function, method or class to inspect. + :return: A 2-tuple with a list of arguments and a dictionary of keywords mapped to + default values. + :rtype: Tuple[List[str], Dict[str, Any]] + """ + try: + signature = inspect.signature(fn_or_class) + except AttributeError: + import pipenv.vendor.funcsigs as funcsigs + + signature = funcsigs.signature(fn_or_class) + args = [] + kwargs = {} + for arg, param in signature.parameters.items(): + if ( + param.kind in (param.POSITIONAL_OR_KEYWORD, param.POSITIONAL_ONLY) + ) and param.default is param.empty: + args.append(arg) + else: + kwargs[arg] = param.default if param.default is not param.empty else None + return args, kwargs + + +def call_function_with_correct_args(fn, **provided_kwargs): + # type: (Callable, Dict[str, Any]) -> Any + """ + Determines which arguments from **provided_kwargs** to call **fn** and calls it. + + Consumes a list of allowed arguments (e.g. from :func:`~inspect.getargs()`) and + uses it to determine which of the arguments in the provided kwargs should be passed + through to the given callable. + + :param Callable fn: A callable which has some dynamic arguments + :param List[str] allowed_args: A list of allowed arguments which can be passed to + the supplied function + :return: The result of calling the function + :rtype: Any + """ + # signature = inspect.signature(fn) + args = [] + kwargs = {} + func_args, func_kwargs = get_allowed_args(fn) + for arg in func_args: + args.append(provided_kwargs[arg]) + for arg in func_kwargs: + if not provided_kwargs.get(arg): + continue + kwargs[arg] = provided_kwargs[arg] + return fn(*args, **kwargs) + + +def filter_allowed_args(fn, **provided_kwargs): + # type: (Callable, Dict[str, Any]) -> Tuple[List[Any], Dict[str, Any]] + """ + Given a function and a kwarg mapping, return only those kwargs used in the function. + + :param Callable fn: A function to inspect + :param Dict[str, Any] kwargs: A mapping of kwargs to filter + :return: A new, filtered kwarg mapping + :rtype: Tuple[List[Any], Dict[str, Any]] + """ + args = [] + kwargs = {} + func_args, func_kwargs = get_allowed_args(fn) + for arg in func_args: + if arg in provided_kwargs: + args.append(provided_kwargs[arg]) + for arg in func_kwargs: + if arg not in provided_kwargs: + continue + kwargs[arg] = provided_kwargs[arg] + return args, kwargs diff --git a/pipenv/vendor/pipdeptree.py b/pipenv/vendor/pipdeptree.py index 899118cc..e939db37 100644 --- a/pipenv/vendor/pipdeptree.py +++ b/pipenv/vendor/pipdeptree.py @@ -1,60 +1,41 @@ from __future__ import print_function import os +import inspect import sys +import subprocess from itertools import chain -from collections import defaultdict +from collections import defaultdict, deque import argparse from operator import attrgetter import json from importlib import import_module +import tempfile try: from collections import OrderedDict except ImportError: from ordereddict import OrderedDict +try: + from collections.abc import Mapping +except ImportError: + from collections import Mapping + pardir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.append(pardir) -from pipenv.vendor.pip_shims import get_installed_distributions, FrozenRequirement +from pipenv.vendor.pip_shims.shims import get_installed_distributions, FrozenRequirement import pkg_resources # inline: # from graphviz import backend, Digraph -__version__ = '0.13.2' +__version__ = '2.0.0' flatten = chain.from_iterable -def build_dist_index(pkgs): - """Build an index pkgs by their key as a dict. - - :param list pkgs: list of pkg_resources.Distribution instances - :returns: index of the pkgs by the pkg key - :rtype: dict - - """ - return dict((p.key, DistPackage(p)) for p in pkgs) - - -def construct_tree(index): - """Construct tree representation of the pkgs from the index. - - The keys of the dict representing the tree will be objects of type - DistPackage and the values will be list of ReqPackage objects. - - :param dict index: dist index ie. index of pkgs by their keys - :returns: tree of pkgs and their dependencies - :rtype: dict - - """ - return dict((p, [ReqPackage(r, index.get(r.key)) - for r in p.requires()]) - for p in index.values()) - - def sorted_tree(tree): """Sorts the dict representation of the tree @@ -72,44 +53,6 @@ def sorted_tree(tree): key=lambda kv: kv[0].key)) -def find_tree_root(tree, key): - """Find a root in a tree by it's key - - :param dict tree: the pkg dependency tree obtained by calling - `construct_tree` function - :param str key: key of the root node to find - :returns: a root node if found else None - :rtype: mixed - - """ - result = [p for p in tree.keys() if p.key == key] - assert len(result) in [0, 1] - return None if len(result) == 0 else result[0] - - -def reverse_tree(tree): - """Reverse the dependency tree. - - ie. the keys of the resulting dict are objects of type - ReqPackage and the values are lists of DistPackage objects. - - :param dict tree: the pkg dependency tree obtained by calling - `construct_tree` function - :returns: reversed tree - :rtype: dict - - """ - rtree = defaultdict(list) - child_keys = set(c.key for c in flatten(tree.values())) - for k, vs in tree.items(): - for v in vs: - node = find_tree_root(rtree, v.key) or v - rtree[node].append(k.as_required_by(v)) - if k.key not in child_keys: - rtree[k.as_requirement()] = [] - return rtree - - def guess_version(pkg_key, default='?'): """Guess the version of a pkg when pip doesn't provide it @@ -124,7 +67,11 @@ def guess_version(pkg_key, default='?'): except ImportError: return default else: - return getattr(m, '__version__', default) + v = getattr(m, '__version__', default) + if inspect.ismodule(v): + return getattr(v, '__version__', default) + else: + return v def frozen_req_from_dist(dist): @@ -208,16 +155,21 @@ class DistPackage(Package): """Return a ReqPackage representation of this DistPackage""" return ReqPackage(self._obj.as_requirement(), dist=self) - def as_required_by(self, req): + def as_parent_of(self, req): """Return a DistPackage instance associated to a requirement - This association is necessary for displaying the tree in - reverse. + This association is necessary for reversing the PackageDAG. + + If `req` is None, and the `req` attribute of the current + instance is also None, then the same instance will be + returned. :param ReqPackage req: the requirement to associate with :returns: DistPackage instance """ + if req is None and self.req is None: + return self return self.__class__(self._obj, req) def as_dict(self): @@ -251,6 +203,10 @@ class ReqPackage(Package): return guess_version(self.key, self.UNKNOWN_VERSION) return self.dist.version + @property + def is_missing(self): + return self.installed_version == self.UNKNOWN_VERSION + def is_conflicting(self): """If installed version conflicts with required version""" # unknown installed version is also considered conflicting @@ -285,42 +241,258 @@ class ReqPackage(Package): 'required_version': self.version_spec} -def render_tree(tree, list_all=True, show_only=None, frozen=False, exclude=None): - """Convert tree to string representation +class PackageDAG(Mapping): + """Representation of Package dependencies as directed acyclic graph + using a dict (Mapping) as the underlying datastructure. + + The nodes and their relationships (edges) are internally + stored using a map as follows, + + {a: [b, c], + b: [d], + c: [d, e], + d: [e], + e: [], + f: [b], + g: [e, f]} + + Here, node `a` has 2 children nodes `b` and `c`. Consider edge + direction from `a` -> `b` and `a` -> `c` respectively. + + A node is expected to be an instance of a subclass of + `Package`. The keys are must be of class `DistPackage` and each + item in values must be of class `ReqPackage`. (See also + ReversedPackageDAG where the key and value types are + interchanged). + + """ + + @classmethod + def from_pkgs(cls, pkgs): + pkgs = [DistPackage(p) for p in pkgs] + idx = {p.key: p for p in pkgs} + m = {p: [ReqPackage(r, idx.get(r.key)) + for r in p.requires()] + for p in pkgs} + return cls(m) + + def __init__(self, m): + """Initialize the PackageDAG object + + :param dict m: dict of node objects (refer class docstring) + :returns: None + :rtype: NoneType + + """ + self._obj = m + self._index = {p.key: p for p in list(self._obj)} + + def get_node_as_parent(self, node_key): + """Get the node from the keys of the dict representing the DAG. + + This method is useful if the dict representing the DAG + contains different kind of objects in keys and values. Use + this method to lookup a node obj as a parent (from the keys of + the dict) given a node key. + + :param node_key: identifier corresponding to key attr of node obj + :returns: node obj (as present in the keys of the dict) + :rtype: Object + + """ + try: + return self._index[node_key] + except KeyError: + return None + + def get_children(self, node_key): + """Get child nodes for a node by it's key + + :param str node_key: key of the node to get children of + :returns: list of child nodes + :rtype: ReqPackage[] + + """ + node = self.get_node_as_parent(node_key) + return self._obj[node] if node else [] + + def filter(self, include, exclude): + """Filters nodes in a graph by given parameters + + If a node is included, then all it's children are also + included. + + :param set include: set of node keys to include (or None) + :param set exclude: set of node keys to exclude (or None) + :returns: filtered version of the graph + :rtype: PackageDAG + + """ + # If neither of the filters are specified, short circuit + if include is None and exclude is None: + return self + + # Note: In following comparisons, we use lower cased values so + # that user may specify `key` or `project_name`. As per the + # documentation, `key` is simply + # `project_name.lower()`. Refer: + # https://setuptools.readthedocs.io/en/latest/pkg_resources.html#distribution-objects + if include: + include = set([s.lower() for s in include]) + if exclude: + exclude = set([s.lower() for s in exclude]) + else: + exclude = set([]) + + # Check for mutual exclusion of show_only and exclude sets + # after normalizing the values to lowercase + if include and exclude: + assert not (include & exclude) + + # Traverse the graph in a depth first manner and filter the + # nodes according to `show_only` and `exclude` sets + stack = deque() + m = {} + seen = set([]) + for node in self._obj.keys(): + if node.key in exclude: + continue + if include is None or node.key in include: + stack.append(node) + while True: + if len(stack) > 0: + n = stack.pop() + cldn = [c for c in self._obj[n] + if c.key not in exclude] + m[n] = cldn + seen.add(n.key) + for c in cldn: + if c.key not in seen: + cld_node = self.get_node_as_parent(c.key) + if cld_node: + stack.append(cld_node) + else: + # It means there's no root node + # corresponding to the child node + # ie. a dependency is missing + continue + else: + break + + return self.__class__(m) + + def reverse(self): + """Reverse the DAG, or turn it upside-down + + In other words, the directions of edges of the nodes in the + DAG will be reversed. + + Note that this function purely works on the nodes in the + graph. This implies that to perform a combination of filtering + and reversing, the order in which `filter` and `reverse` + methods should be applied is important. For eg. if reverse is + called on a filtered graph, then only the filtered nodes and + it's children will be considered when reversing. On the other + hand, if filter is called on reversed DAG, then the definition + of "child" nodes is as per the reversed DAG. + + :returns: DAG in the reversed form + :rtype: ReversedPackageDAG + + """ + m = defaultdict(list) + child_keys = set(r.key for r in flatten(self._obj.values())) + for k, vs in self._obj.items(): + for v in vs: + # if v is already added to the dict, then ensure that + # we are using the same object. This check is required + # as we're using array mutation + try: + node = [p for p in m.keys() if p.key == v.key][0] + except IndexError: + node = v + m[node].append(k.as_parent_of(v)) + if k.key not in child_keys: + m[k.as_requirement()] = [] + return ReversedPackageDAG(dict(m)) + + def sort(self): + """Return sorted tree in which the underlying _obj dict is an + OrderedDict, sorted alphabetically by the keys + + :returns: Instance of same class with OrderedDict + + """ + return self.__class__(sorted_tree(self._obj)) + + # Methods required by the abstract base class Mapping + def __getitem__(self, *args): + return self._obj.get(*args) + + def __iter__(self): + return self._obj.__iter__() + + def __len__(self): + return len(self._obj) + + +class ReversedPackageDAG(PackageDAG): + """Representation of Package dependencies in the reverse + order. + + Similar to it's super class `PackageDAG`, the underlying + datastructure is a dict, but here the keys are expected to be of + type `ReqPackage` and each item in the values of type + `DistPackage`. + + Typically, this object will be obtained by calling + `PackageDAG.reverse`. + + """ + + def reverse(self): + """Reverse the already reversed DAG to get the PackageDAG again + + :returns: reverse of the reversed DAG + :rtype: PackageDAG + + """ + m = defaultdict(list) + child_keys = set(r.key for r in flatten(self._obj.values())) + for k, vs in self._obj.items(): + for v in vs: + try: + node = [p for p in m.keys() if p.key == v.key][0] + except IndexError: + node = v.as_parent_of(None) + m[node].append(k) + if k.key not in child_keys: + m[k.dist] = [] + return PackageDAG(dict(m)) + + +def render_text(tree, list_all=True, frozen=False): + """Print tree as text on console :param dict tree: the package tree :param bool list_all: whether to list all the pgks at the root level or only those that are the sub-dependencies - :param set show_only: set of select packages to be shown in the - output. This is optional arg, default: None. :param bool frozen: whether or not show the names of the pkgs in the output that's favourable to pip --freeze - :param set exclude: set of select packages to be excluded from the - output. This is optional arg, default: None. - :returns: string representation of the tree - :rtype: str + :returns: None """ - tree = sorted_tree(tree) - branch_keys = set(r.key for r in flatten(tree.values())) + tree = tree.sort() nodes = tree.keys() + branch_keys = set(r.key for r in flatten(tree.values())) use_bullets = not frozen - key_tree = dict((k.key, v) for k, v in tree.items()) - get_children = lambda n: key_tree.get(n.key, []) - - if show_only: - nodes = [p for p in nodes - if p.key in show_only or p.project_name in show_only] - elif not list_all: + if not list_all: nodes = [p for p in nodes if p.key not in branch_keys] def aux(node, parent=None, indent=0, chain=None): - if exclude and (node.key in exclude or node.project_name in exclude): - return [] - if chain is None: - chain = [node.project_name] + chain = chain or [] node_str = node.render(parent, frozen) if parent: prefix = ' '*indent + ('- ' if use_bullets else '') @@ -328,13 +500,13 @@ def render_tree(tree, list_all=True, show_only=None, frozen=False, exclude=None) result = [node_str] children = [aux(c, node, indent=indent+2, chain=chain+[c.project_name]) - for c in get_children(node) + for c in tree.get_children(node.key) if c.project_name not in chain] result += list(flatten(children)) return result lines = flatten([aux(p) for p in nodes]) - return '\n'.join(lines) + print('\n'.join(lines)) def render_json(tree, indent): @@ -372,11 +544,9 @@ def render_json_tree(tree, indent): :rtype: str """ - tree = sorted_tree(tree) + tree = tree.sort() branch_keys = set(r.key for r in flatten(tree.values())) nodes = [p for p in tree.keys() if p.key not in branch_keys] - key_tree = dict((k.key, v) for k, v in tree.items()) - get_children = lambda n: key_tree.get(n.key, []) def aux(node, parent=None, chain=None): if chain is None: @@ -390,7 +560,7 @@ def render_json_tree(tree, indent): d['dependencies'] = [ aux(c, parent=node, chain=chain+[c.project_name]) - for c in get_children(node) + for c in tree.get_children(node.key) if c.project_name not in chain ] @@ -399,7 +569,7 @@ def render_json_tree(tree, indent): return json.dumps([aux(p) for p in nodes], indent=indent) -def dump_graphviz(tree, output_format='dot'): +def dump_graphviz(tree, output_format='dot', is_reverse=False): """Output dependency graph as one of the supported GraphViz output formats. :param dict tree: dependency graph @@ -423,15 +593,30 @@ def dump_graphviz(tree, output_format='dot'): sys.exit(1) graph = Digraph(format=output_format) - for package, deps in tree.items(): - project_name = package.project_name - label = '{0}\n{1}'.format(project_name, package.version) - graph.node(project_name, label=label) - for dep in deps: - label = dep.version_spec - if not label: - label = 'any' - graph.edge(project_name, dep.project_name, label=label) + + if not is_reverse: + for pkg, deps in tree.items(): + pkg_label = '{0}\\n{1}'.format(pkg.project_name, pkg.version) + graph.node(pkg.key, label=pkg_label) + for dep in deps: + edge_label = dep.version_spec or 'any' + if dep.is_missing: + dep_label = '{0}\\n(missing)'.format(dep.project_name) + graph.node(dep.key, label=dep_label, style='dashed') + graph.edge(pkg.key, dep.key, style='dashed') + else: + graph.edge(pkg.key, dep.key, label=edge_label) + else: + for dep, parents in tree.items(): + dep_label = '{0}\\n{1}'.format(dep.project_name, + dep.installed_version) + graph.node(dep.key, label=dep_label) + for parent in parents: + # req reference of the dep associated with this + # particular parent package + req_ref = parent.req + edge_label = req_ref.version_spec or 'any' + graph.edge(dep.key, parent.key, label=edge_label) # Allow output of dot format, even if GraphViz isn't installed. if output_format == 'dot': @@ -476,25 +661,53 @@ def conflicting_deps(tree): return conflicting +def render_conflicts_text(conflicts): + if conflicts: + print('Warning!!! Possibly conflicting dependencies found:', + file=sys.stderr) + # Enforce alphabetical order when listing conflicts + pkgs = sorted(conflicts.keys(), key=attrgetter('key')) + for p in pkgs: + pkg = p.render_as_root(False) + print('* {}'.format(pkg), file=sys.stderr) + for req in conflicts[p]: + req_str = req.render_as_branch(False) + print(' - {}'.format(req_str), file=sys.stderr) + + def cyclic_deps(tree): """Return cyclic dependencies as list of tuples - :param list pkgs: pkg_resources.Distribution instances - :param dict pkg_index: mapping of pkgs with their respective keys + :param PackageDAG pkgs: package tree/dag :returns: list of tuples representing cyclic dependencies - :rtype: generator + :rtype: list """ - key_tree = dict((k.key, v) for k, v in tree.items()) - get_children = lambda n: key_tree.get(n.key, []) + index = {p.key: set([r.key for r in rs]) for p, rs in tree.items()} cyclic = [] for p, rs in tree.items(): - for req in rs: - if p.key in map(attrgetter('key'), get_children(req)): - cyclic.append((p, req, p)) + for r in rs: + if p.key in index.get(r.key, []): + p_as_dep_of_r = [x for x + in tree.get(tree.get_node_as_parent(r.key)) + if x.key == p.key][0] + cyclic.append((p, r, p_as_dep_of_r)) return cyclic +def render_cycles_text(cycles): + if cycles: + print('Warning!! Cyclic dependencies found:', file=sys.stderr) + # List in alphabetical order of the dependency that's cycling + # (2nd item in the tuple) + cycles = sorted(cycles, key=lambda xs: xs[1].key) + for a, b, c in cycles: + print('* {0} => {1} => {2}'.format(a.project_name, + b.project_name, + c.project_name), + file=sys.stderr) + + def get_parser(): parser = argparse.ArgumentParser(description=( 'Dependency tree of the installed python packages' @@ -503,6 +716,9 @@ def get_parser(): version='{0}'.format(__version__)) parser.add_argument('-f', '--freeze', action='store_true', help='Print names so as to write freeze files') + parser.add_argument('--python', default=sys.executable, + help='Python to use to look for packages in it (default: where' + ' installed)') parser.add_argument('-a', '--all', action='store_true', help='list all deps at top level') parser.add_argument('-l', '--local-only', @@ -567,66 +783,92 @@ def _get_args(): return parser.parse_args() +def handle_non_host_target(args): + of_python = os.path.abspath(args.python) + # if target is not current python re-invoke it under the actual host + if of_python != os.path.abspath(sys.executable): + # there's no way to guarantee that graphviz is available, so refuse + if args.output_format: + print("graphviz functionality is not supported when querying" + " non-host python", file=sys.stderr) + raise SystemExit(1) + argv = sys.argv[1:] # remove current python executable + for py_at, value in enumerate(argv): + if value == "--python": + del argv[py_at] + del argv[py_at] + elif value.startswith("--python"): + del argv[py_at] + # feed the file as argument, instead of file + # to avoid adding the file path to sys.path, that can affect result + file_path = inspect.getsourcefile(sys.modules[__name__]) + with open(file_path, 'rt') as file_handler: + content = file_handler.read() + cmd = [of_python, "-c", content] + cmd.extend(argv) + # invoke from an empty folder to avoid cwd altering sys.path + cwd = tempfile.mkdtemp() + try: + return subprocess.call(cmd, cwd=cwd) + finally: + os.removedirs(cwd) + return None + + def main(): args = _get_args() + result = handle_non_host_target(args) + if result is not None: + return result pkgs = get_installed_distributions(local_only=args.local_only, user_only=args.user_only) - dist_index = build_dist_index(pkgs) - tree = construct_tree(dist_index) + tree = PackageDAG.from_pkgs(pkgs) - if args.json: - print(render_json(tree, indent=4)) - return 0 - elif args.json_tree: - print(render_json_tree(tree, indent=4)) - return 0 - elif args.output_format: - output = dump_graphviz(tree, output_format=args.output_format) - print_graphviz(output) - return 0 + is_text_output = not any([args.json, args.json_tree, args.output_format]) return_code = 0 - # show warnings about possibly conflicting deps if found and - # warnings are enabled - if args.warn != 'silence': - conflicting = conflicting_deps(tree) - if conflicting: - print('Warning!!! Possibly conflicting dependencies found:', - file=sys.stderr) - for p, reqs in conflicting.items(): - pkg = p.render_as_root(False) - print('* {}'.format(pkg), file=sys.stderr) - for req in reqs: - req_str = req.render_as_branch(False) - print(' - {}'.format(req_str), file=sys.stderr) + # Before any reversing or filtering, show warnings to console + # about possibly conflicting or cyclic deps if found and warnings + # are enabled (ie. only if output is to be printed to console) + if is_text_output and args.warn != 'silence': + conflicts = conflicting_deps(tree) + if conflicts: + render_conflicts_text(conflicts) print('-'*72, file=sys.stderr) - cyclic = cyclic_deps(tree) - if cyclic: - print('Warning!! Cyclic dependencies found:', file=sys.stderr) - for a, b, c in cyclic: - print('* {0} => {1} => {2}'.format(a.project_name, - b.project_name, - c.project_name), - file=sys.stderr) + cycles = cyclic_deps(tree) + if cycles: + render_cycles_text(cycles) print('-'*72, file=sys.stderr) - if args.warn == 'fail' and (conflicting or cyclic): + if args.warn == 'fail' and (conflicts or cycles): return_code = 1 + # Reverse the tree (if applicable) before filtering, thus ensuring + # that the filter will be applied on ReverseTree + if args.reverse: + tree = tree.reverse() + show_only = set(args.packages.split(',')) if args.packages else None exclude = set(args.exclude.split(',')) if args.exclude else None - if show_only and exclude and (show_only & exclude): - print('Conflicting packages found in --packages and --exclude lists.', file=sys.stderr) - sys.exit(1) + if show_only is not None or exclude is not None: + tree = tree.filter(show_only, exclude) + + if args.json: + print(render_json(tree, indent=4)) + elif args.json_tree: + print(render_json_tree(tree, indent=4)) + elif args.output_format: + output = dump_graphviz(tree, + output_format=args.output_format, + is_reverse=args.reverse) + print_graphviz(output) + else: + render_text(tree, args.all, args.freeze) - tree = render_tree(tree if not args.reverse else reverse_tree(tree), - list_all=args.all, show_only=show_only, - frozen=args.freeze, exclude=exclude) - print(tree) return return_code diff --git a/pipenv/vendor/pipreqs/__init__.py b/pipenv/vendor/pipreqs/__init__.py index e0aa5d8b..a6ce8933 100644 --- a/pipenv/vendor/pipreqs/__init__.py +++ b/pipenv/vendor/pipreqs/__init__.py @@ -2,4 +2,4 @@ __author__ = 'Vadim Kravcenko' __email__ = 'vadim.kravcenko@gmail.com' -__version__ = '0.4.9' +__version__ = '0.4.10' diff --git a/pipenv/vendor/pipreqs/mapping b/pipenv/vendor/pipreqs/mapping index 46cca297..6f5a469d 100644 --- a/pipenv/vendor/pipreqs/mapping +++ b/pipenv/vendor/pipreqs/mapping @@ -9,7 +9,8 @@ BeautifulSoupTests:BeautifulSoup BioSQL:biopython BuildbotStatusShields:BuildbotEightStatusShields ComputedAttribute:ExtensionClass -Crypto:pycrypto +Crypto:pycryptodome +Cryptodome:pycryptodomex FSM:pexpect FiftyOneDegrees:51degrees_mobile_detector_v3_wrapper GeoBaseMain:GeoBasesDev @@ -263,7 +264,6 @@ armstrong:armstrong.hatband armstrong:armstrong.templates.standard armstrong:armstrong.utils.backends armstrong:armstrong.utils.celery -arrow:arrow_fatisar arstecnica:arstecnica.raccoon.autobahn arstecnica:arstecnica.sqlalchemy.async article-downloader:article_downloader @@ -536,6 +536,7 @@ cassandra:cassandra_driver cassandralauncher:CassandraLauncher cc42:42qucc cerberus:Cerberus +cfnlint:cfn-lint chameleon:Chameleon charmtools:charm_tools chef:PyChef @@ -582,7 +583,9 @@ dateutil:python_dateutil dawg:DAWG deb822:python_debian debian:python_debian +decouple:python-decouple demo:webunit +demosongs:PySynth deployer:juju_deployer depot:filedepot devtools:tg.devtools @@ -698,6 +701,7 @@ html:pies2overrides htmloutput:nosehtmloutput http:pies2overrides hvad:django_hvad +krbV:krbv i99fix:199Fix igraph:python_igraph imdb:IMDbPY @@ -771,6 +775,8 @@ mimeparse:python_mimeparse minitage:minitage.paste minitage:minitage.recipe.common missingdrawables:android_missingdrawables +mixfiles:PySynth +mkfreq:PySynth mkrst_themes:2lazy2rest mockredis:mockredispy modargs:python_modargs @@ -786,11 +792,13 @@ monthdelta:MonthDelta mopidy:Mopidy mopytools:MoPyTools mptt:django_mptt +mpv:python-mpv mrbob:mr.bob msgpack:msgpack_python mutations:aino_mutations mws:amazon_mws mysql:mysql_connector_repackaged +MySQL-python:MySQLdb native_tags:django_native_tags ndg:ndg_httpsclient nereid:trytond_nereid @@ -800,7 +808,7 @@ nester:abofly nester:bssm_pythonSig novaclient:python_novaclient oauth2_provider:alauda_django_oauth -oauth2client:google_api_python_client +oauth2client:oauth2client odf:odfpy ometa:Parsley openid:python_openid @@ -821,12 +829,14 @@ past:future paste:PasteScript path:forked_path path:path.py +patricia:patricia-trie paver:Paver peak:ProxyTypes picasso:anderson.picasso picklefield:django-picklefield pilot:BigJob pivotal:pivotal_py +play_wav:PySynth playhouse:peewee plivoxml:plivo plone:plone.alterego @@ -910,9 +920,9 @@ plone:plone.z3cform plonetheme:plonetheme.barceloneta png:pypng polymorphic:django_polymorphic -portalocker:ConcurrentLogHandler postmark:python_postmark powerprompt:bash_powerprompt +prefetch:django-prefetch printList:AndrewList progressbar:progressbar2 progressbar:progressbar33 @@ -947,6 +957,14 @@ pyrimaa:AEI pysideuic:PySide pysqlite2:adhocracy_pysqlite pysqlite2:pysqlite +pysynth_b:PySynth +pysynth_beeper:PySynth +pysynth_c:PySynth +pysynth_d:PySynth +pysynth_e:PySynth +pysynth_p:PySynth +pysynth_s:PySynth +pysynth_samp:PySynth pythongettext:python_gettext pythonjsonlogger:python_json_logger pyutilib:PyUtilib @@ -1004,6 +1022,7 @@ singleton:pysingleton sittercommon:cerebrod skbio:scikit_bio sklearn:scikit_learn +slack:slackclient slugify:unicode_slugify smarkets:smk_python_sdk snappy:ctypes_snappy diff --git a/pipenv/vendor/pipreqs/pipreqs.py b/pipenv/vendor/pipreqs/pipreqs.py index 791168a9..157dd76c 100644 --- a/pipenv/vendor/pipreqs/pipreqs.py +++ b/pipenv/vendor/pipreqs/pipreqs.py @@ -3,25 +3,38 @@ """pipreqs - Generate pip requirements.txt file based on imports Usage: - pipreqs [options] <path> + pipreqs [options] [<path>] + +Arguments: + <path> The path to the directory containing the application + files for which a requirements file should be + generated (defaults to the current working + directory). Options: - --use-local Use ONLY local package info instead of querying PyPI - --pypi-server <url> Use custom PyPi server - --proxy <url> Use Proxy, parameter will be passed to requests library. You can also just set the - environments parameter in your terminal: + --use-local Use ONLY local package info instead of querying PyPI. + --pypi-server <url> Use custom PyPi server. + --proxy <url> Use Proxy, parameter will be passed to requests + library. You can also just set the environments + parameter in your terminal: $ export HTTP_PROXY="http://10.10.1.10:3128" $ export HTTPS_PROXY="https://10.10.1.10:1080" - --debug Print debug information - --ignore <dirs>... Ignore extra directories, each separated by a comma + --debug Print debug information. + --ignore <dirs>... Ignore extra directories, each separated by a comma. + --no-follow-links Do not follow symbolic links in the project --encoding <charset> Use encoding parameter for file open --savepath <file> Save the list of requirements in the given file - --print Output the list of requirements in the standard output + --print Output the list of requirements in the standard + output. --force Overwrite existing requirements.txt - --diff <file> Compare modules in requirements.txt to project imports. - --clean <file> Clean up requirements.txt by removing modules that are not imported in project. + --diff <file> Compare modules in requirements.txt to project + imports. + --clean <file> Clean up requirements.txt by removing modules + that are not imported in project. + --no-pin Omit version of output packages. """ from __future__ import print_function, absolute_import +from contextlib import contextmanager import os import sys import re @@ -29,12 +42,12 @@ import logging import codecs import ast import traceback -from docopt import docopt -import requests -from yarg import json2package -from yarg.exceptions import HTTPError +from pipenv.vendor.docopt import docopt +import pipenv.vendor.requests as requests +from pipenv.vendor.yarg import json2package +from pipenv.vendor.yarg.exceptions import HTTPError -from pipreqs import __version__ +from pipenv.vendor.pipreqs import __version__ REGEXP = [ re.compile(r'^import (.+)$'), @@ -50,7 +63,39 @@ else: py2_exclude = ["concurrent", "concurrent.futures"] -def get_all_imports(path, encoding=None, extra_ignore_dirs=None): +@contextmanager +def _open(filename=None, mode='r'): + """Open a file or ``sys.stdout`` depending on the provided filename. + + Args: + filename (str): The path to the file that should be opened. If + ``None`` or ``'-'``, ``sys.stdout`` or ``sys.stdin`` is + returned depending on the desired mode. Defaults to ``None``. + mode (str): The mode that should be used to open the file. + + Yields: + A file handle. + + """ + if not filename or filename == '-': + if not mode or 'r' in mode: + file = sys.stdin + elif 'w' in mode: + file = sys.stdout + else: + raise ValueError('Invalid mode for file: {}'.format(mode)) + else: + file = open(filename, mode) + + try: + yield file + finally: + if file not in (sys.stdin, sys.stdout): + file.close() + + +def get_all_imports( + path, encoding=None, extra_ignore_dirs=None, follow_links=True): imports = set() raw_imports = set() candidates = [] @@ -63,7 +108,8 @@ def get_all_imports(path, encoding=None, extra_ignore_dirs=None): ignore_dirs_parsed.append(os.path.basename(os.path.realpath(e))) ignore_dirs.extend(ignore_dirs_parsed) - for root, dirs, files in os.walk(path): + walk = os.walk(path, followlinks=follow_links) + for root, dirs, files in walk: dirs[:] = [d for d in dirs if d not in ignore_dirs] candidates.append(os.path.basename(root)) @@ -71,42 +117,44 @@ def get_all_imports(path, encoding=None, extra_ignore_dirs=None): candidates += [os.path.splitext(fn)[0] for fn in files] for file_name in files: - with open_func(os.path.join(root, file_name), "r", encoding=encoding) as f: + file_name = os.path.join(root, file_name) + with open_func(file_name, "r", encoding=encoding) as f: contents = f.read() - try: - tree = ast.parse(contents) - for node in ast.walk(tree): - if isinstance(node, ast.Import): - for subnode in node.names: - raw_imports.add(subnode.name) - elif isinstance(node, ast.ImportFrom): - raw_imports.add(node.module) - except Exception as exc: - if ignore_errors: - traceback.print_exc(exc) - logging.warn("Failed on file: %s" % os.path.join(root, file_name)) - continue - else: - logging.error("Failed on file: %s" % os.path.join(root, file_name)) - raise exc - - + try: + tree = ast.parse(contents) + for node in ast.walk(tree): + if isinstance(node, ast.Import): + for subnode in node.names: + raw_imports.add(subnode.name) + elif isinstance(node, ast.ImportFrom): + raw_imports.add(node.module) + except Exception as exc: + if ignore_errors: + traceback.print_exc(exc) + logging.warn("Failed on file: %s" % file_name) + continue + else: + logging.error("Failed on file: %s" % file_name) + raise exc # Clean up imports for name in [n for n in raw_imports if n]: - # Sanity check: Name could have been None if the import statement was as from . import X + # Sanity check: Name could have been None if the import + # statement was as ``from . import X`` # Cleanup: We only want to first part of the import. - # Ex: from django.conf --> django.conf. But we only want django as an import + # Ex: from django.conf --> django.conf. But we only want django + # as an import. cleaned_name, _, _ = name.partition('.') imports.add(cleaned_name) - packages = set(imports) - set(set(candidates) & set(imports)) + packages = imports - (set(candidates) & imports) logging.debug('Found packages: {0}'.format(packages)) with open(join("stdlib"), "r") as f: - data = [x.strip() for x in f.readlines()] - data = [x for x in data if x not in py2_exclude] if py2 else data - return sorted(list(set(packages) - set(data))) + data = {x.strip() for x in f} + + data = {x for x in data if x not in py2_exclude} if py2 else data + return list(packages - data) def filter_line(l): @@ -114,32 +162,30 @@ def filter_line(l): def generate_requirements_file(path, imports): - with open(path, "w") as out_file: + with _open(path, "w") as out_file: logging.debug('Writing {num} requirements: {imports} to {file}'.format( num=len(imports), file=path, imports=", ".join([x['name'] for x in imports]) )) fmt = '{name}=={version}' - out_file.write('\n'.join(fmt.format(**item) if item['version'] else '{name}'.format(**item) - for item in imports) + '\n') + out_file.write('\n'.join( + fmt.format(**item) if item['version'] else '{name}'.format(**item) + for item in imports) + '\n') + def output_requirements(imports): - logging.debug('Writing {num} requirements: {imports} to stdout'.format( - num=len(imports), - imports=", ".join([x['name'] for x in imports]) - )) - fmt = '{name}=={version}' - print('\n'.join(fmt.format(**item) if item['version'] else '{name}'.format(**item) - for item in imports)) + generate_requirements_file('-', imports) -def get_imports_info(imports, pypi_server="https://pypi.python.org/pypi/", proxy=None): +def get_imports_info( + imports, pypi_server="https://pypi.python.org/pypi/", proxy=None): result = [] for item in imports: try: - response = requests.get("{0}{1}/json".format(pypi_server, item), proxies=proxy) + response = requests.get( + "{0}{1}/json".format(pypi_server, item), proxies=proxy) if response.status_code == 200: if hasattr(response.content, 'decode'): data = json2package(response.content.decode()) @@ -163,11 +209,13 @@ def get_locally_installed_packages(encoding=None): for root, dirs, files in os.walk(path): for item in files: if "top_level" in item: - with open_func(os.path.join(root, item), "r", encoding=encoding) as f: + item = os.path.join(root, item) + with open_func(item, "r", encoding=encoding) as f: package = root.split(os.sep)[-1].split("-") try: package_import = f.read().strip().split("\n") - except: + except: # NOQA + # TODO: What errors do we intend to suppress here? continue for i_item in package_import: if ((i_item not in ignore) and @@ -203,18 +251,24 @@ def get_import_local(imports, encoding=None): def get_pkg_names(pkgs): - result = [] + """Get PyPI package names from a list of imports. + + Args: + pkgs (List[str]): List of import names. + + Returns: + List[str]: The corresponding PyPI package names. + + """ + result = set() with open(join("mapping"), "r") as f: - data = [x.strip().split(":") for x in f.readlines()] - for pkg in pkgs: - toappend = pkg - for item in data: - if item[0] == pkg: - toappend = item[1] - break - if toappend not in result: - result.append(toappend) - return result + data = dict(x.strip().split(":") for x in f) + for pkg in pkgs: + # Look up the mapped requirement. If a mapping isn't found, + # simply use the package name. + result.add(data.get(pkg, pkg)) + # Return a sorted list for backward compatibility. + return sorted(result, key=lambda s: s.lower()) def get_name_without_alias(name): @@ -228,6 +282,7 @@ def get_name_without_alias(name): def join(f): return os.path.join(os.path.dirname(__file__), f) + def parse_requirements(file_): """Parse a requirements formatted file. @@ -245,7 +300,9 @@ def parse_requirements(file_): tuple: The contents of the file, excluding comments. """ modules = [] - delim = ["<", ">", "=", "!", "~"] # https://www.python.org/dev/peps/pep-0508/#complete-grammar + # For the dependency identifier specification, see + # https://www.python.org/dev/peps/pep-0508/#complete-grammar + delim = ["<", ">", "=", "!", "~"] try: f = open_func(file_, "r") @@ -253,14 +310,16 @@ def parse_requirements(file_): logging.error("Failed on file: {}".format(file_)) raise else: - data = [x.strip() for x in f.readlines() if x != "\n"] - finally: - f.close() + try: + data = [x.strip() for x in f.readlines() if x != "\n"] + finally: + f.close() data = [x for x in data if x[0].isalpha()] for x in data: - if not any([y in x for y in delim]): # Check for modules w/o a specifier. + # Check for modules w/o a specifier. + if not any([y in x for y in delim]): modules.append({"name": x, "version": None}) for y in x: if y in delim: @@ -298,11 +357,13 @@ def compare_modules(file_, imports): def diff(file_, imports): - """Display the difference between modules in a file and imported modules.""" + """Display the difference between modules in a file and imported modules.""" # NOQA modules_not_imported = compare_modules(file_, imports) - logging.info("The following modules are in {} but do not seem to be imported: " - "{}".format(file_, ", ".join(x for x in modules_not_imported))) + logging.info( + "The following modules are in {} but do not seem to be imported: " + "{}".format(file_, ", ".join(x for x in modules_not_imported))) + def clean(file_, imports): """Remove modules that aren't imported in project from file.""" @@ -316,29 +377,36 @@ def clean(file_, imports): logging.error("Failed on file: {}".format(file_)) raise else: - for i in f.readlines(): - if re_remove.match(i) is None: - to_write.append(i) - f.seek(0) - f.truncate() + try: + for i in f.readlines(): + if re_remove.match(i) is None: + to_write.append(i) + f.seek(0) + f.truncate() - for i in to_write: - f.write(i) - finally: - f.close() + for i in to_write: + f.write(i) + finally: + f.close() logging.info("Successfully cleaned up requirements in " + file_) + def init(args): encoding = args.get('--encoding') extra_ignore_dirs = args.get('--ignore') + follow_links = not args.get('--no-follow-links') + input_path = args['<path>'] + if input_path is None: + input_path = os.path.abspath(os.curdir) if extra_ignore_dirs: extra_ignore_dirs = extra_ignore_dirs.split(',') - candidates = get_all_imports(args['<path>'], + candidates = get_all_imports(input_path, encoding=encoding, - extra_ignore_dirs=extra_ignore_dirs) + extra_ignore_dirs=extra_ignore_dirs, + follow_links=follow_links) candidates = get_pkg_names(candidates) logging.debug("Found imports: " + ", ".join(candidates)) pypi_server = "https://pypi.python.org/pypi/" @@ -364,7 +432,7 @@ def init(args): pypi_server=pypi_server) path = (args["--savepath"] if args["--savepath"] else - os.path.join(args['<path>'], "requirements.txt")) + os.path.join(input_path, "requirements.txt")) if args["--diff"]: diff(args["--diff"], imports) @@ -374,11 +442,17 @@ def init(args): clean(args["--clean"], imports) return - if not args["--print"] and not args["--savepath"] and not args["--force"] and os.path.exists(path): + if (not args["--print"] + and not args["--savepath"] + and not args["--force"] + and os.path.exists(path)): logging.warning("Requirements.txt already exists, " "use --force to overwrite it") return + if args.get('--no-pin'): + imports = [{'name': item["name"], 'version': ''} for item in imports] + if args["--print"]: output_requirements(imports) logging.info("Successfully output requirements") diff --git a/pipenv/vendor/pipreqs/stdlib b/pipenv/vendor/pipreqs/stdlib index 71edcc87..470fd5cc 100644 --- a/pipenv/vendor/pipreqs/stdlib +++ b/pipenv/vendor/pipreqs/stdlib @@ -1,6 +1,8 @@ __builtin__ __future__ __main__ +_dummy_thread +_thread _winreg abc aepack @@ -15,6 +17,7 @@ argparse array ast asynchat +asyncio asyncore atexit audioop @@ -28,6 +31,7 @@ binhex bisect bsddb buildtools +builtins bz2 calendar Carbon @@ -101,6 +105,7 @@ code codecs codeop collections +collections.abc ColorPicker colorsys commands @@ -108,12 +113,16 @@ compileall compiler compiler.ast compiler.visitor +concurrent +concurrent.futures ConfigParser +configparser contextlib Cookie cookielib copy copy_reg +copyreg cPickle cProfile crypt @@ -127,6 +136,9 @@ curses.textpad datetime dbhash dbm +dbm.dumb +dbm.gnu +dbm.ndbm decimal DEVICE difflib @@ -188,21 +200,27 @@ dummy_threading EasyDialogs email email.charset +email.contentmanager email.encoders email.errors email.generator email.header +email.headerregistry email.iterators email.message email.mime email.parser +email.policy email.utils encodings encodings.idna +encodings.mbcs encodings.utf_8_sig ensurepip +enum errno exceptions +faulthandler fcntl filecmp fileinput @@ -226,8 +244,8 @@ gensuitemodule getopt getpass gettext -gl GL +gl glob grp gzip @@ -236,9 +254,17 @@ heapq hmac hotshot hotshot.stats +html +html.entities +html.parser htmlentitydefs htmllib HTMLParser +http +http.client +http.cookiejar +http.cookies +http.server httplib ic icopen @@ -248,12 +274,17 @@ imgfile imghdr imp importlib +importlib.abc +importlib.machinery +importlib.util imputil inspect io +ipaddress itertools jpeg json +json.tool keyword lib2to3 linecache @@ -261,6 +292,7 @@ locale logging logging.config logging.handlers +lzma macerrors MacOS macostools @@ -301,11 +333,13 @@ os os.path ossaudiodev parser +pathlib pdb pickle pickletools pipes PixMapWrapper +pkg_resources pkgutil platform plistlib @@ -322,10 +356,12 @@ py_compile pyclbr pydoc Queue +queue quopri random re readline +reprlib resource rexec rfc822 @@ -335,7 +371,9 @@ runpy sched ScrolledText select +selectors sets +setuptools sgmllib sha shelve @@ -350,10 +388,12 @@ smtplib sndhdr socket SocketServer +socketserver spwd sqlite3 ssl stat +statistics statvfs string StringIO @@ -361,8 +401,8 @@ stringprep struct subprocess sunau -sunaudiodev SUNAUDIODEV +sunaudiodev symbol symtable sys @@ -374,6 +414,7 @@ telnetlib tempfile termios test +test.support test.test_support textwrap thread @@ -382,17 +423,30 @@ time timeit Tix Tkinter +tkinter +tkinter.scrolledtext +tkinter.tix +tkinter.ttk token tokenize trace traceback +tracemalloc ttk tty turtle +turtledemo types +typing unicodedata unittest +unittest.mock urllib +urllib.error +urllib.parse +urllib.request +urllib.response +urllib.robotparser urllib2 urlparse user @@ -401,6 +455,7 @@ UserList UserString uu uuid +venv videoreader W warnings @@ -408,6 +463,7 @@ wave weakref webbrowser whichdb +winreg winsound wsgiref wsgiref.handlers @@ -422,654 +478,17 @@ xml.dom.minidom xml.dom.pulldom xml.etree.ElementTree xml.parsers.expat +xml.parsers.expat.errors +xml.parsers.expat.model xml.sax xml.sax.handler xml.sax.saxutils xml.sax.xmlreader +xmlrpc +xmlrpc.client +xmlrpc.server xmlrpclib -zipfile -zipimport -zlib -__future__ -__main__ -_dummy_thread -_thread -abc -aifc -argparse -array -ast -asynchat -asyncio -asyncore -atexit -audioop -base64 -bdb -binascii -binhex -bisect -builtins -bz2 -calendar -cgi -cgitb -chunk -cmath -cmd -code -codecs -codeop -collections -collections.abc -colorsys -compileall -concurrent -concurrent.futures -configparser -contextlib -copy -copyreg -cProfile -crypt -csv -ctypes -curses -curses.ascii -curses.panel -curses.textpad -datetime -dbm -dbm.dumb -dbm.gnu -dbm.ndbm -decimal -difflib -dis -distutils -distutils.archive_util -distutils.bcppcompiler -distutils.ccompiler -distutils.cmd -distutils.command -distutils.command.bdist -distutils.command.bdist_dumb -distutils.command.bdist_msi -distutils.command.bdist_packager -distutils.command.bdist_rpm -distutils.command.bdist_wininst -distutils.command.build -distutils.command.build_clib -distutils.command.build_ext -distutils.command.build_py -distutils.command.build_scripts -distutils.command.check -distutils.command.clean -distutils.command.config -distutils.command.install -distutils.command.install_data -distutils.command.install_headers -distutils.command.install_lib -distutils.command.install_scripts -distutils.command.register -distutils.command.sdist -distutils.core -distutils.cygwinccompiler -distutils.debug -distutils.dep_util -distutils.dir_util -distutils.dist -distutils.errors -distutils.extension -distutils.fancy_getopt -distutils.file_util -distutils.filelist -distutils.log -distutils.msvccompiler -distutils.spawn -distutils.sysconfig -distutils.text_file -distutils.unixccompiler -distutils.util -distutils.version -doctest -dummy_threading -email -email.charset -email.contentmanager -email.encoders -email.errors -email.generator -email.header -email.headerregistry -email.iterators -email.message -email.mime -email.parser -email.policy -email.utils -encodings -encodings.idna -encodings.mbcs -encodings.utf_8_sig -ensurepip -enum -errno -faulthandler -fcntl -filecmp -fileinput -fnmatch -formatter -fpectl -fractions -ftplib -functools -gc -getopt -getpass -gettext -glob -grp -gzip -hashlib -heapq -hmac -html -html.entities -html.parser -http -http.client -http.cookiejar -http.cookies -http.server -imaplib -imghdr -imp -importlib -importlib.abc -importlib.machinery -importlib.util -inspect -io -ipaddress -itertools -json -keyword -lib2to3 -linecache -locale -logging -logging.config -logging.handlers -lzma -macpath -mailbox -mailcap -marshal -math -mimetypes -mmap -modulefinder -msilib -msvcrt -multiprocessing -multiprocessing.connection -multiprocessing.dummy -multiprocessing.managers -multiprocessing.pool -multiprocessing.sharedctypes -netrc -nis -nntplib -numbers -operator -optparse -os -os.path -ossaudiodev -parser -pathlib -pdb -pickle -pickletools -pipes -pkgutil -platform -plistlib -poplib -posix -pprint -profile -pstats -pty -pwd -py_compile -pyclbr -pydoc -queue -quopri -random -re -readline -reprlib -resource -rlcompleter -runpy -sched -select -selectors -shelve -shlex -shutil -signal -site -smtpd -smtplib -sndhdr -socket -socketserver -spwd -sqlite3 -ssl -stat -statistics -string -stringprep -struct -subprocess -sunau -symbol -symtable -sys -sysconfig -syslog -tabnanny -tarfile -telnetlib -tempfile -termios -test -test.support -textwrap -threading -time -timeit -tkinter -tkinter.scrolledtext -tkinter.tix -tkinter.ttk -token -tokenize -trace -traceback -tracemalloc -tty -turtle -turtledemo -types -unicodedata -unittest -unittest.mock -urllib -urllib.error -urllib.parse -urllib.request -urllib.response -urllib.robotparser -uu -uuid -venv -warnings -wave -weakref -webbrowser -winreg -winsound -wsgiref -wsgiref.handlers -wsgiref.headers -wsgiref.simple_server -wsgiref.util -wsgiref.validate -xdrlib -xml -xml.dom -xml.dom.minidom -xml.dom.pulldom -xml.etree.ElementTree -xml.parsers.expat -xml.parsers.expat.errors -xml.parsers.expat.model -xml.sax -xml.sax.handler -xml.sax.saxutils -xml.sax.xmlreader -xmlrpc -xmlrpc.client -xmlrpc.server -zipfile -zipimport -zlib -__future__ -__main__ -_dummy_thread -_thread -abc -aifc -argparse -array -ast -asynchat -asyncio -asyncore -atexit -audioop -base64 -bdb -binascii -binhex -bisect -builtins -bz2 -calendar -cgi -cgitb -chunk -cmath -cmd -code -codecs -codeop -collections -collections.abc -colorsys -compileall -concurrent -concurrent.futures -configparser -contextlib -copy -copyreg -cProfile -crypt -csv -ctypes -curses -curses.ascii -curses.panel -curses.textpad -datetime -dbm -dbm.dumb -dbm.gnu -dbm.ndbm -decimal -difflib -dis -distutils -distutils.archive_util -distutils.bcppcompiler -distutils.ccompiler -distutils.cmd -distutils.command -distutils.command.bdist -distutils.command.bdist_dumb -distutils.command.bdist_msi -distutils.command.bdist_packager -distutils.command.bdist_rpm -distutils.command.bdist_wininst -distutils.command.build -distutils.command.build_clib -distutils.command.build_ext -distutils.command.build_py -distutils.command.build_scripts -distutils.command.check -distutils.command.clean -distutils.command.config -distutils.command.install -distutils.command.install_data -distutils.command.install_headers -distutils.command.install_lib -distutils.command.install_scripts -distutils.command.register -distutils.command.sdist -distutils.core -distutils.cygwinccompiler -distutils.debug -distutils.dep_util -distutils.dir_util -distutils.dist -distutils.errors -distutils.extension -distutils.fancy_getopt -distutils.file_util -distutils.filelist -distutils.log -distutils.msvccompiler -distutils.spawn -distutils.sysconfig -distutils.text_file -distutils.unixccompiler -distutils.util -distutils.version -doctest -dummy_threading -email -email.charset -email.contentmanager -email.encoders -email.errors -email.generator -email.header -email.headerregistry -email.iterators -email.message -email.mime -email.parser -email.policy -email.utils -encodings -encodings.idna -encodings.mbcs -encodings.utf_8_sig -ensurepip -enum -errno -faulthandler -fcntl -filecmp -fileinput -fnmatch -formatter -fpectl -fractions -ftplib -functools -gc -getopt -getpass -gettext -glob -grp -gzip -hashlib -heapq -hmac -html -html.entities -html.parser -http -http.client -http.cookiejar -http.cookies -http.server -imaplib -imghdr -imp -importlib -importlib.abc -importlib.machinery -importlib.util -inspect -io -ipaddress -itertools -json -json.tool -keyword -lib2to3 -linecache -locale -logging -logging.config -logging.handlers -lzma -macpath -mailbox -mailcap -marshal -math -mimetypes -mmap -modulefinder -msilib -msvcrt -multiprocessing -multiprocessing.connection -multiprocessing.dummy -multiprocessing.managers -multiprocessing.pool -multiprocessing.sharedctypes -netrc -nis -nntplib -numbers -operator -optparse -os -os.path -ossaudiodev -parser -pathlib -pdb -pickle -pickletools -pipes -pkgutil -platform -plistlib -poplib -posix -pprint -profile -pstats -pty -pwd -py_compile -pyclbr -pydoc -queue -quopri -random -re -readline -reprlib -resource -rlcompleter -runpy -sched -select -selectors -shelve -shlex -shutil -signal -site -smtpd -smtplib -sndhdr -socket -socketserver -spwd -sqlite3 -ssl -stat -statistics -string -stringprep -struct -subprocess -sunau -symbol -symtable -sys -sysconfig -syslog -tabnanny -tarfile -telnetlib -tempfile -termios -test -test.support -textwrap -threading -time -timeit -tkinter -tkinter.scrolledtext -tkinter.tix -tkinter.ttk -token -tokenize -trace -traceback -tracemalloc -tty -turtle -turtledemo -types -unicodedata -unittest -unittest.mock -urllib -urllib.error -urllib.parse -urllib.request -urllib.response -urllib.robotparser -uu -uuid -venv -warnings -wave -weakref -webbrowser -winreg -winsound -wsgiref -wsgiref.handlers -wsgiref.headers -wsgiref.simple_server -wsgiref.util -wsgiref.validate -xdrlib -xml -xml.dom -xml.dom.minidom -xml.dom.pulldom -xml.etree.ElementTree -xml.parsers.expat -xml.parsers.expat.errors -xml.parsers.expat.model -xml.sax -xml.sax.handler -xml.sax.saxutils -xml.sax.xmlreader -xmlrpc -xmlrpc.client -xmlrpc.server +yp zipapp zipfile zipimport diff --git a/pipenv/vendor/platformdirs/__init__.py b/pipenv/vendor/platformdirs/__init__.py new file mode 100644 index 00000000..26032a3b --- /dev/null +++ b/pipenv/vendor/platformdirs/__init__.py @@ -0,0 +1,329 @@ +""" +Utilities for determining application-specific dirs. See <https://github.com/platformdirs/platformdirs> for details and +usage. +""" +import importlib +import os +import sys +from pathlib import Path +from typing import TYPE_CHECKING, Optional, Type, Union + +if TYPE_CHECKING: + from typing_extensions import Literal # pragma: no cover + +from .api import PlatformDirsABC +from .version import __version__, __version_info__ + + +def _set_platform_dir_class() -> Type[PlatformDirsABC]: + if os.getenv("ANDROID_DATA") == "/data" and os.getenv("ANDROID_ROOT") == "/system": + module, name = "platformdirs.android", "Android" + elif sys.platform == "win32": + module, name = "platformdirs.windows", "Windows" + elif sys.platform == "darwin": + module, name = "platformdirs.macos", "MacOS" + else: + module, name = "platformdirs.unix", "Unix" + result: Type[PlatformDirsABC] = getattr(importlib.import_module(module), name) + return result + + +PlatformDirs = _set_platform_dir_class() #: Currently active platform +AppDirs = PlatformDirs #: Backwards compatibility with appdirs + + +def user_data_dir( + appname: Optional[str] = None, + appauthor: Union[str, None, "Literal[False]"] = None, + version: Optional[str] = None, + roaming: bool = False, +) -> str: + """ + :param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`. + :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`. + :param version: See `version <platformdirs.api.PlatformDirsABC.version>`. + :param roaming: See `roaming <platformdirs.api.PlatformDirsABC.version>`. + :returns: data directory tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, roaming=roaming).user_data_dir + + +def site_data_dir( + appname: Optional[str] = None, + appauthor: Union[str, None, "Literal[False]"] = None, + version: Optional[str] = None, + multipath: bool = False, +) -> str: + """ + :param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`. + :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`. + :param version: See `version <platformdirs.api.PlatformDirsABC.version>`. + :param multipath: See `roaming <platformdirs.api.PlatformDirsABC.multipath>`. + :returns: data directory shared by users + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, multipath=multipath).site_data_dir + + +def user_config_dir( + appname: Optional[str] = None, + appauthor: Union[str, None, "Literal[False]"] = None, + version: Optional[str] = None, + roaming: bool = False, +) -> str: + """ + :param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`. + :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`. + :param version: See `version <platformdirs.api.PlatformDirsABC.version>`. + :param roaming: See `roaming <platformdirs.api.PlatformDirsABC.version>`. + :returns: config directory tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, roaming=roaming).user_config_dir + + +def site_config_dir( + appname: Optional[str] = None, + appauthor: Union[str, None, "Literal[False]"] = None, + version: Optional[str] = None, + multipath: bool = False, +) -> str: + """ + :param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`. + :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`. + :param version: See `version <platformdirs.api.PlatformDirsABC.version>`. + :param multipath: See `roaming <platformdirs.api.PlatformDirsABC.multipath>`. + :returns: config directory shared by the users + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, multipath=multipath).site_config_dir + + +def user_cache_dir( + appname: Optional[str] = None, + appauthor: Union[str, None, "Literal[False]"] = None, + version: Optional[str] = None, + opinion: bool = True, +) -> str: + """ + :param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`. + :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`. + :param version: See `version <platformdirs.api.PlatformDirsABC.version>`. + :param opinion: See `roaming <platformdirs.api.PlatformDirsABC.opinion>`. + :returns: cache directory tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, opinion=opinion).user_cache_dir + + +def user_state_dir( + appname: Optional[str] = None, + appauthor: Union[str, None, "Literal[False]"] = None, + version: Optional[str] = None, + roaming: bool = False, +) -> str: + """ + :param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`. + :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`. + :param version: See `version <platformdirs.api.PlatformDirsABC.version>`. + :param roaming: See `roaming <platformdirs.api.PlatformDirsABC.version>`. + :returns: state directory tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, roaming=roaming).user_state_dir + + +def user_log_dir( + appname: Optional[str] = None, + appauthor: Union[str, None, "Literal[False]"] = None, + version: Optional[str] = None, + opinion: bool = True, +) -> str: + """ + :param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`. + :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`. + :param version: See `version <platformdirs.api.PlatformDirsABC.version>`. + :param opinion: See `roaming <platformdirs.api.PlatformDirsABC.opinion>`. + :returns: log directory tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, opinion=opinion).user_log_dir + + +def user_documents_dir() -> str: + """ + :returns: documents directory tied to the user + """ + return PlatformDirs().user_documents_dir + + +def user_runtime_dir( + appname: Optional[str] = None, + appauthor: Union[str, None, "Literal[False]"] = None, + version: Optional[str] = None, + opinion: bool = True, +) -> str: + """ + :param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`. + :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`. + :param version: See `version <platformdirs.api.PlatformDirsABC.version>`. + :param opinion: See `opinion <platformdirs.api.PlatformDirsABC.opinion>`. + :returns: runtime directory tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, opinion=opinion).user_runtime_dir + + +def user_data_path( + appname: Optional[str] = None, + appauthor: Union[str, None, "Literal[False]"] = None, + version: Optional[str] = None, + roaming: bool = False, +) -> Path: + """ + :param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`. + :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`. + :param version: See `version <platformdirs.api.PlatformDirsABC.version>`. + :param roaming: See `roaming <platformdirs.api.PlatformDirsABC.version>`. + :returns: data path tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, roaming=roaming).user_data_path + + +def site_data_path( + appname: Optional[str] = None, + appauthor: Union[str, None, "Literal[False]"] = None, + version: Optional[str] = None, + multipath: bool = False, +) -> Path: + """ + :param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`. + :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`. + :param version: See `version <platformdirs.api.PlatformDirsABC.version>`. + :param multipath: See `multipath <platformdirs.api.PlatformDirsABC.multipath>`. + :returns: data path shared by users + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, multipath=multipath).site_data_path + + +def user_config_path( + appname: Optional[str] = None, + appauthor: Union[str, None, "Literal[False]"] = None, + version: Optional[str] = None, + roaming: bool = False, +) -> Path: + """ + :param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`. + :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`. + :param version: See `version <platformdirs.api.PlatformDirsABC.version>`. + :param roaming: See `roaming <platformdirs.api.PlatformDirsABC.version>`. + :returns: config path tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, roaming=roaming).user_config_path + + +def site_config_path( + appname: Optional[str] = None, + appauthor: Union[str, None, "Literal[False]"] = None, + version: Optional[str] = None, + multipath: bool = False, +) -> Path: + """ + :param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`. + :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`. + :param version: See `version <platformdirs.api.PlatformDirsABC.version>`. + :param multipath: See `roaming <platformdirs.api.PlatformDirsABC.multipath>`. + :returns: config path shared by the users + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, multipath=multipath).site_config_path + + +def user_cache_path( + appname: Optional[str] = None, + appauthor: Union[str, None, "Literal[False]"] = None, + version: Optional[str] = None, + opinion: bool = True, +) -> Path: + """ + :param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`. + :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`. + :param version: See `version <platformdirs.api.PlatformDirsABC.version>`. + :param opinion: See `roaming <platformdirs.api.PlatformDirsABC.opinion>`. + :returns: cache path tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, opinion=opinion).user_cache_path + + +def user_state_path( + appname: Optional[str] = None, + appauthor: Union[str, None, "Literal[False]"] = None, + version: Optional[str] = None, + roaming: bool = False, +) -> Path: + """ + :param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`. + :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`. + :param version: See `version <platformdirs.api.PlatformDirsABC.version>`. + :param roaming: See `roaming <platformdirs.api.PlatformDirsABC.version>`. + :returns: state path tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, roaming=roaming).user_state_path + + +def user_log_path( + appname: Optional[str] = None, + appauthor: Union[str, None, "Literal[False]"] = None, + version: Optional[str] = None, + opinion: bool = True, +) -> Path: + """ + :param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`. + :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`. + :param version: See `version <platformdirs.api.PlatformDirsABC.version>`. + :param opinion: See `roaming <platformdirs.api.PlatformDirsABC.opinion>`. + :returns: log path tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, opinion=opinion).user_log_path + + +def user_documents_path() -> Path: + """ + :returns: documents path tied to the user + """ + return PlatformDirs().user_documents_path + + +def user_runtime_path( + appname: Optional[str] = None, + appauthor: Union[str, None, "Literal[False]"] = None, + version: Optional[str] = None, + opinion: bool = True, +) -> Path: + """ + :param appname: See `appname <platformdirs.api.PlatformDirsABC.appname>`. + :param appauthor: See `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`. + :param version: See `version <platformdirs.api.PlatformDirsABC.version>`. + :param opinion: See `opinion <platformdirs.api.PlatformDirsABC.opinion>`. + :returns: runtime path tied to the user + """ + return PlatformDirs(appname=appname, appauthor=appauthor, version=version, opinion=opinion).user_runtime_path + + +__all__ = [ + "__version__", + "__version_info__", + "PlatformDirs", + "AppDirs", + "PlatformDirsABC", + "user_data_dir", + "user_config_dir", + "user_cache_dir", + "user_state_dir", + "user_log_dir", + "user_documents_dir", + "user_runtime_dir", + "site_data_dir", + "site_config_dir", + "user_data_path", + "user_config_path", + "user_cache_path", + "user_state_path", + "user_log_path", + "user_documents_path", + "user_runtime_path", + "site_data_path", + "site_config_path", +] diff --git a/pipenv/vendor/platformdirs/__main__.py b/pipenv/vendor/platformdirs/__main__.py new file mode 100644 index 00000000..bc24c1c7 --- /dev/null +++ b/pipenv/vendor/platformdirs/__main__.py @@ -0,0 +1,44 @@ +from pipenv.vendor.platformdirs import PlatformDirs, __version__ + +PROPS = ( + "user_data_dir", + "user_config_dir", + "user_cache_dir", + "user_state_dir", + "user_log_dir", + "user_documents_dir", + "user_runtime_dir", + "site_data_dir", + "site_config_dir", +) + + +def main() -> None: + app_name = "MyApp" + app_author = "MyCompany" + + print(f"-- platformdirs {__version__} --") + + print("-- app dirs (with optional 'version')") + dirs = PlatformDirs(app_name, app_author, version="1.0") + for prop in PROPS: + print(f"{prop}: {getattr(dirs, prop)}") + + print("\n-- app dirs (without optional 'version')") + dirs = PlatformDirs(app_name, app_author) + for prop in PROPS: + print(f"{prop}: {getattr(dirs, prop)}") + + print("\n-- app dirs (without optional 'appauthor')") + dirs = PlatformDirs(app_name) + for prop in PROPS: + print(f"{prop}: {getattr(dirs, prop)}") + + print("\n-- app dirs (with disabled 'appauthor')") + dirs = PlatformDirs(app_name, appauthor=False) + for prop in PROPS: + print(f"{prop}: {getattr(dirs, prop)}") + + +if __name__ == "__main__": + main() diff --git a/pipenv/vendor/platformdirs/android.py b/pipenv/vendor/platformdirs/android.py new file mode 100644 index 00000000..75990457 --- /dev/null +++ b/pipenv/vendor/platformdirs/android.py @@ -0,0 +1,117 @@ +import os +import re +import sys +from functools import lru_cache + +from .api import PlatformDirsABC + + +class Android(PlatformDirsABC): + """ + Follows the guidance `from here <https://android.stackexchange.com/a/216132>`_. Makes use of the + `appname <platformdirs.api.PlatformDirsABC.appname>` and + `version <platformdirs.api.PlatformDirsABC.version>`. + """ + + @property + def user_data_dir(self) -> str: + """:return: data directory tied to the user, e.g. ``/data/user/<userid>/<packagename>/files/<AppName>``""" + return self._append_app_name_and_version(_android_folder(), "files") + + @property + def site_data_dir(self) -> str: + """:return: data directory shared by users, same as `user_data_dir`""" + return self.user_data_dir + + @property + def user_config_dir(self) -> str: + """ + :return: config directory tied to the user, e.g. ``/data/user/<userid>/<packagename>/shared_prefs/<AppName>`` + """ + return self._append_app_name_and_version(_android_folder(), "shared_prefs") + + @property + def site_config_dir(self) -> str: + """:return: config directory shared by the users, same as `user_config_dir`""" + return self.user_config_dir + + @property + def user_cache_dir(self) -> str: + """:return: cache directory tied to the user, e.g. e.g. ``/data/user/<userid>/<packagename>/cache/<AppName>``""" + return self._append_app_name_and_version(_android_folder(), "cache") + + @property + def user_state_dir(self) -> str: + """:return: state directory tied to the user, same as `user_data_dir`""" + return self.user_data_dir + + @property + def user_log_dir(self) -> str: + """ + :return: log directory tied to the user, same as `user_cache_dir` if not opinionated else ``log`` in it, + e.g. ``/data/user/<userid>/<packagename>/cache/<AppName>/log`` + """ + path = self.user_cache_dir + if self.opinion: + path = os.path.join(path, "log") + return path + + @property + def user_documents_dir(self) -> str: + """ + :return: documents directory tied to the user e.g. ``/storage/emulated/0/Documents`` + """ + return _android_documents_folder() + + @property + def user_runtime_dir(self) -> str: + """ + :return: runtime directory tied to the user, same as `user_cache_dir` if not opinionated else ``tmp`` in it, + e.g. ``/data/user/<userid>/<packagename>/cache/<AppName>/tmp`` + """ + path = self.user_cache_dir + if self.opinion: + path = os.path.join(path, "tmp") + return path + + +@lru_cache(maxsize=1) +def _android_folder() -> str: + """:return: base folder for the Android OS""" + try: + # First try to get path to android app via pyjnius + from jnius import autoclass # noqa: SC200 + + Context = autoclass("android.content.Context") # noqa: SC200 + result: str = Context.getFilesDir().getParentFile().getAbsolutePath() + except Exception: + # if fails find an android folder looking path on the sys.path + pattern = re.compile(r"/data/(data|user/\d+)/(.+)/files") + for path in sys.path: + if pattern.match(path): + result = path.split("/files")[0] + break + else: + raise OSError("Cannot find path to android app folder") + return result + + +@lru_cache(maxsize=1) +def _android_documents_folder() -> str: + """:return: documents folder for the Android OS""" + # Get directories with pyjnius + try: + from jnius import autoclass # noqa: SC200 + + Context = autoclass("android.content.Context") # noqa: SC200 + Environment = autoclass("android.os.Environment") + documents_dir: str = Context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath() + except Exception: + documents_dir = "/storage/emulated/0/Documents" + + return documents_dir + + +__all__ = [ + "Android", +] diff --git a/pipenv/vendor/platformdirs/api.py b/pipenv/vendor/platformdirs/api.py new file mode 100644 index 00000000..ebd96afe --- /dev/null +++ b/pipenv/vendor/platformdirs/api.py @@ -0,0 +1,155 @@ +import os +import sys +from abc import ABC, abstractmethod +from pathlib import Path +from typing import Optional, Union + +if sys.version_info >= (3, 8): # pragma: no branch + from typing import Literal # pragma: no cover + + +class PlatformDirsABC(ABC): + """ + Abstract base class for platform directories. + """ + + def __init__( + self, + appname: Optional[str] = None, + appauthor: Union[str, None, "Literal[False]"] = None, + version: Optional[str] = None, + roaming: bool = False, + multipath: bool = False, + opinion: bool = True, + ): + """ + Create a new platform directory. + + :param appname: See `appname`. + :param appauthor: See `appauthor`. + :param version: See `version`. + :param roaming: See `roaming`. + :param multipath: See `multipath`. + :param opinion: See `opinion`. + """ + self.appname = appname #: The name of application. + self.appauthor = appauthor + """ + The name of the app author or distributing body for this application. Typically, it is the owning company name. + Defaults to `appname`. You may pass ``False`` to disable it. + """ + self.version = version + """ + An optional version path element to append to the path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this would typically be ``<major>.<minor>``. + """ + self.roaming = roaming + """ + Whether to use the roaming appdata directory on Windows. That means that for users on a Windows network setup + for roaming profiles, this user data will be synced on login (see + `here <http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>`_). + """ + self.multipath = multipath + """ + An optional parameter only applicable to Unix/Linux which indicates that the entire list of data dirs should be + returned. By default, the first item would only be returned. + """ + self.opinion = opinion #: A flag to indicating to use opinionated values. + + def _append_app_name_and_version(self, *base: str) -> str: + params = list(base[1:]) + if self.appname: + params.append(self.appname) + if self.version: + params.append(self.version) + return os.path.join(base[0], *params) + + @property + @abstractmethod + def user_data_dir(self) -> str: + """:return: data directory tied to the user""" + + @property + @abstractmethod + def site_data_dir(self) -> str: + """:return: data directory shared by users""" + + @property + @abstractmethod + def user_config_dir(self) -> str: + """:return: config directory tied to the user""" + + @property + @abstractmethod + def site_config_dir(self) -> str: + """:return: config directory shared by the users""" + + @property + @abstractmethod + def user_cache_dir(self) -> str: + """:return: cache directory tied to the user""" + + @property + @abstractmethod + def user_state_dir(self) -> str: + """:return: state directory tied to the user""" + + @property + @abstractmethod + def user_log_dir(self) -> str: + """:return: log directory tied to the user""" + + @property + @abstractmethod + def user_documents_dir(self) -> str: + """:return: documents directory tied to the user""" + + @property + @abstractmethod + def user_runtime_dir(self) -> str: + """:return: runtime directory tied to the user""" + + @property + def user_data_path(self) -> Path: + """:return: data path tied to the user""" + return Path(self.user_data_dir) + + @property + def site_data_path(self) -> Path: + """:return: data path shared by users""" + return Path(self.site_data_dir) + + @property + def user_config_path(self) -> Path: + """:return: config path tied to the user""" + return Path(self.user_config_dir) + + @property + def site_config_path(self) -> Path: + """:return: config path shared by the users""" + return Path(self.site_config_dir) + + @property + def user_cache_path(self) -> Path: + """:return: cache path tied to the user""" + return Path(self.user_cache_dir) + + @property + def user_state_path(self) -> Path: + """:return: state path tied to the user""" + return Path(self.user_state_dir) + + @property + def user_log_path(self) -> Path: + """:return: log path tied to the user""" + return Path(self.user_log_dir) + + @property + def user_documents_path(self) -> Path: + """:return: documents path tied to the user""" + return Path(self.user_documents_dir) + + @property + def user_runtime_path(self) -> Path: + """:return: runtime path tied to the user""" + return Path(self.user_runtime_dir) diff --git a/pipenv/vendor/platformdirs/macos.py b/pipenv/vendor/platformdirs/macos.py new file mode 100644 index 00000000..fffd1a7f --- /dev/null +++ b/pipenv/vendor/platformdirs/macos.py @@ -0,0 +1,62 @@ +import os + +from .api import PlatformDirsABC + + +class MacOS(PlatformDirsABC): + """ + Platform directories for the macOS operating system. Follows the guidance from `Apple documentation + <https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/MacOSXDirectories/MacOSXDirectories.html>`_. + Makes use of the `appname <platformdirs.api.PlatformDirsABC.appname>` and + `version <platformdirs.api.PlatformDirsABC.version>`. + """ + + @property + def user_data_dir(self) -> str: + """:return: data directory tied to the user, e.g. ``~/Library/Application Support/$appname/$version``""" + return self._append_app_name_and_version(os.path.expanduser("~/Library/Application Support/")) + + @property + def site_data_dir(self) -> str: + """:return: data directory shared by users, e.g. ``/Library/Application Support/$appname/$version``""" + return self._append_app_name_and_version("/Library/Application Support") + + @property + def user_config_dir(self) -> str: + """:return: config directory tied to the user, e.g. ``~/Library/Preferences/$appname/$version``""" + return self._append_app_name_and_version(os.path.expanduser("~/Library/Preferences/")) + + @property + def site_config_dir(self) -> str: + """:return: config directory shared by the users, e.g. ``/Library/Preferences/$appname``""" + return self._append_app_name_and_version("/Library/Preferences") + + @property + def user_cache_dir(self) -> str: + """:return: cache directory tied to the user, e.g. ``~/Library/Caches/$appname/$version``""" + return self._append_app_name_and_version(os.path.expanduser("~/Library/Caches")) + + @property + def user_state_dir(self) -> str: + """:return: state directory tied to the user, same as `user_data_dir`""" + return self.user_data_dir + + @property + def user_log_dir(self) -> str: + """:return: log directory tied to the user, e.g. ``~/Library/Logs/$appname/$version``""" + return self._append_app_name_and_version(os.path.expanduser("~/Library/Logs")) + + @property + def user_documents_dir(self) -> str: + """:return: documents directory tied to the user, e.g. ``~/Documents``""" + return os.path.expanduser("~/Documents") + + @property + def user_runtime_dir(self) -> str: + """:return: runtime directory tied to the user, e.g. ``~/Library/Caches/TemporaryItems/$appname/$version``""" + return self._append_app_name_and_version(os.path.expanduser("~/Library/Caches/TemporaryItems")) + + +__all__ = [ + "MacOS", +] diff --git a/pipenv/vendor/platformdirs/py.typed b/pipenv/vendor/platformdirs/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/pipenv/vendor/platformdirs/unix.py b/pipenv/vendor/platformdirs/unix.py new file mode 100644 index 00000000..688b2799 --- /dev/null +++ b/pipenv/vendor/platformdirs/unix.py @@ -0,0 +1,180 @@ +import os +import sys +from configparser import ConfigParser +from pathlib import Path +from typing import Optional + +from .api import PlatformDirsABC + +if sys.platform.startswith("linux"): # pragma: no branch # no op check, only to please the type checker + from os import getuid +else: + + def getuid() -> int: + raise RuntimeError("should only be used on Linux") + + +class Unix(PlatformDirsABC): + """ + On Unix/Linux, we follow the + `XDG Basedir Spec <https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html>`_. The spec allows + overriding directories with environment variables. The examples show are the default values, alongside the name of + the environment variable that overrides them. Makes use of the + `appname <platformdirs.api.PlatformDirsABC.appname>`, + `version <platformdirs.api.PlatformDirsABC.version>`, + `multipath <platformdirs.api.PlatformDirsABC.multipath>`, + `opinion <platformdirs.api.PlatformDirsABC.opinion>`. + """ + + @property + def user_data_dir(self) -> str: + """ + :return: data directory tied to the user, e.g. ``~/.local/share/$appname/$version`` or + ``$XDG_DATA_HOME/$appname/$version`` + """ + path = os.environ.get("XDG_DATA_HOME", "") + if not path.strip(): + path = os.path.expanduser("~/.local/share") + return self._append_app_name_and_version(path) + + @property + def site_data_dir(self) -> str: + """ + :return: data directories shared by users (if `multipath <platformdirs.api.PlatformDirsABC.multipath>` is + enabled and ``XDG_DATA_DIR`` is set and a multi path the response is also a multi path separated by the OS + path separator), e.g. ``/usr/local/share/$appname/$version`` or ``/usr/share/$appname/$version`` + """ + # XDG default for $XDG_DATA_DIRS; only first, if multipath is False + path = os.environ.get("XDG_DATA_DIRS", "") + if not path.strip(): + path = f"/usr/local/share{os.pathsep}/usr/share" + return self._with_multi_path(path) + + def _with_multi_path(self, path: str) -> str: + path_list = path.split(os.pathsep) + if not self.multipath: + path_list = path_list[0:1] + path_list = [self._append_app_name_and_version(os.path.expanduser(p)) for p in path_list] + return os.pathsep.join(path_list) + + @property + def user_config_dir(self) -> str: + """ + :return: config directory tied to the user, e.g. ``~/.config/$appname/$version`` or + ``$XDG_CONFIG_HOME/$appname/$version`` + """ + path = os.environ.get("XDG_CONFIG_HOME", "") + if not path.strip(): + path = os.path.expanduser("~/.config") + return self._append_app_name_and_version(path) + + @property + def site_config_dir(self) -> str: + """ + :return: config directories shared by users (if `multipath <platformdirs.api.PlatformDirsABC.multipath>` + is enabled and ``XDG_DATA_DIR`` is set and a multi path the response is also a multi path separated by the OS + path separator), e.g. ``/etc/xdg/$appname/$version`` + """ + # XDG default for $XDG_CONFIG_DIRS only first, if multipath is False + path = os.environ.get("XDG_CONFIG_DIRS", "") + if not path.strip(): + path = "/etc/xdg" + return self._with_multi_path(path) + + @property + def user_cache_dir(self) -> str: + """ + :return: cache directory tied to the user, e.g. ``~/.cache/$appname/$version`` or + ``~/$XDG_CACHE_HOME/$appname/$version`` + """ + path = os.environ.get("XDG_CACHE_HOME", "") + if not path.strip(): + path = os.path.expanduser("~/.cache") + return self._append_app_name_and_version(path) + + @property + def user_state_dir(self) -> str: + """ + :return: state directory tied to the user, e.g. ``~/.local/state/$appname/$version`` or + ``$XDG_STATE_HOME/$appname/$version`` + """ + path = os.environ.get("XDG_STATE_HOME", "") + if not path.strip(): + path = os.path.expanduser("~/.local/state") + return self._append_app_name_and_version(path) + + @property + def user_log_dir(self) -> str: + """ + :return: log directory tied to the user, same as `user_data_dir` if not opinionated else ``log`` in it + """ + path = self.user_cache_dir + if self.opinion: + path = os.path.join(path, "log") + return path + + @property + def user_documents_dir(self) -> str: + """ + :return: documents directory tied to the user, e.g. ``~/Documents`` + """ + documents_dir = _get_user_dirs_folder("XDG_DOCUMENTS_DIR") + if documents_dir is None: + documents_dir = os.environ.get("XDG_DOCUMENTS_DIR", "").strip() + if not documents_dir: + documents_dir = os.path.expanduser("~/Documents") + + return documents_dir + + @property + def user_runtime_dir(self) -> str: + """ + :return: runtime directory tied to the user, e.g. ``/run/user/$(id -u)/$appname/$version`` or + ``$XDG_RUNTIME_DIR/$appname/$version`` + """ + path = os.environ.get("XDG_RUNTIME_DIR", "") + if not path.strip(): + path = f"/run/user/{getuid()}" + return self._append_app_name_and_version(path) + + @property + def site_data_path(self) -> Path: + """:return: data path shared by users. Only return first item, even if ``multipath`` is set to ``True``""" + return self._first_item_as_path_if_multipath(self.site_data_dir) + + @property + def site_config_path(self) -> Path: + """:return: config path shared by the users. Only return first item, even if ``multipath`` is set to ``True``""" + return self._first_item_as_path_if_multipath(self.site_config_dir) + + def _first_item_as_path_if_multipath(self, directory: str) -> Path: + if self.multipath: + # If multipath is True, the first path is returned. + directory = directory.split(os.pathsep)[0] + return Path(directory) + + +def _get_user_dirs_folder(key: str) -> Optional[str]: + """Return directory from user-dirs.dirs config file. See https://freedesktop.org/wiki/Software/xdg-user-dirs/""" + user_dirs_config_path = os.path.join(Unix().user_config_dir, "user-dirs.dirs") + if os.path.exists(user_dirs_config_path): + parser = ConfigParser() + + with open(user_dirs_config_path) as stream: + # Add fake section header, so ConfigParser doesn't complain + parser.read_string(f"[top]\n{stream.read()}") + + if key not in parser["top"]: + return None + + path = parser["top"][key].strip('"') + # Handle relative home paths + path = path.replace("$HOME", os.path.expanduser("~")) + return path + + return None + + +__all__ = [ + "Unix", +] diff --git a/pipenv/vendor/platformdirs/version.py b/pipenv/vendor/platformdirs/version.py new file mode 100644 index 00000000..3bdf9709 --- /dev/null +++ b/pipenv/vendor/platformdirs/version.py @@ -0,0 +1,4 @@ +""" Version information """ + +__version__ = "2.4.0" +__version_info__ = (2, 4, 0) diff --git a/pipenv/vendor/platformdirs/windows.py b/pipenv/vendor/platformdirs/windows.py new file mode 100644 index 00000000..c75cf99e --- /dev/null +++ b/pipenv/vendor/platformdirs/windows.py @@ -0,0 +1,180 @@ +import ctypes +import os +from functools import lru_cache +from typing import Callable, Optional + +from .api import PlatformDirsABC + + +class Windows(PlatformDirsABC): + """`MSDN on where to store app data files + <http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120>`_. + Makes use of the + `appname <platformdirs.api.PlatformDirsABC.appname>`, + `appauthor <platformdirs.api.PlatformDirsABC.appauthor>`, + `version <platformdirs.api.PlatformDirsABC.version>`, + `roaming <platformdirs.api.PlatformDirsABC.roaming>`, + `opinion <platformdirs.api.PlatformDirsABC.opinion>`.""" + + @property + def user_data_dir(self) -> str: + """ + :return: data directory tied to the user, e.g. + ``%USERPROFILE%\\AppData\\Local\\$appauthor\\$appname`` (not roaming) or + ``%USERPROFILE%\\AppData\\Roaming\\$appauthor\\$appname`` (roaming) + """ + const = "CSIDL_APPDATA" if self.roaming else "CSIDL_LOCAL_APPDATA" + path = os.path.normpath(get_win_folder(const)) + return self._append_parts(path) + + def _append_parts(self, path: str, *, opinion_value: Optional[str] = None) -> str: + params = [] + if self.appname: + if self.appauthor is not False: + author = self.appauthor or self.appname + params.append(author) + params.append(self.appname) + if opinion_value is not None and self.opinion: + params.append(opinion_value) + if self.version: + params.append(self.version) + return os.path.join(path, *params) + + @property + def site_data_dir(self) -> str: + """:return: data directory shared by users, e.g. ``C:\\ProgramData\\$appauthor\\$appname``""" + path = os.path.normpath(get_win_folder("CSIDL_COMMON_APPDATA")) + return self._append_parts(path) + + @property + def user_config_dir(self) -> str: + """:return: config directory tied to the user, same as `user_data_dir`""" + return self.user_data_dir + + @property + def site_config_dir(self) -> str: + """:return: config directory shared by the users, same as `site_data_dir`""" + return self.site_data_dir + + @property + def user_cache_dir(self) -> str: + """ + :return: cache directory tied to the user (if opinionated with ``Cache`` folder within ``$appname``) e.g. + ``%USERPROFILE%\\AppData\\Local\\$appauthor\\$appname\\Cache\\$version`` + """ + path = os.path.normpath(get_win_folder("CSIDL_LOCAL_APPDATA")) + return self._append_parts(path, opinion_value="Cache") + + @property + def user_state_dir(self) -> str: + """:return: state directory tied to the user, same as `user_data_dir`""" + return self.user_data_dir + + @property + def user_log_dir(self) -> str: + """ + :return: log directory tied to the user, same as `user_data_dir` if not opinionated else ``Logs`` in it + """ + path = self.user_data_dir + if self.opinion: + path = os.path.join(path, "Logs") + return path + + @property + def user_documents_dir(self) -> str: + """ + :return: documents directory tied to the user e.g. ``%USERPROFILE%\\Documents`` + """ + return os.path.normpath(get_win_folder("CSIDL_PERSONAL")) + + @property + def user_runtime_dir(self) -> str: + """ + :return: runtime directory tied to the user, e.g. + ``%USERPROFILE%\\AppData\\Local\\Temp\\$appauthor\\$appname`` + """ + path = os.path.normpath(os.path.join(get_win_folder("CSIDL_LOCAL_APPDATA"), "Temp")) + return self._append_parts(path) + + +def get_win_folder_from_env_vars(csidl_name: str) -> str: + """Get folder from environment variables.""" + if csidl_name == "CSIDL_PERSONAL": # does not have an environment name + return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Documents") + + env_var_name = { + "CSIDL_APPDATA": "APPDATA", + "CSIDL_COMMON_APPDATA": "ALLUSERSPROFILE", + "CSIDL_LOCAL_APPDATA": "LOCALAPPDATA", + }.get(csidl_name) + if env_var_name is None: + raise ValueError(f"Unknown CSIDL name: {csidl_name}") + result = os.environ.get(env_var_name) + if result is None: + raise ValueError(f"Unset environment variable: {env_var_name}") + return result + + +def get_win_folder_from_registry(csidl_name: str) -> str: + """Get folder from the registry. + + This is a fallback technique at best. I'm not sure if using the + registry for this guarantees us the correct answer for all CSIDL_* + names. + """ + shell_folder_name = { + "CSIDL_APPDATA": "AppData", + "CSIDL_COMMON_APPDATA": "Common AppData", + "CSIDL_LOCAL_APPDATA": "Local AppData", + "CSIDL_PERSONAL": "Personal", + }.get(csidl_name) + if shell_folder_name is None: + raise ValueError(f"Unknown CSIDL name: {csidl_name}") + + import winreg + + key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders") + directory, _ = winreg.QueryValueEx(key, shell_folder_name) + return str(directory) + + +def get_win_folder_via_ctypes(csidl_name: str) -> str: + """Get folder with ctypes.""" + csidl_const = { + "CSIDL_APPDATA": 26, + "CSIDL_COMMON_APPDATA": 35, + "CSIDL_LOCAL_APPDATA": 28, + "CSIDL_PERSONAL": 5, + }.get(csidl_name) + if csidl_const is None: + raise ValueError(f"Unknown CSIDL name: {csidl_name}") + + buf = ctypes.create_unicode_buffer(1024) + windll = getattr(ctypes, "windll") # noqa: B009 # using getattr to avoid false positive with mypy type checker + windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf) + + # Downgrade to short path name if it has highbit chars. + if any(ord(c) > 255 for c in buf): + buf2 = ctypes.create_unicode_buffer(1024) + if windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024): + buf = buf2 + + return buf.value + + +def _pick_get_win_folder() -> Callable[[str], str]: + if hasattr(ctypes, "windll"): + return get_win_folder_via_ctypes + try: + import winreg # noqa: F401 + except ImportError: + return get_win_folder_from_env_vars + else: + return get_win_folder_from_registry + + +get_win_folder = lru_cache(maxsize=None)(_pick_get_win_folder()) + +__all__ = [ + "Windows", +] diff --git a/pipenv/vendor/plette/__init__.py b/pipenv/vendor/plette/__init__.py index 5daf460c..3ca8aca3 100644 --- a/pipenv/vendor/plette/__init__.py +++ b/pipenv/vendor/plette/__init__.py @@ -3,7 +3,7 @@ __all__ = [ "Lockfile", "Pipfile", ] -__version__ = '0.2.3.dev0' +__version__ = '0.2.3' from .lockfiles import Lockfile from .pipfiles import Pipfile diff --git a/pipenv/vendor/plette/lockfiles.py b/pipenv/vendor/plette/lockfiles.py index 10df07e1..b6148510 100644 --- a/pipenv/vendor/plette/lockfiles.py +++ b/pipenv/vendor/plette/lockfiles.py @@ -8,7 +8,7 @@ try: except ImportError: import collections as collections_abc -import six +import pipenv.vendor.six as six from .models import DataView, Meta, PackageCollection diff --git a/pipenv/vendor/plette/models/base.py b/pipenv/vendor/plette/models/base.py index fad0d09e..7932afba 100644 --- a/pipenv/vendor/plette/models/base.py +++ b/pipenv/vendor/plette/models/base.py @@ -1,5 +1,5 @@ try: - import cerberus + import pipenv.vendor.cerberus as cerberus except ImportError: cerberus = None @@ -8,13 +8,24 @@ class ValidationError(ValueError): def __init__(self, value, validator): super(ValidationError, self).__init__(value) self.validator = validator + self.value = value + + def __str__(self): + return '{}\n{}'.format( + self.value, + '\n'.join( + '{}: {}'.format(k, e) + for k, errors in self.validator.errors.items() + for e in errors + ) + ) VALIDATORS = {} def validate(cls, data): - if not cerberus: # Skip validation if Cerberus is not available. + if not cerberus: # Skip validation if Cerberus is not available. return schema = cls.__SCHEMA__ key = id(schema) @@ -22,7 +33,7 @@ def validate(cls, data): v = VALIDATORS[key] except KeyError: v = VALIDATORS[key] = cerberus.Validator(schema, allow_unknown=True) - if v.validate(dict(data), normalize=False): + if v.validate(data, normalize=False): return raise ValidationError(data, v) @@ -33,6 +44,7 @@ class DataView(object): Validates the input mapping on creation. A subclass is expected to provide a `__SCHEMA__` class attribute specifying a validator schema. """ + def __init__(self, data): self.validate(data) self._data = data @@ -42,9 +54,11 @@ class DataView(object): def __eq__(self, other): if not isinstance(other, type(self)): - raise TypeError("cannot compare {0!r} with {1!r}".format( - type(self).__name__, type(other).__name__, - )) + raise TypeError( + "cannot compare {0!r} with {1!r}".format( + type(self).__name__, type(other).__name__ + ) + ) return self._data == other._data def __getitem__(self, key): @@ -78,6 +92,7 @@ class DataViewCollection(DataView): You should not instantiate an instance from this class, but from one of its subclasses instead. """ + item_class = None def __repr__(self): @@ -103,6 +118,7 @@ class DataViewMapping(DataViewCollection): The keys are primitive values, while values are instances of `item_class`. """ + @classmethod def validate(cls, data): for d in data.values(): @@ -126,6 +142,7 @@ class DataViewSequence(DataViewCollection): Each entry is an instance of `item_class`. """ + @classmethod def validate(cls, data): for d in data: diff --git a/pipenv/vendor/plette/models/packages.py b/pipenv/vendor/plette/models/packages.py index 5bb36215..53ee29e9 100644 --- a/pipenv/vendor/plette/models/packages.py +++ b/pipenv/vendor/plette/models/packages.py @@ -1,4 +1,4 @@ -import six +import pipenv.vendor.six as six from .base import DataView diff --git a/pipenv/vendor/plette/models/scripts.py b/pipenv/vendor/plette/models/scripts.py index 7f34d816..3b2e81e4 100644 --- a/pipenv/vendor/plette/models/scripts.py +++ b/pipenv/vendor/plette/models/scripts.py @@ -1,7 +1,7 @@ import re import shlex -import six +import pipenv.vendor.six as six from .base import DataView diff --git a/pipenv/vendor/plette/pipfiles.py b/pipenv/vendor/plette/pipfiles.py index 95f413de..f5f9ce29 100644 --- a/pipenv/vendor/plette/pipfiles.py +++ b/pipenv/vendor/plette/pipfiles.py @@ -3,8 +3,8 @@ from __future__ import unicode_literals import hashlib import json -import six -import tomlkit +import pipenv.vendor.six as six +import pipenv.vendor.tomlkit as tomlkit from .models import ( DataView, Hash, Requires, diff --git a/pipenv/vendor/ptyprocess/__init__.py b/pipenv/vendor/ptyprocess/__init__.py index e633d0cd..3a6268e8 100644 --- a/pipenv/vendor/ptyprocess/__init__.py +++ b/pipenv/vendor/ptyprocess/__init__.py @@ -1,4 +1,4 @@ """Run a subprocess in a pseudo terminal""" from .ptyprocess import PtyProcess, PtyProcessUnicode, PtyProcessError -__version__ = '0.6.0' +__version__ = '0.7.0' diff --git a/pipenv/vendor/ptyprocess/ptyprocess.py b/pipenv/vendor/ptyprocess/ptyprocess.py index 29b4e43b..78d19fdf 100644 --- a/pipenv/vendor/ptyprocess/ptyprocess.py +++ b/pipenv/vendor/ptyprocess/ptyprocess.py @@ -178,7 +178,7 @@ class PtyProcess(object): @classmethod def spawn( cls, argv, cwd=None, env=None, echo=True, preexec_fn=None, - dimensions=(24, 80)): + dimensions=(24, 80), pass_fds=()): '''Start the given command in a child process in a pseudo terminal. This does all the fork/exec type of stuff for a pty, and returns an @@ -190,6 +190,10 @@ class PtyProcess(object): Dimensions of the psuedoterminal used for the subprocess can be specified as a tuple (rows, cols), or the default (24, 80) will be used. + + By default, all file descriptors except 0, 1 and 2 are closed. This + behavior can be overridden with pass_fds, a list of file descriptors to + keep open between the parent and the child. ''' # Note that it is difficult for this method to fail. # You cannot detect if the child process cannot start. @@ -255,12 +259,14 @@ class PtyProcess(object): # Do not allow child to inherit open file descriptors from parent, # with the exception of the exec_err_pipe_write of the pipe + # and pass_fds. # Impose ceiling on max_fd: AIX bugfix for users with unlimited # nofiles where resource.RLIMIT_NOFILE is 2^63-1 and os.closerange() # occasionally raises out of range error max_fd = min(1048576, resource.getrlimit(resource.RLIMIT_NOFILE)[0]) - os.closerange(3, exec_err_pipe_write) - os.closerange(exec_err_pipe_write+1, max_fd) + spass_fds = sorted(set(pass_fds) | {exec_err_pipe_write}) + for pair in zip([2] + spass_fds, spass_fds + [max_fd]): + os.closerange(pair[0]+1, pair[1]) if cwd is not None: os.chdir(cwd) diff --git a/pipenv/vendor/pyparsing.py b/pipenv/vendor/pyparsing.py index ab804d53..961738f8 100644 --- a/pipenv/vendor/pyparsing.py +++ b/pipenv/vendor/pyparsing.py @@ -1,4 +1,4 @@ -#-*- coding: utf-8 -*- +# -*- coding: utf-8 -*- # module pyparsing.py # # Copyright (c) 2003-2019 Paul T. McGuire @@ -41,7 +41,7 @@ Here is a program to parse "Hello, World!" (or any greeting of the form (the :class:`'+'<ParserElement.__add__>` operators create :class:`And` expressions, and the strings are auto-converted to :class:`Literal` expressions):: - from pyparsing import Word, alphas + from pipenv.vendor.pyparsing import Word, alphas # define grammar of a greeting greet = Word(alphas) + "," + Word(alphas) + "!" @@ -87,14 +87,16 @@ classes inherit from. Use the docstrings for examples of how to: more complex ones - associate names with your parsed results using :class:`ParserElement.setResultsName` + - access the parsed data, which is returned as a :class:`ParseResults` + object - find some helpful expression short-cuts like :class:`delimitedList` and :class:`oneOf` - find more useful common expressions in the :class:`pyparsing_common` namespace class """ -__version__ = "2.3.1" -__versionTime__ = "09 Jan 2019 23:26 UTC" +__version__ = "2.4.7" +__versionTime__ = "30 Mar 2020 00:43 UTC" __author__ = "Paul McGuire <ptmcg@users.sourceforge.net>" import string @@ -109,6 +111,10 @@ import pprint import traceback import types from datetime import datetime +from operator import itemgetter +import itertools +from functools import wraps +from contextlib import contextmanager try: # Python 3 @@ -124,11 +130,11 @@ except ImportError: try: # Python 3 from collections.abc import Iterable - from collections.abc import MutableMapping + from collections.abc import MutableMapping, Mapping except ImportError: # Python 2.7 from collections import Iterable - from collections import MutableMapping + from collections import MutableMapping, Mapping try: from collections import OrderedDict as _OrderedDict @@ -143,29 +149,73 @@ try: except ImportError: class SimpleNamespace: pass +# version compatibility configuration +__compat__ = SimpleNamespace() +__compat__.__doc__ = """ + A cross-version compatibility configuration for pyparsing features that will be + released in a future version. By setting values in this configuration to True, + those features can be enabled in prior versions for compatibility development + and testing. -#~ sys.stderr.write( "testing pyparsing module, version %s, %s\n" % (__version__,__versionTime__ ) ) + - collect_all_And_tokens - flag to enable fix for Issue #63 that fixes erroneous grouping + of results names when an And expression is nested within an Or or MatchFirst; set to + True to enable bugfix released in pyparsing 2.3.0, or False to preserve + pre-2.3.0 handling of named results +""" +__compat__.collect_all_And_tokens = True -__all__ = [ -'And', 'CaselessKeyword', 'CaselessLiteral', 'CharsNotIn', 'Combine', 'Dict', 'Each', 'Empty', -'FollowedBy', 'Forward', 'GoToColumn', 'Group', 'Keyword', 'LineEnd', 'LineStart', 'Literal', -'PrecededBy', 'MatchFirst', 'NoMatch', 'NotAny', 'OneOrMore', 'OnlyOnce', 'Optional', 'Or', -'ParseBaseException', 'ParseElementEnhance', 'ParseException', 'ParseExpression', 'ParseFatalException', -'ParseResults', 'ParseSyntaxException', 'ParserElement', 'QuotedString', 'RecursiveGrammarException', -'Regex', 'SkipTo', 'StringEnd', 'StringStart', 'Suppress', 'Token', 'TokenConverter', -'White', 'Word', 'WordEnd', 'WordStart', 'ZeroOrMore', 'Char', -'alphanums', 'alphas', 'alphas8bit', 'anyCloseTag', 'anyOpenTag', 'cStyleComment', 'col', -'commaSeparatedList', 'commonHTMLEntity', 'countedArray', 'cppStyleComment', 'dblQuotedString', -'dblSlashComment', 'delimitedList', 'dictOf', 'downcaseTokens', 'empty', 'hexnums', -'htmlComment', 'javaStyleComment', 'line', 'lineEnd', 'lineStart', 'lineno', -'makeHTMLTags', 'makeXMLTags', 'matchOnlyAtCol', 'matchPreviousExpr', 'matchPreviousLiteral', -'nestedExpr', 'nullDebugAction', 'nums', 'oneOf', 'opAssoc', 'operatorPrecedence', 'printables', -'punc8bit', 'pythonStyleComment', 'quotedString', 'removeQuotes', 'replaceHTMLEntity', -'replaceWith', 'restOfLine', 'sglQuotedString', 'srange', 'stringEnd', -'stringStart', 'traceParseAction', 'unicodeString', 'upcaseTokens', 'withAttribute', -'indentedBlock', 'originalTextFor', 'ungroup', 'infixNotation','locatedExpr', 'withClass', -'CloseMatch', 'tokenMap', 'pyparsing_common', 'pyparsing_unicode', 'unicode_set', -] +__diag__ = SimpleNamespace() +__diag__.__doc__ = """ +Diagnostic configuration (all default to False) + - warn_multiple_tokens_in_named_alternation - flag to enable warnings when a results + name is defined on a MatchFirst or Or expression with one or more And subexpressions + (only warns if __compat__.collect_all_And_tokens is False) + - warn_ungrouped_named_tokens_in_collection - flag to enable warnings when a results + name is defined on a containing expression with ungrouped subexpressions that also + have results names + - warn_name_set_on_empty_Forward - flag to enable warnings whan a Forward is defined + with a results name, but has no contents defined + - warn_on_multiple_string_args_to_oneof - flag to enable warnings whan oneOf is + incorrectly called with multiple str arguments + - enable_debug_on_named_expressions - flag to auto-enable debug on all subsequent + calls to ParserElement.setName() +""" +__diag__.warn_multiple_tokens_in_named_alternation = False +__diag__.warn_ungrouped_named_tokens_in_collection = False +__diag__.warn_name_set_on_empty_Forward = False +__diag__.warn_on_multiple_string_args_to_oneof = False +__diag__.enable_debug_on_named_expressions = False +__diag__._all_names = [nm for nm in vars(__diag__) if nm.startswith("enable_") or nm.startswith("warn_")] + +def _enable_all_warnings(): + __diag__.warn_multiple_tokens_in_named_alternation = True + __diag__.warn_ungrouped_named_tokens_in_collection = True + __diag__.warn_name_set_on_empty_Forward = True + __diag__.warn_on_multiple_string_args_to_oneof = True +__diag__.enable_all_warnings = _enable_all_warnings + + +__all__ = ['__version__', '__versionTime__', '__author__', '__compat__', '__diag__', + 'And', 'CaselessKeyword', 'CaselessLiteral', 'CharsNotIn', 'Combine', 'Dict', 'Each', 'Empty', + 'FollowedBy', 'Forward', 'GoToColumn', 'Group', 'Keyword', 'LineEnd', 'LineStart', 'Literal', + 'PrecededBy', 'MatchFirst', 'NoMatch', 'NotAny', 'OneOrMore', 'OnlyOnce', 'Optional', 'Or', + 'ParseBaseException', 'ParseElementEnhance', 'ParseException', 'ParseExpression', 'ParseFatalException', + 'ParseResults', 'ParseSyntaxException', 'ParserElement', 'QuotedString', 'RecursiveGrammarException', + 'Regex', 'SkipTo', 'StringEnd', 'StringStart', 'Suppress', 'Token', 'TokenConverter', + 'White', 'Word', 'WordEnd', 'WordStart', 'ZeroOrMore', 'Char', + 'alphanums', 'alphas', 'alphas8bit', 'anyCloseTag', 'anyOpenTag', 'cStyleComment', 'col', + 'commaSeparatedList', 'commonHTMLEntity', 'countedArray', 'cppStyleComment', 'dblQuotedString', + 'dblSlashComment', 'delimitedList', 'dictOf', 'downcaseTokens', 'empty', 'hexnums', + 'htmlComment', 'javaStyleComment', 'line', 'lineEnd', 'lineStart', 'lineno', + 'makeHTMLTags', 'makeXMLTags', 'matchOnlyAtCol', 'matchPreviousExpr', 'matchPreviousLiteral', + 'nestedExpr', 'nullDebugAction', 'nums', 'oneOf', 'opAssoc', 'operatorPrecedence', 'printables', + 'punc8bit', 'pythonStyleComment', 'quotedString', 'removeQuotes', 'replaceHTMLEntity', + 'replaceWith', 'restOfLine', 'sglQuotedString', 'srange', 'stringEnd', + 'stringStart', 'traceParseAction', 'unicodeString', 'upcaseTokens', 'withAttribute', + 'indentedBlock', 'originalTextFor', 'ungroup', 'infixNotation', 'locatedExpr', 'withClass', + 'CloseMatch', 'tokenMap', 'pyparsing_common', 'pyparsing_unicode', 'unicode_set', + 'conditionAsParseAction', 're', + ] system_version = tuple(sys.version_info)[:3] PY_3 = system_version[0] == 3 @@ -190,7 +240,7 @@ else: < returns the unicode object | encodes it with the default encoding | ... >. """ - if isinstance(obj,unicode): + if isinstance(obj, unicode): return obj try: @@ -208,9 +258,10 @@ else: # build list of single arg builtins, tolerant of Python version, that can be used as parse actions singleArgBuiltins = [] import __builtin__ + for fname in "sum len sorted reversed list tuple set any all min max".split(): try: - singleArgBuiltins.append(getattr(__builtin__,fname)) + singleArgBuiltins.append(getattr(__builtin__, fname)) except AttributeError: continue @@ -221,23 +272,36 @@ def _xml_escape(data): # ampersand must be replaced first from_symbols = '&><"\'' - to_symbols = ('&'+s+';' for s in "amp gt lt quot apos".split()) - for from_,to_ in zip(from_symbols, to_symbols): + to_symbols = ('&' + s + ';' for s in "amp gt lt quot apos".split()) + for from_, to_ in zip(from_symbols, to_symbols): data = data.replace(from_, to_) return data -alphas = string.ascii_uppercase + string.ascii_lowercase -nums = "0123456789" -hexnums = nums + "ABCDEFabcdef" -alphanums = alphas + nums -_bslash = chr(92) +alphas = string.ascii_uppercase + string.ascii_lowercase +nums = "0123456789" +hexnums = nums + "ABCDEFabcdef" +alphanums = alphas + nums +_bslash = chr(92) printables = "".join(c for c in string.printable if c not in string.whitespace) + +def conditionAsParseAction(fn, message=None, fatal=False): + msg = message if message is not None else "failed user-defined condition" + exc_type = ParseFatalException if fatal else ParseException + fn = _trim_arity(fn) + + @wraps(fn) + def pa(s, l, t): + if not bool(fn(s, l, t)): + raise exc_type(s, l, msg) + + return pa + class ParseBaseException(Exception): """base exception class for all parsing runtime exceptions""" # Performance tuning: we construct a *lot* of these, so keep this # constructor as small and fast as possible - def __init__( self, pstr, loc=0, msg=None, elem=None ): + def __init__(self, pstr, loc=0, msg=None, elem=None): self.loc = loc if msg is None: self.msg = pstr @@ -256,27 +320,34 @@ class ParseBaseException(Exception): """ return cls(pe.pstr, pe.loc, pe.msg, pe.parserElement) - def __getattr__( self, aname ): + def __getattr__(self, aname): """supported attributes by name are: - lineno - returns the line number of the exception text - col - returns the column number of the exception text - line - returns the line containing the exception text """ - if( aname == "lineno" ): - return lineno( self.loc, self.pstr ) - elif( aname in ("col", "column") ): - return col( self.loc, self.pstr ) - elif( aname == "line" ): - return line( self.loc, self.pstr ) + if aname == "lineno": + return lineno(self.loc, self.pstr) + elif aname in ("col", "column"): + return col(self.loc, self.pstr) + elif aname == "line": + return line(self.loc, self.pstr) else: raise AttributeError(aname) - def __str__( self ): - return "%s (at char %d), (line:%d, col:%d)" % \ - ( self.msg, self.loc, self.lineno, self.column ) - def __repr__( self ): + def __str__(self): + if self.pstr: + if self.loc >= len(self.pstr): + foundstr = ', found end of text' + else: + foundstr = (', found %r' % self.pstr[self.loc:self.loc + 1]).replace(r'\\', '\\') + else: + foundstr = '' + return ("%s%s (at char %d), (line:%d, col:%d)" % + (self.msg, foundstr, self.loc, self.lineno, self.column)) + def __repr__(self): return _ustr(self) - def markInputline( self, markerString = ">!<" ): + def markInputline(self, markerString=">!<"): """Extracts the exception line from the input string, and marks the location of the exception with a special symbol. """ @@ -350,7 +421,7 @@ class ParseException(ParseBaseException): callers = inspect.getinnerframes(exc.__traceback__, context=depth) seen = set() for i, ff in enumerate(callers[-depth:]): - frm = ff.frame + frm = ff[0] f_self = frm.f_locals.get('self', None) if isinstance(f_self, ParserElement): @@ -412,21 +483,21 @@ class RecursiveGrammarException(Exception): """exception thrown by :class:`ParserElement.validate` if the grammar could be improperly recursive """ - def __init__( self, parseElementList ): + def __init__(self, parseElementList): self.parseElementTrace = parseElementList - def __str__( self ): + def __str__(self): return "RecursiveGrammarException: %s" % self.parseElementTrace class _ParseResultsWithOffset(object): - def __init__(self,p1,p2): - self.tup = (p1,p2) - def __getitem__(self,i): + def __init__(self, p1, p2): + self.tup = (p1, p2) + def __getitem__(self, i): return self.tup[i] def __repr__(self): return repr(self.tup[0]) - def setOffset(self,i): - self.tup = (self.tup[0],i) + def setOffset(self, i): + self.tup = (self.tup[0], i) class ParseResults(object): """Structured parse results, to provide multiple means of access to @@ -471,7 +542,7 @@ class ParseResults(object): - month: 12 - year: 1999 """ - def __new__(cls, toklist=None, name=None, asList=True, modal=True ): + def __new__(cls, toklist=None, name=None, asList=True, modal=True): if isinstance(toklist, cls): return toklist retobj = object.__new__(cls) @@ -480,7 +551,7 @@ class ParseResults(object): # Performance tuning: we construct a *lot* of these, so keep this # constructor as small and fast as possible - def __init__( self, toklist=None, name=None, asList=True, modal=True, isinstance=isinstance ): + def __init__(self, toklist=None, name=None, asList=True, modal=True, isinstance=isinstance): if self.__doinit: self.__doinit = False self.__name = None @@ -501,85 +572,93 @@ class ParseResults(object): if name is not None and name: if not modal: self.__accumNames[name] = 0 - if isinstance(name,int): - name = _ustr(name) # will always return a str, but use _ustr for consistency + if isinstance(name, int): + name = _ustr(name) # will always return a str, but use _ustr for consistency self.__name = name - if not (isinstance(toklist, (type(None), basestring, list)) and toklist in (None,'',[])): - if isinstance(toklist,basestring): - toklist = [ toklist ] + if not (isinstance(toklist, (type(None), basestring, list)) and toklist in (None, '', [])): + if isinstance(toklist, basestring): + toklist = [toklist] if asList: - if isinstance(toklist,ParseResults): + if isinstance(toklist, ParseResults): self[name] = _ParseResultsWithOffset(ParseResults(toklist.__toklist), 0) else: - self[name] = _ParseResultsWithOffset(ParseResults(toklist[0]),0) + self[name] = _ParseResultsWithOffset(ParseResults(toklist[0]), 0) self[name].__name = name else: try: self[name] = toklist[0] - except (KeyError,TypeError,IndexError): + except (KeyError, TypeError, IndexError): self[name] = toklist - def __getitem__( self, i ): - if isinstance( i, (int,slice) ): + def __getitem__(self, i): + if isinstance(i, (int, slice)): return self.__toklist[i] else: if i not in self.__accumNames: return self.__tokdict[i][-1][0] else: - return ParseResults([ v[0] for v in self.__tokdict[i] ]) + return ParseResults([v[0] for v in self.__tokdict[i]]) - def __setitem__( self, k, v, isinstance=isinstance ): - if isinstance(v,_ParseResultsWithOffset): - self.__tokdict[k] = self.__tokdict.get(k,list()) + [v] + def __setitem__(self, k, v, isinstance=isinstance): + if isinstance(v, _ParseResultsWithOffset): + self.__tokdict[k] = self.__tokdict.get(k, list()) + [v] sub = v[0] - elif isinstance(k,(int,slice)): + elif isinstance(k, (int, slice)): self.__toklist[k] = v sub = v else: - self.__tokdict[k] = self.__tokdict.get(k,list()) + [_ParseResultsWithOffset(v,0)] + self.__tokdict[k] = self.__tokdict.get(k, list()) + [_ParseResultsWithOffset(v, 0)] sub = v - if isinstance(sub,ParseResults): + if isinstance(sub, ParseResults): sub.__parent = wkref(self) - def __delitem__( self, i ): - if isinstance(i,(int,slice)): - mylen = len( self.__toklist ) + def __delitem__(self, i): + if isinstance(i, (int, slice)): + mylen = len(self.__toklist) del self.__toklist[i] # convert int to slice if isinstance(i, int): if i < 0: i += mylen - i = slice(i, i+1) + i = slice(i, i + 1) # get removed indices removed = list(range(*i.indices(mylen))) removed.reverse() # fixup indices in token dictionary - for name,occurrences in self.__tokdict.items(): + for name, occurrences in self.__tokdict.items(): for j in removed: for k, (value, position) in enumerate(occurrences): occurrences[k] = _ParseResultsWithOffset(value, position - (position > j)) else: del self.__tokdict[i] - def __contains__( self, k ): + def __contains__(self, k): return k in self.__tokdict - def __len__( self ): return len( self.__toklist ) - def __bool__(self): return ( not not self.__toklist ) + def __len__(self): + return len(self.__toklist) + + def __bool__(self): + return (not not self.__toklist) __nonzero__ = __bool__ - def __iter__( self ): return iter( self.__toklist ) - def __reversed__( self ): return iter( self.__toklist[::-1] ) - def _iterkeys( self ): + + def __iter__(self): + return iter(self.__toklist) + + def __reversed__(self): + return iter(self.__toklist[::-1]) + + def _iterkeys(self): if hasattr(self.__tokdict, "iterkeys"): return self.__tokdict.iterkeys() else: return iter(self.__tokdict) - def _itervalues( self ): + def _itervalues(self): return (self[k] for k in self._iterkeys()) - def _iteritems( self ): + def _iteritems(self): return ((k, self[k]) for k in self._iterkeys()) if PY_3: @@ -602,24 +681,24 @@ class ParseResults(object): iteritems = _iteritems """Returns an iterator of all named result key-value tuples (Python 2.x only).""" - def keys( self ): + def keys(self): """Returns all named result keys (as a list in Python 2.x, as an iterator in Python 3.x).""" return list(self.iterkeys()) - def values( self ): + def values(self): """Returns all named result values (as a list in Python 2.x, as an iterator in Python 3.x).""" return list(self.itervalues()) - def items( self ): + def items(self): """Returns all named result key-values (as a list of tuples in Python 2.x, as an iterator in Python 3.x).""" return list(self.iteritems()) - def haskeys( self ): + def haskeys(self): """Since keys() returns an iterator, this method is helpful in bypassing code that looks for the existence of any defined results names.""" return bool(self.__tokdict) - def pop( self, *args, **kwargs): + def pop(self, *args, **kwargs): """ Removes and returns item at specified index (default= ``last``). Supports both ``list`` and ``dict`` semantics for ``pop()``. If @@ -658,14 +737,14 @@ class ParseResults(object): """ if not args: args = [-1] - for k,v in kwargs.items(): + for k, v in kwargs.items(): if k == 'default': args = (args[0], v) else: raise TypeError("pop() got an unexpected keyword argument '%s'" % k) - if (isinstance(args[0], int) or - len(args) == 1 or - args[0] in self): + if (isinstance(args[0], int) + or len(args) == 1 + or args[0] in self): index = args[0] ret = self[index] del self[index] @@ -697,7 +776,7 @@ class ParseResults(object): else: return defaultValue - def insert( self, index, insStr ): + def insert(self, index, insStr): """ Inserts new element at location index in the list of parsed tokens. @@ -714,11 +793,11 @@ class ParseResults(object): """ self.__toklist.insert(index, insStr) # fixup indices in token dictionary - for name,occurrences in self.__tokdict.items(): + for name, occurrences in self.__tokdict.items(): for k, (value, position) in enumerate(occurrences): occurrences[k] = _ParseResultsWithOffset(value, position + (position > index)) - def append( self, item ): + def append(self, item): """ Add single element to end of ParseResults list of elements. @@ -733,7 +812,7 @@ class ParseResults(object): """ self.__toklist.append(item) - def extend( self, itemseq ): + def extend(self, itemseq): """ Add sequence of elements to end of ParseResults list of elements. @@ -748,78 +827,70 @@ class ParseResults(object): print(patt.addParseAction(make_palindrome).parseString("lskdj sdlkjf lksd")) # -> 'lskdjsdlkjflksddsklfjkldsjdksl' """ if isinstance(itemseq, ParseResults): - self += itemseq + self.__iadd__(itemseq) else: self.__toklist.extend(itemseq) - def clear( self ): + def clear(self): """ Clear all elements and results names. """ del self.__toklist[:] self.__tokdict.clear() - def __getattr__( self, name ): + def __getattr__(self, name): try: return self[name] except KeyError: return "" - if name in self.__tokdict: - if name not in self.__accumNames: - return self.__tokdict[name][-1][0] - else: - return ParseResults([ v[0] for v in self.__tokdict[name] ]) - else: - return "" - - def __add__( self, other ): + def __add__(self, other): ret = self.copy() ret += other return ret - def __iadd__( self, other ): + def __iadd__(self, other): if other.__tokdict: offset = len(self.__toklist) - addoffset = lambda a: offset if a<0 else a+offset + addoffset = lambda a: offset if a < 0 else a + offset otheritems = other.__tokdict.items() - otherdictitems = [(k, _ParseResultsWithOffset(v[0],addoffset(v[1])) ) - for (k,vlist) in otheritems for v in vlist] - for k,v in otherdictitems: + otherdictitems = [(k, _ParseResultsWithOffset(v[0], addoffset(v[1]))) + for k, vlist in otheritems for v in vlist] + for k, v in otherdictitems: self[k] = v - if isinstance(v[0],ParseResults): + if isinstance(v[0], ParseResults): v[0].__parent = wkref(self) self.__toklist += other.__toklist - self.__accumNames.update( other.__accumNames ) + self.__accumNames.update(other.__accumNames) return self def __radd__(self, other): - if isinstance(other,int) and other == 0: + if isinstance(other, int) and other == 0: # useful for merging many ParseResults using sum() builtin return self.copy() else: # this may raise a TypeError - so be it return other + self - def __repr__( self ): - return "(%s, %s)" % ( repr( self.__toklist ), repr( self.__tokdict ) ) + def __repr__(self): + return "(%s, %s)" % (repr(self.__toklist), repr(self.__tokdict)) - def __str__( self ): + def __str__(self): return '[' + ', '.join(_ustr(i) if isinstance(i, ParseResults) else repr(i) for i in self.__toklist) + ']' - def _asStringList( self, sep='' ): + def _asStringList(self, sep=''): out = [] for item in self.__toklist: if out and sep: out.append(sep) - if isinstance( item, ParseResults ): + if isinstance(item, ParseResults): out += item._asStringList() else: - out.append( _ustr(item) ) + out.append(_ustr(item)) return out - def asList( self ): + def asList(self): """ Returns the parse results as a nested list of matching tokens, all converted to strings. @@ -834,9 +905,9 @@ class ParseResults(object): result_list = result.asList() print(type(result_list), result_list) # -> <class 'list'> ['sldkj', 'lsdkj', 'sldkj'] """ - return [res.asList() if isinstance(res,ParseResults) else res for res in self.__toklist] + return [res.asList() if isinstance(res, ParseResults) else res for res in self.__toklist] - def asDict( self ): + def asDict(self): """ Returns the named parse results as a nested dictionary. @@ -870,27 +941,27 @@ class ParseResults(object): else: return obj - return dict((k,toItem(v)) for k,v in item_fn()) + return dict((k, toItem(v)) for k, v in item_fn()) - def copy( self ): + def copy(self): """ Returns a new copy of a :class:`ParseResults` object. """ - ret = ParseResults( self.__toklist ) + ret = ParseResults(self.__toklist) ret.__tokdict = dict(self.__tokdict.items()) ret.__parent = self.__parent - ret.__accumNames.update( self.__accumNames ) + ret.__accumNames.update(self.__accumNames) ret.__name = self.__name return ret - def asXML( self, doctag=None, namedItemsOnly=False, indent="", formatted=True ): + def asXML(self, doctag=None, namedItemsOnly=False, indent="", formatted=True): """ (Deprecated) Returns the parse results as XML. Tags are created for tokens and lists that have defined results names. """ nl = "\n" out = [] - namedItems = dict((v[1],k) for (k,vlist) in self.__tokdict.items() - for v in vlist) + namedItems = dict((v[1], k) for (k, vlist) in self.__tokdict.items() + for v in vlist) nextLevelIndent = indent + " " # collapse out indents if formatting is not desired @@ -912,20 +983,20 @@ class ParseResults(object): else: selfTag = "ITEM" - out += [ nl, indent, "<", selfTag, ">" ] + out += [nl, indent, "<", selfTag, ">"] - for i,res in enumerate(self.__toklist): - if isinstance(res,ParseResults): + for i, res in enumerate(self.__toklist): + if isinstance(res, ParseResults): if i in namedItems: - out += [ res.asXML(namedItems[i], - namedItemsOnly and doctag is None, - nextLevelIndent, - formatted)] + out += [res.asXML(namedItems[i], + namedItemsOnly and doctag is None, + nextLevelIndent, + formatted)] else: - out += [ res.asXML(None, - namedItemsOnly and doctag is None, - nextLevelIndent, - formatted)] + out += [res.asXML(None, + namedItemsOnly and doctag is None, + nextLevelIndent, + formatted)] else: # individual token, see if there is a name for it resTag = None @@ -937,16 +1008,16 @@ class ParseResults(object): else: resTag = "ITEM" xmlBodyText = _xml_escape(_ustr(res)) - out += [ nl, nextLevelIndent, "<", resTag, ">", - xmlBodyText, - "</", resTag, ">" ] + out += [nl, nextLevelIndent, "<", resTag, ">", + xmlBodyText, + "</", resTag, ">"] - out += [ nl, indent, "</", selfTag, ">" ] + out += [nl, indent, "</", selfTag, ">"] return "".join(out) - def __lookup(self,sub): - for k,vlist in self.__tokdict.items(): - for v,loc in vlist: + def __lookup(self, sub): + for k, vlist in self.__tokdict.items(): + for v, loc in vlist: if sub is v: return k return None @@ -984,14 +1055,14 @@ class ParseResults(object): return par.__lookup(self) else: return None - elif (len(self) == 1 and - len(self.__tokdict) == 1 and - next(iter(self.__tokdict.values()))[0][1] in (0,-1)): + elif (len(self) == 1 + and len(self.__tokdict) == 1 + and next(iter(self.__tokdict.values()))[0][1] in (0, -1)): return next(iter(self.__tokdict.keys())) else: return None - def dump(self, indent='', depth=0, full=True): + def dump(self, indent='', full=True, include_list=True, _depth=0): """ Diagnostic method for listing out the contents of a :class:`ParseResults`. Accepts an optional ``indent`` argument so @@ -1014,28 +1085,45 @@ class ParseResults(object): """ out = [] NL = '\n' - out.append( indent+_ustr(self.asList()) ) + if include_list: + out.append(indent + _ustr(self.asList())) + else: + out.append('') + if full: if self.haskeys(): - items = sorted((str(k), v) for k,v in self.items()) - for k,v in items: + items = sorted((str(k), v) for k, v in self.items()) + for k, v in items: if out: out.append(NL) - out.append( "%s%s- %s: " % (indent,(' '*depth), k) ) - if isinstance(v,ParseResults): + out.append("%s%s- %s: " % (indent, (' ' * _depth), k)) + if isinstance(v, ParseResults): if v: - out.append( v.dump(indent,depth+1) ) + out.append(v.dump(indent=indent, full=full, include_list=include_list, _depth=_depth + 1)) else: out.append(_ustr(v)) else: out.append(repr(v)) - elif any(isinstance(vv,ParseResults) for vv in self): + elif any(isinstance(vv, ParseResults) for vv in self): v = self - for i,vv in enumerate(v): - if isinstance(vv,ParseResults): - out.append("\n%s%s[%d]:\n%s%s%s" % (indent,(' '*(depth)),i,indent,(' '*(depth+1)),vv.dump(indent,depth+1) )) + for i, vv in enumerate(v): + if isinstance(vv, ParseResults): + out.append("\n%s%s[%d]:\n%s%s%s" % (indent, + (' ' * (_depth)), + i, + indent, + (' ' * (_depth + 1)), + vv.dump(indent=indent, + full=full, + include_list=include_list, + _depth=_depth + 1))) else: - out.append("\n%s%s[%d]:\n%s%s%s" % (indent,(' '*(depth)),i,indent,(' '*(depth+1)),_ustr(vv))) + out.append("\n%s%s[%d]:\n%s%s%s" % (indent, + (' ' * (_depth)), + i, + indent, + (' ' * (_depth + 1)), + _ustr(vv))) return "".join(out) @@ -1068,18 +1156,15 @@ class ParseResults(object): # add support for pickle protocol def __getstate__(self): - return ( self.__toklist, - ( self.__tokdict.copy(), - self.__parent is not None and self.__parent() or None, - self.__accumNames, - self.__name ) ) + return (self.__toklist, + (self.__tokdict.copy(), + self.__parent is not None and self.__parent() or None, + self.__accumNames, + self.__name)) - def __setstate__(self,state): + def __setstate__(self, state): self.__toklist = state[0] - (self.__tokdict, - par, - inAccumNames, - self.__name) = state[1] + self.__tokdict, par, inAccumNames, self.__name = state[1] self.__accumNames = {} self.__accumNames.update(inAccumNames) if par is not None: @@ -1091,11 +1176,39 @@ class ParseResults(object): return self.__toklist, self.__name, self.__asList, self.__modal def __dir__(self): - return (dir(type(self)) + list(self.keys())) + return dir(type(self)) + list(self.keys()) + + @classmethod + def from_dict(cls, other, name=None): + """ + Helper classmethod to construct a ParseResults from a dict, preserving the + name-value relations as results names. If an optional 'name' argument is + given, a nested ParseResults will be returned + """ + def is_iterable(obj): + try: + iter(obj) + except Exception: + return False + else: + if PY_3: + return not isinstance(obj, (str, bytes)) + else: + return not isinstance(obj, basestring) + + ret = cls([]) + for k, v in other.items(): + if isinstance(v, Mapping): + ret += cls.from_dict(v, name=k) + else: + ret += cls([v], name=k, asList=is_iterable(v)) + if name is not None: + ret = cls([ret], name=name) + return ret MutableMapping.register(ParseResults) -def col (loc,strg): +def col (loc, strg): """Returns current column within a string, counting newlines as line separators. The first column is number 1. @@ -1107,9 +1220,9 @@ def col (loc,strg): location, and line and column positions within the parsed string. """ s = strg - return 1 if 0<loc<len(s) and s[loc-1] == '\n' else loc - s.rfind("\n", 0, loc) + return 1 if 0 < loc < len(s) and s[loc-1] == '\n' else loc - s.rfind("\n", 0, loc) -def lineno(loc,strg): +def lineno(loc, strg): """Returns current line number within a string, counting newlines as line separators. The first line is number 1. @@ -1119,26 +1232,26 @@ def lineno(loc,strg): suggested methods to maintain a consistent view of the parsed string, the parse location, and line and column positions within the parsed string. """ - return strg.count("\n",0,loc) + 1 + return strg.count("\n", 0, loc) + 1 -def line( loc, strg ): +def line(loc, strg): """Returns the line of text containing loc within a string, counting newlines as line separators. """ lastCR = strg.rfind("\n", 0, loc) nextCR = strg.find("\n", loc) if nextCR >= 0: - return strg[lastCR+1:nextCR] + return strg[lastCR + 1:nextCR] else: - return strg[lastCR+1:] + return strg[lastCR + 1:] -def _defaultStartDebugAction( instring, loc, expr ): - print (("Match " + _ustr(expr) + " at loc " + _ustr(loc) + "(%d,%d)" % ( lineno(loc,instring), col(loc,instring) ))) +def _defaultStartDebugAction(instring, loc, expr): + print(("Match " + _ustr(expr) + " at loc " + _ustr(loc) + "(%d,%d)" % (lineno(loc, instring), col(loc, instring)))) -def _defaultSuccessDebugAction( instring, startloc, endloc, expr, toks ): - print ("Matched " + _ustr(expr) + " -> " + str(toks.asList())) +def _defaultSuccessDebugAction(instring, startloc, endloc, expr, toks): + print("Matched " + _ustr(expr) + " -> " + str(toks.asList())) -def _defaultExceptionDebugAction( instring, loc, expr, exc ): - print ("Exception raised:" + _ustr(exc)) +def _defaultExceptionDebugAction(instring, loc, expr, exc): + print("Exception raised:" + _ustr(exc)) def nullDebugAction(*args): """'Do-nothing' debug action, to suppress debugging output during parsing.""" @@ -1169,16 +1282,16 @@ def nullDebugAction(*args): 'decorator to trim function calls to match the arity of the target' def _trim_arity(func, maxargs=2): if func in singleArgBuiltins: - return lambda s,l,t: func(t) + return lambda s, l, t: func(t) limit = [0] foundArity = [False] # traceback return data structure changed in Py3.5 - normalize back to plain tuples - if system_version[:2] >= (3,5): + if system_version[:2] >= (3, 5): def extract_stack(limit=0): # special handling for Python 3.5.0 - extra deep call stack by 1 - offset = -3 if system_version == (3,5,0) else -2 - frame_summary = traceback.extract_stack(limit=-offset+limit-1)[offset] + offset = -3 if system_version == (3, 5, 0) else -2 + frame_summary = traceback.extract_stack(limit=-offset + limit - 1)[offset] return [frame_summary[:2]] def extract_tb(tb, limit=0): frames = traceback.extract_tb(tb, limit=limit) @@ -1195,7 +1308,7 @@ def _trim_arity(func, maxargs=2): # IF ANY CODE CHANGES, EVEN JUST COMMENTS OR BLANK LINES, BETWEEN THE NEXT LINE AND # THE CALL TO FUNC INSIDE WRAPPER, LINE_DIFF MUST BE MODIFIED!!!! this_line = extract_stack(limit=2)[-1] - pa_call_line_synth = (this_line[0], this_line[1]+LINE_DIFF) + pa_call_line_synth = (this_line[0], this_line[1] + LINE_DIFF) def wrapper(*args): while 1: @@ -1213,7 +1326,10 @@ def _trim_arity(func, maxargs=2): if not extract_tb(tb, limit=2)[-1][:2] == pa_call_line_synth: raise finally: - del tb + try: + del tb + except NameError: + pass if limit[0] <= maxargs: limit[0] += 1 @@ -1231,13 +1347,14 @@ def _trim_arity(func, maxargs=2): return wrapper + class ParserElement(object): """Abstract base level parser element class.""" DEFAULT_WHITE_CHARS = " \n\t\r" verbose_stacktrace = False @staticmethod - def setDefaultWhitespaceChars( chars ): + def setDefaultWhitespaceChars(chars): r""" Overrides the default whitespace chars @@ -1274,10 +1391,16 @@ class ParserElement(object): """ ParserElement._literalStringClass = cls - def __init__( self, savelist=False ): + @classmethod + def _trim_traceback(cls, tb): + while tb.tb_next: + tb = tb.tb_next + return tb + + def __init__(self, savelist=False): self.parseAction = list() self.failAction = None - #~ self.name = "<unknown>" # don't define self.name, let subclasses try/except upcall + # ~ self.name = "<unknown>" # don't define self.name, let subclasses try/except upcall self.strRepr = None self.resultsName = None self.saveAsList = savelist @@ -1292,12 +1415,12 @@ class ParserElement(object): self.mayIndexError = True # used to optimize exception handling for subclasses that don't advance parse index self.errmsg = "" self.modalResults = True # used to mark results names as modal (report only last) or cumulative (list all) - self.debugActions = ( None, None, None ) #custom debug actions + self.debugActions = (None, None, None) # custom debug actions self.re = None self.callPreparse = True # used to avoid redundant calls to preParse self.callDuringTry = False - def copy( self ): + def copy(self): """ Make a copy of this :class:`ParserElement`. Useful for defining different parse actions for the same parsing pattern, using copies of @@ -1306,8 +1429,8 @@ class ParserElement(object): Example:: integer = Word(nums).setParseAction(lambda toks: int(toks[0])) - integerK = integer.copy().addParseAction(lambda toks: toks[0]*1024) + Suppress("K") - integerM = integer.copy().addParseAction(lambda toks: toks[0]*1024*1024) + Suppress("M") + integerK = integer.copy().addParseAction(lambda toks: toks[0] * 1024) + Suppress("K") + integerM = integer.copy().addParseAction(lambda toks: toks[0] * 1024 * 1024) + Suppress("M") print(OneOrMore(integerK | integerM | integer).parseString("5K 100 640K 256M")) @@ -1317,16 +1440,16 @@ class ParserElement(object): Equivalent form of ``expr.copy()`` is just ``expr()``:: - integerM = integer().addParseAction(lambda toks: toks[0]*1024*1024) + Suppress("M") + integerM = integer().addParseAction(lambda toks: toks[0] * 1024 * 1024) + Suppress("M") """ - cpy = copy.copy( self ) + cpy = copy.copy(self) cpy.parseAction = self.parseAction[:] cpy.ignoreExprs = self.ignoreExprs[:] if self.copyDefaultWhiteChars: cpy.whiteChars = ParserElement.DEFAULT_WHITE_CHARS return cpy - def setName( self, name ): + def setName(self, name): """ Define name for this expression, makes debugging and exception messages clearer. @@ -1337,11 +1460,11 @@ class ParserElement(object): """ self.name = name self.errmsg = "Expected " + self.name - if hasattr(self,"exception"): - self.exception.msg = self.errmsg + if __diag__.enable_debug_on_named_expressions: + self.setDebug() return self - def setResultsName( self, name, listAllMatches=False ): + def setResultsName(self, name, listAllMatches=False): """ Define name for referencing matching tokens as a nested attribute of the returned parse results. @@ -1362,15 +1485,18 @@ class ParserElement(object): # equivalent form: date_str = integer("year") + '/' + integer("month") + '/' + integer("day") """ + return self._setResultsName(name, listAllMatches) + + def _setResultsName(self, name, listAllMatches=False): newself = self.copy() if name.endswith("*"): name = name[:-1] - listAllMatches=True + listAllMatches = True newself.resultsName = name newself.modalResults = not listAllMatches return newself - def setBreak(self,breakFlag = True): + def setBreak(self, breakFlag=True): """Method to invoke the Python pdb debugger when this element is about to be parsed. Set ``breakFlag`` to True to enable, False to disable. @@ -1379,20 +1505,21 @@ class ParserElement(object): _parseMethod = self._parse def breaker(instring, loc, doActions=True, callPreParse=True): import pdb + # this call to pdb.set_trace() is intentional, not a checkin error pdb.set_trace() - return _parseMethod( instring, loc, doActions, callPreParse ) + return _parseMethod(instring, loc, doActions, callPreParse) breaker._originalParseMethod = _parseMethod self._parse = breaker else: - if hasattr(self._parse,"_originalParseMethod"): + if hasattr(self._parse, "_originalParseMethod"): self._parse = self._parse._originalParseMethod return self - def setParseAction( self, *fns, **kwargs ): + def setParseAction(self, *fns, **kwargs): """ Define one or more actions to perform when successfully matching parse element definition. - Parse action fn is a callable method with 0-3 arguments, called as ``fn(s,loc,toks)`` , - ``fn(loc,toks)`` , ``fn(toks)`` , or just ``fn()`` , where: + Parse action fn is a callable method with 0-3 arguments, called as ``fn(s, loc, toks)`` , + ``fn(loc, toks)`` , ``fn(toks)`` , or just ``fn()`` , where: - s = the original string being parsed (see note below) - loc = the location of the matching substring @@ -1402,8 +1529,11 @@ class ParserElement(object): value from fn, and the modified list of tokens will replace the original. Otherwise, fn does not need to return any value. + If None is passed as the parse action, all previously added parse actions for this + expression are cleared. + Optional keyword arguments: - - callDuringTry = (default= ``False`` ) indicate if parse action should be run during lookaheads and alternate testing + - callDuringTry = (default= ``False``) indicate if parse action should be run during lookaheads and alternate testing Note: the default parsing behavior is to expand tabs in the input string before starting the parsing process. See :class:`parseString for more @@ -1425,11 +1555,16 @@ class ParserElement(object): # note that integer fields are now ints, not strings date_str.parseString("1999/12/31") # -> [1999, '/', 12, '/', 31] """ - self.parseAction = list(map(_trim_arity, list(fns))) - self.callDuringTry = kwargs.get("callDuringTry", False) + if list(fns) == [None,]: + self.parseAction = [] + else: + if not all(callable(fn) for fn in fns): + raise TypeError("parse actions must be callable") + self.parseAction = list(map(_trim_arity, list(fns))) + self.callDuringTry = kwargs.get("callDuringTry", False) return self - def addParseAction( self, *fns, **kwargs ): + def addParseAction(self, *fns, **kwargs): """ Add one or more parse actions to expression's list of parse actions. See :class:`setParseAction`. @@ -1457,21 +1592,17 @@ class ParserElement(object): result = date_str.parseString("1999/12/31") # -> Exception: Only support years 2000 and later (at char 0), (line:1, col:1) """ - msg = kwargs.get("message", "failed user-defined condition") - exc_type = ParseFatalException if kwargs.get("fatal", False) else ParseException for fn in fns: - fn = _trim_arity(fn) - def pa(s,l,t): - if not bool(fn(s,l,t)): - raise exc_type(s,l,msg) - self.parseAction.append(pa) + self.parseAction.append(conditionAsParseAction(fn, message=kwargs.get('message'), + fatal=kwargs.get('fatal', False))) + self.callDuringTry = self.callDuringTry or kwargs.get("callDuringTry", False) return self - def setFailAction( self, fn ): + def setFailAction(self, fn): """Define action to perform if parsing fails at this expression. Fail acton fn is a callable function that takes the arguments - ``fn(s,loc,expr,err)`` where: + ``fn(s, loc, expr, err)`` where: - s = string being parsed - loc = location where expression match was attempted and failed - expr = the parse expression that failed @@ -1481,22 +1612,22 @@ class ParserElement(object): self.failAction = fn return self - def _skipIgnorables( self, instring, loc ): + def _skipIgnorables(self, instring, loc): exprsFound = True while exprsFound: exprsFound = False for e in self.ignoreExprs: try: while 1: - loc,dummy = e._parse( instring, loc ) + loc, dummy = e._parse(instring, loc) exprsFound = True except ParseException: pass return loc - def preParse( self, instring, loc ): + def preParse(self, instring, loc): if self.ignoreExprs: - loc = self._skipIgnorables( instring, loc ) + loc = self._skipIgnorables(instring, loc) if self.skipWhitespace: wt = self.whiteChars @@ -1506,101 +1637,105 @@ class ParserElement(object): return loc - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): return loc, [] - def postParse( self, instring, loc, tokenlist ): + def postParse(self, instring, loc, tokenlist): return tokenlist - #~ @profile - def _parseNoCache( self, instring, loc, doActions=True, callPreParse=True ): - debugging = ( self.debug ) #and doActions ) + # ~ @profile + def _parseNoCache(self, instring, loc, doActions=True, callPreParse=True): + TRY, MATCH, FAIL = 0, 1, 2 + debugging = (self.debug) # and doActions) if debugging or self.failAction: - #~ print ("Match",self,"at loc",loc,"(%d,%d)" % ( lineno(loc,instring), col(loc,instring) )) - if (self.debugActions[0] ): - self.debugActions[0]( instring, loc, self ) - if callPreParse and self.callPreparse: - preloc = self.preParse( instring, loc ) - else: - preloc = loc - tokensStart = preloc + # ~ print ("Match", self, "at loc", loc, "(%d, %d)" % (lineno(loc, instring), col(loc, instring))) + if self.debugActions[TRY]: + self.debugActions[TRY](instring, loc, self) try: - try: - loc,tokens = self.parseImpl( instring, preloc, doActions ) - except IndexError: - raise ParseException( instring, len(instring), self.errmsg, self ) - except ParseBaseException as err: - #~ print ("Exception raised:", err) - if self.debugActions[2]: - self.debugActions[2]( instring, tokensStart, self, err ) + if callPreParse and self.callPreparse: + preloc = self.preParse(instring, loc) + else: + preloc = loc + tokensStart = preloc + if self.mayIndexError or preloc >= len(instring): + try: + loc, tokens = self.parseImpl(instring, preloc, doActions) + except IndexError: + raise ParseException(instring, len(instring), self.errmsg, self) + else: + loc, tokens = self.parseImpl(instring, preloc, doActions) + except Exception as err: + # ~ print ("Exception raised:", err) + if self.debugActions[FAIL]: + self.debugActions[FAIL](instring, tokensStart, self, err) if self.failAction: - self.failAction( instring, tokensStart, self, err ) + self.failAction(instring, tokensStart, self, err) raise else: if callPreParse and self.callPreparse: - preloc = self.preParse( instring, loc ) + preloc = self.preParse(instring, loc) else: preloc = loc tokensStart = preloc if self.mayIndexError or preloc >= len(instring): try: - loc,tokens = self.parseImpl( instring, preloc, doActions ) + loc, tokens = self.parseImpl(instring, preloc, doActions) except IndexError: - raise ParseException( instring, len(instring), self.errmsg, self ) + raise ParseException(instring, len(instring), self.errmsg, self) else: - loc,tokens = self.parseImpl( instring, preloc, doActions ) + loc, tokens = self.parseImpl(instring, preloc, doActions) - tokens = self.postParse( instring, loc, tokens ) + tokens = self.postParse(instring, loc, tokens) - retTokens = ParseResults( tokens, self.resultsName, asList=self.saveAsList, modal=self.modalResults ) + retTokens = ParseResults(tokens, self.resultsName, asList=self.saveAsList, modal=self.modalResults) if self.parseAction and (doActions or self.callDuringTry): if debugging: try: for fn in self.parseAction: try: - tokens = fn( instring, tokensStart, retTokens ) + tokens = fn(instring, tokensStart, retTokens) except IndexError as parse_action_exc: exc = ParseException("exception raised in parse action") exc.__cause__ = parse_action_exc raise exc if tokens is not None and tokens is not retTokens: - retTokens = ParseResults( tokens, + retTokens = ParseResults(tokens, self.resultsName, - asList=self.saveAsList and isinstance(tokens,(ParseResults,list)), - modal=self.modalResults ) - except ParseBaseException as err: - #~ print "Exception raised in user parse action:", err - if (self.debugActions[2] ): - self.debugActions[2]( instring, tokensStart, self, err ) + asList=self.saveAsList and isinstance(tokens, (ParseResults, list)), + modal=self.modalResults) + except Exception as err: + # ~ print "Exception raised in user parse action:", err + if self.debugActions[FAIL]: + self.debugActions[FAIL](instring, tokensStart, self, err) raise else: for fn in self.parseAction: try: - tokens = fn( instring, tokensStart, retTokens ) + tokens = fn(instring, tokensStart, retTokens) except IndexError as parse_action_exc: exc = ParseException("exception raised in parse action") exc.__cause__ = parse_action_exc raise exc if tokens is not None and tokens is not retTokens: - retTokens = ParseResults( tokens, + retTokens = ParseResults(tokens, self.resultsName, - asList=self.saveAsList and isinstance(tokens,(ParseResults,list)), - modal=self.modalResults ) + asList=self.saveAsList and isinstance(tokens, (ParseResults, list)), + modal=self.modalResults) if debugging: - #~ print ("Matched",self,"->",retTokens.asList()) - if (self.debugActions[1] ): - self.debugActions[1]( instring, tokensStart, loc, self, retTokens ) + # ~ print ("Matched", self, "->", retTokens.asList()) + if self.debugActions[MATCH]: + self.debugActions[MATCH](instring, tokensStart, loc, self, retTokens) return loc, retTokens - def tryParse( self, instring, loc ): + def tryParse(self, instring, loc): try: - return self._parse( instring, loc, doActions=False )[0] + return self._parse(instring, loc, doActions=False)[0] except ParseFatalException: - raise ParseException( instring, loc, self.errmsg, self) + raise ParseException(instring, loc, self.errmsg, self) def canParseNext(self, instring, loc): try: @@ -1697,7 +1832,7 @@ class ParserElement(object): # this method gets repeatedly called during backtracking with the same arguments - # we can cache these arguments and save ourselves the trouble of re-parsing the contained expression - def _parseCache( self, instring, loc, doActions=True, callPreParse=True ): + def _parseCache(self, instring, loc, doActions=True, callPreParse=True): HIT, MISS = 0, 1 lookup = (self, instring, loc, callPreParse, doActions) with ParserElement.packrat_cache_lock: @@ -1718,7 +1853,7 @@ class ParserElement(object): ParserElement.packrat_cache_stats[HIT] += 1 if isinstance(value, Exception): raise value - return (value[0], value[1].copy()) + return value[0], value[1].copy() _parse = _parseNoCache @@ -1752,7 +1887,7 @@ class ParserElement(object): Example:: - import pyparsing + import pipenv.vendor.pyparsing as pyparsing pyparsing.ParserElement.enablePackrat() """ if not ParserElement._packratEnabled: @@ -1763,12 +1898,16 @@ class ParserElement(object): ParserElement.packrat_cache = ParserElement._FifoCache(cache_size_limit) ParserElement._parse = ParserElement._parseCache - def parseString( self, instring, parseAll=False ): + def parseString(self, instring, parseAll=False): """ Execute the parse expression with the given string. This is the main interface to the client code, once the complete expression has been built. + Returns the parsed data as a :class:`ParseResults` object, which may be + accessed as a list, or as a dict or object with attributes if the given parser + includes results names. + If you want the grammar to require that the entire input string be successfully parsed, then set ``parseAll`` to True (equivalent to ending the grammar with ``StringEnd()``). @@ -1782,7 +1921,7 @@ class ParserElement(object): - calling ``parseWithTabs`` on your grammar before calling ``parseString`` (see :class:`parseWithTabs`) - - define your parse action using the full ``(s,loc,toks)`` signature, and + - define your parse action using the full ``(s, loc, toks)`` signature, and reference the input string using the parse action's ``s`` argument - explictly expand the tabs in your input string before calling ``parseString`` @@ -1795,27 +1934,29 @@ class ParserElement(object): ParserElement.resetCache() if not self.streamlined: self.streamline() - #~ self.saveAsList = True + # ~ self.saveAsList = True for e in self.ignoreExprs: e.streamline() if not self.keepTabs: instring = instring.expandtabs() try: - loc, tokens = self._parse( instring, 0 ) + loc, tokens = self._parse(instring, 0) if parseAll: - loc = self.preParse( instring, loc ) + loc = self.preParse(instring, loc) se = Empty() + StringEnd() - se._parse( instring, loc ) + se._parse(instring, loc) except ParseBaseException as exc: if ParserElement.verbose_stacktrace: raise else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace + # catch and re-raise exception from here, clearing out pyparsing internal stack trace + if getattr(exc, '__traceback__', None) is not None: + exc.__traceback__ = self._trim_traceback(exc.__traceback__) raise exc else: return tokens - def scanString( self, instring, maxMatches=_MAX_INT, overlap=False ): + def scanString(self, instring, maxMatches=_MAX_INT, overlap=False): """ Scan the input string for expression matches. Each match will return the matching tokens, start location, and end location. May be called with optional @@ -1830,7 +1971,7 @@ class ParserElement(object): source = "sldjf123lsdjjkf345sldkjf879lkjsfd987" print(source) - for tokens,start,end in Word(alphas).scanString(source): + for tokens, start, end in Word(alphas).scanString(source): print(' '*start + '^'*(end-start)) print(' '*start + tokens[0]) @@ -1862,16 +2003,16 @@ class ParserElement(object): try: while loc <= instrlen and matches < maxMatches: try: - preloc = preparseFn( instring, loc ) - nextLoc,tokens = parseFn( instring, preloc, callPreParse=False ) + preloc = preparseFn(instring, loc) + nextLoc, tokens = parseFn(instring, preloc, callPreParse=False) except ParseException: - loc = preloc+1 + loc = preloc + 1 else: if nextLoc > loc: matches += 1 yield tokens, preloc, nextLoc if overlap: - nextloc = preparseFn( instring, loc ) + nextloc = preparseFn(instring, loc) if nextloc > loc: loc = nextLoc else: @@ -1879,15 +2020,17 @@ class ParserElement(object): else: loc = nextLoc else: - loc = preloc+1 + loc = preloc + 1 except ParseBaseException as exc: if ParserElement.verbose_stacktrace: raise else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace + # catch and re-raise exception from here, clearing out pyparsing internal stack trace + if getattr(exc, '__traceback__', None) is not None: + exc.__traceback__ = self._trim_traceback(exc.__traceback__) raise exc - def transformString( self, instring ): + def transformString(self, instring): """ Extension to :class:`scanString`, to modify matching text with modified tokens that may be returned from a parse action. To use ``transformString``, define a grammar and @@ -1913,27 +2056,29 @@ class ParserElement(object): # keep string locs straight between transformString and scanString self.keepTabs = True try: - for t,s,e in self.scanString( instring ): - out.append( instring[lastE:s] ) + for t, s, e in self.scanString(instring): + out.append(instring[lastE:s]) if t: - if isinstance(t,ParseResults): + if isinstance(t, ParseResults): out += t.asList() - elif isinstance(t,list): + elif isinstance(t, list): out += t else: out.append(t) lastE = e out.append(instring[lastE:]) out = [o for o in out if o] - return "".join(map(_ustr,_flatten(out))) + return "".join(map(_ustr, _flatten(out))) except ParseBaseException as exc: if ParserElement.verbose_stacktrace: raise else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace + # catch and re-raise exception from here, clearing out pyparsing internal stack trace + if getattr(exc, '__traceback__', None) is not None: + exc.__traceback__ = self._trim_traceback(exc.__traceback__) raise exc - def searchString( self, instring, maxMatches=_MAX_INT ): + def searchString(self, instring, maxMatches=_MAX_INT): """ Another extension to :class:`scanString`, simplifying the access to the tokens found to match the given parse expression. May be called with optional @@ -1955,12 +2100,14 @@ class ParserElement(object): ['More', 'Iron', 'Lead', 'Gold', 'I', 'Electricity'] """ try: - return ParseResults([ t for t,s,e in self.scanString( instring, maxMatches ) ]) + return ParseResults([t for t, s, e in self.scanString(instring, maxMatches)]) except ParseBaseException as exc: if ParserElement.verbose_stacktrace: raise else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace + # catch and re-raise exception from here, clearing out pyparsing internal stack trace + if getattr(exc, '__traceback__', None) is not None: + exc.__traceback__ = self._trim_traceback(exc.__traceback__) raise exc def split(self, instring, maxsplit=_MAX_INT, includeSeparators=False): @@ -1981,14 +2128,14 @@ class ParserElement(object): """ splits = 0 last = 0 - for t,s,e in self.scanString(instring, maxMatches=maxsplit): + for t, s, e in self.scanString(instring, maxMatches=maxsplit): yield instring[last:s] if includeSeparators: yield t[0] last = e yield instring[last:] - def __add__(self, other ): + def __add__(self, other): """ Implementation of + operator - returns :class:`And`. Adding strings to a ParserElement converts them to :class:`Literal`s by default. @@ -2002,24 +2149,42 @@ class ParserElement(object): prints:: Hello, World! -> ['Hello', ',', 'World', '!'] - """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return And( [ self, other ] ) - def __radd__(self, other ): + ``...`` may be used as a parse expression as a short form of :class:`SkipTo`. + + Literal('start') + ... + Literal('end') + + is equivalent to: + + Literal('start') + SkipTo('end')("_skipped*") + Literal('end') + + Note that the skipped text is returned with '_skipped' as a results name, + and to support having multiple skips in the same parser, the value returned is + a list of all skipped text. + """ + if other is Ellipsis: + return _PendingSkip(self) + + if isinstance(other, basestring): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return And([self, other]) + + def __radd__(self, other): """ Implementation of + operator when left operand is not a :class:`ParserElement` """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): + if other is Ellipsis: + return SkipTo(self)("_skipped*") + self + + if isinstance(other, basestring): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) + SyntaxWarning, stacklevel=2) return None return other + self @@ -2027,64 +2192,70 @@ class ParserElement(object): """ Implementation of - operator, returns :class:`And` with error stop """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): + if isinstance(other, basestring): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) + SyntaxWarning, stacklevel=2) return None return self + And._ErrorStop() + other - def __rsub__(self, other ): + def __rsub__(self, other): """ Implementation of - operator when left operand is not a :class:`ParserElement` """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): + if isinstance(other, basestring): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) + SyntaxWarning, stacklevel=2) return None return other - self - def __mul__(self,other): + def __mul__(self, other): """ Implementation of * operator, allows use of ``expr * 3`` in place of ``expr + expr + expr``. Expressions may also me multiplied by a 2-integer - tuple, similar to ``{min,max}`` multipliers in regular expressions. Tuples + tuple, similar to ``{min, max}`` multipliers in regular expressions. Tuples may also include ``None`` as in: - - ``expr*(n,None)`` or ``expr*(n,)`` is equivalent + - ``expr*(n, None)`` or ``expr*(n, )`` is equivalent to ``expr*n + ZeroOrMore(expr)`` (read as "at least n instances of ``expr``") - - ``expr*(None,n)`` is equivalent to ``expr*(0,n)`` + - ``expr*(None, n)`` is equivalent to ``expr*(0, n)`` (read as "0 to n instances of ``expr``") - - ``expr*(None,None)`` is equivalent to ``ZeroOrMore(expr)`` - - ``expr*(1,None)`` is equivalent to ``OneOrMore(expr)`` + - ``expr*(None, None)`` is equivalent to ``ZeroOrMore(expr)`` + - ``expr*(1, None)`` is equivalent to ``OneOrMore(expr)`` - Note that ``expr*(None,n)`` does not raise an exception if + Note that ``expr*(None, n)`` does not raise an exception if more than n exprs exist in the input stream; that is, - ``expr*(None,n)`` does not enforce a maximum number of expr + ``expr*(None, n)`` does not enforce a maximum number of expr occurrences. If this behavior is desired, then write - ``expr*(None,n) + ~expr`` + ``expr*(None, n) + ~expr`` """ - if isinstance(other,int): - minElements, optElements = other,0 - elif isinstance(other,tuple): + if other is Ellipsis: + other = (0, None) + elif isinstance(other, tuple) and other[:1] == (Ellipsis,): + other = ((0, ) + other[1:] + (None,))[:2] + + if isinstance(other, int): + minElements, optElements = other, 0 + elif isinstance(other, tuple): + other = tuple(o if o is not Ellipsis else None for o in other) other = (other + (None, None))[:2] if other[0] is None: other = (0, other[1]) - if isinstance(other[0],int) and other[1] is None: + if isinstance(other[0], int) and other[1] is None: if other[0] == 0: return ZeroOrMore(self) if other[0] == 1: return OneOrMore(self) else: - return self*other[0] + ZeroOrMore(self) - elif isinstance(other[0],int) and isinstance(other[1],int): + return self * other[0] + ZeroOrMore(self) + elif isinstance(other[0], int) and isinstance(other[1], int): minElements, optElements = other optElements -= minElements else: - raise TypeError("cannot multiply 'ParserElement' and ('%s','%s') objects", type(other[0]),type(other[1])) + raise TypeError("cannot multiply 'ParserElement' and ('%s', '%s') objects", type(other[0]), type(other[1])) else: raise TypeError("cannot multiply 'ParserElement' and '%s' objects", type(other)) @@ -2093,108 +2264,152 @@ class ParserElement(object): if optElements < 0: raise ValueError("second tuple value must be greater or equal to first tuple value") if minElements == optElements == 0: - raise ValueError("cannot multiply ParserElement by 0 or (0,0)") + raise ValueError("cannot multiply ParserElement by 0 or (0, 0)") - if (optElements): + if optElements: def makeOptionalList(n): - if n>1: - return Optional(self + makeOptionalList(n-1)) + if n > 1: + return Optional(self + makeOptionalList(n - 1)) else: return Optional(self) if minElements: if minElements == 1: ret = self + makeOptionalList(optElements) else: - ret = And([self]*minElements) + makeOptionalList(optElements) + ret = And([self] * minElements) + makeOptionalList(optElements) else: ret = makeOptionalList(optElements) else: if minElements == 1: ret = self else: - ret = And([self]*minElements) + ret = And([self] * minElements) return ret def __rmul__(self, other): return self.__mul__(other) - def __or__(self, other ): + def __or__(self, other): """ Implementation of | operator - returns :class:`MatchFirst` """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return MatchFirst( [ self, other ] ) + if other is Ellipsis: + return _PendingSkip(self, must_skip=True) - def __ror__(self, other ): + if isinstance(other, basestring): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return MatchFirst([self, other]) + + def __ror__(self, other): """ Implementation of | operator when left operand is not a :class:`ParserElement` """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): + if isinstance(other, basestring): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) + SyntaxWarning, stacklevel=2) return None return other | self - def __xor__(self, other ): + def __xor__(self, other): """ Implementation of ^ operator - returns :class:`Or` """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): + if isinstance(other, basestring): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) + SyntaxWarning, stacklevel=2) return None - return Or( [ self, other ] ) + return Or([self, other]) - def __rxor__(self, other ): + def __rxor__(self, other): """ Implementation of ^ operator when left operand is not a :class:`ParserElement` """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): + if isinstance(other, basestring): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) + SyntaxWarning, stacklevel=2) return None return other ^ self - def __and__(self, other ): + def __and__(self, other): """ Implementation of & operator - returns :class:`Each` """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): + if isinstance(other, basestring): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) + SyntaxWarning, stacklevel=2) return None - return Each( [ self, other ] ) + return Each([self, other]) - def __rand__(self, other ): + def __rand__(self, other): """ Implementation of & operator when left operand is not a :class:`ParserElement` """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): + if isinstance(other, basestring): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) + SyntaxWarning, stacklevel=2) return None return other & self - def __invert__( self ): + def __invert__(self): """ Implementation of ~ operator - returns :class:`NotAny` """ - return NotAny( self ) + return NotAny(self) + + def __iter__(self): + # must implement __iter__ to override legacy use of sequential access to __getitem__ to + # iterate over a sequence + raise TypeError('%r object is not iterable' % self.__class__.__name__) + + def __getitem__(self, key): + """ + use ``[]`` indexing notation as a short form for expression repetition: + - ``expr[n]`` is equivalent to ``expr*n`` + - ``expr[m, n]`` is equivalent to ``expr*(m, n)`` + - ``expr[n, ...]`` or ``expr[n,]`` is equivalent + to ``expr*n + ZeroOrMore(expr)`` + (read as "at least n instances of ``expr``") + - ``expr[..., n]`` is equivalent to ``expr*(0, n)`` + (read as "0 to n instances of ``expr``") + - ``expr[...]`` and ``expr[0, ...]`` are equivalent to ``ZeroOrMore(expr)`` + - ``expr[1, ...]`` is equivalent to ``OneOrMore(expr)`` + ``None`` may be used in place of ``...``. + + Note that ``expr[..., n]`` and ``expr[m, n]``do not raise an exception + if more than ``n`` ``expr``s exist in the input stream. If this behavior is + desired, then write ``expr[..., n] + ~expr``. + """ + + # convert single arg keys to tuples + try: + if isinstance(key, str): + key = (key,) + iter(key) + except TypeError: + key = (key, key) + + if len(key) > 2: + warnings.warn("only 1 or 2 index arguments supported ({0}{1})".format(key[:5], + '... [{0}]'.format(len(key)) + if len(key) > 5 else '')) + + # clip to 2 elements + ret = self * tuple(key[:2]) + return ret def __call__(self, name=None): """ @@ -2208,22 +2423,22 @@ class ParserElement(object): Example:: # these are equivalent - userdata = Word(alphas).setResultsName("name") + Word(nums+"-").setResultsName("socsecno") - userdata = Word(alphas)("name") + Word(nums+"-")("socsecno") + userdata = Word(alphas).setResultsName("name") + Word(nums + "-").setResultsName("socsecno") + userdata = Word(alphas)("name") + Word(nums + "-")("socsecno") """ if name is not None: - return self.setResultsName(name) + return self._setResultsName(name) else: return self.copy() - def suppress( self ): + def suppress(self): """ Suppresses the output of this :class:`ParserElement`; useful to keep punctuation from cluttering up returned output. """ - return Suppress( self ) + return Suppress(self) - def leaveWhitespace( self ): + def leaveWhitespace(self): """ Disables the skipping of whitespace before matching the characters in the :class:`ParserElement`'s defined pattern. This is normally only used internally by @@ -2232,7 +2447,7 @@ class ParserElement(object): self.skipWhitespace = False return self - def setWhitespaceChars( self, chars ): + def setWhitespaceChars(self, chars): """ Overrides the default whitespace chars """ @@ -2241,7 +2456,7 @@ class ParserElement(object): self.copyDefaultWhiteChars = False return self - def parseWithTabs( self ): + def parseWithTabs(self): """ Overrides default behavior to expand ``<TAB>``s to spaces before parsing the input string. Must be called before ``parseString`` when the input grammar contains elements that @@ -2250,7 +2465,7 @@ class ParserElement(object): self.keepTabs = True return self - def ignore( self, other ): + def ignore(self, other): """ Define expression to be ignored (e.g., comments) while doing pattern matching; may be called repeatedly, to define multiple comment or other @@ -2267,14 +2482,14 @@ class ParserElement(object): if isinstance(other, basestring): other = Suppress(other) - if isinstance( other, Suppress ): + if isinstance(other, Suppress): if other not in self.ignoreExprs: self.ignoreExprs.append(other) else: - self.ignoreExprs.append( Suppress( other.copy() ) ) + self.ignoreExprs.append(Suppress(other.copy())) return self - def setDebugActions( self, startAction, successAction, exceptionAction ): + def setDebugActions(self, startAction, successAction, exceptionAction): """ Enable display of debugging messages while doing pattern matching. """ @@ -2284,7 +2499,7 @@ class ParserElement(object): self.debug = True return self - def setDebug( self, flag=True ): + def setDebug(self, flag=True): """ Enable display of debugging messages while doing pattern matching. Set ``flag`` to True to enable, False to disable. @@ -2322,32 +2537,32 @@ class ParserElement(object): name created for the :class:`Word` expression without calling ``setName`` is ``"W:(ABCD...)"``. """ if flag: - self.setDebugActions( _defaultStartDebugAction, _defaultSuccessDebugAction, _defaultExceptionDebugAction ) + self.setDebugActions(_defaultStartDebugAction, _defaultSuccessDebugAction, _defaultExceptionDebugAction) else: self.debug = False return self - def __str__( self ): + def __str__(self): return self.name - def __repr__( self ): + def __repr__(self): return _ustr(self) - def streamline( self ): + def streamline(self): self.streamlined = True self.strRepr = None return self - def checkRecursion( self, parseElementList ): + def checkRecursion(self, parseElementList): pass - def validate( self, validateTrace=[] ): + def validate(self, validateTrace=None): """ Check defined expressions for valid structure, check for infinite recursive definitions. """ - self.checkRecursion( [] ) + self.checkRecursion([]) - def parseFile( self, file_or_filename, parseAll=False ): + def parseFile(self, file_or_filename, parseAll=False): """ Execute the parse expression on the given file or filename. If a filename is specified (instead of a file object), @@ -2364,27 +2579,30 @@ class ParserElement(object): if ParserElement.verbose_stacktrace: raise else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace + # catch and re-raise exception from here, clearing out pyparsing internal stack trace + if getattr(exc, '__traceback__', None) is not None: + exc.__traceback__ = self._trim_traceback(exc.__traceback__) raise exc - def __eq__(self,other): - if isinstance(other, ParserElement): - return self is other or vars(self) == vars(other) + def __eq__(self, other): + if self is other: + return True elif isinstance(other, basestring): return self.matches(other) - else: - return super(ParserElement,self)==other + elif isinstance(other, ParserElement): + return vars(self) == vars(other) + return False - def __ne__(self,other): + def __ne__(self, other): return not (self == other) def __hash__(self): - return hash(id(self)) + return id(self) - def __req__(self,other): + def __req__(self, other): return self == other - def __rne__(self,other): + def __rne__(self, other): return not (self == other) def matches(self, testString, parseAll=True): @@ -2408,7 +2626,8 @@ class ParserElement(object): return False def runTests(self, tests, parseAll=True, comment='#', - fullDump=True, printResults=True, failureTests=False, postParse=None): + fullDump=True, printResults=True, failureTests=False, postParse=None, + file=None): """ Execute the parse expression on a series of test strings, showing each test, the parsed results or where the parse failed. Quick and easy way to @@ -2425,6 +2644,8 @@ class ParserElement(object): - failureTests - (default= ``False``) indicates if these tests are expected to fail parsing - postParse - (default= ``None``) optional callback for successful parse results; called as `fn(test_string, parse_results)` and returns a string to be added to the test output + - file - (default=``None``) optional file-like object to which test output will be written; + if None, will default to ``sys.stdout`` Returns: a (success, results) tuple, where success indicates that all tests succeeded (or failed if ``failureTests`` is True), and the results contain a list of lines of each @@ -2504,37 +2725,34 @@ class ParserElement(object): tests = list(map(str.strip, tests.rstrip().splitlines())) if isinstance(comment, basestring): comment = Literal(comment) + if file is None: + file = sys.stdout + print_ = file.write + allResults = [] comments = [] success = True + NL = Literal(r'\n').addParseAction(replaceWith('\n')).ignore(quotedString) + BOM = u'\ufeff' for t in tests: if comment is not None and comment.matches(t, False) or comments and not t: comments.append(t) continue if not t: continue - out = ['\n'.join(comments), t] + out = ['\n' + '\n'.join(comments) if comments else '', t] comments = [] try: # convert newline marks to actual newlines, and strip leading BOM if present - t = t.replace(r'\n','\n').lstrip('\ufeff') + t = NL.transformString(t.lstrip(BOM)) result = self.parseString(t, parseAll=parseAll) - out.append(result.dump(full=fullDump)) - success = success and not failureTests - if postParse is not None: - try: - pp_value = postParse(t, result) - if pp_value is not None: - out.append(str(pp_value)) - except Exception as e: - out.append("{0} failed: {1}: {2}".format(postParse.__name__, type(e).__name__, e)) except ParseBaseException as pe: fatal = "(FATAL)" if isinstance(pe, ParseFatalException) else "" if '\n' in t: out.append(line(pe.loc, t)) - out.append(' '*(col(pe.loc,t)-1) + '^' + fatal) + out.append(' ' * (col(pe.loc, t) - 1) + '^' + fatal) else: - out.append(' '*pe.loc + '^' + fatal) + out.append(' ' * pe.loc + '^' + fatal) out.append("FAIL: " + str(pe)) success = success and failureTests result = pe @@ -2542,30 +2760,80 @@ class ParserElement(object): out.append("FAIL-EXCEPTION: " + str(exc)) success = success and failureTests result = exc + else: + success = success and not failureTests + if postParse is not None: + try: + pp_value = postParse(t, result) + if pp_value is not None: + if isinstance(pp_value, ParseResults): + out.append(pp_value.dump()) + else: + out.append(str(pp_value)) + else: + out.append(result.dump()) + except Exception as e: + out.append(result.dump(full=fullDump)) + out.append("{0} failed: {1}: {2}".format(postParse.__name__, type(e).__name__, e)) + else: + out.append(result.dump(full=fullDump)) if printResults: if fullDump: out.append('') - print('\n'.join(out)) + print_('\n'.join(out)) allResults.append((t, result)) return success, allResults +class _PendingSkip(ParserElement): + # internal placeholder class to hold a place were '...' is added to a parser element, + # once another ParserElement is added, this placeholder will be replaced with a SkipTo + def __init__(self, expr, must_skip=False): + super(_PendingSkip, self).__init__() + self.strRepr = str(expr + Empty()).replace('Empty', '...') + self.name = self.strRepr + self.anchor = expr + self.must_skip = must_skip + + def __add__(self, other): + skipper = SkipTo(other).setName("...")("_skipped*") + if self.must_skip: + def must_skip(t): + if not t._skipped or t._skipped.asList() == ['']: + del t[0] + t.pop("_skipped", None) + def show_skip(t): + if t._skipped.asList()[-1:] == ['']: + skipped = t.pop('_skipped') + t['_skipped'] = 'missing <' + repr(self.anchor) + '>' + return (self.anchor + skipper().addParseAction(must_skip) + | skipper().addParseAction(show_skip)) + other + + return self.anchor + skipper + other + + def __repr__(self): + return self.strRepr + + def parseImpl(self, *args): + raise Exception("use of `...` expression without following SkipTo target expression") + + class Token(ParserElement): """Abstract :class:`ParserElement` subclass, for defining atomic matching patterns. """ - def __init__( self ): - super(Token,self).__init__( savelist=False ) + def __init__(self): + super(Token, self).__init__(savelist=False) class Empty(Token): """An empty token, will always match. """ - def __init__( self ): - super(Empty,self).__init__() + def __init__(self): + super(Empty, self).__init__() self.name = "Empty" self.mayReturnEmpty = True self.mayIndexError = False @@ -2574,14 +2842,14 @@ class Empty(Token): class NoMatch(Token): """A token that will never match. """ - def __init__( self ): - super(NoMatch,self).__init__() + def __init__(self): + super(NoMatch, self).__init__() self.name = "NoMatch" self.mayReturnEmpty = True self.mayIndexError = False self.errmsg = "Unmatchable token" - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): raise ParseException(instring, loc, self.errmsg, self) @@ -2599,8 +2867,8 @@ class Literal(Token): For keyword matching (force word break before and after the matched string), use :class:`Keyword` or :class:`CaselessKeyword`. """ - def __init__( self, matchString ): - super(Literal,self).__init__() + def __init__(self, matchString): + super(Literal, self).__init__() self.match = matchString self.matchLen = len(matchString) try: @@ -2614,15 +2882,22 @@ class Literal(Token): self.mayReturnEmpty = False self.mayIndexError = False - # Performance tuning: this routine gets called a *lot* - # if this is a single character match string and the first character matches, - # short-circuit as quickly as possible, and avoid calling startswith - #~ @profile - def parseImpl( self, instring, loc, doActions=True ): - if (instring[loc] == self.firstMatchChar and - (self.matchLen==1 or instring.startswith(self.match,loc)) ): - return loc+self.matchLen, self.match + # Performance tuning: modify __class__ to select + # a parseImpl optimized for single-character check + if self.matchLen == 1 and type(self) is Literal: + self.__class__ = _SingleCharLiteral + + def parseImpl(self, instring, loc, doActions=True): + if instring[loc] == self.firstMatchChar and instring.startswith(self.match, loc): + return loc + self.matchLen, self.match raise ParseException(instring, loc, self.errmsg, self) + +class _SingleCharLiteral(Literal): + def parseImpl(self, instring, loc, doActions=True): + if instring[loc] == self.firstMatchChar: + return loc + 1, self.match + raise ParseException(instring, loc, self.errmsg, self) + _L = Literal ParserElement._literalStringClass = Literal @@ -2651,10 +2926,10 @@ class Keyword(Token): For case-insensitive matching, use :class:`CaselessKeyword`. """ - DEFAULT_KEYWORD_CHARS = alphanums+"_$" + DEFAULT_KEYWORD_CHARS = alphanums + "_$" - def __init__( self, matchString, identChars=None, caseless=False ): - super(Keyword,self).__init__() + def __init__(self, matchString, identChars=None, caseless=False): + super(Keyword, self).__init__() if identChars is None: identChars = Keyword.DEFAULT_KEYWORD_CHARS self.match = matchString @@ -2663,7 +2938,7 @@ class Keyword(Token): self.firstMatchChar = matchString[0] except IndexError: warnings.warn("null string passed to Keyword; use Empty() instead", - SyntaxWarning, stacklevel=2) + SyntaxWarning, stacklevel=2) self.name = '"%s"' % self.match self.errmsg = "Expected " + self.name self.mayReturnEmpty = False @@ -2674,27 +2949,32 @@ class Keyword(Token): identChars = identChars.upper() self.identChars = set(identChars) - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): if self.caseless: - if ( (instring[ loc:loc+self.matchLen ].upper() == self.caselessmatch) and - (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen].upper() not in self.identChars) and - (loc == 0 or instring[loc-1].upper() not in self.identChars) ): - return loc+self.matchLen, self.match + if ((instring[loc:loc + self.matchLen].upper() == self.caselessmatch) + and (loc >= len(instring) - self.matchLen + or instring[loc + self.matchLen].upper() not in self.identChars) + and (loc == 0 + or instring[loc - 1].upper() not in self.identChars)): + return loc + self.matchLen, self.match + else: - if (instring[loc] == self.firstMatchChar and - (self.matchLen==1 or instring.startswith(self.match,loc)) and - (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen] not in self.identChars) and - (loc == 0 or instring[loc-1] not in self.identChars) ): - return loc+self.matchLen, self.match + if instring[loc] == self.firstMatchChar: + if ((self.matchLen == 1 or instring.startswith(self.match, loc)) + and (loc >= len(instring) - self.matchLen + or instring[loc + self.matchLen] not in self.identChars) + and (loc == 0 or instring[loc - 1] not in self.identChars)): + return loc + self.matchLen, self.match + raise ParseException(instring, loc, self.errmsg, self) def copy(self): - c = super(Keyword,self).copy() + c = super(Keyword, self).copy() c.identChars = Keyword.DEFAULT_KEYWORD_CHARS return c @staticmethod - def setDefaultKeywordChars( chars ): + def setDefaultKeywordChars(chars): """Overrides the default Keyword chars """ Keyword.DEFAULT_KEYWORD_CHARS = chars @@ -2710,16 +2990,16 @@ class CaselessLiteral(Literal): (Contrast with example for :class:`CaselessKeyword`.) """ - def __init__( self, matchString ): - super(CaselessLiteral,self).__init__( matchString.upper() ) + def __init__(self, matchString): + super(CaselessLiteral, self).__init__(matchString.upper()) # Preserve the defining literal. self.returnString = matchString self.name = "'%s'" % self.returnString self.errmsg = "Expected " + self.name - def parseImpl( self, instring, loc, doActions=True ): - if instring[ loc:loc+self.matchLen ].upper() == self.match: - return loc+self.matchLen, self.returnString + def parseImpl(self, instring, loc, doActions=True): + if instring[loc:loc + self.matchLen].upper() == self.match: + return loc + self.matchLen, self.returnString raise ParseException(instring, loc, self.errmsg, self) class CaselessKeyword(Keyword): @@ -2732,8 +3012,8 @@ class CaselessKeyword(Keyword): (Contrast with example for :class:`CaselessLiteral`.) """ - def __init__( self, matchString, identChars=None ): - super(CaselessKeyword,self).__init__( matchString, identChars, caseless=True ) + def __init__(self, matchString, identChars=None): + super(CaselessKeyword, self).__init__(matchString, identChars, caseless=True) class CloseMatch(Token): """A variation on :class:`Literal` which matches "close" matches, @@ -2769,7 +3049,7 @@ class CloseMatch(Token): patt.parseString("ATCAXCGAAXGGA") # -> (['ATCAXCGAAXGGA'], {'mismatches': [[4, 9]], 'original': ['ATCATCGAATGGA']}) """ def __init__(self, match_string, maxMismatches=1): - super(CloseMatch,self).__init__() + super(CloseMatch, self).__init__() self.name = match_string self.match_string = match_string self.maxMismatches = maxMismatches @@ -2777,7 +3057,7 @@ class CloseMatch(Token): self.mayIndexError = False self.mayReturnEmpty = False - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): start = loc instrlen = len(instring) maxloc = start + len(self.match_string) @@ -2788,8 +3068,8 @@ class CloseMatch(Token): mismatches = [] maxMismatches = self.maxMismatches - for match_stringloc,s_m in enumerate(zip(instring[loc:maxloc], self.match_string)): - src,mat = s_m + for match_stringloc, s_m in enumerate(zip(instring[loc:maxloc], match_string)): + src, mat = s_m if src != mat: mismatches.append(match_stringloc) if len(mismatches) > maxMismatches: @@ -2797,7 +3077,7 @@ class CloseMatch(Token): else: loc = match_stringloc + 1 results = ParseResults([instring[start:loc]]) - results['original'] = self.match_string + results['original'] = match_string results['mismatches'] = mismatches return loc, results @@ -2849,7 +3129,7 @@ class Word(Token): capital_word = Word(alphas.upper(), alphas.lower()) # hostnames are alphanumeric, with leading alpha, and '-' - hostname = Word(alphas, alphanums+'-') + hostname = Word(alphas, alphanums + '-') # roman numeral (not a strict parser, accepts invalid mix of characters) roman = Word("IVXLCDM") @@ -2857,15 +3137,16 @@ class Word(Token): # any string of non-whitespace characters, except for ',' csv_value = Word(printables, excludeChars=",") """ - def __init__( self, initChars, bodyChars=None, min=1, max=0, exact=0, asKeyword=False, excludeChars=None ): - super(Word,self).__init__() + def __init__(self, initChars, bodyChars=None, min=1, max=0, exact=0, asKeyword=False, excludeChars=None): + super(Word, self).__init__() if excludeChars: + excludeChars = set(excludeChars) initChars = ''.join(c for c in initChars if c not in excludeChars) if bodyChars: bodyChars = ''.join(c for c in bodyChars if c not in excludeChars) self.initCharsOrig = initChars self.initChars = set(initChars) - if bodyChars : + if bodyChars: self.bodyCharsOrig = bodyChars self.bodyChars = set(bodyChars) else: @@ -2893,34 +3174,28 @@ class Word(Token): self.mayIndexError = False self.asKeyword = asKeyword - if ' ' not in self.initCharsOrig+self.bodyCharsOrig and (min==1 and max==0 and exact==0): + if ' ' not in self.initCharsOrig + self.bodyCharsOrig and (min == 1 and max == 0 and exact == 0): if self.bodyCharsOrig == self.initCharsOrig: self.reString = "[%s]+" % _escapeRegexRangeChars(self.initCharsOrig) elif len(self.initCharsOrig) == 1: - self.reString = "%s[%s]*" % \ - (re.escape(self.initCharsOrig), - _escapeRegexRangeChars(self.bodyCharsOrig),) + self.reString = "%s[%s]*" % (re.escape(self.initCharsOrig), + _escapeRegexRangeChars(self.bodyCharsOrig),) else: - self.reString = "[%s][%s]*" % \ - (_escapeRegexRangeChars(self.initCharsOrig), - _escapeRegexRangeChars(self.bodyCharsOrig),) + self.reString = "[%s][%s]*" % (_escapeRegexRangeChars(self.initCharsOrig), + _escapeRegexRangeChars(self.bodyCharsOrig),) if self.asKeyword: - self.reString = r"\b"+self.reString+r"\b" + self.reString = r"\b" + self.reString + r"\b" + try: - self.re = re.compile( self.reString ) + self.re = re.compile(self.reString) except Exception: self.re = None + else: + self.re_match = self.re.match + self.__class__ = _WordRegex - def parseImpl( self, instring, loc, doActions=True ): - if self.re: - result = self.re.match(instring,loc) - if not result: - raise ParseException(instring, loc, self.errmsg, self) - - loc = result.end() - return loc, result.group() - - if not(instring[ loc ] in self.initChars): + def parseImpl(self, instring, loc, doActions=True): + if instring[loc] not in self.initChars: raise ParseException(instring, loc, self.errmsg, self) start = loc @@ -2928,17 +3203,18 @@ class Word(Token): instrlen = len(instring) bodychars = self.bodyChars maxloc = start + self.maxLen - maxloc = min( maxloc, instrlen ) + maxloc = min(maxloc, instrlen) while loc < maxloc and instring[loc] in bodychars: loc += 1 throwException = False if loc - start < self.minLen: throwException = True - if self.maxSpecified and loc < instrlen and instring[loc] in bodychars: + elif self.maxSpecified and loc < instrlen and instring[loc] in bodychars: throwException = True - if self.asKeyword: - if (start>0 and instring[start-1] in bodychars) or (loc<instrlen and instring[loc] in bodychars): + elif self.asKeyword: + if (start > 0 and instring[start - 1] in bodychars + or loc < instrlen and instring[loc] in bodychars): throwException = True if throwException: @@ -2946,38 +3222,49 @@ class Word(Token): return loc, instring[start:loc] - def __str__( self ): + def __str__(self): try: - return super(Word,self).__str__() + return super(Word, self).__str__() except Exception: pass - if self.strRepr is None: def charsAsStr(s): - if len(s)>4: - return s[:4]+"..." + if len(s) > 4: + return s[:4] + "..." else: return s - if ( self.initCharsOrig != self.bodyCharsOrig ): - self.strRepr = "W:(%s,%s)" % ( charsAsStr(self.initCharsOrig), charsAsStr(self.bodyCharsOrig) ) + if self.initCharsOrig != self.bodyCharsOrig: + self.strRepr = "W:(%s, %s)" % (charsAsStr(self.initCharsOrig), charsAsStr(self.bodyCharsOrig)) else: self.strRepr = "W:(%s)" % charsAsStr(self.initCharsOrig) return self.strRepr +class _WordRegex(Word): + def parseImpl(self, instring, loc, doActions=True): + result = self.re_match(instring, loc) + if not result: + raise ParseException(instring, loc, self.errmsg, self) -class Char(Word): + loc = result.end() + return loc, result.group() + + +class Char(_WordRegex): """A short-cut class for defining ``Word(characters, exact=1)``, when defining a match of any single character in a string of characters. """ - def __init__(self, charset): - super(Char, self).__init__(charset, exact=1) - self.reString = "[%s]" % _escapeRegexRangeChars(self.initCharsOrig) - self.re = re.compile( self.reString ) + def __init__(self, charset, asKeyword=False, excludeChars=None): + super(Char, self).__init__(charset, exact=1, asKeyword=asKeyword, excludeChars=excludeChars) + self.reString = "[%s]" % _escapeRegexRangeChars(''.join(self.initChars)) + if asKeyword: + self.reString = r"\b%s\b" % self.reString + self.re = re.compile(self.reString) + self.re_match = self.re.match class Regex(Token): @@ -2987,26 +3274,35 @@ class Regex(Token): If the given regex contains named groups (defined using ``(?P<name>...)``), these will be preserved as named parse results. + If instead of the Python stdlib re module you wish to use a different RE module + (such as the `regex` module), you can replace it by either building your + Regex object with a compiled RE that was compiled using regex: + Example:: realnum = Regex(r"[+-]?\d+\.\d*") date = Regex(r'(?P<year>\d{4})-(?P<month>\d\d?)-(?P<day>\d\d?)') # ref: https://stackoverflow.com/questions/267399/how-do-you-match-only-valid-roman-numerals-with-a-regular-expression roman = Regex(r"M{0,4}(CM|CD|D?{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})") + + # use regex module instead of stdlib re module to construct a Regex using + # a compiled regular expression + import regex + parser = pp.Regex(regex.compile(r'[0-9]')) + """ - compiledREtype = type(re.compile("[A-Z]")) - def __init__( self, pattern, flags=0, asGroupList=False, asMatch=False): + def __init__(self, pattern, flags=0, asGroupList=False, asMatch=False): """The parameters ``pattern`` and ``flags`` are passed to the ``re.compile()`` function as-is. See the Python `re module <https://docs.python.org/3/library/re.html>`_ module for an explanation of the acceptable patterns and flags. """ - super(Regex,self).__init__() + super(Regex, self).__init__() if isinstance(pattern, basestring): if not pattern: warnings.warn("null string passed to Regex; use Empty() instead", - SyntaxWarning, stacklevel=2) + SyntaxWarning, stacklevel=2) self.pattern = pattern self.flags = flags @@ -3016,46 +3312,64 @@ class Regex(Token): self.reString = self.pattern except sre_constants.error: warnings.warn("invalid pattern (%s) passed to Regex" % pattern, - SyntaxWarning, stacklevel=2) + SyntaxWarning, stacklevel=2) raise - elif isinstance(pattern, Regex.compiledREtype): + elif hasattr(pattern, 'pattern') and hasattr(pattern, 'match'): self.re = pattern - self.pattern = \ - self.reString = str(pattern) + self.pattern = self.reString = pattern.pattern self.flags = flags else: - raise ValueError("Regex may only be constructed with a string or a compiled RE object") + raise TypeError("Regex may only be constructed with a string or a compiled RE object") + + self.re_match = self.re.match self.name = _ustr(self) self.errmsg = "Expected " + self.name self.mayIndexError = False - self.mayReturnEmpty = True + self.mayReturnEmpty = self.re_match("") is not None self.asGroupList = asGroupList self.asMatch = asMatch + if self.asGroupList: + self.parseImpl = self.parseImplAsGroupList + if self.asMatch: + self.parseImpl = self.parseImplAsMatch - def parseImpl( self, instring, loc, doActions=True ): - result = self.re.match(instring,loc) + def parseImpl(self, instring, loc, doActions=True): + result = self.re_match(instring, loc) if not result: raise ParseException(instring, loc, self.errmsg, self) loc = result.end() - if self.asMatch: - ret = result - elif self.asGroupList: - ret = result.groups() - else: - ret = ParseResults(result.group()) - d = result.groupdict() - if d: - for k, v in d.items(): - ret[k] = v - return loc,ret + ret = ParseResults(result.group()) + d = result.groupdict() + if d: + for k, v in d.items(): + ret[k] = v + return loc, ret - def __str__( self ): + def parseImplAsGroupList(self, instring, loc, doActions=True): + result = self.re_match(instring, loc) + if not result: + raise ParseException(instring, loc, self.errmsg, self) + + loc = result.end() + ret = result.groups() + return loc, ret + + def parseImplAsMatch(self, instring, loc, doActions=True): + result = self.re_match(instring, loc) + if not result: + raise ParseException(instring, loc, self.errmsg, self) + + loc = result.end() + ret = result + return loc, ret + + def __str__(self): try: - return super(Regex,self).__str__() + return super(Regex, self).__str__() except Exception: pass @@ -3065,7 +3379,7 @@ class Regex(Token): return self.strRepr def sub(self, repl): - """ + r""" Return Regex with an attached parse action to transform the parsed result as if called using `re.sub(expr, repl, string) <https://docs.python.org/3/library/re.html#re.sub>`_. @@ -3077,12 +3391,12 @@ class Regex(Token): """ if self.asGroupList: warnings.warn("cannot use sub() with Regex(asGroupList=True)", - SyntaxWarning, stacklevel=2) + SyntaxWarning, stacklevel=2) raise SyntaxError() if self.asMatch and callable(repl): warnings.warn("cannot use sub() with a callable with Regex(asMatch=True)", - SyntaxWarning, stacklevel=2) + SyntaxWarning, stacklevel=2) raise SyntaxError() if self.asMatch: @@ -3102,20 +3416,20 @@ class QuotedString(Token): - quoteChar - string of one or more characters defining the quote delimiting string - escChar - character to escape quotes, typically backslash - (default= ``None`` ) + (default= ``None``) - escQuote - special quote sequence to escape an embedded quote string (such as SQL's ``""`` to escape an embedded ``"``) - (default= ``None`` ) + (default= ``None``) - multiline - boolean indicating whether quotes can span - multiple lines (default= ``False`` ) + multiple lines (default= ``False``) - unquoteResults - boolean indicating whether the matched text - should be unquoted (default= ``True`` ) + should be unquoted (default= ``True``) - endQuoteChar - string of one or more characters defining the end of the quote delimited string (default= ``None`` => same as quoteChar) - convertWhitespaceEscapes - convert escaped whitespace (``'\t'``, ``'\n'``, etc.) to actual whitespace - (default= ``True`` ) + (default= ``True``) Example:: @@ -3132,13 +3446,14 @@ class QuotedString(Token): [['This is the "quote"']] [['This is the quote with "embedded" quotes']] """ - def __init__( self, quoteChar, escChar=None, escQuote=None, multiline=False, unquoteResults=True, endQuoteChar=None, convertWhitespaceEscapes=True): - super(QuotedString,self).__init__() + def __init__(self, quoteChar, escChar=None, escQuote=None, multiline=False, + unquoteResults=True, endQuoteChar=None, convertWhitespaceEscapes=True): + super(QuotedString, self).__init__() # remove white space from quote chars - wont work anyway quoteChar = quoteChar.strip() if not quoteChar: - warnings.warn("quoteChar cannot be the empty string",SyntaxWarning,stacklevel=2) + warnings.warn("quoteChar cannot be the empty string", SyntaxWarning, stacklevel=2) raise SyntaxError() if endQuoteChar is None: @@ -3146,7 +3461,7 @@ class QuotedString(Token): else: endQuoteChar = endQuoteChar.strip() if not endQuoteChar: - warnings.warn("endQuoteChar cannot be the empty string",SyntaxWarning,stacklevel=2) + warnings.warn("endQuoteChar cannot be the empty string", SyntaxWarning, stacklevel=2) raise SyntaxError() self.quoteChar = quoteChar @@ -3161,35 +3476,34 @@ class QuotedString(Token): if multiline: self.flags = re.MULTILINE | re.DOTALL - self.pattern = r'%s(?:[^%s%s]' % \ - ( re.escape(self.quoteChar), - _escapeRegexRangeChars(self.endQuoteChar[0]), - (escChar is not None and _escapeRegexRangeChars(escChar) or '') ) + self.pattern = r'%s(?:[^%s%s]' % (re.escape(self.quoteChar), + _escapeRegexRangeChars(self.endQuoteChar[0]), + (escChar is not None and _escapeRegexRangeChars(escChar) or '')) else: self.flags = 0 - self.pattern = r'%s(?:[^%s\n\r%s]' % \ - ( re.escape(self.quoteChar), - _escapeRegexRangeChars(self.endQuoteChar[0]), - (escChar is not None and _escapeRegexRangeChars(escChar) or '') ) + self.pattern = r'%s(?:[^%s\n\r%s]' % (re.escape(self.quoteChar), + _escapeRegexRangeChars(self.endQuoteChar[0]), + (escChar is not None and _escapeRegexRangeChars(escChar) or '')) if len(self.endQuoteChar) > 1: self.pattern += ( '|(?:' + ')|(?:'.join("%s[^%s]" % (re.escape(self.endQuoteChar[:i]), - _escapeRegexRangeChars(self.endQuoteChar[i])) - for i in range(len(self.endQuoteChar)-1,0,-1)) + ')' - ) + _escapeRegexRangeChars(self.endQuoteChar[i])) + for i in range(len(self.endQuoteChar) - 1, 0, -1)) + ')') + if escQuote: self.pattern += (r'|(?:%s)' % re.escape(escQuote)) if escChar: self.pattern += (r'|(?:%s.)' % re.escape(escChar)) - self.escCharReplacePattern = re.escape(self.escChar)+"(.)" + self.escCharReplacePattern = re.escape(self.escChar) + "(.)" self.pattern += (r')*%s' % re.escape(self.endQuoteChar)) try: self.re = re.compile(self.pattern, self.flags) self.reString = self.pattern + self.re_match = self.re.match except sre_constants.error: warnings.warn("invalid pattern (%s) passed to Regex" % self.pattern, - SyntaxWarning, stacklevel=2) + SyntaxWarning, stacklevel=2) raise self.name = _ustr(self) @@ -3197,8 +3511,8 @@ class QuotedString(Token): self.mayIndexError = False self.mayReturnEmpty = True - def parseImpl( self, instring, loc, doActions=True ): - result = instring[loc] == self.firstQuoteChar and self.re.match(instring,loc) or None + def parseImpl(self, instring, loc, doActions=True): + result = instring[loc] == self.firstQuoteChar and self.re_match(instring, loc) or None if not result: raise ParseException(instring, loc, self.errmsg, self) @@ -3208,18 +3522,18 @@ class QuotedString(Token): if self.unquoteResults: # strip off quotes - ret = ret[self.quoteCharLen:-self.endQuoteCharLen] + ret = ret[self.quoteCharLen: -self.endQuoteCharLen] - if isinstance(ret,basestring): + if isinstance(ret, basestring): # replace escaped whitespace if '\\' in ret and self.convertWhitespaceEscapes: ws_map = { - r'\t' : '\t', - r'\n' : '\n', - r'\f' : '\f', - r'\r' : '\r', + r'\t': '\t', + r'\n': '\n', + r'\f': '\f', + r'\r': '\r', } - for wslit,wschar in ws_map.items(): + for wslit, wschar in ws_map.items(): ret = ret.replace(wslit, wschar) # replace escaped characters @@ -3232,9 +3546,9 @@ class QuotedString(Token): return loc, ret - def __str__( self ): + def __str__(self): try: - return super(QuotedString,self).__str__() + return super(QuotedString, self).__str__() except Exception: pass @@ -3264,15 +3578,14 @@ class CharsNotIn(Token): ['dkls', 'lsdkjf', 's12 34', '@!#', '213'] """ - def __init__( self, notChars, min=1, max=0, exact=0 ): - super(CharsNotIn,self).__init__() + def __init__(self, notChars, min=1, max=0, exact=0): + super(CharsNotIn, self).__init__() self.skipWhitespace = False self.notChars = notChars if min < 1: - raise ValueError( - "cannot specify a minimum length < 1; use " + - "Optional(CharsNotIn()) if zero-length char group is permitted") + raise ValueError("cannot specify a minimum length < 1; use " + "Optional(CharsNotIn()) if zero-length char group is permitted") self.minLen = min @@ -3287,19 +3600,18 @@ class CharsNotIn(Token): self.name = _ustr(self) self.errmsg = "Expected " + self.name - self.mayReturnEmpty = ( self.minLen == 0 ) + self.mayReturnEmpty = (self.minLen == 0) self.mayIndexError = False - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): if instring[loc] in self.notChars: raise ParseException(instring, loc, self.errmsg, self) start = loc loc += 1 notchars = self.notChars - maxlen = min( start+self.maxLen, len(instring) ) - while loc < maxlen and \ - (instring[loc] not in notchars): + maxlen = min(start + self.maxLen, len(instring)) + while loc < maxlen and instring[loc] not in notchars: loc += 1 if loc - start < self.minLen: @@ -3307,7 +3619,7 @@ class CharsNotIn(Token): return loc, instring[start:loc] - def __str__( self ): + def __str__(self): try: return super(CharsNotIn, self).__str__() except Exception: @@ -3336,30 +3648,30 @@ class White(Token): '\n': '<LF>', '\r': '<CR>', '\f': '<FF>', - 'u\00A0': '<NBSP>', - 'u\1680': '<OGHAM_SPACE_MARK>', - 'u\180E': '<MONGOLIAN_VOWEL_SEPARATOR>', - 'u\2000': '<EN_QUAD>', - 'u\2001': '<EM_QUAD>', - 'u\2002': '<EN_SPACE>', - 'u\2003': '<EM_SPACE>', - 'u\2004': '<THREE-PER-EM_SPACE>', - 'u\2005': '<FOUR-PER-EM_SPACE>', - 'u\2006': '<SIX-PER-EM_SPACE>', - 'u\2007': '<FIGURE_SPACE>', - 'u\2008': '<PUNCTUATION_SPACE>', - 'u\2009': '<THIN_SPACE>', - 'u\200A': '<HAIR_SPACE>', - 'u\200B': '<ZERO_WIDTH_SPACE>', - 'u\202F': '<NNBSP>', - 'u\205F': '<MMSP>', - 'u\3000': '<IDEOGRAPHIC_SPACE>', + u'\u00A0': '<NBSP>', + u'\u1680': '<OGHAM_SPACE_MARK>', + u'\u180E': '<MONGOLIAN_VOWEL_SEPARATOR>', + u'\u2000': '<EN_QUAD>', + u'\u2001': '<EM_QUAD>', + u'\u2002': '<EN_SPACE>', + u'\u2003': '<EM_SPACE>', + u'\u2004': '<THREE-PER-EM_SPACE>', + u'\u2005': '<FOUR-PER-EM_SPACE>', + u'\u2006': '<SIX-PER-EM_SPACE>', + u'\u2007': '<FIGURE_SPACE>', + u'\u2008': '<PUNCTUATION_SPACE>', + u'\u2009': '<THIN_SPACE>', + u'\u200A': '<HAIR_SPACE>', + u'\u200B': '<ZERO_WIDTH_SPACE>', + u'\u202F': '<NNBSP>', + u'\u205F': '<MMSP>', + u'\u3000': '<IDEOGRAPHIC_SPACE>', } def __init__(self, ws=" \t\r\n", min=1, max=0, exact=0): - super(White,self).__init__() + super(White, self).__init__() self.matchWhite = ws - self.setWhitespaceChars( "".join(c for c in self.whiteChars if c not in self.matchWhite) ) - #~ self.leaveWhitespace() + self.setWhitespaceChars("".join(c for c in self.whiteChars if c not in self.matchWhite)) + # ~ self.leaveWhitespace() self.name = ("".join(White.whiteStrs[c] for c in self.matchWhite)) self.mayReturnEmpty = True self.errmsg = "Expected " + self.name @@ -3375,13 +3687,13 @@ class White(Token): self.maxLen = exact self.minLen = exact - def parseImpl( self, instring, loc, doActions=True ): - if not(instring[ loc ] in self.matchWhite): + def parseImpl(self, instring, loc, doActions=True): + if instring[loc] not in self.matchWhite: raise ParseException(instring, loc, self.errmsg, self) start = loc loc += 1 maxloc = start + self.maxLen - maxloc = min( maxloc, len(instring) ) + maxloc = min(maxloc, len(instring)) while loc < maxloc and instring[loc] in self.matchWhite: loc += 1 @@ -3392,9 +3704,9 @@ class White(Token): class _PositionToken(Token): - def __init__( self ): - super(_PositionToken,self).__init__() - self.name=self.__class__.__name__ + def __init__(self): + super(_PositionToken, self).__init__() + self.name = self.__class__.__name__ self.mayReturnEmpty = True self.mayIndexError = False @@ -3402,30 +3714,30 @@ class GoToColumn(_PositionToken): """Token to advance to a specific column of input text; useful for tabular report scraping. """ - def __init__( self, colno ): - super(GoToColumn,self).__init__() + def __init__(self, colno): + super(GoToColumn, self).__init__() self.col = colno - def preParse( self, instring, loc ): - if col(loc,instring) != self.col: + def preParse(self, instring, loc): + if col(loc, instring) != self.col: instrlen = len(instring) if self.ignoreExprs: - loc = self._skipIgnorables( instring, loc ) - while loc < instrlen and instring[loc].isspace() and col( loc, instring ) != self.col : + loc = self._skipIgnorables(instring, loc) + while loc < instrlen and instring[loc].isspace() and col(loc, instring) != self.col: loc += 1 return loc - def parseImpl( self, instring, loc, doActions=True ): - thiscol = col( loc, instring ) + def parseImpl(self, instring, loc, doActions=True): + thiscol = col(loc, instring) if thiscol > self.col: - raise ParseException( instring, loc, "Text not in expected column", self ) + raise ParseException(instring, loc, "Text not in expected column", self) newloc = loc + self.col - thiscol - ret = instring[ loc: newloc ] + ret = instring[loc: newloc] return newloc, ret class LineStart(_PositionToken): - """Matches if current position is at the beginning of a line within + r"""Matches if current position is at the beginning of a line within the parse string Example:: @@ -3446,11 +3758,11 @@ class LineStart(_PositionToken): ['AAA', ' and this line'] """ - def __init__( self ): - super(LineStart,self).__init__() + def __init__(self): + super(LineStart, self).__init__() self.errmsg = "Expected start of line" - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): if col(loc, instring) == 1: return loc, [] raise ParseException(instring, loc, self.errmsg, self) @@ -3459,19 +3771,19 @@ class LineEnd(_PositionToken): """Matches if current position is at the end of a line within the parse string """ - def __init__( self ): - super(LineEnd,self).__init__() - self.setWhitespaceChars( ParserElement.DEFAULT_WHITE_CHARS.replace("\n","") ) + def __init__(self): + super(LineEnd, self).__init__() + self.setWhitespaceChars(ParserElement.DEFAULT_WHITE_CHARS.replace("\n", "")) self.errmsg = "Expected end of line" - def parseImpl( self, instring, loc, doActions=True ): - if loc<len(instring): + def parseImpl(self, instring, loc, doActions=True): + if loc < len(instring): if instring[loc] == "\n": - return loc+1, "\n" + return loc + 1, "\n" else: raise ParseException(instring, loc, self.errmsg, self) elif loc == len(instring): - return loc+1, [] + return loc + 1, [] else: raise ParseException(instring, loc, self.errmsg, self) @@ -3479,29 +3791,29 @@ class StringStart(_PositionToken): """Matches if current position is at the beginning of the parse string """ - def __init__( self ): - super(StringStart,self).__init__() + def __init__(self): + super(StringStart, self).__init__() self.errmsg = "Expected start of text" - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): if loc != 0: # see if entire string up to here is just whitespace and ignoreables - if loc != self.preParse( instring, 0 ): + if loc != self.preParse(instring, 0): raise ParseException(instring, loc, self.errmsg, self) return loc, [] class StringEnd(_PositionToken): """Matches if current position is at the end of the parse string """ - def __init__( self ): - super(StringEnd,self).__init__() + def __init__(self): + super(StringEnd, self).__init__() self.errmsg = "Expected end of text" - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): if loc < len(instring): raise ParseException(instring, loc, self.errmsg, self) elif loc == len(instring): - return loc+1, [] + return loc + 1, [] elif loc > len(instring): return loc, [] else: @@ -3516,15 +3828,15 @@ class WordStart(_PositionToken): the beginning of the string being parsed, or at the beginning of a line. """ - def __init__(self, wordChars = printables): - super(WordStart,self).__init__() + def __init__(self, wordChars=printables): + super(WordStart, self).__init__() self.wordChars = set(wordChars) self.errmsg = "Not at the start of a word" - def parseImpl(self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): if loc != 0: - if (instring[loc-1] in self.wordChars or - instring[loc] not in self.wordChars): + if (instring[loc - 1] in self.wordChars + or instring[loc] not in self.wordChars): raise ParseException(instring, loc, self.errmsg, self) return loc, [] @@ -3536,17 +3848,17 @@ class WordEnd(_PositionToken): will also match at the end of the string being parsed, or at the end of a line. """ - def __init__(self, wordChars = printables): - super(WordEnd,self).__init__() + def __init__(self, wordChars=printables): + super(WordEnd, self).__init__() self.wordChars = set(wordChars) self.skipWhitespace = False self.errmsg = "Not at the end of a word" - def parseImpl(self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): instrlen = len(instring) - if instrlen>0 and loc<instrlen: + if instrlen > 0 and loc < instrlen: if (instring[loc] in self.wordChars or - instring[loc-1] not in self.wordChars): + instring[loc - 1] not in self.wordChars): raise ParseException(instring, loc, self.errmsg, self) return loc, [] @@ -3555,90 +3867,89 @@ class ParseExpression(ParserElement): """Abstract subclass of ParserElement, for combining and post-processing parsed tokens. """ - def __init__( self, exprs, savelist = False ): - super(ParseExpression,self).__init__(savelist) - if isinstance( exprs, _generatorType ): + def __init__(self, exprs, savelist=False): + super(ParseExpression, self).__init__(savelist) + if isinstance(exprs, _generatorType): exprs = list(exprs) - if isinstance( exprs, basestring ): - self.exprs = [ ParserElement._literalStringClass( exprs ) ] - elif isinstance( exprs, Iterable ): + if isinstance(exprs, basestring): + self.exprs = [self._literalStringClass(exprs)] + elif isinstance(exprs, ParserElement): + self.exprs = [exprs] + elif isinstance(exprs, Iterable): exprs = list(exprs) # if sequence of strings provided, wrap with Literal - if all(isinstance(expr, basestring) for expr in exprs): - exprs = map(ParserElement._literalStringClass, exprs) + if any(isinstance(expr, basestring) for expr in exprs): + exprs = (self._literalStringClass(e) if isinstance(e, basestring) else e for e in exprs) self.exprs = list(exprs) else: try: - self.exprs = list( exprs ) + self.exprs = list(exprs) except TypeError: - self.exprs = [ exprs ] + self.exprs = [exprs] self.callPreparse = False - def __getitem__( self, i ): - return self.exprs[i] - - def append( self, other ): - self.exprs.append( other ) + def append(self, other): + self.exprs.append(other) self.strRepr = None return self - def leaveWhitespace( self ): + def leaveWhitespace(self): """Extends ``leaveWhitespace`` defined in base class, and also invokes ``leaveWhitespace`` on all contained expressions.""" self.skipWhitespace = False - self.exprs = [ e.copy() for e in self.exprs ] + self.exprs = [e.copy() for e in self.exprs] for e in self.exprs: e.leaveWhitespace() return self - def ignore( self, other ): - if isinstance( other, Suppress ): + def ignore(self, other): + if isinstance(other, Suppress): if other not in self.ignoreExprs: - super( ParseExpression, self).ignore( other ) + super(ParseExpression, self).ignore(other) for e in self.exprs: - e.ignore( self.ignoreExprs[-1] ) + e.ignore(self.ignoreExprs[-1]) else: - super( ParseExpression, self).ignore( other ) + super(ParseExpression, self).ignore(other) for e in self.exprs: - e.ignore( self.ignoreExprs[-1] ) + e.ignore(self.ignoreExprs[-1]) return self - def __str__( self ): + def __str__(self): try: - return super(ParseExpression,self).__str__() + return super(ParseExpression, self).__str__() except Exception: pass if self.strRepr is None: - self.strRepr = "%s:(%s)" % ( self.__class__.__name__, _ustr(self.exprs) ) + self.strRepr = "%s:(%s)" % (self.__class__.__name__, _ustr(self.exprs)) return self.strRepr - def streamline( self ): - super(ParseExpression,self).streamline() + def streamline(self): + super(ParseExpression, self).streamline() for e in self.exprs: e.streamline() - # collapse nested And's of the form And( And( And( a,b), c), d) to And( a,b,c,d ) + # collapse nested And's of the form And(And(And(a, b), c), d) to And(a, b, c, d) # but only if there are no parse actions or resultsNames on the nested And's # (likewise for Or's and MatchFirst's) - if ( len(self.exprs) == 2 ): + if len(self.exprs) == 2: other = self.exprs[0] - if ( isinstance( other, self.__class__ ) and - not(other.parseAction) and - other.resultsName is None and - not other.debug ): - self.exprs = other.exprs[:] + [ self.exprs[1] ] + if (isinstance(other, self.__class__) + and not other.parseAction + and other.resultsName is None + and not other.debug): + self.exprs = other.exprs[:] + [self.exprs[1]] self.strRepr = None self.mayReturnEmpty |= other.mayReturnEmpty self.mayIndexError |= other.mayIndexError other = self.exprs[-1] - if ( isinstance( other, self.__class__ ) and - not(other.parseAction) and - other.resultsName is None and - not other.debug ): + if (isinstance(other, self.__class__) + and not other.parseAction + and other.resultsName is None + and not other.debug): self.exprs = self.exprs[:-1] + other.exprs[:] self.strRepr = None self.mayReturnEmpty |= other.mayReturnEmpty @@ -3648,21 +3959,31 @@ class ParseExpression(ParserElement): return self - def setResultsName( self, name, listAllMatches=False ): - ret = super(ParseExpression,self).setResultsName(name,listAllMatches) - return ret - - def validate( self, validateTrace=[] ): - tmp = validateTrace[:]+[self] + def validate(self, validateTrace=None): + tmp = (validateTrace if validateTrace is not None else [])[:] + [self] for e in self.exprs: e.validate(tmp) - self.checkRecursion( [] ) + self.checkRecursion([]) def copy(self): - ret = super(ParseExpression,self).copy() + ret = super(ParseExpression, self).copy() ret.exprs = [e.copy() for e in self.exprs] return ret + def _setResultsName(self, name, listAllMatches=False): + if __diag__.warn_ungrouped_named_tokens_in_collection: + for e in self.exprs: + if isinstance(e, ParserElement) and e.resultsName: + warnings.warn("{0}: setting results name {1!r} on {2} expression " + "collides with {3!r} on contained expression".format("warn_ungrouped_named_tokens_in_collection", + name, + type(self).__name__, + e.resultsName), + stacklevel=3) + + return super(ParseExpression, self)._setResultsName(name, listAllMatches) + + class And(ParseExpression): """ Requires all given :class:`ParseExpression` s to be found in the given order. @@ -3676,33 +3997,59 @@ class And(ParseExpression): integer = Word(nums) name_expr = OneOrMore(Word(alphas)) - expr = And([integer("id"),name_expr("name"),integer("age")]) + expr = And([integer("id"), name_expr("name"), integer("age")]) # more easily written as: expr = integer("id") + name_expr("name") + integer("age") """ class _ErrorStop(Empty): def __init__(self, *args, **kwargs): - super(And._ErrorStop,self).__init__(*args, **kwargs) + super(And._ErrorStop, self).__init__(*args, **kwargs) self.name = '-' self.leaveWhitespace() - def __init__( self, exprs, savelist = True ): - super(And,self).__init__(exprs, savelist) + def __init__(self, exprs, savelist=True): + exprs = list(exprs) + if exprs and Ellipsis in exprs: + tmp = [] + for i, expr in enumerate(exprs): + if expr is Ellipsis: + if i < len(exprs) - 1: + skipto_arg = (Empty() + exprs[i + 1]).exprs[-1] + tmp.append(SkipTo(skipto_arg)("_skipped*")) + else: + raise Exception("cannot construct And with sequence ending in ...") + else: + tmp.append(expr) + exprs[:] = tmp + super(And, self).__init__(exprs, savelist) self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs) - self.setWhitespaceChars( self.exprs[0].whiteChars ) + self.setWhitespaceChars(self.exprs[0].whiteChars) self.skipWhitespace = self.exprs[0].skipWhitespace self.callPreparse = True def streamline(self): + # collapse any _PendingSkip's + if self.exprs: + if any(isinstance(e, ParseExpression) and e.exprs and isinstance(e.exprs[-1], _PendingSkip) + for e in self.exprs[:-1]): + for i, e in enumerate(self.exprs[:-1]): + if e is None: + continue + if (isinstance(e, ParseExpression) + and e.exprs and isinstance(e.exprs[-1], _PendingSkip)): + e.exprs[-1] = e.exprs[-1] + self.exprs[i + 1] + self.exprs[i + 1] = None + self.exprs = [e for e in self.exprs if e is not None] + super(And, self).streamline() self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs) return self - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): # pass False as last arg to _parse for first element, since we already # pre-parsed the string as part of our And pre-parsing - loc, resultlist = self.exprs[0]._parse( instring, loc, doActions, callPreParse=False ) + loc, resultlist = self.exprs[0]._parse(instring, loc, doActions, callPreParse=False) errorStop = False for e in self.exprs[1:]: if isinstance(e, And._ErrorStop): @@ -3710,7 +4057,7 @@ class And(ParseExpression): continue if errorStop: try: - loc, exprtokens = e._parse( instring, loc, doActions ) + loc, exprtokens = e._parse(instring, loc, doActions) except ParseSyntaxException: raise except ParseBaseException as pe: @@ -3719,25 +4066,25 @@ class And(ParseExpression): except IndexError: raise ParseSyntaxException(instring, len(instring), self.errmsg, self) else: - loc, exprtokens = e._parse( instring, loc, doActions ) + loc, exprtokens = e._parse(instring, loc, doActions) if exprtokens or exprtokens.haskeys(): resultlist += exprtokens return loc, resultlist - def __iadd__(self, other ): - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - return self.append( other ) #And( [ self, other ] ) + def __iadd__(self, other): + if isinstance(other, basestring): + other = self._literalStringClass(other) + return self.append(other) # And([self, other]) - def checkRecursion( self, parseElementList ): - subRecCheckList = parseElementList[:] + [ self ] + def checkRecursion(self, parseElementList): + subRecCheckList = parseElementList[:] + [self] for e in self.exprs: - e.checkRecursion( subRecCheckList ) + e.checkRecursion(subRecCheckList) if not e.mayReturnEmpty: break - def __str__( self ): - if hasattr(self,"name"): + def __str__(self): + if hasattr(self, "name"): return self.name if self.strRepr is None: @@ -3763,8 +4110,8 @@ class Or(ParseExpression): [['123'], ['3.1416'], ['789']] """ - def __init__( self, exprs, savelist = False ): - super(Or,self).__init__(exprs, savelist) + def __init__(self, exprs, savelist=False): + super(Or, self).__init__(exprs, savelist) if self.exprs: self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs) else: @@ -3772,16 +4119,17 @@ class Or(ParseExpression): def streamline(self): super(Or, self).streamline() - self.saveAsList = any(e.saveAsList for e in self.exprs) + if __compat__.collect_all_And_tokens: + self.saveAsList = any(e.saveAsList for e in self.exprs) return self - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): maxExcLoc = -1 maxException = None matches = [] for e in self.exprs: try: - loc2 = e.tryParse( instring, loc ) + loc2 = e.tryParse(instring, loc) except ParseException as err: err.__traceback__ = None if err.loc > maxExcLoc: @@ -3789,22 +4137,45 @@ class Or(ParseExpression): maxExcLoc = err.loc except IndexError: if len(instring) > maxExcLoc: - maxException = ParseException(instring,len(instring),e.errmsg,self) + maxException = ParseException(instring, len(instring), e.errmsg, self) maxExcLoc = len(instring) else: # save match among all matches, to retry longest to shortest matches.append((loc2, e)) if matches: - matches.sort(key=lambda x: -x[0]) - for _,e in matches: + # re-evaluate all matches in descending order of length of match, in case attached actions + # might change whether or how much they match of the input. + matches.sort(key=itemgetter(0), reverse=True) + + if not doActions: + # no further conditions or parse actions to change the selection of + # alternative, so the first match will be the best match + best_expr = matches[0][1] + return best_expr._parse(instring, loc, doActions) + + longest = -1, None + for loc1, expr1 in matches: + if loc1 <= longest[0]: + # already have a longer match than this one will deliver, we are done + return longest + try: - return e._parse( instring, loc, doActions ) + loc2, toks = expr1._parse(instring, loc, doActions) except ParseException as err: err.__traceback__ = None if err.loc > maxExcLoc: maxException = err maxExcLoc = err.loc + else: + if loc2 >= loc1: + return loc2, toks + # didn't match as much as before + elif loc2 > longest[0]: + longest = loc2, toks + + if longest != (-1, None): + return longest if maxException is not None: maxException.msg = self.errmsg @@ -3813,13 +4184,13 @@ class Or(ParseExpression): raise ParseException(instring, loc, "no defined alternatives to match", self) - def __ixor__(self, other ): - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - return self.append( other ) #Or( [ self, other ] ) + def __ixor__(self, other): + if isinstance(other, basestring): + other = self._literalStringClass(other) + return self.append(other) # Or([self, other]) - def __str__( self ): - if hasattr(self,"name"): + def __str__(self): + if hasattr(self, "name"): return self.name if self.strRepr is None: @@ -3827,10 +4198,22 @@ class Or(ParseExpression): return self.strRepr - def checkRecursion( self, parseElementList ): - subRecCheckList = parseElementList[:] + [ self ] + def checkRecursion(self, parseElementList): + subRecCheckList = parseElementList[:] + [self] for e in self.exprs: - e.checkRecursion( subRecCheckList ) + e.checkRecursion(subRecCheckList) + + def _setResultsName(self, name, listAllMatches=False): + if (not __compat__.collect_all_And_tokens + and __diag__.warn_multiple_tokens_in_named_alternation): + if any(isinstance(e, And) for e in self.exprs): + warnings.warn("{0}: setting results name {1!r} on {2} expression " + "may only return a single token for an And alternative, " + "in future will return the full list of tokens".format( + "warn_multiple_tokens_in_named_alternation", name, type(self).__name__), + stacklevel=3) + + return super(Or, self)._setResultsName(name, listAllMatches) class MatchFirst(ParseExpression): @@ -3850,25 +4233,25 @@ class MatchFirst(ParseExpression): number = Combine(Word(nums) + '.' + Word(nums)) | Word(nums) print(number.searchString("123 3.1416 789")) # Better -> [['123'], ['3.1416'], ['789']] """ - def __init__( self, exprs, savelist = False ): - super(MatchFirst,self).__init__(exprs, savelist) + def __init__(self, exprs, savelist=False): + super(MatchFirst, self).__init__(exprs, savelist) if self.exprs: self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs) - # self.saveAsList = any(e.saveAsList for e in self.exprs) else: self.mayReturnEmpty = True def streamline(self): super(MatchFirst, self).streamline() - self.saveAsList = any(e.saveAsList for e in self.exprs) + if __compat__.collect_all_And_tokens: + self.saveAsList = any(e.saveAsList for e in self.exprs) return self - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): maxExcLoc = -1 maxException = None for e in self.exprs: try: - ret = e._parse( instring, loc, doActions ) + ret = e._parse(instring, loc, doActions) return ret except ParseException as err: if err.loc > maxExcLoc: @@ -3876,7 +4259,7 @@ class MatchFirst(ParseExpression): maxExcLoc = err.loc except IndexError: if len(instring) > maxExcLoc: - maxException = ParseException(instring,len(instring),e.errmsg,self) + maxException = ParseException(instring, len(instring), e.errmsg, self) maxExcLoc = len(instring) # only got here if no expression matched, raise exception for match that made it the furthest @@ -3887,13 +4270,13 @@ class MatchFirst(ParseExpression): else: raise ParseException(instring, loc, "no defined alternatives to match", self) - def __ior__(self, other ): - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - return self.append( other ) #MatchFirst( [ self, other ] ) + def __ior__(self, other): + if isinstance(other, basestring): + other = self._literalStringClass(other) + return self.append(other) # MatchFirst([self, other]) - def __str__( self ): - if hasattr(self,"name"): + def __str__(self): + if hasattr(self, "name"): return self.name if self.strRepr is None: @@ -3901,10 +4284,22 @@ class MatchFirst(ParseExpression): return self.strRepr - def checkRecursion( self, parseElementList ): - subRecCheckList = parseElementList[:] + [ self ] + def checkRecursion(self, parseElementList): + subRecCheckList = parseElementList[:] + [self] for e in self.exprs: - e.checkRecursion( subRecCheckList ) + e.checkRecursion(subRecCheckList) + + def _setResultsName(self, name, listAllMatches=False): + if (not __compat__.collect_all_And_tokens + and __diag__.warn_multiple_tokens_in_named_alternation): + if any(isinstance(e, And) for e in self.exprs): + warnings.warn("{0}: setting results name {1!r} on {2} expression " + "may only return a single token for an And alternative, " + "in future will return the full list of tokens".format( + "warn_multiple_tokens_in_named_alternation", name, type(self).__name__), + stacklevel=3) + + return super(MatchFirst, self)._setResultsName(name, listAllMatches) class Each(ParseExpression): @@ -3964,8 +4359,8 @@ class Each(ParseExpression): - shape: TRIANGLE - size: 20 """ - def __init__( self, exprs, savelist = True ): - super(Each,self).__init__(exprs, savelist) + def __init__(self, exprs, savelist=True): + super(Each, self).__init__(exprs, savelist) self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs) self.skipWhitespace = True self.initExprGroups = True @@ -3976,15 +4371,15 @@ class Each(ParseExpression): self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs) return self - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): if self.initExprGroups: - self.opt1map = dict((id(e.expr),e) for e in self.exprs if isinstance(e,Optional)) - opt1 = [ e.expr for e in self.exprs if isinstance(e,Optional) ] - opt2 = [ e for e in self.exprs if e.mayReturnEmpty and not isinstance(e,Optional)] + self.opt1map = dict((id(e.expr), e) for e in self.exprs if isinstance(e, Optional)) + opt1 = [e.expr for e in self.exprs if isinstance(e, Optional)] + opt2 = [e for e in self.exprs if e.mayReturnEmpty and not isinstance(e, (Optional, Regex))] self.optionals = opt1 + opt2 - self.multioptionals = [ e.expr for e in self.exprs if isinstance(e,ZeroOrMore) ] - self.multirequired = [ e.expr for e in self.exprs if isinstance(e,OneOrMore) ] - self.required = [ e for e in self.exprs if not isinstance(e,(Optional,ZeroOrMore,OneOrMore)) ] + self.multioptionals = [e.expr for e in self.exprs if isinstance(e, ZeroOrMore)] + self.multirequired = [e.expr for e in self.exprs if isinstance(e, OneOrMore)] + self.required = [e for e in self.exprs if not isinstance(e, (Optional, ZeroOrMore, OneOrMore))] self.required += self.multirequired self.initExprGroups = False tmpLoc = loc @@ -3998,11 +4393,11 @@ class Each(ParseExpression): failed = [] for e in tmpExprs: try: - tmpLoc = e.tryParse( instring, tmpLoc ) + tmpLoc = e.tryParse(instring, tmpLoc) except ParseException: failed.append(e) else: - matchOrder.append(self.opt1map.get(id(e),e)) + matchOrder.append(self.opt1map.get(id(e), e)) if e in tmpReqd: tmpReqd.remove(e) elif e in tmpOpt: @@ -4012,21 +4407,21 @@ class Each(ParseExpression): if tmpReqd: missing = ", ".join(_ustr(e) for e in tmpReqd) - raise ParseException(instring,loc,"Missing one or more required elements (%s)" % missing ) + raise ParseException(instring, loc, "Missing one or more required elements (%s)" % missing) # add any unmatched Optionals, in case they have default values defined - matchOrder += [e for e in self.exprs if isinstance(e,Optional) and e.expr in tmpOpt] + matchOrder += [e for e in self.exprs if isinstance(e, Optional) and e.expr in tmpOpt] resultlist = [] for e in matchOrder: - loc,results = e._parse(instring,loc,doActions) + loc, results = e._parse(instring, loc, doActions) resultlist.append(results) finalResults = sum(resultlist, ParseResults([])) return loc, finalResults - def __str__( self ): - if hasattr(self,"name"): + def __str__(self): + if hasattr(self, "name"): return self.name if self.strRepr is None: @@ -4034,86 +4429,88 @@ class Each(ParseExpression): return self.strRepr - def checkRecursion( self, parseElementList ): - subRecCheckList = parseElementList[:] + [ self ] + def checkRecursion(self, parseElementList): + subRecCheckList = parseElementList[:] + [self] for e in self.exprs: - e.checkRecursion( subRecCheckList ) + e.checkRecursion(subRecCheckList) class ParseElementEnhance(ParserElement): """Abstract subclass of :class:`ParserElement`, for combining and post-processing parsed tokens. """ - def __init__( self, expr, savelist=False ): - super(ParseElementEnhance,self).__init__(savelist) - if isinstance( expr, basestring ): - if issubclass(ParserElement._literalStringClass, Token): - expr = ParserElement._literalStringClass(expr) + def __init__(self, expr, savelist=False): + super(ParseElementEnhance, self).__init__(savelist) + if isinstance(expr, basestring): + if issubclass(self._literalStringClass, Token): + expr = self._literalStringClass(expr) else: - expr = ParserElement._literalStringClass(Literal(expr)) + expr = self._literalStringClass(Literal(expr)) self.expr = expr self.strRepr = None if expr is not None: self.mayIndexError = expr.mayIndexError self.mayReturnEmpty = expr.mayReturnEmpty - self.setWhitespaceChars( expr.whiteChars ) + self.setWhitespaceChars(expr.whiteChars) self.skipWhitespace = expr.skipWhitespace self.saveAsList = expr.saveAsList self.callPreparse = expr.callPreparse self.ignoreExprs.extend(expr.ignoreExprs) - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): if self.expr is not None: - return self.expr._parse( instring, loc, doActions, callPreParse=False ) + return self.expr._parse(instring, loc, doActions, callPreParse=False) else: - raise ParseException("",loc,self.errmsg,self) + raise ParseException("", loc, self.errmsg, self) - def leaveWhitespace( self ): + def leaveWhitespace(self): self.skipWhitespace = False self.expr = self.expr.copy() if self.expr is not None: self.expr.leaveWhitespace() return self - def ignore( self, other ): - if isinstance( other, Suppress ): + def ignore(self, other): + if isinstance(other, Suppress): if other not in self.ignoreExprs: - super( ParseElementEnhance, self).ignore( other ) + super(ParseElementEnhance, self).ignore(other) if self.expr is not None: - self.expr.ignore( self.ignoreExprs[-1] ) + self.expr.ignore(self.ignoreExprs[-1]) else: - super( ParseElementEnhance, self).ignore( other ) + super(ParseElementEnhance, self).ignore(other) if self.expr is not None: - self.expr.ignore( self.ignoreExprs[-1] ) + self.expr.ignore(self.ignoreExprs[-1]) return self - def streamline( self ): - super(ParseElementEnhance,self).streamline() + def streamline(self): + super(ParseElementEnhance, self).streamline() if self.expr is not None: self.expr.streamline() return self - def checkRecursion( self, parseElementList ): + def checkRecursion(self, parseElementList): if self in parseElementList: - raise RecursiveGrammarException( parseElementList+[self] ) - subRecCheckList = parseElementList[:] + [ self ] + raise RecursiveGrammarException(parseElementList + [self]) + subRecCheckList = parseElementList[:] + [self] if self.expr is not None: - self.expr.checkRecursion( subRecCheckList ) + self.expr.checkRecursion(subRecCheckList) - def validate( self, validateTrace=[] ): - tmp = validateTrace[:]+[self] + def validate(self, validateTrace=None): + if validateTrace is None: + validateTrace = [] + tmp = validateTrace[:] + [self] if self.expr is not None: self.expr.validate(tmp) - self.checkRecursion( [] ) + self.checkRecursion([]) - def __str__( self ): + def __str__(self): try: - return super(ParseElementEnhance,self).__str__() + return super(ParseElementEnhance, self).__str__() except Exception: pass if self.strRepr is None and self.expr is not None: - self.strRepr = "%s:(%s)" % ( self.__class__.__name__, _ustr(self.expr) ) + self.strRepr = "%s:(%s)" % (self.__class__.__name__, _ustr(self.expr)) return self.strRepr @@ -4139,13 +4536,16 @@ class FollowedBy(ParseElementEnhance): [['shape', 'SQUARE'], ['color', 'BLACK'], ['posn', 'upper left']] """ - def __init__( self, expr ): - super(FollowedBy,self).__init__(expr) + def __init__(self, expr): + super(FollowedBy, self).__init__(expr) self.mayReturnEmpty = True - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): + # by using self._expr.parse and deleting the contents of the returned ParseResults list + # we keep any named results that were defined in the FollowedBy expression _, ret = self.expr._parse(instring, loc, doActions=doActions) del ret[:] + return loc, ret @@ -4198,6 +4598,7 @@ class PrecededBy(ParseElementEnhance): self.retreat = retreat self.errmsg = "not preceded by " + str(expr) self.skipWhitespace = False + self.parseAction.append(lambda s, l, t: t.__delitem__(slice(None, None))) def parseImpl(self, instring, loc=0, doActions=True): if self.exact: @@ -4208,19 +4609,18 @@ class PrecededBy(ParseElementEnhance): else: # retreat specified a maximum lookbehind window, iterate test_expr = self.expr + StringEnd() - instring_slice = instring[:loc] + instring_slice = instring[max(0, loc - self.retreat):loc] last_expr = ParseException(instring, loc, self.errmsg) - for offset in range(1, min(loc, self.retreat+1)): + for offset in range(1, min(loc, self.retreat + 1)+1): try: - _, ret = test_expr._parse(instring_slice, loc-offset) + # print('trying', offset, instring_slice, repr(instring_slice[loc - offset:])) + _, ret = test_expr._parse(instring_slice, len(instring_slice) - offset) except ParseBaseException as pbe: last_expr = pbe else: break else: raise last_expr - # return empty list of tokens, but preserve any defined results names - del ret[:] return loc, ret @@ -4247,20 +4647,20 @@ class NotAny(ParseElementEnhance): # integers that are followed by "." are actually floats integer = Word(nums) + ~Char(".") """ - def __init__( self, expr ): - super(NotAny,self).__init__(expr) - #~ self.leaveWhitespace() + def __init__(self, expr): + super(NotAny, self).__init__(expr) + # ~ self.leaveWhitespace() self.skipWhitespace = False # do NOT use self.leaveWhitespace(), don't want to propagate to exprs self.mayReturnEmpty = True - self.errmsg = "Found unwanted token, "+_ustr(self.expr) + self.errmsg = "Found unwanted token, " + _ustr(self.expr) - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): if self.expr.canParseNext(instring, loc): raise ParseException(instring, loc, self.errmsg, self) return loc, [] - def __str__( self ): - if hasattr(self,"name"): + def __str__(self): + if hasattr(self, "name"): return self.name if self.strRepr is None: @@ -4269,15 +4669,21 @@ class NotAny(ParseElementEnhance): return self.strRepr class _MultipleMatch(ParseElementEnhance): - def __init__( self, expr, stopOn=None): + def __init__(self, expr, stopOn=None): super(_MultipleMatch, self).__init__(expr) self.saveAsList = True ender = stopOn if isinstance(ender, basestring): - ender = ParserElement._literalStringClass(ender) - self.not_ender = ~ender if ender is not None else None + ender = self._literalStringClass(ender) + self.stopOn(ender) - def parseImpl( self, instring, loc, doActions=True ): + def stopOn(self, ender): + if isinstance(ender, basestring): + ender = self._literalStringClass(ender) + self.not_ender = ~ender if ender is not None else None + return self + + def parseImpl(self, instring, loc, doActions=True): self_expr_parse = self.expr._parse self_skip_ignorables = self._skipIgnorables check_ender = self.not_ender is not None @@ -4288,24 +4694,38 @@ class _MultipleMatch(ParseElementEnhance): # if so, fail) if check_ender: try_not_ender(instring, loc) - loc, tokens = self_expr_parse( instring, loc, doActions, callPreParse=False ) + loc, tokens = self_expr_parse(instring, loc, doActions, callPreParse=False) try: hasIgnoreExprs = (not not self.ignoreExprs) while 1: if check_ender: try_not_ender(instring, loc) if hasIgnoreExprs: - preloc = self_skip_ignorables( instring, loc ) + preloc = self_skip_ignorables(instring, loc) else: preloc = loc - loc, tmptokens = self_expr_parse( instring, preloc, doActions ) + loc, tmptokens = self_expr_parse(instring, preloc, doActions) if tmptokens or tmptokens.haskeys(): tokens += tmptokens - except (ParseException,IndexError): + except (ParseException, IndexError): pass return loc, tokens + def _setResultsName(self, name, listAllMatches=False): + if __diag__.warn_ungrouped_named_tokens_in_collection: + for e in [self.expr] + getattr(self.expr, 'exprs', []): + if isinstance(e, ParserElement) and e.resultsName: + warnings.warn("{0}: setting results name {1!r} on {2} expression " + "collides with {3!r} on contained expression".format("warn_ungrouped_named_tokens_in_collection", + name, + type(self).__name__, + e.resultsName), + stacklevel=3) + + return super(_MultipleMatch, self)._setResultsName(name, listAllMatches) + + class OneOrMore(_MultipleMatch): """Repetition of one or more of the given expression. @@ -4332,8 +4752,8 @@ class OneOrMore(_MultipleMatch): (attr_expr * (1,)).parseString(text).pprint() """ - def __str__( self ): - if hasattr(self,"name"): + def __str__(self): + if hasattr(self, "name"): return self.name if self.strRepr is None: @@ -4352,18 +4772,18 @@ class ZeroOrMore(_MultipleMatch): Example: similar to :class:`OneOrMore` """ - def __init__( self, expr, stopOn=None): - super(ZeroOrMore,self).__init__(expr, stopOn=stopOn) + def __init__(self, expr, stopOn=None): + super(ZeroOrMore, self).__init__(expr, stopOn=stopOn) self.mayReturnEmpty = True - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): try: return super(ZeroOrMore, self).parseImpl(instring, loc, doActions) - except (ParseException,IndexError): + except (ParseException, IndexError): return loc, [] - def __str__( self ): - if hasattr(self,"name"): + def __str__(self): + if hasattr(self, "name"): return self.name if self.strRepr is None: @@ -4371,6 +4791,7 @@ class ZeroOrMore(_MultipleMatch): return self.strRepr + class _NullToken(object): def __bool__(self): return False @@ -4378,7 +4799,6 @@ class _NullToken(object): def __str__(self): return "" -_optionalNotMatched = _NullToken() class Optional(ParseElementEnhance): """Optional matching of the given expression. @@ -4416,28 +4836,30 @@ class Optional(ParseElementEnhance): ^ FAIL: Expected end of text (at char 5), (line:1, col:6) """ - def __init__( self, expr, default=_optionalNotMatched ): - super(Optional,self).__init__( expr, savelist=False ) + __optionalNotMatched = _NullToken() + + def __init__(self, expr, default=__optionalNotMatched): + super(Optional, self).__init__(expr, savelist=False) self.saveAsList = self.expr.saveAsList self.defaultValue = default self.mayReturnEmpty = True - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): try: - loc, tokens = self.expr._parse( instring, loc, doActions, callPreParse=False ) - except (ParseException,IndexError): - if self.defaultValue is not _optionalNotMatched: + loc, tokens = self.expr._parse(instring, loc, doActions, callPreParse=False) + except (ParseException, IndexError): + if self.defaultValue is not self.__optionalNotMatched: if self.expr.resultsName: - tokens = ParseResults([ self.defaultValue ]) + tokens = ParseResults([self.defaultValue]) tokens[self.expr.resultsName] = self.defaultValue else: - tokens = [ self.defaultValue ] + tokens = [self.defaultValue] else: tokens = [] return loc, tokens - def __str__( self ): - if hasattr(self,"name"): + def __str__(self): + if hasattr(self, "name"): return self.name if self.strRepr is None: @@ -4503,20 +4925,20 @@ class SkipTo(ParseElementEnhance): - issue_num: 79 - sev: Minor """ - def __init__( self, other, include=False, ignore=None, failOn=None ): - super( SkipTo, self ).__init__( other ) + def __init__(self, other, include=False, ignore=None, failOn=None): + super(SkipTo, self).__init__(other) self.ignoreExpr = ignore self.mayReturnEmpty = True self.mayIndexError = False self.includeMatch = include self.saveAsList = False if isinstance(failOn, basestring): - self.failOn = ParserElement._literalStringClass(failOn) + self.failOn = self._literalStringClass(failOn) else: self.failOn = failOn - self.errmsg = "No match found for "+_ustr(self.expr) + self.errmsg = "No match found for " + _ustr(self.expr) - def parseImpl( self, instring, loc, doActions=True ): + def parseImpl(self, instring, loc, doActions=True): startloc = loc instrlen = len(instring) expr = self.expr @@ -4558,7 +4980,7 @@ class SkipTo(ParseElementEnhance): skipresult = ParseResults(skiptext) if self.includeMatch: - loc, mat = expr_parse(instring,loc,doActions,callPreParse=False) + loc, mat = expr_parse(instring, loc, doActions, callPreParse=False) skipresult += mat return loc, skipresult @@ -4590,17 +5012,17 @@ class Forward(ParseElementEnhance): See :class:`ParseResults.pprint` for an example of a recursive parser created using ``Forward``. """ - def __init__( self, other=None ): - super(Forward,self).__init__( other, savelist=False ) + def __init__(self, other=None): + super(Forward, self).__init__(other, savelist=False) - def __lshift__( self, other ): - if isinstance( other, basestring ): - other = ParserElement._literalStringClass(other) + def __lshift__(self, other): + if isinstance(other, basestring): + other = self._literalStringClass(other) self.expr = other self.strRepr = None self.mayIndexError = self.expr.mayIndexError self.mayReturnEmpty = self.expr.mayReturnEmpty - self.setWhitespaceChars( self.expr.whiteChars ) + self.setWhitespaceChars(self.expr.whiteChars) self.skipWhitespace = self.expr.skipWhitespace self.saveAsList = self.expr.saveAsList self.ignoreExprs.extend(self.expr.ignoreExprs) @@ -4609,59 +5031,72 @@ class Forward(ParseElementEnhance): def __ilshift__(self, other): return self << other - def leaveWhitespace( self ): + def leaveWhitespace(self): self.skipWhitespace = False return self - def streamline( self ): + def streamline(self): if not self.streamlined: self.streamlined = True if self.expr is not None: self.expr.streamline() return self - def validate( self, validateTrace=[] ): + def validate(self, validateTrace=None): + if validateTrace is None: + validateTrace = [] + if self not in validateTrace: - tmp = validateTrace[:]+[self] + tmp = validateTrace[:] + [self] if self.expr is not None: self.expr.validate(tmp) self.checkRecursion([]) - def __str__( self ): - if hasattr(self,"name"): + def __str__(self): + if hasattr(self, "name"): return self.name - return self.__class__.__name__ + ": ..." + if self.strRepr is not None: + return self.strRepr - # stubbed out for now - creates awful memory and perf issues - self._revertClass = self.__class__ - self.__class__ = _ForwardNoRecurse + # Avoid infinite recursion by setting a temporary strRepr + self.strRepr = ": ..." + + # Use the string representation of main expression. + retString = '...' try: if self.expr is not None: - retString = _ustr(self.expr) + retString = _ustr(self.expr)[:1000] else: retString = "None" finally: - self.__class__ = self._revertClass - return self.__class__.__name__ + ": " + retString + self.strRepr = self.__class__.__name__ + ": " + retString + return self.strRepr def copy(self): if self.expr is not None: - return super(Forward,self).copy() + return super(Forward, self).copy() else: ret = Forward() ret <<= self return ret -class _ForwardNoRecurse(Forward): - def __str__( self ): - return "..." + def _setResultsName(self, name, listAllMatches=False): + if __diag__.warn_name_set_on_empty_Forward: + if self.expr is None: + warnings.warn("{0}: setting results name {0!r} on {1} expression " + "that has no contained expression".format("warn_name_set_on_empty_Forward", + name, + type(self).__name__), + stacklevel=3) + + return super(Forward, self)._setResultsName(name, listAllMatches) class TokenConverter(ParseElementEnhance): """ Abstract subclass of :class:`ParseExpression`, for converting parsed results. """ - def __init__( self, expr, savelist=False ): - super(TokenConverter,self).__init__( expr )#, savelist ) + def __init__(self, expr, savelist=False): + super(TokenConverter, self).__init__(expr) # , savelist) self.saveAsList = False class Combine(TokenConverter): @@ -4682,8 +5117,8 @@ class Combine(TokenConverter): # no match when there are internal spaces print(real.parseString('3. 1416')) # -> Exception: Expected W:(0123...) """ - def __init__( self, expr, joinString="", adjacent=True ): - super(Combine,self).__init__( expr ) + def __init__(self, expr, joinString="", adjacent=True): + super(Combine, self).__init__(expr) # suppress whitespace-stripping in contained parse expressions, but re-enable it on the Combine itself if adjacent: self.leaveWhitespace() @@ -4692,20 +5127,20 @@ class Combine(TokenConverter): self.joinString = joinString self.callPreparse = True - def ignore( self, other ): + def ignore(self, other): if self.adjacent: ParserElement.ignore(self, other) else: - super( Combine, self).ignore( other ) + super(Combine, self).ignore(other) return self - def postParse( self, instring, loc, tokenlist ): + def postParse(self, instring, loc, tokenlist): retToks = tokenlist.copy() del retToks[:] - retToks += ParseResults([ "".join(tokenlist._asStringList(self.joinString)) ], modal=self.modalResults) + retToks += ParseResults(["".join(tokenlist._asStringList(self.joinString))], modal=self.modalResults) if self.resultsName and retToks.haskeys(): - return [ retToks ] + return [retToks] else: return retToks @@ -4719,17 +5154,17 @@ class Group(TokenConverter): num = Word(nums) term = ident | num func = ident + Optional(delimitedList(term)) - print(func.parseString("fn a,b,100")) # -> ['fn', 'a', 'b', '100'] + print(func.parseString("fn a, b, 100")) # -> ['fn', 'a', 'b', '100'] func = ident + Group(Optional(delimitedList(term))) - print(func.parseString("fn a,b,100")) # -> ['fn', ['a', 'b', '100']] + print(func.parseString("fn a, b, 100")) # -> ['fn', ['a', 'b', '100']] """ - def __init__( self, expr ): - super(Group,self).__init__( expr ) - self.saveAsList = expr.saveAsList + def __init__(self, expr): + super(Group, self).__init__(expr) + self.saveAsList = True - def postParse( self, instring, loc, tokenlist ): - return [ tokenlist ] + def postParse(self, instring, loc, tokenlist): + return [tokenlist] class Dict(TokenConverter): """Converter to return a repetitive expression as a list, but also @@ -4770,31 +5205,31 @@ class Dict(TokenConverter): See more examples at :class:`ParseResults` of accessing fields by results name. """ - def __init__( self, expr ): - super(Dict,self).__init__( expr ) + def __init__(self, expr): + super(Dict, self).__init__(expr) self.saveAsList = True - def postParse( self, instring, loc, tokenlist ): - for i,tok in enumerate(tokenlist): + def postParse(self, instring, loc, tokenlist): + for i, tok in enumerate(tokenlist): if len(tok) == 0: continue ikey = tok[0] - if isinstance(ikey,int): + if isinstance(ikey, int): ikey = _ustr(tok[0]).strip() - if len(tok)==1: - tokenlist[ikey] = _ParseResultsWithOffset("",i) - elif len(tok)==2 and not isinstance(tok[1],ParseResults): - tokenlist[ikey] = _ParseResultsWithOffset(tok[1],i) + if len(tok) == 1: + tokenlist[ikey] = _ParseResultsWithOffset("", i) + elif len(tok) == 2 and not isinstance(tok[1], ParseResults): + tokenlist[ikey] = _ParseResultsWithOffset(tok[1], i) else: - dictvalue = tok.copy() #ParseResults(i) + dictvalue = tok.copy() # ParseResults(i) del dictvalue[0] - if len(dictvalue)!= 1 or (isinstance(dictvalue,ParseResults) and dictvalue.haskeys()): - tokenlist[ikey] = _ParseResultsWithOffset(dictvalue,i) + if len(dictvalue) != 1 or (isinstance(dictvalue, ParseResults) and dictvalue.haskeys()): + tokenlist[ikey] = _ParseResultsWithOffset(dictvalue, i) else: - tokenlist[ikey] = _ParseResultsWithOffset(dictvalue[0],i) + tokenlist[ikey] = _ParseResultsWithOffset(dictvalue[0], i) if self.resultsName: - return [ tokenlist ] + return [tokenlist] else: return tokenlist @@ -4821,10 +5256,10 @@ class Suppress(TokenConverter): (See also :class:`delimitedList`.) """ - def postParse( self, instring, loc, tokenlist ): + def postParse(self, instring, loc, tokenlist): return [] - def suppress( self ): + def suppress(self): return self @@ -4834,12 +5269,12 @@ class OnlyOnce(object): def __init__(self, methodCall): self.callable = _trim_arity(methodCall) self.called = False - def __call__(self,s,l,t): + def __call__(self, s, l, t): if not self.called: - results = self.callable(s,l,t) + results = self.callable(s, l, t) self.called = True return results - raise ParseException(s,l,"") + raise ParseException(s, l, "") def reset(self): self.called = False @@ -4871,16 +5306,16 @@ def traceParseAction(f): f = _trim_arity(f) def z(*paArgs): thisFunc = f.__name__ - s,l,t = paArgs[-3:] - if len(paArgs)>3: + s, l, t = paArgs[-3:] + if len(paArgs) > 3: thisFunc = paArgs[0].__class__.__name__ + '.' + thisFunc - sys.stderr.write( ">>entering %s(line: '%s', %d, %r)\n" % (thisFunc,line(l,s),l,t) ) + sys.stderr.write(">>entering %s(line: '%s', %d, %r)\n" % (thisFunc, line(l, s), l, t)) try: ret = f(*paArgs) except Exception as exc: - sys.stderr.write( "<<leaving %s (exception: %s)\n" % (thisFunc,exc) ) + sys.stderr.write("<<leaving %s (exception: %s)\n" % (thisFunc, exc)) raise - sys.stderr.write( "<<leaving %s (ret: %r)\n" % (thisFunc,ret) ) + sys.stderr.write("<<leaving %s (ret: %r)\n" % (thisFunc, ret)) return ret try: z.__name__ = f.__name__ @@ -4891,7 +5326,7 @@ def traceParseAction(f): # # global helpers # -def delimitedList( expr, delim=",", combine=False ): +def delimitedList(expr, delim=",", combine=False): """Helper to define a delimited list of expressions - the delimiter defaults to ','. By default, the list elements and delimiters can have intervening whitespace, and comments, but this can be @@ -4906,13 +5341,13 @@ def delimitedList( expr, delim=",", combine=False ): delimitedList(Word(alphas)).parseString("aa,bb,cc") # -> ['aa', 'bb', 'cc'] delimitedList(Word(hexnums), delim=':', combine=True).parseString("AA:BB:CC:DD:EE") # -> ['AA:BB:CC:DD:EE'] """ - dlName = _ustr(expr)+" ["+_ustr(delim)+" "+_ustr(expr)+"]..." + dlName = _ustr(expr) + " [" + _ustr(delim) + " " + _ustr(expr) + "]..." if combine: - return Combine( expr + ZeroOrMore( delim + expr ) ).setName(dlName) + return Combine(expr + ZeroOrMore(delim + expr)).setName(dlName) else: - return ( expr + ZeroOrMore( Suppress( delim ) + expr ) ).setName(dlName) + return (expr + ZeroOrMore(Suppress(delim) + expr)).setName(dlName) -def countedArray( expr, intExpr=None ): +def countedArray(expr, intExpr=None): """Helper to define a counted list of expressions. This helper defines a pattern of the form:: @@ -4936,22 +5371,22 @@ def countedArray( expr, intExpr=None ): countedArray(Word(alphas), intExpr=binaryConstant).parseString('10 ab cd ef') # -> ['ab', 'cd'] """ arrayExpr = Forward() - def countFieldParseAction(s,l,t): + def countFieldParseAction(s, l, t): n = t[0] - arrayExpr << (n and Group(And([expr]*n)) or Group(empty)) + arrayExpr << (n and Group(And([expr] * n)) or Group(empty)) return [] if intExpr is None: - intExpr = Word(nums).setParseAction(lambda t:int(t[0])) + intExpr = Word(nums).setParseAction(lambda t: int(t[0])) else: intExpr = intExpr.copy() intExpr.setName("arrayLen") intExpr.addParseAction(countFieldParseAction, callDuringTry=True) - return ( intExpr + arrayExpr ).setName('(len) ' + _ustr(expr) + '...') + return (intExpr + arrayExpr).setName('(len) ' + _ustr(expr) + '...') def _flatten(L): ret = [] for i in L: - if isinstance(i,list): + if isinstance(i, list): ret.extend(_flatten(i)) else: ret.append(i) @@ -4973,7 +5408,7 @@ def matchPreviousLiteral(expr): enabled. """ rep = Forward() - def copyTokenToRepeater(s,l,t): + def copyTokenToRepeater(s, l, t): if t: if len(t) == 1: rep << t[0] @@ -5005,26 +5440,26 @@ def matchPreviousExpr(expr): rep = Forward() e2 = expr.copy() rep <<= e2 - def copyTokenToRepeater(s,l,t): + def copyTokenToRepeater(s, l, t): matchTokens = _flatten(t.asList()) - def mustMatchTheseTokens(s,l,t): + def mustMatchTheseTokens(s, l, t): theseTokens = _flatten(t.asList()) - if theseTokens != matchTokens: - raise ParseException("",0,"") - rep.setParseAction( mustMatchTheseTokens, callDuringTry=True ) + if theseTokens != matchTokens: + raise ParseException('', 0, '') + rep.setParseAction(mustMatchTheseTokens, callDuringTry=True) expr.addParseAction(copyTokenToRepeater, callDuringTry=True) rep.setName('(prev) ' + _ustr(expr)) return rep def _escapeRegexRangeChars(s): - #~ escape these chars: ^-] - for c in r"\^-]": - s = s.replace(c,_bslash+c) - s = s.replace("\n",r"\n") - s = s.replace("\t",r"\t") + # ~ escape these chars: ^-[] + for c in r"\^-[]": + s = s.replace(c, _bslash + c) + s = s.replace("\n", r"\n") + s = s.replace("\t", r"\t") return _ustr(s) -def oneOf( strs, caseless=False, useRegex=True ): +def oneOf(strs, caseless=False, useRegex=True, asKeyword=False): """Helper to quickly define a set of alternative Literals, and makes sure to do longest-first testing when there is a conflict, regardless of the input order, but returns @@ -5038,8 +5473,10 @@ def oneOf( strs, caseless=False, useRegex=True ): caseless - useRegex - (default= ``True``) - as an optimization, will generate a Regex object; otherwise, will generate - a :class:`MatchFirst` object (if ``caseless=True``, or if + a :class:`MatchFirst` object (if ``caseless=True`` or ``asKeyword=True``, or if creating a :class:`Regex` raises an exception) + - asKeyword - (default=``False``) - enforce Keyword-style matching on the + generated expressions Example:: @@ -5054,57 +5491,62 @@ def oneOf( strs, caseless=False, useRegex=True ): [['B', '=', '12'], ['AA', '=', '23'], ['B', '<=', 'AA'], ['AA', '>', '12']] """ + if isinstance(caseless, basestring): + warnings.warn("More than one string argument passed to oneOf, pass " + "choices as a list or space-delimited string", stacklevel=2) + if caseless: - isequal = ( lambda a,b: a.upper() == b.upper() ) - masks = ( lambda a,b: b.upper().startswith(a.upper()) ) - parseElementClass = CaselessLiteral + isequal = (lambda a, b: a.upper() == b.upper()) + masks = (lambda a, b: b.upper().startswith(a.upper())) + parseElementClass = CaselessKeyword if asKeyword else CaselessLiteral else: - isequal = ( lambda a,b: a == b ) - masks = ( lambda a,b: b.startswith(a) ) - parseElementClass = Literal + isequal = (lambda a, b: a == b) + masks = (lambda a, b: b.startswith(a)) + parseElementClass = Keyword if asKeyword else Literal symbols = [] - if isinstance(strs,basestring): + if isinstance(strs, basestring): symbols = strs.split() elif isinstance(strs, Iterable): symbols = list(strs) else: warnings.warn("Invalid argument to oneOf, expected string or iterable", - SyntaxWarning, stacklevel=2) + SyntaxWarning, stacklevel=2) if not symbols: return NoMatch() - i = 0 - while i < len(symbols)-1: - cur = symbols[i] - for j,other in enumerate(symbols[i+1:]): - if ( isequal(other, cur) ): - del symbols[i+j+1] - break - elif ( masks(cur, other) ): - del symbols[i+j+1] - symbols.insert(i,other) - cur = other - break - else: - i += 1 - - if not caseless and useRegex: - #~ print (strs,"->", "|".join( [ _escapeRegexChars(sym) for sym in symbols] )) - try: - if len(symbols)==len("".join(symbols)): - return Regex( "[%s]" % "".join(_escapeRegexRangeChars(sym) for sym in symbols) ).setName(' | '.join(symbols)) + if not asKeyword: + # if not producing keywords, need to reorder to take care to avoid masking + # longer choices with shorter ones + i = 0 + while i < len(symbols) - 1: + cur = symbols[i] + for j, other in enumerate(symbols[i + 1:]): + if isequal(other, cur): + del symbols[i + j + 1] + break + elif masks(cur, other): + del symbols[i + j + 1] + symbols.insert(i, other) + break else: - return Regex( "|".join(re.escape(sym) for sym in symbols) ).setName(' | '.join(symbols)) + i += 1 + + if not (caseless or asKeyword) and useRegex: + # ~ print (strs, "->", "|".join([_escapeRegexChars(sym) for sym in symbols])) + try: + if len(symbols) == len("".join(symbols)): + return Regex("[%s]" % "".join(_escapeRegexRangeChars(sym) for sym in symbols)).setName(' | '.join(symbols)) + else: + return Regex("|".join(re.escape(sym) for sym in symbols)).setName(' | '.join(symbols)) except Exception: warnings.warn("Exception creating Regex for oneOf, building MatchFirst", SyntaxWarning, stacklevel=2) - # last resort, just use MatchFirst return MatchFirst(parseElementClass(sym) for sym in symbols).setName(' | '.join(symbols)) -def dictOf( key, value ): +def dictOf(key, value): """Helper to easily and clearly define a dictionary by specifying the respective patterns for the key and value. Takes care of defining the :class:`Dict`, :class:`ZeroOrMore`, and @@ -5162,8 +5604,8 @@ def originalTextFor(expr, asString=True): Example:: src = "this is test <b> bold <i>text</i> </b> normal text " - for tag in ("b","i"): - opener,closer = makeHTMLTags(tag) + for tag in ("b", "i"): + opener, closer = makeHTMLTags(tag) patt = originalTextFor(opener + SkipTo(closer) + closer) print(patt.searchString(src)[0]) @@ -5172,14 +5614,14 @@ def originalTextFor(expr, asString=True): ['<b> bold <i>text</i> </b>'] ['<i>text</i>'] """ - locMarker = Empty().setParseAction(lambda s,loc,t: loc) + locMarker = Empty().setParseAction(lambda s, loc, t: loc) endlocMarker = locMarker.copy() endlocMarker.callPreparse = False matchExpr = locMarker("_original_start") + expr + endlocMarker("_original_end") if asString: - extractText = lambda s,l,t: s[t._original_start:t._original_end] + extractText = lambda s, l, t: s[t._original_start: t._original_end] else: - def extractText(s,l,t): + def extractText(s, l, t): t[:] = [s[t.pop('_original_start'):t.pop('_original_end')]] matchExpr.setParseAction(extractText) matchExpr.ignoreExprs = expr.ignoreExprs @@ -5189,7 +5631,7 @@ def ungroup(expr): """Helper to undo pyparsing's default grouping of And expressions, even if all but one are non-empty. """ - return TokenConverter(expr).setParseAction(lambda t:t[0]) + return TokenConverter(expr).addParseAction(lambda t: t[0]) def locatedExpr(expr): """Helper to decorate a returned token with its starting and ending @@ -5216,7 +5658,7 @@ def locatedExpr(expr): [[8, 'lksdjjf', 15]] [[18, 'lkkjj', 23]] """ - locator = Empty().setParseAction(lambda s,l,t: l) + locator = Empty().setParseAction(lambda s, l, t: l) return Group(locator("locn_start") + expr("value") + locator.copy().leaveWhitespace()("locn_end")) @@ -5227,12 +5669,12 @@ lineEnd = LineEnd().setName("lineEnd") stringStart = StringStart().setName("stringStart") stringEnd = StringEnd().setName("stringEnd") -_escapedPunc = Word( _bslash, r"\[]-*.$+^?()~ ", exact=2 ).setParseAction(lambda s,l,t:t[0][1]) -_escapedHexChar = Regex(r"\\0?[xX][0-9a-fA-F]+").setParseAction(lambda s,l,t:unichr(int(t[0].lstrip(r'\0x'),16))) -_escapedOctChar = Regex(r"\\0[0-7]+").setParseAction(lambda s,l,t:unichr(int(t[0][1:],8))) +_escapedPunc = Word(_bslash, r"\[]-*.$+^?()~ ", exact=2).setParseAction(lambda s, l, t: t[0][1]) +_escapedHexChar = Regex(r"\\0?[xX][0-9a-fA-F]+").setParseAction(lambda s, l, t: unichr(int(t[0].lstrip(r'\0x'), 16))) +_escapedOctChar = Regex(r"\\0[0-7]+").setParseAction(lambda s, l, t: unichr(int(t[0][1:], 8))) _singleChar = _escapedPunc | _escapedHexChar | _escapedOctChar | CharsNotIn(r'\]', exact=1) _charRange = Group(_singleChar + Suppress("-") + _singleChar) -_reBracketExpr = Literal("[") + Optional("^").setResultsName("negate") + Group( OneOrMore( _charRange | _singleChar ) ).setResultsName("body") + "]" +_reBracketExpr = Literal("[") + Optional("^").setResultsName("negate") + Group(OneOrMore(_charRange | _singleChar)).setResultsName("body") + "]" def srange(s): r"""Helper to easily define string ranges for use in Word @@ -5260,7 +5702,7 @@ def srange(s): - any combination of the above (``'aeiouy'``, ``'a-zA-Z0-9_$'``, etc.) """ - _expanded = lambda p: p if not isinstance(p,ParseResults) else ''.join(unichr(c) for c in range(ord(p[0]),ord(p[1])+1)) + _expanded = lambda p: p if not isinstance(p, ParseResults) else ''.join(unichr(c) for c in range(ord(p[0]), ord(p[1]) + 1)) try: return "".join(_expanded(part) for part in _reBracketExpr.parseString(s).body) except Exception: @@ -5270,9 +5712,9 @@ def matchOnlyAtCol(n): """Helper method for defining parse actions that require matching at a specific column in the input text. """ - def verifyCol(strg,locn,toks): - if col(locn,strg) != n: - raise ParseException(strg,locn,"matched token not at column %d" % n) + def verifyCol(strg, locn, toks): + if col(locn, strg) != n: + raise ParseException(strg, locn, "matched token not at column %d" % n) return verifyCol def replaceWith(replStr): @@ -5288,9 +5730,9 @@ def replaceWith(replStr): OneOrMore(term).parseString("324 234 N/A 234") # -> [324, 234, nan, 234] """ - return lambda s,l,t: [replStr] + return lambda s, l, t: [replStr] -def removeQuotes(s,l,t): +def removeQuotes(s, l, t): """Helper parse action for removing quotation marks from parsed quoted strings. @@ -5341,7 +5783,7 @@ def tokenMap(func, *args): now is the winter of our discontent made glorious summer by this sun of york ['Now Is The Winter Of Our Discontent Made Glorious Summer By This Sun Of York'] """ - def pa(s,l,t): + def pa(s, l, t): return [func(tokn, *args) for tokn in t] try: @@ -5361,33 +5803,41 @@ downcaseTokens = tokenMap(lambda t: _ustr(t).lower()) """(Deprecated) Helper parse action to convert tokens to lower case. Deprecated in favor of :class:`pyparsing_common.downcaseTokens`""" -def _makeTags(tagStr, xml): +def _makeTags(tagStr, xml, + suppress_LT=Suppress("<"), + suppress_GT=Suppress(">")): """Internal helper to construct opening and closing tag expressions, given a tag name""" - if isinstance(tagStr,basestring): + if isinstance(tagStr, basestring): resname = tagStr tagStr = Keyword(tagStr, caseless=not xml) else: resname = tagStr.name - tagAttrName = Word(alphas,alphanums+"_-:") - if (xml): - tagAttrValue = dblQuotedString.copy().setParseAction( removeQuotes ) - openTag = Suppress("<") + tagStr("tag") + \ - Dict(ZeroOrMore(Group( tagAttrName + Suppress("=") + tagAttrValue ))) + \ - Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">") + tagAttrName = Word(alphas, alphanums + "_-:") + if xml: + tagAttrValue = dblQuotedString.copy().setParseAction(removeQuotes) + openTag = (suppress_LT + + tagStr("tag") + + Dict(ZeroOrMore(Group(tagAttrName + Suppress("=") + tagAttrValue))) + + Optional("/", default=[False])("empty").setParseAction(lambda s, l, t: t[0] == '/') + + suppress_GT) else: - printablesLessRAbrack = "".join(c for c in printables if c not in ">") - tagAttrValue = quotedString.copy().setParseAction( removeQuotes ) | Word(printablesLessRAbrack) - openTag = Suppress("<") + tagStr("tag") + \ - Dict(ZeroOrMore(Group( tagAttrName.setParseAction(downcaseTokens) + \ - Optional( Suppress("=") + tagAttrValue ) ))) + \ - Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">") - closeTag = Combine(_L("</") + tagStr + ">") + tagAttrValue = quotedString.copy().setParseAction(removeQuotes) | Word(printables, excludeChars=">") + openTag = (suppress_LT + + tagStr("tag") + + Dict(ZeroOrMore(Group(tagAttrName.setParseAction(downcaseTokens) + + Optional(Suppress("=") + tagAttrValue)))) + + Optional("/", default=[False])("empty").setParseAction(lambda s, l, t: t[0] == '/') + + suppress_GT) + closeTag = Combine(_L("</") + tagStr + ">", adjacent=False) - openTag = openTag.setResultsName("start"+"".join(resname.replace(":"," ").title().split())).setName("<%s>" % resname) - closeTag = closeTag.setResultsName("end"+"".join(resname.replace(":"," ").title().split())).setName("</%s>" % resname) + openTag.setName("<%s>" % resname) + # add start<tagname> results name in parse action now that ungrouped names are not reported at two levels + openTag.addParseAction(lambda t: t.__setitem__("start" + "".join(resname.replace(":", " ").title().split()), t.copy())) + closeTag = closeTag("end" + "".join(resname.replace(":", " ").title().split())).setName("</%s>" % resname) openTag.tag = resname closeTag.tag = resname + openTag.tag_body = SkipTo(closeTag()) return openTag, closeTag def makeHTMLTags(tagStr): @@ -5400,7 +5850,7 @@ def makeHTMLTags(tagStr): text = '<td>More info at the <a href="https://github.com/pyparsing/pyparsing/wiki">pyparsing</a> wiki page</td>' # makeHTMLTags returns pyparsing expressions for the opening and # closing tags as a 2-tuple - a,a_end = makeHTMLTags("A") + a, a_end = makeHTMLTags("A") link_expr = a + SkipTo(a_end)("link_text") + a_end for link in link_expr.searchString(text): @@ -5412,7 +5862,7 @@ def makeHTMLTags(tagStr): pyparsing -> https://github.com/pyparsing/pyparsing/wiki """ - return _makeTags( tagStr, False ) + return _makeTags(tagStr, False) def makeXMLTags(tagStr): """Helper to construct opening and closing tag expressions for XML, @@ -5420,9 +5870,9 @@ def makeXMLTags(tagStr): Example: similar to :class:`makeHTMLTags` """ - return _makeTags( tagStr, True ) + return _makeTags(tagStr, True) -def withAttribute(*args,**attrDict): +def withAttribute(*args, **attrDict): """Helper to create a validating parse action to be used with start tags created with :class:`makeXMLTags` or :class:`makeHTMLTags`. Use ``withAttribute`` to qualify @@ -5435,7 +5885,7 @@ def withAttribute(*args,**attrDict): - keyword arguments, as in ``(align="right")``, or - as an explicit dict with ``**`` operator, when an attribute name is also a Python reserved word, as in ``**{"class":"Customer", "align":"right"}`` - - a list of name-value tuples, as in ``(("ns1:class", "Customer"), ("ns2:align","right"))`` + - a list of name-value tuples, as in ``(("ns1:class", "Customer"), ("ns2:align", "right"))`` For attribute names with a namespace prefix, you must use the second form. Attribute names are matched insensitive to upper/lower case. @@ -5482,13 +5932,13 @@ def withAttribute(*args,**attrDict): attrs = args[:] else: attrs = attrDict.items() - attrs = [(k,v) for k,v in attrs] - def pa(s,l,tokens): - for attrName,attrValue in attrs: + attrs = [(k, v) for k, v in attrs] + def pa(s, l, tokens): + for attrName, attrValue in attrs: if attrName not in tokens: - raise ParseException(s,l,"no matching attribute " + attrName) + raise ParseException(s, l, "no matching attribute " + attrName) if attrValue != withAttribute.ANY_VALUE and tokens[attrName] != attrValue: - raise ParseException(s,l,"attribute '%s' has value '%s', must be '%s'" % + raise ParseException(s, l, "attribute '%s' has value '%s', must be '%s'" % (attrName, tokens[attrName], attrValue)) return pa withAttribute.ANY_VALUE = object() @@ -5529,13 +5979,13 @@ def withClass(classname, namespace=''): 1,3 2,3 1,1 """ classattr = "%s:class" % namespace if namespace else "class" - return withAttribute(**{classattr : classname}) + return withAttribute(**{classattr: classname}) opAssoc = SimpleNamespace() opAssoc.LEFT = object() opAssoc.RIGHT = object() -def infixNotation( baseExpr, opList, lpar=Suppress('('), rpar=Suppress(')') ): +def infixNotation(baseExpr, opList, lpar=Suppress('('), rpar=Suppress(')')): """Helper method for constructing grammars of expressions made up of operators working in a precedence hierarchy. Operators may be unary or binary, left- or right-associative. Parse actions can also be @@ -5613,9 +6063,9 @@ def infixNotation( baseExpr, opList, lpar=Suppress('('), rpar=Suppress(')') ): return loc, [] ret = Forward() - lastExpr = baseExpr | ( lpar + ret + rpar ) - for i,operDef in enumerate(opList): - opExpr,arity,rightLeftAssoc,pa = (operDef + (None,))[:4] + lastExpr = baseExpr | (lpar + ret + rpar) + for i, operDef in enumerate(opList): + opExpr, arity, rightLeftAssoc, pa = (operDef + (None, ))[:4] termName = "%s term" % opExpr if arity < 3 else "%s%s term" % opExpr if arity == 3: if opExpr is None or len(opExpr) != 2: @@ -5625,15 +6075,15 @@ def infixNotation( baseExpr, opList, lpar=Suppress('('), rpar=Suppress(')') ): thisExpr = Forward().setName(termName) if rightLeftAssoc == opAssoc.LEFT: if arity == 1: - matchExpr = _FB(lastExpr + opExpr) + Group( lastExpr + OneOrMore( opExpr ) ) + matchExpr = _FB(lastExpr + opExpr) + Group(lastExpr + OneOrMore(opExpr)) elif arity == 2: if opExpr is not None: - matchExpr = _FB(lastExpr + opExpr + lastExpr) + Group( lastExpr + OneOrMore( opExpr + lastExpr ) ) + matchExpr = _FB(lastExpr + opExpr + lastExpr) + Group(lastExpr + OneOrMore(opExpr + lastExpr)) else: - matchExpr = _FB(lastExpr+lastExpr) + Group( lastExpr + OneOrMore(lastExpr) ) + matchExpr = _FB(lastExpr + lastExpr) + Group(lastExpr + OneOrMore(lastExpr)) elif arity == 3: - matchExpr = _FB(lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr) + \ - Group( lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr ) + matchExpr = (_FB(lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr) + + Group(lastExpr + OneOrMore(opExpr1 + lastExpr + opExpr2 + lastExpr))) else: raise ValueError("operator must be unary (1), binary (2), or ternary (3)") elif rightLeftAssoc == opAssoc.RIGHT: @@ -5641,15 +6091,15 @@ def infixNotation( baseExpr, opList, lpar=Suppress('('), rpar=Suppress(')') ): # try to avoid LR with this extra test if not isinstance(opExpr, Optional): opExpr = Optional(opExpr) - matchExpr = _FB(opExpr.expr + thisExpr) + Group( opExpr + thisExpr ) + matchExpr = _FB(opExpr.expr + thisExpr) + Group(opExpr + thisExpr) elif arity == 2: if opExpr is not None: - matchExpr = _FB(lastExpr + opExpr + thisExpr) + Group( lastExpr + OneOrMore( opExpr + thisExpr ) ) + matchExpr = _FB(lastExpr + opExpr + thisExpr) + Group(lastExpr + OneOrMore(opExpr + thisExpr)) else: - matchExpr = _FB(lastExpr + thisExpr) + Group( lastExpr + OneOrMore( thisExpr ) ) + matchExpr = _FB(lastExpr + thisExpr) + Group(lastExpr + OneOrMore(thisExpr)) elif arity == 3: - matchExpr = _FB(lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr) + \ - Group( lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr ) + matchExpr = (_FB(lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr) + + Group(lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr)) else: raise ValueError("operator must be unary (1), binary (2), or ternary (3)") else: @@ -5659,7 +6109,7 @@ def infixNotation( baseExpr, opList, lpar=Suppress('('), rpar=Suppress(')') ): matchExpr.setParseAction(*pa) else: matchExpr.setParseAction(pa) - thisExpr <<= ( matchExpr.setName(termName) | lastExpr ) + thisExpr <<= (matchExpr.setName(termName) | lastExpr) lastExpr = thisExpr ret <<= lastExpr return ret @@ -5668,10 +6118,10 @@ operatorPrecedence = infixNotation """(Deprecated) Former name of :class:`infixNotation`, will be dropped in a future release.""" -dblQuotedString = Combine(Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*')+'"').setName("string enclosed in double quotes") -sglQuotedString = Combine(Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*")+"'").setName("string enclosed in single quotes") -quotedString = Combine(Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*')+'"'| - Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*")+"'").setName("quotedString using single or double quotes") +dblQuotedString = Combine(Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*') + '"').setName("string enclosed in double quotes") +sglQuotedString = Combine(Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*") + "'").setName("string enclosed in single quotes") +quotedString = Combine(Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*') + '"' + | Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*") + "'").setName("quotedString using single or double quotes") unicodeString = Combine(_L('u') + quotedString.copy()).setName("unicode string literal") def nestedExpr(opener="(", closer=")", content=None, ignoreExpr=quotedString.copy()): @@ -5707,7 +6157,7 @@ def nestedExpr(opener="(", closer=")", content=None, ignoreExpr=quotedString.cop ident = Word(alphas+'_', alphanums+'_') number = pyparsing_common.number arg = Group(decl_data_type + ident) - LPAR,RPAR = map(Suppress, "()") + LPAR, RPAR = map(Suppress, "()") code_body = nestedExpr('{', '}', ignoreExpr=(quotedString | cStyleComment)) @@ -5742,33 +6192,40 @@ def nestedExpr(opener="(", closer=")", content=None, ignoreExpr=quotedString.cop if opener == closer: raise ValueError("opening and closing strings cannot be the same") if content is None: - if isinstance(opener,basestring) and isinstance(closer,basestring): - if len(opener) == 1 and len(closer)==1: + if isinstance(opener, basestring) and isinstance(closer, basestring): + if len(opener) == 1 and len(closer) == 1: if ignoreExpr is not None: - content = (Combine(OneOrMore(~ignoreExpr + - CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS,exact=1)) - ).setParseAction(lambda t:t[0].strip())) + content = (Combine(OneOrMore(~ignoreExpr + + CharsNotIn(opener + + closer + + ParserElement.DEFAULT_WHITE_CHARS, exact=1) + ) + ).setParseAction(lambda t: t[0].strip())) else: - content = (empty.copy()+CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS - ).setParseAction(lambda t:t[0].strip())) + content = (empty.copy() + CharsNotIn(opener + + closer + + ParserElement.DEFAULT_WHITE_CHARS + ).setParseAction(lambda t: t[0].strip())) else: if ignoreExpr is not None: - content = (Combine(OneOrMore(~ignoreExpr + - ~Literal(opener) + ~Literal(closer) + - CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) - ).setParseAction(lambda t:t[0].strip())) + content = (Combine(OneOrMore(~ignoreExpr + + ~Literal(opener) + + ~Literal(closer) + + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS, exact=1)) + ).setParseAction(lambda t: t[0].strip())) else: - content = (Combine(OneOrMore(~Literal(opener) + ~Literal(closer) + - CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) - ).setParseAction(lambda t:t[0].strip())) + content = (Combine(OneOrMore(~Literal(opener) + + ~Literal(closer) + + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS, exact=1)) + ).setParseAction(lambda t: t[0].strip())) else: raise ValueError("opening and closing arguments must be strings if no content expression is given") ret = Forward() if ignoreExpr is not None: - ret <<= Group( Suppress(opener) + ZeroOrMore( ignoreExpr | ret | content ) + Suppress(closer) ) + ret <<= Group(Suppress(opener) + ZeroOrMore(ignoreExpr | ret | content) + Suppress(closer)) else: - ret <<= Group( Suppress(opener) + ZeroOrMore( ret | content ) + Suppress(closer) ) - ret.setName('nested %s%s expression' % (opener,closer)) + ret <<= Group(Suppress(opener) + ZeroOrMore(ret | content) + Suppress(closer)) + ret.setName('nested %s%s expression' % (opener, closer)) return ret def indentedBlock(blockStatementExpr, indentStack, indent=True): @@ -5783,7 +6240,7 @@ def indentedBlock(blockStatementExpr, indentStack, indent=True): (multiple statementWithIndentedBlock expressions within a single grammar should share a common indentStack) - indent - boolean indicating whether block must be indented beyond - the the current level; set to False for block of left-most + the current level; set to False for block of left-most statements (default= ``True``) A valid block must contain at least one ``blockStatement``. @@ -5816,15 +6273,15 @@ def indentedBlock(blockStatementExpr, indentStack, indent=True): stmt = Forward() identifier = Word(alphas, alphanums) - funcDecl = ("def" + identifier + Group( "(" + Optional( delimitedList(identifier) ) + ")" ) + ":") + funcDecl = ("def" + identifier + Group("(" + Optional(delimitedList(identifier)) + ")") + ":") func_body = indentedBlock(stmt, indentStack) - funcDef = Group( funcDecl + func_body ) + funcDef = Group(funcDecl + func_body) rvalue = Forward() funcCall = Group(identifier + "(" + Optional(delimitedList(rvalue)) + ")") rvalue << (funcCall | identifier | Word(nums)) assignment = Group(identifier + "=" + rvalue) - stmt << ( funcDef | assignment | identifier ) + stmt << (funcDef | assignment | identifier) module_body = OneOrMore(stmt) @@ -5852,47 +6309,56 @@ def indentedBlock(blockStatementExpr, indentStack, indent=True): ':', [[['def', 'eggs', ['(', 'z', ')'], ':', [['pass']]]]]]] """ - def checkPeerIndent(s,l,t): + backup_stack = indentStack[:] + + def reset_stack(): + indentStack[:] = backup_stack + + def checkPeerIndent(s, l, t): if l >= len(s): return - curCol = col(l,s) + curCol = col(l, s) if curCol != indentStack[-1]: if curCol > indentStack[-1]: - raise ParseFatalException(s,l,"illegal nesting") - raise ParseException(s,l,"not a peer entry") + raise ParseException(s, l, "illegal nesting") + raise ParseException(s, l, "not a peer entry") - def checkSubIndent(s,l,t): - curCol = col(l,s) + def checkSubIndent(s, l, t): + curCol = col(l, s) if curCol > indentStack[-1]: - indentStack.append( curCol ) + indentStack.append(curCol) else: - raise ParseException(s,l,"not a subentry") + raise ParseException(s, l, "not a subentry") - def checkUnindent(s,l,t): + def checkUnindent(s, l, t): if l >= len(s): return - curCol = col(l,s) - if not(indentStack and curCol < indentStack[-1] and curCol <= indentStack[-2]): - raise ParseException(s,l,"not an unindent") - indentStack.pop() + curCol = col(l, s) + if not(indentStack and curCol in indentStack): + raise ParseException(s, l, "not an unindent") + if curCol < indentStack[-1]: + indentStack.pop() - NL = OneOrMore(LineEnd().setWhitespaceChars("\t ").suppress()) + NL = OneOrMore(LineEnd().setWhitespaceChars("\t ").suppress(), stopOn=StringEnd()) INDENT = (Empty() + Empty().setParseAction(checkSubIndent)).setName('INDENT') PEER = Empty().setParseAction(checkPeerIndent).setName('') UNDENT = Empty().setParseAction(checkUnindent).setName('UNINDENT') if indent: - smExpr = Group( Optional(NL) + - #~ FollowedBy(blockStatementExpr) + - INDENT + (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) + UNDENT) + smExpr = Group(Optional(NL) + + INDENT + + OneOrMore(PEER + Group(blockStatementExpr) + Optional(NL), stopOn=StringEnd()) + + UNDENT) else: - smExpr = Group( Optional(NL) + - (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) ) + smExpr = Group(Optional(NL) + + OneOrMore(PEER + Group(blockStatementExpr) + Optional(NL), stopOn=StringEnd()) + + UNDENT) + smExpr.setFailAction(lambda a, b, c, d: reset_stack()) blockStatementExpr.ignore(_bslash + LineEnd()) return smExpr.setName('indented block') alphas8bit = srange(r"[\0xc0-\0xd6\0xd8-\0xf6\0xf8-\0xff]") punc8bit = srange(r"[\0xa1-\0xbf\0xd7\0xf7]") -anyOpenTag,anyCloseTag = makeHTMLTags(Word(alphas,alphanums+"_:").setName('any tag')) -_htmlEntityMap = dict(zip("gt lt amp nbsp quot apos".split(),'><& "\'')) +anyOpenTag, anyCloseTag = makeHTMLTags(Word(alphas, alphanums + "_:").setName('any tag')) +_htmlEntityMap = dict(zip("gt lt amp nbsp quot apos".split(), '><& "\'')) commonHTMLEntity = Regex('&(?P<entity>' + '|'.join(_htmlEntityMap.keys()) +");").setName("common HTML entity") def replaceHTMLEntity(t): """Helper parser action to replace common HTML entities with their special characters""" @@ -5909,7 +6375,7 @@ restOfLine = Regex(r".*").leaveWhitespace().setName("rest of line") dblSlashComment = Regex(r"//(?:\\\n|[^\n])*").setName("// comment") "Comment of the form ``// ... (to end of line)``" -cppStyleComment = Combine(Regex(r"/\*(?:[^*]|\*(?!/))*") + '*/'| dblSlashComment).setName("C++ style comment") +cppStyleComment = Combine(Regex(r"/\*(?:[^*]|\*(?!/))*") + '*/' | dblSlashComment).setName("C++ style comment") "Comment of either form :class:`cStyleComment` or :class:`dblSlashComment`" javaStyleComment = cppStyleComment @@ -5918,10 +6384,10 @@ javaStyleComment = cppStyleComment pythonStyleComment = Regex(r"#.*").setName("Python style comment") "Comment of the form ``# ... (to end of line)``" -_commasepitem = Combine(OneOrMore(Word(printables, excludeChars=',') + - Optional( Word(" \t") + - ~Literal(",") + ~LineEnd() ) ) ).streamline().setName("commaItem") -commaSeparatedList = delimitedList( Optional( quotedString.copy() | _commasepitem, default="") ).setName("commaSeparatedList") +_commasepitem = Combine(OneOrMore(Word(printables, excludeChars=',') + + Optional(Word(" \t") + + ~Literal(",") + ~LineEnd()))).streamline().setName("commaItem") +commaSeparatedList = delimitedList(Optional(quotedString.copy() | _commasepitem, default="")).setName("commaSeparatedList") """(Deprecated) Predefined expression of 1 or more printable words or quoted strings, separated by commas. @@ -6087,7 +6553,7 @@ class pyparsing_common: integer = Word(nums).setName("integer").setParseAction(convertToInteger) """expression that parses an unsigned integer, returns an int""" - hex_integer = Word(hexnums).setName("hex integer").setParseAction(tokenMap(int,16)) + hex_integer = Word(hexnums).setName("hex integer").setParseAction(tokenMap(int, 16)) """expression that parses a hexadecimal integer, returns an int""" signed_integer = Regex(r'[+-]?\d+').setName("signed integer").setParseAction(convertToInteger) @@ -6101,10 +6567,10 @@ class pyparsing_common: """mixed integer of the form 'integer - fraction', with optional leading integer, returns float""" mixed_integer.addParseAction(sum) - real = Regex(r'[+-]?\d+\.\d*').setName("real number").setParseAction(convertToFloat) + real = Regex(r'[+-]?(?:\d+\.\d*|\.\d+)').setName("real number").setParseAction(convertToFloat) """expression that parses a floating point number and returns a float""" - sci_real = Regex(r'[+-]?\d+([eE][+-]?\d+|\.\d*([eE][+-]?\d+)?)').setName("real number with scientific notation").setParseAction(convertToFloat) + sci_real = Regex(r'[+-]?(?:\d+(?:[eE][+-]?\d+)|(?:\d+\.\d*|\.\d+)(?:[eE][+-]?\d+)?)').setName("real number with scientific notation").setParseAction(convertToFloat) """expression that parses a floating point number with optional scientific notation and returns a float""" @@ -6115,15 +6581,18 @@ class pyparsing_common: fnumber = Regex(r'[+-]?\d+\.?\d*([eE][+-]?\d+)?').setName("fnumber").setParseAction(convertToFloat) """any int or real number, returned as float""" - identifier = Word(alphas+'_', alphanums+'_').setName("identifier") + identifier = Word(alphas + '_', alphanums + '_').setName("identifier") """typical code identifier (leading alpha or '_', followed by 0 or more alphas, nums, or '_')""" ipv4_address = Regex(r'(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})){3}').setName("IPv4 address") "IPv4 address (``0.0.0.0 - 255.255.255.255``)" _ipv6_part = Regex(r'[0-9a-fA-F]{1,4}').setName("hex_integer") - _full_ipv6_address = (_ipv6_part + (':' + _ipv6_part)*7).setName("full IPv6 address") - _short_ipv6_address = (Optional(_ipv6_part + (':' + _ipv6_part)*(0,6)) + "::" + Optional(_ipv6_part + (':' + _ipv6_part)*(0,6))).setName("short IPv6 address") + _full_ipv6_address = (_ipv6_part + (':' + _ipv6_part) * 7).setName("full IPv6 address") + _short_ipv6_address = (Optional(_ipv6_part + (':' + _ipv6_part) * (0, 6)) + + "::" + + Optional(_ipv6_part + (':' + _ipv6_part) * (0, 6)) + ).setName("short IPv6 address") _short_ipv6_address.addCondition(lambda t: sum(1 for tt in t if pyparsing_common._ipv6_part.matches(tt)) < 8) _mixed_ipv6_address = ("::ffff:" + ipv4_address).setName("mixed IPv6 address") ipv6_address = Combine((_full_ipv6_address | _mixed_ipv6_address | _short_ipv6_address).setName("IPv6 address")).setName("IPv6 address") @@ -6150,7 +6619,7 @@ class pyparsing_common: [datetime.date(1999, 12, 31)] """ - def cvt_fn(s,l,t): + def cvt_fn(s, l, t): try: return datetime.strptime(t[0], fmt).date() except ValueError as ve: @@ -6175,7 +6644,7 @@ class pyparsing_common: [datetime.datetime(1999, 12, 31, 23, 59, 59, 999000)] """ - def cvt_fn(s,l,t): + def cvt_fn(s, l, t): try: return datetime.strptime(t[0], fmt) except ValueError as ve: @@ -6200,7 +6669,7 @@ class pyparsing_common: # strip HTML links from normal text text = '<td>More info at the <a href="https://github.com/pyparsing/pyparsing/wiki">pyparsing</a> wiki page</td>' - td,td_end = makeHTMLTags("TD") + td, td_end = makeHTMLTags("TD") table_text = td + SkipTo(td_end).setParseAction(pyparsing_common.stripHTMLTags)("body") + td_end print(table_text.parseString(text).body) @@ -6210,9 +6679,13 @@ class pyparsing_common: """ return pyparsing_common._html_stripper.transformString(tokens[0]) - _commasepitem = Combine(OneOrMore(~Literal(",") + ~LineEnd() + Word(printables, excludeChars=',') - + Optional( White(" \t") ) ) ).streamline().setName("commaItem") - comma_separated_list = delimitedList( Optional( quotedString.copy() | _commasepitem, default="") ).setName("comma separated list") + _commasepitem = Combine(OneOrMore(~Literal(",") + + ~LineEnd() + + Word(printables, excludeChars=',') + + Optional(White(" \t")))).streamline().setName("commaItem") + comma_separated_list = delimitedList(Optional(quotedString.copy() + | _commasepitem, default='') + ).setName("comma separated list") """Predefined expression of 1 or more printable words or quoted strings, separated by commas.""" upcaseTokens = staticmethod(tokenMap(lambda t: _ustr(t).upper())) @@ -6231,7 +6704,8 @@ class _lazyclassproperty(object): def __get__(self, obj, cls): if cls is None: cls = type(obj) - if not hasattr(cls, '_intern') or any(cls._intern is getattr(superclass, '_intern', []) for superclass in cls.__mro__[1:]): + if not hasattr(cls, '_intern') or any(cls._intern is getattr(superclass, '_intern', []) + for superclass in cls.__mro__[1:]): cls._intern = {} attrname = self.fn.__name__ if attrname not in cls._intern: @@ -6262,7 +6736,7 @@ class unicode_set(object): if cc is unicode_set: break for rr in cc._ranges: - ret.extend(range(rr[0], rr[-1]+1)) + ret.extend(range(rr[0], rr[-1] + 1)) return [unichr(c) for c in sorted(set(ret))] @_lazyclassproperty @@ -6318,27 +6792,27 @@ class pyparsing_unicode(unicode_set): class Chinese(unicode_set): "Unicode set for Chinese Unicode Character Range" - _ranges = [(0x4e00, 0x9fff), (0x3000, 0x303f), ] + _ranges = [(0x4e00, 0x9fff), (0x3000, 0x303f),] class Japanese(unicode_set): "Unicode set for Japanese Unicode Character Range, combining Kanji, Hiragana, and Katakana ranges" - _ranges = [ ] + _ranges = [] class Kanji(unicode_set): "Unicode set for Kanji Unicode Character Range" - _ranges = [(0x4E00, 0x9Fbf), (0x3000, 0x303f), ] + _ranges = [(0x4E00, 0x9Fbf), (0x3000, 0x303f),] class Hiragana(unicode_set): "Unicode set for Hiragana Unicode Character Range" - _ranges = [(0x3040, 0x309f), ] + _ranges = [(0x3040, 0x309f),] class Katakana(unicode_set): "Unicode set for Katakana Unicode Character Range" - _ranges = [(0x30a0, 0x30ff), ] + _ranges = [(0x30a0, 0x30ff),] class Korean(unicode_set): "Unicode set for Korean Unicode Character Range" - _ranges = [(0xac00, 0xd7af), (0x1100, 0x11ff), (0x3130, 0x318f), (0xa960, 0xa97f), (0xd7b0, 0xd7ff), (0x3000, 0x303f), ] + _ranges = [(0xac00, 0xd7af), (0x1100, 0x11ff), (0x3130, 0x318f), (0xa960, 0xa97f), (0xd7b0, 0xd7ff), (0x3000, 0x303f),] class CJK(Chinese, Japanese, Korean): "Unicode set for combined Chinese, Japanese, and Korean (CJK) Unicode Character Range" @@ -6346,15 +6820,15 @@ class pyparsing_unicode(unicode_set): class Thai(unicode_set): "Unicode set for Thai Unicode Character Range" - _ranges = [(0x0e01, 0x0e3a), (0x0e3f, 0x0e5b), ] + _ranges = [(0x0e01, 0x0e3a), (0x0e3f, 0x0e5b),] class Arabic(unicode_set): "Unicode set for Arabic Unicode Character Range" - _ranges = [(0x0600, 0x061b), (0x061e, 0x06ff), (0x0700, 0x077f), ] + _ranges = [(0x0600, 0x061b), (0x061e, 0x06ff), (0x0700, 0x077f),] class Hebrew(unicode_set): "Unicode set for Hebrew Unicode Character Range" - _ranges = [(0x0590, 0x05ff), ] + _ranges = [(0x0590, 0x05ff),] class Devanagari(unicode_set): "Unicode set for Devanagari Unicode Character Range" @@ -6366,18 +6840,199 @@ pyparsing_unicode.Japanese._ranges = (pyparsing_unicode.Japanese.Kanji._ranges # define ranges in language character sets if PY_3: - setattr(pyparsing_unicode, "العربية", pyparsing_unicode.Arabic) - setattr(pyparsing_unicode, "中文", pyparsing_unicode.Chinese) - setattr(pyparsing_unicode, "кириллица", pyparsing_unicode.Cyrillic) - setattr(pyparsing_unicode, "Ελληνικά", pyparsing_unicode.Greek) - setattr(pyparsing_unicode, "עִברִית", pyparsing_unicode.Hebrew) - setattr(pyparsing_unicode, "日本語", pyparsing_unicode.Japanese) - setattr(pyparsing_unicode.Japanese, "漢字", pyparsing_unicode.Japanese.Kanji) - setattr(pyparsing_unicode.Japanese, "カタカナ", pyparsing_unicode.Japanese.Katakana) - setattr(pyparsing_unicode.Japanese, "ひらがな", pyparsing_unicode.Japanese.Hiragana) - setattr(pyparsing_unicode, "한국어", pyparsing_unicode.Korean) - setattr(pyparsing_unicode, "ไทย", pyparsing_unicode.Thai) - setattr(pyparsing_unicode, "देवनागरी", pyparsing_unicode.Devanagari) + setattr(pyparsing_unicode, u"العربية", pyparsing_unicode.Arabic) + setattr(pyparsing_unicode, u"中文", pyparsing_unicode.Chinese) + setattr(pyparsing_unicode, u"кириллица", pyparsing_unicode.Cyrillic) + setattr(pyparsing_unicode, u"Ελληνικά", pyparsing_unicode.Greek) + setattr(pyparsing_unicode, u"עִברִית", pyparsing_unicode.Hebrew) + setattr(pyparsing_unicode, u"日本語", pyparsing_unicode.Japanese) + setattr(pyparsing_unicode.Japanese, u"漢字", pyparsing_unicode.Japanese.Kanji) + setattr(pyparsing_unicode.Japanese, u"カタカナ", pyparsing_unicode.Japanese.Katakana) + setattr(pyparsing_unicode.Japanese, u"ひらがな", pyparsing_unicode.Japanese.Hiragana) + setattr(pyparsing_unicode, u"한국어", pyparsing_unicode.Korean) + setattr(pyparsing_unicode, u"ไทย", pyparsing_unicode.Thai) + setattr(pyparsing_unicode, u"देवनागरी", pyparsing_unicode.Devanagari) + + +class pyparsing_test: + """ + namespace class for classes useful in writing unit tests + """ + + class reset_pyparsing_context: + """ + Context manager to be used when writing unit tests that modify pyparsing config values: + - packrat parsing + - default whitespace characters. + - default keyword characters + - literal string auto-conversion class + - __diag__ settings + + Example: + with reset_pyparsing_context(): + # test that literals used to construct a grammar are automatically suppressed + ParserElement.inlineLiteralsUsing(Suppress) + + term = Word(alphas) | Word(nums) + group = Group('(' + term[...] + ')') + + # assert that the '()' characters are not included in the parsed tokens + self.assertParseAndCheckLisst(group, "(abc 123 def)", ['abc', '123', 'def']) + + # after exiting context manager, literals are converted to Literal expressions again + """ + + def __init__(self): + self._save_context = {} + + def save(self): + self._save_context["default_whitespace"] = ParserElement.DEFAULT_WHITE_CHARS + self._save_context["default_keyword_chars"] = Keyword.DEFAULT_KEYWORD_CHARS + self._save_context[ + "literal_string_class" + ] = ParserElement._literalStringClass + self._save_context["packrat_enabled"] = ParserElement._packratEnabled + self._save_context["packrat_parse"] = ParserElement._parse + self._save_context["__diag__"] = { + name: getattr(__diag__, name) for name in __diag__._all_names + } + self._save_context["__compat__"] = { + "collect_all_And_tokens": __compat__.collect_all_And_tokens + } + return self + + def restore(self): + # reset pyparsing global state + if ( + ParserElement.DEFAULT_WHITE_CHARS + != self._save_context["default_whitespace"] + ): + ParserElement.setDefaultWhitespaceChars( + self._save_context["default_whitespace"] + ) + Keyword.DEFAULT_KEYWORD_CHARS = self._save_context["default_keyword_chars"] + ParserElement.inlineLiteralsUsing( + self._save_context["literal_string_class"] + ) + for name, value in self._save_context["__diag__"].items(): + setattr(__diag__, name, value) + ParserElement._packratEnabled = self._save_context["packrat_enabled"] + ParserElement._parse = self._save_context["packrat_parse"] + __compat__.collect_all_And_tokens = self._save_context["__compat__"] + + def __enter__(self): + return self.save() + + def __exit__(self, *args): + return self.restore() + + class TestParseResultsAsserts: + """ + A mixin class to add parse results assertion methods to normal unittest.TestCase classes. + """ + def assertParseResultsEquals( + self, result, expected_list=None, expected_dict=None, msg=None + ): + """ + Unit test assertion to compare a ParseResults object with an optional expected_list, + and compare any defined results names with an optional expected_dict. + """ + if expected_list is not None: + self.assertEqual(expected_list, result.asList(), msg=msg) + if expected_dict is not None: + self.assertEqual(expected_dict, result.asDict(), msg=msg) + + def assertParseAndCheckList( + self, expr, test_string, expected_list, msg=None, verbose=True + ): + """ + Convenience wrapper assert to test a parser element and input string, and assert that + the resulting ParseResults.asList() is equal to the expected_list. + """ + result = expr.parseString(test_string, parseAll=True) + if verbose: + print(result.dump()) + self.assertParseResultsEquals(result, expected_list=expected_list, msg=msg) + + def assertParseAndCheckDict( + self, expr, test_string, expected_dict, msg=None, verbose=True + ): + """ + Convenience wrapper assert to test a parser element and input string, and assert that + the resulting ParseResults.asDict() is equal to the expected_dict. + """ + result = expr.parseString(test_string, parseAll=True) + if verbose: + print(result.dump()) + self.assertParseResultsEquals(result, expected_dict=expected_dict, msg=msg) + + def assertRunTestResults( + self, run_tests_report, expected_parse_results=None, msg=None + ): + """ + Unit test assertion to evaluate output of ParserElement.runTests(). If a list of + list-dict tuples is given as the expected_parse_results argument, then these are zipped + with the report tuples returned by runTests and evaluated using assertParseResultsEquals. + Finally, asserts that the overall runTests() success value is True. + + :param run_tests_report: tuple(bool, [tuple(str, ParseResults or Exception)]) returned from runTests + :param expected_parse_results (optional): [tuple(str, list, dict, Exception)] + """ + run_test_success, run_test_results = run_tests_report + + if expected_parse_results is not None: + merged = [ + (rpt[0], rpt[1], expected) + for rpt, expected in zip(run_test_results, expected_parse_results) + ] + for test_string, result, expected in merged: + # expected should be a tuple containing a list and/or a dict or an exception, + # and optional failure message string + # an empty tuple will skip any result validation + fail_msg = next( + (exp for exp in expected if isinstance(exp, str)), None + ) + expected_exception = next( + ( + exp + for exp in expected + if isinstance(exp, type) and issubclass(exp, Exception) + ), + None, + ) + if expected_exception is not None: + with self.assertRaises( + expected_exception=expected_exception, msg=fail_msg or msg + ): + if isinstance(result, Exception): + raise result + else: + expected_list = next( + (exp for exp in expected if isinstance(exp, list)), None + ) + expected_dict = next( + (exp for exp in expected if isinstance(exp, dict)), None + ) + if (expected_list, expected_dict) != (None, None): + self.assertParseResultsEquals( + result, + expected_list=expected_list, + expected_dict=expected_dict, + msg=fail_msg or msg, + ) + else: + # warning here maybe? + print("no validation for {!r}".format(test_string)) + + # do this last, in case some specific test results can be reported instead + self.assertTrue( + run_test_success, msg=msg if msg is not None else "failed runTests" + ) + + @contextmanager + def assertRaisesParseException(self, exc_type=ParseException, msg=None): + with self.assertRaises(exc_type, msg=msg): + yield if __name__ == "__main__": diff --git a/pipenv/vendor/pythonfinder/__init__.py b/pipenv/vendor/pythonfinder/__init__.py index 428599bd..7ff28b14 100644 --- a/pipenv/vendor/pythonfinder/__init__.py +++ b/pipenv/vendor/pythonfinder/__init__.py @@ -10,7 +10,7 @@ from .exceptions import InvalidPythonVersion from .models import SystemPath, WindowsFinder from .pythonfinder import Finder -__version__ = "1.2.2.dev0" +__version__ = "1.2.9" logger = logging.getLogger(__name__) diff --git a/pipenv/vendor/pythonfinder/__main__.py b/pipenv/vendor/pythonfinder/__main__.py index 3083e72d..e6d7c950 100644 --- a/pipenv/vendor/pythonfinder/__main__.py +++ b/pipenv/vendor/pythonfinder/__main__.py @@ -6,7 +6,7 @@ from __future__ import absolute_import import os import sys -from pythonfinder.cli import cli +from pipenv.vendor.pythonfinder.cli import cli PYTHONFINDER_MAIN = os.path.dirname(os.path.abspath(__file__)) diff --git a/pipenv/vendor/pythonfinder/_vendor/pep514tools/__init__.py b/pipenv/vendor/pythonfinder/_vendor/pep514tools/__init__.py index e7e74408..6ca492f7 100644 --- a/pipenv/vendor/pythonfinder/_vendor/pep514tools/__init__.py +++ b/pipenv/vendor/pythonfinder/_vendor/pep514tools/__init__.py @@ -8,4 +8,4 @@ __author__ = 'Steve Dower <steve.dower@python.org>' __version__ = '0.1.0' -from pythonfinder._vendor.pep514tools.environment import findall, find, findone +from pipenv.vendor.pythonfinder._vendor.pep514tools.environment import findall, find, findone diff --git a/pipenv/vendor/pythonfinder/_vendor/pep514tools/environment.py b/pipenv/vendor/pythonfinder/_vendor/pep514tools/environment.py index e201d0b5..9523f2d0 100644 --- a/pipenv/vendor/pythonfinder/_vendor/pep514tools/environment.py +++ b/pipenv/vendor/pythonfinder/_vendor/pep514tools/environment.py @@ -8,7 +8,7 @@ __all__ = ['Environment', 'findall', 'find', 'findone'] from itertools import count -from pythonfinder._vendor.pep514tools._registry import open_source, REGISTRY_SOURCE_LM, REGISTRY_SOURCE_LM_WOW6432, REGISTRY_SOURCE_CU +from pipenv.vendor.pythonfinder._vendor.pep514tools._registry import open_source, REGISTRY_SOURCE_LM, REGISTRY_SOURCE_LM_WOW6432, REGISTRY_SOURCE_CU import re import sys @@ -16,7 +16,7 @@ import sys _PYTHONCORE_COMPATIBILITY_TAGS = { '2.0', '2.1', '2.2', '2.3', '2.4', '2.5', '2.6', '2.7', '3.0', '3.1', '3.2', '3.3', '3.4', '3.5', '3.6', '3.7', - '3.8' + '3.8', '3.9' } _IS_64BIT_OS = None diff --git a/pipenv/vendor/pythonfinder/cli.py b/pipenv/vendor/pythonfinder/cli.py index eb2e603a..ea34ffab 100644 --- a/pipenv/vendor/pythonfinder/cli.py +++ b/pipenv/vendor/pythonfinder/cli.py @@ -1,10 +1,7 @@ # -*- coding=utf-8 -*- from __future__ import absolute_import, print_function, unicode_literals -import sys - -import click -import crayons +import pipenv.vendor.click as click from . import __version__ from .pythonfinder import Finder @@ -32,10 +29,11 @@ def cli( if version: click.echo( "{0} version {1}".format( - crayons.white("PythonFinder", bold=True), crayons.yellow(__version__) + click.style("PythonFinder", fg="white", bold=True), + click.style(str(__version__), fg="yellow") ) ) - sys.exit(0) + ctx.exit() finder = Finder(ignore_unsupported=ignore_unsupported) if findall: versions = [v for v in finder.find_all_python_versions()] @@ -54,7 +52,7 @@ def cli( ), fg="yellow", ) - sys.exit(0) + ctx.exit() else: click.secho( "ERROR: No valid python versions found! Check your path and try again.", @@ -78,22 +76,22 @@ def cli( ), fg="yellow", ) - sys.exit(0) + ctx.exit() else: click.secho("Failed to find matching executable...", fg="yellow") - sys.exit(1) + ctx.exit(1) elif which: found = finder.system_path.which(which.strip()) if found: click.secho("Found Executable: {0}".format(found), fg="white") - sys.exit(0) + ctx.exit() else: click.secho("Failed to find matching executable...", fg="yellow") - sys.exit(1) + ctx.exit(1) else: click.echo("Please provide a command", color="red") - sys.exit(1) - sys.exit() + ctx.exit(1) + ctx.exit() if __name__ == "__main__": diff --git a/pipenv/vendor/pythonfinder/compat.py b/pipenv/vendor/pythonfinder/compat.py new file mode 100644 index 00000000..71c4b6a2 --- /dev/null +++ b/pipenv/vendor/pythonfinder/compat.py @@ -0,0 +1,42 @@ +# -*- coding=utf-8 -*- +import sys + +import pipenv.vendor.six as six + +if sys.version_info[:2] <= (3, 4): + from pathlib2 import Path # type: ignore # noqa +else: + from pathlib import Path + +if six.PY3: + from functools import lru_cache + from builtins import TimeoutError +else: + from backports.functools_lru_cache import lru_cache # type: ignore # noqa + + class TimeoutError(OSError): + pass + + +def getpreferredencoding(): + import locale + # Borrowed from Invoke + # (see https://github.com/pyinvoke/invoke/blob/93af29d/invoke/runners.py#L881) + _encoding = locale.getpreferredencoding(False) + if six.PY2 and not sys.platform == "win32": + _default_encoding = locale.getdefaultlocale()[1] + if _default_encoding is not None: + _encoding = _default_encoding + return _encoding + + +DEFAULT_ENCODING = getpreferredencoding() + + +def fs_str(string): + """Encodes a string into the proper filesystem encoding""" + + if isinstance(string, str): + return string + assert not isinstance(string, bytes) + return string.encode(DEFAULT_ENCODING) diff --git a/pipenv/vendor/pythonfinder/models/__init__.py b/pipenv/vendor/pythonfinder/models/__init__.py index e788c21c..3d05af63 100644 --- a/pipenv/vendor/pythonfinder/models/__init__.py +++ b/pipenv/vendor/pythonfinder/models/__init__.py @@ -6,7 +6,7 @@ import operator from itertools import chain -import six +import pipenv.vendor.six as six from ..utils import KNOWN_EXTS, unnest from .path import SystemPath diff --git a/pipenv/vendor/pythonfinder/models/mixins.py b/pipenv/vendor/pythonfinder/models/mixins.py index a3637e12..001b7017 100644 --- a/pipenv/vendor/pythonfinder/models/mixins.py +++ b/pipenv/vendor/pythonfinder/models/mixins.py @@ -5,11 +5,10 @@ import abc import operator from collections import defaultdict -import attr -import six -from cached_property import cached_property -from vistir.compat import fs_str +import pipenv.vendor.attr as attr +import pipenv.vendor.six as six +from ..compat import fs_str from ..environment import MYPY_RUNNING from ..exceptions import InvalidPythonVersion from ..utils import ( @@ -36,7 +35,7 @@ if MYPY_RUNNING: TypeVar, Type, ) - from vistir.compat import Path + from ..compat import Path # noqa BaseFinderType = TypeVar("BaseFinderType") @@ -45,17 +44,17 @@ if MYPY_RUNNING: class BasePath(object): path = attr.ib(default=None) # type: Path _children = attr.ib( - default=attr.Factory(dict), cmp=False + default=attr.Factory(dict), order=False ) # type: Dict[str, PathEntry] only_python = attr.ib(default=False) # type: bool name = attr.ib(type=str) - _py_version = attr.ib(default=None, cmp=False) # type: Optional[PythonVersion] + _py_version = attr.ib(default=None, order=False) # type: Optional[PythonVersion] _pythons = attr.ib( - default=attr.Factory(defaultdict), cmp=False + default=attr.Factory(defaultdict), order=False ) # type: DefaultDict[str, PathEntry] - _is_dir = attr.ib(default=None, cmp=False) # type: Optional[bool] - _is_executable = attr.ib(default=None, cmp=False) # type: Optional[bool] - _is_python = attr.ib(default=None, cmp=False) # type: Optional[bool] + _is_dir = attr.ib(default=None, order=False) # type: Optional[bool] + _is_executable = attr.ib(default=None, order=False) # type: Optional[bool] + _is_python = attr.ib(default=None, order=False) # type: Optional[bool] def __str__(self): # type: () -> str @@ -174,7 +173,6 @@ class BasePath(object): # type: () -> None self._is_dir = None - # @cached_property @property def is_executable(self): # type: () -> bool @@ -195,7 +193,6 @@ class BasePath(object): # type: () -> None self._is_executable = None - # @cached_property @property def is_python(self): # type: () -> bool @@ -240,7 +237,6 @@ class BasePath(object): return py_version return None - # @cached_property @property def py_version(self): # type: () -> Optional[PythonVersion] diff --git a/pipenv/vendor/pythonfinder/models/path.py b/pipenv/vendor/pythonfinder/models/path.py index 80d5ac59..269496d6 100644 --- a/pipenv/vendor/pythonfinder/models/path.py +++ b/pipenv/vendor/pythonfinder/models/path.py @@ -1,18 +1,16 @@ # -*- coding=utf-8 -*- from __future__ import absolute_import, print_function -import copy import operator import os import sys from collections import defaultdict from itertools import chain -import attr -import six -from cached_property import cached_property -from vistir.compat import Path, fs_str -from vistir.misc import dedup +import pipenv.vendor.attr as attr +import pipenv.vendor.six as six +from pipenv.vendor.cached_property import cached_property +from ..compat import Path, fs_str from ..environment import ( ASDF_DATA_DIR, @@ -27,11 +25,10 @@ from ..exceptions import InvalidPythonVersion from ..utils import ( Iterable, Sequence, + dedup, ensure_path, - expand_paths, filter_pythons, is_in_path, - looks_like_python, normalize_path, optional_instance_of, parse_asdf_version_order, @@ -41,7 +38,6 @@ from ..utils import ( unnest, ) from .mixins import BaseFinder, BasePath -from .python import PythonVersion if MYPY_RUNNING: from typing import ( @@ -58,7 +54,7 @@ if MYPY_RUNNING: Any, TypeVar, ) - from .python import PythonFinder + from .python import PythonFinder, PythonVersion from .windows import WindowsFinder FinderType = TypeVar("FinderType", BaseFinder, PythonFinder, WindowsFinder) @@ -130,7 +126,7 @@ class SystemPath(object): self._python_executables = {} self._executables = [] self.python_version_dict = defaultdict(list) - self.version_dict = defaultdict(list) + self._version_dict = defaultdict(list) self.path_order = [] self.pyenv_finder = None self.asdf_finder = None @@ -193,7 +189,7 @@ class SystemPath(object): if entry not in self._version_dict[version] and entry.is_python: self._version_dict[version].append(entry) for p, entry in self.python_executables.items(): - version = entry.as_python + version = entry.as_python # type: PythonVersion if not version: continue if not isinstance(version, tuple): @@ -226,11 +222,12 @@ class SystemPath(object): path=p.absolute(), is_root=True, only_python=self.only_python ) for p in path_instances + if p.exists() } ) new_instance = attr.evolve( new_instance, - path_order=[p.as_posix() for p in path_instances], + path_order=[p.as_posix() for p in path_instances if p.exists()], paths=path_entries, ) if os.name == "nt" and "windows" not in self.finders: @@ -306,7 +303,7 @@ class SystemPath(object): normalized = normalize_path(current_path) if normalized != target: new_order.append(normalized) - new_order = [p for p in reversed(new_order)] + new_order = [ensure_path(p).as_posix() for p in reversed(new_order)] return attr.evolve(self, path_order=new_order, paths=new_paths) def _setup_asdf(self): @@ -331,7 +328,8 @@ class SystemPath(object): # we are in a virtualenv without global pyenv on the path, so we should # not write pyenv to the path here return self - root_paths = [p for p in asdf_finder.roots] + # * These are the root paths for the finder + _ = [p for p in asdf_finder.roots] new_instance = self._slice_in_paths(asdf_index, [asdf_finder.root]) paths = self.paths.copy() paths[asdf_finder.root] = asdf_finder @@ -393,8 +391,8 @@ class SystemPath(object): # we are in a virtualenv without global pyenv on the path, so we should # not write pyenv to the path here return self - - root_paths = [p for p in pyenv_finder.roots] + # * These are the root paths for the finder + _ = [p for p in pyenv_finder.roots] new_instance = self._slice_in_paths(pyenv_index, [pyenv_finder.root]) paths = new_instance.paths.copy() paths[pyenv_finder.root] = pyenv_finder @@ -432,7 +430,7 @@ class SystemPath(object): _path = self.paths.get(path) if not _path: _path = self.paths.get(path.as_posix()) - if not _path and path.as_posix() in self.path_order: + if not _path and path.as_posix() in self.path_order and path.exists(): _path = PathEntry.create( path=path.absolute(), is_root=True, only_python=self.only_python ) @@ -649,7 +647,7 @@ class SystemPath(object): if global_search: if "PATH" in os.environ: paths = os.environ["PATH"].split(os.pathsep) - path_order = [] + path_order = [] # type: List[str] if path: path_order = [path] path_instance = ensure_path(path) @@ -665,13 +663,13 @@ class SystemPath(object): paths = [path] + paths paths = [p for p in paths if not any(is_in_path(p, shim) for shim in SHIM_PATHS)] _path_objects = [ensure_path(p.strip('"')) for p in paths] - paths = [p.as_posix() for p in _path_objects] path_entries.update( { p.as_posix(): PathEntry.create( path=p.absolute(), is_root=True, only_python=only_python ) for p in _path_objects + if p.exists() } ) instance = cls( @@ -688,18 +686,22 @@ class SystemPath(object): @attr.s(slots=True) class PathEntry(BasePath): - is_root = attr.ib(default=True, type=bool, cmp=False) + is_root = attr.ib(default=True, type=bool, order=False) def __lt__(self, other): + # type: (BasePath) -> bool return self.path.as_posix() < other.path.as_posix() def __lte__(self, other): + # type: (BasePath) -> bool return self.path.as_posix() <= other.path.as_posix() def __gt__(self, other): + # type: (BasePath) -> bool return self.path.as_posix() > other.path.as_posix() def __gte__(self, other): + # type: (BasePath) -> bool return self.path.as_posix() >= other.path.as_posix() def __del__(self): @@ -738,11 +740,13 @@ class PathEntry(BasePath): except (InvalidPythonVersion, ValueError): continue else: - entry = PathEntry.create(path=child, **pass_args) # type: ignore + try: + entry = PathEntry.create(path=child, **pass_args) # type: ignore + except (InvalidPythonVersion, ValueError): + continue yield (child.as_posix(), entry) return - # @cached_property @property def children(self): # type: () -> Dict[str, PathEntry] diff --git a/pipenv/vendor/pythonfinder/models/python.py b/pipenv/vendor/pythonfinder/models/python.py index 427eb694..f1351c6f 100644 --- a/pipenv/vendor/pythonfinder/models/python.py +++ b/pipenv/vendor/pythonfinder/models/python.py @@ -1,24 +1,25 @@ # -*- coding=utf-8 -*- from __future__ import absolute_import, print_function -import copy import logging import operator +import os import platform import sys from collections import defaultdict -import attr -import six -from packaging.version import Version -from vistir.compat import Path, lru_cache +import pipenv.vendor.attr as attr +import pipenv.vendor.six as six +from pipenv.vendor.packaging.version import Version +from ..compat import Path, lru_cache from ..environment import ASDF_DATA_DIR, MYPY_RUNNING, PYENV_ROOT, SYSTEM_ARCH from ..exceptions import InvalidPythonVersion from ..utils import ( RE_MATCHER, _filter_none, ensure_path, + expand_paths, get_python_version, guess_company, is_in_path, @@ -46,9 +47,14 @@ if MYPY_RUNNING: Type, TypeVar, Iterator, + overload, ) from .path import PathEntry from .._vendor.pep514tools.environment import Environment +else: + + def overload(f): + return f logger = logging.getLogger(__name__) @@ -128,6 +134,8 @@ class PythonFinder(BaseFinder, BasePath): # type: (Union[Path, str]) -> Path if isinstance(base, six.string_types): base = Path(base) + if os.name == "nt": + return base return base / "bin" @classmethod @@ -231,6 +239,7 @@ class PythonFinder(BaseFinder, BasePath): # type: () -> DefaultDict[str, PathEntry] return self.pythons + @overload @classmethod def create(cls, root, sort_function, version_glob_path=None, ignore_unsupported=True): # type: (str, Callable, Optional[str], bool) -> PythonFinder @@ -281,12 +290,10 @@ class PythonFinder(BaseFinder, BasePath): ] else: pythons = [sub_finder(path) for path in self.paths] - pythons = [p for p in pythons if p and p.is_python and p.as_python is not None] + pythons = expand_paths(pythons, True) version_sort = operator.attrgetter("as_python.version_sort") paths = [ - p - for p in sorted(list(pythons), key=version_sort, reverse=True) - if p is not None + p for p in sorted(pythons, key=version_sort, reverse=True) if p is not None ] return paths @@ -447,9 +454,9 @@ class PythonVersion(object): if arch.isdigit(): arch = "{0}bit".format(arch) if ( - (major is None or self.major and self.major == major) - and (minor is None or self.minor and self.minor == minor) - and (patch is None or self.patch and self.patch == patch) + (major is None or self.major == major) + and (minor is None or self.minor == minor) + and (patch is None or self.patch == patch) and (pre is None or self.is_prerelease == pre) and (dev is None or self.is_devrelease == dev) and (arch is None or own_arch == arch) @@ -501,7 +508,7 @@ class PythonVersion(object): for key in metadata: try: - current_value = getattr(self, key) + _ = getattr(self, key) except AttributeError: continue else: @@ -677,7 +684,7 @@ class VersionMap(object): # type: (...) -> None version = entry.as_python # type: PythonVersion if version: - entries = self.versions[version.version_tuple] + _ = self.versions[version.version_tuple] paths = {p.path for p in self.versions.get(version.version_tuple, [])} if entry.path not in paths: self.versions[version.version_tuple].append(entry) diff --git a/pipenv/vendor/pythonfinder/models/windows.py b/pipenv/vendor/pythonfinder/models/windows.py index a0e69b03..5a4d9136 100644 --- a/pipenv/vendor/pythonfinder/models/windows.py +++ b/pipenv/vendor/pythonfinder/models/windows.py @@ -4,7 +4,7 @@ from __future__ import absolute_import, print_function import operator from collections import defaultdict -import attr +import pipenv.vendor.attr as attr from ..environment import MYPY_RUNNING from ..exceptions import InvalidPythonVersion @@ -42,7 +42,10 @@ class WindowsFinder(BaseFinder): ) pythons = [py for py in self.version_list if version_matcher(py)] version_sort = operator.attrgetter("version_sort") - return [c.comes_from for c in sorted(pythons, key=version_sort, reverse=True)] + return [ + c.comes_from for c in sorted(pythons, key=version_sort, reverse=True) + if c.comes_from + ] def find_python_version( self, @@ -75,7 +78,7 @@ class WindowsFinder(BaseFinder): def get_versions(self): # type: () -> DefaultDict[Tuple, PathEntry] versions = defaultdict(PathEntry) # type: DefaultDict[Tuple, PathEntry] - from pythonfinder._vendor.pep514tools import environment as pep514env + from pipenv.vendor.pythonfinder._vendor.pep514tools import environment as pep514env env_versions = pep514env.findall() path = None @@ -89,11 +92,13 @@ class WindowsFinder(BaseFinder): path = ensure_path(install_path.__getattr__("")) except AttributeError: continue + if not path.exists(): + continue try: py_version = PythonVersion.from_windows_launcher( version_object, name=name, company=company ) - except InvalidPythonVersion: + except (InvalidPythonVersion, AttributeError): continue if py_version is None: continue diff --git a/pipenv/vendor/pythonfinder/pythonfinder.py b/pipenv/vendor/pythonfinder/pythonfinder.py index 400a3170..e7d9c217 100644 --- a/pipenv/vendor/pythonfinder/pythonfinder.py +++ b/pipenv/vendor/pythonfinder/pythonfinder.py @@ -5,11 +5,11 @@ import importlib import operator import os -import six -from click import secho -from vistir.compat import lru_cache +import pipenv.vendor.six as six +from pipenv.vendor.click import secho from . import environment +from .compat import lru_cache from .exceptions import InvalidPythonVersion from .utils import Iterable, filter_pythons, version_re @@ -51,7 +51,8 @@ class Finder(object): :param system: bool, optional :param global_search: Whether to search the global path from os.environ, defaults to True :param global_search: bool, optional - :param ignore_unsupported: Whether to ignore unsupported python versions, if False, an error is raised, defaults to True + :param ignore_unsupported: Whether to ignore unsupported python versions, if False, an + error is raised, defaults to True :param ignore_unsupported: bool, optional :param bool sort_by_path: Whether to always sort by path :returns: a :class:`~pythonfinder.pythonfinder.Finder` object. @@ -133,8 +134,16 @@ class Finder(object): return self.system_path.which(exe) @classmethod - def parse_major(cls, major, minor=None, patch=None, pre=None, dev=None, arch=None): - # type: (Optional[str], Optional[int], Optional[int], Optional[bool], Optional[bool], Optional[str]) -> Dict[str, Union[int, str, bool, None]] + def parse_major( + cls, + major, # type: Optional[str] + minor=None, # type: Optional[int] + patch=None, # type: Optional[int] + pre=None, # type: Optional[bool] + dev=None, # type: Optional[bool] + arch=None, # type: Optional[str] + ): + # type: (...) -> Dict[str, Union[int, str, bool, None]] from .models import PythonVersion major_is_str = major and isinstance(major, six.string_types) @@ -289,11 +298,18 @@ class Finder(object): @lru_cache(maxsize=1024) def find_all_python_versions( - self, major=None, minor=None, patch=None, pre=None, dev=None, arch=None, name=None + self, + major=None, # type: Optional[Union[str, int]] + minor=None, # type: Optional[int] + patch=None, # type: Optional[int] + pre=None, # type: Optional[bool] + dev=None, # type: Optional[bool] + arch=None, # type: Optional[str] + name=None, # type: Optional[str] ): - # type: (Optional[Union[str, int]], Optional[int], Optional[int], Optional[bool], Optional[bool], Optional[str], Optional[str]) -> List[PathEntry] + # type: (...) -> List[PathEntry] version_sort = operator.attrgetter("as_python.version_sort") - python_version_dict = getattr(self.system_path, "python_version_dict") + python_version_dict = getattr(self.system_path, "python_version_dict", {}) if python_version_dict: paths = ( path @@ -309,7 +325,9 @@ class Finder(object): if not isinstance(versions, Iterable): versions = [versions] # This list has already been mostly sorted on windows, we don't need to reverse it again - path_list = sorted(versions, key=version_sort, reverse=True) + path_list = sorted( + filter(lambda v: v and v.as_python, versions), key=version_sort, reverse=True + ) path_map = {} # type: Dict[str, PathEntry] for path in path_list: try: diff --git a/pipenv/vendor/pythonfinder/utils.py b/pipenv/vendor/pythonfinder/utils.py index 1c190b01..ee5502c5 100644 --- a/pipenv/vendor/pythonfinder/utils.py +++ b/pipenv/vendor/pythonfinder/utils.py @@ -5,14 +5,16 @@ import io import itertools import os import re +import subprocess +from collections import OrderedDict from fnmatch import fnmatch from threading import Timer -import attr -import six -import vistir -from packaging.version import LegacyVersion, Version +import pipenv.vendor.attr as attr +import pipenv.vendor.six as six +from pipenv.vendor.packaging.version import LegacyVersion, Version +from .compat import Path, lru_cache, TimeoutError # noqa from .environment import MYPY_RUNNING, PYENV_ROOT, SUBPROCESS_TIMEOUT from .exceptions import InvalidPythonVersion @@ -23,18 +25,13 @@ six.add_move( six.MovedAttribute("Sequence", "collections", "collections.abc") ) # type: ignore # noqa # fmt: off -from six.moves import Iterable # type: ignore # noqa # isort:skip -from six.moves import Sequence # type: ignore # noqa # isort:skip +from pipenv.vendor.six.moves import Iterable # type: ignore # noqa # isort:skip +from pipenv.vendor.six.moves import Sequence # type: ignore # noqa # isort:skip # fmt: on -try: - from functools import lru_cache -except ImportError: - from backports.functools_lru_cache import lru_cache # type: ignore # noqa - if MYPY_RUNNING: from typing import Any, Union, List, Callable, Set, Tuple, Dict, Optional, Iterator - from attr.validators import _OptionalValidator # type: ignore + from pipenv.vendor.attr.validators import _OptionalValidator # type: ignore from .models.path import PathEntry @@ -65,7 +62,7 @@ else: KNOWN_EXTS = KNOWN_EXTS | set( filter(None, os.environ.get("PATHEXT", "").split(os.pathsep)) ) -PY_MATCH_STR = r"((?P<implementation>{0})(?:\d?(?:\.\d[cpm]{{0,3}}))?(?:-?[\d\.]+)*[^zw])".format( +PY_MATCH_STR = r"((?P<implementation>{0})(?:\d?(?:\.\d[cpm]{{0,3}}))?(?:-?[\d\.]+)*(?!w))".format( "|".join(PYTHON_IMPLEMENTATIONS) ) EXE_MATCH_STR = r"{0}(?:\.(?P<ext>{1}))?".format(PY_MATCH_STR, "|".join(KNOWN_EXTS)) @@ -98,27 +95,32 @@ def get_python_version(path): "-c", "import sys; print('.'.join([str(i) for i in sys.version_info[:3]]))", ] + subprocess_kwargs = { + "env": os.environ.copy(), + "universal_newlines": True, + "stdout": subprocess.PIPE, + "stderr": subprocess.PIPE, + "shell": False, + } + c = subprocess.Popen(version_cmd, **subprocess_kwargs) + timer = Timer(SUBPROCESS_TIMEOUT, c.kill) try: - c = vistir.misc.run( - version_cmd, - block=True, - nospin=True, - return_object=True, - combine_stderr=False, - write_to_stdout=False, - ) - timer = Timer(SUBPROCESS_TIMEOUT, c.kill) + out, _ = c.communicate() + except (SystemExit, KeyboardInterrupt, TimeoutError): + c.terminate() + out, _ = c.communicate() + raise except OSError: raise InvalidPythonVersion("%s is not a valid python path" % path) - if not c.out: + if not out: raise InvalidPythonVersion("%s is not a valid python path" % path) - return c.out.strip() + return out.strip() @lru_cache(maxsize=1024) def parse_python_version(version_str): # type: (str) -> Dict[str, Union[str, int, Version]] - from packaging.version import parse as parse_version + from pipenv.vendor.packaging.version import parse as parse_version is_debug = False if version_str.endswith("-debug"): @@ -190,13 +192,13 @@ def path_is_executable(path): @lru_cache(maxsize=1024) def path_is_known_executable(path): - # type: (vistir.compat.Path) -> bool + # type: (Path) -> bool """ Returns whether a given path is a known executable from known executable extensions or has the executable bit toggled. :param path: The path to the target executable. - :type path: :class:`~vistir.compat.Path` + :type path: :class:`~Path` :return: True if the path has chmod +x, or is a readable, known executable extension. :rtype: bool """ @@ -229,12 +231,12 @@ def looks_like_python(name): @lru_cache(maxsize=1024) def path_is_python(path): - # type: (vistir.compat.Path) -> bool + # type: (Path) -> bool """ Determine whether the supplied path is executable and looks like a possible path to python. :param path: The path to an executable. - :type path: :class:`~vistir.compat.Path` + :type path: :class:`~Path` :return: Whether the provided path is an executable path to python. :rtype: bool """ @@ -278,7 +280,7 @@ def path_is_pythoncore(path): @lru_cache(maxsize=1024) def ensure_path(path): - # type: (Union[vistir.compat.Path, str]) -> vistir.compat.Path + # type: (Union[Path, str]) -> Path """ Given a path (either a string or a Path object), expand variables and return a Path object. @@ -288,9 +290,9 @@ def ensure_path(path): :rtype: :class:`~pathlib.Path` """ - if isinstance(path, vistir.compat.Path): + if isinstance(path, Path): return path - path = vistir.compat.Path(os.path.expandvars(path)) + path = Path(os.path.expandvars(path)) return path.absolute() @@ -313,10 +315,10 @@ def normalize_path(path): @lru_cache(maxsize=1024) def filter_pythons(path): - # type: (Union[str, vistir.compat.Path]) -> Iterable + # type: (Union[str, Path]) -> Iterable """Return all valid pythons in a given path""" - if not isinstance(path, vistir.compat.Path): - path = vistir.compat.Path(str(path)) + if not isinstance(path, Path): + path = Path(str(path)) if not path.is_dir(): return path if path_is_python(path) else None return filter(path_is_python, path.iterdir()) @@ -377,7 +379,7 @@ def split_version_and_name( patch=None, # type: Optional[Union[str, int]] name=None, # type: Optional[str] ): - # type: (...) -> Tuple[Optional[Union[str, int]], Optional[Union[str, int]], Optional[Union[str, int]], Optional[str]] + # type: (...) -> Tuple[Optional[Union[str, int]], Optional[Union[str, int]], Optional[Union[str, int]], Optional[str]] # noqa if isinstance(major, six.string_types) and not minor and not patch: # Only proceed if this is in the format "x.y.z" or similar if major.isdigit() or (major.count(".") > 0 and major[0].isdigit()): @@ -420,7 +422,7 @@ def expand_paths(path, only_python=True): isinstance(path, Sequence) and not getattr(path.__class__, "__name__", "") == "PathEntry" ): - for p in unnest(path): + for p in path: if p is None: continue for expanded in itertools.chain.from_iterable( @@ -435,5 +437,15 @@ def expand_paths(path, only_python=True): ): yield sub_path else: - if path is not None and path.is_python and path.as_python is not None: + if path is not None and ( + not only_python or (path.is_python and path.as_python is not None) + ): yield path + + +def dedup(iterable): + # type: (Iterable) -> Iterable + """Deduplicate an iterable object like iter(set(iterable)) but + order-reserved. + """ + return iter(OrderedDict.fromkeys(iterable)) diff --git a/pipenv/vendor/pytoml/LICENSE b/pipenv/vendor/pytoml/LICENSE deleted file mode 100644 index 9739fc67..00000000 --- a/pipenv/vendor/pytoml/LICENSE +++ /dev/null @@ -1,16 +0,0 @@ -No-notice MIT License - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/pipenv/vendor/pytoml/__init__.py b/pipenv/vendor/pytoml/__init__.py deleted file mode 100644 index 8ed060ff..00000000 --- a/pipenv/vendor/pytoml/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .core import TomlError -from .parser import load, loads -from .test import translate_to_test -from .writer import dump, dumps \ No newline at end of file diff --git a/pipenv/vendor/pytoml/core.py b/pipenv/vendor/pytoml/core.py deleted file mode 100644 index c182734e..00000000 --- a/pipenv/vendor/pytoml/core.py +++ /dev/null @@ -1,13 +0,0 @@ -class TomlError(RuntimeError): - def __init__(self, message, line, col, filename): - RuntimeError.__init__(self, message, line, col, filename) - self.message = message - self.line = line - self.col = col - self.filename = filename - - def __str__(self): - return '{}({}, {}): {}'.format(self.filename, self.line, self.col, self.message) - - def __repr__(self): - return 'TomlError({!r}, {!r}, {!r}, {!r})'.format(self.message, self.line, self.col, self.filename) diff --git a/pipenv/vendor/pytoml/parser.py b/pipenv/vendor/pytoml/parser.py deleted file mode 100644 index 3493aa64..00000000 --- a/pipenv/vendor/pytoml/parser.py +++ /dev/null @@ -1,341 +0,0 @@ -import string, re, sys, datetime -from .core import TomlError -from .utils import rfc3339_re, parse_rfc3339_re - -if sys.version_info[0] == 2: - _chr = unichr -else: - _chr = chr - -def load(fin, translate=lambda t, x, v: v, object_pairs_hook=dict): - return loads(fin.read(), translate=translate, object_pairs_hook=object_pairs_hook, filename=getattr(fin, 'name', repr(fin))) - -def loads(s, filename='<string>', translate=lambda t, x, v: v, object_pairs_hook=dict): - if isinstance(s, bytes): - s = s.decode('utf-8') - - s = s.replace('\r\n', '\n') - - root = object_pairs_hook() - tables = object_pairs_hook() - scope = root - - src = _Source(s, filename=filename) - ast = _p_toml(src, object_pairs_hook=object_pairs_hook) - - def error(msg): - raise TomlError(msg, pos[0], pos[1], filename) - - def process_value(v, object_pairs_hook): - kind, text, value, pos = v - if kind == 'str' and value.startswith('\n'): - value = value[1:] - if kind == 'array': - if value and any(k != value[0][0] for k, t, v, p in value[1:]): - error('array-type-mismatch') - value = [process_value(item, object_pairs_hook=object_pairs_hook) for item in value] - elif kind == 'table': - value = object_pairs_hook([(k, process_value(value[k], object_pairs_hook=object_pairs_hook)) for k in value]) - return translate(kind, text, value) - - for kind, value, pos in ast: - if kind == 'kv': - k, v = value - if k in scope: - error('duplicate_keys. Key "{0}" was used more than once.'.format(k)) - scope[k] = process_value(v, object_pairs_hook=object_pairs_hook) - else: - is_table_array = (kind == 'table_array') - cur = tables - for name in value[:-1]: - if isinstance(cur.get(name), list): - d, cur = cur[name][-1] - else: - d, cur = cur.setdefault(name, (None, object_pairs_hook())) - - scope = object_pairs_hook() - name = value[-1] - if name not in cur: - if is_table_array: - cur[name] = [(scope, object_pairs_hook())] - else: - cur[name] = (scope, object_pairs_hook()) - elif isinstance(cur[name], list): - if not is_table_array: - error('table_type_mismatch') - cur[name].append((scope, object_pairs_hook())) - else: - if is_table_array: - error('table_type_mismatch') - old_scope, next_table = cur[name] - if old_scope is not None: - error('duplicate_tables') - cur[name] = (scope, next_table) - - def merge_tables(scope, tables): - if scope is None: - scope = object_pairs_hook() - for k in tables: - if k in scope: - error('key_table_conflict') - v = tables[k] - if isinstance(v, list): - scope[k] = [merge_tables(sc, tbl) for sc, tbl in v] - else: - scope[k] = merge_tables(v[0], v[1]) - return scope - - return merge_tables(root, tables) - -class _Source: - def __init__(self, s, filename=None): - self.s = s - self._pos = (1, 1) - self._last = None - self._filename = filename - self.backtrack_stack = [] - - def last(self): - return self._last - - def pos(self): - return self._pos - - def fail(self): - return self._expect(None) - - def consume_dot(self): - if self.s: - self._last = self.s[0] - self.s = self[1:] - self._advance(self._last) - return self._last - return None - - def expect_dot(self): - return self._expect(self.consume_dot()) - - def consume_eof(self): - if not self.s: - self._last = '' - return True - return False - - def expect_eof(self): - return self._expect(self.consume_eof()) - - def consume(self, s): - if self.s.startswith(s): - self.s = self.s[len(s):] - self._last = s - self._advance(s) - return True - return False - - def expect(self, s): - return self._expect(self.consume(s)) - - def consume_re(self, re): - m = re.match(self.s) - if m: - self.s = self.s[len(m.group(0)):] - self._last = m - self._advance(m.group(0)) - return m - return None - - def expect_re(self, re): - return self._expect(self.consume_re(re)) - - def __enter__(self): - self.backtrack_stack.append((self.s, self._pos)) - - def __exit__(self, type, value, traceback): - if type is None: - self.backtrack_stack.pop() - else: - self.s, self._pos = self.backtrack_stack.pop() - return type == TomlError - - def commit(self): - self.backtrack_stack[-1] = (self.s, self._pos) - - def _expect(self, r): - if not r: - raise TomlError('msg', self._pos[0], self._pos[1], self._filename) - return r - - def _advance(self, s): - suffix_pos = s.rfind('\n') - if suffix_pos == -1: - self._pos = (self._pos[0], self._pos[1] + len(s)) - else: - self._pos = (self._pos[0] + s.count('\n'), len(s) - suffix_pos) - -_ews_re = re.compile(r'(?:[ \t]|#[^\n]*\n|#[^\n]*\Z|\n)*') -def _p_ews(s): - s.expect_re(_ews_re) - -_ws_re = re.compile(r'[ \t]*') -def _p_ws(s): - s.expect_re(_ws_re) - -_escapes = { 'b': '\b', 'n': '\n', 'r': '\r', 't': '\t', '"': '"', - '\\': '\\', 'f': '\f' } - -_basicstr_re = re.compile(r'[^"\\\000-\037]*') -_short_uni_re = re.compile(r'u([0-9a-fA-F]{4})') -_long_uni_re = re.compile(r'U([0-9a-fA-F]{8})') -_escapes_re = re.compile(r'[btnfr\"\\]') -_newline_esc_re = re.compile('\n[ \t\n]*') -def _p_basicstr_content(s, content=_basicstr_re): - res = [] - while True: - res.append(s.expect_re(content).group(0)) - if not s.consume('\\'): - break - if s.consume_re(_newline_esc_re): - pass - elif s.consume_re(_short_uni_re) or s.consume_re(_long_uni_re): - v = int(s.last().group(1), 16) - if 0xd800 <= v < 0xe000: - s.fail() - res.append(_chr(v)) - else: - s.expect_re(_escapes_re) - res.append(_escapes[s.last().group(0)]) - return ''.join(res) - -_key_re = re.compile(r'[0-9a-zA-Z-_]+') -def _p_key(s): - with s: - s.expect('"') - r = _p_basicstr_content(s, _basicstr_re) - s.expect('"') - return r - if s.consume('\''): - if s.consume('\'\''): - r = s.expect_re(_litstr_ml_re).group(0) - s.expect('\'\'\'') - else: - r = s.expect_re(_litstr_re).group(0) - s.expect('\'') - return r - return s.expect_re(_key_re).group(0) - -_float_re = re.compile(r'[+-]?(?:0|[1-9](?:_?\d)*)(?:\.\d(?:_?\d)*)?(?:[eE][+-]?(?:\d(?:_?\d)*))?') - -_basicstr_ml_re = re.compile(r'(?:""?(?!")|[^"\\\000-\011\013-\037])*') -_litstr_re = re.compile(r"[^'\000\010\012-\037]*") -_litstr_ml_re = re.compile(r"(?:(?:|'|'')(?:[^'\000-\010\013-\037]))*") -def _p_value(s, object_pairs_hook): - pos = s.pos() - - if s.consume('true'): - return 'bool', s.last(), True, pos - if s.consume('false'): - return 'bool', s.last(), False, pos - - if s.consume('"'): - if s.consume('""'): - r = _p_basicstr_content(s, _basicstr_ml_re) - s.expect('"""') - else: - r = _p_basicstr_content(s, _basicstr_re) - s.expect('"') - return 'str', r, r, pos - - if s.consume('\''): - if s.consume('\'\''): - r = s.expect_re(_litstr_ml_re).group(0) - s.expect('\'\'\'') - else: - r = s.expect_re(_litstr_re).group(0) - s.expect('\'') - return 'str', r, r, pos - - if s.consume_re(rfc3339_re): - m = s.last() - return 'datetime', m.group(0), parse_rfc3339_re(m), pos - - if s.consume_re(_float_re): - m = s.last().group(0) - r = m.replace('_','') - if '.' in m or 'e' in m or 'E' in m: - return 'float', m, float(r), pos - else: - return 'int', m, int(r, 10), pos - - if s.consume('['): - items = [] - with s: - while True: - _p_ews(s) - items.append(_p_value(s, object_pairs_hook=object_pairs_hook)) - s.commit() - _p_ews(s) - s.expect(',') - s.commit() - _p_ews(s) - s.expect(']') - return 'array', None, items, pos - - if s.consume('{'): - _p_ws(s) - items = object_pairs_hook() - if not s.consume('}'): - k = _p_key(s) - _p_ws(s) - s.expect('=') - _p_ws(s) - items[k] = _p_value(s, object_pairs_hook=object_pairs_hook) - _p_ws(s) - while s.consume(','): - _p_ws(s) - k = _p_key(s) - _p_ws(s) - s.expect('=') - _p_ws(s) - items[k] = _p_value(s, object_pairs_hook=object_pairs_hook) - _p_ws(s) - s.expect('}') - return 'table', None, items, pos - - s.fail() - -def _p_stmt(s, object_pairs_hook): - pos = s.pos() - if s.consume( '['): - is_array = s.consume('[') - _p_ws(s) - keys = [_p_key(s)] - _p_ws(s) - while s.consume('.'): - _p_ws(s) - keys.append(_p_key(s)) - _p_ws(s) - s.expect(']') - if is_array: - s.expect(']') - return 'table_array' if is_array else 'table', keys, pos - - key = _p_key(s) - _p_ws(s) - s.expect('=') - _p_ws(s) - value = _p_value(s, object_pairs_hook=object_pairs_hook) - return 'kv', (key, value), pos - -_stmtsep_re = re.compile(r'(?:[ \t]*(?:#[^\n]*)?\n)+[ \t]*') -def _p_toml(s, object_pairs_hook): - stmts = [] - _p_ews(s) - with s: - stmts.append(_p_stmt(s, object_pairs_hook=object_pairs_hook)) - while True: - s.commit() - s.expect_re(_stmtsep_re) - stmts.append(_p_stmt(s, object_pairs_hook=object_pairs_hook)) - _p_ews(s) - s.expect_eof() - return stmts diff --git a/pipenv/vendor/pytoml/test.py b/pipenv/vendor/pytoml/test.py deleted file mode 100644 index ec8abfc6..00000000 --- a/pipenv/vendor/pytoml/test.py +++ /dev/null @@ -1,30 +0,0 @@ -import datetime -from .utils import format_rfc3339 - -try: - _string_types = (str, unicode) - _int_types = (int, long) -except NameError: - _string_types = str - _int_types = int - -def translate_to_test(v): - if isinstance(v, dict): - return { k: translate_to_test(v) for k, v in v.items() } - if isinstance(v, list): - a = [translate_to_test(x) for x in v] - if v and isinstance(v[0], dict): - return a - else: - return {'type': 'array', 'value': a} - if isinstance(v, datetime.datetime): - return {'type': 'datetime', 'value': format_rfc3339(v)} - if isinstance(v, bool): - return {'type': 'bool', 'value': 'true' if v else 'false'} - if isinstance(v, _int_types): - return {'type': 'integer', 'value': str(v)} - if isinstance(v, float): - return {'type': 'float', 'value': '{:.17}'.format(v)} - if isinstance(v, _string_types): - return {'type': 'string', 'value': v} - raise RuntimeError('unexpected value: {!r}'.format(v)) diff --git a/pipenv/vendor/pytoml/utils.py b/pipenv/vendor/pytoml/utils.py deleted file mode 100644 index 636a680b..00000000 --- a/pipenv/vendor/pytoml/utils.py +++ /dev/null @@ -1,67 +0,0 @@ -import datetime -import re - -rfc3339_re = re.compile(r'(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.\d+)?(?:Z|([+-]\d{2}):(\d{2}))') - -def parse_rfc3339(v): - m = rfc3339_re.match(v) - if not m or m.group(0) != v: - return None - return parse_rfc3339_re(m) - -def parse_rfc3339_re(m): - r = map(int, m.groups()[:6]) - if m.group(7): - micro = float(m.group(7)) - else: - micro = 0 - - if m.group(8): - g = int(m.group(8), 10) * 60 + int(m.group(9), 10) - tz = _TimeZone(datetime.timedelta(0, g * 60)) - else: - tz = _TimeZone(datetime.timedelta(0, 0)) - - y, m, d, H, M, S = r - return datetime.datetime(y, m, d, H, M, S, int(micro * 1000000), tz) - - -def format_rfc3339(v): - offs = v.utcoffset() - offs = int(offs.total_seconds()) // 60 if offs is not None else 0 - - if offs == 0: - suffix = 'Z' - else: - if offs > 0: - suffix = '+' - else: - suffix = '-' - offs = -offs - suffix = '{0}{1:02}:{2:02}'.format(suffix, offs // 60, offs % 60) - - if v.microsecond: - return v.strftime('%Y-%m-%dT%H:%M:%S.%f') + suffix - else: - return v.strftime('%Y-%m-%dT%H:%M:%S') + suffix - -class _TimeZone(datetime.tzinfo): - def __init__(self, offset): - self._offset = offset - - def utcoffset(self, dt): - return self._offset - - def dst(self, dt): - return None - - def tzname(self, dt): - m = self._offset.total_seconds() // 60 - if m < 0: - res = '-' - m = -m - else: - res = '+' - h = m // 60 - m = m - h * 60 - return '{}{:.02}{:.02}'.format(res, h, m) diff --git a/pipenv/vendor/pytoml/writer.py b/pipenv/vendor/pytoml/writer.py deleted file mode 100644 index 73b5089c..00000000 --- a/pipenv/vendor/pytoml/writer.py +++ /dev/null @@ -1,106 +0,0 @@ -from __future__ import unicode_literals -import io, datetime, math, string, sys - -from .utils import format_rfc3339 - -if sys.version_info[0] == 3: - long = int - unicode = str - - -def dumps(obj, sort_keys=False): - fout = io.StringIO() - dump(obj, fout, sort_keys=sort_keys) - return fout.getvalue() - - -_escapes = {'\n': 'n', '\r': 'r', '\\': '\\', '\t': 't', '\b': 'b', '\f': 'f', '"': '"'} - - -def _escape_string(s): - res = [] - start = 0 - - def flush(): - if start != i: - res.append(s[start:i]) - return i + 1 - - i = 0 - while i < len(s): - c = s[i] - if c in '"\\\n\r\t\b\f': - start = flush() - res.append('\\' + _escapes[c]) - elif ord(c) < 0x20: - start = flush() - res.append('\\u%04x' % ord(c)) - i += 1 - - flush() - return '"' + ''.join(res) + '"' - - -_key_chars = string.digits + string.ascii_letters + '-_' -def _escape_id(s): - if any(c not in _key_chars for c in s): - return _escape_string(s) - return s - - -def _format_value(v): - if isinstance(v, bool): - return 'true' if v else 'false' - if isinstance(v, int) or isinstance(v, long): - return unicode(v) - if isinstance(v, float): - if math.isnan(v) or math.isinf(v): - raise ValueError("{0} is not a valid TOML value".format(v)) - else: - return repr(v) - elif isinstance(v, unicode) or isinstance(v, bytes): - return _escape_string(v) - elif isinstance(v, datetime.datetime): - return format_rfc3339(v) - elif isinstance(v, list): - return '[{0}]'.format(', '.join(_format_value(obj) for obj in v)) - elif isinstance(v, dict): - return '{{{0}}}'.format(', '.join('{} = {}'.format(_escape_id(k), _format_value(obj)) for k, obj in v.items())) - else: - raise RuntimeError(v) - - -def dump(obj, fout, sort_keys=False): - tables = [((), obj, False)] - - while tables: - name, table, is_array = tables.pop() - if name: - section_name = '.'.join(_escape_id(c) for c in name) - if is_array: - fout.write('[[{0}]]\n'.format(section_name)) - else: - fout.write('[{0}]\n'.format(section_name)) - - table_keys = sorted(table.keys()) if sort_keys else table.keys() - new_tables = [] - has_kv = False - for k in table_keys: - v = table[k] - if isinstance(v, dict): - new_tables.append((name + (k,), v, False)) - elif isinstance(v, list) and v and all(isinstance(o, dict) for o in v): - new_tables.extend((name + (k,), d, True) for d in v) - elif v is None: - # based on mojombo's comment: https://github.com/toml-lang/toml/issues/146#issuecomment-25019344 - fout.write( - '#{} = null # To use: uncomment and replace null with value\n'.format(_escape_id(k))) - has_kv = True - else: - fout.write('{0} = {1}\n'.format(_escape_id(k), _format_value(v))) - has_kv = True - - tables.extend(reversed(new_tables)) - - if (name or has_kv) and tables: - fout.write('\n') diff --git a/pipenv/vendor/requests/LICENSE b/pipenv/vendor/requests/LICENSE index 841c6023..67db8588 100644 --- a/pipenv/vendor/requests/LICENSE +++ b/pipenv/vendor/requests/LICENSE @@ -1,13 +1,175 @@ -Copyright 2018 Kenneth Reitz - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ - https://www.apache.org/licenses/LICENSE-2.0 + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/pipenv/vendor/requests/__init__.py b/pipenv/vendor/requests/__init__.py index 9a899df6..d3b58866 100644 --- a/pipenv/vendor/requests/__init__.py +++ b/pipenv/vendor/requests/__init__.py @@ -9,14 +9,14 @@ Requests HTTP Library ~~~~~~~~~~~~~~~~~~~~~ -Requests is an HTTP library, written in Python, for human beings. Basic GET -usage: +Requests is an HTTP library, written in Python, for human beings. +Basic GET usage: >>> import requests >>> r = requests.get('https://www.python.org') >>> r.status_code 200 - >>> 'Python is a programming language' in r.content + >>> b'Python is a programming language' in r.content True ... or POST: @@ -27,26 +27,34 @@ usage: { ... "form": { - "key2": "value2", - "key1": "value1" + "key1": "value1", + "key2": "value2" }, ... } The other HTTP methods are supported - see `requests.api`. Full documentation -is at <http://python-requests.org>. +is at <https://requests.readthedocs.io>. :copyright: (c) 2017 by Kenneth Reitz. :license: Apache 2.0, see LICENSE for more details. """ -import urllib3 -import chardet +import pipenv.vendor.urllib3 as urllib3 import warnings from .exceptions import RequestsDependencyWarning +try: + from pipenv.vendor.charset_normalizer import __version__ as charset_normalizer_version +except ImportError: + charset_normalizer_version = None -def check_compatibility(urllib3_version, chardet_version): +try: + from pipenv.vendor.chardet import __version__ as chardet_version +except ImportError: + chardet_version = None + +def check_compatibility(urllib3_version, chardet_version, charset_normalizer_version): urllib3_version = urllib3_version.split('.') assert urllib3_version != ['dev'] # Verify urllib3 isn't installed from git. @@ -57,19 +65,24 @@ def check_compatibility(urllib3_version, chardet_version): # Check urllib3 for compatibility. major, minor, patch = urllib3_version # noqa: F811 major, minor, patch = int(major), int(minor), int(patch) - # urllib3 >= 1.21.1, <= 1.25 + # urllib3 >= 1.21.1, <= 1.26 assert major == 1 assert minor >= 21 - assert minor <= 25 - - # Check chardet for compatibility. - major, minor, patch = chardet_version.split('.')[:3] - major, minor, patch = int(major), int(minor), int(patch) - # chardet >= 3.0.2, < 3.1.0 - assert major == 3 - assert minor < 1 - assert patch >= 2 + assert minor <= 26 + # Check charset_normalizer for compatibility. + if chardet_version: + major, minor, patch = chardet_version.split('.')[:3] + major, minor, patch = int(major), int(minor), int(patch) + # chardet_version >= 3.0.2, < 5.0.0 + assert (3, 0, 2) <= (major, minor, patch) < (5, 0, 0) + elif charset_normalizer_version: + major, minor, patch = charset_normalizer_version.split('.')[:3] + major, minor, patch = int(major), int(minor), int(patch) + # charset_normalizer >= 2.0.0 < 3.0.0 + assert (2, 0, 0) <= (major, minor, patch) < (3, 0, 0) + else: + raise Exception("You need either charset_normalizer or chardet installed") def _check_cryptography(cryptography_version): # cryptography < 1.3.4 @@ -84,25 +97,33 @@ def _check_cryptography(cryptography_version): # Check imported dependencies for compatibility. try: - check_compatibility(urllib3.__version__, chardet.__version__) + check_compatibility(urllib3.__version__, chardet_version, charset_normalizer_version) except (AssertionError, ValueError): - warnings.warn("urllib3 ({}) or chardet ({}) doesn't match a supported " - "version!".format(urllib3.__version__, chardet.__version__), + warnings.warn("urllib3 ({}) or chardet ({})/charset_normalizer ({}) doesn't match a supported " + "version!".format(urllib3.__version__, chardet_version, charset_normalizer_version), RequestsDependencyWarning) -# Attempt to enable urllib3's SNI support, if possible +# Attempt to enable urllib3's fallback for SNI support +# if the standard library doesn't support SNI or the +# 'ssl' library isn't available. try: - from urllib3.contrib import pyopenssl - pyopenssl.inject_into_urllib3() + try: + import ssl + except ImportError: + ssl = None - # Check cryptography version - from cryptography import __version__ as cryptography_version - _check_cryptography(cryptography_version) + if not getattr(ssl, "HAS_SNI", False): + from pipenv.vendor.urllib3.contrib import pyopenssl + pyopenssl.inject_into_urllib3() + + # Check cryptography version + from cryptography import __version__ as cryptography_version + _check_cryptography(cryptography_version) except ImportError: pass # urllib3's DependencyWarnings should be silenced. -from urllib3.exceptions import DependencyWarning +from pipenv.vendor.urllib3.exceptions import DependencyWarning warnings.simplefilter('ignore', DependencyWarning) from .__version__ import __title__, __description__, __url__, __version__ diff --git a/pipenv/vendor/requests/__version__.py b/pipenv/vendor/requests/__version__.py index 9844f740..0d7cde1d 100644 --- a/pipenv/vendor/requests/__version__.py +++ b/pipenv/vendor/requests/__version__.py @@ -4,11 +4,11 @@ __title__ = 'requests' __description__ = 'Python HTTP for Humans.' -__url__ = 'http://python-requests.org' -__version__ = '2.22.0' -__build__ = 0x022200 +__url__ = 'https://requests.readthedocs.io' +__version__ = '2.26.0' +__build__ = 0x022600 __author__ = 'Kenneth Reitz' __author_email__ = 'me@kennethreitz.org' __license__ = 'Apache 2.0' -__copyright__ = 'Copyright 2019 Kenneth Reitz' +__copyright__ = 'Copyright 2020 Kenneth Reitz' __cake__ = u'\u2728 \U0001f370 \u2728' diff --git a/pipenv/vendor/requests/adapters.py b/pipenv/vendor/requests/adapters.py index fa4d9b3c..7d473a82 100644 --- a/pipenv/vendor/requests/adapters.py +++ b/pipenv/vendor/requests/adapters.py @@ -11,22 +11,22 @@ and maintain connections. import os.path import socket -from urllib3.poolmanager import PoolManager, proxy_from_url -from urllib3.response import HTTPResponse -from urllib3.util import parse_url -from urllib3.util import Timeout as TimeoutSauce -from urllib3.util.retry import Retry -from urllib3.exceptions import ClosedPoolError -from urllib3.exceptions import ConnectTimeoutError -from urllib3.exceptions import HTTPError as _HTTPError -from urllib3.exceptions import MaxRetryError -from urllib3.exceptions import NewConnectionError -from urllib3.exceptions import ProxyError as _ProxyError -from urllib3.exceptions import ProtocolError -from urllib3.exceptions import ReadTimeoutError -from urllib3.exceptions import SSLError as _SSLError -from urllib3.exceptions import ResponseError -from urllib3.exceptions import LocationValueError +from pipenv.vendor.urllib3.poolmanager import PoolManager, proxy_from_url +from pipenv.vendor.urllib3.response import HTTPResponse +from pipenv.vendor.urllib3.util import parse_url +from pipenv.vendor.urllib3.util import Timeout as TimeoutSauce +from pipenv.vendor.urllib3.util.retry import Retry +from pipenv.vendor.urllib3.exceptions import ClosedPoolError +from pipenv.vendor.urllib3.exceptions import ConnectTimeoutError +from pipenv.vendor.urllib3.exceptions import HTTPError as _HTTPError +from pipenv.vendor.urllib3.exceptions import MaxRetryError +from pipenv.vendor.urllib3.exceptions import NewConnectionError +from pipenv.vendor.urllib3.exceptions import ProxyError as _ProxyError +from pipenv.vendor.urllib3.exceptions import ProtocolError +from pipenv.vendor.urllib3.exceptions import ReadTimeoutError +from pipenv.vendor.urllib3.exceptions import SSLError as _SSLError +from pipenv.vendor.urllib3.exceptions import ResponseError +from pipenv.vendor.urllib3.exceptions import LocationValueError from .models import Response from .compat import urlparse, basestring @@ -41,7 +41,7 @@ from .exceptions import (ConnectionError, ConnectTimeout, ReadTimeout, SSLError, from .auth import _basic_auth_str try: - from urllib3.contrib.socks import SOCKSProxyManager + from pipenv.vendor.urllib3.contrib.socks import SOCKSProxyManager except ImportError: def SOCKSProxyManager(*args, **kwargs): raise InvalidSchema("Missing dependencies for SOCKS support.") diff --git a/pipenv/vendor/requests/api.py b/pipenv/vendor/requests/api.py index ef71d075..4cba90ee 100644 --- a/pipenv/vendor/requests/api.py +++ b/pipenv/vendor/requests/api.py @@ -16,7 +16,7 @@ from . import sessions def request(method, url, **kwargs): """Constructs and sends a :class:`Request <Request>`. - :param method: method for the new :class:`Request` object. + :param method: method for the new :class:`Request` object: ``GET``, ``OPTIONS``, ``HEAD``, ``POST``, ``PUT``, ``PATCH``, or ``DELETE``. :param url: URL for the new :class:`Request` object. :param params: (optional) Dictionary, list of tuples or bytes to send in the query string for the :class:`Request`. @@ -50,6 +50,7 @@ def request(method, url, **kwargs): >>> import requests >>> req = requests.request('GET', 'https://httpbin.org/get') + >>> req <Response [200]> """ @@ -71,7 +72,6 @@ def get(url, params=None, **kwargs): :rtype: requests.Response """ - kwargs.setdefault('allow_redirects', True) return request('get', url, params=params, **kwargs) @@ -84,7 +84,6 @@ def options(url, **kwargs): :rtype: requests.Response """ - kwargs.setdefault('allow_redirects', True) return request('options', url, **kwargs) @@ -92,7 +91,9 @@ def head(url, **kwargs): r"""Sends a HEAD request. :param url: URL for the new :class:`Request` object. - :param \*\*kwargs: Optional arguments that ``request`` takes. + :param \*\*kwargs: Optional arguments that ``request`` takes. If + `allow_redirects` is not provided, it will be set to `False` (as + opposed to the default :meth:`request` behavior). :return: :class:`Response <Response>` object :rtype: requests.Response """ diff --git a/pipenv/vendor/requests/auth.py b/pipenv/vendor/requests/auth.py index bdde51c7..eeface39 100644 --- a/pipenv/vendor/requests/auth.py +++ b/pipenv/vendor/requests/auth.py @@ -50,7 +50,7 @@ def _basic_auth_str(username, password): "Non-string passwords will no longer be supported in Requests " "3.0.0. Please convert the object you've passed in ({!r}) to " "a string or bytes object in the near future to avoid " - "problems.".format(password), + "problems.".format(type(password)), category=DeprecationWarning, ) password = str(password) @@ -239,7 +239,7 @@ class HTTPDigestAuth(AuthBase): """ # If response is not 4xx, do not auth - # See https://github.com/requests/requests/issues/3772 + # See https://github.com/psf/requests/issues/3772 if not 400 <= r.status_code < 500: self._thread_local.num_401_calls = 1 return r diff --git a/pipenv/vendor/requests/certs.py b/pipenv/vendor/requests/certs.py index d1a378d7..b685ee80 100644 --- a/pipenv/vendor/requests/certs.py +++ b/pipenv/vendor/requests/certs.py @@ -12,7 +12,7 @@ If you are packaging Requests, e.g., for a Linux distribution or a managed environment, you can change the definition of where() to return a separately packaged CA bundle. """ -from certifi import where +from pipenv.vendor.certifi import where if __name__ == '__main__': print(where()) diff --git a/pipenv/vendor/requests/compat.py b/pipenv/vendor/requests/compat.py index c44b35ef..8bc44327 100644 --- a/pipenv/vendor/requests/compat.py +++ b/pipenv/vendor/requests/compat.py @@ -8,7 +8,10 @@ This module handles import compatibility issues between Python 2 and Python 3. """ -import chardet +try: + import pipenv.vendor.chardet as chardet +except ImportError: + import pipenv.vendor.charset_normalizer as chardet import sys @@ -43,6 +46,7 @@ if is_py2: import cookielib from Cookie import Morsel from StringIO import StringIO + # Keep OrderedDict for backwards compatibility. from collections import Callable, Mapping, MutableMapping, OrderedDict @@ -59,6 +63,7 @@ elif is_py3: from http import cookiejar as cookielib from http.cookies import Morsel from io import StringIO + # Keep OrderedDict for backwards compatibility. from collections import OrderedDict from collections.abc import Callable, Mapping, MutableMapping diff --git a/pipenv/vendor/requests/exceptions.py b/pipenv/vendor/requests/exceptions.py index a80cad80..f5d6de81 100644 --- a/pipenv/vendor/requests/exceptions.py +++ b/pipenv/vendor/requests/exceptions.py @@ -6,7 +6,7 @@ requests.exceptions This module contains the set of Requests' exceptions. """ -from urllib3.exceptions import HTTPError as BaseHTTPError +from pipenv.vendor.urllib3.exceptions import HTTPError as BaseHTTPError class RequestException(IOError): @@ -25,6 +25,10 @@ class RequestException(IOError): super(RequestException, self).__init__(*args, **kwargs) +class InvalidJSONError(RequestException): + """A JSON error occurred.""" + + class HTTPError(RequestException): """An HTTP error occurred.""" @@ -94,11 +98,11 @@ class ChunkedEncodingError(RequestException): class ContentDecodingError(RequestException, BaseHTTPError): - """Failed to decode response content""" + """Failed to decode response content.""" class StreamConsumedError(RequestException, TypeError): - """The content for this response was already consumed""" + """The content for this response was already consumed.""" class RetryError(RequestException): @@ -106,21 +110,18 @@ class RetryError(RequestException): class UnrewindableBodyError(RequestException): - """Requests encountered an error when trying to rewind a body""" + """Requests encountered an error when trying to rewind a body.""" # Warnings class RequestsWarning(Warning): """Base warning for Requests.""" - pass class FileModeWarning(RequestsWarning, DeprecationWarning): """A file was opened in text mode, but Requests determined its binary length.""" - pass class RequestsDependencyWarning(RequestsWarning): """An imported dependency doesn't match the expected version range.""" - pass diff --git a/pipenv/vendor/requests/help.py b/pipenv/vendor/requests/help.py index e53d35ef..144e7eb8 100644 --- a/pipenv/vendor/requests/help.py +++ b/pipenv/vendor/requests/help.py @@ -6,14 +6,23 @@ import platform import sys import ssl -import idna -import urllib3 -import chardet +import pipenv.vendor.idna as idna +import pipenv.vendor.urllib3 as urllib3 from . import __version__ as requests_version try: - from urllib3.contrib import pyopenssl + import pipenv.vendor.charset_normalizer as charset_normalizer +except ImportError: + charset_normalizer = None + +try: + import pipenv.vendor.chardet as chardet +except ImportError: + chardet = None + +try: + from pipenv.vendor.urllib3.contrib import pyopenssl except ImportError: pyopenssl = None OpenSSL = None @@ -71,7 +80,12 @@ def info(): implementation_info = _implementation() urllib3_info = {'version': urllib3.__version__} - chardet_info = {'version': chardet.__version__} + charset_normalizer_info = {'version': None} + chardet_info = {'version': None} + if charset_normalizer: + charset_normalizer_info = {'version': charset_normalizer.__version__} + if chardet: + chardet_info = {'version': chardet.__version__} pyopenssl_info = { 'version': None, @@ -99,9 +113,11 @@ def info(): 'implementation': implementation_info, 'system_ssl': system_ssl_info, 'using_pyopenssl': pyopenssl is not None, + 'using_charset_normalizer': chardet is None, 'pyOpenSSL': pyopenssl_info, 'urllib3': urllib3_info, 'chardet': chardet_info, + 'charset_normalizer': charset_normalizer_info, 'cryptography': cryptography_info, 'idna': idna_info, 'requests': { diff --git a/pipenv/vendor/requests/models.py b/pipenv/vendor/requests/models.py index 62dcd0b7..0de756f9 100644 --- a/pipenv/vendor/requests/models.py +++ b/pipenv/vendor/requests/models.py @@ -12,13 +12,13 @@ import sys # Import encoding now, to avoid implicit import later. # Implicit import within threads may cause LookupError when standard library is in a ZIP, -# such as in Embedded Python. See https://github.com/requests/requests/issues/3578. +# such as in Embedded Python. See https://github.com/psf/requests/issues/3578. import encodings.idna -from urllib3.fields import RequestField -from urllib3.filepost import encode_multipart_formdata -from urllib3.util import parse_url -from urllib3.exceptions import ( +from pipenv.vendor.urllib3.fields import RequestField +from pipenv.vendor.urllib3.filepost import encode_multipart_formdata +from pipenv.vendor.urllib3.util import parse_url +from pipenv.vendor.urllib3.exceptions import ( DecodeError, ReadTimeoutError, ProtocolError, LocationParseError) from io import UnsupportedOperation @@ -29,7 +29,7 @@ from .auth import HTTPBasicAuth from .cookies import cookiejar_from_dict, get_cookie_header, _copy_cookie_jar from .exceptions import ( HTTPError, MissingSchema, InvalidURL, ChunkedEncodingError, - ContentDecodingError, ConnectionError, StreamConsumedError) + ContentDecodingError, ConnectionError, StreamConsumedError, InvalidJSONError) from ._internal_utils import to_native_string, unicode_is_ascii from .utils import ( guess_filename, get_auth_from_url, requote_uri, @@ -273,13 +273,16 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): """The fully mutable :class:`PreparedRequest <PreparedRequest>` object, containing the exact bytes that will be sent to the server. - Generated from either a :class:`Request <Request>` object or manually. + Instances are generated from a :class:`Request <Request>` object, and + should not be instantiated manually; doing so may produce undesirable + effects. Usage:: >>> import requests >>> req = requests.Request('GET', 'https://httpbin.org/get') >>> r = req.prepare() + >>> r <PreparedRequest [GET]> >>> s = requests.Session() @@ -344,7 +347,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): @staticmethod def _get_idna_encoded_host(host): - import idna + import pipenv.vendor.idna as idna try: host = idna.encode(host, uts46=True).decode('utf-8') @@ -358,7 +361,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): #: We're unable to blindly call unicode/str functions #: as this will include the bytestring indicator (b'') #: on python 3.x. - #: https://github.com/requests/requests/pull/2238 + #: https://github.com/psf/requests/pull/2238 if isinstance(url, bytes): url = url.decode('utf8') else: @@ -463,7 +466,12 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): # urllib3 requires a bytes-like body. Python 2's json.dumps # provides this natively, but Python 3 gives a Unicode string. content_type = 'application/json' - body = complexjson.dumps(json) + + try: + body = complexjson.dumps(json, allow_nan=False) + except ValueError as ve: + raise InvalidJSONError(ve, request=self) + if not isinstance(body, bytes): body = body.encode('utf-8') @@ -472,12 +480,12 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): not isinstance(data, (basestring, list, tuple, Mapping)) ]) - try: - length = super_len(data) - except (TypeError, AttributeError, UnsupportedOperation): - length = None - if is_stream: + try: + length = super_len(data) + except (TypeError, AttributeError, UnsupportedOperation): + length = None + body = data if getattr(body, 'tell', None) is not None: @@ -608,7 +616,7 @@ class Response(object): #: File-like object representation of response (for advanced usage). #: Use of ``raw`` requires that ``stream=True`` be set on the request. - # This requirement does not apply for use internally to Requests. + #: This requirement does not apply for use internally to Requests. self.raw = None #: Final URL location of Response. @@ -723,7 +731,7 @@ class Response(object): @property def apparent_encoding(self): - """The apparent encoding, provided by the chardet library.""" + """The apparent encoding, provided by the charset_normalizer or chardet libraries.""" return chardet.detect(self.content)['encoding'] def iter_content(self, chunk_size=1, decode_unicode=False): @@ -837,7 +845,7 @@ class Response(object): """Content of the response, in unicode. If Response.encoding is None, encoding will be guessed using - ``chardet``. + ``charset_normalizer`` or ``chardet``. The encoding of the response content is determined based solely on HTTP headers, following RFC 2616 to the letter. If you can take advantage of @@ -874,13 +882,18 @@ class Response(object): r"""Returns the json-encoded content of a response, if any. :param \*\*kwargs: Optional arguments that ``json.loads`` takes. - :raises ValueError: If the response body does not contain valid json. + :raises simplejson.JSONDecodeError: If the response body does not + contain valid json and simplejson is installed. + :raises json.JSONDecodeError: If the response body does not contain + valid json and simplejson is not installed on Python 3. + :raises ValueError: If the response body does not contain valid + json and simplejson is not installed on Python 2. """ if not self.encoding and self.content and len(self.content) > 3: # No encoding set. JSON RFC 4627 section 3 states we should expect # UTF-8, -16 or -32. Detect which one to use; If the detection or - # decoding fails, fall back to `self.text` (using chardet to make + # decoding fails, fall back to `self.text` (using charset_normalizer to make # a best guess). encoding = guess_json_utf(self.content) if encoding is not None: @@ -915,7 +928,7 @@ class Response(object): return l def raise_for_status(self): - """Raises stored :class:`HTTPError`, if one occurred.""" + """Raises :class:`HTTPError`, if one occurred.""" http_error_msg = '' if isinstance(self.reason, bytes): diff --git a/pipenv/vendor/requests/packages.py b/pipenv/vendor/requests/packages.py index 7232fe0f..b4045690 100644 --- a/pipenv/vendor/requests/packages.py +++ b/pipenv/vendor/requests/packages.py @@ -1,9 +1,17 @@ import sys +try: + import pipenv.vendor.chardet as chardet +except ImportError: + import pipenv.vendor.charset_normalizer as chardet + import warnings + + warnings.filterwarnings('ignore', 'Trying to detect', module='charset_normalizer') + # This code exists for backwards compatibility reasons. # I don't like it either. Just look the other way. :) -for package in ('urllib3', 'idna', 'chardet'): +for package in ('urllib3', 'idna'): locals()[package] = __import__(package) # This traversal is apparently necessary such that the identities are # preserved (requests.packages.urllib3.* is urllib3.*) @@ -11,4 +19,8 @@ for package in ('urllib3', 'idna', 'chardet'): if mod == package or mod.startswith(package + '.'): sys.modules['requests.packages.' + mod] = sys.modules[mod] +target = chardet.__name__ +for mod in list(sys.modules): + if mod == target or mod.startswith(target + '.'): + sys.modules['requests.packages.' + target.replace(target, 'chardet')] = sys.modules[mod] # Kinda cool, though, right? diff --git a/pipenv/vendor/requests/sessions.py b/pipenv/vendor/requests/sessions.py index d73d700f..ae4bcc8e 100644 --- a/pipenv/vendor/requests/sessions.py +++ b/pipenv/vendor/requests/sessions.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- """ -requests.session -~~~~~~~~~~~~~~~~ +requests.sessions +~~~~~~~~~~~~~~~~~ This module provides a Session object to manage and persist settings across requests (cookies, auth, proxies). @@ -11,9 +11,10 @@ import os import sys import time from datetime import timedelta +from collections import OrderedDict from .auth import _basic_auth_str -from .compat import cookielib, is_py3, OrderedDict, urljoin, urlparse, Mapping +from .compat import cookielib, is_py3, urljoin, urlparse, Mapping from .cookies import ( cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar, merge_cookies) from .models import Request, PreparedRequest, DEFAULT_REDIRECT_LIMIT @@ -162,7 +163,7 @@ class SessionRedirectMixin(object): resp.raw.read(decode_content=False) if len(resp.history) >= self.max_redirects: - raise TooManyRedirects('Exceeded %s redirects.' % self.max_redirects, response=resp) + raise TooManyRedirects('Exceeded {} redirects.'.format(self.max_redirects), response=resp) # Release the connection back into the pool. resp.close() @@ -170,7 +171,7 @@ class SessionRedirectMixin(object): # Handle redirection without scheme (see: RFC 1808 Section 4) if url.startswith('//'): parsed_rurl = urlparse(resp.url) - url = '%s:%s' % (to_native_string(parsed_rurl.scheme), url) + url = ':'.join([to_native_string(parsed_rurl.scheme), url]) # Normalize url case and attach previous fragment if needed (RFC 7231 7.1.2) parsed = urlparse(url) @@ -192,19 +193,16 @@ class SessionRedirectMixin(object): self.rebuild_method(prepared_request, resp) - # https://github.com/requests/requests/issues/1084 + # https://github.com/psf/requests/issues/1084 if resp.status_code not in (codes.temporary_redirect, codes.permanent_redirect): - # https://github.com/requests/requests/issues/3490 + # https://github.com/psf/requests/issues/3490 purged_headers = ('Content-Length', 'Content-Type', 'Transfer-Encoding') for header in purged_headers: prepared_request.headers.pop(header, None) prepared_request.body = None headers = prepared_request.headers - try: - del headers['Cookie'] - except KeyError: - pass + headers.pop('Cookie', None) # Extract any cookies sent on the response to the cookiejar # in the new request. Because we've mutated our copied prepared @@ -271,7 +269,6 @@ class SessionRedirectMixin(object): if new_auth is not None: prepared_request.prepare_auth(new_auth) - return def rebuild_proxies(self, prepared_request, proxies): """This method re-evaluates the proxy configuration by considering the @@ -352,13 +349,13 @@ class Session(SessionRedirectMixin): Or as a context manager:: >>> with requests.Session() as s: - >>> s.get('https://httpbin.org/get') + ... s.get('https://httpbin.org/get') <Response [200]> """ __attrs__ = [ 'headers', 'cookies', 'auth', 'proxies', 'hooks', 'params', 'verify', - 'cert', 'prefetch', 'adapters', 'stream', 'trust_env', + 'cert', 'adapters', 'stream', 'trust_env', 'max_redirects', ] @@ -390,6 +387,13 @@ class Session(SessionRedirectMixin): self.stream = False #: SSL Verification default. + #: Defaults to `True`, requiring requests to verify the TLS certificate at the + #: remote end. + #: If verify is set to `False`, requests will accept any TLS certificate + #: presented by the server, and will ignore hostname mismatches and/or + #: expired certificates, which will make your application vulnerable to + #: man-in-the-middle (MitM) attacks. + #: Only set this to `False` for testing. self.verify = True #: SSL client certificate default, if String, path to ssl client @@ -498,7 +502,12 @@ class Session(SessionRedirectMixin): content. Defaults to ``False``. :param verify: (optional) Either a boolean, in which case it controls whether we verify the server's TLS certificate, or a string, in which case it must be a path - to a CA bundle to use. Defaults to ``True``. + to a CA bundle to use. Defaults to ``True``. When set to + ``False``, requests will accept any TLS certificate presented by + the server, and will ignore hostname mismatches and/or expired + certificates, which will make your application vulnerable to + man-in-the-middle (MitM) attacks. Setting verify to ``False`` + may be useful during local development or testing. :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair. :rtype: requests.Response @@ -624,7 +633,7 @@ class Session(SessionRedirectMixin): kwargs.setdefault('stream', self.stream) kwargs.setdefault('verify', self.verify) kwargs.setdefault('cert', self.cert) - kwargs.setdefault('proxies', self.proxies) + kwargs.setdefault('proxies', self.rebuild_proxies(request, self.proxies)) # It's possible that users might accidentally send a Request object. # Guard against that specific failure case. @@ -661,11 +670,13 @@ class Session(SessionRedirectMixin): extract_cookies_to_jar(self.cookies, request, r.raw) - # Redirect resolving generator. - gen = self.resolve_redirects(r, request, **kwargs) - # Resolve redirects if allowed. - history = [resp for resp in gen] if allow_redirects else [] + if allow_redirects: + # Redirect resolving generator. + gen = self.resolve_redirects(r, request, **kwargs) + history = [resp for resp in gen] + else: + history = [] # Shuffle things around if there's history. if history: @@ -728,7 +739,7 @@ class Session(SessionRedirectMixin): return adapter # Nothing matches :-/ - raise InvalidSchema("No connection adapters were found for '%s'" % url) + raise InvalidSchema("No connection adapters were found for {!r}".format(url)) def close(self): """Closes all adapters and as such the session""" diff --git a/pipenv/vendor/requests/status_codes.py b/pipenv/vendor/requests/status_codes.py index 813e8c4e..d80a7cd4 100644 --- a/pipenv/vendor/requests/status_codes.py +++ b/pipenv/vendor/requests/status_codes.py @@ -5,12 +5,15 @@ The ``codes`` object defines a mapping from common names for HTTP statuses to their numerical codes, accessible either as attributes or as dictionary items. ->>> requests.codes['temporary_redirect'] -307 ->>> requests.codes.teapot -418 ->>> requests.codes['\o/'] -200 +Example:: + + >>> import requests + >>> requests.codes['temporary_redirect'] + 307 + >>> requests.codes.teapot + 418 + >>> requests.codes['\o/'] + 200 Some codes have multiple names, and both upper- and lower-case versions of the names are allowed. For example, ``codes.ok``, ``codes.OK``, and diff --git a/pipenv/vendor/requests/structures.py b/pipenv/vendor/requests/structures.py index da930e28..8ee0ba7a 100644 --- a/pipenv/vendor/requests/structures.py +++ b/pipenv/vendor/requests/structures.py @@ -7,7 +7,9 @@ requests.structures Data structures that power Requests. """ -from .compat import OrderedDict, Mapping, MutableMapping +from collections import OrderedDict + +from .compat import Mapping, MutableMapping class CaseInsensitiveDict(MutableMapping): diff --git a/pipenv/vendor/requests/utils.py b/pipenv/vendor/requests/utils.py index 8170a8d2..47c3ef08 100644 --- a/pipenv/vendor/requests/utils.py +++ b/pipenv/vendor/requests/utils.py @@ -19,6 +19,8 @@ import sys import tempfile import warnings import zipfile +from collections import OrderedDict +from pipenv.vendor.urllib3.util import make_headers from .__version__ import __version__ from . import certs @@ -26,7 +28,7 @@ from . import certs from ._internal_utils import to_native_string from .compat import parse_http_list as _parse_list_header from .compat import ( - quote, urlparse, bytes, str, OrderedDict, unquote, getproxies, + quote, urlparse, bytes, str, unquote, getproxies, proxy_bypass, urlunparse, basestring, integer_types, is_py3, proxy_bypass_environment, getproxies_environment, Mapping) from .cookies import cookiejar_from_dict @@ -40,6 +42,11 @@ DEFAULT_CA_BUNDLE_PATH = certs.where() DEFAULT_PORTS = {'http': 80, 'https': 443} +# Ensure that ', ' is used to preserve previous delimiter behavior. +DEFAULT_ACCEPT_ENCODING = ", ".join( + re.split(r",\s*", make_headers(accept_encoding=True)["accept-encoding"]) +) + if sys.platform == 'win32': # provide a proxy_bypass version on Windows without DNS lookups @@ -168,18 +175,24 @@ def super_len(o): def get_netrc_auth(url, raise_errors=False): """Returns the Requests tuple auth for a given url from netrc.""" + netrc_file = os.environ.get('NETRC') + if netrc_file is not None: + netrc_locations = (netrc_file,) + else: + netrc_locations = ('~/{}'.format(f) for f in NETRC_FILES) + try: from netrc import netrc, NetrcParseError netrc_path = None - for f in NETRC_FILES: + for f in netrc_locations: try: - loc = os.path.expanduser('~/{}'.format(f)) + loc = os.path.expanduser(f) except KeyError: # os.path.expanduser can fail when $HOME is undefined and # getpwuid fails. See https://bugs.python.org/issue20164 & - # https://github.com/requests/requests/issues/1846 + # https://github.com/psf/requests/issues/1846 return if os.path.exists(loc): @@ -211,7 +224,7 @@ def get_netrc_auth(url, raise_errors=False): if raise_errors: raise - # AppEngine hackiness. + # App Engine hackiness. except (ImportError, AttributeError): pass @@ -249,13 +262,28 @@ def extract_zipped_paths(path): # we have a valid zip archive and a valid member of that archive tmp = tempfile.gettempdir() - extracted_path = os.path.join(tmp, *member.split('/')) + extracted_path = os.path.join(tmp, member.split('/')[-1]) if not os.path.exists(extracted_path): - extracted_path = zip_file.extract(member, path=tmp) - + # use read + write to avoid the creating nested folders, we only want the file, avoids mkdir racing condition + with atomic_open(extracted_path) as file_handler: + file_handler.write(zip_file.read(member)) return extracted_path +@contextlib.contextmanager +def atomic_open(filename): + """Write a file to the disk in an atomic fashion""" + replacer = os.rename if sys.version_info[0] == 2 else os.replace + tmp_descriptor, tmp_name = tempfile.mkstemp(dir=os.path.dirname(filename)) + try: + with os.fdopen(tmp_descriptor, 'wb') as tmp_handler: + yield tmp_handler + replacer(tmp_name, filename) + except BaseException: + os.remove(tmp_name) + raise + + def from_key_val_list(value): """Take an object and test to see if it can be represented as a dictionary. Unless it can not be represented as such, return an @@ -266,6 +294,8 @@ def from_key_val_list(value): >>> from_key_val_list([('key', 'val')]) OrderedDict([('key', 'val')]) >>> from_key_val_list('string') + Traceback (most recent call last): + ... ValueError: cannot encode objects that are not 2-tuples >>> from_key_val_list({'key': 'val'}) OrderedDict([('key', 'val')]) @@ -292,7 +322,9 @@ def to_key_val_list(value): >>> to_key_val_list({'key': 'val'}) [('key', 'val')] >>> to_key_val_list('string') - ValueError: cannot encode objects that are not 2-tuples. + Traceback (most recent call last): + ... + ValueError: cannot encode objects that are not 2-tuples :rtype: list """ @@ -492,6 +524,10 @@ def get_encoding_from_headers(headers): if 'text' in content_type: return 'ISO-8859-1' + if 'application/json' in content_type: + # Assume UTF-8 based on RFC 4627: https://www.ietf.org/rfc/rfc4627.txt since the charset was unset + return 'utf-8' + def stream_decode_response_unicode(iterator, r): """Stream decodes a iterator.""" @@ -805,7 +841,7 @@ def default_headers(): """ return CaseInsensitiveDict({ 'User-Agent': default_user_agent(), - 'Accept-Encoding': ', '.join(('gzip', 'deflate')), + 'Accept-Encoding': DEFAULT_ACCEPT_ENCODING, 'Accept': '*/*', 'Connection': 'keep-alive', }) diff --git a/pipenv/vendor/requirementslib/LICENSE b/pipenv/vendor/requirementslib/LICENSE index 8c731e27..21aaa475 100644 --- a/pipenv/vendor/requirementslib/LICENSE +++ b/pipenv/vendor/requirementslib/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright 2018 Dan Ryan. +Copyright 2019-2021 Dan Ryan and Frost Ming. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pipenv/vendor/requirementslib/__init__.py b/pipenv/vendor/requirementslib/__init__.py index 70b604a8..dc8520ab 100644 --- a/pipenv/vendor/requirementslib/__init__.py +++ b/pipenv/vendor/requirementslib/__init__.py @@ -4,13 +4,11 @@ from __future__ import absolute_import, print_function import logging import warnings -from vistir.compat import ResourceWarning - from .models.lockfile import Lockfile from .models.pipfile import Pipfile from .models.requirements import Requirement -__version__ = "1.5.2.dev0" +__version__ = "1.6.1" logger = logging.getLogger(__name__) diff --git a/pipenv/vendor/requirementslib/environment.py b/pipenv/vendor/requirementslib/environment.py index 2a7d9b0e..86de7286 100644 --- a/pipenv/vendor/requirementslib/environment.py +++ b/pipenv/vendor/requirementslib/environment.py @@ -1,8 +1,9 @@ # -*- coding=utf-8 -*- -from __future__ import print_function, absolute_import +from __future__ import absolute_import, print_function import os -from appdirs import user_cache_dir + +from pipenv.vendor.platformdirs import user_cache_dir def is_type_checking(): @@ -13,5 +14,7 @@ def is_type_checking(): return TYPE_CHECKING -REQUIREMENTSLIB_CACHE_DIR = os.getenv("REQUIREMENTSLIB_CACHE_DIR", user_cache_dir("pipenv")) +REQUIREMENTSLIB_CACHE_DIR = os.getenv( + "REQUIREMENTSLIB_CACHE_DIR", user_cache_dir("pipenv") +) MYPY_RUNNING = os.environ.get("MYPY_RUNNING", is_type_checking()) diff --git a/pipenv/vendor/requirementslib/exceptions.py b/pipenv/vendor/requirementslib/exceptions.py index d11dbce9..eacef437 100644 --- a/pipenv/vendor/requirementslib/exceptions.py +++ b/pipenv/vendor/requirementslib/exceptions.py @@ -5,20 +5,6 @@ import errno import os import sys -import six -from vistir.compat import FileNotFoundError - -if six.PY2: - - class FileExistsError(OSError): - def __init__(self, *args, **kwargs): - self.errno = errno.EEXIST - super(FileExistsError, self).__init__(*args, **kwargs) - - -else: - from six.moves.builtins import FileExistsError - class RequirementError(Exception): pass @@ -44,7 +30,7 @@ class FileCorruptException(OSError): if not backup_path and args: args = reversed(args) backup_path = args.pop() - if not isinstance(backup_path, six.string_types) or not os.path.exists( + if not isinstance(backup_path, str) or not os.path.exists( os.path.abspath(os.path.dirname(backup_path)) ): args.append(backup_path) diff --git a/pipenv/vendor/requirementslib/models/__init__.py b/pipenv/vendor/requirementslib/models/__init__.py index e819cdd5..40a96afc 100644 --- a/pipenv/vendor/requirementslib/models/__init__.py +++ b/pipenv/vendor/requirementslib/models/__init__.py @@ -1,2 +1 @@ # -*- coding: utf-8 -*- -# This is intentionally left blank diff --git a/pipenv/vendor/requirementslib/models/cache.py b/pipenv/vendor/requirementslib/models/cache.py index f1639ea2..8dc5159f 100644 --- a/pipenv/vendor/requirementslib/models/cache.py +++ b/pipenv/vendor/requirementslib/models/cache.py @@ -6,18 +6,15 @@ import copy import hashlib import json import os +import pathlib import sys -import vistir - -from appdirs import user_cache_dir -from pip_shims.shims import FAVORITE_HASH, SafeFileCache -from packaging.requirements import Requirement - -from .utils import as_tuple, key_from_req, lookup_table, get_pinned_version - -from ..exceptions import FileExistsError +import pipenv.vendor.vistir as vistir +from pipenv.vendor.packaging.requirements import Requirement +from pipenv.vendor.pip_shims.shims import FAVORITE_HASH, SafeFileCache +from pipenv.vendor.platformdirs import user_cache_dir +from .utils import as_tuple, get_pinned_version, key_from_req, lookup_table CACHE_DIR = os.environ.get("PIPENV_CACHE_DIR", user_cache_dir("pipenv")) @@ -29,64 +26,64 @@ class CorruptCacheError(Exception): def __str__(self): lines = [ - 'The dependency cache seems to have been corrupted.', - 'Inspect, or delete, the following file:', - ' {}'.format(self.path), + "The dependency cache seems to have been corrupted.", + "Inspect, or delete, the following file:", + " {}".format(self.path), ] return os.linesep.join(lines) def read_cache_file(cache_file_path): - with open(cache_file_path, 'r') as cache_file: + with open(cache_file_path, "r") as cache_file: try: doc = json.load(cache_file) except ValueError: raise CorruptCacheError(cache_file_path) # Check version and load the contents - assert doc['__format__'] == 1, 'Unknown cache file format' - return doc['dependencies'] + assert doc["__format__"] == 1, "Unknown cache file format" + return doc["dependencies"] class DependencyCache(object): - """ - Creates a new persistent dependency cache for the current Python version. - The cache file is written to the appropriate user cache dir for the - current platform, i.e. + """Creates a new persistent dependency cache for the current Python + version. The cache file is written to the appropriate user cache dir for + the current platform, i.e. ~/.cache/pip-tools/depcache-pyX.Y.json Where X.Y indicates the Python version. """ + def __init__(self, cache_dir=None): if cache_dir is None: cache_dir = CACHE_DIR - if not vistir.compat.Path(CACHE_DIR).absolute().is_dir(): + if not pathlib.Path(CACHE_DIR).absolute().is_dir(): try: vistir.path.mkdir_p(os.path.abspath(cache_dir)) - except (FileExistsError, OSError): + except OSError: pass - py_version = '.'.join(str(digit) for digit in sys.version_info[:2]) - cache_filename = 'depcache-py{}.json'.format(py_version) + py_version = ".".join(str(digit) for digit in sys.version_info[:2]) + cache_filename = "depcache-py{}.json".format(py_version) self._cache_file = os.path.join(cache_dir, cache_filename) self._cache = None @property def cache(self): - """ - The dictionary that is the actual in-memory cache. This property - lazily loads the cache from disk. + """The dictionary that is the actual in-memory cache. + + This property lazily loads the cache from disk. """ if self._cache is None: self.read_cache() return self._cache def as_cache_key(self, ireq): - """ - Given a requirement, return its cache key. This behavior is a little weird in order to allow backwards - compatibility with cache files. For a requirement without extras, this will return, for example: + """Given a requirement, return its cache key. This behavior is a little + weird in order to allow backwards compatibility with cache files. For a + requirement without extras, this will return, for example: ("ipython", "2.1.0") @@ -112,10 +109,10 @@ class DependencyCache(object): def write_cache(self): """Writes the cache to disk as JSON.""" doc = { - '__format__': 1, - 'dependencies': self._cache, + "__format__": 1, + "dependencies": self._cache, } - with open(self._cache_file, 'w') as f: + with open(self._cache_file, "w") as f: json.dump(doc, f, sort_keys=True) def clear(self): @@ -149,20 +146,20 @@ class DependencyCache(object): return self.cache.get(pkgname, {}).get(pkgversion_and_extras, default) def reverse_dependencies(self, ireqs): - """ - Returns a lookup table of reverse dependencies for all the given ireqs. + """Returns a lookup table of reverse dependencies for all the given + ireqs. Since this is all static, it only works if the dependency cache - contains the complete data, otherwise you end up with a partial view. - This is typically no problem if you use this function after the entire - dependency tree is resolved. + contains the complete data, otherwise you end up with a partial + view. This is typically no problem if you use this function + after the entire dependency tree is resolved. """ ireqs_as_cache_values = [self.as_cache_key(ireq) for ireq in ireqs] return self._reverse_dependencies(ireqs_as_cache_values) def _reverse_dependencies(self, cache_keys): - """ - Returns a lookup table of reverse dependencies for all the given cache keys. + """Returns a lookup table of reverse dependencies for all the given + cache keys. Example input: @@ -177,35 +174,39 @@ class DependencyCache(object): 'flake8': [], 'mccabe': ['flake8'], 'pyflakes': ['flake8']} - """ # First, collect all the dependencies into a sequence of (parent, child) tuples, like [('flake8', 'pep8'), # ('flake8', 'mccabe'), ...] - return lookup_table((key_from_req(Requirement(dep_name)), name) - for name, version_and_extras in cache_keys - for dep_name in self.cache[name][version_and_extras]) + return lookup_table( + (key_from_req(Requirement(dep_name)), name) + for name, version_and_extras in cache_keys + for dep_name in self.cache[name][version_and_extras] + ) class HashCache(SafeFileCache): """Caches hashes of PyPI artifacts so we do not need to re-download them. - Hashes are only cached when the URL appears to contain a hash in it and the - cache key includes the hash value returned from the server). This ought to - avoid ssues where the location on the server changes. + Hashes are only cached when the URL appears to contain a hash in it + and the cache key includes the hash value returned from the server). + This ought to avoid ssues where the location on the server changes. """ + def __init__(self, *args, **kwargs): session = kwargs.pop("session", None) if not session: - import requests + import pipenv.vendor.requests as requests + session = requests.session() atexit.register(session.close) - cache_dir = kwargs.pop('cache_dir', CACHE_DIR) + cache_dir = kwargs.pop("cache_dir", CACHE_DIR) self.session = session - kwargs.setdefault('directory', os.path.join(cache_dir, 'hash-cache')) + kwargs.setdefault("directory", os.path.join(cache_dir, "hash-cache")) super(HashCache, self).__init__(*args, **kwargs) def get_hash(self, location): - from pip_shims import VcsSupport + from pipenv.vendor.pip_shims import VcsSupport + # if there is no location hash (i.e., md5 / sha256 / etc) we on't want to store it hash_value = None vcs = VcsSupport() @@ -216,13 +217,17 @@ class HashCache(SafeFileCache): can_hash = new_location.hash if can_hash: # hash url WITH fragment - hash_value = self._get_file_hash(new_location.url) if not new_location.url.startswith("ssh") else None + hash_value = ( + self._get_file_hash(new_location.url) + if not new_location.url.startswith("ssh") + else None + ) if not hash_value: hash_value = self._get_file_hash(new_location) - hash_value = hash_value.encode('utf8') + hash_value = hash_value.encode("utf8") if can_hash: self.set(new_location.url, hash_value) - return hash_value.decode('utf8') + return hash_value.decode("utf8") def _get_file_hash(self, location): h = hashlib.new(FAVORITE_HASH) @@ -242,6 +247,7 @@ class _JSONCache(object): Where X.Y indicates the Python version. """ + filename_format = None def __init__(self, cache_dir=CACHE_DIR): @@ -287,21 +293,19 @@ class _JSONCache(object): return name, "{}{}".format(version, extras_string) def read_cache(self): - """Reads the cached contents into memory. - """ + """Reads the cached contents into memory.""" if os.path.exists(self._cache_file): self._cache = read_cache_file(self._cache_file) else: self._cache = {} def write_cache(self): - """Writes the cache to disk as JSON. - """ + """Writes the cache to disk as JSON.""" doc = { - '__format__': 1, - 'dependencies': self._cache, + "__format__": 1, + "dependencies": self._cache, } - with open(self._cache_file, 'w') as f: + with open(self._cache_file, "w") as f: json.dump(doc, f, sort_keys=True) def clear(self): @@ -336,6 +340,6 @@ class _JSONCache(object): class RequiresPythonCache(_JSONCache): - """Cache a candidate's Requires-Python information. - """ + """Cache a candidate's Requires-Python information.""" + filename_format = "pyreqcache-py{python_version}.json" diff --git a/pipenv/vendor/requirementslib/models/dependencies.py b/pipenv/vendor/requirementslib/models/dependencies.py index 82eaba5f..19d7720a 100644 --- a/pipenv/vendor/requirementslib/models/dependencies.py +++ b/pipenv/vendor/requirementslib/models/dependencies.py @@ -5,20 +5,23 @@ import contextlib import copy import functools import os +from contextlib import ExitStack +from json import JSONDecodeError -import attr +import pipenv.vendor.attr as attr import packaging.markers import packaging.version import pip_shims.shims -import requests -from first import first -from packaging.utils import canonicalize_name -from vistir.compat import JSONDecodeError, fs_str -from vistir.contextmanagers import cd, temp_environ -from vistir.misc import partialclass -from vistir.path import create_tracked_tempdir +import pipenv.vendor.requests as requests +from pipenv.vendor.packaging.utils import canonicalize_name +from pipenv.vendor.vistir.compat import fs_str +from pipenv.vendor.vistir.contextmanagers import cd, temp_environ +from pipenv.vendor.vistir.path import create_tracked_tempdir +from ..environment import MYPY_RUNNING +from ..utils import _ensure_dir, prepare_pip_source_args from .cache import CACHE_DIR, DependencyCache +from .setup_info import SetupInfo from .utils import ( clean_requires_python, fix_requires_python_marker, @@ -30,29 +33,29 @@ from .utils import ( name_from_req, version_from_ireq, ) -from ..environment import MYPY_RUNNING -from ..utils import _ensure_dir, prepare_pip_source_args if MYPY_RUNNING: from typing import ( Any, Dict, - List, Generator, + List, Optional, - Union, + Set, + Text, Tuple, TypeVar, - Text, - Set, + Union, ) - from pip_shims.shims import ( - InstallRequirement, - InstallationCandidate, - PackageFinder, + + from pipenv.vendor.packaging.markers import Marker + from pipenv.vendor.packaging.requirements import Requirement as PackagingRequirement + from pipenv.vendor.pip_shims.shims import ( Command, + InstallationCandidate, + InstallRequirement, + PackageFinder, ) - from packaging.requirements import Requirement as PackagingRequirement TRequirement = TypeVar("TRequirement") RequirementType = TypeVar( @@ -67,9 +70,14 @@ PKGS_DOWNLOAD_DIR = fs_str(os.path.join(CACHE_DIR, "pkgs")) WHEEL_DOWNLOAD_DIR = fs_str(os.path.join(CACHE_DIR, "wheels")) DEPENDENCY_CACHE = DependencyCache() -WHEEL_CACHE = pip_shims.shims.WheelCache( - CACHE_DIR, pip_shims.shims.FormatControl(set(), set()) -) + + +@contextlib.contextmanager +def _get_wheel_cache(): + with pip_shims.shims.global_tempdir_manager(): + yield pip_shims.shims.WheelCache( + CACHE_DIR, pip_shims.shims.FormatControl(set(), set()) + ) def _get_filtered_versions(ireq, versions, prereleases): @@ -78,15 +86,15 @@ def _get_filtered_versions(ireq, versions, prereleases): def find_all_matches(finder, ireq, pre=False): # type: (PackageFinder, InstallRequirement, bool) -> List[InstallationCandidate] - """Find all matching dependencies using the supplied finder and the - given ireq. + """Find all matching dependencies using the supplied finder and the given + ireq. :param finder: A package finder for discovering matching candidates. - :type finder: :class:`~pip._internal.index.PackageFinder` + :type finder: :class:`~pipenv.patched.notpip._internal.index.PackageFinder` :param ireq: An install requirement. - :type ireq: :class:`~pip._internal.req.req_install.InstallRequirement` + :type ireq: :class:`~pipenv.patched.notpip._internal.req.req_install.InstallRequirement` :return: A list of matching candidates. - :rtype: list[:class:`~pip._internal.index.InstallationCandidate`] + :rtype: list[:class:`~pipenv.patched.notpip._internal.index.InstallationCandidate`] """ candidates = clean_requires_python(finder.find_all_candidates(ireq.name)) @@ -103,22 +111,7 @@ def get_pip_command(): # Use pip's parser for pip.conf management and defaults. # General options (find_links, index_url, extra_index_url, trusted_host, # and pre) are defered to pip. - import optparse - - class PipCommand(pip_shims.shims.Command): - name = "PipCommand" - - pip_command = PipCommand() - pip_command.parser.add_option(pip_shims.shims.cmdoptions.no_binary()) - pip_command.parser.add_option(pip_shims.shims.cmdoptions.only_binary()) - index_opts = pip_shims.shims.cmdoptions.make_option_group( - pip_shims.shims.cmdoptions.index_group, pip_command.parser - ) - pip_command.parser.insert_option_group(0, index_opts) - pip_command.parser.add_option( - optparse.Option("--pre", action="store_true", default=False) - ) - + pip_command = pip_shims.shims.InstallCommand() return pip_command @@ -135,7 +128,8 @@ class AbstractDependency(object): @property def version_set(self): - """Return the set of versions for the candidates in this abstract dependency. + """Return the set of versions for the candidates in this abstract + dependency. :return: A set of matching versions :rtype: set(str) @@ -146,8 +140,8 @@ class AbstractDependency(object): return set(packaging.version.parse(version_from_ireq(c)) for c in self.candidates) def compatible_versions(self, other): - """Find compatible version numbers between this abstract - dependency and another one. + """Find compatible version numbers between this abstract dependency and + another one. :param other: An abstract dependency to compare with. :type other: :class:`~requirementslib.models.dependency.AbstractDependency` @@ -155,9 +149,9 @@ class AbstractDependency(object): :rtype: set(str) """ - if len(self.candidates) == 1 and first(self.candidates).editable: + if len(self.candidates) == 1 and next(iter(self.candidates)).editable: return self - elif len(other.candidates) == 1 and first(other.candidates).editable: + elif len(other.candidates) == 1 and next(iter(other.candidates)).editable: return other return self.version_set & other.version_set @@ -174,9 +168,9 @@ class AbstractDependency(object): from .requirements import Requirement - if len(self.candidates) == 1 and first(self.candidates).editable: + if len(self.candidates) == 1 and next(iter(self.candidates)).editable: return self - elif len(other.candidates) == 1 and first(other.candidates).editable: + elif len(other.candidates) == 1 and next(iter(other.candidates)).editable: return other new_specifiers = self.specifiers & other.specifiers markers = set(self.markers) if self.markers else set() @@ -219,7 +213,7 @@ class AbstractDependency(object): """Get the dependencies of the supplied candidate. :param candidate: An installrequirement - :type candidate: :class:`~pip._internal.req.req_install.InstallRequirement` + :type candidate: :class:`~pipenv.patched.notpip._internal.req.req_install.InstallRequirement` :return: A list of abstract dependencies :rtype: list[:class:`~requirementslib.models.dependency.AbstractDependency`] """ @@ -235,8 +229,9 @@ class AbstractDependency(object): @classmethod def from_requirement(cls, requirement, parent=None): - """Creates a new :class:`~requirementslib.models.dependency.AbstractDependency` - from a :class:`~requirementslib.models.requirements.Requirement` object. + """Creates a new + :class:`~requirementslib.models.dependency.AbstractDependency` from a + :class:`~requirementslib.models.requirements.Requirement` object. This class is used to find all candidates matching a given set of specifiers and a given requirement. @@ -250,7 +245,7 @@ class AbstractDependency(object): extras = requirement.ireq.extras is_pinned = is_pinned_requirement(requirement.ireq) is_constraint = bool(parent) - finder = get_finder(sources=None) + _, finder = get_finder(sources=None) candidates = [] if not is_pinned and not requirement.editable: for r in requirement.find_all_matches(finder=finder): @@ -261,7 +256,7 @@ class AbstractDependency(object): markers=markers, constraint=is_constraint, ) - req.req.link = r.location + req.req.link = getattr(r, "location", getattr(r, "link", None)) req.parent = parent candidates.append(req) candidates = sorted( @@ -330,11 +325,11 @@ def get_dependencies(ireq, sources=None, parent=None): """Get all dependencies for a given install requirement. :param ireq: A single InstallRequirement - :type ireq: :class:`~pip._internal.req.req_install.InstallRequirement` + :type ireq: :class:`~pipenv.patched.notpip._internal.req.req_install.InstallRequirement` :param sources: Pipfile-formatted sources, defaults to None :type sources: list[dict], optional :param parent: The parent of this list of dependencies, defaults to None - :type parent: :class:`~pip._internal.req.req_install.InstallRequirement` + :type parent: :class:`~pipenv.patched.notpip._internal.req.req_install.InstallRequirement` :return: A set of dependency lines for generating new InstallRequirements. :rtype: set(str) """ @@ -362,23 +357,26 @@ def get_dependencies(ireq, sources=None, parent=None): def get_dependencies_from_wheel_cache(ireq): - """Retrieves dependencies for the given install requirement from the wheel cache. + # type: (pip_shims.shims.InstallRequirement) -> Optional[Set[pip_shims.shims.InstallRequirement]] + """Retrieves dependencies for the given install requirement from the wheel + cache. :param ireq: A single InstallRequirement - :type ireq: :class:`~pip._internal.req.req_install.InstallRequirement` + :type ireq: :class:`~pipenv.patched.notpip._internal.req.req_install.InstallRequirement` :return: A set of dependency lines for generating new InstallRequirements. :rtype: set(str) or None """ if ireq.editable or not is_pinned_requirement(ireq): return - matches = WHEEL_CACHE.get(ireq.link, name_from_req(ireq.req)) - if matches: - matches = set(matches) - if not DEPENDENCY_CACHE.get(ireq): - DEPENDENCY_CACHE[ireq] = [format_requirement(m) for m in matches] - return matches - return + with _get_wheel_cache() as wheel_cache: + matches = wheel_cache.get(ireq.link, name_from_req(ireq.req)) + if matches: + matches = set(matches) + if not DEPENDENCY_CACHE.get(ireq): + DEPENDENCY_CACHE[ireq] = [format_requirement(m) for m in matches] + return matches + return None def _marker_contains_extra(ireq): @@ -387,10 +385,11 @@ def _marker_contains_extra(ireq): def get_dependencies_from_json(ireq): - """Retrieves dependencies for the given install requirement from the json api. + """Retrieves dependencies for the given install requirement from the json + api. :param ireq: A single InstallRequirement - :type ireq: :class:`~pip._internal.req.req_install.InstallRequirement` + :type ireq: :class:`~pipenv.patched.notpip._internal.req.req_install.InstallRequirement` :return: A set of dependency lines for generating new InstallRequirements. :rtype: set(str) or None """ @@ -436,10 +435,11 @@ def get_dependencies_from_json(ireq): def get_dependencies_from_cache(ireq): - """Retrieves dependencies for the given install requirement from the dependency cache. + """Retrieves dependencies for the given install requirement from the + dependency cache. :param ireq: A single InstallRequirement - :type ireq: :class:`~pip._internal.req.req_install.InstallRequirement` + :type ireq: :class:`~pipenv.patched.notpip._internal.req.req_install.InstallRequirement` :return: A set of dependency lines for generating new InstallRequirements. :rtype: set(str) or None """ @@ -477,119 +477,49 @@ def is_python(section): def get_dependencies_from_index(dep, sources=None, pip_options=None, wheel_cache=None): - """Retrieves dependencies for the given install requirement from the pip resolver. + """Retrieves dependencies for the given install requirement from the pip + resolver. :param dep: A single InstallRequirement - :type dep: :class:`~pip._internal.req.req_install.InstallRequirement` + :type dep: :class:`~pipenv.patched.notpip._internal.req.req_install.InstallRequirement` :param sources: Pipfile-formatted sources, defaults to None :type sources: list[dict], optional :return: A set of dependency lines for generating new InstallRequirements. :rtype: set(str) or None """ - finder = get_finder(sources=sources, pip_options=pip_options) - if not wheel_cache: - wheel_cache = WHEEL_CACHE + session, finder = get_finder(sources=sources, pip_options=pip_options) dep.is_direct = True - reqset = pip_shims.shims.RequirementSet() - reqset.add_requirement(dep) requirements = None setup_requires = {} - with temp_environ(), start_resolver( - finder=finder, wheel_cache=wheel_cache - ) as resolver: + with temp_environ(), ExitStack() as stack: + if not wheel_cache: + wheel_cache = stack.enter_context(_get_wheel_cache()) os.environ["PIP_EXISTS_ACTION"] = "i" - dist = None if dep.editable and not dep.prepared and not dep.req: - with cd(dep.setup_py_dir): - from setuptools.dist import distutils - - try: - dist = distutils.core.run_setup(dep.setup_py) - except (ImportError, TypeError, AttributeError): - dist = None - else: - setup_requires[dist.get_name()] = dist.setup_requires - if not dist: - try: - dist = dep.get_dist() - except (TypeError, ValueError, AttributeError): - pass - else: - setup_requires[dist.get_name()] = dist.setup_requires - resolver.require_hashes = False - try: - results = resolver._resolve_one(reqset, dep) - except Exception: - # FIXME: Needs to bubble the exception somehow to the user. - results = [] - finally: - try: - wheel_cache.cleanup() - except AttributeError: - pass - resolver_requires_python = getattr(resolver, "requires_python", None) - requires_python = getattr(reqset, "requires_python", resolver_requires_python) - if requires_python: - add_marker = fix_requires_python_marker(requires_python) - reqset.remove(dep) - if dep.req.marker: - dep.req.marker._markers.extend(["and"].extend(add_marker._markers)) - else: - dep.req.marker = add_marker - reqset.add(dep) - requirements = set() - for r in results: - if requires_python: - if r.req.marker: - r.req.marker._markers.extend(["and"].extend(add_marker._markers)) - else: - r.req.marker = add_marker - requirements.add(format_requirement(r)) - for section in setup_requires: - python_version = section - not_python = not is_python(section) - - # This is for cleaning up :extras: formatted markers - # by adding them to the results of the resolver - # since any such extra would have been returned as a result anyway - for value in setup_requires[section]: - - # This is a marker. - if is_python(section): - python_version = value[1:-1] - else: - not_python = True - - if ":" not in value and not_python: - try: - requirement_str = "{0}{1}".format(value, python_version).replace( - ":", ";" - ) - requirements.add( - format_requirement( - make_install_requirement(requirement_str).ireq - ) - ) - # Anything could go wrong here -- can't be too careful. - except Exception: - pass - + setup_info = SetupInfo.from_ireq(dep) + results = setup_info.get_info() + setup_requires.update(results["setup_requires"]) + requirements = set(results["requires"].values()) + else: + results = pip_shims.shims.resolve(dep) + requirements = [v for v in results.values() if v.name != dep.name] + requirements = set([format_requirement(r) for r in requirements]) if not dep.editable and is_pinned_requirement(dep) and requirements is not None: DEPENDENCY_CACHE[dep] = list(requirements) return requirements -def get_pip_options(args=[], sources=None, pip_command=None): - """Build a pip command from a list of sources +def get_pip_options(args=None, sources=None, pip_command=None): + """Build a pip command from a list of sources. :param args: positional arguments passed through to the pip parser :param sources: A list of pipfile-formatted sources, defaults to None :param sources: list[dict], optional :param pip_command: A pre-built pip command instance - :type pip_command: :class:`~pip._internal.cli.base_command.Command` + :type pip_command: :class:`~pipenv.patched.notpip._internal.cli.base_command.Command` :return: An instance of pip_options using the supplied arguments plus sane defaults - :rtype: :class:`~pip._internal.cli.cmdoptions` + :rtype: :class:`~pipenv.patched.notpip._internal.cli.cmdoptions` """ if not pip_command: @@ -597,7 +527,7 @@ def get_pip_options(args=[], sources=None, pip_command=None): if not sources: sources = [{"url": "https://pypi.org/simple", "name": "pypi", "verify_ssl": True}] _ensure_dir(CACHE_DIR) - pip_args = args + pip_args = args or [] pip_args = prepare_pip_source_args(sources, pip_args) pip_options, _ = pip_command.parser.parse_args(pip_args) pip_options.cache_dir = CACHE_DIR @@ -606,95 +536,96 @@ def get_pip_options(args=[], sources=None, pip_command=None): def get_finder(sources=None, pip_command=None, pip_options=None): # type: (List[Dict[S, Union[S, bool]]], Optional[Command], Any) -> PackageFinder - """Get a package finder for looking up candidates to install + """Get a package finder for looking up candidates to install. :param sources: A list of pipfile-formatted sources, defaults to None :param sources: list[dict], optional :param pip_command: A pip command instance, defaults to None - :type pip_command: :class:`~pip._internal.cli.base_command.Command` + :type pip_command: :class:`~pipenv.patched.notpip._internal.cli.base_command.Command` :param pip_options: A pip options, defaults to None - :type pip_options: :class:`~pip._internal.cli.cmdoptions` + :type pip_options: :class:`~pipenv.patched.notpip._internal.cli.cmdoptions` :return: A package finder - :rtype: :class:`~pip._internal.index.PackageFinder` + :rtype: :class:`~pipenv.patched.notpip._internal.index.PackageFinder` """ if not pip_command: - pip_command = get_pip_command() + pip_command = pip_shims.shims.InstallCommand() if not sources: sources = [{"url": "https://pypi.org/simple", "name": "pypi", "verify_ssl": True}] if not pip_options: pip_options = get_pip_options(sources=sources, pip_command=pip_command) session = pip_command._build_session(pip_options) atexit.register(session.close) - finder = pip_shims.shims.PackageFinder( - find_links=[], - index_urls=[s.get("url") for s in sources], - trusted_hosts=[], - allow_all_prereleases=pip_options.pre, - session=session, + finder = pip_shims.shims.get_package_finder( + pip_shims.shims.InstallCommand(), options=pip_options, session=session ) - return finder + return session, finder @contextlib.contextmanager -def start_resolver(finder=None, wheel_cache=None): +def start_resolver(finder=None, session=None, wheel_cache=None): """Context manager to produce a resolver. :param finder: A package finder to use for searching the index - :type finder: :class:`~pip._internal.index.PackageFinder` + :type finder: :class:`~pipenv.patched.notpip._internal.index.PackageFinder` + :param :class:`~requests.Session` session: A session instance + :param :class:`~pipenv.patched.notpip._internal.cache.WheelCache` wheel_cache: A pip WheelCache instance :return: A 3-tuple of finder, preparer, resolver - :rtype: (:class:`~pip._internal.operations.prepare.RequirementPreparer`, :class:`~pip._internal.resolve.Resolver`) + :rtype: (:class:`~pipenv.patched.notpip._internal.operations.prepare.RequirementPreparer`, + :class:`~pipenv.patched.notpip._internal.resolve.Resolver`) """ pip_command = get_pip_command() pip_options = get_pip_options(pip_command=pip_command) - + session = None if not finder: - finder = get_finder(pip_command=pip_command, pip_options=pip_options) - - if not wheel_cache: - wheel_cache = WHEEL_CACHE - _ensure_dir(fs_str(os.path.join(wheel_cache.cache_dir, "wheels"))) + session, finder = get_finder(pip_command=pip_command, pip_options=pip_options) + if not session: + session = pip_command._build_session(pip_options) download_dir = PKGS_DOWNLOAD_DIR _ensure_dir(download_dir) _build_dir = create_tracked_tempdir(fs_str("build")) _source_dir = create_tracked_tempdir(fs_str("source")) - preparer = partialclass( - pip_shims.shims.RequirementPreparer, - build_dir=_build_dir, - src_dir=_source_dir, - download_dir=download_dir, - wheel_download_dir=WHEEL_DOWNLOAD_DIR, - progress_bar="off", - build_isolation=False, - ) - resolver = partialclass( - pip_shims.shims.Resolver, - finder=finder, - session=finder.session, - upgrade_strategy="to-satisfy-only", - force_reinstall=True, - ignore_dependencies=False, - ignore_requires_python=True, - ignore_installed=True, - isolated=False, - wheel_cache=wheel_cache, - use_user_site=False, - ) try: - if packaging.version.parse( - pip_shims.shims.pip_version - ) >= packaging.version.parse("18"): - with pip_shims.shims.RequirementTracker() as req_tracker: - preparer = preparer(req_tracker=req_tracker) - yield resolver(preparer=preparer) - else: - preparer = preparer() - yield resolver(preparer=preparer) + with ExitStack() as ctx: + ctx.enter_context(pip_shims.shims.global_tempdir_manager()) + if not wheel_cache: + wheel_cache = ctx.enter_context(_get_wheel_cache()) + _ensure_dir(fs_str(os.path.join(wheel_cache.cache_dir, "wheels"))) + preparer = ctx.enter_context( + pip_shims.shims.make_preparer( + options=pip_options, + finder=finder, + session=session, + build_dir=_build_dir, + src_dir=_source_dir, + download_dir=download_dir, + wheel_download_dir=WHEEL_DOWNLOAD_DIR, + progress_bar="off", + build_isolation=False, + install_cmd=pip_command, + ) + ) + resolver = pip_shims.shims.get_resolver( + finder=finder, + ignore_dependencies=False, + ignore_requires_python=True, + preparer=preparer, + session=session, + options=pip_options, + install_cmd=pip_command, + wheel_cache=wheel_cache, + force_reinstall=True, + ignore_installed=True, + upgrade_strategy="to-satisfy-only", + isolated=False, + use_user_site=False, + ) + yield resolver finally: - finder.session.close() + session.close() def get_grouped_dependencies(constraints): @@ -704,10 +635,10 @@ def get_grouped_dependencies(constraints): # then we take the loose match (which _is_ flexible) and start moving backwards in # versions by popping them off of a stack and checking for the conflicting package for _, ireqs in full_groupby(constraints, key=key_from_ireq): - ireqs = list(ireqs) - editable_ireq = first(ireqs, key=lambda ireq: ireq.editable) + ireqs = sorted(ireqs, key=lambda ireq: ireq.editable) + editable_ireq = next(iter(ireq for ireq in ireqs if ireq.editable), None) if editable_ireq: - yield editable_ireq # ignore all the other specs: the editable one is the one that counts + yield editable_ireq # only the editable match mattters, ignore all others continue ireqs = iter(ireqs) # deepcopy the accumulator so as to not modify the self.our_constraints invariant diff --git a/pipenv/vendor/requirementslib/models/lockfile.py b/pipenv/vendor/requirementslib/models/lockfile.py index 42248868..53f613df 100644 --- a/pipenv/vendor/requirementslib/models/lockfile.py +++ b/pipenv/vendor/requirementslib/models/lockfile.py @@ -2,27 +2,25 @@ from __future__ import absolute_import, print_function import copy -import os - -import attr import itertools +import os +from json import JSONDecodeError +from pathlib import Path + +import pipenv.vendor.attr as attr import plette.lockfiles -import six - -from vistir.compat import Path, FileNotFoundError, JSONDecodeError +from ..exceptions import LockfileCorruptException, MissingParameter, PipfileNotFound +from ..utils import is_editable, is_vcs, merge_items from .project import ProjectFile from .requirements import Requirement - from .utils import optional_instance_of -from ..exceptions import LockfileCorruptException, PipfileNotFound, MissingParameter -from ..utils import is_vcs, is_editable, merge_items -DEFAULT_NEWLINES = u"\n" +DEFAULT_NEWLINES = "\n" def preferred_newlines(f): - if isinstance(f.newlines, six.text_type): + if isinstance(f.newlines, str): return f.newlines return DEFAULT_NEWLINES @@ -38,11 +36,11 @@ class Lockfile(object): _dev_requirements = attr.ib(default=attr.Factory(list), type=list) projectfile = attr.ib(validator=is_projectfile, type=ProjectFile) _lockfile = attr.ib(validator=is_lockfile, type=plette.lockfiles.Lockfile) - newlines = attr.ib(default=DEFAULT_NEWLINES, type=six.text_type) + newlines = attr.ib(default=DEFAULT_NEWLINES, type=str) @path.default def _get_path(self): - return Path(os.curdir).absolute() + return Path(os.curdir).joinpath("Pipfile.lock").absolute() @projectfile.default def _get_projectfile(self): @@ -50,7 +48,7 @@ class Lockfile(object): @_lockfile.default def _get_lockfile(self): - return self.projectfile.lockfile + return self.projectfile.model @property def lockfile(self): @@ -120,23 +118,21 @@ class Lockfile(object): @classmethod def read_projectfile(cls, path): - """Read the specified project file and provide an interface for writing/updating. + """Read the specified project file and provide an interface for + writing/updating. :param str path: Path to the target file. :return: A project file with the model and location for interaction :rtype: :class:`~requirementslib.models.project.ProjectFile` """ - pf = ProjectFile.read( - path, - plette.lockfiles.Lockfile, - invalid_ok=True - ) + pf = ProjectFile.read(path, plette.lockfiles.Lockfile, invalid_ok=True) return pf @classmethod def lockfile_from_pipfile(cls, pipfile_path): from .pipfile import Pipfile + if os.path.isfile(pipfile_path): if not os.path.isabs(pipfile_path): pipfile_path = os.path.abspath(pipfile_path) @@ -164,7 +160,9 @@ class Lockfile(object): if not project_path.exists(): raise OSError("Project does not exist: %s" % project_path.as_posix()) elif not lockfile_path.exists() and not create: - raise FileNotFoundError("Lockfile does not exist: %s" % lockfile_path.as_posix()) + raise FileNotFoundError( + "Lockfile does not exist: %s" % lockfile_path.as_posix() + ) projectfile = cls.read_projectfile(lockfile_path.as_posix()) if not lockfile_path.exists(): if not data: @@ -207,10 +205,14 @@ class Lockfile(object): lockfile.update(data) else: lockfile = plette.lockfiles.Lockfile(data) - projectfile = ProjectFile(line_ending=DEFAULT_NEWLINES, location=lockfile_path, model=lockfile) + projectfile = ProjectFile( + line_ending=DEFAULT_NEWLINES, location=lockfile_path, model=lockfile + ) return cls( - projectfile=projectfile, lockfile=lockfile, - newlines=projectfile.line_ending, path=Path(projectfile.location) + projectfile=projectfile, + lockfile=lockfile, + newlines=projectfile.line_ending, + path=Path(projectfile.location), ) @classmethod @@ -243,7 +245,7 @@ class Lockfile(object): "projectfile": projectfile, "lockfile": projectfile.model, "newlines": projectfile.line_ending, - "path": lockfile_path + "path": lockfile_path, } return cls(**creation_args) @@ -260,7 +262,8 @@ class Lockfile(object): return self._lockfile.default def get_requirements(self, dev=True, only=False): - """Produces a generator which generates requirements from the desired section. + """Produces a generator which generates requirements from the desired + section. :param bool dev: Indicates whether to use dev requirements, defaults to False :return: Requirements from the relevant the relevant pipfile @@ -296,13 +299,11 @@ class Lockfile(object): self.projectfile.write() def as_requirements(self, include_hashes=False, dev=False): - """Returns a list of requirements in pip-style format""" + """Returns a list of requirements in pip-style format.""" lines = [] section = self.dev_requirements if dev else self.requirements for req in section: - kwargs = { - "include_hashes": include_hashes, - } + kwargs = {"include_hashes": include_hashes} if req.editable: kwargs["include_markers"] = False r = req.as_line(**kwargs) diff --git a/pipenv/vendor/requirementslib/models/markers.py b/pipenv/vendor/requirementslib/models/markers.py index fc85fbdd..54fb7164 100644 --- a/pipenv/vendor/requirementslib/models/markers.py +++ b/pipenv/vendor/requirementslib/models/markers.py @@ -1,30 +1,29 @@ # -*- coding: utf-8 -*- import itertools import operator +import re +from collections.abc import Mapping, Set +from functools import lru_cache, reduce -import attr +import pipenv.vendor.attr as attr import distlib.markers import packaging.version -import six -from packaging.markers import InvalidMarker, Marker -from packaging.specifiers import Specifier, SpecifierSet -from vistir.compat import Mapping, Set, lru_cache -from vistir.misc import dedup +from pipenv.vendor.packaging.markers import InvalidMarker, Marker +from pipenv.vendor.packaging.specifiers import Specifier, SpecifierSet +from pipenv.vendor.vistir.misc import dedup -from .utils import filter_none, validate_markers from ..environment import MYPY_RUNNING from ..exceptions import RequirementError - -from six.moves import reduce # isort:skip - +from .utils import filter_none, validate_markers if MYPY_RUNNING: - from typing import Optional, List, Type, Any, Tuple, Union, AnyStr, Text, Iterator + from typing import Any, AnyStr, Iterator, List, Optional, Text, Tuple, Type, Union STRING_TYPE = Union[str, bytes, Text] -MAX_VERSIONS = {2: 7, 3: 10} +MAX_VERSIONS = {1: 7, 2: 7, 3: 11, 4: 0} +DEPRECATED_VERSIONS = ["3.0", "3.1", "3.2", "3.3"] def is_instance(item, cls): @@ -128,7 +127,7 @@ def _tuplize_version(version): @lru_cache(maxsize=1024) def _format_version(version): # type: (Tuple[int, ...]) -> STRING_TYPE - if not isinstance(version, six.string_types): + if not isinstance(version, str): return ".".join(str(i) for i in version) return version @@ -147,9 +146,8 @@ def _format_pyspec(specifier): version = getattr(specifier, "version", specifier).rstrip() if version and version.endswith("*"): if version.endswith(".*"): - version = version.rstrip(".*") - else: - version = version.rstrip("*") + version = version[:-2] + version = version.rstrip("*") specifier = Specifier("{0}{1}".format(specifier.operator, version)) try: op = REPLACE_RANGES[specifier.operator] @@ -196,18 +194,23 @@ def _get_specs(specset): return sorted(result, key=operator.itemgetter(1)) +# TODO: Rename this to something meaningful def _group_by_op(specs): # type: (Union[Set[Specifier], SpecifierSet]) -> Iterator specs = [_get_specs(x) for x in list(specs)] - flattened = [(op, version) for spec in specs for op, version in spec] + flattened = [ + ((op, len(version) > 2), version) for spec in specs for op, version in spec + ] specs = sorted(flattened) grouping = itertools.groupby(specs, key=operator.itemgetter(0)) return grouping +# TODO: rename this to something meaningful def normalize_specifier_set(specs): # type: (Union[str, SpecifierSet]) -> Optional[Set[Specifier]] - """Given a specifier set, a string, or an iterable, normalize the specifiers + """Given a specifier set, a string, or an iterable, normalize the + specifiers. .. note:: This function exists largely to deal with ``pyzmq`` which handles the ``requires_python`` specifier incorrectly, using ``3.7*`` rather than @@ -223,18 +226,20 @@ def normalize_specifier_set(specs): if isinstance(specs, set): return specs # when we aren't dealing with a string at all, we can normalize this as usual - elif not isinstance(specs, six.string_types): + elif not isinstance(specs, str): return {_format_pyspec(spec) for spec in specs} spec_list = [] for spec in specs.split(","): + spec = spec.strip() if spec.endswith(".*"): - spec = spec.rstrip(".*") - elif spec.endswith("*"): - spec = spec.rstrip("*") + spec = spec[:-2] + spec = spec.rstrip("*") spec_list.append(spec) return normalize_specifier_set(SpecifierSet(",".join(spec_list))) +# TODO: Check if this is used by anything public otherwise make it private +# And rename it to something meaningful def get_sorted_version_string(version_set): # type: (Set[AnyStr]) -> AnyStr version_list = sorted( @@ -244,6 +249,9 @@ def get_sorted_version_string(version_set): return version +# TODO: Rename this to something meaningful +# TODO: Add a deprecation decorator and deprecate this -- i'm sure it's used +# in other libraries @lru_cache(maxsize=1024) def cleanup_pyspecs(specs, joiner="or"): specs = normalize_specifier_set(specs) @@ -275,7 +283,8 @@ def cleanup_pyspecs(specs, joiner="or"): "==": lambda x: "in" if len(x) > 1 else "==", } translation_keys = list(translation_map.keys()) - for op, versions in _group_by_op(tuple(specs)): + for op_and_version_type, versions in _group_by_op(tuple(specs)): + op = op_and_version_type[0] versions = [version[1] for version in versions] versions = sorted(dedup(versions)) op_key = next(iter(k for k in translation_keys if op in k), None) @@ -284,10 +293,11 @@ def cleanup_pyspecs(specs, joiner="or"): version_value = translation_map[op_key][joiner](versions) if op in op_translations: op = op_translations[op](versions) - results[op] = version_value - return sorted([(k, v) for k, v in results.items()], key=operator.itemgetter(1)) + results[(op, op_and_version_type[1])] = version_value + return sorted([(k[0], v) for k, v in results.items()], key=operator.itemgetter(1)) +# TODO: Rename this to something meaningful @lru_cache(maxsize=1024) def fix_version_tuple(version_tuple): # type: (Tuple[AnyStr, AnyStr]) -> Tuple[AnyStr, AnyStr] @@ -302,6 +312,7 @@ def fix_version_tuple(version_tuple): return (op, version) +# TODO: Rename this to something meaningful, deprecate it (See prior function) @lru_cache(maxsize=128) def get_versions(specset, group_by_operator=True): # type: (Union[Set[Specifier], SpecifierSet], bool) -> List[Tuple[STRING_TYPE, STRING_TYPE]] @@ -355,10 +366,11 @@ def _strip_pyversion(elements): def _strip_marker_elem(elem_name, elements): """Remove the supplied element from the marker. - This is not a comprehensive implementation, but relies on an important - characteristic of metadata generation: The element's operand is always - associated with an "and" operator. This means that we can simply remove the - operand and the "and" operator associated with it. + This is not a comprehensive implementation, but relies on an + important characteristic of metadata generation: The element's + operand is always associated with an "and" operator. This means that + we can simply remove the operand and the "and" operator associated + with it. """ extra_indexes = [] @@ -412,8 +424,8 @@ def get_without_extra(marker): def get_without_pyversion(marker): """Built a new marker without the `python_version` part. - This could return `None` if the `python_version` section is the only section in the - marker. + This could return `None` if the `python_version` section is the only + section in the marker. """ return _get_stripped_marker(marker, _strip_pyversion) @@ -431,7 +443,7 @@ def _markers_collect_extras(markers, collection): def _markers_collect_pyversions(markers, collection): local_collection = [] marker_format_str = "{0}" - for i, el in enumerate(reversed(markers)): + for el in reversed(markers): if isinstance(el, tuple) and el[0].value == "python_version": new_marker = str(gen_marker(el)) local_collection.append(marker_format_str.format(new_marker)) @@ -477,8 +489,7 @@ def get_contained_extras(marker): @lru_cache(maxsize=1024) def get_contained_pyversions(marker): - """Collect all `python_version` operands from a marker. - """ + """Collect all `python_version` operands from a marker.""" collection = [] if not marker: @@ -509,8 +520,7 @@ def get_contained_pyversions(marker): @lru_cache(maxsize=128) def contains_extra(marker): - """Check whehter a marker contains an "extra == ..." operand. - """ + """Check whehter a marker contains an "extra == ..." operand.""" if not marker: return False marker = _ensure_marker(marker) @@ -519,8 +529,7 @@ def contains_extra(marker): @lru_cache(maxsize=128) def contains_pyversion(marker): - """Check whether a marker contains a python_version operand. - """ + """Check whether a marker contains a python_version operand.""" if not marker: return False @@ -528,39 +537,68 @@ def contains_pyversion(marker): return _markers_contains_pyversion(marker._markers) +def _split_specifierset_str(specset_str, prefix="=="): + # type: (str, str) -> Set[Specifier] + """Take a specifierset string and split it into a list to join for + specifier sets. + + :param str specset_str: A string containing python versions, often comma separated + :param str prefix: A prefix to use when generating the specifier set + :return: A list of :class:`Specifier` instances generated with the provided prefix + :rtype: Set[Specifier] + """ + specifiers = set() + if "," not in specset_str and " " in specset_str: + values = [v.strip() for v in specset_str.split()] + else: + values = [v.strip() for v in specset_str.split(",")] + if prefix == "!=" and any(v in values for v in DEPRECATED_VERSIONS): + values += DEPRECATED_VERSIONS[:] + for value in sorted(values): + specifiers.add(Specifier("{0}{1}".format(prefix, value))) + return specifiers + + +def _get_specifiers_from_markers(marker_item): + """Given a marker item, get specifiers from the version marker. + + :param :class:`~packaging.markers.Marker` marker_sequence: A marker describing a version constraint + :return: A set of specifiers corresponding to the marker constraint + :rtype: Set[Specifier] + """ + specifiers = set() + if isinstance(marker_item, tuple): + variable, op, value = marker_item + if variable.value != "python_version": + return specifiers + if op.value == "in": + specifiers.update(_split_specifierset_str(value.value, prefix="==")) + elif op.value == "not in": + specifiers.update(_split_specifierset_str(value.value, prefix="!=")) + else: + specifiers.add(Specifier("{0}{1}".format(op.value, value.value))) + elif isinstance(marker_item, list): + parts = get_specset(marker_item) + if parts: + specifiers.update(parts) + return specifiers + + def get_specset(marker_list): # type: (List) -> Optional[SpecifierSet] specset = set() _last_str = "and" for marker_parts in marker_list: - if isinstance(marker_parts, tuple): - variable, op, value = marker_parts - if variable.value != "python_version": - continue - if op.value == "in": - values = [v.strip() for v in value.value.split(",")] - specset.update(Specifier("=={0}".format(v)) for v in values) - elif op.value == "not in": - values = [v.strip() for v in value.value.split(",")] - bad_versions = ["3.0", "3.1", "3.2", "3.3"] - if len(values) >= 2 and any(v in values for v in bad_versions): - values = bad_versions - specset.update( - Specifier("!={0}".format(v.strip())) for v in sorted(bad_versions) - ) - else: - specset.add(Specifier("{0}{1}".format(op.value, value.value))) - elif isinstance(marker_parts, list): - parts = get_specset(marker_parts) - if parts: - specset.update(parts) - elif isinstance(marker_parts, str): - _last_str = marker_parts + if isinstance(marker_parts, str): + _last_str = marker_parts # noqa + else: + specset.update(_get_specifiers_from_markers(marker_parts)) specifiers = SpecifierSet() specifiers._specs = frozenset(specset) return specifiers +# TODO: Refactor this (reduce complexity) def parse_marker_dict(marker_dict): op = marker_dict["op"] lhs = marker_dict["lhs"] @@ -629,13 +667,20 @@ def parse_marker_dict(marker_dict): return specset, finalized_marker +def _contains_micro_version(version_string): + return re.search(r"\d+\.\d+\.\d+", version_string) is not None + + def format_pyversion(parts): op, val = parts - return "python_version {0} '{1}'".format(op, val) + version_marker = ( + "python_full_version" if _contains_micro_version(val) else "python_version" + ) + return "{0} {1} '{2}'".format(version_marker, op, val) def normalize_marker_str(marker): - # type: (Union[Marker, STRING_TYPE]) + # type: (Union[Marker, STRING_TYPE]) -> str marker_str = "" if not marker: return None @@ -670,3 +715,16 @@ def marker_from_specifier(spec): marker_segments.append(format_pyversion(marker_segment)) marker_str = " and ".join(marker_segments).replace('"', "'") return Marker(marker_str) + + +def merge_markers(m1, m2): + # type: (Marker, Marker) -> Optional[Marker] + if not all((m1, m2)): + return next(iter(v for v in (m1, m2) if v), None) + m1 = _ensure_marker(m1) + m2 = _ensure_marker(m2) + _markers = [] # type: List[Marker] + for marker in (m1, m2): + _markers.append(str(marker)) + marker_str = " and ".join([normalize_marker_str(m) for m in _markers if m]) + return _ensure_marker(normalize_marker_str(marker_str)) diff --git a/pipenv/vendor/requirementslib/models/metadata.py b/pipenv/vendor/requirementslib/models/metadata.py new file mode 100644 index 00000000..668189ef --- /dev/null +++ b/pipenv/vendor/requirementslib/models/metadata.py @@ -0,0 +1,1246 @@ +# -*- coding=utf-8 -*- +import datetime +import functools +import io +import json +import logging +import operator +import os +import zipfile +from collections import defaultdict +from functools import reduce +from typing import Sequence + +import pipenv.vendor.attr as attr +import dateutil.parser +import distlib.metadata +import distlib.wheel +import packaging.version +import pipenv.vendor.requests as requests +import pipenv.vendor.vistir as vistir +from pipenv.vendor.packaging.markers import Marker +from pipenv.vendor.packaging.requirements import Requirement as PackagingRequirement +from pipenv.vendor.packaging.specifiers import Specifier, SpecifierSet +from pipenv.vendor.packaging.tags import Tag + +from ..environment import MYPY_RUNNING +from .markers import ( + get_contained_extras, + get_contained_pyversions, + get_without_extra, + get_without_pyversion, + marker_from_specifier, + merge_markers, + normalize_specifier_set, +) +from .requirements import Requirement +from .utils import filter_dict, get_pinned_version, is_pinned_requirement + +ch = logging.StreamHandler() +formatter = logging.Formatter("%(asctime)s %(levelname)s: %(message)s") +ch.setFormatter(formatter) +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + + +if MYPY_RUNNING: + from typing import ( + Any, + Callable, + Dict, + Generator, + Generic, + Iterator, + List, + Optional, + Set, + Tuple, + Type, + TypeVar, + Union, + ) + + from pipenv.vendor.attr import Attribute # noqa + + from .setup_info import SetupInfo + + TAttrsClass = TypeVar("TAttrsClass") + AttrsClass = Generic[TAttrsClass] + TDigestDict = Dict[str, str] + TProjectUrls = Dict[str, str] + TReleaseUrlDict = Dict[str, Union[bool, int, str, TDigestDict]] + TReleasesList = List[TReleaseUrlDict] + TReleasesDict = Dict[str, TReleasesList] + TDownloads = Dict[str, int] + TPackageInfo = Dict[str, Optional[Union[str, List[str], TDownloads, TProjectUrls]]] + TPackage = Dict[str, Union[TPackageInfo, int, TReleasesDict, TReleasesList]] + + +VALID_ALGORITHMS = { + "sha1": 40, + "sha3_224": 56, + "sha512": 128, + "blake2b": 128, + "sha256": 64, + "sha384": 96, + "blake2s": 64, + "sha3_256": 64, + "sha3_512": 128, + "md5": 32, + "sha3_384": 96, + "sha224": 56, +} # type: Dict[str, int] + +PACKAGE_TYPES = { + "sdist", + "bdist_wheel", + "bdist_egg", + "bdist_dumb", + "bdist_wininst", + "bdist_rpm", + "bdist_msi", + "bdist_dmg", +} + + +class PackageEncoder(json.JSONEncoder): + def default(self, obj): # noqa:E0202 # noqa:W0221 + if isinstance(obj, datetime.datetime): + return obj.isoformat() + elif isinstance(obj, PackagingRequirement): + return obj.__dict__ + elif isinstance(obj, set): + return tuple(obj) + elif isinstance(obj, (Specifier, SpecifierSet, Marker)): + return str(obj) + else: + return json.JSONEncoder.default(self, obj) + + +def validate_extras(inst, attrib, value): + # type: ("Dependency", Attribute, Tuple[str, ...]) -> None + duplicates = [k for k in value if value.count(k) > 1] + if duplicates: + raise ValueError("Found duplicate keys: {0}".format(", ".join(duplicates))) + return None + + +def validate_digest(inst, attrib, value): + # type: ("Digest", Attribute, str) -> None + expected_length = VALID_ALGORITHMS[inst.algorithm.lower()] + if len(value) != expected_length: + raise ValueError( + "Expected a digest of length {0!s}, got one of length {1!s}".format( + expected_length, len(value) + ) + ) + return None + + +def get_local_wheel_metadata(wheel_file): + # type: (str) -> Optional[distlib.metadata.Metadata] + parsed_metadata = None + with io.open(wheel_file, "rb") as fh: + with zipfile.ZipFile(fh, mode="r", compression=zipfile.ZIP_DEFLATED) as zf: + metadata = None + for fn in zf.namelist(): + if os.path.basename(fn) == "METADATA": + metadata = fn + break + if metadata is None: + raise RuntimeError("No metadata found in wheel: {0}".format(wheel_file)) + with zf.open(metadata, "r") as metadata_fh: + parsed_metadata = distlib.metadata.Metadata(fileobj=metadata_fh) + return parsed_metadata + + +def get_remote_sdist_metadata(line): + # type: (str) -> SetupInfo + req = Requirement.from_line(line) + try: + _ = req.run_requires() + except SystemExit: + raise RuntimeError("Failed to compute metadata for dependency {0}".format(line)) + else: + return req.line_instance.setup_info + + +def get_remote_wheel_metadata(whl_file): + # type: (str) -> Optional[distlib.metadata.Metadata] + parsed_metadata = None + data = io.BytesIO() + with vistir.contextmanagers.open_file(whl_file) as fp: + for chunk in iter(lambda: fp.read(8096), b""): + data.write(chunk) + with zipfile.ZipFile(data, mode="r", compression=zipfile.ZIP_DEFLATED) as zf: + metadata = None + for fn in zf.namelist(): + if os.path.basename(fn) == "METADATA": + metadata = fn + break + if metadata is None: + raise RuntimeError("No metadata found in wheel: {0}".format(whl_file)) + with zf.open(metadata, "r") as metadata_fh: + parsed_metadata = distlib.metadata.Metadata(fileobj=metadata_fh) + return parsed_metadata + + +def create_specifierset(spec=None): + # type: (Optional[str]) -> SpecifierSet + if isinstance(spec, SpecifierSet): + return spec + elif isinstance(spec, (set, list, tuple)): + spec = " and ".join(spec) + if spec is None: + spec = "" + return SpecifierSet(spec) + + +@attr.s(frozen=True, eq=True) +class ExtrasCollection(object): + #: The name of the extras collection (e.g. 'security') + name = attr.ib(type=str) + #: The dependency the collection belongs to + parent = attr.ib(type="Dependency") + #: The members of the collection + dependencies = attr.ib(factory=set) # type: Set["Dependency"] + + def add_dependency(self, dependency): + # type: ("Dependency") -> "ExtrasCollection" + if not isinstance(dependency, Dependency): + raise TypeError( + "Expected a Dependency instance, received {0!r}".format(dependency) + ) + dependencies = self.dependencies.copy() + dependencies.add(dependency) + return attr.evolve(self, dependencies=dependencies) + + +@attr.s(frozen=True, eq=True) +class Dependency(object): + #: The name of the dependency + name = attr.ib(type=str) + #: A requirement instance + requirement = attr.ib(type=PackagingRequirement, eq=False) + #: The specifier defined in the dependency definition + specifier = attr.ib(type=SpecifierSet, converter=create_specifierset, eq=False) + #: Any extras this dependency declares + extras = attr.ib(factory=tuple, validator=validate_extras) # type: Tuple[str, ...] + #: The name of the extra meta-dependency this one came from (e.g. 'security') + from_extras = attr.ib(default=None, eq=False) # type: Optional[str] + #: The declared specifier set of allowable python versions for this dependency + python_version = attr.ib( + default="", type=SpecifierSet, converter=create_specifierset, eq=False + ) + #: The parent of this dependency (i.e. where it came from) + parent = attr.ib(default=None) # type: Optional[Dependency] + #: The markers for this dependency + markers = attr.ib(default=None, eq=False) # type: Optional[Marker] + _specset_str = attr.ib(default="", type=str) + _python_version_str = attr.ib(default="", type=str) + _marker_str = attr.ib(default="", type=str) + + def __str__(self): + # type: () -> str + return str(self.requirement) + + def as_line(self): + # type: () -> str + line_str = "{0}".format(self.name) + if self.extras: + line_str = "{0}[{1}]".format(line_str, ",".join(self.extras)) + if self.specifier: + line_str = "{0}{1!s}".format(line_str, self.specifier) + py_version_part = "" + if self.python_version: + specifiers = normalize_specifier_set(self.python_version) + markers = [] + if specifiers is not None: + markers = [marker_from_specifier(str(s)) for s in specifiers] + py_version_part = reduce(merge_markers, markers) + if self.markers: + line_str = "{0}; {1}".format(line_str, str(self.markers)) + if py_version_part: + line_str = "{0} and {1}".format(line_str, py_version_part) + elif py_version_part and not self.markers: + line_str = "{0}; {1}".format(line_str, py_version_part) + return line_str + + def pin(self): + # type: () -> "Package" + base_package = get_package(self.name) + sorted_releases = sorted( + base_package.releases.non_yanked_releases, + key=operator.attrgetter("parsed_version"), + reverse=True, + ) + version = next( + iter(self.specifier.filter((r.version for r in sorted_releases))), None + ) + if not version: + version = next( + iter( + self.specifier.filter( + (r.version for r in sorted_releases), prereleases=True + ) + ), + None, + ) + if not version: + raise RuntimeError( + "Failed to resolve {0} ({1!s})".format(self.name, self.specifier) + ) + match = get_package_version(self.name, str(version)) + return match + + @classmethod + def from_requirement(cls, req, parent=None): + # type: (PackagingRequirement, Optional["Dependency"]) -> "Dependency" + from_extras, marker, python_version = None, None, None + specset_str, py_version_str, marker_str = "", "", "" + if req.marker: + marker = Marker(str(req.marker)) + from_extras = next(iter(list(get_contained_extras(marker))), None) + python_version = get_contained_pyversions(marker) + marker = get_without_extra(get_without_pyversion(marker)) + if not str(marker) or not marker or not marker._markers: + marker = None + req.marker = marker + if marker is not None: + marker_str = str(marker) + if req.specifier: + specset_str = str(req.specifier) + if python_version: + py_version_str = str(python_version) + return cls( + name=req.name, + specifier=req.specifier, + extras=tuple(sorted(set(req.extras))) + if req.extras is not None + else req.extras, + requirement=req, + from_extras=from_extras, + python_version=python_version, + markers=marker, + parent=parent, + specset_str=specset_str, + python_version_str=py_version_str, + marker_str=marker_str, + ) + + @classmethod + def from_info(cls, info): + # type: ("PackageInfo") -> "Dependency" + marker_str = "" + specset_str, py_version_str = "", "" + if info.requires_python: + # XXX: Some markers are improperly formatted -- we already handle most cases + # XXX: but learned about new broken formats, such as + # XXX: python_version in "2.6 2.7 3.2 3.3" (note the lack of commas) + # XXX: as a marker on a dependency of a library called 'pickleshare' + # XXX: Some packages also have invalid markers with stray characters, + # XXX: such as 'algoliasearch' + try: + marker = marker_from_specifier(info.requires_python) + except Exception: + marker_str = "" + else: + if not marker or not marker._markers: + marker_str = "" + else: + marker_str = "{0!s}".format(marker) + req_str = "{0}=={1}".format(info.name, info.version) + if marker_str: + req_str = "{0}; {1}".format(req_str, marker_str) + req = PackagingRequirement(req_str) + requires_python_str = ( + info.requires_python if info.requires_python is not None else "" + ) + if req.specifier: + specset_str = str(req.specifier) + if requires_python_str: + py_version_str = requires_python_str + return cls( + name=info.name, + specifier=req.specifier, + extras=tuple(sorted(set(req.extras))) + if req.extras is not None + else req.extras, + requirement=req, + from_extras=None, + python_version=SpecifierSet(requires_python_str), + markers=None, + parent=None, + specset_str=specset_str, + python_version_str=py_version_str, + marker_str=marker_str, + ) + + @classmethod + def from_str(cls, depstr, parent=None): + # type: (str, Optional["Dependency"]) -> "Dependency" + try: + req = PackagingRequirement(depstr) + except Exception: + raise + return cls.from_requirement(req, parent=parent) + + def add_parent(self, parent): + # type: ("Dependency") -> "Dependency" + return attr.evolve(self, parent=parent) + + +@attr.s(frozen=True, eq=True) +class Digest(object): + #: The algorithm declared for the digest, e.g. 'sha256' + algorithm = attr.ib( + type=str, validator=attr.validators.in_(VALID_ALGORITHMS.keys()), eq=True + ) + #: The digest value + value = attr.ib(type=str, validator=validate_digest, eq=True) + + def __str__(self): + # type: () -> str + return "{0}:{1}".format(self.algorithm, self.value) + + @classmethod + def create(cls, algorithm, value): + # type: (str, str) -> "Digest" + return cls(algorithm=algorithm, value=value) + + @classmethod + def collection_from_dict(cls, digest_dict): + # type: (TDigestDict) -> List["Digest"] + return [cls.create(k, v) for k, v in digest_dict.items()] + + +# XXX: This is necessary because attrs converters can only be functions, not classmethods +def create_digest_collection(digest_dict): + # type: (TDigestDict) -> List["Digest"] + return Digest.collection_from_dict(digest_dict) + + +def instance_check_converter(expected_type=None, converter=None): + # type: (Optional[Type], Optional[Callable]) -> Callable + def _converter(val): + if expected_type is not None and isinstance(val, expected_type): + return val + return converter(val) + + return _converter + + +@attr.s(frozen=True, eq=True) +class ParsedTag(object): + #: The marker string corresponding to the tag + marker_string = attr.ib(default=None) # type: Optional[str] + #: The python version represented by the tag + python_version = attr.ib(default=None) # type: Optional[str] + #: The platform represented by the tag + platform_system = attr.ib(default=None) # type: Optional[str] + #: the ABI represented by the tag + abi = attr.ib(default=None) # type: Optional[str] + + +def parse_tag(tag): + # type: (Tag) -> ParsedTag + """Parse a :class:`~packaging.tags.Tag` instance. + + :param :class:`~packaging.tags.Tag` tag: A tag to parse + :return: A parsed tag with combined markers, supported platform and python version + :rtype: :class:`~ParsedTag` + """ + platform_system = None + python_version = None + version = None + marker_str = "" + if tag.platform.startswith("macos"): + platform_system = "Darwin" + elif tag.platform.startswith("manylinux") or tag.platform.startswith("linux"): + platform_system = "Linux" + elif tag.platform.startswith("win32"): + platform_system = "Windows" + if platform_system: + marker_str = 'platform_system == "{}"'.format(platform_system) + if tag.interpreter: + version = tag.interpreter[2:] + py_version_str = "" + if len(version) == 1: + py_version_str = ">={}.0,<{}".format(version, str(int(version) + 1)) + elif len(version) > 1 and len(version) <= 3: + # reverse the existing version so we can add 1 to the first element + # and re-reverse, generating the new version, e.g. [3, 2, 8] => + # [8, 2, 3] => [9, 2, 3] => [3, 2, 9] + next_version_list = list(reversed(version[:])) + next_version_list[0] = str(int(next_version_list[0]) + 1) + next_version = ".".join(list(reversed(next_version_list))) + version = ".".join(version) + py_version_str = ">={},<{}".format(version, next_version) + else: + py_version_str = "{0}".format(version) + python_version = marker_from_specifier(py_version_str) + if python_version: + if marker_str: + marker_str = "{0} and {1!s}".format(marker_str, python_version) + else: + marker_str = str(python_version) + return ParsedTag( + marker_string=marker_str, + python_version=version, + platform_system=platform_system, + abi=tag.abi, + ) + + +@attr.s(frozen=True, eq=True) +class ReleaseUrl(object): + #: The MD5 digest of the given release + md5_digest = attr.ib(type=Digest) + #: The package type of the url + packagetype = attr.ib(type=str, validator=attr.validators.in_(PACKAGE_TYPES)) + #: The upload timestamp from the package + upload_time = attr.ib( + type=datetime.datetime, + converter=instance_check_converter(datetime.datetime, dateutil.parser.parse), # type: ignore + ) + #: The ISO8601 formatted upload timestamp of the package + upload_time_iso_8601 = attr.ib( + type=datetime.datetime, + converter=instance_check_converter(datetime.datetime, dateutil.parser.parse), # type: ignore + ) + #: The size in bytes of the package + size = attr.ib(type=int) + #: The URL of the package + url = attr.ib(type=str) + #: The digests of the package + digests = attr.ib( + converter=instance_check_converter(list, create_digest_collection) # type: ignore + ) # type: List[Digest] + #: The name of the package + name = attr.ib(type=str, default=None) + #: The available comments of the given upload + comment_text = attr.ib(type=str, default="") + #: Whether the url has been yanked from the server + yanked = attr.ib(type=bool, default=False) + #: The number of downloads (deprecated) + downloads = attr.ib(type=int, default=-1) + #: The filename of the current upload + filename = attr.ib(type=str, default="") + #: Whether the upload has a signature + has_sig = attr.ib(type=bool, default=False) + #: The python_version attribute of the upload (e.g. 'source', 'py27', etc) + python_version = attr.ib(type=str, default="source") + #: The 'requires_python' restriction on the package + requires_python = attr.ib(type=str, default=None) + #: A list of valid aprsed tags from the upload + tags = attr.ib(factory=list) # type: List[ParsedTag] + + @property + def is_wheel(self): + # type: () -> bool + return os.path.splitext(self.filename)[-1].lower() == ".whl" + + @property + def is_sdist(self): + # type: () -> bool + return self.python_version == "source" + + @property + def markers(self): + # type: () -> Optional[str] + # TODO: Compare dependencies in parent and add markers for python version + # TODO: Compare dependencies in parent and add markers for platform + # XXX: We can't use wheel-based markers until we do it via derived markers by + # XXX: comparing in the parent (i.e. 'Release' instance or so) and merging + # XXX: down to the common / minimal set of markers otherwise we wind up + # XXX: with an unmanageable set and combinatorial explosion + # if self.is_wheel: + # return self.get_markers_from_wheel() + if self.requires_python: + return marker_from_specifier(self.requires_python) + return None + + @property + def pep508_url(self): + # type: () -> str + markers = self.markers + req_str = "{0} @ {1}#egg={0}".format(self.name, self.url) + if markers: + req_str = "{0}; {1}".format(req_str, markers) + return req_str + + def get_markers_from_wheel(self): + # type: () -> str + supported_platforms = [] # type: List[str] + supported_pyversions = [] + supported_abis = [] + markers = [] + for parsed_tag in self.tags: + if parsed_tag.marker_string: + markers.append(Marker(parsed_tag.marker_string)) + if parsed_tag.python_version: + supported_pyversions.append(parsed_tag.python_version) + if parsed_tag.abi: + supported_abis.append(parsed_tag.abi) + if not (markers or supported_platforms): + return "" + if ( + all(pyversion in supported_pyversions for pyversion in ["2", "3"]) + and not supported_platforms + ): + marker_line = "" + else: + marker_line = " or ".join(["{}".format(str(marker)) for marker in markers]) + return marker_line + + def get_dependencies(self): + # type: () -> Tuple["ReleaseUrl", Dict[str, Union[List[str], str]]] + results = {"requires_python": None} + requires_dist = [] # type: List[str] + if self.is_wheel: + metadata = get_remote_wheel_metadata(self.url) + if metadata is not None: + requires_dist = metadata.run_requires + if not self.requires_python: + results["requires_python"] = metadata._legacy.get("Requires-Python") + else: + try: + metadata = get_remote_sdist_metadata(self.pep508_url) + except Exception: + requires_dist = [] + else: + requires_dist = [str(v) for v in metadata.requires.values()] + results["requires_dist"] = requires_dist + requires_python = getattr(self, "requires_python", results["requires_python"]) + return attr.evolve(self, requires_python=requires_python), results + + @property + def sha256(self): + # type: () -> str + return next( + iter(digest for digest in self.digests if digest.algorithm == "sha256") + ).value + + @classmethod + def create(cls, release_dict, name=None): + # type: (TReleaseUrlDict, Optional[str]) -> "ReleaseUrl" + valid_digest_keys = set("{0}_digest".format(k) for k in VALID_ALGORITHMS.keys()) + digest_keys = set(release_dict.keys()) & valid_digest_keys + creation_kwargs = ( + {} + ) # type: Dict[str, Union[bool, int, str, Digest, TDigestDict]] + creation_kwargs = {k: v for k, v in release_dict.items() if k not in digest_keys} + if name is not None: + creation_kwargs["name"] = name + for k in digest_keys: + digest = release_dict[k] + if not isinstance(digest, str): + raise TypeError("Digests must be strings, got {!r}".format(digest)) + creation_kwargs[k] = Digest.create(k.replace("_digest", ""), digest) + release_url = cls(**filter_dict(creation_kwargs)) # type: ignore + if release_url.is_wheel: + supported_tags = [ + parse_tag(Tag(*tag)) for tag in distlib.wheel.Wheel(release_url.url).tags + ] + release_url = attr.evolve(release_url, tags=supported_tags) + return release_url + + +def create_release_urls_from_list(urls, name=None): + # type: (Union[TReleasesList, List[ReleaseUrl]], Optional[str]) -> List[ReleaseUrl] + url_list = [] + for release_dict in urls: + if isinstance(release_dict, ReleaseUrl): + if name and not release_dict.name: + release_dict = attr.evolve(release_dict, name=name) + url_list.append(release_dict) + continue + url_list.append(ReleaseUrl.create(release_dict, name=name)) + return url_list + + +@attr.s(frozen=True, eq=True) +class ReleaseUrlCollection(Sequence): + #: A list of release URLs + urls = attr.ib(converter=create_release_urls_from_list) + #: the name of the package + name = attr.ib(default=None) # type: Optional[str] + + @classmethod + def create(cls, urls, name=None): + # type: (TReleasesList, Optional[str]) -> "ReleaseUrlCollection" + return cls(urls=urls, name=name) + + @property + def wheels(self): + # type: () -> Iterator[ReleaseUrl] + for url in self.urls: + if not url.is_wheel: + continue + yield url + + @property + def sdists(self): + # type: () -> Iterator[ReleaseUrl] + for url in self.urls: + if not url.is_sdist: + continue + yield url + + def __iter__(self): + # type: () -> Iterator[ReleaseUrl] + return iter(self.urls) + + def __getitem__(self, key): + # type: (int) -> ReleaseUrl + return self.urls.__getitem__(key) + + def __len__(self): + # type: () -> int + return len(self.urls) + + @property + def latest(self): + # type: () -> Optional[ReleaseUrl] + if not self.urls: + return None + return next( + iter(sorted(self.urls, key=operator.attrgetter("upload_time"), reverse=True)) + ) + + @property + def latest_timestamp(self): + # type: () -> Optional[datetime.datetime] + latest = self.latest + if latest is not None: + return latest.upload_time + return None + + def find_package_type(self, type_): + # type: (str) -> Optional[ReleaseUrl] + """Given a package type (e.g. sdist, bdist_wheel), find the matching + release. + + :param str type_: A package type from :const:`~PACKAGE_TYPES` + :return: The package from this collection matching that type, if available + :rtype: Optional[ReleaseUrl] + """ + if type_ not in PACKAGE_TYPES: + raise ValueError( + "Invalid package type: {0}. Expected one of {1}".format( + type_, " ".join(PACKAGE_TYPES) + ) + ) + return next(iter(url for url in self.urls if url.packagetype == type_), None) + + +def convert_release_urls_to_collection(urls=None, name=None): + # type: (Optional[TReleasesList], Optional[str]) -> ReleaseUrlCollection + if urls is None: + urls = [] + urls = create_release_urls_from_list(urls, name=name) + return ReleaseUrlCollection.create(urls, name=name) + + +@attr.s(frozen=True) +class Release(Sequence): + #: The version of the release + version = attr.ib(type=str) + #: The URL collection for the release + urls = attr.ib( + converter=instance_check_converter( # type: ignore + ReleaseUrlCollection, convert_release_urls_to_collection + ), + type=ReleaseUrlCollection, + ) + #: the name of the package + name = attr.ib(default=None) # type: Optional[str] + + def __iter__(self): + # type: () -> Iterator[ReleaseUrlCollection] + return iter(self.urls) + + def __getitem__(self, key): + return self.urls[key] + + def __len__(self): + # type: () -> int + return len(self.urls) + + @property + def yanked(self): + # type: () -> bool + if not self.urls: + return True + return False + + @property + def parsed_version(self): + # type: () -> packaging.version._BaseVersion + return packaging.version.parse(self.version) + + @property + def wheels(self): + # type: () -> Iterator[ReleaseUrl] + return self.urls.wheels + + @property + def sdists(self): + # type: () -> Iterator[ReleaseUrl] + return self.urls.sdists + + @property + def latest(self): + # type: () -> ReleaseUrl + return self.urls.latest + + @property + def latest_timestamp(self): + # type: () -> datetime.datetime + return self.urls.latest_timestamp + + def to_lockfile(self): + # type: () -> Dict[str, Union[List[str], str]] + return { + "hashes": [str(url.sha256) for url in self.urls if url.sha256 is not None], + "version": "=={0}".format(self.version), + } + + +def get_release(version, urls, name=None): + # type: (str, TReleasesList, Optional[str]) -> Release + release_kwargs = {"version": version, "name": name} + if not isinstance(urls, ReleaseUrlCollection): + release_kwargs["urls"] = convert_release_urls_to_collection(urls, name=name) + else: + release_kwargs["urls"] = urls + return Release(**release_kwargs) # type: ignore + + +def get_releases_from_package(releases, name=None): + # type: (TReleasesDict, Optional[str]) -> List[Release] + release_list = [] + for version, urls in releases.items(): + release_list.append(get_release(version, urls, name=name)) + return release_list + + +@attr.s(frozen=True) +class ReleaseCollection(object): + releases = attr.ib( + factory=list, + converter=instance_check_converter(list, get_releases_from_package), # type: ignore + ) # type: List[Release] + + def __iter__(self): + # type: () -> Iterator[Release] + return iter(self.releases) + + def __getitem__(self, key): + # type: (str) -> Release + result = next(iter(r for r in self.releases if r.version == key), None) + if result is None: + raise KeyError(key) + return result + + def __len__(self): + # type: () -> int + return len(self.releases) + + def get_latest_lockfile(self): + # type: () -> Dict[str, Union[str, List[str]]] + return self.latest.to_lockfile() + + def wheels(self): + # type: () -> Iterator[ReleaseUrl] + for release in self.sort_releases(): + for wheel in release.wheels: + yield wheel + + def sdists(self): + # type: () -> Iterator[ReleaseUrl] + for release in self.sort_releases(): + for sdist in release.sdists: + yield sdist + + @property + def non_yanked_releases(self): + # type: () -> List[Release] + return list(r for r in self.releases if not r.yanked) + + def sort_releases(self): + # type: () -> List[Release] + return sorted( + self.non_yanked_releases, + key=operator.attrgetter("latest_timestamp"), + reverse=True, + ) + + @property + def latest(self): + # type: () -> Optional[Release] + return next(iter(r for r in self.sort_releases() if not r.yanked)) + + @classmethod + def load(cls, releases, name=None): + # type: (Union[TReleasesDict, List[Release]], Optional[str]) -> "ReleaseCollection" + if not isinstance(releases, list): + releases = get_releases_from_package(releases, name=name) + return cls(releases) + + +def convert_releases_to_collection(releases, name=None): + # type: (TReleasesDict, Optional[str]) -> ReleaseCollection + return ReleaseCollection.load(releases, name=name) + + +def split_keywords(value): + # type: (Union[str, List]) -> List[str] + if value and isinstance(value, str): + return value.split(",") + elif isinstance(value, list): + return value + return [] + + +def create_dependencies( + requires_dist, # type: Optional[List[Dependency]] + parent=None, # type: Optional[Dependency] +): + # type: (...) -> Optional[Set[Dependency]] + if requires_dist is None: + return None + dependencies = set() + for req in requires_dist: + if not isinstance(req, Dependency): + dependencies.add(Dependency.from_str(req, parent=parent)) + else: + dependencies.add(req) + return dependencies + + +@attr.s(frozen=True) +class PackageInfo(object): + name = attr.ib(type=str) + version = attr.ib(type=str) + package_url = attr.ib(type=str) + summary = attr.ib(type=str, default=None) # type: Optional[str] + author = attr.ib(type=str, default=None) # type: Optional[str] + keywords = attr.ib(factory=list, converter=split_keywords) # type: List[str] + description = attr.ib(type=str, default="") + download_url = attr.ib(type=str, default="") + home_page = attr.ib(type=str, default="") + license = attr.ib(type=str, default="") + maintainer = attr.ib(type=str, default="") + maintainer_email = attr.ib(type=str, default="") + downloads = attr.ib(factory=dict) # type: Dict[str, int] + docs_url = attr.ib(default=None) # type: Optional[str] + platform = attr.ib(type=str, default="") + project_url = attr.ib(type=str, default="") + project_urls = attr.ib(factory=dict) # type: Dict[str, str] + requires_python = attr.ib(default=None) # type: Optional[str] + requires_dist = attr.ib(factory=list) # type: List[Dependency] + release_url = attr.ib(default=None) # type: Optional[str] + description_content_type = attr.ib(type=str, default="text/md") + bugtrack_url = attr.ib(default=None) # type: str + classifiers = attr.ib(factory=list) # type: List[str] + author_email = attr.ib(default=None) # type: Optional[str] + markers = attr.ib(default=None) # type: Optional[str] + dependencies = attr.ib(default=None) # type: Tuple[Dependency] + + @classmethod + def from_json(cls, info_json): + # type: (TPackageInfo) -> "PackageInfo" + return cls(**filter_dict(info_json)) # type: ignore + + def to_dependency(self): + # type: () -> Dependency + return Dependency.from_info(self) + + def create_dependencies(self, force=False): + # type: (bool) -> "PackageInfo" + """Create values for **self.dependencies**. + + :param bool force: Sets **self.dependencies** to an empty tuple if it would be + None, defaults to False. + :return: An updated instance of the current object with **self.dependencies** + updated accordingly. + :rtype: :class:`PackageInfo` + """ + if not self.dependencies and not self.requires_dist: + if force: + return attr.evolve(self, dependencies=tuple()) + return self + self_dependency = self.to_dependency() + deps = set() + self_dependencies = tuple() if not self.dependencies else self.dependencies + for dep in self_dependencies: + if dep is None: + continue + new_dep = dep.add_parent(self_dependency) + deps.add(new_dep) + created_deps = create_dependencies(self.requires_dist, parent=self_dependency) + if created_deps is not None: + for dep in created_deps: + if dep is None: + continue + deps.add(dep) + return attr.evolve(self, dependencies=tuple(sorted(deps))) + + +def convert_package_info(info_json): + # type: (Union[TPackageInfo, PackageInfo]) -> PackageInfo + if isinstance(info_json, PackageInfo): + return info_json + return PackageInfo.from_json(info_json) + + +def add_markers_to_dep(d, marker_str): + # type: (str, Union[str, Marker]) -> str + req = PackagingRequirement(d) + existing_marker = getattr(req, "marker", None) + if isinstance(marker_str, Marker): + marker_str = str(marker_str) + if existing_marker is not None: + marker_str = str(merge_markers(existing_marker, marker_str)) + if marker_str: + marker_str = marker_str.replace("'", '"') + req.marker = Marker(marker_str) + return str(req) + + +@attr.s +class Package(object): + info = attr.ib(type=PackageInfo, converter=convert_package_info) + last_serial = attr.ib(type=int) + releases = attr.ib( + type=ReleaseCollection, + converter=instance_check_converter( # type: ignore + ReleaseCollection, convert_releases_to_collection + ), + ) + # XXX: Note: sometimes releases have no urls at the top level (e.g. pyrouge) + urls = attr.ib( + type=ReleaseUrlCollection, + converter=instance_check_converter( # type: ignore + ReleaseUrlCollection, convert_release_urls_to_collection + ), + ) + + @urls.default + def _get_urls_collection(self): + return functools.partial( + convert_release_urls_to_collection, urls=[], name=self.name + ) + + @property + def name(self): + # type: () -> str + return self.info.name + + @property + def version(self): + # type: () -> str + return self.info.version + + @property + def requirement(self): + # type: () -> PackagingRequirement + return self.info.to_dependency().requirement + + @property + def latest_sdist(self): + # type: () -> ReleaseUrl + return next(iter(self.urls.sdists)) + + @property + def latest_wheels(self): + # type: () -> Iterator[ReleaseUrl] + for wheel in self.urls.wheels: + yield wheel + + @property + def dependencies(self): + # type: () -> List[Dependency] + if self.info.dependencies is None and list(self.urls): + rval = self.get_dependencies() + return rval.dependencies + return list(self.info.dependencies) + + def get_dependencies(self): + # type: () -> "Package" + urls = [] # type: List[ReleaseUrl] + deps = set() # type: Set[str] + info = self.info + if info.dependencies is None: + for url in self.urls: + try: + url, dep_dict = url.get_dependencies() + except (RuntimeError, TypeError): + # This happens if we are parsing `setup.py` and we fail + if url.is_sdist: + continue + else: + raise + markers = url.markers + dep_list = dep_dict.get("requires_dist", []) + for dep in dep_list: + # XXX: We need to parse these as requirements and "and" the markers + # XXX: together because they may contain "extra" markers which we + # XXX: will need to parse and remove + deps.add(add_markers_to_dep(dep, markers)) + urls.append(url) + if None in deps: + deps.remove(None) + info = attr.evolve( + self.info, requires_dist=tuple(sorted(deps)) + ).create_dependencies(force=True) + return attr.evolve(self, info=info, urls=urls) + + @classmethod + def from_json(cls, package_json): + # type: (Dict[str, Any]) -> "Package" + info = convert_package_info(package_json["info"]).create_dependencies() + releases = convert_releases_to_collection( + package_json["releases"], name=info.name + ) + urls = convert_release_urls_to_collection(package_json["urls"], name=info.name) + return cls( + info=info, + releases=releases, + urls=urls, + last_serial=package_json["last_serial"], + ) + + def pin_dependencies(self, include_extras=None): + # type: (Optional[List[str]]) -> Tuple[List["Package"], Dict[str, List[SpecifierSet]]] + deps = [] + if include_extras: + include_extras = list(sorted(set(include_extras))) + else: + include_extras = [] + constraints = defaultdict(list) + for dep in self.dependencies: + if dep.from_extras and dep.from_extras not in include_extras: + continue + if dep.specifier: + constraints[dep.name].append(dep.specifier) + try: + pinned = dep.pin() + except requests.exceptions.HTTPError: + continue + deps.append(pinned) + return deps, constraints + + def get_latest_lockfile(self): + # type: () -> Dict[str, Dict[str, Union[List[str], str]]] + lockfile = {} + constraints = {dep.name: dep.specifier for dep in self.dependencies} + deps, _ = self.pin_dependencies() + for dep in deps: + dep = dep.get_dependencies() + for sub_dep in dep.dependencies: + if sub_dep.name not in constraints: + logger.info( + "Adding {0} (from {1}) {2!s}".format( + sub_dep.name, dep.name, sub_dep.specifier + ) + ) + constraints[sub_dep.name] = sub_dep.specifier + else: + existing = "{0} (from {1}): {2!s} + ".format( + sub_dep.name, dep.name, constraints[sub_dep.name] + ) + new_specifier = sub_dep.specifier + merged = constraints[sub_dep.name] & new_specifier + logger.info( + "Updating: {0}{1!s} = {2!s}".format( + existing, new_specifier, merged + ) + ) + constraints[sub_dep.name] = merged + + lockfile.update({dep.info.name: dep.releases.get_latest_lockfile()}) + for sub_dep_name, specset in constraints.items(): + try: + sub_dep_pkg = get_package(sub_dep_name) + except requests.exceptions.HTTPError: + continue + logger.info("Getting package: {0} ({1!s})".format(sub_dep, specset)) + sorted_releases = list( + sorted( + sub_dep_pkg.releases, + key=operator.attrgetter("parsed_version"), + reverse=True, + ) + ) + try: + version = next(iter(specset.filter((r.version for r in sorted_releases)))) + except StopIteration: + logger.info( + "No version of {0} matches specifier: {1}".format(sub_dep, specset) + ) + logger.info( + "Available versions: {0}".format( + " ".join([r.version for r in sorted_releases]) + ) + ) + raise + sub_dep_instance = get_package_version(sub_dep_name, version=str(version)) + if sub_dep_instance is None: + continue + lockfile.update( + { + sub_dep_instance.info.name: sub_dep_instance.releases.get_latest_lockfile() + } + ) + # lockfile.update(dep.get_latest_lockfile()) + lockfile.update({self.info.name: self.releases.get_latest_lockfile()}) + return lockfile + + def as_dict(self): + # type: () -> Dict[str, Any] + return json.loads(self.serialize()) + + def serialize(self): + # type: () -> str + return json.dumps(attr.asdict(self), cls=PackageEncoder, indent=4) + + +def get_package(name): + # type: (str) -> Package + url = "https://pypi.org/pypi/{}/json".format(name) + with requests.get(url) as r: + r.raise_for_status() + result = r.json() + package = Package.from_json(result) + return package + + +def get_package_version(name, version): + # type: (str, str) -> Package + url = "https://pypi.org/pypi/{0}/{1}/json".format(name, version) + with requests.get(url) as r: + r.raise_for_status() + result = r.json() + package = Package.from_json(result) + return package + + +def get_package_from_requirement(req): + # type: (PackagingRequirement) -> Tuple[Package, Set[str]] + versions = set() + if is_pinned_requirement(req): + version = get_pinned_version(req) + versions.add(version) + pkg = get_package_version(req.name, version) + else: + pkg = get_package(req.name) + sorted_releases = list( + sorted(pkg.releases, key=operator.attrgetter("parsed_version"), reverse=True) + ) + versions = set(req.specifier.filter((r.version for r in sorted_releases))) + version = next(iter(req.specifier.filter((r.version for r in sorted_releases)))) + if pkg.version not in versions: + pkg = get_package_version(pkg.name, version) + return pkg, versions diff --git a/pipenv/vendor/requirementslib/models/pipfile.py b/pipenv/vendor/requirementslib/models/pipfile.py index e55ad741..d949079b 100644 --- a/pipenv/vendor/requirementslib/models/pipfile.py +++ b/pipenv/vendor/requirementslib/models/pipfile.py @@ -3,24 +3,24 @@ from __future__ import absolute_import, print_function, unicode_literals import copy +import itertools import os -import sys +from pathlib import Path -import attr +import pipenv.vendor.attr as attr import plette.models.base import plette.pipfiles -import tomlkit -from vistir.compat import FileNotFoundError, Path +import pipenv.vendor.tomlkit as tomlkit -from .project import ProjectFile -from .requirements import Requirement -from .utils import get_url_name, optional_instance_of, tomlkit_value_to_python from ..environment import MYPY_RUNNING from ..exceptions import RequirementError from ..utils import is_editable, is_vcs, merge_items +from .project import ProjectFile +from .requirements import Requirement +from .utils import get_url_name, optional_instance_of, tomlkit_value_to_python if MYPY_RUNNING: - from typing import Union, Any, Dict, Iterable, Mapping, List, Text + from typing import Any, Dict, Iterable, List, Mapping, Text, Union package_type = Dict[Text, Dict[Text, Union[List[Text], Text]]] source_type = Dict[Text, Union[Text, bool]] @@ -29,83 +29,61 @@ if MYPY_RUNNING: lockfile_type = Dict[Text, Union[package_type, meta_type]] -# Let's start by patching plette to make sure we can validate data without being broken -try: - import cerberus -except ImportError: - cerberus = None - -VALIDATORS = plette.models.base.VALIDATORS - - -def patch_plette(): - # type: () -> None - - global VALIDATORS - - def validate(cls, data): - # type: (Any, Dict[Text, Any]) -> None - if not cerberus: # Skip validation if Cerberus is not available. - return - schema = cls.__SCHEMA__ - key = id(schema) - try: - v = VALIDATORS[key] - except KeyError: - v = VALIDATORS[key] = cerberus.Validator(schema, allow_unknown=True) - if v.validate(dict(data), normalize=False): - return - raise plette.models.base.ValidationError(data, v) - - names = ["plette.models.base", plette.models.base.__name__] - names = [name for name in names if name in sys.modules] - for name in names: - if name in sys.modules: - module = sys.modules[name] - else: - module = plette.models.base - original_fn = getattr(module, "validate") - for key in ["__qualname__", "__name__", "__module__"]: - original_val = getattr(original_fn, key, None) - if original_val is not None: - setattr(validate, key, original_val) - setattr(module, "validate", validate) - sys.modules[name] = module - - -patch_plette() - - is_pipfile = optional_instance_of(plette.pipfiles.Pipfile) is_path = optional_instance_of(Path) is_projectfile = optional_instance_of(ProjectFile) def reorder_source_keys(data): - # type: ignore - sources = data["source"] # type: sources_type - for i, entry in enumerate(sources): - table = tomlkit.table() # type: Mapping + # type: (tomlkit.toml_document.TOMLDocument) -> tomlkit.toml_document.TOMLDocument + sources = [] # type: sources_type + for source_key in ["source", "sources"]: + sources.extend(data.get(source_key, tomlkit.aot()).value) + new_source_aot = tomlkit.aot() + for entry in sources: + table = tomlkit.table() # type: tomlkit.items.Table source_entry = PipfileLoader.populate_source(entry.copy()) - table["name"] = source_entry["name"] - table["url"] = source_entry["url"] - table["verify_ssl"] = source_entry["verify_ssl"] - data["source"][i] = table + for key in ["name", "url", "verify_ssl"]: + table.update({key: source_entry[key]}) + new_source_aot.append(table) + data["source"] = new_source_aot + if data.get("sources", None): + del data["sources"] return data class PipfileLoader(plette.pipfiles.Pipfile): @classmethod def validate(cls, data): - # type: (Dict[Text, Any]) -> None + # type: (tomlkit.toml_document.TOMLDocument) -> None for key, klass in plette.pipfiles.PIPFILE_SECTIONS.items(): - if key not in data or key == "source": + if key not in data or key == "sources": continue try: klass.validate(data[key]) except Exception: pass + @classmethod + def ensure_package_sections(cls, data): + # type: (tomlkit.toml_document.TOMLDocument[Text, Any]) -> tomlkit.toml_document.TOMLDocument[Text, Any] + """Ensure that all pipfile package sections are present in the given + toml document. + + :param :class:`~tomlkit.toml_document.TOMLDocument` data: The toml document to + ensure package sections are present on + :return: The updated toml document, ensuring ``packages`` and ``dev-packages`` + sections are present + :rtype: :class:`~tomlkit.toml_document.TOMLDocument` + """ + package_keys = ( + k for k in plette.pipfiles.PIPFILE_SECTIONS.keys() if k.endswith("packages") + ) + for key in package_keys: + if key not in data: + data.update({key: tomlkit.table()}) + return data + @classmethod def populate_source(cls, source): """Derive missing values of source from the existing fields.""" @@ -115,7 +93,7 @@ class PipfileLoader(plette.pipfiles.Pipfile): if "verify_ssl" not in source: source["verify_ssl"] = "https://" in source["url"] if not isinstance(source["verify_ssl"], bool): - source["verify_ssl"] = source["verify_ssl"].lower() == "true" + source["verify_ssl"] = str(source["verify_ssl"]).lower() == "true" return source @classmethod @@ -125,11 +103,10 @@ class PipfileLoader(plette.pipfiles.Pipfile): if encoding is not None: content = content.decode(encoding) _data = tomlkit.loads(content) - _data["source"] = _data.get("source", []) + _data.get("sources", []) + should_reload = "source" not in _data _data = reorder_source_keys(_data) - if "source" not in _data: + if should_reload: if "sources" in _data: - _data["source"] = _data["sources"] content = tomlkit.dumps(_data) else: # HACK: There is no good way to prepend a section to an existing @@ -139,10 +116,19 @@ class PipfileLoader(plette.pipfiles.Pipfile): sep = "" if content.startswith("\n") else "\n" content = plette.pipfiles.DEFAULT_SOURCE_TOML + sep + content data = tomlkit.loads(content) + data = cls.ensure_package_sections(data) instance = cls(data) instance._data = dict(instance._data) return instance + def __contains__(self, key): + # type: (Text) -> bool + if key not in self._data: + package_keys = self._data.get("packages", {}).keys() + dev_package_keys = self._data.get("dev-packages", {}).keys() + return any(key in pkg_list for pkg_list in (package_keys, dev_package_keys)) + return True + def __getattribute__(self, key): # type: (Text) -> Any if key == "source": @@ -177,6 +163,19 @@ class Pipfile(object): # type: () -> Union[plette.pipfiles.Pipfile, PipfileLoader] return self.projectfile.model + @property + def root(self): + return self.path.parent + + @property + def extended_keys(self): + return [ + k + for k in itertools.product( + ("packages", "dev-packages"), ("", "vcs", "editable") + ) + ] + @property def pipfile(self): # type: () -> Union[PipfileLoader, plette.pipfiles.Pipfile] @@ -186,11 +185,11 @@ class Pipfile(object): # type: (bool, bool) -> Dict[Text, Dict[Text, Union[List[Text], Text]]] deps = {} # type: Dict[Text, Dict[Text, Union[List[Text], Text]]] if dev: - deps.update(self.pipfile._data["dev-packages"]) + deps.update(dict(self.pipfile._data.get("dev-packages", {}))) if only: return deps return tomlkit_value_to_python( - merge_items([deps, self.pipfile._data["packages"]]) + merge_items([deps, dict(self.pipfile._data.get("packages", {}))]) ) def get(self, k): @@ -256,7 +255,8 @@ class Pipfile(object): @classmethod def read_projectfile(cls, path): # type: (Text) -> ProjectFile - """Read the specified project file and provide an interface for writing/updating. + """Read the specified project file and provide an interface for + writing/updating. :param Text path: Path to the target file. :return: A project file with the model and location for interaction @@ -268,8 +268,7 @@ class Pipfile(object): @classmethod def load_projectfile(cls, path, create=False): # type: (Text, bool) -> ProjectFile - """ - Given a path, load or create the necessary pipfile. + """Given a path, load or create the necessary pipfile. :param Text path: Path to the project root or pipfile :param bool create: Whether to create the pipfile if not found, defaults to True @@ -294,8 +293,7 @@ class Pipfile(object): @classmethod def load(cls, path, create=False): # type: (Text, bool) -> Pipfile - """ - Given a path, load or create the necessary pipfile. + """Given a path, load or create the necessary pipfile. :param Text path: Path to the project root or pipfile :param bool create: Whether to create the pipfile if not found, defaults to True @@ -357,22 +355,27 @@ class Pipfile(object): # type: () -> None pyproject = self.path.parent.joinpath("pyproject.toml") if pyproject.exists(): - self._pyproject = tomlkit.load(pyproject) + self._pyproject = tomlkit.loads(pyproject.read_text()) build_system = self._pyproject.get("build-system", None) - if not os.path.exists(self.path_to("setup.py")): - if not build_system or not build_system.get("requires"): - build_system = { - "requires": ["setuptools>=40.8", "wheel"], - "build-backend": "setuptools.build_meta:__legacy__", - } - self._build_system = build_system + if build_system and not build_system.get("build_backend"): + build_system["build-backend"] = "setuptools.build_meta:__legacy__" + elif not build_system or not build_system.get("requires"): + build_system = { + "requires": ["setuptools>=40.8", "wheel"], + "build-backend": "setuptools.build_meta:__legacy__", + } + self.build_system = build_system @property def build_requires(self): # type: () -> List[Text] + if not self.build_system: + self._read_pyproject() return self.build_system.get("requires", []) @property def build_backend(self): # type: () -> Text + if not self.build_system: + self._read_pyproject() return self.build_system.get("build-backend", None) diff --git a/pipenv/vendor/requirementslib/models/project.py b/pipenv/vendor/requirementslib/models/project.py index 7c1b0e81..9211cd31 100644 --- a/pipenv/vendor/requirementslib/models/project.py +++ b/pipenv/vendor/requirementslib/models/project.py @@ -6,14 +6,12 @@ import collections import io import os -import attr +import pipenv.vendor.attr as attr import packaging.markers import packaging.utils -import plette +import pipenv.vendor.plette as plette import plette.models -import six -import tomlkit -from vistir.compat import FileNotFoundError +import pipenv.vendor.tomlkit as tomlkit SectionDifference = collections.namedtuple("SectionDifference", ["inthis", "inthat"]) FileDifference = collections.namedtuple("FileDifference", ["default", "develop"]) @@ -39,15 +37,14 @@ DEFAULT_NEWLINES = "\n" def preferred_newlines(f): - if isinstance(f.newlines, six.text_type): + if isinstance(f.newlines, str): return f.newlines return DEFAULT_NEWLINES @attr.s class ProjectFile(object): - """A file in the Pipfile project. - """ + """A file in the Pipfile project.""" location = attr.ib() line_ending = attr.ib() @@ -74,7 +71,7 @@ class ProjectFile(object): self.model.dump(f) def dumps(self): - strio = six.StringIO() + strio = io.StringIO() self.model.dump(strio) return strio.getvalue() @@ -141,7 +138,7 @@ class Project(object): ) def add_line_to_pipfile(self, line, develop): - from requirementslib import Requirement + from pipenv.vendor.requirementslib import Requirement requirement = Requirement.from_line(line) section = self._get_pipfile_section(develop=develop) diff --git a/pipenv/vendor/requirementslib/models/requirements.py b/pipenv/vendor/requirementslib/models/requirements.py index dc766619..435bd2a6 100644 --- a/pipenv/vendor/requirementslib/models/requirements.py +++ b/pipenv/vendor/requirementslib/models/requirements.py @@ -8,29 +8,26 @@ import os import sys from contextlib import contextmanager from distutils.sysconfig import get_python_lib -from functools import partial +from functools import lru_cache +from pathlib import Path +from urllib import parse as urllib_parse +from urllib.parse import unquote -import attr -import pip_shims -import six -import vistir -from cached_property import cached_property -from first import first -from packaging.markers import Marker -from packaging.requirements import Requirement as PackagingRequirement -from packaging.specifiers import ( +import pipenv.vendor.attr as attr +import pipenv.vendor.pip_shims as pip_shims +from pipenv.vendor.cached_property import cached_property +from pipenv.vendor.packaging.markers import Marker +from pipenv.vendor.packaging.requirements import Requirement as PackagingRequirement +from pipenv.vendor.packaging.specifiers import ( InvalidSpecifier, LegacySpecifier, Specifier, SpecifierSet, ) -from packaging.utils import canonicalize_name -from six.moves.urllib import parse as urllib_parse -from six.moves.urllib.parse import unquote -from vistir.compat import FileNotFoundError, Mapping, Path, lru_cache -from vistir.contextmanagers import temp_path -from vistir.misc import dedup -from vistir.path import ( +from pipenv.vendor.packaging.utils import canonicalize_name +from pipenv.vendor.vistir.contextmanagers import temp_path +from pipenv.vendor.vistir.misc import dedup +from pipenv.vendor.vistir.path import ( create_tracked_tempdir, get_converted_relative_path, is_file_url, @@ -39,13 +36,18 @@ from vistir.path import ( normalize_path, ) -from .markers import ( - cleanup_pyspecs, - contains_pyversion, - format_pyversion, - get_contained_pyversions, - normalize_marker_str, +from ..environment import MYPY_RUNNING +from ..exceptions import RequirementError +from ..utils import ( + VCS_LIST, + add_ssh_scheme_to_git_uri, + get_setup_paths, + is_installable_dir, + is_installable_file, + is_vcs, + strip_ssh_from_git_uri, ) +from .markers import normalize_marker_str from .setup_info import ( SetupInfo, _prepare_wheel_building_kwargs, @@ -57,10 +59,10 @@ from .url import URI from .utils import ( DIRECT_URL_RE, HASH_STRING, - URL_RE, build_vcs_uri, convert_direct_url_to_url, create_link, + expand_env_variables, extras_to_string, filter_none, format_requirement, @@ -80,48 +82,39 @@ from .utils import ( validate_specifiers, validate_vcs, ) -from ..environment import MYPY_RUNNING -from ..exceptions import RequirementError -from ..utils import ( - VCS_LIST, - add_ssh_scheme_to_git_uri, - get_setup_paths, - is_installable_dir, - is_installable_file, - is_vcs, - strip_ssh_from_git_uri, -) if MYPY_RUNNING: from typing import ( - Optional, - TypeVar, - List, - Dict, - Union, Any, - Tuple, + AnyStr, + Dict, + FrozenSet, + Generator, + List, + Optional, Sequence, Set, - AnyStr, Text, - Generator, - FrozenSet, + Tuple, + TypeVar, + Union, ) - from pip_shims.shims import ( - Link, - InstallRequirement, - PackageFinder, + + from pipenv.vendor.pip_shims.shims import ( InstallationCandidate, + InstallRequirement, + Link, + PackageFinder, ) RequirementType = TypeVar( "RequirementType", covariant=True, bound=PackagingRequirement ) F = TypeVar("F", "FileRequirement", "VCSRequirement", covariant=True) - from six.moves.urllib.parse import SplitResult - from .vcs import VCSRepository + from urllib.parse import SplitResult + from .dependencies import AbstractDependency + from .vcs import VCSRepository NON_STRING_ITERABLE = Union[List, Set, Tuple] STRING_TYPE = Union[str, bytes, Text] @@ -137,9 +130,6 @@ if MYPY_RUNNING: SPECIFIERS_BY_LENGTH = sorted(list(Specifier._operators.keys()), key=len, reverse=True) -run = partial(vistir.misc.run, combine_stderr=False, return_object=True, nospin=True) - - class Line(object): def __init__(self, line, extras=None): # type: (AnyStr, Optional[Union[List[S], Set[S], Tuple[S, ...]]]) -> None @@ -164,8 +154,7 @@ class Line(object): self.parsed_marker = None # type: Optional[Marker] self.preferred_scheme = None # type: Optional[STRING_TYPE] self._requirement = None # type: Optional[PackagingRequirement] - self.is_direct_url = False # type: bool - self._parsed_url = None # type: Optional[urllib_parse.ParseResult] + self._parsed_url = None # type: Optional[URI] self._setup_cfg = None # type: Optional[STRING_TYPE] self._setup_py = None # type: Optional[STRING_TYPE] self._pyproject_toml = None # type: Optional[STRING_TYPE] @@ -301,7 +290,7 @@ class Line(object): def line_for_ireq(self): # type: () -> STRING_TYPE line = "" # type: STRING_TYPE - if self.is_file or self.is_url and not self.is_vcs: + if self.is_file or self.is_remote_url and not self.is_vcs: scheme = self.preferred_scheme if self.preferred_scheme is not None else "uri" local_line = next( iter( @@ -340,7 +329,7 @@ class Line(object): if self.editable: if not line: if self.is_path or self.is_file: - if not self.path: + if not self.path and self.url is not None: line = pip_shims.shims.url_to_path(self.url) else: line = self.path @@ -437,7 +426,7 @@ class Line(object): # note: we need versions for direct dependencies at the very least if ( self.is_file - or self.is_url + or self.is_remote_url or self.is_path or (self.is_vcs and not self.editable) ): @@ -457,7 +446,7 @@ class Line(object): def specifiers(self, specifiers): # type: (Union[Text, str, SpecifierSet]) -> None if not isinstance(specifiers, SpecifierSet): - if isinstance(specifiers, six.string_types): + if isinstance(specifiers, str): specifiers = SpecifierSet(specifiers) else: raise TypeError("Must pass a string or a SpecifierSet") @@ -485,7 +474,7 @@ class Line(object): self.parse_requirement() if self._requirement is None and self._name is not None: self._requirement = init_requirement(canonicalize_name(self.name)) - if self.is_file or self.is_url and self._requirement is not None: + if self.is_file or self.is_remote_url and self._requirement is not None: self._requirement.url = self.url if ( self._requirement @@ -548,11 +537,11 @@ class Line(object): return self._pyproject_backend def parse_hashes(self): - # type: () -> None - """ - Parse hashes from *self.line* and set them on the current object. - :returns: Nothing - :rtype: None + # type: () -> "Line" + """Parse hashes from *self.line* and set them on the current object. + + :returns: Self + :rtype: `:class:~Line` """ line, hashes = self.split_hashes(self.line) self.hashes = hashes @@ -560,24 +549,29 @@ class Line(object): return self def parse_extras(self): - # type: () -> None + # type: () -> "Line" """ Parse extras from *self.line* and set them on the current object - :returns: Nothing - :rtype: None + :returns: self + :rtype: :class:`~Line` """ extras = None - if "@" in self.line or self.is_vcs or self.is_url: - line = "{0}".format(self.line) - uri = URI.parse(line) - name = uri.name - if name: - self._name = name - if uri.host and uri.path and uri.scheme: - self.line = uri.to_string( - escape_password=False, direct=False, strip_ssh=uri.is_implicit_ssh - ) - else: + line = "{0}".format(self.line) + if any([self.is_vcs, self.is_url, "@" in line]): + try: + if self.parsed_url.name: + self._name = self.parsed_url.name + if ( + self.parsed_url.host + and self.parsed_url.path + and self.parsed_url.scheme + ): + self.line = self.parsed_url.to_string( + escape_password=False, + direct=False, + strip_ssh=self.parsed_url.is_implicit_ssh, + ) + except ValueError: self.line, extras = pip_shims.shims._strip_extras(self.line) else: self.line, extras = pip_shims.shims._strip_extras(self.line) @@ -595,37 +589,10 @@ class Line(object): def get_url(self): # type: () -> STRING_TYPE - """Sets ``self.name`` if given a **PEP-508** style URL""" - line = self.line - try: - parsed = URI.parse(line) - line = parsed.to_string(escape_password=False, direct=False, strip_ref=True) - except ValueError: - pass - else: - self._parsed_url = parsed - return line - if self.vcs is not None and self.line.startswith("{0}+".format(self.vcs)): - _, _, _parseable = self.line.partition("+") - parsed = urllib_parse.urlparse(add_ssh_scheme_to_git_uri(_parseable)) - line, _ = split_ref_from_uri(line) - else: - parsed = urllib_parse.urlparse(add_ssh_scheme_to_git_uri(line)) - if "@" in self.line and parsed.scheme == "": - name, _, url = self.line.partition("@") - if self._name is None: - url = url.strip() - self._name = name.strip() - if is_valid_url(url): - self.is_direct_url = True - line = url.strip() - parsed = urllib_parse.urlparse(line) - url_path = parsed.path - if "@" in url_path: - url_path, _, _ = url_path.rpartition("@") - parsed = parsed._replace(path=url_path) - self._parsed_url = parsed - return line + """Sets ``self.name`` if given a **PEP-508** style URL.""" + return self.parsed_url.to_string( + escape_password=False, direct=False, strip_ref=True + ) @property def name(self): @@ -655,20 +622,16 @@ class Line(object): @property def url(self): # type: () -> Optional[STRING_TYPE] - if self.uri is not None: - url = add_ssh_scheme_to_git_uri(self.uri) - else: - url = getattr(self.link, "url_without_fragment", None) - if url is not None: - url = add_ssh_scheme_to_git_uri(unquote(url)) - if url is not None and self._parsed_url is None: - if self.vcs is not None: - _, _, _parseable = url.partition("+") - self._parsed_url = urllib_parse.urlparse(_parseable) - if self.is_vcs: - # strip the ref from the url - url, _ = split_ref_from_uri(url) - return url + try: + return self.parsed_url.to_string( + escape_password=False, + strip_ref=True, + strip_name=True, + strip_subdir=True, + strip_ssh=False, + ) + except ValueError: + return None @property def link(self): @@ -694,30 +657,46 @@ class Line(object): @property def is_artifact(self): # type: () -> bool + if self.link is None: return False - return self.link.is_artifact + return not self.link.is_vcs @property def is_vcs(self): # type: () -> bool # Installable local files and installable non-vcs urls are handled # as files, generally speaking - if is_vcs(self.line) or is_vcs(self.get_url()): - return True + try: + if is_vcs(self.line) or is_vcs(self.get_url()): + return True + except ValueError: + return False return False @property def is_url(self): # type: () -> bool - url = self.get_url() + try: + url = self.get_url() + except ValueError: + return False if is_valid_url(url) or is_file_url(url): return True return False + @property + def is_remote_url(self): + # type: () -> bool + return self.is_url and self.parsed_url.host is not None + @property def is_path(self): # type: () -> bool + try: + line_url = self.get_url() + except ValueError: + line_url = None if ( self.path and ( @@ -729,7 +708,7 @@ class Line(object): ): return True elif (os.path.exists(self.line) and is_installable_file(self.line)) or ( - os.path.exists(self.get_url()) and is_installable_file(self.get_url()) + line_url and os.path.exists(line_url) and is_installable_file(line_url) ): return True return False @@ -737,22 +716,32 @@ class Line(object): @property def is_file_url(self): # type: () -> bool - url = self.get_url() - parsed_url_scheme = self._parsed_url.scheme if self._parsed_url else "" - if url and is_file_url(self.get_url()) or parsed_url_scheme == "file": + try: + url = self.get_url() + except ValueError: + return False + try: + parsed_url_scheme = self.parsed_url.scheme + except ValueError: + return False + if url and is_file_url(url) or parsed_url_scheme == "file": return True return False @property def is_file(self): # type: () -> bool + try: + url = self.get_url() + except ValueError: + return False if ( self.is_path - or (is_file_url(self.get_url()) and is_installable_file(self.get_url())) + or (is_file_url(url) and is_installable_file(url)) or ( self._parsed_url - and self._parsed_url.scheme == "file" - and is_installable_file(urllib_parse.urlunparse(self._parsed_url)) + and self._parsed_url.is_file_url + and is_installable_file(self._parsed_url.url_without_fragment_or_ref) ) ): return True @@ -761,7 +750,13 @@ class Line(object): @property def is_named(self): # type: () -> bool - return not (self.is_file_url or self.is_url or self.is_file or self.is_vcs) + return not ( + self.is_file_url + or self.is_url + or self.is_file + or self.is_vcs + or self.is_direct_url + ) @property def ref(self): @@ -780,7 +775,11 @@ class Line(object): @property def is_installable(self): # type: () -> bool - possible_paths = (self.line, self.get_url(), self.path, self.base_path) + try: + url = self.get_url() + except ValueError: + url = None + possible_paths = (self.line, url, self.path, self.base_path) return any(is_installable_file(p) for p in possible_paths if p is not None) @property @@ -791,27 +790,25 @@ class Line(object): def get_setup_info(self): # type: () -> SetupInfo - setup_info = SetupInfo.from_ireq(self.ireq) - if not setup_info.name: - setup_info.get_info() + setup_info = None + with pip_shims.shims.global_tempdir_manager(): + setup_info = SetupInfo.from_ireq(self.ireq, subdir=self.subdirectory) + if not setup_info.name: + setup_info.get_info() return setup_info @property def setup_info(self): # type: () -> Optional[SetupInfo] - if self._setup_info is None and not self.is_named and not self.is_wheel: - if self._setup_info: - if not self._setup_info.name: - self._setup_info.get_info() - else: - # make two attempts at this before failing to allow for stale data + if not self._setup_info and not self.is_named and not self.is_wheel: + # make two attempts at this before failing to allow for stale data + try: + self.setup_info = self.get_setup_info() + except FileNotFoundError: try: self.setup_info = self.get_setup_info() except FileNotFoundError: - try: - self.setup_info = self.get_setup_info() - except FileNotFoundError: - raise + raise return self._setup_info @setup_info.setter @@ -851,6 +848,21 @@ class Line(object): self._vcsrepo = self._get_vcsrepo() return self._vcsrepo + @property + def parsed_url(self): + # type: () -> URI + if self._parsed_url is None: + self._parsed_url = URI.parse(self.line) + return self._parsed_url + + @property + def is_direct_url(self): + # type: () -> bool + try: + return self.is_url and self.parsed_url.is_direct_url + except ValueError: + return self.is_url and bool(DIRECT_URL_RE.match(self.line)) + @cached_property def metadata(self): # type: () -> Dict[Any, Any] @@ -861,17 +873,21 @@ class Line(object): @cached_property def parsed_setup_cfg(self): # type: () -> Dict[Any, Any] - if self.is_local and self.path and is_installable_dir(self.path): - if self.setup_cfg: - return parse_setup_cfg(self.setup_cfg) - return {} + if not ( + self.is_local + and self.path + and is_installable_dir(self.path) + and self.setup_cfg + ): + return {} + return self.setup_info.parse_setup_cfg() @cached_property def parsed_setup_py(self): # type: () -> Dict[Any, Any] if self.is_local and self.path and is_installable_dir(self.path): if self.setup_py: - return ast_parse_setup_py(self.setup_py) + return ast_parse_setup_py(self.setup_py, raising=False) return {} @vcsrepo.setter @@ -881,10 +897,8 @@ class Line(object): ireq = self.ireq wheel_kwargs = self.wheel_kwargs.copy() wheel_kwargs["src_dir"] = repo.checkout_directory - ireq.source_dir = wheel_kwargs["src_dir"] - ireq.build_location(wheel_kwargs["build_dir"]) - ireq._temp_build_dir.path = wheel_kwargs["build_dir"] - with temp_path(): + with pip_shims.shims.global_tempdir_manager(), temp_path(): + ireq.ensure_has_source_dir(wheel_kwargs["src_dir"]) sys.path = [repo.checkout_directory, "", ".", get_python_lib(plat_specific=0)] setupinfo = SetupInfo.create( repo.checkout_directory, @@ -904,8 +918,8 @@ class Line(object): ireq = pip_shims.shims.install_req_from_line(line) if self.is_named: ireq = pip_shims.shims.install_req_from_line(self.line) - if self.is_file or self.is_url: - ireq.link = self.link + if self.is_file or self.is_remote_url: + ireq.link = pip_shims.shims.Link(expand_env_variables(self.link.url)) if self.extras and not ireq.extras: ireq.extras = set(self.extras) if self.parsed_marker is not None and not ireq.markers: @@ -926,7 +940,7 @@ class Line(object): # type: () -> Optional[STRING_TYPE] if not self.is_wheel: pass - from pip_shims.shims import Wheel + from pipenv.vendor.pip_shims.shims import Wheel _wheel = Wheel(self.link.filename) name = _wheel.name @@ -990,7 +1004,7 @@ class Line(object): parsed_setup_py = self.parsed_setup_py if parsed_setup_py: name = parsed_setup_py.get("name", "") - if name: + if name and isinstance(name, str): return name return None @@ -998,10 +1012,11 @@ class Line(object): # type: () -> "Line" if self._name is None: name = None - if self.link is not None: + if self.link is not None and self.line_is_installable: name = self._parse_name_from_link() if name is None and ( - (self.is_url or self.is_artifact or self.is_vcs) and self._parsed_url + (self.is_remote_url or self.is_artifact or self.is_vcs) + and self._parsed_url ): if self._parsed_url.fragment: _, _, name = self._parsed_url.fragment.partition("egg=") @@ -1010,7 +1025,7 @@ class Line(object): name, _, _ = name.partition("&") if name is None and self.is_named: name = self._parse_name_from_line() - elif name is None and self.is_file or self.is_url or self.is_path: + elif name is None and self.is_file or self.is_remote_url or self.is_path: if self.is_local: name = self._parse_name_from_path() if name is not None: @@ -1049,17 +1064,17 @@ class Line(object): # else: # req.link = self.link if self.ref and self._requirement is not None: + self._requirement.revision = self.ref if self._vcsrepo is not None: - self._requirement.revision = self._vcsrepo.get_commit_hash() - else: - self._requirement.revision = self.ref + with pip_shims.shims.global_tempdir_manager(): + self._requirement.revision = self._vcsrepo.get_commit_hash() return self._requirement def parse_requirement(self): # type: () -> "Line" if self._name is None: self.parse_name() - if not self._name and not self.is_vcs and not self.is_named: + if not any([self._name, self.is_vcs, self.is_named]): if self.setup_info and self.setup_info.name: self._name = self.setup_info.name name, extras, url = self.requirement_info @@ -1104,53 +1119,64 @@ class Line(object): def parse_link(self): # type: () -> "Line" parsed_url = None # type: Optional[URI] - if not is_valid_url(self.line) and ( - self.line.startswith("./") - or (os.path.exists(self.line) or os.path.isabs(self.line)) + if ( + not is_valid_url(self.line) + and is_installable_file(os.path.abspath(self.line)) + and ( + self.line.startswith("./") + or (os.path.exists(self.line) or os.path.isabs(self.line)) + ) ): url = pip_shims.shims.path_to_url(os.path.abspath(self.line)) - parsed_url = URI.parse(url) - elif is_valid_url(self.line) or is_vcs(self.line) or is_file_url(self.line): - parsed_url = URI.parse(self.line) - if parsed_url is not None: - line = parsed_url.to_string( - escape_password=False, direct=False, strip_ref=True, strip_ssh=False - ) - if parsed_url.is_vcs: - self.vcs, _ = parsed_url.scheme.split("+") - if parsed_url.is_file_url: - self.is_local = True - parsed_link = parsed_url.as_link - self._ref = parsed_url.ref - self.uri = parsed_url.bare_url - if parsed_url.name: - self._name = parsed_url.name - if parsed_url.extras: - self.extras = tuple(sorted(set(parsed_url.extras))) + self._parsed_url = parsed_url = URI.parse(url) + elif any( + [ + is_valid_url(self.line), + is_vcs(self.line), + is_file_url(self.line), + self.is_direct_url, + ] + ): + parsed_url = self.parsed_url + if parsed_url is None or ( + parsed_url.is_file_url and not parsed_url.is_installable + ): + return None + if parsed_url.is_vcs: + self.vcs, _ = parsed_url.scheme.split("+") + if parsed_url.is_file_url: + self.is_local = True + parsed_link = parsed_url.as_link + self._ref = parsed_url.ref + self.uri = parsed_url.bare_url + if parsed_url.name: + self._name = parsed_url.name + if parsed_url.extras: + self.extras = tuple(sorted(set(parsed_url.extras))) + self._link = parsed_link + vcs, prefer, relpath, path, uri, link = FileRequirement.get_link_from_line( + self.line + ) + ref = None + if link is not None and "@" in unquote(link.path) and uri is not None: + uri, _, ref = unquote(uri).rpartition("@") + if relpath is not None and "@" in relpath: + relpath, _, ref = relpath.rpartition("@") + if path is not None and "@" in path: + path, _ = split_ref_from_uri(path) + link_url = link.url_without_fragment + if "@" in link_url: + link_url, _ = split_ref_from_uri(link_url) + self.preferred_scheme = prefer + self.relpath = relpath + self.path = path + # self.uri = uri + if prefer in ("path", "relpath") or uri.startswith("file"): + self.is_local = True + if parsed_url.is_vcs or parsed_url.is_direct_url and parsed_link: self._link = parsed_link - vcs, prefer, relpath, path, uri, link = FileRequirement.get_link_from_line( - self.line - ) - ref = None - if link is not None and "@" in unquote(link.path) and uri is not None: - uri, _, ref = unquote(uri).rpartition("@") - if relpath is not None and "@" in relpath: - relpath, _, ref = relpath.rpartition("@") - if path is not None and "@" in path: - path, _ = split_ref_from_uri(path) - link_url = link.url_without_fragment - if "@" in link_url: - link_url, _ = split_ref_from_uri(link_url) - self.preferred_scheme = prefer - self.relpath = relpath - self.path = path - # self.uri = uri - if prefer in ("path", "relpath") or uri.startswith("file"): - self.is_local = True - if parsed_url.is_vcs or parsed_url.is_direct_url and parsed_link: - self._link = parsed_link - else: - self._link = link + else: + self._link = link return self def parse_markers(self): @@ -1203,26 +1229,50 @@ class Line(object): @property def line_is_installable(self): # type: () -> bool - """ - This is a safeguard against decoy requirements when a user installs a package - whose name coincides with the name of a folder in the cwd, e.g. install *alembic* - when there is a folder called *alembic* in the working directory. + """This is a safeguard against decoy requirements when a user installs + a package whose name coincides with the name of a folder in the cwd, + e.g. install *alembic* when there is a folder called *alembic* in the + working directory. - In this case we first need to check that the given requirement is a valid - URL, VCS requirement, or installable filesystem path before deciding to treat it - as a file requirement over a named requirement. + In this case we first need to check that the given requirement + is a valid URL, VCS requirement, or installable filesystem path + before deciding to treat it as a file requirement over a named + requirement. """ line = self.line + direct_url_match = DIRECT_URL_RE.match(line) + if direct_url_match: + match_dict = direct_url_match.groupdict() + auth = "" + username = match_dict.get("username", None) + password = match_dict.get("password", None) + port = match_dict.get("port", None) + path = match_dict.get("path", None) + ref = match_dict.get("ref", None) + if username is not None: + auth = "{0}".format(username) + if password: + auth = "{0}:{1}".format(auth, password) if auth else password + line = match_dict.get("host", "") + if auth: + line = "{auth}@{line}".format(auth=auth, line=line) + if port: + line = "{line}:{port}".format(line=line, port=port) + if path: + line = "{line}{pathsep}{path}".format( + line=line, pathsep=match_dict["pathsep"], path=path + ) + if ref: + line = "{line}@{ref}".format(line=line, ref=ref) + line = "{scheme}{line}".format(scheme=match_dict["scheme"], line=line) if is_file_url(line): link = create_link(line) line = link.url_without_fragment line, _ = split_ref_from_uri(line) if ( is_vcs(line) - or ( - is_valid_url(line) - and (not is_file_url(line) or is_installable_file(line)) - ) + or (not is_file_url(line) and is_valid_url(line)) + or (is_file_url(line) and is_installable_file(line)) or is_installable_file(line) ): return True @@ -1250,6 +1300,8 @@ class Line(object): raise RequirementError( "Supplied requirement is not installable: {0!r}".format(self.line) ) + elif self.is_named and self._name is None: + self.parse_name() self.parse_link() # self.parse_requirement() # self.parse_ireq() @@ -1322,7 +1374,7 @@ class NamedRequirement(object): creation_args["version"] = version # type: ignore req = init_requirement("{0}{1}".format(name, version)) if req and extras and req.extras and isinstance(req.extras, tuple): - if isinstance(extras, six.string_types): + if isinstance(extras, str): req.extras = (extras) + tuple(["{0}".format(xtra) for xtra in req.extras]) elif isinstance(extras, (tuple, list)): req.extras += tuple(extras) @@ -1354,42 +1406,51 @@ LinkInfo = collections.namedtuple( ) -@attr.s(slots=True, cmp=True, hash=True) +@attr.s(slots=True, eq=True, order=True, hash=True) class FileRequirement(object): """File requirements for tar.gz installable files or wheels or setup.py containing directories.""" #: Path to the relevant `setup.py` location - setup_path = attr.ib(default=None, cmp=True) # type: Optional[STRING_TYPE] + setup_path = attr.ib(default=None, eq=True, order=True) # type: Optional[STRING_TYPE] #: path to hit - without any of the VCS prefixes (like git+ / http+ / etc) - path = attr.ib(default=None, cmp=True) # type: Optional[STRING_TYPE] + path = attr.ib(default=None, eq=True, order=True) # type: Optional[STRING_TYPE] #: Whether the package is editable - editable = attr.ib(default=False, cmp=True) # type: bool + editable = attr.ib(default=False, eq=True, order=True) # type: bool #: Extras if applicable extras = attr.ib( - default=attr.Factory(tuple), cmp=True + default=attr.Factory(tuple), eq=True, order=True ) # type: Tuple[STRING_TYPE, ...] - _uri_scheme = attr.ib(default=None, cmp=True) # type: Optional[STRING_TYPE] + _uri_scheme = attr.ib( + default=None, eq=True, order=True + ) # type: Optional[STRING_TYPE] #: URI of the package - uri = attr.ib(cmp=True) # type: Optional[STRING_TYPE] + uri = attr.ib(eq=True, order=True) # type: Optional[STRING_TYPE] #: Link object representing the package to clone - link = attr.ib(cmp=True) # type: Optional[Link] + link = attr.ib(eq=True, order=True) # type: Optional[Link] #: PyProject Requirements pyproject_requires = attr.ib( - factory=tuple, cmp=True + factory=tuple, eq=True, order=True ) # type: Optional[Tuple[STRING_TYPE, ...]] #: PyProject Build System - pyproject_backend = attr.ib(default=None, cmp=True) # type: Optional[STRING_TYPE] + pyproject_backend = attr.ib( + default=None, eq=True, order=True + ) # type: Optional[STRING_TYPE] #: PyProject Path - pyproject_path = attr.ib(default=None, cmp=True) # type: Optional[STRING_TYPE] + pyproject_path = attr.ib( + default=None, eq=True, order=True + ) # type: Optional[STRING_TYPE] + subdirectory = attr.ib(default=None) # type: Optional[STRING_TYPE] #: Setup metadata e.g. dependencies - _setup_info = attr.ib(default=None, cmp=True) # type: Optional[SetupInfo] - _has_hashed_name = attr.ib(default=False, cmp=True) # type: bool - _parsed_line = attr.ib(default=None, cmp=False, hash=True) # type: Optional[Line] + _setup_info = attr.ib(default=None, eq=True, order=True) # type: Optional[SetupInfo] + _has_hashed_name = attr.ib(default=False, eq=True, order=True) # type: bool + _parsed_line = attr.ib( + default=None, eq=False, order=False, hash=True + ) # type: Optional[Line] #: Package name - name = attr.ib(cmp=True) # type: Optional[STRING_TYPE] + name = attr.ib(eq=True, order=True) # type: Optional[STRING_TYPE] #: A :class:`~pkg_resources.Requirement` instance - req = attr.ib(cmp=True) # type: Optional[PackagingRequirement] + req = attr.ib(eq=True, order=True) # type: Optional[PackagingRequirement] @classmethod def get_link_from_line(cls, line): @@ -1408,7 +1469,7 @@ class FileRequirement(object): forward slashes. Can be None if the line is a remote URI. - `uri` is the absolute URI to the package. Can be None if the line is not a URI. - - `link` is an instance of :class:`pip._internal.index.Link`, + - `link` is an instance of :class:`pipenv.patched.notpip._internal.index.Link`, representing a URI parse result based on the value of `uri`. This function is provided to deal with edge cases concerning URIs @@ -1548,24 +1609,26 @@ class FileRequirement(object): @property def setup_info(self): # type: () -> Optional[SetupInfo] - from .setup_info import SetupInfo - if self._setup_info is None and self.parsed_line: if self.parsed_line and self._parsed_line and self.parsed_line.setup_info: if ( self._parsed_line._setup_info and not self._parsed_line._setup_info.name ): - self._parsed_line._setup_info.get_info() + with pip_shims.shims.global_tempdir_manager(): + self._parsed_line._setup_info.get_info() self._setup_info = self.parsed_line._setup_info elif self.parsed_line and ( self.parsed_line.ireq and not self.parsed_line.is_wheel ): - self._setup_info = SetupInfo.from_ireq(self.parsed_line.ireq) + with pip_shims.shims.global_tempdir_manager(): + self._setup_info = SetupInfo.from_ireq( + self.parsed_line.ireq, subdir=self.subdirectory + ) else: if self.link and not self.link.is_wheel: self._setup_info = Line(self.line_part).setup_info - if self._setup_info: + with pip_shims.shims.global_tempdir_manager(): self._setup_info.get_info() return self._setup_info @@ -1585,7 +1648,7 @@ class FileRequirement(object): elif ( getattr(self, "req", None) and self.req is not None - and getattr(self.req, "url") + and getattr(self.req, "url", None) ): return self.req.url elif self.link is not None: @@ -1650,7 +1713,7 @@ class FileRequirement(object): elif ( getattr(self, "req", None) and self.req is not None - and (getattr(self.req, "url") and self.req.url is not None) + and (getattr(self.req, "url", None) and self.req.url is not None) ): uri = self.req.url if uri and is_file_url(uri): @@ -1686,172 +1749,6 @@ class FileRequirement(object): return path.as_posix() return None - @classmethod - def create( - cls, - path=None, # type: Optional[STRING_TYPE] - uri=None, # type: STRING_TYPE - editable=False, # type: bool - extras=None, # type: Optional[Tuple[STRING_TYPE, ...]] - link=None, # type: Link - vcs_type=None, # type: Optional[Any] - name=None, # type: Optional[STRING_TYPE] - req=None, # type: Optional[Any] - line=None, # type: Optional[STRING_TYPE] - uri_scheme=None, # type: STRING_TYPE - setup_path=None, # type: Optional[Any] - relpath=None, # type: Optional[Any] - parsed_line=None, # type: Optional[Line] - ): - # type: (...) -> F - if parsed_line is None and line is not None: - parsed_line = Line(line) - if relpath and not path: - path = relpath - if not path and uri and link is not None and link.scheme == "file": - path = os.path.abspath( - pip_shims.shims.url_to_path(unquote(uri)) - ) # type: ignore - try: - path = get_converted_relative_path(path) - except ValueError: # Vistir raises a ValueError if it can't make a relpath - path = path - if line is not None and not (uri_scheme and uri and link): - vcs_type, uri_scheme, relpath, path, uri, link = cls.get_link_from_line(line) - if not uri_scheme: - uri_scheme = "path" if path else "file" - if path and not uri: - uri = unquote( - pip_shims.shims.path_to_url(os.path.abspath(path)) - ) # type: ignore - if not link and uri: - link = cls.get_link_from_line(uri).link - if not uri and link: - uri = unquote(link.url_without_fragment) - if not extras: - extras = () - pyproject_path = None - pyproject_requires = None - pyproject_backend = None - pyproject_tuple = None # type: Optional[Tuple[STRING_TYPE]] - if path is not None: - pyproject_requires_and_backend = get_pyproject(path) - if pyproject_requires_and_backend is not None: - pyproject_requires, pyproject_backend = pyproject_requires_and_backend - if path: - setup_paths = get_setup_paths(path) - if isinstance(setup_paths, Mapping): - if "pyproject_toml" in setup_paths and setup_paths["pyproject_toml"]: - pyproject_path = Path(setup_paths["pyproject_toml"]) - if "setup_py" in setup_paths and setup_paths["setup_py"]: - setup_path = Path(setup_paths["setup_py"]).as_posix() - if setup_path and isinstance(setup_path, Path): - setup_path = setup_path.as_posix() - creation_kwargs = { - "editable": editable, - "extras": extras, - "pyproject_path": pyproject_path, - "setup_path": setup_path, - "uri_scheme": uri_scheme, - "link": link, - "uri": uri, - "pyproject_requires": pyproject_tuple, - "pyproject_backend": pyproject_backend, - "path": path or relpath, - "parsed_line": parsed_line, - } - if vcs_type: - creation_kwargs["vcs"] = vcs_type - if name: - creation_kwargs["name"] = name - _line = None # type: Optional[STRING_TYPE] - ireq = None # type: Optional[InstallRequirement] - setup_info = None # type: Optional[SetupInfo] - if parsed_line: - if parsed_line.name: - name = parsed_line.name - if parsed_line.setup_info: - name = parsed_line.setup_info.as_dict().get("name", name) - if not name or not parsed_line: - if link is not None and link.url_without_fragment is not None: - _line = unquote(link.url_without_fragment) - if name: - _line = "{0}#egg={1}".format(_line, name) - if _line and extras and extras_to_string(extras) not in _line: - _line = "{0}[{1}]".format( - _line, ",".join(sorted(set(extras))) - ) # type: ignore - elif isinstance(uri, six.string_types): - _line = unquote(uri) - elif line: - _line = unquote(line) - if editable: - if ( - _line - and extras - and extras_to_string(extras) not in _line - and ( - (link and link.scheme == "file") - or (uri and uri.startswith("file")) - or (not uri and not link) - ) - ): - _line = "{0}[{1}]".format( - _line, ",".join(sorted(set(extras))) - ) # type: ignore - if ireq is None: - ireq = pip_shims.shims.install_req_from_editable( - _line - ) # type: ignore - else: - _line = path if (uri_scheme and uri_scheme == "path") else _line - if _line and extras and extras_to_string(extras) not in _line: - _line = "{0}[{1}]".format( - _line, ",".join(sorted(set(extras))) - ) # type: ignore - if ireq is None: - ireq = pip_shims.shims.install_req_from_line(_line) # type: ignore - if editable: - _line = "-e {0}".format(editable) - if _line: - parsed_line = Line(_line) - if ireq is None and parsed_line and parsed_line.ireq: - ireq = parsed_line.ireq - if extras and ireq is not None and not ireq.extras: - ireq.extras = set(extras) - if setup_info is None: - setup_info = SetupInfo.from_ireq(ireq) - setupinfo_dict = setup_info.as_dict() - setup_name = setupinfo_dict.get("name", None) - build_requires = () # type: Tuple[STRING_TYPE, ...] - build_backend = "" - if setup_name is not None: - name = setup_name - build_requires = setupinfo_dict.get("build_requires", build_requires) - build_backend = setupinfo_dict.get("build_backend", build_backend) - if "pyproject_requires" not in creation_kwargs and build_requires: - creation_kwargs["pyproject_requires"] = tuple(build_requires) - if "pyproject_backend" not in creation_kwargs and build_backend: - creation_kwargs["pyproject_backend"] = build_backend - if setup_info is None and parsed_line and parsed_line.setup_info: - setup_info = parsed_line.setup_info - creation_kwargs["setup_info"] = setup_info - if path or relpath: - creation_kwargs["path"] = relpath if relpath else path - if req is not None: - creation_kwargs["req"] = req - creation_req = creation_kwargs.get("req") - if creation_kwargs.get("req") is not None: - creation_req_line = getattr(creation_req, "line", None) - if creation_req_line is None and line is not None: - creation_kwargs["req"].line = line # type: ignore - if parsed_line and parsed_line.name: - if name and len(parsed_line.name) != 7 and len(name) == 7: - name = parsed_line.name - if name: - creation_kwargs["name"] = name - return cls(**creation_kwargs) # type: ignore - @classmethod def from_line(cls, line, editable=None, extras=None, parsed_line=None): # type: (AnyStr, Optional[bool], Optional[Tuple[AnyStr, ...]], Optional[Line]) -> F @@ -1868,7 +1765,7 @@ class FileRequirement(object): uri = pipfile.get("uri") fil = pipfile.get("file") path = pipfile.get("path") - if path and isinstance(path, six.string_types): + if path and isinstance(path, str): if isinstance(path, Path) and not path.is_absolute(): path = get_converted_relative_path(path.as_posix()) elif not os.path.isabs(path): @@ -1893,7 +1790,7 @@ class FileRequirement(object): if not uri: uri = pip_shims.shims.path_to_url(path) link_info = None # type: Optional[LinkInfo] - if uri and isinstance(uri, six.string_types): + if uri and isinstance(uri, str): link_info = cls.get_link_from_line(uri) else: raise ValueError( @@ -1903,7 +1800,7 @@ class FileRequirement(object): if link_info: link = link_info.link if link.url_without_fragment: - uri = unquote(link.url_without_fragment) + uri = link.url_without_fragment extras = () # type: Optional[Tuple[STRING_TYPE, ...]] if "extras" in pipfile: extras = tuple(pipfile["extras"]) # type: ignore @@ -1925,11 +1822,11 @@ class FileRequirement(object): else: if name: line_name = "{0}{1}".format(name, extras_string) - line = "{0}#egg={1}".format(unquote(link.url_without_fragment), line_name) + line = "{0}#egg={1}".format(link.url_without_fragment, line_name) else: if link: - line = unquote(link.url) - elif uri and isinstance(uri, six.string_types): + line = link.url + elif uri and isinstance(uri, str): line = uri else: raise ValueError( @@ -1951,12 +1848,13 @@ class FileRequirement(object): link_url = None # type: Optional[STRING_TYPE] seed = None # type: Optional[STRING_TYPE] if self.link is not None: - link_url = unquote(self.link.url_without_fragment) + link_url = self.link.url_without_fragment + is_vcs = getattr(self.link, "is_vcs", not self.link.is_artifact) if self._uri_scheme and self._uri_scheme == "path": # We may need any one of these for passing to pip seed = self.path or link_url or self.uri elif (self._uri_scheme and self._uri_scheme == "file") or ( - (self.link.is_artifact or self.link.is_wheel) and self.link.url + (self.link.is_wheel or not is_vcs) and self.link.url ): seed = link_url or self.uri # add egg fragments to remote artifacts (valid urls only) @@ -1998,6 +1896,9 @@ class FileRequirement(object): collision_order = ["file", "uri", "path"] # type: List[STRING_TYPE] collisions = [] # type: List[STRING_TYPE] key_match = next(iter(k for k in collision_order if k in pipfile_dict.keys())) + is_vcs = None + if self.link is not None: + is_vcs = getattr(self.link, "is_vcs", not self.link.is_artifact) if self._uri_scheme: dict_key = self._uri_scheme target_key = dict_key if dict_key in pipfile_dict else key_match @@ -2009,7 +1910,7 @@ class FileRequirement(object): pipfile_dict[dict_key] = winning_value elif ( self.is_remote_artifact - or (self.link is not None and self.link.is_artifact) + or (is_vcs is not None and not is_vcs) and (self._uri_scheme and self._uri_scheme == "file") ): dict_key = "file" @@ -2046,7 +1947,6 @@ class VCSRequirement(FileRequirement): #: vcs reference name (branch / commit / tag) ref = attr.ib(default=None) # type: Optional[STRING_TYPE] #: Subdirectory to use for installation if applicable - subdirectory = attr.ib(default=None) # type: Optional[STRING_TYPE] _repo = attr.ib(default=None) # type: Optional[VCSRepository] _base_line = attr.ib(default=None) # type: Optional[STRING_TYPE] name = attr.ib() # type: STRING_TYPE @@ -2114,20 +2014,21 @@ class VCSRequirement(FileRequirement): def setup_info(self): if self._parsed_line and self._parsed_line.setup_info: if not self._parsed_line.setup_info.name: - self._parsed_line._setup_info.get_info() + with pip_shims.shims.global_tempdir_manager(): + self._parsed_line._setup_info.get_info() return self._parsed_line.setup_info + subdir = self.subdirectory or self.parsed_line.subdirectory if self._repo: - from .setup_info import SetupInfo - - self._setup_info = SetupInfo.from_ireq( - Line(self._repo.checkout_directory).ireq - ) - self._setup_info.get_info() + with pip_shims.shims.global_tempdir_manager(): + self._setup_info = SetupInfo.from_ireq( + Line(self._repo.checkout_directory).ireq, subdir=subdir + ) + self._setup_info.get_info() return self._setup_info ireq = self.parsed_line.ireq - from .setup_info import SetupInfo - self._setup_info = SetupInfo.from_ireq(ireq) + with pip_shims.shims.global_tempdir_manager(): + self._setup_info = SetupInfo.from_ireq(ireq, subdir=subdir) return self._setup_info @setup_info.setter @@ -2157,7 +2058,7 @@ class VCSRequirement(FileRequirement): ) req = init_requirement(canonicalize_name(self.name)) req.editable = self.editable - if not getattr(req, "url"): + if not getattr(req, "url", None): if url is not None: url = add_ssh_scheme_to_git_uri(url) elif self.uri is not None: @@ -2240,7 +2141,7 @@ class VCSRequirement(FileRequirement): if checkout_dir is None: checkout_dir = self.get_checkout_dir(src_dir=src_dir) vcsrepo = VCSRepository( - url=self.url, + url=expand_env_variables(self.url), name=self.name, ref=self.ref if self.ref else None, checkout_directory=checkout_dir, @@ -2268,21 +2169,18 @@ class VCSRequirement(FileRequirement): def get_commit_hash(self): # type: () -> STRING_TYPE - hash_ = None - hash_ = self.repo.get_commit_hash() + with pip_shims.shims.global_tempdir_manager(): + hash_ = self.repo.get_commit_hash() return hash_ def update_repo(self, src_dir=None, ref=None): # type: (Optional[STRING_TYPE], Optional[STRING_TYPE]) -> STRING_TYPE if ref: self.ref = ref - else: - if self.ref: - ref = self.ref repo_hash = None - if not self.is_local and ref is not None: - self.repo.checkout_ref(ref) - repo_hash = self.repo.get_commit_hash() + if not self.is_local and self.ref is not None: + self.repo.checkout_ref(self.ref) + repo_hash = self.get_commit_hash() if self.req: self.req.revision = repo_hash return repo_hash @@ -2298,7 +2196,8 @@ class VCSRequirement(FileRequirement): self.req = self.parsed_line.requirement else: self.req = self.get_requirement() - revision = self.req.revision = vcsrepo.get_commit_hash() + with pip_shims.shims.global_tempdir_manager(): + revision = self.req.revision = vcsrepo.get_commit_hash() # Remove potential ref in the end of uri after ref is parsed if self.link and "@" in self.link.show_url and self.uri and "@" in self.uri: @@ -2360,7 +2259,7 @@ class VCSRequirement(FileRequirement): if key in VCS_LIST and key in pipfile_keys: creation_args["vcs"] = key target = pipfile[key] - if isinstance(target, six.string_types): + if isinstance(target, str): drive, path = os.path.splitdrive(target) if ( not drive @@ -2391,7 +2290,7 @@ class VCSRequirement(FileRequirement): @property def line_part(self): # type: () -> STRING_TYPE - """requirements.txt compatible line part sans-extras""" + """requirements.txt compatible line part sans-extras.""" base = "" # type: STRING_TYPE if self.is_local: base_link = self.link @@ -2406,9 +2305,7 @@ class VCSRequirement(FileRequirement): self._parsed_line.is_direct_url and self._parsed_line.line_with_prefix ): return self._parsed_line.line_with_prefix - elif getattr(self, "_base_line", None) and ( - isinstance(self._base_line, six.string_types) - ): + elif getattr(self, "_base_line", None) and (isinstance(self._base_line, str)): base = self._base_line else: base = getattr(self, "link", self.get_link()).url @@ -2431,7 +2328,7 @@ class VCSRequirement(FileRequirement): alt_type = "" # type: Optional[STRING_TYPE] vcs_value = "" # type: STRING_TYPE if src_keys: - chosen_key = first(src_keys) + chosen_key = next(iter(src_keys)) vcs_type = pipfile.pop("vcs") if chosen_key in pipfile: vcs_value = pipfile[chosen_key] @@ -2475,29 +2372,34 @@ class VCSRequirement(FileRequirement): return {name: pipfile_dict} # type: ignore -@attr.s(cmp=True, hash=True) +@attr.s(eq=True, order=True, hash=True) class Requirement(object): - _name = attr.ib(cmp=True) # type: STRING_TYPE + _name = attr.ib(eq=True, order=True) # type: STRING_TYPE vcs = attr.ib( - default=None, validator=attr.validators.optional(validate_vcs), cmp=True + default=None, + validator=attr.validators.optional(validate_vcs), + eq=True, + order=True, ) # type: Optional[STRING_TYPE] req = attr.ib( - default=None, cmp=True + default=None, eq=True, order=True ) # type: Optional[Union[VCSRequirement, FileRequirement, NamedRequirement]] - markers = attr.ib(default=None, cmp=True) # type: Optional[STRING_TYPE] + markers = attr.ib(default=None, eq=True, order=True) # type: Optional[STRING_TYPE] _specifiers = attr.ib( - validator=attr.validators.optional(validate_specifiers), cmp=True + validator=attr.validators.optional(validate_specifiers), eq=True, order=True ) # type: Optional[STRING_TYPE] - index = attr.ib(default=None, cmp=True) # type: Optional[STRING_TYPE] - editable = attr.ib(default=None, cmp=True) # type: Optional[bool] + index = attr.ib(default=None, eq=True, order=True) # type: Optional[STRING_TYPE] + editable = attr.ib(default=None, eq=True, order=True) # type: Optional[bool] hashes = attr.ib( - factory=frozenset, converter=frozenset, cmp=True + factory=frozenset, converter=frozenset, eq=True, order=True ) # type: FrozenSet[STRING_TYPE] - extras = attr.ib(factory=tuple, cmp=True) # type: Tuple[STRING_TYPE, ...] - abstract_dep = attr.ib(default=None, cmp=False) # type: Optional[AbstractDependency] - _line_instance = attr.ib(default=None, cmp=False) # type: Optional[Line] + extras = attr.ib(factory=tuple, eq=True, order=True) # type: Tuple[STRING_TYPE, ...] + abstract_dep = attr.ib( + default=None, eq=False, order=False + ) # type: Optional[AbstractDependency] + _line_instance = attr.ib(default=None, eq=False, order=False) # type: Optional[Line] _ireq = attr.ib( - default=None, cmp=False + default=None, eq=False, order=False ) # type: Optional[pip_shims.InstallRequirement] def __hash__(self): @@ -2535,7 +2437,7 @@ class Requirement(object): new_hashes = set() # type: Set[STRING_TYPE] if self.hashes is not None: new_hashes |= set(self.hashes) - if isinstance(hashes, six.string_types): + if isinstance(hashes, str): new_hashes.add(hashes) else: new_hashes |= set(hashes) @@ -2558,7 +2460,7 @@ class Requirement(object): def hashes_as_pip(self): # type: () -> STRING_TYPE hashes = self.get_hashes_as_pip() - assert isinstance(hashes, six.string_types) + assert isinstance(hashes, str) return hashes @property @@ -2721,7 +2623,8 @@ class Requirement(object): if self.req is not None and ( not isinstance(self.req, NamedRequirement) and self.req.is_local ): - setup_info = self.run_requires() + with pip_shims.shims.global_tempdir_manager(): + setup_info = self.run_requires() build_backend = setup_info.get("build_backend") return build_backend return "setuptools.build_meta" @@ -2773,7 +2676,8 @@ class Requirement(object): None ) # type: Optional[Union[VCSRequirement, FileRequirement, NamedRequirement]] if ( - (parsed_line.is_file and parsed_line.is_installable) or parsed_line.is_url + (parsed_line.is_file and parsed_line.is_installable) + or parsed_line.is_remote_url ) and not parsed_line.is_vcs: r = file_req_from_parsed_line(parsed_line) elif parsed_line.is_vcs: @@ -2833,7 +2737,7 @@ class Requirement(object): if hasattr(pipfile, "keys"): _pipfile = dict(pipfile).copy() _pipfile["version"] = get_version(pipfile) - vcs = first([vcs for vcs in VCS_LIST if vcs in _pipfile]) + vcs = next(iter([vcs for vcs in VCS_LIST if vcs in _pipfile]), None) if vcs: _pipfile["vcs"] = vcs r = VCSRequirement.from_pipfile(name, pipfile) @@ -3066,8 +2970,8 @@ class Requirement(object): from .dependencies import ( AbstractDependency, - get_dependencies, get_abstract_dependencies, + get_dependencies, ) if not self.abstract_dep: @@ -3094,13 +2998,13 @@ class Requirement(object): :param sources: list[dict], optional :param PackageFinder finder: A **PackageFinder** instance from pip's repository implementation :return: A list of Installation Candidates - :rtype: list[ :class:`~pip._internal.index.InstallationCandidate` ] + :rtype: list[ :class:`~pipenv.patched.notpip._internal.index.InstallationCandidate` ] """ - from .dependencies import get_finder, find_all_matches + from .dependencies import find_all_matches, get_finder if not finder: - finder = get_finder(sources=sources) + _, finder = get_finder(sources=sources) return find_all_matches(finder, self.as_ireq()) def run_requires(self, sources=None, finder=None): @@ -3109,16 +3013,16 @@ class Requirement(object): elif self.line_instance and self.line_instance.setup_info is not None: info_dict = self.line_instance.setup_info.as_dict() else: - from .setup_info import SetupInfo if not finder: from .dependencies import get_finder finder = get_finder(sources=sources) - info = SetupInfo.from_requirement(self, finder=finder) - if info is None: - return {} - info_dict = info.get_info() + with pip_shims.shims.global_tempdir_manager(): + info = SetupInfo.from_requirement(self, finder=finder) + if info is None: + return {} + info_dict = info.get_info() if self.req and not self.req.setup_info: self.req._setup_info = info if self.req._has_hashed_name and info_dict.get("name"): @@ -3167,6 +3071,9 @@ def file_req_from_parsed_line(parsed_line): pyproject_requires = None # type: Optional[Tuple[STRING_TYPE, ...]] if parsed_line.pyproject_requires is not None: pyproject_requires = tuple(parsed_line.pyproject_requires) + pyproject_path = ( + Path(parsed_line.pyproject_toml) if parsed_line.pyproject_toml else None + ) req_dict = { "setup_path": parsed_line.setup_py, "path": path, @@ -3177,9 +3084,7 @@ def file_req_from_parsed_line(parsed_line): "uri": parsed_line.uri, "pyproject_requires": pyproject_requires, "pyproject_backend": parsed_line.pyproject_backend, - "pyproject_path": Path(parsed_line.pyproject_toml) - if parsed_line.pyproject_toml - else None, + "pyproject_path": pyproject_path, "parsed_line": parsed_line, "req": parsed_line.requirement, } @@ -3246,3 +3151,8 @@ def named_req_from_parsed_line(parsed_line): parsed_line=parsed_line, ) return NamedRequirement.from_line(parsed_line.line) + + +if __name__ == "__main__": + line = Line("vistir@ git+https://github.com/sarugaku/vistir.git@master") + print(line) diff --git a/pipenv/vendor/requirementslib/models/resolvers.py b/pipenv/vendor/requirementslib/models/resolvers.py index bd773ba6..1f6818b0 100644 --- a/pipenv/vendor/requirementslib/models/resolvers.py +++ b/pipenv/vendor/requirementslib/models/resolvers.py @@ -1,10 +1,8 @@ # -*- coding=utf-8 -*- from contextlib import contextmanager -import attr -import six - -from pip_shims.shims import Wheel +import pipenv.vendor.attr as attr +from pipenv.vendor.pip_shims.shims import Wheel from .cache import HashCache from .utils import format_requirement, is_pinned_requirement, version_from_ireq @@ -40,15 +38,16 @@ class DependencyResolver(object): def create(cls, finder=None, allow_prereleases=False, get_all_hashes=True): if not finder: from .dependencies import get_finder + finder_args = [] if allow_prereleases: - finder_args.append('--pre') + finder_args.append("--pre") finder = get_finder(*finder_args) creation_kwargs = { - 'allow_prereleases': allow_prereleases, - 'include_incompatible_hashes': get_all_hashes, - 'finder': finder, - 'hash_cache': HashCache(), + "allow_prereleases": allow_prereleases, + "include_incompatible_hashes": get_all_hashes, + "finder": finder, + "hash_cache": HashCache(), } resolver = cls(**creation_kwargs) return resolver @@ -62,8 +61,8 @@ class DependencyResolver(object): return list(self.pinned_deps.values()) def add_abstract_dep(self, dep): - """Add an abstract dependency by either creating a new entry or - merging with an old one. + """Add an abstract dependency by either creating a new entry or merging + with an old one. :param dep: An abstract dependency to add :type dep: :class:`~requirementslib.models.dependency.AbstractDependency` @@ -75,9 +74,9 @@ class DependencyResolver(object): compatible_versions = self.dep_dict[dep.name].compatible_versions(dep) if compatible_versions: self.candidate_dict[dep.name] = compatible_versions - self.dep_dict[dep.name] = self.dep_dict[ - dep.name - ].compatible_abstract_dep(dep) + self.dep_dict[dep.name] = self.dep_dict[dep.name].compatible_abstract_dep( + dep + ) else: raise ResolutionError else: @@ -85,10 +84,12 @@ class DependencyResolver(object): self.dep_dict[dep.name] = dep def pin_deps(self): - """Pins the current abstract dependencies and adds them to the history dict. + """Pins the current abstract dependencies and adds them to the history + dict. - Adds any new dependencies to the abstract dependencies already present by - merging them together to form new, compatible abstract dependencies. + Adds any new dependencies to the abstract dependencies already + present by merging them together to form new, compatible + abstract dependencies. """ for name in list(self.dep_dict.keys()): @@ -103,8 +104,10 @@ class DependencyResolver(object): old_version = version_from_ireq(self.pinned_deps[name]) if not pin.editable: new_version = version_from_ireq(pin) - if (new_version != old_version and - new_version not in self.candidate_dict[name]): + if ( + new_version != old_version + and new_version not in self.candidate_dict[name] + ): continue pin.parent = abs_dep.parent pin_subdeps = self.dep_dict[name].get_deps(pin) @@ -120,7 +123,8 @@ class DependencyResolver(object): break def resolve(self, root_nodes, max_rounds=20): - """Resolves dependencies using a backtracking resolver and multiple endpoints. + """Resolves dependencies using a backtracking resolver and multiple + endpoints. Note: this resolver caches aggressively. Runs for *max_rounds* or until any two pinning rounds yield the same outcome. @@ -139,10 +143,11 @@ class DependencyResolver(object): # Coerce input into AbstractDependency instances. # We accept str, Requirement, and AbstractDependency as input. - from .dependencies import AbstractDependency from ..utils import log + from .dependencies import AbstractDependency + for dep in root_nodes: - if isinstance(dep, six.string_types): + if isinstance(dep, str): dep = AbstractDependency.from_string(dep) elif not isinstance(dep, AbstractDependency): dep = AbstractDependency.from_requirement(dep) @@ -185,43 +190,52 @@ class DependencyResolver(object): def get_hashes_for_one(self, ireq): if not self.finder: from .dependencies import get_finder + finder_args = [] if self.allow_prereleases: - finder_args.append('--pre') + finder_args.append("--pre") self.finder = get_finder(*finder_args) if ireq.editable: return set() - from pip_shims import VcsSupport + from pipenv.vendor.pip_shims import VcsSupport + vcs = VcsSupport() - if ireq.link and ireq.link.scheme in vcs.all_schemes and 'ssh' in ireq.link.scheme: + if ( + ireq.link + and ireq.link.scheme in vcs.all_schemes + and "ssh" in ireq.link.scheme + ): return set() if not is_pinned_requirement(ireq): - raise TypeError( - "Expected pinned requirement, got {}".format(ireq)) + raise TypeError("Expected pinned requirement, got {}".format(ireq)) matching_candidates = set() with self.allow_all_wheels(): from .dependencies import find_all_matches - matching_candidates = ( - find_all_matches(self.finder, ireq, pre=self.allow_prereleases) + + matching_candidates = find_all_matches( + self.finder, ireq, pre=self.allow_prereleases ) return { - self.hash_cache.get_hash(candidate.location) + self.hash_cache.get_hash( + getattr(candidate, "location", getattr(candidate, "link", None)) + ) for candidate in matching_candidates } @contextmanager def allow_all_wheels(self): - """ - Monkey patches pip.Wheel to allow wheels from all platforms and Python versions. + """Monkey patches pip.Wheel to allow wheels from all platforms and + Python versions. - This also saves the candidate cache and set a new one, or else the results from the - previous non-patched calls will interfere. + This also saves the candidate cache and set a new one, or else + the results from the previous non-patched calls will interfere. """ + def _wheel_supported(self, tags=None): # Ignore current platform. Support everything. return True diff --git a/pipenv/vendor/requirementslib/models/setup_info.py b/pipenv/vendor/requirementslib/models/setup_info.py index 3bee49ea..772a4d81 100644 --- a/pipenv/vendor/requirementslib/models/setup_info.py +++ b/pipenv/vendor/requirementslib/models/setup_info.py @@ -3,30 +3,32 @@ from __future__ import absolute_import, print_function import ast import atexit +import configparser import contextlib import importlib +import operator import os import shutil import sys -from functools import partial +from collections.abc import Iterable, Mapping +from functools import lru_cache, partial +from pathlib import Path +from urllib.parse import urlparse, urlunparse +from weakref import finalize -import attr +import pipenv.vendor.attr as attr import packaging.specifiers import packaging.utils import packaging.version import pep517.envbuild import pep517.wrappers -import pkg_resources.extern.packaging.requirements as pkg_resources_requirements -import six -from appdirs import user_cache_dir -from distlib.wheel import Wheel -from packaging.markers import Marker -from six.moves import configparser -from six.moves.urllib.parse import unquote, urlparse, urlunparse -from vistir.compat import FileNotFoundError, Iterable, Mapping, Path, lru_cache -from vistir.contextmanagers import cd, temp_path -from vistir.misc import run -from vistir.path import create_tracked_tempdir, ensure_mkdir_p, mkdir_p, rmtree +from pipenv.vendor.distlib.wheel import Wheel +from pipenv.vendor.packaging.markers import Marker +from pipenv.vendor.pip_shims.utils import call_function_with_correct_args +from pipenv.vendor.platformdirs import user_cache_dir +from pipenv.vendor.vistir.contextmanagers import cd, temp_path +from pipenv.vendor.vistir.misc import run +from pipenv.vendor.vistir.path import create_tracked_tempdir, ensure_mkdir_p, mkdir_p, rmtree from ..environment import MYPY_RUNNING from ..exceptions import RequirementError @@ -35,48 +37,45 @@ from .utils import ( get_name_variants, get_pyproject, init_requirement, - read_source, split_vcs_method_from_uri, strip_extras_markers_from_requirement, ) try: - from setuptools.dist import distutils, Distribution + import pkg_resources.extern.packaging.requirements as pkg_resources_requirements +except ModuleNotFoundError: + pkg_resources_requirements = None + +try: + from setuptools.dist import Distribution, distutils except ImportError: import distutils from distutils.core import Distribution - -try: - from os import scandir -except ImportError: - from scandir import scandir - +from contextlib import ExitStack +from os import scandir if MYPY_RUNNING: from typing import ( Any, - Callable, + AnyStr, Dict, - List, Generator, + List, Optional, - Union, + Sequence, + Set, + Text, Tuple, TypeVar, - Text, - Set, - AnyStr, - Sequence, + Union, ) - from pip_shims.shims import InstallRequirement, PackageFinder - from pkg_resources import ( - PathMetadata, - DistInfoDistribution, - EggInfoDistribution, - Requirement as PkgResourcesRequirement, - ) - from packaging.requirements import Requirement as PackagingRequirement + + import pipenv.vendor.requests as requests + from pipenv.vendor.packaging.requirements import Requirement as PackagingRequirement + from pipenv.vendor.pip_shims.shims import InstallRequirement, PackageFinder + from pkg_resources import DistInfoDistribution, EggInfoDistribution, PathMetadata + from pkg_resources import Requirement as PkgResourcesRequirement TRequirement = TypeVar("TRequirement") RequirementType = TypeVar( @@ -138,6 +137,7 @@ class BuildEnv(pep517.envbuild.BuildEnvironment): class HookCaller(pep517.wrappers.Pep517HookCaller): def __init__(self, source_dir, build_backend, backend_path=None): + super().__init__(source_dir, build_backend, backend_path=backend_path) self.source_dir = os.path.abspath(source_dir) self.build_backend = build_backend self._subprocess_runner = pep517_subprocess_runner @@ -148,43 +148,6 @@ class HookCaller(pep517.wrappers.Pep517HookCaller): self.backend_path = backend_path -def parse_special_directives(setup_entry, package_dir=None): - # type: (S, Optional[STRING_TYPE]) -> S - rv = setup_entry - if not package_dir: - package_dir = os.getcwd() - if setup_entry.startswith("file:"): - _, path = setup_entry.split("file:") - path = path.strip() - if os.path.exists(path): - rv = read_source(path) - elif setup_entry.startswith("attr:"): - _, resource = setup_entry.split("attr:") - resource = resource.strip() - with temp_path(): - sys.path.insert(0, package_dir) - if "." in resource: - resource, _, attribute = resource.rpartition(".") - package, _, path = resource.partition(".") - base_path = os.path.join(package_dir, package) - if path: - path = os.path.join(base_path, os.path.join(*path.split("."))) - else: - path = base_path - if not os.path.exists(path) and os.path.exists("{0}.py".format(path)): - path = "{0}.py".format(path) - elif os.path.isdir(path): - path = os.path.join(path, "__init__.py") - rv = ast_parse_attribute_from_file(path, attribute) - if rv: - return str(rv) - module = importlib.import_module(resource) - rv = getattr(module, attribute) - if not isinstance(rv, six.string_types): - rv = str(rv) - return rv - - def make_base_requirements(reqs): # type: (Sequence[STRING_TYPE]) -> Set[BaseRequirement] requirements = set() @@ -193,133 +156,388 @@ def make_base_requirements(reqs): for req in reqs: if isinstance(req, BaseRequirement): requirements.add(req) - elif isinstance(req, pkg_resources_requirements.Requirement): + elif pkg_resources_requirements is not None and isinstance( + req, pkg_resources_requirements.Requirement + ): requirements.add(BaseRequirement.from_req(req)) - elif req and isinstance(req, six.string_types) and not req.startswith("#"): + elif req and isinstance(req, str) and not req.startswith("#"): requirements.add(BaseRequirement.from_string(req)) return requirements +def suppress_unparsable(func, *args, **kwargs): + try: + return func(*args, **kwargs) + except Unparsable: + return None + + +class Unparsable(ValueError): + """Not able to parse from setup.py.""" + + +class SetupReader: + """Class that reads a setup.py file without executing it.""" + + @classmethod + def read_setup_py(cls, file: Path, raising: bool = True) -> "Dict[str, Any]": + + with file.open(encoding="utf-8") as f: + content = f.read() + + body = ast.parse(content).body + + setup_call, body = cls._find_setup_call(body) + if not setup_call: + return {} + + if raising: + + def caller(func, *args, **kwargs): + return func(*args, **kwargs) + + else: + caller = suppress_unparsable + + return { + "name": caller(cls._find_single_string, setup_call, body, "name"), + "version": caller(cls._find_single_string, setup_call, body, "version"), + "install_requires": caller(cls._find_install_requires, setup_call, body), + "extras_require": caller(cls._find_extras_require, setup_call, body), + "python_requires": caller( + cls._find_single_string, setup_call, body, "python_requires" + ), + } + + @staticmethod + def read_setup_cfg(file: Path) -> "Dict[str, Any]": + parser = configparser.ConfigParser() + + parser.read(str(file)) + + name = None + version = None + if parser.has_option("metadata", "name"): + name = parser.get("metadata", "name") + + if parser.has_option("metadata", "version"): + version = parser.get("metadata", "version") + + install_requires = [] + extras_require: "Dict[str, List[str]]" = {} + python_requires = None + if parser.has_section("options"): + if parser.has_option("options", "install_requires"): + for dep in parser.get("options", "install_requires").split("\n"): + dep = dep.strip() + if not dep: + continue + + install_requires.append(dep) + + if parser.has_option("options", "python_requires"): + python_requires = parser.get("options", "python_requires") + + if parser.has_section("options.extras_require"): + for group in parser.options("options.extras_require"): + extras_require[group] = [] + deps = parser.get("options.extras_require", group) + for dep in deps.split("\n"): + dep = dep.strip() + if not dep: + continue + + extras_require[group].append(dep) + + return { + "name": name, + "version": version, + "install_requires": install_requires, + "extras_require": extras_require, + "python_requires": python_requires, + } + + @classmethod + def _find_setup_call( + cls, elements: "List[Any]" + ) -> "Tuple[Optional[ast.Call], Optional[List[Any]]]": + funcdefs = [] + for i, element in enumerate(elements): + if isinstance(element, ast.If) and i == len(elements) - 1: + # Checking if the last element is an if statement + # and if it is 'if __name__ == "__main__"' which + # could contain the call to setup() + test = element.test + if not isinstance(test, ast.Compare): + continue + + left = test.left + if not isinstance(left, ast.Name): + continue + + if left.id != "__name__": + continue + + setup_call, body = cls._find_sub_setup_call([element]) + if not setup_call: + continue + + return setup_call, body + elements + if not isinstance(element, ast.Expr): + if isinstance(element, ast.FunctionDef): + funcdefs.append(element) + + continue + + value = element.value + if not isinstance(value, ast.Call): + continue + + func = value.func + if not (isinstance(func, ast.Name) and func.id == "setup") and not ( + isinstance(func, ast.Attribute) + and isinstance(func.value, ast.Name) + and func.value.id == "setuptools" + and func.attr == "setup" + ): + continue + + return value, elements + + # Nothing, we inspect the function definitions + return cls._find_sub_setup_call(funcdefs) + + @classmethod + def _find_sub_setup_call( + cls, elements: "List[Any]" + ) -> "Tuple[Optional[ast.Call], Optional[List[Any]]]": + for element in elements: + if not isinstance(element, (ast.FunctionDef, ast.If)): + continue + + setup_call = cls._find_setup_call(element.body) + if setup_call != (None, None): + setup_call, body = setup_call + + body = elements + body + + return setup_call, body + + return None, None + + @classmethod + def _find_install_requires(cls, call: ast.Call, body: "Iterable[Any]") -> "List[str]": + value = cls._find_in_call(call, "install_requires") + if value is None: + # Trying to find in kwargs + kwargs = cls._find_call_kwargs(call) + + if kwargs is None: + return [] + + if not isinstance(kwargs, ast.Name): + raise Unparsable() + + variable = cls._find_variable_in_body(body, kwargs.id) + if not isinstance(variable, (ast.Dict, ast.Call)): + raise Unparsable() + + if isinstance(variable, ast.Call): + if not isinstance(variable.func, ast.Name): + raise Unparsable() + + if variable.func.id != "dict": + raise Unparsable() + + value = cls._find_in_call(variable, "install_requires") + else: + value = cls._find_in_dict(variable, "install_requires") + + if value is None: + return [] + + if isinstance(value, ast.List): + return [el.s for el in value.elts] + elif isinstance(value, ast.Name): + variable = cls._find_variable_in_body(body, value.id) + + if variable is not None and isinstance(variable, ast.List): + return [el.s for el in variable.elts] + + raise Unparsable() + + @classmethod + def _find_extras_require( + cls, call: ast.Call, body: "Iterable[Any]" + ) -> "Dict[str, List[str]]": + extras_require: "Dict[str, List[str]]" = {} + value = cls._find_in_call(call, "extras_require") + if value is None: + # Trying to find in kwargs + kwargs = cls._find_call_kwargs(call) + + if kwargs is None: + return extras_require + + if not isinstance(kwargs, ast.Name): + raise Unparsable() + + variable = cls._find_variable_in_body(body, kwargs.id) + if not isinstance(variable, (ast.Dict, ast.Call)): + raise Unparsable() + + if isinstance(variable, ast.Call): + if not isinstance(variable.func, ast.Name): + raise Unparsable() + + if variable.func.id != "dict": + raise Unparsable() + + value = cls._find_in_call(variable, "extras_require") + else: + value = cls._find_in_dict(variable, "extras_require") + + if value is None: + return extras_require + + if isinstance(value, ast.Dict): + for key, val in zip(value.keys, value.values): + if isinstance(val, ast.Name): + val = cls._find_variable_in_body(body, val.id) + + if isinstance(val, ast.List): + extras_require[key.s] = [e.s for e in val.elts] + else: + raise Unparsable() + elif isinstance(value, ast.Name): + variable = cls._find_variable_in_body(body, value.id) + + if variable is None or not isinstance(variable, ast.Dict): + raise Unparsable() + + for key, val in zip(variable.keys, variable.values): + if isinstance(val, ast.Name): + val = cls._find_variable_in_body(body, val.id) + + if isinstance(val, ast.List): + extras_require[key.s] = [e.s for e in val.elts] + else: + raise Unparsable() + else: + raise Unparsable() + + return extras_require + + @classmethod + def _find_single_string( + cls, call: ast.Call, body: "List[Any]", name: str + ) -> "Optional[str]": + value = cls._find_in_call(call, name) + if value is None: + # Trying to find in kwargs + kwargs = cls._find_call_kwargs(call) + if kwargs is None: + return None + + if not isinstance(kwargs, ast.Name): + raise Unparsable() + + variable = cls._find_variable_in_body(body, kwargs.id) + if not isinstance(variable, (ast.Dict, ast.Call)): + raise Unparsable() + + if isinstance(variable, ast.Call): + if not isinstance(variable.func, ast.Name): + raise Unparsable() + + if variable.func.id != "dict": + raise Unparsable() + + value = cls._find_in_call(variable, name) + else: + value = cls._find_in_dict(variable, name) + + if value is None: + return None + + if isinstance(value, ast.Str): + return value.s + elif isinstance(value, ast.Name): + variable = cls._find_variable_in_body(body, value.id) + + if variable is not None and isinstance(variable, ast.Str): + return variable.s + + raise Unparsable() + + @staticmethod + def _find_in_call(call: ast.Call, name: str) -> "Optional[Any]": + for keyword in call.keywords: + if keyword.arg == name: + return keyword.value + return None + + @staticmethod + def _find_call_kwargs(call: ast.Call) -> "Optional[Any]": + kwargs = None + for keyword in call.keywords: + if keyword.arg is None: + kwargs = keyword.value + + return kwargs + + @staticmethod + def _find_variable_in_body(body: "Iterable[Any]", name: str) -> "Optional[Any]": + for elem in body: + + if not isinstance(elem, (ast.Assign, ast.AnnAssign)): + continue + + if getattr(elem, "target", None) and elem.target.id == name: + return elem.value + + for target in elem.targets: + if not isinstance(target, ast.Name): + continue + + if target.id == name: + return elem.value + return None + + @staticmethod + def _find_in_dict(dict_: ast.Dict, name: str) -> "Optional[Any]": + for key, val in zip(dict_.keys, dict_.values): + if isinstance(key, ast.Str) and key.s == name: + return val + return None + + def setuptools_parse_setup_cfg(path): from setuptools.config import read_configuration parsed = read_configuration(path) results = parsed.get("metadata", {}) - results.update({parsed.get("options", {})}) - results["install_requires"] = make_base_requirements( - results.get("install_requires", []) - ) - extras = {} - for extras_section, extras in results.get("extras_require", {}).items(): - new_reqs = tuple(make_base_requirements(extras)) - if new_reqs: - extras[extras_section] = new_reqs - results["extras_require"] = extras - results["setup_requires"] = make_base_requirements(results.get("setup_requires", [])) + results.update(parsed.get("options", {})) + if "install_requires" in results: + results["install_requires"] = make_base_requirements( + results.get("install_requires", []) + ) + if "extras_require" in results: + extras = {} + for extras_section, extras_reqs in results.get("extras_require", {}).items(): + new_reqs = tuple(make_base_requirements(extras_reqs)) + if new_reqs: + extras[extras_section] = new_reqs + results["extras_require"] = extras + if "setup_requires" in results: + results["setup_requires"] = make_base_requirements( + results.get("setup_requires", []) + ) return results -def get_package_dir_from_setupcfg(parser, base_dir=None): - # type: (configparser.ConfigParser, STRING_TYPE) -> Text - if base_dir is not None: - package_dir = base_dir - else: - package_dir = os.getcwd() - if parser.has_option("options", "packages.find"): - pkg_dir = parser.get("options", "packages.find") - if isinstance(package_dir, Mapping): - package_dir = os.path.join(package_dir, pkg_dir.get("where")) - elif parser.has_option("options", "packages"): - pkg_dir = parser.get("options", "packages") - if "find:" in pkg_dir: - _, pkg_dir = pkg_dir.split("find:") - pkg_dir = pkg_dir.strip() - package_dir = os.path.join(package_dir, pkg_dir) - elif os.path.exists(os.path.join(package_dir, "setup.py")): - setup_py = ast_parse_setup_py(os.path.join(package_dir, "setup.py")) - if "package_dir" in setup_py: - package_lookup = setup_py["package_dir"] - if not isinstance(package_lookup, Mapping): - package_dir = package_lookup - package_dir = package_lookup.get( - next(iter(list(package_lookup.keys()))), package_dir - ) - if not os.path.isabs(package_dir): - if not base_dir: - package_dir = os.path.join(os.path.getcwd(), package_dir) - else: - package_dir = os.path.join(base_dir, package_dir) - return package_dir - - -def get_name_and_version_from_setupcfg(parser, package_dir): - # type: (configparser.ConfigParser, STRING_TYPE) -> Tuple[Optional[S], Optional[S]] - name, version = None, None - if parser.has_option("metadata", "name"): - name = parse_special_directives(parser.get("metadata", "name"), package_dir) - if parser.has_option("metadata", "version"): - version = parse_special_directives(parser.get("metadata", "version"), package_dir) - return name, version - - -def get_extras_from_setupcfg(parser): - # type: (configparser.ConfigParser) -> Dict[STRING_TYPE, Tuple[BaseRequirement, ...]] - extras = {} # type: Dict[STRING_TYPE, Tuple[BaseRequirement, ...]] - if "options.extras_require" not in parser.sections(): - return extras - extras_require_section = parser.options("options.extras_require") - for section in extras_require_section: - if section in ["options", "metadata"]: - continue - section_contents = parser.get("options.extras_require", section) - section_list = section_contents.split("\n") - section_extras = tuple(make_base_requirements(section_list)) - if section_extras: - extras[section] = section_extras - return extras - - -def parse_setup_cfg(setup_cfg_path): - # type: (S) -> Dict[S, Union[S, None, Set[BaseRequirement], List[S], Dict[STRING_TYPE, Tuple[BaseRequirement]]]] - if not os.path.exists(setup_cfg_path): - raise FileNotFoundError(setup_cfg_path) - try: - return setuptools_parse_setup_cfg(setup_cfg_path) - except Exception: - pass - default_opts = { - "metadata": {"name": "", "version": ""}, - "options": { - "install_requires": "", - "python_requires": "", - "build_requires": "", - "setup_requires": "", - "extras": "", - "packages.find": {"where": "."}, - }, - } - parser = configparser.ConfigParser(default_opts) - parser.read(setup_cfg_path) - results = {} - base_dir = os.path.dirname(os.path.abspath(setup_cfg_path)) - package_dir = get_package_dir_from_setupcfg(parser, base_dir=base_dir) - name, version = get_name_and_version_from_setupcfg(parser, package_dir) - results["name"] = name - results["version"] = version - install_requires = set() # type: Set[BaseRequirement] - if parser.has_option("options", "install_requires"): - install_requires = make_base_requirements( - parser.get("options", "install_requires").split("\n") - ) - results["install_requires"] = install_requires - if parser.has_option("options", "python_requires"): - results["python_requires"] = parse_special_directives( - parser.get("options", "python_requires"), package_dir - ) - if parser.has_option("options", "build_requires"): - results["build_requires"] = parser.get("options", "build_requires") - results["extras_require"] = get_extras_from_setupcfg(parser) - return results +def parse_setup_cfg(path: str) -> "Dict[str, Any]": + return SetupReader.read_setup_cfg(Path(path)) @contextlib.contextmanager @@ -389,13 +607,25 @@ def ensure_reqs(reqs): for req in reqs: if not req: continue - if isinstance(req, six.string_types): + if isinstance(req, str): req = pkg_resources.Requirement.parse("{0}".format(str(req))) # req = strip_extras_markers_from_requirement(req) new_reqs.append(req) return new_reqs +def any_valid_values(data: "Dict[str, Any]", fields: "Iterable[str]") -> bool: + def is_valid(value: "Any") -> bool: + if isinstance(value, (list, tuple)): + return all(map(is_valid, value)) + elif isinstance(value, dict): + return all(map(is_valid, value.values())) + return isinstance(value, str) + + fields = [field for field in fields if field in data] + return fields and all(is_valid(data[field]) for field in fields) + + def _prepare_wheel_building_kwargs( ireq=None, # type: Optional[InstallRequirement] src_root=None, # type: Optional[STRING_TYPE] @@ -408,14 +638,11 @@ def _prepare_wheel_building_kwargs( wheel_download_dir = os.path.join(CACHE_DIR, "wheels") # type: STRING_TYPE mkdir_p(wheel_download_dir) - if src_dir is None: if editable and src_root is not None: src_dir = src_root - elif ireq is None and src_root is not None and not editable: + elif src_root is not None: src_dir = _get_src_dir(root=src_root) # type: STRING_TYPE - elif ireq is not None and ireq.editable and src_root is not None: - src_dir = _get_src_dir(root=src_root) else: src_dir = create_tracked_tempdir(prefix="reqlib-src") @@ -452,25 +679,37 @@ class ScandirCloser(object): pass +def _is_venv_dir(path): + # type: (AnyStr) -> bool + if os.name == "nt": + return os.path.isfile(os.path.join(path, "Scripts/python.exe")) or os.path.isfile( + os.path.join(path, "Scripts/activate") + ) + else: + return os.path.isfile(os.path.join(path, "bin/python")) or os.path.isfile( + os.path.join(path, "bin/activate") + ) + + def iter_metadata(path, pkg_name=None, metadata_type="egg-info"): # type: (AnyStr, Optional[AnyStr], AnyStr) -> Generator if pkg_name is not None: pkg_variants = get_name_variants(pkg_name) - non_matching_dirs = [] - with contextlib.closing(ScandirCloser(path)) as path_iterator: - for entry in path_iterator: - if entry.is_dir(): - entry_name, ext = os.path.splitext(entry.name) - if ext.endswith(metadata_type): - if pkg_name is None or entry_name.lower() in pkg_variants: - yield entry - elif not entry.name.endswith(metadata_type): - non_matching_dirs.append(entry) - for entry in non_matching_dirs: - for dir_entry in iter_metadata( - entry.path, pkg_name=pkg_name, metadata_type=metadata_type - ): - yield dir_entry + dirs_to_search = [path] + while dirs_to_search: + p = dirs_to_search.pop(0) + # Skip when the directory is like a venv + if _is_venv_dir(p): + continue + with contextlib.closing(ScandirCloser(p)) as path_iterator: + for entry in path_iterator: + if entry.is_dir(): + entry_name, ext = os.path.splitext(entry.name) + if ext.endswith(metadata_type): + if pkg_name is None or entry_name.lower() in pkg_variants: + yield entry + elif not entry.name.endswith(metadata_type): + dirs_to_search.append(entry.path) def find_egginfo(target, pkg_name=None): @@ -562,7 +801,7 @@ def get_extra_name_from_marker(marker): def get_metadata_from_wheel(wheel_path): # type: (S) -> Dict[Any, Any] - if not isinstance(wheel_path, six.string_types): + if not isinstance(wheel_path, str): raise TypeError("Expected string instance, received {0!r}".format(wheel_path)) try: dist = Wheel(wheel_path) @@ -637,210 +876,8 @@ def get_metadata_from_dist(dist): } -class Analyzer(ast.NodeVisitor): - def __init__(self): - self.name_types = [] - self.function_map = {} # type: Dict[Any, Any] - self.functions = [] - self.strings = [] - self.assignments = {} - self.binOps = [] - self.binOps_map = {} - super(Analyzer, self).__init__() - - def generic_visit(self, node): - if isinstance(node, ast.Call): - self.functions.append(node) - self.function_map.update(ast_unparse(node, initial_mapping=True)) - if isinstance(node, ast.Name): - self.name_types.append(node) - if isinstance(node, ast.Str): - self.strings.append(node) - if isinstance(node, ast.Assign): - self.assignments.update(ast_unparse(node, initial_mapping=True)) - super(Analyzer, self).generic_visit(node) - - def visit_BinOp(self, node): - left = ast_unparse(node.left, initial_mapping=True) - right = ast_unparse(node.right, initial_mapping=True) - node.left = left - node.right = right - self.binOps.append(node) - - def unmap_binops(self): - for binop in self.binOps: - self.binOps_map[binop] = ast_unparse(binop, analyzer=self) - - def match_assignment_str(self, match): - return next( - iter(k for k in self.assignments if getattr(k, "id", "") == match), None - ) - - def match_assignment_name(self, match): - return next( - iter(k for k in self.assignments if getattr(k, "id", "") == match.id), None - ) - - -def ast_unparse(item, initial_mapping=False, analyzer=None, recurse=True): # noqa:C901 - # type: (Any, bool, Optional[Analyzer], bool) -> Union[List[Any], Dict[Any, Any], Tuple[Any, ...], STRING_TYPE] - unparse = partial( - ast_unparse, initial_mapping=initial_mapping, analyzer=analyzer, recurse=recurse - ) - if isinstance(item, ast.Dict): - unparsed = dict(zip(unparse(item.keys), unparse(item.values))) - elif isinstance(item, ast.List): - unparsed = [unparse(el) for el in item.elts] - elif isinstance(item, ast.Tuple): - unparsed = tuple([unparse(el) for el in item.elts]) - elif isinstance(item, ast.Str): - unparsed = item.s - elif isinstance(item, ast.Subscript): - unparsed = unparse(item.value) - elif isinstance(item, ast.BinOp): - if analyzer and item in analyzer.binOps_map: - unparsed = analyzer.binOps_map[item] - elif isinstance(item.op, ast.Add): - if not initial_mapping: - right_item = unparse(item.right) - left_item = unparse(item.left) - if not all( - isinstance(side, (six.string_types, int, float, list, tuple)) - for side in (left_item, right_item) - ): - item.left = left_item - item.right = right_item - unparsed = item - else: - unparsed = left_item + right_item - else: - unparsed = item - elif isinstance(item.op, ast.Sub): - unparsed = unparse(item.left) - unparse(item.right) - else: - unparsed = item - elif isinstance(item, ast.Name): - if not initial_mapping: - unparsed = item.id - if analyzer and recurse: - if item in analyzer.assignments: - items = unparse(analyzer.assignments[item]) - unparsed = items.get(item.id, item.id) - else: - assignment = analyzer.match_assignment_name(item) - if assignment is not None: - items = unparse(analyzer.assignments[assignment]) - unparsed = items.get(item.id, item.id) - else: - unparsed = item - elif six.PY3 and isinstance(item, ast.NameConstant): - unparsed = item.value - elif isinstance(item, ast.Attribute): - attr_name = getattr(item, "value", None) - attr_attr = getattr(item, "attr", None) - name = None - if initial_mapping: - unparsed = item - elif attr_name and not recurse: - name = attr_name - else: - name = unparse(attr_name) if attr_name is not None else attr_attr - if name and attr_attr: - if not initial_mapping and isinstance(name, six.string_types): - unparsed = ".".join([item for item in (name, attr_attr) if item]) - else: - unparsed = item - elif attr_attr and not name and not initial_mapping: - unparsed = attr_attr - else: - unparsed = name if not unparsed else unparsed - elif isinstance(item, ast.Call): - unparsed = {} - if isinstance(item.func, ast.Name): - func_name = unparse(item.func) - elif isinstance(item.func, ast.Attribute): - func_name = unparse(item.func) - if func_name: - unparsed[func_name] = {} - for keyword in item.keywords: - unparsed[func_name].update(unparse(keyword)) - elif isinstance(item, ast.keyword): - unparsed = {unparse(item.arg): unparse(item.value)} - elif isinstance(item, ast.Assign): - # XXX: DO NOT UNPARSE THIS - # XXX: If we unparse this it becomes impossible to map it back - # XXX: To the original node in the AST so we can find the - # XXX: Original reference - if not initial_mapping: - target = unparse(next(iter(item.targets)), recurse=False) - val = unparse(item.value, recurse=False) - if isinstance(target, (tuple, set, list)): - unparsed = dict(zip(target, val)) - else: - unparsed = {target: val} - else: - unparsed = {next(iter(item.targets)): item} - elif isinstance(item, Mapping): - unparsed = {} - for k, v in item.items(): - try: - unparsed[unparse(k)] = unparse(v) - except TypeError: - unparsed[k] = unparse(v) - elif isinstance(item, (list, tuple)): - unparsed = type(item)([unparse(el) for el in item]) - elif isinstance(item, six.string_types): - unparsed = item - else: - return item - return unparsed - - -def ast_parse_attribute_from_file(path, attribute): - # type: (S) -> Any - analyzer = ast_parse_file(path) - target_value = None - for k, v in analyzer.assignments.items(): - name = "" - if isinstance(k, ast.Name): - name = k.id - elif isinstance(k, ast.Attribute): - fn = ast_unparse(k) - if isinstance(fn, six.string_types): - _, _, name = fn.rpartition(".") - if name == attribute: - target_value = ast_unparse(v, analyzer=analyzer) - break - if isinstance(target_value, Mapping) and attribute in target_value: - return target_value[attribute] - return target_value - - -def ast_parse_file(path): - # type: (S) -> Analyzer - tree = ast.parse(read_source(path)) - ast_analyzer = Analyzer() - ast_analyzer.visit(tree) - return ast_analyzer - - -def ast_parse_setup_py(path): - # type: (S) -> Dict[Any, Any] - ast_analyzer = ast_parse_file(path) - setup = {} # type: Dict[Any, Any] - ast_analyzer.unmap_binops() - for k, v in ast_analyzer.function_map.items(): - fn_name = "" - if isinstance(k, ast.Name): - fn_name = k.id - elif isinstance(k, ast.Attribute): - fn = ast_unparse(k) - if isinstance(fn, six.string_types): - _, _, fn_name = fn.rpartition(".") - if fn_name == "setup": - setup = v - cleaned_setup = ast_unparse(setup, analyzer=ast_analyzer) - return cleaned_setup +def ast_parse_setup_py(path: str, raising: bool = True) -> "Dict[str, Any]": + return SetupReader.read_setup_py(Path(path), raising) def run_setup(script_path, egg_base=None): @@ -868,26 +905,16 @@ def run_setup(script_path, egg_base=None): script_name = os.path.basename(script_path) g = {"__file__": script_name, "__name__": "__main__"} sys.path.insert(0, target_cwd) - local_dict = {} - if sys.version_info < (3, 5): - save_argv = sys.argv - else: - save_argv = sys.argv.copy() + + save_argv = sys.argv.copy() try: global _setup_distribution, _setup_stop_after _setup_stop_after = "run" sys.argv[0] = script_name sys.argv[1:] = args with open(script_name, "rb") as f: - contents = f.read() - if six.PY3: - contents.replace(br"\r\n", br"\n") - else: - contents.replace(r"\r\n", r"\n") - if sys.version_info < (3, 5): - exec(contents, g, local_dict) - else: - exec(contents, g) + contents = f.read().replace(br"\r\n", br"\n") + exec(contents, g) # We couldn't import everything needed to run setup except Exception: python = os.environ.get("PIP_PYTHON_PATH", sys.executable) @@ -909,9 +936,9 @@ def run_setup(script_path, egg_base=None): @attr.s(slots=True, frozen=True) class BaseRequirement(object): - name = attr.ib(default="", cmp=True) # type: STRING_TYPE + name = attr.ib(default="", eq=True, order=True) # type: STRING_TYPE requirement = attr.ib( - default=None, cmp=True + default=None, eq=True, order=True ) # type: Optional[PkgResourcesRequirement] def __str__(self): @@ -951,8 +978,8 @@ class BaseRequirement(object): @attr.s(slots=True, frozen=True) class Extra(object): - name = attr.ib(default=None, cmp=True) # type: STRING_TYPE - requirements = attr.ib(factory=frozenset, cmp=True, type=frozenset) + name = attr.ib(default=None, eq=True, order=True) # type: STRING_TYPE + requirements = attr.ib(factory=frozenset, eq=True, order=True, type=frozenset) def __str__(self): # type: () -> S @@ -973,29 +1000,34 @@ class Extra(object): return {self.name: tuple([r.requirement for r in self.requirements])} -@attr.s(slots=True, cmp=True, hash=True) +@attr.s(slots=True, eq=True, hash=True) class SetupInfo(object): - name = attr.ib(default=None, cmp=True) # type: STRING_TYPE - base_dir = attr.ib(default=None, cmp=True, hash=False) # type: STRING_TYPE - _version = attr.ib(default=None, cmp=True) # type: STRING_TYPE + name = attr.ib(default=None, eq=True) # type: STRING_TYPE + base_dir = attr.ib(default=None, eq=True, hash=False) # type: STRING_TYPE + _version = attr.ib(default=None, eq=True) # type: STRING_TYPE _requirements = attr.ib( - type=frozenset, factory=frozenset, cmp=True, hash=True + type=frozenset, factory=frozenset, eq=True, hash=True ) # type: Optional[frozenset] - build_requires = attr.ib(default=None, cmp=True) # type: Optional[Tuple] - build_backend = attr.ib(cmp=True) # type: STRING_TYPE - setup_requires = attr.ib(default=None, cmp=True) # type: Optional[Tuple] + build_requires = attr.ib(default=None, eq=True) # type: Optional[Tuple] + build_backend = attr.ib(eq=True) # type: STRING_TYPE + setup_requires = attr.ib(default=None, eq=True) # type: Optional[Tuple] python_requires = attr.ib( - default=None, cmp=True + default=None, eq=True ) # type: Optional[packaging.specifiers.SpecifierSet] - _extras_requirements = attr.ib(default=None, cmp=True) # type: Optional[Tuple] - setup_cfg = attr.ib(type=Path, default=None, cmp=True, hash=False) - setup_py = attr.ib(type=Path, default=None, cmp=True, hash=False) - pyproject = attr.ib(type=Path, default=None, cmp=True, hash=False) + _extras_requirements = attr.ib(default=None, eq=True) # type: Optional[Tuple] + setup_cfg = attr.ib(type=Path, default=None, eq=True, hash=False) + setup_py = attr.ib(type=Path, default=None, eq=True, hash=False) + pyproject = attr.ib(type=Path, default=None, eq=True, hash=False) ireq = attr.ib( - default=None, cmp=True, hash=False + default=None, eq=True, hash=False ) # type: Optional[InstallRequirement] - extra_kwargs = attr.ib(default=attr.Factory(dict), type=dict, cmp=False, hash=False) + extra_kwargs = attr.ib(default=attr.Factory(dict), type=dict, eq=False, hash=False) metadata = attr.ib(default=None) # type: Optional[Tuple[STRING_TYPE]] + stack = attr.ib(default=None, eq=False) # type: Optional[ExitStack] + _finalizer = attr.ib(default=None, eq=False) # type: Any + + def __attrs_post_init__(self): + self._finalizer = finalize(self, self.stack.close) @build_backend.default def get_build_backend(self): @@ -1033,11 +1065,6 @@ class SetupInfo(object): self._version = info.get("version", None) return self._version - @classmethod - def get_setup_cfg(cls, setup_cfg_path): - # type: (S) -> Dict[S, Union[S, None, Set[BaseRequirement], List[S], Tuple[S, Tuple[BaseRequirement]]]] - return parse_setup_cfg(setup_cfg_path) - @property def egg_base(self): # type: () -> S @@ -1060,7 +1087,7 @@ class SetupInfo(object): def update_from_dict(self, metadata): name = metadata.get("name", self.name) - if isinstance(name, six.string_types): + if isinstance(name, str): self.name = self.name if self.name else name version = metadata.get("version", None) if version: @@ -1092,14 +1119,14 @@ class SetupInfo(object): self.python_requires = metadata.get("python_requires", self.python_requires) extras_require = metadata.get("extras_require", {}) extras_tuples = [] - for section in set(list(extras_require.keys())) - set(list(self.extras.keys())): + if self._extras_requirements is None: + self._extras_requirements = () + for section in set(extras_require) - {v[0] for v in self._extras_requirements}: extras = extras_require[section] extras_set = make_base_requirements(extras) if self.ireq and self.ireq.extras and section in self.ireq.extras: requirements |= extras_set extras_tuples.append((section, tuple(extras_set))) - if self._extras_requirements is None: - self._extras_requirements = () self._extras_requirements += tuple(extras_tuples) build_backend = metadata.get("build_backend", "setuptools.build_meta:__legacy__") if not self.build_backend: @@ -1120,7 +1147,10 @@ class SetupInfo(object): def parse_setup_cfg(self): # type: () -> Dict[STRING_TYPE, Any] if self.setup_cfg is not None and self.setup_cfg.exists(): - parsed = self.get_setup_cfg(self.setup_cfg.as_posix()) + try: + parsed = setuptools_parse_setup_cfg(self.setup_cfg.as_posix()) + except Exception: + parsed = parse_setup_cfg(self.setup_cfg.as_posix()) if not parsed: return {} return parsed @@ -1180,27 +1210,7 @@ class SetupInfo(object): def build_wheel(self): # type: () -> S - if not self.pyproject.exists(): - build_requires = ", ".join(['"{0}"'.format(r) for r in self.build_requires]) - self.pyproject.write_text( - u""" -[build-system] -requires = [{0}] -build-backend = "{1}" - """.format( - build_requires, self.build_backend - ).strip() - ) - return build_pep517( - self.base_dir, - self.extra_kwargs["build_dir"], - config_settings=self.pep517_config, - dist_type="wheel", - ) - - # noinspection PyPackageRequirements - def build_sdist(self): - # type: () -> S + need_delete = False if not self.pyproject.exists(): if not self.build_requires: build_requires = '"setuptools", "wheel"' @@ -1209,20 +1219,59 @@ build-backend = "{1}" ['"{0}"'.format(r) for r in self.build_requires] ) self.pyproject.write_text( - u""" + str( + """ [build-system] requires = [{0}] build-backend = "{1}" - """.format( - build_requires, self.build_backend - ).strip() + """.format( + build_requires, self.build_backend + ).strip() + ) ) - return build_pep517( + need_delete = True + result = build_pep517( + self.base_dir, + self.extra_kwargs["build_dir"], + config_settings=self.pep517_config, + dist_type="wheel", + ) + if need_delete: + self.pyproject.unlink() + return result + + # noinspection PyPackageRequirements + def build_sdist(self): + # type: () -> S + need_delete = False + if not self.pyproject.exists(): + if not self.build_requires: + build_requires = '"setuptools", "wheel"' + else: + build_requires = ", ".join( + ['"{0}"'.format(r) for r in self.build_requires] + ) + self.pyproject.write_text( + str( + """ +[build-system] +requires = [{0}] +build-backend = "{1}" + """.format( + build_requires, self.build_backend + ).strip() + ) + ) + need_delete = True + result = build_pep517( self.base_dir, self.extra_kwargs["build_dir"], config_settings=self.pep517_config, dist_type="sdist", ) + if need_delete: + self.pyproject.unlink() + return result def build(self): # type: () -> "SetupInfo" @@ -1255,8 +1304,8 @@ build-backend = "{1}" # type: () -> Dict[S, Any] """Wipe existing distribution info metadata for rebuilding. - Erases metadata from **self.egg_base** and unsets **self.requirements** - and **self.extras**. + Erases metadata from **self.egg_base** and unsets + **self.requirements** and **self.extras**. """ for metadata_dir in os.listdir(self.egg_base): shutil.rmtree(metadata_dir, ignore_errors=True) @@ -1278,7 +1327,8 @@ build-backend = "{1}" def get_egg_metadata(self, metadata_dir=None, metadata_type=None): # type: (Optional[AnyStr], Optional[AnyStr]) -> Dict[Any, Any] - """Given a metadata directory, return the corresponding metadata dictionary. + """Given a metadata directory, return the corresponding metadata + dictionary. :param Optional[str] metadata_dir: Root metadata path, default: `os.getcwd()` :param Optional[str] metadata_type: Type of metadata to search for, default None @@ -1325,7 +1375,12 @@ build-backend = "{1}" _metadata += (k, v) self.metadata = _metadata cleaned = metadata.copy() - cleaned.update({"install_requires": metadata.get("requires", [])}) + cleaned.update( + { + "install_requires": metadata.get("requires", []), + "extras_require": metadata.get("extras", {}), + } + ) if cleaned: self.update_from_dict(cleaned.copy()) else: @@ -1339,7 +1394,6 @@ build-backend = "{1}" :return: The current instance :rtype: `SetupInfo` """ - if self.pyproject and self.pyproject.exists(): result = get_pyproject(self.pyproject.parent) if result is not None: @@ -1360,32 +1414,36 @@ build-backend = "{1}" # type: () -> Dict[S, Any] parse_setupcfg = False parse_setuppy = False + self.run_pyproject() if self.setup_cfg and self.setup_cfg.exists(): parse_setupcfg = True if self.setup_py and self.setup_py.exists(): parse_setuppy = True - if parse_setuppy or parse_setupcfg: - with cd(self.base_dir): - if parse_setuppy: - self.update_from_dict(self.parse_setup_py()) - if parse_setupcfg: - self.update_from_dict(self.parse_setup_cfg()) - if self.name is not None and any( - [ - self.requires, - self.setup_requires, - self._extras_requirements, - self.build_backend, - ] - ): + if ( + self.build_backend.startswith("setuptools") + and parse_setuppy + or parse_setupcfg + ): + parsed = {} + try: + with cd(self.base_dir): + if parse_setuppy: + parsed.update(self.parse_setup_py()) + if parse_setupcfg: + parsed.update(self.parse_setup_cfg()) + except Unparsable: + pass + else: + self.update_from_dict(parsed) return self.as_dict() + return self.get_info() def get_info(self): # type: () -> Dict[S, Any] - with cd(self.base_dir): - self.run_pyproject() - self.build() + if self.metadata is None: + with cd(self.base_dir): + self.build() if self.setup_py and self.setup_py.exists() and self.metadata is None: if not self.requires or not self.name: @@ -1434,19 +1492,21 @@ build-backend = "{1}" @classmethod @lru_cache() - def from_ireq(cls, ireq, subdir=None, finder=None): - # type: (InstallRequirement, Optional[AnyStr], Optional[PackageFinder]) -> Optional[SetupInfo] + def from_ireq(cls, ireq, subdir=None, finder=None, session=None): + # type: (InstallRequirement, Optional[AnyStr], Optional[PackageFinder], Optional[requests.Session]) -> Optional[SetupInfo] import pip_shims.shims if not ireq.link: return None if ireq.link.is_wheel: return None - if not finder: - from .dependencies import get_finder - - finder = get_finder() - _, uri = split_vcs_method_from_uri(unquote(ireq.link.url_without_fragment)) + stack = ExitStack() + if not session: + cmd = pip_shims.shims.InstallCommand() + options, _ = cmd.parser.parse_args([]) + session = cmd._build_session(options) + stack.enter_context(pip_shims.shims.global_tempdir_manager()) + vcs, uri = split_vcs_method_from_uri(ireq.link.url_without_fragment) parsed = urlparse(uri) if "file" in parsed.scheme: url_path = parsed.path @@ -1455,17 +1515,19 @@ build-backend = "{1}" parsed = parsed._replace(path=url_path) uri = urlunparse(parsed) path = None + is_file = False if ireq.link.scheme == "file" or uri.startswith("file://"): + is_file = True if "file:/" in uri and "file:///" not in uri: uri = uri.replace("file:/", "file:///") path = pip_shims.shims.url_to_path(uri) kwargs = _prepare_wheel_building_kwargs(ireq) - ireq.source_dir = kwargs["src_dir"] - if not ( - ireq.editable - and pip_shims.shims.is_file_url(ireq.link) - and not ireq.link.is_artifact - ): + is_artifact_or_vcs = getattr( + ireq.link, "is_vcs", getattr(ireq.link, "is_artifact", False) + ) + is_vcs = True if vcs else is_artifact_or_vcs + + if not (ireq.editable and is_file and is_vcs): if ireq.is_wheel: only_download = True download_dir = kwargs["wheel_download_dir"] @@ -1476,26 +1538,41 @@ build-backend = "{1}" raise RequirementError( "The file URL points to a directory not installable: {}".format(ireq.link) ) - ireq.build_location(kwargs["build_dir"]) - src_dir = ireq.ensure_has_source_dir(kwargs["src_dir"]) - ireq._temp_build_dir.path = kwargs["build_dir"] - - ireq.populate_link(finder, False, False) - pip_shims.shims.unpack_url( - ireq.link, - src_dir, - download_dir, - only_download=only_download, - session=finder.session, - hashes=ireq.hashes(False), - progress_bar="off", + # this ensures the build dir is treated as the temporary build location + # and the source dir is treated as permanent / not deleted by pip + build_location_func = getattr(ireq, "build_location", None) + if build_location_func is None: + build_location_func = getattr(ireq, "ensure_build_location", None) + if not ireq.source_dir: + build_kwargs = { + "build_dir": kwargs["build_dir"], + "autodelete": False, + "parallel_builds": True, + } + call_function_with_correct_args(build_location_func, **build_kwargs) + ireq.ensure_has_source_dir(kwargs["src_dir"]) + pip_shims.shims.shim_unpack( + download_dir=download_dir, + ireq=ireq, + only_download=only_download, + session=session, + hashes=ireq.hashes(False), + ) + created = cls.create( + ireq.source_dir, subdirectory=subdir, ireq=ireq, kwargs=kwargs, stack=stack ) - created = cls.create(src_dir, subdirectory=subdir, ireq=ireq, kwargs=kwargs) return created @classmethod - def create(cls, base_dir, subdirectory=None, ireq=None, kwargs=None): - # type: (AnyStr, Optional[AnyStr], Optional[InstallRequirement], Optional[Dict[AnyStr, AnyStr]]) -> Optional[SetupInfo] + def create( + cls, + base_dir, # type: str + subdirectory=None, # type: Optional[str] + ireq=None, # type: Optional[InstallRequirement] + kwargs=None, # type: Optional[Dict[str, str]] + stack=None, # type: Optional[ExitStack] + ): + # type: (...) -> Optional[SetupInfo] if not base_dir or base_dir is None: return None @@ -1512,6 +1589,9 @@ build-backend = "{1}" creation_kwargs["pyproject"] = pyproject creation_kwargs["setup_py"] = setup_py creation_kwargs["setup_cfg"] = setup_cfg + if stack is None: + stack = ExitStack() + creation_kwargs["stack"] = stack if ireq: creation_kwargs["ireq"] = ireq created = cls(**creation_kwargs) diff --git a/pipenv/vendor/requirementslib/models/url.py b/pipenv/vendor/requirementslib/models/url.py index 889a4bdd..d2e90cd1 100644 --- a/pipenv/vendor/requirementslib/models/url.py +++ b/pipenv/vendor/requirementslib/models/url.py @@ -1,20 +1,24 @@ # -*- coding=utf-8 -*- from __future__ import absolute_import, print_function -import attr +from urllib.parse import quote +from urllib.parse import unquote as url_unquote +from urllib.parse import unquote_plus + +import pipenv.vendor.attr as attr import pip_shims.shims -from orderedmultidict import omdict -from six.moves.urllib.parse import quote_plus, unquote_plus -from urllib3 import util as urllib3_util -from urllib3.util import parse_url as urllib3_parse -from urllib3.util.url import Url +from pipenv.vendor.orderedmultidict import omdict +from pipenv.vendor.urllib3.util import parse_url as urllib3_parse +from pipenv.vendor.urllib3.util.url import Url from ..environment import MYPY_RUNNING +from ..utils import is_installable_file +from .utils import extras_to_string, parse_extras if MYPY_RUNNING: - from typing import List, Tuple, Text, Union, TypeVar, Optional - from pip_shims.shims import Link - from vistir.compat import Path + from typing import Dict, Optional, Text, Tuple, TypeVar, Union + + from pipenv.vendor.pip_shims.shims import Link _T = TypeVar("_T") STRING_TYPE = Union[bytes, str, Text] @@ -23,8 +27,7 @@ if MYPY_RUNNING: def _get_parsed_url(url): # type: (S) -> Url - """ - This is a stand-in function for `urllib3.util.parse_url` + """This is a stand-in function for `urllib3.util.parse_url` The orignal function doesn't handle special characters very well, this simply splits out the authentication section, creates the parsed url, then puts the authentication @@ -41,13 +44,14 @@ def _get_parsed_url(url): auth, _, url = url.rpartition("@") url = "{scheme}://{url}".format(scheme=scheme, url=url) parsed = urllib3_parse(url)._replace(auth=auth) + if parsed.auth: + return parsed._replace(auth=url_unquote(parsed.auth)) return parsed def remove_password_from_url(url): # type: (S) -> S - """ - Given a url, remove the password and insert 4 dashes + """Given a url, remove the password and insert 4 dashes. :param url: The url to replace the authentication in :type url: S @@ -62,7 +66,7 @@ def remove_password_from_url(url): return parsed.url -@attr.s +@attr.s(hash=True) class URI(object): #: The target hostname, e.g. `amazon.com` host = attr.ib(type=str) @@ -96,6 +100,8 @@ class URI(object): is_implicit_ssh = attr.ib(default=False, type=bool) _auth = attr.ib(default=None, type=str, repr=False) _fragment_dict = attr.ib(factory=dict, type=dict) + _username_is_quoted = attr.ib(type=bool, default=False) + _password_is_quoted = attr.ib(type=bool, default=False) def _parse_query(self): # type: () -> URI @@ -103,12 +109,18 @@ class URI(object): query_dict = omdict() queries = query.split("&") query_items = [] + subdirectory = self.subdirectory if self.subdirectory else None for q in queries: key, _, val = q.partition("=") - val = unquote_plus(val.replace("+", " ")) - query_items.append((key, val)) + val = unquote_plus(val) + if key == "subdirectory" and not subdirectory: + subdirectory = val + else: + query_items.append((key, val)) query_dict.load(query_items) - return attr.evolve(self, query_dict=query_dict, query=query) + return attr.evolve( + self, query_dict=query_dict, subdirectory=subdirectory, query=query + ) def _parse_fragment(self): # type: () -> URI @@ -122,7 +134,7 @@ class URI(object): extras = self.extras for q in fragments: key, _, val = q.partition("=") - val = unquote_plus(val.replace("+", " ")) + val = unquote_plus(val) fragment_items[key] = val if key == "egg": from .utils import parse_extras @@ -145,28 +157,65 @@ class URI(object): # type: () -> URI if self._auth: username, _, password = self._auth.partition(":") - password = quote_plus(password) - return attr.evolve(self, username=username, password=password) + username_is_quoted, password_is_quoted = False, False + quoted_username, quoted_password = "", "" + if password: + quoted_password = quote(password) + password_is_quoted = quoted_password != password + if username: + quoted_username = quote(username) + username_is_quoted = quoted_username != username + return attr.evolve( + self, + username=quoted_username, + password=quoted_password, + username_is_quoted=username_is_quoted, + password_is_quoted=password_is_quoted, + ) return self def get_password(self, unquote=False, include_token=True): # type: (bool, bool) -> str - password = self.password - if password and unquote: - password = unquote_plus(password) - else: - password = "" + password = self.password if self.password else "" + if password and unquote and self._password_is_quoted: + password = url_unquote(password) return password + def get_username(self, unquote=False): + # type: (bool) -> str + username = self.username if self.username else "" + if username and unquote and self._username_is_quoted: + username = url_unquote(username) + return username + @staticmethod def parse_subdirectory(url_part): # type: (str) -> Tuple[str, Optional[str]] subdir = None if "&subdirectory" in url_part: url_part, _, subdir = url_part.rpartition("&") - subdir = "&{0}".format(subdir.strip()) + if "#egg=" not in url_part: + subdir = "#{0}".format(subdir.strip()) + else: + subdir = "&{0}".format(subdir.strip()) return url_part.strip(), subdir + @classmethod + def get_parsed_url(cls, url): + # if there is a "#" in the auth section, this could break url parsing + parsed_url = _get_parsed_url(url) + if "@" in url and "#" in url: + scheme = "{0}://".format(parsed_url.scheme) + if parsed_url.scheme == "file": + scheme = "{0}/".format(scheme) + url_without_scheme = url.replace(scheme, "") + maybe_auth, _, maybe_url = url_without_scheme.partition("@") + if "#" in maybe_auth and (not parsed_url.host or "." not in parsed_url.host): + new_parsed_url = _get_parsed_url("{0}{1}".format(scheme, maybe_url)) + new_parsed_url = new_parsed_url._replace(auth=maybe_auth) + return new_parsed_url + return parsed_url + @classmethod def parse(cls, url): # type: (S) -> URI @@ -187,8 +236,9 @@ class URI(object): url, ref = split_ref_from_uri(url.strip()) if "file:/" in url and "file:///" not in url: url = url.replace("file:/", "file:///") - parsed = _get_parsed_url(url) - if not (parsed.scheme and parsed.host and parsed.path): + parsed = cls.get_parsed_url(url) + # if there is a "#" in the auth section, this could break url parsing + if not (parsed.scheme and parsed.host): # check if this is a file uri if not ( parsed.scheme @@ -199,24 +249,9 @@ class URI(object): parsed_dict = dict(parsed._asdict()).copy() parsed_dict["is_direct_url"] = is_direct_url parsed_dict["is_implicit_ssh"] = is_implicit_ssh - if name_with_extras: - fragment = "" - if parsed_dict["fragment"] is not None: - fragment = "{0}".format(parsed_dict["fragment"]) - if fragment.startswith("egg="): - name, extras = pip_shims.shims._strip_extras(name_with_extras) - fragment_name, fragment_extras = pip_shims.shims._strip_extras(fragment) - if fragment_extras and not extras: - name_with_extras = "{0}{1}".format(name, fragment_extras) - fragment = "" - elif "&subdirectory" in parsed_dict["path"]: - path, fragment = cls.parse_subdirectory(parsed_dict["path"]) - parsed_dict["path"] = path - elif ref is not None and "&subdirectory" in ref: - ref, fragment = cls.parse_subdirectory(ref) - parsed_dict["fragment"] = "egg={0}{1}".format(name_with_extras, fragment) - if ref is not None: - parsed_dict["ref"] = ref.strip() + parsed_dict.update( + **update_url_name_and_fragment(name_with_extras, ref, parsed_dict) + ) # type: ignore return cls(**parsed_dict)._parse_auth()._parse_query()._parse_fragment() def to_string( @@ -230,8 +265,8 @@ class URI(object): strip_subdir=False, # type: bool ): # type: (...) -> str - """ - Converts the current URI to a string, unquoting or escaping the password as needed + """Converts the current URI to a string, unquoting or escaping the + password as needed. :param escape_password: Whether to replace password with ``----``, default True :param escape_password: bool, optional @@ -249,21 +284,32 @@ class URI(object): if direct is None: direct = self.is_direct_url if escape_password: - password = "----" if (self.password or self.username) else "" + password = "----" if self.password else "" + if password: + username = self.get_username(unquote=unquote) + elif self.username: + username = "----" + else: + username = "" else: password = self.get_password(unquote=unquote) + username = self.get_username(unquote=unquote) auth = "" - if self.username: + if username: if password: - auth = "{self.username}:{password}@".format(password=password, self=self) + auth = "{username}:{password}@".format( + password=password, username=username + ) else: - auth = "{self.username}@".format(self=self) + auth = "{username}@".format(username=username) query = "" if self.query: query = "{query}?{self.query}".format(query=query, self=self) + subdir_prefix = "#" if not direct: if self.name and not strip_name: fragment = "#egg={self.name_with_extras}".format(self=self) + subdir_prefix = "&" elif not strip_name and ( self.extras and self.scheme and self.scheme.startswith("file") ): @@ -274,8 +320,8 @@ class URI(object): fragment = "" query = "{query}{fragment}".format(query=query, fragment=fragment) if self.subdirectory and not strip_subdir: - query = "{query}&subdirectory={self.subdirectory}".format( - query=query, self=self + query = "{query}{subdir_prefix}subdirectory={self.subdirectory}".format( + query=query, subdir_prefix=subdir_prefix, self=self ) host_port_path = self.get_host_port_path(strip_ref=strip_ref) url = "{self.scheme}://{auth}{host_port_path}{query}".format( @@ -292,13 +338,25 @@ class URI(object): def get_host_port_path(self, strip_ref=False): # type: (bool) -> str host = self.host if self.host else "" - if self.port: + if self.port is not None: host = "{host}:{self.port!s}".format(host=host, self=self) - path = "{self.path}".format(self=self) + path = "{self.path}".format(self=self) if self.path else "" if self.ref and not strip_ref: path = "{path}@{self.ref}".format(path=path, self=self) return "{host}{path}".format(host=host, path=path) + @property + def hidden_auth(self): + # type: () -> str + auth = "" + if self.username and self.password: + password = "****" + username = self.get_username(unquote=True) + auth = "{username}:{password}".format(username=username, password=password) + elif self.username and not self.password: + auth = "****" + return auth + @property def name_with_extras(self): # type: () -> str @@ -364,7 +422,10 @@ class URI(object): def base_url(self): # type: () -> str return self.to_string( - escape_password=False, strip_ssh=self.is_implicit_ssh, direct=False + escape_password=False, + strip_ssh=self.is_implicit_ssh, + direct=False, + unquote=False, ) @property @@ -372,6 +433,11 @@ class URI(object): # type: () -> str return self.to_string(escape_password=False, strip_ssh=False, direct=False) + @property + def secret(self): + # type: () -> str + return self.full_url + @property def safe_string(self): # type: () -> str @@ -387,6 +453,11 @@ class URI(object): # type: () -> str return self.to_string(escape_password=False, unquote=False) + @property + def is_installable(self): + # type: () -> bool + return self.is_file_url and is_installable_file(self.bare_url) + @property def is_vcs(self): # type: () -> bool @@ -402,3 +473,36 @@ class URI(object): def __str__(self): # type: () -> str return self.to_string(escape_password=True, unquote=True) + + +def update_url_name_and_fragment(name_with_extras, ref, parsed_dict): + # type: (Optional[str], Optional[str], Dict[str, Optional[str]]) -> Dict[str, Optional[str]] + if name_with_extras: + fragment = "" # type: Optional[str] + parsed_extras = () + name, extras = pip_shims.shims._strip_extras(name_with_extras) + if extras: + parsed_extras = parsed_extras + tuple(parse_extras(extras)) + if parsed_dict["fragment"] is not None: + fragment = "{0}".format(parsed_dict["fragment"]) + if fragment.startswith("egg="): + _, _, fragment_part = fragment.partition("=") + fragment_name, fragment_extras = pip_shims.shims._strip_extras( + fragment_part + ) + name = name if name else fragment_name + if fragment_extras: + parsed_extras = parsed_extras + tuple(parse_extras(fragment_extras)) + name_with_extras = "{0}{1}".format(name, extras_to_string(parsed_extras)) + elif ( + parsed_dict.get("path") is not None and "&subdirectory" in parsed_dict["path"] + ): + path, fragment = URI.parse_subdirectory(parsed_dict["path"]) # type: ignore + parsed_dict["path"] = path + elif ref is not None and "&subdirectory" in ref: + ref, fragment = URI.parse_subdirectory(ref) + parsed_dict["name"] = name + parsed_dict["extras"] = parsed_extras + if ref: + parsed_dict["ref"] = ref.strip() + return parsed_dict diff --git a/pipenv/vendor/requirementslib/models/utils.py b/pipenv/vendor/requirementslib/models/utils.py index e79ea7e9..5e116599 100644 --- a/pipenv/vendor/requirementslib/models/utils.py +++ b/pipenv/vendor/requirementslib/models/utils.py @@ -7,55 +7,52 @@ import re import string import sys from collections import defaultdict +from functools import lru_cache from itertools import chain, groupby -from operator import attrgetter +from pathlib import Path -import six -import tomlkit -from attr import validators -from first import first -from packaging.markers import InvalidMarker, Marker, Op, Value, Variable -from packaging.specifiers import InvalidSpecifier, Specifier, SpecifierSet -from packaging.version import parse as parse_version -from plette.models import Package, PackageCollection -from six.moves.urllib import parse as urllib_parse -from tomlkit.container import Container -from tomlkit.items import AoT, Array, Bool, InlineTable, Item, String, Table -from urllib3 import util as urllib3_util -from vistir.compat import lru_cache -from vistir.misc import dedup -from vistir.path import is_valid_url +import pipenv.vendor.tomlkit as tomlkit +from pipenv.vendor.attr import validators +from pipenv.vendor.packaging.markers import InvalidMarker, Marker, Op, Value, Variable +from pipenv.vendor.packaging.specifiers import InvalidSpecifier, Specifier, SpecifierSet +from pipenv.vendor.packaging.version import parse as parse_version +from pipenv.vendor.plette.models import Package, PackageCollection +from pipenv.vendor.tomlkit.container import Container +from pipenv.vendor.tomlkit.items import AoT, Array, Bool, InlineTable, Item, String, Table +from pipenv.vendor.urllib3 import util as urllib3_util +from pipenv.vendor.urllib3.util import parse_url as urllib3_parse +from pipenv.vendor.vistir.misc import dedup +from pipenv.vendor.vistir.path import is_valid_url from ..environment import MYPY_RUNNING from ..utils import SCHEME_LIST, VCS_LIST, is_star if MYPY_RUNNING: + from typing import Iterable # noqa from typing import ( - Union, - Optional, - List, - Set, Any, - TypeVar, - Tuple, - Sequence, - Dict, - Text, AnyStr, + Dict, + List, Match, - Iterable, # noqa + Optional, + Sequence, + Set, + Text, + Tuple, + TypeVar, + Union, ) - from attr import _ValidatorType # noqa - from packaging.requirements import Requirement as PackagingRequirement + + from pipenv.vendor.attr import _ValidatorType # noqa + from pipenv.vendor.packaging.markers import Marker as PkgResourcesMarker + from pipenv.vendor.packaging.markers import Op as PkgResourcesOp + from pipenv.vendor.packaging.markers import Value as PkgResourcesValue + from pipenv.vendor.packaging.markers import Variable as PkgResourcesVariable + from pipenv.vendor.packaging.requirements import Requirement as PackagingRequirement + from pipenv.vendor.pip_shims.shims import Link from pkg_resources import Requirement as PkgResourcesRequirement - from pkg_resources.extern.packaging.markers import ( - Op as PkgResourcesOp, - Variable as PkgResourcesVariable, - Value as PkgResourcesValue, - Marker as PkgResourcesMarker, - ) - from pip_shims.shims import Link - from vistir.compat import Path + from pipenv.vendor.urllib3.util.url import Url _T = TypeVar("_T") TMarker = Union[Marker, PkgResourcesMarker] @@ -103,6 +100,11 @@ def filter_none(k, v): return False +def filter_dict(dict_): + # type: (Dict[AnyStr, Any]) -> Dict[AnyStr, Any] + return {k: v for k, v in dict_.items() if filter_none(k, v)} + + def optional_instance_of(cls): # type: (Any) -> _ValidatorType[Optional[_T]] return validators.optional(validators.instance_of(cls)) @@ -111,9 +113,9 @@ def optional_instance_of(cls): def create_link(link): # type: (AnyStr) -> Link - if not isinstance(link, six.string_types): + if not isinstance(link, str): raise TypeError("must provide a string to instantiate a new link") - from pip_shims.shims import Link + from pipenv.vendor.pip_shims.shims import Link # noqa: F811 return Link(link) @@ -174,14 +176,13 @@ def tomlkit_dict_to_python(toml_dict): def get_url_name(url): # type: (AnyStr) -> AnyStr - """ - Given a url, derive an appropriate name to use in a pipfile. + """Given a url, derive an appropriate name to use in a pipfile. :param str url: A url to derive a string from :returns: The name of the corresponding pipfile entry :rtype: Text """ - if not isinstance(url, six.string_types): + if not isinstance(url, str): raise TypeError("Expected a string, got {0!r}".format(url)) return urllib3_util.parse_url(url).host @@ -189,7 +190,7 @@ def get_url_name(url): def init_requirement(name): # type: (AnyStr) -> TRequirement - if not isinstance(name, six.string_types): + if not isinstance(name, str): raise TypeError("must supply a name to generate a requirement") from pkg_resources import Requirement @@ -203,13 +204,13 @@ def init_requirement(name): def extras_to_string(extras): # type: (Iterable[S]) -> S - """Turn a list of extras into a string + """Turn a list of extras into a string. :param List[str]] extras: a list of extras to format :return: A string of extras :rtype: str """ - if isinstance(extras, six.string_types): + if isinstance(extras, str): if extras.startswith("["): return extras else: @@ -221,7 +222,7 @@ def extras_to_string(extras): def parse_extras(extras_str): # type: (AnyStr) -> List[AnyStr] - """Turn a string of extras into a parsed extras list + """Turn a string of extras into a parsed extras list. :param str extras_str: An extras string :return: A sorted list of extras @@ -236,7 +237,7 @@ def parse_extras(extras_str): def specs_to_string(specs): # type: (List[Union[STRING_TYPE, Specifier]]) -> AnyStr - """Turn a list of specifier tuples into a string + """Turn a list of specifier tuples into a string. :param List[Union[Specifier, str]] specs: a list of specifiers to format :return: A string of specifiers @@ -244,7 +245,7 @@ def specs_to_string(specs): """ if specs: - if isinstance(specs, six.string_types): + if isinstance(specs, str): return specs try: extras = ",".join(["".join(spec) for spec in specs]) @@ -282,9 +283,31 @@ def build_vcs_uri( return uri +def _get_parsed_url(url): + # type: (S) -> Url + """This is a stand-in function for `urllib3.util.parse_url` + + The orignal function doesn't handle special characters very well, this simply splits + out the authentication section, creates the parsed url, then puts the authentication + section back in, bypassing validation. + + :return: The new, parsed URL object + :rtype: :class:`~urllib3.util.url.Url` + """ + + try: + parsed = urllib3_parse(url) + except ValueError: + scheme, _, url = url.partition("://") + auth, _, url = url.rpartition("@") + url = "{scheme}://{url}".format(scheme=scheme, url=url) + parsed = urllib3_parse(url)._replace(auth=auth) + return parsed + + def convert_direct_url_to_url(direct_url): # type: (AnyStr) -> AnyStr - """Converts direct URLs to standard, link-style URLs + """Converts direct URLs to standard, link-style URLs. Given a direct url as defined by *PEP 508*, convert to a :class:`~pip_shims.shims.Link` compatible URL by moving the name and extras into an **egg_fragment**. @@ -325,8 +348,7 @@ def convert_direct_url_to_url(direct_url): def convert_url_to_direct_url(url, name=None): # type: (AnyStr, Optional[AnyStr]) -> AnyStr - """ - Converts normal link-style URLs to direct urls. + """Converts normal link-style URLs to direct urls. Given a :class:`~pip_shims.shims.Link` compatible URL, convert to a direct url as defined by *PEP 508* by extracting the name and extras from the **egg_fragment**. @@ -339,7 +361,7 @@ def convert_url_to_direct_url(url, name=None): :raises ValueError: Raised when the URL can't be parsed or a name can't be found. :raises TypeError: When a non-string input is provided. """ - if not isinstance(url, six.string_types): + if not isinstance(url, str): raise TypeError( "Expected a string to convert to a direct url, got {0!r}".format(url) ) @@ -383,15 +405,14 @@ def get_version(pipfile_entry): return "" return pipfile_entry.get("version", "").strip().lstrip("(").rstrip(")") - if isinstance(pipfile_entry, six.string_types): + if isinstance(pipfile_entry, str): return pipfile_entry.strip().lstrip("(").rstrip(")") return "" def strip_extras_markers_from_requirement(req): # type: (TRequirement) -> TRequirement - """ - Strips extras markers from requirement instances. + """Strips extras markers from requirement instances. Given a :class:`~packaging.requirements.Requirement` instance with markers defining *extra == 'name'*, strip out the extras from the markers and return the cleaned @@ -458,9 +479,8 @@ def get_default_pyproject_backend(): def get_pyproject(path): # type: (Union[STRING_TYPE, Path]) -> Optional[Tuple[List[STRING_TYPE], STRING_TYPE]] - """ - Given a base path, look for the corresponding ``pyproject.toml`` file and return its - build_requires and build_backend. + """Given a base path, look for the corresponding ``pyproject.toml`` file + and return its build_requires and build_backend. :param AnyStr path: The root path of the project, should be a directory (will be truncated) :return: A 2 tuple of build requirements and the build backend @@ -468,7 +488,6 @@ def get_pyproject(path): """ if not path: return - from vistir.compat import Path if not isinstance(path, Path): path = Path(path) @@ -483,7 +502,7 @@ def get_pyproject(path): backend = get_default_pyproject_backend() else: pyproject_data = {} - with io.open(pp_toml.as_posix(), encoding="utf-8") as fh: + with open(pp_toml.as_posix(), encoding="utf-8") as fh: pyproject_data = tomlkit.loads(fh.read()) build_system = pyproject_data.get("build-system", None) if build_system is None: @@ -503,7 +522,7 @@ def get_pyproject(path): def split_markers_from_line(line): # type: (AnyStr) -> Tuple[AnyStr, Optional[AnyStr]] - """Split markers from a dependency""" + """Split markers from a dependency.""" quote_chars = ["'", '"'] line_quote = next( iter(quote for quote in quote_chars if line.startswith(quote)), None @@ -525,8 +544,9 @@ def split_vcs_method_from_uri(uri): # type: (AnyStr) -> Tuple[Optional[STRING_TYPE], STRING_TYPE] """Split a vcs+uri formatted uri into (vcs, uri)""" vcs_start = "{0}+" - vcs = None # type: Optional[STRING_TYPE] - vcs = first([vcs for vcs in VCS_LIST if uri.startswith(vcs_start.format(vcs))]) + vcs = next( + iter([vcs for vcs in VCS_LIST if uri.startswith(vcs_start.format(vcs))]), None + ) if vcs: vcs, uri = uri.split("+", 1) return vcs, uri @@ -534,23 +554,23 @@ def split_vcs_method_from_uri(uri): def split_ref_from_uri(uri): # type: (AnyStr) -> Tuple[AnyStr, Optional[AnyStr]] - """ - Given a path or URI, check for a ref and split it from the path if it is present, - returning a tuple of the original input and the ref or None. + """Given a path or URI, check for a ref and split it from the path if it is + present, returning a tuple of the original input and the ref or None. :param AnyStr uri: The path or URI to split :returns: A 2-tuple of the path or URI and the ref :rtype: Tuple[AnyStr, Optional[AnyStr]] """ - if not isinstance(uri, six.string_types): + if not isinstance(uri, str): raise TypeError("Expected a string, received {0!r}".format(uri)) - parsed = urllib_parse.urlparse(uri) - path = parsed.path + parsed = _get_parsed_url(uri) + path = parsed.path if parsed.path else "" + scheme = parsed.scheme if parsed.scheme else "" ref = None - if parsed.scheme != "file" and "@" in path: + if scheme != "file" and "@" in path: path, _, ref = path.rpartition("@") parsed = parsed._replace(path=path) - return (urllib_parse.urlunparse(parsed), ref) + return (parsed.url, ref) def validate_vcs(instance, attr_, value): @@ -694,7 +714,7 @@ def get_pinned_version(ireq): except AttributeError: raise TypeError("Expected InstallRequirement, not {}".format(type(ireq).__name__)) - if ireq.editable: + if getattr(ireq, "editable", False): raise ValueError("InstallRequirement is editable") if not specifier: raise ValueError("InstallRequirement has no version specification") @@ -709,8 +729,7 @@ def get_pinned_version(ireq): def is_pinned_requirement(ireq): - """ - Returns whether an InstallRequirement is a "pinned" requirement. + """Returns whether an InstallRequirement is a "pinned" requirement. An InstallRequirement is considered pinned if: @@ -734,38 +753,32 @@ def is_pinned_requirement(ireq): def as_tuple(ireq): - """ - Pulls out the (name: str, version:str, extras:(str)) tuple from the pinned InstallRequirement. - """ + """Pulls out the (name: str, version:str, extras:(str)) tuple from the + pinned InstallRequirement.""" if not is_pinned_requirement(ireq): raise TypeError("Expected a pinned InstallRequirement, got {}".format(ireq)) name = key_from_req(ireq.req) - version = first(ireq.specifier._specs)._spec[1] + version = next(iter(ireq.specifier._specs))._spec[1] extras = tuple(sorted(ireq.extras)) return name, version, extras def full_groupby(iterable, key=None): - """ - Like groupby(), but sorts the input on the group key first. - """ + """Like groupby(), but sorts the input on the group key first.""" return groupby(sorted(iterable, key=key), key=key) def flat_map(fn, collection): - """ - Map a function over a collection and flatten the result by one-level - """ + """Map a function over a collection and flatten the result by one-level.""" return chain.from_iterable(map(fn, collection)) def lookup_table(values, key=None, keyval=None, unique=False, use_lists=False): - """ - Builds a dict-based lookup table (index) elegantly. + """Builds a dict-based lookup table (index) elegantly. Supports building normal and unique lookup tables. For example: @@ -826,7 +839,7 @@ def lookup_table(values, key=None, keyval=None, unique=False, use_lists=False): def name_from_req(req): - """Get the name of the requirement""" + """Get the name of the requirement.""" if hasattr(req, "project_name"): # from pkg_resources, such as installed dists for pip-sync return req.project_name @@ -838,8 +851,7 @@ def name_from_req(req): def make_install_requirement( name, version=None, extras=None, markers=None, constraint=False ): - """ - Generates an :class:`~pip._internal.req.req_install.InstallRequirement`. + """Generates an :class:`~pipenv.patched.notpip._internal.req.req_install.InstallRequirement`. Create an InstallRequirement from the supplied metadata. @@ -854,11 +866,11 @@ def make_install_requirement( :param constraint: Whether to flag the requirement as a constraint, defaults to False. :param constraint: bool, optional :return: A generated InstallRequirement - :rtype: :class:`~pip._internal.req.req_install.InstallRequirement` + :rtype: :class:`~pipenv.patched.notpip._internal.req.req_install.InstallRequirement` """ # If no extras are specified, the extras string is blank - from pip_shims.shims import install_req_from_line + from pipenv.vendor.pip_shims.shims import install_req_from_line extras_string = "" requirement_string = "{0}".format(name) @@ -874,27 +886,37 @@ def make_install_requirement( def version_from_ireq(ireq): - """version_from_ireq Extract the version from a supplied :class:`~pip._internal.req.req_install.InstallRequirement` + """version_from_ireq Extract the version from a supplied + :class:`~pipenv.patched.notpip._internal.req.req_install.InstallRequirement` :param ireq: An InstallRequirement - :type ireq: :class:`~pip._internal.req.req_install.InstallRequirement` + :type ireq: :class:`~pipenv.patched.notpip._internal.req.req_install.InstallRequirement` :return: The version of the InstallRequirement. :rtype: str """ - return first(ireq.specifier._specs).version + return next(iter(ireq.specifier._specs)).version + + +def _get_requires_python(candidate): + # type: (Any) -> str + requires_python = getattr(candidate, "requires_python", None) + if requires_python is not None: + link = getattr(candidate, "location", getattr(candidate, "link", None)) + requires_python = getattr(link, "requires_python", None) + return requires_python def clean_requires_python(candidates): - """Get a cleaned list of all the candidates with valid specifiers in the `requires_python` attributes.""" + """Get a cleaned list of all the candidates with valid specifiers in the + `requires_python` attributes.""" all_candidates = [] sys_version = ".".join(map(str, sys.version_info[:3])) - from packaging.version import parse as parse_version + from pipenv.vendor.packaging.version import parse as parse_version py_version = parse_version(os.environ.get("PIP_PYTHON_VERSION", sys_version)) for c in candidates: - from_location = attrgetter("location.requires_python") - requires_python = getattr(c, "requires_python", from_location(c)) + requires_python = _get_requires_python(c) if requires_python: # Old specifications had people setting this to single digits # which is effectively the same as '>=digit,<digit+1' @@ -914,7 +936,7 @@ def clean_requires_python(candidates): def fix_requires_python_marker(requires_python): - from packaging.requirements import Requirement as PackagingRequirement + from pipenv.vendor.packaging.requirements import Requirement as PackagingRequirement marker_str = "" if any(requires_python.startswith(op) for op in Specifier._operators.keys()): @@ -948,25 +970,24 @@ def normalize_name(pkg): :rtype: AnyStr """ - assert isinstance(pkg, six.string_types) + assert isinstance(pkg, str) return pkg.replace("_", "-").lower() def get_name_variants(pkg): # type: (STRING_TYPE) -> Set[STRING_TYPE] - """ - Given a packager name, get the variants of its name for both the canonicalized - and "safe" forms. + """Given a packager name, get the variants of its name for both the + canonicalized and "safe" forms. :param AnyStr pkg: The package to lookup :returns: A list of names. :rtype: Set """ - if not isinstance(pkg, six.string_types): + if not isinstance(pkg, str): raise TypeError("must provide a string to derive package names") + from pipenv.vendor.packaging.utils import canonicalize_name from pkg_resources import safe_name - from packaging.utils import canonicalize_name pkg = pkg.lower() names = {safe_name(pkg), canonicalize_name(pkg), pkg.replace("-", "_")} @@ -975,20 +996,33 @@ def get_name_variants(pkg): def read_source(path, encoding="utf-8"): # type: (S, S) -> S - """ - Read a source file and get the contents with proper encoding for Python 2/3. + """Read a source file and get the contents with proper encoding for Python + 2/3. :param AnyStr path: the file path :param AnyStr encoding: the encoding that defaults to UTF-8 :returns: The contents of the source file :rtype: AnyStr """ - if six.PY3: - with open(path, "r", encoding=encoding) as fp: - return fp.read() - else: - with open(path, "r") as fp: - return fp.read() + with open(path, "r", encoding=encoding) as fp: + return fp.read() + + +def expand_env_variables(line): + # type: (AnyStr) -> AnyStr + """Expand the env vars in a line following pip's standard. + https://pip.pypa.io/en/stable/reference/pip_install/#id10. + + Matches environment variable-style values in '${MY_VARIABLE_1}' with + the variable name consisting of only uppercase letters, digits or + the '_' + """ + + def replace_with_env(match): + value = os.getenv(match.group(1)) + return value if value else match.group() + + return re.sub(r"\$\{([A-Z0-9_]+)\}", replace_with_env, line) SETUPTOOLS_SHIM = ( diff --git a/pipenv/vendor/requirementslib/models/vcs.py b/pipenv/vendor/requirementslib/models/vcs.py index 9296f605..5933623d 100644 --- a/pipenv/vendor/requirementslib/models/vcs.py +++ b/pipenv/vendor/requirementslib/models/vcs.py @@ -1,55 +1,79 @@ # -*- coding=utf-8 -*- from __future__ import absolute_import, print_function -import attr import importlib import os -import pip_shims -import six import sys +import pipenv.vendor.attr as attr +import pipenv.vendor.pip_shims as pip_shims + +from ..environment import MYPY_RUNNING +from .url import URI + +if MYPY_RUNNING: + from typing import Any, Optional, Tuple + @attr.s(hash=True) class VCSRepository(object): DEFAULT_RUN_ARGS = None - url = attr.ib() - name = attr.ib() - checkout_directory = attr.ib() - vcs_type = attr.ib() - subdirectory = attr.ib(default=None) - commit_sha = attr.ib(default=None) - ref = attr.ib(default=None) - repo_instance = attr.ib() - clone_log = attr.ib(default=None) + url = attr.ib() # type: str + name = attr.ib() # type: str + checkout_directory = attr.ib() # type: str + vcs_type = attr.ib() # type: str + parsed_url = attr.ib() # type: URI + subdirectory = attr.ib(default=None) # type: Optional[str] + commit_sha = attr.ib(default=None) # type: Optional[str] + ref = attr.ib(default=None) # type: Optional[str] + repo_backend = attr.ib() # type: Any + clone_log = attr.ib(default=None) # type: Optional[str] - @repo_instance.default - def get_repo_instance(self): + @parsed_url.default + def get_parsed_url(self): + # type: () -> URI + return URI.parse(self.url) + + @repo_backend.default + def get_repo_backend(self): if self.DEFAULT_RUN_ARGS is None: default_run_args = self.monkeypatch_pip() else: default_run_args = self.DEFAULT_RUN_ARGS - from pip_shims.shims import VcsSupport + from pipenv.vendor.pip_shims.shims import VcsSupport + VCS_SUPPORT = VcsSupport() - backend = VCS_SUPPORT._registry.get(self.vcs_type) - repo = backend(url=self.url) - if repo.run_command.__func__.__defaults__ != default_run_args: - repo.run_command.__func__.__defaults__ = default_run_args - return repo + backend = VCS_SUPPORT.get_backend(self.vcs_type) + # repo = backend(url=self.url) + if backend.run_command.__func__.__defaults__ != default_run_args: + backend.run_command.__func__.__defaults__ = default_run_args + return backend @property def is_local(self): + # type: () -> bool url = self.url - if '+' in url: - url = url.split('+')[1] + if "+" in url: + url = url.split("+")[1] return url.startswith("file") def obtain(self): - if (os.path.exists(self.checkout_directory) and not - self.repo_instance.is_repository_directory(self.checkout_directory)): - self.repo_instance.unpack(self.checkout_directory) + # type: () -> None + lt_pip_19_2 = ( + pip_shims.parsed_pip_version.parsed_version < pip_shims.parse_version("19.2") + ) + if lt_pip_19_2: + self.repo_backend = self.repo_backend(self.url) + if os.path.exists( + self.checkout_directory + ) and not self.repo_backend.is_repository_directory(self.checkout_directory): + self.repo_backend.unpack(self.checkout_directory) elif not os.path.exists(self.checkout_directory): - self.repo_instance.obtain(self.checkout_directory) + if lt_pip_19_2: + self.repo_backend.obtain(self.checkout_directory) + else: + self.repo_backend.obtain(self.checkout_directory, self.parsed_url) else: if self.ref: self.checkout_ref(self.ref) @@ -57,38 +81,53 @@ class VCSRepository(object): self.commit_sha = self.get_commit_hash() def checkout_ref(self, ref): - if not self.repo_instance.is_commit_id_equal( - self.checkout_directory, self.get_commit_hash() - ) and not self.repo_instance.is_commit_id_equal(self.checkout_directory, ref): - if not self.is_local: - self.update(ref) + # type: (str) -> None + rev_opts = self.repo_backend.make_rev_options(ref) + if not any( + [ + self.repo_backend.is_commit_id_equal(self.checkout_directory, ref), + self.repo_backend.is_commit_id_equal(self.checkout_directory, rev_opts), + self.is_local, + ] + ): + self.update(ref) def update(self, ref): - target_ref = self.repo_instance.make_rev_options(ref) - if pip_shims.parse_version(pip_shims.pip_version) > pip_shims.parse_version("18.0"): - self.repo_instance.update(self.checkout_directory, self.url, target_ref) + # type: (str) -> None + target_ref = self.repo_backend.make_rev_options(ref) + if pip_shims.parse_version(pip_shims.pip_version) > pip_shims.parse_version( + "18.0" + ): + self.repo_backend.update(self.checkout_directory, self.url, target_ref) else: - self.repo_instance.update(self.checkout_directory, target_ref) + self.repo_backend.update(self.checkout_directory, target_ref) self.commit_sha = self.get_commit_hash() def get_commit_hash(self, ref=None): - return self.repo_instance.get_revision(self.checkout_directory) + # type: (Optional[str]) -> str + with pip_shims.shims.global_tempdir_manager(): + return self.repo_backend.get_revision(self.checkout_directory) @classmethod def monkeypatch_pip(cls): + # type: () -> Tuple[Any, ...] + from pipenv.vendor.pip_shims.compat import get_allowed_args + target_module = pip_shims.shims.VcsSupport.__module__ pip_vcs = importlib.import_module(target_module) + args, kwargs = get_allowed_args(pip_vcs.VersionControl.run_command) run_command_defaults = pip_vcs.VersionControl.run_command.__defaults__ - # set the default to not write stdout, the first option sets this value - new_defaults = [False,] + list(run_command_defaults)[1:] - new_defaults = tuple(new_defaults) - if six.PY3: - try: - pip_vcs.VersionControl.run_command.__defaults__ = new_defaults - except AttributeError: - pip_vcs.VersionControl.run_command.__func__.__defaults__ = new_defaults + if "show_stdout" not in args and "show_stdout" not in kwargs: + new_defaults = run_command_defaults else: + # set the default to not write stdout, the first option sets this value + new_defaults = [False] + list(run_command_defaults)[1:] + new_defaults = tuple(new_defaults) + try: + pip_vcs.VersionControl.run_command.__defaults__ = new_defaults + except AttributeError: pip_vcs.VersionControl.run_command.__func__.__defaults__ = new_defaults + sys.modules[target_module] = pip_vcs cls.DEFAULT_RUN_ARGS = new_defaults return new_defaults diff --git a/pipenv/vendor/requirementslib/utils.py b/pipenv/vendor/requirementslib/utils.py index 503a13d0..9048a8ef 100644 --- a/pipenv/vendor/requirementslib/utils.py +++ b/pipenv/vendor/requirementslib/utils.py @@ -4,37 +4,20 @@ from __future__ import absolute_import, print_function import logging import os import sys +from collections.abc import ItemsView, Mapping, Sequence, Set +from pathlib import Path +from urllib.parse import urlparse, urlsplit, urlunparse import pip_shims.shims -import six -import six.moves -import tomlkit -import vistir -from six.moves.urllib.parse import urlparse, urlsplit, urlunparse -from vistir.compat import Path -from vistir.path import ensure_mkdir_p, is_valid_url +import pipenv.vendor.tomlkit as tomlkit +import pipenv.vendor.vistir as vistir +from pipenv.vendor.vistir.compat import fs_decode +from pipenv.vendor.vistir.path import ensure_mkdir_p, is_valid_url from .environment import MYPY_RUNNING -# fmt: off -six.add_move( # type: ignore - six.MovedAttribute("Mapping", "collections", "collections.abc") # type: ignore -) # noqa # isort:skip -six.add_move( # type: ignore - six.MovedAttribute("Sequence", "collections", "collections.abc") # type: ignore -) # noqa # isort:skip -six.add_move( # type: ignore - six.MovedAttribute("Set", "collections", "collections.abc") # type: ignore -) # noqa # isort:skip -six.add_move( # type: ignore - six.MovedAttribute("ItemsView", "collections", "collections.abc") # type: ignore -) # noqa -from six.moves import ItemsView, Mapping, Sequence, Set # type: ignore # noqa # isort:skip -# fmt: on - - if MYPY_RUNNING: - from typing import Dict, Any, Optional, Union, Tuple, List, Iterable, Text, TypeVar + from typing import Any, Dict, Iterable, List, Optional, Text, Tuple, TypeVar, Union STRING_TYPE = Union[bytes, str, Text] S = TypeVar("S", bytes, str, Text) @@ -104,8 +87,8 @@ def is_installable_dir(path): def strip_ssh_from_git_uri(uri): # type: (S) -> S - """Return git+ssh:// formatted URI to git+git@ format""" - if isinstance(uri, six.string_types): + """Return git+ssh:// formatted URI to git+git@ format.""" + if isinstance(uri, str): if "git+ssh://" in uri: parsed = urlparse(uri) # split the path on the first separating / so we can put the first segment @@ -121,8 +104,8 @@ def strip_ssh_from_git_uri(uri): def add_ssh_scheme_to_git_uri(uri): # type: (S) -> S - """Cleans VCS uris from pip format""" - if isinstance(uri, six.string_types): + """Cleans VCS uris from pip format.""" + if isinstance(uri, str): # Add scheme for parsing purposes, this is also what pip does if uri.startswith("git+") and "://" not in uri: uri = uri.replace("git+", "git+ssh://", 1) @@ -140,7 +123,7 @@ def is_vcs(pipfile_entry): if isinstance(pipfile_entry, Mapping): return any(key for key in pipfile_entry.keys() if key in VCS_LIST) - elif isinstance(pipfile_entry, six.string_types): + elif isinstance(pipfile_entry, str): if not is_valid_url(pipfile_entry) and pipfile_entry.startswith("git+"): pipfile_entry = add_ssh_scheme_to_git_uri(pipfile_entry) @@ -153,21 +136,21 @@ def is_editable(pipfile_entry): # type: (PipfileType) -> bool if isinstance(pipfile_entry, Mapping): return pipfile_entry.get("editable", False) is True - if isinstance(pipfile_entry, six.string_types): + if isinstance(pipfile_entry, str): return pipfile_entry.startswith("-e ") return False def is_star(val): # type: (PipfileType) -> bool - return (isinstance(val, six.string_types) and val == "*") or ( + return (isinstance(val, str) and val == "*") or ( isinstance(val, Mapping) and val.get("version", "") == "*" ) def convert_entry_to_path(path): # type: (Dict[S, Union[S, bool, Tuple[S], List[S]]]) -> S - """Convert a pipfile entry to a string""" + """Convert a pipfile entry to a string.""" if not isinstance(path, Mapping): raise TypeError("expecting a mapping, received {0!r}".format(path)) @@ -180,13 +163,15 @@ def convert_entry_to_path(path): elif "path" in path: path = path["path"] - return path + if not os.name == "nt": + return fs_decode(path) + return Path(fs_decode(path)).as_posix() def is_installable_file(path): # type: (PipfileType) -> bool - """Determine if a path can potentially be installed""" - from packaging import specifiers + """Determine if a path can potentially be installed.""" + from pipenv.vendor.packaging import specifiers if isinstance(path, Mapping): path = convert_entry_to_path(path) @@ -209,7 +194,7 @@ def is_installable_file(path): or (len(parsed.scheme) == 1 and os.name == "nt") ) if parsed.scheme and parsed.scheme == "file": - path = vistir.compat.fs_decode(vistir.path.url_to_path(path)) + path = fs_decode(vistir.path.url_to_path(path)) normalized_path = vistir.path.normalize_path(path) if is_local and not os.path.exists(normalized_path): return False @@ -228,9 +213,10 @@ def is_installable_file(path): def get_dist_metadata(dist): - import pkg_resources from email.parser import FeedParser + import pkg_resources + if isinstance(dist, pkg_resources.DistInfoDistribution) and dist.has_metadata( "METADATA" ): @@ -341,10 +327,8 @@ _REMAP_EXIT = object() class PathAccessError(KeyError, IndexError, TypeError): - """An amalgamation of KeyError, IndexError, and TypeError, - representing what can occur when looking up a path in a nested - object. - """ + """An amalgamation of KeyError, IndexError, and TypeError, representing + what can occur when looking up a path in a nested object.""" def __init__(self, exc, seg, path): self.exc = exc @@ -366,6 +350,7 @@ class PathAccessError(KeyError, IndexError, TypeError): def get_path(root, path, default=_UNSET): """Retrieve a value from a nested object via a tuple representing the lookup path. + >>> root = {'a': {'b': {'c': [[1], [2], [3]]}}} >>> get_path(root, ('a', 'b', 'c', 2, 0)) 3 @@ -389,7 +374,7 @@ def get_path(root, path, default=_UNSET): default: The value to be returned should any ``PathAccessError`` exceptions be raised. """ - if isinstance(path, six.string_types): + if isinstance(path, str): path = path.split(".") cur = root try: @@ -424,8 +409,12 @@ _orig_default_visit = default_visit # Modified from https://github.com/mahmoud/boltons/blob/master/boltons/iterutils.py def dict_path_enter(path, key, value): - if isinstance(value, six.string_types): + if isinstance(value, str): return value, False + elif isinstance(value, (tomlkit.items.Table, tomlkit.items.InlineTable)): + return value.__class__( + tomlkit.container.Container(), value.trivia, False + ), ItemsView(value) elif isinstance(value, (Mapping, dict)): return value.__class__(), ItemsView(value) elif isinstance(value, tomlkit.items.Array): @@ -477,16 +466,16 @@ def dict_path_exit(path, key, old_parent, new_parent, new_items): def remap( root, visit=default_visit, enter=dict_path_enter, exit=dict_path_exit, **kwargs ): - """The remap ("recursive map") function is used to traverse and - transform nested structures. Lists, tuples, sets, and dictionaries - are just a few of the data structures nested into heterogenous - tree-like structures that are so common in programming. - Unfortunately, Python's built-in ways to manipulate collections - are almost all flat. List comprehensions may be fast and succinct, - but they do not recurse, making it tedious to apply quick changes - or complex transforms to real-world data. - remap goes where list comprehensions cannot. - Here's an example of removing all Nones from some data: + """The remap ("recursive map") function is used to traverse and transform + nested structures. Lists, tuples, sets, and dictionaries are just a few of + the data structures nested into heterogenous tree-like structures that are + so common in programming. Unfortunately, Python's built-in ways to + manipulate collections are almost all flat. List comprehensions may be fast + and succinct, but they do not recurse, making it tedious to apply quick + changes or complex transforms to real-world data. remap goes where list + comprehensions cannot. Here's an example of removing all Nones from some + data: + >>> from pprint import pprint >>> reviews = {'Star Trek': {'TNG': 10, 'DS9': 8.5, 'ENT': None}, ... 'Babylon 5': 6, 'Dr. Who': None} @@ -642,7 +631,7 @@ def merge_items(target_list, sourced=False): try: cur_val = get_path(ret, path + (key,)) - except KeyError as ke: + except KeyError: pass else: new_parent = cur_val diff --git a/pipenv/vendor/resolvelib/__init__.py b/pipenv/vendor/resolvelib/__init__.py deleted file mode 100644 index e0e37434..00000000 --- a/pipenv/vendor/resolvelib/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -__all__ = [ - '__version__', - 'AbstractProvider', 'BaseReporter', 'Resolver', - 'NoVersionsAvailable', 'RequirementsConflicted', - 'ResolutionError', 'ResolutionImpossible', 'ResolutionTooDeep', -] - -__version__ = '0.2.2' - - -from .providers import AbstractProvider -from .reporters import BaseReporter -from .resolvers import ( - NoVersionsAvailable, RequirementsConflicted, - Resolver, ResolutionError, ResolutionImpossible, ResolutionTooDeep, -) diff --git a/pipenv/vendor/resolvelib/providers.py b/pipenv/vendor/resolvelib/providers.py deleted file mode 100644 index 515c0db4..00000000 --- a/pipenv/vendor/resolvelib/providers.py +++ /dev/null @@ -1,78 +0,0 @@ -class AbstractProvider(object): - """Delegate class to provide requirment interface for the resolver. - """ - def identify(self, dependency): - """Given a dependency, return an identifier for it. - - This is used in many places to identify the dependency, e.g. whether - two requirements should have their specifier parts merged, whether - two specifications would conflict with each other (because they the - same name but different versions). - """ - raise NotImplementedError - - def get_preference(self, resolution, candidates, information): - """Produce a sort key for given specification based on preference. - - The preference is defined as "I think this requirement should be - resolved first". The lower the return value is, the more preferred - this group of arguments is. - - :param resolution: Currently pinned candidate, or `None`. - :param candidates: A list of possible candidates. - :param information: A list of requirement information. - - Each information instance is a named tuple with two entries: - - * `requirement` specifies a requirement contributing to the current - candidate list - * `parent` specifies the candidate that provids (dependend on) the - requirement, or `None` to indicate a root requirement. - - The preference could depend on a various of issues, including (not - necessarily in this order): - - * Is this package pinned in the current resolution result? - * How relaxed is the requirement? Stricter ones should probably be - worked on first? (I don't know, actually.) - * How many possibilities are there to satisfy this requirement? Those - with few left should likely be worked on first, I guess? - * Are there any known conflicts for this requirement? We should - probably work on those with the most known conflicts. - - A sortable value should be returned (this will be used as the `key` - parameter of the built-in sorting function). The smaller the value is, - the more preferred this specification is (i.e. the sorting function - is called with `reverse=False`). - """ - raise NotImplementedError - - def find_matches(self, requirement): - """Find all possible candidates that satisfy a requirement. - - This should try to get candidates based on the requirement's type. - For VCS, local, and archive requirements, the one-and-only match is - returned, and for a "named" requirement, the index(es) should be - consulted to find concrete candidates for this requirement. - - The returned candidates should be sorted by reversed preference, e.g. - the latest should be LAST. This is done so list-popping can be as - efficient as possible. - """ - raise NotImplementedError - - def is_satisfied_by(self, requirement, candidate): - """Whether the given requirement can be satisfied by a candidate. - - A boolean should be retuened to indicate whether `candidate` is a - viable solution to the requirement. - """ - raise NotImplementedError - - def get_dependencies(self, candidate): - """Get dependencies of a candidate. - - This should return a collection of requirements that `candidate` - specifies as its dependencies. - """ - raise NotImplementedError diff --git a/pipenv/vendor/resolvelib/reporters.py b/pipenv/vendor/resolvelib/reporters.py deleted file mode 100644 index c723031f..00000000 --- a/pipenv/vendor/resolvelib/reporters.py +++ /dev/null @@ -1,23 +0,0 @@ -class BaseReporter(object): - """Delegate class to provider progress reporting for the resolver. - """ - def starting(self): - """Called before the resolution actually starts. - """ - - def starting_round(self, index): - """Called before each round of resolution starts. - - The index is zero-based. - """ - - def ending_round(self, index, state): - """Called before each round of resolution ends. - - This is NOT called if the resolution ends at this round. Use `ending` - if you want to report finalization. The index is zero-based. - """ - - def ending(self, state): - """Called before the resolution ends successfully. - """ diff --git a/pipenv/vendor/resolvelib/resolvers.py b/pipenv/vendor/resolvelib/resolvers.py deleted file mode 100644 index 9c69628e..00000000 --- a/pipenv/vendor/resolvelib/resolvers.py +++ /dev/null @@ -1,287 +0,0 @@ -import collections - -from .structs import DirectedGraph - - -RequirementInformation = collections.namedtuple('RequirementInformation', [ - 'requirement', 'parent', -]) - - -class NoVersionsAvailable(Exception): - def __init__(self, requirement, parent): - super(NoVersionsAvailable, self).__init__() - self.requirement = requirement - self.parent = parent - - -class RequirementsConflicted(Exception): - def __init__(self, criterion): - super(RequirementsConflicted, self).__init__() - self.criterion = criterion - - -class Criterion(object): - """Internal representation of possible resolution results of a package. - - This holds two attributes: - - * `information` is a collection of `RequirementInformation` pairs. Each - pair is a requirement contributing to this criterion, and the candidate - that provides the requirement. - * `candidates` is a collection containing all possible candidates deducted - from the union of contributing requirements. It should never be empty. - """ - def __init__(self, candidates, information): - self.candidates = candidates - self.information = information - - @classmethod - def from_requirement(cls, provider, requirement, parent): - """Build an instance from a requirement. - """ - candidates = provider.find_matches(requirement) - if not candidates: - raise NoVersionsAvailable(requirement, parent) - return cls( - candidates=candidates, - information=[RequirementInformation(requirement, parent)], - ) - - def iter_requirement(self): - return (i.requirement for i in self.information) - - def iter_parent(self): - return (i.parent for i in self.information) - - def merged_with(self, provider, requirement, parent): - """Build a new instance from this and a new requirement. - """ - infos = list(self.information) - infos.append(RequirementInformation(requirement, parent)) - candidates = [ - c for c in self.candidates - if provider.is_satisfied_by(requirement, c) - ] - if not candidates: - raise RequirementsConflicted(self) - return type(self)(candidates, infos) - - -class ResolutionError(Exception): - pass - - -class ResolutionImpossible(ResolutionError): - def __init__(self, requirements): - super(ResolutionImpossible, self).__init__() - self.requirements = requirements - - -class ResolutionTooDeep(ResolutionError): - def __init__(self, round_count): - super(ResolutionTooDeep, self).__init__(round_count) - self.round_count = round_count - - -# Resolution state in a round. -State = collections.namedtuple('State', 'mapping graph') - - -class Resolution(object): - """Stateful resolution object. - - This is designed as a one-off object that holds information to kick start - the resolution process, and holds the results afterwards. - """ - def __init__(self, provider, reporter): - self._p = provider - self._r = reporter - self._criteria = {} - self._states = [] - - @property - def state(self): - try: - return self._states[-1] - except IndexError: - raise AttributeError('state') - - def _push_new_state(self): - """Push a new state into history. - - This new state will be used to hold resolution results of the next - coming round. - """ - try: - base = self._states[-1] - except IndexError: - graph = DirectedGraph() - graph.add(None) # Sentinel as root dependencies' parent. - state = State(mapping={}, graph=graph) - else: - state = State( - mapping=base.mapping.copy(), - graph=base.graph.copy(), - ) - self._states.append(state) - - def _contribute_to_criteria(self, name, requirement, parent): - try: - crit = self._criteria[name] - except KeyError: - crit = Criterion.from_requirement(self._p, requirement, parent) - else: - crit = crit.merged_with(self._p, requirement, parent) - self._criteria[name] = crit - - def _get_criterion_item_preference(self, item): - name, criterion = item - try: - pinned = self.state.mapping[name] - except (IndexError, KeyError): - pinned = None - return self._p.get_preference( - pinned, criterion.candidates, criterion.information, - ) - - def _is_current_pin_satisfying(self, name, criterion): - try: - current_pin = self.state.mapping[name] - except KeyError: - return False - return all( - self._p.is_satisfied_by(r, current_pin) - for r in criterion.iter_requirement() - ) - - def _check_pinnability(self, candidate, dependencies): - backup = self._criteria.copy() - contributed = set() - try: - for subdep in dependencies: - key = self._p.identify(subdep) - self._contribute_to_criteria(key, subdep, parent=candidate) - contributed.add(key) - except RequirementsConflicted: - self._criteria = backup - return None - return contributed - - def _pin_candidate(self, name, criterion, candidate, child_names): - try: - self.state.graph.remove(name) - except KeyError: - pass - self.state.mapping[name] = candidate - self.state.graph.add(name) - for parent in criterion.iter_parent(): - parent_name = None if parent is None else self._p.identify(parent) - try: - self.state.graph.connect(parent_name, name) - except KeyError: - # Parent is not yet pinned. Skip now; this edge will be - # connected when the parent is being pinned. - pass - for child_name in child_names: - try: - self.state.graph.connect(name, child_name) - except KeyError: - # Child is not yet pinned. Skip now; this edge will be - # connected when the child is being pinned. - pass - - def _pin_criteria(self): - criterion_names = [name for name, _ in sorted( - self._criteria.items(), - key=self._get_criterion_item_preference, - )] - for name in criterion_names: - # Any pin may modify any criterion during the loop. Criteria are - # replaced, not updated in-place, so we need to read this value - # in the loop instead of outside. (sarugaku/resolvelib#5) - criterion = self._criteria[name] - - if self._is_current_pin_satisfying(name, criterion): - # If the current pin already works, just use it. - continue - candidates = list(criterion.candidates) - while candidates: - candidate = candidates.pop() - dependencies = self._p.get_dependencies(candidate) - child_names = self._check_pinnability(candidate, dependencies) - if child_names is None: - continue - self._pin_candidate(name, criterion, candidate, child_names) - break - else: # All candidates tried, nothing works. Give up. (?) - raise ResolutionImpossible(list(criterion.iter_requirement())) - - def resolve(self, requirements, max_rounds): - if self._states: - raise RuntimeError('already resolved') - - for requirement in requirements: - try: - name = self._p.identify(requirement) - self._contribute_to_criteria(name, requirement, parent=None) - except RequirementsConflicted as e: - # If initial requirements conflict, nothing would ever work. - raise ResolutionImpossible(e.requirements + [requirement]) - - last = None - self._r.starting() - - for round_index in range(max_rounds): - self._r.starting_round(round_index) - - self._push_new_state() - self._pin_criteria() - - curr = self.state - if last is not None and len(curr.mapping) == len(last.mapping): - # Nothing new added. Done! Remove the duplicated entry. - del self._states[-1] - self._r.ending(last) - return - last = curr - - self._r.ending_round(round_index, curr) - - raise ResolutionTooDeep(max_rounds) - - -class Resolver(object): - """The thing that performs the actual resolution work. - """ - def __init__(self, provider, reporter): - self.provider = provider - self.reporter = reporter - - def resolve(self, requirements, max_rounds=20): - """Take a collection of constraints, spit out the resolution result. - - The return value is a representation to the final resolution result. It - is a tuple subclass with two public members: - - * `mapping`: A dict of resolved candidates. Each key is an identifier - of a requirement (as returned by the provider's `identify` method), - and the value is the resolved candidate. - * `graph`: A `DirectedGraph` instance representing the dependency tree. - The vertices are keys of `mapping`, and each edge represents *why* - a particular package is included. A special vertex `None` is - included to represent parents of user-supplied requirements. - - The following exceptions may be raised if a resolution cannot be found: - - * `NoVersionsAvailable`: A requirement has no available candidates. - * `ResolutionImpossible`: A resolution cannot be found for the given - combination of requirements. - * `ResolutionTooDeep`: The dependency tree is too deeply nested and - the resolver gave up. This is usually caused by a circular - dependency, but you can try to resolve this by increasing the - `max_rounds` argument. - """ - resolution = Resolution(self.provider, self.reporter) - resolution.resolve(requirements, max_rounds=max_rounds) - return resolution.state diff --git a/pipenv/vendor/resolvelib/structs.py b/pipenv/vendor/resolvelib/structs.py deleted file mode 100644 index 97bd0095..00000000 --- a/pipenv/vendor/resolvelib/structs.py +++ /dev/null @@ -1,67 +0,0 @@ -class DirectedGraph(object): - """A graph structure with directed edges. - """ - def __init__(self): - self._vertices = set() - self._forwards = {} # <key> -> Set[<key>] - self._backwards = {} # <key> -> Set[<key>] - - def __iter__(self): - return iter(self._vertices) - - def __len__(self): - return len(self._vertices) - - def __contains__(self, key): - return key in self._vertices - - def copy(self): - """Return a shallow copy of this graph. - """ - other = DirectedGraph() - other._vertices = set(self._vertices) - other._forwards = {k: set(v) for k, v in self._forwards.items()} - other._backwards = {k: set(v) for k, v in self._backwards.items()} - return other - - def add(self, key): - """Add a new vertex to the graph. - """ - if key in self._vertices: - raise ValueError('vertex exists') - self._vertices.add(key) - self._forwards[key] = set() - self._backwards[key] = set() - - def remove(self, key): - """Remove a vertex from the graph, disconnecting all edges from/to it. - """ - self._vertices.remove(key) - for f in self._forwards.pop(key): - self._backwards[f].remove(key) - for t in self._backwards.pop(key): - self._forwards[t].remove(key) - - def connected(self, f, t): - return f in self._backwards[t] and t in self._forwards[f] - - def connect(self, f, t): - """Connect two existing vertices. - - Nothing happens if the vertices are already connected. - """ - if t not in self._vertices: - raise KeyError(t) - self._forwards[f].add(t) - self._backwards[t].add(f) - - def iter_edges(self): - for f, children in self._forwards.items(): - for t in children: - yield f, t - - def iter_children(self, key): - return iter(self._forwards[key]) - - def iter_parents(self, key): - return iter(self._backwards[key]) diff --git a/pipenv/vendor/scandir.py b/pipenv/vendor/scandir.py deleted file mode 100644 index 44f949fd..00000000 --- a/pipenv/vendor/scandir.py +++ /dev/null @@ -1,690 +0,0 @@ -"""scandir, a better directory iterator and faster os.walk(), now in the Python 3.5 stdlib - -scandir() is a generator version of os.listdir() that returns an -iterator over files in a directory, and also exposes the extra -information most OSes provide while iterating files in a directory -(such as type and stat information). - -This module also includes a version of os.walk() that uses scandir() -to speed it up significantly. - -See README.md or https://github.com/benhoyt/scandir for rationale and -docs, or read PEP 471 (https://www.python.org/dev/peps/pep-0471/) for -more details on its inclusion into Python 3.5 - -scandir is released under the new BSD 3-clause license. See -LICENSE.txt for the full license text. -""" - -from __future__ import division - -from errno import ENOENT -from os import listdir, lstat, stat, strerror -from os.path import join, islink -from stat import S_IFDIR, S_IFLNK, S_IFREG -import collections -import sys - -_scandir = None - -try: - import ctypes -except ImportError: - ctypes = None - -if _scandir is None and ctypes is None: - import warnings - warnings.warn("scandir can't find the compiled _scandir C module " - "or ctypes, using slow generic fallback") - -__version__ = '1.10.0' -__all__ = ['scandir', 'walk'] - -# Windows FILE_ATTRIBUTE constants for interpreting the -# FIND_DATA.dwFileAttributes member -FILE_ATTRIBUTE_ARCHIVE = 32 -FILE_ATTRIBUTE_COMPRESSED = 2048 -FILE_ATTRIBUTE_DEVICE = 64 -FILE_ATTRIBUTE_DIRECTORY = 16 -FILE_ATTRIBUTE_ENCRYPTED = 16384 -FILE_ATTRIBUTE_HIDDEN = 2 -FILE_ATTRIBUTE_INTEGRITY_STREAM = 32768 -FILE_ATTRIBUTE_NORMAL = 128 -FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 8192 -FILE_ATTRIBUTE_NO_SCRUB_DATA = 131072 -FILE_ATTRIBUTE_OFFLINE = 4096 -FILE_ATTRIBUTE_READONLY = 1 -FILE_ATTRIBUTE_REPARSE_POINT = 1024 -FILE_ATTRIBUTE_SPARSE_FILE = 512 -FILE_ATTRIBUTE_SYSTEM = 4 -FILE_ATTRIBUTE_TEMPORARY = 256 -FILE_ATTRIBUTE_VIRTUAL = 65536 - -IS_PY3 = sys.version_info >= (3, 0) - -if IS_PY3: - unicode = str # Because Python <= 3.2 doesn't have u'unicode' syntax - - -class GenericDirEntry(object): - __slots__ = ('name', '_stat', '_lstat', '_scandir_path', '_path') - - def __init__(self, scandir_path, name): - self._scandir_path = scandir_path - self.name = name - self._stat = None - self._lstat = None - self._path = None - - @property - def path(self): - if self._path is None: - self._path = join(self._scandir_path, self.name) - return self._path - - def stat(self, follow_symlinks=True): - if follow_symlinks: - if self._stat is None: - self._stat = stat(self.path) - return self._stat - else: - if self._lstat is None: - self._lstat = lstat(self.path) - return self._lstat - - # The code duplication below is intentional: this is for slightly - # better performance on systems that fall back to GenericDirEntry. - # It avoids an additional attribute lookup and method call, which - # are relatively slow on CPython. - def is_dir(self, follow_symlinks=True): - try: - st = self.stat(follow_symlinks=follow_symlinks) - except OSError as e: - if e.errno != ENOENT: - raise - return False # Path doesn't exist or is a broken symlink - return st.st_mode & 0o170000 == S_IFDIR - - def is_file(self, follow_symlinks=True): - try: - st = self.stat(follow_symlinks=follow_symlinks) - except OSError as e: - if e.errno != ENOENT: - raise - return False # Path doesn't exist or is a broken symlink - return st.st_mode & 0o170000 == S_IFREG - - def is_symlink(self): - try: - st = self.stat(follow_symlinks=False) - except OSError as e: - if e.errno != ENOENT: - raise - return False # Path doesn't exist or is a broken symlink - return st.st_mode & 0o170000 == S_IFLNK - - def inode(self): - st = self.stat(follow_symlinks=False) - return st.st_ino - - def __str__(self): - return '<{0}: {1!r}>'.format(self.__class__.__name__, self.name) - - __repr__ = __str__ - - -def _scandir_generic(path=unicode('.')): - """Like os.listdir(), but yield DirEntry objects instead of returning - a list of names. - """ - for name in listdir(path): - yield GenericDirEntry(path, name) - - -if IS_PY3 and sys.platform == 'win32': - def scandir_generic(path=unicode('.')): - if isinstance(path, bytes): - raise TypeError("os.scandir() doesn't support bytes path on Windows, use Unicode instead") - return _scandir_generic(path) - scandir_generic.__doc__ = _scandir_generic.__doc__ -else: - scandir_generic = _scandir_generic - - -scandir_c = None -scandir_python = None - - -if sys.platform == 'win32': - if ctypes is not None: - from ctypes import wintypes - - # Various constants from windows.h - INVALID_HANDLE_VALUE = ctypes.c_void_p(-1).value - ERROR_FILE_NOT_FOUND = 2 - ERROR_NO_MORE_FILES = 18 - IO_REPARSE_TAG_SYMLINK = 0xA000000C - - # Numer of seconds between 1601-01-01 and 1970-01-01 - SECONDS_BETWEEN_EPOCHS = 11644473600 - - kernel32 = ctypes.windll.kernel32 - - # ctypes wrappers for (wide string versions of) FindFirstFile, - # FindNextFile, and FindClose - FindFirstFile = kernel32.FindFirstFileW - FindFirstFile.argtypes = [ - wintypes.LPCWSTR, - ctypes.POINTER(wintypes.WIN32_FIND_DATAW), - ] - FindFirstFile.restype = wintypes.HANDLE - - FindNextFile = kernel32.FindNextFileW - FindNextFile.argtypes = [ - wintypes.HANDLE, - ctypes.POINTER(wintypes.WIN32_FIND_DATAW), - ] - FindNextFile.restype = wintypes.BOOL - - FindClose = kernel32.FindClose - FindClose.argtypes = [wintypes.HANDLE] - FindClose.restype = wintypes.BOOL - - Win32StatResult = collections.namedtuple('Win32StatResult', [ - 'st_mode', - 'st_ino', - 'st_dev', - 'st_nlink', - 'st_uid', - 'st_gid', - 'st_size', - 'st_atime', - 'st_mtime', - 'st_ctime', - 'st_atime_ns', - 'st_mtime_ns', - 'st_ctime_ns', - 'st_file_attributes', - ]) - - def filetime_to_time(filetime): - """Convert Win32 FILETIME to time since Unix epoch in seconds.""" - total = filetime.dwHighDateTime << 32 | filetime.dwLowDateTime - return total / 10000000 - SECONDS_BETWEEN_EPOCHS - - def find_data_to_stat(data): - """Convert Win32 FIND_DATA struct to stat_result.""" - # First convert Win32 dwFileAttributes to st_mode - attributes = data.dwFileAttributes - st_mode = 0 - if attributes & FILE_ATTRIBUTE_DIRECTORY: - st_mode |= S_IFDIR | 0o111 - else: - st_mode |= S_IFREG - if attributes & FILE_ATTRIBUTE_READONLY: - st_mode |= 0o444 - else: - st_mode |= 0o666 - if (attributes & FILE_ATTRIBUTE_REPARSE_POINT and - data.dwReserved0 == IO_REPARSE_TAG_SYMLINK): - st_mode ^= st_mode & 0o170000 - st_mode |= S_IFLNK - - st_size = data.nFileSizeHigh << 32 | data.nFileSizeLow - st_atime = filetime_to_time(data.ftLastAccessTime) - st_mtime = filetime_to_time(data.ftLastWriteTime) - st_ctime = filetime_to_time(data.ftCreationTime) - - # Some fields set to zero per CPython's posixmodule.c: st_ino, st_dev, - # st_nlink, st_uid, st_gid - return Win32StatResult(st_mode, 0, 0, 0, 0, 0, st_size, - st_atime, st_mtime, st_ctime, - int(st_atime * 1000000000), - int(st_mtime * 1000000000), - int(st_ctime * 1000000000), - attributes) - - class Win32DirEntryPython(object): - __slots__ = ('name', '_stat', '_lstat', '_find_data', '_scandir_path', '_path', '_inode') - - def __init__(self, scandir_path, name, find_data): - self._scandir_path = scandir_path - self.name = name - self._stat = None - self._lstat = None - self._find_data = find_data - self._path = None - self._inode = None - - @property - def path(self): - if self._path is None: - self._path = join(self._scandir_path, self.name) - return self._path - - def stat(self, follow_symlinks=True): - if follow_symlinks: - if self._stat is None: - if self.is_symlink(): - # It's a symlink, call link-following stat() - self._stat = stat(self.path) - else: - # Not a symlink, stat is same as lstat value - if self._lstat is None: - self._lstat = find_data_to_stat(self._find_data) - self._stat = self._lstat - return self._stat - else: - if self._lstat is None: - # Lazily convert to stat object, because it's slow - # in Python, and often we only need is_dir() etc - self._lstat = find_data_to_stat(self._find_data) - return self._lstat - - def is_dir(self, follow_symlinks=True): - is_symlink = self.is_symlink() - if follow_symlinks and is_symlink: - try: - return self.stat().st_mode & 0o170000 == S_IFDIR - except OSError as e: - if e.errno != ENOENT: - raise - return False - elif is_symlink: - return False - else: - return (self._find_data.dwFileAttributes & - FILE_ATTRIBUTE_DIRECTORY != 0) - - def is_file(self, follow_symlinks=True): - is_symlink = self.is_symlink() - if follow_symlinks and is_symlink: - try: - return self.stat().st_mode & 0o170000 == S_IFREG - except OSError as e: - if e.errno != ENOENT: - raise - return False - elif is_symlink: - return False - else: - return (self._find_data.dwFileAttributes & - FILE_ATTRIBUTE_DIRECTORY == 0) - - def is_symlink(self): - return (self._find_data.dwFileAttributes & - FILE_ATTRIBUTE_REPARSE_POINT != 0 and - self._find_data.dwReserved0 == IO_REPARSE_TAG_SYMLINK) - - def inode(self): - if self._inode is None: - self._inode = lstat(self.path).st_ino - return self._inode - - def __str__(self): - return '<{0}: {1!r}>'.format(self.__class__.__name__, self.name) - - __repr__ = __str__ - - def win_error(error, filename): - exc = WindowsError(error, ctypes.FormatError(error)) - exc.filename = filename - return exc - - def _scandir_python(path=unicode('.')): - """Like os.listdir(), but yield DirEntry objects instead of returning - a list of names. - """ - # Call FindFirstFile and handle errors - if isinstance(path, bytes): - is_bytes = True - filename = join(path.decode('mbcs', 'strict'), '*.*') - else: - is_bytes = False - filename = join(path, '*.*') - data = wintypes.WIN32_FIND_DATAW() - data_p = ctypes.byref(data) - handle = FindFirstFile(filename, data_p) - if handle == INVALID_HANDLE_VALUE: - error = ctypes.GetLastError() - if error == ERROR_FILE_NOT_FOUND: - # No files, don't yield anything - return - raise win_error(error, path) - - # Call FindNextFile in a loop, stopping when no more files - try: - while True: - # Skip '.' and '..' (current and parent directory), but - # otherwise yield (filename, stat_result) tuple - name = data.cFileName - if name not in ('.', '..'): - if is_bytes: - name = name.encode('mbcs', 'replace') - yield Win32DirEntryPython(path, name, data) - - data = wintypes.WIN32_FIND_DATAW() - data_p = ctypes.byref(data) - success = FindNextFile(handle, data_p) - if not success: - error = ctypes.GetLastError() - if error == ERROR_NO_MORE_FILES: - break - raise win_error(error, path) - finally: - if not FindClose(handle): - raise win_error(ctypes.GetLastError(), path) - - if IS_PY3: - def scandir_python(path=unicode('.')): - if isinstance(path, bytes): - raise TypeError("os.scandir() doesn't support bytes path on Windows, use Unicode instead") - return _scandir_python(path) - scandir_python.__doc__ = _scandir_python.__doc__ - else: - scandir_python = _scandir_python - - if _scandir is not None: - scandir_c = _scandir.scandir - DirEntry_c = _scandir.DirEntry - - if _scandir is not None: - scandir = scandir_c - DirEntry = DirEntry_c - elif ctypes is not None: - scandir = scandir_python - DirEntry = Win32DirEntryPython - else: - scandir = scandir_generic - DirEntry = GenericDirEntry - - -# Linux, OS X, and BSD implementation -elif sys.platform.startswith(('linux', 'darwin', 'sunos5')) or 'bsd' in sys.platform: - have_dirent_d_type = (sys.platform != 'sunos5') - - if ctypes is not None and have_dirent_d_type: - import ctypes.util - - DIR_p = ctypes.c_void_p - - # Rather annoying how the dirent struct is slightly different on each - # platform. The only fields we care about are d_name and d_type. - class Dirent(ctypes.Structure): - if sys.platform.startswith('linux'): - _fields_ = ( - ('d_ino', ctypes.c_ulong), - ('d_off', ctypes.c_long), - ('d_reclen', ctypes.c_ushort), - ('d_type', ctypes.c_byte), - ('d_name', ctypes.c_char * 256), - ) - elif 'openbsd' in sys.platform: - _fields_ = ( - ('d_ino', ctypes.c_uint64), - ('d_off', ctypes.c_uint64), - ('d_reclen', ctypes.c_uint16), - ('d_type', ctypes.c_uint8), - ('d_namlen', ctypes.c_uint8), - ('__d_padding', ctypes.c_uint8 * 4), - ('d_name', ctypes.c_char * 256), - ) - else: - _fields_ = ( - ('d_ino', ctypes.c_uint32), # must be uint32, not ulong - ('d_reclen', ctypes.c_ushort), - ('d_type', ctypes.c_byte), - ('d_namlen', ctypes.c_byte), - ('d_name', ctypes.c_char * 256), - ) - - DT_UNKNOWN = 0 - DT_DIR = 4 - DT_REG = 8 - DT_LNK = 10 - - Dirent_p = ctypes.POINTER(Dirent) - Dirent_pp = ctypes.POINTER(Dirent_p) - - libc = ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True) - opendir = libc.opendir - opendir.argtypes = [ctypes.c_char_p] - opendir.restype = DIR_p - - readdir_r = libc.readdir_r - readdir_r.argtypes = [DIR_p, Dirent_p, Dirent_pp] - readdir_r.restype = ctypes.c_int - - closedir = libc.closedir - closedir.argtypes = [DIR_p] - closedir.restype = ctypes.c_int - - file_system_encoding = sys.getfilesystemencoding() - - class PosixDirEntry(object): - __slots__ = ('name', '_d_type', '_stat', '_lstat', '_scandir_path', '_path', '_inode') - - def __init__(self, scandir_path, name, d_type, inode): - self._scandir_path = scandir_path - self.name = name - self._d_type = d_type - self._inode = inode - self._stat = None - self._lstat = None - self._path = None - - @property - def path(self): - if self._path is None: - self._path = join(self._scandir_path, self.name) - return self._path - - def stat(self, follow_symlinks=True): - if follow_symlinks: - if self._stat is None: - if self.is_symlink(): - self._stat = stat(self.path) - else: - if self._lstat is None: - self._lstat = lstat(self.path) - self._stat = self._lstat - return self._stat - else: - if self._lstat is None: - self._lstat = lstat(self.path) - return self._lstat - - def is_dir(self, follow_symlinks=True): - if (self._d_type == DT_UNKNOWN or - (follow_symlinks and self.is_symlink())): - try: - st = self.stat(follow_symlinks=follow_symlinks) - except OSError as e: - if e.errno != ENOENT: - raise - return False - return st.st_mode & 0o170000 == S_IFDIR - else: - return self._d_type == DT_DIR - - def is_file(self, follow_symlinks=True): - if (self._d_type == DT_UNKNOWN or - (follow_symlinks and self.is_symlink())): - try: - st = self.stat(follow_symlinks=follow_symlinks) - except OSError as e: - if e.errno != ENOENT: - raise - return False - return st.st_mode & 0o170000 == S_IFREG - else: - return self._d_type == DT_REG - - def is_symlink(self): - if self._d_type == DT_UNKNOWN: - try: - st = self.stat(follow_symlinks=False) - except OSError as e: - if e.errno != ENOENT: - raise - return False - return st.st_mode & 0o170000 == S_IFLNK - else: - return self._d_type == DT_LNK - - def inode(self): - return self._inode - - def __str__(self): - return '<{0}: {1!r}>'.format(self.__class__.__name__, self.name) - - __repr__ = __str__ - - def posix_error(filename): - errno = ctypes.get_errno() - exc = OSError(errno, strerror(errno)) - exc.filename = filename - return exc - - def scandir_python(path=unicode('.')): - """Like os.listdir(), but yield DirEntry objects instead of returning - a list of names. - """ - if isinstance(path, bytes): - opendir_path = path - is_bytes = True - else: - opendir_path = path.encode(file_system_encoding) - is_bytes = False - dir_p = opendir(opendir_path) - if not dir_p: - raise posix_error(path) - try: - result = Dirent_p() - while True: - entry = Dirent() - if readdir_r(dir_p, entry, result): - raise posix_error(path) - if not result: - break - name = entry.d_name - if name not in (b'.', b'..'): - if not is_bytes: - name = name.decode(file_system_encoding) - yield PosixDirEntry(path, name, entry.d_type, entry.d_ino) - finally: - if closedir(dir_p): - raise posix_error(path) - - if _scandir is not None: - scandir_c = _scandir.scandir - DirEntry_c = _scandir.DirEntry - - if _scandir is not None: - scandir = scandir_c - DirEntry = DirEntry_c - elif ctypes is not None and have_dirent_d_type: - scandir = scandir_python - DirEntry = PosixDirEntry - else: - scandir = scandir_generic - DirEntry = GenericDirEntry - - -# Some other system -- no d_type or stat information -else: - scandir = scandir_generic - DirEntry = GenericDirEntry - - -def _walk(top, topdown=True, onerror=None, followlinks=False): - """Like Python 3.5's implementation of os.walk() -- faster than - the pre-Python 3.5 version as it uses scandir() internally. - """ - dirs = [] - nondirs = [] - - # We may not have read permission for top, in which case we can't - # get a list of the files the directory contains. os.walk - # always suppressed the exception then, rather than blow up for a - # minor reason when (say) a thousand readable directories are still - # left to visit. That logic is copied here. - try: - scandir_it = scandir(top) - except OSError as error: - if onerror is not None: - onerror(error) - return - - while True: - try: - try: - entry = next(scandir_it) - except StopIteration: - break - except OSError as error: - if onerror is not None: - onerror(error) - return - - try: - is_dir = entry.is_dir() - except OSError: - # If is_dir() raises an OSError, consider that the entry is not - # a directory, same behaviour than os.path.isdir(). - is_dir = False - - if is_dir: - dirs.append(entry.name) - else: - nondirs.append(entry.name) - - if not topdown and is_dir: - # Bottom-up: recurse into sub-directory, but exclude symlinks to - # directories if followlinks is False - if followlinks: - walk_into = True - else: - try: - is_symlink = entry.is_symlink() - except OSError: - # If is_symlink() raises an OSError, consider that the - # entry is not a symbolic link, same behaviour than - # os.path.islink(). - is_symlink = False - walk_into = not is_symlink - - if walk_into: - for entry in walk(entry.path, topdown, onerror, followlinks): - yield entry - - # Yield before recursion if going top down - if topdown: - yield top, dirs, nondirs - - # Recurse into sub-directories - for name in dirs: - new_path = join(top, name) - # Issue #23605: os.path.islink() is used instead of caching - # entry.is_symlink() result during the loop on os.scandir() because - # the caller can replace the directory entry during the "yield" - # above. - if followlinks or not islink(new_path): - for entry in walk(new_path, topdown, onerror, followlinks): - yield entry - else: - # Yield after recursion if going bottom up - yield top, dirs, nondirs - - -if IS_PY3 or sys.platform != 'win32': - walk = _walk -else: - # Fix for broken unicode handling on Windows on Python 2.x, see: - # https://github.com/benhoyt/scandir/issues/54 - file_system_encoding = sys.getfilesystemencoding() - - def walk(top, topdown=True, onerror=None, followlinks=False): - if isinstance(top, bytes): - top = top.decode(file_system_encoding) - return _walk(top, topdown, onerror, followlinks) diff --git a/pipenv/vendor/semver.LICENSE.txt b/pipenv/vendor/semver.LICENSE.txt deleted file mode 100644 index f98e22bc..00000000 --- a/pipenv/vendor/semver.LICENSE.txt +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2013, Konstantine Rybnikov -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - - Redistributions in binary form must reproduce the above copyright notice, this - list of conditions and the following disclaimer in the documentation and/or - other materials provided with the distribution. - - Neither the name of the {organization} nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/pipenv/vendor/semver.py b/pipenv/vendor/semver.py deleted file mode 100644 index 5f5be2c2..00000000 --- a/pipenv/vendor/semver.py +++ /dev/null @@ -1,510 +0,0 @@ -""" -Python helper for Semantic Versioning (http://semver.org/) -""" - -import collections -import re - - -__version__ = '2.8.1' -__author__ = 'Kostiantyn Rybnikov' -__author_email__ = 'k-bx@k-bx.com' -__maintainer__ = 'Sebastien Celles' -__maintainer_email__ = "s.celles@gmail.com" - -_REGEX = re.compile( - r""" - ^ - (?P<major>(?:0|[1-9][0-9]*)) - \. - (?P<minor>(?:0|[1-9][0-9]*)) - \. - (?P<patch>(?:0|[1-9][0-9]*)) - (\-(?P<prerelease> - (?:0|[1-9A-Za-z-][0-9A-Za-z-]*) - (\.(?:0|[1-9A-Za-z-][0-9A-Za-z-]*))* - ))? - (\+(?P<build> - [0-9A-Za-z-]+ - (\.[0-9A-Za-z-]+)* - ))? - $ - """, re.VERBOSE) - -_LAST_NUMBER = re.compile(r'(?:[^\d]*(\d+)[^\d]*)+') - -if not hasattr(__builtins__, 'cmp'): - def cmp(a, b): - return (a > b) - (a < b) - - -def parse(version): - """Parse version to major, minor, patch, pre-release, build parts. - - :param version: version string - :return: dictionary with the keys 'build', 'major', 'minor', 'patch', - and 'prerelease'. The prerelease or build keys can be None - if not provided - :rtype: dict - - >>> import semver - >>> ver = semver.parse('3.4.5-pre.2+build.4') - >>> ver['major'] - 3 - >>> ver['minor'] - 4 - >>> ver['patch'] - 5 - >>> ver['prerelease'] - 'pre.2' - >>> ver['build'] - 'build.4' - """ - match = _REGEX.match(version) - if match is None: - raise ValueError('%s is not valid SemVer string' % version) - - version_parts = match.groupdict() - - version_parts['major'] = int(version_parts['major']) - version_parts['minor'] = int(version_parts['minor']) - version_parts['patch'] = int(version_parts['patch']) - - return version_parts - - -class VersionInfo(object): - """ - :param int major: version when you make incompatible API changes. - :param int minor: version when you add functionality in - a backwards-compatible manner. - :param int patch: version when you make backwards-compatible bug fixes. - :param str prerelease: an optional prerelease string - :param str build: an optional build string - """ - __slots__ = ('_major', '_minor', '_patch', '_prerelease', '_build') - - def __init__(self, major, minor, patch, prerelease=None, build=None): - self._major = major - self._minor = minor - self._patch = patch - self._prerelease = prerelease - self._build = build - - @property - def major(self): - return self._major - - @property - def minor(self): - return self._minor - - @property - def patch(self): - return self._patch - - @property - def prerelease(self): - return self._prerelease - - @property - def build(self): - return self._build - - def _astuple(self): - return (self.major, self.minor, self.patch, - self.prerelease, self.build) - - def _asdict(self): - return collections.OrderedDict(( - ("major", self.major), - ("minor", self.minor), - ("patch", self.patch), - ("prerelease", self.prerelease), - ("build", self.build) - )) - - def __eq__(self, other): - if not isinstance(other, (VersionInfo, dict)): - return NotImplemented - return _compare_by_keys(self._asdict(), _to_dict(other)) == 0 - - def __ne__(self, other): - if not isinstance(other, (VersionInfo, dict)): - return NotImplemented - return _compare_by_keys(self._asdict(), _to_dict(other)) != 0 - - def __lt__(self, other): - if not isinstance(other, (VersionInfo, dict)): - return NotImplemented - return _compare_by_keys(self._asdict(), _to_dict(other)) < 0 - - def __le__(self, other): - if not isinstance(other, (VersionInfo, dict)): - return NotImplemented - return _compare_by_keys(self._asdict(), _to_dict(other)) <= 0 - - def __gt__(self, other): - if not isinstance(other, (VersionInfo, dict)): - return NotImplemented - return _compare_by_keys(self._asdict(), _to_dict(other)) > 0 - - def __ge__(self, other): - if not isinstance(other, (VersionInfo, dict)): - return NotImplemented - return _compare_by_keys(self._asdict(), _to_dict(other)) >= 0 - - def __repr__(self): - s = ", ".join("%s=%r" % (key, val) - for key, val in self._asdict().items()) - return "VersionInfo(%s)" % s - - def __str__(self): - return format_version(*(self._astuple())) - - def __hash__(self): - return hash(self._astuple()) - - @staticmethod - def parse(version): - """Parse version string to a VersionInfo instance. - - >>> from semver import VersionInfo - >>> VersionInfo.parse('3.4.5-pre.2+build.4') - VersionInfo(major=3, minor=4, patch=5, \ -prerelease='pre.2', build='build.4') - - :param version: version string - :return: a :class:`VersionInfo` instance - :rtype: :class:`VersionInfo` - """ - return parse_version_info(version) - - -def _to_dict(obj): - if isinstance(obj, VersionInfo): - return obj._asdict() - return obj - - -def parse_version_info(version): - """Parse version string to a VersionInfo instance. - - :param version: version string - :return: a :class:`VersionInfo` instance - :rtype: :class:`VersionInfo` - - >>> import semver - >>> version_info = semver.parse_version_info("3.4.5-pre.2+build.4") - >>> version_info.major - 3 - >>> version_info.minor - 4 - >>> version_info.patch - 5 - >>> version_info.prerelease - 'pre.2' - >>> version_info.build - 'build.4' - """ - parts = parse(version) - version_info = VersionInfo( - parts['major'], parts['minor'], parts['patch'], - parts['prerelease'], parts['build']) - - return version_info - - -def _nat_cmp(a, b): - def convert(text): - return int(text) if re.match('^[0-9]+$', text) else text - - def split_key(key): - return [convert(c) for c in key.split('.')] - - def cmp_prerelease_tag(a, b): - if isinstance(a, int) and isinstance(b, int): - return cmp(a, b) - elif isinstance(a, int): - return -1 - elif isinstance(b, int): - return 1 - else: - return cmp(a, b) - - a, b = a or '', b or '' - a_parts, b_parts = split_key(a), split_key(b) - for sub_a, sub_b in zip(a_parts, b_parts): - cmp_result = cmp_prerelease_tag(sub_a, sub_b) - if cmp_result != 0: - return cmp_result - else: - return cmp(len(a), len(b)) - - -def _compare_by_keys(d1, d2): - for key in ['major', 'minor', 'patch']: - v = cmp(d1.get(key), d2.get(key)) - if v: - return v - - rc1, rc2 = d1.get('prerelease'), d2.get('prerelease') - rccmp = _nat_cmp(rc1, rc2) - - if not rccmp: - return 0 - if not rc1: - return 1 - elif not rc2: - return -1 - - return rccmp - - -def compare(ver1, ver2): - """Compare two versions - - :param ver1: version string 1 - :param ver2: version string 2 - :return: The return value is negative if ver1 < ver2, - zero if ver1 == ver2 and strictly positive if ver1 > ver2 - :rtype: int - - >>> import semver - >>> semver.compare("1.0.0", "2.0.0") - -1 - >>> semver.compare("2.0.0", "1.0.0") - 1 - >>> semver.compare("2.0.0", "2.0.0") - 0 - """ - - v1, v2 = parse(ver1), parse(ver2) - - return _compare_by_keys(v1, v2) - - -def match(version, match_expr): - """Compare two versions through a comparison - - :param str version: a version string - :param str match_expr: operator and version; valid operators are - < smaller than - > greater than - >= greator or equal than - <= smaller or equal than - == equal - != not equal - :return: True if the expression matches the version, otherwise False - :rtype: bool - - >>> import semver - >>> semver.match("2.0.0", ">=1.0.0") - True - >>> semver.match("1.0.0", ">1.0.0") - False - """ - prefix = match_expr[:2] - if prefix in ('>=', '<=', '==', '!='): - match_version = match_expr[2:] - elif prefix and prefix[0] in ('>', '<'): - prefix = prefix[0] - match_version = match_expr[1:] - else: - raise ValueError("match_expr parameter should be in format <op><ver>, " - "where <op> is one of " - "['<', '>', '==', '<=', '>=', '!=']. " - "You provided: %r" % match_expr) - - possibilities_dict = { - '>': (1,), - '<': (-1,), - '==': (0,), - '!=': (-1, 1), - '>=': (0, 1), - '<=': (-1, 0) - } - - possibilities = possibilities_dict[prefix] - cmp_res = compare(version, match_version) - - return cmp_res in possibilities - - -def max_ver(ver1, ver2): - """Returns the greater version of two versions - - :param ver1: version string 1 - :param ver2: version string 2 - :return: the greater version of the two - :rtype: :class:`VersionInfo` - - >>> import semver - >>> semver.max_ver("1.0.0", "2.0.0") - '2.0.0' - """ - cmp_res = compare(ver1, ver2) - if cmp_res == 0 or cmp_res == 1: - return ver1 - else: - return ver2 - - -def min_ver(ver1, ver2): - """Returns the smaller version of two versions - - :param ver1: version string 1 - :param ver2: version string 2 - :return: the smaller version of the two - :rtype: :class:`VersionInfo` - - >>> import semver - >>> semver.min_ver("1.0.0", "2.0.0") - '1.0.0' - """ - cmp_res = compare(ver1, ver2) - if cmp_res == 0 or cmp_res == -1: - return ver1 - else: - return ver2 - - -def format_version(major, minor, patch, prerelease=None, build=None): - """Format a version according to the Semantic Versioning specification - - :param str major: the required major part of a version - :param str minor: the required minor part of a version - :param str patch: the required patch part of a version - :param str prerelease: the optional prerelease part of a version - :param str build: the optional build part of a version - :return: the formatted string - :rtype: str - - >>> import semver - >>> semver.format_version(3, 4, 5, 'pre.2', 'build.4') - '3.4.5-pre.2+build.4' - """ - version = "%d.%d.%d" % (major, minor, patch) - if prerelease is not None: - version = version + "-%s" % prerelease - - if build is not None: - version = version + "+%s" % build - - return version - - -def _increment_string(string): - """ - Look for the last sequence of number(s) in a string and increment, from: - http://code.activestate.com/recipes/442460-increment-numbers-in-a-string/#c1 - """ - match = _LAST_NUMBER.search(string) - if match: - next_ = str(int(match.group(1)) + 1) - start, end = match.span(1) - string = string[:max(end - len(next_), start)] + next_ + string[end:] - return string - - -def bump_major(version): - """Raise the major part of the version - - :param: version string - :return: the raised version string - :rtype: str - - >>> import semver - >>> semver.bump_major("3.4.5") - '4.0.0' - """ - verinfo = parse(version) - return format_version(verinfo['major'] + 1, 0, 0) - - -def bump_minor(version): - """Raise the minor part of the version - - :param: version string - :return: the raised version string - :rtype: str - - >>> import semver - >>> semver.bump_minor("3.4.5") - '3.5.0' - """ - verinfo = parse(version) - return format_version(verinfo['major'], verinfo['minor'] + 1, 0) - - -def bump_patch(version): - """Raise the patch part of the version - - :param: version string - :return: the raised version string - :rtype: str - - >>> import semver - >>> semver.bump_patch("3.4.5") - '3.4.6' - """ - verinfo = parse(version) - return format_version(verinfo['major'], verinfo['minor'], - verinfo['patch'] + 1) - - -def bump_prerelease(version, token='rc'): - """Raise the prerelease part of the version - - :param version: version string - :param token: defaults to 'rc' - :return: the raised version string - :rtype: str - - >>> bump_prerelease('3.4.5', 'dev') - '3.4.5-dev.1' - """ - verinfo = parse(version) - verinfo['prerelease'] = _increment_string( - verinfo['prerelease'] or (token or 'rc') + '.0' - ) - return format_version(verinfo['major'], verinfo['minor'], verinfo['patch'], - verinfo['prerelease']) - - -def bump_build(version, token='build'): - """Raise the build part of the version - - :param version: version string - :param token: defaults to 'build' - :return: the raised version string - :rtype: str - - >>> bump_build('3.4.5-rc.1+build.9') - '3.4.5-rc.1+build.10' - """ - verinfo = parse(version) - verinfo['build'] = _increment_string( - verinfo['build'] or (token or 'build') + '.0' - ) - return format_version(verinfo['major'], verinfo['minor'], verinfo['patch'], - verinfo['prerelease'], verinfo['build']) - - -def finalize_version(version): - """Remove any prerelease and build metadata from the version - - :param version: version string - :return: the finalized version string - :rtype: str - - >>> finalize_version('1.2.3-rc.5') - '1.2.3' - """ - verinfo = parse(version) - return format_version(verinfo['major'], verinfo['minor'], verinfo['patch']) - - -if __name__ == "__main__": - import doctest - doctest.testmod() diff --git a/pipenv/vendor/shellingham/__init__.py b/pipenv/vendor/shellingham/__init__.py index b834b74b..20929b2a 100644 --- a/pipenv/vendor/shellingham/__init__.py +++ b/pipenv/vendor/shellingham/__init__.py @@ -4,21 +4,20 @@ import os from ._core import ShellDetectionFailure -__version__ = '1.3.1' +__version__ = "1.4.0" -def detect_shell(pid=None, max_depth=6): +def detect_shell(pid=None, max_depth=10): name = os.name try: - impl = importlib.import_module('.' + name, __name__) + impl = importlib.import_module(".{}".format(name), __name__) except ImportError: - raise RuntimeError( - 'Shell detection not implemented for {0!r}'.format(name), - ) + message = "Shell detection not implemented for {0!r}".format(name) + raise RuntimeError(message) try: get_shell = impl.get_shell except AttributeError: - raise RuntimeError('get_shell not implemented for {0!r}'.format(name)) + raise RuntimeError("get_shell not implemented for {0!r}".format(name)) shell = get_shell(pid, max_depth=max_depth) if shell: return shell diff --git a/pipenv/vendor/shellingham/_consts.py b/pipenv/vendor/shellingham/_consts.py deleted file mode 100644 index 2c788fec..00000000 --- a/pipenv/vendor/shellingham/_consts.py +++ /dev/null @@ -1,7 +0,0 @@ -SHELL_NAMES = { - 'sh', 'bash', 'dash', # Bourne. - 'csh', 'tcsh', # C. - 'ksh', 'zsh', 'fish', # Common alternatives. - 'cmd', 'powershell', 'pwsh', # Microsoft. - 'elvish', 'xonsh', # More exotic. -} diff --git a/pipenv/vendor/shellingham/_core.py b/pipenv/vendor/shellingham/_core.py index da103a94..2c974c8f 100644 --- a/pipenv/vendor/shellingham/_core.py +++ b/pipenv/vendor/shellingham/_core.py @@ -1,10 +1,10 @@ -SHELL_NAMES = { - 'sh', 'bash', 'dash', 'ash', # Bourne. - 'csh', 'tcsh', # C. - 'ksh', 'zsh', 'fish', # Common alternatives. - 'cmd', 'powershell', 'pwsh', # Microsoft. - 'elvish', 'xonsh', # More exotic. -} +SHELL_NAMES = ( + {"sh", "bash", "dash", "ash"} # Bourne. + | {"csh", "tcsh"} # C. + | {"ksh", "zsh", "fish"} # Common alternatives. + | {"cmd", "powershell", "pwsh"} # Microsoft. + | {"elvish", "xonsh"} # More exotic. +) class ShellDetectionFailure(EnvironmentError): diff --git a/pipenv/vendor/shellingham/nt.py b/pipenv/vendor/shellingham/nt.py index d66bc33f..4ddc91b9 100644 --- a/pipenv/vendor/shellingham/nt.py +++ b/pipenv/vendor/shellingham/nt.py @@ -1,80 +1,146 @@ -# Code based on the winappdbg project http://winappdbg.sourceforge.net/ -# (BSD License) - adapted from Celery by Dan Ryan (dan@danryan.co) -# https://github.com/celery/celery/blob/2.5-archived/celery/concurrency/processes/_win.py - +import contextlib +import ctypes import os -import sys -from ctypes import ( - byref, sizeof, windll, Structure, WinError, - c_size_t, c_char, c_void_p +from ctypes.wintypes import ( + BOOL, + CHAR, + DWORD, + HANDLE, + LONG, + LPWSTR, + MAX_PATH, + PDWORD, + ULONG, ) -from ctypes.wintypes import DWORD, LONG -from ._core import SHELL_NAMES +from pipenv.vendor.shellingham._core import SHELL_NAMES +INVALID_HANDLE_VALUE = HANDLE(-1).value ERROR_NO_MORE_FILES = 18 ERROR_INSUFFICIENT_BUFFER = 122 - -INVALID_HANDLE_VALUE = c_void_p(-1).value +TH32CS_SNAPPROCESS = 2 +PROCESS_QUERY_LIMITED_INFORMATION = 0x1000 -if sys.version_info[0] < 3: - string_types = (str, unicode) # noqa -else: - string_types = (str,) +kernel32 = ctypes.windll.kernel32 -class PROCESSENTRY32(Structure): - _fields_ = [ - ('dwSize', DWORD), - ('cntUsage', DWORD), - ('th32ProcessID', DWORD), - ('th32DefaultHeapID', c_size_t), - ('th32ModuleID', DWORD), - ('cntThreads', DWORD), - ('th32ParentProcessID', DWORD), - ('pcPriClassBase', LONG), - ('dwFlags', DWORD), - ('szExeFile', c_char * 260), - ] +def _check_handle(error_val=0): + def check(ret, func, args): + if ret == error_val: + raise ctypes.WinError() + return ret + + return check -def _iter_process(): - """Iterate through processes, yielding process ID and properties of each. +def _check_expected(expected): + def check(ret, func, args): + if ret: + return True + code = ctypes.GetLastError() + if code == expected: + return False + raise ctypes.WinError(code) - Example usage:: + return check - >>> for pid, info in _iter_process(): - ... print(pid, '->', info) - 1509 -> {'parent_pid': 1201, 'executable': 'python.exe'} - """ - # TODO: Process32{First,Next} does not return full executable path, only - # the name. To get the full path, Module32{First,Next} is needed, but that - # does not contain parent process information. We probably need to call - # BOTH to build the correct process tree. - h_process = windll.kernel32.CreateToolhelp32Snapshot( - 2, # dwFlags=TH32CS_SNAPPROCESS (include all processes). - 0, # th32ProcessID=0 (the current process). + +class ProcessEntry32(ctypes.Structure): + _fields_ = ( + ("dwSize", DWORD), + ("cntUsage", DWORD), + ("th32ProcessID", DWORD), + ("th32DefaultHeapID", ctypes.POINTER(ULONG)), + ("th32ModuleID", DWORD), + ("cntThreads", DWORD), + ("th32ParentProcessID", DWORD), + ("pcPriClassBase", LONG), + ("dwFlags", DWORD), + ("szExeFile", CHAR * MAX_PATH), ) - if h_process == INVALID_HANDLE_VALUE: - raise WinError() - pe = PROCESSENTRY32() - pe.dwSize = sizeof(PROCESSENTRY32) - success = windll.kernel32.Process32First(h_process, byref(pe)) + + +kernel32.CloseHandle.argtypes = [HANDLE] +kernel32.CloseHandle.restype = BOOL + +kernel32.CreateToolhelp32Snapshot.argtypes = [DWORD, DWORD] +kernel32.CreateToolhelp32Snapshot.restype = HANDLE +kernel32.CreateToolhelp32Snapshot.errcheck = _check_handle( # type: ignore + INVALID_HANDLE_VALUE, +) + +kernel32.Process32First.argtypes = [HANDLE, ctypes.POINTER(ProcessEntry32)] +kernel32.Process32First.restype = BOOL +kernel32.Process32First.errcheck = _check_expected( # type: ignore + ERROR_NO_MORE_FILES, +) + +kernel32.Process32Next.argtypes = [HANDLE, ctypes.POINTER(ProcessEntry32)] +kernel32.Process32Next.restype = BOOL +kernel32.Process32Next.errcheck = _check_expected( # type: ignore + ERROR_NO_MORE_FILES, +) + +kernel32.GetCurrentProcessId.argtypes = [] +kernel32.GetCurrentProcessId.restype = DWORD + +kernel32.OpenProcess.argtypes = [DWORD, BOOL, DWORD] +kernel32.OpenProcess.restype = HANDLE +kernel32.OpenProcess.errcheck = _check_handle( # type: ignore + INVALID_HANDLE_VALUE, +) + +kernel32.QueryFullProcessImageNameW.argtypes = [HANDLE, DWORD, LPWSTR, PDWORD] +kernel32.QueryFullProcessImageNameW.restype = BOOL +kernel32.QueryFullProcessImageNameW.errcheck = _check_expected( # type: ignore + ERROR_INSUFFICIENT_BUFFER, +) + + +@contextlib.contextmanager +def _handle(f, *args, **kwargs): + handle = f(*args, **kwargs) + try: + yield handle + finally: + kernel32.CloseHandle(handle) + + +def _iter_processes(): + f = kernel32.CreateToolhelp32Snapshot + with _handle(f, TH32CS_SNAPPROCESS, 0) as snap: + entry = ProcessEntry32() + entry.dwSize = ctypes.sizeof(entry) + ret = kernel32.Process32First(snap, entry) + while ret: + yield entry + ret = kernel32.Process32Next(snap, entry) + + +def _get_full_path(proch): + size = DWORD(MAX_PATH) while True: - if not success: - errcode = windll.kernel32.GetLastError() - if errcode == ERROR_NO_MORE_FILES: - # No more processes to iterate through, we're done here. - return - elif errcode == ERROR_INSUFFICIENT_BUFFER: - # This is likely because the file path is longer than the - # Windows limit. Just ignore it, it's likely not what we're - # looking for. We can fix this when it actually matters. (#8) - continue - raise WinError() + path_buff = ctypes.create_unicode_buffer("", size.value) + if kernel32.QueryFullProcessImageNameW(proch, 0, path_buff, size): + return path_buff.value + size.value *= 2 + + +def get_shell(pid=None, max_depth=10): + proc_map = { + proc.th32ProcessID: (proc.th32ParentProcessID, proc.szExeFile) + for proc in _iter_processes() + } + pid = pid or os.getpid() + + for _ in range(0, max_depth + 1): + try: + ppid, executable = proc_map[pid] + except KeyError: # No such process? Give up. + break # The executable name would be encoded with the current code page if # we're in ANSI mode (usually). Try to decode it into str/unicode, @@ -82,47 +148,16 @@ def _iter_process(): # I think). Note that we need to use 'mbcs' instead of encoding # settings from sys because this is from the Windows API, not Python # internals (which those settings reflect). (pypa/pipenv#3382) - executable = pe.szExeFile if isinstance(executable, bytes): - executable = executable.decode('mbcs', 'replace') + executable = executable.decode("mbcs", "replace") - info = {'executable': executable} - if pe.th32ParentProcessID: - info['parent_pid'] = pe.th32ParentProcessID - yield pe.th32ProcessID, info - success = windll.kernel32.Process32Next(h_process, byref(pe)) + name = executable.rpartition(".")[0].lower() + if name not in SHELL_NAMES: + pid = ppid + continue + key = PROCESS_QUERY_LIMITED_INFORMATION + with _handle(kernel32.OpenProcess, key, 0, pid) as proch: + return (name, _get_full_path(proch)) -def _get_executable(process_dict): - try: - executable = process_dict.get('executable') - except (AttributeError, TypeError): - return None - if isinstance(executable, string_types): - executable = executable.lower().rsplit('.', 1)[0] - return executable - - -def get_shell(pid=None, max_depth=6): - """Get the shell that the supplied pid or os.getpid() is running in. - """ - if not pid: - pid = os.getpid() - processes = dict(_iter_process()) - - def check_parent(pid, lvl=0): - ppid = processes[pid].get('parent_pid') - shell_name = _get_executable(processes.get(ppid)) - if shell_name in SHELL_NAMES: - return (shell_name, processes[ppid]['executable']) - if lvl >= max_depth: - return None - return check_parent(ppid, lvl=lvl + 1) - - shell_name = _get_executable(processes.get(pid)) - if shell_name in SHELL_NAMES: - return (shell_name, processes[pid]['executable']) - try: - return check_parent(pid) - except KeyError: - return None + return None diff --git a/pipenv/vendor/shellingham/posix.py b/pipenv/vendor/shellingham/posix.py deleted file mode 100644 index 0bbf988b..00000000 --- a/pipenv/vendor/shellingham/posix.py +++ /dev/null @@ -1,56 +0,0 @@ -import collections -import os -import shlex -import subprocess -import sys - -from ._consts import SHELL_NAMES - - -Process = collections.namedtuple('Process', 'args pid ppid') - - -def _get_process_mapping(): - """Try to look up the process tree via the output of `ps`. - """ - output = subprocess.check_output([ - 'ps', '-ww', '-o', 'pid=', '-o', 'ppid=', '-o', 'args=', - ]) - if not isinstance(output, str): - output = output.decode(sys.stdout.encoding) - processes = {} - for line in output.split('\n'): - try: - pid, ppid, args = line.strip().split(None, 2) - except ValueError: - continue - processes[pid] = Process( - args=tuple(shlex.split(args)), pid=pid, ppid=ppid, - ) - return processes - - -def get_shell(pid=None, max_depth=6): - """Get the shell that the supplied pid or os.getpid() is running in. - """ - pid = str(pid or os.getpid()) - mapping = _get_process_mapping() - login_shell = os.environ.get('SHELL', '') - for _ in range(max_depth): - try: - proc = mapping[pid] - except KeyError: - break - name = os.path.basename(proc.args[0]).lower() - if name in SHELL_NAMES: - return (name, proc.args[0]) - elif proc.args[0].startswith('-'): - # This is the login shell. Use the SHELL environ if possible - # because it provides better information. - if login_shell: - name = login_shell.lower() - else: - name = proc.args[0][1:].lower() - return (os.path.basename(name), name) - pid = proc.ppid # Go up one level. - return None diff --git a/pipenv/vendor/shellingham/posix/__init__.py b/pipenv/vendor/shellingham/posix/__init__.py index 923032b6..fe1713f9 100644 --- a/pipenv/vendor/shellingham/posix/__init__.py +++ b/pipenv/vendor/shellingham/posix/__init__.py @@ -1,4 +1,5 @@ import os +import re from .._core import SHELL_NAMES, ShellDetectionFailure from . import proc, ps @@ -16,30 +17,24 @@ def _get_process_mapping(): except EnvironmentError: continue return mapping - raise ShellDetectionFailure('compatible proc fs or ps utility is required') + raise ShellDetectionFailure("compatible proc fs or ps utility is required") -def _iter_process_command(mapping, pid, max_depth): - """Iterator to traverse up the tree, yielding `argv[0]` of each process. - """ +def _iter_process_args(mapping, pid, max_depth): + """Traverse up the tree and yield each process's argument list.""" for _ in range(max_depth): try: proc = mapping[pid] - except KeyError: # We've reached the root process. Give up. + except KeyError: # We've reached the root process. Give up. break - try: - cmd = proc.args[0] - except IndexError: # Process has no name? Whatever, ignore it. - pass - else: - yield cmd - pid = proc.ppid # Go up one level. + if proc.args: # Persumably the process should always have a name? + yield proc.args + pid = proc.ppid # Go up one level. def _get_login_shell(proc_cmd): - """Form shell information from the SHELL environment variable if possible. - """ - login_shell = os.environ.get('SHELL', '') + """Form shell information from SHELL environ if possible.""" + login_shell = os.environ.get("SHELL", "") if login_shell: proc_cmd = login_shell else: @@ -47,15 +42,49 @@ def _get_login_shell(proc_cmd): return (os.path.basename(proc_cmd).lower(), proc_cmd) -def get_shell(pid=None, max_depth=6): - """Get the shell that the supplied pid or os.getpid() is running in. +_INTERPRETER_SHELL_NAMES = [ + (re.compile(r"^python(\d+(\.\d+)?)?$"), {"xonsh"}), +] + + +def _get_interpreter_shell(proc_name, proc_args): + """Get shell invoked via an interpreter. + + Some shells are implemented on, and invoked with an interpreter, e.g. xonsh + is commonly executed with an executable Python script. This detects what + script the interpreter is actually running, and check whether that looks + like a shell. + + See sarugaku/shellingham#26 for rational. """ + for pattern, shell_names in _INTERPRETER_SHELL_NAMES: + if not pattern.match(proc_name): + continue + for arg in proc_args: + name = os.path.basename(arg).lower() + if os.path.isfile(arg) and name in shell_names: + return (name, arg) + return None + + +def _get_shell(cmd, *args): + if cmd.startswith("-"): # Login shell! Let's use this. + return _get_login_shell(cmd) + name = os.path.basename(cmd).lower() + if name in SHELL_NAMES: # Command looks like a shell. + return (name, cmd) + shell = _get_interpreter_shell(name, args) + if shell: + return shell + return None + + +def get_shell(pid=None, max_depth=10): + """Get the shell that the supplied pid or os.getpid() is running in.""" pid = str(pid or os.getpid()) mapping = _get_process_mapping() - for proc_cmd in _iter_process_command(mapping, pid, max_depth): - if proc_cmd.startswith('-'): # Login shell! Let's use this. - return _get_login_shell(proc_cmd) - name = os.path.basename(proc_cmd).lower() - if name in SHELL_NAMES: # The inner-most (non-login) shell. - return (name, proc_cmd) + for proc_args in _iter_process_args(mapping, pid, max_depth): + shell = _get_shell(*proc_args) + if shell: + return shell return None diff --git a/pipenv/vendor/shellingham/posix/_core.py b/pipenv/vendor/shellingham/posix/_core.py index e164fae6..adc49e6e 100644 --- a/pipenv/vendor/shellingham/posix/_core.py +++ b/pipenv/vendor/shellingham/posix/_core.py @@ -1,3 +1,3 @@ import collections -Process = collections.namedtuple('Process', 'args pid ppid') +Process = collections.namedtuple("Process", "args pid ppid") diff --git a/pipenv/vendor/shellingham/posix/_proc.py b/pipenv/vendor/shellingham/posix/_proc.py deleted file mode 100644 index e3a6e46d..00000000 --- a/pipenv/vendor/shellingham/posix/_proc.py +++ /dev/null @@ -1,35 +0,0 @@ -import os -import re - -from ._default import Process - - -STAT_PPID = 3 -STAT_TTY = 6 - - -def get_process_mapping(): - """Try to look up the process tree via the /proc interface. - """ - with open('/proc/{0}/stat'.format(os.getpid())) as f: - self_tty = f.read().split()[STAT_TTY] - processes = {} - for pid in os.listdir('/proc'): - if not pid.isdigit(): - continue - try: - stat = '/proc/{0}/stat'.format(pid) - cmdline = '/proc/{0}/cmdline'.format(pid) - with open(stat) as fstat, open(cmdline) as fcmdline: - stat = re.findall(r'\(.+\)|\S+', fstat.read()) - cmd = fcmdline.read().split('\x00')[:-1] - ppid = stat[STAT_PPID] - tty = stat[STAT_TTY] - if tty == self_tty: - processes[pid] = Process( - args=tuple(cmd), pid=pid, ppid=ppid, - ) - except IOError: - # Process has disappeared - just ignore it. - continue - return processes diff --git a/pipenv/vendor/shellingham/posix/_ps.py b/pipenv/vendor/shellingham/posix/_ps.py deleted file mode 100644 index 86944276..00000000 --- a/pipenv/vendor/shellingham/posix/_ps.py +++ /dev/null @@ -1,27 +0,0 @@ -import collections -import shlex -import subprocess -import sys - - -Process = collections.namedtuple('Process', 'args pid ppid') - - -def get_process_mapping(): - """Try to look up the process tree via the output of `ps`. - """ - output = subprocess.check_output([ - 'ps', '-ww', '-o', 'pid=', '-o', 'ppid=', '-o', 'args=', - ]) - if not isinstance(output, str): - output = output.decode(sys.stdout.encoding) - processes = {} - for line in output.split('\n'): - try: - pid, ppid, args = line.strip().split(None, 2) - except ValueError: - continue - processes[pid] = Process( - args=tuple(shlex.split(args)), pid=pid, ppid=ppid, - ) - return processes diff --git a/pipenv/vendor/shellingham/posix/proc.py b/pipenv/vendor/shellingham/posix/proc.py index 89d6c8e9..418a3bc2 100644 --- a/pipenv/vendor/shellingham/posix/proc.py +++ b/pipenv/vendor/shellingham/posix/proc.py @@ -9,7 +9,7 @@ from ._core import Process STAT_PPID = 3 STAT_TTY = 6 -STAT_PATTERN = re.compile(r'\(.+\)|\S+') +STAT_PATTERN = re.compile(r"\(.+\)|\S+") def detect_proc(): @@ -22,29 +22,29 @@ def detect_proc(): * `status`: BSD-style, i.e. ``/proc/{pid}/status``. """ pid = os.getpid() - for name in ('stat', 'status'): - if os.path.exists(os.path.join('/proc', str(pid), name)): + for name in ("stat", "status"): + if os.path.exists(os.path.join("/proc", str(pid), name)): return name - raise ProcFormatError('unsupported proc format') + raise ProcFormatError("unsupported proc format") def _get_stat(pid, name): - path = os.path.join('/proc', str(pid), name) - with io.open(path, encoding='ascii', errors='replace') as f: + path = os.path.join("/proc", str(pid), name) + with io.open(path, encoding="ascii", errors="replace") as f: # We only care about TTY and PPID -- all numbers. parts = STAT_PATTERN.findall(f.read()) return parts[STAT_TTY], parts[STAT_PPID] def _get_cmdline(pid): - path = os.path.join('/proc', str(pid), 'cmdline') - encoding = sys.getfilesystemencoding() or 'utf-8' - with io.open(path, encoding=encoding, errors='replace') as f: + path = os.path.join("/proc", str(pid), "cmdline") + encoding = sys.getfilesystemencoding() or "utf-8" + with io.open(path, encoding=encoding, errors="replace") as f: # XXX: Command line arguments can be arbitrary byte sequences, not # necessarily decodable. For Shellingham's purpose, however, we don't # care. (pypa/pipenv#2820) # cmdline appends an extra NULL at the end, hence the [:-1]. - return tuple(f.read().split('\0')[:-1]) + return tuple(f.read().split("\0")[:-1]) class ProcFormatError(EnvironmentError): @@ -52,12 +52,11 @@ class ProcFormatError(EnvironmentError): def get_process_mapping(): - """Try to look up the process tree via the /proc interface. - """ + """Try to look up the process tree via the /proc interface.""" stat_name = detect_proc() self_tty = _get_stat(os.getpid(), stat_name)[0] processes = {} - for pid in os.listdir('/proc'): + for pid in os.listdir("/proc"): if not pid.isdigit(): continue try: diff --git a/pipenv/vendor/shellingham/posix/ps.py b/pipenv/vendor/shellingham/posix/ps.py index cf7e56f2..3de6d258 100644 --- a/pipenv/vendor/shellingham/posix/ps.py +++ b/pipenv/vendor/shellingham/posix/ps.py @@ -10,16 +10,14 @@ class PsNotAvailable(EnvironmentError): def get_process_mapping(): - """Try to look up the process tree via the output of `ps`. - """ + """Try to look up the process tree via the output of `ps`.""" try: - output = subprocess.check_output([ - 'ps', '-ww', '-o', 'pid=', '-o', 'ppid=', '-o', 'args=', - ]) - except OSError as e: # Python 2-compatible FileNotFoundError. + cmd = ["ps", "-ww", "-o", "pid=", "-o", "ppid=", "-o", "args="] + output = subprocess.check_output(cmd) + except OSError as e: # Python 2-compatible FileNotFoundError. if e.errno != errno.ENOENT: raise - raise PsNotAvailable('ps not found') + raise PsNotAvailable("ps not found") except subprocess.CalledProcessError as e: # `ps` can return 1 if the process list is completely empty. # (sarugaku/shellingham#15) @@ -30,7 +28,7 @@ def get_process_mapping(): encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() output = output.decode(encoding) processes = {} - for line in output.split('\n'): + for line in output.split("\n"): try: pid, ppid, args = line.strip().split(None, 2) # XXX: This is not right, but we are really out of options. @@ -38,7 +36,7 @@ def get_process_mapping(): # and this is "Good Enough" for obtaining shell names. Hopefully # people don't name their shell with a space, or have something # like "/usr/bin/xonsh is uber". (sarugaku/shellingham#14) - args = tuple(a.strip() for a in args.split(' ')) + args = tuple(a.strip() for a in args.split(" ")) except ValueError: continue processes[pid] = Process(args=args, pid=pid, ppid=ppid) diff --git a/pipenv/vendor/six.LICENSE b/pipenv/vendor/six.LICENSE index 365d1074..de663311 100644 --- a/pipenv/vendor/six.LICENSE +++ b/pipenv/vendor/six.LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2010-2018 Benjamin Peterson +Copyright (c) 2010-2020 Benjamin Peterson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/pipenv/vendor/six.py b/pipenv/vendor/six.py index 89b2188f..4e15675d 100644 --- a/pipenv/vendor/six.py +++ b/pipenv/vendor/six.py @@ -1,4 +1,4 @@ -# Copyright (c) 2010-2018 Benjamin Peterson +# Copyright (c) 2010-2020 Benjamin Peterson # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -29,7 +29,7 @@ import sys import types __author__ = "Benjamin Peterson <benjamin@python.org>" -__version__ = "1.12.0" +__version__ = "1.16.0" # Useful for very coarse version differentiation. @@ -71,6 +71,11 @@ else: MAXSIZE = int((1 << 63) - 1) del X +if PY34: + from importlib.util import spec_from_loader +else: + spec_from_loader = None + def _add_doc(func, doc): """Add documentation to a function.""" @@ -186,6 +191,11 @@ class _SixMetaPathImporter(object): return self return None + def find_spec(self, fullname, path, target=None): + if fullname in self.known_modules: + return spec_from_loader(fullname, self) + return None + def __get_module(self, fullname): try: return self.known_modules[fullname] @@ -223,6 +233,12 @@ class _SixMetaPathImporter(object): return None get_source = get_code # same as get_code + def create_module(self, spec): + return self.load_module(spec.name) + + def exec_module(self, module): + pass + _importer = _SixMetaPathImporter(__name__) @@ -255,9 +271,11 @@ _moved_attributes = [ MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"), MovedModule("builtins", "__builtin__"), MovedModule("configparser", "ConfigParser"), + MovedModule("collections_abc", "collections", "collections.abc" if sys.version_info >= (3, 3) else "collections"), MovedModule("copyreg", "copy_reg"), MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), - MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"), + MovedModule("dbm_ndbm", "dbm", "dbm.ndbm"), + MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread" if sys.version_info < (3, 9) else "_thread"), MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), MovedModule("http_cookies", "Cookie", "http.cookies"), MovedModule("html_entities", "htmlentitydefs", "html.entities"), @@ -637,13 +655,16 @@ if PY3: import io StringIO = io.StringIO BytesIO = io.BytesIO + del io _assertCountEqual = "assertCountEqual" if sys.version_info[1] <= 1: _assertRaisesRegex = "assertRaisesRegexp" _assertRegex = "assertRegexpMatches" + _assertNotRegex = "assertNotRegexpMatches" else: _assertRaisesRegex = "assertRaisesRegex" _assertRegex = "assertRegex" + _assertNotRegex = "assertNotRegex" else: def b(s): return s @@ -665,6 +686,7 @@ else: _assertCountEqual = "assertItemsEqual" _assertRaisesRegex = "assertRaisesRegexp" _assertRegex = "assertRegexpMatches" + _assertNotRegex = "assertNotRegexpMatches" _add_doc(b, """Byte literal""") _add_doc(u, """Text literal""") @@ -681,6 +703,10 @@ def assertRegex(self, *args, **kwargs): return getattr(self, _assertRegex)(*args, **kwargs) +def assertNotRegex(self, *args, **kwargs): + return getattr(self, _assertNotRegex)(*args, **kwargs) + + if PY3: exec_ = getattr(moves.builtins, "exec") @@ -716,16 +742,7 @@ else: """) -if sys.version_info[:2] == (3, 2): - exec_("""def raise_from(value, from_value): - try: - if from_value is None: - raise value - raise value from from_value - finally: - value = None -""") -elif sys.version_info[:2] > (3, 2): +if sys.version_info[:2] > (3,): exec_("""def raise_from(value, from_value): try: raise value from from_value @@ -805,13 +822,33 @@ if sys.version_info[:2] < (3, 3): _add_doc(reraise, """Reraise an exception.""") if sys.version_info[0:2] < (3, 4): + # This does exactly the same what the :func:`py3:functools.update_wrapper` + # function does on Python versions after 3.2. It sets the ``__wrapped__`` + # attribute on ``wrapper`` object and it doesn't raise an error if any of + # the attributes mentioned in ``assigned`` and ``updated`` are missing on + # ``wrapped`` object. + def _update_wrapper(wrapper, wrapped, + assigned=functools.WRAPPER_ASSIGNMENTS, + updated=functools.WRAPPER_UPDATES): + for attr in assigned: + try: + value = getattr(wrapped, attr) + except AttributeError: + continue + else: + setattr(wrapper, attr, value) + for attr in updated: + getattr(wrapper, attr).update(getattr(wrapped, attr, {})) + wrapper.__wrapped__ = wrapped + return wrapper + _update_wrapper.__doc__ = functools.update_wrapper.__doc__ + def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, updated=functools.WRAPPER_UPDATES): - def wrapper(f): - f = functools.wraps(wrapped, assigned, updated)(f) - f.__wrapped__ = wrapped - return f - return wrapper + return functools.partial(_update_wrapper, wrapped=wrapped, + assigned=assigned, updated=updated) + wraps.__doc__ = functools.wraps.__doc__ + else: wraps = functools.wraps @@ -824,7 +861,15 @@ def with_metaclass(meta, *bases): class metaclass(type): def __new__(cls, name, this_bases, d): - return meta(name, bases, d) + if sys.version_info[:2] >= (3, 7): + # This version introduced PEP 560 that requires a bit + # of extra care (we mimic what is done by __build_class__). + resolved_bases = types.resolve_bases(bases) + if resolved_bases is not bases: + d['__orig_bases__'] = bases + else: + resolved_bases = bases + return meta(name, resolved_bases, d) @classmethod def __prepare__(cls, name, this_bases): @@ -861,12 +906,11 @@ def ensure_binary(s, encoding='utf-8', errors='strict'): - `str` -> encoded to `bytes` - `bytes` -> `bytes` """ + if isinstance(s, binary_type): + return s if isinstance(s, text_type): return s.encode(encoding, errors) - elif isinstance(s, binary_type): - return s - else: - raise TypeError("not expecting type '%s'" % type(s)) + raise TypeError("not expecting type '%s'" % type(s)) def ensure_str(s, encoding='utf-8', errors='strict'): @@ -880,12 +924,15 @@ def ensure_str(s, encoding='utf-8', errors='strict'): - `str` -> `str` - `bytes` -> decoded to `str` """ - if not isinstance(s, (text_type, binary_type)): - raise TypeError("not expecting type '%s'" % type(s)) + # Optimization: Fast return for the common case. + if type(s) is str: + return s if PY2 and isinstance(s, text_type): - s = s.encode(encoding, errors) + return s.encode(encoding, errors) elif PY3 and isinstance(s, binary_type): - s = s.decode(encoding, errors) + return s.decode(encoding, errors) + elif not isinstance(s, (text_type, binary_type)): + raise TypeError("not expecting type '%s'" % type(s)) return s @@ -908,10 +955,9 @@ def ensure_text(s, encoding='utf-8', errors='strict'): raise TypeError("not expecting type '%s'" % type(s)) - def python_2_unicode_compatible(klass): """ - A decorator that defines __unicode__ and __str__ methods under Python 2. + A class decorator that defines __unicode__ and __str__ methods under Python 2. Under Python 3 it does nothing. To support Python 2 and 3 with a single code base, define a __str__ method diff --git a/pipenv/vendor/backports/shutil_get_terminal_size/LICENSE b/pipenv/vendor/termcolor.COPYING.txt similarity index 94% rename from pipenv/vendor/backports/shutil_get_terminal_size/LICENSE rename to pipenv/vendor/termcolor.COPYING.txt index d62803cf..d5df9763 100644 --- a/pipenv/vendor/backports/shutil_get_terminal_size/LICENSE +++ b/pipenv/vendor/termcolor.COPYING.txt @@ -1,6 +1,4 @@ -The MIT License (MIT) - -Copyright (c) 2014 Christopher Rosell +Copyright (c) 2008-2011 Volvox Development Team Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pipenv/vendor/yaspin/termcolor.py b/pipenv/vendor/termcolor.py similarity index 100% rename from pipenv/vendor/yaspin/termcolor.py rename to pipenv/vendor/termcolor.py diff --git a/pipenv/vendor/toml/LICENSE b/pipenv/vendor/toml/LICENSE index 08e981ff..5010e307 100644 --- a/pipenv/vendor/toml/LICENSE +++ b/pipenv/vendor/toml/LICENSE @@ -1,11 +1,12 @@ The MIT License -Copyright 2013-2018 William Pearson +Copyright 2013-2019 William Pearson Copyright 2015-2016 Julien Enselme Copyright 2016 Google Inc. Copyright 2017 Samuel Vasko Copyright 2017 Nate Prewitt Copyright 2017 Jack Evans +Copyright 2019 Filippo Broggini Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pipenv/vendor/toml/__init__.py b/pipenv/vendor/toml/__init__.py index e436b271..5dd5b1ce 100644 --- a/pipenv/vendor/toml/__init__.py +++ b/pipenv/vendor/toml/__init__.py @@ -3,19 +3,23 @@ Released under the MIT license. """ -from toml import encoder -from toml import decoder +from pipenv.vendor.toml import encoder +from pipenv.vendor.toml import decoder -__version__ = "0.10.0" +__version__ = "0.10.2" _spec_ = "0.5.0" load = decoder.load loads = decoder.loads TomlDecoder = decoder.TomlDecoder TomlDecodeError = decoder.TomlDecodeError +TomlPreserveCommentDecoder = decoder.TomlPreserveCommentDecoder dump = encoder.dump dumps = encoder.dumps TomlEncoder = encoder.TomlEncoder TomlArraySeparatorEncoder = encoder.TomlArraySeparatorEncoder TomlPreserveInlineDictEncoder = encoder.TomlPreserveInlineDictEncoder +TomlNumpyEncoder = encoder.TomlNumpyEncoder +TomlPreserveCommentEncoder = encoder.TomlPreserveCommentEncoder +TomlPathlibEncoder = encoder.TomlPathlibEncoder diff --git a/pipenv/vendor/toml/decoder.py b/pipenv/vendor/toml/decoder.py index 4d468dd4..62719155 100644 --- a/pipenv/vendor/toml/decoder.py +++ b/pipenv/vendor/toml/decoder.py @@ -4,7 +4,7 @@ from os import linesep import re import sys -from toml.tz import TomlTz +from pipenv.vendor.toml.tz import TomlTz if sys.version_info < (3,): _range = xrange # noqa: F821 @@ -24,7 +24,7 @@ def _detect_pathlib_path(p): def _ispath(p): - if isinstance(p, basestring): + if isinstance(p, (bytes, basestring)): return True return _detect_pathlib_path(p) @@ -44,7 +44,7 @@ except NameError: FNFError = IOError -TIME_RE = re.compile("([0-9]{2}):([0-9]{2}):([0-9]{2})(\.([0-9]{3,6}))?") +TIME_RE = re.compile(r"([0-9]{2}):([0-9]{2}):([0-9]{2})(\.([0-9]{3,6}))?") class TomlDecodeError(ValueError): @@ -66,6 +66,27 @@ class TomlDecodeError(ValueError): _number_with_underscores = re.compile('([0-9])(_([0-9]))*') +class CommentValue(object): + def __init__(self, val, comment, beginline, _dict): + self.val = val + separator = "\n" if beginline else " " + self.comment = separator + comment + self._dict = _dict + + def __getitem__(self, key): + return self.val[key] + + def __setitem__(self, key, value): + self.val[key] = value + + def dump(self, dump_value_func): + retstr = dump_value_func(self.val) + if isinstance(self.val, self._dict): + return self.comment + "\n" + unicode(retstr) + else: + return unicode(retstr) + self.comment + + def _strictly_valid_num(n): n = n.strip() if not n: @@ -96,6 +117,7 @@ def load(f, _dict=dict, decoder=None): f: Path to the file to open, array of files to read into single dict or a file descriptor _dict: (optional) Specifies the class of the returned toml dictionary + decoder: The decoder to use Returns: Parsed toml file represented as a dictionary @@ -120,9 +142,9 @@ def load(f, _dict=dict, decoder=None): "existing file.") raise FNFError(error_msg) if decoder is None: - decoder = TomlDecoder() + decoder = TomlDecoder(_dict) d = decoder.get_empty_table() - for l in f: + for l in f: # noqa: E741 if op.exists(l): d.update(load(l, _dict, decoder)) else: @@ -177,19 +199,30 @@ def loads(s, _dict=dict, decoder=None): keygroup = False dottedkey = False keyname = 0 + key = '' + prev_key = '' + line_no = 1 + for i, item in enumerate(sl): if item == '\r' and sl[i + 1] == '\n': sl[i] = ' ' continue if keyname: + key += item if item == '\n': raise TomlDecodeError("Key name found without value." " Reached end of line.", original, i) if openstring: if item == openstrchar: - keyname = 2 - openstring = False - openstrchar = "" + oddbackslash = False + k = 1 + while i >= k and sl[i - k] == '\\': + oddbackslash = not oddbackslash + k += 1 + if not oddbackslash: + keyname = 2 + openstring = False + openstrchar = "" continue elif keyname == 1: if item.isspace(): @@ -220,6 +253,8 @@ def loads(s, _dict=dict, decoder=None): continue if item == '=': keyname = 0 + prev_key = key[:-1].rstrip() + key = '' dottedkey = False else: raise TomlDecodeError("Found invalid character in key name: '" + @@ -272,12 +307,16 @@ def loads(s, _dict=dict, decoder=None): if item == '#' and (not openstring and not keygroup and not arrayoftables): j = i + comment = "" try: while sl[j] != '\n': + comment += s[j] sl[j] = ' ' j += 1 except IndexError: break + if not openarr: + decoder.preserve_comment(line_no, prev_key, comment, beginline) if item == '[' and (not openstring and not keygroup and not arrayoftables): if beginline: @@ -308,12 +347,20 @@ def loads(s, _dict=dict, decoder=None): sl[i] = ' ' else: beginline = True + line_no += 1 elif beginline and sl[i] != ' ' and sl[i] != '\t': beginline = False if not keygroup and not arrayoftables: if sl[i] == '=': raise TomlDecodeError("Found empty keyname. ", original, i) keyname = 1 + key += item + if keyname: + raise TomlDecodeError("Key name found without value." + " Reached end of file.", original, len(s)) + if openstring: # reached EOF and have an unterminated string + raise TomlDecodeError("Unterminated string found." + " Reached end of file.", original, len(s)) s = ''.join(sl) s = s.split('\n') multikey = None @@ -323,6 +370,9 @@ def loads(s, _dict=dict, decoder=None): for idx, line in enumerate(s): if idx > 0: pos += len(s[idx - 1]) + 1 + + decoder.embed_comments(idx, currentlevel) + if not multilinestr or multibackslash or '\n' not in multilinestr: line = line.strip() if line == "" and (not multikey or multibackslash): @@ -333,9 +383,14 @@ def loads(s, _dict=dict, decoder=None): else: multilinestr += line multibackslash = False - if len(line) > 2 and (line[-1] == multilinestr[0] and - line[-2] == multilinestr[0] and - line[-3] == multilinestr[0]): + closed = False + if multilinestr[0] == '[': + closed = line[-1] == ']' + elif len(line) > 2: + closed = (line[-1] == multilinestr[0] and + line[-2] == multilinestr[0] and + line[-3] == multilinestr[0]) + if closed: try: value, vtype = decoder.load_value(multilinestr) except ValueError as err: @@ -385,7 +440,8 @@ def loads(s, _dict=dict, decoder=None): groups[i][0] == "'"): groupstr = groups[i] j = i + 1 - while not groupstr[0] == groupstr[-1]: + while ((not groupstr[0] == groupstr[-1]) or + len(groupstr) == 1): j += 1 if j > len(groups) + 2: raise TomlDecodeError("Invalid group name '" + @@ -663,7 +719,8 @@ class TomlDecoder(object): while len(pair[-1]) and (pair[-1][0] != ' ' and pair[-1][0] != '\t' and pair[-1][0] != "'" and pair[-1][0] != '"' and pair[-1][0] != '[' and pair[-1][0] != '{' and - pair[-1] != 'true' and pair[-1] != 'false'): + pair[-1].strip() != 'true' and + pair[-1].strip() != 'false'): try: float(pair[-1]) break @@ -671,6 +728,8 @@ class TomlDecoder(object): pass if _load_date(pair[-1]) is not None: break + if TIME_RE.match(pair[-1]): + break i += 1 prev_val = pair[-1] pair = line.split('=', i) @@ -704,16 +763,10 @@ class TomlDecoder(object): pair[0] = levels[-1].strip() elif (pair[0][0] == '"' or pair[0][0] == "'") and \ (pair[0][-1] == pair[0][0]): - pair[0] = pair[0][1:-1] - if len(pair[1]) > 2 and ((pair[1][0] == '"' or pair[1][0] == "'") and - pair[1][1] == pair[1][0] and - pair[1][2] == pair[1][0] and - not (len(pair[1]) > 5 and - pair[1][-1] == pair[1][0] and - pair[1][-2] == pair[1][0] and - pair[1][-3] == pair[1][0])): - k = len(pair[1]) - 1 - while k > -1 and pair[1][k] == '\\': + pair[0] = _unescape(pair[0][1:-1]) + k, koffset = self._load_line_multiline_str(pair[1]) + if k > -1: + while k > -1 and pair[1][k + koffset] == '\\': multibackslash = not multibackslash k -= 1 if multibackslash: @@ -734,13 +787,37 @@ class TomlDecoder(object): else: currentlevel[pair[0]] = value + def _load_line_multiline_str(self, p): + poffset = 0 + if len(p) < 3: + return -1, poffset + if p[0] == '[' and (p.strip()[-1] != ']' and + self._load_array_isstrarray(p)): + newp = p[1:].strip().split(',') + while len(newp) > 1 and newp[-1][0] != '"' and newp[-1][0] != "'": + newp = newp[:-2] + [newp[-2] + ',' + newp[-1]] + newp = newp[-1] + poffset = len(p) - len(newp) + p = newp + if p[0] != '"' and p[0] != "'": + return -1, poffset + if p[1] != p[0] or p[2] != p[0]: + return -1, poffset + if len(p) > 5 and p[-1] == p[0] and p[-2] == p[0] and p[-3] == p[0]: + return -1, poffset + return len(p) - 1, poffset + def load_value(self, v, strictly_valid=True): if not v: raise ValueError("Empty value is invalid") if v == 'true': return (True, "bool") + elif v.lower() == 'true': + raise ValueError("Only all lowercase booleans allowed") elif v == 'false': return (False, "bool") + elif v.lower() == 'false': + raise ValueError("Only all lowercase booleans allowed") elif v[0] == '"' or v[0] == "'": quotechar = v[0] testv = v[1:].split(quotechar) @@ -769,7 +846,8 @@ class TomlDecoder(object): pass if not oddbackslash: if closed: - raise ValueError("Stuff after closed string. WTF?") + raise ValueError("Found tokens after a closed " + + "string. Invalid TOML.") else: if not triplequote or triplequotecount > 1: closed = True @@ -857,15 +935,18 @@ class TomlDecoder(object): break return not backslash + def _load_array_isstrarray(self, a): + a = a[1:-1].strip() + if a != '' and (a[0] == '"' or a[0] == "'"): + return True + return False + def load_array(self, a): atype = None retval = [] a = a.strip() if '[' not in a[1:-1] or "" != a[1:-1].split('[')[0].strip(): - strarray = False - tmpa = a[1:-1].strip() - if tmpa != '' and (tmpa[0] == '"' or tmpa[0] == "'"): - strarray = True + strarray = self._load_array_isstrarray(a) if not a[1:-1].strip().startswith('{'): a = a[1:-1].split(',') else: @@ -874,6 +955,7 @@ class TomlDecoder(object): new_a = [] start_group_index = 1 end_group_index = 2 + open_bracket_count = 1 if a[start_group_index] == '{' else 0 in_str = False while end_group_index < len(a[1:]): if a[end_group_index] == '"' or a[end_group_index] == "'": @@ -884,9 +966,15 @@ class TomlDecoder(object): in_str = not in_str backslash_index -= 1 in_str = not in_str + if not in_str and a[end_group_index] == '{': + open_bracket_count += 1 if in_str or a[end_group_index] != '}': end_group_index += 1 continue + elif a[end_group_index] == '}' and open_bracket_count > 1: + open_bracket_count -= 1 + end_group_index += 1 + continue # Increase end_group_index by 1 to get the closing bracket end_group_index += 1 @@ -943,3 +1031,27 @@ class TomlDecoder(object): atype = ntype retval.append(nval) return retval + + def preserve_comment(self, line_no, key, comment, beginline): + pass + + def embed_comments(self, idx, currentlevel): + pass + + +class TomlPreserveCommentDecoder(TomlDecoder): + + def __init__(self, _dict=dict): + self.saved_comments = {} + super(TomlPreserveCommentDecoder, self).__init__(_dict) + + def preserve_comment(self, line_no, key, comment, beginline): + self.saved_comments[line_no] = (key, comment, beginline) + + def embed_comments(self, idx, currentlevel): + if idx not in self.saved_comments: + return + + key, comment, beginline = self.saved_comments[idx] + currentlevel[key] = CommentValue(currentlevel[key], comment, beginline, + self._dict) diff --git a/pipenv/vendor/toml/encoder.py b/pipenv/vendor/toml/encoder.py index 79bfd37b..b04756ec 100644 --- a/pipenv/vendor/toml/encoder.py +++ b/pipenv/vendor/toml/encoder.py @@ -1,19 +1,21 @@ import datetime import re import sys +from decimal import Decimal -from toml.decoder import InlineTableDict +from pipenv.vendor.toml.decoder import InlineTableDict if sys.version_info >= (3,): unicode = str -def dump(o, f): +def dump(o, f, encoder=None): """Writes out dict as toml to a file Args: o: Object to dump into toml f: File descriptor where the toml should be stored + encoder: The ``TomlEncoder`` to use for constructing the output string Returns: String containing the toml corresponding to dictionary @@ -24,7 +26,7 @@ def dump(o, f): if not f.write: raise TypeError("You can only dump an object to a file descriptor") - d = dumps(o) + d = dumps(o, encoder=encoder) f.write(d) return d @@ -34,11 +36,22 @@ def dumps(o, encoder=None): Args: o: Object to dump into toml - - preserve: Boolean parameter. If true, preserve inline tables. + encoder: The ``TomlEncoder`` to use for constructing the output string Returns: String containing the toml corresponding to dict + + Examples: + ```python + >>> import toml + >>> output = { + ... 'a': "I'm a string", + ... 'b': ["I'm", "a", "list"], + ... 'c': 2400 + ... } + >>> toml.dumps(output) + 'a = "I\'m a string"\nb = [ "I\'m", "a", "list",]\nc = 2400\n' + ``` """ retval = "" @@ -46,7 +59,13 @@ def dumps(o, encoder=None): encoder = TomlEncoder(o.__class__) addtoretval, sections = encoder.dump_sections(o, "") retval += addtoretval + outer_objs = [id(o)] while sections: + section_ids = [id(section) for section in sections.values()] + for outer_obj in outer_objs: + if outer_obj in section_ids: + raise ValueError("Circular reference detected") + outer_objs += section_ids newsections = encoder.get_empty_table() for section in sections: addtoretval, addtosections = encoder.dump_sections( @@ -96,7 +115,7 @@ def _dump_str(v): def _dump_float(v): - return "{0:.16}".format(v).replace("e+0", "e+").replace("e-0", "e-") + return "{}".format(v).replace("e+0", "e+").replace("e-0", "e-") def _dump_time(v): @@ -119,6 +138,7 @@ class TomlEncoder(object): bool: lambda v: unicode(v).lower(), int: lambda v: v, float: _dump_float, + Decimal: _dump_float, datetime.datetime: lambda v: v.isoformat().replace('+00:00', 'Z'), datetime.time: _dump_time, datetime.date: lambda v: v.isoformat() @@ -169,10 +189,7 @@ class TomlEncoder(object): section = unicode(section) qsection = section if not re.match(r'^[A-Za-z0-9_-]+$', section): - if '"' in section: - qsection = "'" + section + "'" - else: - qsection = '"' + section + '"' + qsection = _dump_str(section) if not isinstance(o[section], dict): arrayoftables = False if isinstance(o[section], list): @@ -248,3 +265,40 @@ class TomlArraySeparatorEncoder(TomlEncoder): t = s retval += "]" return retval + + +class TomlNumpyEncoder(TomlEncoder): + + def __init__(self, _dict=dict, preserve=False): + import numpy as np + super(TomlNumpyEncoder, self).__init__(_dict, preserve) + self.dump_funcs[np.float16] = _dump_float + self.dump_funcs[np.float32] = _dump_float + self.dump_funcs[np.float64] = _dump_float + self.dump_funcs[np.int16] = self._dump_int + self.dump_funcs[np.int32] = self._dump_int + self.dump_funcs[np.int64] = self._dump_int + + def _dump_int(self, v): + return "{}".format(int(v)) + + +class TomlPreserveCommentEncoder(TomlEncoder): + + def __init__(self, _dict=dict, preserve=False): + from pipenv.vendor.toml.decoder import CommentValue + super(TomlPreserveCommentEncoder, self).__init__(_dict, preserve) + self.dump_funcs[CommentValue] = lambda v: v.dump(self.dump_value) + + +class TomlPathlibEncoder(TomlEncoder): + + def _dump_pathlib_path(self, v): + return _dump_str(str(v)) + + def dump_value(self, v): + if (3, 4) <= sys.version_info: + import pathlib + if isinstance(v, pathlib.PurePath): + v = str(v) + return super(TomlPathlibEncoder, self).dump_value(v) diff --git a/pipenv/vendor/toml/ordered.py b/pipenv/vendor/toml/ordered.py index 9c20c41a..3c9a8ef2 100644 --- a/pipenv/vendor/toml/ordered.py +++ b/pipenv/vendor/toml/ordered.py @@ -1,6 +1,6 @@ from collections import OrderedDict -from toml import TomlEncoder -from toml import TomlDecoder +from pipenv.vendor.toml import TomlEncoder +from pipenv.vendor.toml import TomlDecoder class TomlOrderedDecoder(TomlDecoder): diff --git a/pipenv/vendor/toml/tz.py b/pipenv/vendor/toml/tz.py index 93c3c8ad..bf20593a 100644 --- a/pipenv/vendor/toml/tz.py +++ b/pipenv/vendor/toml/tz.py @@ -11,6 +11,9 @@ class TomlTz(tzinfo): self._hours = int(self._raw_offset[1:3]) self._minutes = int(self._raw_offset[4:6]) + def __deepcopy__(self, memo): + return self.__class__(self._raw_offset) + def tzname(self, dt): return "UTC" + self._raw_offset diff --git a/pipenv/vendor/click_completion/LICENSE b/pipenv/vendor/tomli/LICENSE similarity index 94% rename from pipenv/vendor/click_completion/LICENSE rename to pipenv/vendor/tomli/LICENSE index d1073bb7..e859590f 100644 --- a/pipenv/vendor/click_completion/LICENSE +++ b/pipenv/vendor/tomli/LICENSE @@ -1,6 +1,6 @@ -The MIT License (MIT) +MIT License -Copyright (c) 2016 Gaëtan Lehmann +Copyright (c) 2021 Taneli Hukkinen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pipenv/vendor/tomli/__init__.py b/pipenv/vendor/tomli/__init__.py new file mode 100644 index 00000000..88e81d92 --- /dev/null +++ b/pipenv/vendor/tomli/__init__.py @@ -0,0 +1,6 @@ +"""A lil' TOML parser.""" + +__all__ = ("loads", "load", "TOMLDecodeError") +__version__ = "1.1.0" # DO NOT EDIT THIS LINE MANUALLY. LET bump2version UTILITY DO IT + +from pipenv.vendor.tomli._parser import TOMLDecodeError, load, loads diff --git a/pipenv/vendor/tomli/_parser.py b/pipenv/vendor/tomli/_parser.py new file mode 100644 index 00000000..5332915b --- /dev/null +++ b/pipenv/vendor/tomli/_parser.py @@ -0,0 +1,667 @@ +import string +from types import MappingProxyType +from typing import ( + IO, + Any, + Callable, + Dict, + FrozenSet, + Iterable, + NamedTuple, + Optional, + Tuple, +) + +from pipenv.vendor.tomli._re import ( + RE_DATETIME, + RE_LOCALTIME, + RE_NUMBER, + match_to_datetime, + match_to_localtime, + match_to_number, +) + +ASCII_CTRL = frozenset(chr(i) for i in range(32)) | frozenset(chr(127)) + +# Neither of these sets include quotation mark or backslash. They are +# currently handled as separate cases in the parser functions. +ILLEGAL_BASIC_STR_CHARS = ASCII_CTRL - frozenset("\t") +ILLEGAL_MULTILINE_BASIC_STR_CHARS = ASCII_CTRL - frozenset("\t\n\r") + +ILLEGAL_LITERAL_STR_CHARS = ILLEGAL_BASIC_STR_CHARS +ILLEGAL_MULTILINE_LITERAL_STR_CHARS = ASCII_CTRL - frozenset("\t\n") + +ILLEGAL_COMMENT_CHARS = ILLEGAL_BASIC_STR_CHARS + +TOML_WS = frozenset(" \t") +TOML_WS_AND_NEWLINE = TOML_WS | frozenset("\n") +BARE_KEY_CHARS = frozenset(string.ascii_letters + string.digits + "-_") +KEY_INITIAL_CHARS = BARE_KEY_CHARS | frozenset("\"'") +HEXDIGIT_CHARS = frozenset(string.hexdigits) + +BASIC_STR_ESCAPE_REPLACEMENTS = MappingProxyType( + { + "\\b": "\u0008", # backspace + "\\t": "\u0009", # tab + "\\n": "\u000A", # linefeed + "\\f": "\u000C", # form feed + "\\r": "\u000D", # carriage return + '\\"': "\u0022", # quote + "\\\\": "\u005C", # backslash + } +) + +# Type annotations +ParseFloat = Callable[[str], Any] +Key = Tuple[str, ...] +Pos = int + + +class TOMLDecodeError(ValueError): + """An error raised if a document is not valid TOML.""" + + +def load(fp: IO, *, parse_float: ParseFloat = float) -> Dict[str, Any]: + """Parse TOML from a file object.""" + s = fp.read() + if isinstance(s, bytes): + s = s.decode() + return loads(s, parse_float=parse_float) + + +def loads(s: str, *, parse_float: ParseFloat = float) -> Dict[str, Any]: # noqa: C901 + """Parse TOML from a string.""" + + # The spec allows converting "\r\n" to "\n", even in string + # literals. Let's do so to simplify parsing. + src = s.replace("\r\n", "\n") + pos = 0 + out = Output(NestedDict(), Flags()) + header: Key = () + + # Parse one statement at a time + # (typically means one line in TOML source) + while True: + # 1. Skip line leading whitespace + pos = skip_chars(src, pos, TOML_WS) + + # 2. Parse rules. Expect one of the following: + # - end of file + # - end of line + # - comment + # - key/value pair + # - append dict to list (and move to its namespace) + # - create dict (and move to its namespace) + # Skip trailing whitespace when applicable. + try: + char = src[pos] + except IndexError: + break + if char == "\n": + pos += 1 + continue + if char in KEY_INITIAL_CHARS: + pos = key_value_rule(src, pos, out, header, parse_float) + pos = skip_chars(src, pos, TOML_WS) + elif char == "[": + try: + second_char: Optional[str] = src[pos + 1] + except IndexError: + second_char = None + if second_char == "[": + pos, header = create_list_rule(src, pos, out) + else: + pos, header = create_dict_rule(src, pos, out) + pos = skip_chars(src, pos, TOML_WS) + elif char != "#": + raise suffixed_err(src, pos, "Invalid statement") + + # 3. Skip comment + pos = skip_comment(src, pos) + + # 4. Expect end of line or end of file + try: + char = src[pos] + except IndexError: + break + if char != "\n": + raise suffixed_err( + src, pos, "Expected newline or end of document after a statement" + ) + pos += 1 + + return out.data.dict + + +class Flags: + """Flags that map to parsed keys/namespaces.""" + + # Marks an immutable namespace (inline array or inline table). + FROZEN = 0 + # Marks a nest that has been explicitly created and can no longer + # be opened using the "[table]" syntax. + EXPLICIT_NEST = 1 + + def __init__(self) -> None: + self._flags: Dict[str, dict] = {} + + def unset_all(self, key: Key) -> None: + cont = self._flags + for k in key[:-1]: + if k not in cont: + return + cont = cont[k]["nested"] + cont.pop(key[-1], None) + + def set_for_relative_key(self, head_key: Key, rel_key: Key, flag: int) -> None: + cont = self._flags + for k in head_key: + if k not in cont: + cont[k] = {"flags": set(), "recursive_flags": set(), "nested": {}} + cont = cont[k]["nested"] + for k in rel_key: + if k in cont: + cont[k]["flags"].add(flag) + else: + cont[k] = {"flags": {flag}, "recursive_flags": set(), "nested": {}} + cont = cont[k]["nested"] + + def set(self, key: Key, flag: int, *, recursive: bool) -> None: # noqa: A003 + cont = self._flags + key_parent, key_stem = key[:-1], key[-1] + for k in key_parent: + if k not in cont: + cont[k] = {"flags": set(), "recursive_flags": set(), "nested": {}} + cont = cont[k]["nested"] + if key_stem not in cont: + cont[key_stem] = {"flags": set(), "recursive_flags": set(), "nested": {}} + cont[key_stem]["recursive_flags" if recursive else "flags"].add(flag) + + def is_(self, key: Key, flag: int) -> bool: + if not key: + return False # document root has no flags + cont = self._flags + for k in key[:-1]: + if k not in cont: + return False + inner_cont = cont[k] + if flag in inner_cont["recursive_flags"]: + return True + cont = inner_cont["nested"] + key_stem = key[-1] + if key_stem in cont: + cont = cont[key_stem] + return flag in cont["flags"] or flag in cont["recursive_flags"] + return False + + +class NestedDict: + def __init__(self) -> None: + # The parsed content of the TOML document + self.dict: Dict[str, Any] = {} + + def get_or_create_nest( + self, + key: Key, + *, + access_lists: bool = True, + ) -> dict: + cont: Any = self.dict + for k in key: + if k not in cont: + cont[k] = {} + cont = cont[k] + if access_lists and isinstance(cont, list): + cont = cont[-1] + if not isinstance(cont, dict): + raise KeyError("There is no nest behind this key") + return cont + + def append_nest_to_list(self, key: Key) -> None: + cont = self.get_or_create_nest(key[:-1]) + last_key = key[-1] + if last_key in cont: + list_ = cont[last_key] + if not isinstance(list_, list): + raise KeyError("An object other than list found behind this key") + list_.append({}) + else: + cont[last_key] = [{}] + + +class Output(NamedTuple): + data: NestedDict + flags: Flags + + +def skip_chars(src: str, pos: Pos, chars: Iterable[str]) -> Pos: + try: + while src[pos] in chars: + pos += 1 + except IndexError: + pass + return pos + + +def skip_until( + src: str, + pos: Pos, + expect: str, + *, + error_on: FrozenSet[str], + error_on_eof: bool, +) -> Pos: + try: + new_pos = src.index(expect, pos) + except ValueError: + new_pos = len(src) + if error_on_eof: + raise suffixed_err(src, new_pos, f'Expected "{expect!r}"') + + if not error_on.isdisjoint(src[pos:new_pos]): + while src[pos] not in error_on: + pos += 1 + raise suffixed_err(src, pos, f'Found invalid character "{src[pos]!r}"') + return new_pos + + +def skip_comment(src: str, pos: Pos) -> Pos: + try: + char: Optional[str] = src[pos] + except IndexError: + char = None + if char == "#": + return skip_until( + src, pos + 1, "\n", error_on=ILLEGAL_COMMENT_CHARS, error_on_eof=False + ) + return pos + + +def skip_comments_and_array_ws(src: str, pos: Pos) -> Pos: + while True: + pos_before_skip = pos + pos = skip_chars(src, pos, TOML_WS_AND_NEWLINE) + pos = skip_comment(src, pos) + if pos == pos_before_skip: + return pos + + +def create_dict_rule(src: str, pos: Pos, out: Output) -> Tuple[Pos, Key]: + pos += 1 # Skip "[" + pos = skip_chars(src, pos, TOML_WS) + pos, key = parse_key(src, pos) + + if out.flags.is_(key, Flags.EXPLICIT_NEST) or out.flags.is_(key, Flags.FROZEN): + raise suffixed_err(src, pos, f"Can not declare {key} twice") + out.flags.set(key, Flags.EXPLICIT_NEST, recursive=False) + try: + out.data.get_or_create_nest(key) + except KeyError: + raise suffixed_err(src, pos, "Can not overwrite a value") + + if not src.startswith("]", pos): + raise suffixed_err(src, pos, 'Expected "]" at the end of a table declaration') + return pos + 1, key + + +def create_list_rule(src: str, pos: Pos, out: Output) -> Tuple[Pos, Key]: + pos += 2 # Skip "[[" + pos = skip_chars(src, pos, TOML_WS) + pos, key = parse_key(src, pos) + + if out.flags.is_(key, Flags.FROZEN): + raise suffixed_err(src, pos, f"Can not mutate immutable namespace {key}") + # Free the namespace now that it points to another empty list item... + out.flags.unset_all(key) + # ...but this key precisely is still prohibited from table declaration + out.flags.set(key, Flags.EXPLICIT_NEST, recursive=False) + try: + out.data.append_nest_to_list(key) + except KeyError: + raise suffixed_err(src, pos, "Can not overwrite a value") + + if not src.startswith("]]", pos): + raise suffixed_err(src, pos, 'Expected "]]" at the end of an array declaration') + return pos + 2, key + + +def key_value_rule( + src: str, pos: Pos, out: Output, header: Key, parse_float: ParseFloat +) -> Pos: + pos, key, value = parse_key_value_pair(src, pos, parse_float) + key_parent, key_stem = key[:-1], key[-1] + abs_key_parent = header + key_parent + + if out.flags.is_(abs_key_parent, Flags.FROZEN): + raise suffixed_err( + src, pos, f"Can not mutate immutable namespace {abs_key_parent}" + ) + # Containers in the relative path can't be opened with the table syntax after this + out.flags.set_for_relative_key(header, key, Flags.EXPLICIT_NEST) + try: + nest = out.data.get_or_create_nest(abs_key_parent) + except KeyError: + raise suffixed_err(src, pos, "Can not overwrite a value") + if key_stem in nest: + raise suffixed_err(src, pos, "Can not overwrite a value") + # Mark inline table and array namespaces recursively immutable + if isinstance(value, (dict, list)): + out.flags.set(header + key, Flags.FROZEN, recursive=True) + nest[key_stem] = value + return pos + + +def parse_key_value_pair( + src: str, pos: Pos, parse_float: ParseFloat +) -> Tuple[Pos, Key, Any]: + pos, key = parse_key(src, pos) + try: + char: Optional[str] = src[pos] + except IndexError: + char = None + if char != "=": + raise suffixed_err(src, pos, 'Expected "=" after a key in a key/value pair') + pos += 1 + pos = skip_chars(src, pos, TOML_WS) + pos, value = parse_value(src, pos, parse_float) + return pos, key, value + + +def parse_key(src: str, pos: Pos) -> Tuple[Pos, Key]: + pos, key_part = parse_key_part(src, pos) + key: Key = (key_part,) + pos = skip_chars(src, pos, TOML_WS) + while True: + try: + char: Optional[str] = src[pos] + except IndexError: + char = None + if char != ".": + return pos, key + pos += 1 + pos = skip_chars(src, pos, TOML_WS) + pos, key_part = parse_key_part(src, pos) + key += (key_part,) + pos = skip_chars(src, pos, TOML_WS) + + +def parse_key_part(src: str, pos: Pos) -> Tuple[Pos, str]: + try: + char: Optional[str] = src[pos] + except IndexError: + char = None + if char in BARE_KEY_CHARS: + start_pos = pos + pos = skip_chars(src, pos, BARE_KEY_CHARS) + return pos, src[start_pos:pos] + if char == "'": + return parse_literal_str(src, pos) + if char == '"': + return parse_one_line_basic_str(src, pos) + raise suffixed_err(src, pos, "Invalid initial character for a key part") + + +def parse_one_line_basic_str(src: str, pos: Pos) -> Tuple[Pos, str]: + pos += 1 + return parse_basic_str(src, pos, multiline=False) + + +def parse_array(src: str, pos: Pos, parse_float: ParseFloat) -> Tuple[Pos, list]: + pos += 1 + array: list = [] + + pos = skip_comments_and_array_ws(src, pos) + if src.startswith("]", pos): + return pos + 1, array + while True: + pos, val = parse_value(src, pos, parse_float) + array.append(val) + pos = skip_comments_and_array_ws(src, pos) + + c = src[pos : pos + 1] + if c == "]": + return pos + 1, array + if c != ",": + raise suffixed_err(src, pos, "Unclosed array") + pos += 1 + + pos = skip_comments_and_array_ws(src, pos) + if src.startswith("]", pos): + return pos + 1, array + + +def parse_inline_table(src: str, pos: Pos, parse_float: ParseFloat) -> Tuple[Pos, dict]: + pos += 1 + nested_dict = NestedDict() + flags = Flags() + + pos = skip_chars(src, pos, TOML_WS) + if src.startswith("}", pos): + return pos + 1, nested_dict.dict + while True: + pos, key, value = parse_key_value_pair(src, pos, parse_float) + key_parent, key_stem = key[:-1], key[-1] + if flags.is_(key, Flags.FROZEN): + raise suffixed_err(src, pos, f"Can not mutate immutable namespace {key}") + try: + nest = nested_dict.get_or_create_nest(key_parent, access_lists=False) + except KeyError: + raise suffixed_err(src, pos, "Can not overwrite a value") + if key_stem in nest: + raise suffixed_err(src, pos, f'Duplicate inline table key "{key_stem}"') + nest[key_stem] = value + pos = skip_chars(src, pos, TOML_WS) + c = src[pos : pos + 1] + if c == "}": + return pos + 1, nested_dict.dict + if c != ",": + raise suffixed_err(src, pos, "Unclosed inline table") + if isinstance(value, (dict, list)): + flags.set(key, Flags.FROZEN, recursive=True) + pos += 1 + pos = skip_chars(src, pos, TOML_WS) + + +def parse_basic_str_escape( # noqa: C901 + src: str, pos: Pos, *, multiline: bool = False +) -> Tuple[Pos, str]: + escape_id = src[pos : pos + 2] + pos += 2 + if multiline and escape_id in {"\\ ", "\\\t", "\\\n"}: + # Skip whitespace until next non-whitespace character or end of + # the doc. Error if non-whitespace is found before newline. + if escape_id != "\\\n": + pos = skip_chars(src, pos, TOML_WS) + try: + char = src[pos] + except IndexError: + return pos, "" + if char != "\n": + raise suffixed_err(src, pos, 'Unescaped "\\" in a string') + pos += 1 + pos = skip_chars(src, pos, TOML_WS_AND_NEWLINE) + return pos, "" + if escape_id == "\\u": + return parse_hex_char(src, pos, 4) + if escape_id == "\\U": + return parse_hex_char(src, pos, 8) + try: + return pos, BASIC_STR_ESCAPE_REPLACEMENTS[escape_id] + except KeyError: + if len(escape_id) != 2: + raise suffixed_err(src, pos, "Unterminated string") + raise suffixed_err(src, pos, 'Unescaped "\\" in a string') + + +def parse_basic_str_escape_multiline(src: str, pos: Pos) -> Tuple[Pos, str]: + return parse_basic_str_escape(src, pos, multiline=True) + + +def parse_hex_char(src: str, pos: Pos, hex_len: int) -> Tuple[Pos, str]: + hex_str = src[pos : pos + hex_len] + if len(hex_str) != hex_len or not HEXDIGIT_CHARS.issuperset(hex_str): + raise suffixed_err(src, pos, "Invalid hex value") + pos += hex_len + hex_int = int(hex_str, 16) + if not is_unicode_scalar_value(hex_int): + raise suffixed_err(src, pos, "Escaped character is not a Unicode scalar value") + return pos, chr(hex_int) + + +def parse_literal_str(src: str, pos: Pos) -> Tuple[Pos, str]: + pos += 1 # Skip starting apostrophe + start_pos = pos + pos = skip_until( + src, pos, "'", error_on=ILLEGAL_LITERAL_STR_CHARS, error_on_eof=True + ) + return pos + 1, src[start_pos:pos] # Skip ending apostrophe + + +def parse_multiline_str(src: str, pos: Pos, *, literal: bool) -> Tuple[Pos, str]: + pos += 3 + if src.startswith("\n", pos): + pos += 1 + + if literal: + delim = "'" + end_pos = skip_until( + src, + pos, + "'''", + error_on=ILLEGAL_MULTILINE_LITERAL_STR_CHARS, + error_on_eof=True, + ) + result = src[pos:end_pos] + pos = end_pos + 3 + else: + delim = '"' + pos, result = parse_basic_str(src, pos, multiline=True) + + # Add at maximum two extra apostrophes/quotes if the end sequence + # is 4 or 5 chars long instead of just 3. + if not src.startswith(delim, pos): + return pos, result + pos += 1 + if not src.startswith(delim, pos): + return pos, result + delim + pos += 1 + return pos, result + (delim * 2) + + +def parse_basic_str(src: str, pos: Pos, *, multiline: bool) -> Tuple[Pos, str]: + if multiline: + error_on = ILLEGAL_MULTILINE_BASIC_STR_CHARS + parse_escapes = parse_basic_str_escape_multiline + else: + error_on = ILLEGAL_BASIC_STR_CHARS + parse_escapes = parse_basic_str_escape + result = "" + start_pos = pos + while True: + try: + char = src[pos] + except IndexError: + raise suffixed_err(src, pos, "Unterminated string") + if char == '"': + if not multiline: + return pos + 1, result + src[start_pos:pos] + if src.startswith('"""', pos): + return pos + 3, result + src[start_pos:pos] + pos += 1 + continue + if char == "\\": + result += src[start_pos:pos] + pos, parsed_escape = parse_escapes(src, pos) + result += parsed_escape + start_pos = pos + continue + if char in error_on: + raise suffixed_err(src, pos, f'Illegal character "{char!r}"') + pos += 1 + + +def parse_value( # noqa: C901 + src: str, pos: Pos, parse_float: ParseFloat +) -> Tuple[Pos, Any]: + try: + char: Optional[str] = src[pos] + except IndexError: + char = None + + # Basic strings + if char == '"': + if src.startswith('"""', pos): + return parse_multiline_str(src, pos, literal=False) + return parse_one_line_basic_str(src, pos) + + # Literal strings + if char == "'": + if src.startswith("'''", pos): + return parse_multiline_str(src, pos, literal=True) + return parse_literal_str(src, pos) + + # Booleans + if char == "t": + if src.startswith("true", pos): + return pos + 4, True + if char == "f": + if src.startswith("false", pos): + return pos + 5, False + + # Dates and times + datetime_match = RE_DATETIME.match(src, pos) + if datetime_match: + try: + datetime_obj = match_to_datetime(datetime_match) + except ValueError: + raise suffixed_err(src, pos, "Invalid date or datetime") + return datetime_match.end(), datetime_obj + localtime_match = RE_LOCALTIME.match(src, pos) + if localtime_match: + return localtime_match.end(), match_to_localtime(localtime_match) + + # Integers and "normal" floats. + # The regex will greedily match any type starting with a decimal + # char, so needs to be located after handling of dates and times. + number_match = RE_NUMBER.match(src, pos) + if number_match: + return number_match.end(), match_to_number(number_match, parse_float) + + # Arrays + if char == "[": + return parse_array(src, pos, parse_float) + + # Inline tables + if char == "{": + return parse_inline_table(src, pos, parse_float) + + # Special floats + first_three = src[pos : pos + 3] + if first_three in {"inf", "nan"}: + return pos + 3, parse_float(first_three) + first_four = src[pos : pos + 4] + if first_four in {"-inf", "+inf", "-nan", "+nan"}: + return pos + 4, parse_float(first_four) + + raise suffixed_err(src, pos, "Invalid value") + + +def suffixed_err(src: str, pos: Pos, msg: str) -> TOMLDecodeError: + """Return a `TOMLDecodeError` where error message is suffixed with + coordinates in source.""" + + def coord_repr(src: str, pos: Pos) -> str: + if pos >= len(src): + return "end of document" + line = src.count("\n", 0, pos) + 1 + if line == 1: + column = pos + 1 + else: + column = pos - src.rindex("\n", 0, pos) + return f"line {line}, column {column}" + + return TOMLDecodeError(f"{msg} (at {coord_repr(src, pos)})") + + +def is_unicode_scalar_value(codepoint: int) -> bool: + return (0 <= codepoint <= 55295) or (57344 <= codepoint <= 1114111) diff --git a/pipenv/vendor/tomli/_re.py b/pipenv/vendor/tomli/_re.py new file mode 100644 index 00000000..7d42632b --- /dev/null +++ b/pipenv/vendor/tomli/_re.py @@ -0,0 +1,102 @@ +from datetime import date, datetime, time, timedelta, timezone, tzinfo +from functools import lru_cache +import re +from typing import TYPE_CHECKING, Any, Optional, Union + +if TYPE_CHECKING: + from pipenv.vendor.tomli._parser import ParseFloat + +# E.g. +# - 00:32:00.999999 +# - 00:32:00 +_TIME_RE_STR = r"([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])(?:\.([0-9]{1,6})[0-9]*)?" + +RE_NUMBER = re.compile( + r""" +0 +(?: + x[0-9A-Fa-f](?:_?[0-9A-Fa-f])* # hex + | + b[01](?:_?[01])* # bin + | + o[0-7](?:_?[0-7])* # oct +) +| +[+-]?(?:0|[1-9](?:_?[0-9])*) # dec, integer part +(?P<floatpart> + (?:\.[0-9](?:_?[0-9])*)? # optional fractional part + (?:[eE][+-]?[0-9](?:_?[0-9])*)? # optional exponent part +) +""", + flags=re.VERBOSE, +) +RE_LOCALTIME = re.compile(_TIME_RE_STR) +RE_DATETIME = re.compile( + fr""" +([0-9]{{4}})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]) # date, e.g. 1988-10-27 +(?: + [T ] + {_TIME_RE_STR} + (?:(Z)|([+-])([01][0-9]|2[0-3]):([0-5][0-9]))? # optional time offset +)? +""", + flags=re.VERBOSE, +) + + +def match_to_datetime(match: "re.Match") -> Union[datetime, date]: + """Convert a `RE_DATETIME` match to `datetime.datetime` or `datetime.date`. + + Raises ValueError if the match does not correspond to a valid date + or datetime. + """ + ( + year_str, + month_str, + day_str, + hour_str, + minute_str, + sec_str, + micros_str, + zulu_time, + offset_sign_str, + offset_hour_str, + offset_minute_str, + ) = match.groups() + year, month, day = int(year_str), int(month_str), int(day_str) + if hour_str is None: + return date(year, month, day) + hour, minute, sec = int(hour_str), int(minute_str), int(sec_str) + micros = int(micros_str.ljust(6, "0")) if micros_str else 0 + if offset_sign_str: + tz: Optional[tzinfo] = cached_tz( + offset_hour_str, offset_minute_str, offset_sign_str + ) + elif zulu_time: + tz = timezone.utc + else: # local date-time + tz = None + return datetime(year, month, day, hour, minute, sec, micros, tzinfo=tz) + + +@lru_cache(maxsize=None) +def cached_tz(hour_str: str, minute_str: str, sign_str: str) -> timezone: + sign = 1 if sign_str == "+" else -1 + return timezone( + timedelta( + hours=sign * int(hour_str), + minutes=sign * int(minute_str), + ) + ) + + +def match_to_localtime(match: "re.Match") -> time: + hour_str, minute_str, sec_str, micros_str = match.groups() + micros = int(micros_str.ljust(6, "0")) if micros_str else 0 + return time(int(hour_str), int(minute_str), int(sec_str), micros) + + +def match_to_number(match: "re.Match", parse_float: "ParseFloat") -> Any: + if match.group("floatpart"): + return parse_float(match.group()) + return int(match.group(), 0) diff --git a/pipenv/vendor/tomli/py.typed b/pipenv/vendor/tomli/py.typed new file mode 100644 index 00000000..7632ecf7 --- /dev/null +++ b/pipenv/vendor/tomli/py.typed @@ -0,0 +1 @@ +# Marker file for PEP 561 diff --git a/pipenv/vendor/tomlkit/__init__.py b/pipenv/vendor/tomlkit/__init__.py index 9ab90e0a..110d6c19 100644 --- a/pipenv/vendor/tomlkit/__init__.py +++ b/pipenv/vendor/tomlkit/__init__.py @@ -22,4 +22,4 @@ from .api import value from .api import ws -__version__ = "0.5.3" +__version__ = "0.7.2" diff --git a/pipenv/vendor/tomlkit/_compat.py b/pipenv/vendor/tomlkit/_compat.py index b7407af6..487ed990 100644 --- a/pipenv/vendor/tomlkit/_compat.py +++ b/pipenv/vendor/tomlkit/_compat.py @@ -1,6 +1,7 @@ import re import sys + try: from datetime import timezone except ImportError: @@ -67,12 +68,12 @@ except ImportError: if self._name is None: return "%s.%s(%r)" % ( self.__class__.__module__, - self.__class__.__qualname__, + self.__class__.__name__, self._offset, ) return "%s.%s(%r, %r)" % ( self.__class__.__module__, - self.__class__.__qualname__, + self.__class__.__name__, self._offset, self._name, ) @@ -137,6 +138,7 @@ except ImportError: PY2 = sys.version_info[0] == 2 PY36 = sys.version_info >= (3, 6) +PY38 = sys.version_info >= (3, 8) if PY2: unicode = unicode @@ -148,6 +150,17 @@ else: long = int +if PY36: + OrderedDict = dict +else: + from collections import OrderedDict + +try: + from collections.abc import MutableMapping +except ImportError: + from collections import MutableMapping + + def decode(string, encodings=None): if not PY2 and not isinstance(string, bytes): return string diff --git a/pipenv/vendor/tomlkit/_utils.py b/pipenv/vendor/tomlkit/_utils.py index 0a68be9f..2ae3e424 100644 --- a/pipenv/vendor/tomlkit/_utils.py +++ b/pipenv/vendor/tomlkit/_utils.py @@ -4,11 +4,18 @@ from datetime import date from datetime import datetime from datetime import time from datetime import timedelta - +from typing import Union from ._compat import decode from ._compat import timezone + +try: + from collections.abc import Mapping +except ImportError: + from collections import Mapping + + RFC_3339_LOOSE = re.compile( "^" r"(([0-9]+)-(\d{2})-(\d{2}))?" # Date @@ -52,8 +59,6 @@ def parse_rfc3339(string): # type: (str) -> Union[datetime, date, time] if m.group(7): microsecond = int(("{:<06s}".format(m.group(8)))[:6]) - dt = datetime(year, month, day, hour, minute, second, microsecond) - if m.group(9): # Timezone tz = m.group(9) @@ -129,3 +134,11 @@ def escape_string(s): flush() return "".join(res) + + +def merge_dicts(d1, d2): + for k, v in d2.items(): + if k in d1 and isinstance(d1[k], dict) and isinstance(d2[k], Mapping): + merge_dicts(d1[k], d2[k]) + else: + d1[k] = d2[k] diff --git a/pipenv/vendor/tomlkit/api.py b/pipenv/vendor/tomlkit/api.py index 0ac26752..3de41219 100644 --- a/pipenv/vendor/tomlkit/api.py +++ b/pipenv/vendor/tomlkit/api.py @@ -1,26 +1,28 @@ import datetime as _datetime +from typing import Tuple + from ._utils import parse_rfc3339 from .container import Container from .items import AoT -from .items import Comment -from .items import InlineTable -from .items import Item as _Item from .items import Array from .items import Bool -from .items import Key +from .items import Comment from .items import Date from .items import DateTime from .items import Float -from .items import Table +from .items import InlineTable from .items import Integer +from .items import Item as _Item +from .items import Key +from .items import String +from .items import Table +from .items import Time from .items import Trivia from .items import Whitespace -from .items import String from .items import item from .parser import Parser from .toml_document import TOMLDocument as _TOMLDocument -from .items import Time def loads(string): # type: (str) -> _TOMLDocument @@ -32,12 +34,12 @@ def loads(string): # type: (str) -> _TOMLDocument return parse(string) -def dumps(data): # type: (_TOMLDocument) -> str +def dumps(data, sort_keys=False): # type: (_TOMLDocument, bool) -> str """ Dumps a TOMLDocument into a string. """ if not isinstance(data, _TOMLDocument) and isinstance(data, dict): - data = item(data) + data = item(data, _sort_keys=sort_keys) return data.as_string() @@ -109,7 +111,7 @@ def table(): # type: () -> Table def inline_table(): # type: () -> InlineTable - return InlineTable(Container(), Trivia()) + return InlineTable(Container(), Trivia(), new=True) def aot(): # type: () -> AoT diff --git a/pipenv/vendor/tomlkit/container.py b/pipenv/vendor/tomlkit/container.py index 340491c1..e0976368 100644 --- a/pipenv/vendor/tomlkit/container.py +++ b/pipenv/vendor/tomlkit/container.py @@ -2,21 +2,35 @@ from __future__ import unicode_literals import copy +from typing import Any +from typing import Dict +from typing import Generator +from typing import List +from typing import Optional +from typing import Tuple +from typing import Union + +from ._compat import MutableMapping from ._compat import decode +from ._utils import merge_dicts from .exceptions import KeyAlreadyPresent from .exceptions import NonExistentKey +from .exceptions import ParseError +from .exceptions import TOMLKitError from .items import AoT from .items import Comment from .items import Item from .items import Key from .items import Null from .items import Table -from .items import Trivia from .items import Whitespace from .items import item as _item -class Container(dict): +_NOT_SET = object() + + +class Container(MutableMapping, dict): """ A container for items within a TOMLDocument. """ @@ -25,6 +39,7 @@ class Container(dict): self._map = {} # type: Dict[Key, int] self._body = [] # type: List[Tuple[Optional[Key], Item]] self._parsed = parsed + self._table_keys = [] @property def body(self): # type: () -> List[Tuple[Optional[Key], Item]] @@ -44,7 +59,7 @@ class Container(dict): v = v.value if k in d: - d[k].update(v) + merge_dicts(d[k], v) else: d[k] = v @@ -97,15 +112,16 @@ class Container(dict): if isinstance(item, AoT) and self._body and not self._parsed: if item and "\n" not in item[0].trivia.indent: item[0].trivia.indent = "\n" + item[0].trivia.indent - else: - self.append(None, Whitespace("\n")) if key is not None and key in self: current_idx = self._map[key] if isinstance(current_idx, tuple): - current_idx = current_idx[0] + current_body_element = self._body[current_idx[-1]] + else: + current_body_element = self._body[current_idx] + + current = current_body_element[1] - current = self._body[current_idx][1] if isinstance(item, Table): if not isinstance(current, (Table, AoT)): raise KeyAlreadyPresent(key) @@ -120,17 +136,46 @@ class Container(dict): else: current.append(item) + return self + elif current.is_aot(): + if not item.is_aot_element(): + # Tried to define a table after an AoT with the same name. + raise KeyAlreadyPresent(key) + + current.append(item) + return self elif current.is_super_table(): if item.is_super_table(): + # We need to merge both super tables + if ( + self._table_keys[-1] != current_body_element[0] + or key.is_dotted() + or current_body_element[0].is_dotted() + ): + if not isinstance(current_idx, tuple): + current_idx = (current_idx,) + + self._map[key] = current_idx + (len(self._body),) + self._body.append((key, item)) + self._table_keys.append(key) + + # Building a temporary proxy to check for errors + OutOfOrderTableProxy(self, self._map[key]) + + return self + for k, v in item.value.body: current.append(k, v) return self + elif current_body_element[0].is_dotted(): + raise TOMLKitError("Redefinition of an existing table") elif not item.is_super_table(): raise KeyAlreadyPresent(key) elif isinstance(item, AoT): if not isinstance(current, AoT): + # Tried to define an AoT after a table with the same name. raise KeyAlreadyPresent(key) for table in item.body: @@ -164,7 +209,7 @@ class Container(dict): if key_after is not None: if isinstance(key_after, int): - if key_after + 1 < len(self._body) - 1: + if key_after + 1 < len(self._body): return self._insert_at(key_after + 1, key, item) else: previous_item = self._body[-1][1] @@ -182,25 +227,26 @@ class Container(dict): if key in self._map: current_idx = self._map[key] if isinstance(current_idx, tuple): - current_idx = current_idx[0] + current_idx = current_idx[-1] current = self._body[current_idx][1] if key is not None and not isinstance(current, Table): raise KeyAlreadyPresent(key) # Adding sub tables to a currently existing table - idx = self._map[key] - if not isinstance(idx, tuple): - idx = (idx,) + if not isinstance(current_idx, tuple): + current_idx = (current_idx,) - self._map[key] = idx + (len(self._body),) + self._map[key] = current_idx + (len(self._body),) else: self._map[key] = len(self._body) self._body.append((key, item)) + if item.is_table(): + self._table_keys.append(key) if key is not None: - super(Container, self).__setitem__(key.key, item.value) + dict.__setitem__(self, key.key, item.value) return self @@ -216,14 +262,9 @@ class Container(dict): for i in idx: self._body[i] = (None, Null()) else: - old_data = self._body[idx][1] - trivia = getattr(old_data, "trivia", None) - if trivia and trivia.comment: - self._body[idx] = (None, Comment(Trivia(comment_ws="", comment=trivia.comment))) - else: - self._body[idx] = (None, Null()) + self._body[idx] = (None, Null()) - super(Container, self).__delitem__(key.key) + dict.__delitem__(self, key.key) return self @@ -245,6 +286,9 @@ class Container(dict): item = _item(item) idx = self._map[key] + # Insert after the max index if there are many. + if isinstance(idx, tuple): + idx = max(idx) current_item = self._body[idx][1] if "\n" not in current_item.trivia.trail: current_item.trivia.trail += "\n" @@ -267,7 +311,7 @@ class Container(dict): self._body.insert(idx + 1, (other_key, item)) if key is not None: - super(Container, self).__setitem__(other_key.key, item.value) + dict.__setitem__(self, other_key.key, item.value) return self @@ -309,7 +353,7 @@ class Container(dict): self._body.insert(idx, (key, item)) if key is not None: - super(Container, self).__setitem__(key.key, item.value) + dict.__setitem__(self, key.key, item.value) return self @@ -321,13 +365,19 @@ class Container(dict): if idx is None: raise NonExistentKey(key) + if isinstance(idx, tuple): + # The item we are getting is an out of order table + # so we need a proxy to retrieve the proper objects + # from the parent container + return OutOfOrderTableProxy(self, idx) + return self._body[idx][1] def last_item(self): # type: () -> Optional[Item] if self._body: return self._body[-1][1] - def as_string(self, prefix=None): # type: () -> str + def as_string(self): # type: () -> str s = "" for k, v in self._body: if k is not None: @@ -462,30 +512,25 @@ class Container(dict): # Dictionary methods - def keys(self): # type: () -> Generator[str] - for k, _ in self._body: - if k is None: - continue + def pop(self, key, default=_NOT_SET): + try: + value = self[key] + except KeyError: + if default is _NOT_SET: + raise - yield k.key + return default - def values(self): # type: () -> Generator[Item] - for k, v in self._body: - if k is None: - continue + del self[key] - yield v.value + return value - def items(self): # type: () -> Generator[Item] - for k, v in self.value.items(): - if k is None: - continue + def setdefault( + self, key, default=None + ): # type: (Union[Key, str], Any) -> Union[Item, Container] + super(Container, self).setdefault(key, default=default) - yield k, v - - def update(self, other): # type: (Dict) -> None - for k, v in other.items(): - self[k] = v + return self[key] def __contains__(self, key): # type: (Union[Key, str]) -> bool if not isinstance(key, Key): @@ -493,6 +538,12 @@ class Container(dict): return key in self._map + def __setitem__(self, key, value): # type: (Union[Key, str], Any) -> None + if key is not None and key in self: + self._replace(key, key, value) + else: + self.append(key, value) + def __getitem__(self, key): # type: (Union[Key, str]) -> Union[Item, Container] if not isinstance(key, Key): key = Key(key) @@ -502,22 +553,16 @@ class Container(dict): raise NonExistentKey(key) if isinstance(idx, tuple): - container = Container(True) - - for i in idx: - item = self._body[i][1] - - if isinstance(item, Table): - for k, v in item.value.body: - container.append(k, v) - else: - container.append(key, item) - - return container + # The item we are getting is an out of order table + # so we need a proxy to retrieve the proper objects + # from the parent container + return OutOfOrderTableProxy(self, idx) item = self._body[idx][1] + if item.is_boolean(): + return item.value - return item.value + return item def __setitem__(self, key, value): # type: (Union[Key, str], Any) -> None if key is not None and key in self: @@ -528,6 +573,12 @@ class Container(dict): def __delitem__(self, key): # type: (Union[Key, str]) -> None self.remove(key) + def __len__(self): # type: () -> int + return dict.__len__(self) + + def __iter__(self): # type: () -> Iterator[str] + return iter(dict.keys(self)) + def _replace( self, key, new_key, value ): # type: (Union[Key, str], Union[Key, str], Item) -> None @@ -546,6 +597,9 @@ class Container(dict): def _replace_at( self, idx, new_key, value ): # type: (Union[int, Tuple[int]], Union[Key, str], Item) -> None + if not isinstance(new_key, Key): + new_key = Key(new_key) + if isinstance(idx, tuple): for i in idx[1:]: self._body[i] = (None, Null()) @@ -555,6 +609,8 @@ class Container(dict): k, v = self._body[idx] self._map[new_key] = self._map.pop(k) + if new_key != k: + dict.__delitem__(self, k) if isinstance(self._map[new_key], tuple): self._map[new_key] = self._map[new_key][0] @@ -574,11 +630,14 @@ class Container(dict): self._body[idx] = (new_key, value) - super(Container, self).__setitem__(new_key.key, value.value) + dict.__setitem__(self, new_key.key, value.value) def __str__(self): # type: () -> str return str(self.value) + def __repr__(self): # type: () -> str + return repr(self.value) + def __eq__(self, other): # type: (Dict) -> bool if not isinstance(other, dict): return NotImplemented @@ -595,23 +654,138 @@ class Container(dict): return ( self.__class__, self._getstate(protocol), - (self._map, self._body, self._parsed), + (self._map, self._body, self._parsed, self._table_keys), ) def __setstate__(self, state): self._map = state[0] self._body = state[1] self._parsed = state[2] + self._table_keys = state[3] + + for key, item in self._body: + if key is not None: + dict.__setitem__(self, key.key, item.value) def copy(self): # type: () -> Container return copy.copy(self) def __copy__(self): # type: () -> Container c = self.__class__(self._parsed) - for k, v in super(Container, self).copy().items(): - super(Container, c).__setitem__(k, v) + for k, v in dict.items(self): + dict.__setitem__(c, k, v) c._body += self.body c._map.update(self._map) return c + + +class OutOfOrderTableProxy(MutableMapping, dict): + def __init__(self, container, indices): # type: (Container, Tuple) -> None + self._container = container + self._internal_container = Container(self._container.parsing) + self._tables = [] + self._tables_map = {} + self._map = {} + + for i in indices: + key, item = self._container._body[i] + + if isinstance(item, Table): + self._tables.append(item) + table_idx = len(self._tables) - 1 + for k, v in item.value.body: + self._internal_container.append(k, v) + self._tables_map[k] = table_idx + if k is not None: + dict.__setitem__(self, k.key, v) + else: + self._internal_container.append(key, item) + self._map[key] = i + if key is not None: + dict.__setitem__(self, key.key, item) + + @property + def value(self): + return self._internal_container.value + + def __getitem__(self, key): # type: (Union[Key, str]) -> Any + if key not in self._internal_container: + raise NonExistentKey(key) + + return self._internal_container[key] + + def __setitem__(self, key, item): # type: (Union[Key, str], Any) -> None + if key in self._map: + idx = self._map[key] + self._container._replace_at(idx, key, item) + elif key in self._tables_map: + table = self._tables[self._tables_map[key]] + table[key] = item + elif self._tables: + table = self._tables[0] + table[key] = item + else: + self._container[key] = item + + if key is not None: + dict.__setitem__(self, key, item) + + def __delitem__(self, key): # type: (Union[Key, str]) -> None + if key in self._map: + idx = self._map[key] + del self._container[key] + del self._map[key] + elif key in self._tables_map: + table = self._tables[self._tables_map[key]] + del table[key] + del self._tables_map[key] + else: + raise NonExistentKey(key) + + del self._internal_container[key] + + def keys(self): + return self._internal_container.keys() + + def values(self): + return self._internal_container.values() + + def items(self): # type: () -> Generator[Item] + return self._internal_container.items() + + def update(self, other): # type: (Dict) -> None + self._internal_container.update(other) + + def get(self, key, default=None): # type: (Any, Optional[Any]) -> Any + return self._internal_container.get(key, default=default) + + def pop(self, key, default=_NOT_SET): + return self._internal_container.pop(key, default=default) + + def setdefault( + self, key, default=None + ): # type: (Union[Key, str], Any) -> Union[Item, Container] + return self._internal_container.setdefault(key, default=default) + + def __contains__(self, key): + return key in self._internal_container + + def __iter__(self): # type: () -> Iterator[str] + return iter(self._internal_container) + + def __str__(self): + return str(self._internal_container) + + def __repr__(self): + return repr(self._internal_container) + + def __eq__(self, other): # type: (Dict) -> bool + if not isinstance(other, dict): + return NotImplemented + + return self._internal_container == other + + def __getattr__(self, attribute): + return getattr(self._internal_container, attribute) diff --git a/pipenv/vendor/tomlkit/exceptions.py b/pipenv/vendor/tomlkit/exceptions.py index c1a4e620..d0c7ab5a 100644 --- a/pipenv/vendor/tomlkit/exceptions.py +++ b/pipenv/vendor/tomlkit/exceptions.py @@ -1,3 +1,7 @@ +from __future__ import unicode_literals + +from typing import Optional + class TOMLKitError(Exception): @@ -200,3 +204,20 @@ class KeyAlreadyPresent(TOMLKitError): message = 'Key "{}" already exists.'.format(key) super(KeyAlreadyPresent, self).__init__(message) + + +class InvalidControlChar(ParseError): + def __init__(self, line, col, char, type): # type: (int, int, int, str) -> None + display_code = "\\u00" + + if char < 16: + display_code += "0" + + display_code += str(char) + + message = ( + "Control characters (codes less than 0x1f and 0x7f) are not allowed in {}, " + "use {} instead".format(type, display_code) + ) + + super(InvalidControlChar, self).__init__(line, col, message=message) diff --git a/pipenv/vendor/tomlkit/items.py b/pipenv/vendor/tomlkit/items.py index f199e8df..9e746a8c 100644 --- a/pipenv/vendor/tomlkit/items.py +++ b/pipenv/vendor/tomlkit/items.py @@ -6,23 +6,30 @@ import string from datetime import date from datetime import datetime from datetime import time +from enum import Enum +from typing import Any +from typing import Dict +from typing import Generator +from typing import List +from typing import Optional +from typing import Union from ._compat import PY2 +from ._compat import PY38 +from ._compat import MutableMapping from ._compat import decode from ._compat import long from ._compat import unicode from ._utils import escape_string + if PY2: - from pipenv.vendor.backports.enum import Enum from pipenv.vendor.backports.functools_lru_cache import lru_cache else: - from enum import Enum from functools import lru_cache -from toml.decoder import InlineTableDict -def item(value, _parent=None): +def item(value, _parent=None, _sort_keys=False): from .container import Container if isinstance(value, Item): @@ -35,15 +42,15 @@ def item(value, _parent=None): elif isinstance(value, float): return Float(value, Trivia(), str(value)) elif isinstance(value, dict): - if isinstance(value, InlineTableDict): - val = InlineTable(Container(), Trivia()) - else: - val = Table(Container(), Trivia(), False) - for k, v in sorted(value.items(), key=lambda i: (isinstance(i[1], dict), i[0])): - val[k] = item(v, _parent=val) + val = Table(Container(), Trivia(), False) + for k, v in sorted( + value.items(), + key=lambda i: (isinstance(i[1], dict), i[0] if _sort_keys else 1), + ): + val[k] = item(v, _parent=val, _sort_keys=_sort_keys) return val - elif isinstance(value, list): + elif isinstance(value, (tuple, list)): if value and isinstance(value[0], dict): a = AoT([]) else: @@ -54,13 +61,14 @@ def item(value, _parent=None): table = Table(Container(), Trivia(), True) for k, _v in sorted( - v.items(), key=lambda i: (isinstance(i[1], dict), i[0]) + v.items(), + key=lambda i: (isinstance(i[1], dict), i[0] if _sort_keys else 1), ): - i = item(_v) + i = item(_v, _sort_keys=_sort_keys) if isinstance(table, InlineTable): i.trivia.trail = "" - table[k] = item(i) + table[k] = item(i, _sort_keys=_sort_keys) v = table @@ -70,13 +78,32 @@ def item(value, _parent=None): elif isinstance(value, (str, unicode)): escaped = escape_string(value) - return String(StringType.SLB, value, escaped, Trivia()) + return String(StringType.SLB, decode(value), escaped, Trivia()) elif isinstance(value, datetime): - return DateTime(value, Trivia(), value.isoformat().replace("+00:00", "Z")) + return DateTime( + value.year, + value.month, + value.day, + value.hour, + value.minute, + value.second, + value.microsecond, + value.tzinfo, + Trivia(), + value.isoformat().replace("+00:00", "Z"), + ) elif isinstance(value, date): - return Date(value, Trivia(), value.isoformat()) + return Date(value.year, value.month, value.day, Trivia(), value.isoformat()) elif isinstance(value, time): - return Time(value, Trivia(), value.isoformat()) + return Time( + value.hour, + value.minute, + value.second, + value.microsecond, + value.tzinfo, + Trivia(), + value.isoformat(), + ) raise ValueError("Invalid type {}".format(type(value))) @@ -179,7 +206,9 @@ class Key: A key value. """ - def __init__(self, k, t=None, sep=None, dotted=False): # type: (str) -> None + def __init__( + self, k, t=None, sep=None, dotted=False, original=None + ): # type: (str, Optional[KeyType], Optional[str], bool, Optional[str]) -> None if t is None: if any( [c not in string.ascii_letters + string.digits + "-" + "_" for c in k] @@ -194,6 +223,11 @@ class Key: self.sep = sep self.key = k + if original is None: + original = k + + self._original = original + self._dotted = dotted @property @@ -203,8 +237,11 @@ class Key: def is_dotted(self): # type: () -> bool return self._dotted + def is_bare(self): # type: () -> bool + return self.t == KeyType.Bare + def as_string(self): # type: () -> str - return "{}{}{}".format(self.delimiter, self.key, self.delimiter) + return "{}{}{}".format(self.delimiter, self._original, self.delimiter) def __hash__(self): # type: () -> int return hash(self.key) @@ -260,6 +297,18 @@ class Item(object): return self + def is_boolean(self): # type: () -> bool + return isinstance(self, Bool) + + def is_table(self): # type: () -> bool + return isinstance(self, Table) + + def is_inline_table(self): # type: () -> bool + return isinstance(self, InlineTable) + + def is_aot(self): # type: () -> bool + return isinstance(self, AoT) + def _getstate(self, protocol=3): return (self._trivia,) @@ -465,7 +514,7 @@ class Bool(Item): A boolean literal. """ - def __init__(self, t, trivia): # type: (float, Trivia) -> None + def __init__(self, t, trivia): # type: (int, Trivia) -> None super(Bool, self).__init__(trivia) self._value = bool(t) @@ -484,26 +533,59 @@ class Bool(Item): def _getstate(self, protocol=3): return self._value, self._trivia + def __bool__(self): + return self._value + + __nonzero__ = __bool__ + + def __eq__(self, other): + if not isinstance(other, bool): + return NotImplemented + + return other == self._value + + def __hash__(self): + return hash(self._value) + + def __repr__(self): + return repr(self._value) + class DateTime(Item, datetime): """ A datetime literal. """ - def __new__(cls, value, *_): # type: (..., datetime, ...) -> datetime + def __new__( + cls, + year, + month, + day, + hour, + minute, + second, + microsecond, + tzinfo, + trivia, + raw, + **kwargs + ): # type: (int, int, int, int, int, int, int, Optional[datetime.tzinfo], Trivia, str, Any) -> datetime return datetime.__new__( cls, - value.year, - value.month, - value.day, - value.hour, - value.minute, - value.second, - value.microsecond, - tzinfo=value.tzinfo, + year, + month, + day, + hour, + minute, + second, + microsecond, + tzinfo=tzinfo, + **kwargs ) - def __init__(self, _, trivia, raw): # type: (datetime, Trivia, str) -> None + def __init__( + self, year, month, day, hour, minute, second, microsecond, tzinfo, trivia, raw + ): # type: (int, int, int, int, int, int, int, Optional[datetime.tzinfo], Trivia, str) -> None super(DateTime, self).__init__(trivia) self._raw = raw @@ -520,12 +602,36 @@ class DateTime(Item, datetime): return self._raw def __add__(self, other): - result = super(DateTime, self).__add__(other) + if PY38: + result = datetime( + self.year, + self.month, + self.day, + self.hour, + self.minute, + self.second, + self.microsecond, + self.tzinfo, + ).__add__(other) + else: + result = super(DateTime, self).__add__(other) return self._new(result) def __sub__(self, other): - result = super(DateTime, self).__sub__(other) + if PY38: + result = datetime( + self.year, + self.month, + self.day, + self.hour, + self.minute, + self.second, + self.microsecond, + self.tzinfo, + ).__sub__(other) + else: + result = super(DateTime, self).__sub__(other) if isinstance(result, datetime): result = self._new(result) @@ -535,20 +641,29 @@ class DateTime(Item, datetime): def _new(self, result): raw = result.isoformat() - return DateTime(result, self._trivia, raw) + return DateTime( + result.year, + result.month, + result.day, + result.hour, + result.minute, + result.second, + result.microsecond, + result.tzinfo, + self._trivia, + raw, + ) def _getstate(self, protocol=3): return ( - datetime( - self.year, - self.month, - self.day, - self.hour, - self.minute, - self.second, - self.microsecond, - self.tzinfo, - ), + self.year, + self.month, + self.day, + self.hour, + self.minute, + self.second, + self.microsecond, + self.tzinfo, self._trivia, self._raw, ) @@ -559,10 +674,12 @@ class Date(Item, date): A date literal. """ - def __new__(cls, value, *_): # type: (..., date, ...) -> date - return date.__new__(cls, value.year, value.month, value.day) + def __new__(cls, year, month, day, *_): # type: (int, int, int, Any) -> date + return date.__new__(cls, year, month, day) - def __init__(self, _, trivia, raw): # type: (date, Trivia, str) -> None + def __init__( + self, year, month, day, trivia, raw + ): # type: (int, int, int, Trivia, str) -> None super(Date, self).__init__(trivia) self._raw = raw @@ -579,22 +696,31 @@ class Date(Item, date): return self._raw def __add__(self, other): - result = super(Date, self).__add__(other) + if PY38: + result = date(self.year, self.month, self.day).__add__(other) + else: + result = super(Date, self).__add__(other) return self._new(result) def __sub__(self, other): - result = super(Date, self).__sub__(other) + if PY38: + result = date(self.year, self.month, self.day).__sub__(other) + else: + result = super(Date, self).__sub__(other) - return self._new(result) + if isinstance(result, date): + result = self._new(result) + + return result def _new(self, result): raw = result.isoformat() - return Date(result, self._trivia, raw) + return Date(result.year, result.month, result.day, self._trivia, raw) def _getstate(self, protocol=3): - return (datetime(self.year, self.month, self.day), self._trivia, self._raw) + return (self.year, self.month, self.day, self._trivia, self._raw) class Time(Item, time): @@ -602,12 +728,14 @@ class Time(Item, time): A time literal. """ - def __new__(cls, value, *_): # type: (time, ...) -> time - return time.__new__( - cls, value.hour, value.minute, value.second, value.microsecond - ) + def __new__( + cls, hour, minute, second, microsecond, tzinfo, *_ + ): # type: (int, int, int, int, Optional[datetime.tzinfo], Any) -> time + return time.__new__(cls, hour, minute, second, microsecond, tzinfo) - def __init__(self, _, trivia, raw): # type: (time, Trivia, str) -> None + def __init__( + self, hour, minute, second, microsecond, tzinfo, trivia, raw + ): # type: (int, int, int, int, Optional[datetime.tzinfo], Trivia, str) -> None super(Time, self).__init__(trivia) self._raw = raw @@ -625,7 +753,11 @@ class Time(Item, time): def _getstate(self, protocol=3): return ( - time(self.hour, self.minute, self.second, self.microsecond, self.tzinfo), + self.hour, + self.minute, + self.second, + self.microsecond, + self.tzinfo, self._trivia, self._raw, ) @@ -636,7 +768,9 @@ class Array(Item, list): An array literal """ - def __init__(self, value, trivia): # type: (list, Trivia) -> None + def __init__( + self, value, trivia, multiline=False + ): # type: (list, Trivia, bool) -> None super(Array, self).__init__(trivia) list.__init__( @@ -644,6 +778,7 @@ class Array(Item, list): ) self._value = value + self._multiline = multiline @property def discriminant(self): # type: () -> int @@ -653,22 +788,25 @@ class Array(Item, list): def value(self): # type: () -> list return self - def is_homogeneous(self): # type: () -> bool - if not self: - return True + def multiline(self, multiline): # type: (bool) -> self + self._multiline = multiline - discriminants = [ - i.discriminant - for i in self._value - if not isinstance(i, (Whitespace, Comment)) - ] - - return len(set(discriminants)) == 1 + return self def as_string(self): # type: () -> str - return "[{}]".format("".join(v.as_string() for v in self._value)) + if not self._multiline: + return "[{}]".format("".join(v.as_string() for v in self._value)) - def append(self, _item): # type: () -> None + s = "[\n" + self.trivia.indent + " " * 4 + s += (",\n" + self.trivia.indent + " " * 4).join( + v.as_string() for v in self._value if not isinstance(v, Whitespace) + ) + s += ",\n" + s += "]" + + return s + + def append(self, _item): # type: (Any) -> None if self._value: self._value.append(Whitespace(", ")) @@ -677,9 +815,6 @@ class Array(Item, list): self._value.append(it) - if not self.is_homogeneous(): - raise ValueError("Array has mixed types elements") - if not PY2: def clear(self): @@ -732,7 +867,7 @@ class Array(Item, list): return self._value, self._trivia -class Table(Item, dict): +class Table(Item, MutableMapping, dict): """ A table literal. """ @@ -745,7 +880,7 @@ class Table(Item, dict): is_super_table=False, name=None, display_name=None, - ): # type: (tomlkit.container.Container, Trivia, bool, ...) -> None + ): # type: (tomlkit.container.Container, Trivia, bool, bool, Optional[str], Optional[str]) -> None super(Table, self).__init__(trivia) self.name = name @@ -756,7 +891,7 @@ class Table(Item, dict): for k, v in self._value.body: if k is not None: - super(Table, self).__setitem__(k.key, v) + dict.__setitem__(self, k.key, v) @property def value(self): # type: () -> tomlkit.container.Container @@ -790,7 +925,7 @@ class Table(Item, dict): key = key.key if key is not None: - super(Table, self).__setitem__(key, _item) + dict.__setitem__(self, key, _item) m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent) if not m: @@ -807,6 +942,20 @@ class Table(Item, dict): return self + def raw_append(self, key, _item): # type: (Union[Key, str], Any) -> Table + if not isinstance(_item, Item): + _item = item(_item) + + self._value.append(key, _item) + + if isinstance(key, Key): + key = key.key + + if key is not None: + dict.__setitem__(self, key, _item) + + return self + def remove(self, key): # type: (Union[Key, str]) -> Table self._value.remove(key) @@ -814,7 +963,7 @@ class Table(Item, dict): key = key.key if key is not None: - super(Table, self).__delitem__(key) + dict.__delitem__(self, key) return self @@ -824,8 +973,8 @@ class Table(Item, dict): def is_super_table(self): # type: () -> bool return self._is_super_table - def as_string(self, prefix=None): # type: () -> str - return self._value.as_string(prefix=prefix) + def as_string(self): # type: () -> str + return self._value.as_string() # Helpers @@ -844,39 +993,32 @@ class Table(Item, dict): return self - def keys(self): # type: () -> Generator[str] - for k in self._value.keys(): - yield k + def get(self, key, default=None): # type: (Any, Optional[Any]) -> Any + return self._value.get(key, default) - def values(self): # type: () -> Generator[Item] - for v in self._value.values(): - yield v + def setdefault( + self, key, default=None + ): # type: (Union[Key, str], Any) -> Union[Item, Container] + super(Table, self).setdefault(key, default=default) - def items(self): # type: () -> Generator[Item] - for k, v in self._value.items(): - yield k, v - - def update(self, other): # type: (Dict) -> None - for k, v in other.items(): - self[k] = v - - def __contains__(self, key): # type: (Union[Key, str]) -> bool - return key in self._value + return self[key] def __getitem__(self, key): # type: (Union[Key, str]) -> Item return self._value[key] def __setitem__(self, key, value): # type: (Union[Key, str], Any) -> None + fix_indent = key not in self + if not isinstance(value, Item): value = item(value) self._value[key] = value if key is not None: - super(Table, self).__setitem__(key, value) + dict.__setitem__(self, key, value) m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent) - if not m: + if not m or not fix_indent: return indent = m.group(1) @@ -891,8 +1033,14 @@ class Table(Item, dict): def __delitem__(self, key): # type: (Union[Key, str]) -> None self.remove(key) - def __repr__(self): - return super(Table, self).__repr__() + def __len__(self): # type: () -> int + return len(self._value) + + def __iter__(self): # type: () -> Iterator[str] + return iter(self._value) + + def __repr__(self): # type: () -> str + return repr(self._value) def _getstate(self, protocol=3): return ( @@ -905,21 +1053,22 @@ class Table(Item, dict): ) -class InlineTable(Item, dict): +class InlineTable(Item, MutableMapping, dict): """ An inline table literal. """ def __init__( - self, value, trivia - ): # type: (tomlkit.container.Container, Trivia) -> None + self, value, trivia, new=False + ): # type: (tomlkit.container.Container, Trivia, bool) -> None super(InlineTable, self).__init__(trivia) self._value = value + self._new = new for k, v in self._value.body: if k is not None: - super(InlineTable, self).__setitem__(k.key, v) + dict.__setitem__(self, k.key, v) @property def discriminant(self): # type: () -> int @@ -937,7 +1086,7 @@ class InlineTable(Item, dict): _item = item(_item) if not isinstance(_item, (Whitespace, Comment)): - if not _item.trivia.indent and len(self._value) > 0: + if not _item.trivia.indent and len(self._value) > 0 and not self._new: _item.trivia.indent = " " if _item.trivia.comment: _item.trivia.comment = "" @@ -948,7 +1097,7 @@ class InlineTable(Item, dict): key = key.key if key is not None: - super(InlineTable, self).__setitem__(key, _item) + dict.__setitem__(self, key, _item) return self @@ -959,7 +1108,7 @@ class InlineTable(Item, dict): key = key.key if key is not None: - super(InlineTable, self).__delitem__(key) + dict.__delitem__(self, key) return self @@ -968,7 +1117,10 @@ class InlineTable(Item, dict): for i, (k, v) in enumerate(self._value.body): if k is None: if i == len(self._value.body) - 1: - buf = buf.rstrip(",") + if self._new: + buf = buf.rstrip(", ") + else: + buf = buf.rstrip(",") buf += v.as_string() @@ -976,7 +1128,7 @@ class InlineTable(Item, dict): buf += "{}{}{}{}{}{}".format( v.trivia.indent, - k.as_string(), + k.as_string() + ("." if k.is_dotted() else ""), k.sep, v.as_string(), v.trivia.comment, @@ -985,26 +1137,22 @@ class InlineTable(Item, dict): if i != len(self._value.body) - 1: buf += "," + if self._new: + buf += " " buf += "}" return buf - def keys(self): # type: () -> Generator[str] - for k in self._value.keys(): - yield k + def get(self, key, default=None): # type: (Any, Optional[Any]) -> Any + return self._value.get(key, default) - def values(self): # type: () -> Generator[Item] - for v in self._value.values(): - yield v + def setdefault( + self, key, default=None + ): # type: (Union[Key, str], Any) -> Union[Item, Container] + super(InlineTable, self).setdefault(key, default=default) - def items(self): # type: () -> Generator[Item] - for k, v in self._value.items(): - yield k, v - - def update(self, other): # type: (Dict) -> None - for k, v in other.items(): - self[k] = v + return self[key] def __contains__(self, key): # type: (Union[Key, str]) -> bool return key in self._value @@ -1019,7 +1167,8 @@ class InlineTable(Item, dict): self._value[key] = value if key is not None: - super(InlineTable, self).__setitem__(key, value) + dict.__setitem__(self, key, value) + if value.trivia.comment: value.trivia.comment = "" @@ -1039,8 +1188,14 @@ class InlineTable(Item, dict): def __delitem__(self, key): # type: (Union[Key, str]) -> None self.remove(key) + def __len__(self): # type: () -> int + return len(self._value) + + def __iter__(self): # type: () -> Iterator[str] + return iter(self._value) + def __repr__(self): - return super(InlineTable, self).__repr__() + return repr(self._value) def _getstate(self, protocol=3): return (self._value, self._trivia) @@ -1097,7 +1252,7 @@ class AoT(Item, list): def __init__( self, body, name=None, parsed=False - ): # type: (List[Table], Optional[str]) -> None + ): # type: (List[Table], Optional[str], bool) -> None self.name = name self._body = [] self._parsed = parsed @@ -1142,7 +1297,7 @@ class AoT(Item, list): def as_string(self): # type: () -> str b = "" for table in self._body: - b += table.as_string(prefix=self.name) + b += table.as_string() return b diff --git a/pipenv/vendor/tomlkit/parser.py b/pipenv/vendor/tomlkit/parser.py index 3f507bb4..b702088d 100644 --- a/pipenv/vendor/tomlkit/parser.py +++ b/pipenv/vendor/tomlkit/parser.py @@ -4,22 +4,29 @@ from __future__ import unicode_literals import re import string +from typing import Any +from typing import Generator +from typing import List +from typing import Optional +from typing import Tuple +from typing import Union + from ._compat import chr from ._compat import decode -from ._utils import _escaped from ._utils import RFC_3339_LOOSE +from ._utils import _escaped from ._utils import parse_rfc3339 from .container import Container from .exceptions import EmptyKeyError from .exceptions import EmptyTableNameError from .exceptions import InternalParserError from .exceptions import InvalidCharInStringError -from .exceptions import InvalidDateTimeError +from .exceptions import InvalidControlChar from .exceptions import InvalidDateError -from .exceptions import InvalidTimeError +from .exceptions import InvalidDateTimeError from .exceptions import InvalidNumberError +from .exceptions import InvalidTimeError from .exceptions import InvalidUnicodeValueError -from .exceptions import MixedArrayTypesError from .exceptions import ParseError from .exceptions import UnexpectedCharError from .exceptions import UnexpectedEofError @@ -48,6 +55,13 @@ from .toml_char import TOMLChar from .toml_document import TOMLDocument +CTRL_I = 0x09 # Tab +CTRL_J = 0x0A # Line feed +CTRL_M = 0x0D # Carriage return +CTRL_CHAR_LIMIT = 0x1F +CHR_DEL = 0x7F + + class Parser: """ Parser for TOML documents. @@ -194,6 +208,7 @@ class Parser: in_name = False current = "" t = KeyType.Bare + parts = 0 for c in name: c = TOMLChar(c) @@ -205,14 +220,21 @@ class Parser: if not current: raise self.parse_error() - yield Key(current, t=t, sep="") + yield Key(current.strip(), t=t, sep="", original=current) + + parts += 1 current = "" t = KeyType.Bare continue elif c in {"'", '"'}: if in_name: - if t == KeyType.Literal and c == '"': + if ( + t == KeyType.Literal + and c == '"' + or t == KeyType.Basic + and c == "'" + ): current += c continue @@ -221,17 +243,31 @@ class Parser: in_name = False else: + if ( + current.strip() + and TOMLChar(current[-1]).is_spaces() + and not parts + ): + raise self.parse_error() + in_name = True t = KeyType.Literal if c == "'" else KeyType.Basic continue elif in_name or c.is_bare_key_char(): current += c + elif c.is_spaces(): + # A space is only valid at this point + # if it's in between parts. + # We store it for now and will check + # later if it's valid + current += c + continue else: raise self.parse_error() - if current: - yield Key(current, t=t, sep="") + if current.strip(): + yield Key(current.strip(), t=t, sep="", original=current) def _parse_item(self): # type: () -> Optional[Tuple[Optional[Key], Item]] """ @@ -294,8 +330,13 @@ class Parser: self.inc() # Skip # # The comment itself - while not self.end() and not self._current.is_nl() and self.inc(): - pass + while not self.end() and not self._current.is_nl(): + code = ord(self._current) + if code == CHR_DEL or code <= CTRL_CHAR_LIMIT and code != CTRL_I: + raise self.parse_error(InvalidControlChar, code, "comments") + + if not self.inc(): + break comment = self.extract() self.mark() @@ -335,8 +376,6 @@ class Parser: # Key key = self._parse_key() - if not key.key.strip(): - raise self.parse_error(EmptyKeyError) self.mark() @@ -349,16 +388,20 @@ class Parser: found_equals = True pass - key.sep = self.extract() + if not key.sep: + key.sep = self.extract() + else: + key.sep += self.extract() # Value val = self._parse_value() - # Comment if parse_comment: cws, comment, trail = self._parse_comment_trail() meta = val.trivia - meta.comment_ws = cws + if not meta.comment_ws: + meta.comment_ws = cws + meta.comment = comment meta.trail = trail else: @@ -419,30 +462,70 @@ class Parser: dotted = False self.mark() - while self._current.is_bare_key_char() and self.inc(): + while ( + self._current.is_bare_key_char() or self._current.is_spaces() + ) and self.inc(): pass - key = self.extract() + original = self.extract() + key = original.strip() + if not key: + # Empty key + raise self.parse_error(ParseError, "Empty key found") + + if " " in key: + # Bare key with spaces in it + raise self.parse_error(ParseError, 'Invalid key "{}"'.format(key)) if self._current == ".": self.inc() dotted = True - key += "." + self._parse_key().as_string() + original += "." + self._parse_key().as_string() + key = original.strip() key_type = KeyType.Bare - return Key(key, key_type, "", dotted) + return Key(key, key_type, "", dotted, original=original) def _handle_dotted_key( self, container, key, value - ): # type: (Container, Key, Any) -> None - names = tuple(self._split_table_name(key.key)) + ): # type: (Union[Container, Table], Key, Any) -> None + names = tuple(self._split_table_name(key.as_string())) name = names[0] name._dotted = True if name in container: - table = container.item(name) + if not isinstance(value, Table): + table = Table(Container(True), Trivia(), False, is_super_table=True) + _table = table + for i, _name in enumerate(names[1:]): + if i == len(names) - 2: + _name.sep = key.sep + + _table.append(_name, value) + else: + _name._dotted = True + _table.append( + _name, + Table( + Container(True), + Trivia(), + False, + is_super_table=i < len(names) - 2, + ), + ) + + _table = _table[_name] + + value = table + + container.append(name, value) + + return else: table = Table(Container(True), Trivia(), False, is_super_table=True) - container.append(name, table) + if isinstance(container, Table): + container.raw_append(name, table) + else: + container.append(name, table) for i, _name in enumerate(names[1:]): if i == len(names) - 2: @@ -452,7 +535,7 @@ class Parser: else: _name._dotted = True if _name in table.value: - table = table.value.item(_name) + table = table.value[_name] else: table.append( _name, @@ -517,19 +600,63 @@ class Parser: if m.group(1) and m.group(5): # datetime try: - return DateTime(parse_rfc3339(raw), trivia, raw) + dt = parse_rfc3339(raw) + return DateTime( + dt.year, + dt.month, + dt.day, + dt.hour, + dt.minute, + dt.second, + dt.microsecond, + dt.tzinfo, + trivia, + raw, + ) except ValueError: raise self.parse_error(InvalidDateTimeError) if m.group(1): try: - return Date(parse_rfc3339(raw), trivia, raw) + dt = parse_rfc3339(raw) + date = Date(dt.year, dt.month, dt.day, trivia, raw) + self.mark() + while self._current not in "\t\n\r#,]}" and self.inc(): + pass + + time_raw = self.extract() + if not time_raw.strip(): + trivia.comment_ws = time_raw + return date + + dt = parse_rfc3339(raw + time_raw) + return DateTime( + dt.year, + dt.month, + dt.day, + dt.hour, + dt.minute, + dt.second, + dt.microsecond, + dt.tzinfo, + trivia, + raw + time_raw, + ) except ValueError: raise self.parse_error(InvalidDateError) if m.group(5): try: - return Time(parse_rfc3339(raw), trivia, raw) + t = parse_rfc3339(raw) + return Time( + t.hour, + t.minute, + t.second, + t.microsecond, + t.tzinfo, + trivia, + raw, + ) except ValueError: raise self.parse_error(InvalidTimeError) @@ -614,10 +741,7 @@ class Parser: except ValueError: pass else: - if res.is_homogeneous(): - return res - - raise self.parse_error(MixedArrayTypesError) + return res def _parse_inline_table(self): # type: () -> InlineTable # consume opening bracket, EOF here is an issue (middle of array) @@ -640,15 +764,25 @@ class Parser: # consume closing bracket, EOF here doesn't matter self.inc() break - if trailing_comma is False: + + if ( + trailing_comma is False + or trailing_comma is None + and self._current == "," + ): + # Either the previous key-value pair was not followed by a comma + # or the table has an unexpected leading comma. raise self.parse_error(UnexpectedCharError, self._current) else: # True: previous key-value pair was followed by a comma - if self._current == "}": + if self._current == "}" or self._current == ",": raise self.parse_error(UnexpectedCharError, self._current) key, val = self._parse_key_value(False) - elems.add(key, val) + if key.is_dotted(): + self._handle_dotted_key(elems, key, val) + else: + elems.add(key, val) # consume trailing whitespace mark = self._idx @@ -675,7 +809,7 @@ class Parser: if ( len(raw) > 1 and raw.startswith("0") - and not raw.startswith(("0.", "0o", "0x", "0b")) + and not raw.startswith(("0.", "0o", "0x", "0b", "0e")) ): return @@ -798,33 +932,52 @@ class Parser: escaped = False # whether the previous key was ESCAPE while True: - if delim.is_singleline() and self._current.is_nl(): - # single line cannot have actual newline characters - raise self.parse_error(InvalidCharInStringError, self._current) + code = ord(self._current) + if ( + delim.is_singleline() + and not escaped + and (code == CHR_DEL or code <= CTRL_CHAR_LIMIT and code != CTRL_I) + ): + raise self.parse_error(InvalidControlChar, code, "strings") + elif ( + delim.is_multiline() + and not escaped + and ( + code == CHR_DEL + or code <= CTRL_CHAR_LIMIT + and code not in [CTRL_I, CTRL_J, CTRL_M] + ) + ): + raise self.parse_error(InvalidControlChar, code, "strings") elif not escaped and self._current == delim.unit: # try to process current as a closing delim original = self.extract() close = "" if delim.is_multiline(): - # try consuming three delims as this would mean the end of - # the string - for last in [False, False, True]: - if self._current != delim.unit: - # Not a triple quote, leave in result as-is. - # Adding back the characters we already consumed - value += close - close = "" # clear the close - break + # Consume the delimiters to see if we are at the end of the string + close = "" + while self._current == delim.unit: + close += self._current + self.inc() - close += delim.unit - - # consume this delim, EOF here is only an issue if this - # is not the third (last) delim character - self.inc(exception=UnexpectedEofError if not last else None) - - if not close: # if there is no close characters, keep parsing + if len(close) < 3: + # Not a triple quote, leave in result as-is. + # Adding back the characters we already consumed + value += close continue + + if len(close) == 3: + # We are at the end of the string + return String(delim, value, original, Trivia()) + + if len(close) >= 6: + raise self.parse_error(InvalidCharInStringError, self._current) + + value += close[:-3] + original += close[:-3] + + return String(delim, value, original, Trivia()) else: # consume the closing delim, we do not care if EOF occurs as # that would simply imply the end of self._src @@ -853,8 +1006,8 @@ class Parser: self.inc(exception=UnexpectedEofError) def _parse_table( - self, parent_name=None - ): # type: (Optional[str]) -> Tuple[Key, Union[Table, AoT]] + self, parent_name=None, parent=None + ): # type: (Optional[str], Optional[Table]) -> Tuple[Key, Union[Table, AoT]] """ Parses a table element. """ @@ -876,20 +1029,54 @@ class Parser: is_aot = True - # Key + # Consume any whitespace self.mark() - while self._current != "]" and self.inc(): - if self.end(): - raise self.parse_error(UnexpectedEofError) - + while self._current.is_spaces() and self.inc(): pass - name = self.extract() + ws_prefix = self.extract() + + # Key + if self._current in [StringType.SLL.value, StringType.SLB.value]: + delimiter = ( + StringType.SLL + if self._current == StringType.SLL.value + else StringType.SLB + ) + name = self._parse_string(delimiter) + name = "{delimiter}{name}{delimiter}".format( + delimiter=delimiter.value, name=name + ) + + self.mark() + while self._current != "]" and self.inc(): + if self.end(): + raise self.parse_error(UnexpectedEofError) + + pass + + ws_suffix = self.extract() + name += ws_suffix + else: + self.mark() + while self._current != "]" and self.inc(): + if self.end(): + raise self.parse_error(UnexpectedEofError) + + pass + + name = self.extract() + + name = ws_prefix + name + if not name.strip(): raise self.parse_error(EmptyTableNameError) key = Key(name, sep="") name_parts = tuple(self._split_table_name(name)) + if any(" " in part.key.strip() and part.is_bare() for part in name_parts): + raise self.parse_error(ParseError, 'Invalid table name "{}"'.format(name)) + missing_table = False if parent_name: parent_name_parts = tuple(self._split_table_name(parent_name)) @@ -911,6 +1098,13 @@ class Parser: cws, comment, trail = self._parse_comment_trail() result = Null() + table = Table( + values, + Trivia(indent, cws, comment, trail), + is_aot, + name=name, + display_name=name, + ) if len(name_parts) > 1: if missing_table: @@ -944,9 +1138,11 @@ class Parser: ) if is_aot and i == len(name_parts[1:]) - 1: - table.append(_name, AoT([child], name=table.name, parsed=True)) + table.raw_append( + _name, AoT([child], name=table.name, parsed=True) + ) else: - table.append(_name, child) + table.raw_append(_name, child) table = child values = table.value @@ -960,17 +1156,17 @@ class Parser: _key, item = item if not self._merge_ws(item, values): if _key is not None and _key.is_dotted(): - self._handle_dotted_key(values, _key, item) + self._handle_dotted_key(table, _key, item) else: - values.append(_key, item) + table.raw_append(_key, item) else: if self._current == "[": is_aot_next, name_next = self._peek_table() if self._is_child(name, name_next): - key_next, table_next = self._parse_table(name) + key_next, table_next = self._parse_table(name, table) - values.append(key_next, table_next) + table.raw_append(key_next, table_next) # Picking up any sibling while not self.end(): @@ -979,9 +1175,9 @@ class Parser: if not self._is_child(name, name_next): break - key_next, table_next = self._parse_table(name) + key_next, table_next = self._parse_table(name, table) - values.append(key_next, table_next) + table.raw_append(key_next, table_next) break else: @@ -991,13 +1187,7 @@ class Parser: ) if isinstance(result, Null): - result = Table( - values, - Trivia(indent, cws, comment, trail), - is_aot, - name=name, - display_name=name, - ) + result = table if is_aot and (not self._aot_stack or name != self._aot_stack[-1]): result = self._parse_aot(result, name) @@ -1013,6 +1203,7 @@ class Parser: as well as whether it is part of an AoT. """ # we always want to restore after exiting this scope + table_name = "" with self._state(save_marker=True, restore=True): if self._current != "[": raise self.parse_error( @@ -1105,7 +1296,7 @@ class Parser: try: value = chr(int(extracted, 16)) - except ValueError: + except (ValueError, OverflowError): value = None return value, extracted diff --git a/pipenv/vendor/tomlkit/py.typed b/pipenv/vendor/tomlkit/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/pipenv/vendor/tomlkit/source.py b/pipenv/vendor/tomlkit/source.py index ddb580e4..6a6a2391 100644 --- a/pipenv/vendor/tomlkit/source.py +++ b/pipenv/vendor/tomlkit/source.py @@ -4,12 +4,16 @@ from __future__ import unicode_literals import itertools from copy import copy +from typing import Any +from typing import Optional +from typing import Tuple +from typing import Type from ._compat import PY2 from ._compat import unicode -from .exceptions import UnexpectedEofError -from .exceptions import UnexpectedCharError from .exceptions import ParseError +from .exceptions import UnexpectedCharError +from .exceptions import UnexpectedEofError from .toml_char import TOMLChar @@ -114,7 +118,7 @@ class Source(unicode): """ return self[self._marker : self._idx] - def inc(self, exception=None): # type: (Optional[ParseError.__class__]) -> bool + def inc(self, exception=None): # type: (Optional[Type[ParseError]]) -> bool """ Increments the parser if the end of the input has not been reached. Returns whether or not it was able to advance. @@ -170,7 +174,7 @@ class Source(unicode): def parse_error( self, exception=ParseError, *args - ): # type: (ParseError.__class__, ...) -> ParseError + ): # type: (Type[ParseError], Any) -> ParseError """ Creates a generic "parse error" at the current position. """ diff --git a/pipenv/vendor/tomlkit/toml_char.py b/pipenv/vendor/tomlkit/toml_char.py index 02c55172..1faf0aaa 100644 --- a/pipenv/vendor/tomlkit/toml_char.py +++ b/pipenv/vendor/tomlkit/toml_char.py @@ -3,6 +3,7 @@ import string from ._compat import PY2 from ._compat import unicode + if PY2: from pipenv.vendor.backports.functools_lru_cache import lru_cache else: diff --git a/pipenv/vendor/tomlkit/toml_file.py b/pipenv/vendor/tomlkit/toml_file.py index 631e9959..3b416664 100644 --- a/pipenv/vendor/tomlkit/toml_file.py +++ b/pipenv/vendor/tomlkit/toml_file.py @@ -1,5 +1,8 @@ import io +from typing import Any +from typing import Dict + from .api import loads from .toml_document import TOMLDocument diff --git a/pipenv/vendor/urllib3/LICENSE.txt b/pipenv/vendor/urllib3/LICENSE.txt index c89cf27b..429a1767 100644 --- a/pipenv/vendor/urllib3/LICENSE.txt +++ b/pipenv/vendor/urllib3/LICENSE.txt @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2008-2019 Andrey Petrov and contributors (see CONTRIBUTORS.txt) +Copyright (c) 2008-2020 Andrey Petrov and contributors (see CONTRIBUTORS.txt) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pipenv/vendor/urllib3/__init__.py b/pipenv/vendor/urllib3/__init__.py index eb915886..fe86b59d 100644 --- a/pipenv/vendor/urllib3/__init__.py +++ b/pipenv/vendor/urllib3/__init__.py @@ -1,48 +1,43 @@ """ -urllib3 - Thread-safe connection pooling and re-using. +Python HTTP library with thread-safe connection pooling, file post support, user friendly, and more """ from __future__ import absolute_import -import warnings -from .connectionpool import ( - HTTPConnectionPool, - HTTPSConnectionPool, - connection_from_url -) +# Set default logging handler to avoid "No handler found" warnings. +import logging +import warnings +from logging import NullHandler from . import exceptions +from ._version import __version__ +from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool, connection_from_url from .filepost import encode_multipart_formdata from .poolmanager import PoolManager, ProxyManager, proxy_from_url from .response import HTTPResponse from .util.request import make_headers -from .util.url import get_host -from .util.timeout import Timeout from .util.retry import Retry +from .util.timeout import Timeout +from .util.url import get_host - -# Set default logging handler to avoid "No handler found" warnings. -import logging -from logging import NullHandler - -__author__ = 'Andrey Petrov (andrey.petrov@shazow.net)' -__license__ = 'MIT' -__version__ = '1.25.2' +__author__ = "Andrey Petrov (andrey.petrov@shazow.net)" +__license__ = "MIT" +__version__ = __version__ __all__ = ( - 'HTTPConnectionPool', - 'HTTPSConnectionPool', - 'PoolManager', - 'ProxyManager', - 'HTTPResponse', - 'Retry', - 'Timeout', - 'add_stderr_logger', - 'connection_from_url', - 'disable_warnings', - 'encode_multipart_formdata', - 'get_host', - 'make_headers', - 'proxy_from_url', + "HTTPConnectionPool", + "HTTPSConnectionPool", + "PoolManager", + "ProxyManager", + "HTTPResponse", + "Retry", + "Timeout", + "add_stderr_logger", + "connection_from_url", + "disable_warnings", + "encode_multipart_formdata", + "get_host", + "make_headers", + "proxy_from_url", ) logging.getLogger(__name__).addHandler(NullHandler()) @@ -59,10 +54,10 @@ def add_stderr_logger(level=logging.DEBUG): # even if urllib3 is vendored within another package. logger = logging.getLogger(__name__) handler = logging.StreamHandler() - handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s %(message)s')) + handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s")) logger.addHandler(handler) logger.setLevel(level) - logger.debug('Added a stderr logging handler to logger: %s', __name__) + logger.debug("Added a stderr logging handler to logger: %s", __name__) return handler @@ -74,18 +69,17 @@ del NullHandler # shouldn't be: otherwise, it's very hard for users to use most Python # mechanisms to silence them. # SecurityWarning's always go off by default. -warnings.simplefilter('always', exceptions.SecurityWarning, append=True) +warnings.simplefilter("always", exceptions.SecurityWarning, append=True) # SubjectAltNameWarning's should go off once per host -warnings.simplefilter('default', exceptions.SubjectAltNameWarning, append=True) +warnings.simplefilter("default", exceptions.SubjectAltNameWarning, append=True) # InsecurePlatformWarning's don't vary between requests, so we keep it default. -warnings.simplefilter('default', exceptions.InsecurePlatformWarning, - append=True) +warnings.simplefilter("default", exceptions.InsecurePlatformWarning, append=True) # SNIMissingWarnings should go off only once. -warnings.simplefilter('default', exceptions.SNIMissingWarning, append=True) +warnings.simplefilter("default", exceptions.SNIMissingWarning, append=True) def disable_warnings(category=exceptions.HTTPWarning): """ Helper for quickly disabling all urllib3 warnings. """ - warnings.simplefilter('ignore', category) + warnings.simplefilter("ignore", category) diff --git a/pipenv/vendor/urllib3/_collections.py b/pipenv/vendor/urllib3/_collections.py index 34f23811..da9857e9 100644 --- a/pipenv/vendor/urllib3/_collections.py +++ b/pipenv/vendor/urllib3/_collections.py @@ -1,4 +1,5 @@ from __future__ import absolute_import + try: from collections.abc import Mapping, MutableMapping except ImportError: @@ -6,6 +7,7 @@ except ImportError: try: from threading import RLock except ImportError: # Platform-specific: No threads available + class RLock: def __enter__(self): pass @@ -15,11 +17,12 @@ except ImportError: # Platform-specific: No threads available from collections import OrderedDict + from .exceptions import InvalidHeader -from .packages.six import iterkeys, itervalues, PY3 +from .packages import six +from .packages.six import iterkeys, itervalues - -__all__ = ['RecentlyUsedContainer', 'HTTPHeaderDict'] +__all__ = ["RecentlyUsedContainer", "HTTPHeaderDict"] _Null = object() @@ -82,7 +85,9 @@ class RecentlyUsedContainer(MutableMapping): return len(self._container) def __iter__(self): - raise NotImplementedError('Iteration over this class is unlikely to be threadsafe.') + raise NotImplementedError( + "Iteration over this class is unlikely to be threadsafe." + ) def clear(self): with self.lock: @@ -150,7 +155,7 @@ class HTTPHeaderDict(MutableMapping): def __getitem__(self, key): val = self._container[key.lower()] - return ', '.join(val[1:]) + return ", ".join(val[1:]) def __delitem__(self, key): del self._container[key.lower()] @@ -159,17 +164,18 @@ class HTTPHeaderDict(MutableMapping): return key.lower() in self._container def __eq__(self, other): - if not isinstance(other, Mapping) and not hasattr(other, 'keys'): + if not isinstance(other, Mapping) and not hasattr(other, "keys"): return False if not isinstance(other, type(self)): other = type(self)(other) - return (dict((k.lower(), v) for k, v in self.itermerged()) == - dict((k.lower(), v) for k, v in other.itermerged())) + return dict((k.lower(), v) for k, v in self.itermerged()) == dict( + (k.lower(), v) for k, v in other.itermerged() + ) def __ne__(self, other): return not self.__eq__(other) - if not PY3: # Python 2 + if six.PY2: # Python 2 iterkeys = MutableMapping.iterkeys itervalues = MutableMapping.itervalues @@ -184,9 +190,9 @@ class HTTPHeaderDict(MutableMapping): yield vals[0] def pop(self, key, default=__marker): - '''D.pop(k[,d]) -> v, remove specified key and return the corresponding value. - If key is not found, d is returned if given, otherwise KeyError is raised. - ''' + """D.pop(k[,d]) -> v, remove specified key and return the corresponding value. + If key is not found, d is returned if given, otherwise KeyError is raised. + """ # Using the MutableMapping function directly fails due to the private marker. # Using ordinary dict.pop would expose the internal structures. # So let's reinvent the wheel. @@ -228,8 +234,10 @@ class HTTPHeaderDict(MutableMapping): with self.add instead of self.__setitem__ """ if len(args) > 1: - raise TypeError("extend() takes at most 1 positional " - "arguments ({0} given)".format(len(args))) + raise TypeError( + "extend() takes at most 1 positional " + "arguments ({0} given)".format(len(args)) + ) other = args[0] if len(args) >= 1 else () if isinstance(other, HTTPHeaderDict): @@ -295,7 +303,7 @@ class HTTPHeaderDict(MutableMapping): """Iterate over all headers, merging duplicate ones together.""" for key in self: val = self._container[key.lower()] - yield val[0], ', '.join(val[1:]) + yield val[0], ", ".join(val[1:]) def items(self): return list(self.iteritems()) @@ -306,7 +314,7 @@ class HTTPHeaderDict(MutableMapping): # python2.7 does not expose a proper API for exporting multiheaders # efficiently. This function re-reads raw lines from the message # object and extracts the multiheaders properly. - obs_fold_continued_leaders = (' ', '\t') + obs_fold_continued_leaders = (" ", "\t") headers = [] for line in message.headers: @@ -316,14 +324,14 @@ class HTTPHeaderDict(MutableMapping): # in RFC-7230 S3.2.4. This indicates a multiline header, but # there exists no previous header to which we can attach it. raise InvalidHeader( - 'Header continuation with no previous header: %s' % line + "Header continuation with no previous header: %s" % line ) else: key, value = headers[-1] - headers[-1] = (key, value + ' ' + line.strip()) + headers[-1] = (key, value + " " + line.strip()) continue - key, value = line.split(':', 1) + key, value = line.split(":", 1) headers.append((key, value.strip())) return cls(headers) diff --git a/pipenv/vendor/urllib3/_version.py b/pipenv/vendor/urllib3/_version.py new file mode 100644 index 00000000..e8ebee95 --- /dev/null +++ b/pipenv/vendor/urllib3/_version.py @@ -0,0 +1,2 @@ +# This file is protected via CODEOWNERS +__version__ = "1.26.6" diff --git a/pipenv/vendor/urllib3/connection.py b/pipenv/vendor/urllib3/connection.py index f816ee80..4c996659 100644 --- a/pipenv/vendor/urllib3/connection.py +++ b/pipenv/vendor/urllib3/connection.py @@ -1,16 +1,22 @@ from __future__ import absolute_import + import datetime import logging import os +import re import socket -from socket import error as SocketError, timeout as SocketTimeout import warnings +from socket import error as SocketError +from socket import timeout as SocketTimeout + from .packages import six from .packages.six.moves.http_client import HTTPConnection as _HTTPConnection from .packages.six.moves.http_client import HTTPException # noqa: F401 +from .util.proxy import create_proxy_ssl_context try: # Compiled with SSL? import ssl + BaseSSLError = ssl.SSLError except (ImportError, AttributeError): # Platform-specific: No SSL. ssl = None @@ -28,71 +34,71 @@ except NameError: pass +try: # Python 3: + # Not a no-op, we're adding this to the namespace so it can be imported. + BrokenPipeError = BrokenPipeError +except NameError: # Python 2: + + class BrokenPipeError(Exception): + pass + + +from ._collections import HTTPHeaderDict # noqa (historical, removed in v2) +from ._version import __version__ from .exceptions import ( - NewConnectionError, ConnectTimeoutError, + NewConnectionError, SubjectAltNameWarning, SystemTimeWarning, ) -from .packages.ssl_match_hostname import match_hostname, CertificateError - +from .packages.ssl_match_hostname import CertificateError, match_hostname +from .util import SKIP_HEADER, SKIPPABLE_HEADERS, connection from .util.ssl_ import ( - resolve_cert_reqs, - resolve_ssl_version, assert_fingerprint, create_urllib3_context, - ssl_wrap_socket + resolve_cert_reqs, + resolve_ssl_version, + ssl_wrap_socket, ) - -from .util import connection - -from ._collections import HTTPHeaderDict - log = logging.getLogger(__name__) -port_by_scheme = { - 'http': 80, - 'https': 443, -} +port_by_scheme = {"http": 80, "https": 443} -# When updating RECENT_DATE, move it to within two years of the current date, -# and not less than 6 months ago. -# Example: if Today is 2018-01-01, then RECENT_DATE should be any date on or -# after 2016-01-01 (today - 2 years) AND before 2017-07-01 (today - 6 months) -RECENT_DATE = datetime.date(2017, 6, 30) +# When it comes time to update this value as a part of regular maintenance +# (ie test_recent_date is failing) update it to ~6 months before the current date. +RECENT_DATE = datetime.date(2020, 7, 1) - -class DummyConnection(object): - """Used to detect a failed ConnectionCls import.""" - pass +_CONTAINS_CONTROL_CHAR_RE = re.compile(r"[^-!#$%&'*+.^_`|~0-9a-zA-Z]") class HTTPConnection(_HTTPConnection, object): """ - Based on httplib.HTTPConnection but provides an extra constructor + Based on :class:`http.client.HTTPConnection` but provides an extra constructor backwards-compatibility layer between older and newer Pythons. Additional keyword parameters are used to configure attributes of the connection. Accepted parameters include: - - ``strict``: See the documentation on :class:`urllib3.connectionpool.HTTPConnectionPool` - - ``source_address``: Set the source address for the current connection. - - ``socket_options``: Set specific options on the underlying socket. If not specified, then - defaults are loaded from ``HTTPConnection.default_socket_options`` which includes disabling - Nagle's algorithm (sets TCP_NODELAY to 1) unless the connection is behind a proxy. + - ``strict``: See the documentation on :class:`urllib3.connectionpool.HTTPConnectionPool` + - ``source_address``: Set the source address for the current connection. + - ``socket_options``: Set specific options on the underlying socket. If not specified, then + defaults are loaded from ``HTTPConnection.default_socket_options`` which includes disabling + Nagle's algorithm (sets TCP_NODELAY to 1) unless the connection is behind a proxy. - For example, if you wish to enable TCP Keep Alive in addition to the defaults, - you might pass:: + For example, if you wish to enable TCP Keep Alive in addition to the defaults, + you might pass: - HTTPConnection.default_socket_options + [ - (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), - ] + .. code-block:: python - Or you may want to disable the defaults by passing an empty list (e.g., ``[]``). + HTTPConnection.default_socket_options + [ + (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), + ] + + Or you may want to disable the defaults by passing an empty list (e.g., ``[]``). """ - default_port = port_by_scheme['http'] + default_port = port_by_scheme["http"] #: Disable Nagle's algorithm by default. #: ``[(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)]`` @@ -102,15 +108,19 @@ class HTTPConnection(_HTTPConnection, object): is_verified = False def __init__(self, *args, **kw): - if six.PY3: - kw.pop('strict', None) + if not six.PY2: + kw.pop("strict", None) # Pre-set source_address. - self.source_address = kw.get('source_address') + self.source_address = kw.get("source_address") #: The socket options provided by the user. If no options are #: provided, we use the default options. - self.socket_options = kw.pop('socket_options', self.default_socket_options) + self.socket_options = kw.pop("socket_options", self.default_socket_options) + + # Proxy options provided by the user. + self.proxy = kw.pop("proxy", None) + self.proxy_config = kw.pop("proxy_config", None) _HTTPConnection.__init__(self, *args, **kw) @@ -131,7 +141,7 @@ class HTTPConnection(_HTTPConnection, object): those cases where it's appropriate (i.e., when doing DNS lookup to establish the actual TCP connection across which we're going to send HTTP requests). """ - return self._dns_host.rstrip('.') + return self._dns_host.rstrip(".") @host.setter def host(self, value): @@ -144,36 +154,43 @@ class HTTPConnection(_HTTPConnection, object): self._dns_host = value def _new_conn(self): - """ Establish a socket connection and set nodelay settings on it. + """Establish a socket connection and set nodelay settings on it. :return: New socket connection. """ extra_kw = {} if self.source_address: - extra_kw['source_address'] = self.source_address + extra_kw["source_address"] = self.source_address if self.socket_options: - extra_kw['socket_options'] = self.socket_options + extra_kw["socket_options"] = self.socket_options try: conn = connection.create_connection( - (self._dns_host, self.port), self.timeout, **extra_kw) + (self._dns_host, self.port), self.timeout, **extra_kw + ) except SocketTimeout: raise ConnectTimeoutError( - self, "Connection to %s timed out. (connect timeout=%s)" % - (self.host, self.timeout)) + self, + "Connection to %s timed out. (connect timeout=%s)" + % (self.host, self.timeout), + ) except SocketError as e: raise NewConnectionError( - self, "Failed to establish a new connection: %s" % e) + self, "Failed to establish a new connection: %s" % e + ) return conn + def _is_using_tunnel(self): + # Google App Engine's httplib does not define _tunnel_host + return getattr(self, "_tunnel_host", None) + def _prepare_conn(self, conn): self.sock = conn - # Google App Engine's httplib does not define _tunnel_host - if getattr(self, '_tunnel_host', None): + if self._is_using_tunnel(): # TODO: Fix tunnel so it doesn't depend on self.sock state. self._tunnel() # Mark this connection as not reusable @@ -183,24 +200,57 @@ class HTTPConnection(_HTTPConnection, object): conn = self._new_conn() self._prepare_conn(conn) + def putrequest(self, method, url, *args, **kwargs): + """ """ + # Empty docstring because the indentation of CPython's implementation + # is broken but we don't want this method in our documentation. + match = _CONTAINS_CONTROL_CHAR_RE.search(method) + if match: + raise ValueError( + "Method cannot contain non-token characters %r (found at least %r)" + % (method, match.group()) + ) + + return _HTTPConnection.putrequest(self, method, url, *args, **kwargs) + + def putheader(self, header, *values): + """ """ + if not any(isinstance(v, str) and v == SKIP_HEADER for v in values): + _HTTPConnection.putheader(self, header, *values) + elif six.ensure_str(header.lower()) not in SKIPPABLE_HEADERS: + raise ValueError( + "urllib3.util.SKIP_HEADER only supports '%s'" + % ("', '".join(map(str.title, sorted(SKIPPABLE_HEADERS))),) + ) + + def request(self, method, url, body=None, headers=None): + if headers is None: + headers = {} + else: + # Avoid modifying the headers passed into .request() + headers = headers.copy() + if "user-agent" not in (six.ensure_str(k.lower()) for k in headers): + headers["User-Agent"] = _get_default_user_agent() + super(HTTPConnection, self).request(method, url, body=body, headers=headers) + def request_chunked(self, method, url, body=None, headers=None): """ Alternative to the common request method, which sends the body with chunked encoding and not as one block """ - headers = HTTPHeaderDict(headers if headers is not None else {}) - skip_accept_encoding = 'accept-encoding' in headers - skip_host = 'host' in headers + headers = headers or {} + header_keys = set([six.ensure_str(k.lower()) for k in headers]) + skip_accept_encoding = "accept-encoding" in header_keys + skip_host = "host" in header_keys self.putrequest( - method, - url, - skip_accept_encoding=skip_accept_encoding, - skip_host=skip_host + method, url, skip_accept_encoding=skip_accept_encoding, skip_host=skip_host ) + if "user-agent" not in header_keys: + self.putheader("User-Agent", _get_default_user_agent()) for header, value in headers.items(): self.putheader(header, value) - if 'transfer-encoding' not in headers: - self.putheader('Transfer-Encoding', 'chunked') + if "transfer-encoding" not in header_keys: + self.putheader("Transfer-Encoding", "chunked") self.endheaders() if body is not None: @@ -211,29 +261,49 @@ class HTTPConnection(_HTTPConnection, object): if not chunk: continue if not isinstance(chunk, bytes): - chunk = chunk.encode('utf8') + chunk = chunk.encode("utf8") len_str = hex(len(chunk))[2:] - self.send(len_str.encode('utf-8')) - self.send(b'\r\n') - self.send(chunk) - self.send(b'\r\n') + to_send = bytearray(len_str.encode()) + to_send += b"\r\n" + to_send += chunk + to_send += b"\r\n" + self.send(to_send) # After the if clause, to always have a closed body - self.send(b'0\r\n\r\n') + self.send(b"0\r\n\r\n") class HTTPSConnection(HTTPConnection): - default_port = port_by_scheme['https'] + """ + Many of the parameters to this constructor are passed to the underlying SSL + socket by means of :py:func:`urllib3.util.ssl_wrap_socket`. + """ + default_port = port_by_scheme["https"] + + cert_reqs = None + ca_certs = None + ca_cert_dir = None + ca_cert_data = None ssl_version = None + assert_fingerprint = None + tls_in_tls_required = False - def __init__(self, host, port=None, key_file=None, cert_file=None, - key_password=None, strict=None, - timeout=socket._GLOBAL_DEFAULT_TIMEOUT, - ssl_context=None, server_hostname=None, **kw): + def __init__( + self, + host, + port=None, + key_file=None, + cert_file=None, + key_password=None, + strict=None, + timeout=socket._GLOBAL_DEFAULT_TIMEOUT, + ssl_context=None, + server_hostname=None, + **kw + ): - HTTPConnection.__init__(self, host, port, strict=strict, - timeout=timeout, **kw) + HTTPConnection.__init__(self, host, port, strict=strict, timeout=timeout, **kw) self.key_file = key_file self.cert_file = cert_file @@ -243,43 +313,20 @@ class HTTPSConnection(HTTPConnection): # Required property for Google AppEngine 1.9.0 which otherwise causes # HTTPS requests to go out as HTTP. (See Issue #356) - self._protocol = 'https' + self._protocol = "https" - def connect(self): - conn = self._new_conn() - self._prepare_conn(conn) - - if self.ssl_context is None: - self.ssl_context = create_urllib3_context( - ssl_version=resolve_ssl_version(None), - cert_reqs=resolve_cert_reqs(None), - ) - - self.sock = ssl_wrap_socket( - sock=conn, - keyfile=self.key_file, - certfile=self.cert_file, - key_password=self.key_password, - ssl_context=self.ssl_context, - server_hostname=self.server_hostname - ) - - -class VerifiedHTTPSConnection(HTTPSConnection): - """ - Based on httplib.HTTPSConnection but wraps the socket with - SSL certification. - """ - cert_reqs = None - ca_certs = None - ca_cert_dir = None - ssl_version = None - assert_fingerprint = None - - def set_cert(self, key_file=None, cert_file=None, - cert_reqs=None, key_password=None, ca_certs=None, - assert_hostname=None, assert_fingerprint=None, - ca_cert_dir=None): + def set_cert( + self, + key_file=None, + cert_file=None, + cert_reqs=None, + key_password=None, + ca_certs=None, + assert_hostname=None, + assert_fingerprint=None, + ca_cert_dir=None, + ca_cert_data=None, + ): """ This method should only be called once, before the connection is used. """ @@ -299,15 +346,21 @@ class VerifiedHTTPSConnection(HTTPSConnection): self.assert_fingerprint = assert_fingerprint self.ca_certs = ca_certs and os.path.expanduser(ca_certs) self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir) + self.ca_cert_data = ca_cert_data def connect(self): # Add certificate verification conn = self._new_conn() hostname = self.host + tls_in_tls = False + + if self._is_using_tunnel(): + if self.tls_in_tls_required: + conn = self._connect_tls_proxy(hostname, conn) + tls_in_tls = True - # Google App Engine's httplib does not define _tunnel_host - if getattr(self, '_tunnel_host', None): self.sock = conn + # Calls self._set_hostport(), so self.host is # self._tunnel_host below. self._tunnel() @@ -323,15 +376,19 @@ class VerifiedHTTPSConnection(HTTPSConnection): is_time_off = datetime.date.today() < RECENT_DATE if is_time_off: - warnings.warn(( - 'System time is way off (before {0}). This will probably ' - 'lead to SSL verification errors').format(RECENT_DATE), - SystemTimeWarning + warnings.warn( + ( + "System time is way off (before {0}). This will probably " + "lead to SSL verification errors" + ).format(RECENT_DATE), + SystemTimeWarning, ) # Wrap socket using verification with the root certs in # trusted_root_certs + default_ssl_context = False if self.ssl_context is None: + default_ssl_context = True self.ssl_context = create_urllib3_context( ssl_version=resolve_ssl_version(self.ssl_version), cert_reqs=resolve_cert_reqs(self.cert_reqs), @@ -339,6 +396,18 @@ class VerifiedHTTPSConnection(HTTPSConnection): context = self.ssl_context context.verify_mode = resolve_cert_reqs(self.cert_reqs) + + # Try to load OS default certs if none are given. + # Works well on Windows (requires Python3.4+) + if ( + not self.ca_certs + and not self.ca_cert_dir + and not self.ca_cert_data + and default_ssl_context + and hasattr(context, "load_default_certs") + ): + context.load_default_certs() + self.sock = ssl_wrap_socket( sock=conn, keyfile=self.key_file, @@ -346,32 +415,95 @@ class VerifiedHTTPSConnection(HTTPSConnection): key_password=self.key_password, ca_certs=self.ca_certs, ca_cert_dir=self.ca_cert_dir, + ca_cert_data=self.ca_cert_data, server_hostname=server_hostname, - ssl_context=context) + ssl_context=context, + tls_in_tls=tls_in_tls, + ) + + # If we're using all defaults and the connection + # is TLSv1 or TLSv1.1 we throw a DeprecationWarning + # for the host. + if ( + default_ssl_context + and self.ssl_version is None + and hasattr(self.sock, "version") + and self.sock.version() in {"TLSv1", "TLSv1.1"} + ): + warnings.warn( + "Negotiating TLSv1/TLSv1.1 by default is deprecated " + "and will be disabled in urllib3 v2.0.0. Connecting to " + "'%s' with '%s' can be enabled by explicitly opting-in " + "with 'ssl_version'" % (self.host, self.sock.version()), + DeprecationWarning, + ) if self.assert_fingerprint: - assert_fingerprint(self.sock.getpeercert(binary_form=True), - self.assert_fingerprint) - elif context.verify_mode != ssl.CERT_NONE \ - and not getattr(context, 'check_hostname', False) \ - and self.assert_hostname is not False: + assert_fingerprint( + self.sock.getpeercert(binary_form=True), self.assert_fingerprint + ) + elif ( + context.verify_mode != ssl.CERT_NONE + and not getattr(context, "check_hostname", False) + and self.assert_hostname is not False + ): # While urllib3 attempts to always turn off hostname matching from # the TLS library, this cannot always be done. So we check whether # the TLS Library still thinks it's matching hostnames. cert = self.sock.getpeercert() - if not cert.get('subjectAltName', ()): - warnings.warn(( - 'Certificate for {0} has no `subjectAltName`, falling back to check for a ' - '`commonName` for now. This feature is being removed by major browsers and ' - 'deprecated by RFC 2818. (See https://github.com/shazow/urllib3/issues/497 ' - 'for details.)'.format(hostname)), - SubjectAltNameWarning + if not cert.get("subjectAltName", ()): + warnings.warn( + ( + "Certificate for {0} has no `subjectAltName`, falling back to check for a " + "`commonName` for now. This feature is being removed by major browsers and " + "deprecated by RFC 2818. (See https://github.com/urllib3/urllib3/issues/497 " + "for details.)".format(hostname) + ), + SubjectAltNameWarning, ) _match_hostname(cert, self.assert_hostname or server_hostname) self.is_verified = ( - context.verify_mode == ssl.CERT_REQUIRED or - self.assert_fingerprint is not None + context.verify_mode == ssl.CERT_REQUIRED + or self.assert_fingerprint is not None + ) + + def _connect_tls_proxy(self, hostname, conn): + """ + Establish a TLS connection to the proxy using the provided SSL context. + """ + proxy_config = self.proxy_config + ssl_context = proxy_config.ssl_context + if ssl_context: + # If the user provided a proxy context, we assume CA and client + # certificates have already been set + return ssl_wrap_socket( + sock=conn, + server_hostname=hostname, + ssl_context=ssl_context, + ) + + ssl_context = create_proxy_ssl_context( + self.ssl_version, + self.cert_reqs, + self.ca_certs, + self.ca_cert_dir, + self.ca_cert_data, + ) + # By default urllib3's SSLContext disables `check_hostname` and uses + # a custom check. For proxies we're good with relying on the default + # verification. + ssl_context.check_hostname = True + + # If no cert was provided, use only the default options for server + # certificate validation + return ssl_wrap_socket( + sock=conn, + ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, + ca_cert_data=self.ca_cert_data, + server_hostname=hostname, + ssl_context=ssl_context, ) @@ -379,9 +511,10 @@ def _match_hostname(cert, asserted_hostname): try: match_hostname(cert, asserted_hostname) except CertificateError as e: - log.error( - 'Certificate did not match expected hostname: %s. ' - 'Certificate: %s', asserted_hostname, cert + log.warning( + "Certificate did not match expected hostname: %s. Certificate: %s", + asserted_hostname, + cert, ) # Add cert to exception and reraise so client code can inspect # the cert when catching the exception, if they want to @@ -389,9 +522,18 @@ def _match_hostname(cert, asserted_hostname): raise -if ssl: - # Make a copy for testing. - UnverifiedHTTPSConnection = HTTPSConnection - HTTPSConnection = VerifiedHTTPSConnection -else: - HTTPSConnection = DummyConnection +def _get_default_user_agent(): + return "python-urllib3/%s" % __version__ + + +class DummyConnection(object): + """Used to detect a failed ConnectionCls import.""" + + pass + + +if not ssl: + HTTPSConnection = DummyConnection # noqa: F811 + + +VerifiedHTTPSConnection = HTTPSConnection diff --git a/pipenv/vendor/urllib3/connectionpool.py b/pipenv/vendor/urllib3/connectionpool.py index 157568a3..459bbe09 100644 --- a/pipenv/vendor/urllib3/connectionpool.py +++ b/pipenv/vendor/urllib3/connectionpool.py @@ -1,49 +1,53 @@ from __future__ import absolute_import + import errno import logging +import socket import sys import warnings +from socket import error as SocketError +from socket import timeout as SocketTimeout -from socket import error as SocketError, timeout as SocketTimeout -import socket - - +from .connection import ( + BaseSSLError, + BrokenPipeError, + DummyConnection, + HTTPConnection, + HTTPException, + HTTPSConnection, + VerifiedHTTPSConnection, + port_by_scheme, +) from .exceptions import ( ClosedPoolError, - ProtocolError, EmptyPoolError, HeaderParsingError, HostChangedError, + InsecureRequestWarning, LocationValueError, MaxRetryError, + NewConnectionError, + ProtocolError, ProxyError, ReadTimeoutError, SSLError, TimeoutError, - InsecureRequestWarning, - NewConnectionError, ) -from .packages.ssl_match_hostname import CertificateError from .packages import six from .packages.six.moves import queue -from .packages.rfc3986.normalizers import normalize_host -from .connection import ( - port_by_scheme, - DummyConnection, - HTTPConnection, HTTPSConnection, VerifiedHTTPSConnection, - HTTPException, BaseSSLError, -) +from .packages.ssl_match_hostname import CertificateError from .request import RequestMethods from .response import HTTPResponse - from .util.connection import is_connection_dropped +from .util.proxy import connection_requires_http_tunnel +from .util.queue import LifoQueue from .util.request import set_file_position from .util.response import assert_header_parsing from .util.retry import Retry from .util.timeout import Timeout -from .util.url import get_host, Url, NORMALIZABLE_SCHEMES -from .util.queue import LifoQueue - +from .util.url import Url, _encode_target +from .util.url import _normalize_host as normalize_host +from .util.url import get_host, parse_url xrange = six.moves.xrange @@ -57,6 +61,11 @@ class ConnectionPool(object): """ Base class for all connection pools, such as :class:`.HTTPConnectionPool` and :class:`.HTTPSConnectionPool`. + + .. note:: + ConnectionPool.urlopen() does not normalize or percent-encode target URIs + which is useful if your target server doesn't support percent-encoded + target URIs. """ scheme = None @@ -71,8 +80,7 @@ class ConnectionPool(object): self.port = port def __str__(self): - return '%s(host=%r, port=%r)' % (type(self).__name__, - self.host, self.port) + return "%s(host=%r, port=%r)" % (type(self).__name__, self.host, self.port) def __enter__(self): return self @@ -99,16 +107,16 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): :param host: Host used for this HTTP Connection (e.g. "localhost"), passed into - :class:`httplib.HTTPConnection`. + :class:`http.client.HTTPConnection`. :param port: Port used for this HTTP Connection (None is equivalent to 80), passed - into :class:`httplib.HTTPConnection`. + into :class:`http.client.HTTPConnection`. :param strict: Causes BadStatusLine to be raised if the status line can't be parsed as a valid HTTP/1.0 or 1.1 status line, passed into - :class:`httplib.HTTPConnection`. + :class:`http.client.HTTPConnection`. .. note:: Only works in Python 2. This parameter is ignored in Python 3. @@ -142,26 +150,36 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): :param _proxy: Parsed proxy URL, should not be used directly, instead, see - :class:`urllib3.connectionpool.ProxyManager`" + :class:`urllib3.ProxyManager` :param _proxy_headers: A dictionary with proxy headers, should not be used directly, - instead, see :class:`urllib3.connectionpool.ProxyManager`" + instead, see :class:`urllib3.ProxyManager` :param \\**conn_kw: Additional parameters are used to create fresh :class:`urllib3.connection.HTTPConnection`, :class:`urllib3.connection.HTTPSConnection` instances. """ - scheme = 'http' + scheme = "http" ConnectionCls = HTTPConnection ResponseCls = HTTPResponse - def __init__(self, host, port=None, strict=False, - timeout=Timeout.DEFAULT_TIMEOUT, maxsize=1, block=False, - headers=None, retries=None, - _proxy=None, _proxy_headers=None, - **conn_kw): + def __init__( + self, + host, + port=None, + strict=False, + timeout=Timeout.DEFAULT_TIMEOUT, + maxsize=1, + block=False, + headers=None, + retries=None, + _proxy=None, + _proxy_headers=None, + _proxy_config=None, + **conn_kw + ): ConnectionPool.__init__(self, host, port) RequestMethods.__init__(self, headers) @@ -181,6 +199,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): self.proxy = _proxy self.proxy_headers = _proxy_headers or {} + self.proxy_config = _proxy_config # Fill the queue up so that doing get() on it will block properly for _ in xrange(maxsize): @@ -195,19 +214,30 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): # Enable Nagle's algorithm for proxies, to avoid packet fragmentation. # We cannot know if the user has added default socket options, so we cannot replace the # list. - self.conn_kw.setdefault('socket_options', []) + self.conn_kw.setdefault("socket_options", []) + + self.conn_kw["proxy"] = self.proxy + self.conn_kw["proxy_config"] = self.proxy_config def _new_conn(self): """ Return a fresh :class:`HTTPConnection`. """ self.num_connections += 1 - log.debug("Starting new HTTP connection (%d): %s:%s", - self.num_connections, self.host, self.port or "80") + log.debug( + "Starting new HTTP connection (%d): %s:%s", + self.num_connections, + self.host, + self.port or "80", + ) - conn = self.ConnectionCls(host=self.host, port=self.port, - timeout=self.timeout.connect_timeout, - strict=self.strict, **self.conn_kw) + conn = self.ConnectionCls( + host=self.host, + port=self.port, + timeout=self.timeout.connect_timeout, + strict=self.strict, + **self.conn_kw + ) return conn def _get_conn(self, timeout=None): @@ -231,18 +261,19 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): except queue.Empty: if self.block: - raise EmptyPoolError(self, - "Pool reached maximum size and no more " - "connections are allowed.") + raise EmptyPoolError( + self, + "Pool reached maximum size and no more connections are allowed.", + ) pass # Oh well, we'll create a new connection then # If this is a persistent connection, check if it got disconnected if conn and is_connection_dropped(conn): log.debug("Resetting dropped connection: %s", self.host) conn.close() - if getattr(conn, 'auto_open', 1) == 0: + if getattr(conn, "auto_open", 1) == 0: # This is a proxied connection that has been mutated by - # httplib._tunnel() and cannot be reused (since it would + # http.client._tunnel() and cannot be reused (since it would # attempt to bypass the proxy) conn = None @@ -270,9 +301,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): pass except queue.Full: # This should never happen if self.block == True - log.warning( - "Connection pool is full, discarding connection: %s", - self.host) + log.warning("Connection pool is full, discarding connection: %s", self.host) # Connection never got put back into the pool, close it. if conn: @@ -289,7 +318,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): pass def _get_timeout(self, timeout): - """ Helper that always returns a :class:`urllib3.util.Timeout` """ + """Helper that always returns a :class:`urllib3.util.Timeout`""" if timeout is _Default: return self.timeout.clone() @@ -304,21 +333,30 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): """Is the error actually a timeout? Will raise a ReadTimeout or pass""" if isinstance(err, SocketTimeout): - raise ReadTimeoutError(self, url, "Read timed out. (read timeout=%s)" % timeout_value) + raise ReadTimeoutError( + self, url, "Read timed out. (read timeout=%s)" % timeout_value + ) # See the above comment about EAGAIN in Python 3. In Python 2 we have # to specifically catch it and throw the timeout error - if hasattr(err, 'errno') and err.errno in _blocking_errnos: - raise ReadTimeoutError(self, url, "Read timed out. (read timeout=%s)" % timeout_value) + if hasattr(err, "errno") and err.errno in _blocking_errnos: + raise ReadTimeoutError( + self, url, "Read timed out. (read timeout=%s)" % timeout_value + ) # Catch possible read timeouts thrown as SSL errors. If not the # case, rethrow the original. We need to do this because of: # http://bugs.python.org/issue10272 - if 'timed out' in str(err) or 'did not complete (read)' in str(err): # Python < 2.7.4 - raise ReadTimeoutError(self, url, "Read timed out. (read timeout=%s)" % timeout_value) + if "timed out" in str(err) or "did not complete (read)" in str( + err + ): # Python < 2.7.4 + raise ReadTimeoutError( + self, url, "Read timed out. (read timeout=%s)" % timeout_value + ) - def _make_request(self, conn, method, url, timeout=_Default, chunked=False, - **httplib_request_kw): + def _make_request( + self, conn, method, url, timeout=_Default, chunked=False, **httplib_request_kw + ): """ Perform a request on a given urllib connection object taken from our pool. @@ -347,18 +385,36 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): self._raise_timeout(err=e, url=url, timeout_value=conn.timeout) raise - # conn.request() calls httplib.*.request, not the method in + # conn.request() calls http.client.*.request, not the method in # urllib3.request. It also calls makefile (recv) on the socket. - if chunked: - conn.request_chunked(method, url, **httplib_request_kw) - else: - conn.request(method, url, **httplib_request_kw) + try: + if chunked: + conn.request_chunked(method, url, **httplib_request_kw) + else: + conn.request(method, url, **httplib_request_kw) + + # We are swallowing BrokenPipeError (errno.EPIPE) since the server is + # legitimately able to close the connection after sending a valid response. + # With this behaviour, the received response is still readable. + except BrokenPipeError: + # Python 3 + pass + except IOError as e: + # Python 2 and macOS/Linux + # EPIPE and ESHUTDOWN are BrokenPipeError on Python 2, and EPROTOTYPE is needed on macOS + # https://erickt.github.io/blog/2014/11/19/adventures-in-debugging-a-potential-osx-kernel-bug/ + if e.errno not in { + errno.EPIPE, + errno.ESHUTDOWN, + errno.EPROTOTYPE, + }: + raise # Reset the timeout for the recv() on the socket read_timeout = timeout_obj.read_timeout # App Engine doesn't have a sock attr - if getattr(conn, 'sock', None): + if getattr(conn, "sock", None): # In Python 3 socket.py will catch EAGAIN and return None when you # try and read into the file pointer created by http.client, which # instead raises a BadStatusLine exception. Instead of catching @@ -366,7 +422,8 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): # timeouts, check for a zero timeout before making the request. if read_timeout == 0: raise ReadTimeoutError( - self, url, "Read timed out. (read timeout=%s)" % read_timeout) + self, url, "Read timed out. (read timeout=%s)" % read_timeout + ) if read_timeout is Timeout.DEFAULT_TIMEOUT: conn.sock.settimeout(socket.getdefaulttimeout()) else: # None or a value @@ -381,26 +438,38 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): # Python 3 try: httplib_response = conn.getresponse() - except Exception as e: - # Remove the TypeError from the exception chain in Python 3; - # otherwise it looks like a programming error was the cause. + except BaseException as e: + # Remove the TypeError from the exception chain in + # Python 3 (including for exceptions like SystemExit). + # Otherwise it looks like a bug in the code. six.raise_from(e, None) except (SocketTimeout, BaseSSLError, SocketError) as e: self._raise_timeout(err=e, url=url, timeout_value=read_timeout) raise # AppEngine doesn't have a version attr. - http_version = getattr(conn, '_http_vsn_str', 'HTTP/?') - log.debug("%s://%s:%s \"%s %s %s\" %s %s", self.scheme, self.host, self.port, - method, url, http_version, httplib_response.status, - httplib_response.length) + http_version = getattr(conn, "_http_vsn_str", "HTTP/?") + log.debug( + '%s://%s:%s "%s %s %s" %s %s', + self.scheme, + self.host, + self.port, + method, + url, + http_version, + httplib_response.status, + httplib_response.length, + ) try: assert_header_parsing(httplib_response.msg) except (HeaderParsingError, TypeError) as hpe: # Platform-specific: Python 3 log.warning( - 'Failed to parse headers (url=%s): %s', - self._absolute_url(url), hpe, exc_info=True) + "Failed to parse headers (url=%s): %s", + self._absolute_url(url), + hpe, + exc_info=True, + ) return httplib_response @@ -430,7 +499,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): Check if the given ``url`` is a member of the same host as this connection pool. """ - if url.startswith('/'): + if url.startswith("/"): return True # TODO: Add optional support for socket.gethostbyname checking. @@ -446,10 +515,22 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): return (scheme, host, port) == (self.scheme, self.host, self.port) - def urlopen(self, method, url, body=None, headers=None, retries=None, - redirect=True, assert_same_host=True, timeout=_Default, - pool_timeout=None, release_conn=None, chunked=False, - body_pos=None, **response_kw): + def urlopen( + self, + method, + url, + body=None, + headers=None, + retries=None, + redirect=True, + assert_same_host=True, + timeout=_Default, + pool_timeout=None, + release_conn=None, + chunked=False, + body_pos=None, + **response_kw + ): """ Get a connection from the pool and perform an HTTP request. This is the lowest level call for making a request, so you'll need to specify all @@ -470,10 +551,12 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): :param method: HTTP request method (such as GET, POST, PUT, etc.) + :param url: + The URL to perform the request on. + :param body: - Data to send in the request body (useful for creating - POST requests, see HTTPConnectionPool.post_url for - more convenience). + Data to send in the request body, either :class:`str`, :class:`bytes`, + an iterable of :class:`str`/:class:`bytes`, or a file-like object. :param headers: Dictionary of custom headers to send, such as User-Agent, @@ -503,7 +586,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): :param assert_same_host: If ``True``, will make sure that the host of the pool requests is - consistent else will raise HostChangedError. When False, you can + consistent else will raise HostChangedError. When ``False``, you can use the pool on an HTTP proxy and request foreign hosts. :param timeout: @@ -540,6 +623,10 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): Additional parameters are passed to :meth:`urllib3.response.HTTPResponse.from_httplib` """ + + parsed_url = parse_url(url) + destination_scheme = parsed_url.scheme + if headers is None: headers = self.headers @@ -547,12 +634,18 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): retries = Retry.from_int(retries, redirect=redirect, default=self.retries) if release_conn is None: - release_conn = response_kw.get('preload_content', True) + release_conn = response_kw.get("preload_content", True) # Check host if assert_same_host and not self.is_same_host(url): raise HostChangedError(self, url, retries) + # Ensure that the URL we're connecting to is properly encoded + if url.startswith("/"): + url = six.ensure_str(_encode_target(url)) + else: + url = six.ensure_str(parsed_url.url) + conn = None # Track whether `conn` needs to be released before @@ -563,13 +656,17 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): # # See issue #651 [1] for details. # - # [1] <https://github.com/shazow/urllib3/issues/651> + # [1] <https://github.com/urllib3/urllib3/issues/651> release_this_conn = release_conn - # Merge the proxy headers. Only do this in HTTP. We have to copy the - # headers dict so we can safely change it without those changes being - # reflected in anyone else's copy. - if self.scheme == 'http': + http_tunnel_required = connection_requires_http_tunnel( + self.proxy, self.proxy_config, destination_scheme + ) + + # Merge the proxy headers. Only done when not using HTTP CONNECT. We + # have to copy the headers dict so we can safely change it without those + # changes being reflected in anyone else's copy. + if not http_tunnel_required: headers = headers.copy() headers.update(self.proxy_headers) @@ -592,15 +689,22 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): conn.timeout = timeout_obj.connect_timeout - is_new_proxy_conn = self.proxy is not None and not getattr(conn, 'sock', None) - if is_new_proxy_conn: + is_new_proxy_conn = self.proxy is not None and not getattr( + conn, "sock", None + ) + if is_new_proxy_conn and http_tunnel_required: self._prepare_proxy(conn) # Make the request on the httplib connection object. - httplib_response = self._make_request(conn, method, url, - timeout=timeout_obj, - body=body, headers=headers, - chunked=chunked) + httplib_response = self._make_request( + conn, + method, + url, + timeout=timeout_obj, + body=body, + headers=headers, + chunked=chunked, + ) # If we're going to release the connection in ``finally:``, then # the response doesn't need to know about the connection. Otherwise @@ -609,36 +713,48 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): response_conn = conn if not release_conn else None # Pass method to Response for length checking - response_kw['request_method'] = method + response_kw["request_method"] = method # Import httplib's response into our own wrapper object - response = self.ResponseCls.from_httplib(httplib_response, - pool=self, - connection=response_conn, - retries=retries, - **response_kw) + response = self.ResponseCls.from_httplib( + httplib_response, + pool=self, + connection=response_conn, + retries=retries, + **response_kw + ) # Everything went great! clean_exit = True - except queue.Empty: - # Timed out by queue. - raise EmptyPoolError(self, "No pool connections are available.") + except EmptyPoolError: + # Didn't get a connection from the pool, no need to clean up + clean_exit = True + release_this_conn = False + raise - except (TimeoutError, HTTPException, SocketError, ProtocolError, - BaseSSLError, SSLError, CertificateError) as e: + except ( + TimeoutError, + HTTPException, + SocketError, + ProtocolError, + BaseSSLError, + SSLError, + CertificateError, + ) as e: # Discard the connection for these exceptions. It will be # replaced during the next _get_conn() call. clean_exit = False if isinstance(e, (BaseSSLError, CertificateError)): e = SSLError(e) elif isinstance(e, (SocketError, NewConnectionError)) and self.proxy: - e = ProxyError('Cannot connect to proxy.', e) + e = ProxyError("Cannot connect to proxy.", e) elif isinstance(e, (SocketError, HTTPException)): - e = ProtocolError('Connection aborted.', e) + e = ProtocolError("Connection aborted.", e) - retries = retries.increment(method, url, error=e, _pool=self, - _stacktrace=sys.exc_info()[2]) + retries = retries.increment( + method, url, error=e, _pool=self, _stacktrace=sys.exc_info()[2] + ) retries.sleep() # Keep track of the error for the retry warning. @@ -661,77 +777,87 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): if not conn: # Try again - log.warning("Retrying (%r) after connection " - "broken by '%r': %s", retries, err, url) - return self.urlopen(method, url, body, headers, retries, - redirect, assert_same_host, - timeout=timeout, pool_timeout=pool_timeout, - release_conn=release_conn, body_pos=body_pos, - **response_kw) - - def drain_and_release_conn(response): - try: - # discard any remaining response body, the connection will be - # released back to the pool once the entire response is read - response.read() - except (TimeoutError, HTTPException, SocketError, ProtocolError, - BaseSSLError, SSLError): - pass + log.warning( + "Retrying (%r) after connection broken by '%r': %s", retries, err, url + ) + return self.urlopen( + method, + url, + body, + headers, + retries, + redirect, + assert_same_host, + timeout=timeout, + pool_timeout=pool_timeout, + release_conn=release_conn, + chunked=chunked, + body_pos=body_pos, + **response_kw + ) # Handle redirect? redirect_location = redirect and response.get_redirect_location() if redirect_location: if response.status == 303: - method = 'GET' + method = "GET" try: retries = retries.increment(method, url, response=response, _pool=self) except MaxRetryError: if retries.raise_on_redirect: - # Drain and release the connection for this response, since - # we're not returning it to be released manually. - drain_and_release_conn(response) + response.drain_conn() raise return response - # drain and return the connection to the pool before recursing - drain_and_release_conn(response) - + response.drain_conn() retries.sleep_for_retry(response) log.debug("Redirecting %s -> %s", url, redirect_location) return self.urlopen( - method, redirect_location, body, headers, - retries=retries, redirect=redirect, + method, + redirect_location, + body, + headers, + retries=retries, + redirect=redirect, assert_same_host=assert_same_host, - timeout=timeout, pool_timeout=pool_timeout, - release_conn=release_conn, body_pos=body_pos, - **response_kw) + timeout=timeout, + pool_timeout=pool_timeout, + release_conn=release_conn, + chunked=chunked, + body_pos=body_pos, + **response_kw + ) # Check if we should retry the HTTP response. - has_retry_after = bool(response.getheader('Retry-After')) + has_retry_after = bool(response.getheader("Retry-After")) if retries.is_retry(method, response.status, has_retry_after): try: retries = retries.increment(method, url, response=response, _pool=self) except MaxRetryError: if retries.raise_on_status: - # Drain and release the connection for this response, since - # we're not returning it to be released manually. - drain_and_release_conn(response) + response.drain_conn() raise return response - # drain and return the connection to the pool before recursing - drain_and_release_conn(response) - + response.drain_conn() retries.sleep(response) log.debug("Retry: %s", url) return self.urlopen( - method, url, body, headers, - retries=retries, redirect=redirect, + method, + url, + body, + headers, + retries=retries, + redirect=redirect, assert_same_host=assert_same_host, - timeout=timeout, pool_timeout=pool_timeout, + timeout=timeout, + pool_timeout=pool_timeout, release_conn=release_conn, - body_pos=body_pos, **response_kw) + chunked=chunked, + body_pos=body_pos, + **response_kw + ) return response @@ -740,11 +866,7 @@ class HTTPSConnectionPool(HTTPConnectionPool): """ Same as :class:`.HTTPConnectionPool`, but HTTPS. - When Python is compiled with the :mod:`ssl` module, then - :class:`.VerifiedHTTPSConnection` is used, which *can* verify certificates, - instead of :class:`.HTTPSConnection`. - - :class:`.VerifiedHTTPSConnection` uses one of ``assert_fingerprint``, + :class:`.HTTPSConnection` uses one of ``assert_fingerprint``, ``assert_hostname`` and ``host`` in this order to verify connections. If ``assert_hostname`` is False, no verification is done. @@ -754,21 +876,47 @@ class HTTPSConnectionPool(HTTPConnectionPool): the connection socket into an SSL socket. """ - scheme = 'https' + scheme = "https" ConnectionCls = HTTPSConnection - def __init__(self, host, port=None, - strict=False, timeout=Timeout.DEFAULT_TIMEOUT, maxsize=1, - block=False, headers=None, retries=None, - _proxy=None, _proxy_headers=None, - key_file=None, cert_file=None, cert_reqs=None, - key_password=None, ca_certs=None, ssl_version=None, - assert_hostname=None, assert_fingerprint=None, - ca_cert_dir=None, **conn_kw): + def __init__( + self, + host, + port=None, + strict=False, + timeout=Timeout.DEFAULT_TIMEOUT, + maxsize=1, + block=False, + headers=None, + retries=None, + _proxy=None, + _proxy_headers=None, + key_file=None, + cert_file=None, + cert_reqs=None, + key_password=None, + ca_certs=None, + ssl_version=None, + assert_hostname=None, + assert_fingerprint=None, + ca_cert_dir=None, + **conn_kw + ): - HTTPConnectionPool.__init__(self, host, port, strict, timeout, maxsize, - block, headers, retries, _proxy, _proxy_headers, - **conn_kw) + HTTPConnectionPool.__init__( + self, + host, + port, + strict, + timeout, + maxsize, + block, + headers, + retries, + _proxy, + _proxy_headers, + **conn_kw + ) self.key_file = key_file self.cert_file = cert_file @@ -787,36 +935,50 @@ class HTTPSConnectionPool(HTTPConnectionPool): """ if isinstance(conn, VerifiedHTTPSConnection): - conn.set_cert(key_file=self.key_file, - key_password=self.key_password, - cert_file=self.cert_file, - cert_reqs=self.cert_reqs, - ca_certs=self.ca_certs, - ca_cert_dir=self.ca_cert_dir, - assert_hostname=self.assert_hostname, - assert_fingerprint=self.assert_fingerprint) + conn.set_cert( + key_file=self.key_file, + key_password=self.key_password, + cert_file=self.cert_file, + cert_reqs=self.cert_reqs, + ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, + assert_hostname=self.assert_hostname, + assert_fingerprint=self.assert_fingerprint, + ) conn.ssl_version = self.ssl_version return conn def _prepare_proxy(self, conn): """ - Establish tunnel connection early, because otherwise httplib - would improperly set Host: header to proxy's IP:port. + Establishes a tunnel connection through HTTP CONNECT. + + Tunnel connection is established early because otherwise httplib would + improperly set Host: header to proxy's IP:port. """ + conn.set_tunnel(self._proxy_host, self.port, self.proxy_headers) + + if self.proxy.scheme == "https": + conn.tls_in_tls_required = True + conn.connect() def _new_conn(self): """ - Return a fresh :class:`httplib.HTTPSConnection`. + Return a fresh :class:`http.client.HTTPSConnection`. """ self.num_connections += 1 - log.debug("Starting new HTTPS connection (%d): %s:%s", - self.num_connections, self.host, self.port or "443") + log.debug( + "Starting new HTTPS connection (%d): %s:%s", + self.num_connections, + self.host, + self.port or "443", + ) if not self.ConnectionCls or self.ConnectionCls is DummyConnection: - raise SSLError("Can't connect to HTTPS URL because the SSL " - "module is not available.") + raise SSLError( + "Can't connect to HTTPS URL because the SSL module is not available." + ) actual_host = self.host actual_port = self.port @@ -824,11 +986,16 @@ class HTTPSConnectionPool(HTTPConnectionPool): actual_host = self.proxy.host actual_port = self.proxy.port - conn = self.ConnectionCls(host=actual_host, port=actual_port, - timeout=self.timeout.connect_timeout, - strict=self.strict, cert_file=self.cert_file, - key_file=self.key_file, key_password=self.key_password, - **self.conn_kw) + conn = self.ConnectionCls( + host=actual_host, + port=actual_port, + timeout=self.timeout.connect_timeout, + strict=self.strict, + cert_file=self.cert_file, + key_file=self.key_file, + key_password=self.key_password, + **self.conn_kw + ) return self._prepare_conn(conn) @@ -839,16 +1006,19 @@ class HTTPSConnectionPool(HTTPConnectionPool): super(HTTPSConnectionPool, self)._validate_conn(conn) # Force connect early to allow us to validate the connection. - if not getattr(conn, 'sock', None): # AppEngine might not have `.sock` + if not getattr(conn, "sock", None): # AppEngine might not have `.sock` conn.connect() if not conn.is_verified: - warnings.warn(( - 'Unverified HTTPS request is being made. ' - 'Adding certificate verification is strongly advised. See: ' - 'https://urllib3.readthedocs.io/en/latest/advanced-usage.html' - '#ssl-warnings'), - InsecureRequestWarning) + warnings.warn( + ( + "Unverified HTTPS request is being made to host '%s'. " + "Adding certificate verification is strongly advised. See: " + "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" + "#ssl-warnings" % conn.host + ), + InsecureRequestWarning, + ) def connection_from_url(url, **kw): @@ -873,7 +1043,7 @@ def connection_from_url(url, **kw): """ scheme, host, port = get_host(url) port = port or port_by_scheme.get(scheme, 80) - if scheme == 'https': + if scheme == "https": return HTTPSConnectionPool(host, port=port, **kw) else: return HTTPConnectionPool(host, port=port, **kw) @@ -884,14 +1054,14 @@ def _normalize_host(host, scheme): Normalize hosts for comparisons and use with sockets. """ + host = normalize_host(host, scheme) + # httplib doesn't like it when we include brackets in IPv6 addresses # Specifically, if we include brackets but also pass the port then # httplib crazily doubles up the square brackets on the Host header. # Instead, we need to make sure we never pass ``None`` as the port. # However, for backward compatibility reasons we can't actually # *assert* that. See http://bugs.python.org/issue28539 - if host.startswith('[') and host.endswith(']'): - host = host.strip('[]') - if scheme in NORMALIZABLE_SCHEMES: - host = normalize_host(host) + if host.startswith("[") and host.endswith("]"): + host = host[1:-1] return host diff --git a/pipenv/vendor/urllib3/contrib/_appengine_environ.py b/pipenv/vendor/urllib3/contrib/_appengine_environ.py index f3e00942..8765b907 100644 --- a/pipenv/vendor/urllib3/contrib/_appengine_environ.py +++ b/pipenv/vendor/urllib3/contrib/_appengine_environ.py @@ -6,25 +6,31 @@ import os def is_appengine(): - return (is_local_appengine() or - is_prod_appengine() or - is_prod_appengine_mvms()) + return is_local_appengine() or is_prod_appengine() def is_appengine_sandbox(): - return is_appengine() and not is_prod_appengine_mvms() + """Reports if the app is running in the first generation sandbox. + + The second generation runtimes are technically still in a sandbox, but it + is much less restrictive, so generally you shouldn't need to check for it. + see https://cloud.google.com/appengine/docs/standard/runtimes + """ + return is_appengine() and os.environ["APPENGINE_RUNTIME"] == "python27" def is_local_appengine(): - return ('APPENGINE_RUNTIME' in os.environ and - 'Development/' in os.environ['SERVER_SOFTWARE']) + return "APPENGINE_RUNTIME" in os.environ and os.environ.get( + "SERVER_SOFTWARE", "" + ).startswith("Development/") def is_prod_appengine(): - return ('APPENGINE_RUNTIME' in os.environ and - 'Google App Engine/' in os.environ['SERVER_SOFTWARE'] and - not is_prod_appengine_mvms()) + return "APPENGINE_RUNTIME" in os.environ and os.environ.get( + "SERVER_SOFTWARE", "" + ).startswith("Google App Engine/") def is_prod_appengine_mvms(): - return os.environ.get('GAE_VM', False) == 'true' + """Deprecated.""" + return False diff --git a/pipenv/vendor/urllib3/contrib/_securetransport/bindings.py b/pipenv/vendor/urllib3/contrib/_securetransport/bindings.py index be342153..4a1449e8 100644 --- a/pipenv/vendor/urllib3/contrib/_securetransport/bindings.py +++ b/pipenv/vendor/urllib3/contrib/_securetransport/bindings.py @@ -32,35 +32,60 @@ license and by oscrypto's: from __future__ import absolute_import import platform -from ctypes.util import find_library from ctypes import ( - c_void_p, c_int32, c_char_p, c_size_t, c_byte, c_uint32, c_ulong, c_long, - c_bool + CDLL, + CFUNCTYPE, + POINTER, + c_bool, + c_byte, + c_char_p, + c_int32, + c_long, + c_size_t, + c_uint32, + c_ulong, + c_void_p, ) -from ctypes import CDLL, POINTER, CFUNCTYPE +from ctypes.util import find_library +from pipenv.vendor.urllib3.packages.six import raise_from -security_path = find_library('Security') -if not security_path: - raise ImportError('The library Security could not be found') - - -core_foundation_path = find_library('CoreFoundation') -if not core_foundation_path: - raise ImportError('The library CoreFoundation could not be found') - +if platform.system() != "Darwin": + raise ImportError("Only macOS is supported") version = platform.mac_ver()[0] -version_info = tuple(map(int, version.split('.'))) +version_info = tuple(map(int, version.split("."))) if version_info < (10, 8): raise OSError( - 'Only OS X 10.8 and newer are supported, not %s.%s' % ( - version_info[0], version_info[1] - ) + "Only OS X 10.8 and newer are supported, not %s.%s" + % (version_info[0], version_info[1]) ) -Security = CDLL(security_path, use_errno=True) -CoreFoundation = CDLL(core_foundation_path, use_errno=True) + +def load_cdll(name, macos10_16_path): + """Loads a CDLL by name, falling back to known path on 10.16+""" + try: + # Big Sur is technically 11 but we use 10.16 due to the Big Sur + # beta being labeled as 10.16. + if version_info >= (10, 16): + path = macos10_16_path + else: + path = find_library(name) + if not path: + raise OSError # Caught and reraised as 'ImportError' + return CDLL(path, use_errno=True) + except OSError: + raise_from(ImportError("The library %s failed to load" % name), None) + + +Security = load_cdll( + "Security", "/System/Library/Frameworks/Security.framework/Security" +) +CoreFoundation = load_cdll( + "CoreFoundation", + "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation", +) + Boolean = c_bool CFIndex = c_long @@ -129,27 +154,19 @@ try: Security.SecKeyGetTypeID.argtypes = [] Security.SecKeyGetTypeID.restype = CFTypeID - Security.SecCertificateCreateWithData.argtypes = [ - CFAllocatorRef, - CFDataRef - ] + Security.SecCertificateCreateWithData.argtypes = [CFAllocatorRef, CFDataRef] Security.SecCertificateCreateWithData.restype = SecCertificateRef - Security.SecCertificateCopyData.argtypes = [ - SecCertificateRef - ] + Security.SecCertificateCopyData.argtypes = [SecCertificateRef] Security.SecCertificateCopyData.restype = CFDataRef - Security.SecCopyErrorMessageString.argtypes = [ - OSStatus, - c_void_p - ] + Security.SecCopyErrorMessageString.argtypes = [OSStatus, c_void_p] Security.SecCopyErrorMessageString.restype = CFStringRef Security.SecIdentityCreateWithCertificate.argtypes = [ CFTypeRef, SecCertificateRef, - POINTER(SecIdentityRef) + POINTER(SecIdentityRef), ] Security.SecIdentityCreateWithCertificate.restype = OSStatus @@ -159,201 +176,133 @@ try: c_void_p, Boolean, c_void_p, - POINTER(SecKeychainRef) + POINTER(SecKeychainRef), ] Security.SecKeychainCreate.restype = OSStatus - Security.SecKeychainDelete.argtypes = [ - SecKeychainRef - ] + Security.SecKeychainDelete.argtypes = [SecKeychainRef] Security.SecKeychainDelete.restype = OSStatus Security.SecPKCS12Import.argtypes = [ CFDataRef, CFDictionaryRef, - POINTER(CFArrayRef) + POINTER(CFArrayRef), ] Security.SecPKCS12Import.restype = OSStatus SSLReadFunc = CFUNCTYPE(OSStatus, SSLConnectionRef, c_void_p, POINTER(c_size_t)) - SSLWriteFunc = CFUNCTYPE(OSStatus, SSLConnectionRef, POINTER(c_byte), POINTER(c_size_t)) + SSLWriteFunc = CFUNCTYPE( + OSStatus, SSLConnectionRef, POINTER(c_byte), POINTER(c_size_t) + ) - Security.SSLSetIOFuncs.argtypes = [ - SSLContextRef, - SSLReadFunc, - SSLWriteFunc - ] + Security.SSLSetIOFuncs.argtypes = [SSLContextRef, SSLReadFunc, SSLWriteFunc] Security.SSLSetIOFuncs.restype = OSStatus - Security.SSLSetPeerID.argtypes = [ - SSLContextRef, - c_char_p, - c_size_t - ] + Security.SSLSetPeerID.argtypes = [SSLContextRef, c_char_p, c_size_t] Security.SSLSetPeerID.restype = OSStatus - Security.SSLSetCertificate.argtypes = [ - SSLContextRef, - CFArrayRef - ] + Security.SSLSetCertificate.argtypes = [SSLContextRef, CFArrayRef] Security.SSLSetCertificate.restype = OSStatus - Security.SSLSetCertificateAuthorities.argtypes = [ - SSLContextRef, - CFTypeRef, - Boolean - ] + Security.SSLSetCertificateAuthorities.argtypes = [SSLContextRef, CFTypeRef, Boolean] Security.SSLSetCertificateAuthorities.restype = OSStatus - Security.SSLSetConnection.argtypes = [ - SSLContextRef, - SSLConnectionRef - ] + Security.SSLSetConnection.argtypes = [SSLContextRef, SSLConnectionRef] Security.SSLSetConnection.restype = OSStatus - Security.SSLSetPeerDomainName.argtypes = [ - SSLContextRef, - c_char_p, - c_size_t - ] + Security.SSLSetPeerDomainName.argtypes = [SSLContextRef, c_char_p, c_size_t] Security.SSLSetPeerDomainName.restype = OSStatus - Security.SSLHandshake.argtypes = [ - SSLContextRef - ] + Security.SSLHandshake.argtypes = [SSLContextRef] Security.SSLHandshake.restype = OSStatus - Security.SSLRead.argtypes = [ - SSLContextRef, - c_char_p, - c_size_t, - POINTER(c_size_t) - ] + Security.SSLRead.argtypes = [SSLContextRef, c_char_p, c_size_t, POINTER(c_size_t)] Security.SSLRead.restype = OSStatus - Security.SSLWrite.argtypes = [ - SSLContextRef, - c_char_p, - c_size_t, - POINTER(c_size_t) - ] + Security.SSLWrite.argtypes = [SSLContextRef, c_char_p, c_size_t, POINTER(c_size_t)] Security.SSLWrite.restype = OSStatus - Security.SSLClose.argtypes = [ - SSLContextRef - ] + Security.SSLClose.argtypes = [SSLContextRef] Security.SSLClose.restype = OSStatus - Security.SSLGetNumberSupportedCiphers.argtypes = [ - SSLContextRef, - POINTER(c_size_t) - ] + Security.SSLGetNumberSupportedCiphers.argtypes = [SSLContextRef, POINTER(c_size_t)] Security.SSLGetNumberSupportedCiphers.restype = OSStatus Security.SSLGetSupportedCiphers.argtypes = [ SSLContextRef, POINTER(SSLCipherSuite), - POINTER(c_size_t) + POINTER(c_size_t), ] Security.SSLGetSupportedCiphers.restype = OSStatus Security.SSLSetEnabledCiphers.argtypes = [ SSLContextRef, POINTER(SSLCipherSuite), - c_size_t + c_size_t, ] Security.SSLSetEnabledCiphers.restype = OSStatus - Security.SSLGetNumberEnabledCiphers.argtype = [ - SSLContextRef, - POINTER(c_size_t) - ] + Security.SSLGetNumberEnabledCiphers.argtype = [SSLContextRef, POINTER(c_size_t)] Security.SSLGetNumberEnabledCiphers.restype = OSStatus Security.SSLGetEnabledCiphers.argtypes = [ SSLContextRef, POINTER(SSLCipherSuite), - POINTER(c_size_t) + POINTER(c_size_t), ] Security.SSLGetEnabledCiphers.restype = OSStatus - Security.SSLGetNegotiatedCipher.argtypes = [ - SSLContextRef, - POINTER(SSLCipherSuite) - ] + Security.SSLGetNegotiatedCipher.argtypes = [SSLContextRef, POINTER(SSLCipherSuite)] Security.SSLGetNegotiatedCipher.restype = OSStatus Security.SSLGetNegotiatedProtocolVersion.argtypes = [ SSLContextRef, - POINTER(SSLProtocol) + POINTER(SSLProtocol), ] Security.SSLGetNegotiatedProtocolVersion.restype = OSStatus - Security.SSLCopyPeerTrust.argtypes = [ - SSLContextRef, - POINTER(SecTrustRef) - ] + Security.SSLCopyPeerTrust.argtypes = [SSLContextRef, POINTER(SecTrustRef)] Security.SSLCopyPeerTrust.restype = OSStatus - Security.SecTrustSetAnchorCertificates.argtypes = [ - SecTrustRef, - CFArrayRef - ] + Security.SecTrustSetAnchorCertificates.argtypes = [SecTrustRef, CFArrayRef] Security.SecTrustSetAnchorCertificates.restype = OSStatus - Security.SecTrustSetAnchorCertificatesOnly.argstypes = [ - SecTrustRef, - Boolean - ] + Security.SecTrustSetAnchorCertificatesOnly.argstypes = [SecTrustRef, Boolean] Security.SecTrustSetAnchorCertificatesOnly.restype = OSStatus - Security.SecTrustEvaluate.argtypes = [ - SecTrustRef, - POINTER(SecTrustResultType) - ] + Security.SecTrustEvaluate.argtypes = [SecTrustRef, POINTER(SecTrustResultType)] Security.SecTrustEvaluate.restype = OSStatus - Security.SecTrustGetCertificateCount.argtypes = [ - SecTrustRef - ] + Security.SecTrustGetCertificateCount.argtypes = [SecTrustRef] Security.SecTrustGetCertificateCount.restype = CFIndex - Security.SecTrustGetCertificateAtIndex.argtypes = [ - SecTrustRef, - CFIndex - ] + Security.SecTrustGetCertificateAtIndex.argtypes = [SecTrustRef, CFIndex] Security.SecTrustGetCertificateAtIndex.restype = SecCertificateRef Security.SSLCreateContext.argtypes = [ CFAllocatorRef, SSLProtocolSide, - SSLConnectionType + SSLConnectionType, ] Security.SSLCreateContext.restype = SSLContextRef - Security.SSLSetSessionOption.argtypes = [ - SSLContextRef, - SSLSessionOption, - Boolean - ] + Security.SSLSetSessionOption.argtypes = [SSLContextRef, SSLSessionOption, Boolean] Security.SSLSetSessionOption.restype = OSStatus - Security.SSLSetProtocolVersionMin.argtypes = [ - SSLContextRef, - SSLProtocol - ] + Security.SSLSetProtocolVersionMin.argtypes = [SSLContextRef, SSLProtocol] Security.SSLSetProtocolVersionMin.restype = OSStatus - Security.SSLSetProtocolVersionMax.argtypes = [ - SSLContextRef, - SSLProtocol - ] + Security.SSLSetProtocolVersionMax.argtypes = [SSLContextRef, SSLProtocol] Security.SSLSetProtocolVersionMax.restype = OSStatus - Security.SecCopyErrorMessageString.argtypes = [ - OSStatus, - c_void_p - ] + try: + Security.SSLSetALPNProtocols.argtypes = [SSLContextRef, CFArrayRef] + Security.SSLSetALPNProtocols.restype = OSStatus + except AttributeError: + # Supported only in 10.12+ + pass + + Security.SecCopyErrorMessageString.argtypes = [OSStatus, c_void_p] Security.SecCopyErrorMessageString.restype = CFStringRef Security.SSLReadFunc = SSLReadFunc @@ -369,64 +318,47 @@ try: Security.OSStatus = OSStatus Security.kSecImportExportPassphrase = CFStringRef.in_dll( - Security, 'kSecImportExportPassphrase' + Security, "kSecImportExportPassphrase" ) Security.kSecImportItemIdentity = CFStringRef.in_dll( - Security, 'kSecImportItemIdentity' + Security, "kSecImportItemIdentity" ) # CoreFoundation time! - CoreFoundation.CFRetain.argtypes = [ - CFTypeRef - ] + CoreFoundation.CFRetain.argtypes = [CFTypeRef] CoreFoundation.CFRetain.restype = CFTypeRef - CoreFoundation.CFRelease.argtypes = [ - CFTypeRef - ] + CoreFoundation.CFRelease.argtypes = [CFTypeRef] CoreFoundation.CFRelease.restype = None - CoreFoundation.CFGetTypeID.argtypes = [ - CFTypeRef - ] + CoreFoundation.CFGetTypeID.argtypes = [CFTypeRef] CoreFoundation.CFGetTypeID.restype = CFTypeID CoreFoundation.CFStringCreateWithCString.argtypes = [ CFAllocatorRef, c_char_p, - CFStringEncoding + CFStringEncoding, ] CoreFoundation.CFStringCreateWithCString.restype = CFStringRef - CoreFoundation.CFStringGetCStringPtr.argtypes = [ - CFStringRef, - CFStringEncoding - ] + CoreFoundation.CFStringGetCStringPtr.argtypes = [CFStringRef, CFStringEncoding] CoreFoundation.CFStringGetCStringPtr.restype = c_char_p CoreFoundation.CFStringGetCString.argtypes = [ CFStringRef, c_char_p, CFIndex, - CFStringEncoding + CFStringEncoding, ] CoreFoundation.CFStringGetCString.restype = c_bool - CoreFoundation.CFDataCreate.argtypes = [ - CFAllocatorRef, - c_char_p, - CFIndex - ] + CoreFoundation.CFDataCreate.argtypes = [CFAllocatorRef, c_char_p, CFIndex] CoreFoundation.CFDataCreate.restype = CFDataRef - CoreFoundation.CFDataGetLength.argtypes = [ - CFDataRef - ] + CoreFoundation.CFDataGetLength.argtypes = [CFDataRef] CoreFoundation.CFDataGetLength.restype = CFIndex - CoreFoundation.CFDataGetBytePtr.argtypes = [ - CFDataRef - ] + CoreFoundation.CFDataGetBytePtr.argtypes = [CFDataRef] CoreFoundation.CFDataGetBytePtr.restype = c_void_p CoreFoundation.CFDictionaryCreate.argtypes = [ @@ -435,14 +367,11 @@ try: POINTER(CFTypeRef), CFIndex, CFDictionaryKeyCallBacks, - CFDictionaryValueCallBacks + CFDictionaryValueCallBacks, ] CoreFoundation.CFDictionaryCreate.restype = CFDictionaryRef - CoreFoundation.CFDictionaryGetValue.argtypes = [ - CFDictionaryRef, - CFTypeRef - ] + CoreFoundation.CFDictionaryGetValue.argtypes = [CFDictionaryRef, CFTypeRef] CoreFoundation.CFDictionaryGetValue.restype = CFTypeRef CoreFoundation.CFArrayCreate.argtypes = [ @@ -456,36 +385,30 @@ try: CoreFoundation.CFArrayCreateMutable.argtypes = [ CFAllocatorRef, CFIndex, - CFArrayCallBacks + CFArrayCallBacks, ] CoreFoundation.CFArrayCreateMutable.restype = CFMutableArrayRef - CoreFoundation.CFArrayAppendValue.argtypes = [ - CFMutableArrayRef, - c_void_p - ] + CoreFoundation.CFArrayAppendValue.argtypes = [CFMutableArrayRef, c_void_p] CoreFoundation.CFArrayAppendValue.restype = None - CoreFoundation.CFArrayGetCount.argtypes = [ - CFArrayRef - ] + CoreFoundation.CFArrayGetCount.argtypes = [CFArrayRef] CoreFoundation.CFArrayGetCount.restype = CFIndex - CoreFoundation.CFArrayGetValueAtIndex.argtypes = [ - CFArrayRef, - CFIndex - ] + CoreFoundation.CFArrayGetValueAtIndex.argtypes = [CFArrayRef, CFIndex] CoreFoundation.CFArrayGetValueAtIndex.restype = c_void_p CoreFoundation.kCFAllocatorDefault = CFAllocatorRef.in_dll( - CoreFoundation, 'kCFAllocatorDefault' + CoreFoundation, "kCFAllocatorDefault" + ) + CoreFoundation.kCFTypeArrayCallBacks = c_void_p.in_dll( + CoreFoundation, "kCFTypeArrayCallBacks" ) - CoreFoundation.kCFTypeArrayCallBacks = c_void_p.in_dll(CoreFoundation, 'kCFTypeArrayCallBacks') CoreFoundation.kCFTypeDictionaryKeyCallBacks = c_void_p.in_dll( - CoreFoundation, 'kCFTypeDictionaryKeyCallBacks' + CoreFoundation, "kCFTypeDictionaryKeyCallBacks" ) CoreFoundation.kCFTypeDictionaryValueCallBacks = c_void_p.in_dll( - CoreFoundation, 'kCFTypeDictionaryValueCallBacks' + CoreFoundation, "kCFTypeDictionaryValueCallBacks" ) CoreFoundation.CFTypeRef = CFTypeRef @@ -494,7 +417,7 @@ try: CoreFoundation.CFDictionaryRef = CFDictionaryRef except (AttributeError): - raise ImportError('Error initializing ctypes') + raise ImportError("Error initializing ctypes") class CFConst(object): @@ -502,6 +425,7 @@ class CFConst(object): A class object that acts as essentially a namespace for CoreFoundation constants. """ + kCFStringEncodingUTF8 = CFStringEncoding(0x08000100) @@ -509,6 +433,7 @@ class SecurityConst(object): """ A class object that acts as essentially a namespace for Security constants. """ + kSSLSessionOptionBreakOnServerAuth = 0 kSSLProtocol2 = 1 @@ -516,6 +441,7 @@ class SecurityConst(object): kTLSProtocol1 = 4 kTLSProtocol11 = 7 kTLSProtocol12 = 8 + # SecureTransport does not support TLS 1.3 even if there's a constant for it kTLSProtocol13 = 10 kTLSProtocolMaxSupported = 999 diff --git a/pipenv/vendor/urllib3/contrib/_securetransport/low_level.py b/pipenv/vendor/urllib3/contrib/_securetransport/low_level.py index b13cd9e7..ed812019 100644 --- a/pipenv/vendor/urllib3/contrib/_securetransport/low_level.py +++ b/pipenv/vendor/urllib3/contrib/_securetransport/low_level.py @@ -10,13 +10,13 @@ appropriate and useful assistance to the higher-level code. import base64 import ctypes import itertools -import re import os +import re import ssl +import struct import tempfile -from .bindings import Security, CoreFoundation, CFConst - +from .bindings import CFConst, CoreFoundation, Security # This regular expression is used to grab PEM data out of a PEM bundle. _PEM_CERTS_RE = re.compile( @@ -56,6 +56,51 @@ def _cf_dictionary_from_tuples(tuples): ) +def _cfstr(py_bstr): + """ + Given a Python binary data, create a CFString. + The string must be CFReleased by the caller. + """ + c_str = ctypes.c_char_p(py_bstr) + cf_str = CoreFoundation.CFStringCreateWithCString( + CoreFoundation.kCFAllocatorDefault, + c_str, + CFConst.kCFStringEncodingUTF8, + ) + return cf_str + + +def _create_cfstring_array(lst): + """ + Given a list of Python binary data, create an associated CFMutableArray. + The array must be CFReleased by the caller. + + Raises an ssl.SSLError on failure. + """ + cf_arr = None + try: + cf_arr = CoreFoundation.CFArrayCreateMutable( + CoreFoundation.kCFAllocatorDefault, + 0, + ctypes.byref(CoreFoundation.kCFTypeArrayCallBacks), + ) + if not cf_arr: + raise MemoryError("Unable to allocate memory!") + for item in lst: + cf_str = _cfstr(item) + if not cf_str: + raise MemoryError("Unable to allocate memory!") + try: + CoreFoundation.CFArrayAppendValue(cf_arr, cf_str) + finally: + CoreFoundation.CFRelease(cf_str) + except BaseException as e: + if cf_arr: + CoreFoundation.CFRelease(cf_arr) + raise ssl.SSLError("Unable to allocate array: %s" % (e,)) + return cf_arr + + def _cf_string_to_unicode(value): """ Creates a Unicode string from a CFString object. Used entirely for error @@ -66,22 +111,18 @@ def _cf_string_to_unicode(value): value_as_void_p = ctypes.cast(value, ctypes.POINTER(ctypes.c_void_p)) string = CoreFoundation.CFStringGetCStringPtr( - value_as_void_p, - CFConst.kCFStringEncodingUTF8 + value_as_void_p, CFConst.kCFStringEncodingUTF8 ) if string is None: buffer = ctypes.create_string_buffer(1024) result = CoreFoundation.CFStringGetCString( - value_as_void_p, - buffer, - 1024, - CFConst.kCFStringEncodingUTF8 + value_as_void_p, buffer, 1024, CFConst.kCFStringEncodingUTF8 ) if not result: - raise OSError('Error copying C string from CFStringRef') + raise OSError("Error copying C string from CFStringRef") string = buffer.value if string is not None: - string = string.decode('utf-8') + string = string.decode("utf-8") return string @@ -97,8 +138,8 @@ def _assert_no_error(error, exception_class=None): output = _cf_string_to_unicode(cf_error_string) CoreFoundation.CFRelease(cf_error_string) - if output is None or output == u'': - output = u'OSStatus %s' % error + if output is None or output == u"": + output = u"OSStatus %s" % error if exception_class is None: exception_class = ssl.SSLError @@ -115,8 +156,7 @@ def _cert_array_from_pem(pem_bundle): pem_bundle = pem_bundle.replace(b"\r\n", b"\n") der_certs = [ - base64.b64decode(match.group(1)) - for match in _PEM_CERTS_RE.finditer(pem_bundle) + base64.b64decode(match.group(1)) for match in _PEM_CERTS_RE.finditer(pem_bundle) ] if not der_certs: raise ssl.SSLError("No root certificates specified") @@ -124,7 +164,7 @@ def _cert_array_from_pem(pem_bundle): cert_array = CoreFoundation.CFArrayCreateMutable( CoreFoundation.kCFAllocatorDefault, 0, - ctypes.byref(CoreFoundation.kCFTypeArrayCallBacks) + ctypes.byref(CoreFoundation.kCFTypeArrayCallBacks), ) if not cert_array: raise ssl.SSLError("Unable to allocate memory!") @@ -186,21 +226,16 @@ def _temporary_keychain(): # some random bytes to password-protect the keychain we're creating, so we # ask for 40 random bytes. random_bytes = os.urandom(40) - filename = base64.b16encode(random_bytes[:8]).decode('utf-8') + filename = base64.b16encode(random_bytes[:8]).decode("utf-8") password = base64.b16encode(random_bytes[8:]) # Must be valid UTF-8 tempdirectory = tempfile.mkdtemp() - keychain_path = os.path.join(tempdirectory, filename).encode('utf-8') + keychain_path = os.path.join(tempdirectory, filename).encode("utf-8") # We now want to create the keychain itself. keychain = Security.SecKeychainRef() status = Security.SecKeychainCreate( - keychain_path, - len(password), - password, - False, - None, - ctypes.byref(keychain) + keychain_path, len(password), password, False, None, ctypes.byref(keychain) ) _assert_no_error(status) @@ -219,14 +254,12 @@ def _load_items_from_file(keychain, path): identities = [] result_array = None - with open(path, 'rb') as f: + with open(path, "rb") as f: raw_filedata = f.read() try: filedata = CoreFoundation.CFDataCreate( - CoreFoundation.kCFAllocatorDefault, - raw_filedata, - len(raw_filedata) + CoreFoundation.kCFAllocatorDefault, raw_filedata, len(raw_filedata) ) result_array = CoreFoundation.CFArrayRef() result = Security.SecItemImport( @@ -237,7 +270,7 @@ def _load_items_from_file(keychain, path): 0, # import flags None, # key params, can include passphrase in the future keychain, # The keychain to insert into - ctypes.byref(result_array) # Results + ctypes.byref(result_array), # Results ) _assert_no_error(result) @@ -247,9 +280,7 @@ def _load_items_from_file(keychain, path): # keychain already has them! result_count = CoreFoundation.CFArrayGetCount(result_array) for index in range(result_count): - item = CoreFoundation.CFArrayGetValueAtIndex( - result_array, index - ) + item = CoreFoundation.CFArrayGetValueAtIndex(result_array, index) item = ctypes.cast(item, CoreFoundation.CFTypeRef) if _is_cert(item): @@ -307,9 +338,7 @@ def _load_client_cert_chain(keychain, *paths): try: for file_path in paths: - new_identities, new_certs = _load_items_from_file( - keychain, file_path - ) + new_identities, new_certs = _load_items_from_file(keychain, file_path) identities.extend(new_identities) certificates.extend(new_certs) @@ -318,9 +347,7 @@ def _load_client_cert_chain(keychain, *paths): if not identities: new_identity = Security.SecIdentityRef() status = Security.SecIdentityCreateWithCertificate( - keychain, - certificates[0], - ctypes.byref(new_identity) + keychain, certificates[0], ctypes.byref(new_identity) ) _assert_no_error(status) identities.append(new_identity) @@ -344,3 +371,26 @@ def _load_client_cert_chain(keychain, *paths): finally: for obj in itertools.chain(identities, certificates): CoreFoundation.CFRelease(obj) + + +TLS_PROTOCOL_VERSIONS = { + "SSLv2": (0, 2), + "SSLv3": (3, 0), + "TLSv1": (3, 1), + "TLSv1.1": (3, 2), + "TLSv1.2": (3, 3), +} + + +def _build_tls_unknown_ca_alert(version): + """ + Builds a TLS alert record for an unknown CA. + """ + ver_maj, ver_min = TLS_PROTOCOL_VERSIONS[version] + severity_fatal = 0x02 + description_unknown_ca = 0x30 + msg = struct.pack(">BB", severity_fatal, description_unknown_ca) + msg_len = len(msg) + record_type_alert = 0x15 + record = struct.pack(">BBBH", record_type_alert, ver_maj, ver_min, msg_len) + msg + return record diff --git a/pipenv/vendor/urllib3/contrib/appengine.py b/pipenv/vendor/urllib3/contrib/appengine.py index 2952f114..1b783c23 100644 --- a/pipenv/vendor/urllib3/contrib/appengine.py +++ b/pipenv/vendor/urllib3/contrib/appengine.py @@ -4,8 +4,8 @@ This module provides a pool manager that uses Google App Engine's Example usage:: - from urllib3 import PoolManager - from urllib3.contrib.appengine import AppEngineManager, is_appengine_sandbox + from pipenv.vendor.urllib3 import PoolManager + from pipenv.vendor.urllib3.contrib.appengine import AppEngineManager, is_appengine_sandbox if is_appengine_sandbox(): # AppEngineManager uses AppEngine's URLFetch API behind the scenes @@ -39,24 +39,24 @@ urllib3 on Google App Engine: """ from __future__ import absolute_import + import io import logging import warnings -from ..packages.six.moves.urllib.parse import urljoin from ..exceptions import ( HTTPError, HTTPWarning, MaxRetryError, ProtocolError, + SSLError, TimeoutError, - SSLError ) - +from ..packages.six.moves.urllib.parse import urljoin from ..request import RequestMethods from ..response import HTTPResponse -from ..util.timeout import Timeout from ..util.retry import Retry +from ..util.timeout import Timeout from . import _appengine_environ try: @@ -90,29 +90,30 @@ class AppEngineManager(RequestMethods): * If you attempt to use this on App Engine Flexible, as full socket support is available. * If a request size is more than 10 megabytes. - * If a response size is more than 32 megabtyes. + * If a response size is more than 32 megabytes. * If you use an unsupported request method such as OPTIONS. Beyond those cases, it will raise normal urllib3 errors. """ - def __init__(self, headers=None, retries=None, validate_certificate=True, - urlfetch_retries=True): + def __init__( + self, + headers=None, + retries=None, + validate_certificate=True, + urlfetch_retries=True, + ): if not urlfetch: raise AppEnginePlatformError( - "URLFetch is not available in this environment.") - - if is_prod_appengine_mvms(): - raise AppEnginePlatformError( - "Use normal urllib3.PoolManager instead of AppEngineManager" - "on Managed VMs, as using URLFetch is not necessary in " - "this environment.") + "URLFetch is not available in this environment." + ) warnings.warn( "urllib3 is using URLFetch on Google App Engine sandbox instead " "of sockets. To use sockets directly instead of URLFetch see " - "https://urllib3.readthedocs.io/en/latest/reference/urllib3.contrib.html.", - AppEnginePlatformWarning) + "https://urllib3.readthedocs.io/en/1.26.x/reference/urllib3.contrib.html.", + AppEnginePlatformWarning, + ) RequestMethods.__init__(self, headers) self.validate_certificate = validate_certificate @@ -127,17 +128,22 @@ class AppEngineManager(RequestMethods): # Return False to re-raise any potential exceptions return False - def urlopen(self, method, url, body=None, headers=None, - retries=None, redirect=True, timeout=Timeout.DEFAULT_TIMEOUT, - **response_kw): + def urlopen( + self, + method, + url, + body=None, + headers=None, + retries=None, + redirect=True, + timeout=Timeout.DEFAULT_TIMEOUT, + **response_kw + ): retries = self._get_retries(retries, redirect) try: - follow_redirects = ( - redirect and - retries.redirect != 0 and - retries.total) + follow_redirects = redirect and retries.redirect != 0 and retries.total response = urlfetch.fetch( url, payload=body, @@ -152,44 +158,52 @@ class AppEngineManager(RequestMethods): raise TimeoutError(self, e) except urlfetch.InvalidURLError as e: - if 'too large' in str(e): + if "too large" in str(e): raise AppEnginePlatformError( "URLFetch request too large, URLFetch only " - "supports requests up to 10mb in size.", e) + "supports requests up to 10mb in size.", + e, + ) raise ProtocolError(e) except urlfetch.DownloadError as e: - if 'Too many redirects' in str(e): + if "Too many redirects" in str(e): raise MaxRetryError(self, url, reason=e) raise ProtocolError(e) except urlfetch.ResponseTooLargeError as e: raise AppEnginePlatformError( "URLFetch response too large, URLFetch only supports" - "responses up to 32mb in size.", e) + "responses up to 32mb in size.", + e, + ) except urlfetch.SSLCertificateError as e: raise SSLError(e) except urlfetch.InvalidMethodError as e: raise AppEnginePlatformError( - "URLFetch does not support method: %s" % method, e) + "URLFetch does not support method: %s" % method, e + ) http_response = self._urlfetch_response_to_http_response( - response, retries=retries, **response_kw) + response, retries=retries, **response_kw + ) # Handle redirect? redirect_location = redirect and http_response.get_redirect_location() if redirect_location: # Check for redirect response - if (self.urlfetch_retries and retries.raise_on_redirect): + if self.urlfetch_retries and retries.raise_on_redirect: raise MaxRetryError(self, url, "too many redirects") else: if http_response.status == 303: - method = 'GET' + method = "GET" try: - retries = retries.increment(method, url, response=http_response, _pool=self) + retries = retries.increment( + method, url, response=http_response, _pool=self + ) except MaxRetryError: if retries.raise_on_redirect: raise MaxRetryError(self, url, "too many redirects") @@ -199,22 +213,32 @@ class AppEngineManager(RequestMethods): log.debug("Redirecting %s -> %s", url, redirect_location) redirect_url = urljoin(url, redirect_location) return self.urlopen( - method, redirect_url, body, headers, - retries=retries, redirect=redirect, - timeout=timeout, **response_kw) + method, + redirect_url, + body, + headers, + retries=retries, + redirect=redirect, + timeout=timeout, + **response_kw + ) # Check if we should retry the HTTP response. - has_retry_after = bool(http_response.getheader('Retry-After')) + has_retry_after = bool(http_response.getheader("Retry-After")) if retries.is_retry(method, http_response.status, has_retry_after): - retries = retries.increment( - method, url, response=http_response, _pool=self) + retries = retries.increment(method, url, response=http_response, _pool=self) log.debug("Retry: %s", url) retries.sleep(http_response) return self.urlopen( - method, url, - body=body, headers=headers, - retries=retries, redirect=redirect, - timeout=timeout, **response_kw) + method, + url, + body=body, + headers=headers, + retries=retries, + redirect=redirect, + timeout=timeout, + **response_kw + ) return http_response @@ -223,18 +247,18 @@ class AppEngineManager(RequestMethods): if is_prod_appengine(): # Production GAE handles deflate encoding automatically, but does # not remove the encoding header. - content_encoding = urlfetch_resp.headers.get('content-encoding') + content_encoding = urlfetch_resp.headers.get("content-encoding") - if content_encoding == 'deflate': - del urlfetch_resp.headers['content-encoding'] + if content_encoding == "deflate": + del urlfetch_resp.headers["content-encoding"] - transfer_encoding = urlfetch_resp.headers.get('transfer-encoding') + transfer_encoding = urlfetch_resp.headers.get("transfer-encoding") # We have a full response's content, # so let's make sure we don't report ourselves as chunked data. - if transfer_encoding == 'chunked': + if transfer_encoding == "chunked": encodings = transfer_encoding.split(",") - encodings.remove('chunked') - urlfetch_resp.headers['transfer-encoding'] = ','.join(encodings) + encodings.remove("chunked") + urlfetch_resp.headers["transfer-encoding"] = ",".join(encodings) original_response = HTTPResponse( # In order for decoding to work, we must present the content as @@ -262,20 +286,21 @@ class AppEngineManager(RequestMethods): warnings.warn( "URLFetch does not support granular timeout settings, " "reverting to total or default URLFetch timeout.", - AppEnginePlatformWarning) + AppEnginePlatformWarning, + ) return timeout.total return timeout def _get_retries(self, retries, redirect): if not isinstance(retries, Retry): - retries = Retry.from_int( - retries, redirect=redirect, default=self.retries) + retries = Retry.from_int(retries, redirect=redirect, default=self.retries) if retries.connect or retries.read or retries.redirect: warnings.warn( "URLFetch only supports total retries and does not " "recognize connect, read, or redirect retry parameters.", - AppEnginePlatformWarning) + AppEnginePlatformWarning, + ) return retries diff --git a/pipenv/vendor/urllib3/contrib/ntlmpool.py b/pipenv/vendor/urllib3/contrib/ntlmpool.py index 8ea127c5..41a8fd17 100644 --- a/pipenv/vendor/urllib3/contrib/ntlmpool.py +++ b/pipenv/vendor/urllib3/contrib/ntlmpool.py @@ -5,12 +5,21 @@ Issue #10, see: http://code.google.com/p/urllib3/issues/detail?id=10 """ from __future__ import absolute_import +import warnings from logging import getLogger + from ntlm import ntlm from .. import HTTPSConnectionPool from ..packages.six.moves.http_client import HTTPSConnection +warnings.warn( + "The 'urllib3.contrib.ntlmpool' module is deprecated and will be removed " + "in urllib3 v2.0 release, urllib3 is not able to support it properly due " + "to reasons listed in issue: https://github.com/urllib3/urllib3/issues/2282. " + "If you are a user of this module please comment in the mentioned issue.", + DeprecationWarning, +) log = getLogger(__name__) @@ -20,7 +29,7 @@ class NTLMConnectionPool(HTTPSConnectionPool): Implements an NTLM authentication version of an urllib3 connection pool """ - scheme = 'https' + scheme = "https" def __init__(self, user, pw, authurl, *args, **kwargs): """ @@ -31,7 +40,7 @@ class NTLMConnectionPool(HTTPSConnectionPool): super(NTLMConnectionPool, self).__init__(*args, **kwargs) self.authurl = authurl self.rawuser = user - user_parts = user.split('\\', 1) + user_parts = user.split("\\", 1) self.domain = user_parts[0].upper() self.user = user_parts[1] self.pw = pw @@ -40,72 +49,82 @@ class NTLMConnectionPool(HTTPSConnectionPool): # Performs the NTLM handshake that secures the connection. The socket # must be kept open while requests are performed. self.num_connections += 1 - log.debug('Starting NTLM HTTPS connection no. %d: https://%s%s', - self.num_connections, self.host, self.authurl) + log.debug( + "Starting NTLM HTTPS connection no. %d: https://%s%s", + self.num_connections, + self.host, + self.authurl, + ) - headers = {'Connection': 'Keep-Alive'} - req_header = 'Authorization' - resp_header = 'www-authenticate' + headers = {"Connection": "Keep-Alive"} + req_header = "Authorization" + resp_header = "www-authenticate" conn = HTTPSConnection(host=self.host, port=self.port) # Send negotiation message - headers[req_header] = ( - 'NTLM %s' % ntlm.create_NTLM_NEGOTIATE_MESSAGE(self.rawuser)) - log.debug('Request headers: %s', headers) - conn.request('GET', self.authurl, None, headers) + headers[req_header] = "NTLM %s" % ntlm.create_NTLM_NEGOTIATE_MESSAGE( + self.rawuser + ) + log.debug("Request headers: %s", headers) + conn.request("GET", self.authurl, None, headers) res = conn.getresponse() reshdr = dict(res.getheaders()) - log.debug('Response status: %s %s', res.status, res.reason) - log.debug('Response headers: %s', reshdr) - log.debug('Response data: %s [...]', res.read(100)) + log.debug("Response status: %s %s", res.status, res.reason) + log.debug("Response headers: %s", reshdr) + log.debug("Response data: %s [...]", res.read(100)) # Remove the reference to the socket, so that it can not be closed by # the response object (we want to keep the socket open) res.fp = None # Server should respond with a challenge message - auth_header_values = reshdr[resp_header].split(', ') + auth_header_values = reshdr[resp_header].split(", ") auth_header_value = None for s in auth_header_values: - if s[:5] == 'NTLM ': + if s[:5] == "NTLM ": auth_header_value = s[5:] if auth_header_value is None: - raise Exception('Unexpected %s response header: %s' % - (resp_header, reshdr[resp_header])) + raise Exception( + "Unexpected %s response header: %s" % (resp_header, reshdr[resp_header]) + ) # Send authentication message - ServerChallenge, NegotiateFlags = \ - ntlm.parse_NTLM_CHALLENGE_MESSAGE(auth_header_value) - auth_msg = ntlm.create_NTLM_AUTHENTICATE_MESSAGE(ServerChallenge, - self.user, - self.domain, - self.pw, - NegotiateFlags) - headers[req_header] = 'NTLM %s' % auth_msg - log.debug('Request headers: %s', headers) - conn.request('GET', self.authurl, None, headers) + ServerChallenge, NegotiateFlags = ntlm.parse_NTLM_CHALLENGE_MESSAGE( + auth_header_value + ) + auth_msg = ntlm.create_NTLM_AUTHENTICATE_MESSAGE( + ServerChallenge, self.user, self.domain, self.pw, NegotiateFlags + ) + headers[req_header] = "NTLM %s" % auth_msg + log.debug("Request headers: %s", headers) + conn.request("GET", self.authurl, None, headers) res = conn.getresponse() - log.debug('Response status: %s %s', res.status, res.reason) - log.debug('Response headers: %s', dict(res.getheaders())) - log.debug('Response data: %s [...]', res.read()[:100]) + log.debug("Response status: %s %s", res.status, res.reason) + log.debug("Response headers: %s", dict(res.getheaders())) + log.debug("Response data: %s [...]", res.read()[:100]) if res.status != 200: if res.status == 401: - raise Exception('Server rejected request: wrong ' - 'username or password') - raise Exception('Wrong server response: %s %s' % - (res.status, res.reason)) + raise Exception("Server rejected request: wrong username or password") + raise Exception("Wrong server response: %s %s" % (res.status, res.reason)) res.fp = None - log.debug('Connection established') + log.debug("Connection established") return conn - def urlopen(self, method, url, body=None, headers=None, retries=3, - redirect=True, assert_same_host=True): + def urlopen( + self, + method, + url, + body=None, + headers=None, + retries=3, + redirect=True, + assert_same_host=True, + ): if headers is None: headers = {} - headers['Connection'] = 'Keep-Alive' - return super(NTLMConnectionPool, self).urlopen(method, url, body, - headers, retries, - redirect, - assert_same_host) + headers["Connection"] = "Keep-Alive" + return super(NTLMConnectionPool, self).urlopen( + method, url, body, headers, retries, redirect, assert_same_host + ) diff --git a/pipenv/vendor/urllib3/contrib/pyopenssl.py b/pipenv/vendor/urllib3/contrib/pyopenssl.py index 821c174f..62c8d91b 100644 --- a/pipenv/vendor/urllib3/contrib/pyopenssl.py +++ b/pipenv/vendor/urllib3/contrib/pyopenssl.py @@ -1,27 +1,31 @@ """ -SSL with SNI_-support for Python 2. Follow these instructions if you would -like to verify SSL certificates in Python 2. Note, the default libraries do +TLS with SNI_-support for Python 2. Follow these instructions if you would +like to verify TLS certificates in Python 2. Note, the default libraries do *not* do certificate checking; you need to do additional work to validate certificates yourself. This needs the following packages installed: -* pyOpenSSL (tested with 16.0.0) -* cryptography (minimum 1.3.4, from pyopenssl) -* idna (minimum 2.0, from cryptography) +* `pyOpenSSL`_ (tested with 16.0.0) +* `cryptography`_ (minimum 1.3.4, from pyopenssl) +* `idna`_ (minimum 2.0, from cryptography) However, pyopenssl depends on cryptography, which depends on idna, so while we use all three directly here we end up having relatively few packages required. You can install them with the following command: - pip install pyopenssl cryptography idna +.. code-block:: bash + + $ python -m pip install pyopenssl cryptography idna To activate certificate checking, call :func:`~urllib3.contrib.pyopenssl.inject_into_urllib3` from your Python code before you begin making HTTP requests. This can be done in a ``sitecustomize`` module, or at any other time before your application begins using ``urllib3``, -like this:: +like this: + +.. code-block:: python try: import urllib3.contrib.pyopenssl @@ -35,11 +39,11 @@ when the required modules are installed. Activating this module also has the positive side effect of disabling SSL/TLS compression in Python 2 (see `CRIME attack`_). -If you want to configure the default list of supported cipher suites, you can -set the ``urllib3.contrib.pyopenssl.DEFAULT_SSL_CIPHER_LIST`` variable. - .. _sni: https://en.wikipedia.org/wiki/Server_Name_Indication .. _crime attack: https://en.wikipedia.org/wiki/CRIME_(security_exploit) +.. _pyopenssl: https://www.pyopenssl.org +.. _cryptography: https://cryptography.io +.. _idna: https://github.com/kjd/idna """ from __future__ import absolute_import @@ -47,6 +51,7 @@ import OpenSSL.SSL from cryptography import x509 from cryptography.hazmat.backends.openssl import backend as openssl_backend from cryptography.hazmat.backends.openssl.x509 import _Certificate + try: from cryptography.x509 import UnsupportedExtension except ImportError: @@ -54,8 +59,10 @@ except ImportError: class UnsupportedExtension(Exception): pass -from socket import timeout, error as SocketError + from io import BytesIO +from socket import error as SocketError +from socket import timeout try: # Platform-specific: Python 2 from socket import _fileobject @@ -65,13 +72,13 @@ except ImportError: # Platform-specific: Python 3 import logging import ssl -from ..packages import six import sys from .. import util +from ..packages import six +from ..util.ssl_ import PROTOCOL_TLS_CLIENT - -__all__ = ['inject_into_urllib3', 'extract_from_urllib3'] +__all__ = ["inject_into_urllib3", "extract_from_urllib3"] # SNI always works. HAS_SNI = True @@ -79,28 +86,27 @@ HAS_SNI = True # Map from urllib3 to PyOpenSSL compatible parameter-values. _openssl_versions = { util.PROTOCOL_TLS: OpenSSL.SSL.SSLv23_METHOD, + PROTOCOL_TLS_CLIENT: OpenSSL.SSL.SSLv23_METHOD, ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD, } -if hasattr(ssl, 'PROTOCOL_SSLv3') and hasattr(OpenSSL.SSL, 'SSLv3_METHOD'): +if hasattr(ssl, "PROTOCOL_SSLv3") and hasattr(OpenSSL.SSL, "SSLv3_METHOD"): _openssl_versions[ssl.PROTOCOL_SSLv3] = OpenSSL.SSL.SSLv3_METHOD -if hasattr(ssl, 'PROTOCOL_TLSv1_1') and hasattr(OpenSSL.SSL, 'TLSv1_1_METHOD'): +if hasattr(ssl, "PROTOCOL_TLSv1_1") and hasattr(OpenSSL.SSL, "TLSv1_1_METHOD"): _openssl_versions[ssl.PROTOCOL_TLSv1_1] = OpenSSL.SSL.TLSv1_1_METHOD -if hasattr(ssl, 'PROTOCOL_TLSv1_2') and hasattr(OpenSSL.SSL, 'TLSv1_2_METHOD'): +if hasattr(ssl, "PROTOCOL_TLSv1_2") and hasattr(OpenSSL.SSL, "TLSv1_2_METHOD"): _openssl_versions[ssl.PROTOCOL_TLSv1_2] = OpenSSL.SSL.TLSv1_2_METHOD _stdlib_to_openssl_verify = { ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE, ssl.CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER, - ssl.CERT_REQUIRED: - OpenSSL.SSL.VERIFY_PEER + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT, + ssl.CERT_REQUIRED: OpenSSL.SSL.VERIFY_PEER + + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT, } -_openssl_to_stdlib_verify = dict( - (v, k) for k, v in _stdlib_to_openssl_verify.items() -) +_openssl_to_stdlib_verify = dict((v, k) for k, v in _stdlib_to_openssl_verify.items()) # OpenSSL will only write 16K at a time SSL_WRITE_BLOCKSIZE = 16384 @@ -113,7 +119,7 @@ log = logging.getLogger(__name__) def inject_into_urllib3(): - 'Monkey-patch urllib3 with PyOpenSSL-backed SSL-support.' + "Monkey-patch urllib3 with PyOpenSSL-backed SSL-support." _validate_dependencies_met() @@ -126,7 +132,7 @@ def inject_into_urllib3(): def extract_from_urllib3(): - 'Undo monkey-patching by :func:`inject_into_urllib3`.' + "Undo monkey-patching by :func:`inject_into_urllib3`." util.SSLContext = orig_util_SSLContext util.ssl_.SSLContext = orig_util_SSLContext @@ -143,17 +149,23 @@ def _validate_dependencies_met(): """ # Method added in `cryptography==1.1`; not available in older versions from cryptography.x509.extensions import Extensions + if getattr(Extensions, "get_extension_for_class", None) is None: - raise ImportError("'cryptography' module missing required functionality. " - "Try upgrading to v1.3.4 or newer.") + raise ImportError( + "'cryptography' module missing required functionality. " + "Try upgrading to v1.3.4 or newer." + ) # pyOpenSSL 0.14 and above use cryptography for OpenSSL bindings. The _x509 # attribute is only present on those versions. from OpenSSL.crypto import X509 + x509 = X509() if getattr(x509, "_x509", None) is None: - raise ImportError("'pyOpenSSL' module missing required functionality. " - "Try upgrading to v0.14 or newer.") + raise ImportError( + "'pyOpenSSL' module missing required functionality. " + "Try upgrading to v0.14 or newer." + ) def _dnsname_to_stdlib(name): @@ -169,32 +181,33 @@ def _dnsname_to_stdlib(name): If the name cannot be idna-encoded then we return None signalling that the name given should be skipped. """ + def idna_encode(name): """ Borrowed wholesale from the Python Cryptography Project. It turns out that we can't just safely call `idna.encode`: it can explode for wildcard names. This avoids that problem. """ - import idna + import pipenv.vendor.idna as idna try: - for prefix in [u'*.', u'.']: + for prefix in [u"*.", u"."]: if name.startswith(prefix): - name = name[len(prefix):] - return prefix.encode('ascii') + idna.encode(name) + name = name[len(prefix) :] + return prefix.encode("ascii") + idna.encode(name) return idna.encode(name) except idna.core.IDNAError: return None # Don't send IPv6 addresses through the IDNA encoder. - if ':' in name: + if ":" in name: return name name = idna_encode(name) if name is None: return None elif sys.version_info >= (3, 0): - name = name.decode('utf-8') + name = name.decode("utf-8") return name @@ -213,14 +226,16 @@ def get_subj_alt_name(peer_cert): # We want to find the SAN extension. Ask Cryptography to locate it (it's # faster than looping in Python) try: - ext = cert.extensions.get_extension_for_class( - x509.SubjectAlternativeName - ).value + ext = cert.extensions.get_extension_for_class(x509.SubjectAlternativeName).value except x509.ExtensionNotFound: # No such extension, return the empty list. return [] - except (x509.DuplicateExtension, UnsupportedExtension, - x509.UnsupportedGeneralNameType, UnicodeError) as e: + except ( + x509.DuplicateExtension, + UnsupportedExtension, + x509.UnsupportedGeneralNameType, + UnicodeError, + ) as e: # A problem has been found with the quality of the certificate. Assume # no SAN field is present. log.warning( @@ -239,23 +254,23 @@ def get_subj_alt_name(peer_cert): # does with certificates, and so we need to attempt to do the same. # We also want to skip over names which cannot be idna encoded. names = [ - ('DNS', name) for name in map(_dnsname_to_stdlib, ext.get_values_for_type(x509.DNSName)) + ("DNS", name) + for name in map(_dnsname_to_stdlib, ext.get_values_for_type(x509.DNSName)) if name is not None ] names.extend( - ('IP Address', str(name)) - for name in ext.get_values_for_type(x509.IPAddress) + ("IP Address", str(name)) for name in ext.get_values_for_type(x509.IPAddress) ) return names class WrappedSocket(object): - '''API-compatibility wrapper for Python OpenSSL's Connection-class. + """API-compatibility wrapper for Python OpenSSL's Connection-class. Note: _makefile_refs, _drop() and _reuse() are needed for the garbage collector of pypy. - ''' + """ def __init__(self, connection, socket, suppress_ragged_eofs=True): self.connection = connection @@ -278,18 +293,18 @@ class WrappedSocket(object): try: data = self.connection.recv(*args, **kwargs) except OpenSSL.SSL.SysCallError as e: - if self.suppress_ragged_eofs and e.args == (-1, 'Unexpected EOF'): - return b'' + if self.suppress_ragged_eofs and e.args == (-1, "Unexpected EOF"): + return b"" else: raise SocketError(str(e)) except OpenSSL.SSL.ZeroReturnError: if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: - return b'' + return b"" else: raise except OpenSSL.SSL.WantReadError: if not util.wait_for_read(self.socket, self.socket.gettimeout()): - raise timeout('The read operation timed out') + raise timeout("The read operation timed out") else: return self.recv(*args, **kwargs) @@ -303,7 +318,7 @@ class WrappedSocket(object): try: return self.connection.recv_into(*args, **kwargs) except OpenSSL.SSL.SysCallError as e: - if self.suppress_ragged_eofs and e.args == (-1, 'Unexpected EOF'): + if self.suppress_ragged_eofs and e.args == (-1, "Unexpected EOF"): return 0 else: raise SocketError(str(e)) @@ -314,7 +329,7 @@ class WrappedSocket(object): raise except OpenSSL.SSL.WantReadError: if not util.wait_for_read(self.socket, self.socket.gettimeout()): - raise timeout('The read operation timed out') + raise timeout("The read operation timed out") else: return self.recv_into(*args, **kwargs) @@ -339,7 +354,9 @@ class WrappedSocket(object): def sendall(self, data): total_sent = 0 while total_sent < len(data): - sent = self._send_until_done(data[total_sent:total_sent + SSL_WRITE_BLOCKSIZE]) + sent = self._send_until_done( + data[total_sent : total_sent + SSL_WRITE_BLOCKSIZE] + ) total_sent += sent def shutdown(self): @@ -363,15 +380,11 @@ class WrappedSocket(object): return x509 if binary_form: - return OpenSSL.crypto.dump_certificate( - OpenSSL.crypto.FILETYPE_ASN1, - x509) + return OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_ASN1, x509) return { - 'subject': ( - (('commonName', x509.get_subject().CN),), - ), - 'subjectAltName': get_subj_alt_name(x509) + "subject": ((("commonName", x509.get_subject().CN),),), + "subjectAltName": get_subj_alt_name(x509), } def version(self): @@ -388,9 +401,12 @@ class WrappedSocket(object): if _fileobject: # Platform-specific: Python 2 + def makefile(self, mode, bufsize=-1): self._makefile_refs += 1 return _fileobject(self, mode, bufsize, close=True) + + else: # Platform-specific: Python 3 makefile = backport_makefile @@ -403,6 +419,7 @@ class PyOpenSSLContext(object): for translating the interface of the standard library ``SSLContext`` object to calls into PyOpenSSL. """ + def __init__(self, protocol): self.protocol = _openssl_versions[protocol] self._ctx = OpenSSL.SSL.Context(self.protocol) @@ -424,43 +441,52 @@ class PyOpenSSLContext(object): @verify_mode.setter def verify_mode(self, value): - self._ctx.set_verify( - _stdlib_to_openssl_verify[value], - _verify_callback - ) + self._ctx.set_verify(_stdlib_to_openssl_verify[value], _verify_callback) def set_default_verify_paths(self): self._ctx.set_default_verify_paths() def set_ciphers(self, ciphers): if isinstance(ciphers, six.text_type): - ciphers = ciphers.encode('utf-8') + ciphers = ciphers.encode("utf-8") self._ctx.set_cipher_list(ciphers) def load_verify_locations(self, cafile=None, capath=None, cadata=None): if cafile is not None: - cafile = cafile.encode('utf-8') + cafile = cafile.encode("utf-8") if capath is not None: - capath = capath.encode('utf-8') - self._ctx.load_verify_locations(cafile, capath) - if cadata is not None: - self._ctx.load_verify_locations(BytesIO(cadata)) + capath = capath.encode("utf-8") + try: + self._ctx.load_verify_locations(cafile, capath) + if cadata is not None: + self._ctx.load_verify_locations(BytesIO(cadata)) + except OpenSSL.SSL.Error as e: + raise ssl.SSLError("unable to load trusted certificates: %r" % e) def load_cert_chain(self, certfile, keyfile=None, password=None): self._ctx.use_certificate_chain_file(certfile) if password is not None: if not isinstance(password, six.binary_type): - password = password.encode('utf-8') + password = password.encode("utf-8") self._ctx.set_passwd_cb(lambda *_: password) self._ctx.use_privatekey_file(keyfile or certfile) - def wrap_socket(self, sock, server_side=False, - do_handshake_on_connect=True, suppress_ragged_eofs=True, - server_hostname=None): + def set_alpn_protocols(self, protocols): + protocols = [six.ensure_binary(p) for p in protocols] + return self._ctx.set_alpn_protos(protocols) + + def wrap_socket( + self, + sock, + server_side=False, + do_handshake_on_connect=True, + suppress_ragged_eofs=True, + server_hostname=None, + ): cnx = OpenSSL.SSL.Connection(self._ctx, sock) if isinstance(server_hostname, six.text_type): # Platform-specific: Python 3 - server_hostname = server_hostname.encode('utf-8') + server_hostname = server_hostname.encode("utf-8") if server_hostname is not None: cnx.set_tlsext_host_name(server_hostname) @@ -472,10 +498,10 @@ class PyOpenSSLContext(object): cnx.do_handshake() except OpenSSL.SSL.WantReadError: if not util.wait_for_read(sock, sock.gettimeout()): - raise timeout('select timed out') + raise timeout("select timed out") continue except OpenSSL.SSL.Error as e: - raise ssl.SSLError('bad handshake: %r' % e) + raise ssl.SSLError("bad handshake: %r" % e) break return WrappedSocket(cnx, sock) diff --git a/pipenv/vendor/urllib3/contrib/securetransport.py b/pipenv/vendor/urllib3/contrib/securetransport.py index 4dc48484..aaa32e6b 100644 --- a/pipenv/vendor/urllib3/contrib/securetransport.py +++ b/pipenv/vendor/urllib3/contrib/securetransport.py @@ -29,6 +29,8 @@ library. An enormous debt is owed to him for blazing this trail for us. For that reason, this code should be considered to be covered both by urllib3's license and by oscrypto's: +.. code-block:: + Copyright (c) 2015-2016 Will Bond <will@wbond.net> Permission is hereby granted, free of charge, to any person obtaining a @@ -58,16 +60,22 @@ import os.path import shutil import socket import ssl +import struct import threading import weakref +import pipenv.vendor.six as six + from .. import util -from ._securetransport.bindings import ( - Security, SecurityConst, CoreFoundation -) +from ..util.ssl_ import PROTOCOL_TLS_CLIENT +from ._securetransport.bindings import CoreFoundation, Security, SecurityConst from ._securetransport.low_level import ( - _assert_no_error, _cert_array_from_pem, _temporary_keychain, - _load_client_cert_chain + _assert_no_error, + _build_tls_unknown_ca_alert, + _cert_array_from_pem, + _create_cfstring_array, + _load_client_cert_chain, + _temporary_keychain, ) try: # Platform-specific: Python 2 @@ -76,7 +84,7 @@ except ImportError: # Platform-specific: Python 3 _fileobject = None from ..packages.backports.makefile import backport_makefile -__all__ = ['inject_into_urllib3', 'extract_from_urllib3'] +__all__ = ["inject_into_urllib3", "extract_from_urllib3"] # SNI always works HAS_SNI = True @@ -144,31 +152,37 @@ CIPHER_SUITES = [ ] # Basically this is simple: for PROTOCOL_SSLv23 we turn it into a low of -# TLSv1 and a high of TLSv1.3. For everything else, we pin to that version. -# TLSv1 to 1.2 are supported on macOS 10.8+ and TLSv1.3 is macOS 10.13+ +# TLSv1 and a high of TLSv1.2. For everything else, we pin to that version. +# TLSv1 to 1.2 are supported on macOS 10.8+ _protocol_to_min_max = { - util.PROTOCOL_TLS: (SecurityConst.kTLSProtocol1, SecurityConst.kTLSProtocolMaxSupported), + util.PROTOCOL_TLS: (SecurityConst.kTLSProtocol1, SecurityConst.kTLSProtocol12), + PROTOCOL_TLS_CLIENT: (SecurityConst.kTLSProtocol1, SecurityConst.kTLSProtocol12), } if hasattr(ssl, "PROTOCOL_SSLv2"): _protocol_to_min_max[ssl.PROTOCOL_SSLv2] = ( - SecurityConst.kSSLProtocol2, SecurityConst.kSSLProtocol2 + SecurityConst.kSSLProtocol2, + SecurityConst.kSSLProtocol2, ) if hasattr(ssl, "PROTOCOL_SSLv3"): _protocol_to_min_max[ssl.PROTOCOL_SSLv3] = ( - SecurityConst.kSSLProtocol3, SecurityConst.kSSLProtocol3 + SecurityConst.kSSLProtocol3, + SecurityConst.kSSLProtocol3, ) if hasattr(ssl, "PROTOCOL_TLSv1"): _protocol_to_min_max[ssl.PROTOCOL_TLSv1] = ( - SecurityConst.kTLSProtocol1, SecurityConst.kTLSProtocol1 + SecurityConst.kTLSProtocol1, + SecurityConst.kTLSProtocol1, ) if hasattr(ssl, "PROTOCOL_TLSv1_1"): _protocol_to_min_max[ssl.PROTOCOL_TLSv1_1] = ( - SecurityConst.kTLSProtocol11, SecurityConst.kTLSProtocol11 + SecurityConst.kTLSProtocol11, + SecurityConst.kTLSProtocol11, ) if hasattr(ssl, "PROTOCOL_TLSv1_2"): _protocol_to_min_max[ssl.PROTOCOL_TLSv1_2] = ( - SecurityConst.kTLSProtocol12, SecurityConst.kTLSProtocol12 + SecurityConst.kTLSProtocol12, + SecurityConst.kTLSProtocol12, ) @@ -218,7 +232,7 @@ def _read_callback(connection_id, data_buffer, data_length_pointer): while read_count < requested_length: if timeout is None or timeout >= 0: if not util.wait_for_read(base_socket, timeout): - raise socket.error(errno.EAGAIN, 'timed out') + raise socket.error(errno.EAGAIN, "timed out") remaining = requested_length - read_count buffer = (ctypes.c_char * remaining).from_address( @@ -274,7 +288,7 @@ def _write_callback(connection_id, data_buffer, data_length_pointer): while sent < bytes_to_write: if timeout is None or timeout >= 0: if not util.wait_for_write(base_socket, timeout): - raise socket.error(errno.EAGAIN, 'timed out') + raise socket.error(errno.EAGAIN, "timed out") chunk_sent = base_socket.send(data) sent += chunk_sent @@ -316,6 +330,7 @@ class WrappedSocket(object): Note: _makefile_refs, _drop(), and _reuse() are needed for the garbage collector of PyPy. """ + def __init__(self, socket): self.socket = socket self.context = None @@ -368,19 +383,58 @@ class WrappedSocket(object): ) _assert_no_error(result) + def _set_alpn_protocols(self, protocols): + """ + Sets up the ALPN protocols on the context. + """ + if not protocols: + return + protocols_arr = _create_cfstring_array(protocols) + try: + result = Security.SSLSetALPNProtocols(self.context, protocols_arr) + _assert_no_error(result) + finally: + CoreFoundation.CFRelease(protocols_arr) + def _custom_validate(self, verify, trust_bundle): """ Called when we have set custom validation. We do this in two cases: first, when cert validation is entirely disabled; and second, when using a custom trust DB. + Raises an SSLError if the connection is not trusted. """ # If we disabled cert validation, just say: cool. if not verify: return + successes = ( + SecurityConst.kSecTrustResultUnspecified, + SecurityConst.kSecTrustResultProceed, + ) + try: + trust_result = self._evaluate_trust(trust_bundle) + if trust_result in successes: + return + reason = "error code: %d" % (trust_result,) + except Exception as e: + # Do not trust on error + reason = "exception: %r" % (e,) + + # SecureTransport does not send an alert nor shuts down the connection. + rec = _build_tls_unknown_ca_alert(self.version()) + self.socket.sendall(rec) + # close the connection immediately + # l_onoff = 1, activate linger + # l_linger = 0, linger for 0 seoncds + opts = struct.pack("ii", 1, 0) + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, opts) + self.close() + raise ssl.SSLError("certificate verify failed, %s" % reason) + + def _evaluate_trust(self, trust_bundle): # We want data in memory, so load it up. if os.path.isfile(trust_bundle): - with open(trust_bundle, 'rb') as f: + with open(trust_bundle, "rb") as f: trust_bundle = f.read() cert_array = None @@ -394,9 +448,7 @@ class WrappedSocket(object): # created for this connection, shove our CAs into it, tell ST to # ignore everything else it knows, and then ask if it can build a # chain. This is a buuuunch of code. - result = Security.SSLCopyPeerTrust( - self.context, ctypes.byref(trust) - ) + result = Security.SSLCopyPeerTrust(self.context, ctypes.byref(trust)) _assert_no_error(result) if not trust: raise ssl.SSLError("Failed to copy trust reference") @@ -408,9 +460,7 @@ class WrappedSocket(object): _assert_no_error(result) trust_result = Security.SecTrustResultType() - result = Security.SecTrustEvaluate( - trust, ctypes.byref(trust_result) - ) + result = Security.SecTrustEvaluate(trust, ctypes.byref(trust_result)) _assert_no_error(result) finally: if trust: @@ -419,26 +469,20 @@ class WrappedSocket(object): if cert_array is not None: CoreFoundation.CFRelease(cert_array) - # Ok, now we can look at what the result was. - successes = ( - SecurityConst.kSecTrustResultUnspecified, - SecurityConst.kSecTrustResultProceed - ) - if trust_result.value not in successes: - raise ssl.SSLError( - "certificate verify failed, error code: %d" % - trust_result.value - ) + return trust_result.value - def handshake(self, - server_hostname, - verify, - trust_bundle, - min_version, - max_version, - client_cert, - client_key, - client_key_passphrase): + def handshake( + self, + server_hostname, + verify, + trust_bundle, + min_version, + max_version, + client_cert, + client_key, + client_key_passphrase, + alpn_protocols, + ): """ Actually performs the TLS handshake. This is run automatically by wrapped socket, and shouldn't be needed in user code. @@ -468,7 +512,7 @@ class WrappedSocket(object): # If we have a server hostname, we should set that too. if server_hostname: if not isinstance(server_hostname, bytes): - server_hostname = server_hostname.encode('utf-8') + server_hostname = server_hostname.encode("utf-8") result = Security.SSLSetPeerDomainName( self.context, server_hostname, len(server_hostname) @@ -478,17 +522,14 @@ class WrappedSocket(object): # Setup the ciphers. self._set_ciphers() + # Setup the ALPN protocols. + self._set_alpn_protocols(alpn_protocols) + # Set the minimum and maximum TLS versions. result = Security.SSLSetProtocolVersionMin(self.context, min_version) _assert_no_error(result) - # TLS 1.3 isn't necessarily enabled by the OS - # so we have to detect when we error out and try - # setting TLS 1.3 if it's allowed. kTLSProtocolMaxSupported - # was added in macOS 10.13 along with kTLSProtocol13. result = Security.SSLSetProtocolVersionMax(self.context, max_version) - if result != 0 and max_version == SecurityConst.kTLSProtocolMaxSupported: - result = Security.SSLSetProtocolVersionMax(self.context, SecurityConst.kTLSProtocol12) _assert_no_error(result) # If there's a trust DB, we need to use it. We do that by telling @@ -497,9 +538,7 @@ class WrappedSocket(object): # authing in that case. if not verify or trust_bundle is not None: result = Security.SSLSetSessionOption( - self.context, - SecurityConst.kSSLSessionOptionBreakOnServerAuth, - True + self.context, SecurityConst.kSSLSessionOptionBreakOnServerAuth, True ) _assert_no_error(result) @@ -509,9 +548,7 @@ class WrappedSocket(object): self._client_cert_chain = _load_client_cert_chain( self._keychain, client_cert, client_key ) - result = Security.SSLSetCertificate( - self.context, self._client_cert_chain - ) + result = Security.SSLSetCertificate(self.context, self._client_cert_chain) _assert_no_error(result) while True: @@ -562,7 +599,7 @@ class WrappedSocket(object): # There are some result codes that we want to treat as "not always # errors". Specifically, those are errSSLWouldBlock, # errSSLClosedGraceful, and errSSLClosedNoNotify. - if (result == SecurityConst.errSSLWouldBlock): + if result == SecurityConst.errSSLWouldBlock: # If we didn't process any bytes, then this was just a time out. # However, we can get errSSLWouldBlock in situations when we *did* # read some data, and in those cases we should just read "short" @@ -570,7 +607,10 @@ class WrappedSocket(object): if processed_bytes.value == 0: # Timed out, no data read. raise socket.timeout("recv timed out") - elif result in (SecurityConst.errSSLClosedGraceful, SecurityConst.errSSLClosedNoNotify): + elif result in ( + SecurityConst.errSSLClosedGraceful, + SecurityConst.errSSLClosedNoNotify, + ): # The remote peer has closed this connection. We should do so as # well. Note that we don't actually return here because in # principle this could actually be fired along with return data. @@ -609,7 +649,7 @@ class WrappedSocket(object): def sendall(self, data): total_sent = 0 while total_sent < len(data): - sent = self.send(data[total_sent:total_sent + SSL_WRITE_BLOCKSIZE]) + sent = self.send(data[total_sent : total_sent + SSL_WRITE_BLOCKSIZE]) total_sent += sent def shutdown(self): @@ -656,18 +696,14 @@ class WrappedSocket(object): # instead to just flag to urllib3 that it shouldn't do its own hostname # validation when using SecureTransport. if not binary_form: - raise ValueError( - "SecureTransport only supports dumping binary certs" - ) + raise ValueError("SecureTransport only supports dumping binary certs") trust = Security.SecTrustRef() certdata = None der_bytes = None try: # Grab the trust store. - result = Security.SSLCopyPeerTrust( - self.context, ctypes.byref(trust) - ) + result = Security.SSLCopyPeerTrust(self.context, ctypes.byref(trust)) _assert_no_error(result) if not trust: # Probably we haven't done the handshake yet. No biggie. @@ -699,22 +735,24 @@ class WrappedSocket(object): def version(self): protocol = Security.SSLProtocol() - result = Security.SSLGetNegotiatedProtocolVersion(self.context, ctypes.byref(protocol)) + result = Security.SSLGetNegotiatedProtocolVersion( + self.context, ctypes.byref(protocol) + ) _assert_no_error(result) if protocol.value == SecurityConst.kTLSProtocol13: - return 'TLSv1.3' + raise ssl.SSLError("SecureTransport does not support TLS 1.3") elif protocol.value == SecurityConst.kTLSProtocol12: - return 'TLSv1.2' + return "TLSv1.2" elif protocol.value == SecurityConst.kTLSProtocol11: - return 'TLSv1.1' + return "TLSv1.1" elif protocol.value == SecurityConst.kTLSProtocol1: - return 'TLSv1' + return "TLSv1" elif protocol.value == SecurityConst.kSSLProtocol3: - return 'SSLv3' + return "SSLv3" elif protocol.value == SecurityConst.kSSLProtocol2: - return 'SSLv2' + return "SSLv2" else: - raise ssl.SSLError('Unknown TLS version: %r' % protocol) + raise ssl.SSLError("Unknown TLS version: %r" % protocol) def _reuse(self): self._makefile_refs += 1 @@ -727,16 +765,21 @@ class WrappedSocket(object): if _fileobject: # Platform-specific: Python 2 + def makefile(self, mode, bufsize=-1): self._makefile_refs += 1 return _fileobject(self, mode, bufsize, close=True) + + else: # Platform-specific: Python 3 + def makefile(self, mode="r", buffering=None, *args, **kwargs): # We disable buffering with SecureTransport because it conflicts with # the buffering that ST does internally (see issue #1153 for more). buffering = 0 return backport_makefile(self, mode, buffering, *args, **kwargs) + WrappedSocket.makefile = makefile @@ -746,6 +789,7 @@ class SecureTransportContext(object): interface of the standard library ``SSLContext`` object to calls into SecureTransport. """ + def __init__(self, protocol): self._min_version, self._max_version = _protocol_to_min_max[protocol] self._options = 0 @@ -754,6 +798,7 @@ class SecureTransportContext(object): self._client_cert = None self._client_key = None self._client_key_passphrase = None + self._alpn_protocols = None @property def check_hostname(self): @@ -812,16 +857,17 @@ class SecureTransportContext(object): def set_ciphers(self, ciphers): # For now, we just require the default cipher string. if ciphers != util.ssl_.DEFAULT_CIPHERS: - raise ValueError( - "SecureTransport doesn't support custom cipher strings" - ) + raise ValueError("SecureTransport doesn't support custom cipher strings") def load_verify_locations(self, cafile=None, capath=None, cadata=None): # OK, we only really support cadata and cafile. if capath is not None: - raise ValueError( - "SecureTransport does not support cert directories" - ) + raise ValueError("SecureTransport does not support cert directories") + + # Raise if cafile does not exist. + if cafile is not None: + with open(cafile): + pass self._trust_bundle = cafile or cadata @@ -830,9 +876,26 @@ class SecureTransportContext(object): self._client_key = keyfile self._client_cert_passphrase = password - def wrap_socket(self, sock, server_side=False, - do_handshake_on_connect=True, suppress_ragged_eofs=True, - server_hostname=None): + def set_alpn_protocols(self, protocols): + """ + Sets the ALPN protocols that will later be set on the context. + + Raises a NotImplementedError if ALPN is not supported. + """ + if not hasattr(Security, "SSLSetALPNProtocols"): + raise NotImplementedError( + "SecureTransport supports ALPN only in macOS 10.12+" + ) + self._alpn_protocols = [six.ensure_binary(p) for p in protocols] + + def wrap_socket( + self, + sock, + server_side=False, + do_handshake_on_connect=True, + suppress_ragged_eofs=True, + server_hostname=None, + ): # So, what do we do here? Firstly, we assert some properties. This is a # stripped down shim, so there is some functionality we don't support. # See PEP 543 for the real deal. @@ -846,8 +909,14 @@ class SecureTransportContext(object): # Now we can handshake wrapped_socket.handshake( - server_hostname, self._verify, self._trust_bundle, - self._min_version, self._max_version, self._client_cert, - self._client_key, self._client_key_passphrase + server_hostname, + self._verify, + self._trust_bundle, + self._min_version, + self._max_version, + self._client_cert, + self._client_key, + self._client_key_passphrase, + self._alpn_protocols, ) return wrapped_socket diff --git a/pipenv/vendor/urllib3/contrib/socks.py b/pipenv/vendor/urllib3/contrib/socks.py index 636d261f..c326e80d 100644 --- a/pipenv/vendor/urllib3/contrib/socks.py +++ b/pipenv/vendor/urllib3/contrib/socks.py @@ -14,22 +14,26 @@ supports the following SOCKS features: - SOCKS5 with local DNS (``proxy_url='socks5://...``) - Usernames and passwords for the SOCKS proxy - .. note:: - It is recommended to use ``socks5h://`` or ``socks4a://`` schemes in - your ``proxy_url`` to ensure that DNS resolution is done from the remote - server instead of client-side when connecting to a domain name. +.. note:: + It is recommended to use ``socks5h://`` or ``socks4a://`` schemes in + your ``proxy_url`` to ensure that DNS resolution is done from the remote + server instead of client-side when connecting to a domain name. SOCKS4 supports IPv4 and domain names with the SOCKS4A extension. SOCKS5 supports IPv4, IPv6, and domain names. When connecting to a SOCKS4 proxy the ``username`` portion of the ``proxy_url`` -will be sent as the ``userid`` section of the SOCKS request:: +will be sent as the ``userid`` section of the SOCKS request: + +.. code-block:: python proxy_url="socks4a://<userid>@proxy-host" When connecting to a SOCKS5 proxy the ``username`` and ``password`` portion of the ``proxy_url`` will be sent as the username/password to authenticate -with the proxy:: +with the proxy: + +.. code-block:: python proxy_url="socks5h://<username>:<password>@proxy-host" @@ -40,25 +44,24 @@ try: import socks except ImportError: import warnings + from ..exceptions import DependencyWarning - warnings.warn(( - 'SOCKS support in urllib3 requires the installation of optional ' - 'dependencies: specifically, PySocks. For more information, see ' - 'https://urllib3.readthedocs.io/en/latest/contrib.html#socks-proxies' + warnings.warn( + ( + "SOCKS support in urllib3 requires the installation of optional " + "dependencies: specifically, PySocks. For more information, see " + "https://urllib3.readthedocs.io/en/1.26.x/contrib.html#socks-proxies" ), - DependencyWarning + DependencyWarning, ) raise -from socket import error as SocketError, timeout as SocketTimeout +from socket import error as SocketError +from socket import timeout as SocketTimeout -from ..connection import ( - HTTPConnection, HTTPSConnection -) -from ..connectionpool import ( - HTTPConnectionPool, HTTPSConnectionPool -) +from ..connection import HTTPConnection, HTTPSConnection +from ..connectionpool import HTTPConnectionPool, HTTPSConnectionPool from ..exceptions import ConnectTimeoutError, NewConnectionError from ..poolmanager import PoolManager from ..util.url import parse_url @@ -73,8 +76,9 @@ class SOCKSConnection(HTTPConnection): """ A plain-text HTTP connection that connects via a SOCKS proxy. """ + def __init__(self, *args, **kwargs): - self._socks_options = kwargs.pop('_socks_options') + self._socks_options = kwargs.pop("_socks_options") super(SOCKSConnection, self).__init__(*args, **kwargs) def _new_conn(self): @@ -83,28 +87,30 @@ class SOCKSConnection(HTTPConnection): """ extra_kw = {} if self.source_address: - extra_kw['source_address'] = self.source_address + extra_kw["source_address"] = self.source_address if self.socket_options: - extra_kw['socket_options'] = self.socket_options + extra_kw["socket_options"] = self.socket_options try: conn = socks.create_connection( (self.host, self.port), - proxy_type=self._socks_options['socks_version'], - proxy_addr=self._socks_options['proxy_host'], - proxy_port=self._socks_options['proxy_port'], - proxy_username=self._socks_options['username'], - proxy_password=self._socks_options['password'], - proxy_rdns=self._socks_options['rdns'], + proxy_type=self._socks_options["socks_version"], + proxy_addr=self._socks_options["proxy_host"], + proxy_port=self._socks_options["proxy_port"], + proxy_username=self._socks_options["username"], + proxy_password=self._socks_options["password"], + proxy_rdns=self._socks_options["rdns"], timeout=self.timeout, **extra_kw ) except SocketTimeout: raise ConnectTimeoutError( - self, "Connection to %s timed out. (connect timeout=%s)" % - (self.host, self.timeout)) + self, + "Connection to %s timed out. (connect timeout=%s)" + % (self.host, self.timeout), + ) except socks.ProxyError as e: # This is fragile as hell, but it seems to be the only way to raise @@ -114,23 +120,22 @@ class SOCKSConnection(HTTPConnection): if isinstance(error, SocketTimeout): raise ConnectTimeoutError( self, - "Connection to %s timed out. (connect timeout=%s)" % - (self.host, self.timeout) + "Connection to %s timed out. (connect timeout=%s)" + % (self.host, self.timeout), ) else: raise NewConnectionError( - self, - "Failed to establish a new connection: %s" % error + self, "Failed to establish a new connection: %s" % error ) else: raise NewConnectionError( - self, - "Failed to establish a new connection: %s" % e + self, "Failed to establish a new connection: %s" % e ) except SocketError as e: # Defensive: PySocks should catch all these. raise NewConnectionError( - self, "Failed to establish a new connection: %s" % e) + self, "Failed to establish a new connection: %s" % e + ) return conn @@ -156,47 +161,53 @@ class SOCKSProxyManager(PoolManager): A version of the urllib3 ProxyManager that routes connections via the defined SOCKS proxy. """ + pool_classes_by_scheme = { - 'http': SOCKSHTTPConnectionPool, - 'https': SOCKSHTTPSConnectionPool, + "http": SOCKSHTTPConnectionPool, + "https": SOCKSHTTPSConnectionPool, } - def __init__(self, proxy_url, username=None, password=None, - num_pools=10, headers=None, **connection_pool_kw): + def __init__( + self, + proxy_url, + username=None, + password=None, + num_pools=10, + headers=None, + **connection_pool_kw + ): parsed = parse_url(proxy_url) if username is None and password is None and parsed.auth is not None: - split = parsed.auth.split(':') + split = parsed.auth.split(":") if len(split) == 2: username, password = split - if parsed.scheme == 'socks5': + if parsed.scheme == "socks5": socks_version = socks.PROXY_TYPE_SOCKS5 rdns = False - elif parsed.scheme == 'socks5h': + elif parsed.scheme == "socks5h": socks_version = socks.PROXY_TYPE_SOCKS5 rdns = True - elif parsed.scheme == 'socks4': + elif parsed.scheme == "socks4": socks_version = socks.PROXY_TYPE_SOCKS4 rdns = False - elif parsed.scheme == 'socks4a': + elif parsed.scheme == "socks4a": socks_version = socks.PROXY_TYPE_SOCKS4 rdns = True else: - raise ValueError( - "Unable to determine SOCKS version from %s" % proxy_url - ) + raise ValueError("Unable to determine SOCKS version from %s" % proxy_url) self.proxy_url = proxy_url socks_options = { - 'socks_version': socks_version, - 'proxy_host': parsed.host, - 'proxy_port': parsed.port, - 'username': username, - 'password': password, - 'rdns': rdns + "socks_version": socks_version, + "proxy_host": parsed.host, + "proxy_port": parsed.port, + "username": username, + "password": password, + "rdns": rdns, } - connection_pool_kw['_socks_options'] = socks_options + connection_pool_kw["_socks_options"] = socks_options super(SOCKSProxyManager, self).__init__( num_pools, headers, **connection_pool_kw diff --git a/pipenv/vendor/urllib3/exceptions.py b/pipenv/vendor/urllib3/exceptions.py index 7bbaa987..cba6f3f5 100644 --- a/pipenv/vendor/urllib3/exceptions.py +++ b/pipenv/vendor/urllib3/exceptions.py @@ -1,22 +1,25 @@ from __future__ import absolute_import -from .packages.six.moves.http_client import ( - IncompleteRead as httplib_IncompleteRead -) + +from .packages.six.moves.http_client import IncompleteRead as httplib_IncompleteRead + # Base Exceptions class HTTPError(Exception): - "Base exception used by this module." + """Base exception used by this module.""" + pass class HTTPWarning(Warning): - "Base warning used by this module." + """Base warning used by this module.""" + pass class PoolError(HTTPError): - "Base exception for errors caused within a pool." + """Base exception for errors caused within a pool.""" + def __init__(self, pool, message): self.pool = pool HTTPError.__init__(self, "%s: %s" % (pool, message)) @@ -27,7 +30,8 @@ class PoolError(HTTPError): class RequestError(PoolError): - "Base exception for PoolErrors that have associated URLs." + """Base exception for PoolErrors that have associated URLs.""" + def __init__(self, pool, url, message): self.url = url PoolError.__init__(self, pool, message) @@ -38,22 +42,28 @@ class RequestError(PoolError): class SSLError(HTTPError): - "Raised when SSL certificate fails in an HTTPS connection." + """Raised when SSL certificate fails in an HTTPS connection.""" + pass class ProxyError(HTTPError): - "Raised when the connection to a proxy fails." - pass + """Raised when the connection to a proxy fails.""" + + def __init__(self, message, error, *args): + super(ProxyError, self).__init__(message, error, *args) + self.original_error = error class DecodeError(HTTPError): - "Raised when automatic decoding based on Content-Type fails." + """Raised when automatic decoding based on Content-Type fails.""" + pass class ProtocolError(HTTPError): - "Raised when something unexpected happens mid-request/response." + """Raised when something unexpected happens mid-request/response.""" + pass @@ -63,6 +73,7 @@ ConnectionError = ProtocolError # Leaf Exceptions + class MaxRetryError(RequestError): """Raised when the maximum number of retries is exceeded. @@ -76,14 +87,13 @@ class MaxRetryError(RequestError): def __init__(self, pool, url, reason=None): self.reason = reason - message = "Max retries exceeded with url: %s (Caused by %r)" % ( - url, reason) + message = "Max retries exceeded with url: %s (Caused by %r)" % (url, reason) RequestError.__init__(self, pool, url, message) class HostChangedError(RequestError): - "Raised when an existing pool gets a request for a foreign host." + """Raised when an existing pool gets a request for a foreign host.""" def __init__(self, pool, url, retries=3): message = "Tried to open a foreign host with url: %s" % url @@ -92,53 +102,61 @@ class HostChangedError(RequestError): class TimeoutStateError(HTTPError): - """ Raised when passing an invalid state to a timeout """ + """Raised when passing an invalid state to a timeout""" + pass class TimeoutError(HTTPError): - """ Raised when a socket timeout error occurs. + """Raised when a socket timeout error occurs. Catching this error will catch both :exc:`ReadTimeoutErrors <ReadTimeoutError>` and :exc:`ConnectTimeoutErrors <ConnectTimeoutError>`. """ + pass class ReadTimeoutError(TimeoutError, RequestError): - "Raised when a socket timeout occurs while receiving data from a server" + """Raised when a socket timeout occurs while receiving data from a server""" + pass # This timeout error does not have a URL attached and needs to inherit from the # base HTTPError class ConnectTimeoutError(TimeoutError): - "Raised when a socket timeout occurs while connecting to a server" + """Raised when a socket timeout occurs while connecting to a server""" + pass class NewConnectionError(ConnectTimeoutError, PoolError): - "Raised when we fail to establish a new connection. Usually ECONNREFUSED." + """Raised when we fail to establish a new connection. Usually ECONNREFUSED.""" + pass class EmptyPoolError(PoolError): - "Raised when a pool runs out of connections and no more are allowed." + """Raised when a pool runs out of connections and no more are allowed.""" + pass class ClosedPoolError(PoolError): - "Raised when a request enters a pool after the pool has been closed." + """Raised when a request enters a pool after the pool has been closed.""" + pass class LocationValueError(ValueError, HTTPError): - "Raised when there is something wrong with a given URL input." + """Raised when there is something wrong with a given URL input.""" + pass class LocationParseError(LocationValueError): - "Raised when get_host or similar fails to parse the URL input." + """Raised when get_host or similar fails to parse the URL input.""" def __init__(self, location): message = "Failed to parse: %s" % location @@ -147,39 +165,56 @@ class LocationParseError(LocationValueError): self.location = location +class URLSchemeUnknown(LocationValueError): + """Raised when a URL input has an unsupported scheme.""" + + def __init__(self, scheme): + message = "Not supported URL scheme %s" % scheme + super(URLSchemeUnknown, self).__init__(message) + + self.scheme = scheme + + class ResponseError(HTTPError): - "Used as a container for an error reason supplied in a MaxRetryError." - GENERIC_ERROR = 'too many error responses' - SPECIFIC_ERROR = 'too many {status_code} error responses' + """Used as a container for an error reason supplied in a MaxRetryError.""" + + GENERIC_ERROR = "too many error responses" + SPECIFIC_ERROR = "too many {status_code} error responses" class SecurityWarning(HTTPWarning): - "Warned when performing security reducing actions" + """Warned when performing security reducing actions""" + pass class SubjectAltNameWarning(SecurityWarning): - "Warned when connecting to a host with a certificate missing a SAN." + """Warned when connecting to a host with a certificate missing a SAN.""" + pass class InsecureRequestWarning(SecurityWarning): - "Warned when making an unverified HTTPS request." + """Warned when making an unverified HTTPS request.""" + pass class SystemTimeWarning(SecurityWarning): - "Warned when system time is suspected to be wrong" + """Warned when system time is suspected to be wrong""" + pass class InsecurePlatformWarning(SecurityWarning): - "Warned when certain SSL configuration is not available on a platform." + """Warned when certain TLS/SSL configuration is not available on a platform.""" + pass class SNIMissingWarning(HTTPWarning): - "Warned when making a HTTPS request without SNI available." + """Warned when making a HTTPS request without SNI available.""" + pass @@ -188,19 +223,22 @@ class DependencyWarning(HTTPWarning): Warned when an attempt is made to import a module with missing optional dependencies. """ + pass class ResponseNotChunked(ProtocolError, ValueError): - "Response needs to be chunked in order to read it as chunks." + """Response needs to be chunked in order to read it as chunks.""" + pass class BodyNotHttplibCompatible(HTTPError): """ - Body should be httplib.HTTPResponse like (have an fp attribute which - returns raw chunks) for read_chunked(). + Body should be :class:`http.client.HTTPResponse` like + (have an fp attribute which returns raw chunks) for read_chunked(). """ + pass @@ -208,39 +246,78 @@ class IncompleteRead(HTTPError, httplib_IncompleteRead): """ Response length doesn't match expected Content-Length - Subclass of http_client.IncompleteRead to allow int value - for `partial` to avoid creating large objects on streamed - reads. + Subclass of :class:`http.client.IncompleteRead` to allow int value + for ``partial`` to avoid creating large objects on streamed reads. """ + def __init__(self, partial, expected): super(IncompleteRead, self).__init__(partial, expected) def __repr__(self): - return ('IncompleteRead(%i bytes read, ' - '%i more expected)' % (self.partial, self.expected)) + return "IncompleteRead(%i bytes read, %i more expected)" % ( + self.partial, + self.expected, + ) + + +class InvalidChunkLength(HTTPError, httplib_IncompleteRead): + """Invalid chunk length in a chunked response.""" + + def __init__(self, response, length): + super(InvalidChunkLength, self).__init__( + response.tell(), response.length_remaining + ) + self.response = response + self.length = length + + def __repr__(self): + return "InvalidChunkLength(got length %r, %i bytes read)" % ( + self.length, + self.partial, + ) class InvalidHeader(HTTPError): - "The header provided was somehow invalid." + """The header provided was somehow invalid.""" + pass -class ProxySchemeUnknown(AssertionError, ValueError): - "ProxyManager does not support the supplied scheme" +class ProxySchemeUnknown(AssertionError, URLSchemeUnknown): + """ProxyManager does not support the supplied scheme""" + # TODO(t-8ch): Stop inheriting from AssertionError in v2.0. def __init__(self, scheme): - message = "Not supported proxy scheme %s" % scheme + # 'localhost' is here because our URL parser parses + # localhost:8080 -> scheme=localhost, remove if we fix this. + if scheme == "localhost": + scheme = None + if scheme is None: + message = "Proxy URL had no scheme, should start with http:// or https://" + else: + message = ( + "Proxy URL had unsupported scheme %s, should use http:// or https://" + % scheme + ) super(ProxySchemeUnknown, self).__init__(message) +class ProxySchemeUnsupported(ValueError): + """Fetching HTTPS resources through HTTPS proxies is unsupported""" + + pass + + class HeaderParsingError(HTTPError): - "Raised by assert_header_parsing, but we convert it to a log.warning statement." + """Raised by assert_header_parsing, but we convert it to a log.warning statement.""" + def __init__(self, defects, unparsed_data): - message = '%s, unparsed data: %r' % (defects or 'Unknown', unparsed_data) + message = "%s, unparsed data: %r" % (defects or "Unknown", unparsed_data) super(HeaderParsingError, self).__init__(message) class UnrewindableBodyError(HTTPError): - "urllib3 encountered an error when trying to rewind a body" + """urllib3 encountered an error when trying to rewind a body""" + pass diff --git a/pipenv/vendor/urllib3/fields.py b/pipenv/vendor/urllib3/fields.py index 6a9a5a7f..9d630f49 100644 --- a/pipenv/vendor/urllib3/fields.py +++ b/pipenv/vendor/urllib3/fields.py @@ -1,4 +1,5 @@ from __future__ import absolute_import + import email.utils import mimetypes import re @@ -6,7 +7,7 @@ import re from .packages import six -def guess_content_type(filename, default='application/octet-stream'): +def guess_content_type(filename, default="application/octet-stream"): """ Guess the "Content-Type" of a file. @@ -26,7 +27,8 @@ def format_header_param_rfc2231(name, value): strategy defined in RFC 2231. Particularly useful for header parameters which might contain - non-ASCII values, like file names. This follows RFC 2388 Section 4.4. + non-ASCII values, like file names. This follows + `RFC 2388 Section 4.4 <https://tools.ietf.org/html/rfc2388#section-4.4>`_. :param name: The name of the parameter, a string expected to be ASCII only. @@ -41,22 +43,22 @@ def format_header_param_rfc2231(name, value): if not any(ch in value for ch in '"\\\r\n'): result = u'%s="%s"' % (name, value) try: - result.encode('ascii') + result.encode("ascii") except (UnicodeEncodeError, UnicodeDecodeError): pass else: return result - if not six.PY3: # Python 2: - value = value.encode('utf-8') + if six.PY2: # Python 2: + value = value.encode("utf-8") # encode_rfc2231 accepts an encoded string and returns an ascii-encoded # string in Python 2 but accepts and returns unicode strings in Python 3 - value = email.utils.encode_rfc2231(value, 'utf-8') - value = '%s*=%s' % (name, value) + value = email.utils.encode_rfc2231(value, "utf-8") + value = "%s*=%s" % (name, value) - if not six.PY3: # Python 2: - value = value.decode('utf-8') + if six.PY2: # Python 2: + value = value.decode("utf-8") return value @@ -65,27 +67,24 @@ _HTML5_REPLACEMENTS = { u"\u0022": u"%22", # Replace "\" with "\\". u"\u005C": u"\u005C\u005C", - u"\u005C": u"\u005C\u005C", } # All control characters from 0x00 to 0x1F *except* 0x1B. -_HTML5_REPLACEMENTS.update({ - six.unichr(cc): u"%{:02X}".format(cc) - for cc - in range(0x00, 0x1F+1) - if cc not in (0x1B,) -}) +_HTML5_REPLACEMENTS.update( + { + six.unichr(cc): u"%{:02X}".format(cc) + for cc in range(0x00, 0x1F + 1) + if cc not in (0x1B,) + } +) def _replace_multiple(value, needles_and_replacements): - def replacer(match): return needles_and_replacements[match.group(0)] pattern = re.compile( - r"|".join([ - re.escape(needle) for needle in needles_and_replacements.keys() - ]) + r"|".join([re.escape(needle) for needle in needles_and_replacements.keys()]) ) result = pattern.sub(replacer, value) @@ -140,13 +139,15 @@ class RequestField(object): An optional callable that is used to encode and format the headers. By default, this is :func:`format_header_param_html5`. """ + def __init__( - self, - name, - data, - filename=None, - headers=None, - header_formatter=format_header_param_html5): + self, + name, + data, + filename=None, + headers=None, + header_formatter=format_header_param_html5, + ): self._name = name self._filename = filename self.data = data @@ -156,11 +157,7 @@ class RequestField(object): self.header_formatter = header_formatter @classmethod - def from_tuples( - cls, - fieldname, - value, - header_formatter=format_header_param_html5): + def from_tuples(cls, fieldname, value, header_formatter=format_header_param_html5): """ A :class:`~urllib3.fields.RequestField` factory from old-style tuple parameters. @@ -189,7 +186,8 @@ class RequestField(object): data = value request_param = cls( - fieldname, data, filename=filename, header_formatter=header_formatter) + fieldname, data, filename=filename, header_formatter=header_formatter + ) request_param.make_multipart(content_type=content_type) return request_param @@ -227,7 +225,7 @@ class RequestField(object): if value is not None: parts.append(self._render_part(name, value)) - return u'; '.join(parts) + return u"; ".join(parts) def render_headers(self): """ @@ -235,21 +233,22 @@ class RequestField(object): """ lines = [] - sort_keys = ['Content-Disposition', 'Content-Type', 'Content-Location'] + sort_keys = ["Content-Disposition", "Content-Type", "Content-Location"] for sort_key in sort_keys: if self.headers.get(sort_key, False): - lines.append(u'%s: %s' % (sort_key, self.headers[sort_key])) + lines.append(u"%s: %s" % (sort_key, self.headers[sort_key])) for header_name, header_value in self.headers.items(): if header_name not in sort_keys: if header_value: - lines.append(u'%s: %s' % (header_name, header_value)) + lines.append(u"%s: %s" % (header_name, header_value)) - lines.append(u'\r\n') - return u'\r\n'.join(lines) + lines.append(u"\r\n") + return u"\r\n".join(lines) - def make_multipart(self, content_disposition=None, content_type=None, - content_location=None): + def make_multipart( + self, content_disposition=None, content_type=None, content_location=None + ): """ Makes this request field into a multipart request field. @@ -262,11 +261,14 @@ class RequestField(object): The 'Content-Location' of the request body. """ - self.headers['Content-Disposition'] = content_disposition or u'form-data' - self.headers['Content-Disposition'] += u'; '.join([ - u'', self._render_parts( - ((u'name', self._name), (u'filename', self._filename)) - ) - ]) - self.headers['Content-Type'] = content_type - self.headers['Content-Location'] = content_location + self.headers["Content-Disposition"] = content_disposition or u"form-data" + self.headers["Content-Disposition"] += u"; ".join( + [ + u"", + self._render_parts( + ((u"name", self._name), (u"filename", self._filename)) + ), + ] + ) + self.headers["Content-Type"] = content_type + self.headers["Content-Location"] = content_location diff --git a/pipenv/vendor/urllib3/filepost.py b/pipenv/vendor/urllib3/filepost.py index 78f1e19b..36c9252c 100644 --- a/pipenv/vendor/urllib3/filepost.py +++ b/pipenv/vendor/urllib3/filepost.py @@ -1,15 +1,15 @@ from __future__ import absolute_import + import binascii import codecs import os - from io import BytesIO +from .fields import RequestField from .packages import six from .packages.six import b -from .fields import RequestField -writer = codecs.lookup('utf-8')[3] +writer = codecs.lookup("utf-8")[3] def choose_boundary(): @@ -17,8 +17,8 @@ def choose_boundary(): Our embarrassingly-simple replacement for mimetools.choose_boundary. """ boundary = binascii.hexlify(os.urandom(16)) - if six.PY3: - boundary = boundary.decode('ascii') + if not six.PY2: + boundary = boundary.decode("ascii") return boundary @@ -76,7 +76,7 @@ def encode_multipart_formdata(fields, boundary=None): boundary = choose_boundary() for field in iter_field_objects(fields): - body.write(b('--%s\r\n' % (boundary))) + body.write(b("--%s\r\n" % (boundary))) writer(body).write(field.render_headers()) data = field.data @@ -89,10 +89,10 @@ def encode_multipart_formdata(fields, boundary=None): else: body.write(data) - body.write(b'\r\n') + body.write(b"\r\n") - body.write(b('--%s--\r\n' % (boundary))) + body.write(b("--%s--\r\n" % (boundary))) - content_type = str('multipart/form-data; boundary=%s' % boundary) + content_type = str("multipart/form-data; boundary=%s" % boundary) return body.getvalue(), content_type diff --git a/pipenv/vendor/urllib3/packages/__init__.py b/pipenv/vendor/urllib3/packages/__init__.py index 170e974c..fce4caa6 100644 --- a/pipenv/vendor/urllib3/packages/__init__.py +++ b/pipenv/vendor/urllib3/packages/__init__.py @@ -2,4 +2,4 @@ from __future__ import absolute_import from . import ssl_match_hostname -__all__ = ('ssl_match_hostname', ) +__all__ = ("ssl_match_hostname",) diff --git a/pipenv/vendor/urllib3/packages/backports/makefile.py b/pipenv/vendor/urllib3/packages/backports/makefile.py index 740db377..b8fb2154 100644 --- a/pipenv/vendor/urllib3/packages/backports/makefile.py +++ b/pipenv/vendor/urllib3/packages/backports/makefile.py @@ -7,19 +7,17 @@ Backports the Python 3 ``socket.makefile`` method for use with anything that wants to create a "fake" socket object. """ import io - from socket import SocketIO -def backport_makefile(self, mode="r", buffering=None, encoding=None, - errors=None, newline=None): +def backport_makefile( + self, mode="r", buffering=None, encoding=None, errors=None, newline=None +): """ Backport of ``socket.makefile`` from Python 3.5. """ if not set(mode) <= {"r", "w", "b"}: - raise ValueError( - "invalid mode %r (only r, w, b allowed)" % (mode,) - ) + raise ValueError("invalid mode %r (only r, w, b allowed)" % (mode,)) writing = "w" in mode reading = "r" in mode or not writing assert reading or writing diff --git a/pipenv/vendor/urllib3/packages/rfc3986/__init__.py b/pipenv/vendor/urllib3/packages/rfc3986/__init__.py deleted file mode 100644 index 9d3c3bc9..00000000 --- a/pipenv/vendor/urllib3/packages/rfc3986/__init__.py +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2014 Rackspace -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -An implementation of semantics and validations described in RFC 3986. - -See http://rfc3986.readthedocs.io/ for detailed documentation. - -:copyright: (c) 2014 Rackspace -:license: Apache v2.0, see LICENSE for details -""" - -from .api import iri_reference -from .api import IRIReference -from .api import is_valid_uri -from .api import normalize_uri -from .api import uri_reference -from .api import URIReference -from .api import urlparse -from .parseresult import ParseResult - -__title__ = 'rfc3986' -__author__ = 'Ian Stapleton Cordasco' -__author_email__ = 'graffatcolmingov@gmail.com' -__license__ = 'Apache v2.0' -__copyright__ = 'Copyright 2014 Rackspace' -__version__ = '1.3.1' - -__all__ = ( - 'ParseResult', - 'URIReference', - 'IRIReference', - 'is_valid_uri', - 'normalize_uri', - 'uri_reference', - 'iri_reference', - 'urlparse', - '__title__', - '__author__', - '__author_email__', - '__license__', - '__copyright__', - '__version__', -) diff --git a/pipenv/vendor/urllib3/packages/rfc3986/_mixin.py b/pipenv/vendor/urllib3/packages/rfc3986/_mixin.py deleted file mode 100644 index 543925cd..00000000 --- a/pipenv/vendor/urllib3/packages/rfc3986/_mixin.py +++ /dev/null @@ -1,353 +0,0 @@ -"""Module containing the implementation of the URIMixin class.""" -import warnings - -from . import exceptions as exc -from . import misc -from . import normalizers -from . import validators - - -class URIMixin(object): - """Mixin with all shared methods for URIs and IRIs.""" - - __hash__ = tuple.__hash__ - - def authority_info(self): - """Return a dictionary with the ``userinfo``, ``host``, and ``port``. - - If the authority is not valid, it will raise a - :class:`~rfc3986.exceptions.InvalidAuthority` Exception. - - :returns: - ``{'userinfo': 'username:password', 'host': 'www.example.com', - 'port': '80'}`` - :rtype: dict - :raises rfc3986.exceptions.InvalidAuthority: - If the authority is not ``None`` and can not be parsed. - """ - if not self.authority: - return {'userinfo': None, 'host': None, 'port': None} - - match = self._match_subauthority() - - if match is None: - # In this case, we have an authority that was parsed from the URI - # Reference, but it cannot be further parsed by our - # misc.SUBAUTHORITY_MATCHER. In this case it must not be a valid - # authority. - raise exc.InvalidAuthority(self.authority.encode(self.encoding)) - - # We had a match, now let's ensure that it is actually a valid host - # address if it is IPv4 - matches = match.groupdict() - host = matches.get('host') - - if (host and misc.IPv4_MATCHER.match(host) and not - validators.valid_ipv4_host_address(host)): - # If we have a host, it appears to be IPv4 and it does not have - # valid bytes, it is an InvalidAuthority. - raise exc.InvalidAuthority(self.authority.encode(self.encoding)) - - return matches - - def _match_subauthority(self): - return misc.SUBAUTHORITY_MATCHER.match(self.authority) - - @property - def host(self): - """If present, a string representing the host.""" - try: - authority = self.authority_info() - except exc.InvalidAuthority: - return None - return authority['host'] - - @property - def port(self): - """If present, the port extracted from the authority.""" - try: - authority = self.authority_info() - except exc.InvalidAuthority: - return None - return authority['port'] - - @property - def userinfo(self): - """If present, the userinfo extracted from the authority.""" - try: - authority = self.authority_info() - except exc.InvalidAuthority: - return None - return authority['userinfo'] - - def is_absolute(self): - """Determine if this URI Reference is an absolute URI. - - See http://tools.ietf.org/html/rfc3986#section-4.3 for explanation. - - :returns: ``True`` if it is an absolute URI, ``False`` otherwise. - :rtype: bool - """ - return bool(misc.ABSOLUTE_URI_MATCHER.match(self.unsplit())) - - def is_valid(self, **kwargs): - """Determine if the URI is valid. - - .. deprecated:: 1.1.0 - - Use the :class:`~rfc3986.validators.Validator` object instead. - - :param bool require_scheme: Set to ``True`` if you wish to require the - presence of the scheme component. - :param bool require_authority: Set to ``True`` if you wish to require - the presence of the authority component. - :param bool require_path: Set to ``True`` if you wish to require the - presence of the path component. - :param bool require_query: Set to ``True`` if you wish to require the - presence of the query component. - :param bool require_fragment: Set to ``True`` if you wish to require - the presence of the fragment component. - :returns: ``True`` if the URI is valid. ``False`` otherwise. - :rtype: bool - """ - warnings.warn("Please use rfc3986.validators.Validator instead. " - "This method will be eventually removed.", - DeprecationWarning) - validators = [ - (self.scheme_is_valid, kwargs.get('require_scheme', False)), - (self.authority_is_valid, kwargs.get('require_authority', False)), - (self.path_is_valid, kwargs.get('require_path', False)), - (self.query_is_valid, kwargs.get('require_query', False)), - (self.fragment_is_valid, kwargs.get('require_fragment', False)), - ] - return all(v(r) for v, r in validators) - - def authority_is_valid(self, require=False): - """Determine if the authority component is valid. - - .. deprecated:: 1.1.0 - - Use the :class:`~rfc3986.validators.Validator` object instead. - - :param bool require: - Set to ``True`` to require the presence of this component. - :returns: - ``True`` if the authority is valid. ``False`` otherwise. - :rtype: - bool - """ - warnings.warn("Please use rfc3986.validators.Validator instead. " - "This method will be eventually removed.", - DeprecationWarning) - try: - self.authority_info() - except exc.InvalidAuthority: - return False - - return validators.authority_is_valid( - self.authority, - host=self.host, - require=require, - ) - - def scheme_is_valid(self, require=False): - """Determine if the scheme component is valid. - - .. deprecated:: 1.1.0 - - Use the :class:`~rfc3986.validators.Validator` object instead. - - :param str require: Set to ``True`` to require the presence of this - component. - :returns: ``True`` if the scheme is valid. ``False`` otherwise. - :rtype: bool - """ - warnings.warn("Please use rfc3986.validators.Validator instead. " - "This method will be eventually removed.", - DeprecationWarning) - return validators.scheme_is_valid(self.scheme, require) - - def path_is_valid(self, require=False): - """Determine if the path component is valid. - - .. deprecated:: 1.1.0 - - Use the :class:`~rfc3986.validators.Validator` object instead. - - :param str require: Set to ``True`` to require the presence of this - component. - :returns: ``True`` if the path is valid. ``False`` otherwise. - :rtype: bool - """ - warnings.warn("Please use rfc3986.validators.Validator instead. " - "This method will be eventually removed.", - DeprecationWarning) - return validators.path_is_valid(self.path, require) - - def query_is_valid(self, require=False): - """Determine if the query component is valid. - - .. deprecated:: 1.1.0 - - Use the :class:`~rfc3986.validators.Validator` object instead. - - :param str require: Set to ``True`` to require the presence of this - component. - :returns: ``True`` if the query is valid. ``False`` otherwise. - :rtype: bool - """ - warnings.warn("Please use rfc3986.validators.Validator instead. " - "This method will be eventually removed.", - DeprecationWarning) - return validators.query_is_valid(self.query, require) - - def fragment_is_valid(self, require=False): - """Determine if the fragment component is valid. - - .. deprecated:: 1.1.0 - - Use the Validator object instead. - - :param str require: Set to ``True`` to require the presence of this - component. - :returns: ``True`` if the fragment is valid. ``False`` otherwise. - :rtype: bool - """ - warnings.warn("Please use rfc3986.validators.Validator instead. " - "This method will be eventually removed.", - DeprecationWarning) - return validators.fragment_is_valid(self.fragment, require) - - def normalized_equality(self, other_ref): - """Compare this URIReference to another URIReference. - - :param URIReference other_ref: (required), The reference with which - we're comparing. - :returns: ``True`` if the references are equal, ``False`` otherwise. - :rtype: bool - """ - return tuple(self.normalize()) == tuple(other_ref.normalize()) - - def resolve_with(self, base_uri, strict=False): - """Use an absolute URI Reference to resolve this relative reference. - - Assuming this is a relative reference that you would like to resolve, - use the provided base URI to resolve it. - - See http://tools.ietf.org/html/rfc3986#section-5 for more information. - - :param base_uri: Either a string or URIReference. It must be an - absolute URI or it will raise an exception. - :returns: A new URIReference which is the result of resolving this - reference using ``base_uri``. - :rtype: :class:`URIReference` - :raises rfc3986.exceptions.ResolutionError: - If the ``base_uri`` is not an absolute URI. - """ - if not isinstance(base_uri, URIMixin): - base_uri = type(self).from_string(base_uri) - - if not base_uri.is_absolute(): - raise exc.ResolutionError(base_uri) - - # This is optional per - # http://tools.ietf.org/html/rfc3986#section-5.2.1 - base_uri = base_uri.normalize() - - # The reference we're resolving - resolving = self - - if not strict and resolving.scheme == base_uri.scheme: - resolving = resolving.copy_with(scheme=None) - - # http://tools.ietf.org/html/rfc3986#page-32 - if resolving.scheme is not None: - target = resolving.copy_with( - path=normalizers.normalize_path(resolving.path) - ) - else: - if resolving.authority is not None: - target = resolving.copy_with( - scheme=base_uri.scheme, - path=normalizers.normalize_path(resolving.path) - ) - else: - if resolving.path is None: - if resolving.query is not None: - query = resolving.query - else: - query = base_uri.query - target = resolving.copy_with( - scheme=base_uri.scheme, - authority=base_uri.authority, - path=base_uri.path, - query=query - ) - else: - if resolving.path.startswith('/'): - path = normalizers.normalize_path(resolving.path) - else: - path = normalizers.normalize_path( - misc.merge_paths(base_uri, resolving.path) - ) - target = resolving.copy_with( - scheme=base_uri.scheme, - authority=base_uri.authority, - path=path, - query=resolving.query - ) - return target - - def unsplit(self): - """Create a URI string from the components. - - :returns: The URI Reference reconstituted as a string. - :rtype: str - """ - # See http://tools.ietf.org/html/rfc3986#section-5.3 - result_list = [] - if self.scheme: - result_list.extend([self.scheme, ':']) - if self.authority: - result_list.extend(['//', self.authority]) - if self.path: - result_list.append(self.path) - if self.query is not None: - result_list.extend(['?', self.query]) - if self.fragment is not None: - result_list.extend(['#', self.fragment]) - return ''.join(result_list) - - def copy_with(self, scheme=misc.UseExisting, authority=misc.UseExisting, - path=misc.UseExisting, query=misc.UseExisting, - fragment=misc.UseExisting): - """Create a copy of this reference with the new components. - - :param str scheme: - (optional) The scheme to use for the new reference. - :param str authority: - (optional) The authority to use for the new reference. - :param str path: - (optional) The path to use for the new reference. - :param str query: - (optional) The query to use for the new reference. - :param str fragment: - (optional) The fragment to use for the new reference. - :returns: - New URIReference with provided components. - :rtype: - URIReference - """ - attributes = { - 'scheme': scheme, - 'authority': authority, - 'path': path, - 'query': query, - 'fragment': fragment, - } - for key, value in list(attributes.items()): - if value is misc.UseExisting: - del attributes[key] - uri = self._replace(**attributes) - uri.encoding = self.encoding - return uri diff --git a/pipenv/vendor/urllib3/packages/rfc3986/abnf_regexp.py b/pipenv/vendor/urllib3/packages/rfc3986/abnf_regexp.py deleted file mode 100644 index 24c9c3d0..00000000 --- a/pipenv/vendor/urllib3/packages/rfc3986/abnf_regexp.py +++ /dev/null @@ -1,267 +0,0 @@ -# -*- coding: utf-8 -*- -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Module for the regular expressions crafted from ABNF.""" - -import sys - -# https://tools.ietf.org/html/rfc3986#page-13 -GEN_DELIMS = GENERIC_DELIMITERS = ":/?#[]@" -GENERIC_DELIMITERS_SET = set(GENERIC_DELIMITERS) -# https://tools.ietf.org/html/rfc3986#page-13 -SUB_DELIMS = SUB_DELIMITERS = "!$&'()*+,;=" -SUB_DELIMITERS_SET = set(SUB_DELIMITERS) -# Escape the '*' for use in regular expressions -SUB_DELIMITERS_RE = r"!$&'()\*+,;=" -RESERVED_CHARS_SET = GENERIC_DELIMITERS_SET.union(SUB_DELIMITERS_SET) -ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' -DIGIT = '0123456789' -# https://tools.ietf.org/html/rfc3986#section-2.3 -UNRESERVED = UNRESERVED_CHARS = ALPHA + DIGIT + r'._!-' -UNRESERVED_CHARS_SET = set(UNRESERVED_CHARS) -NON_PCT_ENCODED_SET = RESERVED_CHARS_SET.union(UNRESERVED_CHARS_SET) -# We need to escape the '-' in this case: -UNRESERVED_RE = r'A-Za-z0-9._~\-' - -# Percent encoded character values -PERCENT_ENCODED = PCT_ENCODED = '%[A-Fa-f0-9]{2}' -PCHAR = '([' + UNRESERVED_RE + SUB_DELIMITERS_RE + ':@]|%s)' % PCT_ENCODED - -# NOTE(sigmavirus24): We're going to use more strict regular expressions -# than appear in Appendix B for scheme. This will prevent over-eager -# consuming of items that aren't schemes. -SCHEME_RE = '[a-zA-Z][a-zA-Z0-9+.-]*' -_AUTHORITY_RE = '[^/?#]*' -_PATH_RE = '[^?#]*' -_QUERY_RE = '[^#]*' -_FRAGMENT_RE = '.*' - -# Extracted from http://tools.ietf.org/html/rfc3986#appendix-B -COMPONENT_PATTERN_DICT = { - 'scheme': SCHEME_RE, - 'authority': _AUTHORITY_RE, - 'path': _PATH_RE, - 'query': _QUERY_RE, - 'fragment': _FRAGMENT_RE, -} - -# See http://tools.ietf.org/html/rfc3986#appendix-B -# In this case, we name each of the important matches so we can use -# SRE_Match#groupdict to parse the values out if we so choose. This is also -# modified to ignore other matches that are not important to the parsing of -# the reference so we can also simply use SRE_Match#groups. -URL_PARSING_RE = ( - r'(?:(?P<scheme>{scheme}):)?(?://(?P<authority>{authority}))?' - r'(?P<path>{path})(?:\?(?P<query>{query}))?' - r'(?:#(?P<fragment>{fragment}))?' -).format(**COMPONENT_PATTERN_DICT) - - -# ######################### -# Authority Matcher Section -# ######################### - -# Host patterns, see: http://tools.ietf.org/html/rfc3986#section-3.2.2 -# The pattern for a regular name, e.g., www.google.com, api.github.com -REGULAR_NAME_RE = REG_NAME = '((?:{0}|[{1}])*)'.format( - '%[0-9A-Fa-f]{2}', SUB_DELIMITERS_RE + UNRESERVED_RE -) -# The pattern for an IPv4 address, e.g., 192.168.255.255, 127.0.0.1, -IPv4_RE = r'([0-9]{1,3}\.){3}[0-9]{1,3}' -# Hexadecimal characters used in each piece of an IPv6 address -HEXDIG_RE = '[0-9A-Fa-f]{1,4}' -# Least-significant 32 bits of an IPv6 address -LS32_RE = '({hex}:{hex}|{ipv4})'.format(hex=HEXDIG_RE, ipv4=IPv4_RE) -# Substitutions into the following patterns for IPv6 patterns defined -# http://tools.ietf.org/html/rfc3986#page-20 -_subs = {'hex': HEXDIG_RE, 'ls32': LS32_RE} - -# Below: h16 = hexdig, see: https://tools.ietf.org/html/rfc5234 for details -# about ABNF (Augmented Backus-Naur Form) use in the comments -variations = [ - # 6( h16 ":" ) ls32 - '(%(hex)s:){6}%(ls32)s' % _subs, - # "::" 5( h16 ":" ) ls32 - '::(%(hex)s:){5}%(ls32)s' % _subs, - # [ h16 ] "::" 4( h16 ":" ) ls32 - '(%(hex)s)?::(%(hex)s:){4}%(ls32)s' % _subs, - # [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 - '((%(hex)s:)?%(hex)s)?::(%(hex)s:){3}%(ls32)s' % _subs, - # [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 - '((%(hex)s:){0,2}%(hex)s)?::(%(hex)s:){2}%(ls32)s' % _subs, - # [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 - '((%(hex)s:){0,3}%(hex)s)?::%(hex)s:%(ls32)s' % _subs, - # [ *4( h16 ":" ) h16 ] "::" ls32 - '((%(hex)s:){0,4}%(hex)s)?::%(ls32)s' % _subs, - # [ *5( h16 ":" ) h16 ] "::" h16 - '((%(hex)s:){0,5}%(hex)s)?::%(hex)s' % _subs, - # [ *6( h16 ":" ) h16 ] "::" - '((%(hex)s:){0,6}%(hex)s)?::' % _subs, -] - -IPv6_RE = '(({0})|({1})|({2})|({3})|({4})|({5})|({6})|({7})|({8}))'.format( - *variations -) - -IPv_FUTURE_RE = r'v[0-9A-Fa-f]+\.[%s]+' % ( - UNRESERVED_RE + SUB_DELIMITERS_RE + ':' -) - -# RFC 6874 Zone ID ABNF -ZONE_ID = '(?:[' + UNRESERVED_RE + ']|' + PCT_ENCODED + ')+' - -IPv6_ADDRZ_RFC4007_RE = IPv6_RE + '(?:(?:%25|%)' + ZONE_ID + ')?' -IPv6_ADDRZ_RE = IPv6_RE + '(?:%25' + ZONE_ID + ')?' - -IP_LITERAL_RE = r'\[({0}|{1})\]'.format( - IPv6_ADDRZ_RFC4007_RE, - IPv_FUTURE_RE, -) - -# Pattern for matching the host piece of the authority -HOST_RE = HOST_PATTERN = '({0}|{1}|{2})'.format( - REG_NAME, - IPv4_RE, - IP_LITERAL_RE, -) -USERINFO_RE = '^([' + UNRESERVED_RE + SUB_DELIMITERS_RE + ':]|%s)+' % ( - PCT_ENCODED -) -PORT_RE = '[0-9]{1,5}' - -# #################### -# Path Matcher Section -# #################### - -# See http://tools.ietf.org/html/rfc3986#section-3.3 for more information -# about the path patterns defined below. -segments = { - 'segment': PCHAR + '*', - # Non-zero length segment - 'segment-nz': PCHAR + '+', - # Non-zero length segment without ":" - 'segment-nz-nc': PCHAR.replace(':', '') + '+' -} - -# Path types taken from Section 3.3 (linked above) -PATH_EMPTY = '^$' -PATH_ROOTLESS = '%(segment-nz)s(/%(segment)s)*' % segments -PATH_NOSCHEME = '%(segment-nz-nc)s(/%(segment)s)*' % segments -PATH_ABSOLUTE = '/(%s)?' % PATH_ROOTLESS -PATH_ABEMPTY = '(/%(segment)s)*' % segments -PATH_RE = '^(%s|%s|%s|%s|%s)$' % ( - PATH_ABEMPTY, PATH_ABSOLUTE, PATH_NOSCHEME, PATH_ROOTLESS, PATH_EMPTY -) - -FRAGMENT_RE = QUERY_RE = ( - '^([/?:@' + UNRESERVED_RE + SUB_DELIMITERS_RE + ']|%s)*$' % PCT_ENCODED -) - -# ########################## -# Relative reference matcher -# ########################## - -# See http://tools.ietf.org/html/rfc3986#section-4.2 for details -RELATIVE_PART_RE = '(//%s%s|%s|%s|%s)' % ( - COMPONENT_PATTERN_DICT['authority'], - PATH_ABEMPTY, - PATH_ABSOLUTE, - PATH_NOSCHEME, - PATH_EMPTY, -) - -# See http://tools.ietf.org/html/rfc3986#section-3 for definition -HIER_PART_RE = '(//%s%s|%s|%s|%s)' % ( - COMPONENT_PATTERN_DICT['authority'], - PATH_ABEMPTY, - PATH_ABSOLUTE, - PATH_ROOTLESS, - PATH_EMPTY, -) - -# ############### -# IRIs / RFC 3987 -# ############### - -# Only wide-unicode gets the high-ranges of UCSCHAR -if sys.maxunicode > 0xFFFF: # pragma: no cover - IPRIVATE = u'\uE000-\uF8FF\U000F0000-\U000FFFFD\U00100000-\U0010FFFD' - UCSCHAR_RE = ( - u'\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF' - u'\U00010000-\U0001FFFD\U00020000-\U0002FFFD' - u'\U00030000-\U0003FFFD\U00040000-\U0004FFFD' - u'\U00050000-\U0005FFFD\U00060000-\U0006FFFD' - u'\U00070000-\U0007FFFD\U00080000-\U0008FFFD' - u'\U00090000-\U0009FFFD\U000A0000-\U000AFFFD' - u'\U000B0000-\U000BFFFD\U000C0000-\U000CFFFD' - u'\U000D0000-\U000DFFFD\U000E1000-\U000EFFFD' - ) -else: # pragma: no cover - IPRIVATE = u'\uE000-\uF8FF' - UCSCHAR_RE = ( - u'\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF' - ) - -IUNRESERVED_RE = u'A-Za-z0-9\\._~\\-' + UCSCHAR_RE -IPCHAR = u'([' + IUNRESERVED_RE + SUB_DELIMITERS_RE + u':@]|%s)' % PCT_ENCODED - -isegments = { - 'isegment': IPCHAR + u'*', - # Non-zero length segment - 'isegment-nz': IPCHAR + u'+', - # Non-zero length segment without ":" - 'isegment-nz-nc': IPCHAR.replace(':', '') + u'+' -} - -IPATH_ROOTLESS = u'%(isegment-nz)s(/%(isegment)s)*' % isegments -IPATH_NOSCHEME = u'%(isegment-nz-nc)s(/%(isegment)s)*' % isegments -IPATH_ABSOLUTE = u'/(?:%s)?' % IPATH_ROOTLESS -IPATH_ABEMPTY = u'(?:/%(isegment)s)*' % isegments -IPATH_RE = u'^(?:%s|%s|%s|%s|%s)$' % ( - IPATH_ABEMPTY, IPATH_ABSOLUTE, IPATH_NOSCHEME, IPATH_ROOTLESS, PATH_EMPTY -) - -IREGULAR_NAME_RE = IREG_NAME = u'(?:{0}|[{1}])*'.format( - u'%[0-9A-Fa-f]{2}', SUB_DELIMITERS_RE + IUNRESERVED_RE -) - -IHOST_RE = IHOST_PATTERN = u'({0}|{1}|{2})'.format( - IREG_NAME, - IPv4_RE, - IP_LITERAL_RE, -) - -IUSERINFO_RE = u'^(?:[' + IUNRESERVED_RE + SUB_DELIMITERS_RE + u':]|%s)+' % ( - PCT_ENCODED -) - -IFRAGMENT_RE = (u'^(?:[/?:@' + IUNRESERVED_RE + SUB_DELIMITERS_RE - + u']|%s)*$' % PCT_ENCODED) -IQUERY_RE = (u'^(?:[/?:@' + IUNRESERVED_RE + SUB_DELIMITERS_RE - + IPRIVATE + u']|%s)*$' % PCT_ENCODED) - -IRELATIVE_PART_RE = u'(//%s%s|%s|%s|%s)' % ( - COMPONENT_PATTERN_DICT['authority'], - IPATH_ABEMPTY, - IPATH_ABSOLUTE, - IPATH_NOSCHEME, - PATH_EMPTY, -) - -IHIER_PART_RE = u'(//%s%s|%s|%s|%s)' % ( - COMPONENT_PATTERN_DICT['authority'], - IPATH_ABEMPTY, - IPATH_ABSOLUTE, - IPATH_ROOTLESS, - PATH_EMPTY, -) diff --git a/pipenv/vendor/urllib3/packages/rfc3986/api.py b/pipenv/vendor/urllib3/packages/rfc3986/api.py deleted file mode 100644 index ddc4a1cd..00000000 --- a/pipenv/vendor/urllib3/packages/rfc3986/api.py +++ /dev/null @@ -1,106 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2014 Rackspace -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -Module containing the simple and functional API for rfc3986. - -This module defines functions and provides access to the public attributes -and classes of rfc3986. -""" - -from .iri import IRIReference -from .parseresult import ParseResult -from .uri import URIReference - - -def uri_reference(uri, encoding='utf-8'): - """Parse a URI string into a URIReference. - - This is a convenience function. You could achieve the same end by using - ``URIReference.from_string(uri)``. - - :param str uri: The URI which needs to be parsed into a reference. - :param str encoding: The encoding of the string provided - :returns: A parsed URI - :rtype: :class:`URIReference` - """ - return URIReference.from_string(uri, encoding) - - -def iri_reference(iri, encoding='utf-8'): - """Parse a IRI string into an IRIReference. - - This is a convenience function. You could achieve the same end by using - ``IRIReference.from_string(iri)``. - - :param str iri: The IRI which needs to be parsed into a reference. - :param str encoding: The encoding of the string provided - :returns: A parsed IRI - :rtype: :class:`IRIReference` - """ - return IRIReference.from_string(iri, encoding) - - -def is_valid_uri(uri, encoding='utf-8', **kwargs): - """Determine if the URI given is valid. - - This is a convenience function. You could use either - ``uri_reference(uri).is_valid()`` or - ``URIReference.from_string(uri).is_valid()`` to achieve the same result. - - :param str uri: The URI to be validated. - :param str encoding: The encoding of the string provided - :param bool require_scheme: Set to ``True`` if you wish to require the - presence of the scheme component. - :param bool require_authority: Set to ``True`` if you wish to require the - presence of the authority component. - :param bool require_path: Set to ``True`` if you wish to require the - presence of the path component. - :param bool require_query: Set to ``True`` if you wish to require the - presence of the query component. - :param bool require_fragment: Set to ``True`` if you wish to require the - presence of the fragment component. - :returns: ``True`` if the URI is valid, ``False`` otherwise. - :rtype: bool - """ - return URIReference.from_string(uri, encoding).is_valid(**kwargs) - - -def normalize_uri(uri, encoding='utf-8'): - """Normalize the given URI. - - This is a convenience function. You could use either - ``uri_reference(uri).normalize().unsplit()`` or - ``URIReference.from_string(uri).normalize().unsplit()`` instead. - - :param str uri: The URI to be normalized. - :param str encoding: The encoding of the string provided - :returns: The normalized URI. - :rtype: str - """ - normalized_reference = URIReference.from_string(uri, encoding).normalize() - return normalized_reference.unsplit() - - -def urlparse(uri, encoding='utf-8'): - """Parse a given URI and return a ParseResult. - - This is a partial replacement of the standard library's urlparse function. - - :param str uri: The URI to be parsed. - :param str encoding: The encoding of the string provided. - :returns: A parsed URI - :rtype: :class:`~rfc3986.parseresult.ParseResult` - """ - return ParseResult.from_string(uri, encoding, strict=False) diff --git a/pipenv/vendor/urllib3/packages/rfc3986/builder.py b/pipenv/vendor/urllib3/packages/rfc3986/builder.py deleted file mode 100644 index 79342799..00000000 --- a/pipenv/vendor/urllib3/packages/rfc3986/builder.py +++ /dev/null @@ -1,298 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017 Ian Stapleton Cordasco -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Module containing the logic for the URIBuilder object.""" -from . import compat -from . import normalizers -from . import uri - - -class URIBuilder(object): - """Object to aid in building up a URI Reference from parts. - - .. note:: - - This object should be instantiated by the user, but it's recommended - that it is not provided with arguments. Instead, use the available - method to populate the fields. - - """ - - def __init__(self, scheme=None, userinfo=None, host=None, port=None, - path=None, query=None, fragment=None): - """Initialize our URI builder. - - :param str scheme: - (optional) - :param str userinfo: - (optional) - :param str host: - (optional) - :param int port: - (optional) - :param str path: - (optional) - :param str query: - (optional) - :param str fragment: - (optional) - """ - self.scheme = scheme - self.userinfo = userinfo - self.host = host - self.port = port - self.path = path - self.query = query - self.fragment = fragment - - def __repr__(self): - """Provide a convenient view of our builder object.""" - formatstr = ('URIBuilder(scheme={b.scheme}, userinfo={b.userinfo}, ' - 'host={b.host}, port={b.port}, path={b.path}, ' - 'query={b.query}, fragment={b.fragment})') - return formatstr.format(b=self) - - def add_scheme(self, scheme): - """Add a scheme to our builder object. - - After normalizing, this will generate a new URIBuilder instance with - the specified scheme and all other attributes the same. - - .. code-block:: python - - >>> URIBuilder().add_scheme('HTTPS') - URIBuilder(scheme='https', userinfo=None, host=None, port=None, - path=None, query=None, fragment=None) - - """ - scheme = normalizers.normalize_scheme(scheme) - return URIBuilder( - scheme=scheme, - userinfo=self.userinfo, - host=self.host, - port=self.port, - path=self.path, - query=self.query, - fragment=self.fragment, - ) - - def add_credentials(self, username, password): - """Add credentials as the userinfo portion of the URI. - - .. code-block:: python - - >>> URIBuilder().add_credentials('root', 's3crete') - URIBuilder(scheme=None, userinfo='root:s3crete', host=None, - port=None, path=None, query=None, fragment=None) - - >>> URIBuilder().add_credentials('root', None) - URIBuilder(scheme=None, userinfo='root', host=None, - port=None, path=None, query=None, fragment=None) - """ - if username is None: - raise ValueError('Username cannot be None') - userinfo = normalizers.normalize_username(username) - - if password is not None: - userinfo = '{}:{}'.format( - userinfo, - normalizers.normalize_password(password), - ) - - return URIBuilder( - scheme=self.scheme, - userinfo=userinfo, - host=self.host, - port=self.port, - path=self.path, - query=self.query, - fragment=self.fragment, - ) - - def add_host(self, host): - """Add hostname to the URI. - - .. code-block:: python - - >>> URIBuilder().add_host('google.com') - URIBuilder(scheme=None, userinfo=None, host='google.com', - port=None, path=None, query=None, fragment=None) - - """ - return URIBuilder( - scheme=self.scheme, - userinfo=self.userinfo, - host=normalizers.normalize_host(host), - port=self.port, - path=self.path, - query=self.query, - fragment=self.fragment, - ) - - def add_port(self, port): - """Add port to the URI. - - .. code-block:: python - - >>> URIBuilder().add_port(80) - URIBuilder(scheme=None, userinfo=None, host=None, port='80', - path=None, query=None, fragment=None) - - >>> URIBuilder().add_port(443) - URIBuilder(scheme=None, userinfo=None, host=None, port='443', - path=None, query=None, fragment=None) - - """ - port_int = int(port) - if port_int < 0: - raise ValueError( - 'ports are not allowed to be negative. You provided {}'.format( - port_int, - ) - ) - if port_int > 65535: - raise ValueError( - 'ports are not allowed to be larger than 65535. ' - 'You provided {}'.format( - port_int, - ) - ) - - return URIBuilder( - scheme=self.scheme, - userinfo=self.userinfo, - host=self.host, - port='{}'.format(port_int), - path=self.path, - query=self.query, - fragment=self.fragment, - ) - - def add_path(self, path): - """Add a path to the URI. - - .. code-block:: python - - >>> URIBuilder().add_path('sigmavirus24/rfc3985') - URIBuilder(scheme=None, userinfo=None, host=None, port=None, - path='/sigmavirus24/rfc3986', query=None, fragment=None) - - >>> URIBuilder().add_path('/checkout.php') - URIBuilder(scheme=None, userinfo=None, host=None, port=None, - path='/checkout.php', query=None, fragment=None) - - """ - if not path.startswith('/'): - path = '/{}'.format(path) - - return URIBuilder( - scheme=self.scheme, - userinfo=self.userinfo, - host=self.host, - port=self.port, - path=normalizers.normalize_path(path), - query=self.query, - fragment=self.fragment, - ) - - def add_query_from(self, query_items): - """Generate and add a query a dictionary or list of tuples. - - .. code-block:: python - - >>> URIBuilder().add_query_from({'a': 'b c'}) - URIBuilder(scheme=None, userinfo=None, host=None, port=None, - path=None, query='a=b+c', fragment=None) - - >>> URIBuilder().add_query_from([('a', 'b c')]) - URIBuilder(scheme=None, userinfo=None, host=None, port=None, - path=None, query='a=b+c', fragment=None) - - """ - query = normalizers.normalize_query(compat.urlencode(query_items)) - - return URIBuilder( - scheme=self.scheme, - userinfo=self.userinfo, - host=self.host, - port=self.port, - path=self.path, - query=query, - fragment=self.fragment, - ) - - def add_query(self, query): - """Add a pre-formated query string to the URI. - - .. code-block:: python - - >>> URIBuilder().add_query('a=b&c=d') - URIBuilder(scheme=None, userinfo=None, host=None, port=None, - path=None, query='a=b&c=d', fragment=None) - - """ - return URIBuilder( - scheme=self.scheme, - userinfo=self.userinfo, - host=self.host, - port=self.port, - path=self.path, - query=normalizers.normalize_query(query), - fragment=self.fragment, - ) - - def add_fragment(self, fragment): - """Add a fragment to the URI. - - .. code-block:: python - - >>> URIBuilder().add_fragment('section-2.6.1') - URIBuilder(scheme=None, userinfo=None, host=None, port=None, - path=None, query=None, fragment='section-2.6.1') - - """ - return URIBuilder( - scheme=self.scheme, - userinfo=self.userinfo, - host=self.host, - port=self.port, - path=self.path, - query=self.query, - fragment=normalizers.normalize_fragment(fragment), - ) - - def finalize(self): - """Create a URIReference from our builder. - - .. code-block:: python - - >>> URIBuilder().add_scheme('https').add_host('github.com' - ... ).add_path('sigmavirus24/rfc3986').finalize().unsplit() - 'https://github.com/sigmavirus24/rfc3986' - - >>> URIBuilder().add_scheme('https').add_host('github.com' - ... ).add_path('sigmavirus24/rfc3986').add_credentials( - ... 'sigmavirus24', 'not-re@l').finalize().unsplit() - 'https://sigmavirus24:not-re%40l@github.com/sigmavirus24/rfc3986' - - """ - return uri.URIReference( - self.scheme, - normalizers.normalize_authority( - (self.userinfo, self.host, self.port) - ), - self.path, - self.query, - self.fragment, - ) diff --git a/pipenv/vendor/urllib3/packages/rfc3986/compat.py b/pipenv/vendor/urllib3/packages/rfc3986/compat.py deleted file mode 100644 index 8968c384..00000000 --- a/pipenv/vendor/urllib3/packages/rfc3986/compat.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2014 Rackspace -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Compatibility module for Python 2 and 3 support.""" -import sys - -try: - from urllib.parse import quote as urlquote -except ImportError: # Python 2.x - from urllib import quote as urlquote - -try: - from urllib.parse import urlencode -except ImportError: # Python 2.x - from urllib import urlencode - -__all__ = ( - 'to_bytes', - 'to_str', - 'urlquote', - 'urlencode', -) - -PY3 = (3, 0) <= sys.version_info < (4, 0) -PY2 = (2, 6) <= sys.version_info < (2, 8) - - -if PY3: - unicode = str # Python 3.x - - -def to_str(b, encoding='utf-8'): - """Ensure that b is text in the specified encoding.""" - if hasattr(b, 'decode') and not isinstance(b, unicode): - b = b.decode(encoding) - return b - - -def to_bytes(s, encoding='utf-8'): - """Ensure that s is converted to bytes from the encoding.""" - if hasattr(s, 'encode') and not isinstance(s, bytes): - s = s.encode(encoding) - return s diff --git a/pipenv/vendor/urllib3/packages/rfc3986/exceptions.py b/pipenv/vendor/urllib3/packages/rfc3986/exceptions.py deleted file mode 100644 index da8ca7cb..00000000 --- a/pipenv/vendor/urllib3/packages/rfc3986/exceptions.py +++ /dev/null @@ -1,118 +0,0 @@ -# -*- coding: utf-8 -*- -"""Exceptions module for rfc3986.""" - -from . import compat - - -class RFC3986Exception(Exception): - """Base class for all rfc3986 exception classes.""" - - pass - - -class InvalidAuthority(RFC3986Exception): - """Exception when the authority string is invalid.""" - - def __init__(self, authority): - """Initialize the exception with the invalid authority.""" - super(InvalidAuthority, self).__init__( - u"The authority ({0}) is not valid.".format( - compat.to_str(authority))) - - -class InvalidPort(RFC3986Exception): - """Exception when the port is invalid.""" - - def __init__(self, port): - """Initialize the exception with the invalid port.""" - super(InvalidPort, self).__init__( - 'The port ("{0}") is not valid.'.format(port)) - - -class ResolutionError(RFC3986Exception): - """Exception to indicate a failure to resolve a URI.""" - - def __init__(self, uri): - """Initialize the error with the failed URI.""" - super(ResolutionError, self).__init__( - "{0} is not an absolute URI.".format(uri.unsplit())) - - -class ValidationError(RFC3986Exception): - """Exception raised during Validation of a URI.""" - - pass - - -class MissingComponentError(ValidationError): - """Exception raised when a required component is missing.""" - - def __init__(self, uri, *component_names): - """Initialize the error with the missing component name.""" - verb = 'was' - if len(component_names) > 1: - verb = 'were' - - self.uri = uri - self.components = sorted(component_names) - components = ', '.join(self.components) - super(MissingComponentError, self).__init__( - "{} {} required but missing".format(components, verb), - uri, - self.components, - ) - - -class UnpermittedComponentError(ValidationError): - """Exception raised when a component has an unpermitted value.""" - - def __init__(self, component_name, component_value, allowed_values): - """Initialize the error with the unpermitted component.""" - super(UnpermittedComponentError, self).__init__( - "{} was required to be one of {!r} but was {!r}".format( - component_name, list(sorted(allowed_values)), component_value, - ), - component_name, - component_value, - allowed_values, - ) - self.component_name = component_name - self.component_value = component_value - self.allowed_values = allowed_values - - -class PasswordForbidden(ValidationError): - """Exception raised when a URL has a password in the userinfo section.""" - - def __init__(self, uri): - """Initialize the error with the URI that failed validation.""" - unsplit = getattr(uri, 'unsplit', lambda: uri) - super(PasswordForbidden, self).__init__( - '"{}" contained a password when validation forbade it'.format( - unsplit() - ) - ) - self.uri = uri - - -class InvalidComponentsError(ValidationError): - """Exception raised when one or more components are invalid.""" - - def __init__(self, uri, *component_names): - """Initialize the error with the invalid component name(s).""" - verb = 'was' - if len(component_names) > 1: - verb = 'were' - - self.uri = uri - self.components = sorted(component_names) - components = ', '.join(self.components) - super(InvalidComponentsError, self).__init__( - "{} {} found to be invalid".format(components, verb), - uri, - self.components, - ) - - -class MissingDependencyError(RFC3986Exception): - """Exception raised when an IRI is encoded without the 'idna' module.""" diff --git a/pipenv/vendor/urllib3/packages/rfc3986/iri.py b/pipenv/vendor/urllib3/packages/rfc3986/iri.py deleted file mode 100644 index 9c01fe1c..00000000 --- a/pipenv/vendor/urllib3/packages/rfc3986/iri.py +++ /dev/null @@ -1,147 +0,0 @@ -"""Module containing the implementation of the IRIReference class.""" -# -*- coding: utf-8 -*- -# Copyright (c) 2014 Rackspace -# Copyright (c) 2015 Ian Stapleton Cordasco -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from collections import namedtuple - -from . import compat -from . import exceptions -from . import misc -from . import normalizers -from . import uri - - -try: - import idna -except ImportError: # pragma: no cover - idna = None - - -class IRIReference(namedtuple('IRIReference', misc.URI_COMPONENTS), - uri.URIMixin): - """Immutable object representing a parsed IRI Reference. - - Can be encoded into an URIReference object via the procedure - specified in RFC 3987 Section 3.1 - - .. note:: - The IRI submodule is a new interface and may possibly change in - the future. Check for changes to the interface when upgrading. - """ - - slots = () - - def __new__(cls, scheme, authority, path, query, fragment, - encoding='utf-8'): - """Create a new IRIReference.""" - ref = super(IRIReference, cls).__new__( - cls, - scheme or None, - authority or None, - path or None, - query, - fragment) - ref.encoding = encoding - return ref - - def __eq__(self, other): - """Compare this reference to another.""" - other_ref = other - if isinstance(other, tuple): - other_ref = self.__class__(*other) - elif not isinstance(other, IRIReference): - try: - other_ref = self.__class__.from_string(other) - except TypeError: - raise TypeError( - 'Unable to compare {0}() to {1}()'.format( - type(self).__name__, type(other).__name__)) - - # See http://tools.ietf.org/html/rfc3986#section-6.2 - return tuple(self) == tuple(other_ref) - - def _match_subauthority(self): - return misc.ISUBAUTHORITY_MATCHER.match(self.authority) - - @classmethod - def from_string(cls, iri_string, encoding='utf-8'): - """Parse a IRI reference from the given unicode IRI string. - - :param str iri_string: Unicode IRI to be parsed into a reference. - :param str encoding: The encoding of the string provided - :returns: :class:`IRIReference` or subclass thereof - """ - iri_string = compat.to_str(iri_string, encoding) - - split_iri = misc.IRI_MATCHER.match(iri_string).groupdict() - return cls( - split_iri['scheme'], split_iri['authority'], - normalizers.encode_component(split_iri['path'], encoding), - normalizers.encode_component(split_iri['query'], encoding), - normalizers.encode_component(split_iri['fragment'], encoding), - encoding, - ) - - def encode(self, idna_encoder=None): # noqa: C901 - """Encode an IRIReference into a URIReference instance. - - If the ``idna`` module is installed or the ``rfc3986[idna]`` - extra is used then unicode characters in the IRI host - component will be encoded with IDNA2008. - - :param idna_encoder: - Function that encodes each part of the host component - If not given will raise an exception if the IRI - contains a host component. - :rtype: uri.URIReference - :returns: A URI reference - """ - authority = self.authority - if authority: - if idna_encoder is None: - if idna is None: # pragma: no cover - raise exceptions.MissingDependencyError( - "Could not import the 'idna' module " - "and the IRI hostname requires encoding" - ) - - def idna_encoder(name): - if any(ord(c) > 128 for c in name): - try: - return idna.encode(name.lower(), - strict=True, - std3_rules=True) - except idna.IDNAError: - raise exceptions.InvalidAuthority(self.authority) - return name - - authority = "" - if self.host: - authority = ".".join([compat.to_str(idna_encoder(part)) - for part in self.host.split(".")]) - - if self.userinfo is not None: - authority = (normalizers.encode_component( - self.userinfo, self.encoding) + '@' + authority) - - if self.port is not None: - authority += ":" + str(self.port) - - return uri.URIReference(self.scheme, - authority, - path=self.path, - query=self.query, - fragment=self.fragment, - encoding=self.encoding) diff --git a/pipenv/vendor/urllib3/packages/rfc3986/misc.py b/pipenv/vendor/urllib3/packages/rfc3986/misc.py deleted file mode 100644 index 00f9f3b9..00000000 --- a/pipenv/vendor/urllib3/packages/rfc3986/misc.py +++ /dev/null @@ -1,146 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2014 Rackspace -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -Module containing compiled regular expressions and constants. - -This module contains important constants, patterns, and compiled regular -expressions for parsing and validating URIs and their components. -""" - -import re - -from . import abnf_regexp - -# These are enumerated for the named tuple used as a superclass of -# URIReference -URI_COMPONENTS = ['scheme', 'authority', 'path', 'query', 'fragment'] - -important_characters = { - 'generic_delimiters': abnf_regexp.GENERIC_DELIMITERS, - 'sub_delimiters': abnf_regexp.SUB_DELIMITERS, - # We need to escape the '*' in this case - 're_sub_delimiters': abnf_regexp.SUB_DELIMITERS_RE, - 'unreserved_chars': abnf_regexp.UNRESERVED_CHARS, - # We need to escape the '-' in this case: - 're_unreserved': abnf_regexp.UNRESERVED_RE, -} - -# For details about delimiters and reserved characters, see: -# http://tools.ietf.org/html/rfc3986#section-2.2 -GENERIC_DELIMITERS = abnf_regexp.GENERIC_DELIMITERS_SET -SUB_DELIMITERS = abnf_regexp.SUB_DELIMITERS_SET -RESERVED_CHARS = abnf_regexp.RESERVED_CHARS_SET -# For details about unreserved characters, see: -# http://tools.ietf.org/html/rfc3986#section-2.3 -UNRESERVED_CHARS = abnf_regexp.UNRESERVED_CHARS_SET -NON_PCT_ENCODED = abnf_regexp.NON_PCT_ENCODED_SET - -URI_MATCHER = re.compile(abnf_regexp.URL_PARSING_RE) - -SUBAUTHORITY_MATCHER = re.compile(( - '^(?:(?P<userinfo>{0})@)?' # userinfo - '(?P<host>{1})' # host - ':?(?P<port>{2})?$' # port - ).format(abnf_regexp.USERINFO_RE, - abnf_regexp.HOST_PATTERN, - abnf_regexp.PORT_RE)) - - -HOST_MATCHER = re.compile('^' + abnf_regexp.HOST_RE + '$') -IPv4_MATCHER = re.compile('^' + abnf_regexp.IPv4_RE + '$') -IPv6_MATCHER = re.compile(r'^\[' + abnf_regexp.IPv6_ADDRZ_RFC4007_RE + r'\]$') - -# Used by host validator -IPv6_NO_RFC4007_MATCHER = re.compile(r'^\[%s\]$' % ( - abnf_regexp.IPv6_ADDRZ_RE -)) - -# Matcher used to validate path components -PATH_MATCHER = re.compile(abnf_regexp.PATH_RE) - - -# ################################## -# Query and Fragment Matcher Section -# ################################## - -QUERY_MATCHER = re.compile(abnf_regexp.QUERY_RE) - -FRAGMENT_MATCHER = QUERY_MATCHER - -# Scheme validation, see: http://tools.ietf.org/html/rfc3986#section-3.1 -SCHEME_MATCHER = re.compile('^{0}$'.format(abnf_regexp.SCHEME_RE)) - -RELATIVE_REF_MATCHER = re.compile(r'^%s(\?%s)?(#%s)?$' % ( - abnf_regexp.RELATIVE_PART_RE, - abnf_regexp.QUERY_RE, - abnf_regexp.FRAGMENT_RE, -)) - -# See http://tools.ietf.org/html/rfc3986#section-4.3 -ABSOLUTE_URI_MATCHER = re.compile(r'^%s:%s(\?%s)?$' % ( - abnf_regexp.COMPONENT_PATTERN_DICT['scheme'], - abnf_regexp.HIER_PART_RE, - abnf_regexp.QUERY_RE[1:-1], -)) - -# ############### -# IRIs / RFC 3987 -# ############### - -IRI_MATCHER = re.compile(abnf_regexp.URL_PARSING_RE, re.UNICODE) - -ISUBAUTHORITY_MATCHER = re.compile(( - u'^(?:(?P<userinfo>{0})@)?' # iuserinfo - u'(?P<host>{1})' # ihost - u':?(?P<port>{2})?$' # port - ).format(abnf_regexp.IUSERINFO_RE, - abnf_regexp.IHOST_RE, - abnf_regexp.PORT_RE), re.UNICODE) - - -IHOST_MATCHER = re.compile('^' + abnf_regexp.IHOST_RE + '$', re.UNICODE) - -IPATH_MATCHER = re.compile(abnf_regexp.IPATH_RE, re.UNICODE) - -IQUERY_MATCHER = re.compile(abnf_regexp.IQUERY_RE, re.UNICODE) - -IFRAGMENT_MATCHER = re.compile(abnf_regexp.IFRAGMENT_RE, re.UNICODE) - - -RELATIVE_IRI_MATCHER = re.compile(u'^%s(?:\\?%s)?(?:%s)?$' % ( - abnf_regexp.IRELATIVE_PART_RE, - abnf_regexp.IQUERY_RE, - abnf_regexp.IFRAGMENT_RE -), re.UNICODE) - -ABSOLUTE_IRI_MATCHER = re.compile(u'^%s:%s(?:\\?%s)?$' % ( - abnf_regexp.COMPONENT_PATTERN_DICT['scheme'], - abnf_regexp.IHIER_PART_RE, - abnf_regexp.IQUERY_RE[1:-1] -), re.UNICODE) - - -# Path merger as defined in http://tools.ietf.org/html/rfc3986#section-5.2.3 -def merge_paths(base_uri, relative_path): - """Merge a base URI's path with a relative URI's path.""" - if base_uri.path is None and base_uri.authority is not None: - return '/' + relative_path - else: - path = base_uri.path or '' - index = path.rfind('/') - return path[:index] + '/' + relative_path - - -UseExisting = object() diff --git a/pipenv/vendor/urllib3/packages/rfc3986/normalizers.py b/pipenv/vendor/urllib3/packages/rfc3986/normalizers.py deleted file mode 100644 index 2eb1bb36..00000000 --- a/pipenv/vendor/urllib3/packages/rfc3986/normalizers.py +++ /dev/null @@ -1,167 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2014 Rackspace -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Module with functions to normalize components.""" -import re - -from . import compat -from . import misc - - -def normalize_scheme(scheme): - """Normalize the scheme component.""" - return scheme.lower() - - -def normalize_authority(authority): - """Normalize an authority tuple to a string.""" - userinfo, host, port = authority - result = '' - if userinfo: - result += normalize_percent_characters(userinfo) + '@' - if host: - result += normalize_host(host) - if port: - result += ':' + port - return result - - -def normalize_username(username): - """Normalize a username to make it safe to include in userinfo.""" - return compat.urlquote(username) - - -def normalize_password(password): - """Normalize a password to make safe for userinfo.""" - return compat.urlquote(password) - - -def normalize_host(host): - """Normalize a host string.""" - if misc.IPv6_MATCHER.match(host): - percent = host.find('%') - if percent != -1: - percent_25 = host.find('%25') - - # Replace RFC 4007 IPv6 Zone ID delimiter '%' with '%25' - # from RFC 6874. If the host is '[<IPv6 addr>%25]' then we - # assume RFC 4007 and normalize to '[<IPV6 addr>%2525]' - if percent_25 == -1 or percent < percent_25 or \ - (percent == percent_25 and percent_25 == len(host) - 4): - host = host.replace('%', '%25', 1) - - # Don't normalize the casing of the Zone ID - return host[:percent].lower() + host[percent:] - - return host.lower() - - -def normalize_path(path): - """Normalize the path string.""" - if not path: - return path - - path = normalize_percent_characters(path) - return remove_dot_segments(path) - - -def normalize_query(query): - """Normalize the query string.""" - if not query: - return query - return normalize_percent_characters(query) - - -def normalize_fragment(fragment): - """Normalize the fragment string.""" - if not fragment: - return fragment - return normalize_percent_characters(fragment) - - -PERCENT_MATCHER = re.compile('%[A-Fa-f0-9]{2}') - - -def normalize_percent_characters(s): - """All percent characters should be upper-cased. - - For example, ``"%3afoo%DF%ab"`` should be turned into ``"%3Afoo%DF%AB"``. - """ - matches = set(PERCENT_MATCHER.findall(s)) - for m in matches: - if not m.isupper(): - s = s.replace(m, m.upper()) - return s - - -def remove_dot_segments(s): - """Remove dot segments from the string. - - See also Section 5.2.4 of :rfc:`3986`. - """ - # See http://tools.ietf.org/html/rfc3986#section-5.2.4 for pseudo-code - segments = s.split('/') # Turn the path into a list of segments - output = [] # Initialize the variable to use to store output - - for segment in segments: - # '.' is the current directory, so ignore it, it is superfluous - if segment == '.': - continue - # Anything other than '..', should be appended to the output - elif segment != '..': - output.append(segment) - # In this case segment == '..', if we can, we should pop the last - # element - elif output: - output.pop() - - # If the path starts with '/' and the output is empty or the first string - # is non-empty - if s.startswith('/') and (not output or output[0]): - output.insert(0, '') - - # If the path starts with '/.' or '/..' ensure we add one more empty - # string to add a trailing '/' - if s.endswith(('/.', '/..')): - output.append('') - - return '/'.join(output) - - -def encode_component(uri_component, encoding): - """Encode the specific component in the provided encoding.""" - if uri_component is None: - return uri_component - - # Try to see if the component we're encoding is already percent-encoded - # so we can skip all '%' characters but still encode all others. - percent_encodings = len(PERCENT_MATCHER.findall( - compat.to_str(uri_component, encoding))) - - uri_bytes = compat.to_bytes(uri_component, encoding) - is_percent_encoded = percent_encodings == uri_bytes.count(b'%') - - encoded_uri = bytearray() - - for i in range(0, len(uri_bytes)): - # Will return a single character bytestring on both Python 2 & 3 - byte = uri_bytes[i:i+1] - byte_ord = ord(byte) - if ((is_percent_encoded and byte == b'%') - or (byte_ord < 128 and byte.decode() in misc.NON_PCT_ENCODED)): - encoded_uri.extend(byte) - continue - encoded_uri.extend('%{0:02x}'.format(byte_ord).encode().upper()) - - return encoded_uri.decode(encoding) diff --git a/pipenv/vendor/urllib3/packages/rfc3986/parseresult.py b/pipenv/vendor/urllib3/packages/rfc3986/parseresult.py deleted file mode 100644 index 0a734566..00000000 --- a/pipenv/vendor/urllib3/packages/rfc3986/parseresult.py +++ /dev/null @@ -1,385 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015 Ian Stapleton Cordasco -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Module containing the urlparse compatibility logic.""" -from collections import namedtuple - -from . import compat -from . import exceptions -from . import misc -from . import normalizers -from . import uri - -__all__ = ('ParseResult', 'ParseResultBytes') - -PARSED_COMPONENTS = ('scheme', 'userinfo', 'host', 'port', 'path', 'query', - 'fragment') - - -class ParseResultMixin(object): - def _generate_authority(self, attributes): - # I swear I did not align the comparisons below. That's just how they - # happened to align based on pep8 and attribute lengths. - userinfo, host, port = (attributes[p] - for p in ('userinfo', 'host', 'port')) - if (self.userinfo != userinfo or - self.host != host or - self.port != port): - if port: - port = '{0}'.format(port) - return normalizers.normalize_authority( - (compat.to_str(userinfo, self.encoding), - compat.to_str(host, self.encoding), - port) - ) - return self.authority - - def geturl(self): - """Shim to match the standard library method.""" - return self.unsplit() - - @property - def hostname(self): - """Shim to match the standard library.""" - return self.host - - @property - def netloc(self): - """Shim to match the standard library.""" - return self.authority - - @property - def params(self): - """Shim to match the standard library.""" - return self.query - - -class ParseResult(namedtuple('ParseResult', PARSED_COMPONENTS), - ParseResultMixin): - """Implementation of urlparse compatibility class. - - This uses the URIReference logic to handle compatibility with the - urlparse.ParseResult class. - """ - - slots = () - - def __new__(cls, scheme, userinfo, host, port, path, query, fragment, - uri_ref, encoding='utf-8'): - """Create a new ParseResult.""" - parse_result = super(ParseResult, cls).__new__( - cls, - scheme or None, - userinfo or None, - host, - port or None, - path or None, - query, - fragment) - parse_result.encoding = encoding - parse_result.reference = uri_ref - return parse_result - - @classmethod - def from_parts(cls, scheme=None, userinfo=None, host=None, port=None, - path=None, query=None, fragment=None, encoding='utf-8'): - """Create a ParseResult instance from its parts.""" - authority = '' - if userinfo is not None: - authority += userinfo + '@' - if host is not None: - authority += host - if port is not None: - authority += ':{0}'.format(port) - uri_ref = uri.URIReference(scheme=scheme, - authority=authority, - path=path, - query=query, - fragment=fragment, - encoding=encoding).normalize() - userinfo, host, port = authority_from(uri_ref, strict=True) - return cls(scheme=uri_ref.scheme, - userinfo=userinfo, - host=host, - port=port, - path=uri_ref.path, - query=uri_ref.query, - fragment=uri_ref.fragment, - uri_ref=uri_ref, - encoding=encoding) - - @classmethod - def from_string(cls, uri_string, encoding='utf-8', strict=True, - lazy_normalize=True): - """Parse a URI from the given unicode URI string. - - :param str uri_string: Unicode URI to be parsed into a reference. - :param str encoding: The encoding of the string provided - :param bool strict: Parse strictly according to :rfc:`3986` if True. - If False, parse similarly to the standard library's urlparse - function. - :returns: :class:`ParseResult` or subclass thereof - """ - reference = uri.URIReference.from_string(uri_string, encoding) - if not lazy_normalize: - reference = reference.normalize() - userinfo, host, port = authority_from(reference, strict) - - return cls(scheme=reference.scheme, - userinfo=userinfo, - host=host, - port=port, - path=reference.path, - query=reference.query, - fragment=reference.fragment, - uri_ref=reference, - encoding=encoding) - - @property - def authority(self): - """Return the normalized authority.""" - return self.reference.authority - - def copy_with(self, scheme=misc.UseExisting, userinfo=misc.UseExisting, - host=misc.UseExisting, port=misc.UseExisting, - path=misc.UseExisting, query=misc.UseExisting, - fragment=misc.UseExisting): - """Create a copy of this instance replacing with specified parts.""" - attributes = zip(PARSED_COMPONENTS, - (scheme, userinfo, host, port, path, query, fragment)) - attrs_dict = {} - for name, value in attributes: - if value is misc.UseExisting: - value = getattr(self, name) - attrs_dict[name] = value - authority = self._generate_authority(attrs_dict) - ref = self.reference.copy_with(scheme=attrs_dict['scheme'], - authority=authority, - path=attrs_dict['path'], - query=attrs_dict['query'], - fragment=attrs_dict['fragment']) - return ParseResult(uri_ref=ref, encoding=self.encoding, **attrs_dict) - - def encode(self, encoding=None): - """Convert to an instance of ParseResultBytes.""" - encoding = encoding or self.encoding - attrs = dict( - zip(PARSED_COMPONENTS, - (attr.encode(encoding) if hasattr(attr, 'encode') else attr - for attr in self))) - return ParseResultBytes( - uri_ref=self.reference, - encoding=encoding, - **attrs - ) - - def unsplit(self, use_idna=False): - """Create a URI string from the components. - - :returns: The parsed URI reconstituted as a string. - :rtype: str - """ - parse_result = self - if use_idna and self.host: - hostbytes = self.host.encode('idna') - host = hostbytes.decode(self.encoding) - parse_result = self.copy_with(host=host) - return parse_result.reference.unsplit() - - -class ParseResultBytes(namedtuple('ParseResultBytes', PARSED_COMPONENTS), - ParseResultMixin): - """Compatibility shim for the urlparse.ParseResultBytes object.""" - - def __new__(cls, scheme, userinfo, host, port, path, query, fragment, - uri_ref, encoding='utf-8', lazy_normalize=True): - """Create a new ParseResultBytes instance.""" - parse_result = super(ParseResultBytes, cls).__new__( - cls, - scheme or None, - userinfo or None, - host, - port or None, - path or None, - query or None, - fragment or None) - parse_result.encoding = encoding - parse_result.reference = uri_ref - parse_result.lazy_normalize = lazy_normalize - return parse_result - - @classmethod - def from_parts(cls, scheme=None, userinfo=None, host=None, port=None, - path=None, query=None, fragment=None, encoding='utf-8', - lazy_normalize=True): - """Create a ParseResult instance from its parts.""" - authority = '' - if userinfo is not None: - authority += userinfo + '@' - if host is not None: - authority += host - if port is not None: - authority += ':{0}'.format(int(port)) - uri_ref = uri.URIReference(scheme=scheme, - authority=authority, - path=path, - query=query, - fragment=fragment, - encoding=encoding) - if not lazy_normalize: - uri_ref = uri_ref.normalize() - to_bytes = compat.to_bytes - userinfo, host, port = authority_from(uri_ref, strict=True) - return cls(scheme=to_bytes(scheme, encoding), - userinfo=to_bytes(userinfo, encoding), - host=to_bytes(host, encoding), - port=port, - path=to_bytes(path, encoding), - query=to_bytes(query, encoding), - fragment=to_bytes(fragment, encoding), - uri_ref=uri_ref, - encoding=encoding, - lazy_normalize=lazy_normalize) - - @classmethod - def from_string(cls, uri_string, encoding='utf-8', strict=True, - lazy_normalize=True): - """Parse a URI from the given unicode URI string. - - :param str uri_string: Unicode URI to be parsed into a reference. - :param str encoding: The encoding of the string provided - :param bool strict: Parse strictly according to :rfc:`3986` if True. - If False, parse similarly to the standard library's urlparse - function. - :returns: :class:`ParseResultBytes` or subclass thereof - """ - reference = uri.URIReference.from_string(uri_string, encoding) - if not lazy_normalize: - reference = reference.normalize() - userinfo, host, port = authority_from(reference, strict) - - to_bytes = compat.to_bytes - return cls(scheme=to_bytes(reference.scheme, encoding), - userinfo=to_bytes(userinfo, encoding), - host=to_bytes(host, encoding), - port=port, - path=to_bytes(reference.path, encoding), - query=to_bytes(reference.query, encoding), - fragment=to_bytes(reference.fragment, encoding), - uri_ref=reference, - encoding=encoding, - lazy_normalize=lazy_normalize) - - @property - def authority(self): - """Return the normalized authority.""" - return self.reference.authority.encode(self.encoding) - - def copy_with(self, scheme=misc.UseExisting, userinfo=misc.UseExisting, - host=misc.UseExisting, port=misc.UseExisting, - path=misc.UseExisting, query=misc.UseExisting, - fragment=misc.UseExisting, lazy_normalize=True): - """Create a copy of this instance replacing with specified parts.""" - attributes = zip(PARSED_COMPONENTS, - (scheme, userinfo, host, port, path, query, fragment)) - attrs_dict = {} - for name, value in attributes: - if value is misc.UseExisting: - value = getattr(self, name) - if not isinstance(value, bytes) and hasattr(value, 'encode'): - value = value.encode(self.encoding) - attrs_dict[name] = value - authority = self._generate_authority(attrs_dict) - to_str = compat.to_str - ref = self.reference.copy_with( - scheme=to_str(attrs_dict['scheme'], self.encoding), - authority=to_str(authority, self.encoding), - path=to_str(attrs_dict['path'], self.encoding), - query=to_str(attrs_dict['query'], self.encoding), - fragment=to_str(attrs_dict['fragment'], self.encoding) - ) - if not lazy_normalize: - ref = ref.normalize() - return ParseResultBytes( - uri_ref=ref, - encoding=self.encoding, - lazy_normalize=lazy_normalize, - **attrs_dict - ) - - def unsplit(self, use_idna=False): - """Create a URI bytes object from the components. - - :returns: The parsed URI reconstituted as a string. - :rtype: bytes - """ - parse_result = self - if use_idna and self.host: - # self.host is bytes, to encode to idna, we need to decode it - # first - host = self.host.decode(self.encoding) - hostbytes = host.encode('idna') - parse_result = self.copy_with(host=hostbytes) - if self.lazy_normalize: - parse_result = parse_result.copy_with(lazy_normalize=False) - uri = parse_result.reference.unsplit() - return uri.encode(self.encoding) - - -def split_authority(authority): - # Initialize our expected return values - userinfo = host = port = None - # Initialize an extra var we may need to use - extra_host = None - # Set-up rest in case there is no userinfo portion - rest = authority - - if '@' in authority: - userinfo, rest = authority.rsplit('@', 1) - - # Handle IPv6 host addresses - if rest.startswith('['): - host, rest = rest.split(']', 1) - host += ']' - - if ':' in rest: - extra_host, port = rest.split(':', 1) - elif not host and rest: - host = rest - - if extra_host and not host: - host = extra_host - - return userinfo, host, port - - -def authority_from(reference, strict): - try: - subauthority = reference.authority_info() - except exceptions.InvalidAuthority: - if strict: - raise - userinfo, host, port = split_authority(reference.authority) - else: - # Thanks to Richard Barrell for this idea: - # https://twitter.com/0x2ba22e11/status/617338811975139328 - userinfo, host, port = (subauthority.get(p) - for p in ('userinfo', 'host', 'port')) - - if port: - try: - port = int(port) - except ValueError: - raise exceptions.InvalidPort(port) - return userinfo, host, port diff --git a/pipenv/vendor/urllib3/packages/rfc3986/uri.py b/pipenv/vendor/urllib3/packages/rfc3986/uri.py deleted file mode 100644 index d1d71505..00000000 --- a/pipenv/vendor/urllib3/packages/rfc3986/uri.py +++ /dev/null @@ -1,153 +0,0 @@ -"""Module containing the implementation of the URIReference class.""" -# -*- coding: utf-8 -*- -# Copyright (c) 2014 Rackspace -# Copyright (c) 2015 Ian Stapleton Cordasco -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from collections import namedtuple - -from . import compat -from . import misc -from . import normalizers -from ._mixin import URIMixin - - -class URIReference(namedtuple('URIReference', misc.URI_COMPONENTS), URIMixin): - """Immutable object representing a parsed URI Reference. - - .. note:: - - This class is not intended to be directly instantiated by the user. - - This object exposes attributes for the following components of a - URI: - - - scheme - - authority - - path - - query - - fragment - - .. attribute:: scheme - - The scheme that was parsed for the URI Reference. For example, - ``http``, ``https``, ``smtp``, ``imap``, etc. - - .. attribute:: authority - - Component of the URI that contains the user information, host, - and port sub-components. For example, - ``google.com``, ``127.0.0.1:5000``, ``username@[::1]``, - ``username:password@example.com:443``, etc. - - .. attribute:: path - - The path that was parsed for the given URI Reference. For example, - ``/``, ``/index.php``, etc. - - .. attribute:: query - - The query component for a given URI Reference. For example, ``a=b``, - ``a=b%20c``, ``a=b+c``, ``a=b,c=d,e=%20f``, etc. - - .. attribute:: fragment - - The fragment component of a URI. For example, ``section-3.1``. - - This class also provides extra attributes for easier access to information - like the subcomponents of the authority component. - - .. attribute:: userinfo - - The user information parsed from the authority. - - .. attribute:: host - - The hostname, IPv4, or IPv6 adddres parsed from the authority. - - .. attribute:: port - - The port parsed from the authority. - """ - - slots = () - - def __new__(cls, scheme, authority, path, query, fragment, - encoding='utf-8'): - """Create a new URIReference.""" - ref = super(URIReference, cls).__new__( - cls, - scheme or None, - authority or None, - path or None, - query, - fragment) - ref.encoding = encoding - return ref - - __hash__ = tuple.__hash__ - - def __eq__(self, other): - """Compare this reference to another.""" - other_ref = other - if isinstance(other, tuple): - other_ref = URIReference(*other) - elif not isinstance(other, URIReference): - try: - other_ref = URIReference.from_string(other) - except TypeError: - raise TypeError( - 'Unable to compare URIReference() to {0}()'.format( - type(other).__name__)) - - # See http://tools.ietf.org/html/rfc3986#section-6.2 - naive_equality = tuple(self) == tuple(other_ref) - return naive_equality or self.normalized_equality(other_ref) - - def normalize(self): - """Normalize this reference as described in Section 6.2.2. - - This is not an in-place normalization. Instead this creates a new - URIReference. - - :returns: A new reference object with normalized components. - :rtype: URIReference - """ - # See http://tools.ietf.org/html/rfc3986#section-6.2.2 for logic in - # this method. - return URIReference(normalizers.normalize_scheme(self.scheme or ''), - normalizers.normalize_authority( - (self.userinfo, self.host, self.port)), - normalizers.normalize_path(self.path or ''), - normalizers.normalize_query(self.query), - normalizers.normalize_fragment(self.fragment), - self.encoding) - - @classmethod - def from_string(cls, uri_string, encoding='utf-8'): - """Parse a URI reference from the given unicode URI string. - - :param str uri_string: Unicode URI to be parsed into a reference. - :param str encoding: The encoding of the string provided - :returns: :class:`URIReference` or subclass thereof - """ - uri_string = compat.to_str(uri_string, encoding) - - split_uri = misc.URI_MATCHER.match(uri_string).groupdict() - return cls( - split_uri['scheme'], split_uri['authority'], - normalizers.encode_component(split_uri['path'], encoding), - normalizers.encode_component(split_uri['query'], encoding), - normalizers.encode_component(split_uri['fragment'], encoding), - encoding, - ) diff --git a/pipenv/vendor/urllib3/packages/rfc3986/validators.py b/pipenv/vendor/urllib3/packages/rfc3986/validators.py deleted file mode 100644 index 7fc97215..00000000 --- a/pipenv/vendor/urllib3/packages/rfc3986/validators.py +++ /dev/null @@ -1,450 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017 Ian Stapleton Cordasco -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Module containing the validation logic for rfc3986.""" -from . import exceptions -from . import misc -from . import normalizers - - -class Validator(object): - """Object used to configure validation of all objects in rfc3986. - - .. versionadded:: 1.0 - - Example usage:: - - >>> from rfc3986 import api, validators - >>> uri = api.uri_reference('https://github.com/') - >>> validator = validators.Validator().require_presence_of( - ... 'scheme', 'host', 'path', - ... ).allow_schemes( - ... 'http', 'https', - ... ).allow_hosts( - ... '127.0.0.1', 'github.com', - ... ) - >>> validator.validate(uri) - >>> invalid_uri = rfc3986.uri_reference('imap://mail.google.com') - >>> validator.validate(invalid_uri) - Traceback (most recent call last): - ... - rfc3986.exceptions.MissingComponentError: ('path was required but - missing', URIReference(scheme=u'imap', authority=u'mail.google.com', - path=None, query=None, fragment=None), ['path']) - - """ - - COMPONENT_NAMES = frozenset([ - 'scheme', - 'userinfo', - 'host', - 'port', - 'path', - 'query', - 'fragment', - ]) - - def __init__(self): - """Initialize our default validations.""" - self.allowed_schemes = set() - self.allowed_hosts = set() - self.allowed_ports = set() - self.allow_password = True - self.required_components = { - 'scheme': False, - 'userinfo': False, - 'host': False, - 'port': False, - 'path': False, - 'query': False, - 'fragment': False, - } - self.validated_components = self.required_components.copy() - - def allow_schemes(self, *schemes): - """Require the scheme to be one of the provided schemes. - - .. versionadded:: 1.0 - - :param schemes: - Schemes, without ``://`` that are allowed. - :returns: - The validator instance. - :rtype: - Validator - """ - for scheme in schemes: - self.allowed_schemes.add(normalizers.normalize_scheme(scheme)) - return self - - def allow_hosts(self, *hosts): - """Require the host to be one of the provided hosts. - - .. versionadded:: 1.0 - - :param hosts: - Hosts that are allowed. - :returns: - The validator instance. - :rtype: - Validator - """ - for host in hosts: - self.allowed_hosts.add(normalizers.normalize_host(host)) - return self - - def allow_ports(self, *ports): - """Require the port to be one of the provided ports. - - .. versionadded:: 1.0 - - :param ports: - Ports that are allowed. - :returns: - The validator instance. - :rtype: - Validator - """ - for port in ports: - port_int = int(port, base=10) - if 0 <= port_int <= 65535: - self.allowed_ports.add(port) - return self - - def allow_use_of_password(self): - """Allow passwords to be present in the URI. - - .. versionadded:: 1.0 - - :returns: - The validator instance. - :rtype: - Validator - """ - self.allow_password = True - return self - - def forbid_use_of_password(self): - """Prevent passwords from being included in the URI. - - .. versionadded:: 1.0 - - :returns: - The validator instance. - :rtype: - Validator - """ - self.allow_password = False - return self - - def check_validity_of(self, *components): - """Check the validity of the components provided. - - This can be specified repeatedly. - - .. versionadded:: 1.1 - - :param components: - Names of components from :attr:`Validator.COMPONENT_NAMES`. - :returns: - The validator instance. - :rtype: - Validator - """ - components = [c.lower() for c in components] - for component in components: - if component not in self.COMPONENT_NAMES: - raise ValueError( - '"{}" is not a valid component'.format(component) - ) - self.validated_components.update({ - component: True for component in components - }) - return self - - def require_presence_of(self, *components): - """Require the components provided. - - This can be specified repeatedly. - - .. versionadded:: 1.0 - - :param components: - Names of components from :attr:`Validator.COMPONENT_NAMES`. - :returns: - The validator instance. - :rtype: - Validator - """ - components = [c.lower() for c in components] - for component in components: - if component not in self.COMPONENT_NAMES: - raise ValueError( - '"{}" is not a valid component'.format(component) - ) - self.required_components.update({ - component: True for component in components - }) - return self - - def validate(self, uri): - """Check a URI for conditions specified on this validator. - - .. versionadded:: 1.0 - - :param uri: - Parsed URI to validate. - :type uri: - rfc3986.uri.URIReference - :raises MissingComponentError: - When a required component is missing. - :raises UnpermittedComponentError: - When a component is not one of those allowed. - :raises PasswordForbidden: - When a password is present in the userinfo component but is - not permitted by configuration. - :raises InvalidComponentsError: - When a component was found to be invalid. - """ - if not self.allow_password: - check_password(uri) - - required_components = [ - component - for component, required in self.required_components.items() - if required - ] - validated_components = [ - component - for component, required in self.validated_components.items() - if required - ] - if required_components: - ensure_required_components_exist(uri, required_components) - if validated_components: - ensure_components_are_valid(uri, validated_components) - - ensure_one_of(self.allowed_schemes, uri, 'scheme') - ensure_one_of(self.allowed_hosts, uri, 'host') - ensure_one_of(self.allowed_ports, uri, 'port') - - -def check_password(uri): - """Assert that there is no password present in the uri.""" - userinfo = uri.userinfo - if not userinfo: - return - credentials = userinfo.split(':', 1) - if len(credentials) <= 1: - return - raise exceptions.PasswordForbidden(uri) - - -def ensure_one_of(allowed_values, uri, attribute): - """Assert that the uri's attribute is one of the allowed values.""" - value = getattr(uri, attribute) - if value is not None and allowed_values and value not in allowed_values: - raise exceptions.UnpermittedComponentError( - attribute, value, allowed_values, - ) - - -def ensure_required_components_exist(uri, required_components): - """Assert that all required components are present in the URI.""" - missing_components = sorted([ - component - for component in required_components - if getattr(uri, component) is None - ]) - if missing_components: - raise exceptions.MissingComponentError(uri, *missing_components) - - -def is_valid(value, matcher, require): - """Determine if a value is valid based on the provided matcher. - - :param str value: - Value to validate. - :param matcher: - Compiled regular expression to use to validate the value. - :param require: - Whether or not the value is required. - """ - if require: - return (value is not None - and matcher.match(value)) - - # require is False and value is not None - return value is None or matcher.match(value) - - -def authority_is_valid(authority, host=None, require=False): - """Determine if the authority string is valid. - - :param str authority: - The authority to validate. - :param str host: - (optional) The host portion of the authority to validate. - :param bool require: - (optional) Specify if authority must not be None. - :returns: - ``True`` if valid, ``False`` otherwise - :rtype: - bool - """ - validated = is_valid(authority, misc.SUBAUTHORITY_MATCHER, require) - if validated and host is not None: - return host_is_valid(host, require) - return validated - - -def host_is_valid(host, require=False): - """Determine if the host string is valid. - - :param str host: - The host to validate. - :param bool require: - (optional) Specify if host must not be None. - :returns: - ``True`` if valid, ``False`` otherwise - :rtype: - bool - """ - validated = is_valid(host, misc.HOST_MATCHER, require) - if validated and host is not None and misc.IPv4_MATCHER.match(host): - return valid_ipv4_host_address(host) - elif validated and host is not None and misc.IPv6_MATCHER.match(host): - return misc.IPv6_NO_RFC4007_MATCHER.match(host) is not None - return validated - - -def scheme_is_valid(scheme, require=False): - """Determine if the scheme is valid. - - :param str scheme: - The scheme string to validate. - :param bool require: - (optional) Set to ``True`` to require the presence of a scheme. - :returns: - ``True`` if the scheme is valid. ``False`` otherwise. - :rtype: - bool - """ - return is_valid(scheme, misc.SCHEME_MATCHER, require) - - -def path_is_valid(path, require=False): - """Determine if the path component is valid. - - :param str path: - The path string to validate. - :param bool require: - (optional) Set to ``True`` to require the presence of a path. - :returns: - ``True`` if the path is valid. ``False`` otherwise. - :rtype: - bool - """ - return is_valid(path, misc.PATH_MATCHER, require) - - -def query_is_valid(query, require=False): - """Determine if the query component is valid. - - :param str query: - The query string to validate. - :param bool require: - (optional) Set to ``True`` to require the presence of a query. - :returns: - ``True`` if the query is valid. ``False`` otherwise. - :rtype: - bool - """ - return is_valid(query, misc.QUERY_MATCHER, require) - - -def fragment_is_valid(fragment, require=False): - """Determine if the fragment component is valid. - - :param str fragment: - The fragment string to validate. - :param bool require: - (optional) Set to ``True`` to require the presence of a fragment. - :returns: - ``True`` if the fragment is valid. ``False`` otherwise. - :rtype: - bool - """ - return is_valid(fragment, misc.FRAGMENT_MATCHER, require) - - -def valid_ipv4_host_address(host): - """Determine if the given host is a valid IPv4 address.""" - # If the host exists, and it might be IPv4, check each byte in the - # address. - return all([0 <= int(byte, base=10) <= 255 for byte in host.split('.')]) - - -_COMPONENT_VALIDATORS = { - 'scheme': scheme_is_valid, - 'path': path_is_valid, - 'query': query_is_valid, - 'fragment': fragment_is_valid, -} - -_SUBAUTHORITY_VALIDATORS = set(['userinfo', 'host', 'port']) - - -def subauthority_component_is_valid(uri, component): - """Determine if the userinfo, host, and port are valid.""" - try: - subauthority_dict = uri.authority_info() - except exceptions.InvalidAuthority: - return False - - # If we can parse the authority into sub-components and we're not - # validating the port, we can assume it's valid. - if component == 'host': - return host_is_valid(subauthority_dict['host']) - elif component != 'port': - return True - - try: - port = int(subauthority_dict['port']) - except TypeError: - # If the port wasn't provided it'll be None and int(None) raises a - # TypeError - return True - - return (0 <= port <= 65535) - - -def ensure_components_are_valid(uri, validated_components): - """Assert that all components are valid in the URI.""" - invalid_components = set([]) - for component in validated_components: - if component in _SUBAUTHORITY_VALIDATORS: - if not subauthority_component_is_valid(uri, component): - invalid_components.add(component) - # Python's peephole optimizer means that while this continue *is* - # actually executed, coverage.py cannot detect that. See also, - # https://bitbucket.org/ned/coveragepy/issues/198/continue-marked-as-not-covered - continue # nocov: Python 2.7, 3.3, 3.4 - - validator = _COMPONENT_VALIDATORS[component] - if not validator(getattr(uri, component)): - invalid_components.add(component) - - if invalid_components: - raise exceptions.InvalidComponentsError(uri, *invalid_components) diff --git a/pipenv/vendor/urllib3/packages/six.py b/pipenv/vendor/urllib3/packages/six.py index 190c0239..ba50acb0 100644 --- a/pipenv/vendor/urllib3/packages/six.py +++ b/pipenv/vendor/urllib3/packages/six.py @@ -1,6 +1,4 @@ -"""Utilities for writing code that runs on Python 2 and 3""" - -# Copyright (c) 2010-2015 Benjamin Peterson +# Copyright (c) 2010-2020 Benjamin Peterson # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -20,6 +18,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +"""Utilities for writing code that runs on Python 2 and 3""" + from __future__ import absolute_import import functools @@ -29,7 +29,7 @@ import sys import types __author__ = "Benjamin Peterson <benjamin@python.org>" -__version__ = "1.10.0" +__version__ = "1.16.0" # Useful for very coarse version differentiation. @@ -38,15 +38,15 @@ PY3 = sys.version_info[0] == 3 PY34 = sys.version_info[0:2] >= (3, 4) if PY3: - string_types = str, - integer_types = int, - class_types = type, + string_types = (str,) + integer_types = (int,) + class_types = (type,) text_type = str binary_type = bytes MAXSIZE = sys.maxsize else: - string_types = basestring, + string_types = (basestring,) integer_types = (int, long) class_types = (type, types.ClassType) text_type = unicode @@ -58,9 +58,9 @@ else: else: # It's possible to have sizeof(long) != sizeof(Py_ssize_t). class X(object): - def __len__(self): return 1 << 31 + try: len(X()) except OverflowError: @@ -71,6 +71,11 @@ else: MAXSIZE = int((1 << 63) - 1) del X +if PY34: + from importlib.util import spec_from_loader +else: + spec_from_loader = None + def _add_doc(func, doc): """Add documentation to a function.""" @@ -84,7 +89,6 @@ def _import_module(name): class _LazyDescr(object): - def __init__(self, name): self.name = name @@ -101,7 +105,6 @@ class _LazyDescr(object): class MovedModule(_LazyDescr): - def __init__(self, name, old, new=None): super(MovedModule, self).__init__(name) if PY3: @@ -122,7 +125,6 @@ class MovedModule(_LazyDescr): class _LazyModule(types.ModuleType): - def __init__(self, name): super(_LazyModule, self).__init__(name) self.__doc__ = self.__class__.__doc__ @@ -137,7 +139,6 @@ class _LazyModule(types.ModuleType): class MovedAttribute(_LazyDescr): - def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): super(MovedAttribute, self).__init__(name) if PY3: @@ -186,6 +187,11 @@ class _SixMetaPathImporter(object): return self return None + def find_spec(self, fullname, path, target=None): + if fullname in self.known_modules: + return spec_from_loader(fullname, self) + return None + def __get_module(self, fullname): try: return self.known_modules[fullname] @@ -221,28 +227,42 @@ class _SixMetaPathImporter(object): Required, if is_package is implemented""" self.__get_module(fullname) # eventually raises ImportError return None + get_source = get_code # same as get_code + def create_module(self, spec): + return self.load_module(spec.name) + + def exec_module(self, module): + pass + + _importer = _SixMetaPathImporter(__name__) class _MovedItems(_LazyModule): """Lazy loading of moved objects""" + __path__ = [] # mark as package _moved_attributes = [ MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), - MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"), + MovedAttribute( + "filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse" + ), MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), MovedAttribute("intern", "__builtin__", "sys"), MovedAttribute("map", "itertools", "builtins", "imap", "map"), MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"), MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"), + MovedAttribute("getoutput", "commands", "subprocess"), MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), - MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"), + MovedAttribute( + "reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload" + ), MovedAttribute("reduce", "__builtin__", "functools"), MovedAttribute("shlex_quote", "pipes", "shlex", "quote"), MovedAttribute("StringIO", "StringIO", "io"), @@ -251,21 +271,36 @@ _moved_attributes = [ MovedAttribute("UserString", "UserString", "collections"), MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), - MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"), + MovedAttribute( + "zip_longest", "itertools", "itertools", "izip_longest", "zip_longest" + ), MovedModule("builtins", "__builtin__"), MovedModule("configparser", "ConfigParser"), + MovedModule( + "collections_abc", + "collections", + "collections.abc" if sys.version_info >= (3, 3) else "collections", + ), MovedModule("copyreg", "copy_reg"), MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), - MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"), + MovedModule("dbm_ndbm", "dbm", "dbm.ndbm"), + MovedModule( + "_dummy_thread", + "dummy_thread", + "_dummy_thread" if sys.version_info < (3, 9) else "_thread", + ), MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), MovedModule("http_cookies", "Cookie", "http.cookies"), MovedModule("html_entities", "htmlentitydefs", "html.entities"), MovedModule("html_parser", "HTMLParser", "html.parser"), MovedModule("http_client", "httplib", "http.client"), - MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), - MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"), - MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), + MovedModule("email_mime_image", "email.MIMEImage", "email.mime.image"), + MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), + MovedModule( + "email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart" + ), + MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), @@ -283,15 +318,12 @@ _moved_attributes = [ MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"), MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), - MovedModule("tkinter_colorchooser", "tkColorChooser", - "tkinter.colorchooser"), - MovedModule("tkinter_commondialog", "tkCommonDialog", - "tkinter.commondialog"), + MovedModule("tkinter_colorchooser", "tkColorChooser", "tkinter.colorchooser"), + MovedModule("tkinter_commondialog", "tkCommonDialog", "tkinter.commondialog"), MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), MovedModule("tkinter_font", "tkFont", "tkinter.font"), MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), - MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", - "tkinter.simpledialog"), + MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", "tkinter.simpledialog"), MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), @@ -337,10 +369,14 @@ _urllib_parse_moved_attributes = [ MovedAttribute("quote_plus", "urllib", "urllib.parse"), MovedAttribute("unquote", "urllib", "urllib.parse"), MovedAttribute("unquote_plus", "urllib", "urllib.parse"), + MovedAttribute( + "unquote_to_bytes", "urllib", "urllib.parse", "unquote", "unquote_to_bytes" + ), MovedAttribute("urlencode", "urllib", "urllib.parse"), MovedAttribute("splitquery", "urllib", "urllib.parse"), MovedAttribute("splittag", "urllib", "urllib.parse"), MovedAttribute("splituser", "urllib", "urllib.parse"), + MovedAttribute("splitvalue", "urllib", "urllib.parse"), MovedAttribute("uses_fragment", "urlparse", "urllib.parse"), MovedAttribute("uses_netloc", "urlparse", "urllib.parse"), MovedAttribute("uses_params", "urlparse", "urllib.parse"), @@ -353,8 +389,11 @@ del attr Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes -_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), - "moves.urllib_parse", "moves.urllib.parse") +_importer._add_module( + Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), + "moves.urllib_parse", + "moves.urllib.parse", +) class Module_six_moves_urllib_error(_LazyModule): @@ -373,8 +412,11 @@ del attr Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes -_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), - "moves.urllib_error", "moves.urllib.error") +_importer._add_module( + Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), + "moves.urllib_error", + "moves.urllib.error", +) class Module_six_moves_urllib_request(_LazyModule): @@ -416,6 +458,8 @@ _urllib_request_moved_attributes = [ MovedAttribute("URLopener", "urllib", "urllib.request"), MovedAttribute("FancyURLopener", "urllib", "urllib.request"), MovedAttribute("proxy_bypass", "urllib", "urllib.request"), + MovedAttribute("parse_http_list", "urllib2", "urllib.request"), + MovedAttribute("parse_keqv_list", "urllib2", "urllib.request"), ] for attr in _urllib_request_moved_attributes: setattr(Module_six_moves_urllib_request, attr.name, attr) @@ -423,8 +467,11 @@ del attr Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes -_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), - "moves.urllib_request", "moves.urllib.request") +_importer._add_module( + Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), + "moves.urllib_request", + "moves.urllib.request", +) class Module_six_moves_urllib_response(_LazyModule): @@ -444,8 +491,11 @@ del attr Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes -_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), - "moves.urllib_response", "moves.urllib.response") +_importer._add_module( + Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), + "moves.urllib_response", + "moves.urllib.response", +) class Module_six_moves_urllib_robotparser(_LazyModule): @@ -460,15 +510,21 @@ for attr in _urllib_robotparser_moved_attributes: setattr(Module_six_moves_urllib_robotparser, attr.name, attr) del attr -Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes +Module_six_moves_urllib_robotparser._moved_attributes = ( + _urllib_robotparser_moved_attributes +) -_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), - "moves.urllib_robotparser", "moves.urllib.robotparser") +_importer._add_module( + Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), + "moves.urllib_robotparser", + "moves.urllib.robotparser", +) class Module_six_moves_urllib(types.ModuleType): """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" + __path__ = [] # mark as package parse = _importer._get_module("moves.urllib_parse") error = _importer._get_module("moves.urllib_error") @@ -477,10 +533,12 @@ class Module_six_moves_urllib(types.ModuleType): robotparser = _importer._get_module("moves.urllib_robotparser") def __dir__(self): - return ['parse', 'error', 'request', 'response', 'robotparser'] + return ["parse", "error", "request", "response", "robotparser"] -_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"), - "moves.urllib") + +_importer._add_module( + Module_six_moves_urllib(__name__ + ".moves.urllib"), "moves.urllib" +) def add_move(move): @@ -520,19 +578,24 @@ else: try: advance_iterator = next except NameError: + def advance_iterator(it): return it.next() + + next = advance_iterator try: callable = callable except NameError: + def callable(obj): return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) if PY3: + def get_unbound_function(unbound): return unbound @@ -543,6 +606,7 @@ if PY3: Iterator = object else: + def get_unbound_function(unbound): return unbound.im_func @@ -553,13 +617,13 @@ else: return types.MethodType(func, None, cls) class Iterator(object): - def next(self): return type(self).__next__(self) callable = callable -_add_doc(get_unbound_function, - """Get the function out of a possibly unbound function""") +_add_doc( + get_unbound_function, """Get the function out of a possibly unbound function""" +) get_method_function = operator.attrgetter(_meth_func) @@ -571,6 +635,7 @@ get_function_globals = operator.attrgetter(_func_globals) if PY3: + def iterkeys(d, **kw): return iter(d.keys(**kw)) @@ -589,6 +654,7 @@ if PY3: viewitems = operator.methodcaller("items") else: + def iterkeys(d, **kw): return d.iterkeys(**kw) @@ -609,42 +675,52 @@ else: _add_doc(iterkeys, "Return an iterator over the keys of a dictionary.") _add_doc(itervalues, "Return an iterator over the values of a dictionary.") -_add_doc(iteritems, - "Return an iterator over the (key, value) pairs of a dictionary.") -_add_doc(iterlists, - "Return an iterator over the (key, [values]) pairs of a dictionary.") +_add_doc(iteritems, "Return an iterator over the (key, value) pairs of a dictionary.") +_add_doc( + iterlists, "Return an iterator over the (key, [values]) pairs of a dictionary." +) if PY3: + def b(s): return s.encode("latin-1") def u(s): return s + unichr = chr import struct + int2byte = struct.Struct(">B").pack del struct byte2int = operator.itemgetter(0) indexbytes = operator.getitem iterbytes = iter import io + StringIO = io.StringIO BytesIO = io.BytesIO + del io _assertCountEqual = "assertCountEqual" if sys.version_info[1] <= 1: _assertRaisesRegex = "assertRaisesRegexp" _assertRegex = "assertRegexpMatches" + _assertNotRegex = "assertNotRegexpMatches" else: _assertRaisesRegex = "assertRaisesRegex" _assertRegex = "assertRegex" + _assertNotRegex = "assertNotRegex" else: + def b(s): return s + # Workaround for standalone backslash def u(s): - return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape") + return unicode(s.replace(r"\\", r"\\\\"), "unicode_escape") + unichr = unichr int2byte = chr @@ -653,12 +729,15 @@ else: def indexbytes(buf, i): return ord(buf[i]) + iterbytes = functools.partial(itertools.imap, ord) import StringIO + StringIO = BytesIO = StringIO.StringIO _assertCountEqual = "assertItemsEqual" _assertRaisesRegex = "assertRaisesRegexp" _assertRegex = "assertRegexpMatches" + _assertNotRegex = "assertNotRegexpMatches" _add_doc(b, """Byte literal""") _add_doc(u, """Text literal""") @@ -675,17 +754,27 @@ def assertRegex(self, *args, **kwargs): return getattr(self, _assertRegex)(*args, **kwargs) +def assertNotRegex(self, *args, **kwargs): + return getattr(self, _assertNotRegex)(*args, **kwargs) + + if PY3: exec_ = getattr(moves.builtins, "exec") def reraise(tp, value, tb=None): - if value is None: - value = tp() - if value.__traceback__ is not tb: - raise value.with_traceback(tb) - raise value + try: + if value is None: + value = tp() + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + finally: + value = None + tb = None + else: + def exec_(_code_, _globs_=None, _locs_=None): """Execute code in a namespace.""" if _globs_ is None: @@ -696,30 +785,36 @@ else: del frame elif _locs_ is None: _locs_ = _globs_ - exec("""exec _code_ in _globs_, _locs_""") + exec ("""exec _code_ in _globs_, _locs_""") - exec_("""def reraise(tp, value, tb=None): - raise tp, value, tb -""") + exec_( + """def reraise(tp, value, tb=None): + try: + raise tp, value, tb + finally: + tb = None +""" + ) -if sys.version_info[:2] == (3, 2): - exec_("""def raise_from(value, from_value): - if from_value is None: - raise value - raise value from from_value -""") -elif sys.version_info[:2] > (3, 2): - exec_("""def raise_from(value, from_value): - raise value from from_value -""") +if sys.version_info[:2] > (3,): + exec_( + """def raise_from(value, from_value): + try: + raise value from from_value + finally: + value = None +""" + ) else: + def raise_from(value, from_value): raise value print_ = getattr(moves.builtins, "print", None) if print_ is None: + def print_(*args, **kwargs): """The new-style print function for Python 2.4 and 2.5.""" fp = kwargs.pop("file", sys.stdout) @@ -730,14 +825,17 @@ if print_ is None: if not isinstance(data, basestring): data = str(data) # If the file has an encoding, encode unicode with it. - if (isinstance(fp, file) and - isinstance(data, unicode) and - fp.encoding is not None): + if ( + isinstance(fp, file) + and isinstance(data, unicode) + and fp.encoding is not None + ): errors = getattr(fp, "errors", None) if errors is None: errors = "strict" data = data.encode(fp.encoding, errors) fp.write(data) + want_unicode = False sep = kwargs.pop("sep", None) if sep is not None: @@ -773,6 +871,8 @@ if print_ is None: write(sep) write(arg) write(end) + + if sys.version_info[:2] < (3, 3): _print = print_ @@ -783,16 +883,46 @@ if sys.version_info[:2] < (3, 3): if flush and fp is not None: fp.flush() + _add_doc(reraise, """Reraise an exception.""") if sys.version_info[0:2] < (3, 4): - def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, - updated=functools.WRAPPER_UPDATES): - def wrapper(f): - f = functools.wraps(wrapped, assigned, updated)(f) - f.__wrapped__ = wrapped - return f + # This does exactly the same what the :func:`py3:functools.update_wrapper` + # function does on Python versions after 3.2. It sets the ``__wrapped__`` + # attribute on ``wrapper`` object and it doesn't raise an error if any of + # the attributes mentioned in ``assigned`` and ``updated`` are missing on + # ``wrapped`` object. + def _update_wrapper( + wrapper, + wrapped, + assigned=functools.WRAPPER_ASSIGNMENTS, + updated=functools.WRAPPER_UPDATES, + ): + for attr in assigned: + try: + value = getattr(wrapped, attr) + except AttributeError: + continue + else: + setattr(wrapper, attr, value) + for attr in updated: + getattr(wrapper, attr).update(getattr(wrapped, attr, {})) + wrapper.__wrapped__ = wrapped return wrapper + + _update_wrapper.__doc__ = functools.update_wrapper.__doc__ + + def wraps( + wrapped, + assigned=functools.WRAPPER_ASSIGNMENTS, + updated=functools.WRAPPER_UPDATES, + ): + return functools.partial( + _update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated + ) + + wraps.__doc__ = functools.wraps.__doc__ + else: wraps = functools.wraps @@ -802,44 +932,121 @@ def with_metaclass(meta, *bases): # This requires a bit of explanation: the basic idea is to make a dummy # metaclass for one level of class instantiation that replaces itself with # the actual metaclass. - class metaclass(meta): - + class metaclass(type): def __new__(cls, name, this_bases, d): - return meta(name, bases, d) - return type.__new__(metaclass, 'temporary_class', (), {}) + if sys.version_info[:2] >= (3, 7): + # This version introduced PEP 560 that requires a bit + # of extra care (we mimic what is done by __build_class__). + resolved_bases = types.resolve_bases(bases) + if resolved_bases is not bases: + d["__orig_bases__"] = bases + else: + resolved_bases = bases + return meta(name, resolved_bases, d) + + @classmethod + def __prepare__(cls, name, this_bases): + return meta.__prepare__(name, bases) + + return type.__new__(metaclass, "temporary_class", (), {}) def add_metaclass(metaclass): """Class decorator for creating a class with a metaclass.""" + def wrapper(cls): orig_vars = cls.__dict__.copy() - slots = orig_vars.get('__slots__') + slots = orig_vars.get("__slots__") if slots is not None: if isinstance(slots, str): slots = [slots] for slots_var in slots: orig_vars.pop(slots_var) - orig_vars.pop('__dict__', None) - orig_vars.pop('__weakref__', None) + orig_vars.pop("__dict__", None) + orig_vars.pop("__weakref__", None) + if hasattr(cls, "__qualname__"): + orig_vars["__qualname__"] = cls.__qualname__ return metaclass(cls.__name__, cls.__bases__, orig_vars) + return wrapper +def ensure_binary(s, encoding="utf-8", errors="strict"): + """Coerce **s** to six.binary_type. + + For Python 2: + - `unicode` -> encoded to `str` + - `str` -> `str` + + For Python 3: + - `str` -> encoded to `bytes` + - `bytes` -> `bytes` + """ + if isinstance(s, binary_type): + return s + if isinstance(s, text_type): + return s.encode(encoding, errors) + raise TypeError("not expecting type '%s'" % type(s)) + + +def ensure_str(s, encoding="utf-8", errors="strict"): + """Coerce *s* to `str`. + + For Python 2: + - `unicode` -> encoded to `str` + - `str` -> `str` + + For Python 3: + - `str` -> `str` + - `bytes` -> decoded to `str` + """ + # Optimization: Fast return for the common case. + if type(s) is str: + return s + if PY2 and isinstance(s, text_type): + return s.encode(encoding, errors) + elif PY3 and isinstance(s, binary_type): + return s.decode(encoding, errors) + elif not isinstance(s, (text_type, binary_type)): + raise TypeError("not expecting type '%s'" % type(s)) + return s + + +def ensure_text(s, encoding="utf-8", errors="strict"): + """Coerce *s* to six.text_type. + + For Python 2: + - `unicode` -> `unicode` + - `str` -> `unicode` + + For Python 3: + - `str` -> `str` + - `bytes` -> decoded to `str` + """ + if isinstance(s, binary_type): + return s.decode(encoding, errors) + elif isinstance(s, text_type): + return s + else: + raise TypeError("not expecting type '%s'" % type(s)) + + def python_2_unicode_compatible(klass): """ - A decorator that defines __unicode__ and __str__ methods under Python 2. + A class decorator that defines __unicode__ and __str__ methods under Python 2. Under Python 3 it does nothing. To support Python 2 and 3 with a single code base, define a __str__ method returning text and apply this decorator to the class. """ if PY2: - if '__str__' not in klass.__dict__: - raise ValueError("@python_2_unicode_compatible cannot be applied " - "to %s because it doesn't define __str__()." % - klass.__name__) + if "__str__" not in klass.__dict__: + raise ValueError( + "@python_2_unicode_compatible cannot be applied " + "to %s because it doesn't define __str__()." % klass.__name__ + ) klass.__unicode__ = klass.__str__ - klass.__str__ = lambda self: self.__unicode__().encode('utf-8') + klass.__str__ = lambda self: self.__unicode__().encode("utf-8") return klass @@ -859,8 +1066,10 @@ if sys.meta_path: # be floating around. Therefore, we can't use isinstance() to check for # the six meta path importer, since the other six instance will have # inserted an importer with different class. - if (type(importer).__name__ == "_SixMetaPathImporter" and - importer.name == __name__): + if ( + type(importer).__name__ == "_SixMetaPathImporter" + and importer.name == __name__ + ): del sys.meta_path[i] break del i, importer diff --git a/pipenv/vendor/urllib3/packages/ssl_match_hostname/__init__.py b/pipenv/vendor/urllib3/packages/ssl_match_hostname/__init__.py index d6594eb2..ef3fde52 100644 --- a/pipenv/vendor/urllib3/packages/ssl_match_hostname/__init__.py +++ b/pipenv/vendor/urllib3/packages/ssl_match_hostname/__init__.py @@ -1,19 +1,24 @@ import sys try: - # Our match_hostname function is the same as 3.5's, so we only want to + # Our match_hostname function is the same as 3.10's, so we only want to # import the match_hostname function if it's at least that good. - if sys.version_info < (3, 5): + # We also fallback on Python 3.10+ because our code doesn't emit + # deprecation warnings and is the same as Python 3.10 otherwise. + if sys.version_info < (3, 5) or sys.version_info >= (3, 10): raise ImportError("Fallback to vendored code") from ssl import CertificateError, match_hostname except ImportError: try: # Backport of the function from a pypi module - from backports.ssl_match_hostname import CertificateError, match_hostname + from backports.ssl_match_hostname import ( # type: ignore + CertificateError, + match_hostname, + ) except ImportError: # Our vendored copy - from ._implementation import CertificateError, match_hostname + from ._implementation import CertificateError, match_hostname # type: ignore # Not needed, but documenting what we provide. -__all__ = ('CertificateError', 'match_hostname') +__all__ = ("CertificateError", "match_hostname") diff --git a/pipenv/vendor/urllib3/packages/ssl_match_hostname/_implementation.py b/pipenv/vendor/urllib3/packages/ssl_match_hostname/_implementation.py index d6e66c01..689208d3 100644 --- a/pipenv/vendor/urllib3/packages/ssl_match_hostname/_implementation.py +++ b/pipenv/vendor/urllib3/packages/ssl_match_hostname/_implementation.py @@ -15,7 +15,7 @@ try: except ImportError: ipaddress = None -__version__ = '3.5.0.1' +__version__ = "3.5.0.1" class CertificateError(ValueError): @@ -33,18 +33,19 @@ def _dnsname_match(dn, hostname, max_wildcards=1): # Ported from python3-syntax: # leftmost, *remainder = dn.split(r'.') - parts = dn.split(r'.') + parts = dn.split(r".") leftmost = parts[0] remainder = parts[1:] - wildcards = leftmost.count('*') + wildcards = leftmost.count("*") if wildcards > max_wildcards: # Issue #17980: avoid denials of service by refusing more # than one wildcard per fragment. A survey of established # policy among SSL implementations showed it to be a # reasonable choice. raise CertificateError( - "too many wildcards in certificate DNS name: " + repr(dn)) + "too many wildcards in certificate DNS name: " + repr(dn) + ) # speed up common case w/o wildcards if not wildcards: @@ -53,11 +54,11 @@ def _dnsname_match(dn, hostname, max_wildcards=1): # RFC 6125, section 6.4.3, subitem 1. # The client SHOULD NOT attempt to match a presented identifier in which # the wildcard character comprises a label other than the left-most label. - if leftmost == '*': + if leftmost == "*": # When '*' is a fragment by itself, it matches a non-empty dotless # fragment. - pats.append('[^.]+') - elif leftmost.startswith('xn--') or hostname.startswith('xn--'): + pats.append("[^.]+") + elif leftmost.startswith("xn--") or hostname.startswith("xn--"): # RFC 6125, section 6.4.3, subitem 3. # The client SHOULD NOT attempt to match a presented identifier # where the wildcard character is embedded within an A-label or @@ -65,21 +66,22 @@ def _dnsname_match(dn, hostname, max_wildcards=1): pats.append(re.escape(leftmost)) else: # Otherwise, '*' matches any dotless string, e.g. www* - pats.append(re.escape(leftmost).replace(r'\*', '[^.]*')) + pats.append(re.escape(leftmost).replace(r"\*", "[^.]*")) # add the remaining fragments, ignore any wildcards for frag in remainder: pats.append(re.escape(frag)) - pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE) + pat = re.compile(r"\A" + r"\.".join(pats) + r"\Z", re.IGNORECASE) return pat.match(hostname) def _to_unicode(obj): if isinstance(obj, str) and sys.version_info < (3,): - obj = unicode(obj, encoding='ascii', errors='strict') + obj = unicode(obj, encoding="ascii", errors="strict") return obj + def _ipaddress_match(ipname, host_ip): """Exact matching of IP addresses. @@ -101,9 +103,11 @@ def match_hostname(cert, hostname): returns nothing. """ if not cert: - raise ValueError("empty or no certificate, match_hostname needs a " - "SSL socket or SSL context with either " - "CERT_OPTIONAL or CERT_REQUIRED") + raise ValueError( + "empty or no certificate, match_hostname needs a " + "SSL socket or SSL context with either " + "CERT_OPTIONAL or CERT_REQUIRED" + ) try: # Divergence from upstream: ipaddress can't handle byte str host_ip = ipaddress.ip_address(_to_unicode(hostname)) @@ -122,35 +126,35 @@ def match_hostname(cert, hostname): else: raise dnsnames = [] - san = cert.get('subjectAltName', ()) + san = cert.get("subjectAltName", ()) for key, value in san: - if key == 'DNS': + if key == "DNS": if host_ip is None and _dnsname_match(value, hostname): return dnsnames.append(value) - elif key == 'IP Address': + elif key == "IP Address": if host_ip is not None and _ipaddress_match(value, host_ip): return dnsnames.append(value) if not dnsnames: # The subject is only checked when there is no dNSName entry # in subjectAltName - for sub in cert.get('subject', ()): + for sub in cert.get("subject", ()): for key, value in sub: # XXX according to RFC 2818, the most specific Common Name # must be used. - if key == 'commonName': + if key == "commonName": if _dnsname_match(value, hostname): return dnsnames.append(value) if len(dnsnames) > 1: - raise CertificateError("hostname %r " - "doesn't match either of %s" - % (hostname, ', '.join(map(repr, dnsnames)))) + raise CertificateError( + "hostname %r " + "doesn't match either of %s" % (hostname, ", ".join(map(repr, dnsnames))) + ) elif len(dnsnames) == 1: - raise CertificateError("hostname %r " - "doesn't match %r" - % (hostname, dnsnames[0])) + raise CertificateError("hostname %r doesn't match %r" % (hostname, dnsnames[0])) else: - raise CertificateError("no appropriate commonName or " - "subjectAltName fields were found") + raise CertificateError( + "no appropriate commonName or subjectAltName fields were found" + ) diff --git a/pipenv/vendor/urllib3/poolmanager.py b/pipenv/vendor/urllib3/poolmanager.py index a6ade6e9..3a31a285 100644 --- a/pipenv/vendor/urllib3/poolmanager.py +++ b/pipenv/vendor/urllib3/poolmanager.py @@ -1,61 +1,78 @@ from __future__ import absolute_import + import collections import functools import logging from ._collections import RecentlyUsedContainer -from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool -from .connectionpool import port_by_scheme -from .exceptions import LocationValueError, MaxRetryError, ProxySchemeUnknown +from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool, port_by_scheme +from .exceptions import ( + LocationValueError, + MaxRetryError, + ProxySchemeUnknown, + ProxySchemeUnsupported, + URLSchemeUnknown, +) from .packages import six from .packages.six.moves.urllib.parse import urljoin from .request import RequestMethods -from .util.url import parse_url +from .util.proxy import connection_requires_http_tunnel from .util.retry import Retry +from .util.url import parse_url - -__all__ = ['PoolManager', 'ProxyManager', 'proxy_from_url'] +__all__ = ["PoolManager", "ProxyManager", "proxy_from_url"] log = logging.getLogger(__name__) -SSL_KEYWORDS = ('key_file', 'cert_file', 'cert_reqs', 'ca_certs', - 'ssl_version', 'ca_cert_dir', 'ssl_context', - 'key_password') +SSL_KEYWORDS = ( + "key_file", + "cert_file", + "cert_reqs", + "ca_certs", + "ssl_version", + "ca_cert_dir", + "ssl_context", + "key_password", +) # All known keyword arguments that could be provided to the pool manager, its # pools, or the underlying connections. This is used to construct a pool key. _key_fields = ( - 'key_scheme', # str - 'key_host', # str - 'key_port', # int - 'key_timeout', # int or float or Timeout - 'key_retries', # int or Retry - 'key_strict', # bool - 'key_block', # bool - 'key_source_address', # str - 'key_key_file', # str - 'key_key_password', # str - 'key_cert_file', # str - 'key_cert_reqs', # str - 'key_ca_certs', # str - 'key_ssl_version', # str - 'key_ca_cert_dir', # str - 'key_ssl_context', # instance of ssl.SSLContext or urllib3.util.ssl_.SSLContext - 'key_maxsize', # int - 'key_headers', # dict - 'key__proxy', # parsed proxy url - 'key__proxy_headers', # dict - 'key_socket_options', # list of (level (int), optname (int), value (int or str)) tuples - 'key__socks_options', # dict - 'key_assert_hostname', # bool or string - 'key_assert_fingerprint', # str - 'key_server_hostname', # str + "key_scheme", # str + "key_host", # str + "key_port", # int + "key_timeout", # int or float or Timeout + "key_retries", # int or Retry + "key_strict", # bool + "key_block", # bool + "key_source_address", # str + "key_key_file", # str + "key_key_password", # str + "key_cert_file", # str + "key_cert_reqs", # str + "key_ca_certs", # str + "key_ssl_version", # str + "key_ca_cert_dir", # str + "key_ssl_context", # instance of ssl.SSLContext or urllib3.util.ssl_.SSLContext + "key_maxsize", # int + "key_headers", # dict + "key__proxy", # parsed proxy url + "key__proxy_headers", # dict + "key__proxy_config", # class + "key_socket_options", # list of (level (int), optname (int), value (int or str)) tuples + "key__socks_options", # dict + "key_assert_hostname", # bool or string + "key_assert_fingerprint", # str + "key_server_hostname", # str ) #: The namedtuple class used to construct keys for the connection pool. #: All custom key schemes should include the fields in this key at a minimum. -PoolKey = collections.namedtuple('PoolKey', _key_fields) +PoolKey = collections.namedtuple("PoolKey", _key_fields) + +_proxy_config_fields = ("ssl_context", "use_forwarding_for_https") +ProxyConfig = collections.namedtuple("ProxyConfig", _proxy_config_fields) def _default_key_normalizer(key_class, request_context): @@ -80,24 +97,24 @@ def _default_key_normalizer(key_class, request_context): """ # Since we mutate the dictionary, make a copy first context = request_context.copy() - context['scheme'] = context['scheme'].lower() - context['host'] = context['host'].lower() + context["scheme"] = context["scheme"].lower() + context["host"] = context["host"].lower() # These are both dictionaries and need to be transformed into frozensets - for key in ('headers', '_proxy_headers', '_socks_options'): + for key in ("headers", "_proxy_headers", "_socks_options"): if key in context and context[key] is not None: context[key] = frozenset(context[key].items()) # The socket_options key may be a list and needs to be transformed into a # tuple. - socket_opts = context.get('socket_options') + socket_opts = context.get("socket_options") if socket_opts is not None: - context['socket_options'] = tuple(socket_opts) + context["socket_options"] = tuple(socket_opts) # Map the kwargs to the names in the namedtuple - this is necessary since # namedtuples can't have fields starting with '_'. for key in list(context.keys()): - context['key_' + key] = context.pop(key) + context["key_" + key] = context.pop(key) # Default to ``None`` for keys missing from the context for field in key_class._fields: @@ -112,14 +129,11 @@ def _default_key_normalizer(key_class, request_context): #: Each PoolManager makes a copy of this dictionary so they can be configured #: globally here, or individually on the instance. key_fn_by_scheme = { - 'http': functools.partial(_default_key_normalizer, PoolKey), - 'https': functools.partial(_default_key_normalizer, PoolKey), + "http": functools.partial(_default_key_normalizer, PoolKey), + "https": functools.partial(_default_key_normalizer, PoolKey), } -pool_classes_by_scheme = { - 'http': HTTPConnectionPool, - 'https': HTTPSConnectionPool, -} +pool_classes_by_scheme = {"http": HTTPConnectionPool, "https": HTTPSConnectionPool} class PoolManager(RequestMethods): @@ -151,12 +165,12 @@ class PoolManager(RequestMethods): """ proxy = None + proxy_config = None def __init__(self, num_pools=10, headers=None, **connection_pool_kw): RequestMethods.__init__(self, headers) self.connection_pool_kw = connection_pool_kw - self.pools = RecentlyUsedContainer(num_pools, - dispose_func=lambda p: p.close()) + self.pools = RecentlyUsedContainer(num_pools, dispose_func=lambda p: p.close()) # Locally set the pool classes and keys so other PoolManagers can # override them. @@ -173,7 +187,7 @@ class PoolManager(RequestMethods): def _new_pool(self, scheme, host, port, request_context=None): """ - Create a new :class:`ConnectionPool` based on host, port, scheme, and + Create a new :class:`urllib3.connectionpool.ConnectionPool` based on host, port, scheme, and any additional pool keyword arguments. If ``request_context`` is provided, it is provided as keyword arguments @@ -189,10 +203,10 @@ class PoolManager(RequestMethods): # this function has historically only used the scheme, host, and port # in the positional args. When an API change is acceptable these can # be removed. - for key in ('scheme', 'host', 'port'): + for key in ("scheme", "host", "port"): request_context.pop(key, None) - if scheme == 'http': + if scheme == "http": for kw in SSL_KEYWORDS: request_context.pop(kw, None) @@ -207,9 +221,9 @@ class PoolManager(RequestMethods): """ self.pools.clear() - def connection_from_host(self, host, port=None, scheme='http', pool_kwargs=None): + def connection_from_host(self, host, port=None, scheme="http", pool_kwargs=None): """ - Get a :class:`ConnectionPool` based on the host, port, and scheme. + Get a :class:`urllib3.connectionpool.ConnectionPool` based on the host, port, and scheme. If ``port`` isn't given, it will be derived from the ``scheme`` using ``urllib3.connectionpool.port_by_scheme``. If ``pool_kwargs`` is @@ -222,30 +236,32 @@ class PoolManager(RequestMethods): raise LocationValueError("No host specified.") request_context = self._merge_pool_kwargs(pool_kwargs) - request_context['scheme'] = scheme or 'http' + request_context["scheme"] = scheme or "http" if not port: - port = port_by_scheme.get(request_context['scheme'].lower(), 80) - request_context['port'] = port - request_context['host'] = host + port = port_by_scheme.get(request_context["scheme"].lower(), 80) + request_context["port"] = port + request_context["host"] = host return self.connection_from_context(request_context) def connection_from_context(self, request_context): """ - Get a :class:`ConnectionPool` based on the request context. + Get a :class:`urllib3.connectionpool.ConnectionPool` based on the request context. ``request_context`` must at least contain the ``scheme`` key and its value must be a key in ``key_fn_by_scheme`` instance variable. """ - scheme = request_context['scheme'].lower() - pool_key_constructor = self.key_fn_by_scheme[scheme] + scheme = request_context["scheme"].lower() + pool_key_constructor = self.key_fn_by_scheme.get(scheme) + if not pool_key_constructor: + raise URLSchemeUnknown(scheme) pool_key = pool_key_constructor(request_context) return self.connection_from_pool_key(pool_key, request_context=request_context) def connection_from_pool_key(self, pool_key, request_context=None): """ - Get a :class:`ConnectionPool` based on the provided pool key. + Get a :class:`urllib3.connectionpool.ConnectionPool` based on the provided pool key. ``pool_key`` should be a namedtuple that only contains immutable objects. At a minimum it must have the ``scheme``, ``host``, and @@ -259,9 +275,9 @@ class PoolManager(RequestMethods): return pool # Make a fresh ConnectionPool of the desired type - scheme = request_context['scheme'] - host = request_context['host'] - port = request_context['port'] + scheme = request_context["scheme"] + host = request_context["host"] + port = request_context["port"] pool = self._new_pool(scheme, host, port, request_context=request_context) self.pools[pool_key] = pool @@ -279,8 +295,9 @@ class PoolManager(RequestMethods): not used. """ u = parse_url(url) - return self.connection_from_host(u.host, port=u.port, scheme=u.scheme, - pool_kwargs=pool_kwargs) + return self.connection_from_host( + u.host, port=u.port, scheme=u.scheme, pool_kwargs=pool_kwargs + ) def _merge_pool_kwargs(self, override): """ @@ -302,9 +319,39 @@ class PoolManager(RequestMethods): base_pool_kwargs[key] = value return base_pool_kwargs + def _proxy_requires_url_absolute_form(self, parsed_url): + """ + Indicates if the proxy requires the complete destination URL in the + request. Normally this is only needed when not using an HTTP CONNECT + tunnel. + """ + if self.proxy is None: + return False + + return not connection_requires_http_tunnel( + self.proxy, self.proxy_config, parsed_url.scheme + ) + + def _validate_proxy_scheme_url_selection(self, url_scheme): + """ + Validates that were not attempting to do TLS in TLS connections on + Python2 or with unsupported SSL implementations. + """ + if self.proxy is None or url_scheme != "https": + return + + if self.proxy.scheme != "https": + return + + if six.PY2 and not self.proxy_config.use_forwarding_for_https: + raise ProxySchemeUnsupported( + "Contacting HTTPS destinations through HTTPS proxies " + "'via CONNECT tunnels' is not supported in Python 2" + ) + def urlopen(self, method, url, redirect=True, **kw): """ - Same as :meth:`urllib3.connectionpool.HTTPConnectionPool.urlopen` + Same as :meth:`urllib3.HTTPConnectionPool.urlopen` with custom cross-host redirect logic and only sends the request-uri portion of the ``url``. @@ -312,15 +359,17 @@ class PoolManager(RequestMethods): :class:`urllib3.connectionpool.ConnectionPool` can be chosen for it. """ u = parse_url(url) + self._validate_proxy_scheme_url_selection(u.scheme) + conn = self.connection_from_host(u.host, port=u.port, scheme=u.scheme) - kw['assert_same_host'] = False - kw['redirect'] = False + kw["assert_same_host"] = False + kw["redirect"] = False - if 'headers' not in kw: - kw['headers'] = self.headers.copy() + if "headers" not in kw: + kw["headers"] = self.headers.copy() - if self.proxy is not None and u.scheme == "http": + if self._proxy_requires_url_absolute_form(u): response = conn.urlopen(method, url, **kw) else: response = conn.urlopen(method, u.request_uri, **kw) @@ -334,33 +383,37 @@ class PoolManager(RequestMethods): # RFC 7231, Section 6.4.4 if response.status == 303: - method = 'GET' + method = "GET" - retries = kw.get('retries') + retries = kw.get("retries") if not isinstance(retries, Retry): retries = Retry.from_int(retries, redirect=redirect) # Strip headers marked as unsafe to forward to the redirected location. # Check remove_headers_on_redirect to avoid a potential network call within # conn.is_same_host() which may use socket.gethostbyname() in the future. - if (retries.remove_headers_on_redirect - and not conn.is_same_host(redirect_location)): - headers = list(six.iterkeys(kw['headers'])) + if retries.remove_headers_on_redirect and not conn.is_same_host( + redirect_location + ): + headers = list(six.iterkeys(kw["headers"])) for header in headers: if header.lower() in retries.remove_headers_on_redirect: - kw['headers'].pop(header, None) + kw["headers"].pop(header, None) try: retries = retries.increment(method, url, response=response, _pool=conn) except MaxRetryError: if retries.raise_on_redirect: + response.drain_conn() raise return response - kw['retries'] = retries - kw['redirect'] = redirect + kw["retries"] = retries + kw["redirect"] = redirect log.info("Redirecting %s -> %s", url, redirect_location) + + response.drain_conn() return self.urlopen(method, redirect_location, **kw) @@ -378,6 +431,19 @@ class ProxyManager(PoolManager): HTTPS/CONNECT case they are sent only once. Could be used for proxy authentication. + :param proxy_ssl_context: + The proxy SSL context is used to establish the TLS connection to the + proxy when using HTTPS proxies. + + :param use_forwarding_for_https: + (Defaults to False) If set to True will forward requests to the HTTPS + proxy to be made on behalf of the client instead of creating a TLS + tunnel via the CONNECT method. **Enabling this flag means that request + and response headers and content will be visible from the HTTPS proxy** + whereas tunneling keeps request and response headers and content + private. IP address, target hostname, SNI, and port are always visible + to an HTTPS proxy even when this flag is disabled. + Example: >>> proxy = urllib3.ProxyManager('http://localhost:3128/') >>> r1 = proxy.request('GET', 'http://google.com/') @@ -391,47 +457,63 @@ class ProxyManager(PoolManager): """ - def __init__(self, proxy_url, num_pools=10, headers=None, - proxy_headers=None, **connection_pool_kw): + def __init__( + self, + proxy_url, + num_pools=10, + headers=None, + proxy_headers=None, + proxy_ssl_context=None, + use_forwarding_for_https=False, + **connection_pool_kw + ): if isinstance(proxy_url, HTTPConnectionPool): - proxy_url = '%s://%s:%i' % (proxy_url.scheme, proxy_url.host, - proxy_url.port) + proxy_url = "%s://%s:%i" % ( + proxy_url.scheme, + proxy_url.host, + proxy_url.port, + ) proxy = parse_url(proxy_url) - if not proxy.port: - port = port_by_scheme.get(proxy.scheme, 80) - proxy = proxy._replace(port=port) if proxy.scheme not in ("http", "https"): raise ProxySchemeUnknown(proxy.scheme) + if not proxy.port: + port = port_by_scheme.get(proxy.scheme, 80) + proxy = proxy._replace(port=port) + self.proxy = proxy self.proxy_headers = proxy_headers or {} + self.proxy_ssl_context = proxy_ssl_context + self.proxy_config = ProxyConfig(proxy_ssl_context, use_forwarding_for_https) - connection_pool_kw['_proxy'] = self.proxy - connection_pool_kw['_proxy_headers'] = self.proxy_headers + connection_pool_kw["_proxy"] = self.proxy + connection_pool_kw["_proxy_headers"] = self.proxy_headers + connection_pool_kw["_proxy_config"] = self.proxy_config - super(ProxyManager, self).__init__( - num_pools, headers, **connection_pool_kw) + super(ProxyManager, self).__init__(num_pools, headers, **connection_pool_kw) - def connection_from_host(self, host, port=None, scheme='http', pool_kwargs=None): + def connection_from_host(self, host, port=None, scheme="http", pool_kwargs=None): if scheme == "https": return super(ProxyManager, self).connection_from_host( - host, port, scheme, pool_kwargs=pool_kwargs) + host, port, scheme, pool_kwargs=pool_kwargs + ) return super(ProxyManager, self).connection_from_host( - self.proxy.host, self.proxy.port, self.proxy.scheme, pool_kwargs=pool_kwargs) + self.proxy.host, self.proxy.port, self.proxy.scheme, pool_kwargs=pool_kwargs + ) def _set_proxy_headers(self, url, headers=None): """ Sets headers needed by proxies: specifically, the Accept and Host headers. Only sets headers not provided by the user. """ - headers_ = {'Accept': '*/*'} + headers_ = {"Accept": "*/*"} netloc = parse_url(url).netloc if netloc: - headers_['Host'] = netloc + headers_["Host"] = netloc if headers: headers_.update(headers) @@ -440,13 +522,12 @@ class ProxyManager(PoolManager): def urlopen(self, method, url, redirect=True, **kw): "Same as HTTP(S)ConnectionPool.urlopen, ``url`` must be absolute." u = parse_url(url) - - if u.scheme == "http": - # For proxied HTTPS requests, httplib sets the necessary headers - # on the CONNECT to the proxy. For HTTP, we'll definitely - # need to set 'Host' at the very least. - headers = kw.get('headers', self.headers) - kw['headers'] = self._set_proxy_headers(url, headers) + if not connection_requires_http_tunnel(self.proxy, self.proxy_config, u.scheme): + # For connections using HTTP CONNECT, httplib sets the necessary + # headers on the CONNECT to the proxy. If we're not using CONNECT, + # we'll definitely need to set 'Host' at the very least. + headers = kw.get("headers", self.headers) + kw["headers"] = self._set_proxy_headers(url, headers) return super(ProxyManager, self).urlopen(method, url, redirect=redirect, **kw) diff --git a/pipenv/vendor/urllib3/request.py b/pipenv/vendor/urllib3/request.py index 8f2f44bb..398386a5 100644 --- a/pipenv/vendor/urllib3/request.py +++ b/pipenv/vendor/urllib3/request.py @@ -3,15 +3,14 @@ from __future__ import absolute_import from .filepost import encode_multipart_formdata from .packages.six.moves.urllib.parse import urlencode - -__all__ = ['RequestMethods'] +__all__ = ["RequestMethods"] class RequestMethods(object): """ Convenience mixin for classes who implement a :meth:`urlopen` method, such - as :class:`~urllib3.connectionpool.HTTPConnectionPool` and - :class:`~urllib3.poolmanager.PoolManager`. + as :class:`urllib3.HTTPConnectionPool` and + :class:`urllib3.PoolManager`. Provides behavior for making common types of HTTP request methods and decides which type of request field encoding to use. @@ -36,16 +35,25 @@ class RequestMethods(object): explicitly. """ - _encode_url_methods = {'DELETE', 'GET', 'HEAD', 'OPTIONS'} + _encode_url_methods = {"DELETE", "GET", "HEAD", "OPTIONS"} def __init__(self, headers=None): self.headers = headers or {} - def urlopen(self, method, url, body=None, headers=None, - encode_multipart=True, multipart_boundary=None, - **kw): # Abstract - raise NotImplementedError("Classes extending RequestMethods must implement " - "their own ``urlopen`` method.") + def urlopen( + self, + method, + url, + body=None, + headers=None, + encode_multipart=True, + multipart_boundary=None, + **kw + ): # Abstract + raise NotImplementedError( + "Classes extending RequestMethods must implement " + "their own ``urlopen`` method." + ) def request(self, method, url, fields=None, headers=None, **urlopen_kw): """ @@ -60,19 +68,18 @@ class RequestMethods(object): """ method = method.upper() - urlopen_kw['request_url'] = url + urlopen_kw["request_url"] = url if method in self._encode_url_methods: - return self.request_encode_url(method, url, fields=fields, - headers=headers, - **urlopen_kw) + return self.request_encode_url( + method, url, fields=fields, headers=headers, **urlopen_kw + ) else: - return self.request_encode_body(method, url, fields=fields, - headers=headers, - **urlopen_kw) + return self.request_encode_body( + method, url, fields=fields, headers=headers, **urlopen_kw + ) - def request_encode_url(self, method, url, fields=None, headers=None, - **urlopen_kw): + def request_encode_url(self, method, url, fields=None, headers=None, **urlopen_kw): """ Make a request using :meth:`urlopen` with the ``fields`` encoded in the url. This is useful for request methods like GET, HEAD, DELETE, etc. @@ -80,25 +87,32 @@ class RequestMethods(object): if headers is None: headers = self.headers - extra_kw = {'headers': headers} + extra_kw = {"headers": headers} extra_kw.update(urlopen_kw) if fields: - url += '?' + urlencode(fields) + url += "?" + urlencode(fields) return self.urlopen(method, url, **extra_kw) - def request_encode_body(self, method, url, fields=None, headers=None, - encode_multipart=True, multipart_boundary=None, - **urlopen_kw): + def request_encode_body( + self, + method, + url, + fields=None, + headers=None, + encode_multipart=True, + multipart_boundary=None, + **urlopen_kw + ): """ Make a request using :meth:`urlopen` with the ``fields`` encoded in the body. This is useful for request methods like POST, PUT, PATCH, etc. When ``encode_multipart=True`` (default), then - :meth:`urllib3.filepost.encode_multipart_formdata` is used to encode + :func:`urllib3.encode_multipart_formdata` is used to encode the payload with the appropriate content type. Otherwise - :meth:`urllib.urlencode` is used with the + :func:`urllib.parse.urlencode` is used with the 'application/x-www-form-urlencoded' content type. Multipart encoding must be used when posting files, and it's reasonably @@ -129,22 +143,28 @@ class RequestMethods(object): if headers is None: headers = self.headers - extra_kw = {'headers': {}} + extra_kw = {"headers": {}} if fields: - if 'body' in urlopen_kw: + if "body" in urlopen_kw: raise TypeError( - "request got values for both 'fields' and 'body', can only specify one.") + "request got values for both 'fields' and 'body', can only specify one." + ) if encode_multipart: - body, content_type = encode_multipart_formdata(fields, boundary=multipart_boundary) + body, content_type = encode_multipart_formdata( + fields, boundary=multipart_boundary + ) else: - body, content_type = urlencode(fields), 'application/x-www-form-urlencoded' + body, content_type = ( + urlencode(fields), + "application/x-www-form-urlencoded", + ) - extra_kw['body'] = body - extra_kw['headers'] = {'Content-Type': content_type} + extra_kw["body"] = body + extra_kw["headers"] = {"Content-Type": content_type} - extra_kw['headers'].update(headers) + extra_kw["headers"].update(headers) extra_kw.update(urlopen_kw) return self.urlopen(method, url, **extra_kw) diff --git a/pipenv/vendor/urllib3/response.py b/pipenv/vendor/urllib3/response.py index 4f857932..38693f4f 100644 --- a/pipenv/vendor/urllib3/response.py +++ b/pipenv/vendor/urllib3/response.py @@ -1,10 +1,11 @@ from __future__ import absolute_import -from contextlib import contextmanager -import zlib + import io import logging -from socket import timeout as SocketTimeout +import zlib +from contextlib import contextmanager from socket import error as SocketError +from socket import timeout as SocketTimeout try: import brotli @@ -12,23 +13,29 @@ except ImportError: brotli = None from ._collections import HTTPHeaderDict +from .connection import BaseSSLError, HTTPException from .exceptions import ( - BodyNotHttplibCompatible, ProtocolError, DecodeError, ReadTimeoutError, - ResponseNotChunked, IncompleteRead, InvalidHeader + BodyNotHttplibCompatible, + DecodeError, + HTTPError, + IncompleteRead, + InvalidChunkLength, + InvalidHeader, + ProtocolError, + ReadTimeoutError, + ResponseNotChunked, + SSLError, ) -from .packages.six import string_types as basestring, PY3 -from .packages.six.moves import http_client as httplib -from .connection import HTTPException, BaseSSLError +from .packages import six from .util.response import is_fp_closed, is_response_to_head log = logging.getLogger(__name__) class DeflateDecoder(object): - def __init__(self): self._first_try = True - self._data = b'' + self._data = b"" self._obj = zlib.decompressobj() def __getattr__(self, name): @@ -65,7 +72,6 @@ class GzipDecoderState(object): class GzipDecoder(object): - def __init__(self): self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) self._state = GzipDecoderState.FIRST_MEMBER @@ -96,22 +102,22 @@ class GzipDecoder(object): if brotli is not None: + class BrotliDecoder(object): # Supports both 'brotlipy' and 'Brotli' packages # since they share an import name. The top branches # are for 'brotlipy' and bottom branches for 'Brotli' def __init__(self): self._obj = brotli.Decompressor() - - def decompress(self, data): - if hasattr(self._obj, 'decompress'): - return self._obj.decompress(data) - return self._obj.process(data) + if hasattr(self._obj, "decompress"): + self.decompress = self._obj.decompress + else: + self.decompress = self._obj.process def flush(self): - if hasattr(self._obj, 'flush'): + if hasattr(self._obj, "flush"): return self._obj.flush() - return b'' + return b"" class MultiDecoder(object): @@ -124,7 +130,7 @@ class MultiDecoder(object): """ def __init__(self, modes): - self._decoders = [_get_decoder(m.strip()) for m in modes.split(',')] + self._decoders = [_get_decoder(m.strip()) for m in modes.split(",")] def flush(self): return self._decoders[0].flush() @@ -136,13 +142,13 @@ class MultiDecoder(object): def _get_decoder(mode): - if ',' in mode: + if "," in mode: return MultiDecoder(mode) - if mode == 'gzip': + if mode == "gzip": return GzipDecoder() - if brotli is not None and mode == 'br': + if brotli is not None and mode == "br": return BrotliDecoder() return DeflateDecoder() @@ -152,13 +158,13 @@ class HTTPResponse(io.IOBase): """ HTTP Response container. - Backwards-compatible to httplib's HTTPResponse but the response ``body`` is + Backwards-compatible with :class:`http.client.HTTPResponse` but the response ``body`` is loaded and decoded on-demand when the ``data`` property is accessed. This class is also compatible with the Python standard library's :mod:`io` module, and can hence be treated as a readable object in the context of that framework. - Extra parameters for behaviour not present in httplib.HTTPResponse: + Extra parameters for behaviour not present in :class:`http.client.HTTPResponse`: :param preload_content: If True, the response's body will be preloaded during construction. @@ -168,7 +174,7 @@ class HTTPResponse(io.IOBase): 'content-encoding' header. :param original_response: - When this HTTPResponse wrapper is generated from an httplib.HTTPResponse + When this HTTPResponse wrapper is generated from an :class:`http.client.HTTPResponse` object, it's convenient to include the original for debug purposes. It's otherwise unused. @@ -181,16 +187,31 @@ class HTTPResponse(io.IOBase): value of Content-Length header, if present. Otherwise, raise error. """ - CONTENT_DECODERS = ['gzip', 'deflate'] + CONTENT_DECODERS = ["gzip", "deflate"] if brotli is not None: - CONTENT_DECODERS += ['br'] + CONTENT_DECODERS += ["br"] REDIRECT_STATUSES = [301, 302, 303, 307, 308] - def __init__(self, body='', headers=None, status=0, version=0, reason=None, - strict=0, preload_content=True, decode_content=True, - original_response=None, pool=None, connection=None, msg=None, - retries=None, enforce_content_length=False, - request_method=None, request_url=None): + def __init__( + self, + body="", + headers=None, + status=0, + version=0, + reason=None, + strict=0, + preload_content=True, + decode_content=True, + original_response=None, + pool=None, + connection=None, + msg=None, + retries=None, + enforce_content_length=False, + request_method=None, + request_url=None, + auto_close=True, + ): if isinstance(headers, HTTPHeaderDict): self.headers = headers @@ -203,6 +224,7 @@ class HTTPResponse(io.IOBase): self.decode_content = decode_content self.retries = retries self.enforce_content_length = enforce_content_length + self.auto_close = auto_close self._decoder = None self._body = None @@ -212,19 +234,19 @@ class HTTPResponse(io.IOBase): self.msg = msg self._request_url = request_url - if body and isinstance(body, (basestring, bytes)): + if body and isinstance(body, (six.string_types, bytes)): self._body = body self._pool = pool self._connection = connection - if hasattr(body, 'read'): + if hasattr(body, "read"): self._fp = body # Are we using the chunked-style of transfer encoding? self.chunked = False self.chunk_left = None - tr_enc = self.headers.get('transfer-encoding', '').lower() + tr_enc = self.headers.get("transfer-encoding", "").lower() # Don't incur the penalty of creating a list and then discarding it encodings = (enc.strip() for enc in tr_enc.split(",")) if "chunked" in encodings: @@ -246,7 +268,7 @@ class HTTPResponse(io.IOBase): location. ``False`` if not a redirect status code. """ if self.status in self.REDIRECT_STATUSES: - return self.headers.get('location') + return self.headers.get("location") return False @@ -257,9 +279,20 @@ class HTTPResponse(io.IOBase): self._pool._put_conn(self._connection) self._connection = None + def drain_conn(self): + """ + Read and discard any remaining HTTP response data in the response connection. + + Unread data in the HTTPResponse connection blocks the connection from being released back to the pool. + """ + try: + self.read() + except (HTTPError, SocketError, BaseSSLError, HTTPException): + pass + @property def data(self): - # For backwords-compat with earlier urllib3 0.4 and earlier. + # For backwards-compat with earlier urllib3 0.4 and earlier. if self._body: return self._body @@ -276,8 +309,8 @@ class HTTPResponse(io.IOBase): def tell(self): """ Obtain the number of bytes pulled over the wire so far. May differ from - the amount of content returned by :meth:``HTTPResponse.read`` if bytes - are encoded on the wire (e.g, compressed). + the amount of content returned by :meth:``urllib3.response.HTTPResponse.read`` + if bytes are encoded on the wire (e.g, compressed). """ return self._fp_bytes_read @@ -285,18 +318,20 @@ class HTTPResponse(io.IOBase): """ Set initial length value for Response content if available. """ - length = self.headers.get('content-length') + length = self.headers.get("content-length") if length is not None: if self.chunked: # This Response will fail with an IncompleteRead if it can't be # received as chunked. This method falls back to attempt reading # the response before raising an exception. - log.warning("Received response with both Content-Length and " - "Transfer-Encoding set. This is expressly forbidden " - "by RFC 7230 sec 3.3.2. Ignoring Content-Length and " - "attempting to process response as Transfer-Encoding: " - "chunked.") + log.warning( + "Received response with both Content-Length and " + "Transfer-Encoding set. This is expressly forbidden " + "by RFC 7230 sec 3.3.2. Ignoring Content-Length and " + "attempting to process response as Transfer-Encoding: " + "chunked." + ) return None try: @@ -305,10 +340,12 @@ class HTTPResponse(io.IOBase): # (e.g. Content-Length: 42, 42). This line ensures the values # are all valid ints and that as long as the `set` length is 1, # all values are the same. Otherwise, the header is invalid. - lengths = set([int(val) for val in length.split(',')]) + lengths = set([int(val) for val in length.split(",")]) if len(lengths) > 1: - raise InvalidHeader("Content-Length contained multiple " - "unmatching values (%s)" % length) + raise InvalidHeader( + "Content-Length contained multiple " + "unmatching values (%s)" % length + ) length = lengths.pop() except ValueError: length = None @@ -324,7 +361,7 @@ class HTTPResponse(io.IOBase): status = 0 # Check for responses that shouldn't include a body - if status in (204, 304) or 100 <= status < 200 or request_method == 'HEAD': + if status in (204, 304) or 100 <= status < 200 or request_method == "HEAD": length = 0 return length @@ -335,14 +372,16 @@ class HTTPResponse(io.IOBase): """ # Note: content-encoding value should be case-insensitive, per RFC 7230 # Section 3.2 - content_encoding = self.headers.get('content-encoding', '').lower() + content_encoding = self.headers.get("content-encoding", "").lower() if self._decoder is None: if content_encoding in self.CONTENT_DECODERS: self._decoder = _get_decoder(content_encoding) - elif ',' in content_encoding: + elif "," in content_encoding: encodings = [ - e.strip() for e in content_encoding.split(',') - if e.strip() in self.CONTENT_DECODERS] + e.strip() + for e in content_encoding.split(",") + if e.strip() in self.CONTENT_DECODERS + ] if len(encodings): self._decoder = _get_decoder(content_encoding) @@ -361,10 +400,12 @@ class HTTPResponse(io.IOBase): if self._decoder: data = self._decoder.decompress(data) except self.DECODER_ERROR_CLASSES as e: - content_encoding = self.headers.get('content-encoding', '').lower() + content_encoding = self.headers.get("content-encoding", "").lower() raise DecodeError( "Received response with content-encoding: %s, but " - "failed to decode it." % content_encoding, e) + "failed to decode it." % content_encoding, + e, + ) if flush_decoder: data += self._flush_decoder() @@ -376,10 +417,10 @@ class HTTPResponse(io.IOBase): being used. """ if self._decoder: - buf = self._decoder.decompress(b'') + buf = self._decoder.decompress(b"") return buf + self._decoder.flush() - return b'' + return b"" @contextmanager def _error_catcher(self): @@ -399,20 +440,19 @@ class HTTPResponse(io.IOBase): except SocketTimeout: # FIXME: Ideally we'd like to include the url in the ReadTimeoutError but # there is yet no clean way to get at it from this context. - raise ReadTimeoutError(self._pool, None, 'Read timed out.') + raise ReadTimeoutError(self._pool, None, "Read timed out.") except BaseSSLError as e: # FIXME: Is there a better way to differentiate between SSLErrors? - if 'read operation timed out' not in str(e): # Defensive: - # This shouldn't happen but just in case we're missing an edge - # case, let's avoid swallowing SSL errors. - raise + if "read operation timed out" not in str(e): + # SSL errors related to framing/MAC get wrapped and reraised here + raise SSLError(e) - raise ReadTimeoutError(self._pool, None, 'Read timed out.') + raise ReadTimeoutError(self._pool, None, "Read timed out.") except (HTTPException, SocketError) as e: # This includes IncompleteRead. - raise ProtocolError('Connection broken: %r' % e, e) + raise ProtocolError("Connection broken: %r" % e, e) # If no exception is thrown, we should avoid cleaning up # unnecessarily. @@ -440,7 +480,7 @@ class HTTPResponse(io.IOBase): def read(self, amt=None, decode_content=None, cache_content=False): """ - Similar to :meth:`httplib.HTTPResponse.read`, but with two additional + Similar to :meth:`http.client.HTTPResponse.read`, but with two additional parameters: ``decode_content`` and ``cache_content``. :param amt: @@ -467,17 +507,19 @@ class HTTPResponse(io.IOBase): return flush_decoder = False - data = None + fp_closed = getattr(self._fp, "closed", False) with self._error_catcher(): if amt is None: # cStringIO doesn't like amt=None - data = self._fp.read() + data = self._fp.read() if not fp_closed else b"" flush_decoder = True else: cache_content = False - data = self._fp.read(amt) - if amt != 0 and not data: # Platform-specific: Buggy versions of Python. + data = self._fp.read(amt) if not fp_closed else b"" + if ( + amt != 0 and not data + ): # Platform-specific: Buggy versions of Python. # Close the connection when no data is returned # # This is redundant to what httplib/http.client _should_ @@ -487,7 +529,10 @@ class HTTPResponse(io.IOBase): # no harm in redundantly calling close. self._fp.close() flush_decoder = True - if self.enforce_content_length and self.length_remaining not in (0, None): + if self.enforce_content_length and self.length_remaining not in ( + 0, + None, + ): # This is an edge case that httplib failed to cover due # to concerns of backward compatibility. We're # addressing it here to make sure IncompleteRead is @@ -507,7 +552,7 @@ class HTTPResponse(io.IOBase): return data - def stream(self, amt=2**16, decode_content=None): + def stream(self, amt=2 ** 16, decode_content=None): """ A generator wrapper for the read() method. A call will block until ``amt`` bytes have been read from the connection or until the @@ -536,7 +581,7 @@ class HTTPResponse(io.IOBase): @classmethod def from_httplib(ResponseCls, r, **response_kw): """ - Given an :class:`httplib.HTTPResponse` instance ``r``, return a + Given an :class:`http.client.HTTPResponse` instance ``r``, return a corresponding :class:`urllib3.response.HTTPResponse` object. Remaining parameters are passed to the HTTPResponse constructor, along @@ -545,25 +590,27 @@ class HTTPResponse(io.IOBase): headers = r.msg if not isinstance(headers, HTTPHeaderDict): - if PY3: - headers = HTTPHeaderDict(headers.items()) - else: + if six.PY2: # Python 2.7 headers = HTTPHeaderDict.from_httplib(headers) + else: + headers = HTTPHeaderDict(headers.items()) # HTTPResponse objects in Python 3 don't have a .strict attribute - strict = getattr(r, 'strict', 0) - resp = ResponseCls(body=r, - headers=headers, - status=r.status, - version=r.version, - reason=r.reason, - strict=strict, - original_response=r, - **response_kw) + strict = getattr(r, "strict", 0) + resp = ResponseCls( + body=r, + headers=headers, + status=r.status, + version=r.version, + reason=r.reason, + strict=strict, + original_response=r, + **response_kw + ) return resp - # Backwards-compatibility methods for httplib.HTTPResponse + # Backwards-compatibility methods for http.client.HTTPResponse def getheaders(self): return self.headers @@ -582,13 +629,18 @@ class HTTPResponse(io.IOBase): if self._connection: self._connection.close() + if not self.auto_close: + io.IOBase.close(self) + @property def closed(self): - if self._fp is None: + if not self.auto_close: + return io.IOBase.closed.__get__(self) + elif self._fp is None: return True - elif hasattr(self._fp, 'isclosed'): + elif hasattr(self._fp, "isclosed"): return self._fp.isclosed() - elif hasattr(self._fp, 'closed'): + elif hasattr(self._fp, "closed"): return self._fp.closed else: return True @@ -599,11 +651,17 @@ class HTTPResponse(io.IOBase): elif hasattr(self._fp, "fileno"): return self._fp.fileno() else: - raise IOError("The file-like object this HTTPResponse is wrapped " - "around has no file descriptor") + raise IOError( + "The file-like object this HTTPResponse is wrapped " + "around has no file descriptor" + ) def flush(self): - if self._fp is not None and hasattr(self._fp, 'flush'): + if ( + self._fp is not None + and hasattr(self._fp, "flush") + and not getattr(self._fp, "closed", False) + ): return self._fp.flush() def readable(self): @@ -616,17 +674,17 @@ class HTTPResponse(io.IOBase): if len(temp) == 0: return 0 else: - b[:len(temp)] = temp + b[: len(temp)] = temp return len(temp) def supports_chunked_reads(self): """ Checks if the underlying file-like object looks like a - httplib.HTTPResponse object. We do this by testing for the fp - attribute. If it is present we assume it returns raw chunks as + :class:`http.client.HTTPResponse` object. We do this by testing for + the fp attribute. If it is present we assume it returns raw chunks as processed by read_chunked(). """ - return hasattr(self._fp, 'fp') + return hasattr(self._fp, "fp") def _update_chunk_length(self): # First, we'll figure out length of a chunk and then @@ -634,13 +692,13 @@ class HTTPResponse(io.IOBase): if self.chunk_left is not None: return line = self._fp.fp.readline() - line = line.split(b';', 1)[0] + line = line.split(b";", 1)[0] try: self.chunk_left = int(line, 16) except ValueError: # Invalid chunked protocol response, abort. self.close() - raise httplib.IncompleteRead(line) + raise InvalidChunkLength(self, line) def _handle_chunk(self, amt): returned_chunk = None @@ -683,11 +741,13 @@ class HTTPResponse(io.IOBase): if not self.chunked: raise ResponseNotChunked( "Response is not chunked. " - "Header 'transfer-encoding: chunked' is missing.") + "Header 'transfer-encoding: chunked' is missing." + ) if not self.supports_chunked_reads(): raise BodyNotHttplibCompatible( - "Body should be httplib.HTTPResponse like. " - "It should have have an fp attribute which returns raw chunks.") + "Body should be http.client.HTTPResponse like. " + "It should have have an fp attribute which returns raw chunks." + ) with self._error_catcher(): # Don't bother reading the body of a HEAD request. @@ -705,8 +765,9 @@ class HTTPResponse(io.IOBase): if self.chunk_left == 0: break chunk = self._handle_chunk(amt) - decoded = self._decode(chunk, decode_content=decode_content, - flush_decoder=False) + decoded = self._decode( + chunk, decode_content=decode_content, flush_decoder=False + ) if decoded: yield decoded @@ -724,7 +785,7 @@ class HTTPResponse(io.IOBase): if not line: # Some sites may not end with '\r\n'. break - if line == b'\r\n': + if line == b"\r\n": break # We read everything; close the "file". @@ -743,7 +804,7 @@ class HTTPResponse(io.IOBase): return self._request_url def __iter__(self): - buffer = [b""] + buffer = [] for chunk in self.stream(decode_content=True): if b"\n" in chunk: chunk = chunk.split(b"\n") diff --git a/pipenv/vendor/urllib3/util/__init__.py b/pipenv/vendor/urllib3/util/__init__.py index 2914bb46..4547fc52 100644 --- a/pipenv/vendor/urllib3/util/__init__.py +++ b/pipenv/vendor/urllib3/util/__init__.py @@ -1,56 +1,49 @@ from __future__ import absolute_import + # For backwards compatibility, provide imports that used to be here. from .connection import is_connection_dropped -from .request import make_headers +from .request import SKIP_HEADER, SKIPPABLE_HEADERS, make_headers from .response import is_fp_closed +from .retry import Retry from .ssl_ import ( - SSLContext, + ALPN_PROTOCOLS, HAS_SNI, IS_PYOPENSSL, IS_SECURETRANSPORT, + PROTOCOL_TLS, + SSLContext, assert_fingerprint, resolve_cert_reqs, resolve_ssl_version, ssl_wrap_socket, - PROTOCOL_TLS, -) -from .timeout import ( - current_time, - Timeout, -) - -from .retry import Retry -from .url import ( - get_host, - parse_url, - split_first, - Url, -) -from .wait import ( - wait_for_read, - wait_for_write ) +from .timeout import Timeout, current_time +from .url import Url, get_host, parse_url, split_first +from .wait import wait_for_read, wait_for_write __all__ = ( - 'HAS_SNI', - 'IS_PYOPENSSL', - 'IS_SECURETRANSPORT', - 'SSLContext', - 'PROTOCOL_TLS', - 'Retry', - 'Timeout', - 'Url', - 'assert_fingerprint', - 'current_time', - 'is_connection_dropped', - 'is_fp_closed', - 'get_host', - 'parse_url', - 'make_headers', - 'resolve_cert_reqs', - 'resolve_ssl_version', - 'split_first', - 'ssl_wrap_socket', - 'wait_for_read', - 'wait_for_write' + "HAS_SNI", + "IS_PYOPENSSL", + "IS_SECURETRANSPORT", + "SSLContext", + "PROTOCOL_TLS", + "ALPN_PROTOCOLS", + "Retry", + "Timeout", + "Url", + "assert_fingerprint", + "current_time", + "is_connection_dropped", + "is_fp_closed", + "get_host", + "parse_url", + "make_headers", + "resolve_cert_reqs", + "resolve_ssl_version", + "split_first", + "ssl_wrap_socket", + "wait_for_read", + "wait_for_write", + "SKIP_HEADER", + "SKIPPABLE_HEADERS", ) diff --git a/pipenv/vendor/urllib3/util/connection.py b/pipenv/vendor/urllib3/util/connection.py index 5ad70b2f..9a2781ac 100644 --- a/pipenv/vendor/urllib3/util/connection.py +++ b/pipenv/vendor/urllib3/util/connection.py @@ -1,7 +1,12 @@ from __future__ import absolute_import + import socket -from .wait import NoWayToWaitForSocketError, wait_for_read + +from pipenv.vendor.urllib3.exceptions import LocationParseError + from ..contrib import _appengine_environ +from ..packages import six +from .wait import NoWayToWaitForSocketError, wait_for_read def is_connection_dropped(conn): # Platform-specific @@ -9,12 +14,12 @@ def is_connection_dropped(conn): # Platform-specific Returns True if the connection is dropped and should be closed. :param conn: - :class:`httplib.HTTPConnection` object. + :class:`http.client.HTTPConnection` object. Note: For platforms like AppEngine, this will always return ``False`` to let the platform handle connection recycling transparently for us. """ - sock = getattr(conn, 'sock', False) + sock = getattr(conn, "sock", False) if sock is False: # Platform-specific: AppEngine return False if sock is None: # Connection already closed (such as by httplib). @@ -30,23 +35,27 @@ def is_connection_dropped(conn): # Platform-specific # library test suite. Added to its signature is only `socket_options`. # One additional modification is that we avoid binding to IPv6 servers # discovered in DNS if the system doesn't have IPv6 functionality. -def create_connection(address, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, - source_address=None, socket_options=None): +def create_connection( + address, + timeout=socket._GLOBAL_DEFAULT_TIMEOUT, + source_address=None, + socket_options=None, +): """Connect to *address* and return the socket object. Convenience function. Connect to *address* (a 2-tuple ``(host, port)``) and return the socket object. Passing the optional *timeout* parameter will set the timeout on the socket instance before attempting to connect. If no *timeout* is supplied, the - global default timeout setting returned by :func:`getdefaulttimeout` + global default timeout setting returned by :func:`socket.getdefaulttimeout` is used. If *source_address* is set it must be a tuple of (host, port) for the socket to bind as a source address before making the connection. An host of '' or port 0 tells the OS to use the default. """ host, port = address - if host.startswith('['): - host = host.strip('[]') + if host.startswith("["): + host = host.strip("[]") err = None # Using the value from allowed_gai_family() in the context of getaddrinfo lets @@ -54,6 +63,13 @@ def create_connection(address, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, # The original create_connection function always returns all records. family = allowed_gai_family() + try: + host.encode("idna") + except UnicodeError: + return six.raise_from( + LocationParseError(u"'%s', label empty or too long" % host), None + ) + for res in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM): af, socktype, proto, canonname, sa = res sock = None @@ -102,7 +118,7 @@ def allowed_gai_family(): def _has_ipv6(host): - """ Returns True if the system can bind an IPv6 address. """ + """Returns True if the system can bind an IPv6 address.""" sock = None has_ipv6 = False @@ -117,7 +133,7 @@ def _has_ipv6(host): # has_ipv6 returns true if cPython was compiled with IPv6 support. # It does not tell us if the system has IPv6 support enabled. To # determine that we must bind to an IPv6 address. - # https://github.com/shazow/urllib3/pull/611 + # https://github.com/urllib3/urllib3/pull/611 # https://bugs.python.org/issue658327 try: sock = socket.socket(socket.AF_INET6) @@ -131,4 +147,4 @@ def _has_ipv6(host): return has_ipv6 -HAS_IPV6 = _has_ipv6('::1') +HAS_IPV6 = _has_ipv6("::1") diff --git a/pipenv/vendor/urllib3/util/proxy.py b/pipenv/vendor/urllib3/util/proxy.py new file mode 100644 index 00000000..34f884d5 --- /dev/null +++ b/pipenv/vendor/urllib3/util/proxy.py @@ -0,0 +1,56 @@ +from .ssl_ import create_urllib3_context, resolve_cert_reqs, resolve_ssl_version + + +def connection_requires_http_tunnel( + proxy_url=None, proxy_config=None, destination_scheme=None +): + """ + Returns True if the connection requires an HTTP CONNECT through the proxy. + + :param URL proxy_url: + URL of the proxy. + :param ProxyConfig proxy_config: + Proxy configuration from poolmanager.py + :param str destination_scheme: + The scheme of the destination. (i.e https, http, etc) + """ + # If we're not using a proxy, no way to use a tunnel. + if proxy_url is None: + return False + + # HTTP destinations never require tunneling, we always forward. + if destination_scheme == "http": + return False + + # Support for forwarding with HTTPS proxies and HTTPS destinations. + if ( + proxy_url.scheme == "https" + and proxy_config + and proxy_config.use_forwarding_for_https + ): + return False + + # Otherwise always use a tunnel. + return True + + +def create_proxy_ssl_context( + ssl_version, cert_reqs, ca_certs=None, ca_cert_dir=None, ca_cert_data=None +): + """ + Generates a default proxy ssl context if one hasn't been provided by the + user. + """ + ssl_context = create_urllib3_context( + ssl_version=resolve_ssl_version(ssl_version), + cert_reqs=resolve_cert_reqs(cert_reqs), + ) + if ( + not ca_certs + and not ca_cert_dir + and not ca_cert_data + and hasattr(ssl_context, "load_default_certs") + ): + ssl_context.load_default_certs() + + return ssl_context diff --git a/pipenv/vendor/urllib3/util/queue.py b/pipenv/vendor/urllib3/util/queue.py index d3d379a1..41784104 100644 --- a/pipenv/vendor/urllib3/util/queue.py +++ b/pipenv/vendor/urllib3/util/queue.py @@ -1,4 +1,5 @@ import collections + from ..packages import six from ..packages.six.moves import queue diff --git a/pipenv/vendor/urllib3/util/request.py b/pipenv/vendor/urllib3/util/request.py index 280b8530..25103383 100644 --- a/pipenv/vendor/urllib3/util/request.py +++ b/pipenv/vendor/urllib3/util/request.py @@ -1,22 +1,36 @@ from __future__ import absolute_import + from base64 import b64encode -from ..packages.six import b, integer_types from ..exceptions import UnrewindableBodyError +from ..packages.six import b, integer_types -ACCEPT_ENCODING = 'gzip,deflate' +# Pass as a value within ``headers`` to skip +# emitting some HTTP headers that are added automatically. +# The only headers that are supported are ``Accept-Encoding``, +# ``Host``, and ``User-Agent``. +SKIP_HEADER = "@@@SKIP_HEADER@@@" +SKIPPABLE_HEADERS = frozenset(["accept-encoding", "host", "user-agent"]) + +ACCEPT_ENCODING = "gzip,deflate" try: import brotli as _unused_module_brotli # noqa: F401 except ImportError: pass else: - ACCEPT_ENCODING += ',br' + ACCEPT_ENCODING += ",br" _FAILEDTELL = object() -def make_headers(keep_alive=None, accept_encoding=None, user_agent=None, - basic_auth=None, proxy_basic_auth=None, disable_cache=None): +def make_headers( + keep_alive=None, + accept_encoding=None, + user_agent=None, + basic_auth=None, + proxy_basic_auth=None, + disable_cache=None, +): """ Shortcuts for generating request headers. @@ -56,27 +70,27 @@ def make_headers(keep_alive=None, accept_encoding=None, user_agent=None, if isinstance(accept_encoding, str): pass elif isinstance(accept_encoding, list): - accept_encoding = ','.join(accept_encoding) + accept_encoding = ",".join(accept_encoding) else: accept_encoding = ACCEPT_ENCODING - headers['accept-encoding'] = accept_encoding + headers["accept-encoding"] = accept_encoding if user_agent: - headers['user-agent'] = user_agent + headers["user-agent"] = user_agent if keep_alive: - headers['connection'] = 'keep-alive' + headers["connection"] = "keep-alive" if basic_auth: - headers['authorization'] = 'Basic ' + \ - b64encode(b(basic_auth)).decode('utf-8') + headers["authorization"] = "Basic " + b64encode(b(basic_auth)).decode("utf-8") if proxy_basic_auth: - headers['proxy-authorization'] = 'Basic ' + \ - b64encode(b(proxy_basic_auth)).decode('utf-8') + headers["proxy-authorization"] = "Basic " + b64encode( + b(proxy_basic_auth) + ).decode("utf-8") if disable_cache: - headers['cache-control'] = 'no-cache' + headers["cache-control"] = "no-cache" return headers @@ -88,7 +102,7 @@ def set_file_position(body, pos): """ if pos is not None: rewind_body(body, pos) - elif getattr(body, 'tell', None) is not None: + elif getattr(body, "tell", None) is not None: try: pos = body.tell() except (IOError, OSError): @@ -110,16 +124,20 @@ def rewind_body(body, body_pos): :param int pos: Position to seek to in file. """ - body_seek = getattr(body, 'seek', None) + body_seek = getattr(body, "seek", None) if body_seek is not None and isinstance(body_pos, integer_types): try: body_seek(body_pos) except (IOError, OSError): - raise UnrewindableBodyError("An error occurred when rewinding request " - "body for redirect/retry.") + raise UnrewindableBodyError( + "An error occurred when rewinding request body for redirect/retry." + ) elif body_pos is _FAILEDTELL: - raise UnrewindableBodyError("Unable to record file position for rewinding " - "request body during a redirect/retry.") + raise UnrewindableBodyError( + "Unable to record file position for rewinding " + "request body during a redirect/retry." + ) else: - raise ValueError("body_pos must be of type integer, " - "instead it was %s." % type(body_pos)) + raise ValueError( + "body_pos must be of type integer, instead it was %s." % type(body_pos) + ) diff --git a/pipenv/vendor/urllib3/util/response.py b/pipenv/vendor/urllib3/util/response.py index 3d548648..5ea609cc 100644 --- a/pipenv/vendor/urllib3/util/response.py +++ b/pipenv/vendor/urllib3/util/response.py @@ -1,7 +1,9 @@ from __future__ import absolute_import -from ..packages.six.moves import http_client as httplib + +from email.errors import MultipartInvariantViolationDefect, StartBoundaryNotFoundDefect from ..exceptions import HeaderParsingError +from ..packages.six.moves import http_client as httplib def is_fp_closed(obj): @@ -42,8 +44,7 @@ def assert_header_parsing(headers): Only works on Python 3. - :param headers: Headers to verify. - :type headers: `httplib.HTTPMessage`. + :param http.client.HTTPMessage headers: Headers to verify. :raises urllib3.exceptions.HeaderParsingError: If parsing errors are found. @@ -52,11 +53,10 @@ def assert_header_parsing(headers): # This will fail silently if we pass in the wrong kind of parameter. # To make debugging easier add an explicit check. if not isinstance(headers, httplib.HTTPMessage): - raise TypeError('expected httplib.Message, got {0}.'.format( - type(headers))) + raise TypeError("expected httplib.Message, got {0}.".format(type(headers))) - defects = getattr(headers, 'defects', None) - get_payload = getattr(headers, 'get_payload', None) + defects = getattr(headers, "defects", None) + get_payload = getattr(headers, "get_payload", None) unparsed_data = None if get_payload: @@ -67,6 +67,25 @@ def assert_header_parsing(headers): if isinstance(payload, (bytes, str)): unparsed_data = payload + if defects: + # httplib is assuming a response body is available + # when parsing headers even when httplib only sends + # header data to parse_headers() This results in + # defects on multipart responses in particular. + # See: https://github.com/urllib3/urllib3/issues/800 + + # So we ignore the following defects: + # - StartBoundaryNotFoundDefect: + # The claimed start boundary was never found. + # - MultipartInvariantViolationDefect: + # A message claimed to be a multipart but no subparts were found. + defects = [ + defect + for defect in defects + if not isinstance( + defect, (StartBoundaryNotFoundDefect, MultipartInvariantViolationDefect) + ) + ] if defects or unparsed_data: raise HeaderParsingError(defects=defects, unparsed_data=unparsed_data) @@ -77,11 +96,12 @@ def is_response_to_head(response): Checks whether the request of a response has been a HEAD-request. Handles the quirks of AppEngine. - :param conn: - :type conn: :class:`httplib.HTTPResponse` + :param http.client.HTTPResponse response: + Response to check if the originating request + used 'HEAD' as a method. """ # FIXME: Can we do this somehow without accessing private httplib _method? method = response._method if isinstance(method, int): # Platform-specific: Appengine return method == 3 - return method.upper() == 'HEAD' + return method.upper() == "HEAD" diff --git a/pipenv/vendor/urllib3/util/retry.py b/pipenv/vendor/urllib3/util/retry.py index 02429ee8..c7dc42f1 100644 --- a/pipenv/vendor/urllib3/util/retry.py +++ b/pipenv/vendor/urllib3/util/retry.py @@ -1,32 +1,78 @@ from __future__ import absolute_import -import time + +import email import logging +import re +import time +import warnings from collections import namedtuple from itertools import takewhile -import email -import re from ..exceptions import ( ConnectTimeoutError, + InvalidHeader, MaxRetryError, ProtocolError, + ProxyError, ReadTimeoutError, ResponseError, - InvalidHeader, ) from ..packages import six - log = logging.getLogger(__name__) # Data structure for representing the metadata of requests that result in a retry. -RequestHistory = namedtuple('RequestHistory', ["method", "url", "error", - "status", "redirect_location"]) +RequestHistory = namedtuple( + "RequestHistory", ["method", "url", "error", "status", "redirect_location"] +) +# TODO: In v2 we can remove this sentinel and metaclass with deprecated options. +_Default = object() + + +class _RetryMeta(type): + @property + def DEFAULT_METHOD_WHITELIST(cls): + warnings.warn( + "Using 'Retry.DEFAULT_METHOD_WHITELIST' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_ALLOWED_METHODS' instead", + DeprecationWarning, + ) + return cls.DEFAULT_ALLOWED_METHODS + + @DEFAULT_METHOD_WHITELIST.setter + def DEFAULT_METHOD_WHITELIST(cls, value): + warnings.warn( + "Using 'Retry.DEFAULT_METHOD_WHITELIST' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_ALLOWED_METHODS' instead", + DeprecationWarning, + ) + cls.DEFAULT_ALLOWED_METHODS = value + + @property + def DEFAULT_REDIRECT_HEADERS_BLACKLIST(cls): + warnings.warn( + "Using 'Retry.DEFAULT_REDIRECT_HEADERS_BLACKLIST' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_REMOVE_HEADERS_ON_REDIRECT' instead", + DeprecationWarning, + ) + return cls.DEFAULT_REMOVE_HEADERS_ON_REDIRECT + + @DEFAULT_REDIRECT_HEADERS_BLACKLIST.setter + def DEFAULT_REDIRECT_HEADERS_BLACKLIST(cls, value): + warnings.warn( + "Using 'Retry.DEFAULT_REDIRECT_HEADERS_BLACKLIST' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_REMOVE_HEADERS_ON_REDIRECT' instead", + DeprecationWarning, + ) + cls.DEFAULT_REMOVE_HEADERS_ON_REDIRECT = value + + +@six.add_metaclass(_RetryMeta) class Retry(object): - """ Retry configuration. + """Retry configuration. Each retry attempt will create a new Retry object with updated values, so they can be safely reused. @@ -52,8 +98,7 @@ class Retry(object): Total number of retries to allow. Takes precedence over other counts. Set to ``None`` to remove this constraint and fall back on other - counts. It's a good idea to set this to some sensibly-high value to - account for unexpected edge cases and avoid infinite retry loops. + counts. Set to ``0`` to fail on the first retry. @@ -94,18 +139,35 @@ class Retry(object): Set to ``0`` to fail on the first retry of this type. - :param iterable method_whitelist: + :param int other: + How many times to retry on other errors. + + Other errors are errors that are not connect, read, redirect or status errors. + These errors might be raised after the request was sent to the server, so the + request might have side-effects. + + Set to ``0`` to fail on the first retry of this type. + + If ``total`` is not set, it's a good idea to set this to 0 to account + for unexpected edge cases and avoid infinite retry loops. + + :param iterable allowed_methods: Set of uppercased HTTP method verbs that we should retry on. By default, we only retry on methods which are considered to be idempotent (multiple requests with the same parameters end with the - same state). See :attr:`Retry.DEFAULT_METHOD_WHITELIST`. + same state). See :attr:`Retry.DEFAULT_ALLOWED_METHODS`. Set to a ``False`` value to retry on any verb. + .. warning:: + + Previously this parameter was named ``method_whitelist``, that + usage is deprecated in v1.26.0 and will be removed in v2.0. + :param iterable status_forcelist: A set of integer HTTP status codes that we should force a retry on. - A retry is initiated if the request method is in ``method_whitelist`` + A retry is initiated if the request method is in ``allowed_methods`` and the response status code is in ``status_forcelist``. By default, this is disabled with ``None``. @@ -146,26 +208,64 @@ class Retry(object): request. """ - DEFAULT_METHOD_WHITELIST = frozenset([ - 'HEAD', 'GET', 'PUT', 'DELETE', 'OPTIONS', 'TRACE']) + #: Default methods to be used for ``allowed_methods`` + DEFAULT_ALLOWED_METHODS = frozenset( + ["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE"] + ) + #: Default status codes to be used for ``status_forcelist`` RETRY_AFTER_STATUS_CODES = frozenset([413, 429, 503]) - DEFAULT_REDIRECT_HEADERS_BLACKLIST = frozenset(['Authorization']) + #: Default headers to be used for ``remove_headers_on_redirect`` + DEFAULT_REMOVE_HEADERS_ON_REDIRECT = frozenset(["Authorization"]) #: Maximum backoff time. BACKOFF_MAX = 120 - def __init__(self, total=10, connect=None, read=None, redirect=None, status=None, - method_whitelist=DEFAULT_METHOD_WHITELIST, status_forcelist=None, - backoff_factor=0, raise_on_redirect=True, raise_on_status=True, - history=None, respect_retry_after_header=True, - remove_headers_on_redirect=DEFAULT_REDIRECT_HEADERS_BLACKLIST): + def __init__( + self, + total=10, + connect=None, + read=None, + redirect=None, + status=None, + other=None, + allowed_methods=_Default, + status_forcelist=None, + backoff_factor=0, + raise_on_redirect=True, + raise_on_status=True, + history=None, + respect_retry_after_header=True, + remove_headers_on_redirect=_Default, + # TODO: Deprecated, remove in v2.0 + method_whitelist=_Default, + ): + + if method_whitelist is not _Default: + if allowed_methods is not _Default: + raise ValueError( + "Using both 'allowed_methods' and " + "'method_whitelist' together is not allowed. " + "Instead only use 'allowed_methods'" + ) + warnings.warn( + "Using 'method_whitelist' with Retry is deprecated and " + "will be removed in v2.0. Use 'allowed_methods' instead", + DeprecationWarning, + stacklevel=2, + ) + allowed_methods = method_whitelist + if allowed_methods is _Default: + allowed_methods = self.DEFAULT_ALLOWED_METHODS + if remove_headers_on_redirect is _Default: + remove_headers_on_redirect = self.DEFAULT_REMOVE_HEADERS_ON_REDIRECT self.total = total self.connect = connect self.read = read self.status = status + self.other = other if redirect is False or total is False: redirect = 0 @@ -173,33 +273,55 @@ class Retry(object): self.redirect = redirect self.status_forcelist = status_forcelist or set() - self.method_whitelist = method_whitelist + self.allowed_methods = allowed_methods self.backoff_factor = backoff_factor self.raise_on_redirect = raise_on_redirect self.raise_on_status = raise_on_status self.history = history or tuple() self.respect_retry_after_header = respect_retry_after_header - self.remove_headers_on_redirect = frozenset([ - h.lower() for h in remove_headers_on_redirect]) + self.remove_headers_on_redirect = frozenset( + [h.lower() for h in remove_headers_on_redirect] + ) def new(self, **kw): params = dict( total=self.total, - connect=self.connect, read=self.read, redirect=self.redirect, status=self.status, - method_whitelist=self.method_whitelist, + connect=self.connect, + read=self.read, + redirect=self.redirect, + status=self.status, + other=self.other, status_forcelist=self.status_forcelist, backoff_factor=self.backoff_factor, raise_on_redirect=self.raise_on_redirect, raise_on_status=self.raise_on_status, history=self.history, - remove_headers_on_redirect=self.remove_headers_on_redirect + remove_headers_on_redirect=self.remove_headers_on_redirect, + respect_retry_after_header=self.respect_retry_after_header, ) + + # TODO: If already given in **kw we use what's given to us + # If not given we need to figure out what to pass. We decide + # based on whether our class has the 'method_whitelist' property + # and if so we pass the deprecated 'method_whitelist' otherwise + # we use 'allowed_methods'. Remove in v2.0 + if "method_whitelist" not in kw and "allowed_methods" not in kw: + if "method_whitelist" in self.__dict__: + warnings.warn( + "Using 'method_whitelist' with Retry is deprecated and " + "will be removed in v2.0. Use 'allowed_methods' instead", + DeprecationWarning, + ) + params["method_whitelist"] = self.allowed_methods + else: + params["allowed_methods"] = self.allowed_methods + params.update(kw) return type(self)(**params) @classmethod def from_int(cls, retries, redirect=True, default=None): - """ Backwards-compatibility for the old retries format.""" + """Backwards-compatibility for the old retries format.""" if retries is None: retries = default if default is not None else cls.DEFAULT @@ -212,13 +334,16 @@ class Retry(object): return new_retries def get_backoff_time(self): - """ Formula for computing the current backoff + """Formula for computing the current backoff :rtype: float """ # We want to consider only the last consecutive errors sequence (Ignore redirects). - consecutive_errors_len = len(list(takewhile(lambda x: x.redirect_location is None, - reversed(self.history)))) + consecutive_errors_len = len( + list( + takewhile(lambda x: x.redirect_location is None, reversed(self.history)) + ) + ) if consecutive_errors_len <= 1: return 0 @@ -230,10 +355,17 @@ class Retry(object): if re.match(r"^\s*[0-9]+\s*$", retry_after): seconds = int(retry_after) else: - retry_date_tuple = email.utils.parsedate(retry_after) + retry_date_tuple = email.utils.parsedate_tz(retry_after) if retry_date_tuple is None: raise InvalidHeader("Invalid Retry-After header: %s" % retry_after) - retry_date = time.mktime(retry_date_tuple) + if retry_date_tuple[9] is None: # Python 2 + # Assume UTC if no timezone was specified + # On Python2.7, parsedate_tz returns None for a timezone offset + # instead of 0 if no timezone is given, where mktime_tz treats + # a None timezone offset as local time. + retry_date_tuple = retry_date_tuple[:9] + (0,) + retry_date_tuple[10:] + + retry_date = email.utils.mktime_tz(retry_date_tuple) seconds = retry_date - time.time() if seconds < 0: @@ -242,7 +374,7 @@ class Retry(object): return seconds def get_retry_after(self, response): - """ Get the value of Retry-After in seconds. """ + """Get the value of Retry-After in seconds.""" retry_after = response.getheader("Retry-After") @@ -266,7 +398,7 @@ class Retry(object): time.sleep(backoff) def sleep(self, response=None): - """ Sleep between retry attempts. + """Sleep between retry attempts. This method will respect a server's ``Retry-After`` response header and sleep the duration of the time requested. If that is not present, it @@ -274,7 +406,7 @@ class Retry(object): this method will return immediately. """ - if response: + if self.respect_retry_after_header and response: slept = self.sleep_for_retry(response) if slept: return @@ -282,28 +414,41 @@ class Retry(object): self._sleep_backoff() def _is_connection_error(self, err): - """ Errors when we're fairly sure that the server did not receive the + """Errors when we're fairly sure that the server did not receive the request, so it should be safe to retry. """ + if isinstance(err, ProxyError): + err = err.original_error return isinstance(err, ConnectTimeoutError) def _is_read_error(self, err): - """ Errors that occur after the request has been started, so we should + """Errors that occur after the request has been started, so we should assume that the server began processing it. """ return isinstance(err, (ReadTimeoutError, ProtocolError)) def _is_method_retryable(self, method): - """ Checks if a given HTTP method should be retried upon, depending if - it is included on the method whitelist. + """Checks if a given HTTP method should be retried upon, depending if + it is included in the allowed_methods """ - if self.method_whitelist and method.upper() not in self.method_whitelist: - return False + # TODO: For now favor if the Retry implementation sets its own method_whitelist + # property outside of our constructor to avoid breaking custom implementations. + if "method_whitelist" in self.__dict__: + warnings.warn( + "Using 'method_whitelist' with Retry is deprecated and " + "will be removed in v2.0. Use 'allowed_methods' instead", + DeprecationWarning, + ) + allowed_methods = self.method_whitelist + else: + allowed_methods = self.allowed_methods + if allowed_methods and method.upper() not in allowed_methods: + return False return True def is_retry(self, method, status_code, has_retry_after=False): - """ Is this method/status code retryable? (Based on whitelists and control + """Is this method/status code retryable? (Based on allowlists and control variables such as the number of total retries to allow, whether to respect the Retry-After header, whether this header is present, and whether the returned status code is on the list of status codes to @@ -315,21 +460,39 @@ class Retry(object): if self.status_forcelist and status_code in self.status_forcelist: return True - return (self.total and self.respect_retry_after_header and - has_retry_after and (status_code in self.RETRY_AFTER_STATUS_CODES)) + return ( + self.total + and self.respect_retry_after_header + and has_retry_after + and (status_code in self.RETRY_AFTER_STATUS_CODES) + ) def is_exhausted(self): - """ Are we out of retries? """ - retry_counts = (self.total, self.connect, self.read, self.redirect, self.status) + """Are we out of retries?""" + retry_counts = ( + self.total, + self.connect, + self.read, + self.redirect, + self.status, + self.other, + ) retry_counts = list(filter(None, retry_counts)) if not retry_counts: return False return min(retry_counts) < 0 - def increment(self, method=None, url=None, response=None, error=None, - _pool=None, _stacktrace=None): - """ Return a new Retry object with incremented retry counters. + def increment( + self, + method=None, + url=None, + response=None, + error=None, + _pool=None, + _stacktrace=None, + ): + """Return a new Retry object with incremented retry counters. :param response: A response object, or None, if the server did not return a response. @@ -351,7 +514,8 @@ class Retry(object): read = self.read redirect = self.redirect status_count = self.status - cause = 'unknown' + other = self.other + cause = "unknown" status = None redirect_location = None @@ -369,31 +533,42 @@ class Retry(object): elif read is not None: read -= 1 + elif error: + # Other retry? + if other is not None: + other -= 1 + elif response and response.get_redirect_location(): # Redirect retry? if redirect is not None: redirect -= 1 - cause = 'too many redirects' + cause = "too many redirects" redirect_location = response.get_redirect_location() status = response.status else: # Incrementing because of a server error like a 500 in - # status_forcelist and a the given method is in the whitelist + # status_forcelist and the given method is in the allowed_methods cause = ResponseError.GENERIC_ERROR if response and response.status: if status_count is not None: status_count -= 1 - cause = ResponseError.SPECIFIC_ERROR.format( - status_code=response.status) + cause = ResponseError.SPECIFIC_ERROR.format(status_code=response.status) status = response.status - history = self.history + (RequestHistory(method, url, error, status, redirect_location),) + history = self.history + ( + RequestHistory(method, url, error, status, redirect_location), + ) new_retry = self.new( total=total, - connect=connect, read=read, redirect=redirect, status=status_count, - history=history) + connect=connect, + read=read, + redirect=redirect, + status=status_count, + other=other, + history=history, + ) if new_retry.is_exhausted(): raise MaxRetryError(_pool, url, error or ResponseError(cause)) @@ -403,9 +578,24 @@ class Retry(object): return new_retry def __repr__(self): - return ('{cls.__name__}(total={self.total}, connect={self.connect}, ' - 'read={self.read}, redirect={self.redirect}, status={self.status})').format( - cls=type(self), self=self) + return ( + "{cls.__name__}(total={self.total}, connect={self.connect}, " + "read={self.read}, redirect={self.redirect}, status={self.status})" + ).format(cls=type(self), self=self) + + def __getattr__(self, item): + if item == "method_whitelist": + # TODO: Remove this deprecated alias in v2.0 + warnings.warn( + "Using 'method_whitelist' with Retry is deprecated and " + "will be removed in v2.0. Use 'allowed_methods' instead", + DeprecationWarning, + ) + return self.allowed_methods + try: + return getattr(super(Retry, self), item) + except AttributeError: + return getattr(Retry, item) # For backwards compatibility (equivalent to pre-v1.9): diff --git a/pipenv/vendor/urllib3/util/ssl_.py b/pipenv/vendor/urllib3/util/ssl_.py index f271ce93..8d424bc4 100644 --- a/pipenv/vendor/urllib3/util/ssl_.py +++ b/pipenv/vendor/urllib3/util/ssl_.py @@ -1,28 +1,30 @@ from __future__ import absolute_import -import errno -import warnings -import hmac -import re +import hmac +import os +import sys +import warnings from binascii import hexlify, unhexlify from hashlib import md5, sha1, sha256 -from ..exceptions import SSLError, InsecurePlatformWarning, SNIMissingWarning +from ..exceptions import ( + InsecurePlatformWarning, + ProxySchemeUnsupported, + SNIMissingWarning, + SSLError, +) from ..packages import six -from ..packages.rfc3986 import abnf_regexp - +from .url import BRACELESS_IPV6_ADDRZ_RE, IPV4_RE SSLContext = None +SSLTransport = None HAS_SNI = False IS_PYOPENSSL = False IS_SECURETRANSPORT = False +ALPN_PROTOCOLS = ["http/1.1"] # Maps the length of a digest to a possible hash function producing this digest -HASHFUNC_MAP = { - 32: md5, - 40: sha1, - 64: sha256, -} +HASHFUNC_MAP = {32: md5, 40: sha1, 64: sha256} def _const_compare_digest_backport(a, b): @@ -33,49 +35,61 @@ def _const_compare_digest_backport(a, b): Returns True if the digests match, and False otherwise. """ result = abs(len(a) - len(b)) - for l, r in zip(bytearray(a), bytearray(b)): - result |= l ^ r + for left, right in zip(bytearray(a), bytearray(b)): + result |= left ^ right return result == 0 -_const_compare_digest = getattr(hmac, 'compare_digest', - _const_compare_digest_backport) - -# Borrow rfc3986's regular expressions for IPv4 -# and IPv6 addresses for use in is_ipaddress() -_IP_ADDRESS_REGEX = re.compile( - r'^(?:%s|%s|%s)$' % ( - abnf_regexp.IPv4_RE, - abnf_regexp.IPv6_RE, - abnf_regexp.IPv6_ADDRZ_RFC4007_RE - ) -) +_const_compare_digest = getattr(hmac, "compare_digest", _const_compare_digest_backport) try: # Test for SSL features import ssl - from ssl import wrap_socket, CERT_REQUIRED + from ssl import CERT_REQUIRED, wrap_socket +except ImportError: + pass + +try: from ssl import HAS_SNI # Has SNI? except ImportError: pass +try: + from .ssltransport import SSLTransport +except ImportError: + pass + + try: # Platform-specific: Python 3.6 from ssl import PROTOCOL_TLS + PROTOCOL_SSLv23 = PROTOCOL_TLS except ImportError: try: from ssl import PROTOCOL_SSLv23 as PROTOCOL_TLS + PROTOCOL_SSLv23 = PROTOCOL_TLS except ImportError: PROTOCOL_SSLv23 = PROTOCOL_TLS = 2 +try: + from ssl import PROTOCOL_TLS_CLIENT +except ImportError: + PROTOCOL_TLS_CLIENT = PROTOCOL_TLS + try: - from ssl import OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_COMPRESSION + from ssl import OP_NO_COMPRESSION, OP_NO_SSLv2, OP_NO_SSLv3 except ImportError: OP_NO_SSLv2, OP_NO_SSLv3 = 0x1000000, 0x2000000 OP_NO_COMPRESSION = 0x20000 +try: # OP_NO_TICKET was added in Python 3.6 + from ssl import OP_NO_TICKET +except ImportError: + OP_NO_TICKET = 0x4000 + + # A secure default. # Sources for more information on TLS ciphers: # @@ -93,26 +107,29 @@ except ImportError: # insecure ciphers for security reasons. # - NOTE: TLS 1.3 cipher suites are managed through a different interface # not exposed by CPython (yet!) and are enabled by default if they're available. -DEFAULT_CIPHERS = ':'.join([ - 'ECDHE+AESGCM', - 'ECDHE+CHACHA20', - 'DHE+AESGCM', - 'DHE+CHACHA20', - 'ECDH+AESGCM', - 'DH+AESGCM', - 'ECDH+AES', - 'DH+AES', - 'RSA+AESGCM', - 'RSA+AES', - '!aNULL', - '!eNULL', - '!MD5', - '!DSS', -]) +DEFAULT_CIPHERS = ":".join( + [ + "ECDHE+AESGCM", + "ECDHE+CHACHA20", + "DHE+AESGCM", + "DHE+CHACHA20", + "ECDH+AESGCM", + "DH+AESGCM", + "ECDH+AES", + "DH+AES", + "RSA+AESGCM", + "RSA+AES", + "!aNULL", + "!eNULL", + "!MD5", + "!DSS", + ] +) try: from ssl import SSLContext # Modern SSL? except ImportError: + class SSLContext(object): # Platform-specific: Python 2 def __init__(self, protocol_version): self.protocol = protocol_version @@ -129,32 +146,35 @@ except ImportError: self.certfile = certfile self.keyfile = keyfile - def load_verify_locations(self, cafile=None, capath=None): + def load_verify_locations(self, cafile=None, capath=None, cadata=None): self.ca_certs = cafile if capath is not None: raise SSLError("CA directories not supported in older Pythons") + if cadata is not None: + raise SSLError("CA data not supported in older Pythons") + def set_ciphers(self, cipher_suite): self.ciphers = cipher_suite def wrap_socket(self, socket, server_hostname=None, server_side=False): warnings.warn( - 'A true SSLContext object is not available. This prevents ' - 'urllib3 from configuring SSL appropriately and may cause ' - 'certain SSL connections to fail. You can upgrade to a newer ' - 'version of Python to solve this. For more information, see ' - 'https://urllib3.readthedocs.io/en/latest/advanced-usage.html' - '#ssl-warnings', - InsecurePlatformWarning + "A true SSLContext object is not available. This prevents " + "urllib3 from configuring SSL appropriately and may cause " + "certain SSL connections to fail. You can upgrade to a newer " + "version of Python to solve this. For more information, see " + "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" + "#ssl-warnings", + InsecurePlatformWarning, ) kwargs = { - 'keyfile': self.keyfile, - 'certfile': self.certfile, - 'ca_certs': self.ca_certs, - 'cert_reqs': self.verify_mode, - 'ssl_version': self.protocol, - 'server_side': server_side, + "keyfile": self.keyfile, + "certfile": self.certfile, + "ca_certs": self.ca_certs, + "cert_reqs": self.verify_mode, + "ssl_version": self.protocol, + "server_side": server_side, } return wrap_socket(socket, ciphers=self.ciphers, **kwargs) @@ -169,12 +189,11 @@ def assert_fingerprint(cert, fingerprint): Fingerprint as string of hexdigits, can be interspersed by colons. """ - fingerprint = fingerprint.replace(':', '').lower() + fingerprint = fingerprint.replace(":", "").lower() digest_length = len(fingerprint) hashfunc = HASHFUNC_MAP.get(digest_length) if not hashfunc: - raise SSLError( - 'Fingerprint of invalid length: {0}'.format(fingerprint)) + raise SSLError("Fingerprint of invalid length: {0}".format(fingerprint)) # We need encode() here for py32; works on py2 and p33. fingerprint_bytes = unhexlify(fingerprint.encode()) @@ -182,15 +201,18 @@ def assert_fingerprint(cert, fingerprint): cert_digest = hashfunc(cert).digest() if not _const_compare_digest(cert_digest, fingerprint_bytes): - raise SSLError('Fingerprints did not match. Expected "{0}", got "{1}".' - .format(fingerprint, hexlify(cert_digest))) + raise SSLError( + 'Fingerprints did not match. Expected "{0}", got "{1}".'.format( + fingerprint, hexlify(cert_digest) + ) + ) def resolve_cert_reqs(candidate): """ Resolves the argument to a numeric constant, which can be passed to the wrap_socket function/method from the ssl module. - Defaults to :data:`ssl.CERT_NONE`. + Defaults to :data:`ssl.CERT_REQUIRED`. If given a string it is assumed to be the name of the constant in the :mod:`ssl` module or its abbreviation. (So you can specify `REQUIRED` instead of `CERT_REQUIRED`. @@ -203,7 +225,7 @@ def resolve_cert_reqs(candidate): if isinstance(candidate, str): res = getattr(ssl, candidate, None) if res is None: - res = getattr(ssl, 'CERT_' + candidate) + res = getattr(ssl, "CERT_" + candidate) return res return candidate @@ -219,14 +241,15 @@ def resolve_ssl_version(candidate): if isinstance(candidate, str): res = getattr(ssl, candidate, None) if res is None: - res = getattr(ssl, 'PROTOCOL_' + candidate) + res = getattr(ssl, "PROTOCOL_" + candidate) return res return candidate -def create_urllib3_context(ssl_version=None, cert_reqs=None, - options=None, ciphers=None): +def create_urllib3_context( + ssl_version=None, cert_reqs=None, options=None, ciphers=None +): """All arguments have the same meaning as ``ssl_wrap_socket``. By default, this function does a lot of the same work that @@ -237,7 +260,7 @@ def create_urllib3_context(ssl_version=None, cert_reqs=None, If you wish to enable SSLv3, you can do:: - from urllib3.util import ssl_ + from pipenv.vendor.urllib3.util import ssl_ context = ssl_.create_urllib3_context() context.options &= ~ssl_.OP_NO_SSLv3 @@ -253,14 +276,18 @@ def create_urllib3_context(ssl_version=None, cert_reqs=None, ``ssl.CERT_REQUIRED``. :param options: Specific OpenSSL options. These default to ``ssl.OP_NO_SSLv2``, - ``ssl.OP_NO_SSLv3``, ``ssl.OP_NO_COMPRESSION``. + ``ssl.OP_NO_SSLv3``, ``ssl.OP_NO_COMPRESSION``, and ``ssl.OP_NO_TICKET``. :param ciphers: Which cipher suites to allow the server to select. :returns: Constructed SSLContext object with specified options :rtype: SSLContext """ - context = SSLContext(ssl_version or PROTOCOL_TLS) + # PROTOCOL_TLS is deprecated in Python 3.10 + if not ssl_version or ssl_version == PROTOCOL_TLS: + ssl_version = PROTOCOL_TLS_CLIENT + + context = SSLContext(ssl_version) context.set_ciphers(ciphers or DEFAULT_CIPHERS) @@ -276,21 +303,70 @@ def create_urllib3_context(ssl_version=None, cert_reqs=None, # Disable compression to prevent CRIME attacks for OpenSSL 1.0+ # (issue #309) options |= OP_NO_COMPRESSION + # TLSv1.2 only. Unless set explicitly, do not request tickets. + # This may save some bandwidth on wire, and although the ticket is encrypted, + # there is a risk associated with it being on wire, + # if the server is not rotating its ticketing keys properly. + options |= OP_NO_TICKET context.options |= options - context.verify_mode = cert_reqs - if getattr(context, 'check_hostname', None) is not None: # Platform-specific: Python 3.2 - # We do our own verification, including fingerprints and alternative - # hostnames. So disable it here - context.check_hostname = False + # Enable post-handshake authentication for TLS 1.3, see GH #1634. PHA is + # necessary for conditional client cert authentication with TLS 1.3. + # The attribute is None for OpenSSL <= 1.1.0 or does not exist in older + # versions of Python. We only enable on Python 3.7.4+ or if certificate + # verification is enabled to work around Python issue #37428 + # See: https://bugs.python.org/issue37428 + if (cert_reqs == ssl.CERT_REQUIRED or sys.version_info >= (3, 7, 4)) and getattr( + context, "post_handshake_auth", None + ) is not None: + context.post_handshake_auth = True + + def disable_check_hostname(): + if ( + getattr(context, "check_hostname", None) is not None + ): # Platform-specific: Python 3.2 + # We do our own verification, including fingerprints and alternative + # hostnames. So disable it here + context.check_hostname = False + + # The order of the below lines setting verify_mode and check_hostname + # matter due to safe-guards SSLContext has to prevent an SSLContext with + # check_hostname=True, verify_mode=NONE/OPTIONAL. This is made even more + # complex because we don't know whether PROTOCOL_TLS_CLIENT will be used + # or not so we don't know the initial state of the freshly created SSLContext. + if cert_reqs == ssl.CERT_REQUIRED: + context.verify_mode = cert_reqs + disable_check_hostname() + else: + disable_check_hostname() + context.verify_mode = cert_reqs + + # Enable logging of TLS session keys via defacto standard environment variable + # 'SSLKEYLOGFILE', if the feature is available (Python 3.8+). Skip empty values. + if hasattr(context, "keylog_filename"): + sslkeylogfile = os.environ.get("SSLKEYLOGFILE") + if sslkeylogfile: + context.keylog_filename = sslkeylogfile + return context -def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, - ca_certs=None, server_hostname=None, - ssl_version=None, ciphers=None, ssl_context=None, - ca_cert_dir=None, key_password=None): +def ssl_wrap_socket( + sock, + keyfile=None, + certfile=None, + cert_reqs=None, + ca_certs=None, + server_hostname=None, + ssl_version=None, + ciphers=None, + ssl_context=None, + ca_cert_dir=None, + key_password=None, + ca_cert_data=None, + tls_in_tls=False, +): """ All arguments except for server_hostname, ssl_context, and ca_cert_dir have the same meaning as they do when using :func:`ssl.wrap_socket`. @@ -308,28 +384,26 @@ def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, SSLContext.load_verify_locations(). :param key_password: Optional password if the keyfile is encrypted. + :param ca_cert_data: + Optional string containing CA certificates in PEM format suitable for + passing as the cadata parameter to SSLContext.load_verify_locations() + :param tls_in_tls: + Use SSLTransport to wrap the existing socket. """ context = ssl_context if context is None: # Note: This branch of code and all the variables in it are no longer # used by urllib3 itself. We should consider deprecating and removing # this code. - context = create_urllib3_context(ssl_version, cert_reqs, - ciphers=ciphers) + context = create_urllib3_context(ssl_version, cert_reqs, ciphers=ciphers) - if ca_certs or ca_cert_dir: + if ca_certs or ca_cert_dir or ca_cert_data: try: - context.load_verify_locations(ca_certs, ca_cert_dir) - except IOError as e: # Platform-specific: Python 2.7 + context.load_verify_locations(ca_certs, ca_cert_dir, ca_cert_data) + except (IOError, OSError) as e: raise SSLError(e) - # Py33 raises FileNotFoundError which subclasses OSError - # These are not equivalent unless we check the errno attribute - except OSError as e: # Platform-specific: Python 3.3 and beyond - if e.errno == errno.ENOENT: - raise SSLError(e) - raise - elif ssl_context is None and hasattr(context, 'load_default_certs'): + elif ssl_context is None and hasattr(context, "load_default_certs"): # try to load OS default certs; works well on Windows (require Python3.4+) context.load_default_certs() @@ -345,27 +419,39 @@ def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, else: context.load_cert_chain(certfile, keyfile, key_password) + try: + if hasattr(context, "set_alpn_protocols"): + context.set_alpn_protocols(ALPN_PROTOCOLS) + except NotImplementedError: # Defensive: in CI, we always have set_alpn_protocols + pass + # If we detect server_hostname is an IP address then the SNI # extension should not be used according to RFC3546 Section 3.1 - # We shouldn't warn the user if SNI isn't available but we would - # not be using SNI anyways due to IP address for server_hostname. - if ((server_hostname is not None and not is_ipaddress(server_hostname)) - or IS_SECURETRANSPORT): - if HAS_SNI and server_hostname is not None: - return context.wrap_socket(sock, server_hostname=server_hostname) - + use_sni_hostname = server_hostname and not is_ipaddress(server_hostname) + # SecureTransport uses server_hostname in certificate verification. + send_sni = (use_sni_hostname and HAS_SNI) or ( + IS_SECURETRANSPORT and server_hostname + ) + # Do not warn the user if server_hostname is an invalid SNI hostname. + if not HAS_SNI and use_sni_hostname: warnings.warn( - 'An HTTPS request has been made, but the SNI (Server Name ' - 'Indication) extension to TLS is not available on this platform. ' - 'This may cause the server to present an incorrect TLS ' - 'certificate, which can cause validation failures. You can upgrade to ' - 'a newer version of Python to solve this. For more information, see ' - 'https://urllib3.readthedocs.io/en/latest/advanced-usage.html' - '#ssl-warnings', - SNIMissingWarning + "An HTTPS request has been made, but the SNI (Server Name " + "Indication) extension to TLS is not available on this platform. " + "This may cause the server to present an incorrect TLS " + "certificate, which can cause validation failures. You can upgrade to " + "a newer version of Python to solve this. For more information, see " + "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" + "#ssl-warnings", + SNIMissingWarning, ) - return context.wrap_socket(sock) + if send_sni: + ssl_sock = _ssl_wrap_socket_impl( + sock, context, tls_in_tls, server_hostname=server_hostname + ) + else: + ssl_sock = _ssl_wrap_socket_impl(sock, context, tls_in_tls) + return ssl_sock def is_ipaddress(hostname): @@ -375,18 +461,35 @@ def is_ipaddress(hostname): :param str hostname: Hostname to examine. :return: True if the hostname is an IP address, False otherwise. """ - if six.PY3 and isinstance(hostname, bytes): + if not six.PY2 and isinstance(hostname, bytes): # IDN A-label bytes are ASCII compatible. - hostname = hostname.decode('ascii') - return _IP_ADDRESS_REGEX.match(hostname) is not None + hostname = hostname.decode("ascii") + return bool(IPV4_RE.match(hostname) or BRACELESS_IPV6_ADDRZ_RE.match(hostname)) def _is_key_file_encrypted(key_file): """Detects if a key file is encrypted or not.""" - with open(key_file, 'r') as f: + with open(key_file, "r") as f: for line in f: # Look for Proc-Type: 4,ENCRYPTED - if 'ENCRYPTED' in line: + if "ENCRYPTED" in line: return True return False + + +def _ssl_wrap_socket_impl(sock, ssl_context, tls_in_tls, server_hostname=None): + if tls_in_tls: + if not SSLTransport: + # Import error, ssl is not available. + raise ProxySchemeUnsupported( + "TLS in TLS requires support for the 'ssl' module" + ) + + SSLTransport._validate_ssl_context_for_tls_in_tls(ssl_context) + return SSLTransport(sock, ssl_context, server_hostname) + + if server_hostname: + return ssl_context.wrap_socket(sock, server_hostname=server_hostname) + else: + return ssl_context.wrap_socket(sock) diff --git a/pipenv/vendor/urllib3/util/ssltransport.py b/pipenv/vendor/urllib3/util/ssltransport.py new file mode 100644 index 00000000..77bf19ec --- /dev/null +++ b/pipenv/vendor/urllib3/util/ssltransport.py @@ -0,0 +1,221 @@ +import io +import socket +import ssl + +from pipenv.vendor.urllib3.exceptions import ProxySchemeUnsupported +from pipenv.vendor.urllib3.packages import six + +SSL_BLOCKSIZE = 16384 + + +class SSLTransport: + """ + The SSLTransport wraps an existing socket and establishes an SSL connection. + + Contrary to Python's implementation of SSLSocket, it allows you to chain + multiple TLS connections together. It's particularly useful if you need to + implement TLS within TLS. + + The class supports most of the socket API operations. + """ + + @staticmethod + def _validate_ssl_context_for_tls_in_tls(ssl_context): + """ + Raises a ProxySchemeUnsupported if the provided ssl_context can't be used + for TLS in TLS. + + The only requirement is that the ssl_context provides the 'wrap_bio' + methods. + """ + + if not hasattr(ssl_context, "wrap_bio"): + if six.PY2: + raise ProxySchemeUnsupported( + "TLS in TLS requires SSLContext.wrap_bio() which isn't " + "supported on Python 2" + ) + else: + raise ProxySchemeUnsupported( + "TLS in TLS requires SSLContext.wrap_bio() which isn't " + "available on non-native SSLContext" + ) + + def __init__( + self, socket, ssl_context, server_hostname=None, suppress_ragged_eofs=True + ): + """ + Create an SSLTransport around socket using the provided ssl_context. + """ + self.incoming = ssl.MemoryBIO() + self.outgoing = ssl.MemoryBIO() + + self.suppress_ragged_eofs = suppress_ragged_eofs + self.socket = socket + + self.sslobj = ssl_context.wrap_bio( + self.incoming, self.outgoing, server_hostname=server_hostname + ) + + # Perform initial handshake. + self._ssl_io_loop(self.sslobj.do_handshake) + + def __enter__(self): + return self + + def __exit__(self, *_): + self.close() + + def fileno(self): + return self.socket.fileno() + + def read(self, len=1024, buffer=None): + return self._wrap_ssl_read(len, buffer) + + def recv(self, len=1024, flags=0): + if flags != 0: + raise ValueError("non-zero flags not allowed in calls to recv") + return self._wrap_ssl_read(len) + + def recv_into(self, buffer, nbytes=None, flags=0): + if flags != 0: + raise ValueError("non-zero flags not allowed in calls to recv_into") + if buffer and (nbytes is None): + nbytes = len(buffer) + elif nbytes is None: + nbytes = 1024 + return self.read(nbytes, buffer) + + def sendall(self, data, flags=0): + if flags != 0: + raise ValueError("non-zero flags not allowed in calls to sendall") + count = 0 + with memoryview(data) as view, view.cast("B") as byte_view: + amount = len(byte_view) + while count < amount: + v = self.send(byte_view[count:]) + count += v + + def send(self, data, flags=0): + if flags != 0: + raise ValueError("non-zero flags not allowed in calls to send") + response = self._ssl_io_loop(self.sslobj.write, data) + return response + + def makefile( + self, mode="r", buffering=None, encoding=None, errors=None, newline=None + ): + """ + Python's httpclient uses makefile and buffered io when reading HTTP + messages and we need to support it. + + This is unfortunately a copy and paste of socket.py makefile with small + changes to point to the socket directly. + """ + if not set(mode) <= {"r", "w", "b"}: + raise ValueError("invalid mode %r (only r, w, b allowed)" % (mode,)) + + writing = "w" in mode + reading = "r" in mode or not writing + assert reading or writing + binary = "b" in mode + rawmode = "" + if reading: + rawmode += "r" + if writing: + rawmode += "w" + raw = socket.SocketIO(self, rawmode) + self.socket._io_refs += 1 + if buffering is None: + buffering = -1 + if buffering < 0: + buffering = io.DEFAULT_BUFFER_SIZE + if buffering == 0: + if not binary: + raise ValueError("unbuffered streams must be binary") + return raw + if reading and writing: + buffer = io.BufferedRWPair(raw, raw, buffering) + elif reading: + buffer = io.BufferedReader(raw, buffering) + else: + assert writing + buffer = io.BufferedWriter(raw, buffering) + if binary: + return buffer + text = io.TextIOWrapper(buffer, encoding, errors, newline) + text.mode = mode + return text + + def unwrap(self): + self._ssl_io_loop(self.sslobj.unwrap) + + def close(self): + self.socket.close() + + def getpeercert(self, binary_form=False): + return self.sslobj.getpeercert(binary_form) + + def version(self): + return self.sslobj.version() + + def cipher(self): + return self.sslobj.cipher() + + def selected_alpn_protocol(self): + return self.sslobj.selected_alpn_protocol() + + def selected_npn_protocol(self): + return self.sslobj.selected_npn_protocol() + + def shared_ciphers(self): + return self.sslobj.shared_ciphers() + + def compression(self): + return self.sslobj.compression() + + def settimeout(self, value): + self.socket.settimeout(value) + + def gettimeout(self): + return self.socket.gettimeout() + + def _decref_socketios(self): + self.socket._decref_socketios() + + def _wrap_ssl_read(self, len, buffer=None): + try: + return self._ssl_io_loop(self.sslobj.read, len, buffer) + except ssl.SSLError as e: + if e.errno == ssl.SSL_ERROR_EOF and self.suppress_ragged_eofs: + return 0 # eof, return 0. + else: + raise + + def _ssl_io_loop(self, func, *args): + """Performs an I/O loop between incoming/outgoing and the socket.""" + should_loop = True + ret = None + + while should_loop: + errno = None + try: + ret = func(*args) + except ssl.SSLError as e: + if e.errno not in (ssl.SSL_ERROR_WANT_READ, ssl.SSL_ERROR_WANT_WRITE): + # WANT_READ, and WANT_WRITE are expected, others are not. + raise e + errno = e.errno + + buf = self.outgoing.read() + self.socket.sendall(buf) + + if errno is None: + should_loop = False + elif errno == ssl.SSL_ERROR_WANT_READ: + buf = self.socket.recv(SSL_BLOCKSIZE) + if buf: + self.incoming.write(buf) + else: + self.incoming.write_eof() + return ret diff --git a/pipenv/vendor/urllib3/util/timeout.py b/pipenv/vendor/urllib3/util/timeout.py index a4d004a8..ff69593b 100644 --- a/pipenv/vendor/urllib3/util/timeout.py +++ b/pipenv/vendor/urllib3/util/timeout.py @@ -1,8 +1,10 @@ from __future__ import absolute_import + +import time + # The default socket timeout, used by httplib to indicate that no timeout was # specified by the user from socket import _GLOBAL_DEFAULT_TIMEOUT -import time from ..exceptions import TimeoutStateError @@ -16,22 +18,28 @@ current_time = getattr(time, "monotonic", time.time) class Timeout(object): - """ Timeout configuration. + """Timeout configuration. - Timeouts can be defined as a default for a pool:: + Timeouts can be defined as a default for a pool: - timeout = Timeout(connect=2.0, read=7.0) - http = PoolManager(timeout=timeout) - response = http.request('GET', 'http://example.com/') + .. code-block:: python - Or per-request (which overrides the default for the pool):: + timeout = Timeout(connect=2.0, read=7.0) + http = PoolManager(timeout=timeout) + response = http.request('GET', 'http://example.com/') - response = http.request('GET', 'http://example.com/', timeout=Timeout(10)) + Or per-request (which overrides the default for the pool): - Timeouts can be disabled by setting all the parameters to ``None``:: + .. code-block:: python - no_timeout = Timeout(connect=None, read=None) - response = http.request('GET', 'http://example.com/, timeout=no_timeout) + response = http.request('GET', 'http://example.com/', timeout=Timeout(10)) + + Timeouts can be disabled by setting all the parameters to ``None``: + + .. code-block:: python + + no_timeout = Timeout(connect=None, read=None) + response = http.request('GET', 'http://example.com/, timeout=no_timeout) :param total: @@ -42,26 +50,27 @@ class Timeout(object): Defaults to None. - :type total: integer, float, or None + :type total: int, float, or None :param connect: - The maximum amount of time to wait for a connection attempt to a server - to succeed. Omitting the parameter will default the connect timeout to - the system default, probably `the global default timeout in socket.py + The maximum amount of time (in seconds) to wait for a connection + attempt to a server to succeed. Omitting the parameter will default the + connect timeout to the system default, probably `the global default + timeout in socket.py <http://hg.python.org/cpython/file/603b4d593758/Lib/socket.py#l535>`_. None will set an infinite timeout for connection attempts. - :type connect: integer, float, or None + :type connect: int, float, or None :param read: - The maximum amount of time to wait between consecutive - read operations for a response from the server. Omitting - the parameter will default the read timeout to the system - default, probably `the global default timeout in socket.py + The maximum amount of time (in seconds) to wait between consecutive + read operations for a response from the server. Omitting the parameter + will default the read timeout to the system default, probably `the + global default timeout in socket.py <http://hg.python.org/cpython/file/603b4d593758/Lib/socket.py#l535>`_. None will set an infinite timeout. - :type read: integer, float, or None + :type read: int, float, or None .. note:: @@ -91,18 +100,25 @@ class Timeout(object): DEFAULT_TIMEOUT = _GLOBAL_DEFAULT_TIMEOUT def __init__(self, total=None, connect=_Default, read=_Default): - self._connect = self._validate_timeout(connect, 'connect') - self._read = self._validate_timeout(read, 'read') - self.total = self._validate_timeout(total, 'total') + self._connect = self._validate_timeout(connect, "connect") + self._read = self._validate_timeout(read, "read") + self.total = self._validate_timeout(total, "total") self._start_connect = None - def __str__(self): - return '%s(connect=%r, read=%r, total=%r)' % ( - type(self).__name__, self._connect, self._read, self.total) + def __repr__(self): + return "%s(connect=%r, read=%r, total=%r)" % ( + type(self).__name__, + self._connect, + self._read, + self.total, + ) + + # __str__ provided for backwards compatibility + __str__ = __repr__ @classmethod def _validate_timeout(cls, value, name): - """ Check that a timeout attribute is valid. + """Check that a timeout attribute is valid. :param value: The timeout value to validate :param name: The name of the timeout attribute to validate. This is @@ -118,29 +134,37 @@ class Timeout(object): return value if isinstance(value, bool): - raise ValueError("Timeout cannot be a boolean value. It must " - "be an int, float or None.") + raise ValueError( + "Timeout cannot be a boolean value. It must " + "be an int, float or None." + ) try: float(value) except (TypeError, ValueError): - raise ValueError("Timeout value %s was %s, but it must be an " - "int, float or None." % (name, value)) + raise ValueError( + "Timeout value %s was %s, but it must be an " + "int, float or None." % (name, value) + ) try: if value <= 0: - raise ValueError("Attempted to set %s timeout to %s, but the " - "timeout cannot be set to a value less " - "than or equal to 0." % (name, value)) + raise ValueError( + "Attempted to set %s timeout to %s, but the " + "timeout cannot be set to a value less " + "than or equal to 0." % (name, value) + ) except TypeError: # Python 3 - raise ValueError("Timeout value %s was %s, but it must be an " - "int, float or None." % (name, value)) + raise ValueError( + "Timeout value %s was %s, but it must be an " + "int, float or None." % (name, value) + ) return value @classmethod def from_float(cls, timeout): - """ Create a new Timeout from a legacy timeout value. + """Create a new Timeout from a legacy timeout value. The timeout value used by httplib.py sets the same timeout on the connect(), and recv() socket requests. This creates a :class:`Timeout` @@ -155,7 +179,7 @@ class Timeout(object): return Timeout(read=timeout, connect=timeout) def clone(self): - """ Create a copy of the timeout object + """Create a copy of the timeout object Timeout properties are stored per-pool but each request needs a fresh Timeout object to ensure each one has its own start/stop configured. @@ -166,11 +190,10 @@ class Timeout(object): # We can't use copy.deepcopy because that will also create a new object # for _GLOBAL_DEFAULT_TIMEOUT, which socket.py uses as a sentinel to # detect the user default. - return Timeout(connect=self._connect, read=self._read, - total=self.total) + return Timeout(connect=self._connect, read=self._read, total=self.total) def start_connect(self): - """ Start the timeout clock, used during a connect() attempt + """Start the timeout clock, used during a connect() attempt :raises urllib3.exceptions.TimeoutStateError: if you attempt to start a timer that has been started already. @@ -181,21 +204,22 @@ class Timeout(object): return self._start_connect def get_connect_duration(self): - """ Gets the time elapsed since the call to :meth:`start_connect`. + """Gets the time elapsed since the call to :meth:`start_connect`. - :return: Elapsed time. + :return: Elapsed time in seconds. :rtype: float :raises urllib3.exceptions.TimeoutStateError: if you attempt to get duration for a timer that hasn't been started. """ if self._start_connect is None: - raise TimeoutStateError("Can't get connect duration for timer " - "that has not started.") + raise TimeoutStateError( + "Can't get connect duration for timer that has not started." + ) return current_time() - self._start_connect @property def connect_timeout(self): - """ Get the value to use when setting a connection timeout. + """Get the value to use when setting a connection timeout. This will be a positive float or integer, the value None (never timeout), or the default system timeout. @@ -213,7 +237,7 @@ class Timeout(object): @property def read_timeout(self): - """ Get the value for the read timeout. + """Get the value for the read timeout. This assumes some time has elapsed in the connection timeout and computes the read timeout appropriately. @@ -228,15 +252,16 @@ class Timeout(object): :raises urllib3.exceptions.TimeoutStateError: If :meth:`start_connect` has not yet been called on this object. """ - if (self.total is not None and - self.total is not self.DEFAULT_TIMEOUT and - self._read is not None and - self._read is not self.DEFAULT_TIMEOUT): + if ( + self.total is not None + and self.total is not self.DEFAULT_TIMEOUT + and self._read is not None + and self._read is not self.DEFAULT_TIMEOUT + ): # In case the connect timeout has not yet been established. if self._start_connect is None: return self._read - return max(0, min(self.total - self.get_connect_duration(), - self._read)) + return max(0, min(self.total - self.get_connect_duration(), self._read)) elif self.total is not None and self.total is not self.DEFAULT_TIMEOUT: return max(0, self.total - self.get_connect_duration()) else: diff --git a/pipenv/vendor/urllib3/util/url.py b/pipenv/vendor/urllib3/util/url.py index 0bc6ced7..d26f8c52 100644 --- a/pipenv/vendor/urllib3/util/url.py +++ b/pipenv/vendor/urllib3/util/url.py @@ -1,43 +1,110 @@ from __future__ import absolute_import + import re from collections import namedtuple from ..exceptions import LocationParseError -from ..packages import six, rfc3986 -from ..packages.rfc3986.exceptions import RFC3986Exception, ValidationError -from ..packages.rfc3986.validators import Validator -from ..packages.rfc3986 import abnf_regexp, normalizers, compat, misc +from ..packages import six - -url_attrs = ['scheme', 'auth', 'host', 'port', 'path', 'query', 'fragment'] +url_attrs = ["scheme", "auth", "host", "port", "path", "query", "fragment"] # We only want to normalize urls with an HTTP(S) scheme. # urllib3 infers URLs without a scheme (None) to be http. -NORMALIZABLE_SCHEMES = ('http', 'https', None) +NORMALIZABLE_SCHEMES = ("http", "https", None) -# Regex for detecting URLs with schemes. RFC 3986 Section 3.1 -SCHEME_REGEX = re.compile(r"^(?:[a-zA-Z][a-zA-Z0-9+\-]*:|/)") +# Almost all of these patterns were derived from the +# 'rfc3986' module: https://github.com/python-hyper/rfc3986 +PERCENT_RE = re.compile(r"%[a-fA-F0-9]{2}") +SCHEME_RE = re.compile(r"^(?:[a-zA-Z][a-zA-Z0-9+-]*:|/)") +URI_RE = re.compile( + r"^(?:([a-zA-Z][a-zA-Z0-9+.-]*):)?" + r"(?://([^\\/?#]*))?" + r"([^?#]*)" + r"(?:\?([^#]*))?" + r"(?:#(.*))?$", + re.UNICODE | re.DOTALL, +) -PATH_CHARS = abnf_regexp.UNRESERVED_CHARS_SET | abnf_regexp.SUB_DELIMITERS_SET | {':', '@', '/'} -QUERY_CHARS = FRAGMENT_CHARS = PATH_CHARS | {'?'} +IPV4_PAT = r"(?:[0-9]{1,3}\.){3}[0-9]{1,3}" +HEX_PAT = "[0-9A-Fa-f]{1,4}" +LS32_PAT = "(?:{hex}:{hex}|{ipv4})".format(hex=HEX_PAT, ipv4=IPV4_PAT) +_subs = {"hex": HEX_PAT, "ls32": LS32_PAT} +_variations = [ + # 6( h16 ":" ) ls32 + "(?:%(hex)s:){6}%(ls32)s", + # "::" 5( h16 ":" ) ls32 + "::(?:%(hex)s:){5}%(ls32)s", + # [ h16 ] "::" 4( h16 ":" ) ls32 + "(?:%(hex)s)?::(?:%(hex)s:){4}%(ls32)s", + # [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 + "(?:(?:%(hex)s:)?%(hex)s)?::(?:%(hex)s:){3}%(ls32)s", + # [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 + "(?:(?:%(hex)s:){0,2}%(hex)s)?::(?:%(hex)s:){2}%(ls32)s", + # [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 + "(?:(?:%(hex)s:){0,3}%(hex)s)?::%(hex)s:%(ls32)s", + # [ *4( h16 ":" ) h16 ] "::" ls32 + "(?:(?:%(hex)s:){0,4}%(hex)s)?::%(ls32)s", + # [ *5( h16 ":" ) h16 ] "::" h16 + "(?:(?:%(hex)s:){0,5}%(hex)s)?::%(hex)s", + # [ *6( h16 ":" ) h16 ] "::" + "(?:(?:%(hex)s:){0,6}%(hex)s)?::", +] + +UNRESERVED_PAT = r"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._!\-~" +IPV6_PAT = "(?:" + "|".join([x % _subs for x in _variations]) + ")" +ZONE_ID_PAT = "(?:%25|%)(?:[" + UNRESERVED_PAT + "]|%[a-fA-F0-9]{2})+" +IPV6_ADDRZ_PAT = r"\[" + IPV6_PAT + r"(?:" + ZONE_ID_PAT + r")?\]" +REG_NAME_PAT = r"(?:[^\[\]%:/?#]|%[a-fA-F0-9]{2})*" +TARGET_RE = re.compile(r"^(/[^?#]*)(?:\?([^#]*))?(?:#.*)?$") + +IPV4_RE = re.compile("^" + IPV4_PAT + "$") +IPV6_RE = re.compile("^" + IPV6_PAT + "$") +IPV6_ADDRZ_RE = re.compile("^" + IPV6_ADDRZ_PAT + "$") +BRACELESS_IPV6_ADDRZ_RE = re.compile("^" + IPV6_ADDRZ_PAT[2:-2] + "$") +ZONE_ID_RE = re.compile("(" + ZONE_ID_PAT + r")\]$") + +_HOST_PORT_PAT = ("^(%s|%s|%s)(?::([0-9]{0,5}))?$") % ( + REG_NAME_PAT, + IPV4_PAT, + IPV6_ADDRZ_PAT, +) +_HOST_PORT_RE = re.compile(_HOST_PORT_PAT, re.UNICODE | re.DOTALL) + +UNRESERVED_CHARS = set( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-~" +) +SUB_DELIM_CHARS = set("!$&'()*+,;=") +USERINFO_CHARS = UNRESERVED_CHARS | SUB_DELIM_CHARS | {":"} +PATH_CHARS = USERINFO_CHARS | {"@", "/"} +QUERY_CHARS = FRAGMENT_CHARS = PATH_CHARS | {"?"} -class Url(namedtuple('Url', url_attrs)): +class Url(namedtuple("Url", url_attrs)): """ Data structure for representing an HTTP URL. Used as a return value for :func:`parse_url`. Both the scheme and host are normalized as they are both case-insensitive according to RFC 3986. """ + __slots__ = () - def __new__(cls, scheme=None, auth=None, host=None, port=None, path=None, - query=None, fragment=None): - if path and not path.startswith('/'): - path = '/' + path + def __new__( + cls, + scheme=None, + auth=None, + host=None, + port=None, + path=None, + query=None, + fragment=None, + ): + if path and not path.startswith("/"): + path = "/" + path if scheme is not None: scheme = scheme.lower() - return super(Url, cls).__new__(cls, scheme, auth, host, port, path, - query, fragment) + return super(Url, cls).__new__( + cls, scheme, auth, host, port, path, query, fragment + ) @property def hostname(self): @@ -47,10 +114,10 @@ class Url(namedtuple('Url', url_attrs)): @property def request_uri(self): """Absolute path including the query string.""" - uri = self.path or '/' + uri = self.path or "/" if self.query is not None: - uri += '?' + self.query + uri += "?" + self.query return uri @@ -58,7 +125,7 @@ class Url(namedtuple('Url', url_attrs)): def netloc(self): """Network location including host and port""" if self.port: - return '%s:%d' % (self.host, self.port) + return "%s:%d" % (self.host, self.port) return self.host @property @@ -81,23 +148,23 @@ class Url(namedtuple('Url', url_attrs)): 'http://username:password@host.com:80/path?query#fragment' """ scheme, auth, host, port, path, query, fragment = self - url = u'' + url = u"" # We use "is not None" we want things to happen with empty strings (or 0 port) if scheme is not None: - url += scheme + u'://' + url += scheme + u"://" if auth is not None: - url += auth + u'@' + url += auth + u"@" if host is not None: url += host if port is not None: - url += u':' + str(port) + url += u":" + str(port) if path is not None: url += path if query is not None: - url += u'?' + query + url += u"?" + query if fragment is not None: - url += u'#' + fragment + url += u"#" + fragment return url @@ -135,48 +202,140 @@ def split_first(s, delims): min_delim = d if min_idx is None or min_idx < 0: - return s, '', None + return s, "", None - return s[:min_idx], s[min_idx + 1:], min_delim + return s[:min_idx], s[min_idx + 1 :], min_delim -def _encode_invalid_chars(component, allowed_chars, encoding='utf-8'): +def _encode_invalid_chars(component, allowed_chars, encoding="utf-8"): """Percent-encodes a URI component without reapplying - onto an already percent-encoded component. Based on - rfc3986.normalizers.encode_component() + onto an already percent-encoded component. """ if component is None: return component + component = six.ensure_text(component) + + # Normalize existing percent-encoded bytes. # Try to see if the component we're encoding is already percent-encoded # so we can skip all '%' characters but still encode all others. - percent_encodings = len(normalizers.PERCENT_MATCHER.findall( - compat.to_str(component, encoding))) - - uri_bytes = component.encode('utf-8', 'surrogatepass') - is_percent_encoded = percent_encodings == uri_bytes.count(b'%') + component, percent_encodings = PERCENT_RE.subn( + lambda match: match.group(0).upper(), component + ) + uri_bytes = component.encode("utf-8", "surrogatepass") + is_percent_encoded = percent_encodings == uri_bytes.count(b"%") encoded_component = bytearray() for i in range(0, len(uri_bytes)): # Will return a single character bytestring on both Python 2 & 3 - byte = uri_bytes[i:i+1] + byte = uri_bytes[i : i + 1] byte_ord = ord(byte) - if ((is_percent_encoded and byte == b'%') - or (byte_ord < 128 and byte.decode() in allowed_chars)): - encoded_component.extend(byte) + if (is_percent_encoded and byte == b"%") or ( + byte_ord < 128 and byte.decode() in allowed_chars + ): + encoded_component += byte continue - encoded_component.extend('%{0:02x}'.format(byte_ord).encode().upper()) + encoded_component.extend(b"%" + (hex(byte_ord)[2:].encode().zfill(2).upper())) return encoded_component.decode(encoding) +def _remove_path_dot_segments(path): + # See http://tools.ietf.org/html/rfc3986#section-5.2.4 for pseudo-code + segments = path.split("/") # Turn the path into a list of segments + output = [] # Initialize the variable to use to store output + + for segment in segments: + # '.' is the current directory, so ignore it, it is superfluous + if segment == ".": + continue + # Anything other than '..', should be appended to the output + elif segment != "..": + output.append(segment) + # In this case segment == '..', if we can, we should pop the last + # element + elif output: + output.pop() + + # If the path starts with '/' and the output is empty or the first string + # is non-empty + if path.startswith("/") and (not output or output[0]): + output.insert(0, "") + + # If the path starts with '/.' or '/..' ensure we add one more empty + # string to add a trailing '/' + if path.endswith(("/.", "/..")): + output.append("") + + return "/".join(output) + + +def _normalize_host(host, scheme): + if host: + if isinstance(host, six.binary_type): + host = six.ensure_str(host) + + if scheme in NORMALIZABLE_SCHEMES: + is_ipv6 = IPV6_ADDRZ_RE.match(host) + if is_ipv6: + match = ZONE_ID_RE.search(host) + if match: + start, end = match.span(1) + zone_id = host[start:end] + + if zone_id.startswith("%25") and zone_id != "%25": + zone_id = zone_id[3:] + else: + zone_id = zone_id[1:] + zone_id = "%" + _encode_invalid_chars(zone_id, UNRESERVED_CHARS) + return host[:start].lower() + zone_id + host[end:] + else: + return host.lower() + elif not IPV4_RE.match(host): + return six.ensure_str( + b".".join([_idna_encode(label) for label in host.split(".")]) + ) + return host + + +def _idna_encode(name): + if name and any([ord(x) > 128 for x in name]): + try: + import pipenv.vendor.idna as idna + except ImportError: + six.raise_from( + LocationParseError("Unable to parse URL without the 'idna' module"), + None, + ) + try: + return idna.encode(name.lower(), strict=True, std3_rules=True) + except idna.IDNAError: + six.raise_from( + LocationParseError(u"Name '%s' is not a valid IDNA label" % name), None + ) + return name.lower().encode("ascii") + + +def _encode_target(target): + """Percent-encodes a request target so that there are no invalid characters""" + path, query = TARGET_RE.match(target).groups() + target = _encode_invalid_chars(path, PATH_CHARS) + query = _encode_invalid_chars(query, QUERY_CHARS) + if query is not None: + target += "?" + query + return target + + def parse_url(url): """ Given a url, return a parsed :class:`.Url` namedtuple. Best-effort is performed to parse incomplete urls. Fields not provided will be None. This parser is RFC 3986 compliant. + The parser logic and helper functions are based heavily on + work done in the ``rfc3986`` module. + :param str url: URL to parse into a :class:`.Url` namedtuple. Partly backwards-compatible with :mod:`urlparse`. @@ -194,90 +353,74 @@ def parse_url(url): # Empty return Url() - is_string = not isinstance(url, six.binary_type) - - # RFC 3986 doesn't like URLs that have a host but don't start - # with a scheme and we support URLs like that so we need to - # detect that problem and add an empty scheme indication. - # We don't get hurt on path-only URLs here as it's stripped - # off and given an empty scheme anyways. - if not SCHEME_REGEX.search(url): + source_url = url + if not SCHEME_RE.search(url): url = "//" + url - def idna_encode(name): - if name and any([ord(x) > 128 for x in name]): - try: - import idna - except ImportError: - raise LocationParseError("Unable to parse URL without the 'idna' module") - try: - return idna.encode(name.lower(), strict=True, std3_rules=True) - except idna.IDNAError: - raise LocationParseError(u"Name '%s' is not a valid IDNA label" % name) - return name - try: - split_iri = misc.IRI_MATCHER.match(compat.to_str(url)).groupdict() - iri_ref = rfc3986.IRIReference( - split_iri['scheme'], split_iri['authority'], - _encode_invalid_chars(split_iri['path'], PATH_CHARS), - _encode_invalid_chars(split_iri['query'], QUERY_CHARS), - _encode_invalid_chars(split_iri['fragment'], FRAGMENT_CHARS) - ) - has_authority = iri_ref.authority is not None - uri_ref = iri_ref.encode(idna_encoder=idna_encode) - except (ValueError, RFC3986Exception): - return six.raise_from(LocationParseError(url), None) + scheme, authority, path, query, fragment = URI_RE.match(url).groups() + normalize_uri = scheme is None or scheme.lower() in NORMALIZABLE_SCHEMES - # rfc3986 strips the authority if it's invalid - if has_authority and uri_ref.authority is None: - raise LocationParseError(url) + if scheme: + scheme = scheme.lower() - # Only normalize schemes we understand to not break http+unix - # or other schemes that don't follow RFC 3986. - if uri_ref.scheme is None or uri_ref.scheme.lower() in NORMALIZABLE_SCHEMES: - uri_ref = uri_ref.normalize() + if authority: + auth, _, host_port = authority.rpartition("@") + auth = auth or None + host, port = _HOST_PORT_RE.match(host_port).groups() + if auth and normalize_uri: + auth = _encode_invalid_chars(auth, USERINFO_CHARS) + if port == "": + port = None + else: + auth, host, port = None, None, None - # Validate all URIReference components and ensure that all - # components that were set before are still set after - # normalization has completed. - validator = Validator() - try: - validator.check_validity_of( - *validator.COMPONENT_NAMES - ).validate(uri_ref) - except ValidationError: - return six.raise_from(LocationParseError(url), None) + if port is not None: + port = int(port) + if not (0 <= port <= 65535): + raise LocationParseError(url) + + host = _normalize_host(host, scheme) + + if normalize_uri and path: + path = _remove_path_dot_segments(path) + path = _encode_invalid_chars(path, PATH_CHARS) + if normalize_uri and query: + query = _encode_invalid_chars(query, QUERY_CHARS) + if normalize_uri and fragment: + fragment = _encode_invalid_chars(fragment, FRAGMENT_CHARS) + + except (ValueError, AttributeError): + return six.raise_from(LocationParseError(source_url), None) # For the sake of backwards compatibility we put empty # string values for path if there are any defined values # beyond the path in the URL. # TODO: Remove this when we break backwards compatibility. - path = uri_ref.path if not path: - if (uri_ref.query is not None - or uri_ref.fragment is not None): + if query is not None or fragment is not None: path = "" else: path = None # Ensure that each part of the URL is a `str` for # backwards compatibility. - def to_input_type(x): - if x is None: - return None - elif not is_string and not isinstance(x, six.binary_type): - return x.encode('utf-8') - return x + if isinstance(url, six.text_type): + ensure_func = six.ensure_text + else: + ensure_func = six.ensure_str + + def ensure_type(x): + return x if x is None else ensure_func(x) return Url( - scheme=to_input_type(uri_ref.scheme), - auth=to_input_type(uri_ref.userinfo), - host=to_input_type(uri_ref.host), - port=int(uri_ref.port) if uri_ref.port is not None else None, - path=to_input_type(path), - query=to_input_type(uri_ref.query), - fragment=to_input_type(uri_ref.fragment) + scheme=ensure_type(scheme), + auth=ensure_type(auth), + host=ensure_type(host), + port=port, + path=ensure_type(path), + query=ensure_type(query), + fragment=ensure_type(fragment), ) @@ -286,4 +429,4 @@ def get_host(url): Deprecated. Use :func:`parse_url` instead. """ p = parse_url(url) - return p.scheme or 'http', p.hostname, p.port + return p.scheme or "http", p.hostname, p.port diff --git a/pipenv/vendor/urllib3/util/wait.py b/pipenv/vendor/urllib3/util/wait.py index 4db71baf..c280646c 100644 --- a/pipenv/vendor/urllib3/util/wait.py +++ b/pipenv/vendor/urllib3/util/wait.py @@ -1,7 +1,8 @@ import errno -from functools import partial import select import sys +from functools import partial + try: from time import monotonic except ImportError: @@ -40,6 +41,8 @@ if sys.version_info >= (3, 5): # Modern Python, that retries syscalls by default def _retry_on_intr(fn, timeout): return fn(timeout) + + else: # Old and broken Pythons. def _retry_on_intr(fn, timeout): @@ -137,14 +140,14 @@ def wait_for_socket(*args, **kwargs): def wait_for_read(sock, timeout=None): - """ Waits for reading to be available on a given socket. + """Waits for reading to be available on a given socket. Returns True if the socket is readable, or False if the timeout expired. """ return wait_for_socket(sock, read=True, timeout=timeout) def wait_for_write(sock, timeout=None): - """ Waits for writing to be available on a given socket. + """Waits for writing to be available on a given socket. Returns True if the socket is readable, or False if the timeout expired. """ return wait_for_socket(sock, write=True, timeout=timeout) diff --git a/pipenv/vendor/vendor.txt b/pipenv/vendor/vendor.txt index ac5c524d..3d7b39ea 100644 --- a/pipenv/vendor/vendor.txt +++ b/pipenv/vendor/vendor.txt @@ -1,51 +1,48 @@ -appdirs==1.4.3 -backports.shutil_get_terminal_size==1.0.0 -backports.weakref==1.0.post1 -click==7.0 -click-completion==0.5.1 +appdirs==1.4.4 +attrs==21.2.0 +cached-property==1.5.2 +cerberus==1.3.4 +certifi==2021.5.30 +charset-normalizer==2.0.7 click-didyoumean==0.0.3 -colorama==0.4.1 -delegator.py==0.1.1 - pexpect==4.7.0 - ptyprocess==0.6.0 -python-dotenv==0.10.2 -first==2.0.1 -iso8601==0.1.12 -jinja2==2.10. -markupsafe==1.1.1 -parse==1.12.0 -pathlib2==2.3.3 - scandir==1.10 -pipdeptree==0.13.2 -pipreqs==0.4.9 - docopt==0.6.2 - yarg==0.1.9 -pythonfinder==1.2.1 -requests==2.22.0 - chardet==3.0.4 - idna==2.8 - urllib3==1.25.2 - certifi==2019.3.9 -requirementslib==1.5.1 - attrs==19.1.0 - distlib==0.2.9 - packaging==19.0 - pyparsing==2.3.1 - git+https://github.com/sarugaku/plette.git@master#egg=plette - tomlkit==0.5.3 -shellingham==1.3.1 -six==1.12.0 -semver==2.8.1 -toml==0.10.0 -cached-property==1.5.1 -vistir==0.4.2 -pip-shims==0.3.2 -enum34==1.1.6 -yaspin==0.14.3 -cerberus==1.3.1 -resolvelib==0.2.2 -backports.functools_lru_cache==1.5 -pep517==0.5.0 - pytoml==0.1.20 -git+https://github.com/sarugaku/passa.git@master#egg=passa -orderedmultidict==1.0 +click==8.0.3 +colorama==0.4.4 +distlib==0.3.2 +docopt==0.6.2 +dparse==0.5.1 +funcsigs==1.0.2 +idna==3.2 +importlib-metadata==4.6.1 +importlib-resources==5.2.0 +iso8601==0.1.16 +markupsafe==2.0.1 +more-itertools==8.8.0 +orderedmultidict==1.0.1 +packaging==21.0 +parse==1.19.0 +pep517==0.11.0 +pexpect==4.8.0 +pip-shims==0.6.0 +pipdeptree==2.0.0 +pipreqs==0.4.10 +platformdirs==2.4.0 +plette[validation]==0.2.3 +ptyprocess==0.7.0 +pyparsing==2.4.7 +python-dateutil==2.8.2 +python-dotenv==0.19.0 +pythonfinder==1.2.9 +requests==2.26.0 +requirementslib==1.6.1 +shellingham==1.4.0 +six==1.16.0 +termcolor==1.1.0 +toml==0.10.2 +tomli==1.1.0 +tomlkit==0.7.2 +urllib3==1.26.6 +vistir==0.5.2 +wheel==0.36.2 +yarg==0.1.9 +yaspin==2.0.0 +zipp==3.5.0 diff --git a/pipenv/vendor/vendor_pip.txt b/pipenv/vendor/vendor_pip.txt deleted file mode 100644 index 7b548255..00000000 --- a/pipenv/vendor/vendor_pip.txt +++ /dev/null @@ -1,23 +0,0 @@ -appdirs==1.4.3 -CacheControl==0.12.5 -colorama==0.4.1 -distlib==0.2.8 -distro==1.4.0 -html5lib==1.0.1 -ipaddress==1.0.22 # Only needed on 2.6 and 2.7 -lockfile==0.12.2 -msgpack==0.5.6 -packaging==19.0 -pep517==0.5.0 -progress==1.5 -pyparsing==2.4.0 -pytoml==0.1.20 -requests==2.21.0 - certifi==2019.3.9 - chardet==3.0.4 - idna==2.8 - urllib3==1.25.2 -retrying==1.3.3 -setuptools==41.0.1 -six==1.12.0 -webencodings==0.5.1 diff --git a/pipenv/vendor/vistir/__init__.py b/pipenv/vendor/vistir/__init__.py index 821ea29b..1227629a 100644 --- a/pipenv/vendor/vistir/__init__.py +++ b/pipenv/vendor/vistir/__init__.py @@ -36,7 +36,7 @@ from .misc import ( from .path import create_tracked_tempdir, create_tracked_tempfile, mkdir_p, rmtree from .spin import create_spinner -__version__ = "0.4.2" +__version__ = "0.5.2" __all__ = [ diff --git a/pipenv/vendor/vistir/_winconsole.py b/pipenv/vendor/vistir/_winconsole.py index a29c22d8..cb5e6d27 100644 --- a/pipenv/vendor/vistir/_winconsole.py +++ b/pipenv/vendor/vistir/_winconsole.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# This Module is taken in full from the click project +# This Module is taken in part from the click project and expanded # see https://github.com/pallets/click/blob/6cafd32/click/_winconsole.py # Copyright © 2014 by the Pallets team. @@ -60,11 +60,11 @@ from ctypes import ( py_object, windll, ) -from ctypes.wintypes import LPCWSTR, LPWSTR +from ctypes.wintypes import HANDLE, LPCWSTR, LPWSTR from itertools import count import msvcrt -from six import PY2, text_type +from pipenv.vendor.six import PY2, text_type from .compat import IS_TYPE_CHECKING from .misc import StreamWrapper, run, to_text @@ -83,19 +83,18 @@ if IS_TYPE_CHECKING: c_ssize_p = POINTER(c_ssize_t) - -kernel32 = windll.kernel32 -GetStdHandle = kernel32.GetStdHandle -ReadConsoleW = kernel32.ReadConsoleW -WriteConsoleW = kernel32.WriteConsoleW -GetLastError = kernel32.GetLastError -GetConsoleCursorInfo = kernel32.GetConsoleCursorInfo -SetConsoleCursorInfo = kernel32.SetConsoleCursorInfo -GetCommandLineW = WINFUNCTYPE(LPWSTR)(("GetCommandLineW", windll.kernel32)) CommandLineToArgvW = WINFUNCTYPE(POINTER(LPWSTR), LPCWSTR, POINTER(c_int))( ("CommandLineToArgvW", windll.shell32) ) - +kernel32 = windll.kernel32 +GetLastError = kernel32.GetLastError +GetCommandLineW = WINFUNCTYPE(LPWSTR)(("GetCommandLineW", windll.kernel32)) +GetConsoleCursorInfo = kernel32.GetConsoleCursorInfo +GetStdHandle = kernel32.GetStdHandle +LocalFree = WINFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p)(("LocalFree", windll.kernel32)) +ReadConsoleW = kernel32.ReadConsoleW +SetConsoleCursorInfo = kernel32.SetConsoleCursorInfo +WriteConsoleW = kernel32.WriteConsoleW # XXX: Added for cursor hiding on windows STDOUT_HANDLE_ID = ctypes.c_ulong(-11) @@ -354,7 +353,11 @@ if PY2: def _get_windows_argv(): argc = c_int(0) argv_unicode = CommandLineToArgvW(GetCommandLineW(), byref(argc)) - argv = [argv_unicode[i] for i in range(0, argc.value)] + try: + argv = [argv_unicode[i] for i in range(0, argc.value)] + finally: + LocalFree(argv_unicode) + del argv_unicode if not hasattr(sys, "frozen"): argv = argv[1:] diff --git a/pipenv/vendor/vistir/backports/surrogateescape.py b/pipenv/vendor/vistir/backports/surrogateescape.py index 0532be08..5897d573 100644 --- a/pipenv/vendor/vistir/backports/surrogateescape.py +++ b/pipenv/vendor/vistir/backports/surrogateescape.py @@ -9,7 +9,7 @@ Source: misc/python/surrogateescape.py in https://bitbucket.org/haypo/misc import codecs import sys -import six +import pipenv.vendor.six as six FS_ERRORS = "surrogateescape" @@ -35,7 +35,7 @@ if six.PY3: _unichr = chr bytes_chr = lambda code: bytes((code,)) else: - _unichr = unichr + _unichr = unichr # type: ignore bytes_chr = chr diff --git a/pipenv/vendor/vistir/backports/tempfile.py b/pipenv/vendor/vistir/backports/tempfile.py index a3d7f3df..45b8a086 100644 --- a/pipenv/vendor/vistir/backports/tempfile.py +++ b/pipenv/vendor/vistir/backports/tempfile.py @@ -7,12 +7,12 @@ import os import sys from tempfile import _bin_openflags, _mkstemp_inner, gettempdir -import six +import pipenv.vendor.six as six try: from weakref import finalize except ImportError: - from pipenv.vendor.backports.weakref import finalize + from backports.weakref import finalize def fs_encode(path): diff --git a/pipenv/vendor/vistir/cmdparse.py b/pipenv/vendor/vistir/cmdparse.py index 664ae7df..b04274a5 100644 --- a/pipenv/vendor/vistir/cmdparse.py +++ b/pipenv/vendor/vistir/cmdparse.py @@ -5,7 +5,7 @@ import itertools import re import shlex -import six +import pipenv.vendor.six as six __all__ = ["ScriptEmptyError", "Script"] diff --git a/pipenv/vendor/vistir/compat.py b/pipenv/vendor/vistir/compat.py index ee96f761..336f4f7c 100644 --- a/pipenv/vendor/vistir/compat.py +++ b/pipenv/vendor/vistir/compat.py @@ -8,7 +8,7 @@ import sys import warnings from tempfile import mkdtemp -import six +import pipenv.vendor.six as six from .backports.tempfile import NamedTemporaryFile as _NamedTemporaryFile @@ -29,11 +29,23 @@ __all__ = [ "TemporaryDirectory", "NamedTemporaryFile", "to_native_string", - "Iterable", + "samefile", "Mapping", - "Sequence", - "Set", + "Hashable", + "MutableMapping", + "Container", + "Iterator", + "KeysView", "ItemsView", + "MappingView", + "Iterable", + "Set", + "Sequence", + "Sized", + "ValuesView", + "MutableSet", + "MutableSequence", + "Callable", "fs_encode", "fs_decode", "_fs_encode_errors", @@ -43,24 +55,80 @@ __all__ = [ if sys.version_info >= (3, 5): # pragma: no cover from pathlib import Path else: # pragma: no cover - from pipenv.vendor.pathlib2 import Path + from pathlib2 import Path -if six.PY3: # pragma: no cover +if sys.version_info >= (3, 4): # pragma: no cover # Only Python 3.4+ is supported from functools import lru_cache, partialmethod from tempfile import NamedTemporaryFile from shutil import get_terminal_size from weakref import finalize + from collections.abc import ( + Mapping, + Hashable, + MutableMapping, + Container, + Iterator, + KeysView, + ItemsView, + MappingView, + Iterable, + Set, + Sequence, + Sized, + ValuesView, + MutableSet, + MutableSequence, + Callable, + ) + from os.path import samefile + else: # pragma: no cover # Only Python 2.7 is supported - from pipenv.vendor.backports.functools_lru_cache import lru_cache + from backports.functools_lru_cache import lru_cache + from backports.shutil_get_terminal_size import get_terminal_size from .backports.functools import partialmethod # type: ignore - from pipenv.vendor.backports.shutil_get_terminal_size import get_terminal_size from .backports.surrogateescape import register_surrogateescape + from collections import ( + Mapping, + Hashable, + MutableMapping, + Container, + Iterator, + KeysView, + ItemsView, + MappingView, + Iterable, + Set, + Sequence, + Sized, + ValuesView, + MutableSet, + MutableSequence, + Callable, + ) register_surrogateescape() NamedTemporaryFile = _NamedTemporaryFile - from pipenv.vendor.backports.weakref import finalize # type: ignore + from backports.weakref import finalize # type: ignore + + try: + from os.path import samefile + except ImportError: + + def samestat(s1, s2): + """Test whether two stat buffers reference the same file.""" + return s1.st_ino == s2.st_ino and s1.st_dev == s2.st_dev + + def samefile(f1, f2): + """Test whether two pathnames reference the same actual file or + directory This is determined by the device number and i-node number + and raises an exception if an os.stat() call on either pathname + fails.""" + s1 = os.stat(f1) + s2 = os.stat(f2) + return samestat(s1, s2) + try: # Introduced Python 3.5 @@ -76,7 +144,7 @@ if six.PY2: # pragma: no cover pass class FileNotFoundError(IOError): - """No such file or directory""" + """No such file or directory.""" def __init__(self, *args, **kwargs): self.errno = errno.ENOENT @@ -95,7 +163,7 @@ if six.PY2: # pragma: no cover super(TimeoutError, self).__init__(*args, **kwargs) class IsADirectoryError(OSError): - """The command does not work on directories""" + """The command does not work on directories.""" def __init__(self, *args, **kwargs): self.errno = errno.EISDIR @@ -118,24 +186,6 @@ else: # pragma: no cover ) from io import StringIO -six.add_move( - six.MovedAttribute("Iterable", "collections", "collections.abc") -) # type: ignore -six.add_move( - six.MovedAttribute("Mapping", "collections", "collections.abc") -) # type: ignore -six.add_move( - six.MovedAttribute("Sequence", "collections", "collections.abc") -) # type: ignore -six.add_move(six.MovedAttribute("Set", "collections", "collections.abc")) # type: ignore -six.add_move( - six.MovedAttribute("ItemsView", "collections", "collections.abc") -) # type: ignore - -# fmt: off -from six.moves import ItemsView, Iterable, Mapping, Sequence, Set # type: ignore # noqa # isort:skip -# fmt: on - if not sys.warnoptions: warnings.simplefilter("default", ResourceWarning) @@ -213,7 +263,7 @@ class TemporaryDirectory(object): def is_bytes(string): - """Check if a string is a bytes instance + """Check if a string is a bytes instance. :param Union[str, bytes] string: A string that may be string or bytes like :return: Whether the provided string is a bytes type or not @@ -227,7 +277,7 @@ def is_bytes(string): def fs_str(string): - """Encodes a string into the proper filesystem encoding + """Encodes a string into the proper filesystem encoding. Borrowed from pip-tools """ @@ -239,8 +289,7 @@ def fs_str(string): def _get_path(path): - """ - Fetch the string value from a path-like object + """Fetch the string value from a path-like object. Returns **None** if there is no string value. """ @@ -324,8 +373,7 @@ def _chunks(b, indexes): def fs_encode(path): - """ - Encode a filesystem path to the proper filesystem encoding + """Encode a filesystem path to the proper filesystem encoding. :param Union[str, bytes] path: A string-like path :returns: A bytes-encoded filesystem path representation @@ -349,8 +397,7 @@ def fs_encode(path): def fs_decode(path): - """ - Decode a filesystem path using the proper filesystem encoding + """Decode a filesystem path using the proper filesystem encoding. :param path: The filesystem path to decode from bytes or string :return: The filesystem path, decoded with the determined encoding @@ -376,17 +423,15 @@ def fs_decode(path): if sys.version_info[0] < 3: # pragma: no cover - _fs_encode_errors = "surrogateescape" + _fs_encode_errors = "surrogatepass" if sys.platform == "win32" else "surrogateescape" _fs_decode_errors = "surrogateescape" _fs_encoding = "utf-8" else: # pragma: no cover _fs_encoding = "utf-8" + _fs_decode_errors = "surrogateescape" if sys.platform.startswith("win"): _fs_error_fn = None - if sys.version_info[:2] > (3, 4): - alt_strategy = "surrogatepass" - else: - alt_strategy = "surrogateescape" + _fs_encode_errors = "surrogatepass" else: if sys.version_info >= (3, 3): _fs_encoding = sys.getfilesystemencoding() @@ -394,8 +439,8 @@ else: # pragma: no cover _fs_encoding = sys.getdefaultencoding() alt_strategy = "surrogateescape" _fs_error_fn = getattr(sys, "getfilesystemencodeerrors", None) - _fs_encode_errors = _fs_error_fn() if _fs_error_fn else alt_strategy - _fs_decode_errors = _fs_error_fn() if _fs_error_fn else alt_strategy + _fs_encode_errors = _fs_error_fn() if _fs_error_fn else alt_strategy + _fs_decode_errors = _fs_error_fn() if _fs_error_fn else _fs_decode_errors _byte = chr if sys.version_info < (3,) else lambda i: bytes([i]) diff --git a/pipenv/vendor/vistir/contextmanagers.py b/pipenv/vendor/vistir/contextmanagers.py index 49ec964f..251641d5 100644 --- a/pipenv/vendor/vistir/contextmanagers.py +++ b/pipenv/vendor/vistir/contextmanagers.py @@ -5,13 +5,38 @@ import io import os import stat import sys -from contextlib import contextmanager +from contextlib import closing, contextmanager -import six +import pipenv.vendor.six as six -from .compat import NamedTemporaryFile, Path +from .compat import IS_TYPE_CHECKING, NamedTemporaryFile, Path from .path import is_file_url, is_valid_url, path_to_url, url_to_path +if IS_TYPE_CHECKING: + from typing import ( + Any, + Bytes, + Callable, + ContextManager, + Dict, + IO, + Iterator, + Optional, + Union, + Text, + Tuple, + TypeVar, + ) + from types import ModuleType + from pipenv.vendor.requests import Session + from pipenv.vendor.six.moves.http_client import HTTPResponse as Urllib_HTTPResponse + from pipenv.vendor.urllib3.response import HTTPResponse as Urllib3_HTTPResponse + from .spin import VistirSpinner, DummySpinner + + TSpinner = Union[VistirSpinner, DummySpinner] + _T = TypeVar("_T") + + __all__ = [ "temp_environ", "temp_path", @@ -29,6 +54,7 @@ __all__ = [ # See https://github.com/berdario/pew/blob/master/pew/_utils.py#L82 @contextmanager def temp_environ(): + # type: () -> Iterator[None] """Allow the ability to set os.environ temporarily""" environ = dict(os.environ) try: @@ -40,17 +66,30 @@ def temp_environ(): @contextmanager def temp_path(): + # type: () -> Iterator[None] """A context manager which allows the ability to set sys.path temporarily >>> path_from_virtualenv = load_path("/path/to/venv/bin/python") >>> print(sys.path) - ['/home/user/.pyenv/versions/3.7.0/bin', '/home/user/.pyenv/versions/3.7.0/lib/python37.zip', '/home/user/.pyenv/versions/3.7.0/lib/python3.7', '/home/user/.pyenv/versions/3.7.0/lib/python3.7/lib-dynload', '/home/user/.pyenv/versions/3.7.0/lib/python3.7/site-packages'] + [ + '/home/user/.pyenv/versions/3.7.0/bin', + '/home/user/.pyenv/versions/3.7.0/lib/python37.zip', + '/home/user/.pyenv/versions/3.7.0/lib/python3.7', + '/home/user/.pyenv/versions/3.7.0/lib/python3.7/lib-dynload', + '/home/user/.pyenv/versions/3.7.0/lib/python3.7/site-packages' + ] >>> with temp_path(): sys.path = path_from_virtualenv # Running in the context of the path above run(["pip", "install", "stuff"]) >>> print(sys.path) - ['/home/user/.pyenv/versions/3.7.0/bin', '/home/user/.pyenv/versions/3.7.0/lib/python37.zip', '/home/user/.pyenv/versions/3.7.0/lib/python3.7', '/home/user/.pyenv/versions/3.7.0/lib/python3.7/lib-dynload', '/home/user/.pyenv/versions/3.7.0/lib/python3.7/site-packages'] + [ + '/home/user/.pyenv/versions/3.7.0/bin', + '/home/user/.pyenv/versions/3.7.0/lib/python37.zip', + '/home/user/.pyenv/versions/3.7.0/lib/python3.7', + '/home/user/.pyenv/versions/3.7.0/lib/python3.7/lib-dynload', + '/home/user/.pyenv/versions/3.7.0/lib/python3.7/site-packages' + ] """ path = [p for p in sys.path] @@ -62,6 +101,7 @@ def temp_path(): @contextmanager def cd(path): + # type: () -> Iterator[None] """Context manager to temporarily change working directories :param str path: The directory to move into @@ -88,6 +128,7 @@ def cd(path): @contextmanager def dummy_spinner(spin_type, text, **kwargs): + # type: (str, str, Any) class FakeClass(object): def __init__(self, text=""): self.text = text @@ -110,12 +151,13 @@ def dummy_spinner(spin_type, text, **kwargs): @contextmanager def spinner( - spinner_name=None, - start_text=None, - handler_map=None, - nospin=False, - write_to_stdout=True, + spinner_name=None, # type: Optional[str] + start_text=None, # type: Optional[str] + handler_map=None, # type: Optional[Dict[str, Callable]] + nospin=False, # type: bool + write_to_stdout=True, # type: bool ): + # type: (...) -> ContextManager[TSpinner] """Get a spinner object or a dummy spinner to wrap a context. :param str spinner_name: A spinner type e.g. "dots" or "bouncingBar" (default: {"bouncingBar"}) @@ -134,7 +176,7 @@ def spinner( has_yaspin = None try: - import yaspin + import pipenv.vendor.yaspin as yaspin except ImportError: has_yaspin = False if not nospin: @@ -165,6 +207,7 @@ def spinner( @contextmanager def atomic_open_for_write(target, binary=False, newline=None, encoding=None): + # type: (str, bool, Optional[str], Optional[str]) -> None """Atomically open `target` for writing. This is based on Lektor's `atomic_open()` utility, but simplified a lot @@ -173,8 +216,10 @@ def atomic_open_for_write(target, binary=False, newline=None, encoding=None): :param str target: Target filename to write :param bool binary: Whether to open in binary mode, default False - :param str newline: The newline character to use when writing, determined from system if not supplied - :param str encoding: The encoding to use when writing, defaults to system encoding + :param Optional[str] newline: The newline character to use when writing, determined + from system if not supplied. + :param Optional[str] encoding: The encoding to use when writing, defaults to system + encoding. How this works: @@ -234,7 +279,10 @@ def atomic_open_for_write(target, binary=False, newline=None, encoding=None): delete=False, ) # set permissions to 0644 - os.chmod(f.name, stat.S_IWUSR | stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH) + try: + os.chmod(f.name, stat.S_IWUSR | stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH) + except OSError: + pass try: yield f except BaseException: @@ -254,13 +302,19 @@ def atomic_open_for_write(target, binary=False, newline=None, encoding=None): @contextmanager -def open_file(link, session=None, stream=True): +def open_file( + link, # type: Union[_T, str] + session=None, # type: Optional[Session] + stream=True, # type: bool +): + # type: (...) -> ContextManager[Union[IO[bytes], Urllib3_HTTPResponse, Urllib_HTTPResponse]] """ Open local or remote file for reading. - :type link: pip._internal.index.Link or str - :type session: requests.Session - :param bool stream: Try to stream if remote, default True + :param pipenv.patched.notpip._internal.index.Link link: A link object from resolving dependencies with + pip, or else a URL. + :param Optional[Session] session: A :class:`~requests.Session` instance + :param bool stream: Whether to stream the content if remote, default True :raises ValueError: If link points to a local directory. :return: a context manager to the opened file-like object """ @@ -285,24 +339,32 @@ def open_file(link, session=None, stream=True): # Remote URL headers = {"Accept-Encoding": "identity"} if not session: - from requests import Session - - session = Session() - with session.get(link, headers=headers, stream=stream) as resp: try: - raw = getattr(resp, "raw", None) - result = raw if raw else resp - yield result - finally: - if raw: - conn = getattr(raw, "_connection") - if conn is not None: - conn.close() - result.close() + from pipenv.vendor.requests import Session # noqa + except ImportError: + session = None + else: + session = Session() + if session is None: + with closing(six.moves.urllib.request.urlopen(link)) as f: + yield f + else: + with session.get(link, headers=headers, stream=stream) as resp: + try: + raw = getattr(resp, "raw", None) + result = raw if raw else resp + yield result + finally: + if raw: + conn = raw._connection + if conn is not None: + conn.close() + result.close() @contextmanager def replaced_stream(stream_name): + # type: (str) -> Iterator[IO[Text]] """ Context manager to temporarily swap out *stream_name* with a stream wrapper. @@ -329,6 +391,7 @@ def replaced_stream(stream_name): @contextmanager def replaced_streams(): + # type: () -> Iterator[Tuple[IO[Text], IO[Text]]] """ Context manager to replace both ``sys.stdout`` and ``sys.stderr`` using ``replaced_stream`` diff --git a/pipenv/vendor/vistir/misc.py b/pipenv/vendor/vistir/misc.py index 36218a50..68b7fa17 100644 --- a/pipenv/vendor/vistir/misc.py +++ b/pipenv/vendor/vistir/misc.py @@ -1,19 +1,23 @@ # -*- coding=utf-8 -*- from __future__ import absolute_import, print_function, unicode_literals +import atexit import io +import itertools import json import locale import logging import os import subprocess import sys +import threading from collections import OrderedDict from functools import partial from itertools import islice, tee from weakref import WeakKeyDictionary -import six +import pipenv.vendor.six as six +from pipenv.vendor.six.moves.queue import Empty, Queue from .cmdparse import Script from .compat import ( @@ -21,6 +25,8 @@ from .compat import ( Path, StringIO, TimeoutError, + _fs_decode_errors, + _fs_encode_errors, fs_str, is_bytes, partialmethod, @@ -58,7 +64,7 @@ __all__ = [ if MYPY_RUNNING: - from typing import Any, Dict, List, Optional, Union + from typing import Any, Dict, Generator, IO, List, Optional, Text, Tuple, Union from .spin import VistirSpinner @@ -66,8 +72,7 @@ def _get_logger(name=None, level="ERROR"): # type: (Optional[str], str) -> logging.Logger if not name: name = __name__ - if isinstance(level, six.string_types): - level = getattr(logging, level.upper()) + level = getattr(logging, level.upper()) logger = logging.getLogger(name) logger.setLevel(level) formatter = logging.Formatter( @@ -83,8 +88,9 @@ def shell_escape(cmd): # type: (Union[str, List[str]]) -> str """Escape strings for use in :func:`~subprocess.Popen` and :func:`run`. - This is a passthrough method for instantiating a :class:`~vistir.cmdparse.Script` - object which can be used to escape commands to output as a single string. + This is a passthrough method for instantiating a + :class:`~vistir.cmdparse.Script` object which can be used to escape + commands to output as a single string. """ cmd = Script.parse(cmd) return cmd.cmdify() @@ -92,14 +98,25 @@ def shell_escape(cmd): def unnest(elem): # type: (Iterable) -> Any - """Flatten an arbitrarily nested iterable + """Flatten an arbitrarily nested iterable. :param elem: An iterable to flatten :type elem: :class:`~collections.Iterable` - >>> nested_iterable = (1234, (3456, 4398345, (234234)), (2396, (23895750, 9283798, 29384, (289375983275, 293759, 2347, (2098, 7987, 27599))))) + >>> nested_iterable = ( + 1234, (3456, 4398345, (234234)), ( + 2396, ( + 23895750, 9283798, 29384, ( + 289375983275, 293759, 2347, ( + 2098, 7987, 27599 + ) + ) + ) + ) + ) >>> list(vistir.misc.unnest(nested_iterable)) - [1234, 3456, 4398345, 234234, 2396, 23895750, 9283798, 29384, 289375983275, 293759, 2347, 2098, 7987, 27599] + [1234, 3456, 4398345, 234234, 2396, 23895750, 9283798, 29384, 289375983275, 293759, + 2347, 2098, 7987, 27599] """ if isinstance(elem, Iterable) and not isinstance(elem, six.string_types): @@ -127,14 +144,19 @@ def _is_iterable(elem): def dedup(iterable): # type: (Iterable) -> Iterable - """Deduplicate an iterable object like iter(set(iterable)) but - order-reserved. - """ + """Deduplicate an iterable object like iter(set(iterable)) but order- + preserved.""" return iter(OrderedDict.fromkeys(iterable)) -def _spawn_subprocess(script, env=None, block=True, cwd=None, combine_stderr=True): - # type: (Union[str, List[str]], Optional[Dict[str, str], bool, Optional[str], bool]) -> subprocess.Popen +def _spawn_subprocess( + script, # type: Union[str, List[str]] + env=None, # type: Optional[Dict[str, str]] + block=True, # type: bool + cwd=None, # type: Optional[Union[str, Path]] + combine_stderr=True, # type: bool +): + # type: (...) -> subprocess.Popen from distutils.spawn import find_executable if not env: @@ -147,6 +169,10 @@ def _spawn_subprocess(script, env=None, block=True, cwd=None, combine_stderr=Tru "stderr": subprocess.PIPE if not combine_stderr else subprocess.STDOUT, "shell": False, } + if sys.version_info[:2] > (3, 5): + options.update({"universal_newlines": True, "encoding": "utf-8"}) + elif os.name != "nt": + options["universal_newlines"] = True if not block: options["stdin"] = subprocess.PIPE if cwd: @@ -170,80 +196,298 @@ def _spawn_subprocess(script, env=None, block=True, cwd=None, combine_stderr=Tru return subprocess.Popen(script.cmdify(), **options) -def _read_streams(stream_dict): - results = {} - for outstream in stream_dict.keys(): - stream = stream_dict[outstream] - if not stream: - results[outstream] = None - continue - line = to_text(stream.readline()) - if not line: - results[outstream] = None - continue - line = to_text("{0}".format(line.rstrip())) - results[outstream] = line - return results +class SubprocessStreamWrapper(object): + def __init__( + self, + display_stderr_maxlen=200, # type: int + display_line_for_loops=20, # type: int + subprocess=None, # type: subprocess.Popen + spinner=None, # type: Optional[VistirSpinner] + verbose=False, # type: bool + stdout_allowed=False, # type: bool + ): + # type: (...) -> None + stdout_encoding = None + stderr_encoding = None + preferred_encoding = getpreferredencoding() + if subprocess is not None: + stdout_encoding = self.get_subprocess_encoding(subprocess, "stdout") + stderr_encoding = self.get_subprocess_encoding(subprocess, "stderr") + self.stdout_encoding = stdout_encoding or preferred_encoding + self.stderr_encoding = stderr_encoding or preferred_encoding + self.stdout_lines = [] + self.text_stdout_lines = [] + self.stderr_lines = [] + self.text_stderr_lines = [] + self.display_line = "" + self.display_line_loops_displayed = 0 + self.display_line_shown_for_loops = display_line_for_loops + self.display_line_max_len = display_stderr_maxlen + self.spinner = spinner + self.stdout_allowed = stdout_allowed + self.verbose = verbose + self._iterated_stdout = None + self._iterated_stderr = None + self._subprocess = subprocess + self._queues = { + "streams": Queue(), + "lines": Queue(), + } + self._threads = { + stream_name: threading.Thread( + target=self.enqueue_stream, + args=(self._subprocess, stream_name, self._queues["streams"]), + ) + for stream_name in ("stdout", "stderr") + } + self._threads["watcher"] = threading.Thread( + target=self.process_output_lines, + args=(self._queues["streams"], self._queues["lines"]), + ) + self.start_threads() + def enqueue_stream(self, proc, stream_name, queue): + # type: (subprocess.Popen, str, Queue) -> None + if not getattr(proc, stream_name, None): + queue.put(("stderr", None)) + else: + for line in iter(getattr(proc, stream_name).readline, ""): + queue.put((stream_name, line)) + getattr(proc, stream_name).close() + @property + def stderr(self): + return self._subprocess.stderr -def get_stream_results(cmd_instance, verbose, maxlen, spinner=None, stdout_allowed=False): - stream_results = {"stdout": [], "stderr": []} - streams = {"stderr": cmd_instance.stderr, "stdout": cmd_instance.stdout} - while True: - stream_contents = _read_streams(streams) - stdout_line = stream_contents["stdout"] - stderr_line = stream_contents["stderr"] - if not (stdout_line or stderr_line): - break - last_changed = 0 - display_line = "" - for stream_name in stream_contents.keys(): - if stream_contents[stream_name] and stream_name in stream_results: - line = stream_contents[stream_name] - stream_results[stream_name].append(line) - display_line = ( - fs_str("{0}".format(line)) - if stream_name == "stderr" - else display_line - ) - if display_line and last_changed < 100: - last_changed = 0 - display_line = "" - elif display_line: - last_changed += 1 - if len(display_line) > maxlen: - display_line = "{0}...".format(display_line[:maxlen]) + @property + def stdout(self): + return self._subprocess.stdout + + @classmethod + def get_subprocess_encoding(cls, cmd_instance, stream_name): + # type: (subprocess.Popen, str) -> Optional[str] + stream = getattr(cmd_instance, stream_name, None) + if stream is not None: + return get_output_encoding(getattr(stream, "encoding", None)) + return None + + @property + def stdout_iter(self): + if self._iterated_stdout is None and self.stdout: + self._iterated_stdout = iter(self.stdout.readline, "") + return self._iterated_stdout + + @property + def stderr_iter(self): + if self._iterated_stderr is None and self.stderr: + self._iterated_stderr = iter(self.stderr.readline, "") + return self._iterated_stderr + + def _decode_line(self, line, encoding): + # type: (Union[str, bytes], str) -> str + if isinstance(line, six.binary_type): + line = to_text( + line.decode(encoding, errors=_fs_decode_errors).encode( + "utf-8", errors=_fs_encode_errors + ), + errors="backslashreplace", + ) + else: + line = to_text(line, encoding=encoding, errors=_fs_encode_errors) + return line + + def start_threads(self): + for thread in self._threads.values(): + thread.daemon = True + thread.start() + + @property + def subprocess(self): + return self._subprocess + + @property + def out(self): + # type: () -> str + return getattr(self.subprocess, "out", "") + + @out.setter + def out(self, value): + # type: (str) -> None + self._subprocess.out = value + + @property + def err(self): + # type: () -> str + return getattr(self.subprocess, "err", "") + + @err.setter + def err(self, value): + # type: (str) -> None + self._subprocess.err = value + + def poll(self): + # type: () -> Optional[int] + return self.subprocess.poll() + + def wait(self, timeout=None): + # type: (self, Optional[int]) -> Optional[int] + kwargs = {} + if sys.version_info[0] >= 3: + kwargs = {"timeout": timeout} + result = self._subprocess.wait(**kwargs) + self.gather_output() + return result + + @property + def returncode(self): + # type: () -> Optional[int] + return self.subprocess.returncode + + @property + def text_stdout(self): + return os.linesep.join(self.text_stdout_lines) + + @property + def text_stderr(self): + return os.linesep.join(self.text_stderr_lines) + + @property + def stderr_closed(self): + # type: () -> bool + return self.stderr is None or (self.stderr is not None and self.stderr.closed) + + @property + def stdout_closed(self): + # type: () -> bool + return self.stdout is None or (self.stdout is not None and self.stdout.closed) + + @property + def running(self): + # type: () -> bool + return any(t.is_alive() for t in self._threads.values()) or not all( + [self.stderr_closed, self.stdout_closed, self.subprocess_finished] + ) + + @property + def subprocess_finished(self): + if self._subprocess is None: + return False + return ( + self._subprocess.poll() is not None or self._subprocess.returncode is not None + ) + + def update_display_line(self, new_line): + # type: () -> None + if self.display_line: + if new_line != self.display_line: + self.display_line_loops_displayed = 0 + new_line = fs_str("{}".format(new_line)) + if len(new_line) > self.display_line_max_len: + new_line = "{}...".format(new_line[: self.display_line_max_len]) + self.display_line = new_line + elif self.display_line_loops_displayed >= self.display_line_shown_for_loops: + self.display_line = "" + self.display_line_loops_displayed = 0 + else: + self.display_line_loops_displayed += 1 + return None + + @classmethod + def check_line_content(cls, line): + # type: (Optional[str]) -> bool + return line is not None and line != "" + + def get_line(self, queue): + # type: (Queue) -> Tuple[Optional[str], ...] + stream, result = None, None + try: + stream, result = queue.get_nowait() + except Empty: + result = None + return stream, result + + def process_output_lines(self, recv_queue, line_queue): + # type: (Queue, Queue) -> None + stream, line = self.get_line(recv_queue) + while self.poll() is None or line is not None: + if self.check_line_content(line): + line = to_text("{}".format(line).rstrip()) + line_queue.put((stream, line)) + stream, line = self.get_line(recv_queue) + + def gather_output(self, spinner=None, stdout_allowed=False, verbose=False): + # type: (Optional[VistirSpinner], bool, bool) -> None + if not getattr(self._subprocess, "out", None): + self._subprocess.out = "" + if not getattr(self._subprocess, "err", None): + self._subprocess.err = "" + if not self._queues["streams"].empty(): + self.process_output_lines(self._queues["streams"], self._queues["lines"]) + while not self._queues["lines"].empty(): + try: + stream_name, line = self._queues["lines"].get() + except Empty: + if not self._threads["watcher"].is_active(): + break + pass + if stream_name == "stdout": + text_line = self._decode_line(line, self.stdout_encoding) + self.text_stdout_lines.append(text_line) + self.out += "{}\n".format(text_line) if verbose: - use_stderr = not stdout_allowed or stream_name != "stdout" - if spinner: - target = spinner.stderr if use_stderr else spinner.stdout - spinner.hide_and_write(display_line, target=target) - else: - target = sys.stderr if use_stderr else sys.stdout - target.write(display_line) - target.flush() - if spinner: - spinner.text = to_native_string( - "{0} {1}".format(spinner.text, display_line) + _write_subprocess_result( + line, "stdout", spinner=spinner, stdout_allowed=stdout_allowed ) - continue - return stream_results + else: + text_err = self._decode_line(line, self.stderr_encoding) + self.text_stderr_lines.append(text_err) + self.update_display_line(line) + self.err += "{}\n".format(text_err) + _write_subprocess_result( + line, "stderr", spinner=spinner, stdout_allowed=stdout_allowed + ) + if spinner: + spinner.text = to_native_string( + "{} {}".format(spinner.text, self.display_line) + ) + self.out = self.out.strip() + self.err = self.err.strip() + + +def _write_subprocess_result(result, stream_name, spinner=None, stdout_allowed=False): + # type: (str, str, Optional[VistirSpinner], bool) -> None + if not stdout_allowed and stream_name == "stdout": + stream_name = "stderr" + if spinner: + spinner.hide_and_write(result, target=getattr(spinner, stream_name)) + else: + target_stream = getattr(sys, stream_name) + target_stream.write(result) + target_stream.flush() + return None + + +def attach_stream_reader( + cmd_instance, verbose, maxlen, spinner=None, stdout_allowed=False +): + streams = SubprocessStreamWrapper( + subprocess=cmd_instance, + display_stderr_maxlen=maxlen, + spinner=spinner, + verbose=verbose, + stdout_allowed=stdout_allowed, + ) + streams.gather_output(spinner=spinner, verbose=verbose, stdout_allowed=stdout_allowed) + return streams def _handle_nonblocking_subprocess(c, spinner=None): - # type: (subprocess.Popen, VistirSpinner) -> subprocess.Popen - try: + while c.running: c.wait() - finally: - if c.stdout: - c.stdout.close() - if c.stderr: - c.stderr.close() if spinner: - if c.returncode > 0: + if c.returncode != 0: spinner.fail(to_native_string("Failed...cleaning up...")) - if not os.name == "nt": + elif c.returncode == 0 and not os.name == "nt": spinner.ok(to_native_string("✔ Complete")) else: spinner.ok(to_native_string("Complete")) @@ -269,18 +513,15 @@ def _create_subprocess( c = _spawn_subprocess( cmd, env=env, block=block, cwd=cwd, combine_stderr=combine_stderr ) - except Exception as exc: + except Exception as exc: # pragma: no cover import traceback - formatted_tb = "".join( - traceback.format_exception(*sys.exc_info()) - ) # pragma: no cover - sys.stderr.write( # pragma: no cover - "Error while executing command %s:" - % to_native_string(" ".join(cmd._parts)) # pragma: no cover - ) # pragma: no cover - sys.stderr.write(formatted_tb) # pragma: no cover - raise exc # pragma: no cover + formatted_tb = "".join(traceback.format_exception(*sys.exc_info())) + sys.stderr.write( + "Error while executing command %s:" % to_native_string(" ".join(cmd._parts)) + ) + sys.stderr.write(formatted_tb) + raise exc if not block: c.stdin.close() spinner_orig_text = "" @@ -288,7 +529,7 @@ def _create_subprocess( spinner_orig_text = spinner.text if not spinner_orig_text and start_text is not None: spinner_orig_text = start_text - stream_results = get_stream_results( + c = attach_stream_reader( c, verbose=verbose, maxlen=display_limit, @@ -296,21 +537,13 @@ def _create_subprocess( stdout_allowed=write_to_stdout, ) _handle_nonblocking_subprocess(c, spinner) - output = stream_results["stdout"] - err = stream_results["stderr"] - c.out = "\n".join(output) if output else "" - c.err = "\n".join(err) if err else "" else: try: c.out, c.err = c.communicate() - except (SystemExit, KeyboardInterrupt, TimeoutError): + except (SystemExit, KeyboardInterrupt, TimeoutError): # pragma: no cover c.terminate() c.out, c.err = c.communicate() raise - if not block: - c.wait() - c.out = to_text("{0}".format(c.out)) if c.out else fs_str("") - c.err = to_text("{0}".format(c.err)) if c.err else fs_str("") if not return_object: return c.out.strip(), c.err.strip() return c @@ -334,14 +567,19 @@ def run( :param list cmd: A list representing the command you want to run. :param dict env: Additional environment settings to pass through to the subprocess. :param bool return_object: When True, returns the whole subprocess instance - :param bool block: When False, returns a potentially still-running :class:`subprocess.Popen` instance + :param bool block: When False, returns a potentially still-running + :class:`subprocess.Popen` instance :param str cwd: Current working directory contect to use for spawning the subprocess. :param bool verbose: Whether to print stdout in real time when non-blocking. :param bool nospin: Whether to disable the cli spinner. - :param str spinner_name: The name of the spinner to use if enabled, defaults to bouncingBar - :param bool combine_stderr: Optionally merge stdout and stderr in the subprocess, false if nonblocking. - :param int dispay_limit: The max width of output lines to display when using a spinner. - :param bool write_to_stdout: Whether to write to stdout when using a spinner, default True. + :param str spinner_name: The name of the spinner to use if enabled, defaults to + bouncingBar + :param bool combine_stderr: Optionally merge stdout and stderr in the subprocess, + false if nonblocking. + :param int dispay_limit: The max width of output lines to display when using a + spinner. + :param bool write_to_stdout: Whether to write to stdout when using a spinner, + defaults to True. :returns: A 2-tuple of (output, error) or a :class:`subprocess.Popen` object. .. Warning:: Merging standard out and standarad error in a nonblocking subprocess @@ -350,11 +588,13 @@ def run( """ _env = os.environ.copy() + _env["PYTHONIOENCODING"] = str("utf-8") + _env["PYTHONUTF8"] = str("1") if env: _env.update(env) if six.PY2: - fs_encode = partial(to_bytes, encoding=locale_encoding) - _env = {fs_encode(k): fs_encode(v) for k, v in _env.items()} + _fs_encode = partial(to_bytes, encoding=locale_encoding) + _env = {_fs_encode(k): _fs_encode(v) for k, v in _env.items()} else: _env = {k: fs_str(v) for k, v in _env.items()} if not spinner_name: @@ -390,14 +630,21 @@ def run( def load_path(python): - """Load the :mod:`sys.path` from the given python executable's environment as json + """Load the :mod:`sys.path` from the given python executable's environment + as json. :param str python: Path to a valid python executable - :return: A python representation of the `sys.path` value of the given python executable. + :return: A python representation of the `sys.path` value of the given python + executable. :rtype: list >>> load_path("/home/user/.virtualenvs/requirementslib-5MhGuG3C/bin/python") - ['', '/home/user/.virtualenvs/requirementslib-5MhGuG3C/lib/python37.zip', '/home/user/.virtualenvs/requirementslib-5MhGuG3C/lib/python3.7', '/home/user/.virtualenvs/requirementslib-5MhGuG3C/lib/python3.7/lib-dynload', '/home/user/.pyenv/versions/3.7.0/lib/python3.7', '/home/user/.virtualenvs/requirementslib-5MhGuG3C/lib/python3.7/site-packages', '/home/user/git/requirementslib/src'] + ['', '/home/user/.virtualenvs/requirementslib-5MhGuG3C/lib/python37.zip', + '/home/user/.virtualenvs/requirementslib-5MhGuG3C/lib/python3.7', + '/home/user/.virtualenvs/requirementslib-5MhGuG3C/lib/python3.7/lib-dynload', + '/home/user/.pyenv/versions/3.7.0/lib/python3.7', + '/home/user/.virtualenvs/requirementslib-5MhGuG3C/lib/python3.7/site-packages', + '/home/user/git/requirementslib/src'] """ python = Path(python).as_posix() @@ -411,7 +658,7 @@ def load_path(python): def partialclass(cls, *args, **kwargs): - """Returns a partially instantiated class + """Returns a partially instantiated class. :return: A partial class instance :rtype: cls @@ -421,7 +668,15 @@ def partialclass(cls, *args, **kwargs): <class '__main__.Source'> >>> source(name="pypi") >>> source.__dict__ - mappingproxy({'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Source' objects>, '__weakref__': <attribute '__weakref__' of 'Source' objects>, '__doc__': None, '__init__': functools.partialmethod(<function Source.__init__ at 0x7f23af429bf8>, , url='https://pypi.org/simple')}) + mappingproxy({ + '__module__': '__main__', + '__dict__': <attribute '__dict__' of 'Source' objects>, + '__weakref__': <attribute '__weakref__' of 'Source' objects>, + '__doc__': None, + '__init__': functools.partialmethod( + <function Source.__init__ at 0x7f23af429bf8>, , url='https://pypi.org/simple' + ) + }) >>> new_source = source(name="pypi") >>> new_source <__main__.Source object at 0x7f23af189b38> @@ -530,8 +785,8 @@ def to_text(string, encoding="utf-8", errors=None): def divide(n, iterable): - """ - split an iterable into n groups, per https://more-itertools.readthedocs.io/en/latest/api.html#grouping + """split an iterable into n groups, per https://more- + itertools.readthedocs.io/en/latest/api.html#grouping. :param int n: Number of unique groups :param iter iterable: An iterable to split up @@ -582,11 +837,11 @@ except Exception: def getpreferredencoding(): - """Determine the proper output encoding for terminal rendering""" + """Determine the proper output encoding for terminal rendering.""" # Borrowed from Invoke # (see https://github.com/pyinvoke/invoke/blob/93af29d/invoke/runners.py#L881) - _encoding = locale.getpreferredencoding(False) + _encoding = sys.getdefaultencoding() or locale.getpreferredencoding(False) if six.PY2 and not sys.platform == "win32": _default_encoding = locale.getdefaultlocale()[1] if _default_encoding is not None: @@ -598,8 +853,7 @@ PREFERRED_ENCODING = getpreferredencoding() def get_output_encoding(source_encoding): - """ - Given a source encoding, determine the preferred output encoding. + """Given a source encoding, determine the preferred output encoding. :param str source_encoding: The encoding of the source material. :returns: The output encoding to decode to. @@ -634,11 +888,13 @@ def _encode(output, encoding=None, errors=None, translation_map=None): def decode_for_output(output, target_stream=None, translation_map=None): - """Given a string, decode it for output to a terminal + """Given a string, decode it for output to a terminal. :param str output: A string to print to a terminal - :param target_stream: A stream to write to, we will encode to target this stream if possible. - :param dict translation_map: A mapping of unicode character ordinals to replacement strings. + :param target_stream: A stream to write to, we will encode to target this stream if + possible. + :param dict translation_map: A mapping of unicode character ordinals to replacement + strings. :return: A re-encoded string using the preferred encoding :rtype: str """ @@ -661,8 +917,7 @@ def decode_for_output(output, target_stream=None, translation_map=None): def get_canonical_encoding_name(name): # type: (str) -> str - """ - Given an encoding name, get the canonical name from a codec lookup. + """Given an encoding name, get the canonical name from a codec lookup. :param str name: The name of the codec to lookup :return: The canonical version of the codec name @@ -700,8 +955,8 @@ def _get_binary_buffer(stream): def get_wrapped_stream(stream, encoding=None, errors="replace"): - """ - Given a stream, wrap it in a `StreamWrapper` instance and return the wrapped stream. + """Given a stream, wrap it in a `StreamWrapper` instance and return the + wrapped stream. :param stream: A stream instance to wrap :param str encoding: The encoding to use for the stream @@ -716,7 +971,7 @@ def get_wrapped_stream(stream, encoding=None, errors="replace"): if stream is not None and encoding is None: encoding = "utf-8" if not encoding: - encoding = get_output_encoding(stream) + encoding = get_output_encoding(getattr(stream, "encoding", None)) else: encoding = get_canonical_encoding_name(encoding) return StreamWrapper(stream, encoding, errors, line_buffering=True) @@ -724,10 +979,8 @@ def get_wrapped_stream(stream, encoding=None, errors="replace"): class StreamWrapper(io.TextIOWrapper): - """ - This wrapper class will wrap a provided stream and supply an interface - for compatibility. - """ + """This wrapper class will wrap a provided stream and supply an interface + for compatibility.""" def __init__(self, stream, encoding, errors, line_buffering=True, **kwargs): self._stream = stream = _StreamProvider(stream) @@ -847,7 +1100,7 @@ def _isatty(stream): _wrap_for_color = None try: - import colorama + import pipenv.vendor.colorama as colorama except ImportError: colorama = None @@ -858,7 +1111,9 @@ if os.name == "nt" or sys.platform.startswith("win"): if colorama is not None: def _is_wrapped_for_color(stream): - return isinstance(stream, (colorama.AnsiToWin32, colorama.ansitowin32.StreamWrapper)) + return isinstance( + stream, (colorama.AnsiToWin32, colorama.ansitowin32.StreamWrapper) + ) def _wrap_for_color(stream, color=None): try: @@ -909,7 +1164,7 @@ def _cached_stream_lookup(stream_lookup_func, stream_resolution_func): def get_text_stream(stream="stdout", encoding=None): - """Retrieve a unicode stream wrapper around **sys.stdout** or **sys.stderr**. + """Retrieve a utf-8 stream wrapper around **sys.stdout** or **sys.stderr**. :param str stream: The name of the stream to wrap from the :mod:`sys` module. :param str encoding: An optional encoding to use. @@ -961,7 +1216,8 @@ TEXT_STREAMS = { def replace_with_text_stream(stream_name): - """Given a stream name, replace the target stream with a text-converted equivalent + """Given a stream name, replace the target stream with a text-converted + equivalent. :param str stream_name: The name of a target stream, such as **stdout** or **stderr** :return: None @@ -986,7 +1242,8 @@ def _can_use_color(stream=None, color=None): def echo(text, fg=None, bg=None, style=None, file=None, err=False, color=None): - """Write the given text to the provided stream or **sys.stdout** by default. + """Write the given text to the provided stream or **sys.stdout** by + default. Provides optional foreground and background colors from the ansi defaults: **grey**, **red**, **green**, **yellow**, **blue**, **magenta**, **cyan** @@ -1004,7 +1261,7 @@ def echo(text, fg=None, bg=None, style=None, file=None, err=False, color=None): """ if file and not hasattr(file, "write"): - raise TypeError("Expected a writable stream, received {0!r}".format(file)) + raise TypeError("Expected a writable stream, received {!r}".format(file)) if not file: if err: file = _text_stderr() diff --git a/pipenv/vendor/vistir/path.py b/pipenv/vendor/vistir/path.py index d5b02f64..0b684d7c 100644 --- a/pipenv/vendor/vistir/path.py +++ b/pipenv/vendor/vistir/path.py @@ -8,12 +8,14 @@ import os import posixpath import shutil import stat +import sys import time +import unicodedata import warnings -import six -from six.moves import urllib_parse -from six.moves.urllib import request as urllib_request +import pipenv.vendor.six as six +from pipenv.vendor.six.moves import urllib_parse +from pipenv.vendor.six.moves.urllib import request as urllib_request from .backports.tempfile import _TemporaryFileWrapper from .compat import ( @@ -30,8 +32,36 @@ from .compat import ( fs_encode, ) +# fmt: off +if six.PY3: + from urllib.parse import quote_from_bytes as quote +else: + from urllib import quote +# fmt: on + + if IS_TYPE_CHECKING: - from typing import Optional, Callable, Text, ByteString, AnyStr + from types import TracebackType + from typing import ( + Any, + AnyStr, + ByteString, + Callable, + Generator, + Iterator, + List, + Optional, + Text, + Tuple, + Type, + Union, + ) + + if six.PY3: + TPath = os.PathLike + else: + TPath = Union[str, bytes] + TFunc = Callable[..., Any] __all__ = [ "check_for_unc_path", @@ -64,16 +94,18 @@ if os.name == "nt": def unicode_path(path): + # type: (TPath) -> Text # Paths are supposed to be represented as unicode here - if six.PY2 and not isinstance(path, six.text_type): + if six.PY2 and isinstance(path, six.binary_type): return path.decode(_fs_encoding) return path def native_path(path): - if six.PY2 and not isinstance(path, bytes): + # type: (TPath) -> str + if six.PY2 and isinstance(path, six.text_type): return path.encode(_fs_encoding) - return path + return str(path) # once again thank you django... @@ -83,20 +115,18 @@ if six.PY3 or os.name == "nt": else: def abspathu(path): - """ - Version of os.path.abspath that uses the unicode representation - of the current working directory, thus avoiding a UnicodeDecodeError - in join when the cwd has non-ASCII characters. - """ + # type: (TPath) -> Text + """Version of os.path.abspath that uses the unicode representation of + the current working directory, thus avoiding a UnicodeDecodeError in + join when the cwd has non-ASCII characters.""" if not os.path.isabs(path): path = os.path.join(os.getcwdu(), path) return os.path.normpath(path) def normalize_path(path): - # type: (AnyStr) -> AnyStr - """ - Return a case-normalized absolute variable-expanded path. + # type: (TPath) -> Text + """Return a case-normalized absolute variable-expanded path. :param str path: The non-normalized path :return: A normalized, expanded, case-normalized path @@ -113,9 +143,8 @@ def normalize_path(path): def is_in_path(path, parent): - # type: (AnyStr, AnyStr) -> bool - """ - Determine if the provided full path is in the given parent root. + # type: (TPath, TPath) -> bool + """Determine if the provided full path is in the given parent root. :param str path: The full path to check the location of. :param str parent: The parent path to check for membership in @@ -123,11 +152,11 @@ def is_in_path(path, parent): :rtype: bool """ - return normalize_path(str(path)).startswith(normalize_path(str(parent))) + return normalize_path(path).startswith(normalize_path(parent)) def normalize_drive(path): - # type: (str) -> Text + # type: (TPath) -> Text """Normalize drive in path so they stay consistent. This currently only affects local drives on Windows, which can be @@ -136,8 +165,10 @@ def normalize_drive(path): """ from .misc import to_text - if os.name != "nt" or not isinstance(path, six.string_types): - return path + if os.name != "nt" or not ( + isinstance(path, six.string_types) or getattr(path, "__fspath__", None) + ): + return path # type: ignore drive, tail = os.path.splitdrive(path) # Only match (lower cased) local drives (e.g. 'c:'), not UNC mounts. @@ -148,7 +179,7 @@ def normalize_drive(path): def path_to_url(path): - # type: (str) -> Text + # type: (TPath) -> Text """Convert the supplied local path to a file uri. :param str path: A string pointing to or representing a local path @@ -158,23 +189,31 @@ def path_to_url(path): >>> path_to_url("/home/user/code/myrepo/myfile.zip") 'file:///home/user/code/myrepo/myfile.zip' """ - from .misc import to_text, to_bytes + from .misc import to_bytes if not path: - return path - path = to_bytes(path, encoding="utf-8") - normalized_path = to_text(normalize_drive(os.path.abspath(path)), encoding="utf-8") - return to_text(Path(normalized_path).as_uri(), encoding="utf-8") + return path # type: ignore + normalized_path = Path(normalize_drive(os.path.abspath(path))).as_posix() + if os.name == "nt" and normalized_path[1] == ":": + drive, _, path = normalized_path.partition(":") + # XXX: This enables us to handle half-surrogates that were never + # XXX: actually part of a surrogate pair, but were just incidentally + # XXX: passed in as a piece of a filename + quoted_path = quote(fs_encode(path)) + return fs_decode("file:///{}:{}".format(drive, quoted_path)) + # XXX: This is also here to help deal with incidental dangling surrogates + # XXX: on linux, by making sure they are preserved during encoding so that + # XXX: we can urlencode the backslash correctly + bytes_path = to_bytes(normalized_path, errors="backslashreplace") + return fs_decode("file://{}".format(quote(bytes_path))) def url_to_path(url): - # type: (str) -> ByteString - """ - Convert a valid file url to a local filesystem path + # type: (str) -> str + """Convert a valid file url to a local filesystem path. Follows logic taken from pip's equivalent function """ - from .misc import to_bytes assert is_file_url(url), "Only file: urls can be converted to local paths" _, netloc, path, _, _ = urllib_parse.urlsplit(url) @@ -183,41 +222,45 @@ def url_to_path(url): netloc = "\\\\" + netloc path = urllib_request.url2pathname(netloc + path) - return to_bytes(path, encoding="utf-8") + return urllib_parse.unquote(path) def is_valid_url(url): - """Checks if a given string is an url""" + # type: (Union[str, bytes]) -> bool + """Checks if a given string is an url.""" from .misc import to_text if not url: - return url + return url # type: ignore pieces = urllib_parse.urlparse(to_text(url)) return all([pieces.scheme, pieces.netloc]) def is_file_url(url): - """Returns true if the given url is a file url""" + # type: (Any) -> bool + """Returns true if the given url is a file url.""" from .misc import to_text if not url: return False if not isinstance(url, six.string_types): try: - url = getattr(url, "url") + url = url.url except AttributeError: - raise ValueError("Cannot parse url from unknown type: {0!r}".format(url)) + raise ValueError("Cannot parse url from unknown type: {!r}".format(url)) url = to_text(url, encoding="utf-8") return urllib_parse.urlparse(url.lower()).scheme == "file" def is_readonly_path(fn): + # type: (TPath) -> bool """Check if a provided path exists and is readonly. - Permissions check is `bool(path.stat & stat.S_IREAD)` or `not os.access(path, os.W_OK)` + Permissions check is `bool(path.stat & stat.S_IREAD)` or `not + os.access(path, os.W_OK)` """ - fn = fs_encode(fn) + fn = fs_decode(fs_encode(fn)) if os.path.exists(fn): file_stat = os.stat(fn).st_mode return not bool(file_stat & stat.S_IWRITE) or not os.access(fn, os.W_OK) @@ -225,47 +268,35 @@ def is_readonly_path(fn): def mkdir_p(newdir, mode=0o777): - """Recursively creates the target directory and all of its parents if they do not - already exist. Fails silently if they do. + # type: (TPath, int) -> None + """Recursively creates the target directory and all of its parents if they + do not already exist. Fails silently if they do. :param str newdir: The directory path to ensure :raises: OSError if a file is encountered along the way """ - # http://code.activestate.com/recipes/82465-a-friendly-mkdir/ - - newdir = fs_encode(newdir) + newdir = fs_decode(fs_encode(newdir)) if os.path.exists(newdir): if not os.path.isdir(newdir): raise OSError( - "a file with the same name as the desired dir, '{0}', already exists.".format( + "a file with the same name as the desired dir, '{}', already exists.".format( fs_decode(newdir) ) ) - else: - head, tail = os.path.split(newdir) - # Make sure the tail doesn't point to the asame place as the head - curdir = fs_encode(".") - tail_and_head_match = ( - os.path.relpath(tail, start=os.path.basename(head)) == curdir - ) - if tail and not tail_and_head_match and not os.path.isdir(newdir): - target = os.path.join(head, tail) - if os.path.exists(target) and os.path.isfile(target): - raise OSError( - "A file with the same name as the desired dir, '{0}', already exists.".format( - fs_decode(newdir) - ) - ) - os.makedirs(os.path.join(head, tail), mode) + return None + os.makedirs(newdir, mode) def ensure_mkdir_p(mode=0o777): - """Decorator to ensure `mkdir_p` is called to the function's return value. - """ + # type: (int) -> Callable[Callable[..., Any], Callable[..., Any]] + """Decorator to ensure `mkdir_p` is called to the function's return + value.""" def decorator(f): + # type: (Callable[..., Any]) -> Callable[..., Any] @functools.wraps(f) def decorated(*args, **kwargs): + # type: () -> str path = f(*args, **kwargs) mkdir_p(path, mode=mode) return path @@ -279,6 +310,7 @@ TRACKED_TEMPORARY_DIRECTORIES = [] def create_tracked_tempdir(*args, **kwargs): + # type: (Any, Any) -> str """Create a tracked temporary directory. This uses `TemporaryDirectory`, but does not remove the directory when @@ -296,6 +328,7 @@ def create_tracked_tempdir(*args, **kwargs): def create_tracked_tempfile(*args, **kwargs): + # type: (Any, Any) -> str """Create a tracked temporary file. This uses the `NamedTemporaryFile` construct, but does not remove the file @@ -309,6 +342,7 @@ def create_tracked_tempfile(*args, **kwargs): def _find_icacls_exe(): + # type: () -> Optional[Text] if os.name == "nt": paths = [ os.path.expandvars(r"%windir%\{0}").format(subdir) @@ -326,15 +360,14 @@ def _find_icacls_exe(): def set_write_bit(fn): # type: (str) -> None - """ - Set read-write permissions for the current user on the target path. Fail silently - if the path doesn't exist. + """Set read-write permissions for the current user on the target path. Fail + silently if the path doesn't exist. :param str fn: The target filename or path :return: None """ - fn = fs_encode(fn) + fn = fs_decode(fs_encode(fn)) if not os.path.exists(fn): return file_stat = os.stat(fn).st_mode @@ -350,13 +383,15 @@ def set_write_bit(fn): c = run( [ icacls_exe, - "''{0}''".format(fn), + "''{}''".format(fn), "/grant", - "{0}:WD".format(user_sid), + "{}:WD".format(user_sid), "/T", "/C", "/Q", - ], nospin=True, return_object=True + ], + nospin=True, + return_object=True, ) if not c.err and c.returncode == 0: return @@ -377,8 +412,7 @@ def set_write_bit(fn): def rmtree(directory, ignore_errors=False, onerror=None): # type: (str, bool, Optional[Callable]) -> None - """ - Stand-in for :func:`~shutil.rmtree` with additional error-handling. + """Stand-in for :func:`~shutil.rmtree` with additional error-handling. This version of `rmtree` handles read-only paths, especially in the case of index files written by certain source control systems. @@ -392,20 +426,20 @@ def rmtree(directory, ignore_errors=False, onerror=None): Setting `ignore_errors=True` may cause this to silently fail to delete the path """ - directory = fs_encode(directory) + directory = fs_decode(fs_encode(directory)) if onerror is None: onerror = handle_remove_readonly try: shutil.rmtree(directory, ignore_errors=ignore_errors, onerror=onerror) - except (IOError, OSError, FileNotFoundError, PermissionError) as exc: + except (IOError, OSError, FileNotFoundError, PermissionError) as exc: # noqa:B014 # Ignore removal failures where the file doesn't exist if exc.errno != errno.ENOENT: raise def _wait_for_files(path): # pragma: no cover - """ - Retry with backoff up to 1 second to delete files from a directory. + # type: (Union[str, TPath]) -> Optional[List[TPath]] + """Retry with backoff up to 1 second to delete files from a directory. :param str path: The path to crawl to delete files from :return: A list of remaining paths or None @@ -427,7 +461,7 @@ def _wait_for_files(path): # pragma: no cover except FileNotFoundError as e: if e.errno == errno.ENOENT: return - except (OSError, IOError, PermissionError): + except (OSError, IOError, PermissionError): # noqa:B014 time.sleep(timeout) timeout *= 2 remaining.append(path) @@ -437,6 +471,7 @@ def _wait_for_files(path): # pragma: no cover def handle_remove_readonly(func, path, exc): + # type: (Callable[..., str], TPath, Tuple[Type[OSError], OSError, TracebackType]) -> None """Error handler for shutil.rmtree. Windows source repo folders are read-only by default, so this error handler @@ -461,7 +496,7 @@ def handle_remove_readonly(func, path, exc): set_write_bit(path) try: func(path) - except ( + except ( # noqa:B014 OSError, IOError, FileNotFoundError, @@ -484,7 +519,7 @@ def handle_remove_readonly(func, path, exc): remaining = _wait_for_files(path) try: func(path) - except (OSError, IOError, FileNotFoundError, PermissionError) as e: + except (OSError, IOError, FileNotFoundError, PermissionError) as e: # noqa:B014 if e.errno in PERM_ERRORS: if e.errno != errno.ENOENT: # File still exists warnings.warn(default_warning_message.format(path), ResourceWarning) @@ -494,10 +529,12 @@ def handle_remove_readonly(func, path, exc): def walk_up(bottom): + # type: (Union[TPath, str]) -> Generator[Tuple[str, List[str], List[str]], None, None] """Mimic os.walk, but walk 'up' instead of down the directory tree. + From: https://gist.github.com/zdavkeos/1098474 """ - bottom = os.path.realpath(bottom) + bottom = os.path.realpath(str(bottom)) # Get files in current dir. try: names = os.listdir(bottom) @@ -522,7 +559,8 @@ def walk_up(bottom): def check_for_unc_path(path): - """ Checks to see if a pathlib `Path` object is a unc path or not""" + # type: (Path) -> bool + """Checks to see if a pathlib `Path` object is a unc path or not.""" if ( os.name == "nt" and len(path.drive) > 2 @@ -535,6 +573,7 @@ def check_for_unc_path(path): def get_converted_relative_path(path, relative_to=None): + # type: (TPath, Optional[TPath]) -> str """Convert `path` to be relative. Given a vague relative path, return the path relative to the given @@ -590,11 +629,11 @@ def get_converted_relative_path(path, relative_to=None): def safe_expandvars(value): - """Call os.path.expandvars if value is a string, otherwise do nothing. - """ + # type: (TPath) -> str + """Call os.path.expandvars if value is a string, otherwise do nothing.""" if isinstance(value, six.string_types): return os.path.expandvars(value) - return value + return value # type: ignore class _TrackedTempfileWrapper(_TemporaryFileWrapper, object): diff --git a/pipenv/vendor/vistir/spin.py b/pipenv/vendor/vistir/spin.py index 64b615de..2c7c65cf 100644 --- a/pipenv/vendor/vistir/spin.py +++ b/pipenv/vendor/vistir/spin.py @@ -9,16 +9,36 @@ import threading import time from io import StringIO -import colorama -import six +import pipenv.vendor.colorama as colorama +import pipenv.vendor.six as six -from .compat import to_native_string +from .compat import IS_TYPE_CHECKING, to_native_string from .cursor import hide_cursor, show_cursor from .misc import decode_for_output, to_text from .termcolors import COLOR_MAP, COLORS, DISABLE_COLORS, colored +if IS_TYPE_CHECKING: + from typing import ( + Any, + Callable, + ContextManager, + Dict, + IO, + Optional, + Text, + Type, + TypeVar, + Union, + ) + + TSignalMap = Dict[ + Type[signal.SIGINT], + Callable[..., int, str, Union["DummySpinner", "VistirSpinner"]], + ] + _T = TypeVar("_T", covariant=True) + try: - import yaspin + import pipenv.vendor.yaspin as yaspin except ImportError: # pragma: no cover yaspin = None Spinners = None @@ -66,6 +86,7 @@ decode_output = functools.partial(decode_for_output, translation_map=TRANSLATION class DummySpinner(object): def __init__(self, text="", **kwargs): + # type: (str, Any) -> None if DISABLE_COLORS: colorama.init() self.text = to_native_string(decode_output(text)) if text else "" @@ -108,6 +129,7 @@ class DummySpinner(object): pass def fail(self, exitcode=1, text="FAIL"): + # type: (int, str) -> None if text is not None and text != "None": if self.write_to_stdout: self.write(text) @@ -116,6 +138,7 @@ class DummySpinner(object): self._close_output_buffer() def ok(self, text="OK"): + # type: (str) -> int if text is not None and text != "None": if self.write_to_stdout: self.write(text) @@ -125,6 +148,7 @@ class DummySpinner(object): return 0 def hide_and_write(self, text, target=None): + # type: (str, Optional[str]) -> None if not target: target = self.stdout if text is None or isinstance(text, six.string_types) and text == "None": @@ -136,6 +160,7 @@ class DummySpinner(object): self._show_cursor(target=target) def write(self, text=None): + # type: (Optional[str]) -> None if not self.write_to_stdout: return self.write_err(text) if text is None or isinstance(text, six.string_types) and text == "None": @@ -151,6 +176,7 @@ class DummySpinner(object): stdout.write(CLEAR_LINE) def write_err(self, text=None): + # type: (Optional[str]) -> None if text is None or isinstance(text, six.string_types) and text == "None": pass text = to_text(text) @@ -168,10 +194,12 @@ class DummySpinner(object): @staticmethod def _hide_cursor(target=None): + # type: (Optional[IO]) -> None pass @staticmethod def _show_cursor(target=None): + # type: (Optional[IO]) -> None pass @@ -183,6 +211,7 @@ class VistirSpinner(SpinBase): "A spinner class for handling spinners on windows and posix." def __init__(self, *args, **kwargs): + # type: (Any, Any) """ Get a spinner object or a dummy spinner to wrap a context. @@ -196,7 +225,7 @@ class VistirSpinner(SpinBase): self.handler = handler colorama.init() - sigmap = {} + sigmap = {} # type: TSignalMap if handler: sigmap.update({signal.SIGINT: handler, signal.SIGTERM: handler}) handler_map = kwargs.pop("handler_map", {}) @@ -218,11 +247,15 @@ class VistirSpinner(SpinBase): self.out_buff = StringIO() self.write_to_stdout = write_to_stdout self.is_dummy = bool(yaspin is None) + self._stop_spin = None # type: Optional[threading.Event] + self._hide_spin = None # type: Optional[threading.Event] + self._spin_thread = None # type: Optional[threading.Thread] super(VistirSpinner, self).__init__(*args, **kwargs) if DISABLE_COLORS: colorama.deinit() def ok(self, text=u"OK", err=False): + # type: (str, bool) -> None """Set Ok (success) finalizer to a spinner.""" # Do not display spin text for ok state self._text = None @@ -232,6 +265,7 @@ class VistirSpinner(SpinBase): self._freeze(_text, err=err) def fail(self, text=u"FAIL", err=False): + # type: (str, bool) -> None """Set fail finalizer to a spinner.""" # Do not display spin text for fail state self._text = None @@ -241,6 +275,7 @@ class VistirSpinner(SpinBase): self._freeze(_text, err=err) def hide_and_write(self, text, target=None): + # type: (str, Optional[str]) -> None if not target: target = self.stdout if text is None or isinstance(text, six.string_types) and text == u"None": @@ -252,6 +287,7 @@ class VistirSpinner(SpinBase): self._show_cursor(target=target) def write(self, text): # pragma: no cover + # type: (str) -> None if not self.write_to_stdout: return self.write_err(text) stdout = self.stdout @@ -266,6 +302,7 @@ class VistirSpinner(SpinBase): self.out_buff.write(text) def write_err(self, text): # pragma: no cover + # type: (str) -> None """Write error text in the terminal without breaking the spinner.""" stderr = self.stderr if self.stderr.closed: @@ -279,6 +316,7 @@ class VistirSpinner(SpinBase): self.out_buff.write(decode_output(text, target_stream=self.out_buff)) def start(self): + # type: () -> None if self._sigmap: self._register_signal_handlers() @@ -292,6 +330,7 @@ class VistirSpinner(SpinBase): self._spin_thread.start() def stop(self): + # type: () -> None if self._dfl_sigmap: # Reset registered signal handlers to default ones self._reset_signal_handlers() @@ -314,6 +353,7 @@ class VistirSpinner(SpinBase): self.out_buff.close() def _freeze(self, final_text, err=False): + # type: (str, bool) -> None """Stop spinner, compose last frame and 'freeze' it.""" if not final_text: final_text = "" @@ -330,12 +370,14 @@ class VistirSpinner(SpinBase): target.write(self._last_frame) def _compose_color_func(self): + # type: () -> Callable[..., str] fn = functools.partial( colored, color=self._color, on_color=self._on_color, attrs=list(self._attrs) ) return fn def _compose_out(self, frame, mode=None): + # type: (str, Optional[str]) -> Text # Ensure Unicode input frame = to_text(frame) @@ -355,6 +397,7 @@ class VistirSpinner(SpinBase): return out def _spin(self): + # type: () -> None target = self.stdout if self.write_to_stdout else self.stderr clear_fn = self._clear_line if self.write_to_stdout else self._clear_err while not self._stop_spin.is_set(): @@ -379,6 +422,7 @@ class VistirSpinner(SpinBase): target.write("\b") def _register_signal_handlers(self): + # type: () -> None # SIGKILL cannot be caught or ignored, and the receiving # process cannot perform any clean-up upon receiving this # signal. @@ -411,31 +455,37 @@ class VistirSpinner(SpinBase): signal.signal(sig, sig_handler) def _reset_signal_handlers(self): + # type: () -> None for sig, sig_handler in self._dfl_sigmap.items(): signal.signal(sig, sig_handler) @staticmethod def _hide_cursor(target=None): + # type: (Optional[IO]) -> None if not target: target = sys.stdout hide_cursor(stream=target) @staticmethod def _show_cursor(target=None): + # type: (Optional[IO]) -> None if not target: target = sys.stdout show_cursor(stream=target) @staticmethod def _clear_err(): + # type: () -> None sys.stderr.write(CLEAR_LINE) @staticmethod def _clear_line(): + # type: () -> None sys.stdout.write(CLEAR_LINE) def create_spinner(*args, **kwargs): + # type: (Any, Any) -> Union[DummySpinner, VistirSpinner] nospin = kwargs.pop("nospin", False) use_yaspin = kwargs.pop("use_yaspin", not nospin) if nospin or not use_yaspin: diff --git a/pipenv/vendor/vistir/termcolors.py b/pipenv/vendor/vistir/termcolors.py index 27b5ff44..68f6e03a 100644 --- a/pipenv/vendor/vistir/termcolors.py +++ b/pipenv/vendor/vistir/termcolors.py @@ -4,8 +4,8 @@ from __future__ import absolute_import, print_function, unicode_literals import os import re -import colorama -import six +import pipenv.vendor.colorama as colorama +import pipenv.vendor.six as six from .compat import to_native_string diff --git a/pipenv/vendor/wheel/LICENSE.txt b/pipenv/vendor/wheel/LICENSE.txt new file mode 100644 index 00000000..c3441e6c --- /dev/null +++ b/pipenv/vendor/wheel/LICENSE.txt @@ -0,0 +1,22 @@ +"wheel" copyright (c) 2012-2014 Daniel Holth <dholth@fastmail.fm> and +contributors. + +The MIT License + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/pipenv/vendor/wheel/__init__.py b/pipenv/vendor/wheel/__init__.py new file mode 100644 index 00000000..349a8f7b --- /dev/null +++ b/pipenv/vendor/wheel/__init__.py @@ -0,0 +1 @@ +__version__ = '0.36.2' diff --git a/pipenv/vendor/wheel/__main__.py b/pipenv/vendor/wheel/__main__.py new file mode 100644 index 00000000..b3773a20 --- /dev/null +++ b/pipenv/vendor/wheel/__main__.py @@ -0,0 +1,19 @@ +""" +Wheel command line tool (enable python -m wheel syntax) +""" + +import sys + + +def main(): # needed for console script + if __package__ == '': + # To be able to run 'python wheel-0.9.whl/wheel': + import os.path + path = os.path.dirname(os.path.dirname(__file__)) + sys.path[0:0] = [path] + import wheel.cli + sys.exit(wheel.cli.main()) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/pipenv/vendor/wheel/bdist_wheel.py b/pipenv/vendor/wheel/bdist_wheel.py new file mode 100644 index 00000000..80e43d0a --- /dev/null +++ b/pipenv/vendor/wheel/bdist_wheel.py @@ -0,0 +1,492 @@ +""" +Create a wheel (.whl) distribution. + +A wheel is a built archive format. +""" + +import distutils +import os +import shutil +import stat +import sys +import re +import warnings +from collections import OrderedDict +from distutils.core import Command +from distutils import log as logger +from io import BytesIO +from glob import iglob +from shutil import rmtree +from sysconfig import get_config_var +from zipfile import ZIP_DEFLATED, ZIP_STORED + +import pkg_resources + +from .pkginfo import write_pkg_info +from .macosx_libfile import calculate_macosx_platform_tag +from .metadata import pkginfo_to_metadata +from .vendored.packaging import tags +from .wheelfile import WheelFile +from . import __version__ as wheel_version + +if sys.version_info < (3,): + from email.generator import Generator as BytesGenerator +else: + from email.generator import BytesGenerator + +safe_name = pkg_resources.safe_name +safe_version = pkg_resources.safe_version + +PY_LIMITED_API_PATTERN = r'cp3\d' + + +def python_tag(): + return 'py{}'.format(sys.version_info[0]) + + +def get_platform(archive_root): + """Return our platform name 'win32', 'linux_x86_64'""" + # XXX remove distutils dependency + result = distutils.util.get_platform() + if result.startswith("macosx") and archive_root is not None: + result = calculate_macosx_platform_tag(archive_root, result) + if result == "linux_x86_64" and sys.maxsize == 2147483647: + # pip pull request #3497 + result = "linux_i686" + return result + + +def get_flag(var, fallback, expected=True, warn=True): + """Use a fallback value for determining SOABI flags if the needed config + var is unset or unavailable.""" + val = get_config_var(var) + if val is None: + if warn: + warnings.warn("Config variable '{0}' is unset, Python ABI tag may " + "be incorrect".format(var), RuntimeWarning, 2) + return fallback + return val == expected + + +def get_abi_tag(): + """Return the ABI tag based on SOABI (if available) or emulate SOABI + (CPython 2, PyPy).""" + soabi = get_config_var('SOABI') + impl = tags.interpreter_name() + if not soabi and impl in ('cp', 'pp') and hasattr(sys, 'maxunicode'): + d = '' + m = '' + u = '' + if get_flag('Py_DEBUG', + hasattr(sys, 'gettotalrefcount'), + warn=(impl == 'cp')): + d = 'd' + if get_flag('WITH_PYMALLOC', + impl == 'cp', + warn=(impl == 'cp' and + sys.version_info < (3, 8))) \ + and sys.version_info < (3, 8): + m = 'm' + if get_flag('Py_UNICODE_SIZE', + sys.maxunicode == 0x10ffff, + expected=4, + warn=(impl == 'cp' and + sys.version_info < (3, 3))) \ + and sys.version_info < (3, 3): + u = 'u' + abi = '%s%s%s%s%s' % (impl, tags.interpreter_version(), d, m, u) + elif soabi and soabi.startswith('cpython-'): + abi = 'cp' + soabi.split('-')[1] + elif soabi and soabi.startswith('pypy-'): + # we want something like pypy36-pp73 + abi = '-'.join(soabi.split('-')[:2]) + abi = abi.replace('.', '_').replace('-', '_') + elif soabi: + abi = soabi.replace('.', '_').replace('-', '_') + else: + abi = None + return abi + + +def safer_name(name): + return safe_name(name).replace('-', '_') + + +def safer_version(version): + return safe_version(version).replace('-', '_') + + +def remove_readonly(func, path, excinfo): + print(str(excinfo[1])) + os.chmod(path, stat.S_IWRITE) + func(path) + + +class bdist_wheel(Command): + + description = 'create a wheel distribution' + + supported_compressions = OrderedDict([ + ('stored', ZIP_STORED), + ('deflated', ZIP_DEFLATED) + ]) + + user_options = [('bdist-dir=', 'b', + "temporary directory for creating the distribution"), + ('plat-name=', 'p', + "platform name to embed in generated filenames " + "(default: %s)" % get_platform(None)), + ('keep-temp', 'k', + "keep the pseudo-installation tree around after " + + "creating the distribution archive"), + ('dist-dir=', 'd', + "directory to put final built distributions in"), + ('skip-build', None, + "skip rebuilding everything (for testing/debugging)"), + ('relative', None, + "build the archive using relative paths " + "(default: false)"), + ('owner=', 'u', + "Owner name used when creating a tar file" + " [default: current user]"), + ('group=', 'g', + "Group name used when creating a tar file" + " [default: current group]"), + ('universal', None, + "make a universal wheel" + " (default: false)"), + ('compression=', None, + "zipfile compression (one of: {})" + " (default: 'deflated')" + .format(', '.join(supported_compressions))), + ('python-tag=', None, + "Python implementation compatibility tag" + " (default: '%s')" % (python_tag())), + ('build-number=', None, + "Build number for this particular version. " + "As specified in PEP-0427, this must start with a digit. " + "[default: None]"), + ('py-limited-api=', None, + "Python tag (cp32|cp33|cpNN) for abi3 wheel tag" + " (default: false)"), + ] + + boolean_options = ['keep-temp', 'skip-build', 'relative', 'universal'] + + def initialize_options(self): + self.bdist_dir = None + self.data_dir = None + self.plat_name = None + self.plat_tag = None + self.format = 'zip' + self.keep_temp = False + self.dist_dir = None + self.egginfo_dir = None + self.root_is_pure = None + self.skip_build = None + self.relative = False + self.owner = None + self.group = None + self.universal = False + self.compression = 'deflated' + self.python_tag = python_tag() + self.build_number = None + self.py_limited_api = False + self.plat_name_supplied = False + + def finalize_options(self): + if self.bdist_dir is None: + bdist_base = self.get_finalized_command('bdist').bdist_base + self.bdist_dir = os.path.join(bdist_base, 'wheel') + + self.data_dir = self.wheel_dist_name + '.data' + self.plat_name_supplied = self.plat_name is not None + + try: + self.compression = self.supported_compressions[self.compression] + except KeyError: + raise ValueError('Unsupported compression: {}'.format(self.compression)) + + need_options = ('dist_dir', 'plat_name', 'skip_build') + + self.set_undefined_options('bdist', + *zip(need_options, need_options)) + + self.root_is_pure = not (self.distribution.has_ext_modules() + or self.distribution.has_c_libraries()) + + if self.py_limited_api and not re.match(PY_LIMITED_API_PATTERN, self.py_limited_api): + raise ValueError("py-limited-api must match '%s'" % PY_LIMITED_API_PATTERN) + + # Support legacy [wheel] section for setting universal + wheel = self.distribution.get_option_dict('wheel') + if 'universal' in wheel: + # please don't define this in your global configs + logger.warn('The [wheel] section is deprecated. Use [bdist_wheel] instead.') + val = wheel['universal'][1].strip() + if val.lower() in ('1', 'true', 'yes'): + self.universal = True + + if self.build_number is not None and not self.build_number[:1].isdigit(): + raise ValueError("Build tag (build-number) must start with a digit.") + + @property + def wheel_dist_name(self): + """Return distribution full name with - replaced with _""" + components = (safer_name(self.distribution.get_name()), + safer_version(self.distribution.get_version())) + if self.build_number: + components += (self.build_number,) + return '-'.join(components) + + def get_tag(self): + # bdist sets self.plat_name if unset, we should only use it for purepy + # wheels if the user supplied it. + if self.plat_name_supplied: + plat_name = self.plat_name + elif self.root_is_pure: + plat_name = 'any' + else: + # macosx contains system version in platform name so need special handle + if self.plat_name and not self.plat_name.startswith("macosx"): + plat_name = self.plat_name + else: + # on macosx always limit the platform name to comply with any + # c-extension modules in bdist_dir, since the user can specify + # a higher MACOSX_DEPLOYMENT_TARGET via tools like CMake + + # on other platforms, and on macosx if there are no c-extension + # modules, use the default platform name. + plat_name = get_platform(self.bdist_dir) + + if plat_name in ('linux-x86_64', 'linux_x86_64') and sys.maxsize == 2147483647: + plat_name = 'linux_i686' + + plat_name = plat_name.lower().replace('-', '_').replace('.', '_') + + if self.root_is_pure: + if self.universal: + impl = 'py2.py3' + else: + impl = self.python_tag + tag = (impl, 'none', plat_name) + else: + impl_name = tags.interpreter_name() + impl_ver = tags.interpreter_version() + impl = impl_name + impl_ver + # We don't work on CPython 3.1, 3.0. + if self.py_limited_api and (impl_name + impl_ver).startswith('cp3'): + impl = self.py_limited_api + abi_tag = 'abi3' + else: + abi_tag = str(get_abi_tag()).lower() + tag = (impl, abi_tag, plat_name) + # issue gh-374: allow overriding plat_name + supported_tags = [(t.interpreter, t.abi, plat_name) + for t in tags.sys_tags()] + assert tag in supported_tags, "would build wheel with unsupported tag {}".format(tag) + return tag + + def run(self): + build_scripts = self.reinitialize_command('build_scripts') + build_scripts.executable = 'python' + build_scripts.force = True + + build_ext = self.reinitialize_command('build_ext') + build_ext.inplace = False + + if not self.skip_build: + self.run_command('build') + + install = self.reinitialize_command('install', + reinit_subcommands=True) + install.root = self.bdist_dir + install.compile = False + install.skip_build = self.skip_build + install.warn_dir = False + + # A wheel without setuptools scripts is more cross-platform. + # Use the (undocumented) `no_ep` option to setuptools' + # install_scripts command to avoid creating entry point scripts. + install_scripts = self.reinitialize_command('install_scripts') + install_scripts.no_ep = True + + # Use a custom scheme for the archive, because we have to decide + # at installation time which scheme to use. + for key in ('headers', 'scripts', 'data', 'purelib', 'platlib'): + setattr(install, + 'install_' + key, + os.path.join(self.data_dir, key)) + + basedir_observed = '' + + if os.name == 'nt': + # win32 barfs if any of these are ''; could be '.'? + # (distutils.command.install:change_roots bug) + basedir_observed = os.path.normpath(os.path.join(self.data_dir, '..')) + self.install_libbase = self.install_lib = basedir_observed + + setattr(install, + 'install_purelib' if self.root_is_pure else 'install_platlib', + basedir_observed) + + logger.info("installing to %s", self.bdist_dir) + + self.run_command('install') + + impl_tag, abi_tag, plat_tag = self.get_tag() + archive_basename = "{}-{}-{}-{}".format(self.wheel_dist_name, impl_tag, abi_tag, plat_tag) + if not self.relative: + archive_root = self.bdist_dir + else: + archive_root = os.path.join( + self.bdist_dir, + self._ensure_relative(install.install_base)) + + self.set_undefined_options('install_egg_info', ('target', 'egginfo_dir')) + distinfo_dirname = '{}-{}.dist-info'.format( + safer_name(self.distribution.get_name()), + safer_version(self.distribution.get_version())) + distinfo_dir = os.path.join(self.bdist_dir, distinfo_dirname) + self.egg2dist(self.egginfo_dir, distinfo_dir) + + self.write_wheelfile(distinfo_dir) + + # Make the archive + if not os.path.exists(self.dist_dir): + os.makedirs(self.dist_dir) + + wheel_path = os.path.join(self.dist_dir, archive_basename + '.whl') + with WheelFile(wheel_path, 'w', self.compression) as wf: + wf.write_files(archive_root) + + # Add to 'Distribution.dist_files' so that the "upload" command works + getattr(self.distribution, 'dist_files', []).append( + ('bdist_wheel', + '{}.{}'.format(*sys.version_info[:2]), # like 3.7 + wheel_path)) + + if not self.keep_temp: + logger.info('removing %s', self.bdist_dir) + if not self.dry_run: + rmtree(self.bdist_dir, onerror=remove_readonly) + + def write_wheelfile(self, wheelfile_base, generator='bdist_wheel (' + wheel_version + ')'): + from email.message import Message + + # Workaround for Python 2.7 for when "generator" is unicode + if sys.version_info < (3,) and not isinstance(generator, str): + generator = generator.encode('utf-8') + + msg = Message() + msg['Wheel-Version'] = '1.0' # of the spec + msg['Generator'] = generator + msg['Root-Is-Purelib'] = str(self.root_is_pure).lower() + if self.build_number is not None: + msg['Build'] = self.build_number + + # Doesn't work for bdist_wininst + impl_tag, abi_tag, plat_tag = self.get_tag() + for impl in impl_tag.split('.'): + for abi in abi_tag.split('.'): + for plat in plat_tag.split('.'): + msg['Tag'] = '-'.join((impl, abi, plat)) + + wheelfile_path = os.path.join(wheelfile_base, 'WHEEL') + logger.info('creating %s', wheelfile_path) + buffer = BytesIO() + BytesGenerator(buffer, maxheaderlen=0).flatten(msg) + with open(wheelfile_path, 'wb') as f: + f.write(buffer.getvalue().replace(b'\r\n', b'\r')) + + def _ensure_relative(self, path): + # copied from dir_util, deleted + drive, path = os.path.splitdrive(path) + if path[0:1] == os.sep: + path = drive + path[1:] + return path + + @property + def license_paths(self): + metadata = self.distribution.get_option_dict('metadata') + files = set() + patterns = sorted({ + option for option in metadata.get('license_files', ('', ''))[1].split() + }) + + if 'license_file' in metadata: + warnings.warn('The "license_file" option is deprecated. Use ' + '"license_files" instead.', DeprecationWarning) + files.add(metadata['license_file'][1]) + + if 'license_file' not in metadata and 'license_files' not in metadata: + patterns = ('LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*') + + for pattern in patterns: + for path in iglob(pattern): + if path.endswith('~'): + logger.debug('ignoring license file "%s" as it looks like a backup', path) + continue + + if path not in files and os.path.isfile(path): + logger.info('adding license file "%s" (matched pattern "%s")', path, pattern) + files.add(path) + + return files + + def egg2dist(self, egginfo_path, distinfo_path): + """Convert an .egg-info directory into a .dist-info directory""" + def adios(p): + """Appropriately delete directory, file or link.""" + if os.path.exists(p) and not os.path.islink(p) and os.path.isdir(p): + shutil.rmtree(p) + elif os.path.exists(p): + os.unlink(p) + + adios(distinfo_path) + + if not os.path.exists(egginfo_path): + # There is no egg-info. This is probably because the egg-info + # file/directory is not named matching the distribution name used + # to name the archive file. Check for this case and report + # accordingly. + import glob + pat = os.path.join(os.path.dirname(egginfo_path), '*.egg-info') + possible = glob.glob(pat) + err = "Egg metadata expected at %s but not found" % (egginfo_path,) + if possible: + alt = os.path.basename(possible[0]) + err += " (%s found - possible misnamed archive file?)" % (alt,) + + raise ValueError(err) + + if os.path.isfile(egginfo_path): + # .egg-info is a single file + pkginfo_path = egginfo_path + pkg_info = pkginfo_to_metadata(egginfo_path, egginfo_path) + os.mkdir(distinfo_path) + else: + # .egg-info is a directory + pkginfo_path = os.path.join(egginfo_path, 'PKG-INFO') + pkg_info = pkginfo_to_metadata(egginfo_path, pkginfo_path) + + # ignore common egg metadata that is useless to wheel + shutil.copytree(egginfo_path, distinfo_path, + ignore=lambda x, y: {'PKG-INFO', 'requires.txt', 'SOURCES.txt', + 'not-zip-safe'} + ) + + # delete dependency_links if it is only whitespace + dependency_links_path = os.path.join(distinfo_path, 'dependency_links.txt') + with open(dependency_links_path, 'r') as dependency_links_file: + dependency_links = dependency_links_file.read().strip() + if not dependency_links: + adios(dependency_links_path) + + write_pkg_info(os.path.join(distinfo_path, 'METADATA'), pkg_info) + + for license_path in self.license_paths: + filename = os.path.basename(license_path) + shutil.copy(license_path, os.path.join(distinfo_path, filename)) + + adios(egginfo_path) diff --git a/pipenv/vendor/wheel/cli/__init__.py b/pipenv/vendor/wheel/cli/__init__.py new file mode 100644 index 00000000..95740bfb --- /dev/null +++ b/pipenv/vendor/wheel/cli/__init__.py @@ -0,0 +1,88 @@ +""" +Wheel command-line utility. +""" + +from __future__ import print_function + +import argparse +import os +import sys + + +def require_pkgresources(name): + try: + import pkg_resources # noqa: F401 + except ImportError: + raise RuntimeError("'{0}' needs pkg_resources (part of setuptools).".format(name)) + + +class WheelError(Exception): + pass + + +def unpack_f(args): + from .unpack import unpack + unpack(args.wheelfile, args.dest) + + +def pack_f(args): + from .pack import pack + pack(args.directory, args.dest_dir, args.build_number) + + +def convert_f(args): + from .convert import convert + convert(args.files, args.dest_dir, args.verbose) + + +def version_f(args): + from .. import __version__ + print("wheel %s" % __version__) + + +def parser(): + p = argparse.ArgumentParser() + s = p.add_subparsers(help="commands") + + unpack_parser = s.add_parser('unpack', help='Unpack wheel') + unpack_parser.add_argument('--dest', '-d', help='Destination directory', + default='.') + unpack_parser.add_argument('wheelfile', help='Wheel file') + unpack_parser.set_defaults(func=unpack_f) + + repack_parser = s.add_parser('pack', help='Repack wheel') + repack_parser.add_argument('directory', help='Root directory of the unpacked wheel') + repack_parser.add_argument('--dest-dir', '-d', default=os.path.curdir, + help="Directory to store the wheel (default %(default)s)") + repack_parser.add_argument('--build-number', help="Build tag to use in the wheel name") + repack_parser.set_defaults(func=pack_f) + + convert_parser = s.add_parser('convert', help='Convert egg or wininst to wheel') + convert_parser.add_argument('files', nargs='*', help='Files to convert') + convert_parser.add_argument('--dest-dir', '-d', default=os.path.curdir, + help="Directory to store wheels (default %(default)s)") + convert_parser.add_argument('--verbose', '-v', action='store_true') + convert_parser.set_defaults(func=convert_f) + + version_parser = s.add_parser('version', help='Print version and exit') + version_parser.set_defaults(func=version_f) + + help_parser = s.add_parser('help', help='Show this help') + help_parser.set_defaults(func=lambda args: p.print_help()) + + return p + + +def main(): + p = parser() + args = p.parse_args() + if not hasattr(args, 'func'): + p.print_help() + else: + try: + args.func(args) + return 0 + except WheelError as e: + print(e, file=sys.stderr) + + return 1 diff --git a/pipenv/vendor/wheel/cli/convert.py b/pipenv/vendor/wheel/cli/convert.py new file mode 100644 index 00000000..154f1b1e --- /dev/null +++ b/pipenv/vendor/wheel/cli/convert.py @@ -0,0 +1,269 @@ +import os.path +import re +import shutil +import sys +import tempfile +import zipfile +from distutils import dist +from glob import iglob + +from ..bdist_wheel import bdist_wheel +from ..wheelfile import WheelFile +from . import WheelError, require_pkgresources + +egg_info_re = re.compile(r''' + (?P<name>.+?)-(?P<ver>.+?) + (-(?P<pyver>py\d\.\d+) + (-(?P<arch>.+?))? + )?.egg$''', re.VERBOSE) + + +class _bdist_wheel_tag(bdist_wheel): + # allow the client to override the default generated wheel tag + # The default bdist_wheel implementation uses python and abi tags + # of the running python process. This is not suitable for + # generating/repackaging prebuild binaries. + + full_tag_supplied = False + full_tag = None # None or a (pytag, soabitag, plattag) triple + + def get_tag(self): + if self.full_tag_supplied and self.full_tag is not None: + return self.full_tag + else: + return bdist_wheel.get_tag(self) + + +def egg2wheel(egg_path, dest_dir): + filename = os.path.basename(egg_path) + match = egg_info_re.match(filename) + if not match: + raise WheelError('Invalid egg file name: {}'.format(filename)) + + egg_info = match.groupdict() + dir = tempfile.mkdtemp(suffix="_e2w") + if os.path.isfile(egg_path): + # assume we have a bdist_egg otherwise + with zipfile.ZipFile(egg_path) as egg: + egg.extractall(dir) + else: + # support buildout-style installed eggs directories + for pth in os.listdir(egg_path): + src = os.path.join(egg_path, pth) + if os.path.isfile(src): + shutil.copy2(src, dir) + else: + shutil.copytree(src, os.path.join(dir, pth)) + + pyver = egg_info['pyver'] + if pyver: + pyver = egg_info['pyver'] = pyver.replace('.', '') + + arch = (egg_info['arch'] or 'any').replace('.', '_').replace('-', '_') + + # assume all binary eggs are for CPython + abi = 'cp' + pyver[2:] if arch != 'any' else 'none' + + root_is_purelib = egg_info['arch'] is None + if root_is_purelib: + bw = bdist_wheel(dist.Distribution()) + else: + bw = _bdist_wheel_tag(dist.Distribution()) + + bw.root_is_pure = root_is_purelib + bw.python_tag = pyver + bw.plat_name_supplied = True + bw.plat_name = egg_info['arch'] or 'any' + if not root_is_purelib: + bw.full_tag_supplied = True + bw.full_tag = (pyver, abi, arch) + + dist_info_dir = os.path.join(dir, '{name}-{ver}.dist-info'.format(**egg_info)) + bw.egg2dist(os.path.join(dir, 'EGG-INFO'), dist_info_dir) + bw.write_wheelfile(dist_info_dir, generator='egg2wheel') + wheel_name = '{name}-{ver}-{pyver}-{}-{}.whl'.format(abi, arch, **egg_info) + with WheelFile(os.path.join(dest_dir, wheel_name), 'w') as wf: + wf.write_files(dir) + + shutil.rmtree(dir) + + +def parse_wininst_info(wininfo_name, egginfo_name): + """Extract metadata from filenames. + + Extracts the 4 metadataitems needed (name, version, pyversion, arch) from + the installer filename and the name of the egg-info directory embedded in + the zipfile (if any). + + The egginfo filename has the format:: + + name-ver(-pyver)(-arch).egg-info + + The installer filename has the format:: + + name-ver.arch(-pyver).exe + + Some things to note: + + 1. The installer filename is not definitive. An installer can be renamed + and work perfectly well as an installer. So more reliable data should + be used whenever possible. + 2. The egg-info data should be preferred for the name and version, because + these come straight from the distutils metadata, and are mandatory. + 3. The pyver from the egg-info data should be ignored, as it is + constructed from the version of Python used to build the installer, + which is irrelevant - the installer filename is correct here (even to + the point that when it's not there, any version is implied). + 4. The architecture must be taken from the installer filename, as it is + not included in the egg-info data. + 5. Architecture-neutral installers still have an architecture because the + installer format itself (being executable) is architecture-specific. We + should therefore ignore the architecture if the content is pure-python. + """ + + egginfo = None + if egginfo_name: + egginfo = egg_info_re.search(egginfo_name) + if not egginfo: + raise ValueError("Egg info filename %s is not valid" % (egginfo_name,)) + + # Parse the wininst filename + # 1. Distribution name (up to the first '-') + w_name, sep, rest = wininfo_name.partition('-') + if not sep: + raise ValueError("Installer filename %s is not valid" % (wininfo_name,)) + + # Strip '.exe' + rest = rest[:-4] + # 2. Python version (from the last '-', must start with 'py') + rest2, sep, w_pyver = rest.rpartition('-') + if sep and w_pyver.startswith('py'): + rest = rest2 + w_pyver = w_pyver.replace('.', '') + else: + # Not version specific - use py2.py3. While it is possible that + # pure-Python code is not compatible with both Python 2 and 3, there + # is no way of knowing from the wininst format, so we assume the best + # here (the user can always manually rename the wheel to be more + # restrictive if needed). + w_pyver = 'py2.py3' + # 3. Version and architecture + w_ver, sep, w_arch = rest.rpartition('.') + if not sep: + raise ValueError("Installer filename %s is not valid" % (wininfo_name,)) + + if egginfo: + w_name = egginfo.group('name') + w_ver = egginfo.group('ver') + + return {'name': w_name, 'ver': w_ver, 'arch': w_arch, 'pyver': w_pyver} + + +def wininst2wheel(path, dest_dir): + with zipfile.ZipFile(path) as bdw: + # Search for egg-info in the archive + egginfo_name = None + for filename in bdw.namelist(): + if '.egg-info' in filename: + egginfo_name = filename + break + + info = parse_wininst_info(os.path.basename(path), egginfo_name) + + root_is_purelib = True + for zipinfo in bdw.infolist(): + if zipinfo.filename.startswith('PLATLIB'): + root_is_purelib = False + break + if root_is_purelib: + paths = {'purelib': ''} + else: + paths = {'platlib': ''} + + dist_info = "%(name)s-%(ver)s" % info + datadir = "%s.data/" % dist_info + + # rewrite paths to trick ZipFile into extracting an egg + # XXX grab wininst .ini - between .exe, padding, and first zip file. + members = [] + egginfo_name = '' + for zipinfo in bdw.infolist(): + key, basename = zipinfo.filename.split('/', 1) + key = key.lower() + basepath = paths.get(key, None) + if basepath is None: + basepath = datadir + key.lower() + '/' + oldname = zipinfo.filename + newname = basepath + basename + zipinfo.filename = newname + del bdw.NameToInfo[oldname] + bdw.NameToInfo[newname] = zipinfo + # Collect member names, but omit '' (from an entry like "PLATLIB/" + if newname: + members.append(newname) + # Remember egg-info name for the egg2dist call below + if not egginfo_name: + if newname.endswith('.egg-info'): + egginfo_name = newname + elif '.egg-info/' in newname: + egginfo_name, sep, _ = newname.rpartition('/') + dir = tempfile.mkdtemp(suffix="_b2w") + bdw.extractall(dir, members) + + # egg2wheel + abi = 'none' + pyver = info['pyver'] + arch = (info['arch'] or 'any').replace('.', '_').replace('-', '_') + # Wininst installers always have arch even if they are not + # architecture-specific (because the format itself is). + # So, assume the content is architecture-neutral if root is purelib. + if root_is_purelib: + arch = 'any' + # If the installer is architecture-specific, it's almost certainly also + # CPython-specific. + if arch != 'any': + pyver = pyver.replace('py', 'cp') + wheel_name = '-'.join((dist_info, pyver, abi, arch)) + if root_is_purelib: + bw = bdist_wheel(dist.Distribution()) + else: + bw = _bdist_wheel_tag(dist.Distribution()) + + bw.root_is_pure = root_is_purelib + bw.python_tag = pyver + bw.plat_name_supplied = True + bw.plat_name = info['arch'] or 'any' + + if not root_is_purelib: + bw.full_tag_supplied = True + bw.full_tag = (pyver, abi, arch) + + dist_info_dir = os.path.join(dir, '%s.dist-info' % dist_info) + bw.egg2dist(os.path.join(dir, egginfo_name), dist_info_dir) + bw.write_wheelfile(dist_info_dir, generator='wininst2wheel') + + wheel_path = os.path.join(dest_dir, wheel_name) + with WheelFile(wheel_path, 'w') as wf: + wf.write_files(dir) + + shutil.rmtree(dir) + + +def convert(files, dest_dir, verbose): + # Only support wheel convert if pkg_resources is present + require_pkgresources('wheel convert') + + for pat in files: + for installer in iglob(pat): + if os.path.splitext(installer)[1] == '.egg': + conv = egg2wheel + else: + conv = wininst2wheel + + if verbose: + print("{}... ".format(installer)) + sys.stdout.flush() + + conv(installer, dest_dir) + if verbose: + print("OK") diff --git a/pipenv/vendor/wheel/cli/pack.py b/pipenv/vendor/wheel/cli/pack.py new file mode 100644 index 00000000..762da308 --- /dev/null +++ b/pipenv/vendor/wheel/cli/pack.py @@ -0,0 +1,79 @@ +from __future__ import print_function + +import os.path +import re +import sys + +from pipenv.vendor.wheel.cli import WheelError +from pipenv.vendor.wheel.wheelfile import WheelFile + +DIST_INFO_RE = re.compile(r"^(?P<namever>(?P<name>.+?)-(?P<ver>\d.*?))\.dist-info$") +BUILD_NUM_RE = re.compile(br'Build: (\d\w*)$') + + +def pack(directory, dest_dir, build_number): + """Repack a previously unpacked wheel directory into a new wheel file. + + The .dist-info/WHEEL file must contain one or more tags so that the target + wheel file name can be determined. + + :param directory: The unpacked wheel directory + :param dest_dir: Destination directory (defaults to the current directory) + """ + # Find the .dist-info directory + dist_info_dirs = [fn for fn in os.listdir(directory) + if os.path.isdir(os.path.join(directory, fn)) and DIST_INFO_RE.match(fn)] + if len(dist_info_dirs) > 1: + raise WheelError('Multiple .dist-info directories found in {}'.format(directory)) + elif not dist_info_dirs: + raise WheelError('No .dist-info directories found in {}'.format(directory)) + + # Determine the target wheel filename + dist_info_dir = dist_info_dirs[0] + name_version = DIST_INFO_RE.match(dist_info_dir).group('namever') + + # Read the tags and the existing build number from .dist-info/WHEEL + existing_build_number = None + wheel_file_path = os.path.join(directory, dist_info_dir, 'WHEEL') + with open(wheel_file_path) as f: + tags = [] + for line in f: + if line.startswith('Tag: '): + tags.append(line.split(' ')[1].rstrip()) + elif line.startswith('Build: '): + existing_build_number = line.split(' ')[1].rstrip() + + if not tags: + raise WheelError('No tags present in {}/WHEEL; cannot determine target wheel filename' + .format(dist_info_dir)) + + # Set the wheel file name and add/replace/remove the Build tag in .dist-info/WHEEL + build_number = build_number if build_number is not None else existing_build_number + if build_number is not None: + if build_number: + name_version += '-' + build_number + + if build_number != existing_build_number: + replacement = ('Build: %s\r\n' % build_number).encode('ascii') if build_number else b'' + with open(wheel_file_path, 'rb+') as f: + wheel_file_content = f.read() + if not BUILD_NUM_RE.subn(replacement, wheel_file_content)[1]: + wheel_file_content += replacement + + f.truncate() + f.write(wheel_file_content) + + # Reassemble the tags for the wheel file + impls = sorted({tag.split('-')[0] for tag in tags}) + abivers = sorted({tag.split('-')[1] for tag in tags}) + platforms = sorted({tag.split('-')[2] for tag in tags}) + tagline = '-'.join(['.'.join(impls), '.'.join(abivers), '.'.join(platforms)]) + + # Repack the wheel + wheel_path = os.path.join(dest_dir, '{}-{}.whl'.format(name_version, tagline)) + with WheelFile(wheel_path, 'w') as wf: + print("Repacking wheel as {}...".format(wheel_path), end='') + sys.stdout.flush() + wf.write_files(directory) + + print('OK') diff --git a/pipenv/vendor/wheel/cli/unpack.py b/pipenv/vendor/wheel/cli/unpack.py new file mode 100644 index 00000000..2e9857a3 --- /dev/null +++ b/pipenv/vendor/wheel/cli/unpack.py @@ -0,0 +1,25 @@ +from __future__ import print_function + +import os.path +import sys + +from ..wheelfile import WheelFile + + +def unpack(path, dest='.'): + """Unpack a wheel. + + Wheel content will be unpacked to {dest}/{name}-{ver}, where {name} + is the package name and {ver} its version. + + :param path: The path to the wheel. + :param dest: Destination directory (default to current directory). + """ + with WheelFile(path) as wf: + namever = wf.parsed_filename.group('namever') + destination = os.path.join(dest, namever) + print("Unpacking to: {}...".format(destination), end='') + sys.stdout.flush() + wf.extractall(destination) + + print('OK') diff --git a/pipenv/vendor/wheel/macosx_libfile.py b/pipenv/vendor/wheel/macosx_libfile.py new file mode 100644 index 00000000..8918039f --- /dev/null +++ b/pipenv/vendor/wheel/macosx_libfile.py @@ -0,0 +1,428 @@ +""" +This module contains function to analyse dynamic library +headers to extract system information + +Currently only for MacOSX + +Library file on macosx system starts with Mach-O or Fat field. +This can be distinguish by first 32 bites and it is called magic number. +Proper value of magic number is with suffix _MAGIC. Suffix _CIGAM means +reversed bytes order. +Both fields can occur in two types: 32 and 64 bytes. + +FAT field inform that this library contains few version of library +(typically for different types version). It contains +information where Mach-O headers starts. + +Each section started with Mach-O header contains one library +(So if file starts with this field it contains only one version). + +After filed Mach-O there are section fields. +Each of them starts with two fields: +cmd - magic number for this command +cmdsize - total size occupied by this section information. + +In this case only sections LC_VERSION_MIN_MACOSX (for macosx 10.13 and earlier) +and LC_BUILD_VERSION (for macosx 10.14 and newer) are interesting, +because them contains information about minimal system version. + +Important remarks: +- For fat files this implementation looks for maximum number version. + It not check if it is 32 or 64 and do not compare it with currently builded package. + So it is possible to false report higher version that needed. +- All structures signatures are taken form macosx header files. +- I think that binary format will be more stable than `otool` output. + and if apple introduce some changes both implementation will need to be updated. +- The system compile will set the deployment target no lower than + 11.0 for arm64 builds. For "Universal 2" builds use the x86_64 deployment + target when the arm64 target is 11.0. +""" + +import ctypes +import os +import sys + +"""here the needed const and struct from mach-o header files""" + +FAT_MAGIC = 0xcafebabe +FAT_CIGAM = 0xbebafeca +FAT_MAGIC_64 = 0xcafebabf +FAT_CIGAM_64 = 0xbfbafeca +MH_MAGIC = 0xfeedface +MH_CIGAM = 0xcefaedfe +MH_MAGIC_64 = 0xfeedfacf +MH_CIGAM_64 = 0xcffaedfe + +LC_VERSION_MIN_MACOSX = 0x24 +LC_BUILD_VERSION = 0x32 + +CPU_TYPE_ARM64 = 0x0100000c + +mach_header_fields = [ + ("magic", ctypes.c_uint32), ("cputype", ctypes.c_int), + ("cpusubtype", ctypes.c_int), ("filetype", ctypes.c_uint32), + ("ncmds", ctypes.c_uint32), ("sizeofcmds", ctypes.c_uint32), + ("flags", ctypes.c_uint32) + ] +""" +struct mach_header { + uint32_t magic; /* mach magic number identifier */ + cpu_type_t cputype; /* cpu specifier */ + cpu_subtype_t cpusubtype; /* machine specifier */ + uint32_t filetype; /* type of file */ + uint32_t ncmds; /* number of load commands */ + uint32_t sizeofcmds; /* the size of all the load commands */ + uint32_t flags; /* flags */ +}; +typedef integer_t cpu_type_t; +typedef integer_t cpu_subtype_t; +""" + +mach_header_fields_64 = mach_header_fields + [("reserved", ctypes.c_uint32)] +""" +struct mach_header_64 { + uint32_t magic; /* mach magic number identifier */ + cpu_type_t cputype; /* cpu specifier */ + cpu_subtype_t cpusubtype; /* machine specifier */ + uint32_t filetype; /* type of file */ + uint32_t ncmds; /* number of load commands */ + uint32_t sizeofcmds; /* the size of all the load commands */ + uint32_t flags; /* flags */ + uint32_t reserved; /* reserved */ +}; +""" + +fat_header_fields = [("magic", ctypes.c_uint32), ("nfat_arch", ctypes.c_uint32)] +""" +struct fat_header { + uint32_t magic; /* FAT_MAGIC or FAT_MAGIC_64 */ + uint32_t nfat_arch; /* number of structs that follow */ +}; +""" + +fat_arch_fields = [ + ("cputype", ctypes.c_int), ("cpusubtype", ctypes.c_int), + ("offset", ctypes.c_uint32), ("size", ctypes.c_uint32), + ("align", ctypes.c_uint32) +] +""" +struct fat_arch { + cpu_type_t cputype; /* cpu specifier (int) */ + cpu_subtype_t cpusubtype; /* machine specifier (int) */ + uint32_t offset; /* file offset to this object file */ + uint32_t size; /* size of this object file */ + uint32_t align; /* alignment as a power of 2 */ +}; +""" + +fat_arch_64_fields = [ + ("cputype", ctypes.c_int), ("cpusubtype", ctypes.c_int), + ("offset", ctypes.c_uint64), ("size", ctypes.c_uint64), + ("align", ctypes.c_uint32), ("reserved", ctypes.c_uint32) +] +""" +struct fat_arch_64 { + cpu_type_t cputype; /* cpu specifier (int) */ + cpu_subtype_t cpusubtype; /* machine specifier (int) */ + uint64_t offset; /* file offset to this object file */ + uint64_t size; /* size of this object file */ + uint32_t align; /* alignment as a power of 2 */ + uint32_t reserved; /* reserved */ +}; +""" + +segment_base_fields = [("cmd", ctypes.c_uint32), ("cmdsize", ctypes.c_uint32)] +"""base for reading segment info""" + +segment_command_fields = [ + ("cmd", ctypes.c_uint32), ("cmdsize", ctypes.c_uint32), + ("segname", ctypes.c_char * 16), ("vmaddr", ctypes.c_uint32), + ("vmsize", ctypes.c_uint32), ("fileoff", ctypes.c_uint32), + ("filesize", ctypes.c_uint32), ("maxprot", ctypes.c_int), + ("initprot", ctypes.c_int), ("nsects", ctypes.c_uint32), + ("flags", ctypes.c_uint32), + ] +""" +struct segment_command { /* for 32-bit architectures */ + uint32_t cmd; /* LC_SEGMENT */ + uint32_t cmdsize; /* includes sizeof section structs */ + char segname[16]; /* segment name */ + uint32_t vmaddr; /* memory address of this segment */ + uint32_t vmsize; /* memory size of this segment */ + uint32_t fileoff; /* file offset of this segment */ + uint32_t filesize; /* amount to map from the file */ + vm_prot_t maxprot; /* maximum VM protection */ + vm_prot_t initprot; /* initial VM protection */ + uint32_t nsects; /* number of sections in segment */ + uint32_t flags; /* flags */ +}; +typedef int vm_prot_t; +""" + +segment_command_fields_64 = [ + ("cmd", ctypes.c_uint32), ("cmdsize", ctypes.c_uint32), + ("segname", ctypes.c_char * 16), ("vmaddr", ctypes.c_uint64), + ("vmsize", ctypes.c_uint64), ("fileoff", ctypes.c_uint64), + ("filesize", ctypes.c_uint64), ("maxprot", ctypes.c_int), + ("initprot", ctypes.c_int), ("nsects", ctypes.c_uint32), + ("flags", ctypes.c_uint32), + ] +""" +struct segment_command_64 { /* for 64-bit architectures */ + uint32_t cmd; /* LC_SEGMENT_64 */ + uint32_t cmdsize; /* includes sizeof section_64 structs */ + char segname[16]; /* segment name */ + uint64_t vmaddr; /* memory address of this segment */ + uint64_t vmsize; /* memory size of this segment */ + uint64_t fileoff; /* file offset of this segment */ + uint64_t filesize; /* amount to map from the file */ + vm_prot_t maxprot; /* maximum VM protection */ + vm_prot_t initprot; /* initial VM protection */ + uint32_t nsects; /* number of sections in segment */ + uint32_t flags; /* flags */ +}; +""" + +version_min_command_fields = segment_base_fields + \ + [("version", ctypes.c_uint32), ("sdk", ctypes.c_uint32)] +""" +struct version_min_command { + uint32_t cmd; /* LC_VERSION_MIN_MACOSX or + LC_VERSION_MIN_IPHONEOS or + LC_VERSION_MIN_WATCHOS or + LC_VERSION_MIN_TVOS */ + uint32_t cmdsize; /* sizeof(struct min_version_command) */ + uint32_t version; /* X.Y.Z is encoded in nibbles xxxx.yy.zz */ + uint32_t sdk; /* X.Y.Z is encoded in nibbles xxxx.yy.zz */ +}; +""" + +build_version_command_fields = segment_base_fields + \ + [("platform", ctypes.c_uint32), ("minos", ctypes.c_uint32), + ("sdk", ctypes.c_uint32), ("ntools", ctypes.c_uint32)] +""" +struct build_version_command { + uint32_t cmd; /* LC_BUILD_VERSION */ + uint32_t cmdsize; /* sizeof(struct build_version_command) plus */ + /* ntools * sizeof(struct build_tool_version) */ + uint32_t platform; /* platform */ + uint32_t minos; /* X.Y.Z is encoded in nibbles xxxx.yy.zz */ + uint32_t sdk; /* X.Y.Z is encoded in nibbles xxxx.yy.zz */ + uint32_t ntools; /* number of tool entries following this */ +}; +""" + + +def swap32(x): + return (((x << 24) & 0xFF000000) | + ((x << 8) & 0x00FF0000) | + ((x >> 8) & 0x0000FF00) | + ((x >> 24) & 0x000000FF)) + + +def get_base_class_and_magic_number(lib_file, seek=None): + if seek is None: + seek = lib_file.tell() + else: + lib_file.seek(seek) + magic_number = ctypes.c_uint32.from_buffer_copy( + lib_file.read(ctypes.sizeof(ctypes.c_uint32))).value + + # Handle wrong byte order + if magic_number in [FAT_CIGAM, FAT_CIGAM_64, MH_CIGAM, MH_CIGAM_64]: + if sys.byteorder == "little": + BaseClass = ctypes.BigEndianStructure + else: + BaseClass = ctypes.LittleEndianStructure + + magic_number = swap32(magic_number) + else: + BaseClass = ctypes.Structure + + lib_file.seek(seek) + return BaseClass, magic_number + + +def read_data(struct_class, lib_file): + return struct_class.from_buffer_copy(lib_file.read( + ctypes.sizeof(struct_class))) + + +def extract_macosx_min_system_version(path_to_lib): + with open(path_to_lib, "rb") as lib_file: + BaseClass, magic_number = get_base_class_and_magic_number(lib_file, 0) + if magic_number not in [FAT_MAGIC, FAT_MAGIC_64, MH_MAGIC, MH_MAGIC_64]: + return + + if magic_number in [FAT_MAGIC, FAT_CIGAM_64]: + class FatHeader(BaseClass): + _fields_ = fat_header_fields + + fat_header = read_data(FatHeader, lib_file) + if magic_number == FAT_MAGIC: + + class FatArch(BaseClass): + _fields_ = fat_arch_fields + else: + + class FatArch(BaseClass): + _fields_ = fat_arch_64_fields + + fat_arch_list = [read_data(FatArch, lib_file) for _ in range(fat_header.nfat_arch)] + + versions_list = [] + for el in fat_arch_list: + try: + version = read_mach_header(lib_file, el.offset) + if version is not None: + if el.cputype == CPU_TYPE_ARM64 and len(fat_arch_list) != 1: + # Xcode will not set the deployment target below 11.0.0 + # for the arm64 architecture. Ignore the arm64 deployment + # in fat binaries when the target is 11.0.0, that way + # the other architetures can select a lower deployment + # target. + # This is safe because there is no arm64 variant for + # macOS 10.15 or earlier. + if version == (11, 0, 0): + continue + versions_list.append(version) + except ValueError: + pass + + if len(versions_list) > 0: + return max(versions_list) + else: + return None + + else: + try: + return read_mach_header(lib_file, 0) + except ValueError: + """when some error during read library files""" + return None + + +def read_mach_header(lib_file, seek=None): + """ + This funcition parse mach-O header and extract + information about minimal system version + + :param lib_file: reference to opened library file with pointer + """ + if seek is not None: + lib_file.seek(seek) + base_class, magic_number = get_base_class_and_magic_number(lib_file) + arch = "32" if magic_number == MH_MAGIC else "64" + + class SegmentBase(base_class): + _fields_ = segment_base_fields + + if arch == "32": + + class MachHeader(base_class): + _fields_ = mach_header_fields + + else: + + class MachHeader(base_class): + _fields_ = mach_header_fields_64 + + mach_header = read_data(MachHeader, lib_file) + for _i in range(mach_header.ncmds): + pos = lib_file.tell() + segment_base = read_data(SegmentBase, lib_file) + lib_file.seek(pos) + if segment_base.cmd == LC_VERSION_MIN_MACOSX: + class VersionMinCommand(base_class): + _fields_ = version_min_command_fields + + version_info = read_data(VersionMinCommand, lib_file) + return parse_version(version_info.version) + elif segment_base.cmd == LC_BUILD_VERSION: + class VersionBuild(base_class): + _fields_ = build_version_command_fields + + version_info = read_data(VersionBuild, lib_file) + return parse_version(version_info.minos) + else: + lib_file.seek(pos + segment_base.cmdsize) + continue + + +def parse_version(version): + x = (version & 0xffff0000) >> 16 + y = (version & 0x0000ff00) >> 8 + z = (version & 0x000000ff) + return x, y, z + + +def calculate_macosx_platform_tag(archive_root, platform_tag): + """ + Calculate proper macosx platform tag basing on files which are included to wheel + + Example platform tag `macosx-10.14-x86_64` + """ + prefix, base_version, suffix = platform_tag.split('-') + base_version = tuple([int(x) for x in base_version.split(".")]) + base_version = base_version[:2] + if base_version[0] > 10: + base_version = (base_version[0], 0) + assert len(base_version) == 2 + if "MACOSX_DEPLOYMENT_TARGET" in os.environ: + deploy_target = tuple([int(x) for x in os.environ[ + "MACOSX_DEPLOYMENT_TARGET"].split(".")]) + deploy_target = deploy_target[:2] + if deploy_target[0] > 10: + deploy_target = (deploy_target[0], 0) + if deploy_target < base_version: + sys.stderr.write( + "[WARNING] MACOSX_DEPLOYMENT_TARGET is set to a lower value ({}) than the " + "version on which the Python interpreter was compiled ({}), and will be " + "ignored.\n".format('.'.join(str(x) for x in deploy_target), + '.'.join(str(x) for x in base_version)) + ) + else: + base_version = deploy_target + + assert len(base_version) == 2 + start_version = base_version + versions_dict = {} + for (dirpath, dirnames, filenames) in os.walk(archive_root): + for filename in filenames: + if filename.endswith('.dylib') or filename.endswith('.so'): + lib_path = os.path.join(dirpath, filename) + min_ver = extract_macosx_min_system_version(lib_path) + if min_ver is not None: + min_ver = min_ver[0:2] + if min_ver[0] > 10: + min_ver = (min_ver[0], 0) + versions_dict[lib_path] = min_ver + + if len(versions_dict) > 0: + base_version = max(base_version, max(versions_dict.values())) + + # macosx platform tag do not support minor bugfix release + fin_base_version = "_".join([str(x) for x in base_version]) + if start_version < base_version: + problematic_files = [k for k, v in versions_dict.items() if v > start_version] + problematic_files = "\n".join(problematic_files) + if len(problematic_files) == 1: + files_form = "this file" + else: + files_form = "these files" + error_message = \ + "[WARNING] This wheel needs a higher macOS version than {} " \ + "To silence this warning, set MACOSX_DEPLOYMENT_TARGET to at least " +\ + fin_base_version + " or recreate " + files_form + " with lower " \ + "MACOSX_DEPLOYMENT_TARGET: \n" + problematic_files + + if "MACOSX_DEPLOYMENT_TARGET" in os.environ: + error_message = error_message.format("is set in MACOSX_DEPLOYMENT_TARGET variable.") + else: + error_message = error_message.format( + "the version your Python interpreter is compiled against.") + + sys.stderr.write(error_message) + + platform_tag = prefix + "_" + fin_base_version + "_" + suffix + return platform_tag diff --git a/pipenv/vendor/wheel/metadata.py b/pipenv/vendor/wheel/metadata.py new file mode 100644 index 00000000..37efa743 --- /dev/null +++ b/pipenv/vendor/wheel/metadata.py @@ -0,0 +1,133 @@ +""" +Tools for converting old- to new-style metadata. +""" + +import os.path +import textwrap + +import pkg_resources + +from .pkginfo import read_pkg_info + + +def requires_to_requires_dist(requirement): + """Return the version specifier for a requirement in PEP 345/566 fashion.""" + if getattr(requirement, 'url', None): + return " @ " + requirement.url + + requires_dist = [] + for op, ver in requirement.specs: + requires_dist.append(op + ver) + if not requires_dist: + return '' + return " (%s)" % ','.join(sorted(requires_dist)) + + +def convert_requirements(requirements): + """Yield Requires-Dist: strings for parsed requirements strings.""" + for req in requirements: + parsed_requirement = pkg_resources.Requirement.parse(req) + spec = requires_to_requires_dist(parsed_requirement) + extras = ",".join(sorted(parsed_requirement.extras)) + if extras: + extras = "[%s]" % extras + yield (parsed_requirement.project_name + extras + spec) + + +def generate_requirements(extras_require): + """ + Convert requirements from a setup()-style dictionary to ('Requires-Dist', 'requirement') + and ('Provides-Extra', 'extra') tuples. + + extras_require is a dictionary of {extra: [requirements]} as passed to setup(), + using the empty extra {'': [requirements]} to hold install_requires. + """ + for extra, depends in extras_require.items(): + condition = '' + extra = extra or '' + if ':' in extra: # setuptools extra:condition syntax + extra, condition = extra.split(':', 1) + + extra = pkg_resources.safe_extra(extra) + if extra: + yield 'Provides-Extra', extra + if condition: + condition = "(" + condition + ") and " + condition += "extra == '%s'" % extra + + if condition: + condition = ' ; ' + condition + + for new_req in convert_requirements(depends): + yield 'Requires-Dist', new_req + condition + + +def pkginfo_to_metadata(egg_info_path, pkginfo_path): + """ + Convert .egg-info directory with PKG-INFO to the Metadata 2.1 format + """ + pkg_info = read_pkg_info(pkginfo_path) + pkg_info.replace_header('Metadata-Version', '2.1') + # Those will be regenerated from `requires.txt`. + del pkg_info['Provides-Extra'] + del pkg_info['Requires-Dist'] + requires_path = os.path.join(egg_info_path, 'requires.txt') + if os.path.exists(requires_path): + with open(requires_path) as requires_file: + requires = requires_file.read() + + parsed_requirements = sorted(pkg_resources.split_sections(requires), + key=lambda x: x[0] or '') + for extra, reqs in parsed_requirements: + for key, value in generate_requirements({extra: reqs}): + if (key, value) not in pkg_info.items(): + pkg_info[key] = value + + description = pkg_info['Description'] + if description: + pkg_info.set_payload(dedent_description(pkg_info)) + del pkg_info['Description'] + + return pkg_info + + +def pkginfo_unicode(pkg_info, field): + """Hack to coax Unicode out of an email Message() - Python 3.3+""" + text = pkg_info[field] + field = field.lower() + if not isinstance(text, str): + for item in pkg_info.raw_items(): + if item[0].lower() == field: + text = item[1].encode('ascii', 'surrogateescape') \ + .decode('utf-8') + break + + return text + + +def dedent_description(pkg_info): + """ + Dedent and convert pkg_info['Description'] to Unicode. + """ + description = pkg_info['Description'] + + # Python 3 Unicode handling, sorta. + surrogates = False + if not isinstance(description, str): + surrogates = True + description = pkginfo_unicode(pkg_info, 'Description') + + description_lines = description.splitlines() + description_dedent = '\n'.join( + # if the first line of long_description is blank, + # the first line here will be indented. + (description_lines[0].lstrip(), + textwrap.dedent('\n'.join(description_lines[1:])), + '\n')) + + if surrogates: + description_dedent = description_dedent \ + .encode("utf8") \ + .decode("ascii", "surrogateescape") + + return description_dedent diff --git a/pipenv/vendor/wheel/pkginfo.py b/pipenv/vendor/wheel/pkginfo.py new file mode 100644 index 00000000..115be45b --- /dev/null +++ b/pipenv/vendor/wheel/pkginfo.py @@ -0,0 +1,43 @@ +"""Tools for reading and writing PKG-INFO / METADATA without caring +about the encoding.""" + +from email.parser import Parser + +try: + unicode + _PY3 = False +except NameError: + _PY3 = True + +if not _PY3: + from email.generator import Generator + + def read_pkg_info_bytes(bytestr): + return Parser().parsestr(bytestr) + + def read_pkg_info(path): + with open(path, "r") as headers: + message = Parser().parse(headers) + return message + + def write_pkg_info(path, message): + with open(path, 'w') as metadata: + Generator(metadata, mangle_from_=False, maxheaderlen=0).flatten(message) +else: + from email.generator import BytesGenerator + + def read_pkg_info_bytes(bytestr): + headers = bytestr.decode(encoding="ascii", errors="surrogateescape") + message = Parser().parsestr(headers) + return message + + def read_pkg_info(path): + with open(path, "r", + encoding="ascii", + errors="surrogateescape") as headers: + message = Parser().parse(headers) + return message + + def write_pkg_info(path, message): + with open(path, "wb") as out: + BytesGenerator(out, mangle_from_=False, maxheaderlen=0).flatten(message) diff --git a/pipenv/vendor/wheel/util.py b/pipenv/vendor/wheel/util.py new file mode 100644 index 00000000..3ae2b445 --- /dev/null +++ b/pipenv/vendor/wheel/util.py @@ -0,0 +1,46 @@ +import base64 +import io +import sys + + +if sys.version_info[0] < 3: + text_type = unicode # noqa: F821 + + StringIO = io.BytesIO + + def native(s, encoding='utf-8'): + if isinstance(s, unicode): # noqa: F821 + return s.encode(encoding) + return s +else: + text_type = str + + StringIO = io.StringIO + + def native(s, encoding='utf-8'): + if isinstance(s, bytes): + return s.decode(encoding) + return s + + +def urlsafe_b64encode(data): + """urlsafe_b64encode without padding""" + return base64.urlsafe_b64encode(data).rstrip(b'=') + + +def urlsafe_b64decode(data): + """urlsafe_b64decode without padding""" + pad = b'=' * (4 - (len(data) & 3)) + return base64.urlsafe_b64decode(data + pad) + + +def as_unicode(s): + if isinstance(s, bytes): + return s.decode('utf-8') + return s + + +def as_bytes(s): + if isinstance(s, text_type): + return s.encode('utf-8') + return s diff --git a/pipenv/vendor/wheel/vendored/__init__.py b/pipenv/vendor/wheel/vendored/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pipenv/vendor/wheel/vendored/packaging/__init__.py b/pipenv/vendor/wheel/vendored/packaging/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pipenv/vendor/wheel/vendored/packaging/_typing.py b/pipenv/vendor/wheel/vendored/packaging/_typing.py new file mode 100644 index 00000000..480a196c --- /dev/null +++ b/pipenv/vendor/wheel/vendored/packaging/_typing.py @@ -0,0 +1,48 @@ +"""For neatly implementing static typing in packaging. + +`mypy` - the static type analysis tool we use - uses the `typing` module, which +provides core functionality fundamental to mypy's functioning. + +Generally, `typing` would be imported at runtime and used in that fashion - +it acts as a no-op at runtime and does not have any run-time overhead by +design. + +As it turns out, `typing` is not vendorable - it uses separate sources for +Python 2/Python 3. Thus, this codebase can not expect it to be present. +To work around this, mypy allows the typing import to be behind a False-y +optional to prevent it from running at runtime and type-comments can be used +to remove the need for the types to be accessible directly during runtime. + +This module provides the False-y guard in a nicely named fashion so that a +curious maintainer can reach here to read this. + +In packaging, all static-typing related imports should be guarded as follows: + + from pipenv.vendor.packaging._typing import TYPE_CHECKING + + if TYPE_CHECKING: + from typing import ... + +Ref: https://github.com/python/mypy/issues/3216 +""" + +__all__ = ["TYPE_CHECKING", "cast"] + +# The TYPE_CHECKING constant defined by the typing module is False at runtime +# but True while type checking. +if False: # pragma: no cover + from typing import TYPE_CHECKING +else: + TYPE_CHECKING = False + +# typing's cast syntax requires calling typing.cast at runtime, but we don't +# want to import typing at runtime. Here, we inform the type checkers that +# we're importing `typing.cast` as `cast` and re-implement typing.cast's +# runtime behavior in a block that is ignored by type checkers. +if TYPE_CHECKING: # pragma: no cover + # not executed at runtime + from typing import cast +else: + # executed at runtime + def cast(type_, value): # noqa + return value diff --git a/pipenv/vendor/wheel/vendored/packaging/tags.py b/pipenv/vendor/wheel/vendored/packaging/tags.py new file mode 100644 index 00000000..3306c690 --- /dev/null +++ b/pipenv/vendor/wheel/vendored/packaging/tags.py @@ -0,0 +1,852 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import + +import distutils.util + +try: + from importlib.machinery import EXTENSION_SUFFIXES +except ImportError: # pragma: no cover + import imp + + EXTENSION_SUFFIXES = [x[0] for x in imp.get_suffixes()] + del imp +import collections +import logging +import os +import platform +import re +import struct +import sys +import sysconfig +import warnings + +from ._typing import TYPE_CHECKING, cast + +if TYPE_CHECKING: # pragma: no cover + from typing import ( + Dict, + FrozenSet, + IO, + Iterable, + Iterator, + List, + Optional, + Sequence, + Tuple, + Union, + ) + + PythonVersion = Sequence[int] + MacVersion = Tuple[int, int] + GlibcVersion = Tuple[int, int] + + +logger = logging.getLogger(__name__) + +INTERPRETER_SHORT_NAMES = { + "python": "py", # Generic. + "cpython": "cp", + "pypy": "pp", + "ironpython": "ip", + "jython": "jy", +} # type: Dict[str, str] + + +_32_BIT_INTERPRETER = sys.maxsize <= 2 ** 32 + + +_LEGACY_MANYLINUX_MAP = { + # CentOS 7 w/ glibc 2.17 (PEP 599) + (2, 17): "manylinux2014", + # CentOS 6 w/ glibc 2.12 (PEP 571) + (2, 12): "manylinux2010", + # CentOS 5 w/ glibc 2.5 (PEP 513) + (2, 5): "manylinux1", +} + +# If glibc ever changes its major version, we need to know what the last +# minor version was, so we can build the complete list of all versions. +# For now, guess what the highest minor version might be, assume it will +# be 50 for testing. Once this actually happens, update the dictionary +# with the actual value. +_LAST_GLIBC_MINOR = collections.defaultdict(lambda: 50) # type: Dict[int, int] +glibcVersion = collections.namedtuple("Version", ["major", "minor"]) + + +class Tag(object): + """ + A representation of the tag triple for a wheel. + + Instances are considered immutable and thus are hashable. Equality checking + is also supported. + """ + + __slots__ = ["_interpreter", "_abi", "_platform", "_hash"] + + def __init__(self, interpreter, abi, platform): + # type: (str, str, str) -> None + self._interpreter = interpreter.lower() + self._abi = abi.lower() + self._platform = platform.lower() + # The __hash__ of every single element in a Set[Tag] will be evaluated each time + # that a set calls its `.disjoint()` method, which may be called hundreds of + # times when scanning a page of links for packages with tags matching that + # Set[Tag]. Pre-computing the value here produces significant speedups for + # downstream consumers. + self._hash = hash((self._interpreter, self._abi, self._platform)) + + @property + def interpreter(self): + # type: () -> str + return self._interpreter + + @property + def abi(self): + # type: () -> str + return self._abi + + @property + def platform(self): + # type: () -> str + return self._platform + + def __eq__(self, other): + # type: (object) -> bool + if not isinstance(other, Tag): + return NotImplemented + + return ( + (self.platform == other.platform) + and (self.abi == other.abi) + and (self.interpreter == other.interpreter) + ) + + def __hash__(self): + # type: () -> int + return self._hash + + def __str__(self): + # type: () -> str + return "{}-{}-{}".format(self._interpreter, self._abi, self._platform) + + def __repr__(self): + # type: () -> str + return "<{self} @ {self_id}>".format(self=self, self_id=id(self)) + + +def parse_tag(tag): + # type: (str) -> FrozenSet[Tag] + """ + Parses the provided tag (e.g. `py3-none-any`) into a frozenset of Tag instances. + + Returning a set is required due to the possibility that the tag is a + compressed tag set. + """ + tags = set() + interpreters, abis, platforms = tag.split("-") + for interpreter in interpreters.split("."): + for abi in abis.split("."): + for platform_ in platforms.split("."): + tags.add(Tag(interpreter, abi, platform_)) + return frozenset(tags) + + +def _warn_keyword_parameter(func_name, kwargs): + # type: (str, Dict[str, bool]) -> bool + """ + Backwards-compatibility with Python 2.7 to allow treating 'warn' as keyword-only. + """ + if not kwargs: + return False + elif len(kwargs) > 1 or "warn" not in kwargs: + kwargs.pop("warn", None) + arg = next(iter(kwargs.keys())) + raise TypeError( + "{}() got an unexpected keyword argument {!r}".format(func_name, arg) + ) + return kwargs["warn"] + + +def _get_config_var(name, warn=False): + # type: (str, bool) -> Union[int, str, None] + value = sysconfig.get_config_var(name) + if value is None and warn: + logger.debug( + "Config variable '%s' is unset, Python ABI tag may be incorrect", name + ) + return value + + +def _normalize_string(string): + # type: (str) -> str + return string.replace(".", "_").replace("-", "_") + + +def _abi3_applies(python_version): + # type: (PythonVersion) -> bool + """ + Determine if the Python version supports abi3. + + PEP 384 was first implemented in Python 3.2. + """ + return len(python_version) > 1 and tuple(python_version) >= (3, 2) + + +def _cpython_abis(py_version, warn=False): + # type: (PythonVersion, bool) -> List[str] + py_version = tuple(py_version) # To allow for version comparison. + abis = [] + version = _version_nodot(py_version[:2]) + debug = pymalloc = ucs4 = "" + with_debug = _get_config_var("Py_DEBUG", warn) + has_refcount = hasattr(sys, "gettotalrefcount") + # Windows doesn't set Py_DEBUG, so checking for support of debug-compiled + # extension modules is the best option. + # https://github.com/pypa/pip/issues/3383#issuecomment-173267692 + has_ext = "_d.pyd" in EXTENSION_SUFFIXES + if with_debug or (with_debug is None and (has_refcount or has_ext)): + debug = "d" + if py_version < (3, 8): + with_pymalloc = _get_config_var("WITH_PYMALLOC", warn) + if with_pymalloc or with_pymalloc is None: + pymalloc = "m" + if py_version < (3, 3): + unicode_size = _get_config_var("Py_UNICODE_SIZE", warn) + if unicode_size == 4 or ( + unicode_size is None and sys.maxunicode == 0x10FFFF + ): + ucs4 = "u" + elif debug: + # Debug builds can also load "normal" extension modules. + # We can also assume no UCS-4 or pymalloc requirement. + abis.append("cp{version}".format(version=version)) + abis.insert( + 0, + "cp{version}{debug}{pymalloc}{ucs4}".format( + version=version, debug=debug, pymalloc=pymalloc, ucs4=ucs4 + ), + ) + return abis + + +def cpython_tags( + python_version=None, # type: Optional[PythonVersion] + abis=None, # type: Optional[Iterable[str]] + platforms=None, # type: Optional[Iterable[str]] + **kwargs # type: bool +): + # type: (...) -> Iterator[Tag] + """ + Yields the tags for a CPython interpreter. + + The tags consist of: + - cp<python_version>-<abi>-<platform> + - cp<python_version>-abi3-<platform> + - cp<python_version>-none-<platform> + - cp<less than python_version>-abi3-<platform> # Older Python versions down to 3.2. + + If python_version only specifies a major version then user-provided ABIs and + the 'none' ABItag will be used. + + If 'abi3' or 'none' are specified in 'abis' then they will be yielded at + their normal position and not at the beginning. + """ + warn = _warn_keyword_parameter("cpython_tags", kwargs) + if not python_version: + python_version = sys.version_info[:2] + + interpreter = "cp{}".format(_version_nodot(python_version[:2])) + + if abis is None: + if len(python_version) > 1: + abis = _cpython_abis(python_version, warn) + else: + abis = [] + abis = list(abis) + # 'abi3' and 'none' are explicitly handled later. + for explicit_abi in ("abi3", "none"): + try: + abis.remove(explicit_abi) + except ValueError: + pass + + platforms = list(platforms or _platform_tags()) + for abi in abis: + for platform_ in platforms: + yield Tag(interpreter, abi, platform_) + if _abi3_applies(python_version): + for tag in (Tag(interpreter, "abi3", platform_) for platform_ in platforms): + yield tag + for tag in (Tag(interpreter, "none", platform_) for platform_ in platforms): + yield tag + + if _abi3_applies(python_version): + for minor_version in range(python_version[1] - 1, 1, -1): + for platform_ in platforms: + interpreter = "cp{version}".format( + version=_version_nodot((python_version[0], minor_version)) + ) + yield Tag(interpreter, "abi3", platform_) + + +def _generic_abi(): + # type: () -> Iterator[str] + abi = sysconfig.get_config_var("SOABI") + if abi: + yield _normalize_string(abi) + + +def generic_tags( + interpreter=None, # type: Optional[str] + abis=None, # type: Optional[Iterable[str]] + platforms=None, # type: Optional[Iterable[str]] + **kwargs # type: bool +): + # type: (...) -> Iterator[Tag] + """ + Yields the tags for a generic interpreter. + + The tags consist of: + - <interpreter>-<abi>-<platform> + + The "none" ABI will be added if it was not explicitly provided. + """ + warn = _warn_keyword_parameter("generic_tags", kwargs) + if not interpreter: + interp_name = interpreter_name() + interp_version = interpreter_version(warn=warn) + interpreter = "".join([interp_name, interp_version]) + if abis is None: + abis = _generic_abi() + platforms = list(platforms or _platform_tags()) + abis = list(abis) + if "none" not in abis: + abis.append("none") + for abi in abis: + for platform_ in platforms: + yield Tag(interpreter, abi, platform_) + + +def _py_interpreter_range(py_version): + # type: (PythonVersion) -> Iterator[str] + """ + Yields Python versions in descending order. + + After the latest version, the major-only version will be yielded, and then + all previous versions of that major version. + """ + if len(py_version) > 1: + yield "py{version}".format(version=_version_nodot(py_version[:2])) + yield "py{major}".format(major=py_version[0]) + if len(py_version) > 1: + for minor in range(py_version[1] - 1, -1, -1): + yield "py{version}".format(version=_version_nodot((py_version[0], minor))) + + +def compatible_tags( + python_version=None, # type: Optional[PythonVersion] + interpreter=None, # type: Optional[str] + platforms=None, # type: Optional[Iterable[str]] +): + # type: (...) -> Iterator[Tag] + """ + Yields the sequence of tags that are compatible with a specific version of Python. + + The tags consist of: + - py*-none-<platform> + - <interpreter>-none-any # ... if `interpreter` is provided. + - py*-none-any + """ + if not python_version: + python_version = sys.version_info[:2] + platforms = list(platforms or _platform_tags()) + for version in _py_interpreter_range(python_version): + for platform_ in platforms: + yield Tag(version, "none", platform_) + if interpreter: + yield Tag(interpreter, "none", "any") + for version in _py_interpreter_range(python_version): + yield Tag(version, "none", "any") + + +def _mac_arch(arch, is_32bit=_32_BIT_INTERPRETER): + # type: (str, bool) -> str + if not is_32bit: + return arch + + if arch.startswith("ppc"): + return "ppc" + + return "i386" + + +def _mac_binary_formats(version, cpu_arch): + # type: (MacVersion, str) -> List[str] + formats = [cpu_arch] + if cpu_arch == "x86_64": + if version < (10, 4): + return [] + formats.extend(["intel", "fat64", "fat32"]) + + elif cpu_arch == "i386": + if version < (10, 4): + return [] + formats.extend(["intel", "fat32", "fat"]) + + elif cpu_arch == "ppc64": + # TODO: Need to care about 32-bit PPC for ppc64 through 10.2? + if version > (10, 5) or version < (10, 4): + return [] + formats.append("fat64") + + elif cpu_arch == "ppc": + if version > (10, 6): + return [] + formats.extend(["fat32", "fat"]) + + if cpu_arch in {"arm64", "x86_64"}: + formats.append("universal2") + + if cpu_arch in {"x86_64", "i386", "ppc64", "ppc", "intel"}: + formats.append("universal") + + return formats + + +def mac_platforms(version=None, arch=None): + # type: (Optional[MacVersion], Optional[str]) -> Iterator[str] + """ + Yields the platform tags for a macOS system. + + The `version` parameter is a two-item tuple specifying the macOS version to + generate platform tags for. The `arch` parameter is the CPU architecture to + generate platform tags for. Both parameters default to the appropriate value + for the current system. + """ + version_str, _, cpu_arch = platform.mac_ver() # type: ignore + if version is None: + version = cast("MacVersion", tuple(map(int, version_str.split(".")[:2]))) + else: + version = version + if arch is None: + arch = _mac_arch(cpu_arch) + else: + arch = arch + + if (10, 0) <= version and version < (11, 0): + # Prior to Mac OS 11, each yearly release of Mac OS bumped the + # "minor" version number. The major version was always 10. + for minor_version in range(version[1], -1, -1): + compat_version = 10, minor_version + binary_formats = _mac_binary_formats(compat_version, arch) + for binary_format in binary_formats: + yield "macosx_{major}_{minor}_{binary_format}".format( + major=10, minor=minor_version, binary_format=binary_format + ) + + if version >= (11, 0): + # Starting with Mac OS 11, each yearly release bumps the major version + # number. The minor versions are now the midyear updates. + for major_version in range(version[0], 10, -1): + compat_version = major_version, 0 + binary_formats = _mac_binary_formats(compat_version, arch) + for binary_format in binary_formats: + yield "macosx_{major}_{minor}_{binary_format}".format( + major=major_version, minor=0, binary_format=binary_format + ) + + if version >= (11, 0) and arch == "x86_64": + # Mac OS 11 on x86_64 is compatible with binaries from previous releases. + # Arm64 support was introduced in 11.0, so no Arm binaries from previous + # releases exist. + for minor_version in range(16, 3, -1): + compat_version = 10, minor_version + binary_formats = _mac_binary_formats(compat_version, arch) + for binary_format in binary_formats: + yield "macosx_{major}_{minor}_{binary_format}".format( + major=compat_version[0], + minor=compat_version[1], + binary_format=binary_format, + ) + + +# From PEP 513, PEP 600 +def _is_manylinux_compatible(name, arch, glibc_version): + # type: (str, str, GlibcVersion) -> bool + sys_glibc = _get_glibc_version() + if sys_glibc < glibc_version: + return False + # Check for presence of _manylinux module. + try: + import _manylinux # noqa + except ImportError: + pass + else: + if hasattr(_manylinux, "manylinux_compatible"): + result = _manylinux.manylinux_compatible( + glibc_version[0], glibc_version[1], arch + ) + if result is not None: + return bool(result) + else: + if glibc_version == (2, 5): + if hasattr(_manylinux, "manylinux1_compatible"): + return bool(_manylinux.manylinux1_compatible) + if glibc_version == (2, 12): + if hasattr(_manylinux, "manylinux2010_compatible"): + return bool(_manylinux.manylinux2010_compatible) + if glibc_version == (2, 17): + if hasattr(_manylinux, "manylinux2014_compatible"): + return bool(_manylinux.manylinux2014_compatible) + return True + + +def _glibc_version_string(): + # type: () -> Optional[str] + # Returns glibc version string, or None if not using glibc. + return _glibc_version_string_confstr() or _glibc_version_string_ctypes() + + +def _glibc_version_string_confstr(): + # type: () -> Optional[str] + """ + Primary implementation of glibc_version_string using os.confstr. + """ + # os.confstr is quite a bit faster than ctypes.DLL. It's also less likely + # to be broken or missing. This strategy is used in the standard library + # platform module. + # https://github.com/python/cpython/blob/fcf1d003bf4f0100c9d0921ff3d70e1127ca1b71/Lib/platform.py#L175-L183 + try: + # os.confstr("CS_GNU_LIBC_VERSION") returns a string like "glibc 2.17". + version_string = os.confstr( # type: ignore[attr-defined] # noqa: F821 + "CS_GNU_LIBC_VERSION" + ) + assert version_string is not None + _, version = version_string.split() # type: Tuple[str, str] + except (AssertionError, AttributeError, OSError, ValueError): + # os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)... + return None + return version + + +def _glibc_version_string_ctypes(): + # type: () -> Optional[str] + """ + Fallback implementation of glibc_version_string using ctypes. + """ + try: + import ctypes + except ImportError: + return None + + # ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen + # manpage says, "If filename is NULL, then the returned handle is for the + # main program". This way we can let the linker do the work to figure out + # which libc our process is actually using. + # + # We must also handle the special case where the executable is not a + # dynamically linked executable. This can occur when using musl libc, + # for example. In this situation, dlopen() will error, leading to an + # OSError. Interestingly, at least in the case of musl, there is no + # errno set on the OSError. The single string argument used to construct + # OSError comes from libc itself and is therefore not portable to + # hard code here. In any case, failure to call dlopen() means we + # can proceed, so we bail on our attempt. + try: + # Note: typeshed is wrong here so we are ignoring this line. + process_namespace = ctypes.CDLL(None) # type: ignore + except OSError: + return None + + try: + gnu_get_libc_version = process_namespace.gnu_get_libc_version + except AttributeError: + # Symbol doesn't exist -> therefore, we are not linked to + # glibc. + return None + + # Call gnu_get_libc_version, which returns a string like "2.5" + gnu_get_libc_version.restype = ctypes.c_char_p + version_str = gnu_get_libc_version() # type: str + # py2 / py3 compatibility: + if not isinstance(version_str, str): + version_str = version_str.decode("ascii") + + return version_str + + +def _parse_glibc_version(version_str): + # type: (str) -> Tuple[int, int] + # Parse glibc version. + # + # We use a regexp instead of str.split because we want to discard any + # random junk that might come after the minor version -- this might happen + # in patched/forked versions of glibc (e.g. Linaro's version of glibc + # uses version strings like "2.20-2014.11"). See gh-3588. + m = re.match(r"(?P<major>[0-9]+)\.(?P<minor>[0-9]+)", version_str) + if not m: + warnings.warn( + "Expected glibc version with 2 components major.minor," + " got: %s" % version_str, + RuntimeWarning, + ) + return -1, -1 + return (int(m.group("major")), int(m.group("minor"))) + + +_glibc_version = [] # type: List[Tuple[int, int]] + + +def _get_glibc_version(): + # type: () -> Tuple[int, int] + if _glibc_version: + return _glibc_version[0] + version_str = _glibc_version_string() + if version_str is None: + _glibc_version.append((-1, -1)) + else: + _glibc_version.append(_parse_glibc_version(version_str)) + return _glibc_version[0] + + +# Python does not provide platform information at sufficient granularity to +# identify the architecture of the running executable in some cases, so we +# determine it dynamically by reading the information from the running +# process. This only applies on Linux, which uses the ELF format. +class _ELFFileHeader(object): + # https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header + class _InvalidELFFileHeader(ValueError): + """ + An invalid ELF file header was found. + """ + + ELF_MAGIC_NUMBER = 0x7F454C46 + ELFCLASS32 = 1 + ELFCLASS64 = 2 + ELFDATA2LSB = 1 + ELFDATA2MSB = 2 + EM_386 = 3 + EM_S390 = 22 + EM_ARM = 40 + EM_X86_64 = 62 + EF_ARM_ABIMASK = 0xFF000000 + EF_ARM_ABI_VER5 = 0x05000000 + EF_ARM_ABI_FLOAT_HARD = 0x00000400 + + def __init__(self, file): + # type: (IO[bytes]) -> None + def unpack(fmt): + # type: (str) -> int + try: + (result,) = struct.unpack( + fmt, file.read(struct.calcsize(fmt)) + ) # type: (int, ) + except struct.error: + raise _ELFFileHeader._InvalidELFFileHeader() + return result + + self.e_ident_magic = unpack(">I") + if self.e_ident_magic != self.ELF_MAGIC_NUMBER: + raise _ELFFileHeader._InvalidELFFileHeader() + self.e_ident_class = unpack("B") + if self.e_ident_class not in {self.ELFCLASS32, self.ELFCLASS64}: + raise _ELFFileHeader._InvalidELFFileHeader() + self.e_ident_data = unpack("B") + if self.e_ident_data not in {self.ELFDATA2LSB, self.ELFDATA2MSB}: + raise _ELFFileHeader._InvalidELFFileHeader() + self.e_ident_version = unpack("B") + self.e_ident_osabi = unpack("B") + self.e_ident_abiversion = unpack("B") + self.e_ident_pad = file.read(7) + format_h = "<H" if self.e_ident_data == self.ELFDATA2LSB else ">H" + format_i = "<I" if self.e_ident_data == self.ELFDATA2LSB else ">I" + format_q = "<Q" if self.e_ident_data == self.ELFDATA2LSB else ">Q" + format_p = format_i if self.e_ident_class == self.ELFCLASS32 else format_q + self.e_type = unpack(format_h) + self.e_machine = unpack(format_h) + self.e_version = unpack(format_i) + self.e_entry = unpack(format_p) + self.e_phoff = unpack(format_p) + self.e_shoff = unpack(format_p) + self.e_flags = unpack(format_i) + self.e_ehsize = unpack(format_h) + self.e_phentsize = unpack(format_h) + self.e_phnum = unpack(format_h) + self.e_shentsize = unpack(format_h) + self.e_shnum = unpack(format_h) + self.e_shstrndx = unpack(format_h) + + +def _get_elf_header(): + # type: () -> Optional[_ELFFileHeader] + try: + with open(sys.executable, "rb") as f: + elf_header = _ELFFileHeader(f) + except (IOError, OSError, TypeError, _ELFFileHeader._InvalidELFFileHeader): + return None + return elf_header + + +def _is_linux_armhf(): + # type: () -> bool + # hard-float ABI can be detected from the ELF header of the running + # process + # https://static.docs.arm.com/ihi0044/g/aaelf32.pdf + elf_header = _get_elf_header() + if elf_header is None: + return False + result = elf_header.e_ident_class == elf_header.ELFCLASS32 + result &= elf_header.e_ident_data == elf_header.ELFDATA2LSB + result &= elf_header.e_machine == elf_header.EM_ARM + result &= ( + elf_header.e_flags & elf_header.EF_ARM_ABIMASK + ) == elf_header.EF_ARM_ABI_VER5 + result &= ( + elf_header.e_flags & elf_header.EF_ARM_ABI_FLOAT_HARD + ) == elf_header.EF_ARM_ABI_FLOAT_HARD + return result + + +def _is_linux_i686(): + # type: () -> bool + elf_header = _get_elf_header() + if elf_header is None: + return False + result = elf_header.e_ident_class == elf_header.ELFCLASS32 + result &= elf_header.e_ident_data == elf_header.ELFDATA2LSB + result &= elf_header.e_machine == elf_header.EM_386 + return result + + +def _have_compatible_manylinux_abi(arch): + # type: (str) -> bool + if arch == "armv7l": + return _is_linux_armhf() + if arch == "i686": + return _is_linux_i686() + return arch in {"x86_64", "aarch64", "ppc64", "ppc64le", "s390x"} + + +def _manylinux_tags(linux, arch): + # type: (str, str) -> Iterator[str] + # Oldest glibc to be supported regardless of architecture is (2, 17). + too_old_glibc2 = glibcVersion(2, 16) + if arch in {"x86_64", "i686"}: + # On x86/i686 also oldest glibc to be supported is (2, 5). + too_old_glibc2 = glibcVersion(2, 4) + current_glibc = glibcVersion(*_get_glibc_version()) + glibc_max_list = [current_glibc] + # We can assume compatibility across glibc major versions. + # https://sourceware.org/bugzilla/show_bug.cgi?id=24636 + # + # Build a list of maximum glibc versions so that we can + # output the canonical list of all glibc from current_glibc + # down to too_old_glibc2, including all intermediary versions. + for glibc_major in range(current_glibc.major - 1, 1, -1): + glibc_max_list.append(glibcVersion(glibc_major, _LAST_GLIBC_MINOR[glibc_major])) + for glibc_max in glibc_max_list: + if glibc_max.major == too_old_glibc2.major: + min_minor = too_old_glibc2.minor + else: + # For other glibc major versions oldest supported is (x, 0). + min_minor = -1 + for glibc_minor in range(glibc_max.minor, min_minor, -1): + glibc_version = (glibc_max.major, glibc_minor) + tag = "manylinux_{}_{}".format(*glibc_version) + if _is_manylinux_compatible(tag, arch, glibc_version): + yield linux.replace("linux", tag) + # Handle the legacy manylinux1, manylinux2010, manylinux2014 tags. + if glibc_version in _LEGACY_MANYLINUX_MAP: + legacy_tag = _LEGACY_MANYLINUX_MAP[glibc_version] + if _is_manylinux_compatible(legacy_tag, arch, glibc_version): + yield linux.replace("linux", legacy_tag) + + +def _linux_platforms(is_32bit=_32_BIT_INTERPRETER): + # type: (bool) -> Iterator[str] + linux = _normalize_string(distutils.util.get_platform()) + if is_32bit: + if linux == "linux_x86_64": + linux = "linux_i686" + elif linux == "linux_aarch64": + linux = "linux_armv7l" + _, arch = linux.split("_", 1) + if _have_compatible_manylinux_abi(arch): + for tag in _manylinux_tags(linux, arch): + yield tag + yield linux + + +def _generic_platforms(): + # type: () -> Iterator[str] + yield _normalize_string(distutils.util.get_platform()) + + +def _platform_tags(): + # type: () -> Iterator[str] + """ + Provides the platform tags for this installation. + """ + if platform.system() == "Darwin": + return mac_platforms() + elif platform.system() == "Linux": + return _linux_platforms() + else: + return _generic_platforms() + + +def interpreter_name(): + # type: () -> str + """ + Returns the name of the running interpreter. + """ + try: + name = sys.implementation.name # type: ignore + except AttributeError: # pragma: no cover + # Python 2.7 compatibility. + name = platform.python_implementation().lower() + return INTERPRETER_SHORT_NAMES.get(name) or name + + +def interpreter_version(**kwargs): + # type: (bool) -> str + """ + Returns the version of the running interpreter. + """ + warn = _warn_keyword_parameter("interpreter_version", kwargs) + version = _get_config_var("py_version_nodot", warn=warn) + if version: + version = str(version) + else: + version = _version_nodot(sys.version_info[:2]) + return version + + +def _version_nodot(version): + # type: (PythonVersion) -> str + return "".join(map(str, version)) + + +def sys_tags(**kwargs): + # type: (bool) -> Iterator[Tag] + """ + Returns the sequence of tag triples for the running interpreter. + + The order of the sequence corresponds to priority order for the + interpreter, from most to least important. + """ + warn = _warn_keyword_parameter("sys_tags", kwargs) + + interp_name = interpreter_name() + if interp_name == "cp": + for tag in cpython_tags(warn=warn): + yield tag + else: + for tag in generic_tags(): + yield tag + + for tag in compatible_tags(): + yield tag diff --git a/pipenv/vendor/wheel/wheelfile.py b/pipenv/vendor/wheel/wheelfile.py new file mode 100644 index 00000000..d29c0823 --- /dev/null +++ b/pipenv/vendor/wheel/wheelfile.py @@ -0,0 +1,169 @@ +from __future__ import print_function + +import csv +import hashlib +import os.path +import re +import stat +import time +from collections import OrderedDict +from distutils import log as logger +from zipfile import ZIP_DEFLATED, ZipInfo, ZipFile + +from pipenv.vendor.wheel.cli import WheelError +from pipenv.vendor.wheel.util import urlsafe_b64decode, as_unicode, native, urlsafe_b64encode, as_bytes, StringIO + +# Non-greedy matching of an optional build number may be too clever (more +# invalid wheel filenames will match). Separate regex for .dist-info? +WHEEL_INFO_RE = re.compile( + r"""^(?P<namever>(?P<name>.+?)-(?P<ver>.+?))(-(?P<build>\d[^-]*))? + -(?P<pyver>.+?)-(?P<abi>.+?)-(?P<plat>.+?)\.whl$""", + re.VERBOSE) + + +def get_zipinfo_datetime(timestamp=None): + # Some applications need reproducible .whl files, but they can't do this without forcing + # the timestamp of the individual ZipInfo objects. See issue #143. + timestamp = int(os.environ.get('SOURCE_DATE_EPOCH', timestamp or time.time())) + return time.gmtime(timestamp)[0:6] + + +class WheelFile(ZipFile): + """A ZipFile derivative class that also reads SHA-256 hashes from + .dist-info/RECORD and checks any read files against those. + """ + + _default_algorithm = hashlib.sha256 + + def __init__(self, file, mode='r', compression=ZIP_DEFLATED): + basename = os.path.basename(file) + self.parsed_filename = WHEEL_INFO_RE.match(basename) + if not basename.endswith('.whl') or self.parsed_filename is None: + raise WheelError("Bad wheel filename {!r}".format(basename)) + + ZipFile.__init__(self, file, mode, compression=compression, allowZip64=True) + + self.dist_info_path = '{}.dist-info'.format(self.parsed_filename.group('namever')) + self.record_path = self.dist_info_path + '/RECORD' + self._file_hashes = OrderedDict() + self._file_sizes = {} + if mode == 'r': + # Ignore RECORD and any embedded wheel signatures + self._file_hashes[self.record_path] = None, None + self._file_hashes[self.record_path + '.jws'] = None, None + self._file_hashes[self.record_path + '.p7s'] = None, None + + # Fill in the expected hashes by reading them from RECORD + try: + record = self.open(self.record_path) + except KeyError: + raise WheelError('Missing {} file'.format(self.record_path)) + + with record: + for line in record: + line = line.decode('utf-8') + path, hash_sum, size = line.rsplit(u',', 2) + if hash_sum: + algorithm, hash_sum = hash_sum.split(u'=') + try: + hashlib.new(algorithm) + except ValueError: + raise WheelError('Unsupported hash algorithm: {}'.format(algorithm)) + + if algorithm.lower() in {'md5', 'sha1'}: + raise WheelError( + 'Weak hash algorithm ({}) is not permitted by PEP 427' + .format(algorithm)) + + self._file_hashes[path] = ( + algorithm, urlsafe_b64decode(hash_sum.encode('ascii'))) + + def open(self, name_or_info, mode="r", pwd=None): + def _update_crc(newdata, eof=None): + if eof is None: + eof = ef._eof + update_crc_orig(newdata) + else: # Python 2 + update_crc_orig(newdata, eof) + + running_hash.update(newdata) + if eof and running_hash.digest() != expected_hash: + raise WheelError("Hash mismatch for file '{}'".format(native(ef_name))) + + ef_name = as_unicode(name_or_info.filename if isinstance(name_or_info, ZipInfo) + else name_or_info) + if mode == 'r' and not ef_name.endswith('/') and ef_name not in self._file_hashes: + raise WheelError("No hash found for file '{}'".format(native(ef_name))) + + ef = ZipFile.open(self, name_or_info, mode, pwd) + if mode == 'r' and not ef_name.endswith('/'): + algorithm, expected_hash = self._file_hashes[ef_name] + if expected_hash is not None: + # Monkey patch the _update_crc method to also check for the hash from RECORD + running_hash = hashlib.new(algorithm) + update_crc_orig, ef._update_crc = ef._update_crc, _update_crc + + return ef + + def write_files(self, base_dir): + logger.info("creating '%s' and adding '%s' to it", self.filename, base_dir) + deferred = [] + for root, dirnames, filenames in os.walk(base_dir): + # Sort the directory names so that `os.walk` will walk them in a + # defined order on the next iteration. + dirnames.sort() + for name in sorted(filenames): + path = os.path.normpath(os.path.join(root, name)) + if os.path.isfile(path): + arcname = os.path.relpath(path, base_dir).replace(os.path.sep, '/') + if arcname == self.record_path: + pass + elif root.endswith('.dist-info'): + deferred.append((path, arcname)) + else: + self.write(path, arcname) + + deferred.sort() + for path, arcname in deferred: + self.write(path, arcname) + + def write(self, filename, arcname=None, compress_type=None): + with open(filename, 'rb') as f: + st = os.fstat(f.fileno()) + data = f.read() + + zinfo = ZipInfo(arcname or filename, date_time=get_zipinfo_datetime(st.st_mtime)) + zinfo.external_attr = (stat.S_IMODE(st.st_mode) | stat.S_IFMT(st.st_mode)) << 16 + zinfo.compress_type = compress_type or self.compression + self.writestr(zinfo, data, compress_type) + + def writestr(self, zinfo_or_arcname, bytes, compress_type=None): + ZipFile.writestr(self, zinfo_or_arcname, bytes, compress_type) + fname = (zinfo_or_arcname.filename if isinstance(zinfo_or_arcname, ZipInfo) + else zinfo_or_arcname) + logger.info("adding '%s'", fname) + if fname != self.record_path: + hash_ = self._default_algorithm(bytes) + self._file_hashes[fname] = hash_.name, native(urlsafe_b64encode(hash_.digest())) + self._file_sizes[fname] = len(bytes) + + def close(self): + # Write RECORD + if self.fp is not None and self.mode == 'w' and self._file_hashes: + data = StringIO() + writer = csv.writer(data, delimiter=',', quotechar='"', lineterminator='\n') + writer.writerows(( + ( + fname, + algorithm + "=" + hash_, + self._file_sizes[fname] + ) + for fname, (algorithm, hash_) in self._file_hashes.items() + )) + writer.writerow((format(self.record_path), "", "")) + zinfo = ZipInfo(native(self.record_path), date_time=get_zipinfo_datetime()) + zinfo.compress_type = self.compression + zinfo.external_attr = 0o664 << 16 + self.writestr(zinfo, as_bytes(data.getvalue())) + + ZipFile.close(self) diff --git a/pipenv/vendor/yarg/client.py b/pipenv/vendor/yarg/client.py index 829b40b7..51e7955e 100644 --- a/pipenv/vendor/yarg/client.py +++ b/pipenv/vendor/yarg/client.py @@ -23,7 +23,7 @@ # SOFTWARE. -import requests +import pipenv.vendor.requests as requests from .exceptions import HTTPError from .package import json2package diff --git a/pipenv/vendor/yarg/exceptions.py b/pipenv/vendor/yarg/exceptions.py index adfdb838..3ee517fc 100644 --- a/pipenv/vendor/yarg/exceptions.py +++ b/pipenv/vendor/yarg/exceptions.py @@ -23,7 +23,7 @@ # SOFTWARE. -from requests.exceptions import HTTPError as RHTTPError +from pipenv.vendor.requests.exceptions import HTTPError as RHTTPError class YargException(Exception): diff --git a/pipenv/vendor/yarg/parse.py b/pipenv/vendor/yarg/parse.py index 60cd90a8..28ccd6cf 100644 --- a/pipenv/vendor/yarg/parse.py +++ b/pipenv/vendor/yarg/parse.py @@ -25,7 +25,7 @@ from datetime import datetime import xml.etree.ElementTree -import requests +import pipenv.vendor.requests as requests from .exceptions import HTTPError diff --git a/pipenv/vendor/yaspin/LICENSE b/pipenv/vendor/yaspin/LICENSE index 2458104e..c4902dbf 100644 --- a/pipenv/vendor/yaspin/LICENSE +++ b/pipenv/vendor/yaspin/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018 Pavlo Dmytrenko +Copyright (c) 2021 Pavlo Dmytrenko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pipenv/vendor/yaspin/__init__.py b/pipenv/vendor/yaspin/__init__.py index 57853a13..e3ea0da3 100644 --- a/pipenv/vendor/yaspin/__init__.py +++ b/pipenv/vendor/yaspin/__init__.py @@ -1,8 +1,5 @@ -# -*- coding: utf-8 -*- - -from __future__ import absolute_import - -from .__version__ import __version__ # noqa +# :copyright: (c) 2021 by Pavlo Dmytrenko. +# :license: MIT, see LICENSE for more details. from .api import kbi_safe_yaspin, yaspin from .base_spinner import Spinner diff --git a/pipenv/vendor/yaspin/__version__.py b/pipenv/vendor/yaspin/__version__.py deleted file mode 100644 index 23f00709..00000000 --- a/pipenv/vendor/yaspin/__version__.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.14.3" diff --git a/pipenv/vendor/yaspin/api.py b/pipenv/vendor/yaspin/api.py index f59ce002..6fb56c89 100644 --- a/pipenv/vendor/yaspin/api.py +++ b/pipenv/vendor/yaspin/api.py @@ -1,13 +1,11 @@ -# -*- coding: utf-8 -*- +# :copyright: (c) 2021 by Pavlo Dmytrenko. +# :license: MIT, see LICENSE for more details. """ yaspin.api ~~~~~~~~~~ This module implements the Yaspin API. - -:copyright: (c) 2018 by Pavlo Dmytrenko. -:license: MIT, see LICENSE for more details. """ import signal @@ -32,6 +30,7 @@ def yaspin(*args, **kwargs): of the text string. sigmap (dict, optional): Maps POSIX signals to their respective handlers. + timer (bool, optional): Prints a timer showing the elapsed time. Returns: core.Yaspin: instance of the Yaspin class. diff --git a/pipenv/vendor/yaspin/base_spinner.py b/pipenv/vendor/yaspin/base_spinner.py index 537ff799..7fff95a4 100644 --- a/pipenv/vendor/yaspin/base_spinner.py +++ b/pipenv/vendor/yaspin/base_spinner.py @@ -1,4 +1,5 @@ -# -*- coding: utf-8 -*- +# :copyright: (c) 2021 by Pavlo Dmytrenko. +# :license: MIT, see LICENSE for more details. """ yaspin.base_spinner @@ -6,9 +7,6 @@ yaspin.base_spinner Spinner class, used to construct other spinners. """ - -from __future__ import absolute_import - from collections import namedtuple diff --git a/pipenv/vendor/yaspin/compat.py b/pipenv/vendor/yaspin/compat.py deleted file mode 100644 index 744de5a1..00000000 --- a/pipenv/vendor/yaspin/compat.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -yaspin.compat -~~~~~~~~~~~~~ - -Compatibility layer. -""" - -import sys - - -PY2 = sys.version_info[0] == 2 - - -if PY2: - builtin_str = str - bytes = str - str = unicode # noqa - basestring = basestring # noqa - - def iteritems(dct): - return dct.iteritems() - - -else: - builtin_str = str - bytes = bytes - str = str - basestring = (str, bytes) - - def iteritems(dct): - return dct.items() diff --git a/pipenv/vendor/yaspin/constants.py b/pipenv/vendor/yaspin/constants.py index b26baabe..e86e4af4 100644 --- a/pipenv/vendor/yaspin/constants.py +++ b/pipenv/vendor/yaspin/constants.py @@ -1,4 +1,5 @@ -# -*- coding: utf-8 -*- +# :copyright: (c) 2021 by Pavlo Dmytrenko. +# :license: MIT, see LICENSE for more details. """ yaspin.constants @@ -38,12 +39,15 @@ COLOR_ATTRS = COLOR_MAP.keys() # Get spinner names: # $ < yaspin/data/spinners.json | jq '. | keys' SPINNER_ATTRS = [ + "aesthetic", "arc", "arrow", "arrow2", "arrow3", "balloon", "balloon2", + "betaWave", + "bluePulse", "bounce", "bouncingBall", "bouncingBar", @@ -65,9 +69,12 @@ SPINNER_ATTRS = [ "dots6", "dots7", "dots8", + "dots8Bit", "dots9", "dqpb", "earth", + "fingerDance", + "fistBump", "flip", "grenade", "growHorizontal", @@ -77,9 +84,13 @@ SPINNER_ATTRS = [ "layer", "line", "line2", + "material", + "mindblown", "monkey", "moon", "noise", + "orangeBluePulse", + "orangePulse", "pipe", "point", "pong", @@ -88,10 +99,13 @@ SPINNER_ATTRS = [ "simpleDots", "simpleDotsScrolling", "smiley", + "soccerHeader", + "speaker", "squareCorners", "squish", "star", "star2", + "timeTravel", "toggle", "toggle10", "toggle11", diff --git a/pipenv/vendor/yaspin/core.py b/pipenv/vendor/yaspin/core.py index 12960b3b..3bb628c8 100644 --- a/pipenv/vendor/yaspin/core.py +++ b/pipenv/vendor/yaspin/core.py @@ -1,4 +1,5 @@ -# -*- coding: utf-8 -*- +# :copyright: (c) 2021 by Pavlo Dmytrenko. +# :license: MIT, see LICENSE for more details. """ yaspin.yaspin @@ -7,29 +8,28 @@ yaspin.yaspin A lightweight terminal spinner. """ -from __future__ import absolute_import - +import contextlib +import datetime import functools import itertools import signal import sys import threading import time +from typing import List, Set, Union -import colorama +from pipenv.vendor.termcolor import colored +from pipenv.vendor import colorama from pipenv.vendor.vistir import cursor -from .base_spinner import default_spinner -from .compat import PY2, basestring, builtin_str, bytes, iteritems, str -from .constants import COLOR_ATTRS, COLOR_MAP, ENCODING, SPINNER_ATTRS +from .base_spinner import Spinner, default_spinner +from .constants import COLOR_ATTRS, COLOR_MAP, SPINNER_ATTRS from .helpers import to_unicode -from .termcolor import colored - colorama.init() -class Yaspin(object): +class Yaspin: # pylint: disable=useless-object-inheritance,too-many-instance-attributes """Implements a context manager that spawns a thread to write spinner frames into a tty (stdout) during context execution. @@ -39,17 +39,8 @@ class Yaspin(object): # it sets the sys.stdout.encoding attribute to the terminal's encoding. # The print statement's handler will automatically encode unicode # arguments into bytes. - # - # In Py2 when piping or redirecting output, Python does not detect - # the desired character set of the output, it sets sys.stdout.encoding - # to None, and print will invoke the default "ascii" codec. - # - # Py3 invokes "UTF-8" codec by default. - # - # Thats why in Py2, output should be encoded manually with desired - # encoding in order to support pipes and redirects. - def __init__( + def __init__( # pylint: disable=too-many-arguments self, spinner=None, text="", @@ -59,6 +50,7 @@ class Yaspin(object): reversal=False, side="left", sigmap=None, + timer=False, ): # Spinner self._spinner = self._set_spinner(spinner) @@ -73,9 +65,12 @@ class Yaspin(object): self._color_func = self._compose_color_func() # Other - self._text = self._set_text(text) + self._text = text self._side = self._set_side(side) self._reversal = reversal + self._timer = timer + self._start_time = None + self._stop_time = None # Helper flags self._stop_spin = None @@ -83,6 +78,7 @@ class Yaspin(object): self._spin_thread = None self._last_frame = None self._stdout_lock = threading.Lock() + self._hidden_level = 0 # Signals @@ -100,10 +96,7 @@ class Yaspin(object): # Dunders # def __repr__(self): - repr_ = u"<Yaspin frames={0!s}>".format(self._frames) - if PY2: - return repr_.encode(ENCODING) - return repr_ + return "<Yaspin frames={0!s}>".format(self._frames) def __enter__(self): self.start() @@ -126,7 +119,7 @@ class Yaspin(object): def __getattr__(self, name): # CLI spinners if name in SPINNER_ATTRS: - from .spinners import Spinners + from .spinners import Spinners # pylint: disable=import-outside-toplevel sp = getattr(Spinners, name) self.spinner = sp @@ -171,7 +164,7 @@ class Yaspin(object): @text.setter def text(self, txt): - self._text = self._set_text(txt) + self._text = txt @property def color(self): @@ -219,6 +212,16 @@ class Yaspin(object): self._frames = self._set_frames(self._spinner, self._reversal) self._cycle = self._set_cycle(self._frames) + @property + def elapsed_time(self): + if self._start_time is None: + return 0 + + if self._stop_time is None: + return time.time() - self._start_time + + return self._stop_time - self._start_time + # # Public # @@ -229,12 +232,16 @@ class Yaspin(object): if sys.stdout.isatty(): self._hide_cursor() + self._start_time = time.time() + self._stop_time = None # Reset value to properly calculate subsequent spinner starts (if any) # pylint: disable=line-too-long self._stop_spin = threading.Event() self._hide_spin = threading.Event() self._spin_thread = threading.Thread(target=self._spin) self._spin_thread.start() def stop(self): + self._stop_time = time.time() + if self._dfl_sigmap: # Reset registered signal handlers to default ones self._reset_signal_handlers() @@ -266,6 +273,20 @@ class Yaspin(object): # can be rewritten to sys.stdout.flush() + @contextlib.contextmanager + def hidden(self): + """Hide the spinner within a block, can be nested""" + if self._hidden_level == 0: + self.hide() + self._hidden_level += 1 + + try: + yield + finally: + self._hidden_level -= 1 + if self._hidden_level == 0: + self.show() + def show(self): """Show the hidden spinner.""" thr_is_alive = self._spin_thread and self._spin_thread.is_alive() @@ -287,12 +308,13 @@ class Yaspin(object): sys.stdout.write("\r") self._clear_line() - _text = to_unicode(text) - if PY2: - _text = _text.encode(ENCODING) + if isinstance(text, (str, bytes)): + _text = to_unicode(text) + else: + _text = str(text) - # Ensure output is bytes for Py2 and Unicode for Py3 - assert isinstance(_text, builtin_str) + # Ensure output is Unicode + assert isinstance(_text, str) sys.stdout.write("{0}\n".format(_text)) @@ -339,24 +361,22 @@ class Yaspin(object): sys.stdout.flush() # Wait - time.sleep(self._interval) + self._stop_spin.wait(self._interval) def _compose_color_func(self): - fn = functools.partial( + return functools.partial( colored, color=self._color, on_color=self._on_color, attrs=list(self._attrs), ) - return fn def _compose_out(self, frame, mode=None): # Ensure Unicode input assert isinstance(frame, str) assert isinstance(self._text, str) - frame = frame.encode(ENCODING) if PY2 else frame - text = self._text.encode(ENCODING) if PY2 else self._text + text = self._text # Colors if self._color_func is not None: @@ -366,14 +386,18 @@ class Yaspin(object): if self._side == "right": frame, text = text, frame + if self._timer: + sec, fsec = divmod(round(100 * self.elapsed_time), 100) + text += " ({}.{:02.0f})".format(datetime.timedelta(seconds=sec), fsec) + # Mode if not mode: out = "\r{0} {1}".format(frame, text) else: out = "{0} {1}\n".format(frame, text) - # Ensure output is bytes for Py2 and Unicode for Py3 - assert isinstance(out, builtin_str) + # Ensure output is Unicode + assert isinstance(out, str) return out @@ -390,7 +414,7 @@ class Yaspin(object): except AttributeError: pass - for sig, sig_handler in iteritems(self._sigmap): + for sig, sig_handler in self._sigmap.items(): # A handler for a particular signal, once set, remains # installed until it is explicitly reset. Store default # signal handlers for subsequent reset at cleanup phase. @@ -410,17 +434,15 @@ class Yaspin(object): signal.signal(sig, sig_handler) def _reset_signal_handlers(self): - for sig, sig_handler in iteritems(self._dfl_sigmap): + for sig, sig_handler in self._dfl_sigmap.items(): signal.signal(sig, sig_handler) # # Static # @staticmethod - def _set_color(value): - # type: (str) -> str - available_values = [k for k, v in iteritems(COLOR_MAP) if v == "color"] - + def _set_color(value: str) -> str: + available_values = [k for k, v in COLOR_MAP.items() if v == "color"] if value not in available_values: raise ValueError( "'{0}': unsupported color value. Use one of the: {1}".format( @@ -430,40 +452,28 @@ class Yaspin(object): return value @staticmethod - def _set_on_color(value): - # type: (str) -> str - available_values = [ - k for k, v in iteritems(COLOR_MAP) if v == "on_color" - ] + def _set_on_color(value: str) -> str: + available_values = [k for k, v in COLOR_MAP.items() if v == "on_color"] if value not in available_values: raise ValueError( "'{0}': unsupported on_color value. " - "Use one of the: {1}".format( - value, ", ".join(available_values) - ) + "Use one of the: {1}".format(value, ", ".join(available_values)) ) return value @staticmethod - def _set_attrs(attrs): - # type: (List[str]) -> Set[str] - available_values = [k for k, v in iteritems(COLOR_MAP) if v == "attrs"] - + def _set_attrs(attrs: List[str]) -> Set[str]: + available_values = [k for k, v in COLOR_MAP.items() if v == "attrs"] for attr in attrs: if attr not in available_values: raise ValueError( "'{0}': unsupported attribute value. " - "Use one of the: {1}".format( - attr, ", ".join(available_values) - ) + "Use one of the: {1}".format(attr, ", ".join(available_values)) ) return set(attrs) @staticmethod def _set_spinner(spinner): - if not spinner: - sp = default_spinner - if hasattr(spinner, "frames") and hasattr(spinner, "interval"): if not spinner.frames or not spinner.interval: sp = default_spinner @@ -475,23 +485,20 @@ class Yaspin(object): return sp @staticmethod - def _set_side(side): - # type: (str) -> str + def _set_side(side: str) -> str: if side not in ("left", "right"): raise ValueError( - "'{0}': unsupported side value. " - "Use either 'left' or 'right'." + "'{0}': unsupported side value. " "Use either 'left' or 'right'." ) return side @staticmethod - def _set_frames(spinner, reversal): - # type: (base_spinner.Spinner, bool) -> Union[str, List] + def _set_frames(spinner: Spinner, reversal: bool) -> Union[str, List]: uframes = None # unicode frames uframes_seq = None # sequence of unicode frames - if isinstance(spinner.frames, basestring): - uframes = to_unicode(spinner.frames) if PY2 else spinner.frames + if isinstance(spinner.frames, str): + uframes = spinner.frames # TODO (pavdmyt): support any type that implements iterable if isinstance(spinner.frames, (list, tuple)): @@ -507,9 +514,7 @@ class Yaspin(object): # Empty ``spinner.frames`` is handled by ``Yaspin._set_spinner``. # This code is very unlikely to be executed. However, it's still # here to be on a safe side. - raise ValueError( - "{0!r}: no frames found in spinner".format(spinner) - ) + raise ValueError("{0!r}: no frames found in spinner".format(spinner)) # Builtin ``reversed`` returns reverse iterator, # which adds unnecessary difficulty for returning @@ -528,12 +533,6 @@ class Yaspin(object): def _set_cycle(frames): return itertools.cycle(frames) - @staticmethod - def _set_text(text): - if PY2: - return to_unicode(text) - return text - @staticmethod def _hide_cursor(): cursor.hide_cursor() @@ -544,4 +543,4 @@ class Yaspin(object): @staticmethod def _clear_line(): - sys.stdout.write(chr(27) + "[K") + sys.stdout.write("\033[K") diff --git a/pipenv/vendor/yaspin/data/spinners.json b/pipenv/vendor/yaspin/data/spinners.json index b388b2a5..89b0edba 100644 --- a/pipenv/vendor/yaspin/data/spinners.json +++ b/pipenv/vendor/yaspin/data/spinners.json @@ -274,6 +274,267 @@ "⠀⡀" ] }, + "dots8Bit": { + "interval": 80, + "frames": [ + "⠀", + "⠁", + "⠂", + "⠃", + "⠄", + "⠅", + "⠆", + "⠇", + "⡀", + "⡁", + "⡂", + "⡃", + "⡄", + "⡅", + "⡆", + "⡇", + "⠈", + "⠉", + "⠊", + "⠋", + "⠌", + "⠍", + "⠎", + "⠏", + "⡈", + "⡉", + "⡊", + "⡋", + "⡌", + "⡍", + "⡎", + "⡏", + "⠐", + "⠑", + "⠒", + "⠓", + "⠔", + "⠕", + "⠖", + "⠗", + "⡐", + "⡑", + "⡒", + "⡓", + "⡔", + "⡕", + "⡖", + "⡗", + "⠘", + "⠙", + "⠚", + "⠛", + "⠜", + "⠝", + "⠞", + "⠟", + "⡘", + "⡙", + "⡚", + "⡛", + "⡜", + "⡝", + "⡞", + "⡟", + "⠠", + "⠡", + "⠢", + "⠣", + "⠤", + "⠥", + "⠦", + "⠧", + "⡠", + "⡡", + "⡢", + "⡣", + "⡤", + "⡥", + "⡦", + "⡧", + "⠨", + "⠩", + "⠪", + "⠫", + "⠬", + "⠭", + "⠮", + "⠯", + "⡨", + "⡩", + "⡪", + "⡫", + "⡬", + "⡭", + "⡮", + "⡯", + "⠰", + "⠱", + "⠲", + "⠳", + "⠴", + "⠵", + "⠶", + "⠷", + "⡰", + "⡱", + "⡲", + "⡳", + "⡴", + "⡵", + "⡶", + "⡷", + "⠸", + "⠹", + "⠺", + "⠻", + "⠼", + "⠽", + "⠾", + "⠿", + "⡸", + "⡹", + "⡺", + "⡻", + "⡼", + "⡽", + "⡾", + "⡿", + "⢀", + "⢁", + "⢂", + "⢃", + "⢄", + "⢅", + "⢆", + "⢇", + "⣀", + "⣁", + "⣂", + "⣃", + "⣄", + "⣅", + "⣆", + "⣇", + "⢈", + "⢉", + "⢊", + "⢋", + "⢌", + "⢍", + "⢎", + "⢏", + "⣈", + "⣉", + "⣊", + "⣋", + "⣌", + "⣍", + "⣎", + "⣏", + "⢐", + "⢑", + "⢒", + "⢓", + "⢔", + "⢕", + "⢖", + "⢗", + "⣐", + "⣑", + "⣒", + "⣓", + "⣔", + "⣕", + "⣖", + "⣗", + "⢘", + "⢙", + "⢚", + "⢛", + "⢜", + "⢝", + "⢞", + "⢟", + "⣘", + "⣙", + "⣚", + "⣛", + "⣜", + "⣝", + "⣞", + "⣟", + "⢠", + "⢡", + "⢢", + "⢣", + "⢤", + "⢥", + "⢦", + "⢧", + "⣠", + "⣡", + "⣢", + "⣣", + "⣤", + "⣥", + "⣦", + "⣧", + "⢨", + "⢩", + "⢪", + "⢫", + "⢬", + "⢭", + "⢮", + "⢯", + "⣨", + "⣩", + "⣪", + "⣫", + "⣬", + "⣭", + "⣮", + "⣯", + "⢰", + "⢱", + "⢲", + "⢳", + "⢴", + "⢵", + "⢶", + "⢷", + "⣰", + "⣱", + "⣲", + "⣳", + "⣴", + "⣵", + "⣶", + "⣷", + "⢸", + "⢹", + "⢺", + "⢻", + "⢼", + "⢽", + "⢾", + "⢿", + "⣸", + "⣹", + "⣺", + "⣻", + "⣼", + "⣽", + "⣾", + "⣿" + ] + }, "line": { "interval": 130, "frames": [ @@ -742,6 +1003,103 @@ "🌏 " ] }, + "material": { + "interval": 17, + "frames": [ + "█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "███▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "████▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "███████▁▁▁▁▁▁▁▁▁▁▁▁▁", + "████████▁▁▁▁▁▁▁▁▁▁▁▁", + "█████████▁▁▁▁▁▁▁▁▁▁▁", + "█████████▁▁▁▁▁▁▁▁▁▁▁", + "██████████▁▁▁▁▁▁▁▁▁▁", + "███████████▁▁▁▁▁▁▁▁▁", + "█████████████▁▁▁▁▁▁▁", + "██████████████▁▁▁▁▁▁", + "██████████████▁▁▁▁▁▁", + "▁██████████████▁▁▁▁▁", + "▁██████████████▁▁▁▁▁", + "▁██████████████▁▁▁▁▁", + "▁▁██████████████▁▁▁▁", + "▁▁▁██████████████▁▁▁", + "▁▁▁▁█████████████▁▁▁", + "▁▁▁▁██████████████▁▁", + "▁▁▁▁██████████████▁▁", + "▁▁▁▁▁██████████████▁", + "▁▁▁▁▁██████████████▁", + "▁▁▁▁▁██████████████▁", + "▁▁▁▁▁▁██████████████", + "▁▁▁▁▁▁██████████████", + "▁▁▁▁▁▁▁█████████████", + "▁▁▁▁▁▁▁█████████████", + "▁▁▁▁▁▁▁▁████████████", + "▁▁▁▁▁▁▁▁████████████", + "▁▁▁▁▁▁▁▁▁███████████", + "▁▁▁▁▁▁▁▁▁███████████", + "▁▁▁▁▁▁▁▁▁▁██████████", + "▁▁▁▁▁▁▁▁▁▁██████████", + "▁▁▁▁▁▁▁▁▁▁▁▁████████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁███████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁██████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████", + "█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████", + "██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███", + "██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███", + "███▁▁▁▁▁▁▁▁▁▁▁▁▁▁███", + "████▁▁▁▁▁▁▁▁▁▁▁▁▁▁██", + "█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█", + "█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█", + "██████▁▁▁▁▁▁▁▁▁▁▁▁▁█", + "████████▁▁▁▁▁▁▁▁▁▁▁▁", + "█████████▁▁▁▁▁▁▁▁▁▁▁", + "█████████▁▁▁▁▁▁▁▁▁▁▁", + "█████████▁▁▁▁▁▁▁▁▁▁▁", + "█████████▁▁▁▁▁▁▁▁▁▁▁", + "███████████▁▁▁▁▁▁▁▁▁", + "████████████▁▁▁▁▁▁▁▁", + "████████████▁▁▁▁▁▁▁▁", + "██████████████▁▁▁▁▁▁", + "██████████████▁▁▁▁▁▁", + "▁██████████████▁▁▁▁▁", + "▁██████████████▁▁▁▁▁", + "▁▁▁█████████████▁▁▁▁", + "▁▁▁▁▁████████████▁▁▁", + "▁▁▁▁▁████████████▁▁▁", + "▁▁▁▁▁▁███████████▁▁▁", + "▁▁▁▁▁▁▁▁█████████▁▁▁", + "▁▁▁▁▁▁▁▁█████████▁▁▁", + "▁▁▁▁▁▁▁▁▁█████████▁▁", + "▁▁▁▁▁▁▁▁▁█████████▁▁", + "▁▁▁▁▁▁▁▁▁▁█████████▁", + "▁▁▁▁▁▁▁▁▁▁▁████████▁", + "▁▁▁▁▁▁▁▁▁▁▁████████▁", + "▁▁▁▁▁▁▁▁▁▁▁▁███████▁", + "▁▁▁▁▁▁▁▁▁▁▁▁███████▁", + "▁▁▁▁▁▁▁▁▁▁▁▁▁███████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁███████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁" + ] + }, "moon": { "interval": 80, "frames": [ @@ -875,8 +1233,8 @@ "grenade": { "interval": 80, "frames": [ - "، ", - "′ ", + "، ", + "′ ", " ´ ", " ‾ ", " ⸌", @@ -908,5 +1266,150 @@ "=", "≡" ] + }, + "betaWave": { + "interval": 80, + "frames": [ + "ρββββββ", + "βρβββββ", + "ββρββββ", + "βββρβββ", + "ββββρββ", + "βββββρβ", + "ββββββρ" + ] + }, + "fingerDance": { + "interval": 160, + "frames": [ + "🤘 ", + "🤟 ", + "🖖 ", + "✋ ", + "🤚 ", + "👆 " + ] + }, + "fistBump": { + "interval": 80, + "frames": [ + "🤜\u3000\u3000\u3000\u3000🤛 ", + "🤜\u3000\u3000\u3000\u3000🤛 ", + "🤜\u3000\u3000\u3000\u3000🤛 ", + "\u3000🤜\u3000\u3000🤛\u3000 ", + "\u3000\u3000🤜🤛\u3000\u3000 ", + "\u3000🤜✨🤛\u3000\u3000 ", + "🤜\u3000✨\u3000🤛\u3000 " + ] + }, + "soccerHeader": { + "interval": 80, + "frames": [ + " 🧑⚽️ 🧑 ", + "🧑 ⚽️ 🧑 ", + "🧑 ⚽️ 🧑 ", + "🧑 ⚽️ 🧑 ", + "🧑 ⚽️ 🧑 ", + "🧑 ⚽️ 🧑 ", + "🧑 ⚽️🧑 ", + "🧑 ⚽️ 🧑 ", + "🧑 ⚽️ 🧑 ", + "🧑 ⚽️ 🧑 ", + "🧑 ⚽️ 🧑 ", + "🧑 ⚽️ 🧑 " + ] + }, + "mindblown": { + "interval": 160, + "frames": [ + "😐 ", + "😐 ", + "😮 ", + "😮 ", + "😦 ", + "😦 ", + "😧 ", + "😧 ", + "🤯 ", + "💥 ", + "✨ ", + "\u3000 ", + "\u3000 ", + "\u3000 " + ] + }, + "speaker": { + "interval": 160, + "frames": [ + "🔈 ", + "🔉 ", + "🔊 ", + "🔉 " + ] + }, + "orangePulse": { + "interval": 100, + "frames": [ + "🔸 ", + "🔶 ", + "🟠 ", + "🟠 ", + "🔶 " + ] + }, + "bluePulse": { + "interval": 100, + "frames": [ + "🔹 ", + "🔷 ", + "🔵 ", + "🔵 ", + "🔷 " + ] + }, + "orangeBluePulse": { + "interval": 100, + "frames": [ + "🔸 ", + "🔶 ", + "🟠 ", + "🟠 ", + "🔶 ", + "🔹 ", + "🔷 ", + "🔵 ", + "🔵 ", + "🔷 " + ] + }, + "timeTravel": { + "interval": 100, + "frames": [ + "🕛 ", + "🕚 ", + "🕙 ", + "🕘 ", + "🕗 ", + "🕖 ", + "🕕 ", + "🕔 ", + "🕓 ", + "🕒 ", + "🕑 ", + "🕐 " + ] + }, + "aesthetic": { + "interval": 80, + "frames": [ + "▰▱▱▱▱▱▱", + "▰▰▱▱▱▱▱", + "▰▰▰▱▱▱▱", + "▰▰▰▰▱▱▱", + "▰▰▰▰▰▱▱", + "▰▰▰▰▰▰▱", + "▰▰▰▰▰▰▰", + "▰▱▱▱▱▱▱" + ] } } diff --git a/pipenv/vendor/yaspin/helpers.py b/pipenv/vendor/yaspin/helpers.py index 49ce0d06..67483016 100644 --- a/pipenv/vendor/yaspin/helpers.py +++ b/pipenv/vendor/yaspin/helpers.py @@ -1,4 +1,5 @@ -# -*- coding: utf-8 -*- +# :copyright: (c) 2021 by Pavlo Dmytrenko. +# :license: MIT, see LICENSE for more details. """ yaspin.helpers @@ -7,9 +8,6 @@ yaspin.helpers Helper functions. """ -from __future__ import absolute_import - -from .compat import bytes from .constants import ENCODING diff --git a/pipenv/vendor/yaspin/signal_handlers.py b/pipenv/vendor/yaspin/signal_handlers.py index f38f5d6b..a4bb7921 100644 --- a/pipenv/vendor/yaspin/signal_handlers.py +++ b/pipenv/vendor/yaspin/signal_handlers.py @@ -1,4 +1,5 @@ -# -*- coding: utf-8 -*- +# :copyright: (c) 2021 by Pavlo Dmytrenko. +# :license: MIT, see LICENSE for more details. """ yaspin.signal_handlers @@ -11,7 +12,7 @@ when the signal occurs. import sys -def default_handler(signum, frame, spinner): +def default_handler(signum, frame, spinner): # pylint: disable=unused-argument """Signal handler, used to gracefully shut down the ``spinner`` instance when specified signal is received by the process running the ``spinner``. @@ -23,7 +24,7 @@ def default_handler(signum, frame, spinner): sys.exit(0) -def fancy_handler(signum, frame, spinner): +def fancy_handler(signum, frame, spinner): # pylint: disable=unused-argument """Signal handler, used to gracefully shut down the ``spinner`` instance when specified signal is received by the process running the ``spinner``. diff --git a/pipenv/vendor/yaspin/spinners.py b/pipenv/vendor/yaspin/spinners.py index 9c3fa7b8..d607949f 100644 --- a/pipenv/vendor/yaspin/spinners.py +++ b/pipenv/vendor/yaspin/spinners.py @@ -1,4 +1,5 @@ -# -*- coding: utf-8 -*- +# :copyright: (c) 2021 by Pavlo Dmytrenko. +# :license: MIT, see LICENSE for more details. """ yaspin.spinners @@ -7,23 +8,21 @@ yaspin.spinners A collection of cli spinners. """ -import codecs -import os +import pkgutil from collections import namedtuple + try: import simplejson as json except ImportError: import json -THIS_DIR = os.path.dirname(os.path.realpath(__file__)) -SPINNERS_PATH = os.path.join(THIS_DIR, "data/spinners.json") +SPINNERS_DATA = pkgutil.get_data(__name__, "data/spinners.json").decode("utf-8") def _hook(dct): return namedtuple("Spinner", dct.keys())(*dct.values()) -with codecs.open(SPINNERS_PATH, encoding="utf-8") as f: - Spinners = json.load(f, object_hook=_hook) +Spinners = json.loads(SPINNERS_DATA, object_hook=_hook) diff --git a/pipenv/patched/notpip/_vendor/lockfile/LICENSE b/pipenv/vendor/zipp.LICENSE similarity index 90% rename from pipenv/patched/notpip/_vendor/lockfile/LICENSE rename to pipenv/vendor/zipp.LICENSE index 610c0793..353924be 100644 --- a/pipenv/patched/notpip/_vendor/lockfile/LICENSE +++ b/pipenv/vendor/zipp.LICENSE @@ -1,6 +1,4 @@ -This is the MIT license: http://www.opensource.org/licenses/mit-license.php - -Copyright (c) 2007 Skip Montanaro. +Copyright Jason R. Coombs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to diff --git a/pipenv/vendor/zipp.py b/pipenv/vendor/zipp.py new file mode 100644 index 00000000..69cdaad4 --- /dev/null +++ b/pipenv/vendor/zipp.py @@ -0,0 +1,326 @@ +import io +import posixpath +import zipfile +import itertools +import contextlib +import sys +import pathlib + +if sys.version_info < (3, 7): + from collections import OrderedDict +else: + OrderedDict = dict + + +def _parents(path): + """ + Given a path with elements separated by + posixpath.sep, generate all parents of that path. + + >>> list(_parents('b/d')) + ['b'] + >>> list(_parents('/b/d/')) + ['/b'] + >>> list(_parents('b/d/f/')) + ['b/d', 'b'] + >>> list(_parents('b')) + [] + >>> list(_parents('')) + [] + """ + return itertools.islice(_ancestry(path), 1, None) + + +def _ancestry(path): + """ + Given a path with elements separated by + posixpath.sep, generate all elements of that path + + >>> list(_ancestry('b/d')) + ['b/d', 'b'] + >>> list(_ancestry('/b/d/')) + ['/b/d', '/b'] + >>> list(_ancestry('b/d/f/')) + ['b/d/f', 'b/d', 'b'] + >>> list(_ancestry('b')) + ['b'] + >>> list(_ancestry('')) + [] + """ + path = path.rstrip(posixpath.sep) + while path and path != posixpath.sep: + yield path + path, tail = posixpath.split(path) + + +_dedupe = OrderedDict.fromkeys +"""Deduplicate an iterable in original order""" + + +def _difference(minuend, subtrahend): + """ + Return items in minuend not in subtrahend, retaining order + with O(1) lookup. + """ + return itertools.filterfalse(set(subtrahend).__contains__, minuend) + + +class CompleteDirs(zipfile.ZipFile): + """ + A ZipFile subclass that ensures that implied directories + are always included in the namelist. + """ + + @staticmethod + def _implied_dirs(names): + parents = itertools.chain.from_iterable(map(_parents, names)) + as_dirs = (p + posixpath.sep for p in parents) + return _dedupe(_difference(as_dirs, names)) + + def namelist(self): + names = super(CompleteDirs, self).namelist() + return names + list(self._implied_dirs(names)) + + def _name_set(self): + return set(self.namelist()) + + def resolve_dir(self, name): + """ + If the name represents a directory, return that name + as a directory (with the trailing slash). + """ + names = self._name_set() + dirname = name + '/' + dir_match = name not in names and dirname in names + return dirname if dir_match else name + + @classmethod + def make(cls, source): + """ + Given a source (filename or zipfile), return an + appropriate CompleteDirs subclass. + """ + if isinstance(source, CompleteDirs): + return source + + if not isinstance(source, zipfile.ZipFile): + return cls(_pathlib_compat(source)) + + # Only allow for FastLookup when supplied zipfile is read-only + if 'r' not in source.mode: + cls = CompleteDirs + + source.__class__ = cls + return source + + +class FastLookup(CompleteDirs): + """ + ZipFile subclass to ensure implicit + dirs exist and are resolved rapidly. + """ + + def namelist(self): + with contextlib.suppress(AttributeError): + return self.__names + self.__names = super(FastLookup, self).namelist() + return self.__names + + def _name_set(self): + with contextlib.suppress(AttributeError): + return self.__lookup + self.__lookup = super(FastLookup, self)._name_set() + return self.__lookup + + +def _pathlib_compat(path): + """ + For path-like objects, convert to a filename for compatibility + on Python 3.6.1 and earlier. + """ + try: + return path.__fspath__() + except AttributeError: + return str(path) + + +class Path: + """ + A pathlib-compatible interface for zip files. + + Consider a zip file with this structure:: + + . + ├── a.txt + └── b + ├── c.txt + └── d + └── e.txt + + >>> data = io.BytesIO() + >>> zf = zipfile.ZipFile(data, 'w') + >>> zf.writestr('a.txt', 'content of a') + >>> zf.writestr('b/c.txt', 'content of c') + >>> zf.writestr('b/d/e.txt', 'content of e') + >>> zf.filename = 'mem/abcde.zip' + + Path accepts the zipfile object itself or a filename + + >>> root = Path(zf) + + From there, several path operations are available. + + Directory iteration (including the zip file itself): + + >>> a, b = root.iterdir() + >>> a + Path('mem/abcde.zip', 'a.txt') + >>> b + Path('mem/abcde.zip', 'b/') + + name property: + + >>> b.name + 'b' + + join with divide operator: + + >>> c = b / 'c.txt' + >>> c + Path('mem/abcde.zip', 'b/c.txt') + >>> c.name + 'c.txt' + + Read text: + + >>> c.read_text() + 'content of c' + + existence: + + >>> c.exists() + True + >>> (b / 'missing.txt').exists() + False + + Coercion to string: + + >>> import os + >>> str(c).replace(os.sep, posixpath.sep) + 'mem/abcde.zip/b/c.txt' + + At the root, ``name``, ``filename``, and ``parent`` + resolve to the zipfile. Note these attributes are not + valid and will raise a ``ValueError`` if the zipfile + has no filename. + + >>> root.name + 'abcde.zip' + >>> str(root.filename).replace(os.sep, posixpath.sep) + 'mem/abcde.zip' + >>> str(root.parent) + 'mem' + """ + + __repr = "{self.__class__.__name__}({self.root.filename!r}, {self.at!r})" + + def __init__(self, root, at=""): + """ + Construct a Path from a ZipFile or filename. + + Note: When the source is an existing ZipFile object, + its type (__class__) will be mutated to a + specialized type. If the caller wishes to retain the + original type, the caller should either create a + separate ZipFile object or pass a filename. + """ + self.root = FastLookup.make(root) + self.at = at + + def open(self, mode='r', *args, pwd=None, **kwargs): + """ + Open this entry as text or binary following the semantics + of ``pathlib.Path.open()`` by passing arguments through + to io.TextIOWrapper(). + """ + if self.is_dir(): + raise IsADirectoryError(self) + zip_mode = mode[0] + if not self.exists() and zip_mode == 'r': + raise FileNotFoundError(self) + stream = self.root.open(self.at, zip_mode, pwd=pwd) + if 'b' in mode: + if args or kwargs: + raise ValueError("encoding args invalid for binary operation") + return stream + return io.TextIOWrapper(stream, *args, **kwargs) + + @property + def name(self): + return pathlib.Path(self.at).name or self.filename.name + + @property + def suffix(self): + return pathlib.Path(self.at).suffix or self.filename.suffix + + @property + def suffixes(self): + return pathlib.Path(self.at).suffixes or self.filename.suffixes + + @property + def stem(self): + return pathlib.Path(self.at).stem or self.filename.stem + + @property + def filename(self): + return pathlib.Path(self.root.filename).joinpath(self.at) + + def read_text(self, *args, **kwargs): + with self.open('r', *args, **kwargs) as strm: + return strm.read() + + def read_bytes(self): + with self.open('rb') as strm: + return strm.read() + + def _is_child(self, path): + return posixpath.dirname(path.at.rstrip("/")) == self.at.rstrip("/") + + def _next(self, at): + return self.__class__(self.root, at) + + def is_dir(self): + return not self.at or self.at.endswith("/") + + def is_file(self): + return self.exists() and not self.is_dir() + + def exists(self): + return self.at in self.root._name_set() + + def iterdir(self): + if not self.is_dir(): + raise ValueError("Can't listdir a file") + subs = map(self._next, self.root.namelist()) + return filter(self._is_child, subs) + + def __str__(self): + return posixpath.join(self.root.filename, self.at) + + def __repr__(self): + return self.__repr.format(self=self) + + def joinpath(self, *other): + next = posixpath.join(self.at, *map(_pathlib_compat, other)) + return self._next(self.root.resolve_dir(next)) + + __truediv__ = joinpath + + @property + def parent(self): + if not self.at: + return self.filename.parent + parent_at = posixpath.dirname(self.at.rstrip('/')) + if parent_at: + parent_at += '/' + return self._next(parent_at) diff --git a/run-tests.sh b/run-tests.sh index aef44f63..adc95095 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -1,53 +1,56 @@ #!/usr/bin/env bash -# NOTE: set TEST_SUITE to be markers you want to run. +# Tested on debian and alpine +# You will need to install some dependencies yourself. set -eo pipefail export PYTHONIOENCODING="utf-8" export LANG=C.UTF-8 -export PIP_PROCESS_DEPENDENCY_LINKS="1" + # Let's use a temporary cache directory export PIPENV_CACHE_DIR=`mktemp -d 2>/dev/null || mktemp -d -t 'pipenv_cache'` -prefix() { - sed "s/^/ $1: /" -} +# on some Linux OS python is python3 +PYTHON=${PYTHON:-"python"} +PIPENV_PYTHON="${PIPENV_PYTHON:-3.7}" -if [[ ! -z "$TEST_SUITE" ]]; then - echo "Using TEST_SUITE=$TEST_SUITE" -fi +PIP_CALL="${PIP_CALL:-${PYTHON} -m pip install --user}" HOME=$(readlink -f ~/) + if [[ -z "$HOME" ]]; then if [[ "$USER" == "root" ]]; then HOME="/root" fi fi + if [[ ! -z "$HOME" ]]; then export PATH="${HOME}/.local/bin:${PATH}" fi -pip install certifi -export GIT_SSL_CAINFO=$(python -m certifi) -echo "Path: $PATH" -echo "Installing Pipenv…" -python -m pip install --upgrade -e "$(pwd)" setuptools wheel pip -VENV_CMD="python -m pipenv --venv" + +# This installs the dependencies for pipenv +${PIP_CALL} --upgrade pip setuptools wheel virtualenv --upgrade-strategy=eager + +VENV_CMD="${PYTHON} -m pipenv --venv" RM_CMD="pipenv --rm" -echo "$ PIPENV_PYTHON=2.7 $VENV_CMD && PIPENV_PYTHON=2.7 $RM_CMD" -echo "$ PIPENV_PYTHON=3.7 $VENV_CMD && PIPENV_PYTHON=3.7 $RM_CMD" -{ PIPENV_PYTHON=2.7 $VENV_CMD && PIPENV_PYTHON=2.7 $RM_CMD ; PIPENV_PYTHON=3.7 $VENV_CMD && PIPENV_PYTHON=3.7 $RM_CMD ; } -echo "Installing dependencies…" -INSTALL_CMD="python -m pipenv install --deploy --dev" -echo "$ PIPENV_PYTHON=2.7 $INSTALL_CMD" -echo "$ PIPENV_PYTHON=3.7 $INSTALL_CMD" +echo "$ PIPENV_PYTHON=${PIPENV_PYTHON} $VENV_CMD && PIPENV_PYTHON=${PIPENV_PYTHON} $RM_CMD" + +{ PIPENV_PYTHON="${PIPENV_PYTHON}" $VENV_CMD && PIPENV_PYTHON=${PIPENV_PYTHON} $RM_CMD ; } + +echo "Installing dependencies..." + +INSTALL_CMD="${PYTHON} -m pipenv install --deploy --dev" + +echo "$ PIPENV_PYTHON=${PIPENV_PYTHON} $INSTALL_CMD" + +PIPENV_PYTHON=${PIPENV_PYTHON} $INSTALL_CMD -{ ( PIPENV_PYTHON=2.7 $INSTALL_CMD & ); PIPENV_PYTHON=3.7 $INSTALL_CMD ; } echo "$ git submodule sync && git submodule update --init --recursive" git submodule sync && git submodule update --init --recursive -echo "$ pipenv run time pytest" -PIPENV_PYTHON=2.7 python -m pipenv run time pytest -PIPENV_PYTHON=3.7 python -m pipenv run time pytest +echo "$pipenv run pytest -ra -n auto -v --fulltrace tests" + +PIPENV_PYTHON=${PIPENV_PYTHON} ${PYTHON} -m pipenv run pytest -v -ra -n auto --fulltrace tests diff --git a/setup.cfg b/setup.cfg index fbc691d4..6ee423ab 100644 --- a/setup.cfg +++ b/setup.cfg @@ -4,9 +4,14 @@ license = MIT license_file = LICENSE [flake8] -exclude = - .git,__pycache__,docs/,pipenv/vendor/,pipenv/patched,get-pipenv.py, - .eggs/,setup.py,tests/fixtures/ +extend-exclude = + docs/, + pipenv/vendor/, + pipenv/patched/, + get-pipenv.py, + setup.py, + tests/fixtures/, + tests/test_artifacts/ ignore = # The default ignore list: E121,E123,E126,E226,E24,E704, @@ -27,11 +32,9 @@ lines_after_imports=2 lines_between_types=1 multi_line_output=5 line_length=80 -not_skip=__init__.py known_first_party = pipenv tests -ignore_trailing_comma=true [mypy] ignore_missing_imports=true @@ -41,8 +44,7 @@ python_version=3.6 mypy_path=typeshed/pyi:typeshed/imports [tool:pytest] -addopts = -ra -n auto -plugins = xdist +addopts = -ra testpaths = tests ; Add vendor and patched in addition to the default list of ignored dirs ; Additionally, ignore tasks, news, test subdirectories and peeps directory @@ -65,3 +67,6 @@ norecursedirs = filterwarnings = ignore::DeprecationWarning ignore::PendingDeprecationWarning + +[bdist_wheel] +universal = 1 diff --git a/setup.py b/setup.py index c3ee913b..3d56518c 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- import codecs import os import sys @@ -26,23 +25,20 @@ required = [ "certifi", "setuptools>=36.2.1", "virtualenv-clone>=0.2.5", - "virtualenv", - 'enum34; python_version<"3"', - # LEAVE THIS HERE!!! we have vendored dependencies that require it - 'typing; python_version<"3.5"' + "virtualenv" ] extras = { "dev": [ "towncrier", "bs4", "twine", - "sphinx<2", + "sphinx", "flake8>=3.3.0,<4.0", "black;python_version>='3.6'", "parver", "invoke", ], - "tests": ["pytest<5.0", "pytest-tap", "pytest-xdist", "flaky", "mock"], + "tests": ["pytest>=5.0", "pytest-timeout", "pytest-xdist", "flaky", "mock"], } @@ -56,7 +52,7 @@ class DebCommand(Command): @staticmethod def status(s): """Prints things in bold.""" - print("\033[1m{0}\033[0m".format(s)) + print(f"\033[1m{s}\033[0m") def initialize_options(self): pass @@ -66,16 +62,16 @@ class DebCommand(Command): def run(self): try: - self.status("Removing previous builds…") + self.status("Removing previous builds...") rmtree(os.path.join(here, "deb_dist")) except FileNotFoundError: pass - self.status(u"Creating debian mainfest…") + self.status("Creating debian mainfest...") os.system( "python setup.py --command-packages=stdeb.command sdist_dsc -z artful --package3=pipenv --depends3=python3-virtualenv-clone" ) - self.status(u"Building .deb…") - os.chdir("deb_dist/pipenv-{0}".format(about["__version__"])) + self.status("Building .deb...") + os.chdir("deb_dist/pipenv-{}".format(about["__version__"])) os.system("dpkg-buildpackage -rfakeroot -uc -us") @@ -88,7 +84,7 @@ class UploadCommand(Command): @staticmethod def status(s): """Prints things in bold.""" - print("\033[1m{0}\033[0m".format(s)) + print(f"\033[1m{s}\033[0m") def initialize_options(self): pass @@ -98,16 +94,16 @@ class UploadCommand(Command): def run(self): try: - self.status("Removing previous builds…") + self.status("Removing previous builds...") rmtree(os.path.join(here, "dist")) except FileNotFoundError: pass - self.status("Building Source distribution…") - os.system("{0} setup.py sdist bdist_wheel".format(sys.executable)) - self.status("Uploading the package to PyPI via Twine…") + self.status("Building Source distribution...") + os.system(f"{sys.executable} setup.py sdist bdist_wheel") + self.status("Uploading the package to PyPI via Twine...") os.system("twine upload dist/*") - self.status("Pushing git tags…") - os.system("git tag v{0}".format(about["__version__"])) + self.status("Pushing git tags...") + os.system("git tag v{}".format(about["__version__"])) os.system("git push --tags") sys.exit() @@ -132,7 +128,6 @@ setup( "": ["LICENSE", "NOTICES"], "pipenv.vendor.requests": ["*.pem"], "pipenv.vendor.certifi": ["*.pem"], - "pipenv.vendor.click_completion": ["*.j2"], "pipenv.patched.notpip._vendor.certifi": ["*.pem"], "pipenv.patched.notpip._vendor.requests": ["*.pem"], "pipenv.patched.notpip._vendor.distlib._backport": ["sysconfig.cfg"], @@ -143,7 +138,8 @@ setup( "w64.exe", ], }, - python_requires=">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*", + python_requires=">=3.6", + zip_safe=True, setup_requires=[], install_requires=required, extras_require=extras, @@ -152,12 +148,12 @@ setup( classifiers=[ "License :: OSI Approved :: MIT License", "Programming Language :: Python", - "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", ], diff --git a/tasks/__init__.py b/tasks/__init__.py index d81d101d..07a054a9 100644 --- a/tasks/__init__.py +++ b/tasks/__init__.py @@ -1,4 +1,3 @@ -# -*- coding=utf-8 -*- # Copied from pip's vendoring process # see https://github.com/pypa/pip/blob/95bcf8c5f6394298035a7332c441868f3b0169f4/tasks/__init__.py from pathlib import Path @@ -6,9 +5,8 @@ from pathlib import Path import invoke from . import release, vendoring -from .vendoring import vendor_passa ROOT = Path(".").parent.parent.absolute() -ns = invoke.Collection(vendoring, release, release.clean_mdchangelog, vendor_passa.vendor_passa) +ns = invoke.Collection(vendoring, release, release.clean_mdchangelog) diff --git a/tasks/release.py b/tasks/release.py index c43a7c85..058c546e 100644 --- a/tasks/release.py +++ b/tasks/release.py @@ -1,14 +1,13 @@ -# -*- coding=utf-8 -*- import datetime import os import pathlib import re +import sys +import subprocess import invoke from parver import Version -from towncrier._builder import find_fragments, render_fragments, split_fragments -from towncrier._settings import load_config from pipenv.__version__ import __version__ from pipenv.vendor.vistir.contextmanagers import temp_environ @@ -52,30 +51,34 @@ def get_build_dir(ctx): def _render_log(): """Totally tap into Towncrier internals to get an in-memory result. """ - config = load_config(ROOT) - definitions = config["types"] - fragments, fragment_filenames = find_fragments( - pathlib.Path(config["directory"]).absolute(), - config["sections"], - None, - definitions, - ) - rendered = render_fragments( - pathlib.Path(config["template"]).read_text(encoding="utf-8"), - config["issue_format"], - split_fragments(fragments, definitions), - definitions, - config["underlines"][1:], - False, # Don't add newlines to wrapped text. - ) + rendered = subprocess.check_output(["towncrier", "--draft"]).decode("utf-8") return rendered -@invoke.task -def release(ctx, dry_run=False): +release_help = { + "manual": "Build the man pages.", + "dry_run": "No-op, simulate what would happen if run for real.", + "local": "Build package locally and upload to PyPI.", + "pre": "Build a pre-release version, must be paired with a tag.", + "tag": "A release tag, e.g. 'a', 'b', 'rc', 'post'.", + "month_offset": "How many months to offset the release date by.", +} + + +@invoke.task(help=release_help) +def release(ctx, manual=False, local=False, dry_run=False, pre=False, tag=None, month_offset="0"): + trunc_month = False + if pre: + trunc_month = True drop_dist_dirs(ctx) - bump_version(ctx, dry_run=dry_run) - version = find_version(ctx) + version = bump_version( + ctx, + dry_run=dry_run, + pre=pre, + tag=tag, + month_offset=month_offset, + trunc_month=trunc_month + ) tag_content = _render_log() if dry_run: ctx.run("towncrier --draft > CHANGELOG.draft.rst") @@ -84,36 +87,48 @@ def release(ctx, dry_run=False): log("would update: pipenv/pipenv.1") log(f'Would commit with message: "Release v{version}"') else: - ctx.run("towncrier") - ctx.run( - "git add CHANGELOG.rst news/ {0}".format(get_version_file(ctx).as_posix()) - ) - ctx.run("git rm CHANGELOG.draft.rst") + if pre: + log("generating towncrier draft...") + ctx.run("towncrier --draft > CHANGELOG.draft.rst") + ctx.run(f"git add {get_version_file(ctx).as_posix()}") + else: + ctx.run("towncrier") + ctx.run( + f"git add CHANGELOG.rst news/ {get_version_file(ctx).as_posix()}" + ) + log("removing changelog draft if present") + draft_changelog = pathlib.Path("CHANGELOG.draft.rst") + if draft_changelog.exists(): + draft_changelog.unlink() + log("generating man files...") generate_manual(ctx) ctx.run("git add pipenv/pipenv.1") ctx.run(f'git commit -m "Release v{version}"') tag_content = tag_content.replace('"', '\\"') - if dry_run: + if dry_run or pre: log(f"Generated tag content: {tag_content}") - markdown = ctx.run( - "pandoc CHANGELOG.draft.rst -f rst -t markdown", hide=True - ).stdout.strip() - content = clean_mdchangelog(ctx, markdown) - log(f"would generate markdown: {content}") + # draft_rstfile = "CHANGELOG.draft.rst" + # markdown_path = pathlib.Path(draft_rstfile).with_suffix(".md") + # generate_markdown(ctx, source_rstfile=draft_rstfile) + # clean_mdchangelog(ctx, markdown_path.as_posix()) + # log(f"would generate markdown: {markdown_path.read_text()}") + if not dry_run: + ctx.run(f'git tag -a v{version} -m "Version v{version}\n\n{tag_content}"') else: - generate_markdown(ctx) - clean_mdchangelog(ctx) + # generate_markdown(ctx) + # clean_mdchangelog(ctx) ctx.run(f'git tag -a v{version} -m "Version v{version}\n\n{tag_content}"') - build_dists(ctx) - if dry_run: + if local: + build_dists(ctx) dist_pattern = f'{PACKAGE_NAME.replace("-", "[-_]")}-*' artifacts = list(ROOT.joinpath("dist").glob(dist_pattern)) - filename_display = "\n".join(f" {a}" for a in artifacts) - log(f"Would upload dists: {filename_display}") - else: - upload_dists(ctx) - bump_version(ctx, dev=True) + if dry_run: + filename_display = "\n".join(f" {a}" for a in artifacts) + log(f"Would upload dists: {filename_display}") + else: + upload_dists(ctx) + bump_version(ctx, dev=True) def drop_dist_dirs(ctx): @@ -126,23 +141,20 @@ def drop_dist_dirs(ctx): @invoke.task def build_dists(ctx): drop_dist_dirs(ctx) - for py_version in ["3.6", "2.7"]: - env = {"PIPENV_PYTHON": py_version} - with ctx.cd(ROOT.as_posix()), temp_environ(): - executable = ctx.run( - "python -c 'import sys; print(sys.executable)'", hide=True - ).stdout.strip() - log("Building sdist using %s ...." % executable) - os.environ["PIPENV_PYTHON"] = py_version - ctx.run("pipenv install --dev", env=env) - ctx.run( - "pipenv run pip install -e . --upgrade --upgrade-strategy=eager", env=env - ) - log("Building wheel using python %s ...." % py_version) - if py_version == "3.6": - ctx.run("pipenv run python setup.py sdist bdist_wheel", env=env) - else: - ctx.run("pipenv run python setup.py bdist_wheel", env=env) + py_version = ".".join(str(v) for v in sys.version_info[:2]) + env = {"PIPENV_PYTHON": py_version} + with ctx.cd(ROOT.as_posix()), temp_environ(): + executable = ctx.run( + "python -c 'import sys; print(sys.executable)'", hide=True + ).stdout.strip() + log("Building sdist using %s ...." % executable) + os.environ["PIPENV_PYTHON"] = py_version + ctx.run("pipenv install --dev", env=env) + ctx.run( + "pipenv run pip install -e . --upgrade --upgrade-strategy=eager", env=env + ) + log("Building wheel using python %s ...." % py_version) + ctx.run(f"pipenv run python setup.py sdist bdist_wheel", env=env) @invoke.task(build_dists) @@ -162,15 +174,21 @@ def upload_dists(ctx, repo="pypi"): @invoke.task -def generate_markdown(ctx): +def generate_markdown(ctx, source_rstfile=None): log("Generating markdown from changelog...") - ctx.run("pandoc CHANGELOG.rst -f rst -t markdown -o CHANGELOG.md") + if source_rstfile is None: + source_rstfile = "CHANGELOG.rst" + source_file = pathlib.Path(source_rstfile) + dest_file = source_file.with_suffix(".md") + ctx.run( + f"pandoc {source_file.as_posix()} -f rst -t markdown -o {dest_file.as_posix()}" + ) @invoke.task def generate_manual(ctx, commit=False): log("Generating manual from reStructuredText source...") - ctx.run("make man -C docs") + ctx.run("make man") ctx.run("cp docs/_build/man/pipenv.1 pipenv/") if commit: log("Commiting...") @@ -205,10 +223,13 @@ def generate_changelog(ctx, commit=False, draft=False): @invoke.task -def clean_mdchangelog(ctx, content=None): +def clean_mdchangelog(ctx, filename=None, content=None): changelog = None if not content: - changelog = _get_git_root(ctx) / "CHANGELOG.md" + if filename is not None: + changelog = pathlib.Path(filename) + else: + changelog = _get_git_root(ctx) / "CHANGELOG.md" content = changelog.read_text() content = re.sub( r"([^\n]+)\n?\s+\[[\\]+(#\d+)\]\(https://github\.com/pypa/[\w\-]+/issues/\d+\)", @@ -234,28 +255,80 @@ def tag_version(ctx, push=False): ctx.run("git push --tags") +def add_one_day(dt): + return dt + datetime.timedelta(days=1) + + +def date_offset(dt, month_offset=0, day_offset=0, truncate=False): + new_month = (dt.month + month_offset) % 12 + year_offset = new_month // 12 + replace_args = { + "month": dt.month + month_offset, + "year": dt.year + year_offset, + } + log("Getting updated date from date: {} using month offset: {} and year offset {}".format( + dt, new_month, replace_args["year"] + )) + if day_offset: + dt = dt + datetime.timedelta(days=day_offset) + log(f"updated date using day offset: {day_offset} => {dt}") + if truncate: + log("Truncating...") + replace_args["day"] = 1 + return dt.replace(**replace_args) + + @invoke.task -def bump_version(ctx, dry_run=False, dev=False, pre=False, tag=None, commit=False): +def bump_version(ctx, dry_run=False, dev=False, pre=False, tag=None, commit=False, month_offset="0", trunc_month=False): current_version = Version.parse(__version__) + current_date = datetime.date(*current_version.release) today = datetime.date.today() - tomorrow = today + datetime.timedelta(days=1) - if pre and not tag: - print('Using "pre" requires a corresponding tag.') - return - if not (dev or pre or tag): - new_version = current_version.replace(release=today.timetuple()[:3]).clear( - pre=True, dev=True - ) + day_offset = 0 + month_offset = int(month_offset) + if month_offset: + # if we are offsetting by a month, grab the first day of the month + trunc_month = True + elif ( + current_date == today + and not current_version.is_prerelease + and not current_version.is_release_candidate + ): + day_offset = 1 + target_day = date_offset( + today, + month_offset=month_offset, + day_offset=day_offset, + truncate=trunc_month + ) + log(f"target_day: {target_day}") + target_timetuple = target_day.timetuple()[:3] + new_version = current_version.replace(release=target_timetuple) if pre and dev: raise RuntimeError("Can't use 'pre' and 'dev' together!") - if dev or pre: - new_version = current_version.replace(release=tomorrow.timetuple()[:3]).clear( - pre=True, dev=True + if dev: + new_version = new_version.replace(pre=None).bump_dev() + elif pre: + if not tag: + print('Using "pre" requires a corresponding tag.') + return + tag_version = re.match( + r"(?P<tag>alpha|a|beta|b|c|preview|pre|rc)(?P<version>[0-9]+)?", tag ) - if dev: - new_version = new_version.bump_dev() + tag_dict = tag_version.groupdict() + tag = tag_dict.get("tag", tag) + tag_version = int(tag_dict["version"]) if tag_dict["version"] is not None else 0 + if new_version.dev is not None: + new_version = new_version.replace(dev=None) + if new_version.pre_tag: + if new_version.pre_tag != tag: + log(f"Swapping prerelease tag: {new_version.pre_tag} for {tag}") + new_version = new_version.replace(pre_tag=tag, pre=tag_version) else: + new_version = new_version.replace(pre_tag=tag, pre=tag_version) + if tag_version == 0: new_version = new_version.bump_pre(tag=tag) + else: + new_version = new_version.replace(pre=None, dev=None) log("Updating version to %s" % new_version.normalize()) version = find_version(ctx) log("Found current version: %s" % version) @@ -269,6 +342,7 @@ def bump_version(ctx, dry_run=False, dev=False, pre=False, tag=None, commit=Fals file_contents.replace(version, str(new_version.normalize())) ) if commit: - ctx.run("git add {0}".format(version_file.as_posix())) + ctx.run(f"git add {version_file.as_posix()}") log("Committing...") ctx.run('git commit -s -m "Bumped version."') + return str(new_version) diff --git a/tasks/vendoring/__init__.py b/tasks/vendoring/__init__.py index 8329ba4e..d5d27417 100644 --- a/tasks/vendoring/__init__.py +++ b/tasks/vendoring/__init__.py @@ -1,12 +1,12 @@ -# -*- coding=utf-8 -*- # Taken from pip # see https://github.com/pypa/pip/blob/95bcf8c5f6394298035a7332c441868f3b0169f4/tasks/vendoring/__init__.py """"Vendoring script, python 3.5 needed""" -import io +import itertools +import os import re import shutil -import sys + # from tempfile import TemporaryDirectory import tarfile import zipfile @@ -19,78 +19,74 @@ import requests from urllib3.util import parse_url as urllib3_parse -from pipenv.utils import mkdir_p -from pipenv.vendor.vistir.compat import NamedTemporaryFile, TemporaryDirectory +from tempfile import TemporaryDirectory from pipenv.vendor.vistir.contextmanagers import open_file import pipenv.vendor.parse as parse -TASK_NAME = 'update' +TASK_NAME = "update" LIBRARY_DIRNAMES = { - 'requirements-parser': 'requirements', - 'backports.shutil_get_terminal_size': 'backports/shutil_get_terminal_size', - 'backports.weakref': 'backports/weakref', - 'backports.functools_lru_cache': 'backports/functools_lru_cache', - 'shutil_backports': 'backports/shutil_get_terminal_size', - 'python-dotenv': 'dotenv', - 'pip-tools': 'piptools', - 'setuptools': 'pkg_resources', - 'msgpack-python': 'msgpack', - 'attrs': 'attr', - 'enum': 'backports/enum' + "requirements-parser": "requirements", + "backports.shutil_get_terminal_size": "backports/shutil_get_terminal_size", + "backports.weakref": "backports/weakref", + "backports.functools_lru_cache": "backports/functools_lru_cache", + "python-dotenv": "dotenv", + "pip-tools": "piptools", + "setuptools": "pkg_resources", + "msgpack-python": "msgpack", + "attrs": "attr", + "enum": "backports/enum", } -PY2_DOWNLOAD = ['enum34'] +PY2_DOWNLOAD = ["enum34"] # from time to time, remove the no longer needed ones HARDCODED_LICENSE_URLS = { - 'pytoml': 'https://github.com/avakar/pytoml/raw/master/LICENSE', - 'cursor': 'https://raw.githubusercontent.com/GijsTimmers/cursor/master/LICENSE', - 'delegator.py': 'https://raw.githubusercontent.com/kennethreitz/delegator.py/master/LICENSE', - 'click-didyoumean': 'https://raw.githubusercontent.com/click-contrib/click-didyoumean/master/LICENSE', - 'click-completion': 'https://raw.githubusercontent.com/click-contrib/click-completion/master/LICENSE', - 'parse': 'https://raw.githubusercontent.com/techalchemy/parse/master/LICENSE', - 'semver': 'https://raw.githubusercontent.com/k-bx/python-semver/master/LICENSE.txt', - 'crayons': 'https://raw.githubusercontent.com/kennethreitz/crayons/master/LICENSE', - 'pip-tools': 'https://raw.githubusercontent.com/jazzband/pip-tools/master/LICENSE', - 'pytoml': 'https://github.com/avakar/pytoml/raw/master/LICENSE', - 'webencodings': 'https://github.com/SimonSapin/python-webencodings/raw/' - 'master/LICENSE', - 'requirementslib': 'https://github.com/techalchemy/requirementslib/raw/master/LICENSE', - 'distlib': 'https://github.com/vsajip/distlib/raw/master/LICENSE.txt', - 'pythonfinder': 'https://raw.githubusercontent.com/techalchemy/pythonfinder/master/LICENSE.txt', - 'pyparsing': 'https://raw.githubusercontent.com/pyparsing/pyparsing/master/LICENSE', - 'resolvelib': 'https://raw.githubusercontent.com/sarugaku/resolvelib/master/LICENSE' + "cursor": "https://raw.githubusercontent.com/GijsTimmers/cursor/master/LICENSE", + "delegator.py": "https://raw.githubusercontent.com/amitt001/delegator.py/master/LICENSE", + "click-didyoumean": "https://raw.githubusercontent.com/click-contrib/click-didyoumean/master/LICENSE", + "click-completion": "https://raw.githubusercontent.com/click-contrib/click-completion/master/LICENSE", + "parse": "https://raw.githubusercontent.com/techalchemy/parse/master/LICENSE", + "crayons": "https://raw.githubusercontent.com/MasterOdin/crayons/master/LICENSE", + "pip-tools": "https://raw.githubusercontent.com/jazzband/pip-tools/master/LICENSE", + "pytoml": "https://github.com/avakar/pytoml/raw/master/LICENSE", + "webencodings": "https://github.com/SimonSapin/python-webencodings/raw/" + "master/LICENSE", + "requirementslib": "https://github.com/techalchemy/requirementslib/raw/master/LICENSE", + "distlib": "https://github.com/vsajip/distlib/raw/master/LICENSE.txt", + "pythonfinder": "https://raw.githubusercontent.com/techalchemy/pythonfinder/master/LICENSE.txt", + "pyparsing": "https://raw.githubusercontent.com/pyparsing/pyparsing/master/LICENSE", + "funcsigs": "https://raw.githubusercontent.com/aliles/funcsigs/master/LICENSE", } FILE_WHITE_LIST = ( - 'Makefile', - 'vendor.txt', - 'patched.txt', - '__init__.py', - 'README.rst', - 'README.md', - 'appdirs.py', - 'safety.zip', - 'cacert.pem', - 'vendor_pip.txt', + "Makefile", + "vendor.txt", + "patched.txt", + "__init__.py", + "README.rst", + "README.md", + "appdirs.py", + "safety.zip", + "cacert.pem", + "vendor_pip.txt", ) -PATCHED_RENAMES = { - 'pip': 'notpip' -} +PATCHED_RENAMES = {"pip": "notpip"} LIBRARY_RENAMES = { - 'pip': 'pipenv.patched.notpip', + "pip": "pipenv.patched.notpip", "functools32": "pipenv.vendor.backports.functools_lru_cache", - 'enum34': 'enum', } +GLOBAL_REPLACEMENT = [ + (r"\bpip\._vendor", r"pipenv.patched.notpip._vendor"), + (r"\bpip\._internal", r"pipenv.patched.notpip._internal"), +] -LICENSE_RENAMES = { - "pythonfinder/LICENSE": "pythonfinder/pep514tools.LICENSE" -} + +LICENSE_RENAMES = {"pythonfinder/LICENSE": "pythonfinder/pep514tools.LICENSE"} def drop_dir(path): @@ -107,38 +103,65 @@ def remove_all(paths): def log(msg): - print('[vendoring.%s] %s' % (TASK_NAME, msg)) + print(f"[vendoring.{TASK_NAME}] {msg}") def _get_git_root(ctx): - return Path(ctx.run('git rev-parse --show-toplevel', hide=True).stdout.strip()) + return Path(ctx.run("git rev-parse --show-toplevel", hide=True).stdout.strip()) def _get_vendor_dir(ctx): - return _get_git_root(ctx) / 'pipenv' / 'vendor' + return _get_git_root(ctx) / "pipenv" / "vendor" def _get_patched_dir(ctx): - return _get_git_root(ctx) / 'pipenv' / 'patched' + return _get_git_root(ctx) / "pipenv" / "patched" def clean_vendor(ctx, vendor_dir): # Old _vendor cleanup - remove_all(vendor_dir.glob('*.pyc')) - log('Cleaning %s' % vendor_dir) + remove_all(vendor_dir.glob("*.pyc")) + log("Cleaning %s" % vendor_dir) for item in vendor_dir.iterdir(): if item.is_dir(): shutil.rmtree(str(item)) elif item.name not in FILE_WHITE_LIST: item.unlink() else: - log('Skipping %s' % item) + log("Skipping %s" % item) + + +def detect_all_vendored_libs(ctx): + types = ("patched", "vendor") + retval = {} + + for type_ in types: + vendor_dir = _get_vendor_dir(ctx) if type_ == "vendor" else _get_patched_dir(ctx) + + for item in vendor_dir.iterdir(): + name = None + if item.name == "__pycache__": + continue + elif item.is_dir(): + name = item.name + elif "LICENSE" in item.name or "COPYING" in item.name: + continue + elif item.name.endswith(".pyi"): + continue + elif item.name not in FILE_WHITE_LIST: + name = item.name[:-3] + if name is not None and name not in LIBRARY_RENAMES: + retval[name] = f"pipenv.{type_}.{name}" + retval.update(LIBRARY_RENAMES) + return retval def detect_vendored_libs(vendor_dir): retval = [] for item in vendor_dir.iterdir(): - if item.is_dir(): + if item.name == "__pycache__": + continue + elif item.is_dir(): retval.append(item.name) elif "LICENSE" in item.name or "COPYING" in item.name: continue @@ -149,135 +172,59 @@ def detect_vendored_libs(vendor_dir): return retval -def rewrite_imports(package_dir, vendored_libs, vendor_dir): +def rewrite_imports(package_dir, vendored_libs): for item in package_dir.iterdir(): if item.is_dir(): - rewrite_imports(item, vendored_libs, vendor_dir) - elif item.name.endswith('.py'): - rewrite_file_imports(item, vendored_libs, vendor_dir) + rewrite_imports(item, vendored_libs) + elif item.name.endswith(".py"): + rewrite_file_imports(item, vendored_libs) -def rewrite_file_imports(item, vendored_libs, vendor_dir): +def rewrite_file_imports(item, vendored_libs): """Rewrite 'import xxx' and 'from xxx import' for vendored_libs""" # log('Reading file: %s' % item) try: - text = item.read_text(encoding='utf-8') + text = item.read_text(encoding="utf-8") except UnicodeDecodeError: - text = item.read_text(encoding='cp1252') - renames = LIBRARY_RENAMES - for k in LIBRARY_RENAMES.keys(): - if k not in vendored_libs: - vendored_libs.append(k) - for lib in vendored_libs: - to_lib = lib - if lib in renames: - to_lib = renames[lib] + text = item.read_text(encoding="cp1252") + + for lib, to_lib in vendored_libs.items(): text = re.sub( - r'([\n\s]*)import %s([\n\s\.]+)' % lib, - r'\1import %s\2' % to_lib, - text, + r"^(?m)(\s*)import %s((?:\.\S*)?\s+as)" % lib, r"\1import %s\2" % to_lib, text, ) - text = re.sub( - r'([\n\s]*)from %s([\s\.])+' % lib, - r'\1from %s\2' % to_lib, - text, - ) - text = re.sub( - r"(\n\s*)__import__\('%s([\s'\.])+" % lib, - r"\1__import__('%s\2" % to_lib, - text, - ) - item.write_text(text, encoding='utf-8') + text = re.sub(r"^(?m)(\s*)from %s([\s\.]+)" % lib, r"\1from %s\2" % to_lib, text) + text = re.sub(r"^(?m)(\s*)import %s(\s*[,\n#])" % lib, r"\1import %s as %s\2" % (to_lib, lib), text) + for pattern, sub in GLOBAL_REPLACEMENT: + text = re.sub(pattern, sub, text) + item.write_text(text, encoding="utf-8") def apply_patch(ctx, patch_file_path): - log('Applying patch %s' % patch_file_path.name) - ctx.run('git apply --ignore-whitespace --verbose %s' % patch_file_path) + log("Applying patch %s" % patch_file_path.name) + ctx.run("git apply --ignore-whitespace --verbose %s" % patch_file_path) -@invoke.task -def update_safety(ctx): - ignore_subdeps = ['pip', 'pip-egg-info', 'bin'] - ignore_files = ['pip-delete-this-directory.txt', 'PKG-INFO'] - vendor_dir = _get_patched_dir(ctx) - log('Using vendor dir: %s' % vendor_dir) - log('Downloading safety package files...') - build_dir = vendor_dir / 'build' - download_dir = TemporaryDirectory(prefix='pipenv-', suffix='-safety') - if build_dir.exists() and build_dir.is_dir(): - drop_dir(build_dir) - - ctx.run( - 'pip download -b {0} --no-binary=:all: --no-clean -d {1} safety pyyaml'.format( - str(build_dir), str(download_dir.name), - ) - ) - safety_dir = build_dir / 'safety' - yaml_build_dir = build_dir / 'pyyaml' - main_file = safety_dir / '__main__.py' - main_content = """ -import sys -yaml_lib = 'yaml{0}'.format(sys.version_info[0]) -locals()[yaml_lib] = __import__(yaml_lib) -sys.modules['yaml'] = sys.modules[yaml_lib] -from safety.cli import cli - -# Disable insecure warnings. -import urllib3 -from urllib3.exceptions import InsecureRequestWarning -urllib3.disable_warnings(InsecureRequestWarning) - -cli(prog_name="safety") - """.strip() - with open(str(main_file), 'w') as fh: - fh.write(main_content) - - with ctx.cd(str(safety_dir)): - ctx.run('pip install --no-compile --no-binary=:all: -t . .') - safety_dir = safety_dir.absolute() - yaml_dir = safety_dir / 'yaml' - if yaml_dir.exists(): - version_choices = ['2', '3'] - version_choices.remove(str(sys.version_info[0])) - mkdir_p(str(safety_dir / 'yaml{0}'.format(sys.version_info[0]))) - for fn in yaml_dir.glob('*.py'): - fn.rename(str(safety_dir.joinpath('yaml{0}'.format(sys.version_info[0]), fn.name))) - if version_choices[0] == '2': - lib = yaml_build_dir / 'lib' / 'yaml' - else: - lib = yaml_build_dir / 'lib3' / 'yaml' - shutil.copytree(str(lib.absolute()), str(safety_dir / 'yaml{0}'.format(version_choices[0]))) - requests_dir = safety_dir / 'requests' - cacert = vendor_dir / 'requests' / 'cacert.pem' - if not cacert.exists(): - from pipenv.vendor import requests - cacert = Path(requests.certs.where()) - target_cert = requests_dir / 'cacert.pem' - target_cert.write_bytes(cacert.read_bytes()) - ctx.run("sed -i 's/r = requests.get(url=url, timeout=REQUEST_TIMEOUT, headers=headers)/r = requests.get(url=url, timeout=REQUEST_TIMEOUT, headers=headers, verify=False)/g' {0}".format(str(safety_dir / 'safety' / 'safety.py'))) - for egg in safety_dir.glob('*.egg-info'): - drop_dir(egg.absolute()) - for dep in ignore_subdeps: - dep_dir = safety_dir / dep - if dep_dir.exists(): - drop_dir(dep_dir) - for dep in ignore_files: - fn = safety_dir / dep - if fn.exists(): - fn.unlink() - zip_name = '{0}/safety'.format(str(vendor_dir)) - shutil.make_archive(zip_name, format='zip', root_dir=str(safety_dir), base_dir='./') - drop_dir(build_dir) - download_dir.cleanup() +def _recursive_write_to_zip(zf, path, root=None): + if path == Path(zf.filename): + return + if root is None: + if not path.is_dir(): + raise ValueError('root is required for non-directory path') + root = path + if not path.is_dir(): + zf.write(str(path), str(path.relative_to(root))) + return + for c in path.iterdir(): + _recursive_write_to_zip(zf, c, root) def rename_if_needed(ctx, vendor_dir, item): - rename_dict = LIBRARY_RENAMES if vendor_dir.name != 'patched' else PATCHED_RENAMES + rename_dict = LIBRARY_RENAMES if vendor_dir.name != "patched" else PATCHED_RENAMES new_path = None if item.name in rename_dict or item.name in LIBRARY_DIRNAMES: new_name = rename_dict.get(item.name, LIBRARY_DIRNAMES.get(item.name)) new_path = item.parent / new_name - log('Renaming %s => %s' % (item.name, new_path)) + log(f"Renaming {item.name} => {new_path}") # handle existing directories try: item.rename(str(new_path)) @@ -287,67 +234,118 @@ def rename_if_needed(ctx, vendor_dir, item): def write_backport_imports(ctx, vendor_dir): - backport_dir = vendor_dir / 'backports' + backport_dir = vendor_dir / "backports" if not backport_dir.exists(): return - backport_init = backport_dir / '__init__.py' + backport_init = backport_dir / "__init__.py" backport_libs = detect_vendored_libs(backport_dir) init_py_lines = backport_init.read_text().splitlines() for lib in backport_libs: - lib_line = 'from . import {0}'.format(lib) + lib_line = f"from . import {lib}" if lib_line not in init_py_lines: - log('Adding backport %s to __init__.py exports' % lib) + log("Adding backport %s to __init__.py exports" % lib) init_py_lines.append(lib_line) - backport_init.write_text('\n'.join(init_py_lines) + '\n') + backport_init.write_text("\n".join(init_py_lines) + "\n") def _ensure_package_in_requirements(ctx, requirements_file, package): requirement = None - log('using requirements file: %s' % requirements_file) + log("using requirements file: %s" % requirements_file) req_file_lines = [l for l in requirements_file.read_text().splitlines()] if package: match = [r for r in req_file_lines if r.strip().lower().startswith(package)] matched_req = None if match: for m in match: - specifiers = [m.index(s) for s in ['>', '<', '=', '~'] if s in m] - if m.lower() == package or (specifiers and m[:min(specifiers)].lower() == package): - matched_req = "{0}".format(m) + specifiers = [m.index(s) for s in [">", "<", "=", "~"] if s in m] + if m.lower() == package or ( + specifiers and m[: min(specifiers)].lower() == package + ): + matched_req = f"{m}" requirement = matched_req log("Matched req: %r" % matched_req) if not matched_req: - req_file_lines.append("{0}".format(package)) + req_file_lines.append(f"{package}") log("Writing requirements file: %s" % requirements_file) - requirements_file.write_text('\n'.join(req_file_lines)) - requirement = "{0}".format(package) + requirements_file.write_text("\n".join(req_file_lines)) + requirement = f"{package}" return requirement +def install_pyyaml(ctx, vendor_dir): + with TemporaryDirectory(prefix="pipenv-", suffix="-yaml") as download_dir: + pip_command = "pip download --no-binary=:all: --no-clean --no-deps -d {} pyyaml".format( + download_dir + ) + log(f"downloading deps via pip: {pip_command}") + ctx.run(pip_command) + downloaded = next(Path(download_dir).glob("*.tar.gz")) + with tarfile.open(downloaded, mode="r:gz") as tf: + tf.extractall(download_dir) + extracted = next((p for p in downloaded.parent.iterdir() if p != downloaded)) + yaml_dir = vendor_dir / "yaml" + path_dict = { + "current_path": extracted / "lib/yaml", + "destination": vendor_dir / "yaml3", + } + if yaml_dir.exists(): + drop_dir(yaml_dir) + path_dict["current_path"].rename(path_dict["destination"]) + path_dict["destination"].joinpath("LICENSE").write_text(extracted.joinpath("LICENSE").read_text()) + + def install(ctx, vendor_dir, package=None): - requirements_file = vendor_dir / "{0}.txt".format(vendor_dir.name) - requirement = "-r {0}".format(requirements_file.as_posix()) - log('Using requirements file: %s' % requirement) + requirements_file = vendor_dir / f"{vendor_dir.name}.txt" + requirement = f"-r {requirements_file.as_posix()}" + log("Using requirements file: %s" % requirement) if package: requirement = _ensure_package_in_requirements(ctx, requirements_file, package) # We use --no-deps because we want to ensure that all of our dependencies # are added to vendor.txt, this includes all dependencies recursively up # the chain. ctx.run( - 'pip install -t {0} --no-compile --no-deps --upgrade {1}'.format( - vendor_dir.as_posix(), - requirement, + "pip install -t {} --no-compile --no-deps --upgrade {}".format( + vendor_dir.as_posix(), requirement, ) ) + # read licenses from distinfo files if possible + for path in vendor_dir.glob("*.dist-info"): + pkg, _, _ = path.stem.rpartition("-") + license_file = path / "LICENSE" + if not license_file.exists(): + continue + if vendor_dir.joinpath(pkg).exists(): + vendor_dir.joinpath(pkg).joinpath("LICENSE").write_text( + license_file.read_text() + ) + elif vendor_dir.joinpath(f"{pkg}.py").exists(): + vendor_dir.joinpath(f"{pkg}.LICENSE").write_text( + license_file.read_text() + ) + else: + pkg = pkg.replace("-", "?").replace("_", "?") + matched_path = next( + iter(pth for pth in vendor_dir.glob(f"{pkg}*")), None + ) + if matched_path is not None: + if matched_path.is_dir(): + target = vendor_dir.joinpath(matched_path).joinpath("LICENSE") + else: + target = vendor_dir.joinpath(f"{matched_path}.LICENSE") + target.write_text( + license_file.read_text() + ) def post_install_cleanup(ctx, vendor_dir): - remove_all(vendor_dir.glob('*.dist-info')) - remove_all(vendor_dir.glob('*.egg-info')) + remove_all(vendor_dir.glob("*.dist-info")) + remove_all(vendor_dir.glob("*.egg-info")) # Cleanup setuptools unneeded parts - drop_dir(vendor_dir / 'bin') - drop_dir(vendor_dir / 'tests') - remove_all(vendor_dir.glob('toml.py')) + drop_dir(vendor_dir / "bin") + drop_dir(vendor_dir / "tests") + drop_dir(vendor_dir / "shutil_backports") + remove_all(vendor_dir.glob("toml.py")) @invoke.task @@ -357,27 +355,27 @@ def apply_patches(ctx, patched=False, pre=False): else: vendor_dir = _get_vendor_dir(ctx) log("Applying pre-patches...") - patch_dir = Path(__file__).parent / 'patches' / vendor_dir.name + patch_dir = Path(__file__).parent / "patches" / vendor_dir.name if pre: if not patched: pass - for patch in patch_dir.glob('*.patch'): - if not patch.name.startswith('_post'): + for patch in patch_dir.glob("*.patch"): + if not patch.name.startswith("_post"): apply_patch(ctx, patch) else: - patches = patch_dir.glob('*.patch' if not patched else '_post*.patch') + patches = patch_dir.glob("*.patch" if not patched else "_post*.patch") for patch in patches: apply_patch(ctx, patch) def vendor(ctx, vendor_dir, package=None, rewrite=True): - log('Reinstalling vendored libraries') - is_patched = vendor_dir.name == 'patched' + log("Reinstalling vendored libraries") + is_patched = vendor_dir.name == "patched" install(ctx, vendor_dir, package=package) - log('Running post-install cleanup...') + log("Running post-install cleanup...") post_install_cleanup(ctx, vendor_dir) # Detect the vendored packages/modules - vendored_libs = detect_vendored_libs(_get_vendor_dir(ctx)) + vendored_libs = detect_all_vendored_libs(ctx) log("Detected vendored libraries: %s" % ", ".join(vendored_libs)) # Apply pre-patches @@ -385,58 +383,61 @@ def vendor(ctx, vendor_dir, package=None, rewrite=True): if is_patched: apply_patches(ctx, patched=is_patched, pre=True) log("Removing scandir library files...") - remove_all(vendor_dir.glob('*.so')) - drop_dir(vendor_dir / 'setuptools') - drop_dir(vendor_dir / 'pkg_resources' / '_vendor') - drop_dir(vendor_dir / 'pkg_resources' / 'extern') - drop_dir(vendor_dir / 'bin') + for extension in ("*.so", "*.pyd", "*.egg-info", "*.dist-info"): + remove_all(vendor_dir.glob(extension)) + for dirname in ("setuptools", "pkg_resources/_vendor", "pkg_resources/extern", "bin"): + drop_dir(vendor_dir / dirname) # Global import rewrites - log('Renaming specified libs...') + log("Renaming specified libs...") for item in vendor_dir.iterdir(): if item.is_dir(): if rewrite and not package or (package and item.name.lower() in package): - log('Rewriting imports for %s...' % item) - rewrite_imports(item, vendored_libs, vendor_dir) + log("Rewriting imports for %s..." % item) + rewrite_imports(item, vendored_libs) rename_if_needed(ctx, vendor_dir, item) elif item.name not in FILE_WHITE_LIST: if rewrite and not package or (package and item.stem.lower() in package): - rewrite_file_imports(item, vendored_libs, vendor_dir) + rewrite_file_imports(item, vendored_libs) write_backport_imports(ctx, vendor_dir) if not package: apply_patches(ctx, patched=is_patched, pre=False) if is_patched: - piptools_vendor = vendor_dir / 'piptools' / '_vendored' + piptools_vendor = vendor_dir / "piptools" / "_vendored" if piptools_vendor.exists(): drop_dir(piptools_vendor) - msgpack = vendor_dir / 'notpip' / '_vendor' / 'msgpack' + msgpack = vendor_dir / "notpip" / "_vendor" / "msgpack" if msgpack.exists(): - remove_all(msgpack.glob('*.so')) + remove_all(msgpack.glob("*.so")) @invoke.task -def redo_imports(ctx, library): - vendor_dir = _get_vendor_dir(ctx) - log('Using vendor dir: %s' % vendor_dir) - vendored_libs = detect_vendored_libs(vendor_dir) - item = vendor_dir / library - library_name = vendor_dir / '{0}.py'.format(library) - log("Detected vendored libraries: %s" % ", ".join(vendored_libs)) - log('Rewriting imports for %s...' % item) - if item.is_dir(): - rewrite_imports(item, vendored_libs, vendor_dir) +def redo_imports(ctx, library, vendor_dir=None): + if vendor_dir is None: + vendor_dir = _get_vendor_dir(ctx) else: - rewrite_file_imports(library_name, vendored_libs, vendor_dir) + vendor_dir = Path(vendor_dir).absolute() + log("Using vendor dir: %s" % vendor_dir) + vendored_libs = detect_all_vendored_libs(ctx) + item = vendor_dir / library + library_name = vendor_dir / f"{library}.py" + log("Detected vendored libraries: %s" % ", ".join(vendored_libs)) + log("Rewriting imports for %s..." % item) + if item.is_dir(): + rewrite_imports(item, vendored_libs) + else: + rewrite_file_imports(library_name, vendored_libs) @invoke.task def rewrite_all_imports(ctx): vendor_dir = _get_vendor_dir(ctx) - log('Using vendor dir: %s' % vendor_dir) - vendored_libs = detect_vendored_libs(vendor_dir) + patched_dir = _get_patched_dir(ctx) + log("Using vendor dir: %s" % vendor_dir) + vendored_libs = detect_all_vendored_libs(ctx) log("Detected vendored libraries: %s" % ", ".join(vendored_libs)) log("Rewriting all imports related to vendored libs") - for item in vendor_dir.iterdir(): + for item in itertools.chain(patched_dir.iterdir(), vendor_dir.iterdir()): if item.is_dir(): rewrite_imports(item, vendored_libs) elif item.name not in FILE_WHITE_LIST: @@ -444,15 +445,27 @@ def rewrite_all_imports(ctx): @invoke.task -def packages_missing_licenses(ctx, vendor_dir=None, requirements_file='vendor.txt', package=None): +def packages_missing_licenses( + ctx, vendor_dir=None, requirements_file="vendor.txt", package=None +): if not vendor_dir: vendor_dir = _get_vendor_dir(ctx) - requirements = vendor_dir.joinpath(requirements_file).read_text().splitlines() + if package is not None: + requirements = [package] + else: + requirements = vendor_dir.joinpath(requirements_file).read_text().splitlines() new_requirements = [] - LICENSES = ["LICENSE-MIT", "LICENSE", "LICENSE.txt", "LICENSE.APACHE", "LICENSE.BSD"] + LICENSE_EXTS = ("rst", "txt", "APACHE", "BSD", "md") + LICENSES = [ + ".".join(lic) + for lic in itertools.product(("LICENSE", "LICENSE-MIT"), LICENSE_EXTS) + ] for i, req in enumerate(requirements): - pkg = req.strip().split("=")[0] - possible_pkgs = [pkg, pkg.replace('-', '_')] + if req.startswith("git+"): + pkg = req.strip().split("#egg=")[1] + else: + pkg = req.strip().split("=")[0] + possible_pkgs = [pkg, pkg.replace("-", "_")] match_found = False if pkg in PY2_DOWNLOAD: match_found = True @@ -461,7 +474,7 @@ def packages_missing_licenses(ctx, vendor_dir=None, requirements_file='vendor.tx possible_pkgs.append(LIBRARY_DIRNAMES[pkg]) for pkgpath in possible_pkgs: pkgpath = vendor_dir.joinpath(pkgpath) - py_path = pkgpath.parent / "{0}.py".format(pkgpath.stem) + py_path = pkgpath.parent / f"{pkgpath.stem}.py" if pkgpath.exists() and pkgpath.is_dir(): for license_path in LICENSES: license_path = pkgpath.joinpath(license_path) @@ -471,7 +484,7 @@ def packages_missing_licenses(ctx, vendor_dir=None, requirements_file='vendor.tx break elif pkgpath.exists() or py_path.exists(): for license_path in LICENSES: - license_name = "{0}.{1}".format(pkgpath.stem, license_path) + license_name = f"{pkgpath.stem}.{license_path}" license_path = pkgpath.parent / license_name if license_path.exists(): match_found = True @@ -482,48 +495,49 @@ def packages_missing_licenses(ctx, vendor_dir=None, requirements_file='vendor.tx if match_found: continue else: - # log("%s: No license found in %s" % (pkg, pkgpath)) + # log("%s: No license found in %s" % (pkg, pkgpath)) new_requirements.append(req) return new_requirements @invoke.task def download_licenses( - ctx, vendor_dir=None, requirements_file='vendor.txt', package=None, only=False, - patched=False + ctx, + vendor_dir=None, + requirements_file="vendor.txt", + package=None, + only=False, + patched=False, ): - log('Downloading licenses') + log("Downloading licenses") if not vendor_dir: if patched: vendor_dir = _get_patched_dir(ctx) - requirements_file = 'patched.txt' + requirements_file = "patched.txt" else: vendor_dir = _get_vendor_dir(ctx) requirements_file = vendor_dir / requirements_file - requirements = packages_missing_licenses(ctx, vendor_dir, requirements_file, package=package) - - with NamedTemporaryFile(prefix="pipenv", suffix="vendor-reqs", delete=False, mode="w") as fh: - fh.write("\n".join(requirements)) - new_requirements_file = fh.name - new_requirements_file = Path(new_requirements_file) + requirements = packages_missing_licenses( + ctx, vendor_dir, requirements_file, package=package + ) log(requirements) - tmp_dir = vendor_dir / '__tmp__' + tmp_dir = vendor_dir / "__tmp__" # TODO: Fix this whenever it gets sorted out (see https://github.com/pypa/pip/issues/5739) cmd = "pip download --no-binary :all: --only-binary requests_download --no-deps" enum_cmd = "pip download --no-deps" - ctx.run('pip install flit') # needed for the next step - for req in requirements_file.read_text().splitlines(): + ctx.run("pip install flit") # needed for the next step + for req in requirements: if req.startswith("enum34"): - exe_cmd = "{0} -d {1} {2}".format(enum_cmd, tmp_dir.as_posix(), req) + exe_cmd = f"{enum_cmd} -d {tmp_dir.as_posix()} {req}" else: - exe_cmd = "{0} --no-build-isolation -d {1} {2}".format( + exe_cmd = "{} --no-build-isolation -d {} {}".format( cmd, tmp_dir.as_posix(), req ) try: ctx.run(exe_cmd) except invoke.exceptions.UnexpectedExit as e: if "Disabling PEP 517 processing is invalid" not in e.result.stderr: - log("WARNING: Failed to download license for {0}".format(req)) + log(f"WARNING: Failed to download license for {req}") continue parse_target = ( "Disabling PEP 517 processing is invalid: project specifies a build " @@ -534,31 +548,28 @@ def download_licenses( if backend is not None: if "." in backend: backend, _, _ = backend.partition(".") - ctx.run("pip install {0}".format(backend)) + ctx.run(f"pip install {backend}") ctx.run( - "{0} --no-build-isolation -d {1} {2}".format( - cmd, tmp_dir.as_posix(), req - ) + f"{cmd} --no-build-isolation -d {tmp_dir.as_posix()} {req}" ) for sdist in tmp_dir.iterdir(): extract_license(vendor_dir, sdist) - new_requirements_file.unlink() drop_dir(tmp_dir) def extract_license(vendor_dir, sdist): - if sdist.stem.endswith('.tar'): + if sdist.stem.endswith(".tar"): ext = sdist.suffix[1:] - with tarfile.open(sdist, mode='r:{}'.format(ext)) as tar: + with tarfile.open(sdist, mode=f"r:{ext}") as tar: found = find_and_extract_license(vendor_dir, tar, tar.getmembers()) - elif sdist.suffix in ('.zip', '.whl'): + elif sdist.suffix in (".zip", ".whl"): with zipfile.ZipFile(sdist) as zip: found = find_and_extract_license(vendor_dir, zip, zip.infolist()) else: - raise NotImplementedError('new sdist type!') + raise NotImplementedError("new sdist type!") if not found: - log('License not found in {}, will download'.format(sdist.name)) + log(f"License not found in {sdist.name}, will download") license_fallback(vendor_dir, sdist.name) @@ -569,10 +580,10 @@ def find_and_extract_license(vendor_dir, tar, members): name = member.name except AttributeError: # zipfile name = member.filename - if 'LICENSE' in name or 'COPYING' in name: - if '/test' in name: + if "LICENSE" in name or "COPYING" in name: + if "/test" in name: # some testing licenses in hml5lib and distlib - log('Ignoring {}'.format(name)) + log(f"Ignoring {name}") continue found = True extract_license_member(vendor_dir, tar, member, name) @@ -583,13 +594,13 @@ def license_fallback(vendor_dir, sdist_name): """Hardcoded license URLs. Check when updating if those are still needed""" libname = libname_from_dir(sdist_name) if libname not in HARDCODED_LICENSE_URLS: - raise ValueError('No hardcoded URL for {} license'.format(libname)) + raise ValueError(f"No hardcoded URL for {libname} license") url = HARDCODED_LICENSE_URLS[libname] - _, _, name = url.rpartition('/') + _, _, name = url.rpartition("/") dest = license_destination(vendor_dir, libname, name) - r = requests.get(url, allow_redirects=True) - log('Downloading {}'.format(url)) + r = requests.get(url, allow_redirects=True, verify=False) + log(f"Downloading {url}") r.raise_for_status() dest.write_bytes(r.content) @@ -597,11 +608,11 @@ def license_fallback(vendor_dir, sdist_name): def libname_from_dir(dirname): """Reconstruct the library name without it's version""" parts = [] - for part in dirname.split('-'): + for part in dirname.split("-"): if part[0].isdigit(): break parts.append(part) - return '-'.join(parts) + return "-".join(parts) def license_destination(vendor_dir, libname, filename): @@ -609,10 +620,10 @@ def license_destination(vendor_dir, libname, filename): normal = vendor_dir / libname if normal.is_dir(): return normal / filename - lowercase = vendor_dir / libname.lower().replace('-', '_') + lowercase = vendor_dir / libname.lower().replace("-", "_") if lowercase.is_dir(): return lowercase / filename - rename_dict = LIBRARY_RENAMES if vendor_dir.name != 'patched' else PATCHED_RENAMES + rename_dict = LIBRARY_RENAMES if vendor_dir.name != "patched" else PATCHED_RENAMES # Short circuit all logic if we are renaming the whole library if libname in rename_dict: return vendor_dir / rename_dict[libname] / filename @@ -620,15 +631,15 @@ def license_destination(vendor_dir, libname, filename): override = vendor_dir / LIBRARY_DIRNAMES[libname] if not override.exists() and override.parent.exists(): # for flattened subdeps, specifically backports/weakref.py - return ( - vendor_dir / override.parent - ) / '{0}.{1}'.format(override.name, filename) + return (vendor_dir / override.parent) / "{}.{}".format( + override.name, filename + ) license_path = Path(LIBRARY_DIRNAMES[libname]) / filename if license_path.as_posix() in LICENSE_RENAMES: return vendor_dir / LICENSE_RENAMES[license_path.as_posix()] return vendor_dir / LIBRARY_DIRNAMES[libname] / filename # fallback to libname.LICENSE (used for nondirs) - return vendor_dir / '{}.{}'.format(libname, filename) + return vendor_dir / f"{libname}.{filename}" def extract_license_member(vendor_dir, tar, member, name): @@ -636,7 +647,7 @@ def extract_license_member(vendor_dir, tar, member, name): dirname = list(mpath.parents)[-2].name # -1 is . libname = libname_from_dir(dirname) dest = license_destination(vendor_dir, libname, mpath.name) - log('Extracting {} into {}'.format(name, dest)) + log(f"Extracting {name} into {dest}") try: fileobj = tar.extractfile(member) dest.write_bytes(fileobj.read()) @@ -645,18 +656,20 @@ def extract_license_member(vendor_dir, tar, member, name): @invoke.task() -def generate_patch(ctx, package_path, patch_description, base='HEAD'): +def generate_patch(ctx, package_path, patch_description, base="HEAD"): pkg = Path(package_path) - if len(pkg.parts) != 2 or pkg.parts[0] not in ('vendor', 'patched'): - raise ValueError('example usage: generate-patch patched/piptools some-description') + if len(pkg.parts) != 2 or pkg.parts[0] not in ("vendor", "patched"): + raise ValueError( + "example usage: generate-patch patched/piptools some-description" + ) if patch_description: - patch_fn = '{0}-{1}.patch'.format(pkg.parts[1], patch_description) + patch_fn = f"{pkg.parts[1]}-{patch_description}.patch" else: - patch_fn = '{0}.patch'.format(pkg.parts[1]) - command = 'git diff {base} -p {root} > {out}'.format( + patch_fn = f"{pkg.parts[1]}.patch" + command = "git diff {base} -p {root} > {out}".format( base=base, - root=Path('pipenv').joinpath(pkg), - out=Path(__file__).parent.joinpath('patches', pkg.parts[0], patch_fn), + root=Path("pipenv").joinpath(pkg), + out=Path(__file__).parent.joinpath("patches", pkg.parts[0], patch_fn), ) with ctx.cd(str(_get_git_root(ctx))): log(command) @@ -666,48 +679,104 @@ def generate_patch(ctx, package_path, patch_description, base='HEAD'): @invoke.task() def update_pip_deps(ctx): patched_dir = _get_patched_dir(ctx) - base_vendor_dir = _get_vendor_dir(ctx) - base_vendor_file = base_vendor_dir / "vendor_pip.txt" pip_dir = patched_dir / "notpip" vendor_dir = pip_dir / "_vendor" - vendor_file = vendor_dir / "vendor.txt" - vendor_file.write_bytes(base_vendor_file.read_bytes()) download_licenses(ctx, vendor_dir) -@invoke.task(name=TASK_NAME) -def main(ctx, package=None): +@invoke.task +def download_all_licenses(ctx, include_pip=False): vendor_dir = _get_vendor_dir(ctx) patched_dir = _get_patched_dir(ctx) - log('Using vendor dir: %s' % vendor_dir) + download_licenses(ctx, vendor_dir) + download_licenses(ctx, patched_dir, "patched.txt") + if include_pip: + update_pip_deps(ctx) + + +def unpin_file(contents): + requirements = [] + for line in contents.splitlines(): + if "==" in line: + line, _, _ = line.strip().partition("=") + if not line.startswith("#"): + requirements.append(line) + return "\n".join(sorted(requirements)) + + +def unpin_and_copy_requirements(ctx, requirement_file, name="requirements.txt"): + tempdir = TemporaryDirectory() + target = Path(tempdir.name).joinpath("requirements.txt") + contents = unpin_file(requirement_file.read_text()) + target.write_text(contents) + env = { + "PIPENV_IGNORE_VIRTUALENVS": "1", + "PIPENV_NOSPIN": "1", + "PIPENV_PYTHON": "3.6", + } + with ctx.cd(tempdir.name): + ctx.run(f"pipenv install -r {target.as_posix()}", env=env, hide=True) + result = ctx.run("pipenv lock -r", env=env, hide=True).stdout.strip() + ctx.run("pipenv --rm", env=env, hide=True) + result = list(sorted(line.strip() for line in result.splitlines()[1:])) + new_requirements = requirement_file.parent.joinpath(name) + requirement_file.rename( + requirement_file.parent.joinpath(f"{name}.bak") + ) + new_requirements.write_text("\n".join(result)) + return result + + +@invoke.task +def unpin_and_update_vendored(ctx, vendor=True, patched=False): + if vendor: + vendor_file = _get_vendor_dir(ctx) / "vendor.txt" + unpin_and_copy_requirements(ctx, vendor_file, name="vendor.txt") + if patched: + patched_file = _get_patched_dir(ctx) / "patched.txt" + unpin_and_copy_requirements(ctx, patched_file, name="patched.txt") + + +@invoke.task(name=TASK_NAME) +def main(ctx, package=None, type=None): + vendor_dir = _get_vendor_dir(ctx) + patched_dir = _get_patched_dir(ctx) + if type == "vendor": + target_dirs = [vendor_dir] + elif type == "patched": + target_dirs = [patched_dir] + else: + target_dirs = [vendor_dir, patched_dir] if package: + log("Using vendor dir: %s" % vendor_dir) vendor(ctx, vendor_dir, package=package) download_licenses(ctx, vendor_dir, package=package) log("Vendored %s" % package) return - clean_vendor(ctx, vendor_dir) - clean_vendor(ctx, patched_dir) - vendor(ctx, vendor_dir) - vendor(ctx, patched_dir, rewrite=True) - download_licenses(ctx, vendor_dir) - download_licenses(ctx, patched_dir, 'patched.txt') - for pip_dir in [patched_dir / 'notpip']: - _vendor_dir = pip_dir / '_vendor' - vendor_src_file = vendor_dir / 'vendor_pip.txt' - vendor_file = _vendor_dir / 'vendor.txt' - vendor_file.write_bytes(vendor_src_file.read_bytes()) - download_licenses(ctx, _vendor_dir) - # from .vendor_passa import vendor_passa - # log("Vendoring passa...") - # vendor_passa(ctx) - # update_safety(ctx) - log('Revendoring complete') + for package_dir in target_dirs: + clean_vendor(ctx, package_dir) + if package_dir == patched_dir: + install_pyyaml(ctx, patched_dir) + vendor(ctx, patched_dir, rewrite=True) + else: + vendor(ctx, package_dir) + req_txt = "vendor.txt" if package_dir == vendor_dir else "patched.txt" + download_licenses(ctx, package_dir, req_txt) + if package_dir == patched_dir: + update_pip_deps(ctx) + log("Revendoring complete") + + +@invoke.task +def install_yaml(ctx): + patched_dir = _get_patched_dir(ctx) + install_pyyaml(ctx, patched_dir) @invoke.task def vendor_artifact(ctx, package, version=None): - simple = requests.get("https://pypi.org/simple/{0}/".format(package)) - pkg_str = "{0}-{1}".format(package, version) + simple = requests.get(f"https://pypi.org/simple/{package}/") + pkg_str = f"{package}-{version}" soup = bs4.BeautifulSoup(simple.content) links = [ a.attrs["href"] for a in soup.find_all("a") if a.getText().startswith(pkg_str) @@ -718,6 +787,6 @@ def vendor_artifact(ctx, package, version=None): dest_dir.mkdir() _, _, dest_path = urllib3_parse(link).path.rpartition("/") dest_file = dest_dir / dest_path - with io.open(dest_file.as_posix(), "wb") as target_handle: + with open(dest_file.as_posix(), "wb") as target_handle: with open_file(link) as fp: shutil.copyfileobj(fp, target_handle) diff --git a/tasks/vendoring/patches/patched/_post-pip-update-pep425tags.patch b/tasks/vendoring/patches/patched/_post-pip-update-pep425tags.patch deleted file mode 100644 index b4ffbc9f..00000000 --- a/tasks/vendoring/patches/patched/_post-pip-update-pep425tags.patch +++ /dev/null @@ -1,22 +0,0 @@ -diff --git a/pipenv/patched/notpip/_internal/pep425tags.py b/pipenv/patched/notpip/_internal/pep425tags.py -index 3c760ca3..3b11b965 100644 ---- a/pipenv/patched/notpip/_internal/pep425tags.py -+++ b/pipenv/patched/notpip/_internal/pep425tags.py -@@ -178,7 +178,7 @@ def is_manylinux1_compatible(): - pass - - # Check glibc version. CentOS 5 uses glibc 2.5. -- return pip._internal.utils.glibc.have_compatible_glibc(2, 5) -+ return pipenv.patched.notpip._internal.utils.glibc.have_compatible_glibc(2, 5) - - - def is_manylinux2010_compatible(): -@@ -196,7 +196,7 @@ def is_manylinux2010_compatible(): - pass - - # Check glibc version. CentOS 6 uses glibc 2.12. -- return pip._internal.utils.glibc.have_compatible_glibc(2, 12) -+ return pipenv.patched.notpip._internal.utils.glibc.have_compatible_glibc(2, 12) - - - def get_darwin_arches(major, minor, machine): diff --git a/tasks/vendoring/patches/patched/_post-pip-update-pypi-uri.patch b/tasks/vendoring/patches/patched/_post-pip-update-pypi-uri.patch deleted file mode 100644 index 93f7ccbc..00000000 --- a/tasks/vendoring/patches/patched/_post-pip-update-pypi-uri.patch +++ /dev/null @@ -1,44 +0,0 @@ -diff --git a/pipenv/patched/notpip/_vendor/distlib/index.py b/pipenv/patched/notpip/_vendor/distlib/index.py -index 2406be21..7a87cdcf 100644 ---- a/pipenv/patched/notpip/_vendor/distlib/index.py -+++ b/pipenv/patched/notpip/_vendor/distlib/index.py -@@ -22,7 +22,7 @@ from .util import cached_property, zip_dir, ServerProxy - - logger = logging.getLogger(__name__) - --DEFAULT_INDEX = 'https://pypi.python.org/pypi' -+DEFAULT_INDEX = 'https://pypi.org/pypi' - DEFAULT_REALM = 'pypi' - - class PackageIndex(object): -diff --git a/pipenv/patched/notpip/_vendor/distlib/locators.py b/pipenv/patched/notpip/_vendor/distlib/locators.py -index 5c655c3e..a7ed9469 100644 ---- a/pipenv/patched/notpip/_vendor/distlib/locators.py -+++ b/pipenv/patched/notpip/_vendor/distlib/locators.py -@@ -36,7 +36,7 @@ logger = logging.getLogger(__name__) - HASHER_HASH = re.compile(r'^(\w+)=([a-f0-9]+)') - CHARSET = re.compile(r';\s*charset\s*=\s*(.*)\s*$', re.I) - HTML_CONTENT_TYPE = re.compile('text/html|application/x(ht)?ml') --DEFAULT_INDEX = 'https://pypi.python.org/pypi' -+DEFAULT_INDEX = 'https://pypi.org/pypi' - - def get_all_distribution_names(url=None): - """ -@@ -197,7 +197,7 @@ class Locator(object): - is_downloadable = basename.endswith(self.downloadable_extensions) - if is_wheel: - compatible = is_compatible(Wheel(basename), self.wheel_tags) -- return (t.scheme == 'https', 'pypi.python.org' in t.netloc, -+ return (t.scheme == 'https', 'pypi.org' in t.netloc, - is_downloadable, is_wheel, compatible, basename) - - def prefer_url(self, url1, url2): -@@ -1049,7 +1049,7 @@ class AggregatingLocator(Locator): - # versions which don't conform to PEP 426 / PEP 440. - default_locator = AggregatingLocator( - JSONLocator(), -- SimpleScrapingLocator('https://pypi.python.org/simple/', -+ SimpleScrapingLocator('https://pypi.org/simple/', - timeout=3.0), - scheme='legacy') - diff --git a/tasks/vendoring/patches/patched/_post-pip-update-requests-imports.patch b/tasks/vendoring/patches/patched/_post-pip-update-requests-imports.patch deleted file mode 100644 index 79e12659..00000000 --- a/tasks/vendoring/patches/patched/_post-pip-update-requests-imports.patch +++ /dev/null @@ -1,21 +0,0 @@ -diff --git a/pipenv/patched/notpip/_vendor/requests/packages.py b/pipenv/patched/notpip/_vendor/requests/packages.py -index 9582fa73..258c89ed 100644 ---- a/pipenv/patched/notpip/_vendor/requests/packages.py -+++ b/pipenv/patched/notpip/_vendor/requests/packages.py -@@ -4,13 +4,13 @@ import sys - # I don't like it either. Just look the other way. :) - - for package in ('urllib3', 'idna', 'chardet'): -- vendored_package = "pip._vendor." + package -+ vendored_package = "notpip._vendor." + package - locals()[package] = __import__(vendored_package) - # This traversal is apparently necessary such that the identities are - # preserved (requests.packages.urllib3.* is urllib3.*) - for mod in list(sys.modules): - if mod == vendored_package or mod.startswith(vendored_package + '.'): -- unprefixed_mod = mod[len("pip._vendor."):] -- sys.modules['pip._vendor.requests.packages.' + unprefixed_mod] = sys.modules[mod] -+ unprefixed_mod = mod[len("notpip._vendor."):] -+ sys.modules['notpip._vendor.requests.packages.' + unprefixed_mod] = sys.modules[mod] - - # Kinda cool, though, right? diff --git a/tasks/vendoring/patches/patched/_post_pip_import.patch b/tasks/vendoring/patches/patched/_post_pip_import.patch new file mode 100644 index 00000000..26cd89af --- /dev/null +++ b/tasks/vendoring/patches/patched/_post_pip_import.patch @@ -0,0 +1,41 @@ +diff --git a/pipenv/patched/notpip/__main__.py b/pipenv/patched/notpip/__main__.py +index 204a8ca2..546caab1 100644 +--- a/pipenv/patched/notpip/__main__.py ++++ b/pipenv/patched/notpip/__main__.py +@@ -26,6 +26,7 @@ if __name__ == "__main__": + warnings.filterwarnings( + "ignore", category=DeprecationWarning, module=".*packaging\\.version" + ) ++ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))) + from pipenv.patched.notpip._internal.cli.main import main as _main + + sys.exit(_main()) +diff --git a/pipenv/patched/notpip/_internal/resolution/resolvelib/candidates.py b/pipenv/patched/notpip/_internal/resolution/resolvelib/candidates.py +index 0ba06c52..6fdb59b7 100644 +--- a/pipenv/patched/notpip/_internal/resolution/resolvelib/candidates.py ++++ b/pipenv/patched/notpip/_internal/resolution/resolvelib/candidates.py +@@ -253,7 +253,10 @@ class _InstallRequirementBackedCandidate(Candidate): + yield self._get_requires_python_dependency() + + def get_install_requirement(self) -> Optional[InstallRequirement]: +- return self._ireq ++ ireq = self._ireq ++ if self._version and ireq.req and not ireq.req.url: ++ ireq.req.specifier = SpecifierSet(f"=={self._version}") ++ return ireq + + + class LinkCandidate(_InstallRequirementBackedCandidate): +diff --git a/pipenv/patched/notpip/_internal/build_env.py b/pipenv/patched/notpip/_internal/build_env.py +index 05457c5a..d8c66b3f 100644 +--- a/pipenv/patched/notpip/_internal/build_env.py ++++ b/pipenv/patched/notpip/_internal/build_env.py +@@ -17,7 +17,7 @@ from pipenv.patched.notpip._vendor.certifi import where + from pipenv.patched.notpip._vendor.packaging.requirements import Requirement + from pipenv.patched.notpip._vendor.packaging.version import Version + +-from pipenv.patched.notpip import __file__ as pip_location ++from pip import __file__ as pip_location + from pipenv.patched.notpip._internal.cli.spinners import open_spinner + from pipenv.patched.notpip._internal.locations import get_platlib, get_prefixed_libs, get_purelib + from pipenv.patched.notpip._internal.metadata import get_environment diff --git a/tasks/vendoring/patches/patched/crayons.patch b/tasks/vendoring/patches/patched/crayons.patch index d7fa3d40..2760ca81 100644 --- a/tasks/vendoring/patches/patched/crayons.patch +++ b/tasks/vendoring/patches/patched/crayons.patch @@ -8,8 +8,9 @@ index 455d3e90..de735daf 100644 -PY3 = sys.version_info[0] >= 3 - -+import shellingham - import colorama +-import colorama ++from pipenv.vendor import shellingham ++from pipenv.vendor import colorama +PY3 = sys.version_info[0] >= 3 + diff --git a/tasks/vendoring/patches/patched/pip19.patch b/tasks/vendoring/patches/patched/pip19.patch deleted file mode 100644 index 74b98f50..00000000 --- a/tasks/vendoring/patches/patched/pip19.patch +++ /dev/null @@ -1,542 +0,0 @@ -diff --git a/pipenv/patched/pip/_internal/download.py b/pipenv/patched/pip/_internal/download.py -index 2bbe1762..872af328 100644 ---- a/pipenv/patched/pip/_internal/download.py -+++ b/pipenv/patched/pip/_internal/download.py -@@ -77,7 +77,7 @@ def user_agent(): - Return a string representing the user agent. - """ - data = { -- "installer": {"name": "pip", "version": pip.__version__}, -+ "installer": {"name": "pip", "version": pipenv.patched.notpip.__version__}, - "python": platform.python_version(), - "implementation": { - "name": platform.python_implementation(), -diff --git a/pipenv/patched/pip/_internal/index.py b/pipenv/patched/pip/_internal/index.py -index 9eda3a35..67dd952c 100644 ---- a/pipenv/patched/pip/_internal/index.py -+++ b/pipenv/patched/pip/_internal/index.py -@@ -331,6 +331,9 @@ class PackageFinder(object): - # The Session we'll use to make requests - self.session = session - -+ # Kenneth's Hack -+ self.extra = None -+ - # The valid tags to check potential found wheel candidates against - self.valid_tags = get_supported( - versions=versions, -@@ -369,6 +372,23 @@ class PackageFinder(object): - ) - return "\n".join(lines) - -+ @staticmethod -+ def get_extras_links(links): -+ requires = [] -+ extras = {} -+ -+ current_list = requires -+ -+ for link in links: -+ if not link: -+ current_list = requires -+ if link.startswith('['): -+ current_list = [] -+ extras[link[1:-1]] = current_list -+ else: -+ current_list.append(link) -+ return extras -+ - @staticmethod - def _sort_locations(locations, expand_dir=False): - # type: (Sequence[str], bool) -> Tuple[List[str], List[str]] -@@ -427,8 +447,8 @@ class PackageFinder(object): - - return files, urls - -- def _candidate_sort_key(self, candidate): -- # type: (InstallationCandidate) -> CandidateSortingKey -+ def _candidate_sort_key(self, candidate, ignore_compatibility=True): -+ # type: (InstallationCandidate, bool) -> CandidateSortingKey - """ - Function used to generate link sort key for link tuples. - The greater the return value, the more preferred it is. -@@ -448,14 +468,18 @@ class PackageFinder(object): - if candidate.location.is_wheel: - # can raise InvalidWheelFilename - wheel = Wheel(candidate.location.filename) -- if not wheel.supported(self.valid_tags): -+ if not wheel.supported(self.valid_tags) and not ignore_compatibility: - raise UnsupportedWheel( - "%s is not a supported wheel for this platform. It " - "can't be sorted." % wheel.filename - ) - if self.prefer_binary: - binary_preference = 1 -- pri = -(wheel.support_index_min(self.valid_tags)) -+ tags = self.valid_tags if not ignore_compatibility else None -+ try: -+ pri = -(wheel.support_index_min(tags=tags)) -+ except TypeError: -+ pri = -(support_num) - if wheel.build_tag is not None: - match = re.match(r'^(\d+)(.*)$', wheel.build_tag) - build_tag_groups = match.groups() -@@ -608,7 +632,10 @@ class PackageFinder(object): - - page_versions = [] - for page in self._get_pages(url_locations, project_name): -- logger.debug('Analyzing links from page %s', page.url) -+ try: -+ logger.debug('Analyzing links from page %s', page.url) -+ except AttributeError: -+ continue - with indent_log(): - page_versions.extend( - self._package_versions(page.iter_links(), search) -@@ -628,8 +655,8 @@ class PackageFinder(object): - # This is an intentional priority ordering - return file_versions + find_links_versions + page_versions - -- def find_requirement(self, req, upgrade): -- # type: (InstallRequirement, bool) -> Optional[Link] -+ def find_requirement(self, req, upgrade, ignore_compatibility=False): -+ # type: (InstallRequirement, bool, bool) -> Optional[Link] - """Try to find a Link matching req - - Expects req, an InstallRequirement and upgrade, a boolean -@@ -784,8 +811,8 @@ class PackageFinder(object): - logger.debug('Skipping link %s; %s', link, reason) - self.logged_links.add(link) - -- def _link_package_versions(self, link, search): -- # type: (Link, Search) -> Optional[InstallationCandidate] -+ def _link_package_versions(self, link, search, ignore_compatibility=True): -+ # type: (Link, Search, bool) -> Optional[InstallationCandidate] - """Return an InstallationCandidate or None""" - version = None - if link.egg_fragment: -@@ -801,12 +828,12 @@ class PackageFinder(object): - link, 'unsupported archive format: %s' % ext, - ) - return None -- if "binary" not in search.formats and ext == WHEEL_EXTENSION: -+ if "binary" not in search.formats and ext == WHEEL_EXTENSION and not ignore_compatibility: - self._log_skipped_link( - link, 'No binaries permitted for %s' % search.supplied, - ) - return None -- if "macosx10" in link.path and ext == '.zip': -+ if "macosx10" in link.path and ext == '.zip' and not ignore_compatibility: - self._log_skipped_link(link, 'macosx10 one') - return None - if ext == WHEEL_EXTENSION: -@@ -820,7 +847,7 @@ class PackageFinder(object): - link, 'wrong project name (not %s)' % search.supplied) - return None - -- if not wheel.supported(self.valid_tags): -+ if not wheel.supported(self.valid_tags) and not ignore_compatibility: - self._log_skipped_link( - link, 'it is not compatible with this Python') - return None -@@ -856,14 +883,14 @@ class PackageFinder(object): - link.filename, link.requires_python) - support_this_python = True - -- if not support_this_python: -+ if not support_this_python and not ignore_compatibility: - logger.debug("The package %s is incompatible with the python " - "version in use. Acceptable python versions are: %s", - link, link.requires_python) - return None - logger.debug('Found link %s, version: %s', link, version) - -- return InstallationCandidate(search.supplied, version, link) -+ return InstallationCandidate(search.supplied, version, link, link.requires_python) - - - def _find_name_version_sep(egg_info, canonical_name): -diff --git a/pipenv/patched/pip/_internal/models/candidate.py b/pipenv/patched/pip/_internal/models/candidate.py -index 4475458a..6748957d 100644 ---- a/pipenv/patched/pip/_internal/models/candidate.py -+++ b/pipenv/patched/pip/_internal/models/candidate.py -@@ -13,11 +13,12 @@ class InstallationCandidate(KeyBasedCompareMixin): - """Represents a potential "candidate" for installation. - """ - -- def __init__(self, project, version, location): -- # type: (Any, str, Link) -> None -+ def __init__(self, project, version, location, requires_python=None): -+ # type: (Any, str, Link, Any) -> None - self.project = project - self.version = parse_version(version) # type: _BaseVersion - self.location = location -+ self.requires_python = requires_python - - super(InstallationCandidate, self).__init__( - key=(self.project, self.version, self.location), -diff --git a/pipenv/patched/pip/_internal/operations/prepare.py b/pipenv/patched/pip/_internal/operations/prepare.py -index 4f31dd5a..ed0c86b2 100644 ---- a/pipenv/patched/pip/_internal/operations/prepare.py -+++ b/pipenv/patched/pip/_internal/operations/prepare.py -@@ -17,7 +17,7 @@ from pip._internal.exceptions import ( - from pip._internal.utils.compat import expanduser - from pip._internal.utils.hashes import MissingHashes - from pip._internal.utils.logging import indent_log --from pip._internal.utils.misc import display_path, normalize_path -+from pip._internal.utils.misc import display_path, normalize_path, rmtree - from pip._internal.utils.typing import MYPY_CHECK_RUNNING - from pip._internal.vcs import vcs - -@@ -258,14 +258,7 @@ class RequirementPreparer(object): - # package unpacked in `req.source_dir` - # package unpacked in `req.source_dir` - if os.path.exists(os.path.join(req.source_dir, 'setup.py')): -- raise PreviousBuildDirError( -- "pip can't proceed with requirements '%s' due to a" -- " pre-existing build directory (%s). This is " -- "likely due to a previous installation that failed" -- ". pip is being responsible and not assuming it " -- "can delete this. Please delete it and try again." -- % (req, req.source_dir) -- ) -+ rmtree(req.source_dir) - req.populate_link(finder, upgrade_allowed, require_hashes) - - # We can't hit this spot and have populate_link return None. -diff --git a/pipenv/patched/pip/_internal/pep425tags.py b/pipenv/patched/pip/_internal/pep425tags.py -index 1e782d1a..3c760ca3 100644 ---- a/pipenv/patched/pip/_internal/pep425tags.py -+++ b/pipenv/patched/pip/_internal/pep425tags.py -@@ -10,7 +10,10 @@ import sysconfig - import warnings - from collections import OrderedDict - --import pip._internal.utils.glibc -+try: -+ import pip._internal.utils.glibc -+except ImportError: -+ import pip.utils.glibc - from pip._internal.utils.compat import get_extension_suffixes - from pip._internal.utils.typing import MYPY_CHECK_RUNNING - -diff --git a/pipenv/patched/pip/_internal/req/req_install.py b/pipenv/patched/pip/_internal/req/req_install.py -index a4834b00..2c22e141 100644 ---- a/pipenv/patched/pip/_internal/req/req_install.py -+++ b/pipenv/patched/pip/_internal/req/req_install.py -@@ -588,7 +588,8 @@ class InstallRequirement(object): - self.setup_py, self.link, - ) - script = SETUPTOOLS_SHIM % self.setup_py -- base_cmd = [sys.executable, '-c', script] -+ sys_executable = os.environ.get('PIP_PYTHON_PATH', sys.executable) -+ base_cmd = [sys_executable, '-c', script] - if self.isolated: - base_cmd += ["--no-user-cfg"] - egg_info_cmd = base_cmd + ['egg_info'] -@@ -746,9 +747,10 @@ class InstallRequirement(object): - with indent_log(): - # FIXME: should we do --install-headers here too? - with self.build_env: -+ sys_executable = os.environ.get('PIP_PYTHON_PATH', sys.executable) - call_subprocess( - [ -- sys.executable, -+ sys_executable, - '-c', - SETUPTOOLS_SHIM % self.setup_py - ] + -@@ -995,7 +997,8 @@ class InstallRequirement(object): - pycompile # type: bool - ): - # type: (...) -> List[str] -- install_args = [sys.executable, "-u"] -+ sys_executable = os.environ.get('PIP_PYTHON_PATH', sys.executable) -+ install_args = [sys_executable, "-u"] - install_args.append('-c') - install_args.append(SETUPTOOLS_SHIM % self.setup_py) - install_args += list(global_options) + \ -diff --git a/pipenv/patched/pip/_internal/req/req_set.py b/pipenv/patched/pip/_internal/req/req_set.py -index d1410e93..69a53bf2 100644 ---- a/pipenv/patched/pip/_internal/req/req_set.py -+++ b/pipenv/patched/pip/_internal/req/req_set.py -@@ -18,7 +18,7 @@ logger = logging.getLogger(__name__) - - class RequirementSet(object): - -- def __init__(self, require_hashes=False, check_supported_wheels=True): -+ def __init__(self, require_hashes=False, check_supported_wheels=True, ignore_compatibility=True): - # type: (bool, bool) -> None - """Create a RequirementSet. - """ -@@ -26,6 +26,9 @@ class RequirementSet(object): - self.requirements = OrderedDict() # type: Dict[str, InstallRequirement] # noqa: E501 - self.require_hashes = require_hashes - self.check_supported_wheels = check_supported_wheels -+ if ignore_compatibility: -+ self.check_supported_wheels = False -+ self.ignore_compatibility = (check_supported_wheels is False or ignore_compatibility is True) - - # Mapping of alias: real_name - self.requirement_aliases = {} # type: Dict[str, str] -@@ -186,7 +189,7 @@ class RequirementSet(object): - return self.requirements[name] - if name in self.requirement_aliases: - return self.requirements[self.requirement_aliases[name]] -- raise KeyError("No project with the name %r" % project_name) -+ pass - - def cleanup_files(self): - # type: () -> None -diff --git a/pipenv/patched/pip/_internal/resolve.py b/pipenv/patched/pip/_internal/resolve.py -index 33f572f1..dfe149ad 100644 ---- a/pipenv/patched/pip/_internal/resolve.py -+++ b/pipenv/patched/pip/_internal/resolve.py -@@ -19,6 +19,7 @@ from pip._internal.exceptions import ( - UnsupportedPythonVersion, - ) - from pip._internal.req.constructors import install_req_from_req_string -+from pip._internal.req.req_install import InstallRequirement - from pip._internal.utils.logging import indent_log - from pip._internal.utils.misc import dist_in_usersite, ensure_dir - from pip._internal.utils.packaging import check_dist_requires_python -@@ -58,7 +59,8 @@ class Resolver(object): - force_reinstall, # type: bool - isolated, # type: bool - upgrade_strategy, # type: str -- use_pep517=None # type: Optional[bool] -+ use_pep517=None, # type: Optional[bool] -+ ignore_compatibility=False, # type: bool - ): - # type: (...) -> None - super(Resolver, self).__init__() -@@ -81,8 +83,12 @@ class Resolver(object): - self.ignore_dependencies = ignore_dependencies - self.ignore_installed = ignore_installed - self.ignore_requires_python = ignore_requires_python -+ self.ignore_compatibility = ignore_compatibility - self.use_user_site = use_user_site - self.use_pep517 = use_pep517 -+ self.requires_python = None -+ if self.ignore_compatibility: -+ self.ignore_requires_python = True - - self._discovered_dependencies = \ - defaultdict(list) # type: DefaultDict[str, List] -@@ -273,7 +279,8 @@ class Resolver(object): - def _resolve_one( - self, - requirement_set, # type: RequirementSet -- req_to_install # type: InstallRequirement -+ req_to_install, # type: InstallRequirement -+ ignore_requires_python=False # type: bool - ): - # type: (...) -> List[InstallRequirement] - """Prepare a single requirements file. -@@ -298,11 +305,18 @@ class Resolver(object): - try: - check_dist_requires_python(dist) - except UnsupportedPythonVersion as err: -- if self.ignore_requires_python: -+ if self.ignore_requires_python or ignore_requires_python or self.ignore_compatibility: - logger.warning(err.args[0]) - else: - raise - -+ # A huge hack, by Kenneth Reitz. -+ try: -+ self.requires_python = check_dist_requires_python(dist, absorb=False) -+ except TypeError: -+ self.requires_python = None -+ -+ - more_reqs = [] # type: List[InstallRequirement] - - def add_req(subreq, extras_requested): -@@ -329,10 +343,14 @@ class Resolver(object): - # We add req_to_install before its dependencies, so that we - # can refer to it when adding dependencies. - if not requirement_set.has_requirement(req_to_install.name): -+ available_requested = sorted( -+ set(dist.extras) & set(req_to_install.extras) -+ ) - # 'unnamed' requirements will get added here - req_to_install.is_direct = True - requirement_set.add_requirement( - req_to_install, parent_req_name=None, -+ extras_requested=available_requested, - ) - - if not self.ignore_dependencies: -@@ -356,6 +374,20 @@ class Resolver(object): - for subreq in dist.requires(available_requested): - add_req(subreq, extras_requested=available_requested) - -+ # Hack for deep-resolving extras. -+ for available in available_requested: -+ if hasattr(dist, '_DistInfoDistribution__dep_map'): -+ for req in dist._DistInfoDistribution__dep_map[available]: -+ req = InstallRequirement( -+ req, -+ req_to_install, -+ isolated=self.isolated, -+ wheel_cache=self.wheel_cache, -+ use_pep517=None -+ ) -+ -+ more_reqs.append(req) -+ - if not req_to_install.editable and not req_to_install.satisfied_by: - # XXX: --no-install leads this to report 'Successfully - # downloaded' for only non-editable reqs, even though we took -diff --git a/pipenv/patched/pip/_internal/utils/packaging.py b/pipenv/patched/pip/_internal/utils/packaging.py -index 7aaf7b5e..d56f0512 100644 ---- a/pipenv/patched/pip/_internal/utils/packaging.py -+++ b/pipenv/patched/pip/_internal/utils/packaging.py -@@ -37,7 +37,7 @@ def check_requires_python(requires_python): - requires_python_specifier = specifiers.SpecifierSet(requires_python) - - # We only use major.minor.micro -- python_version = version.parse('.'.join(map(str, sys.version_info[:3]))) -+ python_version = version.parse('{0}.{1}.{2}'.format(*sys.version_info[:3])) - return python_version in requires_python_specifier - - -@@ -57,9 +57,11 @@ def get_metadata(dist): - return feed_parser.close() - - --def check_dist_requires_python(dist): -+def check_dist_requires_python(dist, absorb=False): - pkg_info_dict = get_metadata(dist) - requires_python = pkg_info_dict.get('Requires-Python') -+ if absorb: -+ return requires_python - try: - if not check_requires_python(requires_python): - raise exceptions.UnsupportedPythonVersion( -diff --git a/pipenv/patched/pip/_internal/utils/temp_dir.py b/pipenv/patched/pip/_internal/utils/temp_dir.py -index 2c81ad55..ff2ccc5a 100644 ---- a/pipenv/patched/pip/_internal/utils/temp_dir.py -+++ b/pipenv/patched/pip/_internal/utils/temp_dir.py -@@ -5,8 +5,10 @@ import itertools - import logging - import os.path - import tempfile -+import warnings - - from pip._internal.utils.misc import rmtree -+from pipenv.vendor.vistir.compat import finalize, ResourceWarning - - logger = logging.getLogger(__name__) - -@@ -47,6 +49,20 @@ class TempDirectory(object): - self.path = path - self.delete = delete - self.kind = kind -+ self._finalizer = None -+ if path: -+ self._register_finalizer() -+ -+ def _register_finalizer(self): -+ if self.delete and self.path: -+ self._finalizer = finalize( -+ self, -+ self._cleanup, -+ self.path, -+ warn_message = None -+ ) -+ else: -+ self._finalizer = None - - def __repr__(self): - return "<{} {!r}>".format(self.__class__.__name__, self.path) -@@ -74,14 +90,30 @@ class TempDirectory(object): - self.path = os.path.realpath( - tempfile.mkdtemp(prefix="pip-{}-".format(self.kind)) - ) -+ self._register_finalizer() - logger.debug("Created temporary directory: {}".format(self.path)) - -+ @classmethod -+ def _cleanup(cls, name, warn_message=None): -+ try: -+ rmtree(name) -+ except OSError: -+ pass -+ else: -+ if warn_message: -+ warnings.warn(warn_message, ResourceWarning) -+ - def cleanup(self): - """Remove the temporary directory created and reset state - """ -- if self.path is not None and os.path.exists(self.path): -- rmtree(self.path) -- self.path = None -+ if getattr(self._finalizer, "detach", None) and self._finalizer.detach(): -+ if os.path.exists(self.path): -+ try: -+ rmtree(self.path) -+ except OSError: -+ pass -+ else: -+ self.path = None - - - class AdjacentTempDirectory(TempDirectory): -@@ -152,4 +184,5 @@ class AdjacentTempDirectory(TempDirectory): - self.path = os.path.realpath( - tempfile.mkdtemp(prefix="pip-{}-".format(self.kind)) - ) -+ self._register_finalizer() - logger.debug("Created temporary directory: {}".format(self.path)) -diff --git a/pipenv/patched/pip/_internal/wheel.py b/pipenv/patched/pip/_internal/wheel.py -index 67bcc7f7..968cdff9 100644 ---- a/pipenv/patched/pip/_internal/wheel.py -+++ b/pipenv/patched/pip/_internal/wheel.py -@@ -114,7 +114,7 @@ def fix_script(path): - firstline = script.readline() - if not firstline.startswith(b'#!python'): - return False -- exename = sys.executable.encode(sys.getfilesystemencoding()) -+ exename = os.environ.get('PIP_PYTHON_PATH', sys.executable).encode(sys.getfilesystemencoding()) - firstline = b'#!' + exename + os.linesep.encode("ascii") - rest = script.read() - with open(path, 'wb') as script: -@@ -201,7 +201,8 @@ def message_about_scripts_not_on_PATH(scripts): - ] - # If an executable sits with sys.executable, we don't warn for it. - # This covers the case of venv invocations without activating the venv. -- not_warn_dirs.append(os.path.normcase(os.path.dirname(sys.executable))) -+ executable_loc = os.environ.get("PIP_PYTHON_PATH", sys.executable) -+ not_warn_dirs.append(os.path.normcase(os.path.dirname(executable_loc))) - warn_for = { - parent_dir: scripts for parent_dir, scripts in grouped_by_dir.items() - if os.path.normcase(parent_dir) not in not_warn_dirs -@@ -901,8 +902,9 @@ class WheelBuilder(object): - # isolating. Currently, it breaks Python in virtualenvs, because it - # relies on site.py to find parts of the standard library outside the - # virtualenv. -+ executable_loc = os.environ.get('PIP_PYTHON_PATH', sys.executable) - return [ -- sys.executable, '-u', '-c', -+ executable_loc, '-u', '-c', - SETUPTOOLS_SHIM % req.setup_py - ] + list(self.global_options) - -diff --git a/pipenv/patched/pip/_internal/utils/misc.py b/pipenv/patched/pip/_internal/utils/misc.py -index 84605ee3..649311c0 100644 ---- a/pipenv/patched/pip/_internal/utils/misc.py -+++ b/pipenv/patched/pip/_internal/utils/misc.py -@@ -117,8 +117,8 @@ def get_prog(): - @retry(stop_max_delay=3000, wait_fixed=500) - def rmtree(dir, ignore_errors=False): - # type: (str, bool) -> None -- shutil.rmtree(dir, ignore_errors=ignore_errors, -- onerror=rmtree_errorhandler) -+ from pipenv.vendor.vistir.path import rmtree as vistir_rmtree, handle_remove_readonly -+ vistir_rmtree(dir, onerror=handle_remove_readonly, ignore_errors=ignore_errors) - - - def rmtree_errorhandler(func, path, exc_info): diff --git a/tasks/vendoring/patches/patched/pip21.patch b/tasks/vendoring/patches/patched/pip21.patch new file mode 100644 index 00000000..f700a99a --- /dev/null +++ b/tasks/vendoring/patches/patched/pip21.patch @@ -0,0 +1,254 @@ +diff --git a/pipenv/patched/pip/_internal/build_env.py b/pipenv/patched/pip/_internal/build_env.py +index de98163d..a5a99042 100644 +--- a/pipenv/patched/pip/_internal/build_env.py ++++ b/pipenv/patched/pip/_internal/build_env.py +@@ -223,8 +223,9 @@ class BuildEnvironment: + prefix: _Prefix, + message: str, + ) -> None: ++ sys_executable = os.environ.get('PIP_PYTHON_PATH', sys.executable) + args = [ +- sys.executable, pip_runnable, 'install', ++ sys_executable, pip_runnable, 'install', + '--ignore-installed', '--no-user', '--prefix', prefix.path, + '--no-warn-script-location', + ] # type: List[str] +diff --git a/pipenv/patched/pip/_internal/commands/__init__.py b/pipenv/patched/pip/_internal/commands/__init__.py +index 8e94b38f..eb236d70 100644 +--- a/pipenv/patched/pip/_internal/commands/__init__.py ++++ b/pipenv/patched/pip/_internal/commands/__init__.py +@@ -20,67 +20,67 @@ CommandInfo = namedtuple('CommandInfo', 'module_path, class_name, summary') + # so that the ordering won't be lost when using Python 2.7. + commands_dict: Dict[str, CommandInfo] = OrderedDict([ + ('install', CommandInfo( +- 'pip._internal.commands.install', 'InstallCommand', ++ 'pipenv.patched.notpip._internal.commands.install', 'InstallCommand', + 'Install packages.', + )), + ('download', CommandInfo( +- 'pip._internal.commands.download', 'DownloadCommand', ++ 'pipenv.patched.notpip._internal.commands.download', 'DownloadCommand', + 'Download packages.', + )), + ('uninstall', CommandInfo( +- 'pip._internal.commands.uninstall', 'UninstallCommand', ++ 'pipenv.patched.notpip._internal.commands.uninstall', 'UninstallCommand', + 'Uninstall packages.', + )), + ('freeze', CommandInfo( +- 'pip._internal.commands.freeze', 'FreezeCommand', ++ 'pipenv.patched.notpip._internal.commands.freeze', 'FreezeCommand', + 'Output installed packages in requirements format.', + )), + ('list', CommandInfo( +- 'pip._internal.commands.list', 'ListCommand', ++ 'pipenv.patched.notpip._internal.commands.list', 'ListCommand', + 'List installed packages.', + )), + ('show', CommandInfo( +- 'pip._internal.commands.show', 'ShowCommand', ++ 'pipenv.patched.notpip._internal.commands.show', 'ShowCommand', + 'Show information about installed packages.', + )), + ('check', CommandInfo( +- 'pip._internal.commands.check', 'CheckCommand', ++ 'pipenv.patched.notpip._internal.commands.check', 'CheckCommand', + 'Verify installed packages have compatible dependencies.', + )), + ('config', CommandInfo( +- 'pip._internal.commands.configuration', 'ConfigurationCommand', ++ 'pipenv.patched.notpip._internal.commands.configuration', 'ConfigurationCommand', + 'Manage local and global configuration.', + )), + ('search', CommandInfo( +- 'pip._internal.commands.search', 'SearchCommand', ++ 'pipenv.patched.notpip._internal.commands.search', 'SearchCommand', + 'Search PyPI for packages.', + )), + ('cache', CommandInfo( +- 'pip._internal.commands.cache', 'CacheCommand', ++ 'pipenv.patched.notpip._internal.commands.cache', 'CacheCommand', + "Inspect and manage pip's wheel cache.", + )), + ('index', CommandInfo( +- 'pip._internal.commands.index', 'IndexCommand', ++ 'pipenv.patched.notpip._internal.commands.index', 'IndexCommand', + "Inspect information available from package indexes.", + )), + ('wheel', CommandInfo( +- 'pip._internal.commands.wheel', 'WheelCommand', ++ 'pipenv.patched.notpip._internal.commands.wheel', 'WheelCommand', + 'Build wheels from your requirements.', + )), + ('hash', CommandInfo( +- 'pip._internal.commands.hash', 'HashCommand', ++ 'pipenv.patched.notpip._internal.commands.hash', 'HashCommand', + 'Compute hashes of package archives.', + )), + ('completion', CommandInfo( +- 'pip._internal.commands.completion', 'CompletionCommand', ++ 'pipenv.patched.notpip._internal.commands.completion', 'CompletionCommand', + 'A helper command used for command completion.', + )), + ('debug', CommandInfo( +- 'pip._internal.commands.debug', 'DebugCommand', ++ 'pipenv.patched.notpip._internal.commands.debug', 'DebugCommand', + 'Show information useful for debugging.', + )), + ('help', CommandInfo( +- 'pip._internal.commands.help', 'HelpCommand', ++ 'pipenv.patched.notpip._internal.commands.help', 'HelpCommand', + 'Show help for commands.', + )), + ]) +diff --git a/pipenv/patched/pip/_internal/index/package_finder.py b/pipenv/patched/pip/_internal/index/package_finder.py +index 2dadb5ae..31769869 100644 +--- a/pipenv/patched/pip/_internal/index/package_finder.py ++++ b/pipenv/patched/pip/_internal/index/package_finder.py +@@ -112,6 +112,7 @@ class LinkEvaluator: + target_python: TargetPython, + allow_yanked: bool, + ignore_requires_python: Optional[bool] = None, ++ ignore_compatibility: Optional[bool] = None, + ) -> None: + """ + :param project_name: The user supplied package name. +@@ -129,15 +130,20 @@ class LinkEvaluator: + :param ignore_requires_python: Whether to ignore incompatible + PEP 503 "data-requires-python" values in HTML links. Defaults + to False. ++ :param ignore_compatibility: Whether to ignore ++ compatibility of python versions and allow all versions of packages. + """ + if ignore_requires_python is None: + ignore_requires_python = False ++ if ignore_compatibility is None: ++ ignore_compatibility = True + + self._allow_yanked = allow_yanked + self._canonical_name = canonical_name + self._ignore_requires_python = ignore_requires_python + self._formats = formats + self._target_python = target_python ++ self._ignore_compatibility = ignore_compatibility + + self.project_name = project_name + +@@ -164,11 +170,11 @@ class LinkEvaluator: + return (False, 'not a file') + if ext not in SUPPORTED_EXTENSIONS: + return (False, f'unsupported archive format: {ext}') +- if "binary" not in self._formats and ext == WHEEL_EXTENSION: ++ if "binary" not in self._formats and ext == WHEEL_EXTENSION and not self._ignore_compatibility: + reason = 'No binaries permitted for {}'.format( + self.project_name) + return (False, reason) +- if "macosx10" in link.path and ext == '.zip': ++ if "macosx10" in link.path and ext == '.zip' and not self._ignore_compatibility: + return (False, 'macosx10 one') + if ext == WHEEL_EXTENSION: + try: +@@ -181,7 +187,7 @@ class LinkEvaluator: + return (False, reason) + + supported_tags = self._target_python.get_tags() +- if not wheel.supported(supported_tags): ++ if not wheel.supported(supported_tags) and not self._ignore_compatibility: + # Include the wheel's tags in the reason string to + # simplify troubleshooting compatibility issues. + file_tags = wheel.get_formatted_file_tags() +@@ -219,7 +225,7 @@ class LinkEvaluator: + link, version_info=self._target_python.py_version_info, + ignore_requires_python=self._ignore_requires_python, + ) +- if not supports_python: ++ if not supports_python and not self._ignore_compatibility: + # Return None for the reason text to suppress calling + # _log_skipped_link(). + return (False, None) +@@ -470,7 +476,10 @@ class CandidateEvaluator: + + return sorted(filtered_applicable_candidates, key=self._sort_key) + +- def _sort_key(self, candidate: InstallationCandidate) -> CandidateSortingKey: ++ def _sort_key( ++ self, candidate: InstallationCandidate, ++ ignore_compatibility: bool = True ++ ) -> CandidateSortingKey: + """ + Function to pass as the `key` argument to a call to sorted() to sort + InstallationCandidates by preference. +@@ -513,10 +522,12 @@ class CandidateEvaluator: + valid_tags, self._wheel_tag_preferences + )) + except ValueError: +- raise UnsupportedWheel( +- "{} is not a supported wheel for this platform. It " +- "can't be sorted.".format(wheel.filename) +- ) ++ if not ignore_compatibility: ++ raise UnsupportedWheel( ++ "{} is not a supported wheel for this platform. It " ++ "can't be sorted.".format(wheel.filename) ++ ) ++ pri = -(support_num) + if self._prefer_binary: + binary_preference = 1 + if wheel.build_tag is not None: +@@ -578,6 +589,7 @@ class PackageFinder: + format_control: Optional[FormatControl] = None, + candidate_prefs: Optional[CandidatePreferences] = None, + ignore_requires_python: Optional[bool] = None, ++ ignore_compatibility: Optional[bool] = False + ) -> None: + """ + This constructor is primarily meant to be used by the create() class +@@ -599,6 +611,7 @@ class PackageFinder: + self._ignore_requires_python = ignore_requires_python + self._link_collector = link_collector + self._target_python = target_python ++ self._ignore_compatibility = ignore_compatibility + + self.format_control = format_control + +@@ -691,6 +704,7 @@ class PackageFinder: + target_python=self._target_python, + allow_yanked=self._allow_yanked, + ignore_requires_python=self._ignore_requires_python, ++ ignore_compatibility=self._ignore_compatibility + ) + + def _sort_links(self, links: Iterable[Link]) -> List[Link]: +diff --git a/pipenv/patched/pip/_internal/req/req_install.py b/pipenv/patched/pip/_internal/req/req_install.py +index 6a42a510..318578e0 100644 +--- a/pipenv/patched/pip/_internal/req/req_install.py ++++ b/pipenv/patched/pip/_internal/req/req_install.py +@@ -488,6 +488,7 @@ class InstallRequirement: + self.pyproject_requires = requires + self.pep517_backend = Pep517HookCaller( + self.unpacked_source_directory, backend, backend_path=backend_path, ++ python_executable=os.getenv('PIP_PYTHON_PATH', sys.executable) + ) + + def _generate_metadata(self) -> str: +diff --git a/pipenv/patched/pip/_vendor/requests/packages.py b/pipenv/patched/pip/_vendor/requests/packages.py +index 9582fa73..5fb6f07d 100644 +--- a/pipenv/patched/pip/_vendor/requests/packages.py ++++ b/pipenv/patched/pip/_vendor/requests/packages.py +@@ -4,13 +4,13 @@ import sys + # I don't like it either. Just look the other way. :) + + for package in ('urllib3', 'idna', 'chardet'): +- vendored_package = "pip._vendor." + package ++ vendored_package = "pipenv.patched.notpip._vendor." + package + locals()[package] = __import__(vendored_package) + # This traversal is apparently necessary such that the identities are + # preserved (requests.packages.urllib3.* is urllib3.*) + for mod in list(sys.modules): + if mod == vendored_package or mod.startswith(vendored_package + '.'): +- unprefixed_mod = mod[len("pip._vendor."):] +- sys.modules['pip._vendor.requests.packages.' + unprefixed_mod] = sys.modules[mod] ++ unprefixed_mod = mod[len("pipenv.patched.notpip._vendor."):] ++ sys.modules['pipenv.patched.notpip._vendor.requests.packages.' + unprefixed_mod] = sys.modules[mod] + + # Kinda cool, though, right? diff --git a/tasks/vendoring/patches/patched/piptools.patch b/tasks/vendoring/patches/patched/piptools.patch deleted file mode 100644 index 52d66f51..00000000 --- a/tasks/vendoring/patches/patched/piptools.patch +++ /dev/null @@ -1,699 +0,0 @@ -diff --git a/pipenv/patched/piptools/_compat/__init__.py b/pipenv/patched/piptools/_compat/__init__.py -index e4ac717..19adcbc 100644 ---- a/pipenv/patched/piptools/_compat/__init__.py -+++ b/pipenv/patched/piptools/_compat/__init__.py -@@ -31,4 +31,6 @@ from .pip_compat import ( - install_req_from_editable, - stdlib_pkgs, - DEV_PKGS, -+ SafeFileCache, -+ InstallationError - ) -diff --git a/pipenv/patched/piptools/_compat/pip_compat.py b/pipenv/patched/piptools/_compat/pip_compat.py -index 82ccb8b..715144a 100644 ---- a/pipenv/patched/piptools/_compat/pip_compat.py -+++ b/pipenv/patched/piptools/_compat/pip_compat.py -@@ -1,48 +1,51 @@ - # -*- coding=utf-8 -*- --import importlib -+__all__ = [ -+ "InstallRequirement", -+ "parse_requirements", -+ "RequirementSet", -+ "FAVORITE_HASH", -+ "is_file_url", -+ "path_to_url", -+ "url_to_path", -+ "PackageFinder", -+ "FormatControl", -+ "Wheel", -+ "Command", -+ "cmdoptions", -+ "get_installed_distributions", -+ "PyPI", -+ "stdlib_pkgs", -+ "DEV_PKGS", -+ "install_req_from_line", -+ "install_req_from_editable", -+ "user_cache_dir", -+ "SafeFileCache", -+ "InstallationError" -+] - --import pip --import pkg_resources -+import os -+os.environ["PIP_SHIMS_BASE_MODULE"] = str("pipenv.patched.notpip") - --def do_import(module_path, subimport=None, old_path=None): -- old_path = old_path or module_path -- prefixes = ["pip._internal", "pip"] -- paths = [module_path, old_path] -- search_order = ["{0}.{1}".format(p, pth) for p in prefixes for pth in paths if pth is not None] -- package = subimport if subimport else None -- for to_import in search_order: -- if not subimport: -- to_import, _, package = to_import.rpartition(".") -- try: -- imported = importlib.import_module(to_import) -- except ImportError: -- continue -- else: -- return getattr(imported, package) -- -- --InstallRequirement = do_import('req.req_install', 'InstallRequirement') --parse_requirements = do_import('req.req_file', 'parse_requirements') --RequirementSet = do_import('req.req_set', 'RequirementSet') --user_cache_dir = do_import('utils.appdirs', 'user_cache_dir') --FAVORITE_HASH = do_import('utils.hashes', 'FAVORITE_HASH') --is_file_url = do_import('download', 'is_file_url') --path_to_url = do_import('download', 'path_to_url') --url_to_path = do_import('download', 'url_to_path') --PackageFinder = do_import('index', 'PackageFinder') --FormatControl = do_import('index', 'FormatControl') --Wheel = do_import('wheel', 'Wheel') --Command = do_import('cli.base_command', 'Command', old_path='basecommand') --cmdoptions = do_import('cli.cmdoptions', old_path='cmdoptions') --get_installed_distributions = do_import('utils.misc', 'get_installed_distributions', old_path='utils') --PyPI = do_import('models.index', 'PyPI') --stdlib_pkgs = do_import('utils.compat', 'stdlib_pkgs', old_path='compat') --DEV_PKGS = do_import('commands.freeze', 'DEV_PKGS') -- --# pip 18.1 has refactored InstallRequirement constructors use by pip-tools. --if pkg_resources.parse_version(pip.__version__) < pkg_resources.parse_version('18.1'): -- install_req_from_line = InstallRequirement.from_line -- install_req_from_editable = InstallRequirement.from_editable --else: -- install_req_from_line = do_import('req.constructors', 'install_req_from_line') -- install_req_from_editable = do_import('req.constructors', 'install_req_from_editable') -+from pip_shims.shims import ( -+ InstallRequirement, -+ parse_requirements, -+ RequirementSet, -+ FAVORITE_HASH, -+ is_file_url, -+ path_to_url, -+ url_to_path, -+ PackageFinder, -+ FormatControl, -+ Wheel, -+ Command, -+ cmdoptions, -+ get_installed_distributions, -+ PyPI, -+ stdlib_pkgs, -+ DEV_PKGS, -+ install_req_from_line, -+ install_req_from_editable, -+ USER_CACHE_DIR as user_cache_dir, -+ SafeFileCache, -+ InstallationError -+) -diff --git a/pipenv/patched/piptools/locations.py b/pipenv/patched/piptools/locations.py -index 4e6174c..9e0c6f1 100644 ---- a/pipenv/patched/piptools/locations.py -+++ b/pipenv/patched/piptools/locations.py -@@ -5,7 +5,11 @@ from .click import secho - from ._compat import user_cache_dir - - # The user_cache_dir helper comes straight from pip itself --CACHE_DIR = user_cache_dir('pip-tools') -+try: -+ from pipenv.environments import PIPENV_CACHE_DIR -+ CACHE_DIR = PIPENV_CACHE_DIR -+except ImportError: -+ CACHE_DIR = user_cache_dir('pipenv') - - # NOTE - # We used to store the cache dir under ~/.pip-tools, which is not the -diff --git a/pipenv/patched/piptools/repositories/local.py b/pipenv/patched/piptools/repositories/local.py -index 08dabe1..36bafdb 100644 ---- a/pipenv/patched/piptools/repositories/local.py -+++ b/pipenv/patched/piptools/repositories/local.py -@@ -56,7 +56,8 @@ class LocalRequirementsRepository(BaseRepository): - if existing_pin and ireq_satisfied_by_existing_pin(ireq, existing_pin): - project, version, _ = as_tuple(existing_pin) - return make_install_requirement( -- project, version, ireq.extras, constraint=ireq.constraint -+ project, version, ireq.extras, constraint=ireq.constraint, -+ markers=ireq.markers - ) - else: - return self.repository.find_best_match(ireq, prereleases) -diff --git a/pipenv/patched/piptools/repositories/pypi.py b/pipenv/patched/piptools/repositories/pypi.py -index e54ae08..75b8208 100644 ---- a/pipenv/patched/piptools/repositories/pypi.py -+++ b/pipenv/patched/piptools/repositories/pypi.py -@@ -2,14 +2,22 @@ - from __future__ import (absolute_import, division, print_function, - unicode_literals) - -+import copy - import hashlib - import os - from contextlib import contextmanager - from shutil import rmtree - --import pip - import pkg_resources - -+from packaging.requirements import Requirement -+from packaging.specifiers import SpecifierSet, Specifier -+ -+os.environ["PIP_SHIMS_BASE_MODULE"] = str("pipenv.patched.notpip") -+import pip_shims -+from pip_shims.shims import VcsSupport, WheelCache, InstallationError, pip_version -+ -+ - from .._compat import ( - is_file_url, - url_to_path, -@@ -18,13 +26,15 @@ from .._compat import ( - Wheel, - FAVORITE_HASH, - TemporaryDirectory, -- PyPI -+ PyPI, -+ InstallRequirement, -+ SafeFileCache - ) - - from ..cache import CACHE_DIR - from ..exceptions import NoCandidateFound --from ..utils import (fs_str, is_pinned_requirement, lookup_table, -- make_install_requirement) -+from ..utils import (fs_str, is_pinned_requirement, lookup_table, dedup, -+ make_install_requirement, clean_requires_python) - from .base import BaseRepository - - try: -@@ -34,10 +44,44 @@ except ImportError: - def RequirementTracker(): - yield - --try: -- from pip._internal.cache import WheelCache --except ImportError: -- from pip.wheel import WheelCache -+ -+class HashCache(SafeFileCache): -+ """Caches hashes of PyPI artifacts so we do not need to re-download them -+ -+ Hashes are only cached when the URL appears to contain a hash in it and the cache key includes -+ the hash value returned from the server). This ought to avoid ssues where the location on the -+ server changes.""" -+ def __init__(self, *args, **kwargs): -+ session = kwargs.pop('session') -+ self.session = session -+ kwargs.setdefault('directory', os.path.join(CACHE_DIR, 'hash-cache')) -+ super(HashCache, self).__init__(*args, **kwargs) -+ -+ def get_hash(self, location): -+ # if there is no location hash (i.e., md5 / sha256 / etc) we on't want to store it -+ hash_value = None -+ vcs = VcsSupport() -+ orig_scheme = location.scheme -+ new_location = copy.deepcopy(location) -+ if orig_scheme in vcs.all_schemes: -+ new_location.url = new_location.url.split("+", 1)[-1] -+ can_hash = new_location.hash -+ if can_hash: -+ # hash url WITH fragment -+ hash_value = self.get(new_location.url) -+ if not hash_value: -+ hash_value = self._get_file_hash(new_location) if not new_location.url.startswith("ssh") else None -+ hash_value = hash_value.encode('utf8') if hash_value else None -+ if can_hash: -+ self.set(new_location.url, hash_value) -+ return hash_value.decode('utf8') if hash_value else None -+ -+ def _get_file_hash(self, location): -+ h = hashlib.new(FAVORITE_HASH) -+ with open_local_or_remote_file(location, self.session) as fp: -+ for chunk in iter(lambda: fp.read(8096), b""): -+ h.update(chunk) -+ return ":".join([FAVORITE_HASH, h.hexdigest()]) - - - class PyPIRepository(BaseRepository): -@@ -49,10 +93,11 @@ class PyPIRepository(BaseRepository): - config), but any other PyPI mirror can be used if index_urls is - changed/configured on the Finder. - """ -- def __init__(self, pip_options, session, build_isolation=False): -+ def __init__(self, pip_options, session, build_isolation=False, use_json=False): - self.session = session - self.pip_options = pip_options - self.build_isolation = build_isolation -+ self.use_json = use_json - - index_urls = [pip_options.index_url] + pip_options.extra_index_urls - if pip_options.no_index: -@@ -67,7 +112,7 @@ class PyPIRepository(BaseRepository): - } - - # pip 19.0 has removed process_dependency_links from the PackageFinder constructor -- if pkg_resources.parse_version(pip.__version__) < pkg_resources.parse_version('19.0'): -+ if pkg_resources.parse_version(pip_version) < pkg_resources.parse_version('19.0'): - finder_kwargs["process_dependency_links"] = pip_options.process_dependency_links - - self.finder = PackageFinder(**finder_kwargs) -@@ -82,6 +127,10 @@ class PyPIRepository(BaseRepository): - # of all secondary dependencies for the given requirement, so we - # only have to go to disk once for each requirement - self._dependencies_cache = {} -+ self._json_dep_cache = {} -+ -+ # stores *full* path + fragment => sha256 -+ self._hash_cache = HashCache(session=session) - - # Setup file paths - self.freshen_build_caches() -@@ -122,10 +171,13 @@ class PyPIRepository(BaseRepository): - if ireq.editable: - return ireq # return itself as the best match - -- all_candidates = self.find_all_candidates(ireq.name) -+ all_candidates = clean_requires_python(self.find_all_candidates(ireq.name)) - candidates_by_version = lookup_table(all_candidates, key=lambda c: c.version, unique=True) -- matching_versions = ireq.specifier.filter((candidate.version for candidate in all_candidates), -- prereleases=prereleases) -+ try: -+ matching_versions = ireq.specifier.filter((candidate.version for candidate in all_candidates), -+ prereleases=prereleases) -+ except TypeError: -+ matching_versions = [candidate.version for candidate in all_candidates] - - # Reuses pip's internal candidate sort key to sort - matching_candidates = [candidates_by_version[ver] for ver in matching_versions] -@@ -135,14 +187,75 @@ class PyPIRepository(BaseRepository): - - # Turn the candidate into a pinned InstallRequirement - return make_install_requirement( -- best_candidate.project, best_candidate.version, ireq.extras, constraint=ireq.constraint -+ best_candidate.project, best_candidate.version, ireq.extras, ireq.markers, constraint=ireq.constraint - ) - -+ def get_dependencies(self, ireq): -+ json_results = set() -+ -+ if self.use_json: -+ try: -+ json_results = self.get_json_dependencies(ireq) -+ except TypeError: -+ json_results = set() -+ -+ legacy_results = self.get_legacy_dependencies(ireq) -+ json_results.update(legacy_results) -+ -+ return json_results -+ -+ def get_json_dependencies(self, ireq): -+ -+ if not (is_pinned_requirement(ireq)): -+ raise TypeError('Expected pinned InstallRequirement, got {}'.format(ireq)) -+ -+ def gen(ireq): -+ if self.DEFAULT_INDEX_URL not in self.finder.index_urls: -+ return -+ -+ url = 'https://pypi.org/pypi/{0}/json'.format(ireq.req.name) -+ releases = self.session.get(url).json()['releases'] -+ -+ matches = [ -+ r for r in releases -+ if '=={0}'.format(r) == str(ireq.req.specifier) -+ ] -+ if not matches: -+ return -+ -+ release_requires = self.session.get( -+ 'https://pypi.org/pypi/{0}/{1}/json'.format( -+ ireq.req.name, matches[0], -+ ), -+ ).json() -+ try: -+ requires_dist = release_requires['info']['requires_dist'] -+ except KeyError: -+ return -+ -+ for requires in requires_dist: -+ i = InstallRequirement.from_line(requires) -+ if 'extra' not in repr(i.markers): -+ yield i -+ -+ try: -+ if ireq not in self._json_dep_cache: -+ self._json_dep_cache[ireq] = [g for g in gen(ireq)] -+ -+ return set(self._json_dep_cache[ireq]) -+ except Exception: -+ return set() -+ - def resolve_reqs(self, download_dir, ireq, wheel_cache): - results = None -+ ireq.isolated = self.build_isolation -+ ireq._wheel_cache = wheel_cache -+ if ireq and not ireq.link: -+ ireq.populate_link(self.finder, False, False) -+ if ireq.link and not ireq.link.is_wheel: -+ ireq.ensure_has_source_dir(self.source_dir) - try: - from pip._internal.operations.prepare import RequirementPreparer -- from pip._internal.resolve import Resolver as PipResolver - except ImportError: - # Pip 9 and below - reqset = RequirementSet( -@@ -151,9 +264,11 @@ class PyPIRepository(BaseRepository): - download_dir=download_dir, - wheel_download_dir=self._wheel_download_dir, - session=self.session, -+ ignore_installed=True, -+ ignore_compatibility=False, - wheel_cache=wheel_cache - ) -- results = reqset._prepare_file(self.finder, ireq) -+ results = reqset._prepare_file(self.finder, ireq, ignore_requires_python=True) - else: - # pip >= 10 - preparer_kwargs = { -@@ -170,11 +285,13 @@ class PyPIRepository(BaseRepository): - 'upgrade_strategy': "to-satisfy-only", - 'force_reinstall': False, - 'ignore_dependencies': False, -- 'ignore_requires_python': False, -+ 'ignore_requires_python': True, - 'ignore_installed': True, -- 'isolated': False, -+ 'ignore_compatibility': False, -+ 'isolated': True, - 'wheel_cache': wheel_cache, -- 'use_user_site': False -+ 'use_user_site': False, -+ 'use_pep517': True - } - resolver = None - preparer = None -@@ -186,15 +303,21 @@ class PyPIRepository(BaseRepository): - resolver_kwargs['preparer'] = preparer - reqset = RequirementSet() - ireq.is_direct = True -- reqset.add_requirement(ireq) -- resolver = PipResolver(**resolver_kwargs) -+ # reqset.add_requirement(ireq) -+ resolver = pip_shims.shims.Resolver(**resolver_kwargs) - resolver.require_hashes = False - results = resolver._resolve_one(reqset, ireq) -- reqset.cleanup_files() - -- return set(results) -+ cleanup_fn = getattr(reqset, "cleanup_files", None) -+ if cleanup_fn is not None: -+ try: -+ cleanup_fn() -+ except OSError: -+ pass -+ results = set(results) if results else set() -+ return results, ireq - -- def get_dependencies(self, ireq): -+ def get_legacy_dependencies(self, ireq): - """ - Given a pinned or an editable InstallRequirement, returns a set of - dependencies (also InstallRequirements, but not necessarily pinned). -@@ -223,7 +346,8 @@ class PyPIRepository(BaseRepository): - wheel_cache = WheelCache(CACHE_DIR, self.pip_options.format_control) - prev_tracker = os.environ.get('PIP_REQ_TRACKER') - try: -- self._dependencies_cache[ireq] = self.resolve_reqs(download_dir, ireq, wheel_cache) -+ results, ireq = self.resolve_reqs(download_dir, ireq, wheel_cache) -+ self._dependencies_cache[ireq] = results - finally: - if 'PIP_REQ_TRACKER' in os.environ: - if prev_tracker: -@@ -245,6 +369,10 @@ class PyPIRepository(BaseRepository): - if ireq.editable: - return set() - -+ vcs = VcsSupport() -+ if ireq.link and ireq.link.scheme in vcs.all_schemes and 'ssh' in ireq.link.scheme: -+ return set() -+ - if not is_pinned_requirement(ireq): - raise TypeError( - "Expected pinned requirement, got {}".format(ireq)) -@@ -252,24 +380,16 @@ class PyPIRepository(BaseRepository): - # We need to get all of the candidates that match our current version - # pin, these will represent all of the files that could possibly - # satisfy this constraint. -- all_candidates = self.find_all_candidates(ireq.name) -- candidates_by_version = lookup_table(all_candidates, key=lambda c: c.version) -- matching_versions = list( -- ireq.specifier.filter((candidate.version for candidate in all_candidates))) -- matching_candidates = candidates_by_version[matching_versions[0]] -+ matching_candidates = ( -+ c for c in clean_requires_python(self.find_all_candidates(ireq.name)) -+ if c.version in ireq.specifier -+ ) - - return { -- self._get_file_hash(candidate.location) -- for candidate in matching_candidates -+ h for h in map(lambda c: self._hash_cache.get_hash(c.location), -+ matching_candidates) if h is not None - } - -- def _get_file_hash(self, location): -- h = hashlib.new(FAVORITE_HASH) -- with open_local_or_remote_file(location, self.session) as fp: -- for chunk in iter(lambda: fp.read(8096), b""): -- h.update(chunk) -- return ":".join([FAVORITE_HASH, h.hexdigest()]) -- - @contextmanager - def allow_all_wheels(self): - """ -diff --git a/pipenv/patched/piptools/resolver.py b/pipenv/patched/piptools/resolver.py -index 494d385..b642bc9 100644 ---- a/pipenv/patched/piptools/resolver.py -+++ b/pipenv/patched/piptools/resolver.py -@@ -28,6 +28,7 @@ class RequirementSummary(object): - self.req = ireq.req - self.key = key_from_req(ireq.req) - self.extras = str(sorted(ireq.extras)) -+ self.markers = ireq.markers - self.specifier = str(ireq.specifier) - - def __eq__(self, other): -@@ -119,7 +120,7 @@ class Resolver(object): - @staticmethod - def check_constraints(constraints): - for constraint in constraints: -- if constraint.link is not None and not constraint.editable: -+ if constraint.link is not None and not constraint.editable and not constraint.is_wheel: - msg = ('pip-compile does not support URLs as packages, unless they are editable. ' - 'Perhaps add -e option?') - raise UnsupportedConstraint(msg, constraint) -@@ -155,6 +156,12 @@ class Resolver(object): - # NOTE we may be losing some info on dropped reqs here - combined_ireq.req.specifier &= ireq.req.specifier - combined_ireq.constraint &= ireq.constraint -+ if not combined_ireq.markers: -+ combined_ireq.markers = ireq.markers -+ else: -+ _markers = combined_ireq.markers._markers -+ if not isinstance(_markers[0], (tuple, list)): -+ combined_ireq.markers._markers = [_markers, 'and', ireq.markers._markers] - # Return a sorted, de-duped tuple of extras - combined_ireq.extras = tuple(sorted(set(tuple(combined_ireq.extras) + tuple(ireq.extras)))) - yield combined_ireq -@@ -272,6 +279,15 @@ class Resolver(object): - for dependency in self.repository.get_dependencies(ireq): - yield dependency - return -+ -+ # fix our malformed extras -+ if ireq.extras: -+ if getattr(ireq, "extra", None): -+ if ireq.extras: -+ ireq.extras.extend(ireq.extra) -+ else: -+ ireq.extras = ireq.extra -+ - elif not is_pinned_requirement(ireq): - raise TypeError('Expected pinned or editable requirement, got {}'.format(ireq)) - -@@ -282,7 +298,7 @@ class Resolver(object): - if ireq not in self.dependency_cache: - log.debug(' {} not in cache, need to check index'.format(format_requirement(ireq)), fg='yellow') - dependencies = self.repository.get_dependencies(ireq) -- self.dependency_cache[ireq] = sorted(str(ireq.req) for ireq in dependencies) -+ self.dependency_cache[ireq] = sorted(set(format_requirement(ireq) for ireq in dependencies)) - - # Example: ['Werkzeug>=0.9', 'Jinja2>=2.4'] - dependency_strings = self.dependency_cache[ireq] -diff --git a/pipenv/patched/piptools/utils.py b/pipenv/patched/piptools/utils.py -index 9b4b4c2..8875543 100644 ---- a/pipenv/patched/piptools/utils.py -+++ b/pipenv/patched/piptools/utils.py -@@ -2,10 +2,17 @@ - from __future__ import (absolute_import, division, print_function, - unicode_literals) - -+import os - import sys - from itertools import chain, groupby - from collections import OrderedDict - -+import six -+ -+from pipenv.vendor.packaging.specifiers import SpecifierSet, InvalidSpecifier -+from pipenv.vendor.packaging.version import Version, InvalidVersion, parse as parse_version -+from pipenv.vendor.packaging.markers import Marker, Op, Value, Variable -+ - from ._compat import install_req_from_line - - from .click import style -@@ -14,6 +21,71 @@ from .click import style - UNSAFE_PACKAGES = {'setuptools', 'distribute', 'pip'} - - -+ -+def simplify_markers(ireq): -+ """simplify_markers "This code cleans up markers for a specific :class:`~InstallRequirement`" -+ -+ Clean and deduplicate markers. -+ -+ :param ireq: An InstallRequirement to clean -+ :type ireq: :class:`~pip._internal.req.req_install.InstallRequirement` -+ :return: An InstallRequirement with cleaned Markers -+ :rtype: :class:`~pip._internal.req.req_install.InstallRequirement` -+ """ -+ -+ if not getattr(ireq, 'markers', None): -+ return ireq -+ markers = ireq.markers -+ marker_list = [] -+ if isinstance(markers, six.string_types): -+ if ';' in markers: -+ markers = [Marker(m_str.strip()) for m_str in markers.split(';')] -+ else: -+ markers = Marker(markers) -+ for m in markers._markers: -+ _single_marker = [] -+ if isinstance(m[0], six.string_types): -+ continue -+ if not isinstance(m[0], (list, tuple)): -+ marker_list.append(''.join([_piece.serialize() for _piece in m])) -+ continue -+ for _marker_part in m: -+ if isinstance(_marker_part, six.string_types): -+ _single_marker.append(_marker_part) -+ continue -+ _single_marker.append(''.join([_piece.serialize() for _piece in _marker_part])) -+ _single_marker = [_m.strip() for _m in _single_marker] -+ marker_list.append(tuple(_single_marker,)) -+ marker_str = ' and '.join(list(dedup(tuple(marker_list,)))) if marker_list else '' -+ new_markers = Marker(marker_str) -+ ireq.markers = new_markers -+ new_ireq = install_req_from_line(format_requirement(ireq)) -+ if ireq.constraint: -+ new_ireq.constraint = ireq.constraint -+ return new_ireq -+ -+ -+def clean_requires_python(candidates): -+ """Get a cleaned list of all the candidates with valid specifiers in the `requires_python` attributes.""" -+ all_candidates = [] -+ py_version = parse_version(os.environ.get('PIP_PYTHON_VERSION', '.'.join(map(str, sys.version_info[:3])))) -+ for c in candidates: -+ if getattr(c, "requires_python", None): -+ # Old specifications had people setting this to single digits -+ # which is effectively the same as '>=digit,<digit+1' -+ if c.requires_python.isdigit(): -+ c.requires_python = '>={0},<{1}'.format(c.requires_python, int(c.requires_python) + 1) -+ try: -+ specifierset = SpecifierSet(c.requires_python) -+ except InvalidSpecifier: -+ continue -+ else: -+ if not specifierset.contains(py_version): -+ continue -+ all_candidates.append(c) -+ return all_candidates -+ -+ - def key_from_ireq(ireq): - """Get a standardized key for an InstallRequirement.""" - if ireq.req is None and ireq.link is not None: -@@ -39,16 +111,51 @@ def comment(text): - return style(text, fg='green') - - --def make_install_requirement(name, version, extras, constraint=False): -+def make_install_requirement(name, version, extras, markers, constraint=False): - # If no extras are specified, the extras string is blank - extras_string = "" - if extras: - # Sort extras for stability - extras_string = "[{}]".format(",".join(sorted(extras))) - -- return install_req_from_line( -- str('{}{}=={}'.format(name, extras_string, version)), -- constraint=constraint) -+ if not markers: -+ return install_req_from_line( -+ str('{}{}=={}'.format(name, extras_string, version)), -+ constraint=constraint) -+ else: -+ return install_req_from_line( -+ str('{}{}=={}; {}'.format(name, extras_string, version, str(markers))), -+ constraint=constraint) -+ -+ -+def _requirement_to_str_lowercase_name(requirement): -+ """ -+ Formats a packaging.requirements.Requirement with a lowercase name. -+ -+ This is simply a copy of -+ https://github.com/pypa/packaging/blob/16.8/packaging/requirements.py#L109-L124 -+ modified to lowercase the dependency name. -+ -+ Previously, we were invoking the original Requirement.__str__ method and -+ lowercasing the entire result, which would lowercase the name, *and* other, -+ important stuff that should not be lowercased (such as the marker). See -+ this issue for more information: https://github.com/pypa/pipenv/issues/2113. -+ """ -+ parts = [requirement.name.lower()] -+ -+ if requirement.extras: -+ parts.append("[{0}]".format(",".join(sorted(requirement.extras)))) -+ -+ if requirement.specifier: -+ parts.append(str(requirement.specifier)) -+ -+ if requirement.url: -+ parts.append("@ {0}".format(requirement.url)) -+ -+ if requirement.marker: -+ parts.append("; {0}".format(requirement.marker)) -+ -+ return "".join(parts) - - - def format_requirement(ireq, marker=None, hashes=None): -@@ -59,10 +166,10 @@ def format_requirement(ireq, marker=None, hashes=None): - if ireq.editable: - line = '-e {}'.format(ireq.link) - else: -- line = str(ireq.req).lower() -+ line = _requirement_to_str_lowercase_name(ireq.req) - -- if marker: -- line = '{} ; {}'.format(line, marker) -+ if marker and ';' not in line: -+ line = '{}; {}'.format(line, marker) - - if hashes: - for hash_ in sorted(hashes): diff --git a/tasks/vendoring/patches/patched/safety-main.patch b/tasks/vendoring/patches/patched/safety-main.patch new file mode 100644 index 00000000..3d214863 --- /dev/null +++ b/tasks/vendoring/patches/patched/safety-main.patch @@ -0,0 +1,57 @@ +diff --git a/pipenv/patched/safety/__main__.py b/pipenv/patched/safety/__main__.py +index d9a0bdab..f905408a 100644 +--- a/pipenv/patched/safety/__main__.py ++++ b/pipenv/patched/safety/__main__.py +@@ -1,8 +1,51 @@ + """Allow safety to be executable through `python -m safety`.""" + from __future__ import absolute_import + +-from .cli import cli ++import os ++import sys ++import sysconfig ++ ++ ++PATCHED_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ++PIPENV_DIR = os.path.dirname(PATCHED_DIR) ++VENDORED_DIR = os.path.join("PIPENV_DIR", "vendor") ++ ++ ++def get_site_packages(): ++ prefixes = {sys.prefix, sysconfig.get_config_var('prefix')} ++ try: ++ prefixes.add(sys.real_prefix) ++ except AttributeError: ++ pass ++ form = sysconfig.get_path('purelib', expand=False) ++ py_version_short = '{0[0]}.{0[1]}'.format(sys.version_info) ++ return { ++ form.format(base=prefix, py_version_short=py_version_short) ++ for prefix in prefixes ++ } ++ ++ ++def insert_before_site_packages(*paths): ++ site_packages = get_site_packages() ++ index = None ++ for i, path in enumerate(sys.path): ++ if path in site_packages: ++ index = i ++ break ++ if index is None: ++ sys.path += list(paths) ++ else: ++ sys.path = sys.path[:index] + list(paths) + sys.path[index:] ++ ++ ++def insert_pipenv_dirs(): ++ insert_before_site_packages(os.path.dirname(PIPENV_DIR), PATCHED_DIR, VENDORED_DIR) + + + if __name__ == "__main__": # pragma: no cover ++ insert_pipenv_dirs() ++ yaml_lib = "pipenv.patched.yaml{0}".format(sys.version_info[0]) ++ locals()[yaml_lib] = __import__(yaml_lib) ++ sys.modules["yaml"] = sys.modules[yaml_lib] ++ from safety.cli import cli + cli(prog_name="safety") diff --git a/tasks/vendoring/patches/vendor/click-completion-enum-import.patch b/tasks/vendoring/patches/vendor/click-completion-enum-import.patch deleted file mode 100644 index 20971bf3..00000000 --- a/tasks/vendoring/patches/vendor/click-completion-enum-import.patch +++ /dev/null @@ -1,39 +0,0 @@ -diff --git a/pipenv/vendor/click_completion/__init__.py b/pipenv/vendor/click_completion/__init__.py -index 1443c8f..b849ae2 100644 ---- a/pipenv/vendor/click_completion/__init__.py -+++ b/pipenv/vendor/click_completion/__init__.py -@@ -6,7 +6,13 @@ from __future__ import print_function, absolute_import - import six - - from click import ParamType --from enum import Enum -+if six.PY3: -+ try: -+ from enum import Enum -+ except ImportError: -+ from pipenv.vendor.backports.enum import Enum -+else: -+ from pipenv.vendor.backports.enum import Enum - - from click_completion.core import completion_configuration, get_code, install, shells, resolve_ctx, get_choices, \ - startswith, Shell -diff --git a/pipenv/vendor/click_completion/core.py b/pipenv/vendor/click_completion/core.py -index 2ede6ef..dc47d47 100644 ---- a/pipenv/vendor/click_completion/core.py -+++ b/pipenv/vendor/click_completion/core.py -@@ -10,7 +10,14 @@ import subprocess - - import click - from click import Option, Argument, MultiCommand, echo --from enum import Enum -+import six -+if six.PY3: -+ try: -+ from enum import Enum -+ except ImportError: -+ from pipenv.vendor.backports.enum import Enum -+else: -+ from pipenv.vendor.backports.enum import Enum - - from click_completion.lib import resolve_ctx, split_args, single_quote, double_quote, get_auto_shell - diff --git a/tasks/vendoring/patches/vendor/delegator-close-filehandles.patch b/tasks/vendoring/patches/vendor/delegator-close-filehandles.patch deleted file mode 100644 index 092a6994..00000000 --- a/tasks/vendoring/patches/vendor/delegator-close-filehandles.patch +++ /dev/null @@ -1,88 +0,0 @@ -diff --git a/pipenv/vendor/delegator.py b/pipenv/vendor/delegator.py -index d15aeb97..cf6f91c8 100644 ---- a/pipenv/vendor/delegator.py -+++ b/pipenv/vendor/delegator.py -@@ -7,6 +7,8 @@ import locale - import errno - - from pexpect.popen_spawn import PopenSpawn -+import pexpect -+pexpect.EOF.__module__ = "pexpect.exceptions" - - # Include `unicode` in STR_TYPES for Python 2.X - try: -@@ -110,8 +112,11 @@ class Command(object): - if self.subprocess.before: - result += self.subprocess.before - -- if self.subprocess.after: -- result += self.subprocess.after -+ if self.subprocess.after and self.subprocess.after not in (pexpect.EOF, pexpect.TIMEOUT): -+ try: -+ result += self.subprocess.after -+ except (pexpect.EOF, pexpect.TIMEOUT): -+ pass - - result += self.subprocess.read() - return result -@@ -178,6 +183,7 @@ class Command(object): - # Use subprocess. - if self.blocking: - popen_kwargs = self._default_popen_kwargs.copy() -+ del popen_kwargs["stdin"] - popen_kwargs["universal_newlines"] = not binary - if cwd: - popen_kwargs["cwd"] = cwd -@@ -205,7 +211,10 @@ class Command(object): - if self.blocking: - raise RuntimeError("expect can only be used on non-blocking commands.") - -- self.subprocess.expect(pattern=pattern, timeout=timeout) -+ try: -+ self.subprocess.expect(pattern=pattern, timeout=timeout) -+ except pexpect.EOF: -+ pass - - def send(self, s, end=os.linesep, signal=False): - """Sends the given string or signal to std_in.""" -@@ -234,14 +243,25 @@ class Command(object): - """Blocks until process is complete.""" - if self._uses_subprocess: - # consume stdout and stderr -- try: -- stdout, stderr = self.subprocess.communicate() -- self.__out = stdout -- self.__err = stderr -- except ValueError: -- pass # Don't read from finished subprocesses. -+ if self.blocking: -+ try: -+ stdout, stderr = self.subprocess.communicate() -+ self.__out = stdout -+ self.__err = stderr -+ except ValueError: -+ pass # Don't read from finished subprocesses. -+ else: -+ self.subprocess.stdin.close() -+ self.std_out.close() -+ self.std_err.close() -+ self.subprocess.wait() - else: -- self.subprocess.wait() -+ self.subprocess.sendeof() -+ try: -+ self.subprocess.wait() -+ finally: -+ if self.subprocess.proc.stdout: -+ self.subprocess.proc.stdout.close() - - def pipe(self, command, timeout=None, cwd=None): - """Runs the current command and passes its output to the next -@@ -263,7 +283,6 @@ class Command(object): - c.run(block=False, cwd=cwd) - if data: - c.send(data) -- c.subprocess.sendeof() - c.block() - return c - diff --git a/tasks/vendoring/patches/vendor/dotenv-typing-imports.patch b/tasks/vendoring/patches/vendor/dotenv-typing-imports.patch deleted file mode 100644 index 386cecd1..00000000 --- a/tasks/vendoring/patches/vendor/dotenv-typing-imports.patch +++ /dev/null @@ -1,110 +0,0 @@ -diff --git a/pipenv/vendor/dotenv/__init__.py b/pipenv/vendor/dotenv/__init__.py -index 1867868..b88d9bc 100644 ---- a/pipenv/vendor/dotenv/__init__.py -+++ b/pipenv/vendor/dotenv/__init__.py -@@ -1,6 +1,9 @@ --from typing import Any, Optional -+from .compat import IS_TYPE_CHECKING - from .main import load_dotenv, get_key, set_key, unset_key, find_dotenv, dotenv_values - -+if IS_TYPE_CHECKING: -+ from typing import Any, Optional -+ - - def load_ipython_extension(ipython): - # type: (Any) -> None -diff --git a/pipenv/vendor/dotenv/cli.py b/pipenv/vendor/dotenv/cli.py -index 45f4b76..829b14a 100644 ---- a/pipenv/vendor/dotenv/cli.py -+++ b/pipenv/vendor/dotenv/cli.py -@@ -1,6 +1,5 @@ - import os - import sys --from typing import Any, List - - try: - import click -@@ -9,9 +8,13 @@ except ImportError: - 'Run pip install "python-dotenv[cli]" to fix this.') - sys.exit(1) - -+from .compat import IS_TYPE_CHECKING - from .main import dotenv_values, get_key, set_key, unset_key, run_command - from .version import __version__ - -+if IS_TYPE_CHECKING: -+ from typing import Any, List -+ - - @click.group() - @click.option('-f', '--file', default=os.path.join(os.getcwd(), '.env'), -diff --git a/pipenv/vendor/dotenv/compat.py b/pipenv/vendor/dotenv/compat.py -index 99ffb39..7a8694f 100644 ---- a/pipenv/vendor/dotenv/compat.py -+++ b/pipenv/vendor/dotenv/compat.py -@@ -1,3 +1,4 @@ -+import os - import sys - - if sys.version_info >= (3, 0): -@@ -6,3 +7,15 @@ else: - from StringIO import StringIO # noqa - - PY2 = sys.version_info[0] == 2 # type: bool -+ -+ -+def is_type_checking(): -+ # type: () -> bool -+ try: -+ from typing import TYPE_CHECKING -+ except ImportError: -+ return False -+ return TYPE_CHECKING -+ -+ -+IS_TYPE_CHECKING = os.environ.get("MYPY_RUNNING", is_type_checking()) -diff --git a/pipenv/vendor/dotenv/main.py b/pipenv/vendor/dotenv/main.py -index 0812282..64d4269 100644 ---- a/pipenv/vendor/dotenv/main.py -+++ b/pipenv/vendor/dotenv/main.py -@@ -9,15 +9,17 @@ import shutil - import sys - from subprocess import Popen - import tempfile --from typing import (Any, Dict, Iterator, List, Match, NamedTuple, Optional, # noqa -- Pattern, Union, TYPE_CHECKING, Text, IO, Tuple) # noqa - import warnings - from collections import OrderedDict - from contextlib import contextmanager - --from .compat import StringIO, PY2 -+from .compat import StringIO, PY2, IS_TYPE_CHECKING - --if TYPE_CHECKING: # pragma: no cover -+if IS_TYPE_CHECKING: # pragma: no cover -+ from typing import ( -+ Dict, Iterator, List, Match, Optional, Pattern, Union, -+ Text, IO, Tuple -+ ) - if sys.version_info >= (3, 6): - _PathLike = os.PathLike - else: -@@ -59,10 +61,14 @@ _binding = re.compile( - - _escape_sequence = re.compile(r"\\[\\'\"abfnrtv]") # type: Pattern[Text] - -- --Binding = NamedTuple("Binding", [("key", Optional[Text]), -- ("value", Optional[Text]), -- ("original", Text)]) -+try: -+ from typing import NamedTuple, Optional, Text -+ Binding = NamedTuple("Binding", [("key", Optional[Text]), -+ ("value", Optional[Text]), -+ ("original", Text)]) -+except ImportError: -+ from collections import namedtuple -+ Binding = namedtuple("Binding", ["key", "value", "original"]) - - - def decode_escapes(string): diff --git a/tasks/vendoring/patches/vendor/drop_scandir_import.patch b/tasks/vendoring/patches/vendor/drop_scandir_import.patch deleted file mode 100644 index e80b314b..00000000 --- a/tasks/vendoring/patches/vendor/drop_scandir_import.patch +++ /dev/null @@ -1,16 +0,0 @@ -diff --git a/pipenv/vendor/scandir.py b/pipenv/vendor/scandir.py -index aac7208..8bbae2c 100644 ---- a/pipenv/vendor/scandir.py -+++ b/pipenv/vendor/scandir.py -@@ -25,10 +25,7 @@ from stat import S_IFDIR, S_IFLNK, S_IFREG - import collections - import sys - --try: -- import _scandir --except ImportError: -- _scandir = None -+_scandir = None - - try: - import ctypes diff --git a/tasks/vendoring/patches/vendor/passa-close-session.patch b/tasks/vendoring/patches/vendor/passa-close-session.patch deleted file mode 100644 index 38846bef..00000000 --- a/tasks/vendoring/patches/vendor/passa-close-session.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff --git a/pipenv/vendor/passa/internals/dependencies.py b/pipenv/vendor/passa/internals/dependencies.py -index 53b19b17..358cc33b 100644 ---- a/pipenv/vendor/passa/internals/dependencies.py -+++ b/pipenv/vendor/passa/internals/dependencies.py -@@ -154,6 +154,7 @@ def _get_dependencies_from_json(ireq, sources): - return dependencies - except Exception as e: - print("unable to read dependencies via {0} ({1})".format(url, e)) -+ session.close() - return - - diff --git a/tasks/vendoring/patches/vendor/pip_shims-import.patch b/tasks/vendoring/patches/vendor/pip_shims-import.patch new file mode 100644 index 00000000..8f2ea8b9 --- /dev/null +++ b/tasks/vendoring/patches/vendor/pip_shims-import.patch @@ -0,0 +1,63 @@ +diff --git a/pipenv/vendor/pip_shims/__init__.py b/pipenv/vendor/pip_shims/__init__.py +index c66a075a..febea8df 100644 +--- a/pipenv/vendor/pip_shims/__init__.py ++++ b/pipenv/vendor/pip_shims/__init__.py +@@ -31,10 +31,15 @@ __version__ = "0.6.0" + if "pip_shims" in sys.modules: + # mainly to keep a reference to the old module on hand so it doesn't get + # weakref'd away +- old_module = sys.modules["pip_shims"] ++ if __name__ != "pip_shims": ++ del sys.modules["pip_shims"] + + +-module = sys.modules["pip_shims"] = shims._new() ++if __name__ in sys.modules: ++ old_module = sys.modules[__name__] ++ ++ ++module = sys.modules["pip_shims"] = sys.modules[__name__] = shims._new() + module.shims = shims + module.__dict__.update( + { +diff --git a/pipenv/vendor/pip_shims/utils.py b/pipenv/vendor/pip_shims/utils.py +index 2a31a4df..d3b5e708 100644 +--- a/pipenv/vendor/pip_shims/utils.py ++++ b/pipenv/vendor/pip_shims/utils.py +@@ -11,7 +11,7 @@ import sys + from collections.abc import Callable + from functools import wraps + +-import packaging.version ++from pipenv.vendor.packaging import version as pkg_version + + from .environment import MYPY_RUNNING + +@@ -107,10 +107,10 @@ def _parse(version): + + @memoize + def parse_version(version): +- # type: (str) -> packaging.version._BaseVersion ++ # type: (str) -> pkg_version._BaseVersion + if not isinstance(version, STRING_TYPES): + raise TypeError("Can only derive versions from string, got {!r}".format(version)) +- return packaging.version.parse(version) ++ return pkg_version.parse(version) + + + @memoize +@@ -130,10 +130,10 @@ def split_package(module, subimport=None): + :Example: + + >>> from pip_shims.utils import split_package +- >>> split_package("pipenv.patched.notpip._internal.req.req_install", subimport="InstallRequirement") +- ("pipenv.patched.notpip._internal.req.req_install", "InstallRequirement") +- >>> split_package("pipenv.patched.notpip._internal.cli.base_command") +- ("pipenv.patched.notpip._internal.cli", "base_command") ++ >>> split_package("pip._internal.req.req_install", subimport="InstallRequirement") ++ ("pip._internal.req.req_install", "InstallRequirement") ++ >>> split_package("pip._internal.cli.base_command") ++ ("pip._internal.cli", "base_command") + """ + package = None + if subimport: diff --git a/tasks/vendoring/patches/vendor/pip_shims_module_names.patch b/tasks/vendoring/patches/vendor/pip_shims_module_names.patch deleted file mode 100644 index 8658dcaa..00000000 --- a/tasks/vendoring/patches/vendor/pip_shims_module_names.patch +++ /dev/null @@ -1,17 +0,0 @@ -diff --git a/pipenv/vendor/pip_shims/__init__.py b/pipenv/vendor/pip_shims/__init__.py -index 1342f793..70fb0d58 100644 ---- a/pipenv/vendor/pip_shims/__init__.py -+++ b/pipenv/vendor/pip_shims/__init__.py -@@ -8,10 +8,10 @@ __version__ = '0.3.1' - from . import shims - - --old_module = sys.modules["pip_shims"] -+old_module = sys.modules[__name__] - - --module = sys.modules["pip_shims"] = shims._new() -+module = sys.modules[__name__] = shims._new() - module.shims = shims - module.__dict__.update({ - '__file__': __file__, diff --git a/tasks/vendoring/patches/vendor/pipdeptree-updated-pip18.patch b/tasks/vendoring/patches/vendor/pipdeptree-update-pip-import.patch similarity index 65% rename from tasks/vendoring/patches/vendor/pipdeptree-updated-pip18.patch rename to tasks/vendoring/patches/vendor/pipdeptree-update-pip-import.patch index 5d5a381f..810d674f 100644 --- a/tasks/vendoring/patches/vendor/pipdeptree-updated-pip18.patch +++ b/tasks/vendoring/patches/vendor/pipdeptree-update-pip-import.patch @@ -1,10 +1,10 @@ diff --git a/pipenv/vendor/pipdeptree.py b/pipenv/vendor/pipdeptree.py -index 7820aa5..2082fc8 100644 +index e15cd8f4..9f692879 100644 --- a/pipenv/vendor/pipdeptree.py +++ b/pipenv/vendor/pipdeptree.py -@@ -13,11 +13,9 @@ try: +@@ -21,13 +21,11 @@ try: except ImportError: - from ordereddict import OrderedDict + from collections import Mapping -try: - from pipenv.patched.notpip._internal.utils.misc import get_installed_distributions @@ -13,8 +13,10 @@ index 7820aa5..2082fc8 100644 - from pipenv.patched.notpip import get_installed_distributions, FrozenRequirement +pardir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +sys.path.append(pardir) -+from pipenv.vendor.pip_shims import get_installed_distributions, FrozenRequirement ++from pipenv.vendor.pip_shims.shims import get_installed_distributions, FrozenRequirement - import pkg_resources +-from pipenv.patched.notpip._vendor import pkg_resources ++import pkg_resources # inline: - + # from graphviz import backend, Digraph + diff --git a/tasks/vendoring/patches/vendor/tomlkit-fix.patch b/tasks/vendoring/patches/vendor/tomlkit-fix.patch deleted file mode 100644 index 4aa6c16f..00000000 --- a/tasks/vendoring/patches/vendor/tomlkit-fix.patch +++ /dev/null @@ -1,152 +0,0 @@ -diff --git a/pipenv/vendor/tomlkit/api.py b/pipenv/vendor/tomlkit/api.py -index e541c20c..0ac26752 100644 ---- a/pipenv/vendor/tomlkit/api.py -+++ b/pipenv/vendor/tomlkit/api.py -@@ -1,7 +1,5 @@ - import datetime as _datetime - --from typing import Tuple -- - from ._utils import parse_rfc3339 - from .container import Container - from .items import AoT -diff --git a/pipenv/vendor/tomlkit/container.py b/pipenv/vendor/tomlkit/container.py -index cb8af1d5..9b5db5cb 100644 ---- a/pipenv/vendor/tomlkit/container.py -+++ b/pipenv/vendor/tomlkit/container.py -@@ -1,15 +1,7 @@ - from __future__ import unicode_literals - - import copy - --from typing import Any --from typing import Dict --from typing import Generator --from typing import List --from typing import Optional --from typing import Tuple --from typing import Union -- - from ._compat import decode - from .exceptions import KeyAlreadyPresent - from .exceptions import NonExistentKey -@@ -17,6 +9,7 @@ from .items import Item - from .items import Key - from .items import Null - from .items import Table -+from .items import Trivia - from .items import Whitespace - from .items import item as _item - -@@ -221,7 +214,12 @@ class Container(dict): - for i in idx: - self._body[i] = (None, Null()) - else: -- self._body[idx] = (None, Null()) -+ old_data = self._body[idx][1] -+ trivia = getattr(old_data, "trivia", None) -+ if trivia and trivia.comment: -+ self._body[idx] = (None, Comment(Trivia(comment_ws="", comment=trivia.comment))) -+ else: -+ self._body[idx] = (None, Null()) - - super(Container, self).__delitem__(key.key) - -diff --git a/pipenv/vendor/tomlkit/exceptions.py b/pipenv/vendor/tomlkit/exceptions.py -index 4fbc667b..c1a4e620 100644 ---- a/pipenv/vendor/tomlkit/exceptions.py -+++ b/pipenv/vendor/tomlkit/exceptions.py -@@ -1,5 +1,3 @@ --from typing import Optional -- - - class TOMLKitError(Exception): - -diff --git a/pipenv/vendor/tomlkit/items.py b/pipenv/vendor/tomlkit/items.py -index 375b5f02..cccfd4a1 100644 ---- a/pipenv/vendor/tomlkit/items.py -+++ b/pipenv/vendor/tomlkit/items.py -@@ -6,14 +6,6 @@ import string - from datetime import date - from datetime import datetime - from datetime import time --from enum import Enum --from typing import Any --from typing import Dict --from typing import Generator --from typing import List --from typing import Optional --from typing import Union -- - - from ._compat import PY2 - from ._compat import decode -@@ -22,9 +14,12 @@ from ._compat import unicode - from ._utils import escape_string - - if PY2: -+ from pipenv.vendor.backports.enum import Enum - from pipenv.vendor.backports.functools_lru_cache import lru_cache - else: -+ from enum import Enum - from functools import lru_cache -+from toml.decoder import InlineTableDict - - - def item(value, _parent=None): -@@ -40,7 +35,10 @@ def item(value, _parent=None): - elif isinstance(value, float): - return Float(value, Trivia(), str(value)) - elif isinstance(value, dict): -- val = Table(Container(), Trivia(), False) -+ if isinstance(value, InlineTableDict): -+ val = InlineTable(Container(), Trivia()) -+ else: -+ val = Table(Container(), Trivia(), False) - for k, v in sorted(value.items(), key=lambda i: (isinstance(i[1], dict), i[0])): - val[k] = item(v, _parent=val) - -diff --git a/pipenv/vendor/tomlkit/parser.py b/pipenv/vendor/tomlkit/parser.py -index 7b948331..3f507bb4 100644 ---- a/pipenv/vendor/tomlkit/parser.py -+++ b/pipenv/vendor/tomlkit/parser.py -@@ -4,13 +4,6 @@ from __future__ import unicode_literals - import re - import string - --from typing import Any --from typing import Generator --from typing import List --from typing import Optional --from typing import Tuple --from typing import Union -- - from ._compat import chr - from ._compat import decode - from ._utils import _escaped -diff --git a/pipenv/vendor/tomlkit/source.py b/pipenv/vendor/tomlkit/source.py -index 1a96e058..dcfdafd0 100644 ---- a/pipenv/vendor/tomlkit/source.py -+++ b/pipenv/vendor/tomlkit/source.py -@@ -4,8 +4,6 @@ from __future__ import unicode_literals - import itertools - - from copy import copy --from typing import Optional --from typing import Tuple - - from ._compat import PY2 - from ._compat import unicode -diff --git a/pipenv/vendor/tomlkit/toml_file.py b/pipenv/vendor/tomlkit/toml_file.py -index 3b416664..631e9959 100644 ---- a/pipenv/vendor/tomlkit/toml_file.py -+++ b/pipenv/vendor/tomlkit/toml_file.py -@@ -1,8 +1,5 @@ - import io - --from typing import Any --from typing import Dict -- - from .api import loads - from .toml_document import TOMLDocument - diff --git a/tasks/vendoring/patches/vendor/tomlkit-items-fix.patch b/tasks/vendoring/patches/vendor/tomlkit-items-fix.patch new file mode 100644 index 00000000..128a1964 --- /dev/null +++ b/tasks/vendoring/patches/vendor/tomlkit-items-fix.patch @@ -0,0 +1,13 @@ +diff --git a/pipenv/vendor/tomlkit/items.py b/pipenv/vendor/tomlkit/items.py +index 6617c522..9e746a8c 100644 +--- a/pipenv/vendor/tomlkit/items.py ++++ b/pipenv/vendor/tomlkit/items.py +@@ -50,7 +50,7 @@ def item(value, _parent=None, _sort_keys=False): + val[k] = item(v, _parent=val, _sort_keys=_sort_keys) + + return val +- elif isinstance(value, list): ++ elif isinstance(value, (tuple, list)): + if value and isinstance(value[0], dict): + a = AoT([]) + else: diff --git a/tasks/vendoring/patches/vendor/vistir-imports.patch b/tasks/vendoring/patches/vendor/vistir-imports.patch deleted file mode 100644 index 725e8a56..00000000 --- a/tasks/vendoring/patches/vendor/vistir-imports.patch +++ /dev/null @@ -1,44 +0,0 @@ -diff --git a/pipenv/vendor/vistir/backports/tempfile.py b/pipenv/vendor/vistir/backports/tempfile.py -index f5594a2d..a3d7f3df 100644 ---- a/pipenv/vendor/vistir/backports/tempfile.py -+++ b/pipenv/vendor/vistir/backports/tempfile.py -@@ -12,7 +12,7 @@ import six - try: - from weakref import finalize - except ImportError: -- from backports.weakref import finalize -+ from pipenv.vendor.backports.weakref import finalize - - - def fs_encode(path): -diff --git a/pipenv/vendor/vistir/compat.py b/pipenv/vendor/vistir/compat.py -index b5904bc7..a44aafbe 100644 ---- a/pipenv/vendor/vistir/compat.py -+++ b/pipenv/vendor/vistir/compat.py -@@ -43,7 +43,7 @@ __all__ = [ - if sys.version_info >= (3, 5): # pragma: no cover - from pathlib import Path - else: # pragma: no cover -- from pathlib2 import Path -+ from pipenv.vendor.pathlib2 import Path - - if six.PY3: # pragma: no cover - # Only Python 3.4+ is supported -@@ -53,14 +53,14 @@ if six.PY3: # pragma: no cover - from weakref import finalize - else: # pragma: no cover - # Only Python 2.7 is supported -- from backports.functools_lru_cache import lru_cache -+ from pipenv.vendor.backports.functools_lru_cache import lru_cache - from .backports.functools import partialmethod # type: ignore -- from backports.shutil_get_terminal_size import get_terminal_size -+ from pipenv.vendor.backports.shutil_get_terminal_size import get_terminal_size - from .backports.surrogateescape import register_surrogateescape - - register_surrogateescape() - NamedTemporaryFile = _NamedTemporaryFile -- from backports.weakref import finalize # type: ignore -+ from pipenv.vendor.backports.weakref import finalize # type: ignore - - try: - # Introduced Python 3.5 diff --git a/tasks/vendoring/patches/vendor/yaspin-signal-handling.patch b/tasks/vendoring/patches/vendor/yaspin-signal-handling.patch index 511e782a..b74a08dc 100644 --- a/tasks/vendoring/patches/vendor/yaspin-signal-handling.patch +++ b/tasks/vendoring/patches/vendor/yaspin-signal-handling.patch @@ -1,28 +1,24 @@ diff --git a/pipenv/vendor/yaspin/core.py b/pipenv/vendor/yaspin/core.py -index d01fb98e..06b8b621 100644 +index 76dc2439..d8a0716c 100644 --- a/pipenv/vendor/yaspin/core.py +++ b/pipenv/vendor/yaspin/core.py -@@ -16,6 +16,9 @@ import sys - import threading - import time +@@ -19,11 +19,15 @@ import time + from typing import List, Set, Union -+import colorama + from pipenv.vendor.termcolor import colored ++from pipenv.vendor import colorama +from pipenv.vendor.vistir import cursor -+ - from .base_spinner import default_spinner - from .compat import PY2, basestring, builtin_str, bytes, iteritems, str - from .constants import COLOR_ATTRS, COLOR_MAP, ENCODING, SPINNER_ATTRS -@@ -23,6 +26,9 @@ from .helpers import to_unicode - from .termcolor import colored + from .base_spinner import Spinner, default_spinner + from .constants import COLOR_ATTRS, COLOR_MAP, SPINNER_ATTRS + from .helpers import to_unicode +colorama.init() + -+ - class Yaspin(object): + + class Yaspin: # pylint: disable=useless-object-inheritance,too-many-instance-attributes """Implements a context manager that spawns a thread - to write spinner frames into a tty (stdout) during -@@ -369,11 +375,14 @@ class Yaspin(object): +@@ -401,11 +405,14 @@ class Yaspin: # pylint: disable=useless-object-inheritance,too-many-instance-at # SIGKILL cannot be caught or ignored, and the receiving # process cannot perform any clean-up upon receiving this # signal. @@ -40,9 +36,9 @@ index d01fb98e..06b8b621 100644 + except AttributeError: + pass - for sig, sig_handler in iteritems(self._sigmap): + for sig, sig_handler in self._sigmap.items(): # A handler for a particular signal, once set, remains -@@ -521,14 +530,12 @@ class Yaspin(object): +@@ -528,13 +535,11 @@ class Yaspin: # pylint: disable=useless-object-inheritance,too-many-instance-at @staticmethod def _hide_cursor(): @@ -58,5 +54,3 @@ index d01fb98e..06b8b621 100644 @staticmethod def _clear_line(): -- sys.stdout.write("\033[K") -+ sys.stdout.write(chr(27) + "[K") diff --git a/tasks/vendoring/vendor_passa.py b/tasks/vendoring/vendor_passa.py deleted file mode 100644 index 2da91259..00000000 --- a/tasks/vendoring/vendor_passa.py +++ /dev/null @@ -1,20 +0,0 @@ -import invoke - -from pipenv._compat import TemporaryDirectory - -from . import _get_vendor_dir, log - - -@invoke.task -def vendor_passa(ctx): - with TemporaryDirectory(prefix='passa') as passa_dir: - vendor_dir = _get_vendor_dir(ctx).absolute().as_posix() - ctx.run("git clone https://github.com/sarugaku/passa.git {0}".format(passa_dir.name)) - with ctx.cd("{0}".format(passa_dir.name)): - # ctx.run("git checkout 0.3.0") - ctx.run("pip install plette[validation] requirementslib distlib pip-shims -q --exists-action=i") - log("Packing Passa") - ctx.run("invoke pack") - log("Moving pack to vendor dir!") - ctx.run("mv pack/passa.zip {0}".format(vendor_dir)) - log("Successfully vendored passa!") diff --git a/tests/conftest.py b/tests/conftest.py index e69de29b..13778403 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -0,0 +1,17 @@ +import pytest +import os + +# Note that we have to do this *before* `pipenv.environments` gets imported, +# which is why we're doing it here as a side effect of importing this module. +# CI=1 is necessary as a workaround for https://github.com/pypa/pipenv/issues/4909 +os.environ['CI'] = '1' + +def pytest_sessionstart(session): + import pipenv.environments + assert pipenv.environments.PIPENV_IS_CI + + +@pytest.fixture() +def project(): + from pipenv.project import Project + return Project() diff --git a/tests/fixtures/cython-import-package/pyproject.toml b/tests/fixtures/cython-import-package/pyproject.toml new file mode 100644 index 00000000..661b63c5 --- /dev/null +++ b/tests/fixtures/cython-import-package/pyproject.toml @@ -0,0 +1,52 @@ +[build-system] +requires = ["setuptools >= 40.6.0", "setuptools-scm", "cython"] +build-backend = "setuptools.build_meta" + +[tool.black] +line-length = 90 +target_version = ['py27', 'py35', 'py36', 'py37', 'py38'] +include = '\.pyi?$' +exclude = ''' +/( + \.eggs + | \.git + | \.hg + | \.mypy_cache + | \.tox + | \.pyre_configuration + | \.venv + | _build + | buck-out + | build + | dist +) +''' + +[tool.towncrier] +package = 'cython-import-package' +package_dir = 'src' +filename = 'CHANGELOG.rst' +directory = 'news/' +title_format = '{version} ({project_date})' +issue_format = '`#{issue} <https://github.com/sarugaku/cython_import_package/issues/{issue}>`_' +template = 'tasks/CHANGELOG.rst.jinja2' + + [[tool.towncrier.type]] + directory = 'feature' + name = 'Features' + showcontent = true + + [[tool.towncrier.type]] + directory = 'bugfix' + name = 'Bug Fixes' + showcontent = true + + [[tool.towncrier.type]] + directory = 'trivial' + name = 'Trivial Changes' + showcontent = false + + [[tool.towncrier.type]] + directory = 'removal' + name = 'Removals and Deprecations' + showcontent = true diff --git a/tests/fixtures/cython-import-package/setup.cfg b/tests/fixtures/cython-import-package/setup.cfg new file mode 100644 index 00000000..7069d27d --- /dev/null +++ b/tests/fixtures/cython-import-package/setup.cfg @@ -0,0 +1,57 @@ +[metadata] +name = cython_import_package +package_name = cython-import-package +description = A fake python package. +url = https://github.com/sarugaku/cython_import_package +author = Dan Ryan +author_email = dan@danryan.co +long_description = file: README.rst +license = ISC License +keywords = fake package test +classifier = + Development Status :: 1 - Planning + License :: OSI Approved :: ISC License (ISCL) + Operating System :: OS Independent + Programming Language :: Python :: 2 + Programming Language :: Python :: 2.6 + Programming Language :: Python :: 2.7 + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.4 + Programming Language :: Python :: 3.5 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Topic :: Software Development :: Libraries :: Python Modules + +[options.extras_require] +tests = + pytest + pytest-xdist + pytest-cov + pytest-timeout + readme-renderer[md] + twine +dev = + black;python_version>="3.6" + flake8 + flake8-bugbear;python_version>="3.5" + invoke + isort + mypy;python_version>="3.5" + parver + pre-commit + rope + wheel + +[options] +zip_safe = true +python_requires = >=2.6,!=3.0,!=3.1,!=3.2,!=3.3 +install_requires = + attrs + vistir + +[bdist_wheel] +universal = 1 + +[egg_info] +tag_build = +tag_date = 0 diff --git a/tests/fixtures/cython-import-package/setup.py b/tests/fixtures/cython-import-package/setup.py new file mode 100644 index 00000000..3a816a94 --- /dev/null +++ b/tests/fixtures/cython-import-package/setup.py @@ -0,0 +1,43 @@ +import ast +import os + +from setuptools import setup, find_packages +from setuptools.command.test import test as TestCommand + +# ORDER MATTERS +# Import this after setuptools or it will fail +from Cython.Build import cythonize # noqa: I100 +import Cython.Distutils + + + +ROOT = os.path.dirname(__file__) + +PACKAGE_NAME = 'cython_import_package' + +VERSION = None + +with open(os.path.join(ROOT, 'src', PACKAGE_NAME.replace("-", "_"), '__init__.py')) as f: + for line in f: + if line.startswith('__version__ = '): + VERSION = ast.literal_eval(line[len('__version__ = '):].strip()) + break +if VERSION is None: + raise OSError('failed to read version') + + +# Put everything in setup.cfg, except those that don't actually work? +setup( + # These really don't work. + package_dir={'': 'src'}, + packages=find_packages('src'), + + # I don't know how to specify an empty key in setup.cfg. + package_data={ + '': ['LICENSE*', 'README*'], + }, + setup_requires=["setuptools_scm", "cython"], + + # I need this to be dynamic. + version=VERSION, +) diff --git a/tests/fixtures/cython-import-package/src/cython_import_package/__init__.py b/tests/fixtures/cython-import-package/src/cython_import_package/__init__.py new file mode 100644 index 00000000..f102a9ca --- /dev/null +++ b/tests/fixtures/cython-import-package/src/cython_import_package/__init__.py @@ -0,0 +1 @@ +__version__ = "0.0.1" diff --git a/tests/fixtures/fake-package/docs/conf.py b/tests/fixtures/fake-package/docs/conf.py index 3aded982..5f59ed52 100644 --- a/tests/fixtures/fake-package/docs/conf.py +++ b/tests/fixtures/fake-package/docs/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Configuration file for the Sphinx documentation builder. # diff --git a/tests/fixtures/fake-package/setup.py b/tests/fixtures/fake-package/setup.py index 1d2c88f8..3048d506 100644 --- a/tests/fixtures/fake-package/setup.py +++ b/tests/fixtures/fake-package/setup.py @@ -16,7 +16,7 @@ with open(os.path.join(ROOT, 'src', PACKAGE_NAME.replace("-", "_"), '__init__.py VERSION = ast.literal_eval(line[len('__version__ = '):].strip()) break if VERSION is None: - raise EnvironmentError('failed to read version') + raise OSError('failed to read version') # Put everything in setup.cfg, except those that don't actually work? diff --git a/tests/fixtures/fake-package/tasks/__init__.py b/tests/fixtures/fake-package/tasks/__init__.py index a8cedab4..9b68d16c 100644 --- a/tests/fixtures/fake-package/tasks/__init__.py +++ b/tests/fixtures/fake-package/tasks/__init__.py @@ -152,14 +152,14 @@ def build_docs(ctx): minor = [str(i) for i in _current_version.release[:2]] docs_folder = (ROOT / 'docs').as_posix() if not docs_folder.endswith('/'): - docs_folder = '{0}/'.format(docs_folder) + docs_folder = f'{docs_folder}/' args = ["--ext-autodoc", "--ext-viewcode", "-o", docs_folder] args.extend(["-A", "'Dan Ryan <dan@danryan.co>'"]) args.extend(["-R", str(_current_version)]) args.extend(["-V", ".".join(minor)]) args.extend(["-e", "-M", "-F", f"src/{PACKAGE_NAME}"]) print("Building docs...") - ctx.run("sphinx-apidoc {0}".format(" ".join(args))) + ctx.run("sphinx-apidoc {}".format(" ".join(args))) @invoke.task diff --git a/tests/fixtures/legacy-backend-package/pyproject.toml b/tests/fixtures/legacy-backend-package/pyproject.toml new file mode 100644 index 00000000..e646fb3c --- /dev/null +++ b/tests/fixtures/legacy-backend-package/pyproject.toml @@ -0,0 +1,51 @@ +[build-system] +requires = ["setuptools>=30.3.0", "wheel", "setuptools_scm>=3.3.1"] + +[tool.black] +line-length = 90 +target_version = ['py27', 'py35', 'py36', 'py37', 'py38'] +include = '\.pyi?$' +exclude = ''' +/( + \.eggs + | \.git + | \.hg + | \.mypy_cache + | \.tox + | \.pyre_configuration + | \.venv + | _build + | buck-out + | build + | dist +) +''' + +[tool.towncrier] +package = 'legacy-backend-package' +package_dir = 'src' +filename = 'CHANGELOG.rst' +directory = 'news/' +title_format = '{version} ({project_date})' +issue_format = '`#{issue} <https://github.com/sarugaku/legacy_backend_package/issues/{issue}>`_' +template = 'tasks/CHANGELOG.rst.jinja2' + + [[tool.towncrier.type]] + directory = 'feature' + name = 'Features' + showcontent = true + + [[tool.towncrier.type]] + directory = 'bugfix' + name = 'Bug Fixes' + showcontent = true + + [[tool.towncrier.type]] + directory = 'trivial' + name = 'Trivial Changes' + showcontent = false + + [[tool.towncrier.type]] + directory = 'removal' + name = 'Removals and Deprecations' + showcontent = true diff --git a/tests/fixtures/legacy-backend-package/setup.cfg b/tests/fixtures/legacy-backend-package/setup.cfg new file mode 100644 index 00000000..4d98081c --- /dev/null +++ b/tests/fixtures/legacy-backend-package/setup.cfg @@ -0,0 +1,127 @@ +[metadata] +name = legacy_backend_package +package_name = legacy-backend-package +description = A fake python package. +url = https://github.com/sarugaku/legacy_backend_package +author = Dan Ryan +author_email = dan@danryan.co +long_description = file: README.rst +license = ISC License +keywords = fake package test +classifier = + Development Status :: 1 - Planning + License :: OSI Approved :: ISC License (ISCL) + Operating System :: OS Independent + Programming Language :: Python :: 2 + Programming Language :: Python :: 2.6 + Programming Language :: Python :: 2.7 + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.4 + Programming Language :: Python :: 3.5 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Topic :: Software Development :: Libraries :: Python Modules + +[options.extras_require] +tests = + pytest + pytest-xdist + pytest-cov + pytest-timeout + readme-renderer[md] + twine +dev = + black;python_version>="3.6" + flake8 + flake8-bugbear;python_version>="3.5" + invoke + isort + mypy;python_version>="3.5" + parver + pre-commit + rope + wheel + +[options] +zip_safe = true +python_requires = >=2.6,!=3.0,!=3.1,!=3.2,!=3.3 +setup_requires = + setuptools_scm>=3.3.1 +install_requires = + attrs + vistir + +[bdist_wheel] +universal = 1 + +[egg_info] +tag_build = +tag_date = 0 + + +[tool:pytest] +strict = true +plugins = cov flake8 +addopts = -ra +testpaths = tests/ +norecursedirs = .* build dist news tasks docs +flake8-ignore = + docs/source/* ALL + tests/*.py ALL + setup.py ALL +filterwarnings = + ignore::DeprecationWarning + ignore::PendingDeprecationWarning + +[isort] +atomic = true +not_skip = __init__.py +line_length = 90 +indent = ' ' +multi_line_output = 3 +known_third_party = invoke,parver,pytest,setuptools,towncrier +known_first_party = + legacy_backend_package + tests +combine_as_imports=True +include_trailing_comma = True +force_grid_wrap=0 + +[flake8] +max-line-length = 90 +select = C,E,F,W,B,B950 +ignore = + # The default ignore list: + D203,F401,E123,E203,W503,E501,E402 + #E121,E123,E126,E226,E24,E704, + # Our additions: + # E123: closing bracket does not match indentation of opening bracket’s line + # E203: whitespace before ‘:’ + # E129: visually indented line with same indent as next logical line + # E222: multiple spaces after operator + # E231: missing whitespace after ',' + # D203: 1 blank line required before class docstring + # E402: module level import not at top of file + # E501: line too long (using B950 from flake8-bugbear) + # F401: Module imported but unused + # W503: line break before binary operator (not a pep8 issue, should be ignored) +exclude = + .tox, + .git, + __pycache__, + docs/source/*, + build, + dist, + tests/*, + *.pyc, + *.egg-info, + .cache, + .eggs, + setup.py, +max-complexity=13 + +[mypy] +ignore_missing_imports=true +follow_imports=skip +html_report=mypyhtml +python_version=2.7 diff --git a/tests/fixtures/legacy-backend-package/setup.py b/tests/fixtures/legacy-backend-package/setup.py new file mode 100644 index 00000000..bb43fcaa --- /dev/null +++ b/tests/fixtures/legacy-backend-package/setup.py @@ -0,0 +1,35 @@ +import ast +import os + +from setuptools import setup, find_packages +from setuptools.command.test import test as TestCommand + +ROOT = os.path.dirname(__file__) + +PACKAGE_NAME = 'legacy_backend_package' + +VERSION = None + +with open(os.path.join(ROOT, 'src', PACKAGE_NAME.replace("-", "_"), '__init__.py')) as f: + for line in f: + if line.startswith('__version__ = '): + VERSION = ast.literal_eval(line[len('__version__ = '):].strip()) + break +if VERSION is None: + raise OSError('failed to read version') + + +# Put everything in setup.cfg, except those that don't actually work? +setup( + # These really don't work. + package_dir={'': 'src'}, + packages=find_packages('src'), + + # I don't know how to specify an empty key in setup.cfg. + package_data={ + '': ['LICENSE*', 'README*'], + }, + + # I need this to be dynamic. + version=VERSION, +) diff --git a/tests/fixtures/legacy-backend-package/src/legacy_backend_package/__init__.py b/tests/fixtures/legacy-backend-package/src/legacy_backend_package/__init__.py new file mode 100644 index 00000000..f102a9ca --- /dev/null +++ b/tests/fixtures/legacy-backend-package/src/legacy_backend_package/__init__.py @@ -0,0 +1 @@ +__version__ = "0.0.1" diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 53973d34..50f0ea80 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -1,37 +1,38 @@ -# -*- coding=utf-8 -*- -from __future__ import absolute_import, print_function - import errno +import functools import json import logging import os +import shlex import shutil +import traceback import sys import warnings - +from pathlib import Path from shutil import rmtree as _rmtree +from tempfile import TemporaryDirectory import pytest import requests +from click.testing import CliRunner +from pytest_pypi.app import prepare_fixtures +from pytest_pypi.app import prepare_packages as prepare_pypi_packages -from pipenv._compat import Path +from pipenv.cli import cli from pipenv.exceptions import VirtualenvActivationException -from pipenv.vendor import delegator, toml, tomlkit -from pipenv.vendor.vistir.compat import ( - FileNotFoundError, PermissionError, ResourceWarning, TemporaryDirectory, - fs_encode, fs_str -) +from pipenv.utils import subprocess_run +from pipenv.vendor import toml, tomlkit +from pipenv.vendor.vistir.compat import fs_encode, fs_str from pipenv.vendor.vistir.contextmanagers import temp_environ from pipenv.vendor.vistir.misc import run from pipenv.vendor.vistir.path import ( create_tracked_tempdir, handle_remove_readonly, mkdir_p ) -from pytest_pypi.app import prepare_fixtures -from pytest_pypi.app import prepare_packages as prepare_pypi_packages log = logging.getLogger(__name__) warnings.simplefilter("default", category=ResourceWarning) +cli_runner = CliRunner(mix_stderr=False) HAS_WARNED_GITHUB = False @@ -49,11 +50,11 @@ def check_internet(): try_internet(url) except KeyboardInterrupt: warnings.warn( - "Skipped connecting to internet: {0}".format(url), RuntimeWarning + f"Skipped connecting to internet: {url}", RuntimeWarning ) except Exception: warnings.warn( - "Failed connecting to internet: {0}".format(url), RuntimeWarning + f"Failed connecting to internet: {url}", RuntimeWarning ) else: has_internet = True @@ -69,8 +70,8 @@ def check_github_ssh(): # GitHub does not provide shell access.' if ssh keys are available and # registered with GitHub. Otherwise, the command will fail with # return_code=255 and say 'Permission denied (publickey).' - c = delegator.run('ssh -T git@github.com') - res = True if c.return_code == 1 else False + c = subprocess_run('ssh -o StrictHostKeyChecking=no -o CheckHostIP=no -T git@github.com', timeout=30, shell=True) + res = True if c.returncode == 1 else False except KeyboardInterrupt: warnings.warn( "KeyboardInterrupt while checking GitHub ssh access", RuntimeWarning @@ -90,11 +91,8 @@ def check_github_ssh(): def check_for_mercurial(): - c = delegator.run("hg --help") - if c.return_code != 0: - return False - else: - return True + c = subprocess_run("hg --help", shell=True) + return c.returncode == 0 TESTS_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -111,20 +109,22 @@ def pytest_runtest_setup(item): pytest.skip('requires github ssh') if item.get_closest_marker('needs_hg') is not None and not WE_HAVE_HG: pytest.skip('requires mercurial') - if item.get_closest_marker('skip_py27_win') is not None and ( - sys.version_info[:2] <= (2, 7) and os.name == "nt" + if item.get_closest_marker('skip_py38') is not None and ( + sys.version_info[:2] == (3, 8) ): - pytest.skip('must use python > 2.7 on windows') - if item.get_closest_marker('py3_only') is not None and ( - sys.version_info < (3, 0) - ): - pytest.skip('test only runs on python 3') + pytest.skip('test not applicable on python 3.8') if item.get_closest_marker('skip_osx') is not None and sys.platform == 'darwin': pytest.skip('test does not apply on OSX') if item.get_closest_marker('lte_py36') is not None and ( sys.version_info >= (3, 7) ): pytest.skip('test only runs on python < 3.7') + if item.get_closest_marker('skip_py36') is not None and ( + sys.version_info[:2] == (3, 6) + ): + pytest.skip('test is skipped on python 3.6') + if item.get_closest_marker('skip_windows') is not None and (os.name == 'nt'): + pytest.skip('test does not run on windows') @pytest.fixture @@ -137,10 +137,7 @@ def pathlib_tmpdir(request, tmpdir): def _create_tracked_dir(): - tmp_location = os.environ.get("TEMP", os.environ.get("TMP")) temp_args = {"prefix": "pipenv-", "suffix": "-test"} - if tmp_location is not None: - temp_args["dir"] = tmp_location temp_path = create_tracked_tempdir(**temp_args) return temp_path @@ -151,6 +148,22 @@ def vistir_tmpdir(): yield Path(temp_path) +@pytest.fixture() +def local_tempdir(request): + old_temp = os.environ.get("TEMP", "") + new_temp = Path(os.getcwd()).absolute() / "temp" + new_temp.mkdir(parents=True, exist_ok=True) + os.environ["TEMP"] = new_temp.as_posix() + + def finalize(): + os.environ['TEMP'] = fs_str(old_temp) + _rmtree_func(new_temp.as_posix()) + + request.addfinalizer(finalize) + with TemporaryDirectory(dir=new_temp.as_posix()) as temp_dir: + yield Path(temp_dir) + + @pytest.fixture(name='create_tmpdir') def vistir_tmpdir_factory(): @@ -183,22 +196,21 @@ def isolate(create_tmpdir): os.environ["GIT_CONFIG_NOSYSTEM"] = fs_str("1") os.environ["GIT_AUTHOR_NAME"] = fs_str("pipenv") os.environ["GIT_AUTHOR_EMAIL"] = fs_str("pipenv@pipenv.org") + os.environ["GIT_ASK_YESNO"] = fs_str("false") workon_home = create_tmpdir() os.environ["WORKON_HOME"] = fs_str(str(workon_home)) - os.environ["HOME"] = home_dir + os.environ["HOME"] = os.path.abspath(home_dir) mkdir_p(os.path.join(home_dir, "projects")) # Ignore PIPENV_ACTIVE so that it works as under a bare environment. os.environ.pop("PIPENV_ACTIVE", None) os.environ.pop("VIRTUAL_ENV", None) - global WE_HAVE_GITHUB_SSH_KEYS - WE_HAVE_GITHUB_SSH_KEYS = check_github_ssh() WE_HAVE_INTERNET = check_internet() -WE_HAVE_GITHUB_SSH_KEYS = check_github_ssh() +WE_HAVE_GITHUB_SSH_KEYS = False -class _Pipfile(object): +class _Pipfile: def __init__(self, path): self.path = path if self.path.exists(): @@ -209,7 +221,7 @@ class _Pipfile(object): self.document["requires"] = self.document.get("requires", tomlkit.table()) self.document["packages"] = self.document.get("packages", tomlkit.table()) self.document["dev_packages"] = self.document.get("dev_packages", tomlkit.table()) - super(_Pipfile, self).__init__() + super().__init__() def install(self, package, value, dev=False): section = "packages" if not dev else "dev_packages" @@ -267,38 +279,40 @@ class _Pipfile(object): fixture_pypi = os.getenv("ARTIFACT_PYPI_URL") if fixture_pypi: if pkg and not filename: - url = "{0}/artifacts/{1}".format(fixture_pypi, pkg) + url = f"{fixture_pypi}/artifacts/{pkg}" else: - url = "{0}/artifacts/{1}/{2}".format(fixture_pypi, pkg, filename) + url = f"{fixture_pypi}/artifacts/{pkg}/{filename}" return url if pkg and not filename: return cls.get_fixture_path(file_path).as_uri() -class _PipenvInstance(object): +class _PipenvInstance: """An instance of a Pipenv Project...""" def __init__( - self, pypi=None, pipfile=True, chdir=False, path=None, home_dir=None, + self, pypi=None, pipfile=True, chdir=True, path=None, capfd=None, venv_root=None, ignore_virtualenvs=True, venv_in_project=True, name=None ): self.index_url = os.getenv("PIPENV_TEST_INDEX") self.pypi = None + self.env = {} + self.capfd = capfd if pypi: self.pypi = pypi.url elif self.index_url is not None: self.pypi, _, _ = self.index_url.rpartition("/") if self.index_url else "" self.index = os.getenv("PIPENV_PYPI_INDEX") - os.environ["PYTHONWARNINGS"] = "ignore:DEPRECATION" + self.env["PYTHONWARNINGS"] = "ignore:DEPRECATION" if ignore_virtualenvs: - os.environ["PIPENV_IGNORE_VIRTUALENVS"] = fs_str("1") + self.env["PIPENV_IGNORE_VIRTUALENVS"] = fs_str("1") if venv_root: - os.environ["VIRTUAL_ENV"] = venv_root + self.env["VIRTUAL_ENV"] = venv_root if venv_in_project: - os.environ["PIPENV_VENV_IN_PROJECT"] = fs_str("1") + self.env["PIPENV_VENV_IN_PROJECT"] = fs_str("1") else: - os.environ.pop("PIPENV_VENV_IN_PROJECT", None) + self.env.pop("PIPENV_VENV_IN_PROJECT", None) - self.original_dir = os.path.abspath(os.curdir) + self.original_dir = Path(__file__).parent.parent.parent path = path if path else os.environ.get("PIPENV_PROJECT_DIR", None) if name is not None: path = Path(os.environ["HOME"]) / "projects" / name @@ -326,7 +340,7 @@ class _PipenvInstance(object): self.chdir = chdir if self.pypi and "PIPENV_PYPI_URL" not in os.environ: - os.environ['PIPENV_PYPI_URL'] = fs_str('{0}'.format(self.pypi)) + self.env['PIPENV_PYPI_URL'] = fs_str(f'{self.pypi}') # os.environ['PIPENV_PYPI_URL'] = fs_str('{0}'.format(self.pypi.url)) # os.environ['PIPENV_TEST_INDEX'] = fs_str('{0}/simple'.format(self.pypi.url)) @@ -362,38 +376,39 @@ class _PipenvInstance(object): # a bit of a hack to make sure the virtualenv is created with TemporaryDirectory(prefix='pipenv-', suffix='-cache') as tempdir: - os.environ['PIPENV_CACHE_DIR'] = fs_str(tempdir.name) - c = delegator.run( - 'pipenv {0}'.format(cmd), block=block, - cwd=os.path.abspath(self.path), env=os.environ.copy() - ) - if 'PIPENV_CACHE_DIR' in os.environ: - del os.environ['PIPENV_CACHE_DIR'] - - if 'PIPENV_PIPFILE' in os.environ: - del os.environ['PIPENV_PIPFILE'] - + cmd_args = shlex.split(cmd) + env = {**self.env, **{'PIPENV_CACHE_DIR': tempdir}} + self.capfd.readouterr() + r = cli_runner.invoke(cli, cmd_args, env=env) + r.returncode = r.exit_code # Pretty output for failing tests. + out, err = self.capfd.readouterr() + if out: + r.stdout_bytes = r.stdout_bytes + out + if err: + r.stderr_bytes = r.stderr_bytes + err if block: - print('$ pipenv {0}'.format(cmd)) - print(c.out) - print(c.err, file=sys.stderr) - if c.return_code != 0: + print(f'$ pipenv {cmd}') + print(r.stdout) + print(r.stderr, file=sys.stderr) + if r.exception: + print(''.join(traceback.format_exception(*r.exc_info)), file=sys.stderr) + if r.returncode != 0: print("Command failed...") # Where the action happens. - return c + return r @property def pipfile(self): p_path = os.sep.join([self.path, 'Pipfile']) - with open(p_path, 'r') as f: + with open(p_path) as f: return toml.loads(f.read()) @property def lockfile(self): p_path = self.lockfile_path - with open(p_path, 'r') as f: + with open(p_path) as f: return json.loads(f.read()) @property @@ -408,7 +423,7 @@ def _rmtree_func(path, ignore_errors=True, onerror=None): onerror = handle_remove_readonly try: shutil_rmtree(directory, ignore_errors=ignore_errors, onerror=onerror) - except (IOError, OSError, FileNotFoundError, PermissionError) as exc: + except (OSError, FileNotFoundError, PermissionError) as exc: # Ignore removal failures where the file doesn't exist if exc.errno != errno.ENOENT: raise @@ -427,39 +442,39 @@ def pip_src_dir(request, vistir_tmpdir): @pytest.fixture() -def PipenvInstance(pip_src_dir, monkeypatch, pypi): +def PipenvInstance(pip_src_dir, monkeypatch, pypi, capfdbinary): with temp_environ(), monkeypatch.context() as m: m.setattr(shutil, "rmtree", _rmtree_func) original_umask = os.umask(0o007) m.setenv("PIPENV_NOSPIN", fs_str("1")) m.setenv("CI", fs_str("1")) m.setenv('PIPENV_DONT_USE_PYENV', fs_str('1')) - m.setenv("PIPENV_TEST_INDEX", "{0}/simple".format(pypi.url)) + m.setenv("PIPENV_TEST_INDEX", f"{pypi.url}/simple") m.setenv("PIPENV_PYPI_INDEX", "simple") m.setenv("ARTIFACT_PYPI_URL", pypi.url) m.setenv("PIPENV_PYPI_URL", pypi.url) warnings.simplefilter("ignore", category=ResourceWarning) warnings.filterwarnings("ignore", category=ResourceWarning, message="unclosed.*<ssl.SSLSocket.*>") try: - yield _PipenvInstance + yield functools.partial(_PipenvInstance, capfd=capfdbinary) finally: os.umask(original_umask) @pytest.fixture() -def PipenvInstance_NoPyPI(monkeypatch, pip_src_dir, pypi): +def PipenvInstance_NoPyPI(monkeypatch, pip_src_dir, pypi, capfdbinary): with temp_environ(), monkeypatch.context() as m: m.setattr(shutil, "rmtree", _rmtree_func) original_umask = os.umask(0o007) m.setenv("PIPENV_NOSPIN", fs_str("1")) m.setenv("CI", fs_str("1")) m.setenv('PIPENV_DONT_USE_PYENV', fs_str('1')) - m.setenv("PIPENV_TEST_INDEX", "{0}/simple".format(pypi.url)) + m.setenv("PIPENV_TEST_INDEX", f"{pypi.url}/simple") m.setenv("ARTIFACT_PYPI_URL", pypi.url) warnings.simplefilter("ignore", category=ResourceWarning) warnings.filterwarnings("ignore", category=ResourceWarning, message="unclosed.*<ssl.SSLSocket.*>") try: - yield _PipenvInstance + yield functools.partial(_PipenvInstance, capfd=capfdbinary) finally: os.umask(original_umask) @@ -469,13 +484,13 @@ def testsroot(): return TESTS_ROOT -class VirtualEnv(object): +class VirtualEnv: def __init__(self, name="venv", base_dir=None): if base_dir is None: base_dir = Path(_create_tracked_dir()) self.base_dir = base_dir self.name = name - self.path = base_dir / name + self.path = (base_dir / name).resolve() def __enter__(self): self._old_environ = os.environ.copy() @@ -506,17 +521,14 @@ class VirtualEnv(object): code = compile(f.read(), str(activate_this), "exec") exec(code, dict(__file__=str(activate_this))) os.environ["VIRTUAL_ENV"] = str(self.path) - try: - return self.path.absolute().resolve() - except OSError: - return self.path.absolute() + return self.path else: raise VirtualenvActivationException("Can't find the activate_this.py script.") @pytest.fixture() def virtualenv(vistir_tmpdir): - with temp_environ(), VirtualEnv(base_dir=vistir_tmpdir) as venv: + with VirtualEnv(base_dir=vistir_tmpdir) as venv: yield venv diff --git a/tests/integration/test_cli.py b/tests/integration/test_cli.py index 04253fc5..b2184b1f 100644 --- a/tests/integration/test_cli.py +++ b/tests/integration/test_cli.py @@ -1,34 +1,32 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function """Tests to ensure `pipenv --option` works. """ import os import re - +from pathlib import Path import pytest from flaky import flaky -from pipenv.utils import normalize_drive +from pipenv.utils import normalize_drive, subprocess_run @pytest.mark.cli def test_pipenv_where(PipenvInstance): with PipenvInstance() as p: c = p.pipenv("--where") - assert c.ok - assert normalize_drive(p.path) in c.out + assert c.returncode == 0 + assert normalize_drive(p.path) in c.stdout @pytest.mark.cli def test_pipenv_venv(PipenvInstance): with PipenvInstance() as p: c = p.pipenv('--python python') - assert c.ok + assert c.returncode == 0 c = p.pipenv('--venv') - assert c.ok - venv_path = c.out.strip() + assert c.returncode == 0 + venv_path = c.stdout.strip() assert os.path.isdir(venv_path) @@ -36,10 +34,10 @@ def test_pipenv_venv(PipenvInstance): def test_pipenv_py(PipenvInstance): with PipenvInstance() as p: c = p.pipenv('--python python') - assert c.ok + assert c.returncode == 0 c = p.pipenv('--py') - assert c.ok - python = c.out.strip() + assert c.returncode == 0 + python = c.stdout.strip() assert os.path.basename(python).startswith('python') @@ -47,13 +45,13 @@ def test_pipenv_py(PipenvInstance): def test_pipenv_site_packages(PipenvInstance): with PipenvInstance() as p: c = p.pipenv('--python python --site-packages') - assert c.return_code == 0 - assert 'Making site-packages available' in c.err + assert c.returncode == 0 + assert 'Making site-packages available' in c.stderr # no-global-site-packages.txt under stdlib dir should not exist. c = p.pipenv('run python -c "import sysconfig; print(sysconfig.get_path(\'stdlib\'))"') - assert c.return_code == 0 - stdlib_path = c.out.strip() + assert c.returncode == 0 + stdlib_path = c.stdout.strip() assert not os.path.isfile(os.path.join(stdlib_path, 'no-global-site-packages.txt')) @@ -61,23 +59,23 @@ def test_pipenv_site_packages(PipenvInstance): def test_pipenv_support(PipenvInstance): with PipenvInstance() as p: c = p.pipenv('--support') - assert c.ok - assert c.out + assert c.returncode == 0 + assert c.stdout @pytest.mark.cli def test_pipenv_rm(PipenvInstance): with PipenvInstance() as p: c = p.pipenv('--python python') - assert c.ok + assert c.returncode == 0 c = p.pipenv('--venv') - assert c.ok - venv_path = c.out.strip() + assert c.returncode == 0 + venv_path = c.stdout.strip() assert os.path.isdir(venv_path) c = p.pipenv('--rm') - assert c.ok - assert c.out + assert c.returncode == 0 + assert c.stdout assert not os.path.isdir(venv_path) @@ -85,30 +83,30 @@ def test_pipenv_rm(PipenvInstance): def test_pipenv_graph(PipenvInstance): with PipenvInstance() as p: c = p.pipenv('install tablib') - assert c.ok + assert c.returncode == 0 graph = p.pipenv("graph") - assert graph.ok - assert "tablib" in graph.out + assert graph.returncode == 0 + assert "tablib" in graph.stdout graph_json = p.pipenv("graph --json") - assert graph_json.ok - assert "tablib" in graph_json.out + assert graph_json.returncode == 0 + assert "tablib" in graph_json.stdout graph_json_tree = p.pipenv("graph --json-tree") - assert graph_json_tree.ok - assert "tablib" in graph_json_tree.out + assert graph_json_tree.returncode == 0 + assert "tablib" in graph_json_tree.stdout @pytest.mark.cli def test_pipenv_graph_reverse(PipenvInstance): with PipenvInstance() as p: c = p.pipenv('install tablib==0.13.0') - assert c.ok + assert c.returncode == 0 c = p.pipenv('graph --reverse') - assert c.ok - output = c.out + assert c.returncode == 0 + output = c.stdout c = p.pipenv('graph --reverse --json') - assert c.return_code == 1 - assert 'Warning: Using both --reverse and --json together is not supported.' in c.err + assert c.returncode == 1 + assert 'Warning: Using both --reverse and --json together is not supported.' in c.stderr requests_dependency = [ ('backports.csv', 'backports.csv'), @@ -120,22 +118,22 @@ def test_pipenv_graph_reverse(PipenvInstance): ] for dep_name, dep_constraint in requests_dependency: - pat = r'^[ -]*{}==[\d.]+'.format(dep_name) + pat = fr'^[ -]*{dep_name}==[\d.]+' dep_match = re.search(pat, output, flags=re.MULTILINE) - assert dep_match is not None, '{} not found in {}'.format(pat, output) + assert dep_match is not None, f'{pat} not found in {output}' # openpyxl should be indented if dep_name == 'openpyxl': openpyxl_dep = re.search(r'^openpyxl', output, flags=re.MULTILINE) - assert openpyxl_dep is None, 'openpyxl should not appear at begining of lines in {}'.format(output) + assert openpyxl_dep is None, f'openpyxl should not appear at beginning of lines in {output}' assert ' - openpyxl==2.5.4 [requires: et-xmlfile]' in output else: - dep_match = re.search(r'^[ -]*{}==[\d.]+$'.format(dep_name), output, flags=re.MULTILINE) - assert dep_match is not None, '{} not found at beginning of line in {}'.format(dep_name, output) + dep_match = re.search(fr'^[ -]*{dep_name}==[\d.]+$', output, flags=re.MULTILINE) + assert dep_match is not None, f'{dep_name} not found at beginning of line in {output}' - dep_requests_match = re.search(r'^ +- tablib==0.13.0 \[requires: {}\]$'.format(dep_constraint), output, flags=re.MULTILINE) - assert dep_requests_match is not None, 'constraint {} not found in {}'.format(dep_constraint, output) + dep_requests_match = re.search(fr'^ +- tablib==0.13.0 \[requires: {dep_constraint}\]$', output, flags=re.MULTILINE) + assert dep_requests_match is not None, f'constraint {dep_constraint} not found in {output}' assert dep_requests_match.start() > dep_match.start() @@ -144,17 +142,18 @@ def test_pipenv_graph_reverse(PipenvInstance): @flaky def test_pipenv_check(PipenvInstance): with PipenvInstance() as p: - p.pipenv('install requests==1.0.0') + c = p.pipenv('install pyyaml') + assert c.returncode == 0 c = p.pipenv('check') - assert c.return_code != 0 - assert 'requests' in c.out - c = p.pipenv('uninstall requests') - assert c.ok + assert c.returncode != 0 + assert 'pyyaml' in c.stdout + c = p.pipenv('uninstall pyyaml') + assert c.returncode == 0 c = p.pipenv('install six') - assert c.ok + assert c.returncode == 0 c = p.pipenv('check --ignore 35015') - assert c.return_code == 0 - assert 'Ignoring' in c.err + assert c.returncode == 0 + assert 'Ignoring' in c.stderr @pytest.mark.cli @@ -163,12 +162,12 @@ def test_pipenv_clean_pip_no_warnings(PipenvInstance): with open('setup.py', 'w') as f: f.write('from setuptools import setup; setup(name="empty")') c = p.pipenv('install -e .') - assert c.return_code == 0 - c = p.pipenv('run pip install pytz') - assert c.return_code == 0 + assert c.returncode == 0 + c = p.pipenv(f'run pip install -i {p.index_url} pytz') + assert c.returncode == 0 c = p.pipenv('clean') - assert c.return_code == 0 - assert c.out, "{0} -- STDERR: {1}".format(c.out, c.err) + assert c.returncode == 0 + assert c.stdout, f"{c.stdout} -- STDERR: {c.stderr}" @pytest.mark.cli @@ -178,36 +177,50 @@ def test_pipenv_clean_pip_warnings(PipenvInstance): f.write('from setuptools import setup; setup(name="empty")') # create a fake git repo to trigger a pip freeze warning os.mkdir('.git') - c = p.pipenv("run pip install -e .") - assert c.return_code == 0 + c = p.pipenv(f"run pip install -i {p.index_url} -e .") + assert c.returncode == 0 c = p.pipenv('clean') - assert c.return_code == 0 - assert c.err + assert c.returncode == 0 + assert c.stderr @pytest.mark.cli def test_venv_envs(PipenvInstance): with PipenvInstance() as p: - assert p.pipenv('--envs').out + assert p.pipenv('--envs').stdout @pytest.mark.cli def test_bare_output(PipenvInstance): with PipenvInstance() as p: - assert p.pipenv('').out + assert p.pipenv('').stdout + + +@pytest.mark.cli +def test_scripts(PipenvInstance): + with PipenvInstance() as p: + with open(p.pipfile_path, "w") as f: + contents = """ +[scripts] +pyver = "which python" + """.strip() + f.write(contents) + c = p.pipenv('scripts') + assert 'pyver' in c.stdout + assert 'which python' in c.stdout @pytest.mark.cli def test_help(PipenvInstance): with PipenvInstance() as p: - assert p.pipenv('--help').out + assert p.pipenv('--help').stdout @pytest.mark.cli def test_man(PipenvInstance): - with PipenvInstance() as p: - c = p.pipenv('--man') - assert c.return_code == 0 or c.err + with PipenvInstance(): + c = subprocess_run(["pipenv", "--man"]) + assert c.returncode == 0, c.stderr @pytest.mark.cli @@ -224,7 +237,7 @@ def test_install_parse_error(PipenvInstance): """.strip() f.write(contents) c = p.pipenv('install requests u/\\/p@r\\$34b13+pkg') - assert c.return_code != 0 + assert c.returncode != 0 assert 'u/\\/p@r$34b13+pkg' not in p.pipfile['packages'] @@ -247,24 +260,24 @@ import flask assert all(pkg in p.pipfile['packages'] for pkg in ['requests', 'click', 'flask']), p.pipfile["packages"] c = p.pipenv('check --unused .') - assert 'click' not in c.out - assert 'flask' not in c.out + assert 'click' not in c.stdout + assert 'flask' not in c.stdout @pytest.mark.cli def test_pipenv_clear(PipenvInstance): with PipenvInstance() as p: c = p.pipenv('--clear') - assert c.return_code == 0 - assert 'Clearing caches' in c.out + assert c.returncode == 0 + assert 'Clearing caches' in c.stdout @pytest.mark.cli def test_pipenv_three(PipenvInstance): with PipenvInstance() as p: c = p.pipenv('--three') - assert c.return_code == 0 - assert 'Successfully created virtual environment' in c.err + assert c.returncode == 0 + assert 'Successfully created virtual environment' in c.stderr @pytest.mark.outdated @@ -277,4 +290,45 @@ sqlalchemy = "<=1.2.3" """.strip() f.write(contents) c = p.pipenv('update --pre --outdated') - assert c.return_code == 0 + assert c.returncode == 0 + + +@pytest.mark.cli +def test_pipenv_verify_without_pipfile(PipenvInstance): + with PipenvInstance(pipfile=False) as p: + c = p.pipenv('verify') + assert c.returncode == 1 + assert 'No Pipfile present at project home.' in c.stderr + + +@pytest.mark.cli +def test_pipenv_verify_without_pipfile_lock(PipenvInstance): + with PipenvInstance() as p: + c = p.pipenv('verify') + assert c.returncode == 1 + assert 'Pipfile.lock is out-of-date.' in c.stderr + + +@pytest.mark.cli +def test_pipenv_verify_locked_passing(PipenvInstance): + with PipenvInstance() as p: + p.pipenv('lock') + c = p.pipenv('verify') + assert c.returncode == 0 + assert 'Pipfile.lock is up-to-date.' in c.stdout + + +@pytest.mark.cli +def test_pipenv_verify_locked_outdated_failing(PipenvInstance): + with PipenvInstance() as p: + p.pipenv('lock') + + # modify the Pipfile + pf = Path(p.path).joinpath('Pipfile') + pf_data = pf.read_text() + pf_new = re.sub(r'\[packages\]', '[packages]\nrequests = "*"', pf_data) + pf.write_text(pf_new) + + c = p.pipenv('verify') + assert c.returncode == 1 + assert 'Pipfile.lock is out-of-date.' in c.stderr diff --git a/tests/integration/test_dot_venv.py b/tests/integration/test_dot_venv.py index 3cbc88db..96ad37a7 100644 --- a/tests/integration/test_dot_venv.py +++ b/tests/integration/test_dot_venv.py @@ -1,10 +1,12 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, print_function + import os +from pathlib import Path +from tempfile import TemporaryDirectory import pytest -from pipenv._compat import Path, TemporaryDirectory from pipenv.utils import normalize_drive, temp_environ @@ -14,8 +16,8 @@ def test_venv_in_project(PipenvInstance): os.environ['PIPENV_VENV_IN_PROJECT'] = '1' with PipenvInstance() as p: c = p.pipenv('install requests') - assert c.return_code == 0 - assert normalize_drive(p.path) in p.pipenv('--venv').out + assert c.returncode == 0 + assert normalize_drive(p.path) in p.pipenv('--venv').stdout @pytest.mark.dotvenv @@ -24,13 +26,13 @@ def test_venv_at_project_root(PipenvInstance): with PipenvInstance(chdir=True) as p: os.environ['PIPENV_VENV_IN_PROJECT'] = '1' c = p.pipenv('install') - assert c.return_code == 0 - assert normalize_drive(p.path) in p.pipenv('--venv').out + assert c.returncode == 0 + assert normalize_drive(p.path) in p.pipenv('--venv').stdout del os.environ['PIPENV_VENV_IN_PROJECT'] os.mkdir('subdir') os.chdir('subdir') # should still detect installed - assert normalize_drive(p.path) in p.pipenv('--venv').out + assert normalize_drive(p.path) in p.pipenv('--venv').stdout @pytest.mark.dotvenv @@ -38,8 +40,8 @@ def test_reuse_previous_venv(PipenvInstance): with PipenvInstance(chdir=True) as p: os.mkdir('.venv') c = p.pipenv('install requests') - assert c.return_code == 0 - assert normalize_drive(p.path) in p.pipenv('--venv').out + assert c.returncode == 0 + assert normalize_drive(p.path) in p.pipenv('--venv').stdout @pytest.mark.dotvenv @@ -56,26 +58,56 @@ def test_venv_file(venv_name, PipenvInstance): with temp_environ(), TemporaryDirectory( prefix='pipenv-', suffix='temp_workon_home' ) as workon_home: - os.environ['WORKON_HOME'] = workon_home.name + os.environ['WORKON_HOME'] = workon_home if 'PIPENV_VENV_IN_PROJECT' in os.environ: del os.environ['PIPENV_VENV_IN_PROJECT'] c = p.pipenv('install') - assert c.return_code == 0 + assert c.returncode == 0 c = p.pipenv('--venv') - assert c.return_code == 0 - venv_loc = Path(c.out.strip()).absolute() + assert c.returncode == 0 + venv_loc = Path(c.stdout.strip()).absolute() assert venv_loc.exists() assert venv_loc.joinpath('.project').exists() venv_path = normalize_drive(venv_loc.as_posix()) if os.path.sep in venv_name: venv_expected_path = Path(p.path).joinpath(venv_name).absolute().as_posix() else: - venv_expected_path = Path(workon_home.name).joinpath(venv_name).absolute().as_posix() + venv_expected_path = Path(workon_home).joinpath(venv_name).absolute().as_posix() assert venv_path == normalize_drive(venv_expected_path) +@pytest.mark.dotvenv +def test_empty_venv_file(PipenvInstance): + """Tests virtualenv creation when a empty .venv file exists at the project root + """ + with PipenvInstance(chdir=True) as p: + file_path = os.path.join(p.path, '.venv') + with open(file_path, 'w'): + pass + + with temp_environ(), TemporaryDirectory( + prefix='pipenv-', suffix='temp_workon_home' + ) as workon_home: + os.environ['WORKON_HOME'] = workon_home + if 'PIPENV_VENV_IN_PROJECT' in os.environ: + del os.environ['PIPENV_VENV_IN_PROJECT'] + + c = p.pipenv('install') + assert c.returncode == 0 + + c = p.pipenv('--venv') + assert c.returncode == 0 + venv_loc = Path(c.stdout.strip()).absolute() + assert venv_loc.exists() + assert venv_loc.joinpath('.project').exists() + from pathlib import PurePosixPath + venv_path = normalize_drive(venv_loc.as_posix()) + venv_path_parent = str(PurePosixPath(venv_path).parent) + assert venv_path_parent == Path(workon_home).absolute().as_posix() + + @pytest.mark.dotvenv def test_venv_file_with_path(PipenvInstance): """Tests virtualenv creation when a .venv file exists at the project root @@ -90,13 +122,13 @@ def test_venv_file_with_path(PipenvInstance): file_path = os.path.join(p.path, '.venv') with open(file_path, 'w') as f: - f.write(venv_path.name) + f.write(venv_path) c = p.pipenv('install') - assert c.return_code == 0 + assert c.returncode == 0 c = p.pipenv('--venv') - assert c.return_code == 0 - venv_loc = Path(c.out.strip()) + assert c.returncode == 0 + venv_loc = Path(c.stdout.strip()) assert venv_loc.joinpath('.project').exists() - assert venv_loc == Path(venv_path.name) + assert venv_loc == Path(venv_path) diff --git a/tests/integration/test_install_basic.py b/tests/integration/test_install_basic.py index a6beb21d..fb124142 100644 --- a/tests/integration/test_install_basic.py +++ b/tests/integration/test_install_basic.py @@ -1,23 +1,23 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function import os +from pathlib import Path +from tempfile import TemporaryDirectory + import pytest from flaky import flaky -from pipenv._compat import Path, TemporaryDirectory -from pipenv.utils import temp_environ -from pipenv.vendor import delegator +from pipenv.utils import subprocess_run, temp_environ -@pytest.mark.install @pytest.mark.setup +@pytest.mark.basic +@pytest.mark.install def test_basic_setup(PipenvInstance): with PipenvInstance() as p: with PipenvInstance(pipfile=False) as p: c = p.pipenv("install requests") - assert c.return_code == 0 + assert c.returncode == 0 assert "requests" in p.pipfile["packages"] assert "requests" in p.lockfile["default"] @@ -28,12 +28,13 @@ def test_basic_setup(PipenvInstance): @flaky +@pytest.mark.basic @pytest.mark.install @pytest.mark.skip_osx def test_basic_install(PipenvInstance): with PipenvInstance() as p: c = p.pipenv("install requests") - assert c.return_code == 0 + assert c.returncode == 0 assert "requests" in p.pipfile["packages"] assert "requests" in p.lockfile["default"] assert "chardet" in p.lockfile["default"] @@ -43,6 +44,7 @@ def test_basic_install(PipenvInstance): @flaky +@pytest.mark.basic @pytest.mark.install def test_mirror_install(PipenvInstance): with temp_environ(), PipenvInstance(chdir=True) as p: @@ -52,8 +54,8 @@ def test_mirror_install(PipenvInstance): assert "pypi.org" not in mirror_url # This should sufficiently demonstrate the mirror functionality # since pypi.org is the default when PIPENV_TEST_INDEX is unset. - c = p.pipenv("install requests --pypi-mirror {0}".format(mirror_url)) - assert c.return_code == 0 + c = p.pipenv(f"install requests --pypi-mirror {mirror_url}") + assert c.returncode == 0 # Ensure the --pypi-mirror parameter hasn't altered the Pipfile or Pipfile.lock sources assert len(p.pipfile["source"]) == 1 assert len(p.lockfile["_meta"]["sources"]) == 1 @@ -69,6 +71,7 @@ def test_mirror_install(PipenvInstance): @flaky +@pytest.mark.basic @pytest.mark.install @pytest.mark.needs_internet def test_bad_mirror_install(PipenvInstance): @@ -76,7 +79,7 @@ def test_bad_mirror_install(PipenvInstance): # This demonstrates that the mirror parameter is being used os.environ.pop("PIPENV_TEST_INDEX", None) c = p.pipenv("install requests --pypi-mirror https://pypi.example.org") - assert c.return_code != 0 + assert c.returncode != 0 @pytest.mark.lock @@ -85,10 +88,10 @@ def test_bad_mirror_install(PipenvInstance): def test_complex_lock(PipenvInstance): with PipenvInstance() as p: c = p.pipenv("install apscheduler") - assert c.return_code == 0 + assert c.returncode == 0 assert "apscheduler" in p.pipfile["packages"] - assert "funcsigs" in p.lockfile[u"default"] - assert "futures" in p.lockfile[u"default"] + assert "funcsigs" in p.lockfile["default"] + assert "futures" in p.lockfile["default"] @flaky @@ -97,7 +100,7 @@ def test_complex_lock(PipenvInstance): def test_basic_dev_install(PipenvInstance): with PipenvInstance() as p: c = p.pipenv("install requests --dev") - assert c.return_code == 0 + assert c.returncode == 0 assert "requests" in p.pipfile["dev-packages"] assert "requests" in p.lockfile["develop"] assert "chardet" in p.lockfile["develop"] @@ -106,11 +109,12 @@ def test_basic_dev_install(PipenvInstance): assert "certifi" in p.lockfile["develop"] c = p.pipenv("run python -m requests.help") - assert c.return_code == 0 + assert c.returncode == 0 @flaky @pytest.mark.dev +@pytest.mark.basic @pytest.mark.install def test_install_without_dev(PipenvInstance): """Ensure that running `pipenv install` doesn't install dev packages""" @@ -125,18 +129,19 @@ tablib = "*" """.strip() f.write(contents) c = p.pipenv("install") - assert c.return_code == 0 + assert c.returncode == 0 assert "six" in p.pipfile["packages"] assert "tablib" in p.pipfile["dev-packages"] assert "six" in p.lockfile["default"] assert "tablib" in p.lockfile["develop"] c = p.pipenv('run python -c "import tablib"') - assert c.return_code != 0 + assert c.returncode != 0 c = p.pipenv('run python -c "import six"') - assert c.return_code == 0 + assert c.returncode == 0 @flaky +@pytest.mark.basic @pytest.mark.install def test_install_without_dev_section(PipenvInstance): with PipenvInstance() as p: @@ -147,22 +152,23 @@ six = "*" """.strip() f.write(contents) c = p.pipenv("install") - assert c.return_code == 0 + assert c.returncode == 0 assert "six" in p.pipfile["packages"] assert p.pipfile.get("dev-packages", {}) == {} assert "six" in p.lockfile["default"] assert p.lockfile["develop"] == {} c = p.pipenv('run python -c "import six"') - assert c.return_code == 0 + assert c.returncode == 0 @flaky +@pytest.mark.lock @pytest.mark.extras @pytest.mark.install def test_extras_install(PipenvInstance): with PipenvInstance(chdir=True) as p: c = p.pipenv("install requests[socks]") - assert c.return_code == 0 + assert c.returncode == 0 assert "requests" in p.pipfile["packages"] assert "extras" in p.pipfile["packages"]["requests"] @@ -175,6 +181,7 @@ def test_extras_install(PipenvInstance): @flaky @pytest.mark.pin +@pytest.mark.basic @pytest.mark.install def test_windows_pinned_pipfile(PipenvInstance): with PipenvInstance() as p: @@ -185,12 +192,13 @@ requests = "==2.19.1" """.strip() f.write(contents) c = p.pipenv("install") - assert c.return_code == 0 + assert c.returncode == 0 assert "requests" in p.pipfile["packages"] assert "requests" in p.lockfile["default"] @flaky +@pytest.mark.basic @pytest.mark.install @pytest.mark.resolver @pytest.mark.backup_resolver @@ -204,7 +212,7 @@ def test_backup_resolver(PipenvInstance): f.write(contents) c = p.pipenv("install") - assert c.return_code == 0 + assert c.returncode == 0 assert "ibm-db-sa-py3" in p.lockfile["default"] @@ -221,7 +229,7 @@ requests = {version = "*"} f.write(contents) c = p.pipenv("install") - assert c.return_code == 0 + assert c.returncode == 0 assert "requests" in p.lockfile["default"] assert "idna" in p.lockfile["default"] @@ -230,7 +238,7 @@ requests = {version = "*"} assert "chardet" in p.lockfile["default"] c = p.pipenv('run python -c "import requests; import idna; import certifi;"') - assert c.return_code == 0 + assert c.returncode == 0 @flaky @@ -246,7 +254,7 @@ version = "*" f.write(contents) c = p.pipenv("install") - assert c.return_code == 0 + assert c.returncode == 0 assert "requests" in p.lockfile["default"] assert "idna" in p.lockfile["default"] @@ -255,39 +263,45 @@ version = "*" assert "chardet" in p.lockfile["default"] c = p.pipenv('run python -c "import requests; import idna; import certifi;"') - assert c.return_code == 0 + assert c.returncode == 0 @pytest.mark.bad +@pytest.mark.basic @pytest.mark.install def test_bad_packages(PipenvInstance): with PipenvInstance() as p: c = p.pipenv("install NotAPackage") - assert c.return_code > 0 + assert c.returncode > 0 +@pytest.mark.lock @pytest.mark.extras @pytest.mark.install @pytest.mark.requirements -@pytest.mark.skip(reason="Not mocking this.") -def test_requirements_to_pipfile(PipenvInstance): +def test_requirements_to_pipfile(PipenvInstance, pypi): with PipenvInstance(pipfile=False, chdir=True) as p: # Write a requirements file with open("requirements.txt", "w") as f: - f.write("requests[socks]==2.18.1\n") + f.write( + f"-i {pypi.url}\n" + "# -i https://private.pypi.org/simple\n" + "requests[socks]==2.19.1\n" + ) c = p.pipenv("install") - assert c.return_code == 0 - print(c.out) - print(c.err) - print(delegator.run("ls -l").out) - + assert c.returncode == 0 + print(c.stdout) + print(c.stderr) # assert stuff in pipfile assert "requests" in p.pipfile["packages"] assert "extras" in p.pipfile["packages"]["requests"] - + assert not any( + source['url'] == 'https://private.pypi.org/simple' + for source in p.pipfile['source'] + ) # assert stuff in lockfile assert "requests" in p.lockfile["default"] assert "chardet" in p.lockfile["default"] @@ -296,6 +310,7 @@ def test_requirements_to_pipfile(PipenvInstance): assert "pysocks" in p.lockfile["default"] +@pytest.mark.basic @pytest.mark.install @pytest.mark.skip_osx @pytest.mark.requirements @@ -309,7 +324,7 @@ def test_skip_requirements_when_pipfile(PipenvInstance): with open("requirements.txt", "w") as f: f.write("requests==2.18.1\n") c = p.pipenv("install six") - assert c.return_code == 0 + assert c.returncode == 0 with open(p.pipfile_path, "w") as f: contents = """ [packages] @@ -318,7 +333,7 @@ fake_package = "<0.12" """.strip() f.write(contents) c = p.pipenv("install") - assert c.ok + assert c.returncode == 0 assert "fake_package" in p.pipfile["packages"] assert "fake-package" in p.lockfile["default"] assert "six" in p.pipfile["packages"] @@ -332,9 +347,10 @@ fake_package = "<0.12" def test_clean_on_empty_venv(PipenvInstance): with PipenvInstance() as p: c = p.pipenv("clean") - assert c.return_code == 0 + assert c.returncode == 0 +@pytest.mark.basic @pytest.mark.install def test_install_does_not_extrapolate_environ(PipenvInstance): """Ensure environment variables are not expanded in lock file. @@ -355,27 +371,29 @@ name = 'mockpi' # Ensure simple install does not extrapolate. c = p.pipenv("install") - assert c.return_code == 0 + assert c.returncode == 0 assert p.pipfile["source"][0]["url"] == "${PYPI_URL}/simple" assert p.lockfile["_meta"]["sources"][0]["url"] == "${PYPI_URL}/simple" # Ensure package install does not extrapolate. c = p.pipenv("install six") - assert c.return_code == 0 + assert c.returncode == 0 assert p.pipfile["source"][0]["url"] == "${PYPI_URL}/simple" assert p.lockfile["_meta"]["sources"][0]["url"] == "${PYPI_URL}/simple" +@pytest.mark.basic @pytest.mark.editable @pytest.mark.badparameter @pytest.mark.install def test_editable_no_args(PipenvInstance): with PipenvInstance() as p: c = p.pipenv("install -e") - assert c.return_code != 0 - assert "Error: -e option requires an argument" in c.err + assert c.returncode != 0 + assert "Error: Option '-e' requires an argument" in c.stderr +@pytest.mark.basic @pytest.mark.install @pytest.mark.virtualenv def test_install_venv_project_directory(PipenvInstance): @@ -385,45 +403,47 @@ def test_install_venv_project_directory(PipenvInstance): with temp_environ(), TemporaryDirectory( prefix="pipenv-", suffix="temp_workon_home" ) as workon_home: - os.environ["WORKON_HOME"] = workon_home.name + os.environ["WORKON_HOME"] = workon_home if "PIPENV_VENV_IN_PROJECT" in os.environ: del os.environ["PIPENV_VENV_IN_PROJECT"] c = p.pipenv("install six") - assert c.return_code == 0 + assert c.returncode == 0 venv_loc = None - for line in c.err.splitlines(): + for line in c.stderr.splitlines(): if line.startswith("Virtualenv location:"): venv_loc = Path(line.split(":", 1)[-1].strip()) assert venv_loc is not None assert venv_loc.joinpath(".project").exists() +@pytest.mark.cli @pytest.mark.deploy @pytest.mark.system def test_system_and_deploy_work(PipenvInstance): with PipenvInstance(chdir=True) as p: - c = p.pipenv("install six requests") - assert c.return_code == 0 + c = p.pipenv("install tablib") + assert c.returncode == 0 c = p.pipenv("--rm") - assert c.return_code == 0 - c = delegator.run("virtualenv .venv") - assert c.return_code == 0 + assert c.returncode == 0 + c = subprocess_run(["virtualenv", ".venv"]) + assert c.returncode == 0 c = p.pipenv("install --system --deploy") - assert c.return_code == 0 + assert c.returncode == 0 c = p.pipenv("--rm") - assert c.return_code == 0 + assert c.returncode == 0 Path(p.pipfile_path).write_text( - u""" + """ [packages] -requests +tablib = "*" """.strip() ) c = p.pipenv("install --system") - assert c.return_code == 0 + assert c.returncode == 0 +@pytest.mark.basic @pytest.mark.install def test_install_creates_pipfile(PipenvInstance): with PipenvInstance(chdir=True) as p: @@ -433,26 +453,29 @@ def test_install_creates_pipfile(PipenvInstance): del os.environ["PIPENV_PIPFILE"] assert not os.path.isfile(p.pipfile_path) c = p.pipenv("install") - assert c.return_code == 0 + assert c.returncode == 0 assert os.path.isfile(p.pipfile_path) +@pytest.mark.basic @pytest.mark.install def test_install_non_exist_dep(PipenvInstance): with PipenvInstance(chdir=True) as p: c = p.pipenv("install dateutil") - assert not c.ok + assert c.returncode assert "dateutil" not in p.pipfile["packages"] +@pytest.mark.basic @pytest.mark.install def test_install_package_with_dots(PipenvInstance): with PipenvInstance(chdir=True) as p: c = p.pipenv("install backports.html") - assert c.ok + assert c.returncode == 0 assert "backports.html" in p.pipfile["packages"] +@pytest.mark.basic @pytest.mark.install def test_rewrite_outline_table(PipenvInstance): with PipenvInstance(chdir=True) as p: @@ -466,11 +489,11 @@ version = "*" extras = ["socks"] """.strip() f.write(contents) - c = p.pipenv("install plette") - assert c.return_code == 0 + c = p.pipenv("install flask") + assert c.returncode == 0 with open(p.pipfile_path) as f: contents = f.read() assert "[packages.requests]" not in contents assert 'six = {version = "*"}' in contents assert 'requests = {version = "*"' in contents - assert 'plette = "*"' in contents + assert 'flask = "*"' in contents diff --git a/tests/integration/test_install_markers.py b/tests/integration/test_install_markers.py index de3ba193..1bb28eb1 100644 --- a/tests/integration/test_install_markers.py +++ b/tests/integration/test_install_markers.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function import os import pytest @@ -24,12 +22,12 @@ fake_package = {version = "*", markers="os_name=='splashwear'"} f.write(contents) c = p.pipenv('install') - assert c.return_code == 0 - assert 'Ignoring' in c.out + assert c.returncode == 0 + assert 'Ignoring' in c.stdout assert 'markers' in p.lockfile['default']['fake-package'], p.lockfile["default"] c = p.pipenv('run python -c "import fake_package;"') - assert c.return_code == 1 + assert c.returncode == 1 @flaky @@ -47,7 +45,7 @@ depends-on-marked-package = "*" f.write(contents) c = p.pipenv('install') - assert c.return_code == 0 + assert c.returncode == 0 # depends-on-marked-package has an install_requires of # 'pytz; platform_python_implementation=="CPython"' @@ -58,8 +56,8 @@ depends-on-marked-package = "*" @flaky -@pytest.mark.run @pytest.mark.alt +@pytest.mark.markers @pytest.mark.install def test_specific_package_environment_markers(PipenvInstance): @@ -72,13 +70,13 @@ fake-package = {version = "*", os_name = "== 'splashwear'"} f.write(contents) c = p.pipenv('install') - assert c.return_code == 0 + assert c.returncode == 0 - assert 'Ignoring' in c.out + assert 'Ignoring' in c.stdout assert 'markers' in p.lockfile['default']['fake-package'] c = p.pipenv('run python -c "import fake_package;"') - assert c.return_code == 1 + assert c.returncode == 1 @flaky @@ -96,7 +94,7 @@ funcsigs = {version = "*", os_name = "== 'splashwear'"} f.write(contents) c = p.pipenv('install') - assert c.return_code == 0 + assert c.returncode == 0 assert "markers" in p.lockfile['default']['funcsigs'], p.lockfile['default']['funcsigs'] assert p.lockfile['default']['funcsigs']['markers'] == "os_name == 'splashwear'", p.lockfile['default']['funcsigs'] @@ -121,16 +119,14 @@ funcsigs = "*" f.write(contents) c = p.pipenv('install') - assert c.return_code == 0 + assert c.returncode == 0 assert p.lockfile['default']['funcsigs'].get('markers', '') == '' @flaky -@pytest.mark.lock +@pytest.mark.markers @pytest.mark.complex -@pytest.mark.py3_only -@pytest.mark.lte_py36 def test_resolver_unique_markers(PipenvInstance): """vcrpy has a dependency on `yarl` which comes with a marker of 'python version in "3.4, 3.5, 3.6" - this marker duplicates itself: @@ -141,18 +137,21 @@ def test_resolver_unique_markers(PipenvInstance): """ with PipenvInstance(chdir=True) as p: c = p.pipenv('install vcrpy==2.0.1') - assert c.return_code == 0 - c = p.pipenv('lock') - assert c.return_code == 0 + assert c.returncode == 0 assert 'yarl' in p.lockfile['default'] yarl = p.lockfile['default']['yarl'] assert 'markers' in yarl # Two possible marker sets are ok here - assert yarl['markers'] in ["python_version in '3.4, 3.5, 3.6'", "python_version >= '3.4'"] + assert yarl['markers'] in [ + "python_version in '3.4, 3.5, 3.6'", + "python_version >= '3.4'", + "python_version >= '3.5'", # yarl 1.3.0 requires python 3.5.3 + ] @flaky @pytest.mark.project +@pytest.mark.needs_internet def test_environment_variable_value_does_not_change_hash(PipenvInstance): with PipenvInstance(chdir=True) as p: with temp_environ(): @@ -173,14 +172,14 @@ six = "*" assert project.get_lockfile_hash() is None c = p.pipenv('install') - assert c.return_code == 0 + assert c.returncode == 0 lock_hash = project.get_lockfile_hash() assert lock_hash is not None assert lock_hash == project.calculate_pipfile_hash() # sanity check on pytest assert 'PYPI_USERNAME' not in str(pipfile.load(p.pipfile_path)) - assert c.return_code == 0 + assert c.returncode == 0 assert project.get_lockfile_hash() == project.calculate_pipfile_hash() os.environ['PYPI_PASSWORD'] = 'pass2' diff --git a/tests/integration/test_install_twists.py b/tests/integration/test_install_twists.py index 4fcbd4c5..ecb1d143 100644 --- a/tests/integration/test_install_twists.py +++ b/tests/integration/test_install_twists.py @@ -1,16 +1,12 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function import os import shutil import sys +from pathlib import Path import pytest - from flaky import flaky -from pipenv._compat import Path from pipenv.utils import mkdir_p, temp_environ -from pipenv.vendor import delegator @pytest.mark.extras @@ -48,26 +44,26 @@ testpipenv = {path = ".", editable = true, extras = ["dev"]} """.strip()) # project.write_toml({"packages": pipfile, "dev-packages": {}}) c = p.pipenv("install") - assert c.return_code == 0 + assert c.returncode == 0 assert "testpipenv" in p.lockfile["default"] assert p.lockfile["default"]["testpipenv"]["extras"] == ["dev"] assert "six" in p.lockfile["default"] c = p.pipenv("uninstall --all") - assert c.return_code == 0 - print("Current directory: {0}".format(os.getcwd()), file=sys.stderr) - c = p.pipenv("install {0}".format(line)) - assert c.return_code == 0 + assert c.returncode == 0 + print(f"Current directory: {os.getcwd()}", file=sys.stderr) + c = p.pipenv(f"install {line}") + assert c.returncode == 0 assert "testpipenv" in p.pipfile["packages"] assert p.pipfile["packages"]["testpipenv"]["path"] == "." assert p.pipfile["packages"]["testpipenv"]["extras"] == ["dev"] assert "six" in p.lockfile["default"] -@pytest.mark.install @pytest.mark.local +@pytest.mark.install @pytest.mark.needs_internet @flaky -class TestDirectDependencies(object): +class TestDirectDependencies: """Ensure dependency_links are parsed and installed. This is needed for private repo dependencies. @@ -95,10 +91,8 @@ setup( def helper_dependency_links_install_test(pipenv_instance, deplink): TestDirectDependencies.helper_dependency_links_install_make_setup(pipenv_instance, deplink) c = pipenv_instance.pipenv("install -v -e .") - assert c.return_code == 0 + assert c.returncode == 0 assert "test-private-dependency" in pipenv_instance.lockfile["default"] - assert "version" in pipenv_instance.lockfile["default"]["test-private-dependency"] - assert "0.1" in pipenv_instance.lockfile["default"]["test-private-dependency"]["version"] def test_https_dependency_links_install(self, PipenvInstance): """Ensure dependency_links are parsed and installed (needed for private repo dependencies). @@ -122,21 +116,22 @@ setup( @pytest.mark.e +@pytest.mark.local @pytest.mark.install @pytest.mark.skip(reason="this doesn't work on windows") def test_e_dot(PipenvInstance, pip_src_dir): with PipenvInstance() as p: path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - c = p.pipenv("install -e '{0}' --dev".format(path)) + c = p.pipenv(f"install -e '{path}' --dev") - assert c.return_code == 0 + assert c.returncode == 0 key = [k for k in p.pipfile["dev-packages"].keys()][0] assert "path" in p.pipfile["dev-packages"][key] assert "requests" in p.lockfile["develop"] - @pytest.mark.install +@pytest.mark.multiprocessing @flaky def test_multiprocess_bug_and_install(PipenvInstance): with temp_environ(): @@ -153,18 +148,18 @@ urllib3 = "*" f.write(contents) c = p.pipenv("install") - assert c.return_code == 0 + assert c.returncode == 0 assert "pytz" in p.lockfile["default"] assert "six" in p.lockfile["default"] assert "urllib3" in p.lockfile["default"] c = p.pipenv('run python -c "import six; import pytz; import urllib3;"') - assert c.return_code == 0 + assert c.returncode == 0 -@pytest.mark.sequential @pytest.mark.install +@pytest.mark.sequential @flaky def test_sequential_mode(PipenvInstance): @@ -179,18 +174,18 @@ pytz = "*" f.write(contents) c = p.pipenv("install --sequential") - assert c.return_code == 0 + assert c.returncode == 0 assert "six" in p.lockfile["default"] assert "pytz" in p.lockfile["default"] assert "urllib3" in p.lockfile["default"] c = p.pipenv('run python -c "import six; import urllib3; import pytz;"') - assert c.return_code == 0 + assert c.returncode == 0 -@pytest.mark.install @pytest.mark.run +@pytest.mark.install def test_normalize_name_install(PipenvInstance): with PipenvInstance() as p: with open(p.pipfile_path, "w") as f: @@ -202,17 +197,17 @@ Requests = "==2.14.0" # Inline comment f.write(contents) c = p.pipenv("install") - assert c.return_code == 0 + assert c.returncode == 0 c = p.pipenv("install requests") - assert c.return_code == 0 + assert c.returncode == 0 assert "requests" not in p.pipfile["packages"] assert p.pipfile["packages"]["Requests"] == "==2.14.0" c = p.pipenv("install requests==2.18.4") - assert c.return_code == 0 + assert c.returncode == 0 assert p.pipfile["packages"]["Requests"] == "==2.18.4" c = p.pipenv("install python_DateUtil") - assert c.return_code == 0 + assert c.returncode == 0 assert "python-dateutil" in p.pipfile["packages"] with open(p.pipfile_path) as f: contents = f.read() @@ -221,9 +216,10 @@ Requests = "==2.14.0" # Inline comment @flaky -@pytest.mark.files -@pytest.mark.resolver @pytest.mark.eggs +@pytest.mark.files +@pytest.mark.local +@pytest.mark.resolver def test_local_package(PipenvInstance, pip_src_dir, testsroot): """This test ensures that local packages (directories with a setup.py) installed in editable mode have their dependencies resolved as well""" @@ -239,8 +235,8 @@ def test_local_package(PipenvInstance, pip_src_dir, testsroot): with tarfile.open(copy_to, "r:gz") as tgz: tgz.extractall(path=p.path) - c = p.pipenv("install -e {0}".format(package)) - assert c.return_code == 0 + c = p.pipenv(f"install -e {package}") + assert c.returncode == 0 assert all( pkg in p.lockfile["default"] for pkg in ["urllib3", "idna", "certifi", "chardet"] @@ -248,6 +244,7 @@ def test_local_package(PipenvInstance, pip_src_dir, testsroot): @pytest.mark.files +@pytest.mark.local @flaky def test_local_zipfiles(PipenvInstance, testsroot): file_name = "requests-2.19.1.tar.gz" @@ -258,13 +255,13 @@ def test_local_zipfiles(PipenvInstance, testsroot): # This tests for a bug when installing a zipfile in the current dir shutil.copy(source_path, os.path.join(p.path, file_name)) - c = p.pipenv("install {}".format(file_name)) - assert c.return_code == 0 + c = p.pipenv(f"install {file_name}") + assert c.returncode == 0 key = [k for k in p.pipfile["packages"].keys()][0] dep = p.pipfile["packages"][key] assert "file" in dep or "path" in dep - assert c.return_code == 0 + assert c.returncode == 0 # This now gets resolved to its name correctly dep = p.lockfile["default"]["requests"] @@ -272,6 +269,7 @@ def test_local_zipfiles(PipenvInstance, testsroot): assert "file" in dep or "path" in dep +@pytest.mark.local @pytest.mark.files @flaky def test_relative_paths(PipenvInstance, testsroot): @@ -284,17 +282,18 @@ def test_relative_paths(PipenvInstance, testsroot): mkdir_p(artifact_path) shutil.copy(source_path, os.path.join(artifact_path, file_name)) # Test installing a relative path in a subdirectory - c = p.pipenv("install {}/{}".format(artifact_dir, file_name)) - assert c.return_code == 0 + c = p.pipenv(f"install {artifact_dir}/{file_name}") + assert c.returncode == 0 key = next(k for k in p.pipfile["packages"].keys()) dep = p.pipfile["packages"][key] assert "path" in dep assert Path(".", artifact_dir, file_name) == Path(dep["path"]) - assert c.return_code == 0 + assert c.returncode == 0 @pytest.mark.install +@pytest.mark.local @pytest.mark.local_file @flaky def test_install_local_file_collision(PipenvInstance): @@ -303,14 +302,14 @@ def test_install_local_file_collision(PipenvInstance): fake_file = os.path.join(p.path, target_package) with open(fake_file, "w") as f: f.write("") - c = p.pipenv("install {}".format(target_package)) - assert c.return_code == 0 + c = p.pipenv(f"install {target_package}") + assert c.returncode == 0 assert target_package in p.pipfile["packages"] assert p.pipfile["packages"][target_package] == "*" assert target_package in p.lockfile["default"] -@pytest.mark.url +@pytest.mark.urls @pytest.mark.install def test_install_local_uri_special_character(PipenvInstance, testsroot): file_name = "six-1.11.0+mkl-py2.py3-none-any.whl" @@ -330,13 +329,13 @@ six = {{path = "./artifacts/{}"}} ) f.write(contents.strip()) c = p.pipenv("install") - assert c.return_code == 0 + assert c.returncode == 0 assert "six" in p.lockfile["default"] +@pytest.mark.run @pytest.mark.files @pytest.mark.install -@pytest.mark.run def test_multiple_editable_packages_should_not_race(PipenvInstance, testsroot): """Test for a race condition that can occur when installing multiple 'editable' packages at once, and which causes some of them to not be importable. @@ -356,9 +355,8 @@ def test_multiple_editable_packages_should_not_race(PipenvInstance, testsroot): with PipenvInstance(chdir=True) as p: for pkg_name in pkgs: - source_path = p._pipfile.get_fixture_path("git/{0}/".format(pkg_name)).as_posix() - c = delegator.run("git clone {0} ./{1}".format(source_path, pkg_name)) - assert c.return_code == 0 + source_path = p._pipfile.get_fixture_path(f"git/{pkg_name}/").as_posix() + shutil.copytree(source_path, pkg_name) pipfile_string += '"{0}" = {{path = "./{0}", editable = true}}\n'.format(pkg_name) @@ -366,22 +364,21 @@ def test_multiple_editable_packages_should_not_race(PipenvInstance, testsroot): f.write(pipfile_string.strip()) c = p.pipenv('install') - assert c.return_code == 0 + assert c.returncode == 0 c = p.pipenv('run python -c "import requests, flask, six, jinja2"') - assert c.return_code == 0, c.err + assert c.returncode == 0, c.stderr @pytest.mark.outdated -@pytest.mark.py3_only def test_outdated_should_compare_postreleases_without_failing(PipenvInstance): with PipenvInstance(chdir=True) as p: c = p.pipenv("install ibm-db-sa-py3==0.3.0") - assert c.return_code == 0 + assert c.returncode == 0 c = p.pipenv("update --outdated") - assert c.return_code == 0 - assert "Skipped Update" in c.err + assert c.returncode == 0 + assert "Skipped Update" in c.stderr p._pipfile.update("ibm-db-sa-py3", "*") c = p.pipenv("update --outdated") - assert c.return_code != 0 - assert "out-of-date" in c.out + assert c.returncode != 0 + assert "out-of-date" in c.stdout diff --git a/tests/integration/test_install_uri.py b/tests/integration/test_install_uri.py index b71df965..7db10bfb 100644 --- a/tests/integration/test_install_uri.py +++ b/tests/integration/test_install_uri.py @@ -1,31 +1,31 @@ # -*- coding=utf-8 -*- from __future__ import absolute_import, print_function -import pytest +import os +from pathlib import Path + +import pytest from flaky import flaky -import delegator - -from pipenv._compat import Path +from pipenv.utils import subprocess_run @flaky @pytest.mark.vcs @pytest.mark.install @pytest.mark.needs_internet -def test_basic_vcs_install(PipenvInstance): # ! This is failing +def test_basic_vcs_install(PipenvInstance): with PipenvInstance(chdir=True) as p: c = p.pipenv("install git+https://github.com/benjaminp/six.git@1.11.0#egg=six") - assert c.return_code == 0 + assert c.returncode == 0 # edge case where normal package starts with VCS name shouldn't be flagged as vcs c = p.pipenv("install gitdb2") - assert c.return_code == 0 + assert c.returncode == 0 assert all(package in p.pipfile["packages"] for package in ["six", "gitdb2"]) assert "git" in p.pipfile["packages"]["six"] assert p.lockfile["default"]["six"] == { "git": "https://github.com/benjaminp/six.git", "ref": "15e31431af97e5e64b80af0a3f598d382bcdd49a", - "version": "==1.11.0" } assert "gitdb2" in p.lockfile["default"] @@ -37,13 +37,30 @@ def test_basic_vcs_install(PipenvInstance): # ! This is failing def test_git_vcs_install(PipenvInstance): with PipenvInstance(chdir=True) as p: c = p.pipenv("install git+git://github.com/benjaminp/six.git@1.11.0#egg=six") - assert c.return_code == 0 + assert c.returncode == 0 assert "six" in p.pipfile["packages"] assert "git" in p.pipfile["packages"]["six"] assert p.lockfile["default"]["six"] == { "git": "git://github.com/benjaminp/six.git", "ref": "15e31431af97e5e64b80af0a3f598d382bcdd49a", - "version": "==1.11.0" + } + + +@flaky +@pytest.mark.vcs +@pytest.mark.install +@pytest.mark.needs_internet +def test_git_vcs_install_with_env_var(PipenvInstance): + with PipenvInstance(chdir=True) as p: + p._pipfile.add("six", {"git": "git://${GIT_HOST}/benjaminp/six.git", "ref": "1.11.0"}) + os.environ["GIT_HOST"] = "github.com" + c = p.pipenv("install") + assert c.returncode == 0 + assert "six" in p.pipfile["packages"] + assert "git" in p.pipfile["packages"]["six"] + assert p.lockfile["default"]["six"] == { + "git": "git://${GIT_HOST}/benjaminp/six.git", + "ref": "15e31431af97e5e64b80af0a3f598d382bcdd49a", } @@ -55,13 +72,12 @@ def test_git_vcs_install(PipenvInstance): def test_ssh_vcs_install(PipenvInstance): with PipenvInstance(chdir=True) as p: c = p.pipenv("install git+ssh://git@github.com/benjaminp/six.git@1.11.0#egg=six") - assert c.return_code == 0 + assert c.returncode == 0 assert "six" in p.pipfile["packages"] assert "git" in p.pipfile["packages"]["six"] assert p.lockfile["default"]["six"] == { "git": "ssh://git@github.com/benjaminp/six.git", "ref": "15e31431af97e5e64b80af0a3f598d382bcdd49a", - "version": "==1.11.0" } @@ -76,7 +92,7 @@ def test_urls_work(PipenvInstance): c = p.pipenv( "install {0}".format(path) ) - assert c.return_code == 0 + assert c.returncode == 0 dep = list(p.pipfile["packages"].values())[0] assert "file" in dep, p.pipfile @@ -99,7 +115,7 @@ def test_file_urls_work(PipenvInstance, pip_src_dir): whl = whl.absolute() wheel_url = whl.as_uri() c = p.pipenv('install "{0}"'.format(wheel_url)) - assert c.return_code == 0 + assert c.returncode == 0 assert "six" in p.pipfile["packages"] assert "file" in p.pipfile["packages"]["six"] @@ -111,18 +127,19 @@ def test_local_vcs_urls_work(PipenvInstance, tmpdir): six_dir = tmpdir.join("six") six_path = Path(six_dir.strpath) with PipenvInstance(chdir=True) as p: - c = delegator.run( - "git clone https://github.com/benjaminp/six.git {0}".format(six_dir.strpath) + c = subprocess_run( + ["git", "clone", "https://github.com/benjaminp/six.git", six_dir.strpath] ) - assert c.return_code == 0 + assert c.returncode == 0 c = p.pipenv("install git+{0}#egg=six".format(six_path.as_uri())) - assert c.return_code == 0 + assert c.returncode == 0 assert "six" in p.pipfile["packages"] @pytest.mark.e @pytest.mark.vcs +@pytest.mark.urls @pytest.mark.install @pytest.mark.needs_internet def test_editable_vcs_install(PipenvInstance_NoPyPI): @@ -130,7 +147,7 @@ def test_editable_vcs_install(PipenvInstance_NoPyPI): c = p.pipenv( "install -e git+https://github.com/kennethreitz/requests.git#egg=requests" ) - assert c.return_code == 0 + assert c.returncode == 0 assert "requests" in p.pipfile["packages"] assert "git" in p.pipfile["packages"]["requests"] assert "editable" in p.pipfile["packages"]["requests"] @@ -142,7 +159,7 @@ def test_editable_vcs_install(PipenvInstance_NoPyPI): @pytest.mark.vcs -@pytest.mark.tablib +@pytest.mark.urls @pytest.mark.install @pytest.mark.needs_internet def test_install_editable_git_tag(PipenvInstance_NoPyPI): @@ -152,7 +169,7 @@ def test_install_editable_git_tag(PipenvInstance_NoPyPI): c = p.pipenv( "install -e git+https://github.com/benjaminp/six.git@1.11.0#egg=six" ) - assert c.return_code == 0 + assert c.returncode == 0 assert "six" in p.pipfile["packages"] assert "six" in p.lockfile["default"] assert "git" in p.lockfile["default"]["six"] @@ -163,6 +180,7 @@ def test_install_editable_git_tag(PipenvInstance_NoPyPI): assert "ref" in p.lockfile["default"]["six"] +@pytest.mark.urls @pytest.mark.index @pytest.mark.install @pytest.mark.needs_internet @@ -187,26 +205,55 @@ six = "*" """.strip() f.write(contents) c = p.pipenv("install pipenv-test-private-package --index testpypi") - assert c.return_code == 0 + assert c.returncode == 0 + + +@pytest.mark.urls +@pytest.mark.index +@pytest.mark.install +@pytest.mark.needs_internet +def test_install_specifying_index_url(PipenvInstance_NoPyPI): + with PipenvInstance_NoPyPI() as p: + with open(p.pipfile_path, "w") as f: + contents = """ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +six = "*" + +[dev-packages] + """.strip() + f.write(contents) + c = p.pipenv("install pipenv-test-private-package --index https://test.pypi.org/simple") + assert c.returncode == 0 + pipfile = p.pipfile + assert pipfile["source"][1]["url"] == "https://test.pypi.org/simple" + assert pipfile["source"][1]["name"] == "testpypi" + assert pipfile["packages"]["pipenv-test-private-package"]["index"] == "testpypi" @pytest.mark.vcs +@pytest.mark.urls @pytest.mark.install @pytest.mark.needs_internet def test_install_local_vcs_not_in_lockfile(PipenvInstance): with PipenvInstance(chdir=True) as p: # six_path = os.path.join(p.path, "six") six_path = p._pipfile.get_fixture_path("git/six/").as_posix() - c = delegator.run("git clone {0} ./six".format(six_path)) - assert c.return_code == 0 - c = p.pipenv("install -e ./six".format(six_path)) - assert c.return_code == 0 + c = subprocess_run(["git", "clone", six_path, "./six"]) + assert c.returncode == 0 + c = p.pipenv("install -e ./six") + assert c.returncode == 0 six_key = list(p.pipfile["packages"].keys())[0] # we don't need the rest of the test anymore, this just works on its own assert six_key == "six" @pytest.mark.vcs +@pytest.mark.urls @pytest.mark.install @pytest.mark.needs_internet def test_get_vcs_refs(PipenvInstance_NoPyPI): @@ -214,7 +261,7 @@ def test_get_vcs_refs(PipenvInstance_NoPyPI): c = p.pipenv( "install -e git+https://github.com/benjaminp/six.git@1.9.0#egg=six" ) - assert c.return_code == 0 + assert c.returncode == 0 assert "six" in p.pipfile["packages"] assert "six" in p.lockfile["default"] assert ( @@ -225,7 +272,7 @@ def test_get_vcs_refs(PipenvInstance_NoPyPI): new_content = pipfile.read_text().replace(u"1.9.0", u"1.11.0") pipfile.write_text(new_content) c = p.pipenv("lock") - assert c.return_code == 0 + assert c.returncode == 0 assert ( p.lockfile["default"]["six"]["ref"] == "15e31431af97e5e64b80af0a3f598d382bcdd49a" @@ -235,17 +282,16 @@ def test_get_vcs_refs(PipenvInstance_NoPyPI): @pytest.mark.vcs +@pytest.mark.urls @pytest.mark.install @pytest.mark.needs_internet -@pytest.mark.skip_py27_win def test_vcs_entry_supersedes_non_vcs(PipenvInstance): """See issue #2181 -- non-editable VCS dep was specified, but not showing up in the lockfile -- due to not running pip install before locking and not locking the resolution graph of non-editable vcs dependencies. """ with PipenvInstance(chdir=True) as p: - # pyinstaller_path = p._pipfile.get_fixture_path("git/pyinstaller") - pyinstaller_uri = "https://github.com/pyinstaller/pyinstaller.git" + jinja2_uri = p._pipfile.get_fixture_path("git/jinja2").as_uri() with open(p.pipfile_path, "w") as f: f.write( """ @@ -255,24 +301,25 @@ verify_ssl = true name = "pypi" [packages] -PyUpdater = "*" -PyInstaller = {{ref = "develop", git = "{0}"}} - """.format(pyinstaller_uri).strip() +Flask = "*" +Jinja2 = {{ref = "2.11.0", git = "{0}"}} + """.format(jinja2_uri).strip() ) c = p.pipenv("install") - assert c.return_code == 0 - installed_packages = ["PyUpdater", "PyInstaller"] + assert c.returncode == 0 + installed_packages = ["Flask", "Jinja2"] assert all([k in p.pipfile["packages"] for k in installed_packages]) assert all([k.lower() in p.lockfile["default"] for k in installed_packages]) - assert all([k in p.lockfile["default"]["pyinstaller"] for k in ["ref", "git"]]), str(p.lockfile["default"]) - assert p.lockfile["default"]["pyinstaller"].get("ref") is not None + assert all([k in p.lockfile["default"]["jinja2"] for k in ["ref", "git"]]), str(p.lockfile["default"]) + assert p.lockfile["default"]["jinja2"].get("ref") is not None assert ( - p.lockfile["default"]["pyinstaller"]["git"] - == pyinstaller_uri + p.lockfile["default"]["jinja2"]["git"] + == jinja2_uri ) @pytest.mark.vcs +@pytest.mark.urls @pytest.mark.install @pytest.mark.needs_internet def test_vcs_can_use_markers(PipenvInstance): @@ -281,6 +328,6 @@ def test_vcs_can_use_markers(PipenvInstance): p._pipfile.install("six", {"git": "{0}".format(path.as_uri()), "markers": "sys_platform == 'linux'"}) assert "six" in p.pipfile["packages"] c = p.pipenv("install") - assert c.return_code == 0 + assert c.returncode == 0 assert "six" in p.lockfile["default"] assert "git" in p.lockfile["default"]["six"] diff --git a/tests/integration/test_lock.py b/tests/integration/test_lock.py index 7d207cca..e1235f54 100644 --- a/tests/integration/test_lock.py +++ b/tests/integration/test_lock.py @@ -1,13 +1,12 @@ -# -*- coding: utf-8 -*- - import json import os import sys +from pathlib import Path import pytest +import pytest_pypi.app from flaky import flaky -from vistir.compat import Path from vistir.misc import to_text from pipenv.utils import temp_environ @@ -24,7 +23,7 @@ def test_lock_handle_eggs(PipenvInstance): RandomWords = "*" """) c = p.pipenv('lock --verbose') - assert c.return_code == 0 + assert c.returncode == 0 assert 'randomwords' in p.lockfile['default'] assert p.lockfile['default']['randomwords']['version'] == '==0.2.1' @@ -49,14 +48,49 @@ flask = "==0.12.2" c = p.pipenv('lock -r') d = p.pipenv('lock -r -d') - assert c.return_code == 0 - assert d.return_code == 0 + assert c.returncode == 0 + assert d.returncode == 0 for req in req_list: - assert req in c.out + assert req in c.stdout for req in dev_req_list: - assert req in d.out + assert req in d.stdout + + +@pytest.mark.lock +def test_lock_includes_hashes_for_all_platforms(PipenvInstance): + """ Locking should include hashes for *all* platforms, not just the + platform we're running lock on. """ + + releases = pytest_pypi.app.packages['yarl'].releases + def get_hash(release_name): + # Convert a specific filename to a hash like what would show up in a Pipfile.lock. + # For example: + # 'yarl-1.3.0-cp35-cp35m-manylinux1_x86_64.whl' -> 'sha256:3890ab952d508523ef4881457c4099056546593fa05e93da84c7250516e632eb' + return f"sha256:{releases[release_name].hash}" + + with PipenvInstance() as p: + with open(p.pipfile_path, 'w') as f: + contents = """ +[packages] +yarl = "==1.3.0" + """.strip() + f.write(contents) + + c = p.pipenv('lock') + assert c.returncode == 0 + + lock = p.lockfile + assert 'yarl' in lock['default'] + assert set(lock['default']['yarl']['hashes']) == { + get_hash('yarl-1.3.0-cp35-cp35m-manylinux1_x86_64.whl'), + get_hash('yarl-1.3.0-cp35-cp35m-win_amd64.whl'), + get_hash('yarl-1.3.0-cp36-cp36m-manylinux1_x86_64.whl'), + get_hash('yarl-1.3.0-cp36-cp36m-win_amd64.whl'), + get_hash('yarl-1.3.0-cp37-cp37m-win_amd64.whl'), + get_hash('yarl-1.3.0.tar.gz'), + } @pytest.mark.lock @@ -73,7 +107,7 @@ PyTest = "==3.1.0" f.write(contents) c = p.pipenv('lock') - assert c.return_code == 0 + assert c.returncode == 0 lock = p.lockfile assert 'requests' in lock['default'] assert lock['default']['requests']['version'] == "==2.14.0" @@ -89,7 +123,7 @@ PyTest = "*" f.write(updated_contents) c = p.pipenv('lock --keep-outdated') - assert c.return_code == 0 + assert c.returncode == 0 lock = p.lockfile assert 'requests' in lock['default'] assert lock['default']['requests']['version'] == "==2.18.4" @@ -103,33 +137,48 @@ def test_keep_outdated_doesnt_remove_lockfile_entries(PipenvInstance): with PipenvInstance(chdir=True) as p: p._pipfile.add("requests", "==2.18.4") p._pipfile.add("colorama", {"version": "*", "markers": "os_name=='FakeOS'"}) - p.pipenv("install") + c = p.pipenv("install") + assert c.returncode == 0 + assert "doesn't match your environment, its dependencies won't be resolved." in c.stderr p._pipfile.add("six", "*") p.pipenv("lock --keep-outdated") assert "colorama" in p.lockfile["default"] assert p.lockfile["default"]["colorama"]["markers"] == "os_name == 'FakeOS'" +@pytest.mark.lock +def test_resolve_skip_unmatched_requirements(PipenvInstance): + with PipenvInstance(chdir=True) as p: + p._pipfile.add("missing-package", {"markers": "os_name=='FakeOS'"}) + c = p.pipenv("lock") + assert c.returncode == 0 + assert ( + "Could not find a version of missing-package; " + "os_name == 'FakeOS' that matches your environment" + ) in c.stderr + + @pytest.mark.lock @pytest.mark.keep_outdated def test_keep_outdated_doesnt_upgrade_pipfile_pins(PipenvInstance): with PipenvInstance(chdir=True) as p: p._pipfile.add("urllib3", "==1.21.1") c = p.pipenv("install") - assert c.ok + assert c.returncode == 0 p._pipfile.add("requests", "==2.18.4") c = p.pipenv("lock --keep-outdated") - assert c.ok + assert c.returncode == 0 assert "requests" in p.lockfile["default"] assert "urllib3" in p.lockfile["default"] assert p.lockfile["default"]["requests"]["version"] == "==2.18.4" assert p.lockfile["default"]["urllib3"]["version"] == "==1.21.1" +@pytest.mark.lock def test_keep_outdated_keeps_markers_not_removed(PipenvInstance): with PipenvInstance(chdir=True) as p: c = p.pipenv("install six click") - assert c.ok + assert c.returncode == 0 lockfile = Path(p.lockfile_path) lockfile_content = lockfile.read_text() lockfile_json = json.loads(lockfile_content) @@ -137,7 +186,7 @@ def test_keep_outdated_keeps_markers_not_removed(PipenvInstance): lockfile_json["default"]["six"]["markers"] = "python_version >= '2.7'" lockfile.write_text(to_text(json.dumps(lockfile_json))) c = p.pipenv("lock --keep-outdated") - assert c.ok + assert c.returncode == 0 assert p.lockfile["default"]["six"].get("markers", "") == "python_version >= '2.7'" @@ -147,48 +196,50 @@ def test_keep_outdated_doesnt_update_satisfied_constraints(PipenvInstance): with PipenvInstance(chdir=True) as p: p._pipfile.add("requests", "==2.18.4") c = p.pipenv("install") - assert c.ok + assert c.returncode == 0 p._pipfile.add("requests", "*") assert p.pipfile["packages"]["requests"] == "*" c = p.pipenv("lock --keep-outdated") - assert c.ok + assert c.returncode == 0 assert "requests" in p.lockfile["default"] assert "urllib3" in p.lockfile["default"] # ensure this didn't update requests assert p.lockfile["default"]["requests"]["version"] == "==2.18.4" c = p.pipenv("lock") - assert c.ok + assert c.returncode == 0 assert p.lockfile["default"]["requests"]["version"] != "==2.18.4" @pytest.mark.lock @pytest.mark.complex @pytest.mark.needs_internet -def test_complex_lock_with_vcs_deps(PipenvInstance, pip_src_dir): +def test_complex_lock_with_vcs_deps(local_tempdir, PipenvInstance, pip_src_dir): # This uses the real PyPI since we need Internet to access the Git # dependency anyway. - with PipenvInstance() as p: + with PipenvInstance() as p, local_tempdir: + requests_uri = p._pipfile.get_fixture_path("git/requests").as_uri() + dateutil_uri = p._pipfile.get_fixture_path("git/dateutil").as_uri() with open(p.pipfile_path, 'w') as f: contents = """ [packages] click = "==6.7" [dev-packages] -requests = {git = "https://github.com/psf/requests.git"} - """.strip() +requests = {git = "%s"} + """.strip() % requests_uri f.write(contents) c = p.pipenv('install') - assert c.return_code == 0 + assert c.returncode == 0 lock = p.lockfile assert 'requests' in lock['develop'] assert 'click' in lock['default'] - c = p.pipenv('run pip install -e git+https://github.com/dateutil/dateutil#egg=python_dateutil') - assert c.return_code == 0 + c = p.pipenv(f'run pip install -e git+{dateutil_uri}#egg=python_dateutil') + assert c.returncode == 0 c = p.pipenv('lock') - assert c.return_code == 0 + assert c.returncode == 0 lock = p.lockfile assert 'requests' in lock['develop'] assert 'click' in lock['default'] @@ -212,7 +263,7 @@ allow_prereleases = true f.write(contents) c = p.pipenv('lock') - assert c.return_code == 0 + assert c.returncode == 0 assert p.lockfile['default']['sqlalchemy']['version'] == '==1.2.0b3' @@ -232,10 +283,10 @@ maya = "*" f.write(contents) c = p.pipenv('lock --verbose') - assert c.return_code == 0 + assert c.returncode == 0 c = p.pipenv('install') - assert c.return_code == 0 + assert c.returncode == 0 @pytest.mark.lock @@ -250,14 +301,14 @@ requests = {version = "*", extras = ["socks"]} f.write(contents) c = p.pipenv('lock') - assert c.return_code == 0 + assert c.returncode == 0 assert "requests" in p.lockfile["default"] assert "pysocks" in p.lockfile["default"] assert "markers" not in p.lockfile["default"]['pysocks'] c = p.pipenv('lock -r') - assert c.return_code == 0 - assert "extra == 'socks'" not in c.out.strip() + assert c.returncode == 0 + assert "extra == 'socks'" not in c.stdout.strip() @pytest.mark.lock @@ -278,9 +329,9 @@ records = {extras = ["pandas"], version = "==0.5.2"} f.write(contents) c = p.pipenv('install') - assert c.return_code == 0 + assert c.returncode == 0 c = p.pipenv('lock') - assert c.return_code == 0 + assert c.returncode == 0 assert 'tablib' in p.lockfile['default'] assert 'pandas' in p.lockfile['default'] @@ -309,7 +360,7 @@ requests = "*" """.strip() f.write(contents) c = p.pipenv('install --skip-lock') - assert c.return_code == 0 + assert c.returncode == 0 @pytest.mark.lock @@ -338,11 +389,11 @@ requests = "*" """.strip() f.write(contents) c = p.pipenv('install') - assert c.return_code == 0 + assert c.returncode == 0 c = p.pipenv('lock -r') - assert c.return_code == 0 - assert '-i https://pypi.org/simple' in c.out.strip() - assert '--extra-index-url https://test.pypi.org/simple' in c.out.strip() + assert c.returncode == 0 + assert '-i https://pypi.org/simple' in c.stdout.strip() + assert '--extra-index-url https://test.pypi.org/simple' in c.stdout.strip() @pytest.mark.lock @@ -374,15 +425,66 @@ six = {version = "*", index = "testpypi"} fake-package = "*" """.strip() f.write(contents) - c = p.pipenv('install --pypi-mirror {0}'.format(mirror_url)) - assert c.return_code == 0 - c = p.pipenv('lock -r --pypi-mirror {0}'.format(mirror_url)) - assert c.return_code == 0 - assert '-i https://pypi.org/simple' in c.out.strip() - assert '--extra-index-url https://test.pypi.org/simple' in c.out.strip() - # Mirror url should not have replaced source URLs - assert '-i {0}'.format(mirror_url) not in c.out.strip() - assert '--extra-index-url {}'.format(mirror_url) not in c.out.strip() + c = p.pipenv(f'install --pypi-mirror {mirror_url}') + assert c.returncode == 0 + c = p.pipenv(f'lock -r --pypi-mirror {mirror_url}') + assert c.returncode == 0 + assert f'-i {mirror_url}' in c.stdout.strip() + assert '--extra-index-url https://test.pypi.org/simple' in c.stdout.strip() + assert f'--extra-index-url {mirror_url}' not in c.stdout.strip() + + +@pytest.mark.lock +@pytest.mark.install +@pytest.mark.skip_windows +@pytest.mark.skipif(sys.version_info >= (3, 9), reason="old setuptools doesn't work") +@pytest.mark.needs_internet +def test_outdated_setuptools_with_pep517_legacy_build_meta_is_updated(PipenvInstance): + """ + This test ensures we are using build isolation and a pep517 backend + because the package in question includes ``pyproject.toml`` but lacks + a ``build-backend`` declaration. In this case, ``pip`` defaults to using + ``setuptools.build_meta:__legacy__`` as a builder, but without ``pep517`` + enabled and with ``setuptools==40.2.0`` installed, this build backend was + not yet available. ``setuptools<40.8`` will not be aware of this backend. + + If pip is able to build in isolation with a pep517 backend, this will not + matter and the test will still pass as pip will by default install a more + recent version of ``setuptools``. + """ + with PipenvInstance(chdir=True) as p: + c = p.pipenv('run pip install "setuptools<=40.2"') + assert c.returncode == 0 + c = p.pipenv("run python -c 'import setuptools; print(setuptools.__version__)'") + assert c.returncode == 0 + assert c.stdout.strip() == "40.2.0" + c = p.pipenv("install legacy-backend-package") + assert c.returncode == 0 + assert "vistir" in p.lockfile["default"] + + +@pytest.mark.lock +@pytest.mark.install +@pytest.mark.skip_windows +@pytest.mark.skipif(sys.version_info >= (3, 9), reason="old setuptools doesn't work") +@pytest.mark.needs_internet +def test_outdated_setuptools_with_pep517_cython_import_in_setuppy(PipenvInstance): + """ + This test ensures we are using build isolation and a pep517 backend + because the package in question declares 'cython' as a build dependency + in ``pyproject.toml``, then imports it in ``setup.py``. The pep517 + backend will have to install it first, so this will only pass if the + resolver is buliding with a proper backend. + """ + with PipenvInstance(chdir=True) as p: + c = p.pipenv('run pip install "setuptools<=40.2"') + assert c.returncode == 0 + c = p.pipenv("run python -c 'import setuptools; print(setuptools.__version__)'") + assert c.returncode == 0 + assert c.stdout.strip() == "40.2.0" + c = p.pipenv("install cython-import-package") + assert c.returncode == 0 + assert "vistir" in p.lockfile["default"] @pytest.mark.index @@ -403,7 +505,7 @@ requests = "==2.14.0" with temp_environ(): os.environ['MY_ENV_VAR'] = 'simple' c = p.pipenv('lock') - assert c.return_code == 0 + assert c.returncode == 0 assert 'requests' in p.lockfile['default'] with open(p.pipfile_path, 'w') as f: @@ -417,7 +519,7 @@ requests = "==2.14.0" f.write(contents) c = p.pipenv('lock') - assert c.return_code == 0 + assert c.returncode == 0 assert 'requests' in p.lockfile['default'] @@ -426,18 +528,19 @@ requests = "==2.14.0" @pytest.mark.needs_internet def test_lock_editable_vcs_without_install(PipenvInstance): with PipenvInstance(chdir=True) as p: + requests_uri = p._pipfile.get_fixture_path("git/requests").as_uri() with open(p.pipfile_path, 'w') as f: f.write(""" [packages] -requests = {git = "https://github.com/psf/requests.git", ref = "master", editable = true} - """.strip()) +requests = {git = "%s", editable = true} + """.strip() % requests_uri) c = p.pipenv('lock') - assert c.return_code == 0 + assert c.returncode == 0 assert 'requests' in p.lockfile['default'] assert 'idna' in p.lockfile['default'] - assert 'chardet' in p.lockfile['default'] + assert 'certifi' in p.lockfile['default'] c = p.pipenv('install') - assert c.return_code == 0 + assert c.returncode == 0 @pytest.mark.vcs @@ -445,35 +548,18 @@ requests = {git = "https://github.com/psf/requests.git", ref = "master", editabl @pytest.mark.needs_internet def test_lock_editable_vcs_with_ref_in_git(PipenvInstance): with PipenvInstance(chdir=True) as p: + requests_uri = p._pipfile.get_fixture_path("git/requests").as_uri() with open(p.pipfile_path, 'w') as f: f.write(""" [packages] -requests = {git = "https://github.com/psf/requests.git@883caaf", editable = true} - """.strip()) +requests = {git = "%s@883caaf", editable = true} + """.strip() % requests_uri) c = p.pipenv('lock') - assert c.return_code == 0 - assert p.lockfile['default']['requests']['git'] == 'https://github.com/psf/requests.git' + assert c.returncode == 0 + assert p.lockfile['default']['requests']['git'] == requests_uri assert p.lockfile['default']['requests']['ref'] == '883caaf145fbe93bd0d208a6b864de9146087312' c = p.pipenv('install') - assert c.return_code == 0 - - -@pytest.mark.vcs -@pytest.mark.lock -@pytest.mark.needs_internet -def test_lock_editable_vcs_with_ref(PipenvInstance): - with PipenvInstance(chdir=True) as p: - with open(p.pipfile_path, 'w') as f: - f.write(""" -[packages] -requests = {git = "https://github.com/psf/requests.git", ref = "883caaf", editable = true} - """.strip()) - c = p.pipenv('lock') - assert c.return_code == 0 - assert p.lockfile['default']['requests']['git'] == 'https://github.com/psf/requests.git' - assert p.lockfile['default']['requests']['ref'] == '883caaf145fbe93bd0d208a6b864de9146087312' - c = p.pipenv('install') - assert c.return_code == 0 + assert c.returncode == 0 @pytest.mark.vcs @@ -482,19 +568,23 @@ requests = {git = "https://github.com/psf/requests.git", ref = "883caaf", editab @pytest.mark.needs_internet def test_lock_editable_vcs_with_extras_without_install(PipenvInstance): with PipenvInstance(chdir=True) as p: + requests_uri = p._pipfile.get_fixture_path("git/requests").as_uri() with open(p.pipfile_path, 'w') as f: f.write(""" [packages] -requests = {git = "https://github.com/psf/requests.git", editable = true, extras = ["socks"]} - """.strip()) +requests = {git = "%s", editable = true, extras = ["socks"]} + """.strip() % requests_uri) c = p.pipenv('lock') - assert c.return_code == 0 + assert c.returncode == 0 assert 'requests' in p.lockfile['default'] assert 'idna' in p.lockfile['default'] - assert 'chardet' in p.lockfile['default'] + assert 'certifi' in p.lockfile['default'] assert "socks" in p.lockfile["default"]["requests"]["extras"] c = p.pipenv('install') - assert c.return_code == 0 + assert c.returncode == 0 + assert "requests" in p.lockfile["default"] + # For backward compatibility we want to make sure not to include the 'version' key + assert "version" not in p.lockfile["default"]["requests"] @pytest.mark.vcs @@ -502,18 +592,19 @@ requests = {git = "https://github.com/psf/requests.git", editable = true, extras @pytest.mark.needs_internet def test_lock_editable_vcs_with_markers_without_install(PipenvInstance): with PipenvInstance(chdir=True) as p: + requests_uri = p._pipfile.get_fixture_path("git/requests").as_uri() with open(p.pipfile_path, 'w') as f: f.write(""" [packages] -requests = {git = "https://github.com/psf/requests.git", ref = "master", editable = true, markers = "python_version >= '2.6'"} - """.strip()) +requests = {git = "%s", editable = true, markers = "python_version >= '2.6'"} + """.strip() % requests_uri) c = p.pipenv('lock') - assert c.return_code == 0 + assert c.returncode == 0 assert 'requests' in p.lockfile['default'] assert 'idna' in p.lockfile['default'] - assert 'chardet' in p.lockfile['default'] + assert 'certifi' in p.lockfile['default'] c = p.pipenv('install') - assert c.return_code == 0 + assert c.returncode == 0 @pytest.mark.lock @@ -526,10 +617,10 @@ def test_lock_respecting_python_version(PipenvInstance): django = "*" """.strip()) c = p.pipenv('install ') - assert c.return_code == 0 + assert c.returncode == 0 c = p.pipenv('run python --version') - assert c.return_code == 0 - py_version = c.err.splitlines()[-1].strip().split()[-1] + assert c.returncode == 0 + py_version = c.stderr.splitlines()[-1].strip().split()[-1] django_version = '==2.0.6' if py_version.startswith('3') else '==1.11.13' assert py_version == '2.7.14' assert p.lockfile['default']['django']['version'] == django_version @@ -542,8 +633,8 @@ def test_lockfile_corrupted(PipenvInstance): with open(p.lockfile_path, 'w') as f: f.write('{corrupted}') c = p.pipenv('install') - assert c.return_code == 0 - assert 'Pipfile.lock is corrupted' in c.err + assert c.returncode == 0 + assert 'Pipfile.lock is corrupted' in c.stderr assert p.lockfile['_meta'] @@ -554,8 +645,8 @@ def test_lockfile_with_empty_dict(PipenvInstance): with open(p.lockfile_path, 'w') as f: f.write('{}') c = p.pipenv('install') - assert c.return_code == 0 - assert 'Pipfile.lock is corrupted' in c.err + assert c.returncode == 0 + assert 'Pipfile.lock is corrupted' in c.stderr assert p.lockfile['_meta'] @@ -567,30 +658,25 @@ def test_lock_with_incomplete_source(PipenvInstance): with open(p.pipfile_path, 'w') as f: f.write(""" [[source]] -url = "https://test.pypi.org/simple" +url = "{}" [packages] requests = "*" - """) + """.format(p.index_url)) c = p.pipenv('install --skip-lock') - assert c.return_code == 0 + assert c.returncode == 0 c = p.pipenv('install') - assert c.return_code == 0 + assert c.returncode == 0 assert p.lockfile['_meta']['sources'] @pytest.mark.lock @pytest.mark.install -def test_lock_no_warnings(PipenvInstance): +def test_lock_no_warnings(PipenvInstance, recwarn): with PipenvInstance(chdir=True) as p: - os.environ["PYTHONWARNINGS"] = str("once") c = p.pipenv("install six") - assert c.return_code == 0 - c = p.pipenv('run python -c "import warnings; warnings.warn(\\"This is a warning\\", DeprecationWarning); print(\\"hello\\")"') - assert c.return_code == 0 - assert "Warning" in c.err - assert "Warning" not in c.out - assert "hello" in c.out + assert c.returncode == 0 + assert len(recwarn) == 0 @pytest.mark.lock @@ -608,9 +694,9 @@ def test_lock_missing_cache_entries_gets_all_hashes(PipenvInstance, tmpdir): p._pipfile.add("pathlib2", "*") assert "pathlib2" in p.pipfile["packages"] c = p.pipenv("install") - assert c.return_code == 0, (c.err, ("\n".join(["{0}: {1}\n".format(k, v) for k, v in os.environ.items()]))) + assert c.returncode == 0, (c.stderr, ("\n".join([f"{k}: {v}\n" for k, v in os.environ.items()]))) c = p.pipenv("lock --clear") - assert c.return_code == 0, c.err + assert c.returncode == 0, c.stderr assert "pathlib2" in p.lockfile["default"] assert "scandir" in p.lockfile["default"] assert isinstance(p.lockfile["default"]["scandir"]["hashes"], list) @@ -625,12 +711,12 @@ def test_vcs_lock_respects_top_level_pins(PipenvInstance): with PipenvInstance(chdir=True) as p: requests_uri = p._pipfile.get_fixture_path("git/requests").as_uri() p._pipfile.add("requests", { - "editable": True, "git": "{0}".format(requests_uri), + "editable": True, "git": f"{requests_uri}", "ref": "v2.18.4" }) p._pipfile.add("urllib3", "==1.21.1") c = p.pipenv("install") - assert c.return_code == 0 + assert c.returncode == 0 assert "requests" in p.lockfile["default"] assert "git" in p.lockfile["default"]["requests"] assert "urllib3" in p.lockfile["default"] @@ -642,21 +728,79 @@ def test_lock_after_update_source_name(PipenvInstance): with PipenvInstance(chdir=True) as p: contents = """ [[source]] -url = "https://test.pypi.org/simple" +url = "{}" verify_ssl = true name = "test" [packages] six = "*" - """.strip() + """.format(p.index_url).strip() with open(p.pipfile_path, 'w') as f: f.write(contents) c = p.pipenv("lock") - assert c.return_code == 0 + assert c.returncode == 0 assert p.lockfile["default"]["six"]["index"] == "test" with open(p.pipfile_path, 'w') as f: f.write(contents.replace('name = "test"', 'name = "custom"')) c = p.pipenv("lock --clear") - assert c.return_code == 0 + assert c.returncode == 0 assert "index" in p.lockfile["default"]["six"] - assert p.lockfile["default"]["six"]["index"] == "custom", Path(p.lockfile_path).read_text() # p.lockfile["default"]["six"] + assert p.lockfile["default"]["six"]["index"] == "custom", Path(p.lockfile_path).read_text() + + +@pytest.mark.lock +def test_lock_nested_direct_url(PipenvInstance): + """ + The dependency 'test_package' has a declared dependency on + a PEP508 style VCS URL. This ensures that we capture the dependency + here along with its own dependencies. + """ + with PipenvInstance(chdir=True) as p: + c = p.pipenv("install test_package") + assert c.returncode == 0 + assert "vistir" in p.lockfile["default"] + assert "colorama" in p.lockfile["default"] + assert "six" in p.lockfile["default"] + + +@pytest.mark.lock +@pytest.mark.needs_internet +def test_lock_nested_vcs_direct_url(PipenvInstance): + with PipenvInstance(chdir=True) as p: + p._pipfile.add("pep508_package", { + "git": "https://github.com/techalchemy/test-project.git", + "editable": True, "ref": "master", + "subdirectory": "parent_folder/pep508-package" + }) + c = p.pipenv("lock") + assert c.returncode == 0 + assert "git" in p.lockfile["default"]["pep508-package"] + assert "sibling-package" in p.lockfile["default"] + assert "git" in p.lockfile["default"]["sibling-package"] + assert "subdirectory" in p.lockfile["default"]["sibling-package"] + assert "version" not in p.lockfile["default"]["sibling-package"] + + +@pytest.mark.lock +@pytest.mark.install +def test_lock_package_with_wildcard_version(PipenvInstance): + with PipenvInstance(chdir=True) as p: + c = p.pipenv("install 'six==1.11.*'") + assert c.returncode == 0 + assert "six" in p.pipfile["packages"] + assert p.pipfile["packages"]["six"] == "==1.11.*" + assert "six" in p.lockfile["default"] + assert "version" in p.lockfile["default"]["six"] + assert p.lockfile["default"]["six"]["version"] == "==1.11.0" + + +@pytest.mark.lock +@pytest.mark.install +def test_default_lock_overwrite_dev_lock(PipenvInstance): + with PipenvInstance(chdir=True) as p: + c = p.pipenv("install 'click==6.7'") + assert c.returncode == 0 + c = p.pipenv("install -d flask") + assert c.returncode == 0 + assert p.lockfile["default"]["click"]["version"] == "==6.7" + assert p.lockfile["develop"]["click"]["version"] == "==6.7" diff --git a/tests/integration/test_pipenv.py b/tests/integration/test_pipenv.py index 12ae2348..ea352395 100644 --- a/tests/integration/test_pipenv.py +++ b/tests/integration/test_pipenv.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function """Misc. tests that don't fit anywhere. XXX: Try our best to reduce tests in this file. @@ -10,8 +8,7 @@ import os import pytest from pipenv.project import Project -from pipenv.utils import temp_environ -from pipenv.vendor import delegator +from pipenv.utils import subprocess_run, temp_environ @pytest.mark.code @@ -27,36 +24,35 @@ def test_code_import_manual(PipenvInstance): @pytest.mark.lock @pytest.mark.deploy -@pytest.mark.cli def test_deploy_works(PipenvInstance): with PipenvInstance(chdir=True) as p: with open(p.pipfile_path, 'w') as f: contents = """ [packages] -requests = "==2.14.0" -flask = "==0.12.2" +requests = "==2.19.1" +flask = "==1.1.2" [dev-packages] -pytest = "==3.1.1" +pytest = "==4.6.9" """.strip() f.write(contents) c = p.pipenv('install --verbose') - if c.return_code != 0: - assert c.err == '' or c.err is None - assert c.out == '' - assert c.return_code == 0 + if c.returncode != 0: + assert c.stderr == '' or c.stderr is None + assert c.stdout == '' + assert c.returncode == 0 c = p.pipenv('lock') - assert c.return_code == 0 + assert c.returncode == 0 with open(p.pipfile_path, 'w') as f: contents = """ [packages] -requests = "==2.14.0" +requests = "==2.19.1" """.strip() f.write(contents) c = p.pipenv('install --deploy') - assert c.return_code > 0 + assert c.returncode > 0 @pytest.mark.update @@ -64,20 +60,20 @@ requests = "==2.14.0" def test_update_locks(PipenvInstance): with PipenvInstance() as p: c = p.pipenv('install jdcal==1.3') - assert c.return_code == 0 + assert c.returncode == 0 assert p.lockfile['default']['jdcal']['version'] == '==1.3' - with open(p.pipfile_path, 'r') as fh: + with open(p.pipfile_path) as fh: pipfile_contents = fh.read() assert '==1.3' in pipfile_contents pipfile_contents = pipfile_contents.replace('==1.3', '*') with open(p.pipfile_path, 'w') as fh: fh.write(pipfile_contents) c = p.pipenv('update jdcal') - assert c.return_code == 0 + assert c.returncode == 0 assert p.lockfile['default']['jdcal']['version'] == '==1.4' c = p.pipenv('run pip freeze') - assert c.return_code == 0 - lines = c.out.splitlines() + assert c.returncode == 0 + lines = c.stdout.splitlines() assert 'jdcal==1.4' in [l.strip() for l in lines] @@ -85,8 +81,8 @@ def test_update_locks(PipenvInstance): @pytest.mark.proper_names def test_proper_names_unamanged_virtualenv(PipenvInstance): with PipenvInstance(chdir=True): - c = delegator.run('python -m virtualenv .venv') - assert c.return_code == 0 + c = subprocess_run(['python', '-m', 'virtualenv', '.venv']) + assert c.returncode == 0 project = Project() assert project.proper_names == [] @@ -98,10 +94,10 @@ def test_directory_with_leading_dash(raw_venv, PipenvInstance): if "PIPENV_VENV_IN_PROJECT" in os.environ: del os.environ['PIPENV_VENV_IN_PROJECT'] c = p.pipenv('run pip freeze') - assert c.return_code == 0 + assert c.returncode == 0 c = p.pipenv('--venv') - assert c.return_code == 0 - venv_path = c.out.strip() + assert c.returncode == 0 + venv_path = c.stdout.strip() assert os.path.isdir(venv_path) # Manually clean up environment, since PipenvInstance assumes that # the virutalenv is in the project directory. diff --git a/tests/integration/test_project.py b/tests/integration/test_project.py index bef9912f..1ad60a2a 100644 --- a/tests/integration/test_project.py +++ b/tests/integration/test_project.py @@ -1,16 +1,14 @@ -# -*- coding=utf-8 -*- -from __future__ import absolute_import, print_function -import io import os import tarfile +from pathlib import Path + import pytest from pipenv.patched import pipfile from pipenv.project import Project from pipenv.utils import temp_environ -from pipenv.vendor.vistir.path import is_in_path -from pipenv.vendor.delegator import run as delegator_run +from pipenv.vendor.vistir.path import is_in_path, normalize_path @pytest.mark.project @@ -63,7 +61,7 @@ six = {{version = "*", index = "pypi"}} if lock_first: # force source to be cached c = p.pipenv('lock') - assert c.return_code == 0 + assert c.returncode == 0 project = Project() sources = [ ['pypi', 'https://pypi.org/simple'], @@ -84,36 +82,36 @@ six = {{version = "*", index = "pypi"}} @pytest.mark.install @pytest.mark.project -@pytest.mark.parametrize('newlines', [u'\n', u'\r\n']) +@pytest.mark.parametrize('newlines', ['\n', '\r\n']) def test_maintain_file_line_endings(PipenvInstance, newlines): with PipenvInstance(chdir=True) as p: # Initial pipfile + lockfile generation c = p.pipenv('install pytz') - assert c.return_code == 0 + assert c.returncode == 0 # Rewrite each file with parameterized newlines for fn in [p.pipfile_path, p.lockfile_path]: - with io.open(fn) as f: + with open(fn) as f: contents = f.read() written_newlines = f.newlines - assert written_newlines == u'\n', '{0!r} != {1!r} for {2}'.format( - written_newlines, u'\n', fn, + assert written_newlines == '\n', '{!r} != {!r} for {}'.format( + written_newlines, '\n', fn, ) # message because of https://github.com/pytest-dev/pytest/issues/3443 - with io.open(fn, 'w', newline=newlines) as f: + with open(fn, 'w', newline=newlines) as f: f.write(contents) # Run pipenv install to programatically rewrite c = p.pipenv('install chardet') - assert c.return_code == 0 + assert c.returncode == 0 # Make sure we kept the right newlines for fn in [p.pipfile_path, p.lockfile_path]: - with io.open(fn) as f: + with open(fn) as f: f.read() # Consumes the content to detect newlines. actual_newlines = f.newlines - assert actual_newlines == newlines, '{0!r} != {1!r} for {2}'.format( + assert actual_newlines == newlines, '{!r} != {!r} for {}'.format( actual_newlines, newlines, fn, ) # message because of https://github.com/pytest-dev/pytest/issues/3443 @@ -121,6 +119,7 @@ def test_maintain_file_line_endings(PipenvInstance, newlines): @pytest.mark.project @pytest.mark.sources +@pytest.mark.needs_internet def test_many_indexes(PipenvInstance): with PipenvInstance(chdir=True) as p: with open(p.pipfile_path, 'w') as f: @@ -148,7 +147,7 @@ six = {{version = "*", index = "pypi"}} """.format(os.environ['PIPENV_TEST_INDEX']).strip() f.write(contents) c = p.pipenv('install') - assert c.return_code == 0 + assert c.returncode == 0 @pytest.mark.install @@ -160,8 +159,8 @@ def test_include_editable_packages(PipenvInstance, testsroot, pathlib_tmpdir): with PipenvInstance(chdir=True) as p: with tarfile.open(source_path, "r:gz") as tarinfo: tarinfo.extractall(path=str(pathlib_tmpdir)) - c = p.pipenv('install -e {0}'.format(package.as_posix())) - assert c.return_code == 0 + c = p.pipenv(f'install -e {package.as_posix()}') + assert c.returncode == 0 project = Project() assert "tablib" in [ package.project_name @@ -173,38 +172,28 @@ def test_include_editable_packages(PipenvInstance, testsroot, pathlib_tmpdir): @pytest.mark.virtualenv def test_run_in_virtualenv_with_global_context(PipenvInstance, virtualenv): with PipenvInstance(chdir=True, venv_root=virtualenv.as_posix(), ignore_virtualenvs=False, venv_in_project=False) as p: - c = delegator_run( - "pipenv run pip freeze", cwd=os.path.abspath(p.path), - env=os.environ.copy() - ) - assert c.return_code == 0, (c.out, c.err) - assert 'Creating a virtualenv' not in c.err, c.err + c = p.pipenv("run pip freeze") + assert c.returncode == 0, (c.stdout, c.stderr) + assert 'Creating a virtualenv' not in c.stderr, c.stderr project = Project() - assert project.virtualenv_location == virtualenv.as_posix(), ( - project.virtualenv_location, virtualenv.as_posix() + assert Path(project.virtualenv_location).resolve() == Path(virtualenv), ( + project.virtualenv_location, str(virtualenv) ) - c = delegator_run( - "pipenv run pip install click", cwd=os.path.abspath(p.path), - env=os.environ.copy() - ) - assert c.return_code == 0, (c.out, c.err) - assert "Courtesy Notice" in c.err, (c.out, c.err) - c = delegator_run( - "pipenv install six", cwd=os.path.abspath(p.path), env=os.environ.copy() - ) - assert c.return_code == 0, (c.out, c.err) - c = delegator_run( - 'pipenv run python -c "import click;print(click.__file__)"', - cwd=os.path.abspath(p.path), env=os.environ.copy() - ) - assert c.return_code == 0, (c.out, c.err) - assert is_in_path(c.out.strip(), str(virtualenv)), (c.out.strip(), str(virtualenv)) - c = delegator_run( - "pipenv clean --dry-run", cwd=os.path.abspath(p.path), - env=os.environ.copy() - ) - assert c.return_code == 0, (c.out, c.err) - assert "click" in c.out, c.out + + c = p.pipenv(f"run pip install -i {p.index_url} click") + assert c.returncode == 0, (c.stdout, c.stderr) + assert "Courtesy Notice" in c.stderr, (c.stdout, c.stderr) + + c = p.pipenv("install six") + assert c.returncode == 0, (c.stdout, c.stderr) + + c = p.pipenv("run python -c 'import click;print(click.__file__)'") + assert c.returncode == 0, (c.stdout, c.stderr) + assert is_in_path(c.stdout.strip(), str(virtualenv)), (c.stdout.strip(), str(virtualenv)) + + c = p.pipenv("clean --dry-run") + assert c.returncode == 0, (c.stdout, c.stderr) + assert "click" in c.stdout, c.stdout @pytest.mark.project @@ -212,16 +201,32 @@ def test_run_in_virtualenv_with_global_context(PipenvInstance, virtualenv): def test_run_in_virtualenv(PipenvInstance): with PipenvInstance(chdir=True) as p: c = p.pipenv('run pip freeze') - assert c.return_code == 0 - assert 'Creating a virtualenv' in c.err + assert c.returncode == 0 + assert 'Creating a virtualenv' in c.stderr project = Project() c = p.pipenv("run pip install click") - assert c.return_code == 0 + assert c.returncode == 0 c = p.pipenv("install six") - assert c.return_code == 0 + assert c.returncode == 0 c = p.pipenv('run python -c "import click;print(click.__file__)"') - assert c.return_code == 0 - assert c.out.strip().startswith(str(project.virtualenv_location)) + assert c.returncode == 0 + assert normalize_path(c.stdout.strip()).startswith( + normalize_path(str(project.virtualenv_location)) + ) c = p.pipenv("clean --dry-run") - assert c.return_code == 0 - assert "click" in c.out + assert c.returncode == 0 + assert "click" in c.stdout + + +@pytest.mark.project +@pytest.mark.sources +def test_no_sources_in_pipfile(PipenvInstance): + with PipenvInstance(chdir=True) as p: + with open(p.pipfile_path, 'w') as f: + contents = """ +[packages] +pytest = "*" + """.strip() + f.write(contents) + c = p.pipenv('install --skip-lock') + assert c.returncode == 0 diff --git a/tests/integration/test_run.py b/tests/integration/test_run.py index 28f97e48..cf6a7f79 100644 --- a/tests/integration/test_run.py +++ b/tests/integration/test_run.py @@ -1,23 +1,20 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function import os import pytest from pipenv.project import Project -from pipenv.utils import temp_environ +from pipenv.utils import subprocess_run, temp_environ @pytest.mark.run @pytest.mark.dotenv def test_env(PipenvInstance): with PipenvInstance(pipfile=False, chdir=True) as p: - with open('.env', 'w') as f: - f.write('HELLO=WORLD') - - c = p.pipenv('run python -c "import os; print(os.environ[\'HELLO\'])"') - assert c.return_code == 0 - assert 'WORLD' in c.out + with open(os.path.join(p.path, ".env"), "w") as f: + f.write("HELLO=WORLD") + c = subprocess_run(['pipenv', 'run', 'python', '-c', "import os; print(os.environ['HELLO'])"], env=p.env) + assert c.returncode == 0 + assert 'WORLD' in c.stdout @pytest.mark.run @@ -36,19 +33,17 @@ multicommand = "bash -c \"cd docs && make html\"" else: f.write('scriptwithenv = "echo $HELLO"\n') c = p.pipenv('install') - assert c.return_code == 0 - + assert c.returncode == 0 c = p.pipenv('run printfoo') - assert c.return_code == 0 - assert c.out == 'foo\n' - assert c.err == '' + assert c.returncode == 0 + assert c.stdout.splitlines()[0] == 'foo' + assert not c.stderr.strip() c = p.pipenv('run notfoundscript') - assert c.return_code == 1 - assert c.out == '' + assert c.returncode != 0 + assert c.stdout == '' if os.name != 'nt': # TODO: Implement this message for Windows. - assert 'Error' in c.err - assert 'randomthingtotally (from notfoundscript)' in c.err + assert 'not found' in c.stderr project = Project() @@ -63,6 +58,27 @@ multicommand = "bash -c \"cd docs && make html\"" with temp_environ(): os.environ['HELLO'] = 'WORLD' c = p.pipenv("run scriptwithenv") - assert c.ok + assert c.returncode == 0 if os.name != "nt": # This doesn't work on CI windows. - assert c.out.strip() == "WORLD" + assert c.stdout.strip() == "WORLD" + + +@pytest.mark.run +@pytest.mark.skip_windows +def test_run_with_usr_env_shebang(PipenvInstance): + with PipenvInstance(chdir=True) as p: + p.pipenv('install') + script_path = os.path.join(p.path, "test_script") + with open(script_path, "w") as f: + f.write( + "#!/usr/bin/env python\n" + "import sys, os\n\n" + "print(sys.prefix)\n" + "print(os.getenv('VIRTUAL_ENV'))\n" + ) + os.chmod(script_path, 0o700) + c = p.pipenv("run ./test_script") + assert c.returncode == 0 + project = Project() + lines = [line.strip() for line in c.stdout.splitlines()] + assert all(line == project.virtualenv_location for line in lines) diff --git a/tests/integration/test_sync.py b/tests/integration/test_sync.py index d085aaf4..967d3f2e 100644 --- a/tests/integration/test_sync.py +++ b/tests/integration/test_sync.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function import json import os @@ -8,6 +6,7 @@ import pytest from pipenv.utils import temp_environ +@pytest.mark.lock @pytest.mark.sync def test_sync_error_without_lockfile(PipenvInstance): with PipenvInstance(chdir=True) as p: @@ -17,8 +16,8 @@ def test_sync_error_without_lockfile(PipenvInstance): """.strip()) c = p.pipenv('sync') - assert c.return_code != 0 - assert 'Pipfile.lock not found!' in c.err + assert c.returncode != 0 + assert 'Pipfile.lock not found!' in c.stderr @pytest.mark.sync @@ -37,10 +36,10 @@ verify_ssl = true [packages] six = "*" """.strip()) - c = p.pipenv('lock --pypi-mirror {0}'.format(mirror_url)) - assert c.return_code == 0 - c = p.pipenv('sync --pypi-mirror {0}'.format(mirror_url)) - assert c.return_code == 0 + c = p.pipenv(f'lock --pypi-mirror {mirror_url}') + assert c.returncode == 0 + c = p.pipenv(f'sync --pypi-mirror {mirror_url}') + assert c.returncode == 0 @pytest.mark.sync @@ -56,7 +55,7 @@ def test_sync_should_not_lock(PipenvInstance): # Perform initial lock. c = p.pipenv('lock') - assert c.return_code == 0 + assert c.returncode == 0 lockfile_content = p.lockfile assert lockfile_content @@ -67,7 +66,7 @@ def test_sync_should_not_lock(PipenvInstance): six = "*" """.strip()) c = p.pipenv('sync') - assert c.return_code == 0 + assert c.returncode == 0 assert lockfile_content == p.lockfile @@ -83,7 +82,7 @@ requests = "*" f.write(contents) c = p.pipenv('lock') - assert c.return_code == 0 + assert c.returncode == 0 # Force hash mismatch when installing `requests` lock = p.lockfile @@ -92,7 +91,7 @@ requests = "*" json.dump(lock, f) c = p.pipenv('sync --sequential') - assert c.return_code != 0 + assert c.returncode != 0 @pytest.mark.sync @@ -107,8 +106,8 @@ requests = "*" f.write(contents) c = p.pipenv('lock') - assert c.return_code == 0 + assert c.returncode == 0 c = p.pipenv('sync --sequential --verbose') for package in p.lockfile['default']: - assert 'Successfully installed {}'.format(package) in c.out + assert f'Successfully installed {package}' in c.stdout diff --git a/tests/integration/test_uninstall.py b/tests/integration/test_uninstall.py index bc82df8d..8ecc56f6 100644 --- a/tests/integration/test_uninstall.py +++ b/tests/integration/test_uninstall.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function import os import shutil @@ -8,7 +6,6 @@ import pytest from pipenv.utils import temp_environ -@pytest.mark.run @pytest.mark.uninstall @pytest.mark.install def test_uninstall_requests(PipenvInstance): @@ -18,44 +15,44 @@ def test_uninstall_requests(PipenvInstance): # caused by devendoring with PipenvInstance() as p: c = p.pipenv("install requests") - assert c.return_code == 0 + assert c.returncode == 0 assert "requests" in p.pipfile["packages"] c = p.pipenv("run python -m requests.help") - assert c.return_code == 0 + assert c.returncode == 0 c = p.pipenv("uninstall requests") - assert c.return_code == 0 + assert c.returncode == 0 assert "requests" not in p.pipfile["dev-packages"] c = p.pipenv("run python -m requests.help") - assert c.return_code > 0 + assert c.returncode > 0 +@pytest.mark.uninstall def test_uninstall_django(PipenvInstance): with PipenvInstance() as p: - c = p.pipenv("install Django==1.11.13") - assert c.return_code == 0 + c = p.pipenv("install Django") + assert c.returncode == 0 assert "django" in p.pipfile["packages"] assert "django" in p.lockfile["default"] assert "pytz" in p.lockfile["default"] c = p.pipenv("run python -m django --version") - assert c.return_code == 0 + assert c.returncode == 0 c = p.pipenv("uninstall Django") - assert c.return_code == 0 + assert c.returncode == 0 assert "django" not in p.pipfile["dev-packages"] assert "django" not in p.lockfile["develop"] assert p.lockfile["develop"] == {} c = p.pipenv("run python -m django --version") - assert c.return_code > 0 + assert c.returncode > 0 -@pytest.mark.run -@pytest.mark.uninstall @pytest.mark.install +@pytest.mark.uninstall def test_mirror_uninstall(PipenvInstance): with temp_environ(), PipenvInstance(chdir=True) as p: @@ -64,8 +61,8 @@ def test_mirror_uninstall(PipenvInstance): ) assert "pypi.org" not in mirror_url - c = p.pipenv("install Django==1.11.13 --pypi-mirror {0}".format(mirror_url)) - assert c.return_code == 0 + c = p.pipenv(f"install Django --pypi-mirror {mirror_url}") + assert c.returncode == 0 assert "django" in p.pipfile["packages"] assert "django" in p.lockfile["default"] assert "pytz" in p.lockfile["default"] @@ -76,10 +73,10 @@ def test_mirror_uninstall(PipenvInstance): assert "https://pypi.org/simple" == p.lockfile["_meta"]["sources"][0]["url"] c = p.pipenv("run python -m django --version") - assert c.return_code == 0 + assert c.returncode == 0 - c = p.pipenv("uninstall Django --pypi-mirror {0}".format(mirror_url)) - assert c.return_code == 0 + c = p.pipenv(f"uninstall Django --pypi-mirror {mirror_url}") + assert c.returncode == 0 assert "django" not in p.pipfile["dev-packages"] assert "django" not in p.lockfile["develop"] assert p.lockfile["develop"] == {} @@ -90,12 +87,12 @@ def test_mirror_uninstall(PipenvInstance): assert "https://pypi.org/simple" == p.lockfile["_meta"]["sources"][0]["url"] c = p.pipenv("run python -m django --version") - assert c.return_code > 0 + assert c.returncode > 0 @pytest.mark.files -@pytest.mark.uninstall @pytest.mark.install +@pytest.mark.uninstall def test_uninstall_all_local_files(PipenvInstance, testsroot): file_name = "tablib-0.12.1.tar.gz" # Not sure where travis/appveyor run tests from @@ -104,26 +101,25 @@ def test_uninstall_all_local_files(PipenvInstance, testsroot): with PipenvInstance(chdir=True) as p: shutil.copy(source_path, os.path.join(p.path, file_name)) os.mkdir(os.path.join(p.path, "tablib")) - c = p.pipenv("install {}".format(file_name)) - assert c.return_code == 0 + c = p.pipenv(f"install {file_name}") + assert c.returncode == 0 c = p.pipenv("uninstall --all") - assert c.return_code == 0 - assert "tablib" in c.out + assert c.returncode == 0 + assert "tablib" in c.stdout # Uninstall --all is not supposed to remove things from the pipfile # Note that it didn't before, but that instead local filenames showed as hashes assert "tablib" in p.pipfile["packages"] -@pytest.mark.run -@pytest.mark.uninstall @pytest.mark.install +@pytest.mark.uninstall def test_uninstall_all_dev(PipenvInstance): with PipenvInstance() as p: c = p.pipenv("install --dev Django==1.11.13 six") - assert c.return_code == 0 + assert c.returncode == 0 c = p.pipenv("install tablib") - assert c.return_code == 0 + assert c.returncode == 0 assert "tablib" in p.pipfile["packages"] assert "django" in p.pipfile["dev-packages"] @@ -133,10 +129,10 @@ def test_uninstall_all_dev(PipenvInstance): assert "six" in p.lockfile["develop"] c = p.pipenv('run python -c "import django"') - assert c.return_code == 0 + assert c.returncode == 0 c = p.pipenv("uninstall --all-dev") - assert c.return_code == 0 + assert c.returncode == 0 assert p.pipfile["dev-packages"] == {} assert "django" not in p.lockfile["develop"] assert "six" not in p.lockfile["develop"] @@ -144,14 +140,13 @@ def test_uninstall_all_dev(PipenvInstance): assert "tablib" in p.lockfile["default"] c = p.pipenv('run python -c "import django"') - assert c.return_code > 0 + assert c.returncode > 0 c = p.pipenv('run python -c "import tablib"') - assert c.return_code == 0 + assert c.returncode == 0 @pytest.mark.uninstall -@pytest.mark.run def test_normalize_name_uninstall(PipenvInstance): with PipenvInstance() as p: with open(p.pipfile_path, "w") as f: @@ -159,12 +154,12 @@ def test_normalize_name_uninstall(PipenvInstance): # Pre comment [packages] Requests = "*" -python_DateUtil = "*" # Inline comment +python_DateUtil = "*" """ f.write(contents) c = p.pipenv("install") - assert c.return_code == 0 + assert c.returncode == 0 c = p.pipenv("uninstall python_dateutil") assert "Requests" in p.pipfile["packages"] @@ -172,4 +167,30 @@ python_DateUtil = "*" # Inline comment with open(p.pipfile_path) as f: contents = f.read() assert "# Pre comment" in contents - assert "# Inline comment" in contents + + +@pytest.mark.install +@pytest.mark.uninstall +def test_uninstall_all_dev_with_shared_dependencies(PipenvInstance): + with PipenvInstance() as p: + c = p.pipenv("install pytest") + assert c.returncode == 0 + + c = p.pipenv("install --dev six") + assert c.returncode == 0 + + c = p.pipenv("uninstall --all-dev") + assert c.returncode == 0 + + assert "six" in p.lockfile["develop"] + + +@pytest.mark.uninstall +def test_uninstall_missing_parameters(PipenvInstance): + with PipenvInstance() as p: + c = p.pipenv("install requests") + assert c.returncode == 0 + + c = p.pipenv("uninstall") + assert c.returncode != 0 + assert "No package provided!" in c.stderr diff --git a/tests/integration/test_windows.py b/tests/integration/test_windows.py index a74be386..07d9a832 100644 --- a/tests/integration/test_windows.py +++ b/tests/integration/test_windows.py @@ -1,11 +1,9 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function import os +from pathlib import Path import pytest -from pipenv._compat import Path - +from pipenv.utils import subprocess_run # This module is run only on Windows. pytestmark = pytest.mark.skipif(os.name != 'nt', reason="only relevant on windows") @@ -17,12 +15,12 @@ def test_case_changes_windows(PipenvInstance): """ with PipenvInstance(chdir=True) as p: c = p.pipenv('install pytz') - assert c.return_code == 0 + assert c.returncode == 0 # Canonical venv location. c = p.pipenv('--venv') - assert c.return_code == 0 - virtualenv_location = c.out.strip() + assert c.returncode == 0 + virtualenv_location = c.stdout.strip() # Dance around to change the casing of the project directory. target = p.path.upper() @@ -34,11 +32,12 @@ def test_case_changes_windows(PipenvInstance): # Ensure the incorrectly-cased project can find the correct venv. c = p.pipenv('--venv') - assert c.return_code == 0 - assert c.out.strip().lower() == virtualenv_location.lower() + assert c.returncode == 0 + assert c.stdout.strip().lower() == virtualenv_location.lower() @pytest.mark.files +@pytest.mark.local def test_local_path_windows(PipenvInstance): whl = ( Path(__file__).parent.parent @@ -49,10 +48,11 @@ def test_local_path_windows(PipenvInstance): except OSError: whl = whl.absolute() with PipenvInstance(chdir=True) as p: - c = p.pipenv('install "{0}"'.format(whl)) - assert c.return_code == 0 + c = p.pipenv(f'install "{whl}"') + assert c.returncode == 0 +@pytest.mark.local @pytest.mark.files def test_local_path_windows_forward_slash(PipenvInstance): whl = ( @@ -64,18 +64,24 @@ def test_local_path_windows_forward_slash(PipenvInstance): except OSError: whl = whl.absolute() with PipenvInstance(chdir=True) as p: - c = p.pipenv('install "{0}"'.format(whl.as_posix())) - assert c.return_code == 0 + c = p.pipenv(f'install "{whl.as_posix()}"') + assert c.returncode == 0 @pytest.mark.cli def test_pipenv_clean_windows(PipenvInstance): with PipenvInstance(chdir=True) as p: c = p.pipenv('install requests') - assert c.return_code == 0 - c = p.pipenv('run pip install click') - assert c.return_code == 0 + assert c.returncode == 0 + c = p.pipenv(f'run pip install -i {p.index_url} click') + assert c.returncode == 0 c = p.pipenv('clean --dry-run') - assert c.return_code == 0 - assert 'click' in c.out.strip() + assert c.returncode == 0 + assert 'click' in c.stdout.strip() + +@pytest.mark.cli +def test_pipenv_run_with_special_chars_windows(PipenvInstance): + with PipenvInstance(): + c = subprocess_run(["pipenv", "run", "echo", "[3-1]"]) + assert c.returncode == 0, c.stderr diff --git a/tests/pypi b/tests/pypi index 38f55ba5..f5530013 160000 --- a/tests/pypi +++ b/tests/pypi @@ -1 +1 @@ -Subproject commit 38f55ba5883f1ce47c6f1f46feecc0d318c444a5 +Subproject commit f5530013426d6392d67cd1703f379d20a768c1cf diff --git a/tests/pytest-pypi/pyproject.toml b/tests/pytest-pypi/pyproject.toml new file mode 100644 index 00000000..864b334a --- /dev/null +++ b/tests/pytest-pypi/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta:__legacy__" diff --git a/tests/pytest-pypi/pytest_pypi/app.py b/tests/pytest-pypi/pytest_pypi/app.py index 5484eeb7..784150f8 100644 --- a/tests/pytest-pypi/pytest_pypi/app.py +++ b/tests/pytest-pypi/pytest_pypi/app.py @@ -1,5 +1,4 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function +import collections import contextlib import io import json @@ -8,12 +7,15 @@ import os from tarfile import is_tarfile from zipfile import is_zipfile +import distlib.wheel import requests from six.moves import xmlrpc_client from flask import Flask, redirect, abort, render_template, send_file, jsonify +ReleaseTuple = collections.namedtuple("ReleaseTuple", ["path", "requires_python", "hash"]) + app = Flask(__name__) session = requests.Session() @@ -38,28 +40,28 @@ def get_pypi_package_names(): return pypi_packages -class Package(object): +class Package: """Package represents a collection of releases from one or more directories""" def __init__(self, name): - super(Package, self).__init__() + super().__init__() self.name = name self.releases = {} self._package_dirs = set() @property def json(self): - for path in self._package_dirs: + for path, _ in self._package_dirs: try: with open(os.path.join(path, 'api.json')) as f: return json.load(f) except FileNotFoundError: - r = session.get('https://pypi.org/pypi/{0}/json'.format(self.name)) + r = session.get(f'https://pypi.org/pypi/{self.name}/json') response = r.json() releases = response["releases"] files = { pkg for pkg_dir in self._package_dirs - for pkg in os.listdir(pkg_dir) + for pkg in os.listdir(pkg_dir.path) } for release in list(releases.keys()): values = ( @@ -71,31 +73,42 @@ class Package(object): else: del releases[release] response["releases"] = releases - with io.open(os.path.join(path, "api.json"), "w") as fh: + with open(os.path.join(path, "api.json"), "w") as fh: json.dump(response, fh, indent=4) return response def __repr__(self): - return "<Package name={0!r} releases={1!r}".format(self.name, len(self.releases)) + return f"<Package name={self.name!r} releases={len(self.releases)!r}" def add_release(self, path_to_binary): path_to_binary = os.path.abspath(path_to_binary) path, release = os.path.split(path_to_binary) - self.releases[release] = path_to_binary - self._package_dirs.add(path) + requires_python = "" + hash_value = "" + if path_to_binary.endswith(".whl"): + pkg = distlib.wheel.Wheel(path_to_binary) + md_dict = pkg.metadata.todict() + requires_python = md_dict.get("requires_python", "") + if requires_python.count(".") > 1: + requires_python, _, _ = requires_python.rpartition(".") + if os.path.isfile(path_to_binary + ".sha256"): + with open(path_to_binary + ".sha256") as f: + hash_value = f.read().strip() + self.releases[release] = ReleaseTuple(path_to_binary, requires_python, hash_value) + self._package_dirs.add(ReleaseTuple(path, requires_python, hash_value)) -class Artifact(object): +class Artifact: """Represents an artifact for download""" def __init__(self, name): - super(Artifact, self).__init__() + super().__init__() self.name = name self.files = {} self._artifact_dirs = set() def __repr__(self): - return "<Artifact name={0!r} files={1!r}".format(self.name, len(self.files)) + return f"<Artifact name={self.name!r} files={len(self.files)!r}" def add_file(self, path): path = os.path.abspath(path) @@ -107,7 +120,7 @@ class Artifact(object): def prepare_fixtures(path): path = os.path.abspath(path) if not (os.path.exists(path) and os.path.isdir(path)): - raise ValueError("{} is not a directory!".format(path)) + raise ValueError(f"{path} is not a directory!") for root, dirs, files in os.walk(path): package_name, _, _ = os.path.relpath(root, start=path).partition(os.path.sep) if package_name not in ARTIFACTS: @@ -129,7 +142,7 @@ def prepare_packages(path): """Add packages in path to the registry.""" path = os.path.abspath(path) if not (os.path.exists(path) and os.path.isdir(path)): - raise ValueError("{} is not a directory!".format(path)) + raise ValueError(f"{path} is not a directory!") for root, dirs, files in os.walk(path): if all([setup_file in list(files) for setup_file in ("setup.py", "setup.cfg")]): continue @@ -144,9 +157,9 @@ def prepare_packages(path): packages[package_name] = Package(package_name) packages[package_name].add_release(os.path.join(root, file)) - remaining = get_pypi_package_names() - set(list(packages.keys())) - for pypi_pkg in remaining: - packages[pypi_pkg] = Package(pypi_pkg) + # remaining = get_pypi_package_names() - set(list(packages.keys())) + # for pypi_pkg in remaining: + # packages[pypi_pkg] = Package(pypi_pkg) @app.route('/') @@ -170,7 +183,7 @@ def simple_package(package): return render_template('package.html', package=packages[package]) else: try: - r = requests.get("https://pypi.org/simple/{0}".format(package)) + r = requests.get(f"https://pypi.org/simple/{package}") r.raise_for_status() except Exception: abort(404) @@ -194,7 +207,7 @@ def serve_package(package, release): package = packages[package] if release in package.releases: - return send_file(package.releases[release]) + return send_file(package.releases[release].path) abort(404) diff --git a/tests/pytest-pypi/pytest_pypi/plugin.py b/tests/pytest-pypi/pytest_pypi/plugin.py index 83cd73fb..14d4a3c7 100644 --- a/tests/pytest-pypi/pytest_pypi/plugin.py +++ b/tests/pytest-pypi/pytest_pypi/plugin.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import import pytest from .app import app as pypi_app from . import serve, certs diff --git a/tests/pytest-pypi/pytest_pypi/serve.py b/tests/pytest-pypi/pytest_pypi/serve.py index 07a92dc0..ac903524 100644 --- a/tests/pytest-pypi/pytest_pypi/serve.py +++ b/tests/pytest-pypi/pytest_pypi/serve.py @@ -77,7 +77,7 @@ class SecureWSGIServer(WSGIServer): # Thanks, WSGIRequestHandler!! -class Server(object): +class Server: """ HTTP server running a WSGI application in its own thread. """ @@ -119,7 +119,7 @@ class Server(object): @property def url(self): - return '{0}://{1}:{2}'.format(self.protocol, self.host, self.port) + return f'{self.protocol}://{self.host}:{self.port}' def join(self, url, allow_fragments=True): return urljoin(self.url, url, allow_fragments=allow_fragments) @@ -130,5 +130,5 @@ class SecureServer(Server): def __init__(self, host='127.0.0.1', port=0, application=None, **kwargs): kwargs['server_class'] = SecureWSGIServer - super(SecureServer, self).__init__(host, port, application, **kwargs) + super().__init__(host, port, application, **kwargs) self.protocol = 'https' diff --git a/tests/pytest-pypi/pytest_pypi/templates/package.html b/tests/pytest-pypi/pytest_pypi/templates/package.html index 26ba9eca..f8c5858a 100644 --- a/tests/pytest-pypi/pytest_pypi/templates/package.html +++ b/tests/pytest-pypi/pytest_pypi/templates/package.html @@ -6,8 +6,8 @@ </head> <body> <h1>Links for {{ package.name }}</h1> - {% for release in package.releases %} - <a href="/{{ package.name }}/{{ release }}">{{ release }}</a> + {% for release, value in package.releases.items() %} + <a href="/{{ package.name }}/{{ release }}{%- if value.hash %}#sha256={{ value.hash }}{% endif %}"{%- if value.requires_python %} data-requires-python="{{ value.requires_python }}"{% endif %}>{{ release }}</a> <br> {% endfor %} </body> diff --git a/tests/pytest-pypi/pytest_pypi/templates/simple.html b/tests/pytest-pypi/pytest_pypi/templates/simple.html index 97f67558..2b63b49f 100644 --- a/tests/pytest-pypi/pytest_pypi/templates/simple.html +++ b/tests/pytest-pypi/pytest_pypi/templates/simple.html @@ -10,4 +10,4 @@ <br> {% endfor %} </body> -</html> \ No newline at end of file +</html> diff --git a/tests/pytest-pypi/setup.py b/tests/pytest-pypi/setup.py index 558dd3f8..2fcdd58b 100644 --- a/tests/pytest-pypi/setup.py +++ b/tests/pytest-pypi/setup.py @@ -24,7 +24,7 @@ class UploadCommand(Command): @staticmethod def status(s): """Prints things in bold.""" - print('\033[1m{0}\033[0m'.format(s)) + print(f'\033[1m{s}\033[0m') def initialize_options(self): pass @@ -34,19 +34,19 @@ class UploadCommand(Command): def run(self): try: - self.status('Removing previous builds…') + self.status('Removing previous builds...') rmtree(os.path.join(here, 'dist')) except OSError: pass - self.status('Building Source and Wheel (universal) distribution…') - os.system('{0} setup.py sdist bdist_wheel --universal'.format(sys.executable)) + self.status('Building Source and Wheel (universal) distribution...') + os.system(f'{sys.executable} setup.py sdist bdist_wheel --universal') - self.status('Uploading the package to PyPI via Twine…') + self.status('Uploading the package to PyPI via Twine...') os.system('twine upload dist/*') - self.status('Pushing git tags…') - os.system('git tag v{0}'.format(__version__)) + self.status('Pushing git tags...') + os.system(f'git tag v{__version__}') os.system('git push --tags') sys.exit() diff --git a/tests/test_artifacts/git/flask b/tests/test_artifacts/git/flask index dcc02d6e..1b4ace9b 160000 --- a/tests/test_artifacts/git/flask +++ b/tests/test_artifacts/git/flask @@ -1 +1 @@ -Subproject commit dcc02d6e7d4bf486e64aa5b6e55a75501c2ba2e5 +Subproject commit 1b4ace9ba5e77679bf9d8e409283654f7589907e diff --git a/tests/test_artifacts/git/requests-2.18.4 b/tests/test_artifacts/git/requests-2.18.4 deleted file mode 160000 index a3d7cf3f..00000000 --- a/tests/test_artifacts/git/requests-2.18.4 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a3d7cf3f27e74c28ef30f01e9f2e483570ab042e diff --git a/tests/test_artifacts/git/six-1.9.0 b/tests/test_artifacts/git/six-1.9.0 deleted file mode 160000 index 5efb522b..00000000 --- a/tests/test_artifacts/git/six-1.9.0 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5efb522b0647f7467248273ec1b893d06b984a59 diff --git a/tests/unit/test_core.py b/tests/unit/test_core.py index 61d318c3..66e28a1f 100644 --- a/tests/unit/test_core.py +++ b/tests/unit/test_core.py @@ -1,59 +1,70 @@ import os +from tempfile import TemporaryDirectory -import mock import pytest -from pipenv._compat import TemporaryDirectory from pipenv.core import load_dot_env, warn_in_virtualenv from pipenv.utils import temp_environ -@mock.patch('pipenv.environments.PIPENV_VIRTUALENV', 'totallyrealenv') -@mock.patch('pipenv.environments.PIPENV_VERBOSITY', -1) @pytest.mark.core -def test_suppress_nested_venv_warning(capsys): +def test_suppress_nested_venv_warning(capsys, project): # Capture the stderr of warn_in_virtualenv to test for the presence of the # courtesy notice. - warn_in_virtualenv() + project.s.PIPENV_VIRTUALENV = 'totallyrealenv' + project.s.PIPENV_VERBOSITY = -1 + warn_in_virtualenv(project) output, err = capsys.readouterr() assert 'Courtesy Notice' not in err @pytest.mark.core -def test_load_dot_env_from_environment_variable_location(capsys): - with temp_environ(), TemporaryDirectory(prefix='pipenv-', suffix='') as tempdir: - dotenv_path = os.path.join(tempdir.name, 'test.env') +def test_load_dot_env_from_environment_variable_location(monkeypatch, capsys, project): + with temp_environ(), monkeypatch.context() as m, TemporaryDirectory(prefix='pipenv-', suffix='') as tempdir: + if os.name == "nt": + import click + is_console = False + m.setattr(click._winconsole, "_is_console", lambda x: is_console) + dotenv_path = os.path.join(tempdir, 'test.env') key, val = 'SOME_KEY', 'some_value' with open(dotenv_path, 'w') as f: - f.write('{}={}'.format(key, val)) + f.write(f'{key}={val}') - with mock.patch('pipenv.environments.PIPENV_DOTENV_LOCATION', dotenv_path): - load_dot_env() + project.s.PIPENV_DOTENV_LOCATION = str(dotenv_path) + load_dot_env(project) assert os.environ[key] == val @pytest.mark.core -def test_doesnt_load_dot_env_if_disabled(capsys): - with temp_environ(), TemporaryDirectory(prefix='pipenv-', suffix='') as tempdir: - dotenv_path = os.path.join(tempdir.name, 'test.env') +def test_doesnt_load_dot_env_if_disabled(monkeypatch, capsys, project): + with temp_environ(), monkeypatch.context() as m, TemporaryDirectory(prefix='pipenv-', suffix='') as tempdir: + if os.name == "nt": + import click + is_console = False + m.setattr(click._winconsole, "_is_console", lambda x: is_console) + dotenv_path = os.path.join(tempdir, 'test.env') key, val = 'SOME_KEY', 'some_value' with open(dotenv_path, 'w') as f: - f.write('{}={}'.format(key, val)) + f.write(f'{key}={val}') - with mock.patch('pipenv.environments.PIPENV_DOTENV_LOCATION', dotenv_path): - with mock.patch('pipenv.environments.PIPENV_DONT_LOAD_ENV', '1'): - load_dot_env() - assert key not in os.environ - - load_dot_env() - assert key in os.environ + project.s.PIPENV_DOTENV_LOCATION = str(dotenv_path) + project.s.PIPENV_DONT_LOAD_ENV = True + load_dot_env(project) + assert key not in os.environ + project.s.PIPENV_DONT_LOAD_ENV = False + load_dot_env(project) + assert key in os.environ @pytest.mark.core -def test_load_dot_env_warns_if_file_doesnt_exist(capsys): - with temp_environ(), TemporaryDirectory(prefix='pipenv-', suffix='') as tempdir: - dotenv_path = os.path.join(tempdir.name, 'does-not-exist.env') - with mock.patch('pipenv.environments.PIPENV_DOTENV_LOCATION', dotenv_path): - load_dot_env() +def test_load_dot_env_warns_if_file_doesnt_exist(monkeypatch, capsys, project): + with temp_environ(), monkeypatch.context() as m, TemporaryDirectory(prefix='pipenv-', suffix='') as tempdir: + if os.name == "nt": + import click + is_console = False + m.setattr(click._winconsole, "_is_console", lambda x: is_console) + dotenv_path = os.path.join(tempdir, 'does-not-exist.env') + project.s.PIPENV_DOTENV_LOCATION = str(dotenv_path) + load_dot_env(project) output, err = capsys.readouterr() assert 'Warning' in err diff --git a/tests/unit/test_environments.py b/tests/unit/test_environments.py new file mode 100644 index 00000000..efeb2424 --- /dev/null +++ b/tests/unit/test_environments.py @@ -0,0 +1,68 @@ +import itertools +import pytest +import os +from pipenv import environments +from pipenv.utils import temp_environ + + +@pytest.mark.environments +@pytest.mark.parametrize( + "arg, prefix, use_negation", + list(itertools.product(("ENABLE_SOMETHING",), ("FAKEPREFIX", None), (True, False))), +) +def test_get_from_env(arg, prefix, use_negation): + negated_arg = f"NO_{arg}" + positive_var = arg + negative_var = negated_arg + if prefix: + negative_var = f"{prefix}_{negative_var}" + positive_var = f"{prefix}_{positive_var}" + # set the positive first + for var_to_set, opposite_var in ((arg, negated_arg), (negated_arg, arg)): + os.environ.pop(var_to_set, None) + os.environ.pop(opposite_var, None) + with temp_environ(): + is_positive = var_to_set == arg + is_negative = not is_positive + envvar = positive_var if is_positive else negative_var + os.environ[envvar] = "true" + main_expected_value = True if is_positive else None + if use_negation and not is_positive: + main_expected_value = False + # use negation means if the normal variable isnt set we will check + # for the negated version + negative_expected_value = ( + True if is_negative else None + ) + if is_positive: + assert ( + environments.get_from_env( + var_to_set, prefix, check_for_negation=use_negation + ) + is main_expected_value + ) + assert ( + environments.get_from_env( + opposite_var, prefix, check_for_negation=use_negation + ) + is negative_expected_value + ) + else: + # var_to_set = negative version i.e. NO_xxxx + # opposite_var = positive_version i.e. XXXX + + # get NO_BLAH -- expecting this to be True + assert ( + environments.get_from_env( + var_to_set, prefix, check_for_negation=use_negation + ) + is negative_expected_value + ) + # get BLAH -- expecting False if checking for negation + # but otherwise should be None + assert ( + environments.get_from_env( + opposite_var, prefix, check_for_negation=use_negation + ) + is main_expected_value + ) diff --git a/tests/unit/test_help.py b/tests/unit/test_help.py index d3755601..812925f7 100644 --- a/tests/unit/test_help.py +++ b/tests/unit/test_help.py @@ -2,23 +2,14 @@ import os import subprocess import sys +import pytest + +@pytest.mark.cli +@pytest.mark.help def test_help(): output = subprocess.check_output( [sys.executable, '-m', 'pipenv.help'], stderr=subprocess.STDOUT, env=os.environ.copy(), ) assert output - - -def test_count_of_description_pre_option(): - test_command = 'pipenv install --help' - test_line = '--pre Allow pre-releases.' - out = subprocess.Popen(test_command.split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - stdout, _ = out.communicate() - lines = stdout.decode().split('\n') - count = 0 - for line in lines: - if line.strip().split() == test_line.split(): - count += 1 - assert count == 1 diff --git a/tests/unit/test_patched.py b/tests/unit/test_patched.py deleted file mode 100644 index 03e1c039..00000000 --- a/tests/unit/test_patched.py +++ /dev/null @@ -1,132 +0,0 @@ -from __future__ import absolute_import - -import pytest - -from notpip._internal.index import PackageFinder - - -get_extras_links_scenarios = { - 'windows and not windows': ( - [ - 'chardet', - '[:platform_system != "windows"]', - 'twisted', - '[:platform_system == "windows"]', - 'twisted[windows_platform]', - ], - { - ':platform_system != "windows"': [ - 'twisted', - ], - ':platform_system == "windows"': [ - 'twisted[windows_platform]', - ], - }, - ), - 'requests': ( - [ - 'chardet<3.1.0,>=3.0.2', - 'idna<2.7,>=2.5', - 'urllib3<1.23,>=1.21.1', - 'certifi>=2017.4.17', - '[security]', - 'pyOpenSSL>=0.14', - 'cryptography>=1.3.4', - 'idna>=2.0.0', - '[socks]', - 'PySocks!=1.5.7,>=1.5.6', - ( - '[socks:sys_platform == "win32"' - ' and (python_version == "2.7" or python_version == "2.6")]' - ), - 'win_inet_pton', - ], - { - 'security': [ - 'pyOpenSSL>=0.14', - 'cryptography>=1.3.4', - 'idna>=2.0.0', - ], - 'socks': [ - 'PySocks!=1.5.7,>=1.5.6', - ], - 'socks:sys_platform == "win32" ' - 'and (python_version == "2.7" or python_version == "2.6")': [ - 'win_inet_pton', - ], - } - ), - 'attrs': ( - [ - '[dev]', - 'coverage', - 'hypothesis', - 'pympler', - 'pytest', - 'six', - 'zope.interface', - 'sphinx', - 'zope.interface', - '[docs]', - 'sphinx', - 'zope.interface', - '[tests]', - 'coverage', - 'hypothesis', - 'pympler', - 'pytest', - 'six', - 'zope.interface', - ], - { - 'dev': [ - 'coverage', - 'hypothesis', - 'pympler', - 'pytest', - 'six', - 'zope.interface', - 'sphinx', - 'zope.interface', - ], - 'docs': [ - 'sphinx', - 'zope.interface', - ], - 'tests': [ - 'coverage', - 'hypothesis', - 'pympler', - 'pytest', - 'six', - 'zope.interface', - ], - }, - ), - 'misc': ( - [ - 'chardet', - '[:platform_system != "windows"]', - 'attrs', - '[:platform_system == "windows"]', - 'pytz', - ], - { - ':platform_system != "windows"': [ - 'attrs', - ], - ':platform_system == "windows"': [ - 'pytz', - ], - }, - ), -} - - -@pytest.mark.parametrize( - 'scenarios,expected', - list(get_extras_links_scenarios.values()), - ids=list(get_extras_links_scenarios.keys()), -) -def test_get_extras_links(scenarios, expected): - assert PackageFinder.get_extras_links(scenarios) == expected diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index d3363037..9139c8c3 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import os import pytest @@ -51,11 +50,11 @@ DEP_PIP_PAIRS = [ # Extras in url { "discord.py": { - "file": "https://github.com/Rapptz/discord.py/archive/rewrite.zip", + "file": "https://github.com/Rapptz/discord.py/archive/async.zip", "extras": ["voice"], } }, - "https://github.com/Rapptz/discord.py/archive/rewrite.zip#egg=discord.py[voice]", + "https://github.com/Rapptz/discord.py/archive/async.zip#egg=discord.py[voice]", ), ( { @@ -78,6 +77,7 @@ def mock_unpack(link, source_dir, download_dir, only_download=False, session=Non @pytest.mark.utils @pytest.mark.parametrize("deps, expected", DEP_PIP_PAIRS) +@pytest.mark.needs_internet def test_convert_deps_to_pip(monkeypatch, deps, expected): with monkeypatch.context() as m: import pip_shims @@ -129,14 +129,37 @@ def test_convert_deps_to_pip_one_way(deps, expected): assert pipenv.utils.convert_deps_to_pip(deps, r=False) == [expected.lower()] -@pytest.mark.skipif(isinstance(u"", str), reason="don't need to test if unicode is str") +@pytest.mark.skipif(isinstance("", str), reason="don't need to test if unicode is str") @pytest.mark.utils def test_convert_deps_to_pip_unicode(): - deps = {u"django": u"==1.10"} + deps = {"django": "==1.10"} deps = pipenv.utils.convert_deps_to_pip(deps, r=False) assert deps[0] == "django==1.10" +@pytest.mark.parametrize("line,result", [ + ("-i https://example.com/simple/", ("https://example.com/simple/", None, None, [])), + ("--extra-index-url=https://example.com/simple/", (None, "https://example.com/simple/", None, [])), + ("--trusted-host=example.com", (None, None, "example.com", [])), + ("# -i https://example.com/simple/", (None, None, None, [])), + ("requests # -i https://example.com/simple/", (None, None, None, ["requests"])), +]) +@pytest.mark.utils +def test_parse_indexes(line, result): + assert pipenv.utils.parse_indexes(line) == result + + +@pytest.mark.parametrize("line", [ + "-i https://example.com/simple/ --extra-index-url=https://extra.com/simple/", + "--extra-index-url https://example.com/simple/ --trusted-host=example.com", + "requests -i https://example.com/simple/", +]) +@pytest.mark.utils +def test_parse_indexes_individual_lines(line): + with pytest.raises(ValueError): + pipenv.utils.parse_indexes(line, strict=True) + + class TestUtils: """Test utility functions in pipenv""" @@ -223,6 +246,7 @@ class TestUtils: assert pipenv.utils.is_valid_url(not_url) is False @pytest.mark.utils + @pytest.mark.needs_internet def test_download_file(self): url = "https://github.com/pypa/pipenv/blob/master/README.md" output = "test_download.md" @@ -425,6 +449,7 @@ twine = "*" == expected_args ) + @pytest.mark.utils def test_invalid_prepare_pip_source_args(self): sources = [{}] with pytest.raises(PipenvUsageError): diff --git a/tests/unit/test_utils_windows_executable.py b/tests/unit/test_utils_windows_executable.py index b74cfbf3..22f20b35 100644 --- a/tests/unit/test_utils_windows_executable.py +++ b/tests/unit/test_utils_windows_executable.py @@ -13,6 +13,7 @@ pytestmark = pytest.mark.skipif( ) +@pytest.mark.utils @mock.patch('os.path.isfile') @mock.patch('pipenv.utils.find_executable') def test_find_windows_executable(mocked_find_executable, mocked_isfile): diff --git a/tests/unit/test_vendor.py b/tests/unit/test_vendor.py index aea93112..62f317b9 100644 --- a/tests/unit/test_vendor.py +++ b/tests/unit/test_vendor.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # We need to import the patched packages directly from sys.path, so the # identity checks can pass. import pipenv # noqa @@ -82,6 +81,6 @@ def test_token_date(dt, content): def test_dump_nonascii_string(): - content = u'name = "Stažené"\n' + content = 'name = "Stažené"\n' toml_content = tomlkit.dumps(tomlkit.loads(content)) assert toml_content == content